@legioncodeinc/rflectr 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/cli.js +4 -1
  2. package/dist/cli.js.map +1 -0
  3. package/package.json +4 -1
  4. package/.markdown-link-check.json +0 -7
  5. package/AGENTS.md +0 -169
  6. package/assets/733630021_1421561133353555_3999689754075308337_n.jpg +0 -0
  7. package/assets/github-home-image.png +0 -0
  8. package/assets/og-image.jpg +0 -0
  9. package/assets/og-image.png +0 -0
  10. package/assets/og-image.psd +0 -0
  11. package/assets/rflectr-no-bg.png +0 -0
  12. package/assets/vertex-models.example.json +0 -14
  13. package/library/README.md +0 -39
  14. package/library/issues/README.md +0 -46
  15. package/library/issues/backlog/README.md +0 -26
  16. package/library/issues/completed/README.md +0 -13
  17. package/library/issues/in-work/README.md +0 -13
  18. package/library/knowledge/README.md +0 -34
  19. package/library/knowledge/private/README.md +0 -40
  20. package/library/knowledge/private/ai/README.md +0 -8
  21. package/library/knowledge/private/ai/model-discovery-classification.md +0 -81
  22. package/library/knowledge/private/ai/translation-layer.md +0 -88
  23. package/library/knowledge/private/architecture/README.md +0 -10
  24. package/library/knowledge/private/architecture/launch-flow-claude.md +0 -93
  25. package/library/knowledge/private/architecture/system-overview.md +0 -108
  26. package/library/knowledge/private/auth/README.md +0 -9
  27. package/library/knowledge/private/auth/oauth-device-flows.md +0 -95
  28. package/library/knowledge/private/data/README.md +0 -8
  29. package/library/knowledge/private/data/preferences-config.md +0 -87
  30. package/library/knowledge/private/data/provider-registry.md +0 -126
  31. package/library/knowledge/private/infrastructure/README.md +0 -7
  32. package/library/knowledge/private/infrastructure/server-gateway.md +0 -87
  33. package/library/knowledge/private/integrations/README.md +0 -8
  34. package/library/knowledge/private/integrations/harnesses.md +0 -87
  35. package/library/knowledge/private/integrations/local-proxy.md +0 -82
  36. package/library/knowledge/private/security/README.md +0 -9
  37. package/library/knowledge/private/security/credential-storage.md +0 -129
  38. package/library/knowledge/private/standards/documentation-framework.md +0 -154
  39. package/library/knowledge/public/README.md +0 -49
  40. package/library/knowledge/public/faqs/README.md +0 -7
  41. package/library/knowledge/public/faqs/troubleshooting.md +0 -92
  42. package/library/knowledge/public/guides/README.md +0 -13
  43. package/library/knowledge/public/guides/ai-agents.md +0 -273
  44. package/library/knowledge/public/guides/api-server.md +0 -108
  45. package/library/knowledge/public/guides/claude-desktop.md +0 -382
  46. package/library/knowledge/public/guides/codex.md +0 -296
  47. package/library/knowledge/public/guides/gemini-cli.md +0 -105
  48. package/library/knowledge/public/guides/model-compatibility.md +0 -80
  49. package/library/knowledge/public/guides/providers.md +0 -90
  50. package/library/knowledge/public/overview/README.md +0 -7
  51. package/library/knowledge/public/overview/what-is-rflectr.md +0 -71
  52. package/library/notes/README.md +0 -21
  53. package/library/requirements/README.md +0 -51
  54. package/library/requirements/backlog/README.md +0 -30
  55. package/library/requirements/completed/README.md +0 -14
  56. package/library/requirements/completed/prd-001-cli-core-launch-orchestration/prd-001-cli-core-launch-orchestration-index.md +0 -205
  57. package/library/requirements/completed/prd-001-cli-core-launch-orchestration/qa/.gitkeep +0 -0
  58. package/library/requirements/completed/prd-002-provider-registry/prd-002-provider-registry-index.md +0 -263
  59. package/library/requirements/completed/prd-002-provider-registry/qa/.gitkeep +0 -0
  60. package/library/requirements/completed/prd-003-model-discovery-classification/prd-003-model-discovery-classification-index.md +0 -260
  61. package/library/requirements/completed/prd-003-model-discovery-classification/qa/.gitkeep +0 -0
  62. package/library/requirements/completed/prd-004-translation-layer/prd-004-translation-layer-index.md +0 -196
  63. package/library/requirements/completed/prd-004-translation-layer/qa/.gitkeep +0 -0
  64. package/library/requirements/completed/prd-005-local-proxy-catalog-routing/prd-005-local-proxy-catalog-routing-index.md +0 -176
  65. package/library/requirements/completed/prd-005-local-proxy-catalog-routing/qa/.gitkeep +0 -0
  66. package/library/requirements/completed/prd-006-credential-storage/prd-006-credential-storage-index.md +0 -190
  67. package/library/requirements/completed/prd-006-credential-storage/qa/.gitkeep +0 -0
  68. package/library/requirements/completed/prd-007-oauth-device-flows/prd-007-oauth-device-flows-index.md +0 -208
  69. package/library/requirements/completed/prd-007-oauth-device-flows/qa/.gitkeep +0 -0
  70. package/library/requirements/completed/prd-008-preferences-tiers-favorites/prd-008-preferences-tiers-favorites-index.md +0 -249
  71. package/library/requirements/completed/prd-008-preferences-tiers-favorites/qa/.gitkeep +0 -0
  72. package/library/requirements/completed/prd-009-codex-integration/prd-009-codex-integration-index.md +0 -212
  73. package/library/requirements/completed/prd-009-codex-integration/qa/.gitkeep +0 -0
  74. package/library/requirements/completed/prd-010-gemini-cli-integration/prd-010-gemini-cli-integration-index.md +0 -211
  75. package/library/requirements/completed/prd-010-gemini-cli-integration/qa/.gitkeep +0 -0
  76. package/library/requirements/completed/prd-011-claude-desktop-integration/prd-011-claude-desktop-integration-index.md +0 -228
  77. package/library/requirements/completed/prd-011-claude-desktop-integration/qa/.gitkeep +0 -0
  78. package/library/requirements/completed/prd-012-server-gateway/prd-012-server-gateway-index.md +0 -356
  79. package/library/requirements/completed/prd-012-server-gateway/qa/.gitkeep +0 -0
  80. package/library/requirements/in-work/README.md +0 -19
  81. package/library/requirements/reports/README.md +0 -31
  82. package/scripts/refresh-models-dev-cache.mjs +0 -34
  83. package/test-proxy.ts +0 -19
  84. package/test-split.js +0 -1
@@ -1,249 +0,0 @@
1
- # PRD-008: Preferences, Subscription Tiers & Favorites *(Retroactive)*
2
-
3
- > **Status:** Shipped
4
- > **Priority:** —
5
- > **Effort:** —
6
- > **Written:** June 2026
7
- > **Retroactive:** Yes — written after implementation (rflectr v0.2.7).
8
- > **Source:** `src/config.ts`, `src/favorites.ts`, `src/favorites-resolver.ts`, `src/prompts.ts`
9
-
10
- ---
11
-
12
- ## Overview
13
-
14
- `rflectr` keeps a small, durable layer of user state so the CLI feels personal across sessions: the last backend/model/provider you launched, recently used models per provider, a curated list of favorite models for the mid-session `/model` switch menu, and per-command `server` settings. All of it lives in a single JSON file under a per-user app-home directory, resolved with an override hook and migrated transparently from two earlier locations.
15
-
16
- On top of that state sit three interaction systems:
17
-
18
- 1. **Preferences store** — read/write helpers around `~/.rflectr/config.json`, with legacy-path migration and a `--dry-run` write-skip contract.
19
- 2. **Subscription / Zen tier filtering** — a `RegistrySubscriptionFilter` (`free` / `zen` / `go`) carried on the OpenCode Zen registry stub that scopes which cloud models surface; seeded as `free` during first-run.
20
- 3. **Favorites** — a manager command (`rflectr models`) plus a resolver shared with the Codex catalog, capped at `MAX_MODEL_CATALOG` (20), and a large-catalog UX (search + pagination) that keeps long model lists navigable.
21
-
22
- This PRD documents what was built, grounded in the shipped code.
23
-
24
- ---
25
-
26
- ## What Was Built
27
-
28
- - A single app-home directory (`~/.rflectr`, override via `RFLECTR_HOME`) holding `config.json` and sibling files, created `0o700` / files `0o600` (`src/paths.ts:28`, `src/config.ts:63`).
29
- - `loadPreferences()` / `savePreferences()` with a shallow per-key merge so callers pass only what changed (`src/config.ts:67`, `src/config.ts:85`).
30
- - `recordLaunchSelection(agent, providerId, modelId, prefs)` — updates the per-agent `last*` fields and prepends to `recentModelsByProvider` (deduped, capped at 3) (`src/config.ts:101`).
31
- - Two-stage legacy migration: dotfile dir (`~/.opencode-starter`) then OS `conf` store, both one-directional and idempotent (`src/config.ts:17`, `src/config.ts:34`).
32
- - `lastProvider === 'opencode'` normalized to `'zen'` on read (`src/config.ts:70`).
33
- - Pure favorites algebra: `isFavorite`, `addFavorite` (duplicate / cap results), `removeFavorite` (`src/favorites.ts`).
34
- - The `rflectr models` favorites manager: list / add (global search or browse-by-provider, multi-select) / remove, single save on Done (`src/cli.ts:534`).
35
- - A surface-agnostic favorites resolver shared by Claude and Codex (`src/favorites-resolver.ts`).
36
- - Recent-models hint at the top of the local-model picker, with a "Browse all models →" escape hatch (`src/prompts.ts:305`).
37
- - Large-catalog UX: search at `MODEL_SEARCH_THRESHOLD = 25`, pagination at `MODEL_PAGE_SIZE = 15` (`src/prompts.ts:22`).
38
- - Inline, never-dead-end first-run wizard that ends in launch or explicit cancel (`src/first-run.ts:36`).
39
-
40
- ---
41
-
42
- ## Goals
43
-
44
- - Persist enough state that repeat launches pre-select the user's prior choice rather than starting cold.
45
- - Make a curated multi-model `/model` switch menu possible without re-picking every session.
46
- - Keep one canonical config location, surviving the project's two historical renames without user action.
47
- - Honor `--dry-run` as a true no-write simulation of a fresh first run.
48
- - Keep long provider model lists usable (search / paginate) instead of dumping 100+ entries into one prompt.
49
-
50
- ## Non-Goals
51
-
52
- - Touching `~/.claude/settings.json` — launch config is env-var-only (see [PRD-001](../prd-001-cli-core-launch-orchestration/prd-001-cli-core-launch-orchestration-index.md)).
53
- - Storing secrets in `config.json` — API keys live in the OS credential store ([PRD-006](../prd-006-credential-storage/) — folder pending). The server password is migrated *out* of `config.json` into the keyring on first read (`src/config.ts:131`).
54
- - Cloud-syncing preferences across machines (the file is per-user, per-host).
55
-
56
- ---
57
-
58
- ## Features
59
-
60
- | # | Feature | Entry point | Notes |
61
- |---|---------|-------------|-------|
62
- | F1 | App-home resolution + override | `getAppHome()` (`src/paths.ts:28`) | `RFLECTR_HOME` (or deprecated `OPENCODE_STARTER_HOME`) wins, else `~/.rflectr` |
63
- | F2 | Preferences load/save (shallow merge) | `loadPreferences` / `savePreferences` (`src/config.ts:67`) | Pass only changed keys |
64
- | F3 | Record launch selection | `recordLaunchSelection` (`src/config.ts:101`) | Per-agent `last*` + recent list (max 3, dedup) |
65
- | F4 | Legacy migration (2 stages) | `ensureConfigMigrated` (`src/config.ts:34`) | Dotfile dir → OS conf store; idempotent |
66
- | F5 | Subscription/Zen tier filter | `RegistrySubscriptionFilter` (`src/registry/types.ts:7`) | `free` / `zen` / `go` on the Zen stub |
67
- | F6 | Favorites algebra | `addFavorite` / `removeFavorite` (`src/favorites.ts`) | Cap at `MAX_MODEL_CATALOG` (20) |
68
- | F7 | Favorites manager command | `runModelsCommand` (`src/cli.ts:534`) | `rflectr models`; saves once on Done |
69
- | F8 | Favorites resolver (shared) | `resolveFavorite` / `buildFavoritesList` (`src/favorites-resolver.ts:52`) | Stale favorites dropped silently |
70
- | F9 | Recent-models picker | `pickLocalModel` (`src/prompts.ts:305`) | Recent shown first + "Browse all" |
71
- | F10 | Large-catalog search/pagination | `selectModelWithSearch` / `selectLargeCatalog` (`src/prompts.ts:246`) | Thresholds 25 / 15 |
72
- | F11 | First-run wizard | `runFirstRunWizard` (`src/first-run.ts:36`) | Zen quick-start / import / providers |
73
-
74
- ---
75
-
76
- ## Architecture & Implementation
77
-
78
- ### Config store schema
79
-
80
- `config.json` is parsed into `UserPreferences` (`src/types.ts:72`). Unknown keys are tolerated; everything is optional.
81
-
82
- | Field | Type | Purpose | Cite |
83
- |-------|------|---------|------|
84
- | `lastBackend` | `'zen' \| 'go'` | Last cloud backend launched | `src/types.ts:73` |
85
- | `lastModel` | `string` | Last Claude Code model (pre-selects wizard) | `src/types.ts:74` |
86
- | `lastProvider` | `string` | Last Claude Code provider (`'opencode'`→`'zen'` on read) | `src/config.ts:70` |
87
- | `lastCodexProvider` / `lastCodexModel` | `string` | Last Codex selection | `src/types.ts:76` |
88
- | `lastGeminiProvider` / `lastGeminiModel` | `string` | Last Gemini selection | `src/types.ts:78` |
89
- | `recentModelsByProvider` | `Record<string,string[]>` | Up to 3 recent model ids per provider | `src/types.ts:80` |
90
- | `favoriteModels` | `FavoriteModel[]` | `{ providerId, modelId }`, max 20 | `src/types.ts:81` |
91
- | `server` | object | `savedPassword`, `exposedProviders`, `maskGatewayIds`, `favoritesOnly` | `src/types.ts:82` |
92
-
93
- **Write path.** `writeConfig()` creates the parent dir `0o700` and writes the file `0o600` with a trailing newline (`src/config.ts:61`). `savePreferences()` is a key-by-key conditional merge — every field is copied through only when `!== undefined`, so partial updates never clobber siblings (`src/config.ts:85`).
94
-
95
- > **Note on `subscriptionTier`.** Earlier drafts of the design (and `CLAUDE.md`) describe a `subscriptionTier` (`free`/`zen`/`go`/`both`) preference field. In the shipped v0.2.7 code that field is **not** part of `UserPreferences`, `loadPreferences()`, or `savePreferences()`. The tier concept survives instead as `RegistrySubscriptionFilter` on the registry's Zen provider stub (see *Tier behavior* below). This PRD documents the code as shipped.
96
-
97
- ### App home & override
98
-
99
- `getAppHome(env)` returns `resolveAppHomeOverride(env)` when `RFLECTR_HOME`/`OPENCODE_STARTER_HOME` is set (trimmed, non-empty), else `~/.rflectr` (`src/paths.ts:23`, `src/paths.ts:28`). Sibling path helpers (`getConfigPath`, `getProvidersPath`, `getLogsPath`, `getVertexModelsPath`) all derive from it (`src/paths.ts:38`).
100
-
101
- ### Legacy migration
102
-
103
- `readConfig()` runs `ensureConfigMigrated()` before every read (`src/config.ts:56`). Both stages bail the instant `~/.rflectr/config.json` already exists, making migration idempotent:
104
-
105
- 1. `ensureAppHomeMigrated()` copies `~/.opencode-starter/config.json` (and `vertex-models.json` alongside) into the new home (`src/config.ts:17`).
106
- 2. `ensureConfigMigrated()` then copies the even-older OS `conf` file (`getLegacyConfPath`, platform-specific — `src/paths.ts:54`) and best-effort renames the source to `…​.migrated` (`src/config.ts:49`).
107
-
108
- ### Tier behavior
109
-
110
- The Zen registry stub carries an optional `subscriptionFilter: RegistrySubscriptionFilter` (`src/registry/types.ts:7`, `src/registry/builtins.ts:7`). First-run seeds it as `'free'` via `zenRegistryStub('free')` (`src/first-run.ts:31`). The filter scopes which OpenCode cloud models surface; combined Zen+Go lists track the originating backend per model so the correct base URL is set per selection (see [PRD-003](../prd-003-model-discovery-classification/) for model-discovery merge and `sourceBackend`).
111
-
112
- | Filter | Behavior |
113
- |--------|----------|
114
- | `free` | Zen free models only — seeded default on first-run (`src/first-run.ts:31`) |
115
- | `zen` | Zen backend models |
116
- | `go` | OpenCode Go (paid) backend models |
117
-
118
- `lastBackend` (`'zen' \| 'go'`) records the most recently launched cloud backend independently of the filter (`src/types.ts:73`). The two cloud backends are defined in `BACKENDS` (`src/constants.ts:9`); their `baseUrl` must **not** include `/v1` (the Anthropic SDK appends `/v1/messages`).
119
-
120
- ### Favorites flow
121
-
122
- Pure algebra (`src/favorites.ts`):
123
-
124
- - `isFavorite(list, fav)` — `providerId` + `modelId` equality.
125
- - `addFavorite(list, fav, max=MAX_MODEL_CATALOG)` returns `{ ok:false, reason:'duplicate' }`, `{ ok:false, reason:'cap' }`, or `{ ok:true, list }` (`src/favorites.ts:14`).
126
- - `removeFavorite(list, fav)` — filtered copy (`src/favorites.ts:24`).
127
-
128
- Manager (`runModelsCommand`, `src/cli.ts:534`): fetches the provider catalog, builds a `providerId:modelId → label` lookup, then loops a menu where each favorite row removes-on-select, `+ Add a model →` offers global cross-provider search (`pickGlobalFavoriteModel`, `src/favorites-picker.ts:85`) or browse-by-provider multi-select, and `Done` exits. State is held in memory and written **once** on exit via `savePreferences({ favoriteModels })` only when `favoritesDirty` (`src/cli.ts:728`). The cap is enforced both in the UI (`atCap` disables Add, `src/cli.ts:579`) and in `addFavorite`.
129
-
130
- **Resolution shared with Codex.** `resolveFavorite(fav, ctx)` (`src/favorites-resolver.ts:52`) is route-shape-agnostic — each surface (Claude / Codex / Server) builds its own `ResolveContext`. Zen/Go favorites resolve against `zenModels`/`goModels` + `zenGoApiKey` and carry `sourceBackend`; registry favorites resolve via `ctx.findLocalModel` and are dropped when `ctx.agent` blacklists them via `shouldHideModel` (`src/favorites-resolver.ts:73`). `buildFavoritesList(starting, favorites, ctx, max=20)` dedups (starting model + favorites), caps at `max`, and returns `{ resolved, droppedFavorites }` — **stale / unavailable favorites are silently skipped** (`src/favorites-resolver.ts:87`). Resolved favorites become catalog routes (see [PRD-005](../prd-005-local-proxy-catalog-routing/)); the Codex catalog consumes the same resolver (see [PRD-009](../prd-009-codex-integration/prd-009-codex-integration-index.md)).
131
-
132
- ### Recent models per provider
133
-
134
- On launch, `recordLaunchSelection()` prepends the chosen model id to `recentModelsByProvider[providerId]`, dedupes, and slices to `MAX_RECENT_MODELS = 3` (`src/config.ts:99`, `src/config.ts:107`). `pickLocalModel()` reads them back, showing up to 3 with a `'recent'` hint plus a "Browse all models →" option; when there are none it goes straight to the full browse (`src/prompts.ts:311`).
135
-
136
- ### Large-catalog UX
137
-
138
- `selectModelWithSearch()` shows a flat list when `models.length <= MODEL_SEARCH_THRESHOLD` (25), otherwise delegates to `selectLargeCatalog()` which offers search vs paginated browse at `MODEL_PAGE_SIZE` (15) per page (`src/prompts.ts:246`, `src/prompts.ts:155`). `pickModelFromPagedList()` provides prev/next paging and computes the initial page from a target model id (`src/prompts.ts:87`). Search tokenizes the query on whitespace + punctuation with AND logic across id/name/brand (`src/prompts.ts:52`).
139
-
140
- ### First-run
141
-
142
- `needsFirstRunSetup()` returns true only when the registry is empty **and** no Zen/Go key is stored (`src/first-run.ts:21`). `runFirstRunWizard()` offers Zen quick-start (collect key → seed `zenRegistryStub('free')`), import-from-OpenCode, or set-up-your-own-provider — and every branch terminates in `continue` (launch) or explicit `cancel`, never a dead end (`src/first-run.ts:36`).
143
-
144
- ### `--dry-run`
145
-
146
- All preference writes are gated on the caller passing `dryRun`. In dry-run, favorites load as `[]` (`src/cli.ts:761`) and the launch path skips `recordLaunchSelection`, so a fresh first-run is fully simulated without mutating `config.json`.
147
-
148
- ---
149
-
150
- ## Data Shapes
151
-
152
- ```ts
153
- // src/types.ts:67
154
- export interface FavoriteModel {
155
- providerId: string;
156
- modelId: string;
157
- }
158
-
159
- // src/types.ts:72
160
- export interface UserPreferences {
161
- lastBackend?: 'zen' | 'go';
162
- lastModel?: string;
163
- lastProvider?: string;
164
- lastCodexProvider?: string;
165
- lastCodexModel?: string;
166
- lastGeminiProvider?: string;
167
- lastGeminiModel?: string;
168
- recentModelsByProvider?: Record<string, string[]>;
169
- favoriteModels?: FavoriteModel[];
170
- server?: {
171
- savedPassword?: string;
172
- exposedProviders?: string[];
173
- maskGatewayIds?: boolean;
174
- favoritesOnly?: boolean;
175
- };
176
- }
177
-
178
- // src/registry/types.ts:7
179
- export type RegistrySubscriptionFilter = 'free' | 'zen' | 'go';
180
-
181
- // src/favorites-resolver.ts:8
182
- export interface ResolvedFavorite {
183
- providerId: string;
184
- providerName: string;
185
- model: LocalProviderModel | ServerModelInfo;
186
- apiKey: string;
187
- sourceBackend?: 'zen' | 'go';
188
- }
189
- ```
190
-
191
- ---
192
-
193
- ## Acceptance Criteria
194
-
195
- - [x] App home resolves from `RFLECTR_HOME` (or deprecated `OPENCODE_STARTER_HOME`), else `~/.rflectr` (`src/paths.ts:23`).
196
- - [x] Config dir created `0o700`, file written `0o600` with trailing newline (`src/config.ts:61`).
197
- - [x] `savePreferences()` shallow-merges, copying only defined keys (`src/config.ts:85`).
198
- - [x] `recordLaunchSelection()` sets per-agent `last*` and prepends to `recentModelsByProvider`, deduped and capped at 3 (`src/config.ts:101`).
199
- - [x] Legacy migration runs both stages, is idempotent, and renames the old conf file best-effort (`src/config.ts:34`).
200
- - [x] `lastProvider === 'opencode'` is normalized to `'zen'` on read (`src/config.ts:70`).
201
- - [x] Zen registry stub carries a `subscriptionFilter`, seeded `'free'` at first-run (`src/first-run.ts:31`, `src/registry/builtins.ts:7`).
202
- - [x] `addFavorite` rejects duplicates and enforces the `MAX_MODEL_CATALOG` (20) cap (`src/favorites.ts:14`).
203
- - [x] `rflectr models` saves favorites once on Done, only when dirty (`src/cli.ts:728`).
204
- - [x] Favorites resolver drops stale/unavailable favorites silently and is shared across Claude/Codex/Server (`src/favorites-resolver.ts:87`).
205
- - [x] `buildFavoritesList` dedups starting model + favorites and caps at `max` (`src/favorites-resolver.ts:103`).
206
- - [x] `pickLocalModel` surfaces up to 3 recent models with a "Browse all" escape hatch (`src/prompts.ts:311`).
207
- - [x] Lists above `MODEL_SEARCH_THRESHOLD` (25) switch to search/paginated browse at `MODEL_PAGE_SIZE` (15) (`src/prompts.ts:257`).
208
- - [x] First-run wizard never dead-ends; every path returns `continue` or `cancel` (`src/first-run.ts:36`).
209
- - [x] `--dry-run` loads favorites as `[]` and skips all preference writes (`src/cli.ts:761`).
210
-
211
- ---
212
-
213
- ## Files
214
-
215
- | File | Role |
216
- |------|------|
217
- | `src/config.ts` | Read/write preferences, legacy migration, server-password keyring move, recent-models recording |
218
- | `src/paths.ts` | App-home resolution, override hook, sibling path helpers, legacy conf path |
219
- | `src/types.ts` | `UserPreferences`, `FavoriteModel` shapes |
220
- | `src/favorites.ts` | Pure favorites algebra (`isFavorite`/`addFavorite`/`removeFavorite`) |
221
- | `src/favorites-picker.ts` | Global cross-provider favorite search (`pickGlobalFavoriteModel`) |
222
- | `src/favorites-resolver.ts` | Surface-agnostic favorite → route resolution, shared with Codex |
223
- | `src/prompts.ts` | `pickLocalModel`, recent-models, large-catalog search/pagination |
224
- | `src/first-run.ts` | Inline never-dead-end first-run wizard |
225
- | `src/cli.ts` | `runModelsCommand` favorites manager; launch-time recent/favorite wiring |
226
- | `src/constants.ts` | `BACKENDS`, `MAX_MODEL_CATALOG` |
227
- | `src/registry/builtins.ts` | Zen/Go registry stubs carrying `subscriptionFilter` |
228
- | `src/registry/types.ts` | `RegistrySubscriptionFilter` type |
229
-
230
- ---
231
-
232
- ## Risks & Known Limitations
233
-
234
- - **`subscriptionTier` drift:** the field named in `CLAUDE.md` is not present in shipped code; tier filtering is registry-stub-based (`subscriptionFilter`). Documentation that references a `subscriptionTier` preference key is stale.
235
- - **Stale favorites are invisible:** unavailable favorites (provider removed, model retired) are silently dropped at resolution time; in the `rflectr models` list they render as `★ <id> — provider gone` (`src/cli.ts:575`) but are not auto-pruned from `config.json`.
236
- - **No schema versioning:** `config.json` has no version field; forward/backward compat relies on all keys being optional and unknown keys being tolerated.
237
- - **Per-host only:** preferences are not synced across machines.
238
- - **Best-effort legacy rename:** if renaming the old conf file to `…​.migrated` fails (permissions), the copy still succeeds but the stale source remains (`src/config.ts:51`).
239
- - **Context window in switch-menu mode** reflects the launch model, not live `/model` switches — a downstream limitation of the catalog/gateway path (see [PRD-005](../prd-005-local-proxy-catalog-routing/)), not the preferences layer.
240
-
241
- ---
242
-
243
- ## Related
244
-
245
- - **Knowledge:** [`preferences-config.md`](../../../knowledge/private/data/preferences-config.md)
246
- - [PRD-001 — CLI Core & Launch Orchestration](../prd-001-cli-core-launch-orchestration/prd-001-cli-core-launch-orchestration-index.md) — launch flow that reads/writes these preferences
247
- - [PRD-003 — Model Discovery & Classification](../prd-003-model-discovery-classification/) — tier-scoped model lists, `sourceBackend` merge
248
- - [PRD-005 — Local Proxy & Catalog Routing](../prd-005-local-proxy-catalog-routing/) — catalog routes built from resolved favorites
249
- - [PRD-009 — Codex Integration](../prd-009-codex-integration/prd-009-codex-integration-index.md) — Codex favorites catalog using the shared resolver
@@ -1,212 +0,0 @@
1
- # PRD-009: Codex Integration (CLI + Desktop App) *(Retroactive)*
2
-
3
- > **Status:** Shipped
4
- > **Priority:** —
5
- > **Effort:** —
6
- > **Written:** June 2026
7
- > **Retroactive:** Yes — written after implementation (rflectr v0.2.7).
8
- > **Source:** `src/codex/*`, `src/codex.ts`, `src/codex-app.ts`, `src/codex-proxy.ts`, `src/codex-responses-adapter.ts`
9
-
10
- ## Overview
11
-
12
- `rflectr codex` and `rflectr codex-app` launch OpenAI **Codex** — the terminal CLI and the desktop app — against any model in the user's rflectr registry (Anthropic, xAI, Gemini, Nvidia, DeepSeek, OpenAI, OpenCode Zen/Go, Vertex AI), even though Codex speaks the **OpenAI Responses API** (`POST /v1/responses`) and most of those providers do not.
13
-
14
- The integration follows the shared host-harness skeleton (see [`harnesses.md`](../../../knowledge/private/integrations/harnesses.md)): find the Codex binary → resolve provider + model → start a local proxy that speaks the Responses wire format → point Codex at it → restore/clean up on exit. Codex departs from the Claude Code flow in two ways that define this PRD:
15
-
16
- 1. **A Responses-API translation proxy** (`src/codex-proxy.ts` + `src/codex-responses-adapter.ts`) instead of the Anthropic-format proxy.
17
- 2. **Two binding mechanisms.** The **CLI** is pointed at the proxy by a *sidecar TOML profile* (`~/.codex/rflectr-launch.config.toml`) and `codex --profile rflectr-launch -m <model>` — it never edits `~/.codex/config.toml`. The **desktop app** cannot inherit env or a `--profile` flag, so its `config.toml` is *patched in place with a backup* and *restored on exit* via a lock file.
18
-
19
- A two-tier routing model decides whether a proxy is even needed: **Tier 1 (direct)** for OpenAI API-key providers (Codex talks to OpenAI natively); **Tier 2 (proxy)** for everyone else, including OpenAI via OAuth (`src/codex/routing.ts:60-91`).
20
-
21
- ## What Was Built
22
-
23
- **CLI entry — `runCodexCommand`** (`src/codex.ts:301`): parses `--help` / `--restore` / `--config` / `--vertex` / `--provider` / `--model` / `--trace`, recovers interrupted sessions, builds the provider catalog filtered to Codex-compatible providers, runs the picker (or skips it for scripted launch), resolves a `CodexRoute`, starts the proxy when the route is Tier 2, writes the sidecar profile + catalog, takes a session lock, and spawns Codex. A `finally` block closes the proxy and removes the overlay on exit (`src/codex.ts:653-656`).
24
-
25
- **App entry — `runCodexAppCommand`** (`src/codex-app.ts:284`): macOS/Windows only, parses the same flags, runs the picker, *always* starts the proxy (App is always Tier 2), saves a restore snapshot, backs up and patches `~/.codex/config.toml`, opens (or restarts) the Codex app, then **blocks on `Ctrl+C`** and restores the original config on shutdown (`src/codex-app.ts:589-606`).
26
-
27
- **Responses proxy — `startCodexProxy`** (`src/codex-proxy.ts:138`): a `127.0.0.1:0` HTTP server serving `GET /health`, `GET /v1/models`, `GET /v1/models/<id>`, and `POST /v1/responses`. It pre-instantiates one `LanguageModel` per route via `createLanguageModel()`, resolves the requested model id against the route table (with fuzzy slug lookup and a fallback to `routes[0]`), translates the request through the Responses adapter, optionally trims history to the context window, and streams or returns the SDK result as Responses SSE/JSON.
28
-
29
- **Responses adapter** (`src/codex-responses-adapter.ts`): `translateResponsesRequest` → SDK call params; `streamResponsesResponse` / `generateResponsesResponse` → Responses SSE/JSON; rate-limit and error stream helpers. This reuses the shared Vercel AI SDK translation machinery (`proxy-shared.ts`, `provider-factory.ts`) — the same single translation path documented in PRD-004.
30
-
31
- **Favorites catalog mode**: when `prefs.favoriteModels.length > 0`, both commands resolve favorites via the shared `src/favorites-resolver.ts` (filtered by an `agent: 'codex'` blacklist), build a `CodexProxyRoute[]`, and start a single multi-route proxy. Catalog slugs are collision-safe `${providerId}__${modelId}` (`src/codex/favorites-catalog.ts:13`).
32
-
33
- ## Goals
34
-
35
- - Launch Codex CLI and the Codex desktop app against any registry provider/model, bridging Responses ↔ upstream via one local proxy.
36
- - **Never edit `~/.codex/config.toml` for CLI launches** — use a disposable sidecar profile so the user's personal Codex settings remain untouched.
37
- - For the App, patch `config.toml` *safely*: snapshot + backup before, restore on `Ctrl+C` or crash recovery, never delete conversations.
38
- - Preserve Codex's history visibility in the App by keeping `model_provider = "openai"` and redirecting via `openai_base_url` (a custom provider would hide existing OpenAI/ChatGPT threads).
39
- - Route OpenAI API-key providers directly (Tier 1) and everyone else through the proxy (Tier 2).
40
- - Support a multi-model favorites catalog for mid-session switching, capped at 20 routes.
41
- - Default the CLI sandbox to `danger-full-access` so shell tools reach the network, and strip CI env vars so IDE terminals don't force read-only mode.
42
- - Protect against context overflow (early auto-compaction config for the App + proxy-level truncation as a last resort).
43
-
44
- ## Non-Goals
45
-
46
- - Owning the SDK translation layer itself — that is PRD-004 (this PRD reuses it via the Responses adapter).
47
- - Owning the generic local proxy / catalog routing for Claude Code — that is PRD-005 (Codex has its own Responses proxy, but shares the favorites-resolver and provider-factory).
48
- - Owning the favorites/tier data model — that is PRD-008 (this PRD consumes `favoriteModels` and resolves them per-surface).
49
- - Owning credential storage (PRD-006) or OAuth device flows (PRD-007) — Codex reads resolved credentials and forwards them.
50
- - Supporting the Codex desktop app on Linux (no app exists there).
51
- - Mid-session live model switching in the App via a hot config swap — switching is via the catalog the app loads at launch.
52
-
53
- ## Features
54
-
55
- | Feature | `rflectr codex` (CLI) | `rflectr codex-app` (App) | Source |
56
- |---|---|---|---|
57
- | Binding mechanism | Sidecar TOML profile + `--profile`/`-m` flags; never edits `config.toml` | Patches `~/.codex/config.toml` in place, with backup + restore | `src/codex/profile.ts:31`, `src/codex/app-config.ts:198` |
58
- | Tier 1 direct (OpenAI API key) | Yes — Codex calls OpenAI natively, no proxy | No — always proxied | `src/codex/routing.ts:86-90` |
59
- | Tier 2 proxy (everyone else) | Yes | Yes (always) | `src/codex-proxy.ts:138` |
60
- | Proxy auth | `requireAuth: true` → `RFLECTR_CODEX_KEY=proxy-local` token | `requireAuth: false` (GUI can't send a token) + loopback origin check | `src/codex-proxy.ts:144-194`, `src/codex/launch.ts:90-92` |
61
- | Catalog file | `models-<provider>.json` / `models-favorites.json` | `app-models-<provider>.json` / `app-models-favorites.json` | `src/codex/session.ts:52`, `src/codex/app-session.ts:42` |
62
- | Catalog slug (single) | `stripGoogleModelPrefix(id)` | bare id (`codexAppModelSlug`) | `src/codex/catalog.ts:101-105` |
63
- | Catalog slug (favorites) | `${providerId}__${modelId}` | `${providerId}__${modelId}` | `src/codex/favorites-catalog.ts:13` |
64
- | Session lock | `session.json` (one CLI session) | `session-app.json` + `app-restore-state.json` + backups | `src/codex/session.ts:44`, `src/codex/app-session.ts:34-44` |
65
- | Sandbox | `danger-full-access` (profile + spawn args) | n/a (app manages its own) | `src/codex/profile.ts:17`, `src/codex/launch.ts:82-85` |
66
- | Context overflow | Proxy-level truncation (85% window) | `model_context_window` + `model_auto_compact_token_limit` (70%) + proxy truncation | `src/codex-proxy.ts:40-51`, `src/codex/app-profile.ts:47-50` |
67
- | Vertex AI path | `--vertex` (gcloud ADC) | `--vertex` (gcloud ADC) | `src/codex.ts:206`, `src/codex-app.ts:142` |
68
- | Favorites catalog | Up to 20 routes, multi-route proxy | Up to 20 routes, multi-route proxy | `src/codex/favorites-launch.ts:12`, `src/favorites-resolver.ts:91` |
69
- | `--config` preview | Writes profile + catalog, prints paths, no launch | **Preview only** — prints the TOML, no disk writes, no app, no proxy | `src/codex.ts:583`, `src/codex-app.ts:481` |
70
- | `--restore` | Removes overlay files (`models-*.json`, profile, lock) | Restores `config.toml`, removes `app-models-*.json`; refuses if a live session holds the lock | `src/codex/session.ts:142`, `src/codex/app-session.ts:143` |
71
-
72
- ## Architecture & Implementation
73
-
74
- ### CLI launch flow (`rflectr codex`)
75
-
76
- ```mermaid
77
- flowchart TD
78
- start["runCodexCommand (src/codex.ts:301)"]
79
- start --> bin["findCodexBinary (codex/launch.ts:48)"]
80
- bin --> recover["recoverInterruptedCodexSession (codex/session.ts:173)"]
81
- recover --> cat["fetchProviderCatalog agent:codex"]
82
- cat --> compat["codexCompatibleProviders (codex/routing.ts:40)"]
83
- compat --> pick["pickCodexProvider / pickCodexModel (codex/prompts.ts)"]
84
- pick --> route["resolveCodexRoute (codex/routing.ts:61)"]
85
- route -->|tier=direct, OpenAI key| direct["env OPENAI_API_KEY; profile model_provider=openai; no proxy"]
86
- route -->|tier=proxy| proxy["startCodexProxy requireAuth:true (codex-proxy.ts:138)"]
87
- proxy --> profile["buildCodexProfileToml → rflectr-launch.config.toml (codex/profile.ts:31)"]
88
- direct --> profile
89
- profile --> lock["writeSessionLock session.json (codex/session.ts:111)"]
90
- lock --> spawn["launchCodex --profile rflectr-launch -m model (codex/launch.ts:100)"]
91
- spawn --> done["on exit: proxyHandle.close() + restoreCodexOverlay (codex.ts:653)"]
92
- ```
93
-
94
- **Routing** (`src/codex/routing.ts:61-91`): `resolveCodexRoute` returns `tier: 'direct'` only when `model.npm === '@ai-sdk/openai'`, the provider is **not** OAuth, and `modelFormat === 'openai'`; everything else is `tier: 'proxy'`. For Zen/Go cloud backends the endpoint format is taken from `modelFormat` (the registry npm is unreliable there) and the base URL gets a `/v1` suffix for non-Anthropic models (`src/codex/routing.ts:49-58`).
95
-
96
- **Sidecar profile** (`src/codex/profile.ts:31-64`): a Tier 2 profile defines `model_provider = "rflectr-proxy"`, `[model_providers.rflectr-proxy]` with `base_url = http://127.0.0.1:<port>/v1`, `wire_api = "responses"`, `env_key = "RFLECTR_CODEX_KEY"`, plus `sandbox = "danger-full-access"`. A Tier 1 profile points `base_url` at OpenAI and uses the provider's real `env_key` (e.g. `OPENAI_API_KEY`).
97
-
98
- **Child env** (`src/codex/launch.ts:87-98`): Tier 2 sets `RFLECTR_CODEX_KEY=proxy-local` (the placeholder the proxy validates); Tier 1 sets the provider's real key env var. `stripCodexInheritedEnv` removes nine CI vars (`CI`, `CODEX_CI`, `GITHUB_ACTIONS`, …) so IDE terminals don't drop Codex into read-only CI mode. Spawn always injects `-s danger-full-access` unless the user passed their own sandbox flag (`src/codex/launch.ts:82-85`, `:107`).
99
-
100
- **Session lock & recovery** (`src/codex/session.ts`): `session.json` records pid + paths; a session is "stale" only when the owning pid is no longer alive (`isSessionStale`, `:134`). On launch, `recoverInterruptedCodexSession` removes orphaned overlay files unless a concurrent live session holds the lock. `--restore` calls `restoreCodexOverlay` to delete the profile, lock, and all `models-*.json`.
101
-
102
- ### App launch flow (`rflectr codex-app`)
103
-
104
- ```mermaid
105
- flowchart TD
106
- a["runCodexAppCommand (src/codex-app.ts:284)"]
107
- a --> supp["codexAppSupported() — macOS/Windows only"]
108
- supp --> rec["recoverInterruptedCodexAppSession (app-session.ts:178)"]
109
- rec --> pick["picker → resolveCodexRoute (tier forced to proxy)"]
110
- pick --> px["startCodexProxy requireAuth:false (codex-proxy.ts:138)"]
111
- px --> snap["saveAppRestoreStateBeforePatch (app-session.ts:107)"]
112
- snap --> bak["backupConfigToml → backups/config.toml.<ts>.bak (app-session.ts:95)"]
113
- bak --> patch["applyAppConfigPatch ~/.codex/config.toml (app-config.ts:198)"]
114
- patch --> lock["writeAppSessionLock session-app.json (app-session.ts:66)"]
115
- lock --> open["launchOrRestartCodexApp (app-launch.ts:203)"]
116
- open --> wait["waitForShutdownWithConfirm — block on Ctrl+C (codex-app.ts:59)"]
117
- wait --> quit["quitCodexAppGracefully + restoreCodexAppOverlay (codex-app.ts:593-600)"]
118
- ```
119
-
120
- **Config patch** (`src/codex/app-config.ts:121-212`): `mergeAppConfig` keeps the user's existing root keys but overwrites `model`, `model_provider`, `openai_base_url`, `model_catalog_json`, and the two context fields; it strips any legacy `profile` key and the legacy `rflectr-launch-codex-app` provider/profile tables. The written config is validated (`validateAppConfigText`, `:175`) to assert `model_provider` stays `openai`, the `openai_base_url` matches the proxy port, and the catalog path matches.
121
-
122
- **Why `model_provider = "openai"`** (`src/codex/app-profile.ts:33-52`): the app records the provider on every local thread and filters history by provider, so a custom provider would hide the user's existing OpenAI/ChatGPT threads while a rflectr session is active. rflectr therefore writes the display model `CODEX_APP_DISPLAY_MODEL = 'gpt-5.5'` and redirects the built-in provider with `openai_base_url`. The proxy routes the actual selected model via its fallback mechanism.
123
-
124
- **Backup + lock + restore**: before patching, `saveAppRestoreStateBeforePatch` snapshots the pre-session root keys to `app-restore-state.json`, and `backupConfigToml` copies `config.toml` to a rotating `backups/config.toml.<ts>.bak` (max 5). The lock `session-app.json` records `pid`, `configPath`, `catalogPaths`, `restoreStatePath`, `backupPath`, and `proxyPort`. On exit, `restoreCodexAppOverlay` (`src/codex/app-session.ts:143`) restores from the snapshot (preferred) or the backup file, removes `app-models-*.json`, and clears the lock. It **refuses** to run while another live session holds the lock (`:145-151`). Crash recovery on the next launch is automatic (`recoverInterruptedCodexAppSession`, `:178`).
125
-
126
- **App launch/quit** (`src/codex/app-launch.ts`): finds `Codex.app` (macOS, via fixed paths + `mdfind` on bundle id `com.openai.codex`) or `Codex.exe` (Windows, via `%LOCALAPPDATA%\Programs` candidates + `Get-StartApps`). "Is it running?" uses `osascript` (macOS) / `Get-CimInstance` + window-handle checks (Windows). If Codex is already running, rflectr offers to restart it so new settings apply; otherwise it just opens it.
127
-
128
- ### The Responses proxy & adapter
129
-
130
- `startCodexProxy(routes, options)` (`src/codex-proxy.ts:138`) builds a `Map<modelId, LanguageModel>` up front by calling `createLanguageModel({ npm, modelId: upstreamModelId, apiKey, baseURL, vertex })` per route (`:147-159`), then serves:
131
-
132
- - **`GET /v1/models`** — synthesizes a catalog entry for each route under three id shapes: bare `modelId`, `codexAppModelSlug(modelId)`, and `${providerId}__${modelId}` (`:215-221`), so any slug Codex requests resolves.
133
- - **`POST /v1/responses`** — when `requireAuth`, validates the inbound key equals `proxy-local` (`PROXY_PLACEHOLDER_KEY`, `:74`); resolves the model (with `codexRouteLookupIds` fuzzy matching across `/`, `__`, and app-slug prefixes, `:76-93`); falls back to `routes[0]` if unmatched (handles the app's hardcoded background `gpt-5.4`/`gpt-5.5` ids); translates via `translateResponsesRequest`; trims to the context window if set; then streams (`streamResponsesResponse`) or returns (`generateResponsesResponse`). 429s become a Responses rate-limit body/stream; other upstream errors map to honest HTTP statuses (`upstreamHttpStatus`, `:109-117`).
134
-
135
- **App-mode security** (`requireAuth: false`, `src/codex-proxy.ts:177-194`): since the GUI cannot send the token, POSTs are gated by a loopback `Origin`/`Referer` check instead.
136
-
137
- **Context truncation** (`src/codex-proxy.ts:24-51`): `trimToContextLimit` estimates characters (≈4 chars/token) and drops the oldest messages until the estimate is under 85% of the window, always keeping at least one user-led message.
138
-
139
- **Error formatting** (`src/codex/upstream-error.ts`): `formatUpstreamError` extracts the shortest honest user-facing message from SDK/upstream error shapes — no stack traces, no `file://` noise — for the Codex TUI.
140
-
141
- ### Favorites catalog
142
-
143
- When favorites are active, `resolveCodexFavorites` (`src/codex/favorites-launch.ts:54`) builds a per-surface `ResolveContext` and calls the shared `buildFavoritesList` (`src/favorites-resolver.ts:87`, cap 20). Zen/Go favorites require an OpenCode API key (resolved via `resolveOrCollectApiKey` / credential store) and are dropped if absent. `buildCodexProxyRoutesFromResolved` (`:12`) maps each resolved favorite to a `CodexProxyRoute`, skipping OAuth providers with an empty key (OAuth refresh isn't supported in the favorites proxy, `:24-27`). Each route's catalog slug is the collision-safe `${providerId}__${modelId}`. The starting model is launched via that slug (`codexCliFavoritesSlug`), which must match the profile `model` and `codex -m`.
144
-
145
- ## Acceptance Criteria
146
-
147
- - [x] `rflectr codex` launches Codex CLI against a selected registry provider/model.
148
- - [x] CLI launches **never edit** `~/.codex/config.toml` — only the sidecar `rflectr-launch.config.toml` (`src/codex/profile.ts:31`).
149
- - [x] OpenAI API-key providers route Tier 1 direct (no proxy); OpenAI-via-OAuth and all others route Tier 2 through the proxy (`src/codex/routing.ts:86-90`).
150
- - [x] The Tier 2 CLI child gets `RFLECTR_CODEX_KEY=proxy-local`, and the proxy rejects any other key when `requireAuth` (`src/codex/launch.ts:91`, `src/codex-proxy.ts:248-252`).
151
- - [x] CLI default sandbox is `danger-full-access` in both the profile and the spawn args (`src/codex/profile.ts:17`, `src/codex/launch.ts:84`).
152
- - [x] CI env vars are stripped before spawning Codex (`src/codex/launch.ts:16-34`).
153
- - [x] `rflectr codex-app` patches `config.toml` with a backup and restores it on `Ctrl+C` (`src/codex-app.ts:547-600`).
154
- - [x] App config keeps `model_provider = "openai"` and redirects via `openai_base_url`, validated on write (`src/codex/app-config.ts:185-195`).
155
- - [x] App proxy runs with `requireAuth: false` and a loopback origin guard (`src/codex-proxy.ts:177-194`).
156
- - [x] App writes `model_context_window` + `model_auto_compact_token_limit` (70%) when a window is known (`src/codex/app-profile.ts:47-50`).
157
- - [x] Proxy serves `/v1/responses`, `/v1/models`, `/v1/models/<id>`, and `/health`, translating through the Responses adapter (`src/codex-proxy.ts:196-375`).
158
- - [x] Proxy falls back to `routes[0]` for unknown model ids so the app's hardcoded background ids still resolve (`src/codex-proxy.ts:287-293`).
159
- - [x] Favorites mode builds a multi-route catalog (≤20) with `${providerId}__${modelId}` slugs (`src/codex/favorites-catalog.ts:13`, `src/favorites-resolver.ts:91`).
160
- - [x] Zen/Go favorites are included only when an OpenCode API key is available, and OAuth-empty favorites are skipped (`src/codex/favorites-launch.ts:24-27`, `src/favorites-resolver.ts:57-59`).
161
- - [x] `--config` previews without launching (CLI writes files + prints paths; App prints TOML with zero writes) (`src/codex.ts:583`, `src/codex-app.ts:481-521`).
162
- - [x] `--restore` cleans up overlay/app files; App `--restore` refuses while a live session holds the lock (`src/codex/app-session.ts:143-151`).
163
- - [x] Interrupted sessions auto-recover on the next launch (`src/codex/session.ts:173`, `src/codex/app-session.ts:178`).
164
- - [x] A concurrent live session is detected and blocks a second launch (`src/codex/session.ts:195`, `src/codex/app-session.ts:194`).
165
- - [x] `--vertex` launches Claude models through Google Vertex AI via gcloud ADC for both CLI and App (`src/codex.ts:206`, `src/codex-app.ts:142`).
166
- - [x] App launch is gated to macOS/Windows; Linux is rejected with a clear message (`src/codex/app-launch.ts:12-16`).
167
-
168
- ## Files
169
-
170
- | File | Role |
171
- |---|---|
172
- | `src/codex.ts` | CLI entry `runCodexCommand`; help text; launch artifact writers; Vertex CLI path |
173
- | `src/codex-app.ts` | App entry `runCodexAppCommand`; config patch orchestration; Ctrl+C shutdown loop; Vertex App path |
174
- | `src/codex-proxy.ts` | Local Responses-API proxy (`startCodexProxy`); `CodexProxyRoute`; route resolution; `/v1/models` + `/v1/responses` |
175
- | `src/codex-responses-adapter.ts` | Responses ↔ Vercel AI SDK translation (`translateResponsesRequest`, stream/generate, rate-limit/error helpers) |
176
- | `src/codex/routing.ts` | Tier 1/Tier 2 routing; `resolveCodexRoute`; routable-model filter; `codexProviderEnvKey` |
177
- | `src/codex/profile.ts` | Sidecar `rflectr-launch.config.toml` builder; sandbox constant; catalog path helpers |
178
- | `src/codex/launch.ts` | `findCodexBinary`; CI-env stripping; sandbox-arg injection; child env; `launchCodex` spawn |
179
- | `src/codex/catalog.ts` | `model_catalog_json` builder; reasoning fields; label formatting; serialize |
180
- | `src/codex/session.ts` | CLI overlay session: lock, backup rotation, stale/recovery, restore |
181
- | `src/codex/app-config.ts` | Read/merge/validate/restore `~/.codex/config.toml`; managed-config detection |
182
- | `src/codex/app-profile.ts` | App root config content; display model; context fields; slug helpers |
183
- | `src/codex/app-session.ts` | App session: backup, restore-state snapshot, lock, recovery |
184
- | `src/codex/app-launch.ts` | Find/open/quit/restart the Codex desktop app (macOS + Windows) |
185
- | `src/codex/favorites-catalog.ts` | Favorites slug (`${providerId}__${modelId}`); CLI/App favorites catalog builders |
186
- | `src/codex/favorites-launch.ts` | Resolve favorites → `CodexProxyRoute[]`; OAuth skip; stale-favorite warnings |
187
- | `src/codex/prompts.ts` | Codex provider/model pickers; managed-flag rejection; launch confirm |
188
- | `src/codex/ui.ts` | Intro/outro, proxy/model log lines, session/cleanup panels |
189
- | `src/codex/upstream-error.ts` | Short user-facing messages from SDK/upstream failures |
190
- | `src/favorites-resolver.ts` | Shared per-surface favorite resolution (also used by Claude/Server) |
191
-
192
- ## Risks & Known Limitations
193
-
194
- - **`@ai-sdk/github-copilot` won't work.** OpenCode loads it from internal `@opencode-ai/core`, not a public npm factory rflectr can ship — such providers are silently unroutable.
195
- - **Zen/Go favorites are skipped in Codex when no OpenCode key is present** (and have no gateway path in Codex generally); resolution drops them (`src/favorites-resolver.ts:57-59`).
196
- - **OAuth-empty favorites are dropped from the favorites proxy** — OAuth refresh flows aren't supported there (`src/codex/favorites-launch.ts:24-27`).
197
- - **Codex App is a stateless client** — it resends the full accumulated history every turn (no `previous_response_id`), so a long GPT-5.5 session can't be transparently resumed on a smaller-window model. Mitigated by early auto-compaction config + proxy-level truncation, both lossy as a last resort.
198
- - **App labels any catalog-loaded model "Custom"** (cosmetic) and periodically sends **background requests with hardcoded OpenAI ids** (`gpt-5.4`, `gpt-5.5`); the proxy routes these to the starting model via `routes[0]` fallback.
199
- - **OAuth tokens refresh at launch only** — long sessions may 401 when a token expires; restart to re-auth.
200
- - **Cost display is inaccurate for non-Anthropic models** (the host applies its own pricing table) — a documented, by-design limitation shared across harnesses.
201
- - **macOS sandbox quirk:** the profile TOML alone may not grant network access; rflectr also passes `-s danger-full-access` on spawn ([Codex #10390](https://github.com/openai/codex/issues/10390)).
202
- - **App support is macOS/Windows only** — there is no Codex desktop app on Linux.
203
-
204
- ## Related
205
-
206
- - [`harnesses.md`](../../../knowledge/private/integrations/harnesses.md) — host-harness pattern; where Codex CLI/App depart from it.
207
- - [`codex.md`](../../../knowledge/public/guides/codex.md) — user-facing Codex guide (flags, files owned, tiers, troubleshooting).
208
- - [PRD-004 — Translation Layer](../prd-004-translation-layer/prd-004-translation-layer-index.md) — the SDK adapter the Responses adapter reuses.
209
- - [PRD-005 — Local Proxy & Catalog Routing](../prd-005-local-proxy-catalog-routing/prd-005-local-proxy-catalog-routing-index.md) — the Claude-side proxy this mirrors; shared `provider-factory` / `proxy-shared`.
210
- - [PRD-008 — Preferences, Tiers & Favorites](../prd-008-preferences-tiers-favorites/prd-008-preferences-tiers-favorites-index.md) — favorites data model and `favorites-resolver`.
211
- - [PRD-011 — Claude Desktop Integration](../prd-011-claude-desktop-integration/prd-011-claude-desktop-integration-index.md) — sibling desktop-app integration (config patch + lock-file restore, gateway path).
212
- - [PRD-001 — CLI Core & Launch Orchestration](../prd-001-cli-core-launch-orchestration/prd-001-cli-core-launch-orchestration-index.md) — subcommand dispatch that routes to `codex` / `codex-app`.