@nodeint/migent 0.1.4 → 0.1.6
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 +194 -138
- package/dist/commands/agent.d.ts +8 -1
- package/dist/commands/agent.js +381 -56
- package/dist/commands/config.js +23 -9
- package/dist/commands/execute.d.ts +6 -1
- package/dist/commands/execute.js +105 -20
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +70 -63
- package/dist/commands/prepare.d.ts +1 -0
- package/dist/commands/prepare.js +14 -0
- package/dist/commands/repair.d.ts +1 -0
- package/dist/commands/repair.js +22 -0
- package/dist/commands/service.js +5 -4
- package/dist/commands/sessions.d.ts +24 -1
- package/dist/commands/sessions.js +165 -13
- package/dist/commands/skill.js +58 -32
- package/dist/commands/watch.d.ts +2 -1
- package/dist/commands/watch.js +130 -67
- package/dist/commands/workspace-registry.js +10 -1
- package/dist/display.d.ts +1 -0
- package/dist/display.js +24 -9
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +6 -0
- package/dist/exits.d.ts +12 -0
- package/dist/exits.js +33 -0
- package/dist/follow-log.d.ts +29 -0
- package/dist/follow-log.js +172 -0
- package/dist/fs-utils.d.ts +3 -0
- package/dist/fs-utils.js +26 -0
- package/dist/index.js +5 -1
- package/dist/migrate.js +2 -17
- package/dist/program.js +429 -177
- package/dist/session-row.d.ts +20 -0
- package/dist/session-row.js +21 -0
- package/dist/signals.d.ts +3 -0
- package/dist/signals.js +56 -0
- package/dist/templates/migent/SKILL.md +163 -56
- package/dist/templates/migent/references/config.md +52 -0
- package/dist/templates/migent/references/inspect.md +32 -0
- package/dist/templates/migent/references/schema-config.yaml +35 -0
- package/dist/templates/migent/references/schema-workflow.yaml +71 -0
- package/dist/templates/migent/references/sessions.md +65 -0
- package/dist/text.d.ts +4 -0
- package/dist/text.js +13 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/workspace.js +5 -12
- package/package.json +4 -4
package/dist/program.js
CHANGED
|
@@ -1,119 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { ConfigurationError, DEFAULT_MAX_DISPATCHES } from "@nodeint/migent-kernel";
|
|
4
|
-
import { dispatchAgent } from "./commands/agent.js";
|
|
3
|
+
import { ConfigurationError, DEFAULT_MAX_DISPATCHES, requireAgentSession } from "@nodeint/migent-kernel";
|
|
4
|
+
import { continueAgent, dispatchAgent } from "./commands/agent.js";
|
|
5
5
|
import { runConfigModel, runConfigRole, runConfigShow } from "./commands/config.js";
|
|
6
6
|
import { runDoctor } from "./commands/doctor.js";
|
|
7
7
|
import { executeWorkflow } from "./commands/execute.js";
|
|
8
|
+
import { prepareWorkflowSession } from "./commands/prepare.js";
|
|
9
|
+
import { repairWorkflowSession } from "./commands/repair.js";
|
|
8
10
|
import { runInit } from "./commands/init.js";
|
|
9
|
-
import {
|
|
11
|
+
import { runSessionMetrics, runSessions } from "./commands/sessions.js";
|
|
10
12
|
import { parsePort, runServiceStart, runServiceStatus, runServiceStop } from "./commands/service.js";
|
|
11
13
|
import { installSkill, updateSkill } from "./commands/skill.js";
|
|
12
|
-
import { watchSession } from "./commands/watch.js";
|
|
13
14
|
import { runRegister, runRemove, runWorkspaceList } from "./commands/workspace-registry.js";
|
|
14
15
|
import { printError } from "./display.js";
|
|
16
|
+
import { LOOP_EXITS, STAGE_EXITS } from "./exits.js";
|
|
15
17
|
import { runMigrate } from "./migrate.js";
|
|
18
|
+
import { optionalText, rawText, text } from "./text.js";
|
|
19
|
+
import { PACKAGE_VERSION } from "./version.js";
|
|
16
20
|
import { findWorkspace, invocationCwdPath } from "./workspace.js";
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
"Exit 1 when the session is blocked.",
|
|
21
|
-
"Exit 2 on a configuration error.",
|
|
22
|
-
"Exit 3 when --attempts is spent. The session stays active.",
|
|
23
|
-
"Exit 4 when a route would exceed max_revisions. The session stays active.",
|
|
24
|
-
"Exit 5 when --max-dispatches is spent. The session stays active.",
|
|
25
|
-
"Exit 130 when cancelled.",
|
|
26
|
-
];
|
|
27
|
-
const CONTINUE_EXITS = [
|
|
28
|
-
"Exit 0 when the session is completed.",
|
|
29
|
-
"Exit 1 when the session is blocked.",
|
|
30
|
-
"Exit 2 on a configuration error.",
|
|
31
|
-
"Exit 3 when --attempts is spent. The session stays active.",
|
|
32
|
-
"Exit 4 when a route would exceed max_revisions. The session stays active.",
|
|
33
|
-
"Exit 5 when --max-dispatches is spent. The session stays active.",
|
|
34
|
-
"Exit 130 when cancelled.",
|
|
35
|
-
];
|
|
36
|
-
const STAGE_EXITS = [
|
|
37
|
-
"Exit 0 when the stage outcome is complete, or the session is already completed.",
|
|
38
|
-
"Exit 1 when the stage is not complete, or the session is blocked.",
|
|
39
|
-
"Exit 2 on a configuration error.",
|
|
40
|
-
"Exit 4 when a route would exceed max_revisions. The session stays active.",
|
|
41
|
-
"Exit 130 when cancelled.",
|
|
42
|
-
];
|
|
21
|
+
const PRIOR_HELP = "Completed session with the workflow id and decision named in requires. Checked before outputs are injected.";
|
|
22
|
+
const PROGRESS_HELP = "Print each tool call when it finishes. The default prints one line when a stage starts and one when it finishes.";
|
|
23
|
+
const VERBOSE_HELP = "Accepted for compatibility. Stage lines already include the attempt and the time. The response stays in the execution directory.";
|
|
43
24
|
export function createProgram(status) {
|
|
44
25
|
const program = new Command();
|
|
45
26
|
program
|
|
46
27
|
.name("migent")
|
|
47
28
|
.description("Run named workflows and standalone roles from .migent/.")
|
|
48
|
-
.version(
|
|
29
|
+
.version(PACKAGE_VERSION, "-V, --version", "Print the Migent version.")
|
|
49
30
|
.exitOverride();
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.description("Create a session and continue until it stops.")
|
|
53
|
-
.argument("<workflow>", "Workflow id, or a path to a workflow file.")
|
|
54
|
-
.requiredOption("--task <task>", "Task recorded on the new session.");
|
|
55
|
-
addDispatchOptions(start);
|
|
56
|
-
start.addHelpText("after", exitText(START_EXITS)).action((workflow, options) => settle(status, () => {
|
|
57
|
-
const ceiling = dispatchCeiling(options);
|
|
58
|
-
if (typeof ceiling === "string")
|
|
59
|
-
return 2;
|
|
60
|
-
return executeWorkflow(findWorkspace(invocationCwdPath()), {
|
|
61
|
-
action: "start",
|
|
62
|
-
workflowId: workflow,
|
|
63
|
-
task: String(options.task),
|
|
64
|
-
priorSessionId: optionalText(options.prior),
|
|
65
|
-
maxAttempts: attemptsOf(options),
|
|
66
|
-
maxDispatches: ceiling,
|
|
67
|
-
});
|
|
68
|
-
}));
|
|
69
|
-
const continueCommand = program
|
|
70
|
-
.command("continue")
|
|
71
|
-
.description("Resume a session until it stops.")
|
|
72
|
-
.argument("<session>", "Session to resume.");
|
|
73
|
-
addDispatchOptions(continueCommand);
|
|
74
|
-
continueCommand.addHelpText("after", exitText(CONTINUE_EXITS)).action((session, options) => settle(status, () => {
|
|
75
|
-
const ceiling = dispatchCeiling(options);
|
|
76
|
-
if (typeof ceiling === "string")
|
|
77
|
-
return 2;
|
|
78
|
-
return executeWorkflow(findWorkspace(invocationCwdPath()), {
|
|
79
|
-
action: "continue",
|
|
80
|
-
sessionId: session,
|
|
81
|
-
priorSessionId: optionalText(options.prior),
|
|
82
|
-
maxAttempts: attemptsOf(options),
|
|
83
|
-
maxDispatches: ceiling,
|
|
84
|
-
});
|
|
85
|
-
}));
|
|
86
|
-
program
|
|
87
|
-
.command("stage")
|
|
88
|
-
.description("Advance one stage. Dispatch the next eligible stage once.")
|
|
89
|
-
.argument("[session]", "Session to advance. Omit when passing --workflow.")
|
|
90
|
-
.option("--workflow <workflow>", "Workflow id or path. Creates a session and dispatches its next stage once.")
|
|
91
|
-
.option("--task <task>", "Task recorded on the new session. Required with --workflow.")
|
|
92
|
-
.option("--prior <session>", "Completed session whose workflow and decision match requires.")
|
|
93
|
-
.addHelpText("after", exitText(STAGE_EXITS))
|
|
94
|
-
.action((session, options) => settle(status, () => {
|
|
95
|
-
const sessionId = text(session);
|
|
96
|
-
const workflow = optionalText(options.workflow);
|
|
97
|
-
const task = optionalText(options.task);
|
|
98
|
-
if (sessionId !== null && (workflow !== null || task !== null)) {
|
|
99
|
-
printError("Pass SESSION, or --workflow and --task, not both.");
|
|
100
|
-
return 2;
|
|
101
|
-
}
|
|
102
|
-
if (sessionId === null && (workflow === null || task === null)) {
|
|
103
|
-
printError("Pass SESSION, or both --workflow and --task.");
|
|
104
|
-
return 2;
|
|
105
|
-
}
|
|
106
|
-
return executeWorkflow(findWorkspace(invocationCwdPath()), {
|
|
107
|
-
action: "stage",
|
|
108
|
-
workflowId: workflow,
|
|
109
|
-
task,
|
|
110
|
-
sessionId,
|
|
111
|
-
priorSessionId: optionalText(options.prior),
|
|
112
|
-
maxAttempts: null,
|
|
113
|
-
maxDispatches: null,
|
|
114
|
-
});
|
|
115
|
-
}));
|
|
116
|
-
program
|
|
31
|
+
registerSessionCommand(program, status);
|
|
32
|
+
const init = program
|
|
117
33
|
.command("init")
|
|
118
34
|
.description("Create a minimal config, one workflow, and a sessions gitignore.")
|
|
119
35
|
.option("--provider <provider>", "Provider for the default model. Pass with --model.")
|
|
@@ -121,20 +37,25 @@ export function createProgram(status) {
|
|
|
121
37
|
.option("--option <pair>", "KEY=VALUE stored under models.*.options.", collect, [])
|
|
122
38
|
.option("--preset <preset>", "Workflow to create: implement or plan. Default: implement.", "implement")
|
|
123
39
|
.option("--skill", "Install the Migent skill at .agents/skills/migent.")
|
|
124
|
-
.
|
|
40
|
+
.option("--no-input", "Do not prompt. Same as MIGENT_NO_INPUT=1.");
|
|
41
|
+
init.action((options) => settle(status, () => {
|
|
125
42
|
const provider = optionalText(options.provider);
|
|
126
43
|
const model = optionalText(options.model);
|
|
127
|
-
const interactive = provider === null && Boolean(process.stdin.isTTY);
|
|
44
|
+
const interactive = !inputDisabled(options) && provider === null && Boolean(process.stdin.isTTY);
|
|
128
45
|
return runInit(invocationCwdPath(), {
|
|
129
46
|
provider,
|
|
130
47
|
model,
|
|
131
48
|
options: stringList(options.option),
|
|
132
49
|
preset: typeof options.preset === "string" ? options.preset : "implement",
|
|
50
|
+
presetExplicit: init.getOptionValueSource("preset") === "cli",
|
|
133
51
|
skill: options.skill === true,
|
|
134
52
|
interactive,
|
|
135
53
|
});
|
|
136
54
|
}));
|
|
137
|
-
const config = program
|
|
55
|
+
const config = program
|
|
56
|
+
.command("config")
|
|
57
|
+
.description("Print .migent/config.yaml, or update models and roles.");
|
|
58
|
+
config.action(() => settle(status, () => runConfigShow(findWorkspace(invocationCwdPath()))));
|
|
138
59
|
config
|
|
139
60
|
.command("show")
|
|
140
61
|
.description("Print .migent/config.yaml.")
|
|
@@ -148,13 +69,14 @@ export function createProgram(status) {
|
|
|
148
69
|
.option("--option <pair>", "KEY=VALUE stored under this model's options.", collect, [])
|
|
149
70
|
.option("--clear-option <key>", "Remove one key from this model's options.", collect, [])
|
|
150
71
|
.option("--json", "Print one JSON object.")
|
|
72
|
+
.option("--no-input", "Do not prompt. Same as MIGENT_NO_INPUT=1. With no changes, list models.")
|
|
151
73
|
.action((name, options) => settle(status, () => {
|
|
152
74
|
const provider = optionalText(options.provider);
|
|
153
75
|
const model = optionalText(options.model);
|
|
154
76
|
const optionValues = stringList(options.option);
|
|
155
77
|
const clearOptions = stringList(options.clearOption);
|
|
156
78
|
const mutating = provider !== null || model !== null || optionValues.length > 0 || clearOptions.length > 0;
|
|
157
|
-
const interactive = Boolean(process.stdin.isTTY) && options.json !== true && !mutating;
|
|
79
|
+
const interactive = !inputDisabled(options) && Boolean(process.stdin.isTTY) && options.json !== true && !mutating;
|
|
158
80
|
return runConfigModel(findWorkspace(invocationCwdPath()), {
|
|
159
81
|
key: text(name),
|
|
160
82
|
provider,
|
|
@@ -169,12 +91,13 @@ export function createProgram(status) {
|
|
|
169
91
|
.command("role")
|
|
170
92
|
.description("List roles, or point roles at a model.")
|
|
171
93
|
.argument("[role...]", "Role to show or update.")
|
|
172
|
-
.option("--model-name <name>", "
|
|
94
|
+
.option("--model-name <name>", "models: name to point the roles at. This is not a provider model id.")
|
|
173
95
|
.option("--provider <provider>", "Provider for this role only. Pass with --model.")
|
|
174
|
-
.option("--model <model>", "
|
|
96
|
+
.option("--model <model>", "Provider model id for this role only. Pass with --provider.")
|
|
175
97
|
.option("--option <pair>", "KEY=VALUE stored under this role's options.", collect, [])
|
|
176
98
|
.option("--clear-option <key>", "Remove one key from this role's options.", collect, [])
|
|
177
99
|
.option("--json", "Print one JSON object.")
|
|
100
|
+
.option("--no-input", "Do not prompt. Same as MIGENT_NO_INPUT=1. With no changes, list roles.")
|
|
178
101
|
.action((roles, options) => settle(status, () => {
|
|
179
102
|
const unique = roles
|
|
180
103
|
.map((role) => text(role))
|
|
@@ -186,7 +109,7 @@ export function createProgram(status) {
|
|
|
186
109
|
const optionValues = stringList(options.option);
|
|
187
110
|
const clearOptions = stringList(options.clearOption);
|
|
188
111
|
const mutating = modelName !== null || provider !== null || model !== null || optionValues.length > 0 || clearOptions.length > 0;
|
|
189
|
-
const interactive = Boolean(process.stdin.isTTY) && options.json !== true && !mutating;
|
|
112
|
+
const interactive = !inputDisabled(options) && Boolean(process.stdin.isTTY) && options.json !== true && !mutating;
|
|
190
113
|
return runConfigRole(findWorkspace(invocationCwdPath()), {
|
|
191
114
|
roles: unique,
|
|
192
115
|
modelName,
|
|
@@ -198,55 +121,17 @@ export function createProgram(status) {
|
|
|
198
121
|
interactive,
|
|
199
122
|
});
|
|
200
123
|
}));
|
|
201
|
-
showHelpWhenBare(status, config);
|
|
202
124
|
program
|
|
203
125
|
.command("doctor")
|
|
204
126
|
.description("Check config, workflows, and provider CLIs.")
|
|
205
127
|
.option("--json", "Print the checks as one JSON object.")
|
|
128
|
+
.addHelpText("after", exitText(["Exit 0 when every check passes.", "Exit 1 when a check fails."]))
|
|
206
129
|
.action((options) => settle(status, () => runDoctor(findWorkspace(invocationCwdPath()), { asJson: options.json === true })));
|
|
207
130
|
program
|
|
208
131
|
.command("migrate")
|
|
209
132
|
.description("Upgrade config and workflows to the current schema version.")
|
|
210
133
|
.option("-y, --yes", "Upgrade without asking. Use this when no terminal is available.")
|
|
211
134
|
.action((options) => settle(status, () => runMigrate(findWorkspace(invocationCwdPath()), { assumeYes: options.yes === true })));
|
|
212
|
-
program
|
|
213
|
-
.command("sessions")
|
|
214
|
-
.description("List sessions and their latest status.")
|
|
215
|
-
.option("--json", "Print the same rows as a JSON array.")
|
|
216
|
-
.action((options) => settle(status, () => listSessions(findWorkspace(invocationCwdPath()), { asJson: options.json === true })));
|
|
217
|
-
program
|
|
218
|
-
.command("watch")
|
|
219
|
-
.description("Follow a session.")
|
|
220
|
-
.argument("<session>", "Session to follow.")
|
|
221
|
-
.option("--execution <execution>", "Limit followed events and --until to this execution.")
|
|
222
|
-
.option("--json", "With --once, print one JSON snapshot. While following, print JSON lines.")
|
|
223
|
-
.option("--cursor <offset>", "Replay events.jsonl from this byte offset. 0 replays the whole log.")
|
|
224
|
-
.option("--once", "Print the snapshot and events cursor, then exit.")
|
|
225
|
-
.option("--until", "Exit when --execution reaches completed, failed, or cancelled.")
|
|
226
|
-
.action((session, options) => settle(status, () => {
|
|
227
|
-
const cursor = options.cursor === undefined ? null : Number(options.cursor);
|
|
228
|
-
if (options.cursor !== undefined && (!Number.isInteger(cursor) || (cursor ?? 0) < 0)) {
|
|
229
|
-
throw new ConfigurationError("--cursor must be >= 0.");
|
|
230
|
-
}
|
|
231
|
-
return watchSession(findWorkspace(invocationCwdPath()), session, {
|
|
232
|
-
once: options.once === true,
|
|
233
|
-
executionId: optionalText(options.execution),
|
|
234
|
-
untilTerminal: options.until === true,
|
|
235
|
-
jsonLines: options.json === true,
|
|
236
|
-
cursor,
|
|
237
|
-
});
|
|
238
|
-
}));
|
|
239
|
-
const agent = program.command("agent").description("Run one role outside a workflow.");
|
|
240
|
-
const agentStart = agent.command("start").description("Create a session and dispatch one turn.");
|
|
241
|
-
addAgentOptions(agentStart);
|
|
242
|
-
agentStart.action((options) => settle(status, () => runAgent(options, null)));
|
|
243
|
-
const agentContinue = agent
|
|
244
|
-
.command("continue")
|
|
245
|
-
.description("Append a turn to a session.")
|
|
246
|
-
.argument("<session>", "Session to continue.");
|
|
247
|
-
addAgentOptions(agentContinue);
|
|
248
|
-
agentContinue.action((session, options) => settle(status, () => runAgent(options, session)));
|
|
249
|
-
showHelpWhenBare(status, agent);
|
|
250
135
|
const skill = program.command("skill").description("Install or update the Migent skill in this project.");
|
|
251
136
|
skill
|
|
252
137
|
.command("install")
|
|
@@ -270,12 +155,13 @@ export function createProgram(status) {
|
|
|
270
155
|
}));
|
|
271
156
|
service
|
|
272
157
|
.command("stop")
|
|
273
|
-
.description("Stop the registry service when its process identity matches.")
|
|
158
|
+
.description("Stop the registry service when its process identity matches. Stop is idempotent and exits 0 when the service is already absent.")
|
|
274
159
|
.action(() => settle(status, () => runServiceStop()));
|
|
275
160
|
service
|
|
276
161
|
.command("status")
|
|
277
|
-
.description("Report whether the registry service is running.")
|
|
162
|
+
.description("Report whether the registry service is running. Status is a query and exits 1 when the service is stopped.")
|
|
278
163
|
.option("--json", "Print one JSON object.")
|
|
164
|
+
.addHelpText("after", exitText(["Exit 0 when the service is running.", "Exit 1 when the service is stopped."]))
|
|
279
165
|
.action((options) => settle(status, () => runServiceStatus(options.json === true)));
|
|
280
166
|
showHelpWhenBare(status, service);
|
|
281
167
|
const workspace = program.command("workspace").description("Register this machine's Migent workspaces.");
|
|
@@ -308,24 +194,383 @@ export function createProgram(status) {
|
|
|
308
194
|
});
|
|
309
195
|
return program;
|
|
310
196
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
197
|
+
const ROLE_EXITS = [
|
|
198
|
+
"Exit 0 when the outcome is complete, or the role session is already completed.",
|
|
199
|
+
"Exit 1 when the outcome is not complete.",
|
|
200
|
+
"Exit 2 on a configuration error.",
|
|
201
|
+
"Exit 6 when a cancelled role session reopened more than one execution and one exhausted verify.",
|
|
202
|
+
"Exit 130 when cancelled. session continue resumes that execution.",
|
|
203
|
+
];
|
|
204
|
+
const RUN_SPLIT = "Pass --workflow for a workflow run, or --role for an agent run.";
|
|
205
|
+
function registerSessionCommand(program, status) {
|
|
206
|
+
const session = program.command("session").description("Create, run, resume, and inspect sessions.");
|
|
207
|
+
const run = session
|
|
208
|
+
.command("run")
|
|
209
|
+
.description("Run a workflow, or one role when --workflow is omitted.")
|
|
210
|
+
.option("--workflow <workflow>", "Workflow id, or a path to a workflow file. Omit to run one role.")
|
|
211
|
+
.option("--task <task>", "Workflow: one-line task stored in task.md. Role: label for a new session.")
|
|
212
|
+
.option("--session <session>", "Workflow: prepared session to start, with --workflow. Role: session to continue.");
|
|
213
|
+
addDispatchOptions(run, "Dispatch the next eligible stage once. Requires --workflow.");
|
|
214
|
+
run
|
|
215
|
+
.option("--role <role>", "Role to run. Omit --workflow.")
|
|
322
216
|
.option("--provider <provider>", "Provider for this role. Pass with --model, or omit both.")
|
|
323
217
|
.option("--model <model>", "Native model id for this role. Pass with --provider, or omit both.")
|
|
324
218
|
.option("--option <pair>", "KEY=VALUE overriding one provider option for this turn.", collect, [])
|
|
325
|
-
.option("--prompt <prompt>", "Prompt text.")
|
|
326
|
-
.option("--file <file>", "Read the prompt from this file.")
|
|
327
|
-
.option("--stdin", "Read the prompt from stdin.")
|
|
328
|
-
.option("--json", "Print the result as one JSON object.")
|
|
219
|
+
.option("--prompt <prompt>", "Prompt text for a role run.")
|
|
220
|
+
.option("--file <file>", "Read the role prompt from this file.")
|
|
221
|
+
.option("--stdin", "Read the role prompt from stdin.")
|
|
222
|
+
.option("--json", "Print the role result as one JSON object.")
|
|
223
|
+
.addHelpText("after", `With --workflow:\n${exitText(LOOP_EXITS)}With --workflow and --once:\n${exitText(STAGE_EXITS)}Without --workflow:\n${exitText(ROLE_EXITS)}Examples:\n migent session run --workflow plan --task "<one line>"\n migent session run --session wave-3 --workflow plan\n migent session run --role reviewer --prompt "<prompt>"\n`)
|
|
224
|
+
.action((options) => settle(status, () => runSessionCommand(options)));
|
|
225
|
+
session
|
|
226
|
+
.command("prepare")
|
|
227
|
+
.description("Create a prepared session and exit. Write task.md, then session run --session <id> --workflow <workflow>.")
|
|
228
|
+
.argument("<id>", "Session id: a lowercase slug such as wave-3.")
|
|
229
|
+
.requiredOption("--workflow <workflow>", "Workflow id, or a path to a workflow file.")
|
|
230
|
+
.option("--prior <session>", PRIOR_HELP)
|
|
231
|
+
.addHelpText("after", `${exitText(["Exit 0 when the session is prepared.", "Exit 2 on a configuration error."])}Examples:\n migent session prepare wave-3 --workflow plan\n`)
|
|
232
|
+
.action((id, options) => settle(status, () => prepareWorkflowSession(findWorkspace(invocationCwdPath()), id, String(options.workflow), optionalText(options.prior))));
|
|
233
|
+
const continueCommand = session
|
|
234
|
+
.command("continue")
|
|
235
|
+
.description("Resume a stopped workflow or role session. A workflow session runs until it stops and resets revision counts. --once dispatches one workflow stage and does not reset them.")
|
|
236
|
+
.argument("<session>", "Session to resume.");
|
|
237
|
+
addDispatchOptions(continueCommand, "Dispatch the next eligible stage once and do not reset revision counts. Without --once, continue resets them.");
|
|
238
|
+
continueCommand
|
|
239
|
+
.option("--reply <text>", "Reply for the blocked stage. The new execution prompt body is this text.")
|
|
240
|
+
.option("--reply-file <file>", "Read the blocked stage's reply from this file.")
|
|
241
|
+
.addHelpText("after", `Workflow session:\n${exitText(LOOP_EXITS)}With --once:\n${exitText(STAGE_EXITS)}Role session:\n${exitText(ROLE_EXITS)}Examples:\n migent session continue <session>\n migent session continue <session> --once\n migent session continue <session> --reply "<reply>"\n migent session continue <session> --stage plan --reply-file answer.md\n`)
|
|
242
|
+
.action((id, options) => settle(status, () => continueSession(id, options)));
|
|
243
|
+
session
|
|
244
|
+
.command("repair")
|
|
245
|
+
.description("Rebuild status.json from the session directory.")
|
|
246
|
+
.argument("<session>", "Session to repair.")
|
|
247
|
+
.option("--accept-task", "Store the hash of the current task.md when status.json does not parse.")
|
|
248
|
+
.addHelpText("after", `${exitText([
|
|
249
|
+
"Exit 0 after status.json is rebuilt.",
|
|
250
|
+
"Exit 2 when the session cannot be repaired, or another process holds the session lock.",
|
|
251
|
+
])}Examples:\n migent session repair <session>\n migent session repair <session> --accept-task\n`)
|
|
252
|
+
.action((id, options) => settle(status, () => repairWorkflowSession(findWorkspace(invocationCwdPath()), id, options.acceptTask === true)));
|
|
253
|
+
session
|
|
254
|
+
.command("list")
|
|
255
|
+
.description("List sessions, newest first.")
|
|
256
|
+
.option("--json", "Print a JSON array without the task. With --latest, print one object, or null.")
|
|
257
|
+
.option("--workflow <id>", "List sessions for this workflow id.")
|
|
258
|
+
.option("--status <status>", "List sessions with this status: active, prepared, completed, blocked, cancelled, or corrupt.")
|
|
259
|
+
.option("--limit <n>", "List the newest n sessions. At least 1. Cannot be combined with --latest.")
|
|
260
|
+
.option("--latest", "Print the newest matching session. Cannot be combined with --limit.")
|
|
261
|
+
.addHelpText("after", `${exitText(["Exit 0 after a list, including an empty list.", "Exit 2 on a configuration error."])}Examples:\n migent session list\n migent session list --latest --json\n migent session list --workflow <id> --status completed --json\n`)
|
|
262
|
+
.action((options) => settle(status, () => runSessions(findWorkspace(invocationCwdPath()), undefined, {
|
|
263
|
+
asJson: options.json === true,
|
|
264
|
+
follow: false,
|
|
265
|
+
execution: null,
|
|
266
|
+
until: false,
|
|
267
|
+
cursor: undefined,
|
|
268
|
+
exitWhenDone: false,
|
|
269
|
+
workflow: typeof options.workflow === "string" ? options.workflow : null,
|
|
270
|
+
status: typeof options.status === "string" ? options.status : null,
|
|
271
|
+
limit: options.limit === undefined ? null : String(options.limit),
|
|
272
|
+
latest: options.latest === true,
|
|
273
|
+
})));
|
|
274
|
+
session
|
|
275
|
+
.command("show")
|
|
276
|
+
.description("Print one session snapshot.")
|
|
277
|
+
.argument("<session>", "Session to inspect.")
|
|
278
|
+
.option("--json", "Print one JSON object without the task.")
|
|
279
|
+
.addHelpText("after", `${exitText(["Exit 0 after a snapshot, including a blocked session.", "Exit 2 on a configuration error."])}Examples:\n migent session show <session>\n migent session show <session> --json\n`)
|
|
280
|
+
.action((id, options) => settle(status, () => runSessions(findWorkspace(invocationCwdPath()), id, {
|
|
281
|
+
asJson: options.json === true,
|
|
282
|
+
follow: false,
|
|
283
|
+
execution: null,
|
|
284
|
+
until: false,
|
|
285
|
+
cursor: undefined,
|
|
286
|
+
exitWhenDone: false,
|
|
287
|
+
workflow: null,
|
|
288
|
+
status: null,
|
|
289
|
+
limit: null,
|
|
290
|
+
latest: false,
|
|
291
|
+
})));
|
|
292
|
+
session
|
|
293
|
+
.command("metrics")
|
|
294
|
+
.description("Print usage, time, and revision counts recorded for a completed workflow session.")
|
|
295
|
+
.argument("[session]", "Workflow session to read. Omit to list sessions that have metrics.")
|
|
296
|
+
.option("--json", "Print one JSON object, or one object per line for a list.")
|
|
297
|
+
.option("--workflow <id>", "With a list, keep sessions for this workflow.")
|
|
298
|
+
.option("--status <status>", "With a list, keep sessions in this status.")
|
|
299
|
+
.option("--limit <n>", "With a list, keep the newest n sessions.")
|
|
300
|
+
.addHelpText("after", `${exitText([
|
|
301
|
+
"Exit 0 after one session object, or one object per line. An empty list prints nothing.",
|
|
302
|
+
"Exit 2 when --json is missing, the session has no metrics.json, that file is corrupt, or a list flag is invalid.",
|
|
303
|
+
])}Examples:\n migent session metrics <session> --json\n migent session metrics --json --workflow <id> --status completed --limit 10\n`)
|
|
304
|
+
.action((id, options) => settle(status, () => runSessionMetrics(findWorkspace(invocationCwdPath()), id, {
|
|
305
|
+
asJson: options.json === true,
|
|
306
|
+
workflow: typeof options.workflow === "string" ? options.workflow : null,
|
|
307
|
+
status: typeof options.status === "string" ? options.status : null,
|
|
308
|
+
limit: options.limit === undefined ? null : String(options.limit),
|
|
309
|
+
})));
|
|
310
|
+
session
|
|
311
|
+
.command("follow")
|
|
312
|
+
.description("Follow a session's events until a signal.")
|
|
313
|
+
.argument("<session>", "Session to follow.")
|
|
314
|
+
.option("--json", "Print a snapshot object, then JSON lines. With --cursor, only JSON lines.")
|
|
315
|
+
.option("--execution <execution>", "Limit followed events and --until to this execution.")
|
|
316
|
+
.option("--cursor <offset>", "Byte offset in events.jsonl. 0 replays the whole log and every execution stream. Any other offset replays events from that byte, and stream lines whose timestamp is at or after the first event there. Skips the snapshot.")
|
|
317
|
+
.option("--until", "Exit when --execution reaches completed, failed, or cancelled. Requires --execution.")
|
|
318
|
+
.option("--exit-when-done", "Exit when the session is completed (0), blocked (1), or cancelled (130).")
|
|
319
|
+
.addHelpText("after", `${exitText([
|
|
320
|
+
"Exit 0 when --exit-when-done and the session is completed.",
|
|
321
|
+
"Exit 1 when --exit-when-done and the session is blocked.",
|
|
322
|
+
"Exit 130 when --exit-when-done and the session is cancelled, or the follow is cancelled.",
|
|
323
|
+
"Exit 2 on a configuration error.",
|
|
324
|
+
])}Examples:\n migent session follow <session>\n migent session follow <session> --exit-when-done\n`)
|
|
325
|
+
.action((id, options) => settle(status, () => runSessions(findWorkspace(invocationCwdPath()), id, {
|
|
326
|
+
asJson: options.json === true,
|
|
327
|
+
follow: true,
|
|
328
|
+
execution: optionalText(options.execution),
|
|
329
|
+
until: options.until === true,
|
|
330
|
+
cursor: options.cursor === undefined ? undefined : String(options.cursor),
|
|
331
|
+
exitWhenDone: options.exitWhenDone === true,
|
|
332
|
+
workflow: null,
|
|
333
|
+
status: null,
|
|
334
|
+
limit: null,
|
|
335
|
+
latest: false,
|
|
336
|
+
})));
|
|
337
|
+
showHelpWhenBare(status, session);
|
|
338
|
+
}
|
|
339
|
+
function runSessionCommand(options) {
|
|
340
|
+
if (options.stage !== undefined && optionalText(options.stage) === null) {
|
|
341
|
+
printError("--stage is empty.");
|
|
342
|
+
return 2;
|
|
343
|
+
}
|
|
344
|
+
const workflowProvided = options.workflow !== undefined;
|
|
345
|
+
const workflowId = optionalText(options.workflow);
|
|
346
|
+
if (workflowProvided && workflowId === null) {
|
|
347
|
+
printError("--workflow is empty.");
|
|
348
|
+
return 2;
|
|
349
|
+
}
|
|
350
|
+
const roleMode = roleFlagsPresent(options);
|
|
351
|
+
const workflowMode = workflowFlagsPresent(options);
|
|
352
|
+
if ((workflowId !== null && roleMode) || (workflowId === null && workflowMode)) {
|
|
353
|
+
printError(RUN_SPLIT);
|
|
354
|
+
return 2;
|
|
355
|
+
}
|
|
356
|
+
if (workflowId !== null) {
|
|
357
|
+
const sessionId = optionalText(options.session);
|
|
358
|
+
const task = rawText(options.task);
|
|
359
|
+
const prepared = sessionId !== null;
|
|
360
|
+
if (prepared && task !== null) {
|
|
361
|
+
printError("Pass --workflow and --task, or --session and --workflow, not both.");
|
|
362
|
+
return 2;
|
|
363
|
+
}
|
|
364
|
+
if (!prepared && task === null) {
|
|
365
|
+
printError("Pass --task.");
|
|
366
|
+
return 2;
|
|
367
|
+
}
|
|
368
|
+
return runWorkflowCommand({
|
|
369
|
+
workflowId,
|
|
370
|
+
task,
|
|
371
|
+
sessionId,
|
|
372
|
+
prepared,
|
|
373
|
+
once: options.once === true,
|
|
374
|
+
options,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
if (!roleMode) {
|
|
378
|
+
printError(RUN_SPLIT);
|
|
379
|
+
return 2;
|
|
380
|
+
}
|
|
381
|
+
return runAgent(options, optionalText(options.session));
|
|
382
|
+
}
|
|
383
|
+
function roleFlagsPresent(options) {
|
|
384
|
+
return (options.role !== undefined ||
|
|
385
|
+
options.provider !== undefined ||
|
|
386
|
+
options.model !== undefined ||
|
|
387
|
+
stringList(options.option).length > 0 ||
|
|
388
|
+
options.prompt !== undefined ||
|
|
389
|
+
options.file !== undefined ||
|
|
390
|
+
options.stdin === true ||
|
|
391
|
+
options.json === true);
|
|
392
|
+
}
|
|
393
|
+
function workflowFlagsPresent(options) {
|
|
394
|
+
return (options.once === true ||
|
|
395
|
+
options.stage !== undefined ||
|
|
396
|
+
options.prior !== undefined ||
|
|
397
|
+
options.maxDispatches !== undefined ||
|
|
398
|
+
options.unlimitedDispatches === true ||
|
|
399
|
+
options.progress === true ||
|
|
400
|
+
options.verbose === true ||
|
|
401
|
+
options.refreshWorkflow === true);
|
|
402
|
+
}
|
|
403
|
+
function runWorkflowCommand(args) {
|
|
404
|
+
if (rejectOnce(args.options))
|
|
405
|
+
return 2;
|
|
406
|
+
const workflowId = args.workflowId;
|
|
407
|
+
const task = args.prepared ? null : args.task;
|
|
408
|
+
const sessionId = args.prepared ? args.sessionId : null;
|
|
409
|
+
const stageId = optionalText(args.options.stage);
|
|
410
|
+
if (!args.once && stageId === null) {
|
|
411
|
+
const ceiling = dispatchCeiling(args.options);
|
|
412
|
+
if (typeof ceiling === "string")
|
|
413
|
+
return 2;
|
|
414
|
+
return executeWorkflow(findWorkspace(invocationCwdPath()), {
|
|
415
|
+
action: "start",
|
|
416
|
+
workflowId,
|
|
417
|
+
task,
|
|
418
|
+
sessionId,
|
|
419
|
+
startPrepared: args.prepared,
|
|
420
|
+
priorSessionId: optionalText(args.options.prior),
|
|
421
|
+
maxDispatches: ceiling,
|
|
422
|
+
refreshWorkflow: args.options.refreshWorkflow === true,
|
|
423
|
+
...outputFlags(args.options),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return executeWorkflow(findWorkspace(invocationCwdPath()), {
|
|
427
|
+
action: "stage",
|
|
428
|
+
workflowId,
|
|
429
|
+
task,
|
|
430
|
+
sessionId,
|
|
431
|
+
stageId,
|
|
432
|
+
startPrepared: args.prepared,
|
|
433
|
+
priorSessionId: optionalText(args.options.prior),
|
|
434
|
+
maxDispatches: null,
|
|
435
|
+
refreshWorkflow: args.options.refreshWorkflow === true,
|
|
436
|
+
...outputFlags(args.options),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function continueSession(session, options) {
|
|
440
|
+
if (options.stage !== undefined && optionalText(options.stage) === null) {
|
|
441
|
+
printError("--stage is empty.");
|
|
442
|
+
return 2;
|
|
443
|
+
}
|
|
444
|
+
const workspace = findWorkspace(invocationCwdPath());
|
|
445
|
+
if (isAgentSession(workspace, session))
|
|
446
|
+
return continueRoleSession(workspace, session, options);
|
|
447
|
+
if (rejectOnce(options))
|
|
448
|
+
return 2;
|
|
449
|
+
const reply = readReply(options);
|
|
450
|
+
if (reply === "invalid")
|
|
451
|
+
return 2;
|
|
452
|
+
const stageId = optionalText(options.stage);
|
|
453
|
+
if (options.once === true || stageId !== null) {
|
|
454
|
+
return executeWorkflow(findWorkspace(invocationCwdPath()), {
|
|
455
|
+
action: "stage",
|
|
456
|
+
sessionId: session,
|
|
457
|
+
stageId,
|
|
458
|
+
priorSessionId: optionalText(options.prior),
|
|
459
|
+
maxDispatches: null,
|
|
460
|
+
continueFailed: true,
|
|
461
|
+
refreshWorkflow: options.refreshWorkflow === true,
|
|
462
|
+
reply,
|
|
463
|
+
...outputFlags(options),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
const ceiling = dispatchCeiling(options);
|
|
467
|
+
if (typeof ceiling === "string")
|
|
468
|
+
return 2;
|
|
469
|
+
return executeWorkflow(findWorkspace(invocationCwdPath()), {
|
|
470
|
+
action: "continue",
|
|
471
|
+
sessionId: session,
|
|
472
|
+
priorSessionId: optionalText(options.prior),
|
|
473
|
+
maxDispatches: ceiling,
|
|
474
|
+
continueFailed: true,
|
|
475
|
+
refreshWorkflow: options.refreshWorkflow === true,
|
|
476
|
+
reply,
|
|
477
|
+
...outputFlags(options),
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function isAgentSession(workspace, sessionId) {
|
|
481
|
+
try {
|
|
482
|
+
requireAgentSession(workspace, sessionId);
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
if (!(error instanceof ConfigurationError))
|
|
487
|
+
throw error;
|
|
488
|
+
if (error.message === `Session not found: ${sessionId}.`)
|
|
489
|
+
return false;
|
|
490
|
+
if (error.message === `Session ${sessionId} was not created by agent.`)
|
|
491
|
+
return false;
|
|
492
|
+
if (error.message.startsWith("Session status file is corrupt:"))
|
|
493
|
+
return false;
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function continueRoleSession(workspace, sessionId, options) {
|
|
498
|
+
const flags = [];
|
|
499
|
+
if (options.once === true)
|
|
500
|
+
flags.push("--once");
|
|
501
|
+
if (options.stage !== undefined)
|
|
502
|
+
flags.push("--stage");
|
|
503
|
+
if (options.prior !== undefined)
|
|
504
|
+
flags.push("--prior");
|
|
505
|
+
if (options.maxDispatches !== undefined)
|
|
506
|
+
flags.push("--max-dispatches");
|
|
507
|
+
if (options.unlimitedDispatches === true)
|
|
508
|
+
flags.push("--unlimited-dispatches");
|
|
509
|
+
if (options.progress === true)
|
|
510
|
+
flags.push("--progress");
|
|
511
|
+
if (options.verbose === true)
|
|
512
|
+
flags.push("--verbose");
|
|
513
|
+
if (options.refreshWorkflow === true)
|
|
514
|
+
flags.push("--refresh-workflow");
|
|
515
|
+
if (flags.length > 0) {
|
|
516
|
+
printError(`Session ${sessionId} is a role session. Omit ${flags.join(", ")}.`);
|
|
517
|
+
return 2;
|
|
518
|
+
}
|
|
519
|
+
const reply = readReply(options);
|
|
520
|
+
if (reply === "invalid")
|
|
521
|
+
return 2;
|
|
522
|
+
return continueAgent(workspace, { sessionId, reply });
|
|
523
|
+
}
|
|
524
|
+
function readReply(options) {
|
|
525
|
+
const hasReply = options.reply !== undefined;
|
|
526
|
+
const hasFile = options.replyFile !== undefined;
|
|
527
|
+
if (!hasReply && !hasFile)
|
|
528
|
+
return null;
|
|
529
|
+
if (hasReply && hasFile) {
|
|
530
|
+
printError("Pass only one of --reply or --reply-file.");
|
|
531
|
+
return "invalid";
|
|
532
|
+
}
|
|
533
|
+
if (hasFile) {
|
|
534
|
+
const file = optionalText(options.replyFile);
|
|
535
|
+
if (file === null) {
|
|
536
|
+
printError("--reply-file is empty.");
|
|
537
|
+
return "invalid";
|
|
538
|
+
}
|
|
539
|
+
let text;
|
|
540
|
+
try {
|
|
541
|
+
text = readFileSync(file, "utf8");
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
545
|
+
printError(`Unable to read reply file: ${file} (${reason})`);
|
|
546
|
+
return "invalid";
|
|
547
|
+
}
|
|
548
|
+
if (text.trim() === "") {
|
|
549
|
+
printError("--reply is empty.");
|
|
550
|
+
return "invalid";
|
|
551
|
+
}
|
|
552
|
+
return text;
|
|
553
|
+
}
|
|
554
|
+
const reply = rawText(options.reply);
|
|
555
|
+
if (reply === null || reply.trim() === "") {
|
|
556
|
+
printError("--reply is empty.");
|
|
557
|
+
return "invalid";
|
|
558
|
+
}
|
|
559
|
+
return reply;
|
|
560
|
+
}
|
|
561
|
+
function addDispatchOptions(command, onceDescription) {
|
|
562
|
+
command
|
|
563
|
+
.option("--once", onceDescription)
|
|
564
|
+
.option("--stage <stage>", "Dispatch this ready stage once. It does not have to be the first ready stage.")
|
|
565
|
+
.option("--prior <session>", PRIOR_HELP)
|
|
566
|
+
.option("--max-dispatches <count>", `Stage dispatches allowed in this command. Default: ${DEFAULT_MAX_DISPATCHES}. Pass --unlimited-dispatches to remove the ceiling.`)
|
|
567
|
+
.option("--unlimited-dispatches", "Remove the dispatch ceiling for this command.")
|
|
568
|
+
.option("--progress", PROGRESS_HELP)
|
|
569
|
+
.option("--verbose", VERBOSE_HELP)
|
|
570
|
+
.option("--refresh-workflow", "Use the current workflow, models, and role instructions for this session. Requires an existing session that has a frozen workflow.");
|
|
571
|
+
}
|
|
572
|
+
function outputFlags(options) {
|
|
573
|
+
return { itemProgress: options.progress === true };
|
|
329
574
|
}
|
|
330
575
|
function showHelpWhenBare(status, command) {
|
|
331
576
|
command.action(() => {
|
|
@@ -353,6 +598,11 @@ function stringList(value) {
|
|
|
353
598
|
return value.filter((item) => typeof item === "string");
|
|
354
599
|
}
|
|
355
600
|
async function runAgent(options, sessionId) {
|
|
601
|
+
const role = optionalText(options.role);
|
|
602
|
+
if (role === null) {
|
|
603
|
+
printError("Pass --role.");
|
|
604
|
+
return 2;
|
|
605
|
+
}
|
|
356
606
|
const provider = optionalText(options.provider);
|
|
357
607
|
const model = optionalText(options.model);
|
|
358
608
|
if ((provider === null) !== (model === null)) {
|
|
@@ -368,7 +618,7 @@ async function runAgent(options, sessionId) {
|
|
|
368
618
|
return 2;
|
|
369
619
|
}
|
|
370
620
|
return dispatchAgent(findWorkspace(invocationCwdPath()), {
|
|
371
|
-
role
|
|
621
|
+
role,
|
|
372
622
|
task: optionalText(options.task),
|
|
373
623
|
sessionId,
|
|
374
624
|
provider,
|
|
@@ -380,6 +630,20 @@ async function runAgent(options, sessionId) {
|
|
|
380
630
|
asJson: options.json === true,
|
|
381
631
|
});
|
|
382
632
|
}
|
|
633
|
+
function rejectOnce(options) {
|
|
634
|
+
if (options.once !== true && options.stage === undefined)
|
|
635
|
+
return false;
|
|
636
|
+
const flag = options.once === true ? "--once" : "--stage";
|
|
637
|
+
if (options.maxDispatches !== undefined) {
|
|
638
|
+
printError(`${flag} cannot be combined with --max-dispatches.`);
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
if (options.unlimitedDispatches === true) {
|
|
642
|
+
printError(`${flag} cannot be combined with --unlimited-dispatches.`);
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
383
647
|
function dispatchCeiling(options) {
|
|
384
648
|
const maxDispatches = optionalText(options.maxDispatches);
|
|
385
649
|
const unlimited = options.unlimitedDispatches === true;
|
|
@@ -393,24 +657,12 @@ function dispatchCeiling(options) {
|
|
|
393
657
|
return DEFAULT_MAX_DISPATCHES;
|
|
394
658
|
return integerFlag(maxDispatches, "--max-dispatches");
|
|
395
659
|
}
|
|
396
|
-
function attemptsOf(options) {
|
|
397
|
-
const raw = optionalText(options.attempts);
|
|
398
|
-
if (raw === null)
|
|
399
|
-
return 3;
|
|
400
|
-
return integerFlag(raw, "--attempts");
|
|
401
|
-
}
|
|
402
660
|
function integerFlag(raw, flag) {
|
|
403
661
|
if (!/^[0-9]+$/.test(raw) || Number(raw) < 1) {
|
|
404
662
|
throw new ConfigurationError(`${flag} must be at least 1.`);
|
|
405
663
|
}
|
|
406
664
|
return Number(raw);
|
|
407
665
|
}
|
|
408
|
-
function
|
|
409
|
-
return
|
|
410
|
-
}
|
|
411
|
-
function text(value) {
|
|
412
|
-
if (typeof value !== "string")
|
|
413
|
-
return null;
|
|
414
|
-
const stripped = value.trim();
|
|
415
|
-
return stripped === "" ? null : stripped;
|
|
666
|
+
function inputDisabled(options) {
|
|
667
|
+
return options.noInput === true || process.env.MIGENT_NO_INPUT === "1";
|
|
416
668
|
}
|