@detergent-software/conductor 3.0.0-dev.3 → 3.0.0-dev.31
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 +251 -0
- package/README.md +203 -64
- package/build/brief-cli.js +1 -1
- package/build/brief-cli.js.map +1 -1
- package/build/broker-cli.js +24 -25
- package/build/broker-cli.js.map +1 -1
- package/build/broker.js +66 -92
- package/build/broker.js.map +1 -1
- package/build/claude.js +50 -7
- package/build/claude.js.map +1 -1
- package/build/cli.js +143 -63
- package/build/cli.js.map +1 -1
- package/build/clones.js +92 -0
- package/build/clones.js.map +1 -0
- package/build/code-host-cli.js +126 -37
- package/build/code-host-cli.js.map +1 -1
- package/build/code-host-service.js +13 -1
- package/build/code-host-service.js.map +1 -1
- package/build/code-host.js +82 -16
- package/build/code-host.js.map +1 -1
- package/build/conditions.js +27 -10
- package/build/conditions.js.map +1 -1
- package/build/config.js +58 -14
- package/build/config.js.map +1 -1
- package/build/console.js +40 -24
- package/build/console.js.map +1 -1
- package/build/credentials.js +47 -34
- package/build/credentials.js.map +1 -1
- package/build/daemon.js +45 -25
- package/build/daemon.js.map +1 -1
- package/build/help.js +179 -42
- package/build/help.js.map +1 -1
- package/build/history.js +4 -2
- package/build/history.js.map +1 -1
- package/build/host/credential-manager.js +109 -0
- package/build/host/credential-manager.js.map +1 -0
- package/build/host/keychain.js +44 -0
- package/build/host/keychain.js.map +1 -0
- package/build/host/launchd.js +118 -0
- package/build/host/launchd.js.map +1 -0
- package/build/host/libsecret.js +40 -0
- package/build/host/libsecret.js.map +1 -0
- package/build/host/macos-terminal.js +63 -0
- package/build/host/macos-terminal.js.map +1 -0
- package/build/host/platform.js +4 -0
- package/build/host/platform.js.map +1 -0
- package/build/host/secrets.js +46 -0
- package/build/host/secrets.js.map +1 -0
- package/build/host/shell.js +26 -0
- package/build/host/shell.js.map +1 -0
- package/build/host/startup.js +26 -0
- package/build/host/startup.js.map +1 -0
- package/build/host/systemd.js +91 -0
- package/build/host/systemd.js.map +1 -0
- package/build/host/task-scheduler.js +78 -0
- package/build/host/task-scheduler.js.map +1 -0
- package/build/host/terminal.js +46 -0
- package/build/host/terminal.js.map +1 -0
- package/build/host/tmux.js +39 -0
- package/build/host/tmux.js.map +1 -0
- package/build/host/windows-terminal.js +46 -0
- package/build/host/windows-terminal.js.map +1 -0
- package/build/in-process.js +6 -5
- package/build/in-process.js.map +1 -1
- package/build/inbox.js +63 -17
- package/build/inbox.js.map +1 -1
- package/build/init.js +510 -0
- package/build/init.js.map +1 -0
- package/build/landing.js +86 -7
- package/build/landing.js.map +1 -1
- package/build/lib/providers/ado/code-host.js +5 -1
- package/build/lib/providers/ado/code-host.js.map +1 -1
- package/build/lib/providers/ado/pipelines.js +138 -26
- package/build/lib/providers/ado/pipelines.js.map +1 -1
- package/build/lib/providers/ado/trigger.js +5 -0
- package/build/lib/providers/ado/trigger.js.map +1 -1
- package/build/lib/providers/ado/writes.js +10 -1
- package/build/lib/providers/ado/writes.js.map +1 -1
- package/build/lib/providers/github/actions.js +90 -3
- package/build/lib/providers/github/actions.js.map +1 -1
- package/build/lib/providers/github/code-host.js +2 -0
- package/build/lib/providers/github/code-host.js.map +1 -1
- package/build/lib/providers/github/writes.js +24 -2
- package/build/lib/providers/github/writes.js.map +1 -1
- package/build/lib/providers/index.js +4 -3
- package/build/lib/providers/index.js.map +1 -1
- package/build/lib/providers/npm/stage.js +28 -5
- package/build/lib/providers/npm/stage.js.map +1 -1
- package/build/lib/providers/timing.js +10 -0
- package/build/lib/providers/timing.js.map +1 -0
- package/build/lib/providers/verdicts/pr.js +47 -6
- package/build/lib/providers/verdicts/pr.js.map +1 -1
- package/build/lib/providers/verdicts/runs.js +12 -2
- package/build/lib/providers/verdicts/runs.js.map +1 -1
- package/build/local-files.js +95 -0
- package/build/local-files.js.map +1 -0
- package/build/manager.js +171 -47
- package/build/manager.js.map +1 -1
- package/build/note.js +1 -1
- package/build/note.js.map +1 -1
- package/build/notify.js +101 -39
- package/build/notify.js.map +1 -1
- package/build/npm-reads.js +323 -0
- package/build/npm-reads.js.map +1 -0
- package/build/paths.js +44 -1
- package/build/paths.js.map +1 -1
- package/build/plugin.js +10 -0
- package/build/plugin.js.map +1 -1
- package/build/profile-fields.js +151 -0
- package/build/profile-fields.js.map +1 -0
- package/build/profile-layers.js +264 -0
- package/build/profile-layers.js.map +1 -0
- package/build/profile.js +93 -50
- package/build/profile.js.map +1 -1
- package/build/project-check-cli.js +19 -11
- package/build/project-check-cli.js.map +1 -1
- package/build/project-check.js +206 -108
- package/build/project-check.js.map +1 -1
- package/build/pushover.js +5 -5
- package/build/pushover.js.map +1 -1
- package/build/registry.js +195 -36
- package/build/registry.js.map +1 -1
- package/build/release-kinds.js +34 -0
- package/build/release-kinds.js.map +1 -0
- package/build/rule-actions.js +4 -2
- package/build/rule-actions.js.map +1 -1
- package/build/rules-service.js +101 -44
- package/build/rules-service.js.map +1 -1
- package/build/rules.js +15 -15
- package/build/rules.js.map +1 -1
- package/build/run.js +20 -7
- package/build/run.js.map +1 -1
- package/build/runs.js +217 -67
- package/build/runs.js.map +1 -1
- package/build/scheduler.js +11 -1
- package/build/scheduler.js.map +1 -1
- package/build/server.js +6 -25
- package/build/server.js.map +1 -1
- package/build/spend.js +4 -0
- package/build/spend.js.map +1 -1
- package/build/store.js +86 -25
- package/build/store.js.map +1 -1
- package/build/timeline.js +104 -0
- package/build/timeline.js.map +1 -0
- package/build/tracker-reads-cli.js +4 -5
- package/build/tracker-reads-cli.js.map +1 -1
- package/build/train-cli.js +6 -5
- package/build/train-cli.js.map +1 -1
- package/build/upgrade.js +8 -9
- package/build/upgrade.js.map +1 -1
- package/build/wait.js +9 -9
- package/build/wait.js.map +1 -1
- package/build/watchers/ado.js +42 -18
- package/build/watchers/ado.js.map +1 -1
- package/build/watchers/github.js +19 -4
- package/build/watchers/github.js.map +1 -1
- package/build/watchers/index.js +2 -2
- package/build/watchers/index.js.map +1 -1
- package/build/watchers/npm.js +6 -4
- package/build/watchers/npm.js.map +1 -1
- package/build/webhook.js +80 -0
- package/build/webhook.js.map +1 -0
- package/build/worktrees.js +199 -92
- package/build/worktrees.js.map +1 -1
- package/package.json +61 -61
- package/plugin/skills/conductor-console/SKILL.md +4 -4
- package/plugin/skills/work-item/SKILL.md +13 -11
- package/build/checkout-sync.js +0 -249
- package/build/checkout-sync.js.map +0 -1
- package/build/credential.js +0 -66
- package/build/credential.js.map +0 -1
- package/build/startup.js +0 -92
- package/build/startup.js.map +0 -1
- package/build/tab.js +0 -32
- package/build/tab.js.map +0 -1
package/build/cli.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import {
|
|
4
|
+
import { delimiter, join } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { parseArgs } from 'node:util';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { briefCommand } from "./brief-cli.js";
|
|
9
9
|
import { heldLocks, lockCommand, lockLine, portsCommand } from "./broker-cli.js";
|
|
10
|
+
import { trustStep } from "./claude.js";
|
|
10
11
|
import { callApi, NotRunningError, ping, readEndpoint } from "./client.js";
|
|
11
|
-
import { branchCommand, checkoutCommand, fileCommand, landCommand, modeCommand, prCreateCommand, prThreadsCommand, replyCommand, runsCommand, stateCommand, } from "./code-host-cli.js";
|
|
12
|
+
import { branchCommand, checkoutCommand, fileCommand, landCommand, modeCommand, prCreateCommand, prThreadsCommand, replyCommand, runsCommand, stateCommand, timelineCommand, } from "./code-host-cli.js";
|
|
12
13
|
import { configSchema, loadConfig } from "./config.js";
|
|
13
|
-
import { claimConsole, CONSOLE_WINDOW, consoleArgs,
|
|
14
|
+
import { claimConsole, CONSOLE_WINDOW, consoleArgs, consoleEnv, consoleTab, escalationLines, escalationSchema, handledSchema, } from "./console.js";
|
|
14
15
|
import { AlreadyRunningError, logCrashes, startDaemon } from "./daemon.js";
|
|
15
16
|
import { ConductorError } from "./errors.js";
|
|
16
17
|
import { eventsCommand } from "./events.js";
|
|
17
18
|
import { commandHelp, isCommand, listing, terminalStyle, terminalWidth, unknownCommand, usageError } from "./help.js";
|
|
18
19
|
import { flagWindow, historyLines, historyReport, historySchema, parseRange, retroMarkdown, } from "./history.js";
|
|
20
|
+
import { startup } from "./host/startup.js";
|
|
21
|
+
import { terminal } from "./host/terminal.js";
|
|
19
22
|
import { inboxLines } from "./inbox.js";
|
|
23
|
+
import { initProject, terminalAsk } from "./init.js";
|
|
20
24
|
import { parseJsonc } from "./jsonc.js";
|
|
21
25
|
import { npmStage } from "./lib/providers/index.js";
|
|
22
26
|
import { processAlive, readPid } from "./lock.js";
|
|
@@ -24,32 +28,32 @@ import { rotatingLog } from "./log.js";
|
|
|
24
28
|
import { fleetWorkerSchema, storedFleet, workerByRef, workerLine } from "./manager.js";
|
|
25
29
|
import { noteCommand } from "./note.js";
|
|
26
30
|
import { isQuiet, notifyLines, notifyStatusSchema, parseUntil, testResultSchema } from "./notify.js";
|
|
31
|
+
import { sharedNpmReads } from "./npm-reads.js";
|
|
27
32
|
import { overrideViewSchema, PAUSE } from "./overrides.js";
|
|
28
33
|
import { packageInfo } from "./package-info.js";
|
|
29
34
|
import { conductorPaths } from "./paths.js";
|
|
30
35
|
import { refreshConductorPlugin } from "./plugin.js";
|
|
31
|
-
import {
|
|
36
|
+
import { profileLines, projectProfile } from "./profile-layers.js";
|
|
37
|
+
import { projectCheckCommand, projectCheckReport } from "./project-check-cli.js";
|
|
32
38
|
import { noProject } from "./project-check.js";
|
|
33
39
|
import { pushoverCredential, pushoverSender } from "./pushover.js";
|
|
34
|
-
import { registerProject } from "./registry.js";
|
|
40
|
+
import { cloneProject, registerProject } from "./registry.js";
|
|
35
41
|
import { rulesLines, rulesService, rulesTestResultSchema, rulesViewSchema, testLines, } from "./rules-service.js";
|
|
36
42
|
import { resolveUntil } from "./rules.js";
|
|
37
43
|
import { outputOf, run } from "./run.js";
|
|
38
44
|
import { queueLines, queueViewSchema } from "./scheduler.js";
|
|
39
45
|
import { awaitStart, serveDetached, ServeStartError } from "./serve.js";
|
|
40
46
|
import { localDay } from "./spend.js";
|
|
41
|
-
import { installStartup, removeStartup, runStartup, startupRunsCli, startupSupervises, TASK_NAME } from "./startup.js";
|
|
42
47
|
import { answerStopRequest, dropStaleStopRequest, pendingStopRequest, STOP_POLL_MS, stopSupervisor, } from "./stop-request.js";
|
|
43
48
|
import { inboxItemSchema, openStore, projectSchema, waitSchema, workerSchema, } from "./store.js";
|
|
44
49
|
import { RESTART_ENV, restartInfoSchema, SERVE_LOCKED_EXIT, serveChild, supervise, SUPERVISE_POLICY, } from "./supervise.js";
|
|
45
|
-
import { openWindowsTerminal } from "./tab.js";
|
|
46
50
|
import { commentsCommand, notesCommand, searchCommand } from "./tracker-reads-cli.js";
|
|
47
51
|
import { trainCommand } from "./train-cli.js";
|
|
48
52
|
import { parseTag, restartDaemon, tagVersion, upgrade } from "./upgrade.js";
|
|
49
53
|
import { waitCommand } from "./wait.js";
|
|
50
54
|
import { whyLines, whySchema } from "./why.js";
|
|
51
55
|
const STOP_WAIT_MS = 10_000;
|
|
52
|
-
// A restarted daemon opens its store and binds the port; under the logon
|
|
56
|
+
// A restarted daemon opens its store and binds the port; under the logon startup node starts cold.
|
|
53
57
|
const START_WAIT_MS = 30_000;
|
|
54
58
|
const NPM_INSTALL_TIMEOUT_MS = 5 * 60_000;
|
|
55
59
|
// A launch makes a worktree (pnpm install) before the session starts.
|
|
@@ -85,7 +89,13 @@ const retireBody = z.object({
|
|
|
85
89
|
work: workSchema.extend({ checkouts: z.record(z.string(), workSchema).optional() }).optional(),
|
|
86
90
|
worker: workerSchema,
|
|
87
91
|
});
|
|
88
|
-
|
|
92
|
+
// hint: from ATK-529 on; an older daemon sends none.
|
|
93
|
+
const attachBody = z.object({
|
|
94
|
+
hint: z.string().nullable().default(null),
|
|
95
|
+
resumed: z.boolean(),
|
|
96
|
+
tab: z.string().nullable(),
|
|
97
|
+
worker: workerSchema,
|
|
98
|
+
});
|
|
89
99
|
const inboxBody = z.object({ items: z.array(inboxItemSchema) });
|
|
90
100
|
const answerBody = z.object({
|
|
91
101
|
comment: z.object({ id: z.string(), url: z.string().nullable() }).nullable(),
|
|
@@ -97,11 +107,14 @@ const answerRefusal = z.object({
|
|
|
97
107
|
error: z.string(),
|
|
98
108
|
posted: z.enum(['no', 'unknown']).optional(),
|
|
99
109
|
});
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
// `conduct project add <url | path> [--name <name>]`: the clone is made here, where its progress
|
|
111
|
+
// shows and no API timeout applies, then the daemon registers it (or the store, with none running).
|
|
112
|
+
// `next`: false for `conduct init`, which says what comes next itself.
|
|
113
|
+
async function addProject(paths, source, name, next = true) {
|
|
114
|
+
const made = await cloneProject(paths, source, { ...(name === undefined ? {} : { name }), say: out });
|
|
102
115
|
let result;
|
|
103
116
|
try {
|
|
104
|
-
result = addBody.parse(await expectOk(callApi(paths, { body: {
|
|
117
|
+
result = addBody.parse(await expectOk(callApi(paths, { body: { name: made.name }, method: 'POST', path: '/projects' })));
|
|
105
118
|
}
|
|
106
119
|
catch (error) {
|
|
107
120
|
if (!(error instanceof NotRunningError))
|
|
@@ -109,9 +122,15 @@ async function addProject(paths, path) {
|
|
|
109
122
|
// No daemon: write the store directly. The daemon reads it when it starts.
|
|
110
123
|
// Its rules are checked against config.json here too, as the daemon would.
|
|
111
124
|
const check = rulesService({ config: loadConfig(paths.config), listProjects: () => [], paths }).checkProfile;
|
|
112
|
-
result = withStore(paths, (store) => registerProject(store,
|
|
125
|
+
result = withStore(paths, (store) => registerProject(store, paths, made.name, check));
|
|
113
126
|
}
|
|
114
127
|
out(`${result.created ? 'registered' : 'updated'} ${projectLine(result.project).trim()}`);
|
|
128
|
+
out(made.cloned ? `cloned ${made.url} into ${made.clone}` : `fetched ${made.clone}, the clone it has`);
|
|
129
|
+
out(`its machine profile, which wins over the repo's: ${result.project.profilePath}`);
|
|
130
|
+
// Claude Code trusts a worktree by its repository, and nobody has run Claude in a new clone yet.
|
|
131
|
+
if (made.cloned && next)
|
|
132
|
+
out(`next: ${trustStep(made.clone)}, then \`conduct project check ${made.name}\``);
|
|
133
|
+
return made;
|
|
115
134
|
}
|
|
116
135
|
// `conduct approve|revise|done`: Jason's answer to an inbox item, by id, or for approve and revise
|
|
117
136
|
// by the item whose plan waits.
|
|
@@ -150,6 +169,8 @@ async function attach(paths, ref) {
|
|
|
150
169
|
out(`${result.resumed ? 'woke' : 'attached to'} ${worker.name} (session ${result.worker.handle ?? '?'})`);
|
|
151
170
|
if (result.tab)
|
|
152
171
|
out(`no tab (${result.tab}); open it yourself: claude attach ${result.worker.handle ?? worker.name}`);
|
|
172
|
+
else if (result.hint)
|
|
173
|
+
out(`to see its tab: ${result.hint}`);
|
|
153
174
|
}
|
|
154
175
|
// The pid of the daemon once it answers /health, for upgrade's restart. failure: the detached
|
|
155
176
|
// serve's spawn error, which ends the wait early.
|
|
@@ -192,7 +213,7 @@ async function consoleCommand(paths, options) {
|
|
|
192
213
|
await claimConsole(paths, options.force);
|
|
193
214
|
const child = spawn('claude', consoleArgs(paths.plugin), {
|
|
194
215
|
cwd: paths.console,
|
|
195
|
-
env:
|
|
216
|
+
env: consoleEnv(process.env),
|
|
196
217
|
stdio: 'inherit',
|
|
197
218
|
});
|
|
198
219
|
// Ctrl+C belongs to the session; this process only waits for it to end.
|
|
@@ -207,40 +228,33 @@ async function consoleCommand(paths, options) {
|
|
|
207
228
|
});
|
|
208
229
|
return;
|
|
209
230
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
'run `conduct console --here` in a terminal of your own',
|
|
213
|
-
]);
|
|
214
|
-
}
|
|
215
|
-
// A tab starts with Windows Terminal's environment, not this one's.
|
|
216
|
-
if (process.env.CONDUCTOR_HOME) {
|
|
217
|
-
throw new ConductorError('a console tab would not see CONDUCTOR_HOME', [
|
|
218
|
-
'run `conduct console --here` in a terminal where it is set',
|
|
219
|
-
]);
|
|
220
|
-
}
|
|
221
|
-
const args = consoleTabArgs(paths.console, process.execPath, realpathSync(fileURLToPath(import.meta.url)));
|
|
231
|
+
const where = terminal({ choice: loadConfig(paths.config).workers.terminal });
|
|
232
|
+
const tab = consoleTab(paths.console, process.execPath, realpathSync(fileURLToPath(import.meta.url)), process.env);
|
|
222
233
|
await claimConsole(paths, options.force);
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
throw new ConductorError(
|
|
226
|
-
|
|
234
|
+
const failed = await where.open(tab);
|
|
235
|
+
if (failed !== null)
|
|
236
|
+
throw new ConductorError(`could not open the console tab in ${where.name}`, [
|
|
237
|
+
failed,
|
|
227
238
|
'run `conduct console --here --force` in a terminal instead',
|
|
228
239
|
]);
|
|
229
|
-
|
|
240
|
+
const hint = where.attachHint(CONSOLE_WINDOW);
|
|
241
|
+
out(`opened the console in ${paths.console} (${hint === null ? `${where.name} window "${CONSOLE_WINDOW}"` : `${where.name}: ${hint}`})`);
|
|
230
242
|
}
|
|
231
243
|
// How `conduct start` and upgrade's restart start the daemon detached from this process.
|
|
232
244
|
function daemonStarter(paths, cliPath) {
|
|
233
245
|
let failure = () => undefined;
|
|
246
|
+
const task = startup();
|
|
234
247
|
return {
|
|
235
248
|
awaitDaemon: () => awaitDaemon(paths, failure),
|
|
236
|
-
runTask:
|
|
249
|
+
runTask: () => task.run(),
|
|
237
250
|
serveDetached: () => {
|
|
238
251
|
// --fail-fast: a first serve that can't start ends the supervisor, so the start says why at once.
|
|
239
252
|
failure = serveDetached(paths.home, process.execPath, [cliPath, 'supervise', '--fail-fast']);
|
|
240
253
|
},
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
254
|
+
taskName: task.name,
|
|
255
|
+
// The startup's daemon uses ~/.conductor, so it can't start one kept in CONDUCTOR_HOME.
|
|
256
|
+
taskRunsCli: async () => !process.env.CONDUCTOR_HOME && (await task.runsCli(cliPath)),
|
|
257
|
+
taskSupervises: () => task.supervises(),
|
|
244
258
|
};
|
|
245
259
|
}
|
|
246
260
|
// `conduct escalations done`: the console reports back on an escalation.
|
|
@@ -365,11 +379,20 @@ function inboxId(ref) {
|
|
|
365
379
|
throw new ConductorError(`"${ref}" is not an inbox item id`, ['`conduct inbox` lists them as #<id>']);
|
|
366
380
|
return Number(id);
|
|
367
381
|
}
|
|
368
|
-
|
|
382
|
+
// `conduct install-startup`: the machine's startup (host/startup.ts) runs this build's supervise.
|
|
383
|
+
async function install() {
|
|
369
384
|
const cliPath = realpathSync(fileURLToPath(import.meta.url));
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
385
|
+
const task = startup();
|
|
386
|
+
// Less what `pnpm exec` or `npm run` put first, which a worktree's removal takes away.
|
|
387
|
+
const path = (process.env.PATH ?? '')
|
|
388
|
+
.split(delimiter)
|
|
389
|
+
.filter((dir) => !/[\\/]node_modules[\\/]\.bin[\\/]?$/.test(dir))
|
|
390
|
+
.join(delimiter);
|
|
391
|
+
const notes = await task.install({ cliPath, nodePath: process.execPath, path });
|
|
392
|
+
out(`registered ${task.name}: ${process.execPath} ${cliPath} supervise`);
|
|
393
|
+
for (const note of notes)
|
|
394
|
+
out(note);
|
|
395
|
+
if (/[\\/](?:\.claude|\.conductor[\\/]projects[\\/][^\\/]+)[\\/]worktrees[\\/]/.test(cliPath)) {
|
|
373
396
|
out('warning: that build is in a worktree, which goes away; run install-startup from the main checkout');
|
|
374
397
|
}
|
|
375
398
|
}
|
|
@@ -404,7 +427,7 @@ async function listProjects(paths, json) {
|
|
|
404
427
|
if (json)
|
|
405
428
|
return out(JSON.stringify(projects, null, 2));
|
|
406
429
|
if (projects.length === 0)
|
|
407
|
-
return out('no projects; add one with `conduct project add <path>`');
|
|
430
|
+
return out('no projects; add one with `conduct project add <url | path>`');
|
|
408
431
|
projects.forEach((p) => out(projectLine(p)));
|
|
409
432
|
}
|
|
410
433
|
async function main(argv) {
|
|
@@ -444,12 +467,15 @@ async function main(argv) {
|
|
|
444
467
|
options: {
|
|
445
468
|
'allow-downgrade': { type: 'boolean' },
|
|
446
469
|
area: { type: 'string' },
|
|
470
|
+
base: { type: 'string' },
|
|
447
471
|
'blocked-by': { multiple: true, type: 'string' },
|
|
448
472
|
'body-file': { type: 'string' },
|
|
473
|
+
branch: { type: 'string' },
|
|
449
474
|
channel: { type: 'string' },
|
|
450
475
|
comments: { type: 'boolean' },
|
|
451
476
|
conductor: { type: 'string' },
|
|
452
477
|
console: { type: 'boolean' },
|
|
478
|
+
count: { type: 'string' },
|
|
453
479
|
'dry-run': { type: 'boolean' },
|
|
454
480
|
'fail-fast': { type: 'boolean' },
|
|
455
481
|
files: { type: 'string' },
|
|
@@ -457,21 +483,24 @@ async function main(argv) {
|
|
|
457
483
|
for: { type: 'string' },
|
|
458
484
|
force: { type: 'boolean' },
|
|
459
485
|
fresh: { type: 'boolean' },
|
|
486
|
+
grep: { type: 'string' },
|
|
460
487
|
help: { short: 'h', type: 'boolean' },
|
|
461
488
|
here: { type: 'boolean' },
|
|
462
489
|
human: { type: 'boolean' },
|
|
463
490
|
item: { type: 'string' },
|
|
464
491
|
json: { type: 'boolean' },
|
|
492
|
+
key: { type: 'string' },
|
|
465
493
|
kind: { type: 'string' },
|
|
466
494
|
last: { type: 'boolean' },
|
|
467
495
|
mode: { type: 'string' },
|
|
496
|
+
name: { type: 'string' },
|
|
468
497
|
'no-worktree': { type: 'boolean' },
|
|
469
498
|
owner: { type: 'string' },
|
|
470
499
|
phase: { type: 'string' },
|
|
471
|
-
prefer: { type: 'string' },
|
|
472
500
|
probe: { type: 'boolean' },
|
|
473
501
|
project: { type: 'string' },
|
|
474
502
|
prompt: { type: 'string' },
|
|
503
|
+
query: { type: 'string' },
|
|
475
504
|
raise: { type: 'boolean' },
|
|
476
505
|
rearm: { type: 'boolean' },
|
|
477
506
|
reason: { type: 'string' },
|
|
@@ -479,18 +508,23 @@ async function main(argv) {
|
|
|
479
508
|
remove: { type: 'boolean' },
|
|
480
509
|
repo: { type: 'string' },
|
|
481
510
|
requeue: { type: 'string' },
|
|
511
|
+
shared: { type: 'boolean' },
|
|
482
512
|
since: { type: 'string' },
|
|
483
513
|
slots: { type: 'string' },
|
|
484
514
|
tag: { type: 'string' },
|
|
515
|
+
task: { type: 'string' },
|
|
516
|
+
team: { type: 'string' },
|
|
485
517
|
text: { type: 'string' },
|
|
486
518
|
tier: { type: 'string' },
|
|
487
519
|
title: { type: 'string' },
|
|
520
|
+
tracker: { type: 'string' },
|
|
488
521
|
until: { type: 'string' },
|
|
489
522
|
version: { short: 'v', type: 'boolean' },
|
|
490
523
|
wait: { type: 'string' },
|
|
491
524
|
'waits-release': { type: 'boolean' },
|
|
492
525
|
weight: { type: 'string' },
|
|
493
526
|
worker: { type: 'string' },
|
|
527
|
+
yes: { type: 'boolean' },
|
|
494
528
|
},
|
|
495
529
|
}));
|
|
496
530
|
const paths = conductorPaths();
|
|
@@ -517,7 +551,7 @@ async function main(argv) {
|
|
|
517
551
|
case 'branch':
|
|
518
552
|
if (sub === undefined || arg === undefined)
|
|
519
553
|
throw usageError('branch');
|
|
520
|
-
process.exitCode = await branchCommand(sub, arg, item, verbIo());
|
|
554
|
+
process.exitCode = await branchCommand(paths, sub, arg, item, verbIo());
|
|
521
555
|
return;
|
|
522
556
|
case 'brief':
|
|
523
557
|
if (sub === undefined)
|
|
@@ -531,7 +565,7 @@ async function main(argv) {
|
|
|
531
565
|
case 'checkout':
|
|
532
566
|
if (sub === undefined || arg === undefined || fourth === undefined)
|
|
533
567
|
throw usageError('checkout');
|
|
534
|
-
process.exitCode = await checkoutCommand(sub, arg, fourth, item, verbIo());
|
|
568
|
+
process.exitCode = await checkoutCommand(paths, sub, arg, fourth, item, verbIo());
|
|
535
569
|
return;
|
|
536
570
|
case 'comments': {
|
|
537
571
|
if (sub === undefined || arg !== undefined)
|
|
@@ -593,6 +627,26 @@ async function main(argv) {
|
|
|
593
627
|
});
|
|
594
628
|
case 'inbox':
|
|
595
629
|
return inbox(paths, values.json === true);
|
|
630
|
+
case 'init':
|
|
631
|
+
if (arg !== undefined)
|
|
632
|
+
throw usageError('init');
|
|
633
|
+
process.exitCode = await initProject({
|
|
634
|
+
add: (checkout, name) => addProject(paths, checkout, name, false),
|
|
635
|
+
ask: terminalAsk(),
|
|
636
|
+
check: (name) => projectCheckReport(paths, name, { worktree: true }, { err: (line) => process.stderr.write(`${line}\n`) }),
|
|
637
|
+
out,
|
|
638
|
+
paths,
|
|
639
|
+
}, sub ?? '.', {
|
|
640
|
+
shared: values.shared === true,
|
|
641
|
+
yes: values.yes === true,
|
|
642
|
+
...(values.base === undefined ? {} : { base: values.base }),
|
|
643
|
+
...(values.key === undefined ? {} : { key: values.key }),
|
|
644
|
+
...(values.name === undefined ? {} : { name: values.name }),
|
|
645
|
+
...(values.query === undefined ? {} : { query: values.query }),
|
|
646
|
+
...(values.team === undefined ? {} : { team: values.team }),
|
|
647
|
+
...(values.tracker === undefined ? {} : { tracker: values.tracker }),
|
|
648
|
+
});
|
|
649
|
+
return;
|
|
596
650
|
case 'install-startup':
|
|
597
651
|
return values.remove ? uninstall() : install();
|
|
598
652
|
case 'land': {
|
|
@@ -612,6 +666,7 @@ async function main(argv) {
|
|
|
612
666
|
...repo,
|
|
613
667
|
...(mode === undefined ? {} : { mode }),
|
|
614
668
|
...(tier === undefined ? {} : { tier }),
|
|
669
|
+
...(values.requeue === undefined ? {} : { requeue: wholeNumber('--requeue', values.requeue) }),
|
|
615
670
|
}, verbIo());
|
|
616
671
|
return;
|
|
617
672
|
}
|
|
@@ -639,7 +694,7 @@ async function main(argv) {
|
|
|
639
694
|
throw usageError('mode');
|
|
640
695
|
if (tier !== 'S' && tier !== 'M' && tier !== 'L')
|
|
641
696
|
throw new ConductorError('--tier takes S, M or L');
|
|
642
|
-
process.exitCode = await modeCommand({ files: values.files, tier }, verbIo());
|
|
697
|
+
process.exitCode = await modeCommand(paths, { files: values.files, tier }, verbIo());
|
|
643
698
|
return;
|
|
644
699
|
}
|
|
645
700
|
case 'note':
|
|
@@ -686,14 +741,23 @@ async function main(argv) {
|
|
|
686
741
|
...(values.until === undefined ? {} : { until: untilOf(values.until) }),
|
|
687
742
|
});
|
|
688
743
|
case 'ports':
|
|
689
|
-
process.exitCode = await portsCommand(paths, sub, {
|
|
690
|
-
json: values.json === true,
|
|
691
|
-
...(values.prefer === undefined ? {} : { prefer: wholeNumber('--prefer', values.prefer) }),
|
|
692
|
-
}, brokerIo());
|
|
744
|
+
process.exitCode = await portsCommand(paths, sub, { json: values.json === true }, brokerIo());
|
|
693
745
|
return;
|
|
746
|
+
case 'profile': {
|
|
747
|
+
if (sub === undefined || arg !== undefined)
|
|
748
|
+
throw usageError('profile');
|
|
749
|
+
// Resolved here from the machine file and the clone, as the daemon resolves it.
|
|
750
|
+
const layered = projectProfile(paths, sub);
|
|
751
|
+
if (values.json === true)
|
|
752
|
+
return out(JSON.stringify({ profile: layered.profile, sources: layered.sources }, null, 2));
|
|
753
|
+
profileLines(sub, layered).forEach((line) => out(line));
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
694
756
|
case 'project':
|
|
695
|
-
if (sub === 'add' && arg !== undefined)
|
|
696
|
-
|
|
757
|
+
if (sub === 'add' && arg !== undefined) {
|
|
758
|
+
await addProject(paths, arg, values.name);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
697
761
|
if (sub === 'remove' && arg !== undefined)
|
|
698
762
|
return removeProject(paths, arg);
|
|
699
763
|
if (sub === 'list')
|
|
@@ -734,6 +798,7 @@ async function main(argv) {
|
|
|
734
798
|
...item,
|
|
735
799
|
...repo,
|
|
736
800
|
...(values.for === undefined ? {} : { for: values.for }),
|
|
801
|
+
...(values.grep === undefined ? {} : { grep: values.grep }),
|
|
737
802
|
...(values.requeue === undefined ? {} : { requeue: values.requeue }),
|
|
738
803
|
}, verbIo());
|
|
739
804
|
return;
|
|
@@ -759,6 +824,19 @@ async function main(argv) {
|
|
|
759
824
|
case 'supervise':
|
|
760
825
|
process.exitCode = await superviseCommand(paths, values['fail-fast'] === true);
|
|
761
826
|
return;
|
|
827
|
+
case 'timeline':
|
|
828
|
+
if (arg !== undefined)
|
|
829
|
+
throw usageError('timeline');
|
|
830
|
+
process.exitCode = await timelineCommand(paths, {
|
|
831
|
+
...repo,
|
|
832
|
+
...(sub === undefined ? {} : { run: sub }),
|
|
833
|
+
...(values.branch === undefined ? {} : { branch: values.branch }),
|
|
834
|
+
...(values.count === undefined ? {} : { count: values.count }),
|
|
835
|
+
...(values.for === undefined ? {} : { for: values.for }),
|
|
836
|
+
...(values.grep === undefined ? {} : { grep: values.grep }),
|
|
837
|
+
...(values.task === undefined ? {} : { task: values.task }),
|
|
838
|
+
}, verbIo());
|
|
839
|
+
return;
|
|
762
840
|
case 'train':
|
|
763
841
|
return trainCommand(paths, sub, { dryRun: values['dry-run'] === true }, { out });
|
|
764
842
|
case 'unpause':
|
|
@@ -814,7 +892,7 @@ async function notifyTest(paths, options) {
|
|
|
814
892
|
...(options.kind ? { kind: options.kind } : {}),
|
|
815
893
|
raise: options.raise,
|
|
816
894
|
};
|
|
817
|
-
// Pushover can take its 15 s, after a
|
|
895
|
+
// Pushover can take its 15 s, after a secret store read.
|
|
818
896
|
const answer = await notifyAnswer(paths, { body, method: 'POST', path: '/notify/test', timeoutMs: 60_000 });
|
|
819
897
|
if (options.raise) {
|
|
820
898
|
const { item } = z.object({ item: inboxItemSchema }).parse(answer);
|
|
@@ -914,11 +992,11 @@ async function planItem(paths, ref) {
|
|
|
914
992
|
}
|
|
915
993
|
function projectLine(p) {
|
|
916
994
|
const state = p.problem
|
|
917
|
-
? `${p.problem}; the daemon reads
|
|
995
|
+
? `${p.problem}; the daemon reads the profile again when it starts, when ~/.conductor/projects/${p.name}.json is saved, and after each fetch of the clone`
|
|
918
996
|
: p.enabled
|
|
919
997
|
? 'enabled'
|
|
920
998
|
: 'disabled';
|
|
921
|
-
return ` ${p.name} ${p.
|
|
999
|
+
return ` ${p.name} ${p.clone} (protocol ${p.protocol}, ${state})`;
|
|
922
1000
|
}
|
|
923
1001
|
// The registered projects: from the daemon, or from the store when it isn't running.
|
|
924
1002
|
async function projectsNow(paths) {
|
|
@@ -963,7 +1041,7 @@ async function removeProject(paths, name) {
|
|
|
963
1041
|
});
|
|
964
1042
|
}
|
|
965
1043
|
const { project, resolved } = result;
|
|
966
|
-
out(`removed ${project.name} (${project.
|
|
1044
|
+
out(`removed ${project.name} (${project.clone})`);
|
|
967
1045
|
for (const item of resolved)
|
|
968
1046
|
out(` resolved #${item.id} (${item.kind}${item.item ? ` ${item.item}` : ''})`);
|
|
969
1047
|
}
|
|
@@ -1093,7 +1171,7 @@ async function shutdownDaemon(paths) {
|
|
|
1093
1171
|
if (readEndpoint(paths) !== null) {
|
|
1094
1172
|
throw new ConductorError(`the conductor (pid ${pid ?? '?'}) did not stop within ${STOP_WAIT_MS / 1000} s`);
|
|
1095
1173
|
}
|
|
1096
|
-
// The port file goes before the process ends; a start (the logon
|
|
1174
|
+
// The port file goes before the process ends; a start (the logon startup's especially) waits for it.
|
|
1097
1175
|
const exitDeadline = Date.now() + STOP_WAIT_MS;
|
|
1098
1176
|
while (pid !== null && processAlive(pid) && Date.now() < exitDeadline)
|
|
1099
1177
|
await sleep(100);
|
|
@@ -1109,7 +1187,7 @@ async function start(paths) {
|
|
|
1109
1187
|
const pid = readPid(paths.pid);
|
|
1110
1188
|
if (pid !== null && (await ping(paths, pid)))
|
|
1111
1189
|
return out(`the conductor is running (pid ${pid})`);
|
|
1112
|
-
// A daemon that is still starting (the logon
|
|
1190
|
+
// A daemon that is still starting (the logon startup's, say) holds the lock: wait for it rather than
|
|
1113
1191
|
// start a second one, which would exit on that lock. A pid a reboot reused gets a start after.
|
|
1114
1192
|
if (pid !== null && processAlive(pid)) {
|
|
1115
1193
|
try {
|
|
@@ -1157,7 +1235,7 @@ async function status(paths, json) {
|
|
|
1157
1235
|
if (json)
|
|
1158
1236
|
return out(JSON.stringify({ home: paths.home, inbox: items, locks, pid, port, projects, running: true, workers, workersFrom: 'daemon' }, null, 2));
|
|
1159
1237
|
out(`conductor running (pid ${pid ?? '?'}, port ${port ?? '?'}, home ${paths.home})`);
|
|
1160
|
-
out(projects.length === 0 ? 'no projects; add one with `conduct project add <path>`' : 'projects:');
|
|
1238
|
+
out(projects.length === 0 ? 'no projects; add one with `conduct project add <url | path>`' : 'projects:');
|
|
1161
1239
|
projects.forEach((p) => out(projectLine(p)));
|
|
1162
1240
|
out(workers.length === 0 ? 'no workers' : 'workers:');
|
|
1163
1241
|
workers.forEach((w) => out(workerLine(w)));
|
|
@@ -1195,7 +1273,7 @@ async function stopDaemon(paths) {
|
|
|
1195
1273
|
out('the conductor is not running');
|
|
1196
1274
|
return false;
|
|
1197
1275
|
case 'supervisor': {
|
|
1198
|
-
// It answers just before it ends; a start (the logon
|
|
1276
|
+
// It answers just before it ends; a start (the logon startup's especially) needs it gone.
|
|
1199
1277
|
const deadline = Date.now() + STOP_WAIT_MS;
|
|
1200
1278
|
while (processAlive(stop.pid) && Date.now() < deadline)
|
|
1201
1279
|
await sleep(100);
|
|
@@ -1204,7 +1282,7 @@ async function stopDaemon(paths) {
|
|
|
1204
1282
|
}
|
|
1205
1283
|
}
|
|
1206
1284
|
}
|
|
1207
|
-
// `conduct supervise`: keeps `conduct serve` running (supervise.ts, ATK-417). The logon
|
|
1285
|
+
// `conduct supervise`: keeps `conduct serve` running (supervise.ts, ATK-417). The logon startup runs
|
|
1208
1286
|
// it; `conduct start` runs it with --fail-fast. The config is read again for each push, so one fixed
|
|
1209
1287
|
// while the daemon is down counts, and one that doesn't load (a reason it may be down) leaves the
|
|
1210
1288
|
// defaults: no quiet hours, the default credential.
|
|
@@ -1226,7 +1304,7 @@ async function superviseCommand(paths, failFast) {
|
|
|
1226
1304
|
rotatingLog(paths.log, config().log).log(...line);
|
|
1227
1305
|
}
|
|
1228
1306
|
catch {
|
|
1229
|
-
// Nowhere else to say it: under the logon
|
|
1307
|
+
// Nowhere else to say it: under the logon startup nobody reads stderr.
|
|
1230
1308
|
}
|
|
1231
1309
|
},
|
|
1232
1310
|
};
|
|
@@ -1302,8 +1380,9 @@ async function superviseCommand(paths, failFast) {
|
|
|
1302
1380
|
}
|
|
1303
1381
|
return code;
|
|
1304
1382
|
}
|
|
1305
|
-
function uninstall() {
|
|
1306
|
-
|
|
1383
|
+
async function uninstall() {
|
|
1384
|
+
const task = startup();
|
|
1385
|
+
out((await task.remove()) ? `removed ${task.name}` : `no ${task.name} to remove`);
|
|
1307
1386
|
}
|
|
1308
1387
|
// --until: 17:00 (the next one), tomorrow, a day such as monday, or a date and time.
|
|
1309
1388
|
function untilOf(text) {
|
|
@@ -1311,7 +1390,8 @@ function untilOf(text) {
|
|
|
1311
1390
|
}
|
|
1312
1391
|
async function upgradeCommand(paths, tag, allowDowngrade) {
|
|
1313
1392
|
const cliPath = realpathSync(fileURLToPath(import.meta.url));
|
|
1314
|
-
|
|
1393
|
+
// npm's own settings, so the tag is read where `npm install --global` installs from (ATK-561).
|
|
1394
|
+
const release = npmStage(sharedNpmReads());
|
|
1315
1395
|
await upgrade({ allowDowngrade, tag: parseTag(tag) }, {
|
|
1316
1396
|
...daemonStarter(paths, cliPath),
|
|
1317
1397
|
cliPath,
|