@moda-ai/cli 1.55.25 → 1.55.27

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.
Files changed (34) hide show
  1. package/dist/{agent-t7dxmpdq.js → agent-vp9prdma.js} +8 -8
  2. package/dist/{cli-qycsmxev.js → cli-0pkc8jhg.js} +3 -3
  3. package/dist/{cli-mebf5vtr.js → cli-4h0ave0f.js} +5 -5
  4. package/dist/{cli-m0045npe.js → cli-6et4z504.js} +10 -8
  5. package/dist/{cli-9hc5bg2r.js → cli-ccjfwzqx.js} +1 -1
  6. package/dist/{cli-0wr3483h.js → cli-hmjdgxrn.js} +1 -1
  7. package/dist/{cli-9btx12wp.js → cli-pg5qfmqc.js} +1 -1
  8. package/dist/{cli-m805msp6.js → cli-q9nsvqqg.js} +14 -14
  9. package/dist/{cli-7xc585hc.js → cli-qwgct6x7.js} +7 -6
  10. package/dist/{cli-dve50x7p.js → cli-r5pp7qny.js} +140 -455
  11. package/dist/{cli-qvg26twp.js → cli-v8q7sd7g.js} +38 -13
  12. package/dist/cli-vdkwkkp5.js +567 -0
  13. package/dist/{cli-dn91fb56.js → cli-wzds257x.js} +92 -49
  14. package/dist/cli.js +986 -362
  15. package/dist/{doctor-q21mr5ya.js → doctor-wmre3mr7.js} +8 -8
  16. package/dist/{fix-0qdtdz5h.js → fix-75s9s0d6.js} +45 -47
  17. package/dist/{harness-vzy7n7ea.js → harness-a7tb9a23.js} +5 -5
  18. package/dist/{harness-github-actions-atc5bbkj.js → harness-github-actions-6v4wydb8.js} +5 -5
  19. package/dist/{index-4b1ybz51.js → index-91bpc8g8.js} +15 -14
  20. package/dist/{production-intelligence-c8e0xw3n.js → production-intelligence-7nqddd4f.js} +88 -28
  21. package/dist/{profile-commands-6e2dgv72.js → profile-commands-49yn02yg.js} +5 -4
  22. package/dist/prompt-source-mcp.js +5 -5
  23. package/dist/{prompts-3h5jcjrw.js → prompts-17wyrst6.js} +6 -6
  24. package/dist/{provision-q86phh4a.js → provision-bc3h68e1.js} +5 -5
  25. package/dist/{registry-s2vdsfw8.js → registry-fpzhjznj.js} +3 -3
  26. package/dist/{skills-vq3njc8a.js → skills-ccdbqghj.js} +5 -5
  27. package/dist/{sync-hq9dpqpr.js → sync-hkxce5zh.js} +7 -7
  28. package/package.json +1 -1
  29. package/skills/integration/index.json +2 -2
  30. package/skills/moda-cli/SKILL.md +4 -1
  31. package/skills/moda-cli/references/dashboard-parity.md +1 -1
  32. package/skills/moda-cli/references/output-protocol.md +35 -10
  33. package/skills/moda-cli/references/search-and-traces.md +20 -2
  34. package/dist/cli-bb7133m7.js +0 -144
package/dist/cli.js CHANGED
@@ -1,11 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- MODA_INTELLIGENCE_V1_JSON_SCHEMA
4
- } from "./cli-bb7133m7.js";
3
+ BATCH_CONCURRENCY,
4
+ BATCH_MAX_TARGETS,
5
+ MODA_INTELLIGENCE_V1_JSON_SCHEMA,
6
+ asNumber,
7
+ asRecord,
8
+ asString,
9
+ collectBatchTargets,
10
+ compactProblemDossier,
11
+ contextTarget,
12
+ embeddedWindowSize,
13
+ frustrationsFromEmotions,
14
+ mapWithConcurrency,
15
+ parseBatchTarget,
16
+ trimToolFailureConversations,
17
+ withFrustrationAnchors,
18
+ withToolFailureAnchors,
19
+ withoutDetectionWindows
20
+ } from "./cli-vdkwkkp5.js";
5
21
  import {
6
22
  createCommandContext,
7
- parseBooleanFlagValue
8
- } from "./cli-qvg26twp.js";
23
+ envOptOutSet,
24
+ envSwitchedOff,
25
+ optOutFlagSet,
26
+ parseBooleanFlagValue,
27
+ withoutFlag
28
+ } from "./cli-v8q7sd7g.js";
9
29
  import {
10
30
  AuditSchema,
11
31
  COMMAND_FLAG_SPECS,
@@ -29,6 +49,7 @@ import {
29
49
  assertPositionals,
30
50
  buildFeedbackRequest,
31
51
  closestMatches,
52
+ describeField,
32
53
  describeSpecFlags,
33
54
  flagSpecFor,
34
55
  isFeedbackRefFlag,
@@ -43,17 +64,17 @@ import {
43
64
  specFlagKeys,
44
65
  specFlagNames,
45
66
  subcommandHelpFlags,
46
- unknownSignalsSubcommandError
47
- } from "./cli-dn91fb56.js";
67
+ unknownSignalsSubcommandError,
68
+ usageLine,
69
+ writeDryRun
70
+ } from "./cli-wzds257x.js";
48
71
  import {
49
72
  ApiError,
50
73
  callControlAPI,
51
74
  callDataAPI
52
- } from "./cli-9btx12wp.js";
75
+ } from "./cli-pg5qfmqc.js";
53
76
  import {
54
77
  AGENT_ERROR_CODES,
55
- BATCH_CONCURRENCY,
56
- BATCH_MAX_TARGETS,
57
78
  CliAccessError,
58
79
  CliAuthError,
59
80
  CliCancellationError,
@@ -73,19 +94,11 @@ import {
73
94
  agentMaxOutputBytes,
74
95
  apiErrorStatus,
75
96
  apiKeyHeaders,
76
- asNumber,
77
- asRecord,
78
- asString,
79
97
  attachInputDetails,
80
98
  canonicalCommandText,
81
- collectBatchTargets,
82
- compactProblemDossier,
83
- contextTarget,
84
99
  createCliOutput,
85
100
  embeddedTenantIdFromApiKey,
86
- embeddedWindowSize,
87
101
  errorToAgentError,
88
- frustrationsFromEmotions,
89
102
  getApiRequestCount,
90
103
  getModaDir,
91
104
  inputDetailsOf,
@@ -93,24 +106,20 @@ import {
93
106
  isNetworkLikeError,
94
107
  isPlatformApiKey,
95
108
  loadProfileConfig,
96
- mapWithConcurrency,
97
- parseBatchTarget,
98
109
  resetApiRequestCount,
99
110
  resolveApiKey,
100
111
  resolveApiTenantId,
101
112
  resolveModaBaseUrl,
113
+ resolveModaBaseUrlStrict,
102
114
  resolveOutputMode,
103
115
  safeWriteStderr,
104
116
  safeWriteStdout,
105
117
  setAgentUpdateNotice,
106
118
  setPlatformTenantOverride,
107
- trimToolFailureConversations,
119
+ shellArg,
108
120
  validateConfig,
109
- wasOutputCancelled,
110
- withFrustrationAnchors,
111
- withToolFailureAnchors,
112
- withoutDetectionWindows
113
- } from "./cli-dve50x7p.js";
121
+ wasOutputCancelled
122
+ } from "./cli-r5pp7qny.js";
114
123
  import {
115
124
  __require
116
125
  } from "./cli-0v6na3yp.js";
@@ -299,16 +308,91 @@ async function runReplaysCommand(context) {
299
308
  }
300
309
 
301
310
  // src/telemetry.ts
311
+ import { spawn as spawn2 } from "node:child_process";
312
+
313
+ // src/detached.ts
302
314
  import { spawn } from "node:child_process";
315
+ var INSPECTOR_VALUE_OPTIONS = new Set(["--inspect-port", "--debug-port", "--inspect-publish-uid"]);
316
+ function nodeOptionTokens(nodeOptions) {
317
+ const tokens = [];
318
+ let start = -1;
319
+ let quoted = false;
320
+ for (let i = 0;i < nodeOptions.length; i += 1) {
321
+ const ch = nodeOptions[i];
322
+ if (start === -1) {
323
+ if (/\s/.test(ch))
324
+ continue;
325
+ start = i;
326
+ }
327
+ if (quoted && ch === "\\") {
328
+ i += 1;
329
+ } else if (ch === '"') {
330
+ quoted = !quoted;
331
+ } else if (!quoted && /\s/.test(ch)) {
332
+ tokens.push(nodeOptions.slice(start, i));
333
+ start = -1;
334
+ }
335
+ }
336
+ if (start !== -1)
337
+ tokens.push(nodeOptions.slice(start));
338
+ return tokens;
339
+ }
340
+ function withoutInspectorOptions(nodeOptions) {
341
+ const tokens = nodeOptionTokens(nodeOptions);
342
+ const kept = [];
343
+ for (let i = 0;i < tokens.length; i += 1) {
344
+ const name = tokens[i].split("=")[0];
345
+ if (!name.startsWith("--inspect") && name !== "--debug-port") {
346
+ kept.push(tokens[i]);
347
+ continue;
348
+ }
349
+ if (INSPECTOR_VALUE_OPTIONS.has(tokens[i]) && tokens[i + 1] && !tokens[i + 1].startsWith("-"))
350
+ i += 1;
351
+ }
352
+ return kept.join(" ");
353
+ }
354
+ function spawnDetachedNode(script, options = {}, spawnImpl = spawn) {
355
+ try {
356
+ const env = { ...process.env, ...options.env };
357
+ for (const key of options.dropEnv ?? [])
358
+ delete env[key];
359
+ if (env.NODE_OPTIONS?.includes("--inspect")) {
360
+ const kept = withoutInspectorOptions(env.NODE_OPTIONS);
361
+ if (kept)
362
+ env.NODE_OPTIONS = kept;
363
+ else
364
+ delete env.NODE_OPTIONS;
365
+ }
366
+ const child = spawnImpl(process.execPath, ["-e", script], {
367
+ detached: true,
368
+ stdio: [options.stdin === undefined ? "ignore" : "pipe", "ignore", "ignore"],
369
+ windowsHide: true,
370
+ env
371
+ });
372
+ child.on("error", () => {});
373
+ if (child.pid === undefined)
374
+ return false;
375
+ if (options.stdin !== undefined && child.stdin) {
376
+ child.stdin.on("error", () => {});
377
+ child.stdin.end(options.stdin);
378
+ }
379
+ child.unref();
380
+ return true;
381
+ } catch {
382
+ return false;
383
+ }
384
+ }
385
+
386
+ // src/telemetry.ts
303
387
  import { randomUUID } from "node:crypto";
304
388
  var INLINE_TELEMETRY_TIMEOUT_MS = 500;
305
389
  var DETACHED_TELEMETRY_TIMEOUT_MS = 5000;
306
390
  var INVOCATION_ID = randomUUID();
307
391
  var TELEMETRY_FLAG = "no-telemetry";
308
- var TELEMETRY_REQUEST_ENV = "MODA_CLI_TELEMETRY_REQUEST";
309
392
  var TELEMETRY_TRANSPORT_ENV = "MODA_CLI_TELEMETRY_TRANSPORT";
310
393
  var MAX_TELEMETRY_DURATION_MS = 86400000;
311
394
  var MAX_TELEMETRY_API_REQUEST_COUNT = 1e6;
395
+ var MAX_UNKNOWN_FLAG_COUNT = 1000;
312
396
  var SEARCH_SURFACE_BY_COMMAND = {
313
397
  search: "message",
314
398
  conversations: "conversation",
@@ -410,11 +494,10 @@ function serializeAdoption(adoption) {
410
494
  return out;
411
495
  }
412
496
  function stripTelemetryFlag(flags) {
413
- const { [TELEMETRY_FLAG]: _drop, ...rest } = flags;
414
- return rest;
497
+ return withoutFlag(flags, TELEMETRY_FLAG);
415
498
  }
416
- function isTelemetryDisabled(flags) {
417
- return flags[TELEMETRY_FLAG] === "true" || process.env.MODA_CLI_TELEMETRY === "0" || process.env.DO_NOT_TRACK === "1";
499
+ function isTelemetryDisabled(flags, env = process.env) {
500
+ return optOutFlagSet(flags, TELEMETRY_FLAG) || envSwitchedOff(env.MODA_CLI_TELEMETRY) || envOptOutSet(env.DO_NOT_TRACK);
418
501
  }
419
502
  var VALIDATION_REASON_MAX = 128;
420
503
  var DEGRADE_REASON_MAX = 64;
@@ -425,88 +508,78 @@ function sanitizeReasonCode(reason, max) {
425
508
  function sanitizeValidationReason(reason) {
426
509
  return sanitizeReasonCode(reason, VALIDATION_REASON_MAX);
427
510
  }
428
- function sanitizedFlagNames(flags) {
429
- return Object.keys(stripTelemetryFlag(flags)).sort();
511
+ var MAX_FLAG_NAMES = 50;
512
+ var FLAG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
513
+ var SECRET_PATTERNS = [
514
+ /moda_sk_/i,
515
+ /(?:^|[^A-Za-z0-9])sk-[A-Za-z0-9_-]{8,}/,
516
+ /(?:^|[^A-Za-z0-9])(?:sk|pk|rk)_(?:live|test)_/i,
517
+ /gh[pousr]_[A-Za-z0-9]{16,}/,
518
+ /github_pat_/i,
519
+ /xox[abprs]-/i,
520
+ /AKIA[0-9A-Z]{16}/,
521
+ /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/,
522
+ /-----BEGIN [A-Z ]*PRIVATE KEY/,
523
+ /[^\s@]+@[^\s@]+\.[A-Za-z]{2,}/,
524
+ /[A-Za-z0-9+/=]{32,}/
525
+ ];
526
+ function looksSecret(value) {
527
+ return SECRET_PATTERNS.some((pattern) => pattern.test(value));
528
+ }
529
+ function reportedFlagNames(flags, acceptedFlags = new Set) {
530
+ const names = new Set;
531
+ let unknownCount = 0;
532
+ for (const key of Object.keys(stripTelemetryFlag(flags))) {
533
+ if (acceptedFlags.has(key.replace(/_/g, "-")) && FLAG_NAME_RE.test(key) && !looksSecret(key))
534
+ names.add(key);
535
+ else
536
+ unknownCount += 1;
537
+ }
538
+ const sorted = [...names].sort();
539
+ return { names: sorted.slice(0, MAX_FLAG_NAMES), unknownCount: unknownCount + Math.max(0, sorted.length - MAX_FLAG_NAMES) };
540
+ }
541
+ function reportedValidationReason(reason, knownFlags = new Set) {
542
+ let code = reason;
543
+ const unknownFlag = code ? /^unknown_flag:(.*)$/s.exec(code) : null;
544
+ if (unknownFlag) {
545
+ const name = unknownFlag[1].replace(/_/g, "-");
546
+ code = knownFlags.has(name) && FLAG_NAME_RE.test(name) ? `unknown_flag:${name}` : "unknown_flag";
547
+ }
548
+ const cleaned = sanitizeValidationReason(code);
549
+ if (!cleaned || !looksSecret(cleaned))
550
+ return cleaned;
551
+ const head = cleaned.split(":")[0];
552
+ return head && !looksSecret(head) ? head : undefined;
553
+ }
554
+ function safeCode(value) {
555
+ return value && !looksSecret(value) ? value : undefined;
430
556
  }
431
557
  var TELEMETRY_SENDER_SCRIPT = [
432
558
  "try {",
433
- ` const raw = process.env.${TELEMETRY_REQUEST_ENV};`,
434
- ` delete process.env.${TELEMETRY_REQUEST_ENV};`,
435
- " const r = raw ? JSON.parse(raw) : null;",
436
- " if (r && typeof r.url === 'string' && typeof fetch === 'function') {",
437
- " const c = new AbortController();",
438
- ` const t = setTimeout(() => c.abort(), ${DETACHED_TELEMETRY_TIMEOUT_MS});`,
439
- " fetch(r.url, { method: 'POST', headers: r.headers, body: r.body, signal: c.signal })",
440
- " .then((res) => (res.body && res.body.cancel ? res.body.cancel() : undefined))",
441
- " .catch(() => {})",
442
- " .finally(() => clearTimeout(t));",
443
- " }",
559
+ " let raw = '';",
560
+ ` const quit = setTimeout(() => process.exit(0), ${DETACHED_TELEMETRY_TIMEOUT_MS + 1000});`,
561
+ " const done = () => clearTimeout(quit);",
562
+ " process.stdin.setEncoding('utf8');",
563
+ " process.stdin.on('error', () => {});",
564
+ " process.stdin.on('data', (chunk) => { raw += chunk; });",
565
+ " process.stdin.on('end', () => {",
566
+ " try {",
567
+ " const r = raw ? JSON.parse(raw) : null;",
568
+ " raw = '';",
569
+ " if (!r || typeof r.url !== 'string' || typeof fetch !== 'function') return done();",
570
+ " const c = new AbortController();",
571
+ ` const t = setTimeout(() => c.abort(), ${DETACHED_TELEMETRY_TIMEOUT_MS});`,
572
+ " fetch(r.url, { method: 'POST', headers: r.headers, body: r.body, signal: c.signal })",
573
+ " .then((res) => (res.body && res.body.cancel ? res.body.cancel() : undefined))",
574
+ " .catch(() => {})",
575
+ " .finally(() => { clearTimeout(t); done(); });",
576
+ " } catch { done(); }",
577
+ " });",
444
578
  "} catch {}"
445
579
  ].join(`
446
580
  `);
447
- var INSPECTOR_VALUE_OPTIONS = new Set(["--inspect-port", "--debug-port", "--inspect-publish-uid"]);
448
- function nodeOptionTokens(nodeOptions) {
449
- const tokens = [];
450
- let start = -1;
451
- let quoted = false;
452
- for (let i = 0;i < nodeOptions.length; i += 1) {
453
- const ch = nodeOptions[i];
454
- if (start === -1) {
455
- if (/\s/.test(ch))
456
- continue;
457
- start = i;
458
- }
459
- if (quoted && ch === "\\") {
460
- i += 1;
461
- } else if (ch === '"') {
462
- quoted = !quoted;
463
- } else if (!quoted && /\s/.test(ch)) {
464
- tokens.push(nodeOptions.slice(start, i));
465
- start = -1;
466
- }
467
- }
468
- if (start !== -1)
469
- tokens.push(nodeOptions.slice(start));
470
- return tokens;
471
- }
472
- function withoutInspectorOptions(nodeOptions) {
473
- const tokens = nodeOptionTokens(nodeOptions);
474
- const kept = [];
475
- for (let i = 0;i < tokens.length; i += 1) {
476
- const name = tokens[i].split("=")[0];
477
- if (!name.startsWith("--inspect") && name !== "--debug-port") {
478
- kept.push(tokens[i]);
479
- continue;
480
- }
481
- if (INSPECTOR_VALUE_OPTIONS.has(tokens[i]) && tokens[i + 1] && !tokens[i + 1].startsWith("-"))
482
- i += 1;
483
- }
484
- return kept.join(" ");
485
- }
486
- function spawnDetachedTelemetrySender(request, spawnImpl = spawn) {
487
- try {
488
- const env = { ...process.env, [TELEMETRY_REQUEST_ENV]: JSON.stringify(request) };
489
- if (env.NODE_OPTIONS?.includes("--inspect")) {
490
- const kept = withoutInspectorOptions(env.NODE_OPTIONS);
491
- if (kept)
492
- env.NODE_OPTIONS = kept;
493
- else
494
- delete env.NODE_OPTIONS;
495
- }
496
- const child = spawnImpl(process.execPath, ["-e", TELEMETRY_SENDER_SCRIPT], {
497
- detached: true,
498
- stdio: "ignore",
499
- windowsHide: true,
500
- env
501
- });
502
- child.on("error", () => {});
503
- if (child.pid === undefined)
504
- return false;
505
- child.unref();
506
- return true;
507
- } catch {
508
- return false;
509
- }
581
+ function spawnDetachedTelemetrySender(request, spawnImpl = spawn2) {
582
+ return spawnDetachedNode(TELEMETRY_SENDER_SCRIPT, { stdin: JSON.stringify(request), dropEnv: ["MODA_API_KEY"] }, spawnImpl);
510
583
  }
511
584
  async function sendInline(request) {
512
585
  const controller = new AbortController;
@@ -526,16 +599,25 @@ async function sendInline(request) {
526
599
  }
527
600
  }
528
601
  function buildCliUsageRequest(input) {
529
- const validationReason = sanitizeValidationReason(input.validationReason);
602
+ const baseUrl = resolveModaBaseUrlStrict(input.profile ? { profile: input.profile } : {});
603
+ if (!baseUrl)
604
+ return;
605
+ const validationReason = reportedValidationReason(input.validationReason, input.knownFlags);
606
+ const flagNames = reportedFlagNames(input.flags, input.acceptedFlags);
607
+ const adoption = serializeAdoption(input.adoption);
608
+ for (const key of ["subcommand", "degrade_reason", "skill_version"]) {
609
+ if (typeof adoption[key] === "string" && looksSecret(adoption[key]))
610
+ delete adoption[key];
611
+ }
530
612
  return {
531
- url: `${resolveModaBaseUrl({ profile: input.profile })}/api/v1/data/cli-usage`,
613
+ url: `${baseUrl}/api/v1/data/cli-usage`,
532
614
  headers: {
533
615
  ...apiKeyHeaders(input.apiKey),
534
616
  "Content-Type": "application/json"
535
617
  },
536
618
  body: JSON.stringify({
537
619
  invocation_id: INVOCATION_ID,
538
- command: input.command.slice(0, 64),
620
+ command: safeCode(input.command.slice(0, 64)) ?? "unknown",
539
621
  status: input.status,
540
622
  error_type: input.errorType,
541
623
  ...validationReason ? { validation_reason: validationReason } : {},
@@ -547,10 +629,11 @@ function buildCliUsageRequest(input) {
547
629
  platform: process.platform,
548
630
  arch: process.arch,
549
631
  flags_json: {
550
- names: sanitizedFlagNames(input.flags),
551
- ...((alias) => alias ? { legacy_alias: alias } : {})(sanitizeReasonCode(input.legacyAlias?.replace(/ /g, ":"), 64))
632
+ names: flagNames.names,
633
+ ...flagNames.unknownCount > 0 ? { unknown_count: Math.min(flagNames.unknownCount, MAX_UNKNOWN_FLAG_COUNT) } : {},
634
+ ...((alias) => alias ? { legacy_alias: alias } : {})(safeCode(sanitizeReasonCode(input.legacyAlias?.replace(/ /g, ":"), 64)))
552
635
  },
553
- ...serializeAdoption(input.adoption)
636
+ ...adoption
554
637
  })
555
638
  };
556
639
  }
@@ -560,6 +643,8 @@ async function sendCliUsageTelemetry(input) {
560
643
  return;
561
644
  }
562
645
  const request = buildCliUsageRequest({ ...input, apiKey: input.apiKey });
646
+ if (!request)
647
+ return;
563
648
  if (process.env[TELEMETRY_TRANSPORT_ENV] !== "inline" && spawnDetachedTelemetrySender(request)) {
564
649
  return;
565
650
  }
@@ -576,11 +661,10 @@ var REGISTRY_TIMEOUT_MS = 1500;
576
661
  var REGISTRY_LATEST_URL = "https://registry.npmjs.org/@moda-ai/cli/latest";
577
662
  var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
578
663
  function stripUpdateCheckFlag(flags) {
579
- const { [UPDATE_CHECK_FLAG]: _drop, ...rest } = flags;
580
- return rest;
664
+ return withoutFlag(flags, UPDATE_CHECK_FLAG);
581
665
  }
582
666
  function isUpdateCheckDisabled(flags, env = process.env) {
583
- return flags[UPDATE_CHECK_FLAG] === "true" || env.MODA_CLI_UPDATE_CHECK === "0" || env.DO_NOT_TRACK === "1" || env.CI !== undefined && env.CI !== "" && env.CI !== "false" && env.CI !== "0";
667
+ return optOutFlagSet(flags, UPDATE_CHECK_FLAG) || envSwitchedOff(env.MODA_CLI_UPDATE_CHECK) || envOptOutSet(env.DO_NOT_TRACK) || envOptOutSet(env.CI);
584
668
  }
585
669
  function cachePath(options = {}) {
586
670
  return join(getModaDir(options), "update-check.json");
@@ -613,9 +697,39 @@ function isNewerVersion(candidate, current) {
613
697
  }
614
698
  return false;
615
699
  }
616
- async function startUpdateCheck(options = {}, now = Date.now()) {
700
+ var UPDATE_CHECK_URL_ENV = "MODA_CLI_UPDATE_CHECK_URL";
701
+ var UPDATE_CHECK_CACHE_ENV = "MODA_CLI_UPDATE_CHECK_CACHE";
702
+ var DETACHED_REGISTRY_TIMEOUT_MS = 1e4;
703
+ var UPDATE_CHECK_SCRIPT = [
704
+ "try {",
705
+ ` const url = process.env.${UPDATE_CHECK_URL_ENV};`,
706
+ ` const path = process.env.${UPDATE_CHECK_CACHE_ENV};`,
707
+ ` const semver = new RegExp(${JSON.stringify(SEMVER_RE.source)});`,
708
+ " if (url && path && typeof fetch === 'function') {",
709
+ " const c = new AbortController();",
710
+ ` const t = setTimeout(() => c.abort(), ${DETACHED_REGISTRY_TIMEOUT_MS});`,
711
+ " fetch(url, { headers: { Accept: 'application/json' }, signal: c.signal })",
712
+ " .then((res) => (res.ok ? res.json() : null))",
713
+ " .then((body) => {",
714
+ " if (body && typeof body.version === 'string' && semver.test(body.version)) {",
715
+ " require('fs').writeFileSync(path, JSON.stringify({ checked_at: Date.now(), latest: body.version }, null, 2) + '\\n', 'utf8');",
716
+ " }",
717
+ " })",
718
+ " .catch(() => {})",
719
+ " .finally(() => clearTimeout(t));",
720
+ " }",
721
+ "} catch {}"
722
+ ].join(`
723
+ `);
724
+ async function startUpdateCheck(options = {}, now = Date.now(), mode = {}) {
617
725
  const cache = readCache(options);
618
- if (cache && now - cache.checked_at < CHECK_INTERVAL_MS) {
726
+ if (cache && cache.checked_at <= now && now - cache.checked_at < CHECK_INTERVAL_MS) {
727
+ return;
728
+ }
729
+ const known = cache && SEMVER_RE.test(cache.latest) ? cache.latest : "";
730
+ writeCache({ checked_at: now, latest: known }, options);
731
+ if (mode.detached) {
732
+ spawnDetachedNode(UPDATE_CHECK_SCRIPT, { env: { [UPDATE_CHECK_URL_ENV]: REGISTRY_LATEST_URL, [UPDATE_CHECK_CACHE_ENV]: cachePath(options) }, dropEnv: ["MODA_API_KEY"] }, mode.spawnImpl);
619
733
  return;
620
734
  }
621
735
  const controller = new AbortController;
@@ -640,16 +754,16 @@ async function startUpdateCheck(options = {}, now = Date.now()) {
640
754
  }
641
755
  function getAvailableUpdate(options = {}) {
642
756
  const cache = readCache(options);
643
- return cache && isNewerVersion(cache.latest, VERSION) ? cache.latest : undefined;
757
+ return cache && SEMVER_RE.test(cache.latest) && isNewerVersion(cache.latest, VERSION) ? cache.latest : undefined;
644
758
  }
645
759
  function getUpdateNotice(options = {}) {
646
- const cache = readCache(options);
647
- if (!cache || !isNewerVersion(cache.latest, VERSION)) {
760
+ const latest = getAvailableUpdate(options);
761
+ if (!latest) {
648
762
  return;
649
763
  }
650
764
  return [
651
765
  "",
652
- `Update available: moda-cli ${VERSION} → ${cache.latest}`,
766
+ `Update available: moda-cli ${VERSION} → ${latest}`,
653
767
  "Run `npm install -g @moda-ai/cli@latest` to update."
654
768
  ].join(`
655
769
  `);
@@ -2249,6 +2363,7 @@ function buildManifest(commands) {
2249
2363
  "--agent",
2250
2364
  "--stream",
2251
2365
  "--json",
2366
+ "--pretty",
2252
2367
  "--human",
2253
2368
  "--profile <name>",
2254
2369
  "--quiet",
@@ -2275,7 +2390,7 @@ function buildManifest(commands) {
2275
2390
  exit_codes: [
2276
2391
  { code: 0, name: "ok", meaning: "Command completed successfully." },
2277
2392
  { code: 1, name: "error", meaning: "Command, input, API, network, or unexpected failure." },
2278
- { code: 3, name: "degraded", meaning: "Degraded result: `moda ask` local fallback." },
2393
+ { code: 3, name: "degraded", meaning: "Degraded result: `moda ask` local fallback, or `investigate`/`failures` with some sources unavailable (`data.failedSources`)." },
2279
2394
  { code: 4, name: "auth_required", meaning: "Run login_command, then resume." },
2280
2395
  { code: 5, name: "input_required", meaning: "Choose from input_request choices, then resume." },
2281
2396
  { code: 130, name: "cancelled", meaning: "Interrupted or output consumer closed." }
@@ -2390,6 +2505,8 @@ function event(name, when, terminal, deprecated = false) {
2390
2505
  function exitCodesForCommand(command) {
2391
2506
  if (command === "ask")
2392
2507
  return [0, 1, 3, 4, 5, 130];
2508
+ if (command === "investigate" || command === "failures")
2509
+ return [0, 1, 3, 4, 130];
2393
2510
  if (command === "init" || command === "harness")
2394
2511
  return [0, 1, 4, 5, 130];
2395
2512
  return [0, 1, 4, 130];
@@ -2522,46 +2639,46 @@ function leafManifestFields(leaf, commands) {
2522
2639
  import { fileURLToPath } from "url";
2523
2640
  import { realpathSync } from "fs";
2524
2641
  async function runPromptsCommand(...args) {
2525
- return (await import("./prompts-3h5jcjrw.js")).runPromptsCommand(...args);
2642
+ return (await import("./prompts-17wyrst6.js")).runPromptsCommand(...args);
2526
2643
  }
2527
2644
  async function runSkillsCommand(...args) {
2528
- return (await import("./skills-vq3njc8a.js")).runSkillsCommand(...args);
2645
+ return (await import("./skills-ccdbqghj.js")).runSkillsCommand(...args);
2529
2646
  }
2530
2647
  async function runFixCommand(...args) {
2531
- return (await import("./fix-0qdtdz5h.js")).runFixCommand(...args);
2648
+ return (await import("./fix-75s9s0d6.js")).runFixCommand(...args);
2532
2649
  }
2533
2650
  async function runFixesCommand(...args) {
2534
- return (await import("./fix-0qdtdz5h.js")).runFixesCommand(...args);
2651
+ return (await import("./fix-75s9s0d6.js")).runFixesCommand(...args);
2535
2652
  }
2536
2653
  async function runHarnessCommand(...args) {
2537
- return (await import("./harness-vzy7n7ea.js")).runHarnessCommand(...args);
2654
+ return (await import("./harness-a7tb9a23.js")).runHarnessCommand(...args);
2538
2655
  }
2539
2656
  async function runSyncCommand(...args) {
2540
- return (await import("./sync-hq9dpqpr.js")).runSyncCommand(...args);
2657
+ return (await import("./sync-hkxce5zh.js")).runSyncCommand(...args);
2541
2658
  }
2542
2659
  async function runRegistryCommand(...args) {
2543
- return (await import("./registry-s2vdsfw8.js")).runRegistryCommand(...args);
2660
+ return (await import("./registry-fpzhjznj.js")).runRegistryCommand(...args);
2544
2661
  }
2545
2662
  async function runDoctorCommand(...args) {
2546
- return (await import("./doctor-q21mr5ya.js")).runDoctorCommand(...args);
2663
+ return (await import("./doctor-wmre3mr7.js")).runDoctorCommand(...args);
2547
2664
  }
2548
2665
  async function runStatusCommand(...args) {
2549
- return (await import("./doctor-q21mr5ya.js")).runStatusCommand(...args);
2666
+ return (await import("./doctor-wmre3mr7.js")).runStatusCommand(...args);
2550
2667
  }
2551
2668
  async function runAuthCommand(...args) {
2552
- return (await import("./profile-commands-6e2dgv72.js")).runAuthCommand(...args);
2669
+ return (await import("./profile-commands-49yn02yg.js")).runAuthCommand(...args);
2553
2670
  }
2554
2671
  async function runConfigCommand(...args) {
2555
- return (await import("./profile-commands-6e2dgv72.js")).runConfigCommand(...args);
2672
+ return (await import("./profile-commands-49yn02yg.js")).runConfigCommand(...args);
2556
2673
  }
2557
2674
  async function runProfilesCommand(...args) {
2558
- return (await import("./profile-commands-6e2dgv72.js")).runProfilesCommand(...args);
2675
+ return (await import("./profile-commands-49yn02yg.js")).runProfilesCommand(...args);
2559
2676
  }
2560
2677
  async function runAgentCommand(...args) {
2561
- return (await import("./agent-t7dxmpdq.js")).runAgentCommand(...args);
2678
+ return (await import("./agent-vp9prdma.js")).runAgentCommand(...args);
2562
2679
  }
2563
2680
  async function runProductionCommand(...args) {
2564
- return (await import("./production-intelligence-c8e0xw3n.js")).runProductionCommand(...args);
2681
+ return (await import("./production-intelligence-7nqddd4f.js")).runProductionCommand(...args);
2565
2682
  }
2566
2683
  function parseArgs(argv) {
2567
2684
  const args = argv.slice(2);
@@ -2575,10 +2692,10 @@ function parseArgs(argv) {
2575
2692
  let endOfOptions = false;
2576
2693
  while (i < args.length) {
2577
2694
  const arg = args[i];
2578
- if (!endOfOptions && arg === "--" && command !== "") {
2695
+ if (!endOfOptions && arg === "--" && command === "") {} else if (!endOfOptions && arg === "--") {
2579
2696
  endOfOptions = true;
2580
2697
  } else if (!endOfOptions && arg.startsWith("--")) {
2581
- i = parseFlag(args, i, flags, command === "", bareFlags, parseCommandFor(command, positional));
2698
+ i = parseFlag(args, i, flags, command === "", bareFlags, command, positional);
2582
2699
  } else if (!endOfOptions && command !== "" && /^-[A-Za-z]/.test(arg)) {
2583
2700
  shortFlags.push(arg);
2584
2701
  } else if (command === "") {
@@ -2655,7 +2772,8 @@ var RAW_BOOLEAN_FLAGS = new Set([
2655
2772
  "tui",
2656
2773
  "no-tui",
2657
2774
  "no-telemetry",
2658
- "no-update-check"
2775
+ "no-update-check",
2776
+ "pretty"
2659
2777
  ]);
2660
2778
  var GLOBAL_BOOLEAN_FLAGS = new Set([
2661
2779
  "agent",
@@ -2671,7 +2789,8 @@ var GLOBAL_BOOLEAN_FLAGS = new Set([
2671
2789
  "dry-run",
2672
2790
  "stream",
2673
2791
  "tui",
2674
- "no-tui"
2792
+ "no-tui",
2793
+ "pretty"
2675
2794
  ]);
2676
2795
  var BOOLEAN_WORDS = new Set(["true", "false", "yes", "no", "on", "off"]);
2677
2796
  var META_SWITCHES = new Set([
@@ -2685,14 +2804,64 @@ var META_SWITCHES = new Set([
2685
2804
  "tui",
2686
2805
  "no-tui",
2687
2806
  "no-telemetry",
2688
- "no-update-check"
2807
+ "no-update-check",
2808
+ "pretty"
2689
2809
  ]);
2690
2810
  var AGENT_VALUE_COMMANDS = new Set(["harness", "sync"]);
2691
- var COMMAND_BOOLEAN_FLAGS = {
2692
- context: new Set(["all", "metadata-only"])
2693
- };
2694
- function parseFlag(args, index, flags, beforeCommand, bareFlags, command = "") {
2695
- const set = (key2, value, bare) => {
2811
+ function canonicalFlagKey(key) {
2812
+ return key.replace(/_/g, "-");
2813
+ }
2814
+ function canonicalizeFlagKeys(flags, bareFlags) {
2815
+ const entries = Object.entries(flags);
2816
+ for (const [key] of entries)
2817
+ delete flags[key];
2818
+ for (const [key, value] of entries)
2819
+ flags[canonicalFlagKey(key)] = value;
2820
+ if (bareFlags) {
2821
+ const canonical = [...new Set(bareFlags.map(canonicalFlagKey))];
2822
+ bareFlags.splice(0, bareFlags.length, ...canonical);
2823
+ }
2824
+ }
2825
+ function parseHandlersFor(command, verb) {
2826
+ if (command === "review")
2827
+ return leafHandlers(leafFor("review", undefined));
2828
+ if (CANONICAL_NOUNS.includes(command) && verb !== undefined) {
2829
+ const leaf = leafFor(command, verb);
2830
+ if (leaf)
2831
+ return leaf.path === "problems unexplained" ? ["problems"] : leafHandlers(leaf);
2832
+ }
2833
+ return [commandRegistry.find(command)?.name ?? command];
2834
+ }
2835
+ function specsOf(name) {
2836
+ const spec = flagSpecFor(name);
2837
+ return spec ? [spec, ...Object.values(spec.subcommands ?? {})] : [];
2838
+ }
2839
+ var switchFlagCache = new Map;
2840
+ function commandSwitchFlags(command, verb) {
2841
+ const handlers = parseHandlersFor(command, verb);
2842
+ const cacheKey = handlers.join(",");
2843
+ const cached = switchFlagCache.get(cacheKey);
2844
+ if (cached)
2845
+ return cached;
2846
+ const booleans = new Set;
2847
+ const valued = new Set;
2848
+ for (const handler of handlers) {
2849
+ for (const spec of specsOf(handler)) {
2850
+ for (const flag of describeSpecFlags(spec)) {
2851
+ (flag.type === "boolean" ? booleans : valued).add(flag.name.slice(2));
2852
+ }
2853
+ }
2854
+ }
2855
+ const switches = new Set([...booleans].filter((name) => !valued.has(name)));
2856
+ switchFlagCache.set(cacheKey, switches);
2857
+ return switches;
2858
+ }
2859
+ function takesIdPositional(command, verb) {
2860
+ return parseHandlersFor(command, verb).some((handler) => specsOf(handler).some((spec) => (spec.positionals ?? []).some((positional) => !positional.variadic)));
2861
+ }
2862
+ function parseFlag(args, index, flags, beforeCommand, bareFlags, command = "", verb) {
2863
+ const set = (rawKey, value, bare) => {
2864
+ const key2 = canonicalFlagKey(rawKey);
2696
2865
  flags[key2] = value;
2697
2866
  const at = bareFlags.indexOf(key2);
2698
2867
  if (at !== -1)
@@ -2708,17 +2877,19 @@ function parseFlag(args, index, flags, beforeCommand, bareFlags, command = "") {
2708
2877
  }
2709
2878
  const key = arg.slice(2);
2710
2879
  const next = args[index + 1];
2711
- if (beforeCommand && GLOBAL_BOOLEAN_FLAGS.has(key)) {
2880
+ if (beforeCommand && GLOBAL_BOOLEAN_FLAGS.has(canonicalFlagKey(key))) {
2712
2881
  set(key, "true", true);
2713
2882
  return index;
2714
2883
  }
2715
- const switchName = key.replace(/_/g, "-");
2716
- if (META_SWITCHES.has(switchName) || switchName === "agent" && !AGENT_VALUE_COMMANDS.has(command)) {
2884
+ const switchName = canonicalFlagKey(key);
2885
+ const parseCommand = parseCommandFor(command, verb);
2886
+ if (META_SWITCHES.has(switchName) || switchName === "agent" && !AGENT_VALUE_COMMANDS.has(parseCommand)) {
2717
2887
  set(key, "true", true);
2718
2888
  return index;
2719
2889
  }
2720
- const idFollows = COMMAND_BOOLEAN_FLAGS[command]?.has(switchName) === true;
2721
- const isSwitch = RAW_BOOLEAN_FLAGS.has(switchName) || idFollows;
2890
+ const schemaSwitch = !beforeCommand && commandSwitchFlags(command, verb).has(switchName);
2891
+ const isSwitch = RAW_BOOLEAN_FLAGS.has(switchName) || schemaSwitch;
2892
+ const idFollows = schemaSwitch && takesIdPositional(command, verb);
2722
2893
  const takesNext = BOOLEAN_WORDS.has(next?.toLowerCase() ?? "") || !idFollows && (next === "0" || next === "1");
2723
2894
  if (!beforeCommand && isSwitch && !takesNext) {
2724
2895
  set(key, "true", true);
@@ -2750,9 +2921,32 @@ function flagsToArgs(flags, positionalKey, positionalValue) {
2750
2921
  result[positionalKey] = positionalValue;
2751
2922
  }
2752
2923
  for (const [key, value] of Object.entries(flags)) {
2753
- const snakeKey = key.replace(/-/g, "_");
2754
- const num = Number(value);
2755
- result[snakeKey] = isNaN(num) || value === "true" ? value : num;
2924
+ result[key.replace(/-/g, "_")] = value;
2925
+ }
2926
+ return result;
2927
+ }
2928
+ var DECIMAL_TEXT = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
2929
+ function coerceNumericFlags(schema, args, command) {
2930
+ const shape = schemaShape(schema);
2931
+ const result = { ...args };
2932
+ for (const [key, value] of Object.entries(args)) {
2933
+ if (typeof value !== "string" || !Object.hasOwn(shape, key))
2934
+ continue;
2935
+ const info = describeField(shape[key]);
2936
+ if (info.type !== "integer" && info.type !== "number")
2937
+ continue;
2938
+ const text = value.trim();
2939
+ if (!DECIMAL_TEXT.test(text)) {
2940
+ const flag = kebabFlag(key);
2941
+ const kind = info.type === "integer" ? "a whole number" : "a number";
2942
+ const range = info.min !== undefined && info.max !== undefined ? ` from ${info.min} to ${info.max}` : "";
2943
+ throw new CliInputError(`${flag} must be ${kind}${range} (got '${value}'), so nothing was sent.`, `Pass digits only, e.g. ${flag}=${info.min ?? 1}. See '${canonicalCommandText(`moda ${command}`)} --help'.`, `bad_number:${flag.slice(2)}`, {
2944
+ field: flag,
2945
+ ...info.min !== undefined || info.max !== undefined ? { range: { ...info.min !== undefined ? { min: info.min } : {}, ...info.max !== undefined ? { max: info.max } : {} } } : {},
2946
+ suggestedCommands: [`${canonicalCommandText(`moda ${command}`)} --help`]
2947
+ });
2948
+ }
2949
+ result[key] = Number(text);
2756
2950
  }
2757
2951
  return result;
2758
2952
  }
@@ -2820,6 +3014,7 @@ Silent aliases: conversations = traces list, cluster-conversations = cluster-tra
2820
3014
 
2821
3015
  Output: piped stdout, CI or an agent env (CLAUDECODE/CURSOR/MODA_AGENT) => agent JSON; a terminal => prose.
2822
3016
  --json | --agent [--stream] | --human Force an output mode (or MODA_FORMAT=human|json|agent|agent-stream)
3017
+ --json is compact when piped; --pretty indents it (indented by default on a terminal)
2823
3018
  --profile=NAME --quiet --no-update-check --version --json-schemas
2824
3019
  Exit codes: 0 ok, 1 error (errors[0].code says which), 3 degraded, 4 auth required, 5 input required, 130 cancelled.
2825
3020
  A command rejects any flag it does not take; the error lists errors[0].allowed and errors[0].did_you_mean.`);
@@ -3082,8 +3277,27 @@ function legacyNamesHelpLine() {
3082
3277
  return lines.join(`
3083
3278
  `);
3084
3279
  }
3085
- async function attachWindows(rows, windowSize) {
3086
- return Promise.all(rows.map(async (row) => {
3280
+ var ENRICH_CONCURRENCY = BATCH_CONCURRENCY;
3281
+ function enrichedRowBytes(row) {
3282
+ const window = asRecord(row.window);
3283
+ if (window && !("error" in window)) {
3284
+ const { conversation: _conversation, ...rest } = row;
3285
+ return jsonByteLength(rest);
3286
+ }
3287
+ return jsonByteLength(row);
3288
+ }
3289
+ async function enrichWithinBudget(rows, enrich, budgetBytes, skipKey) {
3290
+ let spent = 0;
3291
+ const enriched = await mapWithConcurrency(rows, ENRICH_CONCURRENCY, async (row) => {
3292
+ const out = await enrich(row);
3293
+ if (budgetBytes !== undefined)
3294
+ spent += enrichedRowBytes(out);
3295
+ return out;
3296
+ }, budgetBytes === undefined ? {} : { shouldStart: () => spent <= budgetBytes });
3297
+ return [...enriched, ...rows.slice(enriched.length).map((row) => ({ ...row, [skipKey]: "agent_output_budget" }))];
3298
+ }
3299
+ async function attachWindows(rows, windowSize, budgetBytes) {
3300
+ return enrichWithinBudget(rows, async (row) => {
3087
3301
  const anchor = asRecord(row.anchor);
3088
3302
  if (!anchor || anchor.no_anchor === true) {
3089
3303
  return { ...row, window: null };
@@ -3112,7 +3326,7 @@ async function attachWindows(rows, windowSize) {
3112
3326
  const message = err instanceof Error ? err.message : String(err);
3113
3327
  return { ...row, window: { error: message } };
3114
3328
  }
3115
- }));
3329
+ }, budgetBytes, "window_skipped");
3116
3330
  }
3117
3331
  var TOOL_FAILURE_DETAIL_DEFAULT_LIMIT = 5;
3118
3332
  var TOOL_FAILURE_AGENT_SUBTYPES = 20;
@@ -3121,7 +3335,8 @@ async function printWithOptionalWindows(enriched, listKey, includeWindow, window
3121
3335
  let data = enriched;
3122
3336
  if (includeWindow) {
3123
3337
  const list = Array.isArray(enriched[listKey]) ? enriched[listKey] : [];
3124
- data = { ...enriched, [listKey]: await attachWindows(list, windowSize) };
3338
+ const agentMode = output.mode === "agent" || output.mode === "agent-stream";
3339
+ data = { ...enriched, [listKey]: await attachWindows(list, windowSize, agentMode ? agentMaxOutputBytes() : undefined) };
3125
3340
  }
3126
3341
  if (finalize)
3127
3342
  data = finalize(data, allWarnings);
@@ -3179,8 +3394,10 @@ function auditCapStatus(data, limit) {
3179
3394
  const nextCursor = typeof cursorValue === "string" && cursorValue ? cursorValue : null;
3180
3395
  return { rowsCapped, spansCapped, hasMoreKnown: hasMore, rows, spans, reason, nextCursor };
3181
3396
  }
3182
- var AUDIT_MIN_PAGE_BYTES = 32768;
3183
3397
  var AUDIT_AGENT_RESERVE_BYTES = 16384;
3398
+ function auditMaxBytes(budgetBytes) {
3399
+ return Math.max(1, budgetBytes - Math.min(AUDIT_AGENT_RESERVE_BYTES, Math.floor(budgetBytes / 4)));
3400
+ }
3184
3401
  async function fetchAudit(id, query2, maxBytes) {
3185
3402
  const pageQuery = new URLSearchParams(query2);
3186
3403
  if (maxBytes !== undefined)
@@ -3188,16 +3405,22 @@ async function fetchAudit(id, query2, maxBytes) {
3188
3405
  const queryString = pageQuery.toString() ? `?${pageQuery.toString()}` : "";
3189
3406
  return callDataAPI(`/audit/${encodeURIComponent(id)}${queryString}`);
3190
3407
  }
3191
- function auditOutput(command, params, data) {
3192
- const capped = auditCapStatus(data, params.limit ?? AUDIT_DEFAULT_LIMIT);
3408
+ function auditOutput(command, params, data, keep) {
3193
3409
  const auditRecord = asRecord(data);
3194
- if (!capped || !auditRecord) {
3195
- return { data: auditRecord ? { ...auditRecord, truncated: false, has_more: false, next_cursor: null } : data, options: {} };
3196
- }
3410
+ const pageSpans = Array.isArray(auditRecord?.spans) ? auditRecord.spans : [];
3411
+ const offset = params.span_offset ?? 0;
3412
+ const end = Math.max(offset, keep === undefined ? pageSpans.length : Math.min(pageSpans.length, offset + keep));
3413
+ const sliced = offset > 0 || end < pageSpans.length;
3414
+ const shown = Math.max(0, Math.min(end, pageSpans.length) - offset);
3415
+ const partial = end < pageSpans.length;
3416
+ const record = auditRecord && sliced ? {
3417
+ ...auditRecord,
3418
+ spans: pageSpans.slice(offset, end),
3419
+ span_offset: offset,
3420
+ page_spans: pageSpans.length,
3421
+ ...partial ? { next_span_offset: end } : {}
3422
+ } : auditRecord;
3197
3423
  const limit = params.limit ?? AUDIT_DEFAULT_LIMIT;
3198
- const perTrace = "audit one OTLP trace at a time with `moda traces spans <trace_id> --kind=trace` (ids in summary.trace_ids)";
3199
- const warnings = [];
3200
- const nextCommands = [];
3201
3424
  const auditFlags = (extra) => [
3202
3425
  `${canonicalCommandText(`moda ${command}`)} ${shellArg(params.id)}`,
3203
3426
  params.kind && params.kind !== "auto" ? `--kind=${params.kind}` : "",
@@ -3205,29 +3428,75 @@ function auditOutput(command, params, data) {
3205
3428
  params.include_bodies === true ? "--include-bodies" : "",
3206
3429
  ...extra
3207
3430
  ].filter(Boolean).join(" ");
3431
+ const sliceWarnings = [];
3432
+ if (offset > 0 && offset >= pageSpans.length) {
3433
+ sliceWarnings.push(`--span-offset=${offset} is past the ${pageSpans.length} span(s) of this page, so no spans are shown.`);
3434
+ } else if (sliced && !partial) {
3435
+ sliceWarnings.push(`Showing spans ${offset}-${end - 1}, the last part of the ${pageSpans.length} span(s) in this page.`);
3436
+ }
3437
+ if (partial && record) {
3438
+ const continueWith = auditFlags([
3439
+ params.limit ? `--limit=${params.limit}` : "",
3440
+ params.cursor ? `--cursor=${shellArg(params.cursor)}` : "",
3441
+ params.page_bytes ? `--page-bytes=${params.page_bytes}` : "",
3442
+ `--span-offset=${end}`
3443
+ ]);
3444
+ const summary = asRecord(record.summary);
3445
+ return {
3446
+ data: {
3447
+ ...record,
3448
+ truncated: true,
3449
+ has_more: true,
3450
+ next_cursor: null,
3451
+ ...summary ? { summary: { ...summary, has_more: true, next_cursor: null } } : {}
3452
+ },
3453
+ options: {
3454
+ warnings: [
3455
+ `Showing spans ${offset}-${end - 1} of the ${pageSpans.length} in this page to stay under the agent output byte limit (--json has no limit). Read the next ${pageSpans.length - end} with: ${continueWith}. next_cursor is withheld until the last spans of this page are read. Orphan/duplicate analysis covers the whole page.`
3456
+ ],
3457
+ nextCommands: [{
3458
+ command: continueWith,
3459
+ purpose: `Read spans ${end}-${pageSpans.length - 1} of this page (left out to fit the agent byte limit).`,
3460
+ mutability: "read",
3461
+ requires_approval: false
3462
+ }]
3463
+ }
3464
+ };
3465
+ }
3466
+ const capped = auditCapStatus(data, limit);
3467
+ if (!capped || !record) {
3468
+ return {
3469
+ data: record ? { ...record, truncated: false, has_more: false, next_cursor: null } : data,
3470
+ options: sliceWarnings.length > 0 ? { warnings: sliceWarnings } : {}
3471
+ };
3472
+ }
3473
+ const perTrace = "audit one OTLP trace at a time with `moda traces spans <trace_id> --kind=trace` (ids in summary.trace_ids)";
3474
+ const shownText = sliced ? `${shown} shown here (${pageSpans.length} in this page)` : `${capped.spans} shown`;
3475
+ const warnings = [...sliceWarnings];
3476
+ const nextCommands = [];
3208
3477
  if (capped.nextCursor) {
3209
3478
  nextCommands.push({
3210
- command: auditFlags([params.limit ? `--limit=${params.limit}` : "", `--cursor=${capped.nextCursor}`]),
3479
+ command: auditFlags([params.limit ? `--limit=${params.limit}` : "", `--cursor=${shellArg(capped.nextCursor)}`]),
3211
3480
  purpose: "Read the next page of ingest rows of this trace.",
3212
3481
  mutability: "read",
3213
3482
  requires_approval: false
3214
3483
  });
3215
3484
  }
3216
3485
  if (capped.reason === "byte_limit") {
3217
- warnings.push(`Spans were capped: the response reached the server's size budget after ${capped.rows} ingest row(s), so this trace has more spans than the ${capped.spans} shown. Continue with the next_cursor page${params.include_bodies === true ? ", or drop --include-bodies to fit more rows per page" : ""}. Orphan/duplicate analysis covers this page only.`);
3486
+ warnings.push(`Spans were capped: the response reached the server's size budget after ${capped.rows} ingest row(s), so this trace has more spans than the ${shownText}. Continue with the next_cursor page${params.include_bodies === true ? ", or drop --include-bodies to fit more rows per page" : ""}. Orphan/duplicate analysis covers this page only.`);
3218
3487
  } else if (capped.reason === "span_limit" && capped.nextCursor) {
3219
- warnings.push(`Spans were capped: the page reached the server's per-page span cap after ${capped.rows} ingest row(s), so this trace has more spans than the ${capped.spans} shown. Continue with the next_cursor page. Orphan/duplicate analysis covers this page only.`);
3488
+ warnings.push(`Spans were capped: the page reached the server's per-page span cap after ${capped.rows} ingest row(s), so this trace has more spans than the ${shownText}. Continue with the next_cursor page. Orphan/duplicate analysis covers this page only.`);
3220
3489
  } else if (capped.rowsCapped) {
3221
3490
  const bigger = Math.min(AUDIT_MAX_LIMIT, limit * 5);
3222
- warnings.push(`Spans were capped: read ${capped.rows} ingest row(s), the --limit=${limit} maximum, so this trace ${capped.hasMoreKnown ? "has" : "may have"} more spans than the ${capped.spans} shown. ` + (capped.nextCursor ? "Page on with the next_cursor command, or " : "") + (limit < AUDIT_MAX_LIMIT ? `${capped.nextCursor ? "rerun" : "Rerun"} with --limit=${bigger} (max ${AUDIT_MAX_LIMIT}), or ${perTrace}.` : capped.nextCursor ? `${perTrace}.` : `${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`));
3223
- if (limit < AUDIT_MAX_LIMIT && !params.cursor) {
3491
+ warnings.push(`Spans were capped: read ${capped.rows} ingest row(s), the --limit=${limit} maximum, so this trace ${capped.hasMoreKnown ? "has" : "may have"} more spans than the ${shownText}. ` + (capped.nextCursor ? "Page on with the next_cursor command, or " : "") + (limit < AUDIT_MAX_LIMIT ? `${capped.nextCursor ? "rerun" : "Rerun"} with --limit=${bigger} (max ${AUDIT_MAX_LIMIT}), or ${perTrace}.` : capped.nextCursor ? `${perTrace}.` : `${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`));
3492
+ if (limit < AUDIT_MAX_LIMIT && !params.cursor && !sliced) {
3224
3493
  nextCommands.push({ command: auditFlags([`--limit=${bigger}`]), purpose: "Read more ingest rows of this trace.", mutability: "read", requires_approval: false });
3225
3494
  }
3226
3495
  } else {
3227
3496
  warnings.push(`Spans were capped: the server returned ${capped.spans} span(s) and marked the response truncated. ${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`);
3228
3497
  }
3229
3498
  return {
3230
- data: { ...auditRecord, truncated: true, has_more: capped.rowsCapped, next_cursor: capped.nextCursor },
3499
+ data: { ...record, truncated: true, has_more: capped.rowsCapped, next_cursor: capped.nextCursor },
3231
3500
  options: { warnings, ...nextCommands.length > 0 ? { nextCommands } : {} }
3232
3501
  };
3233
3502
  }
@@ -3416,8 +3685,8 @@ async function runTailCommand(params, context) {
3416
3685
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
3417
3686
  }
3418
3687
  }
3419
- async function attachWorldState(rows) {
3420
- return Promise.all(rows.map(async (row) => {
3688
+ async function attachWorldState(rows, budgetBytes) {
3689
+ return enrichWithinBudget(rows, async (row) => {
3421
3690
  const conv_id = asString(row.conversation_id) ?? "";
3422
3691
  if (!conv_id) {
3423
3692
  return { ...row, world_state: null };
@@ -3429,7 +3698,7 @@ async function attachWorldState(rows) {
3429
3698
  const message = err instanceof Error ? err.message : String(err);
3430
3699
  return { ...row, world_state: { error: message } };
3431
3700
  }
3432
- }));
3701
+ }, budgetBytes, "world_state_skipped");
3433
3702
  }
3434
3703
  function recordSearchTelemetry(context, data) {
3435
3704
  const record = asRecord(data);
@@ -3539,7 +3808,7 @@ function parseCommandFlags(schema, args, command, options = {}) {
3539
3808
  ]);
3540
3809
  const unknown = Object.keys(args).filter((key) => !known.has(key));
3541
3810
  if (unknown.includes("dry_run")) {
3542
- throw new CliInputError(`'moda ${command}' has no dry-run mode, so nothing was sent.`, `Remove --dry-run to run it for real. Commands with a real dry-run: sync, registry push, harness sync/delete/register, prompts sync/decide, skills sync/policy, signals <write subcommands>, problem-reopen.`, "unsupported_dry_run");
3811
+ throw new CliInputError(`'moda ${command}' has no dry-run mode (it does not write), so nothing was sent.`, `Remove --dry-run. Every write takes --dry-run: problems feedback|reopen|link, review, feedback, signals <write subcommands>, sync, registry push, harness sync/delete/register, prompts sync/decide, skills sync/policy.`, "unsupported_dry_run");
3543
3812
  }
3544
3813
  if (unknown.length > 0) {
3545
3814
  const flagKeys = [
@@ -3575,8 +3844,9 @@ function parseCommandFlags(schema, args, command, options = {}) {
3575
3844
  suggestedCommands: [helpCommand]
3576
3845
  });
3577
3846
  }
3847
+ const typed = coerceNumericFlags(schema, args, command);
3578
3848
  try {
3579
- return schema.parse(args);
3849
+ return schema.parse(typed);
3580
3850
  } catch (error) {
3581
3851
  if (error instanceof z.ZodError)
3582
3852
  attachFlagDetails(error, schema, command);
@@ -3608,20 +3878,6 @@ function specParseOptions(spec) {
3608
3878
  ...spec.flagAliases ? { flagAliases: spec.flagAliases } : {}
3609
3879
  };
3610
3880
  }
3611
- function withStringIdFlags(args, flags) {
3612
- for (const flag of ["user-id", "conversation-id"]) {
3613
- if (flags[flag] !== undefined)
3614
- args[flag.replace(/-/g, "_")] = flags[flag];
3615
- }
3616
- return args;
3617
- }
3618
- function shellArg(value) {
3619
- if (/^[A-Za-z0-9_.,:@%+\/=-]+$/.test(value))
3620
- return value;
3621
- if (!/["$`\\!]/.test(value))
3622
- return `"${value}"`;
3623
- return `'${value.replace(/'/g, `'\\''`)}'`;
3624
- }
3625
3881
  function semanticIndexCoverage(coverage) {
3626
3882
  const ratio = asRecord(asRecord(coverage)?.semantic_index)?.coverage_ratio;
3627
3883
  if (typeof ratio !== "number" || !Number.isFinite(ratio) || ratio < 0 || ratio > 1)
@@ -3722,8 +3978,7 @@ async function searchFallbackToTraces(params) {
3722
3978
  return;
3723
3979
  const pagination = asRecord(traces.pagination);
3724
3980
  const total = asNumber(pagination?.total) ?? rows.length;
3725
- const nextOffset = offset + rows.length;
3726
- const hasMore = pagination?.has_more === true || (asNumber(pagination?.total) ?? 0) > nextOffset;
3981
+ const serverHasMore = pagination?.has_more === true;
3727
3982
  const traceMode = asString(traces.search_mode) ?? "keyword";
3728
3983
  const literal = traceMode === "keyword";
3729
3984
  if (params.mode === "keyword" && !literal) {
@@ -3745,33 +4000,46 @@ async function searchFallbackToTraces(params) {
3745
4000
  }));
3746
4001
  const first = asString(rows[0].conversation_id);
3747
4002
  const describe = literal ? "trace-level keyword matches (each trace contains the words)" : `trace-level ${traceMode}-ranked related traces (they may not contain the exact words; fallback.total is the candidate pool, not a match count)`;
3748
- const nextCommands = [];
3749
- if (first) {
3750
- nextCommands.push({ command: `moda traces spans ${first}`, purpose: "Open the top trace to locate the relevant message.", mutability: "read", requires_approval: false });
3751
- }
3752
- if (hasMore) {
3753
- nextCommands.push({ command: `${tracesCommand} --offset=${nextOffset}`, purpose: "Page through further trace-level results.", mutability: "read", requires_approval: false });
3754
- }
3755
- return {
3756
- kind: "results",
3757
- hasMore,
3758
- data: {
3759
- results,
3760
- fallback: {
3761
- source: "traces_search",
3762
- reason: "message_index_empty",
3763
- grain: "trace",
3764
- search_mode: traceMode,
3765
- total,
3766
- total_kind: literal ? "matches" : "candidate_pool",
3767
- has_more: hasMore,
3768
- ...offset > 0 ? { offset } : {},
3769
- ...hasMore ? { next_offset: nextOffset } : {}
3770
- }
3771
- },
3772
- warning: `Message-level search returned 0 results, so these are ${describe}, from \`moda traces --search\`. Each result is a whole trace, not a message, with no score or message index. Open one with \`moda traces spans <conversation_id>\` or \`moda traces get <conversation_id>\` to find the matching turn.` + (hasMore ? " More exist: page with the suggested `moda traces --search ... --offset` command." : ""),
3773
- nextCommands
4003
+ const page = (kept) => {
4004
+ const omitted = rows.length - kept;
4005
+ const nextOffset = offset + kept;
4006
+ const hasMore = omitted > 0 || serverHasMore || total > nextOffset;
4007
+ const nextCommands = [];
4008
+ if (first) {
4009
+ nextCommands.push({ command: `moda traces spans ${shellArg(first)}`, purpose: "Open the top trace to locate the relevant message.", mutability: "read", requires_approval: false });
4010
+ }
4011
+ if (hasMore) {
4012
+ nextCommands.push({
4013
+ command: `${tracesCommand} --offset=${nextOffset}`,
4014
+ purpose: omitted > 0 ? `Read the ${omitted} trace-level result(s) left out to fit the agent byte limit, and the ones after them.` : "Page through further trace-level results.",
4015
+ mutability: "read",
4016
+ requires_approval: false
4017
+ });
4018
+ }
4019
+ return {
4020
+ returned: kept,
4021
+ offset,
4022
+ hasMore,
4023
+ data: {
4024
+ results: results.slice(0, kept),
4025
+ fallback: {
4026
+ source: "traces_search",
4027
+ reason: "message_index_empty",
4028
+ grain: "trace",
4029
+ search_mode: traceMode,
4030
+ total,
4031
+ total_kind: literal ? "matches" : "candidate_pool",
4032
+ has_more: hasMore,
4033
+ ...offset > 0 ? { offset } : {},
4034
+ ...hasMore ? { next_offset: nextOffset } : {},
4035
+ ...omitted > 0 ? { omitted_for_byte_limit: omitted } : {}
4036
+ }
4037
+ },
4038
+ warning: `Message-level search returned 0 results, so these are ${describe}, from \`moda traces --search\`. Each result is a whole trace, not a message, with no score or message index. Open one with \`moda traces spans <conversation_id>\` or \`moda traces get <conversation_id>\` to find the matching turn.` + (omitted > 0 ? ` ${kept} of the ${rows.length} trace-level results read are shown, to stay under the agent output byte limit (--json has no limit); the rest start at --offset=${nextOffset}.` : "") + (hasMore ? " More exist: page with the suggested `moda traces --search ... --offset` command." : ""),
4039
+ nextCommands
4040
+ };
3774
4041
  };
4042
+ return { kind: "results", rows: rows.length, page };
3775
4043
  }
3776
4044
  function capProblemsBlocks(data, limit) {
3777
4045
  const truncated = {};
@@ -3822,6 +4090,31 @@ var NO_POSITIONAL_COMMANDS = new Set([
3822
4090
  "tail",
3823
4091
  "problem-impact"
3824
4092
  ]);
4093
+ function rejectInapplicableFlags(command, params, keys, why) {
4094
+ const given = keys.filter((key) => params[key] !== undefined);
4095
+ if (given.length === 0)
4096
+ return;
4097
+ const names = given.map(kebabFlag);
4098
+ const shown = canonicalCommandText(`moda ${command}`);
4099
+ throw new CliInputError(`${names.join(", ")} ${given.length > 1 ? "do" : "does"} not apply ${why}, so nothing was sent.`, `Drop ${names.join(", ")}, or pick the mode that uses ${given.length > 1 ? "them" : "it"}. See '${shown} --help'.`, `inapplicable_flag:${names[0].slice(2)}`, { field: names[0], suggestedCommands: [`${shown} --help`] });
4100
+ }
4101
+ var BATCH_POSITIONAL_COMMANDS = new Set(["context", "audit", "trace"]);
4102
+ function assertPositionalArity(command, positionals) {
4103
+ const spec = flagSpecFor(command);
4104
+ const expected = spec?.positionals ?? [];
4105
+ if (expected.length === 0 || expected.some((entry) => entry.variadic))
4106
+ return;
4107
+ if (BATCH_POSITIONAL_COMMANDS.has(command))
4108
+ return;
4109
+ if (command === "problem" && positionals[0] === "remainder")
4110
+ return;
4111
+ if (positionals.length <= expected.length)
4112
+ return;
4113
+ const extra = positionals.slice(expected.length);
4114
+ const shown = canonicalCommandText(`moda ${command}`);
4115
+ const takes = expected.map((entry) => `<${entry.name}>`).join(" ");
4116
+ throw new CliInputError(`Unexpected argument${extra.length > 1 ? "s" : ""} '${extra.join(" ")}' for '${shown}', which takes ${takes}, so nothing was sent.`, `Quote a value that has spaces, and pass free text as a flag value (e.g. --reason="..."). Usage: ${canonicalCommandText(usageLine(command, spec))}`, "unexpected_positional", { field: `<${expected[expected.length - 1].name}>`, suggestedCommands: [`${shown} --help`] });
4117
+ }
3825
4118
  function assertHandParsedFlags(command, flags, positionals) {
3826
4119
  if (command === "signals" && positionals[0]) {
3827
4120
  if (SIGNALS_MANAGEMENT_SUBCOMMANDS.has(positionals[0]))
@@ -3831,6 +4124,7 @@ function assertHandParsedFlags(command, flags, positionals) {
3831
4124
  if (NO_POSITIONAL_COMMANDS.has(command) && positionals.length > 0) {
3832
4125
  throw new CliInputError(`'moda ${command}' takes no arguments, but got '${positionals.join(" ")}'.`, `Nothing was run. See 'moda ${command} --help' for its flags.`);
3833
4126
  }
4127
+ assertPositionalArity(command, positionals);
3834
4128
  const hand = handParsedFlags(command);
3835
4129
  if (!hand)
3836
4130
  return;
@@ -3959,6 +4253,10 @@ async function readContextMetadataOnly(contextPath, query2) {
3959
4253
  }
3960
4254
  var FULL_TRANSCRIPT_DEFAULT_MAX_MESSAGES = 500;
3961
4255
  var CONTEXT_RANGE_PAGE_SIZE = 200;
4256
+ var CONTEXT_BUDGET_BYTES_PER_FIRST_PAGE_MESSAGE = 480;
4257
+ function jsonByteLength(value) {
4258
+ return Buffer.byteLength(JSON.stringify(value) ?? "", "utf8");
4259
+ }
3962
4260
  var LEGACY_WINDOW_MAX_MESSAGES = 100;
3963
4261
  var CONTEXT_MESSAGE_TRUNCATE_CHARS = 500;
3964
4262
  var LONE_SURROGATE = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
@@ -3996,8 +4294,15 @@ async function readFullTranscript(conversationId, opts = {}) {
3996
4294
  let nextFrom = null;
3997
4295
  let legacy = false;
3998
4296
  let cursor = start;
4297
+ let readBytes = 0;
4298
+ const byteBudget = opts.fits ? opts.maxBytes : undefined;
3999
4299
  for (;; ) {
4000
- const count = Math.min(CONTEXT_RANGE_PAGE_SIZE, requested - messages.length);
4300
+ let count = Math.min(CONTEXT_RANGE_PAGE_SIZE, requested - messages.length);
4301
+ if (byteBudget !== undefined) {
4302
+ const average = messages.length > 0 ? readBytes / messages.length : 0;
4303
+ const estimate = average > 0 ? Math.ceil(Math.max(0, byteBudget - readBytes) / average) + 1 : Math.max(1, Math.floor(byteBudget / CONTEXT_BUDGET_BYTES_PER_FIRST_PAGE_MESSAGE));
4304
+ count = Math.max(1, Math.min(count, estimate));
4305
+ }
4001
4306
  let page;
4002
4307
  try {
4003
4308
  page = asRecord(await callDataAPI(`${path}?from=${cursor}&count=${count}`)) ?? {};
@@ -4015,10 +4320,14 @@ async function readFullTranscript(conversationId, opts = {}) {
4015
4320
  }
4016
4321
  const batch = pageMessages(page);
4017
4322
  messages.push(...batch);
4323
+ if (byteBudget !== undefined)
4324
+ readBytes += batch.reduce((sum, message) => sum + jsonByteLength(message) + 1, 0);
4018
4325
  const next = asNumber(page.next_from);
4019
4326
  nextFrom = page.has_more === true && next !== null && next !== undefined && next > cursor ? next : null;
4020
4327
  if (nextFrom === null || batch.length === 0 || messages.length >= requested)
4021
4328
  break;
4329
+ if (byteBudget !== undefined && readBytes > byteBudget)
4330
+ break;
4022
4331
  cursor = nextFrom;
4023
4332
  }
4024
4333
  if (legacy) {
@@ -4115,12 +4424,14 @@ async function readFullTranscript(conversationId, opts = {}) {
4115
4424
  }
4116
4425
  return build(low);
4117
4426
  }
4118
- async function readContextItem(command, params, inputWarnings, fits) {
4427
+ async function readContextItem(command, params, inputWarnings, budget) {
4428
+ const fits = budget?.fits;
4119
4429
  if (params.all === true) {
4120
4430
  const full = await readFullTranscript(params.conversation_id, {
4121
4431
  from: params.from,
4122
4432
  maxMessages: params.max_messages,
4123
- fits: fits ? (page) => fits(page.data, { warnings: page.warnings, nextCommands: page.nextCommands }) : undefined
4433
+ fits: fits ? (page) => fits(page.data, { warnings: page.warnings, nextCommands: page.nextCommands }) : undefined,
4434
+ maxBytes: budget?.maxBytes
4124
4435
  });
4125
4436
  const fullWarnings = [...inputWarnings, ...full.warnings];
4126
4437
  return {
@@ -4154,13 +4465,85 @@ async function readContextItem(command, params, inputWarnings, fits) {
4154
4465
  warnings.push(`--window=${params.window} is above the maximum of ${CONTEXT_MAX_WINDOW}, so this shows ±${CONTEXT_MAX_WINDOW} messages. For a wider slice run: ${wide}`);
4155
4466
  nextCommands.push({ command: wide, purpose: `Read ±${params.window} messages around the anchor.`, mutability: "read", requires_approval: false });
4156
4467
  }
4157
- return {
4158
- data,
4159
- options: {
4160
- ...warnings.length > 0 ? { warnings } : {},
4161
- ...nextCommands.length > 0 ? { nextCommands } : {}
4468
+ const options = (extraWarnings = [], extraNext = []) => {
4469
+ const allWarnings = [...warnings, ...extraWarnings];
4470
+ const allNext = [...nextCommands, ...extraNext];
4471
+ return {
4472
+ ...allWarnings.length > 0 ? { warnings: allWarnings } : {},
4473
+ ...allNext.length > 0 ? { nextCommands: allNext } : {}
4474
+ };
4475
+ };
4476
+ if (fits && params.metadata_only !== true && !fits(data, options())) {
4477
+ const trimmed = trimContextWindow(params.conversation_id, ctxRecord, params.msg_index, (page, extraWarnings, extraNext) => fits(page, options(extraWarnings, extraNext)));
4478
+ if (trimmed)
4479
+ return { data: trimmed.data, options: options(trimmed.warnings, trimmed.nextCommands) };
4480
+ }
4481
+ return { data, options: options() };
4482
+ }
4483
+ function trimContextWindow(conversationId, record, requestedAnchor, fits) {
4484
+ const ctx = asRecord(record.context);
4485
+ const messages = Array.isArray(ctx?.messages) ? ctx.messages : [];
4486
+ if (!ctx || messages.length <= 1)
4487
+ return;
4488
+ const firstIndex = asNumber(ctx.from_index) ?? asNumber(asRecord(messages[0])?.index) ?? 0;
4489
+ const indexed = messages.map((message, position) => ({
4490
+ message,
4491
+ index: asNumber(asRecord(message)?.index) ?? firstIndex + position
4492
+ }));
4493
+ const center = asNumber(ctx.center_index) ?? requestedAnchor ?? indexed[Math.floor(indexed.length / 2)].index;
4494
+ const requestedFrom = Math.min(asNumber(ctx.from_index) ?? indexed[0].index, indexed[0].index);
4495
+ const requestedTo = Math.max(asNumber(ctx.to_index) ?? indexed[indexed.length - 1].index, indexed[indexed.length - 1].index);
4496
+ const order = [...indexed].sort((left, right) => Math.abs(left.index - center) - Math.abs(right.index - center) || right.index - left.index);
4497
+ const target = shellArg(contextTarget(conversationId));
4498
+ const build = (kept) => {
4499
+ const returned = order.slice(0, kept).sort((left, right) => left.index - right.index);
4500
+ const from = returned[0].index;
4501
+ const to = returned[returned.length - 1].index;
4502
+ const before = from - requestedFrom;
4503
+ const after = requestedTo - to;
4504
+ const nextCommands = [];
4505
+ if (before > 0) {
4506
+ nextCommands.push({
4507
+ command: `moda traces get ${target} --all --from=${requestedFrom} --max-messages=${before}`,
4508
+ purpose: `Read the ${before} message(s) before ${from} that were left out to fit the agent byte limit.`,
4509
+ mutability: "read",
4510
+ requires_approval: false
4511
+ });
4162
4512
  }
4513
+ if (after > 0) {
4514
+ nextCommands.push({
4515
+ command: `moda traces get ${target} --all --from=${to + 1} --max-messages=${after}`,
4516
+ purpose: `Read the ${after} message(s) after ${to} that were left out to fit the agent byte limit.`,
4517
+ mutability: "read",
4518
+ requires_approval: false
4519
+ });
4520
+ }
4521
+ const warnings = kept < order.length ? [
4522
+ `Window cut to messages ${from}-${to} around anchor ${center} to stay under the agent output byte limit (--json has no limit); requested ${requestedFrom}-${requestedTo}. Read the rest with: ${nextCommands.map((next) => next.command).join(" and ")}.`
4523
+ ] : [];
4524
+ const data = {
4525
+ ...record,
4526
+ context: {
4527
+ ...ctx,
4528
+ from_index: from,
4529
+ to_index: to,
4530
+ messages: returned.map((entry) => entry.message),
4531
+ ...kept < order.length ? { requested_from_index: requestedFrom, requested_to_index: requestedTo, omitted_for_byte_limit: order.length - kept } : {}
4532
+ }
4533
+ };
4534
+ return { data, warnings, nextCommands };
4163
4535
  };
4536
+ let low = 1;
4537
+ let high = order.length;
4538
+ while (low < high) {
4539
+ const mid = Math.ceil((low + high) / 2);
4540
+ const page = build(mid);
4541
+ if (fits(page.data, page.warnings, page.nextCommands))
4542
+ low = mid;
4543
+ else
4544
+ high = mid - 1;
4545
+ }
4546
+ return build(low);
4164
4547
  }
4165
4548
  var AUDIT_USAGE = "Usage: moda traces spans <conversation_id|trace_id> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] [--cursor=<next_cursor>]";
4166
4549
  var AUDIT_BATCH_USAGE = `Usage: moda traces spans <id> <id> ... | --ids-from=-|<file> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] (max ${BATCH_MAX_TARGETS} targets)`;
@@ -4176,16 +4559,31 @@ async function readAuditItem(command, params, budget) {
4176
4559
  query2.set("limit", params.limit.toString());
4177
4560
  if (params.cursor)
4178
4561
  query2.set("cursor", params.cursor);
4179
- let maxBytes = budget?.maxBytes;
4180
- let page = auditOutput(command, params, await fetchAudit(params.id, query2, maxBytes));
4181
- while (budget && maxBytes !== undefined && maxBytes > AUDIT_MIN_PAGE_BYTES && !budget.fits(page.data, page.options)) {
4182
- maxBytes = Math.max(AUDIT_MIN_PAGE_BYTES, Math.floor(maxBytes / 2));
4183
- page = auditOutput(command, params, await fetchAudit(params.id, query2, maxBytes));
4562
+ const pageBytes = params.page_bytes ?? (budget ? auditMaxBytes(budget.maxBytes) : undefined);
4563
+ const raw = await fetchAudit(params.id, query2, pageBytes);
4564
+ params = { ...params, page_bytes: pageBytes };
4565
+ const whole = auditOutput(command, params, raw);
4566
+ if (!budget || budget.fits(whole.data, whole.options))
4567
+ return whole;
4568
+ const spans = asRecord(raw)?.spans;
4569
+ const available = (Array.isArray(spans) ? spans.length : 0) - (params.span_offset ?? 0);
4570
+ if (available <= 0)
4571
+ return whole;
4572
+ let low = 1;
4573
+ let high = available;
4574
+ while (low < high) {
4575
+ const mid = Math.ceil((low + high) / 2);
4576
+ const page2 = auditOutput(command, params, raw, mid);
4577
+ if (budget.fits(page2.data, page2.options))
4578
+ low = mid;
4579
+ else
4580
+ high = mid - 1;
4184
4581
  }
4185
- if (budget && !budget.fits(page.data, page.options)) {
4582
+ const page = auditOutput(command, params, raw, low);
4583
+ if (low === 1 && !budget.fits(page.data, page.options)) {
4186
4584
  page.options.warnings = [
4187
4585
  ...page.options.warnings ?? [],
4188
- "This page is larger than the agent output budget even at the smallest page the server returns, so some of its spans are cut from this output. Rerun this command with --json (no byte limit) or a higher MODA_AGENT_MAX_OUTPUT_BYTES to see the whole row before following next_cursor."
4586
+ "One span of this page is larger than the agent output budget, so agent output shortens it. Rerun this command with --json (no byte limit) or a higher MODA_AGENT_MAX_OUTPUT_BYTES to see it whole."
4189
4587
  ];
4190
4588
  }
4191
4589
  return page;
@@ -4202,6 +4600,16 @@ function singleContextTarget(positional, flags) {
4202
4600
  }
4203
4601
  return [target.id, { ...flags, "msg-index": String(target.msgIndex) }];
4204
4602
  }
4603
+ async function withAnchorHint(target, read) {
4604
+ try {
4605
+ return await read();
4606
+ } catch (error) {
4607
+ if (!(error instanceof CliNotFoundError) || target.msgIndex === undefined || target.raw.startsWith("{"))
4608
+ throw error;
4609
+ const verbatim = JSON.stringify({ id: target.raw });
4610
+ throw new CliNotFoundError(error.entity, error.id, `${error.message} '${target.raw}' was read as trace "${target.id}" anchored at message ${target.msgIndex} (the @N suffix is a message index). If "${target.raw}" is the whole trace id, pass it as ${shellArg(verbatim)} or --conversation-id=${shellArg(target.raw)}.`, `moda traces get ${shellArg(verbatim)}`);
4611
+ }
4612
+ }
4205
4613
  function isBatchRead(positionals, flags) {
4206
4614
  return positionals.length > 1 || IDS_FROM_KEYS.some((key) => flags[key] !== undefined);
4207
4615
  }
@@ -4216,14 +4624,41 @@ function splitIdsFrom(flags) {
4216
4624
  }
4217
4625
  return { idsFrom, rest };
4218
4626
  }
4219
- function auditItemByteShare(count) {
4220
- return Math.max(AUDIT_MIN_PAGE_BYTES, Math.floor((agentMaxOutputBytes() - AUDIT_AGENT_RESERVE_BYTES) / count));
4221
- }
4222
4627
  var BATCH_ENVELOPE_RESERVE_BYTES = 4096;
4223
- var BATCH_ENVELOPE_RESERVE_PER_TARGET_BYTES = 256;
4224
- function contextItemByteShare(count) {
4225
- const reserve = BATCH_ENVELOPE_RESERVE_BYTES + BATCH_ENVELOPE_RESERVE_PER_TARGET_BYTES * count;
4226
- return Math.max(1, Math.floor((agentMaxOutputBytes() - reserve) / count));
4628
+ function batchTargetReserveBytes(target) {
4629
+ return 3 * (jsonByteLength(target.raw) + 4) + 16;
4630
+ }
4631
+ var BATCH_MIN_ITEM_SHARE_BYTES = 4096;
4632
+ async function readBatchTargets(targets, agentMode, read) {
4633
+ if (!agentMode) {
4634
+ return mapWithConcurrency(targets, BATCH_CONCURRENCY, (target) => readBatchItem(target, () => read(target, undefined)));
4635
+ }
4636
+ const reserve = targets.reduce((sum, target) => sum + batchTargetReserveBytes(target), BATCH_ENVELOPE_RESERVE_BYTES);
4637
+ const available = Math.max(1, agentMaxOutputBytes() - reserve);
4638
+ const fair = Math.max(BATCH_MIN_ITEM_SHARE_BYTES, Math.floor(available / targets.length));
4639
+ let used = 0;
4640
+ let reserved = 0;
4641
+ const shares = new Map;
4642
+ return mapWithConcurrency(targets, BATCH_CONCURRENCY, async (target, index) => {
4643
+ const share = shares.get(index) ?? fair;
4644
+ try {
4645
+ const result = await readBatchItem(target, () => read(target, { fits: fitsShare(share), maxBytes: share }));
4646
+ used += jsonByteLength(result) + 1;
4647
+ return result;
4648
+ } finally {
4649
+ reserved -= share;
4650
+ }
4651
+ }, {
4652
+ shouldStart: (index) => {
4653
+ const free = available - used - reserved;
4654
+ if (index > 0 && free < BATCH_MIN_ITEM_SHARE_BYTES)
4655
+ return false;
4656
+ const share = Math.max(1, Math.min(fair, free));
4657
+ shares.set(index, share);
4658
+ reserved += share;
4659
+ return true;
4660
+ }
4661
+ });
4227
4662
  }
4228
4663
  function fitsShare(share) {
4229
4664
  return (data, options) => Buffer.byteLength(JSON.stringify({ data, ...options }) ?? "", "utf8") <= share;
@@ -4335,27 +4770,25 @@ async function runContextBatch(command, positionals, flags, context) {
4335
4770
  throw new CliInputError(`${base.all === true ? "--all reads whole traces" : "--metadata-only reads no messages"}, so targets cannot carry an @msg_index anchor; drop the flag or the anchors.`, CONTEXT_BATCH_USAGE, "flag_conflict");
4336
4771
  }
4337
4772
  const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
4338
- const share = agentMode ? contextItemByteShare(targets.length) : undefined;
4339
- const results = await mapWithConcurrency(targets, BATCH_CONCURRENCY, (target) => readBatchItem(target, () => {
4773
+ const results = await readBatchTargets(targets, agentMode, (target, budget) => {
4340
4774
  const params = {
4341
4775
  ...base,
4342
4776
  conversation_id: target.id,
4343
4777
  ...target.msgIndex !== undefined ? { msg_index: target.msgIndex } : {}
4344
4778
  };
4345
- return readContextItem(command, params, [], share !== undefined ? fitsShare(share) : undefined);
4346
- }));
4779
+ return withAnchorHint(target, () => readContextItem(command, params, [], budget));
4780
+ });
4347
4781
  return writeBatchResult(command, context, targets, results, inputWarnings, rest);
4348
4782
  }
4349
4783
  async function runAuditBatch(command, positionals, flags, context) {
4350
4784
  const { idsFrom, rest } = splitIdsFrom(flags);
4351
- if (rest.cursor !== undefined) {
4352
- throw new CliInputError("--cursor continues one trace, so it cannot be used with several ids; follow each item's next_commands instead.", AUDIT_BATCH_USAGE, "flag_conflict");
4785
+ if (rest.cursor !== undefined || Object.keys(rest).some((key) => key.replace(/_/g, "-") === "span-offset")) {
4786
+ throw new CliInputError(`${rest.cursor !== undefined ? "--cursor" : "--span-offset"} continues one trace, so it cannot be used with several ids; follow each item's next_commands instead.`, AUDIT_BATCH_USAGE, "flag_conflict");
4353
4787
  }
4354
4788
  const targets = await collectBatchTargets(positionals, idsFrom, { anchors: false, usage: AUDIT_BATCH_USAGE, command });
4355
4789
  const base = parseCommandFlags(AuditSchema, flagsToArgs(rest, "id", targets[0].id), command, { positionalKey: "id" });
4356
4790
  const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
4357
- const share = agentMode ? auditItemByteShare(targets.length) : undefined;
4358
- const results = await mapWithConcurrency(targets, BATCH_CONCURRENCY, (target) => readBatchItem(target, () => readAuditItem(command, { ...base, id: target.id }, share !== undefined ? { maxBytes: Math.max(AUDIT_MIN_PAGE_BYTES, share), fits: fitsShare(share) } : undefined)));
4791
+ const results = await readBatchTargets(targets, agentMode, (target, budget) => readAuditItem(command, { ...base, id: target.id }, budget));
4359
4792
  return writeBatchResult(command, context, targets, results, [], rest);
4360
4793
  }
4361
4794
  async function runCommand(command, positional, flags, positionals = positional ? [positional] : [], context = createCommandContext({ command, positional, positionals, flags })) {
@@ -4378,6 +4811,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4378
4811
  const args = flagsToArgs(flags);
4379
4812
  const params = parseSpecFlags("clusters", args, command);
4380
4813
  if (params.search !== undefined || params.node_id !== undefined) {
4814
+ rejectInapplicableFlags(command, params, ["parent_id", "time_range"], "to a cluster search (--search/--node-id); they page the cluster hierarchy");
4381
4815
  const query3 = new URLSearchParams;
4382
4816
  if (params.search !== undefined)
4383
4817
  query3.set("q", params.search);
@@ -4394,6 +4828,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4394
4828
  context.output.writeData(data2, warnings.length > 0 ? { warnings } : undefined);
4395
4829
  break;
4396
4830
  }
4831
+ rejectInapplicableFlags(command, params, ["mode", "limit"], "without --search or --node-id; they shape a cluster search");
4397
4832
  const query2 = new URLSearchParams;
4398
4833
  if (params.parent_id)
4399
4834
  query2.set("parent_id", params.parent_id);
@@ -4425,7 +4860,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4425
4860
  }
4426
4861
  case "traces":
4427
4862
  case "conversations": {
4428
- const args = withStringIdFlags(flagsToArgs(flags), flags);
4863
+ const args = flagsToArgs(flags);
4429
4864
  const params = parseSpecFlags("traces", args, command);
4430
4865
  const query2 = new URLSearchParams;
4431
4866
  if (params.search)
@@ -4476,7 +4911,8 @@ async function runCommand(command, positional, flags, positionals = positional ?
4476
4911
  if (params.include_world_state === true) {
4477
4912
  const enriched = asRecord(data) ?? {};
4478
4913
  const list = Array.isArray(enriched.conversations) ? enriched.conversations : [];
4479
- const withState = await attachWorldState(list);
4914
+ const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
4915
+ const withState = await attachWorldState(list, agentMode ? agentMaxOutputBytes() : undefined);
4480
4916
  context.output.writeData({ ...enriched, conversations: withState }, writeOptions);
4481
4917
  } else {
4482
4918
  context.output.writeData(data, writeOptions);
@@ -4484,10 +4920,11 @@ async function runCommand(command, positional, flags, positionals = positional ?
4484
4920
  break;
4485
4921
  }
4486
4922
  case "search": {
4487
- if (!positional || positional.trim() === "") {
4923
+ const queryText = positionals.join(" ");
4924
+ if (queryText.trim() === "") {
4488
4925
  throw new CliInputError("<query> is required", 'Usage: moda traces search "<query>" [--mode=keyword|semantic|hybrid] [--user-id=ID] [--conversation-id=ID] [--time-range=RANGE] [--since=DATE] [--until=DATE] [--include-tool-io] [--limit=N]');
4489
4926
  }
4490
- const args = withStringIdFlags(flagsToArgs(flags, "q", positional), flags);
4927
+ const args = flagsToArgs(flags, "q", queryText);
4491
4928
  const params = parseSpecFlags("search", args, command);
4492
4929
  const query2 = new URLSearchParams;
4493
4930
  query2.set("q", params.q);
@@ -4524,15 +4961,42 @@ async function runCommand(command, positional, flags, positionals = positional ?
4524
4961
  break;
4525
4962
  }
4526
4963
  if (fallback?.kind === "results") {
4527
- warnings.push(fallback.warning);
4528
- const fallbackResults = fallback.data.results;
4529
- const merged = {
4530
- ...searchRecord,
4531
- ...fallback.data,
4532
- pagination: { ...asRecord(searchRecord.pagination) ?? {}, returned: fallbackResults.length, has_more: fallback.hasMore }
4964
+ const write = (kept2) => {
4965
+ const page2 = fallback.page(kept2);
4966
+ const merged = {
4967
+ ...searchRecord,
4968
+ ...page2.data,
4969
+ pagination: {
4970
+ ...asRecord(searchRecord.pagination) ?? {},
4971
+ returned: page2.returned,
4972
+ has_more: page2.hasMore,
4973
+ offset: page2.offset,
4974
+ ...page2.returned < fallback.rows ? { omitted_for_byte_limit: fallback.rows - page2.returned } : {}
4975
+ }
4976
+ };
4977
+ return { data: merged, options: { warnings: [...warnings, page2.warning], nextCommands: page2.nextCommands } };
4978
+ };
4979
+ const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
4980
+ let kept = fallback.rows;
4981
+ const fitsAgent = (count) => {
4982
+ const page2 = write(count);
4983
+ return agentEnvelopeFits(command, page2.data, page2.options);
4533
4984
  };
4534
- recordSearchTelemetry(context, merged);
4535
- context.output.writeData(merged, { warnings, nextCommands: fallback.nextCommands });
4985
+ if (agentMode && kept > 1 && !fitsAgent(kept)) {
4986
+ let low = 1;
4987
+ let high = kept - 1;
4988
+ while (low < high) {
4989
+ const mid = Math.ceil((low + high) / 2);
4990
+ if (fitsAgent(mid))
4991
+ low = mid;
4992
+ else
4993
+ high = mid - 1;
4994
+ }
4995
+ kept = low;
4996
+ }
4997
+ const page = write(kept);
4998
+ recordSearchTelemetry(context, page.data);
4999
+ context.output.writeData(page.data, page.options);
4536
5000
  break;
4537
5001
  }
4538
5002
  }
@@ -4590,7 +5054,8 @@ async function runCommand(command, positional, flags, positionals = positional ?
4590
5054
  return runContextBatch(command, positionals, flags, context);
4591
5055
  const { params, warnings: inputWarnings } = parseContextRequest(...singleContextTarget(positional, flags));
4592
5056
  const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
4593
- const item = await readContextItem(command, params, inputWarnings, agentMode ? (data, options) => agentEnvelopeFits(command, data, options) : undefined);
5057
+ const read = () => readContextItem(command, params, inputWarnings, agentMode ? { fits: (data, options) => agentEnvelopeFits(command, data, options), maxBytes: agentMaxOutputBytes() } : undefined);
5058
+ const item = positional === undefined ? await read() : await withAnchorHint(parseBatchTarget(positional, true, CONTEXT_USAGE), read);
4594
5059
  context.output.writeData(item.data, item.options);
4595
5060
  break;
4596
5061
  }
@@ -4604,15 +5069,12 @@ async function runCommand(command, positional, flags, positionals = positional ?
4604
5069
  const args = flagsToArgs(flags, "id", positional);
4605
5070
  const params = parseSpecFlags("audit", args, command);
4606
5071
  const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
4607
- const page = await readAuditItem(command, params, agentMode ? {
4608
- maxBytes: Math.max(AUDIT_MIN_PAGE_BYTES, agentMaxOutputBytes() - AUDIT_AGENT_RESERVE_BYTES),
4609
- fits: (data, options) => agentEnvelopeFits(command, data, options)
4610
- } : undefined);
5072
+ const page = await readAuditItem(command, params, agentMode ? { maxBytes: agentMaxOutputBytes(), fits: (data, options) => agentEnvelopeFits(command, data, options) } : undefined);
4611
5073
  context.output.writeData(page.data, page.options);
4612
5074
  break;
4613
5075
  }
4614
5076
  case "frustrations": {
4615
- const args = withStringIdFlags(flagsToArgs(flags), flags);
5077
+ const args = flagsToArgs(flags);
4616
5078
  const params = parseSpecFlags("frustrations", args, command);
4617
5079
  const query2 = new URLSearchParams;
4618
5080
  if (params.days_back)
@@ -4775,6 +5237,11 @@ async function runCommand(command, positional, flags, positionals = positional ?
4775
5237
  if (view !== undefined && params.full === true) {
4776
5238
  throw new CliInputError("--full applies to the dossier only; the --evidence/--reports/--traces/--feedback pages are always whole.", "Usage: moda problems get <problem_id> [--full] | moda problems get <problem_id> --evidence|--reports|--traces|--feedback");
4777
5239
  }
5240
+ if (view === undefined) {
5241
+ rejectInapplicableFlags(command, params, ["limit", "cursor", "family", "door"], "to the dossier; they page --evidence/--reports/--traces/--feedback");
5242
+ } else if (view !== "conversations") {
5243
+ rejectInapplicableFlags(command, params, ["family", "door"], `to --${view}; they filter --traces`);
5244
+ }
4778
5245
  if (view === undefined) {
4779
5246
  const data2 = await callDataAPI(`/problems/${encodeURIComponent(params.id)}`);
4780
5247
  const record = asRecord(data2) ?? {};
@@ -4844,15 +5311,21 @@ async function runCommand(command, positional, flags, positionals = positional ?
4844
5311
  throw new CliInputError("--attribution-id must be a UUID.");
4845
5312
  }
4846
5313
  }
4847
- const data = await callDataAPI(`/problems/${params.id}/feedback`, {
5314
+ const feedbackPath = `/problems/${params.id}/feedback`;
5315
+ const feedbackBody = {
5316
+ action: params.action,
5317
+ ...reason !== "" ? { reason } : {},
5318
+ ...params.attribution_id ? { attribution_id: params.attribution_id } : {},
5319
+ ...params.new_name ? { new_display_name: params.new_name } : {},
5320
+ actor: params.actor ?? "moda-cli"
5321
+ };
5322
+ if (context.dryRun) {
5323
+ writeDryRun(context, { plane: "data", method: "POST", path: feedbackPath, body: feedbackBody });
5324
+ break;
5325
+ }
5326
+ const data = await callDataAPI(feedbackPath, {
4848
5327
  method: "POST",
4849
- body: JSON.stringify({
4850
- action: params.action,
4851
- ...reason !== "" ? { reason } : {},
4852
- ...params.attribution_id ? { attribution_id: params.attribution_id } : {},
4853
- ...params.new_name ? { new_display_name: params.new_name } : {},
4854
- actor: params.actor ?? "moda-cli"
4855
- }),
5328
+ body: JSON.stringify(feedbackBody),
4856
5329
  headers: { "Content-Type": "application/json" }
4857
5330
  }, { retries: 0 });
4858
5331
  context.output.writeData(data);
@@ -4870,10 +5343,10 @@ async function runCommand(command, positional, flags, positionals = positional ?
4870
5343
  });
4871
5344
  const link = asRecord(data)?.link;
4872
5345
  const summary = describeLinearLink(link);
4873
- const nextCommands = link ? [] : [`moda problems link ${positional}`];
5346
+ const nextCommands = link ? [] : [`moda problems link ${shellArg(positional)}`];
4874
5347
  if (context.outputMode === "human" && !context.quiet) {
4875
5348
  safeWriteStderr(link ? `${summary}
4876
- ` : `${summary} File one with: moda problems link ${positional}
5349
+ ` : `${summary} File one with: moda problems link ${shellArg(positional)}
4877
5350
  `);
4878
5351
  }
4879
5352
  context.output.writeData(data, { summary, nextCommands });
@@ -4903,13 +5376,19 @@ async function runCommand(command, positional, flags, positionals = positional ?
4903
5376
  }
4904
5377
  const title = params.title?.trim() ?? "";
4905
5378
  const teamId = params.team_id?.trim() ?? "";
4906
- const data = await callDataAPI(`/problems/${params.id}/linear`, {
5379
+ const linearPath = `/problems/${params.id}/linear`;
5380
+ const linearBody = issue !== "" ? { issue } : {
5381
+ ...teamId !== "" ? { team_id: teamId } : {},
5382
+ ...title !== "" ? { title } : {},
5383
+ ...params.priority !== undefined ? { priority: params.priority } : {}
5384
+ };
5385
+ if (context.dryRun) {
5386
+ writeDryRun(context, { plane: "data", method: "POST", path: linearPath, body: linearBody });
5387
+ break;
5388
+ }
5389
+ const data = await callDataAPI(linearPath, {
4907
5390
  method: "POST",
4908
- body: JSON.stringify(issue !== "" ? { issue } : {
4909
- ...teamId !== "" ? { team_id: teamId } : {},
4910
- ...title !== "" ? { title } : {},
4911
- ...params.priority !== undefined ? { priority: params.priority } : {}
4912
- }),
5391
+ body: JSON.stringify(linearBody),
4913
5392
  headers: { "Content-Type": "application/json" }
4914
5393
  }, { retries: 0 }).catch((error) => {
4915
5394
  throw linearApiError(error);
@@ -4941,6 +5420,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4941
5420
  const params = parseSpecFlags("signal", args, command);
4942
5421
  const id = encodeURIComponent(params.id);
4943
5422
  if (params.detections !== true) {
5423
+ rejectInapplicableFlags(command, params, ["days_back", "limit", "offset"], "to the signal summary; they page its hits (--detections)");
4944
5424
  const data2 = await callDataAPI(`/custom-signals/${id}`);
4945
5425
  context.output.writeData(data2);
4946
5426
  break;
@@ -4979,15 +5459,21 @@ async function runCommand(command, positional, flags, positionals = positional ?
4979
5459
  ...flags["msg-dedup-token"] !== undefined ? { msg_dedup_token: flags["msg-dedup-token"] } : {}
4980
5460
  });
4981
5461
  const note = params.note?.trim() ?? "";
4982
- const data = await callDataAPI(`/custom-signals/${encodeURIComponent(params.id)}/labels`, {
5462
+ const labelPath = `/custom-signals/${encodeURIComponent(params.id)}/labels`;
5463
+ const labelBody = {
5464
+ conversation_id: params.conversation_id,
5465
+ label: params.label,
5466
+ ...note !== "" ? { note } : {},
5467
+ ...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
5468
+ ...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
5469
+ };
5470
+ if (context.dryRun) {
5471
+ writeDryRun(context, { plane: "data", method: "POST", path: labelPath, body: labelBody });
5472
+ break;
5473
+ }
5474
+ const data = await callDataAPI(labelPath, {
4983
5475
  method: "POST",
4984
- body: JSON.stringify({
4985
- conversation_id: params.conversation_id,
4986
- label: params.label,
4987
- ...note !== "" ? { note } : {},
4988
- ...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
4989
- ...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
4990
- }),
5476
+ body: JSON.stringify(labelBody),
4991
5477
  headers: { "Content-Type": "application/json" }
4992
5478
  }, { retries: 0 });
4993
5479
  context.output.writeData(data);
@@ -5019,15 +5505,20 @@ async function runCommand(command, positional, flags, positionals = positional ?
5019
5505
  ...flags.note !== undefined ? { note: flags.note } : {}
5020
5506
  });
5021
5507
  const note = params.verdict === "clear" ? "" : params.note?.trim() ?? "";
5022
- const data = await callDataAPI(`/problems/${params.id}/detection-reviews`, {
5508
+ const reviewPath = `/problems/${params.id}/detection-reviews`;
5509
+ const reviewBody = {
5510
+ conversation_id: params.conversation_id,
5511
+ attribution_id: params.attribution_id,
5512
+ verdict: params.verdict,
5513
+ ...note !== "" ? { note } : {}
5514
+ };
5515
+ if (context.dryRun) {
5516
+ writeDryRun(context, { plane: "data", method: "POST", path: reviewPath, body: reviewBody, generatedFields: ["request_nonce"] });
5517
+ break;
5518
+ }
5519
+ const data = await callDataAPI(reviewPath, {
5023
5520
  method: "POST",
5024
- body: JSON.stringify({
5025
- conversation_id: params.conversation_id,
5026
- attribution_id: params.attribution_id,
5027
- verdict: params.verdict,
5028
- ...note !== "" ? { note } : {},
5029
- request_nonce: randomUUID2()
5030
- }),
5521
+ body: JSON.stringify({ ...reviewBody, request_nonce: randomUUID2() }),
5031
5522
  headers: { "Content-Type": "application/json" }
5032
5523
  });
5033
5524
  context.output.writeData(data);
@@ -5054,50 +5545,77 @@ async function runCommand(command, positional, flags, positionals = positional ?
5054
5545
  ...flags["msg-dedup-token"] !== undefined ? { msg_dedup_token: flags["msg-dedup-token"] } : {}
5055
5546
  });
5056
5547
  const reason = params.reason?.trim() ?? "";
5057
- const post = (path, body, retries) => callDataAPI(path, { method: "POST", body: JSON.stringify(body), headers: { "Content-Type": "application/json" } }, retries === undefined ? undefined : { retries });
5058
- let data;
5548
+ let request;
5059
5549
  switch (params.kind) {
5060
5550
  case "problem":
5061
- data = await post(`/problems/${params.target}/feedback`, { action: "dismiss", reason, actor: "moda-cli" }, 0);
5551
+ request = { method: "POST", path: `/problems/${params.target}/feedback`, body: { action: "dismiss", reason, actor: "moda-cli" }, retries: 0 };
5062
5552
  break;
5063
5553
  case "problem-detection":
5064
- data = await post(`/problems/${params.target}/detection-reviews`, {
5065
- conversation_id: params.conversation_id,
5066
- attribution_id: params.attribution_id,
5067
- verdict: params.undo ? "clear" : "false_positive",
5068
- ...params.undo ? {} : { note: reason },
5069
- request_nonce: randomUUID2()
5070
- });
5554
+ request = {
5555
+ method: "POST",
5556
+ path: `/problems/${params.target}/detection-reviews`,
5557
+ body: {
5558
+ conversation_id: params.conversation_id,
5559
+ attribution_id: params.attribution_id,
5560
+ verdict: params.undo ? "clear" : "false_positive",
5561
+ ...params.undo ? {} : { note: reason }
5562
+ },
5563
+ nonce: true
5564
+ };
5071
5565
  break;
5072
5566
  case "custom-signal":
5073
- if (params.undo) {
5074
- data = await callDataAPI(`/custom-signals/${encodeURIComponent(params.target)}/labels/${encodeURIComponent(params.conversation_id ?? "")}`, { method: "DELETE" }, { retries: 0 });
5075
- break;
5076
- }
5077
- data = await post(`/custom-signals/${encodeURIComponent(params.target)}/labels`, {
5078
- conversation_id: params.conversation_id,
5079
- label: "does_not_fit",
5080
- note: reason,
5081
- ...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
5082
- ...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
5083
- }, 0);
5567
+ request = params.undo ? {
5568
+ method: "DELETE",
5569
+ path: `/custom-signals/${encodeURIComponent(params.target)}/labels/${encodeURIComponent(params.conversation_id ?? "")}`,
5570
+ retries: 0
5571
+ } : {
5572
+ method: "POST",
5573
+ path: `/custom-signals/${encodeURIComponent(params.target)}/labels`,
5574
+ body: {
5575
+ conversation_id: params.conversation_id,
5576
+ label: "does_not_fit",
5577
+ note: reason,
5578
+ ...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
5579
+ ...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
5580
+ },
5581
+ retries: 0
5582
+ };
5084
5583
  break;
5085
5584
  default: {
5086
5585
  const apiKind = { quote: "frustration_quote", tool: "tool_call", laziness: "laziness", hallucination: "hallucination" }[params.kind];
5087
- data = await post("/false-positives", {
5088
- kind: apiKind,
5089
- verdict: params.undo ? "clear" : "false_positive",
5090
- ...params.undo ? {} : { note: reason },
5091
- conversation_id: params.target,
5092
- ...params.kind === "tool" ? {
5093
- tool_name: params.tool,
5094
- ...params.tool_use_id ? { tool_use_id: params.tool_use_id } : { msg_index: params.msg_index }
5095
- } : {},
5096
- ...params.kind === "quote" ? { family: params.family, turn: params.turn, quote: params.quote } : {},
5097
- request_nonce: randomUUID2()
5098
- });
5586
+ request = {
5587
+ method: "POST",
5588
+ path: "/false-positives",
5589
+ body: {
5590
+ kind: apiKind,
5591
+ verdict: params.undo ? "clear" : "false_positive",
5592
+ ...params.undo ? {} : { note: reason },
5593
+ conversation_id: params.target,
5594
+ ...params.kind === "tool" ? {
5595
+ tool_name: params.tool,
5596
+ ...params.tool_use_id ? { tool_use_id: params.tool_use_id } : { msg_index: params.msg_index }
5597
+ } : {},
5598
+ ...params.kind === "quote" ? { family: params.family, turn: params.turn, quote: params.quote } : {}
5599
+ },
5600
+ nonce: true
5601
+ };
5099
5602
  }
5100
5603
  }
5604
+ if (context.dryRun) {
5605
+ writeDryRun(context, {
5606
+ plane: "data",
5607
+ method: request.method,
5608
+ path: request.path,
5609
+ ...request.body ? { body: request.body } : {},
5610
+ ...request.nonce ? { generatedFields: ["request_nonce"] } : {}
5611
+ });
5612
+ break;
5613
+ }
5614
+ const sentBody = request.body && request.nonce ? { ...request.body, request_nonce: randomUUID2() } : request.body;
5615
+ const data = await callDataAPI(request.path, {
5616
+ method: request.method,
5617
+ ...sentBody ? { body: JSON.stringify(sentBody), headers: { "Content-Type": "application/json" } } : {}
5618
+ }, request.retries === undefined ? undefined : { retries: request.retries });
5101
5619
  context.output.writeData(data);
5102
5620
  break;
5103
5621
  }
@@ -5125,12 +5643,13 @@ async function runCommand(command, positional, flags, positionals = positional ?
5125
5643
  context.output.writeData(data2);
5126
5644
  break;
5127
5645
  }
5646
+ rejectInapplicableFlags(command, { kind: flags.kind, limit: flags.limit, all: flags.all === "false" ? undefined : flags.all }, ["kind", "limit", "all"], "to one trace's marks (every mark on the trace is listed); they filter the workspace list");
5128
5647
  const data = await callDataAPI(`/conversations/${encodeURIComponent(positional)}/false-positives`);
5129
5648
  context.output.writeData(data);
5130
5649
  break;
5131
5650
  }
5132
5651
  case "emotions": {
5133
- const args = withStringIdFlags(flagsToArgs(flags), flags);
5652
+ const args = flagsToArgs(flags);
5134
5653
  const params = parseSpecFlags("emotions", args, command);
5135
5654
  const query2 = new URLSearchParams;
5136
5655
  if (params.days_back)
@@ -5158,9 +5677,6 @@ async function runCommand(command, positional, flags, positionals = positional ?
5158
5677
  }
5159
5678
  case "laziness": {
5160
5679
  const args = flagsToArgs(flags);
5161
- if (flags["conversation-id"] !== undefined) {
5162
- args.conversation_id = flags["conversation-id"];
5163
- }
5164
5680
  const params = parseSpecFlags("laziness", args, command);
5165
5681
  const query2 = new URLSearchParams;
5166
5682
  if (params.days_back)
@@ -5180,9 +5696,6 @@ async function runCommand(command, positional, flags, positionals = positional ?
5180
5696
  }
5181
5697
  case "hallucinations": {
5182
5698
  const args = flagsToArgs(flags);
5183
- if (flags["conversation-id"] !== undefined) {
5184
- args.conversation_id = flags["conversation-id"];
5185
- }
5186
5699
  const params = parseSpecFlags("hallucinations", args, command);
5187
5700
  const query2 = new URLSearchParams;
5188
5701
  if (params.days_back)
@@ -5223,17 +5736,22 @@ async function runCommand(command, positional, flags, positionals = positional ?
5223
5736
  }
5224
5737
  case "feedback": {
5225
5738
  const { category, severity, note, refs, warnings } = buildFeedbackRequest(positionals, flags, context.bareFlags);
5739
+ const feedbackBody = {
5740
+ category,
5741
+ severity,
5742
+ source: "cli",
5743
+ agent: detectCallerAgent(process.env),
5744
+ cli_version: VERSION,
5745
+ note,
5746
+ ...Object.keys(refs).length > 0 ? { refs } : {}
5747
+ };
5748
+ if (context.dryRun) {
5749
+ writeDryRun(context, { plane: "data", method: "POST", path: "/feedback", body: feedbackBody });
5750
+ break;
5751
+ }
5226
5752
  const data = await callDataAPI("/feedback", {
5227
5753
  method: "POST",
5228
- body: JSON.stringify({
5229
- category,
5230
- severity,
5231
- source: "cli",
5232
- agent: detectCallerAgent(process.env),
5233
- cli_version: VERSION,
5234
- note,
5235
- ...Object.keys(refs).length > 0 ? { refs } : {}
5236
- })
5754
+ body: JSON.stringify(feedbackBody)
5237
5755
  }, { retries: 0 });
5238
5756
  context.output.writeData({ submitted: true, category, severity, ...asRecord(data) ?? {} }, {
5239
5757
  summary: "Feedback submitted. Thank you for helping us improve!",
@@ -5382,7 +5900,7 @@ var commandRegistry = createCommandRegistry([
5382
5900
  resetApiRequestCountBeforeRun: true,
5383
5901
  telemetry: "result-and-error",
5384
5902
  handler: async (context) => {
5385
- const { runInit } = await import("./index-4b1ybz51.js");
5903
+ const { runInit } = await import("./index-91bpc8g8.js");
5386
5904
  if (context.outputMode === "agent-stream") {
5387
5905
  context.output.writeEvent({
5388
5906
  event: "started",
@@ -5432,7 +5950,7 @@ var commandRegistry = createCommandRegistry([
5432
5950
  resetApiRequestCountBeforeRun: true,
5433
5951
  telemetry: "result-and-error",
5434
5952
  handler: async (context) => {
5435
- const { runProvision } = await import("./provision-q86phh4a.js");
5953
+ const { runProvision } = await import("./provision-bc3h68e1.js");
5436
5954
  return runProvision(context, {
5437
5955
  resumeCommand: buildResumeCommand("provision", context.rawFlags)
5438
5956
  });
@@ -6454,6 +6972,9 @@ ${error.suggestions.map((s) => ` ${s}`).join(`
6454
6972
  `)}
6455
6973
  `);
6456
6974
  }
6975
+ } else if (error instanceof ApiError && error.statusCode === 404) {
6976
+ safeWriteStderr2(`${errorToAgentError(error).message}
6977
+ `);
6457
6978
  } else if (error instanceof ApiError) {
6458
6979
  safeWriteStderr2(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}
6459
6980
  `);
@@ -6506,6 +7027,46 @@ function printAgentCliError(command, error, mode = "agent") {
6506
7027
  });
6507
7028
  output.writeError(error);
6508
7029
  }
7030
+ function flagNamesInLabel(label) {
7031
+ return [...label.matchAll(/--([A-Za-z0-9][A-Za-z0-9-]*)/g)].map((match) => match[1]);
7032
+ }
7033
+ function specFlagNamesDeep(spec) {
7034
+ if (!spec)
7035
+ return [];
7036
+ return [...specFlagNames(spec), ...Object.values(spec.subcommands ?? {}).flatMap((sub) => specFlagNames(sub))];
7037
+ }
7038
+ function telemetryAcceptedFlags(definition, route) {
7039
+ const names = new Set([...GLOBAL_FLAG_NAMES, ...GLOBAL_BOOLEAN_FLAGS]);
7040
+ const add = (list) => {
7041
+ for (const name of list)
7042
+ names.add(name);
7043
+ };
7044
+ if (definition) {
7045
+ add(specFlagNamesDeep(flagSpecFor(definition.name)));
7046
+ for (const flag of definition.documentedFlags ?? [])
7047
+ add(flagNamesInLabel(flag.name));
7048
+ for (const sub of definition.subcommands ?? [])
7049
+ for (const flag of sub.flags ?? [])
7050
+ add(flagNamesInLabel(flag.name));
7051
+ }
7052
+ if (route) {
7053
+ add(route.allowedFlags ?? []);
7054
+ add(Object.keys(route.flagAliases ?? {}));
7055
+ add(specFlagNamesDeep(leafFlagSpec(route.leaf)));
7056
+ }
7057
+ return names;
7058
+ }
7059
+ var knownFlagNames;
7060
+ function telemetryKnownFlags() {
7061
+ if (!knownFlagNames) {
7062
+ knownFlagNames = new Set(RAW_BOOLEAN_FLAGS);
7063
+ for (const definition of commandRegistry.all()) {
7064
+ for (const name of telemetryAcceptedFlags(definition, undefined))
7065
+ knownFlagNames.add(name);
7066
+ }
7067
+ }
7068
+ return knownFlagNames;
7069
+ }
6509
7070
  function existingFlagProfile(flags) {
6510
7071
  const name = flags.profile;
6511
7072
  if (!name || name === "true")
@@ -6548,6 +7109,50 @@ function assertRoutedFlags(route, flags) {
6548
7109
  const args = Object.fromEntries(Object.keys(flags).filter((flag) => !aliases.has(flag.replace(/_/g, "-"))).map((flag) => [flag.replace(/-/g, "_"), true]));
6549
7110
  parseCommandFlags(shape.passthrough(), args, route.label, route.flagAliases ? { flagAliases: route.flagAliases } : {});
6550
7111
  }
7112
+ var EMPTY_VALUE_ALLOWED = {
7113
+ context: new Set(["msg-index", "message-index"])
7114
+ };
7115
+ var EXAMPLE_CLEARABLE_FLAGS = new Set(["note", "blurb"]);
7116
+ function assertFlagValues(handler, positionals, typedFlags, bareFlags, route) {
7117
+ const name = route?.handler ?? handler;
7118
+ const spec = flagSpecFor(name);
7119
+ if (spec?.idRefFlags)
7120
+ return;
7121
+ const bare = new Set(bareFlags ?? []);
7122
+ const empty = new Set(Object.keys(typedFlags).filter((key) => String(typedFlags[key]).trim() === ""));
7123
+ if (bare.size === 0 && empty.size === 0)
7124
+ return;
7125
+ const handlerPositionals = route?.positionals ?? positionals;
7126
+ const managementSubcommand = name === "signals" && SIGNALS_MANAGEMENT_SUBCOMMANDS.has(handlerPositionals[0] ?? "");
7127
+ const examplesUpdate = name === "signals" && handlerPositionals[0] === "examples" && handlerPositionals.includes("update");
7128
+ const valued = new Set;
7129
+ const switches = new Set(RAW_BOOLEAN_FLAGS);
7130
+ for (const entry of specsOf(name)) {
7131
+ for (const flag of describeSpecFlags(entry))
7132
+ (flag.type === "boolean" ? switches : valued).add(flag.name.slice(2));
7133
+ for (const positional of entry.positionals ?? [])
7134
+ if (positional.key)
7135
+ valued.add(positional.key.replace(/_/g, "-"));
7136
+ }
7137
+ for (const flag of route?.allowedFlags ?? [])
7138
+ if (!switches.has(flag))
7139
+ valued.add(flag);
7140
+ for (const [key, value] of Object.entries(typedFlags)) {
7141
+ const isBare = bare.has(key) && value === "true";
7142
+ if (!isBare && !empty.has(key))
7143
+ continue;
7144
+ if (!isBare && EMPTY_VALUE_ALLOWED[name]?.has(key))
7145
+ continue;
7146
+ if (!isBare && examplesUpdate && EXAMPLE_CLEARABLE_FLAGS.has(key))
7147
+ continue;
7148
+ const target = route?.flagAliases?.[key] ?? key;
7149
+ const takesValue = valued.has(key) || valued.has(target) || managementSubcommand && !switches.has(key) && !GLOBAL_FLAG_NAMES.has(key);
7150
+ if (!takesValue || switches.has(key))
7151
+ continue;
7152
+ const shown = canonicalCommandText(`moda ${route?.leaf.path ?? handler}`);
7153
+ throw new CliInputError(`--${key} needs a value, so nothing was sent.`, `Pass it as --${key}=VALUE (an empty shell variable leaves the flag without one). See '${shown} --help'.`, `missing_value:${key}`, { field: `--${key}`, suggestedCommands: [`${shown} --help`] });
7154
+ }
7155
+ }
6551
7156
  function renameErrorCommands(error, renames) {
6552
7157
  if (error instanceof CliInputError) {
6553
7158
  const details2 = error.details;
@@ -6565,6 +7170,7 @@ function renameErrorCommands(error, renames) {
6565
7170
  }
6566
7171
  async function dispatchParsedCommandInner(typedParsed, startedAt) {
6567
7172
  const { command, flags } = typedParsed;
7173
+ canonicalizeFlagKeys(flags, typedParsed.bareFlags);
6568
7174
  const switchError = normalizeBooleanFlags(flags);
6569
7175
  const typedFlags = stripUpdateCheckFlag(stripTelemetryFlag(flags));
6570
7176
  const telemetryDisabled = isTelemetryDisabled(flags);
@@ -6577,6 +7183,15 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
6577
7183
  if (command && printTreeHelp(typedParsed))
6578
7184
  return 0;
6579
7185
  const helpTarget = command && command !== "--help" && command !== "-h" ? commandRegistry.find(command) : undefined;
7186
+ if (!helpTarget && command && command !== "--help" && command !== "-h") {
7187
+ const mode = resolveOutputMode(typedFlags, { isTTY: process.stdout.isTTY, env: process.env });
7188
+ const error = unknownCommandError(command);
7189
+ if (mode === "agent" || mode === "agent-stream" || mode === "json")
7190
+ printAgentCliError(command, error, mode);
7191
+ else
7192
+ printCliError(error);
7193
+ return 1;
7194
+ }
6580
7195
  if (helpTarget) {
6581
7196
  const subcommandName = typedParsed.positional;
6582
7197
  const subcommandEntry = subcommandName ? helpTarget.subcommands?.find((entry) => entry.name === subcommandName) : undefined;
@@ -6624,6 +7239,8 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
6624
7239
  if (!handlerTelemetry)
6625
7240
  return;
6626
7241
  const telemetryProfile = existingFlagProfile(typedFlags);
7242
+ if (typedFlags.profile !== undefined && telemetryProfile === undefined)
7243
+ return;
6627
7244
  const adoption = buildSearchAdoptionTelemetry({
6628
7245
  command: handlerTelemetry,
6629
7246
  positional: parsed.positional,
@@ -6651,12 +7268,14 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
6651
7268
  durationMs: Date.now() - startedAt,
6652
7269
  apiRequestCount: input.apiRequestCount,
6653
7270
  adoption,
6654
- ...identity2?.legacyAlias ? { legacyAlias: identity2.legacyAlias } : {}
7271
+ ...identity2?.legacyAlias ? { legacyAlias: identity2.legacyAlias } : {},
7272
+ acceptedFlags: telemetryAcceptedFlags(definition2, route),
7273
+ knownFlags: telemetryKnownFlags()
6655
7274
  });
6656
7275
  };
6657
7276
  const failEarly = (error, commandDefinition, identity2) => {
6658
7277
  const mode = outputModeFor();
6659
- const telemetryOptOut = telemetryDisabled || flags["no-telemetry"] !== undefined && flags["no-telemetry"] !== "false";
7278
+ const telemetryOptOut = telemetryDisabled || optOutFlagSet(flags, "no-telemetry", false);
6660
7279
  const reports = commandDefinition ? commandDefinition.telemetry === "result-and-error" : identity2 !== undefined;
6661
7280
  if (reports && !telemetryOptOut) {
6662
7281
  reportUsage({
@@ -6691,14 +7310,14 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
6691
7310
  }
6692
7311
  if (switchError)
6693
7312
  return failEarly(switchError, definition, identity);
6694
- if (route) {
6695
- try {
7313
+ try {
7314
+ if (route)
6696
7315
  assertRoutedFlags(route, typedFlags);
6697
- } catch (error) {
6698
- if (!(error instanceof CliInputError))
6699
- throw error;
6700
- return failEarly(error, definition, identity);
6701
- }
7316
+ assertFlagValues(definition.name, parsed.positionals, typedFlags, typedParsed.bareFlags, route);
7317
+ } catch (error) {
7318
+ if (!(error instanceof CliInputError))
7319
+ throw error;
7320
+ return failEarly(error, definition, identity);
6702
7321
  }
6703
7322
  if (commandFlags.profile === "true" || commandFlags.profile === "") {
6704
7323
  return failEarly(new CliInputError("--profile needs a value, e.g. --profile=prod.", "List profiles with 'moda profiles list'.", "missing_value:profile"), definition, identity);
@@ -6708,7 +7327,7 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
6708
7327
  const known = Object.keys(loadProfileConfig({ env: process.env }).profiles);
6709
7328
  if (!known.includes(requestedProfile)) {
6710
7329
  const source = commandFlags.profile ? `--profile=${requestedProfile}` : `MODA_PROFILE=${requestedProfile}`;
6711
- return failEarly(new CliInputError(`Moda profile '${requestedProfile}' does not exist (from ${source}), so nothing was run.`, `Known profiles: ${known.join(", ")}. Create it with 'moda profiles create ${requestedProfile}' or fix the name.`, "unknown_profile"), definition, identity);
7330
+ return failEarly(new CliInputError(`Moda profile '${requestedProfile}' does not exist (from ${source}), so nothing was run.`, `Known profiles: ${known.join(", ")}. Create it with 'moda profiles create ${shellArg(requestedProfile)}' or fix the name.`, "unknown_profile"), definition, identity);
6712
7331
  }
6713
7332
  if (commandFlags.profile)
6714
7333
  process.env.MODA_PROFILE = commandFlags.profile;
@@ -6717,7 +7336,7 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
6717
7336
  commandFlags.agent = "true";
6718
7337
  }
6719
7338
  if (!updateCheckDisabled) {
6720
- startUpdateCheck();
7339
+ startUpdateCheck({}, Date.now(), { detached: outputModeFor() !== "human" });
6721
7340
  setAgentUpdateNotice(() => {
6722
7341
  const latest = getAvailableUpdate();
6723
7342
  return latest ? `moda-cli ${VERSION} is out of date (latest ${latest}); commands and flags may differ from the docs. Update: npm install -g @moda-ai/cli@latest` : undefined;
@@ -6857,6 +7476,7 @@ if (isMain) {
6857
7476
  }
6858
7477
  export {
6859
7478
  unknownCommandError,
7479
+ trimContextWindow,
6860
7480
  telemetrySubcommandNames,
6861
7481
  telemetryCommandName,
6862
7482
  stripLoneSurrogates,
@@ -6876,10 +7496,14 @@ export {
6876
7496
  dispatchParsedCommand,
6877
7497
  detectCallerAgent,
6878
7498
  describeLinearLink,
7499
+ commandSwitchFlags,
6879
7500
  commandRegistry,
7501
+ coerceNumericFlags,
6880
7502
  cliValidationReason,
6881
7503
  classifyCliError,
6882
7504
  capProblemsBlocks,
7505
+ canonicalizeFlagKeys,
7506
+ canonicalFlagKey,
6883
7507
  buildResumeCommand,
6884
7508
  auditCapStatus,
6885
7509
  TELEMETRY_EXEMPT_COMMANDS,