No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Stenn Kool 4353a0cf6e Add opt-in --use-credentials flag to authenticate /models fetches
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.
2026-08-02 14:42:26 +02:00
internal Add opt-in --use-credentials flag to authenticate /models fetches 2026-08-02 14:42:26 +02:00
.gitignore Rewrite README, fix module path, drop committed binary 2026-08-01 12:27:17 +02:00
AGENTS.md Add opt-in --use-credentials flag to authenticate /models fetches 2026-08-02 14:42:26 +02:00
go.mod Add opt-in --use-credentials flag to authenticate /models fetches 2026-08-02 14:42:26 +02:00
go.sum Add opt-in --use-credentials flag to authenticate /models fetches 2026-08-02 14:42:26 +02:00
main.go Add opt-in --use-credentials flag to authenticate /models fetches 2026-08-02 14:42:26 +02:00
README.md Add opt-in --use-credentials flag to authenticate /models fetches 2026-08-02 14:42:26 +02:00
Taskfile.yml Initial commit 2026-05-23 02:16:27 +02:00

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:

  1. Reads the providers you already have configured in Zed's settings.json.
  2. Calls each provider's <api_url>/models endpoint to get the live catalog.
  3. Keeps only text-output models (drops image / audio / video generators).
  4. Maps each model to Zed's available_models format — context length, output tokens, and capabilities (tools, vision input, reasoning) inferred from the API metadata.
  5. 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 the openai_compatible subtree is touched; the rest of your config is carried through unchanged.
  • Filtering — a model is kept if every entry in its output_modalities is text (or if no modalities are listed, in which case it's assumed text-only).
  • Capability inferencetools / parallel_tool_calls come from a tools supported parameter, images from an image input modality, and reasoning support from reasoning / include_reasoning parameters. Reasoning-capable models get a default reasoning_effort of high.
  • Authentication — by default requests are unauthenticated. Pass --use-credentials to resolve each provider's API key exactly as Zed does and send it as an Authorization: Bearer header.
  • 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:

  1. Environment variable<PROVIDER_NAME>_API_KEY in UpperSnake case (e.g. SYNTHETIC_API_KEY, CROF_AI_API_KEY).
  2. OS credential store — keyed by the provider's full api_url:
    • macOS Keychain — internet-password item (security find-internet-password -s <url>).
    • Linux Secret Servicesecret-tool lookup url <url>.
    • Windows Credential Managerzed:url=<url> (via wincred).

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 /models requests go out without an Authorization header; providers that require a key just to list models are skipped with an HTTP error and reported under "Skipped". Pass --use-credentials to authenticate with the keys Zed already stores (see Authentication). OpenRouter, Kilo, and Synthetic all expose /models unauthenticated, so they work out of the box.
  • A failed provider never blocks the others. It's recorded in the Skipped list and the run continues.
  • JSONC support is limited to trailing commas// line comments are not stripped and will cause a parse error.
  • settings.json must 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