@moda-ai/cli 1.24.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  runPromptsCommand,
6
6
  runSkillsCommand,
7
7
  runStatusCommand
8
- } from "./cli-0t9z27pc.js";
8
+ } from "./cli-xf5nx5hc.js";
9
9
  import {
10
10
  ApiError,
11
11
  HARNESS_REPORT_APPROVAL_PATH,
@@ -26,7 +26,7 @@ import {
26
26
  summarizeHarness,
27
27
  terminalStyles,
28
28
  validateHarnessReport
29
- } from "./cli-f2f0twjw.js";
29
+ } from "./cli-g931ntph.js";
30
30
  import {
31
31
  authFetch,
32
32
  clearAuthSession,
@@ -34,7 +34,7 @@ import {
34
34
  loadAuthSession,
35
35
  login,
36
36
  revokeCliSessionToken
37
- } from "./cli-8s69sg7f.js";
37
+ } from "./cli-9xw5bq1j.js";
38
38
  import {
39
39
  AGENT_ERROR_CODES,
40
40
  CliAccessError,
@@ -79,7 +79,7 @@ import {
79
79
  validateConfig,
80
80
  wasOutputCancelled,
81
81
  writeSecretJsonFile
82
- } from "./cli-vc7zddj1.js";
82
+ } from "./cli-59yacef3.js";
83
83
 
84
84
  // src/cli.ts
85
85
  import { z as z2 } from "zod";
@@ -92,7 +92,11 @@ var OverviewSchema = z.object({
92
92
  });
93
93
  var ClustersSchema = z.object({
94
94
  parent_id: z.string().optional(),
95
- time_range: z.enum(["all", "1h", "3d", "7d", "24h", "30d", "90d"]).default("all").optional()
95
+ time_range: z.enum(["all", "1h", "3d", "7d", "24h", "30d", "90d"]).default("all").optional(),
96
+ search: z.string().min(2).max(200).optional(),
97
+ node_id: z.string().min(1).max(512).optional(),
98
+ mode: z.enum(["fuzzy", "semantic", "hybrid"]).optional(),
99
+ limit: z.number().min(1).max(50).optional()
96
100
  });
97
101
  var ClusterConversationsSchema = z.object({
98
102
  node_id: z.string(),
@@ -114,7 +118,14 @@ var ConversationsSchema = z.object({
114
118
  var WorldStateSchema = z.object({
115
119
  conversation_id: z.string(),
116
120
  summary_only: boolFlag(),
117
- event_limit: z.number().min(1).max(5000).default(2000).optional()
121
+ event_limit: z.number().min(1).max(5000).default(2000).optional(),
122
+ snapshot: boolFlag(),
123
+ msg_index: z.number().min(0).max(1e5).optional(),
124
+ block_index: z.number().min(0).max(1e5).optional(),
125
+ segment_index: z.number().min(0).max(65535).optional(),
126
+ replay: boolFlag(),
127
+ format: z.enum(["frames", "fold-inputs"]).optional(),
128
+ message_count: z.number().min(1).max(1e6).optional()
118
129
  });
119
130
  var ContextSchema = z.object({
120
131
  conversation_id: z.string(),
@@ -135,6 +146,73 @@ var ProblemsSchema = z.object({
135
146
  days_back: z.number().min(1).max(90).default(30).optional(),
136
147
  limit: z.number().min(1).max(25).default(25).optional()
137
148
  });
149
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
150
+ var PROBLEM_FAMILIES = [
151
+ "tool_failure",
152
+ "emotion",
153
+ "laziness",
154
+ "hallucination",
155
+ "prm_dip"
156
+ ];
157
+ var PROBLEM_DOORS = ["rubric_match", "local_causal_chain"];
158
+ var ProblemSchema = z.object({
159
+ id: z.string().min(1),
160
+ evidence: boolFlag(),
161
+ reports: boolFlag(),
162
+ conversations: boolFlag(),
163
+ feedback: boolFlag(),
164
+ limit: z.number().min(1).max(50).optional(),
165
+ cursor: z.string().optional(),
166
+ family: z.enum(PROBLEM_FAMILIES).optional(),
167
+ door: z.enum(PROBLEM_DOORS).optional()
168
+ });
169
+ var PROBLEM_FEEDBACK_ACTIONS = [
170
+ "mark_fixed",
171
+ "dismiss",
172
+ "flag_attribution",
173
+ "rename"
174
+ ];
175
+ var ProblemFeedbackSchema = z.object({
176
+ id: z.string().min(1),
177
+ action: z.enum(PROBLEM_FEEDBACK_ACTIONS),
178
+ reason: z.string().max(2000).optional(),
179
+ attribution_id: z.string().max(64).optional(),
180
+ new_name: z.string().max(255).optional(),
181
+ actor: z.string().max(255).optional()
182
+ });
183
+ var EMOTION_FAMILIES = [
184
+ "frustration",
185
+ "sadness",
186
+ "confusion",
187
+ "anxiety",
188
+ "trust",
189
+ "positive"
190
+ ];
191
+ var EmotionsSchema = z.object({
192
+ days_back: z.number().min(1).max(90).default(7).optional(),
193
+ limit: z.number().min(1).max(20).default(10).optional(),
194
+ offset: z.number().min(0).max(1e4).optional(),
195
+ family: z.enum(EMOTION_FAMILIES).optional()
196
+ });
197
+ var HallucinationsSchema = z.object({
198
+ days_back: z.number().min(1).max(90).default(7).optional(),
199
+ limit: z.number().min(1).max(20).default(10).optional(),
200
+ offset: z.number().min(0).max(1e4).optional(),
201
+ conversation_id: z.string().max(200).optional(),
202
+ kind: z.enum(["contradicted", "verified"]).optional()
203
+ });
204
+ var StepScoresSchema = z.object({
205
+ conversation_id: z.string().min(1)
206
+ });
207
+ var TAIL_SIGNALS = ["conversations", "emotions", "all"];
208
+ var TailSchema = z.object({
209
+ interval: z.number().min(5).max(3600).default(15).optional(),
210
+ signal: z.enum(TAIL_SIGNALS).default("conversations").optional(),
211
+ once: boolFlag(),
212
+ limit: z.number().min(1).max(50).default(20).optional(),
213
+ max_events: z.number().min(1).max(1e5).optional(),
214
+ full_scan: boolFlag()
215
+ });
138
216
  var ToolFailureDetailSchema = z.object({
139
217
  tool_name: z.string(),
140
218
  subtype: z.string().optional(),
@@ -158,7 +236,7 @@ var SearchSchema = z.object({
158
236
  user_id: z.string().optional(),
159
237
  time_range: z.enum(TIME_RANGES).default("all").optional(),
160
238
  limit: z.number().min(1).max(100).default(20).optional(),
161
- include_tool_io: z.boolean().default(false).optional()
239
+ include_tool_io: boolFlag()
162
240
  });
163
241
  var FEEDBACK_CATEGORIES = [
164
242
  "bad_cluster_label",
@@ -1173,6 +1251,10 @@ var ONLINE_PROBE_TIMEOUT_MS = 5000;
1173
1251
  function subcommand(context) {
1174
1252
  return context.positionals[0];
1175
1253
  }
1254
+ function unknownSubcommandError(parent, attempted, valid) {
1255
+ const message = attempted ? `Unknown ${parent} subcommand '${attempted}'. Valid subcommands: ${valid.join(", ")}.` : `A ${parent} subcommand is required. Valid subcommands: ${valid.join(", ")}.`;
1256
+ return new CliInputError(message, `Usage: moda ${parent} <${valid.join("|")}>`);
1257
+ }
1176
1258
  function requirePositional(context, index, usage) {
1177
1259
  const value = context.positionals[index];
1178
1260
  if (!value)
@@ -1318,7 +1400,7 @@ async function runAuthCommand(context) {
1318
1400
  return { exitCode: EXIT_OK };
1319
1401
  }
1320
1402
  default:
1321
- throw new CliInputError("Unknown auth command.", "Usage: moda auth <login|logout|status|token|whoami>");
1403
+ throw unknownSubcommandError("auth", subcommand(context), ["login", "logout", "status", "token", "whoami"]);
1322
1404
  }
1323
1405
  }
1324
1406
  async function runProfilesCommand(context) {
@@ -1396,12 +1478,12 @@ async function runProfilesCommand(context) {
1396
1478
  return { exitCode: 0 };
1397
1479
  }
1398
1480
  default:
1399
- throw new CliInputError("Unknown profiles command.", "Usage: moda profiles <list|use|create|doctor>");
1481
+ throw unknownSubcommandError("profiles", subcommand(context), ["list", "use", "create", "doctor"]);
1400
1482
  }
1401
1483
  }
1402
1484
  async function runConfigCommand(context) {
1403
1485
  if (subcommand(context) !== "show") {
1404
- throw new CliInputError("Unknown config command.", "Usage: moda config show");
1486
+ throw unknownSubcommandError("config", subcommand(context), ["show"]);
1405
1487
  }
1406
1488
  const config = loadProfileConfig({ env: context.env });
1407
1489
  const profile = getActiveProfile({ profile: context.profile, env: context.env });
@@ -1460,7 +1542,7 @@ async function runKeysCommand(context) {
1460
1542
  throw new CliInputError(`moda keys ${action} is waiting on Moda Cloud key lifecycle support for ${keyId}.`, `Required endpoint: ${KEY_LIFECYCLE_ENDPOINT_REQUIREMENTS[action]}`);
1461
1543
  }
1462
1544
  default:
1463
- throw new CliInputError("Unknown keys command.", "Usage: moda keys <list|create|rotate|revoke>");
1545
+ throw unknownSubcommandError("keys", subcommand(context), ["list", "create", "rotate", "revoke"]);
1464
1546
  }
1465
1547
  }
1466
1548
 
@@ -3855,10 +3937,16 @@ Commands:
3855
3937
  world-state <conversation_id> Get a conversation's world state (slots/threads/events)
3856
3938
  context <conversation_id> Get windowed conversation context
3857
3939
  audit <conversation_id|trace_id> Raw span/trace audit (spans, hierarchy, orphans, duplicates)
3858
- frustrations Get user frustration detections
3940
+ frustrations Get user frustration detections (legacy single-family; see emotions)
3941
+ emotions Multi-family emotion detections (frustration, sadness, confusion, anxiety, trust, positive)
3942
+ hallucinations Grounding detections: contradicted/verified outputs with rule breakdown
3859
3943
  tool-failures Get tool failure overview
3860
3944
  tool-failure-detail <tool_name> Get per-tool failure detail
3861
3945
  problems Rank cross-signal Problems by root cause (what to fix first)
3946
+ problem <problem_id> Open one Problem: dossier, or --evidence/--reports/--conversations/--feedback
3947
+ problem-feedback <problem_id> Mark a Problem fixed, dismiss, rename, or flag a bad attribution
3948
+ step-scores <conversation_id> Graph-PRM step scores (per-segment curves, first bad step, rollup)
3949
+ tail Live-tail new conversations/detections as NDJSON (one line per item)
3862
3950
  feedback "<note>" Flag wrong/missing data or CLI quirks to the Moda team
3863
3951
 
3864
3952
  Prompt management:
@@ -3924,6 +4012,52 @@ Search flags:
3924
4012
  World-state flags:
3925
4013
  --summary-only Return segment summaries only (omit the event stream)
3926
4014
  --event-limit=N Max world-state events to return (1-5000, default 2000)
4015
+ --snapshot Folded slots at an exact tick (what the agent believed at that turn)
4016
+ --msg-index=N --block-index=M Snapshot tick (defaults 0; --segment-index=K scopes to a segment)
4017
+ --replay State-over-time replay; frames mode REQUIRES --message-count=N
4018
+ --format=frames|fold-inputs Replay format (default frames)
4019
+ --message-count=N Number of frames to fold (frames mode)
4020
+
4021
+ Emotions flags:
4022
+ --family=F frustration|sadness|confusion|anxiety|trust|positive
4023
+ --days-back=N --limit=N Window 1-90 (default 7); page size 1-20 (default 10)
4024
+ --offset=N Skip N detections (0-10000)
4025
+
4026
+ Hallucinations flags:
4027
+ --kind=contradicted|verified Narrow the detections list
4028
+ --conversation-id=ID Scope summary + list to one conversation
4029
+ --days-back=N --limit=N Window 1-90 (default 7); page size 1-20 (default 10)
4030
+
4031
+ Problem flags (moda problem <id>):
4032
+ --evidence|--reports|--conversations|--feedback
4033
+ Open one sub-resource page (at most one)
4034
+ --limit=N --cursor=TOKEN Keyset paging (1-50; pass next_cursor back verbatim)
4035
+ --family=F --door=D Filter --conversations (families: tool_failure|emotion|laziness|hallucination|prm_dip)
4036
+
4037
+ Problem-feedback flags:
4038
+ --action=A mark_fixed|dismiss|flag_attribution|rename (required)
4039
+ --reason=TEXT Required for dismiss and flag_attribution
4040
+ --attribution-id=UUID Required for flag_attribution (ids come from problem --evidence)
4041
+ --new-name=NAME Required for rename
4042
+
4043
+ Tail flags:
4044
+ --signal=S conversations|emotions|all (default conversations)
4045
+ --interval=N Poll every N seconds (5-3600, default 15)
4046
+ --once One poll, then exit (baseline page)
4047
+ --limit=N --max-events=N Page size per poll; stop after N stdout records
4048
+ (metadata included; a slot is reserved for the
4049
+ emotions coverage record, which falls back to
4050
+ stderr if the bound leaves no room)
4051
+ --full-scan Emotions: scan the whole window instead of the
4052
+ top-scoring prefix (many more requests). /emotions
4053
+ is score-ranked with no cursor, so the default
4054
+ follows the highest-scoring detections and emits a
4055
+ tail_coverage line stating exactly what it saw.
4056
+
4057
+ Clusters search flags:
4058
+ --search="query" Find clusters by name/meaning (task-cluster search)
4059
+ --node-id=ID Deterministically resolve one cluster node (deep link)
4060
+ --mode=fuzzy|semantic|hybrid Cluster search mode (default hybrid)
3927
4061
 
3928
4062
  Feedback flags:
3929
4063
  --category=CAT bad_cluster_label|mismatched_frustration|missing_data|noisy_data|
@@ -3975,12 +4109,12 @@ Examples:
3975
4109
  moda profiles list
3976
4110
  moda profiles use staging
3977
4111
  moda config show --profile=prod
3978
- moda harness analyze --summary
3979
- moda harness analyze --remote
3980
- moda harness analyze --analyst=claude --tui
3981
- moda harness analyze --analyst=claude --no-tui
3982
- moda harness analyze --analyst=claude --summary
3983
- moda harness analyze --analyst=local-scan --summary
4112
+ moda harness analyze --experimental --summary
4113
+ moda harness analyze --experimental --remote
4114
+ moda harness analyze --experimental --analyst=claude --tui
4115
+ moda harness analyze --experimental --analyst=claude --no-tui
4116
+ moda harness analyze --experimental --analyst=claude --summary
4117
+ moda harness analyze --experimental --analyst=local-scan --summary
3984
4118
  moda harness pull --sync
3985
4119
  moda harness validate-report
3986
4120
  moda harness approve --yes
@@ -4009,6 +4143,16 @@ Examples:
4009
4143
  moda search "billing error" --mode=hybrid
4010
4144
  moda search "refund flow" --mode=semantic --time-range=7d --limit=10
4011
4145
  moda world-state <conversation_id> --summary-only
4146
+ moda world-state <conversation_id> --snapshot --msg-index=42
4147
+ moda world-state <conversation_id> --replay --message-count=50
4148
+ moda clusters --search="billing disputes"
4149
+ moda emotions --family=confusion --days-back=14
4150
+ moda hallucinations --kind=contradicted
4151
+ moda problems --days-back=7
4152
+ moda problem <problem_id> --evidence --limit=10
4153
+ moda problem-feedback <problem_id> --action=mark_fixed
4154
+ moda step-scores <conversation_id>
4155
+ moda tail --signal=all --interval=30
4012
4156
  moda frustrations --days-back=14 --limit=20
4013
4157
  moda feedback "cluster label looks wrong" --category=bad_cluster_label --cluster-id=<node_id>
4014
4158
  moda context <conversation_id> --window=3
@@ -4020,6 +4164,25 @@ Examples:
4020
4164
  moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --conversations=conv_1,conv_2
4021
4165
  moda skills pull`);
4022
4166
  }
4167
+ function printCommandHelp(definition) {
4168
+ const lines = [];
4169
+ lines.push(`moda ${definition.name} — ${definition.description}`);
4170
+ if (definition.aliases && definition.aliases.length > 0) {
4171
+ lines.push(`Aliases: ${definition.aliases.join(", ")}`);
4172
+ }
4173
+ if (definition.mutability === "write") {
4174
+ lines.push("This command can mutate state.");
4175
+ }
4176
+ if (definition.examples.length > 0) {
4177
+ lines.push("", "Examples:");
4178
+ for (const example of definition.examples) {
4179
+ lines.push(` ${example}`);
4180
+ }
4181
+ }
4182
+ lines.push("", "Run 'moda --help' for the full command list and common flags", "(--json, --agent, --profile, --days-back, --limit, --time-range, ...).");
4183
+ console.error(lines.join(`
4184
+ `));
4185
+ }
4023
4186
  async function attachWindows(rows, windowSize) {
4024
4187
  return Promise.all(rows.map(async (row) => {
4025
4188
  const anchor = asRecord(row.anchor);
@@ -4051,14 +4214,201 @@ async function attachWindows(rows, windowSize) {
4051
4214
  }
4052
4215
  }));
4053
4216
  }
4054
- async function printWithOptionalWindows(enriched, listKey, includeWindow, windowSize, output) {
4217
+ async function printWithOptionalWindows(enriched, listKey, includeWindow, windowSize, output, warnings) {
4218
+ const writeOptions = warnings && warnings.length > 0 ? { warnings } : undefined;
4055
4219
  if (!includeWindow) {
4056
- output.writeData(enriched);
4220
+ output.writeData(enriched, writeOptions);
4057
4221
  return;
4058
4222
  }
4059
4223
  const list = Array.isArray(enriched[listKey]) ? enriched[listKey] : [];
4060
4224
  const withWindows = await attachWindows(list, windowSize);
4061
- output.writeData({ ...enriched, [listKey]: withWindows });
4225
+ output.writeData({ ...enriched, [listKey]: withWindows }, writeOptions);
4226
+ }
4227
+ function notFoundWarning(entity, id, isMissing) {
4228
+ if (!isMissing)
4229
+ return [];
4230
+ return [
4231
+ `No ${entity} found for id "${id}" — it may not exist in this tenant (the Data API returns an empty result rather than an error for unknown ids).`
4232
+ ];
4233
+ }
4234
+ var TAIL_SEEN_MAX = 5000;
4235
+ var EMOTIONS_TAIL_PAGE_SIZE = 20;
4236
+ var EMOTIONS_TAIL_MAX_PAGES = 25;
4237
+ var EMOTIONS_OFFSET_CEILING = 1e4;
4238
+ var EMOTIONS_FULL_SCAN_MAX_PAGES = Math.floor(EMOTIONS_OFFSET_CEILING / EMOTIONS_TAIL_PAGE_SIZE) + 1;
4239
+ var EMOTIONS_SEEN_MAX = EMOTIONS_FULL_SCAN_MAX_PAGES * EMOTIONS_TAIL_PAGE_SIZE + 2000;
4240
+ var CONVERSATIONS_SEEN_MAX = TAIL_SEEN_MAX;
4241
+ var TAIL_LIMITS = {
4242
+ emotionsPageSize: EMOTIONS_TAIL_PAGE_SIZE,
4243
+ emotionsFullScanMaxPages: EMOTIONS_FULL_SCAN_MAX_PAGES,
4244
+ emotionsSeenMax: EMOTIONS_SEEN_MAX,
4245
+ conversationsSeenMax: CONVERSATIONS_SEEN_MAX,
4246
+ independentKeyspaces: true
4247
+ };
4248
+ async function runTailCommand(params, context) {
4249
+ const intervalMs = (params.interval ?? 15) * 1000;
4250
+ const signal = params.signal ?? "conversations";
4251
+ const limit = params.limit ?? 20;
4252
+ const seenConversations = new Set;
4253
+ const seenEmotions = new Set;
4254
+ let emitted = 0;
4255
+ let evictionReported = false;
4256
+ const rememberIn = (seen, capacity, key) => {
4257
+ if (seen.has(key))
4258
+ return false;
4259
+ if (seen.size >= capacity) {
4260
+ const iterator = seen.values();
4261
+ for (let i = 0;i < Math.max(1, Math.floor(capacity / 10)); i++) {
4262
+ const next = iterator.next();
4263
+ if (next.done)
4264
+ break;
4265
+ seen.delete(next.value);
4266
+ }
4267
+ if (!evictionReported) {
4268
+ evictionReported = true;
4269
+ emitMeta({
4270
+ type: "tail_dedupe_evicted",
4271
+ capacity,
4272
+ detail: "Dedupe memory reached capacity and oldest ids were dropped; items seen long ago may re-appear once. Restart the tail, or narrow --signal, if exact-once matters."
4273
+ });
4274
+ }
4275
+ }
4276
+ seen.add(key);
4277
+ return true;
4278
+ };
4279
+ const rememberConversation = (key) => rememberIn(seenConversations, CONVERSATIONS_SEEN_MAX, key);
4280
+ const rememberEmotion = (key) => rememberIn(seenEmotions, EMOTIONS_SEEN_MAX, key);
4281
+ const emitsCoverage = signal === "emotions" || signal === "all";
4282
+ const metaReserve = params.max_events !== undefined && emitsCoverage && params.max_events >= 2 ? 1 : 0;
4283
+ const dataBudget = params.max_events === undefined ? undefined : Math.max(0, params.max_events - metaReserve);
4284
+ const capReached = () => dataBudget !== undefined && emitted >= dataBudget;
4285
+ const emitLine = (item) => {
4286
+ if (capReached())
4287
+ return;
4288
+ console.log(JSON.stringify(item));
4289
+ emitted++;
4290
+ };
4291
+ const emitMeta = (item) => {
4292
+ if (params.max_events !== undefined && emitted >= params.max_events) {
4293
+ safeWriteStderr(`${JSON.stringify(item)}
4294
+ `);
4295
+ return;
4296
+ }
4297
+ console.log(JSON.stringify(item));
4298
+ emitted++;
4299
+ };
4300
+ const pollConversations = async () => {
4301
+ const data = await callDataAPI(`/conversations?time_range=1h&limit=${limit}`);
4302
+ const record = asRecord(data) ?? {};
4303
+ const rows = Array.isArray(record.conversations) ? record.conversations : [];
4304
+ for (const row of [...rows].reverse()) {
4305
+ const conversation = asRecord(row);
4306
+ if (!conversation)
4307
+ continue;
4308
+ const id = asString(conversation.conversation_id) ?? asString(conversation.id);
4309
+ if (!id)
4310
+ continue;
4311
+ const timestamp = asString(conversation.last_timestamp) ?? asString(conversation.timestamp) ?? asString(conversation.last_activity) ?? "";
4312
+ const messageCount = asNumber(conversation.message_count);
4313
+ if (capReached())
4314
+ return;
4315
+ if (!rememberConversation(`c:${id}:${timestamp}:${messageCount ?? ""}`))
4316
+ continue;
4317
+ emitLine({
4318
+ type: "conversation",
4319
+ conversation_id: id,
4320
+ timestamp: timestamp || null,
4321
+ message_count: messageCount,
4322
+ environment: conversation.environment ?? null,
4323
+ cluster_name: conversation.cluster_name ?? null,
4324
+ summary: conversation.summary ?? null
4325
+ });
4326
+ }
4327
+ };
4328
+ const pollEmotions = async () => {
4329
+ const maxPages = params.full_scan === true ? EMOTIONS_FULL_SCAN_MAX_PAGES : EMOTIONS_TAIL_MAX_PAGES;
4330
+ const collected = [];
4331
+ let offset = 0;
4332
+ let pages = 0;
4333
+ let truncated = false;
4334
+ let total = null;
4335
+ for (;; ) {
4336
+ const data = await callDataAPI(`/emotions?days_back=1&limit=${EMOTIONS_TAIL_PAGE_SIZE}&offset=${offset}`);
4337
+ const record = asRecord(data) ?? {};
4338
+ const rows = Array.isArray(record.detections) ? record.detections : [];
4339
+ collected.push(...rows.map((row) => asRecord(row)).filter((row) => row !== null));
4340
+ pages++;
4341
+ const pagination = asRecord(record.pagination) ?? {};
4342
+ total = asNumber(pagination.total) ?? total;
4343
+ const hasMore = pagination.has_more === true;
4344
+ if (!hasMore || rows.length === 0)
4345
+ break;
4346
+ if (pages >= maxPages) {
4347
+ truncated = true;
4348
+ break;
4349
+ }
4350
+ if (offset + EMOTIONS_TAIL_PAGE_SIZE > EMOTIONS_OFFSET_CEILING) {
4351
+ truncated = true;
4352
+ break;
4353
+ }
4354
+ offset += EMOTIONS_TAIL_PAGE_SIZE;
4355
+ }
4356
+ collected.sort((a, b) => (asString(a.detected_at) ?? "").localeCompare(asString(b.detected_at) ?? ""));
4357
+ for (const detection of collected) {
4358
+ if (capReached())
4359
+ break;
4360
+ const detectionId = asString(detection.detection_id);
4361
+ if (!detectionId || !rememberEmotion(`e:${detectionId}`))
4362
+ continue;
4363
+ emitLine({
4364
+ type: "emotion",
4365
+ detection_id: detectionId,
4366
+ conversation_id: detection.conversation_id ?? null,
4367
+ family: detection.family ?? null,
4368
+ primary_cause: detection.primary_cause ?? null,
4369
+ detected_at: detection.detected_at ?? null
4370
+ });
4371
+ }
4372
+ if (truncated || total !== null && collected.length < total) {
4373
+ const scanned = collected.length;
4374
+ const coveragePct = total && total > 0 ? Math.round(scanned / total * 1000) / 10 : null;
4375
+ emitMeta({
4376
+ type: "tail_coverage",
4377
+ signal: "emotions",
4378
+ complete: false,
4379
+ scanned,
4380
+ total,
4381
+ coverage_pct: coveragePct,
4382
+ pages_scanned: pages,
4383
+ detail: `Scanned ${scanned}${total !== null ? ` of ${total}` : ""} detection(s) in the last day` + `${coveragePct !== null ? ` (${coveragePct}%)` : ""}. /emotions is ranked by score with no ` + "time ordering or cursor, so this follows the highest-scoring detections and " + "lower-scoring ones may not appear. Use --full-scan for completeness (many more " + "requests, up to the API offset ceiling of 10000), or `moda emotions --offset=N` to page manually."
4384
+ });
4385
+ } else if (total !== null) {
4386
+ emitMeta({
4387
+ type: "tail_coverage",
4388
+ signal: "emotions",
4389
+ complete: true,
4390
+ scanned: collected.length,
4391
+ total,
4392
+ coverage_pct: 100,
4393
+ pages_scanned: pages,
4394
+ detail: "Scanned the full window for this poll."
4395
+ });
4396
+ }
4397
+ };
4398
+ if (params.once !== true && context.output.mode === "human") {
4399
+ console.error(`Tailing ${signal} every ${intervalMs / 1000}s (Ctrl-C to stop). One JSON line per new item.`);
4400
+ }
4401
+ for (;; ) {
4402
+ if (signal === "conversations" || signal === "all")
4403
+ await pollConversations();
4404
+ if (signal === "emotions" || signal === "all")
4405
+ await pollEmotions();
4406
+ if (params.once === true)
4407
+ break;
4408
+ if (params.max_events !== undefined && emitted >= params.max_events)
4409
+ break;
4410
+ await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
4411
+ }
4062
4412
  }
4063
4413
  async function attachWorldState(rows) {
4064
4414
  return Promise.all(rows.map(async (row) => {
@@ -4130,6 +4480,23 @@ async function runCommand(command, positional, flags, positionals = positional ?
4130
4480
  case "clusters": {
4131
4481
  const args = flagsToArgs(flags);
4132
4482
  const params = ClustersSchema.parse(args);
4483
+ if (params.search !== undefined || params.node_id !== undefined) {
4484
+ const query2 = new URLSearchParams;
4485
+ if (params.search !== undefined)
4486
+ query2.set("q", params.search);
4487
+ if (params.node_id !== undefined)
4488
+ query2.set("node_id", params.node_id);
4489
+ if (params.mode)
4490
+ query2.set("mode", params.mode);
4491
+ if (params.limit)
4492
+ query2.set("limit", params.limit.toString());
4493
+ const data2 = await callDataAPI(`/task-clusters/search?${query2.toString()}`);
4494
+ const record = asRecord(data2) ?? {};
4495
+ const matches = Array.isArray(record.matches) ? record.matches : [];
4496
+ const warnings = params.node_id !== undefined && matches.length === 0 ? notFoundWarning("cluster", params.node_id, true) : [];
4497
+ context.output.writeData(data2, warnings.length > 0 ? { warnings } : undefined);
4498
+ break;
4499
+ }
4133
4500
  const query = new URLSearchParams;
4134
4501
  if (params.parent_id)
4135
4502
  query.set("parent_id", params.parent_id);
@@ -4153,7 +4520,9 @@ async function runCommand(command, positional, flags, positionals = positional ?
4153
4520
  query.set("offset", params.offset.toString());
4154
4521
  const queryString = query.toString() ? `?${query.toString()}` : "";
4155
4522
  const data = await callDataAPI(`/clusters/${params.node_id}/conversations${queryString}`);
4156
- context.output.writeData(data);
4523
+ const clusterRecord = asRecord(data) ?? {};
4524
+ const warnings = notFoundWarning("cluster", params.node_id, clusterRecord.cluster === null || clusterRecord.cluster === undefined);
4525
+ context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
4157
4526
  break;
4158
4527
  }
4159
4528
  case "conversations": {
@@ -4211,7 +4580,16 @@ async function runCommand(command, positional, flags, positionals = positional ?
4211
4580
  query.set("include_tool_io", "true");
4212
4581
  const data = await callDataAPI(`/search?${query.toString()}`);
4213
4582
  recordSearchTelemetry(context, data);
4214
- context.output.writeData(data);
4583
+ const searchRecord = asRecord(data) ?? {};
4584
+ const degradeReason = asString(searchRecord.degrade_reason);
4585
+ const actualMode = asString(searchRecord.search_mode);
4586
+ const warnings = [];
4587
+ if (degradeReason && degradeReason !== "none") {
4588
+ warnings.push(`Search degraded to keyword mode (${degradeReason}): the requested ${params.mode ?? "hybrid"} ranking did not run, and keyword scores are not comparable to semantic scores. Retry, or use --mode=keyword with exact identifiers.`);
4589
+ } else if (params.mode && actualMode && actualMode !== params.mode && params.mode !== "hybrid") {
4590
+ warnings.push(`Search ran in ${actualMode} mode instead of the requested ${params.mode} mode.`);
4591
+ }
4592
+ context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
4215
4593
  break;
4216
4594
  }
4217
4595
  case "world-state": {
@@ -4220,6 +4598,37 @@ async function runCommand(command, positional, flags, positionals = positional ?
4220
4598
  }
4221
4599
  const args = flagsToArgs(flags, "conversation_id", positional);
4222
4600
  const params = WorldStateSchema.parse(args);
4601
+ if (params.snapshot === true && params.replay === true) {
4602
+ throw new CliInputError("Choose either --snapshot or --replay, not both.", "Usage: moda world-state <conversation_id> [--snapshot --msg-index=N [--block-index=M] [--segment-index=K]] | [--replay --message-count=N [--format=frames|fold-inputs]]");
4603
+ }
4604
+ if (params.snapshot === true) {
4605
+ const query2 = new URLSearchParams;
4606
+ query2.set("msg_index", (params.msg_index ?? 0).toString());
4607
+ if (params.block_index !== undefined)
4608
+ query2.set("block_index", params.block_index.toString());
4609
+ if (params.segment_index !== undefined)
4610
+ query2.set("segment_index", params.segment_index.toString());
4611
+ const data2 = await callDataAPI(`/conversations/${params.conversation_id}/world-state/snapshot?${query2.toString()}`);
4612
+ const snapshotRecord = asRecord(data2) ?? {};
4613
+ const warnings2 = notFoundWarning("world state", params.conversation_id, snapshotRecord.found === false);
4614
+ context.output.writeData(data2, warnings2.length > 0 ? { warnings: warnings2 } : undefined);
4615
+ break;
4616
+ }
4617
+ if (params.replay === true) {
4618
+ const format = params.format ?? "frames";
4619
+ if (format === "frames" && params.message_count === undefined) {
4620
+ throw new CliInputError("--message-count=N is required with --replay in frames mode (the API returns no frames without it). Use --format=fold-inputs for the raw keyframes/events without a count.");
4621
+ }
4622
+ const query2 = new URLSearchParams;
4623
+ query2.set("format", format === "fold-inputs" ? "fold_inputs" : "frames");
4624
+ if (params.message_count !== undefined)
4625
+ query2.set("message_count", params.message_count.toString());
4626
+ const data2 = await callDataAPI(`/conversations/${params.conversation_id}/world-state/replay?${query2.toString()}`);
4627
+ const replayRecord = asRecord(data2) ?? {};
4628
+ const warnings2 = notFoundWarning("world state", params.conversation_id, replayRecord.hasData === false);
4629
+ context.output.writeData(data2, warnings2.length > 0 ? { warnings: warnings2 } : undefined);
4630
+ break;
4631
+ }
4223
4632
  const query = new URLSearchParams;
4224
4633
  if (params.summary_only === true)
4225
4634
  query.set("summary_only", "1");
@@ -4227,7 +4636,9 @@ async function runCommand(command, positional, flags, positionals = positional ?
4227
4636
  query.set("event_limit", params.event_limit.toString());
4228
4637
  const queryString = query.toString() ? `?${query.toString()}` : "";
4229
4638
  const data = await callDataAPI(`/conversations/${params.conversation_id}/world-state${queryString}`);
4230
- context.output.writeData(data);
4639
+ const wsRecord = asRecord(data) ?? {};
4640
+ const warnings = notFoundWarning("world state", params.conversation_id, wsRecord.hasData === false);
4641
+ context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
4231
4642
  break;
4232
4643
  }
4233
4644
  case "context": {
@@ -4243,7 +4654,9 @@ async function runCommand(command, positional, flags, positionals = positional ?
4243
4654
  query.set("window", params.window.toString());
4244
4655
  const queryString = query.toString() ? `?${query.toString()}` : "";
4245
4656
  const data = await callDataAPI(`/conversations/${params.conversation_id}/context${queryString}`);
4246
- context.output.writeData(data);
4657
+ const ctxRecord = asRecord(data) ?? {};
4658
+ const warnings = notFoundWarning("conversation", params.conversation_id, asNumber(ctxRecord.total_messages) === 0);
4659
+ context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
4247
4660
  break;
4248
4661
  }
4249
4662
  case "audit":
@@ -4305,7 +4718,13 @@ async function runCommand(command, positional, flags, positionals = positional ?
4305
4718
  query.set("offset", params.offset.toString());
4306
4719
  const queryString = query.toString() ? `?${query.toString()}` : "";
4307
4720
  const data = await callDataAPI(`/tool-failures/${params.tool_name}${queryString}`);
4308
- await printWithOptionalWindows(withToolFailureAnchors(data), "examples", params.include_window === true, params.window ?? 1, context.output);
4721
+ const tfRecord = asRecord(data) ?? {};
4722
+ const subtypeCount = Array.isArray(tfRecord.subtypes) ? tfRecord.subtypes.length : 0;
4723
+ const exampleCount = Array.isArray(tfRecord.examples) ? tfRecord.examples.length : 0;
4724
+ const warnings = subtypeCount === 0 && exampleCount === 0 ? [
4725
+ `No failures found for tool "${params.tool_name}" in the last ${params.days_back ?? 7} day(s) — the tool may have no failures in this window, or the name may not exist in this tenant. Run \`moda tool-failures\` to list tools with recorded failures, or widen --days-back.`
4726
+ ] : [];
4727
+ await printWithOptionalWindows(withToolFailureAnchors(data), "examples", params.include_window === true, params.window ?? 1, context.output, warnings);
4309
4728
  break;
4310
4729
  }
4311
4730
  case "problems": {
@@ -4325,6 +4744,149 @@ async function runCommand(command, positional, flags, positionals = positional ?
4325
4744
  context.output.writeData(withLink);
4326
4745
  break;
4327
4746
  }
4747
+ case "problem": {
4748
+ if (!positional) {
4749
+ throw new CliInputError("<problem_id> is required", "Usage: moda problem <problem_id> [--evidence|--reports|--conversations|--feedback] [--limit=N] [--cursor=TOKEN] [--family=F] [--door=D]");
4750
+ }
4751
+ const args = flagsToArgs(flags, "id", positional);
4752
+ const params = ProblemSchema.parse(args);
4753
+ const views = ["evidence", "reports", "conversations", "feedback"].filter((view2) => params[view2] === true);
4754
+ if (views.length > 1) {
4755
+ throw new CliInputError(`Choose at most one of --evidence, --reports, --conversations, --feedback (got ${views.map((v) => `--${v}`).join(" ")}).`, "Usage: moda problem <problem_id> [--evidence|--reports|--conversations|--feedback]");
4756
+ }
4757
+ const view = views[0];
4758
+ if (view === undefined) {
4759
+ const data2 = await callDataAPI(`/problems/${encodeURIComponent(params.id)}`);
4760
+ const record = asRecord(data2) ?? {};
4761
+ const warnings = record.found === false ? [
4762
+ `No problem found for id "${params.id}" — it may not exist in this tenant, or may have been merged away (the API returns found:false rather than an error).`
4763
+ ] : [];
4764
+ context.output.writeData(data2, warnings.length > 0 ? { warnings } : undefined);
4765
+ break;
4766
+ }
4767
+ if (!UUID_RE.test(params.id)) {
4768
+ throw new CliInputError(`--${view} requires a canonical problem UUID; got "${params.id}". Run \`moda problems\` or \`moda problem <id>\` first to resolve the id.`);
4769
+ }
4770
+ const query = new URLSearchParams;
4771
+ if (params.limit)
4772
+ query.set("limit", params.limit.toString());
4773
+ if (params.cursor)
4774
+ query.set("cursor", params.cursor);
4775
+ if (view === "conversations") {
4776
+ if (params.family)
4777
+ query.set("family", params.family);
4778
+ if (params.door)
4779
+ query.set("door", params.door);
4780
+ }
4781
+ const queryString = query.toString() ? `?${query.toString()}` : "";
4782
+ const data = await callDataAPI(`/problems/${params.id}/${view}${queryString}`);
4783
+ context.output.writeData(data);
4784
+ break;
4785
+ }
4786
+ case "problem-feedback": {
4787
+ if (!positional) {
4788
+ throw new CliInputError("<problem_id> is required", `Usage: moda problem-feedback <problem_id> --action=${PROBLEM_FEEDBACK_ACTIONS.join("|")} [--reason=TEXT] [--attribution-id=UUID] [--new-name=NAME]`);
4789
+ }
4790
+ const params = ProblemFeedbackSchema.parse({
4791
+ id: positional,
4792
+ ...flags.action !== undefined ? { action: flags.action } : {},
4793
+ ...flags.reason !== undefined ? { reason: flags.reason } : {},
4794
+ ...flags["attribution-id"] !== undefined ? { attribution_id: flags["attribution-id"] } : {},
4795
+ ...flags["new-name"] !== undefined ? { new_name: flags["new-name"] } : {},
4796
+ ...flags.actor !== undefined ? { actor: flags.actor } : {}
4797
+ });
4798
+ if (!UUID_RE.test(params.id)) {
4799
+ throw new CliInputError(`Problem id must be a canonical UUID; got "${params.id}".`);
4800
+ }
4801
+ const reason = params.reason?.trim() ?? "";
4802
+ if ((params.action === "dismiss" || params.action === "flag_attribution") && reason === "") {
4803
+ throw new CliInputError(`--reason is required for --action=${params.action}.`);
4804
+ }
4805
+ if (params.action === "rename" && (params.new_name?.trim() ?? "") === "") {
4806
+ throw new CliInputError("--new-name is required for --action=rename.");
4807
+ }
4808
+ if (params.action === "flag_attribution") {
4809
+ if (params.attribution_id === undefined || params.attribution_id.trim() === "") {
4810
+ throw new CliInputError("--attribution-id=<uuid> is required for --action=flag_attribution. Run `moda problem <problem_id> --evidence` to list attribution ids.");
4811
+ }
4812
+ if (!UUID_RE.test(params.attribution_id)) {
4813
+ throw new CliInputError("--attribution-id must be a UUID.");
4814
+ }
4815
+ }
4816
+ const data = await callDataAPI(`/problems/${params.id}/feedback`, {
4817
+ method: "POST",
4818
+ body: JSON.stringify({
4819
+ action: params.action,
4820
+ ...reason !== "" ? { reason } : {},
4821
+ ...params.attribution_id ? { attribution_id: params.attribution_id } : {},
4822
+ ...params.new_name ? { new_display_name: params.new_name } : {},
4823
+ actor: params.actor ?? "moda-cli"
4824
+ }),
4825
+ headers: { "Content-Type": "application/json" }
4826
+ }, { retries: 0 });
4827
+ context.output.writeData(data);
4828
+ break;
4829
+ }
4830
+ case "emotions": {
4831
+ const args = flagsToArgs(flags);
4832
+ const params = EmotionsSchema.parse(args);
4833
+ const query = new URLSearchParams;
4834
+ if (params.days_back)
4835
+ query.set("days_back", params.days_back.toString());
4836
+ if (params.limit)
4837
+ query.set("limit", params.limit.toString());
4838
+ if (params.offset !== undefined)
4839
+ query.set("offset", params.offset.toString());
4840
+ if (params.family)
4841
+ query.set("family", params.family);
4842
+ const queryString = query.toString() ? `?${query.toString()}` : "";
4843
+ const data = await callDataAPI(`/emotions${queryString}`);
4844
+ context.output.writeData(data);
4845
+ break;
4846
+ }
4847
+ case "hallucinations": {
4848
+ const args = flagsToArgs(flags);
4849
+ if (flags["conversation-id"] !== undefined) {
4850
+ args.conversation_id = flags["conversation-id"];
4851
+ }
4852
+ const params = HallucinationsSchema.parse(args);
4853
+ const query = new URLSearchParams;
4854
+ if (params.days_back)
4855
+ query.set("days_back", params.days_back.toString());
4856
+ if (params.limit)
4857
+ query.set("limit", params.limit.toString());
4858
+ if (params.offset !== undefined)
4859
+ query.set("offset", params.offset.toString());
4860
+ if (params.conversation_id)
4861
+ query.set("conversation_id", params.conversation_id);
4862
+ if (params.kind)
4863
+ query.set("kind", params.kind);
4864
+ const queryString = query.toString() ? `?${query.toString()}` : "";
4865
+ const data = await callDataAPI(`/hallucinations${queryString}`);
4866
+ context.output.writeData(data);
4867
+ break;
4868
+ }
4869
+ case "step-scores": {
4870
+ if (!positional) {
4871
+ throw new CliInputError("<conversation_id> is required", "Usage: moda step-scores <conversation_id>");
4872
+ }
4873
+ const params = StepScoresSchema.parse({ conversation_id: positional });
4874
+ const data = await callDataAPI(`/conversations/${encodeURIComponent(params.conversation_id)}/step-scores`);
4875
+ const record = asRecord(data) ?? {};
4876
+ const steps = Array.isArray(record.steps) ? record.steps : [];
4877
+ const segments = Array.isArray(record.segments) ? record.segments : [];
4878
+ const warnings = steps.length === 0 && segments.length === 0 ? [
4879
+ `No step scores found for conversation "${params.conversation_id}" — it may not exist in this tenant, or has not been scored yet.`
4880
+ ] : [];
4881
+ context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
4882
+ break;
4883
+ }
4884
+ case "tail": {
4885
+ const args = flagsToArgs(flags);
4886
+ const params = TailSchema.parse(args);
4887
+ await runTailCommand(params, context);
4888
+ break;
4889
+ }
4328
4890
  case "feedback": {
4329
4891
  const note = positionals.join(" ").trim();
4330
4892
  if (note === "") {
@@ -4463,7 +5025,7 @@ var commandRegistry = createCommandRegistry([
4463
5025
  resetApiRequestCountBeforeRun: true,
4464
5026
  telemetry: "result",
4465
5027
  handler: async (context) => {
4466
- const { runInit } = await import("./index-as6n8qxp.js");
5028
+ const { runInit } = await import("./index-efv643ec.js");
4467
5029
  if (context.outputMode === "agent-stream") {
4468
5030
  context.output.writeEvent({
4469
5031
  event: "started",
@@ -4504,7 +5066,7 @@ var commandRegistry = createCommandRegistry([
4504
5066
  resetApiRequestCountBeforeRun: true,
4505
5067
  telemetry: "result-and-error",
4506
5068
  handler: async (context) => {
4507
- const { runProvision } = await import("./provision-dphktm75.js");
5069
+ const { runProvision } = await import("./provision-r0cq3g2v.js");
4508
5070
  return runProvision(context, {
4509
5071
  resumeCommand: buildResumeCommand("provision", context.rawFlags)
4510
5072
  });
@@ -4617,8 +5179,8 @@ var commandRegistry = createCommandRegistry([
4617
5179
  name: "harness",
4618
5180
  description: "Analyze, validate, approve, sync, delete, and explain the local multi-agent harness graph",
4619
5181
  examples: [
4620
- "moda harness analyze --summary",
4621
- "moda harness analyze --remote",
5182
+ "moda harness analyze --experimental --summary",
5183
+ "moda harness analyze --experimental --remote",
4622
5184
  "moda harness pull --sync",
4623
5185
  "moda harness approve --yes",
4624
5186
  "moda harness sync --from-report .moda/harness-report.json",
@@ -4775,6 +5337,65 @@ var commandRegistry = createCommandRegistry([
4775
5337
  examples: ["moda problems", "moda problems --days-back=7 --limit=10"],
4776
5338
  ...dataApiDefaults
4777
5339
  }),
5340
+ legacyCommand({
5341
+ name: "problem",
5342
+ description: "Open one Problem: dossier, or --evidence/--reports/--conversations/--feedback pages",
5343
+ examples: [
5344
+ "moda problem <problem_id>",
5345
+ "moda problem <problem_id> --evidence --limit=10",
5346
+ "moda problem <problem_id> --reports",
5347
+ "moda problem <problem_id> --conversations --family=tool_failure",
5348
+ "moda problem <problem_id> --feedback"
5349
+ ],
5350
+ ...dataApiDefaults
5351
+ }),
5352
+ legacyCommand({
5353
+ name: "problem-feedback",
5354
+ description: "Submit Problem feedback: mark fixed, dismiss, rename, or flag a bad attribution",
5355
+ examples: [
5356
+ "moda problem-feedback <problem_id> --action=mark_fixed",
5357
+ 'moda problem-feedback <problem_id> --action=dismiss --reason="not actionable"',
5358
+ 'moda problem-feedback <problem_id> --action=rename --new-name="Better title"',
5359
+ 'moda problem-feedback <problem_id> --action=flag_attribution --attribution-id=<uuid> --reason="wrong conversation"'
5360
+ ],
5361
+ ...dataApiDefaults,
5362
+ mutability: "write"
5363
+ }),
5364
+ legacyCommand({
5365
+ name: "emotions",
5366
+ description: "Multi-family emotion detections (frustration, sadness, confusion, anxiety, trust, positive)",
5367
+ examples: [
5368
+ "moda emotions --days-back=7",
5369
+ "moda emotions --family=confusion --limit=20"
5370
+ ],
5371
+ ...dataApiDefaults
5372
+ }),
5373
+ legacyCommand({
5374
+ name: "hallucinations",
5375
+ description: "Grounding detections: contradicted/verified outputs with rule breakdown",
5376
+ examples: [
5377
+ "moda hallucinations --days-back=30",
5378
+ "moda hallucinations --kind=contradicted --limit=20",
5379
+ "moda hallucinations --conversation-id=<id>"
5380
+ ],
5381
+ ...dataApiDefaults
5382
+ }),
5383
+ legacyCommand({
5384
+ name: "step-scores",
5385
+ description: "Graph-PRM step scores for a conversation (per-segment curves, first bad step, rollup)",
5386
+ examples: ["moda step-scores <conversation_id>"],
5387
+ ...dataApiDefaults
5388
+ }),
5389
+ legacyCommand({
5390
+ name: "tail",
5391
+ description: "Live-tail new conversations and detections as NDJSON (one JSON line per item)",
5392
+ examples: [
5393
+ "moda tail",
5394
+ "moda tail --signal=emotions --interval=30",
5395
+ "moda tail --once"
5396
+ ],
5397
+ ...dataApiDefaults
5398
+ }),
4778
5399
  legacyCommand({
4779
5400
  name: "feedback",
4780
5401
  description: "Flag wrong/missing data or CLI quirks to the Moda team",
@@ -4888,7 +5509,12 @@ async function dispatchParsedCommand(parsed, startedAt = Date.now()) {
4888
5509
  return 0;
4889
5510
  }
4890
5511
  if (command === "--help" || command === "-h" || flags.help === "true") {
4891
- printHelp();
5512
+ const helpTarget = command && command !== "--help" && command !== "-h" ? commandRegistry.find(command) : undefined;
5513
+ if (helpTarget) {
5514
+ printCommandHelp(helpTarget);
5515
+ } else {
5516
+ printHelp();
5517
+ }
4892
5518
  return 0;
4893
5519
  }
4894
5520
  if (command === "--json-schemas" || flags["json-schemas"] === "true") {
@@ -5069,5 +5695,6 @@ export {
5069
5695
  commandRegistry,
5070
5696
  classifyCliError,
5071
5697
  buildResumeCommand,
5698
+ TAIL_LIMITS,
5072
5699
  CliInputError
5073
5700
  };