mancode 0.6.3 → 0.6.5

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.
Files changed (34) hide show
  1. package/README.en.md +87 -10
  2. package/README.md +60 -10
  3. package/dist/{chunk-NG72XZ3R.js → chunk-E2K22WYH.js} +2323 -1008
  4. package/dist/chunk-E2K22WYH.js.map +1 -0
  5. package/dist/chunk-GI24QVXE.js +1099 -0
  6. package/dist/chunk-GI24QVXE.js.map +1 -0
  7. package/dist/chunk-IRZQYMHD.js +334 -0
  8. package/dist/chunk-IRZQYMHD.js.map +1 -0
  9. package/dist/chunk-THOE33LU.js +1124 -0
  10. package/dist/chunk-THOE33LU.js.map +1 -0
  11. package/dist/{chunk-RDPFQODS.js → chunk-WJ6WARGG.js} +495 -250
  12. package/dist/chunk-WJ6WARGG.js.map +1 -0
  13. package/dist/chunk-WRBNOPFA.js +1235 -0
  14. package/dist/chunk-WRBNOPFA.js.map +1 -0
  15. package/dist/cli.js +22917 -21503
  16. package/dist/cli.js.map +1 -1
  17. package/dist/gateway/worker.d.ts +2 -0
  18. package/dist/gateway/worker.js +181 -0
  19. package/dist/gateway/worker.js.map +1 -0
  20. package/dist/privacy-gateway-Q4GD2JXF.js +20 -0
  21. package/dist/store-GSLSLZ7D.js +11 -0
  22. package/dist/{v3-adapter-7KIOYYWS.js → v3-adapter-T3IKK3LU.js} +4 -2
  23. package/dist/v3-adapter-T3IKK3LU.js.map +1 -0
  24. package/docs/README.md +34 -0
  25. package/docs/privacy-guide.md +76 -0
  26. package/docs/privacy-implementation-plan.md +101 -0
  27. package/docs/privacy-rule-sources.md +22 -0
  28. package/docs/privacy-upstream-license.txt +661 -0
  29. package/package.json +8 -3
  30. package/dist/chunk-NG72XZ3R.js.map +0 -1
  31. package/dist/chunk-RDPFQODS.js.map +0 -1
  32. package/dist/store-C3G3HN3N.js +0 -9
  33. /package/dist/{store-C3G3HN3N.js.map → privacy-gateway-Q4GD2JXF.js.map} +0 -0
  34. /package/dist/{v3-adapter-7KIOYYWS.js.map → store-GSLSLZ7D.js.map} +0 -0
@@ -0,0 +1,1124 @@
1
+ import {
2
+ GatewayError,
3
+ SseDecoder,
4
+ encodeSse,
5
+ gatewayErrorCode
6
+ } from "./chunk-GI24QVXE.js";
7
+ import {
8
+ readProjectRuntimeContext
9
+ } from "./chunk-WRBNOPFA.js";
10
+ import {
11
+ DEFAULT_RULE_IDS,
12
+ RULESET_VERSION
13
+ } from "./chunk-IRZQYMHD.js";
14
+
15
+ // src/commands/privacy-gateway.ts
16
+ import { execFile } from "child_process";
17
+ import { randomBytes as randomBytes2 } from "crypto";
18
+ import { rm as rm2 } from "fs/promises";
19
+ import path2 from "path";
20
+ import { promisify } from "util";
21
+
22
+ // src/gateway/config.ts
23
+ import {
24
+ createHash,
25
+ createHmac,
26
+ randomBytes,
27
+ timingSafeEqual
28
+ } from "crypto";
29
+ import { constants } from "fs";
30
+ import {
31
+ chmod,
32
+ lstat,
33
+ mkdir,
34
+ open,
35
+ readdir,
36
+ realpath,
37
+ rename,
38
+ rm,
39
+ writeFile
40
+ } from "fs/promises";
41
+ import os from "os";
42
+ import path from "path";
43
+ var UPSTREAMS = {
44
+ openai: {
45
+ origin: "https://api.openai.com",
46
+ protocol: "responses",
47
+ envKey: "OPENAI_API_KEY"
48
+ },
49
+ anthropic: {
50
+ origin: "https://api.anthropic.com",
51
+ protocol: "messages",
52
+ envKey: "ANTHROPIC_API_KEY"
53
+ }
54
+ };
55
+ var hashValue = (value) => createHash("sha256").update(value).digest("hex");
56
+ async function gatewayLocation(root) {
57
+ const resolved = await realpath(root);
58
+ let workspaceId = null;
59
+ let checkoutId = null;
60
+ let initialized = false;
61
+ try {
62
+ await lstat(path.join(resolved, ".mancode"));
63
+ initialized = true;
64
+ } catch (error) {
65
+ if (error.code !== "ENOENT")
66
+ throw new GatewayError("MANCODE_GATEWAY_SCOPE_UNAVAILABLE");
67
+ }
68
+ if (initialized) {
69
+ try {
70
+ const runtime = await readProjectRuntimeContext(resolved);
71
+ workspaceId = runtime.workspaceId;
72
+ checkoutId = runtime.checkoutId;
73
+ } catch {
74
+ throw new GatewayError("MANCODE_GATEWAY_SCOPE_UNAVAILABLE");
75
+ }
76
+ }
77
+ const physical = await lstat(resolved);
78
+ const scope = {
79
+ principal: hashValue(`${os.homedir()}:${process.getuid?.() ?? "user"}`),
80
+ checkout: hashValue(
81
+ JSON.stringify({
82
+ root: resolved,
83
+ device: physical.dev,
84
+ inode: physical.ino,
85
+ created: physical.birthtimeMs,
86
+ workspaceId,
87
+ checkoutId
88
+ })
89
+ ),
90
+ workspaceId,
91
+ checkoutId
92
+ };
93
+ return {
94
+ directory: path.join(
95
+ os.homedir(),
96
+ ".mancode",
97
+ "privacy-gateway",
98
+ scope.checkout
99
+ ),
100
+ scope
101
+ };
102
+ }
103
+ function assertKeys(value, expected) {
104
+ if (Object.keys(value).some((key) => !expected.includes(key)) || expected.some((key) => !(key in value)))
105
+ throw new GatewayError("MANCODE_GATEWAY_CONFIG_INVALID");
106
+ }
107
+ function parseGatewayConfig(value, scope) {
108
+ if (!value || typeof value !== "object" || Array.isArray(value))
109
+ throw new GatewayError("MANCODE_GATEWAY_CONFIG_INVALID");
110
+ const config = value;
111
+ assertKeys(config, [
112
+ "schemaVersion",
113
+ "enabled",
114
+ "upstreamId",
115
+ "envKey",
116
+ "clientHost",
117
+ "accessToken",
118
+ "port",
119
+ "scope",
120
+ "ruleIds",
121
+ "rulesetVersion"
122
+ ]);
123
+ if (config.schemaVersion !== 1 || typeof config.enabled !== "boolean" || !(config.upstreamId === "openai" || config.upstreamId === "anthropic") || typeof config.envKey !== "string" || !/^[A-Z][A-Z0-9_]{0,63}$/.test(config.envKey) || typeof config.accessToken !== "string" || !/^[a-f0-9]{64}$/.test(config.accessToken) || !Number.isInteger(config.port) || Number(config.port) < 1024 || Number(config.port) > 65535 || !["codex-cli/0.153.4", "claude-code/2.1.142", "unverified"].includes(
124
+ String(config.clientHost)
125
+ ) || config.rulesetVersion !== RULESET_VERSION || !Array.isArray(config.ruleIds) || config.ruleIds.length === 0 || config.ruleIds.some(
126
+ (id) => typeof id !== "string" || !DEFAULT_RULE_IDS.includes(id)
127
+ ) || new Set(config.ruleIds).size !== config.ruleIds.length)
128
+ throw new GatewayError("MANCODE_GATEWAY_CONFIG_INVALID");
129
+ const binding = config.scope;
130
+ if (config.clientHost === "codex-cli/0.153.4" && config.upstreamId !== "openai" || config.clientHost === "claude-code/2.1.142" && config.upstreamId !== "anthropic")
131
+ throw new GatewayError("MANCODE_GATEWAY_HOST_UPSTREAM_MISMATCH");
132
+ if (binding?.principal !== scope.principal || binding?.checkout !== scope.checkout || binding?.workspaceId !== scope.workspaceId || binding?.checkoutId !== scope.checkoutId || Object.keys(binding).length !== 4)
133
+ throw new GatewayError("MANCODE_GATEWAY_CONFIG_SCOPE_MISMATCH");
134
+ return { ...config, scope: { ...scope } };
135
+ }
136
+ async function readPrivateJson(file) {
137
+ let handle;
138
+ try {
139
+ handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
140
+ const stat = await handle.stat();
141
+ if (!stat.isFile() || stat.size > 64 * 1024 || process.platform !== "win32" && ((stat.mode & 63) !== 0 || stat.uid !== process.getuid?.()))
142
+ throw new GatewayError("MANCODE_GATEWAY_PRIVATE_FILE_UNSAFE");
143
+ return JSON.parse(await handle.readFile("utf8"));
144
+ } catch (error) {
145
+ if (error.code === "ENOENT") return null;
146
+ if (error instanceof GatewayError) throw error;
147
+ throw new GatewayError("MANCODE_GATEWAY_PRIVATE_FILE_INVALID");
148
+ } finally {
149
+ await handle?.close();
150
+ }
151
+ }
152
+ async function readGatewayConfig(root) {
153
+ const location = await gatewayLocation(root);
154
+ const value = await readPrivateJson(
155
+ path.join(location.directory, "config.json")
156
+ );
157
+ return value === null ? null : parseGatewayConfig(value, location.scope);
158
+ }
159
+ async function ensurePrivateDirectory(directory) {
160
+ await mkdir(directory, { recursive: true, mode: 448 });
161
+ const stat = await lstat(directory);
162
+ if (!stat.isDirectory() || stat.isSymbolicLink() || process.platform !== "win32" && stat.uid !== process.getuid?.())
163
+ throw new GatewayError("MANCODE_GATEWAY_PRIVATE_DIRECTORY_UNSAFE");
164
+ await chmod(directory, 448);
165
+ }
166
+ async function writePrivateJson(file, value) {
167
+ const temporary = `${file}.${randomBytes(8).toString("hex")}.tmp`;
168
+ try {
169
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}
170
+ `, {
171
+ flag: "wx",
172
+ mode: 384
173
+ });
174
+ await rename(temporary, file);
175
+ } finally {
176
+ await rm(temporary, { force: true });
177
+ }
178
+ }
179
+ async function configureGateway(root, options) {
180
+ const binding = await gatewayLocation(root);
181
+ if (!binding.scope.workspaceId || !binding.scope.checkoutId)
182
+ throw new GatewayError("MANCODE_GATEWAY_NOT_INITIALIZED");
183
+ return withGatewayLifecycleLock(root, async () => {
184
+ const location = await gatewayLocation(root);
185
+ const existing = await readGatewayConfig(root);
186
+ const config = parseGatewayConfig(
187
+ {
188
+ schemaVersion: 1,
189
+ enabled: options.enabled,
190
+ upstreamId: options.upstreamId ?? existing?.upstreamId ?? "openai",
191
+ envKey: options.envKey ?? (options.upstreamId && options.upstreamId !== existing?.upstreamId ? UPSTREAMS[options.upstreamId].envKey : existing?.envKey) ?? UPSTREAMS[options.upstreamId ?? "openai"].envKey,
192
+ clientHost: options.clientHost ?? (options.upstreamId && options.upstreamId !== existing?.upstreamId ? "unverified" : existing?.clientHost) ?? "unverified",
193
+ accessToken: existing?.accessToken ?? randomBytes(32).toString("hex"),
194
+ port: options.port ?? existing?.port ?? 17641,
195
+ scope: location.scope,
196
+ ruleIds: existing?.ruleIds ?? [...DEFAULT_RULE_IDS],
197
+ rulesetVersion: RULESET_VERSION
198
+ },
199
+ location.scope
200
+ );
201
+ await writePrivateJson(
202
+ path.join(location.directory, "config.json"),
203
+ config
204
+ );
205
+ return config;
206
+ });
207
+ }
208
+ async function withGatewayLifecycleLock(root, action) {
209
+ const location = await gatewayLocation(root);
210
+ await ensurePrivateDirectory(path.dirname(location.directory));
211
+ await ensurePrivateDirectory(location.directory);
212
+ const lock = path.join(location.directory, "config-locks");
213
+ const release = await acquireConfigLock(lock);
214
+ try {
215
+ return await action();
216
+ } finally {
217
+ await release();
218
+ }
219
+ }
220
+ async function acquireConfigLock(lock) {
221
+ await ensurePrivateDirectory(lock);
222
+ const owner = {
223
+ processId: process.pid,
224
+ nonce: randomBytes(16).toString("hex"),
225
+ choosing: true,
226
+ ticket: 0
227
+ };
228
+ const candidate = path.join(lock, `${owner.nonce}.json`);
229
+ const contenders = async () => {
230
+ const result = [];
231
+ for (const name of await readdir(lock)) {
232
+ if (!/^[a-f0-9]{32}\.json$/.test(name)) continue;
233
+ const file = path.join(lock, name);
234
+ const current = await readPrivateJson(file);
235
+ if (!current) continue;
236
+ if (!Number.isSafeInteger(current.processId) || current.processId < 1 || current.nonce !== name.slice(0, -5) || typeof current.choosing !== "boolean" || !Number.isSafeInteger(current.ticket) || current.ticket < 0)
237
+ throw new GatewayError("MANCODE_GATEWAY_CONFIG_LOCK_UNVERIFIED");
238
+ if (!processIsAlive(current.processId)) {
239
+ await rm(file, { force: true });
240
+ continue;
241
+ }
242
+ result.push(current);
243
+ }
244
+ if (result.length > 64)
245
+ throw new GatewayError("MANCODE_GATEWAY_CONFIG_BUSY", 409);
246
+ return result;
247
+ };
248
+ await writePrivateJson(candidate, owner);
249
+ let acquired = false;
250
+ try {
251
+ owner.ticket = Math.max(0, ...(await contenders()).map((other) => other.ticket)) + 1;
252
+ if (!Number.isSafeInteger(owner.ticket))
253
+ throw new GatewayError("MANCODE_GATEWAY_CONFIG_BUSY", 409);
254
+ owner.choosing = false;
255
+ await writePrivateJson(candidate, owner);
256
+ for (let attempt = 0; attempt < 100; attempt++) {
257
+ const blocked = (await contenders()).some(
258
+ (other) => other.nonce !== owner.nonce && (other.choosing || other.ticket < owner.ticket || other.ticket === owner.ticket && other.nonce < owner.nonce)
259
+ );
260
+ if (!blocked) {
261
+ acquired = true;
262
+ return async () => {
263
+ await rm(candidate, { force: true });
264
+ };
265
+ }
266
+ await new Promise((resolve) => setTimeout(resolve, 10));
267
+ }
268
+ throw new GatewayError("MANCODE_GATEWAY_CONFIG_BUSY", 409);
269
+ } finally {
270
+ if (!acquired) await rm(candidate, { force: true });
271
+ }
272
+ }
273
+ function processIsAlive(pid) {
274
+ if (!Number.isSafeInteger(pid) || pid < 1) return true;
275
+ try {
276
+ process.kill(pid, 0);
277
+ return true;
278
+ } catch (error) {
279
+ return error.code !== "ESRCH";
280
+ }
281
+ }
282
+ function gatewayConfigDigest(config) {
283
+ return hashValue(
284
+ JSON.stringify({
285
+ upstreamId: config.upstreamId,
286
+ envKey: config.envKey,
287
+ clientHost: config.clientHost,
288
+ port: config.port,
289
+ scope: config.scope,
290
+ rulesetVersion: config.rulesetVersion,
291
+ ruleIds: config.ruleIds,
292
+ tokenBinding: hashValue(config.accessToken)
293
+ })
294
+ );
295
+ }
296
+ function constantTokenEquals(left, right) {
297
+ const a = Buffer.from(left);
298
+ const b = Buffer.from(right);
299
+ return a.length === b.length && timingSafeEqual(a, b);
300
+ }
301
+ function controlProof(token, instanceId, loadedDigest, challenge, action = "probe") {
302
+ return createHmac("sha256", token).update(
303
+ JSON.stringify({
304
+ purpose: "mancode-gateway-control-v1",
305
+ instanceId,
306
+ loadedDigest,
307
+ challenge,
308
+ action
309
+ })
310
+ ).digest("hex");
311
+ }
312
+
313
+ // src/gateway/server.ts
314
+ import { randomUUID } from "crypto";
315
+ import { once } from "events";
316
+ import {
317
+ createServer
318
+ } from "http";
319
+
320
+ // src/gateway/worker-client.ts
321
+ import { Worker } from "worker_threads";
322
+ var GatewayWorker = class {
323
+ worker;
324
+ sequence = 0;
325
+ closed = false;
326
+ pending = /* @__PURE__ */ new Map();
327
+ constructor(scope, rules, host, url = new URL("./gateway/worker.js", import.meta.url)) {
328
+ this.worker = new Worker(url, {
329
+ workerData: { scope, rules, host },
330
+ resourceLimits: {
331
+ maxOldGenerationSizeMb: 128,
332
+ maxYoungGenerationSizeMb: 32
333
+ }
334
+ });
335
+ this.worker.on(
336
+ "message",
337
+ (message) => {
338
+ const pending = this.pending.get(message.sequence);
339
+ if (!pending) return;
340
+ clearTimeout(pending.timer);
341
+ this.pending.delete(message.sequence);
342
+ if (message.error)
343
+ pending.reject(new GatewayError(message.error, message.status));
344
+ else pending.resolve(message.value);
345
+ }
346
+ );
347
+ this.worker.on("error", () => {
348
+ void this.close();
349
+ });
350
+ this.worker.on("exit", () => {
351
+ void this.close();
352
+ });
353
+ }
354
+ call(operation) {
355
+ if (this.closed)
356
+ return Promise.reject(
357
+ new GatewayError("MANCODE_GATEWAY_WORKER_UNAVAILABLE", 503)
358
+ );
359
+ if (this.pending.size >= 32)
360
+ return Promise.reject(
361
+ new GatewayError("MANCODE_GATEWAY_WORKER_QUEUE_LIMIT", 429)
362
+ );
363
+ const sequence = ++this.sequence;
364
+ return new Promise((resolve, reject) => {
365
+ const timer = setTimeout(() => {
366
+ void this.close();
367
+ }, 1e4);
368
+ this.pending.set(sequence, {
369
+ resolve: (value) => resolve(value),
370
+ reject,
371
+ timer
372
+ });
373
+ this.worker.postMessage({ sequence, operation });
374
+ });
375
+ }
376
+ async close() {
377
+ if (this.closed) return;
378
+ this.closed = true;
379
+ for (const pending of this.pending.values()) {
380
+ clearTimeout(pending.timer);
381
+ pending.reject(
382
+ new GatewayError("MANCODE_GATEWAY_WORKER_UNAVAILABLE", 503)
383
+ );
384
+ }
385
+ this.pending.clear();
386
+ await this.worker.terminate();
387
+ }
388
+ };
389
+
390
+ // src/gateway/server.ts
391
+ var GATEWAY_LIMITS = {
392
+ requestBytes: 1024 * 1024,
393
+ responseBytes: 4 * 1024 * 1024,
394
+ streamBytes: 16 * 1024 * 1024,
395
+ concurrentRequests: 8,
396
+ idleMs: 3e4,
397
+ generationMs: 3e5,
398
+ drainMs: 5e3
399
+ };
400
+ async function readBody(request, signal) {
401
+ const length = request.headers["content-length"];
402
+ if (length && (!/^\d+$/.test(length) || Number(length) > GATEWAY_LIMITS.requestBytes))
403
+ throw new GatewayError("MANCODE_GATEWAY_REQUEST_LIMIT", 413);
404
+ const parts = [];
405
+ let total = 0;
406
+ for await (const chunk of request) {
407
+ signal.throwIfAborted();
408
+ total += chunk.length;
409
+ if (total > GATEWAY_LIMITS.requestBytes)
410
+ throw new GatewayError("MANCODE_GATEWAY_REQUEST_LIMIT", 413);
411
+ parts.push(chunk);
412
+ }
413
+ try {
414
+ return new TextDecoder("utf-8", { fatal: true }).decode(
415
+ Buffer.concat(parts)
416
+ );
417
+ } catch {
418
+ throw new GatewayError("MANCODE_GATEWAY_INVALID_UTF8");
419
+ }
420
+ }
421
+ async function emit(response, text, signal) {
422
+ signal.throwIfAborted();
423
+ if (!response.write(text)) await once(response, "drain", { signal });
424
+ }
425
+ function errorResponse(response, error) {
426
+ if (response.headersSent) {
427
+ response.destroy();
428
+ return;
429
+ }
430
+ response.writeHead(error instanceof GatewayError ? error.status : 502, {
431
+ "content-type": "application/json",
432
+ "cache-control": "no-store",
433
+ connection: "close"
434
+ });
435
+ response.end(
436
+ JSON.stringify({
437
+ error: {
438
+ code: gatewayErrorCode(error),
439
+ message: "Gateway request was not forwarded or could not be completed safely."
440
+ }
441
+ })
442
+ );
443
+ }
444
+ async function startGatewayServer(config, dependencies = {}) {
445
+ if (!config.enabled) throw new GatewayError("MANCODE_GATEWAY_DISABLED", 503);
446
+ const upstream = UPSTREAMS[config.upstreamId];
447
+ const upstreamKey = dependencies.upstreamKey ?? process.env[config.envKey];
448
+ if (!upstreamKey || /[\r\n]/.test(upstreamKey))
449
+ throw new GatewayError("MANCODE_GATEWAY_UPSTREAM_KEY_UNAVAILABLE", 503);
450
+ const instanceId = randomUUID();
451
+ const loadedDigest = gatewayConfigDigest(config);
452
+ const worker = new GatewayWorker(
453
+ hashValue(
454
+ JSON.stringify({
455
+ scope: config.scope,
456
+ instanceId,
457
+ upstream: config.upstreamId,
458
+ loadedDigest
459
+ })
460
+ ),
461
+ config.ruleIds,
462
+ config.clientHost,
463
+ dependencies.workerUrl
464
+ );
465
+ const fetchUpstream = dependencies.fetchUpstream ?? fetch;
466
+ const active = /* @__PURE__ */ new Set();
467
+ let draining = false;
468
+ let port = config.port;
469
+ let routeObservedAt = null;
470
+ let observedHostBinding = null;
471
+ let opaqueBlocks = 0;
472
+ let lastAudit = null;
473
+ let resolveClosed = () => {
474
+ };
475
+ const closed = new Promise((resolve) => {
476
+ resolveClosed = resolve;
477
+ });
478
+ let stopping;
479
+ const health = () => ({
480
+ instanceId,
481
+ loadedDigest,
482
+ scope: config.scope,
483
+ state: draining ? "draining" : "accepting",
484
+ port,
485
+ activeRequests: active.size,
486
+ routeVerified: false,
487
+ routeObservedAt,
488
+ observedHostBinding,
489
+ coverage: "text-only-with-opaque-exclusions",
490
+ opaqueBlocks,
491
+ lastAudit,
492
+ history: "memory-only; restart invalidates previous_response_id"
493
+ });
494
+ const server = createServer(
495
+ {
496
+ maxHeaderSize: 16 * 1024,
497
+ requestTimeout: GATEWAY_LIMITS.idleMs,
498
+ headersTimeout: 1e4
499
+ },
500
+ (request, response) => {
501
+ void handle(request, response).catch(
502
+ (error) => errorResponse(response, error)
503
+ );
504
+ }
505
+ );
506
+ server.maxConnections = 24;
507
+ server.keepAliveTimeout = 5e3;
508
+ server.on("upgrade", (_request, socket) => {
509
+ socket.end(
510
+ "HTTP/1.1 426 Upgrade Required\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
511
+ );
512
+ });
513
+ server.on("connect", (_request, socket) => {
514
+ socket.end(
515
+ "HTTP/1.1 405 Method Not Allowed\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
516
+ );
517
+ });
518
+ server.on("clientError", (_error, socket) => {
519
+ socket.end(
520
+ "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"
521
+ );
522
+ });
523
+ const stop = () => {
524
+ if (stopping) return stopping;
525
+ draining = true;
526
+ stopping = new Promise((resolve) => {
527
+ const timer = setTimeout(() => {
528
+ for (const controller of active) controller.abort();
529
+ server.closeAllConnections();
530
+ }, dependencies.drainMs ?? GATEWAY_LIMITS.drainMs);
531
+ server.close(() => {
532
+ clearTimeout(timer);
533
+ void worker.close().finally(() => {
534
+ resolve();
535
+ resolveClosed();
536
+ });
537
+ });
538
+ server.closeIdleConnections();
539
+ });
540
+ return stopping;
541
+ };
542
+ async function handle(request, response) {
543
+ if (request.headers.host !== `127.0.0.1:${port}` || request.headers.origin !== void 0 || request.url?.startsWith("http"))
544
+ throw new GatewayError("MANCODE_GATEWAY_HOST_REJECTED", 403);
545
+ const challenge = request.method === "GET" ? /^\/__mancode\/probe\?challenge=([a-f0-9]{64})$/.exec(
546
+ request.url ?? ""
547
+ )?.[1] : void 0;
548
+ if (challenge) {
549
+ response.writeHead(200, {
550
+ "content-type": "application/json",
551
+ "cache-control": "no-store"
552
+ });
553
+ response.end(
554
+ JSON.stringify({
555
+ instanceId,
556
+ loadedDigest,
557
+ challenge,
558
+ proof: controlProof(
559
+ config.accessToken,
560
+ instanceId,
561
+ loadedDigest,
562
+ challenge
563
+ )
564
+ })
565
+ );
566
+ return;
567
+ }
568
+ const authorization = request.headers.authorization ?? (typeof request.headers["x-api-key"] === "string" ? `Bearer ${request.headers["x-api-key"]}` : "");
569
+ const controlAction = request.method === "GET" && request.url === "/__mancode/health" ? "health" : request.method === "POST" && request.url === "/__mancode/stop" ? "stop" : void 0;
570
+ const nonce = request.headers["x-mancode-control-nonce"];
571
+ const proof = request.headers["x-mancode-control-proof"];
572
+ const provedControl = controlAction && request.headers["x-mancode-instance"] === instanceId && typeof nonce === "string" && /^[a-f0-9]{64}$/.test(nonce) && typeof proof === "string" && constantTokenEquals(
573
+ proof,
574
+ controlProof(
575
+ config.accessToken,
576
+ instanceId,
577
+ loadedDigest,
578
+ nonce,
579
+ controlAction
580
+ )
581
+ );
582
+ if (!provedControl && !constantTokenEquals(authorization, `Bearer ${config.accessToken}`))
583
+ throw new GatewayError("MANCODE_GATEWAY_AUTH_REQUIRED", 401);
584
+ if (request.method === "GET" && request.url === "/__mancode/health") {
585
+ response.writeHead(200, {
586
+ "content-type": "application/json",
587
+ "cache-control": "no-store"
588
+ });
589
+ response.end(JSON.stringify(health()));
590
+ return;
591
+ }
592
+ if (request.method === "POST" && request.url === "/__mancode/stop") {
593
+ draining = true;
594
+ response.writeHead(202, {
595
+ "content-type": "application/json",
596
+ connection: "close"
597
+ });
598
+ response.end(JSON.stringify({ instanceId, state: "draining" }));
599
+ setImmediate(() => {
600
+ void stop();
601
+ });
602
+ return;
603
+ }
604
+ if (draining) throw new GatewayError("MANCODE_GATEWAY_DRAINING", 503);
605
+ const metadata = request.method === "GET" && /^\/v1\/models(?:\?client_version=\d+\.\d+\.\d+)?$/.test(
606
+ request.url ?? ""
607
+ );
608
+ const generationPath = upstream.protocol === "responses" ? request.url === "/v1/responses" : request.url === "/v1/messages" || request.url === "/v1/messages?beta=true";
609
+ if (!metadata && (request.method !== "POST" || !generationPath))
610
+ throw new GatewayError("MANCODE_GATEWAY_PATH_UNSUPPORTED", 404);
611
+ if (request.headers["content-encoding"] && request.headers["content-encoding"] !== "identity")
612
+ throw new GatewayError("MANCODE_GATEWAY_ENCODING_UNSUPPORTED", 415);
613
+ if (!metadata && !/^application\/json(?:\s*;\s*charset=utf-8)?$/i.test(
614
+ request.headers["content-type"] ?? ""
615
+ ))
616
+ throw new GatewayError("MANCODE_GATEWAY_CONTENT_TYPE_UNSUPPORTED", 415);
617
+ if (active.size >= GATEWAY_LIMITS.concurrentRequests)
618
+ throw new GatewayError("MANCODE_GATEWAY_CONCURRENCY_LIMIT", 429);
619
+ const controller = new AbortController();
620
+ active.add(controller);
621
+ const id = randomUUID();
622
+ const timer = setTimeout(
623
+ () => controller.abort(),
624
+ GATEWAY_LIMITS.generationMs
625
+ );
626
+ request.setTimeout(GATEWAY_LIMITS.idleMs, () => {
627
+ controller.abort();
628
+ request.destroy();
629
+ });
630
+ response.on("close", () => {
631
+ if (!response.writableEnded) controller.abort();
632
+ });
633
+ let began = false;
634
+ try {
635
+ const headers = {
636
+ accept: "application/json, text/event-stream",
637
+ "accept-encoding": "identity"
638
+ };
639
+ if (config.upstreamId === "openai")
640
+ headers.authorization = `Bearer ${upstreamKey}`;
641
+ else {
642
+ headers["x-api-key"] = upstreamKey;
643
+ const version = request.headers["anthropic-version"];
644
+ if (version !== void 0 && (typeof version !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(version)))
645
+ throw new GatewayError("MANCODE_GATEWAY_HEADER_UNSUPPORTED");
646
+ headers["anthropic-version"] = typeof version === "string" ? version : "2023-06-01";
647
+ const beta = request.headers["anthropic-beta"];
648
+ if (beta !== void 0) {
649
+ if (typeof beta !== "string" || !/^[a-z0-9, -]{1,512}$/.test(beta))
650
+ throw new GatewayError("MANCODE_GATEWAY_HEADER_UNSUPPORTED");
651
+ headers["anthropic-beta"] = beta;
652
+ }
653
+ }
654
+ let body;
655
+ if (!metadata) {
656
+ const input = await readBody(request, controller.signal);
657
+ const masked = await worker.call({
658
+ kind: "begin",
659
+ id,
660
+ body: input,
661
+ protocol: upstream.protocol
662
+ });
663
+ began = true;
664
+ controller.signal.throwIfAborted();
665
+ body = masked.body;
666
+ opaqueBlocks += masked.opaqueBlocks;
667
+ headers["content-type"] = "application/json";
668
+ } else if (request.headers["content-length"] || request.headers["transfer-encoding"])
669
+ throw new GatewayError("MANCODE_GATEWAY_METADATA_BODY_REJECTED");
670
+ const upstreamResponse = await fetchUpstream(
671
+ `${upstream.origin}${request.url}`,
672
+ {
673
+ method: request.method,
674
+ headers,
675
+ body,
676
+ redirect: "manual",
677
+ signal: controller.signal
678
+ }
679
+ );
680
+ if (!upstreamResponse.ok || !upstreamResponse.body)
681
+ throw new GatewayError("MANCODE_GATEWAY_UPSTREAM_REJECTED", 502);
682
+ const encoding = upstreamResponse.headers.get("content-encoding");
683
+ if (encoding && encoding !== "identity")
684
+ throw new GatewayError("MANCODE_GATEWAY_ENCODING_UNSUPPORTED", 502);
685
+ const contentType = upstreamResponse.headers.get("content-type") ?? "";
686
+ if (!metadata) {
687
+ routeObservedAt = (/* @__PURE__ */ new Date()).toISOString();
688
+ observedHostBinding = request.headers["x-mancode-host"] === config.clientHost ? config.clientHost : "unverified-client";
689
+ }
690
+ const reader = upstreamResponse.body.getReader();
691
+ let total = 0;
692
+ const parts = [];
693
+ const decoder = new SseDecoder();
694
+ const streaming = !metadata && /^text\/event-stream(?:;|$)/i.test(contentType);
695
+ if (!streaming && !/^application\/json(?:;|$)/i.test(contentType))
696
+ throw new GatewayError(
697
+ "MANCODE_GATEWAY_UPSTREAM_CONTENT_UNSUPPORTED",
698
+ 502
699
+ );
700
+ if (streaming)
701
+ response.writeHead(200, {
702
+ "content-type": "text/event-stream",
703
+ "cache-control": "no-store",
704
+ "x-mancode-coverage": "text-only; opaque-blocks-excluded"
705
+ });
706
+ for (; ; ) {
707
+ const idle = setTimeout(
708
+ () => controller.abort(),
709
+ GATEWAY_LIMITS.idleMs
710
+ );
711
+ let result;
712
+ try {
713
+ result = await reader.read();
714
+ } finally {
715
+ clearTimeout(idle);
716
+ }
717
+ controller.signal.throwIfAborted();
718
+ if (result.done) break;
719
+ total += result.value.length;
720
+ if (total > (streaming ? GATEWAY_LIMITS.streamBytes : GATEWAY_LIMITS.responseBytes))
721
+ throw new GatewayError("MANCODE_GATEWAY_RESPONSE_LIMIT", 502);
722
+ if (!streaming) parts.push(Buffer.from(result.value));
723
+ else
724
+ for (const frame of decoder.push(result.value)) {
725
+ const transformed = await worker.call({ kind: "frame", id, frame });
726
+ opaqueBlocks = Math.max(opaqueBlocks, transformed.opaqueBlocks);
727
+ for (const output of transformed.frames)
728
+ await emit(response, encodeSse(output), controller.signal);
729
+ }
730
+ }
731
+ if (streaming) {
732
+ decoder.push(new Uint8Array(), true);
733
+ const finished = await worker.call({
734
+ kind: "finish",
735
+ id
736
+ });
737
+ lastAudit = finished.audit;
738
+ response.end();
739
+ } else {
740
+ let original;
741
+ try {
742
+ original = new TextDecoder("utf-8", { fatal: true }).decode(
743
+ Buffer.concat(parts)
744
+ );
745
+ } catch {
746
+ throw new GatewayError("MANCODE_GATEWAY_INVALID_UTF8", 502);
747
+ }
748
+ const output = metadata ? { body: original, audit: null } : await worker.call({
749
+ kind: "response",
750
+ id,
751
+ body: original
752
+ });
753
+ lastAudit = output.audit;
754
+ response.writeHead(200, {
755
+ "content-type": "application/json",
756
+ "cache-control": "no-store",
757
+ "x-mancode-coverage": metadata ? "metadata-endpoint" : "text-only; opaque-blocks-excluded"
758
+ });
759
+ response.end(output.body);
760
+ }
761
+ } catch (error) {
762
+ dependencies.onDiagnostic?.(gatewayErrorCode(error));
763
+ controller.abort();
764
+ errorResponse(response, error);
765
+ } finally {
766
+ clearTimeout(timer);
767
+ active.delete(controller);
768
+ if (began) await worker.call({ kind: "release", id }).catch(() => {
769
+ });
770
+ }
771
+ }
772
+ try {
773
+ await new Promise((resolve, reject) => {
774
+ server.once("error", reject);
775
+ server.listen(config.port, "127.0.0.1", () => {
776
+ server.removeListener("error", reject);
777
+ resolve();
778
+ });
779
+ });
780
+ const address = server.address();
781
+ if (!address || typeof address === "string")
782
+ throw new GatewayError("MANCODE_GATEWAY_BIND_FAILED", 503);
783
+ port = address.port;
784
+ return { port, instanceId, health, stop, closed };
785
+ } catch {
786
+ await worker.close();
787
+ throw new GatewayError("MANCODE_GATEWAY_BIND_FAILED", 503);
788
+ }
789
+ }
790
+
791
+ // src/commands/privacy-gateway.ts
792
+ async function control(config, record, action) {
793
+ const challenge = randomBytes2(32).toString("hex");
794
+ const probe = await fetch(
795
+ `http://127.0.0.1:${record.port}/__mancode/probe?challenge=${challenge}`,
796
+ { signal: AbortSignal.timeout(750), redirect: "error" }
797
+ );
798
+ const proof = await readControlJson(probe, 2048);
799
+ if (proof.instanceId !== record.instanceId || proof.loadedDigest !== record.loadedDigest || proof.challenge !== challenge || typeof proof.proof !== "string" || !constantTokenEquals(
800
+ proof.proof,
801
+ controlProof(
802
+ config.accessToken,
803
+ record.instanceId,
804
+ record.loadedDigest,
805
+ challenge
806
+ )
807
+ ))
808
+ throw new GatewayError("MANCODE_GATEWAY_RUNTIME_UNCONFIRMED");
809
+ const response = await fetch(
810
+ `http://127.0.0.1:${record.port}/__mancode/${action}`,
811
+ {
812
+ method: action === "stop" ? "POST" : "GET",
813
+ headers: {
814
+ "x-mancode-instance": record.instanceId,
815
+ "x-mancode-control-nonce": challenge,
816
+ "x-mancode-control-proof": controlProof(
817
+ config.accessToken,
818
+ record.instanceId,
819
+ record.loadedDigest,
820
+ challenge,
821
+ action
822
+ )
823
+ },
824
+ signal: AbortSignal.timeout(750),
825
+ redirect: "error"
826
+ }
827
+ );
828
+ return readControlJson(response, 32 * 1024);
829
+ }
830
+ async function readControlJson(response, limit) {
831
+ if (!response.ok || !response.body)
832
+ throw new GatewayError("MANCODE_GATEWAY_RUNTIME_UNCONFIRMED");
833
+ const reader = response.body.getReader();
834
+ const parts = [];
835
+ let size = 0;
836
+ for (; ; ) {
837
+ const value = await reader.read();
838
+ if (value.done) break;
839
+ size += value.value.length;
840
+ if (size > limit) {
841
+ await reader.cancel();
842
+ throw new GatewayError("MANCODE_GATEWAY_RUNTIME_UNCONFIRMED");
843
+ }
844
+ parts.push(Buffer.from(value.value));
845
+ }
846
+ return JSON.parse(Buffer.concat(parts).toString("utf8"));
847
+ }
848
+ async function inspectRuntime(root, config) {
849
+ const location = await gatewayLocation(root);
850
+ const raw = await readPrivateJson(
851
+ path2.join(location.directory, "runtime.json")
852
+ );
853
+ if (raw === null) return { state: "stopped", record: null, health: null };
854
+ const record = raw;
855
+ if (record.schemaVersion !== 1 || typeof record.instanceId !== "string" || !/^[a-f0-9-]{36}$/.test(record.instanceId) || !Number.isSafeInteger(record.processId) || record.processId < 1 || !Number.isInteger(record.port) || record.port < 1024 || record.port > 65535 || typeof record.loadedDigest !== "string" || !/^[a-f0-9]{64}$/.test(record.loadedDigest) || JSON.stringify(record.scope) !== JSON.stringify(config.scope))
856
+ throw new GatewayError("MANCODE_GATEWAY_RUNTIME_RECORD_INVALID");
857
+ if (!processIsAlive(record.processId))
858
+ return { state: "stopped", record, health: null };
859
+ try {
860
+ const health = await control(config, record, "health");
861
+ if (health.instanceId !== record.instanceId || health.loadedDigest !== record.loadedDigest || JSON.stringify(health.scope) !== JSON.stringify(record.scope) || health.port !== record.port || !["accepting", "draining"].includes(health.state) || health.routeVerified !== false)
862
+ throw new GatewayError("MANCODE_GATEWAY_RUNTIME_SCOPE_MISMATCH");
863
+ return { state: health.state, record, health };
864
+ } catch {
865
+ return {
866
+ state: processIsAlive(record.processId) ? "unconfirmed" : "stopped",
867
+ record,
868
+ health: null
869
+ };
870
+ }
871
+ }
872
+ async function readPrivacyGatewayStatus(root = process.cwd()) {
873
+ const empty = {
874
+ schemaVersion: 1,
875
+ configured: false,
876
+ enabled: false,
877
+ configurationStatus: "missing",
878
+ runtime: "stopped",
879
+ routeVerified: false,
880
+ routeObservedAt: null,
881
+ observedHostBinding: null,
882
+ loadedDigest: null,
883
+ configuredDigest: null,
884
+ pendingRestart: false,
885
+ scope: null,
886
+ upstreamId: null,
887
+ clientHost: null,
888
+ coverage: "Responses/Anthropic Messages HTTP/SSE text only; opaque thinking/signature/encrypted blocks excluded; unsupported executable tool restoration blocked"
889
+ };
890
+ try {
891
+ const config = await readGatewayConfig(root);
892
+ if (!config) return empty;
893
+ const runtime = await inspectRuntime(root, config);
894
+ const configuredDigest = gatewayConfigDigest(config);
895
+ return {
896
+ ...empty,
897
+ configured: true,
898
+ enabled: config.enabled,
899
+ configurationStatus: "valid",
900
+ runtime: runtime.state,
901
+ configuredDigest,
902
+ loadedDigest: runtime.health?.loadedDigest ?? null,
903
+ pendingRestart: Boolean(
904
+ runtime.health && runtime.health.loadedDigest !== configuredDigest
905
+ ),
906
+ routeObservedAt: runtime.health?.routeObservedAt ?? null,
907
+ observedHostBinding: runtime.health?.observedHostBinding ?? null,
908
+ scope: config.scope,
909
+ upstreamId: config.upstreamId,
910
+ clientHost: config.clientHost
911
+ };
912
+ } catch (error) {
913
+ return {
914
+ ...empty,
915
+ enabled: null,
916
+ configurationStatus: "invalid",
917
+ runtime: "unconfirmed",
918
+ error: gatewayErrorCode(error)
919
+ };
920
+ }
921
+ }
922
+ async function configurePrivacyGateway(root, options) {
923
+ await configureGateway(root, options);
924
+ return readPrivacyGatewayStatus(root);
925
+ }
926
+ async function disablePrivacyGateway(root) {
927
+ const config = await readGatewayConfig(root);
928
+ if (!config) return readPrivacyGatewayStatus(root);
929
+ await configureGateway(root, { enabled: false });
930
+ const runtime = await inspectRuntime(root, config);
931
+ if (runtime.health && runtime.record) {
932
+ const stopped = await control(config, runtime.record, "stop");
933
+ if (stopped.instanceId !== runtime.record.instanceId)
934
+ throw new GatewayError("MANCODE_GATEWAY_RUNTIME_SCOPE_MISMATCH");
935
+ for (let attempt = 0; attempt < 60; attempt++) {
936
+ await new Promise((resolve) => setTimeout(resolve, 100));
937
+ const status = await readPrivacyGatewayStatus(root);
938
+ if (status.runtime === "stopped") return status;
939
+ }
940
+ }
941
+ return readPrivacyGatewayStatus(root);
942
+ }
943
+ async function verifyInstalledHost(config) {
944
+ if (config.clientHost === "unverified") return "unverified";
945
+ const executable = config.clientHost.startsWith("codex-cli/") ? "codex" : "claude";
946
+ try {
947
+ const result = await promisify(execFile)(executable, ["--version"], {
948
+ timeout: 5e3,
949
+ maxBuffer: 16 * 1024
950
+ });
951
+ const actual = executable === "codex" ? /codex-cli (\d+\.\d+\.\d+)/.exec(result.stdout)?.[1] : /^(\d+\.\d+\.\d+) \(Claude Code\)/.exec(result.stdout)?.[1];
952
+ if (actual !== config.clientHost.split("/")[1])
953
+ throw new GatewayError("MANCODE_GATEWAY_HOST_VERSION_UNVERIFIED");
954
+ return config.clientHost;
955
+ } catch {
956
+ throw new GatewayError("MANCODE_GATEWAY_HOST_VERSION_UNVERIFIED");
957
+ }
958
+ }
959
+ async function runPrivacyGateway(root, dependencies = {}) {
960
+ const binding = await gatewayLocation(root);
961
+ if (!binding.scope.workspaceId || !binding.scope.checkoutId)
962
+ throw new GatewayError("MANCODE_GATEWAY_NOT_INITIALIZED");
963
+ const { config, server, file } = await withGatewayLifecycleLock(
964
+ root,
965
+ async () => {
966
+ const config2 = await readGatewayConfig(root);
967
+ if (!config2?.enabled) throw new GatewayError("MANCODE_GATEWAY_DISABLED");
968
+ await verifyInstalledHost(config2);
969
+ const runtime = await inspectRuntime(root, config2);
970
+ if (runtime.state !== "stopped")
971
+ throw new GatewayError(
972
+ "MANCODE_GATEWAY_ALREADY_RUNNING_OR_UNCONFIRMED"
973
+ );
974
+ const location = await gatewayLocation(root);
975
+ const server2 = await (dependencies.startServer ?? startGatewayServer)(
976
+ config2
977
+ );
978
+ const record = {
979
+ schemaVersion: 1,
980
+ instanceId: server2.instanceId,
981
+ processId: process.pid,
982
+ port: server2.port,
983
+ scope: config2.scope,
984
+ loadedDigest: gatewayConfigDigest(config2),
985
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
986
+ };
987
+ const file2 = path2.join(location.directory, "runtime.json");
988
+ try {
989
+ await writePrivateJson(file2, record);
990
+ } catch (error) {
991
+ await server2.stop();
992
+ throw error;
993
+ }
994
+ return { config: config2, server: server2, file: file2 };
995
+ }
996
+ );
997
+ const stop = () => {
998
+ void server.stop();
999
+ };
1000
+ try {
1001
+ process.once("SIGINT", stop);
1002
+ process.once("SIGTERM", stop);
1003
+ process.stdout.write(
1004
+ `${JSON.stringify({ gateway: "running", ...server.health(), clientHost: config.clientHost, note: "Dedicated checkout instance. Route verification requires current client evidence. No client configuration was changed." })}
1005
+ `
1006
+ );
1007
+ await server.closed;
1008
+ } finally {
1009
+ process.removeListener("SIGINT", stop);
1010
+ process.removeListener("SIGTERM", stop);
1011
+ await server.stop();
1012
+ const current = await readPrivateJson(file);
1013
+ if (current?.instanceId === server.instanceId)
1014
+ await rm2(file, { force: true });
1015
+ }
1016
+ }
1017
+ async function printPrivacyGatewayConfig(root, host) {
1018
+ const config = await readGatewayConfig(root);
1019
+ if (!config) throw new GatewayError("MANCODE_GATEWAY_NOT_CONFIGURED");
1020
+ const { directory } = await gatewayLocation(root);
1021
+ const note = `# Review before applying. Read accessToken from the private file ${path2.join(directory, "config.json")}.
1022
+ # Put it in MANCODE_GATEWAY_TOKEN for this shell only. No credentials are printed here.
1023
+ # Only this checkout is covered; no desktop, subscription, or cloud routing claim.
1024
+ `;
1025
+ if (host === "codex") {
1026
+ if (config.upstreamId !== "openai")
1027
+ throw new GatewayError("MANCODE_GATEWAY_HOST_UPSTREAM_MISMATCH");
1028
+ return `${note}# User-level Codex configuration fragment; choose this provider explicitly.
1029
+ [model_providers.mancode_privacy]
1030
+ name = "mancode privacy"
1031
+ base_url = "http://127.0.0.1:${config.port}/v1"
1032
+ wire_api = "responses"
1033
+ env_key = "MANCODE_GATEWAY_TOKEN"
1034
+ supports_websockets = false
1035
+ http_headers = { "X-Mancode-Host" = "${config.clientHost}" }
1036
+ `;
1037
+ }
1038
+ if (host === "claude") {
1039
+ if (config.upstreamId !== "anthropic")
1040
+ throw new GatewayError("MANCODE_GATEWAY_HOST_UPSTREAM_MISMATCH");
1041
+ return `${note}# Shell fragment for an explicit Claude Code API-key session.
1042
+ export ANTHROPIC_BASE_URL='http://127.0.0.1:${config.port}'
1043
+ export ANTHROPIC_API_KEY="$MANCODE_GATEWAY_TOKEN"
1044
+ export ANTHROPIC_CUSTOM_HEADERS='X-Mancode-Host: ${config.clientHost}'
1045
+ # Run the gateway in a separate shell with the real upstream env-key reference.
1046
+ `;
1047
+ }
1048
+ throw new GatewayError("MANCODE_GATEWAY_HOST_UNSUPPORTED");
1049
+ }
1050
+ function registerPrivacyGatewayCommands(privacy) {
1051
+ const gateway = privacy.command("gateway").description("Configure and run the optional private local model gateway");
1052
+ const execute = (action) => action().then((value) => {
1053
+ if (value !== void 0)
1054
+ process.stdout.write(
1055
+ `${typeof value === "string" ? value : JSON.stringify(value, null, 2)}
1056
+ `
1057
+ );
1058
+ }).catch((error) => {
1059
+ process.stderr.write(
1060
+ `${JSON.stringify({ error: gatewayErrorCode(error) })}
1061
+ `
1062
+ );
1063
+ process.exitCode = 2;
1064
+ });
1065
+ gateway.command("enable").description("Record intent; does not start or reroute a client").option("--upstream <id>", "openai or anthropic").option(
1066
+ "--env-key <name>",
1067
+ "Reference to the upstream key environment variable"
1068
+ ).option(
1069
+ "--client-host <host>",
1070
+ "codex-cli/0.153.4, claude-code/2.1.142, or unverified"
1071
+ ).option("--port <port>", "Loopback port, 1024\u201365535").option("--json").action(
1072
+ (options) => execute(
1073
+ () => configurePrivacyGateway(process.cwd(), {
1074
+ enabled: true,
1075
+ upstreamId: options.upstream,
1076
+ envKey: options.envKey,
1077
+ clientHost: options.clientHost,
1078
+ port: options.port === void 0 ? void 0 : Number(options.port)
1079
+ })
1080
+ )
1081
+ );
1082
+ gateway.command("disable").description(
1083
+ "Stop new requests and drain the current instance; never enable plaintext forwarding"
1084
+ ).option("--json").action(() => execute(() => disablePrivacyGateway(process.cwd())));
1085
+ gateway.command("status").description("Read configured intent and authenticated runtime evidence").option("--json").action(() => execute(() => readPrivacyGatewayStatus()));
1086
+ gateway.command("doctor").description(
1087
+ "Read local runtime and installed host version; does not call a model"
1088
+ ).option("--json").action(
1089
+ () => execute(async () => {
1090
+ const status = await readPrivacyGatewayStatus();
1091
+ const config = await readGatewayConfig(process.cwd());
1092
+ let hostVersion = "unverified";
1093
+ if (config) {
1094
+ try {
1095
+ hostVersion = await verifyInstalledHost(config);
1096
+ } catch {
1097
+ hostVersion = "version-mismatch-or-unavailable";
1098
+ }
1099
+ }
1100
+ return {
1101
+ ...status,
1102
+ installedHost: hostVersion,
1103
+ upstreamKeyPresent: Boolean(config && process.env[config.envKey]),
1104
+ routeVerified: false
1105
+ };
1106
+ })
1107
+ );
1108
+ gateway.command("run").description("Run one dedicated checkout instance in the foreground").action(() => execute(() => runPrivacyGateway(process.cwd())));
1109
+ gateway.command("print-config").description(
1110
+ "Print a reviewable fragment without credentials or applying it"
1111
+ ).requiredOption("--host <host>", "codex or claude").action(
1112
+ (options) => execute(() => printPrivacyGatewayConfig(process.cwd(), options.host))
1113
+ );
1114
+ }
1115
+
1116
+ export {
1117
+ readPrivacyGatewayStatus,
1118
+ configurePrivacyGateway,
1119
+ disablePrivacyGateway,
1120
+ runPrivacyGateway,
1121
+ printPrivacyGatewayConfig,
1122
+ registerPrivacyGatewayCommands
1123
+ };
1124
+ //# sourceMappingURL=chunk-THOE33LU.js.map