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