Skip to content

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 --help

Global options

These go before the subcommand.

FlagDefaultWhat it does
-c, --config <path>.github/dispatch.ymlWhich configuration to load
--log-level <level>infodebug, info, warn, error
--cassettes <dir>fixtures/cassettesRecorded provider responses
--db <path>.dispatch/index.sqliteLocal decision log and item index
--jsonoffMachine-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.json

Environment

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 repository

gh 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`.
FlagWhat it does
--forceOverwrite 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
FlagWhat it does
--questionsList 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 1

dispatch run

Triage one item.

bash
dispatch run --repo acme/widgets --issue 412 --dry-run
acme/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.
FlagRequiredWhat it does
--repo <owner/name>yes
--issue <number>yesIssue or pull request number
--mode <mode>Override the configured mode
--dry-runDecide and print, never execute
--forceIgnore the decision cache
--replayUse recorded responses instead of the provider
-v, --verboseExplain 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-run

Working offline

bash
dispatch run --repo acme/widgets --issue 412 --replay --dry-run

Needs 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)
FlagDefaultWhat it does
--repo <owner/name>required
--last <n>50How many recent items
--since <date>Only items updated since this ISO date
--mode <mode>from configEvaluate as if in this mode
--liveoffAsk the provider for items with no cassette — costs money
-v, --verboseoffList 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 --verbose

Items 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.
FlagDefaultWhat it does
--repo <owner/name>required
--last <n>50How many recent items
--since <date>Only items updated since this ISO date
--estimate-onlyoffPrint 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).
FlagDefaultWhat it does
--repo <owner/name>allRestrict to one repository
--target <n>0.9Accuracy a threshold must sustain to be recommended
--export <path>Write decisions as JSONL
--limit <n>5000How many decisions to read
bash
# Export for analysis elsewhere
dispatch eval --export decisions.jsonl

Read 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.
FlagWhat it does
--repo <owner/name>Required
--dry-runPrint 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/widgets

Only 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

CodeMeaning
0Success
1Validation 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.

MIT licensed.