@abraca/wiki 2.27.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.
@@ -0,0 +1,1418 @@
1
+ #!/usr/bin/env node
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3
+ //#region \0rolldown/runtime.js
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
13
+ key = keys[i];
14
+ if (!__hasOwnProp.call(to, key) && key !== except) {
15
+ __defProp(to, key, {
16
+ get: ((k) => from[k]).bind(null, key),
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ }
20
+ }
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
25
+ value: mod,
26
+ enumerable: true
27
+ }) : target, mod));
28
+
29
+ //#endregion
30
+ let wtf_wikipedia = require("wtf_wikipedia");
31
+ wtf_wikipedia = __toESM(wtf_wikipedia);
32
+ let wtf_plugin_api = require("wtf-plugin-api");
33
+ wtf_plugin_api = __toESM(wtf_plugin_api);
34
+ let _abraca_dabra = require("@abraca/dabra");
35
+ let _noble_ed25519 = require("@noble/ed25519");
36
+ _noble_ed25519 = __toESM(_noble_ed25519);
37
+ let node_fs_promises = require("node:fs/promises");
38
+ let node_fs = require("node:fs");
39
+ let node_os = require("node:os");
40
+ let node_path = require("node:path");
41
+
42
+ //#region packages/wiki/src/parser.ts
43
+ /**
44
+ * Parse CLI arguments into a structured object.
45
+ * @param argv Raw `process.argv` (includes node path and script path).
46
+ */
47
+ function parseArgs(argv) {
48
+ const args = argv.slice(2);
49
+ const result = {
50
+ positional: [],
51
+ params: {},
52
+ flags: /* @__PURE__ */ new Set()
53
+ };
54
+ for (let i = 0; i < args.length; i++) {
55
+ const arg = args[i];
56
+ if (arg.startsWith("--")) {
57
+ const stripped = arg.slice(2);
58
+ const eqIdx = stripped.indexOf("=");
59
+ if (eqIdx !== -1) result.params[stripped.slice(0, eqIdx)] = stripped.slice(eqIdx + 1);
60
+ else result.flags.add(stripped);
61
+ continue;
62
+ }
63
+ const eqIdx = arg.indexOf("=");
64
+ if (eqIdx > 0) {
65
+ const key = arg.slice(0, eqIdx);
66
+ let value = arg.slice(eqIdx + 1);
67
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
68
+ result.params[key] = value;
69
+ continue;
70
+ }
71
+ result.positional.push(arg);
72
+ }
73
+ return result;
74
+ }
75
+
76
+ //#endregion
77
+ //#region packages/wiki/src/wikipedia.ts
78
+ /**
79
+ * Rate-limited wrapper around wtf_wikipedia + wtf-plugin-api.
80
+ *
81
+ * Responsibilities:
82
+ * - Throttle requests to respect Wikimedia API etiquette
83
+ * - Cache parsed Documents by canonical title
84
+ * - Resolve redirects so callers always see the redirect target
85
+ * - Expose getCategoryPages via wtf-plugin-api
86
+ */
87
+ let pluginExtended = false;
88
+ function ensurePlugin() {
89
+ if (pluginExtended) return;
90
+ wtf_wikipedia.default.extend(wtf_plugin_api.default);
91
+ pluginExtended = true;
92
+ }
93
+ /** A token-bucket-ish throttle: at most `rate` calls per second, FIFO. */
94
+ var RateLimiter = class {
95
+ lastTickMs = 0;
96
+ constructor(intervalMs) {
97
+ this.intervalMs = intervalMs;
98
+ }
99
+ async wait() {
100
+ const now = Date.now();
101
+ const earliest = this.lastTickMs + this.intervalMs;
102
+ if (now < earliest) await new Promise((r) => setTimeout(r, earliest - now));
103
+ this.lastTickMs = Math.max(now, earliest);
104
+ }
105
+ };
106
+ var WikipediaClient = class {
107
+ cache = /* @__PURE__ */ new Map();
108
+ redirects = /* @__PURE__ */ new Map();
109
+ limiter;
110
+ fetchOpts;
111
+ constructor(config) {
112
+ this.config = config;
113
+ ensurePlugin();
114
+ this.limiter = new RateLimiter(Math.max(50, Math.floor(1e3 / Math.max(.1, config.rate))));
115
+ this.fetchOpts = {
116
+ lang: config.lang,
117
+ "Api-User-Agent": config.userAgent,
118
+ follow_redirects: true
119
+ };
120
+ if (config.domain) this.fetchOpts.domain = config.domain;
121
+ }
122
+ /**
123
+ * Fetch and parse a Wikipedia article.
124
+ * - Returns the cached Document if we've seen this title before.
125
+ * - Follows redirects and caches under both source and target titles.
126
+ * - Returns null when the page does not exist.
127
+ */
128
+ async fetchArticle(rawTitle) {
129
+ const title = canonicalTitle(rawTitle);
130
+ if (this.cache.has(title)) return this.cache.get(title);
131
+ if (this.redirects.has(title)) {
132
+ const target = this.redirects.get(title);
133
+ return this.cache.get(target) ?? null;
134
+ }
135
+ await this.limiter.wait();
136
+ let doc;
137
+ try {
138
+ doc = await wtf_wikipedia.default.fetch(title, this.fetchOpts);
139
+ } catch (err) {
140
+ throw new Error(`Wikipedia fetch failed for "${title}": ${err?.message ?? err}`);
141
+ }
142
+ if (!doc) return null;
143
+ if (typeof doc.isRedirect === "function" && doc.isRedirect()) {
144
+ const target = doc.redirectTo?.()?.page;
145
+ if (typeof target === "string") {
146
+ this.redirects.set(title, canonicalTitle(target));
147
+ return await this.fetchArticle(target);
148
+ }
149
+ }
150
+ const resolvedTitle = canonicalTitle(doc.title?.() ?? title);
151
+ this.cache.set(resolvedTitle, doc);
152
+ if (resolvedTitle !== title) this.redirects.set(title, resolvedTitle);
153
+ return doc;
154
+ }
155
+ /**
156
+ * Fetch the member pages of a category (and optionally sub-categories).
157
+ * @param category Category title (with or without "Category:" prefix).
158
+ * @param recursive Whether to traverse sub-categories.
159
+ * @param maxDepth Recursion depth when recursive=true.
160
+ */
161
+ async fetchCategoryPages(category, recursive, maxDepth) {
162
+ await this.limiter.wait();
163
+ const opts = {
164
+ lang: this.config.lang,
165
+ "Api-User-Agent": this.config.userAgent,
166
+ recursive,
167
+ maxDepth
168
+ };
169
+ if (this.config.domain) opts.domain = this.config.domain;
170
+ return (await wtf_wikipedia.default.getCategoryPages(category, opts) ?? []).map((m) => ({
171
+ title: canonicalTitle(m.title),
172
+ type: m.type === "subcat" ? "subcat" : "page"
173
+ }));
174
+ }
175
+ };
176
+ /** Normalize a Wikipedia title — trim, collapse spaces, strip leading/trailing colons. */
177
+ function canonicalTitle(s) {
178
+ return (s ?? "").toString().replace(/_/g, " ").replace(/\s+/g, " ").trim();
179
+ }
180
+ /** Detect a category-namespaced title. */
181
+ const CATEGORY_PREFIX = /^(Category|Catégorie|Kategorie|Categoría|Categoria|Categorie|Kategoria):/i;
182
+ function isCategoryTitle(title) {
183
+ return CATEGORY_PREFIX.test(title);
184
+ }
185
+ /** Strip the "Category:" prefix for display. */
186
+ function stripCategoryPrefix(title) {
187
+ return title.replace(CATEGORY_PREFIX, "").trim();
188
+ }
189
+
190
+ //#endregion
191
+ //#region packages/wiki/src/snapshot.ts
192
+ function snapshotArticle(doc, title) {
193
+ return {
194
+ title,
195
+ linkTitles: collectLinkTitles(doc),
196
+ categories: collectCategories(doc),
197
+ sections: snapshotSections(doc.sections?.() ?? []),
198
+ infobox: snapshotInfobox(doc.infobox?.()),
199
+ lead: leadParagraph(doc),
200
+ url: typeof doc.url === "function" ? doc.url() : null
201
+ };
202
+ }
203
+ function prettyCategoryLabel(catTitle) {
204
+ return stripCategoryPrefix(catTitle);
205
+ }
206
+ function collectLinkTitles(doc) {
207
+ const links = doc.links?.() ?? [];
208
+ const out = /* @__PURE__ */ new Set();
209
+ for (const l of links) {
210
+ if (!l) continue;
211
+ const page = typeof l.page === "function" ? l.page() : null;
212
+ if (typeof page !== "string" || page.length === 0) continue;
213
+ if (isCategoryTitle(page)) continue;
214
+ out.add(canonicalTitle(page));
215
+ }
216
+ return [...out];
217
+ }
218
+ function collectCategories(doc) {
219
+ const out = [];
220
+ for (const c of doc.categories?.() ?? []) {
221
+ const norm = canonicalTitle(c);
222
+ if (norm) out.push(norm);
223
+ }
224
+ return out;
225
+ }
226
+ function snapshotSections(rawSections) {
227
+ const all = rawSections.map((s) => ({
228
+ raw: s,
229
+ title: s.title?.() || "",
230
+ parentRef: typeof s.parent === "function" ? s.parent() : null,
231
+ children: []
232
+ }));
233
+ const byRaw = /* @__PURE__ */ new Map();
234
+ for (const s of all) byRaw.set(s.raw, s);
235
+ const roots = [];
236
+ for (const s of all) if (s.parentRef && byRaw.has(s.parentRef)) byRaw.get(s.parentRef).children.push(materialize(s));
237
+ else roots.push(s);
238
+ return roots.map(materialize);
239
+ }
240
+ function materialize(node) {
241
+ const lists = node.raw.lists?.() ?? [];
242
+ const paragraphs = node.raw.paragraphs?.() ?? [];
243
+ let listLength = 0;
244
+ for (const l of lists) {
245
+ const lines = l.lines?.() ?? [];
246
+ listLength += lines.length;
247
+ }
248
+ const isList = lists.length > 0 && (paragraphs.length === 0 || listLength >= paragraphs.length * 2);
249
+ const bodyParts = [];
250
+ for (const p of paragraphs) {
251
+ const md = paragraphMarkdown(p);
252
+ if (md) bodyParts.push(md);
253
+ }
254
+ for (const l of lists) {
255
+ const lines = l.lines?.() ?? [];
256
+ for (const line of lines) {
257
+ const text = lineText(line);
258
+ if (text) bodyParts.push(`- ${text}`);
259
+ }
260
+ }
261
+ return {
262
+ title: node.title,
263
+ body: bodyParts.join("\n\n"),
264
+ isList,
265
+ listLength,
266
+ children: node.children
267
+ };
268
+ }
269
+ function snapshotInfobox(box) {
270
+ if (!box) return void 0;
271
+ const data = typeof box.json === "function" ? box.json() : null;
272
+ if (!data || typeof data !== "object") return void 0;
273
+ const rows = [];
274
+ for (const [key, val] of Object.entries(data)) {
275
+ const value = stringifyInfoboxValue(val);
276
+ if (!value) continue;
277
+ rows.push({
278
+ key: humanKey(key),
279
+ value
280
+ });
281
+ }
282
+ return rows.length > 0 ? rows : void 0;
283
+ }
284
+ function stringifyInfoboxValue(val) {
285
+ if (val == null) return "";
286
+ if (typeof val === "string") return val;
287
+ if (typeof val === "number" || typeof val === "boolean") return String(val);
288
+ if (Array.isArray(val)) return val.map(stringifyInfoboxValue).filter(Boolean).join(", ");
289
+ if (typeof val === "object") {
290
+ const o = val;
291
+ if (typeof o.text === "string") return o.text;
292
+ if (typeof o.number === "number") return String(o.number);
293
+ }
294
+ return "";
295
+ }
296
+ function humanKey(k) {
297
+ return k.replace(/_/g, " ").replace(/^./, (m) => m.toUpperCase());
298
+ }
299
+ function leadParagraph(doc) {
300
+ const first = (doc.paragraphs?.() ?? [])[0];
301
+ if (!first) return "";
302
+ return paragraphMarkdown(first);
303
+ }
304
+ /**
305
+ * Render a paragraph as markdown, replacing internal links with `[[Title]]`.
306
+ * The streaming orchestrator's link rewriter later swaps `[[Title]]` →
307
+ * `[[docId|label]]` once IDs are known.
308
+ */
309
+ function paragraphMarkdown(paragraph) {
310
+ const sentences = paragraph.sentences?.() ?? [];
311
+ const out = [];
312
+ for (const s of sentences) out.push(sentenceWithWikilinks(s));
313
+ return out.join(" ").trim();
314
+ }
315
+ function sentenceWithWikilinks(sentence) {
316
+ const text = (sentence.text?.() ?? "").toString();
317
+ const links = sentence.links?.() ?? [];
318
+ if (links.length === 0) return text;
319
+ let result = text;
320
+ const replacements = links.map((l) => {
321
+ const page = typeof l.page === "function" ? l.page() : null;
322
+ const display = typeof l.text === "function" ? l.text() : null;
323
+ if (typeof page !== "string" || page.length === 0) return null;
324
+ if (isCategoryTitle(page)) return null;
325
+ const shown = display && display.length > 0 ? display : page;
326
+ return {
327
+ page: canonicalTitle(page),
328
+ shown
329
+ };
330
+ }).filter((x) => x !== null).sort((a, b) => b.shown.length - a.shown.length);
331
+ for (const { page, shown } of replacements) {
332
+ if (!result.includes(shown)) continue;
333
+ const replacement = shown === page ? `[[${page}]]` : `[[${page}|${shown}]]`;
334
+ result = result.replace(shown, replacement);
335
+ }
336
+ return result;
337
+ }
338
+ function lineText(line) {
339
+ if (!line) return "";
340
+ if (typeof line === "string") return line;
341
+ if (typeof line.text === "string") return line.text;
342
+ if (typeof line.text === "function") return line.text();
343
+ return "";
344
+ }
345
+
346
+ //#endregion
347
+ //#region packages/wiki/src/render.ts
348
+ const ICONS = {
349
+ graph: "git-fork",
350
+ article: "book-open",
351
+ category: "tag",
352
+ infobox: "info",
353
+ outline: "list",
354
+ gallery: "images",
355
+ section: "pilcrow",
356
+ categories: "tags"
357
+ };
358
+ /** Decide a page type for a section based on its shape. */
359
+ function pickSectionType(section) {
360
+ if (section.children.length > 0) return {
361
+ type: "outline",
362
+ icon: ICONS.outline
363
+ };
364
+ if (section.isList && section.listLength >= 5) return {
365
+ type: "outline",
366
+ icon: ICONS.outline
367
+ };
368
+ return {
369
+ type: "doc",
370
+ icon: ICONS.section
371
+ };
372
+ }
373
+ /** Render the lead paragraph as the article-doc body. */
374
+ function renderArticleLead(article) {
375
+ return article.lead ?? "";
376
+ }
377
+ /** Render the article as a single doc, sections + infobox inlined. */
378
+ function renderArticleSingleDoc(article) {
379
+ const parts = [];
380
+ if (article.lead) parts.push(article.lead);
381
+ if (article.infobox && article.infobox.length > 0) parts.push("## Infobox", renderInfoboxBody(article.infobox));
382
+ for (const section of article.sections) parts.push(...renderSectionInline(section, 2));
383
+ return parts.join("\n\n");
384
+ }
385
+ function renderSectionInline(section, level) {
386
+ const out = [];
387
+ const prefix = "#".repeat(Math.min(6, level));
388
+ if (section.title) out.push(`${prefix} ${section.title}`);
389
+ if (section.body.trim()) out.push(section.body);
390
+ for (const child of section.children) out.push(...renderSectionInline(child, level + 1));
391
+ return out;
392
+ }
393
+ function renderInfoboxBody(rows) {
394
+ return rows.map((r) => `- **${r.key}:** ${r.value}`).join("\n");
395
+ }
396
+ function renderCategoryBody(members, subcategories) {
397
+ const parts = [];
398
+ if (members.length > 0) {
399
+ parts.push("## Pages");
400
+ parts.push(members.map((m) => `- [[${m}]]`).join("\n"));
401
+ }
402
+ if (subcategories.length > 0) {
403
+ parts.push("## Sub-categories");
404
+ parts.push(subcategories.map((s) => `- ${s}`).join("\n"));
405
+ }
406
+ return parts.join("\n\n");
407
+ }
408
+ /**
409
+ * Replace `[[Title]]` / `[[Title|Alias]]` in markdown with
410
+ * `[[docId|label]]` using the title→docId map. Unresolved titles fall
411
+ * back to plain text (their alias or original title).
412
+ */
413
+ function rewriteLinks(markdown, titleToDocId) {
414
+ return markdown.replace(/\[\[([^\]|]+?)(?:\|([^\]]+?))?\]\]/g, (_match, target, alias) => {
415
+ const title = target.trim();
416
+ const docId = titleToDocId.get(title);
417
+ const display = (alias && alias.trim().length > 0 ? alias : title).trim();
418
+ if (!docId) return display;
419
+ return `[[${docId}|${display}]]`;
420
+ });
421
+ }
422
+
423
+ //#endregion
424
+ //#region node_modules/@noble/hashes/utils.js
425
+ /**
426
+ * Checks if something is Uint8Array. Be careful: nodejs Buffer will return true.
427
+ * @param a - value to test
428
+ * @returns `true` when the value is a Uint8Array-compatible view.
429
+ * @example
430
+ * Check whether a value is a Uint8Array-compatible view.
431
+ * ```ts
432
+ * isBytes(new Uint8Array([1, 2, 3]));
433
+ * ```
434
+ */
435
+ function isBytes(a) {
436
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
437
+ }
438
+ /**
439
+ * Asserts something is Uint8Array.
440
+ * @param value - value to validate
441
+ * @param length - optional exact length constraint
442
+ * @param title - label included in thrown errors
443
+ * @returns The validated byte array.
444
+ * @throws On wrong argument types. {@link TypeError}
445
+ * @throws On wrong argument ranges or values. {@link RangeError}
446
+ * @example
447
+ * Validate that a value is a byte array.
448
+ * ```ts
449
+ * abytes(new Uint8Array([1, 2, 3]));
450
+ * ```
451
+ */
452
+ function abytes(value, length, title = "") {
453
+ const bytes = isBytes(value);
454
+ const len = value?.length;
455
+ const needsLen = length !== void 0;
456
+ if (!bytes || needsLen && len !== length) {
457
+ const prefix = title && `"${title}" `;
458
+ const ofLen = needsLen ? ` of length ${length}` : "";
459
+ const got = bytes ? `length=${len}` : `type=${typeof value}`;
460
+ const message = prefix + "expected Uint8Array" + ofLen + ", got " + got;
461
+ if (!bytes) throw new TypeError(message);
462
+ throw new RangeError(message);
463
+ }
464
+ return value;
465
+ }
466
+ /**
467
+ * Asserts a hash instance has not been destroyed or finished.
468
+ * @param instance - hash instance to validate
469
+ * @param checkFinished - whether to reject finalized instances
470
+ * @throws If the hash instance has already been destroyed or finalized. {@link Error}
471
+ * @example
472
+ * Validate that a hash instance is still usable.
473
+ * ```ts
474
+ * import { aexists } from '@noble/hashes/utils.js';
475
+ * import { sha256 } from '@noble/hashes/sha2.js';
476
+ * const hash = sha256.create();
477
+ * aexists(hash);
478
+ * ```
479
+ */
480
+ function aexists(instance, checkFinished = true) {
481
+ if (instance.destroyed) throw new Error("Hash instance has been destroyed");
482
+ if (checkFinished && instance.finished) throw new Error("Hash#digest() has already been called");
483
+ }
484
+ /**
485
+ * Asserts output is a sufficiently-sized byte array.
486
+ * @param out - destination buffer
487
+ * @param instance - hash instance providing output length
488
+ * Oversized buffers are allowed; downstream code only promises to fill the first `outputLen` bytes.
489
+ * @throws On wrong argument types. {@link TypeError}
490
+ * @throws On wrong argument ranges or values. {@link RangeError}
491
+ * @example
492
+ * Validate a caller-provided digest buffer.
493
+ * ```ts
494
+ * import { aoutput } from '@noble/hashes/utils.js';
495
+ * import { sha256 } from '@noble/hashes/sha2.js';
496
+ * const hash = sha256.create();
497
+ * aoutput(new Uint8Array(hash.outputLen), hash);
498
+ * ```
499
+ */
500
+ function aoutput(out, instance) {
501
+ abytes(out, void 0, "digestInto() output");
502
+ const min = instance.outputLen;
503
+ if (out.length < min) throw new RangeError("\"digestInto() output\" expected to be of length >=" + min);
504
+ }
505
+ /**
506
+ * Zeroizes typed arrays in place. Warning: JS provides no guarantees.
507
+ * @param arrays - arrays to overwrite with zeros
508
+ * @example
509
+ * Zeroize sensitive buffers in place.
510
+ * ```ts
511
+ * clean(new Uint8Array([1, 2, 3]));
512
+ * ```
513
+ */
514
+ function clean(...arrays) {
515
+ for (let i = 0; i < arrays.length; i++) arrays[i].fill(0);
516
+ }
517
+ /**
518
+ * Creates a DataView for byte-level manipulation.
519
+ * @param arr - source typed array
520
+ * @returns DataView over the same buffer region.
521
+ * @example
522
+ * Create a DataView over an existing buffer.
523
+ * ```ts
524
+ * createView(new Uint8Array(4));
525
+ * ```
526
+ */
527
+ function createView(arr) {
528
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
529
+ }
530
+ /** Whether the current platform is little-endian. */
531
+ const isLE = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
532
+ const hasHexBuiltin = typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function";
533
+ /**
534
+ * Creates a callable hash function from a stateful class constructor.
535
+ * @param hashCons - hash constructor or factory
536
+ * @param info - optional metadata such as DER OID
537
+ * @returns Frozen callable hash wrapper with `.create()`.
538
+ * Wrapper construction eagerly calls `hashCons(undefined)` once to read
539
+ * `outputLen` / `blockLen`, so constructor side effects happen at module
540
+ * init time.
541
+ * @example
542
+ * Wrap a stateful hash constructor into a callable helper.
543
+ * ```ts
544
+ * import { createHasher } from '@noble/hashes/utils.js';
545
+ * import { sha256 } from '@noble/hashes/sha2.js';
546
+ * const wrapped = createHasher(sha256.create, { oid: sha256.oid });
547
+ * wrapped(new Uint8Array([1]));
548
+ * ```
549
+ */
550
+ function createHasher(hashCons, info = {}) {
551
+ const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
552
+ const tmp = hashCons(void 0);
553
+ hashC.outputLen = tmp.outputLen;
554
+ hashC.blockLen = tmp.blockLen;
555
+ hashC.canXOF = tmp.canXOF;
556
+ hashC.create = (opts) => hashCons(opts);
557
+ Object.assign(hashC, info);
558
+ return Object.freeze(hashC);
559
+ }
560
+ /**
561
+ * Creates OID metadata for NIST hashes with prefix `06 09 60 86 48 01 65 03 04 02`.
562
+ * @param suffix - final OID byte for the selected hash.
563
+ * The helper accepts any byte even though only the documented NIST hash
564
+ * suffixes are meaningful downstream.
565
+ * @returns Object containing the DER-encoded OID.
566
+ * @example
567
+ * Build OID metadata for a NIST hash.
568
+ * ```ts
569
+ * oidNist(0x01);
570
+ * ```
571
+ */
572
+ const oidNist = (suffix) => ({ oid: Uint8Array.from([
573
+ 6,
574
+ 9,
575
+ 96,
576
+ 134,
577
+ 72,
578
+ 1,
579
+ 101,
580
+ 3,
581
+ 4,
582
+ 2,
583
+ suffix
584
+ ]) });
585
+
586
+ //#endregion
587
+ //#region node_modules/@noble/hashes/_md.js
588
+ /**
589
+ * Internal Merkle-Damgard hash utils.
590
+ * @module
591
+ */
592
+ /**
593
+ * Merkle-Damgard hash construction base class.
594
+ * Could be used to create MD5, RIPEMD, SHA1, SHA2.
595
+ * Accepts only byte-aligned `Uint8Array` input, even when the underlying spec describes bit
596
+ * strings with partial-byte tails.
597
+ * @param blockLen - internal block size in bytes
598
+ * @param outputLen - digest size in bytes
599
+ * @param padOffset - trailing length field size in bytes
600
+ * @param isLE - whether length and state words are encoded in little-endian
601
+ * @example
602
+ * Use a concrete subclass to get the shared Merkle-Damgard update/digest flow.
603
+ * ```ts
604
+ * import { _SHA1 } from '@noble/hashes/legacy.js';
605
+ * const hash = new _SHA1();
606
+ * hash.update(new Uint8Array([97, 98, 99]));
607
+ * hash.digest();
608
+ * ```
609
+ */
610
+ var HashMD = class {
611
+ blockLen;
612
+ outputLen;
613
+ canXOF = false;
614
+ padOffset;
615
+ isLE;
616
+ buffer;
617
+ view;
618
+ finished = false;
619
+ length = 0;
620
+ pos = 0;
621
+ destroyed = false;
622
+ constructor(blockLen, outputLen, padOffset, isLE) {
623
+ this.blockLen = blockLen;
624
+ this.outputLen = outputLen;
625
+ this.padOffset = padOffset;
626
+ this.isLE = isLE;
627
+ this.buffer = new Uint8Array(blockLen);
628
+ this.view = createView(this.buffer);
629
+ }
630
+ update(data) {
631
+ aexists(this);
632
+ abytes(data);
633
+ const { view, buffer, blockLen } = this;
634
+ const len = data.length;
635
+ for (let pos = 0; pos < len;) {
636
+ const take = Math.min(blockLen - this.pos, len - pos);
637
+ if (take === blockLen) {
638
+ const dataView = createView(data);
639
+ for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
640
+ continue;
641
+ }
642
+ buffer.set(data.subarray(pos, pos + take), this.pos);
643
+ this.pos += take;
644
+ pos += take;
645
+ if (this.pos === blockLen) {
646
+ this.process(view, 0);
647
+ this.pos = 0;
648
+ }
649
+ }
650
+ this.length += data.length;
651
+ this.roundClean();
652
+ return this;
653
+ }
654
+ digestInto(out) {
655
+ aexists(this);
656
+ aoutput(out, this);
657
+ this.finished = true;
658
+ const { buffer, view, blockLen, isLE } = this;
659
+ let { pos } = this;
660
+ buffer[pos++] = 128;
661
+ clean(this.buffer.subarray(pos));
662
+ if (this.padOffset > blockLen - pos) {
663
+ this.process(view, 0);
664
+ pos = 0;
665
+ }
666
+ for (let i = pos; i < blockLen; i++) buffer[i] = 0;
667
+ view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);
668
+ this.process(view, 0);
669
+ const oview = createView(out);
670
+ const len = this.outputLen;
671
+ if (len % 4) throw new Error("_sha2: outputLen must be aligned to 32bit");
672
+ const outLen = len / 4;
673
+ const state = this.get();
674
+ if (outLen > state.length) throw new Error("_sha2: outputLen bigger than state");
675
+ for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
676
+ }
677
+ digest() {
678
+ const { buffer, outputLen } = this;
679
+ this.digestInto(buffer);
680
+ const res = buffer.slice(0, outputLen);
681
+ this.destroy();
682
+ return res;
683
+ }
684
+ _cloneInto(to) {
685
+ to ||= new this.constructor();
686
+ to.set(...this.get());
687
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
688
+ to.destroyed = destroyed;
689
+ to.finished = finished;
690
+ to.length = length;
691
+ to.pos = pos;
692
+ if (length % blockLen) to.buffer.set(buffer);
693
+ return to;
694
+ }
695
+ clone() {
696
+ return this._cloneInto();
697
+ }
698
+ };
699
+ /** Initial SHA512 state from RFC 6234 §6.3: eight RFC 64-bit `H(0)` words stored as sixteen
700
+ * big-endian 32-bit halves. Derived from the fractional parts of the square roots of the first
701
+ * eight prime numbers. Exported as a shared table; callers must treat it as read-only because
702
+ * constructors copy halves from it by index. */
703
+ const SHA512_IV = /* @__PURE__ */ Uint32Array.from([
704
+ 1779033703,
705
+ 4089235720,
706
+ 3144134277,
707
+ 2227873595,
708
+ 1013904242,
709
+ 4271175723,
710
+ 2773480762,
711
+ 1595750129,
712
+ 1359893119,
713
+ 2917565137,
714
+ 2600822924,
715
+ 725511199,
716
+ 528734635,
717
+ 4215389547,
718
+ 1541459225,
719
+ 327033209
720
+ ]);
721
+
722
+ //#endregion
723
+ //#region node_modules/@noble/hashes/_u64.js
724
+ const U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
725
+ const _32n = /* @__PURE__ */ BigInt(32);
726
+ function fromBig(n, le = false) {
727
+ if (le) return {
728
+ h: Number(n & U32_MASK64),
729
+ l: Number(n >> _32n & U32_MASK64)
730
+ };
731
+ return {
732
+ h: Number(n >> _32n & U32_MASK64) | 0,
733
+ l: Number(n & U32_MASK64) | 0
734
+ };
735
+ }
736
+ function split(lst, le = false) {
737
+ const len = lst.length;
738
+ let Ah = new Uint32Array(len);
739
+ let Al = new Uint32Array(len);
740
+ for (let i = 0; i < len; i++) {
741
+ const { h, l } = fromBig(lst[i], le);
742
+ [Ah[i], Al[i]] = [h, l];
743
+ }
744
+ return [Ah, Al];
745
+ }
746
+ const shrSH = (h, _l, s) => h >>> s;
747
+ const shrSL = (h, l, s) => h << 32 - s | l >>> s;
748
+ const rotrSH = (h, l, s) => h >>> s | l << 32 - s;
749
+ const rotrSL = (h, l, s) => h << 32 - s | l >>> s;
750
+ const rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32;
751
+ const rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s;
752
+ function add(Ah, Al, Bh, Bl) {
753
+ const l = (Al >>> 0) + (Bl >>> 0);
754
+ return {
755
+ h: Ah + Bh + (l / 2 ** 32 | 0) | 0,
756
+ l: l | 0
757
+ };
758
+ }
759
+ const add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
760
+ const add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
761
+ const add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);
762
+ const add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0;
763
+ const add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
764
+ const add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
765
+
766
+ //#endregion
767
+ //#region node_modules/@noble/hashes/sha2.js
768
+ /**
769
+ * SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.
770
+ * SHA256 is the fastest hash implementable in JS, even faster than Blake3.
771
+ * Check out {@link https://www.rfc-editor.org/rfc/rfc4634 | RFC 4634} and
772
+ * {@link https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf | FIPS 180-4}.
773
+ * @module
774
+ */
775
+ const K512 = split([
776
+ "0x428a2f98d728ae22",
777
+ "0x7137449123ef65cd",
778
+ "0xb5c0fbcfec4d3b2f",
779
+ "0xe9b5dba58189dbbc",
780
+ "0x3956c25bf348b538",
781
+ "0x59f111f1b605d019",
782
+ "0x923f82a4af194f9b",
783
+ "0xab1c5ed5da6d8118",
784
+ "0xd807aa98a3030242",
785
+ "0x12835b0145706fbe",
786
+ "0x243185be4ee4b28c",
787
+ "0x550c7dc3d5ffb4e2",
788
+ "0x72be5d74f27b896f",
789
+ "0x80deb1fe3b1696b1",
790
+ "0x9bdc06a725c71235",
791
+ "0xc19bf174cf692694",
792
+ "0xe49b69c19ef14ad2",
793
+ "0xefbe4786384f25e3",
794
+ "0x0fc19dc68b8cd5b5",
795
+ "0x240ca1cc77ac9c65",
796
+ "0x2de92c6f592b0275",
797
+ "0x4a7484aa6ea6e483",
798
+ "0x5cb0a9dcbd41fbd4",
799
+ "0x76f988da831153b5",
800
+ "0x983e5152ee66dfab",
801
+ "0xa831c66d2db43210",
802
+ "0xb00327c898fb213f",
803
+ "0xbf597fc7beef0ee4",
804
+ "0xc6e00bf33da88fc2",
805
+ "0xd5a79147930aa725",
806
+ "0x06ca6351e003826f",
807
+ "0x142929670a0e6e70",
808
+ "0x27b70a8546d22ffc",
809
+ "0x2e1b21385c26c926",
810
+ "0x4d2c6dfc5ac42aed",
811
+ "0x53380d139d95b3df",
812
+ "0x650a73548baf63de",
813
+ "0x766a0abb3c77b2a8",
814
+ "0x81c2c92e47edaee6",
815
+ "0x92722c851482353b",
816
+ "0xa2bfe8a14cf10364",
817
+ "0xa81a664bbc423001",
818
+ "0xc24b8b70d0f89791",
819
+ "0xc76c51a30654be30",
820
+ "0xd192e819d6ef5218",
821
+ "0xd69906245565a910",
822
+ "0xf40e35855771202a",
823
+ "0x106aa07032bbd1b8",
824
+ "0x19a4c116b8d2d0c8",
825
+ "0x1e376c085141ab53",
826
+ "0x2748774cdf8eeb99",
827
+ "0x34b0bcb5e19b48a8",
828
+ "0x391c0cb3c5c95a63",
829
+ "0x4ed8aa4ae3418acb",
830
+ "0x5b9cca4f7763e373",
831
+ "0x682e6ff3d6b2b8a3",
832
+ "0x748f82ee5defb2fc",
833
+ "0x78a5636f43172f60",
834
+ "0x84c87814a1f0ab72",
835
+ "0x8cc702081a6439ec",
836
+ "0x90befffa23631e28",
837
+ "0xa4506cebde82bde9",
838
+ "0xbef9a3f7b2c67915",
839
+ "0xc67178f2e372532b",
840
+ "0xca273eceea26619c",
841
+ "0xd186b8c721c0c207",
842
+ "0xeada7dd6cde0eb1e",
843
+ "0xf57d4f7fee6ed178",
844
+ "0x06f067aa72176fba",
845
+ "0x0a637dc5a2c898a6",
846
+ "0x113f9804bef90dae",
847
+ "0x1b710b35131c471b",
848
+ "0x28db77f523047d84",
849
+ "0x32caab7b40c72493",
850
+ "0x3c9ebe0a15c9bebc",
851
+ "0x431d67c49c100d4c",
852
+ "0x4cc5d4becb3e42b6",
853
+ "0x597f299cfc657e2a",
854
+ "0x5fcb6fab3ad6faec",
855
+ "0x6c44198c4a475817"
856
+ ].map((n) => BigInt(n)));
857
+ const SHA512_Kh = K512[0];
858
+ const SHA512_Kl = K512[1];
859
+ const SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
860
+ const SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
861
+ /** Internal SHA-384 / SHA-512 compression engine from RFC 6234 §6.4. */
862
+ var SHA2_64B = class extends HashMD {
863
+ constructor(outputLen) {
864
+ super(128, outputLen, 16, false);
865
+ }
866
+ get() {
867
+ const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
868
+ return [
869
+ Ah,
870
+ Al,
871
+ Bh,
872
+ Bl,
873
+ Ch,
874
+ Cl,
875
+ Dh,
876
+ Dl,
877
+ Eh,
878
+ El,
879
+ Fh,
880
+ Fl,
881
+ Gh,
882
+ Gl,
883
+ Hh,
884
+ Hl
885
+ ];
886
+ }
887
+ set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
888
+ this.Ah = Ah | 0;
889
+ this.Al = Al | 0;
890
+ this.Bh = Bh | 0;
891
+ this.Bl = Bl | 0;
892
+ this.Ch = Ch | 0;
893
+ this.Cl = Cl | 0;
894
+ this.Dh = Dh | 0;
895
+ this.Dl = Dl | 0;
896
+ this.Eh = Eh | 0;
897
+ this.El = El | 0;
898
+ this.Fh = Fh | 0;
899
+ this.Fl = Fl | 0;
900
+ this.Gh = Gh | 0;
901
+ this.Gl = Gl | 0;
902
+ this.Hh = Hh | 0;
903
+ this.Hl = Hl | 0;
904
+ }
905
+ process(view, offset) {
906
+ for (let i = 0; i < 16; i++, offset += 4) {
907
+ SHA512_W_H[i] = view.getUint32(offset);
908
+ SHA512_W_L[i] = view.getUint32(offset += 4);
909
+ }
910
+ for (let i = 16; i < 80; i++) {
911
+ const W15h = SHA512_W_H[i - 15] | 0;
912
+ const W15l = SHA512_W_L[i - 15] | 0;
913
+ const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
914
+ const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
915
+ const W2h = SHA512_W_H[i - 2] | 0;
916
+ const W2l = SHA512_W_L[i - 2] | 0;
917
+ const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
918
+ const s1l = rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6);
919
+ const SUMl = add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
920
+ SHA512_W_H[i] = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]) | 0;
921
+ SHA512_W_L[i] = SUMl | 0;
922
+ }
923
+ let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
924
+ for (let i = 0; i < 80; i++) {
925
+ const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
926
+ const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
927
+ const CHIh = Eh & Fh ^ ~Eh & Gh;
928
+ const CHIl = El & Fl ^ ~El & Gl;
929
+ const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
930
+ const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
931
+ const T1l = T1ll | 0;
932
+ const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
933
+ const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
934
+ const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
935
+ const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
936
+ Hh = Gh | 0;
937
+ Hl = Gl | 0;
938
+ Gh = Fh | 0;
939
+ Gl = Fl | 0;
940
+ Fh = Eh | 0;
941
+ Fl = El | 0;
942
+ ({h: Eh, l: El} = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
943
+ Dh = Ch | 0;
944
+ Dl = Cl | 0;
945
+ Ch = Bh | 0;
946
+ Cl = Bl | 0;
947
+ Bh = Ah | 0;
948
+ Bl = Al | 0;
949
+ const All = add3L(T1l, sigma0l, MAJl);
950
+ Ah = add3H(All, T1h, sigma0h, MAJh);
951
+ Al = All | 0;
952
+ }
953
+ ({h: Ah, l: Al} = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
954
+ ({h: Bh, l: Bl} = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
955
+ ({h: Ch, l: Cl} = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
956
+ ({h: Dh, l: Dl} = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
957
+ ({h: Eh, l: El} = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
958
+ ({h: Fh, l: Fl} = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
959
+ ({h: Gh, l: Gl} = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
960
+ ({h: Hh, l: Hl} = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
961
+ this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
962
+ }
963
+ roundClean() {
964
+ clean(SHA512_W_H, SHA512_W_L);
965
+ }
966
+ destroy() {
967
+ this.destroyed = true;
968
+ clean(this.buffer);
969
+ this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
970
+ }
971
+ };
972
+ /** Internal SHA-512 hash class grounded in RFC 6234 §6.3 and §6.4. */
973
+ var _SHA512 = class extends SHA2_64B {
974
+ Ah = SHA512_IV[0] | 0;
975
+ Al = SHA512_IV[1] | 0;
976
+ Bh = SHA512_IV[2] | 0;
977
+ Bl = SHA512_IV[3] | 0;
978
+ Ch = SHA512_IV[4] | 0;
979
+ Cl = SHA512_IV[5] | 0;
980
+ Dh = SHA512_IV[6] | 0;
981
+ Dl = SHA512_IV[7] | 0;
982
+ Eh = SHA512_IV[8] | 0;
983
+ El = SHA512_IV[9] | 0;
984
+ Fh = SHA512_IV[10] | 0;
985
+ Fl = SHA512_IV[11] | 0;
986
+ Gh = SHA512_IV[12] | 0;
987
+ Gl = SHA512_IV[13] | 0;
988
+ Hh = SHA512_IV[14] | 0;
989
+ Hl = SHA512_IV[15] | 0;
990
+ constructor() {
991
+ super(64);
992
+ }
993
+ };
994
+ /**
995
+ * SHA2-512 hash function from RFC 4634.
996
+ * @param msg - message bytes to hash
997
+ * @returns Digest bytes.
998
+ * @example
999
+ * Hash a message with SHA2-512.
1000
+ * ```ts
1001
+ * sha512(new Uint8Array([97, 98, 99]));
1002
+ * ```
1003
+ */
1004
+ const sha512 = /* @__PURE__ */ createHasher(() => new _SHA512(), /* @__PURE__ */ oidNist(3));
1005
+
1006
+ //#endregion
1007
+ //#region packages/wiki/src/crypto.ts
1008
+ /**
1009
+ * Ed25519 key generation, persistence, and challenge signing for CLI auth.
1010
+ * Mirrors @abraca/mcp/src/crypto.ts — standalone to avoid MCP SDK dependency.
1011
+ */
1012
+ _noble_ed25519.hashes.sha512 = sha512;
1013
+ _noble_ed25519.hashes.sha512Async = (m) => Promise.resolve(sha512(m));
1014
+ const DEFAULT_KEY_PATH = (0, node_path.join)((0, node_os.homedir)(), ".abracadabra", "cli.key");
1015
+ function toBase64url(bytes) {
1016
+ return Buffer.from(bytes).toString("base64url");
1017
+ }
1018
+ function fromBase64url(b64) {
1019
+ return new Uint8Array(Buffer.from(b64, "base64url"));
1020
+ }
1021
+ /**
1022
+ * Load an existing Ed25519 keypair from disk, or generate and persist a new one.
1023
+ * The file stores the raw 32-byte private key seed.
1024
+ */
1025
+ async function loadOrCreateKeypair(keyPath) {
1026
+ const path = keyPath || DEFAULT_KEY_PATH;
1027
+ if ((0, node_fs.existsSync)(path)) {
1028
+ const seed = await (0, node_fs_promises.readFile)(path);
1029
+ if (seed.length !== 32) throw new Error(`Invalid key file at ${path}: expected 32 bytes, got ${seed.length}`);
1030
+ const privateKey = new Uint8Array(seed);
1031
+ return {
1032
+ privateKey,
1033
+ publicKeyB64: toBase64url(_noble_ed25519.getPublicKey(privateKey))
1034
+ };
1035
+ }
1036
+ const privateKey = _noble_ed25519.utils.randomSecretKey();
1037
+ const publicKey = _noble_ed25519.getPublicKey(privateKey);
1038
+ const dir = (0, node_path.dirname)(path);
1039
+ if (!(0, node_fs.existsSync)(dir)) await (0, node_fs_promises.mkdir)(dir, {
1040
+ recursive: true,
1041
+ mode: 448
1042
+ });
1043
+ await (0, node_fs_promises.writeFile)(path, Buffer.from(privateKey), { mode: 384 });
1044
+ console.error(`[abracadabra] Generated new keypair at ${path}`);
1045
+ console.error(`[abracadabra] Public key: ${toBase64url(publicKey)}`);
1046
+ return {
1047
+ privateKey,
1048
+ publicKeyB64: toBase64url(publicKey)
1049
+ };
1050
+ }
1051
+ /** Sign a base64url challenge with the private key; returns base64url signature. */
1052
+ function signChallenge(challengeB64, privateKey) {
1053
+ const challenge = fromBase64url(challengeB64);
1054
+ return toBase64url(_noble_ed25519.sign(challenge, privateKey));
1055
+ }
1056
+
1057
+ //#endregion
1058
+ //#region packages/wiki/src/connect.ts
1059
+ /**
1060
+ * Open a DocumentManager session for the wiki command, mirroring the
1061
+ * auth/register flow that CLIConnection uses but using the modern public API.
1062
+ *
1063
+ * Reuses the CLI's Ed25519 keypair handling (loadOrCreateKeypair, signChallenge)
1064
+ * so the wiki command authenticates with the same identity as every other
1065
+ * subcommand.
1066
+ */
1067
+ async function openSession(config) {
1068
+ const keypair = await loadOrCreateKeypair(config.keyFile);
1069
+ const sign = (challenge) => Promise.resolve(signChallenge(challenge, keypair.privateKey));
1070
+ const dm = new _abraca_dabra.DocumentManager({
1071
+ url: config.url,
1072
+ name: config.name ?? "Wiki Extractor",
1073
+ color: config.color,
1074
+ quiet: config.quiet
1075
+ });
1076
+ try {
1077
+ await dm.client.loginWithKey(keypair.publicKeyB64, sign);
1078
+ } catch (err) {
1079
+ const status = err?.status ?? err?.response?.status;
1080
+ if (status === 404 || status === 422) {
1081
+ if (!config.quiet) console.error("[abracadabra] Key not registered, creating new account...");
1082
+ await dm.client.registerWithKey({
1083
+ publicKey: keypair.publicKeyB64,
1084
+ username: (config.name ?? "wiki-extractor").replace(/\s+/g, "-").toLowerCase(),
1085
+ displayName: config.name ?? "Wiki Extractor",
1086
+ deviceName: "CLI Wiki",
1087
+ inviteCode: config.inviteCode
1088
+ });
1089
+ await dm.client.loginWithKey(keypair.publicKeyB64, sign);
1090
+ } else throw err;
1091
+ }
1092
+ await dm.connect();
1093
+ const rootDocId = dm.rootDocId;
1094
+ if (!rootDocId) throw new Error("Connected but no rootDocId — server has no spaces.");
1095
+ return {
1096
+ dm,
1097
+ rootDocId
1098
+ };
1099
+ }
1100
+
1101
+ //#endregion
1102
+ //#region packages/wiki/src/index.ts
1103
+ const USAGE = [
1104
+ "abracadabra-wiki \"<Article Title>\" user-agent=\"<name (email)>\" [options]",
1105
+ "",
1106
+ "Options:",
1107
+ " mode=single|split single doc per article OR split into sections+infobox [split]",
1108
+ " depth=<n> follow internal links to depth N [1]",
1109
+ " category-depth=<n> recurse into sub-categories [1]",
1110
+ " lang=<code> wiki language [en]",
1111
+ " domain=<host> 3rd-party MediaWiki host (overrides lang)",
1112
+ " parent=<docId> parent doc for the new graph [active space root]",
1113
+ " user-agent=<str> Api-User-Agent header (REQUIRED by Wikimedia etiquette)",
1114
+ " rate=<rps> max wikipedia requests per second [3]",
1115
+ " --include-categories expand each article's categories into nested graphs",
1116
+ " --dry-run fetch only the entry article, print outline, no writes",
1117
+ "",
1118
+ "Environment: ABRA_URL (required unless --dry-run), ABRA_KEY_FILE, ABRA_NAME,",
1119
+ " ABRA_COLOR, ABRA_INVITE_CODE, ABRA_WIKI_USER_AGENT."
1120
+ ].join("\n");
1121
+ /**
1122
+ * Run a Wikipedia import for already-parsed args. Returns a human-readable
1123
+ * summary (or an error/usage string). Exported for programmatic use.
1124
+ */
1125
+ async function runWiki(args) {
1126
+ const opts = parseOptions(args);
1127
+ if (typeof opts === "string") return opts;
1128
+ const log = (msg) => {
1129
+ if (!args.flags.has("quiet") && !args.flags.has("q")) console.error(`[wiki] ${msg}`);
1130
+ };
1131
+ const wp = new WikipediaClient({
1132
+ lang: opts.lang,
1133
+ domain: opts.domain,
1134
+ userAgent: opts.userAgent,
1135
+ rate: opts.rate
1136
+ });
1137
+ if (opts.dryRun) {
1138
+ log(`fetch ${opts.title}`);
1139
+ const doc = await wp.fetchArticle(opts.title);
1140
+ if (!doc) return `Article not found: "${opts.title}"`;
1141
+ const snap = snapshotArticle(doc, canonicalTitle(doc.title?.() ?? opts.title));
1142
+ return [
1143
+ `Entry: ${snap.title}`,
1144
+ `URL: ${snap.url ?? "(none)"}`,
1145
+ `Internal links: ${snap.linkTitles.length}`,
1146
+ `Categories: ${snap.categories.length}`,
1147
+ `Sections: ${snap.sections.length}`,
1148
+ `Has infobox: ${snap.infobox && snap.infobox.length > 0 ? "yes" : "no"}`,
1149
+ "",
1150
+ "── Sections ──",
1151
+ printSections(snap.sections, "")
1152
+ ].join("\n");
1153
+ }
1154
+ const env = globalThis.process?.env ?? {};
1155
+ const url = env["ABRA_URL"];
1156
+ if (!url) return "ABRA_URL is required to write to the server. Set it or pass --dry-run.";
1157
+ const { dm } = await openSession({
1158
+ url,
1159
+ name: env["ABRA_NAME"],
1160
+ color: env["ABRA_COLOR"],
1161
+ inviteCode: env["ABRA_INVITE_CODE"],
1162
+ keyFile: env["ABRA_KEY_FILE"],
1163
+ quiet: args.flags.has("quiet") || args.flags.has("q")
1164
+ });
1165
+ try {
1166
+ const result = await runStreaming(dm, wp, opts, log);
1167
+ return [`Done. Created ${result.articleCount} articles${result.categoryCount > 0 ? ` + ${result.categoryCount} categories` : ""}.`, `Root: ${result.rootDocId}`].join("\n");
1168
+ } finally {
1169
+ await dm.destroy().catch(() => {});
1170
+ }
1171
+ }
1172
+ async function runStreaming(dm, wp, opts, log) {
1173
+ const titleToDocId = /* @__PURE__ */ new Map();
1174
+ const fetched = /* @__PURE__ */ new Map();
1175
+ const childrenCreated = /* @__PURE__ */ new Set();
1176
+ const categoryToDocId = /* @__PURE__ */ new Map();
1177
+ let categoriesContainerId = null;
1178
+ log(`fetch ${opts.title}`);
1179
+ const entryDoc = await wp.fetchArticle(opts.title);
1180
+ if (!entryDoc) throw new Error(`Article not found: "${opts.title}"`);
1181
+ const entryTitle = canonicalTitle(entryDoc.title?.() ?? opts.title);
1182
+ const entrySnap = snapshotArticle(entryDoc, entryTitle);
1183
+ fetched.set(entryTitle, entrySnap);
1184
+ const rootEntry = dm.tree.create({
1185
+ parentId: opts.parentDocId ?? null,
1186
+ label: entryTitle,
1187
+ type: "graph",
1188
+ meta: { icon: ICONS.graph }
1189
+ });
1190
+ log(`+ ${rootEntry.id.slice(0, 8)}… ${entryTitle} (graph)`);
1191
+ const entryArticleId = createArticleShell(dm, entrySnap, rootEntry.id, log);
1192
+ titleToDocId.set(entryTitle, entryArticleId);
1193
+ const queue = [{
1194
+ title: entryTitle,
1195
+ depth: 0
1196
+ }];
1197
+ let articleCount = 0;
1198
+ while (queue.length > 0) {
1199
+ const { title, depth } = queue.shift();
1200
+ const articleDocId = titleToDocId.get(title);
1201
+ let snap = fetched.get(title);
1202
+ if (!snap) {
1203
+ log(`fetch [d${depth}] ${title}`);
1204
+ try {
1205
+ const doc = await wp.fetchArticle(title);
1206
+ if (!doc) {
1207
+ log(` not found — leaving stub`);
1208
+ continue;
1209
+ }
1210
+ snap = snapshotArticle(doc, canonicalTitle(doc.title?.() ?? title));
1211
+ fetched.set(title, snap);
1212
+ } catch (err) {
1213
+ log(`! fetch failed: ${err?.message ?? err}`);
1214
+ continue;
1215
+ }
1216
+ }
1217
+ if (opts.mode === "split" && !childrenCreated.has(title)) {
1218
+ createArticleChildren(dm, snap, articleDocId, log);
1219
+ childrenCreated.add(title);
1220
+ }
1221
+ if (depth < opts.depth) for (const linkTitle of snap.linkTitles) {
1222
+ if (titleToDocId.has(linkTitle)) continue;
1223
+ const shell = dm.tree.create({
1224
+ parentId: rootEntry.id,
1225
+ label: linkTitle,
1226
+ type: "doc",
1227
+ meta: { icon: ICONS.article }
1228
+ });
1229
+ titleToDocId.set(linkTitle, shell.id);
1230
+ queue.push({
1231
+ title: linkTitle,
1232
+ depth: depth + 1
1233
+ });
1234
+ log(`+ ${shell.id.slice(0, 8)}… ${linkTitle} (doc, shell)`);
1235
+ }
1236
+ if (opts.includeCategories && snap.categories.length > 0) {
1237
+ if (!categoriesContainerId) {
1238
+ const c = dm.tree.create({
1239
+ parentId: rootEntry.id,
1240
+ label: "Categories",
1241
+ type: "graph",
1242
+ meta: { icon: ICONS.categories }
1243
+ });
1244
+ categoriesContainerId = c.id;
1245
+ log(`+ ${c.id.slice(0, 8)}… Categories (graph)`);
1246
+ }
1247
+ for (const catTitle of snap.categories) {
1248
+ if (categoryToDocId.has(catTitle)) continue;
1249
+ const cat = dm.tree.create({
1250
+ parentId: categoriesContainerId,
1251
+ label: prettyCategoryLabel(catTitle),
1252
+ type: "graph",
1253
+ meta: { icon: ICONS.category }
1254
+ });
1255
+ categoryToDocId.set(catTitle, cat.id);
1256
+ log(`+ ${cat.id.slice(0, 8)}… ${prettyCategoryLabel(catTitle)} (graph, cat)`);
1257
+ }
1258
+ }
1259
+ const body = opts.mode === "split" ? renderArticleLead(snap) : renderArticleSingleDoc(snap);
1260
+ if (body.trim().length > 0) {
1261
+ const rewritten = rewriteLinks(body, titleToDocId);
1262
+ try {
1263
+ await dm.content.write(articleDocId, rewritten);
1264
+ log(`✓ body ${title}`);
1265
+ } catch (err) {
1266
+ log(`! body write failed for ${title}: ${err?.message ?? err}`);
1267
+ }
1268
+ }
1269
+ if (opts.mode === "split") await writeChildrenBodies(dm, snap, articleDocId, titleToDocId, log);
1270
+ articleCount++;
1271
+ }
1272
+ let categoryCount = 0;
1273
+ if (opts.includeCategories && categoryToDocId.size > 0) for (const [catTitle, catDocId] of categoryToDocId) {
1274
+ log(`category ${catTitle}`);
1275
+ try {
1276
+ const members = await wp.fetchCategoryPages(catTitle, opts.categoryDepth > 0, Math.max(0, opts.categoryDepth));
1277
+ const memberArticles = [];
1278
+ const subcats = [];
1279
+ for (const m of members) if (m.type === "subcat") subcats.push(prettyCategoryLabel(m.title));
1280
+ else memberArticles.push(m.title);
1281
+ const rewritten = rewriteLinks(renderCategoryBody(memberArticles, subcats), titleToDocId);
1282
+ if (rewritten.trim().length > 0) {
1283
+ await dm.content.write(catDocId, rewritten);
1284
+ log(`✓ body category ${catTitle}`);
1285
+ }
1286
+ categoryCount++;
1287
+ } catch (err) {
1288
+ log(`! category ${catTitle}: ${err?.message ?? err}`);
1289
+ }
1290
+ }
1291
+ return {
1292
+ rootDocId: rootEntry.id,
1293
+ articleCount,
1294
+ categoryCount
1295
+ };
1296
+ }
1297
+ function createArticleShell(dm, article, parentId, log) {
1298
+ const meta = { icon: ICONS.article };
1299
+ if (article.url) meta.url = article.url;
1300
+ const entry = dm.tree.create({
1301
+ parentId,
1302
+ label: article.title,
1303
+ type: "doc",
1304
+ meta
1305
+ });
1306
+ log(`+ ${entry.id.slice(0, 8)}… ${article.title} (doc)`);
1307
+ return entry.id;
1308
+ }
1309
+ /**
1310
+ * Create section + infobox child docs for a split-mode article. Returns nothing
1311
+ * — children get bodies written later in writeChildrenBodies.
1312
+ */
1313
+ function createArticleChildren(dm, article, articleDocId, log) {
1314
+ if (article.infobox && article.infobox.length > 0) {
1315
+ const ib = dm.tree.create({
1316
+ parentId: articleDocId,
1317
+ label: "Infobox",
1318
+ type: "outline",
1319
+ meta: { icon: ICONS.infobox }
1320
+ });
1321
+ log(` + ${ib.id.slice(0, 8)}… Infobox (outline)`);
1322
+ article._infoboxDocId = ib.id;
1323
+ }
1324
+ for (const section of article.sections) createSectionShell(dm, section, articleDocId, log);
1325
+ }
1326
+ function createSectionShell(dm, section, parentDocId, log) {
1327
+ const hasChildren = section.children.length > 0;
1328
+ if (!section.body.trim() && !hasChildren) return;
1329
+ const { type, icon } = pickSectionType(section);
1330
+ const entry = dm.tree.create({
1331
+ parentId: parentDocId,
1332
+ label: section.title || "Untitled section",
1333
+ type,
1334
+ meta: { icon }
1335
+ });
1336
+ log(` + ${entry.id.slice(0, 8)}… ${entry.label} (${type})`);
1337
+ section._docId = entry.id;
1338
+ for (const child of section.children) createSectionShell(dm, child, entry.id, log);
1339
+ }
1340
+ async function writeChildrenBodies(dm, article, _articleDocId, titleToDocId, log) {
1341
+ const infoboxDocId = article._infoboxDocId;
1342
+ if (infoboxDocId && article.infobox && article.infobox.length > 0) try {
1343
+ await dm.content.write(infoboxDocId, renderInfoboxBody(article.infobox));
1344
+ } catch (err) {
1345
+ log(`! infobox body write failed: ${err?.message ?? err}`);
1346
+ }
1347
+ for (const section of article.sections) await writeSectionBody(dm, section, titleToDocId, log);
1348
+ }
1349
+ async function writeSectionBody(dm, section, titleToDocId, log) {
1350
+ const docId = section._docId;
1351
+ if (docId && section.body.trim().length > 0) try {
1352
+ await dm.content.write(docId, rewriteLinks(section.body, titleToDocId));
1353
+ } catch (err) {
1354
+ log(`! section body write failed for ${section.title}: ${err?.message ?? err}`);
1355
+ }
1356
+ for (const child of section.children) await writeSectionBody(dm, child, titleToDocId, log);
1357
+ }
1358
+ function parseOptions(args) {
1359
+ const title = args.positional[0]?.trim() || args.params["title"];
1360
+ if (!title) return "Missing required positional argument: <title>. Example: abracadabra-wiki \"Toronto Raptors\"";
1361
+ const env = globalThis.process?.env ?? {};
1362
+ const userAgent = args.params["user-agent"] || args.params["userAgent"] || env["ABRA_WIKI_USER_AGENT"];
1363
+ if (!userAgent) return ["Missing required parameter: user-agent=\"your-name (you@example.com)\"", "(Wikimedia etiquette requires an Api-User-Agent header. Pass user-agent=... or set ABRA_WIKI_USER_AGENT.)"].join("\n");
1364
+ const mode = args.params["mode"] ?? "split";
1365
+ if (mode !== "single" && mode !== "split") return `Invalid mode "${mode}". Use mode=single or mode=split.`;
1366
+ const depth = parseIntOr(args.params["depth"], 1);
1367
+ const categoryDepth = parseIntOr(args.params["category-depth"] ?? args.params["categoryDepth"], 1);
1368
+ const rate = parseFloatOr(args.params["rate"], 3);
1369
+ return {
1370
+ title,
1371
+ mode,
1372
+ depth,
1373
+ categoryDepth,
1374
+ includeCategories: args.flags.has("include-categories") || args.flags.has("includeCategories"),
1375
+ lang: args.params["lang"] ?? "en",
1376
+ domain: args.params["domain"],
1377
+ parentDocId: args.params["parent"],
1378
+ userAgent,
1379
+ rate,
1380
+ dryRun: args.flags.has("dry-run") || args.flags.has("dryRun")
1381
+ };
1382
+ }
1383
+ function parseIntOr(s, fallback) {
1384
+ if (!s) return fallback;
1385
+ const n = Number.parseInt(s, 10);
1386
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
1387
+ }
1388
+ function parseFloatOr(s, fallback) {
1389
+ if (!s) return fallback;
1390
+ const n = Number.parseFloat(s);
1391
+ return Number.isFinite(n) && n > 0 ? n : fallback;
1392
+ }
1393
+ function printSections(sections, indent) {
1394
+ const lines = [];
1395
+ for (const s of sections) {
1396
+ const hint = s.body ? ` (${s.body.length}b)` : "";
1397
+ lines.push(`${indent}- ${s.title}${hint}${s.children.length > 0 ? ` [${s.children.length} sub]` : ""}`);
1398
+ if (s.children.length > 0) lines.push(printSections(s.children, indent + " "));
1399
+ }
1400
+ return lines.join("\n");
1401
+ }
1402
+ async function main() {
1403
+ const args = parseArgs(process.argv);
1404
+ if (args.flags.has("help") || args.flags.has("h") || !args.positional[0]?.trim() && !args.params["title"]) {
1405
+ console.log(USAGE);
1406
+ return;
1407
+ }
1408
+ const output = await runWiki(args);
1409
+ if (output) console.log(output);
1410
+ }
1411
+ main().catch((err) => {
1412
+ console.error(`Fatal: ${err?.message ?? err}`);
1413
+ process.exit(1);
1414
+ });
1415
+
1416
+ //#endregion
1417
+ exports.runWiki = runWiki;
1418
+ //# sourceMappingURL=abracadabra-wiki.cjs.map