@chatpanel/gateway 0.6.36 → 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 +23 -0
- package/bin/chatpanel-gateway.js +5 -0
- package/package.json +1 -1
- package/src/connect-agents.js +173 -0
- package/src/server.js +1 -1
package/README.md
CHANGED
|
@@ -191,6 +191,29 @@ command = "chatpanel-gateway"
|
|
|
191
191
|
args = ["mcp"]
|
|
192
192
|
```
|
|
193
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
|
+
|
|
194
217
|
**Claude Code** — one command (`--scope user` makes it available in every project):
|
|
195
218
|
|
|
196
219
|
```bash
|
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": {
|
|
@@ -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/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.
|