limbo-ai 1.8.0 → 1.9.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 +92 -24
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Orchestrates the Docker-based Limbo runtime.
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
|
-
const { execSync, spawnSync } = require('child_process');
|
|
6
|
+
const { execSync, spawn, spawnSync } = require('child_process');
|
|
7
7
|
const crypto = require('crypto');
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const os = require('os');
|
|
@@ -212,7 +212,7 @@ const TEXT = {
|
|
|
212
212
|
modelQuestion: 'Model',
|
|
213
213
|
customModel: 'Add another supported model name',
|
|
214
214
|
customModelPrompt: ' Model name: ',
|
|
215
|
-
invalidModel: 'That model is not
|
|
215
|
+
invalidModel: 'That model is not supported for this provider and access method.',
|
|
216
216
|
supportedModels: 'Supported models:',
|
|
217
217
|
openAiApiKeyPrompt: ' OpenAI API key (sk-...): ',
|
|
218
218
|
anthropicApiKeyPrompt: ' Anthropic API key (sk-ant-...): ',
|
|
@@ -252,15 +252,16 @@ const TEXT = {
|
|
|
252
252
|
logsHint: 'Check logs with: limbo logs',
|
|
253
253
|
healthy: 'Container is healthy.',
|
|
254
254
|
subscriptionSetup: 'Provider authentication',
|
|
255
|
-
openaiSubscriptionIntro: 'Limbo will
|
|
255
|
+
openaiSubscriptionIntro: 'Limbo will authenticate with your AI provider. A URL will appear — open it in your browser to complete login.',
|
|
256
256
|
anthropicSubscriptionIntro: 'Generate a Claude setup-token on any machine with `claude setup-token`, then paste it into the next step.',
|
|
257
|
-
authFlowStart: 'Starting
|
|
258
|
-
authFlowDone: '
|
|
259
|
-
authFlowFailed: '
|
|
260
|
-
authStatusFailed: '
|
|
261
|
-
configFlowStart: 'Applying
|
|
262
|
-
configFlowDone: '
|
|
263
|
-
configFlowFailed: 'Could not
|
|
257
|
+
authFlowStart: 'Starting authentication...',
|
|
258
|
+
authFlowDone: 'Authentication complete.',
|
|
259
|
+
authFlowFailed: 'Authentication did not complete successfully.',
|
|
260
|
+
authStatusFailed: 'Provider auth is still missing or invalid. Try running with --reconfigure.',
|
|
261
|
+
configFlowStart: 'Applying configuration...',
|
|
262
|
+
configFlowDone: 'Configuration applied.',
|
|
263
|
+
configFlowFailed: 'Could not apply configuration. Check your settings and try again.',
|
|
264
|
+
composing: 'Initializing...',
|
|
264
265
|
success: 'Limbo is running!',
|
|
265
266
|
gateway: 'Gateway',
|
|
266
267
|
gatewayToken: 'Gateway token',
|
|
@@ -303,7 +304,7 @@ const TEXT = {
|
|
|
303
304
|
modelQuestion: 'Modelo',
|
|
304
305
|
customModel: 'Agregar otro nombre de modelo soportado',
|
|
305
306
|
customModelPrompt: ' Nombre del modelo: ',
|
|
306
|
-
invalidModel: 'Ese modelo no esta
|
|
307
|
+
invalidModel: 'Ese modelo no esta soportado para este provider y metodo de acceso.',
|
|
307
308
|
supportedModels: 'Modelos soportados:',
|
|
308
309
|
openAiApiKeyPrompt: ' OpenAI API key (sk-...): ',
|
|
309
310
|
anthropicApiKeyPrompt: ' Anthropic API key (sk-ant-...): ',
|
|
@@ -343,15 +344,16 @@ const TEXT = {
|
|
|
343
344
|
logsHint: 'Mira los logs con: limbo logs',
|
|
344
345
|
healthy: 'El container esta healthy.',
|
|
345
346
|
subscriptionSetup: 'Autenticacion del provider',
|
|
346
|
-
openaiSubscriptionIntro: 'Limbo va a
|
|
347
|
+
openaiSubscriptionIntro: 'Limbo va a autenticarse con tu proveedor de IA. Aparecera una URL — abrisla en el navegador para completar el login.',
|
|
347
348
|
anthropicSubscriptionIntro: 'Genera un Claude setup-token en cualquier maquina con `claude setup-token` y pegalo en el siguiente paso.',
|
|
348
|
-
authFlowStart: 'Iniciando autenticacion
|
|
349
|
-
authFlowDone: 'Autenticacion
|
|
350
|
-
authFlowFailed: 'La autenticacion
|
|
351
|
-
authStatusFailed: '
|
|
352
|
-
configFlowStart: 'Aplicando configuracion
|
|
353
|
-
configFlowDone: 'Configuracion
|
|
354
|
-
configFlowFailed: 'No se pudo
|
|
349
|
+
authFlowStart: 'Iniciando autenticacion...',
|
|
350
|
+
authFlowDone: 'Autenticacion completada.',
|
|
351
|
+
authFlowFailed: 'La autenticacion no termino correctamente.',
|
|
352
|
+
authStatusFailed: 'La autenticacion del provider sigue siendo invalida o no esta configurada. Proba con --reconfigure.',
|
|
353
|
+
configFlowStart: 'Aplicando configuracion...',
|
|
354
|
+
configFlowDone: 'Configuracion aplicada.',
|
|
355
|
+
configFlowFailed: 'No se pudo aplicar la configuracion. Revisa los ajustes e intenta de nuevo.',
|
|
356
|
+
composing: 'Inicializando...',
|
|
355
357
|
success: 'Limbo esta corriendo!',
|
|
356
358
|
gateway: 'Gateway',
|
|
357
359
|
gatewayToken: 'Token del gateway',
|
|
@@ -807,7 +809,58 @@ function applyOpenClawConfig(cfg) {
|
|
|
807
809
|
ok(t(cfg.language, 'configFlowDone'));
|
|
808
810
|
}
|
|
809
811
|
|
|
810
|
-
|
|
812
|
+
// Strip ANSI escape sequences so URL/text matching works on TTY output.
|
|
813
|
+
const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').replace(/\r/g, '');
|
|
814
|
+
|
|
815
|
+
// Spawn OpenClaw auth with filtered output: extract OAuth URLs, suppress branding.
|
|
816
|
+
// --tty is required so openclaw sees a TTY inside the container and runs the auth wizard.
|
|
817
|
+
// We pipe stdout/stderr to filter content while the container gets a proper PTY allocation.
|
|
818
|
+
function streamFilteredAuth(dockerArgs) {
|
|
819
|
+
return new Promise((resolve) => {
|
|
820
|
+
const proc = spawn('docker', dockerArgs, {
|
|
821
|
+
cwd: LIMBO_DIR,
|
|
822
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
const urlRe = /https?:\/\/[^\s"'<>\]]+/g;
|
|
826
|
+
const seenUrls = new Set();
|
|
827
|
+
let buf = '';
|
|
828
|
+
|
|
829
|
+
const handleData = (data) => {
|
|
830
|
+
buf += data.toString();
|
|
831
|
+
const lines = buf.split('\n');
|
|
832
|
+
buf = lines.pop(); // hold incomplete last line
|
|
833
|
+
for (const line of lines) emitLine(line);
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
const emitLine = (rawLine) => {
|
|
837
|
+
const line = stripAnsi(rawLine);
|
|
838
|
+
const urls = line.match(urlRe) || [];
|
|
839
|
+
if (urls.length > 0) {
|
|
840
|
+
for (const url of urls) {
|
|
841
|
+
if (!seenUrls.has(url)) {
|
|
842
|
+
seenUrls.add(url);
|
|
843
|
+
console.log(`\n ${c.cyan}${c.bold}→ ${url}${c.reset}\n`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
// Suppress lines that only contain internal gateway/runtime branding
|
|
849
|
+
if (/openclaw/i.test(line)) return;
|
|
850
|
+
if (line.trim()) console.log(` ${line}`);
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
proc.stdout.on('data', handleData);
|
|
854
|
+
proc.stderr.on('data', handleData);
|
|
855
|
+
proc.on('close', (code) => {
|
|
856
|
+
if (buf.trim()) emitLine(buf);
|
|
857
|
+
resolve(code ?? 1);
|
|
858
|
+
});
|
|
859
|
+
proc.on('error', () => resolve(1));
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
async function runSubscriptionAuthFlow(cfg) {
|
|
811
864
|
header(t(cfg.language, 'subscriptionSetup'));
|
|
812
865
|
if (cfg.providerFamily === 'openai') {
|
|
813
866
|
log(t(cfg.language, 'openaiSubscriptionIntro'));
|
|
@@ -820,8 +873,18 @@ function runSubscriptionAuthFlow(cfg) {
|
|
|
820
873
|
? ['models', 'auth', 'login', '--provider', 'openai-codex']
|
|
821
874
|
: ['models', 'auth', 'paste-token', '--provider', 'anthropic'];
|
|
822
875
|
|
|
823
|
-
|
|
824
|
-
if (
|
|
876
|
+
let exitCode;
|
|
877
|
+
if (cfg.providerFamily === 'openai') {
|
|
878
|
+
// --tty allocates a PTY inside the container so openclaw's auth wizard runs correctly.
|
|
879
|
+
// We still pipe stdout/stderr to filter out branding and highlight the OAuth URL.
|
|
880
|
+
exitCode = await streamFilteredAuth(['compose', 'run', '--tty', '--rm', '--entrypoint', 'openclaw', 'limbo', ...authArgs]);
|
|
881
|
+
} else {
|
|
882
|
+
// Anthropic paste-token is interactive (user pastes a token); keep stdio inherited
|
|
883
|
+
const authResult = runOpenClaw(authArgs);
|
|
884
|
+
exitCode = authResult.status;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
if (exitCode !== 0) die(t(cfg.language, 'authFlowFailed'));
|
|
825
888
|
|
|
826
889
|
const statusResult = runOpenClaw(
|
|
827
890
|
['models', 'status', '--check', '--probe-provider', cfg.provider],
|
|
@@ -912,7 +975,7 @@ async function cmdStart() {
|
|
|
912
975
|
pullOrBuildImage(cfg.language);
|
|
913
976
|
|
|
914
977
|
if (cfg.authMode === 'subscription' && (process.argv.includes('--reconfigure') || !alreadyHasEnv)) {
|
|
915
|
-
runSubscriptionAuthFlow(cfg);
|
|
978
|
+
await runSubscriptionAuthFlow(cfg);
|
|
916
979
|
}
|
|
917
980
|
|
|
918
981
|
applyOpenClawConfig({
|
|
@@ -922,7 +985,12 @@ async function cmdStart() {
|
|
|
922
985
|
});
|
|
923
986
|
|
|
924
987
|
header(t(cfg.language, 'starting'));
|
|
925
|
-
|
|
988
|
+
log(t(cfg.language, 'composing'));
|
|
989
|
+
const upResult = runDockerCompose(['up', '-d', '--remove-orphans'], { stdio: 'pipe' });
|
|
990
|
+
if (upResult.status !== 0) {
|
|
991
|
+
process.stderr.write(upResult.stderr || '');
|
|
992
|
+
die('Container failed to start. Run `limbo logs` to investigate.');
|
|
993
|
+
}
|
|
926
994
|
|
|
927
995
|
header(t(cfg.language, 'verifying'));
|
|
928
996
|
const healthy = waitForHealthy(cfg.language);
|