clauderipple 0.2.0
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.
- package/CHANGELOG.md +229 -0
- package/LICENSE +674 -0
- package/README.ko.md +328 -0
- package/README.md +372 -0
- package/bin/clauderipple.js +12 -0
- package/dist/app/assets/trayDownTemplate.png +0 -0
- package/dist/app/assets/trayDownTemplate@2x.png +0 -0
- package/dist/app/assets/trayTemplate.png +0 -0
- package/dist/app/assets/trayTemplate@2x.png +0 -0
- package/dist/app/assets/trayWarnTemplate.png +0 -0
- package/dist/app/assets/trayWarnTemplate@2x.png +0 -0
- package/dist/app/assets/trayWin.png +0 -0
- package/dist/app/assets/trayWin@2x.png +0 -0
- package/dist/app/assets/trayWinDown.png +0 -0
- package/dist/app/assets/trayWinDown@2x.png +0 -0
- package/dist/app/assets/trayWinWarn.png +0 -0
- package/dist/app/assets/trayWinWarn@2x.png +0 -0
- package/dist/app/dist/main.js +518 -0
- package/dist/cli/src/browser.js +21 -0
- package/dist/cli/src/bundle.js +51 -0
- package/dist/cli/src/certs.js +33 -0
- package/dist/cli/src/claude-auth.js +112 -0
- package/dist/cli/src/codex.js +172 -0
- package/dist/cli/src/gen-certs.js +7 -0
- package/dist/cli/src/hooks/agent-title.js +160 -0
- package/dist/cli/src/index.js +489 -0
- package/dist/cli/src/launchd.js +183 -0
- package/dist/cli/src/picker.js +166 -0
- package/dist/cli/src/probe.js +55 -0
- package/dist/cli/src/runtime.js +62 -0
- package/dist/cli/src/schtasks.js +134 -0
- package/dist/cli/src/settings.js +142 -0
- package/dist/cli/src/supervisor.js +100 -0
- package/dist/cli/src/tray.js +85 -0
- package/dist/router/src/admin.js +945 -0
- package/dist/router/src/bootstrap.js +80 -0
- package/dist/router/src/certs.js +65 -0
- package/dist/router/src/compat.js +172 -0
- package/dist/router/src/config.js +179 -0
- package/dist/router/src/health.js +45 -0
- package/dist/router/src/identity.js +51 -0
- package/dist/router/src/index.js +144 -0
- package/dist/router/src/ingress/models.js +29 -0
- package/dist/router/src/ingress/server.js +400 -0
- package/dist/router/src/ingress/translate.js +457 -0
- package/dist/router/src/log.js +81 -0
- package/dist/router/src/picker.js +74 -0
- package/dist/router/src/presets.js +267 -0
- package/dist/router/src/providers/anthropic-observed.js +88 -0
- package/dist/router/src/providers/anthropic-token-file.js +48 -0
- package/dist/router/src/providers/anthropic.js +203 -0
- package/dist/router/src/providers/chatgpt/auth.js +226 -0
- package/dist/router/src/providers/chatgpt/index.js +274 -0
- package/dist/router/src/providers/chatgpt/sse.js +28 -0
- package/dist/router/src/providers/chatgpt/translate.js +393 -0
- package/dist/router/src/providers/claude-oauth.js +252 -0
- package/dist/router/src/providers/openai/index.js +193 -0
- package/dist/router/src/providers/openai/translate.js +504 -0
- package/dist/router/src/proxy.js +724 -0
- package/dist/router/src/redact.js +43 -0
- package/dist/router/src/requestlog.js +346 -0
- package/dist/router/src/routing.js +113 -0
- package/dist/router/src/version.js +8 -0
- package/dist/router/src/x509.js +203 -0
- package/dist/ui/app.js +1228 -0
- package/dist/ui/i18n.js +95 -0
- package/dist/ui/index.html +104 -0
- package/dist/ui/presets-fallback.js +61 -0
- package/dist/ui/style.css +347 -0
- package/docs/ARCHITECTURE.md +441 -0
- package/package.json +66 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# ClaudeRipple — Architecture and verified facts
|
|
2
|
+
|
|
3
|
+
Last verified: 2026-09-11 against Claude Desktop 1.52386.0, Claude Code CLI 2.1.266.
|
|
4
|
+
Everything below is backed by a document, a file, or a measured log. Items marked
|
|
5
|
+
**(assumption)** are not.
|
|
6
|
+
|
|
7
|
+
## 1. The mechanism
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
~/.claude/settings.json
|
|
11
|
+
env.HTTPS_PROXY = http://127.0.0.1:<port>
|
|
12
|
+
env.NODE_EXTRA_CA_CERTS = <state dir>/ca.pem
|
|
13
|
+
|
|
14
|
+
CLI: CONNECT api.anthropic.com → ClaudeRipple terminates TLS (leaf cert signed by ca.pem)
|
|
15
|
+
per request:
|
|
16
|
+
model matches a route → rewrite model (+effort), send to provider adapter
|
|
17
|
+
otherwise → forward byte-for-byte to api.anthropic.com over real TLS
|
|
18
|
+
CLI: CONNECT anything else → blind tunnel
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Why this path is stable:
|
|
22
|
+
|
|
23
|
+
- Anthropic's [corporate proxy docs](https://code.claude.com/docs/en/corporate-proxy)
|
|
24
|
+
state that in Desktop sessions the CLI reads `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY`
|
|
25
|
+
and `NODE_EXTRA_CA_CERTS` from managed settings and `~/.claude/settings.json`,
|
|
26
|
+
and that before v2.1.217 these were ignored. The change was deliberate and its
|
|
27
|
+
direction was "narrow the sources", not "remove". Enterprise TLS-inspecting
|
|
28
|
+
proxies (Zscaler, Netskope) are named as supported.
|
|
29
|
+
- The app itself does **not** pass `settings.json` env to the CLI. The CLI reads
|
|
30
|
+
it. The app only injects OS proxy settings and its own CA bundle, and only when
|
|
31
|
+
the caller env does not already have the key (`NXe` merge in
|
|
32
|
+
`index.chunk-C0sgyfNn.js`). Managed (enterprise) settings are the one tier the
|
|
33
|
+
app force-overrides. App updates therefore do not touch this path; **CLI
|
|
34
|
+
updates are the only moving part.**
|
|
35
|
+
- The app auto-updates the CLI independently of the app (checks
|
|
36
|
+
`downloads.claude.ai/claude-code-releases/stable` every ~4h, signed manifest).
|
|
37
|
+
Health checks must key on the CLI version directory
|
|
38
|
+
`~/Library/Application Support/Claude/claude-code/<ver>/`.
|
|
39
|
+
- No certificate pinning on the CLI spawn path. `setCertificateVerifyProc` exists
|
|
40
|
+
only for the Chrome-extension pairing partition; `rejectUnauthorized` only for
|
|
41
|
+
the remote-control websocket.
|
|
42
|
+
- The CA is never installed in the keychain. `NODE_EXTRA_CA_CERTS` is a Node-only
|
|
43
|
+
trust addition, no admin password, no effect on other programs.
|
|
44
|
+
|
|
45
|
+
## 2. What the app does with the official third-party setting (why competitors lose)
|
|
46
|
+
|
|
47
|
+
Verified in `app.asar` (`.vite/build/index.chunk-C0sgyfNn.js`, `-2CuMdv3h.js`):
|
|
48
|
+
|
|
49
|
+
- The gateway setting (`inferenceGatewayBaseUrl`) is a **whole-app deployment
|
|
50
|
+
mode switch** (`Wl()` → 3P class `Jqt` vs 1P class `Qqt`).
|
|
51
|
+
- 3P mode loads `app://localhost` (local `ion-dist` bundle) instead of
|
|
52
|
+
`https://claude.ai`; claude.ai `/api/`, `/v1/` calls are stubbed with
|
|
53
|
+
`custom_3p_not_available` 503.
|
|
54
|
+
- 3P "Chat" is a Claude Code `local-agent` session, not claude.ai chat.
|
|
55
|
+
- 3P disables the Sessions bridge (Remote Control): `shouldEnableSessionsBridge(){return!1}`,
|
|
56
|
+
`hasClaudeAiProductFeatures(){return!1}`, side sessions unavailable.
|
|
57
|
+
- 1P mode routes nothing through the gateway (`managesProviderRouting(){return!1}`).
|
|
58
|
+
Partial application ("chat on Claude, Code on gateway") is impossible for anyone.
|
|
59
|
+
|
|
60
|
+
Consequence: the product boundary is the **Code tab and its subagents**. General
|
|
61
|
+
chat is out of reach for every approach, ours included.
|
|
62
|
+
|
|
63
|
+
## 3. Model picker
|
|
64
|
+
|
|
65
|
+
- The picker list is produced by the claude.ai web frontend (remote renderer),
|
|
66
|
+
from the `model_selector_config` field of the claude.ai **bootstrap** response
|
|
67
|
+
(also pushed live via `bootstrap_push_revision`). Verified in
|
|
68
|
+
`shared-common-1-rSOeStkD.js` (`model_selector_config:e.model_selector_config`)
|
|
69
|
+
and `shared-2-x4ME0hE_.js` (`modelSelectorConfig` → catalog → picker).
|
|
70
|
+
- The renderer reports the list to the app via IPC `setAvailableCodeModels(modelIds)`
|
|
71
|
+
(`shared-16-sxOyUBaU.js`). The 1P app stores it and **does not validate**
|
|
72
|
+
(`validateSessionModel(e,t){return{ok:!0}}`). The chosen id goes to the CLI as
|
|
73
|
+
`--model <id>` or a `set_model` control request, string-checked only.
|
|
74
|
+
- So: if the picker ever shows `gpt-5.6-sol`, the rest of the chain passes it
|
|
75
|
+
through unchanged. But the picker list travels over claude.ai web traffic in the
|
|
76
|
+
Electron renderer, which a CLI-only proxy cannot see.
|
|
77
|
+
- **v1 decision: alias mode.** Map existing picker entries (e.g. Opus 4.8 →
|
|
78
|
+
GPT-6 Astra) and make the mapping visible in the ClaudeRipple GUI.
|
|
79
|
+
- **Picker mode (implemented 2026-09-11, `clauderipple picker on`).** Verified in
|
|
80
|
+
the app's main bundle (`index.pre.js`): the app reads `egressProxyUrl` from its
|
|
81
|
+
own Config Library (`~/Library/Application Support/Claude-3p/configLibrary/
|
|
82
|
+
_meta.json` → `appliedId` → `<uuid>.json`, flat keys; the `-3p` userData
|
|
83
|
+
suffix is used in both deployment modes, `AW()` in `index.pre.js` — the plain
|
|
84
|
+
`Claude/` dir is NOT read, measured 2026-09-13: app log said "proxy for
|
|
85
|
+
https://claude.ai resolved (direct)") and applies it at start
|
|
86
|
+
as Chromium switches `--proxy-server` + `--proxy-bypass-list` (`DK()`), so the
|
|
87
|
+
renderer's claude.ai traffic goes through the same proxy. No MDM, no OS proxy.
|
|
88
|
+
The bootstrap is fetched by the page itself at
|
|
89
|
+
`/edge-api/bootstrap[/{org}/app_start]?statsig_hashing_algorithm=djb2&...`
|
|
90
|
+
(`window.__BOOTSTRAP_PRELOAD__`), top-level `model_selector_config`. The CA is
|
|
91
|
+
trusted in the **login keychain only** (`security add-trusted-cert -r trustRoot
|
|
92
|
+
-k login.keychain-db`; the user types their password in macOS's dialog). The
|
|
93
|
+
router mints a `claude.ai` leaf from the CA (SNI), passes claude.ai through
|
|
94
|
+
byte-for-byte except the bootstrap, where `cli.extraModels` are cloned from an
|
|
95
|
+
enabled Claude entry into every non-`chat` surface. Open question until the
|
|
96
|
+
first real run: exact surface ids and whether the renderer filters ids; the
|
|
97
|
+
router logs `PICKER injected … surfaces: …` on each bootstrap.
|
|
98
|
+
- Injecting `additional_model_options` into the Claude Code bootstrap response
|
|
99
|
+
does **not** reach the app picker (measured; the app never reads that field).
|
|
100
|
+
- **Windows uses the same mechanism** (measured end to end in a Windows 11 VM,
|
|
101
|
+
2026-09-14: picker listed the GPT models, a call routed, the model answered).
|
|
102
|
+
Two things differ and nothing else does:
|
|
103
|
+
- The Config Library lives at `%LOCALAPPDATA%\Claude-3p\configLibrary\`, not
|
|
104
|
+
`%APPDATA%`. The app creates `Claude-3p` itself but leaves it empty —
|
|
105
|
+
`configLibrary/` is ours to write, on both platforms (`_meta.json` had
|
|
106
|
+
`previousAppliedId: null` on a machine that had never been touched).
|
|
107
|
+
- The CA goes into `Cert:\CurrentUser\Root`. Windows shows a confirmation
|
|
108
|
+
dialog with the fingerprint instead of asking for a password, and **no UAC
|
|
109
|
+
prompt**: a standard, non-elevated user can do it. It asks a second time when
|
|
110
|
+
the certificate is removed, so `picker off` prompts too.
|
|
111
|
+
The app's own log line confirms the proxy took effect:
|
|
112
|
+
`[egress-proxy] pinned to fixed proxy at 127.0.0.1:<port>; OS proxy settings ignored`.
|
|
113
|
+
|
|
114
|
+
## 4. Provider adapters
|
|
115
|
+
|
|
116
|
+
- **Anthropic-compatible providers** (DeepSeek, Kimi/Moonshot, GLM, MiniMax and
|
|
117
|
+
others expose `/v1/messages`): host + model rewrite only, no translation. This
|
|
118
|
+
is the whole reason `claude-code-router` works with just `ANTHROPIC_BASE_URL`.
|
|
119
|
+
**(assumption — verify each provider's endpoint before shipping a preset.)**
|
|
120
|
+
A routed request authenticates as the provider and only as the provider: the
|
|
121
|
+
caller's `authorization` and `x-api-key` are dropped before the provider's own
|
|
122
|
+
headers are added. Presets differ in which header they use (`x-api-key` for
|
|
123
|
+
DeepSeek and MiniMax, `Authorization: Bearer` for the rest), so relying on the
|
|
124
|
+
provider header to overwrite the caller's by name covers one of the two at
|
|
125
|
+
best. An un-routed request keeps its headers — that one really is going to
|
|
126
|
+
Anthropic. Enforced by `packages/router/test/proxy-headers.test.ts`.
|
|
127
|
+
- **ChatGPT subscription (Codex backend):** needs a translation layer,
|
|
128
|
+
Anthropic Messages ⇄ OpenAI Responses, plus OAuth (PKCE) login against
|
|
129
|
+
`chatgpt.com/backend-api/codex`. The upstream endpoint is unofficial and may
|
|
130
|
+
change.
|
|
131
|
+
- Reference implementations, used as **behavioral specs only, no code copied**:
|
|
132
|
+
[husniadil/proxenos](https://github.com/husniadil/proxenos) (Rust, Apache-2.0;
|
|
133
|
+
measured cache hit 97.8–98.7% in daily use) and
|
|
134
|
+
[insightflo/chatgpt-codex-proxy](https://github.com/insightflo/chatgpt-codex-proxy)
|
|
135
|
+
(TypeScript, MIT; OAuth, tool calling, SSE).
|
|
136
|
+
- Cache is the make-or-break metric. An earlier translator of our own (a
|
|
137
|
+
GPT layer over the Claude Code harness, before ClaudeRipple) lost
|
|
138
|
+
`cache_control` breakpoints and re-signed reasoning, giving 12–20% cache
|
|
139
|
+
hit and 8× input token spend. That figure is ours, not a measurement of
|
|
140
|
+
opencodex or any other project. **Acceptance: ≥90% cache hit on a
|
|
141
|
+
multi-turn tool-using session, measured from upstream
|
|
142
|
+
`input_tokens_details.cached_tokens`.**
|
|
143
|
+
- `output_config.effort`: `none…max` accepted by terra/sol/astra, `ultra`
|
|
144
|
+
rejected (luna accepts it). Clamp `ultra` → `max`.
|
|
145
|
+
- Model self-introduction is not evidence of routing. Verify by upstream
|
|
146
|
+
usage records.
|
|
147
|
+
- **Implemented 2026-09-11** in `packages/router/src/providers/chatgpt/`.
|
|
148
|
+
Wire facts used: endpoint `POST {base}/codex/responses` with base
|
|
149
|
+
`https://chatgpt.com/backend-api`; headers `authorization: Bearer`,
|
|
150
|
+
`chatgpt-account-id`, `OpenAI-Beta: responses=experimental`,
|
|
151
|
+
`originator: codex_cli_rs`; body fields `model, instructions, input, tools,
|
|
152
|
+
tool_choice, parallel_tool_calls, reasoning{effort,summary}, text{verbosity},
|
|
153
|
+
store:false, stream:true, prompt_cache_key`. OAuth: `auth.openai.com/oauth/
|
|
154
|
+
authorize|token`, client `app_EMoamEEZ73f0CkXaXp7hrann`, redirect
|
|
155
|
+
`http://localhost:1455/auth/callback`, PKCE S256, account id from the access
|
|
156
|
+
token claim `https://api.openai.com/auth.chatgpt_account_id`.
|
|
157
|
+
- **Verified live 2026-09-13 and cut over** (the user's config now uses this
|
|
158
|
+
adapter; proxenos is no longer in the chain). Measured against the real
|
|
159
|
+
backend: streamed text, tool calls (`tool_use` + `input_json_delta`),
|
|
160
|
+
multi-turn with `tool_result`, non-streaming, `count_tokens` (local
|
|
161
|
+
estimate), `@effort` suffix → `reasoning.effort`. Prompt cache on
|
|
162
|
+
`gpt-5.6-terra`: 0 on the first call after idle, then **7680/7801 = 98.4%**
|
|
163
|
+
on every following call with the same `prompt_cache_key`. `gpt-5.6-luna`
|
|
164
|
+
reported `cached_tokens: 0` on identical repeats (backend behavior, also
|
|
165
|
+
through proxenos) — do not judge the cache metric on luna.
|
|
166
|
+
- Quota comes from the backend's **`x-codex-*` response headers** on every
|
|
167
|
+
response (`x-codex-primary-used-percent`, `-window-minutes`,
|
|
168
|
+
`-reset-after-seconds`, `-reset-at`, `x-codex-plan-type`); the
|
|
169
|
+
`codex.rate_limits` SSE event was not sent in any measured response. The
|
|
170
|
+
adapter reads both.
|
|
171
|
+
- **Tool schema scrub.** The backend validates every `pattern` in
|
|
172
|
+
`tools[].parameters` with a regex engine that rejects lookaround and
|
|
173
|
+
backreferences, and one bad pattern fails the whole request with 400
|
|
174
|
+
`invalid_function_parameters` ("… is not a 'regex'"). Claude Code's
|
|
175
|
+
`Artifact` tool carries `^(?!__.*__$)…` since ~2.1.266, which broke every
|
|
176
|
+
GPT request through proxenos as well. `normalizeSchema` drops such patterns
|
|
177
|
+
(the client validates its own inputs). Upstream error bodies are now logged.
|
|
178
|
+
- Cache-safety decisions: thinking blocks are dropped from replayed history;
|
|
179
|
+
no reasoning `include`; identity line and `instructionsAppend` are constant
|
|
180
|
+
text; `prompt_cache_key` = sha256(metadata.user_id + first user message).
|
|
181
|
+
|
|
182
|
+
### 4c. What a routed model is told it is
|
|
183
|
+
|
|
184
|
+
- Claude Code's system prompt opens with "You are Claude Code, Anthropic's
|
|
185
|
+
official CLI for Claude". A mapped provider receives that text and has nothing
|
|
186
|
+
else to go on: asked what it was, DeepSeek answered that it was Claude while
|
|
187
|
+
the session label read `deepseek-flash` (measured 2026-09-16).
|
|
188
|
+
- So every provider that assembles a system prompt puts one line in front of it:
|
|
189
|
+
`You are <model> (reasoning effort: <effort>), answering through Claude Code, a
|
|
190
|
+
terminal-based coding agent.` The effort is named because a model cannot see
|
|
191
|
+
its own setting; it is omitted when the provider takes no reasoning effort.
|
|
192
|
+
`packages/router/src/identity.ts` holds the sentence, and the ChatGPT,
|
|
193
|
+
OpenAI-compatible and Anthropic-compatible paths all use it.
|
|
194
|
+
- On the Anthropic-compatible path the line is added **after** the compatibility
|
|
195
|
+
pass, so the effort named is the one the provider actually receives. A string
|
|
196
|
+
`system` stays a string; a block array gains a leading block that carries no
|
|
197
|
+
`cache_control`, leaving the caller's breakpoints where they were.
|
|
198
|
+
- `identity: false` per provider turns it off; `instructionsAppend` adds fixed
|
|
199
|
+
text after the prompt. Both are constant per provider, so the prompt cache
|
|
200
|
+
misses once when either changes and not afterwards.
|
|
201
|
+
|
|
202
|
+
### 4a. Claude Code request shapes a translator must handle (measured 2026-09-13, CLI 2.1.266)
|
|
203
|
+
|
|
204
|
+
- **Server-side threads ("tether").** The first request of a session carries
|
|
205
|
+
`thread: {type:"create"}` and the full history; later turns carry
|
|
206
|
+
`thread: {type:"continue", previous_message_id: <our last assistant msg id>}` and
|
|
207
|
+
**only the new messages** (tool results + attachments). A translated provider has no
|
|
208
|
+
such state, so the router answers a `continue` with HTTP 400 and
|
|
209
|
+
`error.details.error_code = "thread_unsupported_request"`; the CLI then resends the
|
|
210
|
+
turn stateless and keeps the session stateless on that model. Accepting the delta
|
|
211
|
+
instead made the model see an orphan `tool_result`, answer "무엇을 도와드릴까요?", and
|
|
212
|
+
kept `cached_tokens` stuck at the tools prefix. `thread`/`diagnostics` are stripped
|
|
213
|
+
from `create` requests before forwarding.
|
|
214
|
+
- **Per-turn billing telemetry.** The first system block is
|
|
215
|
+
`x-anthropic-billing-header: … cch=<hash> …` and the hash changes every turn. Dropped
|
|
216
|
+
from `instructions`, otherwise nothing after it is ever cached. With it dropped and
|
|
217
|
+
sessions stateless, continuation turns measured 94–99% cache hit (`cached_tokens` /
|
|
218
|
+
total input) on gpt-5.6-terra.
|
|
219
|
+
- **Orphan tool results.** Side queries can start with a bare `tool_result`; the
|
|
220
|
+
Responses API rejects a `function_call_output` without its `function_call`
|
|
221
|
+
("No tool call found"), so such results are sent as user text.
|
|
222
|
+
- **Usage snapshots.** The CLI snapshots `message.usage` per streamed content block,
|
|
223
|
+
before `message_delta`; `message_start` therefore announces an input estimate
|
|
224
|
+
(char/4, floored by the last measured total for the conversation) so the app's
|
|
225
|
+
token counter and the CLI's context accounting are not zero.
|
|
226
|
+
|
|
227
|
+
- **Provider base path.** Anthropic-compatible vendors mount the API under a path
|
|
228
|
+
(`https://api.deepseek.com/anthropic`, `https://openrouter.ai/api`,
|
|
229
|
+
`https://dashscope-intl.aliyuncs.com/apps/anthropic`); the router prepends it to
|
|
230
|
+
the CLI's `/v1/messages…`. Forwarding to the bare host returned the vendor's
|
|
231
|
+
website as HTTP 200 HTML (measured 2026-09-13).
|
|
232
|
+
- **Anthropic-only request features.** Claude Code sends `thinking.block_binding`,
|
|
233
|
+
`defer_loading` tools, `context_management`, `output_config.effort`, and an
|
|
234
|
+
`anthropic-beta` header; compatible vendors answer 400 for them
|
|
235
|
+
(`Thinking.block_binding is not supported…`, `Deferred custom tools are only
|
|
236
|
+
supported on Anthropic models…`). `packages/router/src/compat.ts` strips or
|
|
237
|
+
clamps them per provider capability (preset `effortLevels` / `thinking`).
|
|
238
|
+
- **New picker entries need a new session.** The CLI validates `set_model`
|
|
239
|
+
against the model list it received at session start (`additional_model_options`
|
|
240
|
+
in its bootstrap); a model added afterwards is "not a recognized model id"
|
|
241
|
+
until a new session starts.
|
|
242
|
+
|
|
243
|
+
### 4b. OpenAI ingress (Codex CLI and other OpenAI clients)
|
|
244
|
+
|
|
245
|
+
- A second, plain HTTP listener binds **only** `127.0.0.1` on
|
|
246
|
+
`listen.openaiPort` (default `listen.port + 2`). It accepts any syntactically
|
|
247
|
+
valid local `Authorization: Bearer …` value but never forwards that value.
|
|
248
|
+
It provides `POST /v1/responses`, `POST /v1/chat/completions`, and
|
|
249
|
+
`GET /v1/models`.
|
|
250
|
+
- Both POST endpoints resolve their requested `model` with the same
|
|
251
|
+
`routes`/`aliases`/`direct` logic as the proxy. `chatgpt` and
|
|
252
|
+
`openai-compatible` targets are rejected: Codex already has native paths for
|
|
253
|
+
them. Targets are `anthropic-compatible`, or native `anthropic`.
|
|
254
|
+
- Configure a native API-key target as
|
|
255
|
+
`{ "type":"anthropic", "auth":"api-key", "apiKey":"…" }` (or set
|
|
256
|
+
`ANTHROPIC_API_KEY`). Configure a compatible target as today, for example an
|
|
257
|
+
`openrouter` provider at `https://openrouter.ai/api` with its own `headers`.
|
|
258
|
+
Ingress writes requests to the ordinary RequestLog as `kind: "messages"`,
|
|
259
|
+
with `note: "openai-ingress responses|chat"`.
|
|
260
|
+
- `anthropic.auth: "claude-code"` reuses a Claude subscription credential and
|
|
261
|
+
is subject to Anthropic's terms. Source precedence is: (1) the latest valid
|
|
262
|
+
**in-memory** header snapshot observed from an un-routed Claude Code
|
|
263
|
+
`api.anthropic.com/v1/messages` request (expires after 12 hours and vanishes
|
|
264
|
+
on router restart), (2) `CLAUDE_CODE_OAUTH_TOKEN`, (3) Claude Code Keychain
|
|
265
|
+
service `Claude Code-credentials`.claudeAiOauth, (4)
|
|
266
|
+
`~/.claude/.credentials.json`, then (5) ClaudeRipple's own
|
|
267
|
+
`<home>/claude-auth.json` setup-token file. The observed source retains only
|
|
268
|
+
`Authorization`, `anthropic-version`, `anthropic-beta`, `user-agent`,
|
|
269
|
+
`x-app`, `x-stainless-*`, and `anthropic-client-*` request headers and sends
|
|
270
|
+
that exact set upstream. It is never persisted, logged, included in RequestLog
|
|
271
|
+
or picker diagnostics, debug dumps, or any admin API response. Keychain/file
|
|
272
|
+
reads are cached for 60 seconds; ClaudeRipple **never refreshes or writes**
|
|
273
|
+
Claude Code's own credential. Expiry becomes OpenAI-style 401 `Claude Code
|
|
274
|
+
login expired — open Claude Code once to refresh`.
|
|
275
|
+
- `clauderipple claude-login` (0.1.2) is ClaudeRipple's own browser sign-in:
|
|
276
|
+
the OAuth authorization-code flow with PKCE (S256) against Claude Code's
|
|
277
|
+
public client, exactly as Claude Code 2.1.271 does (read from its binary,
|
|
278
|
+
2026-09-16): `https://claude.com/cai/oauth/authorize` →
|
|
279
|
+
`https://platform.claude.com/v1/oauth/token`, scopes `org:create_api_key
|
|
280
|
+
user:profile user:inference user:sessions:claude_code user:mcp_servers
|
|
281
|
+
user:file_upload` (the refresh grant names the subscription scopes only).
|
|
282
|
+
The older `claude.ai/oauth/authorize` answers "Invalid request format"
|
|
283
|
+
(measured). The code returns to a loopback listener, port 54545 preferred
|
|
284
|
+
and any free port otherwise (the authorize server accepts any localhost
|
|
285
|
+
port); with `--manual` the redirect is
|
|
286
|
+
`https://platform.claude.com/oauth/code/callback` and the user pastes
|
|
287
|
+
`code#state` (or the redirect URL). State and PKCE verifier are per attempt; a callback
|
|
288
|
+
with the wrong state is refused without ending the attempt; an attempt
|
|
289
|
+
expires after 5 minutes. The grant (access + refresh token, expiry) is stored
|
|
290
|
+
only in `<home>/claude-auth.json` (mode `0600`) as `source: "oauth"`; the
|
|
291
|
+
ingress refreshes it up to 5 minutes before expiry (one refresh shared by
|
|
292
|
+
concurrent requests) and a failed refresh becomes a clear 401 rather than a
|
|
293
|
+
dead token. The GUI drives the same flow through `POST/GET /api/claude-oauth`
|
|
294
|
+
and `POST /api/claude-oauth/code`; no token value ever appears in an admin
|
|
295
|
+
response or a log line. `--setup-token` keeps the previous path (`claude
|
|
296
|
+
setup-token` from a terminal, long-lived token, `source: "setup-token"`).
|
|
297
|
+
`claude-logout` removes the file either way. Wire facts are behaviorally
|
|
298
|
+
measured (the reference implementations do the same flow); they are not an
|
|
299
|
+
Anthropic guarantee, and reuse of a subscription is subject to its terms.
|
|
300
|
+
- Readiness vs liveness (0.1.2): `/api/status.readiness` and `GET /readyz`
|
|
301
|
+
(200 or 503 + `retry-after: 5`) list what stands between a request and a
|
|
302
|
+
model: `settings` (Claude Code not pointed at us), `upstream` (consecutive
|
|
303
|
+
connect failures), `picker-ca` / `picker-proxy` (picker mode without trust or
|
|
304
|
+
the app proxy entry), `provider:<name>` (TCP unreachable). The tray shows the
|
|
305
|
+
list in its detail line. A router that answers is alive; only an empty list
|
|
306
|
+
means it is ready.
|
|
307
|
+
- **OAuth wire behavior (behavioral-spec evidence; not documented public API
|
|
308
|
+
contract):** the behavior-only OpenCodex source uses `Authorization: Bearer`,
|
|
309
|
+
`anthropic-version: 2023-06-01`,
|
|
310
|
+
`anthropic-beta: claude-code-20250219,oauth-2025-04-20`, a Claude Code
|
|
311
|
+
request fingerprint/session ID, and `custom_` prefixes for non-builtin tool
|
|
312
|
+
names. ClaudeRipple independently implements those behaviors; it does not
|
|
313
|
+
copy reference code. The ingress puts the required compatibility first system
|
|
314
|
+
block `You are Claude Code, Anthropic's official CLI for Claude.` and
|
|
315
|
+
**nothing else in `system`**. Measured 2026-09-13 with a live subscription
|
|
316
|
+
credential: a request whose system prompt carries Codex's long instructions
|
|
317
|
+
after the identity line answers HTTP 429 `{"type":"rate_limit_error",
|
|
318
|
+
"message":"Error"}` (not a real quota limit; a tiny request passes at once).
|
|
319
|
+
So in borrowed-login mode the client's `instructions` / system messages travel
|
|
320
|
+
as the first block of the first user message, wrapped in
|
|
321
|
+
`<operator_instructions>` and cache-marked; with an API key they stay in
|
|
322
|
+
`system`. Also measured: `claude-haiku-4-5` rejects `output_config.effort`
|
|
323
|
+
with 400, so effort is sent only to models matching
|
|
324
|
+
`claudeSupportsEffort()` (Opus/Sonnet/Fable 4.6+ and 5). Verified live:
|
|
325
|
+
Sonnet 5 and Haiku 4.5 with Codex's full instructions, tools and effort → 200;
|
|
326
|
+
`codex exec --profile clauderipple -m claude-sonnet-5` → exit 0.
|
|
327
|
+
- Responses conversion is stateless. A non-null `previous_response_id` receives
|
|
328
|
+
HTTP 400 `previous_response_id unsupported` rather than silently losing
|
|
329
|
+
history (Codex sends `null` when it carries the full transcript itself).
|
|
330
|
+
`instructions` plus message/input items become Anthropic `system` plus
|
|
331
|
+
`messages`; text, base64/URL images, function calls, and function outputs are
|
|
332
|
+
preserved; reasoning items are dropped. Function tools and tool choice map to
|
|
333
|
+
Anthropic tools/tool_choice. `max_output_tokens` → `max_tokens`, and
|
|
334
|
+
`temperature` is preserved. `reasoning.effort` → `output_config.effort`; this
|
|
335
|
+
ingress does not invent a thinking-token budget because that would change
|
|
336
|
+
the caller's requested output ceiling.
|
|
337
|
+
- Cache discipline: serialization has no timestamps; cache breakpoints are put
|
|
338
|
+
on the system block, the final tool definition, and the final user content
|
|
339
|
+
block. Upstream Anthropic usage maps `cache_read_input_tokens` to Responses
|
|
340
|
+
`usage.input_tokens_details.cached_tokens`.
|
|
341
|
+
- Anthropic Messages stream conversion emits `response.created`, output-item
|
|
342
|
+
add/done, text and function-argument deltas/done, then `response.completed`.
|
|
343
|
+
Chat Completions uses the same mapper and emits `delta.tool_calls` plus a
|
|
344
|
+
terminal `[DONE]`.
|
|
345
|
+
- Codex 0.146.0-alpha.9.2 on the development machine supports profile layers
|
|
346
|
+
at `$CODEX_HOME/<name>.config.toml` (`--profile <name>`). `clauderipple codex
|
|
347
|
+
on` adds only a marked `[model_providers.clauderipple]` block in
|
|
348
|
+
`$CODEX_HOME/config.toml` and a marked selection block in
|
|
349
|
+
`$CODEX_HOME/clauderipple.config.toml`; `off` removes only those blocks after
|
|
350
|
+
a backup. The provider uses `base_url = "http://127.0.0.1:<openaiPort>/v1"`,
|
|
351
|
+
and `wire_api = "responses"`; no `env_key`, so the Codex desktop app (which
|
|
352
|
+
has no shell environment) can use it too. The ingress accepts a missing
|
|
353
|
+
`Authorization` header and rejects only a malformed one.
|
|
354
|
+
|
|
355
|
+
Sources: [Codex configuration reference](https://learn.chatgpt.com/docs/config-file/config-reference),
|
|
356
|
+
[Codex advanced configuration](https://learn.chatgpt.com/docs/config-file/config-advanced),
|
|
357
|
+
[Claude Code authentication and credential management](https://code.claude.com/docs/en/authentication),
|
|
358
|
+
and the behavior-only [OpenCodex source](https://github.com/lidge-jun/opencodex)
|
|
359
|
+
inspected 2026-09-13. The latter is not an Anthropic guarantee.
|
|
360
|
+
|
|
361
|
+
### 4c. `openai-compatible` providers (implemented 2026-09-13)
|
|
362
|
+
|
|
363
|
+
- This adapter translates Anthropic Messages into either OpenAI **Chat Completions**
|
|
364
|
+
(the default `wire: "chat"`) or stateless **Responses** (`wire: "responses"`),
|
|
365
|
+
then maps SSE back into Anthropic `message_start`, content-block, `message_delta`,
|
|
366
|
+
and `message_stop` events. It supports text, base64/URL images, function tools,
|
|
367
|
+
tool results, non-streaming replies, and local `count_tokens` estimates.
|
|
368
|
+
- Translation deliberately strips `thinking`, `context_management`, `thread`,
|
|
369
|
+
`diagnostics`, and `container`; `thread:continue` is refused exactly as in §4a so
|
|
370
|
+
the CLI resends a full stateless history. The per-turn
|
|
371
|
+
`x-anthropic-billing-header` system block is also stripped because its changing
|
|
372
|
+
value breaks every vendor's stable-prefix cache opportunity. Orphan tool results
|
|
373
|
+
are sent as user text, not a tool result with no antecedent call.
|
|
374
|
+
- `output_config.effort` is emitted only if provider `caps.reasoning` is `"effort"`,
|
|
375
|
+
clamped to `caps.effortLevels`; Chat Completions receives `reasoning_effort`, while
|
|
376
|
+
Responses receives `reasoning.effort`. A discovered model's explicit
|
|
377
|
+
`effortLevels` overrides provider defaults, including `[]` for “no effort”. When
|
|
378
|
+
an OpenRouter `/models` entry has `supported_parameters`,
|
|
379
|
+
`reasoning_effort` maps to `[low, medium, high]`; its absence maps to `[]`.
|
|
380
|
+
Vendors that do not report that field keep their configured fallback. Chat wire
|
|
381
|
+
forwards Anthropic `stop_sequences` as `stop`; Responses wire deliberately omits
|
|
382
|
+
them because its common stateless schema has no corresponding universal field.
|
|
383
|
+
- We do not assert a provider-wide cache hit rate: the adapter records
|
|
384
|
+
`prompt_tokens_details.cached_tokens` / `input_tokens_details.cached_tokens` when
|
|
385
|
+
a vendor returns it, and otherwise logs zero cache read. The byte-stable
|
|
386
|
+
translated history preserves cache eligibility but does not create vendor caching.
|
|
387
|
+
- Presets use OpenAI-compatible `GET /models` discovery followed by a one-token
|
|
388
|
+
`POST /chat/completions` authentication check. 401/403 is a bad key; 402 or an
|
|
389
|
+
insufficient-credit response means the key is accepted but the account has no
|
|
390
|
+
credit. Preset endpoint and capability claims cite the vendor's official docs in
|
|
391
|
+
`packages/router/src/presets.ts`.
|
|
392
|
+
|
|
393
|
+
## 5. Failure modes that must not exist in the product (all observed)
|
|
394
|
+
|
|
395
|
+
| Observed | Product requirement |
|
|
396
|
+
|---|---|
|
|
397
|
+
| Router stuck with DNS resolution failure for 10h (`gaierror`, 22,795 lines); 11 remote-control workers died within 17s; app kept showing remote as available | Consecutive upstream connect failures → self-exit; supervisor restarts (launchd KeepAlive). Health view shows remote-worker liveness. |
|
|
398
|
+
| Python 3.9 asyncio GC'd connection handler tasks; 1 in 10 requests vanished with no log line (`Task was destroyed but it is pending`); surfaced as "retry" banners, `Connection lost mid-response`, and subagent compaction failures | Keep strong refs to in-flight connections; log every request completion; an integration test that counts requests in vs. responses out. |
|
|
399
|
+
| Translator daemon (proxenos) was not supervised; it died and every GPT request failed with connection refused | One supervisor for the whole chain; the router reports adapter health, not just its own. |
|
|
400
|
+
| `router.log` grew to 17MB | Log rotation by default. |
|
|
401
|
+
| A `restart` (SIGTERM) killed 5 in-flight requests; a streaming answer died mid-response and Claude Desktop showed a connection error (2026-09-11 17:00) | SIGTERM drains: stop accepting, wait for `/v1/messages` calls (up to 90s), then exit. Long-poll worker streams are not waited for (the CLI reconnects them). Never restart the live router casually. |
|
|
402
|
+
| `launchctl kickstart -k` SIGKILLed the router ~5s after its SIGTERM; the drain was cut with 2 model calls open (2026-09-13 13:35) | `clauderipple restart` sends SIGTERM itself, waits for the process to exit (up to 120s), and lets launchd KeepAlive relaunch it. `kickstart -k` is only the fallback. |
|
|
403
|
+
| Drain ran the full budget and still had 2 calls open: `server.close()` stops new TCP connections only, and the CLI kept sending new requests down its existing tunnels (in-flight went 2→1→2; 2026-09-13 13:38) | While draining, new `/v1/messages` requests get `503` + `retry-after: 3` + `connection: close` before the body is read (the SDK retries 5xx and reconnects to the relaunched router). The in-flight count can then only fall. Verified: a 47s stream finished, the next call got 503, exit 1s later. |
|
|
404
|
+
| Every GPT request failed with 400 after a Claude Code update added a lookahead regex to the `Artifact` tool schema (2026-09-13; proxenos too) | Tool-schema scrub in the translator (§4). Upstream error bodies are logged, never just the status. |
|
|
405
|
+
| Unknown-model context window defaulted to 200K, compaction fired at 151K; fixed via `CLAUDE_CODE_MAX_CONTEXT_TOKENS=272000` (applies only to models not in the CLI's built-in table; Claude models unaffected) | Installer sets this env for mapped models; document that it does not affect Claude models. |
|
|
406
|
+
| proxenos sends only the 7-day quota window, so the app shows a "weekly limit" banner | Quota reporting must mirror the shape Anthropic returns. |
|
|
407
|
+
| After a reboot the router listened 4 minutes after login (26s of it between exec and `listen()`), and for that whole window Claude Desktop was a blank page with `ERR_PROXY_CONNECTION_FAILED` — in picker mode every byte the app sends goes through us, so a router that is merely slow reads as an app that is broken (2026-09-14 09:59 boot → 10:15:59 listening) | The launchd agent is `ProcessType=Interactive`, never `Background` (that key throttles CPU and I/O — launchd.plist(5)). `listen()` comes before certificate minting and any other startup work, so a client waits rather than being refused. Every startup logs its budget (`startup Nms: node …, config …, listen …`). |
|
|
408
|
+
| "Start Router" ran `launchctl kickstart -k`, which kills a router that is already coming up and starts the wait over (three runs in the four minutes after login, 2026-09-14) | Starting is idempotent: a running agent is left alone, an unloaded one is re-bootstrapped. Only `restart` may force. |
|
|
409
|
+
| With the router down, the tray app's window loaded the router-served GUI and showed the same blank page as Claude Desktop — nothing anywhere said why | The cause is always named where the user asked for the dashboard. Since the window was dropped (2026-09-16) the tray opens the dashboard in the browser: with the router down it starts it first, and if it does not come up a dialog explains why Claude Desktop is blank. The app keeps a login item so it is there before Claude Desktop is, and posts a notification when the router has been down for 20s. |
|
|
410
|
+
| Windows: the scheduled task inherited the console that started it, so closing the installer window killed the router with `0xC000013A` (Ctrl+C), 2026-09-14 | The task runs a PowerShell launcher that uses `Start-Process -PassThru -Wait`: detached from any console, still tracked so `RestartCount` keeps acting as KeepAlive and the exit code (including the health self-exit 75) propagates. |
|
|
411
|
+
| Windows: `install` registered a logon-triggered task and stopped there, so the router did not exist until the next sign-in — launchd starts at bootstrap (`RunAtLoad`) and the difference was invisible | `install` starts the supervisor itself on both platforms and prints the result. |
|
|
412
|
+
| Windows: trusting the CA failed with "this operation cannot use the UI" — every PowerShell call carried `-NonInteractive`, and the confirmation dialog never appeared, so picker mode stopped with no certificate and no explanation | Certificate trust and removal run WITHOUT `-NonInteractive` (and without `windowsHide`). They are UI operations by design: the OS must be able to show the user what it is being asked to trust. |
|
|
413
|
+
| Windows: sign-in failed with `missing_required_parameter` because the OAuth URL was opened via `cmd /c start`, and cmd reads `&` as a command separator — everything after the first parameter was cut off and run as commands (2026-09-14) | Browsers are opened with `Start-Process <url>` as a single quoted argument. Any URL we hand to a shell must survive its metacharacters. |
|
|
414
|
+
| The provider form's "also show in the Claude app picker" box only records `cli.extraModels`; picker mode itself stayed off, nothing was injected, and the user believed the picker was on (Windows x64, 2026-09-14: `config.json` had no `picker` key, no CA, no Config Library) | When that box is ticked while picker mode is off, the form says so under the box and offers to turn picker mode on right after saving. Any control that *looks* like it enables picker mode must either enable it or say what is still missing. |
|
|
415
|
+
| A DeepSeek mapping answered `401` on every real request while the provider form's own "Test connection" stayed green — the test sends the provider's headers alone, live traffic also carried the caller's `authorization` (2026-09-15, Windows) | A routed request carries provider authentication only (§4). The connection test and live traffic must present the same auth set, or the test certifies a path nobody uses. |
|
|
416
|
+
| Mapping Haiku 4.5 did nothing: the app sends `claude-haiku-4-5-20251001` while the GUI only offers the undated `claude-haiku-4-5`, and route lookup was an exact key match, so the request passed through and Haiku answered itself (2026-09-15) | Route lookup accepts both the dated and undated form of a model id. A mapping the GUI offers must be one the router can actually match. |
|
|
417
|
+
| The native `anthropic` provider (ingress-only) was offered as a mapping target and could be given picker entries; a slot pointed at it made every Claude request fail with `400` (2026-09-15) | Ingress-only providers never appear as mapping targets and create no picker/direct rules. A rule that names one is ignored at resolve time, so an existing config degrades to passthrough instead of failing. |
|
|
418
|
+
| Windows: "Connect Claude subscription" always failed with "Claude Code CLI not found" — discovery looked for an extensionless `claude` on PATH and for the macOS Application Support cache, neither of which exists there (2026-09-15) | Launcher discovery is per-platform: `.exe`/`.cmd`/`.bat` names on Windows, both AppData roots for the Desktop-cached CLI, and a shell for the `.cmd` launcher that cannot be spawned directly. |
|
|
419
|
+
| After updating to 0.1.1 the same Windows install still answered `401` on DeepSeek: the files were new, the router process was the one the scheduled task had started from the old files, and `start` leaves a running router alone. The router also reported `0.1.0` (a literal in two places), so nothing could tell (2026-09-15) | An update replaces the process, not only the files: the app compares the running router's version and files (`/api/status.runtime`) with its own once per launch, re-runs `install` when this installation is not the recorded one, and restarts. One `VERSION` constant, tested against the manifests. |
|
|
420
|
+
| Windows: `restart` on a packaged install never restarted anything — the router runs as `ClaudeRipple.exe` (Electron as Node) and the pid lookup only matched `node.exe`, so no shutdown was sent and `Start-ScheduledTask` was ignored by the task already running; the command still reported "restarted" (found 2026-09-16, review of the update path) | Process discovery matches every runtime we launch, and a restart that finds no process to stop must say so instead of claiming success. The installer's own stop script already matched `ClaudeRipple.exe`; two lookups for the same process must not diverge. |
|
|
421
|
+
| The DeepSeek `401` stayed unexplained for a day: a provider error was logged as a status code only (2026-09-15) | Upstream 4xx/5xx bodies are logged (first 300 characters, key-shaped strings masked) and kept in the request record. This is the same rule already required of the translator above. |
|
|
422
|
+
| "Connect Claude subscription" from the tray/GUI failed with "setup-token failed": `claude setup-token` is an interactive terminal flow and neither place has a terminal (2026-09-15, Windows) | Without a TTY the error says to run `clauderipple claude-login` in a terminal and that an existing Claude Code login is reused anyway; other failures carry what `claude` printed. A button that cannot work where it is must say where it works. |
|
|
423
|
+
| Closing Claude Desktop's window does not quit it; reopening hits `Not main instance, returning early` and the app silently keeps the OLD proxy setting. The user sees "I configured it and nothing happened" with no error anywhere (2026-09-14) | Tell the user that closing the window is not enough, and detect it: with picker mode on, the router knows whether the app is actually routing through it. Surface "configured, but the app has not restarted yet" rather than letting it fail silently. |
|
|
424
|
+
|
|
425
|
+
## 6. Blocked paths (measured, do not retry)
|
|
426
|
+
|
|
427
|
+
- The app puts `ANTHROPIC_BASE_URL` in the CLI process env directly;
|
|
428
|
+
`settings.json` env does not override an existing variable; project-scope
|
|
429
|
+
`.claude/settings.local.json` env is ignored for base URL.
|
|
430
|
+
- `ANTHROPIC_UNIX_SOCKET` does not apply.
|
|
431
|
+
- `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST` is set only in 3P/Cowork.
|
|
432
|
+
- 3P mode is all-or-nothing at app launch and loses connectors.
|
|
433
|
+
|
|
434
|
+
## 7. Competitive position (2026-09-11)
|
|
435
|
+
|
|
436
|
+
Six repos target the app (ModelLink 135★ best); all use the official gateway
|
|
437
|
+
setting and pay its costs (§2). Four are Chinese-market only; ModelLink is
|
|
438
|
+
CC BY-NC-ND (no forks, no sponsors). CLI-targeting `claude-code-router` has
|
|
439
|
+
37,180★, `opencodex` 14,300★ with two README sponsors. Open positions: English
|
|
440
|
+
distribution, GPL-3.0 license (chosen 2026-09-13 over MIT: sole author, reversible later, blocks closed commercial repackaging), sponsor slots. Positioning: an **add-on for Claude
|
|
441
|
+
subscribers**, not a replacement for people without one.
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clauderipple",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Use GPT and 400+ other models inside the Claude Desktop app while staying signed in to your Claude subscription — no third-party gateway mode.",
|
|
5
|
+
"license": "GPL-3.0-only",
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"packages/*"
|
|
8
|
+
],
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=24"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
14
|
+
"dist": "npm --workspace @clauderipple/app run dist",
|
|
15
|
+
"release": "npm --workspace @clauderipple/app run release",
|
|
16
|
+
"router": "node packages/router/src/index.ts",
|
|
17
|
+
"test": "node --test packages/router/test/*.test.ts packages/cli/test/*.test.ts",
|
|
18
|
+
"cli": "node packages/cli/src/index.ts",
|
|
19
|
+
"prepack": "node scripts/build-npm.mjs",
|
|
20
|
+
"build": "node scripts/build-npm.mjs"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^24.3.0",
|
|
24
|
+
"typescript": "^5.9.2"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"clauderipple": "bin/clauderipple.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"bin/",
|
|
32
|
+
"dist/",
|
|
33
|
+
"docs/ARCHITECTURE.md",
|
|
34
|
+
"CHANGELOG.md",
|
|
35
|
+
"README.ko.md"
|
|
36
|
+
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/PBJ-2/clauderipple.git"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"claude",
|
|
46
|
+
"claude-desktop",
|
|
47
|
+
"claude-code",
|
|
48
|
+
"anthropic",
|
|
49
|
+
"gpt",
|
|
50
|
+
"chatgpt",
|
|
51
|
+
"openai",
|
|
52
|
+
"codex",
|
|
53
|
+
"deepseek",
|
|
54
|
+
"kimi",
|
|
55
|
+
"openrouter",
|
|
56
|
+
"llm",
|
|
57
|
+
"proxy",
|
|
58
|
+
"model-router",
|
|
59
|
+
"ai-coding-assistant",
|
|
60
|
+
"mcp"
|
|
61
|
+
],
|
|
62
|
+
"homepage": "https://github.com/PBJ-2/clauderipple",
|
|
63
|
+
"bugs": {
|
|
64
|
+
"url": "https://github.com/PBJ-2/clauderipple/issues"
|
|
65
|
+
}
|
|
66
|
+
}
|