@cognisos/liminal 2.4.0 → 2.4.1

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 (3) hide show
  1. package/dist/bin.js +1459 -234
  2. package/dist/bin.js.map +1 -1
  3. package/package.json +4 -2
package/dist/bin.js CHANGED
@@ -1,7 +1,84 @@
1
1
  #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/rsc/pipeline.ts
13
+ var pipeline_exports = {};
14
+ __export(pipeline_exports, {
15
+ RSCPipelineWrapper: () => RSCPipelineWrapper
16
+ });
17
+ import {
18
+ CompressionPipeline,
19
+ RSCTransport,
20
+ RSCEventEmitter,
21
+ Session,
22
+ CircuitBreaker
23
+ } from "@cognisos/rsc-sdk";
24
+ var RSCPipelineWrapper;
25
+ var init_pipeline = __esm({
26
+ "src/rsc/pipeline.ts"() {
27
+ "use strict";
28
+ RSCPipelineWrapper = class {
29
+ pipeline;
30
+ session;
31
+ events;
32
+ transport;
33
+ circuitBreaker;
34
+ constructor(config) {
35
+ this.circuitBreaker = new CircuitBreaker(5, 5 * 60 * 1e3);
36
+ this.transport = new RSCTransport({
37
+ baseUrl: config.rscBaseUrl,
38
+ apiKey: config.rscApiKey,
39
+ timeout: 3e4,
40
+ maxRetries: 3,
41
+ circuitBreaker: this.circuitBreaker
42
+ });
43
+ this.events = new RSCEventEmitter();
44
+ this.session = new Session(config.sessionId);
45
+ this.pipeline = new CompressionPipeline(
46
+ this.transport,
47
+ {
48
+ threshold: config.compressionThreshold,
49
+ learnFromResponses: config.learnFromResponses,
50
+ latencyBudgetMs: config.latencyBudgetMs,
51
+ sessionId: this.session.sessionId
52
+ },
53
+ this.events
54
+ );
55
+ }
56
+ async healthCheck() {
57
+ try {
58
+ await this.transport.get("/health");
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+ getSessionSummary() {
65
+ return this.session.getSummary();
66
+ }
67
+ getCircuitState() {
68
+ return this.circuitBreaker.getState();
69
+ }
70
+ isCircuitOpen() {
71
+ return this.circuitBreaker.getState() === "open";
72
+ }
73
+ resetCircuitBreaker() {
74
+ this.circuitBreaker.reset();
75
+ }
76
+ };
77
+ }
78
+ });
2
79
 
3
80
  // src/version.ts
4
- var VERSION = true ? "2.4.0" : "0.2.1";
81
+ var VERSION = true ? "2.4.1" : "0.2.1";
5
82
  var BANNER_LINES = [
6
83
  " ___ ___ _____ ______ ___ ________ ________ ___",
7
84
  "|\\ \\ |\\ \\|\\ _ \\ _ \\|\\ \\|\\ ___ \\|\\ __ \\|\\ \\",
@@ -41,11 +118,21 @@ var DEFAULTS = {
41
118
  anthropicUpstreamUrl: "https://api.anthropic.com",
42
119
  port: 3141,
43
120
  compressionThreshold: 100,
44
- compressRoles: ["user"],
121
+ aggregateThreshold: 500,
122
+ hotFraction: 0.3,
123
+ coldFraction: 0.3,
124
+ compressRoles: ["user", "assistant"],
125
+ compressToolResults: true,
45
126
  learnFromResponses: true,
46
127
  latencyBudgetMs: 5e3,
47
128
  enabled: true,
48
- tools: []
129
+ tools: [],
130
+ concurrencyLimit: 6,
131
+ concurrencyTimeoutMs: 15e3,
132
+ maxSessions: 10,
133
+ sessionTtlMs: 18e5,
134
+ latencyWarningMs: 4e3,
135
+ latencyCriticalMs: 8e3
49
136
  };
50
137
  var CONFIGURABLE_KEYS = /* @__PURE__ */ new Set([
51
138
  "apiBaseUrl",
@@ -53,11 +140,21 @@ var CONFIGURABLE_KEYS = /* @__PURE__ */ new Set([
53
140
  "anthropicUpstreamUrl",
54
141
  "port",
55
142
  "compressionThreshold",
143
+ "aggregateThreshold",
144
+ "hotFraction",
145
+ "coldFraction",
56
146
  "compressRoles",
147
+ "compressToolResults",
57
148
  "learnFromResponses",
58
149
  "latencyBudgetMs",
59
150
  "enabled",
60
- "tools"
151
+ "tools",
152
+ "concurrencyLimit",
153
+ "concurrencyTimeoutMs",
154
+ "maxSessions",
155
+ "sessionTtlMs",
156
+ "latencyWarningMs",
157
+ "latencyCriticalMs"
61
158
  ]);
62
159
 
63
160
  // src/config/loader.ts
@@ -77,11 +174,21 @@ function loadConfig() {
77
174
  anthropicUpstreamUrl: DEFAULTS.anthropicUpstreamUrl,
78
175
  port: DEFAULTS.port,
79
176
  compressionThreshold: DEFAULTS.compressionThreshold,
177
+ aggregateThreshold: DEFAULTS.aggregateThreshold,
178
+ hotFraction: DEFAULTS.hotFraction,
179
+ coldFraction: DEFAULTS.coldFraction,
80
180
  compressRoles: DEFAULTS.compressRoles,
181
+ compressToolResults: DEFAULTS.compressToolResults,
81
182
  learnFromResponses: DEFAULTS.learnFromResponses,
82
183
  latencyBudgetMs: DEFAULTS.latencyBudgetMs,
83
184
  enabled: DEFAULTS.enabled,
84
185
  tools: DEFAULTS.tools,
186
+ concurrencyLimit: DEFAULTS.concurrencyLimit,
187
+ concurrencyTimeoutMs: DEFAULTS.concurrencyTimeoutMs,
188
+ maxSessions: DEFAULTS.maxSessions,
189
+ sessionTtlMs: DEFAULTS.sessionTtlMs,
190
+ latencyWarningMs: DEFAULTS.latencyWarningMs,
191
+ latencyCriticalMs: DEFAULTS.latencyCriticalMs,
85
192
  ...fileConfig
86
193
  };
87
194
  if (process.env.LIMINAL_API_KEY) merged.apiKey = process.env.LIMINAL_API_KEY;
@@ -485,7 +592,7 @@ import { createInterface } from "readline/promises";
485
592
  import { stdin, stdout } from "process";
486
593
 
487
594
  // src/auth/supabase.ts
488
- import { randomBytes } from "crypto";
595
+ import { randomBytes, createHash } from "crypto";
489
596
  var SUPABASE_URL = "https://nzcneiyymvgxvttbenhp.supabase.co";
490
597
  var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im56Y25laXl5bXZneHZ0dGJlbmhwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQwNjQ0MjcsImV4cCI6MjA2OTY0MDQyN30.x3E-zGRadbPMmxRqT_PB_KOi00htKpgeb8GiQa4g2z0";
491
598
  function supabaseHeaders(accessToken) {
@@ -542,25 +649,13 @@ async function signUp(email, password, name) {
542
649
  email: body.user?.email ?? email
543
650
  };
544
651
  }
545
- async function fetchApiKey(accessToken, userId) {
546
- const params = new URLSearchParams({
547
- user_id: `eq.${userId}`,
548
- is_active: "eq.true",
549
- select: "api_key",
550
- limit: "1"
551
- });
552
- const res = await fetch(`${SUPABASE_URL}/rest/v1/user_api_keys?${params}`, {
652
+ async function createApiKey(accessToken, userId) {
653
+ await fetch(`${SUPABASE_URL}/rest/v1/user_api_keys?user_id=eq.${userId}`, {
654
+ method: "DELETE",
553
655
  headers: supabaseHeaders(accessToken)
554
656
  });
555
- if (!res.ok) return null;
556
- const rows = await res.json();
557
- if (Array.isArray(rows) && rows.length > 0 && rows[0].api_key) {
558
- return rows[0].api_key;
559
- }
560
- return null;
561
- }
562
- async function createApiKey(accessToken, userId) {
563
- const apiKey = `fmcp_${randomBytes(32).toString("hex")}`;
657
+ const apiKey = `lim_${randomBytes(32).toString("hex")}`;
658
+ const keyHash = createHash("sha256").update(apiKey).digest("hex");
564
659
  const res = await fetch(`${SUPABASE_URL}/rest/v1/user_api_keys`, {
565
660
  method: "POST",
566
661
  headers: {
@@ -570,7 +665,7 @@ async function createApiKey(accessToken, userId) {
570
665
  body: JSON.stringify({
571
666
  user_id: userId,
572
667
  key_name: "Liminal CLI",
573
- api_key: apiKey,
668
+ key_hash: keyHash,
574
669
  is_active: true
575
670
  })
576
671
  });
@@ -582,8 +677,6 @@ async function createApiKey(accessToken, userId) {
582
677
  return apiKey;
583
678
  }
584
679
  async function authenticateAndGetKey(auth) {
585
- const existingKey = await fetchApiKey(auth.accessToken, auth.userId);
586
- if (existingKey) return existingKey;
587
680
  return createApiKey(auth.accessToken, auth.userId);
588
681
  }
589
682
 
@@ -592,7 +685,7 @@ async function loginCommand() {
592
685
  printBanner();
593
686
  try {
594
687
  const config = loadConfig();
595
- if (config.apiKey && config.apiKey.startsWith("fmcp_")) {
688
+ if (config.apiKey && (config.apiKey.startsWith("lim_") || config.apiKey.startsWith("fmcp_"))) {
596
689
  console.log(" Already logged in.");
597
690
  console.log(" Run \x1B[1mliminal logout\x1B[0m first to switch accounts.");
598
691
  return;
@@ -871,19 +964,24 @@ var cursorConnector = {
871
964
  return [];
872
965
  },
873
966
  async setup(port) {
874
- const baseUrl = `http://127.0.0.1:${port}/v1`;
875
967
  return {
876
968
  success: true,
877
969
  shellExports: [],
878
970
  // No env vars — GUI only
879
971
  postSetupInstructions: [
880
- "Cursor requires manual configuration:",
972
+ "Cursor routes API calls through its cloud servers, so localhost",
973
+ "URLs are blocked. You need a tunnel to expose the proxy:",
974
+ "",
975
+ ` npx cloudflared tunnel --url http://localhost:${port}`,
976
+ "",
977
+ "Then configure Cursor with the tunnel URL:",
881
978
  "",
882
979
  " 1. Open Cursor Settings (not VS Code settings)",
883
980
  " 2. Go to Models",
884
981
  ' 3. Enable "Override OpenAI Base URL (when using key)"',
885
- ` 4. Set the base URL to: ${baseUrl}`,
886
- ' 5. Enter any string as the API key (e.g., "liminal")',
982
+ " 4. Set the base URL to your tunnel URL + /v1",
983
+ " (e.g., https://abc123.trycloudflare.com/v1)",
984
+ " 5. Enter your real OpenAI/Anthropic API key",
887
985
  " 6. Restart Cursor",
888
986
  "",
889
987
  "Cursor uses OpenAI format for all models, including Claude.",
@@ -1140,64 +1238,6 @@ async function logoutCommand() {
1140
1238
  console.log(" Run \x1B[1mliminal login\x1B[0m to reconnect.");
1141
1239
  }
1142
1240
 
1143
- // src/rsc/pipeline.ts
1144
- import {
1145
- CompressionPipeline,
1146
- RSCTransport,
1147
- RSCEventEmitter,
1148
- Session,
1149
- CircuitBreaker
1150
- } from "@cognisos/rsc-sdk";
1151
- var RSCPipelineWrapper = class {
1152
- pipeline;
1153
- session;
1154
- events;
1155
- transport;
1156
- circuitBreaker;
1157
- constructor(config) {
1158
- this.circuitBreaker = new CircuitBreaker(5, 5 * 60 * 1e3);
1159
- this.transport = new RSCTransport({
1160
- baseUrl: config.rscBaseUrl,
1161
- apiKey: config.rscApiKey,
1162
- timeout: 3e4,
1163
- maxRetries: 3,
1164
- circuitBreaker: this.circuitBreaker
1165
- });
1166
- this.events = new RSCEventEmitter();
1167
- this.session = new Session(config.sessionId);
1168
- this.pipeline = new CompressionPipeline(
1169
- this.transport,
1170
- {
1171
- threshold: config.compressionThreshold,
1172
- learnFromResponses: config.learnFromResponses,
1173
- latencyBudgetMs: config.latencyBudgetMs,
1174
- sessionId: this.session.sessionId
1175
- },
1176
- this.events
1177
- );
1178
- }
1179
- async healthCheck() {
1180
- try {
1181
- await this.transport.get("/health");
1182
- return true;
1183
- } catch {
1184
- return false;
1185
- }
1186
- }
1187
- getSessionSummary() {
1188
- return this.session.getSummary();
1189
- }
1190
- getCircuitState() {
1191
- return this.circuitBreaker.getState();
1192
- }
1193
- isCircuitOpen() {
1194
- return this.circuitBreaker.getState() === "open";
1195
- }
1196
- resetCircuitBreaker() {
1197
- this.circuitBreaker.reset();
1198
- }
1199
- };
1200
-
1201
1241
  // src/proxy/completions.ts
1202
1242
  import { RSCCircuitOpenError as RSCCircuitOpenError2 } from "@cognisos/rsc-sdk";
1203
1243
 
@@ -1303,66 +1343,115 @@ function isIndentedCodeLine(line) {
1303
1343
  }
1304
1344
 
1305
1345
  // src/rsc/message-compressor.ts
1346
+ var PASSTHROUGH_BLOCK_TYPES = /* @__PURE__ */ new Set(["thinking", "tool_use", "image"]);
1347
+ function sanitizeCompressedText(text) {
1348
+ return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
1349
+ }
1306
1350
  async function compressMessages(messages, pipeline, session, compressRoles) {
1307
1351
  let anyCompressed = false;
1308
1352
  let totalTokensSaved = 0;
1309
1353
  const compressed = await Promise.all(
1310
1354
  messages.map(async (msg) => {
1311
1355
  if (!compressRoles.has(msg.role)) return msg;
1312
- if (typeof msg.content === "string") {
1313
- return compressStringContent(msg, pipeline, session, (c, saved) => {
1314
- anyCompressed = anyCompressed || c;
1315
- totalTokensSaved += saved;
1316
- });
1356
+ return compressMessage(msg, pipeline, session, (c, saved) => {
1357
+ anyCompressed = anyCompressed || c;
1358
+ totalTokensSaved += saved;
1359
+ });
1360
+ })
1361
+ );
1362
+ return { messages: compressed, anyCompressed, totalTokensSaved };
1363
+ }
1364
+ async function compressConversation(pipeline, session, plan, options = { compressToolResults: true }) {
1365
+ if (!plan.shouldCompress) {
1366
+ return {
1367
+ messages: plan.messages.map((tm) => tm.message),
1368
+ anyCompressed: false,
1369
+ totalTokensSaved: 0
1370
+ };
1371
+ }
1372
+ let anyCompressed = false;
1373
+ let totalTokensSaved = 0;
1374
+ const record = (c, saved) => {
1375
+ anyCompressed = anyCompressed || c;
1376
+ totalTokensSaved += saved;
1377
+ };
1378
+ const log = options.logFn;
1379
+ const compressed = await Promise.all(
1380
+ plan.messages.map(async (tm) => {
1381
+ const role = tm.message.role;
1382
+ const blockTypes = Array.isArray(tm.message.content) ? tm.message.content.map((b) => b.type).join(",") : "string";
1383
+ if (tm.tier === "hot") {
1384
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 HOT (verbatim)`);
1385
+ return tm.message;
1317
1386
  }
1318
- if (Array.isArray(msg.content)) {
1319
- return compressArrayContent(msg, pipeline, session, (c, saved) => {
1320
- anyCompressed = anyCompressed || c;
1321
- totalTokensSaved += saved;
1322
- });
1387
+ if (tm.eligibleTokens === 0) {
1388
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (0 eligible tok, skip)`);
1389
+ return tm.message;
1323
1390
  }
1324
- return msg;
1391
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (${tm.eligibleTokens} eligible tok, batch compressing)`);
1392
+ return batchCompressMessage(tm.message, pipeline, session, record, options);
1325
1393
  })
1326
1394
  );
1327
1395
  return { messages: compressed, anyCompressed, totalTokensSaved };
1328
1396
  }
1329
- async function compressStringContent(msg, pipeline, session, record) {
1330
- const text = msg.content;
1331
- const segments = segmentContent(text);
1332
- const hasCode = segments.some((s) => s.type === "code");
1333
- if (!hasCode) {
1334
- try {
1335
- const result = await pipeline.compressForLLM(text);
1336
- session.recordCompression(result.metrics);
1337
- record(!result.metrics.skipped, result.metrics.tokensSaved);
1338
- return { ...msg, content: result.text };
1339
- } catch (err) {
1340
- if (err instanceof RSCCircuitOpenError) {
1341
- session.recordFailure();
1342
- throw err;
1397
+ function extractToolResultText(part) {
1398
+ if (typeof part.content === "string" && part.content.trim()) {
1399
+ return part.content;
1400
+ }
1401
+ if (Array.isArray(part.content)) {
1402
+ const texts = part.content.filter((inner) => inner.type === "text" && typeof inner.text === "string" && inner.text.trim()).map((inner) => inner.text);
1403
+ return texts.length > 0 ? texts.join("\n") : null;
1404
+ }
1405
+ return null;
1406
+ }
1407
+ async function batchCompressMessage(msg, pipeline, session, record, options = { compressToolResults: true }) {
1408
+ if (typeof msg.content === "string") {
1409
+ return compressStringContent(msg, pipeline, session, record, options.semaphore, options.semaphoreTimeoutMs);
1410
+ }
1411
+ if (!Array.isArray(msg.content)) return msg;
1412
+ const parts = msg.content;
1413
+ const textSegments = [];
1414
+ const batchedIndices = /* @__PURE__ */ new Set();
1415
+ for (let i = 0; i < parts.length; i++) {
1416
+ const part = parts[i];
1417
+ if (PASSTHROUGH_BLOCK_TYPES.has(part.type)) continue;
1418
+ if (part.type === "text" && typeof part.text === "string" && part.text.trim()) {
1419
+ textSegments.push(part.text);
1420
+ batchedIndices.add(i);
1421
+ }
1422
+ if (part.type === "tool_result" && options.compressToolResults) {
1423
+ const extracted = extractToolResultText(part);
1424
+ if (extracted) {
1425
+ textSegments.push(extracted);
1426
+ batchedIndices.add(i);
1343
1427
  }
1344
- session.recordFailure();
1345
- return msg;
1346
1428
  }
1347
1429
  }
1430
+ if (textSegments.length === 0) return msg;
1431
+ const batchText = textSegments.join("\n\n");
1348
1432
  try {
1349
- const parts = await Promise.all(
1350
- segments.map(async (seg) => {
1351
- if (seg.type === "code") return seg.text;
1352
- if (seg.text.trim().length === 0) return seg.text;
1353
- try {
1354
- const result = await pipeline.compressForLLM(seg.text);
1355
- session.recordCompression(result.metrics);
1356
- record(!result.metrics.skipped, result.metrics.tokensSaved);
1357
- return result.text;
1358
- } catch (err) {
1359
- if (err instanceof RSCCircuitOpenError) throw err;
1360
- session.recordFailure();
1361
- return seg.text;
1433
+ const compressed = await compressTextWithSegmentation(batchText, pipeline, session, record, options.semaphore, options.semaphoreTimeoutMs);
1434
+ const newParts = [];
1435
+ let isFirstEligible = true;
1436
+ for (let i = 0; i < parts.length; i++) {
1437
+ if (!batchedIndices.has(i)) {
1438
+ newParts.push(parts[i]);
1439
+ continue;
1440
+ }
1441
+ if (isFirstEligible) {
1442
+ if (parts[i].type === "text") {
1443
+ newParts.push({ ...parts[i], text: compressed });
1444
+ } else if (parts[i].type === "tool_result") {
1445
+ newParts.push({ ...parts[i], content: compressed });
1362
1446
  }
1363
- })
1364
- );
1365
- return { ...msg, content: parts.join("") };
1447
+ isFirstEligible = false;
1448
+ } else {
1449
+ if (parts[i].type === "tool_result") {
1450
+ newParts.push({ ...parts[i], content: "" });
1451
+ }
1452
+ }
1453
+ }
1454
+ return { ...msg, content: newParts };
1366
1455
  } catch (err) {
1367
1456
  if (err instanceof RSCCircuitOpenError) {
1368
1457
  session.recordFailure();
@@ -1372,37 +1461,34 @@ async function compressStringContent(msg, pipeline, session, record) {
1372
1461
  return msg;
1373
1462
  }
1374
1463
  }
1375
- async function compressTextWithSegmentation(text, pipeline, session, record) {
1376
- const segments = segmentContent(text);
1377
- const hasCode = segments.some((s) => s.type === "code");
1378
- if (!hasCode) {
1379
- const result = await pipeline.compressForLLM(text);
1380
- session.recordCompression(result.metrics);
1381
- record(!result.metrics.skipped, result.metrics.tokensSaved);
1382
- return result.text;
1464
+ async function compressMessage(msg, pipeline, session, record, options = { compressToolResults: true }) {
1465
+ if (typeof msg.content === "string") {
1466
+ return compressStringContent(msg, pipeline, session, record);
1383
1467
  }
1384
- const parts = await Promise.all(
1385
- segments.map(async (seg) => {
1386
- if (seg.type === "code") return seg.text;
1387
- if (seg.text.trim().length === 0) return seg.text;
1388
- try {
1389
- const result = await pipeline.compressForLLM(seg.text);
1390
- session.recordCompression(result.metrics);
1391
- record(!result.metrics.skipped, result.metrics.tokensSaved);
1392
- return result.text;
1393
- } catch (err) {
1394
- if (err instanceof RSCCircuitOpenError) throw err;
1395
- session.recordFailure();
1396
- return seg.text;
1397
- }
1398
- })
1399
- );
1400
- return parts.join("");
1468
+ if (Array.isArray(msg.content)) {
1469
+ return compressArrayContent(msg, pipeline, session, record, options);
1470
+ }
1471
+ return msg;
1401
1472
  }
1402
- async function compressArrayContent(msg, pipeline, session, record) {
1473
+ async function compressStringContent(msg, pipeline, session, record, semaphore, semaphoreTimeoutMs) {
1474
+ const text = msg.content;
1475
+ try {
1476
+ const compressed = await compressTextWithSegmentation(text, pipeline, session, record, semaphore, semaphoreTimeoutMs);
1477
+ return { ...msg, content: compressed };
1478
+ } catch (err) {
1479
+ if (err instanceof RSCCircuitOpenError) {
1480
+ session.recordFailure();
1481
+ throw err;
1482
+ }
1483
+ session.recordFailure();
1484
+ return msg;
1485
+ }
1486
+ }
1487
+ async function compressArrayContent(msg, pipeline, session, record, options = { compressToolResults: true }) {
1403
1488
  const parts = msg.content;
1404
1489
  const compressedParts = await Promise.all(
1405
1490
  parts.map(async (part) => {
1491
+ if (PASSTHROUGH_BLOCK_TYPES.has(part.type)) return part;
1406
1492
  if (part.type === "text" && typeof part.text === "string") {
1407
1493
  try {
1408
1494
  const compressed = await compressTextWithSegmentation(part.text, pipeline, session, record);
@@ -1416,11 +1502,183 @@ async function compressArrayContent(msg, pipeline, session, record) {
1416
1502
  return part;
1417
1503
  }
1418
1504
  }
1505
+ if (part.type === "tool_result" && options.compressToolResults) {
1506
+ return compressToolResult(part, pipeline, session, record);
1507
+ }
1419
1508
  return part;
1420
1509
  })
1421
1510
  );
1422
1511
  return { ...msg, content: compressedParts };
1423
1512
  }
1513
+ async function compressToolResult(part, pipeline, session, record) {
1514
+ const content = part.content;
1515
+ if (typeof content === "string") {
1516
+ try {
1517
+ const compressed = await compressTextWithSegmentation(content, pipeline, session, record);
1518
+ return { ...part, content: compressed };
1519
+ } catch (err) {
1520
+ if (err instanceof RSCCircuitOpenError) {
1521
+ session.recordFailure();
1522
+ throw err;
1523
+ }
1524
+ session.recordFailure();
1525
+ return part;
1526
+ }
1527
+ }
1528
+ if (Array.isArray(content)) {
1529
+ try {
1530
+ const compressedInner = await Promise.all(
1531
+ content.map(async (inner) => {
1532
+ if (inner.type === "text" && typeof inner.text === "string") {
1533
+ try {
1534
+ const compressed = await compressTextWithSegmentation(inner.text, pipeline, session, record);
1535
+ return { ...inner, text: compressed };
1536
+ } catch (err) {
1537
+ if (err instanceof RSCCircuitOpenError) throw err;
1538
+ session.recordFailure();
1539
+ return inner;
1540
+ }
1541
+ }
1542
+ return inner;
1543
+ })
1544
+ );
1545
+ return { ...part, content: compressedInner };
1546
+ } catch (err) {
1547
+ if (err instanceof RSCCircuitOpenError) {
1548
+ session.recordFailure();
1549
+ throw err;
1550
+ }
1551
+ session.recordFailure();
1552
+ return part;
1553
+ }
1554
+ }
1555
+ return part;
1556
+ }
1557
+ async function compressTextWithSegmentation(text, pipeline, session, record, semaphore, semaphoreTimeoutMs) {
1558
+ const segments = segmentContent(text);
1559
+ const hasCode = segments.some((s) => s.type === "code");
1560
+ if (!hasCode) {
1561
+ if (semaphore) await semaphore.acquire(semaphoreTimeoutMs);
1562
+ try {
1563
+ const result = await pipeline.compressForLLM(text);
1564
+ session.recordCompression(result.metrics);
1565
+ const saved = Math.max(0, result.metrics.tokensSaved);
1566
+ record(!result.metrics.skipped, saved);
1567
+ return sanitizeCompressedText(result.text);
1568
+ } finally {
1569
+ if (semaphore) semaphore.release();
1570
+ }
1571
+ }
1572
+ const parts = await Promise.all(
1573
+ segments.map(async (seg) => {
1574
+ if (seg.type === "code") return seg.text;
1575
+ if (seg.text.trim().length === 0) return seg.text;
1576
+ if (semaphore) await semaphore.acquire(semaphoreTimeoutMs);
1577
+ try {
1578
+ const result = await pipeline.compressForLLM(seg.text);
1579
+ session.recordCompression(result.metrics);
1580
+ const saved = Math.max(0, result.metrics.tokensSaved);
1581
+ record(!result.metrics.skipped, saved);
1582
+ return sanitizeCompressedText(result.text);
1583
+ } catch (err) {
1584
+ if (err instanceof RSCCircuitOpenError) throw err;
1585
+ session.recordFailure();
1586
+ return seg.text;
1587
+ } finally {
1588
+ if (semaphore) semaphore.release();
1589
+ }
1590
+ })
1591
+ );
1592
+ return parts.join("");
1593
+ }
1594
+
1595
+ // src/rsc/conversation-analyzer.ts
1596
+ var SKIP_BLOCK_TYPES = /* @__PURE__ */ new Set(["thinking", "tool_use", "image"]);
1597
+ function estimateTokens(text) {
1598
+ return Math.ceil(text.length / 4);
1599
+ }
1600
+ function estimateBlockTokens(block, compressToolResults) {
1601
+ if (SKIP_BLOCK_TYPES.has(block.type)) return 0;
1602
+ if (block.type === "text" && typeof block.text === "string") {
1603
+ return estimateTokens(block.text);
1604
+ }
1605
+ if (block.type === "tool_result" && compressToolResults) {
1606
+ if (typeof block.content === "string") {
1607
+ return estimateTokens(block.content);
1608
+ }
1609
+ if (Array.isArray(block.content)) {
1610
+ return block.content.reduce(
1611
+ (sum, inner) => sum + estimateBlockTokens(inner, compressToolResults),
1612
+ 0
1613
+ );
1614
+ }
1615
+ }
1616
+ return 0;
1617
+ }
1618
+ function estimateMessageTokens(msg, config) {
1619
+ if (!config.compressRoles.has(msg.role)) return 0;
1620
+ if (typeof msg.content === "string") {
1621
+ return estimateTokens(msg.content);
1622
+ }
1623
+ if (Array.isArray(msg.content)) {
1624
+ return msg.content.reduce(
1625
+ (sum, part) => sum + estimateBlockTokens(part, config.compressToolResults),
1626
+ 0
1627
+ );
1628
+ }
1629
+ return 0;
1630
+ }
1631
+ function analyzeConversation(messages, config) {
1632
+ const n = messages.length;
1633
+ if (n < 5) {
1634
+ const tiered2 = messages.map((msg, i) => ({
1635
+ index: i,
1636
+ message: msg,
1637
+ tier: "hot",
1638
+ eligibleTokens: estimateMessageTokens(msg, config)
1639
+ }));
1640
+ return {
1641
+ messages: tiered2,
1642
+ totalEligibleTokens: 0,
1643
+ shouldCompress: false,
1644
+ hotCount: n,
1645
+ warmCount: 0,
1646
+ coldCount: 0
1647
+ };
1648
+ }
1649
+ const coldEnd = Math.floor(n * config.coldFraction);
1650
+ const hotStart = n - Math.floor(n * config.hotFraction);
1651
+ let totalEligibleTokens = 0;
1652
+ let hotCount = 0;
1653
+ let warmCount = 0;
1654
+ let coldCount = 0;
1655
+ const tiered = messages.map((msg, i) => {
1656
+ let tier;
1657
+ if (i >= hotStart) {
1658
+ tier = "hot";
1659
+ hotCount++;
1660
+ } else if (i < coldEnd) {
1661
+ tier = "cold";
1662
+ coldCount++;
1663
+ } else {
1664
+ tier = "warm";
1665
+ warmCount++;
1666
+ }
1667
+ const eligibleTokens = estimateMessageTokens(msg, config);
1668
+ if (tier !== "hot") {
1669
+ totalEligibleTokens += eligibleTokens;
1670
+ }
1671
+ return { index: i, message: msg, tier, eligibleTokens };
1672
+ });
1673
+ return {
1674
+ messages: tiered,
1675
+ totalEligibleTokens,
1676
+ shouldCompress: totalEligibleTokens >= config.aggregateThreshold,
1677
+ hotCount,
1678
+ warmCount,
1679
+ coldCount
1680
+ };
1681
+ }
1424
1682
 
1425
1683
  // src/rsc/learning.ts
1426
1684
  function createStreamLearningBuffer(pipeline) {
@@ -1523,6 +1781,25 @@ async function pipeSSEResponse(upstreamResponse, clientRes, onContentDelta, onCo
1523
1781
  }
1524
1782
  }
1525
1783
 
1784
+ // src/terminology.ts
1785
+ var TIER_LABELS = {
1786
+ HOT: "Active",
1787
+ WARM: "Recent",
1788
+ COLD: "Archived"
1789
+ };
1790
+ function formatTiersLog(hot, warm, cold, eligibleTokens) {
1791
+ return `[MEMORY] ${TIER_LABELS.HOT}:${hot} ${TIER_LABELS.WARM}:${warm} ${TIER_LABELS.COLD}:${cold} \xB7 ${eligibleTokens} tokens eligible`;
1792
+ }
1793
+ function formatSavedLog(tokensSaved, latencyMs) {
1794
+ return `[SAVED] ${tokensSaved} tokens (${latencyMs}ms)`;
1795
+ }
1796
+ function formatDegradeLog() {
1797
+ return "[STATUS] Connection degraded \u2014 passing through directly";
1798
+ }
1799
+ function formatResponseLog(model, tokensSaved, streaming = false) {
1800
+ return `[RESPONSE] ${streaming ? "Streaming " : ""}${model} response \u2192 client (saved:${tokensSaved}tok)`;
1801
+ }
1802
+
1526
1803
  // src/proxy/completions.ts
1527
1804
  function setCORSHeaders(res) {
1528
1805
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -1539,7 +1816,7 @@ function extractBearerToken(req) {
1539
1816
  if (!auth || !auth.startsWith("Bearer ")) return null;
1540
1817
  return auth.slice(7);
1541
1818
  }
1542
- async function handleChatCompletions(req, res, body, pipeline, config, logger) {
1819
+ async function handleChatCompletions(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey) {
1543
1820
  const request = body;
1544
1821
  if (!request.messages || !Array.isArray(request.messages)) {
1545
1822
  sendJSON(res, 400, {
@@ -1558,23 +1835,44 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger) {
1558
1835
  let anyCompressed = false;
1559
1836
  let totalTokensSaved = 0;
1560
1837
  if (config.enabled && !pipeline.isCircuitOpen()) {
1838
+ const compressStart = Date.now();
1561
1839
  try {
1562
1840
  const compressRoles = new Set(config.compressRoles);
1563
- const result = await compressMessages(
1564
- request.messages,
1841
+ const plan = analyzeConversation(request.messages, {
1842
+ hotFraction: config.hotFraction,
1843
+ coldFraction: config.coldFraction,
1844
+ aggregateThreshold: config.aggregateThreshold,
1845
+ compressRoles,
1846
+ compressToolResults: config.compressToolResults
1847
+ });
1848
+ if (plan.shouldCompress) {
1849
+ logger.log(formatTiersLog(plan.hotCount, plan.warmCount, plan.coldCount, plan.totalEligibleTokens));
1850
+ }
1851
+ const result = await compressConversation(
1565
1852
  pipeline.pipeline,
1566
1853
  pipeline.session,
1567
- compressRoles
1854
+ plan,
1855
+ {
1856
+ compressToolResults: config.compressToolResults,
1857
+ logFn: (msg) => logger.log(msg),
1858
+ semaphore,
1859
+ semaphoreTimeoutMs: config.concurrencyTimeoutMs
1860
+ }
1568
1861
  );
1569
1862
  messages = result.messages;
1570
1863
  anyCompressed = result.anyCompressed;
1571
1864
  totalTokensSaved = result.totalTokensSaved;
1865
+ const latencyMs = Date.now() - compressStart;
1866
+ const alert = latencyMonitor.record(sessionKey, latencyMs);
1867
+ if (alert) {
1868
+ logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message}`);
1869
+ }
1572
1870
  if (result.totalTokensSaved > 0) {
1573
- logger.log(`[COMPRESS] Saved ${result.totalTokensSaved} tokens`);
1871
+ logger.log(formatSavedLog(result.totalTokensSaved, latencyMs));
1574
1872
  }
1575
1873
  } catch (err) {
1576
1874
  if (err instanceof RSCCircuitOpenError2) {
1577
- logger.log("[DEGRADE] Circuit breaker open \u2014 passing through directly");
1875
+ logger.log(formatDegradeLog());
1578
1876
  } else {
1579
1877
  logger.log(`[ERROR] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
1580
1878
  }
@@ -1607,6 +1905,7 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger) {
1607
1905
  }
1608
1906
  if (request.stream && upstreamResponse.body) {
1609
1907
  const learningBuffer = anyCompressed ? createStreamLearningBuffer(pipeline.pipeline) : null;
1908
+ logger.log(formatResponseLog(request.model, totalTokensSaved, true));
1610
1909
  await pipeSSEResponse(
1611
1910
  upstreamResponse,
1612
1911
  res,
@@ -1617,6 +1916,7 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger) {
1617
1916
  return;
1618
1917
  }
1619
1918
  const responseBody = await upstreamResponse.text();
1919
+ logger.log(formatResponseLog(request.model, totalTokensSaved));
1620
1920
  let finalBody = responseBody;
1621
1921
  if (totalTokensSaved > 0) {
1622
1922
  try {
@@ -1757,7 +2057,7 @@ async function pipeAnthropicSSEResponse(upstreamResponse, clientRes, onContentDe
1757
2057
  function setCORSHeaders2(res) {
1758
2058
  res.setHeader("Access-Control-Allow-Origin", "*");
1759
2059
  res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
1760
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta");
2060
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta, x-liminal-session");
1761
2061
  }
1762
2062
  function sendAnthropicError(res, status, type, message) {
1763
2063
  setCORSHeaders2(res);
@@ -1788,7 +2088,7 @@ function convertCompressedToAnthropic(messages) {
1788
2088
  content: msg.content
1789
2089
  }));
1790
2090
  }
1791
- async function handleAnthropicMessages(req, res, body, pipeline, config, logger) {
2091
+ async function handleAnthropicMessages(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey) {
1792
2092
  const request = body;
1793
2093
  if (!request.messages || !Array.isArray(request.messages)) {
1794
2094
  sendAnthropicError(res, 400, "invalid_request_error", "messages is required and must be an array");
@@ -1807,24 +2107,45 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger)
1807
2107
  let anyCompressed = false;
1808
2108
  let totalTokensSaved = 0;
1809
2109
  if (config.enabled && !pipeline.isCircuitOpen()) {
2110
+ const compressStart = Date.now();
1810
2111
  try {
1811
2112
  const compressRoles = new Set(config.compressRoles);
1812
2113
  const compressible = convertAnthropicToCompressible(request.messages);
1813
- const result = await compressMessages(
1814
- compressible,
2114
+ const plan = analyzeConversation(compressible, {
2115
+ hotFraction: config.hotFraction,
2116
+ coldFraction: config.coldFraction,
2117
+ aggregateThreshold: config.aggregateThreshold,
2118
+ compressRoles,
2119
+ compressToolResults: config.compressToolResults
2120
+ });
2121
+ if (plan.shouldCompress) {
2122
+ logger.log(formatTiersLog(plan.hotCount, plan.warmCount, plan.coldCount, plan.totalEligibleTokens));
2123
+ }
2124
+ const result = await compressConversation(
1815
2125
  pipeline.pipeline,
1816
2126
  pipeline.session,
1817
- compressRoles
2127
+ plan,
2128
+ {
2129
+ compressToolResults: config.compressToolResults,
2130
+ logFn: (msg) => logger.log(msg),
2131
+ semaphore,
2132
+ semaphoreTimeoutMs: config.concurrencyTimeoutMs
2133
+ }
1818
2134
  );
1819
2135
  messages = convertCompressedToAnthropic(result.messages);
1820
2136
  anyCompressed = result.anyCompressed;
1821
2137
  totalTokensSaved = result.totalTokensSaved;
2138
+ const latencyMs = Date.now() - compressStart;
2139
+ const alert = latencyMonitor.record(sessionKey, latencyMs);
2140
+ if (alert) {
2141
+ logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message}`);
2142
+ }
1822
2143
  if (result.totalTokensSaved > 0) {
1823
- logger.log(`[COMPRESS] Saved ${result.totalTokensSaved} tokens`);
2144
+ logger.log(formatSavedLog(result.totalTokensSaved, latencyMs));
1824
2145
  }
1825
2146
  } catch (err) {
1826
2147
  if (err instanceof RSCCircuitOpenError3) {
1827
- logger.log("[DEGRADE] Circuit breaker open -- passing through directly");
2148
+ logger.log(formatDegradeLog());
1828
2149
  } else {
1829
2150
  logger.log(`[ERROR] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
1830
2151
  }
@@ -1862,6 +2183,7 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger)
1862
2183
  }
1863
2184
  if (request.stream && upstreamResponse.body) {
1864
2185
  const learningBuffer = anyCompressed ? createStreamLearningBuffer(pipeline.pipeline) : null;
2186
+ logger.log(formatResponseLog(request.model, totalTokensSaved, true));
1865
2187
  await pipeAnthropicSSEResponse(
1866
2188
  upstreamResponse,
1867
2189
  res,
@@ -1872,6 +2194,7 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger)
1872
2194
  return;
1873
2195
  }
1874
2196
  const responseBody = await upstreamResponse.text();
2197
+ logger.log(formatResponseLog(request.model, totalTokensSaved));
1875
2198
  let finalBody = responseBody;
1876
2199
  if (totalTokensSaved > 0) {
1877
2200
  try {
@@ -2102,7 +2425,7 @@ function extractOutputText(output) {
2102
2425
  }
2103
2426
  return texts.join("");
2104
2427
  }
2105
- async function handleResponses(req, res, body, pipeline, config, logger) {
2428
+ async function handleResponses(req, res, body, pipeline, config, logger, semaphore, latencyMonitor, sessionKey) {
2106
2429
  const request = body;
2107
2430
  if (request.input === void 0 || request.input === null) {
2108
2431
  sendJSON2(res, 400, {
@@ -2121,6 +2444,7 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2121
2444
  let anyCompressed = false;
2122
2445
  let totalTokensSaved = 0;
2123
2446
  if (config.enabled && !pipeline.isCircuitOpen()) {
2447
+ const compressStart = Date.now();
2124
2448
  try {
2125
2449
  const compressRoles = new Set(config.compressRoles);
2126
2450
  const compressible = inputToCompressibleMessages(request.input);
@@ -2134,13 +2458,18 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2134
2458
  compressedInput = applyCompressedToInput(request.input, result.messages);
2135
2459
  anyCompressed = result.anyCompressed;
2136
2460
  totalTokensSaved = result.totalTokensSaved;
2461
+ const latencyMs = Date.now() - compressStart;
2462
+ const alert = latencyMonitor.record(sessionKey, latencyMs);
2463
+ if (alert) {
2464
+ logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message}`);
2465
+ }
2137
2466
  if (result.totalTokensSaved > 0) {
2138
- logger.log(`[COMPRESS] Responses API: saved ${result.totalTokensSaved} tokens`);
2467
+ logger.log(formatSavedLog(result.totalTokensSaved, latencyMs));
2139
2468
  }
2140
2469
  }
2141
2470
  } catch (err) {
2142
2471
  if (err instanceof RSCCircuitOpenError4) {
2143
- logger.log("[DEGRADE] Circuit breaker open \u2014 passing through directly");
2472
+ logger.log(formatDegradeLog());
2144
2473
  } else {
2145
2474
  logger.log(`[ERROR] Compression failed: ${err instanceof Error ? err.message : String(err)}`);
2146
2475
  }
@@ -2175,6 +2504,7 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2175
2504
  }
2176
2505
  if (request.stream && upstreamResponse.body) {
2177
2506
  const learningBuffer = anyCompressed ? createStreamLearningBuffer(pipeline.pipeline) : null;
2507
+ logger.log(formatResponseLog(request.model, totalTokensSaved, true));
2178
2508
  await pipeResponsesSSE(
2179
2509
  upstreamResponse,
2180
2510
  res,
@@ -2185,6 +2515,7 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2185
2515
  return;
2186
2516
  }
2187
2517
  const responseBody = await upstreamResponse.text();
2518
+ logger.log(formatResponseLog(request.model, totalTokensSaved));
2188
2519
  let finalBody = responseBody;
2189
2520
  if (totalTokensSaved > 0) {
2190
2521
  try {
@@ -2226,11 +2557,51 @@ async function handleResponses(req, res, body, pipeline, config, logger) {
2226
2557
  }
2227
2558
  }
2228
2559
 
2560
+ // src/proxy/session-identity.ts
2561
+ import * as crypto from "crypto";
2562
+ function identifySession(req, pathname) {
2563
+ const connector = detectConnector(req, pathname);
2564
+ const windowHash = deriveWindowHash(req);
2565
+ return { connector, windowHash, raw: `${connector}:${windowHash}` };
2566
+ }
2567
+ function detectConnector(req, pathname) {
2568
+ if (req.headers["x-api-key"] || req.headers["anthropic-version"]) {
2569
+ return "claude-code";
2570
+ }
2571
+ if (pathname.startsWith("/v1/responses") || pathname.startsWith("/responses")) {
2572
+ return "codex";
2573
+ }
2574
+ const ua = req.headers["user-agent"] ?? "";
2575
+ if (/cursor/i.test(ua)) {
2576
+ return "cursor";
2577
+ }
2578
+ return "openai-compatible";
2579
+ }
2580
+ function deriveWindowHash(req) {
2581
+ const liminalSession = req.headers["x-liminal-session"];
2582
+ if (typeof liminalSession === "string" && liminalSession.length > 0) {
2583
+ return liminalSession;
2584
+ }
2585
+ const credential = extractCredential(req);
2586
+ if (!credential) return "anonymous";
2587
+ return crypto.createHash("sha256").update(credential).digest("hex").slice(0, 8);
2588
+ }
2589
+ function extractCredential(req) {
2590
+ const apiKey = req.headers["x-api-key"];
2591
+ if (typeof apiKey === "string" && apiKey.length > 0) return apiKey;
2592
+ const auth = req.headers["authorization"];
2593
+ if (typeof auth === "string") {
2594
+ const match = auth.match(/^Bearer\s+(.+)$/i);
2595
+ if (match) return match[1];
2596
+ }
2597
+ return null;
2598
+ }
2599
+
2229
2600
  // src/proxy/handler.ts
2230
2601
  function setCORSHeaders4(res) {
2231
2602
  res.setHeader("Access-Control-Allow-Origin", "*");
2232
2603
  res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PATCH");
2233
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta, anthropic-dangerous-direct-browser-access");
2604
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta, anthropic-dangerous-direct-browser-access, x-liminal-session");
2234
2605
  res.setHeader("Access-Control-Max-Age", "86400");
2235
2606
  }
2236
2607
  function sendJSON3(res, status, body) {
@@ -2331,7 +2702,8 @@ async function passthroughToUpstream(req, res, fullUrl, config, logger) {
2331
2702
  }
2332
2703
  }
2333
2704
  }
2334
- function createRequestHandler(pipeline, config, logger) {
2705
+ function createRequestHandler(deps) {
2706
+ const { sessions, semaphore, latencyMonitor, config, logger } = deps;
2335
2707
  const startTime = Date.now();
2336
2708
  return async (req, res) => {
2337
2709
  try {
@@ -2347,27 +2719,37 @@ function createRequestHandler(pipeline, config, logger) {
2347
2719
  return;
2348
2720
  }
2349
2721
  if (method === "GET" && (url === "/health" || url === "/")) {
2350
- const summary = pipeline.getSessionSummary();
2722
+ const sessionSummaries = sessions.getAllSummaries();
2351
2723
  sendJSON3(res, 200, {
2352
2724
  status: "ok",
2353
2725
  version: config.rscApiKey ? "connected" : "no-api-key",
2354
- rsc_connected: !pipeline.isCircuitOpen(),
2355
- circuit_state: pipeline.getCircuitState(),
2356
- session_id: summary.sessionId,
2357
2726
  uptime_ms: Date.now() - startTime,
2358
- session: {
2359
- tokens_processed: summary.tokensProcessed,
2360
- tokens_saved: summary.tokensSaved,
2361
- calls_total: summary.totalCalls,
2362
- calls_compressed: summary.compressedCalls,
2363
- calls_skipped: summary.skippedCalls,
2364
- calls_failed: summary.failedCalls,
2365
- patterns_learned: summary.patternsLearned,
2366
- estimated_cost_saved_usd: summary.estimatedCostSaved
2367
- }
2727
+ concurrency: {
2728
+ active_sessions: sessions.activeCount,
2729
+ semaphore_available: semaphore.available,
2730
+ semaphore_waiting: semaphore.waiting,
2731
+ max_concurrent_rsc_calls: config.concurrencyLimit
2732
+ },
2733
+ latency: {
2734
+ global_p95_ms: latencyMonitor.getGlobalP95()
2735
+ },
2736
+ sessions: sessionSummaries.map((s) => ({
2737
+ session_key: s.key,
2738
+ connector: s.connector,
2739
+ circuit_state: s.circuitState,
2740
+ tokens_processed: s.tokensProcessed,
2741
+ tokens_saved: s.tokensSaved,
2742
+ calls_total: s.totalCalls,
2743
+ calls_compressed: s.compressedCalls,
2744
+ calls_failed: s.failedCalls,
2745
+ p95_latency_ms: latencyMonitor.getSessionP95(s.key),
2746
+ last_active_ago_ms: Date.now() - s.lastAccessedAt
2747
+ }))
2368
2748
  });
2369
2749
  return;
2370
2750
  }
2751
+ const sessionKey = identifySession(req, url);
2752
+ const pipeline = sessions.getOrCreate(sessionKey);
2371
2753
  if (method === "POST" && (url === "/v1/chat/completions" || url === "/chat/completions")) {
2372
2754
  const body = await readBody(req);
2373
2755
  let parsed;
@@ -2379,7 +2761,7 @@ function createRequestHandler(pipeline, config, logger) {
2379
2761
  });
2380
2762
  return;
2381
2763
  }
2382
- await handleChatCompletions(req, res, parsed, pipeline, config, logger);
2764
+ await handleChatCompletions(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw);
2383
2765
  return;
2384
2766
  }
2385
2767
  if (method === "POST" && (url === "/v1/responses" || url === "/responses")) {
@@ -2393,7 +2775,7 @@ function createRequestHandler(pipeline, config, logger) {
2393
2775
  });
2394
2776
  return;
2395
2777
  }
2396
- await handleResponses(req, res, parsed, pipeline, config, logger);
2778
+ await handleResponses(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw);
2397
2779
  return;
2398
2780
  }
2399
2781
  if (method === "POST" && (url === "/v1/messages" || url === "/messages")) {
@@ -2408,7 +2790,7 @@ function createRequestHandler(pipeline, config, logger) {
2408
2790
  });
2409
2791
  return;
2410
2792
  }
2411
- await handleAnthropicMessages(req, res, parsed, pipeline, config, logger);
2793
+ await handleAnthropicMessages(req, res, parsed, pipeline, config, logger, semaphore, latencyMonitor, sessionKey.raw);
2412
2794
  return;
2413
2795
  }
2414
2796
  await passthroughToUpstream(req, res, fullUrl, config, logger);
@@ -2432,9 +2814,11 @@ var ProxyServer = class {
2432
2814
  activePort = null;
2433
2815
  requestedPort;
2434
2816
  handler;
2435
- constructor(port, handler) {
2817
+ connectHandler;
2818
+ constructor(port, handler, connectHandler) {
2436
2819
  this.requestedPort = port;
2437
2820
  this.handler = handler;
2821
+ this.connectHandler = connectHandler ?? null;
2438
2822
  }
2439
2823
  async start() {
2440
2824
  let lastError = null;
@@ -2456,6 +2840,9 @@ var ProxyServer = class {
2456
2840
  listen(port) {
2457
2841
  return new Promise((resolve, reject) => {
2458
2842
  const server = http.createServer(this.handler);
2843
+ if (this.connectHandler) {
2844
+ server.on("connect", this.connectHandler);
2845
+ }
2459
2846
  server.on("error", reject);
2460
2847
  server.listen(port, "127.0.0.1", () => {
2461
2848
  server.removeListener("error", reject);
@@ -2480,6 +2867,10 @@ var ProxyServer = class {
2480
2867
  getPort() {
2481
2868
  return this.activePort;
2482
2869
  }
2870
+ /** Expose internal HTTP server for MITM bridge socket injection */
2871
+ getHttpServer() {
2872
+ return this.server;
2873
+ }
2483
2874
  };
2484
2875
 
2485
2876
  // src/daemon/logger.ts
@@ -2530,17 +2921,668 @@ var FileLogger = class {
2530
2921
  }
2531
2922
  };
2532
2923
 
2924
+ // src/rsc/session-manager.ts
2925
+ init_pipeline();
2926
+ var DEFAULT_MAX_SESSIONS = 10;
2927
+ var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
2928
+ var EVICTION_INTERVAL_MS = 6e4;
2929
+ var SessionManager = class {
2930
+ sessions = /* @__PURE__ */ new Map();
2931
+ config;
2932
+ evictionTimer = null;
2933
+ constructor(config) {
2934
+ this.config = {
2935
+ ...config,
2936
+ maxSessions: config.maxSessions ?? DEFAULT_MAX_SESSIONS,
2937
+ sessionTtlMs: config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS
2938
+ };
2939
+ this.evictionTimer = setInterval(() => this.evictStale(), EVICTION_INTERVAL_MS);
2940
+ if (this.evictionTimer.unref) {
2941
+ this.evictionTimer.unref();
2942
+ }
2943
+ }
2944
+ // ── Public API ──────────────────────────────────────────────────────
2945
+ getOrCreate(key) {
2946
+ const existing = this.sessions.get(key.raw);
2947
+ if (existing) {
2948
+ existing.lastAccessedAt = Date.now();
2949
+ existing.requestCount++;
2950
+ return existing.pipeline;
2951
+ }
2952
+ if (this.sessions.size >= this.config.maxSessions) {
2953
+ this.evictLRU();
2954
+ }
2955
+ const pipeline = new RSCPipelineWrapper({
2956
+ ...this.config.pipelineConfig,
2957
+ sessionId: key.raw
2958
+ });
2959
+ this.sessions.set(key.raw, {
2960
+ pipeline,
2961
+ lastAccessedAt: Date.now(),
2962
+ requestCount: 1,
2963
+ connector: key.connector
2964
+ });
2965
+ this.config.onSessionCreated?.(key.raw, pipeline);
2966
+ return pipeline;
2967
+ }
2968
+ getAllSummaries() {
2969
+ const entries = [];
2970
+ for (const [key, managed] of this.sessions) {
2971
+ entries.push(this.buildHealthEntry(key, managed));
2972
+ }
2973
+ return entries;
2974
+ }
2975
+ getSessionSummary(key) {
2976
+ const managed = this.sessions.get(key);
2977
+ if (!managed) return null;
2978
+ return this.buildHealthEntry(key, managed);
2979
+ }
2980
+ get activeCount() {
2981
+ return this.sessions.size;
2982
+ }
2983
+ shutdown() {
2984
+ if (this.evictionTimer !== null) {
2985
+ clearInterval(this.evictionTimer);
2986
+ this.evictionTimer = null;
2987
+ }
2988
+ this.sessions.clear();
2989
+ }
2990
+ // ── Internals ───────────────────────────────────────────────────────
2991
+ buildHealthEntry(key, managed) {
2992
+ const summary = managed.pipeline.getSessionSummary();
2993
+ return {
2994
+ key,
2995
+ connector: managed.connector,
2996
+ circuitState: managed.pipeline.getCircuitState(),
2997
+ tokensProcessed: summary.tokensProcessed,
2998
+ tokensSaved: summary.tokensSaved,
2999
+ totalCalls: summary.totalCalls,
3000
+ compressedCalls: summary.compressedCalls,
3001
+ failedCalls: summary.failedCalls,
3002
+ lastAccessedAt: managed.lastAccessedAt
3003
+ };
3004
+ }
3005
+ evictStale() {
3006
+ const now = Date.now();
3007
+ const cutoff = now - this.config.sessionTtlMs;
3008
+ for (const [key, managed] of this.sessions) {
3009
+ if (managed.lastAccessedAt < cutoff) {
3010
+ this.sessions.delete(key);
3011
+ this.config.onSessionEvicted?.(key);
3012
+ }
3013
+ }
3014
+ }
3015
+ evictLRU() {
3016
+ let oldestKey = null;
3017
+ let oldestTime = Infinity;
3018
+ for (const [key, managed] of this.sessions) {
3019
+ if (managed.lastAccessedAt < oldestTime) {
3020
+ oldestTime = managed.lastAccessedAt;
3021
+ oldestKey = key;
3022
+ }
3023
+ }
3024
+ if (oldestKey !== null) {
3025
+ this.sessions.delete(oldestKey);
3026
+ this.config.onSessionEvicted?.(oldestKey);
3027
+ }
3028
+ }
3029
+ };
3030
+
3031
+ // src/rsc/semaphore.ts
3032
+ var SemaphoreTimeoutError = class extends Error {
3033
+ constructor(timeoutMs) {
3034
+ super(`Semaphore acquire timed out after ${timeoutMs}ms`);
3035
+ this.name = "SemaphoreTimeoutError";
3036
+ }
3037
+ };
3038
+ var Semaphore = class {
3039
+ permits;
3040
+ queue = [];
3041
+ constructor(maxPermits) {
3042
+ if (maxPermits < 1) throw new RangeError("maxPermits must be >= 1");
3043
+ this.permits = maxPermits;
3044
+ }
3045
+ get available() {
3046
+ return this.permits;
3047
+ }
3048
+ get waiting() {
3049
+ return this.queue.length;
3050
+ }
3051
+ acquire(timeoutMs) {
3052
+ if (this.permits > 0) {
3053
+ this.permits--;
3054
+ return Promise.resolve();
3055
+ }
3056
+ return new Promise((resolve, reject) => {
3057
+ const waiter = { resolve, reject };
3058
+ this.queue.push(waiter);
3059
+ if (timeoutMs !== void 0 && timeoutMs >= 0) {
3060
+ const timer = setTimeout(() => {
3061
+ const idx = this.queue.indexOf(waiter);
3062
+ if (idx !== -1) {
3063
+ this.queue.splice(idx, 1);
3064
+ reject(new SemaphoreTimeoutError(timeoutMs));
3065
+ }
3066
+ }, timeoutMs);
3067
+ const originalResolve = waiter.resolve;
3068
+ waiter.resolve = () => {
3069
+ clearTimeout(timer);
3070
+ originalResolve();
3071
+ };
3072
+ }
3073
+ });
3074
+ }
3075
+ release() {
3076
+ const next = this.queue.shift();
3077
+ if (next) {
3078
+ next.resolve();
3079
+ } else {
3080
+ this.permits++;
3081
+ }
3082
+ }
3083
+ };
3084
+
3085
+ // src/rsc/latency-monitor.ts
3086
+ var DEFAULT_CONFIG = {
3087
+ warningThresholdMs: 4e3,
3088
+ criticalThresholdMs: 8e3,
3089
+ windowSize: 50
3090
+ };
3091
+ var CircularBuffer = class {
3092
+ buffer;
3093
+ index = 0;
3094
+ count = 0;
3095
+ capacity;
3096
+ constructor(capacity) {
3097
+ this.capacity = capacity;
3098
+ this.buffer = new Array(capacity);
3099
+ }
3100
+ push(value) {
3101
+ this.buffer[this.index] = value;
3102
+ this.index = (this.index + 1) % this.capacity;
3103
+ if (this.count < this.capacity) {
3104
+ this.count++;
3105
+ }
3106
+ }
3107
+ getValues() {
3108
+ if (this.count < this.capacity) {
3109
+ return this.buffer.slice(0, this.count);
3110
+ }
3111
+ return [...this.buffer.slice(this.index), ...this.buffer.slice(0, this.index)];
3112
+ }
3113
+ get size() {
3114
+ return this.count;
3115
+ }
3116
+ };
3117
+ function calculateP95(values) {
3118
+ if (values.length === 0) return null;
3119
+ const sorted = [...values].sort((a, b) => a - b);
3120
+ const idx = Math.floor(sorted.length * 0.95);
3121
+ return sorted[Math.min(idx, sorted.length - 1)];
3122
+ }
3123
+ var LatencyMonitor = class {
3124
+ config;
3125
+ sessionWindows = /* @__PURE__ */ new Map();
3126
+ globalWindow;
3127
+ callbacks = [];
3128
+ constructor(config) {
3129
+ this.config = { ...DEFAULT_CONFIG, ...config };
3130
+ this.globalWindow = new CircularBuffer(this.config.windowSize * 4);
3131
+ }
3132
+ record(sessionKey, latencyMs) {
3133
+ let sessionBuf = this.sessionWindows.get(sessionKey);
3134
+ if (!sessionBuf) {
3135
+ sessionBuf = new CircularBuffer(this.config.windowSize);
3136
+ this.sessionWindows.set(sessionKey, sessionBuf);
3137
+ }
3138
+ sessionBuf.push(latencyMs);
3139
+ this.globalWindow.push(latencyMs);
3140
+ const globalP95 = calculateP95(this.globalWindow.getValues());
3141
+ if (globalP95 === null) return null;
3142
+ let alert = null;
3143
+ if (globalP95 >= this.config.criticalThresholdMs) {
3144
+ alert = {
3145
+ type: "critical",
3146
+ message: `Global p95 latency ${globalP95.toFixed(0)}ms exceeds critical threshold ${this.config.criticalThresholdMs}ms`,
3147
+ sessionKey,
3148
+ p95Ms: globalP95,
3149
+ thresholdMs: this.config.criticalThresholdMs,
3150
+ activeSessions: this.sessionWindows.size,
3151
+ suggestion: "Reduce active sessions or increase latency budget"
3152
+ };
3153
+ } else if (globalP95 >= this.config.warningThresholdMs) {
3154
+ alert = {
3155
+ type: "warning",
3156
+ message: `Global p95 latency ${globalP95.toFixed(0)}ms exceeds warning threshold ${this.config.warningThresholdMs}ms`,
3157
+ sessionKey,
3158
+ p95Ms: globalP95,
3159
+ thresholdMs: this.config.warningThresholdMs,
3160
+ activeSessions: this.sessionWindows.size,
3161
+ suggestion: "Consider reducing active sessions"
3162
+ };
3163
+ }
3164
+ if (alert) {
3165
+ for (const cb of this.callbacks) {
3166
+ cb(alert);
3167
+ }
3168
+ }
3169
+ return alert;
3170
+ }
3171
+ getSessionP95(sessionKey) {
3172
+ const buf = this.sessionWindows.get(sessionKey);
3173
+ if (!buf) return null;
3174
+ return calculateP95(buf.getValues());
3175
+ }
3176
+ getGlobalP95() {
3177
+ return calculateP95(this.globalWindow.getValues());
3178
+ }
3179
+ onAlert(cb) {
3180
+ this.callbacks.push(cb);
3181
+ }
3182
+ get sessionCount() {
3183
+ return this.sessionWindows.size;
3184
+ }
3185
+ };
3186
+
3187
+ // src/tls/connect-handler.ts
3188
+ import * as net from "net";
3189
+
3190
+ // src/tls/allowlist.ts
3191
+ var MITM_HOSTS = /* @__PURE__ */ new Set([
3192
+ "api.openai.com",
3193
+ "api.anthropic.com",
3194
+ "generativelanguage.googleapis.com"
3195
+ ]);
3196
+ function shouldIntercept(hostname) {
3197
+ return MITM_HOSTS.has(hostname);
3198
+ }
3199
+
3200
+ // src/tls/connect-handler.ts
3201
+ function createConnectHandler(options) {
3202
+ const { logger, onIntercept } = options;
3203
+ return (req, clientSocket, head) => {
3204
+ const target = req.url ?? "";
3205
+ const [hostname, portStr] = parseConnectTarget(target);
3206
+ const port = parseInt(portStr, 10) || 443;
3207
+ if (!hostname) {
3208
+ logger.log(`[CONNECT] Invalid target: ${target}`);
3209
+ clientSocket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
3210
+ clientSocket.destroy();
3211
+ return;
3212
+ }
3213
+ if (shouldIntercept(hostname) && onIntercept) {
3214
+ logger.log(`[CONNECT] ${hostname}:${port} \u2192 intercept`);
3215
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
3216
+ if (head.length > 0) {
3217
+ clientSocket.unshift(head);
3218
+ }
3219
+ onIntercept(clientSocket, hostname, port);
3220
+ } else {
3221
+ logger.log(`[TUNNEL] ${hostname}:${port} \u2192 passthrough`);
3222
+ const upstreamSocket = net.connect(port, hostname, () => {
3223
+ clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
3224
+ if (head.length > 0) {
3225
+ upstreamSocket.write(head);
3226
+ }
3227
+ clientSocket.pipe(upstreamSocket);
3228
+ upstreamSocket.pipe(clientSocket);
3229
+ });
3230
+ upstreamSocket.on("error", (err) => {
3231
+ logger.log(`[TUNNEL] ${hostname}:${port} upstream error: ${err.message}`);
3232
+ clientSocket.write("HTTP/1.1 502 Bad Gateway\r\n\r\n");
3233
+ clientSocket.destroy();
3234
+ });
3235
+ clientSocket.on("error", (err) => {
3236
+ logger.log(`[TUNNEL] ${hostname}:${port} client error: ${err.message}`);
3237
+ upstreamSocket.destroy();
3238
+ });
3239
+ clientSocket.on("close", () => upstreamSocket.destroy());
3240
+ upstreamSocket.on("close", () => clientSocket.destroy());
3241
+ }
3242
+ };
3243
+ }
3244
+ function parseConnectTarget(target) {
3245
+ const colonIdx = target.lastIndexOf(":");
3246
+ if (colonIdx === -1) return [target, "443"];
3247
+ return [target.slice(0, colonIdx), target.slice(colonIdx + 1)];
3248
+ }
3249
+
3250
+ // src/tls/ca.ts
3251
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync } from "fs";
3252
+ import { join as join5 } from "path";
3253
+ import forge from "node-forge";
3254
+ var CA_CERT_PATH = join5(LIMINAL_DIR, "ca.pem");
3255
+ var CA_KEY_PATH = join5(LIMINAL_DIR, "ca-key.pem");
3256
+ function generateCA() {
3257
+ const keys = forge.pki.rsa.generateKeyPair(2048);
3258
+ const cert = forge.pki.createCertificate();
3259
+ cert.publicKey = keys.publicKey;
3260
+ cert.serialNumber = generateSerialNumber();
3261
+ cert.validity.notBefore = /* @__PURE__ */ new Date();
3262
+ cert.validity.notAfter = /* @__PURE__ */ new Date();
3263
+ cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 5);
3264
+ const attrs = [
3265
+ { name: "commonName", value: "Liminal Proxy CA" },
3266
+ { name: "organizationName", value: "Liminal (Cognisos)" },
3267
+ { shortName: "OU", value: "Local Development" }
3268
+ ];
3269
+ cert.setSubject(attrs);
3270
+ cert.setIssuer(attrs);
3271
+ cert.setExtensions([
3272
+ { name: "basicConstraints", cA: true, critical: true },
3273
+ { name: "keyUsage", keyCertSign: true, cRLSign: true, critical: true },
3274
+ {
3275
+ name: "subjectKeyIdentifier"
3276
+ }
3277
+ ]);
3278
+ cert.sign(keys.privateKey, forge.md.sha256.create());
3279
+ return {
3280
+ certPem: forge.pki.certificateToPem(cert),
3281
+ keyPem: forge.pki.privateKeyToPem(keys.privateKey)
3282
+ };
3283
+ }
3284
+ function generateAndSaveCA() {
3285
+ if (!existsSync6(LIMINAL_DIR)) {
3286
+ mkdirSync3(LIMINAL_DIR, { recursive: true, mode: 448 });
3287
+ }
3288
+ const { certPem, keyPem } = generateCA();
3289
+ writeFileSync3(CA_CERT_PATH, certPem, { encoding: "utf-8", mode: 420 });
3290
+ writeFileSync3(CA_KEY_PATH, keyPem, { encoding: "utf-8", mode: 384 });
3291
+ return { certPem, keyPem };
3292
+ }
3293
+ function loadCA() {
3294
+ if (!existsSync6(CA_CERT_PATH) || !existsSync6(CA_KEY_PATH)) {
3295
+ return null;
3296
+ }
3297
+ return {
3298
+ certPem: readFileSync4(CA_CERT_PATH, "utf-8"),
3299
+ keyPem: readFileSync4(CA_KEY_PATH, "utf-8")
3300
+ };
3301
+ }
3302
+ function ensureCA() {
3303
+ const existing = loadCA();
3304
+ if (existing) return existing;
3305
+ return generateAndSaveCA();
3306
+ }
3307
+ function hasCA() {
3308
+ return existsSync6(CA_CERT_PATH) && existsSync6(CA_KEY_PATH);
3309
+ }
3310
+ function removeCA() {
3311
+ if (existsSync6(CA_CERT_PATH)) unlinkSync(CA_CERT_PATH);
3312
+ if (existsSync6(CA_KEY_PATH)) unlinkSync(CA_KEY_PATH);
3313
+ }
3314
+ function getCAInfo() {
3315
+ const ca = loadCA();
3316
+ if (!ca) return null;
3317
+ const cert = forge.pki.certificateFromPem(ca.certPem);
3318
+ const cn = cert.subject.getField("CN");
3319
+ const der = forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes();
3320
+ const md = forge.md.sha256.create();
3321
+ md.update(der);
3322
+ const fingerprint = md.digest().toHex().match(/.{2}/g).join(":").toUpperCase();
3323
+ return {
3324
+ commonName: cn ? cn.value : "Unknown",
3325
+ validFrom: cert.validity.notBefore,
3326
+ validTo: cert.validity.notAfter,
3327
+ fingerprint
3328
+ };
3329
+ }
3330
+ function generateSerialNumber() {
3331
+ const bytes = forge.random.getBytesSync(16);
3332
+ return forge.util.bytesToHex(bytes);
3333
+ }
3334
+
3335
+ // src/tls/trust.ts
3336
+ import { execSync as execSync3 } from "child_process";
3337
+ import { existsSync as existsSync7, copyFileSync, unlinkSync as unlinkSync2 } from "fs";
3338
+ function installCA() {
3339
+ if (!existsSync7(CA_CERT_PATH)) {
3340
+ return { success: false, message: 'CA certificate not found. Run "liminal init" first.', requiresSudo: false };
3341
+ }
3342
+ const platform = process.platform;
3343
+ if (platform === "darwin") return installMacOS();
3344
+ if (platform === "linux") return installLinux();
3345
+ if (platform === "win32") return installWindows();
3346
+ return { success: false, message: `Unsupported platform: ${platform}`, requiresSudo: false };
3347
+ }
3348
+ function removeCA2() {
3349
+ const platform = process.platform;
3350
+ if (platform === "darwin") return removeMacOS();
3351
+ if (platform === "linux") return removeLinux();
3352
+ if (platform === "win32") return removeWindows();
3353
+ return { success: false, message: `Unsupported platform: ${platform}`, requiresSudo: false };
3354
+ }
3355
+ function isCATrusted() {
3356
+ const platform = process.platform;
3357
+ if (platform === "darwin") return isTrustedMacOS();
3358
+ if (platform === "linux") return isTrustedLinux();
3359
+ if (platform === "win32") return isTrustedWindows();
3360
+ return false;
3361
+ }
3362
+ var MACOS_LABEL = "Liminal Proxy CA";
3363
+ function installMacOS() {
3364
+ try {
3365
+ execSync3(
3366
+ `security add-trusted-cert -r trustRoot -k ~/Library/Keychains/login.keychain-db "${CA_CERT_PATH}"`,
3367
+ { stdio: "pipe" }
3368
+ );
3369
+ return { success: true, message: "CA installed in login keychain (trusted for SSL)", requiresSudo: false };
3370
+ } catch (err) {
3371
+ const msg = err instanceof Error ? err.message : String(err);
3372
+ if (msg.includes("authorization") || msg.includes("permission")) {
3373
+ return {
3374
+ success: false,
3375
+ message: "Keychain access denied. You may need to unlock your keychain or run with sudo.",
3376
+ requiresSudo: true
3377
+ };
3378
+ }
3379
+ return { success: false, message: `Failed to install CA: ${msg}`, requiresSudo: false };
3380
+ }
3381
+ }
3382
+ function removeMacOS() {
3383
+ try {
3384
+ execSync3(
3385
+ `security delete-certificate -c "${MACOS_LABEL}" ~/Library/Keychains/login.keychain-db`,
3386
+ { stdio: "pipe" }
3387
+ );
3388
+ return { success: true, message: "CA removed from login keychain", requiresSudo: false };
3389
+ } catch (err) {
3390
+ const msg = err instanceof Error ? err.message : String(err);
3391
+ if (msg.includes("could not be found")) {
3392
+ return { success: true, message: "CA was not in keychain (already removed)", requiresSudo: false };
3393
+ }
3394
+ return { success: false, message: `Failed to remove CA: ${msg}`, requiresSudo: false };
3395
+ }
3396
+ }
3397
+ function isTrustedMacOS() {
3398
+ try {
3399
+ const out = execSync3(
3400
+ `security find-certificate -c "${MACOS_LABEL}" ~/Library/Keychains/login.keychain-db`,
3401
+ { stdio: "pipe", encoding: "utf-8" }
3402
+ );
3403
+ return out.includes(MACOS_LABEL);
3404
+ } catch {
3405
+ return false;
3406
+ }
3407
+ }
3408
+ var LINUX_CERT_PATH = "/usr/local/share/ca-certificates/liminal-proxy-ca.crt";
3409
+ function installLinux() {
3410
+ try {
3411
+ copyFileSync(CA_CERT_PATH, LINUX_CERT_PATH);
3412
+ execSync3("update-ca-certificates", { stdio: "pipe" });
3413
+ return { success: true, message: "CA installed in system trust store", requiresSudo: true };
3414
+ } catch (err) {
3415
+ const msg = err instanceof Error ? err.message : String(err);
3416
+ if (msg.includes("EACCES") || msg.includes("permission")) {
3417
+ return {
3418
+ success: false,
3419
+ message: `Permission denied. Run with sudo:
3420
+ sudo liminal trust-ca`,
3421
+ requiresSudo: true
3422
+ };
3423
+ }
3424
+ return { success: false, message: `Failed to install CA: ${msg}`, requiresSudo: true };
3425
+ }
3426
+ }
3427
+ function removeLinux() {
3428
+ try {
3429
+ if (existsSync7(LINUX_CERT_PATH)) {
3430
+ unlinkSync2(LINUX_CERT_PATH);
3431
+ execSync3("update-ca-certificates --fresh", { stdio: "pipe" });
3432
+ }
3433
+ return { success: true, message: "CA removed from system trust store", requiresSudo: true };
3434
+ } catch (err) {
3435
+ const msg = err instanceof Error ? err.message : String(err);
3436
+ return { success: false, message: `Failed to remove CA: ${msg}`, requiresSudo: true };
3437
+ }
3438
+ }
3439
+ function isTrustedLinux() {
3440
+ return existsSync7(LINUX_CERT_PATH);
3441
+ }
3442
+ function installWindows() {
3443
+ try {
3444
+ execSync3(`certutil -addstore -user -f "ROOT" "${CA_CERT_PATH}"`, { stdio: "pipe" });
3445
+ return { success: true, message: "CA installed in user certificate store", requiresSudo: false };
3446
+ } catch (err) {
3447
+ const msg = err instanceof Error ? err.message : String(err);
3448
+ return { success: false, message: `Failed to install CA: ${msg}`, requiresSudo: false };
3449
+ }
3450
+ }
3451
+ function removeWindows() {
3452
+ try {
3453
+ execSync3(`certutil -delstore -user "ROOT" "${MACOS_LABEL}"`, { stdio: "pipe" });
3454
+ return { success: true, message: "CA removed from user certificate store", requiresSudo: false };
3455
+ } catch (err) {
3456
+ const msg = err instanceof Error ? err.message : String(err);
3457
+ return { success: false, message: `Failed to remove CA: ${msg}`, requiresSudo: false };
3458
+ }
3459
+ }
3460
+ function isTrustedWindows() {
3461
+ try {
3462
+ const out = execSync3(`certutil -verifystore -user "ROOT" "${MACOS_LABEL}"`, {
3463
+ stdio: "pipe",
3464
+ encoding: "utf-8"
3465
+ });
3466
+ return out.includes("Liminal");
3467
+ } catch {
3468
+ return false;
3469
+ }
3470
+ }
3471
+
3472
+ // src/tls/mitm-bridge.ts
3473
+ import * as tls from "tls";
3474
+
3475
+ // src/tls/cert-generator.ts
3476
+ import forge2 from "node-forge";
3477
+ var CERT_TTL_MS = 24 * 60 * 60 * 1e3;
3478
+ var MAX_CACHE_SIZE = 50;
3479
+ var CertGenerator = class {
3480
+ caCert;
3481
+ caKey;
3482
+ cache = /* @__PURE__ */ new Map();
3483
+ constructor(caCertPem, caKeyPem) {
3484
+ this.caCert = forge2.pki.certificateFromPem(caCertPem);
3485
+ this.caKey = forge2.pki.privateKeyFromPem(caKeyPem);
3486
+ }
3487
+ /**
3488
+ * Get or generate a TLS certificate for the given hostname.
3489
+ * Certificates are cached for 24 hours.
3490
+ */
3491
+ getCert(hostname) {
3492
+ const now = Date.now();
3493
+ const cached = this.cache.get(hostname);
3494
+ if (cached && cached.expiresAt > now) {
3495
+ return cached.cert;
3496
+ }
3497
+ const cert = this.generate(hostname);
3498
+ if (this.cache.size >= MAX_CACHE_SIZE) {
3499
+ const oldest = this.cache.keys().next().value;
3500
+ if (oldest) this.cache.delete(oldest);
3501
+ }
3502
+ this.cache.set(hostname, { cert, expiresAt: now + CERT_TTL_MS });
3503
+ return cert;
3504
+ }
3505
+ get cacheSize() {
3506
+ return this.cache.size;
3507
+ }
3508
+ generate(hostname) {
3509
+ const keys = forge2.pki.rsa.generateKeyPair(2048);
3510
+ const cert = forge2.pki.createCertificate();
3511
+ cert.publicKey = keys.publicKey;
3512
+ cert.serialNumber = randomSerial();
3513
+ cert.validity.notBefore = new Date(Date.now() - 24 * 60 * 60 * 1e3);
3514
+ cert.validity.notAfter = new Date(Date.now() + 24 * 60 * 60 * 1e3);
3515
+ cert.setSubject([
3516
+ { name: "commonName", value: hostname },
3517
+ { name: "organizationName", value: "Liminal Proxy (local)" }
3518
+ ]);
3519
+ cert.setIssuer(this.caCert.subject.attributes);
3520
+ cert.setExtensions([
3521
+ { name: "basicConstraints", cA: false },
3522
+ {
3523
+ name: "keyUsage",
3524
+ digitalSignature: true,
3525
+ keyEncipherment: true,
3526
+ critical: true
3527
+ },
3528
+ {
3529
+ name: "extKeyUsage",
3530
+ serverAuth: true
3531
+ },
3532
+ {
3533
+ name: "subjectAltName",
3534
+ altNames: [{ type: 2, value: hostname }]
3535
+ // DNS name
3536
+ }
3537
+ ]);
3538
+ cert.sign(this.caKey, forge2.md.sha256.create());
3539
+ return {
3540
+ certPem: forge2.pki.certificateToPem(cert),
3541
+ keyPem: forge2.pki.privateKeyToPem(keys.privateKey)
3542
+ };
3543
+ }
3544
+ };
3545
+ function randomSerial() {
3546
+ return forge2.util.bytesToHex(forge2.random.getBytesSync(16));
3547
+ }
3548
+
3549
+ // src/tls/mitm-bridge.ts
3550
+ function createMitmBridge(options) {
3551
+ const { httpServer, caCertPem, caKeyPem, logger } = options;
3552
+ const certGen = new CertGenerator(caCertPem, caKeyPem);
3553
+ return (clientSocket, hostname, _port) => {
3554
+ try {
3555
+ const { certPem, keyPem } = certGen.getCert(hostname);
3556
+ const tlsSocket = new tls.TLSSocket(clientSocket, {
3557
+ isServer: true,
3558
+ key: keyPem,
3559
+ cert: certPem
3560
+ });
3561
+ tlsSocket.on("error", (err) => {
3562
+ logger.log(`[MITM] TLS error for ${hostname}: ${err.message}`);
3563
+ tlsSocket.destroy();
3564
+ });
3565
+ httpServer.emit("connection", tlsSocket);
3566
+ logger.log(`[MITM] TLS bridge established for ${hostname} (cert cache: ${certGen.cacheSize})`);
3567
+ } catch (err) {
3568
+ const message = err instanceof Error ? err.message : String(err);
3569
+ logger.log(`[MITM] Failed to establish bridge for ${hostname}: ${message}`);
3570
+ clientSocket.destroy();
3571
+ }
3572
+ };
3573
+ }
3574
+
2533
3575
  // src/daemon/lifecycle.ts
2534
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync, existsSync as existsSync6 } from "fs";
3576
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
2535
3577
  import { fork } from "child_process";
2536
3578
  import { fileURLToPath } from "url";
2537
3579
  function writePidFile(pid) {
2538
- writeFileSync3(PID_FILE, String(pid), "utf-8");
3580
+ writeFileSync4(PID_FILE, String(pid), "utf-8");
2539
3581
  }
2540
3582
  function readPidFile() {
2541
- if (!existsSync6(PID_FILE)) return null;
3583
+ if (!existsSync8(PID_FILE)) return null;
2542
3584
  try {
2543
- const content = readFileSync4(PID_FILE, "utf-8").trim();
3585
+ const content = readFileSync5(PID_FILE, "utf-8").trim();
2544
3586
  const pid = parseInt(content, 10);
2545
3587
  return isNaN(pid) ? null : pid;
2546
3588
  } catch {
@@ -2549,7 +3591,7 @@ function readPidFile() {
2549
3591
  }
2550
3592
  function removePidFile() {
2551
3593
  try {
2552
- if (existsSync6(PID_FILE)) unlinkSync(PID_FILE);
3594
+ if (existsSync8(PID_FILE)) unlinkSync3(PID_FILE);
2553
3595
  } catch {
2554
3596
  }
2555
3597
  }
@@ -2651,37 +3693,81 @@ async function startCommand(flags) {
2651
3693
  rscBaseUrl: config.apiBaseUrl,
2652
3694
  proxyPort: config.port,
2653
3695
  compressionThreshold: config.compressionThreshold,
3696
+ aggregateThreshold: config.aggregateThreshold,
3697
+ hotFraction: config.hotFraction,
3698
+ coldFraction: config.coldFraction,
2654
3699
  compressRoles: config.compressRoles,
3700
+ compressToolResults: config.compressToolResults,
2655
3701
  learnFromResponses: config.learnFromResponses,
2656
3702
  latencyBudgetMs: config.latencyBudgetMs || void 0,
2657
3703
  upstreamBaseUrl: config.upstreamBaseUrl,
2658
3704
  anthropicUpstreamUrl: config.anthropicUpstreamUrl,
2659
3705
  enabled: config.enabled,
2660
- tools: config.tools
3706
+ tools: config.tools,
3707
+ concurrencyLimit: config.concurrencyLimit,
3708
+ concurrencyTimeoutMs: config.concurrencyTimeoutMs,
3709
+ maxSessions: config.maxSessions,
3710
+ sessionTtlMs: config.sessionTtlMs,
3711
+ latencyWarningMs: config.latencyWarningMs,
3712
+ latencyCriticalMs: config.latencyCriticalMs
2661
3713
  };
2662
- const pipeline = new RSCPipelineWrapper({
2663
- rscApiKey: config.apiKey,
2664
- rscBaseUrl: config.apiBaseUrl,
2665
- compressionThreshold: config.compressionThreshold,
2666
- learnFromResponses: config.learnFromResponses,
2667
- latencyBudgetMs: config.latencyBudgetMs || void 0
3714
+ const semaphore = new Semaphore(resolvedConfig.concurrencyLimit);
3715
+ const latencyMonitor = new LatencyMonitor({
3716
+ warningThresholdMs: resolvedConfig.latencyWarningMs,
3717
+ criticalThresholdMs: resolvedConfig.latencyCriticalMs
2668
3718
  });
2669
- pipeline.events.on("compression", (event) => {
2670
- if (event.tokensSaved > 0) {
2671
- logger.log(`[LIMINAL] Compressed: ${event.tokensSaved} tokens saved (${event.ratio.toFixed(3)} ratio)`);
3719
+ function wireSessionEvents(key, pipeline) {
3720
+ pipeline.events.on("compression", (event) => {
3721
+ if (event.tokensSaved > 0) {
3722
+ logger.log(`[LIMINAL] [${key}] Compressed: ${event.tokensSaved} tokens saved (${event.ratio.toFixed(3)} ratio)`);
3723
+ }
3724
+ });
3725
+ pipeline.events.on("compression_skipped", (event) => {
3726
+ const detail = event.reason === "latency_budget" ? `${event.reason} (${event.estimatedTokens}tok, budget:${event.budgetMs}ms, elapsed:${event.elapsedMs}ms)` : event.reason === "below_threshold" ? `${event.reason} (${event.estimatedTokens}tok < ${config.compressionThreshold}tok threshold)` : `${event.reason} (${event.estimatedTokens}tok)`;
3727
+ logger.log(`[LIMINAL] [${key}] Skipped: ${detail}`);
3728
+ });
3729
+ pipeline.events.on("error", (event) => {
3730
+ logger.log(`[LIMINAL] [${key}] Error: ${event.error.message}`);
3731
+ });
3732
+ pipeline.events.on("degradation", (event) => {
3733
+ logger.log(`[LIMINAL] [${key}] Circuit ${event.circuitState}: ${event.reason}`);
3734
+ });
3735
+ }
3736
+ const sessions = new SessionManager({
3737
+ pipelineConfig: {
3738
+ rscApiKey: config.apiKey,
3739
+ rscBaseUrl: config.apiBaseUrl,
3740
+ compressionThreshold: config.compressionThreshold,
3741
+ learnFromResponses: config.learnFromResponses,
3742
+ latencyBudgetMs: config.latencyBudgetMs || void 0
3743
+ },
3744
+ maxSessions: resolvedConfig.maxSessions,
3745
+ sessionTtlMs: resolvedConfig.sessionTtlMs,
3746
+ onSessionCreated: (key, pipeline) => {
3747
+ logger.log(`[SESSION] Created: ${key}`);
3748
+ wireSessionEvents(key, pipeline);
3749
+ },
3750
+ onSessionEvicted: (key) => {
3751
+ logger.log(`[SESSION] Evicted: ${key} (idle)`);
2672
3752
  }
2673
3753
  });
2674
- pipeline.events.on("compression_skipped", (event) => {
2675
- logger.log(`[LIMINAL] Skipped: ${event.reason}`);
2676
- });
2677
- pipeline.events.on("error", (event) => {
2678
- logger.log(`[LIMINAL] Error: ${event.error.message}`);
3754
+ latencyMonitor.onAlert((alert) => {
3755
+ logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message} (${alert.activeSessions} sessions) \u2014 ${alert.suggestion}`);
2679
3756
  });
2680
- pipeline.events.on("degradation", (event) => {
2681
- logger.log(`[LIMINAL] Circuit ${event.circuitState}: ${event.reason}`);
3757
+ const deps = { sessions, semaphore, latencyMonitor, config: resolvedConfig, logger };
3758
+ const handler = createRequestHandler(deps);
3759
+ let mitmHandler;
3760
+ const connectHandler = createConnectHandler({
3761
+ logger,
3762
+ onIntercept: (socket, hostname, port) => {
3763
+ if (mitmHandler) {
3764
+ mitmHandler(socket, hostname, port);
3765
+ } else {
3766
+ logger.log(`[MITM] No bridge available for ${hostname} \u2014 falling back to passthrough`);
3767
+ }
3768
+ }
2682
3769
  });
2683
- const handler = createRequestHandler(pipeline, resolvedConfig, logger);
2684
- const server = new ProxyServer(config.port, handler);
3770
+ const server = new ProxyServer(config.port, handler, connectHandler);
2685
3771
  setupSignalHandlers(server, logger);
2686
3772
  try {
2687
3773
  const actualPort = await server.start();
@@ -2691,15 +3777,42 @@ async function startCommand(flags) {
2691
3777
  logger.log(`[DAEMON] Upstream (Anthropic): ${config.anthropicUpstreamUrl}`);
2692
3778
  logger.log(`[DAEMON] Liminal API: ${config.apiBaseUrl}`);
2693
3779
  logger.log(`[DAEMON] PID: ${process.pid}`);
3780
+ logger.log(`[DAEMON] Max sessions: ${resolvedConfig.maxSessions}, Concurrency limit: ${resolvedConfig.concurrencyLimit}`);
3781
+ const caReady = hasCA() && isCATrusted();
3782
+ if (caReady) {
3783
+ const httpServer = server.getHttpServer();
3784
+ const ca = loadCA();
3785
+ if (httpServer && ca) {
3786
+ mitmHandler = createMitmBridge({
3787
+ httpServer,
3788
+ caCertPem: ca.certPem,
3789
+ caKeyPem: ca.keyPem,
3790
+ logger
3791
+ });
3792
+ logger.log("[MITM] TLS bridge active \u2014 intercepting LLM API calls");
3793
+ }
3794
+ }
3795
+ logger.log(`[DAEMON] CONNECT handler: active | MITM: ${caReady ? "ready (CA trusted)" : "passthrough only (run liminal trust-ca)"}`);
2694
3796
  if (isForeground && !isForked) {
2695
3797
  printBanner();
2696
3798
  console.log(` Liminal proxy running on http://127.0.0.1:${actualPort}/v1`);
2697
3799
  console.log(` Upstream: ${config.upstreamBaseUrl}`);
3800
+ console.log(` Max sessions: ${resolvedConfig.maxSessions} | Concurrency: ${resolvedConfig.concurrencyLimit}`);
3801
+ if (caReady) {
3802
+ console.log(" MITM: active (Cursor interception ready)");
3803
+ }
2698
3804
  console.log();
2699
3805
  console.log(" Point your AI tool's base URL here. Press Ctrl+C to stop.");
2700
3806
  console.log();
2701
3807
  }
2702
- const healthy = await pipeline.healthCheck();
3808
+ const { RSCPipelineWrapper: RSCPipelineWrapper2 } = await Promise.resolve().then(() => (init_pipeline(), pipeline_exports));
3809
+ const probe = new RSCPipelineWrapper2({
3810
+ rscApiKey: config.apiKey,
3811
+ rscBaseUrl: config.apiBaseUrl,
3812
+ compressionThreshold: config.compressionThreshold,
3813
+ learnFromResponses: false
3814
+ });
3815
+ const healthy = await probe.healthCheck();
2703
3816
  if (healthy) {
2704
3817
  logger.log("[DAEMON] Liminal API health check: OK");
2705
3818
  } else {
@@ -2766,19 +3879,39 @@ async function statusCommand() {
2766
3879
  const data = await res.json();
2767
3880
  const uptime = formatUptime(data.uptime_ms);
2768
3881
  console.log(`Liminal Daemon: running (PID ${state.pid}, port ${port})`);
2769
- console.log(`Circuit: ${data.circuit_state}`);
2770
- console.log(`Session: ${data.session_id}`);
3882
+ console.log(`Status: ${data.status} (${data.version})`);
2771
3883
  console.log(`Uptime: ${uptime}`);
2772
- if (data.session) {
2773
- const s = data.session;
2774
- const savingsPercent = s.tokens_processed > 0 ? (s.tokens_saved / s.tokens_processed * 100).toFixed(1) : "0.0";
3884
+ console.log();
3885
+ const c = data.concurrency;
3886
+ console.log(`Sessions: ${c.active_sessions} active (max ${config.maxSessions})`);
3887
+ console.log(`Semaphore: ${c.semaphore_available}/${c.max_concurrent_rsc_calls} available` + (c.semaphore_waiting > 0 ? ` (${c.semaphore_waiting} waiting)` : ""));
3888
+ const globalP95 = data.latency.global_p95_ms;
3889
+ if (globalP95 !== null) {
3890
+ const latencyFlag = globalP95 >= config.latencyCriticalMs ? " CRITICAL" : globalP95 >= config.latencyWarningMs ? " WARNING" : "";
3891
+ console.log(`Latency: p95 ${globalP95.toFixed(0)}ms${latencyFlag}`);
3892
+ }
3893
+ if (data.sessions.length > 0) {
3894
+ console.log();
3895
+ console.log("\u2500\u2500\u2500 Sessions \u2500\u2500\u2500");
3896
+ for (const s of data.sessions) {
3897
+ const savingsPercent = s.tokens_processed > 0 ? (s.tokens_saved / s.tokens_processed * 100).toFixed(1) : "0.0";
3898
+ const activeAgo = formatUptime(s.last_active_ago_ms);
3899
+ const p95 = s.p95_latency_ms !== null ? `${s.p95_latency_ms.toFixed(0)}ms` : "-";
3900
+ console.log();
3901
+ console.log(` ${s.connector} [${s.session_key}]`);
3902
+ console.log(` Circuit: ${s.circuit_state}`);
3903
+ console.log(` Tokens: ${s.tokens_processed.toLocaleString()} processed, ${s.tokens_saved.toLocaleString()} saved (${savingsPercent}%)`);
3904
+ console.log(` Calls: ${s.calls_total} total (${s.calls_compressed} compressed, ${s.calls_failed} failed)`);
3905
+ console.log(` Latency: p95 ${p95}`);
3906
+ console.log(` Active: ${activeAgo} ago`);
3907
+ }
3908
+ } else {
2775
3909
  console.log();
2776
- console.log(`Tokens: ${s.tokens_processed.toLocaleString()} processed, ${s.tokens_saved.toLocaleString()} saved (${savingsPercent}%)`);
2777
- console.log(`Calls: ${s.calls_total} total (${s.calls_compressed} compressed, ${s.calls_skipped} skipped, ${s.calls_failed} failed)`);
3910
+ console.log("No active sessions.");
2778
3911
  }
2779
3912
  } catch {
2780
3913
  console.log(`Liminal Daemon: running (PID ${state.pid}, port ${port})`);
2781
- console.log("Circuit: unknown (could not reach /health)");
3914
+ console.log("Status: unknown (could not reach /health)");
2782
3915
  }
2783
3916
  }
2784
3917
  function formatUptime(ms) {
@@ -2930,17 +4063,17 @@ async function configCommand(flags) {
2930
4063
  }
2931
4064
 
2932
4065
  // src/commands/logs.ts
2933
- import { readFileSync as readFileSync5, existsSync as existsSync7, statSync as statSync2, createReadStream } from "fs";
4066
+ import { readFileSync as readFileSync6, existsSync as existsSync9, statSync as statSync2, createReadStream } from "fs";
2934
4067
  import { watchFile, unwatchFile } from "fs";
2935
4068
  async function logsCommand(flags) {
2936
4069
  const follow = flags.has("follow") || flags.has("f");
2937
4070
  const linesFlag = flags.get("lines") ?? flags.get("n");
2938
4071
  const lines = typeof linesFlag === "string" ? parseInt(linesFlag, 10) : 50;
2939
- if (!existsSync7(LOG_FILE)) {
4072
+ if (!existsSync9(LOG_FILE)) {
2940
4073
  console.log('No log file found. Start the daemon with "liminal start" to generate logs.');
2941
4074
  return;
2942
4075
  }
2943
- const content = readFileSync5(LOG_FILE, "utf-8");
4076
+ const content = readFileSync6(LOG_FILE, "utf-8");
2944
4077
  const allLines = content.split("\n");
2945
4078
  const tail = allLines.slice(-lines - 1);
2946
4079
  process.stdout.write(tail.join("\n"));
@@ -2966,16 +4099,16 @@ async function logsCommand(flags) {
2966
4099
  }
2967
4100
 
2968
4101
  // src/commands/uninstall.ts
2969
- import { existsSync as existsSync8, rmSync, readFileSync as readFileSync6 } from "fs";
4102
+ import { existsSync as existsSync10, rmSync, readFileSync as readFileSync7 } from "fs";
2970
4103
  var BOLD2 = "\x1B[1m";
2971
4104
  var DIM2 = "\x1B[2m";
2972
4105
  var GREEN2 = "\x1B[32m";
2973
4106
  var YELLOW2 = "\x1B[33m";
2974
4107
  var RESET2 = "\x1B[0m";
2975
4108
  function loadConfiguredTools() {
2976
- if (!existsSync8(CONFIG_FILE)) return [];
4109
+ if (!existsSync10(CONFIG_FILE)) return [];
2977
4110
  try {
2978
- const raw = readFileSync6(CONFIG_FILE, "utf-8");
4111
+ const raw = readFileSync7(CONFIG_FILE, "utf-8");
2979
4112
  const config = JSON.parse(raw);
2980
4113
  if (Array.isArray(config.tools)) return config.tools;
2981
4114
  } catch {
@@ -3063,7 +4196,7 @@ async function uninstallCommand() {
3063
4196
  }
3064
4197
  }
3065
4198
  }
3066
- if (existsSync8(LIMINAL_DIR)) {
4199
+ if (existsSync10(LIMINAL_DIR)) {
3067
4200
  console.log();
3068
4201
  const removeData = await selectPrompt({
3069
4202
  message: "Remove ~/.liminal/ directory? (config, logs, PID file)",
@@ -3094,6 +4227,90 @@ async function uninstallCommand() {
3094
4227
  console.log();
3095
4228
  }
3096
4229
 
4230
+ // src/commands/trust-ca.ts
4231
+ async function trustCACommand() {
4232
+ printBanner();
4233
+ if (isCATrusted()) {
4234
+ console.log(" Liminal CA is already trusted.");
4235
+ const info = getCAInfo();
4236
+ if (info) {
4237
+ console.log(` Fingerprint: ${info.fingerprint}`);
4238
+ console.log(` Valid until: ${info.validTo.toLocaleDateString()}`);
4239
+ }
4240
+ return;
4241
+ }
4242
+ if (!hasCA()) {
4243
+ console.log(" Generating CA certificate...");
4244
+ ensureCA();
4245
+ console.log(" Created ~/.liminal/ca.pem");
4246
+ console.log();
4247
+ }
4248
+ console.log(" Installing Liminal CA certificate");
4249
+ console.log();
4250
+ console.log(" This allows Liminal to transparently compress LLM API");
4251
+ console.log(" traffic from Cursor and other Electron-based editors.");
4252
+ console.log();
4253
+ console.log(" The certificate is scoped to your user account and only");
4254
+ console.log(" used by the local Liminal proxy on 127.0.0.1.");
4255
+ console.log();
4256
+ console.log(` Certificate: ${CA_CERT_PATH}`);
4257
+ console.log();
4258
+ const result = installCA();
4259
+ if (result.success) {
4260
+ console.log(` ${result.message}`);
4261
+ console.log();
4262
+ const info = getCAInfo();
4263
+ if (info) {
4264
+ console.log(` Fingerprint: ${info.fingerprint}`);
4265
+ console.log(` Valid until: ${info.validTo.toLocaleDateString()}`);
4266
+ }
4267
+ console.log();
4268
+ console.log(" You can now use Liminal with Cursor:");
4269
+ console.log(" liminal start");
4270
+ console.log(" cursor --proxy-server=http://127.0.0.1:3141 --disable-http2");
4271
+ } else {
4272
+ console.error(` Failed: ${result.message}`);
4273
+ if (result.requiresSudo) {
4274
+ console.log();
4275
+ console.log(" Try running with elevated permissions:");
4276
+ console.log(" sudo liminal trust-ca");
4277
+ }
4278
+ process.exit(1);
4279
+ }
4280
+ }
4281
+
4282
+ // src/commands/untrust-ca.ts
4283
+ async function untrustCACommand() {
4284
+ printBanner();
4285
+ if (!hasCA() && !isCATrusted()) {
4286
+ console.log(" No Liminal CA found (nothing to remove).");
4287
+ return;
4288
+ }
4289
+ if (isCATrusted()) {
4290
+ console.log(" Removing CA from system trust store...");
4291
+ const result = removeCA2();
4292
+ if (result.success) {
4293
+ console.log(` ${result.message}`);
4294
+ } else {
4295
+ console.error(` ${result.message}`);
4296
+ if (result.requiresSudo) {
4297
+ console.log();
4298
+ console.log(" Try running with elevated permissions:");
4299
+ console.log(" sudo liminal untrust-ca");
4300
+ }
4301
+ process.exit(1);
4302
+ }
4303
+ }
4304
+ if (hasCA()) {
4305
+ console.log(" Removing CA certificate files...");
4306
+ removeCA();
4307
+ console.log(" Removed ~/.liminal/ca.pem and ca-key.pem");
4308
+ }
4309
+ console.log();
4310
+ console.log(" Liminal CA fully removed.");
4311
+ console.log(" Cursor MITM interception is no longer available.");
4312
+ }
4313
+
3097
4314
  // src/bin.ts
3098
4315
  var USAGE = `
3099
4316
  liminal v${VERSION} \u2014 Transparent LLM context compression proxy
@@ -3108,6 +4325,8 @@ var USAGE = `
3108
4325
  liminal summary Detailed session metrics
3109
4326
  liminal config [--set k=v] [--get k] View or edit configuration
3110
4327
  liminal logs [--follow] [--lines N] View proxy logs
4328
+ liminal trust-ca Install CA cert (for Cursor MITM)
4329
+ liminal untrust-ca Remove CA cert
3111
4330
  liminal uninstall Remove Liminal configuration
3112
4331
 
3113
4332
  Options:
@@ -3120,7 +4339,7 @@ var USAGE = `
3120
4339
  3. Connect your AI tools:
3121
4340
  Claude Code: export ANTHROPIC_BASE_URL=http://localhost:3141
3122
4341
  Codex: export OPENAI_BASE_URL=http://localhost:3141/v1
3123
- Cursor: Settings > Models > Base URL > http://localhost:3141/v1
4342
+ Cursor: liminal trust-ca && cursor --proxy-server=http://localhost:3141
3124
4343
  `;
3125
4344
  function parseArgs(argv) {
3126
4345
  const command = argv[2] ?? "";
@@ -3186,6 +4405,12 @@ async function main() {
3186
4405
  case "logs":
3187
4406
  await logsCommand(flags);
3188
4407
  break;
4408
+ case "trust-ca":
4409
+ await trustCACommand();
4410
+ break;
4411
+ case "untrust-ca":
4412
+ await untrustCACommand();
4413
+ break;
3189
4414
  case "uninstall":
3190
4415
  await uninstallCommand();
3191
4416
  break;