@the-open-engine/zeroshot 6.31.3 → 6.32.1
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 +66 -98
- package/cli/index.js +251 -252
- package/cli/lib/setup-provider-readiness.js +86 -0
- package/cli/lib/setup-scanner-worker.js +120 -0
- package/cli/lib/setup-scanner.js +185 -0
- package/cli/lib/setup-wizard-input.js +146 -0
- package/cli/lib/setup-wizard-model.js +205 -0
- package/cli/lib/setup-wizard-plan-view.js +157 -0
- package/cli/lib/setup-wizard-scan-view.js +144 -0
- package/cli/lib/setup-wizard-terminal.js +237 -0
- package/cli/lib/setup-wizard-view.js +180 -0
- package/cli/lib/setup-wizard.js +281 -0
- package/cli/message-formatters-normal.js +14 -18
- package/cli/message-formatters-watch.js +53 -141
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +8 -2
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/agent-cli-provider/provider-registry.d.ts +4 -2
- package/lib/agent-cli-provider/provider-registry.d.ts.map +1 -1
- package/lib/agent-cli-provider/provider-registry.js +13 -3
- package/lib/agent-cli-provider/provider-registry.js.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -1
- package/lib/agent-cli-provider/single-agent-runtime.js +7 -4
- package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -1
- package/lib/agent-cli-provider/types.d.ts +2 -0
- package/lib/agent-cli-provider/types.d.ts.map +1 -1
- package/lib/agent-cli-provider/types.js.map +1 -1
- package/lib/completion.js +102 -153
- package/lib/settings.js +10 -2
- package/lib/setup-apply.js +62 -55
- package/lib/setup-plan.js +32 -52
- package/lib/start-cluster.js +65 -25
- package/npm-shrinkwrap.json +2 -2
- package/package.json +3 -3
- package/scripts/postinstall.js +54 -0
- package/src/agent/agent-lifecycle.js +11 -1
- package/src/agent/agent-task-executor.js +25 -7
- package/src/agent/structured-output-error.js +42 -0
- package/src/agent-cli-provider/adapters/codex.ts +8 -12
- package/src/agent-cli-provider/provider-registry.ts +15 -3
- package/src/agent-cli-provider/single-agent-runtime.ts +11 -11
- package/src/agent-cli-provider/types.ts +2 -0
- package/src/preflight.js +27 -1
- package/src/status-footer.js +19 -12
- package/task-lib/commands/list.js +90 -78
- package/task-lib/commands/status.js +97 -40
- package/task-lib/effective-status.js +52 -0
package/cli/index.js
CHANGED
|
@@ -22,7 +22,7 @@ const { URL } = require('url');
|
|
|
22
22
|
const chalk = require('chalk');
|
|
23
23
|
const Orchestrator = require('../src/orchestrator');
|
|
24
24
|
const { setupCompletion } = require('../lib/completion');
|
|
25
|
-
const { resolveRunMode, describeRunMode } = require('../lib/run-mode');
|
|
25
|
+
const { resolveRunMode, runModeFromPlan, describeRunMode } = require('../lib/run-mode');
|
|
26
26
|
const { formatWatchMode } = require('./message-formatters-watch');
|
|
27
27
|
const {
|
|
28
28
|
formatAgentLifecycle,
|
|
@@ -35,11 +35,7 @@ const {
|
|
|
35
35
|
formatClusterFailed,
|
|
36
36
|
formatGenericMessage,
|
|
37
37
|
} = require('./message-formatters-normal');
|
|
38
|
-
const {
|
|
39
|
-
getColorForSender,
|
|
40
|
-
buildMessagePrefix,
|
|
41
|
-
buildClusterPrefix,
|
|
42
|
-
} = require('./message-formatter-utils');
|
|
38
|
+
const { getColorForSender, buildMessagePrefix } = require('./message-formatter-utils');
|
|
43
39
|
const {
|
|
44
40
|
loadSettings,
|
|
45
41
|
mutateSettings,
|
|
@@ -74,6 +70,7 @@ const {
|
|
|
74
70
|
startClusterFromFile,
|
|
75
71
|
startClusterFromIssue,
|
|
76
72
|
startClusterFromText,
|
|
73
|
+
resolveEffectiveRunPlan,
|
|
77
74
|
} = require('../lib/start-cluster');
|
|
78
75
|
const { requirePreflight } = require('../src/preflight');
|
|
79
76
|
const {
|
|
@@ -92,6 +89,7 @@ const {
|
|
|
92
89
|
waitForResumeOwnership,
|
|
93
90
|
} = require('../lib/detached-startup');
|
|
94
91
|
const { isProcessRunning: isClusterProcessAlive } = require('../lib/process-liveness');
|
|
92
|
+
const { runSetupWizard } = require('./lib/setup-wizard');
|
|
95
93
|
const {
|
|
96
94
|
checkForUpdates,
|
|
97
95
|
isAutomaticUpdateEligible,
|
|
@@ -117,30 +115,41 @@ const PROVIDER_CHOICES = VALID_PROVIDERS.join(', ');
|
|
|
117
115
|
/** @type {import('../src/status-footer').StatusFooter | null} */
|
|
118
116
|
let activeStatusFooter = null;
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
function normalizeLineText(text) {
|
|
119
|
+
let line = String(text);
|
|
120
|
+
while (line.endsWith('\n')) {
|
|
121
|
+
line = line.slice(0, -1);
|
|
122
|
+
if (line.endsWith('\r')) line = line.slice(0, -1);
|
|
123
|
+
}
|
|
124
|
+
return line;
|
|
125
|
+
}
|
|
126
126
|
|
|
127
|
+
function printLine(text = '') {
|
|
128
|
+
const line = normalizeLineText(text);
|
|
127
129
|
if (activeStatusFooter) {
|
|
128
|
-
activeStatusFooter.print(
|
|
129
|
-
|
|
130
|
-
console.log(...args);
|
|
130
|
+
activeStatusFooter.print(line);
|
|
131
|
+
return;
|
|
131
132
|
}
|
|
133
|
+
process.stdout.write(`${line}\n`);
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
* @param {string} text - Text to write
|
|
137
|
-
*/
|
|
138
|
-
function safeWrite(text) {
|
|
136
|
+
function write(text) {
|
|
137
|
+
const chunk = String(text);
|
|
139
138
|
if (activeStatusFooter) {
|
|
140
|
-
activeStatusFooter.
|
|
141
|
-
|
|
142
|
-
process.stdout.write(text);
|
|
139
|
+
activeStatusFooter.write(chunk);
|
|
140
|
+
return;
|
|
143
141
|
}
|
|
142
|
+
process.stdout.write(chunk);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const liveOutputWriter = Object.freeze({ printLine, write });
|
|
146
|
+
|
|
147
|
+
function safePrint(...args) {
|
|
148
|
+
printLine(args.map((arg) => (typeof arg === 'string' ? arg : String(arg))).join(' '));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function safeWrite(text) {
|
|
152
|
+
write(text);
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
/**
|
|
@@ -198,56 +207,43 @@ process.on('unhandledRejection', (reason) => {
|
|
|
198
207
|
// Package root directory (for resolving default config paths)
|
|
199
208
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
200
209
|
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
if (options.docker) {
|
|
212
|
-
options.worktree = false;
|
|
213
|
-
}
|
|
214
|
-
// autoMerge is NOT stored here — it is derived from the run plan (delivery ===
|
|
215
|
-
// 'ship') at every consumer. Writing it here unconditionally would clobber an
|
|
216
|
-
// explicit autoMerge intent (e.g. a future `--auto-merge` flag) back to false.
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async function runClusterPreflight({ input, options, providerOverride, settings, forceProvider }) {
|
|
220
|
-
// Detect which issue provider tool is needed
|
|
210
|
+
async function runClusterPreflight({
|
|
211
|
+
input,
|
|
212
|
+
options,
|
|
213
|
+
plan,
|
|
214
|
+
providerOverride,
|
|
215
|
+
settings,
|
|
216
|
+
forceProvider,
|
|
217
|
+
deps = {},
|
|
218
|
+
}) {
|
|
221
219
|
let issueProvider = null;
|
|
222
220
|
let targetHost = null;
|
|
223
221
|
|
|
224
222
|
if (input.issue) {
|
|
225
223
|
const { detectProvider: detectIssueProvider } = require('../src/issue-providers');
|
|
226
224
|
const ProviderClass = detectIssueProvider(input.issue, settings, forceProvider);
|
|
227
|
-
if (ProviderClass)
|
|
228
|
-
issueProvider = ProviderClass.id;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Extract hostname from URL input for auth checks
|
|
232
|
-
// This ensures we check auth for the target host, not the current git repo
|
|
225
|
+
if (ProviderClass) issueProvider = ProviderClass.id;
|
|
233
226
|
if (/^https?:\/\//.test(input.issue)) {
|
|
234
227
|
try {
|
|
235
|
-
|
|
236
|
-
targetHost = url.hostname;
|
|
228
|
+
targetHost = new URL(input.issue).hostname;
|
|
237
229
|
} catch {
|
|
238
|
-
//
|
|
230
|
+
// Provider parsing reports malformed issue URLs.
|
|
239
231
|
}
|
|
240
232
|
}
|
|
241
233
|
}
|
|
242
234
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
235
|
+
const effectivePlan = plan || resolveEffectiveRunPlan(options, settings);
|
|
236
|
+
const preflight = deps.requirePreflight || requirePreflight;
|
|
237
|
+
await preflight({
|
|
238
|
+
requireGh: issueProvider === 'github',
|
|
239
|
+
requireDocker: effectivePlan.isolation === 'docker',
|
|
240
|
+
requireGit: effectivePlan.isolation === 'worktree',
|
|
241
|
+
autoPr: effectivePlan.delivery !== 'none',
|
|
247
242
|
quiet: process.env.ZEROSHOT_DAEMON === '1',
|
|
248
243
|
provider: providerOverride,
|
|
249
|
-
|
|
250
|
-
|
|
244
|
+
settings,
|
|
245
|
+
issueProvider,
|
|
246
|
+
targetHost,
|
|
251
247
|
});
|
|
252
248
|
}
|
|
253
249
|
|
|
@@ -255,8 +251,8 @@ function shouldRunDetached(options) {
|
|
|
255
251
|
return options.detach && !process.env.ZEROSHOT_DAEMON;
|
|
256
252
|
}
|
|
257
253
|
|
|
258
|
-
function printDetachedClusterStart(
|
|
259
|
-
const runMode =
|
|
254
|
+
function printDetachedClusterStart(plan, clusterId, logPath) {
|
|
255
|
+
const runMode = runModeFromPlan(plan);
|
|
260
256
|
console.log(runMode ? `Started ${clusterId} (${runMode})` : `Started ${clusterId}`);
|
|
261
257
|
if (logPath) {
|
|
262
258
|
console.log(`Setup log: ${logPath}`);
|
|
@@ -328,7 +324,18 @@ function spawnDetachedChild(env, cwd, logFd) {
|
|
|
328
324
|
return daemon;
|
|
329
325
|
}
|
|
330
326
|
|
|
331
|
-
|
|
327
|
+
function applyRunPlanToOptions(options, plan) {
|
|
328
|
+
return {
|
|
329
|
+
...options,
|
|
330
|
+
docker: plan.isolation === 'docker',
|
|
331
|
+
worktree: plan.isolation === 'worktree',
|
|
332
|
+
pr: plan.delivery === 'pr',
|
|
333
|
+
ship: plan.delivery === 'ship',
|
|
334
|
+
noIsolation: plan.isolation === 'none',
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function spawnDetachedCluster(options, plan, clusterId, stdinText) {
|
|
332
339
|
const logFd = createDaemonLogFile(clusterId);
|
|
333
340
|
const targetCwd = detectGitRepoRoot();
|
|
334
341
|
const logPath = path.join(os.homedir(), '.zeroshot', `${clusterId}-daemon.log`);
|
|
@@ -346,7 +353,7 @@ async function spawnDetachedCluster(options, clusterId, stdinText) {
|
|
|
346
353
|
cwd: targetCwd,
|
|
347
354
|
});
|
|
348
355
|
fs.closeSync(logFd);
|
|
349
|
-
printDetachedClusterStart(
|
|
356
|
+
printDetachedClusterStart(plan, clusterId, logPath);
|
|
350
357
|
}
|
|
351
358
|
|
|
352
359
|
// Resume's daemon env is deliberately NOT buildDaemonEnv: run-only keys like
|
|
@@ -407,11 +414,11 @@ function trackActiveCluster(clusterId, orchestrator) {
|
|
|
407
414
|
orchestratorInstance = orchestrator;
|
|
408
415
|
}
|
|
409
416
|
|
|
410
|
-
function printForegroundStartInfo(
|
|
417
|
+
function printForegroundStartInfo(plan, clusterId, configName) {
|
|
411
418
|
if (process.env.ZEROSHOT_DAEMON) {
|
|
412
419
|
return;
|
|
413
420
|
}
|
|
414
|
-
const runMode =
|
|
421
|
+
const runMode = runModeFromPlan(plan);
|
|
415
422
|
console.log(runMode ? `Starting ${clusterId} (${runMode})` : `Starting ${clusterId}`);
|
|
416
423
|
console.log(chalk.dim(`Config: ${configName}`));
|
|
417
424
|
console.log(chalk.dim('Ctrl+C to stop following (cluster keeps running)\n'));
|
|
@@ -656,11 +663,11 @@ function waitForClusterCompletion(orchestrator, clusterId, cleanup) {
|
|
|
656
663
|
});
|
|
657
664
|
}
|
|
658
665
|
|
|
659
|
-
async function streamClusterInForeground(cluster, orchestrator, clusterId,
|
|
666
|
+
async function streamClusterInForeground(cluster, orchestrator, clusterId, plan) {
|
|
660
667
|
const sendersWithOutput = new Set();
|
|
661
668
|
const processedMessageIds = new Set();
|
|
662
669
|
|
|
663
|
-
const statusFooter = createStatusFooter(clusterId, cluster.messageBus,
|
|
670
|
+
const statusFooter = createStatusFooter(clusterId, cluster.messageBus, runModeFromPlan(plan));
|
|
664
671
|
const handleLifecycleMessage = createLifecycleHandler(statusFooter);
|
|
665
672
|
const lifecycleUnsubscribe = cluster.messageBus.subscribeTopic(
|
|
666
673
|
'AGENT_LIFECYCLE',
|
|
@@ -789,17 +796,6 @@ function printClusterTable(enrichedClusters) {
|
|
|
789
796
|
}
|
|
790
797
|
}
|
|
791
798
|
|
|
792
|
-
async function tryGetTasksData(getTasksData, options) {
|
|
793
|
-
if (typeof getTasksData !== 'function') {
|
|
794
|
-
return [];
|
|
795
|
-
}
|
|
796
|
-
try {
|
|
797
|
-
return await getTasksData(options);
|
|
798
|
-
} catch {
|
|
799
|
-
return [];
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
799
|
function printListJson(enrichedClusters, tasks) {
|
|
804
800
|
console.log(
|
|
805
801
|
JSON.stringify(
|
|
@@ -813,6 +809,10 @@ function printListJson(enrichedClusters, tasks) {
|
|
|
813
809
|
);
|
|
814
810
|
}
|
|
815
811
|
|
|
812
|
+
function printJsonError(error) {
|
|
813
|
+
console.log(JSON.stringify({ error: error.message }, null, 2));
|
|
814
|
+
}
|
|
815
|
+
|
|
816
816
|
function reportMissingId(id, options) {
|
|
817
817
|
if (options.json) {
|
|
818
818
|
console.log(JSON.stringify({ error: 'ID not found', id }, null, 2));
|
|
@@ -932,22 +932,11 @@ function printClusterStatusHuman(status, tokensByRole, clusterId) {
|
|
|
932
932
|
printClusterAgents(status);
|
|
933
933
|
}
|
|
934
934
|
|
|
935
|
-
async function tryGetTaskStatusData(getStatusData, id) {
|
|
936
|
-
if (typeof getStatusData !== 'function') {
|
|
937
|
-
return null;
|
|
938
|
-
}
|
|
939
|
-
try {
|
|
940
|
-
return await getStatusData(id);
|
|
941
|
-
} catch {
|
|
942
|
-
return null;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
|
|
946
935
|
async function showTaskStatus(id, options) {
|
|
947
936
|
const { showStatus, getStatusData } = await import('../task-lib/commands/status.js');
|
|
948
937
|
if (options.json) {
|
|
949
|
-
const taskData = await
|
|
950
|
-
console.log(JSON.stringify({ type: 'task',
|
|
938
|
+
const taskData = await getStatusData(id);
|
|
939
|
+
console.log(JSON.stringify({ type: 'task', ...taskData }, null, 2));
|
|
951
940
|
return;
|
|
952
941
|
}
|
|
953
942
|
await showStatus(id);
|
|
@@ -2577,9 +2566,6 @@ function buildTaskLogMessage({ taskId, timestamp, jsonContent, cluster, agent, i
|
|
|
2577
2566
|
};
|
|
2578
2567
|
}
|
|
2579
2568
|
|
|
2580
|
-
// Setup shell completion
|
|
2581
|
-
setupCompletion();
|
|
2582
|
-
|
|
2583
2569
|
// Banner disabled
|
|
2584
2570
|
function showBanner() {
|
|
2585
2571
|
// Banner removed for cleaner output
|
|
@@ -2600,48 +2586,18 @@ if (shouldShowBanner) {
|
|
|
2600
2586
|
|
|
2601
2587
|
program
|
|
2602
2588
|
.name('zeroshot')
|
|
2603
|
-
.description('
|
|
2589
|
+
.description('Independent executor–verifier orchestration for software changes.')
|
|
2604
2590
|
.version(require('../package.json').version)
|
|
2605
|
-
.option('-q, --quiet', '
|
|
2591
|
+
.option('-q, --quiet', 'Suppress prompts (first-run wizard, update checks)')
|
|
2592
|
+
.helpCommand(false)
|
|
2606
2593
|
.addHelpText(
|
|
2607
2594
|
'after',
|
|
2608
2595
|
`
|
|
2609
2596
|
Examples:
|
|
2610
|
-
${chalk.cyan('zeroshot
|
|
2611
|
-
${chalk.cyan('zeroshot run
|
|
2612
|
-
${chalk.cyan('zeroshot
|
|
2613
|
-
${chalk.cyan('zeroshot
|
|
2614
|
-
${chalk.cyan('zeroshot run 123 -d')} Run in background (detached)
|
|
2615
|
-
${chalk.cyan('zeroshot run 123 --docker')} Run in Docker container (safe for e2e tests)
|
|
2616
|
-
${chalk.cyan('zeroshot task run "Fix the bug"')} Run single-agent background task
|
|
2617
|
-
${chalk.cyan('zeroshot list')} List all tasks and clusters
|
|
2618
|
-
${chalk.cyan('zeroshot task list')} List tasks only
|
|
2619
|
-
${chalk.cyan('zeroshot attach <id>')} Attach to running task (Ctrl+B d to detach)
|
|
2620
|
-
${chalk.cyan('zeroshot logs -f')} Stream logs in real-time (like tail -f)
|
|
2621
|
-
${chalk.cyan('zeroshot logs -w')} Watch cluster lifecycle and event summaries
|
|
2622
|
-
${chalk.cyan('zeroshot logs <id> -f')} Stream logs for specific cluster/task
|
|
2623
|
-
${chalk.cyan('zeroshot status <id>')} Detailed status of task or cluster
|
|
2624
|
-
${chalk.cyan('zeroshot finish <id>')} Convert cluster to completion task (creates and merges PR)
|
|
2625
|
-
${chalk.cyan('zeroshot kill <id>')} Kill a running task or cluster
|
|
2626
|
-
${chalk.cyan('zeroshot purge')} Kill all processes and delete all data (with confirmation)
|
|
2627
|
-
${chalk.cyan('zeroshot purge -y')} Purge everything without confirmation
|
|
2628
|
-
${chalk.cyan('zeroshot settings')} Show/manage zeroshot settings (maxModel, config, etc.)
|
|
2629
|
-
${chalk.cyan('zeroshot settings set <key> <val>')} Set a setting (e.g., maxModel haiku)
|
|
2630
|
-
${chalk.cyan('zeroshot providers')} Show provider status and defaults
|
|
2631
|
-
${chalk.cyan('zeroshot config list')} List available cluster configs
|
|
2632
|
-
${chalk.cyan('zeroshot config show <name>')} Visualize a cluster config (agents, triggers, flow)
|
|
2633
|
-
${chalk.cyan('zeroshot export <id>')} Export cluster conversation to file
|
|
2634
|
-
|
|
2635
|
-
Automation levels (cascading: --ship → --pr → --worktree):
|
|
2636
|
-
${chalk.yellow('zeroshot run 123')} → Local run, no isolation
|
|
2637
|
-
${chalk.yellow('zeroshot run 123 --docker')} → Docker isolation, no PR
|
|
2638
|
-
${chalk.yellow('zeroshot run 123 --worktree')} → Git worktree isolation, no PR
|
|
2639
|
-
${chalk.yellow('zeroshot run 123 --pr')} → Worktree + PR (human reviews)
|
|
2640
|
-
${chalk.yellow('zeroshot run 123 --ship')} → Worktree + PR + auto-merge (full automation)
|
|
2641
|
-
${chalk.yellow('zeroshot task run')} → Single-agent background task (simpler, faster)
|
|
2642
|
-
|
|
2643
|
-
Shell completion:
|
|
2644
|
-
${chalk.dim('zeroshot --completion >> ~/.bashrc && source ~/.bashrc')}
|
|
2597
|
+
${chalk.cyan('zeroshot')} Run guided setup or show help
|
|
2598
|
+
${chalk.cyan('zeroshot run "Add tests"')} Start an explicit software-change run
|
|
2599
|
+
${chalk.cyan('zeroshot list')} List tasks and clusters
|
|
2600
|
+
${chalk.cyan('zeroshot logs <id> -f')} Follow a run
|
|
2645
2601
|
`
|
|
2646
2602
|
);
|
|
2647
2603
|
|
|
@@ -2649,20 +2605,34 @@ Shell completion:
|
|
|
2649
2605
|
|
|
2650
2606
|
program
|
|
2651
2607
|
.command('run <input>')
|
|
2608
|
+
.helpGroup('Start:')
|
|
2652
2609
|
.description(
|
|
2653
2610
|
'Start a multi-agent cluster (GitHub issue, markdown file, plain text, or "-" for stdin)'
|
|
2654
2611
|
)
|
|
2612
|
+
.optionsGroup('Input:')
|
|
2655
2613
|
.option('--config <file>', 'Path to cluster config JSON (default: conductor-bootstrap)')
|
|
2614
|
+
.option('-G, --github', 'Force GitHub as issue source')
|
|
2615
|
+
.option('-L, --gitlab', 'Force GitLab as issue source')
|
|
2616
|
+
.option('-J, --jira', 'Force Jira as issue source')
|
|
2617
|
+
.option('-D, --devops', 'Force Azure DevOps as issue source')
|
|
2618
|
+
.option('-N, --linear', 'Force Linear as issue source')
|
|
2619
|
+
.optionsGroup('Isolation:')
|
|
2656
2620
|
.option('--docker', 'Run cluster inside Docker container (full isolation)')
|
|
2657
2621
|
.option('--worktree', 'Use git worktree for isolation (lightweight, no Docker required)')
|
|
2622
|
+
.addOption(
|
|
2623
|
+
new Option('--no-isolation', 'Run in the current checkout without isolation').default(undefined)
|
|
2624
|
+
)
|
|
2658
2625
|
.option(
|
|
2659
2626
|
'--docker-image <image>',
|
|
2660
2627
|
'Docker image for --docker mode (default: zeroshot-cluster-base)'
|
|
2661
2628
|
)
|
|
2629
|
+
.option('--mount <spec...>', 'Add Docker mount (host:container[:ro]). Repeatable.')
|
|
2630
|
+
.option('--no-mounts', 'Disable all Docker credential mounts')
|
|
2662
2631
|
.option(
|
|
2663
|
-
'--
|
|
2664
|
-
'
|
|
2632
|
+
'--container-home <path>',
|
|
2633
|
+
'Container home directory for $HOME expansion (default: /root)'
|
|
2665
2634
|
)
|
|
2635
|
+
.optionsGroup('Delivery:')
|
|
2666
2636
|
.option(
|
|
2667
2637
|
'--pr',
|
|
2668
2638
|
'Create PR for human review (uses worktree isolation by default, use --docker for Docker). Never auto-merges itself; a repo-side branch-protection auto-merge rule or merge queue may still merge the PR independently of zeroshot.'
|
|
@@ -2677,26 +2647,21 @@ program
|
|
|
2677
2647
|
'--close-issue <mode>',
|
|
2678
2648
|
'When to close issue after merge: auto|always|never (default: from .zeroshot/settings.json or never)'
|
|
2679
2649
|
)
|
|
2680
|
-
.
|
|
2650
|
+
.optionsGroup('Provider:')
|
|
2681
2651
|
.option('--provider <provider>', `Override all agents to use a provider (${PROVIDER_CHOICES})`)
|
|
2682
2652
|
.option('--model <model>', 'Override all agent models (provider-specific model id)')
|
|
2653
|
+
.optionsGroup('Runtime:')
|
|
2654
|
+
.option(
|
|
2655
|
+
'--strict-schema',
|
|
2656
|
+
'Enforce JSON schema via CLI (no live streaming). Default: live streaming with local validation'
|
|
2657
|
+
)
|
|
2658
|
+
.option('--workers <n>', 'Max sub-agents for worker to spawn in parallel', parseInt)
|
|
2683
2659
|
.option(
|
|
2684
2660
|
'--sim <mode>',
|
|
2685
2661
|
'Token-free simulation gate for templates (off|fast|deep). Default: fast',
|
|
2686
2662
|
'fast'
|
|
2687
2663
|
)
|
|
2688
|
-
.option('-G, --github', 'Force GitHub as issue source')
|
|
2689
|
-
.option('-L, --gitlab', 'Force GitLab as issue source')
|
|
2690
|
-
.option('-J, --jira', 'Force Jira as issue source')
|
|
2691
|
-
.option('-D, --devops', 'Force Azure DevOps as issue source')
|
|
2692
|
-
.option('-N, --linear', 'Force Linear as issue source')
|
|
2693
2664
|
.option('-d, --detach', 'Run in background (default: attach to first agent)')
|
|
2694
|
-
.option('--mount <spec...>', 'Add Docker mount (host:container[:ro]). Repeatable.')
|
|
2695
|
-
.option('--no-mounts', 'Disable all Docker credential mounts')
|
|
2696
|
-
.option(
|
|
2697
|
-
'--container-home <path>',
|
|
2698
|
-
'Container home directory for $HOME expansion (default: /root)'
|
|
2699
|
-
)
|
|
2700
2665
|
.addHelpText(
|
|
2701
2666
|
'after',
|
|
2702
2667
|
`
|
|
@@ -2735,10 +2700,6 @@ Force provider flags: -G (GitHub), -L (GitLab), -J (Jira), -D (DevOps), -N (Line
|
|
|
2735
2700
|
)
|
|
2736
2701
|
.action(async (inputArg, options) => {
|
|
2737
2702
|
try {
|
|
2738
|
-
// Normalize options (--ship → --pr → --worktree flags)
|
|
2739
|
-
normalizeRunOptions(options);
|
|
2740
|
-
|
|
2741
|
-
// Determine force provider from CLI flags
|
|
2742
2703
|
let forceProvider = null;
|
|
2743
2704
|
if (options.github) forceProvider = 'github';
|
|
2744
2705
|
else if (options.gitlab) forceProvider = 'gitlab';
|
|
@@ -2746,7 +2707,6 @@ Force provider flags: -G (GitHub), -L (GitLab), -J (Jira), -D (DevOps), -N (Line
|
|
|
2746
2707
|
else if (options.devops) forceProvider = 'azure-devops';
|
|
2747
2708
|
else if (options.linear) forceProvider = 'linear';
|
|
2748
2709
|
|
|
2749
|
-
// Stdin input ('-'): read task body from stdin to avoid shell-quoting breakage
|
|
2750
2710
|
let stdinText;
|
|
2751
2711
|
if (isStdinInput(inputArg)) {
|
|
2752
2712
|
if (process.env.ZEROSHOT_DAEMON === '1') {
|
|
@@ -2769,24 +2729,29 @@ Force provider flags: -G (GitHub), -L (GitLab), -J (Jira), -D (DevOps), -N (Line
|
|
|
2769
2729
|
}
|
|
2770
2730
|
}
|
|
2771
2731
|
|
|
2772
|
-
// Auto-detect input type
|
|
2773
2732
|
const settings = loadSettings();
|
|
2774
2733
|
const input =
|
|
2775
2734
|
stdinText !== undefined
|
|
2776
2735
|
? buildTextInput(stdinText)
|
|
2777
2736
|
: detectRunInput(inputArg, settings, forceProvider);
|
|
2778
2737
|
const providerOverride = resolveProviderOverride(options);
|
|
2738
|
+
const effectiveRunPlan = resolveEffectiveRunPlan(options, settings);
|
|
2739
|
+
const effectiveOptions = applyRunPlanToOptions(options, effectiveRunPlan);
|
|
2740
|
+
|
|
2741
|
+
await runClusterPreflight({
|
|
2742
|
+
input,
|
|
2743
|
+
options: effectiveOptions,
|
|
2744
|
+
providerOverride,
|
|
2745
|
+
settings,
|
|
2746
|
+
forceProvider,
|
|
2747
|
+
plan: effectiveRunPlan,
|
|
2748
|
+
});
|
|
2779
2749
|
|
|
2780
|
-
|
|
2781
|
-
await runClusterPreflight({ input, options, providerOverride, settings, forceProvider });
|
|
2782
|
-
|
|
2783
|
-
// Secondary preflight: token-free template simulation/validation
|
|
2784
|
-
const simMode = String(options.sim || 'fast').toLowerCase();
|
|
2750
|
+
const simMode = String(effectiveOptions.sim || 'fast').toLowerCase();
|
|
2785
2751
|
if (simMode !== 'off') {
|
|
2786
2752
|
const { validateTemplates } = require('../src/template-validation');
|
|
2787
2753
|
const templatesDir = path.join(PACKAGE_ROOT, 'cluster-templates');
|
|
2788
|
-
const
|
|
2789
|
-
const report = await validateTemplates({ templatesDir, deep });
|
|
2754
|
+
const report = await validateTemplates({ templatesDir, deep: simMode === 'deep' });
|
|
2790
2755
|
if (!report.valid) {
|
|
2791
2756
|
console.error('\n' + '='.repeat(60));
|
|
2792
2757
|
console.error(`TEMPLATE VALIDATION FAILED (sim=${simMode})`);
|
|
@@ -2795,9 +2760,7 @@ Force provider flags: -G (GitHub), -L (GitLab), -J (Jira), -D (DevOps), -N (Line
|
|
|
2795
2760
|
if (result.valid) continue;
|
|
2796
2761
|
const rel = path.relative(process.cwd(), filePath);
|
|
2797
2762
|
console.error(`\n❌ ${rel}`);
|
|
2798
|
-
for (const err of result.errors) {
|
|
2799
|
-
console.error(` ERROR: ${err}`);
|
|
2800
|
-
}
|
|
2763
|
+
for (const err of result.errors) console.error(` ERROR: ${err}`);
|
|
2801
2764
|
}
|
|
2802
2765
|
console.error('\nFix template errors before running to avoid token burn.\n');
|
|
2803
2766
|
process.exit(1);
|
|
@@ -2805,67 +2768,42 @@ Force provider flags: -G (GitHub), -L (GitLab), -J (Jira), -D (DevOps), -N (Line
|
|
|
2805
2768
|
}
|
|
2806
2769
|
|
|
2807
2770
|
const { generateName } = require('../src/name-generator');
|
|
2808
|
-
|
|
2809
|
-
if (shouldRunDetached(options)) {
|
|
2771
|
+
if (shouldRunDetached(effectiveOptions)) {
|
|
2810
2772
|
const clusterId = generateName('cluster');
|
|
2811
|
-
await spawnDetachedCluster(
|
|
2773
|
+
await spawnDetachedCluster(effectiveOptions, effectiveRunPlan, clusterId, stdinText);
|
|
2812
2774
|
return;
|
|
2813
2775
|
}
|
|
2814
2776
|
|
|
2815
2777
|
const clusterId = resolveClusterId(generateName);
|
|
2816
|
-
|
|
2817
|
-
// === LOAD CONFIG ===
|
|
2818
|
-
// Priority: CLI --config > settings.defaultConfig
|
|
2819
|
-
const configName = resolveConfigName(options, settings);
|
|
2778
|
+
const configName = resolveConfigName(effectiveOptions, settings);
|
|
2820
2779
|
const configPath = resolveConfigPath(configName);
|
|
2821
2780
|
const orchestrator = await getOrchestrator();
|
|
2822
2781
|
const config = loadClusterConfig(orchestrator, configPath, settings, providerOverride);
|
|
2823
2782
|
trackActiveCluster(clusterId, orchestrator);
|
|
2824
|
-
printForegroundStartInfo(
|
|
2783
|
+
printForegroundStartInfo(effectiveRunPlan, clusterId, configName);
|
|
2825
2784
|
|
|
2826
|
-
const strictSchema = resolveStrictSchema(
|
|
2785
|
+
const strictSchema = resolveStrictSchema(effectiveOptions, settings);
|
|
2827
2786
|
applyStrictSchema(config, strictSchema);
|
|
2828
|
-
|
|
2829
|
-
const modelOverride = resolveModelOverride(options);
|
|
2787
|
+
const modelOverride = resolveModelOverride(effectiveOptions);
|
|
2830
2788
|
applyModelOverrideToConfig(config, modelOverride, providerOverride, settings);
|
|
2831
2789
|
|
|
2832
|
-
|
|
2790
|
+
const startArgs = {
|
|
2791
|
+
orchestrator,
|
|
2792
|
+
config,
|
|
2793
|
+
settings,
|
|
2794
|
+
providerOverride,
|
|
2795
|
+
modelOverride,
|
|
2796
|
+
forceProvider,
|
|
2797
|
+
clusterId,
|
|
2798
|
+
options: effectiveOptions,
|
|
2799
|
+
};
|
|
2800
|
+
let cluster;
|
|
2833
2801
|
if (input.text) {
|
|
2834
|
-
cluster = await startClusterFromText({
|
|
2835
|
-
orchestrator,
|
|
2836
|
-
text: input.text,
|
|
2837
|
-
config,
|
|
2838
|
-
settings,
|
|
2839
|
-
providerOverride,
|
|
2840
|
-
modelOverride,
|
|
2841
|
-
forceProvider,
|
|
2842
|
-
clusterId,
|
|
2843
|
-
options,
|
|
2844
|
-
});
|
|
2802
|
+
cluster = await startClusterFromText({ ...startArgs, text: input.text });
|
|
2845
2803
|
} else if (input.issue) {
|
|
2846
|
-
cluster = await startClusterFromIssue({
|
|
2847
|
-
orchestrator,
|
|
2848
|
-
issue: input.issue,
|
|
2849
|
-
config,
|
|
2850
|
-
settings,
|
|
2851
|
-
providerOverride,
|
|
2852
|
-
modelOverride,
|
|
2853
|
-
forceProvider,
|
|
2854
|
-
clusterId,
|
|
2855
|
-
options,
|
|
2856
|
-
});
|
|
2804
|
+
cluster = await startClusterFromIssue({ ...startArgs, issue: input.issue });
|
|
2857
2805
|
} else if (input.file) {
|
|
2858
|
-
cluster = await startClusterFromFile({
|
|
2859
|
-
orchestrator,
|
|
2860
|
-
file: input.file,
|
|
2861
|
-
config,
|
|
2862
|
-
settings,
|
|
2863
|
-
providerOverride,
|
|
2864
|
-
modelOverride,
|
|
2865
|
-
forceProvider,
|
|
2866
|
-
clusterId,
|
|
2867
|
-
options,
|
|
2868
|
-
});
|
|
2806
|
+
cluster = await startClusterFromFile({ ...startArgs, file: input.file });
|
|
2869
2807
|
} else {
|
|
2870
2808
|
throw new Error(
|
|
2871
2809
|
`Invalid run input for cluster ${clusterId}: expected text, issue, or file`
|
|
@@ -2873,15 +2811,12 @@ Force provider flags: -G (GitHub), -L (GitLab), -J (Jira), -D (DevOps), -N (Line
|
|
|
2873
2811
|
}
|
|
2874
2812
|
|
|
2875
2813
|
if (!process.env.ZEROSHOT_DAEMON) {
|
|
2876
|
-
await streamClusterInForeground(cluster, orchestrator, clusterId,
|
|
2814
|
+
await streamClusterInForeground(cluster, orchestrator, clusterId, effectiveRunPlan);
|
|
2877
2815
|
orchestrator.close();
|
|
2878
2816
|
}
|
|
2879
|
-
|
|
2880
2817
|
setupDaemonCleanup(orchestrator, clusterId);
|
|
2881
2818
|
} catch (error) {
|
|
2882
2819
|
if (error.code === 'DUPLICATE_CLUSTER') {
|
|
2883
|
-
// Benign guard rejection, not a crash: nothing was allocated, so there is
|
|
2884
|
-
// nothing to roll back. No stack trace, no "Error:" framing.
|
|
2885
2820
|
if (process.env.ZEROSHOT_DAEMON && process.env.ZEROSHOT_CLUSTER_ID) {
|
|
2886
2821
|
try {
|
|
2887
2822
|
await removeDetachedSetupCluster({
|
|
@@ -2919,10 +2854,14 @@ Force provider flags: -G (GitHub), -L (GitLab), -J (Jira), -D (DevOps), -N (Line
|
|
|
2919
2854
|
|
|
2920
2855
|
// === TASK COMMANDS ===
|
|
2921
2856
|
// Task run - single-agent background task
|
|
2922
|
-
const taskCmd = program
|
|
2857
|
+
const taskCmd = program
|
|
2858
|
+
.command('task')
|
|
2859
|
+
.helpGroup('Automation:')
|
|
2860
|
+
.description('Single-agent task management');
|
|
2923
2861
|
|
|
2924
2862
|
const cmdproofCmd = program
|
|
2925
2863
|
.command('cmdproof')
|
|
2864
|
+
.helpGroup('Configure:')
|
|
2926
2865
|
.description('Run configured cmdproof command proofs');
|
|
2927
2866
|
|
|
2928
2867
|
for (const mode of ['prove', 'verify', 'check']) {
|
|
@@ -3041,6 +2980,7 @@ taskCmd
|
|
|
3041
2980
|
// List command - unified (shows both tasks and clusters)
|
|
3042
2981
|
program
|
|
3043
2982
|
.command('list')
|
|
2983
|
+
.helpGroup('Observe:')
|
|
3044
2984
|
.alias('ls')
|
|
3045
2985
|
.description('List all tasks and clusters')
|
|
3046
2986
|
.option('-s, --status <status>', 'Filter tasks by status (running, completed, failed)')
|
|
@@ -3055,7 +2995,7 @@ program
|
|
|
3055
2995
|
const { listTasks, getTasksData } = await import('../task-lib/commands/list.js');
|
|
3056
2996
|
|
|
3057
2997
|
if (options.json) {
|
|
3058
|
-
const tasks = await
|
|
2998
|
+
const tasks = await getTasksData(options);
|
|
3059
2999
|
printListJson(enrichedClusters, tasks);
|
|
3060
3000
|
return;
|
|
3061
3001
|
}
|
|
@@ -3065,7 +3005,11 @@ program
|
|
|
3065
3005
|
console.log(chalk.bold('\n=== Tasks ==='));
|
|
3066
3006
|
await listTasks(options);
|
|
3067
3007
|
} catch (error) {
|
|
3068
|
-
|
|
3008
|
+
if (options.json) {
|
|
3009
|
+
printJsonError(error);
|
|
3010
|
+
} else {
|
|
3011
|
+
console.error('Error listing:', error.message);
|
|
3012
|
+
}
|
|
3069
3013
|
process.exit(1);
|
|
3070
3014
|
}
|
|
3071
3015
|
});
|
|
@@ -3073,6 +3017,7 @@ program
|
|
|
3073
3017
|
// Status command - smart (works for both tasks and clusters)
|
|
3074
3018
|
program
|
|
3075
3019
|
.command('status <id>')
|
|
3020
|
+
.helpGroup('Observe:')
|
|
3076
3021
|
.description('Get detailed status of a task or cluster')
|
|
3077
3022
|
.option('--json', 'Output as JSON')
|
|
3078
3023
|
.action(async (id, options) => {
|
|
@@ -3100,7 +3045,7 @@ program
|
|
|
3100
3045
|
await showTaskStatus(id, options);
|
|
3101
3046
|
} catch (error) {
|
|
3102
3047
|
if (options.json) {
|
|
3103
|
-
|
|
3048
|
+
printJsonError(error);
|
|
3104
3049
|
} else {
|
|
3105
3050
|
console.error('Error getting status:', error.message);
|
|
3106
3051
|
}
|
|
@@ -3110,6 +3055,7 @@ program
|
|
|
3110
3055
|
|
|
3111
3056
|
program
|
|
3112
3057
|
.command('inspect <id>')
|
|
3058
|
+
.helpGroup('Observe:')
|
|
3113
3059
|
.description('Inspect live process activity for a task or cluster')
|
|
3114
3060
|
.option('--json', 'Output as JSON')
|
|
3115
3061
|
.option('--sample-ms <ms>', 'Sampling period for process activity checks', '1000')
|
|
@@ -3129,6 +3075,7 @@ program
|
|
|
3129
3075
|
// Logs command - smart (works for both tasks and clusters)
|
|
3130
3076
|
program
|
|
3131
3077
|
.command('logs [id]')
|
|
3078
|
+
.helpGroup('Observe:')
|
|
3132
3079
|
.description('View logs (omit ID for all clusters)')
|
|
3133
3080
|
.option('-f, --follow', 'Follow logs in real-time (stream output like tail -f)')
|
|
3134
3081
|
.option('-n, --limit <number>', 'Number of recent messages to show (default: 50)', '50')
|
|
@@ -3161,6 +3108,7 @@ program
|
|
|
3161
3108
|
// Stop command (cluster-only)
|
|
3162
3109
|
program
|
|
3163
3110
|
.command('stop <cluster-id>')
|
|
3111
|
+
.helpGroup('Control:')
|
|
3164
3112
|
.description('Stop a cluster gracefully')
|
|
3165
3113
|
.action(async (clusterId) => {
|
|
3166
3114
|
try {
|
|
@@ -3176,6 +3124,7 @@ program
|
|
|
3176
3124
|
// Kill command - smart (works for both tasks and clusters)
|
|
3177
3125
|
program
|
|
3178
3126
|
.command('kill <id>')
|
|
3127
|
+
.helpGroup('Control:')
|
|
3179
3128
|
.description('Kill a task or cluster')
|
|
3180
3129
|
.action(async (id) => {
|
|
3181
3130
|
try {
|
|
@@ -3205,6 +3154,7 @@ program
|
|
|
3205
3154
|
// Attach command - tmux-style attach to running task or cluster agent
|
|
3206
3155
|
program
|
|
3207
3156
|
.command('attach [id]')
|
|
3157
|
+
.helpGroup('Observe:')
|
|
3208
3158
|
.description('Attach to a running task or cluster agent (Ctrl+C to detach, task keeps running)')
|
|
3209
3159
|
.option('-a, --agent <name>', 'Attach to specific agent in cluster (required for clusters)')
|
|
3210
3160
|
.addHelpText(
|
|
@@ -3257,6 +3207,7 @@ Key bindings:
|
|
|
3257
3207
|
// Kill-all command - kills all running tasks and clusters
|
|
3258
3208
|
program
|
|
3259
3209
|
.command('kill-all')
|
|
3210
|
+
.helpGroup('Maintenance:')
|
|
3260
3211
|
.description('Kill all running tasks and clusters')
|
|
3261
3212
|
.option('-y, --yes', 'Skip confirmation')
|
|
3262
3213
|
.action(async (options) => {
|
|
@@ -3329,6 +3280,7 @@ program
|
|
|
3329
3280
|
// Export command (cluster-only)
|
|
3330
3281
|
program
|
|
3331
3282
|
.command('export <cluster-id>')
|
|
3283
|
+
.helpGroup('Maintenance:')
|
|
3332
3284
|
.description('Export cluster conversation')
|
|
3333
3285
|
.option('-f, --format <format>', 'Export format: json, markdown, html', 'html')
|
|
3334
3286
|
.option('-o, --output <file>', 'Output file (auto-generated for html)')
|
|
@@ -3443,6 +3395,7 @@ program
|
|
|
3443
3395
|
// Resume task or cluster
|
|
3444
3396
|
program
|
|
3445
3397
|
.command('resume <id> [prompt]')
|
|
3398
|
+
.helpGroup('Control:')
|
|
3446
3399
|
.description('Resume a failed task or cluster')
|
|
3447
3400
|
.option('-d, --detach', 'Resume in background (daemon mode)')
|
|
3448
3401
|
.action(async (id, prompt, options) => {
|
|
@@ -3686,6 +3639,7 @@ program
|
|
|
3686
3639
|
// Finish cluster - convert to single-agent completion task
|
|
3687
3640
|
program
|
|
3688
3641
|
.command('finish <id>')
|
|
3642
|
+
.helpGroup('Control:')
|
|
3689
3643
|
.description('Take existing cluster and create completion-focused task (creates PR and merges)')
|
|
3690
3644
|
.option('-y, --yes', 'Skip confirmation if cluster is running')
|
|
3691
3645
|
.action(async (id, options) => {
|
|
@@ -3724,6 +3678,7 @@ program
|
|
|
3724
3678
|
// Clean tasks
|
|
3725
3679
|
program
|
|
3726
3680
|
.command('clean')
|
|
3681
|
+
.helpGroup('Maintenance:')
|
|
3727
3682
|
.description('Remove old task records and logs')
|
|
3728
3683
|
.option('-a, --all', 'Remove all tasks')
|
|
3729
3684
|
.option('-c, --completed', 'Remove completed tasks')
|
|
@@ -3791,6 +3746,7 @@ async function detectAndReportCorruptedClusters(dryRun) {
|
|
|
3791
3746
|
|
|
3792
3747
|
program
|
|
3793
3748
|
.command('gc')
|
|
3749
|
+
.helpGroup('Maintenance:')
|
|
3794
3750
|
.description('Clean up orphaned worktree directories and stale database files')
|
|
3795
3751
|
.option('--dry-run', 'Show what would be removed without deleting')
|
|
3796
3752
|
.action(async (options) => {
|
|
@@ -3803,10 +3759,11 @@ program
|
|
|
3803
3759
|
}
|
|
3804
3760
|
});
|
|
3805
3761
|
|
|
3806
|
-
// Purge all runs (clusters + tasks)
|
|
3762
|
+
// Purge all runs (clusters + tasks).
|
|
3807
3763
|
program
|
|
3808
3764
|
.command('purge')
|
|
3809
|
-
.
|
|
3765
|
+
.helpGroup('Maintenance:')
|
|
3766
|
+
.description('Kill all running tasks and clusters, then delete all Zeroshot run data')
|
|
3810
3767
|
.option('-y, --yes', 'Skip confirmation')
|
|
3811
3768
|
.action(async (options) => {
|
|
3812
3769
|
try {
|
|
@@ -3842,6 +3799,7 @@ program
|
|
|
3842
3799
|
// Schedule a task
|
|
3843
3800
|
program
|
|
3844
3801
|
.command('schedule <prompt>')
|
|
3802
|
+
.helpGroup('Automation:')
|
|
3845
3803
|
.description('Create a recurring scheduled task')
|
|
3846
3804
|
.option('-e, --every <interval>', 'Interval (e.g., "1h", "30m", "1d")')
|
|
3847
3805
|
.option('--cron <expression>', 'Cron expression')
|
|
@@ -3859,6 +3817,7 @@ program
|
|
|
3859
3817
|
// List schedules
|
|
3860
3818
|
program
|
|
3861
3819
|
.command('schedules')
|
|
3820
|
+
.helpGroup('Automation:')
|
|
3862
3821
|
.description('List all scheduled tasks')
|
|
3863
3822
|
.action(async () => {
|
|
3864
3823
|
try {
|
|
@@ -3873,6 +3832,7 @@ program
|
|
|
3873
3832
|
// Unschedule a task
|
|
3874
3833
|
program
|
|
3875
3834
|
.command('unschedule <scheduleId>')
|
|
3835
|
+
.helpGroup('Automation:')
|
|
3876
3836
|
.description('Remove a scheduled task')
|
|
3877
3837
|
.action(async (scheduleId) => {
|
|
3878
3838
|
try {
|
|
@@ -3887,6 +3847,7 @@ program
|
|
|
3887
3847
|
// Scheduler daemon management
|
|
3888
3848
|
program
|
|
3889
3849
|
.command('scheduler <action>')
|
|
3850
|
+
.helpGroup('Automation:')
|
|
3890
3851
|
.description('Manage scheduler daemon (start, stop, status, logs)')
|
|
3891
3852
|
.action(async (action) => {
|
|
3892
3853
|
try {
|
|
@@ -3900,7 +3861,7 @@ program
|
|
|
3900
3861
|
|
|
3901
3862
|
// Get log path (machine-readable)
|
|
3902
3863
|
program
|
|
3903
|
-
.command('get-log-path <taskId>')
|
|
3864
|
+
.command('get-log-path <taskId>', { hidden: true })
|
|
3904
3865
|
.description('Output log file path for a task (machine-readable)')
|
|
3905
3866
|
.action(async (taskId) => {
|
|
3906
3867
|
try {
|
|
@@ -3914,7 +3875,7 @@ program
|
|
|
3914
3875
|
|
|
3915
3876
|
// Resolve the durable task ownership receipt for an in-flight detached launch.
|
|
3916
3877
|
program
|
|
3917
|
-
.command('get-task-id-by-spawn-token <token>')
|
|
3878
|
+
.command('get-task-id-by-spawn-token <token>', { hidden: true })
|
|
3918
3879
|
.description('Output task ID for an internal spawn ownership token (machine-readable)')
|
|
3919
3880
|
.action(async (token) => {
|
|
3920
3881
|
try {
|
|
@@ -3934,10 +3895,13 @@ function failTuiUnavailable() {
|
|
|
3934
3895
|
process.exit(1);
|
|
3935
3896
|
}
|
|
3936
3897
|
|
|
3937
|
-
program
|
|
3898
|
+
program
|
|
3899
|
+
.command('watch', { hidden: true })
|
|
3900
|
+
.description('TUI unavailable in this release')
|
|
3901
|
+
.action(failTuiUnavailable);
|
|
3938
3902
|
|
|
3939
3903
|
program
|
|
3940
|
-
.command('tui')
|
|
3904
|
+
.command('tui', { hidden: true })
|
|
3941
3905
|
.description('TUI unavailable in this release')
|
|
3942
3906
|
.allowExcessArguments(true)
|
|
3943
3907
|
.allowUnknownOption(true)
|
|
@@ -3945,7 +3909,7 @@ program
|
|
|
3945
3909
|
|
|
3946
3910
|
function registerTuiEntrypoint(commandName, providerName) {
|
|
3947
3911
|
program
|
|
3948
|
-
.command(commandName)
|
|
3912
|
+
.command(commandName, { hidden: true })
|
|
3949
3913
|
.description(`TUI unavailable in this release (provider: ${providerName})`)
|
|
3950
3914
|
.allowExcessArguments(true)
|
|
3951
3915
|
.allowUnknownOption(true)
|
|
@@ -3957,12 +3921,11 @@ for (const providerName of VALID_PROVIDERS) {
|
|
|
3957
3921
|
}
|
|
3958
3922
|
|
|
3959
3923
|
// Settings management
|
|
3960
|
-
const settingsCmd = program
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
const UNREGISTERED_HOSTED_COMMAND_NAMES = new Set(['target', 'capsule']);
|
|
3924
|
+
const settingsCmd = program
|
|
3925
|
+
.command('settings')
|
|
3926
|
+
.helpGroup('Configure:')
|
|
3927
|
+
.description('Manage zeroshot settings');
|
|
3928
|
+
const INTERNAL_SETTINGS_KEYS = new Set(['lastUpdateCheckClaim', 'setupVersion', '_targets']);
|
|
3966
3929
|
|
|
3967
3930
|
function visibleSettingKeys() {
|
|
3968
3931
|
return Object.keys(DEFAULT_SETTINGS).filter((key) => !INTERNAL_SETTINGS_KEYS.has(key));
|
|
@@ -4496,7 +4459,10 @@ settingsCmd.action(() => {
|
|
|
4496
4459
|
|
|
4497
4460
|
// Hosted target commands intentionally are not registered on the stable CLI.
|
|
4498
4461
|
// Providers management
|
|
4499
|
-
const providersCmd = program
|
|
4462
|
+
const providersCmd = program
|
|
4463
|
+
.command('providers')
|
|
4464
|
+
.helpGroup('Configure:')
|
|
4465
|
+
.description('Manage AI providers');
|
|
4500
4466
|
providersCmd.action(async () => {
|
|
4501
4467
|
await providersCommand();
|
|
4502
4468
|
});
|
|
@@ -4516,7 +4482,14 @@ providersCmd
|
|
|
4516
4482
|
});
|
|
4517
4483
|
|
|
4518
4484
|
// Setup wizard (read-only facts + setup contract; apply/undo/TTY wizard land separately)
|
|
4519
|
-
const setupCmd = program
|
|
4485
|
+
const setupCmd = program
|
|
4486
|
+
.command('setup')
|
|
4487
|
+
.helpGroup('Start:')
|
|
4488
|
+
.description('Setup and configuration wizard');
|
|
4489
|
+
setupCmd.action(async () => {
|
|
4490
|
+
const result = await runSetupWizard();
|
|
4491
|
+
process.exitCode = result.exitCode;
|
|
4492
|
+
});
|
|
4520
4493
|
setupCmd
|
|
4521
4494
|
.command('plan')
|
|
4522
4495
|
.description('Show read-only setup facts and proposed contract (no writes)')
|
|
@@ -4580,6 +4553,7 @@ setupCmd
|
|
|
4580
4553
|
// Update command
|
|
4581
4554
|
program
|
|
4582
4555
|
.command('update')
|
|
4556
|
+
.helpGroup('Maintenance:')
|
|
4583
4557
|
.description('Update zeroshot to the latest version')
|
|
4584
4558
|
.option('--check', 'Check for updates without installing')
|
|
4585
4559
|
.action(async (options) => {
|
|
@@ -4620,7 +4594,10 @@ program
|
|
|
4620
4594
|
});
|
|
4621
4595
|
|
|
4622
4596
|
// Config visualization commands
|
|
4623
|
-
const configCmd = program
|
|
4597
|
+
const configCmd = program
|
|
4598
|
+
.command('config')
|
|
4599
|
+
.helpGroup('Configure:')
|
|
4600
|
+
.description('Manage and visualize cluster configs');
|
|
4624
4601
|
|
|
4625
4602
|
configCmd
|
|
4626
4603
|
.command('list')
|
|
@@ -4731,7 +4708,10 @@ configCmd
|
|
|
4731
4708
|
});
|
|
4732
4709
|
|
|
4733
4710
|
// Agent library commands
|
|
4734
|
-
const agentsCmd = program
|
|
4711
|
+
const agentsCmd = program
|
|
4712
|
+
.command('agents')
|
|
4713
|
+
.helpGroup('Configure:')
|
|
4714
|
+
.description('View available agent definitions');
|
|
4735
4715
|
|
|
4736
4716
|
agentsCmd
|
|
4737
4717
|
.command('list')
|
|
@@ -6006,14 +5986,15 @@ function formatAgentOutput(msg, prefix) {
|
|
|
6006
5986
|
}
|
|
6007
5987
|
|
|
6008
5988
|
const NORMAL_MESSAGE_HANDLERS = {
|
|
6009
|
-
AGENT_LIFECYCLE: ({ msg, prefix }) => formatAgentLifecycle(msg, prefix),
|
|
6010
|
-
AGENT_ERROR: ({ msg, prefix, timestamp }) =>
|
|
5989
|
+
AGENT_LIFECYCLE: ({ msg, prefix }) => formatAgentLifecycle(msg, prefix, safePrint),
|
|
5990
|
+
AGENT_ERROR: ({ msg, prefix, timestamp }) =>
|
|
5991
|
+
formatAgentErrorNormal(msg, prefix, timestamp, safePrint),
|
|
6011
5992
|
ISSUE_OPENED: ({ msg, prefix, timestamp }) =>
|
|
6012
|
-
formatIssueOpenedNormal(msg, prefix, timestamp, shownNewTaskForCluster),
|
|
5993
|
+
formatIssueOpenedNormal(msg, prefix, timestamp, shownNewTaskForCluster, safePrint),
|
|
6013
5994
|
IMPLEMENTATION_READY: ({ msg, prefix, timestamp }) =>
|
|
6014
|
-
formatImplementationReadyNormal(msg, prefix, timestamp),
|
|
5995
|
+
formatImplementationReadyNormal(msg, prefix, timestamp, safePrint),
|
|
6015
5996
|
VALIDATION_RESULT: ({ msg, prefix, timestamp }) =>
|
|
6016
|
-
formatValidationResultNormal(msg, prefix, timestamp),
|
|
5997
|
+
formatValidationResultNormal(msg, prefix, timestamp, safePrint),
|
|
6017
5998
|
PR_CREATED: ({ msg, prefix, timestamp }) => formatPrCreated(msg, prefix, timestamp, safePrint),
|
|
6018
5999
|
CLUSTER_COMPLETE: ({ msg, prefix, timestamp }) =>
|
|
6019
6000
|
formatClusterComplete(msg, prefix, timestamp, safePrint),
|
|
@@ -6030,8 +6011,7 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
|
|
|
6030
6011
|
|
|
6031
6012
|
// Watch mode: delegate to watch mode formatter
|
|
6032
6013
|
if (watchMode) {
|
|
6033
|
-
|
|
6034
|
-
formatWatchMode(msg, clusterPrefix);
|
|
6014
|
+
formatWatchMode(msg, isActive, liveOutputWriter);
|
|
6035
6015
|
return;
|
|
6036
6016
|
}
|
|
6037
6017
|
|
|
@@ -6055,14 +6035,36 @@ function isStartupUpdateEligible(argv, options = {}) {
|
|
|
6055
6035
|
});
|
|
6056
6036
|
}
|
|
6057
6037
|
|
|
6058
|
-
function
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6038
|
+
function shouldRunInitialSetup({ args, stdin, stdout, settingsExist }) {
|
|
6039
|
+
return (
|
|
6040
|
+
args.length === 0 &&
|
|
6041
|
+
!args.includes('--quiet') &&
|
|
6042
|
+
!settingsExist &&
|
|
6043
|
+
stdin.isTTY === true &&
|
|
6044
|
+
stdout.isTTY === true
|
|
6045
|
+
);
|
|
6046
|
+
}
|
|
6064
6047
|
|
|
6065
|
-
|
|
6048
|
+
async function handleNoArgumentInvocation({
|
|
6049
|
+
args,
|
|
6050
|
+
stdin = process.stdin,
|
|
6051
|
+
stdout = process.stdout,
|
|
6052
|
+
settingsExist,
|
|
6053
|
+
runWizard = runSetupWizard,
|
|
6054
|
+
outputHelp = () => program.outputHelp(),
|
|
6055
|
+
setExitCode = (code) => {
|
|
6056
|
+
process.exitCode = code;
|
|
6057
|
+
},
|
|
6058
|
+
}) {
|
|
6059
|
+
if (args.length !== 0) return false;
|
|
6060
|
+
const hasSettings = settingsExist ?? settingsFileExists();
|
|
6061
|
+
if (shouldRunInitialSetup({ args, stdin, stdout, settingsExist: hasSettings })) {
|
|
6062
|
+
const result = await runWizard({ stdin, stdout });
|
|
6063
|
+
setExitCode(result.exitCode);
|
|
6064
|
+
} else {
|
|
6065
|
+
outputHelp();
|
|
6066
|
+
}
|
|
6067
|
+
return true;
|
|
6066
6068
|
}
|
|
6067
6069
|
|
|
6068
6070
|
// Main entry point
|
|
@@ -6092,14 +6094,8 @@ async function main() {
|
|
|
6092
6094
|
|
|
6093
6095
|
const args = startupArgs;
|
|
6094
6096
|
|
|
6095
|
-
if (args
|
|
6096
|
-
|
|
6097
|
-
return;
|
|
6098
|
-
}
|
|
6099
|
-
|
|
6100
|
-
// Preserve the default local run shorthand without treating hosted-only command
|
|
6101
|
-
// names as task input in the stable parser.
|
|
6102
|
-
applyDefaultCommand(args);
|
|
6097
|
+
if (await handleNoArgumentInvocation({ args })) return;
|
|
6098
|
+
setupCompletion(program);
|
|
6103
6099
|
|
|
6104
6100
|
program.parse();
|
|
6105
6101
|
}
|
|
@@ -6114,11 +6110,14 @@ if (require.main === module) {
|
|
|
6114
6110
|
|
|
6115
6111
|
module.exports = {
|
|
6116
6112
|
assertRequestedWebSearchCliAvailable,
|
|
6113
|
+
runClusterPreflight,
|
|
6117
6114
|
applyModelOverrideToConfig,
|
|
6118
6115
|
inspectAgentAttachment,
|
|
6119
6116
|
printAttachableAgentList,
|
|
6120
6117
|
renderRecentMessagesToTerminal,
|
|
6121
6118
|
isStartupUpdateEligible,
|
|
6119
|
+
handleNoArgumentInvocation,
|
|
6120
|
+
shouldRunInitialSetup,
|
|
6122
6121
|
resolveRunMode,
|
|
6123
6122
|
killRunningClusters,
|
|
6124
6123
|
};
|