@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.
@@ -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.34",
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.34';
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