@stratusagent/cli 0.11.2 → 0.11.4
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 +14 -8
- package/dist/approvals.d.ts +93 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +368 -0
- package/dist/approvals.js.map +1 -0
- package/dist/commands/agent-new.d.ts +22 -0
- package/dist/commands/agent-new.d.ts.map +1 -0
- package/dist/commands/agent-new.js +159 -0
- package/dist/commands/agent-new.js.map +1 -0
- package/dist/commands/agents.d.ts +4 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +90 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/chat.d.ts +4 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +227 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/credential.d.ts +22 -0
- package/dist/commands/credential.d.ts.map +1 -0
- package/dist/commands/credential.js +93 -0
- package/dist/commands/credential.js.map +1 -0
- package/dist/commands/dashboard.d.ts +14 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +195 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/doctor.d.ts +40 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +376 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/grants.d.ts +19 -0
- package/dist/commands/grants.d.ts.map +1 -0
- package/dist/commands/grants.js +128 -0
- package/dist/commands/grants.js.map +1 -0
- package/dist/commands/logs.d.ts +9 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +58 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/memory.d.ts +20 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +110 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/plugins.d.ts +103 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +386 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/restart.d.ts +4 -0
- package/dist/commands/restart.d.ts.map +1 -0
- package/dist/commands/restart.js +25 -0
- package/dist/commands/restart.js.map +1 -0
- package/dist/commands/schedules.d.ts +11 -0
- package/dist/commands/schedules.d.ts.map +1 -0
- package/dist/commands/schedules.js +50 -0
- package/dist/commands/schedules.js.map +1 -0
- package/dist/commands/serve.d.ts +4 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +633 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/service.d.ts +9 -0
- package/dist/commands/service.d.ts.map +1 -0
- package/dist/commands/service.js +97 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/commands/session.d.ts +9 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +25 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +2467 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/skills.d.ts +15 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +272 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/template.d.ts +4 -0
- package/dist/commands/template.d.ts.map +1 -0
- package/dist/commands/template.js +593 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/update.d.ts +21 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +291 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/daemon.d.ts +74 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +192 -0
- package/dist/daemon.js.map +1 -0
- package/dist/environment.d.ts +114 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +2 -0
- package/dist/environment.js.map +1 -0
- package/dist/events.d.ts +17 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +87 -0
- package/dist/events.js.map +1 -0
- package/dist/help.d.ts +2 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +272 -0
- package/dist/help.js.map +1 -0
- package/dist/index.d.ts +32 -624
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -6981
- package/dist/index.js.map +1 -1
- package/dist/io.d.ts +16 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +45 -0
- package/dist/io.js.map +1 -0
- package/dist/loaders.d.ts +31 -0
- package/dist/loaders.d.ts.map +1 -0
- package/dist/loaders.js +56 -0
- package/dist/loaders.js.map +1 -0
- package/dist/main.d.ts +8 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +218 -0
- package/dist/main.js.map +1 -0
- package/dist/npm.d.ts +66 -0
- package/dist/npm.d.ts.map +1 -0
- package/dist/npm.js +201 -0
- package/dist/npm.js.map +1 -0
- package/dist/parse.d.ts +245 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +1098 -0
- package/dist/parse.js.map +1 -0
- package/dist/plugin-catalog.d.ts +44 -0
- package/dist/plugin-catalog.d.ts.map +1 -0
- package/dist/plugin-catalog.js +70 -0
- package/dist/plugin-catalog.js.map +1 -0
- package/dist/preflight.d.ts +60 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +102 -0
- package/dist/preflight.js.map +1 -0
- package/dist/prompter.d.ts +84 -0
- package/dist/prompter.d.ts.map +1 -0
- package/dist/prompter.js +347 -0
- package/dist/prompter.js.map +1 -0
- package/dist/roster.d.ts +57 -0
- package/dist/roster.d.ts.map +1 -0
- package/dist/roster.js +78 -0
- package/dist/roster.js.map +1 -0
- package/dist/runtime.d.ts +76 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +372 -0
- package/dist/runtime.js.map +1 -0
- package/dist/slack.d.ts +26 -0
- package/dist/slack.d.ts.map +1 -0
- package/dist/slack.js +113 -0
- package/dist/slack.js.map +1 -0
- package/dist/source.d.ts +24 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +80 -0
- package/dist/source.js.map +1 -0
- package/dist/supervisor.d.ts +69 -0
- package/dist/supervisor.d.ts.map +1 -0
- package/dist/supervisor.js +218 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/trusted-config.d.ts +84 -0
- package/dist/trusted-config.d.ts.map +1 -0
- package/dist/trusted-config.js +159 -0
- package/dist/trusted-config.js.map +1 -0
- package/package.json +27 -23
package/dist/parse.js
ADDED
|
@@ -0,0 +1,1098 @@
|
|
|
1
|
+
import { isTrustLevel, TRUST_LEVELS } from '@stratusagent/core';
|
|
2
|
+
import { isValidAgentId } from '@stratusagent/agents';
|
|
3
|
+
import { parseProviderName } from '@stratusagent/state';
|
|
4
|
+
/**
|
|
5
|
+
* What a credential name may be: the two conventions in use, and nothing
|
|
6
|
+
* that would be awkward in a soul's `credentials:` list — `search.apiKey`
|
|
7
|
+
* and environment-style `SLACK_TOKEN`. Leading letter required, which also
|
|
8
|
+
* happens to exclude `__proto__`; the store does not *rely* on that (it
|
|
9
|
+
* keys prototype-free maps), because a credentials file can be written by
|
|
10
|
+
* something other than this command.
|
|
11
|
+
*/
|
|
12
|
+
export const CREDENTIAL_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9._-]{0,127}$/;
|
|
13
|
+
const readPromptFromEnvironment = (env) => (env.stdin ?? '').trim();
|
|
14
|
+
const readOptionValue = (tokens, index, flag) => {
|
|
15
|
+
const value = tokens[index + 1];
|
|
16
|
+
if (!value) {
|
|
17
|
+
throw new Error(`Missing value for ${flag}`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
};
|
|
21
|
+
export const parseCommand = (argv, env = {}) => {
|
|
22
|
+
const [command, ...rest] = argv;
|
|
23
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
24
|
+
return { command: 'help' };
|
|
25
|
+
}
|
|
26
|
+
// Before every other command, and answered even where one would fail:
|
|
27
|
+
// the first thing anyone is asked for in a bug report is the version,
|
|
28
|
+
// and a build too broken to serve is exactly when it is wanted. Reaching
|
|
29
|
+
// it through `update --check` was the only way to see it, which asks npm
|
|
30
|
+
// over the network to answer a question about this machine.
|
|
31
|
+
if (command === 'version' || command === '--version' || command === '-v') {
|
|
32
|
+
return { command: 'version' };
|
|
33
|
+
}
|
|
34
|
+
if (command === 'dashboard') {
|
|
35
|
+
let port;
|
|
36
|
+
let host;
|
|
37
|
+
let openBrowser = true;
|
|
38
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
39
|
+
const token = rest[index];
|
|
40
|
+
if (!token) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (token === '--help' || token === '-h') {
|
|
44
|
+
return { command: 'help' };
|
|
45
|
+
}
|
|
46
|
+
if (token === '--port') {
|
|
47
|
+
const value = Number(readOptionValue(rest, index, '--port'));
|
|
48
|
+
if (!Number.isInteger(value) || value < 0 || value > 65535) {
|
|
49
|
+
throw new Error(`Invalid value for --port: ${rest[index + 1] ?? '(missing)'}`);
|
|
50
|
+
}
|
|
51
|
+
port = value;
|
|
52
|
+
index += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (token === '--host') {
|
|
56
|
+
host = readOptionValue(rest, index, '--host');
|
|
57
|
+
index += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (token === '--no-open') {
|
|
61
|
+
openBrowser = false;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
throw new Error(`Unknown option: ${token}`);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
command: 'dashboard',
|
|
68
|
+
...(port !== undefined ? { port } : {}),
|
|
69
|
+
...(host !== undefined ? { host } : {}),
|
|
70
|
+
openBrowser,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (command === 'serve') {
|
|
74
|
+
const parsed = { command: 'serve', events: true };
|
|
75
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
76
|
+
const token = rest[index];
|
|
77
|
+
if (!token) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (token === '--help' || token === '-h') {
|
|
81
|
+
return { command: 'help' };
|
|
82
|
+
}
|
|
83
|
+
if (token === '--config') {
|
|
84
|
+
parsed.configPath = readOptionValue(rest, index, '--config');
|
|
85
|
+
index += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (token === '--idle-timeout') {
|
|
89
|
+
const seconds = Number(readOptionValue(rest, index, '--idle-timeout'));
|
|
90
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
91
|
+
throw new Error(`Invalid value for --idle-timeout: ${rest[index + 1] ?? '(missing)'}`);
|
|
92
|
+
}
|
|
93
|
+
parsed.idleTimeoutMs = Math.round(seconds * 1000);
|
|
94
|
+
index += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (token === '--approvals') {
|
|
98
|
+
const value = readOptionValue(rest, index, '--approvals');
|
|
99
|
+
if (value !== 'headless' && value !== 'remote') {
|
|
100
|
+
throw new Error(`Unsupported approvals mode: ${value}. Use headless or remote.`);
|
|
101
|
+
}
|
|
102
|
+
parsed.approvals = value;
|
|
103
|
+
index += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (token === '--no-events') {
|
|
107
|
+
parsed.events = false;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (token === '--no-log-file') {
|
|
111
|
+
parsed.logToFile = false;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (token === '--no-api') {
|
|
115
|
+
parsed.api = false;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (token === '--api') {
|
|
119
|
+
parsed.api = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (token === '--api-port') {
|
|
123
|
+
const port = Number(readOptionValue(rest, index, '--api-port'));
|
|
124
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
125
|
+
throw new Error(`Invalid value for --api-port: ${rest[index + 1] ?? '(missing)'}`);
|
|
126
|
+
}
|
|
127
|
+
parsed.apiPort = port;
|
|
128
|
+
index += 1;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (token === '--api-host') {
|
|
132
|
+
parsed.apiHost = readOptionValue(rest, index, '--api-host');
|
|
133
|
+
index += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`Unknown option: ${token}`);
|
|
137
|
+
}
|
|
138
|
+
return parsed;
|
|
139
|
+
}
|
|
140
|
+
if (command === 'chat') {
|
|
141
|
+
const parsed = { command: 'chat', events: false };
|
|
142
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
143
|
+
const token = rest[index];
|
|
144
|
+
if (!token) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (token === '--help' || token === '-h') {
|
|
148
|
+
return { command: 'help' };
|
|
149
|
+
}
|
|
150
|
+
if (token === '--provider') {
|
|
151
|
+
parsed.provider = parseProviderName(readOptionValue(rest, index, '--provider'), '--provider');
|
|
152
|
+
index += 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (token === '--model') {
|
|
156
|
+
parsed.model = readOptionValue(rest, index, '--model');
|
|
157
|
+
index += 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (token === '--base-url') {
|
|
161
|
+
parsed.baseUrl = readOptionValue(rest, index, '--base-url');
|
|
162
|
+
index += 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (token === '--soul') {
|
|
166
|
+
parsed.soul = readOptionValue(rest, index, '--soul');
|
|
167
|
+
index += 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (token === '--config') {
|
|
171
|
+
parsed.configPath = readOptionValue(rest, index, '--config');
|
|
172
|
+
index += 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (token === '--approvals') {
|
|
176
|
+
const value = readOptionValue(rest, index, '--approvals');
|
|
177
|
+
if (value !== 'always' && value !== 'ask' && value !== 'gated' && value !== 'never') {
|
|
178
|
+
throw new Error(`Invalid value for --approvals: ${value}`);
|
|
179
|
+
}
|
|
180
|
+
parsed.approvals = value;
|
|
181
|
+
index += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (token === '--max-turns') {
|
|
185
|
+
const value = Number(readOptionValue(rest, index, '--max-turns'));
|
|
186
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
187
|
+
throw new Error(`Invalid value for --max-turns: ${rest[index + 1] ?? '(missing)'}`);
|
|
188
|
+
}
|
|
189
|
+
parsed.maxTurns = value;
|
|
190
|
+
index += 1;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (token === '--events') {
|
|
194
|
+
parsed.events = true;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
throw new Error(`Unknown option: ${token}`);
|
|
198
|
+
}
|
|
199
|
+
return parsed;
|
|
200
|
+
}
|
|
201
|
+
if (command === 'agents' || (command === 'agent' && rest[0] === 'list')) {
|
|
202
|
+
const agentsRest = command === 'agents' ? rest : rest.slice(1);
|
|
203
|
+
let format = 'text';
|
|
204
|
+
let gateway;
|
|
205
|
+
let token;
|
|
206
|
+
for (let index = 0; index < agentsRest.length; index += 1) {
|
|
207
|
+
const argument = agentsRest[index];
|
|
208
|
+
if (!argument) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (argument === '--help' || argument === '-h') {
|
|
212
|
+
return { command: 'help' };
|
|
213
|
+
}
|
|
214
|
+
if (argument === '--format') {
|
|
215
|
+
const value = readOptionValue(agentsRest, index, '--format');
|
|
216
|
+
if (value !== 'text' && value !== 'json') {
|
|
217
|
+
throw new Error(`Unsupported format: ${value}`);
|
|
218
|
+
}
|
|
219
|
+
format = value;
|
|
220
|
+
index += 1;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (argument === '--gateway') {
|
|
224
|
+
gateway = readOptionValue(agentsRest, index, '--gateway');
|
|
225
|
+
index += 1;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (argument === '--token') {
|
|
229
|
+
token = readOptionValue(agentsRest, index, '--token');
|
|
230
|
+
index += 1;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
throw new Error(`Unknown option: ${argument}`);
|
|
234
|
+
}
|
|
235
|
+
if (token !== undefined && gateway === undefined) {
|
|
236
|
+
throw new Error('--token only applies with --gateway.');
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
command: 'agents',
|
|
240
|
+
format,
|
|
241
|
+
...(gateway ? { gateway } : {}),
|
|
242
|
+
...(token ? { token } : {}),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (command === 'schedules' || (command === 'schedule' && rest[0] === 'list') || (command === 'schedule' && rest[0] === 'cancel')) {
|
|
246
|
+
const schedulesRest = command === 'schedules' ? rest : rest.slice(1);
|
|
247
|
+
const action = (command === 'schedule' ? rest[0] : schedulesRest[0]) === 'cancel' ? 'cancel' : 'list';
|
|
248
|
+
const tokens = action === 'cancel' && command === 'schedules' ? schedulesRest.slice(1) : schedulesRest;
|
|
249
|
+
let format = 'text';
|
|
250
|
+
let scheduleId;
|
|
251
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
252
|
+
const token = tokens[index];
|
|
253
|
+
if (!token || (action === 'list' && token === 'list' && index === 0)) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (token === '--help' || token === '-h') {
|
|
257
|
+
return { command: 'help' };
|
|
258
|
+
}
|
|
259
|
+
if (token === '--format') {
|
|
260
|
+
const value = readOptionValue(tokens, index, '--format');
|
|
261
|
+
if (value !== 'text' && value !== 'json') {
|
|
262
|
+
throw new Error(`Unsupported format: ${value}`);
|
|
263
|
+
}
|
|
264
|
+
format = value;
|
|
265
|
+
index += 1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (token.startsWith('--')) {
|
|
269
|
+
throw new Error(`Unknown option: ${token}`);
|
|
270
|
+
}
|
|
271
|
+
if (action === 'cancel' && scheduleId === undefined) {
|
|
272
|
+
scheduleId = token;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
throw new Error(`Unexpected argument: ${token}. Try: stratus schedules, stratus schedules cancel <id>`);
|
|
276
|
+
}
|
|
277
|
+
if (action === 'cancel' && !scheduleId) {
|
|
278
|
+
throw new Error('schedules cancel needs the schedule id: stratus schedules cancel <id>.');
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
command: 'schedules',
|
|
282
|
+
action,
|
|
283
|
+
...(scheduleId ? { scheduleId } : {}),
|
|
284
|
+
format,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
if (command === 'doctor') {
|
|
288
|
+
let format = 'text';
|
|
289
|
+
let configPath;
|
|
290
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
291
|
+
const token = rest[index];
|
|
292
|
+
if (!token) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (token === '--help' || token === '-h') {
|
|
296
|
+
return { command: 'help' };
|
|
297
|
+
}
|
|
298
|
+
if (token === '--format') {
|
|
299
|
+
const value = readOptionValue(rest, index, '--format');
|
|
300
|
+
if (value !== 'text' && value !== 'json') {
|
|
301
|
+
throw new Error(`Unsupported format: ${value}`);
|
|
302
|
+
}
|
|
303
|
+
format = value;
|
|
304
|
+
index += 1;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (token === '--config') {
|
|
308
|
+
configPath = readOptionValue(rest, index, '--config');
|
|
309
|
+
index += 1;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
throw new Error(`Unknown option: ${token}`);
|
|
313
|
+
}
|
|
314
|
+
return { command: 'doctor', format, ...(configPath ? { configPath } : {}) };
|
|
315
|
+
}
|
|
316
|
+
if (command === 'update') {
|
|
317
|
+
let check = false;
|
|
318
|
+
for (const token of rest) {
|
|
319
|
+
if (!token) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (token === '--help' || token === '-h') {
|
|
323
|
+
return { command: 'help' };
|
|
324
|
+
}
|
|
325
|
+
if (token === '--check') {
|
|
326
|
+
check = true;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
throw new Error(`Unknown option: ${token}`);
|
|
330
|
+
}
|
|
331
|
+
return { command: 'update', check };
|
|
332
|
+
}
|
|
333
|
+
if (command === 'service') {
|
|
334
|
+
const action = rest[0];
|
|
335
|
+
if (action === '--help' || action === '-h' || action === undefined) {
|
|
336
|
+
return { command: 'help' };
|
|
337
|
+
}
|
|
338
|
+
if (action !== 'install' && action !== 'uninstall' && action !== 'status' && action !== 'start' && action !== 'stop') {
|
|
339
|
+
throw new Error(`Unknown service action: ${action} (expected install, uninstall, status, start, or stop)`);
|
|
340
|
+
}
|
|
341
|
+
const parsed = { command: 'service', action };
|
|
342
|
+
const serviceRest = rest.slice(1);
|
|
343
|
+
for (let index = 0; index < serviceRest.length; index += 1) {
|
|
344
|
+
const token = serviceRest[index];
|
|
345
|
+
if (!token) {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (token === '--help' || token === '-h') {
|
|
349
|
+
return { command: 'help' };
|
|
350
|
+
}
|
|
351
|
+
if (token === '--no-login') {
|
|
352
|
+
if (action !== 'install') {
|
|
353
|
+
throw new Error('--no-login applies to `stratus service install`.');
|
|
354
|
+
}
|
|
355
|
+
parsed.runAtLogin = false;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (token === '--config') {
|
|
359
|
+
if (action !== 'install') {
|
|
360
|
+
throw new Error('--config applies to `stratus service install`.');
|
|
361
|
+
}
|
|
362
|
+
parsed.configPath = readOptionValue(serviceRest, index, '--config');
|
|
363
|
+
index += 1;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
throw new Error(`Unknown option: ${token}`);
|
|
367
|
+
}
|
|
368
|
+
return parsed;
|
|
369
|
+
}
|
|
370
|
+
if (command === 'logs') {
|
|
371
|
+
let follow = false;
|
|
372
|
+
let limit = 50;
|
|
373
|
+
let agentId;
|
|
374
|
+
let sessionId;
|
|
375
|
+
let format = 'text';
|
|
376
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
377
|
+
const token = rest[index];
|
|
378
|
+
if (!token) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (token === '--help' || token === '-h') {
|
|
382
|
+
return { command: 'help' };
|
|
383
|
+
}
|
|
384
|
+
if (token === '--follow' || token === '-f') {
|
|
385
|
+
follow = true;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (token === '--lines' || token === '-n') {
|
|
389
|
+
const value = Number(readOptionValue(rest, index, token));
|
|
390
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
391
|
+
throw new Error(`Invalid value for ${token}: ${rest[index + 1] ?? '(missing)'}`);
|
|
392
|
+
}
|
|
393
|
+
limit = Math.floor(value);
|
|
394
|
+
index += 1;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (token === '--agent') {
|
|
398
|
+
agentId = readOptionValue(rest, index, '--agent');
|
|
399
|
+
index += 1;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (token === '--session') {
|
|
403
|
+
sessionId = readOptionValue(rest, index, '--session');
|
|
404
|
+
index += 1;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
if (token === '--format') {
|
|
408
|
+
const value = readOptionValue(rest, index, '--format');
|
|
409
|
+
if (value !== 'text' && value !== 'json') {
|
|
410
|
+
throw new Error(`Unsupported format: ${value}`);
|
|
411
|
+
}
|
|
412
|
+
format = value;
|
|
413
|
+
index += 1;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
throw new Error(`Unknown option: ${token}`);
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
command: 'logs',
|
|
420
|
+
follow,
|
|
421
|
+
limit,
|
|
422
|
+
format,
|
|
423
|
+
...(agentId ? { agentId } : {}),
|
|
424
|
+
...(sessionId ? { sessionId } : {}),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
if (command === 'credentials' || command === 'credential') {
|
|
428
|
+
const [subcommand, ...credentialRest] = command === 'credentials' ? ['list', ...rest] : rest;
|
|
429
|
+
if (subcommand === undefined || subcommand === '--help' || subcommand === '-h') {
|
|
430
|
+
return { command: 'help' };
|
|
431
|
+
}
|
|
432
|
+
if (subcommand !== 'set' && subcommand !== 'list' && subcommand !== 'remove') {
|
|
433
|
+
throw new Error(`No credential subcommand named ${JSON.stringify(subcommand)}. It is set, list, or remove.`);
|
|
434
|
+
}
|
|
435
|
+
let name;
|
|
436
|
+
let agentId;
|
|
437
|
+
for (let index = 0; index < credentialRest.length; index += 1) {
|
|
438
|
+
const token = credentialRest[index];
|
|
439
|
+
if (!token) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (token === '--help' || token === '-h') {
|
|
443
|
+
return { command: 'help' };
|
|
444
|
+
}
|
|
445
|
+
if (token === '--agent') {
|
|
446
|
+
agentId = readOptionValue(credentialRest, index, '--agent');
|
|
447
|
+
index += 1;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (token.startsWith('--')) {
|
|
451
|
+
throw new Error(`Unknown option: ${token}`);
|
|
452
|
+
}
|
|
453
|
+
if (name !== undefined) {
|
|
454
|
+
throw new Error(`credential ${subcommand} takes one credential name; got both ${JSON.stringify(name)} and ${JSON.stringify(token)}.`);
|
|
455
|
+
}
|
|
456
|
+
name = token;
|
|
457
|
+
}
|
|
458
|
+
if (subcommand !== 'list' && name === undefined) {
|
|
459
|
+
throw new Error(`credential ${subcommand} needs a credential name — for a search backend that is search.apiKey.`);
|
|
460
|
+
}
|
|
461
|
+
// A name keys a map that a soul's `credentials:` list also names, so the
|
|
462
|
+
// two spellings have to be able to meet. The store itself is
|
|
463
|
+
// prototype-safe whatever lands in it; this rule is what stops a typo —
|
|
464
|
+
// or a name no soul could ever write — from being stored and then
|
|
465
|
+
// reported missing forever.
|
|
466
|
+
if (name !== undefined && !CREDENTIAL_NAME_PATTERN.test(name)) {
|
|
467
|
+
throw new Error(`${JSON.stringify(name)} is not a credential name. Use letters, digits, dots, dashes, or underscores, `
|
|
468
|
+
+ 'starting with a letter — search.apiKey, or an environment-style SLACK_TOKEN.');
|
|
469
|
+
}
|
|
470
|
+
// The agents package's own rule, not a second one: it is already the
|
|
471
|
+
// answer to "what may key a plain object", and it rejects the names —
|
|
472
|
+
// `__proto__`, `toString` — that would store a credential nothing could
|
|
473
|
+
// ever resolve.
|
|
474
|
+
if (agentId !== undefined && !isValidAgentId(agentId)) {
|
|
475
|
+
throw new Error(`${JSON.stringify(agentId)} cannot be an agent id, so a credential stored under it could never be resolved.`);
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
command: 'credential',
|
|
479
|
+
action: subcommand,
|
|
480
|
+
...(name !== undefined ? { name } : {}),
|
|
481
|
+
...(agentId !== undefined ? { agentId } : {}),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
if (command === 'plugins' || (command === 'plugin' && rest[0] === 'list')) {
|
|
485
|
+
const pluginsRest = command === 'plugins' ? rest : rest.slice(1);
|
|
486
|
+
let format = 'text';
|
|
487
|
+
let configPath;
|
|
488
|
+
for (let index = 0; index < pluginsRest.length; index += 1) {
|
|
489
|
+
const token = pluginsRest[index];
|
|
490
|
+
if (token === '--help' || token === '-h') {
|
|
491
|
+
return { command: 'help' };
|
|
492
|
+
}
|
|
493
|
+
if (token === '--format') {
|
|
494
|
+
const value = readOptionValue(pluginsRest, index, '--format');
|
|
495
|
+
if (value !== 'text' && value !== 'json') {
|
|
496
|
+
throw new Error(`Invalid value for --format: ${value}. Use text or json.`);
|
|
497
|
+
}
|
|
498
|
+
format = value;
|
|
499
|
+
index += 1;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (token === '--config') {
|
|
503
|
+
configPath = readOptionValue(pluginsRest, index, '--config');
|
|
504
|
+
index += 1;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
throw new Error(`Unknown option: ${token}`);
|
|
508
|
+
}
|
|
509
|
+
return { command: 'plugins', format, ...(configPath !== undefined ? { configPath } : {}) };
|
|
510
|
+
}
|
|
511
|
+
if (command === 'skills' || (command === 'skill' && rest[0] === 'list')) {
|
|
512
|
+
const skillsRest = command === 'skills' ? rest : rest.slice(1);
|
|
513
|
+
for (const token of skillsRest) {
|
|
514
|
+
if (token === '--help' || token === '-h') {
|
|
515
|
+
return { command: 'help' };
|
|
516
|
+
}
|
|
517
|
+
throw new Error(`Unknown option: ${token}`);
|
|
518
|
+
}
|
|
519
|
+
return { command: 'skills' };
|
|
520
|
+
}
|
|
521
|
+
if (command === 'template') {
|
|
522
|
+
const [subcommand, ...templateRest] = rest;
|
|
523
|
+
if (subcommand === undefined || subcommand === '--help' || subcommand === '-h') {
|
|
524
|
+
return { command: 'help' };
|
|
525
|
+
}
|
|
526
|
+
if (subcommand !== 'add') {
|
|
527
|
+
throw new Error(`Unknown template subcommand: ${subcommand}. Try: stratus template add <path or owner/repo>`);
|
|
528
|
+
}
|
|
529
|
+
let source;
|
|
530
|
+
let yes = false;
|
|
531
|
+
let force = false;
|
|
532
|
+
for (const token of templateRest) {
|
|
533
|
+
if (token === '--help' || token === '-h') {
|
|
534
|
+
return { command: 'help' };
|
|
535
|
+
}
|
|
536
|
+
if (token === '--yes' || token === '-y') {
|
|
537
|
+
yes = true;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
if (token === '--force') {
|
|
541
|
+
force = true;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (token.startsWith('--')) {
|
|
545
|
+
throw new Error(`Unknown option: ${token}`);
|
|
546
|
+
}
|
|
547
|
+
if (source !== undefined) {
|
|
548
|
+
throw new Error(`template add takes one source; got both ${JSON.stringify(source)} and ${JSON.stringify(token)}.`);
|
|
549
|
+
}
|
|
550
|
+
source = token;
|
|
551
|
+
}
|
|
552
|
+
if (source === undefined) {
|
|
553
|
+
throw new Error('template add needs a source: a GitHub owner/repo, a git URL, or a local path.');
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
command: 'template-add',
|
|
557
|
+
source,
|
|
558
|
+
...(yes ? { yes } : {}),
|
|
559
|
+
...(force ? { force } : {}),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
if (command === 'skill') {
|
|
563
|
+
const [subcommand, ...skillRest] = rest;
|
|
564
|
+
if (subcommand === '--help' || subcommand === '-h') {
|
|
565
|
+
return { command: 'help' };
|
|
566
|
+
}
|
|
567
|
+
if (subcommand === 'validate') {
|
|
568
|
+
let target;
|
|
569
|
+
for (const token of skillRest) {
|
|
570
|
+
if (token === '--help' || token === '-h') {
|
|
571
|
+
return { command: 'help' };
|
|
572
|
+
}
|
|
573
|
+
if (token.startsWith('--')) {
|
|
574
|
+
throw new Error(`Unknown option: ${token}`);
|
|
575
|
+
}
|
|
576
|
+
if (target !== undefined) {
|
|
577
|
+
throw new Error(`skill validate takes one target; got both ${JSON.stringify(target)} and ${JSON.stringify(token)}.`);
|
|
578
|
+
}
|
|
579
|
+
target = token;
|
|
580
|
+
}
|
|
581
|
+
if (target === undefined) {
|
|
582
|
+
throw new Error('skill validate needs a target: a skill directory, a directory of skills, or an installed skill id.');
|
|
583
|
+
}
|
|
584
|
+
return { command: 'skill-validate', target };
|
|
585
|
+
}
|
|
586
|
+
if (subcommand === 'reload') {
|
|
587
|
+
const parsed = { command: 'skill-reload' };
|
|
588
|
+
for (let index = 0; index < skillRest.length; index += 1) {
|
|
589
|
+
const token = skillRest[index];
|
|
590
|
+
if (!token) {
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
if (token === '--help' || token === '-h') {
|
|
594
|
+
return { command: 'help' };
|
|
595
|
+
}
|
|
596
|
+
if (token === '--gateway') {
|
|
597
|
+
parsed.gateway = readOptionValue(skillRest, index, '--gateway');
|
|
598
|
+
index += 1;
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
if (token === '--token') {
|
|
602
|
+
parsed.token = readOptionValue(skillRest, index, '--token');
|
|
603
|
+
index += 1;
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
throw new Error(`Unknown option: ${token}`);
|
|
607
|
+
}
|
|
608
|
+
return parsed;
|
|
609
|
+
}
|
|
610
|
+
if (subcommand !== 'add') {
|
|
611
|
+
throw new Error(`Unknown skill subcommand: ${subcommand ?? '(missing)'}. Try: stratus skill add <source>, stratus skill validate <path>, stratus skill reload, stratus skills`);
|
|
612
|
+
}
|
|
613
|
+
let source;
|
|
614
|
+
const skillIds = [];
|
|
615
|
+
let force = false;
|
|
616
|
+
let agentId;
|
|
617
|
+
let reload = true;
|
|
618
|
+
for (let index = 0; index < skillRest.length; index += 1) {
|
|
619
|
+
const token = skillRest[index];
|
|
620
|
+
if (!token) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (token === '--help' || token === '-h') {
|
|
624
|
+
return { command: 'help' };
|
|
625
|
+
}
|
|
626
|
+
if (token === '--skill') {
|
|
627
|
+
skillIds.push(readOptionValue(skillRest, index, '--skill'));
|
|
628
|
+
index += 1;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (token === '--agent') {
|
|
632
|
+
agentId = readOptionValue(skillRest, index, '--agent');
|
|
633
|
+
index += 1;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (token === '--force') {
|
|
637
|
+
force = true;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (token === '--no-reload') {
|
|
641
|
+
reload = false;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (token.startsWith('--')) {
|
|
645
|
+
throw new Error(`Unknown option: ${token}`);
|
|
646
|
+
}
|
|
647
|
+
if (source !== undefined) {
|
|
648
|
+
throw new Error(`skill add takes one source; got both ${JSON.stringify(source)} and ${JSON.stringify(token)}.`);
|
|
649
|
+
}
|
|
650
|
+
source = token;
|
|
651
|
+
}
|
|
652
|
+
if (source === undefined) {
|
|
653
|
+
throw new Error('skill add needs a source: a GitHub owner/repo, a git URL, or a local path.');
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
command: 'skill-add',
|
|
657
|
+
source,
|
|
658
|
+
...(skillIds.length > 0 ? { skillIds } : {}),
|
|
659
|
+
...(force ? { force } : {}),
|
|
660
|
+
...(agentId !== undefined ? { agentId } : {}),
|
|
661
|
+
...(reload ? {} : { reload }),
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (command === 'grants') {
|
|
665
|
+
const action = rest[0] === 'revoke' ? 'revoke' : 'list';
|
|
666
|
+
const tokens = rest[0] === 'revoke' || rest[0] === 'list' ? rest.slice(1) : rest;
|
|
667
|
+
const usage = action === 'revoke'
|
|
668
|
+
? 'stratus grants revoke <agent> --tool <name> | --scope "<command>" | --origin <origin>'
|
|
669
|
+
: 'stratus grants <agent>';
|
|
670
|
+
const parsed = { command: 'grants', action, agentId: '', format: 'text' };
|
|
671
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
672
|
+
const token = tokens[index];
|
|
673
|
+
if (!token) {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (token === '--help' || token === '-h') {
|
|
677
|
+
return { command: 'help' };
|
|
678
|
+
}
|
|
679
|
+
if (token === '--format') {
|
|
680
|
+
const value = readOptionValue(tokens, index, '--format');
|
|
681
|
+
if (value !== 'text' && value !== 'json') {
|
|
682
|
+
throw new Error(`Unsupported format: ${value}`);
|
|
683
|
+
}
|
|
684
|
+
parsed.format = value;
|
|
685
|
+
index += 1;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (token === '--gateway') {
|
|
689
|
+
parsed.gateway = readOptionValue(tokens, index, '--gateway');
|
|
690
|
+
index += 1;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (token === '--token') {
|
|
694
|
+
parsed.token = readOptionValue(tokens, index, '--token');
|
|
695
|
+
index += 1;
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
if (action === 'revoke' && (token === '--tool' || token === '--scope' || token === '--origin')) {
|
|
699
|
+
const value = readOptionValue(tokens, index, token);
|
|
700
|
+
if (token === '--tool') {
|
|
701
|
+
parsed.tool = value;
|
|
702
|
+
}
|
|
703
|
+
else if (token === '--scope') {
|
|
704
|
+
parsed.scope = value;
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
parsed.origin = value;
|
|
708
|
+
}
|
|
709
|
+
index += 1;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (token.startsWith('--')) {
|
|
713
|
+
throw new Error(`Unknown option: ${token}`);
|
|
714
|
+
}
|
|
715
|
+
if (parsed.agentId === '') {
|
|
716
|
+
parsed.agentId = token;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
throw new Error(`Unexpected argument: ${token}. Try: ${usage}`);
|
|
720
|
+
}
|
|
721
|
+
if (parsed.agentId === '') {
|
|
722
|
+
throw new Error(`grants${action === 'revoke' ? ' revoke' : ''} needs the agent id: ${usage}.`);
|
|
723
|
+
}
|
|
724
|
+
// The agents package's own rule, the same one the control API route and
|
|
725
|
+
// `credential --agent` apply — and the reason is sharper here: this id is
|
|
726
|
+
// joined into a path (`<id>.whitelist.json`), and the grant store
|
|
727
|
+
// documents it as an already-validated single segment by the time it
|
|
728
|
+
// reaches that join. Unchecked, `../../other` reads — and on a revoke
|
|
729
|
+
// rewrites — a grant file outside ~/.stratus/agents.
|
|
730
|
+
if (!isValidAgentId(parsed.agentId)) {
|
|
731
|
+
throw new Error(`${JSON.stringify(parsed.agentId)} cannot be an agent id, so it names no grant file. `
|
|
732
|
+
+ 'Use the id `stratus agents` lists.');
|
|
733
|
+
}
|
|
734
|
+
if (action === 'revoke') {
|
|
735
|
+
const named = [parsed.tool, parsed.scope, parsed.origin].filter((value) => value !== undefined).length;
|
|
736
|
+
if (named !== 1) {
|
|
737
|
+
throw new Error(`grants revoke names exactly one of --tool, --scope, or --origin: ${usage}.`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return parsed;
|
|
741
|
+
}
|
|
742
|
+
if (command === 'memory') {
|
|
743
|
+
const [action, ...memoryRest] = rest;
|
|
744
|
+
if (action === undefined || action === '--help' || action === '-h') {
|
|
745
|
+
return { command: 'help' };
|
|
746
|
+
}
|
|
747
|
+
if (action !== 'list' && action !== 'reassert') {
|
|
748
|
+
throw new Error(`Unknown memory subcommand: ${action}. Try: stratus memory list <agent>, stratus memory reassert <agent> --trust user <id>...`);
|
|
749
|
+
}
|
|
750
|
+
let agentId;
|
|
751
|
+
let trust;
|
|
752
|
+
let format = 'text';
|
|
753
|
+
let allUnknown = false;
|
|
754
|
+
const ids = [];
|
|
755
|
+
for (let index = 0; index < memoryRest.length; index += 1) {
|
|
756
|
+
const token = memoryRest[index];
|
|
757
|
+
if (!token) {
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (token === '--help' || token === '-h') {
|
|
761
|
+
return { command: 'help' };
|
|
762
|
+
}
|
|
763
|
+
if (token === '--trust') {
|
|
764
|
+
const value = readOptionValue(memoryRest, index, '--trust');
|
|
765
|
+
if (!isTrustLevel(value)) {
|
|
766
|
+
throw new Error(`Unsupported trust level: ${value}. Use one of ${TRUST_LEVELS.join(', ')}.`);
|
|
767
|
+
}
|
|
768
|
+
trust = value;
|
|
769
|
+
index += 1;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (token === '--format') {
|
|
773
|
+
const value = readOptionValue(memoryRest, index, '--format');
|
|
774
|
+
if (value !== 'text' && value !== 'json') {
|
|
775
|
+
throw new Error(`Unsupported format: ${value}`);
|
|
776
|
+
}
|
|
777
|
+
format = value;
|
|
778
|
+
index += 1;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (token === '--all-unknown' && action === 'reassert') {
|
|
782
|
+
allUnknown = true;
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (token.startsWith('--')) {
|
|
786
|
+
throw new Error(`Unknown option: ${token}`);
|
|
787
|
+
}
|
|
788
|
+
if (agentId === undefined) {
|
|
789
|
+
agentId = token;
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
if (action === 'reassert') {
|
|
793
|
+
ids.push(token);
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
throw new Error(`Unexpected argument: ${token}. Try: stratus memory list <agent>`);
|
|
797
|
+
}
|
|
798
|
+
if (!agentId) {
|
|
799
|
+
throw new Error(`memory ${action} needs the agent id: stratus memory ${action} <agent>${action === 'reassert' ? ' --trust user <id>...' : ''}.`);
|
|
800
|
+
}
|
|
801
|
+
if (action === 'reassert') {
|
|
802
|
+
if (trust === undefined) {
|
|
803
|
+
throw new Error('memory reassert needs --trust <user|agent|external|unknown>: the label you are vouching for.');
|
|
804
|
+
}
|
|
805
|
+
if (ids.length === 0 && !allUnknown) {
|
|
806
|
+
throw new Error('memory reassert needs entry ids, or --all-unknown to re-label every entry that has no recorded origin.');
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return {
|
|
810
|
+
command: 'memory',
|
|
811
|
+
action,
|
|
812
|
+
agentId,
|
|
813
|
+
...(trust !== undefined ? { trust } : {}),
|
|
814
|
+
ids,
|
|
815
|
+
allUnknown,
|
|
816
|
+
format,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
if (command === 'session') {
|
|
820
|
+
const [action, ...sessionRest] = rest;
|
|
821
|
+
if (action === undefined || action === '--help' || action === '-h') {
|
|
822
|
+
return { command: 'help' };
|
|
823
|
+
}
|
|
824
|
+
if (action !== 'rollover') {
|
|
825
|
+
throw new Error(`Unknown session subcommand: ${action}. Try: stratus session rollover <session-id>`);
|
|
826
|
+
}
|
|
827
|
+
const parsed = { command: 'session', action: 'rollover', sessionId: '' };
|
|
828
|
+
for (let index = 0; index < sessionRest.length; index += 1) {
|
|
829
|
+
const token = sessionRest[index];
|
|
830
|
+
if (!token) {
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
if (token === '--help' || token === '-h') {
|
|
834
|
+
return { command: 'help' };
|
|
835
|
+
}
|
|
836
|
+
if (token === '--gateway') {
|
|
837
|
+
parsed.gateway = readOptionValue(sessionRest, index, '--gateway');
|
|
838
|
+
index += 1;
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (token === '--token') {
|
|
842
|
+
parsed.token = readOptionValue(sessionRest, index, '--token');
|
|
843
|
+
index += 1;
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (token.startsWith('--')) {
|
|
847
|
+
throw new Error(`Unknown option: ${token}`);
|
|
848
|
+
}
|
|
849
|
+
if (parsed.sessionId.length > 0) {
|
|
850
|
+
throw new Error(`Unexpected argument: ${token}. Try: stratus session rollover <session-id>`);
|
|
851
|
+
}
|
|
852
|
+
parsed.sessionId = token;
|
|
853
|
+
}
|
|
854
|
+
if (parsed.sessionId.length === 0) {
|
|
855
|
+
throw new Error('session rollover needs the session id: stratus session rollover <session-id>. `stratus logs` shows ids in its last column.');
|
|
856
|
+
}
|
|
857
|
+
return parsed;
|
|
858
|
+
}
|
|
859
|
+
if (command === 'restart') {
|
|
860
|
+
const parsed = { command: 'restart' };
|
|
861
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
862
|
+
const token = rest[index];
|
|
863
|
+
if (!token) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (token === '--help' || token === '-h') {
|
|
867
|
+
return { command: 'help' };
|
|
868
|
+
}
|
|
869
|
+
if (token === '--reason') {
|
|
870
|
+
parsed.reason = readOptionValue(rest, index, '--reason');
|
|
871
|
+
index += 1;
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (token === '--drain-timeout') {
|
|
875
|
+
const seconds = Number(readOptionValue(rest, index, '--drain-timeout'));
|
|
876
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
877
|
+
throw new Error(`Invalid value for --drain-timeout: ${rest[index + 1] ?? '(missing)'}`);
|
|
878
|
+
}
|
|
879
|
+
parsed.drainTimeoutMs = Math.round(seconds * 1000);
|
|
880
|
+
index += 1;
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (token === '--gateway') {
|
|
884
|
+
parsed.gateway = readOptionValue(rest, index, '--gateway');
|
|
885
|
+
index += 1;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (token === '--token') {
|
|
889
|
+
parsed.token = readOptionValue(rest, index, '--token');
|
|
890
|
+
index += 1;
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
throw new Error(`Unknown option: ${token}`);
|
|
894
|
+
}
|
|
895
|
+
return parsed;
|
|
896
|
+
}
|
|
897
|
+
if (command === 'agent') {
|
|
898
|
+
const [subcommand, ...agentRest] = rest;
|
|
899
|
+
if (subcommand === '--help' || subcommand === '-h') {
|
|
900
|
+
return { command: 'help' };
|
|
901
|
+
}
|
|
902
|
+
if (subcommand !== 'new') {
|
|
903
|
+
throw new Error(`Unknown agent subcommand: ${subcommand ?? '(missing)'}. Try: stratus agent new, stratus agent list`);
|
|
904
|
+
}
|
|
905
|
+
let name;
|
|
906
|
+
let instructions;
|
|
907
|
+
let format = 'text';
|
|
908
|
+
for (let index = 0; index < agentRest.length; index += 1) {
|
|
909
|
+
const token = agentRest[index];
|
|
910
|
+
if (!token) {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
if (token === '--help' || token === '-h') {
|
|
914
|
+
return { command: 'help' };
|
|
915
|
+
}
|
|
916
|
+
if (token === '--name') {
|
|
917
|
+
name = readOptionValue(agentRest, index, '--name');
|
|
918
|
+
index += 1;
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
if (token === '--instructions') {
|
|
922
|
+
instructions = readOptionValue(agentRest, index, '--instructions');
|
|
923
|
+
index += 1;
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
if (token === '--format') {
|
|
927
|
+
const value = readOptionValue(agentRest, index, '--format');
|
|
928
|
+
if (value !== 'text' && value !== 'json' && value !== 'soul') {
|
|
929
|
+
throw new Error(`Unsupported format: ${value}`);
|
|
930
|
+
}
|
|
931
|
+
format = value;
|
|
932
|
+
index += 1;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
throw new Error(`Unknown option: ${token}`);
|
|
936
|
+
}
|
|
937
|
+
return {
|
|
938
|
+
command: 'agent-new',
|
|
939
|
+
...(name ? { name } : {}),
|
|
940
|
+
...(instructions ? { instructions } : {}),
|
|
941
|
+
format,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
if (command === 'setup') {
|
|
945
|
+
let configPath;
|
|
946
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
947
|
+
const token = rest[index];
|
|
948
|
+
if (!token) {
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (token === '--help' || token === '-h') {
|
|
952
|
+
return { command: 'help' };
|
|
953
|
+
}
|
|
954
|
+
if (token === '--config') {
|
|
955
|
+
configPath = readOptionValue(rest, index, '--config');
|
|
956
|
+
index += 1;
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
throw new Error(`Unknown option: ${token}`);
|
|
960
|
+
}
|
|
961
|
+
return { command: 'setup', ...(configPath ? { configPath } : {}) };
|
|
962
|
+
}
|
|
963
|
+
if (command !== 'run') {
|
|
964
|
+
throw new Error(`Unknown command: ${command}`);
|
|
965
|
+
}
|
|
966
|
+
let prompt = '';
|
|
967
|
+
let provider;
|
|
968
|
+
let model;
|
|
969
|
+
let baseUrl;
|
|
970
|
+
let configPath;
|
|
971
|
+
let soul;
|
|
972
|
+
let format = 'text';
|
|
973
|
+
let events = true;
|
|
974
|
+
let approvals;
|
|
975
|
+
let maxTurns;
|
|
976
|
+
let useStdin = false;
|
|
977
|
+
const positionals = [];
|
|
978
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
979
|
+
const token = rest[index];
|
|
980
|
+
if (!token) {
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
if (token === '--help' || token === '-h') {
|
|
984
|
+
return { command: 'help' };
|
|
985
|
+
}
|
|
986
|
+
if (token === '--prompt' || token === '-p') {
|
|
987
|
+
prompt = readOptionValue(rest, index, '--prompt');
|
|
988
|
+
index += 1;
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
if (token === '--stdin') {
|
|
992
|
+
useStdin = true;
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
if (token === '--provider') {
|
|
996
|
+
provider = parseProviderName(readOptionValue(rest, index, '--provider'), '--provider');
|
|
997
|
+
index += 1;
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (token === '--model') {
|
|
1001
|
+
model = readOptionValue(rest, index, '--model');
|
|
1002
|
+
index += 1;
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
if (token === '--base-url') {
|
|
1006
|
+
baseUrl = readOptionValue(rest, index, '--base-url');
|
|
1007
|
+
index += 1;
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (token === '--config') {
|
|
1011
|
+
configPath = readOptionValue(rest, index, '--config');
|
|
1012
|
+
index += 1;
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (token === '--soul') {
|
|
1016
|
+
soul = readOptionValue(rest, index, '--soul');
|
|
1017
|
+
index += 1;
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (token === '--format') {
|
|
1021
|
+
const value = readOptionValue(rest, index, '--format');
|
|
1022
|
+
if (value !== 'text' && value !== 'json') {
|
|
1023
|
+
throw new Error(`Unsupported format: ${value}`);
|
|
1024
|
+
}
|
|
1025
|
+
format = value;
|
|
1026
|
+
index += 1;
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
if (token === '--no-events') {
|
|
1030
|
+
events = false;
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
if (token === '--approvals') {
|
|
1034
|
+
const value = readOptionValue(rest, index, '--approvals');
|
|
1035
|
+
if (value !== 'always' && value !== 'ask' && value !== 'gated' && value !== 'never') {
|
|
1036
|
+
throw new Error(`Unsupported approvals mode: ${value}. Use always, ask, gated, or never.`);
|
|
1037
|
+
}
|
|
1038
|
+
approvals = value;
|
|
1039
|
+
index += 1;
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
if (token === '--max-turns') {
|
|
1043
|
+
const value = Number(readOptionValue(rest, index, '--max-turns'));
|
|
1044
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
1045
|
+
throw new Error(`Invalid value for --max-turns: ${rest[index + 1] ?? '(missing)'}`);
|
|
1046
|
+
}
|
|
1047
|
+
maxTurns = value;
|
|
1048
|
+
index += 1;
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (token.startsWith('-')) {
|
|
1052
|
+
throw new Error(`Unknown option: ${token}`);
|
|
1053
|
+
}
|
|
1054
|
+
positionals.push(token);
|
|
1055
|
+
}
|
|
1056
|
+
if (!prompt) {
|
|
1057
|
+
prompt = positionals.join(' ').trim();
|
|
1058
|
+
}
|
|
1059
|
+
if (!prompt && useStdin) {
|
|
1060
|
+
prompt = readPromptFromEnvironment(env);
|
|
1061
|
+
}
|
|
1062
|
+
if (!prompt) {
|
|
1063
|
+
throw new Error('A prompt is required. Pass it with --prompt, --stdin, or as a positional argument.');
|
|
1064
|
+
}
|
|
1065
|
+
if ((approvals === 'ask' || approvals === 'gated') && useStdin) {
|
|
1066
|
+
throw new Error(`--approvals ${approvals} cannot be combined with --stdin because both read from standard input.`);
|
|
1067
|
+
}
|
|
1068
|
+
return {
|
|
1069
|
+
command: 'run',
|
|
1070
|
+
prompt,
|
|
1071
|
+
...(provider ? { provider } : {}),
|
|
1072
|
+
...(model ? { model } : {}),
|
|
1073
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
1074
|
+
...(configPath ? { configPath } : {}),
|
|
1075
|
+
...(soul ? { soul } : {}),
|
|
1076
|
+
format,
|
|
1077
|
+
events,
|
|
1078
|
+
...(approvals !== undefined ? { approvals } : {}),
|
|
1079
|
+
...(maxTurns !== undefined ? { maxTurns } : {}),
|
|
1080
|
+
};
|
|
1081
|
+
};
|
|
1082
|
+
/**
|
|
1083
|
+
* The approval mode a run or chat uses when `--approvals` was not given.
|
|
1084
|
+
*
|
|
1085
|
+
* At a terminal the person is right there, so a gated call asks and a
|
|
1086
|
+
* safe one runs, which is `gated` — the old default of `always` ran
|
|
1087
|
+
* `shell.run` on whatever a page or a soul talked the model into, with
|
|
1088
|
+
* nobody told. Anywhere else — a pipe, a script, `--stdin`, where the
|
|
1089
|
+
* prompt already consumed the terminal — nobody can answer a y/N, and the
|
|
1090
|
+
* run keeps its unattended behavior, saying so once on stderr when a
|
|
1091
|
+
* gated call runs (see `createApprovalPolicy`).
|
|
1092
|
+
*/
|
|
1093
|
+
export const defaultApprovalMode = (env, promptFromStdin = false) => {
|
|
1094
|
+
const terminal = env.terminal
|
|
1095
|
+
?? (env.stdinStream === undefined && env.stdin === undefined && process.stdin.isTTY === true);
|
|
1096
|
+
return terminal && !promptFromStdin ? 'gated' : 'always';
|
|
1097
|
+
};
|
|
1098
|
+
//# sourceMappingURL=parse.js.map
|