Skip to content

bob capture-parse

Explain what in-progress capture text currently means

bob capture-parse --help
Report the authoritative capture grammar's view of TEXT.

The grammar parse is lexical and read-only. For Pomodoro-name spans, the command also best-effort reads the configured vault's theme catalog and adds theme refs; if the catalog is unavailable, parsing still succeeds without those refs. It never writes to the vault or reads the clipboard, and takes no --bob-dir. A '%' clipboard marker still succeeds.

It reports the normalized body, the overall capture mode, the resolved route, section, and block ID, which parts a picker still has to supply, an optional global_destination object for a @@<route> or @@<route>+<block-id> declaration token anywhere in the draft, ordered blank-line-separated item summaries and ranges after inheritance, the UTF-8 byte spans of every recognized token, Obsidian wikilink component spans, every authored sub-bullet's normalized body plus depth, an optional pomodoro_start object for a `@<route>:<block-id>[#<name>]=<X>` start suffix (including the short `=name` suffix, e.g. `@work:outline=bugs`), an optional pomodoro_adjust object for a whole-item `+[N]`/`-[N]` adjustment, an optional pomodoro_shift object for a whole-item `++[N]`/`--[N]` shift, an optional pomodoro_close object for a `=x` link close suffix or whole-item close, and structured diagnostics. Wikilink highlighting is syntax-only and never touches the vault. Byte offsets index the original TEXT before whitespace normalization, are half-open [start, end), never overlap, and always land on a character boundary.

TEXT accepts the same draft 'bob capture' does: one or more blank or whitespace-only physical lines separate capture items, and each item keeps the established authored-bullet grammar. A whitespace-separated line of only session operators yields one `items[]` entry per token, each `range` covers its token, and all entries share `line_start`/`line_end`. Within an item, the first physical line is the parent, later column-zero '-'/'*'/'+' lines become first-level authored children, and later lines prefixed by exactly two ASCII spaces become nested authored children. Separator rows have no spans, diagnostics, or completion fields. Incomplete interactive markers are valid input, not errors, on any line: '@', '@#', '@#Ideas', '@route#', '@^', '@route^', '@+', '@route+', '@route+id#', '@:', '@route:', '@route:id#', '@route:#name', and the legacy '@!' aliases all report mode 'incomplete' plus what they still need. A valid `=<X>` start suffix on a `@<route>:<block-id>[#<name>]` marker reports mode 'pomodoro_task' with a `pomodoro_start` object (`raw` plus 5-minute `duration_units`/`offset_units`) and a `pomodoro_start` span covering the `=` and `<X>` bytes; the Pomodoro-name span always ends before the `=`. An invalid suffix is an `invalid_pomodoro_start` diagnostic instead. A whole-item `+[N]`/`-[N]` adjustment (for example `+5` extends today's current timed Pomodoro by 25 minutes, `-` shortens it by 5 minutes) reports mode 'pomodoro_adjust' with a `pomodoro_adjust` object (`raw` plus sign and 5-minute `units`, defaulting to 1 when the count is omitted) and a `pomodoro_adjust` span covering only the signed token; the parse stays purely lexical and never guesses current ledger times. A whole-item `++[N]`/`--[N]` shift (for example `++3` moves today's running Pomodoro 15 minutes later, `--` moves it 5 minutes earlier) reports mode 'pomodoro_shift' with a `pomodoro_shift` object (`raw`, direction as `later`, and 5-minute `units` defaulting to 1) and a `pomodoro_shift` span covering only the operator token. A `@@` declaration still routes ordinary items in the same draft but never turns an operator into a task or changes its destination. Invalid standalone counts (`+0`, `++0`, overflow) and malformed operator-first items (extra text, markers, or child lines) report an `invalid_pomodoro_adjustment` (one sign) or `invalid_pomodoro_shift` (two signs) diagnostic with a useful range, while `bob capture` keeps its strict execution errors for the same text. Other nonmatching words retain normal task semantics: a bare sign run followed by more text (`- foo`, `-- aside`), a run longer than two or mixed (`+++`, `+-`), and mid-body tokens (`Plan +5`, `C++`) stay ordinary prose. An operator is an action and requests no route or task completion candidates. A whole-item `=`/`=<X>` start (for example `=` starts 25 minutes, `=3` starts 15 minutes, `=-2` starts 25 minutes with a 10-minute offset) reports mode 'pomodoro_start' with a `pomodoro_start` object (`raw` excludes `=`, plus 5-minute `duration_units`/`offset_units` and the additive `drop` list) and a `pomodoro_start` span covering the whole token (only `=<X>` when a `~<K>` drop part is present, with a `pomodoro_start_drop` span covering `~<K>` including the `~`); the human `start` line reads `=3#bugs~1,3 (15m, offset 0u · drop 1, 3)`. Every claimed doubled start (`==`, `==3#bugs`, `==#`, `==~`, `==#bugs=3`) reports mode 'pomodoro_start' with an `invalid_pomodoro_start` diagnostic over the token and a message naming its single-`=` rewrite. The `==x` family reports that it is not a close and teaches the bare `x` rewrite. Bare `==` followed by prose stays ordinary text, preserving Obsidian highlights. The parse stays purely lexical and never guesses current ledger times. A counted or drop-carrying token with extra text, markers, or child lines (`=3 more`, `=3x`, `=~2 more`), an exact token with child lines, and an oversized suffix report 'pomodoro_start' plus an `invalid_pomodoro_start` diagnostic (the extra text, the child line, or the token for overflow), while `bob capture` keeps its strict execution errors for the same text. A trailing `~<K>` drop list (`=~2`, `=3~2,4`, `=3#bugs~1`) starts the next session without those queued Task Links; a dangling `~`/`,` (`=~`, `=~2,`) reports mode 'incomplete' needing `pomodoro_start_task` with the partial spec, the spans typed so far, and one `interactive_placeholder` span over the separator, and every malformed list (a `0`, a duplicate, a second `~`, a `!`, a `#name` after the list, a bad character, an oversized number, or a space inside the list, which gets the no-spaces hint) reports `invalid_pomodoro_start` on the precise range. A named whole-item start (`=bugs`, `=3#bugs`) starts that Pomodoro: `section` carries the typed name with a `pomodoro_name` span over the name bytes only (the `#` is in no span; `=bugs` emits `pomodoro_start [0,1)` and `pomodoro_name [1,5)`), `=<X>#` with no name reports mode 'incomplete' needing `pomodoro_name` with an `interactive_placeholder` span over `#` and the partial `pomodoro_start` spec, and `x#name` reports mode 'pomodoro_close' with an `invalid_pomodoro_close` diagnostic over `#name`. A trailing `@<X>` timing (`=bugs@3` means `=3#bugs`, `=~2@3` means `=3~2`) reports the canonical spec with a second `pomodoro_start` span over `@<X>`; a dangling `@` reports `incomplete` needing `pomodoro_start_timing` with no spec. A whole-item `x[<N>][*<P>][!<M>][~<K>][@<N>]` close (lowercase `x` only, with `*`, `!`, `~`, and `@` in any order; a capitalized `X…` stays prose) reports mode 'pomodoro_close' with a `pomodoro_close` object (`raw` exactly as typed plus the additive `in_progress` list, null when no `<N>` was typed, the `park` list, the `complete` list, the `drop` list, the additive `park_all` and `complete_all` flags, and the `log` entries (`index`, `text`, `details`) in typed order (`index` is omitted for unnumbered bullets under a close without `<N>`/`*<P>` or with wildcard intent, which `bob capture` resolves after staging the running session lineup); and spans covering the `x` token (`pomodoro_close`), the `<N>` list including its commas (`pomodoro_close_in_progress`), the `*<P>` list including the `*` (`pomodoro_close_park`), the `!<M>` list including the `!` (`pomodoro_close_complete`), the `~<K>` list including the `~` (`pomodoro_close_drop`), and each numbered entry index (`pomodoro_close_log_index`; positional entries get no index span, and entry text renders as neutral prose but keeps its wikilink spans); the human `close` line reads `x1*2!3~4 (in progress 1 · parked 2 · complete 3 · drop 4 · defer the rest)`, with `in progress none` for `x0`, `park all` for `x*`, `complete all` for `x!`, `park all · complete 2` for `x*!2`, `in progress 1 · park all` for `x1*`, and a bare `x` for a plain close, and `log 2 'wired the lexer' (+1 detail)` for typed entries with details (`log 'wired the lexer'` with no number for entries resolved at execution). One entry may sit on the close line itself (`bob capture-parse -f json -- 'x wired it'`): it logs to the first task the close works, or to the leading number when one is typed. Under wildcard intent, the default index is omitted and Bob resolves it to the first eligible top-level worked Task Link. The item `body` stays the close token (`x`), the spec `log` carries the entry with an index only when lexically resolved, an explicit number gets a `pomodoro_close_log_index` span on the close line while the default gets none, and entry text keeps its wikilink spans. A dangling inline number (`bob capture-parse -f json -- 'x 2'`) reports mode 'incomplete' needing `pomodoro_close_log_text` with the partial spec and an `interactive_placeholder` span over the number instead of its index span. Several entries still use child bullets below the close (`- [<n>] <text>`, with two-space `  - <detail>` details nesting under their entry): bullets are numbered all or none, and unnumbered bullets log in order to the close's worked tasks (one worked task takes them all, otherwise bullet `i` logs to worked task `i`); a leading number is always a task number, so `- 2 bugs fixed` names task 2. Only the first token is an index and every backslash stays literal. A dangling bullet (`- 1`) reports mode 'incomplete' needing `pomodoro_close_log_text` with the partial spec (lists plus every complete entry with its details), the spans typed so far, and one `interactive_placeholder` span over each dangling number instead of its index span. A mixed-numbering bullet, too many unnumbered bullets, a close that works no task, a non-loggable index, a bad number, a block link, or a fence reports 'pomodoro_close' plus an `invalid_pomodoro_close` diagnostic on the precise range, and so does a bad inline entry (stray marker, misplaced operators, no-spaces join, Task Link ending, block link, fence, or mixing with bullets). Every malformed list (duplicates, explicit overlaps, competing empty wildcard groups such as `x*!`, a misplaced `0`, a second `!` or `~`, a bad character, an oversized number, or a space inside the lists, which gets the no-spaces hint) reports the same. A token ending in a dangling separator (`x1,`, `x~`, `x*1,`, `x!2,`, `x*~`) reports mode 'incomplete' needing `pomodoro_close_task`, with the partial spec, the spans typed so far, and one `interactive_placeholder` span over the separator. A trailing `*`/`!` is a wildcard instead of dangling, so `x*` and `x!` are valid. Two empty wildcard groups conflict and require numbers on at least one group. A same-line chain splits into one `items[]` entry per operator, with child lines attaching to the line's `x` (the last `x` when it closes twice); a chain whose `x` is not last nests its item ranges, so they never partially overlap. Other `x` tokens (`xx`, `xa`, `x-ray`, a capitalized `X…`), other doubled and bare starts followed by prose (`== foo`, `= foo`), and mid-body `x` stay ordinary prose. The retired `=x…`, `=*…`, and `=!…` whole-item spellings report 'pomodoro_close' plus an `invalid_pomodoro_close` diagnostic that names the bare `x…` rewrite. On link items the `=x…` suffix (the `=` separates it from the block ID) spans the same four span kinds after the route and block-ID spans: `@r:id=x1!2` and `^r:id=x1` stay 'pomodoro_link' (or 'pomodoro_task' with body text) and carry the spec, `^r:id=x1,` reports 'incomplete' needing `pomodoro_close_task`, while `#name=x…`, `s:<N>`/`$<N>`/`%` conflicts, project-note `=x`, the retired `=*`/`=!` link aliases, and malformed lists report `invalid_pomodoro_close` on the conflicting component or the precise list range. A `@@` declaration never applies to close, `=`/`=<X>`, or `=name` items, and neither is ever rewritten. A '@^id+' marker already carries the project-note intent: it reports mode 'project_note' with a 'route' need until the route is typed. A '@:id+' marker is the retired project-note form and reports a `retired_project_note_marker` diagnostic. In a project-note item, a trailing ` :id` / ` ^id` word on a first-level task bullet names that task: ` :id` additionally makes it Next and links it into the Pomodoro, reporting mode 'pomodoro_project_note' with a `project_task_link_marker` span over the `:` and a `project_task_block_id` span over the ID (` ^id` spans only the ID). Stripped bodies appear in `sub_bullets` with a parallel `sub_bullet_task_ids` array of `null` or `{"block_id", "link"}` entries, and `section` carries the marker's Pomodoro name. Misplaced, invalid, reserved, empty, or checkbox-carrying IDs report `misplaced_project_task_id` or `invalid_project_task_id`, a repeated ID reports `duplicate_project_task_id`, and a `#pomodoro` name with no ` :` task reports `unused_project_note_pomodoro`. A lone trailing `:` or `^` is an unfinished ID: mode 'incomplete' needing `block_id` with an `interactive_placeholder` span over the sigil and no diagnostic. Outside project-note items these words stay literal text. A solo '@route:block-id[#pomodoro][=<X>]' item links an existing task into today's Pomodoro ledger instead of creating one, and parses as 'pomodoro_link' with the same spans and `pomodoro_start` object; anything else on the item is an `invalid_pomodoro_link` diagnostic. The '^' spelling is identical except the marker resolves to `active_task_route` and `active_task_block_id` spans: '^', '^fragment', and '^route:' parse as 'incomplete' needing `active_task` with one `interactive_placeholder` span, '^route:block-id#' needs `pomodoro_name`, and near misses stay `pomodoro_link` with an `invalid_pomodoro_link` diagnostic. Anything else starting with '^' ('^_^', '^^', '^.') stays ordinary text, and '^' is only recognized as the first token of an item's first line. A single-token, single-line item starting with ':' is a task-picker query: it reports mode 'incomplete' needing `task_link` with one `interactive_placeholder` span over the whole token (sigil included) and no diagnostics, and it is never captured. A whole draft that is one `/` token (with no second `/`) reports 'incomplete' needing `task_goto` with one `interactive_placeholder` span; `bob capture` refuses it. A `+query` token that is the item's whole remaining body after `&`/`*` modifiers leave (leading `+ &foo:bar` or trailing `&foo:bar +`) is an unresolved parent-task selector: it reports `incomplete` needing `task_parent` with a placeholder over the full token, keeps the modifiers, and capture refuses it until a task is selected. An exact lone `+` remains the existing Pomodoro adjustment in parsing. An `&note:block-id` modifier names a prerequisite task link (`&projects/foo:bar`; quote notes with spaces: `&"Shopping List":bar`): one or more modifiers lead or trail the parent line (interleaving with destination, schedule, priority, and clipboard markers in either order) or close an authored child line, and each adds a per-item `dependencies` entry (`raw`, decoded `note` and `block_id`, plus its sigil-inclusive `range`) with `dependency_sigil`, `dependency_note`, and `dependency_block_id` spans. Body text keeps its established mode and gains a `dependency_target` (`new_task` for a task this capture would create); a bare `&note:block-id` with an explicit `@note+task-id` (or the narrow bare `@note:id` / `^note:id` aliases, leading or trailing) reports mode 'task_dependency' with an `existing_task` target. A bare existing owner with `$<N>`, `s:<N>`, `%…`, or child bullets reports a `relationship_needs_text` diagnostic. An ownerless `&note:block-id` reports mode 'incomplete' needing `dependency_target`, and a partial `&`, `&query`, or `&note:` needs `task_dependency` with a placeholder span over the typed query. A mid-line `&` between prose words (`Research & Development`, `R&D`), an `&` inside `[[wikilinks]]` or `` `code` ``, and `\&` (which leaves a visible `&...` in task text) stay literal. `bob capture` executes recognized modifiers through the staged batch writer (managed `DEPENDS ON` line plus derived field and status effects); a draft with prerequisites never captures without them. An additive `*note:block-id` modifier names an existing open task to block: the parsed task would become its prerequisite, reported separately in `blocked_tasks[]` with `blocked_task_sigil`, `blocked_task_note`, and `blocked_task_block_id` spans. It follows the same leading/trailing marker runs and ownership rules as `&`; `*` is also a partial picker need named `blocked_task`. A bare `*` opens that picker only as the last token after existing text, and line-initial `*` remains a Markdown bullet. Mid-line stars, `**bold**`, `*italic*`, and `5 * 3` stay literal. `bob capture` executes recognized `*` modifiers through the staged batch writer: the parsed task becomes a prerequisite of each named target (managed `DEPENDS ON` line plus derived field, status, consumption, and BLOCKS-mirror effects); a draft with blocked-task modifiers never captures without them. An additive `~note:block-id[|label]` modifier relates this task to another task (`~` links out, `~~` links back), optionally with a `|label` (quoted when it holds spaces: `|"follow up"`); each adds a per-item `relations[]` entry (`raw`, `direction`, decoded `note` and `block_id`, `label` with its `label_range`, plus its sigil-inclusive `range`) with `relation_sigil`, `relation_note`, `relation_block_id`, `relation_label_separator`, and `relation_label` spans, and any draft containing a `~` byte reports top-level `relation_grammar: 1` (drafts without `~` stay byte-identical). It follows the same leading/trailing marker runs and ownership rules as `&` (mid-line `~` stays body text; `~/path`, `~5m`, `~~old~~`, and `~~~x:y` stay literal with the documented guards; bare or partial `~` needs `relation_target`, and `~note:id|` needs `relation_label`); an ownerless `~note:block-id` reuses `dependency_target`, and `bob capture` executes recognized `~` / `~~` modifiers through the staged batch writer: it appends or relabels the source task's managed 🪢 LINKS line, mints any block IDs the edge needs, and stages the target's generated 🪢 LINKED FROM mirror; relations never block, consume, promote, or schedule anything. The retired '@route::...' spelling is a diagnostic directing users to '@route^...'; it is not an incomplete Pomodoro marker. A bare trailing '#' reports mode 'pomodoro_note' with no route, section, or block ID and an empty 'needs' list; combining it with an '@route' marker, 's:<N>', or '$<N>' on the same item reports a 'pomodoro_note_conflict' diagnostic instead of failing outright. An invalid marker component, a malformed continuation line, an orphaned nested bullet, an item emptied by marker removal, or a duplicate item-wide marker across lines becomes a diagnostic, so live editors keep a usable parse while 'bob capture' keeps its strict execution errors.

A second @@ declaration reports 'duplicate_global_destination' on every later declaration token while keeping the first declaration effective. An item that has both a local destination marker and the @@ declaration it owns reports a 'global_destination_shadowed' warning because the local marker wins for that item.

Complete and in-progress Obsidian wikilinks such as '[[note', '![[note]]', '[[note#Heading|Alias]]', and '[[#^block-id]]' add semantic delimiter, target, heading, block, and alias spans without changing capture routing.

Only a missing TEXT or a bad flag is an error; every other input succeeds. If TEXT is omitted and stdin is piped, it reads the complete piped stdin stream.

Usage: bob capture-parse [OPTIONS] [TEXT]...

Arguments:
  [TEXT]...
          Capture text; multiple args are joined with spaces; unknown `--flag` before `--` is rejected

Options:
  -f, --format <FORMAT>
          Output format: human or json

          [default: human]
          [possible values: human, json]

  -h, --help
          Show help

  -R, --no-ref
          Keep bare links as tasks instead of reporting them as reference items

Examples:
  bob capture-parse --format json -- 'Call bank @cash+'
  bob capture-parse 'Call bank @Cash+'
  bob capture-parse -f json -- 'jot idea @notes#Ideas'
  bob capture-parse -f json -- 'Postgres 17 minimum @foo+bar#req'
  bob capture-parse -f json -- '@cash+goog-exit'
  bob capture-parse -f json -- '+5'
  bob capture-parse -f json -- '-2'
  bob capture-parse -f json -- '++3'
  bob capture-parse -f json -- '--'
  bob capture-parse -f json -- '+'
  printf '++3\n\nCall bank @Cash+\n' | bob capture-parse -f json
  printf '+5\n\nCall bank @Cash+\n' | bob capture-parse -f json
  echo 'Do work @dev^focus-123' | bob capture-parse -f json
  echo 'Do work @dev:focus-123' | bob capture-parse -f json
  echo 'Do work @dev:focus-123#' | bob capture-parse -f json
  printf '@@foo\nFirst task\n\nSecond task @bar\n' | bob capture-parse -f json
  printf 'Parent\n- first child\n\nSecond @work\n' | bob capture-parse
  bob capture-parse -f json -- 'x'
  bob capture-parse -f json -- 'x1,3!2'
  bob capture-parse -f json -- 'x1~2'
  bob capture-parse -f json -- 'x0!2'
  printf 'x\n- 1 wired the lexer\n' | bob capture-parse -f json
  printf 'x\n- 1\n' | bob capture-parse -f json
  printf 'x =\n- 1 wired it\n' | bob capture-parse -f json
  bob capture-parse -f json -- '='
  bob capture-parse -f json -- '=3'
  bob capture-parse -f json -- '=~2'
  bob capture-parse -f json -- '=bugs'
  bob capture-parse -f json -- '=3#bugs~1'
  bob capture-parse -f json -- 'x =~2'
  bob capture-parse -f json -- '@r:id=x'
  bob capture-parse -f json -- '^r:id=x1'
  printf 'x\n\n=\n' | bob capture-parse -f json
  bob capture-parse -f json -- '+2 x'
  bob capture-parse -f json -- 'Buy Groceries! &foo:bar'
  bob capture-parse -f json -- 'Ask Kelly *job:cleanup'
  bob capture-parse -f json -- '&foo:bar @body+excercise'
  bob capture-parse -f json -- '^r:id &foo:bar'
  bob capture-parse -f json -- '&foo:bar +'
  bob capture-parse -f json -- '&foo:bar'
  bob capture-parse -f json -- 'Buy Groceries! &fo'
  bob capture-parse -f json -- 'Fix trip ~work:foo'
  bob capture-parse -f json -- 'Fix trip ~~work:foo|see'
  bob capture-parse -f json -- '/fix'

Modes:
  task, bullet, pomodoro_task, pomodoro_note, sub_bullet, task_toggle, project_note, pomodoro_project_note, pomodoro_adjust, pomodoro_shift, pomodoro_link, pomodoro_close, pomodoro_start, task_dependency, task_complete, ref, incomplete

Needs:
  route, section, block_id, pomodoro_id, pomodoro_name, task, task_section, active_task, task_link, task_goto, task_parent, pomodoro_close_task, pomodoro_close_log_text, pomodoro_start_task, task_dependency, blocked_task, relation_target, relation_label, dependency_target, task_complete
Tested rolling snapshot bob-cli 41afa14 bob-mac-capture 556bb67 bob-plugins af1e691 Built 2026-10-24T22:34:13Z (zensical) build.json