@tangle-network/traces 0.15.3 → 0.15.5

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/cli.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  appendAll,
9
9
  assembleSessionBundle,
10
10
  assertOutsideSourceBundle,
11
+ assessSpans,
11
12
  buildPolicyEvidenceRecord,
12
13
  buildSessionFactsReport,
13
14
  buildSessionIndexFromRows,
@@ -17,11 +18,13 @@ import {
17
18
  collectSessionSelection,
18
19
  commandAnalyzer,
19
20
  commandRedactor,
21
+ diffRuns,
20
22
  executeUpload,
21
23
  exportTraceEvidenceFile,
22
24
  externalFailureMessage,
23
25
  fileRunContextSupervisorRunReader,
24
26
  findingRejectionDetail,
27
+ formatVerdict,
25
28
  haloAnalyzer,
26
29
  hodoscopeAnalyzer,
27
30
  importCodeTraceBench,
@@ -34,6 +37,7 @@ import {
34
37
  locateSessions,
35
38
  mergeTracesConfig,
36
39
  normalizeTraceQuestions,
40
+ openAgenticTraceStore,
37
41
  parseSession,
38
42
  parseSince,
39
43
  planUpload,
@@ -41,7 +45,9 @@ import {
41
45
  projectSessionBundle,
42
46
  readOtlpInput,
43
47
  readSessionIndexFile,
48
+ redactSpans,
44
49
  renderInspectionReport,
50
+ renderRunDiff,
45
51
  renderSessionFacts,
46
52
  renderValidation,
47
53
  resolveAdapter,
@@ -59,18 +65,22 @@ import {
59
65
  streamSessions,
60
66
  totalAgenticFailureMessage,
61
67
  traceStreamEventsFromSpans,
68
+ tracesVersion,
62
69
  validationExitCode,
70
+ verdictExitCode,
63
71
  verifyFindingsInSandbox,
72
+ verifySafe,
64
73
  verifySessionBundle,
65
74
  watchRunTarget,
66
75
  watchSessions,
76
+ writeAnalysisTraceFile,
67
77
  writeInspectionReportFile,
68
78
  writeOtlpFile,
69
79
  writePolicyEvidenceFile,
70
80
  writeSessionIndexFile,
71
81
  writeTraceEvidenceExportFile,
72
82
  writeTraceQuestionsArtifacts
73
- } from "./chunk-U4EB4HK7.js";
83
+ } from "./chunk-RERVIGLH.js";
74
84
 
75
85
  // src/cli.ts
76
86
  import { readFileSync } from "fs";
@@ -89,11 +99,60 @@ import {
89
99
  renderSupervisorRunMarkdown,
90
100
  rollupSupervisorRuns
91
101
  } from "@tangle-network/agent-eval/supervisor-run";
92
- function packageVersion() {
93
- const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
94
- if (typeof pkg.version !== "string" || !pkg.version) throw new Error("package.json is missing version");
95
- return pkg.version;
102
+
103
+ // src/mcp.ts
104
+ import { rm } from "fs/promises";
105
+ import { dirname } from "path";
106
+ import { buildTraceAnalysisToolDescriptors, redact, shareAllowed, UNTRUSTED_TRACE_TEXT } from "@tangle-network/agent-eval/traces";
107
+ import { createStdioToolServer } from "@tangle-network/agent-runtime/mcp";
108
+ var MCP_RESULT_BYTE_CAP = 512 * 1024;
109
+ async function traceMcpTools(options) {
110
+ const { spans } = redactSpans(options.spans);
111
+ const verdict = assessSpans(spans);
112
+ if (!shareAllowed(verdict)) {
113
+ const findings = verdict.findings.map((f) => `${f.category}/${f.detector} (${f.count})`).join(", ");
114
+ throw new Error(
115
+ `mcp: refusing to serve \u2014 redacted spans are still ${verdict.status}` + (findings ? `: ${findings}` : "") + (verdict.unreadable.length > 0 ? `; unreadable: ${verdict.unreadable.join("; ")}` : "")
116
+ );
117
+ }
118
+ const file = await writeAnalysisTraceFile(spans);
119
+ const store = await openAgenticTraceStore(file);
120
+ const descriptors = buildTraceAnalysisToolDescriptors({ store }).filter((d) => d.name !== "readSpanSource");
121
+ const tools = descriptors.map((descriptor) => ({
122
+ name: descriptor.name,
123
+ description: descriptor.description,
124
+ inputSchema: descriptor.parameters,
125
+ annotations: {
126
+ readOnlyHint: descriptor.readOnly,
127
+ idempotentHint: descriptor.idempotent,
128
+ destructiveHint: false,
129
+ openWorldHint: false
130
+ },
131
+ handler: async (args) => {
132
+ const { value } = redact(await descriptor.handler(args));
133
+ const result = { untrusted: UNTRUSTED_TRACE_TEXT, result: value };
134
+ const bytes = Buffer.byteLength(JSON.stringify(result));
135
+ if (bytes > MCP_RESULT_BYTE_CAP) {
136
+ throw new RangeError(
137
+ `${descriptor.name}: result is ${bytes} bytes, above the ${MCP_RESULT_BYTE_CAP}-byte cap; narrow the request`
138
+ );
139
+ }
140
+ return result;
141
+ }
142
+ }));
143
+ return { tools, otlpPath: file.otlpPath };
96
144
  }
145
+ async function serveTraceMcp(options) {
146
+ const { tools, otlpPath } = await traceMcpTools(options);
147
+ const server = createStdioToolServer({ serverName: "traces", serverVersion: tracesVersion(), tools });
148
+ try {
149
+ await server.serve();
150
+ } finally {
151
+ await rm(dirname(otlpPath), { recursive: true, force: true });
152
+ }
153
+ }
154
+
155
+ // src/cli.ts
97
156
  function parseArgs(argv) {
98
157
  const a = {
99
158
  command: argv[0] === "--help" || argv[0] === "-h" ? "help" : argv[0] ?? "help",
@@ -296,22 +355,14 @@ function parseArgs(argv) {
296
355
  }
297
356
  return a;
298
357
  }
299
- var OTLP_INPUT_COMMANDS = /* @__PURE__ */ new Set(["analyze", "facts", "investigate", "improve", "ask", "stream", "validate"]);
300
- function migrateWritingOtlpFlag(args) {
301
- if (args.otlp === void 0 || OTLP_INPUT_COMMANDS.has(args.command)) return args;
302
- if (args.otlpOut !== void 0) {
358
+ var OTLP_INPUT_COMMANDS = /* @__PURE__ */ new Set(["analyze", "facts", "investigate", "improve", "ask", "stream", "validate", "mcp"]);
359
+ function validateOtlpSelection(args) {
360
+ if (args.otlp === void 0) return args;
361
+ if (!OTLP_INPUT_COMMANDS.has(args.command)) {
303
362
  throw new Error(
304
- `${args.command} was given both --otlp and --otlp-out. --otlp is the deprecated spelling of --otlp-out on writing commands; pass only --otlp-out.`
363
+ `${args.command} does not read --otlp. --otlp reads OTLP-JSONL on ${[...OTLP_INPUT_COMMANDS].join(", ")}; use --otlp-out <path> to write the artifact.`
305
364
  );
306
365
  }
307
- console.error(
308
- `warning: --otlp is deprecated on \`${args.command}\` and will be removed in 0.12. --otlp now READS OTLP-JSONL; use --otlp-out <path> to write the artifact. Treating it as --otlp-out for this run.`
309
- );
310
- return { ...args, otlpOut: args.otlp, otlp: void 0 };
311
- }
312
- function validateOtlpSelection(raw) {
313
- const args = migrateWritingOtlpFlag(raw);
314
- if (args.otlp === void 0) return args;
315
366
  if (args.input) throw new Error("--otlp cannot be combined with an input file");
316
367
  if (args.session) throw new Error("--otlp reads a file and cannot be combined with --session");
317
368
  if (args.supervisorRunDir) throw new Error("--otlp cannot be combined with --supervisor-run-dir");
@@ -686,6 +737,73 @@ async function cmdBundle(args) {
686
737
  `session bundle (${manifest.view} view) \u2192 ${result.directory} (${manifest.files.length} file(s), ${manifest.ledgerSlices.length} ledger slice(s), ${manifest.absent.length} recorded absent)`
687
738
  );
688
739
  }
740
+ async function cmdMcp(args) {
741
+ if (args.help) {
742
+ process.stdout.write(`traces mcp \u2014 read-only MCP server over stdio for the selected traces
743
+
744
+ Usage:
745
+ traces mcp --otlp <file|dir> any OTLP-JSONL
746
+ traces mcp --harness claude-code --last 5 recent harness sessions (any selection flag)
747
+
748
+ Register with a client, for example:
749
+ claude mcp add traces -- traces mcp --otlp ./spans.otlp.jsonl
750
+
751
+ Every tool is read-only and idempotent and says so in its MCP annotations.
752
+ Spans (including causal links) and results are redacted, the server refuses
753
+ to start if the redacted spans are still UNSAFE or UNKNOWN, each result
754
+ carries an untrusted-text notice, and a result above 512 KiB is refused
755
+ rather than truncated. readSpanSource is not served: windowed reads of
756
+ original source bytes cannot be redacted.
757
+ `);
758
+ return;
759
+ }
760
+ const selected = validateOtlpSelection(validateWorkflowSelection(applyCurrentSessionSelection(args)));
761
+ const collected = await collectSpans(selected);
762
+ if (collected.spans.length === 0) throw new Error("mcp: no spans found for the given selection");
763
+ warnIncompleteWorkflow(collected.workflow);
764
+ process.stderr.write(`traces mcp: serving ${collected.spans.length} span(s) over stdio
765
+ `);
766
+ await serveTraceMcp({ spans: collected.spans });
767
+ }
768
+ async function cmdDiff(argv) {
769
+ const inputs = [];
770
+ const kinds = [];
771
+ let format = "text";
772
+ for (let i = 0; i < argv.length; i++) {
773
+ const arg = argv[i];
774
+ if (arg === "--help" || arg === "-h") {
775
+ process.stdout.write(`traces diff \u2014 where two runs of one task first diverge
776
+
777
+ Usage:
778
+ traces diff <a> <b> [--kind TOOL]... [--format text|json]
779
+
780
+ Each side is an OTLP file or directory, any file convert reads, file#<trace id>,
781
+ or file#branch=<id> for one arm (agent.branch.id or agent.branch.arm, and every
782
+ span below it \u2014 refused if the label names more than one subtree). Steps pair
783
+ by span id, then position with the same name and kind, then name and kind
784
+ anywhere. Exit 0 when the runs agree on outcome, 1 when they diverge, 2 when a
785
+ side cannot be read, 3 when the steps line up with no divergence but neither
786
+ side carries a definitive OK/ERROR status or a TOOL step \u2014 there is no
787
+ recorded outcome to call agreement on.
788
+ `);
789
+ return;
790
+ }
791
+ if (arg === "--format") format = argv[++i] ?? "";
792
+ else if (arg === "--kind") kinds.push(argv[++i] ?? "");
793
+ else if (arg.startsWith("--")) throw new Error(`diff: unknown flag ${arg}`);
794
+ else inputs.push(arg);
795
+ }
796
+ if (inputs.length !== 2) {
797
+ throw new Error("diff needs two runs: traces diff <a> <b> (a file, a directory, file#<trace id> or file#branch=<id>)");
798
+ }
799
+ if (format !== "json" && format !== "text") throw new Error(`diff: --format must be json or text, got "${format}"`);
800
+ if (kinds.some((kind) => !kind)) throw new Error("diff: --kind needs a span kind, e.g. --kind TOOL");
801
+ const report = await diffRuns(inputs[0], inputs[1], { kinds });
802
+ process.stdout.write(format === "json" ? `${JSON.stringify(report, null, 2)}
803
+ ` : renderRunDiff(report));
804
+ if (report.diff.firstDivergence) process.exitCode = 1;
805
+ else if (report.caveat) process.exitCode = 3;
806
+ }
689
807
  async function cmdBundleVerify(argv) {
690
808
  let dir;
691
809
  let format = "text";
@@ -1296,11 +1414,11 @@ async function cmdUpload(args) {
1296
1414
  const redactorParts = args.redactorCmd?.split(/\s+/).filter(Boolean) ?? [];
1297
1415
  const redactor = redactorParts[0] ? commandRedactor({ command: redactorParts[0], args: redactorParts.slice(1) }) : void 0;
1298
1416
  const candidates = plan.items;
1299
- const byRule = {};
1417
+ const byDetector = {};
1300
1418
  let totalRedactions = 0;
1301
1419
  for (const i of candidates) {
1302
1420
  totalRedactions += i.redaction.redactionCount;
1303
- for (const [r, n] of Object.entries(i.redaction.byRule)) byRule[r] = (byRule[r] ?? 0) + n;
1421
+ for (const [r, n] of Object.entries(i.redaction.byDetector)) byDetector[r] = (byDetector[r] ?? 0) + n;
1304
1422
  }
1305
1423
  const w = (s) => process.stderr.write(s);
1306
1424
  w(`
@@ -1308,7 +1426,7 @@ Window: since ${new Date(sinceMs).toISOString()}
1308
1426
  `);
1309
1427
  w(`Sessions found: ${plan.items.length} \xB7 final privacy-mode dedup runs before upload
1310
1428
  `);
1311
- w(`PII/secrets redacted: ${totalRedactions}${Object.keys(byRule).length ? ` (${Object.entries(byRule).map(([r, n]) => `${r}:${n}`).join(", ")})` : ""}
1429
+ w(`PII/secrets redacted: ${totalRedactions}${Object.keys(byDetector).length ? ` (${Object.entries(byDetector).map(([r, n]) => `${r}:${n}`).join(", ")})` : ""}
1312
1430
  `);
1313
1431
  for (const i of candidates.slice(0, 25)) {
1314
1432
  w(` + [${i.ref.harness}] ${i.ref.sessionId.slice(0, 8)} ${i.spans.length} spans ${i.redaction.redactionCount} redacted ${i.ref.cwd ?? ""}
@@ -1322,7 +1440,8 @@ Window: since ${new Date(sinceMs).toISOString()}
1322
1440
  }
1323
1441
  if (args.dryRun) {
1324
1442
  const res2 = await executeUpload(plan, { dryRun: true, otlpOut: args.otlpOut, stripContent: args.noContent, redactor });
1325
- console.log(`dry run: ${candidates.length - res2.skippedSessions} session(s), ${totalRedactions} redaction(s). Redacted OTLP -> ${res2.otlpPath}`);
1443
+ reportRefused(res2.refused);
1444
+ console.log(`dry run: ${candidates.length - res2.skippedSessions - res2.refused.length} session(s), ${totalRedactions} redaction(s). Redacted OTLP -> ${res2.otlpPath}`);
1326
1445
  console.log("No upload performed. Set TANGLE_INGEST_URL / TANGLE_INGEST_API_KEY / TANGLE_TENANT_ID and drop --dry-run to send.");
1327
1446
  return;
1328
1447
  }
@@ -1335,10 +1454,79 @@ Window: since ${new Date(sinceMs).toISOString()}
1335
1454
  }
1336
1455
  const res = await executeUpload(plan, { log: (m) => w(`${m}
1337
1456
  `), stripContent: args.noContent, redactor });
1457
+ reportRefused(res.refused);
1338
1458
  console.log(
1339
- `Uploaded ${res.uploadedSessions} session(s), ${res.acceptedSpans} spans accepted, ${res.redactionCount} redaction(s); ${res.skippedSessions} already-uploaded skipped.`
1459
+ `Uploaded ${res.uploadedSessions} session(s), ${res.acceptedSpans} spans accepted, ${res.redactionCount} redaction(s); ${res.skippedSessions} already-uploaded skipped; ${res.refused.length} refused.`
1340
1460
  );
1341
1461
  }
1462
+ function reportRefused(refused) {
1463
+ for (const { harness, sessionId, verdict } of refused) {
1464
+ process.stderr.write(` REFUSED [${harness}] ${sessionId.slice(0, 8)}: ${verdict.status}
1465
+ `);
1466
+ for (const finding of verdict.findings.filter((f) => f.severity === "error")) {
1467
+ process.stderr.write(` ${finding.category}/${finding.detector} x${finding.count} at ${finding.paths.join(", ")}
1468
+ `);
1469
+ }
1470
+ for (const entry of verdict.unreadable) process.stderr.write(` unread ${entry}
1471
+ `);
1472
+ }
1473
+ if (refused.length > 0) {
1474
+ process.stderr.write(`${refused.length} session(s) refused by the share-safety check and kept on this machine.
1475
+ `);
1476
+ process.exitCode = 1;
1477
+ }
1478
+ }
1479
+ async function cmdVerifySafe(argv) {
1480
+ let target;
1481
+ let format = "text";
1482
+ let profile = "share";
1483
+ const knownSecrets = [];
1484
+ for (let i = 0; i < argv.length; i++) {
1485
+ const arg = argv[i];
1486
+ if (arg === "--format") format = argv[++i] ?? "";
1487
+ else if (arg === "--profile") profile = argv[++i] ?? "";
1488
+ else if (arg === "--known-secret-env") {
1489
+ const name = argv[++i] ?? "";
1490
+ const value = process.env[name];
1491
+ if (!value) throw new Error(`verify-safe: environment variable ${name || "(missing name)"} is not set`);
1492
+ knownSecrets.push(value);
1493
+ } else if (arg === "--help" || arg === "-h") {
1494
+ usageVerifySafe();
1495
+ return;
1496
+ } else if (arg.startsWith("--")) throw new Error(`verify-safe: unknown flag ${arg}`);
1497
+ else if (target === void 0) target = arg;
1498
+ else throw new Error(`verify-safe: unexpected argument ${arg}`);
1499
+ }
1500
+ if (!target) throw new Error("verify-safe needs a file or directory: traces verify-safe <file|dir>");
1501
+ if (format !== "json" && format !== "text") throw new Error(`verify-safe: --format must be json or text, got "${format}"`);
1502
+ if (profile !== "default" && profile !== "share" && profile !== "strict") {
1503
+ throw new Error(`verify-safe: --profile must be default, share or strict, got "${profile}"`);
1504
+ }
1505
+ const result = await verifySafe(target, { profile, knownSecrets });
1506
+ process.stdout.write(format === "json" ? `${JSON.stringify(result, null, 2)}
1507
+ ` : `${formatVerdict(result)}
1508
+ `);
1509
+ process.exitCode = verdictExitCode(result);
1510
+ }
1511
+ function usageVerifySafe() {
1512
+ console.log(`traces verify-safe <file|dir> [--profile default|share|strict] [--known-secret-env NAME]... [--format text|json]
1513
+
1514
+ Check whether files are safe to share, without changing them. A directory is read
1515
+ recursively. .json files are parsed whole, .jsonl and .ndjson line by line, and
1516
+ any other file is scanned as text.
1517
+
1518
+ SAFE nothing found exit 0
1519
+ SAFE_WITH_WARNINGS only warnings (raw content, identifiers, media) exit 0
1520
+ UNSAFE a credential, or personal data under share/strict exit 1
1521
+ UNKNOWN a file, line or byte range could not be read exit 2
1522
+
1523
+ --profile default | share (default) | strict; see agent-eval docs/redaction.md
1524
+ --known-secret-env name of an environment variable holding a secret to look for in
1525
+ any encoding (repeatable); the value is never printed
1526
+ --format text (default) | json
1527
+
1528
+ Findings name the file, line and JSON Pointer, never the matched value.`);
1529
+ }
1342
1530
  function usageExport() {
1343
1531
  console.log(`traces export: convert trace evidence files to OpenInference JSONL
1344
1532
 
@@ -1365,7 +1553,7 @@ Examples:
1365
1553
  halo spans.openinference.jsonl --prompt "Analyze this trace slice" --max-turns 1
1366
1554
 
1367
1555
  Safety:
1368
- export runs the same local regex redaction used by upload before writing spans.
1556
+ export runs the same local redaction (agent-eval's redaction core) used by upload before writing spans.
1369
1557
  --metadata must be a JSON object; --attr key=value is repeatable and overrides matching metadata keys.`);
1370
1558
  }
1371
1559
  function usageImportCodeTraceBench() {
@@ -1447,7 +1635,8 @@ Commands:
1447
1635
  facts Print the deterministic session-facts sheet for the selected
1448
1636
  sessions: tool calls excluding synthesized spans, subagent spawns
1449
1637
  with task names, human turns in order, the final message per task,
1450
- changed paths, first/last record times, and the harness token total.
1638
+ changed paths, first/last record times, the harness token total, and
1639
+ the first failure by agent-eval's fixed precedence (or none).
1451
1640
  No model call, no budget, $0. Every fact names the span ids it came
1452
1641
  from; a fact the spans cannot support is null with its reason.
1453
1642
  --format json (default) or text (exit 1 when a session cannot be read)
@@ -1458,6 +1647,17 @@ Commands:
1458
1647
  ledger sliced to the session window, git log, and a sha256 manifest
1459
1648
  (needs --session <id|path> and --out <new-dir>). This is the FULL
1460
1649
  view: it holds the session's own words, for a reader who cites them
1650
+ mcp Serve the selected traces to an MCP client over stdio: 7 read-only,
1651
+ idempotent trace tools; spans and results redacted, results capped
1652
+ and marked untrusted (traces mcp --help)
1653
+ diff <a> <b>
1654
+ Pair two runs' steps (by span id, then position with the same name
1655
+ and kind, then name and kind anywhere) and mark the first divergence:
1656
+ changed, replaced, only-in-a, only-in-b or reordered. Each side is an
1657
+ OTLP file or directory, any file convert reads, file#<trace id>, or
1658
+ file#branch=<id> for one arm (agent.branch.id or agent.branch.arm).
1659
+ --kind TOOL (repeatable) keeps only steps of that span kind.
1660
+ Exit 0 agree, 1 diverge, 2 unreadable (--format text|json)
1461
1661
  bundle verify <bundle-dir>
1462
1662
  Check a bundle against its manifest: every listed file present with
1463
1663
  its recorded size and SHA-256, no unlisted or non-regular file beside
@@ -1487,7 +1687,11 @@ Commands:
1487
1687
  stream Emit JSONL trace stream events for live visualizers or replay
1488
1688
  watch Online observer: tail active sessions, notify on loops + semantic findings
1489
1689
  (watch <target> tails ONE run tree instead: a run directory or a span file)
1490
- upload Redact + upload sessions in a time window to the Tangle Intelligence Platform
1690
+ upload Redact + upload sessions in a time window to the Tangle Intelligence Platform;
1691
+ a session whose final events are UNSAFE or UNKNOWN is refused (exit 1)
1692
+ verify-safe <file|dir>
1693
+ Say whether files are safe to share: SAFE, SAFE_WITH_WARNINGS, UNSAFE
1694
+ (exit 1) or UNKNOWN (exit 2). Read-only (--help for flags)
1491
1695
 
1492
1696
  Options:
1493
1697
  --harness <id> Harness or alias (default: claude-code). Known: ${knownHarnesses().join(", ")}
@@ -1513,9 +1717,8 @@ Options:
1513
1717
  A directory reads the OTLP files under it \u2014 only the otlp/
1514
1718
  subdirectory when the producer made one \u2014 and names the JSONL
1515
1719
  that is not OTLP instead of reading it as broken spans.
1516
- Supported by: validate, analyze, investigate, improve, ask, stream.
1517
- On a WRITING command it is the deprecated spelling of
1518
- --otlp-out; it still works, with a warning, until 0.12.
1720
+ Supported by: validate, analyze, facts, investigate, improve, ask,
1721
+ stream, mcp. Writing commands take --otlp-out.
1519
1722
  --source-bundle <dir> Analyze a retained full bundle; explicitly grant source-field reads.
1520
1723
  Available for analyze, investigate, improve, and ask.
1521
1724
  --otlp-out <path> WRITE the OTLP-JSONL artifact here (also evidence
@@ -1567,7 +1770,7 @@ Options:
1567
1770
  --min-loop <n> Min identical repeated calls to flag a loop (default 3)
1568
1771
  --dry-run upload: redact + dedup + preview, write OTLP, but do NOT send
1569
1772
  --no-content upload: strip prompt/response text; send metadata only
1570
- --redactor <cmd> upload: external PII scrubber (JSON array stdin\u2192stdout) after the regex pass
1773
+ --redactor <cmd> upload: external PII scrubber (JSON array stdin\u2192stdout) after the redaction core
1571
1774
  --yes, -y upload: skip the confirmation prompt
1572
1775
  --version, -v Print the installed traces version
1573
1776
  --help, -h Show help (use \`traces validate --help\`, \`traces export --help\`,
@@ -1585,7 +1788,7 @@ Upload env: TANGLE_INGEST_URL (or TANGLE_ORCHESTRATOR_URL), TANGLE_INGEST_API_KE
1585
1788
  async function main() {
1586
1789
  const rawArgs = process.argv.slice(2);
1587
1790
  if (rawArgs[0] === "--version" || rawArgs[0] === "-v" || rawArgs[0] === "version") {
1588
- console.log(`traces ${packageVersion()}`);
1791
+ console.log(`traces ${tracesVersion()}`);
1589
1792
  return;
1590
1793
  }
1591
1794
  if (rawArgs[0] === "replay-verify") {
@@ -1600,10 +1803,28 @@ async function main() {
1600
1803
  await cmdVerifyFindings(rawArgs.slice(1));
1601
1804
  return;
1602
1805
  }
1806
+ if (rawArgs[0] === "mcp") {
1807
+ await cmdMcp(parseArgs(rawArgs));
1808
+ return;
1809
+ }
1810
+ if (rawArgs[0] === "diff") {
1811
+ try {
1812
+ await cmdDiff(rawArgs.slice(1));
1813
+ } catch (err) {
1814
+ process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
1815
+ `);
1816
+ process.exitCode = 2;
1817
+ }
1818
+ return;
1819
+ }
1603
1820
  if (rawArgs[0] === "bundle" && rawArgs[1] === "verify") {
1604
1821
  await cmdBundleVerify(rawArgs.slice(2));
1605
1822
  return;
1606
1823
  }
1824
+ if (rawArgs[0] === "verify-safe") {
1825
+ await cmdVerifySafe(rawArgs.slice(1));
1826
+ return;
1827
+ }
1607
1828
  const parsedArgs = parseArgs(rawArgs);
1608
1829
  if (parsedArgs.help) {
1609
1830
  if (parsedArgs.command === "import-codetracebench" || parsedArgs.command === "help" && parsedArgs.input === "import-codetracebench") usageImportCodeTraceBench();