@cognisos/liminal 2.4.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -78,7 +78,7 @@ var init_pipeline = __esm({
78
78
  });
79
79
 
80
80
  // src/version.ts
81
- var VERSION = true ? "2.4.1" : "0.2.1";
81
+ var VERSION = true ? "2.4.2" : "0.2.1";
82
82
  var BANNER_LINES = [
83
83
  " ___ ___ _____ ______ ___ ________ ________ ___",
84
84
  "|\\ \\ |\\ \\|\\ _ \\ _ \\|\\ \\|\\ ___ \\|\\ __ \\|\\ \\",
@@ -124,7 +124,7 @@ var DEFAULTS = {
124
124
  compressRoles: ["user", "assistant"],
125
125
  compressToolResults: true,
126
126
  learnFromResponses: true,
127
- latencyBudgetMs: 5e3,
127
+ latencyBudgetMs: 1e4,
128
128
  enabled: true,
129
129
  tools: [],
130
130
  concurrencyLimit: 6,
@@ -912,9 +912,9 @@ import { homedir as homedir4 } from "os";
912
912
  var INFO3 = {
913
913
  id: "cursor",
914
914
  label: "Cursor",
915
- description: "AI-first code editor (GUI config required)",
915
+ description: "AI-first code editor (proxy-based, automated)",
916
916
  protocol: "openai-chat",
917
- automatable: false
917
+ automatable: true
918
918
  };
919
919
  function getCursorPaths() {
920
920
  const platform = process.platform;
@@ -967,25 +967,20 @@ var cursorConnector = {
967
967
  return {
968
968
  success: true,
969
969
  shellExports: [],
970
- // No env vars — GUI only
971
970
  postSetupInstructions: [
972
- "Cursor routes API calls through its cloud servers, so localhost",
973
- "URLs are blocked. You need a tunnel to expose the proxy:",
971
+ "Run the automated setup:",
974
972
  "",
975
- ` npx cloudflared tunnel --url http://localhost:${port}`,
973
+ " liminal setup cursor",
976
974
  "",
977
- "Then configure Cursor with the tunnel URL:",
975
+ "This will:",
976
+ " 1. Generate and install a local CA certificate",
977
+ " 2. Launch Cursor with --proxy-server=http://127.0.0.1:" + port,
978
978
  "",
979
- " 1. Open Cursor Settings (not VS Code settings)",
980
- " 2. Go to Models",
981
- ' 3. Enable "Override OpenAI Base URL (when using key)"',
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",
985
- " 6. Restart Cursor",
979
+ "All LLM API calls are transparently intercepted and compressed.",
980
+ "No Cursor settings changes needed \u2014 works with all models.",
986
981
  "",
987
- "Cursor uses OpenAI format for all models, including Claude.",
988
- "Both Chat Completions and Agent mode (Responses API) are supported."
982
+ "Or launch Cursor manually:",
983
+ ` cursor --proxy-server=http://127.0.0.1:${port}`
989
984
  ]
990
985
  };
991
986
  },
@@ -1376,23 +1371,62 @@ async function compressConversation(pipeline, session, plan, options = { compres
1376
1371
  totalTokensSaved += saved;
1377
1372
  };
1378
1373
  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;
1386
- }
1387
- if (tm.eligibleTokens === 0) {
1388
- log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (0 eligible tok, skip)`);
1389
- return tm.message;
1390
- }
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);
1393
- })
1394
- );
1395
- return { messages: compressed, anyCompressed, totalTokensSaved };
1374
+ const threshold = options.compressionThreshold ?? 100;
1375
+ const results = new Array(plan.messages.length);
1376
+ const compressible = [];
1377
+ for (let i = 0; i < plan.messages.length; i++) {
1378
+ const tm = plan.messages[i];
1379
+ const role = tm.message.role;
1380
+ const blockTypes = Array.isArray(tm.message.content) ? tm.message.content.map((b) => b.type).join(",") : "string";
1381
+ if (tm.tier === "hot") {
1382
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 HOT (verbatim)`);
1383
+ results[i] = tm.message;
1384
+ continue;
1385
+ }
1386
+ if (tm.eligibleTokens === 0) {
1387
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (0 eligible tok, skip)`);
1388
+ results[i] = tm.message;
1389
+ continue;
1390
+ }
1391
+ const proseEstimate = estimateProseTokens(tm.message, options.compressToolResults);
1392
+ if (proseEstimate < threshold) {
1393
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (${proseEstimate} prose tok after segmentation < ${threshold} threshold, skip)`);
1394
+ results[i] = tm.message;
1395
+ continue;
1396
+ }
1397
+ log?.(`[BLOCK] #${tm.index} ${role} [${blockTypes}] \u2192 ${tm.tier.toUpperCase()} (${tm.eligibleTokens} eligible tok, ~${proseEstimate} prose tok, compressing)`);
1398
+ compressible.push({ planIdx: i, tm });
1399
+ }
1400
+ compressible.sort((a, b) => b.tm.eligibleTokens - a.tm.eligibleTokens);
1401
+ for (const { planIdx, tm } of compressible) {
1402
+ results[planIdx] = await batchCompressMessage(tm.message, pipeline, session, record, options);
1403
+ }
1404
+ return { messages: results, anyCompressed, totalTokensSaved };
1405
+ }
1406
+ function estimateProseTokens(msg, compressToolResults) {
1407
+ const text = extractCompressibleText(msg, compressToolResults);
1408
+ if (!text) return 0;
1409
+ const segments = segmentContent(text);
1410
+ return segments.filter((s) => s.type === "prose").reduce((sum, s) => sum + Math.ceil(s.text.length / 4), 0);
1411
+ }
1412
+ function extractCompressibleText(msg, compressToolResults) {
1413
+ if (typeof msg.content === "string") {
1414
+ return msg.content.trim() ? msg.content : null;
1415
+ }
1416
+ if (!Array.isArray(msg.content)) return null;
1417
+ const parts = msg.content;
1418
+ const textSegments = [];
1419
+ for (const part of parts) {
1420
+ if (PASSTHROUGH_BLOCK_TYPES.has(part.type)) continue;
1421
+ if (part.type === "text" && typeof part.text === "string" && part.text.trim()) {
1422
+ textSegments.push(part.text);
1423
+ }
1424
+ if (part.type === "tool_result" && compressToolResults) {
1425
+ const extracted = extractToolResultText(part);
1426
+ if (extracted) textSegments.push(extracted);
1427
+ }
1428
+ }
1429
+ return textSegments.length > 0 ? textSegments.join("\n\n") : null;
1396
1430
  }
1397
1431
  function extractToolResultText(part) {
1398
1432
  if (typeof part.content === "string" && part.content.trim()) {
@@ -1854,6 +1888,7 @@ async function handleChatCompletions(req, res, body, pipeline, config, logger, s
1854
1888
  plan,
1855
1889
  {
1856
1890
  compressToolResults: config.compressToolResults,
1891
+ compressionThreshold: config.compressionThreshold,
1857
1892
  logFn: (msg) => logger.log(msg),
1858
1893
  semaphore,
1859
1894
  semaphoreTimeoutMs: config.concurrencyTimeoutMs
@@ -2127,6 +2162,7 @@ async function handleAnthropicMessages(req, res, body, pipeline, config, logger,
2127
2162
  plan,
2128
2163
  {
2129
2164
  compressToolResults: config.compressToolResults,
2165
+ compressionThreshold: config.compressionThreshold,
2130
2166
  logFn: (msg) => logger.log(msg),
2131
2167
  semaphore,
2132
2168
  semaphoreTimeoutMs: config.concurrencyTimeoutMs
@@ -2703,7 +2739,7 @@ async function passthroughToUpstream(req, res, fullUrl, config, logger) {
2703
2739
  }
2704
2740
  }
2705
2741
  function createRequestHandler(deps) {
2706
- const { sessions, semaphore, latencyMonitor, config, logger } = deps;
2742
+ const { sessions, semaphore, latencyMonitor, mitmStats, config, logger } = deps;
2707
2743
  const startTime = Date.now();
2708
2744
  return async (req, res) => {
2709
2745
  try {
@@ -2733,6 +2769,7 @@ function createRequestHandler(deps) {
2733
2769
  latency: {
2734
2770
  global_p95_ms: latencyMonitor.getGlobalP95()
2735
2771
  },
2772
+ mitm: mitmStats.snapshot(),
2736
2773
  sessions: sessionSummaries.map((s) => ({
2737
2774
  session_key: s.key,
2738
2775
  connector: s.connector,
@@ -3199,7 +3236,7 @@ function shouldIntercept(hostname) {
3199
3236
 
3200
3237
  // src/tls/connect-handler.ts
3201
3238
  function createConnectHandler(options) {
3202
- const { logger, onIntercept } = options;
3239
+ const { logger, onIntercept, onPassthrough } = options;
3203
3240
  return (req, clientSocket, head) => {
3204
3241
  const target = req.url ?? "";
3205
3242
  const [hostname, portStr] = parseConnectTarget(target);
@@ -3219,6 +3256,7 @@ function createConnectHandler(options) {
3219
3256
  onIntercept(clientSocket, hostname, port);
3220
3257
  } else {
3221
3258
  logger.log(`[TUNNEL] ${hostname}:${port} \u2192 passthrough`);
3259
+ onPassthrough?.();
3222
3260
  const upstreamSocket = net.connect(port, hostname, () => {
3223
3261
  clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
3224
3262
  if (head.length > 0) {
@@ -3572,6 +3610,38 @@ function createMitmBridge(options) {
3572
3610
  };
3573
3611
  }
3574
3612
 
3613
+ // src/tls/mitm-stats.ts
3614
+ var MitmStats = class {
3615
+ _intercepted = 0;
3616
+ _passthrough = 0;
3617
+ _activeBridges = 0;
3618
+ _hosts = /* @__PURE__ */ new Set();
3619
+ _enabled = false;
3620
+ enable() {
3621
+ this._enabled = true;
3622
+ }
3623
+ recordIntercept(hostname) {
3624
+ this._intercepted++;
3625
+ this._activeBridges++;
3626
+ this._hosts.add(hostname);
3627
+ }
3628
+ recordBridgeClosed() {
3629
+ if (this._activeBridges > 0) this._activeBridges--;
3630
+ }
3631
+ recordPassthrough() {
3632
+ this._passthrough++;
3633
+ }
3634
+ snapshot() {
3635
+ return {
3636
+ intercepted: this._intercepted,
3637
+ passthrough: this._passthrough,
3638
+ activeBridges: this._activeBridges,
3639
+ hostsIntercepted: [...this._hosts],
3640
+ enabled: this._enabled
3641
+ };
3642
+ }
3643
+ };
3644
+
3575
3645
  // src/daemon/lifecycle.ts
3576
3646
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, existsSync as existsSync8 } from "fs";
3577
3647
  import { fork } from "child_process";
@@ -3754,17 +3824,23 @@ async function startCommand(flags) {
3754
3824
  latencyMonitor.onAlert((alert) => {
3755
3825
  logger.log(`[LATENCY] ${alert.type.toUpperCase()}: ${alert.message} (${alert.activeSessions} sessions) \u2014 ${alert.suggestion}`);
3756
3826
  });
3757
- const deps = { sessions, semaphore, latencyMonitor, config: resolvedConfig, logger };
3827
+ const mitmStats = new MitmStats();
3828
+ const deps = { sessions, semaphore, latencyMonitor, mitmStats, config: resolvedConfig, logger };
3758
3829
  const handler = createRequestHandler(deps);
3759
3830
  let mitmHandler;
3760
3831
  const connectHandler = createConnectHandler({
3761
3832
  logger,
3762
3833
  onIntercept: (socket, hostname, port) => {
3763
3834
  if (mitmHandler) {
3835
+ mitmStats.recordIntercept(hostname);
3764
3836
  mitmHandler(socket, hostname, port);
3765
3837
  } else {
3766
3838
  logger.log(`[MITM] No bridge available for ${hostname} \u2014 falling back to passthrough`);
3839
+ mitmStats.recordPassthrough();
3767
3840
  }
3841
+ },
3842
+ onPassthrough: () => {
3843
+ mitmStats.recordPassthrough();
3768
3844
  }
3769
3845
  });
3770
3846
  const server = new ProxyServer(config.port, handler, connectHandler);
@@ -3789,6 +3865,7 @@ async function startCommand(flags) {
3789
3865
  caKeyPem: ca.keyPem,
3790
3866
  logger
3791
3867
  });
3868
+ mitmStats.enable();
3792
3869
  logger.log("[MITM] TLS bridge active \u2014 intercepting LLM API calls");
3793
3870
  }
3794
3871
  }
@@ -3890,6 +3967,17 @@ async function statusCommand() {
3890
3967
  const latencyFlag = globalP95 >= config.latencyCriticalMs ? " CRITICAL" : globalP95 >= config.latencyWarningMs ? " WARNING" : "";
3891
3968
  console.log(`Latency: p95 ${globalP95.toFixed(0)}ms${latencyFlag}`);
3892
3969
  }
3970
+ if (data.mitm) {
3971
+ const m = data.mitm;
3972
+ if (m.enabled) {
3973
+ console.log(`MITM: active (${m.intercepted} intercepted, ${m.passthrough} passthrough, ${m.activeBridges} active)`);
3974
+ if (m.hostsIntercepted.length > 0) {
3975
+ console.log(` Hosts: ${m.hostsIntercepted.join(", ")}`);
3976
+ }
3977
+ } else {
3978
+ console.log('MITM: disabled (run "liminal trust-ca" to enable)');
3979
+ }
3980
+ }
3893
3981
  if (data.sessions.length > 0) {
3894
3982
  console.log();
3895
3983
  console.log("\u2500\u2500\u2500 Sessions \u2500\u2500\u2500");
@@ -4311,6 +4399,133 @@ async function untrustCACommand() {
4311
4399
  console.log(" Cursor MITM interception is no longer available.");
4312
4400
  }
4313
4401
 
4402
+ // src/commands/setup-cursor.ts
4403
+ import { existsSync as existsSync11 } from "fs";
4404
+ import { homedir as homedir5 } from "os";
4405
+ import { join as join6 } from "path";
4406
+ import { execSync as execSync4, spawn } from "child_process";
4407
+ function findCursorBinary() {
4408
+ const platform = process.platform;
4409
+ if (platform === "darwin") {
4410
+ const cliPath = "/usr/local/bin/cursor";
4411
+ if (existsSync11(cliPath)) return cliPath;
4412
+ const appPath = "/Applications/Cursor.app";
4413
+ if (existsSync11(appPath)) {
4414
+ return "open";
4415
+ }
4416
+ return null;
4417
+ }
4418
+ if (platform === "win32") {
4419
+ const localAppData = process.env.LOCALAPPDATA || join6(homedir5(), "AppData", "Local");
4420
+ const exePath = join6(localAppData, "Programs", "Cursor", "Cursor.exe");
4421
+ if (existsSync11(exePath)) return exePath;
4422
+ return null;
4423
+ }
4424
+ const linuxPath = "/usr/bin/cursor";
4425
+ if (existsSync11(linuxPath)) return linuxPath;
4426
+ try {
4427
+ return execSync4("which cursor", { encoding: "utf-8" }).trim();
4428
+ } catch {
4429
+ return null;
4430
+ }
4431
+ }
4432
+ async function setupCursorCommand(flags) {
4433
+ printBanner();
4434
+ const launch = !flags.has("no-launch");
4435
+ console.log(" [1/4] Checking Cursor installation...");
4436
+ const cursorBin = findCursorBinary();
4437
+ if (!cursorBin) {
4438
+ console.error(" Cursor not found. Install Cursor from https://cursor.com");
4439
+ process.exit(1);
4440
+ }
4441
+ console.log(` Found: ${cursorBin}`);
4442
+ console.log();
4443
+ console.log(" [2/4] Checking CA certificate...");
4444
+ if (!hasCA()) {
4445
+ console.log(" Generating CA certificate...");
4446
+ ensureCA();
4447
+ console.log(` Created ${CA_CERT_PATH}`);
4448
+ }
4449
+ if (!isCATrusted()) {
4450
+ console.log(" Installing CA into system trust store...");
4451
+ console.log();
4452
+ console.log(" This allows Liminal to transparently compress LLM API");
4453
+ console.log(" traffic from Cursor. The certificate is scoped to your");
4454
+ console.log(" user account and only used locally.");
4455
+ console.log();
4456
+ const result = installCA();
4457
+ if (!result.success) {
4458
+ console.error(` CA install failed: ${result.message}`);
4459
+ if (result.requiresSudo) {
4460
+ console.log(" Try: sudo liminal setup cursor");
4461
+ }
4462
+ process.exit(1);
4463
+ }
4464
+ console.log(` ${result.message}`);
4465
+ } else {
4466
+ console.log(" CA already trusted");
4467
+ }
4468
+ const info = getCAInfo();
4469
+ if (info) {
4470
+ console.log(` Fingerprint: ${info.fingerprint}`);
4471
+ }
4472
+ console.log();
4473
+ console.log(" [3/4] Checking Liminal proxy...");
4474
+ if (!isConfigured()) {
4475
+ console.error(' Liminal is not configured. Run "liminal init" first.');
4476
+ process.exit(1);
4477
+ }
4478
+ const state = isDaemonRunning();
4479
+ const config = loadConfig();
4480
+ const port = config.port;
4481
+ if (!state.running) {
4482
+ console.log(" Proxy is not running.");
4483
+ console.log();
4484
+ console.log(" Start the proxy first, then re-run this command:");
4485
+ console.log(" liminal start -d");
4486
+ console.log(" liminal setup cursor");
4487
+ console.log();
4488
+ console.log(" Or start in foreground + launch Cursor manually:");
4489
+ console.log(` liminal start & cursor --proxy-server=http://127.0.0.1:${port}`);
4490
+ process.exit(1);
4491
+ }
4492
+ console.log(` Proxy running on port ${port} (PID ${state.pid})`);
4493
+ console.log();
4494
+ const proxyUrl = `http://127.0.0.1:${port}`;
4495
+ if (launch) {
4496
+ console.log(" [4/4] Launching Cursor with proxy...");
4497
+ console.log(` --proxy-server=${proxyUrl}`);
4498
+ console.log();
4499
+ launchCursor(cursorBin, proxyUrl);
4500
+ console.log(" Cursor launched with Liminal compression active.");
4501
+ console.log();
4502
+ console.log(" All LLM API calls will be transparently compressed.");
4503
+ console.log(' Use "liminal status" to monitor compression stats.');
4504
+ console.log(' Use "liminal logs -f" to watch live traffic.');
4505
+ } else {
4506
+ console.log(" [4/4] Ready! Launch Cursor with:");
4507
+ console.log();
4508
+ console.log(` cursor --proxy-server=${proxyUrl}`);
4509
+ console.log();
4510
+ console.log(" Or create a shell alias:");
4511
+ console.log(` alias cursor-liminal='cursor --proxy-server=${proxyUrl}'`);
4512
+ }
4513
+ }
4514
+ function launchCursor(bin, proxyUrl) {
4515
+ const args = [`--proxy-server=${proxyUrl}`];
4516
+ if (bin === "open" && process.platform === "darwin") {
4517
+ spawn("open", ["-a", "Cursor", "--args", ...args], {
4518
+ detached: true,
4519
+ stdio: "ignore"
4520
+ }).unref();
4521
+ } else {
4522
+ spawn(bin, args, {
4523
+ detached: true,
4524
+ stdio: "ignore"
4525
+ }).unref();
4526
+ }
4527
+ }
4528
+
4314
4529
  // src/bin.ts
4315
4530
  var USAGE = `
4316
4531
  liminal v${VERSION} \u2014 Transparent LLM context compression proxy
@@ -4325,6 +4540,7 @@ var USAGE = `
4325
4540
  liminal summary Detailed session metrics
4326
4541
  liminal config [--set k=v] [--get k] View or edit configuration
4327
4542
  liminal logs [--follow] [--lines N] View proxy logs
4543
+ liminal setup cursor [--no-launch] Set up and launch Cursor with Liminal
4328
4544
  liminal trust-ca Install CA cert (for Cursor MITM)
4329
4545
  liminal untrust-ca Remove CA cert
4330
4546
  liminal uninstall Remove Liminal configuration
@@ -4405,6 +4621,17 @@ async function main() {
4405
4621
  case "logs":
4406
4622
  await logsCommand(flags);
4407
4623
  break;
4624
+ case "setup": {
4625
+ const subcommand = process.argv[3];
4626
+ if (subcommand === "cursor") {
4627
+ await setupCursorCommand(flags);
4628
+ } else {
4629
+ console.error(`Unknown setup target: ${subcommand ?? "(none)"}`);
4630
+ console.error("Available: liminal setup cursor");
4631
+ process.exit(1);
4632
+ }
4633
+ break;
4634
+ }
4408
4635
  case "trust-ca":
4409
4636
  await trustCACommand();
4410
4637
  break;