Skip to content

Commit 5e1aa49

Browse files
quickbeardclaude
andcommitted
feat(gateway): move the gateway API key out of codev.json
The SSO and manual-key configure flows (TUI and desktop) wrote the gateway API key inline as provider.netgate.options.apiKey in the global codev.json, where it is trivially copied into other tools. The key now goes into opencode's auth.json (registry merge by provider id, the same mechanism the custom-provider flow already used) and the config block stays keyless. - buildGatewayConfig: apiKey is optional and omitted from the block unless passed; the sandbox guest config still passes its per-start broker placeholder. - TUI runModelFlow and the desktop configure gate store the key via the local server's auth.set before patching the config; the desktop controller returns the key beside the keyless config for that. - One-time startup migration in packages/opencode moves inline keys for the CoDev candidate provider ids (netgate, ai-gateway, aigateway, plus the hub's saved manual-provider id) into the auth store and scrubs them from codev.json(c), preserving comments and formatting and leaving {env:...}/{file:...} indirections alone. Ordering is auth-store first so a failed scrub can never lose the credential, and an old hub that re-inlines the key on refresh is re-migrated on the next launch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent c17298c commit 5e1aa49

14 files changed

Lines changed: 357 additions & 16 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,23 @@ already in use` and no way forward short of finding the file in a temporary
237237
directory and deleting it. That leftover is now cleared on the next start.
238238
([#248](https://github.com/quickbeard/codev-code/pull/248))
239239

240+
### Provider setup
241+
242+
- **The gateway API key no longer sits in codev.json** — every configure flow
243+
(the TUI and desktop SSO login, the manual-key fallback) wrote the key
244+
in plain sight into the global `codev.json`, the same file people open to
245+
tweak settings, copy between machines, and paste into bug reports. The key
246+
now goes where custom-provider credentials already went: the credential
247+
store (`auth.json`, readable only by your user account), and the provider
248+
block in `codev.json` stays keyless — CoDev Code merges the credential back
249+
in by provider id when it loads. Existing installs migrate themselves on the
250+
next launch: the inline key is moved into the credential store and scrubbed
251+
from the config file, comments and formatting intact, with `{env:...}` /
252+
`{file:...}` indirections deliberately left alone. Configs written by an
253+
older CoDev Hub are re-migrated the same way whenever the hub re-inlines a
254+
key.
255+
([#286](https://github.com/quickbeard/codev-code/pull/286))
256+
240257
### Desktop / Web UI
241258

242259
- **No more "Could not reach Local Server" on corporate proxies** — on

‎DIVERGENCES.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ from the squash message first, or use a plain merge.
9393
| `packages/core/src/global.ts`, `packages/core/test/global.test.ts`, `packages/opencode/test/cli/mcp-add.test.ts` | `const app = "opencode"` → `"codev"`; test expectations `tmpdir()/opencode` → `tmpdir()/codev` and `.config/opencode/` → `.config/codev/` | Config/data/cache/state dirs under `~/.config/codev` etc.; upstream tests assert the literal dir name. | Keep ours (the one constant + expectations); take upstream's surrounding changes. |
9494
| `packages/opencode/src/index.ts`, `packages/opencode/test/cli/error.test.ts`, `packages/opencode/test/cli/help/help-snapshots.test.ts`, `packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap` | `scriptName("opencode")` → `"codev"`; `show()` prefix check `"opencode "` → `"codev "`; registered the fork-only `ConfigureCommand` (cli/cmd/configure.ts, new file) and `CodegraphCommand` (cli/cmd/codegraph.ts, new file) and added `configure` + `codegraph` to the help-snapshot TOP_LEVEL list; test expectation `` `opencode models` `` → `` `codev models` ``; help snapshots regenerated so usage lines read `codev …` | Help/usage text shows the real user-facing command; `codev configure` is the standalone gateway-setup entry point, and `codev codegraph` is the standalone CodeGraph MCP wiring for installs not managed by CoDev Hub. The `show()` check must always match the scriptName; upstream's error-format test and help snapshots assert the literal command. | Keep our strings, command registration + expectation; take upstream's surrounding changes. Regenerate the snapshot file with `bun test test/cli/help --update-snapshots` instead of hand-merging. |
9595
| `package.json` (root) | `name` `opencode` → `codev-code`; `repository.url` → `github.com/quickbeard/codev-code`; added `release` script delegating to packages/opencode; added `bump` script pointing at the fork-only `script/bump-version.ts`; removed the `sso`, `dev:stats`, and `random` scripts; added the `lint-staged` devDependency and a `lint-staged` config block (`"*": "prettier --write --ignore-unknown"`) driving the fork-only `.husky/pre-commit` | The root manifest carries the fork's name (nothing reads it — root is private and unpublished; `packages/script` reads only `packageManager` — so this is cosmetic and also removes the name collision with the `packages/opencode` workspace member, whose name MUST stay `opencode`: build.ts derives bun compile targets and dist dir names from it). `bun run release` and `bun run bump` work from the repo root. `sso` logs into upstream's AWS org (`--sso-session=opencode`) and `dev:stats` runs inside an sst shell against upstream's production stage — neither works without anomalyco credentials; `random` is inert cruft (`echo 'Random script'`). `lint-staged` keeps staged files at Prettier's fixed point on commit, so the formatting drift PR #261 had to clean up cannot silently reaccumulate; `--ignore-unknown` skips files Prettier has no parser for (`bun.lock`, `.husky/*`, binaries) and `.prettierignore` still exempts `DIVERGENCES.md` and the docs merge base. | Keep our name + release/bump lines and the removals (drop any new upstream-infra-only scripts too); take upstream's other fields. |
96-
| `packages/opencode/package.json` | bin key `opencode` → `codev` (shim path `./bin/opencode` unchanged); added `release` script; added dependency `"@codev/brand": "workspace:*"` | Installed command is `codev` — matches the CLI's `scriptName` and help text (the hub's command is `codevhub`; it relinquished the `codev` bin in codev-ai ≥ 0.4.0). `bun run release` publishes the version in this file (upstream-synced; script/release.ts is fork-only). | Keep our bin key and release line; take upstream's other fields including `version`. |
96+
| `packages/opencode/package.json` | bin key `opencode` → `codev` (shim path `./bin/opencode` unchanged); added `release` script; added dependencies `"@codev/brand": "workspace:*"` and `"@codev/gateway": "workspace:*"` (the latter for the gateway-key migration's shared-auth-file read, see the config.ts migration row) | Installed command is `codev` — matches the CLI's `scriptName` and help text (the hub's command is `codevhub`; it relinquished the `codev` bin in codev-ai ≥ 0.4.0). `bun run release` publishes the version in this file (upstream-synced; script/release.ts is fork-only). | Keep our bin key, release line, and dependency lines; take upstream's other fields including `version`. |
9797
| `bun.lock` | Mirrors the bin key rename above, the fork-only workspace packages/deps (`@codev/gateway`, `@codev/brand`), and the fork-only `lint-staged` devDependency | Lockfile records workspace bin mappings and workspace deps. Known quirk: the root workspace stanza still says `"name": "opencode"` — bun never syncs the root manifest's name into an existing lock (verified: neither `bun install` nor `--lockfile-only` rewrites it); it self-corrects only on a from-scratch regeneration and is harmless meanwhile. Don't hand-edit it. | Regenerate with `bun install`; never hand-merge. |
9898
| `.prettierignore` | Appended `DIVERGENCES.md` and `packages/web/src/content/docs/` (each with a comment saying why) | Prettier realigns every row of this file's changed-files table, so a one-row edit churns all ~120 rows — noise in the one file that records upstream-conflict guidance, and a guaranteed conflict on the next sync. Nothing in the repo formatted it before (the pre-push hook only runs `bun typecheck`), so this pins the status quo rather than changing it. The docs directory is the upstream merge base (see `packages/web/FORK-DOCS.md`): nothing builds it, its whole job is to surface upstream doc diffs during a sync, and formatting it only adds conflict surface — plus Prettier's MDX printer mangles `index.mdx`, collapsing the fenced code blocks inside `<TabItem>` into inline text. | Keep our lines; take upstream's other entries. |
9999
| `packages/tui/package.json` | Added dependencies `"@codev/gateway": "workspace:*"` and `"@codev/brand": "workspace:*"` | The fork's gateway configure dialog imports SSO/backend/config-block logic from the fork-only `packages/codev-gateway`; the "Open docs" command takes its URL from `DOCS_URL` in the fork-only `packages/codev-brand` instead of hardcoding the host. | Keep our dependency lines; take upstream's other fields. |
@@ -158,6 +158,7 @@ from the squash message first, or use a plain merge.
158158
| `packages/opencode/src/installation/index.ts` | Updater detects/queries/installs `codev-code` (npm-family); GitHub fallback → quickbeard/codev-code. `method()` drops upstream's `~/.local/bin` → `curl` check, keeping only `~/.opencode/bin`. The `curl` upgrade branch refuses instead of running upstream's install script: `upgradeCurl` + `upgradeScriptShell` + the `Stream` import are removed, and `case "curl"` returns `UpgradeFailedError` pointing at `npm i -g codev-code` | `codev-code upgrade` and the update notification target OUR npm package, never stock opencode. brew/choco/scoop probes never match (no such packages). A `curl`-detected binary (in `~/.opencode/bin`) is a stock opencode install; piping upstream's `opencode.ai/install` script would silently replace the fork with stock opencode. `~/.local/bin` is dropped because it's also a common npm/bun global-bin prefix, so a legitimate package-manager install there was misdetected as curl (now falls through to the manager probes). | Keep our package names, the `~/.local/bin` removal, and the curl refusal; keep `upgradeCurl`/`upgradeScriptShell`/`Stream` deleted (take upstream's changes elsewhere). |
159159
| `packages/opencode/src/cli/cmd/uninstall.ts` | npm-family uninstall commands (display + exec maps) `opencode-ai` → `codev-code` | `codev uninstall` must remove the fork's actual npm package; the old name was a silent no-op for every npm-installed fork user. NOT renamed: brew/choco/scoop package names (`opencode` — the fork publishes no such formula/package, and `method()` never resolves to those for a fork install) and the curl `.opencode/bin` + `# opencode` shell markers (they match upstream install-script artifacts, not the fork's). | Keep our `codev-code` names; take upstream's surrounding changes. |
160160
| `packages/opencode/src/config/config.ts` | `share` defaults to `"disabled"` when unset | Sharing publishes sessions to upstream's share service; internal users must opt in via config. | Keep our default block. |
161+
| `packages/opencode/src/config/config.ts` | Config layer init calls `ConfigGatewayKeyMigration.migrateInlineGatewayKeys(authSvc, fs)` (fork-only `src/config/gateway-key-migration.ts`, covered by fork-only `test/config/gateway-key-migration.test.ts`) before the first global-config load, wrapped in `Effect.catch` → log-warning so a failure never blocks startup | One-time migration for configs written before the keyless gateway block: older writers (the codev hub, this fork's configure flows) inlined the gateway API key as `provider.<id>.options.apiKey` in the global codev.json(c); the credential now lives in opencode's auth.json (registry merge by provider id). Beyond hygiene, an inline key outranks the auth-store key at SDK construction, so a stale leftover would pin requests to a rotated-out credential; an old hub that re-inlines the key on refresh is simply re-migrated next launch. Must run before `loadGlobal` first caches the file. | Keep the import + call (and its placement before the cached global load); take upstream's surrounding layer changes. |
161162
| Config-file rename: `packages/opencode/src/config/{config,managed,tui-migrate}.ts`, `packages/core/src/config.ts`, `packages/opencode/src/plugin/install.ts`, `packages/opencode/src/cli/cmd/{mcp,plug,providers,error}.ts`, user-facing strings (`packages/{tui,app}` dialogs/tips/i18n, skill descriptions in `packages/core/src/plugin/skill.ts` + `packages/opencode/src/skill/index.ts`), bundled prompts (`customize-opencode.md`, `initialize.txt`), tests seeding config fixtures, docs (`packages/web/src/content/docs`), stragglers (`.gitignore` root ignore entry, `packages/tui/src/component/dialog-provider.tsx` credential-dialog strings, `packages/tui/src/util/error.ts` model-not-found hint, `packages/opencode/script/run-workspace-server` header comment, `specs/v2/config.md` discovery prose) | Config file `opencode.json(c)` → `codev.json(c)` everywhere (global, project walk-up, config dirs, managed); global `config.json` support and the legacy TOML `config` migration removed; managed config dir `/Library/Application Support/opencode`→`…/codev`, `/etc/opencode`→`/etc/codev`, `%ProgramData%\opencode`→`…\codev` | The fork's config surface uses the fork's name; hard rename, no legacy fallback. NOT renamed: the `$schema` URL `https://opencode.ai/config.json`, `@opencode-ai/*` packages, theme asset files named `opencode.json`, brew/scoop manifest URLs. | Keep our file names; take upstream's surrounding logic. Rename any new upstream `opencode.json`/global-`config.json` reference to `codev.json` (watch the not-renamed list). |
162163
| `.opencode/` (repo root, whole directory) | Renamed to `.codev/`; inner `opencode.jsonc` → `codev.jsonc`; internal `.opencode/` path mentions in `glossary/README.md`, `skills/effect/SKILL.md`, `command/translate.md` updated; the `opencode-local` reference → `codev-local` pointing at `~/.local/share/codev` | This is the repo's own dogfooding config dir; after the config-dir rename row below, the agent only discovers `.codev/`. | Keep our directory; re-apply upstream's `.opencode/**` changes into `.codev/` (sync surfaces them as modify/delete or add conflicts). |
163164
| Config-dir rename: `packages/opencode/src/config/{paths,config,tui}.ts`, `packages/core/src/config.ts`, `packages/opencode/src/plugin/install.ts`, `packages/opencode/src/plugin/tui/runtime.ts`, `packages/tui/src/context/theme.tsx`, `packages/opencode/src/cli/cmd/{agent,mcp}.ts`, `packages/opencode/src/session/session.ts`, plan permission patterns (`packages/opencode/src/agent/agent.ts`, `packages/core/src/plugin/agent.ts`), TUI tips, skill descriptions, `customize-opencode.md`, `specs/tui-plugins.md`, tests creating `.opencode` fixtures, docs (en + es) | Config directory `.opencode` → `.codev` (project walk-up, `~/.codev`, plans dir, theme/plugin discovery) | The fork's config surface uses the fork's name; hard rename, no legacy fallback. NOT renamed: `~/.opencode/bin` install paths (binary location — see installation/uninstall rows), the `ai.opencode.managed` plist domain, identifiers like provider id `opencode` and theme asset keys. | Keep our dir name; take upstream's surrounding logic. Rename any new upstream `.opencode` dir reference to `.codev` (leave `~/.opencode/bin`). |

‎bun.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/app/src/components/gateway/dialog-configure.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ export function DialogConfigure() {
115115
try {
116116
const result = await sso.complete(model)
117117
const client = serverSDK().client
118+
// The key goes into opencode's auth.json (registry merge by provider id,
119+
// same as the custom-provider dialog) and the config block stays keyless
120+
// — codev.json must not carry the credential. Auth first: a keyless
121+
// config with no auth entry is a provider that 401s on the first chat.
122+
await client.auth.set({ providerID: result.providerID, auth: { type: "api", key: result.apiKey } })
118123
// Raw update instead of serverSync().updateConfig: its onSuccess refetch
119124
// races the forked server-side instance reload. The explicit dispose
120125
// below emits global.disposed, which refreshes every window (mirrors the

‎packages/app/src/gateway/types.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ export type GatewaySetupResult = {
3434

3535
export type GatewayCompleteResult = {
3636
providerID: string
37-
// The buildGatewayConfig fragment the renderer PATCHes into the global
38-
// config; the API key crosses IPC only inside this block.
37+
// The gateway credential the renderer stores via the local server's auth API
38+
// (auth.json registry merge by provider id) before patching the config.
39+
apiKey: string
40+
// The keyless buildGatewayConfig fragment the renderer PATCHes into the
41+
// global config — codev.json must not carry the credential.
3942
config: Record<string, unknown>
4043
}
4144

‎packages/codev-gateway/src/config-block.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ const MODEL_MODALITIES: { attachment: boolean; modalities: { input: Modality[];
3737
// hub seeds the same recents file when it is empty (hub PR #229 — its map
3838
// order never carried the choice). A config written by an older hub still
3939
// carries a pin — nothing here removes one.
40-
export function buildGatewayConfig(input: { apiKey: string; baseUrl: string; model: string; models?: string[] }) {
40+
//
41+
// `apiKey` is normally omitted: the credential lives in opencode's auth.json
42+
// under the provider id and the registry merges it into `options.apiKey` at
43+
// load (same mechanism as buildCustomProviderConfig), keeping the key out of
44+
// the world-readable config file. The sandbox guest config is the one caller
45+
// that still passes a key — a per-start broker placeholder, never the real
46+
// gateway credential (the host egress broker injects that on the way out).
47+
export function buildGatewayConfig(input: { apiKey?: string; baseUrl: string; model: string; models?: string[] }) {
4148
const ids = [...new Set([input.model, ...(input.models ?? [])])]
4249
return {
4350
compaction: {
@@ -50,7 +57,7 @@ export function buildGatewayConfig(input: { apiKey: string; baseUrl: string; mod
5057
name: GATEWAY_PROVIDER.name,
5158
options: {
5259
baseURL: normalizeGatewayBaseUrl(input.baseUrl),
53-
apiKey: input.apiKey,
60+
...(input.apiKey === undefined ? {} : { apiKey: input.apiKey }),
5461
},
5562
models: Object.fromEntries(
5663
ids.map((id) => [

0 commit comments

Comments
 (0)