clauderipple 0.11.1 → 0.12.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.es.md +48 -0
  3. package/README.ja.md +42 -0
  4. package/README.ko.md +39 -0
  5. package/README.md +44 -0
  6. package/README.pt-BR.md +46 -0
  7. package/README.zh-CN.md +40 -0
  8. package/dist/app/dist/claude-desktop.js +31 -0
  9. package/dist/app/dist/main.js +127 -8
  10. package/dist/app/dist/status-model.js +12 -0
  11. package/dist/app/dist/update-model.js +32 -0
  12. package/dist/app/package.json +1 -1
  13. package/dist/cli/src/index.js +5 -1
  14. package/dist/mod/hooks/i18n.ts +6 -0
  15. package/dist/mod/hooks/modes.ts +15 -8
  16. package/dist/mod/hooks/register.tsx +67 -19
  17. package/dist/mod/hooks/settings.js +37 -2
  18. package/dist/mod/hooks/settings.ts +31 -3
  19. package/dist/mod/hooks/ui/agents.tsx +1 -1
  20. package/dist/mod/hooks/ui/art.ts +8 -3
  21. package/dist/mod/hooks/ui/band.tsx +6 -2
  22. package/dist/mod/hooks/ui/flat.ts +11 -8
  23. package/dist/mod/hooks/ui/look.ts +3 -1
  24. package/dist/mod/hooks/ui/view.ts +2 -0
  25. package/dist/mod/hooks/workers.ts +8 -1
  26. package/dist/mod/tests/delegation.test.tsx +50 -0
  27. package/dist/mod/tests/register.test.tsx +138 -1
  28. package/dist/mod/tests/ui.test.tsx +6 -3
  29. package/dist/mod/types/index.d.ts +14 -0
  30. package/dist/router/src/admin.js +41 -76
  31. package/dist/router/src/autoupdate.js +299 -0
  32. package/dist/router/src/bootstrap.js +3 -2
  33. package/dist/router/src/cli-control.js +65 -0
  34. package/dist/router/src/config.js +70 -11
  35. package/dist/router/src/index.js +11 -1
  36. package/dist/router/src/picker.js +25 -1
  37. package/dist/router/src/presets.js +21 -13
  38. package/dist/router/src/providers/chatgpt/index.js +11 -0
  39. package/dist/router/src/providers/google/index.js +7 -2
  40. package/dist/router/src/providers/google/transport.js +16 -1
  41. package/dist/router/src/proxy.js +46 -6
  42. package/dist/router/src/requestlog.js +3 -2
  43. package/dist/router/src/version.js +1 -1
  44. package/dist/router/src/websearch.js +2 -2
  45. package/dist/ui/app.js +247 -42
  46. package/dist/ui/i18n.js +154 -18
  47. package/dist/ui/style.css +21 -0
  48. package/docs/ARCHITECTURE.md +61 -11
  49. package/package.json +1 -1
package/dist/ui/style.css CHANGED
@@ -465,6 +465,23 @@ label.check input { margin-top: 2px; }
465
465
  #phone-content code { display: block; margin-top: 6px; overflow-wrap: anywhere; }
466
466
  #phone-content > .section:first-child { margin-top: 0; }
467
467
 
468
+ /* Ripple Remote, until a phone is paired: the connection card's shape in the accent, like the
469
+ laptop setup card. The QR below pairs from inside the app, so the store has its own button here. */
470
+ .conn-card.remote-promo { --tone: var(--accent-fill); --tint: var(--accent-soft); margin-bottom: 12px; padding-bottom: 20px; }
471
+ .remote-promo:lang(ko) { word-break: keep-all; overflow-wrap: anywhere; }
472
+ .remote-promo .conn-icon svg { stroke-width: 1.9; }
473
+ .remote-eyebrow { margin: 0 0 3px; color: var(--accent); font-size: var(--fs-foot); font-weight: 600; }
474
+ .remote-points { display: grid; gap: 8px; margin: 18px 0 0; padding: 0 0 0 60px; list-style: none; }
475
+ .remote-points li { position: relative; padding-left: 22px; color: var(--label-2); font-size: var(--fs-sub); line-height: 1.45; }
476
+ .remote-points li::before { content: ""; position: absolute; left: 2px; top: 0.42em; width: 9px; height: 5px; border-left: 2px solid var(--accent); border-bottom: 2px solid var(--accent); transform: rotate(-45deg); }
477
+ .remote-points strong { color: var(--label); font-weight: 600; }
478
+ .remote-get { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; margin: 20px 0 0 60px; }
479
+ .btn.large.remote-store { width: auto; gap: 8px; }
480
+ .remote-store-icon { width: 18px; height: 18px; stroke-width: 2; }
481
+ .remote-get .hint { flex: 1 1 200px; }
482
+ .remote-android .login-link { white-space: nowrap; }
483
+ .remote-next { margin: 16px 0 0 60px; padding-top: 14px; border-top: 1px solid var(--separator); color: var(--label-2); font-size: var(--fs-foot); line-height: 1.45; }
484
+
468
485
  /* ---- Providers ----------------------------------------------------------------------- */
469
486
 
470
487
  .provider-workspace { display: grid; grid-template-columns: 264px minmax(0, 1fr); gap: 28px; align-items: start; }
@@ -989,6 +1006,10 @@ a.cell { color: inherit; text-decoration: none; }
989
1006
  .mac-side { flex: 1 1 100%; padding-left: 24px; text-align: left; }
990
1007
  .mac-allow, .mac-setup-note, .mac-setup .form-field, .mac-setup-actions, .mac-setup .conn-note { margin-left: 0; padding-left: 0; }
991
1008
  .mac-setup .form-field, .mac-setup-actions { max-width: none; }
1009
+ .remote-points, .remote-get, .remote-next { margin-left: 0; padding-left: 0; }
1010
+ .remote-get { flex-direction: column; align-items: stretch; }
1011
+ .btn.large.remote-store { width: 100%; }
1012
+ .remote-get .hint { flex: 0 0 auto; text-align: center; }
992
1013
 
993
1014
  /* Requests read as a list: model and status on top, the numbers on one quiet line below. */
994
1015
  .logs-table-card { overflow: visible; }
@@ -686,15 +686,15 @@ must preserve **Data: Artificial Analysis (artificialanalysis.ai)** attribution.
686
686
  its hits arrive as Responses events (`web_search_call`, `url_citation`), a backend the router
687
687
  does not have. DeepSeek's own endpoint and the ChatGPT provider run Anthropic's server tool;
688
688
  OpenCode Go's chat endpoint, and every `openai-compatible` provider, do not.
689
- - **OpenCode Go's Anthropic endpoint does, for DeepSeek only** (corrected 2026-10-07). The
690
- line above once said it did not. Re-measured with the key in `x-api-key` (a bearer there
691
- answers "Missing API key"), Claude Code's own side request to `/zen/go/v1/messages` came back
692
- with ten real hits and `web_search_requests: 1` for deepseek-v4.1-flash, deepseek-v4-flash
693
- and deepseek-v4-pro; Kimi K3, MiniMax M3 and Qwen3.8 Flash took the tool and never searched;
694
- GLM and MiMo are not served there. The preset lists those three as `anthropicSearch`, and a
695
- search routed to one of them (a `smallFast` slot on OpenCode Go's DeepSeek) is relayed there
696
- by `anthropicServerToolBackend`, though the model's turns stay on Chat Completions. The
697
- Clients screen reads `webSearchModels` from `/api/status` to stop calling them unsearchable.
689
+ - **OpenCode Go's DeepSeek does not search, and never did** (corrected 2026-10-21).
690
+ DeepSeek's search has only ever worked on DeepSeek's own API. An entry written 2026-10-07
691
+ (commit 53a8db3) claimed Claude Code's side request to `/zen/go/v1/messages` came back with
692
+ ten real hits for OpenCode Go's DeepSeek models; that claim was wrong — the user reports it
693
+ never searched, and on 2026-10-21 the relay answered HTTP 200 with no
694
+ `web_search_tool_result` hits (`router.log`: "no results returned", twice, then a refusal).
695
+ The preset's `anthropicSearch` list is therefore empty, so `searchProviderFor` answers
696
+ nothing for those models, `/api/status` drops them from `webSearchModels`, and a search
697
+ routed there is refused instead of answered with invented prose.
698
698
  - **A model that cannot search does not merely decline — it invents.** Measured 2026-09-21
699
699
  against OpenCode Go's chat endpoint: `plugins: [{id: "web"}]` is accepted and ignored (HTTP
700
700
  200, no `annotations`, cache reporting no lookup), and the model answers a search-shaped
@@ -1111,10 +1111,14 @@ what we do not have yet — so that adding a provider does not start with readin
1111
1111
  lists them together (measured 2026-09-18). `wire` and `url` sat only on the provider,
1112
1112
  so that one account had to be configured as three providers — a split the user saw in
1113
1113
  the GUI and had to repeat in every `direct` rule. A model entry now overrides `wire`
1114
- (`chat`, `responses` or `anthropic`), `url` and `authHeader`, and `providerFor` in
1114
+ (`chat`, `responses`, `anthropic` or `google`), `url` and `authHeader`, and `providerFor` in
1115
1115
  `config.ts` folds those into an ordinary provider at the moment a request's model is
1116
1116
  known, so every `provider.type` branch downstream works against one unchanged
1117
- contract. A provider with no override for that model is returned by identity.
1117
+ contract. A `google` entry folds into a `google`/`api-key` provider (2026-10-21): the
1118
+ provider's key moves from its headers to `apiKey`, the model's `url` becomes the base,
1119
+ and the provider's `sessionHeader` and every non-auth header travel with it. The key is
1120
+ never restated in the model entry. A provider with no override for that model is returned
1121
+ by identity.
1118
1122
  `authHeader` names a convention rather than restating the key: the provider's own
1119
1123
  credential is re-sent under the header that model's wire expects, so there is one copy
1120
1124
  of the key to rotate. Each endpoint follows the API it imitates — an Anthropic-wire
@@ -1221,6 +1225,17 @@ including the 2026-10-16 Antigravity measurement in §4d-ii, are identified sepa
1221
1225
  - Stream endpoint `POST {base}/v1beta/models/{model}:streamGenerateContent?alt=sse`,
1222
1226
  the key on header `x-goog-api-key`, base default `https://generativelanguage.googleapis.com`
1223
1227
  (https://ai.google.dev/api/generate-content#method:-models.streamgeneratecontent).
1228
+ - **A gemini model on a relay (OpenCode Go, 2026-10-21).** `opencode-go` serves
1229
+ `gemini-4-argon-contributor` at `https://opencode.ai/inference/go/google/v1beta/models/<id>`
1230
+ (https://opencode.ai/docs/go/, read 2026-10-21), so the preset's entry carries
1231
+ `wire: "google"` and that base. The transport still sends the key on `x-goog-api-key`, and
1232
+ adds two things a relay asks for: the provider's `sessionHeader` with
1233
+ `conversationKey(json)` — stable for one conversation, different between two, the same
1234
+ derivation the OpenAI path uses (`providers/openai/index.ts`) — and, only when a session
1235
+ header is set, `user-agent: clauderipple/<version>` instead of an SDK/library name. Without
1236
+ the session header OpenCode answers `400 MissingSessionID`. Plain AI Studio providers set
1237
+ neither and send no custom User-Agent. The provider's other headers are applied as given.
1238
+ Not live-measured as of this date.
1224
1239
  - `systemInstruction` takes `{role:"user"|"model", parts:[…]}`; `contents` is the
1225
1240
  `user`/`model` turn list; a part is `{text}`, `{inlineData:{mimeType,data}}`,
1226
1241
  `{functionCall:{name,args}}` or `{functionResponse:{name,response}}`. The `role` of a
@@ -1584,6 +1599,41 @@ plan unless marked otherwise.
1584
1599
  | Android hotspot never recognised (2026-10-20, found by reading the code for an owner with Windows laptops and Android phones in mind): "on the hotspot" was the iPhone range 172.20.10.0/28 alone, on macOS and Windows. An Android hotspot hands out 192.168.43.x (≤ Android 10) or a random /24 (Android 11+; 192.168/16, 172.16/12, 10/8 by Android 14), so after our own failover `join()` saw no hotspot address and reported `join-failed`/`hotspot-not-visible`, while on the phone the controller read Wi-Fi and never planned the way back, and a by-hand join was never recognised | The product does not assume an iPhone. After our own join of the configured SSID is accepted by the OS, a new, non-link-local address within the settle time is the join, and its subnet (address + netmask from `os.networkInterfaces()`: no Location, no elevation) is kept in `<home>/tether.json` for that hotspot name; "on the hotspot" = iPhone range OR that network (§8). Only our own join replaces it, and a subnet equal to the Wi-Fi watched before is not remembered (logged as ambiguous) so home Wi-Fi is never read as the hotspot. Hints name both phones where the step differs. Not yet run with a real Android phone. |
1585
1600
  | An expired Claude login on the Mac wrote `[desktop state] …로그인이 만료되었습니다…` every few seconds per Claude app session and read the keychain (a synchronous child process) for every request, while phones learned of it only by a failed send (2026-10-15) | A login problem is one host state: logged when it starts and ends, sent to phones as `HostInfo.desktopProblem` (a banner), and the keychain is reread at most every 30 s while expired or after a 401. Per-session Remote Control errors log once per change. The host never refreshes the login itself. |
1586
1601
 
1602
+ ### Router-owned automatic updates (2026-10-21)
1603
+
1604
+ The always-running router owns the scheduler, not the optional tray. `autoupdate.ts` starts its
1605
+ first `update --check --json` after ten minutes and checks again every twelve hours with ±30 minutes
1606
+ of jitter. Only script and global npm installs are eligible; packaged apps, source checkouts and
1607
+ unknown layouts are checked but never automatically installed. The dashboard's **Automatic updates**
1608
+ switch saves `Config.autoUpdate`; omission means on, and changes are hot-reloaded without restarting.
1609
+
1610
+ Installation waits for zero model calls in flight across both the proxy and OpenAI ingress, and ten
1611
+ minutes since the latest request start/end (or router startup). Busy routers are checked every five
1612
+ minutes; after twenty-four hours the wait is abandoned until the next cycle. A request arriving after
1613
+ the idle decision still follows the existing graceful drain rules above. The scheduler stops when
1614
+ that drain begins. Manual dashboard updates and automatic updates share one coordinator and the
1615
+ same detached CLI process-group path in `cli-control.ts`; the installer survives the router's exit.
1616
+
1617
+ Before launch, `<home>/auto-update.json` is atomically written with from/to versions and attempt time.
1618
+ The CLI is pinned to that target through `CLAUDERIPPLE_PACKAGE`. The successor only records completion
1619
+ when its running version exactly matches the target; a CLI exit alone is not proof of completion.
1620
+ Failures retain the failed version, timestamp and error, suppressing automatic retries of that
1621
+ version for twenty-four hours, including across router restarts. A surviving installer PID blocks a
1622
+ second installation. Unreadable state disables installs rather than losing the retry record. Every
1623
+ scheduler decision is logged at info level. GET `/api/status` exposes `autoUpdate`, including enabled,
1624
+ install kind, available version, running state, lastUpdatedFrom/To/At and lastError.
1625
+
1626
+ The tray consumes that status, deduplicates completed/available notifications in memory and
1627
+ `<home>/update-notifications.json`, and relaunches an outdated script/npm tray after confirmed
1628
+ completion, waiting for the installer PID to exit so its tray-runtime setup can finish first.
1629
+ Disabled automatic updates and unsupported install kinds get a click-to-update notice;
1630
+ packaged apps retain the releases-page flow. Existing versions **without this scheduler cannot be
1631
+ remotely updated into it**: their owner must first update manually through an existing supported path.
1632
+ No remotely pushed command or live installation test is part of this feature's validation.
1633
+
1634
+ Sources: `packages/router/src/autoupdate.ts`, `cli-control.ts`, `index.ts`, `admin.ts`, `config.ts`;
1635
+ `packages/app/src/update-model.ts`, `main.ts`; scheduler and tray decision tests.
1636
+
1587
1637
  ## 6. Blocked paths (measured, do not retry)
1588
1638
 
1589
1639
  - The app puts `ANTHROPIC_BASE_URL` in the CLI process env directly;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clauderipple",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "Use GPT (any ChatGPT plan with Codex), DeepSeek and 400+ other models in the Claude Desktop Code tab and Claude Code while staying signed in to your Claude subscription — no third-party gateway mode. Several ChatGPT accounts that take over when one runs out, in Codex too.",
5
5
  "license": "GPL-3.0-only",
6
6
  "workspaces": [