@jterrazz/attestation 0.1.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/README.md +27 -0
- package/bin/attestation +2 -0
- package/dist/audit.cjs +57 -0
- package/dist/audit.cjs.map +1 -0
- package/dist/audit.js +52 -0
- package/dist/audit.js.map +1 -0
- package/dist/browser.cjs +183 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +111 -0
- package/dist/browser.d.ts +111 -0
- package/dist/browser.js +168 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.cjs +284 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +285 -0
- package/dist/cli.js.map +1 -0
- package/dist/eip712-schema.cjs +96 -0
- package/dist/eip712-schema.cjs.map +1 -0
- package/dist/eip712-schema.d.cts +75 -0
- package/dist/eip712-schema.d.ts +75 -0
- package/dist/eip712-schema.js +73 -0
- package/dist/eip712-schema.js.map +1 -0
- package/dist/index.cjs +21 -0
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +4 -0
- package/dist/node.cjs +279 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +87 -0
- package/dist/node.d.ts +87 -0
- package/dist/node.js +274 -0
- package/dist/node.js.map +1 -0
- package/dist/sign-flow.cjs +323 -0
- package/dist/sign-flow.cjs.map +1 -0
- package/dist/sign-flow.js +277 -0
- package/dist/sign-flow.js.map +1 -0
- package/dist/verify.cjs +276 -0
- package/dist/verify.cjs.map +1 -0
- package/dist/verify.d.cts +105 -0
- package/dist/verify.d.ts +105 -0
- package/dist/verify.js +199 -0
- package/dist/verify.js.map +1 -0
- package/package.json +67 -0
package/dist/browser.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { a as toStored, d as canonicalize, i as stringify, l as sha256Hex, n as fromStored, o as buildAttestationMessage, r as parse, t as verifyAttestation, u as InvalidContentError } from "./verify.js";
|
|
2
|
+
import { i as NO_PRIOR_ATTESTATION, n as ATTESTATION_PRIMARY_TYPE, r as ATTESTATION_TYPES_V1, t as ATTESTATION_DOMAIN_V1 } from "./eip712-schema.js";
|
|
3
|
+
import { createPublicClient, http } from "viem";
|
|
4
|
+
import { mainnet } from "viem/chains";
|
|
5
|
+
//#region src/browser/ens.ts
|
|
6
|
+
const client = createPublicClient({
|
|
7
|
+
chain: mainnet,
|
|
8
|
+
transport: http("https://ethereum-rpc.publicnode.com")
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Reverse-resolve an Ethereum address to its primary ENS name.
|
|
12
|
+
*
|
|
13
|
+
* Pure display concern — never persisted in the attestation file. The signer
|
|
14
|
+
* lib operates on addresses; this is rendered dynamically by web consumers who
|
|
15
|
+
* want a friendlier label.
|
|
16
|
+
*
|
|
17
|
+
* Returns null on any failure (network, no name set, malformed reply).
|
|
18
|
+
*/
|
|
19
|
+
async function resolveEnsName(address) {
|
|
20
|
+
try {
|
|
21
|
+
return await client.getEnsName({ address });
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/browser/verify-from-url.ts
|
|
28
|
+
/**
|
|
29
|
+
* End-to-end verify a published article from its public URL.
|
|
30
|
+
*
|
|
31
|
+
* Pipeline:
|
|
32
|
+
* 1. Resolve the article URL → manifest URL (`{url}/proof.json`)
|
|
33
|
+
* 2. Fetch manifest, then content (md), attestation (json), ots (bytes)
|
|
34
|
+
* 3. Recompute SHA-256 of canonical content, recover EIP-712 signer
|
|
35
|
+
* 4. Optionally verify the OTS proof (skipped in browser by default)
|
|
36
|
+
*
|
|
37
|
+
* The same call works in Node, Edge runtime, and modern browsers because the
|
|
38
|
+
* underlying primitives (canonicalize, sha256, recoverTypedDataAddress) are all
|
|
39
|
+
* platform-agnostic.
|
|
40
|
+
*/
|
|
41
|
+
async function verifyFromUrl(articleUrl, opts = {}) {
|
|
42
|
+
const fetchFn = opts.fetchFn ?? fetch;
|
|
43
|
+
const manifestUrl = resolveManifestUrl(articleUrl);
|
|
44
|
+
let manifest;
|
|
45
|
+
try {
|
|
46
|
+
manifest = await fetchJson(fetchFn, manifestUrl);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const details = error.message;
|
|
49
|
+
return {
|
|
50
|
+
authorship: {
|
|
51
|
+
details,
|
|
52
|
+
error: "fetch",
|
|
53
|
+
kind: "failed"
|
|
54
|
+
},
|
|
55
|
+
date: {
|
|
56
|
+
details,
|
|
57
|
+
error: "fetch",
|
|
58
|
+
kind: "failed"
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const base = new URL(manifestUrl);
|
|
63
|
+
let content;
|
|
64
|
+
let attestationJson;
|
|
65
|
+
try {
|
|
66
|
+
[content, attestationJson] = await Promise.all([fetchText(fetchFn, new URL(manifest.content, base).toString()), fetchText(fetchFn, new URL(manifest.attestation, base).toString())]);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const details = error.message;
|
|
69
|
+
return {
|
|
70
|
+
authorship: {
|
|
71
|
+
details,
|
|
72
|
+
error: "fetch",
|
|
73
|
+
kind: "failed"
|
|
74
|
+
},
|
|
75
|
+
date: {
|
|
76
|
+
details,
|
|
77
|
+
error: "fetch",
|
|
78
|
+
kind: "failed"
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const attestation = parse(attestationJson);
|
|
83
|
+
const sig = await verifyAttestation({
|
|
84
|
+
attestation,
|
|
85
|
+
content
|
|
86
|
+
});
|
|
87
|
+
if (!sig.ok) return {
|
|
88
|
+
authorship: {
|
|
89
|
+
error: sig.error.kind,
|
|
90
|
+
kind: "failed"
|
|
91
|
+
},
|
|
92
|
+
date: {
|
|
93
|
+
kind: "skipped",
|
|
94
|
+
reason: "opt-out"
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const authorship = {
|
|
98
|
+
kind: "verified",
|
|
99
|
+
signedAt: /* @__PURE__ */ new Date(Number(attestation.claims.publishedAt) * 1e3),
|
|
100
|
+
signerAddress: sig.signerAddress
|
|
101
|
+
};
|
|
102
|
+
if (opts.skipOts) return {
|
|
103
|
+
authorship,
|
|
104
|
+
date: {
|
|
105
|
+
kind: "skipped",
|
|
106
|
+
reason: "opt-out"
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
if (!manifest.otsVerifier) return {
|
|
110
|
+
authorship,
|
|
111
|
+
date: {
|
|
112
|
+
kind: "skipped",
|
|
113
|
+
reason: "no-ots-file"
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
try {
|
|
117
|
+
const result = await fetchJson(fetchFn, new URL(manifest.otsVerifier, base).toString());
|
|
118
|
+
if (result.ok) return {
|
|
119
|
+
authorship,
|
|
120
|
+
date: {
|
|
121
|
+
bitcoinTime: new Date(result.bitcoinTime),
|
|
122
|
+
kind: "verified"
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
if (result.reason === "pending-bitcoin") return {
|
|
126
|
+
authorship,
|
|
127
|
+
date: { kind: "pending" }
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
authorship,
|
|
131
|
+
date: {
|
|
132
|
+
details: result.details,
|
|
133
|
+
error: result.reason,
|
|
134
|
+
kind: "failed"
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
authorship,
|
|
140
|
+
date: {
|
|
141
|
+
details: error.message,
|
|
142
|
+
error: "fetch",
|
|
143
|
+
kind: "failed"
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function resolveManifestUrl(articleUrl) {
|
|
149
|
+
const base = globalThis.location?.origin;
|
|
150
|
+
const url = new URL(articleUrl, base);
|
|
151
|
+
if (!url.pathname.endsWith("/")) url.pathname += "/";
|
|
152
|
+
url.pathname += "proof.json";
|
|
153
|
+
return url.toString();
|
|
154
|
+
}
|
|
155
|
+
async function fetchJson(fetchFn, url) {
|
|
156
|
+
const res = await fetchFn(url);
|
|
157
|
+
if (!res.ok) throw new Error(`GET ${url}: HTTP ${res.status}`);
|
|
158
|
+
return await res.json();
|
|
159
|
+
}
|
|
160
|
+
async function fetchText(fetchFn, url) {
|
|
161
|
+
const res = await fetchFn(url);
|
|
162
|
+
if (!res.ok) throw new Error(`GET ${url}: HTTP ${res.status}`);
|
|
163
|
+
return res.text();
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
export { ATTESTATION_DOMAIN_V1, ATTESTATION_PRIMARY_TYPE, ATTESTATION_TYPES_V1, InvalidContentError, NO_PRIOR_ATTESTATION, buildAttestationMessage, canonicalize, fromStored, parse, resolveEnsName, sha256Hex, stringify, toStored, verifyAttestation, verifyFromUrl };
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","names":["g"],"sources":["../src/browser/ens.ts","../src/browser/verify-from-url.ts"],"sourcesContent":["import { createPublicClient, http } from 'viem';\nimport { mainnet } from 'viem/chains';\n\n// Default viem transport (cloudflare-eth.com) intermittently fails ENS reverse\n// Lookups (returns null even when a primary name is set). publicnode.com is a\n// CORS-friendly community RPC that resolves correctly.\nconst client = createPublicClient({\n chain: mainnet,\n transport: http('https://ethereum-rpc.publicnode.com'),\n});\n\n/**\n * Reverse-resolve an Ethereum address to its primary ENS name.\n *\n * Pure display concern — never persisted in the attestation file. The signer\n * lib operates on addresses; this is rendered dynamically by web consumers who\n * want a friendlier label.\n *\n * Returns null on any failure (network, no name set, malformed reply).\n */\nexport async function resolveEnsName(address: `0x${string}`): Promise<null | string> {\n try {\n return await client.getEnsName({ address });\n } catch {\n return null;\n }\n}\n","import { parse } from '../attestation/serialize.js';\nimport { verifyAttestation } from '../attestation/verify.js';\nimport { type AuthorshipState, type DateState, type ProofManifest } from './types.js';\n\nexport type VerifyFromUrlOptions = {\n /**\n * Skip Bitcoin/OTS verification entirely. Default false — verifyFromUrl\n * lazy-loads `javascript-opentimestamps` (~200kB) and validates the proof\n * against a public Bitcoin block API. Fails open: bundling/polyfill issues\n * fall back to `skipped` rather than throwing.\n */\n skipOts?: boolean;\n /**\n * Custom fetch (useful for tests or proxies). Defaults to global fetch.\n */\n fetchFn?: typeof fetch;\n};\n\nexport type VerifyFromUrlReport = {\n authorship: AuthorshipState;\n date: DateState;\n};\n\n/**\n * End-to-end verify a published article from its public URL.\n *\n * Pipeline:\n * 1. Resolve the article URL → manifest URL (`{url}/proof.json`)\n * 2. Fetch manifest, then content (md), attestation (json), ots (bytes)\n * 3. Recompute SHA-256 of canonical content, recover EIP-712 signer\n * 4. Optionally verify the OTS proof (skipped in browser by default)\n *\n * The same call works in Node, Edge runtime, and modern browsers because the\n * underlying primitives (canonicalize, sha256, recoverTypedDataAddress) are all\n * platform-agnostic.\n */\nexport async function verifyFromUrl(\n articleUrl: string,\n opts: VerifyFromUrlOptions = {},\n): Promise<VerifyFromUrlReport> {\n const fetchFn = opts.fetchFn ?? fetch;\n\n const manifestUrl = resolveManifestUrl(articleUrl);\n let manifest: ProofManifest;\n try {\n manifest = await fetchJson<ProofManifest>(fetchFn, manifestUrl);\n } catch (error) {\n const details = (error as Error).message;\n return {\n authorship: { details, error: 'fetch', kind: 'failed' },\n date: { details, error: 'fetch', kind: 'failed' },\n };\n }\n\n const base = new URL(manifestUrl);\n let content: string;\n let attestationJson: string;\n try {\n [content, attestationJson] = await Promise.all([\n fetchText(fetchFn, new URL(manifest.content, base).toString()),\n fetchText(fetchFn, new URL(manifest.attestation, base).toString()),\n ]);\n } catch (error) {\n const details = (error as Error).message;\n return {\n authorship: { details, error: 'fetch', kind: 'failed' },\n date: { details, error: 'fetch', kind: 'failed' },\n };\n }\n\n const attestation = parse(attestationJson);\n const sig = await verifyAttestation({ attestation, content });\n\n if (!sig.ok) {\n return {\n authorship: { error: sig.error.kind, kind: 'failed' },\n date: { kind: 'skipped', reason: 'opt-out' },\n };\n }\n\n const authorship: AuthorshipState = {\n kind: 'verified',\n signedAt: new Date(Number(attestation.claims.publishedAt) * 1000),\n signerAddress: sig.signerAddress,\n };\n\n if (opts.skipOts) {\n return { authorship, date: { kind: 'skipped', reason: 'opt-out' } };\n }\n\n // OTS verification needs Node-only deps (fs, crypto) so it runs on the\n // Server. The manifest may point to a verifier endpoint that returns the\n // Result as JSON. If absent, browser falls back to \"skipped\".\n if (!manifest.otsVerifier) {\n return { authorship, date: { kind: 'skipped', reason: 'no-ots-file' } };\n }\n\n try {\n const verifierUrl = new URL(manifest.otsVerifier, base).toString();\n const result = await fetchJson<OtsVerifierResponse>(fetchFn, verifierUrl);\n if (result.ok) {\n return {\n authorship,\n date: { bitcoinTime: new Date(result.bitcoinTime), kind: 'verified' },\n };\n }\n if (result.reason === 'pending-bitcoin') {\n return { authorship, date: { kind: 'pending' } };\n }\n return {\n authorship,\n date: { details: result.details, error: result.reason, kind: 'failed' },\n };\n } catch (error) {\n return {\n authorship,\n date: {\n details: (error as Error).message,\n error: 'fetch',\n kind: 'failed',\n },\n };\n }\n}\n\ntype OtsVerifierResponse =\n | {\n ok: false;\n reason: 'digest-mismatch' | 'invalid-proof' | 'pending-bitcoin';\n details?: string;\n }\n | { ok: true; bitcoinTime: string };\n\nfunction resolveManifestUrl(articleUrl: string): string {\n // Accept absolute URLs and relative paths (browser uses location.origin).\n const g = globalThis as { location?: { origin: string } };\n const base = g.location?.origin;\n const url = new URL(articleUrl, base);\n if (!url.pathname.endsWith('/')) {\n url.pathname += '/';\n }\n url.pathname += 'proof.json';\n return url.toString();\n}\n\nasync function fetchJson<T>(fetchFn: typeof fetch, url: string): Promise<T> {\n const res = await fetchFn(url);\n if (!res.ok) {\n throw new Error(`GET ${url}: HTTP ${res.status}`);\n }\n return (await res.json()) as T;\n}\n\nasync function fetchText(fetchFn: typeof fetch, url: string): Promise<string> {\n const res = await fetchFn(url);\n if (!res.ok) {\n throw new Error(`GET ${url}: HTTP ${res.status}`);\n }\n return res.text();\n}\n"],"mappings":"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"}
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
const require_sign_flow = require("./sign-flow.cjs");
|
|
2
|
+
const require_verify = require("./verify.cjs");
|
|
3
|
+
const require_audit = require("./audit.cjs");
|
|
4
|
+
let node_util = require("node:util");
|
|
5
|
+
let node_fs_promises = require("node:fs/promises");
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
//#region src/cli/io.ts
|
|
8
|
+
const supportsColor = process.stdout.isTTY === true && !process.env.NO_COLOR;
|
|
9
|
+
const fmt = {
|
|
10
|
+
bold: (s) => supportsColor ? `\x1b[1m${s}\x1b[0m` : s,
|
|
11
|
+
dim: (s) => supportsColor ? `\x1b[2m${s}\x1b[0m` : s,
|
|
12
|
+
fail: (s) => supportsColor ? `\x1b[31m${s}\x1b[0m` : s,
|
|
13
|
+
info: (s) => supportsColor ? `\x1b[36m${s}\x1b[0m` : s,
|
|
14
|
+
ok: (s) => supportsColor ? `\x1b[32m${s}\x1b[0m` : s,
|
|
15
|
+
warn: (s) => supportsColor ? `\x1b[33m${s}\x1b[0m` : s
|
|
16
|
+
};
|
|
17
|
+
function checkmark() {
|
|
18
|
+
return fmt.ok("✓");
|
|
19
|
+
}
|
|
20
|
+
function crossmark() {
|
|
21
|
+
return fmt.fail("✗");
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/cli/sign.ts
|
|
25
|
+
async function runSign(args) {
|
|
26
|
+
const content = await (0, node_fs_promises.readFile)(args.file, "utf8");
|
|
27
|
+
const locale = args.locale ?? deriveLocale(args.file);
|
|
28
|
+
const publishedAt = args.publishedAt ? new Date(args.publishedAt) : /* @__PURE__ */ new Date();
|
|
29
|
+
const canonical = require_verify.canonicalize(content);
|
|
30
|
+
process.stdout.write(`${checkmark()} Canonicalized ${canonical.length} bytes (${locale})\n`);
|
|
31
|
+
if (!args.skipAudit) {
|
|
32
|
+
const findings = require_audit.audit(canonical);
|
|
33
|
+
if (findings.length > 0) {
|
|
34
|
+
process.stdout.write(`${fmt.warn("!")} ${findings.length} suspicious char(s) found:\n`);
|
|
35
|
+
for (const f of findings) process.stdout.write(` line ${f.line}, col ${f.column}: U+${f.codepoint.toString(16).toUpperCase().padStart(4, "0")} (${f.name})\n`);
|
|
36
|
+
process.stdout.write(` Pass --skip-audit to sign anyway.\n`);
|
|
37
|
+
throw new Error("Audit failed: clean up suspicious characters first.");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const message = require_verify.buildAttestationMessage({
|
|
41
|
+
content,
|
|
42
|
+
locale,
|
|
43
|
+
priorAttestation: args.priorAttestation,
|
|
44
|
+
publishedAt,
|
|
45
|
+
revision: args.revision,
|
|
46
|
+
slug: args.slug,
|
|
47
|
+
title: args.title
|
|
48
|
+
});
|
|
49
|
+
process.stdout.write(`${fmt.info("→")} Content digest: ${message.subject.contentDigest}\n`);
|
|
50
|
+
process.stdout.write(`${fmt.info("→")} Opening browser to sign with your wallet…\n`);
|
|
51
|
+
const { signature, signerAddress } = await require_sign_flow.signViaBrowser({ message });
|
|
52
|
+
const signed = {
|
|
53
|
+
...message,
|
|
54
|
+
signature,
|
|
55
|
+
signerAddress
|
|
56
|
+
};
|
|
57
|
+
process.stdout.write(`${checkmark()} Signed by ${fmt.bold(signerAddress)}\n`);
|
|
58
|
+
const baseName = (0, node_path.basename)(args.file, (0, node_path.extname)(args.file));
|
|
59
|
+
const dir = (0, node_path.dirname)(args.file);
|
|
60
|
+
const attestationPath = (0, node_path.join)(dir, `${baseName}.attestation.json`);
|
|
61
|
+
await (0, node_fs_promises.writeFile)(attestationPath, require_verify.stringify(signed), "utf8");
|
|
62
|
+
process.stdout.write(`${checkmark()} Wrote ${attestationPath}\n`);
|
|
63
|
+
if (!args.skipStamp) {
|
|
64
|
+
process.stdout.write(`${fmt.info("→")} Submitting to OpenTimestamps calendars…\n`);
|
|
65
|
+
const otsBytes = await require_sign_flow.stampDigest(hexToBytes$1(message.subject.contentDigest));
|
|
66
|
+
const otsPath = (0, node_path.join)(dir, `${baseName}.ots`);
|
|
67
|
+
await (0, node_fs_promises.writeFile)(otsPath, Buffer.from(otsBytes));
|
|
68
|
+
process.stdout.write(`${checkmark()} Wrote ${otsPath} (${otsBytes.length} bytes, calendar-only)\n`);
|
|
69
|
+
process.stdout.write(` ${fmt.dim("Run `attestation upgrade` in ~24h to anchor in Bitcoin.")}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function deriveLocale(file) {
|
|
73
|
+
const name = (0, node_path.basename)(file, (0, node_path.extname)(file));
|
|
74
|
+
if (/^[a-z]{2}$/.test(name)) return name;
|
|
75
|
+
throw new Error(`Cannot derive locale from filename "${(0, node_path.basename)(file)}". Pass --locale explicitly.`);
|
|
76
|
+
}
|
|
77
|
+
function hexToBytes$1(hex) {
|
|
78
|
+
const stripped = hex.slice(2);
|
|
79
|
+
const out = new Uint8Array(stripped.length / 2);
|
|
80
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(stripped.slice(i * 2, i * 2 + 2), 16);
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/cli/upgrade.ts
|
|
85
|
+
async function runUpgrade(args) {
|
|
86
|
+
if (args.files.length === 0) throw new Error("No .ots files provided.");
|
|
87
|
+
for (const path of args.files) {
|
|
88
|
+
process.stdout.write(`${fmt.info("→")} ${path}\n`);
|
|
89
|
+
const before = await (0, node_fs_promises.readFile)(path);
|
|
90
|
+
const { bytes, upgraded } = await require_sign_flow.upgradeProof(new Uint8Array(before));
|
|
91
|
+
if (upgraded) {
|
|
92
|
+
await (0, node_fs_promises.writeFile)(path, Buffer.from(bytes));
|
|
93
|
+
process.stdout.write(` ${checkmark()} Upgraded to Bitcoin attestation (${bytes.length} bytes)\n`);
|
|
94
|
+
} else process.stdout.write(` ${fmt.dim("· No new attestation available yet, leaving file untouched.")}\n`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/cli/verify.ts
|
|
99
|
+
async function runVerify(args) {
|
|
100
|
+
const sources = await loadSources(args);
|
|
101
|
+
const attestation = require_verify.parse(sources.attestationJson);
|
|
102
|
+
const sigResult = await require_verify.verifyAttestation({
|
|
103
|
+
attestation,
|
|
104
|
+
content: sources.content
|
|
105
|
+
});
|
|
106
|
+
if (!sigResult.ok) {
|
|
107
|
+
printSignatureFailure(sigResult.error);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
process.stdout.write(`${checkmark()} Content digest matches signature\n`);
|
|
111
|
+
process.stdout.write(`${checkmark()} Signature valid — signed by ${fmt.bold(sigResult.signerAddress)}\n`);
|
|
112
|
+
if (args.skipOts || sources.otsBytes === null) {
|
|
113
|
+
process.stdout.write(`${fmt.dim("· OTS verification skipped")}\n`);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
const otsResult = await require_sign_flow.verifyOts(hexToBytes(attestation.subject.contentDigest), sources.otsBytes);
|
|
117
|
+
if (!otsResult.ok) {
|
|
118
|
+
process.stdout.write(`${crossmark()} OTS: ${otsResult.reason}`);
|
|
119
|
+
if (otsResult.details) process.stdout.write(` — ${otsResult.details}`);
|
|
120
|
+
process.stdout.write(`\n`);
|
|
121
|
+
return otsResult.reason === "pending-bitcoin";
|
|
122
|
+
}
|
|
123
|
+
process.stdout.write(`${checkmark()} Bitcoin timestamp confirmed at ${otsResult.bitcoinBlockTime.toISOString()}\n`);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
async function loadSources(args) {
|
|
127
|
+
if (/^https?:\/\//.test(args.target)) return loadFromUrl(args.target);
|
|
128
|
+
return loadFromFile(args.target);
|
|
129
|
+
}
|
|
130
|
+
async function loadFromFile(filePath) {
|
|
131
|
+
const content = await (0, node_fs_promises.readFile)(filePath, "utf8");
|
|
132
|
+
const dir = (0, node_path.dirname)(filePath);
|
|
133
|
+
const base = (0, node_path.basename)(filePath, (0, node_path.extname)(filePath));
|
|
134
|
+
const attestationJson = await (0, node_fs_promises.readFile)((0, node_path.join)(dir, `${base}.attestation.json`), "utf8");
|
|
135
|
+
let otsBytes = null;
|
|
136
|
+
try {
|
|
137
|
+
const buf = await (0, node_fs_promises.readFile)((0, node_path.join)(dir, `${base}.ots`));
|
|
138
|
+
otsBytes = new Uint8Array(buf);
|
|
139
|
+
} catch {
|
|
140
|
+
otsBytes = null;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
attestationJson,
|
|
144
|
+
content,
|
|
145
|
+
otsBytes
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async function loadFromUrl(url) {
|
|
149
|
+
const proofManifestUrl = await resolveManifestUrl(url);
|
|
150
|
+
const manifest = await fetchJson(proofManifestUrl);
|
|
151
|
+
const base = new URL(proofManifestUrl);
|
|
152
|
+
const [content, attestationJson, otsBuf] = await Promise.all([
|
|
153
|
+
fetchText(new URL(manifest.content, base).toString()),
|
|
154
|
+
fetchText(new URL(manifest.attestation, base).toString()),
|
|
155
|
+
fetchBytes(new URL(manifest.ots, base).toString()).catch(() => null)
|
|
156
|
+
]);
|
|
157
|
+
return {
|
|
158
|
+
attestationJson,
|
|
159
|
+
content,
|
|
160
|
+
otsBytes: otsBuf
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function resolveManifestUrl(articleUrl) {
|
|
164
|
+
const url = new URL(articleUrl);
|
|
165
|
+
if (!url.pathname.endsWith("/")) url.pathname += "/";
|
|
166
|
+
url.pathname += "proof.json";
|
|
167
|
+
return url.toString();
|
|
168
|
+
}
|
|
169
|
+
async function fetchJson(url) {
|
|
170
|
+
const res = await fetch(url);
|
|
171
|
+
if (!res.ok) throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);
|
|
172
|
+
return await res.json();
|
|
173
|
+
}
|
|
174
|
+
async function fetchText(url) {
|
|
175
|
+
const res = await fetch(url);
|
|
176
|
+
if (!res.ok) throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);
|
|
177
|
+
return res.text();
|
|
178
|
+
}
|
|
179
|
+
async function fetchBytes(url) {
|
|
180
|
+
const res = await fetch(url);
|
|
181
|
+
if (!res.ok) throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);
|
|
182
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
183
|
+
}
|
|
184
|
+
function printSignatureFailure(error) {
|
|
185
|
+
process.stdout.write(`${crossmark()} ${fmt.fail("Verification failed")}: ${error.kind}\n`);
|
|
186
|
+
for (const [k, v] of Object.entries(error)) {
|
|
187
|
+
if (k === "kind") continue;
|
|
188
|
+
process.stdout.write(` ${k}: ${String(v)}\n`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function hexToBytes(hex) {
|
|
192
|
+
const stripped = hex.slice(2);
|
|
193
|
+
const out = new Uint8Array(stripped.length / 2);
|
|
194
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(stripped.slice(i * 2, i * 2 + 2), 16);
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/cli/index.ts
|
|
199
|
+
const HELP = `${fmt.bold("attestation")} — sign articles with EIP-712 + Bitcoin (OpenTimestamps).
|
|
200
|
+
|
|
201
|
+
Usage:
|
|
202
|
+
attestation sign <file> --title <t> --slug <s> [--published-at <iso>]
|
|
203
|
+
attestation verify <url|file> [--skip-ots]
|
|
204
|
+
attestation upgrade <ots-file> [<ots-file> …]
|
|
205
|
+
|
|
206
|
+
Run \`attestation <command> --help\` for command-specific options.
|
|
207
|
+
`;
|
|
208
|
+
async function runCli(argv) {
|
|
209
|
+
const [command, ...rest] = argv;
|
|
210
|
+
if (command === void 0 || command === "-h" || command === "--help" || command === "help") {
|
|
211
|
+
process.stdout.write(HELP);
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
if (command === "sign") return await dispatchSign(rest);
|
|
216
|
+
if (command === "verify") return await dispatchVerify(rest);
|
|
217
|
+
if (command === "upgrade") return await dispatchUpgrade(rest);
|
|
218
|
+
process.stderr.write(`Unknown command: ${command}\n${HELP}`);
|
|
219
|
+
return 2;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
process.stderr.write(`${fmt.fail("Error:")} ${error.message}\n`);
|
|
222
|
+
return 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function dispatchSign(argv) {
|
|
226
|
+
const { positionals, values } = (0, node_util.parseArgs)({
|
|
227
|
+
allowPositionals: true,
|
|
228
|
+
args: argv,
|
|
229
|
+
options: {
|
|
230
|
+
locale: { type: "string" },
|
|
231
|
+
"prior-attestation": { type: "string" },
|
|
232
|
+
"published-at": { type: "string" },
|
|
233
|
+
revision: { type: "string" },
|
|
234
|
+
"skip-audit": { type: "boolean" },
|
|
235
|
+
"skip-stamp": { type: "boolean" },
|
|
236
|
+
slug: { type: "string" },
|
|
237
|
+
title: { type: "string" }
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
const file = positionals[0];
|
|
241
|
+
if (!file) throw new Error("Missing required <file> argument.");
|
|
242
|
+
if (!values.title) throw new Error("Missing required --title.");
|
|
243
|
+
if (!values.slug) throw new Error("Missing required --slug.");
|
|
244
|
+
await runSign({
|
|
245
|
+
file,
|
|
246
|
+
locale: values.locale,
|
|
247
|
+
priorAttestation: values["prior-attestation"],
|
|
248
|
+
publishedAt: values["published-at"],
|
|
249
|
+
revision: values.revision ? Number(values.revision) : void 0,
|
|
250
|
+
skipAudit: values["skip-audit"],
|
|
251
|
+
skipStamp: values["skip-stamp"],
|
|
252
|
+
slug: values.slug,
|
|
253
|
+
title: values.title
|
|
254
|
+
});
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
async function dispatchVerify(argv) {
|
|
258
|
+
const { positionals, values } = (0, node_util.parseArgs)({
|
|
259
|
+
allowPositionals: true,
|
|
260
|
+
args: argv,
|
|
261
|
+
options: { "skip-ots": { type: "boolean" } }
|
|
262
|
+
});
|
|
263
|
+
const target = positionals[0];
|
|
264
|
+
if (!target) throw new Error("Missing required <url|file> argument.");
|
|
265
|
+
return await runVerify({
|
|
266
|
+
skipOts: values["skip-ots"],
|
|
267
|
+
target
|
|
268
|
+
}) ? 0 : 1;
|
|
269
|
+
}
|
|
270
|
+
async function dispatchUpgrade(argv) {
|
|
271
|
+
const { positionals } = (0, node_util.parseArgs)({
|
|
272
|
+
allowPositionals: true,
|
|
273
|
+
args: argv,
|
|
274
|
+
options: {}
|
|
275
|
+
});
|
|
276
|
+
await runUpgrade({ files: positionals });
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/cli.ts
|
|
281
|
+
runCli(process.argv.slice(2)).then((code) => process.exit(code));
|
|
282
|
+
//#endregion
|
|
283
|
+
|
|
284
|
+
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.cjs","names":["canonicalize","audit","buildAttestationMessage","signViaBrowser","stringify","stampDigest","hexToBytes","upgradeProof","parse","verifyAttestation","verifyOts"],"sources":["../src/cli/io.ts","../src/cli/sign.ts","../src/cli/upgrade.ts","../src/cli/verify.ts","../src/cli/index.ts","../src/cli.ts"],"sourcesContent":["/* ANSI helpers — kept tiny so we don't pull in chalk. */\n\nconst supportsColor = process.stdout.isTTY === true && !process.env.NO_COLOR;\n\nexport const fmt = {\n bold: (s: string) => (supportsColor ? `\\x1b[1m${s}\\x1b[0m` : s),\n dim: (s: string) => (supportsColor ? `\\x1b[2m${s}\\x1b[0m` : s),\n fail: (s: string) => (supportsColor ? `\\x1b[31m${s}\\x1b[0m` : s),\n info: (s: string) => (supportsColor ? `\\x1b[36m${s}\\x1b[0m` : s),\n ok: (s: string) => (supportsColor ? `\\x1b[32m${s}\\x1b[0m` : s),\n warn: (s: string) => (supportsColor ? `\\x1b[33m${s}\\x1b[0m` : s),\n};\n\nexport function checkmark(): string {\n return fmt.ok('✓');\n}\n\nexport function crossmark(): string {\n return fmt.fail('✗');\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport { basename, dirname, extname, join } from 'node:path';\n\nimport { buildAttestationMessage } from '../attestation/create.js';\nimport { stringify } from '../attestation/serialize.js';\nimport { type SignedAttestation } from '../attestation/types.js';\nimport { audit } from '../core/audit.js';\nimport { canonicalize } from '../core/canonicalize.js';\nimport { signViaBrowser } from '../eth/sign-flow.js';\nimport { stampDigest } from '../ots/stamp.js';\nimport { checkmark, fmt } from './io.js';\n\nexport type SignArgs = {\n file: string;\n title: string;\n slug: string;\n locale?: string;\n publishedAt?: string;\n revision?: number;\n priorAttestation?: `0x${string}`;\n skipStamp?: boolean;\n skipAudit?: boolean;\n};\n\nexport async function runSign(args: SignArgs): Promise<void> {\n const content = await readFile(args.file, 'utf8');\n const locale = args.locale ?? deriveLocale(args.file);\n const publishedAt = args.publishedAt ? new Date(args.publishedAt) : new Date();\n\n const canonical = canonicalize(content);\n process.stdout.write(`${checkmark()} Canonicalized ${canonical.length} bytes (${locale})\\n`);\n\n if (!args.skipAudit) {\n const findings = audit(canonical);\n if (findings.length > 0) {\n process.stdout.write(`${fmt.warn('!')} ${findings.length} suspicious char(s) found:\\n`);\n for (const f of findings) {\n process.stdout.write(\n ` line ${f.line}, col ${f.column}: U+${f.codepoint\n .toString(16)\n .toUpperCase()\n .padStart(4, '0')} (${f.name})\\n`,\n );\n }\n process.stdout.write(` Pass --skip-audit to sign anyway.\\n`);\n throw new Error('Audit failed: clean up suspicious characters first.');\n }\n }\n\n const message = buildAttestationMessage({\n content,\n locale,\n priorAttestation: args.priorAttestation,\n publishedAt,\n revision: args.revision,\n slug: args.slug,\n title: args.title,\n });\n\n process.stdout.write(`${fmt.info('→')} Content digest: ${message.subject.contentDigest}\\n`);\n process.stdout.write(`${fmt.info('→')} Opening browser to sign with your wallet…\\n`);\n\n const { signature, signerAddress } = await signViaBrowser({ message });\n const signed: SignedAttestation = { ...message, signature, signerAddress };\n\n process.stdout.write(`${checkmark()} Signed by ${fmt.bold(signerAddress)}\\n`);\n\n const baseName = basename(args.file, extname(args.file));\n const dir = dirname(args.file);\n const attestationPath = join(dir, `${baseName}.attestation.json`);\n await writeFile(attestationPath, stringify(signed), 'utf8');\n process.stdout.write(`${checkmark()} Wrote ${attestationPath}\\n`);\n\n if (!args.skipStamp) {\n process.stdout.write(`${fmt.info('→')} Submitting to OpenTimestamps calendars…\\n`);\n const digest = hexToBytes(message.subject.contentDigest);\n const otsBytes = await stampDigest(digest);\n const otsPath = join(dir, `${baseName}.ots`);\n await writeFile(otsPath, Buffer.from(otsBytes));\n process.stdout.write(\n `${checkmark()} Wrote ${otsPath} (${otsBytes.length} bytes, calendar-only)\\n`,\n );\n process.stdout.write(\n ` ${fmt.dim('Run `attestation upgrade` in ~24h to anchor in Bitcoin.')}\\n`,\n );\n }\n}\n\nfunction deriveLocale(file: string): string {\n const name = basename(file, extname(file));\n if (/^[a-z]{2}$/.test(name)) {\n return name;\n }\n throw new Error(\n `Cannot derive locale from filename \"${basename(file)}\". Pass --locale explicitly.`,\n );\n}\n\nfunction hexToBytes(hex: `0x${string}`): Uint8Array {\n const stripped = hex.slice(2);\n const out = new Uint8Array(stripped.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(stripped.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\n\nimport { upgradeProof } from '../ots/stamp.js';\nimport { checkmark, fmt } from './io.js';\n\nexport type UpgradeArgs = {\n files: string[];\n};\n\nexport async function runUpgrade(args: UpgradeArgs): Promise<void> {\n if (args.files.length === 0) {\n throw new Error('No .ots files provided.');\n }\n\n for (const path of args.files) {\n process.stdout.write(`${fmt.info('→')} ${path}\\n`);\n const before = await readFile(path);\n const { bytes, upgraded } = await upgradeProof(new Uint8Array(before));\n\n if (upgraded) {\n await writeFile(path, Buffer.from(bytes));\n process.stdout.write(\n ` ${checkmark()} Upgraded to Bitcoin attestation (${bytes.length} bytes)\\n`,\n );\n } else {\n process.stdout.write(\n ` ${fmt.dim('· No new attestation available yet, leaving file untouched.')}\\n`,\n );\n }\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { basename, dirname, extname, join } from 'node:path';\n\nimport { parse } from '../attestation/serialize.js';\nimport { verifyAttestation } from '../attestation/verify.js';\nimport { verifyOts } from '../ots/verify.js';\nimport { checkmark, crossmark, fmt } from './io.js';\n\nexport type VerifyArgs = {\n /** A URL to an article, or a path to a local markdown file. */\n target: string;\n /** Skip OTS Bitcoin verification (signature-only). */\n skipOts?: boolean;\n};\n\ntype Sources = {\n content: string;\n attestationJson: string;\n otsBytes: null | Uint8Array;\n};\n\nexport async function runVerify(args: VerifyArgs): Promise<boolean> {\n const sources = await loadSources(args);\n\n const attestation = parse(sources.attestationJson);\n const sigResult = await verifyAttestation({ attestation, content: sources.content });\n\n if (!sigResult.ok) {\n printSignatureFailure(sigResult.error);\n return false;\n }\n\n process.stdout.write(`${checkmark()} Content digest matches signature\\n`);\n process.stdout.write(\n `${checkmark()} Signature valid — signed by ${fmt.bold(sigResult.signerAddress)}\\n`,\n );\n\n if (args.skipOts || sources.otsBytes === null) {\n process.stdout.write(`${fmt.dim('· OTS verification skipped')}\\n`);\n return true;\n }\n\n const digest = hexToBytes(attestation.subject.contentDigest);\n const otsResult = await verifyOts(digest, sources.otsBytes);\n\n if (!otsResult.ok) {\n process.stdout.write(`${crossmark()} OTS: ${otsResult.reason}`);\n if (otsResult.details) {\n process.stdout.write(` — ${otsResult.details}`);\n }\n process.stdout.write(`\\n`);\n return otsResult.reason === 'pending-bitcoin'; // Pending is not a hard fail\n }\n\n process.stdout.write(\n `${checkmark()} Bitcoin timestamp confirmed at ${otsResult.bitcoinBlockTime.toISOString()}\\n`,\n );\n return true;\n}\n\nasync function loadSources(args: VerifyArgs): Promise<Sources> {\n if (/^https?:\\/\\//.test(args.target)) {\n return loadFromUrl(args.target);\n }\n return loadFromFile(args.target);\n}\n\nasync function loadFromFile(filePath: string): Promise<Sources> {\n const content = await readFile(filePath, 'utf8');\n const dir = dirname(filePath);\n const base = basename(filePath, extname(filePath));\n const attestationJson = await readFile(join(dir, `${base}.attestation.json`), 'utf8');\n let otsBytes: null | Uint8Array = null;\n try {\n const buf = await readFile(join(dir, `${base}.ots`));\n otsBytes = new Uint8Array(buf);\n } catch {\n otsBytes = null;\n }\n return { attestationJson, content, otsBytes };\n}\n\nasync function loadFromUrl(url: string): Promise<Sources> {\n const proofManifestUrl = await resolveManifestUrl(url);\n const manifest = await fetchJson<ProofManifest>(proofManifestUrl);\n\n const base = new URL(proofManifestUrl);\n const [content, attestationJson, otsBuf] = await Promise.all([\n fetchText(new URL(manifest.content, base).toString()),\n fetchText(new URL(manifest.attestation, base).toString()),\n fetchBytes(new URL(manifest.ots, base).toString()).catch(() => null),\n ]);\n return { attestationJson, content, otsBytes: otsBuf };\n}\n\ntype ProofManifest = {\n schemaVersion: number;\n slug: string;\n content: string;\n attestation: string;\n ots: string;\n};\n\nasync function resolveManifestUrl(articleUrl: string): Promise<string> {\n // Convention: append /proof.json to the article URL (trailing slash tolerated).\n const url = new URL(articleUrl);\n if (!url.pathname.endsWith('/')) {\n url.pathname += '/';\n }\n url.pathname += 'proof.json';\n return url.toString();\n}\n\nasync function fetchJson<T>(url: string): Promise<T> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);\n }\n return (await res.json()) as T;\n}\n\nasync function fetchText(url: string): Promise<string> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);\n }\n return res.text();\n}\n\nasync function fetchBytes(url: string): Promise<Uint8Array> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Fetch ${url} failed: HTTP ${res.status}`);\n }\n return new Uint8Array(await res.arrayBuffer());\n}\n\nfunction printSignatureFailure(error: Record<string, unknown> & { kind: string }): void {\n process.stdout.write(`${crossmark()} ${fmt.fail('Verification failed')}: ${error.kind}\\n`);\n for (const [k, v] of Object.entries(error)) {\n if (k === 'kind') {\n continue;\n }\n process.stdout.write(` ${k}: ${String(v)}\\n`);\n }\n}\n\nfunction hexToBytes(hex: `0x${string}`): Uint8Array {\n const stripped = hex.slice(2);\n const out = new Uint8Array(stripped.length / 2);\n for (let i = 0; i < out.length; i++) {\n out[i] = parseInt(stripped.slice(i * 2, i * 2 + 2), 16);\n }\n return out;\n}\n","import { parseArgs } from 'node:util';\n\nimport { fmt } from './io.js';\nimport { runSign } from './sign.js';\nimport { runUpgrade } from './upgrade.js';\nimport { runVerify } from './verify.js';\n\nconst HELP = `${fmt.bold('attestation')} — sign articles with EIP-712 + Bitcoin (OpenTimestamps).\n\nUsage:\n attestation sign <file> --title <t> --slug <s> [--published-at <iso>]\n attestation verify <url|file> [--skip-ots]\n attestation upgrade <ots-file> [<ots-file> …]\n\nRun \\`attestation <command> --help\\` for command-specific options.\n`;\n\nexport async function runCli(argv: string[]): Promise<number> {\n const [command, ...rest] = argv;\n\n if (command === undefined || command === '-h' || command === '--help' || command === 'help') {\n process.stdout.write(HELP);\n return 0;\n }\n\n try {\n if (command === 'sign') {\n return await dispatchSign(rest);\n }\n if (command === 'verify') {\n return await dispatchVerify(rest);\n }\n if (command === 'upgrade') {\n return await dispatchUpgrade(rest);\n }\n process.stderr.write(`Unknown command: ${command}\\n${HELP}`);\n return 2;\n } catch (error) {\n process.stderr.write(`${fmt.fail('Error:')} ${(error as Error).message}\\n`);\n return 1;\n }\n}\n\nasync function dispatchSign(argv: string[]): Promise<number> {\n const { positionals, values } = parseArgs({\n allowPositionals: true,\n args: argv,\n options: {\n locale: { type: 'string' },\n 'prior-attestation': { type: 'string' },\n 'published-at': { type: 'string' },\n revision: { type: 'string' },\n 'skip-audit': { type: 'boolean' },\n 'skip-stamp': { type: 'boolean' },\n slug: { type: 'string' },\n title: { type: 'string' },\n },\n });\n\n const file = positionals[0];\n if (!file) {\n throw new Error('Missing required <file> argument.');\n }\n if (!values.title) {\n throw new Error('Missing required --title.');\n }\n if (!values.slug) {\n throw new Error('Missing required --slug.');\n }\n\n await runSign({\n file,\n locale: values.locale,\n priorAttestation: values['prior-attestation'] as `0x${string}` | undefined,\n publishedAt: values['published-at'],\n revision: values.revision ? Number(values.revision) : undefined,\n skipAudit: values['skip-audit'],\n skipStamp: values['skip-stamp'],\n slug: values.slug,\n title: values.title,\n });\n return 0;\n}\n\nasync function dispatchVerify(argv: string[]): Promise<number> {\n const { positionals, values } = parseArgs({\n allowPositionals: true,\n args: argv,\n options: {\n 'skip-ots': { type: 'boolean' },\n },\n });\n\n const target = positionals[0];\n if (!target) {\n throw new Error('Missing required <url|file> argument.');\n }\n\n const ok = await runVerify({ skipOts: values['skip-ots'], target });\n return ok ? 0 : 1;\n}\n\nasync function dispatchUpgrade(argv: string[]): Promise<number> {\n const { positionals } = parseArgs({ allowPositionals: true, args: argv, options: {} });\n await runUpgrade({ files: positionals });\n return 0;\n}\n","import { runCli } from './cli/index.js';\n\nvoid runCli(process.argv.slice(2)).then((code) => process.exit(code));\n"],"mappings":"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"}
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|