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