UNPKG

@podx/cli

Version:

๐Ÿ’ป Command-line interface for PODx - Advanced Twitter/X scraping and crypto analysis toolkit

1,009 lines (829 loc) โ€ข 29.3 kB
import chalk from 'chalk'; import { showMainMenu, setupScrapingConfig, confirmClean, setupCredentials, setupDataCollectionOptions, setupAdvancedOptions } from '../ui/prompts'; import { logger } from '@podx/core'; export async function runInteractiveMode(): Promise<void> { logger.info("Starting interactive mode", { operation: "interactive_mode" }); console.log(chalk.cyan.bold('\n๐Ÿš€ Welcome to PODx Interactive Mode!\n')); console.log(chalk.gray('Use arrow keys to navigate โ€ข Press Enter to select โ€ข Press Ctrl+C to exit\n')); let shouldContinue = true; while (shouldContinue) { const choice = await showMainMenu(); console.log(''); // Add spacing switch (choice) { case 'scraping': await showScrapingMenu(); break; case 'search': await showSearchMenu(); break; case 'analysis': await showAnalysisMenu(); break; case 'database': await showDatabaseMenu(); break; case 'system': await showSystemMenu(); break; case 'config': await runGuidedConfiguration(); break; case 'exit': shouldContinue = false; break; } if (shouldContinue) { console.log(chalk.gray('\n' + '='.repeat(60) + '\n')); } } console.log(chalk.red.bold('\n๐Ÿ’€ Thank you for using POD-SCRAPE! ๐Ÿ’€')); console.log(chalk.magenta('๐Ÿ”ฅ Prompt or Die - Scrape or Die Trying ๐Ÿ”ฅ')); console.log(chalk.gray('๐Ÿ’€ Your data awaits... ๐Ÿ’€\n')); } export async function runGuidedScrape(): Promise<void> { console.log(chalk.red.bold('๐ŸŽฏ SCRAPE MISSION CONTROL ๐ŸŽฏ\n')); // Step 1: Get target username const { input } = await import('@inquirer/prompts'); const targetUsername = await input({ message: 'Enter target X account to scrape:', default: 'pmarca', // You might want to get this from config validate: (value) => value.trim() ? true : 'Username is required' }); // Step 2: Select data collection options const dataOptions = await setupDataCollectionOptions(targetUsername); // Step 3: Advanced settings const { confirm } = await import('@inquirer/prompts'); const wantAdvanced = await confirm({ message: 'Configure advanced options?', default: false }); let advancedOptions: { maxTweets: number; maxRepliesPerTweet?: number; tokenFilter?: string; includeRetweets: boolean } = { maxTweets: 1000, // Default value maxRepliesPerTweet: 20, includeRetweets: false }; if (wantAdvanced) { advancedOptions = await setupAdvancedOptions(); } // Step 4: Summary and confirmation console.log(chalk.yellow.bold('\n๐Ÿ“‹ SCRAPING PLAN SUMMARY:')); console.log(chalk.cyan(`๐ŸŽฏ Target: @${targetUsername}`)); console.log(chalk.cyan(`๐Ÿ“Š Max tweets: ${advancedOptions.maxTweets}`)); console.log(chalk.yellow('\n๐Ÿ“ฆ Data to collect:')); if (dataOptions.tweets) console.log(chalk.green(' โœ… Tweets')); if (dataOptions.replies) console.log(chalk.green(' โœ… Replies')); if (dataOptions.cryptoAnalysis) console.log(chalk.green(' โœ… Crypto Analysis')); if (dataOptions.accountAnalysis) console.log(chalk.green(' โœ… Account Analysis')); if (dataOptions.locationData) console.log(chalk.green(' โœ… Location Data')); if (dataOptions.convexSave) console.log(chalk.green(' โœ… Save to Convex')); console.log(''); const proceed = await confirm({ message: chalk.yellow('๐Ÿš€ Ready to launch scraping operation?'), default: true }); if (proceed) { // Import the actual scraping function here to avoid circular dependencies const { runSelectiveDataCollection } = await import('./data'); await runSelectiveDataCollection(targetUsername, dataOptions, advancedOptions); } else { console.log(chalk.gray('โŒ Scraping operation cancelled')); } await waitForContinue(); } export async function runGuidedConfiguration(): Promise<void> { console.log(chalk.blue.bold('โš™๏ธ CONFIGURATION SETUP โš™๏ธ\n')); const credentials = await setupCredentials(); const scrapingConfig = await setupScrapingConfig(); console.log(chalk.yellow.bold('\n๐Ÿ“‹ CONFIGURATION SUMMARY:')); console.log(chalk.cyan(`๐Ÿ‘ค Username: ${credentials.username}`)); console.log(chalk.cyan(`๐Ÿ“ง Email: ${credentials.email}`)); console.log(chalk.cyan(`๐ŸŽฏ Target: @${scrapingConfig.targetUsername}`)); console.log(chalk.cyan(`๐Ÿ“Š Max tweets: ${scrapingConfig.maxTweets}`)); const { confirm } = await import('@inquirer/prompts'); const saveConfig = await confirm({ message: chalk.yellow('Save this configuration?'), default: true }); if (saveConfig) { // Import the configuration handler here const { saveConfiguration } = await import('./config'); await saveConfiguration(credentials, scrapingConfig); console.log(chalk.green('โœ… Configuration saved successfully!')); } else { console.log(chalk.gray('โŒ Configuration not saved')); } await waitForContinue(); } export async function runGuidedClean(): Promise<void> { console.log(chalk.red.bold('๐Ÿงน CLEANUP OPERATIONS ๐Ÿงน\n')); const cleanAll = await confirmClean(); console.log(chalk.yellow.bold('\n๐Ÿ“‹ CLEANUP PLAN:')); if (cleanAll) { console.log(chalk.red(' ๐Ÿ—‘๏ธ Remove all data including scraped tweets')); } else { console.log(chalk.yellow(' ๐Ÿงฝ Clean build artifacts only')); } const { confirm } = await import('@inquirer/prompts'); const proceed = await confirm({ message: chalk.yellow('Proceed with cleanup?'), default: !cleanAll // Default to false for full clean }); if (proceed) { // Import the clean handler here const { runClean } = await import('./api'); await runClean(cleanAll); console.log(chalk.green('โœ… Cleanup completed successfully!')); } else { console.log(chalk.gray('โŒ Cleanup cancelled')); } await waitForContinue(); } export async function waitForContinue(): Promise<void> { console.log(''); const { confirm } = await import('@inquirer/prompts'); await confirm({ message: 'Press Enter to continue...', default: true }); } export async function runInteractiveStatus(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ“Š SYSTEM STATUS ๐Ÿ“Š\n')); // Import the status handler here const { runStatus } = await import('./api'); await runStatus(); } // SCRAPING MENU export async function showScrapingMenu(): Promise<void> { const { select } = await import('@inquirer/prompts'); console.log(chalk.red.bold('๐ŸŽฏ SCRAPING MISSIONS ๐ŸŽฏ\n')); const choice = await select({ message: 'Choose your scraping operation:', choices: [ { name: '๐Ÿฆ Scrape Single Account - Scrape tweets from one X account', value: 'scrape' }, { name: '๐Ÿ“ฆ Batch Scrape - Scrape multiple accounts concurrently', value: 'batch' }, { name: '๐Ÿ“ Scrape Twitter List - Scrape tweets from a Twitter list', value: 'list' }, { name: '๐Ÿ’ฌ Scrape Replies - Scrape replies to specific tweets', value: 'replies' }, { name: 'โฌ…๏ธ Back to Main Menu', value: 'back' } ] }); if (choice === 'scrape') { await runGuidedScrape(); } else if (choice === 'batch') { await runGuidedBatchScrape(); } else if (choice === 'list') { await runGuidedListScrape(); } else if (choice === 'replies') { await runGuidedRepliesScrape(); } // If 'back', just return to main menu } // SEARCH MENU export async function showSearchMenu(): Promise<void> { const { select } = await import('@inquirer/prompts'); console.log(chalk.magenta.bold('๐Ÿ” ADVANCED SEARCH ๐Ÿ”\n')); const choice = await select({ message: 'Choose your search operation:', choices: [ { name: '๐Ÿ”Ž General Search - Search Twitter with advanced filters', value: 'search' }, { name: '๐Ÿ’Ž Crypto Search - Search for crypto-related content', value: 'crypto' }, { name: 'โฌ…๏ธ Back to Main Menu', value: 'back' } ] }); if (choice === 'search') { await runGuidedSearch(); } else if (choice === 'crypto') { await runGuidedCryptoSearch(); } // If 'back', just return to main menu } // ANALYSIS MENU export async function showAnalysisMenu(): Promise<void> { const { select } = await import('@inquirer/prompts'); console.log(chalk.yellow.bold('๐Ÿ“Š ANALYSIS TOOLS ๐Ÿ“Š\n')); const choice = await select({ message: 'Choose your analysis operation:', choices: [ { name: '๐Ÿ’Ž Crypto Analysis - Analyze tweets for crypto signals', value: 'crypto' }, { name: '๐Ÿ‘ค Account Analysis - Analyze account reputation and bots', value: 'accounts' }, { name: '๐Ÿ’ฌ Replies Analysis - Analyze replies for crypto signals', value: 'replies' }, { name: '๐Ÿ”— Solana Contracts - Analyze tweets for Solana addresses', value: 'solana' }, { name: 'โฌ…๏ธ Back to Main Menu', value: 'back' } ] }); if (choice === 'crypto') { await runGuidedCryptoAnalysis(); } else if (choice === 'accounts') { await runGuidedAccountAnalysis(); } else if (choice === 'replies') { await runGuidedRepliesAnalysis(); } else if (choice === 'solana') { await runGuidedSolanaAnalysis(); } // If 'back', just return to main menu } // DATABASE MENU export async function showDatabaseMenu(): Promise<void> { const { select } = await import('@inquirer/prompts'); console.log(chalk.cyan.bold('๐Ÿ—ƒ๏ธ DATABASE OPERATIONS ๐Ÿ—ƒ๏ธ\n')); const choice = await select({ message: 'Choose your database operation:', choices: [ { name: '๐Ÿ’พ Save to Convex - Save analysis data to Convex database', value: 'save' }, { name: '๐Ÿ“Š View Analysis - View latest analysis from database', value: 'view' }, { name: '๐Ÿ” Top Signals - View top crypto signals', value: 'signals' }, { name: '๐Ÿ“ˆ Trending Tokens - View trending tokens', value: 'trending' }, { name: 'โฌ…๏ธ Back to Main Menu', value: 'back' } ] }); if (choice === 'save') { await runGuidedConvexSave(); } else if (choice === 'view') { await runGuidedViewAnalysis(); } else if (choice === 'signals') { await runGuidedTopSignals(); } else if (choice === 'trending') { await runGuidedTrendingTokens(); } // If 'back', just return to main menu } // SYSTEM MENU export async function showSystemMenu(): Promise<void> { const { select } = await import('@inquirer/prompts'); console.log(chalk.green.bold('๐Ÿš€ SYSTEM CONTROL ๐Ÿš€\n')); const choice = await select({ message: 'Choose your system operation:', choices: [ { name: '๐ŸŒ Start API Server - Start the PODx API server', value: 'serve' }, { name: '๐Ÿ’พ Start Database - Start Convex database service', value: 'db' }, { name: '๐ŸŽฏ Start All Services - Start API + Database + CLI', value: 'all' }, { name: '๐Ÿ“Š System Status - Check system status', value: 'status' }, { name: '๐Ÿงน Clean System - Clean build and data directories', value: 'clean' }, { name: '๐Ÿ”‘ Generate Token - Generate JWT tokens for API access', value: 'token' }, { name: 'โฌ…๏ธ Back to Main Menu', value: 'back' } ] }); if (choice === 'serve') { await runGuidedServe(); } else if (choice === 'db') { await runGuidedDatabase(); } else if (choice === 'all') { await runGuidedAllServices(); } else if (choice === 'status') { await runInteractiveStatus(); await waitForContinue(); } else if (choice === 'clean') { await runGuidedClean(); } else if (choice === 'token') { await runGuidedTokenGeneration(); } // If 'back', just return to main menu } // Implementation functions for menu options export async function runGuidedBatchScrape(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ“ฆ BATCH SCRAPE OPERATIONS\n')); const { input, number, confirm } = await import('@inquirer/prompts'); // Get list of usernames const usernamesInput = await input({ message: 'Enter usernames (comma-separated):', validate: (value) => value.trim() ? true : 'At least one username is required' }); const usernames = usernamesInput.split(',').map(u => u.trim()).filter(u => u); const maxTweets = (await number({ message: 'Maximum tweets per account:', default: 100 })) as number; const concurrent = (await number({ message: 'Concurrent accounts to scrape:', default: 3, min: 1, max: 10 })) as number; console.log(chalk.yellow('\n๐Ÿ“‹ Batch Configuration:')); console.log(chalk.cyan(`๐Ÿ‘ฅ Accounts: ${usernames.join(', ')}`)); console.log(chalk.cyan(`๐Ÿ“Š Max tweets: ${maxTweets}`)); console.log(chalk.cyan(`โšก Concurrent: ${concurrent}`)); const proceed = await confirm({ message: 'Start batch scraping?', default: true }); if (proceed) { // Import and run batch scraping const { runBatchScraping } = await import('./data'); await runBatchScraping({ usernames, maxTweets, concurrent, delaySeconds: 2 }); } await waitForContinue(); } export async function runGuidedListScrape(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ“ TWITTER LIST SCRAPING\n')); const { input, number, confirm } = await import('@inquirer/prompts'); const listId = await input({ message: 'Enter Twitter list ID:', validate: (value) => value.trim() ? true : 'List ID is required' }); const maxTweets = await number({ message: 'Maximum tweets to scrape:', default: 100 }); console.log(chalk.yellow('\n๐Ÿ“‹ List Scrape Configuration:')); console.log(chalk.cyan(`๐Ÿ“ List ID: ${listId}`)); console.log(chalk.cyan(`๐Ÿ“Š Max tweets: ${maxTweets}`)); const proceed = await confirm({ message: 'Start list scraping?', default: true }); if (proceed) { // Import and run list scraping const { runListScrape } = await import('./api'); await runListScrape({ id: listId, count: maxTweets }); } await waitForContinue(); } export async function runGuidedRepliesScrape(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ’ฌ REPLIES SCRAPING\n')); const { input, number, confirm } = await import('@inquirer/prompts'); const tweetId = await input({ message: 'Enter tweet ID to scrape replies from:', validate: (value) => value.trim() ? true : 'Tweet ID is required' }); const maxReplies = (await number({ message: 'Maximum replies to scrape:', default: 50 })) as number; console.log(chalk.yellow('\n๐Ÿ“‹ Replies Scrape Configuration:')); console.log(chalk.cyan(`๐Ÿฆ Tweet ID: ${tweetId}`)); console.log(chalk.cyan(`๐Ÿ’ฌ Max replies: ${maxReplies}`)); const proceed = await confirm({ message: 'Start replies scraping?', default: true }); if (proceed) { // Import and run replies scraping const { runRepliesScrape } = await import('./api'); await runRepliesScrape(tweetId, maxReplies); } await waitForContinue(); } export async function runGuidedSearch(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ” ADVANCED TWITTER SEARCH\n')); const { input, select, confirm } = await import('@inquirer/prompts'); const query = await input({ message: 'Enter search query:', validate: (value) => value.trim() ? true : 'Search query is required' }); const mode = await select({ message: 'Search mode:', choices: [ { name: 'Latest tweets', value: 'Latest' }, { name: 'Top tweets', value: 'Top' }, { name: 'People', value: 'People' }, { name: 'Photos', value: 'Photos' }, { name: 'Videos', value: 'Videos' } ] }); console.log(chalk.yellow('\n๐Ÿ“‹ Search Configuration:')); console.log(chalk.cyan(`๐Ÿ” Query: ${query}`)); console.log(chalk.cyan(`๐Ÿ“Š Mode: ${mode}`)); const proceed = await confirm({ message: 'Start search?', default: true }); if (proceed) { // Import and run search const { runTwitterSearch } = await import('./api'); await runTwitterSearch({ query, mode }); } await waitForContinue(); } export async function runGuidedCryptoSearch(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ’Ž CRYPTO TWITTER SEARCH\n')); const { input, checkbox, number, confirm } = await import('@inquirer/prompts'); const tokens = await input({ message: 'Enter token symbols (comma-separated, e.g., BTC,ETH,SOL):', default: 'BTC,ETH' }); const keywords = await input({ message: 'Enter crypto keywords (comma-separated):', default: 'crypto,blockchain,defi' }); const maxTweets = await number({ message: 'Maximum tweets to fetch:', default: 200 }); console.log(chalk.yellow('\n๐Ÿ“‹ Crypto Search Configuration:')); console.log(chalk.cyan(`๐Ÿ’Ž Tokens: ${tokens}`)); console.log(chalk.cyan(`๐Ÿ”‘ Keywords: ${keywords}`)); console.log(chalk.cyan(`๐Ÿ“Š Max tweets: ${maxTweets}`)); const proceed = await confirm({ message: 'Start crypto search?', default: true }); if (proceed) { // Import and run crypto search const { runCryptoTwitterSearch } = await import('./api'); await runCryptoTwitterSearch({ tokens: tokens.split(',').map(t => t.trim()), keywords: keywords.split(',').map(k => k.trim()), maxTweets }); } await waitForContinue(); } export async function runGuidedCryptoAnalysis(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ’Ž CRYPTO ANALYSIS\n')); const { input, confirm } = await import('@inquirer/prompts'); const inputFile = await input({ message: 'Enter input file path:', default: 'scraped_tweets.json' }); const outputFile = await input({ message: 'Enter output file path:', default: 'crypto_analysis.json' }); console.log(chalk.yellow('\n๐Ÿ“‹ Analysis Configuration:')); console.log(chalk.cyan(`๐Ÿ“ฅ Input: ${inputFile}`)); console.log(chalk.cyan(`๐Ÿ“ค Output: ${outputFile}`)); const proceed = await confirm({ message: 'Start crypto analysis?', default: true }); if (proceed) { // Import and run analysis const { runCryptoAnalysis } = await import('./data'); await runCryptoAnalysis(inputFile, outputFile); } await waitForContinue(); } export async function runGuidedAccountAnalysis(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ‘ค ACCOUNT ANALYSIS\n')); const { input, confirm } = await import('@inquirer/prompts'); const inputFile = await input({ message: 'Enter input file path:', default: 'scraped_tweets.json' }); const outputFile = await input({ message: 'Enter output file path:', default: 'account_analysis.json' }); console.log(chalk.yellow('\n๐Ÿ“‹ Account Analysis Configuration:')); console.log(chalk.cyan(`๐Ÿ“ฅ Input: ${inputFile}`)); console.log(chalk.cyan(`๐Ÿ“ค Output: ${outputFile}`)); const proceed = await confirm({ message: 'Start account analysis?', default: true }); if (proceed) { // Import and run account analysis const { runAccountAnalysis } = await import('./data'); await runAccountAnalysis(inputFile, outputFile); } await waitForContinue(); } export async function runGuidedRepliesAnalysis(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ’ฌ REPLIES ANALYSIS\n')); const { input, number, confirm } = await import('@inquirer/prompts'); const inputFile = await input({ message: 'Enter tweets file path:', default: 'scraped_tweets.json' }); const token = await input({ message: 'Enter token to analyze replies for:', default: 'BTC' }); const maxReplies = (await number({ message: 'Maximum replies per tweet:', default: 20 })) as number; console.log(chalk.yellow('\n๐Ÿ“‹ Replies Analysis Configuration:')); console.log(chalk.cyan(`๐Ÿ“ฅ Input: ${inputFile}`)); console.log(chalk.cyan(`๐Ÿ’Ž Token: ${token}`)); console.log(chalk.cyan(`๐Ÿ’ฌ Max replies: ${maxReplies}`)); const proceed = await confirm({ message: 'Start replies analysis?', default: true }); if (proceed) { // Import and run replies analysis const { runRepliesAnalysis } = await import('./data'); await runRepliesAnalysis(inputFile, token, `replies_${token}_analysis.json`, maxReplies); } await waitForContinue(); } export async function runGuidedSolanaAnalysis(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ”— SOLANA CONTRACT ANALYSIS\n')); const { input, confirm } = await import('@inquirer/prompts'); const inputFile = await input({ message: 'Enter tweets file path:', default: 'scraped_tweets.json' }); const outputFile = await input({ message: 'Enter output file path:', default: 'solana_contracts.json' }); console.log(chalk.yellow('\n๐Ÿ“‹ Solana Analysis Configuration:')); console.log(chalk.cyan(`๐Ÿ“ฅ Input: ${inputFile}`)); console.log(chalk.cyan(`๐Ÿ“ค Output: ${outputFile}`)); const proceed = await confirm({ message: 'Start Solana contract analysis?', default: true }); if (proceed) { // Import and run Solana analysis const { runSolanaContracts } = await import('./api'); await runSolanaContracts(inputFile, outputFile); } await waitForContinue(); } export async function runGuidedConvexSave(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ’พ SAVE TO CONVEX DATABASE\n')); const { input, select, confirm } = await import('@inquirer/prompts'); const inputFile = await input({ message: 'Enter analysis file path:', default: 'crypto_analysis.json' }); const dataType = await select({ message: 'What type of data to save?', choices: [ { name: 'Complete Analysis', value: 'analysis' }, { name: 'Token Data', value: 'tokens' }, { name: 'Account Data', value: 'accounts' } ] }); console.log(chalk.yellow('\n๐Ÿ“‹ Convex Save Configuration:')); console.log(chalk.cyan(`๐Ÿ“ฅ File: ${inputFile}`)); console.log(chalk.cyan(`๐Ÿ“Š Type: ${dataType}`)); const proceed = await confirm({ message: 'Save to Convex database?', default: true }); if (proceed) { // Import and run Convex save const { runConvexSave } = await import('./api'); await runConvexSave(inputFile, dataType); } await waitForContinue(); } export async function runGuidedViewAnalysis(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ“Š VIEW LATEST ANALYSIS\n')); // Import and run view analysis const { runViewAnalysis } = await import('./api'); await runViewAnalysis(); await waitForContinue(); } export async function runGuidedTopSignals(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ” VIEW TOP SIGNALS\n')); // Import and run top signals const { runTopSignals } = await import('./api'); await runTopSignals(); await waitForContinue(); } export async function runGuidedTrendingTokens(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ“ˆ VIEW TRENDING TOKENS\n')); // Import and run trending tokens const { runTrendingTokens } = await import('./api'); await runTrendingTokens(); await waitForContinue(); } export async function runGuidedServe(): Promise<void> { console.log(chalk.blue.bold('๐ŸŒ START API SERVER\n')); const { number, confirm } = await import('@inquirer/prompts'); const port = (await number({ message: 'Enter port for API server:', default: 3000 })) as number; console.log(chalk.yellow('\n๐Ÿ“‹ API Server Configuration:')); console.log(chalk.cyan(`๐ŸŒ Port: ${port}`)); const proceed = await confirm({ message: 'Start API server?', default: true }); if (proceed) { // Import and run API server const { runPodxAPI } = await import('./api'); await runPodxAPI(port); } await waitForContinue(); } export async function runGuidedDatabase(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ’พ START DATABASE SERVICE\n')); const { select, confirm } = await import('@inquirer/prompts'); const choice = await select({ message: 'How would you like to start Convex?', choices: [ { name: '๐Ÿš€ Start Convex now (will run in foreground, Ctrl+C to return to CLI)', value: 'start' }, { name: '๐Ÿ“‹ Show command to start Convex manually', value: 'manual' }, { name: 'โ„น๏ธ Check Convex status', value: 'status' } ] }); if (choice === 'start') { console.log(chalk.yellow('\n๐Ÿ“‹ Starting Convex:')); console.log(chalk.cyan('๐Ÿ’พ This will start Convex in foreground')); console.log(chalk.cyan('๐Ÿ’ก Press Ctrl+C when ready to return to CLI')); console.log(''); const proceed = await confirm({ message: 'Ready to start Convex?', default: true }); if (proceed) { console.log(chalk.green('โ„น๏ธ Run: podx db')); console.log(chalk.gray(' (This will start Convex and you can Ctrl+C to return)')); } } else if (choice === 'manual') { console.log(chalk.yellow('\n๐Ÿ“‹ Manual Convex Commands:')); console.log(chalk.cyan('๐Ÿ”ง Start development: podx db')); console.log(chalk.cyan('๐Ÿญ Deploy production: npx convex deploy')); console.log(chalk.cyan('๐Ÿ“Š View dashboard: npx convex dashboard')); console.log(chalk.cyan('๐Ÿ“ˆ View logs: npx convex logs')); } else if (choice === 'status') { console.log(chalk.yellow('\n๐Ÿ“‹ Convex Status:')); console.log(chalk.cyan('๐Ÿ” Checking Convex connection...')); // Could add actual status checking here } await waitForContinue(); } export async function runGuidedAllServices(): Promise<void> { console.log(chalk.blue.bold('๐ŸŽฏ START ALL SERVICES\n')); const { select, confirm } = await import('@inquirer/prompts'); const choice = await select({ message: 'How would you like to start the services?', choices: [ { name: '๐Ÿš€ Start all services now (API + CLI with local database)', value: 'all' }, { name: '๐Ÿ”ง Start services individually', value: 'individual' }, { name: '๐Ÿ“‹ Show manual startup commands', value: 'manual' } ] }); if (choice === 'all') { console.log(chalk.yellow('\n๐Ÿ“‹ Starting All Services:')); console.log(chalk.cyan('๐Ÿ’พ Local Database (automatic)')); console.log(chalk.cyan('๐ŸŒ API Server (background)')); console.log(chalk.cyan('๐Ÿ–ฅ๏ธ CLI Interface (foreground)')); const proceed = await confirm({ message: 'Start all services?', default: true }); if (proceed) { console.log(chalk.green('โ„น๏ธ Run: podx all')); console.log(chalk.gray(' (This will start API server and switch to CLI with local database)')); } } else if (choice === 'individual') { console.log(chalk.yellow('\n๐Ÿ“‹ Individual Startup Commands:')); console.log(chalk.cyan('1. Start API: podx serve')); console.log(chalk.cyan('2. Start CLI: podx')); console.log(chalk.cyan('3. Optional - Start Convex: podx db')); console.log(''); console.log(chalk.gray('๐Ÿ’ก Local database is used automatically for development')); } else if (choice === 'manual') { console.log(chalk.yellow('\n๐Ÿ“‹ Manual Commands:')); console.log(chalk.cyan('๐ŸŒ Start API: podx serve')); console.log(chalk.cyan('๐Ÿ–ฅ๏ธ Start CLI: podx')); console.log(chalk.cyan('๐ŸŽฏ Start All: podx all')); console.log(chalk.cyan('๐Ÿ”ง Start Convex (optional): podx db')); console.log(''); console.log(chalk.gray('๐Ÿ’ก Development uses local database, production uses Convex')); } await waitForContinue(); } export async function runGuidedTokenGeneration(): Promise<void> { console.log(chalk.blue.bold('๐Ÿ”‘ GENERATE JWT TOKENS\n')); const { input, select, confirm } = await import('@inquirer/prompts'); const userId = await input({ message: 'Enter user ID:', default: 'podx-user' }); const tier = await select({ message: 'Select access tier:', choices: [ { name: 'Free', value: 'free' }, { name: 'Premium', value: 'premium' }, { name: 'Admin', value: 'admin' } ] }); const duration = await select({ message: 'Select token duration:', choices: [ { name: '1 Hour', value: '1h' }, { name: '1 Day', value: '1d' }, { name: '7 Days', value: '7d' }, { name: '30 Days', value: '30d' }, { name: 'Unlimited', value: 'unlimited' } ] }); console.log(chalk.yellow('\n๐Ÿ“‹ Token Configuration:')); console.log(chalk.cyan(`๐Ÿ‘ค User ID: ${userId}`)); console.log(chalk.cyan(`โญ Tier: ${tier}`)); console.log(chalk.cyan(`โฐ Duration: ${duration}`)); const proceed = await confirm({ message: 'Generate JWT token?', default: true }); if (proceed) { // Import and run token generation const { runGenerateToken } = await import('./api'); await runGenerateToken(userId, tier, duration); } await waitForContinue(); }