@chatpanel/gateway 0.4.1 → 0.4.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/gateway",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Local privacy gateway — 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": {
@@ -14,7 +14,9 @@
14
14
  "start": "node bin/chatpanel-gateway.js",
15
15
  "test": "node --test",
16
16
  "typecheck": "tsc -p tsconfig.json",
17
- "build:bin": "bash scripts/build-binaries.sh"
17
+ "build:bin": "bash scripts/build-binaries.sh",
18
+ "gen:ner": "node scripts/gen-ner-assets.mjs",
19
+ "prepublishOnly": "node scripts/gen-ner-assets.mjs"
18
20
  },
19
21
  "files": [
20
22
  "src",
@@ -0,0 +1,8 @@
1
+ // GENERATED by scripts/gen-ner-assets.mjs — do NOT edit. Edit ner/* and re-run.
2
+ // Base64 of the bundled spaCy NER server, embedded so a standalone binary can
3
+ // write a runnable ner/ directory to disk (the binary has no ner/ on the FS).
4
+ export const NER_ASSETS = {
5
+ "run.sh": "IyEvdXNyL2Jpbi9lbnYgYmFzaAojIE9uZS1zaG90OiBjcmVhdGUgdGhlIHZlbnYgKGlmIG5lZWRlZCksIGluc3RhbGwgZGVwcyArIHRoZSBzcGFDeSBtb2RlbCwgYW5kIHNlcnZlLgojIFVzYWdlOiAgLi9ydW4uc2ggICAgICAgIChkZWZhdWx0cyB0byBwb3J0IDkwMDkpCiMgICAgICAgICBQT1JUPTkxMDAgLi9ydW4uc2gKc2V0IC1ldW8gcGlwZWZhaWwKY2QgIiQoZGlybmFtZSAiJDAiKSIKCmlmIFsgISAtZCAudmVudiBdOyB0aGVuCiAgZWNobyAi4oaSIGNyZWF0aW5nIHZpcnR1YWwgZW52ICgudmVudinigKYiCiAgcHl0aG9uMyAtbSB2ZW52IC52ZW52CmZpCiMgc2hlbGxjaGVjayBkaXNhYmxlPVNDMTA5MQpzb3VyY2UgLnZlbnYvYmluL2FjdGl2YXRlCgplY2hvICLihpIgaW5zdGFsbGluZyBkZXBlbmRlbmNpZXPigKYiCnBpcCBpbnN0YWxsIC1xIC0tdXBncmFkZSBwaXAKcGlwIGluc3RhbGwgLXEgLXIgcmVxdWlyZW1lbnRzLnR4dApweXRob24gLWMgImltcG9ydCBlbl9jb3JlX3dlYl9zbSIgPi9kZXYvbnVsbCAyPiYxIHx8IHB5dGhvbiAtbSBzcGFjeSBkb3dubG9hZCBlbl9jb3JlX3dlYl9zbQoKZWNobyAi4oaSIHNlcnZpbmcgb24gaHR0cDovLzEyNy4wLjAuMToke1BPUlQ6LTkwMDl9L25lciAgKEN0cmwtQyB0byBzdG9wKSIKZXhlYyB1dmljb3JuIHNlcnZlcjphcHAgLS1wb3J0ICIke1BPUlQ6LTkwMDl9Igo=",
6
+ "server.py": "IiIiCkNoYXRQYW5lbCBsb2NhbCBORVIgaGVscGVyIOKAlCBhIHRpbnkgb24tZGV2aWNlIGVudGl0eSBkZXRlY3RvciBmb3IgUElJIHJlZGFjdGlvbi4KCkNoYXRQYW5lbCdzIFByaXZhY3kg4oaSICJBSSBkZXRlY3Rpb24iIGNhbiBwb2ludCBhdCBhbnkgTE9DQUwgc2VydmljZSB0aGF0IGFjY2VwdHMKICAgIFBPU1QgeyJ0ZXh0IjogIi4uLiJ9CmFuZCByZXR1cm5zCiAgICB7ImVudGl0aWVzIjogW3sidmFsdWUiOiAiLi4uIiwgInR5cGUiOiAiUEVSU09OfE9SR3xHUEV8RU1BSUx8UEhPTkV8Li4uIn1dfQoKVGhpcyB3cmFwcyBzcGFDeSAocGVvcGxlIC8gb3JnYW5pemF0aW9ucyAvIGxvY2F0aW9ucykgQU5EIGFkZHMgYSByZWdleCBwYXNzIGZvcgp0aGUgc3RydWN0dXJlZCBpZGVudGlmaWVycyBzcGFDeSBkb2Vzbid0IGVtaXQgKGVtYWlscywgcGhvbmUgbnVtYmVycywgU1NOcywgY2FyZHMsCklQcyksIHNvIHRoZSBkZXRlY3RvciBpcyBjb21wcmVoZW5zaXZlIG9uIGl0cyBvd24uIE9ubHkgdGhlIHJlZGFjdGVkIHBsYWNlaG9sZGVycwooZS5nLiBbW1BFUlNPTl8xXV0pIGV2ZXIgcmVhY2ggdGhlIGNoYXQgbW9kZWwg4oCUIHRoZSByYXcgdGV4dCBuZXZlciBsZWF2ZXMgeW91ciBib3guCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpTZXR1cCAgKG1vc3QgbWFjaGluZXMgYmxvY2sgZ2xvYmFsIHBpcCBpbnN0YWxscywgc28gdXNlIGEgdmlydHVhbCBlbnYpCgogICAgcHl0aG9uMyAtbSB2ZW52IC52ZW52CiAgICBzb3VyY2UgLnZlbnYvYmluL2FjdGl2YXRlICAgICAgICAgICMgV2luZG93czogLnZlbnZcXFNjcmlwdHNcXGFjdGl2YXRlCiAgICBwaXAgaW5zdGFsbCAtciByZXF1aXJlbWVudHMudHh0CiAgICBweXRob24gLW0gc3BhY3kgZG93bmxvYWQgZW5fY29yZV93ZWJfc20KClJ1biAgKHRoZSBmaWxlIGlzIHNlcnZlci5weSwgc28gdGhlIHV2aWNvcm4gaW1wb3J0IHBhdGggaXMgInNlcnZlcjphcHAiKQoKICAgIHV2aWNvcm4gc2VydmVyOmFwcCAtLXBvcnQgOTAwOQoKVGhlbiBpbiBDaGF0UGFuZWwg4oaSIFNldHRpbmdzIOKGkiBQcml2YWN5OgogICAgUmVkYWN0aW9uIDogT24g4oCUICsgQUkgZGV0ZWN0aW9uCiAgICBEZXRlY3RvciAgOiBMb2NhbCBORVIgc2VydmljZSAoc3BhQ3kgLyBQcmVzaWRpbykKICAgIFVSTCAgICAgICA6IGh0dHA6Ly8xMjcuMC4wLjE6OTAwOS9uZXIKClRpcDogZW5fY29yZV93ZWJfc20gaXMgc21hbGwgKyBmYXN0LiBGb3IgYmV0dGVyIGFjY3VyYWN5IHVzZSBlbl9jb3JlX3dlYl9tZCBvcgplbl9jb3JlX3dlYl90cmYgKGRvd25sb2FkIHRoZSBzYW1lIHdheSwgdGhlbiBjaGFuZ2UgdGhlIGxvYWQgYmVsb3cpLgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoiIiIKZnJvbSBmYXN0YXBpIGltcG9ydCBGYXN0QVBJCgppbXBvcnQgcmUKaW1wb3J0IHNwYWN5CgpNT0RFTCA9ICJlbl9jb3JlX3dlYl9zbSIKbmxwID0gc3BhY3kubG9hZChNT0RFTCkKCmFwcCA9IEZhc3RBUEkodGl0bGU9IkNoYXRQYW5lbCBORVIgaGVscGVyIikKCiMgc3BhQ3kncyBORVIgZW1pdHMgbmFtZXMgLyBvcmdzIC8gbG9jYXRpb25zIGJ1dCBOT1Qgc3RydWN0dXJlZCBpZGVudGlmaWVycywgc28gYWRkCiMgYSByZWdleCBwYXNzIGZvciB0aG9zZS4gKENoYXRQYW5lbCBhbHNvIGNhdGNoZXMgdGhlc2Ugb24tZGV2aWNlLCBidXQgZW1pdHRpbmcgdGhlbQojIGhlcmUga2VlcHMgdGhlIGRldGVjdG9yIHNlbGYtY29udGFpbmVkIOKAlCB3aGF0IHlvdSB0ZXN0IGlzIHdoYXQgeW91IGdldC4pIE9yZGVyCiMgbWF0dGVyczogbW9yZSBzcGVjaWZpYyBwYXR0ZXJucyBydW4gZmlyc3Qgc28gYSBjYXJkIC8gU1NOIGlzbid0IHJlLW1hdGNoZWQgYXMgYQojIHBob25lIG51bWJlci4KX1BBVFRFUk5TID0gWwogICAgKCJFTUFJTCIsIHJlLmNvbXBpbGUociJbQS1aYS16MC05Ll8lK1wtXStAW0EtWmEtejAtOS5cLV0rXC5bQS1aYS16XXsyLH0iKSksCiAgICAoIlNTTiIsIHJlLmNvbXBpbGUociJcYlxkezN9LVxkezJ9LVxkezR9XGIiKSksCiAgICAoIkNSRURJVF9DQVJEIiwgcmUuY29tcGlsZShyIlxiKD86XGRbIC1dPyl7MTMsMTl9XGIiKSksCiAgICAoIklQIiwgcmUuY29tcGlsZShyIlxiKD86XGR7MSwzfVwuKXszfVxkezEsM31cYiIpKSwKICAgICgiUEhPTkUiLCByZS5jb21waWxlKHIiKD88IVxkKSg/OlwrP1xkezEsMn1bIC5cLV0/KT9cKD9cZHszfVwpP1sgLlwtXT9cZHszfVsgLlwtXT9cZHs0fSg/IVxkKSIpKSwKXQoKCmRlZiBfcmVnZXhfZW50aXRpZXModGV4dCk6CiAgICBvdXQsIHRha2VuID0gW10sIFtdCiAgICBmb3IgbGFiZWwsIHJ4IGluIF9QQVRURVJOUzoKICAgICAgICBmb3IgbSBpbiByeC5maW5kaXRlcih0ZXh0KToKICAgICAgICAgICAgc3RhcnQsIGVuZCA9IG0uc3RhcnQoKSwgbS5lbmQoKQogICAgICAgICAgICBpZiBhbnkoc3RhcnQgPCB0ZSBhbmQgdHMgPCBlbmQgZm9yIHRzLCB0ZSBpbiB0YWtlbik6CiAgICAgICAgICAgICAgICBjb250aW51ZSAgIyBzcGFuIGFscmVhZHkgY2xhaW1lZCBieSBhIG1vcmUgc3BlY2lmaWMgcGF0dGVybgogICAgICAgICAgICB0YWtlbi5hcHBlbmQoKHN0YXJ0LCBlbmQpKQogICAgICAgICAgICBvdXQuYXBwZW5kKHsidmFsdWUiOiBtLmdyb3VwKDApLnN0cmlwKCksICJ0eXBlIjogbGFiZWx9KQogICAgcmV0dXJuIG91dAoKCkBhcHAuZ2V0KCIvaGVhbHRoIikKZGVmIGhlYWx0aCgpOgogICAgcmV0dXJuIHsib2siOiBUcnVlLCAibW9kZWwiOiBNT0RFTH0KCgpAYXBwLnBvc3QoIi9uZXIiKQpkZWYgbmVyKHBheWxvYWQ6IGRpY3QpOgogICAgIiIiUmV0dXJuIHNwYUN5IGVudGl0aWVzICsgcmVnZXggaWRlbnRpZmllcnMgaW4gQ2hhdFBhbmVsJ3MgZXhwZWN0ZWQgc2hhcGUuCgogICAgQ2hhdFBhbmVsIG1hcHMgY29tbW9uIGxhYmVscyBpdHNlbGYgKFBFUuKGklBFUlNPTiwgR1BF4oaSTE9DQVRJT04sIOKApikgYW5kIGtlZXBzCiAgICBvbmx5IHRoZSBjYXRlZ29yaWVzIHlvdSBlbmFibGUgaW4gdGhlIFByaXZhY3kgdGFiLCBzbyBpdCdzIGZpbmUgdG8gcmV0dXJuIGFsbAogICAgb2YgdGhlbSBoZXJlLgogICAgIiIiCiAgICB0ZXh0ID0gKHBheWxvYWQgb3Ige30pLmdldCgidGV4dCIsICIiKSBvciAiIgogICAgZG9jID0gbmxwKHRleHQpCiAgICBlbnRzID0gW3sidmFsdWUiOiBlbnQudGV4dCwgInR5cGUiOiBlbnQubGFiZWxffSBmb3IgZW50IGluIGRvYy5lbnRzXQogICAgZW50cy5leHRlbmQoX3JlZ2V4X2VudGl0aWVzKHRleHQpKQogICAgIyBEZS1kdXAgaWRlbnRpY2FsIHZhbHVlK3R5cGUgKHNwYUN5IGFuZCBhIHJlZ2V4IGNhbiBib3RoIHN1cmZhY2UgdGhlIHNhbWUgc3BhbikuCiAgICBzZWVuLCBvdXQgPSBzZXQoKSwgW10KICAgIGZvciBlIGluIGVudHM6CiAgICAgICAga2V5ID0gKGVbInR5cGUiXSwgZVsidmFsdWUiXS5sb3dlcigpKQogICAgICAgIGlmIGVbInZhbHVlIl0gYW5kIGtleSBub3QgaW4gc2VlbjoKICAgICAgICAgICAgc2Vlbi5hZGQoa2V5KQogICAgICAgICAgICBvdXQuYXBwZW5kKGUpCiAgICByZXR1cm4geyJlbnRpdGllcyI6IG91dH0K",
7
+ "requirements.txt": "ZmFzdGFwaQp1dmljb3JuW3N0YW5kYXJkXQpzcGFjeQo=",
8
+ };
package/src/ner.js CHANGED
@@ -9,10 +9,32 @@
9
9
  import { spawn } from 'node:child_process';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import { dirname, join } from 'node:path';
12
- import { existsSync } from 'node:fs';
12
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from 'node:fs';
13
13
  import os from 'node:os';
14
+ import { NER_ASSETS } from './ner-assets.js';
14
15
 
15
- const NER_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'ner');
16
+ // Where the NER server lives ON DISK. We always run from a real, writable user dir
17
+ // (~/.chatpanel/ner) rather than the package's ner/ — because a STANDALONE BINARY
18
+ // has no ner/ on the filesystem at all (it's inside the SEA snapshot), and a global
19
+ // npm dir may be read-only. The venv + installed deps persist here across restarts.
20
+ function materializeNerDir() {
21
+ const dir = join(os.homedir(), '.chatpanel', 'ner');
22
+ try {
23
+ mkdirSync(dir, { recursive: true });
24
+ for (const [name, b64] of Object.entries(NER_ASSETS)) {
25
+ const target = join(dir, name);
26
+ const want = Buffer.from(b64, 'base64');
27
+ // Refresh the scripts if changed (pick up updates), but never touch .venv.
28
+ const have = existsSync(target) ? readFileSync(target) : null;
29
+ if (!have || !have.equals(want)) writeFileSync(target, want);
30
+ if (name.endsWith('.sh')) { try { chmodSync(target, 0o755); } catch { /* best effort */ } }
31
+ }
32
+ return dir;
33
+ } catch (e) {
34
+ console.log(`[ner] could not materialize NER dir at ${dir} (${e.message})`);
35
+ return null;
36
+ }
37
+ }
16
38
 
17
39
  // A login service (LaunchAgent / systemd) inherits a MINIMAL PATH — not your
18
40
  // shell's — so `bash` and a pyenv/homebrew `python3` aren't found. Resolve bash
@@ -86,10 +108,14 @@ export function startNer(cfg) {
86
108
  // 1) Adopt an existing NER already serving on the port (e.g. the user's own).
87
109
  if (await nerReachable(port, ac.signal)) { wire('using existing NER'); return; }
88
110
 
89
- // 2) Otherwise launch the bundled spaCy server.
111
+ // 2) Otherwise launch the bundled spaCy server — materializing run.sh +
112
+ // server.py + requirements.txt to a real on-disk dir first (the binary has
113
+ // no ner/ on the FS). run.sh then creates the venv, installs deps, and runs.
114
+ const nerDir = materializeNerDir();
115
+ if (!nerDir) { console.log('[ner] no writable NER dir — deterministic redaction only'); return; }
90
116
  try {
91
117
  child = spawn(resolveBash(), ['run.sh'], {
92
- cwd: NER_DIR,
118
+ cwd: nerDir,
93
119
  env: {
94
120
  ...process.env,
95
121
  PORT: String(port),
@@ -129,7 +155,7 @@ export function startNer(cfg) {
129
155
  if (await nerReachable(port, ac.signal)) { wire('ready'); return; }
130
156
  await sleep(1000);
131
157
  }
132
- if (!stopped) console.log('[ner] not ready after 300s — continuing deterministic-only (run ./ner/run.sh manually to debug).');
158
+ if (!stopped) console.log(`[ner] not ready after 300s — continuing deterministic-only (run ${join(os.homedir(), '.chatpanel', 'ner', 'run.sh')} manually to debug).`);
133
159
  })();
134
160
 
135
161
  const stop = () => {
package/src/server.js CHANGED
@@ -33,7 +33,7 @@ import * as openai from './openai.js';
33
33
  import * as responses from './responses.js';
34
34
  import * as anthropic from './anthropic.js';
35
35
 
36
- export const VERSION = '0.4.1';
36
+ export const VERSION = '0.4.2';
37
37
 
38
38
  const KNOWN_AGENTS = new Set(['codex', 'claude', 'opencode', 'pi', 'kiro', 'antigravity']);
39
39