@chatpanel/bridge 0.2.14 → 0.2.15

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.2.14",
3
+ "version": "0.2.15",
4
4
  "type": "module",
5
5
  "description": "Local bridge that exposes the AI coding agents installed on your machine — Claude Code (CLI), Codex (CLI), and Gemini CLI — to the ChatPanel Chrome extension over a localhost SSE endpoint. Bring your own agent.",
6
6
  "keywords": [
@@ -88,9 +88,14 @@ function buildSpawn(spec, args, cwd) {
88
88
  // Run cli.js with the interpreter already running the bridge (node/bun).
89
89
  return [process.execPath, [spec.script, ...args], opts];
90
90
  }
91
- // kind === 'native' — direct executable (.exe / mac+linux binary), or a .cmd
92
- // shim via the shell on Windows.
93
- return [spec.bin, args, { ...opts, shell: !!spec.shell }];
91
+ if (spec.kind === 'cmd') {
92
+ // Launch the .cmd/.bat shim via cmd.exe with a real argv (shell:false). Node
93
+ // applies cmd.exe-aware quoting here, so args are passed safely unlike
94
+ // spawn(..., { shell: true }), which concatenates (DEP0190).
95
+ return ['cmd.exe', ['/d', '/s', '/c', spec.bin, ...args], opts];
96
+ }
97
+ // kind === 'native' — a directly executable file (mac/linux binary or .exe).
98
+ return [spec.bin, args, opts];
94
99
  }
95
100
 
96
101
  // Spawn claude (however it resolves) and stream its stream-json output via
package/src/env.js CHANGED
@@ -8,7 +8,7 @@
8
8
  import os from 'node:os';
9
9
  import path from 'node:path';
10
10
  import { spawnSync } from 'node:child_process';
11
- import { readdirSync, existsSync } from 'node:fs';
11
+ import { readdirSync, readFileSync, existsSync } from 'node:fs';
12
12
 
13
13
  let enriched = false;
14
14
 
@@ -81,16 +81,22 @@ function shellWhich(name) {
81
81
  // native binary the old code spawned, so behavior there is unchanged.
82
82
  //
83
83
  // Returns one of:
84
- // { kind: 'native', bin, shell } → spawn(bin, args, { shell })
84
+ // { kind: 'native', bin } → spawn(bin, args) (directly exec'able)
85
85
  // { kind: 'script', script } → spawn(process.execPath, [script, ...args])
86
+ // { kind: 'cmd', bin } → spawn('cmd.exe', ['/c', bin, ...args])
86
87
  // { kind: 'wsl' } → spawn('wsl.exe', [wsl prefix, ...args])
87
88
  // null → not found (caller may fall back to SDK)
89
+ //
90
+ // We never use spawn's `shell: true` — with an args array it concatenates rather
91
+ // than escapes (Node DEP0190 / a real injection surface). The 'script' and 'cmd'
92
+ // kinds run the shim safely with a proper argv instead.
88
93
  export function resolveClaude() {
89
94
  const override = process.env.CHATPANEL_CLAUDE_PATH;
90
95
  if (override) {
91
96
  const ext = path.extname(override).toLowerCase();
92
- if (!isCompiledBinary() && /\.(c?js|mjs)$/.test(ext)) return { kind: 'script', script: override };
93
- return { kind: 'native', bin: override, shell: process.platform === 'win32' && (ext === '.cmd' || ext === '.bat') };
97
+ if (!isCompiledBinary() && /^\.(c?js|mjs)$/.test(ext)) return { kind: 'script', script: override };
98
+ if (process.platform === 'win32' && (ext === '.cmd' || ext === '.bat')) return { kind: 'cmd', bin: override };
99
+ return { kind: 'native', bin: override };
94
100
  }
95
101
 
96
102
  if (process.platform === 'win32') {
@@ -102,12 +108,12 @@ export function resolveClaude() {
102
108
 
103
109
  // macOS / Linux / WSL-native: same resolution the engine used before.
104
110
  const bin = findAgentBin('claude');
105
- return bin ? { kind: 'native', bin, shell: false } : null;
111
+ return bin ? { kind: 'native', bin } : null;
106
112
  }
107
113
 
108
114
  // Locate a runnable Claude Code on Windows. Prefer the package's cli.js (run
109
115
  // with our own Node/Bun — clean arg passing, no cmd.exe quoting), then a real
110
- // .exe, then a .cmd shim via the shell as a last resort.
116
+ // .exe, then a .cmd/.bat shim launched safely via cmd.exe.
111
117
  function findClaudeWindows() {
112
118
  const dirs = (process.env.PATH || '').split(path.delimiter);
113
119
  for (const d of dirs) {
@@ -119,17 +125,18 @@ function findClaudeWindows() {
119
125
  // Running cli.js with our own interpreter only works under a real Node/Bun,
120
126
  // not inside a compiled single-file binary (which is not a JS interpreter).
121
127
  if (!isCompiledBinary()) {
122
- const js = claudeCliJs(d);
128
+ const js = claudeCliJs(d) || shimTarget(d);
123
129
  if (js) return { kind: 'script', script: js };
124
130
  }
125
- if (existsSync(path.join(d, 'claude.exe'))) return { kind: 'native', bin: path.join(d, 'claude.exe'), shell: false };
126
- if (existsSync(path.join(d, 'claude.cmd'))) return { kind: 'native', bin: path.join(d, 'claude.cmd'), shell: true };
127
- if (existsSync(path.join(d, 'claude.bat'))) return { kind: 'native', bin: path.join(d, 'claude.bat'), shell: true };
131
+ if (existsSync(path.join(d, 'claude.exe'))) return { kind: 'native', bin: path.join(d, 'claude.exe') };
132
+ if (existsSync(path.join(d, 'claude.cmd'))) return { kind: 'cmd', bin: path.join(d, 'claude.cmd') };
133
+ if (existsSync(path.join(d, 'claude.bat'))) return { kind: 'cmd', bin: path.join(d, 'claude.bat') };
128
134
  }
129
135
  return null;
130
136
  }
131
137
 
132
- // The npm shim sits next to (or one level up from) the claude-code package.
138
+ // The npm shim usually sits next to (or one level up from) the claude-code
139
+ // package — quick static guesses before parsing the shim itself.
133
140
  function claudeCliJs(dir) {
134
141
  const rels = [
135
142
  ['node_modules', '@anthropic-ai', 'claude-code', 'cli.js'],
@@ -143,6 +150,27 @@ function claudeCliJs(dir) {
143
150
  return null;
144
151
  }
145
152
 
153
+ // Robust fallback: every npm/pnpm/yarn/volta shim literally names the JS entry it
154
+ // runs, relative to the shim dir (`%dp0%\…\cli.js` in .cmd, `$basedir/…/cli.js`
155
+ // in the sh/.ps1 shims). Extract that so any install layout resolves to a real
156
+ // cli.js we can run with our own interpreter.
157
+ function shimTarget(dir) {
158
+ for (const shim of ['claude.cmd', 'claude', 'claude.ps1']) {
159
+ let txt;
160
+ try {
161
+ txt = readFileSync(path.join(dir, shim), 'utf8');
162
+ } catch {
163
+ continue;
164
+ }
165
+ const m = txt.match(/(?:%~?dp0%|\$basedir|\$\{basedir\})[\\/]+([^"'\s]+\.[cm]?js)/i);
166
+ if (m) {
167
+ const abs = path.join(dir, m[1].replace(/[\\/]+/g, path.sep));
168
+ if (existsSync(abs)) return abs;
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+
146
174
  // Is `claude` reachable inside the default WSL distro's login shell? Cached;
147
175
  // re-probed (throttled) while not found so it self-heals once WSL/claude appear.
148
176
  let wslClaude = null;