clauderipple 0.3.0 → 0.3.1

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,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1 — 2026-09-24
4
+
5
+ Fixes for three reports against 0.3.0.
6
+
7
+ ### Fixed
8
+
9
+ - **Claude account rotation no longer breaks requests with a 400** (#15). With
10
+ `accountPool` on, the account's `anthropic-beta` header replaced the client's,
11
+ so fields Claude Code enables per request (`cache_control.scope`,
12
+ `context_management`, …) were refused with `Extra inputs are not permitted`.
13
+ A conversation pinned to an added account failed on every retry, because a
14
+ 400 does not move it. The account now supplies identity only: its OAuth flags
15
+ are added to the client's, on the first attempt and on every retry.
16
+ - **The Windows tray appears** (#8). It was started with `ELECTRON_RUN_AS_NODE`
17
+ set to an empty string, which macOS Electron treats as unset and Windows
18
+ Electron treats as set, so on Windows the tray ran as plain Node and exited
19
+ while the CLI reported "tray started". The variable is now removed.
20
+ - **GLM-5.3 and GLM-5.3 Flash in the Z.AI preset** (#14). Z.AI serves no model
21
+ list, so the preset's list is the whole picker, and it stopped at GLM-5.2.
22
+
3
23
  ## 0.3.0 — 2026-09-23
4
24
 
5
25
  Everything on `main` since 0.2.0. The account rotation that issue #8 asked for
@@ -24,6 +24,20 @@ export function electronPath() {
24
24
  return null;
25
25
  }
26
26
  }
27
+ /**
28
+ * Our environment without ELECTRON_RUN_AS_NODE, so Electron starts as the tray rather than as a
29
+ * plain Node process. The variable has to be removed, not emptied: macOS Electron reads an empty
30
+ * value as unset, but Windows Electron only asks whether it exists, so an empty one ran the tray
31
+ * as Node — it exited at once and no icon ever appeared (measured 2026-09-24, Electron 38,
32
+ * Windows 11; issue #8).
33
+ */
34
+ export function trayEnv(env = process.env) {
35
+ const out = { ...env };
36
+ for (const name of Object.keys(out))
37
+ if (name.toUpperCase() === "ELECTRON_RUN_AS_NODE")
38
+ delete out[name];
39
+ return out;
40
+ }
27
41
  /**
28
42
  * Starts the tray and returns immediately. `detached` outlives this process, which is what a
29
43
  * command typed in a terminal wants; the supervisor uses the foreground form instead.
@@ -32,7 +46,8 @@ export function startTray(options = {}) {
32
46
  const current = runtime();
33
47
  if (current.packaged) {
34
48
  // The packaged app IS the tray: relaunching its own binary with no script is what opens it.
35
- const child = spawn(current.node, [], { detached: true, stdio: "ignore" });
49
+ // Our own CLI runs under ELECTRON_RUN_AS_NODE here, and inheriting it would relaunch Node.
50
+ const child = spawn(current.node, [], { detached: true, stdio: "ignore", env: trayEnv() });
36
51
  child.unref();
37
52
  return { ok: true, message: "✓ tray started" };
38
53
  }
@@ -51,7 +66,7 @@ export function startTray(options = {}) {
51
66
  detached: options.detached ?? true,
52
67
  stdio: "ignore",
53
68
  // Electron would otherwise re-enter as a plain Node process, since that is how the CLI runs.
54
- env: { ...process.env, ELECTRON_RUN_AS_NODE: "" },
69
+ env: trayEnv(),
55
70
  });
56
71
  if (options.detached ?? true)
57
72
  child.unref();
@@ -55,7 +55,13 @@ export const PRESETS = [
55
55
  vendorUrl: "https://z.ai/",
56
56
  anthropicBaseUrl: "https://api.z.ai/api/anthropic",
57
57
  authHeader: "authorization-bearer",
58
- fallbackModels: [{ id: "glm-5.2", name: "GLM-5.2" }],
58
+ // No model list is served, so this list is the picker. glm-5.3-flash is named in
59
+ // https://docs.z.ai/guides/vlm/glm-5.3-flash; its FlashX sibling is not on the Coding Plan yet (issue #14).
60
+ fallbackModels: [
61
+ { id: "glm-5.3", name: "GLM-5.3" },
62
+ { id: "glm-5.3-flash", name: "GLM-5.3 Flash" },
63
+ { id: "glm-5.2", name: "GLM-5.2" },
64
+ ],
59
65
  // https://docs.z.ai/devpack/tool/others documents the endpoint but not an Anthropic effort/thinking contract.
60
66
  effortLevels: [],
61
67
  thinking: "none",
@@ -97,6 +97,22 @@ export function withCredential(headers, credential, previous = {}) {
97
97
  out.push(k, v);
98
98
  return out;
99
99
  }
100
+ /**
101
+ * A Claude account's headers for one turn, with the client's beta flags kept. The account supplies
102
+ * identity; the flags belong to the request, because they switch on body fields the client sent
103
+ * (`cache_control.scope`, `context_management`, …) and the API refuses those fields without their
104
+ * flag — `Extra inputs are not permitted` (issue #15). The account's own OAuth flags are added to
105
+ * the client's, never used in their place. Only for native Claude accounts: a compatible vendor's
106
+ * beta filtering happens elsewhere and must not be undone here.
107
+ */
108
+ export function withClientBetas(credential, clientBeta) {
109
+ if (clientBeta === undefined)
110
+ return credential;
111
+ const name = Object.keys(credential).find((key) => key.toLowerCase() === "anthropic-beta") ?? "anthropic-beta";
112
+ const flags = (raw) => raw.split(",").map((flag) => flag.trim()).filter(Boolean);
113
+ const merged = [...new Set([...flags([].concat(clientBeta).join(",")), ...flags(credential[name] ?? "")])];
114
+ return { ...credential, [name]: merged.join(",") };
115
+ }
100
116
  /**
101
117
  * A body decoded for reading, as text. Bytes that cannot be decoded are read as they arrived.
102
118
  *
@@ -706,7 +722,7 @@ export class Proxy {
706
722
  host: cfg.upstream,
707
723
  port: this.deps.upstreamPort ?? 443,
708
724
  agent: this.deps.upstreamAgent ?? this.agentFor(route.provider, "https:"),
709
- extraHeaders: using.headers,
725
+ extraHeaders: withClientBetas(using.headers, req.headers["anthropic-beta"]),
710
726
  dropClientAuth: true,
711
727
  dropHeaders: new Set(credentials.flatMap((credential) => Object.keys(credential.headers).map((name) => name.toLowerCase()))),
712
728
  dropHeaderPrefixes: ["anthropic-client-", "x-stainless-"],
@@ -871,13 +887,13 @@ export class Proxy {
871
887
  // The model's own shape again: its credential pool is the provider's, but an auth convention
872
888
  // this model overrides has to be the one the retry actually sends.
873
889
  const provider = providerFor(owner, route.model);
874
- const available = provider.type === "anthropic" && provider.accountPool
875
- ? this.claudeAccounts.peekCredentials()
876
- : this.credentialsOf(route.provider, provider);
890
+ const native = provider.type === "anthropic" && provider.accountPool;
891
+ const available = native ? this.claudeAccounts.peekCredentials() : this.credentialsOf(route.provider, provider);
877
892
  const rest = available.filter((c) => !tried.has(c.id));
878
893
  if (rest.length === 0)
879
894
  return null;
880
- return this.pool.pick(route.provider, rest, conversationKey(json));
895
+ const next = this.pool.pick(route.provider, rest, conversationKey(json));
896
+ return next && native ? { ...next, headers: withClientBetas(next.headers, req.headers["anthropic-beta"]) } : next;
881
897
  };
882
898
  // Destroying the upstream makes it emit ECONNRESET (measured, Node 24.15), which is
883
899
  // indistinguishable from the provider dropping us unless we remember that we did it. Without
@@ -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.3.0";
8
+ export const VERSION = "0.3.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clauderipple",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
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
5
  "license": "GPL-3.0-only",
6
6
  "workspaces": [