Skip to content

feat: gateway catalog + per-harness wiring (UI + CLI)#11

Merged
johnnyhuy merged 6 commits into
mainfrom
feat/catalog-generator-and-types
Jul 19, 2026
Merged

feat: gateway catalog + per-harness wiring (UI + CLI)#11
johnnyhuy merged 6 commits into
mainfrom
feat/catalog-generator-and-types

Conversation

@johnnyhuy

Copy link
Copy Markdown
Contributor

Summary

Brings Hoist's gateway model to life — cataloging the major AI gateways we found across the OpenCode / TrueFoundry / LiteLLM docs, and wiring them into Claude Code, Codex, and OpenCode from a single wizard step (or single CLI command).

Catalog (ADR-0002)

  • src/main/providers/catalog.source.json → 18 providers, edited as JSON. npm run gen:catalog regenerates the typed TS.
  • src/main/gateways/catalog.source.json → 10 gateways with auth headers, endpoint paths, and per-gateway notes (TrueFoundry, LiteLLM, Cloudflare, Vercel, OpenRouter, Together AI, OpenCode Zen, Zenlayer, plus 2 custom slots).
  • scripts/gen-catalog.mjs generates TS arrays from the JSON. Single source of truth, future agent-friendly.
  • ProviderEntry.envKeys is now optional (Bedrock / Vertex use cloud creds).

Wiring (per-harness)

  • src/main/fsutil.ts: atomic JSON / text writers with chmod 0600.
  • src/main/wiring/claudeCode.ts: merge env into ~/.claude/settings.json preserving everything else. Uses ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN for non-Anthropic providers too.
  • src/main/wiring/codex.ts: preserves existing TOML, upserts a [model_providers.<id>] block + model / provider lines.
  • src/main/wiring/openCode.ts: merges a provider.<id> block into ~/.config/opencode/opencode.json, preserves existing ones, sets top-level model.
  • src/main/wiring/index.ts: applyWiring orchestrator routes to the right writer by harness id and returns per-harness results.
  • src/main/gateways/resolve.ts: selectOpenAIEndpoint / selectAnthropicEndpoint pick per-provider-per-gateway; unresolvedPlaceholders flags <account_id>-style gaps.

IPC

  • gateway:list returns the catalog with placeholders flagged.
  • gateway:apply validates placeholders, computes the effective base URL per provider (e.g. https://gateway.truefoundry.ai/proxy/v1/messages when provider is Anthropic), runs applyWiring for each selected harness, returns per-harness HarnessWiringResult[].
  • harness:configShow reads and excerpts the config file so the UI can show what's there.
  • Provider list now backs the Keys step too.

UI

  • src/renderer/App.tsx GatewayStep rewritten as a 3-section form: gateway picker (Direct + 6 featured cards), provider + base URL + key, harness checkboxes. Apply surfaces per-harness result rows with envHint code blocks. Cloudflare's <account_id> placeholder warns and Apply refuses un-substituted URLs.

CLI

  • hoist list now also lists gateways
  • hoist gateway list shows per-gateway auth, endpoints, placeholders, notes
  • hoist gateway use <id> -p anthropic -u <url> -k TOKEN [-H installed] [--dry-run] — picks the right endpoint per provider, prompts for key if missing, writes each harness's config
  • hoist harness config [id] — shows path + 600-byte excerpt of the config file

The CLI imports the same src/main/providers/catalog, src/main/gateways, and src/main/wiring modules the Electron app uses — single set of catalog data, single set of writers, only the entrypoint differs.

Verification

  • npm run typecheck
  • npm run lint
  • npm run build ✓ (renderer 213 kB)
  • bun run cli → bun build --compile ✓ (Mach-O arm64, 30 modules)
  • Real keychain round-trip on macOS confirmed end-to-end (CLI uses security add-generic-password)
  • Screenshot above shows Cloudflare gateway selected, base URL with <account_id>, Apply wired Claude Code / OpenCode / Codex with their env hints

Out of scope (next iteration)

  • Bedrock / Vertex probes (cloud-cred providers without a probe endpoint — we'll model those on an IAM check)
  • OpenCode Native plugin for auto-discovery
  • Applying the same effective-base-URL export to other harnesses (the note already tells the user the export they should run)
  • The gen:catalog script should commit catalog.generated.ts from CI so PRs that forget to regenerate stay green (separate task)

Main had a stale lockfile after introducing the `which` dependency in
the previous PR. Regenerated from scratch so `npm ci` works in CI.
…generator

- src/main/providers/types.ts: canonical ProviderEntry shape (envKeys now optional for cloud-cred providers like Bedrock and Vertex)
- src/main/providers/catalog.source.json: data file for the catalog (Anthropic, OpenAI, Google Gemini, Vertex, Bedrock, Azure OpenAI, Azure AI Foundry, Groq, Mistral, Cohere, Together AI, Fireworks, DeepSeek, xAI, OpenRouter, Cerebras, SambaNova, custom OpenAI-compatible)
- src/main/providers/catalog.generated.ts: emitted by scripts/gen-catalog.mjs; do not edit by hand
- src/main/providers/catalog.ts: now a thin re-export of the generated catalog + findProvider
- scripts/gen-catalog.mjs: emits typed array literals from JSON, future-proofs additions per ADR-0002
- package.json: gen:catalog + prebuild/prebuild:renderer hooks; bun.lock excluded

Future catalog edits go in catalog.source.json, then `npm run gen:catalog` regenerates the TS.
- src/main/gateways/types.ts: GatewayEntry, GatewayAuth, GatewayEndpoints, GatewayAuthScheme types
- src/main/gateways/resolve.ts: selectOpenAIEndpoint / selectAnthropicEndpoint picks the right path per provider probeKind; unresolvedPlaceholders flags <account_id>-style gaps in user-supplied URLs
- src/main/gateways/catalog.source.json: TrueFoundry, LiteLLM, Cloudflare AI Gateway, Vercel AI Gateway, OpenRouter, Together AI, OpenCode Zen, Zenlayer, two Anthropic/OpenAI-compatible custom slots
- src/main/gateways/catalog.generated.ts: regenerated by gen:catalog
- src/main/gateways/index.ts: re-exports + findGateway

Determined from the docs of OpenCode, TrueFoundry, and LiteLLM: each has a different "pinned" Anthropic endpoint shape (some proxy, some passthrough), so a small resolver lives here and the IPC chooses per-provider.

Same gen:catalog flow as the providers catalog.
- src/main/fsutil.ts: writeJsonAtomic + writeTextAtomic — atomic temp-file + rename + chmod 0600 helpers
- src/main/wiring/claudeCode.ts: merge env into ~/.claude/settings.json preserving unrelated blocks; clears hoist-managed keys when needed
- src/main/wiring/codex.ts: minimal-preserve in-place writer for ~/.codex/config.toml; upserts [model_providers.<id>] block + model line
- src/main/wiring/openCode.ts: merge provider.<id> block into ~/.config/opencode/opencode.json preserving existing providers
- src/main/wiring/index.ts: applyWiring orchestrator routes to the right writer by harness id

- src/shared/channels.ts: add gatewayList, gatewayApply, harnessConfigShow channels
- src/preload/api.ts: typed HoistAPI now exposes harness.configShow, gateway.list, gateway.apply; VaultEntry/HarnessConfigView/HarnessWiringResult etc.
- src/main/ipc.ts: handler for gatewayList (returns catalog + placeholders), gatewayApply (validates placeholders, computes effective base URL, calls applyWiring per selected harness, returns HarnessWiringResult[]), harnessConfigShow (reads + excerpts the right config file)

Implementation is fully in-process (no shell-out). All writes pass through fsutil so power-loss won't corrupt configs.
- src/renderer/App.tsx GatewayStep rewritten:
  - 7-cell gateway picker (Direct + 6 featured gateways from the catalog): Cloudflare, TrueFoundry, LiteLLM, Vercel, OpenRouter, OpenCode Zen
  - Placeholder-aware hints (Cloudflare's <account_id> warns before submit, apply refuses un-substituted URLs)
  - Provider dropdown wired to PROVIDER_CATALOG (18 entries); picks Anthropic natively
  - Base URL field auto-fills from gateway or provider; respected host hint per gateway
  - Harness checkboxes pre-check installed ones; Codex un-instanced stays off
  - Apply button invokes gateway.apply via IPC; renders per-harness result rows with envHint code blocks + notes (e.g. "OpenCode reads opencode.json on next start.")
  - Effective base URL surfaced after apply so the user can verify what landed in each harness

Layout collapses cleanly at 1280×800; the older Provider / SSO step is unchanged.
- cli/src/lib/providers.ts: thin re-export of src/main providers + gateways catalogs; exposes listProviders, listGateways; the binary now pulls the same JSON-derived catalogs
- cli/src/lib/wiring.ts: thin re-export of applyWiring from src/main/wiring so the binary edits ~/.claude/settings.json, ~/.codex/config.toml, ~/.config/opencode/opencode.json
- cli/src/lib/harnesses.ts: same discoverHarness/installHarness as before (no API keys live in code, only in OS keychain via vault.ts)
- cli/src/lib/probes.ts: provider-driven probe dispatch (only Anthropic implemented today)
- cli/src/index.ts: new commands
    hoist list                         # now also lists gateways
    hoist gateway list                 # full per-gateway detail incl placeholders + auth + endpoints
    hoist gateway use <id> [-p anthropic] [-u https://…] [-k TOKEN] [-H "claude-code,opencode,codex"] [--dry-run]
                                       # validates placeholders, picks the right endpoint per provider, prompts for key if missing, writes each harness
    hoist harness list                 # alias of catalog
    hoist harness config [id]          # show path + 600-byte excerpt of the config file
- cli/tsconfig.json: includes ../src/main + ../src/shared so the catalog JSON sources resolve and TypeScript sees the generated files
- cli/scripts/build-all.ts: deleted (was a no-op shell wrapper; bun build --compile directly from src/index.ts works)

Verified end-to-end against the user's machine:
  $ hoist gateway list                  # 10 gateways
  $ hoist gateway use truefoundry --dry-run --provider openai --api-key TEST
  → Effective base URL: https://gateway.truefoundry.ai
  $ hoist gateway use cloudflare-ai-gateway --dry-run --base-url https://gateway.ai.cloudflare.com/v1/111122223333 --api-key TEST
  → Effective base URL: https://gateway.ai.cloudflare.com/v1/111122223333/
  $ hoist gateway use cloudflare-ai-gateway --dry-run --api-key TEST
  → Base URL still has placeholders: <account_id>
  $ hoist harness config claude-code
  — claude-code  /Users/johnny/.claude/settings.json  …
@johnnyhuy
johnnyhuy merged commit bada08a into main Jul 19, 2026
5 of 6 checks passed
@johnnyhuy
johnnyhuy deleted the feat/catalog-generator-and-types branch July 19, 2026 04:07
johnnyhuy added a commit that referenced this pull request Jul 19, 2026
…ules (#12)

The cli/tsconfig.json previously globbed `../src/main/**`, which dragged
electron- and `which`-dependent files into the cli typecheck. The cli CI
step had no node_modules for those, so it failed after PR #11 merged.

The cli only needs:
- src/main/{providers,gateways,wiring,fsutil} — no electron
- src/shared/{index,channels,types}

Switched to an explicit include list and dropped the bun-types fallback.
`bun run cli → bun build --compile` still passes, runtime unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant