@chatpanel/gateway 0.6.34 → 0.6.35
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/bin/chatpanel-gateway.js +5 -0
- package/package.json +1 -1
- package/src/local-status.js +104 -0
- package/src/server.js +8 -1
package/bin/chatpanel-gateway.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// chatpanel-gateway start the gateway (foreground)
|
|
5
5
|
// chatpanel-gateway mcp stdio MCP server exposing warm history as tools
|
|
6
|
+
// chatpanel-gateway local show the local runtime — bridge + gateway, one view
|
|
6
7
|
// chatpanel-gateway --install register login auto-start + start now
|
|
7
8
|
// chatpanel-gateway --uninstall remove login auto-start
|
|
8
9
|
// chatpanel-gateway --status is auto-start registered?
|
|
@@ -19,6 +20,10 @@ try {
|
|
|
19
20
|
// server.js (which would open a second handle on the warm SQLite store).
|
|
20
21
|
const { runMcpServer } = await import('../src/mcp.js');
|
|
21
22
|
await runMcpServer();
|
|
23
|
+
} else if (arg === 'local') {
|
|
24
|
+
// Read-only unified view of both services. No server.js import — just HTTP probes.
|
|
25
|
+
const { localStatus, formatLocalStatus } = await import('../src/local-status.js');
|
|
26
|
+
process.stdout.write(formatLocalStatus(await localStatus()));
|
|
22
27
|
} else {
|
|
23
28
|
const { start, VERSION } = await import('../src/server.js');
|
|
24
29
|
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.35",
|
|
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,104 @@
|
|
|
1
|
+
// local-status.js — one view of ChatPanel's local runtime: the bridge and the gateway.
|
|
2
|
+
//
|
|
3
|
+
// U3 of docs/bridge-gateway-unification.md: a person should be able to ask "what's running
|
|
4
|
+
// locally?" once and get a straight answer, without knowing there are two services on two
|
|
5
|
+
// ports. This is the read side of that — it probes both over HTTP and reports. It does NOT
|
|
6
|
+
// control either service (start/stop stays with each module's own installer, which owns its
|
|
7
|
+
// launchd/systemd unit); managing another module's service from here would duplicate the
|
|
8
|
+
// knowledge of how to do it and drift.
|
|
9
|
+
//
|
|
10
|
+
// The bridge is OPTIONAL from the gateway's side and the gateway is OPTIONAL from the
|
|
11
|
+
// bridge's — so a missing one is reported plainly, never as an error.
|
|
12
|
+
|
|
13
|
+
import { loadConfig } from './config.js';
|
|
14
|
+
|
|
15
|
+
function gatewayUrl() {
|
|
16
|
+
try {
|
|
17
|
+
return `http://127.0.0.1:${loadConfig().port || 4320}`;
|
|
18
|
+
} catch {
|
|
19
|
+
return 'http://127.0.0.1:4320';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function bridgeUrl() {
|
|
24
|
+
try {
|
|
25
|
+
return String(loadConfig().bridge?.url || 'http://127.0.0.1:4319').replace(/\/+$/, '');
|
|
26
|
+
} catch {
|
|
27
|
+
return 'http://127.0.0.1:4319';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function probe(url, path = '/health') {
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch(url + path, { signal: AbortSignal.timeout(2500) });
|
|
34
|
+
if (!res.ok) return { ok: false, reason: `HTTP ${res.status}` };
|
|
35
|
+
return { ok: true, data: await res.json().catch(() => ({})) };
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return { ok: false, reason: e?.name === 'TimeoutError' ? 'no response' : (e?.message || 'unreachable') };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A structured picture of the local runtime — for the `local` command and for the gateway
|
|
43
|
+
* to log at startup. Pure except the two probes; the caller decides how to render it.
|
|
44
|
+
*/
|
|
45
|
+
export async function localStatus() {
|
|
46
|
+
const gwUrl = gatewayUrl();
|
|
47
|
+
const brUrl = bridgeUrl();
|
|
48
|
+
const [gw, br] = await Promise.all([probe(gwUrl), probe(brUrl)]);
|
|
49
|
+
const skills = br.ok ? await probe(brUrl, '/skills').then((r) => (r.ok ? (r.data.skills || []).length : null)).catch(() => null) : null;
|
|
50
|
+
return {
|
|
51
|
+
gateway: {
|
|
52
|
+
url: gwUrl,
|
|
53
|
+
running: gw.ok,
|
|
54
|
+
version: gw.ok ? gw.data.version : null,
|
|
55
|
+
tier: gw.ok ? gw.data.tier : null,
|
|
56
|
+
reason: gw.ok ? null : gw.reason,
|
|
57
|
+
},
|
|
58
|
+
bridge: {
|
|
59
|
+
url: brUrl,
|
|
60
|
+
running: br.ok,
|
|
61
|
+
version: br.ok ? br.data.version : null,
|
|
62
|
+
agents: br.ok ? (br.data.agents || []).filter((a) => a.available).length : null,
|
|
63
|
+
skills: skills ?? (br.ok ? br.data.skills?.count ?? null : null),
|
|
64
|
+
reason: br.ok ? null : br.reason,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Human-readable block for the CLI. */
|
|
70
|
+
export function formatLocalStatus(s) {
|
|
71
|
+
const line = (name, m, extra) => {
|
|
72
|
+
const dot = m.running ? '●' : '○';
|
|
73
|
+
const head = m.running ? `${name} running · v${m.version}` : `${name} not running${m.reason ? ` (${m.reason})` : ''}`;
|
|
74
|
+
return ` ${dot} ${head}\n ${extra}`;
|
|
75
|
+
};
|
|
76
|
+
const gw = line('Gateway', s.gateway, s.gateway.running
|
|
77
|
+
? `Privacy layer: redaction, routing, voice. ${s.gateway.url}`
|
|
78
|
+
: `Optional upgrade (redaction, routing, voice). Start with: chatpanel-gateway --install`);
|
|
79
|
+
const brExtra = s.bridge.running
|
|
80
|
+
? `Local agents & skills${s.bridge.agents != null ? ` · ${s.bridge.agents} agent(s)` : ''}${s.bridge.skills != null ? ` · ${s.bridge.skills} skill(s)` : ''}. ${s.bridge.url}`
|
|
81
|
+
: `Runs your local coding agents and skills. Start with: curl -fsSL https://dl.chatpanel.net/bridge/install.sh | bash`;
|
|
82
|
+
const br = line('Bridge', s.bridge, brExtra);
|
|
83
|
+
const summary = s.bridge.running && s.gateway.running
|
|
84
|
+
? 'Both running — local traffic can route through the gateway\'s privacy layer.'
|
|
85
|
+
: s.bridge.running
|
|
86
|
+
? 'Bridge up. The gateway is an optional upgrade.'
|
|
87
|
+
: s.gateway.running
|
|
88
|
+
? 'Gateway up. Start the bridge to use local agents and skills.'
|
|
89
|
+
: 'Neither running.';
|
|
90
|
+
return `ChatPanel local\n\n${gw}\n\n${br}\n\n ${summary}\n`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* One-line note for the gateway to log at startup, so the operator sees the unified picture
|
|
95
|
+
* without running anything. Never throws; a probe failure just says "not detected".
|
|
96
|
+
*/
|
|
97
|
+
export async function bridgePresenceNote() {
|
|
98
|
+
const br = await probe(bridgeUrl());
|
|
99
|
+
if (br.ok) {
|
|
100
|
+
const n = (br.data.skills?.count ?? null);
|
|
101
|
+
return `bridge detected at ${bridgeUrl()} (v${br.data.version}${n != null ? `, ${n} skills` : ''}) — its agents and skills are available through this gateway.`;
|
|
102
|
+
}
|
|
103
|
+
return `bridge not detected at ${bridgeUrl()} — 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
|
+
}
|
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.35';
|
|
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.
|
|
@@ -1013,6 +1013,13 @@ export function start(cfg = loadConfig()) {
|
|
|
1013
1013
|
server.listen(cfg.port, cfg.host, () => {
|
|
1014
1014
|
console.log(`ChatPanel Privacy Gateway v${VERSION} on http://${cfg.host}:${cfg.port}`);
|
|
1015
1015
|
console.log(` backend : ${cfg.backend}` + (cfg.backend === 'bridge' ? ` (agent: ${cfg.bridge.agent}, via ${cfg.bridge.url})` : ''));
|
|
1016
|
+
// U3: report the bridge at startup so the operator sees the unified picture without
|
|
1017
|
+
// running anything. Detect only — never force-spawn a managed service. Best-effort and
|
|
1018
|
+
// non-fatal: a probe failure just logs "not detected".
|
|
1019
|
+
import('./local-status.js')
|
|
1020
|
+
.then((m) => m.bridgePresenceNote())
|
|
1021
|
+
.then((note) => console.log(` bridge : ${note}`))
|
|
1022
|
+
.catch(() => {});
|
|
1016
1023
|
console.log(` redaction: ${cfg.redaction.tier}` + (cfg.redaction.detection?.backend && cfg.redaction.detection.backend !== 'off'
|
|
1017
1024
|
? ` + ${cfg.redaction.detection.backend} detector` : (cfg.ner?.autostart ? ' (+ NER starting…)' : '')));
|
|
1018
1025
|
// M7: a non-loopback bind exposes the gateway on the LAN, where the per-request
|