@moda-ai/cli 1.55.10 → 1.55.11

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.
@@ -0,0 +1,496 @@
1
+ import {
2
+ ApiError,
3
+ callControlAPI,
4
+ callDataAPI
5
+ } from "./cli-tmqd1f0m.js";
6
+ import {
7
+ isPlatformApiKey,
8
+ setPlatformTenantOverride
9
+ } from "./cli-m16taf4z.js";
10
+ import {
11
+ CliAccessError,
12
+ CliInputError,
13
+ embeddedTenantIdFromApiKey,
14
+ resolveApiKey,
15
+ resolveApiTenantId
16
+ } from "./cli-429zt7g4.js";
17
+
18
+ // src/parity-commands.ts
19
+ import { readFileSync } from "node:fs";
20
+ import { resolve } from "node:path";
21
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
22
+ var WINDOWS = ["24h", "7d", "30d", "90d"];
23
+ function profileOptions(context) {
24
+ return { profile: context.profile, env: context.env };
25
+ }
26
+ function requireKey(context) {
27
+ if (!resolveApiKey(profileOptions(context))) {
28
+ 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.");
29
+ }
30
+ }
31
+ function requireTenant(context) {
32
+ const key = resolveApiKey(profileOptions(context));
33
+ const keyTenant = key ? embeddedTenantIdFromApiKey(key) : undefined;
34
+ const flagTenant = context.flags["tenant-id"];
35
+ if (flagTenant && keyTenant && flagTenant !== keyTenant) {
36
+ 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.");
37
+ }
38
+ const tenantId = flagTenant || resolveApiTenantId(profileOptions(context));
39
+ if (!tenantId) {
40
+ 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>.");
41
+ }
42
+ if (isPlatformApiKey(key))
43
+ setPlatformTenantOverride(tenantId);
44
+ return tenantId;
45
+ }
46
+ function assertFlags(command, flags, allowed) {
47
+ if (command.startsWith("signals "))
48
+ allowed = [...allowed, "tenant-id"];
49
+ const globals = [
50
+ "agent",
51
+ "json",
52
+ "human",
53
+ "prose",
54
+ "quiet",
55
+ "verbose",
56
+ "yes",
57
+ "profile",
58
+ "progress",
59
+ "format",
60
+ "no-telemetry",
61
+ "no-update-check",
62
+ "help",
63
+ "version",
64
+ "json-schemas",
65
+ "stream",
66
+ "no-stream",
67
+ "tui",
68
+ "no-tui"
69
+ ];
70
+ const known = new Set([...allowed, ...globals]);
71
+ const unknown = Object.keys(flags).filter((flag) => !known.has(flag));
72
+ if (unknown.includes("dry-run")) {
73
+ throw new CliInputError(`'moda ${command}' has no dry-run mode, so nothing was sent.`, "Remove --dry-run to run it for real.");
74
+ }
75
+ if (unknown.length > 0) {
76
+ throw new CliInputError(`Unknown flag${unknown.length > 1 ? "s" : ""} ${unknown.map((flag) => `--${flag}`).join(", ")} for 'moda ${command}'.`, `Valid flags: ${allowed.map((flag) => `--${flag}`).join(", ") || "(none)"}.`);
77
+ }
78
+ }
79
+ function assertPositionals(command, positionals, max, usage) {
80
+ if (positionals.length > max) {
81
+ throw new CliInputError(`Unexpected argument${positionals.length - max > 1 ? "s" : ""} '${positionals.slice(max).join(" ")}' for 'moda ${command}', so nothing was sent.`, `Usage: ${usage}`);
82
+ }
83
+ }
84
+ function oneOf(name, value, allowed) {
85
+ if (value === undefined)
86
+ return;
87
+ if (!allowed.includes(value)) {
88
+ throw new CliInputError(`--${name} must be one of ${allowed.join(", ")} (got '${value}').`);
89
+ }
90
+ return value;
91
+ }
92
+ function csv(value) {
93
+ if (value === undefined)
94
+ return;
95
+ const items = value.split(",").map((item) => item.trim()).filter(Boolean);
96
+ return items.length > 0 ? items : undefined;
97
+ }
98
+ function readBodyFile(flags, cwd) {
99
+ if (!flags.file)
100
+ return {};
101
+ if (flags.file === "true")
102
+ throw new CliInputError("Missing path for --file.");
103
+ let parsed;
104
+ try {
105
+ parsed = JSON.parse(readFileSync(resolve(cwd, flags.file), "utf8"));
106
+ } catch (error) {
107
+ throw new CliInputError(`Could not read --file ${flags.file}: ${error instanceof Error ? error.message : String(error)}`);
108
+ }
109
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
110
+ throw new CliInputError("--file must contain a JSON object (the request body).");
111
+ }
112
+ return parsed;
113
+ }
114
+ function compact(value) {
115
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
116
+ }
117
+ var LLM_WRITE_TIMEOUT_MS = 180000;
118
+ async function send(context, plane, method, endpoint, body, options = {}) {
119
+ const init = { method, ...body !== undefined ? { body: JSON.stringify(body) } : {} };
120
+ if (method !== "GET" && context.dryRun) {
121
+ context.output.writeData({
122
+ dryRun: true,
123
+ planned: { method, endpoint: plane === "data" ? `/api/v1/data${endpoint}` : `/api${endpoint}`, ...body !== undefined ? { body } : {} }
124
+ }, { summary: { text: `Dry run: would ${method} ${endpoint}. Nothing was sent.`, confidence: "high" } });
125
+ return;
126
+ }
127
+ const requestOptions = method === "GET" ? {} : { retries: 0, ...options.slow ? { timeoutMs: LLM_WRITE_TIMEOUT_MS } : {} };
128
+ const data = plane === "data" ? await callDataAPI(endpoint, init, requestOptions) : await callControlAPI(endpoint, init, profileOptions(context), requestOptions);
129
+ context.output.writeData(data ?? { ok: true });
130
+ }
131
+ var USER_COHORTS = ["all", "power_users", "heavy_tool_users", "frustrated", "at_risk", "new_this_week", "silent_sufferers"];
132
+ var USER_SORTS = ["last_seen", "health_score", "conversation_count", "frustration_max"];
133
+ var HEALTH = ["healthy", "at_risk", "frustrated"];
134
+ async function runUsersCommand(context) {
135
+ const flags = context.flags;
136
+ assertFlags("users", flags, [
137
+ "window",
138
+ "page",
139
+ "page-size",
140
+ "limit",
141
+ "search",
142
+ "cohort",
143
+ "sort",
144
+ "order",
145
+ "health-status",
146
+ "min-health",
147
+ "max-health",
148
+ "min-conversations",
149
+ "min-tool-failures",
150
+ "tenant-id"
151
+ ]);
152
+ if (context.positionals.length > 0) {
153
+ throw new CliInputError(`'moda users' takes no arguments. For one user use 'moda user <user_id>'.`);
154
+ }
155
+ if (flags.limit !== undefined && flags["page-size"] !== undefined && flags.limit !== flags["page-size"]) {
156
+ 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");
157
+ }
158
+ requireKey(context);
159
+ const tenantId = requireTenant(context);
160
+ const query = new URLSearchParams(compact({
161
+ window: oneOf("window", flags.window, WINDOWS),
162
+ page: flags.page,
163
+ pageSize: flags["page-size"] ?? flags.limit,
164
+ search: flags.search,
165
+ cohort: oneOf("cohort", flags.cohort, USER_COHORTS),
166
+ sortBy: oneOf("sort", flags.sort, USER_SORTS),
167
+ sortOrder: oneOf("order", flags.order, ["asc", "desc"]),
168
+ healthStatus: csv(flags["health-status"])?.map((value) => oneOf("health-status", value, HEALTH)).join(","),
169
+ minHealthScore: flags["min-health"],
170
+ maxHealthScore: flags["max-health"],
171
+ minConversations: flags["min-conversations"],
172
+ minToolFailures: flags["min-tool-failures"]
173
+ }));
174
+ const suffix = query.toString() ? `?${query.toString()}` : "";
175
+ await send(context, "control", "GET", `/tenants/${encodeURIComponent(tenantId)}/users${suffix}`);
176
+ return { exitCode: 0 };
177
+ }
178
+ async function runUserCommand(context) {
179
+ const flags = context.flags;
180
+ assertFlags("user", flags, ["window", "activity-cursor", "tenant-id"]);
181
+ assertPositionals("user", context.positionals, 1, "moda user <user_id> [--window=24h|7d|30d|90d]");
182
+ const userId = context.positionals[0];
183
+ if (!userId)
184
+ throw new CliInputError("<user_id> is required.", "Usage: moda user <user_id> [--window=24h|7d|30d|90d]");
185
+ requireKey(context);
186
+ const tenantId = requireTenant(context);
187
+ const query = new URLSearchParams(compact({
188
+ window: oneOf("window", flags.window, WINDOWS),
189
+ activityCursor: flags["activity-cursor"]
190
+ }));
191
+ const suffix = query.toString() ? `?${query.toString()}` : "";
192
+ await send(context, "control", "GET", `/tenants/${encodeURIComponent(tenantId)}/users/${encodeURIComponent(userId)}${suffix}`);
193
+ return { exitCode: 0 };
194
+ }
195
+ async function runProblemReopenCommand(context) {
196
+ assertFlags("problem-reopen", context.flags, ["reason", "dry-run"]);
197
+ assertPositionals("problem-reopen", context.positionals, 1, "moda problem-reopen <problem_id> [--reason=TEXT]");
198
+ const problemId = context.positionals[0];
199
+ const usage = "Usage: moda problem-reopen <problem_id> [--reason=TEXT]";
200
+ if (!problemId || !UUID_RE.test(problemId))
201
+ throw new CliInputError("<problem_id> (UUID) is required.", usage);
202
+ const reason = context.flags.reason;
203
+ if (reason !== undefined && reason.length > 500)
204
+ throw new CliInputError("--reason must be 500 characters or fewer.");
205
+ requireKey(context);
206
+ await send(context, "data", "POST", `/problems/${problemId}/reopen`, compact({ reason }));
207
+ return { exitCode: 0 };
208
+ }
209
+ var SIGNAL_FIELD_FLAGS = [
210
+ "name",
211
+ "description",
212
+ "pass-criteria",
213
+ "fail-criteria",
214
+ "polarity",
215
+ "locus",
216
+ "tool-names",
217
+ "keyword-hints",
218
+ "use-case-id",
219
+ "message-types",
220
+ "note",
221
+ "file"
222
+ ];
223
+ function signalBody(flags, cwd) {
224
+ const fromFile = readBodyFile(flags, cwd);
225
+ const scope = compact({
226
+ toolNames: csv(flags["tool-names"]),
227
+ keywordHints: csv(flags["keyword-hints"]),
228
+ useCaseId: flags["use-case-id"],
229
+ messageTypes: csv(flags["message-types"])
230
+ });
231
+ return {
232
+ ...fromFile,
233
+ ...compact({
234
+ name: flags.name,
235
+ description: flags.description,
236
+ passCriteria: flags["pass-criteria"],
237
+ failCriteria: flags["fail-criteria"],
238
+ polarity: oneOf("polarity", flags.polarity, ["violation", "expectation"]),
239
+ locus: oneOf("locus", flags.locus, ["agent", "tool_output", "user_input"]),
240
+ note: flags.note
241
+ }),
242
+ ...Object.keys(scope).length > 0 ? { scope: { ...fromFile.scope ?? {}, ...scope } } : {}
243
+ };
244
+ }
245
+ var SIGNAL_USAGE = [
246
+ "moda signals list custom signals",
247
+ 'moda signals draft --note="…" AI-draft criteria from a description',
248
+ "moda signals create --name --description --pass-criteria --fail-criteria [--polarity --locus --tool-names=a,b] [--file=body.json]",
249
+ "moda signals update <id> [same flags] edit a signal",
250
+ "moda signals activate|pause|delete <id> (delete: drafts only; archive is dashboard-only)",
251
+ "moda signals examples <id> add|update|remove --conversation-id=ID [--label=fits|does_not_fit --note --msg-index]",
252
+ 'moda signals refine <id> --request="…" propose a criteria edit',
253
+ "moda signals revise <id> --conversation-ids=a,b re-derive criteria from labelled traces",
254
+ "moda signals proposals <id> | accept <id> <proposal_id> | reject <id> <proposal_id>",
255
+ "moda signals search <id> [--mode=wizard|full] | search-status <id> <search_id> | search-stop <id> <search_id>",
256
+ "moda signals preview --file=detector.json [--conversation-ids=a,b --max-examples=N]",
257
+ "All write subcommands accept --dry-run (prints the request, sends nothing)."
258
+ ].join(`
259
+ `);
260
+ async function runSignalsManagementCommand(sub, context) {
261
+ const flags = context.flags;
262
+ const args = context.positionals.slice(1);
263
+ const arity = {
264
+ draft: 0,
265
+ create: 0,
266
+ preview: 0,
267
+ update: 1,
268
+ activate: 1,
269
+ pause: 1,
270
+ delete: 1,
271
+ examples: 2,
272
+ refine: 1,
273
+ revise: 1,
274
+ proposals: 1,
275
+ accept: 2,
276
+ reject: 2,
277
+ search: 1,
278
+ "search-status": 2,
279
+ "search-stop": 2
280
+ };
281
+ if (sub in arity)
282
+ assertPositionals(`signals ${sub}`, args, arity[sub], SIGNAL_USAGE.split(`
283
+ `)[0]);
284
+ const base = () => `/tenants/${encodeURIComponent(requireTenant(context))}/custom-signals`;
285
+ const id = (index = 0, what = "a signal id") => {
286
+ const value = args[index];
287
+ if (!value)
288
+ throw new CliInputError(`moda signals ${sub} needs ${what}.`, `Usage:
289
+ ${SIGNAL_USAGE}`);
290
+ return encodeURIComponent(value);
291
+ };
292
+ const start = () => requireKey(context);
293
+ switch (sub) {
294
+ case "draft": {
295
+ assertFlags("signals draft", flags, ["note", "file", "dry-run"]);
296
+ const body = { ...readBodyFile(flags, context.cwd), ...compact({ note: flags.note }) };
297
+ if (!body.note)
298
+ throw new CliInputError("--note is required (describe what the signal should catch).");
299
+ start();
300
+ await send(context, "control", "POST", `${base()}/draft`, body, { slow: true });
301
+ return { exitCode: 0 };
302
+ }
303
+ case "create": {
304
+ assertFlags("signals create", flags, [...SIGNAL_FIELD_FLAGS, "dry-run"]);
305
+ const body = signalBody(flags, context.cwd);
306
+ const missing = ["name", "description", "passCriteria", "failCriteria"].filter((field) => !body[field]);
307
+ if (missing.length > 0) {
308
+ 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.');
309
+ }
310
+ start();
311
+ await send(context, "control", "POST", base(), body);
312
+ return { exitCode: 0 };
313
+ }
314
+ case "update": {
315
+ assertFlags("signals update", flags, [...SIGNAL_FIELD_FLAGS, "dry-run"]);
316
+ const signalId = id();
317
+ const body = signalBody(flags, context.cwd);
318
+ if (Object.keys(body).length === 0)
319
+ throw new CliInputError("Nothing to update: pass at least one field flag or --file.");
320
+ start();
321
+ await send(context, "control", "PATCH", `${base()}/${signalId}`, body);
322
+ return { exitCode: 0 };
323
+ }
324
+ case "activate":
325
+ case "pause": {
326
+ assertFlags(`signals ${sub}`, flags, ["dry-run"]);
327
+ const signalId = id();
328
+ start();
329
+ await send(context, "control", "POST", `${base()}/${signalId}/${sub}`);
330
+ return { exitCode: 0 };
331
+ }
332
+ case "delete": {
333
+ assertFlags("signals delete", flags, ["dry-run"]);
334
+ const signalId = id();
335
+ start();
336
+ await send(context, "control", "DELETE", `${base()}/${signalId}`);
337
+ return { exitCode: 0 };
338
+ }
339
+ case "examples": {
340
+ const signalId = id();
341
+ const action = args[1];
342
+ const conversationId = flags["conversation-id"];
343
+ if (!action || !["add", "update", "remove"].includes(action)) {
344
+ throw new CliInputError("Usage: moda signals examples <id> add|update|remove --conversation-id=ID …");
345
+ }
346
+ if (!conversationId)
347
+ throw new CliInputError("--conversation-id is required.");
348
+ if (action === "add") {
349
+ assertFlags("signals examples add", flags, ["conversation-id", "label", "note", "msg-index", "msg-dedup-token", "criteria", "verdict", "dry-run"]);
350
+ const label = oneOf("label", flags.label, ["fits", "does_not_fit"]);
351
+ if (!label)
352
+ throw new CliInputError("--label=fits|does_not_fit is required.");
353
+ start();
354
+ await send(context, "control", "POST", `${base()}/${signalId}/examples`, compact({
355
+ conversationId,
356
+ label,
357
+ source: "api",
358
+ note: flags.note,
359
+ msgIndex: flags["msg-index"] !== undefined ? Number(flags["msg-index"]) : undefined,
360
+ msgDedupToken: flags["msg-dedup-token"],
361
+ criteria: oneOf("criteria", flags.criteria, ["pass", "fail"]),
362
+ verdict: oneOf("verdict", flags.verdict, ["accept", "reject"])
363
+ }));
364
+ } else if (action === "update") {
365
+ assertFlags("signals examples update", flags, ["conversation-id", "label", "note", "blurb", "dry-run"]);
366
+ const body = compact({ label: oneOf("label", flags.label, ["fits", "does_not_fit"]), note: flags.note, blurb: flags.blurb });
367
+ if (Object.keys(body).length === 0)
368
+ throw new CliInputError("Nothing to update: pass --label, --note, or --blurb.");
369
+ start();
370
+ await send(context, "control", "PATCH", `${base()}/${signalId}/examples/${encodeURIComponent(conversationId)}`, body);
371
+ } else {
372
+ assertFlags("signals examples remove", flags, ["conversation-id", "dry-run"]);
373
+ start();
374
+ await send(context, "control", "DELETE", `${base()}/${signalId}/examples/${encodeURIComponent(conversationId)}`);
375
+ }
376
+ return { exitCode: 0 };
377
+ }
378
+ case "refine": {
379
+ assertFlags("signals refine", flags, ["request", "dry-run"]);
380
+ const signalId = id();
381
+ const request = flags.request;
382
+ if (!request || request.length > 2000)
383
+ throw new CliInputError("--request (1–2000 characters) is required.");
384
+ start();
385
+ await send(context, "control", "POST", `${base()}/${signalId}/refine`, { request }, { slow: true });
386
+ return { exitCode: 0 };
387
+ }
388
+ case "revise": {
389
+ assertFlags("signals revise", flags, ["conversation-ids", "dry-run"]);
390
+ const signalId = id();
391
+ const conversationIds = csv(flags["conversation-ids"]);
392
+ if (!conversationIds)
393
+ throw new CliInputError("--conversation-ids=a,b is required.");
394
+ start();
395
+ await send(context, "control", "POST", `${base()}/${signalId}/revise`, { conversationIds }, { slow: true });
396
+ return { exitCode: 0 };
397
+ }
398
+ case "proposals": {
399
+ assertFlags("signals proposals", flags, []);
400
+ const signalId = id();
401
+ start();
402
+ await send(context, "control", "GET", `${base()}/${signalId}/proposals`);
403
+ return { exitCode: 0 };
404
+ }
405
+ case "accept":
406
+ case "reject": {
407
+ assertFlags(`signals ${sub}`, flags, ["dry-run"]);
408
+ const signalId = id();
409
+ const proposalId = id(1, "a proposal id (see `moda signals proposals <id>`)");
410
+ start();
411
+ await send(context, "control", "POST", `${base()}/${signalId}/proposals/${proposalId}/${sub}`);
412
+ return { exitCode: 0 };
413
+ }
414
+ case "search": {
415
+ assertFlags("signals search", flags, ["mode", "exclude-conversation-ids", "dry-run"]);
416
+ const signalId = id();
417
+ start();
418
+ await send(context, "control", "POST", `${base()}/${signalId}/search`, compact({
419
+ mode: oneOf("mode", flags.mode, ["wizard", "full"]),
420
+ excludeConversationIds: csv(flags["exclude-conversation-ids"])
421
+ }), { slow: true });
422
+ return { exitCode: 0 };
423
+ }
424
+ case "search-status":
425
+ case "search-stop": {
426
+ assertFlags(`signals ${sub}`, flags, sub === "search-stop" ? ["dry-run"] : []);
427
+ const signalId = id();
428
+ const searchId = id(1, "a search id (returned by `moda signals search <id>`)");
429
+ start();
430
+ await send(context, "control", sub === "search-stop" ? "POST" : "GET", `${base()}/${signalId}/searches/${searchId}${sub === "search-stop" ? "/stop" : ""}`);
431
+ return { exitCode: 0 };
432
+ }
433
+ case "preview": {
434
+ assertFlags("signals preview", flags, ["file", "conversation-ids", "max-examples"]);
435
+ const body = readBodyFile(flags, context.cwd);
436
+ if (!body.detector)
437
+ throw new CliInputError('--file must contain {"detector": {...}} (optionally scope).');
438
+ start();
439
+ try {
440
+ await send(context, "control", "POST", `${base()}/detector-preview`, {
441
+ ...body,
442
+ ...compact({
443
+ conversationIds: csv(flags["conversation-ids"]),
444
+ maxExamples: flags["max-examples"] !== undefined ? Number(flags["max-examples"]) : undefined
445
+ })
446
+ });
447
+ } catch (error) {
448
+ if (isTenantFeatureNotFound(error)) {
449
+ 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." });
450
+ }
451
+ throw error;
452
+ }
453
+ return { exitCode: 0 };
454
+ }
455
+ default:
456
+ throw unknownSignalsSubcommandError(sub, args);
457
+ }
458
+ }
459
+ function isTenantFeatureNotFound(error) {
460
+ if (!(error instanceof ApiError) || error.statusCode !== 404)
461
+ return false;
462
+ try {
463
+ const body = JSON.parse(error.responseBody);
464
+ return body.message === "Not Found" && body.error === undefined;
465
+ } catch {
466
+ return false;
467
+ }
468
+ }
469
+ var SIGNALS_MANAGEMENT_SUBCOMMANDS = new Set([
470
+ "draft",
471
+ "create",
472
+ "update",
473
+ "activate",
474
+ "pause",
475
+ "delete",
476
+ "examples",
477
+ "refine",
478
+ "revise",
479
+ "proposals",
480
+ "accept",
481
+ "reject",
482
+ "search",
483
+ "search-status",
484
+ "search-stop",
485
+ "preview"
486
+ ]);
487
+ function unknownSignalsSubcommandError(sub, args = []) {
488
+ if (sub === "archive") {
489
+ const id = args[0] ?? "<signal_id>";
490
+ 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}.`);
491
+ }
492
+ 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:
493
+ ${SIGNAL_USAGE}`);
494
+ }
495
+
496
+ export { requireTenant, assertFlags, assertPositionals, runUsersCommand, runUserCommand, runProblemReopenCommand, runSignalsManagementCommand, SIGNALS_MANAGEMENT_SUBCOMMANDS, unknownSignalsSubcommandError };
@@ -0,0 +1,70 @@
1
+ import {
2
+ createCliOutput,
3
+ outputModeFromFlags
4
+ } from "./cli-429zt7g4.js";
5
+
6
+ // src/command-context.ts
7
+ function flagEnabled(flags, key) {
8
+ return flags[key] === "true";
9
+ }
10
+ var BOOLEAN_TRUE_WORDS = new Set(["true", "yes", "1", "on"]);
11
+ var BOOLEAN_FALSE_WORDS = new Set(["false", "no", "0", "off"]);
12
+ function parseBooleanFlagValue(value) {
13
+ if (typeof value === "boolean")
14
+ return value;
15
+ if (typeof value === "number")
16
+ return value === 1 ? true : value === 0 ? false : undefined;
17
+ if (typeof value !== "string")
18
+ return;
19
+ const text = value.trim().toLowerCase();
20
+ if (BOOLEAN_TRUE_WORDS.has(text))
21
+ return true;
22
+ if (BOOLEAN_FALSE_WORDS.has(text))
23
+ return false;
24
+ return;
25
+ }
26
+ function stringOption(value) {
27
+ if (value === undefined || value === null)
28
+ return;
29
+ const text = String(value).trim();
30
+ return text.length > 0 ? text : undefined;
31
+ }
32
+ function createCommandContext(input, options = {}) {
33
+ const flags = options.commandFlags ?? input.flags;
34
+ const rawFlags = options.rawFlags ?? input.flags;
35
+ const outputMode = options.outputMode ?? outputModeFromFlags(flags);
36
+ const quiet = flagEnabled(flags, "quiet");
37
+ const interactive = outputMode === "human" && !quiet && !flagEnabled(flags, "yes") && process.stdin.isTTY === true && process.stderr.isTTY === true;
38
+ const telemetryMeta = {};
39
+ return {
40
+ command: options.commandName ?? input.command,
41
+ positional: input.positional,
42
+ positionals: input.positionals,
43
+ flags,
44
+ rawFlags,
45
+ ...input.bareFlags ? { bareFlags: input.bareFlags } : {},
46
+ startedAt: options.startedAt ?? Date.now(),
47
+ cwd: options.cwd ?? process.cwd(),
48
+ env: options.env ?? process.env,
49
+ outputMode,
50
+ output: createCliOutput({
51
+ command: options.commandName ?? input.command,
52
+ mode: outputMode,
53
+ quiet
54
+ }),
55
+ profile: flags.profile,
56
+ dryRun: flagEnabled(flags, "dry-run"),
57
+ yes: flagEnabled(flags, "yes"),
58
+ quiet,
59
+ verbose: flagEnabled(flags, "verbose"),
60
+ interactive,
61
+ recordTelemetry(meta) {
62
+ Object.assign(telemetryMeta, meta);
63
+ },
64
+ getTelemetryMeta() {
65
+ return telemetryMeta;
66
+ }
67
+ };
68
+ }
69
+
70
+ export { parseBooleanFlagValue, stringOption, createCommandContext };
@@ -0,0 +1,30 @@
1
+ import {
2
+ resolveTenantId
3
+ } from "./cli-429zt7g4.js";
4
+
5
+ // src/api-key-headers.ts
6
+ var PLATFORM_API_KEY_PREFIX = "moda_pk_";
7
+ var TENANT_HEADER = "x-moda-tenant-id";
8
+ function isPlatformApiKey(key) {
9
+ return Boolean(key?.startsWith(PLATFORM_API_KEY_PREFIX));
10
+ }
11
+ var platformTenantOverride;
12
+ function setPlatformTenantOverride(tenantId) {
13
+ platformTenantOverride = tenantId;
14
+ }
15
+ var TENANT_PATH = /\/tenants\/([^/?#]+)/;
16
+ function tenantFromUrl(url) {
17
+ const match = url ? TENANT_PATH.exec(url) : null;
18
+ return match ? decodeURIComponent(match[1]) : undefined;
19
+ }
20
+ function apiKeyHeaders(key, options = {}, url) {
21
+ const headers = { "x-api-key": key };
22
+ if (isPlatformApiKey(key)) {
23
+ const tenantId = tenantFromUrl(url) || platformTenantOverride || resolveTenantId(options);
24
+ if (tenantId)
25
+ headers[TENANT_HEADER] = tenantId;
26
+ }
27
+ return headers;
28
+ }
29
+
30
+ export { isPlatformApiKey, setPlatformTenantOverride, apiKeyHeaders };