{"record":{"id":"681aa8a5e9025e6c","repo":"jackwener/OpenCLI","slug":"pass-exactly-one-of-url-url-content-text","errorCode":null,"errorMessage":"Pass exactly one of --url <url>, --content <text>, or --file <path>","messagePattern":"Pass exactly one of --url <url>, --content <text>, or --file <path>","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/notebooklm/add-source.js","lineNumber":213,"sourceCode":"    navigateBefore: false,\n    args: [\n        { name: 'notebook', positional: true, required: true, help: 'Notebook id from `notebooklm list` or full notebook URL' },\n        { name: 'url', help: 'Source URL to add (http/https). Pass exactly one of --url, --content, --file.' },\n        { name: 'content', help: 'Raw text content to add as a Text source (max 10 MB).' },\n        { name: 'file', help: `Local file path to upload as a source (max ${MAX_FILE_SOURCE_BYTES} bytes; pdf / txt / md / html / docx / etc.). Uses Google Drive's 3-step resumable upload protocol.` },\n        { name: 'title', help: 'Title for the text source (default \"Text Source\"). Ignored for --url and --file.' },\n        { name: 'mime-type', help: 'Override the auto-detected MIME type when --file is given.' },\n        { name: 'execute', type: 'boolean', help: 'Actually add the remote source to the NotebookLM notebook' },\n    ],\n    columns: ['notebook_id', 'source_id', 'kind', 'identifier', 'notebook_url'],\n    func: async (page, kwargs) => {\n        const notebookId = parseNotebooklmNotebookTarget(String(kwargs.notebook ?? ''));\n        const url = parseSourceUrl(kwargs.url);\n        const content = parseSourceText(kwargs.content);\n        const filePath = typeof kwargs.file === 'string' && kwargs.file.trim() ? kwargs.file.trim() : '';\n        const modes = [url ? 'url' : '', content !== null ? 'text' : '', filePath ? 'file' : ''].filter(Boolean);\n        if (modes.length === 0) {\n            throw new ArgumentError('Pass exactly one of --url <url>, --content <text>, or --file <path>');\n        }\n        if (modes.length > 1) {\n            throw new ArgumentError('Pass exactly one of --url, --content, --file (got: ' + modes.join(' + ') + ')');\n        }\n        requireNotebooklmExecute(kwargs.execute, 'add a NotebookLM source');\n        const title = parseSourceTitle(kwargs.title, 'Text Source');\n        await ensureNotebooklmHome(page);\n        await requireNotebooklmSession(page);\n        if (filePath) {\n            const file = readFileForUpload(filePath);\n            const mime = inferMimeType(file.filename, kwargs['mime-type']);\n            const registerRpc = await callNotebooklmRpc(page, NOTEBOOKLM_ADD_FILE_SOURCE_RPC_ID, buildRegisterFileSourceArgs(notebookId, file.filename));\n            const sourceId = parseAddSourceResult(registerRpc.result, [notebookId]);\n            if (!sourceId) {\n                throw new CommandExecutionError('NotebookLM AddFileSource (o4cbdc) RPC returned no source id; cannot start file upload.');\n            }\n            await uploadFileViaDriveResumable(page, notebookId, sourceId, file.filename, file.base64, file.size);\n            await verifyNotebooklmSourceAdded(page, notebookId, sourceId, 'add-source --file');","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/notebooklm/add-source.js#L195-L231","documentation":"The add-source command handler throws this ArgumentError when none of the three mutually exclusive source modes (--url, --content, --file) was provided. The command requires exactly one source type; the modes array is built from the parsed values and rejected when its length is 0. (A sibling error with the same advice text plus '(got: ...)' fires when more than one is given.)","triggerScenarios":"Invoking add-source with only --notebook (no source flags at all); passing flags the parser didn't recognize so url/content/file all end up empty; an empty --content string (parsed to null via parseSourceText's undefined/null check) combined with missing --url/--file.","commonSituations":"Forgotten argument in scripted calls; shell eating an empty-quoted --content ''; typos like --ur1 or --file-path that the CLI ignores; running the command to 'test' connectivity without intending to add a source.","solutions":["Add exactly one of --url <url>, --content <text>, or --file <path> to the command.","Check flag spelling — unrecognized/misspelled flags are silently ignored, leaving zero modes.","If passing --content from a variable, confirm it is non-empty (an empty value counts as absent).","Review the command in your script/alias to ensure the source argument wasn't dropped by quoting or expansion."],"exampleFix":"// before\naddSource({ notebook: 'abc123' }); // no source given\n// after\naddSource({ notebook: 'abc123', url: 'https://example.com/article' });","handlingStrategy":"validation","validationCode":"const modes = [url, content, file].filter(v => v !== undefined && v !== null && String(v).trim() !== '');\nif (modes.length !== 1) throw new Error('Pass exactly one of --url, --content, --file');","typeGuard":"function hasExactlyOneSource(o) {\n  const n = ['url', 'content', 'file'].filter(k => o[k] !== undefined && o[k] !== null && String(o[k]).trim() !== '').length;\n  return n === 1;\n}","tryCatchPattern":"try {\n  await addSource(args);\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.includes('Pass exactly one of')) {\n    console.error('Usage: add-source --notebook <id> (--url <u> | --content <t> | --file <p>)');\n  } else throw e;\n}","preventionTips":["Always pass exactly one source flag alongside --notebook.","Check flag spelling; unrecognized flags are ignored, leaving zero sources.","In scripts, assert the source variable is non-empty before building the command.","Verify the final argv/kwargs object with hasExactlyOneSource before invoking."],"tags":["argument-validation","notebooklm","cli-usage","missing-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}