import type { Command } from 'commander'; import { parsePaginationFlags } from '../cli/pagination.js'; import type { CliContext } from '../cli/shared.js'; import { formatStatsLine } from '../lib/output.js'; import { TwitterClient } from '../lib/twitter-client.js'; export function registerReadCommands(program: Command, ctx: CliContext): void { program .command('read') .description('Read/fetch a tweet by ID or URL') .argument('', 'Tweet ID or URL to read') .option('--json', 'Output as JSON') .option('--json-full', 'Output as JSON with full raw API response in _raw field') .action(async (tweetIdOrUrl: string, cmdOpts: { json?: boolean; jsonFull?: boolean }) => { const opts = program.opts(); const timeoutMs = ctx.resolveTimeoutFromOptions(opts); const quoteDepth = ctx.resolveQuoteDepthFromOptions(opts); const tweetId = ctx.extractTweetId(tweetIdOrUrl); const relayBaseUrl = ctx.resolveRelayBaseUrlFromOptions(opts); const client = new TwitterClient({ relayBaseUrl, timeoutMs, quoteDepth }); const includeRaw = cmdOpts.jsonFull ?? false; const result = await client.getTweet(tweetId, { includeRaw }); if (result.success && result.tweet) { if (cmdOpts.json || cmdOpts.jsonFull) { console.log(JSON.stringify(result.tweet, null, 2)); } else { ctx.printTweets([result.tweet], { showSeparator: false }); console.log(formatStatsLine(result.tweet, ctx.getOutput())); } } else { console.error(`${ctx.p('err')}Failed to read tweet: ${result.error}`); process.exit(1); } }); program .command('replies') .description('List replies to a tweet (by ID or URL)') .argument('', 'Tweet ID or URL') .option('--all', 'Fetch all replies (paged)') .option('--max-pages ', 'Fetch N pages (implies pagination)') .option('--delay ', 'Delay in ms between page fetches', '1000') .option('--cursor ', 'Resume pagination from a cursor') .option('--json', 'Output as JSON') .option('--json-full', 'Output as JSON with full raw API response in _raw field') .action( async ( tweetIdOrUrl: string, cmdOpts: { all?: boolean; maxPages?: string; delay?: string; cursor?: string; json?: boolean; jsonFull?: boolean; }, ) => { const opts = program.opts(); const timeoutMs = ctx.resolveTimeoutFromOptions(opts); const quoteDepth = ctx.resolveQuoteDepthFromOptions(opts); const tweetId = ctx.extractTweetId(tweetIdOrUrl); const pagination = parsePaginationFlags(cmdOpts, { maxPagesImpliesPagination: true, includeDelay: true }); if (!pagination.ok) { console.error(`${ctx.p('err')}${pagination.error}`); process.exit(1); } const relayBaseUrl = ctx.resolveRelayBaseUrlFromOptions(opts); const client = new TwitterClient({ relayBaseUrl, timeoutMs, quoteDepth }); const includeRaw = cmdOpts.jsonFull ?? false; const result = pagination.usePagination ? await client.getRepliesPaged(tweetId, { includeRaw, maxPages: pagination.maxPages, cursor: pagination.cursor, pageDelayMs: pagination.pageDelayMs, }) : await client.getReplies(tweetId, { includeRaw }); const isJson = Boolean(cmdOpts.json || cmdOpts.jsonFull); if (result.tweets) { ctx.printTweetsResult(result, { json: isJson, usePagination: pagination.usePagination, emptyMessage: 'No replies found.', }); // Show pagination hint if there's more if (result.nextCursor && !isJson) { console.error(`${ctx.p('info')}More replies available. Use --cursor "${result.nextCursor}" to continue.`); } } if (!result.success) { console.error(`${ctx.p('err')}Failed to fetch replies: ${result.error}`); process.exit(1); } }, ); program .command('thread') .description('Show the full conversation thread containing the tweet') .argument('', 'Tweet ID or URL') .option('--all', 'Fetch all thread tweets (paged)') .option('--max-pages ', 'Fetch N pages (implies pagination)') .option('--delay ', 'Delay in ms between page fetches', '1000') .option('--cursor ', 'Resume pagination from a cursor') .option('--json', 'Output as JSON') .option('--json-full', 'Output as JSON with full raw API response in _raw field') .action( async ( tweetIdOrUrl: string, cmdOpts: { all?: boolean; maxPages?: string; delay?: string; cursor?: string; json?: boolean; jsonFull?: boolean; }, ) => { const opts = program.opts(); const timeoutMs = ctx.resolveTimeoutFromOptions(opts); const quoteDepth = ctx.resolveQuoteDepthFromOptions(opts); const tweetId = ctx.extractTweetId(tweetIdOrUrl); const pagination = parsePaginationFlags(cmdOpts, { maxPagesImpliesPagination: true, includeDelay: true }); if (!pagination.ok) { console.error(`${ctx.p('err')}${pagination.error}`); process.exit(1); } const relayBaseUrl = ctx.resolveRelayBaseUrlFromOptions(opts); const client = new TwitterClient({ relayBaseUrl, timeoutMs, quoteDepth }); const includeRaw = cmdOpts.jsonFull ?? false; const result = pagination.usePagination ? await client.getThreadPaged(tweetId, { includeRaw, maxPages: pagination.maxPages, cursor: pagination.cursor, pageDelayMs: pagination.pageDelayMs, }) : await client.getThread(tweetId, { includeRaw }); const isJson = Boolean(cmdOpts.json || cmdOpts.jsonFull); if (result.tweets) { ctx.printTweetsResult(result, { json: isJson, usePagination: pagination.usePagination, emptyMessage: 'No thread tweets found.', }); // Show pagination hint if there's more if (result.nextCursor && !isJson) { console.error( `${ctx.p('info')}More thread tweets available. Use --cursor "${result.nextCursor}" to continue.`, ); } } if (!result.success) { console.error(`${ctx.p('err')}Failed to fetch thread: ${result.error}`); process.exit(1); } }, ); }