@chatpanel/gateway 0.6.35 → 0.6.37
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 +95 -0
- package/bin/chatpanel-gateway.js +5 -0
- package/package.json +2 -2
- package/src/connect-agents.js +173 -0
- package/src/local-status.js +8 -7
- package/src/mcp.js +12 -5
- package/src/server.js +1 -1
package/README.md
CHANGED
|
@@ -172,6 +172,85 @@ Streaming (SSE) is supported on all three: placeholders are restored on the fly,
|
|
|
172
172
|
holding back a tail so a token split across chunks (`[[PER` … `SON_1]]`) still
|
|
173
173
|
restores cleanly.
|
|
174
174
|
|
|
175
|
+
## Use it as an MCP server (Codex, Claude Code, any MCP client)
|
|
176
|
+
|
|
177
|
+
Point one MCP server at the gateway and any CLI agent can reach your **local history**
|
|
178
|
+
(past chats, meeting transcripts, notes — redacted on the way out) **and every skill
|
|
179
|
+
installed on your machine** — across Claude Code, Codex, Copilot, Gemini, Hermes,
|
|
180
|
+
`~/.agents/skills` and any folder you configure. History is served by the gateway; skills
|
|
181
|
+
are proxied from the [bridge](https://github.com/chatpanel/chatpanel-bridge) (optional — if
|
|
182
|
+
it is not running, the history tools still work and the skill tools say so).
|
|
183
|
+
|
|
184
|
+
It is a stdio MCP server: `chatpanel-gateway mcp`.
|
|
185
|
+
|
|
186
|
+
**Codex** — add to `~/.codex/config.toml`:
|
|
187
|
+
|
|
188
|
+
```toml
|
|
189
|
+
[mcp_servers.chatpanel]
|
|
190
|
+
command = "chatpanel-gateway"
|
|
191
|
+
args = ["mcp"]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
> **Corporate Codex with an approval guardian?** If a tool call is rejected with
|
|
195
|
+
> *"Automatic approval review failed"* (an `approvals_reviewer = "auto_review"` policy
|
|
196
|
+
> can't assess a tool it doesn't know), pre-approve ChatPanel's read-only tools the same
|
|
197
|
+
> way you would any other server — one block per tool:
|
|
198
|
+
>
|
|
199
|
+
> ```toml
|
|
200
|
+
> [mcp_servers.chatpanel.tools.search_history]
|
|
201
|
+
> approval_mode = "approve"
|
|
202
|
+
> [mcp_servers.chatpanel.tools.get_record]
|
|
203
|
+
> approval_mode = "approve"
|
|
204
|
+
> [mcp_servers.chatpanel.tools.list_history]
|
|
205
|
+
> approval_mode = "approve"
|
|
206
|
+
> [mcp_servers.chatpanel.tools.list_skills]
|
|
207
|
+
> approval_mode = "approve"
|
|
208
|
+
> [mcp_servers.chatpanel.tools.open_skill]
|
|
209
|
+
> approval_mode = "approve"
|
|
210
|
+
> [mcp_servers.chatpanel.tools.read_skill_file]
|
|
211
|
+
> approval_mode = "approve"
|
|
212
|
+
> ```
|
|
213
|
+
>
|
|
214
|
+
> All six only read local data (your redacted history and your installed skills), so
|
|
215
|
+
> approving them carries no write or network risk.
|
|
216
|
+
|
|
217
|
+
**Claude Code** — one command (`--scope user` makes it available in every project):
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
claude mcp add --scope user chatpanel chatpanel-gateway mcp
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Any other MCP client** — run the stdio server `chatpanel-gateway mcp`, or point at it the
|
|
224
|
+
way your client configures a `command` + `args` stdio server.
|
|
225
|
+
|
|
226
|
+
> If your client launches with a stripped `PATH` and cannot find `chatpanel-gateway`, use
|
|
227
|
+
> the absolute path (find it with `which chatpanel-gateway`).
|
|
228
|
+
|
|
229
|
+
### Tools it exposes
|
|
230
|
+
|
|
231
|
+
| Tool | What it does |
|
|
232
|
+
|------|--------------|
|
|
233
|
+
| `search_history` | Full-text search your chats, meetings and notes by relevance |
|
|
234
|
+
| `get_record` | Fetch one record's full text by id (`chat:…`, `meeting:…`, `note:…`) |
|
|
235
|
+
| `list_history` | Browse/page the corpus (newest first, no bodies) |
|
|
236
|
+
| `list_skills` | List every installed skill (name + one-line description, and where it came from) |
|
|
237
|
+
| `open_skill` | Load one skill's full instructions by name |
|
|
238
|
+
| `read_skill_file` | Read a reference file a skill's instructions point at |
|
|
239
|
+
|
|
240
|
+
Everything a history tool returns is **redacted** with the same engine
|
|
241
|
+
([`@chatpanel/pii`](https://github.com/chatpanel/chatpanel-pii)) the gateway uses for model
|
|
242
|
+
traffic — the real values never leave your device. Skill scripts are never served as text.
|
|
243
|
+
|
|
244
|
+
### One local view
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
chatpanel-gateway local
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
prints what is running — the gateway (this) and the bridge (your agents + skills) — so you
|
|
251
|
+
can see the whole local runtime at a glance. The bridge is an optional companion; a missing
|
|
252
|
+
one is reported plainly, never as an error.
|
|
253
|
+
|
|
175
254
|
## How it fits with ChatPanel
|
|
176
255
|
|
|
177
256
|
The [extension](https://github.com/chatpanel/chatpanel-extension) redacts inside
|
|
@@ -192,6 +271,22 @@ agent's own multi-turn loop is blinded, not just the first prompt.
|
|
|
192
271
|
edits. The default tier touches only structured secrets and (in `full`) detected
|
|
193
272
|
entities — keep your dictionary prose-focused.
|
|
194
273
|
|
|
274
|
+
**Using it as an MCP server:**
|
|
275
|
+
|
|
276
|
+
- The **gateway must be running** for any of its tools to work — it is a background
|
|
277
|
+
service (`chatpanel-gateway --install` registers it to start at login). If a tool
|
|
278
|
+
returns *"the ChatPanel gateway is not running"*, start it and retry; the message tells
|
|
279
|
+
you the command.
|
|
280
|
+
- **Skills need the bridge** (an optional companion). Without it, the history tools still
|
|
281
|
+
work and the skill tools return a one-line *"the bridge is not running — install it
|
|
282
|
+
with …"* — no silent empty result.
|
|
283
|
+
- History tools search the gateway's **warm store**, which is seeded from your ChatPanel
|
|
284
|
+
backups. If `list_history` says it is empty, the gateway has not been seeded yet — open
|
|
285
|
+
ChatPanel so a backup lands, or check the [ingest docs](#endpoints).
|
|
286
|
+
- Everything returned is **redacted** at the configured tier. A tool result may contain a
|
|
287
|
+
placeholder like `[[EMAIL_1]]` where a value was blinded — that is the privacy guarantee
|
|
288
|
+
working, not a bug.
|
|
289
|
+
|
|
195
290
|
## License
|
|
196
291
|
|
|
197
292
|
Source-available under the same license as the ChatPanel extension and bridge —
|
package/bin/chatpanel-gateway.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// chatpanel-gateway start the gateway (foreground)
|
|
5
5
|
// chatpanel-gateway mcp stdio MCP server exposing warm history as tools
|
|
6
6
|
// chatpanel-gateway local show the local runtime — bridge + gateway, one view
|
|
7
|
+
// chatpanel-gateway connect point your CLI agents (Codex, Claude Code, …) at this server
|
|
7
8
|
// chatpanel-gateway --install register login auto-start + start now
|
|
8
9
|
// chatpanel-gateway --uninstall remove login auto-start
|
|
9
10
|
// chatpanel-gateway --status is auto-start registered?
|
|
@@ -24,6 +25,10 @@ try {
|
|
|
24
25
|
// Read-only unified view of both services. No server.js import — just HTTP probes.
|
|
25
26
|
const { localStatus, formatLocalStatus } = await import('../src/local-status.js');
|
|
26
27
|
process.stdout.write(formatLocalStatus(await localStatus()));
|
|
28
|
+
} else if (arg === 'connect') {
|
|
29
|
+
const { connectAgents, formatConnect } = await import('../src/connect-agents.js');
|
|
30
|
+
const dryRun = process.argv.includes('--dry-run') || process.argv.includes('-n');
|
|
31
|
+
process.stdout.write(formatConnect(connectAgents({ dryRun }), { dryRun }));
|
|
27
32
|
} else {
|
|
28
33
|
const { start, VERSION } = await import('../src/server.js');
|
|
29
34
|
const { installService, uninstallService, serviceStatus } = await import('../src/service.js');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/gateway",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.37",
|
|
4
4
|
"description": "Local privacy gateway \u2014 redacts PII out of OpenAI/Anthropic API traffic before it reaches a model, then restores it in the reply. Point opencode, codex, aider, Claude Code, etc. at it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
14
|
"start": "node bin/chatpanel-gateway.js",
|
|
15
|
-
"test": "node
|
|
15
|
+
"test": "node scripts/run-tests.mjs",
|
|
16
16
|
"typecheck": "tsc -p tsconfig.json",
|
|
17
17
|
"build:bin": "bash scripts/build-binaries.sh"
|
|
18
18
|
},
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// connect-agents.js — wire the local CLI agents to the ChatPanel MCP server in one step.
|
|
2
|
+
//
|
|
3
|
+
// `chatpanel-gateway connect` detects the agent CLIs installed on this machine and points
|
|
4
|
+
// each at `chatpanel-gateway mcp`, so a person gets their history + skills in every agent
|
|
5
|
+
// without hand-editing four different config files in four different formats.
|
|
6
|
+
//
|
|
7
|
+
// Two rules keep this safe to run unattended:
|
|
8
|
+
// • BACK UP then EDIT, and be IDEMPOTENT — running it twice changes nothing the second
|
|
9
|
+
// time. A user's existing MCP servers are never touched.
|
|
10
|
+
// • Only auto-write a format we can write CORRECTLY. Codex (TOML append), Claude Code
|
|
11
|
+
// (its own CLI) and Gemini (plain JSON) are safe. For agents whose config is JSONC with
|
|
12
|
+
// comments, or whose format we cannot verify, we PRINT the exact snippet instead of
|
|
13
|
+
// risking a corrupted config. Guessing a format and clobbering a config is worse than
|
|
14
|
+
// asking the user to paste four lines.
|
|
15
|
+
|
|
16
|
+
import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync } from 'node:fs';
|
|
17
|
+
import { execFileSync } from 'node:child_process';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
|
|
21
|
+
// The six read-only tools the ChatPanel MCP server exposes (history + skills). Named here so
|
|
22
|
+
// the Codex approval blocks stay in step with what the server actually advertises.
|
|
23
|
+
const TOOLS = ['search_history', 'get_record', 'list_history', 'list_skills', 'open_skill', 'read_skill_file'];
|
|
24
|
+
|
|
25
|
+
// Resolve the command a config should launch. A bare name works when the client inherits a
|
|
26
|
+
// normal PATH; an absolute path is the safe fallback when it does not.
|
|
27
|
+
export function resolveServerCommand(which = whichSync) {
|
|
28
|
+
const abs = which('chatpanel-gateway');
|
|
29
|
+
return abs || 'chatpanel-gateway';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function whichSync(bin) {
|
|
33
|
+
try {
|
|
34
|
+
return execFileSync('which', [bin], { encoding: 'utf8' }).trim() || null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function backup(path) {
|
|
41
|
+
const b = `${path}.bak.chatpanel`;
|
|
42
|
+
try { copyFileSync(path, b); return b; } catch { return null; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Codex ──────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// ~/.codex/config.toml. TOML, and Node has no TOML writer — but the change is a pure
|
|
47
|
+
// APPEND of a server block plus per-tool approval blocks, so a text append (guarded by a
|
|
48
|
+
// presence check) is correct and non-destructive. The approval blocks matter: a corporate
|
|
49
|
+
// Codex with `approvals_reviewer = "auto_review"` rejects a tool it cannot assess, and this
|
|
50
|
+
// is how every other server on such a setup is pre-approved.
|
|
51
|
+
function connectCodex({ cmd, dryRun, home }) {
|
|
52
|
+
const dir = join(home, '.codex');
|
|
53
|
+
const path = join(dir, 'config.toml');
|
|
54
|
+
if (!existsSync(dir)) return { id: 'codex', status: 'absent' };
|
|
55
|
+
const cur = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
56
|
+
if (/\[mcp_servers\.chatpanel\]/.test(cur)) return { id: 'codex', status: 'already', path };
|
|
57
|
+
|
|
58
|
+
let block = '\n# ChatPanel — local history (redacted) + your installed skills, via one MCP server.\n';
|
|
59
|
+
block += `[mcp_servers.chatpanel]\ncommand = ${JSON.stringify(cmd)}\nargs = ["mcp"]\n\n`;
|
|
60
|
+
block += '# Read-only tools — pre-approved so a corporate auto_review guardian does not reject them.\n';
|
|
61
|
+
for (const t of TOOLS) block += `[mcp_servers.chatpanel.tools.${t}]\napproval_mode = "approve"\n\n`;
|
|
62
|
+
|
|
63
|
+
if (dryRun) return { id: 'codex', status: 'would', path, preview: block.trim() };
|
|
64
|
+
const bak = backup(path);
|
|
65
|
+
writeFileSync(path, `${cur.replace(/\s*$/, '')}\n${block}`);
|
|
66
|
+
return { id: 'codex', status: 'configured', path, backup: bak };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Claude Code ────────────────────────────────────────────────────────────────────────
|
|
70
|
+
// Its own CLI owns ~/.claude.json, so use `claude mcp add` rather than editing it. --scope
|
|
71
|
+
// user makes it available in every project.
|
|
72
|
+
function connectClaude({ cmd, dryRun, which }) {
|
|
73
|
+
const claude = which('claude');
|
|
74
|
+
if (!claude) return { id: 'claude-code', status: 'absent' };
|
|
75
|
+
// Already added? `claude mcp get` exits non-zero when it is not there.
|
|
76
|
+
try { execFileSync(claude, ['mcp', 'get', 'chatpanel'], { stdio: 'ignore' }); return { id: 'claude-code', status: 'already' }; } catch { /* not present */ }
|
|
77
|
+
if (dryRun) return { id: 'claude-code', status: 'would', preview: `claude mcp add --scope user chatpanel ${cmd} mcp` };
|
|
78
|
+
try {
|
|
79
|
+
execFileSync(claude, ['mcp', 'add', '--scope', 'user', 'chatpanel', cmd, 'mcp'], { stdio: 'ignore' });
|
|
80
|
+
return { id: 'claude-code', status: 'configured' };
|
|
81
|
+
} catch (e) {
|
|
82
|
+
return { id: 'claude-code', status: 'error', message: e.message };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Gemini CLI / Antigravity ─────────────────────────────────────────────────────────────
|
|
87
|
+
// ~/.gemini/settings.json — plain JSON, safe to parse and merge.
|
|
88
|
+
function connectGemini({ cmd, dryRun, home }) {
|
|
89
|
+
const dir = join(home, '.gemini');
|
|
90
|
+
const path = join(dir, 'settings.json');
|
|
91
|
+
if (!existsSync(dir)) return { id: 'gemini', status: 'absent' };
|
|
92
|
+
let cfg = {};
|
|
93
|
+
if (existsSync(path)) {
|
|
94
|
+
try { cfg = JSON.parse(readFileSync(path, 'utf8')); } catch { return { id: 'gemini', status: 'error', message: 'settings.json is not valid JSON — left untouched', path }; }
|
|
95
|
+
}
|
|
96
|
+
cfg.mcpServers = cfg.mcpServers || {};
|
|
97
|
+
if (cfg.mcpServers.chatpanel) return { id: 'gemini', status: 'already', path };
|
|
98
|
+
const entry = { command: cmd, args: ['mcp'] };
|
|
99
|
+
if (dryRun) return { id: 'gemini', status: 'would', path, preview: JSON.stringify({ mcpServers: { chatpanel: entry } }, null, 2) };
|
|
100
|
+
const bak = existsSync(path) ? backup(path) : null;
|
|
101
|
+
cfg.mcpServers.chatpanel = entry;
|
|
102
|
+
mkdirSync(dir, { recursive: true });
|
|
103
|
+
writeFileSync(path, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
104
|
+
return { id: 'gemini', status: 'configured', path, backup: bak };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Agents we do NOT auto-write (JSONC with comments, or a format we can't verify) ───────
|
|
108
|
+
// Detected and given the exact snippet, rather than risking a clobbered config.
|
|
109
|
+
function manualAgent(id, present, snippet) {
|
|
110
|
+
return present ? { id, status: 'manual', snippet } : { id, status: 'absent' };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function otherAgents({ cmd, home, which }) {
|
|
114
|
+
const out = [];
|
|
115
|
+
// OpenCode — opencode.json / .jsonc. JSONC keeps comments; we won't rewrite and drop them.
|
|
116
|
+
out.push(manualAgent('opencode', existsSync(join(home, '.opencode')) || existsSync(join(home, '.config', 'opencode')),
|
|
117
|
+
`Add to your opencode config (opencode.json):\n "mcp": { "chatpanel": { "type": "local", "command": [${JSON.stringify(cmd)}, "mcp"], "enabled": true } }`));
|
|
118
|
+
// GitHub Copilot CLI.
|
|
119
|
+
out.push(manualAgent('copilot', existsSync(join(home, '.copilot')),
|
|
120
|
+
`Copilot CLI: add an MCP server named "chatpanel" running: ${cmd} mcp`));
|
|
121
|
+
// Hermes.
|
|
122
|
+
out.push(manualAgent('hermes', existsSync(join(home, '.hermes')) || !!which('hermes'),
|
|
123
|
+
`Hermes: register an MCP server "chatpanel" with command ${cmd} mcp (see Hermes' MCP docs).`));
|
|
124
|
+
// Pi.
|
|
125
|
+
out.push(manualAgent('pi', existsSync(join(home, '.pi')) || !!which('pi'),
|
|
126
|
+
`Pi: add an MCP server "chatpanel" running: ${cmd} mcp`));
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Detect and (unless dryRun) configure every agent found. Returns one result per agent so a
|
|
132
|
+
* caller can render a summary. Pure-ish: the only side effects are the config writes, and
|
|
133
|
+
* dryRun suppresses them.
|
|
134
|
+
*/
|
|
135
|
+
export function connectAgents({ dryRun = false, cmd, home = os.homedir(), which = whichSync } = {}) {
|
|
136
|
+
const serverCmd = cmd || resolveServerCommand(which);
|
|
137
|
+
const results = [
|
|
138
|
+
connectCodex({ cmd: serverCmd, dryRun, home }),
|
|
139
|
+
connectClaude({ cmd: serverCmd, dryRun, which }),
|
|
140
|
+
connectGemini({ cmd: serverCmd, dryRun, home }),
|
|
141
|
+
...otherAgents({ cmd: serverCmd, home, which }),
|
|
142
|
+
];
|
|
143
|
+
return { cmd: serverCmd, results };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const LABEL = {
|
|
147
|
+
codex: 'Codex', 'claude-code': 'Claude Code', gemini: 'Gemini / Antigravity',
|
|
148
|
+
opencode: 'OpenCode', copilot: 'GitHub Copilot', hermes: 'Hermes', pi: 'Pi',
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/** Human summary for the CLI. */
|
|
152
|
+
export function formatConnect({ cmd, results }, { dryRun = false } = {}) {
|
|
153
|
+
const lines = [`ChatPanel MCP — connecting your agents to: ${cmd} mcp`, ''];
|
|
154
|
+
const icon = { configured: '✓', already: '•', would: '→', manual: '✎', absent: '·', error: '✕' };
|
|
155
|
+
for (const r of results) {
|
|
156
|
+
const name = LABEL[r.id] || r.id;
|
|
157
|
+
if (r.status === 'absent') continue; // don't list agents that aren't installed
|
|
158
|
+
const head = ` ${icon[r.status] || '?'} ${name}`;
|
|
159
|
+
if (r.status === 'configured') lines.push(`${head} — configured${r.backup ? ` (backup: ${r.backup})` : ''}`);
|
|
160
|
+
else if (r.status === 'already') lines.push(`${head} — already connected`);
|
|
161
|
+
else if (r.status === 'would') lines.push(`${head} — would configure:\n ${(r.preview || '').split('\n').join('\n ')}`);
|
|
162
|
+
else if (r.status === 'manual') lines.push(`${head} — add it yourself:\n ${r.snippet.split('\n').join('\n ')}`);
|
|
163
|
+
else if (r.status === 'error') lines.push(`${head} — ${r.message}`);
|
|
164
|
+
}
|
|
165
|
+
const configured = results.filter((r) => r.status === 'configured').length;
|
|
166
|
+
lines.push('');
|
|
167
|
+
if (dryRun) lines.push(' (dry run — nothing was written. Re-run without --dry-run to apply.)');
|
|
168
|
+
else if (configured) lines.push(` Done. Restart the configured agent(s) to pick up the new server.`);
|
|
169
|
+
else lines.push(' Nothing to change.');
|
|
170
|
+
const anyManual = results.some((r) => r.status === 'manual');
|
|
171
|
+
if (anyManual) lines.push(' Agents marked ✎ use a config format best edited by hand — the snippet above is exact.');
|
|
172
|
+
return `${lines.join('\n')}\n`;
|
|
173
|
+
}
|
package/src/local-status.js
CHANGED
|
@@ -42,9 +42,9 @@ async function probe(url, path = '/health') {
|
|
|
42
42
|
* A structured picture of the local runtime — for the `local` command and for the gateway
|
|
43
43
|
* to log at startup. Pure except the two probes; the caller decides how to render it.
|
|
44
44
|
*/
|
|
45
|
-
export async function localStatus() {
|
|
46
|
-
const gwUrl = gatewayUrl();
|
|
47
|
-
const brUrl = bridgeUrl();
|
|
45
|
+
export async function localStatus({ gatewayUrl: gwOverride, bridgeUrl: brOverride } = {}) {
|
|
46
|
+
const gwUrl = gwOverride || gatewayUrl();
|
|
47
|
+
const brUrl = brOverride || bridgeUrl();
|
|
48
48
|
const [gw, br] = await Promise.all([probe(gwUrl), probe(brUrl)]);
|
|
49
49
|
const skills = br.ok ? await probe(brUrl, '/skills').then((r) => (r.ok ? (r.data.skills || []).length : null)).catch(() => null) : null;
|
|
50
50
|
return {
|
|
@@ -94,11 +94,12 @@ export function formatLocalStatus(s) {
|
|
|
94
94
|
* One-line note for the gateway to log at startup, so the operator sees the unified picture
|
|
95
95
|
* without running anything. Never throws; a probe failure just says "not detected".
|
|
96
96
|
*/
|
|
97
|
-
export async function bridgePresenceNote() {
|
|
98
|
-
const
|
|
97
|
+
export async function bridgePresenceNote(brOverride) {
|
|
98
|
+
const url = brOverride || bridgeUrl();
|
|
99
|
+
const br = await probe(url);
|
|
99
100
|
if (br.ok) {
|
|
100
101
|
const n = (br.data.skills?.count ?? null);
|
|
101
|
-
return `bridge detected at ${
|
|
102
|
+
return `bridge detected at ${url} (v${br.data.version}${n != null ? `, ${n} skills` : ''}) — its agents and skills are available through this gateway.`;
|
|
102
103
|
}
|
|
103
|
-
return `bridge not detected at ${
|
|
104
|
+
return `bridge not detected at ${url} — local agents/skills are unavailable until it runs (curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash). The gateway runs fine without it.`;
|
|
104
105
|
}
|
package/src/mcp.js
CHANGED
|
@@ -120,9 +120,16 @@ const TOOLS = [
|
|
|
120
120
|
];
|
|
121
121
|
|
|
122
122
|
async function gatewayJson(path, init) {
|
|
123
|
-
|
|
123
|
+
let res;
|
|
124
|
+
try {
|
|
125
|
+
res = await fetch(baseUrl() + path, init);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// The most common cause by far: the gateway service is not running. Say so, and how to
|
|
128
|
+
// fix it, so the agent can relay something actionable instead of a bare fetch error.
|
|
129
|
+
throw new Error(`the ChatPanel gateway is not running at ${baseUrl()} — start it with "chatpanel-gateway --install" (or run "chatpanel-gateway"), then retry. [${e.message}]`);
|
|
130
|
+
}
|
|
124
131
|
const data = await res.json().catch(() => ({}));
|
|
125
|
-
if (!res.ok) throw new Error(data?.error?.message || `gateway ${res.status}`);
|
|
132
|
+
if (!res.ok) throw new Error(data?.error?.message || `the gateway returned HTTP ${res.status} for ${path}`);
|
|
126
133
|
return data;
|
|
127
134
|
}
|
|
128
135
|
|
|
@@ -153,7 +160,7 @@ async function callTool(name, args = {}) {
|
|
|
153
160
|
if (name === 'list_skills') {
|
|
154
161
|
let data;
|
|
155
162
|
try { data = await bridgeJson('/skills'); }
|
|
156
|
-
catch (e) { return `
|
|
163
|
+
catch (e) { return `Installed skills are unavailable because the ChatPanel bridge is not running at ${bridgeBase()}. Install/start it with:\n curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash\nThen retry. History tools work without the bridge. [${e.message}]`; }
|
|
157
164
|
const rows = data.skills || [];
|
|
158
165
|
if (!rows.length) return 'No skills installed on this machine yet.';
|
|
159
166
|
return [`${rows.length} skill(s) installed:`, ...rows.map((r) => `- ${r.command || r.id}: ${r.description || r.name}${r.origin?.source ? ` (from ${r.origin.source})` : ''}`)].join('\n') + '\n\nUse open_skill with a name to load its instructions.';
|
|
@@ -161,7 +168,7 @@ async function callTool(name, args = {}) {
|
|
|
161
168
|
if (name === 'open_skill') {
|
|
162
169
|
let data;
|
|
163
170
|
try { data = await bridgeJson(`/skills/${encodeURIComponent(String(args.name || '').trim())}`); }
|
|
164
|
-
catch (e) { return `Could not open "${args.name}": ${e.message}`; }
|
|
171
|
+
catch (e) { return `Could not open "${args.name}": ${e.message}. If the bridge isn't running, start it: curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash`; }
|
|
165
172
|
return data.skill?.prompt || '(this skill has no extra instructions — just apply it.)';
|
|
166
173
|
}
|
|
167
174
|
if (name === 'read_skill_file') {
|
|
@@ -169,7 +176,7 @@ async function callTool(name, args = {}) {
|
|
|
169
176
|
const path = String(args.path || '').trim().split('/').map(encodeURIComponent).join('/');
|
|
170
177
|
let data;
|
|
171
178
|
try { data = await bridgeJson(`/skills/${skill}/file/${path}`); }
|
|
172
|
-
catch (e) { return `Could not read ${args.path}: ${e.message}`; }
|
|
179
|
+
catch (e) { return `Could not read ${args.path}: ${e.message}. If the bridge isn't running, start it: curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash`; }
|
|
173
180
|
return data.text || '(empty)';
|
|
174
181
|
}
|
|
175
182
|
throw new Error(`unknown tool: ${name}`);
|
package/src/server.js
CHANGED
|
@@ -45,7 +45,7 @@ import * as openai from './openai.js';
|
|
|
45
45
|
import * as responses from './responses.js';
|
|
46
46
|
import * as anthropic from './anthropic.js';
|
|
47
47
|
|
|
48
|
-
export const VERSION = '0.6.
|
|
48
|
+
export const VERSION = '0.6.37';
|
|
49
49
|
|
|
50
50
|
// WARM search tier — SQLite + FTS5 record store (falls back to an encrypted-JSON
|
|
51
51
|
// store if SQLite can't load), fed by the extension's ingest sync + backup-ingest.
|