@moda-ai/cli 1.1.1 → 1.3.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 +36 -0
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +477 -0
- package/dist/analyst-tui.d.ts +81 -0
- package/dist/analyst-tui.js +314 -0
- package/dist/api-client.d.ts +4 -0
- package/dist/api-client.js +20 -3
- package/dist/auth.d.ts +22 -3
- package/dist/auth.js +230 -66
- package/dist/cli.d.ts +20 -3
- package/dist/cli.js +745 -93
- package/dist/command-context.d.ts +34 -0
- package/dist/command-context.js +38 -0
- package/dist/command-registry.d.ts +22 -0
- package/dist/command-registry.js +15 -0
- package/dist/config.d.ts +40 -1
- package/dist/config.js +70 -3
- package/dist/doctor.d.ts +79 -0
- package/dist/doctor.js +1111 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +9 -0
- package/dist/harness-report.d.ts +138 -0
- package/dist/harness-report.js +1464 -0
- package/dist/harness.d.ts +233 -0
- package/dist/harness.js +3264 -0
- package/dist/init/index.d.ts +30 -1
- package/dist/init/index.js +389 -23
- package/dist/init/rules.d.ts +5 -1
- package/dist/init/rules.js +22 -16
- package/dist/init/sdk-guide.d.ts +3 -0
- package/dist/init/sdk-guide.js +397 -0
- package/dist/init/tenant.js +12 -3
- package/dist/keys.d.ts +47 -0
- package/dist/keys.js +136 -0
- package/dist/output.d.ts +118 -0
- package/dist/output.js +228 -0
- package/dist/production-intelligence.d.ts +134 -0
- package/dist/production-intelligence.js +1113 -0
- package/dist/profile-commands.d.ts +10 -0
- package/dist/profile-commands.js +252 -0
- package/dist/profiles.d.ts +90 -0
- package/dist/profiles.js +432 -0
- package/dist/prompts.d.ts +6 -0
- package/dist/prompts.js +368 -0
- package/dist/schemas.d.ts +4 -4
- package/dist/skills.d.ts +25 -0
- package/dist/skills.js +468 -0
- package/dist/sync.d.ts +5 -0
- package/dist/sync.js +27 -0
- package/dist/telemetry.d.ts +18 -0
- package/dist/telemetry.js +63 -0
- package/dist/tui.d.ts +40 -0
- package/dist/tui.js +387 -0
- package/dist/workspace.d.ts +12 -0
- package/dist/workspace.js +1294 -0
- package/hooks/moda-snapshot.sh +41 -0
- package/package.json +10 -7
- package/skills/moda-cli/SKILL.md +48 -4
- package/skills/integrate-moda/SKILL.md +0 -63
package/dist/cli.js
CHANGED
|
@@ -1,43 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { validateConfig, VERSION } from './config.js';
|
|
4
|
-
import { callDataAPI, ApiError } from './api-client.js';
|
|
3
|
+
import { validateConfig, VERSION, resolveApiKey } from './config.js';
|
|
4
|
+
import { callDataAPI, ApiError, getApiRequestCount, resetApiRequestCount, } from './api-client.js';
|
|
5
5
|
import { OverviewSchema, ClustersSchema, ClusterConversationsSchema, ConversationsSchema, ContextSchema, FrustrationsSchema, ToolFailuresSchema, ToolFailureDetailSchema, WorkflowsSchema, RunsSchema, RunDetailSchema, LoopsSchema, LoopDetailSchema, } from './schemas.js';
|
|
6
6
|
import { withFrustrationAnchors, withToolFailureAnchors, embeddedWindowSize, asRecord, asString, asNumber } from './transforms.js';
|
|
7
|
+
import { runPromptsCommand } from './prompts.js';
|
|
8
|
+
// Coding-agent capture (shadow workspace / Shadow Spark / trace hooks) is temporarily
|
|
9
|
+
// disabled while the feature is finished — see the commented-out command registrations below.
|
|
10
|
+
// import { runHooksInstall, runSparkCommand, runWorkspaceCommand } from './workspace.js';
|
|
11
|
+
import { runSkillsCommand } from './skills.js';
|
|
12
|
+
import { runHarnessCommand } from './harness.js';
|
|
13
|
+
import { runSyncCommand } from './sync.js';
|
|
14
|
+
import { runDoctorCommand, runStatusCommand } from './doctor.js';
|
|
15
|
+
import { CliInputError } from './errors.js';
|
|
16
|
+
import { isTelemetryDisabled, sendCliUsageTelemetry, stripTelemetryFlag, } from './telemetry.js';
|
|
17
|
+
import { createCommandContext } from './command-context.js';
|
|
18
|
+
import { createCommandRegistry } from './command-registry.js';
|
|
19
|
+
import { createAgentErrorEnvelope, errorToAgentError, outputModeFromFlags, } from './output.js';
|
|
20
|
+
import { runAuthCommand, runConfigCommand, runKeysCommand, runProfilesCommand } from './profile-commands.js';
|
|
21
|
+
import { runAgentCommand } from './agent.js';
|
|
22
|
+
import { runProductionCommand } from './production-intelligence.js';
|
|
23
|
+
export { CliInputError } from './errors.js';
|
|
7
24
|
export function parseArgs(argv) {
|
|
8
25
|
const args = argv.slice(2);
|
|
9
|
-
|
|
26
|
+
let command = '';
|
|
10
27
|
const flags = {};
|
|
28
|
+
const positionals = [];
|
|
11
29
|
let positional;
|
|
12
|
-
let i =
|
|
30
|
+
let i = 0;
|
|
13
31
|
while (i < args.length) {
|
|
14
32
|
const arg = args[i];
|
|
15
33
|
if (arg.startsWith('--')) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
flags[key] = arg.slice(eqIndex + 1);
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
// --key value
|
|
24
|
-
const key = arg.slice(2);
|
|
25
|
-
const next = args[i + 1];
|
|
26
|
-
if (next !== undefined && !next.startsWith('--')) {
|
|
27
|
-
flags[key] = next;
|
|
28
|
-
i++;
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
flags[key] = 'true';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
+
i = parseFlag(args, i, flags, command === '');
|
|
35
|
+
}
|
|
36
|
+
else if (command === '') {
|
|
37
|
+
command = arg;
|
|
34
38
|
}
|
|
35
39
|
else if (positional === undefined) {
|
|
36
40
|
positional = arg;
|
|
41
|
+
positionals.push(arg);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
positionals.push(arg);
|
|
37
45
|
}
|
|
38
46
|
i++;
|
|
39
47
|
}
|
|
40
|
-
return { command, positional, flags };
|
|
48
|
+
return { command, positional, positionals, flags };
|
|
49
|
+
}
|
|
50
|
+
const GLOBAL_BOOLEAN_FLAGS = new Set([
|
|
51
|
+
'agent',
|
|
52
|
+
'json',
|
|
53
|
+
'help',
|
|
54
|
+
'version',
|
|
55
|
+
'quiet',
|
|
56
|
+
'verbose',
|
|
57
|
+
'yes',
|
|
58
|
+
'dry-run',
|
|
59
|
+
'stream',
|
|
60
|
+
'tui',
|
|
61
|
+
'no-tui',
|
|
62
|
+
]);
|
|
63
|
+
function parseFlag(args, index, flags, beforeCommand) {
|
|
64
|
+
const arg = args[index];
|
|
65
|
+
const eqIndex = arg.indexOf('=');
|
|
66
|
+
if (eqIndex !== -1) {
|
|
67
|
+
flags[arg.slice(2, eqIndex)] = arg.slice(eqIndex + 1);
|
|
68
|
+
return index;
|
|
69
|
+
}
|
|
70
|
+
const key = arg.slice(2);
|
|
71
|
+
const next = args[index + 1];
|
|
72
|
+
if (beforeCommand && GLOBAL_BOOLEAN_FLAGS.has(key)) {
|
|
73
|
+
flags[key] = 'true';
|
|
74
|
+
return index;
|
|
75
|
+
}
|
|
76
|
+
if (next !== undefined && !next.startsWith('--')) {
|
|
77
|
+
flags[key] = next;
|
|
78
|
+
return index + 1;
|
|
79
|
+
}
|
|
80
|
+
flags[key] = 'true';
|
|
81
|
+
return index;
|
|
41
82
|
}
|
|
42
83
|
// Convert kebab-case flags to snake_case for Zod schema compatibility
|
|
43
84
|
export function flagsToArgs(flags, positionalKey, positionalValue) {
|
|
@@ -59,7 +100,39 @@ function printHelp() {
|
|
|
59
100
|
|
|
60
101
|
Commands:
|
|
61
102
|
init Set up Moda in your project
|
|
62
|
-
|
|
103
|
+
status Show concise local Moda setup status
|
|
104
|
+
doctor Diagnose local Moda setup and data flow
|
|
105
|
+
auth login Authenticate with Moda
|
|
106
|
+
auth logout Clear cached Moda auth
|
|
107
|
+
auth whoami Show the authenticated Moda user
|
|
108
|
+
profiles list List local Moda profiles
|
|
109
|
+
profiles use <name> Switch active Moda profile
|
|
110
|
+
profiles create <name> Create or update a local Moda profile
|
|
111
|
+
profiles doctor [name] Validate a local Moda profile
|
|
112
|
+
keys list List local key descriptors
|
|
113
|
+
keys create Create a local key descriptor
|
|
114
|
+
config show Show resolved Moda config
|
|
115
|
+
harness analyze Produce a cited structured harness report
|
|
116
|
+
harness validate-report Validate .moda/harness-report.json citations
|
|
117
|
+
harness approve Approve a validated harness report for sync
|
|
118
|
+
harness scan Discover local runtime agents and write .moda/harness.json
|
|
119
|
+
harness sync Sync the local harness graph to Moda Cloud
|
|
120
|
+
harness agents List detected runtime agents and families
|
|
121
|
+
harness explain Explain the discovered harness graph
|
|
122
|
+
agent context Emit local Moda context for coding agents
|
|
123
|
+
agent investigate Diagnose local Moda readiness for agents
|
|
124
|
+
agent next-action Emit prioritized next commands for agents
|
|
125
|
+
agent evidence <ref> Expand one evidence reference
|
|
126
|
+
investigate Rank production issues with evidence
|
|
127
|
+
failures Show production failures worth fixing first
|
|
128
|
+
why-failed <run_id> Explain a failed or partial production run
|
|
129
|
+
ask "question" Ask Moda a production/harness question
|
|
130
|
+
sync Sync the local harness graph to Moda Cloud
|
|
131
|
+
sync harness Sync the local harness graph to Moda Cloud
|
|
132
|
+
sync prompts Sync prompt versions to Moda Cloud
|
|
133
|
+
doctor --json Emit structured diagnostic checks
|
|
134
|
+
status --json Emit concise setup state
|
|
135
|
+
overview Harness health briefing with production signals
|
|
63
136
|
clusters Browse topic cluster hierarchy
|
|
64
137
|
cluster-conversations <node_id> List conversations in a cluster
|
|
65
138
|
conversations Search and filter conversations
|
|
@@ -75,6 +148,19 @@ Commands:
|
|
|
75
148
|
loops Currently-active loop detections
|
|
76
149
|
loop <detection_id> Single loop detail
|
|
77
150
|
|
|
151
|
+
Prompt management:
|
|
152
|
+
prompts init Create .moda/prompts.yml
|
|
153
|
+
prompts status Show local prompt changes
|
|
154
|
+
prompts diff Show local prompt changes
|
|
155
|
+
prompts sync Upload changed prompt versions
|
|
156
|
+
prompts sync --watch Upload again when prompt files change
|
|
157
|
+
prompts promote <key> Move a dev/staging/prod label
|
|
158
|
+
|
|
159
|
+
Skills:
|
|
160
|
+
skills gen Kick off tenant-wide skill generation
|
|
161
|
+
skills pull Pull tenant-generated skills into .claude/skills (and .cursor/rules)
|
|
162
|
+
skills pull --status=proposed Include not-yet-promoted skills (default: approved)
|
|
163
|
+
|
|
78
164
|
Options:
|
|
79
165
|
--help Show this help message
|
|
80
166
|
--version Show version number
|
|
@@ -84,6 +170,14 @@ Common flags:
|
|
|
84
170
|
--limit=N Maximum results to return
|
|
85
171
|
--offset=N Number of results to skip
|
|
86
172
|
--time-range=RANGE Time range filter (all|1h|3d|7d|24h|30d|90d)
|
|
173
|
+
--profile=NAME Use a named local Moda profile
|
|
174
|
+
--json Emit machine-readable JSON
|
|
175
|
+
--agent Emit schema-versioned agent JSON
|
|
176
|
+
--stream With --agent, emit NDJSON agent events
|
|
177
|
+
--dry-run Show the planned mutation when supported
|
|
178
|
+
--yes Accept explicit automation defaults
|
|
179
|
+
--tui Force the human analyst dashboard when supported
|
|
180
|
+
--no-tui Show raw analyst stream instead of dashboard
|
|
87
181
|
|
|
88
182
|
Runs flags:
|
|
89
183
|
--outcome=OUTCOME Filter by outcome (running|succeeded|partial|failed|loop|abandoned|unknown)
|
|
@@ -92,9 +186,43 @@ Runs flags:
|
|
|
92
186
|
|
|
93
187
|
Init flags:
|
|
94
188
|
--yes Accept all defaults (non-interactive)
|
|
189
|
+
--analyst=auto|claude|codex|cursor|local-scan
|
|
190
|
+
Choose harness analyst adapter
|
|
191
|
+
--tui Show full-screen analyst dashboard in human TTYs
|
|
192
|
+
--no-tui Show raw external analyst output
|
|
193
|
+
--sdk-guide Write .moda/SDK_INTEGRATION.md
|
|
194
|
+
--no-sdk-guide Skip SDK integration guide creation
|
|
195
|
+
--ci-cd Create GitHub Actions Moda validation workflow
|
|
196
|
+
--no-ci-cd Skip CI/CD workflow creation
|
|
95
197
|
|
|
96
198
|
Examples:
|
|
97
199
|
moda init
|
|
200
|
+
moda status
|
|
201
|
+
moda doctor --json
|
|
202
|
+
moda profiles list
|
|
203
|
+
moda profiles use staging
|
|
204
|
+
moda config show --profile=prod
|
|
205
|
+
moda harness analyze --summary
|
|
206
|
+
moda harness analyze --analyst=claude --tui
|
|
207
|
+
moda harness analyze --analyst=claude --no-tui
|
|
208
|
+
moda harness analyze --analyst=claude --summary
|
|
209
|
+
moda harness analyze --analyst=local-scan --summary
|
|
210
|
+
moda harness validate-report
|
|
211
|
+
moda harness approve --yes
|
|
212
|
+
moda harness sync --from-report .moda/harness-report.json
|
|
213
|
+
moda --agent doctor
|
|
214
|
+
moda agent context
|
|
215
|
+
moda agent next-action
|
|
216
|
+
moda agent evidence doctor:harness_report_approved_synced
|
|
217
|
+
moda investigate
|
|
218
|
+
moda failures
|
|
219
|
+
moda why-failed 12345678-1234-1234-1234-1234567890ab
|
|
220
|
+
moda ask "what should I fix first?"
|
|
221
|
+
moda sync --dry-run
|
|
222
|
+
moda sync harness --profile=prod
|
|
223
|
+
moda harness scan --json
|
|
224
|
+
moda harness explain --agent=agent_support
|
|
225
|
+
moda init --ci-cd
|
|
98
226
|
moda overview --days-back=30
|
|
99
227
|
moda clusters --time-range=7d
|
|
100
228
|
moda conversations --search="error" --limit=5
|
|
@@ -105,7 +233,11 @@ Examples:
|
|
|
105
233
|
moda runs --class-id=cron:helpscout-triage-hourly --limit=20
|
|
106
234
|
moda run 12345678-1234-1234-1234-1234567890ab
|
|
107
235
|
moda loops --limit=10
|
|
108
|
-
moda loop 12345678-1234-1234-1234-1234567890ab
|
|
236
|
+
moda loop 12345678-1234-1234-1234-1234567890ab
|
|
237
|
+
moda prompts status
|
|
238
|
+
moda prompts sync
|
|
239
|
+
moda prompts promote support.triage --label=prod --version=pver_abc123
|
|
240
|
+
moda skills pull`);
|
|
109
241
|
}
|
|
110
242
|
// --- Command Handlers ---
|
|
111
243
|
async function attachWindows(rows, windowSize) {
|
|
@@ -141,25 +273,37 @@ async function attachWindows(rows, windowSize) {
|
|
|
141
273
|
}
|
|
142
274
|
}));
|
|
143
275
|
}
|
|
144
|
-
async function printWithOptionalWindows(enriched, listKey, includeWindow, windowSize) {
|
|
276
|
+
async function printWithOptionalWindows(enriched, listKey, includeWindow, windowSize, output) {
|
|
145
277
|
if (!includeWindow) {
|
|
146
|
-
|
|
278
|
+
output.writeData(enriched);
|
|
147
279
|
return;
|
|
148
280
|
}
|
|
149
281
|
const list = Array.isArray(enriched[listKey])
|
|
150
282
|
? enriched[listKey]
|
|
151
283
|
: [];
|
|
152
284
|
const withWindows = await attachWindows(list, windowSize);
|
|
153
|
-
|
|
285
|
+
output.writeData({ ...enriched, [listKey]: withWindows });
|
|
154
286
|
}
|
|
155
|
-
export async function runCommand(command, positional, flags) {
|
|
287
|
+
export async function runCommand(command, positional, flags, positionals = positional ? [positional] : [], context = createCommandContext({ command, positional, positionals, flags })) {
|
|
156
288
|
switch (command) {
|
|
289
|
+
case 'prompts': {
|
|
290
|
+
await runPromptsCommand(positionals, flags, {
|
|
291
|
+
profile: context.profile,
|
|
292
|
+
env: context.env,
|
|
293
|
+
});
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
157
296
|
case 'overview': {
|
|
158
297
|
const args = flagsToArgs(flags);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
298
|
+
OverviewSchema.parse(args);
|
|
299
|
+
await runProductionCommand(context);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case 'investigate':
|
|
303
|
+
case 'failures':
|
|
304
|
+
case 'why-failed':
|
|
305
|
+
case 'ask': {
|
|
306
|
+
await runProductionCommand(context);
|
|
163
307
|
break;
|
|
164
308
|
}
|
|
165
309
|
case 'clusters': {
|
|
@@ -172,14 +316,12 @@ export async function runCommand(command, positional, flags) {
|
|
|
172
316
|
query.set('time_range', params.time_range);
|
|
173
317
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
174
318
|
const data = await callDataAPI(`/clusters${queryString}`);
|
|
175
|
-
|
|
319
|
+
context.output.writeData(data);
|
|
176
320
|
break;
|
|
177
321
|
}
|
|
178
322
|
case 'cluster-conversations': {
|
|
179
323
|
if (!positional) {
|
|
180
|
-
|
|
181
|
-
console.error('Usage: moda cluster-conversations <node_id> [--limit=N] [--offset=N]');
|
|
182
|
-
process.exit(1);
|
|
324
|
+
throw new CliInputError('<node_id> is required', 'Usage: moda cluster-conversations <node_id> [--limit=N] [--offset=N]');
|
|
183
325
|
}
|
|
184
326
|
const args = flagsToArgs(flags, 'node_id', positional);
|
|
185
327
|
const params = ClusterConversationsSchema.parse(args);
|
|
@@ -190,7 +332,7 @@ export async function runCommand(command, positional, flags) {
|
|
|
190
332
|
query.set('offset', params.offset.toString());
|
|
191
333
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
192
334
|
const data = await callDataAPI(`/clusters/${params.node_id}/conversations${queryString}`);
|
|
193
|
-
|
|
335
|
+
context.output.writeData(data);
|
|
194
336
|
break;
|
|
195
337
|
}
|
|
196
338
|
case 'conversations': {
|
|
@@ -213,14 +355,12 @@ export async function runCommand(command, positional, flags) {
|
|
|
213
355
|
query.set('offset', params.offset.toString());
|
|
214
356
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
215
357
|
const data = await callDataAPI(`/conversations${queryString}`);
|
|
216
|
-
|
|
358
|
+
context.output.writeData(data);
|
|
217
359
|
break;
|
|
218
360
|
}
|
|
219
361
|
case 'context': {
|
|
220
362
|
if (!positional) {
|
|
221
|
-
|
|
222
|
-
console.error('Usage: moda context <conversation_id> [--msg-index=N] [--window=N]');
|
|
223
|
-
process.exit(1);
|
|
363
|
+
throw new CliInputError('<conversation_id> is required', 'Usage: moda context <conversation_id> [--msg-index=N] [--window=N]');
|
|
224
364
|
}
|
|
225
365
|
const args = flagsToArgs(flags, 'conversation_id', positional);
|
|
226
366
|
const params = ContextSchema.parse(args);
|
|
@@ -231,7 +371,7 @@ export async function runCommand(command, positional, flags) {
|
|
|
231
371
|
query.set('window', params.window.toString());
|
|
232
372
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
233
373
|
const data = await callDataAPI(`/conversations/${params.conversation_id}/context${queryString}`);
|
|
234
|
-
|
|
374
|
+
context.output.writeData(data);
|
|
235
375
|
break;
|
|
236
376
|
}
|
|
237
377
|
case 'frustrations': {
|
|
@@ -246,7 +386,7 @@ export async function runCommand(command, positional, flags) {
|
|
|
246
386
|
query.set('offset', params.offset.toString());
|
|
247
387
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
248
388
|
const data = await callDataAPI(`/frustrations${queryString}`);
|
|
249
|
-
await printWithOptionalWindows(withFrustrationAnchors(data), 'frustrations', params.include_window === true, params.window ?? 1);
|
|
389
|
+
await printWithOptionalWindows(withFrustrationAnchors(data), 'frustrations', params.include_window === true, params.window ?? 1, context.output);
|
|
250
390
|
break;
|
|
251
391
|
}
|
|
252
392
|
case 'tool-failures': {
|
|
@@ -254,14 +394,12 @@ export async function runCommand(command, positional, flags) {
|
|
|
254
394
|
const params = ToolFailuresSchema.parse(args);
|
|
255
395
|
const queryString = params.days_back ? `?days_back=${params.days_back}` : '';
|
|
256
396
|
const data = await callDataAPI(`/tool-failures${queryString}`);
|
|
257
|
-
|
|
397
|
+
context.output.writeData(data);
|
|
258
398
|
break;
|
|
259
399
|
}
|
|
260
400
|
case 'tool-failure-detail': {
|
|
261
401
|
if (!positional) {
|
|
262
|
-
|
|
263
|
-
console.error('Usage: moda tool-failure-detail <tool_name> [--subtype=S] [--days-back=N] [--limit=N] [--offset=N]');
|
|
264
|
-
process.exit(1);
|
|
402
|
+
throw new CliInputError('<tool_name> is required', 'Usage: moda tool-failure-detail <tool_name> [--subtype=S] [--days-back=N] [--limit=N] [--offset=N]');
|
|
265
403
|
}
|
|
266
404
|
const args = flagsToArgs(flags, 'tool_name', positional);
|
|
267
405
|
const params = ToolFailureDetailSchema.parse(args);
|
|
@@ -276,7 +414,7 @@ export async function runCommand(command, positional, flags) {
|
|
|
276
414
|
query.set('offset', params.offset.toString());
|
|
277
415
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
278
416
|
const data = await callDataAPI(`/tool-failures/${params.tool_name}${queryString}`);
|
|
279
|
-
await printWithOptionalWindows(withToolFailureAnchors(data), 'examples', params.include_window === true, params.window ?? 1);
|
|
417
|
+
await printWithOptionalWindows(withToolFailureAnchors(data), 'examples', params.include_window === true, params.window ?? 1, context.output);
|
|
280
418
|
break;
|
|
281
419
|
}
|
|
282
420
|
// ─── Workflows + Runs ──────────────────────────────────────
|
|
@@ -290,7 +428,7 @@ export async function runCommand(command, positional, flags) {
|
|
|
290
428
|
query.set('user_id', params.user_id);
|
|
291
429
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
292
430
|
const data = await callDataAPI(`/workflows${queryString}`);
|
|
293
|
-
|
|
431
|
+
context.output.writeData(data);
|
|
294
432
|
break;
|
|
295
433
|
}
|
|
296
434
|
case 'runs': {
|
|
@@ -311,19 +449,17 @@ export async function runCommand(command, positional, flags) {
|
|
|
311
449
|
query.set('offset', params.offset.toString());
|
|
312
450
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
313
451
|
const data = await callDataAPI(`/runs${queryString}`);
|
|
314
|
-
|
|
452
|
+
context.output.writeData(data);
|
|
315
453
|
break;
|
|
316
454
|
}
|
|
317
455
|
case 'run': {
|
|
318
456
|
if (!positional) {
|
|
319
|
-
|
|
320
|
-
console.error('Usage: moda run <run_id>');
|
|
321
|
-
process.exit(1);
|
|
457
|
+
throw new CliInputError('<run_id> is required', 'Usage: moda run <run_id>');
|
|
322
458
|
}
|
|
323
459
|
const args = flagsToArgs(flags, 'run_id', positional);
|
|
324
460
|
const params = RunDetailSchema.parse(args);
|
|
325
461
|
const data = await callDataAPI(`/runs/${params.run_id}`);
|
|
326
|
-
|
|
462
|
+
context.output.writeData(data);
|
|
327
463
|
break;
|
|
328
464
|
}
|
|
329
465
|
case 'loops': {
|
|
@@ -334,47 +470,578 @@ export async function runCommand(command, positional, flags) {
|
|
|
334
470
|
query.set('limit', params.limit.toString());
|
|
335
471
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
336
472
|
const data = await callDataAPI(`/loops/active${queryString}`);
|
|
337
|
-
|
|
473
|
+
context.output.writeData(data);
|
|
338
474
|
break;
|
|
339
475
|
}
|
|
340
476
|
case 'loop': {
|
|
341
477
|
if (!positional) {
|
|
342
|
-
|
|
343
|
-
console.error('Usage: moda loop <detection_id>');
|
|
344
|
-
process.exit(1);
|
|
478
|
+
throw new CliInputError('<detection_id> is required', 'Usage: moda loop <detection_id>');
|
|
345
479
|
}
|
|
346
480
|
const args = flagsToArgs(flags, 'detection_id', positional);
|
|
347
481
|
const params = LoopDetailSchema.parse(args);
|
|
348
482
|
const data = await callDataAPI(`/loops/${params.detection_id}`);
|
|
349
|
-
|
|
483
|
+
context.output.writeData(data);
|
|
350
484
|
break;
|
|
351
485
|
}
|
|
352
486
|
default:
|
|
353
|
-
|
|
354
|
-
console.error('Run "moda --help" for usage information.');
|
|
355
|
-
process.exit(1);
|
|
487
|
+
throw new CliInputError(`Unknown command '${command}'`, 'Run "moda --help" for usage information.');
|
|
356
488
|
}
|
|
357
489
|
}
|
|
490
|
+
function legacyCommand(metadata) {
|
|
491
|
+
return {
|
|
492
|
+
...metadata,
|
|
493
|
+
handler: async (context) => {
|
|
494
|
+
await runCommand(context.command, context.positional, context.flags, context.positionals, context);
|
|
495
|
+
return { exitCode: 0 };
|
|
496
|
+
},
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function initAgentWriteOptions(result) {
|
|
500
|
+
const changedFiles = initChangedFiles(result.setupPlan);
|
|
501
|
+
const manualActions = result.setupPlan.filter((action) => action.status === 'manual' || action.status === 'skipped');
|
|
502
|
+
return {
|
|
503
|
+
summary: {
|
|
504
|
+
text: `Moda init completed for ${result.tenantName}. ${changedFiles.length} artifact(s) were created or verified.`,
|
|
505
|
+
confidence: 'high',
|
|
506
|
+
},
|
|
507
|
+
findings: result.phases.map((phase) => ({
|
|
508
|
+
id: `init:${phase.id}`,
|
|
509
|
+
title: phase.title,
|
|
510
|
+
status: phase.status,
|
|
511
|
+
description: phase.details,
|
|
512
|
+
})),
|
|
513
|
+
recommendedActions: manualActions.map((action) => ({
|
|
514
|
+
id: `init:action:${action.category}`,
|
|
515
|
+
title: action.title,
|
|
516
|
+
rationale: action.reason,
|
|
517
|
+
command: action.command,
|
|
518
|
+
mutability: action.command?.includes(' sync') ? 'sync' : 'write',
|
|
519
|
+
requires_approval: true,
|
|
520
|
+
})),
|
|
521
|
+
nextCommands: [
|
|
522
|
+
{
|
|
523
|
+
command: 'moda status',
|
|
524
|
+
purpose: 'Inspect the local Moda setup state.',
|
|
525
|
+
mutability: 'read',
|
|
526
|
+
requires_approval: false,
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
command: 'moda doctor',
|
|
530
|
+
purpose: 'Diagnose local setup and data-flow readiness.',
|
|
531
|
+
mutability: 'read',
|
|
532
|
+
requires_approval: false,
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
command: 'moda harness sync --summary',
|
|
536
|
+
purpose: 'Sync the approved harness graph to Moda Cloud after review.',
|
|
537
|
+
mutability: 'sync',
|
|
538
|
+
requires_approval: true,
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
artifacts: changedFiles.map((file) => ({
|
|
542
|
+
kind: initArtifactKind(file),
|
|
543
|
+
path: file,
|
|
544
|
+
description: 'Created or verified during Moda init.',
|
|
545
|
+
})),
|
|
546
|
+
warnings: manualActions
|
|
547
|
+
.filter((action) => action.reason)
|
|
548
|
+
.map((action) => `${action.title}: ${action.reason}`),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function initChangedFiles(setupPlan) {
|
|
552
|
+
return [...new Set(setupPlan
|
|
553
|
+
.filter((action) => action.status === 'applied')
|
|
554
|
+
.flatMap((action) => action.files))].filter((file) => !file.includes('.env'));
|
|
555
|
+
}
|
|
556
|
+
function initArtifactKind(file) {
|
|
557
|
+
if (file.includes('harness'))
|
|
558
|
+
return 'harness_artifact';
|
|
559
|
+
if (file.includes('prompt'))
|
|
560
|
+
return 'prompt_manifest';
|
|
561
|
+
if (file.includes('workflow'))
|
|
562
|
+
return 'ci_workflow';
|
|
563
|
+
if (file.startsWith('~/'))
|
|
564
|
+
return 'agent_hook_config';
|
|
565
|
+
return 'init_artifact';
|
|
566
|
+
}
|
|
567
|
+
const dataApiDefaults = {
|
|
568
|
+
auth: 'api-key',
|
|
569
|
+
outputModes: ['human', 'json', 'agent'],
|
|
570
|
+
mutability: 'read',
|
|
571
|
+
validateConfigBeforeRun: true,
|
|
572
|
+
resetApiRequestCountBeforeRun: true,
|
|
573
|
+
telemetry: 'result-and-error',
|
|
574
|
+
};
|
|
575
|
+
export const commandRegistry = createCommandRegistry([
|
|
576
|
+
{
|
|
577
|
+
name: 'init',
|
|
578
|
+
description: 'Set up Moda in your project',
|
|
579
|
+
examples: ['moda init', 'moda init --coding-agent-traces', 'moda init --ci-cd'],
|
|
580
|
+
auth: 'none',
|
|
581
|
+
outputModes: ['human', 'json', 'agent'],
|
|
582
|
+
mutability: 'write',
|
|
583
|
+
validateConfigBeforeRun: false,
|
|
584
|
+
resetApiRequestCountBeforeRun: true,
|
|
585
|
+
telemetry: 'result',
|
|
586
|
+
handler: async (context) => {
|
|
587
|
+
const { runInit } = await import('./init/index.js');
|
|
588
|
+
if (context.outputMode === 'agent-stream') {
|
|
589
|
+
context.output.writeEvent({
|
|
590
|
+
event: 'started',
|
|
591
|
+
phase: 'init',
|
|
592
|
+
message: 'Starting Moda init.',
|
|
593
|
+
elapsed_ms: Date.now() - context.startedAt,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
const result = await runInit(flagsToArgs(context.flags), {
|
|
597
|
+
onEvent: (event) => {
|
|
598
|
+
context.output.writeEvent({
|
|
599
|
+
event: 'progress',
|
|
600
|
+
phase: event.phase,
|
|
601
|
+
message: event.message,
|
|
602
|
+
elapsed_ms: Date.now() - context.startedAt,
|
|
603
|
+
});
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
if (context.outputMode !== 'human') {
|
|
607
|
+
context.output.writeData(result, initAgentWriteOptions(result));
|
|
608
|
+
}
|
|
609
|
+
return { exitCode: 0, apiKey: result.apiKey };
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: 'auth',
|
|
614
|
+
description: 'Authenticate and inspect browser auth sessions',
|
|
615
|
+
examples: ['moda auth login', 'moda auth whoami', 'moda auth logout'],
|
|
616
|
+
auth: 'none',
|
|
617
|
+
outputModes: ['human', 'json', 'agent'],
|
|
618
|
+
mutability: 'write',
|
|
619
|
+
validateConfigBeforeRun: false,
|
|
620
|
+
resetApiRequestCountBeforeRun: false,
|
|
621
|
+
telemetry: 'none',
|
|
622
|
+
handler: runAuthCommand,
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: 'status',
|
|
626
|
+
description: 'Show concise local Moda setup status',
|
|
627
|
+
examples: ['moda status', 'moda status --json'],
|
|
628
|
+
auth: 'none',
|
|
629
|
+
outputModes: ['human', 'json', 'agent'],
|
|
630
|
+
mutability: 'read',
|
|
631
|
+
validateConfigBeforeRun: false,
|
|
632
|
+
resetApiRequestCountBeforeRun: false,
|
|
633
|
+
telemetry: 'none',
|
|
634
|
+
handler: runStatusCommand,
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
name: 'doctor',
|
|
638
|
+
description: 'Diagnose local Moda setup and data flow',
|
|
639
|
+
examples: ['moda doctor', 'moda doctor --json', 'moda doctor --offline'],
|
|
640
|
+
auth: 'none',
|
|
641
|
+
outputModes: ['human', 'json', 'agent'],
|
|
642
|
+
mutability: 'read',
|
|
643
|
+
validateConfigBeforeRun: false,
|
|
644
|
+
resetApiRequestCountBeforeRun: false,
|
|
645
|
+
telemetry: 'none',
|
|
646
|
+
handler: runDoctorCommand,
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
name: 'profiles',
|
|
650
|
+
description: 'Manage local Moda profiles',
|
|
651
|
+
examples: ['moda profiles list', 'moda profiles use staging'],
|
|
652
|
+
auth: 'none',
|
|
653
|
+
outputModes: ['human', 'json', 'agent'],
|
|
654
|
+
mutability: 'write',
|
|
655
|
+
validateConfigBeforeRun: false,
|
|
656
|
+
resetApiRequestCountBeforeRun: false,
|
|
657
|
+
telemetry: 'none',
|
|
658
|
+
handler: runProfilesCommand,
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
name: 'config',
|
|
662
|
+
description: 'Show resolved local Moda configuration',
|
|
663
|
+
examples: ['moda config show', 'moda config show --profile=prod'],
|
|
664
|
+
auth: 'none',
|
|
665
|
+
outputModes: ['human', 'json', 'agent'],
|
|
666
|
+
mutability: 'read',
|
|
667
|
+
validateConfigBeforeRun: false,
|
|
668
|
+
resetApiRequestCountBeforeRun: false,
|
|
669
|
+
telemetry: 'none',
|
|
670
|
+
handler: runConfigCommand,
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
name: 'keys',
|
|
674
|
+
description: 'Manage local key descriptors and cloud key lifecycle requirements',
|
|
675
|
+
examples: ['moda keys list', 'moda keys create --name=local-dev'],
|
|
676
|
+
auth: 'none',
|
|
677
|
+
outputModes: ['human', 'json', 'agent'],
|
|
678
|
+
mutability: 'write',
|
|
679
|
+
validateConfigBeforeRun: false,
|
|
680
|
+
resetApiRequestCountBeforeRun: false,
|
|
681
|
+
telemetry: 'none',
|
|
682
|
+
handler: runKeysCommand,
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
name: 'harness',
|
|
686
|
+
description: 'Analyze, validate, approve, sync, and explain the local multi-agent harness graph',
|
|
687
|
+
examples: [
|
|
688
|
+
'moda harness analyze --summary',
|
|
689
|
+
'moda harness approve --yes',
|
|
690
|
+
'moda harness sync --from-report .moda/harness-report.json',
|
|
691
|
+
'moda harness explain --agent=agent_support',
|
|
692
|
+
],
|
|
693
|
+
auth: 'none',
|
|
694
|
+
outputModes: ['human', 'json', 'agent'],
|
|
695
|
+
mutability: 'write',
|
|
696
|
+
validateConfigBeforeRun: false,
|
|
697
|
+
resetApiRequestCountBeforeRun: false,
|
|
698
|
+
telemetry: 'none',
|
|
699
|
+
handler: runHarnessCommand,
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
name: 'agent',
|
|
703
|
+
description: 'Agent-native Moda context, investigations, next actions, and evidence',
|
|
704
|
+
examples: [
|
|
705
|
+
'moda agent context',
|
|
706
|
+
'moda agent investigate',
|
|
707
|
+
'moda agent next-action',
|
|
708
|
+
'moda agent evidence doctor:harness_report_approved_synced',
|
|
709
|
+
],
|
|
710
|
+
auth: 'none',
|
|
711
|
+
outputModes: ['agent', 'agent-stream', 'json'],
|
|
712
|
+
mutability: 'read',
|
|
713
|
+
validateConfigBeforeRun: false,
|
|
714
|
+
resetApiRequestCountBeforeRun: false,
|
|
715
|
+
telemetry: 'none',
|
|
716
|
+
handler: runAgentCommand,
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
name: 'sync',
|
|
720
|
+
description: 'Sync local Moda artifacts to Moda Cloud',
|
|
721
|
+
examples: [
|
|
722
|
+
'moda sync --dry-run',
|
|
723
|
+
'moda sync harness --from-report .moda/harness-report.json',
|
|
724
|
+
'moda sync prompts',
|
|
725
|
+
],
|
|
726
|
+
auth: 'none',
|
|
727
|
+
outputModes: ['human', 'json', 'agent'],
|
|
728
|
+
mutability: 'write',
|
|
729
|
+
validateConfigBeforeRun: false,
|
|
730
|
+
resetApiRequestCountBeforeRun: false,
|
|
731
|
+
telemetry: 'none',
|
|
732
|
+
handler: runSyncCommand,
|
|
733
|
+
},
|
|
734
|
+
legacyCommand({
|
|
735
|
+
name: 'overview',
|
|
736
|
+
description: 'Harness health briefing from production data',
|
|
737
|
+
examples: ['moda overview --days-back=30', 'moda --agent overview'],
|
|
738
|
+
...dataApiDefaults,
|
|
739
|
+
}),
|
|
740
|
+
legacyCommand({
|
|
741
|
+
name: 'investigate',
|
|
742
|
+
description: 'Rank production issues with evidence and next commands',
|
|
743
|
+
examples: ['moda investigate', 'moda investigate --tool lookupCustomer'],
|
|
744
|
+
...dataApiDefaults,
|
|
745
|
+
}),
|
|
746
|
+
legacyCommand({
|
|
747
|
+
name: 'failures',
|
|
748
|
+
description: 'Show production failures worth fixing first',
|
|
749
|
+
examples: ['moda failures', 'moda failures --tool lookupCustomer'],
|
|
750
|
+
...dataApiDefaults,
|
|
751
|
+
}),
|
|
752
|
+
legacyCommand({
|
|
753
|
+
name: 'why-failed',
|
|
754
|
+
description: 'Explain a failed or partial production run',
|
|
755
|
+
examples: ['moda why-failed 12345678-1234-1234-1234-1234567890ab'],
|
|
756
|
+
...dataApiDefaults,
|
|
757
|
+
}),
|
|
758
|
+
legacyCommand({
|
|
759
|
+
name: 'ask',
|
|
760
|
+
description: 'Ask Moda a production intelligence question',
|
|
761
|
+
examples: ['moda ask "what should I fix first?"', 'moda --agent ask "why did this run fail?"'],
|
|
762
|
+
...dataApiDefaults,
|
|
763
|
+
}),
|
|
764
|
+
legacyCommand({
|
|
765
|
+
name: 'clusters',
|
|
766
|
+
description: 'Browse topic cluster hierarchy',
|
|
767
|
+
examples: ['moda clusters --time-range=7d'],
|
|
768
|
+
...dataApiDefaults,
|
|
769
|
+
}),
|
|
770
|
+
legacyCommand({
|
|
771
|
+
name: 'cluster-conversations',
|
|
772
|
+
description: 'List conversations in a cluster',
|
|
773
|
+
examples: ['moda cluster-conversations <node_id> --limit=20'],
|
|
774
|
+
...dataApiDefaults,
|
|
775
|
+
}),
|
|
776
|
+
legacyCommand({
|
|
777
|
+
name: 'conversations',
|
|
778
|
+
description: 'Search and filter conversations',
|
|
779
|
+
examples: ['moda conversations --search="error" --limit=5'],
|
|
780
|
+
...dataApiDefaults,
|
|
781
|
+
}),
|
|
782
|
+
legacyCommand({
|
|
783
|
+
name: 'context',
|
|
784
|
+
description: 'Get windowed conversation context',
|
|
785
|
+
examples: ['moda context <conversation_id> --window=3'],
|
|
786
|
+
...dataApiDefaults,
|
|
787
|
+
}),
|
|
788
|
+
legacyCommand({
|
|
789
|
+
name: 'frustrations',
|
|
790
|
+
description: 'Get user frustration detections',
|
|
791
|
+
examples: ['moda frustrations --days-back=14 --include-window'],
|
|
792
|
+
...dataApiDefaults,
|
|
793
|
+
}),
|
|
794
|
+
legacyCommand({
|
|
795
|
+
name: 'tool-failures',
|
|
796
|
+
description: 'Get tool failure overview',
|
|
797
|
+
examples: ['moda tool-failures --days-back=14'],
|
|
798
|
+
...dataApiDefaults,
|
|
799
|
+
}),
|
|
800
|
+
legacyCommand({
|
|
801
|
+
name: 'tool-failure-detail',
|
|
802
|
+
description: 'Get per-tool failure detail',
|
|
803
|
+
examples: ['moda tool-failure-detail search --include-window'],
|
|
804
|
+
...dataApiDefaults,
|
|
805
|
+
}),
|
|
806
|
+
legacyCommand({
|
|
807
|
+
name: 'workflows',
|
|
808
|
+
description: 'Recurring workflow catalog',
|
|
809
|
+
examples: ['moda workflows --time-range=7d'],
|
|
810
|
+
...dataApiDefaults,
|
|
811
|
+
}),
|
|
812
|
+
legacyCommand({
|
|
813
|
+
name: 'runs',
|
|
814
|
+
description: 'List workflow executions',
|
|
815
|
+
examples: ['moda runs --time-range=24h --outcome=partial'],
|
|
816
|
+
...dataApiDefaults,
|
|
817
|
+
}),
|
|
818
|
+
legacyCommand({
|
|
819
|
+
name: 'run',
|
|
820
|
+
description: 'Get a single workflow execution',
|
|
821
|
+
examples: ['moda run 12345678-1234-1234-1234-1234567890ab'],
|
|
822
|
+
...dataApiDefaults,
|
|
823
|
+
}),
|
|
824
|
+
legacyCommand({
|
|
825
|
+
name: 'loops',
|
|
826
|
+
description: 'List currently-active loop detections',
|
|
827
|
+
examples: ['moda loops --limit=10'],
|
|
828
|
+
...dataApiDefaults,
|
|
829
|
+
}),
|
|
830
|
+
legacyCommand({
|
|
831
|
+
name: 'loop',
|
|
832
|
+
description: 'Get a single loop detection',
|
|
833
|
+
examples: ['moda loop 12345678-1234-1234-1234-1234567890ab'],
|
|
834
|
+
...dataApiDefaults,
|
|
835
|
+
}),
|
|
836
|
+
legacyCommand({
|
|
837
|
+
name: 'prompts',
|
|
838
|
+
description: 'Manage prompt versions and labels',
|
|
839
|
+
examples: ['moda prompts status', 'moda prompts sync'],
|
|
840
|
+
auth: 'api-key',
|
|
841
|
+
outputModes: ['human', 'json', 'agent'],
|
|
842
|
+
mutability: 'write',
|
|
843
|
+
validateConfigBeforeRun: true,
|
|
844
|
+
resetApiRequestCountBeforeRun: true,
|
|
845
|
+
telemetry: 'result-and-error',
|
|
846
|
+
}),
|
|
847
|
+
// Coding-agent capture commands disabled until the feature is production-ready.
|
|
848
|
+
// Re-enable by restoring these registrations, the workspace.js import above, the
|
|
849
|
+
// "Shadow workspace" help section, the init hooks phase, and the doctor spark/hook checks.
|
|
850
|
+
// {
|
|
851
|
+
// name: 'workspace',
|
|
852
|
+
// description: 'Manage Moda shadow workspace snapshots',
|
|
853
|
+
// examples: ['moda workspace init', 'moda workspace snapshot --json', 'moda workspace explain'],
|
|
854
|
+
// auth: 'none',
|
|
855
|
+
// outputModes: ['human', 'json', 'agent'],
|
|
856
|
+
// mutability: 'write',
|
|
857
|
+
// validateConfigBeforeRun: false,
|
|
858
|
+
// resetApiRequestCountBeforeRun: false,
|
|
859
|
+
// telemetry: 'none',
|
|
860
|
+
// handler: async (context) => ({
|
|
861
|
+
// exitCode: await runWorkspaceCommand({
|
|
862
|
+
// command: context.command,
|
|
863
|
+
// positional: context.positional,
|
|
864
|
+
// positionals: context.positionals,
|
|
865
|
+
// flags: context.flags,
|
|
866
|
+
// }),
|
|
867
|
+
// }),
|
|
868
|
+
// },
|
|
869
|
+
// {
|
|
870
|
+
// name: 'spark',
|
|
871
|
+
// aliases: ['shadow'],
|
|
872
|
+
// description: 'Manage Shadow Spark workspace snapshots',
|
|
873
|
+
// examples: ['moda spark status', 'moda spark explain', 'moda spark snapshot --dry-run --json'],
|
|
874
|
+
// auth: 'none',
|
|
875
|
+
// outputModes: ['human', 'json', 'agent'],
|
|
876
|
+
// mutability: 'write',
|
|
877
|
+
// validateConfigBeforeRun: false,
|
|
878
|
+
// resetApiRequestCountBeforeRun: false,
|
|
879
|
+
// telemetry: 'none',
|
|
880
|
+
// handler: runSparkCommand,
|
|
881
|
+
// },
|
|
882
|
+
// {
|
|
883
|
+
// name: 'hooks',
|
|
884
|
+
// description: 'Install coding-agent trace hooks',
|
|
885
|
+
// examples: ['moda hooks install', 'moda hooks install --agent=all'],
|
|
886
|
+
// auth: 'none',
|
|
887
|
+
// outputModes: ['human', 'json', 'agent'],
|
|
888
|
+
// mutability: 'write',
|
|
889
|
+
// validateConfigBeforeRun: false,
|
|
890
|
+
// resetApiRequestCountBeforeRun: false,
|
|
891
|
+
// telemetry: 'none',
|
|
892
|
+
// handler: async (context) => ({
|
|
893
|
+
// exitCode: await runHooksInstall({
|
|
894
|
+
// command: context.command,
|
|
895
|
+
// positional: context.positional,
|
|
896
|
+
// positionals: context.positionals,
|
|
897
|
+
// flags: context.flags,
|
|
898
|
+
// }),
|
|
899
|
+
// }),
|
|
900
|
+
// },
|
|
901
|
+
{
|
|
902
|
+
name: 'skills',
|
|
903
|
+
description: 'Generate and pull Moda skills',
|
|
904
|
+
examples: ['moda skills gen', 'moda skills pull'],
|
|
905
|
+
auth: 'none',
|
|
906
|
+
outputModes: ['human', 'json', 'agent'],
|
|
907
|
+
mutability: 'write',
|
|
908
|
+
validateConfigBeforeRun: false,
|
|
909
|
+
resetApiRequestCountBeforeRun: false,
|
|
910
|
+
telemetry: 'result',
|
|
911
|
+
handler: async (context) => ({
|
|
912
|
+
exitCode: await runSkillsCommand({
|
|
913
|
+
command: context.command,
|
|
914
|
+
positional: context.positional,
|
|
915
|
+
positionals: context.positionals,
|
|
916
|
+
flags: context.flags,
|
|
917
|
+
}),
|
|
918
|
+
}),
|
|
919
|
+
},
|
|
920
|
+
]);
|
|
358
921
|
// --- Main ---
|
|
359
|
-
|
|
360
|
-
|
|
922
|
+
export function classifyCliError(error) {
|
|
923
|
+
if (error instanceof z.ZodError || error instanceof CliInputError) {
|
|
924
|
+
return 'validation_error';
|
|
925
|
+
}
|
|
926
|
+
if (error instanceof ApiError) {
|
|
927
|
+
return 'api_error';
|
|
928
|
+
}
|
|
929
|
+
if (error instanceof Error &&
|
|
930
|
+
(error.message.startsWith('Network error:') ||
|
|
931
|
+
error.message.startsWith('Request timed out'))) {
|
|
932
|
+
return 'network_error';
|
|
933
|
+
}
|
|
934
|
+
return 'unknown_error';
|
|
935
|
+
}
|
|
936
|
+
export function printCliError(error) {
|
|
937
|
+
if (error instanceof CliInputError) {
|
|
938
|
+
console.error(`Error: ${error.message}`);
|
|
939
|
+
if (error.usage)
|
|
940
|
+
console.error(error.usage);
|
|
941
|
+
}
|
|
942
|
+
else if (error instanceof z.ZodError) {
|
|
943
|
+
const issues = error.errors.map((e) => {
|
|
944
|
+
const field = e.path.length ? e.path.join('.') : 'input';
|
|
945
|
+
return ` ${field}: ${e.message}`;
|
|
946
|
+
});
|
|
947
|
+
console.error(`Validation error:\n${issues.join('\n')}`);
|
|
948
|
+
}
|
|
949
|
+
else if (error instanceof ApiError) {
|
|
950
|
+
console.error(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}`);
|
|
951
|
+
}
|
|
952
|
+
else if (error instanceof Error) {
|
|
953
|
+
console.error(`Error: ${error.message}`);
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
console.error('Error:', error);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function printAgentCliError(command, error) {
|
|
960
|
+
console.log(JSON.stringify(createAgentErrorEnvelope(command || 'unknown', errorToAgentError(error))));
|
|
961
|
+
}
|
|
962
|
+
export async function dispatchParsedCommand(parsed, startedAt = Date.now()) {
|
|
963
|
+
const { command, flags } = parsed;
|
|
964
|
+
const commandFlags = stripTelemetryFlag(flags);
|
|
965
|
+
const telemetryDisabled = isTelemetryDisabled(flags);
|
|
361
966
|
if (command === '--version' || command === '-V' || flags.version === 'true') {
|
|
362
967
|
console.log(`moda-cli ${VERSION}`);
|
|
363
|
-
|
|
968
|
+
return 0;
|
|
364
969
|
}
|
|
365
|
-
if (
|
|
970
|
+
if (command === '--help' || command === '-h' || flags.help === 'true') {
|
|
366
971
|
printHelp();
|
|
367
|
-
|
|
972
|
+
return 0;
|
|
368
973
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
974
|
+
if (!command) {
|
|
975
|
+
printHelp();
|
|
976
|
+
return 1;
|
|
977
|
+
}
|
|
978
|
+
const definition = commandRegistry.find(command);
|
|
979
|
+
if (!definition) {
|
|
980
|
+
const error = new CliInputError(`Unknown command '${command}'`, 'Run "moda --help" for usage information.');
|
|
981
|
+
if (outputModeFromFlags(commandFlags) === 'agent' || outputModeFromFlags(commandFlags) === 'agent-stream') {
|
|
982
|
+
printAgentCliError(command, error);
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
printCliError(error);
|
|
986
|
+
}
|
|
987
|
+
return 1;
|
|
988
|
+
}
|
|
989
|
+
if (definition.name === 'agent' && commandFlags.json !== 'true') {
|
|
990
|
+
commandFlags.agent = 'true';
|
|
991
|
+
}
|
|
992
|
+
if (definition.validateConfigBeforeRun) {
|
|
993
|
+
validateConfig();
|
|
994
|
+
}
|
|
995
|
+
if (definition.resetApiRequestCountBeforeRun) {
|
|
996
|
+
resetApiRequestCount();
|
|
997
|
+
}
|
|
998
|
+
const context = createCommandContext(parsed, {
|
|
999
|
+
commandName: definition.name,
|
|
1000
|
+
commandFlags,
|
|
1001
|
+
rawFlags: flags,
|
|
1002
|
+
startedAt,
|
|
1003
|
+
});
|
|
1004
|
+
try {
|
|
1005
|
+
const result = await definition.handler(context);
|
|
1006
|
+
if (!telemetryDisabled && definition.telemetry !== 'none') {
|
|
1007
|
+
void sendCliUsageTelemetry({
|
|
1008
|
+
apiKey: result.apiKey ?? resolveApiKey(),
|
|
1009
|
+
command: definition.name,
|
|
1010
|
+
flags: commandFlags,
|
|
1011
|
+
status: result.exitCode === 0 ? 'success' : 'error',
|
|
1012
|
+
exitCode: result.exitCode,
|
|
1013
|
+
durationMs: Date.now() - startedAt,
|
|
1014
|
+
apiRequestCount: getApiRequestCount(),
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
return result.exitCode;
|
|
1018
|
+
}
|
|
1019
|
+
catch (error) {
|
|
1020
|
+
if (!telemetryDisabled && definition.telemetry === 'result-and-error') {
|
|
1021
|
+
void sendCliUsageTelemetry({
|
|
1022
|
+
apiKey: resolveApiKey(),
|
|
1023
|
+
command: definition.name,
|
|
1024
|
+
flags: commandFlags,
|
|
1025
|
+
status: 'error',
|
|
1026
|
+
errorType: classifyCliError(error),
|
|
1027
|
+
exitCode: 1,
|
|
1028
|
+
durationMs: Date.now() - startedAt,
|
|
1029
|
+
apiRequestCount: getApiRequestCount(),
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
if (context.outputMode === 'agent' || context.outputMode === 'agent-stream') {
|
|
1033
|
+
printAgentCliError(definition.name, error);
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
printCliError(error);
|
|
1037
|
+
}
|
|
1038
|
+
return 1;
|
|
375
1039
|
}
|
|
376
|
-
|
|
377
|
-
|
|
1040
|
+
}
|
|
1041
|
+
async function main() {
|
|
1042
|
+
// Set exitCode (don't process.exit) so any in-flight telemetry request
|
|
1043
|
+
// can resolve before Node drains its event loop and exits naturally.
|
|
1044
|
+
process.exitCode = await dispatchParsedCommand(parseArgs(process.argv));
|
|
378
1045
|
}
|
|
379
1046
|
// Only run main() when this file is executed directly (not when imported as a module).
|
|
380
1047
|
import { fileURLToPath } from 'url';
|
|
@@ -383,23 +1050,8 @@ const isMain = process.argv[1] &&
|
|
|
383
1050
|
fileURLToPath(import.meta.url) === realpathSync(process.argv[1]);
|
|
384
1051
|
if (isMain) {
|
|
385
1052
|
main().catch((error) => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const field = e.path.length ? e.path.join('.') : 'input';
|
|
389
|
-
return ` ${field}: ${e.message}`;
|
|
390
|
-
});
|
|
391
|
-
console.error(`Validation error:\n${issues.join('\n')}`);
|
|
392
|
-
}
|
|
393
|
-
else if (error instanceof ApiError) {
|
|
394
|
-
console.error(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}`);
|
|
395
|
-
}
|
|
396
|
-
else if (error instanceof Error) {
|
|
397
|
-
console.error(`Error: ${error.message}`);
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
console.error('Error:', error);
|
|
401
|
-
}
|
|
402
|
-
process.exit(1);
|
|
1053
|
+
printCliError(error);
|
|
1054
|
+
process.exitCode = 1;
|
|
403
1055
|
});
|
|
404
1056
|
}
|
|
405
1057
|
//# sourceMappingURL=cli.js.map
|