rlm-cli 0.2.5 → 0.2.6
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 +72 -31
- 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";
|
|
@@ -708,34 +732,48 @@ async function interactive() {
|
|
|
708
732
|
if (!hasAnyApiKey()) {
|
|
709
733
|
printBanner();
|
|
710
734
|
console.log(` ${c.red}No API key found.${c.reset}\n`);
|
|
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();
|
|
716
735
|
const setupRl = readline.createInterface({ input: stdin, output: stdout, terminal: true });
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
736
|
+
let setupDone = false;
|
|
737
|
+
while (!setupDone) {
|
|
738
|
+
console.log(` ${c.bold}Select your provider:${c.reset}\n`);
|
|
739
|
+
for (let i = 0; i < SETUP_PROVIDERS.length; i++) {
|
|
740
|
+
console.log(` ${c.dim}${i + 1}${c.reset} ${SETUP_PROVIDERS[i].name} ${c.dim}(${SETUP_PROVIDERS[i].label})${c.reset}`);
|
|
741
|
+
}
|
|
742
|
+
console.log();
|
|
743
|
+
const choice = await questionWithEsc(setupRl, ` ${c.cyan}Provider [1-${SETUP_PROVIDERS.length}]:${c.reset} `);
|
|
744
|
+
if (choice === null) {
|
|
745
|
+
// ESC at provider selection → exit
|
|
746
|
+
console.log(`\n ${c.dim}Exiting.${c.reset}\n`);
|
|
747
|
+
setupRl.close();
|
|
748
|
+
process.exit(0);
|
|
749
|
+
}
|
|
750
|
+
const idx = parseInt(choice, 10) - 1;
|
|
751
|
+
if (isNaN(idx) || idx < 0 || idx >= SETUP_PROVIDERS.length) {
|
|
752
|
+
console.log(`\n ${c.dim}Invalid choice.${c.reset}\n`);
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
const provider = SETUP_PROVIDERS[idx];
|
|
756
|
+
const gotKey = await promptForProviderKey(setupRl, provider);
|
|
757
|
+
if (gotKey === null) {
|
|
758
|
+
// ESC at key entry → back to provider selection
|
|
759
|
+
console.log();
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
if (!gotKey) {
|
|
763
|
+
console.log(`\n ${c.dim}No key provided. Exiting.${c.reset}\n`);
|
|
764
|
+
setupRl.close();
|
|
765
|
+
process.exit(0);
|
|
766
|
+
}
|
|
767
|
+
// Auto-select default model for chosen provider
|
|
768
|
+
const defaultModel = getDefaultModelForProvider(provider.piProvider);
|
|
769
|
+
if (defaultModel) {
|
|
770
|
+
currentModelId = defaultModel;
|
|
771
|
+
console.log(` ${c.green}✓${c.reset} Default model: ${c.bold}${currentModelId}${c.reset}`);
|
|
772
|
+
}
|
|
773
|
+
console.log();
|
|
774
|
+
setupDone = true;
|
|
724
775
|
}
|
|
725
|
-
const provider = SETUP_PROVIDERS[idx];
|
|
726
|
-
const gotKey = await promptForProviderKey(setupRl, provider);
|
|
727
776
|
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
777
|
}
|
|
740
778
|
// Resolve model
|
|
741
779
|
currentModel = resolveModel(currentModelId);
|
|
@@ -901,14 +939,17 @@ async function interactive() {
|
|
|
901
939
|
const p = SETUP_PROVIDERS[i];
|
|
902
940
|
const isCurrent = p.piProvider === curProvider;
|
|
903
941
|
const dot = isCurrent ? `${c.green}●${c.reset}` : ` `;
|
|
904
|
-
|
|
942
|
+
// Only show ✓ for non-current providers that have a key
|
|
943
|
+
const hasKey = !isCurrent && process.env[p.env] ? `${c.green}✓${c.reset}` : ` `;
|
|
905
944
|
const label = isCurrent
|
|
906
945
|
? `${c.cyan}${p.name}${c.reset} ${c.dim}(${p.label})${c.reset}`
|
|
907
946
|
: `${p.name} ${c.dim}(${p.label})${c.reset}`;
|
|
908
947
|
console.log(` ${c.dim}${i + 1}${c.reset} ${dot}${hasKey} ${label}`);
|
|
909
948
|
}
|
|
910
949
|
console.log();
|
|
911
|
-
const provChoice = await
|
|
950
|
+
const provChoice = await questionWithEsc(rl, ` ${c.cyan}Provider [1-${SETUP_PROVIDERS.length}]:${c.reset} ${c.dim}(ESC to cancel)${c.reset} `);
|
|
951
|
+
if (provChoice === null)
|
|
952
|
+
break; // ESC
|
|
912
953
|
const idx = parseInt(provChoice, 10) - 1;
|
|
913
954
|
if (isNaN(idx) || idx < 0 || idx >= SETUP_PROVIDERS.length) {
|
|
914
955
|
console.log(` ${c.dim}Cancelled.${c.reset}`);
|
|
@@ -917,7 +958,7 @@ async function interactive() {
|
|
|
917
958
|
const chosen = SETUP_PROVIDERS[idx];
|
|
918
959
|
const gotKey = await promptForProviderKey(rl, chosen);
|
|
919
960
|
if (!gotKey) {
|
|
920
|
-
|
|
961
|
+
// null (ESC) or false (empty) → cancel
|
|
921
962
|
break;
|
|
922
963
|
}
|
|
923
964
|
// Auto-select first model from new provider
|