@chatpanel/bridge 0.10.5 → 0.10.7

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.5",
3
+ "version": "0.10.7",
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": [
@@ -34,16 +34,21 @@ url="https://dl.chatpanel.net/${asset}"
34
34
  dest="${HOME}/.local/bin"
35
35
  bin="${dest}/chatpanel-bridge"
36
36
  mkdir -p "$dest"
37
+ tmp="$(mktemp "${dest}/.chatpanel-bridge.XXXXXX")"
38
+ trap 'rm -f "$tmp"' EXIT
37
39
 
38
40
  echo "Downloading ChatPanel Bridge (~60 MB)..."
39
- curl -fL --progress-bar "$url" -o "$bin" # show a progress bar (not silent)
40
- chmod +x "$bin"
41
- xattr -c "$bin" 2>/dev/null || true # belt-and-suspenders; curl files aren't quarantined
41
+ curl -fL --progress-bar "$url" -o "$tmp" # show a progress bar (not silent)
42
+ chmod +x "$tmp"
43
+ xattr -c "$tmp" 2>/dev/null || true # belt-and-suspenders; curl files aren't quarantined
42
44
 
43
45
  # Clean upgrade: stop any running bridge (incl. a stray npx one) so the new
44
46
  # install replaces it in place — same path, same service, no duplicates.
45
47
  pkill -f 'chatpanel-bridge' 2>/dev/null || true
46
48
  sleep 1
49
+ rm -f "$bin"
50
+ mv "$tmp" "$bin"
51
+ trap - EXIT
47
52
 
48
53
  echo "Installed to ${bin}"
49
54
  "$bin" --install
@@ -84,7 +84,9 @@ export const kiro = makeCliAgent(
84
84
  modelArg: '--model {model}',
85
85
  trustToolsArg: '--trust-tools={tools}',
86
86
  requiresStableMcp: true,
87
+ autoSetupStableMcp: true,
87
88
  stableMcpConfigCheck: 'kiro',
89
+ stableMcpSetupArgs: ['mcp', 'add', '--scope', 'global', '--name', 'chatpanel_browser', '--url', 'http://127.0.0.1:4319/mcp', '--force'],
88
90
  stableMcpSetupCommand: 'kiro-cli mcp add --scope global --name chatpanel_browser --url http://127.0.0.1:4319/mcp --force',
89
91
  listModelsArgs: '--list-models',
90
92
  label: 'Kiro',
@@ -155,6 +155,12 @@ export function stableMcpSetupCommand(spec = {}) {
155
155
  return `opencode mcp add chatpanel --url ${CHATPANEL_STABLE_MCP_URL}`;
156
156
  }
157
157
 
158
+ export function stableMcpSetupPlan(spec = {}) {
159
+ const args = Array.isArray(spec.stableMcpSetupArgs) ? spec.stableMcpSetupArgs.filter((a) => a != null).map(String) : null;
160
+ if (!args?.length) return null;
161
+ return { command: spec.stableMcpSetupCommandName || spec.command, args };
162
+ }
163
+
158
164
  export function buildPiExtensionSource(mcp) {
159
165
  const specs = mcpToolSpecs(mcp);
160
166
  const declarations = specs.map((spec, index) => {
@@ -256,25 +262,27 @@ async function opencodeHasStableMcpConfig() {
256
262
  return false;
257
263
  }
258
264
 
259
- async function commandStdout(command, args, cwd) {
265
+ export async function commandOutput(command, args, cwd) {
260
266
  const resolved = resolveCommand(command);
261
267
  if (!resolved) return '';
262
268
  const [bin, argv, opts] = buildSpawnSpec(resolved, args, cwd || null);
263
269
  return new Promise((resolve) => {
264
270
  const child = spawn(bin, argv, opts);
265
271
  let out = '';
272
+ let err = '';
266
273
  const timer = setTimeout(() => {
267
274
  child.kill('SIGKILL');
268
- resolve(out);
275
+ resolve(out + err);
269
276
  }, 4000);
270
277
  child.stdout.on('data', (d) => (out += d.toString()));
278
+ child.stderr.on('data', (d) => (err += d.toString()));
271
279
  child.on('error', () => {
272
280
  clearTimeout(timer);
273
- resolve('');
281
+ resolve(out + err);
274
282
  });
275
283
  child.on('close', () => {
276
284
  clearTimeout(timer);
277
- resolve(out);
285
+ resolve(out + err);
278
286
  });
279
287
  try { child.stdin.end(); } catch { /* ignore */ }
280
288
  });
@@ -282,7 +290,7 @@ async function commandStdout(command, args, cwd) {
282
290
 
283
291
  async function kiroHasStableMcpConfig(command, cwd) {
284
292
  for (const scope of ['workspace', 'global', 'default']) {
285
- const out = await commandStdout(command, ['mcp', 'list', scope], cwd);
293
+ const out = await commandOutput(command, ['mcp', 'list', scope], cwd);
286
294
  if (out.includes(CHATPANEL_STABLE_MCP_URL) || /chatpanel_browser/i.test(out)) return true;
287
295
  }
288
296
  return false;
@@ -294,6 +302,59 @@ async function hasStableMcpConfig(spec, cwd) {
294
302
  return false;
295
303
  }
296
304
 
305
+ async function runStableMcpSetup(plan, cwd) {
306
+ const resolved = resolveCommand(plan.command);
307
+ if (!resolved) throw new Error(`Couldn't find "${plan.command}" to set up browser tools.`);
308
+ const [bin, argv, opts] = buildSpawnSpec(resolved, plan.args, cwd || null);
309
+ opts.env = { ...(opts.env || process.env), NO_COLOR: '1', FORCE_COLOR: '0', CLICOLOR: '0', TERM: 'dumb' };
310
+ await new Promise((resolve, reject) => {
311
+ let child;
312
+ try { child = spawn(bin, argv, opts); } catch (e) { return reject(new Error(`Failed to start ${plan.command}: ${e.message}`)); }
313
+ let stderr = '';
314
+ const timer = setTimeout(() => {
315
+ child.kill('SIGKILL');
316
+ reject(new Error(`${plan.command} MCP setup timed out.`));
317
+ }, 20_000);
318
+ child.stderr.on('data', (d) => (stderr += d.toString()));
319
+ child.on('error', (e) => {
320
+ clearTimeout(timer);
321
+ reject(new Error(`Failed to start ${plan.command}: ${e.message}`));
322
+ });
323
+ child.on('close', (code) => {
324
+ clearTimeout(timer);
325
+ if (code === 0) return resolve();
326
+ reject(new Error(`${plan.command} MCP setup exited ${code}: ${stderr.trim().split('\n').pop() || 'failed'}`));
327
+ });
328
+ try { child.stdin.end(); } catch { /* ignore */ }
329
+ });
330
+ }
331
+
332
+ export async function ensureStableMcpConfig(spec, cwd, label, emit, deps = {}) {
333
+ if (!spec.requiresStableMcp) return true;
334
+ const hasConfig = deps.hasConfig || hasStableMcpConfig;
335
+ const runSetup = deps.runSetup || runStableMcpSetup;
336
+ if (await hasConfig(spec, cwd)) return true;
337
+
338
+ const setupCommand = stableMcpSetupCommand(spec);
339
+ if (!spec.autoSetupStableMcp) {
340
+ const text = `${label} needs one-time browser-tool setup: ${setupCommand}`;
341
+ emit({ type: 'status', text });
342
+ throw new Error(text);
343
+ }
344
+
345
+ const plan = stableMcpSetupPlan(spec);
346
+ if (!plan?.command) {
347
+ const text = `${label} needs one-time browser-tool setup: ${setupCommand}`;
348
+ emit({ type: 'status', text });
349
+ throw new Error(text);
350
+ }
351
+
352
+ emit({ type: 'status', text: `${label} is setting up one-time browser tools...` });
353
+ await runSetup(plan, cwd);
354
+ if (await hasConfig(spec, cwd)) return true;
355
+ throw new Error(`${label} browser-tool setup completed, but the MCP server is still not visible. Run: ${setupCommand}`);
356
+ }
357
+
297
358
  export async function chat({ messages, system, options, images }, emit) {
298
359
  // Pro gate — verified, not just UI. No valid signed entitlement → no run.
299
360
  if (!(await isProEntitled(options.entitlement))) {
@@ -376,16 +437,9 @@ export async function runSpec(spec, { messages, system, options = {}, images },
376
437
  if (options.mcp?.url && spec.trustToolsArg) {
377
438
  args.push(...trustToolArgs(spec.trustToolsArg, options.mcp));
378
439
  }
379
- // NOTE: opencode only loads MCP from its GLOBAL config (~/.config/opencode),
380
- // never a per-run/project file so we can't inject it here. opencode reaches
381
- // the browser tools via the bridge's STABLE /mcp endpoint, registered once with
382
- // `opencode mcp add chatpanel --url http://127.0.0.1:4319/mcp`.
383
- if (options.mcp?.url && spec.requiresStableMcp && !(await hasStableMcpConfig(spec, cwd))) {
384
- emit({
385
- type: 'status',
386
- text: `${label} needs one-time browser-tool setup: ${stableMcpSetupCommand(spec)}`,
387
- });
388
- }
440
+ // Some CLIs only load MCP from their persistent config, so ensure that stable
441
+ // /mcp endpoint is present before letting the agent answer with no tools.
442
+ if (options.mcp?.url) await ensureStableMcpConfig(spec, cwd, label, emit);
389
443
 
390
444
  const imageTokens = imageTokensFor(spec.imageArg, imageFiles);
391
445
  let placedImages = false;
package/src/env.js CHANGED
@@ -67,7 +67,7 @@ function shellWhich(name) {
67
67
  if (process.platform === 'win32') return '';
68
68
  try {
69
69
  const shell = process.env.SHELL || '/bin/zsh';
70
- const r = spawnSync(shell, ['-ilc', `command -v ${name} 2>/dev/null`], {
70
+ const r = spawnSync(shell, ['-lc', `command -v ${name} 2>/dev/null`], {
71
71
  encoding: 'utf8',
72
72
  timeout: 4000,
73
73
  });
@@ -319,7 +319,7 @@ export function enrichPath() {
319
319
  let shellPath = '';
320
320
  try {
321
321
  const shell = process.env.SHELL || '/bin/zsh';
322
- const r = spawnSync(shell, ['-ilc', 'command -p echo "$PATH"'], {
322
+ const r = spawnSync(shell, ['-lc', 'command -p echo "$PATH"'], {
323
323
  encoding: 'utf8',
324
324
  timeout: 4000,
325
325
  });
package/src/server.js CHANGED
@@ -30,7 +30,7 @@ import { callLocalMcp } from './mcp-local.js';
30
30
  // Hardcoded (not read from package.json) so it survives Bun's single-file
31
31
  // --compile, where package.json isn't on a readable FS. CI fails the publish if
32
32
  // this drifts from package.json, so the two can't silently diverge.
33
- const VERSION = '0.10.5';
33
+ const VERSION = '0.10.7';
34
34
  const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
35
35
  const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
36
36