@detergent-software/conductor 1.4.0-dev.1 → 1.4.0-dev.11
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.
- package/CHANGELOG.md +102 -0
- package/README.md +86 -20
- package/build/brief-cli.js +105 -23
- package/build/brief-cli.js.map +1 -1
- package/build/brief.js +107 -9
- package/build/brief.js.map +1 -1
- package/build/broker.js +40 -14
- package/build/broker.js.map +1 -1
- package/build/cli.js +49 -18
- package/build/cli.js.map +1 -1
- package/build/code-host-cli.js +46 -2
- package/build/code-host-cli.js.map +1 -1
- package/build/code-host.js +2 -0
- package/build/code-host.js.map +1 -1
- package/build/conditions.js +22 -13
- package/build/conditions.js.map +1 -1
- package/build/config.js +4 -0
- package/build/config.js.map +1 -1
- package/build/daemon.js +74 -19
- package/build/daemon.js.map +1 -1
- package/build/follow-ups.js +72 -0
- package/build/follow-ups.js.map +1 -0
- package/build/help.js +712 -208
- package/build/help.js.map +1 -1
- package/build/in-process.js +30 -0
- package/build/in-process.js.map +1 -0
- package/build/inbox.js +137 -11
- package/build/inbox.js.map +1 -1
- package/build/lib/providers/ado/code-host.js +8 -5
- package/build/lib/providers/ado/code-host.js.map +1 -1
- package/build/lib/providers/ado/pipelines.js +16 -10
- package/build/lib/providers/ado/pipelines.js.map +1 -1
- package/build/lib/providers/github/actions.js +11 -0
- package/build/lib/providers/github/actions.js.map +1 -1
- package/build/lib/providers/github/code-host.js +17 -0
- package/build/lib/providers/github/code-host.js.map +1 -1
- package/build/lib/providers/index.js +1 -1
- package/build/lib/providers/index.js.map +1 -1
- package/build/lib/providers/verdicts/pr.js +14 -12
- package/build/lib/providers/verdicts/pr.js.map +1 -1
- package/build/manager.js +116 -13
- package/build/manager.js.map +1 -1
- package/build/note.js +95 -14
- package/build/note.js.map +1 -1
- package/build/notes.js +104 -16
- package/build/notes.js.map +1 -1
- package/build/notify.js +2 -1
- package/build/notify.js.map +1 -1
- package/build/package-info.js +12 -0
- package/build/package-info.js.map +1 -0
- package/build/profile.js +64 -13
- package/build/profile.js.map +1 -1
- package/build/project-check-cli.js +85 -0
- package/build/project-check-cli.js.map +1 -0
- package/build/project-check.js +362 -0
- package/build/project-check.js.map +1 -0
- package/build/resolve.js +11 -3
- package/build/resolve.js.map +1 -1
- package/build/rule-actions.js +26 -6
- package/build/rule-actions.js.map +1 -1
- package/build/rules-service.js +33 -20
- package/build/rules-service.js.map +1 -1
- package/build/rules.js +31 -14
- package/build/rules.js.map +1 -1
- package/build/runs.js.map +1 -1
- package/build/scheduler.js +43 -1
- package/build/scheduler.js.map +1 -1
- package/build/server.js +76 -7
- package/build/server.js.map +1 -1
- package/build/spend.js +37 -4
- package/build/spend.js.map +1 -1
- package/build/store.js +42 -4
- package/build/store.js.map +1 -1
- package/build/tracker-writes.js +74 -0
- package/build/tracker-writes.js.map +1 -0
- package/build/trackers.js +20 -0
- package/build/trackers.js.map +1 -1
- package/build/watchers/ado.js +7 -1
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/boards-writes.js +182 -34
- package/build/watchers/boards-writes.js.map +1 -1
- package/build/watchers/boards.js +7 -2
- package/build/watchers/boards.js.map +1 -1
- package/build/watchers/github.js +20 -2
- package/build/watchers/github.js.map +1 -1
- package/build/watchers/index.js +32 -4
- package/build/watchers/index.js.map +1 -1
- package/build/watchers/issues-brief.js +48 -0
- package/build/watchers/issues-brief.js.map +1 -0
- package/build/watchers/issues-queue.js +85 -0
- package/build/watchers/issues-queue.js.map +1 -0
- package/build/watchers/issues-writes.js +233 -0
- package/build/watchers/issues-writes.js.map +1 -0
- package/build/watchers/issues.js +264 -0
- package/build/watchers/issues.js.map +1 -0
- package/build/watchers/linear-writes.js +198 -0
- package/build/watchers/linear-writes.js.map +1 -0
- package/build/watchers/source.js.map +1 -1
- package/build/worktrees.js +1 -0
- package/build/worktrees.js.map +1 -1
- package/mod/hooks/model.ts +11 -0
- package/mod/hooks/register.tsx +25 -3
- package/package.json +1 -1
package/build/help.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
1
2
|
import { ConductorError } from "./errors.js";
|
|
2
3
|
// By task, in the order a reader needs them; the commands inside each are listed A–Z.
|
|
3
4
|
export const GROUPS = [
|
|
@@ -11,224 +12,449 @@ export const GROUPS = [
|
|
|
11
12
|
'Setup',
|
|
12
13
|
];
|
|
13
14
|
const DEFAULT_WIDTH = 80;
|
|
15
|
+
// Wider than this, a paragraph is too long a line to read.
|
|
16
|
+
const MAX_WIDTH = 100;
|
|
14
17
|
// Narrower than this, a hanging indent leaves no room for the text.
|
|
15
18
|
const MIN_WIDTH = 40;
|
|
16
19
|
// A longer term goes on a line of its own, with its description under it.
|
|
17
20
|
const MAX_TERM = 24;
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
+
const LOOKS = { command: 'cyan', heading: 'bold', placeholder: 'dim' };
|
|
22
|
+
const ITEM = [
|
|
23
|
+
'--item <item>',
|
|
24
|
+
"the item, when neither the worktree's name nor its branch carries it",
|
|
25
|
+
];
|
|
26
|
+
const LOST_LOGIN = [5, 'a lost provider login: run conduct wait login <provider>'];
|
|
27
|
+
const PR_REPO = [
|
|
28
|
+
'--repo <repo>',
|
|
29
|
+
"github:<owner>/<repo> or ado:<repository>; the profile's code host by default",
|
|
30
|
+
];
|
|
31
|
+
const RANGE = [
|
|
32
|
+
'<range>',
|
|
33
|
+
'today, yesterday, a date (2026-10-10), A..B (2026-10-10..2026-10-11), or a span (24h, the default; 7d)',
|
|
34
|
+
];
|
|
35
|
+
const WHEN = [
|
|
36
|
+
'<when>',
|
|
37
|
+
'17:00 (the next one), tomorrow, a day such as monday, or a date and time',
|
|
38
|
+
];
|
|
39
|
+
const WINDOW = [
|
|
40
|
+
'<when>',
|
|
41
|
+
'for --since or --until: now, today, yesterday, a span ago (30m, 24h, 7d), a date, or a date and time',
|
|
42
|
+
];
|
|
43
|
+
const WORKER = ['<worker>', 'its item (ATK-309) or its name (probe-ATK-309)'];
|
|
44
|
+
const REPORT_OPTIONS = [
|
|
45
|
+
['--json', 'print the report as JSON'],
|
|
46
|
+
['--project <name>', 'one project only'],
|
|
47
|
+
['--since <when>', 'the window starts then'],
|
|
48
|
+
['--until <when>', 'the window ends then'],
|
|
49
|
+
];
|
|
21
50
|
export const COMMANDS = [
|
|
22
51
|
{
|
|
23
|
-
|
|
24
|
-
'
|
|
25
|
-
|
|
52
|
+
arguments: [
|
|
53
|
+
['<item>', "the item (ATK-312), or the inbox item's id as '#12' (quoted: a shell reads #12 as a comment)"],
|
|
54
|
+
],
|
|
55
|
+
description: ['Posts "approve" on the item as you and wakes its worker.'],
|
|
56
|
+
examples: [
|
|
57
|
+
["approve ATK-312's plan", 'approve ATK-312'],
|
|
58
|
+
['the same, with a word for the worker', 'approve ATK-312 --text "skip the docs"'],
|
|
26
59
|
],
|
|
27
60
|
group: 'Inbox and approvals',
|
|
28
61
|
name: 'approve',
|
|
62
|
+
options: [['--text <text>', 'add to the comment: "approve: <text>"']],
|
|
63
|
+
seeAlso: ['revise', 'inbox'],
|
|
29
64
|
summary: 'approve a plan and wake its worker',
|
|
30
65
|
usage: ['approve <item> [--text <text>]'],
|
|
31
66
|
},
|
|
32
67
|
{
|
|
33
|
-
|
|
68
|
+
arguments: [WORKER],
|
|
69
|
+
description: ['Wakes a parked worker (with no event text) and opens its session in a tab.'],
|
|
70
|
+
examples: [["open ATK-309's worker in a tab", 'attach ATK-309']],
|
|
34
71
|
group: 'Projects and workers',
|
|
35
72
|
name: 'attach',
|
|
73
|
+
seeAlso: ['launch', 'retire', 'status'],
|
|
36
74
|
summary: 'wake a parked worker and open it in a tab',
|
|
37
75
|
usage: ['attach <worker>'],
|
|
38
76
|
},
|
|
39
77
|
{
|
|
40
|
-
|
|
41
|
-
|
|
78
|
+
arguments: [
|
|
79
|
+
['<type>', 'a conventional commit type: feat, fix, chore, docs…'],
|
|
80
|
+
['<slug>', 'a few words for the branch, joined by dashes'],
|
|
81
|
+
],
|
|
82
|
+
description: [
|
|
83
|
+
"Renames the worktree's branch (worktree-ATK-454) to <type>/<key>-<slug>, such as feat/atk-454-code-host-writes, with the item from the worktree's name (or --item).",
|
|
42
84
|
'Prints JSON. Runs here, with or without a conductor.',
|
|
43
85
|
],
|
|
86
|
+
examples: [['in the ATK-454 worktree: feat/atk-454-code-host-writes', 'branch feat code-host-writes']],
|
|
44
87
|
group: 'Used by work skills',
|
|
45
88
|
name: 'branch',
|
|
89
|
+
options: [ITEM],
|
|
90
|
+
seeAlso: ['checkout', 'state'],
|
|
46
91
|
summary: "rename the worktree's branch after its item",
|
|
47
92
|
usage: ['branch <type> <slug> [--item <item>]'],
|
|
48
93
|
},
|
|
49
94
|
{
|
|
50
|
-
|
|
95
|
+
arguments: [['<item>', 'the item (ATK-312)']],
|
|
96
|
+
description: [
|
|
51
97
|
"The item's context for a worker's orient step, read by the conductor from the tracker (no MCP): title, status, project, Area and labels, blockers, the items it blocks and related items, its phase notes, the approve or revise: after the newest Plan, and the description.",
|
|
52
|
-
|
|
53
|
-
|
|
98
|
+
"Also the work skill's phase (orient, plan, awaiting-approval, implement, wait, land, done, abandoned, canceled), from the status, the notes and the PR; and the PR (its worker's, else the newest PR opened note's) as its code host has it, with its verdict.",
|
|
99
|
+
'A brief is kept for 30 seconds, and anything the conductor sees happen to the item (a note, an approve or revise it posted, a wait resolved) drops it.',
|
|
100
|
+
"With no conductor, it is read here from this checkout's project, as its account.",
|
|
101
|
+
],
|
|
102
|
+
examples: [
|
|
103
|
+
['orient on ATK-312', 'brief ATK-312'],
|
|
104
|
+
['check for an approval after a wake', 'brief ATK-312 --fresh'],
|
|
105
|
+
],
|
|
106
|
+
exitCodes: [
|
|
107
|
+
[
|
|
108
|
+
3,
|
|
109
|
+
"nobody gave it (no conductor and this isn't the item's project checkout, or the conductor couldn't read the tracker): read the item from the tracker then",
|
|
110
|
+
],
|
|
54
111
|
],
|
|
55
112
|
group: 'Used by work skills',
|
|
56
113
|
name: 'brief',
|
|
57
|
-
|
|
58
|
-
|
|
114
|
+
options: [
|
|
115
|
+
['--comments', "add every comment's words"],
|
|
116
|
+
['--fresh', 'read the tracker again, as a check for an approval after a wake should'],
|
|
117
|
+
['--json', 'print it as JSON'],
|
|
118
|
+
],
|
|
119
|
+
seeAlso: ['note', 'wait'],
|
|
120
|
+
summary: "an item's status, phase, PR, links, notes and approval",
|
|
121
|
+
usage: ['brief <item> [--json] [--fresh] [--comments]'],
|
|
59
122
|
},
|
|
60
123
|
{
|
|
61
|
-
|
|
124
|
+
arguments: [
|
|
125
|
+
['<owner>/<repo>', 'the GitHub repo'],
|
|
126
|
+
['<type>', 'a conventional commit type for the branch'],
|
|
127
|
+
['<slug>', 'a few words for the branch, joined by dashes'],
|
|
128
|
+
],
|
|
129
|
+
description: [
|
|
62
130
|
'Clones another GitHub repo into the worktree at .scratch/repos/<repo>, on <type>/<key>-<slug> from its default branch, with no upstream until the first push. Again on an existing checkout, it says where it is and changes nothing.',
|
|
63
131
|
"The clone stays out of the worktree's git status, and conduct retire checks it for unpushed work.",
|
|
64
132
|
'Prints JSON. Runs here.',
|
|
65
133
|
],
|
|
134
|
+
examples: [
|
|
135
|
+
['clone the registry for a skill fix', 'checkout EmergentSoftware/agentic-toolkit-registry fix lock-path'],
|
|
136
|
+
],
|
|
66
137
|
group: 'Used by work skills',
|
|
67
138
|
name: 'checkout',
|
|
139
|
+
options: [ITEM],
|
|
140
|
+
seeAlso: ['branch', 'state', 'pr'],
|
|
68
141
|
summary: 'clone another repo into the worktree for the item',
|
|
69
142
|
usage: ['checkout <owner>/<repo> <type> <slug> [--item <item>]'],
|
|
70
143
|
},
|
|
71
144
|
{
|
|
72
|
-
|
|
73
|
-
"Opens the console, the Claude session the conductor mod serves, in a Windows Terminal tab in the project's main checkout.
|
|
145
|
+
description: [
|
|
146
|
+
"Opens the console, the Claude session the conductor mod serves, in a Windows Terminal tab in the project's main checkout.",
|
|
74
147
|
'The tab needs Windows Terminal, and refuses while CONDUCTOR_HOME is set (a tab would not see it): use --here then.',
|
|
75
148
|
'Also how to get it back after it closed: it keeps no state of its own.',
|
|
76
|
-
'One at a time: it refuses while a console is open, or started in the last minute and not yet following the events
|
|
149
|
+
'One at a time: it refuses while a console is open, or started in the last minute and not yet following the events.',
|
|
150
|
+
],
|
|
151
|
+
examples: [
|
|
152
|
+
['open the console in a new tab', 'console'],
|
|
153
|
+
['run it in this terminal', 'console --here'],
|
|
77
154
|
],
|
|
78
155
|
group: 'Console and rules',
|
|
79
156
|
name: 'console',
|
|
157
|
+
options: [
|
|
158
|
+
['--force', 'open another console anyway; each escalation then wakes both'],
|
|
159
|
+
['--here', 'run it in this terminal, not a tab'],
|
|
160
|
+
['--project <name>', 'the project whose checkout it runs in; needed when several are registered'],
|
|
161
|
+
],
|
|
162
|
+
seeAlso: ['escalations', 'inbox', 'install-mod'],
|
|
80
163
|
summary: 'open the console, the session the conductor mod serves',
|
|
81
164
|
usage: ['console [--project <name>] [--here] [--force]'],
|
|
82
165
|
},
|
|
83
166
|
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
],
|
|
167
|
+
arguments: [['<id>', 'the inbox id as conduct inbox prints it (#12, or 12)']],
|
|
168
|
+
description: ['For a physical action, or one you handled yourself.'],
|
|
169
|
+
examples: [['you approved the npm stage of inbox item #12 yourself', 'done 12']],
|
|
87
170
|
group: 'Inbox and approvals',
|
|
88
171
|
name: 'done',
|
|
172
|
+
seeAlso: ['inbox', 'approve'],
|
|
89
173
|
summary: 'mark an inbox item done',
|
|
90
174
|
usage: ['done <id>'],
|
|
91
175
|
},
|
|
92
176
|
{
|
|
93
|
-
|
|
177
|
+
commands: [
|
|
178
|
+
['escalations [--json]', "what rules escalated to the console in the last 14 days and it hasn't handled"],
|
|
94
179
|
[
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
'done <id> [--ask] <text>',
|
|
98
|
-
'log the console\'s finding on escalation #<id>; --ask also puts it in the inbox for you (a "console" item). Every word after <id> is the text',
|
|
99
|
-
],
|
|
180
|
+
'done <id> [--ask] <text>',
|
|
181
|
+
"log the console's finding on escalation #<id>. Every word after <id> is the text, quoted or not",
|
|
100
182
|
],
|
|
101
183
|
],
|
|
184
|
+
description: ["The console's work: what the rules escalated to it, and its findings on them."],
|
|
185
|
+
examples: [
|
|
186
|
+
['what waits on the console', 'escalations'],
|
|
187
|
+
['log a finding on #7 and put it in the inbox', 'escalations done 7 --ask the PR needs a merge of develop'],
|
|
188
|
+
],
|
|
102
189
|
group: 'Console and rules',
|
|
103
190
|
name: 'escalations',
|
|
191
|
+
options: [
|
|
192
|
+
['--ask', 'with done: also put the finding in the inbox for you (a "console" item)'],
|
|
193
|
+
['--json', 'print the escalations as JSON'],
|
|
194
|
+
],
|
|
195
|
+
seeAlso: ['console', 'rules', 'why'],
|
|
104
196
|
summary: "what rules escalated to the console and it hasn't handled",
|
|
105
197
|
usage: ['escalations [--json]', 'escalations done <id> [--ask] <text>'],
|
|
106
198
|
},
|
|
107
199
|
{
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
'
|
|
111
|
-
|
|
200
|
+
description: ['The event log, one JSON event per line.'],
|
|
201
|
+
examples: [
|
|
202
|
+
['every event after 4200', 'events --since 4200'],
|
|
203
|
+
['follow the log live', 'events --follow'],
|
|
112
204
|
],
|
|
113
205
|
group: 'Logs and reports',
|
|
114
206
|
name: 'events',
|
|
207
|
+
options: [
|
|
208
|
+
[
|
|
209
|
+
'--console',
|
|
210
|
+
"mark the stream as the console's, as the conductor mod runs it (or CONDUCTOR_CONSOLE=1): with none open, escalations raise an inbox item after 30 s",
|
|
211
|
+
],
|
|
212
|
+
[
|
|
213
|
+
'--follow',
|
|
214
|
+
'keep following it (live without --since), reconnecting when the conductor restarts; the state lines conduct.connected and conduct.disconnected say so',
|
|
215
|
+
],
|
|
216
|
+
['--since <id>', 'the events after <id>; all of them without it'],
|
|
217
|
+
],
|
|
218
|
+
seeAlso: ['history', 'why'],
|
|
115
219
|
summary: 'the event log, one JSON event per line',
|
|
116
220
|
usage: ['events [--since <id>]', 'events --follow [--since <id>] [--console]'],
|
|
117
221
|
},
|
|
118
222
|
{
|
|
119
|
-
|
|
120
|
-
"
|
|
121
|
-
|
|
223
|
+
description: [
|
|
224
|
+
"Files a follow-up of this checkout's item (or --item) on its tracker: in Backlog, in the item's project, with its Area, the profile's triage label, and the human label too with --human. It is related to the item and to each --related, and waits on each --blocked-by.",
|
|
225
|
+
"Search the tracker for a duplicate first: this only files. An Area outside the group, or a key of another project, is refused before anything is written. On Linear a relation that fails once the issue exists (such as an item it doesn't have) is a warning; on ADO Boards the item and its links are one request, so a bad link refuses the whole filing. On GitHub Issues the related items are a Related: line in its body, and a dependency that fails is a warning.",
|
|
226
|
+
'With a conductor, the daemon files it for the worker that owns this checkout and records it; without one it is filed here. Prints JSON with "via".',
|
|
227
|
+
],
|
|
228
|
+
examples: [
|
|
229
|
+
[
|
|
230
|
+
'a follow-up in apps/conductor that waits on ATK-480',
|
|
231
|
+
'file --title "conductor: color the status" --body-file follow-up.md --area apps/conductor --blocked-by ATK-480',
|
|
232
|
+
],
|
|
233
|
+
],
|
|
234
|
+
exitCodes: [[1, 'refused or failed'], LOST_LOGIN],
|
|
235
|
+
group: 'Used by work skills',
|
|
236
|
+
name: 'file',
|
|
237
|
+
options: [
|
|
238
|
+
[
|
|
239
|
+
'--area <area>',
|
|
240
|
+
"one of the tracker's Areas: a label of the Area group, a path below a board's root, or the name after a GitHub Issues area prefix",
|
|
241
|
+
],
|
|
242
|
+
['--blocked-by <item>…', 'the items it waits on; repeated or comma-separated'],
|
|
243
|
+
['--body-file <file>', 'its description'],
|
|
244
|
+
['--human', 'add the human label too'],
|
|
245
|
+
['--item <item>', "the item it follows up, when neither the worktree's name nor its branch carries it"],
|
|
246
|
+
['--related <item>…', 'more items it is related to; repeated or comma-separated'],
|
|
247
|
+
['--title <title>', 'its title'],
|
|
248
|
+
],
|
|
249
|
+
seeAlso: ['brief', 'note'],
|
|
250
|
+
summary: 'file a follow-up of the item, in Backlog with its Area',
|
|
251
|
+
usage: [
|
|
252
|
+
'file --title <title> --body-file <file> --area <area> [--related <item>…] [--blocked-by <item>…] [--human] [--item <item>]',
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
arguments: [['<project>', 'a registered project'], WHEN],
|
|
257
|
+
description: [
|
|
258
|
+
"Sets the project's slots or ordering weight until <when>; with neither, its items go first (weight 100).",
|
|
259
|
+
],
|
|
260
|
+
examples: [
|
|
261
|
+
['work atk first until 17:00', 'focus atk --until 17:00'],
|
|
262
|
+
['six slots for atk until tomorrow', 'focus atk --slots 6 --until tomorrow'],
|
|
122
263
|
],
|
|
123
264
|
group: 'Scheduling',
|
|
124
265
|
name: 'focus',
|
|
266
|
+
options: [
|
|
267
|
+
['--slots <n>', "the project's slots"],
|
|
268
|
+
['--until <when>', 'when the focus ends (required)'],
|
|
269
|
+
['--weight <n>', 'its ordering weight, -100 to 100'],
|
|
270
|
+
],
|
|
271
|
+
seeAlso: ['pause', 'queue'],
|
|
125
272
|
summary: 'more slots or weight for a project, for a while',
|
|
126
273
|
usage: ['focus <project> [--slots <n>] [--weight <n>] --until <when>'],
|
|
127
274
|
},
|
|
128
275
|
{
|
|
129
|
-
|
|
276
|
+
arguments: [RANGE, WINDOW],
|
|
277
|
+
description: [
|
|
130
278
|
'What a window of the log says: shipped items, time in each phase by tier, plan latency, waits by kind, stalls, failures, inbox, slot use, rules and spend.',
|
|
131
|
-
|
|
132
|
-
|
|
279
|
+
'Give a <range> or --since/--until, not both.',
|
|
280
|
+
],
|
|
281
|
+
examples: [
|
|
282
|
+
['the last 24 hours', 'history'],
|
|
283
|
+
['a week of atk', 'history 7d --project atk'],
|
|
284
|
+
['the last 30 minutes', 'history --since 30m'],
|
|
133
285
|
],
|
|
134
286
|
group: 'Logs and reports',
|
|
135
287
|
name: 'history',
|
|
288
|
+
options: REPORT_OPTIONS,
|
|
289
|
+
seeAlso: ['retro', 'events'],
|
|
136
290
|
summary: 'what a window of the log says, from shipped items to spend',
|
|
137
291
|
usage: ['history [<range>] [--since <when>] [--until <when>] [--project <name>] [--json]'],
|
|
138
292
|
},
|
|
139
293
|
{
|
|
140
|
-
|
|
294
|
+
description: ['Each open item with its age, its commands and its links.'],
|
|
295
|
+
examples: [['what waits on you', 'inbox']],
|
|
141
296
|
group: 'Inbox and approvals',
|
|
142
297
|
name: 'inbox',
|
|
298
|
+
options: [['--json', 'print the items as JSON']],
|
|
299
|
+
seeAlso: ['approve', 'done', 'notify'],
|
|
143
300
|
summary: 'what waits on you',
|
|
144
301
|
usage: ['inbox [--json]'],
|
|
145
302
|
},
|
|
146
303
|
{
|
|
147
|
-
|
|
304
|
+
description: [
|
|
148
305
|
'Installs the conductor mod from this package in Claude Code (user scope): the inbox band, pane and commands in the console (a session started with CONDUCTOR_CONSOLE=1).',
|
|
149
|
-
|
|
306
|
+
],
|
|
307
|
+
examples: [
|
|
308
|
+
['install the mod', 'install-mod'],
|
|
309
|
+
['uninstall it', 'install-mod --remove'],
|
|
150
310
|
],
|
|
151
311
|
group: 'Setup',
|
|
152
312
|
name: 'install-mod',
|
|
313
|
+
options: [['--remove', 'uninstall it']],
|
|
314
|
+
seeAlso: ['console'],
|
|
153
315
|
summary: 'install the conductor mod in Claude Code',
|
|
154
316
|
usage: ['install-mod', 'install-mod --remove'],
|
|
155
317
|
},
|
|
156
318
|
{
|
|
157
|
-
|
|
319
|
+
description: [
|
|
158
320
|
"Registers a Windows Task Scheduler task that runs conduct supervise at logon, which restarts the daemon if it dies. Run it from the main checkout: a worktree's build goes away.",
|
|
159
|
-
'--remove removes that logon task.',
|
|
160
321
|
],
|
|
322
|
+
examples: [['start the daemon at every logon', 'install-startup']],
|
|
161
323
|
group: 'Setup',
|
|
162
324
|
name: 'install-startup',
|
|
325
|
+
options: [['--remove', 'remove that logon task']],
|
|
326
|
+
seeAlso: ['supervise', 'start'],
|
|
163
327
|
summary: 'start the daemon at logon (Windows Task Scheduler)',
|
|
164
328
|
usage: ['install-startup', 'install-startup --remove'],
|
|
165
329
|
},
|
|
166
330
|
{
|
|
167
|
-
|
|
331
|
+
arguments: [['<pr>', 'the PR number']],
|
|
332
|
+
description: [
|
|
168
333
|
"Sets the worker's PR up to land, then gives its first verdict. Safe to repeat. A brake already on the PR (a comment, a failed build, conflicts) is reported and nothing is armed. Otherwise it arms the host's own landing: on Azure DevOps a +10 vote (auto mode) and auto-complete with noFastForward; on GitHub, auto-merge with a merge commit (auto mode, where the repo allows it, refused with the reason where it doesn't). Then it posts the Landing note.",
|
|
169
|
-
"--mode is held to the profile's landing rules for the PR's files and the plan's tier: auto where they allow it, else refused. Without --mode the rules pick it. The daemon takes the tier from the item's Plan note; without a conductor, pass --tier.",
|
|
170
334
|
'A Landing note with auto-complete or auto-merge gone means someone cancelled it: auto-merge-cancelled, until --rearm.',
|
|
171
335
|
'With a conductor, the daemon records the landing and keeps it armed while the worker waits (conduct wait pr <pr>): it queues an expired build again and restores a cleared vote.',
|
|
172
|
-
'Prints JSON with the verdict and "via".
|
|
336
|
+
'Prints JSON with the verdict and "via".',
|
|
173
337
|
],
|
|
338
|
+
examples: [
|
|
339
|
+
['land PR 1850 by itself', 'land 1850 --mode auto --reason "S fix, no review paths"'],
|
|
340
|
+
['arm it again after a cancel', 'land 1850 --rearm --reason "Jason said go on"'],
|
|
341
|
+
],
|
|
342
|
+
exitCodes: [[1, 'refused'], LOST_LOGIN],
|
|
174
343
|
group: 'Used by work skills',
|
|
175
344
|
name: 'land',
|
|
345
|
+
options: [
|
|
346
|
+
ITEM,
|
|
347
|
+
[
|
|
348
|
+
'--mode auto|review',
|
|
349
|
+
"held to the profile's landing rules for the PR's files and the plan's tier: auto where they allow it, else refused; without it the rules pick",
|
|
350
|
+
],
|
|
351
|
+
['--reason <why>', 'why this mode, for the Landing note (required)'],
|
|
352
|
+
['--rearm', 'arm it again after someone cancelled it'],
|
|
353
|
+
PR_REPO,
|
|
354
|
+
['--tier S|M|L', "the plan's tier without a conductor; the daemon takes it from the item's Plan note"],
|
|
355
|
+
],
|
|
356
|
+
seeAlso: ['wait', 'pr', 'runs'],
|
|
176
357
|
summary: "arm the worker's PR to land, and its first verdict",
|
|
177
358
|
usage: ['land <pr> --reason <why> [--mode auto|review] [--rearm] [--tier S|M|L] [--repo <repo>] [--item <item>]'],
|
|
178
359
|
},
|
|
179
360
|
{
|
|
180
|
-
|
|
361
|
+
arguments: [['<item>', 'the item to work (ATK-312)']],
|
|
362
|
+
description: [
|
|
181
363
|
"Starts a worker on the item: its worktree, a background session running the project's work skill, and a tab.",
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
['--conductor <name>', 'passed to the work skill as --conductor'],
|
|
187
|
-
],
|
|
364
|
+
],
|
|
365
|
+
examples: [
|
|
366
|
+
['start a worker on ATK-312', 'launch ATK-312'],
|
|
367
|
+
['a probe with its own prompt', 'launch ATK-312 --probe --prompt "time the test suite"'],
|
|
188
368
|
],
|
|
189
369
|
group: 'Projects and workers',
|
|
190
370
|
name: 'launch',
|
|
371
|
+
options: [
|
|
372
|
+
['--conductor <name>', 'passed to the work skill as --conductor'],
|
|
373
|
+
['--probe', "a probe that works no issue: its session and worktree are probe-<item>, out of the fleet's way"],
|
|
374
|
+
['--project <name>', 'the project the item belongs to'],
|
|
375
|
+
['--prompt <text>', "the session's first prompt instead of the work skill (or the probe prompt)"],
|
|
376
|
+
],
|
|
377
|
+
seeAlso: ['attach', 'retire', 'status'],
|
|
191
378
|
summary: 'start a worker: its worktree, a background session, a tab',
|
|
192
379
|
usage: ['launch <item> [--project <name>] [--probe] [--prompt <text>] [--conductor <name>]'],
|
|
193
380
|
},
|
|
194
381
|
{
|
|
195
|
-
|
|
196
|
-
'acquire,
|
|
197
|
-
|
|
198
|
-
'
|
|
382
|
+
commands: [
|
|
383
|
+
['acquire <name>', 'take the lock, or keep it when you hold it'],
|
|
384
|
+
['release <name>', 'let it go'],
|
|
385
|
+
['status <name>', 'who holds it'],
|
|
386
|
+
],
|
|
387
|
+
description: [
|
|
388
|
+
'A lock every project shares, such as chrome for verify runs. It goes stale 30 minutes after its holder last acquired it: acquire it again to keep it.',
|
|
389
|
+
'Prints JSON.',
|
|
390
|
+
],
|
|
391
|
+
examples: [
|
|
392
|
+
['take the browser, waiting up to 30 minutes for it', 'lock acquire chrome --wait 30'],
|
|
393
|
+
['give it back', 'lock release chrome'],
|
|
199
394
|
],
|
|
395
|
+
exitCodes: [[3, 'no conductor answered']],
|
|
200
396
|
group: 'Used by work skills',
|
|
201
397
|
name: 'lock',
|
|
398
|
+
options: [
|
|
399
|
+
['--owner <id>', "who holds it; this checkout's worker's item by default"],
|
|
400
|
+
['--wait <minutes>', 'poll for the lock for up to that many minutes'],
|
|
401
|
+
],
|
|
402
|
+
seeAlso: ['ports', 'status'],
|
|
202
403
|
summary: 'a lock every project shares, such as chrome',
|
|
203
404
|
usage: ['lock acquire|release|status <name> [--owner <id>] [--wait <minutes>]'],
|
|
204
405
|
},
|
|
205
406
|
{
|
|
206
|
-
|
|
207
|
-
'
|
|
208
|
-
'
|
|
209
|
-
|
|
407
|
+
arguments: [
|
|
408
|
+
['<item>', 'the item (ATK-312)'],
|
|
409
|
+
['<file>', 'the note, its heading first'],
|
|
410
|
+
],
|
|
411
|
+
description: [
|
|
412
|
+
'Posts the file as a phase note on the item ("## <work skill> · <Phase>") and records its phase on the worker: this checkout\'s, or --worker.',
|
|
413
|
+
"A Started, PR opened or Landed note also moves the item, to the profile's tracker.transitions (by default In Progress and assigned, In Review, Done on Linear), and PR opened links the worker's PR (else the note's). A Started never reopens a closed item. A state the tracker won't take is a warning: the note stands.",
|
|
414
|
+
"With no conductor, it is posted from this checkout as its project's account, and the item moves the same way.",
|
|
415
|
+
],
|
|
416
|
+
examples: [['post the Plan note', 'note ATK-312 plan.md']],
|
|
417
|
+
exitCodes: [
|
|
418
|
+
[3, "nobody posted it (the tracker refused it, or no conductor and this isn't the item's project checkout)"],
|
|
419
|
+
[
|
|
420
|
+
4,
|
|
421
|
+
'held back because Jason revised an approved plan (by him or by the conductor): re-plan with his words (printed) and wait for approval',
|
|
422
|
+
],
|
|
210
423
|
],
|
|
211
424
|
group: 'Used by work skills',
|
|
212
425
|
name: 'note',
|
|
426
|
+
options: [['--worker <worker>', "the worker whose phase it records; this checkout's by default"]],
|
|
427
|
+
seeAlso: ['brief', 'file', 'wait'],
|
|
213
428
|
summary: 'post a phase note on the item and record the phase',
|
|
214
429
|
usage: ['note <item> <file> [--worker <worker>]'],
|
|
215
430
|
},
|
|
216
431
|
{
|
|
217
|
-
|
|
432
|
+
commands: [
|
|
433
|
+
['notify [--json]', "the notification profile, quiet hours, and each channel's state"],
|
|
434
|
+
[
|
|
435
|
+
'profile [<name>]',
|
|
436
|
+
'switch the profile now (built in: default, away, focus); without <name>, the same as conduct notify',
|
|
437
|
+
],
|
|
218
438
|
[
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
'profile [<name>]',
|
|
222
|
-
'switch the profile now (built in: default, away, focus); --until 17:00 or a date and time hands back to the one before it then. Without <name>, the same as conduct notify',
|
|
223
|
-
],
|
|
224
|
-
[
|
|
225
|
-
'test',
|
|
226
|
-
"push a sample item (default npm-otp) on the profile's channels now; --raise puts a real item in the inbox instead, routed by the profile (quiet hours, reminders), so it takes no --channel",
|
|
227
|
-
],
|
|
439
|
+
'test',
|
|
440
|
+
"push a sample item on the profile's channels now, or with --raise put a real item in the inbox, routed by the profile (quiet hours, reminders)",
|
|
228
441
|
],
|
|
229
442
|
],
|
|
443
|
+
description: ['The notification profile and its channels: see them, switch the profile, or test it.'],
|
|
444
|
+
examples: [
|
|
445
|
+
['away for the afternoon', 'notify profile away --until 17:00'],
|
|
446
|
+
['push a sample npm-otp item', 'notify test'],
|
|
447
|
+
],
|
|
230
448
|
group: 'Inbox and approvals',
|
|
231
449
|
name: 'notify',
|
|
450
|
+
options: [
|
|
451
|
+
['--channel remote-control|pushover', 'with test: push on that channel'],
|
|
452
|
+
['--json', 'print the state as JSON'],
|
|
453
|
+
['--kind <kind>', 'with test: the kind of item (npm-otp by default)'],
|
|
454
|
+
['--raise', 'with test: put a real item in the inbox instead; it takes no --channel'],
|
|
455
|
+
['--until <when>', 'with profile: hand back to the profile before it then (17:00, or a date and time)'],
|
|
456
|
+
],
|
|
457
|
+
seeAlso: ['inbox'],
|
|
232
458
|
summary: 'the notification profile and channels; switch or test them',
|
|
233
459
|
usage: [
|
|
234
460
|
'notify [--json]',
|
|
@@ -237,35 +463,74 @@ export const COMMANDS = [
|
|
|
237
463
|
],
|
|
238
464
|
},
|
|
239
465
|
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
466
|
+
arguments: [['<project>', 'a registered project; every project without it'], WHEN],
|
|
467
|
+
description: ['No new launches in the project, or in every project without one, until <when> or conduct unpause.'],
|
|
468
|
+
examples: [
|
|
469
|
+
['pause every project until tomorrow', 'pause --until tomorrow'],
|
|
470
|
+
['pause atk until you unpause it', 'pause atk'],
|
|
243
471
|
],
|
|
244
472
|
group: 'Scheduling',
|
|
245
473
|
name: 'pause',
|
|
474
|
+
options: [['--until <when>', 'when the pause ends']],
|
|
475
|
+
seeAlso: ['unpause', 'focus', 'queue'],
|
|
246
476
|
summary: 'no new launches, until a time or unpause',
|
|
247
477
|
usage: ['pause [<project>] [--until <when>]'],
|
|
248
478
|
},
|
|
249
479
|
{
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
'
|
|
480
|
+
arguments: [['<path>', "the checkout; without it, every worktree's"]],
|
|
481
|
+
description: [
|
|
482
|
+
"The checkout's dev ports from the port broker: its project's base ports plus its slot, which no other project's worktree shares (a main checkout: slot 0).",
|
|
483
|
+
],
|
|
484
|
+
examples: [
|
|
485
|
+
["this checkout's ports", 'ports .'],
|
|
486
|
+
["every worktree's", 'ports'],
|
|
253
487
|
],
|
|
488
|
+
exitCodes: [[3, 'no conductor brokered it, so the caller keeps its own ports']],
|
|
254
489
|
group: 'Used by work skills',
|
|
255
490
|
name: 'ports',
|
|
491
|
+
options: [
|
|
492
|
+
['--json', 'print them as JSON'],
|
|
493
|
+
['--prefer <slot>', 'ask for this slot first'],
|
|
494
|
+
],
|
|
495
|
+
seeAlso: ['lock'],
|
|
256
496
|
summary: "a checkout's dev ports, from the port broker",
|
|
257
497
|
usage: ['ports [<path>] [--prefer <slot>] [--json]'],
|
|
258
498
|
},
|
|
259
499
|
{
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
500
|
+
commands: [
|
|
501
|
+
[
|
|
502
|
+
'create',
|
|
503
|
+
"check the title carries the item key, the branch carries it too, the description fits the host, no PR is open from the branch, nothing is uncommitted, the branch is ahead of its base, and commitlint passes, all before anything is pushed. Then push and open the PR into the base branch (--repo: into the other repo's default branch, from its checkout)",
|
|
504
|
+
],
|
|
505
|
+
[
|
|
506
|
+
'threads <pr>',
|
|
507
|
+
"the PR's threads (on GitHub each comment and review), each with needsAnswer: a person's, open, with no note after it, and asking for something. A read, so it runs here",
|
|
508
|
+
],
|
|
509
|
+
],
|
|
510
|
+
description: [
|
|
511
|
+
"Opens the worker's PR, checked and pushed, or reads its threads.",
|
|
512
|
+
"The description ends with the tracker's reference when it has one (AB#<id> for an ADO Boards item on a GitHub PR, so the Boards GitHub app links it), counted in its length.",
|
|
513
|
+
'With a conductor, the daemon opens it for the worker that owns this checkout, checking the ownership again, and records the PR on the worker. Without one (or an older one) it opens it here.',
|
|
514
|
+
'Prints JSON with "via": "daemon" or "local".',
|
|
266
515
|
],
|
|
516
|
+
examples: [
|
|
517
|
+
[
|
|
518
|
+
'check everything, push nothing',
|
|
519
|
+
'pr create --title "feat(conductor): help sections (ATK-478)" --body-file pr.md --dry-run',
|
|
520
|
+
],
|
|
521
|
+
["PR 1850's threads", 'pr threads 1850'],
|
|
522
|
+
],
|
|
523
|
+
exitCodes: [[1, 'refused or failed'], LOST_LOGIN],
|
|
267
524
|
group: 'Used by work skills',
|
|
268
525
|
name: 'pr',
|
|
526
|
+
options: [
|
|
527
|
+
['--body-file <file>', 'with create: its description'],
|
|
528
|
+
['--dry-run', 'with create: stop before the push'],
|
|
529
|
+
ITEM,
|
|
530
|
+
PR_REPO,
|
|
531
|
+
['--title <title>', 'with create: its title, with the item key'],
|
|
532
|
+
],
|
|
533
|
+
seeAlso: ['land', 'reply', 'state'],
|
|
269
534
|
summary: "open the worker's PR, checked and pushed; read its threads",
|
|
270
535
|
usage: [
|
|
271
536
|
'pr create --title <title> --body-file <file> [--repo <repo>] [--item <item>] [--dry-run]',
|
|
@@ -273,250 +538,411 @@ export const COMMANDS = [
|
|
|
273
538
|
],
|
|
274
539
|
},
|
|
275
540
|
{
|
|
276
|
-
|
|
541
|
+
commands: [
|
|
542
|
+
['add <path>', 'register the project whose checkout holds .claude/conductor.json; works without the daemon'],
|
|
543
|
+
['list [--json]', 'the registered projects'],
|
|
277
544
|
[
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
'register the project whose checkout holds .claude/conductor.json (again after changing it); works without the daemon',
|
|
281
|
-
],
|
|
282
|
-
['list [--json]', 'the registered projects'],
|
|
545
|
+
'check <name>',
|
|
546
|
+
'whether the project can be conducted, before its first launch finds out: its profile (and what differs from the stored copy), tracker, code host and base branch, release pipelines, a throwaway worktree made and removed through its hooks, its installed skills, a free port slot and its health URLs. One ok, warn, fail or skip line each, with the fix',
|
|
283
547
|
],
|
|
284
548
|
],
|
|
549
|
+
description: [
|
|
550
|
+
'Registers a project, lists the registered ones, or checks one.',
|
|
551
|
+
'After that the daemon takes the profile from disk when it starts and each time the file is saved.',
|
|
552
|
+
"check goes through the daemon, as its accounts, or runs here as the machine's when it isn't running. Only the worktree check writes, and it removes what it made.",
|
|
553
|
+
],
|
|
554
|
+
examples: [
|
|
555
|
+
['register the checkout you are in', 'project add .'],
|
|
556
|
+
['the registered projects', 'project list'],
|
|
557
|
+
['check atk before conducting it', 'project check atk'],
|
|
558
|
+
['the same without the worktree (its hook runs pnpm install)', 'project check atk --no-worktree'],
|
|
559
|
+
],
|
|
285
560
|
group: 'Projects and workers',
|
|
286
561
|
name: 'project',
|
|
287
|
-
|
|
288
|
-
|
|
562
|
+
options: [
|
|
563
|
+
['--json', 'with list or check: print them as JSON'],
|
|
564
|
+
['--no-worktree', 'with check: skip the throwaway worktree'],
|
|
565
|
+
],
|
|
566
|
+
seeAlso: ['launch', 'ports', 'status'],
|
|
567
|
+
summary: 'register a project, list the registered ones, or check one',
|
|
568
|
+
usage: ['project add <path>', 'project list [--json]', 'project check <name> [--no-worktree] [--json]'],
|
|
289
569
|
},
|
|
290
570
|
{
|
|
291
|
-
|
|
571
|
+
description: ['What the scheduler picks next, and why the rest wait.'],
|
|
572
|
+
examples: [
|
|
573
|
+
['what launches next', 'queue'],
|
|
574
|
+
['the same, after a change in the tracker', 'queue --fresh'],
|
|
575
|
+
],
|
|
292
576
|
group: 'Scheduling',
|
|
293
577
|
name: 'queue',
|
|
578
|
+
options: [
|
|
579
|
+
['--fresh', "ask every project's tracker now, instead of using the last read"],
|
|
580
|
+
['--json', 'print it as JSON'],
|
|
581
|
+
],
|
|
582
|
+
seeAlso: ['focus', 'pause', 'status'],
|
|
294
583
|
summary: 'what the scheduler picks next, and why the rest wait',
|
|
295
584
|
usage: ['queue [--json] [--fresh]'],
|
|
296
585
|
},
|
|
297
586
|
{
|
|
298
|
-
|
|
587
|
+
arguments: [
|
|
588
|
+
['<pr>', 'the PR number'],
|
|
589
|
+
['<thread>', 'the Azure DevOps thread; without it, a note on the PR'],
|
|
590
|
+
],
|
|
591
|
+
description: [
|
|
299
592
|
'Answers on the worker\'s PR: in the Azure DevOps thread named, or as a note on the PR (GitHub, which has no threads, or no thread given). The text is headed "## <work skill> · Reply" unless it already starts with a note heading, so the PR reads it as the worker\'s.',
|
|
300
|
-
"With a conductor, the daemon posts it for the worker that owns this checkout, checking the PR is the worker's. Prints JSON.
|
|
593
|
+
"With a conductor, the daemon posts it for the worker that owns this checkout, checking the PR is the worker's. Prints JSON.",
|
|
301
594
|
],
|
|
595
|
+
examples: [['answer thread 4411 on PR 1850', 'reply 1850 4411 --body-file answer.md']],
|
|
596
|
+
exitCodes: [[1, 'refused'], LOST_LOGIN],
|
|
302
597
|
group: 'Used by work skills',
|
|
303
598
|
name: 'reply',
|
|
599
|
+
options: [['--body-file <file>', 'the answer'], ITEM, PR_REPO],
|
|
600
|
+
seeAlso: ['pr', 'land'],
|
|
304
601
|
summary: "answer a comment on the worker's PR",
|
|
305
602
|
usage: ['reply <pr> [<thread>] --body-file <file> [--repo <repo>] [--item <item>]'],
|
|
306
603
|
},
|
|
307
604
|
{
|
|
308
|
-
|
|
309
|
-
|
|
605
|
+
arguments: [WORKER],
|
|
606
|
+
description: [
|
|
607
|
+
"Stops the worker's session and removes its worktree. It refuses while the worker's turn runs or its worktree has unfinished work (uncommitted or unpushed).",
|
|
310
608
|
],
|
|
609
|
+
examples: [['retire ATK-309 once it is done', 'retire ATK-309']],
|
|
311
610
|
group: 'Projects and workers',
|
|
312
611
|
name: 'retire',
|
|
612
|
+
options: [['--force', 'retire it anyway and throw that work away']],
|
|
613
|
+
seeAlso: ['launch', 'attach', 'status'],
|
|
313
614
|
summary: 'stop a worker and remove its worktree and session',
|
|
314
615
|
usage: ['retire <worker> [--force]'],
|
|
315
616
|
},
|
|
316
617
|
{
|
|
317
|
-
|
|
618
|
+
arguments: [RANGE, WINDOW],
|
|
619
|
+
description: ['The same report as conduct history, as Markdown.', 'Give a <range> or --since/--until, not both.'],
|
|
620
|
+
examples: [
|
|
621
|
+
["yesterday's retro", 'retro yesterday'],
|
|
622
|
+
['a week of atk, to a file', 'retro 7d --project atk > retro.md'],
|
|
623
|
+
],
|
|
318
624
|
group: 'Logs and reports',
|
|
319
625
|
name: 'retro',
|
|
626
|
+
options: REPORT_OPTIONS,
|
|
627
|
+
seeAlso: ['history'],
|
|
320
628
|
summary: 'the history report as Markdown',
|
|
321
629
|
usage: ['retro [<range>] [--since <when>] [--until <when>] [--project <name>] [--json]'],
|
|
322
630
|
},
|
|
323
631
|
{
|
|
324
|
-
|
|
632
|
+
arguments: [
|
|
633
|
+
['<item>', "the item (ATK-312), or the inbox item's id as '#12' (quoted: a shell reads #12 as a comment)"],
|
|
634
|
+
['<text>', 'what to change in the plan'],
|
|
635
|
+
],
|
|
636
|
+
description: [
|
|
325
637
|
'Posts "revise: <text>" on the item as you and wakes its worker. Every word after <item> is the text, quoted or not.',
|
|
326
638
|
],
|
|
639
|
+
examples: [['ask for a smaller plan', 'revise ATK-312 put the web change in a PR of its own']],
|
|
327
640
|
group: 'Inbox and approvals',
|
|
328
641
|
name: 'revise',
|
|
642
|
+
seeAlso: ['approve', 'inbox'],
|
|
329
643
|
summary: 'ask for a revised plan and wake its worker',
|
|
330
644
|
usage: ['revise <item> <text>'],
|
|
331
645
|
},
|
|
332
646
|
{
|
|
333
|
-
|
|
647
|
+
commands: [
|
|
648
|
+
[
|
|
649
|
+
'rules [--json]',
|
|
650
|
+
'the rules in force (project, machine, built-in defaults), what replaces what, and any rules file refused. Machine rules are ~/.conductor/rules.json',
|
|
651
|
+
],
|
|
334
652
|
[
|
|
335
|
-
[
|
|
336
|
-
|
|
337
|
-
'the rules in force (project, machine, built-in defaults), what replaces what, and any rules file refused. Machine rules are ~/.conductor/rules.json',
|
|
338
|
-
],
|
|
339
|
-
[
|
|
340
|
-
'test <file> [--json]',
|
|
341
|
-
"dry-run the rules on a sample JSON file (no event: a state rule's subject); acts on nothing",
|
|
342
|
-
],
|
|
653
|
+
'test <file> [--json]',
|
|
654
|
+
"dry-run the rules on a sample JSON file (no event: a state rule's subject); acts on nothing",
|
|
343
655
|
],
|
|
344
|
-
|
|
656
|
+
],
|
|
657
|
+
description: [
|
|
658
|
+
'The rules in force, and a dry run of them on a sample. A sample for rules test:',
|
|
345
659
|
{ pre: ['{ "event": "pr.comment", "project": "atk",', ' "fields": { "wait.kind": "merge" } }'] },
|
|
346
660
|
],
|
|
661
|
+
examples: [
|
|
662
|
+
['the rules in force', 'rules'],
|
|
663
|
+
['what the rules would do with that sample', 'rules test sample.json'],
|
|
664
|
+
],
|
|
347
665
|
group: 'Console and rules',
|
|
348
666
|
name: 'rules',
|
|
667
|
+
options: [['--json', 'print it as JSON']],
|
|
668
|
+
seeAlso: ['why', 'escalations'],
|
|
349
669
|
summary: 'the rules in force; dry-run them on a sample',
|
|
350
670
|
usage: ['rules [--json]', 'rules test <file> [--json]'],
|
|
351
671
|
},
|
|
352
672
|
{
|
|
353
|
-
|
|
673
|
+
arguments: [['<sha>', 'the merge commit']],
|
|
674
|
+
description: [
|
|
354
675
|
"One look at a merge's runs: on Azure Pipelines for the project's Azure DevOps repo (the pipelines whose YAML trigger the merge fires, read in this worktree), on GitHub Actions for a GitHub repo (the runs the merge's push started). The outcome is succeeded, failed, cancelled, waiting-for-approval, missing, none (nothing ran: at once on Azure Pipelines, after 5 minutes on GitHub) or waiting (look again, or conduct wait merge).",
|
|
355
|
-
[
|
|
356
|
-
[
|
|
357
|
-
'--for <a,b>',
|
|
358
|
-
'the outcome comes from these pipelines alone (YAML file name such as deploy-api, its path, or its name); the rest are listed under others',
|
|
359
|
-
],
|
|
360
|
-
[
|
|
361
|
-
'--repo <repo>',
|
|
362
|
-
"github:<owner>/<repo> for another repo, such as the registry; the profile's code host by default",
|
|
363
|
-
],
|
|
364
|
-
],
|
|
365
676
|
"Each failed run comes with its failed tasks and the end of its log (logs). For the project's repo, each release in the profile (releases, else work.releases) whose run succeeded gives what it staged (stages): package, version, stageId, the npm stage approve line, and whether npm has it (published).",
|
|
366
|
-
'Prints JSON. A read, so it runs here.
|
|
677
|
+
'Prints JSON. A read, so it runs here.',
|
|
678
|
+
],
|
|
679
|
+
examples: [
|
|
680
|
+
['the runs of the merge a1b2c3d', 'runs a1b2c3d'],
|
|
681
|
+
['its API deploy alone', 'runs a1b2c3d --for deploy-api'],
|
|
367
682
|
],
|
|
683
|
+
exitCodes: [[1, 'refused or failed'], LOST_LOGIN],
|
|
368
684
|
group: 'Used by work skills',
|
|
369
685
|
name: 'runs',
|
|
686
|
+
options: [
|
|
687
|
+
[
|
|
688
|
+
'--for <a,b>',
|
|
689
|
+
'the outcome comes from these pipelines alone (YAML file name such as deploy-api, its path, or its name); the rest are listed under others',
|
|
690
|
+
],
|
|
691
|
+
[
|
|
692
|
+
'--repo <repo>',
|
|
693
|
+
"github:<owner>/<repo> for another repo, such as the registry; the profile's code host by default",
|
|
694
|
+
],
|
|
695
|
+
],
|
|
696
|
+
seeAlso: ['wait', 'land'],
|
|
370
697
|
summary: "a merge's runs, their verdict, failed logs and npm stages",
|
|
371
698
|
usage: ['runs <sha> [--for <pipeline>[,<pipeline>…]] [--repo <repo>]'],
|
|
372
699
|
},
|
|
373
700
|
{
|
|
374
|
-
|
|
701
|
+
description: ['One daemon per machine. conduct start runs it detached instead.'],
|
|
702
|
+
examples: [['run the daemon in this terminal', 'serve']],
|
|
375
703
|
group: 'Daemon',
|
|
376
704
|
name: 'serve',
|
|
705
|
+
seeAlso: ['start', 'supervise', 'stop'],
|
|
377
706
|
summary: 'run the daemon in this process',
|
|
378
707
|
usage: ['serve'],
|
|
379
708
|
},
|
|
380
709
|
{
|
|
381
|
-
|
|
710
|
+
description: [
|
|
382
711
|
'Starts the logon task when it runs this build, else a detached conduct supervise --fail-fast, and waits until the daemon answers. Does nothing when one runs already.',
|
|
383
712
|
],
|
|
713
|
+
examples: [['start the daemon', 'start']],
|
|
384
714
|
group: 'Daemon',
|
|
385
715
|
name: 'start',
|
|
716
|
+
seeAlso: ['stop', 'status', 'serve'],
|
|
386
717
|
summary: 'start the daemon detached and wait until it answers',
|
|
387
718
|
usage: ['start'],
|
|
388
719
|
},
|
|
389
720
|
{
|
|
390
|
-
|
|
391
|
-
"The worktree's branch, how far it is ahead of and behind its base, its uncommitted files, its branch's PR, and the same for each repo checked out under .scratch/repos.
|
|
392
|
-
"Prints JSON. Runs here; the PR is read as the project's account.
|
|
721
|
+
description: [
|
|
722
|
+
"The worktree's branch, how far it is ahead of and behind its base, its uncommitted files, its branch's PR, and the same for each repo checked out under .scratch/repos.",
|
|
723
|
+
"Prints JSON. Runs here; the PR is read as the project's account.",
|
|
393
724
|
],
|
|
725
|
+
examples: [
|
|
726
|
+
['where this worktree stands', 'state'],
|
|
727
|
+
['the registry checkout alone', 'state --repo EmergentSoftware/agentic-toolkit-registry'],
|
|
728
|
+
],
|
|
729
|
+
exitCodes: [LOST_LOGIN],
|
|
394
730
|
group: 'Used by work skills',
|
|
395
731
|
name: 'state',
|
|
732
|
+
options: [['--repo <repo>', 'one checked-out repo only']],
|
|
733
|
+
seeAlso: ['brief', 'checkout', 'pr'],
|
|
396
734
|
summary: "the worktree's branch, changes and PR",
|
|
397
735
|
usage: ['state [--repo <repo>]'],
|
|
398
736
|
},
|
|
399
737
|
{
|
|
400
|
-
|
|
401
|
-
'
|
|
738
|
+
description: [
|
|
739
|
+
"The daemon's pid, port, projects, workers and held locks. With no daemon running, --json says running: false, lists the workers from the store, and exits 1.",
|
|
402
740
|
],
|
|
741
|
+
examples: [['is the daemon up, and what it runs', 'status']],
|
|
403
742
|
group: 'Daemon',
|
|
404
743
|
name: 'status',
|
|
744
|
+
options: [['--json', 'print it as JSON']],
|
|
745
|
+
seeAlso: ['start', 'queue', 'inbox'],
|
|
405
746
|
summary: "the daemon's pid, port, projects, workers and held locks",
|
|
406
747
|
usage: ['status [--json]'],
|
|
407
748
|
},
|
|
408
749
|
{
|
|
409
|
-
|
|
750
|
+
description: [
|
|
410
751
|
'Stops the daemon through its API. When none answers, a conduct supervise may be waiting to restart one: stop asks it to end through supervisor.stop in the conductor home and waits up to 10 s for its answer, then removes the request. A supervisor heeds only a request written after it started and in the last 30 s.',
|
|
411
752
|
],
|
|
753
|
+
examples: [['stop the daemon', 'stop']],
|
|
412
754
|
group: 'Daemon',
|
|
413
755
|
name: 'stop',
|
|
756
|
+
seeAlso: ['start', 'upgrade'],
|
|
414
757
|
summary: 'stop the running daemon',
|
|
415
758
|
usage: ['stop'],
|
|
416
759
|
},
|
|
417
760
|
{
|
|
418
|
-
|
|
761
|
+
description: [
|
|
419
762
|
'Runs conduct serve and starts it again when it dies (a crash, a kill, a start that fails), after 1 s, 5 s, 15 s, 30 s, then every minute, and every 5 minutes once it has pushed. A daemon down 2 minutes sends one Pushover push, held through quiet hours. A stop (conduct stop, upgrade, Ctrl+C), or another daemon holding the lock, ends it. The logon task runs it.',
|
|
420
|
-
[['--fail-fast', 'end when the first serve exits before it comes up, as conduct start runs it']],
|
|
421
763
|
],
|
|
764
|
+
examples: [['what the logon task runs', 'supervise']],
|
|
422
765
|
group: 'Daemon',
|
|
423
766
|
name: 'supervise',
|
|
767
|
+
options: [['--fail-fast', 'end when the first serve exits before it comes up, as conduct start runs it']],
|
|
768
|
+
seeAlso: ['install-startup', 'start', 'serve'],
|
|
424
769
|
summary: 'run the daemon and restart it when it dies',
|
|
425
770
|
usage: ['supervise [--fail-fast]'],
|
|
426
771
|
},
|
|
427
772
|
{
|
|
428
|
-
|
|
773
|
+
arguments: [['<project>', "a registered project; without it, the checkout you're in"]],
|
|
774
|
+
description: [
|
|
429
775
|
'Several skill publishes in one merge (the profile\'s "train"). A worker marks its registry PR with a "<work skill> · Train" note ("· Train · urgent: <why>" asks for one now); the daemon asks you once enough wait.',
|
|
430
776
|
"Takes each marked PR that targets the default branch, has passed its checks and shares no file with another, merges their heads into one train/<stamp> branch on GitHub's side, opens one PR and notes its link on each. Merge it with a merge commit: each member then shows as merged and its worker wakes.",
|
|
431
|
-
|
|
777
|
+
],
|
|
778
|
+
examples: [
|
|
779
|
+
['what a train would take and leave out', 'train --dry-run'],
|
|
780
|
+
["merge atk's waiting publishes", 'train atk'],
|
|
432
781
|
],
|
|
433
782
|
group: 'Projects and workers',
|
|
434
783
|
name: 'train',
|
|
784
|
+
options: [['--dry-run', 'only read: what waits, and what a train would take and leave out']],
|
|
785
|
+
seeAlso: ['pr', 'land'],
|
|
435
786
|
summary: 'merge waiting skill publishes into one registry PR',
|
|
436
787
|
usage: ['train [<project>] [--dry-run]'],
|
|
437
788
|
},
|
|
438
789
|
{
|
|
439
|
-
|
|
790
|
+
arguments: [['<project>', 'a registered project; every project without it']],
|
|
791
|
+
description: [
|
|
440
792
|
"Ends the project's pause, or without <project> the pause of every project. Unpausing one project leaves a pause of every project in force.",
|
|
441
793
|
],
|
|
794
|
+
examples: [
|
|
795
|
+
['launch again everywhere', 'unpause'],
|
|
796
|
+
['launch again in atk', 'unpause atk'],
|
|
797
|
+
],
|
|
442
798
|
group: 'Scheduling',
|
|
443
799
|
name: 'unpause',
|
|
800
|
+
seeAlso: ['pause', 'focus'],
|
|
444
801
|
summary: 'launch again after a pause',
|
|
445
802
|
usage: ['unpause [<project>]'],
|
|
446
803
|
},
|
|
447
804
|
{
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
805
|
+
description: ['Stops the daemon, installs the newest version on the tag with npm, and starts the daemon again.'],
|
|
806
|
+
examples: [
|
|
807
|
+
['the newest version on the same tag', 'upgrade'],
|
|
808
|
+
['move to the dev tag', 'upgrade --tag dev'],
|
|
451
809
|
],
|
|
452
810
|
group: 'Daemon',
|
|
453
811
|
name: 'upgrade',
|
|
812
|
+
options: [
|
|
813
|
+
['--allow-downgrade', "install the tag's version even when it is older than the installed one"],
|
|
814
|
+
['--tag dev|latest', 'the npm dist-tag; the one the installed version came from by default'],
|
|
815
|
+
],
|
|
816
|
+
seeAlso: ['stop', 'start', 'install-mod'],
|
|
454
817
|
summary: 'install the newest version and restart the daemon',
|
|
455
818
|
usage: ['upgrade [--tag dev|latest] [--allow-downgrade]'],
|
|
456
819
|
},
|
|
457
820
|
{
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
821
|
+
conditions: [
|
|
822
|
+
[
|
|
823
|
+
'pr <n>',
|
|
824
|
+
"the PR's verdict once it isn't waiting: merged, build-failed, conflicts, review-comment, changes-requested, merge-failed, auto-merge-cancelled or abandoned. The daemon keeps a landing conduct land armed while it waits",
|
|
825
|
+
],
|
|
826
|
+
[
|
|
827
|
+
'pr <n> merged|checks|comment',
|
|
828
|
+
'a pull request merged, its checks finished, or a new comment; --repo github:<owner>/<repo> for a GitHub repo',
|
|
829
|
+
],
|
|
830
|
+
[
|
|
831
|
+
'merge <sha>',
|
|
832
|
+
"the merge's runs settled, as conduct runs reads them: on Azure Pipelines for the project's repo, on GitHub Actions with --repo github:<owner>/<repo>; --for <a,b> settles on those pipelines alone. The wake names the outcome and each release's staged version; conduct runs has the logs",
|
|
833
|
+
],
|
|
834
|
+
['run <pipeline> <run id> [done]', 'a pipeline run finished'],
|
|
461
835
|
[
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
836
|
+
'npm <version>',
|
|
837
|
+
"a version on npm; --tag <dist-tag> (default latest), --package <name> (default the profile's)",
|
|
838
|
+
],
|
|
839
|
+
['health <env>', "the environment's health answers --version <x> or --commit <sha>"],
|
|
840
|
+
['approval [<item>]', "an approve or revise: comment after the item's newest Plan note"],
|
|
841
|
+
[
|
|
842
|
+
'item <item> <state>',
|
|
843
|
+
'the item reaches a state; item <item> state|label|note <value> for a state, a label or a note of more than one word',
|
|
844
|
+
],
|
|
845
|
+
[
|
|
846
|
+
'login ado|github|linear',
|
|
847
|
+
"the project's login to the provider works again (projects.<name> in config.json says which account)",
|
|
848
|
+
],
|
|
849
|
+
['time <when>', 'such as 17:00 (the next one) or 2026-10-10T09:00 (local)'],
|
|
850
|
+
['<full form>', 'such as pr:ado:Emergent.AgenticToolkit#1850:merged'],
|
|
851
|
+
],
|
|
852
|
+
description: [
|
|
853
|
+
"In a worker's worktree with the daemon running, registers the wait and exits 0 at once: end the turn, and the daemon resumes the session when the condition holds. Otherwise checks it here and blocks until it holds or --timeout passes.",
|
|
854
|
+
],
|
|
855
|
+
examples: [
|
|
856
|
+
['wait for PR 1850 to merge', 'wait pr 1850 merged'],
|
|
857
|
+
['wait for the plan approval', 'wait approval'],
|
|
858
|
+
['wait for a dev release on npm', 'wait npm 8.7.0-dev.8 --tag dev --package @detergent-software/atk'],
|
|
859
|
+
],
|
|
860
|
+
exitCodes: [
|
|
861
|
+
[0, 'the condition holds, or the daemon registered the wait'],
|
|
862
|
+
[2, '--timeout passed (local only)'],
|
|
863
|
+
[
|
|
864
|
+
5,
|
|
865
|
+
'not from wait: worker verbs (state, pr, land, reply, runs) exit 5 when they met a lost provider login; the worker then runs conduct wait login <provider> and ends its turn',
|
|
491
866
|
],
|
|
492
|
-
'Exit code 5 is for worker verbs (state, pr, land, reply, runs) that met a lost provider login: the worker runs conduct wait login <provider> and ends its turn.',
|
|
493
867
|
],
|
|
494
868
|
group: 'Used by work skills',
|
|
495
869
|
name: 'wait',
|
|
870
|
+
options: [
|
|
871
|
+
['--commit <sha>', 'with health: the commit to answer'],
|
|
872
|
+
['--for <a,b>', 'with merge: settle on these pipelines alone'],
|
|
873
|
+
['--item <item>', "the item for the condition; this checkout's worker's by default"],
|
|
874
|
+
['--local', 'check it here even with the daemon running'],
|
|
875
|
+
['--package <name>', "with npm: the package; the profile's by default"],
|
|
876
|
+
['--repo <repo>', 'with pr and merge: github:<owner>/<repo> for a GitHub repo'],
|
|
877
|
+
['--tag <dist-tag>', 'with npm: the dist-tag; latest by default'],
|
|
878
|
+
['--timeout <minutes>', 'local only: give up after this many minutes; 29 by default'],
|
|
879
|
+
['--version <x>', 'with health: the version to answer'],
|
|
880
|
+
['--worker <worker>', "the worker whose wait it is, by name or item; this checkout's by default"],
|
|
881
|
+
],
|
|
882
|
+
seeAlso: ['land', 'runs', 'brief'],
|
|
496
883
|
summary: 'wait for an outside event, such as a merge or an approval',
|
|
497
884
|
usage: ['wait <condition> [--local] [--timeout <minutes>] [--worker <worker>] [--item <item>]'],
|
|
498
885
|
},
|
|
499
886
|
{
|
|
500
|
-
|
|
501
|
-
|
|
887
|
+
arguments: [['<event id | item>', 'an event id (4200) or an item (ATK-312)']],
|
|
888
|
+
description: [
|
|
889
|
+
"With an event id, that event's rule and the events that led to it; with an item, the same for each of its newest actions.",
|
|
890
|
+
],
|
|
891
|
+
examples: [
|
|
892
|
+
['why the conductor acted on ATK-312', 'why ATK-312'],
|
|
893
|
+
['the rule behind event 4200', 'why 4200'],
|
|
502
894
|
],
|
|
503
895
|
group: 'Console and rules',
|
|
504
896
|
name: 'why',
|
|
897
|
+
options: [['--json', 'print it as JSON']],
|
|
898
|
+
seeAlso: ['rules', 'events', 'escalations'],
|
|
505
899
|
summary: 'the rule behind an action, and the events that led to it',
|
|
506
900
|
usage: ['why <event id | item> [--json]'],
|
|
507
901
|
},
|
|
508
902
|
];
|
|
509
|
-
// The rows
|
|
903
|
+
// The listing's last rows: help itself and the CLI's global options.
|
|
510
904
|
const HELP_ROWS = [
|
|
511
|
-
['help [<command>]', "this listing, or one command's help
|
|
512
|
-
['--
|
|
905
|
+
['help [<command>]', "this listing, or one command's help"],
|
|
906
|
+
['-h, --help', 'the same: conduct --help, or conduct <command> --help'],
|
|
907
|
+
['-v, --version', 'the installed version'],
|
|
513
908
|
];
|
|
514
|
-
|
|
515
|
-
|
|
909
|
+
const FOOTER = [
|
|
910
|
+
'Run conduct help <command> for its options and examples.',
|
|
911
|
+
'A <worker> is its item (ATK-309) or its name (probe-ATK-309).',
|
|
912
|
+
'Files live in ~/.conductor (CONDUCTOR_HOME overrides it).',
|
|
913
|
+
];
|
|
914
|
+
// A placeholder, `conduct <command>`, or a flag: what prose styles.
|
|
915
|
+
const TOKENS = /(<[^<>]+>)|\b(conduct [a-z][a-z-]*)|(?<![\w-])(--?[a-z][a-z-]*)/g;
|
|
916
|
+
// The text as written: tests, a pipe, NO_COLOR.
|
|
917
|
+
export const plain = (_look, text) => text;
|
|
918
|
+
// `conduct help <command>`: the headline, then each section the command has.
|
|
919
|
+
export function commandHelp(name, width = terminalWidth(), paint = plain) {
|
|
516
920
|
const command = find(name);
|
|
517
|
-
const lines =
|
|
518
|
-
|
|
519
|
-
|
|
921
|
+
const lines = hang(`conduct ${name} — `, words(command.summary), width).map((l) => prose(l, paint));
|
|
922
|
+
lines.push('', paint('heading', 'USAGE'), ...formLines(command, width, paint));
|
|
923
|
+
for (const paragraph of command.description)
|
|
924
|
+
lines.push('', ...paragraphLines(paragraph, width, paint));
|
|
925
|
+
const table = (heading, rows) => {
|
|
926
|
+
if (rows?.length)
|
|
927
|
+
lines.push('', paint('heading', heading), ...rowLines(rows, width, paint));
|
|
928
|
+
};
|
|
929
|
+
table('COMMANDS', command.commands);
|
|
930
|
+
table('CONDITIONS', command.conditions);
|
|
931
|
+
table('ARGUMENTS', command.arguments);
|
|
932
|
+
table('OPTIONS', command.options);
|
|
933
|
+
lines.push('', paint('heading', 'EXAMPLES'));
|
|
934
|
+
command.examples.forEach(([comment, example], i) => {
|
|
935
|
+
if (i > 0)
|
|
936
|
+
lines.push('');
|
|
937
|
+
const [verb = '', ...args] = words(example);
|
|
938
|
+
const text = [...hang(' # ', words(comment), width), ...hang(` conduct ${verb} `, args, width)];
|
|
939
|
+
lines.push(...text.map((l) => prose(l, paint)));
|
|
940
|
+
});
|
|
941
|
+
table('EXIT CODES', command.exitCodes?.map(([code, text]) => [String(code), text]));
|
|
942
|
+
if (command.seeAlso?.length) {
|
|
943
|
+
const names = command.seeAlso.map((n, i, all) => `conduct ${n}${i < all.length - 1 ? ',' : ''}`);
|
|
944
|
+
lines.push('', paint('heading', 'SEE ALSO'), ...hang(' ', names, width).map((l) => prose(l, paint)));
|
|
945
|
+
}
|
|
520
946
|
return lines.join('\n');
|
|
521
947
|
}
|
|
522
948
|
// The text after `prefix`, word by word, with every line after the first indented to the prefix's
|
|
@@ -541,46 +967,63 @@ export function hang(prefix, words, width) {
|
|
|
541
967
|
export function isCommand(name) {
|
|
542
968
|
return COMMANDS.some((c) => c.name === name);
|
|
543
969
|
}
|
|
544
|
-
// `conduct` and `conduct help`: one line per command under its group, A–Z,
|
|
545
|
-
|
|
970
|
+
// `conduct` and `conduct help`: what conduct is, then one line per command under its group, A–Z,
|
|
971
|
+
// then help and the global options.
|
|
972
|
+
export function listing(about, width = terminalWidth(), paint = plain) {
|
|
546
973
|
const column = Math.max(...[...COMMANDS.map((c) => c.name), ...HELP_ROWS.map(([term]) => term)].map((t) => t.length)) + 4;
|
|
547
|
-
const
|
|
548
|
-
const lines =
|
|
974
|
+
const header = `conduct ${about.version}${about.description ? ` · ${about.description}` : ''}`;
|
|
975
|
+
const lines = hang('', words(header), width).map((l) => prose(l, paint));
|
|
976
|
+
lines.push('', paint('heading', 'USAGE'), ` ${term('conduct <command> [options]', paint)}`);
|
|
549
977
|
for (const group of GROUPS) {
|
|
550
|
-
const
|
|
551
|
-
|
|
978
|
+
const rows = COMMANDS.filter((c) => c.group === group)
|
|
979
|
+
.toSorted((a, b) => a.name.localeCompare(b.name))
|
|
980
|
+
.map((c) => [c.name, c.summary]);
|
|
981
|
+
lines.push('', paint('heading', group.toUpperCase()), ...rowLines(rows, width, paint, column));
|
|
552
982
|
}
|
|
553
|
-
lines.push('', '
|
|
554
|
-
lines.push('', ...hang('',
|
|
555
|
-
lines.push(...hang('', 'Files live in ~/.conductor (CONDUCTOR_HOME overrides it).'.split(' '), width));
|
|
983
|
+
lines.push('', paint('heading', 'HELP'), ...rowLines(HELP_ROWS, width, paint, column));
|
|
984
|
+
lines.push('', ...FOOTER.flatMap((text) => hang('', words(text), width).map((l) => prose(l, paint))));
|
|
556
985
|
return lines.join('\n');
|
|
557
986
|
}
|
|
558
|
-
//
|
|
987
|
+
// Styled for `stream`: bold headings, cyan command names and flags, dim placeholders. With
|
|
988
|
+
// validateStream (always, but in a test), plain unless the stream takes color, as NO_COLOR and
|
|
989
|
+
// FORCE_COLOR say.
|
|
990
|
+
export function terminalStyle(stream, validateStream = true) {
|
|
991
|
+
return (look, text) => styleText(LOOKS[look], text, { stream, validateStream });
|
|
992
|
+
}
|
|
993
|
+
// The width to wrap to: the terminal's up to 100, or 80 when it isn't one (a pipe has no columns,
|
|
994
|
+
// and some pseudo-terminals say 0).
|
|
559
995
|
export function terminalWidth(stream = process.stdout) {
|
|
560
|
-
|
|
996
|
+
const columns = stream.columns ?? 0;
|
|
997
|
+
return Math.min(Math.max(columns > 0 ? columns : DEFAULT_WIDTH, MIN_WIDTH), MAX_WIDTH);
|
|
561
998
|
}
|
|
562
|
-
// What `conduct help <name>` refuses for a name that is no command
|
|
999
|
+
// What `conduct help <name>` refuses for a name that is no command, with the nearest command when
|
|
1000
|
+
// one is a typo away.
|
|
563
1001
|
export function unknownCommand(name) {
|
|
564
|
-
|
|
1002
|
+
const near = nearest(name);
|
|
1003
|
+
return new ConductorError(`unknown command "${name}"`, [
|
|
1004
|
+
...(near === undefined ? [] : [`did you mean \`conduct ${near}\`?`]),
|
|
1005
|
+
'run `conduct help` for the commands',
|
|
1006
|
+
]);
|
|
565
1007
|
}
|
|
566
1008
|
// A usage error for one command: the message, then its forms and where its help is. Never the
|
|
567
|
-
// whole listing. The CLI prints the details indented by two.
|
|
1009
|
+
// whole listing, and never styled: workers read it. The CLI prints the details indented by two.
|
|
568
1010
|
export function usageError(name, message = `wrong or missing arguments for conduct ${name}`) {
|
|
569
1011
|
return new ConductorError(message, [
|
|
570
1012
|
...usageLines(find(name), terminalWidth(process.stderr) - 2),
|
|
571
1013
|
`run \`conduct help ${name}\` for the details`,
|
|
572
1014
|
]);
|
|
573
1015
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
1016
|
+
// Levenshtein: the fewest single-character edits from `a` to `b`.
|
|
1017
|
+
function editDistance(a, b) {
|
|
1018
|
+
let previous = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
1019
|
+
for (const [i, ca] of [...a].entries()) {
|
|
1020
|
+
const current = [i + 1];
|
|
1021
|
+
for (const [j, cb] of [...b].entries()) {
|
|
1022
|
+
current.push(Math.min((previous[j + 1] ?? 0) + 1, (current[j] ?? 0) + 1, (previous[j] ?? 0) + (ca === cb ? 0 : 1)));
|
|
1023
|
+
}
|
|
1024
|
+
previous = current;
|
|
1025
|
+
}
|
|
1026
|
+
return previous[b.length] ?? 0;
|
|
584
1027
|
}
|
|
585
1028
|
function find(name) {
|
|
586
1029
|
const command = COMMANDS.find((c) => c.name === name);
|
|
@@ -588,6 +1031,59 @@ function find(name) {
|
|
|
588
1031
|
throw new Error(`no help for ${name}`);
|
|
589
1032
|
return command;
|
|
590
1033
|
}
|
|
1034
|
+
// USAGE: each form under the heading.
|
|
1035
|
+
function formLines(command, width, paint) {
|
|
1036
|
+
return forms(command, width, () => ' ').flatMap(([first = '', ...more]) => [
|
|
1037
|
+
` ${term(first.slice(2), paint)}`,
|
|
1038
|
+
...more.map((l) => prose(l, paint)),
|
|
1039
|
+
]);
|
|
1040
|
+
}
|
|
1041
|
+
// Each form as `<head>conduct <form>`, a long one wrapped under its first argument.
|
|
1042
|
+
function forms(command, width, head) {
|
|
1043
|
+
return command.usage.map((form, i) => {
|
|
1044
|
+
const [name = '', ...rest] = synopsisWords(form);
|
|
1045
|
+
return hang(`${head(i)}conduct ${name} `, rest, width);
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
// The command a typo is closest to, within 2 edits and fewer edits than it has letters.
|
|
1049
|
+
function nearest(name) {
|
|
1050
|
+
let best;
|
|
1051
|
+
for (const candidate of [...COMMANDS.map((c) => c.name), 'help'].toSorted()) {
|
|
1052
|
+
const distance = editDistance(name, candidate);
|
|
1053
|
+
if (distance <= 2 && distance < name.length && (best === undefined || distance < best.distance))
|
|
1054
|
+
best = { distance, name: candidate };
|
|
1055
|
+
}
|
|
1056
|
+
return best?.name;
|
|
1057
|
+
}
|
|
1058
|
+
function paragraphLines(paragraph, width, paint) {
|
|
1059
|
+
if (typeof paragraph === 'string')
|
|
1060
|
+
return hang(' ', words(paragraph), width).map((l) => prose(l, paint));
|
|
1061
|
+
return paragraph.pre.map((line) => ` ${line}`);
|
|
1062
|
+
}
|
|
1063
|
+
// Text with its placeholders dim, and `conduct <command>` and flags cyan.
|
|
1064
|
+
function prose(text, paint) {
|
|
1065
|
+
return text.replace(TOKENS, (match, placeholder, command, flag) => {
|
|
1066
|
+
if (placeholder !== undefined)
|
|
1067
|
+
return paint('placeholder', placeholder);
|
|
1068
|
+
if (flag !== undefined)
|
|
1069
|
+
return paint('command', flag);
|
|
1070
|
+
const name = command?.slice('conduct '.length);
|
|
1071
|
+
return name === 'help' || isCommand(name) ? paint('command', match) : match;
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
// Rows of a term and its description, the descriptions in one column: the listing's, else two past
|
|
1075
|
+
// the longest term up to MAX_TERM. A term too long for it stands on its own line, with its
|
|
1076
|
+
// description under it in that column.
|
|
1077
|
+
function rowLines(rows, width, paint, column) {
|
|
1078
|
+
const at = column ?? 2 + Math.max(0, ...rows.map(([t]) => t.length).filter((n) => n <= MAX_TERM)) + 2;
|
|
1079
|
+
return rows.flatMap(([t, text]) => {
|
|
1080
|
+
if (t.length > at - 4) {
|
|
1081
|
+
return [` ${term(t, paint)}`, ...hang(' '.repeat(at), words(text), width).map((l) => prose(l, paint))];
|
|
1082
|
+
}
|
|
1083
|
+
const [first = '', ...more] = hang(` ${t}`.padEnd(at), words(text), width);
|
|
1084
|
+
return [` ${term(t, paint)}${prose(first.slice(2 + t.length), paint)}`, ...more.map((l) => prose(l, paint))];
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
591
1087
|
// A form split at its top-level spaces, so `[--since <when>]` stays on one line.
|
|
592
1088
|
function synopsisWords(form) {
|
|
593
1089
|
const words = [];
|
|
@@ -608,12 +1104,20 @@ function synopsisWords(form) {
|
|
|
608
1104
|
}
|
|
609
1105
|
return [...words, word];
|
|
610
1106
|
}
|
|
611
|
-
//
|
|
1107
|
+
// A term, such as a form or a row's: its leading words (the command and sub-command names) cyan,
|
|
1108
|
+
// then the rest as prose.
|
|
1109
|
+
function term(text, paint) {
|
|
1110
|
+
const parts = synopsisWords(text);
|
|
1111
|
+
const lead = parts.findIndex((w) => !/^[a-z]/.test(w));
|
|
1112
|
+
const names = parts.slice(0, lead === -1 ? parts.length : lead).join(' ');
|
|
1113
|
+
const rest = parts.slice(lead === -1 ? parts.length : lead).join(' ');
|
|
1114
|
+
return [names === '' ? '' : paint('command', names), prose(rest, paint)].filter((p) => p !== '').join(' ');
|
|
1115
|
+
}
|
|
1116
|
+
// `usage: conduct <form>`, then the other forms under it.
|
|
612
1117
|
function usageLines(command, width) {
|
|
613
|
-
return command
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
});
|
|
1118
|
+
return forms(command, width, (i) => (i === 0 ? 'usage: ' : ' ')).flat();
|
|
1119
|
+
}
|
|
1120
|
+
function words(text) {
|
|
1121
|
+
return text.split(' ');
|
|
618
1122
|
}
|
|
619
1123
|
//# sourceMappingURL=help.js.map
|