Bob Command Mode runs bob commands from Obsidian with safe-write guards and run history.

## Plugin facts

| Fact | Value |
| --- | --- |
| Name | Bob Command Mode |
| ID | `bob-command-mode` |
| Version | 0.1.0 |
| Minimum Obsidian version | 1.8.7 |
| Desktop only | Yes |
| Description | Run bob commands from Obsidian with safe-write guards, run history, and an automation API. |

## Registered commands

| Command | Command ID | Default hotkey |
| --- | --- | --- |
| Bob: Command mode | `open` | — |
| Bob: Rerun last command | `rerun-last` | — |
| Bob: Show last output | `show-last-output` | — |

## Run commands

Every catalog entry whose positionals are all optional and whose policy is `run` also appears as a command palette command named `Bob: <title>` with id `run-<entry-id>` (for example `Bob: Today's plan` as `run-plan`). These ids are generated from the cached catalog at load, so the inventory scanner does not list them here. Each one runs its entry through the same run engine and finishes with an outcome toast. `Bob: Rerun last command` repeats the newest recorded command with its original arguments and preview setting. `Bob: Show last output` opens the output pager on the newest run.

## Keyboard and Vim

With Vim mode enabled, `:` in normal mode opens the sheet. `Escape` closes it. In the list, `↑` / `↓` and `Ctrl+P` / `Ctrl+N` move between rows; `Enter` runs, `Tab` opens argument mode, `Shift+Enter` previews, `Ctrl+Enter` runs without preview, and `?` after a query opens `bob <command> --help`. Typing `::` opens Vim's ex line. Vim commands such as `:%s/foo/bar/` and `:42` are routed to Vim, and matching `exmap` commands can be selected from suggestions. The catalog also includes named Pomodoro session actions to start, stop, extend, and shorten a session. Free-text `bob capture TEXT` is not accepted in command mode; use the dedicated capture workflow for free-text capture.

In argument mode, `Tab` accepts the selected completion, arrows or `Ctrl+P` / `Ctrl+N` move through completions, and `Enter` accepts a completion. `Shift+Enter` previews and `Ctrl+Enter` runs the typed command; `Escape` returns to the list. A preview is applied with `Ctrl+Enter`. A writer without a preview requires `Ctrl+Enter` or the Run button on its confirmation card.

In the pager, `j` / `k` scroll a line and `d` / `u` scroll half a page; `g` / `G` go to the start or end. `/` starts search, `n` / `N` move between matches, and `w` toggles wrapping. `y` copies output, `Y` copies the shell-quoted command, `r` reruns, and `[` / `]` move through history. `:` returns to the list, `Escape` returns to the prior stage, and `q` closes the sheet.

Bind Vim's keys with non-recursive mappings so replaying `:` cannot reopen the sheet recursively:

```
exmap bob_command obcommand bob-command-mode:open
nnoremap : :bob_command<CR>
nnoremap g: :
```

Insert-mode `:` remains text, visual-mode `:` remains native, and `g:` opens Vim's native ex line even if the plugin is disabled. The plugin warns if a recursive map replays `:`. If `:` does nothing, run `bob plugins sync` on that host, confirm `bob plugins list` shows it enabled, then reload Obsidian.

## Feedback

Finished runs use toast cards for completed, needs-attention, failed, unknown, and stopped outcomes. Cards show a short summary, elapsed time, and the command line; their actions can open a note or conflict log, scan queued intake, reload deployed plugins, retry, copy the command, or open the saved output. Failed and unknown cards remain until dismissed. The status-bar chip appears after a run has lasted 400 ms, shows active run counts and elapsed time, and turns amber when a writer passes its budget. Clicking it opens the active run's pager.

When a run takes more than 15 seconds and Obsidian is unfocused, the enabled OS notification opens that run's pager when clicked. The `OS notifications` setting controls this behavior.

## Output pager

The output pager is a bottom sheet that streams a run's stdout and stderr, replays finished runs in arrival order, and doubles as run history. It renders ANSI color against theme variables, ticks stderr with a faint red gutter, pretty-prints a body that is a single JSON document, and turns vault paths and `[[wikilinks]]` that resolve into note links. Vim-style keys scroll, search, wrap, copy (ANSI-stripped output with `y`, the shell-quoted command with `Y`), rerun, stop, and step through history newest-first. Toast **Output**, the status chip, OS notification clicks, and `api.commandMode.openOutput` all open this view. A run whose pager is open and focused at finish updates the status pill and skips the toast.

## Command sheet

`Bob: Command mode` opens a bottom sheet with a fuzzy finder over every catalog entry, ranked by path, alias, title, and keywords with frecency tie-breaks. An empty query shows RECENT runs, FOR THIS NOTE rows filled from the active note (ref, area, project, and article notes each get their own rows), FROM CLIPBOARD rows for an http(s) URL, and one section per catalog section plus surface rows that run existing Obsidian commands (daily note, themes manager, stats, tasks, relation labels, reading board, article formatting). Unavailable rows stay visible and dimmed with their reason. `Enter` runs, `Tab` names argument mode (it lands with the args-vim phase), `Shift+Enter` dry-runs a preview into the sheet, `Ctrl+Enter` runs now, and a trailing `?` shows `bob <path> --help` for the selected row. Preview-first rows stream their preview in place and apply with `Ctrl+Enter`; writers without a dry run ask once on an amber confirm card that only `Ctrl+Enter` or Run confirms. Reports grow the sheet into the output pager in place, and `Esc` returns to the list with the query intact.

## Run engine

Runs are detached, file-backed children: `spawn(bobPath, argv)` with output to per-run files under `command-mode/runs/` in the host state directory, an exit receipt when the catalog advertises `run-receipt`, and a JSON run record per run. Writers run one at a time behind a save-every-open-note guard; reads run in parallel. Read budgets kill the child, writer budgets mark the run overdue, and stop is offered only for runs this instance spawned. A plugin reload adopts unfinished records from their receipts, or reports unknown when no receipt arrives in time.

Run records and stdout/stderr files live under `${XDG_STATE_HOME:-~/.local/state}/bob-cli/command-mode/runs/`. They are host-local rather than vault data.

## Settings

| Setting | Key | Default | Effect |
| --- | --- | --- | --- |
| bob path | `bobPath` | Auto (blank) | Find `bob` in its usual install locations or on `PATH`; set an explicit path to override. |
| OS notifications | `osNotifications` | On | Notify on long finished runs while Obsidian is unfocused. |
| Keep runs | `keepRuns` | 50 | Retain at most this many runs in history. |
| Keep days | `keepDays` | 14 | Retain runs younger than this many days. |

## Automation API

Other plugins reach the engine through `app.plugins.plugins["bob-command-mode"].api.commandMode` (version 1): `run(argv, opts)` settles `{ok, runId, outcome, exitCode, reason?}` and never rejects, `openOutput(runId)` opens that run in the output pager, `onRunFinished(cb)` subscribes to finishes, and `catalog()` and `lastRun()` read the cached catalog and newest record. Every finish also fires `bob-command-mode:run-finished` on the workspace with `{runId, argv, entryId, outcome, effect, invalidates}`.
