@chatpanel/gateway 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,168 @@
1
+ # PolyForm Shield License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/shield/1.0.0>
4
+
5
+ Required Notice: Copyright © 2026 ChatPanel (https://chatpanel.net)
6
+
7
+ Licensor Line of Business: ChatPanel — an AI browser side-panel, its local
8
+ bridge, and related developer tools and services (https://chatpanel.net)
9
+
10
+ ## Acceptance
11
+
12
+ In order to get any license under these terms, you must agree
13
+ to them as both strict obligations and conditions to all
14
+ your licenses.
15
+
16
+ ## Copyright License
17
+
18
+ The licensor grants you a copyright license for the
19
+ software to do everything you might do with the software
20
+ that would otherwise infringe the licensor's copyright
21
+ in it for any permitted purpose. However, you may
22
+ only distribute the software according to [Distribution
23
+ License](#distribution-license) and make changes or new works
24
+ based on the software according to [Changes and New Works
25
+ License](#changes-and-new-works-license).
26
+
27
+ ## Distribution License
28
+
29
+ The licensor grants you an additional copyright license to
30
+ distribute copies of the software. Your license to distribute
31
+ covers distributing the software with changes and new works
32
+ permitted by [Changes and New Works License](#changes-and-new-works-license).
33
+
34
+ ## Notices
35
+
36
+ You must ensure that anyone who gets a copy of any part of
37
+ the software from you also gets a copy of these terms or the
38
+ URL for them above, as well as copies of any plain-text lines
39
+ beginning with `Required Notice:` that the licensor provided
40
+ with the software. For example:
41
+
42
+ > Required Notice: Copyright © 2026 ChatPanel (https://chatpanel.net)
43
+
44
+ ## Changes and New Works License
45
+
46
+ The licensor grants you an additional copyright license to
47
+ make changes and new works based on the software for any
48
+ permitted purpose.
49
+
50
+ ## Patent License
51
+
52
+ The licensor grants you a patent license for the software that
53
+ covers patent claims the licensor can license, or becomes able
54
+ to license, that you would infringe by using the software.
55
+
56
+ ## Noncompete
57
+
58
+ Any purpose is a permitted purpose, except for providing any
59
+ product that competes with the software or any product the
60
+ licensor or any of its affiliates provides using the software.
61
+
62
+ ## Competition
63
+
64
+ Goods and services compete even when they provide functionality
65
+ through different kinds of interfaces or for different technical
66
+ platforms. Applications can compete with services, libraries
67
+ with plugins, frameworks with development tools, and so on,
68
+ even if they're written in different programming languages
69
+ or for different computer architectures. Goods and services
70
+ compete even when provided free of charge. If you market a
71
+ product as a practical substitute for the software or another
72
+ product, it definitely competes.
73
+
74
+ ## New Products
75
+
76
+ If you are using the software to provide a product that does
77
+ not compete, but the licensor or any of its affiliates brings
78
+ your product into competition by providing a new version of
79
+ the software or another product using the software, you may
80
+ continue using versions of the software available under these
81
+ terms beforehand to provide your competing product, but not
82
+ any later versions.
83
+
84
+ ## Discontinued Products
85
+
86
+ You may begin using the software to compete with a product
87
+ or service that the licensor or any of its affiliates has
88
+ stopped providing, unless the licensor includes a plain-text
89
+ line beginning with `Licensor Line of Business:` with the
90
+ software that mentions that line of business. For example:
91
+
92
+ > Licensor Line of Business: ChatPanel — an AI browser side-panel, its local
93
+ > bridge, and related developer tools and services (https://chatpanel.net)
94
+
95
+ ## Sales of Business
96
+
97
+ If the licensor or any of its affiliates sells a line of
98
+ business developing the software or using the software
99
+ to provide a product, the buyer can also enforce
100
+ Noncompete for that product.
101
+
102
+ ## Fair Use
103
+
104
+ You may have "fair use" rights for the software under the
105
+ law. These terms do not limit them.
106
+
107
+ ## No Other Rights
108
+
109
+ These terms do not allow you to sublicense or transfer any of
110
+ your licenses to anyone else, or prevent the licensor from
111
+ granting licenses to anyone else. These terms do not imply
112
+ any other licenses.
113
+
114
+ ## Patent Defense
115
+
116
+ If you make any written claim that the software infringes or
117
+ contributes to infringement of any patent, your patent license
118
+ for the software granted under these terms ends immediately. If
119
+ your company makes such a claim, your patent license ends
120
+ immediately for work on behalf of your company.
121
+
122
+ ## Violations
123
+
124
+ The first time you are notified in writing that you have
125
+ violated any of these terms, or done anything with the software
126
+ not covered by your licenses, your licenses can nonetheless
127
+ continue if you come into full compliance with these terms,
128
+ and take practical steps to correct past violations, within
129
+ 32 days of receiving notice. Otherwise, all your licenses
130
+ end immediately.
131
+
132
+ ## No Liability
133
+
134
+ ***As far as the law allows, the software comes as is, without
135
+ any warranty or condition, and the licensor will not be liable
136
+ to you for any damages arising out of these terms or the use
137
+ or nature of the software, under any kind of legal claim.***
138
+
139
+ ## Definitions
140
+
141
+ The **licensor** is the individual or entity offering these
142
+ terms, and the **software** is the software the licensor makes
143
+ available under these terms.
144
+
145
+ A **product** can be a good or service, or a combination
146
+ of them.
147
+
148
+ **You** refers to the individual or entity agreeing to these
149
+ terms.
150
+
151
+ **Your company** is any legal entity, sole proprietorship,
152
+ or other kind of organization that you work for, plus all
153
+ its affiliates.
154
+
155
+ **Affiliates** means the other organizations that an
156
+ organization has control over, is under the control of, or is
157
+ under common control with.
158
+
159
+ **Control** means ownership of substantially all the assets of
160
+ an entity, or the power to direct its management and policies
161
+ by vote, contract, or otherwise. Control can be direct or
162
+ indirect.
163
+
164
+ **Your licenses** are all the licenses granted to you for the
165
+ software under these terms.
166
+
167
+ **Use** means anything you do with the software requiring one
168
+ of your licenses.
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # ChatPanel Privacy Gateway
2
+
3
+ A localhost server that puts **ChatPanel's PII redaction / pseudonymization in the
4
+ middle of two CLI agents** — so you can use the privacy features outside the
5
+ ChatPanel extension. Point [opencode](https://opencode.ai) / pi at the gateway,
6
+ and it drives **codex / Claude Code behind your existing subscription login**
7
+ (via the [bridge](https://github.com/chatpanel/chatpanel-bridge)), redacting on
8
+ the way out and restoring on the way back. The model only ever sees opaque
9
+ placeholders like `[[PERSON_1]]` / `[[EMAIL_2]]` — **the real values never leave
10
+ your machine.**
11
+
12
+ ```
13
+ opencode / pi (configured with a custom provider → the gateway)
14
+ │ baseURL → http://127.0.0.1:4320/v1
15
+
16
+ ┌──────────────────────────────────────────────┐
17
+ │ ChatPanel Privacy Gateway │
18
+ │ 1. detect + redact → [[PERSON_1]] … │
19
+ │ 2. drive the agent behind your login │
20
+ │ 3. restore placeholders in the reply │
21
+ └──────────────────────────────────────────────┘
22
+ │ POST /chat (bridge: subscription-authed CLI)
23
+
24
+ chatpanel-bridge ──spawns──▶ codex / claude (your ChatGPT / enterprise / Claude login)
25
+ ```
26
+
27
+ Two backends (config `backend`):
28
+
29
+ - **`bridge`** (default) — drive the bridge's subscription-authed CLI agents
30
+ (`codex` / `claude` / `opencode` / `pi`). No API keys, uses your login. This is
31
+ the "privacy bridge between two agents" path above.
32
+ - **`api`** — forward redacted traffic to a native OpenAI/Anthropic-compatible
33
+ endpoint (local models, BYO keys). The client's own auth header passes through;
34
+ the gateway stores no keys.
35
+
36
+ The redaction engine is the **same code** the ChatPanel extension runs — the
37
+ [`chatpanel-pii`](https://github.com/chatpanel/chatpanel-pii) package is the
38
+ single source of truth, so a privacy feature added once is shared everywhere.
39
+
40
+ ## Quick start (bridge backend)
41
+
42
+ You need the [ChatPanel bridge](https://github.com/chatpanel/chatpanel-bridge)
43
+ running and logged into codex/claude (the same bridge the extension uses).
44
+
45
+ ```bash
46
+ npm install -g chatpanel-gateway
47
+ chatpanel-gateway
48
+ # → ChatPanel Privacy Gateway v0.1.0 on http://127.0.0.1:4320
49
+ # backend : bridge (agent: codex, via http://127.0.0.1:4319)
50
+ ```
51
+
52
+ Then point your front-end agent at it. **opencode** (`opencode.json`):
53
+
54
+ ```jsonc
55
+ {
56
+ "provider": {
57
+ "chatpanel": {
58
+ "npm": "@ai-sdk/openai-compatible",
59
+ "options": { "baseURL": "http://127.0.0.1:4320/v1" },
60
+ "models": { "codex": {}, "claude": {} } // selects the agent behind the gateway
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ Now opencode talks to codex **through** the gateway — every prompt is redacted
67
+ before codex sees it, and the reply is restored before opencode renders it. The
68
+ request's `model` (`codex`/`claude`/`opencode`/`pi`) picks which agent the bridge
69
+ drives; otherwise the configured default (`codex`) is used.
70
+
71
+ ### Using the api backend instead (local models / BYO keys)
72
+
73
+ Set `backend: "api"` (see config) and the gateway forwards redacted traffic to a
74
+ real provider endpoint, passing your `Authorization` / `x-api-key` through:
75
+
76
+ ```bash
77
+ export OPENAI_BASE_URL=http://127.0.0.1:4320/v1 # OpenAI / codex / aider / cursor
78
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:4320 # Claude Code / Anthropic SDK
79
+ ```
80
+
81
+ ## Name/org redaction is built in (bundled NER)
82
+
83
+ Deterministic redaction (emails, phones, cards, SSNs, API keys, IPs) needs no
84
+ setup. To also blind **names, organizations and locations**, the gateway bundles
85
+ a local spaCy NER server under [`ner/`](ner) and — with `ner.autostart` on (the
86
+ default) — **launches it for you on startup** and switches redaction to the
87
+ `full` tier once it's healthy. First run creates a venv and downloads the model
88
+ (needs `python3`); it's fail-open, so if Python isn't set up the gateway just runs
89
+ deterministic-only.
90
+
91
+ Prefer a local LLM or an external NER service? Set `redaction.detection` yourself
92
+ and the gateway won't autostart the bundled one.
93
+
94
+ ## Configuration
95
+
96
+ Precedence: defaults < `gateway.config.json` (or `$CHATPANEL_GATEWAY_CONFIG`) <
97
+ env vars. See [`gateway.config.example.json`](gateway.config.example.json).
98
+
99
+ | Key | Env | Default | Meaning |
100
+ |-----|-----|---------|---------|
101
+ | `backend` | — | `bridge` | `bridge` (drive CLI agents via login) or `api` (forward to a provider) |
102
+ | `bridge.url` | — | `http://127.0.0.1:4319` | the ChatPanel bridge |
103
+ | `bridge.agent` | — | `codex` | default agent the bridge drives |
104
+ | `bridge.token` | — | _(auto)_ | bridge bearer token; empty = read `~/.chatpanel/bridge-token` |
105
+ | `host` / `port` | `CHATPANEL_GATEWAY_HOST` / `_PORT` | `127.0.0.1` / `4320` | bind address |
106
+ | `upstreams.openai.baseUrl` | `OPENAI_BASE_URL` | `https://api.openai.com` | api backend only |
107
+ | `upstreams.anthropic.baseUrl` | `ANTHROPIC_BASE_URL` | `https://api.anthropic.com` | api backend only |
108
+ | `redaction.tier` | `CHATPANEL_REDACTION_TIER` | `basic` | `basic` (regex) or `full` (+ NER + dictionary) |
109
+ | `redaction.detection` | — | _(auto via NER)_ | local detector; set to override the bundled one |
110
+ | `redaction.dictionary` | — | `[]` | custom `{ value\|pattern, type, alias? }` entries |
111
+ | `ner.autostart` / `ner.port` | — | `true` / `9009` | launch + wire the bundled spaCy NER |
112
+
113
+ ## Endpoints
114
+
115
+ | Route | Behavior |
116
+ |-------|----------|
117
+ | `GET /health` | `{ ok, version, backend, tier }` |
118
+ | `GET /v1/models` | the agent(s) this gateway exposes |
119
+ | `POST /v1/chat/completions` | OpenAI protocol — redact → backend → restore |
120
+ | `POST /v1/responses` | OpenAI Responses protocol (Codex) |
121
+ | `POST /v1/messages` | Anthropic protocol (Claude Code) |
122
+
123
+ Streaming (SSE) is supported on all three: placeholders are restored on the fly,
124
+ holding back a tail so a token split across chunks (`[[PER` … `SON_1]]`) still
125
+ restores cleanly.
126
+
127
+ ## How it fits with ChatPanel
128
+
129
+ The [extension](https://github.com/chatpanel/chatpanel-extension) redacts inside
130
+ the browser; the [bridge](https://github.com/chatpanel/chatpanel-bridge) lets the
131
+ browser drive local CLI agents. This gateway reuses the bridge to put the **same
132
+ redaction engine** ([`chatpanel-pii`](https://github.com/chatpanel/chatpanel-pii))
133
+ in front of *any* agent — so non-browser tools get the privacy too, and the
134
+ agent's own multi-turn loop is blinded, not just the first prompt.
135
+
136
+ ## Caveats
137
+
138
+ - **Reversibility** is best-effort: if the model paraphrases a placeholder instead
139
+ of echoing it, that one reference shows the token. The privacy guarantee (the
140
+ real value never left the device) always holds.
141
+ - A dictionary **alias** is a *permanent* pseudonym — the agent sees the alias,
142
+ not the original, by design.
143
+ - **Code edits**: redacting values that appear inside source can affect round-trip
144
+ edits. The default tier touches only structured secrets and (in `full`) detected
145
+ entities — keep your dictionary prose-focused.
146
+
147
+ ## License
148
+
149
+ Source-available under the same license as the ChatPanel extension and bridge —
150
+ see [LICENSE](LICENSE).
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ // CLI entry for the ChatPanel Privacy Gateway.
3
+ //
4
+ // chatpanel-gateway start the gateway (foreground)
5
+ // chatpanel-gateway --install register login auto-start + start now
6
+ // chatpanel-gateway --uninstall remove login auto-start
7
+ // chatpanel-gateway --status is auto-start registered?
8
+ // chatpanel-gateway --version print version
9
+ //
10
+ // Config comes from gateway.config.json / env (see src/config.js).
11
+ import { start, VERSION } from '../src/server.js';
12
+ import { installService, uninstallService, serviceStatus } from '../src/service.js';
13
+
14
+ const arg = process.argv[2];
15
+
16
+ try {
17
+ switch (arg) {
18
+ case '--version':
19
+ case '-v':
20
+ console.log(VERSION);
21
+ break;
22
+ case '--install':
23
+ installService();
24
+ console.log('ChatPanel Privacy Gateway: installed login auto-start and started it.');
25
+ break;
26
+ case '--uninstall':
27
+ uninstallService();
28
+ console.log('ChatPanel Privacy Gateway: removed login auto-start.');
29
+ break;
30
+ case '--status':
31
+ console.log(serviceStatus() ? 'installed (auto-start registered)' : 'not installed');
32
+ break;
33
+ case undefined:
34
+ start();
35
+ break;
36
+ default:
37
+ console.error(`unknown option: ${arg}\nUsage: chatpanel-gateway [--install|--uninstall|--status|--version]`);
38
+ process.exit(2);
39
+ }
40
+ } catch (e) {
41
+ console.error(`error: ${e.message}`);
42
+ process.exit(1);
43
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "host": "127.0.0.1",
3
+ "port": 4320,
4
+
5
+ "backend": "bridge",
6
+ "bridge": {
7
+ "url": "http://127.0.0.1:4319",
8
+ "agent": "codex",
9
+ "token": ""
10
+ },
11
+
12
+ "upstreams": {
13
+ "openai": { "baseUrl": "https://api.openai.com" },
14
+ "anthropic": { "baseUrl": "https://api.anthropic.com" }
15
+ },
16
+
17
+ "redaction": {
18
+ "tier": "full",
19
+ "redactSystem": true,
20
+ "dictionary": [
21
+ { "value": "Acme Corp", "type": "ORG" },
22
+ { "value": "Project Atlas", "alias": "Project Nimbus" }
23
+ ]
24
+ },
25
+
26
+ "ner": {
27
+ "autostart": true,
28
+ "port": 9009,
29
+ "enableFullTier": true
30
+ },
31
+
32
+ "logRequests": true
33
+ }
package/ner/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # ChatPanel — local NER helper (spaCy)
2
+
3
+ A ~30-line local service that lets ChatPanel **auto-redact names, organizations,
4
+ and locations** before anything is sent to a chat model. Detection runs entirely
5
+ on your machine; the model only ever sees placeholders like `[[PERSON_1]]`.
6
+
7
+ ChatPanel's redaction contract is simple — any local HTTP service works:
8
+
9
+ ```
10
+ POST /ner { "text": "..." }
11
+ → { "entities": [ { "value": "Alex", "type": "PERSON" }, ... ] }
12
+ ```
13
+
14
+ (spaCy's `{ "ents": [{ "text", "label" }] }` shape is also accepted, as is a
15
+ local OpenAI-compatible LLM — see "Other detectors" below.)
16
+
17
+ ## Quick start
18
+
19
+ Most machines block installing Python packages globally, so use a virtual env:
20
+
21
+ ```bash
22
+ cd helpers/ner-server
23
+
24
+ python3 -m venv .venv
25
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
26
+ pip install -r requirements.txt
27
+ python -m spacy download en_core_web_sm
28
+
29
+ uvicorn server:app --port 9009 # the file is server.py → import path "server:app"
30
+ ```
31
+
32
+ …or just run the bundled script (does all of the above):
33
+
34
+ ```bash
35
+ ./run.sh # PORT=9100 ./run.sh to change the port
36
+ ```
37
+
38
+ Check it's up: `curl http://127.0.0.1:9009/health`
39
+
40
+ ## Point ChatPanel at it
41
+
42
+ **Settings → Privacy** (or the 🛡 button in the chat composer):
43
+
44
+ | Field | Value |
45
+ |------|-------|
46
+ | Redaction | **On — + AI detection** |
47
+ | Detector | **Local NER service (spaCy / Presidio)** |
48
+ | Detector URL | `http://127.0.0.1:9009/ner` |
49
+ | Redact types | People / Organizations / Locations / Numbers (your choice) |
50
+
51
+ Now `my name is Alex from Denver` is sent to the model as
52
+ `my name is [[PERSON_1]] from [[LOCATION_1]]`, and the reply is restored to the
53
+ real values in your view.
54
+
55
+ > Turning **Locations** off keeps city names readable (useful for "how far is X
56
+ > from Y" questions) while still redacting people.
57
+
58
+ ## Accuracy vs. speed
59
+
60
+ `en_core_web_sm` is small and fast (good default). For fewer misses:
61
+
62
+ ```bash
63
+ python -m spacy download en_core_web_md # or en_core_web_trf (best, heavier)
64
+ ```
65
+
66
+ then change `MODEL` in `server.py`. Small models can over-tag short acronyms as
67
+ `ORG`; ChatPanel drops noisy short numerics/dates automatically and lets you turn
68
+ off whole categories.
69
+
70
+ ## Other detectors
71
+
72
+ ChatPanel doesn't care what's behind the URL, as long as it returns the entity
73
+ shape above. Drop-in alternatives:
74
+
75
+ - **Microsoft Presidio** — `presidio-analyzer` behind a small FastAPI wrapper
76
+ (returns `{ "results": [...] }`, also accepted).
77
+ - **A local LLM** — set the detector to **Local LLM (OpenAI-compatible)** and
78
+ point it at Ollama / LM Studio / llama.cpp (e.g. `http://127.0.0.1:11434`);
79
+ ChatPanel prompts it for strict JSON entities. Slower than spaCy, no extra
80
+ service to run if you already have a local model. See `../local-model.md`.
81
+
82
+ Everything is local and latency-guarded (cached + timed out + fail-open): if the
83
+ detector is slow or down, ChatPanel falls back to deterministic redaction so chat
84
+ never blocks.
@@ -0,0 +1,3 @@
1
+ fastapi
2
+ uvicorn[standard]
3
+ spacy
package/ner/run.sh ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # One-shot: create the venv (if needed), install deps + the spaCy model, and serve.
3
+ # Usage: ./run.sh (defaults to port 9009)
4
+ # PORT=9100 ./run.sh
5
+ set -euo pipefail
6
+ cd "$(dirname "$0")"
7
+
8
+ if [ ! -d .venv ]; then
9
+ echo "→ creating virtual env (.venv)…"
10
+ python3 -m venv .venv
11
+ fi
12
+ # shellcheck disable=SC1091
13
+ source .venv/bin/activate
14
+
15
+ echo "→ installing dependencies…"
16
+ pip install -q --upgrade pip
17
+ pip install -q -r requirements.txt
18
+ python -c "import en_core_web_sm" >/dev/null 2>&1 || python -m spacy download en_core_web_sm
19
+
20
+ echo "→ serving on http://127.0.0.1:${PORT:-9009}/ner (Ctrl-C to stop)"
21
+ exec uvicorn server:app --port "${PORT:-9009}"
package/ner/server.py ADDED
@@ -0,0 +1,95 @@
1
+ """
2
+ ChatPanel local NER helper — a tiny on-device entity detector for PII redaction.
3
+
4
+ ChatPanel's Privacy → "AI detection" can point at any LOCAL service that accepts
5
+ POST {"text": "..."}
6
+ and returns
7
+ {"entities": [{"value": "...", "type": "PERSON|ORG|GPE|EMAIL|PHONE|..."}]}
8
+
9
+ This wraps spaCy (people / organizations / locations) AND adds a regex pass for
10
+ the structured identifiers spaCy doesn't emit (emails, phone numbers, SSNs, cards,
11
+ IPs), so the detector is comprehensive on its own. Only the redacted placeholders
12
+ (e.g. [[PERSON_1]]) ever reach the chat model — the raw text never leaves your box.
13
+
14
+ --------------------------------------------------------------------------------
15
+ Setup (most machines block global pip installs, so use a virtual env)
16
+
17
+ python3 -m venv .venv
18
+ source .venv/bin/activate # Windows: .venv\\Scripts\\activate
19
+ pip install -r requirements.txt
20
+ python -m spacy download en_core_web_sm
21
+
22
+ Run (the file is server.py, so the uvicorn import path is "server:app")
23
+
24
+ uvicorn server:app --port 9009
25
+
26
+ Then in ChatPanel → Settings → Privacy:
27
+ Redaction : On — + AI detection
28
+ Detector : Local NER service (spaCy / Presidio)
29
+ URL : http://127.0.0.1:9009/ner
30
+
31
+ Tip: en_core_web_sm is small + fast. For better accuracy use en_core_web_md or
32
+ en_core_web_trf (download the same way, then change the load below).
33
+ --------------------------------------------------------------------------------
34
+ """
35
+ from fastapi import FastAPI
36
+
37
+ import re
38
+ import spacy
39
+
40
+ MODEL = "en_core_web_sm"
41
+ nlp = spacy.load(MODEL)
42
+
43
+ app = FastAPI(title="ChatPanel NER helper")
44
+
45
+ # spaCy's NER emits names / orgs / locations but NOT structured identifiers, so add
46
+ # a regex pass for those. (ChatPanel also catches these on-device, but emitting them
47
+ # here keeps the detector self-contained — what you test is what you get.) Order
48
+ # matters: more specific patterns run first so a card / SSN isn't re-matched as a
49
+ # phone number.
50
+ _PATTERNS = [
51
+ ("EMAIL", re.compile(r"[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}")),
52
+ ("SSN", re.compile(r"\b\d{3}-\d{2}-\d{4}\b")),
53
+ ("CREDIT_CARD", re.compile(r"\b(?:\d[ -]?){13,19}\b")),
54
+ ("IP", re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")),
55
+ ("PHONE", re.compile(r"(?<!\d)(?:\+?\d{1,2}[ .\-]?)?\(?\d{3}\)?[ .\-]?\d{3}[ .\-]?\d{4}(?!\d)")),
56
+ ]
57
+
58
+
59
+ def _regex_entities(text):
60
+ out, taken = [], []
61
+ for label, rx in _PATTERNS:
62
+ for m in rx.finditer(text):
63
+ start, end = m.start(), m.end()
64
+ if any(start < te and ts < end for ts, te in taken):
65
+ continue # span already claimed by a more specific pattern
66
+ taken.append((start, end))
67
+ out.append({"value": m.group(0).strip(), "type": label})
68
+ return out
69
+
70
+
71
+ @app.get("/health")
72
+ def health():
73
+ return {"ok": True, "model": MODEL}
74
+
75
+
76
+ @app.post("/ner")
77
+ def ner(payload: dict):
78
+ """Return spaCy entities + regex identifiers in ChatPanel's expected shape.
79
+
80
+ ChatPanel maps common labels itself (PER→PERSON, GPE→LOCATION, …) and keeps
81
+ only the categories you enable in the Privacy tab, so it's fine to return all
82
+ of them here.
83
+ """
84
+ text = (payload or {}).get("text", "") or ""
85
+ doc = nlp(text)
86
+ ents = [{"value": ent.text, "type": ent.label_} for ent in doc.ents]
87
+ ents.extend(_regex_entities(text))
88
+ # De-dup identical value+type (spaCy and a regex can both surface the same span).
89
+ seen, out = set(), []
90
+ for e in ents:
91
+ key = (e["type"], e["value"].lower())
92
+ if e["value"] and key not in seen:
93
+ seen.add(key)
94
+ out.append(e)
95
+ return {"entities": out}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@chatpanel/gateway",
3
+ "version": "0.1.0",
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
+ "type": "module",
6
+ "bin": {
7
+ "chatpanel-gateway": "bin/chatpanel-gateway.js"
8
+ },
9
+ "main": "src/server.js",
10
+ "exports": {
11
+ ".": "./src/server.js"
12
+ },
13
+ "scripts": {
14
+ "start": "node bin/chatpanel-gateway.js",
15
+ "test": "node --test",
16
+ "typecheck": "tsc -p tsconfig.json",
17
+ "build:bin": "bash scripts/build-binaries.sh"
18
+ },
19
+ "files": [
20
+ "src",
21
+ "bin",
22
+ "ner",
23
+ "gateway.config.example.json",
24
+ "LICENSE",
25
+ "README.md"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "dependencies": {
31
+ "@chatpanel/pii": "^0.1.0"
32
+ },
33
+ "homepage": "https://chatpanel.net",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/chatpanel/chatpanel-gateway.git"
37
+ },
38
+ "keywords": [
39
+ "privacy",
40
+ "pii",
41
+ "redaction",
42
+ "pseudonymization",
43
+ "llm",
44
+ "proxy",
45
+ "gateway",
46
+ "openai",
47
+ "anthropic",
48
+ "opencode",
49
+ "codex"
50
+ ],
51
+ "author": "ChatPanel (https://chatpanel.net)",
52
+ "license": "SEE LICENSE IN LICENSE"
53
+ }