CLI reference
Every command, every flag, and what each one is actually for.
bash
npm i -g @dispatch-triage/cli
# or, without installing
npx @dispatch-triage/cli --helpGlobal options
These go before the subcommand.
| Flag | Default | What it does |
|---|---|---|
-c, --config <path> | .github/dispatch.yml | Which configuration to load |
--log-level <level> | info | debug, info, warn, error |
--cassettes <dir> | fixtures/cassettes | Recorded provider responses |
--db <path> | .dispatch/index.sqlite | Local decision log and item index |
--json | off | Machine-readable output on stdout |
Logs always go to stderr, so --json output on stdout stays pipeable:
bash
dispatch replay --repo acme/widgets --last 50 --json > plan.jsonEnvironment
bash
# .env at your repository root
TYPESAFE_API_KEY=... # only for `record`, `run` without --replay, and `eval` setup
GITHUB_TOKEN=... # any command that reads a repositorygh auth token prints a usable token if you already use the GitHub CLI.
dispatch init
Scaffold a configuration, a workflow, and a directory for template overrides.
bash
dispatch init created .github/dispatch.yml
created .github/workflows/dispatch.yml
created .github/dispatch-templates/.gitkeep
Next:
1. Add TYPESAFE_API_KEY to the repository secrets.
2. Fill in the `labels:` dimensions — the descriptions are the rubric.
3. Run `dispatch validate`.
4. Run dispatch replay --repo owner/name --last 50 and read the plan.
5. Only then consider `mode: suggest`.| Flag | What it does |
|---|---|
--force | Overwrite files that already exist |
Without --force it never overwrites; existing files are reported as skipped.
dispatch validate
Check the configuration and report every problem at once — not the first.
bash
dispatch validate.github/dispatch.yml:31:9 warning `needs-repro` is not part of any label dimension. (rules[0].then[0])
That is fine for workflow labels such as `needs-repro`. Run `dispatch sync-labels` so the label exists in the repository.
0 errors, 2 warnings
mode shadow (decides and logs, writes nothing)
model jev-latest
thresholds auto 0.85, suggest 0.6
rules 4
questions 10 per issue, 10 per pull request
destructive none opted in
hash 1f30e865742cad7b| Flag | What it does |
|---|---|
--questions | List every question that will be asked, including dropped ones |
Errors mean a rule can never work — a reference to a question that does not exist, a duplicate rule id, auto below suggest, an empty then, a missing template. They stop the load.
Warnings mean the config is valid and will run, but something will not happen — a destructive op without an opt-in, a label outside your taxonomy, a rule asking an issue-only question on a pull request trigger. They do not stop the load.
Exit code is 1 when there are errors, so it drops into a pre-commit hook:
bash
dispatch validate || exit 1dispatch run
Triage one item.
bash
dispatch run --repo acme/widgets --issue 412 --dry-runacme/widgets#412 issue.opened mode=shadow key=8f2a1c04
question answer confidence
has_repro 0.12 ########.. 76%
is_spam 0.02 ########## 96%
severity 1.60 #######... 72%
type bug ########.. 84%
Will apply
(nothing)
Suppressed
x label: add needs-repro [needs-repro] shadow_mode
Shadow mode decides and logs but writes nothing.| Flag | Required | What it does |
|---|---|---|
--repo <owner/name> | yes | |
--issue <number> | yes | Issue or pull request number |
--mode <mode> | Override the configured mode | |
--dry-run | Decide and print, never execute | |
--force | Ignore the decision cache | |
--replay | Use recorded responses instead of the provider | |
-v, --verbose | Explain every suppression |
--dry-run is not --mode shadow
This catches people out. --mode shadow suppresses everything with a reason, so the plan reads "nothing, because shadow". --dry-run evaluates under the real mode and then declines to execute, so you see what would happen:
bash
# What would auto mode actually do?
dispatch run --repo acme/widgets --issue 412 --mode auto --dry-runWorking offline
bash
dispatch run --repo acme/widgets --issue 412 --replay --dry-runNeeds no API key. Requires a recorded cassette for that item.
dispatch replay
Re-triage history and print the plan. Never writes to GitHub — there is no code path in this command that could.
bash
dispatch replay --repo acme/widgets --last 200 Replayed 20 item(s)
Would apply
3 label: add needs-tests
2 label: add area:docs
2 label: add needs-careful-review
Would suggest
3 label: add needs-repro
3 comment: needs-repro.md
Suppressed
2 below_suggest_threshold careful-review, needs-tests
$0.0000 total, $0.000000 per item (replayed from cassettes)| Flag | Default | What it does |
|---|---|---|
--repo <owner/name> | required | |
--last <n> | 50 | How many recent items |
--since <date> | Only items updated since this ISO date | |
--mode <mode> | from config | Evaluate as if in this mode |
--live | off | Ask the provider for items with no cassette — costs money |
-v, --verbose | off | List every item individually |
This is the command that earns trust before anything is switched on. Run it in auto mode to see what Dispatch would do without giving it permission to do it:
bash
dispatch replay --repo acme/widgets --last 200 --mode auto --verboseItems with no cassette are skipped and counted, so the summary never looks more confident than the evidence behind it.
dispatch record
Capture fixtures from real items. The only command that spends money, and the only one that needs TYPESAFE_API_KEY.
bash
# Check the cost first
dispatch record --repo acme/widgets --last 50 --estimate-only 50 item(s) would be recorded.
Estimated cost: $0.0042
Run again without --estimate-only to record.bash
dispatch record --repo acme/widgets --last 50 Recorded 50 item(s)
Spent $0.0131
Everything from here runs offline: `dispatch replay`, `dispatch eval`, and the test suite.| Flag | Default | What it does |
|---|---|---|
--repo <owner/name> | required | |
--last <n> | 50 | How many recent items |
--since <date> | Only items updated since this ISO date | |
--estimate-only | off | Print the estimated cost and stop |
Recording always runs in shadow mode, so it cannot write to the repository it is sampling.
Fixtures store the redacted state. The recorder asks the provider what it actually sent rather than recording what it was handed, so a cassette is safe to commit even from a repository whose issues contain secrets — provided your data.redact patterns cover them.
dispatch eval
Calibration and accuracy over the local decision log. Talks to nothing.
bash
dispatch eval --repo acme/widgets Calibration
20 decision(s), 14 correction(s) recorded
7 applied, 11 suggested, 2 suppressed
$0.0021 spent
confidence n claimed observed
0.6-0.7 4 0.65 0.50 ! ############
0.7-0.8 11 0.76 0.73 #################
0.8-0.9 22 0.84 0.86 + #####################
0.9-1.0 31 0.95 0.94 #######################
Expected calibration error 0.041
Worst bin 0.150
! claimed more than it delivered + delivered more than it claimed
At confidence >= 0.80, Dispatch is right 90% of the time (53 predictions).| Flag | Default | What it does |
|---|---|---|
--repo <owner/name> | all | Restrict to one repository |
--target <n> | 0.9 | Accuracy a threshold must sustain to be recommended |
--export <path> | Write decisions as JSONL | |
--limit <n> | 5000 | How many decisions to read |
bash
# Export for analysis elsewhere
dispatch eval --export decisions.jsonlRead the caveat
A prediction counts as correct when nobody corrected it, so silence counts as agreement. With no corrections recorded, every accuracy reads 1.00 and the command says so underneath. The honest reading is always "accuracy among items a maintainer actually reviewed".
A recommendation is refused in three cases, all deliberate: fewer than 20 predictions, accuracy that never clears the target at any threshold, and a drift smaller than 0.05 (nudging you from 0.85 to 0.84 every week would train you to ignore the report).
dispatch sync-labels
Create the repository labels your taxonomy implies, so a rule never fails on a label GitHub does not have.
bash
dispatch sync-labels --repo acme/widgets --dry-run create area:parser
create area:renderer
update type:bug
3 label(s) already correct
--dry-run: nothing was changed.
2 created, 1 updated.| Flag | What it does |
|---|---|
--repo <owner/name> | Required |
--dry-run | Print what would change |
It creates labels and updates their descriptions. It never deletes one — a label it does not know about may be load-bearing in someone else's workflow.
Colours are derived from the label name, so the same dimension looks the same across repositories and re-running never churns them.
A complete first session
bash
# 1. Scaffold and check
npx @dispatch-triage/cli init
$EDITOR .github/dispatch.yml # fill in the `labels:` descriptions
npx @dispatch-triage/cli validate
# 2. Capture real answers from your own history — about a cent
npx @dispatch-triage/cli record --repo acme/widgets --last 100 --estimate-only
npx @dispatch-triage/cli record --repo acme/widgets --last 100
# 3. See what it would do, offline and free
npx @dispatch-triage/cli replay --repo acme/widgets --last 100 --mode auto --verbose
# 4. Disagree, tune, repeat — replay costs nothing
$EDITOR .github/dispatch.yml
npx @dispatch-triage/cli replay --repo acme/widgets --last 100 --mode auto
# 5. Create the labels the rules need
npx @dispatch-triage/cli sync-labels --repo acme/widgets --dry-run
npx @dispatch-triage/cli sync-labels --repo acme/widgets
# 6. Commit, let it run in shadow for a week, then:
npx @dispatch-triage/cli eval --repo acme/widgetsOnly after step 6 shows calibration you believe should you move mode off shadow.
What the CLI does not do yet
Duplicate detection does not run from dispatch run or dispatch replay. It works, and runs in the GitHub Action, but it needs its own recorded responses to work offline and dispatch record does not capture those yet. So a CLI preview shows everything except the duplicate check.
dispatch escalate has no CLI command either — escalations run through the Action's command: escalate.
Both are noted here rather than left for you to discover.
Exit codes
| Code | Meaning |
|---|---|
0 | Success |
1 | Validation errors, or a command that could not complete |
Every command prints its problem to stderr and a summary to stdout, so --json output remains safe to pipe.