@chatpanel/bridge 0.10.6 → 0.10.8

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.6",
3
+ "version": "0.10.8",
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
@@ -82,9 +82,14 @@ export const kiro = makeCliAgent(
82
82
  args: 'chat --no-interactive --require-mcp-startup',
83
83
  promptVia: 'arg',
84
84
  modelArg: '--model {model}',
85
- trustToolsArg: '--trust-tools={tools}',
85
+ // Kiro can see MCP tools with --trust-tools, but in --no-interactive mode it
86
+ // only executes MCP calls when this broader trust flag is present.
87
+ trustAllToolsForMcp: true,
88
+ trustAllToolsArg: '--trust-all-tools',
86
89
  requiresStableMcp: true,
90
+ autoSetupStableMcp: true,
87
91
  stableMcpConfigCheck: 'kiro',
92
+ stableMcpSetupArgs: ['mcp', 'add', '--scope', 'global', '--name', 'chatpanel_browser', '--url', 'http://127.0.0.1:4319/mcp', '--force'],
88
93
  stableMcpSetupCommand: 'kiro-cli mcp add --scope global --name chatpanel_browser --url http://127.0.0.1:4319/mcp --force',
89
94
  listModelsArgs: '--list-models',
90
95
  label: 'Kiro',
@@ -150,11 +150,22 @@ export function trustToolArgs(template, mcp) {
150
150
  : [...tmpl.split(/\s+/).filter(Boolean), value];
151
151
  }
152
152
 
153
+ export function mcpTrustArgs(spec = {}, mcp) {
154
+ if (spec.trustAllToolsForMcp) return [spec.trustAllToolsArg || '--trust-all-tools'];
155
+ return spec.trustToolsArg ? trustToolArgs(spec.trustToolsArg, mcp) : [];
156
+ }
157
+
153
158
  export function stableMcpSetupCommand(spec = {}) {
154
159
  if (spec.stableMcpSetupCommand) return spec.stableMcpSetupCommand;
155
160
  return `opencode mcp add chatpanel --url ${CHATPANEL_STABLE_MCP_URL}`;
156
161
  }
157
162
 
163
+ export function stableMcpSetupPlan(spec = {}) {
164
+ const args = Array.isArray(spec.stableMcpSetupArgs) ? spec.stableMcpSetupArgs.filter((a) => a != null).map(String) : null;
165
+ if (!args?.length) return null;
166
+ return { command: spec.stableMcpSetupCommandName || spec.command, args };
167
+ }
168
+
158
169
  export function buildPiExtensionSource(mcp) {
159
170
  const specs = mcpToolSpecs(mcp);
160
171
  const declarations = specs.map((spec, index) => {
@@ -256,25 +267,27 @@ async function opencodeHasStableMcpConfig() {
256
267
  return false;
257
268
  }
258
269
 
259
- async function commandStdout(command, args, cwd) {
270
+ export async function commandOutput(command, args, cwd) {
260
271
  const resolved = resolveCommand(command);
261
272
  if (!resolved) return '';
262
273
  const [bin, argv, opts] = buildSpawnSpec(resolved, args, cwd || null);
263
274
  return new Promise((resolve) => {
264
275
  const child = spawn(bin, argv, opts);
265
276
  let out = '';
277
+ let err = '';
266
278
  const timer = setTimeout(() => {
267
279
  child.kill('SIGKILL');
268
- resolve(out);
280
+ resolve(out + err);
269
281
  }, 4000);
270
282
  child.stdout.on('data', (d) => (out += d.toString()));
283
+ child.stderr.on('data', (d) => (err += d.toString()));
271
284
  child.on('error', () => {
272
285
  clearTimeout(timer);
273
- resolve('');
286
+ resolve(out + err);
274
287
  });
275
288
  child.on('close', () => {
276
289
  clearTimeout(timer);
277
- resolve(out);
290
+ resolve(out + err);
278
291
  });
279
292
  try { child.stdin.end(); } catch { /* ignore */ }
280
293
  });
@@ -282,7 +295,7 @@ async function commandStdout(command, args, cwd) {
282
295
 
283
296
  async function kiroHasStableMcpConfig(command, cwd) {
284
297
  for (const scope of ['workspace', 'global', 'default']) {
285
- const out = await commandStdout(command, ['mcp', 'list', scope], cwd);
298
+ const out = await commandOutput(command, ['mcp', 'list', scope], cwd);
286
299
  if (out.includes(CHATPANEL_STABLE_MCP_URL) || /chatpanel_browser/i.test(out)) return true;
287
300
  }
288
301
  return false;
@@ -294,6 +307,59 @@ async function hasStableMcpConfig(spec, cwd) {
294
307
  return false;
295
308
  }
296
309
 
310
+ async function runStableMcpSetup(plan, cwd) {
311
+ const resolved = resolveCommand(plan.command);
312
+ if (!resolved) throw new Error(`Couldn't find "${plan.command}" to set up browser tools.`);
313
+ const [bin, argv, opts] = buildSpawnSpec(resolved, plan.args, cwd || null);
314
+ opts.env = { ...(opts.env || process.env), NO_COLOR: '1', FORCE_COLOR: '0', CLICOLOR: '0', TERM: 'dumb' };
315
+ await new Promise((resolve, reject) => {
316
+ let child;
317
+ try { child = spawn(bin, argv, opts); } catch (e) { return reject(new Error(`Failed to start ${plan.command}: ${e.message}`)); }
318
+ let stderr = '';
319
+ const timer = setTimeout(() => {
320
+ child.kill('SIGKILL');
321
+ reject(new Error(`${plan.command} MCP setup timed out.`));
322
+ }, 20_000);
323
+ child.stderr.on('data', (d) => (stderr += d.toString()));
324
+ child.on('error', (e) => {
325
+ clearTimeout(timer);
326
+ reject(new Error(`Failed to start ${plan.command}: ${e.message}`));
327
+ });
328
+ child.on('close', (code) => {
329
+ clearTimeout(timer);
330
+ if (code === 0) return resolve();
331
+ reject(new Error(`${plan.command} MCP setup exited ${code}: ${stderr.trim().split('\n').pop() || 'failed'}`));
332
+ });
333
+ try { child.stdin.end(); } catch { /* ignore */ }
334
+ });
335
+ }
336
+
337
+ export async function ensureStableMcpConfig(spec, cwd, label, emit, deps = {}) {
338
+ if (!spec.requiresStableMcp) return true;
339
+ const hasConfig = deps.hasConfig || hasStableMcpConfig;
340
+ const runSetup = deps.runSetup || runStableMcpSetup;
341
+ if (await hasConfig(spec, cwd)) return true;
342
+
343
+ const setupCommand = stableMcpSetupCommand(spec);
344
+ if (!spec.autoSetupStableMcp) {
345
+ const text = `${label} needs one-time browser-tool setup: ${setupCommand}`;
346
+ emit({ type: 'status', text });
347
+ throw new Error(text);
348
+ }
349
+
350
+ const plan = stableMcpSetupPlan(spec);
351
+ if (!plan?.command) {
352
+ const text = `${label} needs one-time browser-tool setup: ${setupCommand}`;
353
+ emit({ type: 'status', text });
354
+ throw new Error(text);
355
+ }
356
+
357
+ emit({ type: 'status', text: `${label} is setting up one-time browser tools...` });
358
+ await runSetup(plan, cwd);
359
+ if (await hasConfig(spec, cwd)) return true;
360
+ throw new Error(`${label} browser-tool setup completed, but the MCP server is still not visible. Run: ${setupCommand}`);
361
+ }
362
+
297
363
  export async function chat({ messages, system, options, images }, emit) {
298
364
  // Pro gate — verified, not just UI. No valid signed entitlement → no run.
299
365
  if (!(await isProEntitled(options.entitlement))) {
@@ -373,19 +439,10 @@ export async function runSpec(spec, { messages, system, options = {}, images },
373
439
  : [...tmpl.split(/\s+/).filter(Boolean), cfgFile];
374
440
  args = [...tokens, ...args];
375
441
  }
376
- if (options.mcp?.url && spec.trustToolsArg) {
377
- args.push(...trustToolArgs(spec.trustToolsArg, options.mcp));
378
- }
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
- }
442
+ if (options.mcp?.url) args.push(...mcpTrustArgs(spec, options.mcp));
443
+ // Some CLIs only load MCP from their persistent config, so ensure that stable
444
+ // /mcp endpoint is present before letting the agent answer with no tools.
445
+ if (options.mcp?.url) await ensureStableMcpConfig(spec, cwd, label, emit);
389
446
 
390
447
  const imageTokens = imageTokensFor(spec.imageArg, imageFiles);
391
448
  let placedImages = false;
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.6';
33
+ const VERSION = '0.10.8';
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