research-cli
Version:
AI-powered research assistant with web search capabilities and beautiful terminal UI
211 lines ⢠8.78 kB
JavaScript
/**
* Easymode command - Guided setup for beginners
*
* This provides a complete interactive setup flow that walks new users
* through configuring their first AI provider with a delightful UX.
*/
import { Command } from "@commander-js/extra-typings";
import { apiKeyManager, } from "../auth/keyring-manager.js";
import { InteractiveUI } from "../ui/interactive.js";
/**
* Create the easymode command
*/
export function createEasymodeCommand() {
const easymodeCommand = new Command("easymode")
.alias("easy")
.alias("setup")
.description("đŻ Guided setup for new users - get started in minutes!")
.option("--provider <provider>", "Skip provider selection and use this provider")
.option("--skip-test", "Skip API key testing")
.action(async (options) => {
await handleEasymode(options);
});
return easymodeCommand;
}
/**
* Main easymode handler - orchestrates the entire setup flow
*/
async function handleEasymode(options) {
// Setup graceful exit handling
InteractiveUI.setupGracefulExit();
try {
// Step 1: Welcome
InteractiveUI.showWelcomeBanner();
await sleep(1000); // Brief pause for dramatic effect
// Step 2: Check if already set up
const existingProviders = await apiKeyManager.listStoredProviders();
if (existingProviders.length > 0) {
const shouldContinue = await InteractiveUI.confirmAction(`You already have ${existingProviders.length} provider(s) configured: ${existingProviders.join(", ")}.\nSet up another provider?`, false);
if (!shouldContinue) {
InteractiveUI.showExistingSetupInfo(existingProviders);
return;
}
}
InteractiveUI.showProgress(1, 4, "Selecting AI provider");
// Step 3: Provider Selection
let selectedProvider;
if (options.provider) {
const validProviders = [
"openai",
"claude",
"gemini",
"perplexity",
];
if (!validProviders.includes(options.provider)) {
InteractiveUI.showErrorMessage(`Invalid provider: ${options.provider}. Valid options: ${validProviders.join(", ")}`);
return;
}
selectedProvider = options.provider;
console.log(`\nđŻ Using pre-selected provider: ${selectedProvider}\n`);
}
else {
const choice = await InteractiveUI.selectProvider();
if (!choice) {
console.log("\nđ Setup cancelled. You can run this again anytime!\n");
return;
}
selectedProvider = choice;
}
InteractiveUI.showProgress(2, 4, `Getting ${selectedProvider} API key`);
// Step 4: API Key Setup
const providerUrls = {
openai: "https://platform.openai.com/api-keys",
claude: "https://console.anthropic.com/",
gemini: "https://makersuite.google.com/app/apikey",
perplexity: "https://www.perplexity.ai/settings/api",
};
const apiKey = await InteractiveUI.getApiKey(selectedProvider, providerUrls[selectedProvider]);
if (!apiKey) {
InteractiveUI.showErrorMessage("API key is required to continue");
return;
}
// Basic format validation
if (!validateApiKeyFormat(selectedProvider, apiKey)) {
const shouldContinue = await InteractiveUI.confirmAction("â ď¸ API key format doesn't match expected pattern. Continue anyway?", false);
if (!shouldContinue) {
console.log("\nđ Setup cancelled. Double-check your API key and try again!\n");
return;
}
}
InteractiveUI.showProgress(3, 4, "Saving credentials securely");
// Step 5: Save API Key
const shouldSave = await InteractiveUI.confirmSaveKey();
let keyStored = false;
if (shouldSave) {
keyStored = await apiKeyManager.storeApiKey(selectedProvider, apiKey);
if (!keyStored) {
console.log("\nâ ď¸ Could not save to secure storage. You can still use the API key for this session.\n");
}
}
InteractiveUI.showProgress(4, 4, "Testing API connection");
// Step 6: Test API Key (unless skipped)
if (!options.skipTest) {
const testResult = await InteractiveUI.testApiKeyWithSpinner(async () => {
return await apiKeyManager.testApiKey(selectedProvider, apiKey);
});
if (!testResult.valid) {
InteractiveUI.showErrorMessage(testResult.error || "API key test failed", selectedProvider);
// If we stored the key but it's invalid, ask if they want to remove it
if (keyStored) {
const shouldRemove = await InteractiveUI.confirmAction("Remove the invalid API key from storage?", true);
if (shouldRemove) {
await apiKeyManager.removeApiKey(selectedProvider);
console.log("\nâ
Invalid key removed from storage\n");
}
}
return;
}
console.log("\nâ
API key test successful!\n");
}
// Step 7: Success!
InteractiveUI.showSuccessMessage(selectedProvider);
// Bonus: Quick demo suggestion
const shouldDemo = await InteractiveUI.confirmAction("đ Want to try a quick test query?", true);
if (shouldDemo) {
await runQuickDemo(selectedProvider, keyStored ? undefined : apiKey);
}
}
catch (error) {
InteractiveUI.showErrorMessage(error instanceof Error ? error.message : "Unknown error occurred");
process.exit(1);
}
finally {
InteractiveUI.cleanup();
}
}
/**
* Validate API key format for basic sanity checking
*/
function validateApiKeyFormat(provider, apiKey) {
const patterns = {
openai: /^sk-(proj-)?[a-zA-Z0-9]{20,}$/,
claude: /^sk-ant-[a-zA-Z0-9-_]{50,}$/,
gemini: /^[a-zA-Z0-9-_]{39}$/,
perplexity: /^pplx-[a-zA-Z0-9]{32,}$/,
};
const pattern = patterns[provider];
return pattern ? pattern.test(apiKey) : true;
}
/**
* Run a quick demo query to show the user how it works
*/
async function runQuickDemo(provider, temporaryApiKey) {
console.log("\nđ Running quick demo...\n");
try {
// Import the query command dynamically to avoid circular deps
const { runQuery } = await import("./query.js");
// Set temporary env var if we have a key that wasn't stored
const originalEnv = process.env[`${provider.toUpperCase()}_API_KEY`];
if (temporaryApiKey) {
process.env[`${provider.toUpperCase()}_API_KEY`] = temporaryApiKey;
}
await runQuery("What is artificial intelligence in one sentence?", {
provider,
model: undefined,
web: false,
webSearchContextSize: undefined,
stream: true,
format: "md",
maxTokens: 100, // Keep it short for demo
temperature: undefined,
timeout: undefined,
dryRun: false,
output: undefined,
verbose: false,
});
// Restore original env
if (temporaryApiKey) {
if (originalEnv !== undefined) {
process.env[`${provider.toUpperCase()}_API_KEY`] = originalEnv;
}
else {
delete process.env[`${provider.toUpperCase()}_API_KEY`];
}
}
console.log("\n⨠Demo complete! You're all set to start researching.\n");
}
catch (error) {
console.log("\nâ ď¸ Demo failed, but your setup is still complete!");
console.log(`Error: ${error instanceof Error ? error.message : "Unknown error"}\n`);
}
}
/**
* Utility function for dramatic pauses
*/
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Extend InteractiveUI with easymode-specific methods
InteractiveUI.showExistingSetupInfo = (providers) => {
console.log("\nâ
You're already set up!\n");
console.log("đ Configured providers:");
providers.forEach((provider) => {
console.log(` ⢠${provider}`);
});
console.log("\nđ Ready to research:");
console.log(' research-cli "your question here"');
console.log(' research-cli "question with web search" --web');
console.log(" research-cli auth list # view all providers");
console.log(" research-cli --help # see all options\n");
};
//# sourceMappingURL=easymode.js.map