@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/version.ts
4
- var VERSION = true ? "0.2.0" : "0.2.0";
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 { RSCTransport, CircuitBreaker } from "@cognisos/rsc-sdk";
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: stdin2, stdout: stdout2 } = streams;
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
- stdin2.removeListener("data", onData);
199
- if (stdin2.setRawMode) stdin2.setRawMode(false);
200
- if ("pause" in stdin2 && typeof stdin2.pause === "function") {
201
- stdin2.pause();
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
- stdout2.write(ANSI.SHOW_CURSOR);
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 (stdin2.setRawMode) stdin2.setRawMode(true);
221
- stdout2.write(ANSI.HIDE_CURSOR);
222
- stdin2.on("data", onData);
223
- if ("resume" in stdin2 && typeof stdin2.resume === "function") {
224
- stdin2.resume();
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 printToolInstructions(tool, port) {
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
- switch (tool) {
356
- case "claude-code":
357
- console.log(" Claude Code:");
358
- console.log(` export ANTHROPIC_BASE_URL=${base}`);
359
- console.log(" (Add to your shell profile for persistence)");
360
- console.log();
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 rl = createInterface({ input: stdin, output: stdout });
387
- let apiKey;
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", description: "export ANTHROPIC_BASE_URL=...", default: true },
410
- { label: "Codex", value: "codex", description: "export OPENAI_BASE_URL=..." },
411
- { label: "Cursor", value: "cursor", description: "Settings > Models > Base URL" },
412
- { label: "Other / OpenAI", value: "openai-compatible", description: "Set base URL manually" }
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
- console.log(" Next steps:");
466
- console.log(" 1. Start the proxy: liminal start");
467
- console.log(" 2. Connect your tools:");
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
- for (const tool of tools) {
470
- printToolInstructions(tool, port);
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 as RSCTransport2,
762
+ RSCTransport,
478
763
  RSCEventEmitter,
479
764
  Session,
480
- CircuitBreaker as CircuitBreaker2
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 CircuitBreaker2(5, 5 * 60 * 1e3);
490
- this.transport = new RSCTransport2({
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 existsSync2 } from "fs";
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 (!existsSync2(logDir)) {
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
- appendFileSync(this.logFile, line);
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 (existsSync2(from)) renameSync(from, to);
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 readFileSync2, writeFileSync as writeFileSync2, unlinkSync, existsSync as existsSync3 } from "fs";
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 (!existsSync3(PID_FILE)) return null;
1474
+ if (!existsSync4(PID_FILE)) return null;
1190
1475
  try {
1191
- const content = readFileSync2(PID_FILE, "utf-8").trim();
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 (existsSync3(PID_FILE)) unlinkSync(PID_FILE);
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 readFileSync3, existsSync as existsSync4, statSync as statSync2, createReadStream } from "fs";
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 (!existsSync4(LOG_FILE)) {
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 = readFileSync3(LOG_FILE, "utf-8");
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 (API key, config)
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 # Enter your API key + select tools
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;