Skip to content

Capture syntax

bob capture reads one draft and writes tasks, bullets, reference jobs, or Pomodoro session changes. This page is the token lookup. The full grammar and refusal messages live in the Capture contract.

Examples below were captured from this checkout's bob in an isolated HOME/XDG_* tree, a synthetic vault, BOB_NOW=2026-10-20 12:00:00, TZ=UTC, and BOB_REF_JOBS_KICK=off. Copy the bundled demo with the first Bob day tutorial when you need that fixture. Session examples need today's daily note with an open ## Pomodoros entry. Dependency writes need Obsidian Tasks settings that define Blocked as ?. Quote tokens in Zsh (*, !, ?, and # glob). Zsh's EQUALS expansion rewrites a word that starts with =, so bob capture =focus fails unquoted: always single-quote ('=focus', 'x =focus'). Network, clipboard, sync, and hook execution are not used here.

Token index

Each row is a distinct operation. Equivalent spellings share a heading. Incomplete picker input is labeled picker and is refused if you submit it.

Token Meaning Heading
Bare public URL Queue a reference job under mac_inbox Bare public URL
URL @route / @route URL Queue the reference under that route URL @route
URL % / URL % @route Queue with companion audio URL %
-r route Force the URL (or task) route from the CLI -r
@@route Shared task destination for unrouted items @@route
@@route+block-id Shared parent-task destination @@route+block-id
@@template: Expand one capture per template row @@template:
@route Write a task to <route>.md @route
@route#Section Ordinary bullet under a matching heading @route#Section
@route# Ordinary bullet under any non-Tasks heading @route#
@route^block-id New open task with a user-authored block ID @route^block-id
@route:block-id New Next task plus a Pomodoro Task Link @route:block-id
@route:block-id#pomodoro Same, under a named Pomodoro @route:block-id#pomodoro
@route:block-id=<X> / @route:block-id=name Same, and start that session (@work:outline=bugs links under BUGS and starts it) @route:block-id=<X>
@route:block-id with no other text Link an existing task into today's ledger Link an existing task
^route:block-id Active-task spelling of a link ^route:block-id
:<query> Picker: choose any open task to link :<query>
/<query> Picker: choose any open task to open /<query>
+query Picker: choose a parent task +query
lone + Editor parent picker; Escape leaves +5m unchanged lone +
&note:block-id Captured task depends on that prerequisite &note:block-id
& / &query Picker: choose a prerequisite &
^route:block-id &note:id Add a prerequisite to an existing owner Add a prerequisite
*note:block-id Named open task depends on the captured task *note:block-id
*query Picker: choose a blocked task *query
^route:block-id *note:id Existing owner blocks an existing task Block from an existing owner
~note:block-id This task links to that task, with an optional label ~note:block-id
~note:block-id\|\|label Same link; the label hides from suggestions once the capture succeeds (a one-off) ~note:block-id
~~note:block-id That task links to this task, with an optional label ~~note:block-id
^route:block-id ~note:id Relate an existing owner Relate an existing owner
! / !query Picker: choose a task to complete !
!note:block-id Complete that existing open task !note:block-id
+[N] Extend today's current timed Pomodoro +[N]
-[N] Shorten today's current timed Pomodoro -[N]
++[N] Shift the running session later ++[N]
--[N] Shift the running session earlier --[N]
= / =<X> Start the next future Pomodoro, or restart =
=name Start a named Pomodoro (=bugs, same token as =#bugs) =name
~<K> Drop those queued Task Links from the start ~<K>
x Close today's running timed Pomodoro x
x!<M> Close, completing those Task Links x!<M>
x*<P> Close, parking those Task Links x*<P>
x~<K> Close, dropping those Task Links x~<K>
Work Log Log work onto the close's worked tasks Work Log
+2 x / x = Chain session operators on one line Operator chain
@route:block-id=x Link that task, then close @route:block-id=x
@route^block-id+ Create a project note @route^block-id+
:id Project-note child: name, Next, and link :id child
^id Project-note child: name only ^id child
@route+block-id Child bullet under an existing task @route+block-id
@route+block-id#section Child under that task's ALL-CAPS section @route+block-id#section
@route+block-id with no other text Ensure Next and move its Task Link Ensure Next
@route+block-id! Toggle that task's Pomodoro Task Link @route+block-id!
trailing # Plain-text note on the current Pomodoro trailing #
s:<N> Schedule N days from today; checkbox tasks start Blocked s:<N>
$ / $<N> Write priority N and roll a scheduled date $
% / %N / %header Capture clipboard content as child bullets %

Overloaded markers

These characters mean different things in different positions. Read them by what they attach to, not as one global operator.

Marker Context Meaning
+ +[N] alone Adjust the current Pomodoro
+ ++[N] alone Shift the running session later
+ +query / lone + Parent-task picker
+ @route+block-id Child under an existing task, or Ensure Next when the item has no other text
+ @route^block-id+ Create a project note
+ after #name Part of the Pomodoro name (BUGS+)
# trailing bare # Pomodoro note
# @route#Section Heading in that note
# @route+id#section with body text ALL-CAPS task section
# @route:id#pomodoro / =name Named Pomodoro (=bugs and =#bugs are one token)
@ @route… Route / destination token
@ @@route Draft-wide destination
@ x@<N> / =name@<X> Timing or keep-list typed last (=bugs@3 means =3#bugs)
@ URL @route Explicit reference parent
* *note:block-id Blocked-task (star) link
* x* Park Task Links on close
* line-initial bare * Never a blocked-task marker
~ ~note:block-id[\|"label"], ~note:block-id\|\|"label" Relation link (~); || hides the label from suggestions once the capture succeeds
~ x~ / =~ Drop Task Links on close / drop on start
~ ~/path, ~5m, mid-line ~x:y Never a relation marker
~~ ~~note:block-id[\|"label"] Reverse relation link (~~)
~~ ~~old~~ Strikethrough, never a relation marker
! !note:block-id Complete an existing task
! x! Complete Task Links on close
% URL % Request companion audio
% trailing % Clipboard capture

Contract: Grammar at a glance.

Mnemonic: ~ on its own ties two tasks; ~ inside x / = drops.

= then a word starts that Pomodoro; # only after a number. =bugs and =#bugs are one token; write # only when a number comes first (=3#bugs) or the name starts with a digit or reads as a close (=#2fa, =#x2).

Incomplete input versus executable syntax

A picker token is a draft state, not a capture. bob capture and bob capture-parse refuse it with a teaching error until the selection becomes a concrete @route:id, !note:id, &note:id, *note:id, ~note:id, ~~note:id, or @route+id. Session tokens that still need a number or name (x~, =~, x@, =#) are the same kind of incomplete input. A bare =name start is complete (=bugs); only the empty =# needs pomodoro_name.

A signed count with extra prose (+2 more, =3 more) is an error, not a task. A sign or = followed by prose (Plan +5, = foo) stays ordinary task text. Capital X never closes; X *job:cleanup is how you capture a task that must start with the word x.

Retired and rejected forms

These are not valid commands. Bob teaches the replacement.

Typed What happens
==, ==<X>, ==#name Retired restart/swap; write one = (=, =3, =bugs)
==x Error: close with x
@route:id+ / @route:id+#pomodoro Retired project-note forms; write @route^id+
@route::id Retired ordinary block-ID form; write @route^id
#now / a free #tag next to @route Retired; heading and Pomodoro # attach to the route token
x#bugs Error: x always closes the running session; write x =bugs to close then start

Contract: Retired close spellings.

Routes and destinations

@route

Write a #task into <route>.md at the vault root. Route names are A–Z a–z 0–9 _ -, lower-cased. The default without a route is mac_inbox.

bob capture 'Review the queue input'
Output
✓ captured  mac_inbox.md
  - [ ] #task Review the queue input [created::2026-10-20]
○ #task Review the queue input Oct 20, 2026

Contract: Routing and insertion.

@@route

A draft-wide destination for otherwise-unrouted items. It may sit anywhere. A plain @@route also routes default (routeless) refs as source global. @@route+block-id keeps refs as tasks.

bob capture 'Buy milk @@work'

Contract: Global destination declaration.

@@route+block-id

Same declaration, but unrouted items become direct children of that existing task.

bob capture 'Check the parser @@work+prep'
Output
✓ captured  work.md
  under [ ] Prep the intake  ^prep
  - Check the parser

Contract: Global destination declaration.

@route#Section

Write an ordinary bullet (no checkbox) into a matching non-Tasks heading.

bob capture 'Remember the timeout @notes#Ideas'
Output
✓ captured  notes.md
  - Remember the timeout [created::2026-10-20]

Contract: Section bullets.

@route#

Same, targeting any non-Tasks heading. Generated status-group headings under Tasks are never bullet targets.

bob capture 'Remember the timeout @notes#'
Output
✓ captured  notes.md
  - Remember the timeout [created::2026-10-20]

Contract: Section bullets.

-r / -r route

--route NAME forces NAME.md and keeps typed @tokens literal. On a bare URL it queues the reference under that route with source explicit.

bob capture -r work -- 'https://example.com/article'

Contract: Command-line options and Saving links.

URL routing and audio

Bare public URL

When the URL routing policy admits it, a bare public URL queues a background reference job under mac_inbox. The confirmation says reading queue; the job files the note later. -R, --no-ref keeps it a task.

bob capture --dry-run 'https://example.com/article'
Output
[dry-run] ok would queue  example.com/article → mac_inbox
  new to your library · reading task lands in mac_inbox.md · file it later
bob capture --dry-run --no-ref 'https://example.com/article'
Output
[dry-run] ok would capture  mac_inbox.md
  - [ ] #task https://example.com/article [created::2026-10-20]

Contract: Saving links to your reading queue.

URL @route

Queue the reference under that route (aliases resolve; source explicit). @route URL is the same.

bob capture --dry-run 'https://example.com/article @work'
Output
[dry-run] ok would queue  example.com/article → work
  new to your library · reading task lands in work.md · file it later

Contract: Saving links to your reading queue.

URL %

Queue the reference with companion audio (ref.listen true). Parent selection matches a bare URL: default, @@route global, or -r. Exact standalone @ remains a compatible listen alias. URL % @route / @route URL % sets an explicit parent.

bob capture --dry-run 'https://example.com/article % @work'
Output
[dry-run] ok would queue  example.com/article → work
  new to your library · reading task lands in work.md · file it later · listen

Contract: Saving links to your reading queue.

Templates

@@template:

The first line is @@template:. The following {N} template captures exactly like its expansion: one item per later nonblank row ({0}-only templates can pack several values on one line). -E, --expand prints the expansion and exits before vault access.

printf '%s\n' '@@template:' 'Call {0} @work' '' 'Alex' 'Sam' | bob capture -E
Output
Call Alex @work

Call Sam @work

A successful real @@template: capture saves the template block (not the rows) on the shelf. Manage it with bob capture-templates.

Editors that run bob capture-rewrite, including the Mac capture panel, turn a typed @@@ at the very start of a draft into the @@template: header and put the caret on the next line.

Contract: Bulk templates.

Block IDs and linked tasks

@route^block-id

Create an ordinary open task with that block ID.

bob capture 'Write an outline @work^outline'
Output
✓ captured  work.md
  - [ ] #task Write an outline [created::2026-10-20] ^outline
○ #task Write an outline Oct 20, 2026 ^outline

Contract: Task with a requested block ID.

@route:block-id

Create a Next ([*]) task and a Pomodoro Task Link. A future s:<N> (N ≥ 1) starts Blocked ([?]) and unlinked.

bob capture 'Write an outline @work:outline'

Contract: Pomodoro-linked tasks.

@route:block-id#pomodoro

Same, linked under a matching named open Pomodoro, or a new named future Pomodoro.

bob capture 'Write an outline @work:outline#focus'
Output
✓ captured  work.md
  - [*] #task Write an outline [created::2026-10-20] ^outline
  Linked under FOCUS

Contract: Pomodoro-linked tasks.

@route:block-id=<X> / @route:block-id=name

Same, and atomically start the selected session. <X> uses five-minute units: =3 means 15 minutes; empty = means 25 minutes. A bare suffix name (@work:outline=bugs) means @work:outline#bugs=.

bob capture '@work:outline=3'
bob capture '@work:outline=bugs'
Suffix on @work:outline or ^work:outline Session and timing
=focus@3 FOCUS, 15 minutes; equivalent to =3#focus or #focus=3
=focus@-2 FOCUS, 25 minutes; start is now minus 10 minutes, rounded up to a five-minute boundary
=#2fa A name starting with a digit, using the explicit # form
=focus@ Incomplete: timing is still needed
=3#focus@2 Rejected: timing appears twice

A link-and-start fills an open () placeholder or creates a started session. It refuses while any timed Pomodoro in today's daily note is open. The whole-item =name start has separate restart and swap behavior.

Contract: Starting the session atomically.

Link the existing ^block-id task in route.md into today's ledger. No new task is created. A task Blocked only by its own prerequisites drops those links first, then the gesture runs. =<X> starts the resolved session.

bob capture '@work:outline'
Output
✓ link  work.md
  [ ] → [*]  Write an outline  ^outline
  Linked under WORK
● #task Write an outline [fresh:: 2026-10-20] Oct 20, 2026 ^outline

The Task Link is a dedicated child under the open Pomodoro:

- [ ] () — WORK
    - [[work#^outline]]

Contract: Linking and starting existing tasks.

^route:block-id

Identical execution to the @ link forms. ^ is the active-task spelling and completes In Progress and Next tasks when that is the gesture.

bob capture '^work:outline'
Output
✓ link  work.md
  [ ] → [*]  Write an outline  ^outline
  Linked under WORK

Contract: Linking and starting existing tasks.

Task pickers

:<query>

Incomplete: pick any open task to link. capture-parse needs task_link. Accepting inserts @route:block-id. Execution never captures the query.

bob capture ':queue'
Output
bob capture: capture item 1 starting on line 1: `:queue` opens the task picker; pick a task to insert its `@<route>:<block-id>` link, or write the link yourself (for example `@sase:deep-fix`)

Contract: Picking any open task with ':'.

/<query>

Incomplete: pick any open task to open in Obsidian. capture-parse needs task_goto. Accepting opens the task; execution never captures it.

bob capture '/queue'
Output
bob capture: capture item 1 starting on line 1: `/queue` opens the Go To picker; pick a task to open it in Obsidian — it never captures

Contract: Going to a task with '/'.

+query

Incomplete: choose a parent task. capture-parse needs task_parent. Accepting inserts @route+block-id. Terminal prose + is the same picker.

bob capture-parse -f json -- '+prep'
Output (excerpt)
{"ok":true,"schema_version":1,"mode":"incomplete","needs":["task_parent"],"body":""}

This is a draft state, not a successful capture.

Contract: Sub-bullets under existing tasks.

lone +

Opens the same parent picker in an editor. Escape leaves the valid +5m Pomodoro adjustment unchanged. A submitted lone + is the five-minute adjustment, not this picker.

bob capture --dry-run '+'
Output
[dry-run] ok would adjust  2026/20261020.md
  WORK 1200-1215 (15m) to 1200-1220 (20m), +5m at line 8

Contract: Grammar at a glance.

&note:block-id

The captured (or explicitly selected) task depends on note.md's ^block-id task. Nested notes use &projects/foo:bar; notes with spaces use &"Shopping List":bar.

bob capture 'Review the intake queue @work^queue &work:prep'
Output
✓ captured  work.md
  - [?] #task Review the intake queue [created::2026-10-20] [id:: work__queue] [dependsOn:: work__prep] ^queue
  ⛓ depends on "Prep the intake"  work.md ^prep  [ ] Ready
? #task Review the intake queue Oct 20, 2026 [id:: work__queue] [dependsOn:: work__prep] ^queue

The source of truth is the managed Depends-On child, not the field:

- [?] #task Review the intake queue [created::2026-10-20] [id:: work__queue] [dependsOn:: work__prep] ^queue
    - ⛓️ **DEPENDS ON:** [[#^prep]]

A selected &note:block-id with no owner is incomplete (dependency_target): type task text or + to choose an existing owner.

Contract: Adding prerequisites with '&'.

& / &query

Incomplete: the prerequisite picker is still open (task_dependency). &query and &note: are the same incomplete family.

bob capture-parse -f json -- '&prep'
Output (excerpt)
{"ok":true,"schema_version":1,"mode":"incomplete","needs":["task_dependency"]}

This is a draft state, not a successful capture.

Contract: Adding prerequisites with '&'.

^route:block-id &note:id

Add a prerequisite to an existing open owner. ^route:id is a bare owner alias here and does not create a Pomodoro link. &note:id ^route:block-id is the same.

bob capture '^work:queue &work:prep'
Output
✓ related  work.md
  [ ] → [?]  Review the intake queue  ^queue
  ⛓ depends on "Prep the intake"  work.md ^prep  [ ] Ready
? #task Review the intake queue Oct 20, 2026 [id:: work__queue] [dependsOn:: work__prep] ^queue

Contract: Relating existing tasks.

*note:block-id

The named open task depends on the captured (or explicitly selected) task. Quote * on the shell. Nested notes and quoted names match &.

bob capture 'Ask for the dump @work^dump *work:queue'
Output
✓ captured  work.md
  - [ ] #task Ask for the dump [created::2026-10-20] [id:: work__dump] ^dump
  🧱 blocks "Review the intake queue"  work.md ^queue  [?] Blocked

A line-initial bare * is never this marker. A selected star with no owner is incomplete (dependency_target).

Contract: Blocking existing tasks with '*'.

*query

Incomplete: the blocked-task picker is still open (blocked_task). X * and X *note: are the same family after other text on the line.

bob capture-parse -f json -- 'Ask for the dump *queue'
Output (excerpt)
{"ok":true,"schema_version":1,"mode":"incomplete","needs":["blocked_task"]}

This is a draft state, not a successful capture.

Contract: Blocking existing tasks with '*'.

^route:block-id *note:id

Make the named open task depend on the existing owner. ^route:id is a bare owner alias and does not create a Pomodoro link.

bob capture '^work:dump *work:queue'
Output
✓ related  work.md
  🧱 blocks "Review the intake queue"  work.md ^queue  [?] Blocked

Contract: Relating existing tasks.

The captured (or explicitly selected) task links to note.md's ^block-id task, with an optional source-side label. Nested notes use ~projects/foo:bar; notes with spaces use ~"Shopping List":bar; labels use ~note:id|see or ~note:id|"follow-up of". Quote ~ on the shell. The target may be open, closed, in any active note, a daily note, or done/ (a mirror is skipped for archived and previous-daily targets). Relations are inert: no status, lane, Pomodoro, or field changes. A || spelling (~note:id||see, ~note:id||"follow-up of") stores the identical label and hides it from suggestions once the capture succeeds; a third pipe is a teaching error (use || to hide a label, | to name one).

The label pipe attaches directly to the target. Editors using capture-rewrite with --cursor just after a newly typed pipe can attach it across spaces or tabs (~note:id | → ~note:id|, ~note:id || → ~note:id||); direct bob capture input does not apply that rewrite.

bob capture 'Fix flaky capture retries ~sase:retry-design|see'
bob capture 'Fix trip ~work:foo||prompted-by'
Output
✓ captured  mac_inbox.md
  🪢 links to "Design a retry policy" · see   sase.md ^retry-design  [ ] Ready

A selected ~note:block-id with no owner is incomplete (dependency_target): type task text or + to choose an existing owner. A bare ~ / ~query / ~note: needs relation_target; a label position (~note:id|, ~note:id||) needs relation_label.

Contract: Linking tasks with '~' and '~~'.

The named task links to the captured (or explicitly selected) task. The captured task is the target, so it is always minted; the named task's LINKS line must be editable (the same refusal set as *: daily notes, done/, _templates, _generated, _conflicts, dot-directories, fenced code).

bob capture 'Write the outline ~~sase_blog:prj|"next step"'

Contract: Linking tasks with '~' and '~~'.

^route:block-id ~note:id

Relate an existing owner. ^route:id is a bare owner alias here and does not create a Pomodoro link. ~note:id ^route:block-id and ^route:block-id ~~note:id are the same family.

bob capture '^bob:fix-retries ~sase:retry-design|see'

Contract: Linking tasks with '~' and '~~'.

Completion

! / !query

Incomplete: pick an open task to complete (task_complete). Accepting inserts !<note>:<block-id>. !fix, !sase:, and !"Shopping List are still typing the query.

bob capture '!'
Output
bob capture: capture item 1 starting on line 1: `!` opens the task picker; pick a task to insert its `!<note>:<block-id>` token, or write one yourself (for example `!sase:fix-flaky`)

Contract: Completing tasks with '!'.

!note:block-id

Complete that existing open task. The token must be the whole capture item. bob capture closes it exactly as x!N would, retires its Task Links, and unblocks dependents. Extra text is invalid_task_complete.

bob capture '!work:prep'
Output
✓ completed [ ] → [x] Prep the intake  work.md ^prep
✓ #task Prep the intake Oct 20, 2026 Oct 20, 2026 ^prep

Contract: Completing tasks with '!'.

Adjustments, shifts, and session start

+[N]

Extend today's current timed Pomodoro by N five-minute units (default 1). +5 is +25 minutes. The item must contain only the signed count.

bob capture '+2'
Output
✓ adjusted  2026/20261020.md
  WORK 1200-1215 (15m) to 1200-1225 (25m), +10m at line 8

Contract: Adjusting the current Pomodoro.

-[N]

Shorten by N units. Bare - is one unit (−5 minutes). Extra text after a bare sign stays prose (- foo).

bob capture --dry-run '-2'
Output
[dry-run] ok would adjust  2026/20261020.md
  WORK 1200-1225 (25m) to 1200-1215 (15m), −10m at line 8

Contract: Adjusting the current Pomodoro.

++[N]

Shift the running timed Pomodoro N units later, keeping its duration. ++3 is +15 minutes. The item must contain only the operator. +++ stays prose.

bob capture --dry-run '++3'
Output
[dry-run] ok would shift  2026/20261020.md
  WORK 1200-1225 (25m) to 1215-1240 (25m), +15m at line 8

Contract: Shifting the current Pomodoro.

--[N]

Shift N units earlier. Bare -- is one unit.

bob capture --dry-run '--'
Output
[dry-run] ok would shift  2026/20261020.md
  WORK 1200-1225 (25m) to 1155-1220 (25m), −5m at line 8

Contract: Shifting the current Pomodoro.

= / =<X>

Start today's next future Pomodoro now with se<X> timing (= is 25 minutes, =3 is 15, =-2 is 25 minutes with a 10-minute offset), or restart the running session with that timing. A restart prints an override warning. The item must contain only the start token.

bob capture '=3'
Output
✓ started  2026/20261020.md
  WORK 1200-1215 (15m) at line 8
  - [ ] (**1200-1215** [t:: 15m]) — WORK
  1 [*] Write an outline work.md ^outline

Contract: Starting the next Pomodoro.

=name / =<X>#pomodoro

Start the named Pomodoro now (=focus, same token as =#focus; equivalent spellings =<X>#pomodoro, =name@<X>). An open match (whole slug, else prefix) starts in place; a completed match starts a new session with that name ("again"); otherwise a new named session is created and started. While another session runs, a different named target swaps in. Empty <X> keeps the running ledger. Write # only when a number comes first (=3#bugs) or the name starts with a digit or reads as a close (=#2fa, =#x2).

bob capture '=focus'
Output
✓ started  2026/20261020.md
  FOCUS 1200-1225 (25m) at line 8
  - [ ] (**1200-1225** [t:: 25m]) — FOCUS

Contract: Starting a named Pomodoro.

~<K>

On a start token, drop the queued Task Links in <K> (=~2, =3~2,4, =bugs~2). ~ always comes last. =~ and =~2, are incomplete (pomodoro_start_task). A start cannot complete links; ! belongs on x.

bob capture --dry-run '=~2'
Output
[dry-run] ok would start  2026/20261020.md
  WORK 1200-1225 (25m) at line 10
  dropped link 2
bob capture-parse -f json -- '=~'
Output (excerpt)
{"ok":true,"schema_version":1,"mode":"incomplete","needs":["pomodoro_start_task"]}

Contract: Starting the next Pomodoro.

Closing, outcomes, and work logs

x

Close today's running timed Pomodoro. Lowercase x only. Selection pieces *, !, ~, and @ may appear in any order. <N> keeps those numbered Task Links in progress; a lone 0 means none. @<N> is the same keep-list typed last (x!1@2 means x2!1).

bob capture --dry-run 'x'
Output
[dry-run] ok would close WORK 1200-1225 → 1200-1200 (0m, −25m) · 2026/20261020.md line 8
  1 [*] → [/] Write an outline work.md ^outline
  next: WORK (created) at line 10 · carries 1 link

A mid-body x stays prose (Plan x). xx, x-ray, and capital X never close.

Contract: Closing the running Pomodoro.

Complete those numbered Task Links. A present-but-empty ! completes every remaining numbered link (x!). x!1 narrows to task 1.

bob capture --dry-run 'x!'
Output
[dry-run] ok would close WORK 1200-1225 → 1200-1200 (0m, −25m) · 2026/20261020.md line 8
  1 [*] → [x] Write an outline work.md ^outline
✓ #task Write an outline Oct 20, 2026 Oct 20, 2026 ^outline

Contract: Choosing each Task Link's outcome.

Park those links (normal work without carrying forward). Empty * parks every remaining numbered link (x*).

bob capture --dry-run 'x*'
Output
[dry-run] ok would close WORK 1200-1225 → 1200-1200 (0m, −25m) · 2026/20261020.md line 8
  1 [*] → [ ] Write an outline work.md ^outline

Contract: Choosing each Task Link's outcome.

Drop those links (removed, not carried, not started). x~ is incomplete (pomodoro_close_task).

bob capture --dry-run 'x~1'
Output
[dry-run] ok would close WORK 1200-1225 → 1200-1200 (0m, −25m) · 2026/20261020.md line 8
  1 [*] dropped  Write an outline work.md ^outline
bob capture-parse -f json -- 'x~'
Output (excerpt)
{"ok":true,"schema_version":1,"mode":"incomplete","needs":["pomodoro_close_task"]}

Contract: Choosing each Task Link's outcome.

Work Log

One entry may sit on the close line; several use child bullets. With no number, the line logs to the first task the close works. A leading number names the task (x 1 3 bugs fixed logs 3 bugs fixed to task 1). x 1 without text is incomplete (pomodoro_close_log_text). Bullets are numbered all or none. A two-space bullet nests an undated detail.

bob capture --dry-run 'x wired the lexer'
Output
[dry-run] ok would close WORK 1200-1225 → 1200-1200 (0m, −25m) · 2026/20261020.md line 8
  1 [*] → [/] Write an outline work.md ^outline
  logged: wired the lexer

Contract: Logging work while closing.

+2 x / x =

Whitespace-separated session tokens on one line run left to right exactly like blank-line items (+2 x, x =, x =bugs, =bugs +2). An inline Work Log entry sits right after its close, with operators trailing (x wired the lexer =). Child bullets attach to that line's x.

bob capture --dry-run '+2 x'
Output
[dry-run] ok would adjust then close  2026/20261020.md
  WORK 1200-1215 (15m) to 1200-1225 (25m), then close

Contract: Chaining session operators on one line.

Put that existing task into the running session, then close it. Own-prerequisite Blocked targets drop those links first. The = is the suffix separator; numbers refer to the post-link lineup. ^route:block-id=x is the active-task spelling. With extra text, create the new linked task and then close.

bob capture --dry-run '@work:outline=x'
Output
[dry-run] ok would link then close  work.md ^outline
  [ ] → [*] Write an outline, then close WORK

Contract: Closing the running Pomodoro.

Project captures

@route^block-id+

Create the project note <route>_<block_id>.md (hyphens in the block ID become underscores). The ^prj task is never linked and the daily note is untouched. #pomodoro on this form sends :id Task Links under that named open Pomodoro.

bob capture '@work^intake-flow+' 'Ship the intake flow'
Output
✓ captured  work_intake_flow.md
  parent  [[work]]
  - [ ] #task #prj Ship the intake flow #hide ^prj
  1 task · sections —
○ #task #prj Ship the intake flow #hide ^prj

Contract: Project notes.

:id child

On a project-note capture, - <task> :<task-id> names the task, makes it Next ([*], or [?] when scheduled), and links [[<stem>#^<task-id>]] into the current/next Pomodoro, or the #pomodoro one.

bob capture '@work^intake-flow+' 'Ship the intake flow' '- Name the stages :stages'
● #task Name the stages Oct 20, 2026 ^stages

The child is Next and linked from today's ledger as [[work_intake_flow#^stages]].

Contract: Project notes.

^id child

On a project-note capture, - <task> ^<task-id> names the task only:

○ #task Named child Oct 20, 2026 ^named-child

Contract: Project notes.

@route+block-id

Ordinary child bullet under an existing task.

bob capture 'Check the parser @work+prep'
Output
✓ captured  work.md
  under [ ] Prep the intake  ^prep
  - Check the parser

Contract: Sub-bullets under existing tasks.

@route+block-id#section

Child bullet under an ALL-CAPS section of that task. With no other text, # names a Pomodoro instead (Ensure Next / move).

bob capture 'Need a parser @work+prep#requirements'
Output
✓ captured  work.md
  under [ ] Prep the intake  ^prep  REQUIREMENTS
  - Need a parser

Contract: Sub-bullets under existing tasks.

@route+block-id with no other text

@route+block-id with no other text ensures the task is Next and relocates its existing open-Pomodoro Task Link to today's implicit current/next Pomodoro. #pomodoro moves the subtree to that named open Pomodoro, creating a named future Pomodoro when missing. Own-prerequisite Blocked targets drop those links first.

bob capture '@work+outline'
Output
✓ ensure-next  work.md
  [ ] → [*]  Write an outline  ^outline
  moved Task Link under WORK
● #task Write an outline Oct 20, 2026 ^outline

Contract: Task Link toggle.

@route+block-id!

Toggle that task's Pomodoro Task Link: link it when unlinked (Ready and own-prerequisite Blocked rise to Next; other Blocked stay unlinked) or unlink it when linked (lane unchanged). Removing a link never lowers Next or Pending.

bob capture '@work+outline!'
Output
✓ toggle  work.md
  [ ] → [*]  Write an outline  ^outline
  linked under WORK

Contract: Task Link toggle.

Pomodoro notes

trailing #

A trailing bare # is a plain-text note on a Pomodoro, not a routed task.

bob capture 'remembered the timeout #'
Output
✓ captured  2026/20261020.md
  under (**1200-1230** [t:: 30m]) — WORK
  - remembered the timeout

Contract: Pomodoro notes.

Scheduling, priority, and clipboard

s:<N>

[scheduled::] N days from today, recognized in the terminal token region. Checkbox-bearing captures start Blocked ([?]), including s:0.

bob capture 'File taxes @work s:1'
Output
✓ captured  work.md
  - [?] #task File taxes [created::2026-10-20] [scheduled::2026-10-21]
? #task File taxes Oct 20, 2026 Oct 21, 2026

Contract: Scheduling and priority.

$ / $<N>

Write priority level N (bare $ = 1) and roll a scheduled date in that level's window. A missing config file is an error; there is no built-in default. An explicit s:<N> wins the date; $<N> still writes priority. Mid-line amounts stay text (Pay $3 to Bob).

bob capture-parse -f json -- 'File taxes @work $1'
Output (excerpt)
{"ok":true,"schema_version":1,"mode":"task","route":"work","body":"File taxes"}

A configured P1 window writes the priority field and rolls [scheduled::] in that window. This illustration is the resulting task text, not a captured envelope:

? #task File taxes Oct 20, 2026 [priority:: high] Oct 21, 2026

Contract: Scheduling and priority.

% / %N / %header

Capture clipboard content as child bullets. %, %N, and %header are the spellings. -n, --no-clip keeps trailing % literal. -c, --clip forces clipboard capture. Clipboard backends are stubbed in this isolated fixture, so the example keeps % literal.

bob capture --no-clip 'Keep the percent @work %'
Output
✓ captured  work.md
  - [ ] #task Keep the percent % [created::2026-10-20]

Contract: Clipboard.

Tested rolling snapshot bob-cli 41afa14 bob-mac-capture 556bb67 bob-plugins af1e691 Built 2026-10-24T22:34:13Z (zensical) build.json