feat: gateway catalog + per-harness wiring (UI + CLI)#11
Merged
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:catalogregenerates 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.mjsgenerates TS arrays from the JSON. Single source of truth, future agent-friendly.ProviderEntry.envKeysis now optional (Bedrock / Vertex use cloud creds).Wiring (per-harness)
src/main/fsutil.ts: atomic JSON / text writers withchmod 0600.src/main/wiring/claudeCode.ts: mergeenvinto~/.claude/settings.jsonpreserving everything else. UsesANTHROPIC_BASE_URL+ANTHROPIC_AUTH_TOKENfor non-Anthropic providers too.src/main/wiring/codex.ts: preserves existing TOML, upserts a[model_providers.<id>]block +model/providerlines.src/main/wiring/openCode.ts: merges aprovider.<id>block into~/.config/opencode/opencode.json, preserves existing ones, sets top-levelmodel.src/main/wiring/index.ts:applyWiringorchestrator routes to the right writer by harness id and returns per-harness results.src/main/gateways/resolve.ts:selectOpenAIEndpoint/selectAnthropicEndpointpick per-provider-per-gateway;unresolvedPlaceholdersflags<account_id>-style gaps.IPC
gateway:listreturns the catalog withplaceholdersflagged.gateway:applyvalidates placeholders, computes the effective base URL per provider (e.g.https://gateway.truefoundry.ai/proxy/v1/messageswhen provider is Anthropic), runsapplyWiringfor each selected harness, returns per-harnessHarnessWiringResult[].harness:configShowreads and excerpts the config file so the UI can show what's there.UI
src/renderer/App.tsxGatewayStep 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 listnow also lists gatewayshoist gateway listshows per-gateway auth, endpoints, placeholders, noteshoist 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 confighoist harness config [id]— shows path + 600-byte excerpt of the config fileThe CLI imports the same
src/main/providers/catalog,src/main/gateways, andsrc/main/wiringmodules 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)security add-generic-password)<account_id>, Apply wired Claude Code / OpenCode / Codex with their env hintsOut of scope (next iteration)
notealready tells the user the export they should run)gen:catalogscript should commitcatalog.generated.tsfrom CI so PRs that forget to regenerate stay green (separate task)