blindseal 0.0.0-stage → 0.5.0

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VaultMCP contributors
4
+ Copyright (c) 2026 Blindseal contributors
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ makeLayer0Result,
4
+ runLayer0,
5
+ runLayer1
6
+ } from "./chunk-XV57KLEO.js";
7
+ import {
8
+ openAudit
9
+ } from "./chunk-PQ457W6T.js";
10
+ import "./chunk-PDX44BCA.js";
11
+
12
+ // src/cli/attest.ts
13
+ import { createHash, randomBytes } from "crypto";
14
+ import { existsSync, readFileSync, writeFileSync } from "fs";
15
+ import { spawnSync } from "child_process";
16
+ import os from "os";
17
+ import path from "path";
18
+
19
+ // src/chain/solana.ts
20
+ var CHAIN_ID_SOLANA_MAINNET = 1;
21
+ var SYSTEM_PROGRAM = "11111111111111111111111111111111";
22
+ var TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
23
+ var RECIPIENT_KEYS = ["recipient", "to", "destination", "dest", "receiver", "toPubkey", "toAddress"];
24
+ var AMOUNT_KEYS = ["amount", "lamports", "value", "tokenAmount", "quantity"];
25
+ var MINT_KEYS = ["mint", "token", "tokenMint", "mintAddress"];
26
+ var AUTHORITY_KEYS = ["authority", "owner", "from", "source", "payer", "fromPubkey", "signer"];
27
+ var PROGRAM_KEYS = ["program", "programId", "programAddress"];
28
+ var TRANSFER_TOOL = /(?:^|_|\b)(?:transfer|send|pay|spl[_-]?transfer|transfer[_-]?checked)(?:_|\b|$)/i;
29
+ function firstString(obj, keys) {
30
+ for (const k of keys) {
31
+ const v = obj[k];
32
+ if (typeof v === "string" && v.length > 0) return v;
33
+ if (typeof v === "number" && Number.isFinite(v)) return String(v);
34
+ if (typeof v === "bigint") return v.toString();
35
+ }
36
+ return void 0;
37
+ }
38
+ function parseSolanaAction(toolName, args, opts = {}) {
39
+ if (!args || typeof args !== "object") return null;
40
+ const a = args;
41
+ const amount = firstString(a, AMOUNT_KEYS);
42
+ const recipient = firstString(a, RECIPIENT_KEYS);
43
+ const explicitProgram = firstString(a, PROGRAM_KEYS);
44
+ const looksLikeTransfer = TRANSFER_TOOL.test(toolName);
45
+ if (!looksLikeTransfer && !explicitProgram) return null;
46
+ if (recipient === void 0 && amount === void 0 && explicitProgram === void 0) return null;
47
+ const mint = firstString(a, MINT_KEYS);
48
+ const sourceAuthority = firstString(a, AUTHORITY_KEYS);
49
+ void opts;
50
+ const chainId = CHAIN_ID_SOLANA_MAINNET;
51
+ const targetProgram = explicitProgram ?? (mint ? TOKEN_PROGRAM : SYSTEM_PROGRAM);
52
+ const instruction = typeof a.instruction === "string" && a.instruction || typeof a.method === "string" && a.method || (mint ? "transferChecked" : "transfer");
53
+ return {
54
+ chain: "solana",
55
+ chainId,
56
+ targetProgram,
57
+ instruction,
58
+ recipient: recipient ?? "",
59
+ mint: mint ?? "",
60
+ amount: amount ?? "",
61
+ sourceAuthority: sourceAuthority ?? ""
62
+ };
63
+ }
64
+
65
+ // src/cli/attest.ts
66
+ var HELP = `blindseal attest \u2014 screen a tool call, record the decision, prove it
67
+
68
+ Usage:
69
+ blindseal attest <toolcall.json> --out <attestation.json> --target-program <program id> [flags]
70
+
71
+ Flags
72
+ --out <file> where to write attestation.json (default ./attestation.json)
73
+ --target-program <id> the gated program the action targets (base58)
74
+ --instruction <16 hex> instruction discriminator (default: transfer-gate gated_transfer)
75
+ --expiry <slot> expirySlot (default 2000000000)
76
+ --prover <path> prover binary (default crates/prover/target/release/blindseal-prover)
77
+ --audit-dir <dir> audit base dir (default ~/.blindseal)
78
+ --help, -h this help
79
+
80
+ Only clean verdicts are attested. The decision is appended to the hash-chained audit log first.
81
+ `;
82
+ var GATED_TRANSFER_DISC = "0x981894646560fc2b";
83
+ function parse(argv) {
84
+ const o = { out: "attestation.json", instruction: GATED_TRANSFER_DISC, expiry: "2000000000", auditDir: path.join(os.homedir(), ".blindseal") };
85
+ for (let i = 0; i < argv.length; i++) {
86
+ const a = argv[i];
87
+ if (a === "--help" || a === "-h") o.help = true;
88
+ else if (a === "--out") o.out = argv[++i];
89
+ else if (a === "--target-program") o.targetProgram = argv[++i];
90
+ else if (a === "--instruction") o.instruction = argv[++i];
91
+ else if (a === "--expiry") o.expiry = argv[++i];
92
+ else if (a === "--prover") o.prover = argv[++i];
93
+ else if (a === "--audit-dir") o.auditDir = argv[++i];
94
+ else if (a.startsWith("--")) throw new Error(`unknown flag: ${a}`);
95
+ else o.file = a;
96
+ }
97
+ return o;
98
+ }
99
+ function findProver(explicit) {
100
+ if (explicit) return explicit;
101
+ let dir = process.cwd();
102
+ for (let i = 0; i < 7; i++) {
103
+ const c = path.join(dir, "crates/prover/target/release/blindseal-prover");
104
+ if (existsSync(c)) return c;
105
+ const parent = path.dirname(dir);
106
+ if (parent === dir) break;
107
+ dir = parent;
108
+ }
109
+ throw new Error("prover binary not found; build crates/prover or pass --prover");
110
+ }
111
+ async function runAttestCli(argv) {
112
+ let o;
113
+ try {
114
+ o = parse(argv);
115
+ } catch (e) {
116
+ process.stderr.write(`blindseal attest: ${e.message}
117
+ ${HELP}`);
118
+ return 2;
119
+ }
120
+ if (o.help || !o.file) {
121
+ process.stdout.write(HELP);
122
+ return o.help ? 0 : 2;
123
+ }
124
+ if (!o.targetProgram) {
125
+ process.stderr.write("blindseal attest: --target-program is required\n");
126
+ return 2;
127
+ }
128
+ const raw = readFileSync(o.file, "utf8");
129
+ const call = JSON.parse(raw);
130
+ const payload = JSON.stringify({ tool: call.toolName, args: call.args });
131
+ const payloadHash = "0x" + createHash("sha256").update(payload).digest("hex");
132
+ const l0 = runLayer0(payload);
133
+ const result = l0.fired ? makeLayer0Result(l0) : runLayer1(payload);
134
+ const verdict = result.verdict;
135
+ const decisionId = "d_" + randomBytes(8).toString("hex");
136
+ const audit = openAudit(o.auditDir);
137
+ const entry = audit.chain.append({ decisionId, verdict, payloadHash });
138
+ process.stdout.write(`decision ${decisionId}: ${verdict} (audit #${entry.index} ${entry.hash.slice(0, 18)}\u2026)
139
+ `);
140
+ if (verdict !== "clean") {
141
+ process.stderr.write(`blindseal attest: verdict is ${verdict} (${result.detectedPatterns.join(", ")}); no attestation for a non-clean call
142
+ `);
143
+ return 1;
144
+ }
145
+ const action = parseSolanaAction(call.toolName, call.args);
146
+ if (!action) {
147
+ process.stderr.write("blindseal attest: the call is not a recognizable on-chain action\n");
148
+ return 1;
149
+ }
150
+ if (!action.recipient || !action.amount || !action.sourceAuthority) {
151
+ process.stderr.write("blindseal attest: action needs recipient, amount and source authority\n");
152
+ return 1;
153
+ }
154
+ const prover = findProver(o.prover);
155
+ const tmpPayload = path.join(os.tmpdir(), `blindseal-payload-${decisionId}.json`);
156
+ writeFileSync(tmpPayload, payload);
157
+ const args = [
158
+ "--payload",
159
+ tmpPayload,
160
+ "--out",
161
+ o.out,
162
+ "--decision-id",
163
+ decisionId,
164
+ "--chain-id",
165
+ String(action.chainId),
166
+ "--target-program",
167
+ o.targetProgram,
168
+ "--source-authority",
169
+ action.sourceAuthority,
170
+ "--recipient",
171
+ action.recipient,
172
+ "--mint",
173
+ action.mint,
174
+ "--amount",
175
+ action.amount,
176
+ "--instruction",
177
+ o.instruction,
178
+ "--expiry",
179
+ o.expiry
180
+ ];
181
+ process.stdout.write(`proving (this takes a while on CPU)\u2026
182
+ `);
183
+ const r = spawnSync(prover, args, { stdio: "inherit" });
184
+ if (r.status !== 0) {
185
+ process.stderr.write(`blindseal attest: prover exited ${r.status}
186
+ `);
187
+ return 1;
188
+ }
189
+ process.stdout.write(`attestation written: ${o.out} (decision ${decisionId}, payloadHash ${payloadHash})
190
+ `);
191
+ return 0;
192
+ }
193
+ export {
194
+ runAttestCli
195
+ };
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ openAudit
4
+ } from "./chunk-PQ457W6T.js";
5
+ import "./chunk-PDX44BCA.js";
6
+
7
+ // src/cli/audit.ts
8
+ import { writeFileSync } from "fs";
9
+ import os from "os";
10
+ import path from "path";
11
+ var HELP = `blindseal audit \u2014 the hash-chained decision log
12
+
13
+ Usage:
14
+ blindseal audit verify [--dir <dir>]
15
+ blindseal audit disclose --from <index> --to <index> [--out <file>] [--dir <dir>]
16
+
17
+ Flags
18
+ --dir <dir> audit base dir (default ~/.blindseal)
19
+ --out <file> write the disclosure here (default disclosure-<from>-<to>.json)
20
+ --help, -h this help
21
+ `;
22
+ async function runAuditCli(argv) {
23
+ const sub = argv[0];
24
+ if (!sub || sub === "--help" || sub === "-h") {
25
+ process.stdout.write(HELP);
26
+ return sub ? 0 : 2;
27
+ }
28
+ let dir = path.join(os.homedir(), ".blindseal"), from, to, out;
29
+ for (let i = 1; i < argv.length; i++) {
30
+ const a = argv[i];
31
+ if (a === "--dir") dir = argv[++i];
32
+ else if (a === "--from") from = Number.parseInt(argv[++i], 10);
33
+ else if (a === "--to") to = Number.parseInt(argv[++i], 10);
34
+ else if (a === "--out") out = argv[++i];
35
+ else {
36
+ process.stderr.write(`blindseal audit: unknown flag ${a}
37
+ ${HELP}`);
38
+ return 2;
39
+ }
40
+ }
41
+ const audit = openAudit(dir);
42
+ if (sub === "verify") {
43
+ const head = audit.chain.head();
44
+ if (!head) {
45
+ process.stdout.write("audit: empty chain (nothing recorded yet)\n");
46
+ return 0;
47
+ }
48
+ const r = audit.chain.verify();
49
+ process.stdout.write(`audit: ${r.ok ? "INTACT" : "BROKEN"} \u2014 ${r.checkedTo - r.checkedFrom + 1} entries, head #${head.index} ${head.hash}${r.ok ? "" : ` (broken at #${r.brokenAt})`}
50
+ `);
51
+ return r.ok ? 0 : 1;
52
+ }
53
+ if (sub === "disclose") {
54
+ if (from === void 0 || to === void 0 || Number.isNaN(from) || Number.isNaN(to) || from > to) {
55
+ process.stderr.write("blindseal audit disclose: --from and --to (indices, from \u2264 to) are required\n");
56
+ return 2;
57
+ }
58
+ const d = audit.disclose(from, to);
59
+ const file = out ?? `disclosure-${from}-${to}.json`;
60
+ writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
61
+ process.stdout.write(`disclosure written: ${file} (${d.entries.length} entries, signer ${d.signerPubkey.slice(0, 16)}\u2026)
62
+ `);
63
+ return 0;
64
+ }
65
+ process.stderr.write(`blindseal audit: unknown subcommand ${sub}
66
+ ${HELP}`);
67
+ return 2;
68
+ }
69
+ export {
70
+ runAuditCli
71
+ };
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ __require
4
+ } from "./chunk-PDX44BCA.js";
5
+
6
+ // src/cli/audit-view.ts
7
+ import { readFileSync, existsSync } from "fs";
8
+ var HELP = `blindseal-audit \u2014 pretty-print the Blindseal audit JSONL
9
+
10
+ Usage:
11
+ blindseal-audit [path] default: $BLINDSEAL_AUDIT_LOG or /tmp/blindseal-audit.jsonl
12
+ blindseal-audit - read from stdin
13
+ blindseal-audit --type capability filter by event type (detection|capability|manifest)
14
+ blindseal-audit --verdict malicious filter by verdict (clean|suspicious|malicious)
15
+ blindseal-audit --tool read_file filter by tool name
16
+ blindseal-audit --since 1h only events newer than 1h (s/m/h/d)
17
+ blindseal-audit --raw one line per event, no color (for grep/jq pipelines)
18
+ `;
19
+ var HAS_TTY = process.stdout.isTTY && !process.env.NO_COLOR;
20
+ function color(s, code) {
21
+ if (!HAS_TTY) return s;
22
+ return `\x1B[${code}m${s}\x1B[0m`;
23
+ }
24
+ var dim = (s) => color(s, "2;37");
25
+ var red = (s) => color(s, "31");
26
+ var yellow = (s) => color(s, "33");
27
+ var green = (s) => color(s, "32");
28
+ var cyan = (s) => color(s, "36");
29
+ var bold = (s) => color(s, "1");
30
+ function verdictPaint(v) {
31
+ if (v === "malicious") return red(bold("malicious"));
32
+ if (v === "suspicious") return yellow("suspicious");
33
+ if (v === "clean") return green("clean");
34
+ return dim(v ?? "?");
35
+ }
36
+ function actionPaint(a) {
37
+ if (a === "block") return red(bold("block"));
38
+ if (a === "warn") return yellow("warn");
39
+ if (a === "allow") return green("allow");
40
+ return dim(a ?? "?");
41
+ }
42
+ function statusPaint(s) {
43
+ if (s === "drift") return red(bold("drift"));
44
+ if (s === "first-seen") return cyan("first-seen");
45
+ if (s === "unchanged") return dim("unchanged");
46
+ return dim(s ?? "?");
47
+ }
48
+ function parseSince(raw) {
49
+ const m = /^(\d+)([smhd])$/.exec(raw);
50
+ if (!m) return void 0;
51
+ const n = Number(m[1]);
52
+ const unit = m[2];
53
+ const mult = unit === "s" ? 1e3 : unit === "m" ? 6e4 : unit === "h" ? 36e5 : 864e5;
54
+ return Date.now() - n * mult;
55
+ }
56
+ function parseArgs(argv) {
57
+ const filter = {};
58
+ let path;
59
+ let help = false;
60
+ for (let i = 0; i < argv.length; i++) {
61
+ const a = argv[i];
62
+ if (a === "--help" || a === "-h") help = true;
63
+ else if (a === "--type") filter.type = argv[++i];
64
+ else if (a === "--verdict") filter.verdict = argv[++i];
65
+ else if (a === "--tool") filter.tool = argv[++i];
66
+ else if (a === "--since") {
67
+ const next = argv[++i] ?? "";
68
+ filter.sinceMs = parseSince(next);
69
+ } else if (a === "--raw") filter.raw = true;
70
+ else if (a && !a.startsWith("--")) path = a;
71
+ }
72
+ return { filter, path, help };
73
+ }
74
+ function loadSource(path) {
75
+ if (path === "-") return readStdin();
76
+ const resolved = path ?? process.env.BLINDSEAL_AUDIT_LOG ?? "/tmp/blindseal-audit.jsonl";
77
+ if (!existsSync(resolved)) {
78
+ throw new Error(`audit log not found: ${resolved}`);
79
+ }
80
+ return readFileSync(resolved, "utf8");
81
+ }
82
+ function readStdin() {
83
+ const chunks = [];
84
+ const fd = 0;
85
+ const buf = Buffer.alloc(64 * 1024);
86
+ try {
87
+ for (; ; ) {
88
+ const { readFileSync: _rfs } = __require("fs");
89
+ try {
90
+ return _rfs("/dev/stdin", "utf8");
91
+ } catch {
92
+ break;
93
+ }
94
+ }
95
+ } catch {
96
+ }
97
+ const fs = __require("fs");
98
+ let total = 0;
99
+ try {
100
+ for (; ; ) {
101
+ const n = fs.readSync(fd, buf, 0, buf.length, null);
102
+ if (n === 0) break;
103
+ chunks.push(buf.slice(0, n));
104
+ total += n;
105
+ if (total > 100 * 1024 * 1024) break;
106
+ }
107
+ } catch {
108
+ }
109
+ return Buffer.concat(chunks).toString("utf8");
110
+ }
111
+ function passesFilter(row, f) {
112
+ if (f.type && row.type !== f.type) return false;
113
+ if (f.verdict && row.verdict !== f.verdict) return false;
114
+ if (f.tool && row.toolName !== f.tool) return false;
115
+ if (f.sinceMs && row.ts < f.sinceMs) return false;
116
+ return true;
117
+ }
118
+ function fmtTs(ts) {
119
+ const d = new Date(ts);
120
+ return d.toISOString().replace("T", " ").replace(/\..+/, "");
121
+ }
122
+ function renderRow(row, raw) {
123
+ if (raw) return JSON.stringify(row);
124
+ const t = dim(fmtTs(row.ts));
125
+ if (row.type === "detection") {
126
+ const v = verdictPaint(row.verdict);
127
+ const L = row.layer != null ? `L${row.layer}` : "L?";
128
+ const m = row.mutated ? red("mut") : dim("-");
129
+ const tool = cyan(row.toolName ?? "?");
130
+ const preview = (row.contentPreview ?? "").slice(0, 80).replace(/\n/g, " ");
131
+ const patterns = row.patterns && row.patterns.length > 0 ? dim(` [${row.patterns.slice(0, 3).join(",")}]`) : "";
132
+ return `${t} ${dim("det")} ${L} ${v} ${tool} mode=${row.mode ?? "?"} ${m}${patterns}
133
+ ${dim("\u2514")} ${preview}`;
134
+ }
135
+ if (row.type === "capability") {
136
+ const a = actionPaint(row.action);
137
+ const tool = cyan(row.toolName ?? "?");
138
+ const sources = row.taintSources && row.taintSources.length > 0 ? dim(` \u2190 ${row.taintSources.slice(0, 3).join(",")}`) : "";
139
+ const args = (row.argsPreview ?? "").slice(0, 80).replace(/\n/g, " ");
140
+ return `${t} ${dim("cap")} -- ${a} ${tool}${sources}
141
+ ${dim("\u2514")} ${args}`;
142
+ }
143
+ if (row.type === "manifest") {
144
+ const s = statusPaint(row.status);
145
+ const fp = dim((row.fingerprint ?? "").slice(0, 12));
146
+ const changes = row.changes && row.changes.length > 0 ? "\n " + row.changes.map((c) => dim("\u2514 ") + yellow(c)).join("\n ") : "";
147
+ return `${t} ${dim("mfs")} -- ${s} ${fp}${changes}`;
148
+ }
149
+ return JSON.stringify(row);
150
+ }
151
+ function summarize(rows) {
152
+ const byType = { detection: 0, capability: 0, manifest: 0 };
153
+ const byVerdict = { clean: 0, suspicious: 0, malicious: 0 };
154
+ const byAction = { allow: 0, warn: 0, block: 0 };
155
+ const byManifest = { "first-seen": 0, unchanged: 0, drift: 0 };
156
+ const tools = /* @__PURE__ */ new Map();
157
+ for (const r of rows) {
158
+ byType[r.type] = (byType[r.type] ?? 0) + 1;
159
+ if (r.verdict) byVerdict[r.verdict] = (byVerdict[r.verdict] ?? 0) + 1;
160
+ if (r.action) byAction[r.action] = (byAction[r.action] ?? 0) + 1;
161
+ if (r.status) byManifest[r.status] = (byManifest[r.status] ?? 0) + 1;
162
+ if (r.toolName) tools.set(r.toolName, (tools.get(r.toolName) ?? 0) + 1);
163
+ }
164
+ const topTools = [...tools.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
165
+ const lines = [];
166
+ lines.push("");
167
+ lines.push(dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
168
+ lines.push(bold("summary"));
169
+ lines.push(` events: ${rows.length}`);
170
+ lines.push(
171
+ ` by type: detection=${byType.detection} capability=${byType.capability} manifest=${byType.manifest}`
172
+ );
173
+ lines.push(
174
+ ` verdicts: ${green(`clean=${byVerdict.clean}`)} ${yellow(`suspicious=${byVerdict.suspicious}`)} ${red(`malicious=${byVerdict.malicious}`)}`
175
+ );
176
+ lines.push(
177
+ ` actions: ${green(`allow=${byAction.allow}`)} ${yellow(`warn=${byAction.warn}`)} ${red(`block=${byAction.block}`)}`
178
+ );
179
+ lines.push(
180
+ ` manifest: ${cyan(`first-seen=${byManifest["first-seen"]}`)} ${dim(`unchanged=${byManifest.unchanged}`)} ${red(`drift=${byManifest.drift}`)}`
181
+ );
182
+ if (topTools.length > 0) {
183
+ lines.push(` top tools: ${topTools.map(([t, n]) => `${cyan(t)}:${n}`).join(" ")}`);
184
+ }
185
+ return lines.join("\n");
186
+ }
187
+ function parseJsonl(raw) {
188
+ const rows = [];
189
+ for (const line of raw.split("\n")) {
190
+ const trimmed = line.trim();
191
+ if (!trimmed) continue;
192
+ try {
193
+ rows.push(JSON.parse(trimmed));
194
+ } catch {
195
+ }
196
+ }
197
+ return rows;
198
+ }
199
+ function render(rows, filter) {
200
+ const matched = rows.filter((r) => passesFilter(r, filter));
201
+ const lines = matched.map((r) => renderRow(r, filter.raw ?? false));
202
+ if (filter.raw) return lines.join("\n");
203
+ return lines.join("\n") + "\n" + summarize(matched);
204
+ }
205
+ function main() {
206
+ const argv = process.argv.slice(2);
207
+ const { filter, path, help } = parseArgs(argv);
208
+ if (help) {
209
+ process.stdout.write(HELP);
210
+ process.exit(0);
211
+ }
212
+ let raw;
213
+ try {
214
+ raw = loadSource(path);
215
+ } catch (err) {
216
+ process.stderr.write(`blindseal-audit: ${err instanceof Error ? err.message : String(err)}
217
+ `);
218
+ process.exit(2);
219
+ }
220
+ const rows = parseJsonl(raw);
221
+ process.stdout.write(render(rows, filter) + "\n");
222
+ }
223
+ if (import.meta.url === `file://${process.argv[1]}`) {
224
+ main();
225
+ }
226
+ export {
227
+ parseJsonl,
228
+ render
229
+ };