---
title: CLI reference
description: Every tadoru command, flag, shared convention, and exit code.
---

All functionality lives in a single binary, `tadoru` — daemon control, permission diagnostics, data retrieval, filter management, agent setup, and the MCP server. The CLI and the [MCP server](/reference/mcp) are thin wrappers over the same core and store, and share the conventions on this page.

- Signed, notarized single Rust binary; no external runtime.
- Strictly local: the CLI performs no network egress of any kind.
- Apart from the destructive operations (`stop`, `purge`), commands have minimal side effects and are safe for agents to call.

## Commands at a glance

| Command | Summary |
| --- | --- |
| [`doctor`](#doctor) | Diagnose required macOS permissions; guide granting |
| [`start`](#start) | Start background recording (launchd) |
| [`stop`](#stop) | Stop recording and unregister (data kept) |
| [`pause`](#pause) / [`resume`](#resume) | Temporarily suspend / resume recording |
| [`status`](#status) | Daemon state, event counts, store info |
| [`record`](#record) | Foreground capture to stdout/file (debug & piping) |
| [`query`](#query) | Retrieve raw events with filters |
| [`timeline`](#timeline) | LLM-ready, token-budgeted timeline |
| [`export`](#export) | Bulk dump of raw events |
| [`purge`](#purge) | Manually delete stored events (destructive) |
| [`filter`](#filter) | Manage capture-time allow/deny lists |
| [`config`](#config) | Initialize, show, locate, or edit configuration |
| [`mcp`](#mcp) | Run the stdio MCP server |
| [`setup`](#setup) | Configure agent instructions and MCP integration |

## Global options

Valid for every subcommand.

| Flag | Description |
| --- | --- |
| `--config <path>` | Override config file path (default `~/.config/tadoru/config.toml`) |
| `--store <path>` | Override store path (default `~/.local/state/tadoru/store.sqlite`) |
| `--json` | Machine-readable JSON for diagnostic/state commands (shortcut for `--format json`) |
| `-q, --quiet` | Suppress progress output and notices |
| `-v, --verbose` | Verbose logging to stderr |
| `--version` / `--help` | Version / help |

## Shared conventions

### Time expressions

Values accepted by `--since` / `--until`:

| Form | Example | Meaning |
| --- | --- | --- |
| Relative | `15m` `2h` `1d` `1w` | Looking back from now; units `s` `m` `h` `d` `w` |
| Absolute | `2026-08-16T09:00:00Z` | RFC3339 timestamp |
| Keyword | `now` | Current moment (mainly for `--until`) |

`--until` defaults to `now`. `--since` defaults per command: `timeline` = `1h`, `query` = `15m`, `export` = `24h`.

### Event types

`--types` takes a comma-separated list of [event types](/reference/events), with trailing wildcards allowed (`browser.*`).

### Output formats

| Value | Commands | Description |
| --- | --- | --- |
| `jsonl` | `query`, `export`, `record` | One raw event per line (machine-readable) |
| `json` | `query`, `export`, `timeline` | Single JSON document; for `timeline`, the structured timeline |
| `md` | `timeline` | LLM-ready Markdown (default) |
| `table` | `query`, `status`, `doctor` | Human-readable table |

### Exit codes

| Code | Meaning |
| --- | --- |
| `0` | Success |
| `1` | General error |
| `2` | Usage error (invalid arguments) |
| `3` | **Missing permissions** (required TCC permission not granted) — a dedicated code so agents and scripts can detect it |
| `4` | No daemon (nothing to act on for `status`, `stop`, ...) |

---

## `doctor`

Diagnoses the granted state of required TCC permissions (Accessibility / Input Monitoring / Automation) against the configured `capture.sources`.

```bash
tadoru doctor            # human-readable; shows granting steps and the System Settings pane if anything is missing
tadoru doctor --fix      # opens the relevant System Settings pane (granting is still a user action)
tadoru doctor --json     # machine-readable, for agents
```

| Flag | Description |
| --- | --- |
| `--fix` | Open the System Settings pane for each missing permission |
| `--json` | Machine-readable output |

- Exits with **code 3** if any required permission is missing.
- `input_monitoring` counts as required only when the configuration captures `input.*`; in configurations that don't, its absence still yields `ok`.

`--json` output:

```json
{
  "ok": false,
  "capture_sources": ["app", "window", "ui", "input", "browser"],
  "permissions": {
    "accessibility":     { "status": "granted" },
    "input_monitoring":  { "status": "denied", "required_for": ["input.key", "input.scroll", "ui.click"] },
    "automation":        { "per_app": { "com.google.Chrome": "not_determined" } }
  },
  "missing_required": ["input_monitoring"],
  "settings_pane": "x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent"
}
```

`status` values: `granted` / `denied` / `not_determined`.

Human-readable output always ends with the next action. When a required permission is denied, it
explains that CLI tools do not appear in macOS permission lists automatically and shows how to use
`tadoru doctor --fix`, the `+` button, and Command-Shift-G (or a single `~`) to add the exact
path of the running executable. When Chrome Automation is `not_determined`, no advance
action is needed: macOS asks the first time Tadoru contacts Chrome. An unsigned or ad-hoc-signed
build also produces a warning that rebuilding will reset granted permissions.

## `start`

Registers Tadoru with launchd and starts background recording.

```bash
tadoru start              # start background recording
tadoru start --foreground # run in the foreground instead (dev/debug)
```

| Flag | Description |
| --- | --- |
| `--foreground` | Don't daemonize; run in the foreground |

If missing permissions are detected, recording does not start and the command exits with **code 3**, explaining what to grant.

After a background start succeeds, the confirmation states that Tadoru is registered as a
launchd background item. A macOS notification or an entry under **Login Items & Extensions** is
expected behavior.

## `stop`

Stops recording and unregisters from launchd. **Not destructive** — stored data is kept. Exits with **code 4** if there is no daemon to stop.

```bash
tadoru stop
```

## `pause`

Temporarily suspends recording without unregistering the daemon.

```bash
tadoru pause --for 30m   # auto-resume after 30 minutes
tadoru pause             # paused until `resume`
```

| Flag | Description |
| --- | --- |
| `--for <TIME>` | Duration; omit to pause indefinitely |

## `resume`

Resumes recording after a pause.

```bash
tadoru resume
```

## `status`

Reports daemon state, capture configuration, and store statistics.

```bash
tadoru status
tadoru status --json
```

Human-readable output includes `TEXT CONTENT      on/off (opt-in)` so the privacy-sensitive opt-in
state is visible without requesting JSON.

```json
{
  "running": true,
  "paused": false,
  "since": "2026-08-16T08:00:00Z",
  "uptime_s": 3600,
  "events_captured": 12345,
  "events_dropped": 2,
  "last_event_ts": "2026-08-16T08:59:58Z",
  "degraded": { "eventtap": "1 degraded collector operation observed" },
  "store": { "path": "~/.local/state/tadoru/store.sqlite", "size_bytes": 5242880, "retention_hours": 48, "oldest_event_ts": "2026-08-14T09:00:00Z" },
  "capture": { "sources": ["app", "window", "ui", "input", "browser"], "text_content": false },
  "permissions_ok": true
}
```

| Field | Meaning |
| --- | --- |
| `events_dropped` | Cumulative events dropped by collectors or the recording pipeline |
| `degraded` | Collector or runtime degradation by component; empty when no degradation is known |
| `permissions_ok` | The running recorder's own permission result while its heartbeat is fresh; a local probe when no recorder is running |

## `record`

Foreground capture: streams raw events as NDJSON to stdout (or a file) without daemonizing. Intended for piping and experiments — use [`start`](#start) for everyday recording.

```bash
tadoru record --stream                 # NDJSON to stdout
tadoru record --out events.jsonl       # to a file
```

| Flag | Description |
| --- | --- |
| `--stream` | Stream events to stdout as they occur |
| `--out <FILE>` | Write to a file instead |
| `--format jsonl` | Output format (NDJSON) |

## `query`

Retrieves raw events matching the given conditions. Machine-readable, structured output.

```bash
tadoru query --since 15m --types browser.navigate,app.activate
tadoru query --since 2h --app Safari --format json --limit 500
```

| Flag | Description |
| --- | --- |
| `--since` / `--until` | Time range (default `--since 15m`) |
| `--types <TYPE,...>` | Event-type filter (comma-separated; `browser.*` wildcards allowed) |
| `--app <NAME>` / `--bundle-id <ID>` | Filter by app name / bundle ID |
| `--limit <N>` | Max events (default 500) |
| `--format` | `jsonl` (default) / `json` / `table` |

These are query-time filters — they narrow reads, not what gets recorded (see [filters](/guides/filters)).

## `timeline`

Splits raw events into sessions, deduplicates, coalesces, and serializes an LLM-ready summary within a token budget. **The command agents call most.**

```bash
tadoru timeline --since 1h --format md --token-budget 4000
tadoru timeline --since 30m --format json --granularity fine
```

| Flag | Description |
| --- | --- |
| `--since` / `--until` | Time range (default `--since 1h`) |
| `--format` | `md` (default; LLM-ready Markdown) / `json` (structured) |
| `--token-budget <N>` | Approximate token cap (default 4000); content is coarsened to fit |
| `--granularity` | `coarse` (default; per session) / `fine` (per interaction) |

`--format json` structure (abridged):

```json
{
  "range": { "since": "2026-08-16T08:00:00Z", "until": "2026-08-16T09:00:00Z" },
  "token_estimate": 3810,
  "truncated": false,
  "sessions": [
    {
      "start": "2026-08-16T08:12:00Z", "end": "2026-08-16T08:31:00Z",
      "app": "Safari", "title_summary": "Reviewing PR #42",
      "activities": ["Viewed 3 files on GitHub", "Wrote 2 comments"],
      "event_ids": ["evt_01J...", "evt_01J..."]
    }
  ]
}
```

## `export`

Dumps every raw event in range (backup / external processing).

```bash
tadoru export --since 24h --format jsonl --out dump.jsonl
```

| Flag | Description |
| --- | --- |
| `--since` / `--until` | Time range (default `--since 24h`) |
| `--format` | `jsonl` / `json` |
| `--out <FILE>` | Output file |

## `purge`

Deletes stored events manually, independent of retention-based auto-purge. **Destructive.**

```bash
tadoru purge --before 24h   # delete events older than 24 hours
tadoru purge --all          # delete everything (confirmation prompt; suppress with --quiet)
```

| Flag | Description |
| --- | --- |
| `--before <TIME>` | Delete events older than this |
| `--all` | Delete everything (asks for confirmation) |

## `filter`

Manages the capture-time allow/deny lists — apps and sites discarded **before** they are written to the store. Semantics and matching rules are covered in the [filters guide](/guides/filters). Equivalent to editing `[filter]` in `config.toml`, with added validation and deduplication.

```bash
tadoru filter show                                      # all lists and the active mode
tadoru filter exclude-app add com.1password.1password
tadoru filter exclude-app remove com.1password.1password
tadoru filter only-app add com.apple.Safari
tadoru filter exclude-site add example.com
tadoru filter only-site add github.com
```

| Subcommand | `[filter]` key | Meaning |
| --- | --- | --- |
| `exclude-app (add\|remove) <BUNDLE_ID or NAME>` | `exclude_apps` | Deny list (everything else is captured) |
| `only-app (add\|remove) <BUNDLE_ID or NAME>` | `include_only_apps` | Allow list (when set, only listed apps are captured) |
| `exclude-site (add\|remove) <DOMAIN>` | `exclude_websites` | Deny list for `browser.*` URL hosts |
| `only-site (add\|remove) <DOMAIN>` | `include_only_websites` | Allow list for `browser.*` URL hosts |

- Apps: specify by `BUNDLE_ID` (recommended) or display name. `filter show` also displays the built-in default exclusions (`1Password`, `Keychain Access`, ...).
- Changes apply from the next event — no daemon restart.
- Adding a single `only-app` entry switches to allow-list mode; `filter show` states the active mode.

## `config`

```bash
tadoru config init   # create a fully commented configuration template
tadoru config path   # print the config file path
tadoru config show   # print the effective configuration (defaults merged)
tadoru config edit   # open the config in $EDITOR
tadoru config set capture.text_content true
```

`config init` writes a complete, commented template containing every supported option and its
current default. By default it creates `~/.config/tadoru/config.toml`; the global
`--config <path>` flag selects a different destination. Missing parent directories are created. On
success, the command prints the created path and exits with code 0. If the destination already
exists, it leaves the file unchanged, reports the existing path, and exits with code 1.

`config set <DOTTED_KEY> <VALUE>` validates and saves one scalar setting. Supported keys and values:

| Key | Accepted values |
| --- | --- |
| `capture.text_content` | `true`, `false` |
| `output.mode` | `stream`, `batch`, `both` |
| `output.batch_interval_s` | Unsigned integer greater than zero |
| `output.store` | `sqlite`, `jsonl` |
| `output.retention_hours` | Unsigned integer greater than zero |

Array settings are not accepted: use [`tadoru filter`](#filter) for filter lists, or
`tadoru config edit` for other arrays. Unknown keys, array keys, and invalid values exit with
code 2 without saving. If the recording daemon is running, a successful `capture.*` or `output.*`
change prints:

```text
Restart recording with `tadoru stop && tadoru start` for this to take effect.
```

Schema: see the [configuration reference](/reference/config).

## `mcp`

Runs the stdio MCP server — JSON-RPC on stdin/stdout, zero network surface. Started by MCP clients (Claude Code, Codex, opencode, Claude Desktop, ...), not usually by hand.

```bash
tadoru mcp [--store <PATH>]
```

- **Read-only view** over the store; independent from the recording daemon.
- Exposed tools: `get_timeline` / `query_events` / `get_status` — see the [MCP reference](/reference/mcp).

## `setup`

Configures Tadoru instructions and MCP integration for the target agent. Agents with native skill support and Claude Desktop retain their file-based setup; opencode and pi only print manual setup instructions and never modify files.

```bash
tadoru setup --agent claude            # SKILL.md into Claude Code's skill location + MCP registration
tadoru setup --agent codex --scope user
tadoru setup --agent hermes            # skill into ~/.hermes/skills/ + MCP in ~/.hermes/config.yaml
tadoru setup --agent pi                # print derived README instructions; no MCP or file writes
tadoru setup --agent claude-desktop    # MCP registration in claude_desktop_config.json
tadoru setup --agent opencode          # print AGENTS.md snippet + MCP JSON; no file writes
```

| Flag | Description |
| --- | --- |
| `--agent` | `claude` / `codex` / `opencode` / `hermes` / `pi` / `claude-desktop` |
| `--scope` | `project` (default; current repository) / `user` (account-wide). It has no effect on the manual opencode/pi output |
| `--print` | Preview planned file changes without writing. opencode and pi never write files, with or without this flag |

Per-agent behavior and setup output: see [agent setup](/agents/setup).
