@moda-ai/cli 1.10.0 → 1.10.2
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 +0 -8
- package/dist/cli.js +1 -86
- package/dist/production-intelligence.d.ts +2 -4
- package/dist/production-intelligence.js +2 -90
- package/dist/schemas.d.ts +0 -43
- package/dist/schemas.js +0 -26
- package/dist/skills.js +138 -1
- package/package.json +1 -1
- package/skills/moda-cli/SKILL.md +4 -56
package/README.md
CHANGED
|
@@ -58,12 +58,6 @@ moda tool-failures # Tool failure overview
|
|
|
58
58
|
moda tool-failure-detail <tool_name> # Per-tool failure detail
|
|
59
59
|
moda tool-failure-detail <tool_name> --include-window --window=1
|
|
60
60
|
|
|
61
|
-
# Agent runs runtime (workflows = recurring class, run = one execution)
|
|
62
|
-
moda workflows --time-range=7d # Recurring workflow catalog with health
|
|
63
|
-
moda runs # Last 7d of workflow executions
|
|
64
|
-
moda runs --time-range=24h --outcome=partial # Last 24h, partial completions only
|
|
65
|
-
moda runs --class-id=cron:helpscout-triage-hourly # Scoped to one workflow class
|
|
66
|
-
moda run <run_id> # One execution + its side-effect receipts
|
|
67
61
|
moda loops # Currently-active loop detections
|
|
68
62
|
moda loop <detection_id> # One loop detail (signature + blast radius)
|
|
69
63
|
|
|
@@ -81,9 +75,7 @@ All commands output JSON. Pipe to `jq` for filtering:
|
|
|
81
75
|
```bash
|
|
82
76
|
moda overview | jq '.frustrations'
|
|
83
77
|
moda frustrations | jq '.frustrations[].primary_cause'
|
|
84
|
-
moda runs --outcome=partial | jq '.runs[] | {job_id, intent_summary, outcome_reason}'
|
|
85
78
|
moda loops | jq '.loops | sort_by(-.cycle_visits) | .[0:5] | .[].loop_signature_human'
|
|
86
|
-
moda workflows | jq '.workflows | sort_by(.health) | .[] | select(.health < 0.8)'
|
|
87
79
|
```
|
|
88
80
|
|
|
89
81
|
## Prompt Management
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { validateConfig, VERSION, resolveApiKey } from './config.js';
|
|
4
4
|
import { callDataAPI, ApiError, getApiRequestCount, resetApiRequestCount, } from './api-client.js';
|
|
5
|
-
import { OverviewSchema, ClustersSchema, ClusterConversationsSchema, ConversationsSchema, SearchSchema, WorldStateSchema, ContextSchema, AuditSchema, FrustrationsSchema, ToolFailuresSchema, ToolFailureDetailSchema,
|
|
5
|
+
import { OverviewSchema, ClustersSchema, ClusterConversationsSchema, ConversationsSchema, SearchSchema, WorldStateSchema, ContextSchema, AuditSchema, FrustrationsSchema, ToolFailuresSchema, ToolFailureDetailSchema, LoopsSchema, LoopDetailSchema, } from './schemas.js';
|
|
6
6
|
import { withFrustrationAnchors, withToolFailureAnchors, embeddedWindowSize, asRecord, asString, asNumber } from './transforms.js';
|
|
7
7
|
import { runPromptsCommand } from './prompts.js';
|
|
8
8
|
// Coding-agent capture (shadow workspace / Shadow Spark / trace hooks) is temporarily
|
|
@@ -130,7 +130,6 @@ Commands:
|
|
|
130
130
|
agent evidence <ref> Expand one evidence reference
|
|
131
131
|
investigate Rank production issues with evidence
|
|
132
132
|
failures Show production failures worth fixing first
|
|
133
|
-
why-failed <run_id> Explain a failed or partial production run
|
|
134
133
|
ask "question" Ask Moda a production/harness question
|
|
135
134
|
sync Sync the local harness graph to Moda Cloud
|
|
136
135
|
sync harness Sync the local harness graph to Moda Cloud
|
|
@@ -149,10 +148,6 @@ Commands:
|
|
|
149
148
|
tool-failures Get tool failure overview
|
|
150
149
|
tool-failure-detail <tool_name> Get per-tool failure detail
|
|
151
150
|
|
|
152
|
-
Agent runs runtime:
|
|
153
|
-
workflows Recurring workflow catalog (per-class 30d health)
|
|
154
|
-
runs List workflow executions with classified outcomes
|
|
155
|
-
run <run_id> Get single execution + side-effect receipts
|
|
156
151
|
loops Currently-active loop detections
|
|
157
152
|
loop <detection_id> Single loop detail
|
|
158
153
|
|
|
@@ -216,11 +211,6 @@ World-state flags:
|
|
|
216
211
|
--summary-only Return segment summaries only (omit the event stream)
|
|
217
212
|
--event-limit=N Max world-state events to return (1-5000, default 2000)
|
|
218
213
|
|
|
219
|
-
Runs flags:
|
|
220
|
-
--outcome=OUTCOME Filter by outcome (running|succeeded|partial|failed|loop|abandoned|unknown)
|
|
221
|
-
--class-id=ID Filter to a single workflow class (e.g. cron:helpscout-triage-hourly)
|
|
222
|
-
--user-id=ID Filter to a single user
|
|
223
|
-
|
|
224
214
|
Init flags:
|
|
225
215
|
--yes Accept all defaults (non-interactive)
|
|
226
216
|
--analyst=auto|claude|codex|cursor|local-scan
|
|
@@ -256,7 +246,6 @@ Examples:
|
|
|
256
246
|
moda agent evidence doctor:harness_report_approved_synced
|
|
257
247
|
moda investigate
|
|
258
248
|
moda failures
|
|
259
|
-
moda why-failed 12345678-1234-1234-1234-1234567890ab
|
|
260
249
|
moda ask "what should I fix first?"
|
|
261
250
|
moda sync --dry-run
|
|
262
251
|
moda sync harness --profile=prod
|
|
@@ -276,10 +265,6 @@ Examples:
|
|
|
276
265
|
moda context <conversation_id> --window=3
|
|
277
266
|
moda audit <conversation_id|trace_id>
|
|
278
267
|
moda audit <trace_id> --kind=trace --json
|
|
279
|
-
moda workflows --time-range=7d
|
|
280
|
-
moda runs --time-range=24h --outcome=partial
|
|
281
|
-
moda runs --class-id=cron:helpscout-triage-hourly --limit=20
|
|
282
|
-
moda run 12345678-1234-1234-1234-1234567890ab
|
|
283
268
|
moda loops --limit=10
|
|
284
269
|
moda loop 12345678-1234-1234-1234-1234567890ab
|
|
285
270
|
moda prompts status
|
|
@@ -405,7 +390,6 @@ export async function runCommand(command, positional, flags, positionals = posit
|
|
|
405
390
|
}
|
|
406
391
|
case 'investigate':
|
|
407
392
|
case 'failures':
|
|
408
|
-
case 'why-failed':
|
|
409
393
|
case 'ask': {
|
|
410
394
|
return (await runProductionCommand(context)).exitCode;
|
|
411
395
|
}
|
|
@@ -594,51 +578,6 @@ export async function runCommand(command, positional, flags, positionals = posit
|
|
|
594
578
|
await printWithOptionalWindows(withToolFailureAnchors(data), 'examples', params.include_window === true, params.window ?? 1, context.output);
|
|
595
579
|
break;
|
|
596
580
|
}
|
|
597
|
-
// ─── Workflows + Runs ──────────────────────────────────────
|
|
598
|
-
case 'workflows': {
|
|
599
|
-
const args = flagsToArgs(flags);
|
|
600
|
-
const params = WorkflowsSchema.parse(args);
|
|
601
|
-
const query = new URLSearchParams();
|
|
602
|
-
if (params.time_range)
|
|
603
|
-
query.set('time_range', params.time_range);
|
|
604
|
-
if (params.user_id)
|
|
605
|
-
query.set('user_id', params.user_id);
|
|
606
|
-
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
607
|
-
const data = await callDataAPI(`/workflows${queryString}`);
|
|
608
|
-
context.output.writeData(data);
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
case 'runs': {
|
|
612
|
-
const args = flagsToArgs(flags);
|
|
613
|
-
const params = RunsSchema.parse(args);
|
|
614
|
-
const query = new URLSearchParams();
|
|
615
|
-
if (params.time_range)
|
|
616
|
-
query.set('time_range', params.time_range);
|
|
617
|
-
if (params.outcome)
|
|
618
|
-
query.set('outcome', params.outcome);
|
|
619
|
-
if (params.class_id)
|
|
620
|
-
query.set('class_id', params.class_id);
|
|
621
|
-
if (params.user_id)
|
|
622
|
-
query.set('user_id', params.user_id);
|
|
623
|
-
if (params.limit)
|
|
624
|
-
query.set('limit', params.limit.toString());
|
|
625
|
-
if (params.offset !== undefined)
|
|
626
|
-
query.set('offset', params.offset.toString());
|
|
627
|
-
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
628
|
-
const data = await callDataAPI(`/runs${queryString}`);
|
|
629
|
-
context.output.writeData(data);
|
|
630
|
-
break;
|
|
631
|
-
}
|
|
632
|
-
case 'run': {
|
|
633
|
-
if (!positional) {
|
|
634
|
-
throw new CliInputError('<run_id> is required', 'Usage: moda run <run_id>');
|
|
635
|
-
}
|
|
636
|
-
const args = flagsToArgs(flags, 'run_id', positional);
|
|
637
|
-
const params = RunDetailSchema.parse(args);
|
|
638
|
-
const data = await callDataAPI(`/runs/${params.run_id}`);
|
|
639
|
-
context.output.writeData(data);
|
|
640
|
-
break;
|
|
641
|
-
}
|
|
642
581
|
case 'loops': {
|
|
643
582
|
const args = flagsToArgs(flags);
|
|
644
583
|
const params = LoopsSchema.parse(args);
|
|
@@ -927,12 +866,6 @@ export const commandRegistry = createCommandRegistry([
|
|
|
927
866
|
examples: ['moda failures', 'moda failures --tool lookupCustomer'],
|
|
928
867
|
...dataApiDefaults,
|
|
929
868
|
}),
|
|
930
|
-
legacyCommand({
|
|
931
|
-
name: 'why-failed',
|
|
932
|
-
description: 'Explain a failed or partial production run',
|
|
933
|
-
examples: ['moda why-failed 12345678-1234-1234-1234-1234567890ab'],
|
|
934
|
-
...dataApiDefaults,
|
|
935
|
-
}),
|
|
936
869
|
legacyCommand({
|
|
937
870
|
name: 'ask',
|
|
938
871
|
description: 'Ask Moda a production intelligence question',
|
|
@@ -1014,24 +947,6 @@ export const commandRegistry = createCommandRegistry([
|
|
|
1014
947
|
examples: ['moda tool-failure-detail search --include-window'],
|
|
1015
948
|
...dataApiDefaults,
|
|
1016
949
|
}),
|
|
1017
|
-
legacyCommand({
|
|
1018
|
-
name: 'workflows',
|
|
1019
|
-
description: 'Recurring workflow catalog',
|
|
1020
|
-
examples: ['moda workflows --time-range=7d'],
|
|
1021
|
-
...dataApiDefaults,
|
|
1022
|
-
}),
|
|
1023
|
-
legacyCommand({
|
|
1024
|
-
name: 'runs',
|
|
1025
|
-
description: 'List workflow executions',
|
|
1026
|
-
examples: ['moda runs --time-range=24h --outcome=partial'],
|
|
1027
|
-
...dataApiDefaults,
|
|
1028
|
-
}),
|
|
1029
|
-
legacyCommand({
|
|
1030
|
-
name: 'run',
|
|
1031
|
-
description: 'Get a single workflow execution',
|
|
1032
|
-
examples: ['moda run 12345678-1234-1234-1234-1234567890ab'],
|
|
1033
|
-
...dataApiDefaults,
|
|
1034
|
-
}),
|
|
1035
950
|
legacyCommand({
|
|
1036
951
|
name: 'loops',
|
|
1037
952
|
description: 'List currently-active loop detections',
|
|
@@ -5,10 +5,10 @@ declare const INVESTIGATION_SCHEMA_VERSION = "production_investigation.v0.1";
|
|
|
5
5
|
declare const OVERVIEW_SCHEMA_VERSION = "production_overview.v0.1";
|
|
6
6
|
declare const ASK_SCHEMA_VERSION = "production_ask.v0.1";
|
|
7
7
|
type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
8
|
-
type FindingKind = 'tool_failure' | 'loop' | 'frustration' | '
|
|
8
|
+
type FindingKind = 'tool_failure' | 'loop' | 'frustration' | 'prompt_regression' | 'skill_gap' | 'insufficient_data';
|
|
9
9
|
export interface ProductionEvidenceRef {
|
|
10
10
|
id: string;
|
|
11
|
-
kind: 'data_api' | 'tool_failure' | 'frustration' | 'loop' | '
|
|
11
|
+
kind: 'data_api' | 'tool_failure' | 'frustration' | 'loop' | 'harness' | 'ask';
|
|
12
12
|
label: string;
|
|
13
13
|
endpoint?: string;
|
|
14
14
|
conversationId?: string;
|
|
@@ -52,7 +52,6 @@ export interface ProductionInvestigation {
|
|
|
52
52
|
generatedAt: string;
|
|
53
53
|
daysBack: number;
|
|
54
54
|
scope: {
|
|
55
|
-
runId?: string;
|
|
56
55
|
conversationId?: string;
|
|
57
56
|
toolName?: string;
|
|
58
57
|
loopId?: string;
|
|
@@ -136,7 +135,6 @@ export declare function loadOverviewBriefing(options: {
|
|
|
136
135
|
export declare function investigateProduction(options: {
|
|
137
136
|
daysBack: number;
|
|
138
137
|
cwd: string;
|
|
139
|
-
runId?: string;
|
|
140
138
|
conversationId?: string;
|
|
141
139
|
toolName?: string;
|
|
142
140
|
loopId?: string;
|
|
@@ -50,15 +50,6 @@ export async function runProductionCommand(context) {
|
|
|
50
50
|
// it from the exit code alone (in addition to `data.degraded` / status).
|
|
51
51
|
return { exitCode: result.degraded ? EXIT_DEGRADED : EXIT_OK };
|
|
52
52
|
}
|
|
53
|
-
if (context.command === 'why-failed') {
|
|
54
|
-
const runId = context.positional;
|
|
55
|
-
if (!runId) {
|
|
56
|
-
throw new CliInputError('<run_id> is required', 'Usage: moda why-failed <run_id>');
|
|
57
|
-
}
|
|
58
|
-
const investigation = await investigateProduction({ daysBack, cwd: context.cwd, runId });
|
|
59
|
-
writeInvestigation(context, investigation);
|
|
60
|
-
return { exitCode: 0 };
|
|
61
|
-
}
|
|
62
53
|
if (context.command === 'failures') {
|
|
63
54
|
const investigation = await investigateProduction({
|
|
64
55
|
daysBack,
|
|
@@ -73,7 +64,6 @@ export async function runProductionCommand(context) {
|
|
|
73
64
|
const investigation = await investigateProduction({
|
|
74
65
|
daysBack,
|
|
75
66
|
cwd: context.cwd,
|
|
76
|
-
runId: context.flags.run,
|
|
77
67
|
conversationId: context.flags.conversation,
|
|
78
68
|
toolName: context.flags.tool,
|
|
79
69
|
loopId: context.flags.loop,
|
|
@@ -137,20 +127,16 @@ export async function investigateProduction(options) {
|
|
|
137
127
|
? `/tool-failures/${encodeURIComponent(options.toolName)}?days_back=${options.daysBack}&limit=5`
|
|
138
128
|
: `/tool-failures?days_back=${options.daysBack}`;
|
|
139
129
|
const frustrationsEndpoint = `/frustrations?days_back=${options.daysBack}&limit=5`;
|
|
140
|
-
const runsEndpoint = options.runId
|
|
141
|
-
? `/runs/${encodeURIComponent(options.runId)}`
|
|
142
|
-
: `/runs?time_range=${timeRangeForDays(options.daysBack)}&limit=20`;
|
|
143
130
|
const loopsEndpoint = options.loopId
|
|
144
131
|
? `/loops/${encodeURIComponent(options.loopId)}`
|
|
145
132
|
: '/loops/active?limit=20';
|
|
146
|
-
const [overview, tools, frustrations,
|
|
133
|
+
const [overview, tools, frustrations, loops] = await Promise.all([
|
|
147
134
|
safeData(overviewEndpoint),
|
|
148
135
|
safeData(toolEndpoint),
|
|
149
136
|
safeData(frustrationsEndpoint),
|
|
150
|
-
safeData(runsEndpoint),
|
|
151
137
|
safeData(loopsEndpoint),
|
|
152
138
|
]);
|
|
153
|
-
for (const [key, result] of Object.entries({ overview, tools, frustrations,
|
|
139
|
+
for (const [key, result] of Object.entries({ overview, tools, frustrations, loops })) {
|
|
154
140
|
sourceStatus[key] = result.status;
|
|
155
141
|
if (result.warning)
|
|
156
142
|
warnings.push(result.warning);
|
|
@@ -161,11 +147,9 @@ export async function investigateProduction(options) {
|
|
|
161
147
|
findings.push(...toolFailureFindings(tools.data, options.daysBack, hints, evidenceRefs, options.toolName));
|
|
162
148
|
if (!options.failureOnly) {
|
|
163
149
|
findings.push(...frustrationFindings(frustrations.data, options.daysBack, evidenceRefs));
|
|
164
|
-
findings.push(...runFindings(runs.data, evidenceRefs, options.runId));
|
|
165
150
|
findings.push(...loopFindings(loops.data, evidenceRefs, options.loopId));
|
|
166
151
|
}
|
|
167
152
|
else {
|
|
168
|
-
findings.push(...runFindings(runs.data, evidenceRefs, options.runId).filter((finding) => finding.kind === 'partial_run'));
|
|
169
153
|
findings.push(...loopFindings(loops.data, evidenceRefs, options.loopId));
|
|
170
154
|
}
|
|
171
155
|
if (findings.length === 0) {
|
|
@@ -179,7 +163,6 @@ export async function investigateProduction(options) {
|
|
|
179
163
|
generatedAt: new Date().toISOString(),
|
|
180
164
|
daysBack: options.daysBack,
|
|
181
165
|
scope: {
|
|
182
|
-
...(options.runId ? { runId: options.runId } : {}),
|
|
183
166
|
...(options.conversationId ? { conversationId: options.conversationId } : {}),
|
|
184
167
|
...(options.toolName ? { toolName: options.toolName } : {}),
|
|
185
168
|
...(options.loopId ? { loopId: options.loopId } : {}),
|
|
@@ -461,45 +444,6 @@ function frustrationFindings(data, daysBack, evidenceRefs) {
|
|
|
461
444
|
}],
|
|
462
445
|
}];
|
|
463
446
|
}
|
|
464
|
-
function runFindings(data, evidenceRefs, scopedRunId) {
|
|
465
|
-
const rows = runRows(data);
|
|
466
|
-
return rows.flatMap((run) => {
|
|
467
|
-
const runId = run.id || scopedRunId;
|
|
468
|
-
const outcome = run.outcome;
|
|
469
|
-
if (!runId || !['failed', 'partial', 'abandoned', 'loop'].includes(outcome))
|
|
470
|
-
return [];
|
|
471
|
-
const evidenceId = `run:${runId}`;
|
|
472
|
-
evidenceRefs.push({
|
|
473
|
-
id: evidenceId,
|
|
474
|
-
kind: 'run',
|
|
475
|
-
label: `${run.agentName || 'Agent run'} ${outcome}`,
|
|
476
|
-
endpoint: `/runs/${runId}`,
|
|
477
|
-
runId,
|
|
478
|
-
conversationId: run.conversationId,
|
|
479
|
-
value: run.raw,
|
|
480
|
-
});
|
|
481
|
-
return [{
|
|
482
|
-
id: `finding_run_${slug(runId)}`,
|
|
483
|
-
kind: outcome === 'partial' ? 'partial_run' : 'tool_failure',
|
|
484
|
-
title: `${run.agentName || 'Agent run'} ended as ${outcome}`,
|
|
485
|
-
severity: outcome === 'failed' ? 'high' : 'medium',
|
|
486
|
-
confidence: 'high',
|
|
487
|
-
impactScore: outcome === 'failed' ? 88 : 76,
|
|
488
|
-
rankReason: `Run outcome is ${outcome}${run.reason ? `: ${run.reason}` : ''}.`,
|
|
489
|
-
summary: run.reason || 'Moda classified this run as incomplete or unhealthy.',
|
|
490
|
-
evidenceRefIds: [evidenceId],
|
|
491
|
-
likelyLocations: [unknownLocation('entrypoint', 'Map this run to a harness entrypoint after harness sync.')],
|
|
492
|
-
recommendedActions: [{
|
|
493
|
-
id: `action_run_${slug(runId)}`,
|
|
494
|
-
title: 'Open the run detail.',
|
|
495
|
-
rationale: 'Run detail includes receipts and missing effects.',
|
|
496
|
-
command: `moda run ${runId}`,
|
|
497
|
-
mutability: 'read',
|
|
498
|
-
requiresApproval: false,
|
|
499
|
-
}],
|
|
500
|
-
}];
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
447
|
function loopFindings(data, evidenceRefs, scopedLoopId) {
|
|
504
448
|
const rows = loopRows(data);
|
|
505
449
|
return rows.flatMap((loop) => {
|
|
@@ -1116,24 +1060,6 @@ function toolRows(data, scopedTool) {
|
|
|
1116
1060
|
}];
|
|
1117
1061
|
});
|
|
1118
1062
|
}
|
|
1119
|
-
function runRows(data) {
|
|
1120
|
-
const record = asRecord(data);
|
|
1121
|
-
const run = asRecord(record?.run);
|
|
1122
|
-
const rows = Array.isArray(record?.runs) ? record.runs : run ? [run] : [];
|
|
1123
|
-
return rows.flatMap((item) => {
|
|
1124
|
-
const row = asRecord(item);
|
|
1125
|
-
if (!row)
|
|
1126
|
-
return [];
|
|
1127
|
-
return [{
|
|
1128
|
-
id: asString(row.job_id) ?? asString(row.run_id) ?? '',
|
|
1129
|
-
outcome: asString(row.outcome) ?? 'unknown',
|
|
1130
|
-
agentName: asString(row.agent_name) ?? undefined,
|
|
1131
|
-
reason: asString(row.outcome_reason) ?? undefined,
|
|
1132
|
-
conversationId: asString(row.conversation_id) ?? undefined,
|
|
1133
|
-
raw: row,
|
|
1134
|
-
}];
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
1063
|
function loopRows(data) {
|
|
1138
1064
|
const record = asRecord(data);
|
|
1139
1065
|
const rows = Array.isArray(record?.loops)
|
|
@@ -1397,17 +1323,6 @@ function numberFlag(flags, key, fallback) {
|
|
|
1397
1323
|
return fallback;
|
|
1398
1324
|
return Math.min(Math.floor(parsed), 90);
|
|
1399
1325
|
}
|
|
1400
|
-
function timeRangeForDays(days) {
|
|
1401
|
-
if (days <= 1)
|
|
1402
|
-
return '24h';
|
|
1403
|
-
if (days <= 3)
|
|
1404
|
-
return '3d';
|
|
1405
|
-
if (days <= 7)
|
|
1406
|
-
return '7d';
|
|
1407
|
-
if (days <= 30)
|
|
1408
|
-
return '30d';
|
|
1409
|
-
return '90d';
|
|
1410
|
-
}
|
|
1411
1326
|
function normalizeConfidence(value) {
|
|
1412
1327
|
if (value === 'high' || value === 'medium' || value === 'low' || value === 'unknown')
|
|
1413
1328
|
return value;
|
|
@@ -1421,7 +1336,6 @@ function evidenceKind(value) {
|
|
|
1421
1336
|
value === 'tool_failure' ||
|
|
1422
1337
|
value === 'frustration' ||
|
|
1423
1338
|
value === 'loop' ||
|
|
1424
|
-
value === 'run' ||
|
|
1425
1339
|
value === 'harness' ||
|
|
1426
1340
|
value === 'ask')
|
|
1427
1341
|
return value;
|
|
@@ -1443,8 +1357,6 @@ function purposeForCommand(command) {
|
|
|
1443
1357
|
return 'Read the relevant conversation window.';
|
|
1444
1358
|
if (command.startsWith('moda loop'))
|
|
1445
1359
|
return 'Inspect loop detection details.';
|
|
1446
|
-
if (command.startsWith('moda run'))
|
|
1447
|
-
return 'Inspect one agent run and side-effect receipts.';
|
|
1448
1360
|
if (command.startsWith('moda doctor'))
|
|
1449
1361
|
return 'Validate Moda setup and data flow.';
|
|
1450
1362
|
if (command.startsWith('moda overview'))
|
package/dist/schemas.d.ts
CHANGED
|
@@ -185,49 +185,6 @@ export declare const SearchSchema: z.ZodObject<{
|
|
|
185
185
|
limit?: number | undefined;
|
|
186
186
|
user_id?: string | undefined;
|
|
187
187
|
}>;
|
|
188
|
-
/** moda workflows — recurring workflow catalog (read from agent_job_classes). */
|
|
189
|
-
export declare const WorkflowsSchema: z.ZodObject<{
|
|
190
|
-
time_range: z.ZodOptional<z.ZodDefault<z.ZodEnum<["all", "1h", "3d", "7d", "24h", "30d", "90d"]>>>;
|
|
191
|
-
user_id: z.ZodOptional<z.ZodString>;
|
|
192
|
-
}, "strip", z.ZodTypeAny, {
|
|
193
|
-
time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
|
|
194
|
-
user_id?: string | undefined;
|
|
195
|
-
}, {
|
|
196
|
-
time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
|
|
197
|
-
user_id?: string | undefined;
|
|
198
|
-
}>;
|
|
199
|
-
/** moda runs — list workflow executions with classified outcomes. */
|
|
200
|
-
export declare const RunsSchema: z.ZodObject<{
|
|
201
|
-
time_range: z.ZodOptional<z.ZodDefault<z.ZodEnum<["all", "1h", "3d", "7d", "24h", "30d", "90d"]>>>;
|
|
202
|
-
outcome: z.ZodOptional<z.ZodEnum<["running", "succeeded", "partial", "failed", "loop", "abandoned", "unknown"]>>;
|
|
203
|
-
/** Filter to one workflow class id (e.g. cron:helpscout-triage-hourly). */
|
|
204
|
-
class_id: z.ZodOptional<z.ZodString>;
|
|
205
|
-
user_id: z.ZodOptional<z.ZodString>;
|
|
206
|
-
limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
207
|
-
offset: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
208
|
-
}, "strip", z.ZodTypeAny, {
|
|
209
|
-
time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
|
|
210
|
-
limit?: number | undefined;
|
|
211
|
-
offset?: number | undefined;
|
|
212
|
-
user_id?: string | undefined;
|
|
213
|
-
outcome?: "unknown" | "loop" | "running" | "failed" | "succeeded" | "partial" | "abandoned" | undefined;
|
|
214
|
-
class_id?: string | undefined;
|
|
215
|
-
}, {
|
|
216
|
-
time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
|
|
217
|
-
limit?: number | undefined;
|
|
218
|
-
offset?: number | undefined;
|
|
219
|
-
user_id?: string | undefined;
|
|
220
|
-
outcome?: "unknown" | "loop" | "running" | "failed" | "succeeded" | "partial" | "abandoned" | undefined;
|
|
221
|
-
class_id?: string | undefined;
|
|
222
|
-
}>;
|
|
223
|
-
/** moda run <runId> — single execution detail with side-effect receipts. */
|
|
224
|
-
export declare const RunDetailSchema: z.ZodObject<{
|
|
225
|
-
run_id: z.ZodString;
|
|
226
|
-
}, "strip", z.ZodTypeAny, {
|
|
227
|
-
run_id: string;
|
|
228
|
-
}, {
|
|
229
|
-
run_id: string;
|
|
230
|
-
}>;
|
|
231
188
|
/** moda loops — currently active loop detections. */
|
|
232
189
|
export declare const LoopsSchema: z.ZodObject<{
|
|
233
190
|
limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
package/dist/schemas.js
CHANGED
|
@@ -78,13 +78,6 @@ export const AuditSchema = z.object({
|
|
|
78
78
|
include_raw: boolFlag(),
|
|
79
79
|
limit: z.number().min(1).max(1000).default(200).optional(),
|
|
80
80
|
});
|
|
81
|
-
// ─── Workflows + Runs (agent runs runtime) ──────────────────
|
|
82
|
-
//
|
|
83
|
-
// Naming model:
|
|
84
|
-
// workflow = the recurring class definition (e.g. cron:helpscout-triage-hourly)
|
|
85
|
-
// run = one execution of a workflow
|
|
86
|
-
// `moda workflows` lists the catalog; `moda runs` lists executions.
|
|
87
|
-
const RUN_OUTCOMES = ['running', 'succeeded', 'partial', 'failed', 'loop', 'abandoned', 'unknown'];
|
|
88
81
|
const TIME_RANGES = ['all', '1h', '3d', '7d', '24h', '30d', '90d'];
|
|
89
82
|
const SEARCH_MODES = ['keyword', 'semantic', 'hybrid'];
|
|
90
83
|
/**
|
|
@@ -99,25 +92,6 @@ export const SearchSchema = z.object({
|
|
|
99
92
|
time_range: z.enum(TIME_RANGES).default('all').optional(),
|
|
100
93
|
limit: z.number().min(1).max(100).default(20).optional(),
|
|
101
94
|
});
|
|
102
|
-
/** moda workflows — recurring workflow catalog (read from agent_job_classes). */
|
|
103
|
-
export const WorkflowsSchema = z.object({
|
|
104
|
-
time_range: z.enum(TIME_RANGES).default('all').optional(),
|
|
105
|
-
user_id: z.string().optional(),
|
|
106
|
-
});
|
|
107
|
-
/** moda runs — list workflow executions with classified outcomes. */
|
|
108
|
-
export const RunsSchema = z.object({
|
|
109
|
-
time_range: z.enum(TIME_RANGES).default('7d').optional(),
|
|
110
|
-
outcome: z.enum(RUN_OUTCOMES).optional(),
|
|
111
|
-
/** Filter to one workflow class id (e.g. cron:helpscout-triage-hourly). */
|
|
112
|
-
class_id: z.string().optional(),
|
|
113
|
-
user_id: z.string().optional(),
|
|
114
|
-
limit: z.number().min(1).max(100).default(50).optional(),
|
|
115
|
-
offset: z.number().min(0).default(0).optional(),
|
|
116
|
-
});
|
|
117
|
-
/** moda run <runId> — single execution detail with side-effect receipts. */
|
|
118
|
-
export const RunDetailSchema = z.object({
|
|
119
|
-
run_id: z.string().uuid({ message: 'run_id must be a UUID' }),
|
|
120
|
-
});
|
|
121
95
|
/** moda loops — currently active loop detections. */
|
|
122
96
|
export const LoopsSchema = z.object({
|
|
123
97
|
limit: z.number().min(1).max(200).default(25).optional(),
|
package/dist/skills.js
CHANGED
|
@@ -27,6 +27,7 @@ import { CliInputError } from './errors.js';
|
|
|
27
27
|
const SKILLS_REQUEST_TIMEOUT_MS = 15_000;
|
|
28
28
|
const VALID_STATUSES = new Set(['approved', 'proposed', 'all']);
|
|
29
29
|
const SKILLS_MANIFEST_PATH = join('.moda', 'skills.yml');
|
|
30
|
+
const SKILL_PROPOSALS_MANIFEST_PATH = join('.moda', 'skill-proposals.yml');
|
|
30
31
|
/**
|
|
31
32
|
* Fetch a tenant's generated skills from the ingest worker. Uses the persisted
|
|
32
33
|
* API key + ingest URL (`~/.moda/config.json`), mirroring snapshot auth.
|
|
@@ -342,11 +343,96 @@ function getGitCommit(cwd) {
|
|
|
342
343
|
return undefined;
|
|
343
344
|
}
|
|
344
345
|
}
|
|
346
|
+
async function fetchSkillProposals(flags, profileOptions = {}) {
|
|
347
|
+
const params = new URLSearchParams();
|
|
348
|
+
if (flags['generation-run-id'])
|
|
349
|
+
params.set('generationRunId', flags['generation-run-id']);
|
|
350
|
+
const status = flags.status ?? 'ready_for_pr';
|
|
351
|
+
if (status !== 'all')
|
|
352
|
+
params.set('status', status);
|
|
353
|
+
if (flags['tenant-id'])
|
|
354
|
+
params.set('tenantId', flags['tenant-id']);
|
|
355
|
+
const suffix = params.toString() ? `?${params.toString()}` : '';
|
|
356
|
+
const response = (await callControlAPI(`/skills/proposals${suffix}`, { method: 'GET' }, profileOptions));
|
|
357
|
+
return Array.isArray(response.proposals) ? response.proposals : [];
|
|
358
|
+
}
|
|
359
|
+
async function applySkillProposal(proposalId, flags, profileOptions = {}, options = {}) {
|
|
360
|
+
const cwd = options.cwd ?? process.cwd();
|
|
361
|
+
const params = new URLSearchParams();
|
|
362
|
+
if (flags['tenant-id'])
|
|
363
|
+
params.set('tenantId', flags['tenant-id']);
|
|
364
|
+
const suffix = params.toString() ? `?${params.toString()}` : '';
|
|
365
|
+
const proposal = (await callControlAPI(`/skills/proposals/${encodeURIComponent(proposalId)}${suffix}`, { method: 'GET' }, profileOptions));
|
|
366
|
+
if (!proposal.skillMd?.trim()) {
|
|
367
|
+
throw new CliInputError(`Proposal ${proposalId} does not include SKILL.md content`);
|
|
368
|
+
}
|
|
369
|
+
const safeId = safeSkillId(proposal.skillId);
|
|
370
|
+
const relativePath = `.claude/skills/${safeId}/SKILL.md`;
|
|
371
|
+
const writeStatus = writeIfChanged(join(cwd, relativePath), proposal.skillMd);
|
|
372
|
+
if (writeStatus === 'failed') {
|
|
373
|
+
throw new Error(`Failed to write ${relativePath}`);
|
|
374
|
+
}
|
|
375
|
+
writeSkillProposalManifest(cwd, proposal, relativePath);
|
|
376
|
+
let acknowledged = true;
|
|
377
|
+
let ackError;
|
|
378
|
+
try {
|
|
379
|
+
await callControlAPI(`/skills/proposals/${encodeURIComponent(proposalId)}/mark-applied${suffix}`, {
|
|
380
|
+
method: 'POST',
|
|
381
|
+
body: JSON.stringify({ appliedBy: 'moda-cli' }),
|
|
382
|
+
}, profileOptions);
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
acknowledged = false;
|
|
386
|
+
ackError = err instanceof Error ? err.message : String(err);
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
proposalId,
|
|
390
|
+
skillId: proposal.skillId,
|
|
391
|
+
path: relativePath,
|
|
392
|
+
status: writeStatus,
|
|
393
|
+
acknowledged,
|
|
394
|
+
ackError,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function safeSkillId(skillId) {
|
|
398
|
+
const safeId = basename(skillId || '');
|
|
399
|
+
if (!safeId || !/^[A-Za-z0-9_-]+$/.test(safeId)) {
|
|
400
|
+
throw new CliInputError(`Invalid skill_id in proposal: ${JSON.stringify(skillId).slice(0, 80)}`);
|
|
401
|
+
}
|
|
402
|
+
return safeId;
|
|
403
|
+
}
|
|
404
|
+
function writeSkillProposalManifest(cwd, proposal, relativePath) {
|
|
405
|
+
const manifestPath = join(cwd, SKILL_PROPOSALS_MANIFEST_PATH);
|
|
406
|
+
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
407
|
+
const existing = existsSync(manifestPath) ? readFileSync(manifestPath, 'utf8') : 'applied_proposals:\n';
|
|
408
|
+
const entry = [
|
|
409
|
+
` - proposal_id: "${escapeYamlString(proposal.proposalId)}"`,
|
|
410
|
+
` skill_id: "${escapeYamlString(proposal.skillId)}"`,
|
|
411
|
+
` skill_family_id: "${escapeYamlString(proposal.skillFamilyId)}"`,
|
|
412
|
+
` skill_version: ${Number(proposal.skillVersion || 0)}`,
|
|
413
|
+
` generation_run_id: "${escapeYamlString(proposal.generationRunId)}"`,
|
|
414
|
+
` clustering_run_id: "${escapeYamlString(proposal.clusteringRunId)}"`,
|
|
415
|
+
` topic_id: "${escapeYamlString(proposal.topicId)}"`,
|
|
416
|
+
` replay_set_id: "${escapeYamlString(proposal.replaySetId)}"`,
|
|
417
|
+
` delta_pass_rate: ${Number(proposal.deltaPassRate || 0)}`,
|
|
418
|
+
` path: "${escapeYamlString(relativePath)}"`,
|
|
419
|
+
` applied_at: "${new Date().toISOString()}"`,
|
|
420
|
+
].join('\n');
|
|
421
|
+
const prefix = existing.endsWith('\n') ? existing : `${existing}\n`;
|
|
422
|
+
writeFileSync(manifestPath, `${prefix}${entry}\n`);
|
|
423
|
+
}
|
|
424
|
+
function escapeYamlString(value) {
|
|
425
|
+
return String(value ?? '').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
426
|
+
}
|
|
427
|
+
function formatProposalRate(value) {
|
|
428
|
+
return `${(Number(value || 0) * 100).toFixed(1)}%`;
|
|
429
|
+
}
|
|
345
430
|
/**
|
|
346
431
|
* `moda skills <subcommand>` dispatcher.
|
|
347
432
|
*/
|
|
348
433
|
export async function runSkillsCommand(args) {
|
|
349
434
|
const sub = args.positional;
|
|
435
|
+
const positionals = args.positionals ?? (sub ? [sub] : []);
|
|
350
436
|
if (sub === 'gen') {
|
|
351
437
|
const result = await generateSkills(args.flags);
|
|
352
438
|
if (args.flags.json === 'true') {
|
|
@@ -371,13 +457,63 @@ export async function runSkillsCommand(args) {
|
|
|
371
457
|
}
|
|
372
458
|
return Number(result.sessions_failed ?? 0) > 0 ? 1 : 0;
|
|
373
459
|
}
|
|
460
|
+
if (sub === 'proposals' && positionals[1] === 'list') {
|
|
461
|
+
const proposals = await fetchSkillProposals(args.flags, { profile: args.flags.profile });
|
|
462
|
+
if (args.flags.json === 'true') {
|
|
463
|
+
console.log(JSON.stringify({ proposals }));
|
|
464
|
+
}
|
|
465
|
+
else if (proposals.length === 0) {
|
|
466
|
+
console.log('No skill proposals found.');
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
for (const proposal of proposals) {
|
|
470
|
+
console.log([
|
|
471
|
+
proposal.proposalId,
|
|
472
|
+
proposal.proposalStatus,
|
|
473
|
+
`${proposal.skillId}@v${proposal.skillVersion}`,
|
|
474
|
+
`delta=${formatProposalRate(proposal.deltaPassRate)}`,
|
|
475
|
+
`baseline=${formatProposalRate(proposal.baselinePassRate)}`,
|
|
476
|
+
`candidate=${formatProposalRate(proposal.candidatePassRate)}`,
|
|
477
|
+
proposal.name,
|
|
478
|
+
].filter(Boolean).join('\t'));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return 0;
|
|
482
|
+
}
|
|
483
|
+
if (sub === 'proposal' && positionals[1] === 'apply') {
|
|
484
|
+
const proposalId = positionals[2] || args.flags['proposal-id'];
|
|
485
|
+
if (!proposalId) {
|
|
486
|
+
throw new CliInputError('A proposal id is required', 'Usage: moda skills proposal apply <proposal-id>');
|
|
487
|
+
}
|
|
488
|
+
const result = await applySkillProposal(proposalId, args.flags, { profile: args.flags.profile });
|
|
489
|
+
if (args.flags.json === 'true') {
|
|
490
|
+
console.log(JSON.stringify(result));
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
console.log([
|
|
494
|
+
`${result.status} ${result.path}`,
|
|
495
|
+
result.acknowledged ? 'acknowledged: true' : 'acknowledged: false',
|
|
496
|
+
].join('\n'));
|
|
497
|
+
}
|
|
498
|
+
if (!result.acknowledged) {
|
|
499
|
+
// The local SKILL.md was written, but the server still lists this
|
|
500
|
+
// proposal as ready_for_pr. Warn (to stderr so JSON stdout stays clean)
|
|
501
|
+
// and exit non-zero so the failed acknowledgement isn't silently treated
|
|
502
|
+
// as success — re-running `apply` re-attempts the acknowledgement.
|
|
503
|
+
console.error(`Warning: wrote ${result.path} but failed to acknowledge proposal ${proposalId} on the server` +
|
|
504
|
+
(result.ackError ? ` (${result.ackError})` : '') +
|
|
505
|
+
'. The proposal is still listed as ready — re-run `moda skills proposal apply` to acknowledge it.');
|
|
506
|
+
return 1;
|
|
507
|
+
}
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
374
510
|
if (sub === 'sync') {
|
|
375
511
|
const result = await runSkillSync(args.flags, { profile: args.flags.profile });
|
|
376
512
|
console.log(JSON.stringify({ synced: result.synced, dryRun: result.dryRun }));
|
|
377
513
|
return 0;
|
|
378
514
|
}
|
|
379
515
|
if (sub !== 'pull') {
|
|
380
|
-
throw new CliInputError(sub ? `Unknown skills subcommand '${sub}'` : 'A skills subcommand is required', 'Usage: moda skills gen [--source=all|sdk|coding-agent] [--max-sessions=N] [--start-at=ISO] [--end-at=ISO] [--replay] [--reprocess-segments] [--force-reprocess] [--wait] | moda skills pull [--status=approved|proposed|all] | moda skills sync [--dry-run]');
|
|
516
|
+
throw new CliInputError(sub ? `Unknown skills subcommand '${sub}'` : 'A skills subcommand is required', 'Usage: moda skills gen [--source=all|sdk|coding-agent] [--max-sessions=N] [--start-at=ISO] [--end-at=ISO] [--replay] [--replay-set-id=ID] [--reprocess-segments] [--force-reprocess] [--wait] | moda skills proposals list [--status=ready_for_pr] | moda skills proposal apply <proposal-id> | moda skills pull [--status=approved|proposed|all] | moda skills sync [--dry-run]');
|
|
381
517
|
}
|
|
382
518
|
const status = (args.flags.status ?? 'approved').toLowerCase();
|
|
383
519
|
if (!VALID_STATUSES.has(status)) {
|
|
@@ -418,6 +554,7 @@ async function generateSkills(flags) {
|
|
|
418
554
|
start_at: flags['start-at'] || undefined,
|
|
419
555
|
end_at: flags['end-at'] || undefined,
|
|
420
556
|
clustering_run_id: flags['clustering-run-id'] || undefined,
|
|
557
|
+
replay_set_id: flags['replay-set-id'] || flags['eval-set-id'] || undefined,
|
|
421
558
|
run_clustering: flags['no-clustering'] !== 'true',
|
|
422
559
|
source: normalizeSkillSource(flags.source),
|
|
423
560
|
clustering_timeout_seconds: parsePositiveInt(flags['clustering-timeout-seconds'], 0),
|
package/package.json
CHANGED
package/skills/moda-cli/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: moda-cli
|
|
3
3
|
version: 2.0.0
|
|
4
|
-
description: Query Moda's AI agent conversation analytics and manage code-first prompt versions from the terminal — semantic/keyword/hybrid message search, overview KPIs, topic clusters, message context, user frustration detections, tool failures,
|
|
4
|
+
description: Query Moda's AI agent conversation analytics and manage code-first prompt versions from the terminal — semantic/keyword/hybrid message search, overview KPIs, topic clusters, message context, user frustration detections, tool failures, active loop detections, and moda prompts status/sync/promote. Use when the user asks about moda, modaflows, conversation analytics, prompt management, user frustrations, agent observability, tool failure debugging, wants to find conversations or tool calls about a topic, or wants to investigate how their AI agent is performing.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Moda CLI
|
|
@@ -369,42 +369,7 @@ Pass `--include-window` to attach a `window` field per row with the message
|
|
|
369
369
|
slice centered on the anchor. `--window=N` (1–5, default `1`) is the
|
|
370
370
|
half-width; `--window=1` ⇒ 3 messages total (anchor ± 1).
|
|
371
371
|
|
|
372
|
-
### 7.
|
|
373
|
-
|
|
374
|
-
Every triggered run of an agent (cron invocation, scheduled task, or
|
|
375
|
-
interactive session) is classified `succeeded | partial | failed | loop |
|
|
376
|
-
abandoned`. Use this layer to answer "did the job actually do its work?",
|
|
377
|
-
which is structurally different from "did it error?" — most jobs that
|
|
378
|
-
partially completed never threw.
|
|
379
|
-
|
|
380
|
-
```bash
|
|
381
|
-
moda runs # last 7d, all outcomes
|
|
382
|
-
moda runs --time-range=24h --outcome=partial # which runs only half-finished
|
|
383
|
-
moda runs --outcome=loop # runs caught in active loops
|
|
384
|
-
moda runs --class-id=cron:helpscout-triage-hourly --limit=20
|
|
385
|
-
moda run <run_id> # one execution + its side-effect receipts
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
`moda run <run_id>` returns the `agent_jobs` row plus every
|
|
389
|
-
`side_effect_receipts` entry for the run — i.e. every tool call the agent
|
|
390
|
-
made (read / write / send / spawn / compute), with success status and
|
|
391
|
-
target system. This is the agent-as-employee log; use it instead of (or
|
|
392
|
-
alongside) `moda context` when debugging "what did the agent actually
|
|
393
|
-
DO".
|
|
394
|
-
|
|
395
|
-
### 8. Recurring workflows / cron status board
|
|
396
|
-
|
|
397
|
-
```bash
|
|
398
|
-
moda workflows # health per recurring class
|
|
399
|
-
moda workflows --time-range=7d # filter to last 7d of activity
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
Each row is one job class (a cron slug, idea-widget id, or sourceType)
|
|
403
|
-
with 30d rolling success / partial / failed / loop counts and a
|
|
404
|
-
`health_score` (0–1). Sort by health to find degraded customer-facing
|
|
405
|
-
crons that aren't crashing but aren't completing either.
|
|
406
|
-
|
|
407
|
-
### 9. Active loop investigation
|
|
372
|
+
### 7. Active loop investigation
|
|
408
373
|
|
|
409
374
|
```bash
|
|
410
375
|
moda loops # everything currently stuck
|
|
@@ -418,7 +383,7 @@ agent re-ran the same action 378 times. Use the `loop_signature_human`
|
|
|
418
383
|
field to recognize cross-conversation patterns (one tool definition bug
|
|
419
384
|
will produce identical signatures across many users).
|
|
420
385
|
|
|
421
|
-
###
|
|
386
|
+
### 8. Prompt management
|
|
422
387
|
|
|
423
388
|
Use these only when working on the code-first prompt registry.
|
|
424
389
|
|
|
@@ -505,19 +470,6 @@ moda clusters --parent-id=<node_id>
|
|
|
505
470
|
moda cluster-conversations <node_id>
|
|
506
471
|
```
|
|
507
472
|
|
|
508
|
-
### Find degraded crons before customers do
|
|
509
|
-
|
|
510
|
-
```bash
|
|
511
|
-
# Health-sorted workflow board
|
|
512
|
-
moda workflows | jq '.workflows | sort_by(.health) | .[] | select(.runs >= 4 and .health < 0.85)'
|
|
513
|
-
|
|
514
|
-
# What loops are eating tokens right now?
|
|
515
|
-
moda loops --limit=20 | jq '.loops[] | {sig: .loop_signature_human, visits: .cycle_visits, conv: .conversation_id}'
|
|
516
|
-
|
|
517
|
-
# Drill into a partial run
|
|
518
|
-
moda runs --outcome=partial --limit=5 | jq -r '.runs[].job_id' | head -1 | xargs -I{} moda run {}
|
|
519
|
-
```
|
|
520
|
-
|
|
521
473
|
### One-liner chains with jq
|
|
522
474
|
|
|
523
475
|
```bash
|
|
@@ -596,9 +548,6 @@ npx: `npx -p @moda-ai/cli moda <command>`.
|
|
|
596
548
|
| `moda frustrations` | User frustration detections with evidence |
|
|
597
549
|
| `moda tool-failures` | Tool failure overview |
|
|
598
550
|
| `moda tool-failure-detail <tool_name>` | Per-tool failure breakdown + examples |
|
|
599
|
-
| `moda workflows` | Recurring workflow catalog with 30d health per class |
|
|
600
|
-
| `moda runs` | Recent workflow executions with classified outcomes |
|
|
601
|
-
| `moda run <run_id>` | One execution + its side-effect receipts |
|
|
602
551
|
| `moda loops` | Currently-active loop detections |
|
|
603
552
|
| `moda loop <detection_id>` | One loop signature with blast radius |
|
|
604
553
|
| `moda prompts status` | Read-only local prompt status |
|
|
@@ -610,8 +559,7 @@ npx: `npx -p @moda-ai/cli moda <command>`.
|
|
|
610
559
|
`--user-id`, `--time-range=<range>`, `--limit=N` (1–100, default 20).
|
|
611
560
|
|
|
612
561
|
Common flags across most commands: `--days-back=N` (1–90), `--limit=N`,
|
|
613
|
-
`--offset=N`, `--time-range=<range>`.
|
|
614
|
-
`--outcome=<outcome>`, `--class-id=<workflow_class_id>`, and `--user-id=<id>`.
|
|
562
|
+
`--offset=N`, `--time-range=<range>`.
|
|
615
563
|
`frustrations` and `tool-failure-detail` additionally accept
|
|
616
564
|
`--include-window` (attach a `window` slice per row) and `--window=N` (1–5,
|
|
617
565
|
default `1`; half-width, so `--window=1` ⇒ 3 messages).
|