rlm-cli 0.2.5 → 0.2.7
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/interactive.js +112 -34
- package/package.json +1 -1
package/dist/interactive.js
CHANGED
|
@@ -157,13 +157,37 @@ function getDefaultModelForProvider(provider) {
|
|
|
157
157
|
const models = getModels(provider);
|
|
158
158
|
return models.length > 0 ? models[0].id : undefined;
|
|
159
159
|
}
|
|
160
|
-
/**
|
|
160
|
+
/** Wrap rl.question with ESC-to-cancel. Returns user input or null on ESC/empty. */
|
|
161
|
+
function questionWithEsc(rlInstance, promptText) {
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
let escaped = false;
|
|
164
|
+
const onKeypress = (_str, key) => {
|
|
165
|
+
if (key?.name === "escape" && !escaped) {
|
|
166
|
+
escaped = true;
|
|
167
|
+
stdin.removeListener("keypress", onKeypress);
|
|
168
|
+
// Clear the prompt line visually
|
|
169
|
+
process.stdout.write("\r\x1b[2K");
|
|
170
|
+
// Programmatically submit empty to close the pending question
|
|
171
|
+
rlInstance.write("\n");
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
stdin.on("keypress", onKeypress);
|
|
175
|
+
rlInstance.question(promptText, (answer) => {
|
|
176
|
+
stdin.removeListener("keypress", onKeypress);
|
|
177
|
+
resolve(escaped ? null : answer.trim() || null);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/** Prompt user for a provider's API key if not already set.
|
|
182
|
+
* Returns true (got key), false (empty input), or null (ESC pressed). */
|
|
161
183
|
async function promptForProviderKey(rlInstance, providerInfo) {
|
|
162
184
|
if (process.env[providerInfo.env])
|
|
163
185
|
return true;
|
|
164
|
-
const key = await
|
|
186
|
+
const key = await questionWithEsc(rlInstance, ` ${c.cyan}${providerInfo.env}:${c.reset} `);
|
|
187
|
+
if (key === null)
|
|
188
|
+
return null; // ESC
|
|
165
189
|
if (!key)
|
|
166
|
-
return false;
|
|
190
|
+
return false; // empty
|
|
167
191
|
process.env[providerInfo.env] = key;
|
|
168
192
|
// Save to shell profile
|
|
169
193
|
const shellRc = process.env.SHELL?.includes("zsh") ? "~/.zshrc" : "~/.bashrc";
|
|
@@ -440,6 +464,41 @@ function displaySubQueryResult(info) {
|
|
|
440
464
|
console.log(` ${c.magenta}└─${c.reset} ${c.dim}${elapsed}s · ${formatSize(info.resultLength)} received${c.reset}`);
|
|
441
465
|
}
|
|
442
466
|
// ── Available models list ────────────────────────────────────────────────────
|
|
467
|
+
/** Filter out deprecated, legacy, preview-only, and non-chat models. */
|
|
468
|
+
const EXCLUDED_MODEL_PATTERNS = [
|
|
469
|
+
// Anthropic legacy
|
|
470
|
+
/^claude-3-haiku/,
|
|
471
|
+
/^claude-3-sonnet/,
|
|
472
|
+
/^claude-3-opus/,
|
|
473
|
+
/^claude-3-5-sonnet-20240620/, // superseded by v2
|
|
474
|
+
// OpenAI legacy
|
|
475
|
+
/^gpt-4$/, // base gpt-4
|
|
476
|
+
/^gpt-4-turbo/, // gpt-4-turbo
|
|
477
|
+
/^gpt-4o-2024-/, // old dated snapshots
|
|
478
|
+
// Non-chat / specialized
|
|
479
|
+
/^codex-/,
|
|
480
|
+
/^gpt-5\.\d+-codex/,
|
|
481
|
+
/^gpt-5-codex/,
|
|
482
|
+
/^gpt-5\.3-codex/,
|
|
483
|
+
/deep-research$/,
|
|
484
|
+
/^gemini-live-/,
|
|
485
|
+
/^grok-beta$/,
|
|
486
|
+
/^grok-vision-beta$/,
|
|
487
|
+
/^grok-2-vision/,
|
|
488
|
+
// Old/niche Mistral
|
|
489
|
+
/^open-mistral-7b$/,
|
|
490
|
+
/^open-mixtral-/,
|
|
491
|
+
/^mistral-nemo$/,
|
|
492
|
+
// Dated previews (keep only -latest and stable)
|
|
493
|
+
/preview-\d{2}-\d{2}$/, // e.g. preview-04-17, preview-05-20
|
|
494
|
+
/preview-\d{2}-\d{4}$/, // e.g. preview-09-2025
|
|
495
|
+
/^labs-/,
|
|
496
|
+
// Specialized/internal variants
|
|
497
|
+
/-customtools$/,
|
|
498
|
+
];
|
|
499
|
+
function isModelExcluded(modelId) {
|
|
500
|
+
return EXCLUDED_MODEL_PATTERNS.some((p) => p.test(modelId));
|
|
501
|
+
}
|
|
443
502
|
/** Collect models from providers that have an API key set. */
|
|
444
503
|
function getAvailableModels() {
|
|
445
504
|
const items = [];
|
|
@@ -448,7 +507,8 @@ function getAvailableModels() {
|
|
|
448
507
|
if (!process.env[key] && provider !== detectProvider())
|
|
449
508
|
continue;
|
|
450
509
|
for (const m of getModels(provider)) {
|
|
451
|
-
|
|
510
|
+
if (!isModelExcluded(m.id))
|
|
511
|
+
items.push({ id: m.id, provider });
|
|
452
512
|
}
|
|
453
513
|
}
|
|
454
514
|
return items;
|
|
@@ -460,7 +520,8 @@ function getModelsForProvider(providerName) {
|
|
|
460
520
|
if (provider !== providerName)
|
|
461
521
|
continue;
|
|
462
522
|
for (const m of getModels(provider)) {
|
|
463
|
-
|
|
523
|
+
if (!isModelExcluded(m.id))
|
|
524
|
+
items.push({ id: m.id, provider });
|
|
464
525
|
}
|
|
465
526
|
}
|
|
466
527
|
return items;
|
|
@@ -707,35 +768,49 @@ async function interactive() {
|
|
|
707
768
|
// Validate env
|
|
708
769
|
if (!hasAnyApiKey()) {
|
|
709
770
|
printBanner();
|
|
710
|
-
console.log(` ${c.
|
|
711
|
-
console.log(` ${c.bold}Select your provider:${c.reset}\n`);
|
|
712
|
-
for (let i = 0; i < SETUP_PROVIDERS.length; i++) {
|
|
713
|
-
console.log(` ${c.dim}${i + 1}${c.reset} ${SETUP_PROVIDERS[i].name} ${c.dim}(${SETUP_PROVIDERS[i].label})${c.reset}`);
|
|
714
|
-
}
|
|
715
|
-
console.log();
|
|
771
|
+
console.log(` ${c.bold}Welcome! Let's get you set up.${c.reset}\n`);
|
|
716
772
|
const setupRl = readline.createInterface({ input: stdin, output: stdout, terminal: true });
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
773
|
+
let setupDone = false;
|
|
774
|
+
while (!setupDone) {
|
|
775
|
+
console.log(` ${c.bold}Select your provider:${c.reset}\n`);
|
|
776
|
+
for (let i = 0; i < SETUP_PROVIDERS.length; i++) {
|
|
777
|
+
console.log(` ${c.dim}${i + 1}${c.reset} ${SETUP_PROVIDERS[i].name} ${c.dim}(${SETUP_PROVIDERS[i].label})${c.reset}`);
|
|
778
|
+
}
|
|
779
|
+
console.log();
|
|
780
|
+
const choice = await questionWithEsc(setupRl, ` ${c.cyan}Provider [1-${SETUP_PROVIDERS.length}]:${c.reset} `);
|
|
781
|
+
if (choice === null) {
|
|
782
|
+
// ESC at provider selection → exit
|
|
783
|
+
console.log(`\n ${c.dim}Exiting.${c.reset}\n`);
|
|
784
|
+
setupRl.close();
|
|
785
|
+
process.exit(0);
|
|
786
|
+
}
|
|
787
|
+
const idx = parseInt(choice, 10) - 1;
|
|
788
|
+
if (isNaN(idx) || idx < 0 || idx >= SETUP_PROVIDERS.length) {
|
|
789
|
+
console.log(`\n ${c.dim}Invalid choice.${c.reset}\n`);
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
const provider = SETUP_PROVIDERS[idx];
|
|
793
|
+
const gotKey = await promptForProviderKey(setupRl, provider);
|
|
794
|
+
if (gotKey === null) {
|
|
795
|
+
// ESC at key entry → back to provider selection
|
|
796
|
+
console.log();
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
if (!gotKey) {
|
|
800
|
+
console.log(`\n ${c.dim}No key provided. Exiting.${c.reset}\n`);
|
|
801
|
+
setupRl.close();
|
|
802
|
+
process.exit(0);
|
|
803
|
+
}
|
|
804
|
+
// Auto-select default model for chosen provider
|
|
805
|
+
const defaultModel = getDefaultModelForProvider(provider.piProvider);
|
|
806
|
+
if (defaultModel) {
|
|
807
|
+
currentModelId = defaultModel;
|
|
808
|
+
console.log(` ${c.green}✓${c.reset} Default model: ${c.bold}${currentModelId}${c.reset}`);
|
|
809
|
+
}
|
|
810
|
+
console.log();
|
|
811
|
+
setupDone = true;
|
|
724
812
|
}
|
|
725
|
-
const provider = SETUP_PROVIDERS[idx];
|
|
726
|
-
const gotKey = await promptForProviderKey(setupRl, provider);
|
|
727
813
|
setupRl.close();
|
|
728
|
-
if (!gotKey) {
|
|
729
|
-
console.log(`\n ${c.dim}No key provided. Exiting.${c.reset}\n`);
|
|
730
|
-
process.exit(0);
|
|
731
|
-
}
|
|
732
|
-
// Auto-select default model for chosen provider
|
|
733
|
-
const defaultModel = getDefaultModelForProvider(provider.piProvider);
|
|
734
|
-
if (defaultModel) {
|
|
735
|
-
currentModelId = defaultModel;
|
|
736
|
-
console.log(` ${c.green}✓${c.reset} Default model: ${c.bold}${currentModelId}${c.reset}`);
|
|
737
|
-
}
|
|
738
|
-
console.log();
|
|
739
814
|
}
|
|
740
815
|
// Resolve model
|
|
741
816
|
currentModel = resolveModel(currentModelId);
|
|
@@ -901,14 +976,17 @@ async function interactive() {
|
|
|
901
976
|
const p = SETUP_PROVIDERS[i];
|
|
902
977
|
const isCurrent = p.piProvider === curProvider;
|
|
903
978
|
const dot = isCurrent ? `${c.green}●${c.reset}` : ` `;
|
|
904
|
-
|
|
979
|
+
// Only show ✓ for non-current providers that have a key
|
|
980
|
+
const hasKey = !isCurrent && process.env[p.env] ? `${c.green}✓${c.reset}` : ` `;
|
|
905
981
|
const label = isCurrent
|
|
906
982
|
? `${c.cyan}${p.name}${c.reset} ${c.dim}(${p.label})${c.reset}`
|
|
907
983
|
: `${p.name} ${c.dim}(${p.label})${c.reset}`;
|
|
908
984
|
console.log(` ${c.dim}${i + 1}${c.reset} ${dot}${hasKey} ${label}`);
|
|
909
985
|
}
|
|
910
986
|
console.log();
|
|
911
|
-
const provChoice = await
|
|
987
|
+
const provChoice = await questionWithEsc(rl, ` ${c.cyan}Provider [1-${SETUP_PROVIDERS.length}]:${c.reset} ${c.dim}(ESC to cancel)${c.reset} `);
|
|
988
|
+
if (provChoice === null)
|
|
989
|
+
break; // ESC
|
|
912
990
|
const idx = parseInt(provChoice, 10) - 1;
|
|
913
991
|
if (isNaN(idx) || idx < 0 || idx >= SETUP_PROVIDERS.length) {
|
|
914
992
|
console.log(` ${c.dim}Cancelled.${c.reset}`);
|
|
@@ -917,7 +995,7 @@ async function interactive() {
|
|
|
917
995
|
const chosen = SETUP_PROVIDERS[idx];
|
|
918
996
|
const gotKey = await promptForProviderKey(rl, chosen);
|
|
919
997
|
if (!gotKey) {
|
|
920
|
-
|
|
998
|
+
// null (ESC) or false (empty) → cancel
|
|
921
999
|
break;
|
|
922
1000
|
}
|
|
923
1001
|
// Auto-select first model from new provider
|