import { Command } from 'commander'; import { registerBookmarksCommand } from '../commands/bookmarks.js'; import { registerCheckCommand } from '../commands/check.js'; import { registerFollowCommands } from '../commands/follow.js'; import { registerHelpCommand } from '../commands/help.js'; import { registerHomeCommand } from '../commands/home.js'; import { registerListsCommand } from '../commands/lists.js'; import { registerNewsCommand } from '../commands/news.js'; import { registerPostCommands } from '../commands/post.js'; import { registerQueryIdsCommand } from '../commands/query-ids.js'; import { registerReadCommands } from '../commands/read.js'; import { registerSearchCommands } from '../commands/search.js'; import { registerUnbookmarkCommand } from '../commands/unbookmark.js'; import { registerUserTweetsCommand } from '../commands/user-tweets.js'; import { registerUserCommands } from '../commands/users.js'; import { getCliVersion } from '../lib/version.js'; import type { CliContext } from './shared.js'; export const KNOWN_COMMANDS = new Set([ 'tweet', 'reply', 'query-ids', 'read', 'replies', 'thread', 'search', 'mentions', 'bookmarks', 'unbookmark', 'follow', 'unfollow', 'following', 'followers', 'likes', 'lists', 'list-timeline', 'home', 'user-tweets', 'news', 'trending', 'help', 'whoami', 'check', ]); export function createProgram(ctx: CliContext): Command { const program: Command = new Command(); program.configureHelp({ showGlobalOptions: true, styleTitle: (t) => ctx.colors.section(t), styleUsage: (t) => ctx.colors.description(t), styleCommandText: (t) => ctx.colors.command(t), styleCommandDescription: (t) => ctx.colors.muted(t), styleOptionTerm: (t) => ctx.colors.option(t), styleOptionText: (t) => ctx.colors.option(t), styleOptionDescription: (t) => ctx.colors.muted(t), styleArgumentTerm: (t) => ctx.colors.argument(t), styleArgumentText: (t) => ctx.colors.argument(t), styleArgumentDescription: (t) => ctx.colors.muted(t), styleSubcommandTerm: (t) => ctx.colors.command(t), styleSubcommandText: (t) => ctx.colors.command(t), styleSubcommandDescription: (t) => ctx.colors.muted(t), styleDescriptionText: (t) => ctx.colors.muted(t), }); const collect = (value: string, previous: string[] = []): string[] => { previous.push(value); return previous; }; program.addHelpText( 'beforeAll', () => `${ctx.colors.banner('bird')} ${ctx.colors.muted(getCliVersion())} ${ctx.colors.subtitle( '— fast X CLI for tweeting, replying, and reading', )}`, ); program.name('bird').description('Post tweets and replies via Twitter/X GraphQL API').version(getCliVersion()); const formatExample = (command: string, description: string): string => `${ctx.colors.command(` ${command}`)}\n${ctx.colors.muted(` ${description}`)}`; program.addHelpText( 'afterAll', () => `\n${ctx.colors.section('Examples')}\n${[ formatExample('bird whoami', 'Show the relay account'), formatExample('bird --relay-base-url http://localhost:6900 whoami', 'Use a specific twitter safe relay'), formatExample('bird tweet "hello from bird"', 'Send a tweet'), formatExample( 'bird 1234567890123456789 --json', 'Read a tweet (ID or URL shorthand for `read`) and print JSON', ), ].join('\n\n')}\n\n${ctx.colors.section('Shortcuts')}\n${[ formatExample('bird [--json]', 'Shorthand for `bird read `'), ].join('\n\n')}\n\n${ctx.colors.section('JSON Output')}\n${ctx.colors.muted( ` Add ${ctx.colors.option('--json')} to: read, replies, thread, search, mentions, bookmarks, likes, following, followers, lists, list-timeline, user-tweets, query-ids`, )}\n${ctx.colors.muted( ` Add ${ctx.colors.option('--json-full')} to include raw API response in ${ctx.colors.argument('_raw')} field (tweet commands only)`, )}\n${ctx.colors.muted(` (Run ${ctx.colors.command('bird --help')} to see per-command flags.)`)}`, ); program.addHelpText( 'afterAll', () => `\n\n${ctx.colors.section('Config')}\n${ctx.colors.muted( ` Reads ${ctx.colors.argument('~/.config/bird/config.json5')} and ${ctx.colors.argument('./.birdrc.json5')} (JSON5)`, )}\n${ctx.colors.muted( ` Supports: relayBaseUrl, profileName, timeoutMs, quoteDepth`, )}\n\n${ctx.colors.section('Env')}\n${ctx.colors.muted( ` ${ctx.colors.option('TWITTER_RELAY_BASE_URL')}, ${ctx.colors.option('BIRD_PROFILE_NAME')}, ${ctx.colors.option('NO_COLOR')}, ${ctx.colors.option('BIRD_TIMEOUT_MS')}, ${ctx.colors.option('BIRD_QUOTE_DEPTH')}`, )}`, ); program .option('--relay-base-url ', 'twitter safe relay base URL', ctx.config.relayBaseUrl) .option('--profile-name ', 'twitter safe relay profile name') .option('--media ', 'Attach media file (repeatable, up to 4 images or 1 video)', collect) .option('--alt ', 'Alt text for the corresponding --media (repeatable)', collect) .option('--timeout ', 'Request timeout in milliseconds') .option('--quote-depth ', 'Max quoted tweet depth (default: 1; 0 disables)') .option('--plain', 'Plain output (stable, no emoji, no color)') .option('--no-emoji', 'Disable emoji output') .option('--no-color', 'Disable ANSI colors (or set NO_COLOR)'); program.hook('preAction', (_thisCommand, actionCommand) => { ctx.applyOutputFromCommand(actionCommand); }); registerHelpCommand(program, ctx); registerQueryIdsCommand(program, ctx); registerPostCommands(program, ctx); registerReadCommands(program, ctx); registerSearchCommands(program, ctx); registerBookmarksCommand(program, ctx); registerUnbookmarkCommand(program, ctx); registerFollowCommands(program, ctx); registerListsCommand(program, ctx); registerHomeCommand(program, ctx); registerUserCommands(program, ctx); registerUserTweetsCommand(program, ctx); registerNewsCommand(program, ctx); registerCheckCommand(program, ctx); return program; }