@chatpanel/bridge 0.10.32 → 0.10.33

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/README.md CHANGED
@@ -92,11 +92,26 @@ npm start # → http://127.0.0.1:4319
92
92
  | `POST` | `/v1/completions` | OpenAI-compatible legacy text Completions (streaming and non-streaming) |
93
93
  | `POST` | `/v1/responses` | OpenAI-compatible Responses API (streaming and non-streaming) |
94
94
  | `POST` | `/v1/messages` | Anthropic-compatible Messages API (streaming and non-streaming) |
95
+ | `GET` | `/skills` | skills installed on this machine, across every agent harness + configured folders |
96
+ | `GET` | `/skills/<name>` · `/skills/<name>/file/<path>` | one skill's instructions · one reference file |
97
+ | `POST` | `/mcp` | MCP endpoint — `chatpanel_skill_*` tools any CLI can call (plus browser tools while a ChatPanel chat is driving) |
95
98
 
96
99
  `/chat` streams Server-Sent Events: `{type:'delta',text}` as the answer is
97
100
  generated, `{type:'tool',name,summary}` / `{type:'status'}` for activity, and a
98
101
  final `{type:'done'}` (or `{type:'error',error}`).
99
102
 
103
+ ### Use your skills from any CLI
104
+
105
+ The bridge discovers the skills installed on your machine — Claude Code, Codex, Copilot,
106
+ Gemini, Hermes, `~/.agents/skills`, and any folder you configure — and exposes them as MCP
107
+ tools (`chatpanel_skill_list` / `_open` / `_read`). Point any MCP client at
108
+ `http://127.0.0.1:4319/mcp` and it can list and load them with no browser open.
109
+
110
+ For **one** server that adds your local **history** (chats, meetings, notes — redacted)
111
+ *alongside* your skills, add the
112
+ [Privacy Gateway](https://github.com/chatpanel/chatpanel-gateway) instead and point your CLI
113
+ at `chatpanel-gateway mcp`. The gateway proxies these skill tools and puts redaction in front.
114
+
100
115
  `options` per agent (set in ChatPanel Settings):
101
116
 
102
117
  ```jsonc
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/bridge",
3
- "version": "0.10.32",
3
+ "version": "0.10.33",
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": [
@@ -52,3 +52,7 @@ if ($installed) {
52
52
  Write-Host " `"$bin`""
53
53
  }
54
54
  Write-Host "Manage it: `"$bin`" --status | --uninstall"
55
+ Write-Host ""
56
+ Write-Host "Optional upgrade - the Privacy Gateway adds PII redaction, model routing and voice," -ForegroundColor Gray
57
+ Write-Host "and lets any CLI reach your history + skills through one MCP server:" -ForegroundColor Gray
58
+ Write-Host " irm https://dl.chatpanel.net/gateway/install.ps1 | iex"
@@ -4,10 +4,23 @@
4
4
  #
5
5
  # curl -fsSL https://raw.githubusercontent.com/chatpanel/chatpanel-bridge/main/scripts/install.sh | bash
6
6
  #
7
+ # Add the optional privacy gateway (redaction, model routing, voice) in the same flow:
8
+ # curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash -s -- --gateway
9
+ #
7
10
  # Downloading via curl means the file is NOT quarantined, so macOS won't show the
8
11
  # "damaged / unidentified developer" prompt that browser downloads trigger.
9
12
  set -euo pipefail
10
13
 
14
+ # --gateway also installs the ChatPanel Privacy Gateway — the optional upgrade that adds
15
+ # PII redaction, model routing and voice in front of everything the bridge does. The bridge
16
+ # is the common case and is always installed; the gateway is opt-in and heavier.
17
+ WITH_GATEWAY=0
18
+ for a in "$@"; do
19
+ case "$a" in
20
+ --gateway|--with-gateway) WITH_GATEWAY=1 ;;
21
+ esac
22
+ done
23
+
11
24
  os="$(uname -s)"
12
25
  arch="$(uname -m)"
13
26
  asset=""
@@ -59,3 +72,23 @@ case ":${PATH}:" in
59
72
  *":${dest}:"*) : ;;
60
73
  *) echo "Tip: add it to your PATH -> export PATH=\"\$HOME/.local/bin:\$PATH\"" ;;
61
74
  esac
75
+
76
+ if [ "$WITH_GATEWAY" = "1" ]; then
77
+ echo
78
+ echo "Adding the ChatPanel Privacy Gateway (optional upgrade)..."
79
+ # One download story: the gateway installer lives at the same dl host. A gateway failure
80
+ # must not fail the bridge install — the bridge is already up.
81
+ if curl -fsSL https://dl.chatpanel.net/gateway/install.sh | bash; then
82
+ echo "Gateway installed. One MCP config now gives any CLI your history + skills, redacted:"
83
+ echo " chatpanel-gateway mcp"
84
+ else
85
+ echo "Gateway install didn't complete — the bridge is unaffected. Retry any time:"
86
+ echo " curl -fsSL https://dl.chatpanel.net/gateway/install.sh | bash"
87
+ fi
88
+ else
89
+ echo
90
+ echo "Optional upgrade — the Privacy Gateway adds PII redaction, model routing and voice,"
91
+ echo "and lets any CLI (Codex, Claude Code) reach your history + skills through one MCP server:"
92
+ echo " curl -fsSL https://dl.chatpanel.net/gateway/install.sh | bash"
93
+ echo " (or re-run this with --gateway to add it now.)"
94
+ fi
@@ -151,6 +151,10 @@ export async function chat({ messages, system, options, images }, emit, { signal
151
151
  args.push('-s', sandbox, '-c', 'approval_policy=never');
152
152
  }
153
153
  if (REASONING) args.push('-c', `model_reasoning_effort=${REASONING}`);
154
+ // Ask Codex to emit reasoning SUMMARIES so the panel can stream the model's thinking.
155
+ // Additive + safe: a no-op for models/providers that don't produce summaries (e.g. some
156
+ // hosted models expose none), and cheap at the default effort. forwardEvent renders them.
157
+ args.push('-c', 'model_reasoning_summary=auto');
154
158
  // Browser tools: register the bridge's MCP server as a stdio MCP server (the
155
159
  // bridge binary in --mcp-stdio mode), so Codex can call our page-action tools.
156
160
  // `-c key=value` parses value as TOML; JSON.stringify yields valid TOML here.
@@ -182,6 +186,9 @@ export async function chat({ messages, system, options, images }, emit, { signal
182
186
 
183
187
  let stdout = '';
184
188
  let stderr = '';
189
+ // Per-run event state: correlate a command's started/completed events and emit each
190
+ // reasoning summary once (Codex sends the same item id across item.started/updated/completed).
191
+ const evState = { started: new Set(), reasoned: new Set(), n: 0 };
185
192
  let idleTimer;
186
193
  const armIdle = () => {
187
194
  clearTimeout(idleTimer);
@@ -201,7 +208,7 @@ export async function chat({ messages, system, options, images }, emit, { signal
201
208
  stdout = stdout.slice(nl + 1);
202
209
  if (!line.startsWith('{')) continue;
203
210
  try {
204
- forwardEvent(JSON.parse(line), emit);
211
+ forwardEvent(JSON.parse(line), emit, evState);
205
212
  } catch {
206
213
  /* not a JSON event line */
207
214
  }
@@ -240,16 +247,58 @@ export async function chat({ messages, system, options, images }, emit, { signal
240
247
  });
241
248
  }
242
249
 
243
- function forwardEvent(ev, emit) {
250
+ // Translate Codex `exec --json` events into the bridge's streaming vocabulary the panel
251
+ // renders richly. Codex sends item.started (in_progress) then item.completed for each item,
252
+ // reusing the item id — so we correlate a tool's start/done by that id and show its command,
253
+ // its output, and its exit status, the way Codex's own CLI does. Schema (codex-cli 0.15x):
254
+ // command_execution: { id, command, aggregated_output, exit_code, status }
255
+ // reasoning: { id, text } (only some models/efforts emit it)
256
+ // file_change: { id, changes:[{path}] }
257
+ // agent_message: { id, text } (the answer — read from -o outFile at close)
258
+ export function forwardEvent(ev, emit, state = { started: new Set(), reasoned: new Set(), n: 0 }) {
244
259
  const t = ev.type || '';
245
260
  const item = ev.item || {};
246
- if (item.type === 'command_execution' || t.includes('command')) {
247
- emit({ type: 'tool', name: 'shell', summary: (item.command || '').slice(0, 60) });
248
- } else if (item.type === 'reasoning' || t.includes('reasoning')) {
249
- emit({ type: 'reasoning' });
250
- } else if (item.type === 'file_change' || t.includes('patch')) {
251
- emit({ type: 'tool', name: 'edit', summary: '' });
252
- } else if (t === 'turn.started' || t === 'thread.started') {
253
- emit({ type: 'status', text: 'Codex working' });
261
+ const itype = item.type || '';
262
+ const completed = t === 'item.completed' || item.status === 'completed' || item.status === 'failed';
263
+
264
+ if (itype === 'command_execution' || (!itype && t.includes('command'))) {
265
+ const id = item.id || `cmd_${state.n++}`;
266
+ if (!state.started.has(id)) {
267
+ state.started.add(id);
268
+ emit({ type: 'tool', name: 'shell', phase: 'start', callId: id, input: { command: item.command || '' } });
269
+ }
270
+ if (completed) {
271
+ const ok = item.exit_code === 0 || item.exit_code == null;
272
+ emit({
273
+ type: 'tool', name: 'shell', phase: 'done', callId: id,
274
+ status: ok ? 'ok' : `exit ${item.exit_code}`,
275
+ result: String(item.aggregated_output || '').slice(0, 4000),
276
+ });
277
+ }
278
+ return;
279
+ }
280
+
281
+ if (itype === 'file_change' || (!itype && t.includes('patch'))) {
282
+ const id = item.id || `edit_${state.n++}`;
283
+ const files = Array.isArray(item.changes) ? item.changes.map((c) => c.path || c.file).filter(Boolean) : [];
284
+ if (!state.started.has(id)) {
285
+ state.started.add(id);
286
+ emit({ type: 'tool', name: 'edit', phase: 'start', callId: id, input: { files } });
287
+ }
288
+ if (completed) emit({ type: 'tool', name: 'edit', phase: 'done', callId: id, status: 'ok' });
289
+ return;
254
290
  }
291
+
292
+ if (itype === 'reasoning' || (!itype && t.includes('reasoning'))) {
293
+ // Emit each reasoning summary once (dedup the started/updated/completed repeats).
294
+ const id = item.id || `r_${state.n++}`;
295
+ const text = item.text || item.summary || '';
296
+ if (text && !state.reasoned.has(id)) {
297
+ state.reasoned.add(id);
298
+ emit({ type: 'reasoning', text: `${text}\n` });
299
+ }
300
+ return;
301
+ }
302
+
303
+ if (t === 'turn.started' || t === 'thread.started') emit({ type: 'status', text: 'Codex working' });
255
304
  }
package/src/server.js CHANGED
@@ -67,7 +67,7 @@ import {
67
67
  // Hardcoded (not read from package.json) so it survives Bun's single-file
68
68
  // --compile, where package.json isn't on a readable FS. CI fails the publish if
69
69
  // this drifts from package.json, so the two can't silently diverge.
70
- const VERSION = '0.10.32';
70
+ const VERSION = '0.10.33';
71
71
  const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
72
72
  const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
73
73