@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 +265 -38
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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:
|
|
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 (
|
|
915
|
+
description: "AI-first code editor (proxy-based, automated)",
|
|
916
916
|
protocol: "openai-chat",
|
|
917
|
-
automatable:
|
|
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
|
-
"
|
|
973
|
-
"URLs are blocked. You need a tunnel to expose the proxy:",
|
|
971
|
+
"Run the automated setup:",
|
|
974
972
|
"",
|
|
975
|
-
|
|
973
|
+
" liminal setup cursor",
|
|
976
974
|
"",
|
|
977
|
-
"
|
|
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
|
-
"
|
|
980
|
-
"
|
|
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
|
-
"
|
|
988
|
-
|
|
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
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
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
|
|
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;
|