From fe745a7d344b3b255a9195e4e4a910ef090780dd Mon Sep 17 00:00:00 2001 From: Francesca Guiducci Date: Mon, 18 May 2026 10:41:11 +0200 Subject: [PATCH 1/3] docs(task): clarify --due is a server-side pass-through; steer to quickadd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --due value on `task add` / `task update` is sent verbatim as `due_string`; the CLI does not parse or rewrite it. Todoist's server `due_string` parser handles simple inputs but does NOT consume `starting ` clauses — strings like `every! 2 weeks starting 2026-05-17` end up stored as the entire literal recurrence rule and the task never advances on completion. The quick-add parser does consume `starting` correctly. Document this in three places users actually hit: - `task add --due` help: shorter description + a Notes block on `td task add --help` describing the failure mode and pointing to `td task quickadd` as the right command for NL recurrence. - `task update --due` help: same pass-through note, referring back to `td task add --help`. - Skill content (`src/lib/skills/content.ts`) — adds a bullet to the existing "Choosing between task add and task quickadd" guidance. Regenerated `skills/todoist-cli/SKILL.md` via `npm run sync:skill`. Co-Authored-By: Claude Opus 4.7 --- skills/todoist-cli/SKILL.md | 1 + src/commands/task/index.ts | 24 ++++++++++++++++++++++-- src/lib/skills/content.ts | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/skills/todoist-cli/SKILL.md b/skills/todoist-cli/SKILL.md index 838e315..716fc45 100644 --- a/skills/todoist-cli/SKILL.md +++ b/skills/todoist-cli/SKILL.md @@ -141,6 +141,7 @@ Choosing between `task add` and `task quickadd`: - Use `td task add` when you need flags that Quick Add syntax can't express (`--deadline`, `--description`, `--parent`, `--duration`, `--uncompletable`, `--order`), when the text is being composed programmatically, or when you need explicit `id:` / URL references for project/section/parent. - `td task quickadd` supports `--stdin`, `--json`, and `--dry-run` only; everything else is embedded in the text. - The top-level `td add ` is a human shorthand for `td task quickadd` — same parser, same flag surface (`--stdin`, `--json`, `--dry-run`). Agents should prefer `td task quickadd` / `qa` for discoverability alongside the other task subcommands. +- `--due` on `task add` / `task update` is **sent verbatim** to the API as `due_string` — the CLI does not parse or rewrite it. The server's `due_string` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** consume `starting ` clauses: a string like `"every! 2 weeks starting 2026-05-17"` is stored as the entire literal recurrence rule and the task never advances on completion. For recurrence with an explicit first-occurrence date, use `td task quickadd " every! 2 weeks starting "` (the quick-add parser consumes the `starting` clause correctly). Useful task flags: - `--stdin` on `task add` reads the task description from stdin; on `task quickadd` (and the top-level `td add`) it reads the full natural-language text from stdin. diff --git a/src/commands/task/index.ts b/src/commands/task/index.ts index d9dbea5..f6776c1 100644 --- a/src/commands/task/index.ts +++ b/src/commands/task/index.ts @@ -115,7 +115,10 @@ Examples: .command('add [content]') .description('Add a task') .option('--content ', 'Task content (legacy, prefer positional argument)') - .option('--due ', 'Due date (natural language or YYYY-MM-DD)') + .option( + '--due ', + 'Due date (YYYY-MM-DD or natural-language; sent verbatim to the API — see Notes below)', + ) .option('--deadline ', 'Deadline date (YYYY-MM-DD)') .addOption( withCaseInsensitiveChoices( @@ -157,6 +160,20 @@ Examples: } return addTask({ ...options, content }) }) + .addHelpText( + 'after', + ` +Notes: + --due is sent verbatim as the task's due_string. The server's due_string + parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but + does NOT consume "starting " clauses — a string like + "every! 2 weeks starting 2026-05-17" is stored as the entire literal + recurrence rule, and the task will never advance on completion. + + For natural-language input — especially recurrence with an explicit first + occurrence — use quickadd, which routes through Todoist's quick-add parser: + td task quickadd "Pay rent every! month starting 2026-06-01"`, + ) const quickaddCmd = task .command('quickadd [text]') @@ -179,7 +196,10 @@ Examples: .command('update [ref]') .description('Update a task') .option('--content ', 'New content') - .option('--due ', 'New due date') + .option( + '--due ', + 'New due date (sent verbatim to the API as due_string — the same caveats as "task add --due" apply; see "td task add --help")', + ) .option('--no-due', 'Remove due date') .option('--deadline ', 'Deadline date (YYYY-MM-DD)') .option('--no-deadline', 'Remove deadline') diff --git a/src/lib/skills/content.ts b/src/lib/skills/content.ts index bc0d71d..f9fa073 100644 --- a/src/lib/skills/content.ts +++ b/src/lib/skills/content.ts @@ -137,6 +137,7 @@ Choosing between \`task add\` and \`task quickadd\`: - Use \`td task add\` when you need flags that Quick Add syntax can't express (\`--deadline\`, \`--description\`, \`--parent\`, \`--duration\`, \`--uncompletable\`, \`--order\`), when the text is being composed programmatically, or when you need explicit \`id:\` / URL references for project/section/parent. - \`td task quickadd\` supports \`--stdin\`, \`--json\`, and \`--dry-run\` only; everything else is embedded in the text. - The top-level \`td add \` is a human shorthand for \`td task quickadd\` — same parser, same flag surface (\`--stdin\`, \`--json\`, \`--dry-run\`). Agents should prefer \`td task quickadd\` / \`qa\` for discoverability alongside the other task subcommands. +- \`--due\` on \`task add\` / \`task update\` is **sent verbatim** to the API as \`due_string\` — the CLI does not parse or rewrite it. The server's \`due_string\` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** consume \`starting \` clauses: a string like \`"every! 2 weeks starting 2026-05-17"\` is stored as the entire literal recurrence rule and the task never advances on completion. For recurrence with an explicit first-occurrence date, use \`td task quickadd " every! 2 weeks starting "\` (the quick-add parser consumes the \`starting\` clause correctly). Useful task flags: - \`--stdin\` on \`task add\` reads the task description from stdin; on \`task quickadd\` (and the top-level \`td add\`) it reads the full natural-language text from stdin. From e68db4de024ea174327296478252431753e56ee5 Mon Sep 17 00:00:00 2001 From: Francesca Guiducci Date: Mon, 18 May 2026 10:55:17 +0200 Subject: [PATCH 2/3] docs(task): add Notes block to task update --help; sync skill bullet Follow-up to fe745a7. Mirrors the same `due_string` caveat into `td task update --help` via an addHelpText('after', ...) block, and points users at `td task reschedule` as the way to move the next occurrence of a recurring task without disturbing the recurrence rule. Also pulls the manual tightening of the SKILL.md bullet back into `src/lib/skills/content.ts` so future `npm run sync:skill` runs stay consistent with the shorter wording. Co-Authored-By: Claude Opus 4.7 --- skills/todoist-cli/SKILL.md | 2 +- src/commands/task/index.ts | 26 ++++++++++++++------------ src/lib/skills/content.ts | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/skills/todoist-cli/SKILL.md b/skills/todoist-cli/SKILL.md index 716fc45..69411f8 100644 --- a/skills/todoist-cli/SKILL.md +++ b/skills/todoist-cli/SKILL.md @@ -141,7 +141,7 @@ Choosing between `task add` and `task quickadd`: - Use `td task add` when you need flags that Quick Add syntax can't express (`--deadline`, `--description`, `--parent`, `--duration`, `--uncompletable`, `--order`), when the text is being composed programmatically, or when you need explicit `id:` / URL references for project/section/parent. - `td task quickadd` supports `--stdin`, `--json`, and `--dry-run` only; everything else is embedded in the text. - The top-level `td add ` is a human shorthand for `td task quickadd` — same parser, same flag surface (`--stdin`, `--json`, `--dry-run`). Agents should prefer `td task quickadd` / `qa` for discoverability alongside the other task subcommands. -- `--due` on `task add` / `task update` is **sent verbatim** to the API as `due_string` — the CLI does not parse or rewrite it. The server's `due_string` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** consume `starting ` clauses: a string like `"every! 2 weeks starting 2026-05-17"` is stored as the entire literal recurrence rule and the task never advances on completion. For recurrence with an explicit first-occurrence date, use `td task quickadd " every! 2 weeks starting "` (the quick-add parser consumes the `starting` clause correctly). +- `--due` on `task add` / `task update` is **sent verbatim** to the API as `due_string` — the CLI does not parse or rewrite it. The server's `due_string` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** unpack some more complex clauses (i.e. `starting `). Please use `td task quickadd ""`. Useful task flags: - `--stdin` on `task add` reads the task description from stdin; on `task quickadd` (and the top-level `td add`) it reads the full natural-language text from stdin. diff --git a/src/commands/task/index.ts b/src/commands/task/index.ts index f6776c1..21bd4b0 100644 --- a/src/commands/task/index.ts +++ b/src/commands/task/index.ts @@ -115,10 +115,7 @@ Examples: .command('add [content]') .description('Add a task') .option('--content ', 'Task content (legacy, prefer positional argument)') - .option( - '--due ', - 'Due date (YYYY-MM-DD or natural-language; sent verbatim to the API — see Notes below)', - ) + .option('--due ', 'Due date (YYYY-MM-DD or simple natural language; see Notes below)') .option('--deadline ', 'Deadline date (YYYY-MM-DD)') .addOption( withCaseInsensitiveChoices( @@ -166,13 +163,9 @@ Examples: Notes: --due is sent verbatim as the task's due_string. The server's due_string parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but - does NOT consume "starting " clauses — a string like - "every! 2 weeks starting 2026-05-17" is stored as the entire literal - recurrence rule, and the task will never advance on completion. - - For natural-language input — especially recurrence with an explicit first - occurrence — use quickadd, which routes through Todoist's quick-add parser: - td task quickadd "Pay rent every! month starting 2026-06-01"`, + does not unpack some more complex clauses (i.e. "starting "). For complex + natural-language input, prefer using quickadd, which routes through Todoist's + quick-add parser.`, ) const quickaddCmd = task @@ -198,7 +191,7 @@ Notes: .option('--content ', 'New content') .option( '--due ', - 'New due date (sent verbatim to the API as due_string — the same caveats as "task add --due" apply; see "td task add --help")', + 'New due date (YYYY-MM-DD or simple natural language; see Notes below)', ) .option('--no-due', 'Remove due date') .option('--deadline ', 'Deadline date (YYYY-MM-DD)') @@ -236,6 +229,15 @@ Notes: } return updateTask(ref, options) }) + .addHelpText( + 'after', + ` +Notes: + --due is sent verbatim as the task's due_string, with the same caveats as + "task add --due": the server's due_string parser does not unpack some more + complex clauses (i.e. "starting "). To move the next occurrence of a + recurring task without changing its recurrence rule, use \`td task reschedule\`.`, + ) const moveCmd = task .command('move [ref]') diff --git a/src/lib/skills/content.ts b/src/lib/skills/content.ts index f9fa073..e468c08 100644 --- a/src/lib/skills/content.ts +++ b/src/lib/skills/content.ts @@ -137,7 +137,7 @@ Choosing between \`task add\` and \`task quickadd\`: - Use \`td task add\` when you need flags that Quick Add syntax can't express (\`--deadline\`, \`--description\`, \`--parent\`, \`--duration\`, \`--uncompletable\`, \`--order\`), when the text is being composed programmatically, or when you need explicit \`id:\` / URL references for project/section/parent. - \`td task quickadd\` supports \`--stdin\`, \`--json\`, and \`--dry-run\` only; everything else is embedded in the text. - The top-level \`td add \` is a human shorthand for \`td task quickadd\` — same parser, same flag surface (\`--stdin\`, \`--json\`, \`--dry-run\`). Agents should prefer \`td task quickadd\` / \`qa\` for discoverability alongside the other task subcommands. -- \`--due\` on \`task add\` / \`task update\` is **sent verbatim** to the API as \`due_string\` — the CLI does not parse or rewrite it. The server's \`due_string\` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** consume \`starting \` clauses: a string like \`"every! 2 weeks starting 2026-05-17"\` is stored as the entire literal recurrence rule and the task never advances on completion. For recurrence with an explicit first-occurrence date, use \`td task quickadd " every! 2 weeks starting "\` (the quick-add parser consumes the \`starting\` clause correctly). +- \`--due\` on \`task add\` / \`task update\` is **sent verbatim** to the API as \`due_string\` — the CLI does not parse or rewrite it. The server's \`due_string\` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** unpack some more complex clauses (i.e. \`starting \`). Please use \`td task quickadd ""\`. Useful task flags: - \`--stdin\` on \`task add\` reads the task description from stdin; on \`task quickadd\` (and the top-level \`td add\`) it reads the full natural-language text from stdin. From 3638951dc6f8ba2bb30da98a263e35eec9d6b39d Mon Sep 17 00:00:00 2001 From: Francesca Guiducci Date: Mon, 18 May 2026 11:00:26 +0200 Subject: [PATCH 3/3] test(task): lock --due caveat into task add/update --help output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses doistbot's P2 review comment: the prior commits added the "--due is sent verbatim" Notes blocks to `td task add --help` and `td task update --help` but had no regression coverage, so a future Commander/help refactor could silently drop the guidance. Adds a new describe block in `src/commands/task/task.test.ts` that captures `--help` output for both subcommands and asserts the key phrases stay present: - `task add --help`: "Notes:", "sent verbatim", `"starting "`, "quick-add parser". - `task update --help`: "Notes:", "sent verbatim", `"starting "`, "same caveats as", `"task add --due"`. Also finalises the surrounding copy that the tests pin: - `task update --help` Notes block drops the `task reschedule` recommendation (reschedule is for moving the next occurrence without changing the recurrence rule — orthogonal to the `due_string` caveat the block is about). - Skill bullet (`src/lib/skills/content.ts`, regenerated `skills/todoist-cli/SKILL.md`) scopes the `td task quickadd` recommendation to new-task creation, since quickadd can't replace an update. Co-Authored-By: Claude Opus 4.7 --- skills/todoist-cli/SKILL.md | 2 +- src/commands/task/index.ts | 3 +-- src/commands/task/task.test.ts | 34 ++++++++++++++++++++++++++++++++++ src/lib/skills/content.ts | 2 +- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/skills/todoist-cli/SKILL.md b/skills/todoist-cli/SKILL.md index 69411f8..25d48a0 100644 --- a/skills/todoist-cli/SKILL.md +++ b/skills/todoist-cli/SKILL.md @@ -141,7 +141,7 @@ Choosing between `task add` and `task quickadd`: - Use `td task add` when you need flags that Quick Add syntax can't express (`--deadline`, `--description`, `--parent`, `--duration`, `--uncompletable`, `--order`), when the text is being composed programmatically, or when you need explicit `id:` / URL references for project/section/parent. - `td task quickadd` supports `--stdin`, `--json`, and `--dry-run` only; everything else is embedded in the text. - The top-level `td add ` is a human shorthand for `td task quickadd` — same parser, same flag surface (`--stdin`, `--json`, `--dry-run`). Agents should prefer `td task quickadd` / `qa` for discoverability alongside the other task subcommands. -- `--due` on `task add` / `task update` is **sent verbatim** to the API as `due_string` — the CLI does not parse or rewrite it. The server's `due_string` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** unpack some more complex clauses (i.e. `starting `). Please use `td task quickadd ""`. +- `--due` on `task add` / `task update` is **sent verbatim** to the API as `due_string` — the CLI does not parse or rewrite it. The server's `due_string` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** unpack some more complex clauses (i.e. `starting `). For complex natural-language input on new tasks, prefer `td task quickadd`, which routes through Todoist's quick-add parser. Useful task flags: - `--stdin` on `task add` reads the task description from stdin; on `task quickadd` (and the top-level `td add`) it reads the full natural-language text from stdin. diff --git a/src/commands/task/index.ts b/src/commands/task/index.ts index 21bd4b0..3858c13 100644 --- a/src/commands/task/index.ts +++ b/src/commands/task/index.ts @@ -235,8 +235,7 @@ Notes: Notes: --due is sent verbatim as the task's due_string, with the same caveats as "task add --due": the server's due_string parser does not unpack some more - complex clauses (i.e. "starting "). To move the next occurrence of a - recurring task without changing its recurrence rule, use \`td task reschedule\`.`, + complex clauses (i.e. "starting ").`, ) const moveCmd = task diff --git a/src/commands/task/task.test.ts b/src/commands/task/task.test.ts index e3b2c18..5ccf824 100644 --- a/src/commands/task/task.test.ts +++ b/src/commands/task/task.test.ts @@ -2768,3 +2768,37 @@ describe('task (no args)', () => { stdoutSpy.mockRestore() }) }) + +describe('task add/update --due help text', () => { + async function captureHelp(args: string[]): Promise { + const program = createProgram() + const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true) + + await expect(program.parseAsync(['node', 'td', ...args])).rejects.toThrow() + + const output = stdoutSpy.mock.calls.map((c) => String(c[0])).join('') + stdoutSpy.mockRestore() + return output + } + + it('task add --help documents the verbatim caveat and points to quickadd', async () => { + const output = await captureHelp(['task', 'add', '--help']) + + expect(output).toContain('--due') + expect(output).toContain('Notes:') + expect(output).toContain('sent verbatim') + expect(output).toContain('"starting "') + expect(output).toContain('quick-add parser') + }) + + it('task update --help documents the verbatim caveat and refers back to task add --due', async () => { + const output = await captureHelp(['task', 'update', '--help']) + + expect(output).toContain('--due') + expect(output).toContain('Notes:') + expect(output).toContain('sent verbatim') + expect(output).toContain('"starting "') + expect(output).toContain('same caveats as') + expect(output).toContain('"task add --due"') + }) +}) diff --git a/src/lib/skills/content.ts b/src/lib/skills/content.ts index e468c08..713ac96 100644 --- a/src/lib/skills/content.ts +++ b/src/lib/skills/content.ts @@ -137,7 +137,7 @@ Choosing between \`task add\` and \`task quickadd\`: - Use \`td task add\` when you need flags that Quick Add syntax can't express (\`--deadline\`, \`--description\`, \`--parent\`, \`--duration\`, \`--uncompletable\`, \`--order\`), when the text is being composed programmatically, or when you need explicit \`id:\` / URL references for project/section/parent. - \`td task quickadd\` supports \`--stdin\`, \`--json\`, and \`--dry-run\` only; everything else is embedded in the text. - The top-level \`td add \` is a human shorthand for \`td task quickadd\` — same parser, same flag surface (\`--stdin\`, \`--json\`, \`--dry-run\`). Agents should prefer \`td task quickadd\` / \`qa\` for discoverability alongside the other task subcommands. -- \`--due\` on \`task add\` / \`task update\` is **sent verbatim** to the API as \`due_string\` — the CLI does not parse or rewrite it. The server's \`due_string\` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** unpack some more complex clauses (i.e. \`starting \`). Please use \`td task quickadd ""\`. +- \`--due\` on \`task add\` / \`task update\` is **sent verbatim** to the API as \`due_string\` — the CLI does not parse or rewrite it. The server's \`due_string\` parser handles simple inputs ("2026-06-01", "tomorrow", "every Monday") but does **not** unpack some more complex clauses (i.e. \`starting \`). For complex natural-language input on new tasks, prefer \`td task quickadd\`, which routes through Todoist's quick-add parser. Useful task flags: - \`--stdin\` on \`task add\` reads the task description from stdin; on \`task quickadd\` (and the top-level \`td add\`) it reads the full natural-language text from stdin.