@moda-ai/cli 1.55.23 → 1.55.25

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-f8a1kejk.js → agent-t7dxmpdq.js} +8 -8
  2. package/dist/{cli-9g8vz4sv.js → cli-0wr3483h.js} +1 -1
  3. package/dist/{cli-4f6jkrnm.js → cli-7xc585hc.js} +5 -5
  4. package/dist/{cli-wkmdw40s.js → cli-9btx12wp.js} +1 -1
  5. package/dist/{cli-4yarp5s1.js → cli-9hc5bg2r.js} +1 -1
  6. package/dist/{cli-cv3jt1ms.js → cli-dn91fb56.js} +10 -10
  7. package/dist/{cli-t3a6eknn.js → cli-dve50x7p.js} +233 -28
  8. package/dist/{cli-54zhv6cx.js → cli-m0045npe.js} +17 -14
  9. package/dist/{cli-s6tw1fje.js → cli-m805msp6.js} +210 -23
  10. package/dist/{cli-z3g07t65.js → cli-mebf5vtr.js} +5 -5
  11. package/dist/{cli-2cggp8ww.js → cli-qvg26twp.js} +5 -4
  12. package/dist/{cli-d2zews6c.js → cli-qycsmxev.js} +3 -3
  13. package/dist/cli.js +1292 -335
  14. package/dist/{doctor-snb87jvy.js → doctor-q21mr5ya.js} +8 -8
  15. package/dist/{fix-fr48gr0w.js → fix-0qdtdz5h.js} +6 -6
  16. package/dist/{harness-github-actions-cfkx9ty9.js → harness-github-actions-atc5bbkj.js} +5 -5
  17. package/dist/{harness-m63b6s0k.js → harness-vzy7n7ea.js} +5 -5
  18. package/dist/{index-nsaph4as.js → index-4b1ybz51.js} +17 -17
  19. package/dist/{production-intelligence-fwjh572m.js → production-intelligence-c8e0xw3n.js} +11 -11
  20. package/dist/{profile-commands-548ts402.js → profile-commands-6e2dgv72.js} +3 -3
  21. package/dist/prompt-source-mcp.js +5 -5
  22. package/dist/{prompts-p66y7wep.js → prompts-3h5jcjrw.js} +6 -6
  23. package/dist/{provision-r9fhrvpw.js → provision-q86phh4a.js} +5 -5
  24. package/dist/{registry-n7dsdxmg.js → registry-s2vdsfw8.js} +3 -3
  25. package/dist/{skills-c17gg69y.js → skills-vq3njc8a.js} +5 -5
  26. package/dist/{sync-bne90xvj.js → sync-hq9dpqpr.js} +7 -7
  27. package/package.json +1 -1
  28. package/skills/integration/index.json +2 -2
  29. package/skills/moda-cli/SKILL.md +35 -30
  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 +13 -12
  36. package/skills/moda-cli/references/prompts.md +6 -2
  37. package/skills/moda-cli/references/recipes.md +14 -14
  38. package/skills/moda-cli/references/search-and-traces.md +32 -32
  39. package/skills/moda-cli/references/signals-and-false-positives.md +29 -29
  40. package/skills/moda-cli/references/troubleshooting.md +4 -4
package/dist/cli.js CHANGED
@@ -5,22 +5,23 @@ import {
5
5
  import {
6
6
  createCommandContext,
7
7
  parseBooleanFlagValue
8
- } from "./cli-2cggp8ww.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,12 +44,12 @@ import {
43
44
  specFlagNames,
44
45
  subcommandHelpFlags,
45
46
  unknownSignalsSubcommandError
46
- } from "./cli-cv3jt1ms.js";
47
+ } from "./cli-dn91fb56.js";
47
48
  import {
48
49
  ApiError,
49
50
  callControlAPI,
50
51
  callDataAPI
51
- } from "./cli-wkmdw40s.js";
52
+ } from "./cli-9btx12wp.js";
52
53
  import {
53
54
  AGENT_ERROR_CODES,
54
55
  BATCH_CONCURRENCY,
@@ -65,6 +66,7 @@ import {
65
66
  EXIT_ERROR,
66
67
  EXIT_INPUT_REQUIRED,
67
68
  EXIT_OK,
69
+ LEGACY_NAMES,
68
70
  MODA_BASE_URL,
69
71
  VERSION,
70
72
  agentEnvelopeFits,
@@ -75,6 +77,7 @@ import {
75
77
  asRecord,
76
78
  asString,
77
79
  attachInputDetails,
80
+ canonicalCommandText,
78
81
  collectBatchTargets,
79
82
  compactProblemDossier,
80
83
  contextTarget,
@@ -85,6 +88,7 @@ import {
85
88
  frustrationsFromEmotions,
86
89
  getApiRequestCount,
87
90
  getModaDir,
91
+ inputDetailsOf,
88
92
  isApiLikeError,
89
93
  isNetworkLikeError,
90
94
  isPlatformApiKey,
@@ -106,7 +110,7 @@ import {
106
110
  withFrustrationAnchors,
107
111
  withToolFailureAnchors,
108
112
  withoutDetectionWindows
109
- } from "./cli-t3a6eknn.js";
113
+ } from "./cli-dve50x7p.js";
110
114
  import {
111
115
  __require
112
116
  } from "./cli-0v6na3yp.js";
@@ -542,7 +546,10 @@ function buildCliUsageRequest(input) {
542
546
  node_version: process.versions.node,
543
547
  platform: process.platform,
544
548
  arch: process.arch,
545
- 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
+ },
546
553
  ...serializeAdoption(input.adoption)
547
554
  })
548
555
  };
@@ -654,6 +661,540 @@ function printUpdateNotice(options = {}) {
654
661
  if (notice)
655
662
  console.error(notice);
656
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
+ }
657
1198
 
658
1199
  // src/command-registry.ts
659
1200
  function createCommandRegistry(definitions) {
@@ -887,11 +1428,11 @@ var SKILLS_SUBCOMMAND_HELP = [
887
1428
  examples: ["moda skills pull", "moda skills pull --status=all"]
888
1429
  },
889
1430
  {
890
- name: "sync",
891
- description: "Upload locally authored .claude/skills/**/SKILL.md files to Moda",
892
- 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]",
893
1434
  flags: [{ name: "--dry-run", description: "Server-side preview; nothing is saved." }],
894
- examples: ["moda skills sync --dry-run", "moda skills sync"]
1435
+ examples: ["moda skills push --dry-run", "moda skills push"]
895
1436
  },
896
1437
  {
897
1438
  name: "list",
@@ -1193,15 +1734,6 @@ var HARNESS_SUBCOMMAND_HELP = [
1193
1734
  examples: ["moda harness diff my-harness-key --from=hver_a --to=hver_b"]
1194
1735
  }
1195
1736
  ];
1196
- var PROBLEMS_SUBCOMMAND_HELP = [
1197
- {
1198
- name: "remainder",
1199
- description: "Signals no Problem explains yet, grouped by family (read-only)",
1200
- usage: "moda problems remainder [--family=<family>] [--days-back=N] [--limit=N] [--cursor=<token>]",
1201
- flags: subcommandHelpFlags(COMMAND_FLAG_SPECS.problems.subcommands.remainder),
1202
- examples: ["moda problems remainder", "moda problems remainder --family=emotion --days-back=7"]
1203
- }
1204
- ];
1205
1737
  var FIX_TIMEOUT = { name: "--timeout=<ms>", description: "How long to wait, in milliseconds (max 86400000)." };
1206
1738
  var FIX_POLL = { name: "--poll-interval=<ms>", description: "Polling interval while waiting, in milliseconds." };
1207
1739
  var FIXES_SUBCOMMAND_HELP = [
@@ -1767,27 +2299,42 @@ function buildManifest(commands) {
1767
2299
  "moda.intelligence.v1",
1768
2300
  "production_ask.v0.1"
1769
2301
  ],
1770
- commands: commands.filter((command) => !command.hidden).map((command) => ({
1771
- name: command.name,
1772
- aliases: command.aliases ?? [],
1773
- summary: command.description,
1774
- output_modes: command.outputModes,
1775
- streaming: command.outputModes.includes("agent-stream") || command.outputModes.includes("agent"),
1776
- examples: command.examples,
1777
- auth: command.auth,
1778
- ...command.authNote ? { auth_note: command.authNote } : {},
1779
- mutability: command.mutability,
1780
- exit_codes: exitCodesForCommand(command.name),
1781
- ...commandFlagFields(command)
1782
- })),
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
+ }),
1783
2329
  concepts: [
1784
2330
  { term: "tenant", meaning: "A Moda organization/workspace that owns data, prompts, skills, and API keys." },
1785
2331
  { term: "profile", meaning: "A named local CLI configuration under ~/.moda that selects environment, tenant, and secret references." },
1786
2332
  { term: "harness", meaning: "Moda’s local graph of runtime agents, prompts, tools, identities, and deployments in a codebase." },
1787
2333
  { term: "harness report", meaning: "A cited analyst artifact under .moda/ that justifies the harness graph before sync." },
1788
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`." },
1789
- { 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`." },
1790
- { 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`." },
1791
2338
  { term: "ingest key", meaning: "A `moda_sk_` API key for SDKs, CI, and Data API calls. Treat it as a secret." },
1792
2339
  { term: "CLI session token", meaning: "A local browser-auth session token used by CLI auth/bootstrap flows, not by application SDKs." },
1793
2340
  { term: "prompt", meaning: "A code-first prompt file tracked by `.moda/prompts.yml` and synced to Moda." },
@@ -1810,7 +2357,11 @@ function renderManifestHuman(manifest) {
1810
2357
  ].join(`
1811
2358
  `);
1812
2359
  }
1813
- 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 {};
1814
2365
  const spec = flagSpecFor(command.name);
1815
2366
  if (spec)
1816
2367
  return manifestFlagFields(spec);
@@ -1843,51 +2394,174 @@ function exitCodesForCommand(command) {
1843
2394
  return [0, 1, 4, 5, 130];
1844
2395
  return [0, 1, 4, 130];
1845
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
+ }
1846
2520
 
1847
2521
  // src/cli.ts
1848
2522
  import { fileURLToPath } from "url";
1849
2523
  import { realpathSync } from "fs";
1850
2524
  async function runPromptsCommand(...args) {
1851
- return (await import("./prompts-p66y7wep.js")).runPromptsCommand(...args);
2525
+ return (await import("./prompts-3h5jcjrw.js")).runPromptsCommand(...args);
1852
2526
  }
1853
2527
  async function runSkillsCommand(...args) {
1854
- return (await import("./skills-c17gg69y.js")).runSkillsCommand(...args);
2528
+ return (await import("./skills-vq3njc8a.js")).runSkillsCommand(...args);
1855
2529
  }
1856
2530
  async function runFixCommand(...args) {
1857
- return (await import("./fix-fr48gr0w.js")).runFixCommand(...args);
2531
+ return (await import("./fix-0qdtdz5h.js")).runFixCommand(...args);
1858
2532
  }
1859
2533
  async function runFixesCommand(...args) {
1860
- return (await import("./fix-fr48gr0w.js")).runFixesCommand(...args);
2534
+ return (await import("./fix-0qdtdz5h.js")).runFixesCommand(...args);
1861
2535
  }
1862
2536
  async function runHarnessCommand(...args) {
1863
- return (await import("./harness-m63b6s0k.js")).runHarnessCommand(...args);
2537
+ return (await import("./harness-vzy7n7ea.js")).runHarnessCommand(...args);
1864
2538
  }
1865
2539
  async function runSyncCommand(...args) {
1866
- return (await import("./sync-bne90xvj.js")).runSyncCommand(...args);
2540
+ return (await import("./sync-hq9dpqpr.js")).runSyncCommand(...args);
1867
2541
  }
1868
2542
  async function runRegistryCommand(...args) {
1869
- return (await import("./registry-n7dsdxmg.js")).runRegistryCommand(...args);
2543
+ return (await import("./registry-s2vdsfw8.js")).runRegistryCommand(...args);
1870
2544
  }
1871
2545
  async function runDoctorCommand(...args) {
1872
- return (await import("./doctor-snb87jvy.js")).runDoctorCommand(...args);
2546
+ return (await import("./doctor-q21mr5ya.js")).runDoctorCommand(...args);
1873
2547
  }
1874
2548
  async function runStatusCommand(...args) {
1875
- return (await import("./doctor-snb87jvy.js")).runStatusCommand(...args);
2549
+ return (await import("./doctor-q21mr5ya.js")).runStatusCommand(...args);
1876
2550
  }
1877
2551
  async function runAuthCommand(...args) {
1878
- return (await import("./profile-commands-548ts402.js")).runAuthCommand(...args);
2552
+ return (await import("./profile-commands-6e2dgv72.js")).runAuthCommand(...args);
1879
2553
  }
1880
2554
  async function runConfigCommand(...args) {
1881
- return (await import("./profile-commands-548ts402.js")).runConfigCommand(...args);
2555
+ return (await import("./profile-commands-6e2dgv72.js")).runConfigCommand(...args);
1882
2556
  }
1883
2557
  async function runProfilesCommand(...args) {
1884
- return (await import("./profile-commands-548ts402.js")).runProfilesCommand(...args);
2558
+ return (await import("./profile-commands-6e2dgv72.js")).runProfilesCommand(...args);
1885
2559
  }
1886
2560
  async function runAgentCommand(...args) {
1887
- return (await import("./agent-f8a1kejk.js")).runAgentCommand(...args);
2561
+ return (await import("./agent-t7dxmpdq.js")).runAgentCommand(...args);
1888
2562
  }
1889
2563
  async function runProductionCommand(...args) {
1890
- return (await import("./production-intelligence-fwjh572m.js")).runProductionCommand(...args);
2564
+ return (await import("./production-intelligence-c8e0xw3n.js")).runProductionCommand(...args);
1891
2565
  }
1892
2566
  function parseArgs(argv) {
1893
2567
  const args = argv.slice(2);
@@ -1904,7 +2578,7 @@ function parseArgs(argv) {
1904
2578
  if (!endOfOptions && arg === "--" && command !== "") {
1905
2579
  endOfOptions = true;
1906
2580
  } else if (!endOfOptions && arg.startsWith("--")) {
1907
- i = parseFlag(args, i, flags, command === "", bareFlags, command);
2581
+ i = parseFlag(args, i, flags, command === "", bareFlags, parseCommandFor(command, positional));
1908
2582
  } else if (!endOfOptions && command !== "" && /^-[A-Za-z]/.test(arg)) {
1909
2583
  shortFlags.push(arg);
1910
2584
  } else if (command === "") {
@@ -2090,58 +2764,35 @@ function buildResumeCommand(command, flags) {
2090
2764
  ];
2091
2765
  }
2092
2766
  function printHelp() {
2093
- console.log(`Usage: moda <command> [options]
2767
+ console.log(`Usage: moda <noun> <verb> [args] [flags]
2094
2768
 
2095
2769
  Start here (goal -> command):
2096
- find something in traces moda search "<query>" (whole traces: moda traces --search=TEXT)
2097
- read one trace moda context <conversation_id> (whole trace: --all)
2098
- what is broken moda overview, then moda problems
2099
- tool errors moda tool-failures, then moda tool-failure-detail <tool_name>
2100
- user emotion and frustration moda emotions
2101
- raw spans for one trace moda audit <conversation_id|trace_id>
2102
- 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)
2103
2778
 
2104
2779
  Production data:
2105
2780
  overview Harness health briefing with production signals
2106
2781
  investigate Rank production issues (tool failures, frustrations, top Problems)
2107
2782
  failures Tool failures only, ranked (no frustrations or Problems)
2108
2783
  ask "<question>" Ask Moda a production question (one paid agent run)
2109
- search "<query>" Search trace messages (keyword/semantic/hybrid)
2110
- traces Search and filter traces (agent runs)
2111
- context <conversation_id> Messages around one turn, or the whole trace with --all
2112
- context <id>[@N] <id>[@N] ... Batch: up to 25 traces in one call (or --ids-from=-|<file>)
2113
- audit <conversation_id|trace_id> Raw span audit (spans, hierarchy, orphans, duplicates)
2114
- audit <id> <id> ... Batch audit: up to 25 ids in one call (or --ids-from=-|<file>)
2115
- world-state <conversation_id> A trace's world state (slots, threads, events)
2116
2784
  clusters Browse or search topic clusters
2117
2785
  cluster-traces <node_id> List traces in a cluster
2118
2786
  users / user <user_id> End users with health and activity (the dashboard Users page)
2119
2787
  tail Live-tail new traces/detections as NDJSON (one line per item)
2120
2788
 
2121
- Detections:
2122
- emotions Emotion detections (frustration, disapproval, confusion, ...)
2123
- frustrations Frustration detections (same data as emotions --family=frustration)
2124
- hallucinations Grounding detections: contradicted/verified outputs
2125
- laziness Laziness detections per trace (repeated calls, premature handoffs, ...)
2126
- tool-failures Tool failure overview
2127
- 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
+ `)}
2128
2792
 
2129
- Problems, signals and review:
2130
- problems Rank cross-signal Problems by root cause (what to fix first)
2131
- problem <problem_id> One Problem: dossier, or its evidence/reports/traces/feedback
2132
- problem-impact Live problem/use-case impact, trend and evidence
2133
- problem-feedback <problem_id> Mark a Problem fixed, dismiss, rename, or flag a bad attribution
2134
- problem-reopen <problem_id> Reopen a Problem marked fixed
2135
- problem-linear <problem_id> Show the Linear issue linked to a Problem
2136
- problem-linear-file <problem_id> File a Problem as a Linear issue, or link an existing one
2137
- detection-reviews <conv_id> Problem detections on a trace with their review state
2138
- detection-review <problem_id> Mark a problem detection correct, a false positive, or clear it
2139
- false-positive <kind> <target> Mark any detection as a false positive
2140
- false-positives [conv_id] Current false-positive marks (workspace or one trace)
2793
+ Custom signals and feedback:
2141
2794
  signals List and manage custom signals
2142
- signal <signal_id> One custom signal, or --detections for its recent hits
2143
- trace-signals <conversation_id> Custom signal hits on one trace
2144
- 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)
2145
2796
  feedback "<note>" Flag wrong/missing data or CLI quirks to the Moda team
2146
2797
 
2147
2798
  Setup:
@@ -2159,13 +2810,13 @@ Groups with their own subcommands (run 'moda <group> --help'):
2159
2810
  harness Analyze, scan, validate, approve, sync and register the agent harness
2160
2811
  prompts Code-first prompt versions: init, status, sync, promote, A/B, propose
2161
2812
  replays Replay results: sets, runs, playouts and transcripts
2162
- skills Generate, pull, install and sync agent skills
2813
+ skills Generate skills; pull (download), push (upload), install
2163
2814
  sync Sync the harness graph and prompts to Moda Cloud
2164
2815
  registry Push prompts, skills, tools and agent wiring from one JSON manifest
2165
2816
 
2166
- Legacy aliases (same behavior): conversations = traces, cluster-conversations = cluster-traces,
2167
- trace = audit, --conversations = --traces, --signal=conversations = --signal=traces.
2168
- <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.
2169
2820
 
2170
2821
  Output: piped stdout, CI or an agent env (CLAUDECODE/CURSOR/MODA_AGENT) => agent JSON; a terminal => prose.
2171
2822
  --json | --agent [--stream] | --human Force an output mode (or MODA_FORMAT=human|json|agent|agent-stream)
@@ -2250,6 +2901,187 @@ function printSubcommandHelp(parent, entry) {
2250
2901
  console.log(lines.join(`
2251
2902
  `));
2252
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
+ }
2253
3085
  async function attachWindows(rows, windowSize) {
2254
3086
  return Promise.all(rows.map(async (row) => {
2255
3087
  const anchor = asRecord(row.anchor);
@@ -2309,7 +3141,7 @@ function withoutRepeatedFrustrationWindow(data) {
2309
3141
  return { ...data, frustrations };
2310
3142
  }
2311
3143
  var NOT_FOUND_HINTS = {
2312
- 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>',
2313
3145
  cluster: "List cluster node ids with: moda clusters",
2314
3146
  problem: "List problem ids with: moda problems"
2315
3147
  };
@@ -2323,7 +3155,7 @@ function noWorldStateWarnings(conversationId, isMissing) {
2323
3155
  return;
2324
3156
  return {
2325
3157
  warnings: [
2326
- `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`
2327
3159
  ]
2328
3160
  };
2329
3161
  }
@@ -2363,11 +3195,11 @@ function auditOutput(command, params, data) {
2363
3195
  return { data: auditRecord ? { ...auditRecord, truncated: false, has_more: false, next_cursor: null } : data, options: {} };
2364
3196
  }
2365
3197
  const limit = params.limit ?? AUDIT_DEFAULT_LIMIT;
2366
- 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)";
2367
3199
  const warnings = [];
2368
3200
  const nextCommands = [];
2369
3201
  const auditFlags = (extra) => [
2370
- `moda ${command} ${shellArg(params.id)}`,
3202
+ `${canonicalCommandText(`moda ${command}`)} ${shellArg(params.id)}`,
2371
3203
  params.kind && params.kind !== "auto" ? `--kind=${params.kind}` : "",
2372
3204
  params.include_raw === true ? "--include-raw" : "",
2373
3205
  params.include_bodies === true ? "--include-bodies" : "",
@@ -2554,7 +3386,7 @@ async function runTailCommand(params, context) {
2554
3386
  total,
2555
3387
  coverage_pct: coveragePct,
2556
3388
  pages_scanned: pages,
2557
- 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.`
2558
3390
  });
2559
3391
  } else if (total !== null) {
2560
3392
  emitMeta({
@@ -2695,8 +3527,8 @@ var WINDOW_FLAG_HINTS = {
2695
3527
  };
2696
3528
  var COMMAND_FLAG_HINTS = {
2697
3529
  problems: {
2698
- 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).",
2699
- 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."
2700
3532
  }
2701
3533
  };
2702
3534
  function parseCommandFlags(schema, args, command, options = {}) {
@@ -2734,8 +3566,9 @@ function parseCommandFlags(schema, args, command, options = {}) {
2734
3566
  return close ? `Did you mean ${close}?` : "";
2735
3567
  }).filter(Boolean);
2736
3568
  const names = unknown.map(kebabFlag).join(", ");
2737
- const helpCommand = `moda ${command} --help`;
2738
- 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, "-")}`, {
2739
3572
  field: kebabFlag(unknown[0]),
2740
3573
  allowed: valid,
2741
3574
  ...didYouMean.length > 0 ? { didYouMean: [...new Set(didYouMean)] } : {},
@@ -2761,7 +3594,7 @@ function attachFlagDetails(error, schema, command) {
2761
3594
  field: field.name,
2762
3595
  ...field.enum ? { allowed: field.enum } : {},
2763
3596
  ...field.min !== undefined || field.max !== undefined ? { range: { ...field.min !== undefined ? { min: field.min } : {}, ...field.max !== undefined ? { max: field.max } : {} } } : {},
2764
- suggestedCommands: [`moda ${command} --help`]
3597
+ suggestedCommands: [`${canonicalCommandText(`moda ${command}`)} --help`]
2765
3598
  });
2766
3599
  }
2767
3600
  function parseSpecFlags(name, args, command = name) {
@@ -2914,7 +3747,7 @@ async function searchFallbackToTraces(params) {
2914
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)`;
2915
3748
  const nextCommands = [];
2916
3749
  if (first) {
2917
- 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 });
2918
3751
  }
2919
3752
  if (hasMore) {
2920
3753
  nextCommands.push({ command: `${tracesCommand} --offset=${nextOffset}`, purpose: "Page through further trace-level results.", mutability: "read", requires_approval: false });
@@ -2936,7 +3769,7 @@ async function searchFallbackToTraces(params) {
2936
3769
  ...hasMore ? { next_offset: nextOffset } : {}
2937
3770
  }
2938
3771
  },
2939
- 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." : ""),
2940
3773
  nextCommands
2941
3774
  };
2942
3775
  }
@@ -3061,9 +3894,9 @@ function processingDisabledWarning(data, family) {
3061
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."
3062
3895
  ];
3063
3896
  }
3064
- var CONTEXT_USAGE = "Usage: moda context <conversation_id> [--msg-index=N (alias --message-index)] [--window=0-5 | --metadata-only]";
3065
- var CONTEXT_ALL_USAGE = "Usage: moda context <conversation_id> --all [--from=N] [--max-messages=N]";
3066
- 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)`;
3067
3900
  var CONTEXT_FLAG_ALIASES = { "message-index": "msg-index" };
3068
3901
  var CONTEXT_MAX_WINDOW = 5;
3069
3902
  var EMPTY_ANCHOR_VALUES = new Set(["", "null", "undefined", "none"]);
@@ -3237,7 +4070,7 @@ async function readFullTranscript(conversationId, opts = {}) {
3237
4070
  if (next !== null) {
3238
4071
  const last = asNumber(returned[returned.length - 1]?.index) ?? next - 1;
3239
4072
  const nextMax = firstMissing !== null ? Math.min(pageSize, Math.max(kept * 2, 50)) : pageSize;
3240
- 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}`;
3241
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}`);
3242
4075
  nextCommands.push({
3243
4076
  command: continueWith,
@@ -3250,7 +4083,7 @@ async function readFullTranscript(conversationId, opts = {}) {
3250
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` : ""}.`);
3251
4084
  }
3252
4085
  if (legacy && returned.some(isTruncated)) {
3253
- 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).`);
3254
4087
  }
3255
4088
  return {
3256
4089
  data: stripLoneSurrogates({
@@ -3317,7 +4150,7 @@ async function readContextItem(command, params, inputWarnings, fits) {
3317
4150
  const center = asNumber(asRecord(ctxRecord.context)?.center_index) ?? params.msg_index ?? 0;
3318
4151
  const span = Math.min(params.window * 2 + 1, CONTEXT_ALL_MAX_MESSAGES);
3319
4152
  const from = Math.max(0, center - Math.floor((span - 1) / 2));
3320
- 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}`;
3321
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}`);
3322
4155
  nextCommands.push({ command: wide, purpose: `Read ±${params.window} messages around the anchor.`, mutability: "read", requires_approval: false });
3323
4156
  }
@@ -3329,8 +4162,8 @@ async function readContextItem(command, params, inputWarnings, fits) {
3329
4162
  }
3330
4163
  };
3331
4164
  }
3332
- var AUDIT_USAGE = "Usage: moda audit <conversation_id|trace_id> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] [--cursor=<next_cursor>]";
3333
- 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)`;
3334
4167
  async function readAuditItem(command, params, budget) {
3335
4168
  const query2 = new URLSearchParams;
3336
4169
  if (params.kind && params.kind !== "auto")
@@ -3443,7 +4276,7 @@ function writeBatchResult(command, context, targets, results, batchWarnings, fla
3443
4276
  const nextCommands = [];
3444
4277
  if (remaining.length > 0) {
3445
4278
  const lines = remaining.map((target) => shellArg(target.raw)).join(" ");
3446
- 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(" ");
3447
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}`);
3448
4281
  nextCommands.push({
3449
4282
  command: continueWith,
@@ -3652,7 +4485,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3652
4485
  }
3653
4486
  case "search": {
3654
4487
  if (!positional || positional.trim() === "") {
3655
- 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]');
3656
4489
  }
3657
4490
  const args = withStringIdFlags(flagsToArgs(flags, "q", positional), flags);
3658
4491
  const params = parseSpecFlags("search", args, command);
@@ -3708,12 +4541,12 @@ async function runCommand(command, positional, flags, positionals = positional ?
3708
4541
  }
3709
4542
  case "world-state": {
3710
4543
  if (!positional) {
3711
- 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]");
3712
4545
  }
3713
4546
  const args = flagsToArgs(flags, "conversation_id", positional);
3714
4547
  const params = parseSpecFlags("world-state", args, command);
3715
4548
  if (params.snapshot === true && params.replay === true) {
3716
- 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]]");
3717
4550
  }
3718
4551
  if (params.snapshot === true) {
3719
4552
  const query3 = new URLSearchParams;
@@ -3812,7 +4645,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3812
4645
  }
3813
4646
  case "tool-failure-detail": {
3814
4647
  if (!positional) {
3815
- 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]]");
3816
4649
  }
3817
4650
  const { "no-include-window": noIncludeWindow, ...toolFlags } = flags;
3818
4651
  if (noIncludeWindow !== undefined && noIncludeWindow !== "false") {
@@ -3843,7 +4676,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3843
4676
  const subtypeCount = Array.isArray(tfRecord.subtypes) ? tfRecord.subtypes.length : 0;
3844
4677
  const exampleCount = Array.isArray(tfRecord.examples) ? tfRecord.examples.length : 0;
3845
4678
  const warnings = subtypeCount === 0 && exampleCount === 0 ? [
3846
- `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.`
3847
4680
  ] : [];
3848
4681
  const knownSubtypes = Array.isArray(tfRecord.subtypes) ? tfRecord.subtypes.map((row) => asString(asRecord(row)?.subtype)).filter((value) => Boolean(value)) : [];
3849
4682
  if (params.subtype && knownSubtypes.length > 0 && !knownSubtypes.includes(params.subtype)) {
@@ -3901,7 +4734,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3901
4734
  break;
3902
4735
  }
3903
4736
  if (positional) {
3904
- 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>'.");
3905
4738
  }
3906
4739
  const args = flagsToArgs(flags);
3907
4740
  const params = parseSpecFlags("problems", args, command);
@@ -3928,7 +4761,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3928
4761
  break;
3929
4762
  }
3930
4763
  if (!positional) {
3931
- 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]");
3932
4765
  }
3933
4766
  const args = flagsToArgs(flags, "id", positional);
3934
4767
  const params = parseSpecFlags("problem", args, command);
@@ -3936,11 +4769,11 @@ async function runCommand(command, positional, flags, positionals = positional ?
3936
4769
  const views = ["evidence", "reports", "conversations", "feedback"].filter((view2) => view2 === "conversations" ? traceView : params[view2] === true);
3937
4770
  if (views.length > 1) {
3938
4771
  const got = views.map((v) => v === "conversations" ? "--traces" : `--${v}`).join(" ");
3939
- 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]");
3940
4773
  }
3941
4774
  const view = views[0];
3942
4775
  if (view !== undefined && params.full === true) {
3943
- 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");
3944
4777
  }
3945
4778
  if (view === undefined) {
3946
4779
  const data2 = await callDataAPI(`/problems/${encodeURIComponent(params.id)}`);
@@ -3963,7 +4796,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3963
4796
  }
3964
4797
  if (!UUID_RE.test(params.id)) {
3965
4798
  const viewFlag = view === "conversations" ? "--traces" : `--${view}`;
3966
- 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.`);
3967
4800
  }
3968
4801
  const query2 = new URLSearchParams;
3969
4802
  if (params.limit)
@@ -3983,7 +4816,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
3983
4816
  }
3984
4817
  case "problem-feedback": {
3985
4818
  if (!positional) {
3986
- 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]`);
3987
4820
  }
3988
4821
  const params = ProblemFeedbackSchema.parse({
3989
4822
  id: positional,
@@ -4005,7 +4838,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4005
4838
  }
4006
4839
  if (params.action === "flag_attribution") {
4007
4840
  if (params.attribution_id === undefined || params.attribution_id.trim() === "") {
4008
- 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.");
4009
4842
  }
4010
4843
  if (!UUID_RE.test(params.attribution_id)) {
4011
4844
  throw new CliInputError("--attribution-id must be a UUID.");
@@ -4027,7 +4860,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4027
4860
  }
4028
4861
  case "problem-linear": {
4029
4862
  if (!positional) {
4030
- 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>");
4031
4864
  }
4032
4865
  if (!UUID_RE.test(positional)) {
4033
4866
  throw new CliInputError(`Problem id must be a canonical UUID; got "${positional}". Run \`moda problems\` to list problems with their canonical ids.`);
@@ -4037,17 +4870,17 @@ async function runCommand(command, positional, flags, positionals = positional ?
4037
4870
  });
4038
4871
  const link = asRecord(data)?.link;
4039
4872
  const summary = describeLinearLink(link);
4040
- const nextCommands = link ? [] : [`moda problem-linear-file ${positional}`];
4873
+ const nextCommands = link ? [] : [`moda problems link ${positional}`];
4041
4874
  if (context.outputMode === "human" && !context.quiet) {
4042
4875
  safeWriteStderr(link ? `${summary}
4043
- ` : `${summary} File one with: moda problem-linear-file ${positional}
4876
+ ` : `${summary} File one with: moda problems link ${positional}
4044
4877
  `);
4045
4878
  }
4046
4879
  context.output.writeData(data, { summary, nextCommands });
4047
4880
  break;
4048
4881
  }
4049
4882
  case "problem-linear-file": {
4050
- 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]";
4051
4884
  if (!positional) {
4052
4885
  throw new CliInputError("<problem_id> is required", usage);
4053
4886
  }
@@ -4126,14 +4959,14 @@ async function runCommand(command, positional, flags, positionals = positional ?
4126
4959
  }
4127
4960
  case "trace-signals": {
4128
4961
  if (!positional) {
4129
- 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>");
4130
4963
  }
4131
4964
  const data = await callDataAPI(`/conversations/${encodeURIComponent(positional)}/custom-signals`);
4132
4965
  context.output.writeData(data);
4133
4966
  break;
4134
4967
  }
4135
4968
  case "signal-label": {
4136
- 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]`;
4137
4970
  if (!positional) {
4138
4971
  throw new CliInputError("<signal_id> is required", usage);
4139
4972
  }
@@ -4162,7 +4995,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4162
4995
  }
4163
4996
  case "detection-reviews": {
4164
4997
  if (!positional) {
4165
- 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]");
4166
4999
  }
4167
5000
  const attributionId = flags["attribution-id"];
4168
5001
  if (attributionId !== undefined && !UUID_RE.test(attributionId)) {
@@ -4174,7 +5007,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4174
5007
  break;
4175
5008
  }
4176
5009
  case "detection-review": {
4177
- 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]`;
4178
5011
  if (!positional) {
4179
5012
  throw new CliInputError("<problem_id> is required", usage);
4180
5013
  }
@@ -4201,7 +5034,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4201
5034
  break;
4202
5035
  }
4203
5036
  case "false-positive": {
4204
- 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]`;
4205
5038
  const [kind, target] = positionals;
4206
5039
  if (!kind || !target)
4207
5040
  throw new CliInputError("<kind> and <target> are required", usage);
@@ -4274,7 +5107,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
4274
5107
  const query2 = new URLSearchParams;
4275
5108
  if (flags.kind !== undefined) {
4276
5109
  if (flags.kind === "problem" || flags.kind === "problem-detection") {
4277
- 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");
4278
5111
  }
4279
5112
  if (!kinds[flags.kind])
4280
5113
  throw new CliInputError(`--kind must be one of ${Object.keys(kinds).join("|")}`);
@@ -4483,7 +5316,7 @@ function initAgentWriteOptions(result) {
4483
5316
  warnings: [
4484
5317
  ...manualActions.filter((action) => action.reason).map((action) => `${action.title}: ${action.reason}`),
4485
5318
  ...result.promptSync?.status === "failed" ? [`Prompt sync failed: ${result.promptSync.error}. Retry with 'moda prompts sync'.`] : [],
4486
- ...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'.`] : [],
4487
5320
  ...result.skillSync?.warnings ?? [],
4488
5321
  ...(result.skillInstall?.warnings ?? []).map((warning) => `moda-cli skill install: ${warning}`)
4489
5322
  ]
@@ -4501,6 +5334,10 @@ function initArtifactKind(file) {
4501
5334
  return "ci_workflow";
4502
5335
  return "init_artifact";
4503
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
+ }
4504
5341
  var dataApiDefaults = {
4505
5342
  auth: "api-key",
4506
5343
  outputModes: ["human", "json", "agent"],
@@ -4545,7 +5382,7 @@ var commandRegistry = createCommandRegistry([
4545
5382
  resetApiRequestCountBeforeRun: true,
4546
5383
  telemetry: "result-and-error",
4547
5384
  handler: async (context) => {
4548
- const { runInit } = await import("./index-nsaph4as.js");
5385
+ const { runInit } = await import("./index-4b1ybz51.js");
4549
5386
  if (context.outputMode === "agent-stream") {
4550
5387
  context.output.writeEvent({
4551
5388
  event: "started",
@@ -4595,7 +5432,7 @@ var commandRegistry = createCommandRegistry([
4595
5432
  resetApiRequestCountBeforeRun: true,
4596
5433
  telemetry: "result-and-error",
4597
5434
  handler: async (context) => {
4598
- const { runProvision } = await import("./provision-r9fhrvpw.js");
5435
+ const { runProvision } = await import("./provision-q86phh4a.js");
4599
5436
  return runProvision(context, {
4600
5437
  resumeCommand: buildResumeCommand("provision", context.rawFlags)
4601
5438
  });
@@ -4879,7 +5716,7 @@ var commandRegistry = createCommandRegistry([
4879
5716
  {
4880
5717
  name: "problem-reopen",
4881
5718
  description: "Reopen a problem that was marked fixed (resolved problems return only via re-detection)",
4882
- 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"],
4883
5720
  auth: "api-key",
4884
5721
  outputModes: ["human", "json", "agent"],
4885
5722
  mutability: "write",
@@ -5025,21 +5862,23 @@ var commandRegistry = createCommandRegistry([
5025
5862
  name: "traces",
5026
5863
  aliases: ["conversations"],
5027
5864
  telemetryCommand: "conversations",
5028
- 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",
5029
5866
  examples: [
5030
- 'moda traces --search="error" --limit=5',
5031
- 'moda traces --world-state="enterprise" --outcome=positive',
5032
- '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'
5033
5870
  ],
5871
+ subcommands: treeSubcommandHelp("traces"),
5872
+ defaultSubcommand: "list",
5034
5873
  ...dataApiDefaults
5035
5874
  }),
5036
5875
  legacyCommand({
5037
5876
  name: "search",
5038
5877
  description: "Search trace messages (keyword, semantic, or hybrid)",
5039
5878
  examples: [
5040
- 'moda search "billing error"',
5041
- 'moda search "refund flow" --mode=semantic --time-range=7d --limit=10',
5042
- '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'
5043
5882
  ],
5044
5883
  ...dataApiDefaults
5045
5884
  }),
@@ -5047,8 +5886,8 @@ var commandRegistry = createCommandRegistry([
5047
5886
  name: "world-state",
5048
5887
  description: "Get a trace's world state (slots, threads, events)",
5049
5888
  examples: [
5050
- "moda world-state <conversation_id>",
5051
- "moda world-state <conversation_id> --summary-only"
5889
+ "moda traces state <conversation_id>",
5890
+ "moda traces state <conversation_id> --summary-only"
5052
5891
  ],
5053
5892
  ...dataApiDefaults
5054
5893
  }),
@@ -5056,13 +5895,13 @@ var commandRegistry = createCommandRegistry([
5056
5895
  name: "context",
5057
5896
  description: "Get windowed trace context (messages around one turn), or the whole trace with --all",
5058
5897
  examples: [
5059
- "moda context <conversation_id> --window=3",
5060
- "moda context <conversation_id> --msg-index=40 --window=0 # --message-index is an alias",
5061
- "moda context <conversation_id> --metadata-only # summary + total_messages, no messages",
5062
- "moda context <conversation_id> --all",
5063
- "moda context <conversation_id> --all --from=500 --max-messages=500",
5064
- "moda context <id>@12 <id2>@3 <id3> --window=2 # batch: up to 25 targets, one envelope; id@N anchors at message N",
5065
- "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"
5066
5905
  ],
5067
5906
  ...dataApiDefaults
5068
5907
  }),
@@ -5071,13 +5910,13 @@ var commandRegistry = createCommandRegistry([
5071
5910
  aliases: ["trace"],
5072
5911
  description: "Raw non-deduped span audit for one trace or OTLP trace_id (hierarchy, tool spans, orphans, duplicates)",
5073
5912
  examples: [
5074
- "moda audit <conversation_id|trace_id>",
5075
- "moda audit <trace_id> --kind=trace --json",
5076
- "moda audit <conversation_id> --include-raw",
5077
- "moda audit <conversation_id> --include-bodies # keep prompt/response bodies (omitted by default; long strings truncated)",
5078
- "moda audit <conversation_id> --limit=1000 # ingest rows to read (default 200, max 1000); warns when capped",
5079
- "moda audit <conversation_id> --cursor=<next_cursor> # next page of a capped audit",
5080
- "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>)"
5081
5920
  ],
5082
5921
  ...dataApiDefaults
5083
5922
  }),
@@ -5085,32 +5924,32 @@ var commandRegistry = createCommandRegistry([
5085
5924
  name: "frustrations",
5086
5925
  description: "User frustration detections: the frustration family of `emotions` (same data as the dashboard), legacy field names",
5087
5926
  examples: [
5088
- "moda frustrations --days-back=14",
5089
- "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"
5090
5929
  ],
5091
5930
  ...dataApiDefaults
5092
5931
  }),
5093
5932
  legacyCommand({
5094
5933
  name: "tool-failures",
5095
5934
  description: "Get tool failure overview",
5096
- examples: ["moda tool-failures --days-back=14"],
5935
+ examples: ["moda detections tools --days-back=14"],
5097
5936
  ...dataApiDefaults
5098
5937
  }),
5099
5938
  legacyCommand({
5100
5939
  name: "tool-failure-detail",
5101
5940
  description: "Get per-tool failure detail (each example embeds its anchor ±2 messages)",
5102
5941
  examples: [
5103
- "moda tool-failure-detail search",
5104
- "moda tool-failure-detail search --compact # no embedded conversation (same as --include-window=false)",
5105
- "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"
5106
5945
  ],
5107
5946
  ...dataApiDefaults
5108
5947
  }),
5109
5948
  legacyCommand({
5110
5949
  name: "problems",
5111
5950
  description: "Rank cross-signal Problems by root cause (what to fix first)",
5112
- examples: ["moda problems", "moda problems --days-back=7 --limit=10", "moda problems remainder --family=emotion"],
5113
- 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"),
5114
5953
  defaultSubcommand: "list",
5115
5954
  ...dataApiDefaults
5116
5955
  }),
@@ -5118,14 +5957,14 @@ var commandRegistry = createCommandRegistry([
5118
5957
  name: "problem-impact",
5119
5958
  description: "Live problem/use-case impact, full analysis denominators, daily recurrence, and paginated evidence",
5120
5959
  examples: [
5121
- "moda problem-impact --mode=catalog",
5122
- "moda problem-impact --node-id=node_0 --days-back=all",
5123
- "moda problem-impact --problem-id=<uuid> --mode=metrics",
5124
- "moda problem-impact --node-id=node_0 --problem-id=<uuid> --mode=evidence",
5125
- "moda problem-impact --node-id=node_0 --mode=trend --days-back=30",
5126
- "moda problem-impact --mode=overview --days-back=7",
5127
- "moda problem-impact --problem-id=<uuid> --mode=affected-use-cases",
5128
- `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"]'`
5129
5968
  ],
5130
5969
  ...dataApiDefaults
5131
5970
  }),
@@ -5133,12 +5972,12 @@ var commandRegistry = createCommandRegistry([
5133
5972
  name: "problem",
5134
5973
  description: "Open one Problem: dossier, or --evidence/--reports/--traces/--feedback pages (--conversations is a legacy alias of --traces)",
5135
5974
  examples: [
5136
- "moda problem <problem_id>",
5137
- "moda problem <problem_id> --full # whole dossier (agent output defaults to a compact one)",
5138
- "moda problem <problem_id> --evidence --limit=10",
5139
- "moda problem <problem_id> --reports",
5140
- "moda problem <problem_id> --traces --family=tool_failure",
5141
- "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"
5142
5981
  ],
5143
5982
  ...dataApiDefaults
5144
5983
  }),
@@ -5146,10 +5985,10 @@ var commandRegistry = createCommandRegistry([
5146
5985
  name: "problem-feedback",
5147
5986
  description: "Submit Problem feedback: mark fixed, dismiss, rename, or flag a bad attribution",
5148
5987
  examples: [
5149
- "moda problem-feedback <problem_id> --action=mark_fixed",
5150
- 'moda problem-feedback <problem_id> --action=dismiss --reason="not actionable"',
5151
- 'moda problem-feedback <problem_id> --action=rename --new-name="Better title"',
5152
- '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"'
5153
5992
  ],
5154
5993
  ...dataApiDefaults,
5155
5994
  mutability: "write"
@@ -5157,16 +5996,16 @@ var commandRegistry = createCommandRegistry([
5157
5996
  legacyCommand({
5158
5997
  name: "problem-linear",
5159
5998
  description: "Show the Linear issue linked to a Problem (key, state, URL); needs Linear connected (Integrations -> Linear)",
5160
- examples: ["moda problem-linear <problem_id>"],
5999
+ examples: ["moda problems linear <problem_id>"],
5161
6000
  ...dataApiDefaults
5162
6001
  }),
5163
6002
  legacyCommand({
5164
6003
  name: "problem-linear-file",
5165
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)",
5166
6005
  examples: [
5167
- "moda problem-linear-file <problem_id>",
5168
- 'moda problem-linear-file <problem_id> --team-id=<team_id> --title="CRM lookups time out" --priority=2',
5169
- "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"
5170
6009
  ],
5171
6010
  ...dataApiDefaults,
5172
6011
  mutability: "write"
@@ -5175,8 +6014,8 @@ var commandRegistry = createCommandRegistry([
5175
6014
  name: "detection-reviews",
5176
6015
  description: "List problem detections on a trace with their review state (correct / false_positive / clear)",
5177
6016
  examples: [
5178
- "moda detection-reviews <conversation_id>",
5179
- "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>"
5180
6019
  ],
5181
6020
  ...dataApiDefaults
5182
6021
  }),
@@ -5184,9 +6023,9 @@ var commandRegistry = createCommandRegistry([
5184
6023
  name: "detection-review",
5185
6024
  description: "Review a problem detection on a trace: mark it correct, a false positive, or clear the review",
5186
6025
  examples: [
5187
- 'moda detection-review <problem_id> --conversation-id=<id> --attribution-id=<uuid> --verdict=false_positive --note="expected retry"',
5188
- "moda detection-review <problem_id> --conversation-id=<id> --attribution-id=<uuid> --verdict=correct",
5189
- "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"
5190
6029
  ],
5191
6030
  ...dataApiDefaults,
5192
6031
  mutability: "write"
@@ -5195,17 +6034,17 @@ var commandRegistry = createCommandRegistry([
5195
6034
  name: "false-positive",
5196
6035
  description: "Flag a detection as a false positive with a reason: problem, problem-detection, quote, tool, laziness, hallucination, or custom-signal",
5197
6036
  helpNotes: [
5198
- "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."
5199
6038
  ],
5200
6039
  examples: [
5201
- 'moda false-positive problem <problem_id> --reason="two unrelated tools"',
5202
- 'moda false-positive problem-detection <problem_id> --conversation-id=<id> --attribution-id=<uuid> --reason="refund was approved"',
5203
- 'moda false-positive quote <conversation_id> --family=frustration --turn=3 --quote="this is useless lol" --reason="joking"',
5204
- 'moda false-positive tool <conversation_id> --tool=send_invoice --tool-use-id=<id> --reason="retried upstream"',
5205
- 'moda false-positive laziness <conversation_id> --reason="reload after context reset is expected"',
5206
- "moda false-positive hallucination <conversation_id> --undo",
5207
- "moda false-positive custom-signal <signal_id> --conversation-id=<id> --undo",
5208
- '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"'
5209
6048
  ],
5210
6049
  ...dataApiDefaults,
5211
6050
  mutability: "write"
@@ -5214,9 +6053,9 @@ var commandRegistry = createCommandRegistry([
5214
6053
  name: "false-positives",
5215
6054
  description: "List current false-positive marks across the workspace, or on one trace (with problem detection reviews)",
5216
6055
  examples: [
5217
- "moda false-positives",
5218
- "moda false-positives --kind=tool --all",
5219
- "moda false-positives <conversation_id>"
6056
+ "moda reviews list",
6057
+ "moda reviews list --kind=tool --all",
6058
+ "moda reviews list <conversation_id>"
5220
6059
  ],
5221
6060
  ...dataApiDefaults
5222
6061
  }),
@@ -5239,22 +6078,22 @@ var commandRegistry = createCommandRegistry([
5239
6078
  description: "Open one custom signal, or --detections for its recent hits (paged)",
5240
6079
  examples: [
5241
6080
  "moda signal <signal_id>",
5242
- "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"
5243
6082
  ],
5244
6083
  ...dataApiDefaults
5245
6084
  }),
5246
6085
  legacyCommand({
5247
6086
  name: "trace-signals",
5248
6087
  description: "Custom signal hits on one trace",
5249
- examples: ["moda trace-signals <conversation_id>"],
6088
+ examples: ["moda detections list --family=custom_signal --conversation-id=<conversation_id>"],
5250
6089
  ...dataApiDefaults
5251
6090
  }),
5252
6091
  legacyCommand({
5253
6092
  name: "signal-label",
5254
6093
  description: "Label a trace for a custom signal: fits, or does_not_fit to mark a false positive",
5255
6094
  examples: [
5256
- 'moda signal-label <signal_id> --conversation-id=<id> --label=does_not_fit --note="refund was approved"',
5257
- "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"
5258
6097
  ],
5259
6098
  ...dataApiDefaults,
5260
6099
  mutability: "write"
@@ -5263,9 +6102,9 @@ var commandRegistry = createCommandRegistry([
5263
6102
  name: "emotions",
5264
6103
  description: "Multi-family emotion detections (frustration, disapproval, sadness, confusion, anxiety, trust, positive)",
5265
6104
  examples: [
5266
- "moda emotions --days-back=7",
5267
- "moda emotions --family=confusion --limit=20",
5268
- "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"
5269
6108
  ],
5270
6109
  ...dataApiDefaults
5271
6110
  }),
@@ -5273,8 +6112,8 @@ var commandRegistry = createCommandRegistry([
5273
6112
  name: "laziness",
5274
6113
  description: "Laziness detections per trace: repeated tool calls, premature handoffs, and other lazy patterns",
5275
6114
  examples: [
5276
- "moda laziness --days-back=30",
5277
- "moda laziness --conversation-id=<id>"
6115
+ "moda detections list --family=laziness --days-back=30",
6116
+ "moda detections list --family=laziness --conversation-id=<id>"
5278
6117
  ],
5279
6118
  ...dataApiDefaults
5280
6119
  }),
@@ -5282,9 +6121,9 @@ var commandRegistry = createCommandRegistry([
5282
6121
  name: "hallucinations",
5283
6122
  description: "Grounding detections: contradicted/verified outputs with rule breakdown",
5284
6123
  examples: [
5285
- "moda hallucinations --days-back=30",
5286
- "moda hallucinations --kind=contradicted --limit=20",
5287
- "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>"
5288
6127
  ],
5289
6128
  ...dataApiDefaults
5290
6129
  }),
@@ -5306,6 +6145,44 @@ var commandRegistry = createCommandRegistry([
5306
6145
  ],
5307
6146
  ...dataApiDefaults
5308
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
+ },
5309
6186
  legacyCommand({
5310
6187
  name: "feedback",
5311
6188
  description: "Flag wrong/missing data or CLI quirks to the Moda team",
@@ -5382,11 +6259,13 @@ var commandRegistry = createCommandRegistry([
5382
6259
  {
5383
6260
  name: "ab",
5384
6261
  description: "Run a judged prompt A/B replay experiment",
5385
- usage: "moda prompts ab --baseline=<path> --candidate=<path> [--traces=<ids>|--set-id=<id>|--auto-generate] [--yes]",
6262
+ usage: "moda prompts ab --baseline=<path> --candidate=<path> [--traces=<ids>|--problem=<problem_id> [--max-cases=<n>]|--set-id=<id>|--auto-generate] [--yes]",
5386
6263
  flags: [
5387
6264
  { name: "--baseline=<path>", description: "Prompt file to treat as the control." },
5388
6265
  { name: "--candidate=<path>", description: "Prompt file to treat as the variant." },
5389
6266
  { name: "--traces=<ids>", description: "Comma-separated trace ids (conversation_id values) to replay, at most 100. Each case uses Moda's distilled scenario and success criteria for the trace; a trace that cannot be distilled falls back to its opening user message with generic criteria, and the run warns. Legacy alias: --conversations=<ids>." },
6267
+ { name: "--problem=<problem_id>", description: "Replay the Problem's failing traces: one case per affected trace, starting at its evidence msg_index (like --traces=<id>@<n>), read from `moda problems get <id> --evidence`, then `--traces` if needed. Prints the traces it chose. Not combinable with --traces, --set-id, --auto-generate, or --cases." },
6268
+ { name: "--max-cases=<n>", description: "With --problem: replay at most this many traces (default 10, max 100)." },
5390
6269
  { name: "--cases=<n>", description: "Cases to auto-generate (default 5, max 100)." },
5391
6270
  { name: "--seeds=<n>", description: "Repeats per case per arm (default 3, max 10)." },
5392
6271
  { name: "--model=<slug>", description: "Assistant model for both arms; must be in the replay allowlist unless --allow-any-model." },
@@ -5394,7 +6273,8 @@ var commandRegistry = createCommandRegistry([
5394
6273
  { name: "--promote", description: "Make this run's replay set the tenant's primary replay set. Off by default, so an ad-hoc A/B never replaces it." }
5395
6274
  ],
5396
6275
  examples: [
5397
- "moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --traces=conv_1,conv_2"
6276
+ "moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --traces=conv_1,conv_2",
6277
+ "moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --problem=<problem_id>"
5398
6278
  ]
5399
6279
  },
5400
6280
  {
@@ -5427,18 +6307,18 @@ var commandRegistry = createCommandRegistry([
5427
6307
  }),
5428
6308
  {
5429
6309
  name: "skills",
5430
- description: "Generate, pull, and sync Moda skills",
6310
+ description: "Generate, pull (download), push (upload), and install Moda skills",
5431
6311
  examples: [
5432
6312
  "moda skills gen",
5433
6313
  "moda skills status",
5434
6314
  "moda skills pull",
5435
- "moda skills sync",
6315
+ "moda skills push",
5436
6316
  "moda skills install --list",
5437
6317
  "moda skills install integration-node-openai"
5438
6318
  ],
5439
6319
  subcommands: SKILLS_SUBCOMMAND_HELP,
5440
6320
  auth: "api-key",
5441
- authNote: "install is local; gen, status, pull, sync, list, show, policy, and proposals need the API key.",
6321
+ authNote: "install is local; gen, status, pull, push, list, show, policy, and proposals need the API key.",
5442
6322
  outputModes: ["human", "json", "agent"],
5443
6323
  mutability: "write",
5444
6324
  validateConfigBeforeRun: false,
@@ -5543,44 +6423,45 @@ function cliValidationReason(error) {
5543
6423
  return;
5544
6424
  }
5545
6425
  function printCliError(error) {
6426
+ const safeWriteStderr2 = (text) => safeWriteStderr(canonicalCommandText(text));
5546
6427
  if (error instanceof CliInputError) {
5547
- safeWriteStderr(`Error: ${error.message}
6428
+ safeWriteStderr2(`Error: ${error.message}
5548
6429
  `);
5549
6430
  if (error.usage)
5550
- safeWriteStderr(`${error.usage}
6431
+ safeWriteStderr2(`${error.usage}
5551
6432
  `);
5552
6433
  } else if (error instanceof CliAccessError) {
5553
- safeWriteStderr(`Error: ${error.message}
6434
+ safeWriteStderr2(`Error: ${error.message}
5554
6435
  `);
5555
6436
  if (error.guidance)
5556
- safeWriteStderr(`${error.guidance}
6437
+ safeWriteStderr2(`${error.guidance}
5557
6438
  `);
5558
6439
  } else if (error instanceof z.ZodError) {
5559
6440
  const issues = error.errors.map((e) => {
5560
6441
  const field = e.path.length ? e.path.join(".") : "input";
5561
6442
  return ` ${field}: ${e.message}`;
5562
6443
  });
5563
- safeWriteStderr(`Validation error:
6444
+ safeWriteStderr2(`Validation error:
5564
6445
  ${[...new Set(issues)].join(`
5565
6446
  `)}
5566
6447
  `);
5567
6448
  } else if (error instanceof ApiError && error.readable) {
5568
- safeWriteStderr(`${error.message}
6449
+ safeWriteStderr2(`${error.message}
5569
6450
  `);
5570
6451
  if (error.suggestions?.length) {
5571
- safeWriteStderr(`Try:
6452
+ safeWriteStderr2(`Try:
5572
6453
  ${error.suggestions.map((s) => ` ${s}`).join(`
5573
6454
  `)}
5574
6455
  `);
5575
6456
  }
5576
6457
  } else if (error instanceof ApiError) {
5577
- safeWriteStderr(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}
6458
+ safeWriteStderr2(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}
5578
6459
  `);
5579
6460
  } else if (error instanceof Error) {
5580
- safeWriteStderr(`Error: ${error.message}
6461
+ safeWriteStderr2(`Error: ${error.message}
5581
6462
  `);
5582
6463
  } else {
5583
- safeWriteStderr(`Error: ${String(error)}
6464
+ safeWriteStderr2(`Error: ${String(error)}
5584
6465
  `);
5585
6466
  }
5586
6467
  }
@@ -5602,14 +6483,18 @@ ${colorOk ? `\x1B[2m${text}\x1B[0m` : text}
5602
6483
  function unknownCommandError(command) {
5603
6484
  const visible = commandRegistry.all().filter((entry) => !entry.hidden);
5604
6485
  const names = visible.flatMap((entry) => [entry.name, ...entry.aliases ?? []]);
5605
- const matches = closestMatches(command, names);
6486
+ const canonical = (name) => {
6487
+ const legacy = legacyNameFor(name, undefined, {});
6488
+ return legacy ? usePath(legacy) : name;
6489
+ };
6490
+ const matches = [...new Set(closestMatches(command, names).map(canonical))];
5606
6491
  const suggestion = matches.length > 0 ? ` Did you mean ${matches.map((name) => `'${name}'`).join(" or ")}?` : "";
5607
6492
  return new CliInputError(`Unknown command '${command}'.${suggestion}`, [
5608
6493
  ...matches.map((name) => `\`moda ${name} --help\``),
5609
6494
  "Run `moda --help` for the command list."
5610
6495
  ].join(" "), "unknown_command", {
5611
6496
  field: "<command>",
5612
- allowed: visible.map((entry) => entry.name),
6497
+ allowed: visible.map((entry) => entry.name).filter((name) => !legacyNameFor(name, undefined, {})),
5613
6498
  ...matches.length > 0 ? { didYouMean: matches } : {},
5614
6499
  suggestedCommands: [...matches.map((name) => `moda ${name} --help`), "moda --help"]
5615
6500
  });
@@ -5638,10 +6523,50 @@ async function dispatchParsedCommand(parsed, startedAt = Date.now()) {
5638
6523
  process.env.MODA_PROFILE = previousProfileEnv;
5639
6524
  }
5640
6525
  }
5641
- async function dispatchParsedCommandInner(parsed, startedAt) {
5642
- const { command, flags } = parsed;
6526
+ function usageIdentityFor(parsed, route, legacy, definition) {
6527
+ if (route)
6528
+ return { command: route.leaf.noun, subcommand: route.telemetrySubcommand };
6529
+ if (legacy) {
6530
+ const kind = legacy.legacy === "false-positive" && FALSE_POSITIVE_KINDS.includes(parsed.positional ?? "") ? parsed.positional : undefined;
6531
+ return { ...legacy.canonical, ...kind ? { subcommand: kind } : {}, legacyAlias: legacy.legacy };
6532
+ }
6533
+ return definition ? canonicalTelemetryForDefault(definition.name, parsed.positional) : undefined;
6534
+ }
6535
+ function rejectedRouteIdentity(parsed) {
6536
+ if (parsed.command === "review") {
6537
+ const kind = FALSE_POSITIVE_KINDS.includes(parsed.positional ?? "") ? parsed.positional : undefined;
6538
+ return { command: "review", ...kind ? { subcommand: kind } : {} };
6539
+ }
6540
+ const leaf = leafFor(parsed.command, parsed.positional);
6541
+ return { command: parsed.command, ...leaf ? { subcommand: leaf.verb } : {} };
6542
+ }
6543
+ function assertRoutedFlags(route, flags) {
6544
+ if (!route.allowedFlags)
6545
+ return;
6546
+ const shape = z.object(Object.fromEntries(route.allowedFlags.map((flag) => [flag.replace(/-/g, "_"), z.unknown()])));
6547
+ const aliases = new Set(Object.keys(route.flagAliases ?? {}));
6548
+ const args = Object.fromEntries(Object.keys(flags).filter((flag) => !aliases.has(flag.replace(/_/g, "-"))).map((flag) => [flag.replace(/-/g, "_"), true]));
6549
+ parseCommandFlags(shape.passthrough(), args, route.label, route.flagAliases ? { flagAliases: route.flagAliases } : {});
6550
+ }
6551
+ function renameErrorCommands(error, renames) {
6552
+ if (error instanceof CliInputError) {
6553
+ const details2 = error.details;
6554
+ 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);
6555
+ const warnings = error.warnings;
6556
+ if (warnings !== undefined)
6557
+ Object.assign(renamed, { warnings });
6558
+ return renamed;
6559
+ }
6560
+ const details = inputDetailsOf(error);
6561
+ if (details?.suggestedCommands && error !== null && typeof error === "object") {
6562
+ attachInputDetails(error, { ...details, suggestedCommands: details.suggestedCommands.map((command) => renameCommands(command, renames)) });
6563
+ }
6564
+ return error;
6565
+ }
6566
+ async function dispatchParsedCommandInner(typedParsed, startedAt) {
6567
+ const { command, flags } = typedParsed;
5643
6568
  const switchError = normalizeBooleanFlags(flags);
5644
- const commandFlags = stripUpdateCheckFlag(stripTelemetryFlag(flags));
6569
+ const typedFlags = stripUpdateCheckFlag(stripTelemetryFlag(flags));
5645
6570
  const telemetryDisabled = isTelemetryDisabled(flags);
5646
6571
  const updateCheckDisabled = isUpdateCheckDisabled(flags);
5647
6572
  if (command === "--version" || command === "-V" || flags.version === "true") {
@@ -5649,9 +6574,11 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5649
6574
  return 0;
5650
6575
  }
5651
6576
  if (command === "--help" || command === "-h" || flags.help === "true") {
6577
+ if (command && printTreeHelp(typedParsed))
6578
+ return 0;
5652
6579
  const helpTarget = command && command !== "--help" && command !== "-h" ? commandRegistry.find(command) : undefined;
5653
6580
  if (helpTarget) {
5654
- const subcommandName = parsed.positional;
6581
+ const subcommandName = typedParsed.positional;
5655
6582
  const subcommandEntry = subcommandName ? helpTarget.subcommands?.find((entry) => entry.name === subcommandName) : undefined;
5656
6583
  if (subcommandEntry) {
5657
6584
  printSubcommandHelp(helpTarget, subcommandEntry);
@@ -5671,62 +6598,117 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5671
6598
  printHelp();
5672
6599
  return 1;
5673
6600
  }
5674
- const failEarly = (error, commandDefinition) => {
5675
- const mode = resolveOutputMode(commandFlags, {
5676
- isTTY: process.stdout.isTTY,
5677
- env: process.env
6601
+ let route;
6602
+ let routeError;
6603
+ try {
6604
+ route = routeCanonical(command, typedParsed.positionals, typedFlags);
6605
+ } catch (error) {
6606
+ routeError = error;
6607
+ }
6608
+ const legacy = route || routeError ? undefined : legacyNameFor(command, typedParsed.positional, typedFlags);
6609
+ const parsed = route ? {
6610
+ ...typedParsed,
6611
+ command: route.handler,
6612
+ positional: route.positionals[0],
6613
+ positionals: route.positionals,
6614
+ flags: route.rewriteFlags(flags)
6615
+ } : typedParsed;
6616
+ const commandFlags = route ? route.rewriteFlags(typedFlags) : typedFlags;
6617
+ const outputModeFor = () => resolveOutputMode(typedFlags, {
6618
+ isTTY: process.stdout.isTTY,
6619
+ env: process.env
6620
+ });
6621
+ const reportUsage = (input) => {
6622
+ const { definition: definition2, identity: identity2 } = input;
6623
+ const handlerTelemetry = definition2 ? telemetryCommandName(definition2) : identity2?.command;
6624
+ if (!handlerTelemetry)
6625
+ return;
6626
+ const telemetryProfile = existingFlagProfile(typedFlags);
6627
+ const adoption = buildSearchAdoptionTelemetry({
6628
+ command: handlerTelemetry,
6629
+ positional: parsed.positional,
6630
+ flags: commandFlags,
6631
+ outputMode: input.outputMode,
6632
+ recorded: input.recorded,
6633
+ skillVersion: process.env.MODA_SKILL_VERSION,
6634
+ subcommands: definition2 ? telemetrySubcommandNames(definition2) : [],
6635
+ defaultSubcommand: definition2?.defaultSubcommand
6636
+ });
6637
+ if (identity2 && adoption) {
6638
+ if (identity2.subcommand)
6639
+ adoption.subcommand = identity2.subcommand;
6640
+ else
6641
+ delete adoption.subcommand;
6642
+ }
6643
+ sendCliUsageTelemetry({
6644
+ apiKey: input.apiKey ?? resolveApiKey({ profile: telemetryProfile }),
6645
+ profile: telemetryProfile,
6646
+ command: identity2?.command ?? handlerTelemetry,
6647
+ flags: typedFlags,
6648
+ status: input.status,
6649
+ ...input.error !== undefined ? { errorType: classifyCliError(input.error), validationReason: cliValidationReason(input.error) } : {},
6650
+ exitCode: input.exitCode,
6651
+ durationMs: Date.now() - startedAt,
6652
+ apiRequestCount: input.apiRequestCount,
6653
+ adoption,
6654
+ ...identity2?.legacyAlias ? { legacyAlias: identity2.legacyAlias } : {}
5678
6655
  });
6656
+ };
6657
+ const failEarly = (error, commandDefinition, identity2) => {
6658
+ const mode = outputModeFor();
5679
6659
  const telemetryOptOut = telemetryDisabled || flags["no-telemetry"] !== undefined && flags["no-telemetry"] !== "false";
5680
- if (commandDefinition && !telemetryOptOut && commandDefinition.telemetry === "result-and-error") {
5681
- const telemetryCommand2 = telemetryCommandName(commandDefinition);
5682
- const telemetryProfile = existingFlagProfile(commandFlags);
5683
- sendCliUsageTelemetry({
5684
- apiKey: resolveApiKey({ profile: telemetryProfile }),
5685
- profile: telemetryProfile,
5686
- command: telemetryCommand2,
5687
- flags: commandFlags,
6660
+ const reports = commandDefinition ? commandDefinition.telemetry === "result-and-error" : identity2 !== undefined;
6661
+ if (reports && !telemetryOptOut) {
6662
+ reportUsage({
6663
+ definition: commandDefinition,
6664
+ identity: identity2,
5688
6665
  status: "error",
5689
- errorType: classifyCliError(error),
5690
- validationReason: cliValidationReason(error),
6666
+ error,
5691
6667
  exitCode: 1,
5692
- durationMs: Date.now() - startedAt,
5693
6668
  apiRequestCount: 0,
5694
- adoption: buildSearchAdoptionTelemetry({
5695
- command: telemetryCommand2,
5696
- positional: parsed.positional,
5697
- flags: commandFlags,
5698
- outputMode: mode,
5699
- recorded: {},
5700
- skillVersion: process.env.MODA_SKILL_VERSION,
5701
- subcommands: telemetrySubcommandNames(commandDefinition),
5702
- defaultSubcommand: commandDefinition.defaultSubcommand
5703
- })
6669
+ outputMode: mode,
6670
+ recorded: {}
5704
6671
  });
5705
6672
  }
5706
6673
  if (mode === "agent" || mode === "agent-stream" || mode === "json") {
5707
- printAgentCliError(command, error, mode);
6674
+ printAgentCliError(route?.leaf.path ?? command, error, mode);
5708
6675
  } else {
5709
6676
  printCliError(error);
5710
6677
  }
5711
6678
  return 1;
5712
6679
  };
5713
- const definition = commandRegistry.find(command);
6680
+ if (routeError) {
6681
+ if (!(routeError instanceof CliInputError))
6682
+ throw routeError;
6683
+ return failEarly(routeError, undefined, rejectedRouteIdentity(typedParsed));
6684
+ }
6685
+ const definition = commandRegistry.find(parsed.command);
5714
6686
  if (!definition)
5715
6687
  return failEarly(unknownCommandError(command));
6688
+ const identity = usageIdentityFor(typedParsed, route, legacy, definition);
5716
6689
  if (parsed.shortFlags?.length) {
5717
- 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);
6690
+ 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);
5718
6691
  }
5719
6692
  if (switchError)
5720
- return failEarly(switchError, definition);
6693
+ return failEarly(switchError, definition, identity);
6694
+ if (route) {
6695
+ try {
6696
+ assertRoutedFlags(route, typedFlags);
6697
+ } catch (error) {
6698
+ if (!(error instanceof CliInputError))
6699
+ throw error;
6700
+ return failEarly(error, definition, identity);
6701
+ }
6702
+ }
5721
6703
  if (commandFlags.profile === "true" || commandFlags.profile === "") {
5722
- return failEarly(new CliInputError("--profile needs a value, e.g. --profile=prod.", "List profiles with 'moda profiles list'.", "missing_value:profile"), definition);
6704
+ return failEarly(new CliInputError("--profile needs a value, e.g. --profile=prod.", "List profiles with 'moda profiles list'.", "missing_value:profile"), definition, identity);
5723
6705
  }
5724
6706
  const requestedProfile = commandFlags.profile ?? process.env.MODA_PROFILE?.trim();
5725
6707
  if (requestedProfile && !PROFILE_MANAGING_COMMANDS.has(definition.name)) {
5726
6708
  const known = Object.keys(loadProfileConfig({ env: process.env }).profiles);
5727
6709
  if (!known.includes(requestedProfile)) {
5728
6710
  const source = commandFlags.profile ? `--profile=${requestedProfile}` : `MODA_PROFILE=${requestedProfile}`;
5729
- 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);
6711
+ return failEarly(new CliInputError(`Moda profile '${requestedProfile}' does not exist (from ${source}), so nothing was run.`, `Known profiles: ${known.join(", ")}. Create it with 'moda profiles create ${requestedProfile}' or fix the name.`, "unknown_profile"), definition, identity);
5730
6712
  }
5731
6713
  if (commandFlags.profile)
5732
6714
  process.env.MODA_PROFILE = commandFlags.profile;
@@ -5741,47 +6723,45 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5741
6723
  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;
5742
6724
  });
5743
6725
  }
6726
+ const deprecation = legacy && !legacy.silent ? { legacy: `moda ${legacy.legacy}`, use: legacy.use, message: deprecationMessage(legacy) } : undefined;
5744
6727
  const context = createCommandContext(parsed, {
5745
6728
  commandName: definition.name,
5746
6729
  commandFlags,
5747
- rawFlags: flags,
6730
+ rawFlags: parsed.flags,
5748
6731
  startedAt,
5749
- outputMode: resolveOutputMode(commandFlags, {
5750
- isTTY: process.stdout.isTTY,
5751
- env: process.env
5752
- })
6732
+ outputMode: outputModeFor(),
6733
+ ...route ? {
6734
+ outputCommand: route.leaf.path,
6735
+ invocation: {
6736
+ command: route.leaf.path,
6737
+ positionals: route.leaf.verb ? typedParsed.positionals.slice(1) : typedParsed.positionals,
6738
+ flags,
6739
+ ...typedParsed.bareFlags ? { bareFlags: typedParsed.bareFlags } : {},
6740
+ routedAs: { command: route.handler, positionals: route.positionals }
6741
+ }
6742
+ } : {},
6743
+ ...deprecation ? { deprecation } : {}
5753
6744
  });
5754
6745
  setActiveContext(context);
5755
- const telemetryCommand = telemetryCommandName(definition);
5756
- const telemetrySubcommands = telemetrySubcommandNames(definition);
6746
+ if (deprecation && !context.quiet && (context.outputMode === "human" || context.outputMode === "json")) {
6747
+ safeWriteStderr(`Deprecated: ${deprecation.message}
6748
+ `);
6749
+ }
5757
6750
  const needsConfig = typeof definition.validateConfigBeforeRun === "function" ? definition.validateConfigBeforeRun(parsed) : definition.validateConfigBeforeRun;
5758
6751
  if (needsConfig) {
5759
6752
  try {
5760
6753
  validateConfig();
5761
6754
  } catch (error) {
5762
6755
  if (!telemetryDisabled && definition.telemetry === "result-and-error") {
5763
- const telemetryProfile = existingFlagProfile(commandFlags);
5764
- sendCliUsageTelemetry({
5765
- apiKey: resolveApiKey({ profile: telemetryProfile }),
5766
- profile: telemetryProfile,
5767
- command: telemetryCommand,
5768
- flags: commandFlags,
6756
+ reportUsage({
6757
+ definition,
6758
+ identity,
5769
6759
  status: "error",
5770
- errorType: classifyCliError(error),
5771
- validationReason: cliValidationReason(error),
6760
+ error,
5772
6761
  exitCode: exitCodeForError(error),
5773
- durationMs: Date.now() - startedAt,
5774
6762
  apiRequestCount: getApiRequestCount(),
5775
- adoption: buildSearchAdoptionTelemetry({
5776
- command: telemetryCommand,
5777
- positional: parsed.positional,
5778
- flags: commandFlags,
5779
- outputMode: context.outputMode,
5780
- recorded: context.getTelemetryMeta(),
5781
- skillVersion: process.env.MODA_SKILL_VERSION,
5782
- subcommands: telemetrySubcommands,
5783
- defaultSubcommand: definition.defaultSubcommand
5784
- })
6763
+ outputMode: context.outputMode,
6764
+ recorded: context.getTelemetryMeta()
5785
6765
  });
5786
6766
  }
5787
6767
  if (context.outputMode === "agent" || context.outputMode === "agent-stream" || context.outputMode === "json") {
@@ -5798,26 +6778,15 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5798
6778
  try {
5799
6779
  const result = await definition.handler(context);
5800
6780
  if (!telemetryDisabled && definition.telemetry !== "none") {
5801
- const telemetryProfile = existingFlagProfile(commandFlags);
5802
- sendCliUsageTelemetry({
5803
- apiKey: result.apiKey ?? resolveApiKey({ profile: telemetryProfile }),
5804
- profile: telemetryProfile,
5805
- command: telemetryCommand,
5806
- flags: commandFlags,
6781
+ reportUsage({
6782
+ definition,
6783
+ identity,
5807
6784
  status: result.exitCode === 0 ? "success" : "error",
5808
6785
  exitCode: result.exitCode,
5809
- durationMs: Date.now() - startedAt,
6786
+ apiKey: result.apiKey,
5810
6787
  apiRequestCount: getApiRequestCount(),
5811
- adoption: buildSearchAdoptionTelemetry({
5812
- command: telemetryCommand,
5813
- positional: parsed.positional,
5814
- flags: commandFlags,
5815
- outputMode: context.outputMode,
5816
- recorded: context.getTelemetryMeta(),
5817
- skillVersion: process.env.MODA_SKILL_VERSION,
5818
- subcommands: telemetrySubcommands,
5819
- defaultSubcommand: definition.defaultSubcommand
5820
- })
6788
+ outputMode: context.outputMode,
6789
+ recorded: context.getTelemetryMeta()
5821
6790
  });
5822
6791
  }
5823
6792
  if (!updateCheckDisabled) {
@@ -5825,30 +6794,18 @@ async function dispatchParsedCommandInner(parsed, startedAt) {
5825
6794
  }
5826
6795
  printFeedbackTip(definition.name, context);
5827
6796
  return finishRun(context, { kind: "completed", exitCode: result.exitCode });
5828
- } catch (error) {
6797
+ } catch (thrown) {
6798
+ const error = route ? renameErrorCommands(thrown, route.renames) : thrown;
5829
6799
  if (!telemetryDisabled && definition.telemetry === "result-and-error") {
5830
- const telemetryProfile = existingFlagProfile(commandFlags);
5831
- sendCliUsageTelemetry({
5832
- apiKey: resolveApiKey({ profile: telemetryProfile }),
5833
- profile: telemetryProfile,
5834
- command: telemetryCommand,
5835
- flags: commandFlags,
6800
+ reportUsage({
6801
+ definition,
6802
+ identity,
5836
6803
  status: "error",
5837
- errorType: classifyCliError(error),
5838
- validationReason: cliValidationReason(error),
6804
+ error,
5839
6805
  exitCode: exitCodeForError(error),
5840
- durationMs: Date.now() - startedAt,
5841
6806
  apiRequestCount: getApiRequestCount(),
5842
- adoption: buildSearchAdoptionTelemetry({
5843
- command: telemetryCommand,
5844
- positional: parsed.positional,
5845
- flags: commandFlags,
5846
- outputMode: context.outputMode,
5847
- recorded: context.getTelemetryMeta(),
5848
- skillVersion: process.env.MODA_SKILL_VERSION,
5849
- subcommands: telemetrySubcommands,
5850
- defaultSubcommand: definition.defaultSubcommand
5851
- })
6807
+ outputMode: context.outputMode,
6808
+ recorded: context.getTelemetryMeta()
5852
6809
  });
5853
6810
  }
5854
6811
  if (context.outputMode === "agent" || context.outputMode === "agent-stream" || context.outputMode === "json") {