@moda-ai/cli 1.55.18 → 1.55.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-cha3kg9d.js → agent-mj622k8s.js} +9 -9
- package/dist/{cli-1exddmc0.js → cli-033n2mpb.js} +2 -2
- package/dist/{cli-5hq827jt.js → cli-569hj0bj.js} +1 -1
- package/dist/{cli-3ezzeqcy.js → cli-5ke3xe6y.js} +47 -3
- package/dist/{cli-av3dqgj5.js → cli-5km7j04z.js} +4 -4
- package/dist/{cli-eazeanvt.js → cli-7ea02wca.js} +1 -1
- package/dist/{cli-qmjnh4z9.js → cli-9x9adrtj.js} +1 -1
- package/dist/{cli-a1mv0ypm.js → cli-a5c9te95.js} +5 -5
- package/dist/cli-cehskjde.js +1302 -0
- package/dist/{cli-5q1gyvxn.js → cli-pc5cw7mx.js} +5 -5
- package/dist/{cli-c8p69n55.js → cli-rqk6f6n6.js} +2 -2
- package/dist/{cli-0e8bfvn7.js → cli-wf7gfa1e.js} +8 -8
- package/dist/cli.js +329 -928
- package/dist/{doctor-kg0yq1wf.js → doctor-mx6jf435.js} +9 -9
- package/dist/{fix-q8er8egx.js → fix-9fmy6p2x.js} +7 -7
- package/dist/{harness-github-actions-ver6cprq.js → harness-github-actions-c826bfxs.js} +6 -6
- package/dist/{harness-v9s0k9s7.js → harness-xvg6ws6x.js} +6 -6
- package/dist/{index-7zqa7t5c.js → index-6qqmjqb2.js} +11 -11
- package/dist/{production-intelligence-k36jsvqx.js → production-intelligence-dbe9dmc5.js} +3 -3
- package/dist/{profile-commands-88sdwzbg.js → profile-commands-kmmd9y86.js} +3 -3
- package/dist/prompt-source-mcp.js +6 -6
- package/dist/{prompts-2r756vtd.js → prompts-1tkhrxkq.js} +7 -7
- package/dist/{provision-n3r7yway.js → provision-wzttap58.js} +4 -4
- package/dist/{registry-5v63ga01.js → registry-atqyb5fa.js} +4 -4
- package/dist/{skills-fed17et3.js → skills-d35nnm66.js} +6 -6
- package/dist/{sync-r4tncne4.js → sync-n2psdffz.js} +8 -8
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/dist/cli-kg0kz05e.js +0 -496
|
@@ -0,0 +1,1302 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApiError,
|
|
3
|
+
callControlAPI,
|
|
4
|
+
callDataAPI
|
|
5
|
+
} from "./cli-rqk6f6n6.js";
|
|
6
|
+
import {
|
|
7
|
+
isPlatformApiKey,
|
|
8
|
+
setPlatformTenantOverride
|
|
9
|
+
} from "./cli-7ea02wca.js";
|
|
10
|
+
import {
|
|
11
|
+
CliAccessError,
|
|
12
|
+
CliInputError,
|
|
13
|
+
embeddedTenantIdFromApiKey,
|
|
14
|
+
resolveApiKey,
|
|
15
|
+
resolveApiTenantId
|
|
16
|
+
} from "./cli-5ke3xe6y.js";
|
|
17
|
+
|
|
18
|
+
// src/schemas.ts
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
var boolFlag = () => z.preprocess((val) => {
|
|
21
|
+
const text = String(val).toLowerCase();
|
|
22
|
+
if (["true", "1", "yes", "on"].includes(text))
|
|
23
|
+
return true;
|
|
24
|
+
if (["false", "0", "no", "off"].includes(text))
|
|
25
|
+
return false;
|
|
26
|
+
return val;
|
|
27
|
+
}, z.boolean()).optional();
|
|
28
|
+
var DAYS_BACK_MESSAGE = "must be a whole number from 1 to 90 (e.g. --days-back=7)";
|
|
29
|
+
var daysBackFlag = (fallback) => {
|
|
30
|
+
const days = z.number({ invalid_type_error: DAYS_BACK_MESSAGE }).int(DAYS_BACK_MESSAGE).min(1, DAYS_BACK_MESSAGE).max(90, DAYS_BACK_MESSAGE);
|
|
31
|
+
const flag = fallback === undefined ? days.optional() : days.default(fallback).optional();
|
|
32
|
+
return flag.describe("Whole days to look back");
|
|
33
|
+
};
|
|
34
|
+
var ISO_BOUND_MESSAGE = "must be an ISO 8601 date (2026-10-07) or datetime (2026-10-07T14:30:00Z)";
|
|
35
|
+
var ISO_BOUND = /^\d{4}-\d{2}-\d{2}(?:[Tt ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:[Zz]|[+-]\d{2}:?\d{2})?)?$/;
|
|
36
|
+
var isoBoundFlag = () => z.string().trim().regex(ISO_BOUND, ISO_BOUND_MESSAGE).optional();
|
|
37
|
+
var OverviewSchema = z.object({
|
|
38
|
+
days_back: daysBackFlag(7)
|
|
39
|
+
});
|
|
40
|
+
var ClustersSchema = z.object({
|
|
41
|
+
parent_id: z.string().optional().describe("List the children of this cluster node"),
|
|
42
|
+
time_range: z.enum(["all", "1h", "3d", "7d", "24h", "30d", "90d"]).default("all").optional().describe("Time window"),
|
|
43
|
+
search: z.string().min(2).max(200).optional().describe("Find clusters by name or meaning"),
|
|
44
|
+
node_id: z.string().min(1).max(512).optional().describe("Resolve one cluster node by id (deep link)"),
|
|
45
|
+
mode: z.enum(["fuzzy", "semantic", "hybrid"]).optional().describe("Cluster search mode (server default hybrid)"),
|
|
46
|
+
limit: z.number().min(1).max(50).optional().describe("Maximum clusters to return (with --search/--node-id)")
|
|
47
|
+
});
|
|
48
|
+
var ClusterConversationsSchema = z.object({
|
|
49
|
+
node_id: z.string().describe("Cluster node id (from moda clusters)"),
|
|
50
|
+
limit: z.number().min(1).max(100).default(10).optional().describe("Maximum traces to return"),
|
|
51
|
+
offset: z.number().min(0).default(0).optional().describe("Number of traces to skip")
|
|
52
|
+
});
|
|
53
|
+
var ConversationsSchema = z.object({
|
|
54
|
+
search: z.string().optional().describe("Ranked trace search (hybrid when embeddings exist, else keyword)"),
|
|
55
|
+
cluster_id: z.string().optional().describe("Only traces in this cluster node"),
|
|
56
|
+
user_id: z.string().optional().describe("Only traces from this end user"),
|
|
57
|
+
time_range: z.enum(["all", "1h", "3d", "24h", "7d", "30d", "90d"]).default("all").optional().describe("Time window"),
|
|
58
|
+
since: isoBoundFlag().describe("Absolute UTC start, inclusive (ISO date or datetime); intersected with --time-range"),
|
|
59
|
+
until: isoBoundFlag().describe("Absolute UTC end, inclusive (ISO date or datetime; a date covers that whole day)"),
|
|
60
|
+
environment: z.enum(["all", "development", "staging", "production"]).default("all").optional().describe("Only traces from this environment"),
|
|
61
|
+
world_state: z.string().max(200).optional().describe("Match world-state content (slots + durable profile); comma = AND"),
|
|
62
|
+
outcome: z.enum(["any", "positive", "negative"]).default("any").optional().describe("Filter by trace outcome (trajectory + frustration)"),
|
|
63
|
+
include_world_state: boolFlag().describe("Attach each result's world-state summary"),
|
|
64
|
+
sort: z.enum(["recent", "longest", "slowest"]).optional().describe("recent = newest activity, longest = most messages, slowest = longest span"),
|
|
65
|
+
limit: z.number().min(1).max(100).default(20).optional().describe("Maximum traces to return"),
|
|
66
|
+
offset: z.number().min(0).default(0).optional().describe("Number of traces to skip")
|
|
67
|
+
});
|
|
68
|
+
var WorldStateSchema = z.object({
|
|
69
|
+
conversation_id: z.string().describe("Trace ID"),
|
|
70
|
+
summary_only: boolFlag().describe("Segment summaries only (omit the event stream)"),
|
|
71
|
+
event_limit: z.number().min(1).max(5000).default(2000).optional().describe("Maximum world-state events to return"),
|
|
72
|
+
snapshot: boolFlag().describe("Folded slots at one tick (what the agent believed at that turn)"),
|
|
73
|
+
msg_index: z.number().min(0).max(1e5).optional().describe("Snapshot tick: message index (default 0)"),
|
|
74
|
+
block_index: z.number().min(0).max(1e5).optional().describe("Snapshot tick: content block index (default 0)"),
|
|
75
|
+
segment_index: z.number().min(0).max(65535).optional().describe("Scope the snapshot to one segment"),
|
|
76
|
+
replay: boolFlag().describe("State-over-time replay (frames mode needs --message-count)"),
|
|
77
|
+
format: z.enum(["frames", "fold-inputs"]).optional().describe("Replay format (default frames)"),
|
|
78
|
+
message_count: z.number().min(1).max(1e6).optional().describe("Messages to fold in frames mode")
|
|
79
|
+
});
|
|
80
|
+
var ContextSchema = z.object({
|
|
81
|
+
conversation_id: z.string().min(1).describe("Trace ID"),
|
|
82
|
+
msg_index: z.number().int().min(0).optional().describe("Anchor message index (default: middle of the trace)"),
|
|
83
|
+
window: z.number().int().min(0).optional().describe("Messages on each side of the anchor (above 5 is clamped)"),
|
|
84
|
+
metadata_only: boolFlag().describe("Summary and total_messages only, no messages"),
|
|
85
|
+
all: boolFlag().describe("Whole transcript in order (first 500 messages by default)"),
|
|
86
|
+
from: z.number().int().min(0).optional().describe("With --all: first message index to read"),
|
|
87
|
+
max_messages: z.number().int().min(1).max(5000).optional().describe("With --all: maximum messages to read (default 500)"),
|
|
88
|
+
ids_from: z.string().min(1).optional().describe("Read several targets (<id> or <id>@<msg_index>), one per line, from a file or - for stdin (batch mode, up to 25)")
|
|
89
|
+
});
|
|
90
|
+
var FrustrationsSchema = z.object({
|
|
91
|
+
days_back: daysBackFlag(7),
|
|
92
|
+
limit: z.number().min(1).max(20).default(10).optional().describe("Maximum detections to return"),
|
|
93
|
+
offset: z.number().min(0).default(0).optional().describe("Number of detections to skip"),
|
|
94
|
+
include_window: boolFlag().describe("Attach the surrounding messages to each detection (agent output omits them by default)"),
|
|
95
|
+
window: z.number().min(1).max(5).default(1).optional().describe("Messages on each side with --include-window"),
|
|
96
|
+
user_id: z.string().min(1).max(256).optional().describe("Scope everything to one end user"),
|
|
97
|
+
conversation_id: z.string().min(1).max(256).optional().describe("Scope everything to one trace ID")
|
|
98
|
+
});
|
|
99
|
+
var ToolFailuresSchema = z.object({
|
|
100
|
+
days_back: daysBackFlag(7)
|
|
101
|
+
});
|
|
102
|
+
var ProblemsSchema = z.object({
|
|
103
|
+
days_back: daysBackFlag(30),
|
|
104
|
+
limit: z.number().min(1).max(25).default(25).optional().describe("Maximum Problems to return")
|
|
105
|
+
});
|
|
106
|
+
var REMAINDER_FAMILIES = [
|
|
107
|
+
"tool_failure",
|
|
108
|
+
"emotion",
|
|
109
|
+
"laziness",
|
|
110
|
+
"hallucination",
|
|
111
|
+
"prm_dip",
|
|
112
|
+
"custom_signal",
|
|
113
|
+
"conversation_reader"
|
|
114
|
+
];
|
|
115
|
+
var ProblemsRemainderSchema = z.object({
|
|
116
|
+
days_back: daysBackFlag(30),
|
|
117
|
+
limit: z.number().min(1).max(25).default(25).optional().describe("Maximum groups to return"),
|
|
118
|
+
family: z.enum(REMAINDER_FAMILIES).optional().describe("Only this signal family (frustration is under emotion)"),
|
|
119
|
+
cursor: z.string().min(1).max(4096).optional().describe("next_cursor from the previous page, verbatim")
|
|
120
|
+
});
|
|
121
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
122
|
+
var ProblemImpactSchema = z.object({
|
|
123
|
+
node_id: z.string().min(1).max(256).optional().describe("Use case (cluster node) id"),
|
|
124
|
+
problem_id: z.string().regex(UUID_RE).optional().describe("Problem id (UUID)"),
|
|
125
|
+
run_id: z.string().min(1).max(256).optional().describe("Analysis run id (for an evidence cursor)"),
|
|
126
|
+
days_back: z.union([z.literal("all"), z.coerce.number().refine((n) => [1, 3, 7, 30, 90].includes(n), "Use all, 1, 3, 7, 30, or 90 days")]).default(7).describe("Window: all, 1, 3, 7, 30 or 90 days"),
|
|
127
|
+
mode: z.enum(["metrics", "trend", "evidence", "catalog", "overview", "affected-use-cases", "trace-use-cases"]).default("metrics").describe("Which view to return"),
|
|
128
|
+
to: z.string().datetime({ precision: 3 }).optional().describe("Window end (ISO timestamp with ms; for an evidence cursor)"),
|
|
129
|
+
cursor: z.string().min(1).optional().describe("Evidence page cursor from the previous response"),
|
|
130
|
+
conversation_ids: z.string().transform((value, ctx) => {
|
|
131
|
+
try {
|
|
132
|
+
return z.array(z.string().min(1).max(512)).min(1).max(25).parse(JSON.parse(value));
|
|
133
|
+
} catch {
|
|
134
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Use a JSON array of 1–25 conversation IDs" });
|
|
135
|
+
return z.NEVER;
|
|
136
|
+
}
|
|
137
|
+
}).optional().describe("JSON array of 1-25 trace IDs (trace-use-cases mode)")
|
|
138
|
+
}).refine((p) => p.mode !== "trace-use-cases" || p.problem_id && p.conversation_ids, {
|
|
139
|
+
message: "Trace use cases require --problem-id and --conversation-ids"
|
|
140
|
+
}).refine((p) => p.node_id || p.problem_id || ["catalog", "overview"].includes(p.mode), {
|
|
141
|
+
message: "Select --node-id or --problem-id, or use --mode=catalog/overview"
|
|
142
|
+
}).refine((p) => !p.cursor || p.mode === "evidence" && p.to && p.run_id, {
|
|
143
|
+
message: "An evidence cursor requires --mode=evidence, --to, and --run-id from the previous response"
|
|
144
|
+
});
|
|
145
|
+
var PROBLEM_FAMILIES = REMAINDER_FAMILIES;
|
|
146
|
+
var PROBLEM_DOORS = ["rubric_match", "local_causal_chain"];
|
|
147
|
+
var ProblemSchema = z.object({
|
|
148
|
+
id: z.string().min(1).describe("Problem id"),
|
|
149
|
+
evidence: boolFlag().describe("Evidence page"),
|
|
150
|
+
reports: boolFlag().describe("Reports page"),
|
|
151
|
+
traces: boolFlag().describe("Affected traces page"),
|
|
152
|
+
conversations: boolFlag().describe("Legacy alias of --traces"),
|
|
153
|
+
feedback: boolFlag().describe("Feedback history page"),
|
|
154
|
+
full: boolFlag().describe("Complete dossier in agent output (default is a compact dossier)"),
|
|
155
|
+
limit: z.number().min(1).max(50).optional().describe("Page size for a sub-resource page"),
|
|
156
|
+
cursor: z.string().optional().describe("next_cursor from the previous page, verbatim"),
|
|
157
|
+
family: z.enum(PROBLEM_FAMILIES).optional().describe("With --traces: only this signal family"),
|
|
158
|
+
door: z.enum(PROBLEM_DOORS).optional().describe("With --traces: only this attribution door")
|
|
159
|
+
});
|
|
160
|
+
var PROBLEM_FEEDBACK_ACTIONS = [
|
|
161
|
+
"mark_fixed",
|
|
162
|
+
"dismiss",
|
|
163
|
+
"flag_attribution",
|
|
164
|
+
"rename"
|
|
165
|
+
];
|
|
166
|
+
var ProblemFeedbackSchema = z.object({
|
|
167
|
+
id: z.string().min(1).describe("Problem id (UUID)"),
|
|
168
|
+
action: z.enum(PROBLEM_FEEDBACK_ACTIONS).describe("What to do with the Problem"),
|
|
169
|
+
reason: z.string().max(2000).optional().describe("Why (required for flag_attribution; optional for dismiss)"),
|
|
170
|
+
attribution_id: z.string().max(64).optional().describe("Attribution UUID (required for flag_attribution; from problem --evidence)"),
|
|
171
|
+
new_name: z.string().max(255).optional().describe("New name (required for rename)"),
|
|
172
|
+
actor: z.string().max(255).optional().describe("Who is acting (recorded with the feedback)")
|
|
173
|
+
});
|
|
174
|
+
var ProblemLinearFileSchema = z.object({
|
|
175
|
+
id: z.string().min(1).describe("Problem id (UUID)"),
|
|
176
|
+
issue: z.string().max(2048).optional().describe("Link this existing Linear issue (ENG-123 or URL) instead of filing"),
|
|
177
|
+
team_id: z.string().max(255).optional().describe("Linear team to file into (default: the integration's default team)"),
|
|
178
|
+
title: z.string().optional().describe("Issue title (default: the Problem's name)"),
|
|
179
|
+
priority: z.preprocess((v) => typeof v === "string" && /^\d+$/.test(v.trim()) ? Number(v) : v, z.number().int().min(0).max(4).optional()).describe("0 none, 1 urgent, 2 high, 3 medium, 4 low")
|
|
180
|
+
});
|
|
181
|
+
var SignalSchema = z.object({
|
|
182
|
+
id: z.string().min(1).max(255).describe("Custom signal id"),
|
|
183
|
+
detections: boolFlag().describe("List recent hits instead of the summary"),
|
|
184
|
+
days_back: daysBackFlag(),
|
|
185
|
+
limit: z.number().min(1).max(20).optional().describe("Hits page size (server default 10)"),
|
|
186
|
+
offset: z.number().min(0).max(1e4).optional().describe("Number of hits to skip")
|
|
187
|
+
});
|
|
188
|
+
var SIGNAL_LABELS = ["fits", "does_not_fit"];
|
|
189
|
+
var SignalLabelSchema = z.object({
|
|
190
|
+
id: z.string().min(1).max(255).describe("Custom signal id"),
|
|
191
|
+
conversation_id: z.string().min(1).max(255).describe("Trace ID to label"),
|
|
192
|
+
label: z.enum(SIGNAL_LABELS).describe("does_not_fit marks a false positive and retracts the hit"),
|
|
193
|
+
note: z.string().max(2000).optional().describe("Why (recommended for does_not_fit)"),
|
|
194
|
+
msg_index: z.preprocess((v) => typeof v === "string" && /^\d+$/.test(v.trim()) ? Number(v) : v, z.number().int().min(0).optional()).describe("Anchor message index (the hit's anchor_msg_index)"),
|
|
195
|
+
msg_dedup_token: z.string().min(1).max(200).optional().describe("Anchor message token (the hit's anchor_dedup_token)")
|
|
196
|
+
});
|
|
197
|
+
var DETECTION_VERDICTS = ["correct", "false_positive", "clear"];
|
|
198
|
+
var DetectionReviewSchema = z.object({
|
|
199
|
+
id: z.string().regex(UUID_RE, "Problem id must be a canonical UUID").describe("Problem id (UUID)"),
|
|
200
|
+
conversation_id: z.string().min(1).max(255).describe("Trace ID the detection is on"),
|
|
201
|
+
attribution_id: z.string().regex(UUID_RE, "Attribution id must be a UUID").describe("Detection id (from problem --evidence or detection-reviews)"),
|
|
202
|
+
verdict: z.enum(DETECTION_VERDICTS).describe("clear undoes an earlier review"),
|
|
203
|
+
note: z.string().max(2000).optional().describe("Why (kept with the review)")
|
|
204
|
+
});
|
|
205
|
+
var FALSE_POSITIVE_KINDS = ["problem", "problem-detection", "quote", "tool", "laziness", "hallucination", "custom-signal"];
|
|
206
|
+
var FalsePositiveSchema = z.object({
|
|
207
|
+
kind: z.enum(FALSE_POSITIVE_KINDS).describe("What kind of detection is wrong"),
|
|
208
|
+
target: z.string().min(1).max(255).describe("Problem id (problem, problem-detection), signal id (custom-signal), else the trace ID"),
|
|
209
|
+
undo: z.boolean().default(false).describe("Remove an earlier mark (every kind except problem)"),
|
|
210
|
+
reason: z.string().max(2000).optional().describe("Why the detection is wrong"),
|
|
211
|
+
conversation_id: z.string().min(1).max(255).optional().describe("Trace ID (required for problem-detection and custom-signal)"),
|
|
212
|
+
attribution_id: z.string().regex(UUID_RE, "Attribution id must be a UUID").optional().describe("Detection UUID (required for problem-detection)"),
|
|
213
|
+
family: z.string().regex(/^[a-z_]{1,64}$/, "Family must be a lowercase emotion family").optional().describe("Emotion family of the quote (quote kind)"),
|
|
214
|
+
turn: z.preprocess((v) => typeof v === "string" && /^\d+$/.test(v.trim()) ? Number(v) : v, z.number().int().min(0).optional()).describe("Turn of the quote (quote kind; from moda emotions)"),
|
|
215
|
+
quote: z.string().min(1).max(4000).optional().describe("The quoted user text (quote kind)"),
|
|
216
|
+
tool: z.string().min(1).max(255).optional().describe("Failing tool name (tool kind)"),
|
|
217
|
+
tool_use_id: z.string().min(1).max(255).optional().describe("Tool call id (tool kind; or pass --msg-index)"),
|
|
218
|
+
msg_index: z.preprocess((v) => typeof v === "string" && /^\d+$/.test(v.trim()) ? Number(v) : v, z.number().int().min(0).optional()).describe("Message index of the tool call (tool kind)"),
|
|
219
|
+
msg_dedup_token: z.string().min(1).max(200).optional().describe("Dedup token of the anchor message")
|
|
220
|
+
}).superRefine((v, ctx) => {
|
|
221
|
+
const need = (ok, message) => {
|
|
222
|
+
if (!ok)
|
|
223
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message });
|
|
224
|
+
};
|
|
225
|
+
if (v.kind === "problem") {
|
|
226
|
+
need(UUID_RE.test(v.target), "Problem id must be a canonical UUID");
|
|
227
|
+
need(!v.undo, "A problem dismissal cannot be undone from the CLI");
|
|
228
|
+
}
|
|
229
|
+
if (v.kind === "problem-detection") {
|
|
230
|
+
need(UUID_RE.test(v.target), "Problem id must be a canonical UUID");
|
|
231
|
+
need(v.conversation_id, "--conversation-id is required for problem-detection");
|
|
232
|
+
need(v.attribution_id, "--attribution-id is required for problem-detection");
|
|
233
|
+
}
|
|
234
|
+
if (v.kind === "quote") {
|
|
235
|
+
need(v.family && v.turn !== undefined && v.quote, "--family, --turn and --quote are required for quote (copy them from moda emotions)");
|
|
236
|
+
}
|
|
237
|
+
if (v.kind === "tool") {
|
|
238
|
+
need(v.tool, "--tool is required for tool (the failing tool name)");
|
|
239
|
+
need(v.tool_use_id || v.msg_index !== undefined, "--tool-use-id or --msg-index is required for tool (from moda tool-failure-detail)");
|
|
240
|
+
}
|
|
241
|
+
if (v.kind === "custom-signal") {
|
|
242
|
+
need(v.conversation_id, "--conversation-id is required for custom-signal");
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
var EMOTION_FAMILIES = [
|
|
246
|
+
"frustration",
|
|
247
|
+
"disapproval",
|
|
248
|
+
"sadness",
|
|
249
|
+
"confusion",
|
|
250
|
+
"anxiety",
|
|
251
|
+
"trust",
|
|
252
|
+
"positive"
|
|
253
|
+
];
|
|
254
|
+
var EmotionsSchema = z.object({
|
|
255
|
+
days_back: daysBackFlag(7),
|
|
256
|
+
limit: z.number().min(1).max(20).default(10).optional().describe("Maximum detections to return"),
|
|
257
|
+
offset: z.number().min(0).max(1e4).optional().describe("Number of detections to skip"),
|
|
258
|
+
family: z.enum(EMOTION_FAMILIES).optional().describe("Only this family in the detections list (summary covers all)"),
|
|
259
|
+
user_id: z.string().min(1).max(256).optional().describe("Scope everything to one end user"),
|
|
260
|
+
conversation_id: z.string().min(1).max(256).optional().describe("Scope everything to one trace ID"),
|
|
261
|
+
include_window: boolFlag().describe("Keep each detection's conversation context (agent output omits it by default)")
|
|
262
|
+
});
|
|
263
|
+
var HallucinationsSchema = z.object({
|
|
264
|
+
days_back: daysBackFlag(7),
|
|
265
|
+
limit: z.number().min(1).max(20).default(10).optional().describe("Maximum detections to return"),
|
|
266
|
+
offset: z.number().min(0).max(1e4).optional().describe("Number of detections to skip"),
|
|
267
|
+
conversation_id: z.string().max(200).optional().describe("Scope summary and list to one trace ID"),
|
|
268
|
+
kind: z.enum(["contradicted", "verified"]).optional().describe("Only this kind in the detections list")
|
|
269
|
+
});
|
|
270
|
+
var LAZINESS_FAMILIES = [
|
|
271
|
+
"omission_comment",
|
|
272
|
+
"omission_phrase",
|
|
273
|
+
"stub_body",
|
|
274
|
+
"refusal_prefix",
|
|
275
|
+
"deflection",
|
|
276
|
+
"data_quality_truncation",
|
|
277
|
+
"promise_no_delivery",
|
|
278
|
+
"capability_refusal_here",
|
|
279
|
+
"capability_refusal_verb",
|
|
280
|
+
"error_admission",
|
|
281
|
+
"irreversibility",
|
|
282
|
+
"stall_recheck",
|
|
283
|
+
"diy_handoff",
|
|
284
|
+
"tool_selection",
|
|
285
|
+
"completion",
|
|
286
|
+
"code_artifact",
|
|
287
|
+
"repeated_tool_call",
|
|
288
|
+
"target_overshoot",
|
|
289
|
+
"data_regression"
|
|
290
|
+
];
|
|
291
|
+
var LazinessSchema = z.object({
|
|
292
|
+
days_back: daysBackFlag(7),
|
|
293
|
+
limit: z.number().min(1).max(50).default(10).optional().describe("Maximum detections to return"),
|
|
294
|
+
offset: z.number().min(0).max(1e4).optional().describe("Number of detections to skip"),
|
|
295
|
+
conversation_id: z.string().max(200).optional().describe("One trace's detections"),
|
|
296
|
+
family: z.enum(LAZINESS_FAMILIES).optional().describe("Only this laziness family")
|
|
297
|
+
});
|
|
298
|
+
var StepScoresSchema = z.object({
|
|
299
|
+
conversation_id: z.string().min(1).describe("Trace ID")
|
|
300
|
+
});
|
|
301
|
+
var TAIL_SIGNALS = ["traces", "conversations", "emotions", "all"];
|
|
302
|
+
var TailSchema = z.object({
|
|
303
|
+
interval: z.number().min(5).max(3600).default(15).optional().describe("Seconds between polls"),
|
|
304
|
+
signal: z.enum(TAIL_SIGNALS).default("traces").optional().describe("What to tail (conversations = legacy alias of traces)"),
|
|
305
|
+
once: boolFlag().describe("Poll once, then exit (baseline page)"),
|
|
306
|
+
limit: z.number().min(1).max(50).default(20).optional().describe("Page size per poll"),
|
|
307
|
+
max_events: z.number().min(1).max(1e5).optional().describe("Stop after this many stdout records"),
|
|
308
|
+
full_scan: boolFlag().describe("Emotions: scan the whole window, not the top-scoring prefix (many more requests)")
|
|
309
|
+
});
|
|
310
|
+
var ToolFailureDetailSchema = z.object({
|
|
311
|
+
tool_name: z.string().describe("Tool name (from moda tool-failures)"),
|
|
312
|
+
subtype: z.string().optional().describe("Only this failure subtype"),
|
|
313
|
+
days_back: daysBackFlag(7),
|
|
314
|
+
limit: z.number().min(1).max(20).default(5).optional().describe("Maximum examples to return"),
|
|
315
|
+
offset: z.number().min(0).default(0).optional().describe("Number of examples to skip"),
|
|
316
|
+
include_window: boolFlag().describe("Embed each example's surrounding messages (on by default)"),
|
|
317
|
+
compact: boolFlag().describe("Drop each example's embedded messages (same as --include-window=false)"),
|
|
318
|
+
window: z.number().min(1).max(5).default(1).optional().describe("Messages on each side of the anchor")
|
|
319
|
+
});
|
|
320
|
+
var AuditSchema = z.object({
|
|
321
|
+
id: z.string().min(1).describe("Trace ID or OTLP trace_id"),
|
|
322
|
+
kind: z.enum(["auto", "trace", "conversation"]).default("auto").optional().describe("How to read the id (auto tries trace, then conversation)"),
|
|
323
|
+
include_raw: boolFlag().describe("Include raw span rows"),
|
|
324
|
+
include_bodies: boolFlag().describe("Keep prompt/response bodies (omitted by default; long strings truncated)"),
|
|
325
|
+
limit: z.number().min(1).max(1000).default(200).optional().describe("Ingest rows to read; warns when capped"),
|
|
326
|
+
cursor: z.string().min(1).max(512).optional().describe("next_cursor from the previous page"),
|
|
327
|
+
ids_from: z.string().min(1).optional().describe("Read several trace ids, one per line, from a file or - for stdin (batch mode, up to 25)")
|
|
328
|
+
});
|
|
329
|
+
var TIME_RANGES = ["all", "1h", "3d", "7d", "24h", "30d", "90d"];
|
|
330
|
+
var SEARCH_MODES = ["keyword", "semantic", "hybrid"];
|
|
331
|
+
var SearchSchema = z.object({
|
|
332
|
+
q: z.string().min(1).max(500).describe("Search text"),
|
|
333
|
+
mode: z.enum(SEARCH_MODES).default("hybrid").optional().describe("keyword = exact words, semantic = meaning, hybrid = both"),
|
|
334
|
+
user_id: z.string().optional().describe("Only messages from this end user"),
|
|
335
|
+
conversation_id: z.string().min(1).max(256).optional().describe("Search inside one trace only"),
|
|
336
|
+
time_range: z.enum(TIME_RANGES).default("all").optional().describe("Time window"),
|
|
337
|
+
since: isoBoundFlag().describe("Absolute UTC start, inclusive (ISO date or datetime); intersected with --time-range"),
|
|
338
|
+
until: isoBoundFlag().describe("Absolute UTC end, inclusive (ISO date or datetime; a date covers that whole day)"),
|
|
339
|
+
limit: z.number().min(1).max(100).default(20).optional().describe("Maximum results to return"),
|
|
340
|
+
include_tool_io: boolFlag().describe("Also search tool inputs and outputs"),
|
|
341
|
+
offset: z.number().min(0).max(1e4).optional().describe("Skip results (pages the trace-level fallback only)")
|
|
342
|
+
});
|
|
343
|
+
var InvestigateSchema = z.object({
|
|
344
|
+
days_back: daysBackFlag(7),
|
|
345
|
+
tool: z.string().optional().describe("Focus on one tool"),
|
|
346
|
+
trace: z.string().optional().describe("Scope the frustration evidence to one trace ID"),
|
|
347
|
+
conversation: z.string().optional().describe("Legacy alias of --trace")
|
|
348
|
+
});
|
|
349
|
+
var FailuresSchema = z.object({
|
|
350
|
+
days_back: daysBackFlag(7),
|
|
351
|
+
tool: z.string().optional().describe("Focus on one tool")
|
|
352
|
+
});
|
|
353
|
+
var AskSchema = z.object({
|
|
354
|
+
question: z.string().min(1).describe("The question, in plain language"),
|
|
355
|
+
days_back: daysBackFlag(7),
|
|
356
|
+
stream: boolFlag().describe("Stream live progress (default in a terminal; --no-stream turns it off)")
|
|
357
|
+
});
|
|
358
|
+
var TraceSignalsSchema = z.object({
|
|
359
|
+
conversation_id: z.string().min(1).describe("Trace ID")
|
|
360
|
+
});
|
|
361
|
+
var ProblemLinearSchema = z.object({
|
|
362
|
+
id: z.string().min(1).describe("Problem id (UUID)")
|
|
363
|
+
});
|
|
364
|
+
var DetectionReviewsSchema = z.object({
|
|
365
|
+
conversation_id: z.string().min(1).describe("Trace ID"),
|
|
366
|
+
attribution_id: z.string().regex(UUID_RE).optional().describe("Only this detection (UUID)")
|
|
367
|
+
});
|
|
368
|
+
var FALSE_POSITIVE_LIST_KINDS = ["quote", "tool", "laziness", "hallucination", "custom-signal"];
|
|
369
|
+
var FalsePositivesSchema = z.object({
|
|
370
|
+
conversation_id: z.string().min(1).optional().describe("List the marks on one trace instead of the whole workspace"),
|
|
371
|
+
kind: z.enum(FALSE_POSITIVE_LIST_KINDS).optional().describe("Only this kind of mark"),
|
|
372
|
+
all: boolFlag().describe("Include cleared (undone) marks"),
|
|
373
|
+
limit: z.number().int().min(1).max(200).optional().describe("Maximum marks to return")
|
|
374
|
+
});
|
|
375
|
+
var SignalsListSchema = z.object({});
|
|
376
|
+
var USER_WINDOWS = ["24h", "7d", "30d", "90d"];
|
|
377
|
+
var USER_COHORTS = ["all", "power_users", "heavy_tool_users", "frustrated", "at_risk", "new_this_week", "silent_sufferers"];
|
|
378
|
+
var USER_SORTS = ["last_seen", "health_score", "conversation_count", "frustration_max"];
|
|
379
|
+
var USER_HEALTH = ["healthy", "at_risk", "frustrated"];
|
|
380
|
+
var UsersSchema = z.object({
|
|
381
|
+
window: z.enum(USER_WINDOWS).optional().describe("Activity window"),
|
|
382
|
+
page: z.number().int().min(1).optional().describe("Page number"),
|
|
383
|
+
page_size: z.number().int().min(1).optional().describe("Users per page"),
|
|
384
|
+
limit: z.number().int().min(1).optional().describe("Alias of --page-size"),
|
|
385
|
+
search: z.string().optional().describe("Match user id or name"),
|
|
386
|
+
cohort: z.enum(USER_COHORTS).optional().describe("Only users in this cohort"),
|
|
387
|
+
sort: z.enum(USER_SORTS).optional().describe("Sort field"),
|
|
388
|
+
order: z.enum(["asc", "desc"]).optional().describe("Sort order"),
|
|
389
|
+
health_status: z.string().optional().describe(`Comma-separated health states (${USER_HEALTH.join(", ")})`),
|
|
390
|
+
min_health: z.number().int().min(0).max(100).optional().describe("Minimum health score"),
|
|
391
|
+
max_health: z.number().int().min(0).max(100).optional().describe("Maximum health score"),
|
|
392
|
+
min_conversations: z.number().int().min(0).optional().describe("Minimum trace count"),
|
|
393
|
+
min_tool_failures: z.number().int().min(0).optional().describe("Minimum tool failure count"),
|
|
394
|
+
tenant_id: z.string().optional().describe("Tenant to read; must match the API key's tenant")
|
|
395
|
+
});
|
|
396
|
+
var UserSchema = z.object({
|
|
397
|
+
user_id: z.string().min(1).describe("End user id"),
|
|
398
|
+
window: z.enum(USER_WINDOWS).optional().describe("Activity window"),
|
|
399
|
+
activity_cursor: z.string().optional().describe("Activity page cursor from the previous response"),
|
|
400
|
+
tenant_id: z.string().optional().describe("Tenant to read; must match the API key's tenant")
|
|
401
|
+
});
|
|
402
|
+
var ProblemReopenSchema = z.object({
|
|
403
|
+
id: z.string().regex(UUID_RE).describe("Problem id (UUID)"),
|
|
404
|
+
reason: z.string().max(500).optional().describe("Why it is being reopened"),
|
|
405
|
+
dry_run: boolFlag().describe("Print the request and send nothing")
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// ../../packages/database/src/agent-feedback.ts
|
|
409
|
+
var FEEDBACK_CATEGORIES = [
|
|
410
|
+
"bad_cluster_label",
|
|
411
|
+
"mismatched_frustration",
|
|
412
|
+
"missing_data",
|
|
413
|
+
"noisy_data",
|
|
414
|
+
"wrong_tool_failure",
|
|
415
|
+
"incorrect_loop",
|
|
416
|
+
"api_quirk",
|
|
417
|
+
"false_positive_attribution",
|
|
418
|
+
"misrouted_fix",
|
|
419
|
+
"harness_bug",
|
|
420
|
+
"other"
|
|
421
|
+
];
|
|
422
|
+
var FEEDBACK_SEVERITIES = ["info", "low", "medium", "high"];
|
|
423
|
+
var DEFAULT_FEEDBACK_CATEGORY = "other";
|
|
424
|
+
var RAW_FEEDBACK_CATEGORY_MAX = 64;
|
|
425
|
+
function isFeedbackCategory(value) {
|
|
426
|
+
return typeof value === "string" && FEEDBACK_CATEGORIES.includes(value);
|
|
427
|
+
}
|
|
428
|
+
function normalizeFeedbackCategory(value) {
|
|
429
|
+
if (value === undefined || value === null || value === "")
|
|
430
|
+
return { category: DEFAULT_FEEDBACK_CATEGORY };
|
|
431
|
+
if (isFeedbackCategory(value))
|
|
432
|
+
return { category: value };
|
|
433
|
+
const raw = String(value).trim().slice(0, RAW_FEEDBACK_CATEGORY_MAX);
|
|
434
|
+
return raw ? { category: DEFAULT_FEEDBACK_CATEGORY, raw_category: raw } : { category: DEFAULT_FEEDBACK_CATEGORY };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/feedback.ts
|
|
438
|
+
import { z as z2 } from "zod";
|
|
439
|
+
var FeedbackSchema = z2.object({
|
|
440
|
+
note: z2.string().min(1).max(4000).describe("What is wrong or missing"),
|
|
441
|
+
category: z2.enum(FEEDBACK_CATEGORIES).default("other").describe("What kind of problem; an unknown value is sent as other"),
|
|
442
|
+
severity: z2.enum(FEEDBACK_SEVERITIES).default("low").describe("How bad it is"),
|
|
443
|
+
conversation_id: z2.string().optional().describe("Attach the trace ID you were looking at"),
|
|
444
|
+
cluster_id: z2.string().optional().describe("Attach a cluster node id"),
|
|
445
|
+
tool_name: z2.string().optional().describe("Attach a tool name"),
|
|
446
|
+
run_id: z2.string().optional().describe("Attach a run id")
|
|
447
|
+
});
|
|
448
|
+
var FEEDBACK_FLAGS = Object.keys(FeedbackSchema.shape).map((key) => key.replace(/_/g, "-"));
|
|
449
|
+
var FEEDBACK_USAGE = `Usage: moda feedback "<note>" [--category=${FEEDBACK_CATEGORIES.join("|")}] ` + `[--severity=${FEEDBACK_SEVERITIES.join("|")}] [--conversation-id=ID] [--cluster-id=ID] ` + "[--tool-name=NAME] [--run-id=ID] [--<name>-id=ID]";
|
|
450
|
+
var NOTE_MAX = 4000;
|
|
451
|
+
var REF_VALUE_MAX = 256;
|
|
452
|
+
var REF_FLAG_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*-(?:id|ids|index|token)$/;
|
|
453
|
+
var REF_FLAG_WORDS = new Set(["turn", "tool", "family", "kind"]);
|
|
454
|
+
function isFeedbackRefFlag(flag) {
|
|
455
|
+
return REF_FLAG_PATTERN.test(flag) || REF_FLAG_WORDS.has(flag);
|
|
456
|
+
}
|
|
457
|
+
function buildFeedbackRequest(positionals, flags, bareFlags) {
|
|
458
|
+
const isBare = (flag) => bareFlags ? bareFlags.includes(flag) : flags[flag] === "true";
|
|
459
|
+
const flagNote = flags.note !== undefined && !isBare("note") ? flags.note.trim() : "";
|
|
460
|
+
const note = [positionals.join(" ").trim(), flagNote].filter(Boolean).join(`
|
|
461
|
+
`);
|
|
462
|
+
if (note === "")
|
|
463
|
+
throw new CliInputError("<note> is required", FEEDBACK_USAGE);
|
|
464
|
+
if (note.length > NOTE_MAX) {
|
|
465
|
+
throw new CliInputError(`<note> is ${note.length} characters; the limit is ${NOTE_MAX}.`, FEEDBACK_USAGE);
|
|
466
|
+
}
|
|
467
|
+
const severity = flags.severity ?? "low";
|
|
468
|
+
if (!FEEDBACK_SEVERITIES.includes(severity)) {
|
|
469
|
+
throw new CliInputError(`Invalid --severity '${severity}'. Valid severities: ${FEEDBACK_SEVERITIES.join(", ")}.`, FEEDBACK_USAGE);
|
|
470
|
+
}
|
|
471
|
+
const refs = {};
|
|
472
|
+
for (const [flag, value] of Object.entries(flags)) {
|
|
473
|
+
if (flag === "category" || flag === "severity" || flag === "note")
|
|
474
|
+
continue;
|
|
475
|
+
if (!FEEDBACK_FLAGS.includes(flag) && !isFeedbackRefFlag(flag))
|
|
476
|
+
continue;
|
|
477
|
+
const trimmed = value.trim();
|
|
478
|
+
if (value === "true" || trimmed === "") {
|
|
479
|
+
throw new CliInputError(`--${flag} needs a value, e.g. --${flag}=<id>.`, FEEDBACK_USAGE);
|
|
480
|
+
}
|
|
481
|
+
if (trimmed.length > REF_VALUE_MAX) {
|
|
482
|
+
throw new CliInputError(`--${flag} is ${trimmed.length} characters; the limit is ${REF_VALUE_MAX}.`, FEEDBACK_USAGE);
|
|
483
|
+
}
|
|
484
|
+
refs[flag.replace(/-/g, "_")] = trimmed;
|
|
485
|
+
}
|
|
486
|
+
const warnings = [];
|
|
487
|
+
const { category, raw_category: rawCategory } = normalizeFeedbackCategory(flags.category);
|
|
488
|
+
if (rawCategory !== undefined) {
|
|
489
|
+
refs.raw_category = rawCategory;
|
|
490
|
+
warnings.push(`Unknown category '${rawCategory}': sent as 'other' with refs.raw_category. ` + `Valid categories: ${FEEDBACK_CATEGORIES.join(", ")}.`);
|
|
491
|
+
}
|
|
492
|
+
const hint = dedicatedCommandHint(category, rawCategory);
|
|
493
|
+
if (hint)
|
|
494
|
+
warnings.push(hint);
|
|
495
|
+
return { category, severity, note, refs, warnings };
|
|
496
|
+
}
|
|
497
|
+
var MISROUTE_CATEGORIES = new Set(["false_positive_attribution", "misrouted_fix"]);
|
|
498
|
+
var MISROUTE_WORDS = /attribut|misrout|false[\s_-]?positive|wrong[\s_-]?(?:problem|fix)/i;
|
|
499
|
+
function dedicatedCommandHint(category, rawCategory) {
|
|
500
|
+
if (!MISROUTE_CATEGORIES.has(category) && !(rawCategory && MISROUTE_WORDS.test(rawCategory)))
|
|
501
|
+
return;
|
|
502
|
+
return "Feedback is read by the Moda team but does not change the problem. To act on it directly, use " + "`moda problem-feedback <problem_id> --action=flag_attribution --attribution-id=<uuid> --reason=TEXT` (a trace wrongly " + "attributed to a problem), `moda false-positive <kind> <target> --reason=TEXT` (a detection that should not " + "have fired), or `moda fix dismiss <fix_id> --reason=TEXT` (a fix aimed at the wrong problem).";
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/command-flags.ts
|
|
506
|
+
import { z as z3 } from "zod";
|
|
507
|
+
var TRACE_ID = { name: "conversation_id", key: "conversation_id", required: true };
|
|
508
|
+
var PROBLEM_ID = { name: "problem_id", key: "id", required: true };
|
|
509
|
+
var COMMAND_FLAG_SPECS = {
|
|
510
|
+
overview: { schema: OverviewSchema, extra: z3.object({ raw: z3.boolean().optional().describe("Print the raw briefing data") }) },
|
|
511
|
+
investigate: { schema: InvestigateSchema, handParsed: true },
|
|
512
|
+
failures: { schema: FailuresSchema, handParsed: true },
|
|
513
|
+
ask: { schema: AskSchema, handParsed: true, positionals: [{ name: "question", key: "question", required: true }] },
|
|
514
|
+
clusters: { schema: ClustersSchema },
|
|
515
|
+
"cluster-traces": { schema: ClusterConversationsSchema, positionals: [{ name: "node_id", key: "node_id", required: true }] },
|
|
516
|
+
traces: { schema: ConversationsSchema },
|
|
517
|
+
search: { schema: SearchSchema, positionals: [{ name: "query", key: "q", required: true }] },
|
|
518
|
+
"world-state": { schema: WorldStateSchema, positionals: [TRACE_ID] },
|
|
519
|
+
context: {
|
|
520
|
+
schema: ContextSchema,
|
|
521
|
+
positionals: [{ ...TRACE_ID, alsoFlag: true }],
|
|
522
|
+
flagAliases: { "message-index": "msg-index" }
|
|
523
|
+
},
|
|
524
|
+
audit: { schema: AuditSchema, positionals: [{ name: "conversation_id|trace_id", key: "id", required: true }] },
|
|
525
|
+
frustrations: { schema: FrustrationsSchema },
|
|
526
|
+
"tool-failures": { schema: ToolFailuresSchema },
|
|
527
|
+
"tool-failure-detail": { schema: ToolFailureDetailSchema, positionals: [{ name: "tool_name", key: "tool_name", required: true }] },
|
|
528
|
+
problems: {
|
|
529
|
+
schema: ProblemsSchema,
|
|
530
|
+
subcommands: { remainder: { schema: ProblemsRemainderSchema } }
|
|
531
|
+
},
|
|
532
|
+
"problem-impact": { schema: ProblemImpactSchema, handParsed: true },
|
|
533
|
+
problem: { schema: ProblemSchema, positionals: [PROBLEM_ID] },
|
|
534
|
+
"problem-feedback": { schema: ProblemFeedbackSchema, handParsed: true, positionals: [PROBLEM_ID] },
|
|
535
|
+
"problem-reopen": { schema: ProblemReopenSchema, handParsed: true, positionals: [PROBLEM_ID] },
|
|
536
|
+
"problem-linear": { schema: ProblemLinearSchema, handParsed: true, positionals: [PROBLEM_ID] },
|
|
537
|
+
"problem-linear-file": { schema: ProblemLinearFileSchema, handParsed: true, positionals: [PROBLEM_ID] },
|
|
538
|
+
"detection-reviews": { schema: DetectionReviewsSchema, handParsed: true, positionals: [TRACE_ID] },
|
|
539
|
+
"detection-review": { schema: DetectionReviewSchema, handParsed: true, positionals: [PROBLEM_ID] },
|
|
540
|
+
"false-positive": {
|
|
541
|
+
schema: FalsePositiveSchema,
|
|
542
|
+
handParsed: true,
|
|
543
|
+
positionals: [
|
|
544
|
+
{ name: "kind", key: "kind", required: true },
|
|
545
|
+
{ name: "target", key: "target", required: true }
|
|
546
|
+
]
|
|
547
|
+
},
|
|
548
|
+
"false-positives": {
|
|
549
|
+
schema: FalsePositivesSchema,
|
|
550
|
+
handParsed: true,
|
|
551
|
+
positionals: [{ name: "conversation_id", key: "conversation_id", required: false }]
|
|
552
|
+
},
|
|
553
|
+
signals: { schema: SignalsListSchema, handParsed: true },
|
|
554
|
+
signal: { schema: SignalSchema, positionals: [{ name: "signal_id", key: "id", required: true }] },
|
|
555
|
+
"trace-signals": { schema: TraceSignalsSchema, handParsed: true, positionals: [TRACE_ID] },
|
|
556
|
+
"signal-label": { schema: SignalLabelSchema, handParsed: true, positionals: [{ name: "signal_id", key: "id", required: true }] },
|
|
557
|
+
emotions: { schema: EmotionsSchema },
|
|
558
|
+
laziness: { schema: LazinessSchema },
|
|
559
|
+
hallucinations: { schema: HallucinationsSchema },
|
|
560
|
+
"step-scores": { schema: StepScoresSchema, handParsed: true, positionals: [TRACE_ID] },
|
|
561
|
+
tail: { schema: TailSchema },
|
|
562
|
+
feedback: {
|
|
563
|
+
schema: FeedbackSchema,
|
|
564
|
+
handParsed: true,
|
|
565
|
+
idRefFlags: true,
|
|
566
|
+
positionals: [{ name: "note", key: "note", required: true, variadic: true, alsoFlag: true }]
|
|
567
|
+
},
|
|
568
|
+
users: { schema: UsersSchema, handParsed: true },
|
|
569
|
+
user: { schema: UserSchema, handParsed: true, positionals: [{ name: "user_id", key: "user_id", required: true }] }
|
|
570
|
+
};
|
|
571
|
+
function flagSpecFor(command) {
|
|
572
|
+
return Object.hasOwn(COMMAND_FLAG_SPECS, command) ? COMMAND_FLAG_SPECS[command] : undefined;
|
|
573
|
+
}
|
|
574
|
+
function kebabFlag(key) {
|
|
575
|
+
return `--${key.replace(/_/g, "-")}`;
|
|
576
|
+
}
|
|
577
|
+
function schemaShape(schema) {
|
|
578
|
+
let current = schema;
|
|
579
|
+
while (current instanceof z3.ZodEffects)
|
|
580
|
+
current = current.innerType();
|
|
581
|
+
return current instanceof z3.ZodObject ? current.shape : {};
|
|
582
|
+
}
|
|
583
|
+
function scalarDefault(value) {
|
|
584
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : undefined;
|
|
585
|
+
}
|
|
586
|
+
function literalValues(schema) {
|
|
587
|
+
if (schema instanceof z3.ZodEnum)
|
|
588
|
+
return [...schema.options];
|
|
589
|
+
if (schema instanceof z3.ZodLiteral)
|
|
590
|
+
return [String(schema.value)];
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
function describeField(field) {
|
|
594
|
+
let current = field;
|
|
595
|
+
let description;
|
|
596
|
+
let optional = false;
|
|
597
|
+
let defaultValue;
|
|
598
|
+
for (;; ) {
|
|
599
|
+
description ??= current.description;
|
|
600
|
+
if (current instanceof z3.ZodOptional || current instanceof z3.ZodNullable) {
|
|
601
|
+
optional = true;
|
|
602
|
+
current = current.unwrap();
|
|
603
|
+
} else if (current instanceof z3.ZodDefault) {
|
|
604
|
+
optional = true;
|
|
605
|
+
defaultValue ??= scalarDefault(current._def.defaultValue());
|
|
606
|
+
current = current._def.innerType;
|
|
607
|
+
} else if (current instanceof z3.ZodEffects) {
|
|
608
|
+
current = current.innerType();
|
|
609
|
+
} else if (current instanceof z3.ZodPipeline) {
|
|
610
|
+
current = current._def.in;
|
|
611
|
+
} else {
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const info = { type: "string", optional, ...defaultValue !== undefined ? { default: defaultValue } : {} };
|
|
616
|
+
if (description)
|
|
617
|
+
info.description = description;
|
|
618
|
+
const values = literalValues(current);
|
|
619
|
+
if (values) {
|
|
620
|
+
info.type = "enum";
|
|
621
|
+
info.enum = values;
|
|
622
|
+
} else if (current instanceof z3.ZodUnion) {
|
|
623
|
+
const options = current.options.map((option) => literalValues(option));
|
|
624
|
+
if (options.every((option) => option !== undefined)) {
|
|
625
|
+
info.type = "enum";
|
|
626
|
+
info.enum = options.flat();
|
|
627
|
+
}
|
|
628
|
+
} else if (current instanceof z3.ZodBoolean) {
|
|
629
|
+
info.type = "boolean";
|
|
630
|
+
} else if (current instanceof z3.ZodNumber) {
|
|
631
|
+
info.type = current.isInt ? "integer" : "number";
|
|
632
|
+
for (const check of current._def.checks) {
|
|
633
|
+
if (check.kind === "min")
|
|
634
|
+
info.min = check.inclusive ? check.value : check.value + (info.type === "integer" ? 1 : 0);
|
|
635
|
+
if (check.kind === "max")
|
|
636
|
+
info.max = check.inclusive ? check.value : check.value - (info.type === "integer" ? 1 : 0);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return info;
|
|
640
|
+
}
|
|
641
|
+
function positionalKeys(spec) {
|
|
642
|
+
return new Set((spec.positionals ?? []).filter((p) => p.key && !p.alsoFlag).map((p) => p.key));
|
|
643
|
+
}
|
|
644
|
+
function specFlagKeys(spec) {
|
|
645
|
+
const skip = positionalKeys(spec);
|
|
646
|
+
return [
|
|
647
|
+
...Object.keys(schemaShape(spec.schema)).filter((key) => !skip.has(key)),
|
|
648
|
+
...Object.keys(spec.extra?.shape ?? {})
|
|
649
|
+
];
|
|
650
|
+
}
|
|
651
|
+
function specFlagNames(spec) {
|
|
652
|
+
return [
|
|
653
|
+
...specFlagKeys(spec).map((key) => key.replace(/_/g, "-")),
|
|
654
|
+
...Object.keys(spec.flagAliases ?? {})
|
|
655
|
+
];
|
|
656
|
+
}
|
|
657
|
+
function describeSpecFlags(spec) {
|
|
658
|
+
const shape = { ...schemaShape(spec.schema), ...spec.extra?.shape ?? {} };
|
|
659
|
+
const flagPositionals = new Map((spec.positionals ?? []).filter((p) => p.alsoFlag && p.key).map((p) => [p.key, p]));
|
|
660
|
+
const flags = specFlagKeys(spec).map((key) => {
|
|
661
|
+
const info = describeField(shape[key]);
|
|
662
|
+
const positional = flagPositionals.get(key);
|
|
663
|
+
if (positional)
|
|
664
|
+
info.description = `Same as the <${positional.name}> argument`;
|
|
665
|
+
const descriptor = { name: kebabFlag(key), type: info.type, required: !info.optional && !positional };
|
|
666
|
+
if (info.enum)
|
|
667
|
+
descriptor.enum = info.enum;
|
|
668
|
+
if (info.min !== undefined)
|
|
669
|
+
descriptor.min = info.min;
|
|
670
|
+
if (info.max !== undefined)
|
|
671
|
+
descriptor.max = info.max;
|
|
672
|
+
if (info.default !== undefined)
|
|
673
|
+
descriptor.default = info.default;
|
|
674
|
+
if (info.description)
|
|
675
|
+
descriptor.description = info.description;
|
|
676
|
+
return descriptor;
|
|
677
|
+
});
|
|
678
|
+
for (const [alias, canonical] of Object.entries(spec.flagAliases ?? {})) {
|
|
679
|
+
const target = flags.find((flag) => flag.name === `--${canonical}`);
|
|
680
|
+
flags.push({
|
|
681
|
+
...target ?? { type: "string" },
|
|
682
|
+
name: `--${alias}`,
|
|
683
|
+
required: false,
|
|
684
|
+
description: `Alias of --${canonical}`,
|
|
685
|
+
alias_of: `--${canonical}`
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
return flags;
|
|
689
|
+
}
|
|
690
|
+
function describeSpecPositionals(spec) {
|
|
691
|
+
const shape = schemaShape(spec.schema);
|
|
692
|
+
return (spec.positionals ?? []).map((positional) => {
|
|
693
|
+
const field = positional.key ? shape[positional.key] : undefined;
|
|
694
|
+
const info = field ? describeField(field) : undefined;
|
|
695
|
+
const description = positional.description ?? info?.description;
|
|
696
|
+
return {
|
|
697
|
+
name: positional.name,
|
|
698
|
+
required: positional.required,
|
|
699
|
+
...info?.enum ? { enum: info.enum } : {},
|
|
700
|
+
...description ? { description } : {},
|
|
701
|
+
...positional.variadic ? { variadic: true } : {},
|
|
702
|
+
...positional.alsoFlag && positional.key ? { flag: kebabFlag(positional.key) } : {}
|
|
703
|
+
};
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
function editDistance(a, b) {
|
|
707
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
708
|
+
for (let i = 1;i <= a.length; i++) {
|
|
709
|
+
let previous = row[0];
|
|
710
|
+
row[0] = i;
|
|
711
|
+
for (let j = 1;j <= b.length; j++) {
|
|
712
|
+
const current = row[j];
|
|
713
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
714
|
+
previous = current;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return row[b.length];
|
|
718
|
+
}
|
|
719
|
+
function closestMatches(input, candidates, max = 3) {
|
|
720
|
+
const normalize = (value) => value.toLowerCase().replace(/_/g, "-");
|
|
721
|
+
const target = normalize(input);
|
|
722
|
+
if (!target)
|
|
723
|
+
return [];
|
|
724
|
+
const limit = Math.min(3, Math.max(1, Math.floor(target.length / 3)));
|
|
725
|
+
const scored = [];
|
|
726
|
+
for (const candidate of new Set(candidates)) {
|
|
727
|
+
const normalized = normalize(candidate);
|
|
728
|
+
if (normalized === target)
|
|
729
|
+
continue;
|
|
730
|
+
const distance = editDistance(target, normalized);
|
|
731
|
+
if (distance <= limit) {
|
|
732
|
+
scored.push({ candidate, score: distance });
|
|
733
|
+
} else if (target.length >= 3 && (normalized.startsWith(target) || target.startsWith(normalized))) {
|
|
734
|
+
scored.push({ candidate, score: limit + 1 });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return scored.sort((left, right) => left.score - right.score || left.candidate.localeCompare(right.candidate)).slice(0, max).map(({ candidate }) => candidate);
|
|
738
|
+
}
|
|
739
|
+
function placeholder(flag) {
|
|
740
|
+
if (flag.type === "boolean")
|
|
741
|
+
return "";
|
|
742
|
+
if (flag.type === "enum" && flag.enum) {
|
|
743
|
+
const joined = flag.enum.join("|");
|
|
744
|
+
return joined.length <= 32 ? `=${joined}` : "=<value>";
|
|
745
|
+
}
|
|
746
|
+
if (flag.type === "integer" || flag.type === "number")
|
|
747
|
+
return "=N";
|
|
748
|
+
if (/(^--|-)(cursor|token)$/.test(flag.name))
|
|
749
|
+
return "=TOKEN";
|
|
750
|
+
return /-ids?$/.test(flag.name) ? "=ID" : "=TEXT";
|
|
751
|
+
}
|
|
752
|
+
function rangeText(flag) {
|
|
753
|
+
if (flag.min !== undefined && flag.max !== undefined)
|
|
754
|
+
return `${flag.min}-${flag.max}`;
|
|
755
|
+
if (flag.min !== undefined)
|
|
756
|
+
return `min ${flag.min}`;
|
|
757
|
+
if (flag.max !== undefined)
|
|
758
|
+
return `max ${flag.max}`;
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
function flagDetail(flag) {
|
|
762
|
+
const facts = [];
|
|
763
|
+
if (flag.required)
|
|
764
|
+
facts.push("required");
|
|
765
|
+
if (flag.type === "enum" && flag.enum && flag.enum.join("|").length > 32)
|
|
766
|
+
facts.push(`one of ${flag.enum.join(", ")}`);
|
|
767
|
+
const range = rangeText(flag);
|
|
768
|
+
if (range)
|
|
769
|
+
facts.push(range);
|
|
770
|
+
if (flag.default !== undefined && flag.type !== "boolean")
|
|
771
|
+
facts.push(`default ${flag.default}`);
|
|
772
|
+
const description = flag.description ?? "";
|
|
773
|
+
if (facts.length === 0)
|
|
774
|
+
return description;
|
|
775
|
+
return description ? `${description} [${facts.join("; ")}]` : `[${facts.join("; ")}]`;
|
|
776
|
+
}
|
|
777
|
+
function usageLine(command, spec) {
|
|
778
|
+
const positionals = (spec.positionals ?? []).map((positional) => {
|
|
779
|
+
const word = `<${positional.name}>${positional.variadic ? "..." : ""}`;
|
|
780
|
+
return positional.required ? word : `[${word}]`;
|
|
781
|
+
});
|
|
782
|
+
const hasFlags = specFlagKeys(spec).length > 0;
|
|
783
|
+
return ["moda", command, ...positionals, ...hasFlags ? ["[flags]"] : []].join(" ");
|
|
784
|
+
}
|
|
785
|
+
function table(rows) {
|
|
786
|
+
const width = Math.min(34, Math.max(...rows.map(([left]) => left.length)));
|
|
787
|
+
return rows.map(([left, right]) => {
|
|
788
|
+
if (!right)
|
|
789
|
+
return ` ${left}`;
|
|
790
|
+
return left.length > width ? ` ${left}
|
|
791
|
+
${" ".repeat(width)} ${right}` : ` ${left.padEnd(width)} ${right}`;
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
function renderFlagHelp(command, spec) {
|
|
795
|
+
const lines = [`Usage: ${usageLine(command, spec)}`];
|
|
796
|
+
const positionals = describeSpecPositionals(spec);
|
|
797
|
+
if (positionals.length > 0) {
|
|
798
|
+
lines.push("", "Arguments:");
|
|
799
|
+
lines.push(...table(positionals.map((positional) => {
|
|
800
|
+
const notes = [
|
|
801
|
+
positional.required ? "required" : "optional",
|
|
802
|
+
...positional.enum ? [`one of ${positional.enum.join(", ")}`] : [],
|
|
803
|
+
...positional.flag ? [`or ${positional.flag}=...`] : []
|
|
804
|
+
];
|
|
805
|
+
return [`<${positional.name}>`, `${positional.description ?? ""} [${notes.join("; ")}]`.trim()];
|
|
806
|
+
})));
|
|
807
|
+
}
|
|
808
|
+
const flags = describeSpecFlags(spec);
|
|
809
|
+
lines.push("", "Flags:");
|
|
810
|
+
if (flags.length === 0) {
|
|
811
|
+
lines.push(" (none besides the output flags below)");
|
|
812
|
+
} else {
|
|
813
|
+
lines.push(...table(flags.map((flag) => [`${flag.name}${placeholder(flag)}`, flagDetail(flag)])));
|
|
814
|
+
}
|
|
815
|
+
for (const [name, subcommand] of Object.entries(spec.subcommands ?? {})) {
|
|
816
|
+
lines.push("", `Flags for 'moda ${command} ${name}':`);
|
|
817
|
+
lines.push(...table(describeSpecFlags(subcommand).map((flag) => [`${flag.name}${placeholder(flag)}`, flagDetail(flag)])));
|
|
818
|
+
}
|
|
819
|
+
return lines;
|
|
820
|
+
}
|
|
821
|
+
function subcommandHelpFlags(spec) {
|
|
822
|
+
return describeSpecFlags(spec).map((flag) => ({ name: `${flag.name}${placeholder(flag)}`, description: flagDetail(flag) }));
|
|
823
|
+
}
|
|
824
|
+
function manifestFlagFields(spec) {
|
|
825
|
+
const subcommands = Object.entries(spec.subcommands ?? {});
|
|
826
|
+
return {
|
|
827
|
+
positionals: describeSpecPositionals(spec),
|
|
828
|
+
flags: describeSpecFlags(spec),
|
|
829
|
+
...subcommands.length > 0 ? { subcommand_flags: Object.fromEntries(subcommands.map(([name, sub]) => [name, describeSpecFlags(sub)])) } : {}
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/parity-commands.ts
|
|
834
|
+
import { readFileSync } from "node:fs";
|
|
835
|
+
import { resolve } from "node:path";
|
|
836
|
+
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
837
|
+
var WINDOWS = USER_WINDOWS;
|
|
838
|
+
function profileOptions(context) {
|
|
839
|
+
return { profile: context.profile, env: context.env };
|
|
840
|
+
}
|
|
841
|
+
function requireKey(context) {
|
|
842
|
+
if (!resolveApiKey(profileOptions(context))) {
|
|
843
|
+
throw new CliInputError(`No Moda API key found for moda ${context.command}.`, "Set MODA_API_KEY, run `moda auth login --api-key`, or select a profile with an API key.");
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function requireTenant(context) {
|
|
847
|
+
const key = resolveApiKey(profileOptions(context));
|
|
848
|
+
const keyTenant = key ? embeddedTenantIdFromApiKey(key) : undefined;
|
|
849
|
+
const flagTenant = context.flags["tenant-id"];
|
|
850
|
+
if (flagTenant && keyTenant && flagTenant !== keyTenant) {
|
|
851
|
+
throw new CliInputError(`--tenant-id=${flagTenant} does not match this API key's tenant (${keyTenant}), so nothing was sent.`, "Drop --tenant-id (the key names its tenant) or use a key for that tenant.");
|
|
852
|
+
}
|
|
853
|
+
const tenantId = flagTenant || resolveApiTenantId(profileOptions(context));
|
|
854
|
+
if (!tenantId) {
|
|
855
|
+
throw new CliInputError("Could not determine the tenant.", "Use a signed API key (it names its tenant), set MODA_TENANT_ID, or pass --tenant-id=<id>.");
|
|
856
|
+
}
|
|
857
|
+
if (isPlatformApiKey(key))
|
|
858
|
+
setPlatformTenantOverride(tenantId);
|
|
859
|
+
return tenantId;
|
|
860
|
+
}
|
|
861
|
+
function assertFlags(command, flags, allowed) {
|
|
862
|
+
if (command.startsWith("signals "))
|
|
863
|
+
allowed = [...allowed, "tenant-id"];
|
|
864
|
+
const globals = [
|
|
865
|
+
"agent",
|
|
866
|
+
"json",
|
|
867
|
+
"human",
|
|
868
|
+
"prose",
|
|
869
|
+
"quiet",
|
|
870
|
+
"verbose",
|
|
871
|
+
"yes",
|
|
872
|
+
"profile",
|
|
873
|
+
"progress",
|
|
874
|
+
"format",
|
|
875
|
+
"no-telemetry",
|
|
876
|
+
"no-update-check",
|
|
877
|
+
"help",
|
|
878
|
+
"version",
|
|
879
|
+
"json-schemas",
|
|
880
|
+
"stream",
|
|
881
|
+
"no-stream",
|
|
882
|
+
"tui",
|
|
883
|
+
"no-tui"
|
|
884
|
+
];
|
|
885
|
+
const known = new Set([...allowed, ...globals]);
|
|
886
|
+
const unknown = Object.keys(flags).filter((flag) => !known.has(flag));
|
|
887
|
+
if (unknown.includes("dry-run")) {
|
|
888
|
+
throw new CliInputError(`'moda ${command}' has no dry-run mode, so nothing was sent.`, "Remove --dry-run to run it for real.");
|
|
889
|
+
}
|
|
890
|
+
if (unknown.length > 0) {
|
|
891
|
+
const valid = allowed.map((flag) => `--${flag}`);
|
|
892
|
+
const didYouMean = [...new Set(unknown.flatMap((flag) => closestMatches(`--${flag}`, valid, 1)))];
|
|
893
|
+
const helpCommand = `moda ${command} --help`;
|
|
894
|
+
throw new CliInputError(`Unknown flag${unknown.length > 1 ? "s" : ""} ${unknown.map((flag) => `--${flag}`).join(", ")} for 'moda ${command}'.` + (didYouMean.length > 0 ? ` Did you mean ${didYouMean.join(", ")}?` : ""), `Valid flags: ${valid.join(", ") || "(none)"}. Run '${helpCommand}'.`, `unknown_flag:${unknown[0]}`, {
|
|
895
|
+
field: `--${unknown[0]}`,
|
|
896
|
+
allowed: valid,
|
|
897
|
+
...didYouMean.length > 0 ? { didYouMean } : {},
|
|
898
|
+
suggestedCommands: [helpCommand]
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function assertPositionals(command, positionals, max, usage) {
|
|
903
|
+
if (positionals.length > max) {
|
|
904
|
+
throw new CliInputError(`Unexpected argument${positionals.length - max > 1 ? "s" : ""} '${positionals.slice(max).join(" ")}' for 'moda ${command}', so nothing was sent.`, `Usage: ${usage}`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function oneOf(name, value, allowed) {
|
|
908
|
+
if (value === undefined)
|
|
909
|
+
return;
|
|
910
|
+
if (!allowed.includes(value)) {
|
|
911
|
+
throw new CliInputError(`--${name} must be one of ${allowed.join(", ")} (got '${value}').`);
|
|
912
|
+
}
|
|
913
|
+
return value;
|
|
914
|
+
}
|
|
915
|
+
function csv(value) {
|
|
916
|
+
if (value === undefined)
|
|
917
|
+
return;
|
|
918
|
+
const items = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
919
|
+
return items.length > 0 ? items : undefined;
|
|
920
|
+
}
|
|
921
|
+
function readBodyFile(flags, cwd) {
|
|
922
|
+
if (!flags.file)
|
|
923
|
+
return {};
|
|
924
|
+
if (flags.file === "true")
|
|
925
|
+
throw new CliInputError("Missing path for --file.");
|
|
926
|
+
let parsed;
|
|
927
|
+
try {
|
|
928
|
+
parsed = JSON.parse(readFileSync(resolve(cwd, flags.file), "utf8"));
|
|
929
|
+
} catch (error) {
|
|
930
|
+
throw new CliInputError(`Could not read --file ${flags.file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
931
|
+
}
|
|
932
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
933
|
+
throw new CliInputError("--file must contain a JSON object (the request body).");
|
|
934
|
+
}
|
|
935
|
+
return parsed;
|
|
936
|
+
}
|
|
937
|
+
function compact(value) {
|
|
938
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
939
|
+
}
|
|
940
|
+
var LLM_WRITE_TIMEOUT_MS = 180000;
|
|
941
|
+
async function send(context, plane, method, endpoint, body, options = {}) {
|
|
942
|
+
const init = { method, ...body !== undefined ? { body: JSON.stringify(body) } : {} };
|
|
943
|
+
if (method !== "GET" && context.dryRun) {
|
|
944
|
+
context.output.writeData({
|
|
945
|
+
dryRun: true,
|
|
946
|
+
planned: { method, endpoint: plane === "data" ? `/api/v1/data${endpoint}` : `/api${endpoint}`, ...body !== undefined ? { body } : {} }
|
|
947
|
+
}, { summary: { text: `Dry run: would ${method} ${endpoint}. Nothing was sent.`, confidence: "high" } });
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
const requestOptions = method === "GET" ? {} : { retries: 0, ...options.slow ? { timeoutMs: LLM_WRITE_TIMEOUT_MS } : {} };
|
|
951
|
+
const data = plane === "data" ? await callDataAPI(endpoint, init, requestOptions) : await callControlAPI(endpoint, init, profileOptions(context), requestOptions);
|
|
952
|
+
context.output.writeData(data ?? { ok: true });
|
|
953
|
+
}
|
|
954
|
+
var HEALTH = USER_HEALTH;
|
|
955
|
+
async function runUsersCommand(context) {
|
|
956
|
+
const flags = context.flags;
|
|
957
|
+
assertFlags("users", flags, specFlagNames(COMMAND_FLAG_SPECS.users));
|
|
958
|
+
if (context.positionals.length > 0) {
|
|
959
|
+
throw new CliInputError(`'moda users' takes no arguments. For one user use 'moda user <user_id>'.`);
|
|
960
|
+
}
|
|
961
|
+
if (flags.limit !== undefined && flags["page-size"] !== undefined && flags.limit !== flags["page-size"]) {
|
|
962
|
+
throw new CliInputError(`Got --limit=${flags.limit} and --page-size=${flags["page-size"]}; they are the same flag for 'moda users', so nothing was sent.`, "Pass only one of --limit / --page-size.", "flag_conflict");
|
|
963
|
+
}
|
|
964
|
+
requireKey(context);
|
|
965
|
+
const tenantId = requireTenant(context);
|
|
966
|
+
const query = new URLSearchParams(compact({
|
|
967
|
+
window: oneOf("window", flags.window, WINDOWS),
|
|
968
|
+
page: flags.page,
|
|
969
|
+
pageSize: flags["page-size"] ?? flags.limit,
|
|
970
|
+
search: flags.search,
|
|
971
|
+
cohort: oneOf("cohort", flags.cohort, USER_COHORTS),
|
|
972
|
+
sortBy: oneOf("sort", flags.sort, USER_SORTS),
|
|
973
|
+
sortOrder: oneOf("order", flags.order, ["asc", "desc"]),
|
|
974
|
+
healthStatus: csv(flags["health-status"])?.map((value) => oneOf("health-status", value, HEALTH)).join(","),
|
|
975
|
+
minHealthScore: flags["min-health"],
|
|
976
|
+
maxHealthScore: flags["max-health"],
|
|
977
|
+
minConversations: flags["min-conversations"],
|
|
978
|
+
minToolFailures: flags["min-tool-failures"]
|
|
979
|
+
}));
|
|
980
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
981
|
+
await send(context, "control", "GET", `/tenants/${encodeURIComponent(tenantId)}/users${suffix}`);
|
|
982
|
+
return { exitCode: 0 };
|
|
983
|
+
}
|
|
984
|
+
async function runUserCommand(context) {
|
|
985
|
+
const flags = context.flags;
|
|
986
|
+
assertFlags("user", flags, specFlagNames(COMMAND_FLAG_SPECS.user));
|
|
987
|
+
assertPositionals("user", context.positionals, 1, "moda user <user_id> [--window=24h|7d|30d|90d]");
|
|
988
|
+
const userId = context.positionals[0];
|
|
989
|
+
if (!userId)
|
|
990
|
+
throw new CliInputError("<user_id> is required.", "Usage: moda user <user_id> [--window=24h|7d|30d|90d]");
|
|
991
|
+
requireKey(context);
|
|
992
|
+
const tenantId = requireTenant(context);
|
|
993
|
+
const query = new URLSearchParams(compact({
|
|
994
|
+
window: oneOf("window", flags.window, WINDOWS),
|
|
995
|
+
activityCursor: flags["activity-cursor"]
|
|
996
|
+
}));
|
|
997
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
998
|
+
await send(context, "control", "GET", `/tenants/${encodeURIComponent(tenantId)}/users/${encodeURIComponent(userId)}${suffix}`);
|
|
999
|
+
return { exitCode: 0 };
|
|
1000
|
+
}
|
|
1001
|
+
async function runProblemReopenCommand(context) {
|
|
1002
|
+
assertFlags("problem-reopen", context.flags, specFlagNames(COMMAND_FLAG_SPECS["problem-reopen"]));
|
|
1003
|
+
assertPositionals("problem-reopen", context.positionals, 1, "moda problem-reopen <problem_id> [--reason=TEXT]");
|
|
1004
|
+
const problemId = context.positionals[0];
|
|
1005
|
+
const usage = "Usage: moda problem-reopen <problem_id> [--reason=TEXT]";
|
|
1006
|
+
if (!problemId || !UUID_RE2.test(problemId))
|
|
1007
|
+
throw new CliInputError("<problem_id> (UUID) is required.", usage);
|
|
1008
|
+
const reason = context.flags.reason;
|
|
1009
|
+
if (reason !== undefined && reason.length > 500)
|
|
1010
|
+
throw new CliInputError("--reason must be 500 characters or fewer.");
|
|
1011
|
+
requireKey(context);
|
|
1012
|
+
await send(context, "data", "POST", `/problems/${problemId}/reopen`, compact({ reason }));
|
|
1013
|
+
return { exitCode: 0 };
|
|
1014
|
+
}
|
|
1015
|
+
var SIGNAL_FIELD_FLAGS = [
|
|
1016
|
+
"name",
|
|
1017
|
+
"description",
|
|
1018
|
+
"pass-criteria",
|
|
1019
|
+
"fail-criteria",
|
|
1020
|
+
"polarity",
|
|
1021
|
+
"locus",
|
|
1022
|
+
"tool-names",
|
|
1023
|
+
"keyword-hints",
|
|
1024
|
+
"use-case-id",
|
|
1025
|
+
"message-types",
|
|
1026
|
+
"note",
|
|
1027
|
+
"file"
|
|
1028
|
+
];
|
|
1029
|
+
function signalBody(flags, cwd) {
|
|
1030
|
+
const fromFile = readBodyFile(flags, cwd);
|
|
1031
|
+
const scope = compact({
|
|
1032
|
+
toolNames: csv(flags["tool-names"]),
|
|
1033
|
+
keywordHints: csv(flags["keyword-hints"]),
|
|
1034
|
+
useCaseId: flags["use-case-id"],
|
|
1035
|
+
messageTypes: csv(flags["message-types"])
|
|
1036
|
+
});
|
|
1037
|
+
return {
|
|
1038
|
+
...fromFile,
|
|
1039
|
+
...compact({
|
|
1040
|
+
name: flags.name,
|
|
1041
|
+
description: flags.description,
|
|
1042
|
+
passCriteria: flags["pass-criteria"],
|
|
1043
|
+
failCriteria: flags["fail-criteria"],
|
|
1044
|
+
polarity: oneOf("polarity", flags.polarity, ["violation", "expectation"]),
|
|
1045
|
+
locus: oneOf("locus", flags.locus, ["agent", "tool_output", "user_input"]),
|
|
1046
|
+
note: flags.note
|
|
1047
|
+
}),
|
|
1048
|
+
...Object.keys(scope).length > 0 ? { scope: { ...fromFile.scope ?? {}, ...scope } } : {}
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
var SIGNAL_USAGE = [
|
|
1052
|
+
"moda signals list custom signals",
|
|
1053
|
+
'moda signals draft --note="…" AI-draft criteria from a description',
|
|
1054
|
+
"moda signals create --name --description --pass-criteria --fail-criteria [--polarity --locus --tool-names=a,b] [--file=body.json]",
|
|
1055
|
+
"moda signals update <id> [same flags] edit a signal",
|
|
1056
|
+
"moda signals activate|pause|delete <id> (delete: drafts only; archive is dashboard-only)",
|
|
1057
|
+
"moda signals examples <id> add|update|remove --conversation-id=ID [--label=fits|does_not_fit --note --msg-index]",
|
|
1058
|
+
'moda signals refine <id> --request="…" propose a criteria edit',
|
|
1059
|
+
"moda signals revise <id> --conversation-ids=a,b re-derive criteria from labelled traces",
|
|
1060
|
+
"moda signals proposals <id> | accept <id> <proposal_id> | reject <id> <proposal_id>",
|
|
1061
|
+
"moda signals search <id> [--mode=wizard|full] | search-status <id> <search_id> | search-stop <id> <search_id>",
|
|
1062
|
+
"moda signals preview --file=detector.json [--conversation-ids=a,b --max-examples=N]",
|
|
1063
|
+
"All write subcommands accept --dry-run (prints the request, sends nothing)."
|
|
1064
|
+
].join(`
|
|
1065
|
+
`);
|
|
1066
|
+
async function runSignalsManagementCommand(sub, context) {
|
|
1067
|
+
const flags = context.flags;
|
|
1068
|
+
const args = context.positionals.slice(1);
|
|
1069
|
+
const arity = {
|
|
1070
|
+
draft: 0,
|
|
1071
|
+
create: 0,
|
|
1072
|
+
preview: 0,
|
|
1073
|
+
update: 1,
|
|
1074
|
+
activate: 1,
|
|
1075
|
+
pause: 1,
|
|
1076
|
+
delete: 1,
|
|
1077
|
+
examples: 2,
|
|
1078
|
+
refine: 1,
|
|
1079
|
+
revise: 1,
|
|
1080
|
+
proposals: 1,
|
|
1081
|
+
accept: 2,
|
|
1082
|
+
reject: 2,
|
|
1083
|
+
search: 1,
|
|
1084
|
+
"search-status": 2,
|
|
1085
|
+
"search-stop": 2
|
|
1086
|
+
};
|
|
1087
|
+
if (sub in arity)
|
|
1088
|
+
assertPositionals(`signals ${sub}`, args, arity[sub], SIGNAL_USAGE.split(`
|
|
1089
|
+
`)[0]);
|
|
1090
|
+
const base = () => `/tenants/${encodeURIComponent(requireTenant(context))}/custom-signals`;
|
|
1091
|
+
const id = (index = 0, what = "a signal id") => {
|
|
1092
|
+
const value = args[index];
|
|
1093
|
+
if (!value)
|
|
1094
|
+
throw new CliInputError(`moda signals ${sub} needs ${what}.`, `Usage:
|
|
1095
|
+
${SIGNAL_USAGE}`);
|
|
1096
|
+
return encodeURIComponent(value);
|
|
1097
|
+
};
|
|
1098
|
+
const start = () => requireKey(context);
|
|
1099
|
+
switch (sub) {
|
|
1100
|
+
case "draft": {
|
|
1101
|
+
assertFlags("signals draft", flags, ["note", "file", "dry-run"]);
|
|
1102
|
+
const body = { ...readBodyFile(flags, context.cwd), ...compact({ note: flags.note }) };
|
|
1103
|
+
if (!body.note)
|
|
1104
|
+
throw new CliInputError("--note is required (describe what the signal should catch).");
|
|
1105
|
+
start();
|
|
1106
|
+
await send(context, "control", "POST", `${base()}/draft`, body, { slow: true });
|
|
1107
|
+
return { exitCode: 0 };
|
|
1108
|
+
}
|
|
1109
|
+
case "create": {
|
|
1110
|
+
assertFlags("signals create", flags, [...SIGNAL_FIELD_FLAGS, "dry-run"]);
|
|
1111
|
+
const body = signalBody(flags, context.cwd);
|
|
1112
|
+
const missing = ["name", "description", "passCriteria", "failCriteria"].filter((field) => !body[field]);
|
|
1113
|
+
if (missing.length > 0) {
|
|
1114
|
+
throw new CliInputError(`Missing ${missing.join(", ")} for signals create.`, 'Pass --name --description --pass-criteria --fail-criteria, or a --file with those fields. Tip: `moda signals draft --note="…"` drafts them.');
|
|
1115
|
+
}
|
|
1116
|
+
start();
|
|
1117
|
+
await send(context, "control", "POST", base(), body);
|
|
1118
|
+
return { exitCode: 0 };
|
|
1119
|
+
}
|
|
1120
|
+
case "update": {
|
|
1121
|
+
assertFlags("signals update", flags, [...SIGNAL_FIELD_FLAGS, "dry-run"]);
|
|
1122
|
+
const signalId = id();
|
|
1123
|
+
const body = signalBody(flags, context.cwd);
|
|
1124
|
+
if (Object.keys(body).length === 0)
|
|
1125
|
+
throw new CliInputError("Nothing to update: pass at least one field flag or --file.");
|
|
1126
|
+
start();
|
|
1127
|
+
await send(context, "control", "PATCH", `${base()}/${signalId}`, body);
|
|
1128
|
+
return { exitCode: 0 };
|
|
1129
|
+
}
|
|
1130
|
+
case "activate":
|
|
1131
|
+
case "pause": {
|
|
1132
|
+
assertFlags(`signals ${sub}`, flags, ["dry-run"]);
|
|
1133
|
+
const signalId = id();
|
|
1134
|
+
start();
|
|
1135
|
+
await send(context, "control", "POST", `${base()}/${signalId}/${sub}`);
|
|
1136
|
+
return { exitCode: 0 };
|
|
1137
|
+
}
|
|
1138
|
+
case "delete": {
|
|
1139
|
+
assertFlags("signals delete", flags, ["dry-run"]);
|
|
1140
|
+
const signalId = id();
|
|
1141
|
+
start();
|
|
1142
|
+
await send(context, "control", "DELETE", `${base()}/${signalId}`);
|
|
1143
|
+
return { exitCode: 0 };
|
|
1144
|
+
}
|
|
1145
|
+
case "examples": {
|
|
1146
|
+
const signalId = id();
|
|
1147
|
+
const action = args[1];
|
|
1148
|
+
const conversationId = flags["conversation-id"];
|
|
1149
|
+
if (!action || !["add", "update", "remove"].includes(action)) {
|
|
1150
|
+
throw new CliInputError("Usage: moda signals examples <id> add|update|remove --conversation-id=ID …");
|
|
1151
|
+
}
|
|
1152
|
+
if (!conversationId)
|
|
1153
|
+
throw new CliInputError("--conversation-id is required.");
|
|
1154
|
+
if (action === "add") {
|
|
1155
|
+
assertFlags("signals examples add", flags, ["conversation-id", "label", "note", "msg-index", "msg-dedup-token", "criteria", "verdict", "dry-run"]);
|
|
1156
|
+
const label = oneOf("label", flags.label, ["fits", "does_not_fit"]);
|
|
1157
|
+
if (!label)
|
|
1158
|
+
throw new CliInputError("--label=fits|does_not_fit is required.");
|
|
1159
|
+
start();
|
|
1160
|
+
await send(context, "control", "POST", `${base()}/${signalId}/examples`, compact({
|
|
1161
|
+
conversationId,
|
|
1162
|
+
label,
|
|
1163
|
+
source: "api",
|
|
1164
|
+
note: flags.note,
|
|
1165
|
+
msgIndex: flags["msg-index"] !== undefined ? Number(flags["msg-index"]) : undefined,
|
|
1166
|
+
msgDedupToken: flags["msg-dedup-token"],
|
|
1167
|
+
criteria: oneOf("criteria", flags.criteria, ["pass", "fail"]),
|
|
1168
|
+
verdict: oneOf("verdict", flags.verdict, ["accept", "reject"])
|
|
1169
|
+
}));
|
|
1170
|
+
} else if (action === "update") {
|
|
1171
|
+
assertFlags("signals examples update", flags, ["conversation-id", "label", "note", "blurb", "dry-run"]);
|
|
1172
|
+
const body = compact({ label: oneOf("label", flags.label, ["fits", "does_not_fit"]), note: flags.note, blurb: flags.blurb });
|
|
1173
|
+
if (Object.keys(body).length === 0)
|
|
1174
|
+
throw new CliInputError("Nothing to update: pass --label, --note, or --blurb.");
|
|
1175
|
+
start();
|
|
1176
|
+
await send(context, "control", "PATCH", `${base()}/${signalId}/examples/${encodeURIComponent(conversationId)}`, body);
|
|
1177
|
+
} else {
|
|
1178
|
+
assertFlags("signals examples remove", flags, ["conversation-id", "dry-run"]);
|
|
1179
|
+
start();
|
|
1180
|
+
await send(context, "control", "DELETE", `${base()}/${signalId}/examples/${encodeURIComponent(conversationId)}`);
|
|
1181
|
+
}
|
|
1182
|
+
return { exitCode: 0 };
|
|
1183
|
+
}
|
|
1184
|
+
case "refine": {
|
|
1185
|
+
assertFlags("signals refine", flags, ["request", "dry-run"]);
|
|
1186
|
+
const signalId = id();
|
|
1187
|
+
const request = flags.request;
|
|
1188
|
+
if (!request || request.length > 2000)
|
|
1189
|
+
throw new CliInputError("--request (1–2000 characters) is required.");
|
|
1190
|
+
start();
|
|
1191
|
+
await send(context, "control", "POST", `${base()}/${signalId}/refine`, { request }, { slow: true });
|
|
1192
|
+
return { exitCode: 0 };
|
|
1193
|
+
}
|
|
1194
|
+
case "revise": {
|
|
1195
|
+
assertFlags("signals revise", flags, ["conversation-ids", "dry-run"]);
|
|
1196
|
+
const signalId = id();
|
|
1197
|
+
const conversationIds = csv(flags["conversation-ids"]);
|
|
1198
|
+
if (!conversationIds)
|
|
1199
|
+
throw new CliInputError("--conversation-ids=a,b is required.");
|
|
1200
|
+
start();
|
|
1201
|
+
await send(context, "control", "POST", `${base()}/${signalId}/revise`, { conversationIds }, { slow: true });
|
|
1202
|
+
return { exitCode: 0 };
|
|
1203
|
+
}
|
|
1204
|
+
case "proposals": {
|
|
1205
|
+
assertFlags("signals proposals", flags, []);
|
|
1206
|
+
const signalId = id();
|
|
1207
|
+
start();
|
|
1208
|
+
await send(context, "control", "GET", `${base()}/${signalId}/proposals`);
|
|
1209
|
+
return { exitCode: 0 };
|
|
1210
|
+
}
|
|
1211
|
+
case "accept":
|
|
1212
|
+
case "reject": {
|
|
1213
|
+
assertFlags(`signals ${sub}`, flags, ["dry-run"]);
|
|
1214
|
+
const signalId = id();
|
|
1215
|
+
const proposalId = id(1, "a proposal id (see `moda signals proposals <id>`)");
|
|
1216
|
+
start();
|
|
1217
|
+
await send(context, "control", "POST", `${base()}/${signalId}/proposals/${proposalId}/${sub}`);
|
|
1218
|
+
return { exitCode: 0 };
|
|
1219
|
+
}
|
|
1220
|
+
case "search": {
|
|
1221
|
+
assertFlags("signals search", flags, ["mode", "exclude-conversation-ids", "dry-run"]);
|
|
1222
|
+
const signalId = id();
|
|
1223
|
+
start();
|
|
1224
|
+
await send(context, "control", "POST", `${base()}/${signalId}/search`, compact({
|
|
1225
|
+
mode: oneOf("mode", flags.mode, ["wizard", "full"]),
|
|
1226
|
+
excludeConversationIds: csv(flags["exclude-conversation-ids"])
|
|
1227
|
+
}), { slow: true });
|
|
1228
|
+
return { exitCode: 0 };
|
|
1229
|
+
}
|
|
1230
|
+
case "search-status":
|
|
1231
|
+
case "search-stop": {
|
|
1232
|
+
assertFlags(`signals ${sub}`, flags, sub === "search-stop" ? ["dry-run"] : []);
|
|
1233
|
+
const signalId = id();
|
|
1234
|
+
const searchId = id(1, "a search id (returned by `moda signals search <id>`)");
|
|
1235
|
+
start();
|
|
1236
|
+
await send(context, "control", sub === "search-stop" ? "POST" : "GET", `${base()}/${signalId}/searches/${searchId}${sub === "search-stop" ? "/stop" : ""}`);
|
|
1237
|
+
return { exitCode: 0 };
|
|
1238
|
+
}
|
|
1239
|
+
case "preview": {
|
|
1240
|
+
assertFlags("signals preview", flags, ["file", "conversation-ids", "max-examples"]);
|
|
1241
|
+
const body = readBodyFile(flags, context.cwd);
|
|
1242
|
+
if (!body.detector)
|
|
1243
|
+
throw new CliInputError('--file must contain {"detector": {...}} (optionally scope).');
|
|
1244
|
+
start();
|
|
1245
|
+
try {
|
|
1246
|
+
await send(context, "control", "POST", `${base()}/detector-preview`, {
|
|
1247
|
+
...body,
|
|
1248
|
+
...compact({
|
|
1249
|
+
conversationIds: csv(flags["conversation-ids"]),
|
|
1250
|
+
maxExamples: flags["max-examples"] !== undefined ? Number(flags["max-examples"]) : undefined
|
|
1251
|
+
})
|
|
1252
|
+
});
|
|
1253
|
+
} catch (error) {
|
|
1254
|
+
if (isTenantFeatureNotFound(error)) {
|
|
1255
|
+
throw new CliAccessError("The signal builder is not enabled for this tenant, so `moda signals preview` is unavailable (the API answered 404).", { guidance: "Ask Moda to enable the signal-builder feature for this tenant. Other `moda signals` subcommands still work." });
|
|
1256
|
+
}
|
|
1257
|
+
throw error;
|
|
1258
|
+
}
|
|
1259
|
+
return { exitCode: 0 };
|
|
1260
|
+
}
|
|
1261
|
+
default:
|
|
1262
|
+
throw unknownSignalsSubcommandError(sub, args);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
function isTenantFeatureNotFound(error) {
|
|
1266
|
+
if (!(error instanceof ApiError) || error.statusCode !== 404)
|
|
1267
|
+
return false;
|
|
1268
|
+
try {
|
|
1269
|
+
const body = JSON.parse(error.responseBody);
|
|
1270
|
+
return body.message === "Not Found" && body.error === undefined;
|
|
1271
|
+
} catch {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
var SIGNALS_MANAGEMENT_SUBCOMMANDS = new Set([
|
|
1276
|
+
"draft",
|
|
1277
|
+
"create",
|
|
1278
|
+
"update",
|
|
1279
|
+
"activate",
|
|
1280
|
+
"pause",
|
|
1281
|
+
"delete",
|
|
1282
|
+
"examples",
|
|
1283
|
+
"refine",
|
|
1284
|
+
"revise",
|
|
1285
|
+
"proposals",
|
|
1286
|
+
"accept",
|
|
1287
|
+
"reject",
|
|
1288
|
+
"search",
|
|
1289
|
+
"search-status",
|
|
1290
|
+
"search-stop",
|
|
1291
|
+
"preview"
|
|
1292
|
+
]);
|
|
1293
|
+
function unknownSignalsSubcommandError(sub, args = []) {
|
|
1294
|
+
if (sub === "archive") {
|
|
1295
|
+
const id = args[0] ?? "<signal_id>";
|
|
1296
|
+
return new CliInputError(`Unknown signals subcommand 'archive': archiving a signal is dashboard-only, so nothing was sent.`, `To stop a signal from running, pause it: moda signals pause ${id}. Drafts can be deleted with moda signals delete ${id}.`);
|
|
1297
|
+
}
|
|
1298
|
+
return new CliInputError(`Unknown signals subcommand '${sub}', so nothing was sent. Valid subcommands: ${[...SIGNALS_MANAGEMENT_SUBCOMMANDS].join(", ")}.`, `Bare 'moda signals' lists custom signals; 'moda signal <signal_id>' opens one. Usage:
|
|
1299
|
+
${SIGNAL_USAGE}`);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
export { ProblemsRemainderSchema, UUID_RE, ProblemImpactSchema, PROBLEM_FEEDBACK_ACTIONS, ProblemFeedbackSchema, ProblemLinearFileSchema, SIGNAL_LABELS, SignalLabelSchema, DETECTION_VERDICTS, DetectionReviewSchema, FALSE_POSITIVE_KINDS, FalsePositiveSchema, StepScoresSchema, AuditSchema, FEEDBACK_USAGE, isFeedbackRefFlag, buildFeedbackRequest, COMMAND_FLAG_SPECS, flagSpecFor, kebabFlag, schemaShape, specFlagKeys, specFlagNames, describeSpecFlags, closestMatches, renderFlagHelp, subcommandHelpFlags, manifestFlagFields, requireTenant, assertFlags, assertPositionals, runUsersCommand, runUserCommand, runProblemReopenCommand, runSignalsManagementCommand, SIGNALS_MANAGEMENT_SUBCOMMANDS, unknownSignalsSubcommandError };
|