limbo-ai 1.6.0 → 1.6.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 (2) hide show
  1. package/cli.js +52 -82
  2. package/package.json +4 -1
package/cli.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // cli.js — Limbo CLI
3
3
  // Orchestrates the Docker-based Limbo runtime.
4
- // Zero npm dependencies — pure Node.js stdlib.
5
4
  'use strict';
6
5
 
7
6
  const { execSync, spawnSync } = require('child_process');
@@ -14,6 +13,7 @@ const readline = require('readline');
14
13
  // ─── Config ──────────────────────────────────────────────────────────────────
15
14
 
16
15
  const LIMBO_DIR = path.join(os.homedir(), '.limbo');
16
+ const VAULT_DIR = path.join(LIMBO_DIR, 'vault');
17
17
  const ENV_FILE = path.join(LIMBO_DIR, '.env');
18
18
  const COMPOSE_FILE = path.join(LIMBO_DIR, 'docker-compose.yml');
19
19
  const GHCR_IMAGE = 'ghcr.io/tomasward1/limbo';
@@ -68,6 +68,7 @@ const COMPOSE_CONTENT = `services:
68
68
  - "127.0.0.1:${PORT}:${PORT}"
69
69
  volumes:
70
70
  - limbo-data:/data
71
+ - ./vault:/data/vault
71
72
  - limbo-openclaw-state:/home/limbo/.openclaw
72
73
  env_file:
73
74
  - .env
@@ -278,6 +279,22 @@ function sleep(ms) {
278
279
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
279
280
  }
280
281
 
282
+ let clackPromise;
283
+
284
+ async function getClack() {
285
+ if (!clackPromise) clackPromise = import('@clack/prompts');
286
+ return clackPromise;
287
+ }
288
+
289
+ async function maybeHandleClackCancel(value) {
290
+ const { cancel, isCancel } = await getClack();
291
+ if (isCancel(value)) {
292
+ cancel('Setup cancelled.');
293
+ process.exit(130);
294
+ }
295
+ return value;
296
+ }
297
+
281
298
  function hasDocker() {
282
299
  const result = spawnSync('docker', ['compose', 'version'], { stdio: 'pipe' });
283
300
  return result.status === 0;
@@ -311,25 +328,34 @@ function createPromptInterface() {
311
328
  return readline.createInterface({ input: process.stdin, output: process.stdout });
312
329
  }
313
330
 
314
- async function promptValidated(rl, question, validate, errorMessage) {
331
+ async function promptValidated(question, validate, errorMessage) {
332
+ if (process.stdin.isTTY && process.stdout.isTTY) {
333
+ const { text } = await getClack();
334
+ while (true) {
335
+ const value = await maybeHandleClackCancel(await text({
336
+ message: question.trim(),
337
+ validate: (input) => {
338
+ const validation = validate(String(input ?? ''));
339
+ return validation.ok ? undefined : (validation.message || errorMessage);
340
+ },
341
+ }));
342
+ const validation = validate(String(value));
343
+ if (validation.ok) return validation.value;
344
+ }
345
+ }
346
+
347
+ const rl = createPromptInterface();
315
348
  while (true) {
316
349
  const value = (await prompt(rl, question)).trim();
317
350
  const validation = validate(value);
318
- if (validation.ok) return validation.value;
351
+ if (validation.ok) {
352
+ rl.close();
353
+ return validation.value;
354
+ }
319
355
  warn(validation.message || errorMessage);
320
356
  }
321
357
  }
322
358
 
323
- function renderMenu(question, options, selectedIndex, lang) {
324
- const lines = [`${c.bold}${question}${c.reset}`, `${c.dim}${t(lang, 'menuHelp')}${c.reset}`, ''];
325
- options.forEach((option, index) => {
326
- const prefix = index === selectedIndex ? `${c.green}>${c.reset}` : ' ';
327
- lines.push(`${prefix} ${option.label}`);
328
- if (option.description) lines.push(` ${c.dim}${option.description}${c.reset}`);
329
- });
330
- return lines.join('\n');
331
- }
332
-
333
359
  async function selectMenu(question, options, lang) {
334
360
  if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== 'function') {
335
361
  const rl = createPromptInterface();
@@ -345,68 +371,16 @@ async function selectMenu(question, options, lang) {
345
371
  warn('Pick one of the listed options.');
346
372
  }
347
373
  }
348
-
349
- return new Promise((resolve) => {
350
- let selectedIndex = 0;
351
- let lastRenderLineCount = 0;
352
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
353
- readline.emitKeypressEvents(process.stdin, rl);
354
- const previousRawMode = process.stdin.isRaw;
355
- process.stdin.setRawMode(true);
356
-
357
- function cleanup() {
358
- process.stdin.off('keypress', onKeypress);
359
- process.stdin.setRawMode(Boolean(previousRawMode));
360
- rl.close();
361
- process.stdout.write('\n');
362
- }
363
-
364
- function draw() {
365
- const output = renderMenu(question, options, selectedIndex, lang);
366
- if (lastRenderLineCount > 0) {
367
- readline.moveCursor(process.stdout, 0, -lastRenderLineCount);
368
- }
369
- for (let i = 0; i < lastRenderLineCount; i++) {
370
- readline.clearLine(process.stdout, 0);
371
- if (i < lastRenderLineCount - 1) readline.moveCursor(process.stdout, 0, 1);
372
- }
373
- if (lastRenderLineCount > 0) {
374
- readline.moveCursor(process.stdout, 0, -Math.max(lastRenderLineCount - 1, 0));
375
- }
376
- readline.cursorTo(process.stdout, 0);
377
- process.stdout.write(output);
378
- lastRenderLineCount = output.split('\n').length;
379
- }
380
-
381
- function onKeypress(_, key = {}) {
382
- if (key.name === 'up' || key.name === 'k') {
383
- selectedIndex = selectedIndex === 0 ? options.length - 1 : selectedIndex - 1;
384
- draw();
385
- return;
386
- }
387
-
388
- if (key.name === 'down' || key.name === 'j') {
389
- selectedIndex = selectedIndex === options.length - 1 ? 0 : selectedIndex + 1;
390
- draw();
391
- return;
392
- }
393
-
394
- if (key.name === 'return') {
395
- const value = options[selectedIndex];
396
- cleanup();
397
- resolve(value);
398
- return;
399
- }
400
-
401
- if (key.ctrl && key.name === 'c') {
402
- cleanup();
403
- process.exit(130);
404
- }
405
- }
406
-
407
- process.stdin.on('keypress', onKeypress);
408
- draw();
409
- });
374
+ const { select } = await getClack();
375
+ const selectedValue = await maybeHandleClackCancel(await select({
376
+ message: question,
377
+ options: options.map((option) => ({
378
+ value: option.value,
379
+ label: option.label,
380
+ hint: option.description,
381
+ })),
382
+ }));
383
+ return options.find((option) => option.value === selectedValue) || options[0];
410
384
  }
411
385
 
412
386
  function parseEnvFile() {
@@ -516,14 +490,11 @@ async function collectConfig(existingEnv = {}) {
516
490
 
517
491
  const modelName = await chooseModel(language, providerFamily, accessMethod);
518
492
  const provider = getModelCatalog(providerFamily, accessMethod).provider;
519
-
520
- const rl = createPromptInterface();
521
493
  let apiKey = '';
522
494
 
523
495
  if (accessMethod === 'api-key') {
524
496
  if (providerFamily === 'openai') {
525
497
  apiKey = await promptValidated(
526
- rl,
527
498
  t(language, 'openAiApiKeyPrompt'),
528
499
  (value) => {
529
500
  if (!value) return { ok: false, message: t(language, 'requiredField') };
@@ -533,7 +504,6 @@ async function collectConfig(existingEnv = {}) {
533
504
  );
534
505
  } else {
535
506
  apiKey = await promptValidated(
536
- rl,
537
507
  t(language, 'anthropicApiKeyPrompt'),
538
508
  (value) => {
539
509
  if (!value) return { ok: false, message: t(language, 'requiredField') };
@@ -552,14 +522,11 @@ async function collectConfig(existingEnv = {}) {
552
522
  let telegramToken = '';
553
523
  if (telegramChoice.value === 'true') {
554
524
  telegramToken = await promptValidated(
555
- rl,
556
525
  t(language, 'telegramTokenPrompt'),
557
526
  (value) => value ? { ok: true, value } : { ok: false, message: t(language, 'requiredField') },
558
527
  );
559
528
  }
560
529
 
561
- rl.close();
562
-
563
530
  return {
564
531
  language,
565
532
  authMode: accessMethod,
@@ -575,6 +542,8 @@ async function collectConfig(existingEnv = {}) {
575
542
 
576
543
  function ensureComposeFile() {
577
544
  fs.mkdirSync(LIMBO_DIR, { recursive: true });
545
+ fs.mkdirSync(path.join(VAULT_DIR, 'notes'), { recursive: true });
546
+ fs.mkdirSync(path.join(VAULT_DIR, 'maps'), { recursive: true });
578
547
  fs.writeFileSync(COMPOSE_FILE, COMPOSE_CONTENT);
579
548
  }
580
549
 
@@ -686,6 +655,7 @@ ${c.green}${c.bold}╚═══════════════════
686
655
  ${c.bold}${t(cfg.language, 'gateway')}:${c.reset} ws://127.0.0.1:${PORT}
687
656
  ${c.bold}${t(cfg.language, 'gatewayToken')}:${c.reset} ${gatewayToken}
688
657
  ${c.bold}${t(cfg.language, 'data')}:${c.reset} ${LIMBO_DIR}
658
+ ${c.bold}Vault:${c.reset} ${VAULT_DIR}
689
659
  ${c.bold}${t(cfg.language, 'logs')}:${c.reset} limbo logs
690
660
  ${c.bold}${t(cfg.language, 'stop')}:${c.reset} limbo stop
691
661
  ${c.bold}${t(cfg.language, 'update')}:${c.reset} limbo update
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "limbo-ai",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Your personal AI memory agent — install and manage Limbo via npx",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -9,6 +9,9 @@
9
9
  "engines": {
10
10
  "node": ">=18"
11
11
  },
12
+ "dependencies": {
13
+ "@clack/prompts": "^1.1.0"
14
+ },
12
15
  "scripts": {
13
16
  "start": "node cli.js start"
14
17
  },