- Go 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Resolves API keys the same way Zed does: <PROVIDER_NAME>_API_KEY env var first, then the OS credential store keyed by the provider's api_url (macOS Keychain via security CLI, Linux Secret Service via secret-tool, Windows Credential Manager via wincred). Keys are sent as Bearer tokens; lookup failures fall back to unauthenticated fetch. |
||
| internal | ||
| .gitignore | ||
| AGENTS.md | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| README.md | ||
| Taskfile.yml | ||
zeddiscover
Keep the Zed editor's language-model list in sync with live provider /models endpoints — automatically.
What it does
The Zed editor can talk to any OpenAI-compatible API via
language_models.openai_compatible in ~/.config/zed/settings.json. The catch: every
model you want in the picker must be declared by hand as an available_models entry —
name, context length, output-token limit, capabilities, and so on.
Gateways like OpenRouter, Kilo, and Synthetic serve hundreds of models that rotate constantly. Maintaining that list manually is tedious, easy to get wrong, and always out of date.
zeddiscover fixes this. It:
- Reads the providers you already have configured in Zed's
settings.json. - Calls each provider's
<api_url>/modelsendpoint to get the live catalog. - Keeps only text-output models (drops image / audio / video generators).
- Maps each model to Zed's
available_modelsformat — context length, output tokens, and capabilities (tools, vision input, reasoning) inferred from the API metadata. - Backs up your config and writes the updated list back.
Point it at a cron job and your Zed model picker stays current forever.
Install
Requires Go 1.21+.
git clone https://git.gitbuild.dev/StennMedia/zeddiscover.git
cd zeddiscover
# Build the binary
task build # or: go build -o zeddiscover .
# Build and install to ~/.local/bin
task install
Usage
zeddiscover # sync every configured provider (writes to disk)
zeddiscover --dry-run # preview changes; does not touch the file
zeddiscover --provider kilo # sync only the provider named "kilo"
zeddiscover --use-credentials # authenticate /models requests with Zed's API keys
zeddiscover --config /path/to/settings.json # use a custom config file
Without an installed binary, run from the repo:
go run . # sync all providers
go run . --dry-run # preview only
go run . --provider kilo # single provider
go run . --use-credentials # authenticate /models requests
Flags
| Flag | Default | Description |
|---|---|---|
--dry-run |
false |
Fetch and report, but write nothing to disk. |
--provider |
(all) | Sync only the named provider. |
--config |
~/.config/zed/settings.json |
Path to the Zed settings file. |
--use-credentials |
false |
Send Authorization: Bearer <key> on /models requests, using keys Zed stores (env var or OS credential store). |
Taskfile shortcuts
The repo uses Task (a YAML-based task runner). Install with
brew install go-task or go install github.com/go-task/task/v3/cmd/task@latest.
| Command | What it does |
|---|---|
task |
Dry-run sync of all providers (go run . --dry-run) |
task build |
Build the zeddiscover binary |
task install |
Build and install to ~/.local/bin |
task run -- --provider kilo |
Run with arbitrary arguments |
Configuring Zed
Just declare your providers with an api_url. You do not need to list any models —
zeddiscover fills in available_models for you.
{
"language_models": {
"openai_compatible": {
"kilo": {
"api_url": "https://api.kilo.ai/api/gateway"
},
"synthetic": {
"api_url": "https://api.synthetic.new/openai/v1"
}
}
}
}
Run zeddiscover, restart Zed, and the model picker is populated.
How it works
settings.json
└─ language_models.openai_compatible.<name>.api_url
│
▼
GET <api_url>/models (OpenRouter-style {"data": [...]} envelope)
│ └─ optional: Authorization: Bearer <key> (--use-credentials)
▼
filter: keep only text-output models
│
▼
map: APIModel ──▶ Zed available_model
(max_tokens, max_output_tokens, capabilities)
│
▼
back up settings.json → settings.json.bak, then write
- Reading — parses
settings.json, tolerating JSONC trailing commas (Zed allows them). Only theopenai_compatiblesubtree is touched; the rest of your config is carried through unchanged. - Filtering — a model is kept if every entry in its
output_modalitiesistext(or if no modalities are listed, in which case it's assumed text-only). - Capability inference —
tools/parallel_tool_callscome from atoolssupported parameter,imagesfrom animageinput modality, and reasoning support fromreasoning/include_reasoningparameters. Reasoning-capable models get a defaultreasoning_effortofhigh. - Authentication — by default requests are unauthenticated. Pass
--use-credentialsto resolve each provider's API key exactly as Zed does and send it as anAuthorization: Bearerheader. - Backup — before any write, the current file is copied to
settings.json.bak.
The mapper understands both the nested OpenRouter schema
(architecture.input_modalities, top_provider.max_completion_tokens) and flatter
provider variants (input_modalities, max_output_tokens, max_output_length).
Authentication
Some providers require an API key just to list /models. zeddiscover --use-credentials
reuses the credentials Zed already has, so there is nothing extra to configure.
Key lookup mirrors Zed's own order, per provider:
- Environment variable —
<PROVIDER_NAME>_API_KEYin UpperSnake case (e.g.SYNTHETIC_API_KEY,CROF_AI_API_KEY). - OS credential store — keyed by the provider's full
api_url:- macOS Keychain — internet-password item (
security find-internet-password -s <url>). - Linux Secret Service —
secret-tool lookup url <url>. - Windows Credential Manager —
zed:url=<url>(viawincred).
- macOS Keychain — internet-password item (
If the key resolves, it is sent as an Authorization: Bearer <key> header; if it
doesn't, the request falls back to unauthenticated. The first keychain/keyring access
may trigger an OS permission/unlock prompt (one per provider) — click "Always Allow"
once and it won't appear again.
Caveats
- Authentication is opt-in. By default
/modelsrequests go out without anAuthorizationheader; providers that require a key just to list models are skipped with an HTTP error and reported under "Skipped". Pass--use-credentialsto authenticate with the keys Zed already stores (see Authentication). OpenRouter, Kilo, and Synthetic all expose/modelsunauthenticated, so they work out of the box. - A failed provider never blocks the others. It's recorded in the
Skippedlist and the run continues. - JSONC support is limited to trailing commas —
//line comments are not stripped and will cause a parse error. settings.jsonmust already exist; the tool reads it before writing.
Project layout
main.go CLI flags, wires dependencies together
internal/
config/config.go Read/write settings.json, JSONC cleanup, .bak backup
model/types.go Domain types (AvailableModel, APIModel, Capabilities)
model/mapper.go Text-only filter + APIModel → AvailableModel mapping
provider/provider.go Fetcher interface
provider/openrouter.go OpenRouter/Kilo/Synthetic /models implementation
sync/syncer.go Runner: iterate providers, fetch, filter, write
auth/auth.go API key lookup (env var, then OS credential store)
auth/store_*.go Per-OS credential store backends (Keychain, Secret Service, …)
Everything lives under internal/ — the module is a self-contained binary, not a
library. Adding support for a differently-shaped provider means implementing the
provider.Fetcher interface.
License
MIT