@sjawhar/pi-envoy 8.4.0 → 8.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,17 +37,17 @@ reordering the board yourself.
37
37
 
38
38
  Before you start implementing an issue, claim it:
39
39
 
40
- ```ts
41
- dispatch_claim({ issue: "LEGION-234" }) // I am implementing this
42
- dispatch_claim({ issue: "LEGION-234", release: true }) // I have stopped; it is free
40
+ ```bash
41
+ dispatch claim --issue LEGION-234 # I am implementing this
42
+ dispatch claim --issue LEGION-234 --release # I have stopped; it is free
43
43
  ```
44
44
 
45
45
  A claim records **your** session — the one making the call, never another — and shows on every
46
- read of the issue: the dashboard header, the issue list and board, `dispatch_read` (a
47
- `Claimed by:` line) and `dispatch_issues` (a claim on the row). A session's claim there ends
46
+ read of the issue: the dashboard header, the issue list and board, `dispatch read` (a
47
+ `Claimed by:` line) and `dispatch issues` (a claim on the row). A session's claim there ends
48
48
  `· not running` when the live agent registry does not list that session, as the dashboard's chip
49
49
  does — that claim is free to take — and `· liveness unknown` when the registry could not be read,
50
- which says nothing either way. `dispatch_issues` plus the dashboard's **Unclaimed** filter is how
50
+ which says nothing either way. `dispatch issues` plus the dashboard's **Unclaimed** filter is how
51
51
  you find work nobody is on.
52
52
 
53
53
  - **`409 ISSUE_CLAIMED` means someone else holds this issue.** When it is another session, the
@@ -55,7 +55,7 @@ you find work nobody is on.
55
55
  that session (its id is in the message; `envoy_send` reaches it) or pick up something else,
56
56
  and tell the human if you believe the work should be yours. When a **human** holds it, the
57
57
  refusal names the person and says nothing about a session running, because there is none to
58
- message: ask them with `dispatch_ask` instead, so the open ask appears in their Inbox, and
58
+ message: ask them with `dispatch ask` instead, so the open ask appears in their Inbox, and
59
59
  never assume their claim has lapsed — only a human releases or forces a human's claim.
60
60
  - **`409 CLAIM_CONTENDED` means the issue changed hands twice while your call ran**, so nothing
61
61
  was applied and nobody's liveness was checked. Read the issue and decide again; it is not a
@@ -71,8 +71,8 @@ you find work nobody is on.
71
71
 
72
72
  **Claiming and moving the status are two separate actions, and you do both.** A claim says which
73
73
  session is on the work; the status says where the work has got to, and humans use it to track
74
- that too. So when you start: `dispatch_claim({ issue })` **and**
75
- `dispatch_issue_update({ issue, status: "in_progress" })`.
74
+ that too. So when you start: `dispatch claim --issue <KEY>` **and**
75
+ `dispatch issue-update --issue <KEY> --status in_progress`.
76
76
 
77
77
  ## Issue status is yours to move
78
78
 
@@ -84,29 +84,29 @@ queue, `done` when the change has been driven in production (a merge is not `don
84
84
  issues you own as well as the root. An issue left at `triage` while work is underway is a defect.
85
85
  Waiting for the deploy lane is not a status and is never announced.
86
86
 
87
- ```ts
88
- // PATCH /api/v1/issues/{key} — status, title, labels, priority, external_links (merged by URL), route, parent
89
- dispatch_issue_update({ issue: "PROJ-175", status: "testing" })
90
- dispatch_issue_update({ issue: "PROJ-175", status: "done", reason: "Shipped in owner/repo#7; verified on the production dashboard." })
91
- dispatch_issue_update({ issue: "PROJ-175", priority: 1 }) // 0–3; see Priority is yours to set
92
- dispatch_issue_update({ issue: "PROJ-175", external_links: ["https://github.com/owner/repo/pull/7"] })
93
- dispatch_issue_update({ issue: "PROJ-175", parent: "PROJ-170" }) // same-project key; "" clears the parent
87
+ ```bash
88
+ # PATCH /api/v1/issues/{key} — status, title, labels, priority, external links (merged by URL), route, parent
89
+ dispatch issue-update --issue PROJ-175 --status testing
90
+ dispatch issue-update --issue PROJ-175 --status done --reason 'Shipped in owner/repo#7; verified on the production dashboard.'
91
+ dispatch issue-update --issue PROJ-175 --priority 1 # 0–3; see Priority is yours to set
92
+ dispatch issue-update --issue PROJ-175 --external-link https://github.com/owner/repo/pull/7
93
+ dispatch issue-update --issue PROJ-175 --parent PROJ-170 # same-project key; --parent '' clears it
94
94
  ```
95
95
 
96
- Closing takes a `reason`, and the tool refuses `status: "done"` without one: it posts the reason on
96
+ Closing takes a `--reason`, and the command refuses `--status done` without one: it posts the reason on
97
97
  the issue as a message, then closes it, because a closed issue refuses messages, comments, and
98
98
  artifacts, so a reason left for later has nowhere to go. When the close fails after the post, the
99
99
  error names the posted message; after a timeout or a server error it also says the close may have
100
100
  landed, so read the issue's status first. A retry points its reason at the posted message rather
101
101
  than repeating it.
102
102
 
103
- The two clears differ: `priority` clears with `null`, while `parent` and `route` clear with `""`.
104
- Guessing the other one is a refusal either way.
103
+ The two clears differ: `--clear-priority` sends null, while `--parent ''` and `--route ''` clear with an
104
+ empty string. Guessing the other one is a refusal either way.
105
105
 
106
- Link the pull request that delivers the issue in `external_links` when you open it; the issue page
107
- renders its state and checks from that link, and `dispatch_read` lists it under `External links:`.
108
- The call is authenticated with the same bearer as every
109
- other `dispatch_*` tool: a Legion pane reads it from the `DISPATCH_TOKEN_FILE` path the daemon sets on
106
+ Link the pull request that delivers the issue with `--external-link` when you open it; the issue page
107
+ renders its state and checks from that link, and `dispatch read` lists it under `External links:`.
108
+ The command is authenticated with the same bearer as every
109
+ other `dispatch` command: a Legion pane reads it from the `DISPATCH_TOKEN_FILE` path the daemon sets on
110
110
  the pane; an OMP session outside Legion reads `dispatch.token` from `~/.config/opencode/envoy.json`.
111
111
 
112
112
  A write to an issue still in `triage` answers once with `… is still in triage …`; move the status
@@ -120,39 +120,39 @@ lowest, and `null` clears it. Agents set it (`dispatch://LEGION/artifact/issue-s
120
120
  the human overrides anything they disagree with from the dashboard. A closed
121
121
  issue takes only `rank`, `components`, and a reopening `status` (any status but `done`);
122
122
  everything else, `priority` included, waits for the reopen (`409 ISSUE_CLOSED`). So reopen it
123
- first, then set the priority — the two cannot go in one call. `rank` itself is not a tool field:
123
+ first, then set the priority — the two cannot go in one command. `rank` itself is not a flag:
124
124
  reorder it the way [Issue reads](#board-rank-priority-and-assignee-on-a-read) describes, through `PATCH /api/v1/issues/{key}`.
125
125
 
126
126
  ## Board rank, priority, and assignee on a read
127
127
 
128
- Issue reads include `rank`, the server-owned ordering key used by project boards; reorder through `PATCH /api/v1/issues/{key}` with `{"rank": {"before": "<key>", "after": "<key>"}}`, either neighbor optional and both in the issue's project. A bearer caller also names its own session in that body, `"actor": {"kind": "session", "id": "<your session id>"}`, or the server refuses with `ACTOR_KIND`. They also include nullable coarse priority (`P0` highest through `P3` lowest) and `assignee`: the lowercase email of the person who answers the issue's asks, or `null` when nobody holds it. `dispatch_read` of an issue prints it as `Assignee: <email>` or `Assignee: unassigned`.
128
+ Issue reads include `rank`, the server-owned ordering key used by project boards; reorder through `PATCH /api/v1/issues/{key}` with `{"rank": {"before": "<key>", "after": "<key>"}}`, either neighbor optional and both in the issue's project. A bearer caller also names its own session in that body, `"actor": {"kind": "session", "id": "<your session id>"}`, or the server refuses with `ACTOR_KIND`. They also include nullable coarse priority (`P0` highest through `P3` lowest) and `assignee`: the lowercase email of the person who answers the issue's asks, or `null` when nobody holds it. `dispatch read` of an issue prints it as `Assignee: <email>` or `Assignee: unassigned`.
129
129
 
130
130
  ## Reading a project's backlog
131
131
 
132
132
  To see the shape of a project rather than find a phrase, list its issues:
133
- ```ts
134
- dispatch_issues({ project, status?, parent?, label?, priority?, route_status?, updated_since?, limit?, offset? })
133
+ ```bash
134
+ dispatch issues --project <KEY>
135
135
  ```
136
136
  Each row carries the issue key, title, status, priority, parent, labels, its open-ask count, and
137
- when it last changed — a roadmap or backlog pass without opening every issue. Filter with `status`
138
- (a lifecycle status), `parent` (one issue's children), `label`, `priority` (a list of `0`–`3`, with
139
- `null` for an issue with no priority: `[0, 1]` is every P0 and P1), `route_status` (below), or
140
- `updated_since` (an RFC3339 timestamp, for "what moved this week"). `limit` is the page size, 50 by
141
- default and 250 at most, and `offset` is where the page starts; the answer says how many issues
137
+ when it last changed — a roadmap or backlog pass without opening every issue. Filter with `--status`
138
+ (a lifecycle status), `--parent` (one issue's children), `--label`, `--priority` (repeated, `0`–`3`, with
139
+ `none` for an issue with no priority: `--priority 0 --priority 1` is every P0 and P1), `--route-status` (below), or
140
+ `--updated-since` (an RFC3339 timestamp, for "what moved this week"). `--limit` is the page size, 50 by
141
+ default and 250 at most, and `--offset` is where the page starts; the output says how many issues
142
142
  match. Repeating with the next offset walks every matching issue only while the list does not
143
143
  change: an issue that enters or leaves what your filters match, or whose status or rank changes,
144
144
  between two pages shifts rows across a page boundary, and the walk then shows one issue twice and
145
145
  misses another.
146
146
 
147
- This is not search: it matches no text. Use `dispatch_search` for a keyword or phrase, and
148
- `dispatch_issues` when you want every issue in a project and its current state.
147
+ This is not search: it matches no text. Use `dispatch search` for a keyword or phrase, and
148
+ `dispatch issues` when you want every issue in a project and its current state.
149
149
 
150
150
  ### The owner audit
151
151
 
152
152
  As the owner of a surface, list the project's P0 and P1 issues and staff or close each one nobody
153
153
  has started:
154
- ```ts
155
- dispatch_issues({ project, priority: [0, 1], limit: 250 })
154
+ ```bash
155
+ dispatch issues --project <KEY> --priority 0 --priority 1 --limit 250
156
156
  ```
157
157
  Every unclaimed row in `triage`, `icebox`, `backlog` or `todo` is a decision: someone takes it and
158
158
  builds it, or it closes. A row in `in_progress`, `testing`, `needs_review` or `retro`, or one that
@@ -175,8 +175,8 @@ The audit finds four shapes:
175
175
  - **Unreachable route.** An open issue whose route names a role nobody holds, or a session that is
176
176
  not running, reaches nobody, whatever its priority, and the priority filter above never finds
177
177
  it. List it on its own:
178
- ```ts
179
- dispatch_issues({ project, route_status: "no_holder", limit: 250 })
178
+ ```bash
179
+ dispatch issues --project <KEY> --route-status no_holder --limit 250
180
180
  ```
181
181
  Each row reads `route role:sre (nobody holds it right now)` or `route session:<id> (that session
182
182
  is not running right now)`. That is one read of the listener, and one read cannot tell a
@@ -184,9 +184,9 @@ The audit finds four shapes:
184
184
  session is absent from the listener for minutes, and its role with it.
185
185
  So a route is unowned only when it is `no_holder` on two reads at least ten minutes apart: list
186
186
  again after ten minutes and act on the issues both lists name. Confirm with the second
187
- `dispatch_issues` read, not `envoy_role_get`: a role lookup releases the claim of a holder whose
187
+ `dispatch issues` read, not `envoy_role_get`: a role lookup releases the claim of a holder whose
188
188
  session is absent from the registry as it answers. Then staff the role, re-route the
189
- issue to a live holder, or clear the route and assign it. `route_status: "unknown"` means the
189
+ issue to a live holder, or clear the route and assign it. `--route-status unknown` means the
190
190
  listener did not answer, so a route could not be judged; a `no_holder` filter refuses rather
191
191
  than answer an empty list then.
192
192
 
@@ -195,19 +195,19 @@ are found by running the check, not by noticing them.
195
195
 
196
196
  ## Search paging
197
197
 
198
- `skill://dispatch` sends you here when a `dispatch_search` page is not enough: more hits remain,
198
+ `skill://dispatch` sends you here when a `dispatch search` page is not enough: more hits remain,
199
199
  or a kind runs out before the page does.
200
200
 
201
- A page holds `limit` hits (20 by default, 50 at most); the first line names how many match
202
- (`showing 1-20 of 312`), and, while more can be reached, the last line names the next `offset`.
203
- Each kind lists at most its best 100, so when the result says the rest cannot be paged to, narrow
204
- the query or name a `project`.
201
+ A page holds `--limit` hits (20 by default, 50 at most); the first line names how many match
202
+ (`showing 1-20 of 312`), and, while more can be reached, the last line names the next `--offset`.
203
+ Each kind lists at most its best 100, so when the output says the rest cannot be paged to, narrow
204
+ the query or name a `--project`.
205
205
 
206
206
  ## Syncing a project's architecture model
207
207
 
208
208
  Import a project's architecture model from its configured source repository now (a human configures the source in Settings):
209
- ```ts
210
- dispatch_architecture_sync({ project: "CORE" })
209
+ ```bash
210
+ dispatch architecture-sync --project CORE
211
211
  ```
212
- It returns the imported commit, or the recorded error when the model was rejected — the previous model stays up. Without a configured source it answers 404 `SOURCE_NOT_FOUND`.
212
+ It prints the imported commit, or the recorded error when the model was rejected — the previous model stays up. Without a configured source it answers 404 `SOURCE_NOT_FOUND`.
213
213
 
@@ -8,23 +8,19 @@ human messages you directly from the Agents page.
8
8
  A human — or any bearer caller over HTTP, such as a test rig — can target the issue message at a
9
9
  live Envoy session or role as **BTW**, **Aside**, or **Steer** (the dashboard calls a steer
10
10
  **Send**). The incoming Dispatch frame names
11
- the issue and includes a `reply_with` hint (`{ tool, args }`, ready to issue on any host); reply on the same open issue with the existing
12
- tool, never a new targeted send:
11
+ the issue and includes a `reply_with` hint (`{ command }`, the `dispatch` command line that answers it, with `...` for
12
+ the body); reply on the same open issue with that command, never a new targeted send:
13
13
 
14
- ```ts
15
- dispatch_message({
16
- issue: "CORE-1",
17
- in_reply_to: "<targeted-message-id>",
18
- body: "The requested answer.",
19
- })
14
+ ```bash
15
+ dispatch message --issue CORE-1 --in-reply-to <targeted-message-id> --body 'The requested answer.'
20
16
  ```
21
17
 
22
- `in_reply_to` correlates the answer under the asker's message in its Conversation card. A BTW
18
+ `--in-reply-to` correlates the answer under the asker's message in its Conversation card. A BTW
23
19
  delivery can post its answer automatically; use this call when the frame asks the primary agent to
24
20
  reply. A human may reply to your message in turn — the follow-up arrives as a targeted frame whose
25
21
  `in_reply_to` names your message and whose `reply_body` quotes it; answer it the same way,
26
- `dispatch_message({ issue, in_reply_to: "<their reply id>", body })`, so the exchange reads as one
27
- thread. `dispatch_message` itself never carries `target` or `delivery`: agent-to-agent traffic goes
22
+ `dispatch message --issue <KEY> --in-reply-to <their reply id> --body-file -`, so the exchange reads as one
23
+ thread. `dispatch message` itself never carries `target` or `delivery`: agent-to-agent traffic goes
28
24
  through Envoy or Oh My Pi's `agent://` messages. A bearer that targets over HTTP names its own
29
25
  session in `actor` (`{kind: "session", id}`), and the card shows that session as the author.
30
26
  `GET /api/v1/agents` (any authenticated caller) lists live sessions with their capabilities
@@ -37,38 +33,35 @@ A human can also message you directly from the **Agents** page, with no issue at
37
33
  Pi, a person's **Send** or **Aside** arrives as their own user message, exactly as if they had
38
34
  typed it at your terminal: your Envoy plugin takes it only once Dispatch accepts it as a person's
39
35
  own fresh message to you, and injects the text Dispatch stored. Answer it in the conversation as
40
- you would anything typed, with no `dispatch_message`; the Agents page shows your conversation
36
+ you would anything typed, with no `dispatch message`; the Agents page shows your conversation
41
37
  live, so they read your answer there.
42
38
 
43
39
  Everything else still arrives as a Dispatch frame: a **BTW**, a broadcast, a direct message on a
44
40
  host that takes no user turn from its plugin (Claude Code), and one Dispatch did not accept.
45
41
  That frame names no issue and its `reply_with` hint carries none either; answer it with the
46
- message's bare id in `in_reply_to`, alone:
42
+ message's bare id in `--in-reply-to`, alone:
47
43
 
48
- ```ts
49
- dispatch_message({
50
- in_reply_to: "<the direct message's id>",
51
- body: "The requested answer.",
52
- })
44
+ ```bash
45
+ dispatch message --in-reply-to <the direct message's id> --body 'The requested answer.'
53
46
  ```
54
47
 
55
- Leave `issue` out — there is no issue to post into, and naming one would file your answer on
48
+ Leave `--issue` out — there is no issue to post into, and naming one would file your answer on
56
49
  unrelated work. Dispatch threads the reply under their message in the same conversation, and the
57
50
  human sees it in your conversation on the Agents page, where it shows as an unread reply until
58
- they read it. Every other message still names its issue, so keep the `issue`
51
+ they read it. Every other message still names its issue, so keep the `--issue`
59
52
  the frame gave you whenever it gave you one; a `dispatch://KEY/message/<id>` reference names the
60
53
  issue its message lives on, so that form is a reply on that issue, not a direct message.
61
54
 
62
- Have more to say after you answered? Call it again with the same `in_reply_to` and the new text:
63
- Dispatch threads that follow-up under your first reply, and the tool result names the reply it
55
+ Have more to say after you answered? Run it again with the same `--in-reply-to` and the new text:
56
+ Dispatch threads that follow-up under your first reply, and the output names the reply it
64
57
  follows. The same text again posts nothing, so a retry is safe. Your host may already have
65
58
  answered a **BTW** automatically before you got here; a second call is then your follow-up to
66
- that answer, so read the result before writing again.
59
+ that answer, so read the output before writing again.
67
60
 
68
61
  Read the whole conversation back — their message and every reply, yours included — with the
69
62
  message id alone; it has no issue:
70
63
 
71
- ```ts
72
- dispatch_read({ message: "<the direct message's id, or any reply's>" })
64
+ ```bash
65
+ dispatch read --message <the direct message's id, or any reply's>
73
66
  ```
74
67
 
@@ -5,16 +5,16 @@ message or document, trace what cites a node, or need the exact `dispatch://` fo
5
5
 
6
6
  ## What comes back
7
7
 
8
- After a restart, catch up with:
8
+ After a restart, catch up with `dispatch read` (`--issue`, or `--project` and `--artifact`, or `--ref`):
9
9
 
10
- ```ts
11
- dispatch_read({ issue?, project?, artifact?, ref? })
10
+ ```bash
11
+ dispatch read --issue <KEY>
12
12
  ```
13
13
 
14
- With an issue ref, it returns the issue summary, open asks, references, and recent events with `details` `{ issue }`. With a project
15
- document owner or ref, it returns a document summary with `details` `{ project, document }`. With an ask ref, it returns that ask's
16
- question, options, state, answer, and its reply thread. With a comment ref, it returns that comment and its quoted reply chain. With a
17
- message ref, it returns that message and its reply chain. Reads do not subscribe; use `dispatch_doc_read` for document contents.
14
+ With an issue ref, it prints the issue summary, open asks, references, and recent events. With a project
15
+ document owner or ref, it prints a document summary. With an ask ref, it prints that ask's
16
+ question, options, state, answer, and its reply thread. With a comment ref, it prints that comment and its quoted reply chain. With a
17
+ message ref, it prints that message and its reply chain. Reads do not subscribe; use `dispatch doc-read` for document contents.
18
18
 
19
19
  An anchored comment or ask also prints `Position:`, where its quote's block stands — `table[3] › row 5 (Red-teamer loop), column Due`
20
20
  is the fourth top-level block, a table, its row 5 (row 0 is the header; the index `delete_row` takes), labelled by the row's cells
@@ -31,7 +31,7 @@ followers — and `Links:` lists what it cites. Each row is `- <edge kind> <node
31
31
  document source the excerpt is the start of the block holding the mention, and a whole list is one block, so every issue named in
32
32
  one list previews the list's first item. When a document references many issues and each backlink should read right, give each
33
33
  issue its own paragraph (or block), not an item of one list. Cross-project, always: a message on another project's issue that
34
- cites an ask shows up under that ask. So "what led to this decision" is one `dispatch_read` on the ask, and "who relies on this
34
+ cites an ask shows up under that ask. So "what led to this decision" is one `dispatch read` on the ask, and "who relies on this
35
35
  document" one read on the document. Cite with `dispatch://` references (below) whenever you name a node in a body — a bare id or
36
36
  title is invisible to the graph.
37
37
 
@@ -40,7 +40,7 @@ title is invisible to the graph.
40
40
  Use these in document, ask, comment, and message bodies. In the dashboard, a reference renders
41
41
  as an inline link whose text is the target's title (an issue's title, an ask's question, a
42
42
  comment's first line, a document's name) once it resolves; a body that is only a bare reference
43
- still gets an unfurl card instead. Every `ref` argument below (and `issue`/`project`) accepts
43
+ still gets an unfurl card instead. Every `--ref` flag below (and `--issue`/`--project`) accepts
44
44
  either form — an issue key or a project key is never ambiguous, since a project key never
45
45
  contains a dash:
46
46
 
@@ -56,7 +56,7 @@ dispatch://PROJECT/artifact/<document-ref>/ask/<id>
56
56
  dispatch://PROJECT/artifact/<document-ref>/comment/<id>
57
57
  ```
58
58
 
59
- A bare UUID or `KEY#seq` is not a reference; the `dispatch://` form is what Dispatch links and records. `dispatch_read` also
59
+ A bare UUID or `KEY#seq` is not a reference; the `dispatch://` form is what Dispatch links and records. `dispatch read` also
60
60
  accepts the dashboard URL of an issue, spec, artifact, ask, comment, or project document on the configured server (it maps to the
61
61
  `dispatch://` form above), and an ask or comment id may be a unique prefix of at least 8 hex characters; a message id is always the
62
62
  full uuid. A non-uuid id on `GET /asks/{id}`, `/comments/{id}`, or `/issues/{key}/messages/{id}` is a 400 `ASK_ID_INPUT` /
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dispatch-brainstorming
3
- description: "Use in a session with Dispatch tools whenever a design conversation with a person starts: brainstorming or /brainstorming, \"let's design\", writing or changing a spec, writing an implementation plan, or a feature request or change that needs a person's choices. In a session with Dispatch it replaces superpowers' brainstorming and writing-plans; a Legion architect's own spec follows skill://legion-architect instead."
3
+ description: "Use in a session with Dispatch whenever a design conversation with a person starts: brainstorming or /brainstorming, \"let's design\", writing or changing a spec, writing an implementation plan, or a feature request or change that needs a person's choices. In a session with Dispatch it replaces superpowers' brainstorming and writing-plans; a Legion architect's own spec follows skill://legion-architect instead."
4
4
  ---
5
5
 
6
6
  # Brainstorming in Dispatch
@@ -34,19 +34,21 @@ decision to its architect (`skill://legion-worker`).
34
34
  ## Where the spec lives
35
35
 
36
36
  Search Dispatch first, as `skill://dispatch-first` says. When an issue already tracks the work, its
37
- primary document is the spec: extend it in place with `dispatch_doc_edit`, keeping the human's own
37
+ primary document is the spec: extend it in place with `dispatch doc-edit`, keeping the human's own
38
38
  text. Otherwise create the issue with its first version as `spec`, in the project a search hit
39
39
  already named or, when none does, the project the human names:
40
40
 
41
- ```ts
42
- dispatch_issue({ project, title, spec })
41
+ ```bash
42
+ dispatch issue --project <KEY> --title '<title>' --spec-file - <<'EOF'
43
+ <the spec's first version>
44
+ EOF
43
45
  ```
44
46
 
45
- No tool lists every project, so when nothing you found names one, ask the human in chat which
47
+ No command lists every project, so when nothing you found names one, ask the human in chat which
46
48
  project the issue belongs in before you create it — a filing question with nowhere else to land
47
49
  yet, not a design question, so it does not change "your reply in chat links the issue and asks
48
50
  nothing" for the design itself. Never read the project from the environment, a configuration file,
49
- or the Dispatch HTTP API: every read and write goes through a `dispatch_*` tool.
51
+ or the Dispatch HTTP API: every read and write goes through a `dispatch` command.
50
52
 
51
53
  Before the first version, read the code and documents the design touches, so the facts you write
52
54
  are ones you checked.
@@ -65,11 +67,11 @@ are ones you checked.
65
67
  nothing is settled until the human has seen it, so even a flagged inference waits for its own
66
68
  decision block.
67
69
  2. **The human answers or comments.** Reply to each human comment in its thread
68
- (`dispatch_comment` with `reply_to`), then rewrite the passage the answer or the comment changes.
69
- Under an open ask whose next move is yours, such as the approval request you must revise or
70
- hand back, reply with `reply_to_ask` and `turn: "agent"` instead: a `reply_to` reply takes the
71
- default turn, which hands the request back to the human unchanged, and a call with a corrected
72
- `summary` is refused while it waits on them.
70
+ (`dispatch comment --reply-to <comment id>`), then rewrite the passage the answer or the comment
71
+ changes. Under an open ask whose next move is yours, such as the approval request you must
72
+ revise or hand back, reply with `--reply-to-ask <ask id> --turn agent` instead: a `--reply-to`
73
+ reply takes the default turn, which hands the request back to the human unchanged, and a call
74
+ with a corrected `--summary` is refused while it waits on them.
73
75
  3. **Each next version folds the answers in and adds what they open.** Keep each answered
74
76
  decision block where it is, fold its answer into the surrounding text in the human's words with
75
77
  the date, recording the person as `skill://dispatch`'s "Writing a spec" says for a settled
@@ -77,7 +79,7 @@ are ones you checked.
77
79
  at once, each as a decision block at the end of the section that sets it up; a question waits
78
80
  only when it depends on an answer still open.
79
81
  4. **A comment that answers a question settles it** as surely as the block does. Fold it into the
80
- text at once, and close the block with `dispatch_resolve_ask` if the human has not.
82
+ text at once, and close the block with `dispatch resolve-ask` if the human has not.
81
83
  5. **Request approval at the end, not after each section.** Once nothing in the spec is new to the
82
84
  human, request it exactly when "Approval of a spec" in `skill://dispatch` says: every decision
83
85
  block settled and folded in, the human has agreed to every point in the spec, and the current
@@ -97,8 +99,10 @@ Once the spec is approved — or, when the issue needed no spec at all (a bug fi
97
99
  decision), once you have enough to plan from — the implementation plan is the issue's `plan.md`
98
100
  document, never its spec and never a repository file:
99
101
 
100
- ```ts
101
- dispatch_artifact({ issue, name: "plan.md", content })
102
+ ```bash
103
+ dispatch artifact --issue <KEY> --name plan.md --content-file - <<'EOF'
104
+ <the plan>
105
+ EOF
102
106
  ```
103
107
 
104
108
  Uploading `plan.md` again writes its next version. `writing-plans`'s content rules still apply in
@@ -108,6 +112,6 @@ Technical choices inside the approved design are made in the plan and need no qu
108
112
  never changes a decision the human made in the spec: a change to one goes back to the spec as a
109
113
  decision block, with the evidence for it.
110
114
 
111
- Execution reads the plan the same way: `dispatch_doc_read({ issue, artifact: "plan.md" })` in place
115
+ Execution reads the plan the same way: `dispatch doc-read --issue <KEY> --artifact plan.md` in place
112
116
  of opening a plan file, then follows `executing-plans` or `subagent-driven-development` as it would
113
117
  for a file on disk.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dispatch-first
3
- description: "Use in any session with Dispatch tools, before planning, filing an issue, asking a human, posting a finding, or starting work that someone may already track or have decided."
3
+ description: "Use in any session with the dispatch command, before planning, filing an issue, asking a human, posting a finding, or starting work that someone may already track or have decided."
4
4
  ---
5
5
 
6
6
  # Dispatch first
@@ -11,18 +11,18 @@ answered, costs that human the time to notice it and splits the history across t
11
11
 
12
12
  ## Search before you act
13
13
 
14
- Before you plan, file an issue, ask, post a finding or start work, search Dispatch. Every word of
14
+ Before you plan, file an issue, ask, post a finding or start work, search Dispatch with the `dispatch` command in your shell (`dispatch --help` lists every command). Every word of
15
15
  the query must match, so each word you add can only lose hits: search with two or three words, the
16
16
  thing and what is wrong with it, as a user would name them. Never paste a draft.
17
17
 
18
- ```ts
19
- dispatch_search({ query: "broadcast send order" })
20
- dispatch_search({ query: "reviewer threads OR review comments" })
18
+ ```bash
19
+ dispatch search --query 'broadcast send order'
20
+ dispatch search --query 'reviewer threads OR review comments'
21
21
  ```
22
22
 
23
23
  When a query finds nothing, drop a word before you add one. Try two or three wordings (the
24
24
  component's name, the symptom, the fix) before you conclude that nothing exists. Open every hit
25
- that could be yours with `dispatch_read`, then its parent (the `child_of` row under `Links:`); the
25
+ that could be yours with `dispatch read`, then its parent (the `child_of` row under `Links:`); the
26
26
  parent's children and the issue's `Components:` line show where the rest of that work lives.
27
27
 
28
28
  ## What to do with what you find
@@ -38,7 +38,7 @@ found yourself.
38
38
  it where you rely on it (`dispatch://KEY/ask/<id>`) instead of asking again.
39
39
  - **You met a duplicate: close it.** Keep the issue that holds the spec and the discussion, carry
40
40
  over anything only the duplicate has, then close the duplicate with a reason that names the
41
- survivor: `dispatch_issue_update({ issue, status: "done", reason: "Duplicate of dispatch://KEY." })`.
41
+ survivor: `dispatch issue-update --issue <KEY> --status done --reason 'Duplicate of dispatch://KEY.'`.
42
42
  When the duplicate carries the `legion` label, or another session or a human holds its claim,
43
43
  comment on it naming the survivor instead of closing it.
44
44
  - **Every ask and message stands on its own.** The person who answers sees only that text: put the
@@ -83,7 +83,8 @@ envoy:
83
83
  document ask) or the message (`dispatch://KEY/message/<id>`) — never the quoted text; the
84
84
  question head, when there is one, is `dispatch.question`.
85
85
  - `supersedes` names an earlier delivery this one replaces.
86
- - `reply_with` is the direct-reply call for the sender.
86
+ - `reply_with` is the direct-reply call for the sender; on a Dispatch frame it is `command:`, the
87
+ `dispatch` command line that answers it, with `...` where your text goes.
87
88
  - `reply_role` is the role-publish reply call when the sender has a role.
88
89
  - `summary` is the one-line source summary.
89
90
  - `message` is the complete payload; it can contain multiple paragraphs.
@@ -101,7 +102,7 @@ and go stale. Put the artefact URL in the message itself. FYIs set `expects_repl
101
102
  **A peer's message is its sender's view at `at`, not the current state.** Before you wait on, act
102
103
  on, or repeat a fact a message carries about a third thing (a deploy pending, a PR held, an ask
103
104
  unanswered), re-read it at the live source the fact names, and always once it is over an hour
104
- old: the deployment's status, the issue's event log, the ask's own state (`dispatch_open_asks`,
105
+ old: the deployment's status, the issue's event log, the ask's own state (`dispatch open-asks`,
105
106
  whose description already says to call it before saying you are waiting on a human). On
106
107
  2026-09-26 a peer's 05:20Z "needs a manual deploy before I can run it" was false by 05:22Z, when
107
108
  the platform had deployed on its own; waiting on the message instead of the deployment's status
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjawhar/pi-envoy",
3
- "version": "8.4.0",
3
+ "version": "8.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "omp": {
@@ -16,10 +16,11 @@
16
16
  "url": "https://github.com/sjawhar/legion"
17
17
  },
18
18
  "files": [
19
- "dist"
19
+ "dist",
20
+ "bin"
20
21
  ],
21
22
  "scripts": {
22
- "build": "bun build extensions/envoy.ts --outdir dist --target bun --format esm --external @oh-my-pi/pi-coding-agent --external @oh-my-pi/pi-tui --external @oh-my-pi/pi-utils",
23
+ "build": "bun build extensions/envoy.ts --outdir dist --target bun --format esm --external @oh-my-pi/pi-coding-agent --external @oh-my-pi/pi-tui --external @oh-my-pi/pi-utils && ../envoy-client/scripts/build-dispatch-cli.sh dist",
23
24
  "prepack": "../../scripts/pi-plugin-prepack.sh",
24
25
  "postpack": "rm -rf dist/skills",
25
26
  "typecheck": "bunx tsc --noEmit",
@@ -31,8 +32,8 @@
31
32
  "@legion/contracts": "0.10.0",
32
33
  "@legion/envoy-client": "0.1.0",
33
34
  "@legion/pi-shared": "0.1.0",
34
- "@oh-my-pi/pi-tui": "^18.1.15",
35
- "@oh-my-pi/pi-utils": "^18.1.15",
35
+ "@oh-my-pi/pi-tui": "^18.6.0",
36
+ "@oh-my-pi/pi-utils": "^18.6.0",
36
37
  "@toon-format/toon": "^4.1.1",
37
38
  "@types/bun": "latest",
38
39
  "nats": "^2.29.2",