@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,211 +0,0 @@
1
- # PRD-010: Gemini CLI Integration *(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/gemini/*`, `src/gemini.ts`, `src/gemini-proxy.ts`, `src/gemini-parts.ts`
9
-
10
- ---
11
-
12
- ## Overview
13
-
14
- `rflectr gemini` launches Google's [Gemini CLI](https://www.npmjs.com/package/@google/gemini-cli) re-pointed at any model in the rflectr registry — Anthropic, xAI, OpenAI, Nvidia, DeepSeek, OpenCode Zen / Go, and more. The Gemini CLI speaks the **Gemini REST protocol** (`POST /v1beta/models/<model>:generateContent`), so rflectr stands up a local **gemini-proxy** that translates that protocol to and from the shared Vercel AI SDK adapter ([PRD-004](../prd-004-translation-layer/prd-004-translation-layer-index.md)) and routes each request to the selected provider. The CLI is pointed at the proxy purely through environment variables — `GOOGLE_GEMINI_BASE_URL` plus a random `GEMINI_API_KEY` proxy token — so no Gemini config file is written or restored.
15
-
16
- This is the third host harness in rflectr after Claude Code and Codex, and it follows the same find-binary → pick provider/model → start translating proxy → launch child with clean env → close proxy on exit skeleton documented in the [harnesses knowledge doc](../../../knowledge/private/integrations/harnesses.md). It ships flagged **🧪 Experimental** in the [public guide](../../../knowledge/public/guides/gemini-cli.md).
17
-
18
- Unlike the Claude/Codex proxies that translate *into* the Anthropic and OpenAI-Responses wire formats, the gemini-proxy is the only one whose **inbound** format is Gemini REST. The proxy therefore owns a Gemini-specific request translator (`translateGeminiRequest`), a Gemini-specific SSE shape on the way out, and the `thought_signature` round-trip on tool-call parts (`src/gemini-parts.ts`).
19
-
20
- ---
21
-
22
- ## What Was Built
23
-
24
- - **`rflectr gemini` command** (`src/gemini.ts:58`) — the full launch orchestration: binary discovery, provider catalog fetch, provider/model picker, route catalog assembly, proxy start, child env, launch, and proxy teardown.
25
- - **Binary discovery + env + launch** (`src/gemini/launch.ts`) — `findGeminiBinary()`, `buildGeminiChildEnv()`, `launchGemini()`.
26
- - **Interactive pickers** (`src/gemini/prompts.ts`) — provider picker (with a Favorites Catalog entry), model picker (recents + browse-all), favorites picker, launch confirm, and managed-flag rejection.
27
- - **The gemini-proxy** (`src/gemini-proxy.ts`) — a local HTTP server on `127.0.0.1:0` serving `GET /v1beta/models`, `GET /v1beta/models/<model>`, and `POST .../<model>:generateContent` / `:streamGenerateContent`, translating each request through `createLanguageModel()` + the AI SDK (`streamText` / `generateText`) and emitting Gemini-format SSE / JSON.
28
- - **Gemini part parsing** (`src/gemini-parts.ts`) — shared Gemini-part → Anthropic-block helpers including `thought_signature` extraction from tool-call parts (`partThoughtSignature`) and usage mapping.
29
- - **Mid-session model switching** — the proxy intercepts a `.model <id>` chat command, switches the active route in memory, and sanitizes the synthetic switch turns out of subsequent request history.
30
- - **Favorites catalog mode** — when favorites are saved ([PRD-008](../prd-008-preferences-tiers-favorites/prd-008-preferences-tiers-favorites-index.md)), they are resolved into proxy routes and exposed both in the provider picker and as switchable `.model` targets.
31
- - **`--trace`** — writes proxy debug logs to `~/.rflectr/logs/gemini-proxy-debug.log`, printed on exit.
32
- - **Agent stdout mode** — clean-stdout detection for non-interactive / scripted use (`wantsCleanAgentStdout('gemini', …)`).
33
-
34
- ---
35
-
36
- ## Goals
37
-
38
- - Let users drive the Gemini CLI against **any** registry provider, not just Google's native Gemini endpoints.
39
- - Reuse the single Vercel AI SDK translation path ([PRD-004](../prd-004-translation-layer/prd-004-translation-layer-index.md)) — no hand-rolled per-provider translation in the Gemini path.
40
- - Keep the host's config untouched: point the CLI at the proxy with environment variables only, and leave the user's shell unchanged on exit.
41
- - Reuse the shared registry, provider picker, preferences, and favorites already used by Claude Code and Codex ([PRD-002](../prd-002-provider-registry/prd-002-provider-registry-index.md), [PRD-008](../prd-008-preferences-tiers-favorites/prd-008-preferences-tiers-favorites-index.md)).
42
- - Support mid-session model switching without restarting the CLI.
43
-
44
- ## Non-Goals
45
-
46
- - A native Google-direct tier that bypasses the proxy. Per `src/gemini.ts:42` and the guide, **all** registry models route through the local translation proxy; Google's own provider entry simply routes through the same proxy back to Google's endpoint via the SDK.
47
- - Surfacing Gemini-internal reasoning (`part.thought`) into the host — it is intentionally dropped (`src/gemini-parts.ts:24`); `includeThoughts` is disabled upstream.
48
- - Refreshing the model name shown in the Gemini CLI UI after a mid-session `.model` switch (a Gemini CLI UI limitation, documented in the guide).
49
- - Writing or restoring any Gemini CLI config file (env-var-only by design).
50
-
51
- ---
52
-
53
- ## Features
54
-
55
- | # | Feature | Where |
56
- |---|---------|-------|
57
- | F1 | `rflectr gemini` launch orchestration | `src/gemini.ts:58` |
58
- | F2 | Gemini binary discovery (PATH + platform fallbacks) | `src/gemini/launch.ts:23` |
59
- | F3 | Env isolation → `GOOGLE_GEMINI_BASE_URL` + proxy-token `GEMINI_API_KEY` | `src/gemini/launch.ts:41` |
60
- | F4 | Spawn Gemini CLI with `-m <modelId>` + passthrough flags | `src/gemini/launch.ts:57` |
61
- | F5 | gemini-proxy: model list + detail endpoints | `src/gemini-proxy.ts:262`, `:274` |
62
- | F6 | gemini-proxy: `:generateContent` / `:streamGenerateContent` | `src/gemini-proxy.ts:289` |
63
- | F7 | `translateGeminiRequest` — Gemini REST → SDK params | `src/gemini-proxy.ts:75` |
64
- | F8 | Gemini-format SSE / JSON response emission | `src/gemini-proxy.ts:342`, `:436` |
65
- | F9 | `.model <id>` mid-session switch + history sanitization | `src/gemini-proxy.ts:306`, `:537` |
66
- | F10 | `thought_signature` extraction from tool-call parts | `src/gemini-parts.ts:8` |
67
- | F11 | Gemini-part → Anthropic-block parsing + usage mapping | `src/gemini-parts.ts:16`, `:76` |
68
- | F12 | Provider / model / favorites pickers | `src/gemini/prompts.ts:15`, `:55`, `:127` |
69
- | F13 | Managed-flag rejection (`--provider`/`--model`/`-m`/`--trace`) | `src/gemini/prompts.ts:167` |
70
- | F14 | Favorites catalog mode → proxy routes | `src/gemini.ts:208` |
71
- | F15 | `--trace` debug logging | `src/gemini.ts:294`, `src/gemini-proxy.ts:217` |
72
-
73
- ---
74
-
75
- ## Architecture & Implementation
76
-
77
- ### Launch flow (`rflectr gemini`)
78
-
79
- ```
80
- runGeminiCommand(geminiArgs, trace, launch) [src/gemini.ts:58]
81
- → findGeminiBinary() [gemini/launch.ts:23 — which/where + fallback paths]
82
- → rejectGeminiManagedFlags(geminiArgs) [gemini/prompts.ts:167 — strip rflectr-managed flags]
83
- → wantsCleanAgentStdout('gemini', …) [launch-target — agent stdout mode]
84
- → fetchProviderCatalog({ agent: 'gemini' }) [provider-catalog — registry providers]
85
- → providersForPicker(catalog) [filter to Gemini-compatible providers]
86
-
87
- ── interactive path ──
88
- → pickGeminiProvider(...) [gemini/prompts.ts:15 — incl. ⭐ Favorites Catalog]
89
- → pickGeminiModel / pickGeminiFavoriteModel [gemini/prompts.ts:55 / :127]
90
- → confirmGeminiLaunch(...) [gemini/prompts.ts:110]
91
-
92
- ── boot path (--provider + --model / non-interactive) ──
93
- → findProviderAndModel(compatible, target) [skip wizard]
94
-
95
- ── shared ──
96
- → recordLaunchSelection('gemini', …) [config — saves lastGeminiProvider/Model]
97
- → resolveLocalProviderApiKey(activeProvider) [provider-catalog — real key from keychain/registry]
98
- → build ProxyRoute[] for provider models + favorites + starting model [gemini.ts:189–267]
99
- → startGeminiProxy(finalRoutes, trace) [gemini-proxy.ts:204 — bind 127.0.0.1:0]
100
- → buildGeminiChildEnv(port, token) [gemini/launch.ts:41]
101
- → launchGemini(path, modelId, env, passthrough) [gemini/launch.ts:57 — stdio:inherit]
102
- → proxyHandle.close() [stop proxy after Gemini exits]
103
- → printTraceLog(...) when --trace [gemini.ts:294]
104
- ```
105
-
106
- Routes are assembled into a de-duplicated `Map<aliasId, ProxyRoute>` (`src/gemini.ts:237`) seeded with the active provider's models, then enriched with resolved favorites (`src/gemini.ts:208`), and finally guaranteed to contain the starting model (`src/gemini.ts:248`). Each `ProxyRoute` carries `npm`, `baseURL` (`apiBaseUrl`), `realModelId` (`upstreamModelId`), `modelFormat`, `contextWindow`, and provider auth metadata so the proxy can lazily build the right SDK `LanguageModel`.
107
-
108
- ### Env contract
109
-
110
- `buildGeminiChildEnv()` (`src/gemini/launch.ts:41`) copies `process.env`, **deletes** the four conflicting Gemini credential vars — `GOOGLE_GEMINI_BASE_URL`, `GEMINI_API_KEY`, `GOOGLE_API_KEY`, `GOOGLE_GENAI_API_KEY` — then sets exactly two:
111
-
112
- | Var | Value | Purpose |
113
- |-----|-------|---------|
114
- | `GOOGLE_GEMINI_BASE_URL` | `http://127.0.0.1:<proxyPort>` | Points the Gemini CLI at the local proxy. |
115
- | `GEMINI_API_KEY` | `<random proxy token>` | A per-session UUID the proxy issues (`startGeminiProxy` `proxyToken`); the user's real key never enters the child. |
116
-
117
- Isolation applies to the child process only; the parent shell is unmodified on exit. `launchGemini()` additionally passes `-m <modelId>` so the CLI requests the selected model by default.
118
-
119
- ### gemini-proxy translation
120
-
121
- ```mermaid
122
- flowchart LR
123
- cli["Gemini CLI"]
124
- cli -->|"GOOGLE_GEMINI_BASE_URL"| proxy["gemini-proxy<br/>127.0.0.1:port"]
125
- proxy -->|"translateGeminiRequest"| sdk["Vercel AI SDK<br/>streamText / generateText"]
126
- sdk --> upstream["Anthropic / xAI / OpenAI / Google / …"]
127
- upstream -->|"fullStream → Gemini SSE"| proxy
128
- proxy -->|":streamGenerateContent SSE"| cli
129
- ```
130
-
131
- The proxy (`startGeminiProxy`, `src/gemini-proxy.ts:204`) binds `127.0.0.1:0`, lazily builds and caches one `LanguageModel` per route via `createLanguageModel()` ([PRD-004](../prd-004-translation-layer/prd-004-translation-layer-index.md), [PRD-005](../prd-005-local-proxy-catalog-routing/prd-005-local-proxy-catalog-routing-index.md)), and serves three surfaces:
132
-
133
- 1. **`GET .../models`** (`src/gemini-proxy.ts:262`) — returns a synthetic catalog where each route is formatted as a Gemini model (`formatGeminiModel`, `src/gemini-proxy.ts:244`) with `inputTokenLimit` from the route's `contextWindow` and `supportedGenerationMethods`.
134
- 2. **`GET .../models/<model>`** (`src/gemini-proxy.ts:274`) — single-model detail, resolved via `lookupGeminiRoute` (which tolerates slash- and `__`-prefixed ids and falls back to the default route).
135
- 3. **`POST .../<model>:generateContent` / `:streamGenerateContent`** (`src/gemini-proxy.ts:289`) — the inference path.
136
-
137
- **`translateGeminiRequest`** (`src/gemini-proxy.ts:75`) converts a Gemini REST body into SDK params:
138
- - **System instruction** — joins `systemInstruction.parts`, then strips Gemini-CLI-injected identity (`stripGeminiIdentity`, `src/gemini-proxy.ts:68`) so the host model is not told it is "Gemini CLI".
139
- - **Contents → messages** — `role: 'model'` → `assistant`; `text` parts (with inline `<thinking>` split into `reasoning`/`text`), `inlineData` → image parts, `functionCall` → SDK `tool-call` (synthesizing a `call_<uuid>` id and tracking name→id lists), and `functionResponse` → SDK `tool-result` (popping the matching id). Consecutive same-role messages are merged (`mergeConsecutiveMessages`).
140
- - **Tools** — each `functionDeclarations[]` entry becomes an SDK `tool()` with a `jsonSchema()` input schema.
141
- - **Tool choice** — `functionCallingConfig.mode` `ANY` → `required`, `AUTO` → `auto`.
142
- - **Generation config** — `maxOutputTokens`, `temperature`, and `responseMimeType: 'application/json'` → `responseFormat: { type: 'json' }`.
143
-
144
- On the way **out**, the streaming handler (`src/gemini-proxy.ts:342`) maps the SDK `fullStream` to Gemini SSE chunks: `reasoning` deltas are wrapped in `<thinking>…</thinking>` text (auto-closed before tool calls / finish), `text-delta` becomes model-role text parts, tool-input deltas are buffered and emitted as a single `functionCall` part on `tool-call`, and `finish` carries `finishReason` (mapped by `mapFinishReason`, `src/gemini-proxy.ts:16`) plus `usageMetadata`. The non-streaming branch (`src/gemini-proxy.ts:436`) builds the equivalent single JSON `candidates[0]` response.
145
-
146
- ### `thought_signature` and Gemini parts
147
-
148
- `src/gemini-parts.ts` provides the shared Gemini-part → Anthropic-block parsing used where Gemini output is consumed in Anthropic terms. `partThoughtSignature` (`src/gemini-parts.ts:8`) reads the signature from either the part itself (`thoughtSignature` / `thought_signature`) or the nested `functionCall`, and `parseGeminiPart` (`src/gemini-parts.ts:16`) folds that signature into the encoded `tool_use.id` via `encodeToolUseId` (the `{id}::ts::{signature}` round-trip from [PRD-004](../prd-004-translation-layer/prd-004-translation-layer-index.md)). Gemini-internal reasoning parts (`part.thought`) are dropped (`src/gemini-parts.ts:24`). `mapGeminiUsage` (`src/gemini-parts.ts:76`) normalizes Gemini `usageMetadata` (subtracting cached tokens) into the Anthropic usage shape.
149
-
150
- ### Mid-session `.model` switch
151
-
152
- The proxy inspects the last user turn (`findLastUserTurn`) and parses a `.model` command (`parseModelCommand`, `src/gemini-proxy.ts:572`). `.model` alone replies with the current model and the available list; `.model <id>` resolves a route via `lookupGeminiRoute`, sets the in-memory `sessionRouteOverride`, and replies with a mock confirmation (`sendMockGeminiResponse`). Subsequent requests have the synthetic switch turns stripped via `sanitizeModelSwitchTurns` (`src/gemini-proxy.ts:537`) so the upstream model never sees the command exchange. (The CLI's displayed model name does not refresh — a documented Gemini CLI UI limitation.)
153
-
154
- ---
155
-
156
- ## Acceptance Criteria
157
-
158
- - [x] `rflectr gemini` discovers the Gemini binary on PATH with platform-specific fallbacks (`src/gemini/launch.ts:23`).
159
- - [x] All registry models route through the local translation proxy (`src/gemini.ts:42`; routes built at `src/gemini.ts:189`).
160
- - [x] The child env points the CLI at `http://127.0.0.1:<port>` via `GOOGLE_GEMINI_BASE_URL` and supplies a random proxy-token `GEMINI_API_KEY`, clearing conflicting `GOOGLE_API_KEY` / `GOOGLE_GENAI_API_KEY` (`src/gemini/launch.ts:41`).
161
- - [x] The proxy serves the Gemini REST surface: `GET .../models`, `GET .../models/<model>`, and `POST .../<model>:generateContent` / `:streamGenerateContent` (`src/gemini-proxy.ts:262`, `:274`, `:289`).
162
- - [x] `translateGeminiRequest` converts system instruction, contents (text/image/functionCall/functionResponse), tools, tool choice, and generation config into SDK params (`src/gemini-proxy.ts:75`).
163
- - [x] Streaming responses are emitted as Gemini SSE chunks with reasoning wrapped in `<thinking>`, buffered tool-call parts, and a finish chunk with `usageMetadata` (`src/gemini-proxy.ts:342`).
164
- - [x] `thought_signature` is read from tool-call parts and round-tripped through the encoded tool-use id (`src/gemini-parts.ts:8`, `:16`).
165
- - [x] Gemini-CLI self-identification is stripped from system and user content (`src/gemini-proxy.ts:68`).
166
- - [x] A `.model <id>` chat command switches routes in-session without restarting and is sanitized out of request history (`src/gemini-proxy.ts:306`, `:537`).
167
- - [x] Favorites are resolved into proxy routes and shown in the provider/model pickers (`src/gemini.ts:208`, `src/gemini/prompts.ts:127`).
168
- - [x] rflectr-managed flags (`--provider`, `--model`, `-m`, `--trace`) are stripped from passthrough args (`src/gemini/prompts.ts:167`).
169
- - [x] `--trace` writes proxy debug logs to `~/.rflectr/logs/gemini-proxy-debug.log` and prints them on exit (`src/gemini.ts:294`, `src/gemini-proxy.ts:217`).
170
- - [x] The proxy is closed after the Gemini CLI exits; the parent shell is left unchanged (`src/gemini.ts:288`).
171
-
172
- ---
173
-
174
- ## Files
175
-
176
- | File | Role |
177
- |------|------|
178
- | `src/gemini.ts` | `rflectr gemini` command + launch orchestration + route assembly. |
179
- | `src/gemini/launch.ts` | Binary discovery, child env contract, child spawn. |
180
- | `src/gemini/prompts.ts` | Provider / model / favorites pickers, launch confirm, managed-flag rejection. |
181
- | `src/gemini-proxy.ts` | Local Gemini-REST ↔ SDK translation proxy, model endpoints, `.model` switch. |
182
- | `src/gemini-parts.ts` | Gemini-part → Anthropic-block parsing, `thought_signature`, usage mapping. |
183
- | `src/provider-factory.ts` | `createLanguageModel()` — SDK provider construction (shared, PRD-004). |
184
- | `src/sdk-adapter.ts` | Vercel AI SDK translation core (shared, PRD-004). |
185
- | `src/provider-catalog.ts` | `fetchProviderCatalog`, `providersForPicker`, `resolveLocalProviderApiKey` (shared, PRD-002). |
186
- | `library/knowledge/private/integrations/harnesses.md` | Host-harness pattern reference. |
187
- | `library/knowledge/public/guides/gemini-cli.md` | User-facing guide (Experimental). |
188
-
189
- ---
190
-
191
- ## Risks & Known Limitations
192
-
193
- - **Experimental maturity.** The guide flags the Gemini path as newer than Claude/Codex; expect rough edges (`gemini-cli.md`).
194
- - **Stale model name in the CLI UI** after a mid-session `.model` switch — the new route is active, but the Gemini CLI's top-right model label does not refresh. This is a Gemini CLI UI limitation, not a routing bug.
195
- - **Gemini-internal reasoning is dropped.** `part.thought` content is never surfaced (`src/gemini-parts.ts:24`); only tool-call `thought_signature` round-trips.
196
- - **Identity-stripping is heuristic.** `stripGeminiIdentity` uses regexes against "Gemini CLI" phrasing; wording changes in the CLI's injected prompts could let some brand mentions through.
197
- - **Cost display** in the Gemini CLI is inaccurate for non-Google models (host applies its own pricing), consistent with the cross-harness limitation.
198
- - **JSON parse errors on first stdout lines** in agent/automation mode unless `-o stream-json` / `-o json` is passed (documented in the guide's troubleshooting table).
199
-
200
- ---
201
-
202
- ## Related
203
-
204
- - [PRD-002: Provider Registry](../prd-002-provider-registry/prd-002-provider-registry-index.md) — the registry, provider picker, and key resolution reused here.
205
- - [PRD-004: Translation Layer (SDK Adapter)](../prd-004-translation-layer/prd-004-translation-layer-index.md) — the single Vercel AI SDK translation path and the `thought_signature` `::ts::` round-trip.
206
- - [PRD-005: Local Proxy & Catalog Routing](../prd-005-local-proxy-catalog-routing/prd-005-local-proxy-catalog-routing-index.md) — the shared `ProxyRoute` / proxy pattern this proxy follows.
207
- - [PRD-008: Preferences, Tiers & Favorites](../prd-008-preferences-tiers-favorites/prd-008-preferences-tiers-favorites-index.md) — favorites catalog source.
208
- - [PRD-009: Codex Integration](../prd-009-codex-integration/prd-009-codex-integration-index.md) — sibling host harness (OpenAI Responses format).
209
- - [PRD-011: Claude Desktop Integration](../prd-011-claude-desktop-integration/prd-011-claude-desktop-integration-index.md) — sibling host harness (gateway config).
210
- - [PRD-012: Server Gateway](../prd-012-server-gateway/prd-012-server-gateway-index.md) — the in-process gateway used by the desktop apps.
211
- - Knowledge: [Host Harnesses](../../../knowledge/private/integrations/harnesses.md) · [Gemini CLI guide](../../../knowledge/public/guides/gemini-cli.md)
@@ -1,228 +0,0 @@
1
- # PRD-011: Claude Desktop Integration *(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/claude-desktop/*`, `src/claude-app.ts`
9
-
10
- ---
11
-
12
- ## Overview
13
-
14
- `rflectr claude-app` launches the **Claude Desktop** app in third-party-inference ("3P") mode, pointed at a local rflectr gateway instead of Anthropic's servers. The user picks a provider + model (or a favorites catalog), rflectr starts an in-process gateway server on a random local port, writes a gateway config into Claude Desktop's on-disk 3P config library, and opens (or restarts) the app. On exit, rflectr restores the original config.
15
-
16
- The defining constraint of this surface: **a desktop app cannot inherit environment variables** the way a launched CLI can. The CLI launchers (`rflectr claude`, `codex`, `gemini`) point the host at the proxy purely through child-process env vars and never touch a config file (see [PRD-001 — CLI Core & Launch Orchestration](../prd-001-cli-core-launch-orchestration/prd-001-cli-core-launch-orchestration-index.md)). Claude Desktop has no such hook. So this is the one surface where rflectr **writes the host application's own config file** — and therefore must back it up and restore it on exit, guarded by a lock file to survive crashes. This is the deliberate exception to rflectr's env-only isolation contract.
17
-
18
- Entry point: `runClaudeAppCommand` (`src/claude-app.ts:61`). Supported on **macOS and Windows only** (`claudeAppSupported`, `src/claude-desktop/app-launch.ts:11`).
19
-
20
- > See also: [`harnesses.md`](../../../knowledge/private/integrations/harnesses.md) (private integration notes — the "where each host departs from the pattern" overview) and [`claude-desktop.md`](../../../knowledge/public/guides/claude-desktop.md) (public user-facing setup guide).
21
-
22
- ---
23
-
24
- ## What Was Built
25
-
26
- - A `rflectr claude-app` command that, on macOS/Windows, drives Claude Desktop into 3P mode against a local gateway with no manual config editing.
27
- - A **config-write** path: a `<uuid>.json` gateway config is written into the Claude Desktop 3P config library, and `_meta.json`'s `appliedId` is repointed at it so the app picks it up on next launch (`writeRflectrConfig`, `src/claude-desktop/app-config.ts:58`).
28
- - A **backup + restore** path: `_meta.json` is copied to `_meta.json.bak` before the patch, and a `.rflectr.lock` file records the live session. On clean exit, crash recovery, or `--restore`, the backup is restored and the injected config removed (`src/claude-desktop/app-session.ts`).
29
- - **Session lifecycle** handling: concurrent-session detection, stale-session recovery on startup, SIGINT/SIGTERM-driven shutdown, and a `process.on('exit')` cleanup hook.
30
- - **Model selection** reusing the Codex provider/model pickers, plus a favorites-catalog mode driven by saved preferences.
31
- - The gateway itself is the full `server` gateway (`startServer`, see [PRD-012 — Server Gateway](../prd-012-server-gateway/prd-012-server-gateway-index.md)), serving `/anthropic` with a synthetic model catalog — not a bespoke per-protocol proxy.
32
-
33
- ---
34
-
35
- ## Goals
36
-
37
- - Let a user route Claude Desktop's **Cowork** and **Code** tabs at registry providers / OpenCode Zen / Go with one command, no manual Developer-menu config.
38
- - Never leave Claude Desktop's config in a broken 3P state after the rflectr session ends — restore the prior config on exit and after crashes.
39
- - Keep the model catalog under the user's control (single selected model, or a favorites-only catalog).
40
- - Reuse the existing `server` gateway and SDK translation layer rather than building a Claude-Desktop-specific proxy.
41
-
42
- ## Non-Goals
43
-
44
- - **Linux support.** macOS and Windows only; `claudeAppSupported()` throws otherwise (`src/claude-desktop/app-launch.ts:11`).
45
- - **Restoring Claude Desktop to first-party (Anthropic sign-in / Chat tab) mode.** rflectr restores the *pre-session* 3P config state; full 1P revert is a documented manual procedure in the user guide ([`claude-desktop.md`](../../../knowledge/public/guides/claude-desktop.md) → "Restore Claude Desktop to Anthropic's servers").
46
- - **The Chat tab.** With 3P inference, Claude Desktop offers only Cowork and Code; Chat is an Anthropic product constraint, not an rflectr limitation.
47
- - **Editing `settings.json`-style host config beyond the 3P config library.** rflectr only writes the `<uuid>.json` and `_meta.json` under `Claude-3p/configLibrary/`.
48
- - **Mid-session model switching** inside the running app (the Gemini CLI surface has a `.model` switch; Claude Desktop does not).
49
-
50
- ---
51
-
52
- ## Features
53
-
54
- | # | Feature | Implementation |
55
- |---|---------|----------------|
56
- | F1 | `rflectr claude-app` command + help/restore/trace flags | `runClaudeAppCommand`, `claudeAppHelpText` (`src/claude-app.ts:27`, `src/claude-app.ts:61`) |
57
- | F2 | Platform gate (macOS/Windows only) | `claudeAppSupported` (`src/claude-desktop/app-launch.ts:11`) |
58
- | F3 | App discovery (Claude.app / Claude.exe / Start menu) | `findClaudeApp` (`src/claude-desktop/app-launch.ts:68`) |
59
- | F4 | Launch or restart the app | `launchOrRestartClaudeApp` (`src/claude-desktop/app-launch.ts:198`) |
60
- | F5 | Provider + model picker (reuses Codex pickers) | `pickCodexProvider` / `pickCodexModel` via `src/claude-app.ts:127`–`134` |
61
- | F6 | Favorites-catalog mode | `favorites.length > 0` branch + `filterServerModelsByFavorites` (`src/claude-app.ts:121`, `src/claude-app.ts:153`) |
62
- | F7 | Gateway config write into 3P config library | `writeRflectrConfig` (`src/claude-desktop/app-config.ts:58`) |
63
- | F8 | `_meta.json` backup before patch | `backupMetaJson` (`src/claude-desktop/app-session.ts:43`) |
64
- | F9 | Lock file (pid / startedAt / uuid / proxyPort) | `ClaudeSessionLock`, `writeSessionLock` (`src/claude-desktop/app-session.ts:7`, `:28`) |
65
- | F10 | Concurrent + stale session detection | `isConcurrentLiveSession`, `hasStaleSession` (`src/claude-desktop/app-session.ts:76`, `:67`) |
66
- | F11 | Shutdown wait (SIGINT/SIGTERM) + exit-hook cleanup | `waitForShutdown`, `setupExitCleanup` (`src/claude-desktop/app-session.ts:94`, `:119`) |
67
- | F12 | Restore on exit / crash / `--restore` | `cleanupSession`, `recoverSession` (`src/claude-desktop/app-session.ts:113`, `:82`) |
68
- | F13 | Local gateway (the `server` gateway, `/anthropic`) | `startServer` + `createGatewayModelCatalog` (`src/claude-app.ts:183`) |
69
- | F14 | Recent-models persistence per provider | `savePreferences` recent-models update (`src/claude-app.ts:206`–`212`) |
70
-
71
- ---
72
-
73
- ## Architecture & Implementation
74
-
75
- ### Where this surface departs from the env-only rule
76
-
77
- CLI hosts are pointed at the proxy through child-process environment variables only (PRD-001). A desktop app has no env to inherit, so Claude Desktop reads a **config file** at launch. rflectr therefore writes that config — and to keep the rule "never permanently mutate the host" intact, it pairs every write with a backup and a guaranteed restore.
78
-
79
- ```mermaid
80
- flowchart TD
81
- cli["CLI hosts (claude, codex, gemini)"] -->|child-process env vars| proxy["translating proxy / gateway"]
82
- app["Desktop app (claude-app)"] -->|"writes &lt;uuid&gt;.json + repoints _meta.json"| proxy
83
- app -.->|"_meta.json.bak + .rflectr.lock"| restore["restore original config on exit / crash"]
84
- proxy --> gw["server gateway /anthropic + SDK adapter"]
85
- ```
86
-
87
- ### Gateway config shape
88
-
89
- `buildRflectrConfig(proxyPort)` (`src/claude-desktop/app-config.ts:48`) produces the 3P gateway profile:
90
-
91
- ```jsonc
92
- {
93
- "inferenceProvider": "gateway",
94
- "inferenceGatewayBaseUrl": "http://127.0.0.1:<port>/anthropic",
95
- "inferenceGatewayApiKey": "dummy",
96
- "inferenceGatewayAuthScheme": "bearer",
97
- "coworkEgressAllowedHosts": ["*"]
98
- }
99
- ```
100
-
101
- - `inferenceGatewayBaseUrl` ends in `/anthropic` with **no `/v1` suffix** — Claude Desktop appends `/v1/models` and `/v1/messages` itself; a `/anthropic/v1` URL would break discovery and inference (documented in [`claude-desktop.md`](../../../knowledge/public/guides/claude-desktop.md)).
102
- - The API key is the literal `'dummy'` because local-mode gateway has no server password; the server is started with `apiKey: 'dummy'` / `serverPassword: null` (`src/claude-app.ts:186`–`187`).
103
-
104
- ### Config write + `_meta.json` repointing
105
-
106
- `writeRflectrConfig(proxyPort)` (`src/claude-desktop/app-config.ts:58`):
107
-
108
- 1. Generates a `randomUUID()` and writes `buildRflectrConfig(...)` to `configLibrary/<uuid>.json`.
109
- 2. Reads (or initializes) `_meta.json`, sets `appliedId = uuid`, and appends an `entries` row `{ id: uuid, name: 'Rflectr Gateway' }` if not already present.
110
- 3. Returns the uuid (used as the session/cleanup key).
111
-
112
- Config roots, by platform (`getClaudeDesktopHome`, `src/claude-desktop/app-config.ts:8`):
113
-
114
- | Platform | 3P config root |
115
- |---|---|
116
- | macOS | `~/Library/Application Support/Claude-3p/` |
117
- | Windows | `%LOCALAPPDATA%\Claude-3p/` |
118
-
119
- The config library and `_meta.json` live under `configLibrary/` within that root (`getConfigLibraryPath`, `getMetaJsonPath`, `src/claude-desktop/app-config.ts:15`, `:19`).
120
-
121
- ### Backup / restore via lock files
122
-
123
- The lock and backup are the safety mechanism that makes config-writing reversible:
124
-
125
- - **Backup:** `backupMetaJson()` copies `_meta.json` → `_meta.json.bak` *before* the patch (`src/claude-desktop/app-session.ts:43`). Called at `src/claude-app.ts:181`, before `startServer` and `writeRflectrConfig`.
126
- - **Lock:** `writeSessionLock({ pid, startedAt, uuid, proxyPort })` writes `.rflectr.lock` in the 3P home (`src/claude-desktop/app-session.ts:28`; shape `ClaudeSessionLock`, `:7`). Written at `src/claude-app.ts:196` right after the config is applied.
127
- - **Restore:** `restoreMetaJson()` copies the `.bak` back over `_meta.json` and deletes the backup (`src/claude-desktop/app-session.ts:51`). `removeRflectrConfig(uuid)` deletes the injected `<uuid>.json` (`:60`).
128
- - **Cleanup entry points:** `cleanupSession(uuid)` (clean exit, `:113`) and `recoverSession()` (crash / `--restore`, `:82`) both run restore + config-removal + lock deletion. `setupExitCleanup(uuid)` registers `cleanupSession` on `process.on('exit')` as a last-resort net (`:119`).
129
-
130
- ### Session lifecycle
131
-
132
- ```mermaid
133
- sequenceDiagram
134
- participant U as User
135
- participant R as rflectr claude-app
136
- participant FS as Claude-3p config
137
- participant App as Claude Desktop
138
-
139
- U->>R: rflectr claude-app
140
- R->>R: claudeAppSupported() + TTY check
141
- R->>R: isConcurrentLiveSession()? -> abort if live
142
- R->>R: hasStaleSession()? -> recoverSession()
143
- R->>R: pick provider + model (or favorites)
144
- R->>FS: backupMetaJson() (_meta.json -> .bak)
145
- R->>R: startServer() on 127.0.0.1:0
146
- R->>FS: writeRflectrConfig(port) (<uuid>.json + _meta.appliedId)
147
- R->>FS: writeSessionLock({pid,uuid,port})
148
- R->>R: setupExitCleanup(uuid)
149
- R->>App: launchOrRestartClaudeApp()
150
- U-->>R: Ctrl+C (SIGINT)
151
- R->>R: waitForShutdown() resolves
152
- R->>FS: cleanupSession(uuid) (restore .bak, rm config, rm lock)
153
- R->>App: optionally quitClaudeAppGracefully()
154
- ```
155
-
156
- Startup guards (`src/claude-app.ts`):
157
- - **Interactive-terminal required** — non-TTY aborts (`src/claude-app.ts:84`).
158
- - **Concurrent session** — `isConcurrentLiveSession()` (lock present + pid alive) aborts with a "stop it with Ctrl+C" message (`src/claude-app.ts:90`; `src/claude-desktop/app-session.ts:76`).
159
- - **Stale session** — `hasStaleSession()` (lock present + pid dead) triggers `recoverSession()` to clean up a prior crash before proceeding (`src/claude-app.ts:96`).
160
-
161
- Shutdown ordering (`src/claude-app.ts:232`–`245`): `waitForShutdown()` resolves on SIGINT/SIGTERM, then `cleanupSession(uuid)` runs **before** the optional "close Claude Desktop?" prompt — so the config is restored ASAP and a second Ctrl+C during the prompt finds nothing left to undo (per the inline comment at `src/claude-app.ts:235`).
162
-
163
- ### Model selection
164
-
165
- - Providers come from `fetchProviderCatalog({ agent: 'codex-app' })`, filtered by `codexCompatibleProviders(..., 'codex-app')` (`src/claude-app.ts:105`, `:113`). The provider's models are narrowed with `routableModelsForProvider(provider, 'codex-app')` (`providerForClaudePicker`, `src/claude-app.ts:57`).
166
- - **Single-model mode:** the picked model becomes a one-entry `ServerModelInfo[]` carrying `modelFormat`, `npm`, `apiBaseUrl`, `baseUrl`, `completionsUrl`, `upstreamModelId`, `contextWindow`, and the resolved provider `apiKey` (`src/claude-app.ts:157`–`174`). Credential resolved via `activeProvider.apiKey` or `resolveProviderCredential(id, authRef)` (`src/claude-app.ts:138`–`148`).
167
- - **Favorites mode:** when `prefs.favoriteModels.length > 0`, a `__favorites__` picker option loads all server models and filters them with `filterServerModelsByFavorites(allModels, favorites)` (`src/claude-app.ts:153`–`155`).
168
- - Either way, the model list is wrapped in `createGatewayModelCatalog(serverModels, { maskGatewayIds: true })` and handed to `startServer` (`src/claude-app.ts:188`). Discovery-id masking is on so Claude Desktop's competitor-name filtering doesn't hide models (rationale in [`claude-desktop.md`](../../../knowledge/public/guides/claude-desktop.md) troubleshooting).
169
- - Recent models per provider are persisted (single-mode only) via `savePreferences` (`src/claude-app.ts:206`–`212`).
170
-
171
- ### App discovery & launch (platform specifics)
172
-
173
- `findClaudeApp()` (`src/claude-desktop/app-launch.ts:68`):
174
- - **macOS:** checks `/Applications/Claude.app` and `~/Applications/Claude.app`, then falls back to `mdfind` by bundle id `com.anthropic.claudefordesktop`.
175
- - **Windows:** checks `%LOCALAPPDATA%\Programs\Claude` and `%LOCALAPPDATA%\Claude` (including `app-*` subfolders) for `Claude.exe`, then falls back to `Get-StartApps` returning a `shell:AppsFolder\<AppID>` URI.
176
-
177
- `launchOrRestartClaudeApp()` (`src/claude-desktop/app-launch.ts:198`): if the app isn't running, it just opens it; if it is running, it prompts to restart (so the new config is read), quits gracefully (`osascript` on macOS, `CloseMainWindow()` on Windows), waits up to 5s, force-quits Windows PIDs if needed, then reopens. "Is it running?" uses `osascript` (macOS) or PowerShell `Get-Process` / matching-PID checks (Windows) (`isClaudeAppRunning`, `:121`).
178
-
179
- ---
180
-
181
- ## Acceptance Criteria
182
-
183
- - [x] `rflectr claude-app` launches Claude Desktop in 3P gateway mode on macOS and Windows (`runClaudeAppCommand`, `src/claude-app.ts:61`; `claudeAppSupported`, `src/claude-desktop/app-launch.ts:11`).
184
- - [x] A `<uuid>.json` gateway config is written into the 3P config library with `inferenceProvider: 'gateway'`, a `/anthropic` base URL (no `/v1`), `bearer` auth, and a `'dummy'` key (`buildRflectrConfig` / `writeRflectrConfig`, `src/claude-desktop/app-config.ts:48`, `:58`).
185
- - [x] `_meta.json` `appliedId` is repointed at the new uuid and an `entries` row is added (`src/claude-desktop/app-config.ts:65`–`71`).
186
- - [x] `_meta.json` is backed up to `_meta.json.bak` before the patch (`backupMetaJson`, `src/claude-desktop/app-session.ts:43`; called `src/claude-app.ts:181`).
187
- - [x] A `.rflectr.lock` records `pid`, `startedAt`, `uuid`, and `proxyPort` (`ClaudeSessionLock` / `writeSessionLock`, `src/claude-desktop/app-session.ts:7`, `:28`).
188
- - [x] On clean exit (Ctrl+C), the original `_meta.json` is restored, the injected config removed, and the lock deleted (`cleanupSession`, `src/claude-desktop/app-session.ts:113`; invoked `src/claude-app.ts:237`).
189
- - [x] After a crash, a stale session is detected and cleaned up on next run, and `--restore` performs the same recovery (`hasStaleSession` + `recoverSession`, `src/claude-desktop/app-session.ts:67`, `:82`; `--restore` at `src/claude-app.ts:67`).
190
- - [x] A concurrent live session is detected and the second invocation aborts (`isConcurrentLiveSession`, `src/claude-desktop/app-session.ts:76`; `src/claude-app.ts:90`).
191
- - [x] Exit-hook cleanup is registered so an abrupt `process.exit` still restores config (`setupExitCleanup`, `src/claude-desktop/app-session.ts:119`; `src/claude-app.ts:204`).
192
- - [x] Both a single selected model and a favorites-only catalog can drive the gateway (`src/claude-app.ts:153`–`174`).
193
- - [x] The gateway is the shared `server` gateway serving `/anthropic`, not a bespoke proxy (`startServer` + `createGatewayModelCatalog`, `src/claude-app.ts:183`–`192`).
194
- - [x] Non-interactive terminals are rejected (`src/claude-app.ts:84`).
195
-
196
- ---
197
-
198
- ## Files
199
-
200
- | File | Role |
201
- |---|---|
202
- | `src/claude-app.ts` | Command entry: `runClaudeAppCommand`, help text, picker orchestration, server start, session wiring, shutdown |
203
- | `src/claude-desktop/app-config.ts` | 3P config paths, `buildRflectrConfig`, `writeRflectrConfig`, `_meta.json` read/write |
204
- | `src/claude-desktop/app-session.ts` | Lock file + backup/restore: `backupMetaJson`, `restoreMetaJson`, `removeRflectrConfig`, lock read/write, stale/concurrent detection, `cleanupSession`, `recoverSession`, `waitForShutdown`, `setupExitCleanup` |
205
- | `src/claude-desktop/app-launch.ts` | App discovery + launch/restart/quit per platform: `claudeAppSupported`, `findClaudeApp`, `isClaudeAppRunning`, `launchOrRestartClaudeApp`, `quitClaudeAppGracefully` |
206
-
207
- ---
208
-
209
- ## Risks & Known Limitations
210
-
211
- - **Config-editing exception to the env-only rule.** Unlike every CLI launcher, this surface writes the host application's config file. Reversibility depends entirely on the backup (`_meta.json.bak`) + lock (`.rflectr.lock`) machinery. If the backup or lock is lost, manual recovery (the documented 1P-revert procedure) is required.
212
- - **Backup granularity.** Only `_meta.json` is backed up; the injected `<uuid>.json` is removed by uuid on cleanup. If `_meta.json` is mutated by Claude Desktop itself between backup and restore, restore overwrites those changes with the pre-session snapshot.
213
- - **`process.on('exit')` constraints.** The exit hook runs synchronous cleanup; it cannot await async work. A hard kill (`SIGKILL`) bypasses both the SIGINT/SIGTERM handler and the exit hook, leaving a stale session for the next-run / `--restore` recovery to clean up.
214
- - **Linux unsupported.** `claudeAppSupported()` throws on any non-darwin/non-win32 platform.
215
- - **No mid-session model switch.** The catalog is fixed at launch; changing models means restarting the session.
216
- - **Chat tab unavailable in 3P mode** (Anthropic product constraint). Claude in Chrome is also incompatible with a gateway. Both documented in [`claude-desktop.md`](../../../knowledge/public/guides/claude-desktop.md).
217
- - **Full 1P revert is manual.** rflectr restores the pre-session 3P config but does not return Claude Desktop to Anthropic sign-in; the user guide documents the multi-step manual revert.
218
-
219
- ---
220
-
221
- ## Related
222
-
223
- - [`harnesses.md`](../../../knowledge/private/integrations/harnesses.md) — private integration notes (the host-departure pattern, platform-differences table).
224
- - [`claude-desktop.md`](../../../knowledge/public/guides/claude-desktop.md) — public setup, gateway cheat sheet, restore-to-1P, troubleshooting.
225
- - [PRD-009 — Codex Integration](../prd-009-codex-integration/prd-009-codex-integration-index.md) — sibling desktop-app surface (`codex-app`) using the same config-patch + backup/restore-via-lock pattern.
226
- - [PRD-005 — Local Proxy & Catalog Routing](../prd-005-local-proxy-catalog-routing/prd-005-local-proxy-catalog-routing-index.md) — the proxy/translation machinery the gateway builds on.
227
- - [PRD-012 — Server Gateway](../prd-012-server-gateway/prd-012-server-gateway-index.md) — the `startServer` gateway that serves `/anthropic` for Claude Desktop.
228
- - [PRD-001 — CLI Core & Launch Orchestration](../prd-001-cli-core-launch-orchestration/prd-001-cli-core-launch-orchestration-index.md) — the env-only isolation contract this surface deliberately departs from.