@cognisos/liminal 0.2.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +386 -93
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/version.ts
|
|
4
|
-
var VERSION = true ? "
|
|
4
|
+
var VERSION = true ? "2.2.0" : "0.2.1";
|
|
5
5
|
var BANNER_LINES = [
|
|
6
6
|
" ___ ___ _____ ______ ___ ________ ________ ___",
|
|
7
7
|
"|\\ \\ |\\ \\|\\ _ \\ _ \\|\\ \\|\\ ___ \\|\\ __ \\|\\ \\",
|
|
@@ -22,9 +22,11 @@ function printBanner() {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// src/commands/init.ts
|
|
25
|
-
import { createInterface } from "readline/promises";
|
|
26
|
-
import { stdin, stdout } from "process";
|
|
27
|
-
import {
|
|
25
|
+
import { createInterface as createInterface2 } from "readline/promises";
|
|
26
|
+
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
27
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync } from "fs";
|
|
28
|
+
import { join as join2 } from "path";
|
|
29
|
+
import { homedir as homedir2 } from "os";
|
|
28
30
|
|
|
29
31
|
// src/config/loader.ts
|
|
30
32
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
@@ -152,6 +154,10 @@ function parseKey(data) {
|
|
|
152
154
|
if (data.length === 1 && data[0] === 27) return { type: "escape" };
|
|
153
155
|
if (data[0] === 13 || data[0] === 10) return { type: "enter" };
|
|
154
156
|
if (data[0] === 32) return { type: "space" };
|
|
157
|
+
if (data[0] === 127 || data[0] === 8) return { type: "backspace" };
|
|
158
|
+
if (data.length === 1 && data[0] >= 33 && data[0] <= 126) {
|
|
159
|
+
return { type: "char", char: String.fromCharCode(data[0]) };
|
|
160
|
+
}
|
|
155
161
|
return { type: "other" };
|
|
156
162
|
}
|
|
157
163
|
function renderSelect(options, cursorIndex, message) {
|
|
@@ -189,18 +195,18 @@ function renderMultiSelect(options, cursorIndex, selected, message) {
|
|
|
189
195
|
return { text: lines.join("\n"), lineCount: lines.length };
|
|
190
196
|
}
|
|
191
197
|
function withRawMode(streams, handler) {
|
|
192
|
-
const { stdin:
|
|
198
|
+
const { stdin: stdin3, stdout: stdout3 } = streams;
|
|
193
199
|
return new Promise((resolve, reject) => {
|
|
194
200
|
let cleaned = false;
|
|
195
201
|
function cleanup() {
|
|
196
202
|
if (cleaned) return;
|
|
197
203
|
cleaned = true;
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
if ("pause" in
|
|
201
|
-
|
|
204
|
+
stdin3.removeListener("data", onData);
|
|
205
|
+
if (stdin3.setRawMode) stdin3.setRawMode(false);
|
|
206
|
+
if ("pause" in stdin3 && typeof stdin3.pause === "function") {
|
|
207
|
+
stdin3.pause();
|
|
202
208
|
}
|
|
203
|
-
|
|
209
|
+
stdout3.write(ANSI.SHOW_CURSOR);
|
|
204
210
|
process.removeListener("exit", cleanup);
|
|
205
211
|
}
|
|
206
212
|
function onData(data) {
|
|
@@ -217,11 +223,11 @@ function withRawMode(streams, handler) {
|
|
|
217
223
|
});
|
|
218
224
|
process.on("exit", cleanup);
|
|
219
225
|
try {
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if ("resume" in
|
|
224
|
-
|
|
226
|
+
if (stdin3.setRawMode) stdin3.setRawMode(true);
|
|
227
|
+
stdout3.write(ANSI.HIDE_CURSOR);
|
|
228
|
+
stdin3.on("data", onData);
|
|
229
|
+
if ("resume" in stdin3 && typeof stdin3.resume === "function") {
|
|
230
|
+
stdin3.resume();
|
|
225
231
|
}
|
|
226
232
|
} catch (err) {
|
|
227
233
|
cleanup();
|
|
@@ -348,34 +354,279 @@ async function multiSelectPrompt(config) {
|
|
|
348
354
|
}
|
|
349
355
|
return result;
|
|
350
356
|
}
|
|
357
|
+
async function passwordPrompt(config) {
|
|
358
|
+
const { message, _streams } = config;
|
|
359
|
+
const streams = _streams ?? { stdin: process.stdin, stdout: process.stdout };
|
|
360
|
+
let password = "";
|
|
361
|
+
streams.stdout.write(` ${ANSI.BOLD}${message}${ANSI.RESET}: `);
|
|
362
|
+
const result = await withRawMode(streams, (resolve) => {
|
|
363
|
+
return (key) => {
|
|
364
|
+
switch (key.type) {
|
|
365
|
+
case "char":
|
|
366
|
+
password += key.char;
|
|
367
|
+
streams.stdout.write("*");
|
|
368
|
+
break;
|
|
369
|
+
case "space":
|
|
370
|
+
password += " ";
|
|
371
|
+
streams.stdout.write("*");
|
|
372
|
+
break;
|
|
373
|
+
case "backspace":
|
|
374
|
+
if (password.length > 0) {
|
|
375
|
+
password = password.slice(0, -1);
|
|
376
|
+
streams.stdout.write("\b \b");
|
|
377
|
+
}
|
|
378
|
+
break;
|
|
379
|
+
case "enter":
|
|
380
|
+
streams.stdout.write("\n");
|
|
381
|
+
resolve(password);
|
|
382
|
+
break;
|
|
383
|
+
case "escape":
|
|
384
|
+
streams.stdout.write("\n");
|
|
385
|
+
resolve("");
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
return result;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/commands/login.ts
|
|
394
|
+
import { createInterface } from "readline/promises";
|
|
395
|
+
import { stdin, stdout } from "process";
|
|
396
|
+
|
|
397
|
+
// src/auth/supabase.ts
|
|
398
|
+
import { randomBytes } from "crypto";
|
|
399
|
+
var SUPABASE_URL = "https://nzcneiyymvgxvttbenhp.supabase.co";
|
|
400
|
+
var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im56Y25laXl5bXZneHZ0dGJlbmhwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQwNjQ0MjcsImV4cCI6MjA2OTY0MDQyN30.x3E-zGRadbPMmxRqT_PB_KOi00htKpgeb8GiQa4g2z0";
|
|
401
|
+
function supabaseHeaders(accessToken) {
|
|
402
|
+
const headers = {
|
|
403
|
+
"Content-Type": "application/json",
|
|
404
|
+
"apikey": SUPABASE_ANON_KEY
|
|
405
|
+
};
|
|
406
|
+
if (accessToken) {
|
|
407
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
408
|
+
}
|
|
409
|
+
return headers;
|
|
410
|
+
}
|
|
411
|
+
async function signIn(email, password) {
|
|
412
|
+
const res = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=password`, {
|
|
413
|
+
method: "POST",
|
|
414
|
+
headers: supabaseHeaders(),
|
|
415
|
+
body: JSON.stringify({ email, password })
|
|
416
|
+
});
|
|
417
|
+
const body = await res.json();
|
|
418
|
+
if (!res.ok) {
|
|
419
|
+
const msg = body?.error_description || body?.error || body?.msg || "Authentication failed";
|
|
420
|
+
const err = { message: msg, status: res.status };
|
|
421
|
+
throw err;
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
accessToken: body.access_token,
|
|
425
|
+
userId: body.user?.id,
|
|
426
|
+
email: body.user?.email ?? email
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
async function signUp(email, password, name) {
|
|
430
|
+
const res = await fetch(`${SUPABASE_URL}/auth/v1/signup`, {
|
|
431
|
+
method: "POST",
|
|
432
|
+
headers: supabaseHeaders(),
|
|
433
|
+
body: JSON.stringify({
|
|
434
|
+
email,
|
|
435
|
+
password,
|
|
436
|
+
data: { name }
|
|
437
|
+
})
|
|
438
|
+
});
|
|
439
|
+
const body = await res.json();
|
|
440
|
+
if (!res.ok) {
|
|
441
|
+
const msg = body?.error_description || body?.error || body?.msg || "Sign up failed";
|
|
442
|
+
const err = { message: msg, status: res.status };
|
|
443
|
+
throw err;
|
|
444
|
+
}
|
|
445
|
+
if (body.user?.identities?.length === 0) {
|
|
446
|
+
const err = { message: "Account already exists. Try logging in instead." };
|
|
447
|
+
throw err;
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
accessToken: body.access_token,
|
|
451
|
+
userId: body.user?.id,
|
|
452
|
+
email: body.user?.email ?? email
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
async function fetchApiKey(accessToken, userId) {
|
|
456
|
+
const params = new URLSearchParams({
|
|
457
|
+
user_id: `eq.${userId}`,
|
|
458
|
+
is_active: "eq.true",
|
|
459
|
+
select: "api_key",
|
|
460
|
+
limit: "1"
|
|
461
|
+
});
|
|
462
|
+
const res = await fetch(`${SUPABASE_URL}/rest/v1/user_api_keys?${params}`, {
|
|
463
|
+
headers: supabaseHeaders(accessToken)
|
|
464
|
+
});
|
|
465
|
+
if (!res.ok) return null;
|
|
466
|
+
const rows = await res.json();
|
|
467
|
+
if (Array.isArray(rows) && rows.length > 0 && rows[0].api_key) {
|
|
468
|
+
return rows[0].api_key;
|
|
469
|
+
}
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
async function createApiKey(accessToken, userId) {
|
|
473
|
+
const apiKey = `fmcp_${randomBytes(32).toString("hex")}`;
|
|
474
|
+
const res = await fetch(`${SUPABASE_URL}/rest/v1/user_api_keys`, {
|
|
475
|
+
method: "POST",
|
|
476
|
+
headers: {
|
|
477
|
+
...supabaseHeaders(accessToken),
|
|
478
|
+
"Prefer": "return=representation"
|
|
479
|
+
},
|
|
480
|
+
body: JSON.stringify({
|
|
481
|
+
user_id: userId,
|
|
482
|
+
key_name: "Liminal CLI",
|
|
483
|
+
api_key: apiKey,
|
|
484
|
+
is_active: true
|
|
485
|
+
})
|
|
486
|
+
});
|
|
487
|
+
if (!res.ok) {
|
|
488
|
+
const body = await res.json().catch(() => ({}));
|
|
489
|
+
const msg = body?.message || body?.error || "Failed to create API key";
|
|
490
|
+
throw { message: msg, status: res.status };
|
|
491
|
+
}
|
|
492
|
+
return apiKey;
|
|
493
|
+
}
|
|
494
|
+
async function authenticateAndGetKey(auth) {
|
|
495
|
+
const existingKey = await fetchApiKey(auth.accessToken, auth.userId);
|
|
496
|
+
if (existingKey) return existingKey;
|
|
497
|
+
return createApiKey(auth.accessToken, auth.userId);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/commands/login.ts
|
|
501
|
+
async function loginCommand() {
|
|
502
|
+
printBanner();
|
|
503
|
+
try {
|
|
504
|
+
const config = loadConfig();
|
|
505
|
+
if (config.apiKey && config.apiKey.startsWith("fmcp_")) {
|
|
506
|
+
console.log(" Already logged in.");
|
|
507
|
+
console.log(" Run \x1B[1mliminal logout\x1B[0m first to switch accounts.");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
} catch {
|
|
511
|
+
}
|
|
512
|
+
await runAuthFlow();
|
|
513
|
+
}
|
|
514
|
+
async function runAuthFlow() {
|
|
515
|
+
const modeResult = await selectPrompt({
|
|
516
|
+
message: "Welcome to Liminal",
|
|
517
|
+
options: [
|
|
518
|
+
{ label: "Log in", value: "login", description: "I have an account" },
|
|
519
|
+
{ label: "Create account", value: "signup", description: "New to Liminal" }
|
|
520
|
+
],
|
|
521
|
+
defaultIndex: 0
|
|
522
|
+
});
|
|
523
|
+
const mode = modeResult ?? "login";
|
|
524
|
+
console.log();
|
|
525
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
526
|
+
let name = "";
|
|
527
|
+
let email;
|
|
528
|
+
try {
|
|
529
|
+
if (mode === "signup") {
|
|
530
|
+
name = (await rl.question(" \x1B[1mName\x1B[0m: ")).trim();
|
|
531
|
+
if (!name) {
|
|
532
|
+
console.error("\n Error: Name is required.");
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
email = (await rl.question(" \x1B[1mEmail\x1B[0m: ")).trim();
|
|
537
|
+
if (!email) {
|
|
538
|
+
console.error("\n Error: Email is required.");
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
} finally {
|
|
542
|
+
rl.close();
|
|
543
|
+
}
|
|
544
|
+
const password = await passwordPrompt({ message: "Password" });
|
|
545
|
+
if (!password) {
|
|
546
|
+
console.error("\n Error: Password is required.");
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
console.log();
|
|
550
|
+
const action = mode === "signup" ? "Creating account" : "Logging in";
|
|
551
|
+
process.stdout.write(` ${action}... `);
|
|
552
|
+
try {
|
|
553
|
+
const auth = mode === "signup" ? await signUp(email, password, name) : await signIn(email, password);
|
|
554
|
+
console.log("OK");
|
|
555
|
+
process.stdout.write(" Setting up API key... ");
|
|
556
|
+
const apiKey = await authenticateAndGetKey(auth);
|
|
557
|
+
console.log("OK");
|
|
558
|
+
ensureDirectories();
|
|
559
|
+
saveConfig({
|
|
560
|
+
apiKey,
|
|
561
|
+
apiBaseUrl: DEFAULTS.apiBaseUrl
|
|
562
|
+
});
|
|
563
|
+
console.log();
|
|
564
|
+
console.log(` Logged in as \x1B[1m${auth.email}\x1B[0m`);
|
|
565
|
+
return apiKey;
|
|
566
|
+
} catch (err) {
|
|
567
|
+
console.log("FAILED");
|
|
568
|
+
const authErr = err;
|
|
569
|
+
console.error(`
|
|
570
|
+
${authErr.message}`);
|
|
571
|
+
if (mode === "login" && authErr.message?.includes("Invalid login")) {
|
|
572
|
+
console.error(" Check your email and password, then try again.");
|
|
573
|
+
}
|
|
574
|
+
process.exit(1);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
351
577
|
|
|
352
578
|
// src/commands/init.ts
|
|
353
|
-
function
|
|
579
|
+
function detectShellProfile() {
|
|
580
|
+
const shell = process.env.SHELL || "";
|
|
581
|
+
const home = homedir2();
|
|
582
|
+
if (shell.endsWith("/zsh")) {
|
|
583
|
+
const zshrc = join2(home, ".zshrc");
|
|
584
|
+
return { name: "~/.zshrc", path: zshrc };
|
|
585
|
+
}
|
|
586
|
+
if (shell.endsWith("/bash")) {
|
|
587
|
+
const bashProfile = join2(home, ".bash_profile");
|
|
588
|
+
if (existsSync2(bashProfile)) {
|
|
589
|
+
return { name: "~/.bash_profile", path: bashProfile };
|
|
590
|
+
}
|
|
591
|
+
return { name: "~/.bashrc", path: join2(home, ".bashrc") };
|
|
592
|
+
}
|
|
593
|
+
const candidates = [
|
|
594
|
+
{ name: "~/.zshrc", path: join2(home, ".zshrc") },
|
|
595
|
+
{ name: "~/.bashrc", path: join2(home, ".bashrc") },
|
|
596
|
+
{ name: "~/.profile", path: join2(home, ".profile") }
|
|
597
|
+
];
|
|
598
|
+
for (const c of candidates) {
|
|
599
|
+
if (existsSync2(c.path)) return c;
|
|
600
|
+
}
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
function getExportLines(tools, port) {
|
|
354
604
|
const base = `http://127.0.0.1:${port}`;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
break;
|
|
362
|
-
case "codex":
|
|
363
|
-
console.log(" Codex:");
|
|
364
|
-
console.log(` export OPENAI_BASE_URL=${base}/v1`);
|
|
365
|
-
console.log(" (Add to your shell profile for persistence)");
|
|
366
|
-
console.log();
|
|
367
|
-
break;
|
|
368
|
-
case "cursor":
|
|
369
|
-
console.log(" Cursor:");
|
|
370
|
-
console.log(` Settings > Models > OpenAI API Base URL: ${base}/v1`);
|
|
371
|
-
console.log();
|
|
372
|
-
break;
|
|
373
|
-
case "openai-compatible":
|
|
374
|
-
console.log(" OpenAI-compatible tools:");
|
|
375
|
-
console.log(` Set your base URL to: ${base}/v1`);
|
|
376
|
-
console.log();
|
|
377
|
-
break;
|
|
605
|
+
const lines = [];
|
|
606
|
+
if (tools.includes("claude-code")) {
|
|
607
|
+
lines.push(`export ANTHROPIC_BASE_URL=${base}`);
|
|
608
|
+
}
|
|
609
|
+
if (tools.includes("codex") || tools.includes("openai-compatible")) {
|
|
610
|
+
lines.push(`export OPENAI_BASE_URL=${base}/v1`);
|
|
378
611
|
}
|
|
612
|
+
return lines;
|
|
613
|
+
}
|
|
614
|
+
function lineExistsInFile(filePath, line) {
|
|
615
|
+
if (!existsSync2(filePath)) return false;
|
|
616
|
+
try {
|
|
617
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
618
|
+
return content.includes(line);
|
|
619
|
+
} catch {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function appendToShellProfile(profile, lines) {
|
|
624
|
+
const block = [
|
|
625
|
+
"",
|
|
626
|
+
"# Liminal \u2014 route AI tools through compression proxy",
|
|
627
|
+
...lines
|
|
628
|
+
].join("\n") + "\n";
|
|
629
|
+
appendFileSync(profile.path, block, "utf-8");
|
|
379
630
|
}
|
|
380
631
|
async function initCommand() {
|
|
381
632
|
printBanner();
|
|
@@ -383,16 +634,11 @@ async function initCommand() {
|
|
|
383
634
|
console.log();
|
|
384
635
|
console.log(" Let's start evolving.");
|
|
385
636
|
console.log();
|
|
386
|
-
const
|
|
387
|
-
|
|
637
|
+
const apiKey = await runAuthFlow();
|
|
638
|
+
console.log();
|
|
639
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
388
640
|
let port;
|
|
389
641
|
try {
|
|
390
|
-
const apiKeyInput = await rl.question(" \x1B[1mLiminal API key\x1B[0m: ");
|
|
391
|
-
if (!apiKeyInput.trim()) {
|
|
392
|
-
console.error("\n Error: API key is required.");
|
|
393
|
-
process.exit(1);
|
|
394
|
-
}
|
|
395
|
-
apiKey = apiKeyInput.trim();
|
|
396
642
|
const portInput = await rl.question(` Proxy port [${DEFAULTS.port}]: `);
|
|
397
643
|
port = portInput.trim() ? parseInt(portInput.trim(), 10) : DEFAULTS.port;
|
|
398
644
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
@@ -406,10 +652,10 @@ async function initCommand() {
|
|
|
406
652
|
const toolsResult = await multiSelectPrompt({
|
|
407
653
|
message: "Which AI tools will you use with Liminal?",
|
|
408
654
|
options: [
|
|
409
|
-
{ label: "Claude Code", value: "claude-code",
|
|
410
|
-
{ label: "Codex", value: "codex"
|
|
411
|
-
{ label: "Cursor", value: "cursor"
|
|
412
|
-
{ label: "Other / OpenAI", value: "openai-compatible"
|
|
655
|
+
{ label: "Claude Code", value: "claude-code", default: true },
|
|
656
|
+
{ label: "Codex", value: "codex" },
|
|
657
|
+
{ label: "Cursor", value: "cursor" },
|
|
658
|
+
{ label: "Other / OpenAI", value: "openai-compatible" }
|
|
413
659
|
]
|
|
414
660
|
});
|
|
415
661
|
const tools = toolsResult ?? ["claude-code"];
|
|
@@ -425,26 +671,6 @@ async function initCommand() {
|
|
|
425
671
|
const learnFromResponses = learnResult ?? true;
|
|
426
672
|
console.log();
|
|
427
673
|
const apiBaseUrl = DEFAULTS.apiBaseUrl;
|
|
428
|
-
process.stdout.write(" Validating API key... ");
|
|
429
|
-
try {
|
|
430
|
-
const breaker = new CircuitBreaker(3, 1e4);
|
|
431
|
-
const transport = new RSCTransport({
|
|
432
|
-
baseUrl: apiBaseUrl,
|
|
433
|
-
apiKey,
|
|
434
|
-
timeout: 1e4,
|
|
435
|
-
maxRetries: 1,
|
|
436
|
-
circuitBreaker: breaker
|
|
437
|
-
});
|
|
438
|
-
await transport.get("/health");
|
|
439
|
-
console.log("OK");
|
|
440
|
-
} catch (err) {
|
|
441
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
442
|
-
console.log("FAILED");
|
|
443
|
-
console.error(`
|
|
444
|
-
Could not connect to Liminal API: ${message}`);
|
|
445
|
-
console.error(" Check your API key and URL, then try again.");
|
|
446
|
-
process.exit(1);
|
|
447
|
-
}
|
|
448
674
|
ensureDirectories();
|
|
449
675
|
saveConfig({
|
|
450
676
|
apiKey,
|
|
@@ -462,22 +688,81 @@ async function initCommand() {
|
|
|
462
688
|
console.log();
|
|
463
689
|
console.log(` Configuration saved to ${CONFIG_FILE}`);
|
|
464
690
|
console.log();
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
691
|
+
const exportLines = getExportLines(tools, port);
|
|
692
|
+
const hasCursor = tools.includes("cursor");
|
|
693
|
+
if (exportLines.length > 0) {
|
|
694
|
+
const profile = detectShellProfile();
|
|
695
|
+
if (profile) {
|
|
696
|
+
const allExist = exportLines.every((line) => lineExistsInFile(profile.path, line));
|
|
697
|
+
if (allExist) {
|
|
698
|
+
console.log(` Shell already configured in ${profile.name}`);
|
|
699
|
+
} else {
|
|
700
|
+
const autoResult = await selectPrompt({
|
|
701
|
+
message: "Configure shell automatically?",
|
|
702
|
+
options: [
|
|
703
|
+
{ label: "Yes", value: true, description: `Add to ${profile.name}` },
|
|
704
|
+
{ label: "No", value: false, description: "I'll set it up manually" }
|
|
705
|
+
],
|
|
706
|
+
defaultIndex: 0
|
|
707
|
+
});
|
|
708
|
+
if (autoResult === true) {
|
|
709
|
+
const newLines = exportLines.filter((line) => !lineExistsInFile(profile.path, line));
|
|
710
|
+
if (newLines.length > 0) {
|
|
711
|
+
appendToShellProfile(profile, newLines);
|
|
712
|
+
}
|
|
713
|
+
console.log();
|
|
714
|
+
console.log(` Added to ${profile.name}:`);
|
|
715
|
+
for (const line of exportLines) {
|
|
716
|
+
console.log(` ${line}`);
|
|
717
|
+
}
|
|
718
|
+
console.log();
|
|
719
|
+
console.log(` Run \x1B[1msource ${profile.name}\x1B[0m or restart your terminal to apply.`);
|
|
720
|
+
} else {
|
|
721
|
+
console.log();
|
|
722
|
+
console.log(" Add these to your shell profile:");
|
|
723
|
+
console.log();
|
|
724
|
+
for (const line of exportLines) {
|
|
725
|
+
console.log(` ${line}`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
console.log(" Add these to your shell profile:");
|
|
731
|
+
console.log();
|
|
732
|
+
for (const line of exportLines) {
|
|
733
|
+
console.log(` ${line}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (hasCursor) {
|
|
738
|
+
console.log();
|
|
739
|
+
console.log(" Cursor setup (manual):");
|
|
740
|
+
console.log(` Settings > Models > OpenAI API Base URL: http://127.0.0.1:${port}/v1`);
|
|
741
|
+
}
|
|
742
|
+
console.log();
|
|
743
|
+
console.log(" Next step:");
|
|
744
|
+
console.log(" liminal start");
|
|
468
745
|
console.log();
|
|
469
|
-
|
|
470
|
-
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// src/commands/logout.ts
|
|
749
|
+
async function logoutCommand() {
|
|
750
|
+
if (!isConfigured()) {
|
|
751
|
+
console.log(" Not currently logged in.");
|
|
752
|
+
return;
|
|
471
753
|
}
|
|
754
|
+
saveConfig({ apiKey: "" });
|
|
755
|
+
console.log(" Logged out.");
|
|
756
|
+
console.log(" Run \x1B[1mliminal login\x1B[0m to reconnect.");
|
|
472
757
|
}
|
|
473
758
|
|
|
474
759
|
// src/rsc/pipeline.ts
|
|
475
760
|
import {
|
|
476
761
|
CompressionPipeline,
|
|
477
|
-
RSCTransport
|
|
762
|
+
RSCTransport,
|
|
478
763
|
RSCEventEmitter,
|
|
479
764
|
Session,
|
|
480
|
-
CircuitBreaker
|
|
765
|
+
CircuitBreaker
|
|
481
766
|
} from "@cognisos/rsc-sdk";
|
|
482
767
|
var RSCPipelineWrapper = class {
|
|
483
768
|
pipeline;
|
|
@@ -486,8 +771,8 @@ var RSCPipelineWrapper = class {
|
|
|
486
771
|
transport;
|
|
487
772
|
circuitBreaker;
|
|
488
773
|
constructor(config) {
|
|
489
|
-
this.circuitBreaker = new
|
|
490
|
-
this.transport = new
|
|
774
|
+
this.circuitBreaker = new CircuitBreaker(5, 5 * 60 * 1e3);
|
|
775
|
+
this.transport = new RSCTransport({
|
|
491
776
|
baseUrl: config.rscBaseUrl,
|
|
492
777
|
apiKey: config.rscApiKey,
|
|
493
778
|
timeout: 3e4,
|
|
@@ -1131,7 +1416,7 @@ var ProxyServer = class {
|
|
|
1131
1416
|
};
|
|
1132
1417
|
|
|
1133
1418
|
// src/daemon/logger.ts
|
|
1134
|
-
import { appendFileSync, statSync, renameSync, mkdirSync as mkdirSync2, existsSync as
|
|
1419
|
+
import { appendFileSync as appendFileSync2, statSync, renameSync, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
1135
1420
|
import { dirname as dirname2 } from "path";
|
|
1136
1421
|
var MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
1137
1422
|
var MAX_BACKUPS = 2;
|
|
@@ -1142,7 +1427,7 @@ var FileLogger = class {
|
|
|
1142
1427
|
this.logFile = options?.logFile ?? LOG_FILE;
|
|
1143
1428
|
this.mirrorStdout = options?.mirrorStdout ?? false;
|
|
1144
1429
|
const logDir = dirname2(this.logFile);
|
|
1145
|
-
if (!
|
|
1430
|
+
if (!existsSync3(logDir)) {
|
|
1146
1431
|
mkdirSync2(logDir, { recursive: true });
|
|
1147
1432
|
}
|
|
1148
1433
|
}
|
|
@@ -1151,7 +1436,7 @@ var FileLogger = class {
|
|
|
1151
1436
|
const line = `[${timestamp}] ${message}
|
|
1152
1437
|
`;
|
|
1153
1438
|
try {
|
|
1154
|
-
|
|
1439
|
+
appendFileSync2(this.logFile, line);
|
|
1155
1440
|
} catch {
|
|
1156
1441
|
process.stderr.write(`[LOG-WRITE-FAILED] ${line}`);
|
|
1157
1442
|
}
|
|
@@ -1167,7 +1452,7 @@ var FileLogger = class {
|
|
|
1167
1452
|
for (let i = MAX_BACKUPS - 1; i >= 1; i--) {
|
|
1168
1453
|
const from = `${this.logFile}.${i}`;
|
|
1169
1454
|
const to = `${this.logFile}.${i + 1}`;
|
|
1170
|
-
if (
|
|
1455
|
+
if (existsSync3(from)) renameSync(from, to);
|
|
1171
1456
|
}
|
|
1172
1457
|
renameSync(this.logFile, `${this.logFile}.1`);
|
|
1173
1458
|
} catch {
|
|
@@ -1179,16 +1464,16 @@ var FileLogger = class {
|
|
|
1179
1464
|
};
|
|
1180
1465
|
|
|
1181
1466
|
// src/daemon/lifecycle.ts
|
|
1182
|
-
import { readFileSync as
|
|
1467
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync, existsSync as existsSync4 } from "fs";
|
|
1183
1468
|
import { fork } from "child_process";
|
|
1184
1469
|
import { fileURLToPath } from "url";
|
|
1185
1470
|
function writePidFile(pid) {
|
|
1186
1471
|
writeFileSync2(PID_FILE, String(pid), "utf-8");
|
|
1187
1472
|
}
|
|
1188
1473
|
function readPidFile() {
|
|
1189
|
-
if (!
|
|
1474
|
+
if (!existsSync4(PID_FILE)) return null;
|
|
1190
1475
|
try {
|
|
1191
|
-
const content =
|
|
1476
|
+
const content = readFileSync3(PID_FILE, "utf-8").trim();
|
|
1192
1477
|
const pid = parseInt(content, 10);
|
|
1193
1478
|
return isNaN(pid) ? null : pid;
|
|
1194
1479
|
} catch {
|
|
@@ -1197,7 +1482,7 @@ function readPidFile() {
|
|
|
1197
1482
|
}
|
|
1198
1483
|
function removePidFile() {
|
|
1199
1484
|
try {
|
|
1200
|
-
if (
|
|
1485
|
+
if (existsSync4(PID_FILE)) unlinkSync(PID_FILE);
|
|
1201
1486
|
} catch {
|
|
1202
1487
|
}
|
|
1203
1488
|
}
|
|
@@ -1578,17 +1863,17 @@ async function configCommand(flags) {
|
|
|
1578
1863
|
}
|
|
1579
1864
|
|
|
1580
1865
|
// src/commands/logs.ts
|
|
1581
|
-
import { readFileSync as
|
|
1866
|
+
import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync2, createReadStream } from "fs";
|
|
1582
1867
|
import { watchFile, unwatchFile } from "fs";
|
|
1583
1868
|
async function logsCommand(flags) {
|
|
1584
1869
|
const follow = flags.has("follow") || flags.has("f");
|
|
1585
1870
|
const linesFlag = flags.get("lines") ?? flags.get("n");
|
|
1586
1871
|
const lines = typeof linesFlag === "string" ? parseInt(linesFlag, 10) : 50;
|
|
1587
|
-
if (!
|
|
1872
|
+
if (!existsSync5(LOG_FILE)) {
|
|
1588
1873
|
console.log('No log file found. Start the daemon with "liminal start" to generate logs.');
|
|
1589
1874
|
return;
|
|
1590
1875
|
}
|
|
1591
|
-
const content =
|
|
1876
|
+
const content = readFileSync4(LOG_FILE, "utf-8");
|
|
1592
1877
|
const allLines = content.split("\n");
|
|
1593
1878
|
const tail = allLines.slice(-lines - 1);
|
|
1594
1879
|
process.stdout.write(tail.join("\n"));
|
|
@@ -1618,7 +1903,9 @@ var USAGE = `
|
|
|
1618
1903
|
liminal v${VERSION} \u2014 Transparent LLM context compression proxy
|
|
1619
1904
|
|
|
1620
1905
|
Usage:
|
|
1621
|
-
liminal init Set up Liminal (
|
|
1906
|
+
liminal init Set up Liminal (login, config)
|
|
1907
|
+
liminal login Log in or create an account
|
|
1908
|
+
liminal logout Log out of your account
|
|
1622
1909
|
liminal start [-d] [--port PORT] Start the compression proxy
|
|
1623
1910
|
liminal stop Stop the running proxy
|
|
1624
1911
|
liminal status Show proxy health and stats
|
|
@@ -1631,7 +1918,7 @@ var USAGE = `
|
|
|
1631
1918
|
-v, --version Show version number
|
|
1632
1919
|
|
|
1633
1920
|
Getting started:
|
|
1634
|
-
1. liminal init #
|
|
1921
|
+
1. liminal init # Log in + select tools
|
|
1635
1922
|
2. liminal start # Start the proxy
|
|
1636
1923
|
3. Connect your AI tools:
|
|
1637
1924
|
Claude Code: export ANTHROPIC_BASE_URL=http://localhost:3141
|
|
@@ -1678,6 +1965,12 @@ async function main() {
|
|
|
1678
1965
|
case "init":
|
|
1679
1966
|
await initCommand();
|
|
1680
1967
|
break;
|
|
1968
|
+
case "login":
|
|
1969
|
+
await loginCommand();
|
|
1970
|
+
break;
|
|
1971
|
+
case "logout":
|
|
1972
|
+
await logoutCommand();
|
|
1973
|
+
break;
|
|
1681
1974
|
case "start":
|
|
1682
1975
|
await startCommand(flags);
|
|
1683
1976
|
break;
|