@podx/cli
Version:
๐ป Command-line interface for PODx - Advanced Twitter/X scraping and crypto analysis toolkit
292 lines (266 loc) โข 13.2 kB
text/typescript
import { Command } from 'commander';
import { runScraping, runBatchScraping, runStatus, runClean, runConfiguration, runCryptoAnalysis, runAccountAnalysis, runRepliesAnalysis, runPodxAPI, runGenerateToken, runConvexSave, runSearchTweets, runListScrape, runCryptoSearch, runSolanaContracts, runHashtagSearch, runTickerSearch, runReplySearch, runAdvancedAnalysis, runBatchAdvancedAnalysis } from '../handlers';
import { config as appConfig } from '@podx/core';
export function registerCommands(program: Command): void {
// ===== SCRAPING COMMANDS =====
program
.command('scrape')
.description('๐ฆ Scrape tweets from a specific X (Twitter) account')
.option('-u, --username <username>', 'Target username to scrape (without @)')
.option('-c, --count <number>', 'Number of tweets to scrape (default: 300)', '300')
.action(async (options) => {
const scraperConfig = {
targetUsername: options.username || appConfig.scraper.targetUsername,
maxTweets: options.count ? parseInt(options.count, 10) : appConfig.scraper.maxTweets
};
await runScraping(scraperConfig);
});
program
.command('batch')
.description('๐ฆ Scrape multiple X accounts concurrently with rate limiting')
.option('-u, --usernames <usernames>', 'Comma-separated usernames (e.g., "elonmusk,pmarca,naval")')
.option('-f, --file <file>', 'File containing usernames (one per line)')
.option('-c, --count <number>', 'Number of tweets to scrape per account', '200')
.option('--concurrent <number>', 'Number of accounts to scrape concurrently', '3')
.option('--delay <seconds>', 'Delay between account scrapes in seconds', '5')
.action(async (options) => {
const batchConfig = {
usernames: options.usernames,
usernamesFile: options.file,
maxTweets: options.count ? parseInt(options.count, 10) : appConfig.scraper.maxTweets,
concurrent: parseInt(options.concurrent, 10),
delaySeconds: parseInt(options.delay, 10)
};
await runBatchScraping(batchConfig);
});
// ===== SYSTEM COMMANDS =====
program
.command('status')
.description('๐ Show current system status, data, and configuration')
.action(runStatus);
program
.command('clean')
.description('๐งน Clean build artifacts and optionally remove data')
.option('--all', 'Remove all data including scraped tweets')
.action(runClean);
program
.command('config')
.description('โ๏ธ Setup or modify Twitter credentials and configuration')
.action(runConfiguration);
// ===== ANALYSIS COMMANDS =====
program
.command('analyze')
.description('๐ Analyze scraped tweets for crypto signals and token mentions')
.option('-f, --file <file>', 'Input tweets file', 'scraped_tweets.json')
.option('-o, --output <file>', 'Output analysis file', 'crypto_analysis.json')
.action(async (options) => {
await runCryptoAnalysis(options.file, options.output);
});
program
.command('accounts')
.description('๐ค Analyze account reputations and detect bots/shillers')
.option('-f, --file <file>', 'Input tweets file', 'scraped_tweets.json')
.option('-o, --output <file>', 'Output analysis file', 'account_analysis.json')
.action(async (options) => {
await runAccountAnalysis(options.file, options.output);
});
program
.command('replies')
.description('๐ฌ Scrape and analyze replies/comments for crypto signals')
.option('-f, --file <file>', 'Input tweets file', 'scraped_tweets.json')
.option('-t, --token <token>', 'Token to analyze replies for (e.g., BTC, SOL)')
.option('-o, --output <file>', 'Output analysis file', 'replies_analysis.json')
.option('-c, --count <number>', 'Max replies per tweet', '20')
.action(async (options) => {
await runRepliesAnalysis(options.file, options.token, options.output, parseInt(options.count));
});
// ===== ADVANCED ANALYSIS WITH BOT DETECTION =====
program
.command('bot-analysis')
.description('๐ค Advanced analysis with bot detection and token shilling monitoring')
.option('-u, --username <username>', 'Target username to analyze (without @)')
.option('-c, --count <number>', 'Number of tweets to scrape and analyze', '300')
.option('--no-bot-detection', 'Disable bot detection analysis')
.option('--no-token-analysis', 'Disable token shilling analysis')
.option('--no-replies', 'Disable reply analysis')
.option('-o, --output <file>', 'Save analysis to custom file')
.action(async (options) => {
const username = options.username || appConfig.scraper.targetUsername;
const count = options.count ? parseInt(options.count, 10) : appConfig.scraper.maxTweets;
await runAdvancedAnalysis(username, count, {
includeBotAnalysis: !options.noBotDetection,
includeTokenAnalysis: !options.noTokenAnalysis,
analyzeReplies: !options.noReplies,
outputFile: options.output
});
});
program
.command('batch-bot-analysis')
.description('๐ค๐ฆ Batch advanced analysis with bot detection for multiple accounts')
.option('-u, --usernames <usernames>', 'Comma-separated usernames to analyze')
.option('-f, --file <file>', 'File containing usernames (one per line)')
.option('-c, --count <number>', 'Number of tweets to analyze per account', '200')
.option('--concurrent <number>', 'Number of accounts to analyze concurrently', '2')
.option('--delay <seconds>', 'Delay between batches in seconds', '10')
.option('--no-bot-detection', 'Disable bot detection analysis')
.option('--no-token-analysis', 'Disable token shilling analysis')
.option('--no-replies', 'Disable reply analysis')
.action(async (options) => {
let usernames: string[] = [];
if (options.usernames) {
usernames = options.usernames.split(',').map((u: string) => u.trim()).filter((u: string) => u);
} else if (options.file) {
const fs = await import('fs/promises');
const content = await fs.readFile(options.file, 'utf-8');
usernames = content.split('\n').map((u: string) => u.trim()).filter((u: string) => u);
} else {
console.error('โ Either --usernames or --file must be provided');
return;
}
await runBatchAdvancedAnalysis(usernames, parseInt(options.count, 10), {
includeBotAnalysis: !options.noBotDetection,
includeTokenAnalysis: !options.noTokenAnalysis,
analyzeReplies: !options.noReplies,
concurrent: parseInt(options.concurrent, 10),
delay: parseInt(options.delay, 10)
});
});
// ===== SEARCH COMMANDS =====
program
.command('search')
.description('๐ Search Twitter for specific queries with advanced filters')
.option('-q, --query <query>', 'Search query (required)')
.option('-c, --count <number>', 'Max tweets to fetch', '100')
.option('-m, --mode <mode>', 'Search mode (Latest|Top|People|Photos|Videos)', 'Latest')
.option('-o, --output <file>', 'Output file', 'search_results.json')
.option('--min-likes <number>', 'Minimum likes filter')
.option('--min-retweets <number>', 'Minimum retweets filter')
.option('--date-from <date>', 'Start date (YYYY-MM-DD)')
.option('--date-to <date>', 'End date (YYYY-MM-DD)')
.option('--no-retweets', 'Exclude retweets')
.action(async (options) => {
if (!options.query) {
console.error('โ Query is required. Use -q or --query to specify search terms.');
return;
}
await runSearchTweets(options);
});
program
.command('crypto-search')
.description('๐ Search Twitter for crypto-related content with enhanced token detection')
.option('-t, --tokens <tokens>', 'Comma-separated token symbols (e.g., BTC,ETH,SOL)')
.option('-k, --keywords <keywords>', 'Comma-separated crypto keywords')
.option('-c, --count <number>', 'Max tweets to fetch', '200')
.option('-m, --mode <mode>', 'Search mode (Latest|Top)', 'Latest')
.option('-o, --output <file>', 'Output file', 'crypto_search_results.json')
.option('--min-engagement <number>', 'Minimum likes for filtering', '0')
.action(async (options) => {
await runCryptoSearch(options);
});
program
.command('hashtags')
.description('๐ท๏ธ Search Twitter for specific hashtags')
.option('-h, --hashtags <hashtags>', 'Comma-separated hashtags (e.g., bitcoin,crypto,web3)')
.option('-c, --count <number>', 'Max tweets to fetch', '100')
.option('-o, --output <file>', 'Output file', 'hashtag_search.json')
.action(async (options) => {
if (!options.hashtags) {
console.error('โ Hashtags are required. Use -h or --hashtags to specify hashtags.');
return;
}
await runHashtagSearch(options);
});
program
.command('tickers')
.description('๐ฐ Search Twitter for specific ticker symbols ($BTC, $ETH, etc.)')
.option('-t, --tickers <tickers>', 'Comma-separated tickers (e.g., BTC,ETH,SOL)')
.option('-c, --count <number>', 'Max tweets to fetch', '100')
.option('-o, --output <file>', 'Output file', 'ticker_search.json')
.action(async (options) => {
if (!options.tickers) {
console.error('โ Tickers are required. Use -t or --tickers to specify ticker symbols.');
return;
}
await runTickerSearch(options);
});
program
.command('list')
.description('๐ Scrape tweets from a Twitter list (experimental)')
.option('-i, --id <listId>', 'Twitter list ID (required)')
.option('-c, --count <number>', 'Max tweets to fetch', '100')
.option('-o, --output <file>', 'Output file', 'list_results.json')
.option('--no-retweets', 'Exclude retweets')
.action(async (options) => {
if (!options.id) {
console.error('โ List ID is required. Use -i or --id to specify the Twitter list ID.');
return;
}
await runListScrape(options);
});
program
.command('reply-search')
.description('๐ฌ Search replies for specific tweet IDs (experimental)')
.option('-i, --ids <ids>', 'Comma-separated tweet IDs')
.option('-c, --count <number>', 'Max replies per tweet', '50')
.option('-o, --output <file>', 'Output file', 'replies_search.json')
.action(async (options) => {
if (!options.ids) {
console.error('โ Tweet IDs are required. Use -i or --ids to specify tweet IDs.');
return;
}
await runReplySearch(options);
});
// ===== SPECIALIZED ANALYSIS =====
program
.command('solana-contracts')
.description('๐ Analyze tweets for Solana contract addresses and smart contracts')
.option('-f, --file <file>', 'Input tweets file', 'scraped_tweets.json')
.option('-o, --output <file>', 'Output file', 'solana_contracts.json')
.action(async (options) => {
await runSolanaContracts(options.file, options.output);
});
// ===== API & SERVICES =====
program
.command('serve')
.description('๐ Start the PODx REST API server')
.option('-p, --port <port>', 'Port to run the API server on', '3000')
.action(async (options) => {
const port = parseInt(options.port, 10);
await runPodxAPI(port);
});
program
.command('token')
.description('๐ Generate JWT tokens for PODX API access')
.option('-u, --user <userId>', 'User ID for the token', 'podx-user')
.option('-t, --tier <tier>', 'Access tier (free|premium|admin)', 'free')
.option('-d, --duration <duration>', 'Token duration (1h|1d|7d|30d|unlimited)', 'unlimited')
.option('-n, --name <name>', 'Optional name/description for the token')
.action(async (options) => {
await runGenerateToken(options.user, options.tier, options.duration, options.name);
});
program
.command('convex')
.description('๐พ Save analysis data to Convex database (optional)')
.option('-f, --file <file>', 'Input analysis file', 'crypto_analysis.json')
.option('-t, --type <type>', 'Data type to save (analysis|tokens|accounts)', 'analysis')
.action(async (options) => {
await runConvexSave(options.file, options.type);
});
program
.command('db')
.description('๐พ Start the PODx database service (Convex development)')
.action(async () => {
console.log('๐พ Starting PODx Database Service...');
console.log('๐ Convex development mode');
console.log('๐ก Use "npx convex dev" to start Convex development server');
});
program
.command('all')
.description('๐ฏ Start all PODx services (API + Database + CLI)')
.action(async () => {
console.log('๐ฏ Starting PODx Full Stack...');
console.log('๐ API Server: http://localhost:3000');
console.log('๐พ Database: Local/Convex mode');
console.log('๐ป CLI: Interactive mode ready');
});
}