@drm3/sdk 0.1.2

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 (228) hide show
  1. package/LICENSE +75 -0
  2. package/README.md +426 -0
  3. package/bin/drm3.js +255 -0
  4. package/dist/alerts-bell.d.ts +7 -0
  5. package/dist/alerts-bell.d.ts.map +1 -0
  6. package/dist/alerts-bell.js +118 -0
  7. package/dist/alerts-bell.js.map +1 -0
  8. package/dist/alerts-fetch.d.ts +41 -0
  9. package/dist/alerts-fetch.d.ts.map +1 -0
  10. package/dist/alerts-fetch.js +60 -0
  11. package/dist/alerts-fetch.js.map +1 -0
  12. package/dist/alerts-styles.d.ts +4 -0
  13. package/dist/alerts-styles.d.ts.map +1 -0
  14. package/dist/alerts-styles.js +49 -0
  15. package/dist/alerts-styles.js.map +1 -0
  16. package/dist/alerts.d.ts +3 -0
  17. package/dist/alerts.d.ts.map +1 -0
  18. package/dist/alerts.global.js +206 -0
  19. package/dist/alerts.global.text.js +2 -0
  20. package/dist/alerts.js +21 -0
  21. package/dist/alerts.js.map +1 -0
  22. package/dist/index.d.ts +64 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +117 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/keys-canonical.d.ts +7 -0
  27. package/dist/keys-canonical.d.ts.map +1 -0
  28. package/dist/keys-canonical.js +57 -0
  29. package/dist/keys-canonical.js.map +1 -0
  30. package/dist/keys-generate.d.ts +39 -0
  31. package/dist/keys-generate.d.ts.map +1 -0
  32. package/dist/keys-generate.js +39 -0
  33. package/dist/keys-generate.js.map +1 -0
  34. package/dist/keys-verify.d.ts +62 -0
  35. package/dist/keys-verify.d.ts.map +1 -0
  36. package/dist/keys-verify.js +68 -0
  37. package/dist/keys-verify.js.map +1 -0
  38. package/dist/keys.d.ts +4 -0
  39. package/dist/keys.d.ts.map +1 -0
  40. package/dist/keys.js +22 -0
  41. package/dist/keys.js.map +1 -0
  42. package/dist/meter/adapter.d.ts +35 -0
  43. package/dist/meter/adapter.d.ts.map +1 -0
  44. package/dist/meter/adapter.js +0 -0
  45. package/dist/meter/adapter.js.map +1 -0
  46. package/dist/meter/cascade.d.ts +56 -0
  47. package/dist/meter/cascade.d.ts.map +1 -0
  48. package/dist/meter/cascade.js +64 -0
  49. package/dist/meter/cascade.js.map +1 -0
  50. package/dist/meter/client.d.ts +78 -0
  51. package/dist/meter/client.d.ts.map +1 -0
  52. package/dist/meter/client.js +84 -0
  53. package/dist/meter/client.js.map +1 -0
  54. package/dist/meter/core.d.ts +37 -0
  55. package/dist/meter/core.d.ts.map +1 -0
  56. package/dist/meter/core.js +49 -0
  57. package/dist/meter/core.js.map +1 -0
  58. package/dist/meter/gate-client.d.ts +94 -0
  59. package/dist/meter/gate-client.d.ts.map +1 -0
  60. package/dist/meter/gate-client.js +77 -0
  61. package/dist/meter/gate-client.js.map +1 -0
  62. package/dist/meter/gate-decide.d.ts +61 -0
  63. package/dist/meter/gate-decide.d.ts.map +1 -0
  64. package/dist/meter/gate-decide.js +54 -0
  65. package/dist/meter/gate-decide.js.map +1 -0
  66. package/dist/meter/gate.d.ts +10 -0
  67. package/dist/meter/gate.d.ts.map +1 -0
  68. package/dist/meter/gate.js +10 -0
  69. package/dist/meter/gate.js.map +1 -0
  70. package/dist/meter/index.d.ts +28 -0
  71. package/dist/meter/index.d.ts.map +1 -0
  72. package/dist/meter/index.js +28 -0
  73. package/dist/meter/index.js.map +1 -0
  74. package/dist/meter/meters-buckets.d.ts +56 -0
  75. package/dist/meter/meters-buckets.d.ts.map +1 -0
  76. package/dist/meter/meters-buckets.js +111 -0
  77. package/dist/meter/meters-buckets.js.map +1 -0
  78. package/dist/meter/meters-charge.d.ts +13 -0
  79. package/dist/meter/meters-charge.d.ts.map +1 -0
  80. package/dist/meter/meters-charge.js +76 -0
  81. package/dist/meter/meters-charge.js.map +1 -0
  82. package/dist/meter/meters.d.ts +63 -0
  83. package/dist/meter/meters.d.ts.map +1 -0
  84. package/dist/meter/meters.js +44 -0
  85. package/dist/meter/meters.js.map +1 -0
  86. package/dist/meter/plans.d.ts +63 -0
  87. package/dist/meter/plans.d.ts.map +1 -0
  88. package/dist/meter/plans.js +114 -0
  89. package/dist/meter/plans.js.map +1 -0
  90. package/dist/meter/pricebook.d.ts +32 -0
  91. package/dist/meter/pricebook.d.ts.map +1 -0
  92. package/dist/meter/pricebook.js +38 -0
  93. package/dist/meter/pricebook.js.map +1 -0
  94. package/dist/meter/resolve.d.ts +103 -0
  95. package/dist/meter/resolve.d.ts.map +1 -0
  96. package/dist/meter/resolve.js +100 -0
  97. package/dist/meter/resolve.js.map +1 -0
  98. package/dist/meter/sink.d.ts +26 -0
  99. package/dist/meter/sink.d.ts.map +1 -0
  100. package/dist/meter/sink.js +9 -0
  101. package/dist/meter/sink.js.map +1 -0
  102. package/dist/meter/sql/d1.d.ts +32 -0
  103. package/dist/meter/sql/d1.d.ts.map +1 -0
  104. package/dist/meter/sql/d1.js +41 -0
  105. package/dist/meter/sql/d1.js.map +1 -0
  106. package/dist/meter/sql/driver.d.ts +34 -0
  107. package/dist/meter/sql/driver.d.ts.map +1 -0
  108. package/dist/meter/sql/driver.js +17 -0
  109. package/dist/meter/sql/driver.js.map +1 -0
  110. package/dist/meter/sql/postgres.d.ts +23 -0
  111. package/dist/meter/sql/postgres.d.ts.map +1 -0
  112. package/dist/meter/sql/postgres.js +36 -0
  113. package/dist/meter/sql/postgres.js.map +1 -0
  114. package/dist/meter/sql/schema.d.ts +13 -0
  115. package/dist/meter/sql/schema.d.ts.map +1 -0
  116. package/dist/meter/sql/schema.js +32 -0
  117. package/dist/meter/sql/schema.js.map +1 -0
  118. package/dist/meter/sql/sql-adapter.d.ts +36 -0
  119. package/dist/meter/sql/sql-adapter.d.ts.map +1 -0
  120. package/dist/meter/sql/sql-adapter.js +124 -0
  121. package/dist/meter/sql/sql-adapter.js.map +1 -0
  122. package/dist/meter/sql/statements.d.ts +30 -0
  123. package/dist/meter/sql/statements.d.ts.map +1 -0
  124. package/dist/meter/sql/statements.js +24 -0
  125. package/dist/meter/sql/statements.js.map +1 -0
  126. package/dist/meter/types.d.ts +48 -0
  127. package/dist/meter/types.d.ts.map +1 -0
  128. package/dist/meter/types.js +8 -0
  129. package/dist/meter/types.js.map +1 -0
  130. package/dist/meter/units.d.ts +33 -0
  131. package/dist/meter/units.d.ts.map +1 -0
  132. package/dist/meter/units.js +45 -0
  133. package/dist/meter/units.js.map +1 -0
  134. package/dist/meter/wallet-buckets.d.ts +63 -0
  135. package/dist/meter/wallet-buckets.d.ts.map +1 -0
  136. package/dist/meter/wallet-buckets.js +131 -0
  137. package/dist/meter/wallet-buckets.js.map +1 -0
  138. package/dist/meter/wallet-charge.d.ts +15 -0
  139. package/dist/meter/wallet-charge.d.ts.map +1 -0
  140. package/dist/meter/wallet-charge.js +64 -0
  141. package/dist/meter/wallet-charge.js.map +1 -0
  142. package/dist/meter/wallet.d.ts +68 -0
  143. package/dist/meter/wallet.d.ts.map +1 -0
  144. package/dist/meter/wallet.js +47 -0
  145. package/dist/meter/wallet.js.map +1 -0
  146. package/dist/receipts-api.d.ts +5 -0
  147. package/dist/receipts-api.d.ts.map +1 -0
  148. package/dist/receipts-api.js +117 -0
  149. package/dist/receipts-api.js.map +1 -0
  150. package/dist/receipts-chain.d.ts +3 -0
  151. package/dist/receipts-chain.d.ts.map +1 -0
  152. package/dist/receipts-chain.js +121 -0
  153. package/dist/receipts-chain.js.map +1 -0
  154. package/dist/receipts-modal.d.ts +14 -0
  155. package/dist/receipts-modal.d.ts.map +1 -0
  156. package/dist/receipts-modal.js +100 -0
  157. package/dist/receipts-modal.js.map +1 -0
  158. package/dist/receipts-registry.d.ts +4 -0
  159. package/dist/receipts-registry.d.ts.map +1 -0
  160. package/dist/receipts-registry.js +54 -0
  161. package/dist/receipts-registry.js.map +1 -0
  162. package/dist/receipts-render.d.ts +9 -0
  163. package/dist/receipts-render.d.ts.map +1 -0
  164. package/dist/receipts-render.js +83 -0
  165. package/dist/receipts-render.js.map +1 -0
  166. package/dist/receipts-types.d.ts +61 -0
  167. package/dist/receipts-types.d.ts.map +1 -0
  168. package/dist/receipts-types.js +12 -0
  169. package/dist/receipts-types.js.map +1 -0
  170. package/dist/receipts-ui.d.ts +10 -0
  171. package/dist/receipts-ui.d.ts.map +1 -0
  172. package/dist/receipts-ui.js +103 -0
  173. package/dist/receipts-ui.js.map +1 -0
  174. package/dist/receipts-verify.d.ts +8 -0
  175. package/dist/receipts-verify.d.ts.map +1 -0
  176. package/dist/receipts-verify.js +113 -0
  177. package/dist/receipts-verify.js.map +1 -0
  178. package/dist/receipts.d.ts +9 -0
  179. package/dist/receipts.d.ts.map +1 -0
  180. package/dist/receipts.global.js +587 -0
  181. package/dist/receipts.global.text.js +2 -0
  182. package/dist/receipts.js +28 -0
  183. package/dist/receipts.js.map +1 -0
  184. package/dist/sso/index.d.ts +4 -0
  185. package/dist/sso/index.d.ts.map +1 -0
  186. package/dist/sso/index.js +10 -0
  187. package/dist/sso/index.js.map +1 -0
  188. package/dist/sso/jwt.d.ts +29 -0
  189. package/dist/sso/jwt.d.ts.map +1 -0
  190. package/dist/sso/jwt.js +73 -0
  191. package/dist/sso/jwt.js.map +1 -0
  192. package/dist/sso/launch.d.ts +70 -0
  193. package/dist/sso/launch.d.ts.map +1 -0
  194. package/dist/sso/launch.js +75 -0
  195. package/dist/sso/launch.js.map +1 -0
  196. package/dist/sso/revocation.d.ts +23 -0
  197. package/dist/sso/revocation.d.ts.map +1 -0
  198. package/dist/sso/revocation.js +53 -0
  199. package/dist/sso/revocation.js.map +1 -0
  200. package/dist/switcher.d.ts +22 -0
  201. package/dist/switcher.d.ts.map +1 -0
  202. package/dist/switcher.js +174 -0
  203. package/dist/switcher.js.map +1 -0
  204. package/dist/x402/chain.d.ts +49 -0
  205. package/dist/x402/chain.d.ts.map +1 -0
  206. package/dist/x402/chain.js +33 -0
  207. package/dist/x402/chain.js.map +1 -0
  208. package/dist/x402/crypto.d.ts +25 -0
  209. package/dist/x402/crypto.d.ts.map +1 -0
  210. package/dist/x402/crypto.js +74 -0
  211. package/dist/x402/crypto.js.map +1 -0
  212. package/dist/x402/eip712.d.ts +20 -0
  213. package/dist/x402/eip712.d.ts.map +1 -0
  214. package/dist/x402/eip712.js +49 -0
  215. package/dist/x402/eip712.js.map +1 -0
  216. package/dist/x402/envelope.d.ts +45 -0
  217. package/dist/x402/envelope.d.ts.map +1 -0
  218. package/dist/x402/envelope.js +50 -0
  219. package/dist/x402/envelope.js.map +1 -0
  220. package/dist/x402/index.d.ts +41 -0
  221. package/dist/x402/index.d.ts.map +1 -0
  222. package/dist/x402/index.js +41 -0
  223. package/dist/x402/index.js.map +1 -0
  224. package/dist/x402/verify.d.ts +51 -0
  225. package/dist/x402/verify.d.ts.map +1 -0
  226. package/dist/x402/verify.js +110 -0
  227. package/dist/x402/verify.js.map +1 -0
  228. package/package.json +100 -0
package/bin/drm3.js ADDED
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ // drm3 - the DRM3 partner CLI. Same capabilities as @drm3/sdk, for a human in a terminal.
3
+ // Zero deps, plain Node >= 20 (WebCrypto Ed25519). Self-contained on purpose: the module
4
+ // source is TypeScript; this bin must run under bare `npx drm3`. The signing discipline
5
+ // (canonicalize + Ed25519 over object-minus-signature) is byte-identical to src/keys.ts
6
+ // and pinned by test/cli.test.ts.
7
+ //
8
+ // drm3 keys generate [--out FILE] make a keypair locally (private never leaves you)
9
+ // drm3 keys csr --subject NAME --key FILE build the CSR to submit to DRM3
10
+ // drm3 keys verify RECEIPT.json [--attestation ATT.json] [--root ed25519:HEX]
11
+ // drm3 keys docs the key-signing process, right here
12
+ // drm3 status fleet + key-registry state, live
13
+ // drm3 keys submit send the CSR to DRM3 (public key only)
14
+ // drm3 partner status --subject NAME is my certificate issued yet?
15
+
16
+ import { readFileSync, writeFileSync } from "node:fs";
17
+ import { webcrypto } from "node:crypto";
18
+ const crypto = globalThis.crypto ?? webcrypto;
19
+
20
+ const CA_BASE = process.env.DRM3_CA_URL || "https://status.drm3.network";
21
+ const REGISTRY = `${CA_BASE}/.well-known/drm3-keys.json`;
22
+
23
+ // ── shared discipline (mirrors src/keys.ts; pinned by test) ──
24
+ export function canonicalize(obj) {
25
+ if (obj === null || typeof obj !== "object") return JSON.stringify(obj);
26
+ if (Array.isArray(obj)) return `[${obj.map(canonicalize).join(",")}]`;
27
+ const keys = Object.keys(obj).sort();
28
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k])}`).join(",")}}`;
29
+ }
30
+ const hex = (b) => [...new Uint8Array(b)].map((x) => x.toString(16).padStart(2, "0")).join("");
31
+ const unhex = (s) => new Uint8Array((s.replace(/^ed25519:/, "").match(/../g) || []).map((x) => Number.parseInt(x, 16)));
32
+ const b64 = (b) => Buffer.from(b).toString("base64");
33
+ const enc = new TextEncoder();
34
+
35
+ async function importVerify(pub) {
36
+ return crypto.subtle.importKey("raw", unhex(pub), { name: "Ed25519" }, false, ["verify"]);
37
+ }
38
+ async function verifySig(pub, sig, payload) {
39
+ try {
40
+ return await crypto.subtle.verify("Ed25519", await importVerify(pub), unhex(sig), enc.encode(payload));
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
46
+ function out(s) {
47
+ process.stdout.write(`${s}\n`);
48
+ }
49
+ function die(s) {
50
+ process.stderr.write(`drm3: ${s}\n`);
51
+ process.exit(1);
52
+ }
53
+ function arg(flags, name, fallback) {
54
+ const i = flags.indexOf(name);
55
+ return i >= 0 && flags[i + 1] ? flags[i + 1] : fallback;
56
+ }
57
+
58
+ async function keysGenerate(flags) {
59
+ const kp = await crypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]);
60
+ const pub = `ed25519:${hex(await crypto.subtle.exportKey("raw", kp.publicKey))}`;
61
+ const priv = b64(await crypto.subtle.exportKey("pkcs8", kp.privateKey));
62
+ const outFile = arg(flags, "--out");
63
+ const record = { schema: "drm3-signing-key/1", public_key: pub, private_key_pkcs8: priv, generated_at: new Date().toISOString() };
64
+ if (outFile) {
65
+ writeFileSync(outFile, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
66
+ out(`public key ${pub}`);
67
+ out(`private key written to ${outFile} (mode 0600). Keep it. DRM3 never needs it.`);
68
+ } else {
69
+ out(JSON.stringify(record, null, 2));
70
+ out("\nShown once. Store the private key yourself; only the public key ever goes to DRM3.");
71
+ }
72
+ }
73
+
74
+ async function keysCsr(flags) {
75
+ const subject = arg(flags, "--subject") || die("--subject NAME is required");
76
+ const keyFile = arg(flags, "--key") || die("--key FILE (from `drm3 keys generate --out FILE`) is required");
77
+ const rec = JSON.parse(readFileSync(keyFile, "utf8"));
78
+ const priv = await crypto.subtle.importKey("pkcs8", Buffer.from(rec.private_key_pkcs8, "base64"), { name: "Ed25519" }, false, ["sign"]);
79
+ const unsigned = {
80
+ schema: "drm3-csr/1",
81
+ subject,
82
+ subject_key: rec.public_key,
83
+ purposes: (arg(flags, "--purposes", "receipt.sign")).split(","),
84
+ requested_valid_days: Number(arg(flags, "--days", "365")),
85
+ nonce: hex(crypto.getRandomValues(new Uint8Array(16))),
86
+ signed_at: new Date().toISOString(),
87
+ };
88
+ const sig = new Uint8Array(await crypto.subtle.sign("Ed25519", priv, enc.encode(canonicalize(unsigned))));
89
+ out(JSON.stringify({ ...unsigned, signature: `ed25519:${hex(sig)}` }, null, 2));
90
+ out("\nSubmit this CSR from your verified partner account. Your private key was not included.");
91
+ }
92
+
93
+ async function keysVerify(flags, positional) {
94
+ const receiptFile = positional[0] || die("usage: drm3 keys verify RECEIPT.json [--attestation ATT.json] [--root KEY]");
95
+ const r = JSON.parse(readFileSync(receiptFile, "utf8"));
96
+ const payload = canonicalize({
97
+ id: r.id, action: r.action, timestamp: r.timestamp, input_hash: r.input_hash,
98
+ output_hash: r.output_hash, cost: r.cost, duration_ms: r.duration_ms,
99
+ parent_id: r.parent_id, metadata: r.metadata, public_key: r.public_key,
100
+ });
101
+ const sigOk = r.public_key && r.signature && (await verifySig(r.public_key, r.signature, payload));
102
+ if (!sigOk) die("FAILED: receipt signature does not verify");
103
+ out("receipt signature OK");
104
+ const attFile = arg(flags, "--attestation");
105
+ if (!attFile) {
106
+ // No cert supplied: check the live registry for the key's status, grade honestly.
107
+ try {
108
+ const reg = await (await fetch(REGISTRY)).json();
109
+ const all = (reg.services || []).flatMap((s) => [...(s.current || []), ...(s.history || [])]);
110
+ const rec = all.find((k) => (k.key || "").toLowerCase() === r.public_key.toLowerCase());
111
+ if (rec) {
112
+ out(`registry status ${rec.status}${rec.path ? ` (${rec.path})` : ""}`);
113
+ if (rec.status === "compromised" || rec.status === "revoked") die("FAILED: key status is untrusted");
114
+ } else out("registry status key not found in the DRM3 registry");
115
+ } catch {
116
+ out("registry status unreachable (offline?)");
117
+ }
118
+ out("grade unanchored (no issuer attestation supplied)");
119
+ return;
120
+ }
121
+ const att = JSON.parse(readFileSync(attFile, "utf8"));
122
+ const { signature, ...unsignedAtt } = att;
123
+ const root = arg(flags, "--root");
124
+ if (root && att.issuer.toLowerCase() !== root.toLowerCase()) die("FAILED: attestation issuer is not the expected root");
125
+ if (!(await verifySig(att.issuer, signature, canonicalize(unsignedAtt)))) die("FAILED: attestation signature");
126
+ if (!(att.subject_keys || []).some((k) => k.key.toLowerCase() === r.public_key.toLowerCase()))
127
+ die("FAILED: receipt key is not covered by the attestation");
128
+ out("issuer attestation OK");
129
+ out("grade root-anchored");
130
+ }
131
+
132
+ /** drm3 keys submit --subject NAME --key FILE - build the CSR and send it to the CA. */
133
+ async function keysSubmit(flags) {
134
+ const subject = arg(flags, "--subject") || die("--subject NAME is required");
135
+ const keyFile = arg(flags, "--key") || die("--key FILE is required");
136
+ const rec = JSON.parse(readFileSync(keyFile, "utf8"));
137
+ const priv = await crypto.subtle.importKey("pkcs8", Buffer.from(rec.private_key_pkcs8, "base64"), { name: "Ed25519" }, false, ["sign"]);
138
+ const unsigned = {
139
+ schema: "drm3-csr/1",
140
+ subject,
141
+ subject_key: rec.public_key,
142
+ purposes: (arg(flags, "--purposes", "receipt.sign")).split(","),
143
+ requested_valid_days: Number(arg(flags, "--days", "365")),
144
+ nonce: hex(crypto.getRandomValues(new Uint8Array(16))),
145
+ signed_at: new Date().toISOString(),
146
+ };
147
+ const sig = new Uint8Array(await crypto.subtle.sign("Ed25519", priv, enc.encode(canonicalize(unsigned))));
148
+ const csr = { ...unsigned, signature: `ed25519:${hex(sig)}` };
149
+ const r = await fetch(`${CA_BASE}/v1/ca/csr`, {
150
+ method: "POST",
151
+ headers: { "Content-Type": "application/json" },
152
+ body: JSON.stringify(csr),
153
+ }).catch((e) => die(`could not reach the CA: ${e?.message || e}`));
154
+ const d = await r.json().catch(() => ({}));
155
+ if (!r.ok) die(`${r.status}: ${d.error || "the CA refused the request"}`);
156
+ out(`request id ${d.request_id}`);
157
+ out(`subject ${d.subject}`);
158
+ out(`status ${String(d.status || "").toUpperCase()}`);
159
+ out("");
160
+ out(d.note || "");
161
+ out("");
162
+ out(`Check it with: drm3 partner status --subject ${subject}`);
163
+ out("Your private key was NOT sent. Only the public half left this machine.");
164
+ }
165
+
166
+ function keysDocs() {
167
+ out(`The DRM3 key-signing process (DRM3 is the Certificate Authority)
168
+
169
+ 1. GENERATE drm3 keys generate --out mykey.json
170
+ An Ed25519 keypair is created on YOUR machine. The private key
171
+ never leaves you. DRM3 never sees it, ever.
172
+
173
+ 2. REQUEST drm3 keys csr --subject your-app --key mykey.json
174
+ Builds a certificate signing request: your PUBLIC key plus what
175
+ it is for, self-signed so DRM3 can prove you hold the private key.
176
+
177
+ 3. VERIFY DRM3 confirms you are an approved partner (a human reviews) and
178
+ checks the CSR self-signature.
179
+
180
+ 4. CERTIFY DRM3 issues a certificate (drm3-issuer-attestation/1) signed by
181
+ the DRM3 Root, binding your key to your name, valid ~12 months.
182
+
183
+ 5. PUBLISH Your key joins the public tree and registry. Anyone can verify
184
+ receipts you sign chain back to a key DRM3 vouched for.
185
+
186
+ Roll: generate a new key, submit a new CSR. Old certs stay valid for their
187
+ window, so history keeps verifying.
188
+ Revoke: tell DRM3 (or we detect compromise); the registry marks it within
189
+ minutes. Verifiers refuse it from then on.
190
+
191
+ A certificate proves WHO signed and THAT it was not altered. It never proves a
192
+ signed statement is true. Full practice statement: drm3-provenance
193
+ docs/specs/CERTIFICATE-PRACTICE-STATEMENT.md`);
194
+ }
195
+
196
+ async function status() {
197
+ try {
198
+ const reg = await (await fetch(REGISTRY)).json();
199
+ const services = reg.services || [];
200
+ const cur = services.reduce((n, s) => n + (s.current || []).length, 0);
201
+ const ri = reg.registry_integrity || {};
202
+ out(`registry ${services.length} signers, ${cur} live keys`);
203
+ out(`registry signed ${ri.signed ? `yes (root ${String(ri.root_key || "").slice(0, 26)}…)` : "no (content hash only)"}`);
204
+ out(`published ${reg.published_at || "?"}`);
205
+ } catch (e) {
206
+ die(`could not reach ${REGISTRY}: ${e?.message || e}`);
207
+ }
208
+ }
209
+
210
+ /** drm3 partner status --subject NAME - is my certificate issued yet? */
211
+ async function partnerStatus(flags) {
212
+ const subject = arg(flags, "--subject") || die("usage: drm3 partner status --subject NAME");
213
+ try {
214
+ const r = await fetch(`${CA_BASE}/v1/ca/cert/${encodeURIComponent(subject)}`);
215
+ const d = await r.json().catch(() => ({}));
216
+ if (r.ok && d.schema) {
217
+ const k = (d.subject_keys || [])[0] || {};
218
+ out(`subject ${d.service}`);
219
+ out("status CERTIFIED");
220
+ out(`certified key ${k.key || "?"}`);
221
+ out(`issued ${d.issued_at || "?"}`);
222
+ out(`valid until ${k.expected_valid_until || "?"}`);
223
+ out(`issuer (DRM3 Root) ${d.issuer || "?"}`);
224
+ return;
225
+ }
226
+ out(`subject ${subject}`);
227
+ out(`status ${(d.status || "not found").toUpperCase()}`);
228
+ out(` ${d.note || "No certificate is published for this subject."}`);
229
+ } catch (e) {
230
+ die(`could not reach the CA: ${e?.message || e}`);
231
+ }
232
+ }
233
+
234
+ const [, , cmd, sub, ...rest] = process.argv;
235
+ const flags = [sub, ...rest].filter(Boolean);
236
+ const positional = flags.filter((f, i) => !f.startsWith("--") && (i === 0 || !flags[i - 1].startsWith("--")));
237
+ const run = async () => {
238
+ if (cmd === "keys" && sub === "generate") return keysGenerate(rest);
239
+ if (cmd === "keys" && sub === "csr") return keysCsr(rest);
240
+ if (cmd === "keys" && sub === "verify") return keysVerify(rest, rest.filter((f, i) => !f.startsWith("--") && (i === 0 || !rest[i - 1].startsWith("--"))));
241
+ if (cmd === "keys" && sub === "submit") return keysSubmit(rest);
242
+ if (cmd === "keys" && sub === "docs") return keysDocs();
243
+ if (cmd === "status") return status();
244
+ if (cmd === "partner" && sub === "status") return partnerStatus(rest);
245
+ out(`drm3 - the DRM3 partner CLI
246
+
247
+ drm3 keys generate [--out FILE]
248
+ drm3 keys csr --subject NAME --key FILE [--purposes a,b] [--days N]
249
+ drm3 keys submit --subject NAME --key FILE build the CSR and send it to DRM3
250
+ drm3 keys verify RECEIPT.json [--attestation ATT.json] [--root ed25519:HEX]
251
+ drm3 keys docs
252
+ drm3 status
253
+ drm3 partner status --subject NAME`);
254
+ };
255
+ run().catch((e) => die(e?.message || String(e)));
@@ -0,0 +1,7 @@
1
+ import { type AlertsOptions } from "./alerts-fetch.js";
2
+ /**
3
+ * Mount the DRM3 alerts bell into `target`. Signed-in-only: the first feed fetch
4
+ * doubles as the auth probe, and any failure leaves the target empty (fail closed).
5
+ */
6
+ export declare function mountAlertsBell(target: HTMLElement | null, opts?: AlertsOptions): void;
7
+ //# sourceMappingURL=alerts-bell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts-bell.d.ts","sourceRoot":"","sources":["../src/alerts-bell.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,KAAK,aAAa,EAA0D,MAAM,mBAAmB,CAAC;AAG/H;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,EAAE,IAAI,GAAE,aAAkB,GAAG,IAAI,CAU1F"}
@@ -0,0 +1,118 @@
1
+ // @drm3/sdk/alerts - the bell UI: mountAlertsBell + buildBell + rendering.
2
+ // Split out of ./alerts; re-exported by the barrel. Moved verbatim from ./alerts.
3
+ import { esc, fetchAlerts, hubOf, safeLink, sortNewest, timeAgo } from "./alerts-fetch.js";
4
+ import { BELL_SVG, KIND_DOTS, injectStyles } from "./alerts-styles.js";
5
+ /**
6
+ * Mount the DRM3 alerts bell into `target`. Signed-in-only: the first feed fetch
7
+ * doubles as the auth probe, and any failure leaves the target empty (fail closed).
8
+ */
9
+ export function mountAlertsBell(target, opts = {}) {
10
+ if (!target || typeof document === "undefined")
11
+ return;
12
+ if (opts.authed === false) {
13
+ target.replaceChildren();
14
+ return;
15
+ }
16
+ void fetchAlerts(opts).then((d) => {
17
+ if (d)
18
+ buildBell(target, opts, d);
19
+ else
20
+ target.replaceChildren();
21
+ });
22
+ }
23
+ function buildBell(target, opts, initial) {
24
+ injectStyles();
25
+ const hub = hubOf(opts);
26
+ target.classList.add("drm3al");
27
+ target.innerHTML = `<button class="drm3al-btn" type="button" aria-label="Alerts" aria-haspopup="true" aria-expanded="false">${BELL_SVG}<span class="drm3al-badge" hidden></span></button>
28
+ <div class="drm3al-panel" role="menu" aria-label="Alerts">
29
+ <div class="drm3al-h">Alerts</div>
30
+ <div class="drm3al-list"></div>
31
+ <div class="drm3al-foot"><a href="${esc(hub)}/account?tab=alerts">All alerts &rarr;</a></div>
32
+ </div>`;
33
+ const btn = target.querySelector(".drm3al-btn");
34
+ const badge = target.querySelector(".drm3al-badge");
35
+ const list = target.querySelector(".drm3al-list");
36
+ let alerts = sortNewest(initial.alerts);
37
+ const setUnread = (n) => {
38
+ badge.hidden = n <= 0;
39
+ badge.textContent = n > 9 ? "9+" : String(n);
40
+ opts.onCount?.(n);
41
+ };
42
+ const row = (a) => {
43
+ const dot = KIND_DOTS[a.kind] || KIND_DOTS.info;
44
+ const link = safeLink(a.link);
45
+ const body = a.body ? `<div class="drm3al-b">${esc(a.body)}</div>` : "";
46
+ const inner = `<span class="drm3al-dot" style="background:${dot}"></span><div class="drm3al-c"><div class="drm3al-t">${esc(a.title || "")}</div>${body}<div class="drm3al-ago">${esc(timeAgo(a.created_at))}</div></div>`;
47
+ const cls = `drm3al-row${a.read ? "" : " drm3al-unread"}`;
48
+ return link ? `<a class="${cls}" href="${esc(link)}">${inner}</a>` : `<div class="${cls}">${inner}</div>`;
49
+ };
50
+ const render = () => {
51
+ list.innerHTML = alerts.length ? alerts.map(row).join("") : `<div class="drm3al-empty">No alerts.</div>`;
52
+ };
53
+ // Quiet background refresh: update badge + list without touching read state. A
54
+ // transient failure (or a dropped session) keeps the current view rather than
55
+ // flashing it away.
56
+ const refresh = async () => {
57
+ const d = await fetchAlerts(opts);
58
+ if (!d)
59
+ return;
60
+ alerts = sortNewest(d.alerts);
61
+ setUnread(d.unread);
62
+ if (target.getAttribute("data-open") === "1")
63
+ render();
64
+ };
65
+ const markAllRead = async () => {
66
+ try {
67
+ await fetch(`${hub}/api/sso/alerts/read`, {
68
+ method: "POST",
69
+ credentials: "include",
70
+ headers: { "content-type": "application/json", ...(opts.ssoToken ? { authorization: `Bearer ${opts.ssoToken}` } : {}) },
71
+ body: JSON.stringify({ all: true }),
72
+ });
73
+ }
74
+ catch {
75
+ /* best effort; the hub reconciles on the next feed read */
76
+ }
77
+ };
78
+ const close = () => {
79
+ target.removeAttribute("data-open");
80
+ btn.setAttribute("aria-expanded", "false");
81
+ };
82
+ // Opening acknowledges the feed: the badge clears immediately, but rows keep
83
+ // their unread highlight for this open (the local flags flip after the paint).
84
+ const open = async () => {
85
+ target.setAttribute("data-open", "1");
86
+ btn.setAttribute("aria-expanded", "true");
87
+ render(); // instant paint from cache
88
+ const d = await fetchAlerts(opts); // re-fetch on open
89
+ if (d) {
90
+ alerts = sortNewest(d.alerts);
91
+ render();
92
+ }
93
+ void markAllRead();
94
+ setUnread(0);
95
+ for (const a of alerts)
96
+ a.read = true;
97
+ };
98
+ btn.addEventListener("click", (e) => {
99
+ e.stopPropagation();
100
+ if (target.getAttribute("data-open") === "1")
101
+ return close();
102
+ void open();
103
+ });
104
+ document.addEventListener("click", (e) => {
105
+ if (!target.contains(e.target))
106
+ close();
107
+ });
108
+ document.addEventListener("keydown", (e) => {
109
+ if (e.key === "Escape")
110
+ close();
111
+ });
112
+ setUnread(initial.unread);
113
+ render();
114
+ const pollMs = opts.pollMs ?? 120000;
115
+ if (pollMs > 0)
116
+ setInterval(() => void refresh(), pollMs);
117
+ }
118
+ //# sourceMappingURL=alerts-bell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts-bell.js","sourceRoot":"","sources":["../src/alerts-bell.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ export interface AlertItem {
2
+ id: string;
3
+ /** "info" | "success" | "warning" | "critical". Unknown kinds fall back to info styling. */
4
+ kind: string;
5
+ title: string;
6
+ body?: string | null;
7
+ link?: string | null;
8
+ created_at?: string;
9
+ read?: boolean;
10
+ }
11
+ export interface AlertsOptions {
12
+ /** DRM3 account hub origin. Default https://drm3.network. */
13
+ hub?: string;
14
+ /** Optional `drm3_sso` token sent as a Bearer; otherwise the hub reads the session cookie. */
15
+ ssoToken?: string;
16
+ /**
17
+ * Whether the current user is signed in to DRM3. Pass `false` to render nothing.
18
+ * `true` or omitted tries the feed, and any failure (incl. a 401) renders nothing,
19
+ * so omitting it is always safe (fails closed).
20
+ */
21
+ authed?: boolean;
22
+ /** Background refresh interval in ms. Default 120000 (2 min); 0 disables it. */
23
+ pollMs?: number;
24
+ /** Called with the unread count on every update, for host UIs that render their own badge. */
25
+ onCount?: (unread: number) => void;
26
+ }
27
+ export declare function esc(s: string): string;
28
+ export declare function safeLink(link?: string | null): string;
29
+ export declare function timeAgo(iso?: string): string;
30
+ export declare function sortNewest(list: AlertItem[]): AlertItem[];
31
+ export declare function hubOf(opts: AlertsOptions): string;
32
+ /**
33
+ * The signed-in user's platform alerts + unread count from the DRM3 account hub.
34
+ * Pass the `drm3_sso` token (Bearer) if you have it; otherwise the hub reads the cookie.
35
+ * Returns null on any error or non-200 (incl. a 401 when signed out) - never throws.
36
+ */
37
+ export declare function fetchAlerts(opts?: AlertsOptions): Promise<{
38
+ alerts: AlertItem[];
39
+ unread: number;
40
+ } | null>;
41
+ //# sourceMappingURL=alerts-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts-fetch.d.ts","sourceRoot":"","sources":["../src/alerts-fetch.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8FAA8F;IAC9F,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAErC;AAGD,wBAAgB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGrD;AAED,wBAAgB,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5C;AAGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAEzD;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAcnH"}
@@ -0,0 +1,60 @@
1
+ // @drm3/sdk/alerts - the signed-in alerts feed client plus the shared option types
2
+ // and the time/link/escape helpers. Split out of ./alerts; imported by ./alerts-bell
3
+ // and re-exported by the barrel. Moved verbatim from ./alerts.
4
+ //
5
+ // The default hub mirrors ACCOUNT_HUB in ./index.
6
+ const DEFAULT_HUB = "https://drm3.network";
7
+ export function esc(s) {
8
+ return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
9
+ }
10
+ // Only http(s) or site-relative links may become an href; anything else is dropped.
11
+ export function safeLink(link) {
12
+ const l = String(link || "");
13
+ return /^https?:\/\//i.test(l) || l.startsWith("/") ? l : "";
14
+ }
15
+ export function timeAgo(iso) {
16
+ if (!iso)
17
+ return "";
18
+ const t = new Date(iso).getTime();
19
+ if (Number.isNaN(t))
20
+ return "";
21
+ const s = Math.max(0, (Date.now() - t) / 1000);
22
+ if (s < 60)
23
+ return "just now";
24
+ if (s < 3600)
25
+ return `${Math.floor(s / 60)}m ago`;
26
+ if (s < 86400)
27
+ return `${Math.floor(s / 3600)}h ago`;
28
+ return `${Math.floor(s / 86400)}d ago`;
29
+ }
30
+ // Newest first. ISO timestamps sort lexicographically; missing ones sink to the bottom.
31
+ export function sortNewest(list) {
32
+ return [...list].sort((a, b) => String(b.created_at || "").localeCompare(String(a.created_at || "")));
33
+ }
34
+ export function hubOf(opts) {
35
+ return (opts.hub || DEFAULT_HUB).replace(/\/+$/, "");
36
+ }
37
+ /**
38
+ * The signed-in user's platform alerts + unread count from the DRM3 account hub.
39
+ * Pass the `drm3_sso` token (Bearer) if you have it; otherwise the hub reads the cookie.
40
+ * Returns null on any error or non-200 (incl. a 401 when signed out) - never throws.
41
+ */
42
+ export async function fetchAlerts(opts = {}) {
43
+ try {
44
+ const r = await fetch(`${hubOf(opts)}/api/sso/alerts`, {
45
+ credentials: "include",
46
+ headers: opts.ssoToken ? { authorization: `Bearer ${opts.ssoToken}` } : undefined,
47
+ });
48
+ if (!r.ok)
49
+ return null;
50
+ const d = (await r.json());
51
+ if (!Array.isArray(d.alerts))
52
+ return null;
53
+ const unread = typeof d.unread === "number" ? d.unread : d.alerts.filter((a) => !a.read).length;
54
+ return { alerts: d.alerts, unread };
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ //# sourceMappingURL=alerts-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts-fetch.js","sourceRoot":"","sources":["../src/alerts-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare const KIND_DOTS: Record<string, string>;
2
+ export declare const BELL_SVG = "<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9\"/><path d=\"M10.3 21a1.94 1.94 0 0 0 3.4 0\"/></svg>";
3
+ export declare function injectStyles(): void;
4
+ //# sourceMappingURL=alerts-styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts-styles.d.ts","sourceRoot":"","sources":["../src/alerts-styles.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAK5C,CAAC;AAEF,eAAO,MAAM,QAAQ,0QAAwP,CAAC;AAG9Q,wBAAgB,YAAY,IAAI,IAAI,CAiCnC"}
@@ -0,0 +1,49 @@
1
+ // @drm3/sdk/alerts - injected scoped styles (.drm3al* namespace), the bell SVG, and
2
+ // the kind accents. Split out of ./alerts so the barrel stays thin; imported by
3
+ // ./alerts-bell. Moved verbatim from ./alerts.
4
+ // Kind accents: dot colors on the dark panel. Accents only - row text stays light
5
+ // (#f2f6fa / #9fb1c4 on #0f1318) so every text surface holds WCAG AA 4.5:1.
6
+ export const KIND_DOTS = {
7
+ info: "#60a5fa",
8
+ success: "#4ade80",
9
+ warning: "#fbbf24",
10
+ critical: "#f87171",
11
+ };
12
+ export const BELL_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>`;
13
+ // styles (injected once)
14
+ export function injectStyles() {
15
+ if (typeof document === "undefined" || document.getElementById("drm3al-css"))
16
+ return;
17
+ const s = document.createElement("style");
18
+ s.id = "drm3al-css";
19
+ s.textContent = `
20
+ .drm3al{position:relative;display:inline-block;font-family:ui-sans-serif,system-ui,sans-serif}
21
+ .drm3al-btn{position:relative;display:grid;place-items:center;width:38px;height:38px;border:0;border-radius:10px;
22
+ background:transparent;color:inherit;cursor:pointer;opacity:.8}
23
+ .drm3al-btn:hover{opacity:1;background:rgba(127,127,127,.12)}
24
+ .drm3al-btn svg{width:20px;height:20px}
25
+ .drm3al-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;
26
+ background:#dc2626;color:#fff;font-size:10px;font-weight:700;line-height:16px;text-align:center;pointer-events:none}
27
+ .drm3al-panel{position:absolute;right:0;top:46px;width:340px;max-height:70vh;overflow-y:auto;z-index:9999;
28
+ background:#0f1318;border:1px solid #1c2530;border-radius:14px;box-shadow:0 18px 50px rgba(0,0,0,.5);
29
+ display:none;color:#f2f6fa;text-align:left}
30
+ .drm3al[data-open="1"] .drm3al-panel{display:block}
31
+ .drm3al-h{padding:12px 14px 6px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#9fb1c4}
32
+ .drm3al-list{padding:0 6px 6px}
33
+ .drm3al-row{display:flex;gap:10px;align-items:flex-start;padding:10px 8px;border-radius:10px;text-decoration:none;color:inherit}
34
+ .drm3al-row+.drm3al-row{margin-top:2px}
35
+ a.drm3al-row:hover{background:#161e28}
36
+ .drm3al-unread{background:rgba(96,165,250,.08)}
37
+ .drm3al-dot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:5px}
38
+ .drm3al-c{flex:1;min-width:0}
39
+ .drm3al-t{font-size:13px;font-weight:600;color:#f2f6fa;line-height:1.35;word-break:break-word}
40
+ .drm3al-b{font-size:12px;color:#9fb1c4;line-height:1.45;margin-top:2px;word-break:break-word}
41
+ .drm3al-ago{font-size:11px;color:#9fb1c4;margin-top:3px}
42
+ .drm3al-empty{padding:18px 14px 22px;color:#9fb1c4;font-size:12px}
43
+ .drm3al-foot{padding:10px 14px;border-top:1px solid #1c2530}
44
+ .drm3al-foot a{color:#7aa7d6;font-size:12px;text-decoration:none}
45
+ .drm3al-foot a:hover{text-decoration:underline}
46
+ @media (max-width:479px){.drm3al-panel{position:fixed;left:10px;right:10px;top:64px;width:auto;max-height:calc(100vh - 84px)}}`;
47
+ document.head.appendChild(s);
48
+ }
49
+ //# sourceMappingURL=alerts-styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts-styles.js","sourceRoot":"","sources":["../src/alerts-styles.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,gFAAgF;AAChF,+CAA+C;AAE/C,kFAAkF;AAClF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,SAAS,GAA2B;IAC/C,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,SAAS;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,qPAAqP,CAAC;AAE9Q,yBAAyB;AACzB,MAAM,UAAU,YAAY;IAC1B,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,QAAQ,CAAC,cAAc,CAAC,YAAY,CAAC;QAAE,OAAO;IACrF,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC,CAAC,EAAE,GAAG,YAAY,CAAC;IACpB,CAAC,CAAC,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;+HA2B6G,CAAC;IAC9H,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { type AlertItem, type AlertsOptions, fetchAlerts } from "./alerts-fetch.js";
2
+ export { mountAlertsBell } from "./alerts-bell.js";
3
+ //# sourceMappingURL=alerts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts.d.ts","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}