@moda-ai/cli 1.55.25 → 1.55.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/{agent-t7dxmpdq.js → agent-vp9prdma.js} +8 -8
  2. package/dist/{cli-qycsmxev.js → cli-0pkc8jhg.js} +3 -3
  3. package/dist/{cli-mebf5vtr.js → cli-4h0ave0f.js} +5 -5
  4. package/dist/{cli-m0045npe.js → cli-6et4z504.js} +10 -8
  5. package/dist/{cli-9hc5bg2r.js → cli-ccjfwzqx.js} +1 -1
  6. package/dist/{cli-0wr3483h.js → cli-hmjdgxrn.js} +1 -1
  7. package/dist/{cli-9btx12wp.js → cli-pg5qfmqc.js} +1 -1
  8. package/dist/{cli-m805msp6.js → cli-q9nsvqqg.js} +14 -14
  9. package/dist/{cli-7xc585hc.js → cli-qwgct6x7.js} +7 -6
  10. package/dist/{cli-dve50x7p.js → cli-r5pp7qny.js} +140 -455
  11. package/dist/{cli-qvg26twp.js → cli-v8q7sd7g.js} +38 -13
  12. package/dist/cli-vdkwkkp5.js +567 -0
  13. package/dist/{cli-dn91fb56.js → cli-wzds257x.js} +92 -49
  14. package/dist/cli.js +986 -362
  15. package/dist/{doctor-q21mr5ya.js → doctor-wmre3mr7.js} +8 -8
  16. package/dist/{fix-0qdtdz5h.js → fix-75s9s0d6.js} +45 -47
  17. package/dist/{harness-vzy7n7ea.js → harness-a7tb9a23.js} +5 -5
  18. package/dist/{harness-github-actions-atc5bbkj.js → harness-github-actions-6v4wydb8.js} +5 -5
  19. package/dist/{index-4b1ybz51.js → index-91bpc8g8.js} +15 -14
  20. package/dist/{production-intelligence-c8e0xw3n.js → production-intelligence-7nqddd4f.js} +88 -28
  21. package/dist/{profile-commands-6e2dgv72.js → profile-commands-49yn02yg.js} +5 -4
  22. package/dist/prompt-source-mcp.js +5 -5
  23. package/dist/{prompts-3h5jcjrw.js → prompts-17wyrst6.js} +6 -6
  24. package/dist/{provision-q86phh4a.js → provision-bc3h68e1.js} +5 -5
  25. package/dist/{registry-s2vdsfw8.js → registry-fpzhjznj.js} +3 -3
  26. package/dist/{skills-vq3njc8a.js → skills-ccdbqghj.js} +5 -5
  27. package/dist/{sync-hq9dpqpr.js → sync-hkxce5zh.js} +7 -7
  28. package/package.json +1 -1
  29. package/skills/integration/index.json +2 -2
  30. package/skills/moda-cli/SKILL.md +4 -1
  31. package/skills/moda-cli/references/dashboard-parity.md +1 -1
  32. package/skills/moda-cli/references/output-protocol.md +35 -10
  33. package/skills/moda-cli/references/search-and-traces.md +20 -2
  34. package/dist/cli-bb7133m7.js +0 -144
@@ -449,6 +449,14 @@ function resolveBaseUrl(options = {}) {
449
449
  return PROD_BASE_URL;
450
450
  }
451
451
  }
452
+ function resolveBaseUrlStrict(options = {}) {
453
+ try {
454
+ getActiveProfile(options);
455
+ } catch {
456
+ return;
457
+ }
458
+ return resolveBaseUrl(options);
459
+ }
452
460
  function resolveIngestUrl(options = {}) {
453
461
  const environment = activeProfileEnvironment(options);
454
462
  const envIngest = optionalString(getEnv(options).MODA_INGEST_URL);
@@ -668,6 +676,9 @@ var MODA_INGEST_URL = process.env.MODA_INGEST_URL || DEFAULT_INGEST_URL;
668
676
  function resolveModaBaseUrl(options = {}) {
669
677
  return resolveBaseUrl(options) || PROD_BASE_URL;
670
678
  }
679
+ function resolveModaBaseUrlStrict(options = {}) {
680
+ return resolveBaseUrlStrict(options);
681
+ }
671
682
  function validateConfig() {
672
683
  if (!resolveApiKey()) {
673
684
  throw new CliAuthError('No Moda API key found. Set MODA_API_KEY=moda_sk_... in the environment, save one with "moda auth login --api-key", or run "moda init" for interactive setup.', {
@@ -689,427 +700,6 @@ function resetApiRequestCount() {
689
700
  apiRequestCount = 0;
690
701
  }
691
702
 
692
- // src/batch-reads.ts
693
- import { readFile } from "node:fs/promises";
694
- var BATCH_MAX_TARGETS = 25;
695
- var BATCH_CONCURRENCY = 4;
696
- var ANCHOR_SUFFIX = /^(.+)@(\d+)$/;
697
- function parseBatchTarget(text, anchors, usage) {
698
- const raw = text.trim();
699
- if (raw.startsWith("{")) {
700
- let parsed;
701
- try {
702
- parsed = JSON.parse(raw);
703
- } catch {
704
- throw new CliInputError(`Could not parse the JSON target ${JSON.stringify(raw)}.`, usage, "bad_batch_target");
705
- }
706
- const record = parsed;
707
- const id = record?.id ?? record?.conversation_id ?? record?.trace_id;
708
- if (typeof id !== "string" || id.trim() === "") {
709
- throw new CliInputError(`JSON target ${JSON.stringify(raw)} has no "id" string.`, usage, "bad_batch_target");
710
- }
711
- const index = record.msg_index ?? record.message_index;
712
- if (index !== undefined && index !== null) {
713
- if (!anchors) {
714
- throw new CliInputError(`JSON target ${JSON.stringify(raw)} has a msg_index, which this command does not take.`, usage, "bad_batch_target");
715
- }
716
- if (typeof index !== "number" || !Number.isSafeInteger(index) || index < 0) {
717
- throw new CliInputError(`JSON target ${JSON.stringify(raw)} has an invalid msg_index (want an integer >= 0).`, usage, "bad_batch_target");
718
- }
719
- return { raw, id, msgIndex: index };
720
- }
721
- return { raw, id };
722
- }
723
- if (anchors) {
724
- const match = ANCHOR_SUFFIX.exec(raw);
725
- if (match) {
726
- const msgIndex = Number(match[2]);
727
- if (!Number.isSafeInteger(msgIndex)) {
728
- throw new CliInputError(`Target ${JSON.stringify(raw)} has an @msg_index past ${Number.MAX_SAFE_INTEGER}. ` + 'If the id itself ends in @<digits>, pass it as {"id": "..."}.', usage, "bad_batch_target");
729
- }
730
- return { raw, id: match[1], msgIndex };
731
- }
732
- }
733
- return { raw, id: raw };
734
- }
735
- function parseTargetLines(text, anchors, usage) {
736
- return text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== "").map((line) => parseBatchTarget(line, anchors, usage));
737
- }
738
- async function readStdinText() {
739
- if (process.stdin.isTTY) {
740
- throw new CliInputError("--ids-from=- reads targets from stdin, but stdin is a terminal. Pipe ids in (one per line) or pass a file path.", undefined, "ids_from_tty");
741
- }
742
- const chunks = [];
743
- for await (const chunk of process.stdin)
744
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
745
- return Buffer.concat(chunks).toString("utf8");
746
- }
747
- async function readIdsFrom(source, usage) {
748
- if (source === "" || source === "true") {
749
- throw new CliInputError("--ids-from needs a value: - (stdin) or a file path.", usage, "missing_flag_value");
750
- }
751
- if (source === "-")
752
- return readStdinText();
753
- try {
754
- return await readFile(source, "utf8");
755
- } catch (error) {
756
- const code = error.code;
757
- throw new CliInputError(`Could not read --ids-from file ${JSON.stringify(source)}${typeof code === "string" ? ` (${code})` : ""}.`, usage, "ids_from_unreadable");
758
- }
759
- }
760
- async function collectBatchTargets(positionals, idsFrom, options) {
761
- const targets = positionals.map((value) => parseBatchTarget(value, options.anchors, options.usage));
762
- if (idsFrom !== undefined) {
763
- targets.push(...parseTargetLines(await readIdsFrom(idsFrom, options.usage), options.anchors, options.usage));
764
- }
765
- const seen = new Set;
766
- const unique = targets.filter((target) => {
767
- const key = `${target.id}\x00${target.msgIndex ?? ""}`;
768
- if (seen.has(key))
769
- return false;
770
- seen.add(key);
771
- return true;
772
- });
773
- if (unique.length === 0) {
774
- throw new CliInputError(`No targets: --ids-from gave no ids (one per line).`, options.usage, "missing_positional");
775
- }
776
- if (unique.length > BATCH_MAX_TARGETS) {
777
- throw new CliInputError(`Got ${unique.length} targets; 'moda ${options.command}' reads at most ${BATCH_MAX_TARGETS} per call, so nothing was read. ` + `Split the ids into calls of ${BATCH_MAX_TARGETS} or fewer.`, options.usage, "batch_too_large");
778
- }
779
- return unique;
780
- }
781
- async function mapWithConcurrency(items, limit, fn) {
782
- const results = new Array(items.length);
783
- let next = 0;
784
- let failure;
785
- const worker = async () => {
786
- while (!failure && next < items.length) {
787
- const index = next++;
788
- try {
789
- results[index] = await fn(items[index], index);
790
- } catch (error) {
791
- failure ??= { error };
792
- }
793
- }
794
- };
795
- await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
796
- if (failure)
797
- throw failure.error;
798
- return results;
799
- }
800
- function idNeedsJsonTarget(id) {
801
- return ANCHOR_SUFFIX.test(id);
802
- }
803
- function contextTarget(id) {
804
- const verbatim = id === id.trim() && !id.startsWith("{") && !idNeedsJsonTarget(id);
805
- return verbatim ? id : JSON.stringify({ id });
806
- }
807
-
808
- // src/transforms.ts
809
- function asRecord(value) {
810
- if (value && typeof value === "object" && !Array.isArray(value)) {
811
- return value;
812
- }
813
- return null;
814
- }
815
- function asString(value) {
816
- return typeof value === "string" ? value : null;
817
- }
818
- function asNumber(value) {
819
- return typeof value === "number" && Number.isFinite(value) ? value : null;
820
- }
821
- function frustrationAnchorIndex(row) {
822
- if (!row)
823
- return null;
824
- const userQuotes = Array.isArray(row.user_quotes) ? row.user_quotes : [];
825
- const firstQuote = asRecord(userQuotes[0]);
826
- const resolved = asNumber(firstQuote?.msg_index) ?? asNumber(row.anchor_msg_index);
827
- if (resolved !== null)
828
- return resolved;
829
- const keyTurns = Array.isArray(row.key_turns) ? row.key_turns : [];
830
- const firstTurn = keyTurns.find((t) => typeof t === "number");
831
- if (firstTurn !== undefined)
832
- return firstTurn;
833
- return asNumber(firstQuote?.turn);
834
- }
835
- function buildFrustrationAnchor(row) {
836
- const conversation_id = asString(row.conversation_id) ?? "";
837
- const userQuotes = Array.isArray(row.user_quotes) ? row.user_quotes : [];
838
- const firstQuote = asRecord(userQuotes[0]);
839
- const keyTurnsRaw = Array.isArray(row.key_turns) ? row.key_turns : [];
840
- const all_turns = keyTurnsRaw.filter((t) => typeof t === "number");
841
- const msg_index = frustrationAnchorIndex(row);
842
- let signal = null;
843
- let quote_preview = null;
844
- if (firstQuote) {
845
- signal = asString(firstQuote.signal);
846
- const quote = asString(firstQuote.quote);
847
- if (quote !== null) {
848
- let end = 0;
849
- let points = 0;
850
- for (const ch of quote) {
851
- if (points++ === 200)
852
- break;
853
- end += ch.length;
854
- }
855
- quote_preview = quote.slice(0, end);
856
- }
857
- }
858
- return {
859
- kind: "frustration",
860
- conversation_id,
861
- msg_index,
862
- signal,
863
- quote_preview,
864
- all_turns,
865
- no_anchor: msg_index === null
866
- };
867
- }
868
- function buildToolFailureAnchor(row, toolName) {
869
- const conversation_id = asString(row.conversation_id) ?? "";
870
- const msg_index = asNumber(row.msg_index);
871
- const tool_use_id = asString(row.tool_use_id);
872
- const error_subtype = asString(row.error_subtype);
873
- return {
874
- kind: "tool_failure",
875
- conversation_id,
876
- msg_index,
877
- tool_name: toolName,
878
- tool_use_id,
879
- error_subtype,
880
- no_anchor: msg_index === null
881
- };
882
- }
883
- function frustrationsFromEmotions(response) {
884
- if (!Array.isArray(response.detections) || Array.isArray(response.frustrations))
885
- return response;
886
- const summary = asRecord(response.summary) ?? {};
887
- const pagination = asRecord(response.pagination) ?? {};
888
- const byFamily = asRecord(summary.by_family) ?? {};
889
- const totalAnalyzed = asNumber(summary.conversations_analyzed) ?? 0;
890
- const byFamilyConversations = asRecord(summary.conversations_by_family);
891
- const frustrated = asNumber(byFamilyConversations?.frustration) ?? asNumber(byFamily.frustration) ?? 0;
892
- const frustrations = response.detections.map((item) => asRecord(item)).filter((row) => row !== null && (row.family === undefined || row.family === "frustration")).map(({ conversation_context, score, is_detected, ...row }) => ({
893
- ...row,
894
- is_frustrated: is_detected === true,
895
- frustration_score: score,
896
- target: row.elicitor === "bot" || row.elicitor === "external" ? row.elicitor : "ambiguous",
897
- conversation: conversation_context ?? null
898
- }));
899
- const atRisk = asNumber(pagination.at_risk) ?? Math.max(0, (asNumber(pagination.total) ?? 0) - frustrated);
900
- const signalBreakdown = {};
901
- for (const item of Array.isArray(response.signal_breakdown) ? response.signal_breakdown : []) {
902
- const row = asRecord(item);
903
- if (row?.family === "frustration" && typeof row.signal === "string") {
904
- signalBreakdown[row.signal] = asNumber(row.count) ?? 0;
905
- }
906
- }
907
- const { detections: _detections, ...rest } = response;
908
- return {
909
- ...rest,
910
- summary: {
911
- total_analyzed: totalAnalyzed,
912
- frustrated_count: frustrated,
913
- at_risk_count: atRisk,
914
- frustration_rate_pct: totalAnalyzed > 0 ? Math.round(frustrated / totalAnalyzed * 1000) / 10 : 0
915
- },
916
- signal_breakdown: signalBreakdown,
917
- frustrations,
918
- source: "emotion_detections"
919
- };
920
- }
921
- function withFrustrationAnchors(response) {
922
- if (!Array.isArray(response.frustrations))
923
- return response;
924
- const frustrations = response.frustrations.map((item) => {
925
- const row = asRecord(item);
926
- if (!row)
927
- return item;
928
- const anchor = buildFrustrationAnchor(row);
929
- return { ...row, anchor };
930
- });
931
- return { ...response, frustrations };
932
- }
933
- var TOOL_FAILURE_CONTEXT_RADIUS = 2;
934
- function shellWord(value) {
935
- return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
936
- }
937
- function trimFailureWindow(value, radius, fallbackCenter) {
938
- const conv = asRecord(value);
939
- if (!conv)
940
- return value ?? null;
941
- const { coverage: _coverage, context, ...convRest } = conv;
942
- const ctx = asRecord(context);
943
- if (!ctx)
944
- return convRest;
945
- const center = asNumber(ctx.center_index) ?? fallbackCenter;
946
- const messages = (Array.isArray(ctx.messages) ? ctx.messages : []).map((message) => asRecord(message)).filter((message) => {
947
- const index = asNumber(message?.index);
948
- return message !== null && (center === null || index === null || Math.abs(index - center) <= radius);
949
- }).map(({ content_blocks, ...message }) => {
950
- const covered = typeof message.content === "string" && message.content.length > 0 || Array.isArray(message.tool_calls) && message.tool_calls.length > 0 || Array.isArray(message.tool_results) && message.tool_results.length > 0;
951
- return covered || content_blocks === undefined ? message : { ...message, content_blocks };
952
- });
953
- const indexes = messages.map((message) => asNumber(message.index)).filter((index) => index !== null);
954
- return {
955
- ...convRest,
956
- context: {
957
- ...ctx,
958
- ...indexes.length > 0 ? { from_index: Math.min(...indexes), to_index: Math.max(...indexes) } : {},
959
- messages
960
- }
961
- };
962
- }
963
- function trimToolFailureConversations(response, opts) {
964
- if (!Array.isArray(response.examples))
965
- return response;
966
- const examples = response.examples.map((item) => {
967
- const row = asRecord(item);
968
- if (!row)
969
- return item;
970
- const { conversation, window, ...rest } = row;
971
- const conversationId = asString(row.conversation_id);
972
- const msgIndex = asNumber(asRecord(row.anchor)?.msg_index) ?? asNumber(row.msg_index);
973
- return {
974
- ...rest,
975
- ...conversationId && msgIndex !== null ? { context_command: `moda traces get ${shellWord(contextTarget(conversationId))} --msg-index=${msgIndex} --window=${TOOL_FAILURE_CONTEXT_RADIUS}` } : {},
976
- ...opts.drop ? {} : { conversation: trimFailureWindow(conversation, TOOL_FAILURE_CONTEXT_RADIUS, msgIndex) },
977
- ..."window" in row ? { window: trimFailureWindow(window, opts.windowRadius ?? TOOL_FAILURE_CONTEXT_RADIUS, msgIndex) } : {}
978
- };
979
- });
980
- return { ...response, examples };
981
- }
982
- function withToolFailureAnchors(response) {
983
- if (!Array.isArray(response.examples))
984
- return response;
985
- const toolName = asString(response.tool_name) ?? "";
986
- const examples = response.examples.map((item) => {
987
- const row = asRecord(item);
988
- if (!row)
989
- return item;
990
- const anchor = buildToolFailureAnchor(row, toolName);
991
- return { ...row, anchor };
992
- });
993
- return { ...response, examples };
994
- }
995
- function embeddedWindowSize(conversation) {
996
- if (!conversation)
997
- return null;
998
- const ctx = asRecord(conversation.context);
999
- if (!ctx)
1000
- return null;
1001
- const center = asNumber(ctx.center_index);
1002
- const from = asNumber(ctx.from_index);
1003
- const to = asNumber(ctx.to_index);
1004
- if (center === null || from === null || to === null)
1005
- return null;
1006
- return Math.min(center - from, to - center);
1007
- }
1008
- var DETECTION_CONTEXT_RADIUS = 2;
1009
- function withoutDetectionWindows(response, listKey, windowKey) {
1010
- const list = response[listKey];
1011
- if (!Array.isArray(list))
1012
- return response;
1013
- const rows = list.map((item) => {
1014
- const row = asRecord(item);
1015
- if (!row)
1016
- return item;
1017
- const { [windowKey]: _window, ...rest } = row;
1018
- const conversationId = asString(row.conversation_id);
1019
- const msgIndex = asNumber(asRecord(row.anchor)?.msg_index) ?? frustrationAnchorIndex(row);
1020
- return {
1021
- ...rest,
1022
- ...conversationId && msgIndex !== null ? { context_command: `moda traces get ${shellWord(contextTarget(conversationId))} --msg-index=${msgIndex} --window=${DETECTION_CONTEXT_RADIUS}` } : {}
1023
- };
1024
- });
1025
- return { ...response, [listKey]: rows };
1026
- }
1027
- var PROBLEM_COMPACT_EVIDENCE = 5;
1028
- var PROBLEM_COMPACT_OMITTED_BLOCKS = [
1029
- "rubric",
1030
- "trend",
1031
- "sub_problems",
1032
- "affected_conversations",
1033
- "remainder",
1034
- "confidence",
1035
- "change_history",
1036
- "stories"
1037
- ];
1038
- function readCommand(command, purpose) {
1039
- return { command, purpose, mutability: "read", requires_approval: false };
1040
- }
1041
- function evidenceMsgIndex(row) {
1042
- return asNumber(row.anchor_msg_index) ?? asNumber(asRecord(asRecord(row.snippet_parsed)?.anchor)?.msg_index);
1043
- }
1044
- function compactProblemDossier(response, problemId) {
1045
- const id = shellWord(asString(response.problem_id) ?? problemId);
1046
- const evidence = (Array.isArray(response.evidence) ? response.evidence : []).map((item) => asRecord(item)).filter((row) => row !== null);
1047
- const topEvidence = evidence.slice(0, PROBLEM_COMPACT_EVIDENCE).map((row) => {
1048
- const conversationId = asString(row.conversation_id);
1049
- const msgIndex = evidenceMsgIndex(row);
1050
- return {
1051
- attribution_id: row.attribution_id ?? null,
1052
- conversation_id: conversationId,
1053
- msg_index: msgIndex,
1054
- signal_family: row.signal_family ?? null,
1055
- signal_type: row.signal_type ?? null,
1056
- confidence: row.confidence ?? null,
1057
- causal_rationale: row.causal_rationale ?? null,
1058
- door: row.door ?? null,
1059
- replay_verified: row.replay_verified ?? null,
1060
- ...conversationId && msgIndex !== null ? { context_command: `moda traces get ${shellWord(contextTarget(conversationId))} --msg-index=${msgIndex}` } : {}
1061
- };
1062
- });
1063
- const reportsBlock = asRecord(response.investigation_reports);
1064
- const reports = (Array.isArray(reportsBlock?.items) ? reportsBlock.items : []).map((item) => asRecord(item)).filter((row) => row !== null).map((row) => ({
1065
- report_id: row.report_id ?? null,
1066
- conversation_id: row.conversation_id ?? null,
1067
- mechanism: row.mechanism ?? null,
1068
- suspected_surface: row.suspected_surface ?? null,
1069
- severity: row.severity ?? null,
1070
- problem_found: row.problem_found ?? null
1071
- }));
1072
- const verification = asRecord(response.verification);
1073
- const { samples: _samples, ...verificationCounts } = verification ?? {};
1074
- const dossier = asRecord(response.dossier);
1075
- const { representative_story_keys: _storyKeys, ...dossierSummary } = dossier ?? {};
1076
- const trend = Array.isArray(response.trend) ? response.trend : [];
1077
- const data = {
1078
- found: response.found ?? true,
1079
- problem_id: response.problem_id ?? problemId,
1080
- requested_problem_id: response.requested_problem_id ?? problemId,
1081
- alias_resolved: response.alias_resolved ?? false,
1082
- view: "compact",
1083
- header: response.header ?? null,
1084
- dossier: dossier ? dossierSummary : null,
1085
- verification: verification ? verificationCounts : null,
1086
- ...response.impact_identity !== undefined ? { impact_identity: response.impact_identity } : {},
1087
- ...response.hierarchy !== undefined ? { hierarchy: response.hierarchy } : {},
1088
- ...trend.length > 0 ? { trend_latest: trend[trend.length - 1], trend_points: trend.length } : {},
1089
- top_evidence: topEvidence,
1090
- evidence_in_dossier: evidence.length,
1091
- reports,
1092
- omitted: {
1093
- evidence_rows: Math.max(0, evidence.length - topEvidence.length),
1094
- report_narratives: reports.length,
1095
- reports_has_more: reportsBlock?.has_more === true,
1096
- blocks: PROBLEM_COMPACT_OMITTED_BLOCKS.filter((key) => (key in response))
1097
- },
1098
- full_command: `moda problems get ${id} --full`
1099
- };
1100
- const nextCommands = [
1101
- readCommand(`moda problems get ${id} --evidence --limit=20`, "Page every evidence row with its anchor (follow next_cursor)."),
1102
- readCommand(`moda problems get ${id} --reports`, "Page investigation reports with narratives and citations."),
1103
- readCommand(`moda problems get ${id} --traces`, "Page affected traces with their latest anchor."),
1104
- readCommand(`moda problems get ${id} --full`, "The complete dossier, every block (large).")
1105
- ];
1106
- const first = topEvidence.find((row) => typeof row.context_command === "string");
1107
- if (first?.context_command) {
1108
- nextCommands.push(readCommand(first.context_command, "Read the trace around the top evidence anchor."));
1109
- }
1110
- return { data, nextCommands };
1111
- }
1112
-
1113
703
  // ../../packages/harness-snapshot/src/policy.ts
1114
704
  var MAX_SCANNED_FILE_BYTES = 512 * 1024;
1115
705
  var DEFAULT_MAX_INVENTORY_FILES = 12000;
@@ -1450,6 +1040,15 @@ function packSnapshot(inputs, opts) {
1450
1040
  skips
1451
1041
  };
1452
1042
  }
1043
+ // src/shell-quote.ts
1044
+ function shellArg(value) {
1045
+ if (/^[A-Za-z0-9_.,:@%+/=-]+$/.test(value))
1046
+ return value;
1047
+ if (!/["$`\\!]/.test(value))
1048
+ return `"${value}"`;
1049
+ return `'${value.replace(/'/g, `'\\''`)}'`;
1050
+ }
1051
+
1453
1052
  // src/legacy-names.ts
1454
1053
  var LEGACY_NAMES = [
1455
1054
  { legacy: "conversations", use: "moda traces list", canonical: { command: "traces", subcommand: "list" }, silent: true },
@@ -1542,7 +1141,9 @@ function quotedArgumentSpans(text) {
1542
1141
  return spans;
1543
1142
  }
1544
1143
  function canonicalInvocation(entry, rest) {
1545
- let words = [...rest];
1144
+ const separator = rest.indexOf("--");
1145
+ const afterSeparator = separator === -1 ? [] : rest.slice(separator);
1146
+ let words = separator === -1 ? [...rest] : rest.slice(0, separator);
1546
1147
  const takeFlag = (pattern) => {
1547
1148
  const index = words.findIndex((word2) => pattern.test(word2));
1548
1149
  if (index === -1)
@@ -1563,7 +1164,7 @@ function canonicalInvocation(entry, rest) {
1563
1164
  takeFlag(/^--detections(=true)?$/);
1564
1165
  break;
1565
1166
  case "false-positive":
1566
- words = words.map((word) => /^--undo(=true)?$/.test(word) ? "--verdict=clear" : word);
1167
+ words = words.filter((word) => !/^--undo=(false|0)$/i.test(word)).map((word) => /^--undo(=true|=1)?$/i.test(word) ? "--verdict=clear" : word);
1567
1168
  break;
1568
1169
  case "signal-label":
1569
1170
  words = words.map((word) => word.replace(/^--label=fits$/, "--verdict=correct").replace(/^--label=does_not_fit$/, "--verdict=false_positive").replace(/^--label=.*$/, "--verdict=correct|false_positive"));
@@ -1585,7 +1186,7 @@ function canonicalInvocation(entry, rest) {
1585
1186
  out.push(token.startsWith("--family=") ? `${token}${familySuffix}` : token);
1586
1187
  }
1587
1188
  }
1588
- return [...out, ...positionals, ...words].join(" ");
1189
+ return [...out, ...positionals, ...words, ...afterSeparator].join(" ");
1589
1190
  }
1590
1191
  function canonicalCommandText(text) {
1591
1192
  if (!text.includes("moda "))
@@ -1616,8 +1217,8 @@ function canonicalCommandText(text) {
1616
1217
 
1617
1218
  // src/output.ts
1618
1219
  import { randomUUID } from "node:crypto";
1619
- import { lstatSync, mkdirSync as mkdirSync2, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
1620
- import { tmpdir } from "node:os";
1220
+ import { chmodSync as chmodSync2, lstatSync, mkdirSync as mkdirSync2, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
1221
+ import { tmpdir, userInfo } from "node:os";
1621
1222
  import { join as join2 } from "node:path";
1622
1223
  var SUGGESTION_KEY = /(_commands?|^hints?|^suggestions?)$/i;
1623
1224
  function canonicalDeep(value, rewriteStrings) {
@@ -1684,20 +1285,51 @@ var MODA_COMMAND = /^(?:moda |.*\| moda )/;
1684
1285
  function withProfileFlag(command, flag) {
1685
1286
  return MODA_COMMAND.test(command) && !/(?:^|\s)--profile(?:[=\s]|$)/.test(command) ? `${command} ${flag}` : command;
1686
1287
  }
1288
+ var GENERATED_COMMAND_KEY = /_commands?$/;
1289
+ var GENERATED_COMMAND_LIST_KEY = /(?:_commands|^recommended_actions|^recommendedActions|^setupPlan)$/;
1290
+ var CUSTOMER_CONTENT_KEYS = new Set([
1291
+ "messages",
1292
+ "context",
1293
+ "conversation",
1294
+ "conversation_context",
1295
+ "window",
1296
+ "tool_calls",
1297
+ "tool_results",
1298
+ "content",
1299
+ "content_blocks",
1300
+ "input",
1301
+ "output",
1302
+ "tool_input",
1303
+ "tool_output",
1304
+ "spans",
1305
+ "ingest",
1306
+ "raw_event",
1307
+ "attributes",
1308
+ "payload",
1309
+ "snippet_parsed",
1310
+ "evidence_snippet",
1311
+ "request",
1312
+ "response"
1313
+ ]);
1687
1314
  function withProfileOnCommands(value, profile) {
1688
- const flag = `--profile=${shellWord(profile)}`;
1689
- const visit = (node, key) => {
1690
- const isCommandKey = key !== undefined && /commands?$/.test(key);
1315
+ const flag = `--profile=${shellArg(profile)}`;
1316
+ const visit = (node, key, inCommandList) => {
1317
+ const isCommandKey = key !== undefined && (GENERATED_COMMAND_KEY.test(key) || inCommandList && key === "command");
1691
1318
  if (typeof node === "string")
1692
1319
  return isCommandKey ? withProfileFlag(node, flag) : node;
1693
- if (Array.isArray(node))
1694
- return node.map((item) => visit(item, isCommandKey ? key : undefined));
1695
- const record = asRecord2(node);
1320
+ if (Array.isArray(node)) {
1321
+ const list = key !== undefined && GENERATED_COMMAND_LIST_KEY.test(key);
1322
+ return node.map((item) => typeof item === "string" ? visit(item, isCommandKey ? key : undefined, false) : visit(item, undefined, list));
1323
+ }
1324
+ const record = asRecord(node);
1696
1325
  if (!record)
1697
1326
  return node;
1698
- return Object.fromEntries(Object.entries(record).map(([entryKey, entry]) => [entryKey, visit(entry, entryKey)]));
1327
+ return Object.fromEntries(Object.entries(record).map(([entryKey, entry]) => [
1328
+ entryKey,
1329
+ CUSTOMER_CONTENT_KEYS.has(entryKey) ? entry : visit(entry, entryKey, inCommandList)
1330
+ ]));
1699
1331
  };
1700
- return visit(value, undefined);
1332
+ return visit(value, undefined, false);
1701
1333
  }
1702
1334
  var FALLBACK_RUN_ID = randomUUID();
1703
1335
  var fallbackSeq = 0;
@@ -1879,12 +1511,16 @@ function createCliOutput(options) {
1879
1511
  if (options.quiet && !bypassQuiet)
1880
1512
  return;
1881
1513
  const profile = options.invocation?.flags.profile;
1514
+ const agentOutput = options.mode === "agent" || options.mode === "agent-stream";
1882
1515
  if (profile) {
1883
- data = withProfileOnCommands(data, profile);
1516
+ if (agentOutput)
1517
+ data = withProfileOnCommands(data, profile);
1884
1518
  if (writeOptions.nextCommands)
1885
- writeOptions = { ...writeOptions, nextCommands: withProfileOnCommands(writeOptions.nextCommands, profile) };
1519
+ writeOptions = { ...writeOptions, nextCommands: withProfileOnCommands({ next_commands: writeOptions.nextCommands }, profile).next_commands };
1520
+ if (writeOptions.recommendedActions)
1521
+ writeOptions = { ...writeOptions, recommendedActions: withProfileOnCommands({ recommended_actions: writeOptions.recommendedActions }, profile).recommended_actions };
1886
1522
  }
1887
- if (options.mode === "agent" || options.mode === "agent-stream") {
1523
+ if (agentOutput) {
1888
1524
  if (state.terminal)
1889
1525
  return;
1890
1526
  const envelope = writeEnvelope(data, writeOptions);
@@ -1905,7 +1541,8 @@ function createCliOutput(options) {
1905
1541
  }
1906
1542
  return;
1907
1543
  }
1908
- safeWriteStdout(`${JSON.stringify(canonicalSuggestionFields(redactDeep(data)), null, 2)}
1544
+ const indent = options.mode === "human" || (options.pretty ?? Boolean(process.stdout.isTTY)) ? 2 : undefined;
1545
+ safeWriteStdout(`${JSON.stringify(canonicalSuggestionFields(redactDeep(data)), null, indent)}
1909
1546
  `);
1910
1547
  for (const warning of writeOptions.warnings ?? []) {
1911
1548
  safeWriteStderr(`Warning: ${canonicalCommandText(warning)}
@@ -2368,6 +2005,16 @@ function errorToAgentError(error) {
2368
2005
  blocking: true
2369
2006
  };
2370
2007
  }
2008
+ if (apiErrorStatus(error) === 404 && !error.readable) {
2009
+ const suggestions = "suggestions" in error && Array.isArray(error.suggestions) ? error.suggestions.filter((s) => typeof s === "string") : [];
2010
+ return {
2011
+ code: "not_found",
2012
+ message: `Not found (HTTP 404): ${serverErrorMessage(error) ?? "the Moda API has no such resource"}.`,
2013
+ cause: "The id may be mistyped, from another tenant or profile, or deleted.",
2014
+ ...suggestions.length > 0 ? { suggested_commands: suggestions } : {},
2015
+ blocking: true
2016
+ };
2017
+ }
2371
2018
  if (isApiLikeError(error)) {
2372
2019
  const statusCode = "statusCode" in error ? error.statusCode : undefined;
2373
2020
  const typedCode = "code" in error && typeof error.code === "string" ? error.code : undefined;
@@ -2445,11 +2092,11 @@ function normalizeSummary(summary, command, status, data) {
2445
2092
  return inferSummary(command, status, data);
2446
2093
  }
2447
2094
  function inferSummary(command, status, data) {
2448
- const record = asRecord2(data);
2095
+ const record = asRecord(data);
2449
2096
  if (status === "error") {
2450
2097
  return { text: `${command} failed.`, confidence: "high" };
2451
2098
  }
2452
- const summary = asRecord2(record?.summary);
2099
+ const summary = asRecord(record?.summary);
2453
2100
  if (summary) {
2454
2101
  const statusValue = typeof summary.status === "string" ? summary.status : undefined;
2455
2102
  const fail = typeof summary.fail === "number" ? summary.fail : undefined;
@@ -2501,11 +2148,11 @@ function inferMutability(command) {
2501
2148
  return "read";
2502
2149
  }
2503
2150
  function inferWarnings(data) {
2504
- const record = asRecord2(data);
2151
+ const record = asRecord(data);
2505
2152
  const direct = record?.warnings;
2506
2153
  if (Array.isArray(direct))
2507
2154
  return direct.filter((item) => typeof item === "string");
2508
- const validation = asRecord2(record?.validation);
2155
+ const validation = asRecord(record?.validation);
2509
2156
  if (Array.isArray(validation?.warnings)) {
2510
2157
  return validation.warnings.filter((item) => typeof item === "string");
2511
2158
  }
@@ -2543,10 +2190,37 @@ function pruneAgentFullOutputs(dir, keep, now = Date.now()) {
2543
2190
  } catch {}
2544
2191
  }
2545
2192
  }
2193
+ function currentUserTag() {
2194
+ if (typeof process.getuid === "function")
2195
+ return String(process.getuid());
2196
+ try {
2197
+ return userInfo().username.replace(/[^A-Za-z0-9_.-]+/g, "_") || "user";
2198
+ } catch {
2199
+ return "user";
2200
+ }
2201
+ }
2202
+ function defaultAgentOutputDir() {
2203
+ return join2(tmpdir(), `moda-agent-output-${currentUserTag()}`);
2204
+ }
2205
+ function ensurePrivateDir(dir) {
2206
+ mkdirSync2(dir, { recursive: true, mode: 448 });
2207
+ const stat = lstatSync(dir);
2208
+ if (!stat.isDirectory())
2209
+ throw new Error(`${dir} is not a directory.`);
2210
+ if (typeof process.getuid === "function" && stat.uid !== process.getuid()) {
2211
+ throw new Error(`${dir} is owned by another user.`);
2212
+ }
2213
+ if (process.platform !== "win32" && (stat.mode & 63) !== 0)
2214
+ chmodSync2(dir, 448);
2215
+ }
2546
2216
  function writeAgentFullOutput(command, data) {
2547
2217
  try {
2548
- const dir = process.env.MODA_AGENT_OUTPUT_DIR || join2(tmpdir(), "moda-agent-output");
2549
- mkdirSync2(dir, { recursive: true, mode: 448 });
2218
+ const configured = process.env.MODA_AGENT_OUTPUT_DIR;
2219
+ const dir = configured || defaultAgentOutputDir();
2220
+ if (configured)
2221
+ mkdirSync2(dir, { recursive: true, mode: 448 });
2222
+ else
2223
+ ensurePrivateDir(dir);
2550
2224
  const slug = command.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "output";
2551
2225
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
2552
2226
  const path = join2(dir, `${slug}-${stamp}-${randomUUID().slice(0, 8)}.json`);
@@ -2567,7 +2241,7 @@ function contentBlocksRedundant(message) {
2567
2241
  const hasToolResults = Array.isArray(message.tool_results) && message.tool_results.length > 0;
2568
2242
  const texts = [];
2569
2243
  for (const item of blocks) {
2570
- const block = asRecord2(item);
2244
+ const block = asRecord(item);
2571
2245
  if (!block)
2572
2246
  return false;
2573
2247
  if (block.type === "text") {
@@ -2599,7 +2273,7 @@ function shedRedundantContentBlocks(value) {
2599
2273
  visit(item);
2600
2274
  return;
2601
2275
  }
2602
- const record = asRecord2(node);
2276
+ const record = asRecord(node);
2603
2277
  if (!record)
2604
2278
  return;
2605
2279
  if ("content_blocks" in record && contentBlocksRedundant(record)) {
@@ -2618,22 +2292,29 @@ function continuationCommand(invocation, overrides) {
2618
2292
  const normalize2 = (key) => key.replace(/_/g, "-");
2619
2293
  const overridden = new Set(Object.keys(overrides).map(normalize2));
2620
2294
  const bare = new Set((invocation.bareFlags ?? []).map(normalize2));
2621
- const parts = ["moda", invocation.command, ...invocation.positionals.map((value) => shellWord(value))];
2295
+ const dashAt = invocation.positionals.findIndex((value) => value.startsWith("-"));
2296
+ const leading = dashAt === -1 ? invocation.positionals : invocation.positionals.slice(0, dashAt);
2297
+ const trailing = dashAt === -1 ? [] : invocation.positionals.slice(dashAt);
2298
+ const parts = ["moda", invocation.command, ...leading.map((value) => shellArg(value))];
2622
2299
  for (const [key, value] of Object.entries(invocation.flags)) {
2623
2300
  const flag = normalize2(key);
2624
2301
  if (overridden.has(flag) || CONTINUATION_SECRET_FLAGS.has(flag))
2625
2302
  continue;
2626
- parts.push(bare.has(flag) && value === "true" ? `--${flag}` : `--${flag}=${shellWord(String(value))}`);
2303
+ parts.push(bare.has(flag) && value === "true" ? `--${flag}` : `--${flag}=${shellArg(String(value))}`);
2627
2304
  }
2628
2305
  for (const [key, value] of Object.entries(overrides))
2629
- parts.push(`--${normalize2(key)}=${shellWord(String(value))}`);
2306
+ parts.push(`--${normalize2(key)}=${shellArg(String(value))}`);
2307
+ if (trailing.length > 0)
2308
+ parts.push("--", ...trailing.map((value) => shellArg(value)));
2630
2309
  return canonicalCommandText(parts.join(" "));
2631
2310
  }
2632
2311
  var PAGED_LIST_BY_COMMAND = {
2633
2312
  "tool-failure-detail": "examples",
2634
2313
  emotions: "detections",
2635
2314
  frustrations: "frustrations",
2636
- "problems remainder": "groups"
2315
+ laziness: "conversations",
2316
+ "problems remainder": "groups",
2317
+ "problem remainder": "groups"
2637
2318
  };
2638
2319
  function pagedListFor(invocation) {
2639
2320
  const { command, positionals } = invocation.routedAs ?? invocation;
@@ -2645,7 +2326,7 @@ function rewritePagination(list, isTopLevel, invocation) {
2645
2326
  const { parent, array, originalLength } = list;
2646
2327
  if (!parent)
2647
2328
  return;
2648
- const nested = asRecord2(parent.pagination);
2329
+ const nested = asRecord(parent.pagination);
2649
2330
  const inline = !nested && !isTopLevel && (("has_more" in parent) || ("next_cursor" in parent)) ? parent : undefined;
2650
2331
  const pagination = nested ?? inline;
2651
2332
  if (!pagination)
@@ -2663,7 +2344,7 @@ function rewritePagination(list, isTopLevel, invocation) {
2663
2344
  const kept = array.length;
2664
2345
  const omitted = originalLength - kept;
2665
2346
  const offset = typeof pagination.offset === "number" && Number.isFinite(pagination.offset) ? pagination.offset : undefined;
2666
- const hadCursor = typeof pagination.next_cursor === "string" && pagination.next_cursor.length > 0;
2347
+ const hadCursor = typeof pagination.next_cursor === "string" && pagination.next_cursor.length > 0 || typeof pagination.nextCursor === "string" && pagination.nextCursor.length > 0;
2667
2348
  if (typeof pagination.limit === "number") {
2668
2349
  pagination.requested_limit = pagination.limit;
2669
2350
  pagination.limit = kept;
@@ -2671,6 +2352,10 @@ function rewritePagination(list, isTopLevel, invocation) {
2671
2352
  pagination.returned = kept;
2672
2353
  pagination.omitted_for_byte_limit = omitted;
2673
2354
  pagination.has_more = true;
2355
+ if ("hasMore" in pagination)
2356
+ pagination.hasMore = true;
2357
+ if ("nextCursor" in pagination && typeof pagination.nextCursor === "string" && pagination.nextCursor)
2358
+ pagination.nextCursor = null;
2674
2359
  const pastOffsetCap = offset !== undefined && offset + kept > MAX_PAGE_OFFSET;
2675
2360
  if (offset !== undefined && !pastOffsetCap)
2676
2361
  pagination.next_offset = offset + kept;
@@ -2781,7 +2466,7 @@ function boundForAgent(data, maxBytes = agentMaxOutputBytes(), options = {}) {
2781
2466
  fullOutputPath
2782
2467
  };
2783
2468
  }
2784
- const root = asRecord2(bounded);
2469
+ const root = asRecord(bounded);
2785
2470
  const notes = [];
2786
2471
  const nextCommands = [];
2787
2472
  for (const list of droppedLists.values()) {
@@ -2819,7 +2504,7 @@ function boundForAgent(data, maxBytes = agentMaxOutputBytes(), options = {}) {
2819
2504
  ...nextCommands.length > 0 ? { nextCommands } : {}
2820
2505
  };
2821
2506
  }
2822
- function asRecord2(value) {
2507
+ function asRecord(value) {
2823
2508
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
2824
2509
  }
2825
2510
  function extractSuggestedCommands(text) {
@@ -2852,4 +2537,4 @@ function apiKeyHeaders(key, options = {}, url) {
2852
2537
  return headers;
2853
2538
  }
2854
2539
 
2855
- export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, describeBaseUrlSource, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, attachInputDetails, inputDetailsOf, CliAccessError, CliInputRequiredError, CliAuthError, CliNotFoundError, CliCancellationError, isNetworkLikeError, isApiLikeError, apiErrorStatus, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveApiTenantId, resolveModaBaseUrl, validateConfig, isPlatformApiKey, setPlatformTenantOverride, apiKeyHeaders, recordApiRequest, getApiRequestCount, resetApiRequestCount, BATCH_MAX_TARGETS, BATCH_CONCURRENCY, parseBatchTarget, collectBatchTargets, mapWithConcurrency, contextTarget, asRecord, asString, asNumber, frustrationAnchorIndex, frustrationsFromEmotions, withFrustrationAnchors, shellWord, trimToolFailureConversations, withToolFailureAnchors, embeddedWindowSize, withoutDetectionWindows, compactProblemDossier, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, LEGACY_NAMES, canonicalCommandText, setAgentUpdateNotice, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, explicitOutputMode, outputModeFromFlags, resolveOutputMode, createCliOutput, agentEnvelopeFits, errorToAgentError, agentMaxOutputBytes };
2540
+ export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, describeBaseUrlSource, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, attachInputDetails, inputDetailsOf, CliAccessError, CliInputRequiredError, CliAuthError, CliNotFoundError, CliCancellationError, isNetworkLikeError, isApiLikeError, apiErrorStatus, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveApiTenantId, resolveModaBaseUrl, resolveModaBaseUrlStrict, validateConfig, isPlatformApiKey, setPlatformTenantOverride, apiKeyHeaders, recordApiRequest, getApiRequestCount, resetApiRequestCount, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, shellArg, LEGACY_NAMES, canonicalCommandText, setAgentUpdateNotice, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, explicitOutputMode, outputModeFromFlags, resolveOutputMode, createCliOutput, agentEnvelopeFits, errorToAgentError, agentMaxOutputBytes, continuationCommand };