@moda-ai/cli 1.55.22 → 1.55.24

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 (40) hide show
  1. package/dist/{agent-mj622k8s.js → agent-b4tdzy9q.js} +8 -9
  2. package/dist/{cli-9x9adrtj.js → cli-0wr3483h.js} +1 -1
  3. package/dist/{cli-a5c9te95.js → cli-7xc585hc.js} +5 -5
  4. package/dist/{cli-rqk6f6n6.js → cli-9btx12wp.js} +24 -5
  5. package/dist/cli-9hc5bg2r.js +61 -0
  6. package/dist/{cli-cehskjde.js → cli-dn91fb56.js} +13 -15
  7. package/dist/{cli-5ke3xe6y.js → cli-dve50x7p.js} +258 -28
  8. package/dist/{cli-569hj0bj.js → cli-qvg26twp.js} +5 -4
  9. package/dist/{cli-033n2mpb.js → cli-qycsmxev.js} +6 -9
  10. package/dist/{cli-0ef72tqx.js → cli-rt26sbz7.js} +14 -14
  11. package/dist/{cli-pc5cw7mx.js → cli-vg88mg20.js} +5 -5
  12. package/dist/{cli-wf7gfa1e.js → cli-y2ty9vtv.js} +8 -10
  13. package/dist/cli.js +1310 -346
  14. package/dist/{doctor-mx6jf435.js → doctor-3r19ry92.js} +8 -9
  15. package/dist/{fix-9fmy6p2x.js → fix-exvxzvtz.js} +6 -7
  16. package/dist/{harness-github-actions-c826bfxs.js → harness-github-actions-atc5bbkj.js} +5 -6
  17. package/dist/{harness-xvg6ws6x.js → harness-vzy7n7ea.js} +5 -6
  18. package/dist/{index-1wbjz3jc.js → index-e1pf1qz2.js} +17 -17
  19. package/dist/{production-intelligence-dbe9dmc5.js → production-intelligence-c8e0xw3n.js} +17 -16
  20. package/dist/{profile-commands-kmmd9y86.js → profile-commands-6e2dgv72.js} +7 -48
  21. package/dist/prompt-source-mcp.js +5 -6
  22. package/dist/{prompts-1tkhrxkq.js → prompts-cvzt994j.js} +6 -7
  23. package/dist/{provision-wzttap58.js → provision-q86phh4a.js} +5 -4
  24. package/dist/{registry-atqyb5fa.js → registry-s2vdsfw8.js} +3 -4
  25. package/dist/{skills-gejdvjak.js → skills-tadh1jss.js} +5 -6
  26. package/dist/{sync-57ercmh9.js → sync-g8wtecz1.js} +7 -8
  27. package/package.json +1 -1
  28. package/skills/integration/index.json +2 -2
  29. package/skills/moda-cli/SKILL.md +31 -29
  30. package/skills/moda-cli/references/commands.md +22 -22
  31. package/skills/moda-cli/references/dashboard-parity.md +4 -4
  32. package/skills/moda-cli/references/detections.md +10 -10
  33. package/skills/moda-cli/references/feedback.md +2 -2
  34. package/skills/moda-cli/references/output-protocol.md +2 -2
  35. package/skills/moda-cli/references/production-intelligence.md +10 -10
  36. package/skills/moda-cli/references/recipes.md +14 -14
  37. package/skills/moda-cli/references/search-and-traces.md +32 -32
  38. package/skills/moda-cli/references/signals-and-false-positives.md +29 -29
  39. package/skills/moda-cli/references/troubleshooting.md +4 -4
  40. package/dist/cli-7ea02wca.js +0 -30
package/dist/cli.js CHANGED
@@ -5,22 +5,23 @@ import {
5
5
  import {
6
6
  createCommandContext,
7
7
  parseBooleanFlagValue
8
- } from "./cli-569hj0bj.js";
8
+ } from "./cli-qvg26twp.js";
9
9
  import {
10
10
  AuditSchema,
11
11
  COMMAND_FLAG_SPECS,
12
12
  DETECTION_VERDICTS,
13
13
  DetectionReviewSchema,
14
+ EMOTION_FAMILIES,
14
15
  FALSE_POSITIVE_KINDS,
15
16
  FEEDBACK_USAGE,
16
17
  FalsePositiveSchema,
18
+ LAZINESS_FAMILIES,
17
19
  PROBLEM_FEEDBACK_ACTIONS,
18
20
  ProblemFeedbackSchema,
19
21
  ProblemImpactSchema,
20
22
  ProblemLinearFileSchema,
21
23
  ProblemsRemainderSchema,
22
24
  SIGNALS_MANAGEMENT_SUBCOMMANDS,
23
- SIGNAL_LABELS,
24
25
  SignalLabelSchema,
25
26
  StepScoresSchema,
26
27
  UUID_RE,
@@ -43,17 +44,12 @@ import {
43
44
  specFlagNames,
44
45
  subcommandHelpFlags,
45
46
  unknownSignalsSubcommandError
46
- } from "./cli-cehskjde.js";
47
+ } from "./cli-dn91fb56.js";
47
48
  import {
48
49
  ApiError,
49
50
  callControlAPI,
50
51
  callDataAPI
51
- } from "./cli-rqk6f6n6.js";
52
- import {
53
- apiKeyHeaders,
54
- isPlatformApiKey,
55
- setPlatformTenantOverride
56
- } from "./cli-7ea02wca.js";
52
+ } from "./cli-9btx12wp.js";
57
53
  import {
58
54
  AGENT_ERROR_CODES,
59
55
  BATCH_CONCURRENCY,
@@ -70,15 +66,18 @@ import {
70
66
  EXIT_ERROR,
71
67
  EXIT_INPUT_REQUIRED,
72
68
  EXIT_OK,
69
+ LEGACY_NAMES,
73
70
  MODA_BASE_URL,
74
71
  VERSION,
75
72
  agentEnvelopeFits,
76
73
  agentMaxOutputBytes,
77
74
  apiErrorStatus,
75
+ apiKeyHeaders,
78
76
  asNumber,
79
77
  asRecord,
80
78
  asString,
81
79
  attachInputDetails,
80
+ canonicalCommandText,
82
81
  collectBatchTargets,
83
82
  compactProblemDossier,
84
83
  contextTarget,
@@ -89,8 +88,10 @@ import {
89
88
  frustrationsFromEmotions,
90
89
  getApiRequestCount,
91
90
  getModaDir,
91
+ inputDetailsOf,
92
92
  isApiLikeError,
93
93
  isNetworkLikeError,
94
+ isPlatformApiKey,
94
95
  loadProfileConfig,
95
96
  mapWithConcurrency,
96
97
  parseBatchTarget,
@@ -102,13 +103,14 @@ import {
102
103
  safeWriteStderr,
103
104
  safeWriteStdout,
104
105
  setAgentUpdateNotice,
106
+ setPlatformTenantOverride,
105
107
  trimToolFailureConversations,
106
108
  validateConfig,
107
109
  wasOutputCancelled,
108
110
  withFrustrationAnchors,
109
111
  withToolFailureAnchors,
110
112
  withoutDetectionWindows
111
- } from "./cli-5ke3xe6y.js";
113
+ } from "./cli-dve50x7p.js";
112
114
  import {
113
115
  __require
114
116
  } from "./cli-0v6na3yp.js";
@@ -544,7 +546,10 @@ function buildCliUsageRequest(input) {
544
546
  node_version: process.versions.node,
545
547
  platform: process.platform,
546
548
  arch: process.arch,
547
- flags_json: { names: sanitizedFlagNames(input.flags) },
549
+ flags_json: {
550
+ names: sanitizedFlagNames(input.flags),
551
+ ...((alias) => alias ? { legacy_alias: alias } : {})(sanitizeReasonCode(input.legacyAlias?.replace(/ /g, ":"), 64))
552
+ },
548
553
  ...serializeAdoption(input.adoption)
549
554
  })
550
555
  };
@@ -656,6 +661,540 @@ function printUpdateNotice(options = {}) {
656
661
  if (notice)
657
662
  console.error(notice);
658
663
  }
664
+ // src/command-tree.ts
665
+ var CANONICAL_LEAVES = [
666
+ {
667
+ noun: "traces",
668
+ verb: "list",
669
+ path: "traces list",
670
+ args: "",
671
+ handler: "traces",
672
+ summary: "List and filter traces (agent runs); --search ranks whole traces",
673
+ examples: ['moda traces list --search="error" --limit=5', 'moda traces list --world-state="enterprise" --outcome=positive']
674
+ },
675
+ {
676
+ noun: "traces",
677
+ verb: "search",
678
+ path: "traces search",
679
+ args: '"<query>"',
680
+ handler: "search",
681
+ summary: "Search trace messages (keyword, semantic, or hybrid)",
682
+ examples: ['moda traces search "billing error"', 'moda traces search "refund flow" --mode=semantic --time-range=7d --limit=10']
683
+ },
684
+ {
685
+ noun: "traces",
686
+ verb: "get",
687
+ path: "traces get",
688
+ args: "<conversation_id>[@N]...",
689
+ handler: "context",
690
+ summary: "Messages around a turn, or the whole trace (--all); up to 25 ids per call",
691
+ examples: [
692
+ "moda traces get <conversation_id> --msg-index=40 --window=3",
693
+ "moda traces get <conversation_id> --all",
694
+ "moda traces get <id>@12 <id2>@3 --window=2 # batch: up to 25 targets (or --ids-from=-|<file>)"
695
+ ]
696
+ },
697
+ {
698
+ noun: "traces",
699
+ verb: "state",
700
+ path: "traces state",
701
+ args: "<conversation_id>",
702
+ handler: "world-state",
703
+ summary: "A trace's world state (slots, threads, events)",
704
+ examples: ["moda traces state <conversation_id> --summary-only", "moda traces state <conversation_id> --snapshot --msg-index=12"]
705
+ },
706
+ {
707
+ noun: "traces",
708
+ verb: "spans",
709
+ path: "traces spans",
710
+ args: "<conversation_id|trace_id>...",
711
+ handler: "audit",
712
+ summary: "Raw span audit (hierarchy, orphans, duplicates); up to 25 ids per call",
713
+ examples: ["moda traces spans <conversation_id>", "moda traces spans <trace_id> --kind=trace --include-bodies"]
714
+ },
715
+ {
716
+ noun: "problems",
717
+ verb: "list",
718
+ path: "problems list",
719
+ args: "",
720
+ handler: "problems",
721
+ summary: "Rank cross-signal Problems by root cause (what to fix first)",
722
+ examples: ["moda problems list", "moda problems list --days-back=7 --limit=10"]
723
+ },
724
+ {
725
+ noun: "problems",
726
+ verb: "get",
727
+ path: "problems get",
728
+ args: "<problem_id>",
729
+ handler: "problem",
730
+ summary: "One Problem: dossier, or its --evidence/--reports/--traces/--feedback pages",
731
+ examples: ["moda problems get <problem_id>", "moda problems get <problem_id> --evidence --limit=10", "moda problems get <problem_id> --traces --family=tool_failure"]
732
+ },
733
+ {
734
+ noun: "problems",
735
+ verb: "unexplained",
736
+ path: "problems unexplained",
737
+ args: "",
738
+ handler: "problems",
739
+ handlerSubcommand: "remainder",
740
+ summary: "Signals no Problem explains yet, grouped by family",
741
+ examples: ["moda problems unexplained --family=emotion --days-back=7"]
742
+ },
743
+ {
744
+ noun: "problems",
745
+ verb: "impact",
746
+ path: "problems impact",
747
+ args: "[<problem_id>]",
748
+ handler: "problem-impact",
749
+ summary: "Impact, denominators, recurrence and evidence for a Problem or use case",
750
+ examples: ["moda problems impact <problem_id> --mode=metrics", "moda problems impact --node-id=node_0 --mode=trend --days-back=30", "moda problems impact --mode=catalog"]
751
+ },
752
+ {
753
+ noun: "problems",
754
+ verb: "feedback",
755
+ path: "problems feedback",
756
+ args: "<problem_id>",
757
+ handler: "problem-feedback",
758
+ summary: "Mark a Problem fixed, dismiss it, rename it, or flag a bad attribution",
759
+ examples: ["moda problems feedback <problem_id> --action=mark_fixed", 'moda problems feedback <problem_id> --action=rename --new-name="Better title"']
760
+ },
761
+ {
762
+ noun: "problems",
763
+ verb: "reopen",
764
+ path: "problems reopen",
765
+ args: "<problem_id>",
766
+ handler: "problem-reopen",
767
+ summary: "Reopen a Problem marked fixed",
768
+ examples: ['moda problems reopen <problem_id> --reason="still happening" --dry-run']
769
+ },
770
+ {
771
+ noun: "problems",
772
+ verb: "linear",
773
+ path: "problems linear",
774
+ args: "<problem_id>",
775
+ handler: "problem-linear",
776
+ summary: "Show the Linear issue linked to a Problem",
777
+ examples: ["moda problems linear <problem_id>"]
778
+ },
779
+ {
780
+ noun: "problems",
781
+ verb: "link",
782
+ path: "problems link",
783
+ args: "<problem_id>",
784
+ handler: "problem-linear-file",
785
+ summary: "File a Problem as a Linear issue, or link an existing one with --issue",
786
+ examples: ["moda problems link <problem_id> --issue=ENG-123", "moda problems link <problem_id> --team-id=<team_id> --priority=2"]
787
+ },
788
+ {
789
+ noun: "detections",
790
+ verb: "list",
791
+ path: "detections list",
792
+ args: "--family=<family>",
793
+ routes: ["emotions", "frustrations", "laziness", "hallucinations", "tool-failures", "signal", "trace-signals"],
794
+ summary: "Detections of one family: emotion[:<f>] frustration laziness[:<f>] hallucination tool_failure custom_signal",
795
+ examples: [
796
+ "moda detections list --family=frustration --limit=10",
797
+ "moda detections list --family=emotion:confusion --days-back=14",
798
+ "moda detections list --family=hallucination --kind=contradicted",
799
+ "moda detections list --family=custom_signal --signal-id=<signal_id>"
800
+ ]
801
+ },
802
+ {
803
+ noun: "detections",
804
+ verb: "tools",
805
+ path: "detections tools",
806
+ args: "[<tool_name>]",
807
+ routes: ["tool-failures", "tool-failure-detail"],
808
+ summary: "Tool failure leaderboard, or one tool's failure examples",
809
+ examples: ["moda detections tools --days-back=14", "moda detections tools <tool_name> --limit=3"]
810
+ },
811
+ {
812
+ noun: "review",
813
+ verb: "",
814
+ path: "review",
815
+ args: "<kind> <target>",
816
+ routes: ["false-positive", "detection-review", "signal-label"],
817
+ summary: "Review a detection: --verdict=false_positive (default), correct, or clear",
818
+ examples: [
819
+ 'moda review tool <conversation_id> --tool=send_invoice --tool-use-id=<id> --reason="retried upstream"',
820
+ "moda review problem-detection <problem_id> --conversation-id=<id> --attribution-id=<uuid> --verdict=correct",
821
+ "moda review custom-signal <signal_id> --conversation-id=<id> --verdict=clear"
822
+ ]
823
+ },
824
+ {
825
+ noun: "reviews",
826
+ verb: "list",
827
+ path: "reviews list",
828
+ args: "[<conversation_id>]",
829
+ routes: ["false-positives", "detection-reviews"],
830
+ summary: "False-positive marks and detection reviews (workspace, or one trace)",
831
+ examples: ["moda reviews list --kind=tool --all", "moda reviews list <conversation_id>", "moda reviews list <conversation_id> --kind=problem-detection"]
832
+ },
833
+ {
834
+ noun: "skills",
835
+ verb: "push",
836
+ path: "skills push",
837
+ args: "",
838
+ handler: "skills",
839
+ handlerSubcommand: "sync",
840
+ summary: "Upload locally authored .claude/skills/**/SKILL.md files to Moda (download: skills pull)",
841
+ examples: ["moda skills push --dry-run", "moda skills push"]
842
+ }
843
+ ];
844
+ var CANONICAL_NOUNS = ["traces", "problems", "detections", "review", "reviews", "skills"];
845
+ var NOUN_SUMMARIES = {
846
+ traces: "Traces (agent runs): list, search, read, world state, raw spans",
847
+ problems: "Problems ranked by root cause, their evidence, impact and lifecycle",
848
+ detections: "Detections of every family, and the tool failure leaderboard",
849
+ review: "Mark a detection a false positive, correct, or clear the mark",
850
+ reviews: "List false-positive marks and detection reviews",
851
+ skills: "Generate, pull, push, and install agent skills"
852
+ };
853
+ function leafFor(noun, verb) {
854
+ if (noun === "review")
855
+ return CANONICAL_LEAVES.find((leaf) => leaf.noun === "review");
856
+ return CANONICAL_LEAVES.find((leaf) => leaf.noun === noun && leaf.verb === verb);
857
+ }
858
+ function leavesOf(noun) {
859
+ return CANONICAL_LEAVES.filter((leaf) => leaf.noun === noun);
860
+ }
861
+ function usePath(entry) {
862
+ return entry.use.replace(/^moda /, "").split(" ").filter((word) => /^[a-z][a-z-]*$/.test(word)).slice(0, entry.canonical.command === "review" ? 1 : 2).join(" ");
863
+ }
864
+ function flagOn(value) {
865
+ return value !== undefined && value !== "false";
866
+ }
867
+ function legacyNameFor(typed, subcommand, flags) {
868
+ const byName = (name) => LEGACY_NAMES.find((entry) => entry.legacy === name);
869
+ if (subcommand !== undefined) {
870
+ const sub = byName(`${typed} ${subcommand}`);
871
+ if (sub)
872
+ return sub;
873
+ }
874
+ if (typed === "signal")
875
+ return flagOn(flags.detections) ? byName("signal --detections") : undefined;
876
+ if (typed === "problems" || typed === "skills" || typed === "sync")
877
+ return;
878
+ return byName(typed);
879
+ }
880
+ function visibleLegacyNames() {
881
+ return LEGACY_NAMES.filter((entry) => !entry.silent);
882
+ }
883
+ function legacyNamesForPath(path) {
884
+ return visibleLegacyNames().filter((entry) => usePath(entry) === path).map((entry) => entry.legacy);
885
+ }
886
+ function deprecationMessage(entry) {
887
+ return `'moda ${entry.legacy}' is a legacy name; use '${entry.use}' (the old name still works).${entry.note ? ` ${entry.note}` : ""}`;
888
+ }
889
+ function canonicalTelemetryForDefault(name, subcommand) {
890
+ if ((name === "traces" || name === "problems") && subcommand === undefined)
891
+ return { command: name, subcommand: "list" };
892
+ return;
893
+ }
894
+ var without = (flags, ...keys) => {
895
+ const copy = { ...flags };
896
+ for (const key of keys) {
897
+ delete copy[key];
898
+ delete copy[key.replace(/-/g, "_")];
899
+ }
900
+ return copy;
901
+ };
902
+ function specFor(command) {
903
+ const spec = flagSpecFor(command);
904
+ if (!spec)
905
+ throw new Error(`No flag spec for routed command '${command}'`);
906
+ return spec;
907
+ }
908
+ function flagNamesOf(command, ...drop) {
909
+ return specFlagNames(specFor(command)).filter((name) => !drop.includes(name));
910
+ }
911
+ function inputError(message, usage, reason, details = {}) {
912
+ return new CliInputError(message, usage, reason, details);
913
+ }
914
+ var DETECTION_FAMILIES = ["emotion", "frustration", "laziness", "hallucination", "tool_failure", "custom_signal"];
915
+ var DETECTION_FAMILY_HANDLERS = {
916
+ emotion: "emotions",
917
+ frustration: "frustrations",
918
+ laziness: "laziness",
919
+ hallucination: "hallucinations",
920
+ tool_failure: "tool-failures",
921
+ custom_signal: "signal"
922
+ };
923
+ var DETECTION_SUBFAMILIES = {
924
+ emotion: EMOTION_FAMILIES,
925
+ laziness: LAZINESS_FAMILIES
926
+ };
927
+ var REVIEW_VERDICTS = ["false_positive", "correct", "clear"];
928
+ function detectionFamilyValues() {
929
+ return DETECTION_FAMILIES.flatMap((family) => [family, ...(DETECTION_SUBFAMILIES[family] ?? []).map((sub) => `${family}:${sub}`)]);
930
+ }
931
+ var DETECTIONS_LIST_USAGE = "Usage: moda detections list --family=emotion[:<family>]|frustration|laziness[:<family>]|hallucination|tool_failure|custom_signal [flags]. See 'moda detections list --help'.";
932
+ function routeDetectionsList(leaf, rest, flags) {
933
+ const raw = flags.family;
934
+ if (raw === undefined || raw === "true" || raw === "") {
935
+ throw inputError("--family is required for 'moda detections list'.", DETECTIONS_LIST_USAGE, "missing_value:family", {
936
+ field: "--family",
937
+ allowed: [...DETECTION_FAMILIES],
938
+ suggestedCommands: ["moda detections list --help"]
939
+ });
940
+ }
941
+ const [base, sub, ...extra] = raw.split(":");
942
+ if (!DETECTION_FAMILIES.includes(base) || extra.length > 0) {
943
+ const didYouMean = closestMatches(raw, detectionFamilyValues(), 2);
944
+ throw inputError(`Unknown detection family '${raw}'.${didYouMean.length ? ` Did you mean ${didYouMean.map((value) => `--family=${value}`).join(" or ")}?` : ""}`, DETECTIONS_LIST_USAGE, "bad_enum:family", { field: "--family", allowed: [...DETECTION_FAMILIES], ...didYouMean.length ? { didYouMean } : {}, suggestedCommands: ["moda detections list --help"] });
945
+ }
946
+ const family = base;
947
+ const subfamilies = DETECTION_SUBFAMILIES[family];
948
+ if (sub !== undefined && (!subfamilies || !subfamilies.includes(sub))) {
949
+ const allowed = subfamilies ? subfamilies.map((value) => `${family}:${value}`) : [family];
950
+ throw inputError(subfamilies ? `Unknown ${family} family '${sub}'.` : `--family=${family} takes no sub-family.`, DETECTIONS_LIST_USAGE, "bad_enum:family", { field: "--family", allowed, didYouMean: closestMatches(raw, allowed, 2), suggestedCommands: [`moda detections list --family=${family} --help`] });
951
+ }
952
+ if (rest.length > 0) {
953
+ throw inputError(`'moda detections list' takes no arguments, but got '${rest.join(" ")}'.`, DETECTIONS_LIST_USAGE, "unexpected_positional");
954
+ }
955
+ const label = `detections list --family=${family}`;
956
+ if (family === "custom_signal") {
957
+ const signalId = flags["signal-id"];
958
+ const traceId = flags["conversation-id"];
959
+ if (signalId === undefined === (traceId === undefined)) {
960
+ throw inputError(signalId === undefined ? "--family=custom_signal needs --signal-id=<signal_id> (one signal's hits) or --conversation-id=<conversation_id> (every signal hit on one trace)." : "Pass --signal-id or --conversation-id with --family=custom_signal, not both.", DETECTIONS_LIST_USAGE, signalId === undefined ? "missing_value:signal-id" : "flag_conflict", { field: "--signal-id", suggestedCommands: ["moda detections list --family=custom_signal --help", "moda signals"] });
961
+ }
962
+ if (signalId !== undefined) {
963
+ return {
964
+ leaf,
965
+ handler: "signal",
966
+ positionals: [signalId],
967
+ label,
968
+ telemetrySubcommand: "list",
969
+ rewriteFlags: (input) => ({ ...without(input, "family", "signal-id"), detections: "true" }),
970
+ allowedFlags: ["family", "signal-id", ...flagNamesOf("signal", "detections")],
971
+ renames: { signal: label }
972
+ };
973
+ }
974
+ return {
975
+ leaf,
976
+ handler: "trace-signals",
977
+ positionals: [traceId],
978
+ label,
979
+ telemetrySubcommand: "list",
980
+ rewriteFlags: (input) => without(input, "family", "conversation-id"),
981
+ allowedFlags: ["family", "conversation-id"],
982
+ renames: { "trace-signals": label }
983
+ };
984
+ }
985
+ const handler = DETECTION_FAMILY_HANDLERS[family];
986
+ return {
987
+ leaf,
988
+ handler,
989
+ positionals: [],
990
+ label,
991
+ telemetrySubcommand: "list",
992
+ rewriteFlags: (input) => ({ ...without(input, "family"), ...sub !== undefined ? { family: sub } : {} }),
993
+ allowedFlags: ["family", ...flagNamesOf(handler, "family")],
994
+ ...specFor(handler).flagAliases ? { flagAliases: specFor(handler).flagAliases } : {},
995
+ renames: { [handler]: label }
996
+ };
997
+ }
998
+ var REVIEW_USAGE = `Usage: moda review <${FALSE_POSITIVE_KINDS.join("|")}> <target> [--verdict=${REVIEW_VERDICTS.join("|")}] [--reason=TEXT]. See 'moda review --help'.`;
999
+ var REVIEW_FLAG_ALIASES = { note: "reason" };
1000
+ function routeReview(leaf, positionals, flags) {
1001
+ const [kind, target, ...extra] = positionals;
1002
+ if (!kind || !FALSE_POSITIVE_KINDS.includes(kind)) {
1003
+ const didYouMean = kind ? closestMatches(kind, FALSE_POSITIVE_KINDS, 2) : [];
1004
+ throw inputError(kind ? `Unknown review kind '${kind}'.${didYouMean.length ? ` Did you mean ${didYouMean.map((value) => `'${value}'`).join(" or ")}?` : ""}` : "<kind> and <target> are required", REVIEW_USAGE, kind ? "bad_enum:kind" : "missing_positional", { field: "<kind>", allowed: [...FALSE_POSITIVE_KINDS], ...didYouMean.length ? { didYouMean } : {}, suggestedCommands: ["moda review --help"] });
1005
+ }
1006
+ if (!target)
1007
+ throw inputError("<kind> and <target> are required", REVIEW_USAGE, "missing_positional", { field: "<target>", suggestedCommands: ["moda review --help"] });
1008
+ if (extra.length > 0)
1009
+ throw inputError(`'moda review' takes <kind> <target>, but also got '${extra.join(" ")}'.`, REVIEW_USAGE, "unexpected_positional");
1010
+ const verdict = flags.verdict ?? "false_positive";
1011
+ if (!REVIEW_VERDICTS.includes(verdict)) {
1012
+ throw inputError(`--verdict must be one of ${REVIEW_VERDICTS.join(", ")}.`, REVIEW_USAGE, "bad_enum:verdict", {
1013
+ field: "--verdict",
1014
+ allowed: [...REVIEW_VERDICTS],
1015
+ didYouMean: closestMatches(verdict, REVIEW_VERDICTS, 1),
1016
+ suggestedCommands: ["moda review --help"]
1017
+ });
1018
+ }
1019
+ const label = `review ${kind}`;
1020
+ const reasonToNote = (input) => {
1021
+ const reason = input.reason ?? input.note;
1022
+ const rest = without(input, "reason", "note", "verdict");
1023
+ return reason !== undefined ? { ...rest, note: reason } : rest;
1024
+ };
1025
+ if (kind === "problem-detection") {
1026
+ return {
1027
+ leaf,
1028
+ handler: "detection-review",
1029
+ positionals: [target],
1030
+ label,
1031
+ telemetrySubcommand: kind,
1032
+ rewriteFlags: (input) => ({ ...reasonToNote(input), verdict }),
1033
+ allowedFlags: ["verdict", ...flagNamesOf("detection-review", "verdict")],
1034
+ flagAliases: { reason: "note" },
1035
+ renames: { "detection-review": label, "false-positive problem-detection": label }
1036
+ };
1037
+ }
1038
+ if (verdict === "correct") {
1039
+ if (kind !== "custom-signal") {
1040
+ throw inputError(`--verdict=correct applies to problem-detection and custom-signal reviews only, not '${kind}'.`, REVIEW_USAGE, "unsupported_verdict", { field: "--verdict", allowed: ["false_positive", "clear"], suggestedCommands: ["moda review --help"] });
1041
+ }
1042
+ return {
1043
+ leaf,
1044
+ handler: "signal-label",
1045
+ positionals: [target],
1046
+ label,
1047
+ telemetrySubcommand: kind,
1048
+ rewriteFlags: (input) => ({ ...reasonToNote(input), label: "fits" }),
1049
+ allowedFlags: ["verdict", ...flagNamesOf("signal-label", "label")],
1050
+ flagAliases: { reason: "note" },
1051
+ renames: { "signal-label": label }
1052
+ };
1053
+ }
1054
+ return {
1055
+ leaf,
1056
+ handler: "false-positive",
1057
+ positionals: [kind, target],
1058
+ label,
1059
+ telemetrySubcommand: kind,
1060
+ rewriteFlags: (input) => {
1061
+ const note = input.note;
1062
+ const rest = without(input, "verdict", "note");
1063
+ return {
1064
+ ...note !== undefined && rest.reason === undefined ? { ...rest, reason: note } : rest,
1065
+ ...verdict === "clear" ? { undo: "true" } : {}
1066
+ };
1067
+ },
1068
+ allowedFlags: ["verdict", ...flagNamesOf("false-positive", "undo")],
1069
+ flagAliases: REVIEW_FLAG_ALIASES,
1070
+ renames: { "false-positive": "review" }
1071
+ };
1072
+ }
1073
+ var REVIEWS_LIST_USAGE = "Usage: moda reviews list [<conversation_id>] [--kind=quote|tool|laziness|hallucination|custom-signal|problem-detection] [--all] [--limit=N]. See 'moda reviews list --help'.";
1074
+ function routeReviewsList(leaf, rest, flags) {
1075
+ if (rest.length > 1)
1076
+ throw inputError(`'moda reviews list' takes at most one <conversation_id>, but got '${rest.join(" ")}'.`, REVIEWS_LIST_USAGE, "unexpected_positional");
1077
+ if (flags.kind === "problem-detection") {
1078
+ if (!rest[0]) {
1079
+ throw inputError("--kind=problem-detection needs a <conversation_id>: detection reviews are read per trace. Nothing was sent.", REVIEWS_LIST_USAGE, "missing_positional", { field: "<conversation_id>", suggestedCommands: ["moda reviews list --help"] });
1080
+ }
1081
+ return {
1082
+ leaf,
1083
+ handler: "detection-reviews",
1084
+ positionals: [rest[0]],
1085
+ label: "reviews list --kind=problem-detection",
1086
+ telemetrySubcommand: "list",
1087
+ rewriteFlags: (input) => without(input, "kind"),
1088
+ allowedFlags: ["kind", ...flagNamesOf("detection-reviews")],
1089
+ renames: { "detection-reviews": "reviews list --kind=problem-detection" }
1090
+ };
1091
+ }
1092
+ return {
1093
+ leaf,
1094
+ handler: "false-positives",
1095
+ positionals: rest,
1096
+ label: "reviews list",
1097
+ telemetrySubcommand: "list",
1098
+ rewriteFlags: (input) => input,
1099
+ allowedFlags: flagNamesOf("false-positives"),
1100
+ renames: { "false-positives": "reviews list", "detection-reviews": "reviews list --kind=problem-detection" }
1101
+ };
1102
+ }
1103
+ function routeDetectionsTools(leaf, rest) {
1104
+ if (rest.length > 1) {
1105
+ throw inputError(`'moda detections tools' takes at most one <tool_name>, but got '${rest.join(" ")}'.`, "Usage: moda detections tools [<tool_name>] [flags]", "unexpected_positional");
1106
+ }
1107
+ const handler = rest[0] === undefined ? "tool-failures" : "tool-failure-detail";
1108
+ return {
1109
+ leaf,
1110
+ handler,
1111
+ positionals: rest,
1112
+ label: "detections tools",
1113
+ telemetrySubcommand: "tools",
1114
+ rewriteFlags: (input) => input,
1115
+ renames: { [handler]: "detections tools" }
1116
+ };
1117
+ }
1118
+ function routeProblemsImpact(leaf, rest, flags) {
1119
+ if (rest.length > 1) {
1120
+ throw inputError(`'moda problems impact' takes at most one <problem_id>, but got '${rest.join(" ")}'.`, "Usage: moda problems impact [<problem_id>] [flags]", "unexpected_positional");
1121
+ }
1122
+ if (rest[0] !== undefined && flags["problem-id"] !== undefined && flags["problem-id"] !== rest[0]) {
1123
+ throw inputError("Pass the problem id once: as <problem_id> or --problem-id, not both.", "Usage: moda problems impact [<problem_id>] [flags]", "flag_conflict", { field: "--problem-id" });
1124
+ }
1125
+ const problemId = rest[0];
1126
+ return {
1127
+ leaf,
1128
+ handler: "problem-impact",
1129
+ positionals: [],
1130
+ label: "problems impact",
1131
+ telemetrySubcommand: "impact",
1132
+ rewriteFlags: (input) => problemId !== undefined ? { ...input, "problem-id": problemId } : input,
1133
+ renames: { "problem-impact": "problems impact" }
1134
+ };
1135
+ }
1136
+ function routeCanonical(command, positionals, flags) {
1137
+ if (command === "review")
1138
+ return routeReview(leafFor("review", undefined), positionals, flags);
1139
+ if (!CANONICAL_NOUNS.includes(command))
1140
+ return;
1141
+ const [verb, ...rest] = positionals;
1142
+ const leaf = verb !== undefined ? leafFor(command, verb) : undefined;
1143
+ if (!leaf) {
1144
+ if (command === "traces" || command === "problems" || command === "skills")
1145
+ return;
1146
+ const verbs = leavesOf(command).map((entry) => entry.verb);
1147
+ const didYouMean = verb ? closestMatches(verb, verbs, 2) : [];
1148
+ throw inputError(verb ? `Unknown ${command} subcommand '${verb}'.${didYouMean.length ? ` Did you mean ${didYouMean.map((name) => `'${name}'`).join(" or ")}?` : ""}` : `'moda ${command}' needs a subcommand: ${verbs.join(", ")}.`, `Usage: ${leavesOf(command).map((entry) => `moda ${entry.path}${entry.args ? ` ${entry.args}` : ""}`).join(" | ")}`, verb ? "unknown_subcommand" : "missing_subcommand", { field: "<subcommand>", allowed: verbs, ...didYouMean.length ? { didYouMean } : {}, suggestedCommands: [`moda ${command} --help`] });
1149
+ }
1150
+ switch (leaf.path) {
1151
+ case "detections list":
1152
+ return routeDetectionsList(leaf, rest, flags);
1153
+ case "detections tools":
1154
+ return routeDetectionsTools(leaf, rest);
1155
+ case "reviews list":
1156
+ return routeReviewsList(leaf, rest, flags);
1157
+ case "problems impact":
1158
+ return routeProblemsImpact(leaf, rest, flags);
1159
+ default: {
1160
+ const handler = leaf.handler;
1161
+ const renameKey = leaf.handlerSubcommand ? `${handler} ${leaf.handlerSubcommand}` : handler;
1162
+ return {
1163
+ leaf,
1164
+ handler,
1165
+ positionals: leaf.handlerSubcommand ? [leaf.handlerSubcommand, ...rest] : rest,
1166
+ rewriteFlags: (input) => input,
1167
+ label: leaf.path,
1168
+ telemetrySubcommand: leaf.verb,
1169
+ renames: { [renameKey]: leaf.path }
1170
+ };
1171
+ }
1172
+ }
1173
+ }
1174
+ function parseCommandFor(command, verb) {
1175
+ const leaf = verb !== undefined && command !== "review" ? leafFor(command, verb) : undefined;
1176
+ return leaf?.handler ?? command;
1177
+ }
1178
+ function renameCommands(text, renames) {
1179
+ const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1180
+ let result = text;
1181
+ for (const [from, to] of Object.entries(renames).sort((left, right) => right[0].length - left[0].length)) {
1182
+ const verbs = leavesOf(from).flatMap((leaf) => leaf.verb ? [escape(leaf.verb)] : []);
1183
+ const canonical = verbs.length > 0 ? `(?! (?:${verbs.join("|")})(?![\\w-]))` : "";
1184
+ result = result.replace(new RegExp(`moda ${escape(from)}(?![\\w-])${canonical}`, "g"), `moda ${to}`);
1185
+ }
1186
+ return result;
1187
+ }
1188
+ function leafHandlers(leaf) {
1189
+ return leaf.routes ? [...leaf.routes] : [leaf.handler];
1190
+ }
1191
+ function leafFlagSpec(leaf) {
1192
+ if (!leaf.handler)
1193
+ return;
1194
+ if (leaf.path === "problems unexplained")
1195
+ return COMMAND_FLAG_SPECS.problems.subcommands.remainder;
1196
+ return flagSpecFor(leaf.handler);
1197
+ }
659
1198
 
660
1199
  // src/command-registry.ts
661
1200
  function createCommandRegistry(definitions) {
@@ -889,11 +1428,11 @@ var SKILLS_SUBCOMMAND_HELP = [
889
1428
  examples: ["moda skills pull", "moda skills pull --status=all"]
890
1429
  },
891
1430
  {
892
- name: "sync",
893
- description: "Upload locally authored .claude/skills/**/SKILL.md files to Moda",
894
- usage: "moda skills sync [--dry-run]",
1431
+ name: "push",
1432
+ description: "Upload locally authored .claude/skills/**/SKILL.md files to Moda (legacy name: sync; download is pull)",
1433
+ usage: "moda skills push [--dry-run]",
895
1434
  flags: [{ name: "--dry-run", description: "Server-side preview; nothing is saved." }],
896
- examples: ["moda skills sync --dry-run", "moda skills sync"]
1435
+ examples: ["moda skills push --dry-run", "moda skills push"]
897
1436
  },
898
1437
  {
899
1438
  name: "list",
@@ -1195,15 +1734,6 @@ var HARNESS_SUBCOMMAND_HELP = [
1195
1734
  examples: ["moda harness diff my-harness-key --from=hver_a --to=hver_b"]
1196
1735
  }
1197
1736
  ];
1198
- var PROBLEMS_SUBCOMMAND_HELP = [
1199
- {
1200
- name: "remainder",
1201
- description: "Signals no Problem explains yet, grouped by family (read-only)",
1202
- usage: "moda problems remainder [--family=<family>] [--days-back=N] [--limit=N] [--cursor=<token>]",
1203
- flags: subcommandHelpFlags(COMMAND_FLAG_SPECS.problems.subcommands.remainder),
1204
- examples: ["moda problems remainder", "moda problems remainder --family=emotion --days-back=7"]
1205
- }
1206
- ];
1207
1737
  var FIX_TIMEOUT = { name: "--timeout=<ms>", description: "How long to wait, in milliseconds (max 86400000)." };
1208
1738
  var FIX_POLL = { name: "--poll-interval=<ms>", description: "Polling interval while waiting, in milliseconds." };
1209
1739
  var FIXES_SUBCOMMAND_HELP = [
@@ -1769,27 +2299,42 @@ function buildManifest(commands) {
1769
2299
  "moda.intelligence.v1",
1770
2300
  "production_ask.v0.1"
1771
2301
  ],
1772
- commands: commands.filter((command) => !command.hidden).map((command) => ({
1773
- name: command.name,
1774
- aliases: command.aliases ?? [],
1775
- summary: command.description,
1776
- output_modes: command.outputModes,
1777
- streaming: command.outputModes.includes("agent-stream") || command.outputModes.includes("agent"),
1778
- examples: command.examples,
1779
- auth: command.auth,
1780
- ...command.authNote ? { auth_note: command.authNote } : {},
1781
- mutability: command.mutability,
1782
- exit_codes: exitCodesForCommand(command.name),
1783
- ...commandFlagFields(command)
1784
- })),
2302
+ commands: commands.filter((command) => !command.hidden).map((command) => {
2303
+ const legacy = legacyEntry(command.name);
2304
+ if (legacy) {
2305
+ return {
2306
+ name: command.name,
2307
+ aliases: command.aliases ?? [],
2308
+ summary: command.description,
2309
+ deprecated_by: legacy
2310
+ };
2311
+ }
2312
+ const deprecatedForms = deprecatedFormsOf(command.name);
2313
+ return {
2314
+ name: command.name,
2315
+ aliases: command.aliases ?? [],
2316
+ summary: command.description,
2317
+ output_modes: command.outputModes,
2318
+ streaming: command.outputModes.includes("agent-stream") || command.outputModes.includes("agent"),
2319
+ examples: command.examples,
2320
+ auth: command.auth,
2321
+ ...command.authNote ? { auth_note: command.authNote } : {},
2322
+ mutability: command.mutability,
2323
+ exit_codes: exitCodesForCommand(command.name),
2324
+ ...commandFlagFields(command, commands),
2325
+ ...treeFields(command.name, commands),
2326
+ ...deprecatedForms.length > 0 ? { deprecated_forms: deprecatedForms } : {}
2327
+ };
2328
+ }),
1785
2329
  concepts: [
1786
2330
  { term: "tenant", meaning: "A Moda organization/workspace that owns data, prompts, skills, and API keys." },
1787
2331
  { term: "profile", meaning: "A named local CLI configuration under ~/.moda that selects environment, tenant, and secret references." },
1788
2332
  { term: "harness", meaning: "Moda’s local graph of runtime agents, prompts, tools, identities, and deployments in a codebase." },
1789
2333
  { term: "harness report", meaning: "A cited analyst artifact under .moda/ that justifies the harness graph before sync." },
1790
2334
  { term: "remote analyze run", meaning: "A Moda-hosted harness analysis tracked in .moda/harness-remote-run.json; fetch its status or result with `moda harness pull`." },
1791
- { term: "trace", meaning: "The full record of one agent run stored in Moda analytics: every message, tool call, thinking block, and step that shares one `conversation_id` (the trace ID). Formerly called a conversation; `moda conversations` remains a legacy alias of `moda traces`." },
1792
- { term: "span", meaning: "Raw OTLP span evidence inside one trace (or one OTLP trace_id), exposed by `moda audit`." },
2335
+ { term: "trace", meaning: "The full record of one agent run stored in Moda analytics: every message, tool call, thinking block, and step that shares one `conversation_id` (the trace ID). Formerly called a conversation; `moda conversations` remains a silent alias of `moda traces list`." },
2336
+ { term: "span", meaning: "Raw OTLP span evidence inside one trace (or one OTLP trace_id), exposed by `moda traces spans`." },
2337
+ { term: "legacy name", meaning: "A command name from before the `moda <noun> <verb>` tree (e.g. `moda context`, now `moda traces get`). It still runs; its entry names the canonical form in `deprecated_by`, and agent output carries a warning and `meta.deprecated`." },
1793
2338
  { term: "ingest key", meaning: "A `moda_sk_` API key for SDKs, CI, and Data API calls. Treat it as a secret." },
1794
2339
  { term: "CLI session token", meaning: "A local browser-auth session token used by CLI auth/bootstrap flows, not by application SDKs." },
1795
2340
  { term: "prompt", meaning: "A code-first prompt file tracked by `.moda/prompts.yml` and synced to Moda." },
@@ -1812,7 +2357,11 @@ function renderManifestHuman(manifest) {
1812
2357
  ].join(`
1813
2358
  `);
1814
2359
  }
1815
- function commandFlagFields(command) {
2360
+ function commandFlagFields(command, commands) {
2361
+ if (command.name === "review")
2362
+ return leafManifestFields(leafFor("review", undefined), commands);
2363
+ if (command.name === "detections" || command.name === "reviews")
2364
+ return {};
1816
2365
  const spec = flagSpecFor(command.name);
1817
2366
  if (spec)
1818
2367
  return manifestFlagFields(spec);
@@ -1845,51 +2394,174 @@ function exitCodesForCommand(command) {
1845
2394
  return [0, 1, 4, 5, 130];
1846
2395
  return [0, 1, 4, 130];
1847
2396
  }
2397
+ function legacyEntry(name) {
2398
+ return visibleLegacyNames().find((entry) => entry.legacy === name)?.use;
2399
+ }
2400
+ function deprecatedFormsOf(name) {
2401
+ return visibleLegacyNames().filter((entry) => entry.legacy.startsWith(`${name} `)).map((entry) => ({ form: `moda ${entry.legacy}`, use: entry.use }));
2402
+ }
2403
+ function treeFields(name, commands) {
2404
+ if (!CANONICAL_NOUNS.includes(name) || name === "review")
2405
+ return {};
2406
+ return { subcommands: leavesOf(name).map((leaf) => leafManifestFields(leaf, commands)) };
2407
+ }
2408
+ function leafMutability(leaf, commands) {
2409
+ const handlers = leafHandlers(leaf);
2410
+ return commands.some((command) => handlers.includes(command.name) && command.mutability === "write") ? "write" : "read";
2411
+ }
2412
+ function documentedFlags(flags) {
2413
+ return flags.map(documentedFlag);
2414
+ }
2415
+ function withoutFlags(flags, ...names) {
2416
+ return flags.filter((flag) => !names.includes(flag.name));
2417
+ }
2418
+ function handlerFlags(command, ...drop) {
2419
+ const spec = flagSpecFor(command);
2420
+ return spec ? withoutFlags(describeSpecFlags(spec), ...drop) : [];
2421
+ }
2422
+ function leafManifestFields(leaf, commands) {
2423
+ const base = {
2424
+ ...leaf.verb ? { name: leaf.verb } : {},
2425
+ path: `moda ${leaf.path}`,
2426
+ summary: leaf.summary,
2427
+ usage: `moda ${leaf.path}${leaf.args ? ` ${leaf.args}` : ""} [flags]`,
2428
+ aliases: legacyNamesForPath(leaf.path),
2429
+ runs: leafHandlers(leaf),
2430
+ mutability: leafMutability(leaf, commands),
2431
+ examples: leaf.examples
2432
+ };
2433
+ const spec = leafFlagSpec(leaf);
2434
+ if (spec) {
2435
+ const fields = manifestFlagFields({ ...spec, subcommands: undefined });
2436
+ if (leaf.path === "problems impact") {
2437
+ fields.positionals = [{ name: "problem_id", required: false, description: "Same as --problem-id", flag: "--problem-id" }];
2438
+ }
2439
+ return { ...base, ...fields };
2440
+ }
2441
+ switch (leaf.path) {
2442
+ case "skills push":
2443
+ return { ...base, flags: documentedFlags([{ name: "--dry-run", description: "Server-side preview; nothing is saved." }]) };
2444
+ case "detections list":
2445
+ return {
2446
+ ...base,
2447
+ flags: [{
2448
+ name: "--family",
2449
+ type: "enum",
2450
+ enum: [...DETECTION_FAMILIES],
2451
+ required: true,
2452
+ description: "Detection family; emotion and laziness take a sub-family after a colon (emotion:confusion, laziness:stub_body)"
2453
+ }],
2454
+ routes: DETECTION_FAMILIES.flatMap((family) => family === "custom_signal" ? [
2455
+ {
2456
+ when: "--family=custom_signal --signal-id=<signal_id>",
2457
+ runs: ["signal"],
2458
+ flags: [
2459
+ { name: "--signal-id", type: "string", required: true, description: "One signal's recent hits (runs signal <id> --detections)" },
2460
+ ...handlerFlags("signal", "--detections")
2461
+ ]
2462
+ },
2463
+ {
2464
+ when: "--family=custom_signal --conversation-id=<conversation_id>",
2465
+ runs: ["trace-signals"],
2466
+ flags: [{ name: "--conversation-id", type: "string", required: true, description: "Every custom signal hit on one trace (runs trace-signals)" }]
2467
+ }
2468
+ ] : [{
2469
+ when: `--family=${family}`,
2470
+ runs: [DETECTION_FAMILY_HANDLERS[family]],
2471
+ ...DETECTION_SUBFAMILIES[family] ? { subfamilies: DETECTION_SUBFAMILIES[family] } : {},
2472
+ flags: handlerFlags(DETECTION_FAMILY_HANDLERS[family], "--family")
2473
+ }])
2474
+ };
2475
+ case "detections tools":
2476
+ return {
2477
+ ...base,
2478
+ positionals: [{ name: "tool_name", required: false, description: "One tool: its failure examples (without it: the leaderboard)" }],
2479
+ routes: [
2480
+ { when: "no <tool_name>", runs: ["tool-failures"], flags: handlerFlags("tool-failures") },
2481
+ { when: "<tool_name>", runs: ["tool-failure-detail"], flags: handlerFlags("tool-failure-detail") }
2482
+ ]
2483
+ };
2484
+ case "review": {
2485
+ const verdict = { name: "--verdict", type: "enum", enum: [...REVIEW_VERDICTS], default: "false_positive", required: false, description: "What the review says" };
2486
+ const alias = (name, of) => ({ name, type: "string", required: false, description: `Alias of ${of}`, alias_of: of });
2487
+ const falsePositiveFlags = [verdict, ...handlerFlags("false-positive", "--undo"), alias("--note", "--reason")];
2488
+ return {
2489
+ ...base,
2490
+ positionals: [
2491
+ { name: "kind", required: true, enum: ["problem", "problem-detection", "quote", "tool", "laziness", "hallucination", "custom-signal"] },
2492
+ { name: "target", required: true, description: "Problem id (problem, problem-detection), signal id (custom-signal), else the trace ID" }
2493
+ ],
2494
+ flags: falsePositiveFlags,
2495
+ routes: [
2496
+ { when: "problem-detection (any verdict)", runs: ["detection-review"], flags: [verdict, ...handlerFlags("detection-review", "--verdict"), alias("--reason", "--note")] },
2497
+ { when: "custom-signal --verdict=correct", runs: ["signal-label"], flags: [verdict, ...handlerFlags("signal-label", "--label"), alias("--reason", "--note")] },
2498
+ { when: "any other kind or verdict (clear = --undo; problem cannot be cleared; correct is rejected)", runs: ["false-positive"], flags: falsePositiveFlags }
2499
+ ]
2500
+ };
2501
+ }
2502
+ case "reviews list":
2503
+ return {
2504
+ ...base,
2505
+ positionals: [{ name: "conversation_id", required: false, description: "List the marks on one trace instead of the whole workspace" }],
2506
+ flags: [
2507
+ ...handlerFlags("false-positives", "--conversation-id", "--kind"),
2508
+ { name: "--kind", type: "enum", enum: ["quote", "tool", "laziness", "hallucination", "custom-signal", "problem-detection"], required: false, description: "Only this kind of mark (problem-detection needs <conversation_id>)" },
2509
+ { name: "--attribution-id", type: "string", required: false, description: "With --kind=problem-detection: only this detection (UUID)" }
2510
+ ],
2511
+ routes: [
2512
+ { when: "--kind=problem-detection <conversation_id>", runs: ["detection-reviews"] },
2513
+ { when: "otherwise", runs: ["false-positives"] }
2514
+ ]
2515
+ };
2516
+ default:
2517
+ return base;
2518
+ }
2519
+ }
1848
2520
 
1849
2521
  // src/cli.ts
1850
2522
  import { fileURLToPath } from "url";
1851
2523
  import { realpathSync } from "fs";
1852
2524
  async function runPromptsCommand(...args) {
1853
- return (await import("./prompts-1tkhrxkq.js")).runPromptsCommand(...args);
2525
+ return (await import("./prompts-cvzt994j.js")).runPromptsCommand(...args);
1854
2526
  }
1855
2527
  async function runSkillsCommand(...args) {
1856
- return (await import("./skills-gejdvjak.js")).runSkillsCommand(...args);
2528
+ return (await import("./skills-tadh1jss.js")).runSkillsCommand(...args);
1857
2529
  }
1858
2530
  async function runFixCommand(...args) {
1859
- return (await import("./fix-9fmy6p2x.js")).runFixCommand(...args);
2531
+ return (await import("./fix-exvxzvtz.js")).runFixCommand(...args);
1860
2532
  }
1861
2533
  async function runFixesCommand(...args) {
1862
- return (await import("./fix-9fmy6p2x.js")).runFixesCommand(...args);
2534
+ return (await import("./fix-exvxzvtz.js")).runFixesCommand(...args);
1863
2535
  }
1864
2536
  async function runHarnessCommand(...args) {
1865
- return (await import("./harness-xvg6ws6x.js")).runHarnessCommand(...args);
2537
+ return (await import("./harness-vzy7n7ea.js")).runHarnessCommand(...args);
1866
2538
  }
1867
2539
  async function runSyncCommand(...args) {
1868
- return (await import("./sync-57ercmh9.js")).runSyncCommand(...args);
2540
+ return (await import("./sync-g8wtecz1.js")).runSyncCommand(...args);
1869
2541
  }
1870
2542
  async function runRegistryCommand(...args) {
1871
- return (await import("./registry-atqyb5fa.js")).runRegistryCommand(...args);
2543
+ return (await import("./registry-s2vdsfw8.js")).runRegistryCommand(...args);
1872
2544
  }
1873
2545
  async function runDoctorCommand(...args) {
1874
- return (await import("./doctor-mx6jf435.js")).runDoctorCommand(...args);
2546
+ return (await import("./doctor-3r19ry92.js")).runDoctorCommand(...args);
1875
2547
  }
1876
2548
  async function runStatusCommand(...args) {
1877
- return (await import("./doctor-mx6jf435.js")).runStatusCommand(...args);
2549
+ return (await import("./doctor-3r19ry92.js")).runStatusCommand(...args);
1878
2550
  }
1879
2551
  async function runAuthCommand(...args) {
1880
- return (await import("./profile-commands-kmmd9y86.js")).runAuthCommand(...args);
2552
+ return (await import("./profile-commands-6e2dgv72.js")).runAuthCommand(...args);
1881
2553
  }
1882
2554
  async function runConfigCommand(...args) {
1883
- return (await import("./profile-commands-kmmd9y86.js")).runConfigCommand(...args);
2555
+ return (await import("./profile-commands-6e2dgv72.js")).runConfigCommand(...args);
1884
2556
  }
1885
2557
  async function runProfilesCommand(...args) {
1886
- return (await import("./profile-commands-kmmd9y86.js")).runProfilesCommand(...args);
2558
+ return (await import("./profile-commands-6e2dgv72.js")).runProfilesCommand(...args);
1887
2559
  }
1888
2560
  async function runAgentCommand(...args) {
1889
- return (await import("./agent-mj622k8s.js")).runAgentCommand(...args);
2561
+ return (await import("./agent-b4tdzy9q.js")).runAgentCommand(...args);
1890
2562
  }
1891
2563
  async function runProductionCommand(...args) {
1892
- return (await import("./production-intelligence-dbe9dmc5.js")).runProductionCommand(...args);
2564
+ return (await import("./production-intelligence-c8e0xw3n.js")).runProductionCommand(...args);
1893
2565
  }
1894
2566
  function parseArgs(argv) {
1895
2567
  const args = argv.slice(2);
@@ -1906,7 +2578,7 @@ function parseArgs(argv) {
1906
2578
  if (!endOfOptions && arg === "--" && command !== "") {
1907
2579
  endOfOptions = true;
1908
2580
  } else if (!endOfOptions && arg.startsWith("--")) {
1909
- i = parseFlag(args, i, flags, command === "", bareFlags, command);
2581
+ i = parseFlag(args, i, flags, command === "", bareFlags, parseCommandFor(command, positional));
1910
2582
  } else if (!endOfOptions && command !== "" && /^-[A-Za-z]/.test(arg)) {
1911
2583
  shortFlags.push(arg);
1912
2584
  } else if (command === "") {
@@ -2092,58 +2764,35 @@ function buildResumeCommand(command, flags) {
2092
2764
  ];
2093
2765
  }
2094
2766
  function printHelp() {
2095
- console.log(`Usage: moda <command> [options]
2767
+ console.log(`Usage: moda <noun> <verb> [args] [flags]
2096
2768
 
2097
2769
  Start here (goal -> command):
2098
- find something in traces moda search "<query>" (whole traces: moda traces --search=TEXT)
2099
- read one trace moda context <conversation_id> (whole trace: --all)
2100
- what is broken moda overview, then moda problems
2101
- tool errors moda tool-failures, then moda tool-failure-detail <tool_name>
2102
- user emotion and frustration moda emotions
2103
- raw spans for one trace moda audit <conversation_id|trace_id>
2104
- every flag of a command moda <command> --help (all commands and flags: moda manifest --json)
2770
+ find something in traces moda traces search "<query>" (whole traces: moda traces list --search=TEXT)
2771
+ read one trace moda traces get <conversation_id> (whole trace: --all)
2772
+ what is broken moda overview, then moda problems list
2773
+ tool errors moda detections tools, then moda detections tools <tool_name>
2774
+ user emotion and frustration moda detections list --family=emotion (or --family=frustration)
2775
+ raw spans for one trace moda traces spans <conversation_id|trace_id>
2776
+ a detection is wrong moda review <kind> <target> --reason=TEXT
2777
+ every flag of a command moda <noun> <verb> --help (all commands and flags: moda manifest --json)
2105
2778
 
2106
2779
  Production data:
2107
2780
  overview Harness health briefing with production signals
2108
2781
  investigate Rank production issues (tool failures, frustrations, top Problems)
2109
2782
  failures Tool failures only, ranked (no frustrations or Problems)
2110
2783
  ask "<question>" Ask Moda a production question (one paid agent run)
2111
- search "<query>" Search trace messages (keyword/semantic/hybrid)
2112
- traces Search and filter traces (agent runs)
2113
- context <conversation_id> Messages around one turn, or the whole trace with --all
2114
- context <id>[@N] <id>[@N] ... Batch: up to 25 traces in one call (or --ids-from=-|<file>)
2115
- audit <conversation_id|trace_id> Raw span audit (spans, hierarchy, orphans, duplicates)
2116
- audit <id> <id> ... Batch audit: up to 25 ids in one call (or --ids-from=-|<file>)
2117
- world-state <conversation_id> A trace's world state (slots, threads, events)
2118
2784
  clusters Browse or search topic clusters
2119
2785
  cluster-traces <node_id> List traces in a cluster
2120
2786
  users / user <user_id> End users with health and activity (the dashboard Users page)
2121
2787
  tail Live-tail new traces/detections as NDJSON (one line per item)
2122
2788
 
2123
- Detections:
2124
- emotions Emotion detections (frustration, disapproval, confusion, ...)
2125
- frustrations Frustration detections (same data as emotions --family=frustration)
2126
- hallucinations Grounding detections: contradicted/verified outputs
2127
- laziness Laziness detections per trace (repeated calls, premature handoffs, ...)
2128
- tool-failures Tool failure overview
2129
- tool-failure-detail <tool_name> Per-tool failure examples
2789
+ Traces, Problems, detections and review (moda <noun> --help lists the verbs):
2790
+ ${canonicalTreeHelpLines().join(`
2791
+ `)}
2130
2792
 
2131
- Problems, signals and review:
2132
- problems Rank cross-signal Problems by root cause (what to fix first)
2133
- problem <problem_id> One Problem: dossier, or its evidence/reports/traces/feedback
2134
- problem-impact Live problem/use-case impact, trend and evidence
2135
- problem-feedback <problem_id> Mark a Problem fixed, dismiss, rename, or flag a bad attribution
2136
- problem-reopen <problem_id> Reopen a Problem marked fixed
2137
- problem-linear <problem_id> Show the Linear issue linked to a Problem
2138
- problem-linear-file <problem_id> File a Problem as a Linear issue, or link an existing one
2139
- detection-reviews <conv_id> Problem detections on a trace with their review state
2140
- detection-review <problem_id> Mark a problem detection correct, a false positive, or clear it
2141
- false-positive <kind> <target> Mark any detection as a false positive
2142
- false-positives [conv_id] Current false-positive marks (workspace or one trace)
2793
+ Custom signals and feedback:
2143
2794
  signals List and manage custom signals
2144
- signal <signal_id> One custom signal, or --detections for its recent hits
2145
- trace-signals <conversation_id> Custom signal hits on one trace
2146
- signal-label <signal_id> Label a trace fits / does_not_fit (false positive) for a signal
2795
+ signal <signal_id> One custom signal (its hits: moda detections list --family=custom_signal)
2147
2796
  feedback "<note>" Flag wrong/missing data or CLI quirks to the Moda team
2148
2797
 
2149
2798
  Setup:
@@ -2161,13 +2810,13 @@ Groups with their own subcommands (run 'moda <group> --help'):
2161
2810
  harness Analyze, scan, validate, approve, sync and register the agent harness
2162
2811
  prompts Code-first prompt versions: init, status, sync, promote, A/B, propose
2163
2812
  replays Replay results: sets, runs, playouts and transcripts
2164
- skills Generate, pull, install and sync agent skills
2813
+ skills Generate skills; pull (download), push (upload), install
2165
2814
  sync Sync the harness graph and prompts to Moda Cloud
2166
2815
  registry Push prompts, skills, tools and agent wiring from one JSON manifest
2167
2816
 
2168
- Legacy aliases (same behavior): conversations = traces, cluster-conversations = cluster-traces,
2169
- trace = audit, --conversations = --traces, --signal=conversations = --signal=traces.
2170
- <conversation_id> is the trace ID.
2817
+ ${legacyNamesHelpLine()}
2818
+ Silent aliases: conversations = traces list, cluster-conversations = cluster-traces, --conversations = --traces,
2819
+ --signal=conversations = --signal=traces. <conversation_id> is the trace ID.
2171
2820
 
2172
2821
  Output: piped stdout, CI or an agent env (CLAUDECODE/CURSOR/MODA_AGENT) => agent JSON; a terminal => prose.
2173
2822
  --json | --agent [--stream] | --human Force an output mode (or MODA_FORMAT=human|json|agent|agent-stream)
@@ -2252,6 +2901,187 @@ function printSubcommandHelp(parent, entry) {
2252
2901
  console.log(lines.join(`
2253
2902
  `));
2254
2903
  }
2904
+ function helpTable(rows) {
2905
+ if (rows.length === 0)
2906
+ return [" (none besides the global flags below)"];
2907
+ const width = Math.min(34, Math.max(...rows.map((row) => row.name.length)));
2908
+ return rows.map((row) => row.name.length > width ? ` ${row.name}
2909
+ ${" ".repeat(width)} ${row.description}` : ` ${row.name.padEnd(width)} ${row.description}`);
2910
+ }
2911
+ function routedFlagRows(command, drop = []) {
2912
+ const spec = flagSpecFor(command);
2913
+ if (!spec)
2914
+ return [];
2915
+ return subcommandHelpFlags(spec).filter((row) => !drop.some((name) => row.name === name || row.name.startsWith(`${name}=`)));
2916
+ }
2917
+ function detectionsListHelp(focus) {
2918
+ const lines = ["Families (--family=):"];
2919
+ lines.push(...helpTable(DETECTION_FAMILIES.map((family) => {
2920
+ const subfamilies = DETECTION_SUBFAMILIES[family];
2921
+ const runs = family === "custom_signal" ? "signal --detections / trace-signals" : DETECTION_FAMILY_HANDLERS[family];
2922
+ return {
2923
+ name: subfamilies ? `${family}[:<family>]` : family,
2924
+ description: `runs ${runs}${subfamilies ? `; <family>: ${subfamilies.join(", ")}` : ""}`
2925
+ };
2926
+ })));
2927
+ const base = focus?.split(":")[0];
2928
+ for (const family of DETECTION_FAMILIES.filter((entry) => !base || entry === base)) {
2929
+ lines.push("", `Flags with --family=${family}:`);
2930
+ if (family === "custom_signal") {
2931
+ lines.push(...helpTable([
2932
+ { name: "--signal-id=ID", description: "One signal's recent hits (paged; runs signal <id> --detections)" },
2933
+ ...routedFlagRows("signal", ["--detections"]),
2934
+ { name: "--conversation-id=ID", description: "Instead of --signal-id: every signal hit on one trace (runs trace-signals)" }
2935
+ ]));
2936
+ } else {
2937
+ lines.push(...helpTable(routedFlagRows(DETECTION_FAMILY_HANDLERS[family], ["--family"])));
2938
+ }
2939
+ }
2940
+ return lines;
2941
+ }
2942
+ function detectionsToolsHelp() {
2943
+ return [
2944
+ "Without <tool_name> (the leaderboard, runs tool-failures):",
2945
+ ...helpTable(routedFlagRows("tool-failures")),
2946
+ "",
2947
+ "With <tool_name> (failure examples, runs tool-failure-detail):",
2948
+ ...helpTable(routedFlagRows("tool-failure-detail", ["--tool-name"]))
2949
+ ];
2950
+ }
2951
+ function reviewHelp() {
2952
+ return [
2953
+ "Kinds and targets: problem <problem_id>, problem-detection <problem_id>, quote|tool|laziness|hallucination <conversation_id>,",
2954
+ "custom-signal <signal_id>.",
2955
+ "",
2956
+ "Verdicts (--verdict, default false_positive) and what each runs:",
2957
+ " problem false_positive: dismiss the Problem (false-positive problem); clear and correct are rejected",
2958
+ " problem-detection every verdict: the detection review the dashboard thumbs write (detection-review)",
2959
+ " custom-signal false_positive: does_not_fit label; clear: removes the label (false-positive custom-signal);",
2960
+ " correct: fits label (signal-label)",
2961
+ " quote, tool, laziness, hallucination",
2962
+ " false_positive or clear (false-positive <kind> [--undo]); correct is rejected",
2963
+ "",
2964
+ "Flags:",
2965
+ ...helpTable([
2966
+ { name: `--verdict=${REVIEW_VERDICTS.join("|")}`, description: "What the review says [default false_positive]" },
2967
+ ...routedFlagRows("false-positive", ["--undo"]),
2968
+ { name: "--note=TEXT", description: "Alias of --reason" }
2969
+ ])
2970
+ ];
2971
+ }
2972
+ function reviewsListHelp() {
2973
+ return [
2974
+ "Flags:",
2975
+ ...helpTable([
2976
+ ...routedFlagRows("false-positives", ["--kind"]),
2977
+ { name: "--kind=<value>", description: "Only this kind of mark: quote, tool, laziness, hallucination, custom-signal, or problem-detection (needs <conversation_id>; runs detection-reviews)" },
2978
+ { name: "--attribution-id=ID", description: "With --kind=problem-detection: only this detection (UUID)" }
2979
+ ])
2980
+ ];
2981
+ }
2982
+ function renderLeafHelp(leaf, options = {}) {
2983
+ const lines = [`moda ${leaf.path} — ${leaf.summary}`];
2984
+ if (options.legacy) {
2985
+ lines.push(`'moda ${options.legacy.legacy}' is a legacy name for '${options.legacy.use}'; it still works.${options.legacy.note ? ` ${options.legacy.note}` : ""}`);
2986
+ }
2987
+ const aliases = legacyNamesForPath(leaf.path);
2988
+ if (aliases.length > 0)
2989
+ lines.push(`Legacy names (still work): ${aliases.join(", ")}`);
2990
+ const handlers = leaf.routes ?? [leaf.handler];
2991
+ if (handlers.some((name) => commandRegistry.find(name)?.mutability === "write"))
2992
+ lines.push("This command can mutate state.");
2993
+ const handlerDefinition = leaf.handler ? commandRegistry.find(leaf.handler) : undefined;
2994
+ if (leaf.handler && !leaf.handlerSubcommand) {
2995
+ for (const note of handlerDefinition?.helpNotes ?? [])
2996
+ lines.push("", renameCommands(note, { [leaf.handler]: leaf.path }));
2997
+ }
2998
+ lines.push("", `Usage: moda ${leaf.path}${leaf.args ? ` ${leaf.args}` : ""} [flags]`);
2999
+ const spec = leafFlagSpec(leaf);
3000
+ let ownFlags = [];
3001
+ if (spec) {
3002
+ const body = renderFlagHelp(leaf.path, { ...spec, subcommands: undefined }).slice(1);
3003
+ lines.push(...body.map((line) => renameCommands(line, { [leaf.handler]: leaf.path })));
3004
+ ownFlags = specFlagKeys(spec);
3005
+ } else if (leaf.path === "skills push") {
3006
+ const entry = SKILLS_SUBCOMMAND_HELP.find((subcommand) => subcommand.name === "push");
3007
+ lines.push("", "Flags:", ...helpTable(entry?.flags ?? []));
3008
+ } else if (leaf.path === "detections list") {
3009
+ lines.push("", ...detectionsListHelp(options.focus));
3010
+ } else if (leaf.path === "detections tools") {
3011
+ lines.push("", ...detectionsToolsHelp());
3012
+ } else if (leaf.path === "review") {
3013
+ lines.push("", ...reviewHelp());
3014
+ } else if (leaf.path === "reviews list") {
3015
+ lines.push("", ...reviewsListHelp());
3016
+ }
3017
+ if (leaf.examples.length > 0)
3018
+ lines.push("", "Examples:", ...leaf.examples.map((example) => ` ${example}`));
3019
+ lines.push("", ...globalFlagHelpLines(ownFlags), "Any other flag is rejected.");
3020
+ return lines.join(`
3021
+ `);
3022
+ }
3023
+ function printTreeHelp(parsed) {
3024
+ const { command, positionals, flags } = parsed;
3025
+ if (command === "review") {
3026
+ console.log(renderLeafHelp(leafFor("review", undefined)));
3027
+ return true;
3028
+ }
3029
+ if (CANONICAL_NOUNS.includes(command) && positionals[0] !== undefined) {
3030
+ const leaf2 = leafFor(command, positionals[0]);
3031
+ if (leaf2) {
3032
+ console.log(renderLeafHelp(leaf2, { focus: flags.family }));
3033
+ return true;
3034
+ }
3035
+ }
3036
+ const legacy = legacyNameFor(command, positionals[0], flags);
3037
+ if (!legacy || legacy.silent)
3038
+ return false;
3039
+ const [noun, verb] = usePath(legacy).split(" ");
3040
+ const leaf = leafFor(noun, verb);
3041
+ if (!leaf) {
3042
+ const target = commandRegistry.find(noun);
3043
+ const entry = target?.subcommands?.find((subcommand) => subcommand.name === verb);
3044
+ if (!target || !entry)
3045
+ return false;
3046
+ console.log(`'moda ${legacy.legacy}' is a legacy name for '${legacy.use}'; it still works. ${legacy.note ?? ""}`.trim());
3047
+ printSubcommandHelp(target, entry);
3048
+ return true;
3049
+ }
3050
+ const focus = /--family=([a-z_]+)/.exec(legacy.use)?.[1];
3051
+ console.log(renderLeafHelp(leaf, { legacy, ...focus ? { focus } : {} }));
3052
+ return true;
3053
+ }
3054
+ function treeSubcommandHelp(noun) {
3055
+ return leavesOf(noun).map((leaf) => ({
3056
+ name: leaf.verb,
3057
+ description: leaf.summary,
3058
+ usage: `moda ${leaf.path}${leaf.args ? ` ${leaf.args}` : ""} [flags]`,
3059
+ examples: [...leaf.examples]
3060
+ }));
3061
+ }
3062
+ function canonicalTreeHelpLines() {
3063
+ const rows = CANONICAL_LEAVES.filter((leaf) => leaf.noun !== "skills").map((leaf) => {
3064
+ const left = `${leaf.path}${leaf.args ? ` ${leaf.args}` : ""}`;
3065
+ return left.length > 32 ? ` ${left}
3066
+ ${" ".repeat(33)}${leaf.summary}` : ` ${left.padEnd(32)} ${leaf.summary}`;
3067
+ });
3068
+ return rows;
3069
+ }
3070
+ function legacyNamesHelpLine() {
3071
+ const words = ["Legacy names (still work; `moda <name> --help` shows the new form):", ...visibleLegacyNames().map((entry, index, all) => `${entry.legacy}${index < all.length - 1 ? "," : "."}`)];
3072
+ const lines = [];
3073
+ let line = "";
3074
+ for (const word of words) {
3075
+ if (line && line.length + word.length + 1 > 110) {
3076
+ lines.push(line);
3077
+ line = " ";
3078
+ }
3079
+ line += line ? ` ${word}` : word;
3080
+ }
3081
+ lines.push(line);
3082
+ return lines.join(`
3083
+ `);
3084
+ }
2255
3085
  async function attachWindows(rows, windowSize) {
2256
3086
  return Promise.all(rows.map(async (row) => {
2257
3087
  const anchor = asRecord(row.anchor);
@@ -2311,7 +3141,7 @@ function withoutRepeatedFrustrationWindow(data) {
2311
3141
  return { ...data, frustrations };
2312
3142
  }
2313
3143
  var NOT_FOUND_HINTS = {
2314
- trace: 'Find trace ids with: moda search "<text>" or moda traces --search=<text>',
3144
+ trace: 'Find trace ids with: moda traces search "<text>" or moda traces --search=<text>',
2315
3145
  cluster: "List cluster node ids with: moda clusters",
2316
3146
  problem: "List problem ids with: moda problems"
2317
3147
  };
@@ -2325,7 +3155,7 @@ function noWorldStateWarnings(conversationId, isMissing) {
2325
3155
  return;
2326
3156
  return {
2327
3157
  warnings: [
2328
- `No world state for trace "${conversationId}": the trace may not exist in this tenant, or its world state has not been generated yet. Check the trace exists with: moda context ${shellArg(contextTarget(conversationId))} --metadata-only`
3158
+ `No world state for trace "${conversationId}": the trace may not exist in this tenant, or its world state has not been generated yet. Check the trace exists with: moda traces get ${shellArg(contextTarget(conversationId))} --metadata-only`
2329
3159
  ]
2330
3160
  };
2331
3161
  }
@@ -2365,11 +3195,11 @@ function auditOutput(command, params, data) {
2365
3195
  return { data: auditRecord ? { ...auditRecord, truncated: false, has_more: false, next_cursor: null } : data, options: {} };
2366
3196
  }
2367
3197
  const limit = params.limit ?? AUDIT_DEFAULT_LIMIT;
2368
- const perTrace = "audit one OTLP trace at a time with `moda audit <trace_id> --kind=trace` (ids in summary.trace_ids)";
3198
+ const perTrace = "audit one OTLP trace at a time with `moda traces spans <trace_id> --kind=trace` (ids in summary.trace_ids)";
2369
3199
  const warnings = [];
2370
3200
  const nextCommands = [];
2371
3201
  const auditFlags = (extra) => [
2372
- `moda ${command} ${shellArg(params.id)}`,
3202
+ `${canonicalCommandText(`moda ${command}`)} ${shellArg(params.id)}`,
2373
3203
  params.kind && params.kind !== "auto" ? `--kind=${params.kind}` : "",
2374
3204
  params.include_raw === true ? "--include-raw" : "",
2375
3205
  params.include_bodies === true ? "--include-bodies" : "",
@@ -2556,7 +3386,7 @@ async function runTailCommand(params, context) {
2556
3386
  total,
2557
3387
  coverage_pct: coveragePct,
2558
3388
  pages_scanned: pages,
2559
- detail: `Scanned ${scanned}${total !== null ? ` of ${total}` : ""} detection(s) in the last day${coveragePct !== null ? ` (${coveragePct}%)` : ""}. /emotions is ranked by score with no time ordering or cursor, so this follows the highest-scoring detections and lower-scoring ones may not appear. Use --full-scan for completeness (many more requests, up to the API offset ceiling of 10000), or \`moda emotions --offset=N\` to page manually.`
3389
+ detail: `Scanned ${scanned}${total !== null ? ` of ${total}` : ""} detection(s) in the last day${coveragePct !== null ? ` (${coveragePct}%)` : ""}. /emotions is ranked by score with no time ordering or cursor, so this follows the highest-scoring detections and lower-scoring ones may not appear. Use --full-scan for completeness (many more requests, up to the API offset ceiling of 10000), or \`moda detections list --family=emotion --offset=N\` to page manually.`
2560
3390
  });
2561
3391
  } else if (total !== null) {
2562
3392
  emitMeta({
@@ -2697,8 +3527,8 @@ var WINDOW_FLAG_HINTS = {
2697
3527
  };
2698
3528
  var COMMAND_FLAG_HINTS = {
2699
3529
  problems: {
2700
- family: "'moda problems' ranks every family together. To filter by family use 'moda problems remainder --family=<family>' (signals no Problem explains yet) or 'moda problem <problem_id> --traces --family=<family>' (one Problem's traces).",
2701
- cursor: "'moda problems' has no cursor; 'moda problems remainder --cursor=<next_cursor>' pages the unexplained signals."
3530
+ family: "'moda problems' ranks every family together. To filter by family use 'moda problems unexplained --family=<family>' (signals no Problem explains yet) or 'moda problems get <problem_id> --traces --family=<family>' (one Problem's traces).",
3531
+ cursor: "'moda problems' has no cursor; 'moda problems unexplained --cursor=<next_cursor>' pages the unexplained signals."
2702
3532
  }
2703
3533
  };
2704
3534
  function parseCommandFlags(schema, args, command, options = {}) {
@@ -2736,8 +3566,9 @@ function parseCommandFlags(schema, args, command, options = {}) {
2736
3566
  return close ? `Did you mean ${close}?` : "";
2737
3567
  }).filter(Boolean);
2738
3568
  const names = unknown.map(kebabFlag).join(", ");
2739
- const helpCommand = `moda ${command} --help`;
2740
- throw new CliInputError(`Unknown flag${unknown.length > 1 ? "s" : ""} ${names} for 'moda ${command}'.` + (didYouMean.length > 0 ? ` Did you mean ${didYouMean.join(", ")}?` : ""), [...hints, `Valid flags: ${valid.join(", ") || "(none)"}. Run '${helpCommand}'.`].join(" "), `unknown_flag:${unknown[0].replace(/_/g, "-")}`, {
3569
+ const shown = canonicalCommandText(`moda ${command}`);
3570
+ const helpCommand = `${shown} --help`;
3571
+ throw new CliInputError(`Unknown flag${unknown.length > 1 ? "s" : ""} ${names} for '${shown}'.` + (didYouMean.length > 0 ? ` Did you mean ${didYouMean.join(", ")}?` : ""), [...hints, `Valid flags: ${valid.join(", ") || "(none)"}. Run '${helpCommand}'.`].join(" "), `unknown_flag:${unknown[0].replace(/_/g, "-")}`, {
2741
3572
  field: kebabFlag(unknown[0]),
2742
3573
  allowed: valid,
2743
3574
  ...didYouMean.length > 0 ? { didYouMean: [...new Set(didYouMean)] } : {},
@@ -2763,7 +3594,7 @@ function attachFlagDetails(error, schema, command) {
2763
3594
  field: field.name,
2764
3595
  ...field.enum ? { allowed: field.enum } : {},
2765
3596
  ...field.min !== undefined || field.max !== undefined ? { range: { ...field.min !== undefined ? { min: field.min } : {}, ...field.max !== undefined ? { max: field.max } : {} } } : {},
2766
- suggestedCommands: [`moda ${command} --help`]
3597
+ suggestedCommands: [`${canonicalCommandText(`moda ${command}`)} --help`]
2767
3598
  });
2768
3599
  }
2769
3600
  function parseSpecFlags(name, args, command = name) {
@@ -2916,7 +3747,7 @@ async function searchFallbackToTraces(params) {
2916
3747
  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)`;
2917
3748
  const nextCommands = [];
2918
3749
  if (first) {
2919
- nextCommands.push({ command: `moda audit ${first}`, purpose: "Open the top trace to locate the relevant message.", mutability: "read", requires_approval: false });
3750
+ nextCommands.push({ command: `moda traces spans ${first}`, purpose: "Open the top trace to locate the relevant message.", mutability: "read", requires_approval: false });
2920
3751
  }
2921
3752
  if (hasMore) {
2922
3753
  nextCommands.push({ command: `${tracesCommand} --offset=${nextOffset}`, purpose: "Page through further trace-level results.", mutability: "read", requires_approval: false });
@@ -2938,21 +3769,27 @@ async function searchFallbackToTraces(params) {
2938
3769
  ...hasMore ? { next_offset: nextOffset } : {}
2939
3770
  }
2940
3771
  },
2941
- 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 audit <conversation_id>\` or \`moda context <conversation_id>\` to find the matching turn.` + (hasMore ? " More exist: page with the suggested `moda traces --search ... --offset` command." : ""),
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." : ""),
2942
3773
  nextCommands
2943
3774
  };
2944
3775
  }
2945
3776
  function capProblemsBlocks(data, limit) {
2946
3777
  const truncated = {};
2947
3778
  const payload = { ...data };
2948
- if (Array.isArray(data.emerging) && data.emerging.length > limit) {
2949
- truncated.emerging = data.emerging.length - limit;
2950
- payload.emerging = data.emerging.slice(0, limit);
3779
+ if (Array.isArray(data.emerging)) {
3780
+ const total = Math.max(asNumber(data.emerging_total) ?? 0, data.emerging.length);
3781
+ if (total > limit)
3782
+ truncated.emerging = total - Math.min(limit, data.emerging.length);
3783
+ if (data.emerging.length > limit)
3784
+ payload.emerging = data.emerging.slice(0, limit);
2951
3785
  }
2952
3786
  const hierarchy = asRecord(data.hierarchy);
2953
- if (hierarchy && Array.isArray(hierarchy.macros) && hierarchy.macros.length > limit) {
2954
- truncated.hierarchy_macros = hierarchy.macros.length - limit;
2955
- payload.hierarchy = { ...hierarchy, macros: hierarchy.macros.slice(0, limit) };
3787
+ if (hierarchy && Array.isArray(hierarchy.macros)) {
3788
+ const total = Math.max(asNumber(hierarchy.macro_total) ?? 0, hierarchy.macros.length);
3789
+ if (total > limit)
3790
+ truncated.hierarchy_macros = total - Math.min(limit, hierarchy.macros.length);
3791
+ if (hierarchy.macros.length > limit)
3792
+ payload.hierarchy = { ...hierarchy, macros: hierarchy.macros.slice(0, limit) };
2956
3793
  }
2957
3794
  return Object.keys(truncated).length > 0 ? { payload, truncated } : { payload };
2958
3795
  }
@@ -3057,9 +3894,9 @@ function processingDisabledWarning(data, family) {
3057
3894
  `${family === "emotion" ? "Emotion" : "Frustration"} analysis is disabled for this tenant (${setting} = false), ` + "so zero results do NOT mean users are not frustrated. Enable it in Settings → Account → Signal processing; " + "only traces ingested after enabling are analyzed."
3058
3895
  ];
3059
3896
  }
3060
- var CONTEXT_USAGE = "Usage: moda context <conversation_id> [--msg-index=N (alias --message-index)] [--window=0-5 | --metadata-only]";
3061
- var CONTEXT_ALL_USAGE = "Usage: moda context <conversation_id> --all [--from=N] [--max-messages=N]";
3062
- var CONTEXT_BATCH_USAGE = `Usage: moda context <id>[@msg_index] <id>[@msg_index] ... | --ids-from=-|<file> [--window=0-5 | --metadata-only | --all [--from=N] [--max-messages=N]] (max ${BATCH_MAX_TARGETS} targets)`;
3897
+ var CONTEXT_USAGE = "Usage: moda traces get <conversation_id> [--msg-index=N (alias --message-index)] [--window=0-5 | --metadata-only]";
3898
+ var CONTEXT_ALL_USAGE = "Usage: moda traces get <conversation_id> --all [--from=N] [--max-messages=N]";
3899
+ var CONTEXT_BATCH_USAGE = `Usage: moda traces get <id>[@msg_index] <id>[@msg_index] ... | --ids-from=-|<file> [--window=0-5 | --metadata-only | --all [--from=N] [--max-messages=N]] (max ${BATCH_MAX_TARGETS} targets)`;
3063
3900
  var CONTEXT_FLAG_ALIASES = { "message-index": "msg-index" };
3064
3901
  var CONTEXT_MAX_WINDOW = 5;
3065
3902
  var EMPTY_ANCHOR_VALUES = new Set(["", "null", "undefined", "none"]);
@@ -3233,7 +4070,7 @@ async function readFullTranscript(conversationId, opts = {}) {
3233
4070
  if (next !== null) {
3234
4071
  const last = asNumber(returned[returned.length - 1]?.index) ?? next - 1;
3235
4072
  const nextMax = firstMissing !== null ? Math.min(pageSize, Math.max(kept * 2, 50)) : pageSize;
3236
- const continueWith = `moda context ${shellArg(contextTarget(conversationId))} --all --from=${next} --max-messages=${nextMax}`;
4073
+ const continueWith = `moda traces get ${shellArg(contextTarget(conversationId))} --all --from=${next} --max-messages=${nextMax}`;
3237
4074
  out.push(`Trace has ${total} messages; returned messages ${start}-${last}. ` + (firstMissing !== null ? `Stopped at ${kept} of the ${messages.length} messages read to stay under the agent output byte limit (--json has no limit). ` : "") + `Continue with: ${continueWith}`);
3238
4075
  nextCommands.push({
3239
4076
  command: continueWith,
@@ -3246,7 +4083,7 @@ async function readFullTranscript(conversationId, opts = {}) {
3246
4083
  out.push(`Message order may not be chronological: ${tied} of ${total} messages share a timestamp with another message (ordered by id, not time)${synthetic > 0 ? ` and ${synthetic} carry a synthetic pre-2022 timestamp` : ""}.`);
3247
4084
  }
3248
4085
  if (legacy && returned.some(isTruncated)) {
3249
- out.push(`This Moda server is older and truncates message text, tool-call inputs and tool results longer than ${CONTEXT_MESSAGE_TRUNCATE_CHARS} characters. For full content (prompts, tool arguments/results) use \`moda audit <conversation_id> --include-bodies\` (or \`--include-raw\` for the verbatim events).`);
4086
+ out.push(`This Moda server is older and truncates message text, tool-call inputs and tool results longer than ${CONTEXT_MESSAGE_TRUNCATE_CHARS} characters. For full content (prompts, tool arguments/results) use \`moda traces spans <conversation_id> --include-bodies\` (or \`--include-raw\` for the verbatim events).`);
3250
4087
  }
3251
4088
  return {
3252
4089
  data: stripLoneSurrogates({
@@ -3313,7 +4150,7 @@ async function readContextItem(command, params, inputWarnings, fits) {
3313
4150
  const center = asNumber(asRecord(ctxRecord.context)?.center_index) ?? params.msg_index ?? 0;
3314
4151
  const span = Math.min(params.window * 2 + 1, CONTEXT_ALL_MAX_MESSAGES);
3315
4152
  const from = Math.max(0, center - Math.floor((span - 1) / 2));
3316
- const wide = `moda context ${shellArg(contextTarget(params.conversation_id))} --all --from=${from} --max-messages=${span}`;
4153
+ const wide = `moda traces get ${shellArg(contextTarget(params.conversation_id))} --all --from=${from} --max-messages=${span}`;
3317
4154
  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}`);
3318
4155
  nextCommands.push({ command: wide, purpose: `Read ±${params.window} messages around the anchor.`, mutability: "read", requires_approval: false });
3319
4156
  }
@@ -3325,8 +4162,8 @@ async function readContextItem(command, params, inputWarnings, fits) {
3325
4162
  }
3326
4163
  };
3327
4164
  }
3328
- var AUDIT_USAGE = "Usage: moda audit <conversation_id|trace_id> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] [--cursor=<next_cursor>]";
3329
- var AUDIT_BATCH_USAGE = `Usage: moda audit <id> <id> ... | --ids-from=-|<file> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] (max ${BATCH_MAX_TARGETS} targets)`;
4165
+ 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
+ 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)`;
3330
4167
  async function readAuditItem(command, params, budget) {
3331
4168
  const query2 = new URLSearchParams;
3332
4169
  if (params.kind && params.kind !== "auto")
@@ -3439,7 +4276,7 @@ function writeBatchResult(command, context, targets, results, batchWarnings, fla
3439
4276
  const nextCommands = [];
3440
4277
  if (remaining.length > 0) {
3441
4278
  const lines = remaining.map((target) => shellArg(target.raw)).join(" ");
3442
- const continueWith = [`printf '%s\\n' ${lines} |`, `moda ${command}`, "--ids-from=-", ...renderBatchFlags(flags)].join(" ");
4279
+ const continueWith = [`printf '%s\\n' ${lines} |`, canonicalCommandText(`moda ${command}`), "--ids-from=-", ...renderBatchFlags(flags)].join(" ");
3443
4280
  warnings.push(`Returned ${kept2} of ${targets.length} targets to stay under the agent output byte limit (--json has no limit). Read the other ${remaining.length} with: ${continueWith}`);
3444
4281
  nextCommands.push({
3445
4282
  command: continueWith,
@@ -3648,7 +4485,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3648
4485
  }
3649
4486
  case "search": {
3650
4487
  if (!positional || positional.trim() === "") {
3651
- throw new CliInputError("<query> is required", 'Usage: moda search "<query>" [--mode=keyword|semantic|hybrid] [--user-id=ID] [--conversation-id=ID] [--time-range=RANGE] [--since=DATE] [--until=DATE] [--include-tool-io] [--limit=N]');
4488
+ 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]');
3652
4489
  }
3653
4490
  const args = withStringIdFlags(flagsToArgs(flags, "q", positional), flags);
3654
4491
  const params = parseSpecFlags("search", args, command);
@@ -3704,12 +4541,12 @@ async function runCommand(command, positional, flags, positionals = positional ?
3704
4541
  }
3705
4542
  case "world-state": {
3706
4543
  if (!positional) {
3707
- throw new CliInputError("<conversation_id> is required", "Usage: moda world-state <conversation_id> [--summary-only] [--event-limit=N]");
4544
+ throw new CliInputError("<conversation_id> is required", "Usage: moda traces state <conversation_id> [--summary-only] [--event-limit=N]");
3708
4545
  }
3709
4546
  const args = flagsToArgs(flags, "conversation_id", positional);
3710
4547
  const params = parseSpecFlags("world-state", args, command);
3711
4548
  if (params.snapshot === true && params.replay === true) {
3712
- throw new CliInputError("Choose either --snapshot or --replay, not both.", "Usage: moda world-state <conversation_id> [--snapshot --msg-index=N [--block-index=M] [--segment-index=K]] | [--replay --message-count=N [--format=frames|fold-inputs]]");
4549
+ throw new CliInputError("Choose either --snapshot or --replay, not both.", "Usage: moda traces state <conversation_id> [--snapshot --msg-index=N [--block-index=M] [--segment-index=K]] | [--replay --message-count=N [--format=frames|fold-inputs]]");
3713
4550
  }
3714
4551
  if (params.snapshot === true) {
3715
4552
  const query3 = new URLSearchParams;
@@ -3789,9 +4626,11 @@ async function runCommand(command, positional, flags, positionals = positional ?
3789
4626
  if (params.conversation_id)
3790
4627
  query2.set("conversation_id", params.conversation_id);
3791
4628
  query2.set("family", "frustration");
3792
- const data = frustrationsFromEmotions(await callDataAPI(`/emotions?${query2.toString()}`));
3793
4629
  const includeWindow = params.include_window === true;
3794
4630
  const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
4631
+ if (agentMode && !includeWindow)
4632
+ query2.set("include_context", "0");
4633
+ const data = frustrationsFromEmotions(await callDataAPI(`/emotions?${query2.toString()}`));
3795
4634
  const anchored = withFrustrationAnchors(data);
3796
4635
  await printWithOptionalWindows(agentMode && !includeWindow ? withoutDetectionWindows(anchored, "frustrations", "conversation") : anchored, "frustrations", includeWindow, params.window ?? 1, context.output, processingDisabledWarning(data, "frustration"), agentMode && includeWindow ? (payload) => withoutRepeatedFrustrationWindow(payload) : undefined);
3797
4636
  break;
@@ -3806,7 +4645,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3806
4645
  }
3807
4646
  case "tool-failure-detail": {
3808
4647
  if (!positional) {
3809
- throw new CliInputError("<tool_name> is required", "Usage: moda tool-failure-detail <tool_name> [--subtype=S] [--days-back=N] [--limit=N (1-20, default 5)] [--offset=N] [--compact | --include-window [--window=N]]");
4648
+ throw new CliInputError("<tool_name> is required", "Usage: moda detections tools <tool_name> [--subtype=S] [--days-back=N] [--limit=N (1-20, default 5)] [--offset=N] [--compact | --include-window [--window=N]]");
3810
4649
  }
3811
4650
  const { "no-include-window": noIncludeWindow, ...toolFlags } = flags;
3812
4651
  if (noIncludeWindow !== undefined && noIncludeWindow !== "false") {
@@ -3829,13 +4668,15 @@ async function runCommand(command, positional, flags, positionals = positional ?
3829
4668
  query2.set("limit", (params.limit ?? TOOL_FAILURE_DETAIL_DEFAULT_LIMIT).toString());
3830
4669
  if (params.offset !== undefined)
3831
4670
  query2.set("offset", params.offset.toString());
4671
+ if (dropConversation)
4672
+ query2.set("include_context", "0");
3832
4673
  const queryString = query2.toString() ? `?${query2.toString()}` : "";
3833
4674
  const data = await callDataAPI(`/tool-failures/${encodeURIComponent(params.tool_name)}${queryString}`);
3834
4675
  const tfRecord = asRecord(data) ?? {};
3835
4676
  const subtypeCount = Array.isArray(tfRecord.subtypes) ? tfRecord.subtypes.length : 0;
3836
4677
  const exampleCount = Array.isArray(tfRecord.examples) ? tfRecord.examples.length : 0;
3837
4678
  const warnings = subtypeCount === 0 && exampleCount === 0 ? [
3838
- `No failures found for tool "${params.tool_name}" in the last ${params.days_back ?? 7} day(s) — the tool may have no failures in this window, or the name may not exist in this tenant. Run \`moda tool-failures\` to list tools with recorded failures, or widen --days-back.`
4679
+ `No failures found for tool "${params.tool_name}" in the last ${params.days_back ?? 7} day(s) — the tool may have no failures in this window, or the name may not exist in this tenant. Run \`moda detections tools\` to list tools with recorded failures, or widen --days-back.`
3839
4680
  ] : [];
3840
4681
  const knownSubtypes = Array.isArray(tfRecord.subtypes) ? tfRecord.subtypes.map((row) => asString(asRecord(row)?.subtype)).filter((value) => Boolean(value)) : [];
3841
4682
  if (params.subtype && knownSubtypes.length > 0 && !knownSubtypes.includes(params.subtype)) {
@@ -3893,7 +4734,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3893
4734
  break;
3894
4735
  }
3895
4736
  if (positional) {
3896
- throw new CliInputError(`Unknown problems subcommand '${positional}'.`, "Usage: moda problems [--days-back=N] [--limit=N] | moda problems remainder [--family=F] [--days-back=N] [--limit=N] [--cursor=TOKEN]. For one Problem use 'moda problem <id>'.");
4737
+ throw new CliInputError(`Unknown problems subcommand '${positional}'.`, "Usage: moda problems [--days-back=N] [--limit=N] | moda problems unexplained [--family=F] [--days-back=N] [--limit=N] [--cursor=TOKEN]. For one Problem use 'moda problems get <id>'.");
3897
4738
  }
3898
4739
  const args = flagsToArgs(flags);
3899
4740
  const params = parseSpecFlags("problems", args, command);
@@ -3920,7 +4761,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3920
4761
  break;
3921
4762
  }
3922
4763
  if (!positional) {
3923
- throw new CliInputError("<problem_id> is required", "Usage: moda problem <problem_id> [--full | --evidence|--reports|--traces|--feedback] [--limit=N] [--cursor=TOKEN] [--family=F] [--door=D]");
4764
+ throw new CliInputError("<problem_id> is required", "Usage: moda problems get <problem_id> [--full | --evidence|--reports|--traces|--feedback] [--limit=N] [--cursor=TOKEN] [--family=F] [--door=D]");
3924
4765
  }
3925
4766
  const args = flagsToArgs(flags, "id", positional);
3926
4767
  const params = parseSpecFlags("problem", args, command);
@@ -3928,11 +4769,11 @@ async function runCommand(command, positional, flags, positionals = positional ?
3928
4769
  const views = ["evidence", "reports", "conversations", "feedback"].filter((view2) => view2 === "conversations" ? traceView : params[view2] === true);
3929
4770
  if (views.length > 1) {
3930
4771
  const got = views.map((v) => v === "conversations" ? "--traces" : `--${v}`).join(" ");
3931
- throw new CliInputError(`Choose at most one of --evidence, --reports, --traces, --feedback (got ${got}).`, "Usage: moda problem <problem_id> [--evidence|--reports|--traces|--feedback]");
4772
+ throw new CliInputError(`Choose at most one of --evidence, --reports, --traces, --feedback (got ${got}).`, "Usage: moda problems get <problem_id> [--evidence|--reports|--traces|--feedback]");
3932
4773
  }
3933
4774
  const view = views[0];
3934
4775
  if (view !== undefined && params.full === true) {
3935
- throw new CliInputError("--full applies to the dossier only; the --evidence/--reports/--traces/--feedback pages are always whole.", "Usage: moda problem <problem_id> [--full] | moda problem <problem_id> --evidence|--reports|--traces|--feedback");
4776
+ 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");
3936
4777
  }
3937
4778
  if (view === undefined) {
3938
4779
  const data2 = await callDataAPI(`/problems/${encodeURIComponent(params.id)}`);
@@ -3955,7 +4796,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3955
4796
  }
3956
4797
  if (!UUID_RE.test(params.id)) {
3957
4798
  const viewFlag = view === "conversations" ? "--traces" : `--${view}`;
3958
- throw new CliInputError(`${viewFlag} requires a canonical problem UUID; got "${params.id}". Run \`moda problems\` or \`moda problem <id>\` first to resolve the id.`);
4799
+ throw new CliInputError(`${viewFlag} requires a canonical problem UUID; got "${params.id}". Run \`moda problems\` or \`moda problems get <id>\` first to resolve the id.`);
3959
4800
  }
3960
4801
  const query2 = new URLSearchParams;
3961
4802
  if (params.limit)
@@ -3975,7 +4816,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3975
4816
  }
3976
4817
  case "problem-feedback": {
3977
4818
  if (!positional) {
3978
- throw new CliInputError("<problem_id> is required", `Usage: moda problem-feedback <problem_id> --action=${PROBLEM_FEEDBACK_ACTIONS.join("|")} [--reason=TEXT] [--attribution-id=UUID] [--new-name=NAME]`);
4819
+ throw new CliInputError("<problem_id> is required", `Usage: moda problems feedback <problem_id> --action=${PROBLEM_FEEDBACK_ACTIONS.join("|")} [--reason=TEXT] [--attribution-id=UUID] [--new-name=NAME]`);
3979
4820
  }
3980
4821
  const params = ProblemFeedbackSchema.parse({
3981
4822
  id: positional,
@@ -3997,7 +4838,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3997
4838
  }
3998
4839
  if (params.action === "flag_attribution") {
3999
4840
  if (params.attribution_id === undefined || params.attribution_id.trim() === "") {
4000
- throw new CliInputError("--attribution-id=<uuid> is required for --action=flag_attribution. Run `moda problem <problem_id> --evidence` to list attribution ids.");
4841
+ throw new CliInputError("--attribution-id=<uuid> is required for --action=flag_attribution. Run `moda problems get <problem_id> --evidence` to list attribution ids.");
4001
4842
  }
4002
4843
  if (!UUID_RE.test(params.attribution_id)) {
4003
4844
  throw new CliInputError("--attribution-id must be a UUID.");
@@ -4019,7 +4860,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4019
4860
  }
4020
4861
  case "problem-linear": {
4021
4862
  if (!positional) {
4022
- throw new CliInputError("<problem_id> is required", "Usage: moda problem-linear <problem_id>");
4863
+ throw new CliInputError("<problem_id> is required", "Usage: moda problems linear <problem_id>");
4023
4864
  }
4024
4865
  if (!UUID_RE.test(positional)) {
4025
4866
  throw new CliInputError(`Problem id must be a canonical UUID; got "${positional}". Run \`moda problems\` to list problems with their canonical ids.`);
@@ -4029,17 +4870,17 @@ async function runCommand(command, positional, flags, positionals = positional ?
4029
4870
  });
4030
4871
  const link = asRecord(data)?.link;
4031
4872
  const summary = describeLinearLink(link);
4032
- const nextCommands = link ? [] : [`moda problem-linear-file ${positional}`];
4873
+ const nextCommands = link ? [] : [`moda problems link ${positional}`];
4033
4874
  if (context.outputMode === "human" && !context.quiet) {
4034
4875
  safeWriteStderr(link ? `${summary}
4035
- ` : `${summary} File one with: moda problem-linear-file ${positional}
4876
+ ` : `${summary} File one with: moda problems link ${positional}
4036
4877
  `);
4037
4878
  }
4038
4879
  context.output.writeData(data, { summary, nextCommands });
4039
4880
  break;
4040
4881
  }
4041
4882
  case "problem-linear-file": {
4042
- const usage = "Usage: moda problem-linear-file <problem_id> [--issue=ENG-123|URL] [--team-id=ID] [--title=TEXT] [--priority=0-4]";
4883
+ const usage = "Usage: moda problems link <problem_id> [--issue=ENG-123|URL] [--team-id=ID] [--title=TEXT] [--priority=0-4]";
4043
4884
  if (!positional) {
4044
4885
  throw new CliInputError("<problem_id> is required", usage);
4045
4886
  }
@@ -4118,14 +4959,14 @@ async function runCommand(command, positional, flags, positionals = positional ?
4118
4959
  }
4119
4960
  case "trace-signals": {
4120
4961
  if (!positional) {
4121
- throw new CliInputError("<conversation_id> is required", "Usage: moda trace-signals <conversation_id>");
4962
+ throw new CliInputError("<conversation_id> is required", "Usage: moda detections list --family=custom_signal --conversation-id=<conversation_id>");
4122
4963
  }
4123
4964
  const data = await callDataAPI(`/conversations/${encodeURIComponent(positional)}/custom-signals`);
4124
4965
  context.output.writeData(data);
4125
4966
  break;
4126
4967
  }
4127
4968
  case "signal-label": {
4128
- const usage = `Usage: moda signal-label <signal_id> --conversation-id=ID --label=${SIGNAL_LABELS.join("|")} [--note=TEXT] [--msg-index=N] [--msg-dedup-token=T]`;
4969
+ const usage = `Usage: moda review custom-signal <signal_id> --conversation-id=ID --verdict=correct|false_positive [--note=TEXT] [--msg-index=N] [--msg-dedup-token=T]`;
4129
4970
  if (!positional) {
4130
4971
  throw new CliInputError("<signal_id> is required", usage);
4131
4972
  }
@@ -4154,7 +4995,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4154
4995
  }
4155
4996
  case "detection-reviews": {
4156
4997
  if (!positional) {
4157
- throw new CliInputError("<conversation_id> is required", "Usage: moda detection-reviews <conversation_id> [--attribution-id=UUID]");
4998
+ throw new CliInputError("<conversation_id> is required", "Usage: moda reviews list <conversation_id> --kind=problem-detection [--attribution-id=UUID]");
4158
4999
  }
4159
5000
  const attributionId = flags["attribution-id"];
4160
5001
  if (attributionId !== undefined && !UUID_RE.test(attributionId)) {
@@ -4166,7 +5007,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4166
5007
  break;
4167
5008
  }
4168
5009
  case "detection-review": {
4169
- const usage = `Usage: moda detection-review <problem_id> --conversation-id=ID --attribution-id=UUID --verdict=${DETECTION_VERDICTS.join("|")} [--note=TEXT]`;
5010
+ const usage = `Usage: moda review problem-detection <problem_id> --conversation-id=ID --attribution-id=UUID --verdict=${DETECTION_VERDICTS.join("|")} [--note=TEXT]`;
4170
5011
  if (!positional) {
4171
5012
  throw new CliInputError("<problem_id> is required", usage);
4172
5013
  }
@@ -4193,7 +5034,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4193
5034
  break;
4194
5035
  }
4195
5036
  case "false-positive": {
4196
- const usage = `Usage: moda false-positive <${FALSE_POSITIVE_KINDS.join("|")}> <target> --reason=TEXT [--undo]`;
5037
+ const usage = `Usage: moda review <${FALSE_POSITIVE_KINDS.join("|")}> <target> --reason=TEXT [--undo]`;
4197
5038
  const [kind, target] = positionals;
4198
5039
  if (!kind || !target)
4199
5040
  throw new CliInputError("<kind> and <target> are required", usage);
@@ -4266,7 +5107,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4266
5107
  const query2 = new URLSearchParams;
4267
5108
  if (flags.kind !== undefined) {
4268
5109
  if (flags.kind === "problem" || flags.kind === "problem-detection") {
4269
- throw new CliInputError(`'moda false-positives' cannot list --kind=${flags.kind}: ${flags.kind === "problem" ? "a dismissed Problem is recorded as Problem feedback" : "a problem-detection false positive is recorded as a detection review on its Problem"}, not in the false-positive log this command reads. Nothing was sent.`, flags.kind === "problem" ? "Read it with 'moda problem <problem_id> --feedback'. --kind for this list takes " + Object.keys(kinds).join("|") + "." : "Read them per trace with 'moda detection-reviews <conversation_id>' or 'moda false-positives <conversation_id>'. --kind for this list takes " + Object.keys(kinds).join("|") + ".", "unsupported_kind");
5110
+ throw new CliInputError(`'moda reviews list' cannot list --kind=${flags.kind}: ${flags.kind === "problem" ? "a dismissed Problem is recorded as Problem feedback" : "a problem-detection false positive is recorded as a detection review on its Problem"}, not in the false-positive log this command reads. Nothing was sent.`, flags.kind === "problem" ? "Read it with 'moda problems get <problem_id> --feedback'. --kind for this list takes " + Object.keys(kinds).join("|") + "." : "Read them per trace with 'moda reviews list <conversation_id> --kind=problem-detection' or 'moda reviews list <conversation_id>'. --kind for this list takes " + Object.keys(kinds).join("|") + ".", "unsupported_kind");
4270
5111
  }
4271
5112
  if (!kinds[flags.kind])
4272
5113
  throw new CliInputError(`--kind must be one of ${Object.keys(kinds).join("|")}`);
@@ -4304,10 +5145,12 @@ async function runCommand(command, positional, flags, positionals = positional ?
4304
5145
  query2.set("user_id", params.user_id);
4305
5146
  if (params.conversation_id)
4306
5147
  query2.set("conversation_id", params.conversation_id);
5148
+ const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
5149
+ if (agentMode && params.include_window !== true)
5150
+ query2.set("include_context", "0");
4307
5151
  const queryString = query2.toString() ? `?${query2.toString()}` : "";
4308
5152
  const raw = await callDataAPI(`/emotions${queryString}`);
4309
5153
  const processingWarnings = processingDisabledWarning(raw, "emotion");
4310
- const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
4311
5154
  const record = asRecord(raw);
4312
5155
  const data = agentMode && params.include_window !== true && record ? withoutDetectionWindows(record, "detections", "conversation_context") : raw;
4313
5156
  context.output.writeData(data, processingWarnings.length > 0 ? { warnings: processingWarnings } : undefined);
@@ -4473,7 +5316,7 @@ function initAgentWriteOptions(result) {
4473
5316
  warnings: [
4474
5317
  ...manualActions.filter((action) => action.reason).map((action) => `${action.title}: ${action.reason}`),
4475
5318
  ...result.promptSync?.status === "failed" ? [`Prompt sync failed: ${result.promptSync.error}. Retry with 'moda prompts sync'.`] : [],
4476
- ...result.skillSync?.status === "failed" ? [`Skill sync failed: ${result.skillSync.error}. Retry with 'moda skills sync'.`] : [],
5319
+ ...result.skillSync?.status === "failed" ? [`Skill sync failed: ${result.skillSync.error}. Retry with 'moda skills push'.`] : [],
4477
5320
  ...result.skillSync?.warnings ?? [],
4478
5321
  ...(result.skillInstall?.warnings ?? []).map((warning) => `moda-cli skill install: ${warning}`)
4479
5322
  ]
@@ -4491,6 +5334,10 @@ function initArtifactKind(file) {
4491
5334
  return "ci_workflow";
4492
5335
  return "init_artifact";
4493
5336
  }
5337
+ async function canonicalNounHandler(context) {
5338
+ const route = routeCanonical(context.command, context.positionals, context.flags);
5339
+ throw new CliInputError(`'moda ${context.command}' was not routed${route ? ` (expected ${route.handler})` : ""}.`, `Run 'moda ${context.command} --help'.`, "unrouted_command");
5340
+ }
4494
5341
  var dataApiDefaults = {
4495
5342
  auth: "api-key",
4496
5343
  outputModes: ["human", "json", "agent"],
@@ -4535,7 +5382,7 @@ var commandRegistry = createCommandRegistry([
4535
5382
  resetApiRequestCountBeforeRun: true,
4536
5383
  telemetry: "result-and-error",
4537
5384
  handler: async (context) => {
4538
- const { runInit } = await import("./index-1wbjz3jc.js");
5385
+ const { runInit } = await import("./index-e1pf1qz2.js");
4539
5386
  if (context.outputMode === "agent-stream") {
4540
5387
  context.output.writeEvent({
4541
5388
  event: "started",
@@ -4585,7 +5432,7 @@ var commandRegistry = createCommandRegistry([
4585
5432
  resetApiRequestCountBeforeRun: true,
4586
5433
  telemetry: "result-and-error",
4587
5434
  handler: async (context) => {
4588
- const { runProvision } = await import("./provision-wzttap58.js");
5435
+ const { runProvision } = await import("./provision-q86phh4a.js");
4589
5436
  return runProvision(context, {
4590
5437
  resumeCommand: buildResumeCommand("provision", context.rawFlags)
4591
5438
  });
@@ -4869,7 +5716,7 @@ var commandRegistry = createCommandRegistry([
4869
5716
  {
4870
5717
  name: "problem-reopen",
4871
5718
  description: "Reopen a problem that was marked fixed (resolved problems return only via re-detection)",
4872
- examples: ['moda problem-reopen <problem_id> --reason="still happening"', "moda problem-reopen <problem_id> --dry-run"],
5719
+ examples: ['moda problems reopen <problem_id> --reason="still happening"', "moda problems reopen <problem_id> --dry-run"],
4873
5720
  auth: "api-key",
4874
5721
  outputModes: ["human", "json", "agent"],
4875
5722
  mutability: "write",
@@ -5015,21 +5862,23 @@ var commandRegistry = createCommandRegistry([
5015
5862
  name: "traces",
5016
5863
  aliases: ["conversations"],
5017
5864
  telemetryCommand: "conversations",
5018
- description: "Search and filter traces (agent runs); --search is ranked (hybrid when embeddings exist, else keyword), see total_kind",
5865
+ description: "Traces (agent runs): list (the bare form; --search is ranked), search, get, state, spans",
5019
5866
  examples: [
5020
- 'moda traces --search="error" --limit=5',
5021
- 'moda traces --world-state="enterprise" --outcome=positive',
5022
- 'moda traces --world-state="refund,enterprise" --include-world-state'
5867
+ 'moda traces list --search="error" --limit=5',
5868
+ "moda traces get <conversation_id> --msg-index=12 --window=3",
5869
+ 'moda traces list --world-state="refund,enterprise" --include-world-state'
5023
5870
  ],
5871
+ subcommands: treeSubcommandHelp("traces"),
5872
+ defaultSubcommand: "list",
5024
5873
  ...dataApiDefaults
5025
5874
  }),
5026
5875
  legacyCommand({
5027
5876
  name: "search",
5028
5877
  description: "Search trace messages (keyword, semantic, or hybrid)",
5029
5878
  examples: [
5030
- 'moda search "billing error"',
5031
- 'moda search "refund flow" --mode=semantic --time-range=7d --limit=10',
5032
- 'moda search "area code" --since=2026-10-07 --until=2026-10-07'
5879
+ 'moda traces search "billing error"',
5880
+ 'moda traces search "refund flow" --mode=semantic --time-range=7d --limit=10',
5881
+ 'moda traces search "area code" --since=2026-10-07 --until=2026-10-07'
5033
5882
  ],
5034
5883
  ...dataApiDefaults
5035
5884
  }),
@@ -5037,8 +5886,8 @@ var commandRegistry = createCommandRegistry([
5037
5886
  name: "world-state",
5038
5887
  description: "Get a trace's world state (slots, threads, events)",
5039
5888
  examples: [
5040
- "moda world-state <conversation_id>",
5041
- "moda world-state <conversation_id> --summary-only"
5889
+ "moda traces state <conversation_id>",
5890
+ "moda traces state <conversation_id> --summary-only"
5042
5891
  ],
5043
5892
  ...dataApiDefaults
5044
5893
  }),
@@ -5046,13 +5895,13 @@ var commandRegistry = createCommandRegistry([
5046
5895
  name: "context",
5047
5896
  description: "Get windowed trace context (messages around one turn), or the whole trace with --all",
5048
5897
  examples: [
5049
- "moda context <conversation_id> --window=3",
5050
- "moda context <conversation_id> --msg-index=40 --window=0 # --message-index is an alias",
5051
- "moda context <conversation_id> --metadata-only # summary + total_messages, no messages",
5052
- "moda context <conversation_id> --all",
5053
- "moda context <conversation_id> --all --from=500 --max-messages=500",
5054
- "moda context <id>@12 <id2>@3 <id3> --window=2 # batch: up to 25 targets, one envelope; id@N anchors at message N",
5055
- "moda context --ids-from=ids.txt --window=1 # one target per line (id or id@N); --ids-from=- reads stdin"
5898
+ "moda traces get <conversation_id> --window=3",
5899
+ "moda traces get <conversation_id> --msg-index=40 --window=0 # --message-index is an alias",
5900
+ "moda traces get <conversation_id> --metadata-only # summary + total_messages, no messages",
5901
+ "moda traces get <conversation_id> --all",
5902
+ "moda traces get <conversation_id> --all --from=500 --max-messages=500",
5903
+ "moda traces get <id>@12 <id2>@3 <id3> --window=2 # batch: up to 25 targets, one envelope; id@N anchors at message N",
5904
+ "moda traces get --ids-from=ids.txt --window=1 # one target per line (id or id@N); --ids-from=- reads stdin"
5056
5905
  ],
5057
5906
  ...dataApiDefaults
5058
5907
  }),
@@ -5061,13 +5910,13 @@ var commandRegistry = createCommandRegistry([
5061
5910
  aliases: ["trace"],
5062
5911
  description: "Raw non-deduped span audit for one trace or OTLP trace_id (hierarchy, tool spans, orphans, duplicates)",
5063
5912
  examples: [
5064
- "moda audit <conversation_id|trace_id>",
5065
- "moda audit <trace_id> --kind=trace --json",
5066
- "moda audit <conversation_id> --include-raw",
5067
- "moda audit <conversation_id> --include-bodies # keep prompt/response bodies (omitted by default; long strings truncated)",
5068
- "moda audit <conversation_id> --limit=1000 # ingest rows to read (default 200, max 1000); warns when capped",
5069
- "moda audit <conversation_id> --cursor=<next_cursor> # next page of a capped audit",
5070
- "moda audit <id1> <id2> <id3> # batch: up to 25 ids, one envelope (or --ids-from=-|<file>)"
5913
+ "moda traces spans <conversation_id|trace_id>",
5914
+ "moda traces spans <trace_id> --kind=trace --json",
5915
+ "moda traces spans <conversation_id> --include-raw",
5916
+ "moda traces spans <conversation_id> --include-bodies # keep prompt/response bodies (omitted by default; long strings truncated)",
5917
+ "moda traces spans <conversation_id> --limit=1000 # ingest rows to read (default 200, max 1000); warns when capped",
5918
+ "moda traces spans <conversation_id> --cursor=<next_cursor> # next page of a capped audit",
5919
+ "moda traces spans <id1> <id2> <id3> # batch: up to 25 ids, one envelope (or --ids-from=-|<file>)"
5071
5920
  ],
5072
5921
  ...dataApiDefaults
5073
5922
  }),
@@ -5075,32 +5924,32 @@ var commandRegistry = createCommandRegistry([
5075
5924
  name: "frustrations",
5076
5925
  description: "User frustration detections: the frustration family of `emotions` (same data as the dashboard), legacy field names",
5077
5926
  examples: [
5078
- "moda frustrations --days-back=14",
5079
- "moda frustrations --days-back=14 --include-window # agent output omits conversation windows unless asked"
5927
+ "moda detections list --family=frustration --days-back=14",
5928
+ "moda detections list --family=frustration --days-back=14 --include-window # agent output omits conversation windows unless asked"
5080
5929
  ],
5081
5930
  ...dataApiDefaults
5082
5931
  }),
5083
5932
  legacyCommand({
5084
5933
  name: "tool-failures",
5085
5934
  description: "Get tool failure overview",
5086
- examples: ["moda tool-failures --days-back=14"],
5935
+ examples: ["moda detections tools --days-back=14"],
5087
5936
  ...dataApiDefaults
5088
5937
  }),
5089
5938
  legacyCommand({
5090
5939
  name: "tool-failure-detail",
5091
5940
  description: "Get per-tool failure detail (each example embeds its anchor ±2 messages)",
5092
5941
  examples: [
5093
- "moda tool-failure-detail search",
5094
- "moda tool-failure-detail search --compact # no embedded conversation (same as --include-window=false)",
5095
- "moda tool-failure-detail search --include-window --window=3"
5942
+ "moda detections tools search",
5943
+ "moda detections tools search --compact # no embedded conversation (same as --include-window=false)",
5944
+ "moda detections tools search --include-window --window=3"
5096
5945
  ],
5097
5946
  ...dataApiDefaults
5098
5947
  }),
5099
5948
  legacyCommand({
5100
5949
  name: "problems",
5101
5950
  description: "Rank cross-signal Problems by root cause (what to fix first)",
5102
- examples: ["moda problems", "moda problems --days-back=7 --limit=10", "moda problems remainder --family=emotion"],
5103
- subcommands: PROBLEMS_SUBCOMMAND_HELP,
5951
+ examples: ["moda problems list", "moda problems list --days-back=7 --limit=10", "moda problems get <problem_id> --evidence", "moda problems unexplained --family=emotion"],
5952
+ subcommands: treeSubcommandHelp("problems"),
5104
5953
  defaultSubcommand: "list",
5105
5954
  ...dataApiDefaults
5106
5955
  }),
@@ -5108,14 +5957,14 @@ var commandRegistry = createCommandRegistry([
5108
5957
  name: "problem-impact",
5109
5958
  description: "Live problem/use-case impact, full analysis denominators, daily recurrence, and paginated evidence",
5110
5959
  examples: [
5111
- "moda problem-impact --mode=catalog",
5112
- "moda problem-impact --node-id=node_0 --days-back=all",
5113
- "moda problem-impact --problem-id=<uuid> --mode=metrics",
5114
- "moda problem-impact --node-id=node_0 --problem-id=<uuid> --mode=evidence",
5115
- "moda problem-impact --node-id=node_0 --mode=trend --days-back=30",
5116
- "moda problem-impact --mode=overview --days-back=7",
5117
- "moda problem-impact --problem-id=<uuid> --mode=affected-use-cases",
5118
- `moda problem-impact --problem-id=<uuid> --mode=trace-use-cases --conversation-ids='["trace_id"]'`
5960
+ "moda problems impact --mode=catalog",
5961
+ "moda problems impact --node-id=node_0 --days-back=all",
5962
+ "moda problems impact --problem-id=<uuid> --mode=metrics",
5963
+ "moda problems impact --node-id=node_0 --problem-id=<uuid> --mode=evidence",
5964
+ "moda problems impact --node-id=node_0 --mode=trend --days-back=30",
5965
+ "moda problems impact --mode=overview --days-back=7",
5966
+ "moda problems impact --problem-id=<uuid> --mode=affected-use-cases",
5967
+ `moda problems impact --problem-id=<uuid> --mode=trace-use-cases --conversation-ids='["trace_id"]'`
5119
5968
  ],
5120
5969
  ...dataApiDefaults
5121
5970
  }),
@@ -5123,12 +5972,12 @@ var commandRegistry = createCommandRegistry([
5123
5972
  name: "problem",
5124
5973
  description: "Open one Problem: dossier, or --evidence/--reports/--traces/--feedback pages (--conversations is a legacy alias of --traces)",
5125
5974
  examples: [
5126
- "moda problem <problem_id>",
5127
- "moda problem <problem_id> --full # whole dossier (agent output defaults to a compact one)",
5128
- "moda problem <problem_id> --evidence --limit=10",
5129
- "moda problem <problem_id> --reports",
5130
- "moda problem <problem_id> --traces --family=tool_failure",
5131
- "moda problem <problem_id> --feedback"
5975
+ "moda problems get <problem_id>",
5976
+ "moda problems get <problem_id> --full # whole dossier (agent output defaults to a compact one)",
5977
+ "moda problems get <problem_id> --evidence --limit=10",
5978
+ "moda problems get <problem_id> --reports",
5979
+ "moda problems get <problem_id> --traces --family=tool_failure",
5980
+ "moda problems get <problem_id> --feedback"
5132
5981
  ],
5133
5982
  ...dataApiDefaults
5134
5983
  }),
@@ -5136,10 +5985,10 @@ var commandRegistry = createCommandRegistry([
5136
5985
  name: "problem-feedback",
5137
5986
  description: "Submit Problem feedback: mark fixed, dismiss, rename, or flag a bad attribution",
5138
5987
  examples: [
5139
- "moda problem-feedback <problem_id> --action=mark_fixed",
5140
- 'moda problem-feedback <problem_id> --action=dismiss --reason="not actionable"',
5141
- 'moda problem-feedback <problem_id> --action=rename --new-name="Better title"',
5142
- 'moda problem-feedback <problem_id> --action=flag_attribution --attribution-id=<uuid> --reason="wrong trace"'
5988
+ "moda problems feedback <problem_id> --action=mark_fixed",
5989
+ 'moda problems feedback <problem_id> --action=dismiss --reason="not actionable"',
5990
+ 'moda problems feedback <problem_id> --action=rename --new-name="Better title"',
5991
+ 'moda problems feedback <problem_id> --action=flag_attribution --attribution-id=<uuid> --reason="wrong trace"'
5143
5992
  ],
5144
5993
  ...dataApiDefaults,
5145
5994
  mutability: "write"
@@ -5147,16 +5996,16 @@ var commandRegistry = createCommandRegistry([
5147
5996
  legacyCommand({
5148
5997
  name: "problem-linear",
5149
5998
  description: "Show the Linear issue linked to a Problem (key, state, URL); needs Linear connected (Integrations -> Linear)",
5150
- examples: ["moda problem-linear <problem_id>"],
5999
+ examples: ["moda problems linear <problem_id>"],
5151
6000
  ...dataApiDefaults
5152
6001
  }),
5153
6002
  legacyCommand({
5154
6003
  name: "problem-linear-file",
5155
6004
  description: "File a Problem as a Linear issue in the team's tracker, or link an existing issue with --issue; needs Linear connected (Integrations -> Linear)",
5156
6005
  examples: [
5157
- "moda problem-linear-file <problem_id>",
5158
- 'moda problem-linear-file <problem_id> --team-id=<team_id> --title="CRM lookups time out" --priority=2',
5159
- "moda problem-linear-file <problem_id> --issue=ENG-123"
6006
+ "moda problems link <problem_id>",
6007
+ 'moda problems link <problem_id> --team-id=<team_id> --title="CRM lookups time out" --priority=2',
6008
+ "moda problems link <problem_id> --issue=ENG-123"
5160
6009
  ],
5161
6010
  ...dataApiDefaults,
5162
6011
  mutability: "write"
@@ -5165,8 +6014,8 @@ var commandRegistry = createCommandRegistry([
5165
6014
  name: "detection-reviews",
5166
6015
  description: "List problem detections on a trace with their review state (correct / false_positive / clear)",
5167
6016
  examples: [
5168
- "moda detection-reviews <conversation_id>",
5169
- "moda detection-reviews <conversation_id> --attribution-id=<uuid>"
6017
+ "moda reviews list <conversation_id> --kind=problem-detection",
6018
+ "moda reviews list <conversation_id> --kind=problem-detection --attribution-id=<uuid>"
5170
6019
  ],
5171
6020
  ...dataApiDefaults
5172
6021
  }),
@@ -5174,9 +6023,9 @@ var commandRegistry = createCommandRegistry([
5174
6023
  name: "detection-review",
5175
6024
  description: "Review a problem detection on a trace: mark it correct, a false positive, or clear the review",
5176
6025
  examples: [
5177
- 'moda detection-review <problem_id> --conversation-id=<id> --attribution-id=<uuid> --verdict=false_positive --note="expected retry"',
5178
- "moda detection-review <problem_id> --conversation-id=<id> --attribution-id=<uuid> --verdict=correct",
5179
- "moda detection-review <problem_id> --conversation-id=<id> --attribution-id=<uuid> --verdict=clear"
6026
+ 'moda review problem-detection <problem_id> --conversation-id=<id> --attribution-id=<uuid> --verdict=false_positive --note="expected retry"',
6027
+ "moda review problem-detection <problem_id> --conversation-id=<id> --attribution-id=<uuid> --verdict=correct",
6028
+ "moda review problem-detection <problem_id> --conversation-id=<id> --attribution-id=<uuid> --verdict=clear"
5180
6029
  ],
5181
6030
  ...dataApiDefaults,
5182
6031
  mutability: "write"
@@ -5185,17 +6034,17 @@ var commandRegistry = createCommandRegistry([
5185
6034
  name: "false-positive",
5186
6035
  description: "Flag a detection as a false positive with a reason: problem, problem-detection, quote, tool, laziness, hallucination, or custom-signal",
5187
6036
  helpNotes: [
5188
- "Kinds: problem <problem_id> dismisses the Problem (hidden at once, logged for review); problem-detection <problem_id> marks one detection on a trace (needs --conversation-id and --attribution-id); quote <conversation_id> needs --family, --turn and --quote (from moda emotions); tool <conversation_id> needs --tool and --tool-use-id or --msg-index; laziness and hallucination <conversation_id> mark that trace; custom-signal <signal_id> needs --conversation-id and is saved as a does_not_fit label. quote, tool, laziness and hallucination marks are audit-only."
6037
+ "Kinds: problem <problem_id> dismisses the Problem (hidden at once, logged for review); problem-detection <problem_id> marks one detection on a trace (needs --conversation-id and --attribution-id); quote <conversation_id> needs --family, --turn and --quote (from moda detections list --family=emotion); tool <conversation_id> needs --tool and --tool-use-id or --msg-index; laziness and hallucination <conversation_id> mark that trace; custom-signal <signal_id> needs --conversation-id and is saved as a does_not_fit label. quote, tool, laziness and hallucination marks are audit-only."
5189
6038
  ],
5190
6039
  examples: [
5191
- 'moda false-positive problem <problem_id> --reason="two unrelated tools"',
5192
- 'moda false-positive problem-detection <problem_id> --conversation-id=<id> --attribution-id=<uuid> --reason="refund was approved"',
5193
- 'moda false-positive quote <conversation_id> --family=frustration --turn=3 --quote="this is useless lol" --reason="joking"',
5194
- 'moda false-positive tool <conversation_id> --tool=send_invoice --tool-use-id=<id> --reason="retried upstream"',
5195
- 'moda false-positive laziness <conversation_id> --reason="reload after context reset is expected"',
5196
- "moda false-positive hallucination <conversation_id> --undo",
5197
- "moda false-positive custom-signal <signal_id> --conversation-id=<id> --undo",
5198
- 'moda false-positive custom-signal <signal_id> --conversation-id=<id> --reason="refund was approved"'
6040
+ 'moda review problem <problem_id> --reason="two unrelated tools"',
6041
+ 'moda review problem-detection <problem_id> --conversation-id=<id> --attribution-id=<uuid> --reason="refund was approved"',
6042
+ 'moda review quote <conversation_id> --family=frustration --turn=3 --quote="this is useless lol" --reason="joking"',
6043
+ 'moda review tool <conversation_id> --tool=send_invoice --tool-use-id=<id> --reason="retried upstream"',
6044
+ 'moda review laziness <conversation_id> --reason="reload after context reset is expected"',
6045
+ "moda review hallucination <conversation_id> --verdict=clear",
6046
+ "moda review custom-signal <signal_id> --conversation-id=<id> --verdict=clear",
6047
+ 'moda review custom-signal <signal_id> --conversation-id=<id> --reason="refund was approved"'
5199
6048
  ],
5200
6049
  ...dataApiDefaults,
5201
6050
  mutability: "write"
@@ -5204,9 +6053,9 @@ var commandRegistry = createCommandRegistry([
5204
6053
  name: "false-positives",
5205
6054
  description: "List current false-positive marks across the workspace, or on one trace (with problem detection reviews)",
5206
6055
  examples: [
5207
- "moda false-positives",
5208
- "moda false-positives --kind=tool --all",
5209
- "moda false-positives <conversation_id>"
6056
+ "moda reviews list",
6057
+ "moda reviews list --kind=tool --all",
6058
+ "moda reviews list <conversation_id>"
5210
6059
  ],
5211
6060
  ...dataApiDefaults
5212
6061
  }),
@@ -5229,22 +6078,22 @@ var commandRegistry = createCommandRegistry([
5229
6078
  description: "Open one custom signal, or --detections for its recent hits (paged)",
5230
6079
  examples: [
5231
6080
  "moda signal <signal_id>",
5232
- "moda signal <signal_id> --detections --days-back=30 --limit=20"
6081
+ "moda detections list --family=custom_signal --signal-id=<signal_id> --days-back=30 --limit=20"
5233
6082
  ],
5234
6083
  ...dataApiDefaults
5235
6084
  }),
5236
6085
  legacyCommand({
5237
6086
  name: "trace-signals",
5238
6087
  description: "Custom signal hits on one trace",
5239
- examples: ["moda trace-signals <conversation_id>"],
6088
+ examples: ["moda detections list --family=custom_signal --conversation-id=<conversation_id>"],
5240
6089
  ...dataApiDefaults
5241
6090
  }),
5242
6091
  legacyCommand({
5243
6092
  name: "signal-label",
5244
6093
  description: "Label a trace for a custom signal: fits, or does_not_fit to mark a false positive",
5245
6094
  examples: [
5246
- 'moda signal-label <signal_id> --conversation-id=<id> --label=does_not_fit --note="refund was approved"',
5247
- "moda signal-label <signal_id> --conversation-id=<id> --label=fits --msg-index=12"
6095
+ 'moda review custom-signal <signal_id> --conversation-id=<id> --verdict=false_positive --note="refund was approved"',
6096
+ "moda review custom-signal <signal_id> --conversation-id=<id> --verdict=correct --msg-index=12"
5248
6097
  ],
5249
6098
  ...dataApiDefaults,
5250
6099
  mutability: "write"
@@ -5253,9 +6102,9 @@ var commandRegistry = createCommandRegistry([
5253
6102
  name: "emotions",
5254
6103
  description: "Multi-family emotion detections (frustration, disapproval, sadness, confusion, anxiety, trust, positive)",
5255
6104
  examples: [
5256
- "moda emotions --days-back=7",
5257
- "moda emotions --family=confusion --limit=20",
5258
- "moda emotions --include-window # keep conversation windows in agent output"
6105
+ "moda detections list --family=emotion --days-back=7",
6106
+ "moda detections list --family=emotion:confusion --limit=20",
6107
+ "moda detections list --family=emotion --include-window # keep conversation windows in agent output"
5259
6108
  ],
5260
6109
  ...dataApiDefaults
5261
6110
  }),
@@ -5263,8 +6112,8 @@ var commandRegistry = createCommandRegistry([
5263
6112
  name: "laziness",
5264
6113
  description: "Laziness detections per trace: repeated tool calls, premature handoffs, and other lazy patterns",
5265
6114
  examples: [
5266
- "moda laziness --days-back=30",
5267
- "moda laziness --conversation-id=<id>"
6115
+ "moda detections list --family=laziness --days-back=30",
6116
+ "moda detections list --family=laziness --conversation-id=<id>"
5268
6117
  ],
5269
6118
  ...dataApiDefaults
5270
6119
  }),
@@ -5272,9 +6121,9 @@ var commandRegistry = createCommandRegistry([
5272
6121
  name: "hallucinations",
5273
6122
  description: "Grounding detections: contradicted/verified outputs with rule breakdown",
5274
6123
  examples: [
5275
- "moda hallucinations --days-back=30",
5276
- "moda hallucinations --kind=contradicted --limit=20",
5277
- "moda hallucinations --conversation-id=<id>"
6124
+ "moda detections list --family=hallucination --days-back=30",
6125
+ "moda detections list --family=hallucination --kind=contradicted --limit=20",
6126
+ "moda detections list --family=hallucination --conversation-id=<id>"
5278
6127
  ],
5279
6128
  ...dataApiDefaults
5280
6129
  }),
@@ -5296,6 +6145,44 @@ var commandRegistry = createCommandRegistry([
5296
6145
  ],
5297
6146
  ...dataApiDefaults
5298
6147
  }),
6148
+ {
6149
+ name: "detections",
6150
+ description: NOUN_SUMMARIES.detections,
6151
+ examples: [...leavesOf("detections").flatMap((leaf) => leaf.examples.slice(0, 2))],
6152
+ subcommands: treeSubcommandHelp("detections"),
6153
+ auth: "api-key",
6154
+ outputModes: ["human", "json", "agent"],
6155
+ mutability: "read",
6156
+ validateConfigBeforeRun: false,
6157
+ resetApiRequestCountBeforeRun: false,
6158
+ telemetry: "result-and-error",
6159
+ handler: canonicalNounHandler
6160
+ },
6161
+ {
6162
+ name: "review",
6163
+ description: NOUN_SUMMARIES.review,
6164
+ examples: [...leafFor("review", undefined)?.examples ?? []],
6165
+ auth: "api-key",
6166
+ outputModes: ["human", "json", "agent"],
6167
+ mutability: "write",
6168
+ validateConfigBeforeRun: false,
6169
+ resetApiRequestCountBeforeRun: false,
6170
+ telemetry: "result-and-error",
6171
+ handler: canonicalNounHandler
6172
+ },
6173
+ {
6174
+ name: "reviews",
6175
+ description: NOUN_SUMMARIES.reviews,
6176
+ examples: [...leavesOf("reviews").flatMap((leaf) => leaf.examples)],
6177
+ subcommands: treeSubcommandHelp("reviews"),
6178
+ auth: "api-key",
6179
+ outputModes: ["human", "json", "agent"],
6180
+ mutability: "read",
6181
+ validateConfigBeforeRun: false,
6182
+ resetApiRequestCountBeforeRun: false,
6183
+ telemetry: "result-and-error",
6184
+ handler: canonicalNounHandler
6185
+ },
5299
6186
  legacyCommand({
5300
6187
  name: "feedback",
5301
6188
  description: "Flag wrong/missing data or CLI quirks to the Moda team",
@@ -5417,18 +6304,18 @@ var commandRegistry = createCommandRegistry([
5417
6304
  }),
5418
6305
  {
5419
6306
  name: "skills",
5420
- description: "Generate, pull, and sync Moda skills",
6307
+ description: "Generate, pull (download), push (upload), and install Moda skills",
5421
6308
  examples: [
5422
6309
  "moda skills gen",
5423
6310
  "moda skills status",
5424
6311
  "moda skills pull",
5425
- "moda skills sync",
6312
+ "moda skills push",
5426
6313
  "moda skills install --list",
5427
6314
  "moda skills install integration-node-openai"
5428
6315
  ],
5429
6316
  subcommands: SKILLS_SUBCOMMAND_HELP,
5430
6317
  auth: "api-key",
5431
- authNote: "install is local; gen, status, pull, sync, list, show, policy, and proposals need the API key.",
6318
+ authNote: "install is local; gen, status, pull, push, list, show, policy, and proposals need the API key.",
5432
6319
  outputModes: ["human", "json", "agent"],
5433
6320
  mutability: "write",
5434
6321
  validateConfigBeforeRun: false,
@@ -5533,44 +6420,45 @@ function cliValidationReason(error) {
5533
6420
  return;
5534
6421
  }
5535
6422
  function printCliError(error) {
6423
+ const safeWriteStderr2 = (text) => safeWriteStderr(canonicalCommandText(text));
5536
6424
  if (error instanceof CliInputError) {
5537
- safeWriteStderr(`Error: ${error.message}
6425
+ safeWriteStderr2(`Error: ${error.message}
5538
6426
  `);
5539
6427
  if (error.usage)
5540
- safeWriteStderr(`${error.usage}
6428
+ safeWriteStderr2(`${error.usage}
5541
6429
  `);
5542
6430
  } else if (error instanceof CliAccessError) {
5543
- safeWriteStderr(`Error: ${error.message}
6431
+ safeWriteStderr2(`Error: ${error.message}
5544
6432
  `);
5545
6433
  if (error.guidance)
5546
- safeWriteStderr(`${error.guidance}
6434
+ safeWriteStderr2(`${error.guidance}
5547
6435
  `);
5548
6436
  } else if (error instanceof z.ZodError) {
5549
6437
  const issues = error.errors.map((e) => {
5550
6438
  const field = e.path.length ? e.path.join(".") : "input";
5551
6439
  return ` ${field}: ${e.message}`;
5552
6440
  });
5553
- safeWriteStderr(`Validation error:
6441
+ safeWriteStderr2(`Validation error:
5554
6442
  ${[...new Set(issues)].join(`
5555
6443
  `)}
5556
6444
  `);
5557
6445
  } else if (error instanceof ApiError && error.readable) {
5558
- safeWriteStderr(`${error.message}
6446
+ safeWriteStderr2(`${error.message}
5559
6447
  `);
5560
6448
  if (error.suggestions?.length) {
5561
- safeWriteStderr(`Try:
6449
+ safeWriteStderr2(`Try:
5562
6450
  ${error.suggestions.map((s) => ` ${s}`).join(`
5563
6451
  `)}
5564
6452
  `);
5565
6453
  }
5566
6454
  } else if (error instanceof ApiError) {
5567
- safeWriteStderr(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}
6455
+ safeWriteStderr2(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}
5568
6456
  `);
5569
6457
  } else if (error instanceof Error) {
5570
- safeWriteStderr(`Error: ${error.message}
6458
+ safeWriteStderr2(`Error: ${error.message}
5571
6459
  `);
5572
6460
  } else {
5573
- safeWriteStderr(`Error: ${String(error)}
6461
+ safeWriteStderr2(`Error: ${String(error)}
5574
6462
  `);
5575
6463
  }
5576
6464
  }
@@ -5592,14 +6480,18 @@ ${colorOk ? `\x1B[2m${text}\x1B[0m` : text}
5592
6480
  function unknownCommandError(command) {
5593
6481
  const visible = commandRegistry.all().filter((entry) => !entry.hidden);
5594
6482
  const names = visible.flatMap((entry) => [entry.name, ...entry.aliases ?? []]);
5595
- const matches = closestMatches(command, names);
6483
+ const canonical = (name) => {
6484
+ const legacy = legacyNameFor(name, undefined, {});
6485
+ return legacy ? usePath(legacy) : name;
6486
+ };
6487
+ const matches = [...new Set(closestMatches(command, names).map(canonical))];
5596
6488
  const suggestion = matches.length > 0 ? ` Did you mean ${matches.map((name) => `'${name}'`).join(" or ")}?` : "";
5597
6489
  return new CliInputError(`Unknown command '${command}'.${suggestion}`, [
5598
6490
  ...matches.map((name) => `\`moda ${name} --help\``),
5599
6491
  "Run `moda --help` for the command list."
5600
6492
  ].join(" "), "unknown_command", {
5601
6493
  field: "<command>",
5602
- allowed: visible.map((entry) => entry.name),
6494
+ allowed: visible.map((entry) => entry.name).filter((name) => !legacyNameFor(name, undefined, {})),
5603
6495
  ...matches.length > 0 ? { didYouMean: matches } : {},
5604
6496
  suggestedCommands: [...matches.map((name) => `moda ${name} --help`), "moda --help"]
5605
6497
  });
@@ -5628,10 +6520,50 @@ async function dispatchParsedCommand(parsed, startedAt = Date.now()) {
5628
6520
  process.env.MODA_PROFILE = previousProfileEnv;
5629
6521
  }
5630
6522
  }
5631
- async function dispatchParsedCommandInner(parsed, startedAt) {
5632
- const { command, flags } = parsed;
6523
+ function usageIdentityFor(parsed, route, legacy, definition) {
6524
+ if (route)
6525
+ return { command: route.leaf.noun, subcommand: route.telemetrySubcommand };
6526
+ if (legacy) {
6527
+ const kind = legacy.legacy === "false-positive" && FALSE_POSITIVE_KINDS.includes(parsed.positional ?? "") ? parsed.positional : undefined;
6528
+ return { ...legacy.canonical, ...kind ? { subcommand: kind } : {}, legacyAlias: legacy.legacy };
6529
+ }
6530
+ return definition ? canonicalTelemetryForDefault(definition.name, parsed.positional) : undefined;
6531
+ }
6532
+ function rejectedRouteIdentity(parsed) {
6533
+ if (parsed.command === "review") {
6534
+ const kind = FALSE_POSITIVE_KINDS.includes(parsed.positional ?? "") ? parsed.positional : undefined;
6535
+ return { command: "review", ...kind ? { subcommand: kind } : {} };
6536
+ }
6537
+ const leaf = leafFor(parsed.command, parsed.positional);
6538
+ return { command: parsed.command, ...leaf ? { subcommand: leaf.verb } : {} };
6539
+ }
6540
+ function assertRoutedFlags(route, flags) {
6541
+ if (!route.allowedFlags)
6542
+ return;
6543
+ const shape = z.object(Object.fromEntries(route.allowedFlags.map((flag) => [flag.replace(/-/g, "_"), z.unknown()])));
6544
+ const aliases = new Set(Object.keys(route.flagAliases ?? {}));
6545
+ const args = Object.fromEntries(Object.keys(flags).filter((flag) => !aliases.has(flag.replace(/_/g, "-"))).map((flag) => [flag.replace(/-/g, "_"), true]));
6546
+ parseCommandFlags(shape.passthrough(), args, route.label, route.flagAliases ? { flagAliases: route.flagAliases } : {});
6547
+ }
6548
+ function renameErrorCommands(error, renames) {
6549
+ if (error instanceof CliInputError) {
6550
+ const details2 = error.details;
6551
+ const renamed = new CliInputError(renameCommands(error.message, renames), error.usage === undefined ? undefined : renameCommands(error.usage, renames), error.reason, details2 ? { ...details2, ...details2.suggestedCommands ? { suggestedCommands: details2.suggestedCommands.map((command) => renameCommands(command, renames)) } : {} } : undefined);
6552
+ const warnings = error.warnings;
6553
+ if (warnings !== undefined)
6554
+ Object.assign(renamed, { warnings });
6555
+ return renamed;
6556
+ }
6557
+ const details = inputDetailsOf(error);
6558
+ if (details?.suggestedCommands && error !== null && typeof error === "object") {
6559
+ attachInputDetails(error, { ...details, suggestedCommands: details.suggestedCommands.map((command) => renameCommands(command, renames)) });
6560
+ }
6561
+ return error;
6562
+ }
6563
+ async function dispatchParsedCommandInner(typedParsed, startedAt) {
6564
+ const { command, flags } = typedParsed;
5633
6565
  const switchError = normalizeBooleanFlags(flags);
5634
- const commandFlags = stripUpdateCheckFlag(stripTelemetryFlag(flags));
6566
+ const typedFlags = stripUpdateCheckFlag(stripTelemetryFlag(flags));
5635
6567
  const telemetryDisabled = isTelemetryDisabled(flags);
5636
6568
  const updateCheckDisabled = isUpdateCheckDisabled(flags);
5637
6569
  if (command === "--version" || command === "-V" || flags.version === "true") {
@@ -5639,9 +6571,11 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5639
6571
  return 0;
5640
6572
  }
5641
6573
  if (command === "--help" || command === "-h" || flags.help === "true") {
6574
+ if (command && printTreeHelp(typedParsed))
6575
+ return 0;
5642
6576
  const helpTarget = command && command !== "--help" && command !== "-h" ? commandRegistry.find(command) : undefined;
5643
6577
  if (helpTarget) {
5644
- const subcommandName = parsed.positional;
6578
+ const subcommandName = typedParsed.positional;
5645
6579
  const subcommandEntry = subcommandName ? helpTarget.subcommands?.find((entry) => entry.name === subcommandName) : undefined;
5646
6580
  if (subcommandEntry) {
5647
6581
  printSubcommandHelp(helpTarget, subcommandEntry);
@@ -5661,62 +6595,117 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5661
6595
  printHelp();
5662
6596
  return 1;
5663
6597
  }
5664
- const failEarly = (error, commandDefinition) => {
5665
- const mode = resolveOutputMode(commandFlags, {
5666
- isTTY: process.stdout.isTTY,
5667
- env: process.env
6598
+ let route;
6599
+ let routeError;
6600
+ try {
6601
+ route = routeCanonical(command, typedParsed.positionals, typedFlags);
6602
+ } catch (error) {
6603
+ routeError = error;
6604
+ }
6605
+ const legacy = route || routeError ? undefined : legacyNameFor(command, typedParsed.positional, typedFlags);
6606
+ const parsed = route ? {
6607
+ ...typedParsed,
6608
+ command: route.handler,
6609
+ positional: route.positionals[0],
6610
+ positionals: route.positionals,
6611
+ flags: route.rewriteFlags(flags)
6612
+ } : typedParsed;
6613
+ const commandFlags = route ? route.rewriteFlags(typedFlags) : typedFlags;
6614
+ const outputModeFor = () => resolveOutputMode(typedFlags, {
6615
+ isTTY: process.stdout.isTTY,
6616
+ env: process.env
6617
+ });
6618
+ const reportUsage = (input) => {
6619
+ const { definition: definition2, identity: identity2 } = input;
6620
+ const handlerTelemetry = definition2 ? telemetryCommandName(definition2) : identity2?.command;
6621
+ if (!handlerTelemetry)
6622
+ return;
6623
+ const telemetryProfile = existingFlagProfile(typedFlags);
6624
+ const adoption = buildSearchAdoptionTelemetry({
6625
+ command: handlerTelemetry,
6626
+ positional: parsed.positional,
6627
+ flags: commandFlags,
6628
+ outputMode: input.outputMode,
6629
+ recorded: input.recorded,
6630
+ skillVersion: process.env.MODA_SKILL_VERSION,
6631
+ subcommands: definition2 ? telemetrySubcommandNames(definition2) : [],
6632
+ defaultSubcommand: definition2?.defaultSubcommand
6633
+ });
6634
+ if (identity2 && adoption) {
6635
+ if (identity2.subcommand)
6636
+ adoption.subcommand = identity2.subcommand;
6637
+ else
6638
+ delete adoption.subcommand;
6639
+ }
6640
+ sendCliUsageTelemetry({
6641
+ apiKey: input.apiKey ?? resolveApiKey({ profile: telemetryProfile }),
6642
+ profile: telemetryProfile,
6643
+ command: identity2?.command ?? handlerTelemetry,
6644
+ flags: typedFlags,
6645
+ status: input.status,
6646
+ ...input.error !== undefined ? { errorType: classifyCliError(input.error), validationReason: cliValidationReason(input.error) } : {},
6647
+ exitCode: input.exitCode,
6648
+ durationMs: Date.now() - startedAt,
6649
+ apiRequestCount: input.apiRequestCount,
6650
+ adoption,
6651
+ ...identity2?.legacyAlias ? { legacyAlias: identity2.legacyAlias } : {}
5668
6652
  });
6653
+ };
6654
+ const failEarly = (error, commandDefinition, identity2) => {
6655
+ const mode = outputModeFor();
5669
6656
  const telemetryOptOut = telemetryDisabled || flags["no-telemetry"] !== undefined && flags["no-telemetry"] !== "false";
5670
- if (commandDefinition && !telemetryOptOut && commandDefinition.telemetry === "result-and-error") {
5671
- const telemetryCommand2 = telemetryCommandName(commandDefinition);
5672
- const telemetryProfile = existingFlagProfile(commandFlags);
5673
- sendCliUsageTelemetry({
5674
- apiKey: resolveApiKey({ profile: telemetryProfile }),
5675
- profile: telemetryProfile,
5676
- command: telemetryCommand2,
5677
- flags: commandFlags,
6657
+ const reports = commandDefinition ? commandDefinition.telemetry === "result-and-error" : identity2 !== undefined;
6658
+ if (reports && !telemetryOptOut) {
6659
+ reportUsage({
6660
+ definition: commandDefinition,
6661
+ identity: identity2,
5678
6662
  status: "error",
5679
- errorType: classifyCliError(error),
5680
- validationReason: cliValidationReason(error),
6663
+ error,
5681
6664
  exitCode: 1,
5682
- durationMs: Date.now() - startedAt,
5683
6665
  apiRequestCount: 0,
5684
- adoption: buildSearchAdoptionTelemetry({
5685
- command: telemetryCommand2,
5686
- positional: parsed.positional,
5687
- flags: commandFlags,
5688
- outputMode: mode,
5689
- recorded: {},
5690
- skillVersion: process.env.MODA_SKILL_VERSION,
5691
- subcommands: telemetrySubcommandNames(commandDefinition),
5692
- defaultSubcommand: commandDefinition.defaultSubcommand
5693
- })
6666
+ outputMode: mode,
6667
+ recorded: {}
5694
6668
  });
5695
6669
  }
5696
6670
  if (mode === "agent" || mode === "agent-stream" || mode === "json") {
5697
- printAgentCliError(command, error, mode);
6671
+ printAgentCliError(route?.leaf.path ?? command, error, mode);
5698
6672
  } else {
5699
6673
  printCliError(error);
5700
6674
  }
5701
6675
  return 1;
5702
6676
  };
5703
- const definition = commandRegistry.find(command);
6677
+ if (routeError) {
6678
+ if (!(routeError instanceof CliInputError))
6679
+ throw routeError;
6680
+ return failEarly(routeError, undefined, rejectedRouteIdentity(typedParsed));
6681
+ }
6682
+ const definition = commandRegistry.find(parsed.command);
5704
6683
  if (!definition)
5705
6684
  return failEarly(unknownCommandError(command));
6685
+ const identity = usageIdentityFor(typedParsed, route, legacy, definition);
5706
6686
  if (parsed.shortFlags?.length) {
5707
- return failEarly(new CliInputError(`Short options are not supported (${parsed.shortFlags.join(", ")}), so nothing was run.`, `Use the long form, e.g. --dry-run, --json, --quiet. See 'moda ${command} --help'.`, "short_flag"), definition);
6687
+ return failEarly(new CliInputError(`Short options are not supported (${parsed.shortFlags.join(", ")}), so nothing was run.`, `Use the long form, e.g. --dry-run, --json, --quiet. See 'moda ${route?.leaf.path ?? command} --help'.`, "short_flag"), definition, identity);
5708
6688
  }
5709
6689
  if (switchError)
5710
- return failEarly(switchError, definition);
6690
+ return failEarly(switchError, definition, identity);
6691
+ if (route) {
6692
+ try {
6693
+ assertRoutedFlags(route, typedFlags);
6694
+ } catch (error) {
6695
+ if (!(error instanceof CliInputError))
6696
+ throw error;
6697
+ return failEarly(error, definition, identity);
6698
+ }
6699
+ }
5711
6700
  if (commandFlags.profile === "true" || commandFlags.profile === "") {
5712
- return failEarly(new CliInputError("--profile needs a value, e.g. --profile=prod.", "List profiles with 'moda profiles list'.", "missing_value:profile"), definition);
6701
+ return failEarly(new CliInputError("--profile needs a value, e.g. --profile=prod.", "List profiles with 'moda profiles list'.", "missing_value:profile"), definition, identity);
5713
6702
  }
5714
6703
  const requestedProfile = commandFlags.profile ?? process.env.MODA_PROFILE?.trim();
5715
6704
  if (requestedProfile && !PROFILE_MANAGING_COMMANDS.has(definition.name)) {
5716
6705
  const known = Object.keys(loadProfileConfig({ env: process.env }).profiles);
5717
6706
  if (!known.includes(requestedProfile)) {
5718
6707
  const source = commandFlags.profile ? `--profile=${requestedProfile}` : `MODA_PROFILE=${requestedProfile}`;
5719
- 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);
6708
+ 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);
5720
6709
  }
5721
6710
  if (commandFlags.profile)
5722
6711
  process.env.MODA_PROFILE = commandFlags.profile;
@@ -5731,47 +6720,45 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5731
6720
  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;
5732
6721
  });
5733
6722
  }
6723
+ const deprecation = legacy && !legacy.silent ? { legacy: `moda ${legacy.legacy}`, use: legacy.use, message: deprecationMessage(legacy) } : undefined;
5734
6724
  const context = createCommandContext(parsed, {
5735
6725
  commandName: definition.name,
5736
6726
  commandFlags,
5737
- rawFlags: flags,
6727
+ rawFlags: parsed.flags,
5738
6728
  startedAt,
5739
- outputMode: resolveOutputMode(commandFlags, {
5740
- isTTY: process.stdout.isTTY,
5741
- env: process.env
5742
- })
6729
+ outputMode: outputModeFor(),
6730
+ ...route ? {
6731
+ outputCommand: route.leaf.path,
6732
+ invocation: {
6733
+ command: route.leaf.path,
6734
+ positionals: route.leaf.verb ? typedParsed.positionals.slice(1) : typedParsed.positionals,
6735
+ flags,
6736
+ ...typedParsed.bareFlags ? { bareFlags: typedParsed.bareFlags } : {},
6737
+ routedAs: { command: route.handler, positionals: route.positionals }
6738
+ }
6739
+ } : {},
6740
+ ...deprecation ? { deprecation } : {}
5743
6741
  });
5744
6742
  setActiveContext(context);
5745
- const telemetryCommand = telemetryCommandName(definition);
5746
- const telemetrySubcommands = telemetrySubcommandNames(definition);
6743
+ if (deprecation && !context.quiet && (context.outputMode === "human" || context.outputMode === "json")) {
6744
+ safeWriteStderr(`Deprecated: ${deprecation.message}
6745
+ `);
6746
+ }
5747
6747
  const needsConfig = typeof definition.validateConfigBeforeRun === "function" ? definition.validateConfigBeforeRun(parsed) : definition.validateConfigBeforeRun;
5748
6748
  if (needsConfig) {
5749
6749
  try {
5750
6750
  validateConfig();
5751
6751
  } catch (error) {
5752
6752
  if (!telemetryDisabled && definition.telemetry === "result-and-error") {
5753
- const telemetryProfile = existingFlagProfile(commandFlags);
5754
- sendCliUsageTelemetry({
5755
- apiKey: resolveApiKey({ profile: telemetryProfile }),
5756
- profile: telemetryProfile,
5757
- command: telemetryCommand,
5758
- flags: commandFlags,
6753
+ reportUsage({
6754
+ definition,
6755
+ identity,
5759
6756
  status: "error",
5760
- errorType: classifyCliError(error),
5761
- validationReason: cliValidationReason(error),
6757
+ error,
5762
6758
  exitCode: exitCodeForError(error),
5763
- durationMs: Date.now() - startedAt,
5764
6759
  apiRequestCount: getApiRequestCount(),
5765
- adoption: buildSearchAdoptionTelemetry({
5766
- command: telemetryCommand,
5767
- positional: parsed.positional,
5768
- flags: commandFlags,
5769
- outputMode: context.outputMode,
5770
- recorded: context.getTelemetryMeta(),
5771
- skillVersion: process.env.MODA_SKILL_VERSION,
5772
- subcommands: telemetrySubcommands,
5773
- defaultSubcommand: definition.defaultSubcommand
5774
- })
6760
+ outputMode: context.outputMode,
6761
+ recorded: context.getTelemetryMeta()
5775
6762
  });
5776
6763
  }
5777
6764
  if (context.outputMode === "agent" || context.outputMode === "agent-stream" || context.outputMode === "json") {
@@ -5788,26 +6775,15 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5788
6775
  try {
5789
6776
  const result = await definition.handler(context);
5790
6777
  if (!telemetryDisabled && definition.telemetry !== "none") {
5791
- const telemetryProfile = existingFlagProfile(commandFlags);
5792
- sendCliUsageTelemetry({
5793
- apiKey: result.apiKey ?? resolveApiKey({ profile: telemetryProfile }),
5794
- profile: telemetryProfile,
5795
- command: telemetryCommand,
5796
- flags: commandFlags,
6778
+ reportUsage({
6779
+ definition,
6780
+ identity,
5797
6781
  status: result.exitCode === 0 ? "success" : "error",
5798
6782
  exitCode: result.exitCode,
5799
- durationMs: Date.now() - startedAt,
6783
+ apiKey: result.apiKey,
5800
6784
  apiRequestCount: getApiRequestCount(),
5801
- adoption: buildSearchAdoptionTelemetry({
5802
- command: telemetryCommand,
5803
- positional: parsed.positional,
5804
- flags: commandFlags,
5805
- outputMode: context.outputMode,
5806
- recorded: context.getTelemetryMeta(),
5807
- skillVersion: process.env.MODA_SKILL_VERSION,
5808
- subcommands: telemetrySubcommands,
5809
- defaultSubcommand: definition.defaultSubcommand
5810
- })
6785
+ outputMode: context.outputMode,
6786
+ recorded: context.getTelemetryMeta()
5811
6787
  });
5812
6788
  }
5813
6789
  if (!updateCheckDisabled) {
@@ -5815,30 +6791,18 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5815
6791
  }
5816
6792
  printFeedbackTip(definition.name, context);
5817
6793
  return finishRun(context, { kind: "completed", exitCode: result.exitCode });
5818
- } catch (error) {
6794
+ } catch (thrown) {
6795
+ const error = route ? renameErrorCommands(thrown, route.renames) : thrown;
5819
6796
  if (!telemetryDisabled && definition.telemetry === "result-and-error") {
5820
- const telemetryProfile = existingFlagProfile(commandFlags);
5821
- sendCliUsageTelemetry({
5822
- apiKey: resolveApiKey({ profile: telemetryProfile }),
5823
- profile: telemetryProfile,
5824
- command: telemetryCommand,
5825
- flags: commandFlags,
6797
+ reportUsage({
6798
+ definition,
6799
+ identity,
5826
6800
  status: "error",
5827
- errorType: classifyCliError(error),
5828
- validationReason: cliValidationReason(error),
6801
+ error,
5829
6802
  exitCode: exitCodeForError(error),
5830
- durationMs: Date.now() - startedAt,
5831
6803
  apiRequestCount: getApiRequestCount(),
5832
- adoption: buildSearchAdoptionTelemetry({
5833
- command: telemetryCommand,
5834
- positional: parsed.positional,
5835
- flags: commandFlags,
5836
- outputMode: context.outputMode,
5837
- recorded: context.getTelemetryMeta(),
5838
- skillVersion: process.env.MODA_SKILL_VERSION,
5839
- subcommands: telemetrySubcommands,
5840
- defaultSubcommand: definition.defaultSubcommand
5841
- })
6804
+ outputMode: context.outputMode,
6805
+ recorded: context.getTelemetryMeta()
5842
6806
  });
5843
6807
  }
5844
6808
  if (context.outputMode === "agent" || context.outputMode === "agent-stream" || context.outputMode === "json") {