fix(help): improve help output
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
"build:dist": "tsc && node scripts/copy-dist-assets.js",
|
"build:dist": "tsc && node scripts/copy-dist-assets.js",
|
||||||
"build:binary": "BIRD_VERSION=$(node -p \"require('./package.json').version\") BIRD_GIT_SHA=$(git rev-parse --short=8 HEAD 2>/dev/null || true) bun build --compile --minify --env=BIRD_* src/cli.ts --outfile bird",
|
"build:binary": "BIRD_VERSION=$(node -p \"require('./package.json').version\") BIRD_GIT_SHA=$(git rev-parse --short=8 HEAD 2>/dev/null || true) bun build --compile --minify --env=BIRD_* src/cli.ts --outfile bird",
|
||||||
"dev": "tsx src/index.ts",
|
"dev": "tsx src/index.ts",
|
||||||
"bird": "pnpm run build:dist && node dist/index.js",
|
"bird": "pnpm run build:dist && node dist/cli.js",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"lint": "pnpm run lint:biome && pnpm run lint:oxlint",
|
"lint": "pnpm run lint:biome && pnpm run lint:oxlint",
|
||||||
|
|||||||
+4
-89
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bird - CLI tool for posting tweets and replies
|
* bird - CLI tool for posting tweets and replies
|
||||||
*
|
*
|
||||||
@@ -9,102 +10,16 @@
|
|||||||
* bird read <tweet-id-or-url>
|
* bird read <tweet-id-or-url>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Command } from 'commander';
|
import { createProgram, KNOWN_COMMANDS } from './cli/program.js';
|
||||||
import { collectCookieSource, createCliContext } from './cli/shared.js';
|
import { createCliContext } from './cli/shared.js';
|
||||||
import { registerBookmarksCommand } from './commands/bookmarks.js';
|
|
||||||
import { registerCheckCommand } from './commands/check.js';
|
|
||||||
import { registerHelpCommand } from './commands/help.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 { registerUserCommands } from './commands/users.js';
|
|
||||||
import { resolveCliInvocation } from './lib/cli-args.js';
|
import { resolveCliInvocation } from './lib/cli-args.js';
|
||||||
import { getCliVersion } from './lib/version.js';
|
|
||||||
|
|
||||||
const program: Command = new Command();
|
|
||||||
|
|
||||||
const rawArgs: string[] = process.argv.slice(2);
|
const rawArgs: string[] = process.argv.slice(2);
|
||||||
const normalizedArgs: string[] = rawArgs[0] === '--' ? rawArgs.slice(1) : rawArgs;
|
const normalizedArgs: string[] = rawArgs[0] === '--' ? rawArgs.slice(1) : rawArgs;
|
||||||
|
|
||||||
const ctx = createCliContext(normalizedArgs);
|
const ctx = createCliContext(normalizedArgs);
|
||||||
|
|
||||||
const collect = (value: string, previous: string[] = []): string[] => {
|
const program = createProgram(ctx);
|
||||||
previous.push(value);
|
|
||||||
return previous;
|
|
||||||
};
|
|
||||||
|
|
||||||
const KNOWN_COMMANDS = new Set([
|
|
||||||
'tweet',
|
|
||||||
'reply',
|
|
||||||
'query-ids',
|
|
||||||
'read',
|
|
||||||
'replies',
|
|
||||||
'thread',
|
|
||||||
'search',
|
|
||||||
'mentions',
|
|
||||||
'bookmarks',
|
|
||||||
'following',
|
|
||||||
'followers',
|
|
||||||
'likes',
|
|
||||||
'help',
|
|
||||||
'whoami',
|
|
||||||
'check',
|
|
||||||
]);
|
|
||||||
|
|
||||||
program.addHelpText(
|
|
||||||
'beforeAll',
|
|
||||||
() => `${ctx.colors.banner('bird CLI')} ${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 logged-in account via GraphQL cookies'),
|
|
||||||
formatExample('bird --firefox-profile default-release whoami', 'Use Firefox profile cookies'),
|
|
||||||
formatExample('bird tweet "hello from bird"', 'Send a tweet'),
|
|
||||||
formatExample('bird replies https://x.com/user/status/1234567890123456789', 'Check replies to a tweet'),
|
|
||||||
].join('\n\n')}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
program
|
|
||||||
.option('--auth-token <token>', 'Twitter auth_token cookie')
|
|
||||||
.option('--ct0 <token>', 'Twitter ct0 cookie')
|
|
||||||
.option('--chrome-profile <name>', 'Chrome profile name for cookie extraction', ctx.config.chromeProfile)
|
|
||||||
.option('--firefox-profile <name>', 'Firefox profile name for cookie extraction', ctx.config.firefoxProfile)
|
|
||||||
.option('--cookie-timeout <ms>', 'Cookie extraction timeout in milliseconds (keychain/OS helpers)')
|
|
||||||
.option(
|
|
||||||
'--cookie-source <source>',
|
|
||||||
'Cookie source for browser cookie extraction (repeatable)',
|
|
||||||
collectCookieSource,
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
.option('--media <path>', 'Attach media file (repeatable, up to 4 images or 1 video)', collect, [])
|
|
||||||
.option('--alt <text>', 'Alt text for the corresponding --media (repeatable)', collect, [])
|
|
||||||
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
|
||||||
.option('--quote-depth <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);
|
|
||||||
registerUserCommands(program, ctx);
|
|
||||||
registerCheckCommand(program, ctx);
|
|
||||||
|
|
||||||
const { argv, showHelp } = resolveCliInvocation(normalizedArgs, KNOWN_COMMANDS);
|
const { argv, showHelp } = resolveCliInvocation(normalizedArgs, KNOWN_COMMANDS);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { Command } from 'commander';
|
||||||
|
import { registerBookmarksCommand } from '../commands/bookmarks.js';
|
||||||
|
import { registerCheckCommand } from '../commands/check.js';
|
||||||
|
import { registerHelpCommand } from '../commands/help.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 { registerUserCommands } from '../commands/users.js';
|
||||||
|
import { getCliVersion } from '../lib/version.js';
|
||||||
|
import { type CliContext, collectCookieSource } from './shared.js';
|
||||||
|
|
||||||
|
export const KNOWN_COMMANDS = new Set([
|
||||||
|
'tweet',
|
||||||
|
'reply',
|
||||||
|
'query-ids',
|
||||||
|
'read',
|
||||||
|
'replies',
|
||||||
|
'thread',
|
||||||
|
'search',
|
||||||
|
'mentions',
|
||||||
|
'bookmarks',
|
||||||
|
'following',
|
||||||
|
'followers',
|
||||||
|
'likes',
|
||||||
|
'help',
|
||||||
|
'whoami',
|
||||||
|
'check',
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function createProgram(ctx: CliContext): Command {
|
||||||
|
const program: Command = new Command();
|
||||||
|
|
||||||
|
program.configureHelp({
|
||||||
|
showGlobalOptions: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 logged-in account via GraphQL cookies'),
|
||||||
|
formatExample('bird --firefox-profile default-release whoami', 'Use Firefox profile cookies'),
|
||||||
|
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 <tweet-id-or-url> [--json]', 'Shorthand for `bird read <tweet-id-or-url>`'),
|
||||||
|
].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, query-ids`,
|
||||||
|
)}\n${ctx.colors.muted(` (Run ${ctx.colors.command('bird <command> --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: chromeProfile, firefoxProfile, cookieSource, cookieTimeoutMs, timeoutMs, quoteDepth`,
|
||||||
|
)}\n\n${ctx.colors.section('Env')}\n${ctx.colors.muted(
|
||||||
|
` ${ctx.colors.option('NO_COLOR')}, ${ctx.colors.option('BIRD_TIMEOUT_MS')}, ${ctx.colors.option('BIRD_COOKIE_TIMEOUT_MS')}, ${ctx.colors.option('BIRD_QUOTE_DEPTH')}`,
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
program
|
||||||
|
.option('--auth-token <token>', 'Twitter auth_token cookie')
|
||||||
|
.option('--ct0 <token>', 'Twitter ct0 cookie')
|
||||||
|
.option('--chrome-profile <name>', 'Chrome profile name for cookie extraction', ctx.config.chromeProfile)
|
||||||
|
.option('--firefox-profile <name>', 'Firefox profile name for cookie extraction', ctx.config.firefoxProfile)
|
||||||
|
.option('--cookie-timeout <ms>', 'Cookie extraction timeout in milliseconds (keychain/OS helpers)')
|
||||||
|
.option('--cookie-source <source>', 'Cookie source for browser cookie extraction (repeatable)', collectCookieSource)
|
||||||
|
.option('--media <path>', 'Attach media file (repeatable, up to 4 images or 1 video)', collect)
|
||||||
|
.option('--alt <text>', 'Alt text for the corresponding --media (repeatable)', collect)
|
||||||
|
.option('--timeout <ms>', 'Request timeout in milliseconds')
|
||||||
|
.option('--quote-depth <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);
|
||||||
|
registerUserCommands(program, ctx);
|
||||||
|
registerCheckCommand(program, ctx);
|
||||||
|
|
||||||
|
return program;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user