@podx/cli
Version:
๐ป Command-line interface for PODx - Advanced Twitter/X scraping and crypto analysis toolkit
1,009 lines (829 loc) โข 29.3 kB
text/typescript
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();
}