@moda-ai/cli 1.55.25 → 1.55.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-t7dxmpdq.js → agent-vp9prdma.js} +8 -8
- package/dist/{cli-qycsmxev.js → cli-0pkc8jhg.js} +3 -3
- package/dist/{cli-mebf5vtr.js → cli-4h0ave0f.js} +5 -5
- package/dist/{cli-m0045npe.js → cli-6et4z504.js} +10 -8
- package/dist/{cli-9hc5bg2r.js → cli-ccjfwzqx.js} +1 -1
- package/dist/{cli-0wr3483h.js → cli-hmjdgxrn.js} +1 -1
- package/dist/{cli-9btx12wp.js → cli-pg5qfmqc.js} +1 -1
- package/dist/{cli-m805msp6.js → cli-q9nsvqqg.js} +14 -14
- package/dist/{cli-7xc585hc.js → cli-qwgct6x7.js} +7 -6
- package/dist/{cli-dve50x7p.js → cli-r5pp7qny.js} +140 -455
- package/dist/{cli-qvg26twp.js → cli-v8q7sd7g.js} +38 -13
- package/dist/cli-vdkwkkp5.js +567 -0
- package/dist/{cli-dn91fb56.js → cli-wzds257x.js} +92 -49
- package/dist/cli.js +986 -362
- package/dist/{doctor-q21mr5ya.js → doctor-wmre3mr7.js} +8 -8
- package/dist/{fix-0qdtdz5h.js → fix-75s9s0d6.js} +45 -47
- package/dist/{harness-vzy7n7ea.js → harness-a7tb9a23.js} +5 -5
- package/dist/{harness-github-actions-atc5bbkj.js → harness-github-actions-6v4wydb8.js} +5 -5
- package/dist/{index-4b1ybz51.js → index-91bpc8g8.js} +15 -14
- package/dist/{production-intelligence-c8e0xw3n.js → production-intelligence-7nqddd4f.js} +88 -28
- package/dist/{profile-commands-6e2dgv72.js → profile-commands-49yn02yg.js} +5 -4
- package/dist/prompt-source-mcp.js +5 -5
- package/dist/{prompts-3h5jcjrw.js → prompts-17wyrst6.js} +6 -6
- package/dist/{provision-q86phh4a.js → provision-bc3h68e1.js} +5 -5
- package/dist/{registry-s2vdsfw8.js → registry-fpzhjznj.js} +3 -3
- package/dist/{skills-vq3njc8a.js → skills-ccdbqghj.js} +5 -5
- package/dist/{sync-hq9dpqpr.js → sync-hkxce5zh.js} +7 -7
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/SKILL.md +4 -1
- package/skills/moda-cli/references/dashboard-parity.md +1 -1
- package/skills/moda-cli/references/output-protocol.md +35 -10
- package/skills/moda-cli/references/search-and-traces.md +20 -2
- package/dist/cli-bb7133m7.js +0 -144
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
BATCH_CONCURRENCY,
|
|
4
|
+
BATCH_MAX_TARGETS,
|
|
5
|
+
MODA_INTELLIGENCE_V1_JSON_SCHEMA,
|
|
6
|
+
asNumber,
|
|
7
|
+
asRecord,
|
|
8
|
+
asString,
|
|
9
|
+
collectBatchTargets,
|
|
10
|
+
compactProblemDossier,
|
|
11
|
+
contextTarget,
|
|
12
|
+
embeddedWindowSize,
|
|
13
|
+
frustrationsFromEmotions,
|
|
14
|
+
mapWithConcurrency,
|
|
15
|
+
parseBatchTarget,
|
|
16
|
+
trimToolFailureConversations,
|
|
17
|
+
withFrustrationAnchors,
|
|
18
|
+
withToolFailureAnchors,
|
|
19
|
+
withoutDetectionWindows
|
|
20
|
+
} from "./cli-vdkwkkp5.js";
|
|
5
21
|
import {
|
|
6
22
|
createCommandContext,
|
|
7
|
-
|
|
8
|
-
|
|
23
|
+
envOptOutSet,
|
|
24
|
+
envSwitchedOff,
|
|
25
|
+
optOutFlagSet,
|
|
26
|
+
parseBooleanFlagValue,
|
|
27
|
+
withoutFlag
|
|
28
|
+
} from "./cli-v8q7sd7g.js";
|
|
9
29
|
import {
|
|
10
30
|
AuditSchema,
|
|
11
31
|
COMMAND_FLAG_SPECS,
|
|
@@ -29,6 +49,7 @@ import {
|
|
|
29
49
|
assertPositionals,
|
|
30
50
|
buildFeedbackRequest,
|
|
31
51
|
closestMatches,
|
|
52
|
+
describeField,
|
|
32
53
|
describeSpecFlags,
|
|
33
54
|
flagSpecFor,
|
|
34
55
|
isFeedbackRefFlag,
|
|
@@ -43,17 +64,17 @@ import {
|
|
|
43
64
|
specFlagKeys,
|
|
44
65
|
specFlagNames,
|
|
45
66
|
subcommandHelpFlags,
|
|
46
|
-
unknownSignalsSubcommandError
|
|
47
|
-
|
|
67
|
+
unknownSignalsSubcommandError,
|
|
68
|
+
usageLine,
|
|
69
|
+
writeDryRun
|
|
70
|
+
} from "./cli-wzds257x.js";
|
|
48
71
|
import {
|
|
49
72
|
ApiError,
|
|
50
73
|
callControlAPI,
|
|
51
74
|
callDataAPI
|
|
52
|
-
} from "./cli-
|
|
75
|
+
} from "./cli-pg5qfmqc.js";
|
|
53
76
|
import {
|
|
54
77
|
AGENT_ERROR_CODES,
|
|
55
|
-
BATCH_CONCURRENCY,
|
|
56
|
-
BATCH_MAX_TARGETS,
|
|
57
78
|
CliAccessError,
|
|
58
79
|
CliAuthError,
|
|
59
80
|
CliCancellationError,
|
|
@@ -73,19 +94,11 @@ import {
|
|
|
73
94
|
agentMaxOutputBytes,
|
|
74
95
|
apiErrorStatus,
|
|
75
96
|
apiKeyHeaders,
|
|
76
|
-
asNumber,
|
|
77
|
-
asRecord,
|
|
78
|
-
asString,
|
|
79
97
|
attachInputDetails,
|
|
80
98
|
canonicalCommandText,
|
|
81
|
-
collectBatchTargets,
|
|
82
|
-
compactProblemDossier,
|
|
83
|
-
contextTarget,
|
|
84
99
|
createCliOutput,
|
|
85
100
|
embeddedTenantIdFromApiKey,
|
|
86
|
-
embeddedWindowSize,
|
|
87
101
|
errorToAgentError,
|
|
88
|
-
frustrationsFromEmotions,
|
|
89
102
|
getApiRequestCount,
|
|
90
103
|
getModaDir,
|
|
91
104
|
inputDetailsOf,
|
|
@@ -93,24 +106,20 @@ import {
|
|
|
93
106
|
isNetworkLikeError,
|
|
94
107
|
isPlatformApiKey,
|
|
95
108
|
loadProfileConfig,
|
|
96
|
-
mapWithConcurrency,
|
|
97
|
-
parseBatchTarget,
|
|
98
109
|
resetApiRequestCount,
|
|
99
110
|
resolveApiKey,
|
|
100
111
|
resolveApiTenantId,
|
|
101
112
|
resolveModaBaseUrl,
|
|
113
|
+
resolveModaBaseUrlStrict,
|
|
102
114
|
resolveOutputMode,
|
|
103
115
|
safeWriteStderr,
|
|
104
116
|
safeWriteStdout,
|
|
105
117
|
setAgentUpdateNotice,
|
|
106
118
|
setPlatformTenantOverride,
|
|
107
|
-
|
|
119
|
+
shellArg,
|
|
108
120
|
validateConfig,
|
|
109
|
-
wasOutputCancelled
|
|
110
|
-
|
|
111
|
-
withToolFailureAnchors,
|
|
112
|
-
withoutDetectionWindows
|
|
113
|
-
} from "./cli-dve50x7p.js";
|
|
121
|
+
wasOutputCancelled
|
|
122
|
+
} from "./cli-r5pp7qny.js";
|
|
114
123
|
import {
|
|
115
124
|
__require
|
|
116
125
|
} from "./cli-0v6na3yp.js";
|
|
@@ -299,16 +308,91 @@ async function runReplaysCommand(context) {
|
|
|
299
308
|
}
|
|
300
309
|
|
|
301
310
|
// src/telemetry.ts
|
|
311
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
312
|
+
|
|
313
|
+
// src/detached.ts
|
|
302
314
|
import { spawn } from "node:child_process";
|
|
315
|
+
var INSPECTOR_VALUE_OPTIONS = new Set(["--inspect-port", "--debug-port", "--inspect-publish-uid"]);
|
|
316
|
+
function nodeOptionTokens(nodeOptions) {
|
|
317
|
+
const tokens = [];
|
|
318
|
+
let start = -1;
|
|
319
|
+
let quoted = false;
|
|
320
|
+
for (let i = 0;i < nodeOptions.length; i += 1) {
|
|
321
|
+
const ch = nodeOptions[i];
|
|
322
|
+
if (start === -1) {
|
|
323
|
+
if (/\s/.test(ch))
|
|
324
|
+
continue;
|
|
325
|
+
start = i;
|
|
326
|
+
}
|
|
327
|
+
if (quoted && ch === "\\") {
|
|
328
|
+
i += 1;
|
|
329
|
+
} else if (ch === '"') {
|
|
330
|
+
quoted = !quoted;
|
|
331
|
+
} else if (!quoted && /\s/.test(ch)) {
|
|
332
|
+
tokens.push(nodeOptions.slice(start, i));
|
|
333
|
+
start = -1;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (start !== -1)
|
|
337
|
+
tokens.push(nodeOptions.slice(start));
|
|
338
|
+
return tokens;
|
|
339
|
+
}
|
|
340
|
+
function withoutInspectorOptions(nodeOptions) {
|
|
341
|
+
const tokens = nodeOptionTokens(nodeOptions);
|
|
342
|
+
const kept = [];
|
|
343
|
+
for (let i = 0;i < tokens.length; i += 1) {
|
|
344
|
+
const name = tokens[i].split("=")[0];
|
|
345
|
+
if (!name.startsWith("--inspect") && name !== "--debug-port") {
|
|
346
|
+
kept.push(tokens[i]);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (INSPECTOR_VALUE_OPTIONS.has(tokens[i]) && tokens[i + 1] && !tokens[i + 1].startsWith("-"))
|
|
350
|
+
i += 1;
|
|
351
|
+
}
|
|
352
|
+
return kept.join(" ");
|
|
353
|
+
}
|
|
354
|
+
function spawnDetachedNode(script, options = {}, spawnImpl = spawn) {
|
|
355
|
+
try {
|
|
356
|
+
const env = { ...process.env, ...options.env };
|
|
357
|
+
for (const key of options.dropEnv ?? [])
|
|
358
|
+
delete env[key];
|
|
359
|
+
if (env.NODE_OPTIONS?.includes("--inspect")) {
|
|
360
|
+
const kept = withoutInspectorOptions(env.NODE_OPTIONS);
|
|
361
|
+
if (kept)
|
|
362
|
+
env.NODE_OPTIONS = kept;
|
|
363
|
+
else
|
|
364
|
+
delete env.NODE_OPTIONS;
|
|
365
|
+
}
|
|
366
|
+
const child = spawnImpl(process.execPath, ["-e", script], {
|
|
367
|
+
detached: true,
|
|
368
|
+
stdio: [options.stdin === undefined ? "ignore" : "pipe", "ignore", "ignore"],
|
|
369
|
+
windowsHide: true,
|
|
370
|
+
env
|
|
371
|
+
});
|
|
372
|
+
child.on("error", () => {});
|
|
373
|
+
if (child.pid === undefined)
|
|
374
|
+
return false;
|
|
375
|
+
if (options.stdin !== undefined && child.stdin) {
|
|
376
|
+
child.stdin.on("error", () => {});
|
|
377
|
+
child.stdin.end(options.stdin);
|
|
378
|
+
}
|
|
379
|
+
child.unref();
|
|
380
|
+
return true;
|
|
381
|
+
} catch {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/telemetry.ts
|
|
303
387
|
import { randomUUID } from "node:crypto";
|
|
304
388
|
var INLINE_TELEMETRY_TIMEOUT_MS = 500;
|
|
305
389
|
var DETACHED_TELEMETRY_TIMEOUT_MS = 5000;
|
|
306
390
|
var INVOCATION_ID = randomUUID();
|
|
307
391
|
var TELEMETRY_FLAG = "no-telemetry";
|
|
308
|
-
var TELEMETRY_REQUEST_ENV = "MODA_CLI_TELEMETRY_REQUEST";
|
|
309
392
|
var TELEMETRY_TRANSPORT_ENV = "MODA_CLI_TELEMETRY_TRANSPORT";
|
|
310
393
|
var MAX_TELEMETRY_DURATION_MS = 86400000;
|
|
311
394
|
var MAX_TELEMETRY_API_REQUEST_COUNT = 1e6;
|
|
395
|
+
var MAX_UNKNOWN_FLAG_COUNT = 1000;
|
|
312
396
|
var SEARCH_SURFACE_BY_COMMAND = {
|
|
313
397
|
search: "message",
|
|
314
398
|
conversations: "conversation",
|
|
@@ -410,11 +494,10 @@ function serializeAdoption(adoption) {
|
|
|
410
494
|
return out;
|
|
411
495
|
}
|
|
412
496
|
function stripTelemetryFlag(flags) {
|
|
413
|
-
|
|
414
|
-
return rest;
|
|
497
|
+
return withoutFlag(flags, TELEMETRY_FLAG);
|
|
415
498
|
}
|
|
416
|
-
function isTelemetryDisabled(flags) {
|
|
417
|
-
return flags
|
|
499
|
+
function isTelemetryDisabled(flags, env = process.env) {
|
|
500
|
+
return optOutFlagSet(flags, TELEMETRY_FLAG) || envSwitchedOff(env.MODA_CLI_TELEMETRY) || envOptOutSet(env.DO_NOT_TRACK);
|
|
418
501
|
}
|
|
419
502
|
var VALIDATION_REASON_MAX = 128;
|
|
420
503
|
var DEGRADE_REASON_MAX = 64;
|
|
@@ -425,88 +508,78 @@ function sanitizeReasonCode(reason, max) {
|
|
|
425
508
|
function sanitizeValidationReason(reason) {
|
|
426
509
|
return sanitizeReasonCode(reason, VALIDATION_REASON_MAX);
|
|
427
510
|
}
|
|
428
|
-
|
|
429
|
-
|
|
511
|
+
var MAX_FLAG_NAMES = 50;
|
|
512
|
+
var FLAG_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
513
|
+
var SECRET_PATTERNS = [
|
|
514
|
+
/moda_sk_/i,
|
|
515
|
+
/(?:^|[^A-Za-z0-9])sk-[A-Za-z0-9_-]{8,}/,
|
|
516
|
+
/(?:^|[^A-Za-z0-9])(?:sk|pk|rk)_(?:live|test)_/i,
|
|
517
|
+
/gh[pousr]_[A-Za-z0-9]{16,}/,
|
|
518
|
+
/github_pat_/i,
|
|
519
|
+
/xox[abprs]-/i,
|
|
520
|
+
/AKIA[0-9A-Z]{16}/,
|
|
521
|
+
/eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/,
|
|
522
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY/,
|
|
523
|
+
/[^\s@]+@[^\s@]+\.[A-Za-z]{2,}/,
|
|
524
|
+
/[A-Za-z0-9+/=]{32,}/
|
|
525
|
+
];
|
|
526
|
+
function looksSecret(value) {
|
|
527
|
+
return SECRET_PATTERNS.some((pattern) => pattern.test(value));
|
|
528
|
+
}
|
|
529
|
+
function reportedFlagNames(flags, acceptedFlags = new Set) {
|
|
530
|
+
const names = new Set;
|
|
531
|
+
let unknownCount = 0;
|
|
532
|
+
for (const key of Object.keys(stripTelemetryFlag(flags))) {
|
|
533
|
+
if (acceptedFlags.has(key.replace(/_/g, "-")) && FLAG_NAME_RE.test(key) && !looksSecret(key))
|
|
534
|
+
names.add(key);
|
|
535
|
+
else
|
|
536
|
+
unknownCount += 1;
|
|
537
|
+
}
|
|
538
|
+
const sorted = [...names].sort();
|
|
539
|
+
return { names: sorted.slice(0, MAX_FLAG_NAMES), unknownCount: unknownCount + Math.max(0, sorted.length - MAX_FLAG_NAMES) };
|
|
540
|
+
}
|
|
541
|
+
function reportedValidationReason(reason, knownFlags = new Set) {
|
|
542
|
+
let code = reason;
|
|
543
|
+
const unknownFlag = code ? /^unknown_flag:(.*)$/s.exec(code) : null;
|
|
544
|
+
if (unknownFlag) {
|
|
545
|
+
const name = unknownFlag[1].replace(/_/g, "-");
|
|
546
|
+
code = knownFlags.has(name) && FLAG_NAME_RE.test(name) ? `unknown_flag:${name}` : "unknown_flag";
|
|
547
|
+
}
|
|
548
|
+
const cleaned = sanitizeValidationReason(code);
|
|
549
|
+
if (!cleaned || !looksSecret(cleaned))
|
|
550
|
+
return cleaned;
|
|
551
|
+
const head = cleaned.split(":")[0];
|
|
552
|
+
return head && !looksSecret(head) ? head : undefined;
|
|
553
|
+
}
|
|
554
|
+
function safeCode(value) {
|
|
555
|
+
return value && !looksSecret(value) ? value : undefined;
|
|
430
556
|
}
|
|
431
557
|
var TELEMETRY_SENDER_SCRIPT = [
|
|
432
558
|
"try {",
|
|
433
|
-
|
|
434
|
-
`
|
|
435
|
-
" const
|
|
436
|
-
"
|
|
437
|
-
"
|
|
438
|
-
|
|
439
|
-
"
|
|
440
|
-
"
|
|
441
|
-
" .
|
|
442
|
-
"
|
|
443
|
-
"
|
|
559
|
+
" let raw = '';",
|
|
560
|
+
` const quit = setTimeout(() => process.exit(0), ${DETACHED_TELEMETRY_TIMEOUT_MS + 1000});`,
|
|
561
|
+
" const done = () => clearTimeout(quit);",
|
|
562
|
+
" process.stdin.setEncoding('utf8');",
|
|
563
|
+
" process.stdin.on('error', () => {});",
|
|
564
|
+
" process.stdin.on('data', (chunk) => { raw += chunk; });",
|
|
565
|
+
" process.stdin.on('end', () => {",
|
|
566
|
+
" try {",
|
|
567
|
+
" const r = raw ? JSON.parse(raw) : null;",
|
|
568
|
+
" raw = '';",
|
|
569
|
+
" if (!r || typeof r.url !== 'string' || typeof fetch !== 'function') return done();",
|
|
570
|
+
" const c = new AbortController();",
|
|
571
|
+
` const t = setTimeout(() => c.abort(), ${DETACHED_TELEMETRY_TIMEOUT_MS});`,
|
|
572
|
+
" fetch(r.url, { method: 'POST', headers: r.headers, body: r.body, signal: c.signal })",
|
|
573
|
+
" .then((res) => (res.body && res.body.cancel ? res.body.cancel() : undefined))",
|
|
574
|
+
" .catch(() => {})",
|
|
575
|
+
" .finally(() => { clearTimeout(t); done(); });",
|
|
576
|
+
" } catch { done(); }",
|
|
577
|
+
" });",
|
|
444
578
|
"} catch {}"
|
|
445
579
|
].join(`
|
|
446
580
|
`);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const tokens = [];
|
|
450
|
-
let start = -1;
|
|
451
|
-
let quoted = false;
|
|
452
|
-
for (let i = 0;i < nodeOptions.length; i += 1) {
|
|
453
|
-
const ch = nodeOptions[i];
|
|
454
|
-
if (start === -1) {
|
|
455
|
-
if (/\s/.test(ch))
|
|
456
|
-
continue;
|
|
457
|
-
start = i;
|
|
458
|
-
}
|
|
459
|
-
if (quoted && ch === "\\") {
|
|
460
|
-
i += 1;
|
|
461
|
-
} else if (ch === '"') {
|
|
462
|
-
quoted = !quoted;
|
|
463
|
-
} else if (!quoted && /\s/.test(ch)) {
|
|
464
|
-
tokens.push(nodeOptions.slice(start, i));
|
|
465
|
-
start = -1;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
if (start !== -1)
|
|
469
|
-
tokens.push(nodeOptions.slice(start));
|
|
470
|
-
return tokens;
|
|
471
|
-
}
|
|
472
|
-
function withoutInspectorOptions(nodeOptions) {
|
|
473
|
-
const tokens = nodeOptionTokens(nodeOptions);
|
|
474
|
-
const kept = [];
|
|
475
|
-
for (let i = 0;i < tokens.length; i += 1) {
|
|
476
|
-
const name = tokens[i].split("=")[0];
|
|
477
|
-
if (!name.startsWith("--inspect") && name !== "--debug-port") {
|
|
478
|
-
kept.push(tokens[i]);
|
|
479
|
-
continue;
|
|
480
|
-
}
|
|
481
|
-
if (INSPECTOR_VALUE_OPTIONS.has(tokens[i]) && tokens[i + 1] && !tokens[i + 1].startsWith("-"))
|
|
482
|
-
i += 1;
|
|
483
|
-
}
|
|
484
|
-
return kept.join(" ");
|
|
485
|
-
}
|
|
486
|
-
function spawnDetachedTelemetrySender(request, spawnImpl = spawn) {
|
|
487
|
-
try {
|
|
488
|
-
const env = { ...process.env, [TELEMETRY_REQUEST_ENV]: JSON.stringify(request) };
|
|
489
|
-
if (env.NODE_OPTIONS?.includes("--inspect")) {
|
|
490
|
-
const kept = withoutInspectorOptions(env.NODE_OPTIONS);
|
|
491
|
-
if (kept)
|
|
492
|
-
env.NODE_OPTIONS = kept;
|
|
493
|
-
else
|
|
494
|
-
delete env.NODE_OPTIONS;
|
|
495
|
-
}
|
|
496
|
-
const child = spawnImpl(process.execPath, ["-e", TELEMETRY_SENDER_SCRIPT], {
|
|
497
|
-
detached: true,
|
|
498
|
-
stdio: "ignore",
|
|
499
|
-
windowsHide: true,
|
|
500
|
-
env
|
|
501
|
-
});
|
|
502
|
-
child.on("error", () => {});
|
|
503
|
-
if (child.pid === undefined)
|
|
504
|
-
return false;
|
|
505
|
-
child.unref();
|
|
506
|
-
return true;
|
|
507
|
-
} catch {
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
581
|
+
function spawnDetachedTelemetrySender(request, spawnImpl = spawn2) {
|
|
582
|
+
return spawnDetachedNode(TELEMETRY_SENDER_SCRIPT, { stdin: JSON.stringify(request), dropEnv: ["MODA_API_KEY"] }, spawnImpl);
|
|
510
583
|
}
|
|
511
584
|
async function sendInline(request) {
|
|
512
585
|
const controller = new AbortController;
|
|
@@ -526,16 +599,25 @@ async function sendInline(request) {
|
|
|
526
599
|
}
|
|
527
600
|
}
|
|
528
601
|
function buildCliUsageRequest(input) {
|
|
529
|
-
const
|
|
602
|
+
const baseUrl = resolveModaBaseUrlStrict(input.profile ? { profile: input.profile } : {});
|
|
603
|
+
if (!baseUrl)
|
|
604
|
+
return;
|
|
605
|
+
const validationReason = reportedValidationReason(input.validationReason, input.knownFlags);
|
|
606
|
+
const flagNames = reportedFlagNames(input.flags, input.acceptedFlags);
|
|
607
|
+
const adoption = serializeAdoption(input.adoption);
|
|
608
|
+
for (const key of ["subcommand", "degrade_reason", "skill_version"]) {
|
|
609
|
+
if (typeof adoption[key] === "string" && looksSecret(adoption[key]))
|
|
610
|
+
delete adoption[key];
|
|
611
|
+
}
|
|
530
612
|
return {
|
|
531
|
-
url: `${
|
|
613
|
+
url: `${baseUrl}/api/v1/data/cli-usage`,
|
|
532
614
|
headers: {
|
|
533
615
|
...apiKeyHeaders(input.apiKey),
|
|
534
616
|
"Content-Type": "application/json"
|
|
535
617
|
},
|
|
536
618
|
body: JSON.stringify({
|
|
537
619
|
invocation_id: INVOCATION_ID,
|
|
538
|
-
command: input.command.slice(0, 64),
|
|
620
|
+
command: safeCode(input.command.slice(0, 64)) ?? "unknown",
|
|
539
621
|
status: input.status,
|
|
540
622
|
error_type: input.errorType,
|
|
541
623
|
...validationReason ? { validation_reason: validationReason } : {},
|
|
@@ -547,10 +629,11 @@ function buildCliUsageRequest(input) {
|
|
|
547
629
|
platform: process.platform,
|
|
548
630
|
arch: process.arch,
|
|
549
631
|
flags_json: {
|
|
550
|
-
names:
|
|
551
|
-
...
|
|
632
|
+
names: flagNames.names,
|
|
633
|
+
...flagNames.unknownCount > 0 ? { unknown_count: Math.min(flagNames.unknownCount, MAX_UNKNOWN_FLAG_COUNT) } : {},
|
|
634
|
+
...((alias) => alias ? { legacy_alias: alias } : {})(safeCode(sanitizeReasonCode(input.legacyAlias?.replace(/ /g, ":"), 64)))
|
|
552
635
|
},
|
|
553
|
-
...
|
|
636
|
+
...adoption
|
|
554
637
|
})
|
|
555
638
|
};
|
|
556
639
|
}
|
|
@@ -560,6 +643,8 @@ async function sendCliUsageTelemetry(input) {
|
|
|
560
643
|
return;
|
|
561
644
|
}
|
|
562
645
|
const request = buildCliUsageRequest({ ...input, apiKey: input.apiKey });
|
|
646
|
+
if (!request)
|
|
647
|
+
return;
|
|
563
648
|
if (process.env[TELEMETRY_TRANSPORT_ENV] !== "inline" && spawnDetachedTelemetrySender(request)) {
|
|
564
649
|
return;
|
|
565
650
|
}
|
|
@@ -576,11 +661,10 @@ var REGISTRY_TIMEOUT_MS = 1500;
|
|
|
576
661
|
var REGISTRY_LATEST_URL = "https://registry.npmjs.org/@moda-ai/cli/latest";
|
|
577
662
|
var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
578
663
|
function stripUpdateCheckFlag(flags) {
|
|
579
|
-
|
|
580
|
-
return rest;
|
|
664
|
+
return withoutFlag(flags, UPDATE_CHECK_FLAG);
|
|
581
665
|
}
|
|
582
666
|
function isUpdateCheckDisabled(flags, env = process.env) {
|
|
583
|
-
return flags
|
|
667
|
+
return optOutFlagSet(flags, UPDATE_CHECK_FLAG) || envSwitchedOff(env.MODA_CLI_UPDATE_CHECK) || envOptOutSet(env.DO_NOT_TRACK) || envOptOutSet(env.CI);
|
|
584
668
|
}
|
|
585
669
|
function cachePath(options = {}) {
|
|
586
670
|
return join(getModaDir(options), "update-check.json");
|
|
@@ -613,9 +697,39 @@ function isNewerVersion(candidate, current) {
|
|
|
613
697
|
}
|
|
614
698
|
return false;
|
|
615
699
|
}
|
|
616
|
-
|
|
700
|
+
var UPDATE_CHECK_URL_ENV = "MODA_CLI_UPDATE_CHECK_URL";
|
|
701
|
+
var UPDATE_CHECK_CACHE_ENV = "MODA_CLI_UPDATE_CHECK_CACHE";
|
|
702
|
+
var DETACHED_REGISTRY_TIMEOUT_MS = 1e4;
|
|
703
|
+
var UPDATE_CHECK_SCRIPT = [
|
|
704
|
+
"try {",
|
|
705
|
+
` const url = process.env.${UPDATE_CHECK_URL_ENV};`,
|
|
706
|
+
` const path = process.env.${UPDATE_CHECK_CACHE_ENV};`,
|
|
707
|
+
` const semver = new RegExp(${JSON.stringify(SEMVER_RE.source)});`,
|
|
708
|
+
" if (url && path && typeof fetch === 'function') {",
|
|
709
|
+
" const c = new AbortController();",
|
|
710
|
+
` const t = setTimeout(() => c.abort(), ${DETACHED_REGISTRY_TIMEOUT_MS});`,
|
|
711
|
+
" fetch(url, { headers: { Accept: 'application/json' }, signal: c.signal })",
|
|
712
|
+
" .then((res) => (res.ok ? res.json() : null))",
|
|
713
|
+
" .then((body) => {",
|
|
714
|
+
" if (body && typeof body.version === 'string' && semver.test(body.version)) {",
|
|
715
|
+
" require('fs').writeFileSync(path, JSON.stringify({ checked_at: Date.now(), latest: body.version }, null, 2) + '\\n', 'utf8');",
|
|
716
|
+
" }",
|
|
717
|
+
" })",
|
|
718
|
+
" .catch(() => {})",
|
|
719
|
+
" .finally(() => clearTimeout(t));",
|
|
720
|
+
" }",
|
|
721
|
+
"} catch {}"
|
|
722
|
+
].join(`
|
|
723
|
+
`);
|
|
724
|
+
async function startUpdateCheck(options = {}, now = Date.now(), mode = {}) {
|
|
617
725
|
const cache = readCache(options);
|
|
618
|
-
if (cache && now - cache.checked_at < CHECK_INTERVAL_MS) {
|
|
726
|
+
if (cache && cache.checked_at <= now && now - cache.checked_at < CHECK_INTERVAL_MS) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const known = cache && SEMVER_RE.test(cache.latest) ? cache.latest : "";
|
|
730
|
+
writeCache({ checked_at: now, latest: known }, options);
|
|
731
|
+
if (mode.detached) {
|
|
732
|
+
spawnDetachedNode(UPDATE_CHECK_SCRIPT, { env: { [UPDATE_CHECK_URL_ENV]: REGISTRY_LATEST_URL, [UPDATE_CHECK_CACHE_ENV]: cachePath(options) }, dropEnv: ["MODA_API_KEY"] }, mode.spawnImpl);
|
|
619
733
|
return;
|
|
620
734
|
}
|
|
621
735
|
const controller = new AbortController;
|
|
@@ -640,16 +754,16 @@ async function startUpdateCheck(options = {}, now = Date.now()) {
|
|
|
640
754
|
}
|
|
641
755
|
function getAvailableUpdate(options = {}) {
|
|
642
756
|
const cache = readCache(options);
|
|
643
|
-
return cache && isNewerVersion(cache.latest, VERSION) ? cache.latest : undefined;
|
|
757
|
+
return cache && SEMVER_RE.test(cache.latest) && isNewerVersion(cache.latest, VERSION) ? cache.latest : undefined;
|
|
644
758
|
}
|
|
645
759
|
function getUpdateNotice(options = {}) {
|
|
646
|
-
const
|
|
647
|
-
if (!
|
|
760
|
+
const latest = getAvailableUpdate(options);
|
|
761
|
+
if (!latest) {
|
|
648
762
|
return;
|
|
649
763
|
}
|
|
650
764
|
return [
|
|
651
765
|
"",
|
|
652
|
-
`Update available: moda-cli ${VERSION} → ${
|
|
766
|
+
`Update available: moda-cli ${VERSION} → ${latest}`,
|
|
653
767
|
"Run `npm install -g @moda-ai/cli@latest` to update."
|
|
654
768
|
].join(`
|
|
655
769
|
`);
|
|
@@ -2249,6 +2363,7 @@ function buildManifest(commands) {
|
|
|
2249
2363
|
"--agent",
|
|
2250
2364
|
"--stream",
|
|
2251
2365
|
"--json",
|
|
2366
|
+
"--pretty",
|
|
2252
2367
|
"--human",
|
|
2253
2368
|
"--profile <name>",
|
|
2254
2369
|
"--quiet",
|
|
@@ -2275,7 +2390,7 @@ function buildManifest(commands) {
|
|
|
2275
2390
|
exit_codes: [
|
|
2276
2391
|
{ code: 0, name: "ok", meaning: "Command completed successfully." },
|
|
2277
2392
|
{ code: 1, name: "error", meaning: "Command, input, API, network, or unexpected failure." },
|
|
2278
|
-
{ code: 3, name: "degraded", meaning: "Degraded result: `moda ask` local fallback." },
|
|
2393
|
+
{ code: 3, name: "degraded", meaning: "Degraded result: `moda ask` local fallback, or `investigate`/`failures` with some sources unavailable (`data.failedSources`)." },
|
|
2279
2394
|
{ code: 4, name: "auth_required", meaning: "Run login_command, then resume." },
|
|
2280
2395
|
{ code: 5, name: "input_required", meaning: "Choose from input_request choices, then resume." },
|
|
2281
2396
|
{ code: 130, name: "cancelled", meaning: "Interrupted or output consumer closed." }
|
|
@@ -2390,6 +2505,8 @@ function event(name, when, terminal, deprecated = false) {
|
|
|
2390
2505
|
function exitCodesForCommand(command) {
|
|
2391
2506
|
if (command === "ask")
|
|
2392
2507
|
return [0, 1, 3, 4, 5, 130];
|
|
2508
|
+
if (command === "investigate" || command === "failures")
|
|
2509
|
+
return [0, 1, 3, 4, 130];
|
|
2393
2510
|
if (command === "init" || command === "harness")
|
|
2394
2511
|
return [0, 1, 4, 5, 130];
|
|
2395
2512
|
return [0, 1, 4, 130];
|
|
@@ -2522,46 +2639,46 @@ function leafManifestFields(leaf, commands) {
|
|
|
2522
2639
|
import { fileURLToPath } from "url";
|
|
2523
2640
|
import { realpathSync } from "fs";
|
|
2524
2641
|
async function runPromptsCommand(...args) {
|
|
2525
|
-
return (await import("./prompts-
|
|
2642
|
+
return (await import("./prompts-17wyrst6.js")).runPromptsCommand(...args);
|
|
2526
2643
|
}
|
|
2527
2644
|
async function runSkillsCommand(...args) {
|
|
2528
|
-
return (await import("./skills-
|
|
2645
|
+
return (await import("./skills-ccdbqghj.js")).runSkillsCommand(...args);
|
|
2529
2646
|
}
|
|
2530
2647
|
async function runFixCommand(...args) {
|
|
2531
|
-
return (await import("./fix-
|
|
2648
|
+
return (await import("./fix-75s9s0d6.js")).runFixCommand(...args);
|
|
2532
2649
|
}
|
|
2533
2650
|
async function runFixesCommand(...args) {
|
|
2534
|
-
return (await import("./fix-
|
|
2651
|
+
return (await import("./fix-75s9s0d6.js")).runFixesCommand(...args);
|
|
2535
2652
|
}
|
|
2536
2653
|
async function runHarnessCommand(...args) {
|
|
2537
|
-
return (await import("./harness-
|
|
2654
|
+
return (await import("./harness-a7tb9a23.js")).runHarnessCommand(...args);
|
|
2538
2655
|
}
|
|
2539
2656
|
async function runSyncCommand(...args) {
|
|
2540
|
-
return (await import("./sync-
|
|
2657
|
+
return (await import("./sync-hkxce5zh.js")).runSyncCommand(...args);
|
|
2541
2658
|
}
|
|
2542
2659
|
async function runRegistryCommand(...args) {
|
|
2543
|
-
return (await import("./registry-
|
|
2660
|
+
return (await import("./registry-fpzhjznj.js")).runRegistryCommand(...args);
|
|
2544
2661
|
}
|
|
2545
2662
|
async function runDoctorCommand(...args) {
|
|
2546
|
-
return (await import("./doctor-
|
|
2663
|
+
return (await import("./doctor-wmre3mr7.js")).runDoctorCommand(...args);
|
|
2547
2664
|
}
|
|
2548
2665
|
async function runStatusCommand(...args) {
|
|
2549
|
-
return (await import("./doctor-
|
|
2666
|
+
return (await import("./doctor-wmre3mr7.js")).runStatusCommand(...args);
|
|
2550
2667
|
}
|
|
2551
2668
|
async function runAuthCommand(...args) {
|
|
2552
|
-
return (await import("./profile-commands-
|
|
2669
|
+
return (await import("./profile-commands-49yn02yg.js")).runAuthCommand(...args);
|
|
2553
2670
|
}
|
|
2554
2671
|
async function runConfigCommand(...args) {
|
|
2555
|
-
return (await import("./profile-commands-
|
|
2672
|
+
return (await import("./profile-commands-49yn02yg.js")).runConfigCommand(...args);
|
|
2556
2673
|
}
|
|
2557
2674
|
async function runProfilesCommand(...args) {
|
|
2558
|
-
return (await import("./profile-commands-
|
|
2675
|
+
return (await import("./profile-commands-49yn02yg.js")).runProfilesCommand(...args);
|
|
2559
2676
|
}
|
|
2560
2677
|
async function runAgentCommand(...args) {
|
|
2561
|
-
return (await import("./agent-
|
|
2678
|
+
return (await import("./agent-vp9prdma.js")).runAgentCommand(...args);
|
|
2562
2679
|
}
|
|
2563
2680
|
async function runProductionCommand(...args) {
|
|
2564
|
-
return (await import("./production-intelligence-
|
|
2681
|
+
return (await import("./production-intelligence-7nqddd4f.js")).runProductionCommand(...args);
|
|
2565
2682
|
}
|
|
2566
2683
|
function parseArgs(argv) {
|
|
2567
2684
|
const args = argv.slice(2);
|
|
@@ -2575,10 +2692,10 @@ function parseArgs(argv) {
|
|
|
2575
2692
|
let endOfOptions = false;
|
|
2576
2693
|
while (i < args.length) {
|
|
2577
2694
|
const arg = args[i];
|
|
2578
|
-
if (!endOfOptions && arg === "--" && command
|
|
2695
|
+
if (!endOfOptions && arg === "--" && command === "") {} else if (!endOfOptions && arg === "--") {
|
|
2579
2696
|
endOfOptions = true;
|
|
2580
2697
|
} else if (!endOfOptions && arg.startsWith("--")) {
|
|
2581
|
-
i = parseFlag(args, i, flags, command === "", bareFlags,
|
|
2698
|
+
i = parseFlag(args, i, flags, command === "", bareFlags, command, positional);
|
|
2582
2699
|
} else if (!endOfOptions && command !== "" && /^-[A-Za-z]/.test(arg)) {
|
|
2583
2700
|
shortFlags.push(arg);
|
|
2584
2701
|
} else if (command === "") {
|
|
@@ -2655,7 +2772,8 @@ var RAW_BOOLEAN_FLAGS = new Set([
|
|
|
2655
2772
|
"tui",
|
|
2656
2773
|
"no-tui",
|
|
2657
2774
|
"no-telemetry",
|
|
2658
|
-
"no-update-check"
|
|
2775
|
+
"no-update-check",
|
|
2776
|
+
"pretty"
|
|
2659
2777
|
]);
|
|
2660
2778
|
var GLOBAL_BOOLEAN_FLAGS = new Set([
|
|
2661
2779
|
"agent",
|
|
@@ -2671,7 +2789,8 @@ var GLOBAL_BOOLEAN_FLAGS = new Set([
|
|
|
2671
2789
|
"dry-run",
|
|
2672
2790
|
"stream",
|
|
2673
2791
|
"tui",
|
|
2674
|
-
"no-tui"
|
|
2792
|
+
"no-tui",
|
|
2793
|
+
"pretty"
|
|
2675
2794
|
]);
|
|
2676
2795
|
var BOOLEAN_WORDS = new Set(["true", "false", "yes", "no", "on", "off"]);
|
|
2677
2796
|
var META_SWITCHES = new Set([
|
|
@@ -2685,14 +2804,64 @@ var META_SWITCHES = new Set([
|
|
|
2685
2804
|
"tui",
|
|
2686
2805
|
"no-tui",
|
|
2687
2806
|
"no-telemetry",
|
|
2688
|
-
"no-update-check"
|
|
2807
|
+
"no-update-check",
|
|
2808
|
+
"pretty"
|
|
2689
2809
|
]);
|
|
2690
2810
|
var AGENT_VALUE_COMMANDS = new Set(["harness", "sync"]);
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
}
|
|
2694
|
-
function
|
|
2695
|
-
const
|
|
2811
|
+
function canonicalFlagKey(key) {
|
|
2812
|
+
return key.replace(/_/g, "-");
|
|
2813
|
+
}
|
|
2814
|
+
function canonicalizeFlagKeys(flags, bareFlags) {
|
|
2815
|
+
const entries = Object.entries(flags);
|
|
2816
|
+
for (const [key] of entries)
|
|
2817
|
+
delete flags[key];
|
|
2818
|
+
for (const [key, value] of entries)
|
|
2819
|
+
flags[canonicalFlagKey(key)] = value;
|
|
2820
|
+
if (bareFlags) {
|
|
2821
|
+
const canonical = [...new Set(bareFlags.map(canonicalFlagKey))];
|
|
2822
|
+
bareFlags.splice(0, bareFlags.length, ...canonical);
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
function parseHandlersFor(command, verb) {
|
|
2826
|
+
if (command === "review")
|
|
2827
|
+
return leafHandlers(leafFor("review", undefined));
|
|
2828
|
+
if (CANONICAL_NOUNS.includes(command) && verb !== undefined) {
|
|
2829
|
+
const leaf = leafFor(command, verb);
|
|
2830
|
+
if (leaf)
|
|
2831
|
+
return leaf.path === "problems unexplained" ? ["problems"] : leafHandlers(leaf);
|
|
2832
|
+
}
|
|
2833
|
+
return [commandRegistry.find(command)?.name ?? command];
|
|
2834
|
+
}
|
|
2835
|
+
function specsOf(name) {
|
|
2836
|
+
const spec = flagSpecFor(name);
|
|
2837
|
+
return spec ? [spec, ...Object.values(spec.subcommands ?? {})] : [];
|
|
2838
|
+
}
|
|
2839
|
+
var switchFlagCache = new Map;
|
|
2840
|
+
function commandSwitchFlags(command, verb) {
|
|
2841
|
+
const handlers = parseHandlersFor(command, verb);
|
|
2842
|
+
const cacheKey = handlers.join(",");
|
|
2843
|
+
const cached = switchFlagCache.get(cacheKey);
|
|
2844
|
+
if (cached)
|
|
2845
|
+
return cached;
|
|
2846
|
+
const booleans = new Set;
|
|
2847
|
+
const valued = new Set;
|
|
2848
|
+
for (const handler of handlers) {
|
|
2849
|
+
for (const spec of specsOf(handler)) {
|
|
2850
|
+
for (const flag of describeSpecFlags(spec)) {
|
|
2851
|
+
(flag.type === "boolean" ? booleans : valued).add(flag.name.slice(2));
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
const switches = new Set([...booleans].filter((name) => !valued.has(name)));
|
|
2856
|
+
switchFlagCache.set(cacheKey, switches);
|
|
2857
|
+
return switches;
|
|
2858
|
+
}
|
|
2859
|
+
function takesIdPositional(command, verb) {
|
|
2860
|
+
return parseHandlersFor(command, verb).some((handler) => specsOf(handler).some((spec) => (spec.positionals ?? []).some((positional) => !positional.variadic)));
|
|
2861
|
+
}
|
|
2862
|
+
function parseFlag(args, index, flags, beforeCommand, bareFlags, command = "", verb) {
|
|
2863
|
+
const set = (rawKey, value, bare) => {
|
|
2864
|
+
const key2 = canonicalFlagKey(rawKey);
|
|
2696
2865
|
flags[key2] = value;
|
|
2697
2866
|
const at = bareFlags.indexOf(key2);
|
|
2698
2867
|
if (at !== -1)
|
|
@@ -2708,17 +2877,19 @@ function parseFlag(args, index, flags, beforeCommand, bareFlags, command = "") {
|
|
|
2708
2877
|
}
|
|
2709
2878
|
const key = arg.slice(2);
|
|
2710
2879
|
const next = args[index + 1];
|
|
2711
|
-
if (beforeCommand && GLOBAL_BOOLEAN_FLAGS.has(key)) {
|
|
2880
|
+
if (beforeCommand && GLOBAL_BOOLEAN_FLAGS.has(canonicalFlagKey(key))) {
|
|
2712
2881
|
set(key, "true", true);
|
|
2713
2882
|
return index;
|
|
2714
2883
|
}
|
|
2715
|
-
const switchName = key
|
|
2716
|
-
|
|
2884
|
+
const switchName = canonicalFlagKey(key);
|
|
2885
|
+
const parseCommand = parseCommandFor(command, verb);
|
|
2886
|
+
if (META_SWITCHES.has(switchName) || switchName === "agent" && !AGENT_VALUE_COMMANDS.has(parseCommand)) {
|
|
2717
2887
|
set(key, "true", true);
|
|
2718
2888
|
return index;
|
|
2719
2889
|
}
|
|
2720
|
-
const
|
|
2721
|
-
const isSwitch = RAW_BOOLEAN_FLAGS.has(switchName) ||
|
|
2890
|
+
const schemaSwitch = !beforeCommand && commandSwitchFlags(command, verb).has(switchName);
|
|
2891
|
+
const isSwitch = RAW_BOOLEAN_FLAGS.has(switchName) || schemaSwitch;
|
|
2892
|
+
const idFollows = schemaSwitch && takesIdPositional(command, verb);
|
|
2722
2893
|
const takesNext = BOOLEAN_WORDS.has(next?.toLowerCase() ?? "") || !idFollows && (next === "0" || next === "1");
|
|
2723
2894
|
if (!beforeCommand && isSwitch && !takesNext) {
|
|
2724
2895
|
set(key, "true", true);
|
|
@@ -2750,9 +2921,32 @@ function flagsToArgs(flags, positionalKey, positionalValue) {
|
|
|
2750
2921
|
result[positionalKey] = positionalValue;
|
|
2751
2922
|
}
|
|
2752
2923
|
for (const [key, value] of Object.entries(flags)) {
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2924
|
+
result[key.replace(/-/g, "_")] = value;
|
|
2925
|
+
}
|
|
2926
|
+
return result;
|
|
2927
|
+
}
|
|
2928
|
+
var DECIMAL_TEXT = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
|
|
2929
|
+
function coerceNumericFlags(schema, args, command) {
|
|
2930
|
+
const shape = schemaShape(schema);
|
|
2931
|
+
const result = { ...args };
|
|
2932
|
+
for (const [key, value] of Object.entries(args)) {
|
|
2933
|
+
if (typeof value !== "string" || !Object.hasOwn(shape, key))
|
|
2934
|
+
continue;
|
|
2935
|
+
const info = describeField(shape[key]);
|
|
2936
|
+
if (info.type !== "integer" && info.type !== "number")
|
|
2937
|
+
continue;
|
|
2938
|
+
const text = value.trim();
|
|
2939
|
+
if (!DECIMAL_TEXT.test(text)) {
|
|
2940
|
+
const flag = kebabFlag(key);
|
|
2941
|
+
const kind = info.type === "integer" ? "a whole number" : "a number";
|
|
2942
|
+
const range = info.min !== undefined && info.max !== undefined ? ` from ${info.min} to ${info.max}` : "";
|
|
2943
|
+
throw new CliInputError(`${flag} must be ${kind}${range} (got '${value}'), so nothing was sent.`, `Pass digits only, e.g. ${flag}=${info.min ?? 1}. See '${canonicalCommandText(`moda ${command}`)} --help'.`, `bad_number:${flag.slice(2)}`, {
|
|
2944
|
+
field: flag,
|
|
2945
|
+
...info.min !== undefined || info.max !== undefined ? { range: { ...info.min !== undefined ? { min: info.min } : {}, ...info.max !== undefined ? { max: info.max } : {} } } : {},
|
|
2946
|
+
suggestedCommands: [`${canonicalCommandText(`moda ${command}`)} --help`]
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
result[key] = Number(text);
|
|
2756
2950
|
}
|
|
2757
2951
|
return result;
|
|
2758
2952
|
}
|
|
@@ -2820,6 +3014,7 @@ Silent aliases: conversations = traces list, cluster-conversations = cluster-tra
|
|
|
2820
3014
|
|
|
2821
3015
|
Output: piped stdout, CI or an agent env (CLAUDECODE/CURSOR/MODA_AGENT) => agent JSON; a terminal => prose.
|
|
2822
3016
|
--json | --agent [--stream] | --human Force an output mode (or MODA_FORMAT=human|json|agent|agent-stream)
|
|
3017
|
+
--json is compact when piped; --pretty indents it (indented by default on a terminal)
|
|
2823
3018
|
--profile=NAME --quiet --no-update-check --version --json-schemas
|
|
2824
3019
|
Exit codes: 0 ok, 1 error (errors[0].code says which), 3 degraded, 4 auth required, 5 input required, 130 cancelled.
|
|
2825
3020
|
A command rejects any flag it does not take; the error lists errors[0].allowed and errors[0].did_you_mean.`);
|
|
@@ -3082,8 +3277,27 @@ function legacyNamesHelpLine() {
|
|
|
3082
3277
|
return lines.join(`
|
|
3083
3278
|
`);
|
|
3084
3279
|
}
|
|
3085
|
-
|
|
3086
|
-
|
|
3280
|
+
var ENRICH_CONCURRENCY = BATCH_CONCURRENCY;
|
|
3281
|
+
function enrichedRowBytes(row) {
|
|
3282
|
+
const window = asRecord(row.window);
|
|
3283
|
+
if (window && !("error" in window)) {
|
|
3284
|
+
const { conversation: _conversation, ...rest } = row;
|
|
3285
|
+
return jsonByteLength(rest);
|
|
3286
|
+
}
|
|
3287
|
+
return jsonByteLength(row);
|
|
3288
|
+
}
|
|
3289
|
+
async function enrichWithinBudget(rows, enrich, budgetBytes, skipKey) {
|
|
3290
|
+
let spent = 0;
|
|
3291
|
+
const enriched = await mapWithConcurrency(rows, ENRICH_CONCURRENCY, async (row) => {
|
|
3292
|
+
const out = await enrich(row);
|
|
3293
|
+
if (budgetBytes !== undefined)
|
|
3294
|
+
spent += enrichedRowBytes(out);
|
|
3295
|
+
return out;
|
|
3296
|
+
}, budgetBytes === undefined ? {} : { shouldStart: () => spent <= budgetBytes });
|
|
3297
|
+
return [...enriched, ...rows.slice(enriched.length).map((row) => ({ ...row, [skipKey]: "agent_output_budget" }))];
|
|
3298
|
+
}
|
|
3299
|
+
async function attachWindows(rows, windowSize, budgetBytes) {
|
|
3300
|
+
return enrichWithinBudget(rows, async (row) => {
|
|
3087
3301
|
const anchor = asRecord(row.anchor);
|
|
3088
3302
|
if (!anchor || anchor.no_anchor === true) {
|
|
3089
3303
|
return { ...row, window: null };
|
|
@@ -3112,7 +3326,7 @@ async function attachWindows(rows, windowSize) {
|
|
|
3112
3326
|
const message = err instanceof Error ? err.message : String(err);
|
|
3113
3327
|
return { ...row, window: { error: message } };
|
|
3114
3328
|
}
|
|
3115
|
-
})
|
|
3329
|
+
}, budgetBytes, "window_skipped");
|
|
3116
3330
|
}
|
|
3117
3331
|
var TOOL_FAILURE_DETAIL_DEFAULT_LIMIT = 5;
|
|
3118
3332
|
var TOOL_FAILURE_AGENT_SUBTYPES = 20;
|
|
@@ -3121,7 +3335,8 @@ async function printWithOptionalWindows(enriched, listKey, includeWindow, window
|
|
|
3121
3335
|
let data = enriched;
|
|
3122
3336
|
if (includeWindow) {
|
|
3123
3337
|
const list = Array.isArray(enriched[listKey]) ? enriched[listKey] : [];
|
|
3124
|
-
|
|
3338
|
+
const agentMode = output.mode === "agent" || output.mode === "agent-stream";
|
|
3339
|
+
data = { ...enriched, [listKey]: await attachWindows(list, windowSize, agentMode ? agentMaxOutputBytes() : undefined) };
|
|
3125
3340
|
}
|
|
3126
3341
|
if (finalize)
|
|
3127
3342
|
data = finalize(data, allWarnings);
|
|
@@ -3179,8 +3394,10 @@ function auditCapStatus(data, limit) {
|
|
|
3179
3394
|
const nextCursor = typeof cursorValue === "string" && cursorValue ? cursorValue : null;
|
|
3180
3395
|
return { rowsCapped, spansCapped, hasMoreKnown: hasMore, rows, spans, reason, nextCursor };
|
|
3181
3396
|
}
|
|
3182
|
-
var AUDIT_MIN_PAGE_BYTES = 32768;
|
|
3183
3397
|
var AUDIT_AGENT_RESERVE_BYTES = 16384;
|
|
3398
|
+
function auditMaxBytes(budgetBytes) {
|
|
3399
|
+
return Math.max(1, budgetBytes - Math.min(AUDIT_AGENT_RESERVE_BYTES, Math.floor(budgetBytes / 4)));
|
|
3400
|
+
}
|
|
3184
3401
|
async function fetchAudit(id, query2, maxBytes) {
|
|
3185
3402
|
const pageQuery = new URLSearchParams(query2);
|
|
3186
3403
|
if (maxBytes !== undefined)
|
|
@@ -3188,16 +3405,22 @@ async function fetchAudit(id, query2, maxBytes) {
|
|
|
3188
3405
|
const queryString = pageQuery.toString() ? `?${pageQuery.toString()}` : "";
|
|
3189
3406
|
return callDataAPI(`/audit/${encodeURIComponent(id)}${queryString}`);
|
|
3190
3407
|
}
|
|
3191
|
-
function auditOutput(command, params, data) {
|
|
3192
|
-
const capped = auditCapStatus(data, params.limit ?? AUDIT_DEFAULT_LIMIT);
|
|
3408
|
+
function auditOutput(command, params, data, keep) {
|
|
3193
3409
|
const auditRecord = asRecord(data);
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3410
|
+
const pageSpans = Array.isArray(auditRecord?.spans) ? auditRecord.spans : [];
|
|
3411
|
+
const offset = params.span_offset ?? 0;
|
|
3412
|
+
const end = Math.max(offset, keep === undefined ? pageSpans.length : Math.min(pageSpans.length, offset + keep));
|
|
3413
|
+
const sliced = offset > 0 || end < pageSpans.length;
|
|
3414
|
+
const shown = Math.max(0, Math.min(end, pageSpans.length) - offset);
|
|
3415
|
+
const partial = end < pageSpans.length;
|
|
3416
|
+
const record = auditRecord && sliced ? {
|
|
3417
|
+
...auditRecord,
|
|
3418
|
+
spans: pageSpans.slice(offset, end),
|
|
3419
|
+
span_offset: offset,
|
|
3420
|
+
page_spans: pageSpans.length,
|
|
3421
|
+
...partial ? { next_span_offset: end } : {}
|
|
3422
|
+
} : auditRecord;
|
|
3197
3423
|
const limit = params.limit ?? AUDIT_DEFAULT_LIMIT;
|
|
3198
|
-
const perTrace = "audit one OTLP trace at a time with `moda traces spans <trace_id> --kind=trace` (ids in summary.trace_ids)";
|
|
3199
|
-
const warnings = [];
|
|
3200
|
-
const nextCommands = [];
|
|
3201
3424
|
const auditFlags = (extra) => [
|
|
3202
3425
|
`${canonicalCommandText(`moda ${command}`)} ${shellArg(params.id)}`,
|
|
3203
3426
|
params.kind && params.kind !== "auto" ? `--kind=${params.kind}` : "",
|
|
@@ -3205,29 +3428,75 @@ function auditOutput(command, params, data) {
|
|
|
3205
3428
|
params.include_bodies === true ? "--include-bodies" : "",
|
|
3206
3429
|
...extra
|
|
3207
3430
|
].filter(Boolean).join(" ");
|
|
3431
|
+
const sliceWarnings = [];
|
|
3432
|
+
if (offset > 0 && offset >= pageSpans.length) {
|
|
3433
|
+
sliceWarnings.push(`--span-offset=${offset} is past the ${pageSpans.length} span(s) of this page, so no spans are shown.`);
|
|
3434
|
+
} else if (sliced && !partial) {
|
|
3435
|
+
sliceWarnings.push(`Showing spans ${offset}-${end - 1}, the last part of the ${pageSpans.length} span(s) in this page.`);
|
|
3436
|
+
}
|
|
3437
|
+
if (partial && record) {
|
|
3438
|
+
const continueWith = auditFlags([
|
|
3439
|
+
params.limit ? `--limit=${params.limit}` : "",
|
|
3440
|
+
params.cursor ? `--cursor=${shellArg(params.cursor)}` : "",
|
|
3441
|
+
params.page_bytes ? `--page-bytes=${params.page_bytes}` : "",
|
|
3442
|
+
`--span-offset=${end}`
|
|
3443
|
+
]);
|
|
3444
|
+
const summary = asRecord(record.summary);
|
|
3445
|
+
return {
|
|
3446
|
+
data: {
|
|
3447
|
+
...record,
|
|
3448
|
+
truncated: true,
|
|
3449
|
+
has_more: true,
|
|
3450
|
+
next_cursor: null,
|
|
3451
|
+
...summary ? { summary: { ...summary, has_more: true, next_cursor: null } } : {}
|
|
3452
|
+
},
|
|
3453
|
+
options: {
|
|
3454
|
+
warnings: [
|
|
3455
|
+
`Showing spans ${offset}-${end - 1} of the ${pageSpans.length} in this page to stay under the agent output byte limit (--json has no limit). Read the next ${pageSpans.length - end} with: ${continueWith}. next_cursor is withheld until the last spans of this page are read. Orphan/duplicate analysis covers the whole page.`
|
|
3456
|
+
],
|
|
3457
|
+
nextCommands: [{
|
|
3458
|
+
command: continueWith,
|
|
3459
|
+
purpose: `Read spans ${end}-${pageSpans.length - 1} of this page (left out to fit the agent byte limit).`,
|
|
3460
|
+
mutability: "read",
|
|
3461
|
+
requires_approval: false
|
|
3462
|
+
}]
|
|
3463
|
+
}
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3466
|
+
const capped = auditCapStatus(data, limit);
|
|
3467
|
+
if (!capped || !record) {
|
|
3468
|
+
return {
|
|
3469
|
+
data: record ? { ...record, truncated: false, has_more: false, next_cursor: null } : data,
|
|
3470
|
+
options: sliceWarnings.length > 0 ? { warnings: sliceWarnings } : {}
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
const perTrace = "audit one OTLP trace at a time with `moda traces spans <trace_id> --kind=trace` (ids in summary.trace_ids)";
|
|
3474
|
+
const shownText = sliced ? `${shown} shown here (${pageSpans.length} in this page)` : `${capped.spans} shown`;
|
|
3475
|
+
const warnings = [...sliceWarnings];
|
|
3476
|
+
const nextCommands = [];
|
|
3208
3477
|
if (capped.nextCursor) {
|
|
3209
3478
|
nextCommands.push({
|
|
3210
|
-
command: auditFlags([params.limit ? `--limit=${params.limit}` : "", `--cursor=${capped.nextCursor}`]),
|
|
3479
|
+
command: auditFlags([params.limit ? `--limit=${params.limit}` : "", `--cursor=${shellArg(capped.nextCursor)}`]),
|
|
3211
3480
|
purpose: "Read the next page of ingest rows of this trace.",
|
|
3212
3481
|
mutability: "read",
|
|
3213
3482
|
requires_approval: false
|
|
3214
3483
|
});
|
|
3215
3484
|
}
|
|
3216
3485
|
if (capped.reason === "byte_limit") {
|
|
3217
|
-
warnings.push(`Spans were capped: the response reached the server's size budget after ${capped.rows} ingest row(s), so this trace has more spans than the ${
|
|
3486
|
+
warnings.push(`Spans were capped: the response reached the server's size budget after ${capped.rows} ingest row(s), so this trace has more spans than the ${shownText}. Continue with the next_cursor page${params.include_bodies === true ? ", or drop --include-bodies to fit more rows per page" : ""}. Orphan/duplicate analysis covers this page only.`);
|
|
3218
3487
|
} else if (capped.reason === "span_limit" && capped.nextCursor) {
|
|
3219
|
-
warnings.push(`Spans were capped: the page reached the server's per-page span cap after ${capped.rows} ingest row(s), so this trace has more spans than the ${
|
|
3488
|
+
warnings.push(`Spans were capped: the page reached the server's per-page span cap after ${capped.rows} ingest row(s), so this trace has more spans than the ${shownText}. Continue with the next_cursor page. Orphan/duplicate analysis covers this page only.`);
|
|
3220
3489
|
} else if (capped.rowsCapped) {
|
|
3221
3490
|
const bigger = Math.min(AUDIT_MAX_LIMIT, limit * 5);
|
|
3222
|
-
warnings.push(`Spans were capped: read ${capped.rows} ingest row(s), the --limit=${limit} maximum, so this trace ${capped.hasMoreKnown ? "has" : "may have"} more spans than the ${
|
|
3223
|
-
if (limit < AUDIT_MAX_LIMIT && !params.cursor) {
|
|
3491
|
+
warnings.push(`Spans were capped: read ${capped.rows} ingest row(s), the --limit=${limit} maximum, so this trace ${capped.hasMoreKnown ? "has" : "may have"} more spans than the ${shownText}. ` + (capped.nextCursor ? "Page on with the next_cursor command, or " : "") + (limit < AUDIT_MAX_LIMIT ? `${capped.nextCursor ? "rerun" : "Rerun"} with --limit=${bigger} (max ${AUDIT_MAX_LIMIT}), or ${perTrace}.` : capped.nextCursor ? `${perTrace}.` : `${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`));
|
|
3492
|
+
if (limit < AUDIT_MAX_LIMIT && !params.cursor && !sliced) {
|
|
3224
3493
|
nextCommands.push({ command: auditFlags([`--limit=${bigger}`]), purpose: "Read more ingest rows of this trace.", mutability: "read", requires_approval: false });
|
|
3225
3494
|
}
|
|
3226
3495
|
} else {
|
|
3227
3496
|
warnings.push(`Spans were capped: the server returned ${capped.spans} span(s) and marked the response truncated. ${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`);
|
|
3228
3497
|
}
|
|
3229
3498
|
return {
|
|
3230
|
-
data: { ...
|
|
3499
|
+
data: { ...record, truncated: true, has_more: capped.rowsCapped, next_cursor: capped.nextCursor },
|
|
3231
3500
|
options: { warnings, ...nextCommands.length > 0 ? { nextCommands } : {} }
|
|
3232
3501
|
};
|
|
3233
3502
|
}
|
|
@@ -3416,8 +3685,8 @@ async function runTailCommand(params, context) {
|
|
|
3416
3685
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
3417
3686
|
}
|
|
3418
3687
|
}
|
|
3419
|
-
async function attachWorldState(rows) {
|
|
3420
|
-
return
|
|
3688
|
+
async function attachWorldState(rows, budgetBytes) {
|
|
3689
|
+
return enrichWithinBudget(rows, async (row) => {
|
|
3421
3690
|
const conv_id = asString(row.conversation_id) ?? "";
|
|
3422
3691
|
if (!conv_id) {
|
|
3423
3692
|
return { ...row, world_state: null };
|
|
@@ -3429,7 +3698,7 @@ async function attachWorldState(rows) {
|
|
|
3429
3698
|
const message = err instanceof Error ? err.message : String(err);
|
|
3430
3699
|
return { ...row, world_state: { error: message } };
|
|
3431
3700
|
}
|
|
3432
|
-
})
|
|
3701
|
+
}, budgetBytes, "world_state_skipped");
|
|
3433
3702
|
}
|
|
3434
3703
|
function recordSearchTelemetry(context, data) {
|
|
3435
3704
|
const record = asRecord(data);
|
|
@@ -3539,7 +3808,7 @@ function parseCommandFlags(schema, args, command, options = {}) {
|
|
|
3539
3808
|
]);
|
|
3540
3809
|
const unknown = Object.keys(args).filter((key) => !known.has(key));
|
|
3541
3810
|
if (unknown.includes("dry_run")) {
|
|
3542
|
-
throw new CliInputError(`'moda ${command}' has no dry-run mode, so nothing was sent.`, `Remove --dry-run
|
|
3811
|
+
throw new CliInputError(`'moda ${command}' has no dry-run mode (it does not write), so nothing was sent.`, `Remove --dry-run. Every write takes --dry-run: problems feedback|reopen|link, review, feedback, signals <write subcommands>, sync, registry push, harness sync/delete/register, prompts sync/decide, skills sync/policy.`, "unsupported_dry_run");
|
|
3543
3812
|
}
|
|
3544
3813
|
if (unknown.length > 0) {
|
|
3545
3814
|
const flagKeys = [
|
|
@@ -3575,8 +3844,9 @@ function parseCommandFlags(schema, args, command, options = {}) {
|
|
|
3575
3844
|
suggestedCommands: [helpCommand]
|
|
3576
3845
|
});
|
|
3577
3846
|
}
|
|
3847
|
+
const typed = coerceNumericFlags(schema, args, command);
|
|
3578
3848
|
try {
|
|
3579
|
-
return schema.parse(
|
|
3849
|
+
return schema.parse(typed);
|
|
3580
3850
|
} catch (error) {
|
|
3581
3851
|
if (error instanceof z.ZodError)
|
|
3582
3852
|
attachFlagDetails(error, schema, command);
|
|
@@ -3608,20 +3878,6 @@ function specParseOptions(spec) {
|
|
|
3608
3878
|
...spec.flagAliases ? { flagAliases: spec.flagAliases } : {}
|
|
3609
3879
|
};
|
|
3610
3880
|
}
|
|
3611
|
-
function withStringIdFlags(args, flags) {
|
|
3612
|
-
for (const flag of ["user-id", "conversation-id"]) {
|
|
3613
|
-
if (flags[flag] !== undefined)
|
|
3614
|
-
args[flag.replace(/-/g, "_")] = flags[flag];
|
|
3615
|
-
}
|
|
3616
|
-
return args;
|
|
3617
|
-
}
|
|
3618
|
-
function shellArg(value) {
|
|
3619
|
-
if (/^[A-Za-z0-9_.,:@%+\/=-]+$/.test(value))
|
|
3620
|
-
return value;
|
|
3621
|
-
if (!/["$`\\!]/.test(value))
|
|
3622
|
-
return `"${value}"`;
|
|
3623
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
3624
|
-
}
|
|
3625
3881
|
function semanticIndexCoverage(coverage) {
|
|
3626
3882
|
const ratio = asRecord(asRecord(coverage)?.semantic_index)?.coverage_ratio;
|
|
3627
3883
|
if (typeof ratio !== "number" || !Number.isFinite(ratio) || ratio < 0 || ratio > 1)
|
|
@@ -3722,8 +3978,7 @@ async function searchFallbackToTraces(params) {
|
|
|
3722
3978
|
return;
|
|
3723
3979
|
const pagination = asRecord(traces.pagination);
|
|
3724
3980
|
const total = asNumber(pagination?.total) ?? rows.length;
|
|
3725
|
-
const
|
|
3726
|
-
const hasMore = pagination?.has_more === true || (asNumber(pagination?.total) ?? 0) > nextOffset;
|
|
3981
|
+
const serverHasMore = pagination?.has_more === true;
|
|
3727
3982
|
const traceMode = asString(traces.search_mode) ?? "keyword";
|
|
3728
3983
|
const literal = traceMode === "keyword";
|
|
3729
3984
|
if (params.mode === "keyword" && !literal) {
|
|
@@ -3745,33 +4000,46 @@ async function searchFallbackToTraces(params) {
|
|
|
3745
4000
|
}));
|
|
3746
4001
|
const first = asString(rows[0].conversation_id);
|
|
3747
4002
|
const describe = literal ? "trace-level keyword matches (each trace contains the words)" : `trace-level ${traceMode}-ranked related traces (they may not contain the exact words; fallback.total is the candidate pool, not a match count)`;
|
|
3748
|
-
const
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
4003
|
+
const page = (kept) => {
|
|
4004
|
+
const omitted = rows.length - kept;
|
|
4005
|
+
const nextOffset = offset + kept;
|
|
4006
|
+
const hasMore = omitted > 0 || serverHasMore || total > nextOffset;
|
|
4007
|
+
const nextCommands = [];
|
|
4008
|
+
if (first) {
|
|
4009
|
+
nextCommands.push({ command: `moda traces spans ${shellArg(first)}`, purpose: "Open the top trace to locate the relevant message.", mutability: "read", requires_approval: false });
|
|
4010
|
+
}
|
|
4011
|
+
if (hasMore) {
|
|
4012
|
+
nextCommands.push({
|
|
4013
|
+
command: `${tracesCommand} --offset=${nextOffset}`,
|
|
4014
|
+
purpose: omitted > 0 ? `Read the ${omitted} trace-level result(s) left out to fit the agent byte limit, and the ones after them.` : "Page through further trace-level results.",
|
|
4015
|
+
mutability: "read",
|
|
4016
|
+
requires_approval: false
|
|
4017
|
+
});
|
|
4018
|
+
}
|
|
4019
|
+
return {
|
|
4020
|
+
returned: kept,
|
|
4021
|
+
offset,
|
|
4022
|
+
hasMore,
|
|
4023
|
+
data: {
|
|
4024
|
+
results: results.slice(0, kept),
|
|
4025
|
+
fallback: {
|
|
4026
|
+
source: "traces_search",
|
|
4027
|
+
reason: "message_index_empty",
|
|
4028
|
+
grain: "trace",
|
|
4029
|
+
search_mode: traceMode,
|
|
4030
|
+
total,
|
|
4031
|
+
total_kind: literal ? "matches" : "candidate_pool",
|
|
4032
|
+
has_more: hasMore,
|
|
4033
|
+
...offset > 0 ? { offset } : {},
|
|
4034
|
+
...hasMore ? { next_offset: nextOffset } : {},
|
|
4035
|
+
...omitted > 0 ? { omitted_for_byte_limit: omitted } : {}
|
|
4036
|
+
}
|
|
4037
|
+
},
|
|
4038
|
+
warning: `Message-level search returned 0 results, so these are ${describe}, from \`moda traces --search\`. Each result is a whole trace, not a message, with no score or message index. Open one with \`moda traces spans <conversation_id>\` or \`moda traces get <conversation_id>\` to find the matching turn.` + (omitted > 0 ? ` ${kept} of the ${rows.length} trace-level results read are shown, to stay under the agent output byte limit (--json has no limit); the rest start at --offset=${nextOffset}.` : "") + (hasMore ? " More exist: page with the suggested `moda traces --search ... --offset` command." : ""),
|
|
4039
|
+
nextCommands
|
|
4040
|
+
};
|
|
3774
4041
|
};
|
|
4042
|
+
return { kind: "results", rows: rows.length, page };
|
|
3775
4043
|
}
|
|
3776
4044
|
function capProblemsBlocks(data, limit) {
|
|
3777
4045
|
const truncated = {};
|
|
@@ -3822,6 +4090,31 @@ var NO_POSITIONAL_COMMANDS = new Set([
|
|
|
3822
4090
|
"tail",
|
|
3823
4091
|
"problem-impact"
|
|
3824
4092
|
]);
|
|
4093
|
+
function rejectInapplicableFlags(command, params, keys, why) {
|
|
4094
|
+
const given = keys.filter((key) => params[key] !== undefined);
|
|
4095
|
+
if (given.length === 0)
|
|
4096
|
+
return;
|
|
4097
|
+
const names = given.map(kebabFlag);
|
|
4098
|
+
const shown = canonicalCommandText(`moda ${command}`);
|
|
4099
|
+
throw new CliInputError(`${names.join(", ")} ${given.length > 1 ? "do" : "does"} not apply ${why}, so nothing was sent.`, `Drop ${names.join(", ")}, or pick the mode that uses ${given.length > 1 ? "them" : "it"}. See '${shown} --help'.`, `inapplicable_flag:${names[0].slice(2)}`, { field: names[0], suggestedCommands: [`${shown} --help`] });
|
|
4100
|
+
}
|
|
4101
|
+
var BATCH_POSITIONAL_COMMANDS = new Set(["context", "audit", "trace"]);
|
|
4102
|
+
function assertPositionalArity(command, positionals) {
|
|
4103
|
+
const spec = flagSpecFor(command);
|
|
4104
|
+
const expected = spec?.positionals ?? [];
|
|
4105
|
+
if (expected.length === 0 || expected.some((entry) => entry.variadic))
|
|
4106
|
+
return;
|
|
4107
|
+
if (BATCH_POSITIONAL_COMMANDS.has(command))
|
|
4108
|
+
return;
|
|
4109
|
+
if (command === "problem" && positionals[0] === "remainder")
|
|
4110
|
+
return;
|
|
4111
|
+
if (positionals.length <= expected.length)
|
|
4112
|
+
return;
|
|
4113
|
+
const extra = positionals.slice(expected.length);
|
|
4114
|
+
const shown = canonicalCommandText(`moda ${command}`);
|
|
4115
|
+
const takes = expected.map((entry) => `<${entry.name}>`).join(" ");
|
|
4116
|
+
throw new CliInputError(`Unexpected argument${extra.length > 1 ? "s" : ""} '${extra.join(" ")}' for '${shown}', which takes ${takes}, so nothing was sent.`, `Quote a value that has spaces, and pass free text as a flag value (e.g. --reason="..."). Usage: ${canonicalCommandText(usageLine(command, spec))}`, "unexpected_positional", { field: `<${expected[expected.length - 1].name}>`, suggestedCommands: [`${shown} --help`] });
|
|
4117
|
+
}
|
|
3825
4118
|
function assertHandParsedFlags(command, flags, positionals) {
|
|
3826
4119
|
if (command === "signals" && positionals[0]) {
|
|
3827
4120
|
if (SIGNALS_MANAGEMENT_SUBCOMMANDS.has(positionals[0]))
|
|
@@ -3831,6 +4124,7 @@ function assertHandParsedFlags(command, flags, positionals) {
|
|
|
3831
4124
|
if (NO_POSITIONAL_COMMANDS.has(command) && positionals.length > 0) {
|
|
3832
4125
|
throw new CliInputError(`'moda ${command}' takes no arguments, but got '${positionals.join(" ")}'.`, `Nothing was run. See 'moda ${command} --help' for its flags.`);
|
|
3833
4126
|
}
|
|
4127
|
+
assertPositionalArity(command, positionals);
|
|
3834
4128
|
const hand = handParsedFlags(command);
|
|
3835
4129
|
if (!hand)
|
|
3836
4130
|
return;
|
|
@@ -3959,6 +4253,10 @@ async function readContextMetadataOnly(contextPath, query2) {
|
|
|
3959
4253
|
}
|
|
3960
4254
|
var FULL_TRANSCRIPT_DEFAULT_MAX_MESSAGES = 500;
|
|
3961
4255
|
var CONTEXT_RANGE_PAGE_SIZE = 200;
|
|
4256
|
+
var CONTEXT_BUDGET_BYTES_PER_FIRST_PAGE_MESSAGE = 480;
|
|
4257
|
+
function jsonByteLength(value) {
|
|
4258
|
+
return Buffer.byteLength(JSON.stringify(value) ?? "", "utf8");
|
|
4259
|
+
}
|
|
3962
4260
|
var LEGACY_WINDOW_MAX_MESSAGES = 100;
|
|
3963
4261
|
var CONTEXT_MESSAGE_TRUNCATE_CHARS = 500;
|
|
3964
4262
|
var LONE_SURROGATE = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
|
|
@@ -3996,8 +4294,15 @@ async function readFullTranscript(conversationId, opts = {}) {
|
|
|
3996
4294
|
let nextFrom = null;
|
|
3997
4295
|
let legacy = false;
|
|
3998
4296
|
let cursor = start;
|
|
4297
|
+
let readBytes = 0;
|
|
4298
|
+
const byteBudget = opts.fits ? opts.maxBytes : undefined;
|
|
3999
4299
|
for (;; ) {
|
|
4000
|
-
|
|
4300
|
+
let count = Math.min(CONTEXT_RANGE_PAGE_SIZE, requested - messages.length);
|
|
4301
|
+
if (byteBudget !== undefined) {
|
|
4302
|
+
const average = messages.length > 0 ? readBytes / messages.length : 0;
|
|
4303
|
+
const estimate = average > 0 ? Math.ceil(Math.max(0, byteBudget - readBytes) / average) + 1 : Math.max(1, Math.floor(byteBudget / CONTEXT_BUDGET_BYTES_PER_FIRST_PAGE_MESSAGE));
|
|
4304
|
+
count = Math.max(1, Math.min(count, estimate));
|
|
4305
|
+
}
|
|
4001
4306
|
let page;
|
|
4002
4307
|
try {
|
|
4003
4308
|
page = asRecord(await callDataAPI(`${path}?from=${cursor}&count=${count}`)) ?? {};
|
|
@@ -4015,10 +4320,14 @@ async function readFullTranscript(conversationId, opts = {}) {
|
|
|
4015
4320
|
}
|
|
4016
4321
|
const batch = pageMessages(page);
|
|
4017
4322
|
messages.push(...batch);
|
|
4323
|
+
if (byteBudget !== undefined)
|
|
4324
|
+
readBytes += batch.reduce((sum, message) => sum + jsonByteLength(message) + 1, 0);
|
|
4018
4325
|
const next = asNumber(page.next_from);
|
|
4019
4326
|
nextFrom = page.has_more === true && next !== null && next !== undefined && next > cursor ? next : null;
|
|
4020
4327
|
if (nextFrom === null || batch.length === 0 || messages.length >= requested)
|
|
4021
4328
|
break;
|
|
4329
|
+
if (byteBudget !== undefined && readBytes > byteBudget)
|
|
4330
|
+
break;
|
|
4022
4331
|
cursor = nextFrom;
|
|
4023
4332
|
}
|
|
4024
4333
|
if (legacy) {
|
|
@@ -4115,12 +4424,14 @@ async function readFullTranscript(conversationId, opts = {}) {
|
|
|
4115
4424
|
}
|
|
4116
4425
|
return build(low);
|
|
4117
4426
|
}
|
|
4118
|
-
async function readContextItem(command, params, inputWarnings,
|
|
4427
|
+
async function readContextItem(command, params, inputWarnings, budget) {
|
|
4428
|
+
const fits = budget?.fits;
|
|
4119
4429
|
if (params.all === true) {
|
|
4120
4430
|
const full = await readFullTranscript(params.conversation_id, {
|
|
4121
4431
|
from: params.from,
|
|
4122
4432
|
maxMessages: params.max_messages,
|
|
4123
|
-
fits: fits ? (page) => fits(page.data, { warnings: page.warnings, nextCommands: page.nextCommands }) : undefined
|
|
4433
|
+
fits: fits ? (page) => fits(page.data, { warnings: page.warnings, nextCommands: page.nextCommands }) : undefined,
|
|
4434
|
+
maxBytes: budget?.maxBytes
|
|
4124
4435
|
});
|
|
4125
4436
|
const fullWarnings = [...inputWarnings, ...full.warnings];
|
|
4126
4437
|
return {
|
|
@@ -4154,13 +4465,85 @@ async function readContextItem(command, params, inputWarnings, fits) {
|
|
|
4154
4465
|
warnings.push(`--window=${params.window} is above the maximum of ${CONTEXT_MAX_WINDOW}, so this shows ±${CONTEXT_MAX_WINDOW} messages. For a wider slice run: ${wide}`);
|
|
4155
4466
|
nextCommands.push({ command: wide, purpose: `Read ±${params.window} messages around the anchor.`, mutability: "read", requires_approval: false });
|
|
4156
4467
|
}
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
...
|
|
4468
|
+
const options = (extraWarnings = [], extraNext = []) => {
|
|
4469
|
+
const allWarnings = [...warnings, ...extraWarnings];
|
|
4470
|
+
const allNext = [...nextCommands, ...extraNext];
|
|
4471
|
+
return {
|
|
4472
|
+
...allWarnings.length > 0 ? { warnings: allWarnings } : {},
|
|
4473
|
+
...allNext.length > 0 ? { nextCommands: allNext } : {}
|
|
4474
|
+
};
|
|
4475
|
+
};
|
|
4476
|
+
if (fits && params.metadata_only !== true && !fits(data, options())) {
|
|
4477
|
+
const trimmed = trimContextWindow(params.conversation_id, ctxRecord, params.msg_index, (page, extraWarnings, extraNext) => fits(page, options(extraWarnings, extraNext)));
|
|
4478
|
+
if (trimmed)
|
|
4479
|
+
return { data: trimmed.data, options: options(trimmed.warnings, trimmed.nextCommands) };
|
|
4480
|
+
}
|
|
4481
|
+
return { data, options: options() };
|
|
4482
|
+
}
|
|
4483
|
+
function trimContextWindow(conversationId, record, requestedAnchor, fits) {
|
|
4484
|
+
const ctx = asRecord(record.context);
|
|
4485
|
+
const messages = Array.isArray(ctx?.messages) ? ctx.messages : [];
|
|
4486
|
+
if (!ctx || messages.length <= 1)
|
|
4487
|
+
return;
|
|
4488
|
+
const firstIndex = asNumber(ctx.from_index) ?? asNumber(asRecord(messages[0])?.index) ?? 0;
|
|
4489
|
+
const indexed = messages.map((message, position) => ({
|
|
4490
|
+
message,
|
|
4491
|
+
index: asNumber(asRecord(message)?.index) ?? firstIndex + position
|
|
4492
|
+
}));
|
|
4493
|
+
const center = asNumber(ctx.center_index) ?? requestedAnchor ?? indexed[Math.floor(indexed.length / 2)].index;
|
|
4494
|
+
const requestedFrom = Math.min(asNumber(ctx.from_index) ?? indexed[0].index, indexed[0].index);
|
|
4495
|
+
const requestedTo = Math.max(asNumber(ctx.to_index) ?? indexed[indexed.length - 1].index, indexed[indexed.length - 1].index);
|
|
4496
|
+
const order = [...indexed].sort((left, right) => Math.abs(left.index - center) - Math.abs(right.index - center) || right.index - left.index);
|
|
4497
|
+
const target = shellArg(contextTarget(conversationId));
|
|
4498
|
+
const build = (kept) => {
|
|
4499
|
+
const returned = order.slice(0, kept).sort((left, right) => left.index - right.index);
|
|
4500
|
+
const from = returned[0].index;
|
|
4501
|
+
const to = returned[returned.length - 1].index;
|
|
4502
|
+
const before = from - requestedFrom;
|
|
4503
|
+
const after = requestedTo - to;
|
|
4504
|
+
const nextCommands = [];
|
|
4505
|
+
if (before > 0) {
|
|
4506
|
+
nextCommands.push({
|
|
4507
|
+
command: `moda traces get ${target} --all --from=${requestedFrom} --max-messages=${before}`,
|
|
4508
|
+
purpose: `Read the ${before} message(s) before ${from} that were left out to fit the agent byte limit.`,
|
|
4509
|
+
mutability: "read",
|
|
4510
|
+
requires_approval: false
|
|
4511
|
+
});
|
|
4162
4512
|
}
|
|
4513
|
+
if (after > 0) {
|
|
4514
|
+
nextCommands.push({
|
|
4515
|
+
command: `moda traces get ${target} --all --from=${to + 1} --max-messages=${after}`,
|
|
4516
|
+
purpose: `Read the ${after} message(s) after ${to} that were left out to fit the agent byte limit.`,
|
|
4517
|
+
mutability: "read",
|
|
4518
|
+
requires_approval: false
|
|
4519
|
+
});
|
|
4520
|
+
}
|
|
4521
|
+
const warnings = kept < order.length ? [
|
|
4522
|
+
`Window cut to messages ${from}-${to} around anchor ${center} to stay under the agent output byte limit (--json has no limit); requested ${requestedFrom}-${requestedTo}. Read the rest with: ${nextCommands.map((next) => next.command).join(" and ")}.`
|
|
4523
|
+
] : [];
|
|
4524
|
+
const data = {
|
|
4525
|
+
...record,
|
|
4526
|
+
context: {
|
|
4527
|
+
...ctx,
|
|
4528
|
+
from_index: from,
|
|
4529
|
+
to_index: to,
|
|
4530
|
+
messages: returned.map((entry) => entry.message),
|
|
4531
|
+
...kept < order.length ? { requested_from_index: requestedFrom, requested_to_index: requestedTo, omitted_for_byte_limit: order.length - kept } : {}
|
|
4532
|
+
}
|
|
4533
|
+
};
|
|
4534
|
+
return { data, warnings, nextCommands };
|
|
4163
4535
|
};
|
|
4536
|
+
let low = 1;
|
|
4537
|
+
let high = order.length;
|
|
4538
|
+
while (low < high) {
|
|
4539
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4540
|
+
const page = build(mid);
|
|
4541
|
+
if (fits(page.data, page.warnings, page.nextCommands))
|
|
4542
|
+
low = mid;
|
|
4543
|
+
else
|
|
4544
|
+
high = mid - 1;
|
|
4545
|
+
}
|
|
4546
|
+
return build(low);
|
|
4164
4547
|
}
|
|
4165
4548
|
var AUDIT_USAGE = "Usage: moda traces spans <conversation_id|trace_id> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] [--cursor=<next_cursor>]";
|
|
4166
4549
|
var AUDIT_BATCH_USAGE = `Usage: moda traces spans <id> <id> ... | --ids-from=-|<file> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] (max ${BATCH_MAX_TARGETS} targets)`;
|
|
@@ -4176,16 +4559,31 @@ async function readAuditItem(command, params, budget) {
|
|
|
4176
4559
|
query2.set("limit", params.limit.toString());
|
|
4177
4560
|
if (params.cursor)
|
|
4178
4561
|
query2.set("cursor", params.cursor);
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4562
|
+
const pageBytes = params.page_bytes ?? (budget ? auditMaxBytes(budget.maxBytes) : undefined);
|
|
4563
|
+
const raw = await fetchAudit(params.id, query2, pageBytes);
|
|
4564
|
+
params = { ...params, page_bytes: pageBytes };
|
|
4565
|
+
const whole = auditOutput(command, params, raw);
|
|
4566
|
+
if (!budget || budget.fits(whole.data, whole.options))
|
|
4567
|
+
return whole;
|
|
4568
|
+
const spans = asRecord(raw)?.spans;
|
|
4569
|
+
const available = (Array.isArray(spans) ? spans.length : 0) - (params.span_offset ?? 0);
|
|
4570
|
+
if (available <= 0)
|
|
4571
|
+
return whole;
|
|
4572
|
+
let low = 1;
|
|
4573
|
+
let high = available;
|
|
4574
|
+
while (low < high) {
|
|
4575
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4576
|
+
const page2 = auditOutput(command, params, raw, mid);
|
|
4577
|
+
if (budget.fits(page2.data, page2.options))
|
|
4578
|
+
low = mid;
|
|
4579
|
+
else
|
|
4580
|
+
high = mid - 1;
|
|
4184
4581
|
}
|
|
4185
|
-
|
|
4582
|
+
const page = auditOutput(command, params, raw, low);
|
|
4583
|
+
if (low === 1 && !budget.fits(page.data, page.options)) {
|
|
4186
4584
|
page.options.warnings = [
|
|
4187
4585
|
...page.options.warnings ?? [],
|
|
4188
|
-
"
|
|
4586
|
+
"One span of this page is larger than the agent output budget, so agent output shortens it. Rerun this command with --json (no byte limit) or a higher MODA_AGENT_MAX_OUTPUT_BYTES to see it whole."
|
|
4189
4587
|
];
|
|
4190
4588
|
}
|
|
4191
4589
|
return page;
|
|
@@ -4202,6 +4600,16 @@ function singleContextTarget(positional, flags) {
|
|
|
4202
4600
|
}
|
|
4203
4601
|
return [target.id, { ...flags, "msg-index": String(target.msgIndex) }];
|
|
4204
4602
|
}
|
|
4603
|
+
async function withAnchorHint(target, read) {
|
|
4604
|
+
try {
|
|
4605
|
+
return await read();
|
|
4606
|
+
} catch (error) {
|
|
4607
|
+
if (!(error instanceof CliNotFoundError) || target.msgIndex === undefined || target.raw.startsWith("{"))
|
|
4608
|
+
throw error;
|
|
4609
|
+
const verbatim = JSON.stringify({ id: target.raw });
|
|
4610
|
+
throw new CliNotFoundError(error.entity, error.id, `${error.message} '${target.raw}' was read as trace "${target.id}" anchored at message ${target.msgIndex} (the @N suffix is a message index). If "${target.raw}" is the whole trace id, pass it as ${shellArg(verbatim)} or --conversation-id=${shellArg(target.raw)}.`, `moda traces get ${shellArg(verbatim)}`);
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4205
4613
|
function isBatchRead(positionals, flags) {
|
|
4206
4614
|
return positionals.length > 1 || IDS_FROM_KEYS.some((key) => flags[key] !== undefined);
|
|
4207
4615
|
}
|
|
@@ -4216,14 +4624,41 @@ function splitIdsFrom(flags) {
|
|
|
4216
4624
|
}
|
|
4217
4625
|
return { idsFrom, rest };
|
|
4218
4626
|
}
|
|
4219
|
-
function auditItemByteShare(count) {
|
|
4220
|
-
return Math.max(AUDIT_MIN_PAGE_BYTES, Math.floor((agentMaxOutputBytes() - AUDIT_AGENT_RESERVE_BYTES) / count));
|
|
4221
|
-
}
|
|
4222
4627
|
var BATCH_ENVELOPE_RESERVE_BYTES = 4096;
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4628
|
+
function batchTargetReserveBytes(target) {
|
|
4629
|
+
return 3 * (jsonByteLength(target.raw) + 4) + 16;
|
|
4630
|
+
}
|
|
4631
|
+
var BATCH_MIN_ITEM_SHARE_BYTES = 4096;
|
|
4632
|
+
async function readBatchTargets(targets, agentMode, read) {
|
|
4633
|
+
if (!agentMode) {
|
|
4634
|
+
return mapWithConcurrency(targets, BATCH_CONCURRENCY, (target) => readBatchItem(target, () => read(target, undefined)));
|
|
4635
|
+
}
|
|
4636
|
+
const reserve = targets.reduce((sum, target) => sum + batchTargetReserveBytes(target), BATCH_ENVELOPE_RESERVE_BYTES);
|
|
4637
|
+
const available = Math.max(1, agentMaxOutputBytes() - reserve);
|
|
4638
|
+
const fair = Math.max(BATCH_MIN_ITEM_SHARE_BYTES, Math.floor(available / targets.length));
|
|
4639
|
+
let used = 0;
|
|
4640
|
+
let reserved = 0;
|
|
4641
|
+
const shares = new Map;
|
|
4642
|
+
return mapWithConcurrency(targets, BATCH_CONCURRENCY, async (target, index) => {
|
|
4643
|
+
const share = shares.get(index) ?? fair;
|
|
4644
|
+
try {
|
|
4645
|
+
const result = await readBatchItem(target, () => read(target, { fits: fitsShare(share), maxBytes: share }));
|
|
4646
|
+
used += jsonByteLength(result) + 1;
|
|
4647
|
+
return result;
|
|
4648
|
+
} finally {
|
|
4649
|
+
reserved -= share;
|
|
4650
|
+
}
|
|
4651
|
+
}, {
|
|
4652
|
+
shouldStart: (index) => {
|
|
4653
|
+
const free = available - used - reserved;
|
|
4654
|
+
if (index > 0 && free < BATCH_MIN_ITEM_SHARE_BYTES)
|
|
4655
|
+
return false;
|
|
4656
|
+
const share = Math.max(1, Math.min(fair, free));
|
|
4657
|
+
shares.set(index, share);
|
|
4658
|
+
reserved += share;
|
|
4659
|
+
return true;
|
|
4660
|
+
}
|
|
4661
|
+
});
|
|
4227
4662
|
}
|
|
4228
4663
|
function fitsShare(share) {
|
|
4229
4664
|
return (data, options) => Buffer.byteLength(JSON.stringify({ data, ...options }) ?? "", "utf8") <= share;
|
|
@@ -4335,27 +4770,25 @@ async function runContextBatch(command, positionals, flags, context) {
|
|
|
4335
4770
|
throw new CliInputError(`${base.all === true ? "--all reads whole traces" : "--metadata-only reads no messages"}, so targets cannot carry an @msg_index anchor; drop the flag or the anchors.`, CONTEXT_BATCH_USAGE, "flag_conflict");
|
|
4336
4771
|
}
|
|
4337
4772
|
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4338
|
-
const
|
|
4339
|
-
const results = await mapWithConcurrency(targets, BATCH_CONCURRENCY, (target) => readBatchItem(target, () => {
|
|
4773
|
+
const results = await readBatchTargets(targets, agentMode, (target, budget) => {
|
|
4340
4774
|
const params = {
|
|
4341
4775
|
...base,
|
|
4342
4776
|
conversation_id: target.id,
|
|
4343
4777
|
...target.msgIndex !== undefined ? { msg_index: target.msgIndex } : {}
|
|
4344
4778
|
};
|
|
4345
|
-
return readContextItem(command, params, [],
|
|
4346
|
-
})
|
|
4779
|
+
return withAnchorHint(target, () => readContextItem(command, params, [], budget));
|
|
4780
|
+
});
|
|
4347
4781
|
return writeBatchResult(command, context, targets, results, inputWarnings, rest);
|
|
4348
4782
|
}
|
|
4349
4783
|
async function runAuditBatch(command, positionals, flags, context) {
|
|
4350
4784
|
const { idsFrom, rest } = splitIdsFrom(flags);
|
|
4351
|
-
if (rest.cursor !== undefined) {
|
|
4352
|
-
throw new CliInputError("--cursor continues one trace, so it cannot be used with several ids; follow each item's next_commands instead
|
|
4785
|
+
if (rest.cursor !== undefined || Object.keys(rest).some((key) => key.replace(/_/g, "-") === "span-offset")) {
|
|
4786
|
+
throw new CliInputError(`${rest.cursor !== undefined ? "--cursor" : "--span-offset"} continues one trace, so it cannot be used with several ids; follow each item's next_commands instead.`, AUDIT_BATCH_USAGE, "flag_conflict");
|
|
4353
4787
|
}
|
|
4354
4788
|
const targets = await collectBatchTargets(positionals, idsFrom, { anchors: false, usage: AUDIT_BATCH_USAGE, command });
|
|
4355
4789
|
const base = parseCommandFlags(AuditSchema, flagsToArgs(rest, "id", targets[0].id), command, { positionalKey: "id" });
|
|
4356
4790
|
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4357
|
-
const
|
|
4358
|
-
const results = await mapWithConcurrency(targets, BATCH_CONCURRENCY, (target) => readBatchItem(target, () => readAuditItem(command, { ...base, id: target.id }, share !== undefined ? { maxBytes: Math.max(AUDIT_MIN_PAGE_BYTES, share), fits: fitsShare(share) } : undefined)));
|
|
4791
|
+
const results = await readBatchTargets(targets, agentMode, (target, budget) => readAuditItem(command, { ...base, id: target.id }, budget));
|
|
4359
4792
|
return writeBatchResult(command, context, targets, results, [], rest);
|
|
4360
4793
|
}
|
|
4361
4794
|
async function runCommand(command, positional, flags, positionals = positional ? [positional] : [], context = createCommandContext({ command, positional, positionals, flags })) {
|
|
@@ -4378,6 +4811,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4378
4811
|
const args = flagsToArgs(flags);
|
|
4379
4812
|
const params = parseSpecFlags("clusters", args, command);
|
|
4380
4813
|
if (params.search !== undefined || params.node_id !== undefined) {
|
|
4814
|
+
rejectInapplicableFlags(command, params, ["parent_id", "time_range"], "to a cluster search (--search/--node-id); they page the cluster hierarchy");
|
|
4381
4815
|
const query3 = new URLSearchParams;
|
|
4382
4816
|
if (params.search !== undefined)
|
|
4383
4817
|
query3.set("q", params.search);
|
|
@@ -4394,6 +4828,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4394
4828
|
context.output.writeData(data2, warnings.length > 0 ? { warnings } : undefined);
|
|
4395
4829
|
break;
|
|
4396
4830
|
}
|
|
4831
|
+
rejectInapplicableFlags(command, params, ["mode", "limit"], "without --search or --node-id; they shape a cluster search");
|
|
4397
4832
|
const query2 = new URLSearchParams;
|
|
4398
4833
|
if (params.parent_id)
|
|
4399
4834
|
query2.set("parent_id", params.parent_id);
|
|
@@ -4425,7 +4860,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4425
4860
|
}
|
|
4426
4861
|
case "traces":
|
|
4427
4862
|
case "conversations": {
|
|
4428
|
-
const args =
|
|
4863
|
+
const args = flagsToArgs(flags);
|
|
4429
4864
|
const params = parseSpecFlags("traces", args, command);
|
|
4430
4865
|
const query2 = new URLSearchParams;
|
|
4431
4866
|
if (params.search)
|
|
@@ -4476,7 +4911,8 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4476
4911
|
if (params.include_world_state === true) {
|
|
4477
4912
|
const enriched = asRecord(data) ?? {};
|
|
4478
4913
|
const list = Array.isArray(enriched.conversations) ? enriched.conversations : [];
|
|
4479
|
-
const
|
|
4914
|
+
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4915
|
+
const withState = await attachWorldState(list, agentMode ? agentMaxOutputBytes() : undefined);
|
|
4480
4916
|
context.output.writeData({ ...enriched, conversations: withState }, writeOptions);
|
|
4481
4917
|
} else {
|
|
4482
4918
|
context.output.writeData(data, writeOptions);
|
|
@@ -4484,10 +4920,11 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4484
4920
|
break;
|
|
4485
4921
|
}
|
|
4486
4922
|
case "search": {
|
|
4487
|
-
|
|
4923
|
+
const queryText = positionals.join(" ");
|
|
4924
|
+
if (queryText.trim() === "") {
|
|
4488
4925
|
throw new CliInputError("<query> is required", 'Usage: moda traces search "<query>" [--mode=keyword|semantic|hybrid] [--user-id=ID] [--conversation-id=ID] [--time-range=RANGE] [--since=DATE] [--until=DATE] [--include-tool-io] [--limit=N]');
|
|
4489
4926
|
}
|
|
4490
|
-
const args =
|
|
4927
|
+
const args = flagsToArgs(flags, "q", queryText);
|
|
4491
4928
|
const params = parseSpecFlags("search", args, command);
|
|
4492
4929
|
const query2 = new URLSearchParams;
|
|
4493
4930
|
query2.set("q", params.q);
|
|
@@ -4524,15 +4961,42 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4524
4961
|
break;
|
|
4525
4962
|
}
|
|
4526
4963
|
if (fallback?.kind === "results") {
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4964
|
+
const write = (kept2) => {
|
|
4965
|
+
const page2 = fallback.page(kept2);
|
|
4966
|
+
const merged = {
|
|
4967
|
+
...searchRecord,
|
|
4968
|
+
...page2.data,
|
|
4969
|
+
pagination: {
|
|
4970
|
+
...asRecord(searchRecord.pagination) ?? {},
|
|
4971
|
+
returned: page2.returned,
|
|
4972
|
+
has_more: page2.hasMore,
|
|
4973
|
+
offset: page2.offset,
|
|
4974
|
+
...page2.returned < fallback.rows ? { omitted_for_byte_limit: fallback.rows - page2.returned } : {}
|
|
4975
|
+
}
|
|
4976
|
+
};
|
|
4977
|
+
return { data: merged, options: { warnings: [...warnings, page2.warning], nextCommands: page2.nextCommands } };
|
|
4978
|
+
};
|
|
4979
|
+
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4980
|
+
let kept = fallback.rows;
|
|
4981
|
+
const fitsAgent = (count) => {
|
|
4982
|
+
const page2 = write(count);
|
|
4983
|
+
return agentEnvelopeFits(command, page2.data, page2.options);
|
|
4533
4984
|
};
|
|
4534
|
-
|
|
4535
|
-
|
|
4985
|
+
if (agentMode && kept > 1 && !fitsAgent(kept)) {
|
|
4986
|
+
let low = 1;
|
|
4987
|
+
let high = kept - 1;
|
|
4988
|
+
while (low < high) {
|
|
4989
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4990
|
+
if (fitsAgent(mid))
|
|
4991
|
+
low = mid;
|
|
4992
|
+
else
|
|
4993
|
+
high = mid - 1;
|
|
4994
|
+
}
|
|
4995
|
+
kept = low;
|
|
4996
|
+
}
|
|
4997
|
+
const page = write(kept);
|
|
4998
|
+
recordSearchTelemetry(context, page.data);
|
|
4999
|
+
context.output.writeData(page.data, page.options);
|
|
4536
5000
|
break;
|
|
4537
5001
|
}
|
|
4538
5002
|
}
|
|
@@ -4590,7 +5054,8 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4590
5054
|
return runContextBatch(command, positionals, flags, context);
|
|
4591
5055
|
const { params, warnings: inputWarnings } = parseContextRequest(...singleContextTarget(positional, flags));
|
|
4592
5056
|
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4593
|
-
const
|
|
5057
|
+
const read = () => readContextItem(command, params, inputWarnings, agentMode ? { fits: (data, options) => agentEnvelopeFits(command, data, options), maxBytes: agentMaxOutputBytes() } : undefined);
|
|
5058
|
+
const item = positional === undefined ? await read() : await withAnchorHint(parseBatchTarget(positional, true, CONTEXT_USAGE), read);
|
|
4594
5059
|
context.output.writeData(item.data, item.options);
|
|
4595
5060
|
break;
|
|
4596
5061
|
}
|
|
@@ -4604,15 +5069,12 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4604
5069
|
const args = flagsToArgs(flags, "id", positional);
|
|
4605
5070
|
const params = parseSpecFlags("audit", args, command);
|
|
4606
5071
|
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4607
|
-
const page = await readAuditItem(command, params, agentMode ? {
|
|
4608
|
-
maxBytes: Math.max(AUDIT_MIN_PAGE_BYTES, agentMaxOutputBytes() - AUDIT_AGENT_RESERVE_BYTES),
|
|
4609
|
-
fits: (data, options) => agentEnvelopeFits(command, data, options)
|
|
4610
|
-
} : undefined);
|
|
5072
|
+
const page = await readAuditItem(command, params, agentMode ? { maxBytes: agentMaxOutputBytes(), fits: (data, options) => agentEnvelopeFits(command, data, options) } : undefined);
|
|
4611
5073
|
context.output.writeData(page.data, page.options);
|
|
4612
5074
|
break;
|
|
4613
5075
|
}
|
|
4614
5076
|
case "frustrations": {
|
|
4615
|
-
const args =
|
|
5077
|
+
const args = flagsToArgs(flags);
|
|
4616
5078
|
const params = parseSpecFlags("frustrations", args, command);
|
|
4617
5079
|
const query2 = new URLSearchParams;
|
|
4618
5080
|
if (params.days_back)
|
|
@@ -4775,6 +5237,11 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4775
5237
|
if (view !== undefined && params.full === true) {
|
|
4776
5238
|
throw new CliInputError("--full applies to the dossier only; the --evidence/--reports/--traces/--feedback pages are always whole.", "Usage: moda problems get <problem_id> [--full] | moda problems get <problem_id> --evidence|--reports|--traces|--feedback");
|
|
4777
5239
|
}
|
|
5240
|
+
if (view === undefined) {
|
|
5241
|
+
rejectInapplicableFlags(command, params, ["limit", "cursor", "family", "door"], "to the dossier; they page --evidence/--reports/--traces/--feedback");
|
|
5242
|
+
} else if (view !== "conversations") {
|
|
5243
|
+
rejectInapplicableFlags(command, params, ["family", "door"], `to --${view}; they filter --traces`);
|
|
5244
|
+
}
|
|
4778
5245
|
if (view === undefined) {
|
|
4779
5246
|
const data2 = await callDataAPI(`/problems/${encodeURIComponent(params.id)}`);
|
|
4780
5247
|
const record = asRecord(data2) ?? {};
|
|
@@ -4844,15 +5311,21 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4844
5311
|
throw new CliInputError("--attribution-id must be a UUID.");
|
|
4845
5312
|
}
|
|
4846
5313
|
}
|
|
4847
|
-
const
|
|
5314
|
+
const feedbackPath = `/problems/${params.id}/feedback`;
|
|
5315
|
+
const feedbackBody = {
|
|
5316
|
+
action: params.action,
|
|
5317
|
+
...reason !== "" ? { reason } : {},
|
|
5318
|
+
...params.attribution_id ? { attribution_id: params.attribution_id } : {},
|
|
5319
|
+
...params.new_name ? { new_display_name: params.new_name } : {},
|
|
5320
|
+
actor: params.actor ?? "moda-cli"
|
|
5321
|
+
};
|
|
5322
|
+
if (context.dryRun) {
|
|
5323
|
+
writeDryRun(context, { plane: "data", method: "POST", path: feedbackPath, body: feedbackBody });
|
|
5324
|
+
break;
|
|
5325
|
+
}
|
|
5326
|
+
const data = await callDataAPI(feedbackPath, {
|
|
4848
5327
|
method: "POST",
|
|
4849
|
-
body: JSON.stringify(
|
|
4850
|
-
action: params.action,
|
|
4851
|
-
...reason !== "" ? { reason } : {},
|
|
4852
|
-
...params.attribution_id ? { attribution_id: params.attribution_id } : {},
|
|
4853
|
-
...params.new_name ? { new_display_name: params.new_name } : {},
|
|
4854
|
-
actor: params.actor ?? "moda-cli"
|
|
4855
|
-
}),
|
|
5328
|
+
body: JSON.stringify(feedbackBody),
|
|
4856
5329
|
headers: { "Content-Type": "application/json" }
|
|
4857
5330
|
}, { retries: 0 });
|
|
4858
5331
|
context.output.writeData(data);
|
|
@@ -4870,10 +5343,10 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4870
5343
|
});
|
|
4871
5344
|
const link = asRecord(data)?.link;
|
|
4872
5345
|
const summary = describeLinearLink(link);
|
|
4873
|
-
const nextCommands = link ? [] : [`moda problems link ${positional}`];
|
|
5346
|
+
const nextCommands = link ? [] : [`moda problems link ${shellArg(positional)}`];
|
|
4874
5347
|
if (context.outputMode === "human" && !context.quiet) {
|
|
4875
5348
|
safeWriteStderr(link ? `${summary}
|
|
4876
|
-
` : `${summary} File one with: moda problems link ${positional}
|
|
5349
|
+
` : `${summary} File one with: moda problems link ${shellArg(positional)}
|
|
4877
5350
|
`);
|
|
4878
5351
|
}
|
|
4879
5352
|
context.output.writeData(data, { summary, nextCommands });
|
|
@@ -4903,13 +5376,19 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4903
5376
|
}
|
|
4904
5377
|
const title = params.title?.trim() ?? "";
|
|
4905
5378
|
const teamId = params.team_id?.trim() ?? "";
|
|
4906
|
-
const
|
|
5379
|
+
const linearPath = `/problems/${params.id}/linear`;
|
|
5380
|
+
const linearBody = issue !== "" ? { issue } : {
|
|
5381
|
+
...teamId !== "" ? { team_id: teamId } : {},
|
|
5382
|
+
...title !== "" ? { title } : {},
|
|
5383
|
+
...params.priority !== undefined ? { priority: params.priority } : {}
|
|
5384
|
+
};
|
|
5385
|
+
if (context.dryRun) {
|
|
5386
|
+
writeDryRun(context, { plane: "data", method: "POST", path: linearPath, body: linearBody });
|
|
5387
|
+
break;
|
|
5388
|
+
}
|
|
5389
|
+
const data = await callDataAPI(linearPath, {
|
|
4907
5390
|
method: "POST",
|
|
4908
|
-
body: JSON.stringify(
|
|
4909
|
-
...teamId !== "" ? { team_id: teamId } : {},
|
|
4910
|
-
...title !== "" ? { title } : {},
|
|
4911
|
-
...params.priority !== undefined ? { priority: params.priority } : {}
|
|
4912
|
-
}),
|
|
5391
|
+
body: JSON.stringify(linearBody),
|
|
4913
5392
|
headers: { "Content-Type": "application/json" }
|
|
4914
5393
|
}, { retries: 0 }).catch((error) => {
|
|
4915
5394
|
throw linearApiError(error);
|
|
@@ -4941,6 +5420,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4941
5420
|
const params = parseSpecFlags("signal", args, command);
|
|
4942
5421
|
const id = encodeURIComponent(params.id);
|
|
4943
5422
|
if (params.detections !== true) {
|
|
5423
|
+
rejectInapplicableFlags(command, params, ["days_back", "limit", "offset"], "to the signal summary; they page its hits (--detections)");
|
|
4944
5424
|
const data2 = await callDataAPI(`/custom-signals/${id}`);
|
|
4945
5425
|
context.output.writeData(data2);
|
|
4946
5426
|
break;
|
|
@@ -4979,15 +5459,21 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4979
5459
|
...flags["msg-dedup-token"] !== undefined ? { msg_dedup_token: flags["msg-dedup-token"] } : {}
|
|
4980
5460
|
});
|
|
4981
5461
|
const note = params.note?.trim() ?? "";
|
|
4982
|
-
const
|
|
5462
|
+
const labelPath = `/custom-signals/${encodeURIComponent(params.id)}/labels`;
|
|
5463
|
+
const labelBody = {
|
|
5464
|
+
conversation_id: params.conversation_id,
|
|
5465
|
+
label: params.label,
|
|
5466
|
+
...note !== "" ? { note } : {},
|
|
5467
|
+
...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
|
|
5468
|
+
...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
|
|
5469
|
+
};
|
|
5470
|
+
if (context.dryRun) {
|
|
5471
|
+
writeDryRun(context, { plane: "data", method: "POST", path: labelPath, body: labelBody });
|
|
5472
|
+
break;
|
|
5473
|
+
}
|
|
5474
|
+
const data = await callDataAPI(labelPath, {
|
|
4983
5475
|
method: "POST",
|
|
4984
|
-
body: JSON.stringify(
|
|
4985
|
-
conversation_id: params.conversation_id,
|
|
4986
|
-
label: params.label,
|
|
4987
|
-
...note !== "" ? { note } : {},
|
|
4988
|
-
...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
|
|
4989
|
-
...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
|
|
4990
|
-
}),
|
|
5476
|
+
body: JSON.stringify(labelBody),
|
|
4991
5477
|
headers: { "Content-Type": "application/json" }
|
|
4992
5478
|
}, { retries: 0 });
|
|
4993
5479
|
context.output.writeData(data);
|
|
@@ -5019,15 +5505,20 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5019
5505
|
...flags.note !== undefined ? { note: flags.note } : {}
|
|
5020
5506
|
});
|
|
5021
5507
|
const note = params.verdict === "clear" ? "" : params.note?.trim() ?? "";
|
|
5022
|
-
const
|
|
5508
|
+
const reviewPath = `/problems/${params.id}/detection-reviews`;
|
|
5509
|
+
const reviewBody = {
|
|
5510
|
+
conversation_id: params.conversation_id,
|
|
5511
|
+
attribution_id: params.attribution_id,
|
|
5512
|
+
verdict: params.verdict,
|
|
5513
|
+
...note !== "" ? { note } : {}
|
|
5514
|
+
};
|
|
5515
|
+
if (context.dryRun) {
|
|
5516
|
+
writeDryRun(context, { plane: "data", method: "POST", path: reviewPath, body: reviewBody, generatedFields: ["request_nonce"] });
|
|
5517
|
+
break;
|
|
5518
|
+
}
|
|
5519
|
+
const data = await callDataAPI(reviewPath, {
|
|
5023
5520
|
method: "POST",
|
|
5024
|
-
body: JSON.stringify({
|
|
5025
|
-
conversation_id: params.conversation_id,
|
|
5026
|
-
attribution_id: params.attribution_id,
|
|
5027
|
-
verdict: params.verdict,
|
|
5028
|
-
...note !== "" ? { note } : {},
|
|
5029
|
-
request_nonce: randomUUID2()
|
|
5030
|
-
}),
|
|
5521
|
+
body: JSON.stringify({ ...reviewBody, request_nonce: randomUUID2() }),
|
|
5031
5522
|
headers: { "Content-Type": "application/json" }
|
|
5032
5523
|
});
|
|
5033
5524
|
context.output.writeData(data);
|
|
@@ -5054,50 +5545,77 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5054
5545
|
...flags["msg-dedup-token"] !== undefined ? { msg_dedup_token: flags["msg-dedup-token"] } : {}
|
|
5055
5546
|
});
|
|
5056
5547
|
const reason = params.reason?.trim() ?? "";
|
|
5057
|
-
|
|
5058
|
-
let data;
|
|
5548
|
+
let request;
|
|
5059
5549
|
switch (params.kind) {
|
|
5060
5550
|
case "problem":
|
|
5061
|
-
|
|
5551
|
+
request = { method: "POST", path: `/problems/${params.target}/feedback`, body: { action: "dismiss", reason, actor: "moda-cli" }, retries: 0 };
|
|
5062
5552
|
break;
|
|
5063
5553
|
case "problem-detection":
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5554
|
+
request = {
|
|
5555
|
+
method: "POST",
|
|
5556
|
+
path: `/problems/${params.target}/detection-reviews`,
|
|
5557
|
+
body: {
|
|
5558
|
+
conversation_id: params.conversation_id,
|
|
5559
|
+
attribution_id: params.attribution_id,
|
|
5560
|
+
verdict: params.undo ? "clear" : "false_positive",
|
|
5561
|
+
...params.undo ? {} : { note: reason }
|
|
5562
|
+
},
|
|
5563
|
+
nonce: true
|
|
5564
|
+
};
|
|
5071
5565
|
break;
|
|
5072
5566
|
case "custom-signal":
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5567
|
+
request = params.undo ? {
|
|
5568
|
+
method: "DELETE",
|
|
5569
|
+
path: `/custom-signals/${encodeURIComponent(params.target)}/labels/${encodeURIComponent(params.conversation_id ?? "")}`,
|
|
5570
|
+
retries: 0
|
|
5571
|
+
} : {
|
|
5572
|
+
method: "POST",
|
|
5573
|
+
path: `/custom-signals/${encodeURIComponent(params.target)}/labels`,
|
|
5574
|
+
body: {
|
|
5575
|
+
conversation_id: params.conversation_id,
|
|
5576
|
+
label: "does_not_fit",
|
|
5577
|
+
note: reason,
|
|
5578
|
+
...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
|
|
5579
|
+
...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
|
|
5580
|
+
},
|
|
5581
|
+
retries: 0
|
|
5582
|
+
};
|
|
5084
5583
|
break;
|
|
5085
5584
|
default: {
|
|
5086
5585
|
const apiKind = { quote: "frustration_quote", tool: "tool_call", laziness: "laziness", hallucination: "hallucination" }[params.kind];
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5586
|
+
request = {
|
|
5587
|
+
method: "POST",
|
|
5588
|
+
path: "/false-positives",
|
|
5589
|
+
body: {
|
|
5590
|
+
kind: apiKind,
|
|
5591
|
+
verdict: params.undo ? "clear" : "false_positive",
|
|
5592
|
+
...params.undo ? {} : { note: reason },
|
|
5593
|
+
conversation_id: params.target,
|
|
5594
|
+
...params.kind === "tool" ? {
|
|
5595
|
+
tool_name: params.tool,
|
|
5596
|
+
...params.tool_use_id ? { tool_use_id: params.tool_use_id } : { msg_index: params.msg_index }
|
|
5597
|
+
} : {},
|
|
5598
|
+
...params.kind === "quote" ? { family: params.family, turn: params.turn, quote: params.quote } : {}
|
|
5599
|
+
},
|
|
5600
|
+
nonce: true
|
|
5601
|
+
};
|
|
5099
5602
|
}
|
|
5100
5603
|
}
|
|
5604
|
+
if (context.dryRun) {
|
|
5605
|
+
writeDryRun(context, {
|
|
5606
|
+
plane: "data",
|
|
5607
|
+
method: request.method,
|
|
5608
|
+
path: request.path,
|
|
5609
|
+
...request.body ? { body: request.body } : {},
|
|
5610
|
+
...request.nonce ? { generatedFields: ["request_nonce"] } : {}
|
|
5611
|
+
});
|
|
5612
|
+
break;
|
|
5613
|
+
}
|
|
5614
|
+
const sentBody = request.body && request.nonce ? { ...request.body, request_nonce: randomUUID2() } : request.body;
|
|
5615
|
+
const data = await callDataAPI(request.path, {
|
|
5616
|
+
method: request.method,
|
|
5617
|
+
...sentBody ? { body: JSON.stringify(sentBody), headers: { "Content-Type": "application/json" } } : {}
|
|
5618
|
+
}, request.retries === undefined ? undefined : { retries: request.retries });
|
|
5101
5619
|
context.output.writeData(data);
|
|
5102
5620
|
break;
|
|
5103
5621
|
}
|
|
@@ -5125,12 +5643,13 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5125
5643
|
context.output.writeData(data2);
|
|
5126
5644
|
break;
|
|
5127
5645
|
}
|
|
5646
|
+
rejectInapplicableFlags(command, { kind: flags.kind, limit: flags.limit, all: flags.all === "false" ? undefined : flags.all }, ["kind", "limit", "all"], "to one trace's marks (every mark on the trace is listed); they filter the workspace list");
|
|
5128
5647
|
const data = await callDataAPI(`/conversations/${encodeURIComponent(positional)}/false-positives`);
|
|
5129
5648
|
context.output.writeData(data);
|
|
5130
5649
|
break;
|
|
5131
5650
|
}
|
|
5132
5651
|
case "emotions": {
|
|
5133
|
-
const args =
|
|
5652
|
+
const args = flagsToArgs(flags);
|
|
5134
5653
|
const params = parseSpecFlags("emotions", args, command);
|
|
5135
5654
|
const query2 = new URLSearchParams;
|
|
5136
5655
|
if (params.days_back)
|
|
@@ -5158,9 +5677,6 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5158
5677
|
}
|
|
5159
5678
|
case "laziness": {
|
|
5160
5679
|
const args = flagsToArgs(flags);
|
|
5161
|
-
if (flags["conversation-id"] !== undefined) {
|
|
5162
|
-
args.conversation_id = flags["conversation-id"];
|
|
5163
|
-
}
|
|
5164
5680
|
const params = parseSpecFlags("laziness", args, command);
|
|
5165
5681
|
const query2 = new URLSearchParams;
|
|
5166
5682
|
if (params.days_back)
|
|
@@ -5180,9 +5696,6 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5180
5696
|
}
|
|
5181
5697
|
case "hallucinations": {
|
|
5182
5698
|
const args = flagsToArgs(flags);
|
|
5183
|
-
if (flags["conversation-id"] !== undefined) {
|
|
5184
|
-
args.conversation_id = flags["conversation-id"];
|
|
5185
|
-
}
|
|
5186
5699
|
const params = parseSpecFlags("hallucinations", args, command);
|
|
5187
5700
|
const query2 = new URLSearchParams;
|
|
5188
5701
|
if (params.days_back)
|
|
@@ -5223,17 +5736,22 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5223
5736
|
}
|
|
5224
5737
|
case "feedback": {
|
|
5225
5738
|
const { category, severity, note, refs, warnings } = buildFeedbackRequest(positionals, flags, context.bareFlags);
|
|
5739
|
+
const feedbackBody = {
|
|
5740
|
+
category,
|
|
5741
|
+
severity,
|
|
5742
|
+
source: "cli",
|
|
5743
|
+
agent: detectCallerAgent(process.env),
|
|
5744
|
+
cli_version: VERSION,
|
|
5745
|
+
note,
|
|
5746
|
+
...Object.keys(refs).length > 0 ? { refs } : {}
|
|
5747
|
+
};
|
|
5748
|
+
if (context.dryRun) {
|
|
5749
|
+
writeDryRun(context, { plane: "data", method: "POST", path: "/feedback", body: feedbackBody });
|
|
5750
|
+
break;
|
|
5751
|
+
}
|
|
5226
5752
|
const data = await callDataAPI("/feedback", {
|
|
5227
5753
|
method: "POST",
|
|
5228
|
-
body: JSON.stringify(
|
|
5229
|
-
category,
|
|
5230
|
-
severity,
|
|
5231
|
-
source: "cli",
|
|
5232
|
-
agent: detectCallerAgent(process.env),
|
|
5233
|
-
cli_version: VERSION,
|
|
5234
|
-
note,
|
|
5235
|
-
...Object.keys(refs).length > 0 ? { refs } : {}
|
|
5236
|
-
})
|
|
5754
|
+
body: JSON.stringify(feedbackBody)
|
|
5237
5755
|
}, { retries: 0 });
|
|
5238
5756
|
context.output.writeData({ submitted: true, category, severity, ...asRecord(data) ?? {} }, {
|
|
5239
5757
|
summary: "Feedback submitted. Thank you for helping us improve!",
|
|
@@ -5382,7 +5900,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
5382
5900
|
resetApiRequestCountBeforeRun: true,
|
|
5383
5901
|
telemetry: "result-and-error",
|
|
5384
5902
|
handler: async (context) => {
|
|
5385
|
-
const { runInit } = await import("./index-
|
|
5903
|
+
const { runInit } = await import("./index-91bpc8g8.js");
|
|
5386
5904
|
if (context.outputMode === "agent-stream") {
|
|
5387
5905
|
context.output.writeEvent({
|
|
5388
5906
|
event: "started",
|
|
@@ -5432,7 +5950,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
5432
5950
|
resetApiRequestCountBeforeRun: true,
|
|
5433
5951
|
telemetry: "result-and-error",
|
|
5434
5952
|
handler: async (context) => {
|
|
5435
|
-
const { runProvision } = await import("./provision-
|
|
5953
|
+
const { runProvision } = await import("./provision-bc3h68e1.js");
|
|
5436
5954
|
return runProvision(context, {
|
|
5437
5955
|
resumeCommand: buildResumeCommand("provision", context.rawFlags)
|
|
5438
5956
|
});
|
|
@@ -6454,6 +6972,9 @@ ${error.suggestions.map((s) => ` ${s}`).join(`
|
|
|
6454
6972
|
`)}
|
|
6455
6973
|
`);
|
|
6456
6974
|
}
|
|
6975
|
+
} else if (error instanceof ApiError && error.statusCode === 404) {
|
|
6976
|
+
safeWriteStderr2(`${errorToAgentError(error).message}
|
|
6977
|
+
`);
|
|
6457
6978
|
} else if (error instanceof ApiError) {
|
|
6458
6979
|
safeWriteStderr2(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}
|
|
6459
6980
|
`);
|
|
@@ -6506,6 +7027,46 @@ function printAgentCliError(command, error, mode = "agent") {
|
|
|
6506
7027
|
});
|
|
6507
7028
|
output.writeError(error);
|
|
6508
7029
|
}
|
|
7030
|
+
function flagNamesInLabel(label) {
|
|
7031
|
+
return [...label.matchAll(/--([A-Za-z0-9][A-Za-z0-9-]*)/g)].map((match) => match[1]);
|
|
7032
|
+
}
|
|
7033
|
+
function specFlagNamesDeep(spec) {
|
|
7034
|
+
if (!spec)
|
|
7035
|
+
return [];
|
|
7036
|
+
return [...specFlagNames(spec), ...Object.values(spec.subcommands ?? {}).flatMap((sub) => specFlagNames(sub))];
|
|
7037
|
+
}
|
|
7038
|
+
function telemetryAcceptedFlags(definition, route) {
|
|
7039
|
+
const names = new Set([...GLOBAL_FLAG_NAMES, ...GLOBAL_BOOLEAN_FLAGS]);
|
|
7040
|
+
const add = (list) => {
|
|
7041
|
+
for (const name of list)
|
|
7042
|
+
names.add(name);
|
|
7043
|
+
};
|
|
7044
|
+
if (definition) {
|
|
7045
|
+
add(specFlagNamesDeep(flagSpecFor(definition.name)));
|
|
7046
|
+
for (const flag of definition.documentedFlags ?? [])
|
|
7047
|
+
add(flagNamesInLabel(flag.name));
|
|
7048
|
+
for (const sub of definition.subcommands ?? [])
|
|
7049
|
+
for (const flag of sub.flags ?? [])
|
|
7050
|
+
add(flagNamesInLabel(flag.name));
|
|
7051
|
+
}
|
|
7052
|
+
if (route) {
|
|
7053
|
+
add(route.allowedFlags ?? []);
|
|
7054
|
+
add(Object.keys(route.flagAliases ?? {}));
|
|
7055
|
+
add(specFlagNamesDeep(leafFlagSpec(route.leaf)));
|
|
7056
|
+
}
|
|
7057
|
+
return names;
|
|
7058
|
+
}
|
|
7059
|
+
var knownFlagNames;
|
|
7060
|
+
function telemetryKnownFlags() {
|
|
7061
|
+
if (!knownFlagNames) {
|
|
7062
|
+
knownFlagNames = new Set(RAW_BOOLEAN_FLAGS);
|
|
7063
|
+
for (const definition of commandRegistry.all()) {
|
|
7064
|
+
for (const name of telemetryAcceptedFlags(definition, undefined))
|
|
7065
|
+
knownFlagNames.add(name);
|
|
7066
|
+
}
|
|
7067
|
+
}
|
|
7068
|
+
return knownFlagNames;
|
|
7069
|
+
}
|
|
6509
7070
|
function existingFlagProfile(flags) {
|
|
6510
7071
|
const name = flags.profile;
|
|
6511
7072
|
if (!name || name === "true")
|
|
@@ -6548,6 +7109,50 @@ function assertRoutedFlags(route, flags) {
|
|
|
6548
7109
|
const args = Object.fromEntries(Object.keys(flags).filter((flag) => !aliases.has(flag.replace(/_/g, "-"))).map((flag) => [flag.replace(/-/g, "_"), true]));
|
|
6549
7110
|
parseCommandFlags(shape.passthrough(), args, route.label, route.flagAliases ? { flagAliases: route.flagAliases } : {});
|
|
6550
7111
|
}
|
|
7112
|
+
var EMPTY_VALUE_ALLOWED = {
|
|
7113
|
+
context: new Set(["msg-index", "message-index"])
|
|
7114
|
+
};
|
|
7115
|
+
var EXAMPLE_CLEARABLE_FLAGS = new Set(["note", "blurb"]);
|
|
7116
|
+
function assertFlagValues(handler, positionals, typedFlags, bareFlags, route) {
|
|
7117
|
+
const name = route?.handler ?? handler;
|
|
7118
|
+
const spec = flagSpecFor(name);
|
|
7119
|
+
if (spec?.idRefFlags)
|
|
7120
|
+
return;
|
|
7121
|
+
const bare = new Set(bareFlags ?? []);
|
|
7122
|
+
const empty = new Set(Object.keys(typedFlags).filter((key) => String(typedFlags[key]).trim() === ""));
|
|
7123
|
+
if (bare.size === 0 && empty.size === 0)
|
|
7124
|
+
return;
|
|
7125
|
+
const handlerPositionals = route?.positionals ?? positionals;
|
|
7126
|
+
const managementSubcommand = name === "signals" && SIGNALS_MANAGEMENT_SUBCOMMANDS.has(handlerPositionals[0] ?? "");
|
|
7127
|
+
const examplesUpdate = name === "signals" && handlerPositionals[0] === "examples" && handlerPositionals.includes("update");
|
|
7128
|
+
const valued = new Set;
|
|
7129
|
+
const switches = new Set(RAW_BOOLEAN_FLAGS);
|
|
7130
|
+
for (const entry of specsOf(name)) {
|
|
7131
|
+
for (const flag of describeSpecFlags(entry))
|
|
7132
|
+
(flag.type === "boolean" ? switches : valued).add(flag.name.slice(2));
|
|
7133
|
+
for (const positional of entry.positionals ?? [])
|
|
7134
|
+
if (positional.key)
|
|
7135
|
+
valued.add(positional.key.replace(/_/g, "-"));
|
|
7136
|
+
}
|
|
7137
|
+
for (const flag of route?.allowedFlags ?? [])
|
|
7138
|
+
if (!switches.has(flag))
|
|
7139
|
+
valued.add(flag);
|
|
7140
|
+
for (const [key, value] of Object.entries(typedFlags)) {
|
|
7141
|
+
const isBare = bare.has(key) && value === "true";
|
|
7142
|
+
if (!isBare && !empty.has(key))
|
|
7143
|
+
continue;
|
|
7144
|
+
if (!isBare && EMPTY_VALUE_ALLOWED[name]?.has(key))
|
|
7145
|
+
continue;
|
|
7146
|
+
if (!isBare && examplesUpdate && EXAMPLE_CLEARABLE_FLAGS.has(key))
|
|
7147
|
+
continue;
|
|
7148
|
+
const target = route?.flagAliases?.[key] ?? key;
|
|
7149
|
+
const takesValue = valued.has(key) || valued.has(target) || managementSubcommand && !switches.has(key) && !GLOBAL_FLAG_NAMES.has(key);
|
|
7150
|
+
if (!takesValue || switches.has(key))
|
|
7151
|
+
continue;
|
|
7152
|
+
const shown = canonicalCommandText(`moda ${route?.leaf.path ?? handler}`);
|
|
7153
|
+
throw new CliInputError(`--${key} needs a value, so nothing was sent.`, `Pass it as --${key}=VALUE (an empty shell variable leaves the flag without one). See '${shown} --help'.`, `missing_value:${key}`, { field: `--${key}`, suggestedCommands: [`${shown} --help`] });
|
|
7154
|
+
}
|
|
7155
|
+
}
|
|
6551
7156
|
function renameErrorCommands(error, renames) {
|
|
6552
7157
|
if (error instanceof CliInputError) {
|
|
6553
7158
|
const details2 = error.details;
|
|
@@ -6565,6 +7170,7 @@ function renameErrorCommands(error, renames) {
|
|
|
6565
7170
|
}
|
|
6566
7171
|
async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
6567
7172
|
const { command, flags } = typedParsed;
|
|
7173
|
+
canonicalizeFlagKeys(flags, typedParsed.bareFlags);
|
|
6568
7174
|
const switchError = normalizeBooleanFlags(flags);
|
|
6569
7175
|
const typedFlags = stripUpdateCheckFlag(stripTelemetryFlag(flags));
|
|
6570
7176
|
const telemetryDisabled = isTelemetryDisabled(flags);
|
|
@@ -6577,6 +7183,15 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
6577
7183
|
if (command && printTreeHelp(typedParsed))
|
|
6578
7184
|
return 0;
|
|
6579
7185
|
const helpTarget = command && command !== "--help" && command !== "-h" ? commandRegistry.find(command) : undefined;
|
|
7186
|
+
if (!helpTarget && command && command !== "--help" && command !== "-h") {
|
|
7187
|
+
const mode = resolveOutputMode(typedFlags, { isTTY: process.stdout.isTTY, env: process.env });
|
|
7188
|
+
const error = unknownCommandError(command);
|
|
7189
|
+
if (mode === "agent" || mode === "agent-stream" || mode === "json")
|
|
7190
|
+
printAgentCliError(command, error, mode);
|
|
7191
|
+
else
|
|
7192
|
+
printCliError(error);
|
|
7193
|
+
return 1;
|
|
7194
|
+
}
|
|
6580
7195
|
if (helpTarget) {
|
|
6581
7196
|
const subcommandName = typedParsed.positional;
|
|
6582
7197
|
const subcommandEntry = subcommandName ? helpTarget.subcommands?.find((entry) => entry.name === subcommandName) : undefined;
|
|
@@ -6624,6 +7239,8 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
6624
7239
|
if (!handlerTelemetry)
|
|
6625
7240
|
return;
|
|
6626
7241
|
const telemetryProfile = existingFlagProfile(typedFlags);
|
|
7242
|
+
if (typedFlags.profile !== undefined && telemetryProfile === undefined)
|
|
7243
|
+
return;
|
|
6627
7244
|
const adoption = buildSearchAdoptionTelemetry({
|
|
6628
7245
|
command: handlerTelemetry,
|
|
6629
7246
|
positional: parsed.positional,
|
|
@@ -6651,12 +7268,14 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
6651
7268
|
durationMs: Date.now() - startedAt,
|
|
6652
7269
|
apiRequestCount: input.apiRequestCount,
|
|
6653
7270
|
adoption,
|
|
6654
|
-
...identity2?.legacyAlias ? { legacyAlias: identity2.legacyAlias } : {}
|
|
7271
|
+
...identity2?.legacyAlias ? { legacyAlias: identity2.legacyAlias } : {},
|
|
7272
|
+
acceptedFlags: telemetryAcceptedFlags(definition2, route),
|
|
7273
|
+
knownFlags: telemetryKnownFlags()
|
|
6655
7274
|
});
|
|
6656
7275
|
};
|
|
6657
7276
|
const failEarly = (error, commandDefinition, identity2) => {
|
|
6658
7277
|
const mode = outputModeFor();
|
|
6659
|
-
const telemetryOptOut = telemetryDisabled || flags
|
|
7278
|
+
const telemetryOptOut = telemetryDisabled || optOutFlagSet(flags, "no-telemetry", false);
|
|
6660
7279
|
const reports = commandDefinition ? commandDefinition.telemetry === "result-and-error" : identity2 !== undefined;
|
|
6661
7280
|
if (reports && !telemetryOptOut) {
|
|
6662
7281
|
reportUsage({
|
|
@@ -6691,14 +7310,14 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
6691
7310
|
}
|
|
6692
7311
|
if (switchError)
|
|
6693
7312
|
return failEarly(switchError, definition, identity);
|
|
6694
|
-
|
|
6695
|
-
|
|
7313
|
+
try {
|
|
7314
|
+
if (route)
|
|
6696
7315
|
assertRoutedFlags(route, typedFlags);
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
7316
|
+
assertFlagValues(definition.name, parsed.positionals, typedFlags, typedParsed.bareFlags, route);
|
|
7317
|
+
} catch (error) {
|
|
7318
|
+
if (!(error instanceof CliInputError))
|
|
7319
|
+
throw error;
|
|
7320
|
+
return failEarly(error, definition, identity);
|
|
6702
7321
|
}
|
|
6703
7322
|
if (commandFlags.profile === "true" || commandFlags.profile === "") {
|
|
6704
7323
|
return failEarly(new CliInputError("--profile needs a value, e.g. --profile=prod.", "List profiles with 'moda profiles list'.", "missing_value:profile"), definition, identity);
|
|
@@ -6708,7 +7327,7 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
6708
7327
|
const known = Object.keys(loadProfileConfig({ env: process.env }).profiles);
|
|
6709
7328
|
if (!known.includes(requestedProfile)) {
|
|
6710
7329
|
const source = commandFlags.profile ? `--profile=${requestedProfile}` : `MODA_PROFILE=${requestedProfile}`;
|
|
6711
|
-
return failEarly(new CliInputError(`Moda profile '${requestedProfile}' does not exist (from ${source}), so nothing was run.`, `Known profiles: ${known.join(", ")}. Create it with 'moda profiles create ${requestedProfile}' or fix the name.`, "unknown_profile"), definition, identity);
|
|
7330
|
+
return failEarly(new CliInputError(`Moda profile '${requestedProfile}' does not exist (from ${source}), so nothing was run.`, `Known profiles: ${known.join(", ")}. Create it with 'moda profiles create ${shellArg(requestedProfile)}' or fix the name.`, "unknown_profile"), definition, identity);
|
|
6712
7331
|
}
|
|
6713
7332
|
if (commandFlags.profile)
|
|
6714
7333
|
process.env.MODA_PROFILE = commandFlags.profile;
|
|
@@ -6717,7 +7336,7 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
6717
7336
|
commandFlags.agent = "true";
|
|
6718
7337
|
}
|
|
6719
7338
|
if (!updateCheckDisabled) {
|
|
6720
|
-
startUpdateCheck();
|
|
7339
|
+
startUpdateCheck({}, Date.now(), { detached: outputModeFor() !== "human" });
|
|
6721
7340
|
setAgentUpdateNotice(() => {
|
|
6722
7341
|
const latest = getAvailableUpdate();
|
|
6723
7342
|
return latest ? `moda-cli ${VERSION} is out of date (latest ${latest}); commands and flags may differ from the docs. Update: npm install -g @moda-ai/cli@latest` : undefined;
|
|
@@ -6857,6 +7476,7 @@ if (isMain) {
|
|
|
6857
7476
|
}
|
|
6858
7477
|
export {
|
|
6859
7478
|
unknownCommandError,
|
|
7479
|
+
trimContextWindow,
|
|
6860
7480
|
telemetrySubcommandNames,
|
|
6861
7481
|
telemetryCommandName,
|
|
6862
7482
|
stripLoneSurrogates,
|
|
@@ -6876,10 +7496,14 @@ export {
|
|
|
6876
7496
|
dispatchParsedCommand,
|
|
6877
7497
|
detectCallerAgent,
|
|
6878
7498
|
describeLinearLink,
|
|
7499
|
+
commandSwitchFlags,
|
|
6879
7500
|
commandRegistry,
|
|
7501
|
+
coerceNumericFlags,
|
|
6880
7502
|
cliValidationReason,
|
|
6881
7503
|
classifyCliError,
|
|
6882
7504
|
capProblemsBlocks,
|
|
7505
|
+
canonicalizeFlagKeys,
|
|
7506
|
+
canonicalFlagKey,
|
|
6883
7507
|
buildResumeCommand,
|
|
6884
7508
|
auditCapStatus,
|
|
6885
7509
|
TELEMETRY_EXEMPT_COMMANDS,
|