limbo-ai 1.5.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.
- package/cli.js +53 -71
- 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,10 +13,11 @@ 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';
|
|
20
|
-
const DEFAULT_TAG = '
|
|
20
|
+
const DEFAULT_TAG = require('./package.json').version;
|
|
21
21
|
const PORT = 18789;
|
|
22
22
|
|
|
23
23
|
// OpenClaw compatibility snapshots from official docs:
|
|
@@ -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(
|
|
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)
|
|
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,56 +371,16 @@ async function selectMenu(question, options, lang) {
|
|
|
345
371
|
warn('Pick one of the listed options.');
|
|
346
372
|
}
|
|
347
373
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
process.stdin.setRawMode(Boolean(previousRawMode));
|
|
359
|
-
rl.close();
|
|
360
|
-
process.stdout.write('\n');
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function draw() {
|
|
364
|
-
readline.cursorTo(process.stdout, 0);
|
|
365
|
-
readline.clearScreenDown(process.stdout);
|
|
366
|
-
process.stdout.write(`${renderMenu(question, options, selectedIndex, lang)}\n`);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function onKeypress(_, key = {}) {
|
|
370
|
-
if (key.name === 'up' || key.name === 'k') {
|
|
371
|
-
selectedIndex = selectedIndex === 0 ? options.length - 1 : selectedIndex - 1;
|
|
372
|
-
draw();
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (key.name === 'down' || key.name === 'j') {
|
|
377
|
-
selectedIndex = selectedIndex === options.length - 1 ? 0 : selectedIndex + 1;
|
|
378
|
-
draw();
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (key.name === 'return') {
|
|
383
|
-
const value = options[selectedIndex];
|
|
384
|
-
cleanup();
|
|
385
|
-
resolve(value);
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (key.ctrl && key.name === 'c') {
|
|
390
|
-
cleanup();
|
|
391
|
-
process.exit(130);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
process.stdin.on('keypress', onKeypress);
|
|
396
|
-
draw();
|
|
397
|
-
});
|
|
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];
|
|
398
384
|
}
|
|
399
385
|
|
|
400
386
|
function parseEnvFile() {
|
|
@@ -504,14 +490,11 @@ async function collectConfig(existingEnv = {}) {
|
|
|
504
490
|
|
|
505
491
|
const modelName = await chooseModel(language, providerFamily, accessMethod);
|
|
506
492
|
const provider = getModelCatalog(providerFamily, accessMethod).provider;
|
|
507
|
-
|
|
508
|
-
const rl = createPromptInterface();
|
|
509
493
|
let apiKey = '';
|
|
510
494
|
|
|
511
495
|
if (accessMethod === 'api-key') {
|
|
512
496
|
if (providerFamily === 'openai') {
|
|
513
497
|
apiKey = await promptValidated(
|
|
514
|
-
rl,
|
|
515
498
|
t(language, 'openAiApiKeyPrompt'),
|
|
516
499
|
(value) => {
|
|
517
500
|
if (!value) return { ok: false, message: t(language, 'requiredField') };
|
|
@@ -521,7 +504,6 @@ async function collectConfig(existingEnv = {}) {
|
|
|
521
504
|
);
|
|
522
505
|
} else {
|
|
523
506
|
apiKey = await promptValidated(
|
|
524
|
-
rl,
|
|
525
507
|
t(language, 'anthropicApiKeyPrompt'),
|
|
526
508
|
(value) => {
|
|
527
509
|
if (!value) return { ok: false, message: t(language, 'requiredField') };
|
|
@@ -540,14 +522,11 @@ async function collectConfig(existingEnv = {}) {
|
|
|
540
522
|
let telegramToken = '';
|
|
541
523
|
if (telegramChoice.value === 'true') {
|
|
542
524
|
telegramToken = await promptValidated(
|
|
543
|
-
rl,
|
|
544
525
|
t(language, 'telegramTokenPrompt'),
|
|
545
526
|
(value) => value ? { ok: true, value } : { ok: false, message: t(language, 'requiredField') },
|
|
546
527
|
);
|
|
547
528
|
}
|
|
548
529
|
|
|
549
|
-
rl.close();
|
|
550
|
-
|
|
551
530
|
return {
|
|
552
531
|
language,
|
|
553
532
|
authMode: accessMethod,
|
|
@@ -563,6 +542,8 @@ async function collectConfig(existingEnv = {}) {
|
|
|
563
542
|
|
|
564
543
|
function ensureComposeFile() {
|
|
565
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 });
|
|
566
547
|
fs.writeFileSync(COMPOSE_FILE, COMPOSE_CONTENT);
|
|
567
548
|
}
|
|
568
549
|
|
|
@@ -674,6 +655,7 @@ ${c.green}${c.bold}╚═══════════════════
|
|
|
674
655
|
${c.bold}${t(cfg.language, 'gateway')}:${c.reset} ws://127.0.0.1:${PORT}
|
|
675
656
|
${c.bold}${t(cfg.language, 'gatewayToken')}:${c.reset} ${gatewayToken}
|
|
676
657
|
${c.bold}${t(cfg.language, 'data')}:${c.reset} ${LIMBO_DIR}
|
|
658
|
+
${c.bold}Vault:${c.reset} ${VAULT_DIR}
|
|
677
659
|
${c.bold}${t(cfg.language, 'logs')}:${c.reset} limbo logs
|
|
678
660
|
${c.bold}${t(cfg.language, 'stop')}:${c.reset} limbo stop
|
|
679
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.
|
|
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
|
},
|