@chatpanel/bridge 0.10.23 → 0.10.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/bridge",
3
- "version": "0.10.23",
3
+ "version": "0.10.24",
4
4
  "type": "module",
5
5
  "description": "Local bridge that exposes the AI coding agents installed on your machine \u2014 Claude Code (CLI), Codex (CLI), and Antigravity CLI (formerly Gemini CLI, which remains available for business/enterprise) \u2014 to the ChatPanel Chrome extension over a localhost SSE endpoint. Bring your own agent.",
6
6
  "keywords": [
package/src/env.js CHANGED
@@ -11,6 +11,7 @@ import { spawnSync } from 'node:child_process';
11
11
  import { readdirSync, readFileSync, existsSync } from 'node:fs';
12
12
 
13
13
  let enriched = false;
14
+ let envEnriched = false;
14
15
 
15
16
  // The agent CLIs the bridge shells out to. Claude has its own richer resolution
16
17
  // (resolveClaude: native / cli.js / WSL / SDK) below.
@@ -421,6 +422,64 @@ export function commandCandidateFiles(name, home = os.homedir(), platform = proc
421
422
  return unique([...fromDirs, ...agentCandidateBins(name, home, platform, env)]);
422
423
  }
423
424
 
425
+ // Agent credentials a LaunchAgent/systemd unit does NOT inherit.
426
+ //
427
+ // enrichPath() repairs the minimal PATH a service gets; this is the same problem
428
+ // one layer up. A CLI that authenticates from a FILE (claude, codex, copilot:
429
+ // ~/.copilot) works fine under the service, but one that reads an API key from
430
+ // the ENVIRONMENT (dsh -> DEEPSEEK_API_KEY) fails with a missing-credential
431
+ // error that looks like a ChatPanel bug — the launchd job's environment is just
432
+ // SSH_AUTH_SOCK and a bare PATH.
433
+ //
434
+ // So: ask the user's login shell for a NARROW allowlist of agent credential
435
+ // variables and fill in only the ones we don't already have. These are the
436
+ // user's own credentials, on their own machine, handed to CLIs the user
437
+ // configured ChatPanel to launch — exactly what would happen had they run the
438
+ // CLI from their terminal. Values are never logged, and /debug never dumps the
439
+ // environment (it exposes only PATH/home, and only under CHATPANEL_BRIDGE_DEBUG).
440
+ const AGENT_ENV_KEYS = [
441
+ 'DEEPSEEK_API_KEY', 'DSH_HOME',
442
+ 'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL',
443
+ 'OPENAI_API_KEY', 'OPENAI_BASE_URL', 'OPENAI_API_BASE',
444
+ 'GEMINI_API_KEY', 'GOOGLE_API_KEY',
445
+ 'OPENROUTER_API_KEY', 'GROQ_API_KEY', 'XAI_API_KEY', 'MISTRAL_API_KEY',
446
+ ];
447
+
448
+ export function enrichAgentEnv() {
449
+ if (envEnriched) return;
450
+ envEnriched = true;
451
+ if (process.platform === 'win32') return; // service env is inherited there
452
+
453
+ const missing = AGENT_ENV_KEYS.filter((k) => !process.env[k]);
454
+ if (!missing.length) return;
455
+ try {
456
+ const shell = process.env.SHELL || '/bin/zsh';
457
+ // Emit NAME\tVALUE for each var that is actually set; trailing `true` keeps
458
+ // the exit status clean when the last test fails.
459
+ const script = `${missing
460
+ .map((k) => `[ -n "\${${k}:-}" ] && printf '%s\\t%s\\n' ${k} "\$${k}"`)
461
+ .join('; ')}; true`;
462
+ // -lic, not -lc: these keys are typically exported from ~/.zshrc (or ~/.bashrc),
463
+ // which a LOGIN-only shell does NOT source — that's interactive-only. TERM=dumb
464
+ // keeps prompt/banner noise down, and the parser below ignores any line that
465
+ // isn't a clean NAME<TAB>VALUE, so rc-file chatter is harmless.
466
+ const r = spawnSync(shell, ['-lic', script], {
467
+ encoding: 'utf8',
468
+ timeout: 6000,
469
+ env: { ...process.env, TERM: 'dumb' },
470
+ });
471
+ for (const line of String(r.stdout || '').split('\n')) {
472
+ const i = line.indexOf('\t');
473
+ if (i <= 0) continue;
474
+ const key = line.slice(0, i).trim();
475
+ const value = line.slice(i + 1);
476
+ if (AGENT_ENV_KEYS.includes(key) && !process.env[key] && value) process.env[key] = value;
477
+ }
478
+ } catch {
479
+ /* no login shell / timeout — agents that need a file-based login still work */
480
+ }
481
+ }
482
+
424
483
  export function enrichPath() {
425
484
  if (enriched) return;
426
485
  enriched = true;
package/src/server.js CHANGED
@@ -29,7 +29,7 @@ import * as antigravity from './engines/antigravity.js';
29
29
  import { pi, opencode, kiro, copilot, deepseek } from './engines/cli-agents.js';
30
30
  import * as custom from './engines/custom.js';
31
31
  import { installService, uninstallService, serviceStatus, restartService } from './service.js';
32
- import { AGENT_CLIS, enrichPath, findAgentBin, resolveCommand } from './env.js';
32
+ import { AGENT_CLIS, enrichPath, enrichAgentEnv, findAgentBin, resolveCommand } from './env.js';
33
33
  import { stripHidden } from './sanitize.js';
34
34
  import { checkForUpdate, selfUpdate } from './update.js';
35
35
  import { callLocalMcp } from './mcp-local.js';
@@ -38,7 +38,7 @@ import { assertPublicHttpUrl, assertPublicWebUrl } from './ssrf.js';
38
38
  // Hardcoded (not read from package.json) so it survives Bun's single-file
39
39
  // --compile, where package.json isn't on a readable FS. CI fails the publish if
40
40
  // this drifts from package.json, so the two can't silently diverge.
41
- const VERSION = '0.10.23';
41
+ const VERSION = '0.10.24';
42
42
  const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
43
43
  const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
44
44
 
@@ -828,6 +828,7 @@ function runMcpStdioProxy(url) {
828
828
 
829
829
  function startServer() {
830
830
  enrichPath(); // so codex/agy (Antigravity) are found even under a minimal service PATH
831
+ enrichAgentEnv(); // and so env-authenticated CLIs (dsh) have their key under launchd
831
832
  ensureToken(); // per-install bearer token for privileged routes (defense-in-depth)
832
833
  // Fail LOUD on a port clash. The bridge binds a FIXED 4319 so the extension always
833
834
  // finds it; if it's taken, say how to recover instead of dying on a raw stack trace.