limbo-ai 1.11.0 → 1.12.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.
Files changed (2) hide show
  1. package/cli.js +62 -11
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -50,6 +50,12 @@ const MODEL_CATALOG = {
50
50
  menuModels: ['claude-opus-4-6', 'claude-sonnet-4-6'],
51
51
  supportedModels: ['claude-opus-4-6', 'claude-sonnet-4-6', 'claude-opus-4-1', 'claude-sonnet-4'],
52
52
  },
53
+ 'openrouter:api-key': {
54
+ provider: 'openrouter',
55
+ defaultModel: 'auto',
56
+ menuModels: [],
57
+ supportedModels: [],
58
+ },
53
59
  };
54
60
 
55
61
  const ASCII_ART = String.raw`
@@ -211,6 +217,7 @@ const TEXT = {
211
217
  providerQuestion: 'AI Provider',
212
218
  providerOpenAI: 'Codex (OpenAI)',
213
219
  providerAnthropic: 'Claude (Anthropic)',
220
+ providerOpenRouter: 'OpenRouter (100+ models)',
214
221
  accessMethodQuestion: 'Access method',
215
222
  accessSubscriptionOpenAI: 'ChatGPT / Codex subscription',
216
223
  accessSubscriptionAnthropic: 'Claude Code subscription',
@@ -225,6 +232,11 @@ const TEXT = {
225
232
  requiredField: 'This field is required.',
226
233
  invalidOpenAIKey: 'OpenAI API keys usually start with "sk-".',
227
234
  invalidAnthropicKey: 'Anthropic API keys usually start with "sk-ant-".',
235
+ openRouterApiKeyPrompt: ' OpenRouter API key (sk-or-...): ',
236
+ openRouterKeyWarn: 'OpenRouter API keys usually start with "sk-or-". Proceeding anyway.',
237
+ openRouterKeyHint: 'Get your key at: https://openrouter.ai/keys',
238
+ openRouterModelPrompt: ' Model name (blank = auto-routing): ',
239
+ openRouterModelHint: 'Examples: anthropic/claude-sonnet-4-6, openai/gpt-4o, google/gemini-2.5-pro',
228
240
  telegramQuestion: 'Want to speak to Limbo through Telegram?',
229
241
  telegramBotFatherSteps: [
230
242
  'To create a Telegram bot:',
@@ -312,6 +324,7 @@ const TEXT = {
312
324
  providerQuestion: 'AI Provider',
313
325
  providerOpenAI: 'Codex (OpenAI)',
314
326
  providerAnthropic: 'Claude (Anthropic)',
327
+ providerOpenRouter: 'OpenRouter (100+ modelos)',
315
328
  accessMethodQuestion: 'Metodo de acceso',
316
329
  accessSubscriptionOpenAI: 'Suscripcion ChatGPT / Codex',
317
330
  accessSubscriptionAnthropic: 'Suscripcion Claude Code',
@@ -326,6 +339,11 @@ const TEXT = {
326
339
  requiredField: 'Este campo es obligatorio.',
327
340
  invalidOpenAIKey: 'Las API keys de OpenAI normalmente empiezan con "sk-".',
328
341
  invalidAnthropicKey: 'Las API keys de Anthropic normalmente empiezan con "sk-ant-".',
342
+ openRouterApiKeyPrompt: ' OpenRouter API key (sk-or-...): ',
343
+ openRouterKeyWarn: 'Las API keys de OpenRouter normalmente empiezan con "sk-or-". Continuando igual.',
344
+ openRouterKeyHint: 'Consegui tu key en: https://openrouter.ai/keys',
345
+ openRouterModelPrompt: ' Nombre del modelo (vacio = auto-routing): ',
346
+ openRouterModelHint: 'Ejemplos: anthropic/claude-sonnet-4-6, openai/gpt-4o, google/gemini-2.5-pro',
329
347
  telegramQuestion: 'Quieres hablar con Limbo por Telegram?',
330
348
  telegramBotFatherSteps: [
331
349
  'Para crear un bot de Telegram:',
@@ -590,7 +608,7 @@ function writeSecrets(cfg, existingEnv = {}) {
590
608
  }
591
609
 
592
610
  const SECRET_KEYS = new Set([
593
- 'LLM_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY',
611
+ 'LLM_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'OPENROUTER_API_KEY',
594
612
  'TELEGRAM_BOT_TOKEN', 'OPENCLAW_GATEWAY_TOKEN',
595
613
  ]);
596
614
 
@@ -615,12 +633,29 @@ function waitForHealthy(lang, maxAttempts = 12) {
615
633
  return false;
616
634
  }
617
635
 
636
+ function deriveProviderFamily(provider) {
637
+ if (!provider) return 'anthropic';
638
+ if (provider.startsWith('openai')) return 'openai';
639
+ if (provider === 'openrouter') return 'openrouter';
640
+ return 'anthropic';
641
+ }
642
+
618
643
  function getModelCatalog(providerFamily, authMode) {
619
644
  return MODEL_CATALOG[`${providerFamily}:${authMode}`];
620
645
  }
621
646
 
622
647
  async function chooseModel(lang, providerFamily, authMode) {
623
648
  const catalog = getModelCatalog(providerFamily, authMode);
649
+
650
+ if (!catalog.menuModels.length) {
651
+ console.log(` ${c.dim}${t(lang, 'openRouterModelHint')}${c.reset}`);
652
+ const modelName = await promptValidated(
653
+ t(lang, 'openRouterModelPrompt'),
654
+ (value) => ({ ok: true, value: value || catalog.defaultModel }),
655
+ );
656
+ return modelName;
657
+ }
658
+
624
659
  const options = catalog.menuModels.map((model) => ({ label: model, value: model }));
625
660
  options.push({ label: t(lang, 'customModel'), value: '__custom__' });
626
661
 
@@ -654,17 +689,23 @@ async function collectConfig(existingEnv = {}) {
654
689
  const providerFamily = (await selectMenu(t(language, 'providerQuestion'), [
655
690
  { label: t(language, 'providerOpenAI'), value: 'openai' },
656
691
  { label: t(language, 'providerAnthropic'), value: 'anthropic' },
692
+ { label: t(language, 'providerOpenRouter'), value: 'openrouter' },
657
693
  ], language)).value;
658
694
 
659
- const accessMethod = (await selectMenu(t(language, 'accessMethodQuestion'), [
660
- {
661
- label: providerFamily === 'openai'
662
- ? t(language, 'accessSubscriptionOpenAI')
663
- : t(language, 'accessSubscriptionAnthropic'),
664
- value: 'subscription',
665
- },
666
- { label: t(language, 'accessApiKey'), value: 'api-key' },
667
- ], language)).value;
695
+ let accessMethod;
696
+ if (providerFamily === 'openrouter') {
697
+ accessMethod = 'api-key';
698
+ } else {
699
+ accessMethod = (await selectMenu(t(language, 'accessMethodQuestion'), [
700
+ {
701
+ label: providerFamily === 'openai'
702
+ ? t(language, 'accessSubscriptionOpenAI')
703
+ : t(language, 'accessSubscriptionAnthropic'),
704
+ value: 'subscription',
705
+ },
706
+ { label: t(language, 'accessApiKey'), value: 'api-key' },
707
+ ], language)).value;
708
+ }
668
709
 
669
710
  const modelName = await chooseModel(language, providerFamily, accessMethod);
670
711
  const provider = getModelCatalog(providerFamily, accessMethod).provider;
@@ -680,6 +721,16 @@ async function collectConfig(existingEnv = {}) {
680
721
  return { ok: true, value };
681
722
  },
682
723
  );
724
+ } else if (providerFamily === 'openrouter') {
725
+ console.log(` ${c.dim}${t(language, 'openRouterKeyHint')}${c.reset}`);
726
+ apiKey = await promptValidated(
727
+ t(language, 'openRouterApiKeyPrompt'),
728
+ (value) => {
729
+ if (!value) return { ok: false, message: t(language, 'requiredField') };
730
+ if (!value.startsWith('sk-or-')) warn(t(language, 'openRouterKeyWarn'));
731
+ return { ok: true, value };
732
+ },
733
+ );
683
734
  } else {
684
735
  apiKey = await promptValidated(
685
736
  t(language, 'anthropicApiKeyPrompt'),
@@ -1138,7 +1189,7 @@ async function cmdStart() {
1138
1189
  cfg = {
1139
1190
  language: lang,
1140
1191
  provider: existingEnv.MODEL_PROVIDER || 'anthropic',
1141
- providerFamily: (existingEnv.MODEL_PROVIDER || 'anthropic').startsWith('openai') ? 'openai' : 'anthropic',
1192
+ providerFamily: deriveProviderFamily(existingEnv.MODEL_PROVIDER),
1142
1193
  authMode: existingEnv.AUTH_MODE || 'api-key',
1143
1194
  modelName: existingEnv.MODEL_NAME || 'claude-opus-4-6',
1144
1195
  telegramEnabled: existingEnv.TELEGRAM_ENABLED || 'false',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "limbo-ai",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Your personal AI memory agent — install and manage Limbo via npx",
5
5
  "type": "commonjs",
6
6
  "bin": {