clauderipple 0.6.0 → 0.8.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 +66 -0
- package/README.ko.md +43 -2
- package/README.md +60 -4
- package/dist/app/package.json +1 -1
- package/dist/cli/src/claude-auth.js +35 -1
- package/dist/cli/src/codex.js +23 -0
- package/dist/cli/src/hooks/agent-title.js +58 -29
- package/dist/cli/src/index.js +89 -1
- package/dist/cli/src/runtime.js +3 -0
- package/dist/cli/src/settings.js +49 -0
- package/dist/mod/.claude-plugin/plugin.json +1 -0
- package/dist/mod/hooks/hooks.json +1 -0
- package/dist/mod/hooks/register.tsx +309 -0
- package/dist/mod/hooks/worker.ts +49 -0
- package/dist/mod/tsconfig.json +3 -0
- package/dist/mod/types/index.d.ts +30 -0
- package/dist/router/src/admin.js +497 -34
- package/dist/router/src/agents.js +50 -9
- package/dist/router/src/catalog.js +80 -0
- package/dist/router/src/compat.js +27 -1
- package/dist/router/src/config.js +33 -0
- package/dist/router/src/index.js +13 -1
- package/dist/router/src/ingress/server.js +3 -2
- package/dist/router/src/picker.js +33 -2
- package/dist/router/src/presets.js +2 -1
- package/dist/router/src/providers/chatgpt/index.js +154 -15
- package/dist/router/src/providers/chatgpt/translate.js +34 -6
- package/dist/router/src/providers/google/accounts.js +345 -0
- package/dist/router/src/providers/google/antigravity.js +392 -0
- package/dist/router/src/providers/google/index.js +617 -0
- package/dist/router/src/providers/google/login.js +249 -0
- package/dist/router/src/providers/google/thoughtSignatures.js +47 -0
- package/dist/router/src/providers/google/translate.js +539 -0
- package/dist/router/src/providers/google/transport.js +118 -0
- package/dist/router/src/providers/openai/translate.js +3 -3
- package/dist/router/src/proxy.js +99 -1
- package/dist/router/src/requestlog.js +2 -0
- package/dist/router/src/routing.js +22 -0
- package/dist/router/src/version.js +1 -1
- package/dist/ui/app.js +436 -44
- package/dist/ui/i18n.js +88 -4
- package/dist/ui/index.html +10 -0
- package/dist/ui/style.css +4 -0
- package/docs/ARCHITECTURE.md +300 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0 — 2026-10-05
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **The route, where you type** (Claude Code mods; a switch on the Clients screen, or
|
|
8
|
+
`clauderipple mod on`). A line above the Claude Code prompt says where your last request
|
|
9
|
+
went: model, provider, effort, cache hit and time, colored by provider and result. `Log` or
|
|
10
|
+
`/ripple-log` opens the request log in a side pane, all sessions or just this one; `×` or
|
|
11
|
+
`/ripple-bar` puts the line away. The router now records which Claude Code session sent each
|
|
12
|
+
request (`X-Claude-Code-Session-Id`), and `/api/requests?session=` filters by it.
|
|
13
|
+
- **Workers stay on their own model.** The same mod catches a `model` argument on an Agent call
|
|
14
|
+
that would have put a ClaudeRipple worker on a Claude alias (its output cut to 64 tokens) and
|
|
15
|
+
runs it on its own model, and reads a `[[ripple: name@level]]` marker wherever it sits in the
|
|
16
|
+
prompt, not only on the first line.
|
|
17
|
+
- **Google Gemini**, on its own wire: an AI Studio key, or a Google account through the
|
|
18
|
+
Antigravity backend with several accounts in rotation. Google's terms name the account route
|
|
19
|
+
as a breach and accounts were suspended for it; the sign-in says so before it starts.
|
|
20
|
+
- **Each routed model gets its own effort menu in the Claude app.** DeepSeek and GLM show
|
|
21
|
+
low/high/max instead of Claude's whole ladder; a model that takes no effort shows none.
|
|
22
|
+
- **Fast mode on GPT.** `/fast` in Claude Code asks the ChatGPT backend for the Codex fast tier.
|
|
23
|
+
- **Updates from the dashboard**: the version row checks and starts an update.
|
|
24
|
+
- **A typed model ID** for a provider that lists no models (#42).
|
|
25
|
+
- **An explicit Auto mode classifier target** (`autoModeClassifier`, JSON only; #43 by
|
|
26
|
+
@Sociopacific). The README says what pointing it at a weaker model gives up.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Measuring an OpenCode provider takes one request per model**, its effort ladder read from
|
|
31
|
+
models.dev, four models at a time, instead of about ten sequential requests each (three
|
|
32
|
+
OpenCode Go models had taken 2m33s). The ladders are the catalogue's: glm-5.3-flash is
|
|
33
|
+
low/high/max, where requests had accepted seven levels.
|
|
34
|
+
- **Generated workers run at `@high`** unless a marker says otherwise.
|
|
35
|
+
- **A clamping tie goes up**: medium on a low/high/max model is high, not low.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **A settings save no longer undoes what the router wrote.** The dashboard saved the whole
|
|
40
|
+
config from the copy it loaded when it opened, so measured ladders and hand edits came back
|
|
41
|
+
with the next unrelated save. Every save now starts from the router's current config.
|
|
42
|
+
- **Routed workers are no longer told to call `advisor`** after the tool was dropped (#47, #48
|
|
43
|
+
by @115dkk).
|
|
44
|
+
- **`claude-login` finds the Desktop-cached CLI** in its new `<version>/<hash>/` folder (#45,
|
|
45
|
+
#46 by @115dkk). macOS had the same layout.
|
|
46
|
+
- **Codex context overflow** is answered as "prompt is too long", so Claude Code compacts
|
|
47
|
+
instead of retrying the same request (#44 by @Sociopacific); a stream held for that opens
|
|
48
|
+
after five seconds regardless.
|
|
49
|
+
- The Codex app sees a GPT model released after the router started; subagent titles keep the
|
|
50
|
+
model and effort under Desktop's new task panel; a provider's connection test reads a
|
|
51
|
+
model-not-found 404 as an accepted key; a Gemini provider waiting on Google's verification
|
|
52
|
+
is not shown as connected, and the page to open is passed on.
|
|
53
|
+
|
|
54
|
+
## 0.7.0 — 2026-09-29
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **Worker tools** (`cli.limitWorkerTools`, off by default; a toggle in the dashboard). Generated
|
|
59
|
+
workers get only the read, edit, run, search and skill tools instead of inheriting every MCP
|
|
60
|
+
server and plugin tool of the session. One worker's first request fell from 32,942 to 8,823
|
|
61
|
+
tokens. Such a worker cannot use a browser or the iOS Simulator; keep those in an agent file of
|
|
62
|
+
your own.
|
|
63
|
+
- **Images from the ChatGPT subscription.** `clauderipple image "<prompt>" -o file.png`, with
|
|
64
|
+
`--aspect square|landscape|portrait`, `--transparent`, `--format` and `--ref FILE` for reference
|
|
65
|
+
images. Underneath is `POST /api/image` on the admin port, which answers with the image itself.
|
|
66
|
+
Generated workers are told the `curl` form, so a worker with only Bash can make one. The backend
|
|
67
|
+
chooses resolution and quality; about 30 seconds an image.
|
|
68
|
+
|
|
3
69
|
## 0.6.0 — 2026-09-28
|
|
4
70
|
|
|
5
71
|
Both suggested in #8 by @artisthanbohee-del.
|
package/README.ko.md
CHANGED
|
@@ -132,6 +132,15 @@ ClaudeRipple이 Claude에 하는 일은 작고 확인할 수 있습니다. Claud
|
|
|
132
132
|
돌려 쓰는 것도 같은 약관 아래 각자 판단할 일입니다. 계정을 어떻게 다룰지는 Anthropic만 정할 수 있으니, 여기 적은
|
|
133
133
|
내용은 보장이 아닙니다.
|
|
134
134
|
|
|
135
|
+
### Gemini를 Google 계정으로 쓰면요?
|
|
136
|
+
|
|
137
|
+
AI Studio API 키는 Google이 공식 지원하는 방식이고 안전합니다. Google 계정 로그인(Antigravity)은 다릅니다.
|
|
138
|
+
Google [Antigravity 약관](https://antigravity.google/terms) 6조와 [Gemini CLI FAQ](https://geminicli.com/docs/resources/faq/)는
|
|
139
|
+
그 로그인 정보를 다른 프로그램에서 쓰는 것을 위반으로 명시합니다. Google은 2026년 2월 이렇게 쓰인 계정을 정지했고(정지되면
|
|
140
|
+
Gemini CLI와 Code Assist도 막혔습니다), [2차 위반은 영구 정지](https://github.com/google-gemini/gemini-cli/discussions/20632)라고
|
|
141
|
+
밝혔습니다. ClaudeRipple은 다른 도구들도 제공하고 요청이 있어 이 로그인을 넣었으며, 시작 전에 이 경고를 보여 주고 트래픽을
|
|
142
|
+
숨기는 일은 하지 않습니다. 쓰신다면 주 계정이 아닌 계정을 쓰세요.
|
|
143
|
+
|
|
135
144
|
### ChatGPT 계정을 여러 개 쓸 수 있나요?
|
|
136
145
|
|
|
137
146
|
됩니다. `clauderipple login`을 할 때마다(또는 대시보드의 **+ ChatGPT 계정 추가**) 계정이 하나씩 늘어납니다.
|
|
@@ -144,6 +153,13 @@ ClaudeRipple이 Claude에 하는 일은 작고 확인할 수 있습니다. Claud
|
|
|
144
153
|
Claude 모델이 이름 그대로 뜹니다. Claude Code 로그인이나 Anthropic API 키로 답합니다.
|
|
145
154
|
[Codex 앱과 Codex CLI](#codex-앱과-codex-cli) 절을 보세요.
|
|
146
155
|
|
|
156
|
+
### 이미지도 만들 수 있나요?
|
|
157
|
+
|
|
158
|
+
됩니다. API 키 없이 ChatGPT 구독으로 만듭니다: `clauderipple image "빨간 종이배" -o boat.png --aspect square`.
|
|
159
|
+
`--ref 파일`로 참고 이미지를, `--transparent`로 투명 배경을 줄 수 있습니다. 한 장에 30초쯤 걸리고, 해상도와 품질은
|
|
160
|
+
구독 쪽이 정합니다. ClaudeRipple이 만든 서브에이전트에도 같은 방법이 적혀 있어 워커도 이미지를 만듭니다.
|
|
161
|
+
[Claude Code](#claude-code-터미널-remote-control-서브에이전트) 절을 보세요.
|
|
162
|
+
|
|
147
163
|
### Claude Code 안에서는 GPT 성능이 떨어지지 않나요?
|
|
148
164
|
|
|
149
165
|
Claude Code의 시스템 프롬프트와 도구 설명은 Claude에 맞춰 쓰여 있어서, 다른 모델은 남에게 맞춘 지시를 읽는 셈이고
|
|
@@ -219,6 +235,14 @@ ClaudeRipple은 Claude Code 프로세스만 신뢰하는 작은 HTTPS 프록시
|
|
|
219
235
|
서버 측 스레드 처리, 도구 호출과 이미지 왕복, 모델이 받는 범위로 추론 강도 클램프, 호환 벤더에는 Anthropic 전용 필드 제거.
|
|
220
236
|
- **제대로 된 요청 로그.** 누가 물었고 어떤 모델이 답했는지, 입력·캐시·출력 토큰, 지연, 상태를 요청마다. 한 시간 요약 포함.
|
|
221
237
|
- **서브에이전트 이름표.** 백그라운드 작업 패널에 "Agent" 대신 `Terra·high · Review`가 보입니다.
|
|
238
|
+
- **입력창에서 보이는 경로.** Claude Code mod가 입력창 위에 한 줄을 띄웁니다. 마지막 요청이 어디로 갔는지, 추론 강도·캐시
|
|
239
|
+
적중률·시간까지. `Log`(또는 `/ripple-log`)로 요청 로그를 옆 패널에 프로바이더·결과별 색으로 띄우고, `×`(또는
|
|
240
|
+
`/ripple-bar`)로 줄을 숨깁니다. 같은 mod가 Agent 호출에 다른 모델이 붙어도 워커를 제 모델로 돌리고,
|
|
241
|
+
`[[ripple: 이름@강도]]` 표식을 프롬프트 어디에 두든 읽습니다. 클라이언트 화면의 스위치 하나, 또는 `clauderipple mod on`.
|
|
242
|
+
- **ChatGPT 구독으로 이미지 생성.** `clauderipple image "<프롬프트>"`. 참고 이미지와 투명 배경도 됩니다. 서브에이전트에도
|
|
243
|
+
방법이 적혀 있어, GPT나 DeepSeek 워커가 이미지 도구 없이 이미지를 만들고 직접 확인합니다.
|
|
244
|
+
- **원하면 가벼운 워커.** 스위치 하나로, 생성된 서브에이전트에 세션의 MCP·플러그인 도구 전부 대신 워커가 쓰는 도구만
|
|
245
|
+
줍니다. 워커 첫 요청이 3.3만에서 0.9만 토큰으로 줄었습니다.
|
|
222
246
|
- **터미널이 싫은 사람을 위해.** 원클릭 연결 확인과 모델 자동 검색이 붙은 프로바이더 프리셋, 드롭다운 모델 매핑, 자동 저장,
|
|
223
247
|
한국어·영어 UI. 런타임을 품고 첫 실행에 스스로 설치하는 메뉴 막대 앱. 서명·공증 완료.
|
|
224
248
|
|
|
@@ -309,8 +333,8 @@ node packages/cli/src/index.ts ui # 브라우저에서 로컬 GUI 열기
|
|
|
309
333
|
```
|
|
310
334
|
|
|
311
335
|
`uninstall`은 전부 되돌리고 `~/.claude/settings.json`을 백업에서 복원합니다. 그 밖의 명령: `status`, `start`, `stop`,
|
|
312
|
-
`restart`, `logs -f`, `login`, `logout`, `claude-login`, `claude-logout`, `picker on|off`, `agent-title on|off`,
|
|
313
|
-
`codex on|off`.
|
|
336
|
+
`restart`, `logs -f`, `login`, `logout`, `claude-login`, `claude-logout`, `picker on|off`, `agent-title on|off`, `mod on|off`,
|
|
337
|
+
`codex on|off`, `update`, `image "<프롬프트>"`.
|
|
314
338
|
</details>
|
|
315
339
|
|
|
316
340
|
## 클라이언트
|
|
@@ -355,6 +379,22 @@ model: gpt-5.6-sol@medium
|
|
|
355
379
|
알 수 없는 에러로 죽게 두지 않고 이름을 대고 거부합니다(`400 ClaudeRipple: no provider declares "…"`).
|
|
356
380
|
Claude 모델은 언제나 그대로 통과합니다. 생성을 끄려면 `"cli": { "agentFiles": false }`.
|
|
357
381
|
|
|
382
|
+
생성된 워커는 기본적으로 세션의 도구를 전부 물려받아, MCP 서버·플러그인 도구가 워커 요청마다 실립니다.
|
|
383
|
+
`"cli": { "limitWorkerTools": true }`(대시보드의 **워커 도구 제한**)를 켜면 읽기·편집·실행·검색·스킬 도구만
|
|
384
|
+
주어 워커 첫 요청이 약 3.3만에서 0.9만 토큰으로 줄었습니다. 대신 그 워커는 브라우저·시뮬레이터 도구를 못 씁니다.
|
|
385
|
+
그런 도구가 필요한 워커는 직접 만든 에이전트 파일로 두면 되고, 그 파일은 건드리지 않습니다.
|
|
386
|
+
|
|
387
|
+
ChatGPT 프로바이더가 있으면 같은 구독으로 이미지를 만들 수 있습니다.
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
clauderipple image "빨간 종이배, 플랫 일러스트" -o boat.png --aspect square
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
`--transparent`, `--format png|jpeg|webp`, `--ref 파일`(참고 이미지, 여러 번 가능)은 선택입니다. 한 장에 30초쯤 걸립니다.
|
|
394
|
+
구독 백엔드가 해상도와 품질을 직접 정하므로, 모양은 `--aspect`로 말로 요청합니다. 이 명령은 관리 포트의
|
|
395
|
+
`POST /api/image`(응답이 이미지 바이트)를 부르는 클라이언트입니다. 생성된 워커에는 그 `curl` 형태가 적혀 있어,
|
|
396
|
+
Bash만 있는 워커도 이미지를 만들고 Read로 확인할 수 있습니다.
|
|
397
|
+
|
|
358
398
|
Claude Code의 `WebSearch`는 작은 모델에 별도 요청을 보냅니다. ChatGPT로 라우팅한 구성에서 그 요청만
|
|
359
399
|
Anthropic 한도를 쓰지 않게 하려면 `config.json`에서 같은 ChatGPT 프로바이더를 검색 백엔드로 지정합니다:
|
|
360
400
|
|
|
@@ -392,6 +432,7 @@ Anthropic API 키로 갑니다. 구독 로그인 재사용은 Anthropic 약관
|
|
|
392
432
|
| 프로바이더 | 종류 | 인증 | 모델 목록 | 비고 |
|
|
393
433
|
|---|---|---|---|---|
|
|
394
434
|
| ChatGPT 구독 | Codex 백엔드 | 로그인 여러 개, 한도 차면 자동 전환(Codex 로그인도 재사용) | 구독에서 자동으로 읽음(GPT-6 Sol·Luna·Astra, GPT-5.6 …) | 추론 강도 low…max(Luna는 ultra), 프롬프트 캐시 94~99 % |
|
|
435
|
+
| Google Gemini | Gemini API | AI Studio API 키, 또는 Google 계정 로그인(Antigravity, 위 FAQ 참고) | 자동 검색 | 번역(Messages ⇄ generateContent), 턴 사이 thought signature 유지 |
|
|
395
436
|
| OpenRouter | Anthropic 호환 | API 키 | 400+, 자동 검색 | 모델별 추론 강도 지원을 API에서 읽음 |
|
|
396
437
|
| DeepSeek, Kimi, Z.ai GLM, MiniMax, Qwen(국제/중국) | Anthropic 호환 | API 키 | 프리셋 | 벤더 공식 문서로 확인 |
|
|
397
438
|
| xAI Grok, Mistral, Groq, Together, Fireworks | OpenAI 호환 | API 키 | 자동 검색 | 번역(Chat Completions / Responses) |
|
package/README.md
CHANGED
|
@@ -151,6 +151,18 @@ subscription login. That use is subject to Anthropic's terms; an Anthropic API k
|
|
|
151
151
|
the question. Rotating several Claude subscriptions is likewise your call under those
|
|
152
152
|
terms. Only Anthropic can say how an account is treated, so nothing here is a promise.
|
|
153
153
|
|
|
154
|
+
### What about Gemini through a Google account?
|
|
155
|
+
|
|
156
|
+
An AI Studio API key is the way Google supports, and the safe one. The Google account
|
|
157
|
+
sign-in (Antigravity) is different: Google's [Antigravity terms](https://antigravity.google/terms)
|
|
158
|
+
(section 6) and the [Gemini CLI FAQ](https://geminicli.com/docs/resources/faq/) name using
|
|
159
|
+
those credentials from another program as a breach. In February 2026 Google suspended
|
|
160
|
+
accounts used this way — the suspension also cut off Gemini CLI and Code Assist — and
|
|
161
|
+
[said](https://github.com/google-gemini/gemini-cli/discussions/20632) a second violation
|
|
162
|
+
is permanent. ClaudeRipple offers the sign-in because others do and people asked, shows
|
|
163
|
+
this warning before it starts, and does nothing to hide the traffic. Use a secondary
|
|
164
|
+
account if you use it at all.
|
|
165
|
+
|
|
154
166
|
### Can I use several ChatGPT accounts?
|
|
155
167
|
|
|
156
168
|
Yes. Each `clauderipple login` (or **+ Add ChatGPT account** in the dashboard) adds one.
|
|
@@ -165,6 +177,15 @@ Yes. `clauderipple codex on` adds ClaudeRipple as a provider, and Claude models
|
|
|
165
177
|
the Codex app's and CLI's model list under their own names. They answer through your
|
|
166
178
|
Claude Code login or an Anthropic API key. See [Codex app and Codex CLI](#codex-app-and-codex-cli).
|
|
167
179
|
|
|
180
|
+
### Can it generate images?
|
|
181
|
+
|
|
182
|
+
Yes, with a ChatGPT subscription and no API key:
|
|
183
|
+
`clauderipple image "a red paper boat" -o boat.png --aspect square`. `--ref FILE` adds
|
|
184
|
+
a reference image and `--transparent` a transparent background. One image takes about
|
|
185
|
+
30 seconds, and the subscription chooses the resolution and quality. Subagents
|
|
186
|
+
ClaudeRipple generates are told the same call, so they can make images too. See
|
|
187
|
+
[Claude Code](#claude-code-terminal-remote-control-subagents).
|
|
188
|
+
|
|
168
189
|
### Does GPT do worse inside Claude Code?
|
|
169
190
|
|
|
170
191
|
Claude Code's system prompt and tool descriptions are written for Claude, so another model
|
|
@@ -234,7 +255,7 @@ you map go to your provider, everything else goes to Anthropic byte for byte.
|
|
|
234
255
|
|
|
235
256
|
- **Any model in Claude Desktop and Claude Code.** GPT-6 Sol / Luna / Astra and
|
|
236
257
|
GPT-5.6 Terra / Sol / Luna through your ChatGPT Plus/Pro subscription (new models
|
|
237
|
-
appear the day OpenAI serves them), or DeepSeek, Kimi, GLM,
|
|
258
|
+
appear the day OpenAI serves them), Google Gemini, or DeepSeek, Kimi, GLM,
|
|
238
259
|
MiniMax, Qwen, Grok, Mistral, Groq, Together, Fireworks, OpenRouter (400+ models)
|
|
239
260
|
and local Ollama / LM Studio. Under their real names in the picker, or mapped onto
|
|
240
261
|
a Claude name.
|
|
@@ -263,6 +284,18 @@ you map go to your provider, everything else goes to Anthropic byte for byte.
|
|
|
263
284
|
latency, status, per request, with a one-hour summary.
|
|
264
285
|
- **Subagent name tags.** The background-task panel shows `Terra·high · Review`
|
|
265
286
|
instead of a generic "Agent".
|
|
287
|
+
- **The route, where you type.** A Claude Code mod puts one line above the prompt:
|
|
288
|
+
where your last request went, its effort, cache hit and time. `Log` (or `/ripple-log`)
|
|
289
|
+
opens the request log in a side pane, color-coded by provider and result; `×` (or
|
|
290
|
+
`/ripple-bar`) hides the line. The same mod keeps a worker on its own model when the
|
|
291
|
+
Agent call names another, and reads its `[[ripple: name@level]]` marker wherever it sits
|
|
292
|
+
in the prompt. One switch on the Clients screen, or `clauderipple mod on`.
|
|
293
|
+
- **Images from your ChatGPT subscription.** `clauderipple image "<prompt>"`, with
|
|
294
|
+
reference images and transparent backgrounds. Subagents are told how, so a GPT or
|
|
295
|
+
DeepSeek worker can make an image and look at it without an image tool.
|
|
296
|
+
- **Lean workers, if you want them.** One switch gives generated subagents only the
|
|
297
|
+
tools a worker uses instead of every MCP server and plugin of the session: one
|
|
298
|
+
worker's first request fell from 33k to 9k tokens.
|
|
266
299
|
- **Made for people who don't want a terminal.** Provider presets with one-click
|
|
267
300
|
connection tests and model discovery, drop-down model mapping, auto-save, Korean
|
|
268
301
|
and English UI. A menu-bar app that carries its own runtime and sets itself up on
|
|
@@ -359,8 +392,8 @@ node packages/cli/src/index.ts ui # open the local GUI in your browser
|
|
|
359
392
|
|
|
360
393
|
`uninstall` reverses everything and restores `~/.claude/settings.json` from a backup.
|
|
361
394
|
Other commands: `status`, `start`, `stop`, `restart`, `logs -f`, `login`, `logout`,
|
|
362
|
-
`claude-login`, `claude-logout`, `picker on|off`, `agent-title on|off`,
|
|
363
|
-
`codex on|off`.
|
|
395
|
+
`claude-login`, `claude-logout`, `picker on|off`, `agent-title on|off`, `mod on|off`,
|
|
396
|
+
`codex on|off`, `update`, `image "<prompt>"`.
|
|
364
397
|
</details>
|
|
365
398
|
|
|
366
399
|
## Clients
|
|
@@ -413,6 +446,27 @@ by name (`400 ClaudeRipple: no provider declares "…"`) instead of being sent o
|
|
|
413
446
|
to fail somewhere less legible; native Claude models always pass through.
|
|
414
447
|
Set `"cli": { "agentFiles": false }` to turn generation off.
|
|
415
448
|
|
|
449
|
+
By default a generated worker inherits every tool of the session, so each MCP
|
|
450
|
+
server and plugin rides along on every worker request. `"cli": {
|
|
451
|
+
"limitWorkerTools": true }` (or **Worker tools** in the dashboard) gives it only
|
|
452
|
+
the read, edit, run, search and skill tools, which cut one worker's first
|
|
453
|
+
request from about 33k to 9k tokens. Such a worker has no browser or simulator
|
|
454
|
+
tools. A worker that needs them belongs in an agent file of your own, which is
|
|
455
|
+
never touched.
|
|
456
|
+
|
|
457
|
+
With a ChatGPT provider configured, images come from the same subscription:
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
clauderipple image "a red paper boat, flat illustration" -o boat.png --aspect square
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
`--transparent`, `--format png|jpeg|webp` and `--ref FILE` (a reference image,
|
|
464
|
+
repeatable) are optional. One image takes about 30 seconds. The subscription
|
|
465
|
+
backend picks the resolution and quality itself, so the shape is asked for in
|
|
466
|
+
words (`--aspect`). The command is a client of `POST /api/image` on the admin
|
|
467
|
+
port, which answers with the image bytes. Generated workers are told the `curl`
|
|
468
|
+
form of it, so a worker with only Bash can make an image and read it back.
|
|
469
|
+
|
|
416
470
|
Claude Code runs `WebSearch` as a separate small-model request. To keep a
|
|
417
471
|
ChatGPT-routed setup from spending Anthropic quota for that request, select the
|
|
418
472
|
same ChatGPT provider as the search backend in `config.json`:
|
|
@@ -455,6 +509,7 @@ provider you configured is available the same way.
|
|
|
455
509
|
| Provider | Kind | Auth | Model list | Notes |
|
|
456
510
|
|---|---|---|---|---|
|
|
457
511
|
| ChatGPT subscription | Codex backend | several sign-ins, switching when one runs out (Codex login reused too) | read from your subscription (GPT-6 Sol, Luna, Astra, GPT-5.6 …) | effort low…max (Luna: ultra), prompt cache 94–99 % |
|
|
512
|
+
| Google Gemini | Gemini API | AI Studio API key, or Google account sign-in (Antigravity, see below) | discovered | translated (Messages ⇄ generateContent), thought signatures kept across turns |
|
|
458
513
|
| OpenRouter | Anthropic-compatible | API key | 400+, discovered | per-model effort support read from the API |
|
|
459
514
|
| DeepSeek, Kimi, Z.ai GLM, MiniMax, Qwen (intl / cn) | Anthropic-compatible | API key | preset | verified against vendor docs |
|
|
460
515
|
| xAI Grok, Mistral, Groq, Together, Fireworks | OpenAI-compatible | API key | discovered | translated (Chat Completions / Responses) |
|
|
@@ -473,7 +528,8 @@ Claude Desktop / claude CLI ──HTTPS_PROXY──▶ ClaudeRipple ──▶ ap
|
|
|
473
528
|
│
|
|
474
529
|
mapped model ──────────┼──▶ chatgpt.com/backend-api (Responses ⇄ Messages)
|
|
475
530
|
├──▶ Anthropic-compatible vendors (+ compat layer)
|
|
476
|
-
|
|
531
|
+
├──▶ OpenAI-compatible vendors (Messages ⇄ Chat/Responses)
|
|
532
|
+
└──▶ Google Gemini (Messages ⇄ generateContent)
|
|
477
533
|
Codex app / CLI ──/v1/responses──▶ ClaudeRipple ingress ──▶ Claude (your login or API key) / vendors
|
|
478
534
|
```
|
|
479
535
|
|
package/dist/app/package.json
CHANGED
|
@@ -39,6 +39,40 @@ function versionBinaries(versionDir) {
|
|
|
39
39
|
? BINARY_NAMES.map((name) => path.join(versionDir, name))
|
|
40
40
|
: [path.join(versionDir, "claude.app", "Contents", "MacOS", "claude")];
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* The launcher of one cached version: in the version directory itself, else one directory below it,
|
|
44
|
+
* newest first. Claude Desktop 2.19675 on Windows caches CLI 2.1.286 as <version>\<12 hex>\claude.exe
|
|
45
|
+
* beside .payload and .verified files, and moved the 2.1.284 it already had into the same shape, so
|
|
46
|
+
* no version directory held a launcher directly any more (2026-10-02).
|
|
47
|
+
*/
|
|
48
|
+
function cachedBinary(versionDir) {
|
|
49
|
+
const flat = versionBinaries(versionDir).find((candidate) => fs.existsSync(candidate));
|
|
50
|
+
if (flat)
|
|
51
|
+
return flat;
|
|
52
|
+
try {
|
|
53
|
+
const subdirectories = fs.readdirSync(versionDir)
|
|
54
|
+
.flatMap((entry) => {
|
|
55
|
+
const directory = path.join(versionDir, entry);
|
|
56
|
+
try {
|
|
57
|
+
const stat = fs.statSync(directory);
|
|
58
|
+
return stat.isDirectory() ? [{ directory, mtimeMs: stat.mtimeMs }] : [];
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
65
|
+
for (const { directory } of subdirectories) {
|
|
66
|
+
const binary = versionBinaries(directory).find((candidate) => fs.existsSync(candidate));
|
|
67
|
+
if (binary)
|
|
68
|
+
return binary;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// A missing or unreadable version directory has no usable binary.
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
42
76
|
export function latestDesktopClaude() {
|
|
43
77
|
for (const directory of desktopClaudeCodeDirs()) {
|
|
44
78
|
try {
|
|
@@ -46,7 +80,7 @@ export function latestDesktopClaude() {
|
|
|
46
80
|
.filter((entry) => /^\d+\.\d+\.\d+$/.test(entry))
|
|
47
81
|
.sort((left, right) => left.localeCompare(right, undefined, { numeric: true }));
|
|
48
82
|
for (const version of versions.reverse()) {
|
|
49
|
-
const binary =
|
|
83
|
+
const binary = cachedBinary(path.join(directory, version));
|
|
50
84
|
if (binary)
|
|
51
85
|
return binary;
|
|
52
86
|
}
|
package/dist/cli/src/codex.js
CHANGED
|
@@ -104,6 +104,29 @@ export function writeCodexCatalog(models, home = codexHome()) {
|
|
|
104
104
|
fs.writeFileSync(file, JSON.stringify({ models: [...theirs, ...ours] }, null, 1), { mode: 0o600 });
|
|
105
105
|
return file;
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Our catalog is a copy of Codex's cache, so it goes stale when Codex refreshes that cache: a model
|
|
109
|
+
* OpenAI released after the router started stayed missing from the app (gpt-6.1-sol, 2026-09-30 —
|
|
110
|
+
* the copy was written at router start, Codex's cache gained the model half an hour later). Rewrite
|
|
111
|
+
* the copy whenever the cache changes, while `codex on` is in effect. Polls, as Codex replaces the
|
|
112
|
+
* file rather than editing it; once a minute is plenty, since the app reads the catalog only when it
|
|
113
|
+
* starts. Returns a stop function.
|
|
114
|
+
*/
|
|
115
|
+
export function followCodexCache(models, home = codexHome(), intervalMs = 60_000, onError = () => { }) {
|
|
116
|
+
const cache = path.join(home, "models_cache.json");
|
|
117
|
+
const listener = (now, before) => {
|
|
118
|
+
if (now.mtimeMs === before.mtimeMs || !codexEnabled(home))
|
|
119
|
+
return;
|
|
120
|
+
try {
|
|
121
|
+
writeCodexCatalog(models(), home);
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
onError(e);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
fs.watchFile(cache, { interval: intervalMs, persistent: false }, listener);
|
|
128
|
+
return () => fs.unwatchFile(cache, listener);
|
|
129
|
+
}
|
|
107
130
|
function catalogBlock(file) {
|
|
108
131
|
return `${CATALOG_START}
|
|
109
132
|
model_catalog_json = ${JSON.stringify(file)}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
// Claude Code PreToolUse hook (matcher: Agent|Task): put the real model and thinking depth into the
|
|
2
2
|
// subagent's title, e.g. "Terra·high · Provider presets", so the Claude Desktop background-task
|
|
3
|
-
// panel shows which model a subagent runs on. The app's
|
|
4
|
-
//
|
|
3
|
+
// panel shows which model a subagent runs on. The app's model line names Claude models only (its
|
|
4
|
+
// display table is a fixed list of Claude families, app web bundle 2026-09-30), so for a routed
|
|
5
|
+
// model the title is the only text we can influence.
|
|
5
6
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// The panel shows this title only while it follows the session live: rebuilt from the transcript
|
|
8
|
+
// (another session opened and back, app restart) it takes the description the parent model wrote,
|
|
9
|
+
// which a hook cannot change. So the generated agent files also ask the parent to write the same
|
|
10
|
+
// prefix itself (packages/router/src/agents.ts); here it is corrected to what the router will do.
|
|
11
|
+
//
|
|
12
|
+
// Model resolution mirrors the router (packages/router/src/routing.ts): a `[[ripple: sol@xhigh]]`
|
|
13
|
+
// marker at the very top of the prompt, else the agent definition's frontmatter `model:`, else the
|
|
14
|
+
// explicit `model` argument, else the parent session's model from the transcript tail. Depth = the
|
|
15
|
+
// marker's level, else the agent file's `@level`, else the hook input's effort.level, after the
|
|
16
|
+
// router's `effortClamp`. Runs in <50ms, never fails the tool call (empty output = no-op).
|
|
10
17
|
//
|
|
11
18
|
// Installed by `clauderipple agent-title on` into ~/.claude/settings.json; the router ships no
|
|
12
19
|
// Python, so this is TypeScript run by the Node recorded in <home>/paths.json.
|
|
@@ -14,36 +21,42 @@ import fs from "node:fs";
|
|
|
14
21
|
import os from "node:os";
|
|
15
22
|
import path from "node:path";
|
|
16
23
|
import { fileURLToPath } from "node:url";
|
|
17
|
-
|
|
24
|
+
// The router's marker (routing.ts MARKER): honoured only at the very top of the prompt.
|
|
25
|
+
const MARKER = /^\s*\[\[\s*(?:ripple|gpt)\s*:\s*([A-Za-z0-9.\-]+)\s*(?:@\s*([A-Za-z]+))?\s*\]\]/;
|
|
26
|
+
const REMINDER = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
|
|
18
27
|
const OUR_PREFIX = /^[^\s·]+(?:·[a-z]+)? · /;
|
|
19
28
|
const CLAUDE_ID = /^claude-(opus|sonnet|haiku|fable)-(\d+(?:-\d+)*?)(?:-\d{8})?$/;
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
/** The router's default `effortClamp` (config.ts), for when config.json cannot be read. */
|
|
30
|
+
const DEFAULT_CLAMP = { ultra: "max" };
|
|
31
|
+
/** A model's name in a title: the last word of its display name ("GPT-6.1 Sol" → "Sol"). */
|
|
32
|
+
export function shortName(name) {
|
|
33
|
+
const words = name.trim().split(/\s+/);
|
|
34
|
+
return words[words.length - 1];
|
|
35
|
+
}
|
|
36
|
+
function titleConfig() {
|
|
37
|
+
const names = { "gpt-5.6-terra": "Terra", "gpt-5.6-sol": "Sol", "gpt-5.6-luna": "Luna", "gpt-6-astra": "Astra", "gpt-6-sol": "Sol", "gpt-6-luna": "Luna" };
|
|
23
38
|
const home = process.env.CLAUDERIPPLE_HOME ?? path.join(os.homedir(), ".clauderipple");
|
|
24
39
|
try {
|
|
25
40
|
const cfg = JSON.parse(fs.readFileSync(path.join(home, "config.json"), "utf8"));
|
|
26
41
|
const add = (id, name) => {
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
const words = name.trim().split(/\s+/);
|
|
30
|
-
out[id.toLowerCase()] = words.length > 1 ? words[words.length - 1] : name.trim();
|
|
42
|
+
if (id && name)
|
|
43
|
+
names[id.toLowerCase()] = shortName(name);
|
|
31
44
|
};
|
|
32
45
|
for (const m of cfg.cli?.extraModels ?? [])
|
|
33
46
|
add(m.model, m.name);
|
|
34
47
|
for (const p of Object.values(cfg.providers ?? {}))
|
|
35
48
|
for (const m of p.models ?? [])
|
|
36
49
|
add(m.id, m.name);
|
|
50
|
+
return { names, aliases: cfg.aliases ?? {}, effortClamp: { ...DEFAULT_CLAMP, ...(cfg.effortClamp ?? {}) } };
|
|
37
51
|
}
|
|
38
52
|
catch {
|
|
39
|
-
|
|
53
|
+
return { names, aliases: {}, effortClamp: DEFAULT_CLAMP };
|
|
40
54
|
}
|
|
41
|
-
return out;
|
|
42
55
|
}
|
|
56
|
+
/** The `[[ripple: <name>@<effort>]]` a prompt starts with, name lowercased. Not resolved. */
|
|
43
57
|
export function fromMarker(prompt) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
return m ? { model: m[1].toLowerCase(), effort: (m[2] ?? "").toLowerCase() } : null;
|
|
58
|
+
const m = MARKER.exec(prompt.replace(REMINDER, ""));
|
|
59
|
+
return m ? { name: m[1].toLowerCase(), effort: (m[2] ?? "").toLowerCase() } : null;
|
|
47
60
|
}
|
|
48
61
|
export function fromAgentFile(subagentType, dirs) {
|
|
49
62
|
if (!subagentType || subagentType.includes("/") || subagentType.startsWith("."))
|
|
@@ -106,28 +119,44 @@ function parentModel(transcriptPath) {
|
|
|
106
119
|
}
|
|
107
120
|
return null;
|
|
108
121
|
}
|
|
109
|
-
|
|
122
|
+
/**
|
|
123
|
+
* The model and effort the router will use for this call. The marker's name resolves as the router's
|
|
124
|
+
* aliases do (agents.ts withAgentAliases): an explicit alias, else an agent file's model, else the
|
|
125
|
+
* name itself; its effort, when absent, is the one the subagent's own `model:` asks for.
|
|
126
|
+
*/
|
|
127
|
+
function routed(inp, agentDirs, aliases) {
|
|
128
|
+
const own = fromAgentFile(typeof inp.subagent_type === "string" ? inp.subagent_type : "", agentDirs);
|
|
129
|
+
const marker = fromMarker(typeof inp.prompt === "string" ? inp.prompt : "");
|
|
130
|
+
if (!marker)
|
|
131
|
+
return own;
|
|
132
|
+
const model = aliases[marker.name]?.toLowerCase() ?? fromAgentFile(marker.name, agentDirs)?.model ?? marker.name;
|
|
133
|
+
return { model, effort: marker.effort || (own?.effort ?? "") };
|
|
134
|
+
}
|
|
135
|
+
export function retitle(input, agentDirs, cfg) {
|
|
110
136
|
if (input.tool_name !== "Agent" && input.tool_name !== "Task")
|
|
111
137
|
return null;
|
|
112
138
|
const inp = input.tool_input ?? {};
|
|
113
139
|
const desc = inp.description;
|
|
114
140
|
if (typeof desc !== "string" || desc.trim() === "")
|
|
115
141
|
return null;
|
|
116
|
-
const
|
|
117
|
-
const
|
|
142
|
+
const clamp = cfg.effortClamp ?? DEFAULT_CLAMP;
|
|
143
|
+
const level = (e) => clamp[e] ?? e;
|
|
144
|
+
const effort = level((typeof input.effort === "object" && input.effort ? input.effort.level : typeof input.effort === "string" ? input.effort : "") ?? "");
|
|
145
|
+
const got = routed(inp, agentDirs, cfg.aliases ?? {});
|
|
146
|
+
const names = cfg.names;
|
|
118
147
|
let tag = null;
|
|
119
|
-
if (got) {
|
|
120
|
-
//
|
|
148
|
+
if (got && !claudePretty(got.model)) {
|
|
149
|
+
// Legacy short forms ("sol") resolve against full ids ("gpt-5.6-sol").
|
|
121
150
|
const key = names[got.model] !== undefined ? got.model : Object.keys(names).find((k) => k.endsWith(`-${got.model}`));
|
|
122
|
-
const name = (key ? names[key] : undefined) ??
|
|
123
|
-
|
|
124
|
-
tag = got.effort ? `${name}·${got.effort}` : name;
|
|
151
|
+
const name = (key ? names[key] : undefined) ?? got.model;
|
|
152
|
+
tag = got.effort ? `${name}·${level(got.effort)}` : name;
|
|
125
153
|
}
|
|
126
154
|
if (tag === null) {
|
|
127
155
|
const name = claudePretty(typeof inp.model === "string" ? inp.model : undefined) ?? (got ? claudePretty(got.model) : null) ?? claudePretty(parentModel(input.transcript_path) ?? undefined);
|
|
128
156
|
if (!name)
|
|
129
157
|
return null;
|
|
130
|
-
|
|
158
|
+
const e = got?.effort ? level(got.effort) : effort;
|
|
159
|
+
tag = e ? `${name}·${e}` : name;
|
|
131
160
|
}
|
|
132
161
|
const prefix = `${tag} · `;
|
|
133
162
|
if (desc.startsWith(prefix))
|
|
@@ -150,7 +179,7 @@ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToP
|
|
|
150
179
|
try {
|
|
151
180
|
const raw = fs.readFileSync(0, "utf8");
|
|
152
181
|
const data = JSON.parse(raw || "{}");
|
|
153
|
-
const updated = retitle(data, defaultAgentDirs(),
|
|
182
|
+
const updated = retitle(data, defaultAgentDirs(), titleConfig());
|
|
154
183
|
if (updated)
|
|
155
184
|
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", updatedInput: updated } }) + "\n");
|
|
156
185
|
}
|