@moda-ai/cli 1.10.2 → 1.11.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/dist/agent-schemas.js +60 -3
- package/dist/auth.js +5 -1
- package/dist/cli.js +124 -38
- package/dist/config.js +5 -3
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +40 -0
- package/dist/exit-funnel.d.ts +14 -0
- package/dist/exit-funnel.js +49 -0
- package/dist/harness.js +18 -6
- package/dist/init/index.js +57 -4
- package/dist/init/tenant.d.ts +6 -1
- package/dist/init/tenant.js +35 -11
- package/dist/manifest.d.ts +5 -0
- package/dist/manifest.js +111 -0
- package/dist/output.d.ts +44 -4
- package/dist/output.js +337 -32
- package/dist/prompts.d.ts +2 -1
- package/dist/prompts.js +38 -13
- package/dist/redact.d.ts +2 -0
- package/dist/redact.js +81 -0
- package/dist/schemas.d.ts +2 -2
- package/dist/skills.d.ts +8 -2
- package/dist/skills.js +33 -19
- package/package.json +1 -1
- package/skills/moda-cli/SKILL.md +51 -7
package/dist/output.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { CliAuthError, CliCancellationError, CliInputError, CliInputRequiredError, isApiLikeError, isNetworkLikeError, } from './errors.js';
|
|
3
|
+
import { redactDeep } from './redact.js';
|
|
1
4
|
export const AGENT_SCHEMA_VERSION = 'moda.agent.v1';
|
|
2
5
|
export const AGENT_EVENT_SCHEMA_VERSION = 'moda.agent_event.v1';
|
|
3
6
|
/**
|
|
@@ -5,12 +8,100 @@ export const AGENT_EVENT_SCHEMA_VERSION = 'moda.agent_event.v1';
|
|
|
5
8
|
* 0 ok — grounded answer from Moda Cloud intelligence.
|
|
6
9
|
* 3 degraded — answer produced from a local/degraded fallback (lower trust).
|
|
7
10
|
* 1 error — command/input/unexpected failure (see envelope `errors[]`).
|
|
11
|
+
* 4 auth — login or token renewal required before retrying.
|
|
12
|
+
* 5 input — non-interactive caller must choose and re-invoke.
|
|
13
|
+
* 130 cancel — interrupted or the output consumer closed the pipe.
|
|
8
14
|
* `EXIT_DEGRADED` is intentionally distinct so a caller can tell a degraded
|
|
9
15
|
* answer apart from both success and hard failure without parsing the payload.
|
|
10
16
|
*/
|
|
11
17
|
export const EXIT_OK = 0;
|
|
12
18
|
export const EXIT_ERROR = 1;
|
|
13
19
|
export const EXIT_DEGRADED = 3;
|
|
20
|
+
export const EXIT_AUTH_REQUIRED = 4;
|
|
21
|
+
export const EXIT_INPUT_REQUIRED = 5;
|
|
22
|
+
export const EXIT_CANCELLED = 130;
|
|
23
|
+
export const AGENT_ERROR_CODES = [
|
|
24
|
+
'input_error',
|
|
25
|
+
'api_error',
|
|
26
|
+
'network_error',
|
|
27
|
+
'auth_required',
|
|
28
|
+
'input_required',
|
|
29
|
+
'cancelled',
|
|
30
|
+
'command_error',
|
|
31
|
+
'unknown_error',
|
|
32
|
+
'cloud_unavailable',
|
|
33
|
+
];
|
|
34
|
+
const FALLBACK_RUN_ID = randomUUID();
|
|
35
|
+
let fallbackSeq = 0;
|
|
36
|
+
let stdoutClosed = false;
|
|
37
|
+
let stderrClosed = false;
|
|
38
|
+
process.stdout.on('error', (error) => {
|
|
39
|
+
if (error.code === 'EPIPE') {
|
|
40
|
+
stdoutClosed = true;
|
|
41
|
+
process.exitCode = EXIT_CANCELLED;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
});
|
|
46
|
+
process.stderr.on('error', (error) => {
|
|
47
|
+
if (error.code === 'EPIPE') {
|
|
48
|
+
stderrClosed = true;
|
|
49
|
+
process.exitCode = EXIT_CANCELLED;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* True once an EPIPE from a closed stdout/stderr consumer has been observed.
|
|
56
|
+
* The stream error listeners above own this state; expose it so callers can
|
|
57
|
+
* detect consumer-cancelled runs explicitly instead of sniffing the global
|
|
58
|
+
* `process.exitCode`.
|
|
59
|
+
*/
|
|
60
|
+
export function wasOutputCancelled() {
|
|
61
|
+
return stdoutClosed || stderrClosed;
|
|
62
|
+
}
|
|
63
|
+
export function safeWriteStdout(text) {
|
|
64
|
+
if (stdoutClosed)
|
|
65
|
+
return false;
|
|
66
|
+
try {
|
|
67
|
+
if (process.env.NODE_ENV === 'test') {
|
|
68
|
+
console.log(text.endsWith('\n') ? text.slice(0, -1) : text);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
process.stdout.write(text);
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error.code === 'EPIPE') {
|
|
77
|
+
stdoutClosed = true;
|
|
78
|
+
process.exitCode = EXIT_CANCELLED;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function safeWriteStderr(text) {
|
|
85
|
+
if (stderrClosed)
|
|
86
|
+
return false;
|
|
87
|
+
try {
|
|
88
|
+
if (process.env.NODE_ENV === 'test') {
|
|
89
|
+
console.error(text.endsWith('\n') ? text.slice(0, -1) : text);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
process.stderr.write(text);
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error.code === 'EPIPE') {
|
|
98
|
+
stderrClosed = true;
|
|
99
|
+
process.exitCode = EXIT_CANCELLED;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
14
105
|
/**
|
|
15
106
|
* Output mode requested by explicit flags only, or `undefined` when the caller
|
|
16
107
|
* did not pick one. Explicit flags always win over auto-detection.
|
|
@@ -75,80 +166,198 @@ export function resolveOutputMode(flags, opts = {}) {
|
|
|
75
166
|
return 'human';
|
|
76
167
|
}
|
|
77
168
|
export function createCliOutput(options) {
|
|
78
|
-
|
|
169
|
+
const state = {
|
|
170
|
+
runId: options.runId ?? randomUUID(),
|
|
171
|
+
seq: 0,
|
|
172
|
+
started: false,
|
|
173
|
+
terminal: false,
|
|
174
|
+
startedAt: Date.now(),
|
|
175
|
+
lastEventAt: Date.now(),
|
|
176
|
+
};
|
|
177
|
+
const emitEvent = (event) => {
|
|
178
|
+
if (options.quiet || options.mode !== 'agent-stream')
|
|
179
|
+
return;
|
|
180
|
+
if (state.terminal)
|
|
181
|
+
return;
|
|
182
|
+
if (event.event === 'started' && state.started)
|
|
183
|
+
return;
|
|
184
|
+
if (event.event === 'started')
|
|
185
|
+
state.started = true;
|
|
186
|
+
if (isTerminalEvent(event.event)) {
|
|
187
|
+
state.terminal = true;
|
|
188
|
+
clearHeartbeat(state);
|
|
189
|
+
}
|
|
190
|
+
state.lastEventAt = Date.now();
|
|
191
|
+
safeWriteStdout(`${JSON.stringify(createAgentEvent(options.command, event, state))}\n`);
|
|
192
|
+
};
|
|
193
|
+
const ensureStarted = () => {
|
|
194
|
+
if (options.mode !== 'agent-stream' || state.started || options.quiet)
|
|
195
|
+
return;
|
|
196
|
+
startHeartbeat(options.command, state);
|
|
197
|
+
emitEvent({
|
|
198
|
+
event: 'started',
|
|
199
|
+
phase: options.command,
|
|
200
|
+
message: `Starting ${options.command}.`,
|
|
201
|
+
elapsed_ms: 0,
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
const writeEnvelope = (data, writeOptions = {}) => {
|
|
205
|
+
return createAgentEnvelope({
|
|
206
|
+
command: options.command,
|
|
207
|
+
status: writeOptions.status === 'error'
|
|
208
|
+
? 'error'
|
|
209
|
+
: writeOptions.status === 'degraded'
|
|
210
|
+
? 'degraded'
|
|
211
|
+
: 'ok',
|
|
212
|
+
data,
|
|
213
|
+
meta: {
|
|
214
|
+
...(writeOptions.meta ?? {}),
|
|
215
|
+
run_id: state.runId,
|
|
216
|
+
},
|
|
217
|
+
summary: writeOptions.summary,
|
|
218
|
+
findings: writeOptions.findings,
|
|
219
|
+
evidenceRefs: writeOptions.evidenceRefs,
|
|
220
|
+
recommendedActions: writeOptions.recommendedActions,
|
|
221
|
+
nextCommands: writeOptions.nextCommands,
|
|
222
|
+
artifacts: writeOptions.artifacts,
|
|
223
|
+
warnings: writeOptions.warnings,
|
|
224
|
+
errors: writeOptions.errors,
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
const output = {
|
|
79
228
|
mode: options.mode,
|
|
80
229
|
quiet: options.quiet === true,
|
|
81
230
|
writeData(data, writeOptions = {}) {
|
|
82
231
|
if (options.quiet)
|
|
83
232
|
return;
|
|
84
233
|
if (options.mode === 'agent' || options.mode === 'agent-stream') {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
? 'error'
|
|
89
|
-
: writeOptions.status === 'degraded'
|
|
90
|
-
? 'degraded'
|
|
91
|
-
: 'ok',
|
|
92
|
-
data,
|
|
93
|
-
meta: writeOptions.meta,
|
|
94
|
-
summary: writeOptions.summary,
|
|
95
|
-
findings: writeOptions.findings,
|
|
96
|
-
evidenceRefs: writeOptions.evidenceRefs,
|
|
97
|
-
recommendedActions: writeOptions.recommendedActions,
|
|
98
|
-
nextCommands: writeOptions.nextCommands,
|
|
99
|
-
artifacts: writeOptions.artifacts,
|
|
100
|
-
warnings: writeOptions.warnings,
|
|
101
|
-
errors: writeOptions.errors,
|
|
102
|
-
});
|
|
234
|
+
if (state.terminal)
|
|
235
|
+
return;
|
|
236
|
+
const envelope = writeEnvelope(data, writeOptions);
|
|
103
237
|
if (options.mode === 'agent-stream') {
|
|
104
|
-
|
|
238
|
+
ensureStarted();
|
|
239
|
+
emitEvent({
|
|
105
240
|
event: envelope.status === 'error' ? 'error' : 'completed',
|
|
106
241
|
status: envelope.status,
|
|
107
242
|
result: envelope,
|
|
108
|
-
|
|
243
|
+
...(envelope.errors[0] ? { error: envelope.errors[0] } : {}),
|
|
244
|
+
});
|
|
109
245
|
}
|
|
110
246
|
else {
|
|
111
|
-
|
|
247
|
+
state.terminal = true;
|
|
248
|
+
safeWriteStdout(`${JSON.stringify(envelope)}\n`);
|
|
112
249
|
}
|
|
113
250
|
return;
|
|
114
251
|
}
|
|
115
|
-
|
|
252
|
+
safeWriteStdout(`${JSON.stringify(redactDeep(data), null, 2)}\n`);
|
|
116
253
|
},
|
|
117
254
|
writeEvent(event) {
|
|
118
255
|
if (options.quiet)
|
|
119
256
|
return;
|
|
120
257
|
if (options.mode !== 'agent-stream')
|
|
121
258
|
return;
|
|
122
|
-
|
|
259
|
+
if (event.event === 'started') {
|
|
260
|
+
startHeartbeat(options.command, state);
|
|
261
|
+
emitEvent(event);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
ensureStarted();
|
|
265
|
+
emitEvent(event);
|
|
266
|
+
},
|
|
267
|
+
writeError(error) {
|
|
268
|
+
if (options.quiet || state.terminal)
|
|
269
|
+
return;
|
|
270
|
+
const agentError = errorToAgentError(error);
|
|
271
|
+
const envelope = createAgentErrorEnvelope(options.command || 'unknown', agentError, state.runId);
|
|
272
|
+
if (options.mode === 'agent-stream') {
|
|
273
|
+
ensureStarted();
|
|
274
|
+
if (error instanceof CliInputRequiredError) {
|
|
275
|
+
emitEvent({
|
|
276
|
+
event: 'needs_input',
|
|
277
|
+
status: 'error',
|
|
278
|
+
message: error.message,
|
|
279
|
+
error: agentError,
|
|
280
|
+
input_request: error.input_request,
|
|
281
|
+
result: envelope,
|
|
282
|
+
});
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (error instanceof CliAuthError) {
|
|
286
|
+
emitEvent({
|
|
287
|
+
event: 'auth_required',
|
|
288
|
+
status: 'error',
|
|
289
|
+
message: error.message,
|
|
290
|
+
error: agentError,
|
|
291
|
+
auth: error.auth,
|
|
292
|
+
result: envelope,
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (error instanceof CliCancellationError) {
|
|
297
|
+
emitEvent({
|
|
298
|
+
event: 'cancelled',
|
|
299
|
+
status: 'error',
|
|
300
|
+
message: error.message,
|
|
301
|
+
error: agentError,
|
|
302
|
+
result: envelope,
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
emitEvent({
|
|
307
|
+
event: 'error',
|
|
308
|
+
status: 'error',
|
|
309
|
+
message: agentError.message,
|
|
310
|
+
error: agentError,
|
|
311
|
+
result: envelope,
|
|
312
|
+
});
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (options.mode === 'agent' || options.mode === 'json') {
|
|
316
|
+
state.terminal = true;
|
|
317
|
+
safeWriteStdout(`${JSON.stringify(envelope)}\n`);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
writeCancelled(reason = 'Command cancelled.') {
|
|
321
|
+
output.writeError(new CliCancellationError(reason));
|
|
322
|
+
},
|
|
323
|
+
hasTerminalOutput() {
|
|
324
|
+
return state.terminal;
|
|
325
|
+
},
|
|
326
|
+
close() {
|
|
327
|
+
clearHeartbeat(state);
|
|
123
328
|
},
|
|
124
329
|
};
|
|
330
|
+
return output;
|
|
125
331
|
}
|
|
126
332
|
export function createAgentEnvelope(options) {
|
|
127
|
-
const
|
|
128
|
-
|
|
333
|
+
const safeData = redactDeep(options.data);
|
|
334
|
+
const bounded = boundForAgent(safeData);
|
|
335
|
+
const envelope = {
|
|
129
336
|
schema_version: AGENT_SCHEMA_VERSION,
|
|
130
337
|
command: options.command,
|
|
131
338
|
status: options.status,
|
|
132
339
|
generated_at: new Date().toISOString(),
|
|
133
340
|
...(options.profile ? { profile: options.profile } : {}),
|
|
134
|
-
summary: normalizeSummary(options.summary, options.command, options.status,
|
|
341
|
+
summary: normalizeSummary(options.summary, options.command, options.status, safeData),
|
|
135
342
|
findings: options.findings ?? [],
|
|
136
343
|
evidence_refs: options.evidenceRefs ?? [],
|
|
137
344
|
recommended_actions: options.recommendedActions ?? [],
|
|
138
345
|
next_commands: normalizeNextCommands(options.nextCommands ?? []),
|
|
139
346
|
artifacts: options.artifacts ?? [],
|
|
140
|
-
warnings: options.warnings ?? inferWarnings(
|
|
347
|
+
warnings: options.warnings ?? inferWarnings(safeData),
|
|
141
348
|
errors: options.errors ?? [],
|
|
142
349
|
data: bounded.data,
|
|
143
350
|
...(options.meta ? { meta: options.meta } : {}),
|
|
144
351
|
...(bounded.truncation ? { truncation: bounded.truncation } : {}),
|
|
145
352
|
};
|
|
353
|
+
return redactDeep(envelope);
|
|
146
354
|
}
|
|
147
|
-
export function createAgentErrorEnvelope(command, error) {
|
|
355
|
+
export function createAgentErrorEnvelope(command, error, runId) {
|
|
148
356
|
return createAgentEnvelope({
|
|
149
357
|
command,
|
|
150
358
|
status: 'error',
|
|
151
359
|
data: null,
|
|
360
|
+
meta: runId ? { run_id: runId } : undefined,
|
|
152
361
|
summary: {
|
|
153
362
|
text: error.message,
|
|
154
363
|
confidence: 'high',
|
|
@@ -162,18 +371,81 @@ export function createAgentErrorEnvelope(command, error) {
|
|
|
162
371
|
})),
|
|
163
372
|
});
|
|
164
373
|
}
|
|
165
|
-
export function createAgentEvent(command, event) {
|
|
166
|
-
|
|
374
|
+
export function createAgentEvent(command, event, state) {
|
|
375
|
+
const stamped = {
|
|
167
376
|
schema_version: AGENT_EVENT_SCHEMA_VERSION,
|
|
168
377
|
command,
|
|
169
378
|
generated_at: new Date().toISOString(),
|
|
379
|
+
run_id: state?.runId ?? FALLBACK_RUN_ID,
|
|
380
|
+
seq: state ? ++state.seq : ++fallbackSeq,
|
|
381
|
+
event_version: event.event_version ?? 1,
|
|
170
382
|
...event,
|
|
171
383
|
};
|
|
384
|
+
return redactDeep(stamped);
|
|
172
385
|
}
|
|
173
386
|
export function errorToAgentError(error) {
|
|
387
|
+
if (error instanceof CliInputRequiredError) {
|
|
388
|
+
return {
|
|
389
|
+
code: 'input_required',
|
|
390
|
+
message: error.message,
|
|
391
|
+
cause: error.usage,
|
|
392
|
+
suggested_commands: error.input_request.resume_command
|
|
393
|
+
? [error.input_request.resume_command.join(' ')]
|
|
394
|
+
: undefined,
|
|
395
|
+
input_request: error.input_request,
|
|
396
|
+
blocking: true,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (error instanceof CliAuthError) {
|
|
400
|
+
const suggested = [error.auth.login_command.join(' ')];
|
|
401
|
+
if (error.auth.resume_command)
|
|
402
|
+
suggested.push(error.auth.resume_command.join(' '));
|
|
403
|
+
return {
|
|
404
|
+
code: 'auth_required',
|
|
405
|
+
message: error.message,
|
|
406
|
+
cause: error.usage,
|
|
407
|
+
suggested_commands: suggested,
|
|
408
|
+
auth: error.auth,
|
|
409
|
+
blocking: true,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
if (error instanceof CliCancellationError) {
|
|
413
|
+
return {
|
|
414
|
+
code: 'cancelled',
|
|
415
|
+
message: error.message,
|
|
416
|
+
blocking: true,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
174
419
|
if (error instanceof Error) {
|
|
420
|
+
if (error instanceof CliInputError || error.name === 'ZodError') {
|
|
421
|
+
return {
|
|
422
|
+
code: 'input_error',
|
|
423
|
+
message: error.message,
|
|
424
|
+
cause: 'usage' in error && typeof error.usage === 'string' ? error.usage : undefined,
|
|
425
|
+
suggested_commands: 'usage' in error && typeof error.usage === 'string'
|
|
426
|
+
? extractSuggestedCommands(error.usage)
|
|
427
|
+
: undefined,
|
|
428
|
+
blocking: true,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
if (isApiLikeError(error)) {
|
|
432
|
+
const statusCode = 'statusCode' in error ? error.statusCode : undefined;
|
|
433
|
+
return {
|
|
434
|
+
code: 'api_error',
|
|
435
|
+
message: error.message,
|
|
436
|
+
cause: typeof statusCode === 'number' ? `HTTP ${statusCode}` : undefined,
|
|
437
|
+
blocking: true,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (isNetworkLikeError(error)) {
|
|
441
|
+
return {
|
|
442
|
+
code: 'network_error',
|
|
443
|
+
message: error.message,
|
|
444
|
+
blocking: true,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
175
447
|
return {
|
|
176
|
-
code:
|
|
448
|
+
code: 'command_error',
|
|
177
449
|
message: error.message,
|
|
178
450
|
cause: 'usage' in error && typeof error.usage === 'string' ? error.usage : undefined,
|
|
179
451
|
suggested_commands: 'usage' in error && typeof error.usage === 'string'
|
|
@@ -188,6 +460,39 @@ export function errorToAgentError(error) {
|
|
|
188
460
|
blocking: true,
|
|
189
461
|
};
|
|
190
462
|
}
|
|
463
|
+
function isTerminalEvent(event) {
|
|
464
|
+
return event === 'completed' ||
|
|
465
|
+
event === 'error' ||
|
|
466
|
+
event === 'cancelled' ||
|
|
467
|
+
event === 'needs_input' ||
|
|
468
|
+
event === 'auth_required';
|
|
469
|
+
}
|
|
470
|
+
function startHeartbeat(command, state) {
|
|
471
|
+
if (state.heartbeat)
|
|
472
|
+
return;
|
|
473
|
+
const intervalMs = Number(process.env.MODA_HEARTBEAT_MS ?? 10_000);
|
|
474
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0)
|
|
475
|
+
return;
|
|
476
|
+
state.heartbeat = setInterval(() => {
|
|
477
|
+
if (state.terminal || stdoutClosed)
|
|
478
|
+
return;
|
|
479
|
+
const now = Date.now();
|
|
480
|
+
if (now - state.lastEventAt < intervalMs)
|
|
481
|
+
return;
|
|
482
|
+
state.lastEventAt = now;
|
|
483
|
+
safeWriteStdout(`${JSON.stringify(createAgentEvent(command, {
|
|
484
|
+
event: 'heartbeat',
|
|
485
|
+
elapsed_ms: now - state.startedAt,
|
|
486
|
+
}, state))}\n`);
|
|
487
|
+
}, intervalMs);
|
|
488
|
+
state.heartbeat.unref();
|
|
489
|
+
}
|
|
490
|
+
function clearHeartbeat(state) {
|
|
491
|
+
if (!state.heartbeat)
|
|
492
|
+
return;
|
|
493
|
+
clearInterval(state.heartbeat);
|
|
494
|
+
state.heartbeat = undefined;
|
|
495
|
+
}
|
|
191
496
|
function normalizeSummary(summary, command, status, data) {
|
|
192
497
|
if (typeof summary === 'string') {
|
|
193
498
|
return { text: summary, confidence: 'high' };
|
package/dist/prompts.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type CommandContext } from './command-context.js';
|
|
1
2
|
import type { ProfileIOOptions } from './profiles.js';
|
|
2
|
-
export declare function runPromptsCommand(
|
|
3
|
+
export declare function runPromptsCommand(input: CommandContext | string[], legacyFlags?: Record<string, string>, legacyProfileOptions?: ProfileIOOptions): Promise<void>;
|
|
3
4
|
export declare function initPrompts(options?: {
|
|
4
5
|
silent?: boolean;
|
|
5
6
|
}): Promise<void>;
|
package/dist/prompts.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, writeFileSync, } from 'node:fs';
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
5
5
|
import { callControlAPI } from './api-client.js';
|
|
6
|
+
import { createCommandContext } from './command-context.js';
|
|
6
7
|
const MODA_DIR = '.moda';
|
|
7
8
|
const MANIFEST_PATH = join(MODA_DIR, 'prompts.yml');
|
|
8
9
|
const LOCK_PATH = join(MODA_DIR, 'prompts.lock.json');
|
|
@@ -12,23 +13,43 @@ const DEFAULT_PROMPT_PATHS = [
|
|
|
12
13
|
'prompts/**/*.prompt.yaml',
|
|
13
14
|
'prompts/**/*.prompt.yml',
|
|
14
15
|
];
|
|
15
|
-
export async function runPromptsCommand(
|
|
16
|
+
export async function runPromptsCommand(input, legacyFlags, legacyProfileOptions) {
|
|
17
|
+
const context = Array.isArray(input)
|
|
18
|
+
? createCommandContext({
|
|
19
|
+
command: 'prompts',
|
|
20
|
+
positional: input[0],
|
|
21
|
+
positionals: input,
|
|
22
|
+
flags: legacyFlags ?? {},
|
|
23
|
+
}, {
|
|
24
|
+
commandFlags: legacyFlags ?? {},
|
|
25
|
+
outputMode: legacyFlags?.agent === 'true' ? 'agent' : legacyFlags?.json === 'true' ? 'json' : 'human',
|
|
26
|
+
env: legacyProfileOptions?.env,
|
|
27
|
+
})
|
|
28
|
+
: input;
|
|
29
|
+
const positionals = context.positionals;
|
|
30
|
+
const flags = context.flags;
|
|
31
|
+
const profileOptions = legacyProfileOptions ?? { profile: context.profile, env: context.env };
|
|
16
32
|
const subcommand = positionals[0] || 'status';
|
|
17
33
|
switch (subcommand) {
|
|
18
34
|
case 'init':
|
|
19
|
-
await initPrompts();
|
|
35
|
+
await initPrompts({ silent: true });
|
|
36
|
+
context.output.writeData({
|
|
37
|
+
manifest: MANIFEST_PATH,
|
|
38
|
+
lockfile: LOCK_PATH,
|
|
39
|
+
promptPaths: readManifest().promptPaths,
|
|
40
|
+
});
|
|
20
41
|
break;
|
|
21
42
|
case 'status':
|
|
22
|
-
|
|
43
|
+
context.output.writeData(await getPromptStatus());
|
|
23
44
|
break;
|
|
24
45
|
case 'diff':
|
|
25
|
-
|
|
46
|
+
context.output.writeData(await getPromptStatus());
|
|
26
47
|
break;
|
|
27
48
|
case 'sync':
|
|
28
|
-
await syncPrompts(flags, profileOptions);
|
|
49
|
+
await syncPrompts(flags, profileOptions, context);
|
|
29
50
|
break;
|
|
30
51
|
case 'promote':
|
|
31
|
-
await promotePrompt(positionals, flags, profileOptions);
|
|
52
|
+
await promotePrompt(positionals, flags, profileOptions, context);
|
|
32
53
|
break;
|
|
33
54
|
default:
|
|
34
55
|
throw new Error(`Unknown prompts command '${subcommand}'. Use init, status, diff, sync, or promote.`);
|
|
@@ -52,12 +73,12 @@ export async function initPrompts(options = {}) {
|
|
|
52
73
|
});
|
|
53
74
|
}
|
|
54
75
|
}
|
|
55
|
-
async function syncPrompts(flags, profileOptions) {
|
|
76
|
+
async function syncPrompts(flags, profileOptions, context) {
|
|
56
77
|
if (flags.watch === 'true') {
|
|
57
78
|
await watchPromptSync(flags, profileOptions);
|
|
58
79
|
return;
|
|
59
80
|
}
|
|
60
|
-
await syncPromptsOnce(flags, profileOptions);
|
|
81
|
+
await syncPromptsOnce(flags, profileOptions, context);
|
|
61
82
|
}
|
|
62
83
|
/**
|
|
63
84
|
* Sync the manifest's prompt files to the backend and update the lockfile.
|
|
@@ -88,12 +109,16 @@ export async function runPromptSync(flags, profileOptions) {
|
|
|
88
109
|
}
|
|
89
110
|
return { synced: response.synced ?? [], dryRun };
|
|
90
111
|
}
|
|
91
|
-
async function syncPromptsOnce(flags, profileOptions) {
|
|
112
|
+
async function syncPromptsOnce(flags, profileOptions, context) {
|
|
92
113
|
const result = await runPromptSync(flags, profileOptions);
|
|
93
|
-
|
|
114
|
+
const payload = {
|
|
94
115
|
synced: result.synced,
|
|
95
116
|
lockfile: result.dryRun ? undefined : LOCK_PATH,
|
|
96
|
-
}
|
|
117
|
+
};
|
|
118
|
+
if (context)
|
|
119
|
+
context.output.writeData(payload);
|
|
120
|
+
else
|
|
121
|
+
printJson(payload);
|
|
97
122
|
}
|
|
98
123
|
async function watchPromptSync(flags, profileOptions) {
|
|
99
124
|
console.error('Watching prompt files. Press Ctrl+C to stop.');
|
|
@@ -111,7 +136,7 @@ async function watchPromptSync(flags, profileOptions) {
|
|
|
111
136
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
112
137
|
}
|
|
113
138
|
}
|
|
114
|
-
async function promotePrompt(positionals, flags, profileOptions) {
|
|
139
|
+
async function promotePrompt(positionals, flags, profileOptions, context) {
|
|
115
140
|
const key = positionals[1];
|
|
116
141
|
const label = flags.label;
|
|
117
142
|
const versionId = flags.version || flags['version-id'];
|
|
@@ -125,7 +150,7 @@ async function promotePrompt(positionals, flags, profileOptions) {
|
|
|
125
150
|
method: 'POST',
|
|
126
151
|
body: JSON.stringify({ label, versionId }),
|
|
127
152
|
}, profileOptions);
|
|
128
|
-
|
|
153
|
+
context.output.writeData(response);
|
|
129
154
|
}
|
|
130
155
|
async function getPromptStatus() {
|
|
131
156
|
await initPrompts({ silent: true });
|
package/dist/redact.d.ts
ADDED
package/dist/redact.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const REDACTED = '[REDACTED]';
|
|
2
|
+
// Pure stack-safety bound. Cycles are handled separately by the `seen` WeakSet,
|
|
3
|
+
// so this only needs to be high enough that legitimate nested payloads (the
|
|
4
|
+
// agent-envelope wrapper plus arbitrary customer tool-call JSON) are never
|
|
5
|
+
// silently truncated to '[MaxDepth]'. 256 is far below any real recursion limit.
|
|
6
|
+
const MAX_DEPTH = 256;
|
|
7
|
+
const SECRET_KEYS = new Set([
|
|
8
|
+
'api_key',
|
|
9
|
+
'apikey',
|
|
10
|
+
'token',
|
|
11
|
+
'access_token',
|
|
12
|
+
'authorization',
|
|
13
|
+
'secret',
|
|
14
|
+
'password',
|
|
15
|
+
'cli_token',
|
|
16
|
+
]);
|
|
17
|
+
const SECRET_PATTERNS = [
|
|
18
|
+
{ pattern: /\bmoda_sk_[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
|
|
19
|
+
{ pattern: /\bmoda_ct_[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
|
|
20
|
+
{ pattern: /\bBearer\s+([A-Za-z0-9._~+/-]+=*)\b/gi, prefixLength: 8 },
|
|
21
|
+
{ pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, prefixLength: 8 },
|
|
22
|
+
];
|
|
23
|
+
export function redactDeep(value) {
|
|
24
|
+
return redactValue(value, 0, new WeakSet());
|
|
25
|
+
}
|
|
26
|
+
function redactValue(value, depth, seen) {
|
|
27
|
+
if (typeof value === 'string')
|
|
28
|
+
return redactString(value);
|
|
29
|
+
if (value === null || typeof value !== 'object')
|
|
30
|
+
return value;
|
|
31
|
+
if (depth >= MAX_DEPTH)
|
|
32
|
+
return '[MaxDepth]';
|
|
33
|
+
if (seen.has(value))
|
|
34
|
+
return '[Circular]';
|
|
35
|
+
seen.add(value);
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
try {
|
|
38
|
+
return value.map((item) => redactValue(item, depth + 1, seen));
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
seen.delete(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const out = {};
|
|
45
|
+
try {
|
|
46
|
+
for (const [key, item] of Object.entries(value)) {
|
|
47
|
+
out[key] = shouldRedactKey(key) ? redactSecretValue(item) : redactValue(item, depth + 1, seen);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
seen.delete(value);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function shouldRedactKey(key) {
|
|
56
|
+
return SECRET_KEYS.has(key.replace(/[-\s]/g, '_').toLowerCase());
|
|
57
|
+
}
|
|
58
|
+
function redactSecretValue(value) {
|
|
59
|
+
if (typeof value !== 'string')
|
|
60
|
+
return REDACTED;
|
|
61
|
+
const redacted = redactString(value);
|
|
62
|
+
if (redacted !== value)
|
|
63
|
+
return redacted;
|
|
64
|
+
if (/^(configured|missing|not_configured|none|present|absent)$/i.test(value))
|
|
65
|
+
return value;
|
|
66
|
+
return REDACTED;
|
|
67
|
+
}
|
|
68
|
+
function redactString(input) {
|
|
69
|
+
let output = input;
|
|
70
|
+
for (const { pattern, prefixLength } of SECRET_PATTERNS) {
|
|
71
|
+
output = output.replace(pattern, (match) => {
|
|
72
|
+
if (/^Bearer\s+/i.test(match)) {
|
|
73
|
+
const token = match.replace(/^Bearer\s+/i, '');
|
|
74
|
+
return `Bearer ${token.slice(0, prefixLength)}...[REDACTED]`;
|
|
75
|
+
}
|
|
76
|
+
return `${match.slice(0, prefixLength)}...[REDACTED]`;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return output;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=redact.js.map
|
package/dist/schemas.d.ts
CHANGED
|
@@ -126,20 +126,20 @@ export declare const ToolFailureDetailSchema: z.ZodObject<{
|
|
|
126
126
|
window: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
127
127
|
}, "strip", z.ZodTypeAny, {
|
|
128
128
|
tool_name: string;
|
|
129
|
+
subtype?: string | undefined;
|
|
129
130
|
days_back?: number | undefined;
|
|
130
131
|
limit?: number | undefined;
|
|
131
132
|
offset?: number | undefined;
|
|
132
133
|
window?: number | undefined;
|
|
133
134
|
include_window?: boolean | undefined;
|
|
134
|
-
subtype?: string | undefined;
|
|
135
135
|
}, {
|
|
136
136
|
tool_name: string;
|
|
137
|
+
subtype?: string | undefined;
|
|
137
138
|
days_back?: number | undefined;
|
|
138
139
|
limit?: number | undefined;
|
|
139
140
|
offset?: number | undefined;
|
|
140
141
|
window?: number | undefined;
|
|
141
142
|
include_window?: unknown;
|
|
142
|
-
subtype?: string | undefined;
|
|
143
143
|
}>;
|
|
144
144
|
/**
|
|
145
145
|
* moda audit <conversation_id|trace_id> — raw, non-deduped span/trace read.
|