{"record":{"id":"4405b7077293a78b","repo":"jackwener/OpenCLI","slug":"twitter-download-requires-either-username-or-t","errorCode":null,"errorMessage":"twitter download requires either <username> or --tweet-url","messagePattern":"twitter download requires either <username> or --tweet-url","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"warning","filePath":"clis/twitter/download.js","lineNumber":323,"sourceCode":"    access: 'read',\n    description: 'Download Twitter/X media (images and videos). Provide either <username> to fetch every media item from their profile via the GraphQL UserMedia endpoint with cursor pagination, or --tweet-url to download a single tweet.',\n    domain: 'x.com',\n    strategy: Strategy.COOKIE,\n    browser: true,\n    args: [\n        { name: 'username', positional: true, help: 'Twitter username (with or without @) to scan their profile media. Either <username> or --tweet-url is required.' },\n        { name: 'tweet-url', help: 'Single tweet URL to download. Use this OR <username>, not both required at once.' },\n        { name: 'limit', type: 'int', default: 10, help: 'Maximum number of media items to download when scanning a profile (default 10). Ignored when --tweet-url is used.' },\n        { name: 'output', default: './twitter-downloads', help: 'Output directory (default ./twitter-downloads). A per-source subdir is created inside.' },\n    ],\n    columns: ['index', 'tweet_id', 'url', 'type', 'status', 'size'],\n    func: async (page, kwargs) => {\n        try {\n            const rawUsername = String(kwargs.username ?? '').trim();\n            const tweetUrl = String(kwargs['tweet-url'] ?? '').trim();\n            const output = kwargs.output;\n            if (!rawUsername && !tweetUrl) {\n                throw new ArgumentError('twitter download requires either <username> or --tweet-url');\n            }\n            if (rawUsername && tweetUrl) {\n                throw new ArgumentError('Use either <username> or --tweet-url, not both');\n            }\n            if (tweetUrl) {\n                return downloadSingleTweet(page, tweetUrl, output);\n            }\n            const limit = requireLimit(kwargs.limit);\n            const username = normalizeTwitterScreenName(rawUsername);\n            if (!username) {\n                throw new ArgumentError('twitter download username must be a valid Twitter/X handle', 'Example: opencli twitter download @jack --limit 20');\n            }\n            return downloadUserMedia(page, username, limit, output);\n        }\n        catch (err) {\n            if (err instanceof CliError) throw err;\n            throw new CommandExecutionError(`twitter download failed: ${err?.message ?? String(err)}`);\n        }","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/download.js#L305-L341","documentation":"The twitter download CLI requires exactly one input selector: a positional <username> to scan a profile's media, or --tweet-url for a single tweet. This ArgumentError is thrown before any network work when neither argument is provided (and a sibling error fires when both are). It is an ordinary usage/validation error, not a runtime failure.","triggerScenarios":"Calling 'twitter download' with no positional username and no --tweet-url flag (e.g. passing only --limit or --output).","commonSituations":"Scripting the CLI and forgetting the positional argument; quoting/argument-passing bugs where the username is swallowed by the shell; mistaking --tweet-url's flag name (using --url or a positional tweet URL instead); CI configs missing required parameters.","solutions":["Pass a username positionally: opencli twitter download @handle --limit 20","Or pass a single tweet: opencli twitter download --tweet-url https://x.com/user/status/1234567890","Provide exactly one of the two — not both, or the sibling 'Use either <username> or --tweet-url, not both' error fires.","Check your shell/CI quoting so the positional username is actually passed through to the CLI."],"exampleFix":"// before\nawait opencli twitter download --limit 10\n// ArgumentError: twitter download requires either <username> or --tweet-url\n\n// after\nawait opencli twitter download @jack --limit 10","handlingStrategy":"validation","validationCode":"const rawUsername = String(kwargs.username ?? '').trim();\nconst tweetUrl = String(kwargs['tweet-url'] ?? '').trim();\nif (!rawUsername && !tweetUrl) {\n    throw new ArgumentError('twitter download requires either <username> or --tweet-url');\n}\nif (rawUsername && tweetUrl) {\n    throw new ArgumentError('Use either <username> or --tweet-url, not both');\n}","typeGuard":"function hasValidInput(args) {\n    const u = String(args?.username ?? '').trim();\n    const t = String(args?.['tweet-url'] ?? '').trim();\n    return Boolean(u) !== Boolean(t); // exactly one present\n}","tryCatchPattern":"try {\n    await twitterDownload(...args);\n} catch (err) {\n    if (err instanceof ArgumentError && err.message.includes('requires either')) {\n        console.error('Usage: opencli twitter download <username> | --tweet-url <url>');\n        process.exitCode = 2;\n    } else throw err;\n}","preventionTips":["Always pass exactly one of: positional username or --tweet-url","Check shell quoting so the positional username reaches the CLI","Use the flag name '--tweet-url' exactly (not --url)","Add the input check to CI/scripts before invoking the CLI"],"tags":["cli","argument-validation","usage","twitter"],"backgroundTag":"missing-required-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}