claude-bridge-cli 1.4.0 → 1.5.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/bin/cli.js CHANGED
@@ -41,9 +41,10 @@ function relayHttpBase(wsUrl) {
41
41
  return `${proto}//${u.host}`;
42
42
  }
43
43
 
44
- async function pairInteractive(relayWsUrl, machine) {
44
+ async function pairInteractive(relayWsUrl, machine, ephemeral) {
45
45
  const base = relayHttpBase(relayWsUrl);
46
46
  process.stdout.write(`[bridge] Starting pair flow for "${machine}" against ${base}\n`);
47
+ if (ephemeral) process.stdout.write(`[bridge] Ephemeral mode — bearer will NOT be saved to disk.\n`);
47
48
 
48
49
  const startResp = await fetch(`${base}/pair-cli/start`, {
49
50
  method: "POST",
@@ -87,13 +88,16 @@ async function pairInteractive(relayWsUrl, machine) {
87
88
  throw new Error(`pair claim failed (${resp.status}): ${text}`);
88
89
  }
89
90
  const { bearer, machine: confirmed } = await resp.json();
90
- console.log(`[bridge] ✓ Paired as "${confirmed}". Bearer saved to ${authFilePath()}`);
91
-
92
- const auth = readAuthFile();
93
- auth.bearers = auth.bearers || {};
94
- auth.bearers[base] = auth.bearers[base] || {};
95
- auth.bearers[base][confirmed] = bearer;
96
- writeAuthFile(auth);
91
+ if (ephemeral) {
92
+ console.log(`[bridge] ✓ Paired as "${confirmed}" (in-memory only, nothing written to disk).`);
93
+ } else {
94
+ console.log(`[bridge] Paired as "${confirmed}". Bearer saved to ${authFilePath()}`);
95
+ const auth = readAuthFile();
96
+ auth.bearers = auth.bearers || {};
97
+ auth.bearers[base] = auth.bearers[base] || {};
98
+ auth.bearers[base][confirmed] = bearer;
99
+ writeAuthFile(auth);
100
+ }
97
101
  return bearer;
98
102
  }
99
103
  throw new Error("pair window expired — re-run to start over");
@@ -127,6 +131,9 @@ Relay options (connect to a remote relay server):
127
131
  --token <bearer> Machine bearer (optional — if omitted, the CLI starts
128
132
  a browser-OTP pair flow and caches the bearer at
129
133
  ~/.claude-bridge/auth.json for next time)
134
+ --ephemeral Pair on every start, never read/write auth.json.
135
+ Forces a fresh browser-OTP each launch — use on
136
+ machines you don't want to leave any saved bearer on.
130
137
  --cf-id <id> Cloudflare Access Client ID (optional)
131
138
  --cf-secret <secret> Cloudflare Access Client Secret (optional)
132
139
 
@@ -134,6 +141,7 @@ Environment variables:
134
141
  All options can be set via env vars with BRIDGE_ prefix:
135
142
  BRIDGE_PORT, BRIDGE_HOST, BRIDGE_CWD, BRIDGE_TIMEOUT,
136
143
  BRIDGE_RELAY_URL, BRIDGE_MACHINE_NAME, BRIDGE_MACHINE_TOKEN,
144
+ BRIDGE_EPHEMERAL=1 (force fresh pair every start, no saved bearer),
137
145
  BRIDGE_CF_ID, BRIDGE_CF_SECRET
138
146
  `;
139
147
 
@@ -161,6 +169,7 @@ function main() {
161
169
  "relay-url": { type: "string", default: env("RELAY_URL", "") },
162
170
  machine: { type: "string", default: env("MACHINE_NAME", "") },
163
171
  token: { type: "string", default: env("MACHINE_TOKEN", "") },
172
+ ephemeral: { type: "boolean", default: env("EPHEMERAL", "") === "1" },
164
173
  "cf-id": { type: "string", default: env("CF_ID", "") },
165
174
  "cf-secret": { type: "string", default: env("CF_SECRET", "") },
166
175
  },
@@ -177,6 +186,7 @@ function main() {
177
186
  url: values["relay-url"],
178
187
  machine: values.machine,
179
188
  token: values.token,
189
+ ephemeral: !!values.ephemeral,
180
190
  cfId: values["cf-id"],
181
191
  cfSecret: values["cf-secret"],
182
192
  } : null,
@@ -238,15 +248,22 @@ async function run(config) {
238
248
  console.error("[bridge] ERROR: --machine required when using --relay-url");
239
249
  process.exit(1);
240
250
  }
241
- // Resolve a bearer: --token > saved auth.json > interactive pair flow.
251
+ // Resolve a bearer:
252
+ // --token → use it directly (never look at auth.json)
253
+ // --ephemeral → always pair fresh, never read or write auth.json
254
+ // default → saved auth.json → fall back to pair flow on miss
242
255
  if (!config.relay.token) {
243
- const saved = lookupSavedBearer(config.relay.url, config.relay.machine);
256
+ const saved = config.relay.ephemeral
257
+ ? null
258
+ : lookupSavedBearer(config.relay.url, config.relay.machine);
244
259
  if (saved) {
245
260
  config.relay.token = saved;
246
261
  console.log(`[bridge] Using saved bearer from ${authFilePath()}`);
247
262
  } else {
248
263
  try {
249
- config.relay.token = await pairInteractive(config.relay.url, config.relay.machine);
264
+ config.relay.token = await pairInteractive(
265
+ config.relay.url, config.relay.machine, config.relay.ephemeral
266
+ );
250
267
  } catch (e) {
251
268
  console.error(`[bridge] ERROR: ${e.message}`);
252
269
  process.exit(1);
package/lib/bridge.js CHANGED
@@ -418,11 +418,18 @@ function askClaude(config, { prompt, session_id, images, cwd, plan_mode, allow_t
418
418
  const isWin = process.platform === "win32";
419
419
  const isCmdShim = isWin && config.claudeBin.endsWith(".cmd");
420
420
 
421
- // Only fall back to @file syntax for prompts that would exceed Windows
422
- // cmd.exe's ~8K arg limit. Direct -p means the JSONL stores the actual
423
- // prompt text (so session history shows the real user messages, not
424
- // temp-file paths). Threshold is conservative: 6000 chars.
425
- const useTempFile = finalPrompt.length > 6000;
421
+ // Fall back to @file syntax in two cases:
422
+ // 1) Prompt exceeds Windows cmd.exe's ~8K arg limit (threshold 6000).
423
+ // 2) Prompt contains a newline AND we're on Windows with a .cmd shim
424
+ // cmd.exe parses arg strings line-by-line and silently truncates
425
+ // at the first \n, even with Node.js's proper argv quoting. Without
426
+ // this fallback, any multi-line prompt to Claude on Windows lost
427
+ // everything after line 1, and the model saw a stub message + the
428
+ // header but none of the body. Manifested as "your message got
429
+ // cut off" responses for prompts that LOOK short.
430
+ const useTempFile =
431
+ finalPrompt.length > 6000 ||
432
+ (isWin && isCmdShim && /\r|\n/.test(finalPrompt));
426
433
  let tmpFile = null;
427
434
  if (useTempFile) {
428
435
  tmpFile = path.join(os.tmpdir(), `claude-prompt-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`);
@@ -46,6 +46,25 @@ function startRelay(config) {
46
46
  });
47
47
 
48
48
  ws.on("close", (code, reason) => {
49
+ // 4003 = bearer rejected by the relay (verify_machine_token failed).
50
+ // The bearer has been rotated or admin-removed; the cached one in
51
+ // auth.json is useless. Stop the reconnect loop and tell the user
52
+ // exactly how to recover.
53
+ if (code === 4003) {
54
+ const reasonStr = reason && reason.toString ? reason.toString() : "unauthorized";
55
+ console.error("");
56
+ console.error(`[relay] Authorization rejected (${code} ${reasonStr}).`);
57
+ console.error("[relay] The saved bearer no longer matches what the relay expects — it was probably rotated or revoked.");
58
+ console.error("[relay] Recover by deleting the cached auth and re-pairing:");
59
+ if (process.platform === "win32") {
60
+ console.error(" del %USERPROFILE%\\.claude-bridge\\auth.json");
61
+ } else {
62
+ console.error(" rm ~/.claude-bridge/auth.json");
63
+ }
64
+ console.error(` claude-bridge-cli start --relay-url ${config.relay.url} --machine ${config.relay.machine}`);
65
+ console.error("");
66
+ process.exit(2);
67
+ }
49
68
  console.log(`[relay] Disconnected (${code}). Reconnecting in ${backoff / 1000}s...`);
50
69
  setTimeout(connect, backoff);
51
70
  backoff = Math.min(60000, backoff * 2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-bridge-cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Use Claude Code from your browser. Runs a local server that connects your browser tools to the Claude CLI.",
5
5
  "main": "lib/bridge.js",
6
6
  "bin": {