@the-open-engine/zeroshot 6.31.2 → 6.32.0
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/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 +4 -3
- package/scripts/postinstall.js +54 -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
|
@@ -53,9 +53,9 @@ function formatAgentLifecycle(msg, prefix, print = console.log) {
|
|
|
53
53
|
* @returns {boolean} True if message was handled
|
|
54
54
|
*/
|
|
55
55
|
function formatAgentError(msg, prefix, timestamp, print = console.log) {
|
|
56
|
-
print('');
|
|
56
|
+
print('');
|
|
57
57
|
print(chalk.bold.red(`${'─'.repeat(60)}`));
|
|
58
|
-
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.red('
|
|
58
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.red('Error: agent failed')}`);
|
|
59
59
|
|
|
60
60
|
if (msg.content?.text) {
|
|
61
61
|
print(`${prefix} ${chalk.red(msg.content.text)}`);
|
|
@@ -64,12 +64,11 @@ function formatAgentError(msg, prefix, timestamp, print = console.log) {
|
|
|
64
64
|
if (msg.content?.data?.stack) {
|
|
65
65
|
const stackLines = msg.content.data.stack.split('\n').slice(0, 5);
|
|
66
66
|
for (const line of stackLines) {
|
|
67
|
-
if (line.trim()) {
|
|
68
|
-
print(`${prefix} ${chalk.dim(line)}`);
|
|
69
|
-
}
|
|
67
|
+
if (line.trim()) print(`${prefix} ${chalk.dim(line)}`);
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
70
|
|
|
71
|
+
print(`${prefix} Next: inspect the agent logs and retry after fixing the cause.`);
|
|
73
72
|
print(chalk.bold.red(`${'─'.repeat(60)}`));
|
|
74
73
|
return true;
|
|
75
74
|
}
|
|
@@ -90,9 +89,9 @@ function formatIssueOpened(msg, prefix, timestamp, shownNewTaskForCluster, print
|
|
|
90
89
|
}
|
|
91
90
|
shownNewTaskForCluster.add(msg.cluster_id);
|
|
92
91
|
|
|
93
|
-
print('');
|
|
92
|
+
print('');
|
|
94
93
|
print(chalk.bold.blue(`${'─'.repeat(60)}`));
|
|
95
|
-
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.blue('📋
|
|
94
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.blue('📋 New task')}`);
|
|
96
95
|
|
|
97
96
|
if (msg.content?.text) {
|
|
98
97
|
const lines = msg.content.text.split('\n').slice(0, 3);
|
|
@@ -140,7 +139,7 @@ function formatImplementationReady(msg, prefix, timestamp, print = console.log)
|
|
|
140
139
|
function formatValidationResult(msg, prefix, timestamp, print = console.log) {
|
|
141
140
|
const data = msg.content?.data || {};
|
|
142
141
|
const approved = data.approved === true || data.approved === 'true';
|
|
143
|
-
const status = approved ? chalk.bold.green('✓
|
|
142
|
+
const status = approved ? chalk.bold.green('✓ Approved') : chalk.bold.red('✗ Rejected');
|
|
144
143
|
|
|
145
144
|
print(`${prefix} ${chalk.gray(timestamp)} ${status}`);
|
|
146
145
|
|
|
@@ -190,9 +189,9 @@ function formatValidationResult(msg, prefix, timestamp, print = console.log) {
|
|
|
190
189
|
* @returns {boolean} True if message was handled
|
|
191
190
|
*/
|
|
192
191
|
function formatClusterComplete(msg, prefix, timestamp, print = console.log) {
|
|
193
|
-
print('');
|
|
192
|
+
print('');
|
|
194
193
|
print(chalk.bold.green(`${'═'.repeat(60)}`));
|
|
195
|
-
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.green('🎉
|
|
194
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.green('🎉 Cluster complete')}`);
|
|
196
195
|
if (msg.content?.data?.reason) {
|
|
197
196
|
print(`${prefix} ${chalk.green(msg.content.data.reason)}`);
|
|
198
197
|
}
|
|
@@ -209,15 +208,12 @@ function formatClusterComplete(msg, prefix, timestamp, print = console.log) {
|
|
|
209
208
|
* @returns {boolean} True if message was handled
|
|
210
209
|
*/
|
|
211
210
|
function formatClusterFailed(msg, prefix, timestamp, print = console.log) {
|
|
212
|
-
print('');
|
|
211
|
+
print('');
|
|
213
212
|
print(chalk.bold.red(`${'═'.repeat(60)}`));
|
|
214
|
-
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.red('
|
|
215
|
-
if (msg.content?.text) {
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
if (msg.content?.data?.reason) {
|
|
219
|
-
print(`${prefix} ${chalk.red(msg.content.data.reason)}`);
|
|
220
|
-
}
|
|
213
|
+
print(`${prefix} ${chalk.gray(timestamp)} ${chalk.bold.red('Error: cluster failed')}`);
|
|
214
|
+
if (msg.content?.text) print(`${prefix} ${chalk.red(msg.content.text)}`);
|
|
215
|
+
if (msg.content?.data?.reason) print(`${prefix} ${chalk.red(msg.content.data.reason)}`);
|
|
216
|
+
print(`${prefix} Next: inspect the cluster logs, then resume after fixing the cause.`);
|
|
221
217
|
print(chalk.bold.red(`${'═'.repeat(60)}`));
|
|
222
218
|
return true;
|
|
223
219
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Watch mode message formatters
|
|
3
|
-
* Simplified, high-level event display for zeroshot watch command
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
const chalk = require('chalk');
|
|
7
2
|
const {
|
|
8
3
|
buildClusterPrefix,
|
|
@@ -11,177 +6,94 @@ const {
|
|
|
11
6
|
} = require('./message-formatter-utils');
|
|
12
7
|
const { EVENT_COPY, formatMergeStatus } = require('./event-copy');
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
function formatAgentError(msg, clusterPrefix) {
|
|
20
|
-
const errorMsg = `${msg.sender} ${chalk.bold.red('ERROR')}`;
|
|
21
|
-
console.log(`${clusterPrefix} ${errorMsg}`);
|
|
9
|
+
const DEFAULT_WRITER = Object.freeze({ printLine: (text) => console.log(text) });
|
|
10
|
+
|
|
11
|
+
function formatAgentError(msg, clusterPrefix, writer) {
|
|
12
|
+
writer.printLine(`${clusterPrefix} ${chalk.bold.red(`Error: ${msg.sender}`)}`);
|
|
22
13
|
if (msg.content?.text) {
|
|
23
|
-
|
|
14
|
+
writer.printLine(`${clusterPrefix} ${chalk.red(msg.content.text)}`);
|
|
24
15
|
}
|
|
16
|
+
writer.printLine(`${clusterPrefix} Next: zeroshot logs ${msg.cluster_id} -f`);
|
|
25
17
|
}
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
* Format ISSUE_OPENED for watch mode
|
|
29
|
-
* @param {Object} msg - Message object
|
|
30
|
-
* @param {string} clusterPrefix - Formatted cluster prefix
|
|
31
|
-
*/
|
|
32
|
-
function formatIssueOpened(msg, clusterPrefix) {
|
|
19
|
+
function formatIssueOpened(msg, clusterPrefix, writer) {
|
|
33
20
|
const issueNum = msg.content?.data?.issue_number || '';
|
|
34
21
|
const title = msg.content?.data?.title || '';
|
|
35
22
|
const prompt = msg.content?.data?.prompt || msg.content?.text || '';
|
|
36
|
-
|
|
37
23
|
const taskDesc = title === 'Manual Input' && prompt ? prompt : title;
|
|
38
24
|
const truncatedDesc =
|
|
39
|
-
taskDesc && taskDesc.length > 60 ? taskDesc.substring(0, 60)
|
|
40
|
-
|
|
25
|
+
taskDesc && taskDesc.length > 60 ? `${taskDesc.substring(0, 60)}...` : taskDesc;
|
|
41
26
|
const eventText = `Started ${issueNum ? `#${issueNum}` : 'task'}${truncatedDesc ? chalk.dim(` - ${truncatedDesc}`) : ''}`;
|
|
42
|
-
|
|
27
|
+
writer.printLine(`${clusterPrefix} ${eventText}`);
|
|
43
28
|
}
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
function formatImplementationReady(msg, clusterPrefix) {
|
|
51
|
-
const agentColor = getColorForSender(msg.sender);
|
|
52
|
-
const agentName = agentColor(msg.sender);
|
|
53
|
-
const eventText = `${agentName} ${EVENT_COPY.IMPLEMENTATION_READY.toLowerCase()}`;
|
|
54
|
-
console.log(`${clusterPrefix} ${eventText}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Format VALIDATION_RESULT for watch mode
|
|
59
|
-
* @param {Object} msg - Message object
|
|
60
|
-
* @param {string} clusterPrefix - Formatted cluster prefix
|
|
61
|
-
*/
|
|
62
|
-
function formatValidationResult(msg, clusterPrefix) {
|
|
63
|
-
const agentColor = getColorForSender(msg.sender);
|
|
64
|
-
const agentName = agentColor(msg.sender);
|
|
65
|
-
const data = msg.content?.data;
|
|
66
|
-
const approved = data?.approved === 'true' || data?.approved === true;
|
|
67
|
-
const status = approved ? chalk.green('APPROVED') : chalk.red('REJECTED');
|
|
68
|
-
|
|
69
|
-
let eventText = `${agentName} ${status}`;
|
|
70
|
-
if (data?.summary && !approved) {
|
|
71
|
-
eventText += chalk.dim(` - ${data.summary}`);
|
|
72
|
-
}
|
|
73
|
-
console.log(`${clusterPrefix} ${eventText}`);
|
|
74
|
-
|
|
75
|
-
if (!approved) {
|
|
76
|
-
printRejectionDetails(data, clusterPrefix);
|
|
77
|
-
}
|
|
30
|
+
function formatImplementationReady(msg, clusterPrefix, writer = DEFAULT_WRITER) {
|
|
31
|
+
const agentName = getColorForSender(msg.sender)(msg.sender);
|
|
32
|
+
writer.printLine(
|
|
33
|
+
`${clusterPrefix} ${agentName} ${EVENT_COPY.IMPLEMENTATION_READY.toLowerCase()}`
|
|
34
|
+
);
|
|
78
35
|
}
|
|
79
36
|
|
|
80
|
-
|
|
81
|
-
* Print rejection details (errors/issues)
|
|
82
|
-
* @param {Object} data - Validation data
|
|
83
|
-
* @param {string} clusterPrefix - Formatted cluster prefix
|
|
84
|
-
*/
|
|
85
|
-
function printRejectionDetails(data, clusterPrefix) {
|
|
37
|
+
function printRejectionDetails(data, clusterPrefix, writer) {
|
|
86
38
|
const errors = parseDataField(data.errors);
|
|
87
39
|
const issues = parseDataField(data.issues);
|
|
88
|
-
|
|
89
40
|
if (errors.length > 0) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
`${clusterPrefix} ${chalk.red('•')} ${errors.length} error${errors.length > 1 ? 's' : ''} (${
|
|
41
|
+
const count = JSON.stringify(errors).length;
|
|
42
|
+
writer.printLine(
|
|
43
|
+
`${clusterPrefix} ${chalk.red('•')} ${errors.length} error${errors.length > 1 ? 's' : ''} (${count} chars)`
|
|
93
44
|
);
|
|
94
45
|
}
|
|
95
|
-
|
|
96
46
|
if (issues.length > 0) {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
`${clusterPrefix} ${chalk.yellow('•')} ${issues.length} issue${issues.length > 1 ? 's' : ''} (${
|
|
47
|
+
const count = JSON.stringify(issues).length;
|
|
48
|
+
writer.printLine(
|
|
49
|
+
`${clusterPrefix} ${chalk.yellow('•')} ${issues.length} issue${issues.length > 1 ? 's' : ''} (${count} chars)`
|
|
100
50
|
);
|
|
101
51
|
}
|
|
102
52
|
}
|
|
103
53
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
54
|
+
function formatValidationResult(msg, clusterPrefix, writer) {
|
|
55
|
+
const agentName = getColorForSender(msg.sender)(msg.sender);
|
|
56
|
+
const data = msg.content?.data;
|
|
57
|
+
const approved = data?.approved === 'true' || data?.approved === true;
|
|
58
|
+
const status = approved ? chalk.green('Approved') : chalk.red('Rejected');
|
|
59
|
+
let eventText = `${agentName} ${status}`;
|
|
60
|
+
if (data?.summary && !approved) eventText += chalk.dim(` - ${data.summary}`);
|
|
61
|
+
writer.printLine(`${clusterPrefix} ${eventText}`);
|
|
62
|
+
if (!approved) printRejectionDetails(data, clusterPrefix, writer);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function formatPrCreated(msg, clusterPrefix, writer = DEFAULT_WRITER) {
|
|
66
|
+
const agentName = getColorForSender(msg.sender)(msg.sender);
|
|
112
67
|
const prNum = msg.content?.data?.pr_number || '';
|
|
113
68
|
let eventText = `${agentName} ${EVENT_COPY.PR_CREATED.toLowerCase()}${prNum ? ` #${prNum}` : ''}`;
|
|
114
69
|
const mergeStatus = formatMergeStatus(msg.content?.data?.merged);
|
|
115
|
-
if (mergeStatus) {
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
console.log(`${clusterPrefix} ${eventText}`);
|
|
70
|
+
if (mergeStatus) eventText += chalk.dim(` — ${mergeStatus}`);
|
|
71
|
+
writer.printLine(`${clusterPrefix} ${eventText}`);
|
|
119
72
|
}
|
|
120
73
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
* @param {string} clusterPrefix - Formatted cluster prefix
|
|
125
|
-
*/
|
|
126
|
-
function formatPrMerged(msg, clusterPrefix) {
|
|
127
|
-
const agentColor = getColorForSender(msg.sender);
|
|
128
|
-
const agentName = agentColor(msg.sender);
|
|
129
|
-
const eventText = `${agentName} merged PR`;
|
|
130
|
-
console.log(`${clusterPrefix} ${eventText}`);
|
|
74
|
+
function formatPrMerged(msg, clusterPrefix, writer) {
|
|
75
|
+
const agentName = getColorForSender(msg.sender)(msg.sender);
|
|
76
|
+
writer.printLine(`${clusterPrefix} ${agentName} merged PR`);
|
|
131
77
|
}
|
|
132
78
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* @param {Object} msg - Message object
|
|
136
|
-
* @param {string} clusterPrefix - Formatted cluster prefix
|
|
137
|
-
*/
|
|
138
|
-
function formatUnknownTopic(msg, clusterPrefix) {
|
|
139
|
-
const agentColor = getColorForSender(msg.sender);
|
|
140
|
-
const agentName = agentColor(msg.sender);
|
|
79
|
+
function formatUnknownTopic(msg, clusterPrefix, writer) {
|
|
80
|
+
const agentName = getColorForSender(msg.sender)(msg.sender);
|
|
141
81
|
const eventText = `${agentName} ${msg.topic.toLowerCase().replace(/_/g, ' ')}`;
|
|
142
|
-
|
|
82
|
+
writer.printLine(`${clusterPrefix} ${eventText}`);
|
|
143
83
|
}
|
|
144
84
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
* @param {Object} msg - Message object
|
|
148
|
-
* @param {boolean} isActive - Whether cluster is active
|
|
149
|
-
* @returns {boolean} True if message was handled
|
|
150
|
-
*/
|
|
151
|
-
function formatWatchMode(msg, isActive) {
|
|
152
|
-
// Skip low-level topics (too noisy for watch mode)
|
|
153
|
-
if (msg.topic === 'AGENT_OUTPUT' || msg.topic === 'AGENT_LIFECYCLE') {
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Clear status line, print message, will be redrawn by status interval
|
|
158
|
-
process.stdout.write('\r' + ' '.repeat(120) + '\r');
|
|
159
|
-
|
|
85
|
+
function formatWatchMode(msg, isActive, writer = DEFAULT_WRITER) {
|
|
86
|
+
if (msg.topic === 'AGENT_OUTPUT' || msg.topic === 'AGENT_LIFECYCLE') return true;
|
|
160
87
|
const clusterPrefix = buildClusterPrefix(msg.cluster_id, isActive);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
formatImplementationReady(msg, clusterPrefix);
|
|
171
|
-
break;
|
|
172
|
-
case 'VALIDATION_RESULT':
|
|
173
|
-
formatValidationResult(msg, clusterPrefix);
|
|
174
|
-
break;
|
|
175
|
-
case 'PR_CREATED':
|
|
176
|
-
formatPrCreated(msg, clusterPrefix);
|
|
177
|
-
break;
|
|
178
|
-
case 'PR_MERGED':
|
|
179
|
-
formatPrMerged(msg, clusterPrefix);
|
|
180
|
-
break;
|
|
181
|
-
default:
|
|
182
|
-
formatUnknownTopic(msg, clusterPrefix);
|
|
183
|
-
}
|
|
184
|
-
|
|
88
|
+
const handlers = {
|
|
89
|
+
AGENT_ERROR: formatAgentError,
|
|
90
|
+
ISSUE_OPENED: formatIssueOpened,
|
|
91
|
+
IMPLEMENTATION_READY: formatImplementationReady,
|
|
92
|
+
VALIDATION_RESULT: formatValidationResult,
|
|
93
|
+
PR_CREATED: formatPrCreated,
|
|
94
|
+
PR_MERGED: formatPrMerged,
|
|
95
|
+
};
|
|
96
|
+
(handlers[msg.topic] || formatUnknownTopic)(msg, clusterPrefix, writer);
|
|
185
97
|
return true;
|
|
186
98
|
}
|
|
187
99
|
|
package/lib/completion.js
CHANGED
|
@@ -1,174 +1,123 @@
|
|
|
1
|
-
const
|
|
2
|
-
const fs = require('fs');
|
|
1
|
+
const os = require('os');
|
|
3
2
|
const path = require('path');
|
|
3
|
+
const omelette = require('omelette');
|
|
4
|
+
const { readClustersFileSync } = require('./clusters-registry');
|
|
5
|
+
|
|
6
|
+
const CLUSTER_ID_COMMANDS = new Set([
|
|
7
|
+
'attach',
|
|
8
|
+
'export',
|
|
9
|
+
'finish',
|
|
10
|
+
'kill',
|
|
11
|
+
'logs',
|
|
12
|
+
'resume',
|
|
13
|
+
'status',
|
|
14
|
+
'stop',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function commandNames(command) {
|
|
18
|
+
return [command.name(), ...command.aliases()];
|
|
19
|
+
}
|
|
4
20
|
|
|
5
|
-
function
|
|
6
|
-
|
|
21
|
+
function visibleCommands(command) {
|
|
22
|
+
return command.createHelp().visibleCommands(command);
|
|
23
|
+
}
|
|
7
24
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
25
|
+
function visibleOptions(command) {
|
|
26
|
+
return command.createHelp().visibleOptions(command);
|
|
27
|
+
}
|
|
11
28
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
29
|
+
function optionNames(option) {
|
|
30
|
+
return [option.short, option.long].filter(Boolean);
|
|
31
|
+
}
|
|
15
32
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
|
|
20
|
-
if (fs.existsSync(clustersDir)) {
|
|
21
|
-
const clusterIds = fs
|
|
22
|
-
.readdirSync(clustersDir)
|
|
23
|
-
.filter((f) => f.endsWith('.json'))
|
|
24
|
-
.map((f) => f.replace('.json', ''));
|
|
25
|
-
reply(clusterIds);
|
|
26
|
-
} else {
|
|
27
|
-
reply([]);
|
|
28
|
-
}
|
|
29
|
-
} catch {
|
|
30
|
-
reply([]);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
+
function findSubcommand(command, token) {
|
|
34
|
+
return visibleCommands(command).find((candidate) => commandNames(candidate).includes(token));
|
|
35
|
+
}
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const completions = ['--follow', '-f', '--limit', '-n'];
|
|
39
|
-
|
|
40
|
-
if (fs.existsSync(clustersDir)) {
|
|
41
|
-
const clusterIds = fs
|
|
42
|
-
.readdirSync(clustersDir)
|
|
43
|
-
.filter((f) => f.endsWith('.json'))
|
|
44
|
-
.map((f) => f.replace('.json', ''));
|
|
45
|
-
reply([...clusterIds, ...completions]);
|
|
46
|
-
} else {
|
|
47
|
-
reply(completions);
|
|
48
|
-
}
|
|
49
|
-
} catch {
|
|
50
|
-
reply(['--follow', '-f', '--limit', '-n']);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
37
|
+
function findOption(command, token) {
|
|
38
|
+
const optionToken = token.split('=', 1)[0];
|
|
39
|
+
return visibleOptions(command).find((option) => optionNames(option).includes(optionToken));
|
|
40
|
+
}
|
|
53
41
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.filter((f) => f.endsWith('.json'))
|
|
62
|
-
.map((f) => f.replace('.json', ''));
|
|
63
|
-
reply(clusterIds);
|
|
64
|
-
} else {
|
|
65
|
-
reply([]);
|
|
66
|
-
}
|
|
67
|
-
} catch {
|
|
68
|
-
reply([]);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
42
|
+
function parseCompletionLine(line) {
|
|
43
|
+
const trailingSpace = /\s$/.test(line);
|
|
44
|
+
const tokens = line.trim().split(/\s+/).filter(Boolean);
|
|
45
|
+
tokens.shift();
|
|
46
|
+
const current = trailingSpace ? '' : (tokens.pop() ?? '');
|
|
47
|
+
return { completed: tokens, current };
|
|
48
|
+
}
|
|
71
49
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const clusterIds = fs
|
|
78
|
-
.readdirSync(clustersDir)
|
|
79
|
-
.filter((f) => f.endsWith('.json'))
|
|
80
|
-
.map((f) => f.replace('.json', ''));
|
|
81
|
-
reply(clusterIds);
|
|
82
|
-
} else {
|
|
83
|
-
reply([]);
|
|
84
|
-
}
|
|
85
|
-
} catch {
|
|
86
|
-
reply([]);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
50
|
+
function resolveCommandContext(program, tokens) {
|
|
51
|
+
let command = program;
|
|
52
|
+
const pathParts = [];
|
|
53
|
+
let expectsOptionValue = false;
|
|
54
|
+
let sawPositional = false;
|
|
89
55
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (fs.existsSync(clustersDir)) {
|
|
95
|
-
const clusterIds = fs
|
|
96
|
-
.readdirSync(clustersDir)
|
|
97
|
-
.filter((f) => f.endsWith('.json'))
|
|
98
|
-
.map((f) => f.replace('.json', ''));
|
|
99
|
-
reply(clusterIds);
|
|
100
|
-
} else {
|
|
101
|
-
reply([]);
|
|
102
|
-
}
|
|
103
|
-
} catch {
|
|
104
|
-
reply([]);
|
|
56
|
+
for (const token of tokens) {
|
|
57
|
+
if (expectsOptionValue) {
|
|
58
|
+
expectsOptionValue = false;
|
|
59
|
+
continue;
|
|
105
60
|
}
|
|
106
|
-
});
|
|
107
61
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const completions = ['--merge', '--pr', '--push'];
|
|
113
|
-
|
|
114
|
-
if (fs.existsSync(clustersDir)) {
|
|
115
|
-
const clusterIds = fs
|
|
116
|
-
.readdirSync(clustersDir)
|
|
117
|
-
.filter((f) => f.endsWith('.json'))
|
|
118
|
-
.map((f) => f.replace('.json', ''));
|
|
119
|
-
reply([...clusterIds, ...completions]);
|
|
120
|
-
} else {
|
|
121
|
-
reply(completions);
|
|
122
|
-
}
|
|
123
|
-
} catch {
|
|
124
|
-
reply(['--merge', '--pr', '--push']);
|
|
62
|
+
const option = findOption(command, token);
|
|
63
|
+
if (option) {
|
|
64
|
+
expectsOptionValue = !token.includes('=') && (option.required || option.optional);
|
|
65
|
+
continue;
|
|
125
66
|
}
|
|
126
|
-
|
|
67
|
+
if (token.startsWith('-')) continue;
|
|
127
68
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const clusterIds = fs
|
|
134
|
-
.readdirSync(clustersDir)
|
|
135
|
-
.filter((f) => f.endsWith('.json'))
|
|
136
|
-
.map((f) => f.replace('.json', ''));
|
|
137
|
-
reply(clusterIds);
|
|
138
|
-
} else {
|
|
139
|
-
reply([]);
|
|
140
|
-
}
|
|
141
|
-
} catch {
|
|
142
|
-
reply([]);
|
|
69
|
+
const subcommand = sawPositional ? null : findSubcommand(command, token);
|
|
70
|
+
if (subcommand) {
|
|
71
|
+
command = subcommand;
|
|
72
|
+
pathParts.push(subcommand.name());
|
|
73
|
+
continue;
|
|
143
74
|
}
|
|
144
|
-
|
|
75
|
+
sawPositional = true;
|
|
76
|
+
}
|
|
145
77
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
78
|
+
return { command, commandPath: pathParts.join(' ') };
|
|
79
|
+
}
|
|
149
80
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
81
|
+
function defaultListClusterIds() {
|
|
82
|
+
const homeDir =
|
|
83
|
+
process.env.ZEROSHOT_HOME || process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
84
|
+
const registry = readClustersFileSync(path.join(homeDir, '.zeroshot'));
|
|
85
|
+
return Object.keys(registry);
|
|
86
|
+
}
|
|
153
87
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
'finish',
|
|
164
|
-
'resume',
|
|
165
|
-
'export',
|
|
166
|
-
'ui',
|
|
167
|
-
'watch',
|
|
168
|
-
]);
|
|
169
|
-
});
|
|
88
|
+
function safeDynamicIds(commandPath, listClusterIds) {
|
|
89
|
+
if (!CLUSTER_ID_COMMANDS.has(commandPath)) return [];
|
|
90
|
+
try {
|
|
91
|
+
const ids = listClusterIds();
|
|
92
|
+
return Array.isArray(ids) ? ids.filter((id) => typeof id === 'string' && id.length > 0) : [];
|
|
93
|
+
} catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
170
97
|
|
|
98
|
+
function getCompletionCandidates(program, line, deps = {}) {
|
|
99
|
+
const { completed, current } = parseCompletionLine(line);
|
|
100
|
+
const { command, commandPath } = resolveCommandContext(program, completed);
|
|
101
|
+
const listClusterIds = deps.listClusterIds || defaultListClusterIds;
|
|
102
|
+
const candidates = [
|
|
103
|
+
...visibleCommands(command).flatMap(commandNames),
|
|
104
|
+
...visibleOptions(command).flatMap(optionNames),
|
|
105
|
+
...safeDynamicIds(commandPath, listClusterIds),
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
return [...new Set(candidates)].filter((candidate) => candidate.startsWith(current));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function setupCompletion(program, deps = {}) {
|
|
112
|
+
const complete = omelette('zeroshot');
|
|
113
|
+
complete.on('complete', (_fragment, data) => {
|
|
114
|
+
data.reply(getCompletionCandidates(program, data.line, deps));
|
|
115
|
+
});
|
|
171
116
|
complete.init();
|
|
117
|
+
return complete;
|
|
172
118
|
}
|
|
173
119
|
|
|
174
|
-
module.exports = {
|
|
120
|
+
module.exports = {
|
|
121
|
+
getCompletionCandidates,
|
|
122
|
+
setupCompletion,
|
|
123
|
+
};
|
package/lib/settings.js
CHANGED
|
@@ -126,8 +126,9 @@ const DEFAULT_SETTINGS_BASE = {
|
|
|
126
126
|
return getProviderDefaults();
|
|
127
127
|
},
|
|
128
128
|
defaultConfig: 'conductor-bootstrap',
|
|
129
|
-
|
|
130
|
-
defaultDelivery: 'none', // 'none' | 'pr' | 'ship'
|
|
129
|
+
defaultIsolation: 'none', // 'none' | 'worktree' | 'docker'
|
|
130
|
+
defaultDelivery: 'none', // 'none' | 'pr' | 'ship'
|
|
131
|
+
setupVersion: null, // Internal marker written only after guided setup succeeds.
|
|
131
132
|
strictSchema: true, // true = reliable json output (default), false = live streaming (may crash - see bold-meadow-11)
|
|
132
133
|
logLevel: 'normal',
|
|
133
134
|
// Automatic update notification cache
|
|
@@ -270,6 +271,10 @@ function applyLegacyModelBounds(settings) {
|
|
|
270
271
|
|
|
271
272
|
function normalizeLoadedSettings(parsed) {
|
|
272
273
|
const normalized = { ...parsed };
|
|
274
|
+
if (!Object.hasOwn(parsed, 'defaultIsolation') && typeof parsed.defaultDocker === 'boolean') {
|
|
275
|
+
normalized.defaultIsolation = parsed.defaultDocker ? 'docker' : 'none';
|
|
276
|
+
}
|
|
277
|
+
delete normalized.defaultDocker;
|
|
273
278
|
if (parsed.defaultProvider) {
|
|
274
279
|
normalized.defaultProvider = normalizeProviderName(parsed.defaultProvider);
|
|
275
280
|
}
|
|
@@ -578,6 +583,9 @@ function validateSetting(key, value) {
|
|
|
578
583
|
if (key === 'defaultDelivery' && !['none', 'pr', 'ship'].includes(value)) {
|
|
579
584
|
return `Invalid defaultDelivery: ${value}. Valid: none, pr, ship`;
|
|
580
585
|
}
|
|
586
|
+
if (key === 'defaultIsolation' && !['none', 'worktree', 'docker'].includes(value)) {
|
|
587
|
+
return `Invalid defaultIsolation: ${value}. Valid: none, worktree, docker`;
|
|
588
|
+
}
|
|
581
589
|
|
|
582
590
|
if (key === 'claudeCommand') {
|
|
583
591
|
return validateClaudeCommand(value);
|