burnledger 0.4.0 → 0.6.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.
Files changed (104) hide show
  1. package/README.md +11 -4
  2. package/dist/cjs/anchor.d.ts +71 -0
  3. package/dist/cjs/anchor.d.ts.map +1 -0
  4. package/dist/cjs/anchor.js +279 -0
  5. package/dist/cjs/anchor.js.map +1 -0
  6. package/dist/cjs/client.d.ts +26 -2
  7. package/dist/cjs/client.d.ts.map +1 -1
  8. package/dist/cjs/client.js +27 -5
  9. package/dist/cjs/client.js.map +1 -1
  10. package/dist/cjs/enclave-seal.d.ts +101 -0
  11. package/dist/cjs/enclave-seal.d.ts.map +1 -0
  12. package/dist/cjs/enclave-seal.js +479 -0
  13. package/dist/cjs/enclave-seal.js.map +1 -0
  14. package/dist/cjs/errors.d.ts +42 -1
  15. package/dist/cjs/errors.d.ts.map +1 -1
  16. package/dist/cjs/errors.js +44 -2
  17. package/dist/cjs/errors.js.map +1 -1
  18. package/dist/cjs/http.d.ts +21 -1
  19. package/dist/cjs/http.d.ts.map +1 -1
  20. package/dist/cjs/http.js +76 -12
  21. package/dist/cjs/http.js.map +1 -1
  22. package/dist/cjs/index.browser.d.ts +2 -4
  23. package/dist/cjs/index.browser.d.ts.map +1 -1
  24. package/dist/cjs/index.browser.js.map +1 -1
  25. package/dist/cjs/index.d.ts +16 -11
  26. package/dist/cjs/index.d.ts.map +1 -1
  27. package/dist/cjs/index.js +20 -1
  28. package/dist/cjs/index.js.map +1 -1
  29. package/dist/cjs/keys.d.ts +66 -0
  30. package/dist/cjs/keys.d.ts.map +1 -1
  31. package/dist/cjs/keys.js +128 -3
  32. package/dist/cjs/keys.js.map +1 -1
  33. package/dist/cjs/models.d.ts +41 -3
  34. package/dist/cjs/models.d.ts.map +1 -1
  35. package/dist/cjs/models.js +25 -4
  36. package/dist/cjs/models.js.map +1 -1
  37. package/dist/cjs/verify.d.ts +72 -6
  38. package/dist/cjs/verify.d.ts.map +1 -1
  39. package/dist/cjs/verify.js +392 -62
  40. package/dist/cjs/verify.js.map +1 -1
  41. package/dist/cjs/web-verifier.d.ts +54 -5
  42. package/dist/cjs/web-verifier.d.ts.map +1 -1
  43. package/dist/cjs/web-verifier.js +36 -3
  44. package/dist/cjs/web-verifier.js.map +1 -1
  45. package/dist/esm/anchor.d.ts +71 -0
  46. package/dist/esm/anchor.d.ts.map +1 -0
  47. package/dist/esm/anchor.js +275 -0
  48. package/dist/esm/anchor.js.map +1 -0
  49. package/dist/esm/cli.d.ts +45 -14
  50. package/dist/esm/cli.d.ts.map +1 -1
  51. package/dist/esm/cli.js +357 -50
  52. package/dist/esm/cli.js.map +1 -1
  53. package/dist/esm/client.d.ts +26 -2
  54. package/dist/esm/client.d.ts.map +1 -1
  55. package/dist/esm/client.js +27 -5
  56. package/dist/esm/client.js.map +1 -1
  57. package/dist/esm/enclave-seal.d.ts +101 -0
  58. package/dist/esm/enclave-seal.d.ts.map +1 -0
  59. package/dist/esm/enclave-seal.js +472 -0
  60. package/dist/esm/enclave-seal.js.map +1 -0
  61. package/dist/esm/errors.d.ts +42 -1
  62. package/dist/esm/errors.d.ts.map +1 -1
  63. package/dist/esm/errors.js +43 -1
  64. package/dist/esm/errors.js.map +1 -1
  65. package/dist/esm/http.d.ts +21 -1
  66. package/dist/esm/http.d.ts.map +1 -1
  67. package/dist/esm/http.js +75 -12
  68. package/dist/esm/http.js.map +1 -1
  69. package/dist/esm/index.browser.d.ts +2 -4
  70. package/dist/esm/index.browser.d.ts.map +1 -1
  71. package/dist/esm/index.browser.js.map +1 -1
  72. package/dist/esm/index.d.ts +16 -11
  73. package/dist/esm/index.d.ts.map +1 -1
  74. package/dist/esm/index.js +11 -2
  75. package/dist/esm/index.js.map +1 -1
  76. package/dist/esm/keys.d.ts +66 -0
  77. package/dist/esm/keys.d.ts.map +1 -1
  78. package/dist/esm/keys.js +125 -3
  79. package/dist/esm/keys.js.map +1 -1
  80. package/dist/esm/models.d.ts +41 -3
  81. package/dist/esm/models.d.ts.map +1 -1
  82. package/dist/esm/models.js +24 -4
  83. package/dist/esm/models.js.map +1 -1
  84. package/dist/esm/verify.d.ts +72 -6
  85. package/dist/esm/verify.d.ts.map +1 -1
  86. package/dist/esm/verify.js +390 -63
  87. package/dist/esm/verify.js.map +1 -1
  88. package/dist/esm/web-verifier.d.ts +54 -5
  89. package/dist/esm/web-verifier.d.ts.map +1 -1
  90. package/dist/esm/web-verifier.js +40 -3
  91. package/dist/esm/web-verifier.js.map +1 -1
  92. package/package.json +1 -1
  93. package/src/anchor.ts +330 -0
  94. package/src/cli.ts +389 -54
  95. package/src/client.ts +46 -4
  96. package/src/enclave-seal.ts +582 -0
  97. package/src/errors.ts +47 -2
  98. package/src/http.ts +79 -12
  99. package/src/index.browser.ts +2 -2
  100. package/src/index.ts +28 -9
  101. package/src/keys.ts +176 -3
  102. package/src/models.ts +72 -7
  103. package/src/verify.ts +467 -72
  104. package/src/web-verifier.ts +58 -5
@@ -0,0 +1,275 @@
1
+ /** The statement's log anchor, tested against the log's own signed head.
2
+ *
3
+ * A certificate status statement is an OFF-LOG document. Nothing stops the
4
+ * issuer signing two of them, at the same instant, saying different things
5
+ * about the same record to two different readers — that is the split-view
6
+ * attack on revocation status, and no signature by the issuer can rule out a
7
+ * second signature by the issuer.
8
+ *
9
+ * What the statement DOES carry is an anchor: sth_tree_size and sth_root_hash,
10
+ * signed, naming the log head the issuer had in front of it when it spoke.
11
+ * Until now this CLI PRINTED that anchor and never checked it, so the field was
12
+ * decoration. Checking it does not make the statement append-only, and this
13
+ * file must not be read as claiming it does. What it buys is narrower and real:
14
+ *
15
+ * - A statement can no longer name a tree the log does not have. The anchor
16
+ * must be a PREFIX of the head the log publishes now, proved by an RFC 6962
17
+ * consistency proof, or this CLI refuses the statement.
18
+ * - So an issuer wanting to show two readers different revocation status has
19
+ * two options, and both leave marks. Anchor both statements to the same
20
+ * head, and the two statements are directly comparable signed evidence of
21
+ * equivocation. Anchor them to different heads, and the log itself has
22
+ * forked — which is what the witness's published record exists to catch,
23
+ * and which no amount of re-signing can hide.
24
+ *
25
+ * What it does NOT close: a reader who checks one statement, alone, against a
26
+ * log the issuer also controls learns only that the issuer is self-consistent.
27
+ * Split view is detected by comparing notes — between readers, or against the
28
+ * witness — and this check is what makes the note worth comparing.
29
+ *
30
+ * This is a port of cmd/cli/anchor.go, verdict for verdict and word for word.
31
+ * Three CLIs that disagree about the same record are worse than one that never
32
+ * checked, so the detail strings are copied rather than paraphrased: a reader
33
+ * diffing the Go and the npm output must see the same line.
34
+ */
35
+ import { bytesToHex, buildTreeHeadPayload, formatTimestamp, hexToBytes, verifyConsistency, } from "./verify.js";
36
+ export const ANCHOR_CONFIRMED = "CONFIRMED";
37
+ export const ANCHOR_UNVERIFIED = "UNVERIFIED";
38
+ export const ANCHOR_INCONSISTENT = "INCONSISTENT";
39
+ /** How much of a log response this will buffer, matching cmd/cli/online.go.
40
+ * A head is a few hundred bytes and a consistency proof is at most 64 hashes;
41
+ * anything near this size is a misbehaving or hostile server, not an answer. */
42
+ const MAX_RESPONSE_BYTES = 1 << 20;
43
+ const REQUEST_TIMEOUT_MS = 10_000;
44
+ /** Test the statement's signed anchor against the log's own signed head.
45
+ *
46
+ * `key` is the key that signed the STATEMENT, taken from the reader's --keys
47
+ * file. The head is verified against it before a single field of the head is
48
+ * used: an unverified head is an unattributed one, and letting it decide
49
+ * whether the anchor is consistent would hand that decision to whoever answered
50
+ * the HTTP request.
51
+ *
52
+ * `statement` must already have passed unwrapStatusStatement, which is what
53
+ * guarantees `sth_root_hash` is 32 bytes of hex. That is the boundary check;
54
+ * this function does not repeat it.
55
+ */
56
+ export async function checkStatementAnchor(crypto, apiUrl, statement, key) {
57
+ const treeSize = statement.sth_tree_size;
58
+ if (typeof treeSize !== "number" || !Number.isSafeInteger(treeSize) || treeSize < 0) {
59
+ // Go's uint64 field would have failed to unmarshal, taking the whole
60
+ // document with it; here the document already parsed, so the anchor is what
61
+ // is unusable. Either way nothing was established, which is UNVERIFIED.
62
+ return unverified("the statement's anchored tree size is not a usable integer");
63
+ }
64
+ if (treeSize === 0)
65
+ return unverified("the statement names no log head to anchor to");
66
+ let head;
67
+ try {
68
+ head = await fetchLogHead(apiUrl);
69
+ }
70
+ catch (e) {
71
+ return unverified(`could not read the log head: ${message(e)}`);
72
+ }
73
+ let headPayload;
74
+ try {
75
+ headPayload = buildTreeHeadPayload(head);
76
+ }
77
+ catch (e) {
78
+ return unverified(`could not rebuild the log head payload: ${message(e)}`);
79
+ }
80
+ const headSignature = hexToBytes(head.signature);
81
+ if (!(await crypto.ed25519Verify(key.keyBytes, headPayload, headSignature))) {
82
+ return unverified("the log head is not signed by the key that signed the statement, so it says nothing about it");
83
+ }
84
+ const headSize = head.tree_size;
85
+ const headRoot = hexToBytes(head.root_hash);
86
+ const statementRoot = hexToBytes(statement.sth_root_hash);
87
+ // A head SMALLER than one the same issuer signed into a statement means the
88
+ // log went backwards. An append-only structure cannot do that, so this needs
89
+ // no proof to refuse.
90
+ if (headSize < treeSize) {
91
+ return {
92
+ verdict: ANCHOR_INCONSISTENT,
93
+ detail: `the statement is anchored to tree size ${treeSize} but the log now publishes ` +
94
+ `${headSize} — an append-only log does not shrink`,
95
+ };
96
+ }
97
+ if (headSize === treeSize) {
98
+ // Consistency is equality at the same size, and it still has to be checked
99
+ // rather than assumed: two different roots signed for one tree size is the
100
+ // fork, stated as plainly as it can be.
101
+ if (!bytesEqual(headRoot, statementRoot)) {
102
+ return {
103
+ verdict: ANCHOR_INCONSISTENT,
104
+ detail: `two different roots signed for tree size ${treeSize}: ` +
105
+ `${bytesToHex(statementRoot.slice(0, 8))} in the statement, ` +
106
+ `${bytesToHex(headRoot.slice(0, 8))} from the log`,
107
+ };
108
+ }
109
+ return {
110
+ verdict: ANCHOR_CONFIRMED,
111
+ detail: `statement anchored to the log's current head (tree size ${headSize})`,
112
+ };
113
+ }
114
+ let proof;
115
+ try {
116
+ proof = await fetchConsistencyProof(apiUrl, treeSize, headSize);
117
+ }
118
+ catch (e) {
119
+ return unverified(`could not read a consistency proof: ${message(e)}`);
120
+ }
121
+ if (!(await verifyConsistency(crypto, treeSize, headSize, statementRoot, headRoot, proof))) {
122
+ return {
123
+ verdict: ANCHOR_INCONSISTENT,
124
+ detail: `the log cannot prove tree size ${treeSize} is a prefix of its current size ` +
125
+ `${headSize} — the statement is anchored to a different history`,
126
+ };
127
+ }
128
+ return {
129
+ verdict: ANCHOR_CONFIRMED,
130
+ detail: `statement's head (tree size ${treeSize}) proved to be a prefix of the log's ` +
131
+ `current head (tree size ${headSize})`,
132
+ };
133
+ }
134
+ function unverified(detail) {
135
+ return { verdict: ANCHOR_UNVERIFIED, detail };
136
+ }
137
+ function message(e) {
138
+ return e instanceof Error ? e.message : String(e);
139
+ }
140
+ function bytesEqual(a, b) {
141
+ if (a.length !== b.length)
142
+ return false;
143
+ for (let i = 0; i < a.length; i++)
144
+ if (a[i] !== b[i])
145
+ return false;
146
+ return true;
147
+ }
148
+ /** Read GET /v1/log/head and validate every field the signature covers.
149
+ *
150
+ * `log_id` is one of them. The endpoint serves none today (handler/log.go
151
+ * signedTreeHeadResponse) and BurnLedger's signer refuses to issue a head that
152
+ * names a log, but buildTreeHeadPayload picks the signing domain from the
153
+ * field, so a head that carries one has to reach it: dropped, the head rebuilds
154
+ * under burnledger.sth.v3, fails its signature and is reported unattributable —
155
+ * while the record path verifies the very same bytes. cmd/cli/anchor.go reads
156
+ * exactly these five fields, and all three CLIs are held to one fixture,
157
+ * testdata/named_head_anchor.json, so a head from a log that stamps its id gets
158
+ * one verdict rather than three.
159
+ */
160
+ async function fetchLogHead(apiUrl) {
161
+ const envelope = await getJson(`${base(apiUrl)}/v1/log/head`);
162
+ const body = unwrapData(envelope);
163
+ const treeSize = body.tree_size;
164
+ if (typeof treeSize !== "number" || !Number.isSafeInteger(treeSize) || treeSize < 0) {
165
+ throw new Error("log head: tree_size is not an exact non-negative integer");
166
+ }
167
+ requireHex(body, "root_hash", 32);
168
+ requireHex(body, "signature", 64);
169
+ // Absent and null both mean "names no log", as they do for Go's string
170
+ // field; anything else that is not a string is a head this build cannot
171
+ // read, which Go's decoder also refuses.
172
+ if (body.log_id != null && typeof body.log_id !== "string") {
173
+ throw new Error("log head: log_id is not a string");
174
+ }
175
+ // Validated here rather than left to the payload rebuild, so that a head with
176
+ // an unreadable timestamp reports the same thing the Go CLI reports.
177
+ try {
178
+ formatTimestamp(body.timestamp);
179
+ }
180
+ catch {
181
+ throw new Error("log head: timestamp is not RFC 3339");
182
+ }
183
+ return body;
184
+ }
185
+ async function fetchConsistencyProof(apiUrl, oldSize, newSize) {
186
+ const url = `${base(apiUrl)}/v1/log/proof/consistency?old_size=${oldSize}&new_size=${newSize}`;
187
+ const body = unwrapData(await getJson(url));
188
+ const raw = body.proof;
189
+ // Go decodes `"proof": null` to an empty slice, and an empty proof is a real
190
+ // answer at some tree sizes. An absent or null field is that, not an error.
191
+ if (raw === null || raw === undefined)
192
+ return [];
193
+ if (!Array.isArray(raw))
194
+ throw new Error("consistency proof: proof is not an array");
195
+ return raw.map((h, i) => {
196
+ if (typeof h !== "string" || !/^[0-9a-fA-F]{64}$/.test(h)) {
197
+ throw new Error(`consistency proof: proof[${i}] is not 32 bytes of hex`);
198
+ }
199
+ return hexToBytes(h);
200
+ });
201
+ }
202
+ function base(apiUrl) {
203
+ return apiUrl.replace(/\/+$/, "");
204
+ }
205
+ /** The API wraps every response in {"data": ...}. A body with no data object is
206
+ * an answer this build cannot read — never a finding about the record. */
207
+ function unwrapData(parsed) {
208
+ if (parsed === null || typeof parsed !== "object") {
209
+ throw new Error("response is not a JSON object");
210
+ }
211
+ const doc = parsed;
212
+ const data = doc.data;
213
+ if (data === null || typeof data !== "object") {
214
+ throw new Error("response has no data object");
215
+ }
216
+ return data;
217
+ }
218
+ function requireHex(doc, field, bytes) {
219
+ const v = doc[field];
220
+ if (typeof v !== "string" || !new RegExp(`^[0-9a-fA-F]{${bytes * 2}}$`).test(v)) {
221
+ throw new Error(`log head: ${field} is not ${bytes} bytes of hex`);
222
+ }
223
+ }
224
+ /** Read one public log endpoint under the same size cap and timeout as the
225
+ * status fetch, and with the same absence of credentials: every endpoint this
226
+ * file touches is public by design, and sending an API key to whatever
227
+ * --api-url happens to point at is a credential leak the check never needed. */
228
+ async function getJson(url) {
229
+ const controller = new AbortController();
230
+ const timer = setTimeout(() => { controller.abort(); }, REQUEST_TIMEOUT_MS);
231
+ try {
232
+ const res = await fetch(url, { signal: controller.signal });
233
+ const body = await readCapped(res);
234
+ if (!res.ok)
235
+ throw new Error(`API returned ${res.status}`);
236
+ try {
237
+ return JSON.parse(body);
238
+ }
239
+ catch (e) {
240
+ throw new Error(`parse response: ${message(e)}`);
241
+ }
242
+ }
243
+ finally {
244
+ clearTimeout(timer);
245
+ }
246
+ }
247
+ /** Read a response body, stopping at the cap instead of buffering whatever a
248
+ * hostile server decides to send. The stream is cancelled on the way out, so an
249
+ * endless body costs one megabyte and not the process. */
250
+ async function readCapped(res) {
251
+ const reader = res.body?.getReader();
252
+ if (reader === undefined)
253
+ return "";
254
+ const chunks = [];
255
+ let total = 0;
256
+ for (;;) {
257
+ const { done, value } = await reader.read();
258
+ if (done)
259
+ break;
260
+ total += value.length;
261
+ if (total > MAX_RESPONSE_BYTES) {
262
+ await reader.cancel();
263
+ throw new Error(`read response: body exceeds ${MAX_RESPONSE_BYTES} bytes`);
264
+ }
265
+ chunks.push(value);
266
+ }
267
+ const joined = new Uint8Array(total);
268
+ let at = 0;
269
+ for (const c of chunks) {
270
+ joined.set(c, at);
271
+ at += c.length;
272
+ }
273
+ return new TextDecoder().decode(joined);
274
+ }
275
+ //# sourceMappingURL=anchor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchor.js","sourceRoot":"","sources":["../../src/anchor.ts"],"names":[],"mappings":"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"}
package/dist/esm/cli.d.ts CHANGED
@@ -1,23 +1,54 @@
1
1
  #!/usr/bin/env node
2
2
  /** burnledger check — offline certificate verification.
3
3
  * Exit codes: 0=valid, 1=invalid, 2=usage error */
4
+ import { type KeyListEvidence } from "./keys.js";
5
+ import { type AnchorResult } from "./anchor.js";
4
6
  type Cert = Record<string, unknown>;
7
+ /** A published status statement, exactly as GET /v1/certificates/{id}/status
8
+ * serves it: hex strings, not decoded bytes. verifyCertificateWithStatus reads
9
+ * this shape, so nothing here re-encodes a field the signature covers. */
10
+ type StatusStatement = Record<string, unknown>;
5
11
  /** Flag first, then environment — the precedence docs/cli.md promises and the
6
- * Go CLI (cmd/cli/online.go resolveAPIConfig) implements. Exported for testing. */
7
- export declare function resolveApiConfig(apiUrl: string, apiKey: string, env: Record<string, string | undefined>): {
8
- apiUrl: string;
9
- apiKey: string;
10
- };
12
+ * Go CLI (cmd/cli/online.go resolveAPIBaseURL) implements. Exported for testing.
13
+ *
14
+ * It no longer resolves a key, and that is the change. GET
15
+ * /v1/certificates/{id}/status is unauthenticated by design: the audience for
16
+ * revocation is a relying party holding a certificate — a regulator, an
17
+ * auditor, the customer's customer — who has no account here and never will.
18
+ * Demanding an API key was what stopped that reader asking the one question the
19
+ * artifact exists to answer. */
20
+ export declare function resolveApiBaseUrl(apiUrl: string, env: Record<string, string | undefined>): string;
21
+ /** The API wraps every response in {"data": ...}; a caller may hand over either
22
+ * the envelope or the document inside it. Mirrors
23
+ * core.ParseCertificateStatusDocument, so this CLI and the Go one cannot
24
+ * disagree about which shape the endpoint returns. Exported for testing.
25
+ *
26
+ * A document that does not parse THROWS, and the caller turns that into a
27
+ * warning and a VALID_REVOCATION_UNKNOWN verdict. That is deliberate: an answer
28
+ * this build cannot read is silence about revocation, not a finding about the
29
+ * certificate. Handing a half-read document to the verifier instead would
30
+ * report "status statement signature is invalid" — a claim about the issuer —
31
+ * when what actually happened is that something returned nonsense.
32
+ *
33
+ * `status` itself is NOT validated against a known set. A statement saying
34
+ * SUSPENDED is a statement this build cannot read, and the verifier already has
35
+ * the right answer for that: it authenticates the signature first, then reports
36
+ * VALID_REVOCATION_UNKNOWN. Refusing it here would discard the statement before
37
+ * anyone authenticated it. */
38
+ export declare function unwrapStatusStatement(parsed: unknown): StatusStatement;
11
39
  /** The pass/fail decision behind the exit code. A signature failure, a
12
- * transparency FAILURE (thrown, not NOT_AVAILABLE), or an online-confirmed
13
- * revocation all make the certificate invalid. Exported for testing. */
14
- export declare function certVerdict(sigResult: string, transparencyFailed: boolean, online: boolean, revocation: {
15
- status: string;
16
- } | undefined): boolean;
17
- export declare function formatOutput(cert: Cert, sigResult: string, transparency: string, transparencyFailed: boolean, revocation: {
18
- status: string;
19
- reason?: string;
20
- } | undefined, online: boolean): string;
40
+ * revocation, or a transparency FAILURE (thrown, not NOT_AVAILABLE) all make
41
+ * the certificate unusable. Revocation is no longer a separate input: it is
42
+ * part of the verdict verifyCertificateWithStatus returns. Exported for
43
+ * testing.
44
+ *
45
+ * An INCONSISTENT anchor refuses, mirroring exitCode in cmd/cli/check.go. It is
46
+ * not a doubt: it is a proof, from the issuer's own signatures, that the head a
47
+ * statement was anchored to is not a prefix of the head the log now publishes.
48
+ * An anchor that could NOT be checked does not refuse — the reader is told so,
49
+ * and the statement still stands on its own signature. */
50
+ export declare function certVerdict(sigResult: string, transparencyFailed: boolean, anchor?: AnchorResult): boolean;
51
+ export declare function formatOutput(cert: Cert, sigResult: string, transparency: string, transparencyFailed: boolean, status: StatusStatement | undefined, online: boolean, anchor?: AnchorResult, keyList?: KeyListEvidence): string;
21
52
  /** Accept the raw API response too: GET /v1/certificates/{id} nests the
22
53
  * signed certificate as data.certificate. Exported for testing. */
23
54
  export declare function unwrapCertificate(parsed: Cert): Cert;
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AACA;mDACmD;AAUnD,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA+BpC;mFACmF;AACnF,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAMpC;AAoDD;;wEAEwE;AACxE,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,OAAO,EAC3B,MAAM,EAAE,OAAO,EACf,UAAU,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GACzC,OAAO,CAMT;AAUD,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAChF,UAAU,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO,GAC3E,MAAM,CAmDR;AAED;mEACmE;AACnE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,CAUpD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AACA;mDACmD;AAcnD,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,WAAW,CAAC;AACnB,OAAO,EAA6C,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3F,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACpC;;0EAE0E;AAC1E,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAkC/C;;;;;;;;gCAQgC;AAChC,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtD,MAAM,CAIR;AA2ED;;;;;;;;;;;;;;;;8BAgB8B;AAC9B,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,CAoBtE;AAqCD;;;;;;;;;;0DAU0D;AAC1D,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,YAAY,GACpE,OAAO,CAGT;AAuDD,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAChF,MAAM,EAAE,eAAe,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,YAAY,EAC3E,OAAO,CAAC,EAAE,eAAe,GACxB,MAAM,CAiGR;AAgFD;mEACmE;AACnE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,CAUpD"}