@tangle-network/traces 0.7.0 → 0.8.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/README.md +57 -3
- package/dist/{chunk-Q56AP2EY.js → chunk-D5OMTM4S.js} +1224 -315
- package/dist/cli.js +260 -12
- package/dist/index.d.ts +330 -28
- package/dist/index.js +51 -5
- package/package.json +12 -13
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
analyzeAdoption,
|
|
4
|
+
analyzeReactions,
|
|
3
5
|
analyzeSpans,
|
|
6
|
+
buildPolicyEvidenceRecord,
|
|
4
7
|
commandAnalyzer,
|
|
5
8
|
commandRedactor,
|
|
6
9
|
executeUpload,
|
|
10
|
+
exportTraceEvidenceFile,
|
|
7
11
|
haloAnalyzer,
|
|
8
12
|
knownHarnesses,
|
|
13
|
+
parseSession,
|
|
9
14
|
parseSince,
|
|
10
15
|
planUpload,
|
|
11
16
|
resolveAdapter,
|
|
12
17
|
runExternalAnalyzers,
|
|
13
18
|
runPipelines,
|
|
14
19
|
selectAdapters,
|
|
20
|
+
serializePolicyEvidence,
|
|
21
|
+
serializeSpans,
|
|
15
22
|
watchSessions,
|
|
16
|
-
writeOtlpFile
|
|
17
|
-
|
|
23
|
+
writeOtlpFile,
|
|
24
|
+
writePolicyEvidenceFile,
|
|
25
|
+
writeTraceEvidenceExportFile
|
|
26
|
+
} from "./chunk-D5OMTM4S.js";
|
|
18
27
|
|
|
19
28
|
// src/cli.ts
|
|
20
|
-
import { stat, writeFile } from "fs/promises";
|
|
29
|
+
import { readFile, stat, writeFile } from "fs/promises";
|
|
21
30
|
|
|
22
31
|
// src/report.ts
|
|
23
32
|
var SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
@@ -95,11 +104,93 @@ function renderPipelines(pr) {
|
|
|
95
104
|
lines.push("");
|
|
96
105
|
return lines.join("\n");
|
|
97
106
|
}
|
|
107
|
+
var REACTION_BADGE = {
|
|
108
|
+
correction: "correction",
|
|
109
|
+
frustration: "frustration",
|
|
110
|
+
jargon: "jargon-complaint",
|
|
111
|
+
structure: "structure-complaint",
|
|
112
|
+
praise: "praise"
|
|
113
|
+
};
|
|
114
|
+
function ratioStr(r) {
|
|
115
|
+
if (r === null) return "n/a (no reaction signals)";
|
|
116
|
+
if (!Number.isFinite(r)) return "\u221E (corrective with zero praise)";
|
|
117
|
+
return `${r.toFixed(2)}:1`;
|
|
118
|
+
}
|
|
119
|
+
function oneLine(s, n) {
|
|
120
|
+
const t = s.replace(/\s+/g, " ").trim();
|
|
121
|
+
return t.length > n ? `${t.slice(0, n)}\u2026` : t;
|
|
122
|
+
}
|
|
123
|
+
function renderReactions(rr) {
|
|
124
|
+
const lines = ["## user reactions (deterministic, human turns only)", ""];
|
|
125
|
+
const total = Object.values(rr.signals).reduce((a, b) => a + b, 0);
|
|
126
|
+
if (rr.humanReactionTurns === 0) {
|
|
127
|
+
lines.push("- No human turns followed an assistant turn (nothing to classify).");
|
|
128
|
+
lines.push("");
|
|
129
|
+
return lines.join("\n");
|
|
130
|
+
}
|
|
131
|
+
lines.push(
|
|
132
|
+
`- **Reaction turns:** ${rr.humanReactionTurns} human turn(s) followed an assistant turn; ${total} carried a reaction signal.`
|
|
133
|
+
);
|
|
134
|
+
lines.push(`- **Corrective-to-positive ratio:** ${ratioStr(rr.correctiveToPositiveRatio)}`);
|
|
135
|
+
lines.push("");
|
|
136
|
+
lines.push("| Signal | Count |");
|
|
137
|
+
lines.push("|---|---|");
|
|
138
|
+
for (const [k, v] of Object.entries(rr.signals)) {
|
|
139
|
+
if (v > 0) lines.push(`| ${REACTION_BADGE[k] ?? k} | ${v} |`);
|
|
140
|
+
}
|
|
141
|
+
lines.push("");
|
|
142
|
+
if (rr.triggerPairs.length > 0) {
|
|
143
|
+
lines.push("### top trigger pairs (assistant prose \u2192 human reaction)");
|
|
144
|
+
lines.push("");
|
|
145
|
+
for (const p of rr.triggerPairs) {
|
|
146
|
+
lines.push(`- **[${p.reactions.map((r) => REACTION_BADGE[r] ?? r).join(", ")}]**`);
|
|
147
|
+
lines.push(` - assistant: \`${oneLine(p.assistant, 180)}\``);
|
|
148
|
+
lines.push(` - human: \`${oneLine(p.human, 180)}\``);
|
|
149
|
+
}
|
|
150
|
+
lines.push("");
|
|
151
|
+
}
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
|
154
|
+
function renderAdoption(ar) {
|
|
155
|
+
const lines = ["## skill & subagent adoption (deterministic)", ""];
|
|
156
|
+
lines.push(
|
|
157
|
+
`- **Skill penetration:** ${(ar.skillPenetration * 100).toFixed(0)}% (${ar.sessionsWithSkill}/${ar.sessionCount} session(s) invoked a skill explicitly)`
|
|
158
|
+
);
|
|
159
|
+
lines.push(
|
|
160
|
+
`- **Explicit skill invocations:** ${ar.totalSkillInvocations} (Skill tool spans) \xB7 **Loop-dispatched runs:** ${ar.totalLoopDispatchedRuns} (from ${ar.skillRunFilesRead} \`.evolve/skill-runs.jsonl\` file(s))`
|
|
161
|
+
);
|
|
162
|
+
lines.push(
|
|
163
|
+
`- **Subagent spawns:** ${ar.totalSubagentSpawns} across ${ar.sessionsWithSubagent} session(s)`
|
|
164
|
+
);
|
|
165
|
+
lines.push("");
|
|
166
|
+
const skillRows = Object.entries(ar.skillInvocations).sort((a, b) => b[1] - a[1]);
|
|
167
|
+
const loopRows = Object.entries(ar.loopDispatchedRuns).sort((a, b) => b[1] - a[1]);
|
|
168
|
+
if (skillRows.length > 0 || loopRows.length > 0) {
|
|
169
|
+
lines.push("| Skill | Explicit invocations | Loop-dispatched runs |");
|
|
170
|
+
lines.push("|---|---|---|");
|
|
171
|
+
const names = [.../* @__PURE__ */ new Set([...skillRows.map((r) => r[0]), ...loopRows.map((r) => r[0])])].sort(
|
|
172
|
+
(a, b) => (ar.skillInvocations[b] ?? 0) + (ar.loopDispatchedRuns[b] ?? 0) - (ar.skillInvocations[a] ?? 0) - (ar.loopDispatchedRuns[a] ?? 0)
|
|
173
|
+
);
|
|
174
|
+
for (const n of names) {
|
|
175
|
+
lines.push(`| \`${n}\` | ${ar.skillInvocations[n] ?? 0} | ${ar.loopDispatchedRuns[n] ?? 0} |`);
|
|
176
|
+
}
|
|
177
|
+
lines.push("");
|
|
178
|
+
}
|
|
179
|
+
const agentRows = Object.entries(ar.subagentSpawns).sort((a, b) => b[1] - a[1]);
|
|
180
|
+
if (agentRows.length > 0) {
|
|
181
|
+
lines.push("| Subagent | Spawns |");
|
|
182
|
+
lines.push("|---|---|");
|
|
183
|
+
for (const [n, c] of agentRows) lines.push(`| \`${n}\` | ${c} |`);
|
|
184
|
+
lines.push("");
|
|
185
|
+
}
|
|
186
|
+
return lines.join("\n");
|
|
187
|
+
}
|
|
98
188
|
|
|
99
189
|
// src/cli.ts
|
|
100
190
|
function parseArgs(argv) {
|
|
101
191
|
const a = {
|
|
102
|
-
command: argv[0] ?? "help",
|
|
192
|
+
command: argv[0] === "--help" || argv[0] === "-h" ? "help" : argv[0] ?? "help",
|
|
193
|
+
help: argv[0] === "--help" || argv[0] === "-h",
|
|
103
194
|
harness: "claude-code",
|
|
104
195
|
harnessExplicit: false,
|
|
105
196
|
all: false,
|
|
@@ -111,7 +202,8 @@ function parseArgs(argv) {
|
|
|
111
202
|
dryRun: false,
|
|
112
203
|
yes: false,
|
|
113
204
|
noContent: false,
|
|
114
|
-
analyzers: []
|
|
205
|
+
analyzers: [],
|
|
206
|
+
attrs: []
|
|
115
207
|
};
|
|
116
208
|
for (let i = 1; i < argv.length; i++) {
|
|
117
209
|
const arg = argv[i];
|
|
@@ -151,6 +243,14 @@ function parseArgs(argv) {
|
|
|
151
243
|
case "--model":
|
|
152
244
|
a.model = next();
|
|
153
245
|
break;
|
|
246
|
+
case "--metadata":
|
|
247
|
+
a.metadata = next();
|
|
248
|
+
break;
|
|
249
|
+
case "--attr": {
|
|
250
|
+
const v = next();
|
|
251
|
+
if (v) a.attrs.push(v);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
154
254
|
case "--interval":
|
|
155
255
|
a.interval = Number(next());
|
|
156
256
|
break;
|
|
@@ -177,12 +277,21 @@ function parseArgs(argv) {
|
|
|
177
277
|
case "--redactor":
|
|
178
278
|
a.redactorCmd = next();
|
|
179
279
|
break;
|
|
280
|
+
case "--format":
|
|
281
|
+
a.format = next();
|
|
282
|
+
break;
|
|
283
|
+
case "--help":
|
|
284
|
+
case "-h":
|
|
285
|
+
a.help = true;
|
|
286
|
+
break;
|
|
180
287
|
case "--yes":
|
|
181
288
|
case "-y":
|
|
182
289
|
a.yes = true;
|
|
183
290
|
break;
|
|
184
291
|
default:
|
|
185
292
|
if (arg?.startsWith("--")) throw new Error(`unknown flag: ${arg}`);
|
|
293
|
+
if (arg && !a.input) a.input = arg;
|
|
294
|
+
else if (arg) throw new Error(`unexpected positional argument: ${arg}`);
|
|
186
295
|
}
|
|
187
296
|
}
|
|
188
297
|
return a;
|
|
@@ -243,23 +352,27 @@ async function collectSpans(args) {
|
|
|
243
352
|
harness: adapter.harness,
|
|
244
353
|
sessionId: args.session,
|
|
245
354
|
path: args.session,
|
|
246
|
-
cwd
|
|
355
|
+
// --session is an explicit file; honor --cwd so adoption can find the
|
|
356
|
+
// project's .evolve/skill-runs.jsonl (locate() infers cwd in the scan path).
|
|
357
|
+
cwd: args.cwd ?? null,
|
|
247
358
|
mtimeMs: st.mtimeMs
|
|
248
359
|
};
|
|
249
|
-
return { spans: await adapter
|
|
360
|
+
return { spans: await parseSession(adapter, ref), harness: adapter.harness, sessionCount: 1, cwds: ref.cwd ? [ref.cwd] : [] };
|
|
250
361
|
}
|
|
251
362
|
const groups = await discover({ ...args, last: args.last || 1 });
|
|
252
363
|
const spans = [];
|
|
253
364
|
let sessionCount = 0;
|
|
254
365
|
const harnesses = [];
|
|
366
|
+
const cwds = [];
|
|
255
367
|
for (const { adapter, refs } of groups) {
|
|
256
368
|
if (refs.length > 0) harnesses.push(adapter.harness);
|
|
257
369
|
for (const ref of refs) {
|
|
258
|
-
spans.push(...await adapter
|
|
370
|
+
spans.push(...await parseSession(adapter, ref));
|
|
371
|
+
if (ref.cwd) cwds.push(ref.cwd);
|
|
259
372
|
sessionCount += 1;
|
|
260
373
|
}
|
|
261
374
|
}
|
|
262
|
-
return { spans, harness: harnesses.join("+") || args.harness, sessionCount };
|
|
375
|
+
return { spans, harness: harnesses.join("+") || args.harness, sessionCount, cwds };
|
|
263
376
|
}
|
|
264
377
|
async function cmdConvert(args) {
|
|
265
378
|
const { spans } = await collectSpans(args);
|
|
@@ -267,8 +380,92 @@ async function cmdConvert(args) {
|
|
|
267
380
|
const path = await writeOtlpFile(spans, args.otlp);
|
|
268
381
|
console.log(`wrote ${spans.length} spans \u2192 ${path}`);
|
|
269
382
|
}
|
|
383
|
+
async function collectSessionRows(args) {
|
|
384
|
+
if (args.session) {
|
|
385
|
+
const adapter = resolveAdapter(args.harness);
|
|
386
|
+
if (!adapter) throw new Error(`unknown harness "${args.harness}"`);
|
|
387
|
+
const st = await stat(args.session);
|
|
388
|
+
const ref = {
|
|
389
|
+
harness: adapter.harness,
|
|
390
|
+
sessionId: args.session,
|
|
391
|
+
path: args.session,
|
|
392
|
+
cwd: args.cwd ?? null,
|
|
393
|
+
mtimeMs: st.mtimeMs
|
|
394
|
+
};
|
|
395
|
+
return [{ ref, spans: await parseSession(adapter, ref) }];
|
|
396
|
+
}
|
|
397
|
+
const groups = await discover({ ...args, last: args.last || 20 });
|
|
398
|
+
const rows = [];
|
|
399
|
+
for (const { adapter, refs } of groups) {
|
|
400
|
+
for (const ref of refs) rows.push({ ref, spans: await parseSession(adapter, ref) });
|
|
401
|
+
}
|
|
402
|
+
return rows;
|
|
403
|
+
}
|
|
404
|
+
async function cmdEvidence(args) {
|
|
405
|
+
const rows = (await collectSessionRows(args)).filter((row) => row.spans.length > 0);
|
|
406
|
+
if (rows.length === 0) throw new Error("no spans found for the given selection");
|
|
407
|
+
const otlpPath = args.otlp ? await writeOtlpFile(rows.flatMap((row) => row.spans), args.otlp) : void 0;
|
|
408
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
409
|
+
const records = await Promise.all(rows.map(
|
|
410
|
+
(row) => buildPolicyEvidenceRecord(row.ref, row.spans, {
|
|
411
|
+
generatedAt,
|
|
412
|
+
minLoopOccurrences: args.minLoop,
|
|
413
|
+
maxLoopExamples: 25,
|
|
414
|
+
otlpPath
|
|
415
|
+
})
|
|
416
|
+
));
|
|
417
|
+
if (args.out) {
|
|
418
|
+
const path = await writePolicyEvidenceFile(records, args.out);
|
|
419
|
+
console.log(`policy evidence \u2192 ${path} (${records.length} session rows${otlpPath ? `, OTLP: ${otlpPath}` : ""})`);
|
|
420
|
+
} else {
|
|
421
|
+
process.stdout.write(serializePolicyEvidence(records));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function cmdExport(args) {
|
|
425
|
+
if (!args.input) throw new Error("export needs an input file; run `traces export --help` for examples");
|
|
426
|
+
const format = args.format ?? "auto";
|
|
427
|
+
if (format !== "auto" && format !== "policy-evidence" && format !== "sandbox-events" && format !== "openinference") {
|
|
428
|
+
throw new Error("--format must be auto, policy-evidence, sandbox-events, or openinference");
|
|
429
|
+
}
|
|
430
|
+
const attributes = await loadExportAttributes(args);
|
|
431
|
+
const outPath = args.out ?? args.otlp;
|
|
432
|
+
if (outPath) {
|
|
433
|
+
const result2 = await writeTraceEvidenceExportFile(args.input, outPath, { format, attributes });
|
|
434
|
+
console.log(
|
|
435
|
+
`exported ${result2.spans.length} OpenInference span(s) from ${result2.format} \u2192 ${result2.path} (${result2.redactionCount} redaction${result2.redactionCount === 1 ? "" : "s"})`
|
|
436
|
+
);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const result = await exportTraceEvidenceFile(args.input, { format, attributes });
|
|
440
|
+
process.stdout.write(serializeSpans(result.spans));
|
|
441
|
+
}
|
|
442
|
+
function parseAttributeValue(raw) {
|
|
443
|
+
if (raw === "true") return true;
|
|
444
|
+
if (raw === "false") return false;
|
|
445
|
+
if (/^-?\d+(?:\.\d+)?$/.test(raw)) {
|
|
446
|
+
const number = Number(raw);
|
|
447
|
+
if (Number.isFinite(number)) return number;
|
|
448
|
+
}
|
|
449
|
+
return raw;
|
|
450
|
+
}
|
|
451
|
+
async function loadExportAttributes(args) {
|
|
452
|
+
const attributes = {};
|
|
453
|
+
if (args.metadata) {
|
|
454
|
+
const parsed = JSON.parse(await readFile(args.metadata, "utf8"));
|
|
455
|
+
if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
456
|
+
throw new Error("--metadata must point to a JSON object");
|
|
457
|
+
}
|
|
458
|
+
Object.assign(attributes, parsed);
|
|
459
|
+
}
|
|
460
|
+
for (const attr of args.attrs) {
|
|
461
|
+
const eq = attr.indexOf("=");
|
|
462
|
+
if (eq <= 0) throw new Error(`--attr must be key=value, got ${attr}`);
|
|
463
|
+
attributes[attr.slice(0, eq)] = parseAttributeValue(attr.slice(eq + 1));
|
|
464
|
+
}
|
|
465
|
+
return attributes;
|
|
466
|
+
}
|
|
270
467
|
async function cmdAnalyze(args) {
|
|
271
|
-
const { spans, harness, sessionCount } = await collectSpans(args);
|
|
468
|
+
const { spans, harness, sessionCount, cwds } = await collectSpans(args);
|
|
272
469
|
if (spans.length === 0) throw new Error("no spans found for the given selection");
|
|
273
470
|
const ai = args.llm ? await buildAxService() : void 0;
|
|
274
471
|
const { otlpPath, result } = await analyzeSpans(spans, {
|
|
@@ -280,8 +477,12 @@ async function cmdAnalyze(args) {
|
|
|
280
477
|
`)
|
|
281
478
|
});
|
|
282
479
|
const pipelines = await runPipelines(spans, { minLoopOccurrences: args.minLoop });
|
|
480
|
+
const reactions = analyzeReactions(spans);
|
|
481
|
+
const adoption = await analyzeAdoption(spans, { cwds });
|
|
283
482
|
let report = `${renderReport(result, { harness, sessionCount, spanCount: spans.length, otlpPath })}
|
|
284
|
-
${renderPipelines(pipelines)}
|
|
483
|
+
${renderPipelines(pipelines)}
|
|
484
|
+
${renderReactions(reactions)}
|
|
485
|
+
${renderAdoption(adoption)}`;
|
|
285
486
|
if (args.analyzers.length > 0 && otlpPath) {
|
|
286
487
|
const engines = args.analyzers.map(
|
|
287
488
|
(spec) => spec === "halo" ? haloAnalyzer({ defaultPrompt: args.analyzerPrompt, model: args.model }) : commandAnalyzer({ name: spec, command: spec, args: (p, prompt) => prompt ? [p, prompt] : [p] })
|
|
@@ -382,6 +583,32 @@ Window: since ${new Date(sinceMs).toISOString()}
|
|
|
382
583
|
`Uploaded ${res.uploadedSessions} session(s), ${res.acceptedSpans} spans accepted, ${res.redactionCount} redaction(s); ${res.skippedSessions} already-uploaded skipped.`
|
|
383
584
|
);
|
|
384
585
|
}
|
|
586
|
+
function usageExport() {
|
|
587
|
+
console.log(`traces export \u2014 convert trace evidence files to OpenInference JSONL
|
|
588
|
+
|
|
589
|
+
Usage:
|
|
590
|
+
traces export <input.jsonl|input.json> --out <spans.jsonl> [--format auto]
|
|
591
|
+
traces export <input.json> --attr task.id=abc --attr arm=baseline --out spans.jsonl
|
|
592
|
+
traces export <input.json> --metadata run-metadata.json --out spans.jsonl
|
|
593
|
+
traces export <input.jsonl|input.json> > spans.jsonl
|
|
594
|
+
|
|
595
|
+
Input formats:
|
|
596
|
+
policy-evidence Compact JSONL rows from \`traces evidence --out policy-evidence.jsonl\`
|
|
597
|
+
sandbox-events Sandbox/OpenCode event arrays with start/raw/result/done/error events
|
|
598
|
+
openinference Existing OpenInference JSONL; rewrites through traces redaction
|
|
599
|
+
auto Detect the format from the file contents (default)
|
|
600
|
+
|
|
601
|
+
Examples:
|
|
602
|
+
traces evidence --all --since 24h --out policy-evidence.jsonl
|
|
603
|
+
traces export policy-evidence.jsonl --out spans.openinference.jsonl
|
|
604
|
+
traces export sandbox-events.json --format sandbox-events --out spans.openinference.jsonl
|
|
605
|
+
traces export sandbox-events.json --attr task.id=aec-001 --attr outcome.score=1 --out spans.openinference.jsonl
|
|
606
|
+
halo spans.openinference.jsonl --prompt "Analyze this trace slice" --max-turns 1
|
|
607
|
+
|
|
608
|
+
Safety:
|
|
609
|
+
export runs the same local regex redaction used by upload before writing spans.
|
|
610
|
+
--metadata must be a JSON object; --attr key=value is repeatable and overrides matching metadata keys.`);
|
|
611
|
+
}
|
|
385
612
|
function usage() {
|
|
386
613
|
console.log(`traces \u2014 analyze, observe & upload coding-agent sessions
|
|
387
614
|
|
|
@@ -389,6 +616,8 @@ Commands:
|
|
|
389
616
|
list List discovered sessions
|
|
390
617
|
analyze Run analyst suite + loop/waste pipelines, write a markdown report
|
|
391
618
|
convert Emit OTLP-JSONL only (HALO: use analyze --analyzer halo)
|
|
619
|
+
export Convert evidence/events files to OpenInference JSONL for HALO
|
|
620
|
+
evidence Emit compact session-evidence JSONL for downstream policy miners
|
|
392
621
|
watch Online observer: tail active sessions, notify on stuck loops (read-only)
|
|
393
622
|
upload Redact + upload sessions in a time window to the Tangle Intelligence Platform
|
|
394
623
|
|
|
@@ -400,7 +629,10 @@ Options:
|
|
|
400
629
|
--cwd <dir> Filter sessions by working directory
|
|
401
630
|
--since <t> upload: window \u2014 30m / 2h / 7d or an ISO date (default 24h); analyze: ISO cutoff
|
|
402
631
|
--out <path> Write report to a file
|
|
403
|
-
--otlp <path> OTLP artifact path (also
|
|
632
|
+
--otlp <path> OTLP artifact path (also evidence provenance / dry-run upload preview)
|
|
633
|
+
--format <kind> export: auto | policy-evidence | sandbox-events | openinference
|
|
634
|
+
--metadata <json> export: attach JSON object fields as span attributes
|
|
635
|
+
--attr <k=v> export: attach one span attribute (repeatable)
|
|
404
636
|
--llm Enable agentic RLM analysts (needs OPENAI_API_KEY / OPENAI_BASE_URL)
|
|
405
637
|
--model <id> --llm model id (e.g. a router model like glm-5.2); default is agent-eval's
|
|
406
638
|
--budget <usd> USD cap for agentic analysts
|
|
@@ -413,12 +645,22 @@ Options:
|
|
|
413
645
|
--no-content upload: strip prompt/response text \u2014 send metadata only
|
|
414
646
|
--redactor <cmd> upload: external PII scrubber (JSON array stdin\u2192stdout) after the regex pass
|
|
415
647
|
--yes, -y upload: skip the confirmation prompt
|
|
648
|
+
--help, -h Show help (use \`traces export --help\` for export examples)
|
|
416
649
|
|
|
417
650
|
Upload env: TANGLE_INGEST_URL (or TANGLE_ORCHESTRATOR_URL), TANGLE_INGEST_API_KEY (or TANGLE_API_KEY), TANGLE_TENANT_ID`);
|
|
418
651
|
}
|
|
419
652
|
async function main() {
|
|
420
653
|
const args = parseArgs(process.argv.slice(2));
|
|
654
|
+
if (args.help) {
|
|
655
|
+
if (args.command === "export" || args.command === "help" && args.input === "export") usageExport();
|
|
656
|
+
else usage();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
421
659
|
switch (args.command) {
|
|
660
|
+
case "help":
|
|
661
|
+
if (args.input === "export") usageExport();
|
|
662
|
+
else usage();
|
|
663
|
+
break;
|
|
422
664
|
case "list":
|
|
423
665
|
await cmdList(args);
|
|
424
666
|
break;
|
|
@@ -428,6 +670,12 @@ async function main() {
|
|
|
428
670
|
case "convert":
|
|
429
671
|
await cmdConvert(args);
|
|
430
672
|
break;
|
|
673
|
+
case "export":
|
|
674
|
+
await cmdExport(args);
|
|
675
|
+
break;
|
|
676
|
+
case "evidence":
|
|
677
|
+
await cmdEvidence(args);
|
|
678
|
+
break;
|
|
431
679
|
case "watch":
|
|
432
680
|
await cmdWatch(args);
|
|
433
681
|
break;
|