omp-conductor 0.16.0 → 0.16.2
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/README.md +25 -0
- package/REFERENCE.md +3 -0
- package/package.json +2 -1
- package/schema/config.schema.json +1 -0
- package/src/admission.ts +111 -7
- package/src/briefs/orchestrator.md +21 -6
- package/src/briefs/policy.md +13 -5
- package/src/cli.ts +110 -381
- package/src/command-help.ts +220 -0
- package/src/command-manifest.ts +471 -0
- package/src/commands/complete.ts +93 -0
- package/src/commands/context.ts +1 -0
- package/src/commands/setup.ts +68 -2
- package/src/commands/tail.ts +204 -44
- package/src/config-schema.ts +4 -0
- package/src/config.ts +31 -1
- package/src/daemon.ts +16 -0
- package/src/depends-on.ts +77 -28
- package/src/doctor.ts +120 -1
- package/src/escalate.ts +77 -4
- package/src/fleet.ts +127 -42
- package/src/setup-host.ts +29 -11
- package/src/setup-wizard.ts +10 -9
- package/src/transcript.ts +1 -1
- package/src/upgrade-verify.ts +25 -3
- package/src/upgrade.ts +61 -2
- package/src/worker.ts +196 -0
- package/src/worktree.ts +13 -1
- package/systemd/omp-conductor-recover.sh +1 -1
- package/systemd/recover-unit-test.sh +2 -2
package/src/cli.ts
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { userInfo } from "node:os";
|
|
11
11
|
import { loadConfig } from "./config.ts";
|
|
12
|
-
import {
|
|
13
|
-
import type { ProjectConfig } from "./types.ts";
|
|
12
|
+
import { COMMAND_MANIFEST, renderUsage } from "./command-manifest.ts";
|
|
14
13
|
import { armCommand } from "./commands/arm.ts";
|
|
15
14
|
import { boardCommand } from "./commands/board.ts";
|
|
16
15
|
import { briefUpgradeCommand } from "./commands/brief-upgrade.ts";
|
|
@@ -53,276 +52,7 @@ import {
|
|
|
53
52
|
type CommandHandler,
|
|
54
53
|
} from "./commands/context.ts";
|
|
55
54
|
|
|
56
|
-
const USAGE =
|
|
57
|
-
|
|
58
|
-
usage:
|
|
59
|
-
omp-conductor setup [area] [--no-ai] [--project NAME]
|
|
60
|
-
omp-conductor start [--port N] [--project NAME]
|
|
61
|
-
omp-conductor --version
|
|
62
|
-
omp-conductor stop [--pane] [--project NAME | --all]
|
|
63
|
-
omp-conductor restart [--now] [--timeout SECONDS] [--port N] [--project NAME]
|
|
64
|
-
omp-conductor upgrade [--to VERSION] [--project NAME]
|
|
65
|
-
omp-conductor upgrade-install --to VERSION [--project NAME]
|
|
66
|
-
omp-conductor upgrade-rollback
|
|
67
|
-
omp-conductor board [--project NAME] [--json]
|
|
68
|
-
omp-conductor dashboard [--port N] [--host ADDR]
|
|
69
|
-
omp-conductor status [--project NAME]
|
|
70
|
-
omp-conductor stats [--since 7d|30d|YYYY-MM-DD] [--project NAME] [--json]
|
|
71
|
-
omp-conductor doctor [--project NAME] [--json] [--probe-telegram]
|
|
72
|
-
omp-conductor restore-db [SNAPSHOT]
|
|
73
|
-
omp-conductor ledger [--issue N] [--limit N] [--project NAME]
|
|
74
|
-
omp-conductor hold [--keep-ticks] [--project NAME | --all]
|
|
75
|
-
omp-conductor arm [--project NAME | --all]
|
|
76
|
-
omp-conductor disarm [--project NAME | --all]
|
|
77
|
-
omp-conductor tail <issue> [--project NAME]
|
|
78
|
-
omp-conductor extend <issue> --turns N [--project NAME]
|
|
79
|
-
omp-conductor worker pause <issue> [--project NAME]
|
|
80
|
-
omp-conductor worker resume <issue> [--project NAME]
|
|
81
|
-
omp-conductor worker stop <issue> --reason TEXT [--project NAME]
|
|
82
|
-
omp-conductor unblock <issue> [--force] [--no-requeue] [--project NAME]
|
|
83
|
-
omp-conductor unfreeze <repo> [--reason TEXT] [--project NAME]
|
|
84
|
-
omp-conductor verb <conductor_*> [--project NAME] [--arg k=v ...]
|
|
85
|
-
omp-conductor daemon [--once] [--port N] [--project NAME]
|
|
86
|
-
omp-conductor resume [--project NAME | --all]
|
|
87
|
-
omp-conductor brief-upgrade [--migrate|--retrofit] [--apply] [--file PATH] [--project NAME]
|
|
88
|
-
omp-conductor friction <escalation-digest|report-noise|report-surprise> --detail TEXT [--issue N] [--project NAME]
|
|
89
|
-
omp-conductor event record --category NAME --summary TEXT --evidence REF [--occurred-at ISO] [--project NAME]
|
|
90
|
-
omp-conductor report --text TEXT [--kind material|digest|tier2|decision-needed|fleet-stopped|confirmed-failure] [--events IDS] [--notices IDS] [--project NAME]
|
|
91
|
-
omp-conductor message --text TEXT [--category CATEGORY] [--blocks TEXT] [--project NAME]
|
|
92
|
-
omp-conductor decision open --question TEXT [--blocks TEXT] [--resolves-when COND] [--project NAME]
|
|
93
|
-
omp-conductor decision resolve <id> --answer TEXT [--project NAME]
|
|
94
|
-
omp-conductor decision withdraw <id> [--reason TEXT] [--project NAME]
|
|
95
|
-
omp-conductor decision list [--project NAME]
|
|
96
|
-
omp-conductor watch add --note TEXT [--blocks TEXT] [--resolves-when COND] [--project NAME]
|
|
97
|
-
omp-conductor watch list [--project NAME]
|
|
98
|
-
omp-conductor intake "<text>" [--project NAME]
|
|
99
|
-
omp-conductor intake list [--project NAME]
|
|
100
|
-
omp-conductor intake dismiss <id> [--project NAME]
|
|
101
|
-
omp-conductor intake groomed <id> --issue <url> [--project NAME]
|
|
102
|
-
omp-conductor help
|
|
103
|
-
|
|
104
|
-
setup interview, then write config.json, the labels, the briefs and the
|
|
105
|
-
staged host files behind one confirm. Bare is a full run — or, when
|
|
106
|
-
the project already exists, a chooser of which area to amend. Naming
|
|
107
|
-
an area positionally skips that chooser and amends only that area:
|
|
108
|
-
${AMEND_AREA_IDS.join(", ")}.
|
|
109
|
-
upgrade update the Bun-global CLI, omp plugin, Herdr recovery plugin, and
|
|
110
|
-
brief as one pinned release. Pauses only new claims, drains live
|
|
111
|
-
workers, reloads, verifies twice, and restores the prior dispatch
|
|
112
|
-
state. Run it from a shell outside the target Herdr session.
|
|
113
|
-
upgrade-install
|
|
114
|
-
the detached executor half of the fleet-installs-itself request:
|
|
115
|
-
the same transaction, journaled per surface, leaving verification
|
|
116
|
-
and dispatch restore to the first tick after the restart. Run by
|
|
117
|
-
the transient unit, never by hand in a Herdr session.
|
|
118
|
-
upgrade-rollback
|
|
119
|
-
the detached rollback unit: restore every surface the failed
|
|
120
|
-
upgrade-install touched, from the durable pre-install snapshot in
|
|
121
|
-
the upgrade journal, and report through the durable outbox.
|
|
122
|
-
start start the installed herdr-fleet.service when present, then run the
|
|
123
|
-
dispatch loop in the background and wait until it answers GET
|
|
124
|
-
/healthz on :8787 (override with --port). Refuses if one is running.
|
|
125
|
-
stop stop the conductor: pause claiming, disarm ticks, then stop the
|
|
126
|
-
dispatch daemon (systemctl-aware: an explicit stop is recorded by
|
|
127
|
-
systemd, so Restart=always does not undo it). Pane stays up unless
|
|
128
|
-
--pane is passed. To bounce the daemon without stopping the fleet,
|
|
129
|
-
use restart.
|
|
130
|
-
stop --pane
|
|
131
|
-
also stop the conductor agent's pane and pin herdr-conductor recovery
|
|
132
|
-
off for that agent only — it does NOT stop herdr-fleet.service or any
|
|
133
|
-
other herdr session. resume clears the pin.
|
|
134
|
-
restart drain the fleet by default: pause new claims, wait until live workers
|
|
135
|
-
reach 0/N (bounded by --timeout SECONDS, default 1800), restart keeping
|
|
136
|
-
the running daemon's port and project unless a flag overrides them,
|
|
137
|
-
then restore the prior dispatch state. --now skips the drain and
|
|
138
|
-
restarts immediately, orphaning any live runs (old behavior). A drain
|
|
139
|
-
that hits --timeout restarts nothing and leaves dispatch paused. On boot
|
|
140
|
-
the new process salvages dirty live worktrees before orphaning those
|
|
141
|
-
rows — see README "Deploying a new package onto a busy fleet". Goes
|
|
142
|
-
through systemctl when the unit owns the live pid; a failed
|
|
143
|
-
installed unit is reset and restarted through systemd, never
|
|
144
|
-
replaced by an unmanaged daemon.
|
|
145
|
-
status layered fleet report: dispatch (running|paused|stopped), ticks and
|
|
146
|
-
next due time, pane, herdr, Telegram bot/API health, daemon, caps
|
|
147
|
-
and active runs.
|
|
148
|
-
stats what the fleet accomplished and at what cost, read wholly from the
|
|
149
|
-
local store: issues merged, merge rate, queue-to-merge lead time
|
|
150
|
-
(median/p90), attempts per merged issue, metered spend per merged
|
|
151
|
-
issue, the failure-class breakdown of what did not merge, and
|
|
152
|
-
tracked gh calls over the window. Continuation chains collapse into
|
|
153
|
-
one journey; $0.00 runs are reported as unmetered, never averaged in
|
|
154
|
-
as free. --json prints the stable {project, window, ghCalls, empty,
|
|
155
|
-
total, repos} shape.
|
|
156
|
-
doctor read-only deployment health, one finding per past failure mode:
|
|
157
|
-
gh auth/scopes, exact-case configured labels, systemd unit drift
|
|
158
|
-
and runtime-dir ownership, config + backup freshness, sqlite
|
|
159
|
-
integrity, spend telemetry, reporting timezones, Telegram health.
|
|
160
|
-
The only write anywhere is the self-identified probe message that
|
|
161
|
-
--probe-telegram sends through the report transport. Exit code 0
|
|
162
|
-
only when nothing failed; --json prints the stable CI shape. Run
|
|
163
|
-
it after install and after every upgrade.
|
|
164
|
-
restore-db
|
|
165
|
-
put conductor.db back to a restorable snapshot, replacing the live
|
|
166
|
-
file and dropping stale -wal/-shm sidecars. Refuses while a daemon
|
|
167
|
-
is live — restoring under a running daemon is a corruption path.
|
|
168
|
-
The default source is the newest snapshot in the configured db
|
|
169
|
-
backup directory; an explicit snapshot path overrides it.
|
|
170
|
-
board open the live keyboard-driven fleet board. It renders queue holds,
|
|
171
|
-
every run lifecycle stage, recent merges, spend and health; Enter
|
|
172
|
-
follows a selected transcript without leaving the board. --json (or
|
|
173
|
-
a non-interactive stdin/stdout) prints a one-shot JSON snapshot of
|
|
174
|
-
the same lanes instead.
|
|
175
|
-
dashboard
|
|
176
|
-
serve the fleet dashboard in a browser: the UI shell and GET
|
|
177
|
-
/api/projects, bearer-authenticated with the token minted 0600 at
|
|
178
|
-
<state>/dashboard-token on first start. Binds 127.0.0.1:8788 by
|
|
179
|
-
default; a non-loopback --host still starts but prints a warning
|
|
180
|
-
naming the token file. A separate process — never a route on the
|
|
181
|
-
daemon port.
|
|
182
|
-
ledger every conductor-verb call and how the daemon decided it: the verb,
|
|
183
|
-
the arguments, allow or refuse, the named refusal reason, and the
|
|
184
|
-
resulting sha. Sessions cannot push, open, merge, label or release
|
|
185
|
-
except through those verbs, so this is the record of what they tried
|
|
186
|
-
as well as what they did. --issue narrows it to one issue's run.
|
|
187
|
-
hold soft stop: pause claiming AND disarm ticks. Daemon and pane stay up.
|
|
188
|
-
This is "stop the conductor overnight" without killing processes.
|
|
189
|
-
Use --all to target every configured project.
|
|
190
|
-
arm proof-gated: send a Telegram challenge and write the arm marker only
|
|
191
|
-
after your reply appears as a user turn in the orchestrator transcript.
|
|
192
|
-
Never auto-armed by resume/hold. Use --all for every project.
|
|
193
|
-
disarm remove the arm marker so ticks skip. Processes untouched. Use --all
|
|
194
|
-
to target every configured project.
|
|
195
|
-
tail follow the newest run for <issue>: the worker's assistant text and
|
|
196
|
-
the tools it calls, printed as they land. Workers are sessions inside
|
|
197
|
-
the daemon rather than terminals, so this is the only way to watch
|
|
198
|
-
one live. Runs until Ctrl-C, or until the run has finished and its
|
|
199
|
-
transcript has stopped growing.
|
|
200
|
-
extend raise a live run's turn ceiling, or set a bounded one-shot ceiling
|
|
201
|
-
after a failed, killed, orphaned or blocked run. Refuses values outside
|
|
202
|
-
configured bounds.
|
|
203
|
-
worker cooperatively pause one live worker at harness idle, then resume the
|
|
204
|
-
same session with a continuation prompt. Its wall clock is frozen
|
|
205
|
-
while parked. Distinct from fleet-level pause/resume.
|
|
206
|
-
unblock clear <issue>'s blocked and failed labels so the next tick can claim
|
|
207
|
-
it again — the supported way back for an escalation you answered,
|
|
208
|
-
and why the brief's "never hand-edit a state label" rule can stay
|
|
209
|
-
absolute. Run history is kept; answered blocks consume the separate
|
|
210
|
-
operational-continuation budget, not failed implementation attempts.
|
|
211
|
-
Refuses when the newest attempt's work could not be committed and
|
|
212
|
-
its worktree is the only copy: re-claiming removes that tree. Use
|
|
213
|
-
--force once you have recovered it or accepted the loss. Restores
|
|
214
|
-
the project queue label by default so the issue is actually
|
|
215
|
-
dispatchable; --no-requeue clears state labels only (the "about to
|
|
216
|
-
close it" case).
|
|
217
|
-
verb run one conductor_* verb as the orchestrator, from the CLI: the same
|
|
218
|
-
checks and the same ledger rows a session's call would get. This is
|
|
219
|
-
how an external orchestrator merges, labels, releases or reads PR
|
|
220
|
-
state without a raw gh call that skips every gate. Arguments are
|
|
221
|
-
strings, one per --arg (e.g. --arg prUrl=https://x --arg headSha=y).
|
|
222
|
-
A refusal exits 3. See conductor_pr_merge/conductor_label/
|
|
223
|
-
conductor_release/conductor_pr_update_branch in the brief.
|
|
224
|
-
event persist one ordinary material outcome without sending it. Category
|
|
225
|
-
is a short lowercase slug; summary and evidence name what happened
|
|
226
|
-
and where it can be verified. --occurred-at defaults to now.
|
|
227
|
-
report hand a rendered report to the daemon's durable outbox. The report is
|
|
228
|
-
persisted before anything is sent, delivered by the daemon with
|
|
229
|
-
bounded retries, and shown by status until it lands. Delivery is
|
|
230
|
-
at-least-once: a crash mid-send is retried and the retry says it may
|
|
231
|
-
be a repeat. --kind digest is accepted at most once per local day,
|
|
232
|
-
decided from the ledger rather than from what you remember sending.
|
|
233
|
-
A digest associates the comma-separated --events and --notices rows
|
|
234
|
-
atomically; omitted rows stay owed. The other --kind values (tier2,
|
|
235
|
-
decision-needed, fleet-stopped, confirmed-failure) declare the
|
|
236
|
-
report's interrupt category: the reporting policy decides between
|
|
237
|
-
sending now and holding, exactly as for a daemon escalation. An
|
|
238
|
-
identical retry is refused only while the earlier handoff is still
|
|
239
|
-
undelivered, and admitted again once it lands (#453).
|
|
240
|
-
message deliver one direct Telegram message to this project's own chat and
|
|
241
|
-
forum topic, resolved from config rather than from whichever chat
|
|
242
|
-
last wrote to the session. This is how a locally injected tick
|
|
243
|
-
answers or asks something directly: telegram_send keeps the active
|
|
244
|
-
topic only while it names no chat, and a tick has no active topic to
|
|
245
|
-
keep. A question — text beginning "QUESTION:", or a --category that
|
|
246
|
-
is not material — records an open decision row before delivery
|
|
247
|
-
(parked on silence: still pending in every tick until answered or
|
|
248
|
-
the seven-day expiry, so no one has to remember a separate
|
|
249
|
-
\`decision open\`), and --category carries the escalation category
|
|
250
|
-
directly instead of a text prefix. The operator's availability
|
|
251
|
-
policy still decides between sending now and holding a notice,
|
|
252
|
-
exactly as it does for an autonomous tick.
|
|
253
|
-
decision record, list and close the questions you have put to your operator.
|
|
254
|
-
A question that lives only in a session's context is lost to the next
|
|
255
|
-
compaction, so \`decision open\` writes it down and every tick's prompt
|
|
256
|
-
carries what is still owed. \`--resolves-when\` attaches a condition the
|
|
257
|
-
daemon checks for you (pr-merged:<url>, issue-closed:<n>,
|
|
258
|
-
npm-version:<pkg>@<version>); a met one is flagged in that digest.
|
|
259
|
-
Unanswered rows expire after seven days rather than accumulating.
|
|
260
|
-
decision open --question TEXT [--blocks TEXT] [--resolves-when COND]
|
|
261
|
-
decision resolve <id> --answer TEXT
|
|
262
|
-
decision withdraw <id> [--reason TEXT]
|
|
263
|
-
decision list
|
|
264
|
-
watch set a condition or carry note for the orchestrator itself, with no
|
|
265
|
-
human in the loop — the same no-sleep surface \`decision open
|
|
266
|
-
--resolves-when\` used to serve, given its own verb so it never renders
|
|
267
|
-
as a question put to the operator. \`--resolves-when\` attaches a
|
|
268
|
-
condition the daemon checks for you; a met watch wakes the next tick
|
|
269
|
-
with its note, exactly as a met question does, but it is listed under
|
|
270
|
-
its own heading and never under "Open operator decisions", and it has
|
|
271
|
-
no seven-day expiry. \`watch list\` shows open watches.
|
|
272
|
-
watch add --note TEXT [--blocks TEXT] [--resolves-when COND]
|
|
273
|
-
watch list
|
|
274
|
-
intake keep a raw idea durably before it becomes anything: record it now
|
|
275
|
-
with \`omp-conductor intake "<text>"\`, list what is still pending,
|
|
276
|
-
dismiss what turned out to be nothing. Backed by the sqlite store,
|
|
277
|
-
not a session, so ideas survive restarts; the orchestrator grooms
|
|
278
|
-
one into an issue as its #300 duty and records that provenance with
|
|
279
|
-
\`omp-conductor intake groomed <id> --issue <url>\`.
|
|
280
|
-
friction record a bounded observation the daemon cannot classify itself:
|
|
281
|
-
an escalation that belonged in a digest, or a tick report that was
|
|
282
|
-
noise/surprising. Repeated observations feed the existing Learning
|
|
283
|
-
loop; recording one never edits policy by itself.
|
|
284
|
-
daemon run the dispatch loop in the foreground; --once runs a single tick
|
|
285
|
-
and exits. This is what \`start\` launches.
|
|
286
|
-
resume clear pause and any pane-recovery pin. Does NOT re-arm: run arm after
|
|
287
|
-
an inbound Telegram proof to bring ticks back. Use --all for every project.
|
|
288
|
-
setup host [NAME]
|
|
289
|
-
re-stage the systemd unit and run the install behind one confirm.
|
|
290
|
-
The units are host-global; NAME (or --project NAME) says which
|
|
291
|
-
project's per-project tail (tick config, brief link) to write, and
|
|
292
|
-
is required on a host with several configured projects.
|
|
293
|
-
setup graph
|
|
294
|
-
set up the code-graph indexes workers query instead of grepping, end
|
|
295
|
-
to end: check prerequisites, clone any missing index-only clone as
|
|
296
|
-
you, install and enable the reindex timer as root, then seed one
|
|
297
|
-
indexing run and verify it. --no-seed enables without seeding;
|
|
298
|
-
--print changes nothing. Exits 1 when no repo has graphProject.
|
|
299
|
-
brief-upgrade
|
|
300
|
-
inspect the brief overlay (package floor + POLICY.md). Reports by
|
|
301
|
-
default. --migrate lifts a bannered ORCHESTRATOR.md owned half into
|
|
302
|
-
POLICY.md and recomposes. --retrofit proposes inserting the YOURS TO
|
|
303
|
-
EDIT banner before the first Releases/Project context/Reporting/
|
|
304
|
-
Amendments heading (#20); --retrofit --apply writes it. --apply only
|
|
305
|
-
ever confirms one of those two — the legacy single-file merge was
|
|
306
|
-
removed in 0.4.3. --file checks a brief that is not where the wizard
|
|
307
|
-
would have put it.
|
|
308
|
-
help print this text (also --help, -h).
|
|
309
|
-
--version
|
|
310
|
-
print the installed omp-conductor package version (also -V, version).
|
|
311
|
-
|
|
312
|
-
Pause is a flag file under the state directory, so it applies to the selected
|
|
313
|
-
project (or every configured project with --all) and survives a daemon restart.
|
|
314
|
-
Hold also removes the arm marker the heartbeat reads, so both brains go quiet
|
|
315
|
-
without killing processes. A running daemon is tracked by a pidfile under
|
|
316
|
-
$OMP_CONDUCTOR_RUNTIME_DIR (default
|
|
317
|
-
~/.omp/run/daemons/omp-conductor), written whether it was started in the
|
|
318
|
-
background or in the foreground, and probed for liveness on every read — a
|
|
319
|
-
stale one never blocks a start.
|
|
320
|
-
|
|
321
|
-
Stop the conductor:
|
|
322
|
-
hold no claims, no tick sends (inspectable)
|
|
323
|
-
stop hold + stop dispatch daemon
|
|
324
|
-
stop --pane stop + pin conductor-pane recovery off
|
|
325
|
-
resume && arm clear pause and any pane pin, then prove inbound Telegram`;
|
|
55
|
+
const USAGE = renderUsage(COMMAND_MANIFEST);
|
|
326
56
|
|
|
327
57
|
/** Applies both `--port 9000` and `--port=9000`; returns undefined when absent. */
|
|
328
58
|
function flag(argv: string[], name: string): string | undefined {
|
|
@@ -413,46 +143,10 @@ function issueArg(verb: string, raw: string | undefined): number {
|
|
|
413
143
|
return issue;
|
|
414
144
|
}
|
|
415
145
|
|
|
416
|
-
|
|
417
|
-
const argv = process.argv.slice(2);
|
|
418
|
-
const cmd = argv[0];
|
|
419
|
-
/**
|
|
420
|
-
* Parsed once, because nearly every verb takes it and a per-case
|
|
421
|
-
* `projectFlag` is a line each of them can forget. Named `projectFlag`
|
|
422
|
-
* rather than `project` because many cases bind their own resolved
|
|
423
|
-
* `ProjectConfig` under that name.
|
|
424
|
-
*/
|
|
425
|
-
const projectFlag = flag(argv, "project");
|
|
426
|
-
/**
|
|
427
|
-
* The parsed essentials every command receives. Argument parsing lives above
|
|
428
|
-
* (and behind) this object; a command module reads what it needs and never
|
|
429
|
-
* imports this entry point, so no verb depends on another's edits here beyond
|
|
430
|
-
* the table line that names it.
|
|
431
|
-
*/
|
|
432
|
-
const ctx: CommandContext = {
|
|
433
|
-
argv,
|
|
434
|
-
projectFlag,
|
|
435
|
-
flag: (name) => flag(argv, name),
|
|
436
|
-
portFlag: () => portFlag(argv),
|
|
437
|
-
turnsFlag: () => turnsFlag(argv),
|
|
438
|
-
digestIdsFlag: (name) => digestIdsFlag(argv, name),
|
|
439
|
-
issueArg: (verb, raw) => issueArg(verb, raw),
|
|
440
|
-
targetProjects: () => targetProjects(),
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
function targetProjects(): ProjectConfig[] {
|
|
444
|
-
// The fleet resolution, delegated to the scope machinery (#514) so the
|
|
445
|
-
// arm/disarm/hold/resume/stop "every project, or the named one" shape is
|
|
446
|
-
// the same code the classification describes it as.
|
|
447
|
-
return resolveProjectsByScope(loadConfig(), "fleet", projectFlag, argv.includes("--all"));
|
|
448
|
-
}
|
|
449
|
-
|
|
450
146
|
/**
|
|
451
147
|
* Removed verbs keep an entry rather than falling through to usage: an operator
|
|
452
148
|
* who typed the old word gets the new one, not a wall of help text. Exit 2 is
|
|
453
|
-
* the same code an unknown verb uses.
|
|
454
|
-
* fixed text and nothing to grow, so they stay as entries here instead of a
|
|
455
|
-
* file per alias — the #462 acceptance carve-out for two-line cases.
|
|
149
|
+
* the same code an unknown verb uses.
|
|
456
150
|
*/
|
|
457
151
|
const REMOVED: Record<string, string> = {
|
|
458
152
|
halt: '"halt" is now "stop" — see help',
|
|
@@ -461,79 +155,114 @@ const REMOVED: Record<string, string> = {
|
|
|
461
155
|
"graph-setup": '"graph-setup" is now "setup graph" — see help',
|
|
462
156
|
};
|
|
463
157
|
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
};
|
|
158
|
+
/** The registration table used by dispatch and the manifest drift test. */
|
|
159
|
+
export function commandHandlers(ctx: CommandContext): Record<string, CommandHandler> {
|
|
160
|
+
return {
|
|
161
|
+
"--version": () => versionCommand(),
|
|
162
|
+
"-V": () => versionCommand(),
|
|
163
|
+
version: () => versionCommand(),
|
|
164
|
+
complete: async () =>
|
|
165
|
+
(await import("./commands/complete.ts")).completeCommand(ctx.argv.slice(1)),
|
|
166
|
+
setup: () => setupCommand(ctx),
|
|
167
|
+
upgrade: () => upgradeCommand(ctx),
|
|
168
|
+
"upgrade-install": () => upgradeInstallCommand(ctx),
|
|
169
|
+
"upgrade-rollback": () => upgradeRollbackCommand(ctx),
|
|
170
|
+
daemon: () => daemonCommand(ctx),
|
|
171
|
+
start: () => startCommand(ctx),
|
|
172
|
+
stop: () => stopCommand(ctx),
|
|
173
|
+
restart: () => restartCommand(ctx),
|
|
174
|
+
status: () => statusCommand(ctx),
|
|
175
|
+
stats: () => statsCommand(ctx),
|
|
176
|
+
doctor: () => doctorCommand(ctx),
|
|
177
|
+
ledger: () => ledgerCommand(ctx),
|
|
178
|
+
board: () => boardCommand(ctx),
|
|
179
|
+
dashboard: () => dashboardCommand(ctx),
|
|
180
|
+
hold: () => holdCommand(ctx),
|
|
181
|
+
arm: () => armCommand(ctx),
|
|
182
|
+
disarm: () => disarmCommand(ctx),
|
|
183
|
+
tail: () => tailCommand(ctx),
|
|
184
|
+
extend: () => extendCommand(ctx),
|
|
185
|
+
worker: () => workerCommand(ctx),
|
|
186
|
+
unblock: () => unblockCommand(ctx),
|
|
187
|
+
unfreeze: () => unfreezeCommand(ctx),
|
|
188
|
+
verb: () => verbCommand(ctx),
|
|
189
|
+
event: () => eventCommand(ctx),
|
|
190
|
+
report: () => reportCommand(ctx),
|
|
191
|
+
message: () => messageCommand(ctx),
|
|
192
|
+
decision: () => decisionCommand(ctx),
|
|
193
|
+
watch: () => watchCommand(ctx),
|
|
194
|
+
intake: () => intakeCommand(ctx),
|
|
195
|
+
friction: () => frictionCommand(ctx),
|
|
196
|
+
"restore-db": () => restoreDbCommand(ctx),
|
|
197
|
+
resume: () => resumeCommand(ctx),
|
|
198
|
+
"brief-upgrade": () => briefUpgradeCommand(ctx),
|
|
199
|
+
help: () => helpCommand(USAGE),
|
|
200
|
+
"--help": () => helpCommand(USAGE),
|
|
201
|
+
"-h": () => helpCommand(USAGE),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
511
204
|
|
|
512
|
-
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
205
|
+
export async function runCli(argv: string[] = process.argv.slice(2)): Promise<void> {
|
|
206
|
+
const cmd = argv[0];
|
|
207
|
+
// Completion runs on every TAB press. Answer before config, daemon, or
|
|
208
|
+
// project-context construction so a shell request stays cheap and fail-empty.
|
|
209
|
+
if (cmd === "complete") {
|
|
210
|
+
try {
|
|
211
|
+
const requestDelimiter = argv.indexOf("--", 1);
|
|
212
|
+
const ownArgs = argv.slice(1, requestDelimiter === -1 ? undefined : requestDelimiter);
|
|
213
|
+
const ownProjectFlag = flag(ownArgs, "project");
|
|
214
|
+
if (COMMAND_SCOPES[cmd] === "none" && ownProjectFlag !== undefined) {
|
|
215
|
+
process.stderr.write(
|
|
216
|
+
`omp-conductor: ${cmd} does not take a project — it needs no --project\n`,
|
|
217
|
+
);
|
|
218
|
+
process.exit(2);
|
|
219
|
+
}
|
|
220
|
+
(await import("./commands/complete.ts")).completeCommand(argv.slice(1));
|
|
221
|
+
return;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
process.stderr.write(`omp-conductor: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
524
226
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
227
|
+
const projectFlag = flag(argv, "project");
|
|
228
|
+
|
|
229
|
+
const ctx: CommandContext = {
|
|
230
|
+
argv,
|
|
231
|
+
projectFlag,
|
|
232
|
+
flag: (name) => flag(argv, name),
|
|
233
|
+
portFlag: () => portFlag(argv),
|
|
234
|
+
turnsFlag: () => turnsFlag(argv),
|
|
235
|
+
digestIdsFlag: (name) => digestIdsFlag(argv, name),
|
|
236
|
+
issueArg: (verb, raw) => issueArg(verb, raw),
|
|
237
|
+
targetProjects: () =>
|
|
238
|
+
resolveProjectsByScope(loadConfig(), "fleet", projectFlag, argv.includes("--all")),
|
|
239
|
+
};
|
|
240
|
+
const commands = commandHandlers(ctx);
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const removed = cmd === undefined ? undefined : REMOVED[cmd];
|
|
244
|
+
if (removed !== undefined) {
|
|
245
|
+
process.stderr.write(`omp-conductor: ${removed}\n`);
|
|
246
|
+
process.exit(2);
|
|
247
|
+
}
|
|
248
|
+
const handler = cmd === undefined ? undefined : commands[cmd];
|
|
249
|
+
if (handler === undefined) {
|
|
250
|
+
process.stderr.write(
|
|
251
|
+
`${cmd === undefined ? "omp-conductor: no subcommand" : `omp-conductor: unknown subcommand "${cmd}"`}\n\n${USAGE}\n`,
|
|
252
|
+
);
|
|
253
|
+
process.exit(2);
|
|
254
|
+
}
|
|
255
|
+
// A "none"-scope verb does no per-project work, so --project cannot change
|
|
256
|
+
// what it does: refuse it with a reason instead of silently ignoring it.
|
|
257
|
+
if (COMMAND_SCOPES[cmd ?? ""] === "none" && projectFlag !== undefined) {
|
|
258
|
+
process.stderr.write(`omp-conductor: ${cmd} does not take a project — it needs no --project\n`);
|
|
259
|
+
process.exit(2);
|
|
260
|
+
}
|
|
261
|
+
await handler(ctx);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
process.stderr.write(`omp-conductor: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
264
|
+
process.exit(1);
|
|
532
265
|
}
|
|
533
|
-
await handler(ctx);
|
|
534
|
-
} catch (err) {
|
|
535
|
-
// Config, lifecycle and `gh` errors are written to be read by a human, so
|
|
536
|
-
// surface the message rather than a stack.
|
|
537
|
-
process.stderr.write(`omp-conductor: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
538
|
-
process.exit(1);
|
|
539
266
|
}
|
|
267
|
+
|
|
268
|
+
if (import.meta.main) await runCli();
|