clauderipple 0.12.4 → 0.12.5

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 CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.5 — 2026-10-22
4
+
5
+ ### Changed
6
+
7
+ - **The install script adds the menu-bar app on macOS and Windows.** It carries the repair
8
+ screen that opens while the router is down, which someone who does not use a terminal needs.
9
+ Electron is about 270MB and fetched once; `CLAUDERIPPLE_NO_TRAY=1` skips it, and a failed
10
+ fetch leaves the router installed with a hint to add the app later. Linux keeps it opt-in.
11
+
12
+ ### Fixed
13
+
14
+ - **Updating restarts the phone host too.** An update replaced the files but left the running
15
+ host on the old code until the machine restarted, so a fix like 0.12.4's Windows sending did
16
+ not arrive. `install` (which every update ends with) now restarts a phone host this
17
+ installation registered, unless a turn is in progress; then it says to run
18
+ `clauderipple remote install` afterwards. A host run from a source checkout is left alone.
19
+ - **Windows with a non-ASCII account name no longer re-registers the router on every install.**
20
+ The scheduled task was read back in the console's code page, so it never matched (#74, #75 by
21
+ @115dkk). Tests that assumed "/" paths or POSIX permission bits now run only where those hold
22
+ (#76).
23
+
3
24
  ## 0.12.4 — 2026-10-22
4
25
 
5
26
  ### Added
package/README.es.md CHANGED
@@ -432,15 +432,17 @@ clauderipple ui
432
432
 
433
433
  **Proveedores** → añade ChatGPT o pega una clave de API → **Asignación de modelos**.
434
434
 
435
- **La app de la barra de menús / bandeja** es opcional. Muestra el estado del router y abre
436
- el panel con un clic:
435
+ **La app de la barra de menús / bandeja** muestra el estado del router, abre el panel con un
436
+ clic y ofrece una pantalla de reparación que funciona aunque el router esté caído:
437
437
 
438
438
  ```sh
439
439
  clauderipple tray
440
440
  ```
441
441
 
442
- Funciona con Electron, que ocupa unos 270 MB y por eso no se instala por defecto.
443
- `clauderipple tray --install` lo descarga una sola vez; todo lo demás funciona sin él.
442
+ En macOS y Windows el script de instalación la añade por defecto. Funciona con Electron, unos
443
+ 270 MB que se descargan una sola vez; define `CLAUDERIPPLE_NO_TRAY=1` antes del comando de
444
+ instalación para omitirla. En Linux, o tras instalar solo con npm, `clauderipple tray --install`
445
+ la añade. Todo lo demás funciona sin ella.
444
446
 
445
447
  **Actualización.** La bandeja ofrece la nueva versión cuando la hay (**Buscar actualizaciones…**), o bien:
446
448
 
package/README.ja.md CHANGED
@@ -258,13 +258,13 @@ clauderipple ui
258
258
 
259
259
  **プロバイダー** → ChatGPTを追加するかAPIキーを貼り付け → **モデルマッピング**。
260
260
 
261
- **メニューバー / トレイアプリ**は任意です。ルーターの状態を表示し、ワンクリックでダッシュボードを開けます。
261
+ **メニューバー / トレイアプリ**はルーターの状態を表示し、ワンクリックでダッシュボードを開き、ルーターが止まっていても開ける修復画面を備えています。
262
262
 
263
263
  ```sh
264
264
  clauderipple tray
265
265
  ```
266
266
 
267
- Electronで動作し、約270MBあるため、デフォルトではインストールされません。`clauderipple tray --install`で一度だけ取得します。それ以外の機能はすべてElectronなしで動きます。
267
+ macOSとWindowsではインストールスクリプトが標準で追加します。Electronで動作し約270MBで、取得は一度だけです。不要ならインストールコマンドの前に`CLAUDERIPPLE_NO_TRAY=1`を付けてください。Linux、またはnpmだけでインストールした場合は`clauderipple tray --install`で追加します。それ以外の機能はすべてアプリなしで動きます。
268
268
 
269
269
  **アップデート**。新しいバージョンがあればトレイから案内されます(**アップデートを確認…**)。または次のコマンドを実行します。
270
270
 
package/README.ko.md CHANGED
@@ -350,14 +350,15 @@ clauderipple ui
350
350
 
351
351
  **프로바이더** → ChatGPT 추가 또는 API 키 붙여 넣기 → **모델 매핑**.
352
352
 
353
- **메뉴 막대·트레이 앱은 선택 사항입니다.** 라우터 상태를 보여주고 한 번에 대시보드를 엽니다.
353
+ **메뉴 막대·트레이 앱**은 라우터 상태를 보여주고, 한 번에 대시보드를 열고, 라우터가 죽어도 열리는 복구 화면을 갖고 있습니다.
354
354
 
355
355
  ```sh
356
356
  clauderipple tray
357
357
  ```
358
358
 
359
- Electron으로 도는데 용량이 270MB라 기본 설치에는 넣지 않았습니다. `clauderipple tray --install`이 한 번만
360
- 받아 옵니다. 나머지 기능은 Electron 없이도 다 돕니다.
359
+ macOS와 Windows에서는 설치 스크립트가 기본으로 함께 설치합니다. Electron으로 돌아 약 270MB이고 한 번만 받습니다.
360
+ 빼고 싶으면 설치 명령 앞에 `CLAUDERIPPLE_NO_TRAY=1`을 붙이세요. Linux나 npm으로만 설치했다면
361
+ `clauderipple tray --install`로 추가합니다. 나머지 기능은 앱 없이도 다 돕니다.
361
362
 
362
363
  **업데이트.** 새 버전이 나오면 트레이가 알려 줍니다(**업데이트 확인…**). 터미널에서는:
363
364
 
package/README.md CHANGED
@@ -413,15 +413,16 @@ clauderipple ui
413
413
 
414
414
  **Providers** → add ChatGPT or paste an API key → **Model mapping**.
415
415
 
416
- **The menu-bar / tray app** is optional. It shows the router's state and opens the
417
- dashboard in a click:
416
+ **The menu-bar / tray app** shows the router's state, opens the dashboard in a click, and
417
+ keeps a repair screen that works while the router is down:
418
418
 
419
419
  ```sh
420
420
  clauderipple tray
421
421
  ```
422
422
 
423
- It runs on Electron, which is about 270MB and is therefore not installed by default.
424
- `clauderipple tray --install` fetches it once; everything else works without it.
423
+ On macOS and Windows the install script adds it by default. It runs on Electron, about 270MB,
424
+ fetched once; set `CLAUDERIPPLE_NO_TRAY=1` before the install command to skip it. On Linux, or
425
+ after an npm-only install, `clauderipple tray --install` adds it. Everything else works without it.
425
426
 
426
427
  **Updating.** The tray offers a new version when there is one (**Check for Updates…**), or:
427
428
 
package/README.pt-BR.md CHANGED
@@ -424,15 +424,17 @@ clauderipple ui
424
424
 
425
425
  **Provedores** → adicione o ChatGPT ou cole uma chave de API → **Mapeamento de modelos**.
426
426
 
427
- **O app da barra de menus / bandeja** é opcional. Ele mostra o estado do roteador e abre o
428
- painel com um clique:
427
+ **O app da barra de menus / bandeja** mostra o estado do roteador, abre o painel com um
428
+ clique e traz uma tela de reparo que funciona mesmo com o roteador fora do ar:
429
429
 
430
430
  ```sh
431
431
  clauderipple tray
432
432
  ```
433
433
 
434
- Ele roda em Electron, que tem cerca de 270 MB e, por isso, não é instalado por padrão.
435
- `clauderipple tray --install` baixa o Electron uma única vez; todo o resto funciona sem ele.
434
+ No macOS e no Windows o script de instalação o adiciona por padrão. Ele roda em Electron, cerca
435
+ de 270 MB baixados uma única vez; defina `CLAUDERIPPLE_NO_TRAY=1` antes do comando de instalação
436
+ para pular. No Linux, ou depois de instalar só pelo npm, `clauderipple tray --install` o adiciona.
437
+ Todo o resto funciona sem ele.
436
438
 
437
439
  **Atualização.** A bandeja oferece a nova versão quando houver uma (**Verificar atualizações…**), ou:
438
440
 
package/README.zh-CN.md CHANGED
@@ -257,13 +257,13 @@ clauderipple ui
257
257
 
258
258
  **供应商** → 添加 ChatGPT 或粘贴 API 密钥 → **模型映射**。
259
259
 
260
- **菜单栏 / 托盘应用**是可选的。它显示路由服务的状态,点一下就能打开控制面板:
260
+ **菜单栏 / 托盘应用**显示路由服务的状态,点一下就能打开控制面板,并带有路由服务停止时也能打开的修复界面:
261
261
 
262
262
  ```sh
263
263
  clauderipple tray
264
264
  ```
265
265
 
266
- 它基于 Electron,体积约 270MB,因此默认不安装。`clauderipple tray --install` 会下载一次;除此之外的一切不需要它也能正常工作。
266
+ 在 macOS 和 Windows 上,安装脚本默认会一并安装它。它基于 Electron,约 270MB,只下载一次;如不需要,在安装命令前加上 `CLAUDERIPPLE_NO_TRAY=1` 即可跳过。在 Linux 上或仅通过 npm 安装时,用 `clauderipple tray --install` 添加。除此之外的一切不需要它也能正常工作。
267
267
 
268
268
  **更新。** 有新版本时,托盘应用会提示(**检查更新…**),也可以运行:
269
269
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "private": true,
3
- "version": "0.12.4",
3
+ "version": "0.12.5",
4
4
  "type": "commonjs"
5
5
  }
@@ -255,6 +255,14 @@ async function install() {
255
255
  catch (e) {
256
256
  console.log(` menu-bar app launcher not written: ${e.message}`);
257
257
  }
258
+ // The phone host has the same problem as the router above: new files, old process. The host
259
+ // decides whether it is ours and idle (remote host-service refreshPlan). Never fatal.
260
+ try {
261
+ execFileSync(installedRuntime.node, [installedRuntime.cli, "remote", "refresh"], { env: { ...process.env, ...installedRuntime.env }, stdio: "inherit" });
262
+ }
263
+ catch {
264
+ console.log(" the phone host was not restarted; if you use it, run: clauderipple remote install");
265
+ }
258
266
  console.log("\nDone. New Claude Desktop Code sessions go through ClaudeRipple. Existing sessions pick up settings.json env changes on their next request.");
259
267
  }
260
268
  async function uninstall() {
@@ -25,9 +25,15 @@ export function taskName() {
25
25
  function ps(value) {
26
26
  return `'${value.replace(/'/g, "''")}'`;
27
27
  }
28
+ /**
29
+ * Windows PowerShell writes to a pipe in the console's code page, which we would read as UTF-8: on
30
+ * a Korean Windows an account named in Hangul came back garbled, so the registered task never
31
+ * matched and every install tried to register it again (measured 2026-10-13). The script sets
32
+ * UTF-8 first.
33
+ */
28
34
  function run(script) {
29
35
  try {
30
- const out = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
36
+ const out = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ${script}`], {
31
37
  env: windowsPowerShellEnv(),
32
38
  stdio: ["ignore", "pipe", "pipe"],
33
39
  windowsHide: true,
@@ -104,6 +104,32 @@ async function waitForHost(dir = remoteHome()) {
104
104
  }
105
105
  throw new Error("host service was registered but did not answer its authenticated status; check its logs");
106
106
  }
107
+ export function refreshPlan(o) {
108
+ if (!o.installed || o.definition === null)
109
+ return "absent";
110
+ const xml = o.script.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
111
+ if (![o.script, xml, o.script.replace(/'/g, "''")].some(form => o.definition.includes(form)))
112
+ return "foreign";
113
+ // An older host does not report it; its update came while the router sat idle, so most likely idle too.
114
+ return o.busy ? "busy" : "restart";
115
+ }
116
+ /** The running host's own answer; undefined when it does not answer or predates the field. */
117
+ export async function hostBusy(dir = remoteHome()) {
118
+ try {
119
+ const cfg = loadConfig(dir, false);
120
+ const secret = fs.readFileSync(path.join(dir, "local.secret"), "utf8").trim();
121
+ const response = await fetch(`http://127.0.0.1:${cfg.port}/local/status`, { headers: { "x-ripple-local": secret }, redirect: "error", signal: AbortSignal.timeout(2000) });
122
+ if (!response.ok) {
123
+ await response.body?.cancel();
124
+ return undefined;
125
+ }
126
+ const body = await response.json();
127
+ return typeof body.busy === "boolean" ? body.busy : undefined;
128
+ }
129
+ catch {
130
+ return undefined;
131
+ }
132
+ }
107
133
  export async function installService(spec) {
108
134
  const platform = process.platform;
109
135
  if (platform === "darwin")
@@ -66,6 +66,7 @@ export class LocalControl {
66
66
  reachableFromPhone: !!cfg.relay || this.where.reachableFromPhone, pushReady: !!cfg.relay,
67
67
  phones: this.opts.phones(), settings: { bind: saved.bind, ...(saved.relay ? { relay: saved.relay } : {}) }, defaultRelay: DEFAULT_RELAY,
68
68
  restartRequired: saved.bind !== cfg.bind || saved.relay !== cfg.relay,
69
+ busy: this.opts.busy?.() ?? false,
69
70
  });
70
71
  return;
71
72
  }
@@ -30,7 +30,7 @@ import { RouterClient } from "./router.js";
30
30
  import { HostServer } from "./server.js";
31
31
  import { LocalControl } from "./local-control.js";
32
32
  import { findClaude, specFor, cliLoginWarning } from "./service.js";
33
- import { installService, uninstallService, serviceStatus, servicePaths, renderService, supervisorName, controlShutdown } from "./host-service.js";
33
+ import { installService, uninstallService, serviceStatus, servicePaths, renderService, supervisorName, controlShutdown, refreshPlan, hostBusy } from "./host-service.js";
34
34
  import { Sessions } from "./sessions.js";
35
35
  import { desktopHandoff } from "./handoff.js";
36
36
  import { claudeBinary, claudeCodeLogin } from "../../cli/src/claude-auth.js";
@@ -179,6 +179,7 @@ async function runHost() {
179
179
  phones: () => server?.phones() ?? [],
180
180
  rotate: token => { server?.rotateToken(token); },
181
181
  shutdown: () => stop(),
182
+ busy: () => live.busy(),
182
183
  }),
183
184
  });
184
185
  server = host;
@@ -234,7 +235,7 @@ async function installCmd() {
234
235
  const cfg = process.argv.includes("--print") ? loadConfig(dir, false) : configFromArgs(dir);
235
236
  // The CLI the host will start; also what a person is told to run when it has no login.
236
237
  const claudePath = findClaude(cfg.claudePath);
237
- const spec = specFor({ script: fileURLToPath(import.meta.url), claudePath, ...(process.env.RIPPLE_REMOTE_HOME ? { remoteHome: process.env.RIPPLE_REMOTE_HOME } : {}) });
238
+ const spec = hostSpec(claudePath);
238
239
  if (process.argv.includes("--print")) {
239
240
  out(renderService(spec));
240
241
  return;
@@ -249,11 +250,38 @@ async function installCmd() {
249
250
  if (warning)
250
251
  out(`⚠ ${warning}\n`);
251
252
  }
253
+ function hostSpec(claudePath) {
254
+ return specFor({ script: fileURLToPath(import.meta.url), claudePath, ...(process.env.RIPPLE_REMOTE_HOME ? { remoteHome: process.env.RIPPLE_REMOTE_HOME } : {}) });
255
+ }
256
+ /** Run by `clauderipple update` from the new files: restarts a host this installation registered (refreshPlan). */
257
+ async function refreshCmd() {
258
+ const p = servicePaths();
259
+ let definition = null;
260
+ try {
261
+ definition = fs.readFileSync(p.definition, "utf8");
262
+ }
263
+ catch { /* not registered */ }
264
+ const plan = refreshPlan({ installed: serviceStatus().installed, definition, script: fileURLToPath(import.meta.url), busy: definition === null ? undefined : await hostBusy() });
265
+ if (plan === "absent")
266
+ return;
267
+ if (plan === "foreign") {
268
+ out(`phone host: runs from other files (${p.definition}); left as it is\n`);
269
+ return;
270
+ }
271
+ if (plan === "busy") {
272
+ out("phone host: a turn is in progress, so it keeps the old code; restart it afterwards with: clauderipple remote install\n");
273
+ return;
274
+ }
275
+ const spec = hostSpec(findClaude(loadConfig(remoteHome(), false).claudePath));
276
+ for (const line of await installService(spec))
277
+ out(`phone host: ${line}\n`);
278
+ }
252
279
  async function main() {
253
280
  const sub = process.argv[2] && !process.argv[2].startsWith("--") ? process.argv[2] : null;
254
281
  switch (sub) {
255
282
  case null: return runHost();
256
283
  case "install": return installCmd();
284
+ case "refresh": return refreshCmd();
257
285
  case "uninstall":
258
286
  for (const line of await uninstallService())
259
287
  out(`${line}\n`);
@@ -5,4 +5,4 @@
5
5
  // from the manifests. Until 0.1.1 the router and the CLI each carried their own literal, and both
6
6
  // still said "0.1.0" in the 0.1.1 release: after an update nobody could tell which router was
7
7
  // running, and the tray app had nothing to compare (2026-09-15, reported from a Windows install).
8
- export const VERSION = "0.12.4";
8
+ export const VERSION = "0.12.5";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clauderipple",
3
- "version": "0.12.4",
3
+ "version": "0.12.5",
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": [