@abraca/cli 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3301 @@
1
+ #!/usr/bin/env node
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) {
14
+ __defProp(to, key, {
15
+ get: ((k) => from[k]).bind(null, key),
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
+ });
18
+ }
19
+ }
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
+ value: mod,
25
+ enumerable: true
26
+ }) : target, mod));
27
+
28
+ //#endregion
29
+ let yjs = require("yjs");
30
+ yjs = __toESM(yjs);
31
+ let _abraca_dabra = require("@abraca/dabra");
32
+ let _noble_ed25519 = require("@noble/ed25519");
33
+ _noble_ed25519 = __toESM(_noble_ed25519);
34
+ let node_fs_promises = require("node:fs/promises");
35
+ let node_fs = require("node:fs");
36
+ node_fs = __toESM(node_fs);
37
+ let node_os = require("node:os");
38
+ let node_path = require("node:path");
39
+ node_path = __toESM(node_path);
40
+
41
+ //#region packages/cli/src/parser.ts
42
+ /**
43
+ * Parse CLI arguments into a structured object.
44
+ * @param argv Raw process.argv (includes node path and script path)
45
+ */
46
+ function parseArgs(argv) {
47
+ const args = argv.slice(2);
48
+ const result = {
49
+ command: "help",
50
+ params: {},
51
+ flags: /* @__PURE__ */ new Set(),
52
+ positional: []
53
+ };
54
+ let commandFound = false;
55
+ for (let i = 0; i < args.length; i++) {
56
+ const arg = args[i];
57
+ if (arg.startsWith("--")) {
58
+ const stripped = arg.slice(2);
59
+ const eqIdx = stripped.indexOf("=");
60
+ if (eqIdx !== -1) {
61
+ const key = stripped.slice(0, eqIdx);
62
+ const value = stripped.slice(eqIdx + 1);
63
+ result.params[key] = value;
64
+ } else result.flags.add(stripped);
65
+ continue;
66
+ }
67
+ const eqIdx = arg.indexOf("=");
68
+ if (eqIdx !== -1 && eqIdx > 0) {
69
+ const key = arg.slice(0, eqIdx);
70
+ let value = arg.slice(eqIdx + 1);
71
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
72
+ result.params[key] = value;
73
+ continue;
74
+ }
75
+ if (!commandFound) {
76
+ result.command = arg;
77
+ commandFound = true;
78
+ continue;
79
+ }
80
+ result.positional.push(arg);
81
+ }
82
+ return result;
83
+ }
84
+
85
+ //#endregion
86
+ //#region packages/cli/node_modules/@noble/hashes/esm/utils.js
87
+ /** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
88
+ function isBytes(a) {
89
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
90
+ }
91
+ /** Asserts something is Uint8Array. */
92
+ function abytes(b, ...lengths) {
93
+ if (!isBytes(b)) throw new Error("Uint8Array expected");
94
+ if (lengths.length > 0 && !lengths.includes(b.length)) throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
95
+ }
96
+ /** Asserts a hash instance has not been destroyed / finished */
97
+ function aexists(instance, checkFinished = true) {
98
+ if (instance.destroyed) throw new Error("Hash instance has been destroyed");
99
+ if (checkFinished && instance.finished) throw new Error("Hash#digest() has already been called");
100
+ }
101
+ /** Asserts output is properly-sized byte array */
102
+ function aoutput(out, instance) {
103
+ abytes(out);
104
+ const min = instance.outputLen;
105
+ if (out.length < min) throw new Error("digestInto() expects output buffer of length at least " + min);
106
+ }
107
+ /** Zeroize a byte array. Warning: JS provides no guarantees. */
108
+ function clean(...arrays) {
109
+ for (let i = 0; i < arrays.length; i++) arrays[i].fill(0);
110
+ }
111
+ /** Create DataView of an array for easy byte-level manipulation. */
112
+ function createView(arr) {
113
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
114
+ }
115
+ /** Is current platform little-endian? Most are. Big-Endian platform: IBM */
116
+ const isLE = new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68;
117
+ const hasHexBuiltin = typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function";
118
+ /**
119
+ * Converts string to bytes using UTF8 encoding.
120
+ * @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])
121
+ */
122
+ function utf8ToBytes(str) {
123
+ if (typeof str !== "string") throw new Error("string expected");
124
+ return new Uint8Array(new TextEncoder().encode(str));
125
+ }
126
+ /**
127
+ * Normalizes (non-hex) string or Uint8Array to Uint8Array.
128
+ * Warning: when Uint8Array is passed, it would NOT get copied.
129
+ * Keep in mind for future mutable operations.
130
+ */
131
+ function toBytes(data) {
132
+ if (typeof data === "string") data = utf8ToBytes(data);
133
+ abytes(data);
134
+ return data;
135
+ }
136
+ /** For runtime check if class implements interface */
137
+ var Hash = class {};
138
+ /** Wraps hash function, creating an interface on top of it */
139
+ function createHasher(hashCons) {
140
+ const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
141
+ const tmp = hashCons();
142
+ hashC.outputLen = tmp.outputLen;
143
+ hashC.blockLen = tmp.blockLen;
144
+ hashC.create = () => hashCons();
145
+ return hashC;
146
+ }
147
+
148
+ //#endregion
149
+ //#region packages/cli/node_modules/@noble/hashes/esm/_md.js
150
+ /**
151
+ * Internal Merkle-Damgard hash utils.
152
+ * @module
153
+ */
154
+ /** Polyfill for Safari 14. https://caniuse.com/mdn-javascript_builtins_dataview_setbiguint64 */
155
+ function setBigUint64(view, byteOffset, value, isLE) {
156
+ if (typeof view.setBigUint64 === "function") return view.setBigUint64(byteOffset, value, isLE);
157
+ const _32n = BigInt(32);
158
+ const _u32_max = BigInt(4294967295);
159
+ const wh = Number(value >> _32n & _u32_max);
160
+ const wl = Number(value & _u32_max);
161
+ const h = isLE ? 4 : 0;
162
+ const l = isLE ? 0 : 4;
163
+ view.setUint32(byteOffset + h, wh, isLE);
164
+ view.setUint32(byteOffset + l, wl, isLE);
165
+ }
166
+ /**
167
+ * Merkle-Damgard hash construction base class.
168
+ * Could be used to create MD5, RIPEMD, SHA1, SHA2.
169
+ */
170
+ var HashMD = class extends Hash {
171
+ constructor(blockLen, outputLen, padOffset, isLE) {
172
+ super();
173
+ this.finished = false;
174
+ this.length = 0;
175
+ this.pos = 0;
176
+ this.destroyed = false;
177
+ this.blockLen = blockLen;
178
+ this.outputLen = outputLen;
179
+ this.padOffset = padOffset;
180
+ this.isLE = isLE;
181
+ this.buffer = new Uint8Array(blockLen);
182
+ this.view = createView(this.buffer);
183
+ }
184
+ update(data) {
185
+ aexists(this);
186
+ data = toBytes(data);
187
+ abytes(data);
188
+ const { view, buffer, blockLen } = this;
189
+ const len = data.length;
190
+ for (let pos = 0; pos < len;) {
191
+ const take = Math.min(blockLen - this.pos, len - pos);
192
+ if (take === blockLen) {
193
+ const dataView = createView(data);
194
+ for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
195
+ continue;
196
+ }
197
+ buffer.set(data.subarray(pos, pos + take), this.pos);
198
+ this.pos += take;
199
+ pos += take;
200
+ if (this.pos === blockLen) {
201
+ this.process(view, 0);
202
+ this.pos = 0;
203
+ }
204
+ }
205
+ this.length += data.length;
206
+ this.roundClean();
207
+ return this;
208
+ }
209
+ digestInto(out) {
210
+ aexists(this);
211
+ aoutput(out, this);
212
+ this.finished = true;
213
+ const { buffer, view, blockLen, isLE } = this;
214
+ let { pos } = this;
215
+ buffer[pos++] = 128;
216
+ clean(this.buffer.subarray(pos));
217
+ if (this.padOffset > blockLen - pos) {
218
+ this.process(view, 0);
219
+ pos = 0;
220
+ }
221
+ for (let i = pos; i < blockLen; i++) buffer[i] = 0;
222
+ setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
223
+ this.process(view, 0);
224
+ const oview = createView(out);
225
+ const len = this.outputLen;
226
+ if (len % 4) throw new Error("_sha2: outputLen should be aligned to 32bit");
227
+ const outLen = len / 4;
228
+ const state = this.get();
229
+ if (outLen > state.length) throw new Error("_sha2: outputLen bigger than state");
230
+ for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
231
+ }
232
+ digest() {
233
+ const { buffer, outputLen } = this;
234
+ this.digestInto(buffer);
235
+ const res = buffer.slice(0, outputLen);
236
+ this.destroy();
237
+ return res;
238
+ }
239
+ _cloneInto(to) {
240
+ to || (to = new this.constructor());
241
+ to.set(...this.get());
242
+ const { blockLen, buffer, length, finished, destroyed, pos } = this;
243
+ to.destroyed = destroyed;
244
+ to.finished = finished;
245
+ to.length = length;
246
+ to.pos = pos;
247
+ if (length % blockLen) to.buffer.set(buffer);
248
+ return to;
249
+ }
250
+ clone() {
251
+ return this._cloneInto();
252
+ }
253
+ };
254
+ /** Initial SHA512 state. Bits 0..64 of frac part of sqrt of primes 2..19 */
255
+ const SHA512_IV = /* @__PURE__ */ Uint32Array.from([
256
+ 1779033703,
257
+ 4089235720,
258
+ 3144134277,
259
+ 2227873595,
260
+ 1013904242,
261
+ 4271175723,
262
+ 2773480762,
263
+ 1595750129,
264
+ 1359893119,
265
+ 2917565137,
266
+ 2600822924,
267
+ 725511199,
268
+ 528734635,
269
+ 4215389547,
270
+ 1541459225,
271
+ 327033209
272
+ ]);
273
+
274
+ //#endregion
275
+ //#region packages/cli/node_modules/@noble/hashes/esm/_u64.js
276
+ /**
277
+ * Internal helpers for u64. BigUint64Array is too slow as per 2025, so we implement it using Uint32Array.
278
+ * @todo re-check https://issues.chromium.org/issues/42212588
279
+ * @module
280
+ */
281
+ const U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
282
+ const _32n = /* @__PURE__ */ BigInt(32);
283
+ function fromBig(n, le = false) {
284
+ if (le) return {
285
+ h: Number(n & U32_MASK64),
286
+ l: Number(n >> _32n & U32_MASK64)
287
+ };
288
+ return {
289
+ h: Number(n >> _32n & U32_MASK64) | 0,
290
+ l: Number(n & U32_MASK64) | 0
291
+ };
292
+ }
293
+ function split(lst, le = false) {
294
+ const len = lst.length;
295
+ let Ah = new Uint32Array(len);
296
+ let Al = new Uint32Array(len);
297
+ for (let i = 0; i < len; i++) {
298
+ const { h, l } = fromBig(lst[i], le);
299
+ [Ah[i], Al[i]] = [h, l];
300
+ }
301
+ return [Ah, Al];
302
+ }
303
+ const shrSH = (h, _l, s) => h >>> s;
304
+ const shrSL = (h, l, s) => h << 32 - s | l >>> s;
305
+ const rotrSH = (h, l, s) => h >>> s | l << 32 - s;
306
+ const rotrSL = (h, l, s) => h << 32 - s | l >>> s;
307
+ const rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32;
308
+ const rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s;
309
+ function add(Ah, Al, Bh, Bl) {
310
+ const l = (Al >>> 0) + (Bl >>> 0);
311
+ return {
312
+ h: Ah + Bh + (l / 2 ** 32 | 0) | 0,
313
+ l: l | 0
314
+ };
315
+ }
316
+ const add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
317
+ const add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
318
+ const add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);
319
+ const add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0;
320
+ const add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
321
+ const add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
322
+
323
+ //#endregion
324
+ //#region packages/cli/node_modules/@noble/hashes/esm/sha2.js
325
+ /**
326
+ * SHA2 hash function. A.k.a. sha256, sha384, sha512, sha512_224, sha512_256.
327
+ * SHA256 is the fastest hash implementable in JS, even faster than Blake3.
328
+ * Check out [RFC 4634](https://datatracker.ietf.org/doc/html/rfc4634) and
329
+ * [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
330
+ * @module
331
+ */
332
+ const K512 = split([
333
+ "0x428a2f98d728ae22",
334
+ "0x7137449123ef65cd",
335
+ "0xb5c0fbcfec4d3b2f",
336
+ "0xe9b5dba58189dbbc",
337
+ "0x3956c25bf348b538",
338
+ "0x59f111f1b605d019",
339
+ "0x923f82a4af194f9b",
340
+ "0xab1c5ed5da6d8118",
341
+ "0xd807aa98a3030242",
342
+ "0x12835b0145706fbe",
343
+ "0x243185be4ee4b28c",
344
+ "0x550c7dc3d5ffb4e2",
345
+ "0x72be5d74f27b896f",
346
+ "0x80deb1fe3b1696b1",
347
+ "0x9bdc06a725c71235",
348
+ "0xc19bf174cf692694",
349
+ "0xe49b69c19ef14ad2",
350
+ "0xefbe4786384f25e3",
351
+ "0x0fc19dc68b8cd5b5",
352
+ "0x240ca1cc77ac9c65",
353
+ "0x2de92c6f592b0275",
354
+ "0x4a7484aa6ea6e483",
355
+ "0x5cb0a9dcbd41fbd4",
356
+ "0x76f988da831153b5",
357
+ "0x983e5152ee66dfab",
358
+ "0xa831c66d2db43210",
359
+ "0xb00327c898fb213f",
360
+ "0xbf597fc7beef0ee4",
361
+ "0xc6e00bf33da88fc2",
362
+ "0xd5a79147930aa725",
363
+ "0x06ca6351e003826f",
364
+ "0x142929670a0e6e70",
365
+ "0x27b70a8546d22ffc",
366
+ "0x2e1b21385c26c926",
367
+ "0x4d2c6dfc5ac42aed",
368
+ "0x53380d139d95b3df",
369
+ "0x650a73548baf63de",
370
+ "0x766a0abb3c77b2a8",
371
+ "0x81c2c92e47edaee6",
372
+ "0x92722c851482353b",
373
+ "0xa2bfe8a14cf10364",
374
+ "0xa81a664bbc423001",
375
+ "0xc24b8b70d0f89791",
376
+ "0xc76c51a30654be30",
377
+ "0xd192e819d6ef5218",
378
+ "0xd69906245565a910",
379
+ "0xf40e35855771202a",
380
+ "0x106aa07032bbd1b8",
381
+ "0x19a4c116b8d2d0c8",
382
+ "0x1e376c085141ab53",
383
+ "0x2748774cdf8eeb99",
384
+ "0x34b0bcb5e19b48a8",
385
+ "0x391c0cb3c5c95a63",
386
+ "0x4ed8aa4ae3418acb",
387
+ "0x5b9cca4f7763e373",
388
+ "0x682e6ff3d6b2b8a3",
389
+ "0x748f82ee5defb2fc",
390
+ "0x78a5636f43172f60",
391
+ "0x84c87814a1f0ab72",
392
+ "0x8cc702081a6439ec",
393
+ "0x90befffa23631e28",
394
+ "0xa4506cebde82bde9",
395
+ "0xbef9a3f7b2c67915",
396
+ "0xc67178f2e372532b",
397
+ "0xca273eceea26619c",
398
+ "0xd186b8c721c0c207",
399
+ "0xeada7dd6cde0eb1e",
400
+ "0xf57d4f7fee6ed178",
401
+ "0x06f067aa72176fba",
402
+ "0x0a637dc5a2c898a6",
403
+ "0x113f9804bef90dae",
404
+ "0x1b710b35131c471b",
405
+ "0x28db77f523047d84",
406
+ "0x32caab7b40c72493",
407
+ "0x3c9ebe0a15c9bebc",
408
+ "0x431d67c49c100d4c",
409
+ "0x4cc5d4becb3e42b6",
410
+ "0x597f299cfc657e2a",
411
+ "0x5fcb6fab3ad6faec",
412
+ "0x6c44198c4a475817"
413
+ ].map((n) => BigInt(n)));
414
+ const SHA512_Kh = K512[0];
415
+ const SHA512_Kl = K512[1];
416
+ const SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
417
+ const SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
418
+ var SHA512 = class extends HashMD {
419
+ constructor(outputLen = 64) {
420
+ super(128, outputLen, 16, false);
421
+ this.Ah = SHA512_IV[0] | 0;
422
+ this.Al = SHA512_IV[1] | 0;
423
+ this.Bh = SHA512_IV[2] | 0;
424
+ this.Bl = SHA512_IV[3] | 0;
425
+ this.Ch = SHA512_IV[4] | 0;
426
+ this.Cl = SHA512_IV[5] | 0;
427
+ this.Dh = SHA512_IV[6] | 0;
428
+ this.Dl = SHA512_IV[7] | 0;
429
+ this.Eh = SHA512_IV[8] | 0;
430
+ this.El = SHA512_IV[9] | 0;
431
+ this.Fh = SHA512_IV[10] | 0;
432
+ this.Fl = SHA512_IV[11] | 0;
433
+ this.Gh = SHA512_IV[12] | 0;
434
+ this.Gl = SHA512_IV[13] | 0;
435
+ this.Hh = SHA512_IV[14] | 0;
436
+ this.Hl = SHA512_IV[15] | 0;
437
+ }
438
+ get() {
439
+ const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
440
+ return [
441
+ Ah,
442
+ Al,
443
+ Bh,
444
+ Bl,
445
+ Ch,
446
+ Cl,
447
+ Dh,
448
+ Dl,
449
+ Eh,
450
+ El,
451
+ Fh,
452
+ Fl,
453
+ Gh,
454
+ Gl,
455
+ Hh,
456
+ Hl
457
+ ];
458
+ }
459
+ set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
460
+ this.Ah = Ah | 0;
461
+ this.Al = Al | 0;
462
+ this.Bh = Bh | 0;
463
+ this.Bl = Bl | 0;
464
+ this.Ch = Ch | 0;
465
+ this.Cl = Cl | 0;
466
+ this.Dh = Dh | 0;
467
+ this.Dl = Dl | 0;
468
+ this.Eh = Eh | 0;
469
+ this.El = El | 0;
470
+ this.Fh = Fh | 0;
471
+ this.Fl = Fl | 0;
472
+ this.Gh = Gh | 0;
473
+ this.Gl = Gl | 0;
474
+ this.Hh = Hh | 0;
475
+ this.Hl = Hl | 0;
476
+ }
477
+ process(view, offset) {
478
+ for (let i = 0; i < 16; i++, offset += 4) {
479
+ SHA512_W_H[i] = view.getUint32(offset);
480
+ SHA512_W_L[i] = view.getUint32(offset += 4);
481
+ }
482
+ for (let i = 16; i < 80; i++) {
483
+ const W15h = SHA512_W_H[i - 15] | 0;
484
+ const W15l = SHA512_W_L[i - 15] | 0;
485
+ const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
486
+ const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
487
+ const W2h = SHA512_W_H[i - 2] | 0;
488
+ const W2l = SHA512_W_L[i - 2] | 0;
489
+ const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
490
+ const s1l = rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6);
491
+ const SUMl = add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
492
+ SHA512_W_H[i] = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]) | 0;
493
+ SHA512_W_L[i] = SUMl | 0;
494
+ }
495
+ let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
496
+ for (let i = 0; i < 80; i++) {
497
+ const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
498
+ const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
499
+ const CHIh = Eh & Fh ^ ~Eh & Gh;
500
+ const CHIl = El & Fl ^ ~El & Gl;
501
+ const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
502
+ const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
503
+ const T1l = T1ll | 0;
504
+ const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
505
+ const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
506
+ const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
507
+ const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
508
+ Hh = Gh | 0;
509
+ Hl = Gl | 0;
510
+ Gh = Fh | 0;
511
+ Gl = Fl | 0;
512
+ Fh = Eh | 0;
513
+ Fl = El | 0;
514
+ ({h: Eh, l: El} = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
515
+ Dh = Ch | 0;
516
+ Dl = Cl | 0;
517
+ Ch = Bh | 0;
518
+ Cl = Bl | 0;
519
+ Bh = Ah | 0;
520
+ Bl = Al | 0;
521
+ const All = add3L(T1l, sigma0l, MAJl);
522
+ Ah = add3H(All, T1h, sigma0h, MAJh);
523
+ Al = All | 0;
524
+ }
525
+ ({h: Ah, l: Al} = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
526
+ ({h: Bh, l: Bl} = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
527
+ ({h: Ch, l: Cl} = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
528
+ ({h: Dh, l: Dl} = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
529
+ ({h: Eh, l: El} = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
530
+ ({h: Fh, l: Fl} = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
531
+ ({h: Gh, l: Gl} = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
532
+ ({h: Hh, l: Hl} = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
533
+ this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
534
+ }
535
+ roundClean() {
536
+ clean(SHA512_W_H, SHA512_W_L);
537
+ }
538
+ destroy() {
539
+ clean(this.buffer);
540
+ this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
541
+ }
542
+ };
543
+ /** SHA2-512 hash function from RFC 4634. */
544
+ const sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
545
+
546
+ //#endregion
547
+ //#region packages/cli/src/crypto.ts
548
+ /**
549
+ * Ed25519 key generation, persistence, and challenge signing for CLI auth.
550
+ * Mirrors @abraca/mcp/src/crypto.ts — standalone to avoid MCP SDK dependency.
551
+ */
552
+ _noble_ed25519.etc.sha512Sync = (...msgs) => sha512(_noble_ed25519.etc.concatBytes(...msgs));
553
+ const DEFAULT_KEY_PATH = (0, node_path.join)((0, node_os.homedir)(), ".abracadabra", "cli.key");
554
+ function toBase64url(bytes) {
555
+ return Buffer.from(bytes).toString("base64url");
556
+ }
557
+ function fromBase64url(b64) {
558
+ return new Uint8Array(Buffer.from(b64, "base64url"));
559
+ }
560
+ /**
561
+ * Load an existing Ed25519 keypair from disk, or generate and persist a new one.
562
+ * The file stores the raw 32-byte private key seed.
563
+ */
564
+ async function loadOrCreateKeypair(keyPath) {
565
+ const path = keyPath || DEFAULT_KEY_PATH;
566
+ if ((0, node_fs.existsSync)(path)) {
567
+ const seed = await (0, node_fs_promises.readFile)(path);
568
+ if (seed.length !== 32) throw new Error(`Invalid key file at ${path}: expected 32 bytes, got ${seed.length}`);
569
+ const privateKey = new Uint8Array(seed);
570
+ return {
571
+ privateKey,
572
+ publicKeyB64: toBase64url(_noble_ed25519.getPublicKey(privateKey))
573
+ };
574
+ }
575
+ const privateKey = _noble_ed25519.utils.randomPrivateKey();
576
+ const publicKey = _noble_ed25519.getPublicKey(privateKey);
577
+ const dir = (0, node_path.dirname)(path);
578
+ if (!(0, node_fs.existsSync)(dir)) await (0, node_fs_promises.mkdir)(dir, {
579
+ recursive: true,
580
+ mode: 448
581
+ });
582
+ await (0, node_fs_promises.writeFile)(path, Buffer.from(privateKey), { mode: 384 });
583
+ console.error(`[abracadabra] Generated new keypair at ${path}`);
584
+ console.error(`[abracadabra] Public key: ${toBase64url(publicKey)}`);
585
+ return {
586
+ privateKey,
587
+ publicKeyB64: toBase64url(publicKey)
588
+ };
589
+ }
590
+ /** Sign a base64url challenge with the private key; returns base64url signature. */
591
+ function signChallenge(challengeB64, privateKey) {
592
+ const challenge = fromBase64url(challengeB64);
593
+ return toBase64url(_noble_ed25519.sign(challenge, privateKey));
594
+ }
595
+
596
+ //#endregion
597
+ //#region packages/cli/src/connection.ts
598
+ /**
599
+ * CLIConnection — manages the CRDT connection lifecycle for the CLI.
600
+ *
601
+ * Handles: Ed25519 auth → space discovery → Y.Doc sync → awareness.
602
+ * Reuses the same patterns as AbracadabraMCPServer but without MCP SDK dependency.
603
+ */
604
+ function waitForSync(provider, timeoutMs = 15e3) {
605
+ return new Promise((resolve, reject) => {
606
+ const timer = setTimeout(() => {
607
+ provider.off("synced", handler);
608
+ reject(/* @__PURE__ */ new Error(`Sync timed out after ${timeoutMs}ms`));
609
+ }, timeoutMs);
610
+ function handler() {
611
+ clearTimeout(timer);
612
+ resolve();
613
+ }
614
+ provider.on("synced", handler);
615
+ });
616
+ }
617
+ /** Map a DocumentMeta to SpaceMeta shape for display compatibility. */
618
+ function docToSpaceMeta(doc) {
619
+ const publicAccess = doc.public_access;
620
+ let visibility = "private";
621
+ if (publicAccess && publicAccess !== "none") visibility = "public";
622
+ return {
623
+ id: doc.id,
624
+ doc_id: doc.id,
625
+ name: doc.label ?? doc.id,
626
+ description: doc.description ?? null,
627
+ visibility,
628
+ is_hub: doc.is_hub ?? false,
629
+ owner_id: doc.owner_id ?? null,
630
+ created_at: 0,
631
+ updated_at: doc.updated_at ?? 0,
632
+ public_access: publicAccess ?? null
633
+ };
634
+ }
635
+ var CLIConnection = class {
636
+ config;
637
+ client;
638
+ _serverInfo = null;
639
+ _rootDocId = null;
640
+ _spaces = [];
641
+ _rootDoc = null;
642
+ _rootProvider = null;
643
+ _userId = null;
644
+ _signFn = null;
645
+ childCache = /* @__PURE__ */ new Map();
646
+ constructor(config) {
647
+ this.config = config;
648
+ this.client = new _abraca_dabra.AbracadabraClient({
649
+ url: config.url,
650
+ persistAuth: false
651
+ });
652
+ }
653
+ get displayName() {
654
+ return this.config.name || "CLI User";
655
+ }
656
+ get displayColor() {
657
+ return this.config.color || "hsl(45, 90%, 55%)";
658
+ }
659
+ get serverInfo() {
660
+ return this._serverInfo;
661
+ }
662
+ get rootDocId() {
663
+ return this._rootDocId;
664
+ }
665
+ get spaces() {
666
+ return this._spaces;
667
+ }
668
+ get rootDoc() {
669
+ return this._rootDoc;
670
+ }
671
+ get rootProvider() {
672
+ return this._rootProvider;
673
+ }
674
+ get userId() {
675
+ return this._userId;
676
+ }
677
+ log(msg) {
678
+ if (!this.config.quiet) console.error(`[abracadabra] ${msg}`);
679
+ }
680
+ /** Connect to the server: authenticate, discover spaces, sync root doc. */
681
+ async connect() {
682
+ const keypair = await loadOrCreateKeypair(this.config.keyFile);
683
+ this._userId = keypair.publicKeyB64;
684
+ const signFn = (challenge) => Promise.resolve(signChallenge(challenge, keypair.privateKey));
685
+ this._signFn = signFn;
686
+ try {
687
+ await this.client.loginWithKey(keypair.publicKeyB64, signFn);
688
+ } catch (err) {
689
+ const status = err?.status ?? err?.response?.status;
690
+ if (status === 404 || status === 422) {
691
+ this.log("Key not registered, creating new account...");
692
+ await this.client.registerWithKey({
693
+ publicKey: keypair.publicKeyB64,
694
+ username: this.displayName.replace(/\s+/g, "-").toLowerCase(),
695
+ displayName: this.displayName,
696
+ deviceName: "CLI",
697
+ inviteCode: this.config.inviteCode
698
+ });
699
+ await this.client.loginWithKey(keypair.publicKeyB64, signFn);
700
+ } else throw err;
701
+ }
702
+ this.log(`Authenticated as ${this.displayName} (${keypair.publicKeyB64.slice(0, 12)}...)`);
703
+ this._serverInfo = await this.client.serverInfo();
704
+ let initialDocId = this._serverInfo.index_doc_id ?? null;
705
+ try {
706
+ const roots = await this.client.listRootDocuments();
707
+ this._spaces = roots.map(docToSpaceMeta);
708
+ const hub = roots.find((d) => d.is_hub);
709
+ if (hub) {
710
+ initialDocId = hub.id;
711
+ this.log(`Hub document: ${hub.label ?? hub.id} (${hub.id})`);
712
+ } else if (roots.length > 0) {
713
+ initialDocId = roots[0].id;
714
+ this.log(`No hub, using first root doc: ${roots[0].label ?? roots[0].id}`);
715
+ }
716
+ } catch {
717
+ try {
718
+ this._spaces = await this.client.listSpaces();
719
+ const hub = this._spaces.find((s) => s.is_hub);
720
+ if (hub) initialDocId = hub.doc_id;
721
+ else if (this._spaces.length > 0) initialDocId = this._spaces[0].doc_id;
722
+ } catch {
723
+ this.log("Neither /docs?root=true nor /spaces available, using index_doc_id");
724
+ }
725
+ }
726
+ if (!initialDocId) throw new Error("No entry point found: server has neither spaces nor index_doc_id configured.");
727
+ this._rootDocId = initialDocId;
728
+ const doc = new yjs.Doc({ guid: initialDocId });
729
+ const provider = new _abraca_dabra.AbracadabraProvider({
730
+ name: initialDocId,
731
+ document: doc,
732
+ client: this.client,
733
+ disableOfflineStore: true,
734
+ subdocLoading: "lazy"
735
+ });
736
+ await waitForSync(provider);
737
+ provider.awareness.setLocalStateField("user", {
738
+ name: this.displayName,
739
+ color: this.displayColor,
740
+ publicKey: this._userId,
741
+ isAgent: false
742
+ });
743
+ provider.awareness.setLocalStateField("status", null);
744
+ this._rootDoc = doc;
745
+ this._rootProvider = provider;
746
+ this.log("Connected and synced");
747
+ }
748
+ /** Switch active space to a different root document. */
749
+ async switchSpace(docId) {
750
+ for (const [, cached] of this.childCache) cached.provider.destroy();
751
+ this.childCache.clear();
752
+ if (this._rootProvider) {
753
+ this._rootProvider.destroy();
754
+ this._rootProvider = null;
755
+ }
756
+ this._rootDoc = null;
757
+ if (!this.client.isTokenValid() && this._signFn && this._userId) await this.client.loginWithKey(this._userId, this._signFn);
758
+ const doc = new yjs.Doc({ guid: docId });
759
+ const provider = new _abraca_dabra.AbracadabraProvider({
760
+ name: docId,
761
+ document: doc,
762
+ client: this.client,
763
+ disableOfflineStore: true,
764
+ subdocLoading: "lazy"
765
+ });
766
+ await waitForSync(provider);
767
+ provider.awareness.setLocalStateField("user", {
768
+ name: this.displayName,
769
+ color: this.displayColor,
770
+ publicKey: this._userId,
771
+ isAgent: false
772
+ });
773
+ this._rootDoc = doc;
774
+ this._rootProvider = provider;
775
+ this._rootDocId = docId;
776
+ this.log(`Switched to space ${docId}`);
777
+ }
778
+ /** Get the root doc-tree Y.Map. */
779
+ getTreeMap() {
780
+ return this._rootDoc?.getMap("doc-tree") ?? null;
781
+ }
782
+ /** Get the root doc-trash Y.Map. */
783
+ getTrashMap() {
784
+ return this._rootDoc?.getMap("doc-trash") ?? null;
785
+ }
786
+ /** Get or create a child provider for a document. */
787
+ async getChildProvider(docId) {
788
+ const cached = this.childCache.get(docId);
789
+ if (cached) {
790
+ cached.lastAccessed = Date.now();
791
+ return cached.provider;
792
+ }
793
+ if (!this._rootProvider) throw new Error("Not connected. Call connect() first.");
794
+ if (!this.client.isTokenValid() && this._signFn && this._userId) await this.client.loginWithKey(this._userId, this._signFn);
795
+ const childProvider = await this._rootProvider.loadChild(docId);
796
+ await waitForSync(childProvider);
797
+ childProvider.awareness.setLocalStateField("user", {
798
+ name: this.displayName,
799
+ color: this.displayColor,
800
+ publicKey: this._userId,
801
+ isAgent: false
802
+ });
803
+ this.childCache.set(docId, {
804
+ provider: childProvider,
805
+ lastAccessed: Date.now()
806
+ });
807
+ return childProvider;
808
+ }
809
+ /** Graceful shutdown. */
810
+ async destroy() {
811
+ for (const [, cached] of this.childCache) cached.provider.destroy();
812
+ this.childCache.clear();
813
+ if (this._rootProvider) {
814
+ this._rootProvider.awareness.setLocalStateField("status", null);
815
+ this._rootProvider.destroy();
816
+ this._rootProvider = null;
817
+ }
818
+ this._rootDoc = null;
819
+ this.log("Disconnected");
820
+ }
821
+ };
822
+
823
+ //#endregion
824
+ //#region packages/cli/src/command.ts
825
+ const commands = [];
826
+ function registerCommand(cmd) {
827
+ commands.push(cmd);
828
+ }
829
+ function getCommand(name) {
830
+ return commands.find((c) => c.name === name || c.aliases?.includes(name)) ?? null;
831
+ }
832
+ function getAllCommands() {
833
+ return [...commands];
834
+ }
835
+
836
+ //#endregion
837
+ //#region packages/cli/src/output.ts
838
+ function getFormat(args, defaultFormat = "text") {
839
+ const f = args.params["format"];
840
+ if (f && [
841
+ "json",
842
+ "tsv",
843
+ "tree",
844
+ "text",
845
+ "md"
846
+ ].includes(f)) return f;
847
+ return defaultFormat;
848
+ }
849
+ /** Print a JSON-serializable value. */
850
+ function printJson(data) {
851
+ return JSON.stringify(data, null, 2);
852
+ }
853
+ /** Format a tree of nested items with box-drawing characters. */
854
+ function printTree(items, opts = {}) {
855
+ const lines = [];
856
+ for (let i = 0; i < items.length; i++) {
857
+ const item = items[i];
858
+ const isLast = i === items.length - 1;
859
+ const prefix = (opts.isLast ?? []).map((last) => last ? " " : "│ ").join("");
860
+ const connector = isLast ? "└── " : "├── ";
861
+ const typeSuffix = item.type ? ` (${item.type})` : "";
862
+ lines.push(`${prefix}${connector}${item.label}${typeSuffix}`);
863
+ if (item.children && item.children.length > 0) lines.push(printTree(item.children, { isLast: [...opts.isLast ?? [], isLast] }));
864
+ }
865
+ return lines.join("\n");
866
+ }
867
+ /** Format relative time (e.g. "2m ago", "1h ago"). */
868
+ function relativeTime(timestamp) {
869
+ if (!timestamp) return "—";
870
+ const diff = Date.now() - timestamp;
871
+ if (diff < 0) return "just now";
872
+ if (diff < 6e4) return `${Math.floor(diff / 1e3)}s ago`;
873
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
874
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
875
+ return `${Math.floor(diff / 864e5)}d ago`;
876
+ }
877
+ /** Pad a string to a fixed width. */
878
+ function pad(str, width) {
879
+ if (str.length >= width) return str.slice(0, width);
880
+ return str + " ".repeat(width - str.length);
881
+ }
882
+ /** Format a table with auto-column widths. */
883
+ function printTable(rows, headers) {
884
+ const allRows = headers ? [headers, ...rows] : rows;
885
+ if (allRows.length === 0) return "";
886
+ const colCount = Math.max(...allRows.map((r) => r.length));
887
+ const widths = [];
888
+ for (let c = 0; c < colCount; c++) widths[c] = Math.max(...allRows.map((r) => (r[c] ?? "").length));
889
+ return allRows.map((row) => row.map((cell, i) => pad(cell, widths[i])).join(" ")).join("\n");
890
+ }
891
+
892
+ //#endregion
893
+ //#region packages/cli/src/commands/help.ts
894
+ /**
895
+ * help + version commands.
896
+ */
897
+ registerCommand({
898
+ name: "help",
899
+ aliases: ["h", "?"],
900
+ description: "Show list of all available commands.",
901
+ usage: "help [<command>]",
902
+ async run(_conn, args) {
903
+ const target = args.positional[0];
904
+ if (target) {
905
+ const cmd = getAllCommands().find((c) => c.name === target || c.aliases?.includes(target));
906
+ if (!cmd) return `Unknown command: ${target}\n\nRun 'abracadabra help' to see all commands.`;
907
+ const aliasStr = cmd.aliases?.length ? ` (aliases: ${cmd.aliases.join(", ")})` : "";
908
+ let text = `${cmd.name}${aliasStr}\n ${cmd.description}`;
909
+ if (cmd.usage) text += `\n\n Usage: abracadabra ${cmd.usage}`;
910
+ return text;
911
+ }
912
+ const cmds = getAllCommands();
913
+ const maxLen = Math.max(...cmds.map((c) => c.name.length));
914
+ return [
915
+ "Abracadabra CLI — interact with CRDT document workspaces from the terminal.",
916
+ "",
917
+ "Usage: abracadabra <command> [key=value ...] [--flags]",
918
+ "",
919
+ "Commands:",
920
+ ...cmds.map((c) => {
921
+ const aliasStr = c.aliases?.length ? ` (${c.aliases.join(", ")})` : "";
922
+ return ` ${pad(c.name, maxLen + 2)}${c.description}${aliasStr}`;
923
+ }),
924
+ "",
925
+ "Environment:",
926
+ " ABRA_URL Server URL (required)",
927
+ " ABRA_KEY_FILE Path to Ed25519 key file",
928
+ " ABRA_INVITE_CODE Invite code for first-run registration",
929
+ " ABRA_NAME Display name",
930
+ " ABRA_COLOR Presence color",
931
+ "",
932
+ "Run \"abracadabra help <command>\" for command-specific help."
933
+ ].join("\n");
934
+ }
935
+ });
936
+ registerCommand({
937
+ name: "version",
938
+ aliases: ["v"],
939
+ description: "Show CLI and server version.",
940
+ async run(conn) {
941
+ const lines = [`Abracadabra CLI v1.0.0`];
942
+ if (conn?.serverInfo) {
943
+ const si = conn.serverInfo;
944
+ if (si.version) lines.push(`Server v${si.version}`);
945
+ if (si.name) lines.push(`Server name ${si.name}`);
946
+ if (si.protocol_version) lines.push(`Protocol ${si.protocol_version}`);
947
+ }
948
+ return lines.join("\n");
949
+ }
950
+ });
951
+
952
+ //#endregion
953
+ //#region packages/cli/src/resolve.ts
954
+ /**
955
+ * Document resolution utilities for the CLI.
956
+ *
957
+ * Resolves documents by id, name (label), or path (label chain from root).
958
+ * Similar to Obsidian's file= / path= resolution.
959
+ */
960
+ /** Safely read a tree map value, converting Y.Map to plain object if needed. */
961
+ function toPlain$2(val) {
962
+ return val instanceof yjs.Map ? val.toJSON() : val;
963
+ }
964
+ /** Read all tree entries from the Y.Map. */
965
+ function readEntries(treeMap) {
966
+ const entries = [];
967
+ treeMap.forEach((raw, id) => {
968
+ const value = toPlain$2(raw);
969
+ if (typeof value !== "object" || value === null) return;
970
+ entries.push({
971
+ id,
972
+ label: value.label || "Untitled",
973
+ parentId: value.parentId ?? null,
974
+ order: value.order ?? 0,
975
+ type: value.type,
976
+ meta: value.meta,
977
+ createdAt: value.createdAt,
978
+ updatedAt: value.updatedAt
979
+ });
980
+ });
981
+ return entries;
982
+ }
983
+ /** Get direct children of a parent, sorted by order. */
984
+ function childrenOf(entries, parentId) {
985
+ return entries.filter((e) => e.parentId === parentId).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
986
+ }
987
+ /** Get all descendants of an entry. */
988
+ function descendantsOf(entries, id) {
989
+ const result = [];
990
+ const visited = /* @__PURE__ */ new Set();
991
+ function collect(pid) {
992
+ if (visited.has(pid)) return;
993
+ visited.add(pid);
994
+ for (const child of childrenOf(entries, pid)) {
995
+ result.push(child);
996
+ collect(child.id);
997
+ }
998
+ }
999
+ collect(id);
1000
+ return result;
1001
+ }
1002
+ /** Build a nested tree structure for display. */
1003
+ function buildTree(entries, rootId, maxDepth, currentDepth = 0, visited = /* @__PURE__ */ new Set()) {
1004
+ if (maxDepth >= 0 && currentDepth >= maxDepth) return [];
1005
+ return childrenOf(entries, rootId).filter((e) => !visited.has(e.id)).map((entry) => {
1006
+ const next = new Set(visited);
1007
+ next.add(entry.id);
1008
+ return {
1009
+ id: entry.id,
1010
+ label: entry.label,
1011
+ type: entry.type,
1012
+ meta: entry.meta,
1013
+ children: buildTree(entries, entry.id, maxDepth, currentDepth + 1, next)
1014
+ };
1015
+ });
1016
+ }
1017
+ /**
1018
+ * Normalize a document ID so the hub/root doc ID is treated as the tree root (null).
1019
+ */
1020
+ function normalizeRootId(id, conn) {
1021
+ if (id == null) return null;
1022
+ return id === conn.rootDocId ? null : id;
1023
+ }
1024
+ /**
1025
+ * Resolve a document from parsed args.
1026
+ *
1027
+ * Resolution order:
1028
+ * 1. id=<uuid> — Direct document ID
1029
+ * 2. name=<label> — Case-insensitive label match (first match wins)
1030
+ * 3. path=<a/b/c> — Resolve by label path from root
1031
+ * 4. First positional argument — tries name resolution
1032
+ *
1033
+ * Returns the document ID or null if not found.
1034
+ */
1035
+ function resolveDocument(conn, params, positional) {
1036
+ const treeMap = conn.getTreeMap();
1037
+ if (!treeMap) return null;
1038
+ const entries = readEntries(treeMap);
1039
+ if (params["id"]) {
1040
+ const entry = entries.find((e) => e.id === params["id"]);
1041
+ return entry ? entry.id : null;
1042
+ }
1043
+ const name = params["name"] || params["file"] || positional[0];
1044
+ if (name) {
1045
+ const lower = name.toLowerCase();
1046
+ const match = entries.find((e) => e.label.toLowerCase() === lower);
1047
+ if (match) return match.id;
1048
+ const substring = entries.find((e) => e.label.toLowerCase().includes(lower));
1049
+ if (substring) return substring.id;
1050
+ }
1051
+ if (params["path"]) {
1052
+ const segments = params["path"].split("/").map((s) => s.trim()).filter(Boolean);
1053
+ let currentParent = normalizeRootId(conn.rootDocId, conn);
1054
+ for (const segment of segments) {
1055
+ const lower = segment.toLowerCase();
1056
+ const match = childrenOf(entries, currentParent).find((c) => c.label.toLowerCase() === lower);
1057
+ if (!match) return null;
1058
+ currentParent = match.id;
1059
+ }
1060
+ return currentParent;
1061
+ }
1062
+ return null;
1063
+ }
1064
+ /** Get the ancestor path labels for a document. */
1065
+ function getAncestorPath(entries, docId) {
1066
+ const byId = new Map(entries.map((e) => [e.id, e]));
1067
+ const path = [];
1068
+ let current = byId.get(docId)?.parentId;
1069
+ const visited = /* @__PURE__ */ new Set();
1070
+ while (current && !visited.has(current)) {
1071
+ visited.add(current);
1072
+ const parent = byId.get(current);
1073
+ if (!parent) break;
1074
+ path.unshift(parent.label);
1075
+ current = parent.parentId;
1076
+ }
1077
+ return path;
1078
+ }
1079
+
1080
+ //#endregion
1081
+ //#region packages/cli/src/commands/spaces.ts
1082
+ /**
1083
+ * spaces, spaces:switch, info commands.
1084
+ */
1085
+ registerCommand({
1086
+ name: "info",
1087
+ description: "Show server and space info.",
1088
+ async run(conn) {
1089
+ if (!conn) return "Not connected";
1090
+ const si = conn.serverInfo;
1091
+ if (!si) return "No server info available";
1092
+ const treeMap = conn.getTreeMap();
1093
+ const docCount = treeMap ? readEntries(treeMap).length : 0;
1094
+ const rootProvider = conn.rootProvider;
1095
+ const onlineUsers = rootProvider ? Array.from(rootProvider.awareness.getStates().values()).filter((s) => s.user).length : 0;
1096
+ return [
1097
+ `Server: ${si.name ?? "—"}`,
1098
+ `URL: ${conn.config.url}`,
1099
+ `Version: ${si.version ?? "—"}`,
1100
+ `Protocol: ${si.protocol_version ?? "—"}`,
1101
+ `Hub Doc: ${conn.rootDocId ?? "—"}`,
1102
+ `Auth: ${si.auth_methods?.join(", ") ?? "—"}`,
1103
+ `Registration:${si.registration_allowed ? " open" : " closed"}${si.invite_only ? " (invite only)" : ""}`,
1104
+ `Documents: ${docCount}`,
1105
+ `Users: ${onlineUsers} online`
1106
+ ].join("\n");
1107
+ }
1108
+ });
1109
+ registerCommand({
1110
+ name: "spaces",
1111
+ description: "List available spaces/root documents.",
1112
+ usage: "spaces [--format=json|tsv]",
1113
+ async run(conn, args) {
1114
+ if (!conn) return "Not connected";
1115
+ const spaces = conn.spaces;
1116
+ if (!spaces.length) return "No spaces available.";
1117
+ const format = getFormat(args, "text");
1118
+ const active = conn.rootDocId;
1119
+ if (format === "json") return printJson(spaces.map((s) => ({
1120
+ ...s,
1121
+ active: s.doc_id === active
1122
+ })));
1123
+ return printTable(spaces.map((s) => [
1124
+ s.doc_id === active ? "▸" : " ",
1125
+ s.doc_id.slice(0, 8) + "…",
1126
+ s.name,
1127
+ s.is_hub ? "hub" : "",
1128
+ s.visibility
1129
+ ]), [
1130
+ "",
1131
+ "ID",
1132
+ "NAME",
1133
+ "HUB",
1134
+ "VISIBILITY"
1135
+ ]);
1136
+ }
1137
+ });
1138
+ registerCommand({
1139
+ name: "spaces:switch",
1140
+ aliases: ["switch"],
1141
+ description: "Switch the active space.",
1142
+ usage: "spaces:switch name=<name> | id=<docId>",
1143
+ async run(conn, args) {
1144
+ if (!conn) return "Not connected";
1145
+ const targetName = args.params["name"] || args.positional[0];
1146
+ const targetId = args.params["id"];
1147
+ let docId = null;
1148
+ if (targetId) docId = targetId;
1149
+ else if (targetName) {
1150
+ const lower = targetName.toLowerCase();
1151
+ const space = conn.spaces.find((s) => s.name.toLowerCase() === lower || s.doc_id === targetName);
1152
+ if (space) docId = space.doc_id;
1153
+ }
1154
+ if (!docId) return "Space not found. Use \"abracadabra spaces\" to list available spaces.";
1155
+ await conn.switchSpace(docId);
1156
+ return `Switched to space "${conn.spaces.find((s) => s.doc_id === docId)?.name ?? docId}"`;
1157
+ }
1158
+ });
1159
+
1160
+ //#endregion
1161
+ //#region packages/cli/src/commands/tree.ts
1162
+ /**
1163
+ * tree, ls, search commands — navigate and explore the document tree.
1164
+ */
1165
+ function toTreeNodes(nodes) {
1166
+ return nodes.map((n) => ({
1167
+ label: n.label,
1168
+ type: n.type,
1169
+ children: n.children.length > 0 ? toTreeNodes(n.children) : void 0
1170
+ }));
1171
+ }
1172
+ registerCommand({
1173
+ name: "tree",
1174
+ aliases: ["t"],
1175
+ description: "Show the full document tree hierarchy.",
1176
+ usage: "tree [id=<docId>] [depth=<n>] [--format=json|tree]",
1177
+ async run(conn, args) {
1178
+ if (!conn) return "Not connected";
1179
+ const treeMap = conn.getTreeMap();
1180
+ if (!treeMap) return "Not connected";
1181
+ const rootId = normalizeRootId(args.params["id"] || args.positional[0] || conn.rootDocId, conn);
1182
+ const maxDepth = args.params["depth"] ? parseInt(args.params["depth"], 10) : -1;
1183
+ const format = getFormat(args, "tree");
1184
+ const tree = buildTree(readEntries(treeMap), rootId, maxDepth);
1185
+ if (format === "json") return printJson(tree);
1186
+ if (tree.length === 0) return "(empty tree)";
1187
+ return (conn.spaces.find((s) => s.doc_id === conn.rootDocId)?.name ?? conn.rootDocId ?? "Workspace") + "\n" + printTree(toTreeNodes(tree));
1188
+ }
1189
+ });
1190
+ registerCommand({
1191
+ name: "ls",
1192
+ aliases: ["list", "l"],
1193
+ description: "List direct children of a document (defaults to root).",
1194
+ usage: "ls [id=<parentId>] [name=<parentName>] [--format=json|tsv] [--total]",
1195
+ async run(conn, args) {
1196
+ if (!conn) return "Not connected";
1197
+ const treeMap = conn.getTreeMap();
1198
+ if (!treeMap) return "Not connected";
1199
+ let parentId = null;
1200
+ if (args.params["id"] || args.params["name"] || args.params["path"] || args.positional[0]) {
1201
+ parentId = resolveDocument(conn, args.params, args.positional);
1202
+ if (!parentId && (args.params["id"] || args.params["name"] || args.params["path"])) return "Document not found.";
1203
+ }
1204
+ const targetId = normalizeRootId(parentId || conn.rootDocId, conn);
1205
+ const children = childrenOf(readEntries(treeMap), targetId);
1206
+ if (args.flags.has("total")) return String(children.length);
1207
+ if (getFormat(args, "text") === "json") return printJson(children);
1208
+ if (children.length === 0) return "(no children)";
1209
+ return printTable(children.map((c) => [
1210
+ c.id.slice(0, 8) + "…",
1211
+ c.label,
1212
+ c.type ?? "—",
1213
+ relativeTime(c.updatedAt)
1214
+ ]), [
1215
+ "ID",
1216
+ "LABEL",
1217
+ "TYPE",
1218
+ "UPDATED"
1219
+ ]);
1220
+ }
1221
+ });
1222
+ registerCommand({
1223
+ name: "search",
1224
+ aliases: ["find", "s"],
1225
+ description: "Search documents by label across the tree.",
1226
+ usage: "search query=<text> [id=<rootId>] [--format=json|tsv] [--total]",
1227
+ async run(conn, args) {
1228
+ if (!conn) return "Not connected";
1229
+ const treeMap = conn.getTreeMap();
1230
+ if (!treeMap) return "Not connected";
1231
+ const query = args.params["query"] || args.positional[0];
1232
+ if (!query) return "Missing required parameter: query=<text>";
1233
+ const entries = readEntries(treeMap);
1234
+ const lowerQuery = query.toLowerCase();
1235
+ const matches = entries.filter((e) => e.label.toLowerCase().includes(lowerQuery));
1236
+ if (args.flags.has("total")) return String(matches.length);
1237
+ const format = getFormat(args, "text");
1238
+ if (matches.length === 0) return `No documents found matching "${query}".`;
1239
+ const results = matches.map((entry) => ({
1240
+ id: entry.id,
1241
+ label: entry.label,
1242
+ type: entry.type,
1243
+ path: getAncestorPath(entries, entry.id)
1244
+ }));
1245
+ if (format === "json") return printJson(results);
1246
+ return printTable(results.map((r) => [
1247
+ r.path.length > 0 ? r.path.join(" / ") + " / " + r.label : r.label,
1248
+ r.label,
1249
+ r.type ?? "—"
1250
+ ]), [
1251
+ "PATH",
1252
+ "LABEL",
1253
+ "TYPE"
1254
+ ]);
1255
+ }
1256
+ });
1257
+
1258
+ //#endregion
1259
+ //#region packages/mcp/src/converters/yjsToMarkdown.ts
1260
+ /**
1261
+ * Y.XmlFragment → Markdown serializer.
1262
+ * Walks the TipTap document structure and produces markdown text.
1263
+ */
1264
+ function deltaToMarkdown(delta) {
1265
+ return delta.map((op) => {
1266
+ let text = op.insert;
1267
+ if (!op.attributes) return text;
1268
+ const a = op.attributes;
1269
+ if (a.code) text = `\`${text}\``;
1270
+ if (a.bold) text = `**${text}**`;
1271
+ if (a.italic) text = `*${text}*`;
1272
+ if (a.strike) text = `~~${text}~~`;
1273
+ if (a.link?.href) text = `[${text}](${a.link.href})`;
1274
+ if (a.badge) text = `:badge[${a.badge.label || text}]`;
1275
+ if (a.kbd) text = `:kbd{value="${a.kbd.value || text}"}`;
1276
+ if (a.proseIcon) text = `:icon{name="${a.proseIcon.name}"}`;
1277
+ return text;
1278
+ }).join("");
1279
+ }
1280
+ function xmlTextToMarkdown(xmlText) {
1281
+ return deltaToMarkdown(xmlText.toDelta());
1282
+ }
1283
+ function elementTextContent(el) {
1284
+ const parts = [];
1285
+ for (let i = 0; i < el.length; i++) {
1286
+ const child = el.get(i);
1287
+ if (child instanceof yjs.XmlText) parts.push(xmlTextToMarkdown(child));
1288
+ else if (child instanceof yjs.XmlElement) parts.push(elementTextContent(child));
1289
+ }
1290
+ return parts.join("");
1291
+ }
1292
+ function serializeElement(el, indent = "") {
1293
+ switch (el.nodeName) {
1294
+ case "documentHeader":
1295
+ case "documentMeta": return "";
1296
+ case "heading": {
1297
+ const level = Number(el.getAttribute("level")) || 1;
1298
+ return `${"#".repeat(level)} ${elementTextContent(el)}`;
1299
+ }
1300
+ case "paragraph": return elementTextContent(el);
1301
+ case "bulletList": return serializeList(el, "bullet", indent);
1302
+ case "orderedList": return serializeList(el, "ordered", indent);
1303
+ case "taskList": return serializeTaskList(el, indent);
1304
+ case "codeBlock": return `\`\`\`${el.getAttribute("language") || ""}\n${elementTextContent(el)}\n\`\`\``;
1305
+ case "blockquote": {
1306
+ const lines = [];
1307
+ for (let i = 0; i < el.length; i++) {
1308
+ const child = el.get(i);
1309
+ if (child instanceof yjs.XmlElement) lines.push(serializeElement(child, indent));
1310
+ }
1311
+ return lines.map((l) => `> ${l}`).join("\n");
1312
+ }
1313
+ case "horizontalRule": return "---";
1314
+ case "table": return serializeTable(el);
1315
+ case "docEmbed": {
1316
+ const docId = el.getAttribute("docId");
1317
+ return docId ? `![[${docId}]]` : "";
1318
+ }
1319
+ case "svgEmbed": {
1320
+ const svg = el.getAttribute("svg") || "";
1321
+ const svgTitle = el.getAttribute("title") || "";
1322
+ if (!svg) return "";
1323
+ return `\`\`\`svg${svgTitle ? ` ${svgTitle}` : ""}\n${svg}\n\`\`\``;
1324
+ }
1325
+ case "image": {
1326
+ const src = el.getAttribute("src") || "";
1327
+ const alt = el.getAttribute("alt") || "";
1328
+ const w = el.getAttribute("width");
1329
+ const h = el.getAttribute("height");
1330
+ let attrs = "";
1331
+ if (w || h) {
1332
+ const parts = [];
1333
+ if (w) parts.push(`width="${w}"`);
1334
+ if (h) parts.push(`height="${h}"`);
1335
+ attrs = `{${parts.join(" ")}}`;
1336
+ }
1337
+ return `![${alt}](${src})${attrs}`;
1338
+ }
1339
+ case "callout": return `::${el.getAttribute("type") || "note"}\n${serializeChildren(el, indent)}\n::`;
1340
+ case "collapsible": {
1341
+ const label = el.getAttribute("label") || "Details";
1342
+ const open = el.getAttribute("open");
1343
+ const props = [`label="${label}"`];
1344
+ if (open === true || open === "true") props.push("open=\"true\"");
1345
+ const inner = serializeChildren(el, indent);
1346
+ return `::collapsible{${props.join(" ")}}\n${inner}\n::`;
1347
+ }
1348
+ case "steps": return `::steps\n${serializeChildren(el, indent)}\n::`;
1349
+ case "card": {
1350
+ const title = el.getAttribute("title") || "";
1351
+ const icon = el.getAttribute("icon") || "";
1352
+ const to = el.getAttribute("to") || "";
1353
+ const props = [];
1354
+ if (title) props.push(`title="${title}"`);
1355
+ if (icon) props.push(`icon="${icon}"`);
1356
+ if (to) props.push(`to="${to}"`);
1357
+ const inner = serializeChildren(el, indent);
1358
+ return `::card{${props.join(" ")}}\n${inner}\n::`;
1359
+ }
1360
+ case "cardGroup": return `::card-group\n${serializeChildren(el, indent)}\n::`;
1361
+ case "codeCollapse": return `::code-collapse\n${serializeChildren(el, indent)}\n::`;
1362
+ case "codeGroup": return `::code-group\n${serializeChildren(el, indent)}\n::`;
1363
+ case "codePreview": return `::code-preview\n${serializeChildren(el, indent)}\n::`;
1364
+ case "codeTree": return `::code-tree{files="${el.getAttribute("files") || "[]"}"}\n::`;
1365
+ case "accordion": return serializeSlottedComponent(el, "accordion", "accordionItem", "item");
1366
+ case "tabs": return serializeSlottedComponent(el, "tabs", "tabsItem", "tab");
1367
+ case "field": {
1368
+ const fieldName = el.getAttribute("name") || "";
1369
+ const fieldType = el.getAttribute("type") || "string";
1370
+ const required = el.getAttribute("required");
1371
+ const props = [];
1372
+ if (fieldName) props.push(`name="${fieldName}"`);
1373
+ props.push(`type="${fieldType}"`);
1374
+ if (required === true || required === "true") props.push("required=\"true\"");
1375
+ const inner = serializeChildren(el, indent);
1376
+ return `::field{${props.join(" ")}}\n${inner}\n::`;
1377
+ }
1378
+ case "fieldGroup": return `::field-group\n${serializeChildren(el, indent)}\n::`;
1379
+ default: return serializeChildren(el, indent);
1380
+ }
1381
+ }
1382
+ function serializeList(el, type, indent) {
1383
+ const lines = [];
1384
+ for (let i = 0; i < el.length; i++) {
1385
+ const item = el.get(i);
1386
+ if (item instanceof yjs.XmlElement && item.nodeName === "listItem") {
1387
+ const prefix = type === "bullet" ? "- " : `${i + 1}. `;
1388
+ const content = elementTextContent(item);
1389
+ lines.push(`${indent}${prefix}${content}`);
1390
+ }
1391
+ }
1392
+ return lines.join("\n");
1393
+ }
1394
+ function serializeTaskList(el, indent) {
1395
+ const lines = [];
1396
+ for (let i = 0; i < el.length; i++) {
1397
+ const item = el.get(i);
1398
+ if (item instanceof yjs.XmlElement && item.nodeName === "taskItem") {
1399
+ const checked = item.getAttribute("checked");
1400
+ const marker = checked === true || checked === "true" ? "[x]" : "[ ]";
1401
+ const content = elementTextContent(item);
1402
+ lines.push(`${indent}- ${marker} ${content}`);
1403
+ }
1404
+ }
1405
+ return lines.join("\n");
1406
+ }
1407
+ function serializeTable(el) {
1408
+ const rows = [];
1409
+ for (let i = 0; i < el.length; i++) {
1410
+ const row = el.get(i);
1411
+ if (!(row instanceof yjs.XmlElement) || row.nodeName !== "tableRow") continue;
1412
+ const cells = [];
1413
+ for (let j = 0; j < row.length; j++) {
1414
+ const cell = row.get(j);
1415
+ if (cell instanceof yjs.XmlElement) cells.push(elementTextContent(cell));
1416
+ }
1417
+ rows.push(cells);
1418
+ }
1419
+ if (!rows.length) return "";
1420
+ const headerRow = rows[0];
1421
+ const lines = [`| ${headerRow.join(" | ")} |`];
1422
+ lines.push(`| ${headerRow.map(() => "---").join(" | ")} |`);
1423
+ for (let i = 1; i < rows.length; i++) lines.push(`| ${rows[i].join(" | ")} |`);
1424
+ return lines.join("\n");
1425
+ }
1426
+ function serializeSlottedComponent(el, componentName, childNodeName, slotName) {
1427
+ const parts = [`::${componentName}`];
1428
+ for (let i = 0; i < el.length; i++) {
1429
+ const child = el.get(i);
1430
+ if (!(child instanceof yjs.XmlElement) || child.nodeName !== childNodeName) continue;
1431
+ const label = child.getAttribute("label") || `Item ${i + 1}`;
1432
+ const icon = child.getAttribute("icon") || "";
1433
+ const props = [`label="${label}"`];
1434
+ if (icon) props.push(`icon="${icon}"`);
1435
+ parts.push(`#${slotName}{${props.join(" ")}}`);
1436
+ const inner = serializeChildren(child, "");
1437
+ if (inner) parts.push(inner);
1438
+ }
1439
+ parts.push("::");
1440
+ return parts.join("\n");
1441
+ }
1442
+ function serializeChildren(el, indent) {
1443
+ const parts = [];
1444
+ for (let i = 0; i < el.length; i++) {
1445
+ const child = el.get(i);
1446
+ if (child instanceof yjs.XmlElement) {
1447
+ const serialized = serializeElement(child, indent);
1448
+ if (serialized) parts.push(serialized);
1449
+ } else if (child instanceof yjs.XmlText) {
1450
+ const text = xmlTextToMarkdown(child);
1451
+ if (text) parts.push(text);
1452
+ }
1453
+ }
1454
+ return parts.join("\n\n");
1455
+ }
1456
+ /**
1457
+ * Converts a Y.XmlFragment (TipTap document) to markdown.
1458
+ * Extracts the title from the documentHeader element.
1459
+ *
1460
+ * @returns `{ title, markdown }` where title is the H1/header text
1461
+ */
1462
+ function yjsToMarkdown(fragment) {
1463
+ let title = "Untitled";
1464
+ const bodyParts = [];
1465
+ for (let i = 0; i < fragment.length; i++) {
1466
+ const child = fragment.get(i);
1467
+ if (!(child instanceof yjs.XmlElement)) continue;
1468
+ if (child.nodeName === "documentHeader") {
1469
+ title = elementTextContent(child) || "Untitled";
1470
+ continue;
1471
+ }
1472
+ if (child.nodeName === "documentMeta") continue;
1473
+ const serialized = serializeElement(child);
1474
+ if (serialized !== "") bodyParts.push(serialized);
1475
+ }
1476
+ return {
1477
+ title,
1478
+ markdown: bodyParts.join("\n\n")
1479
+ };
1480
+ }
1481
+
1482
+ //#endregion
1483
+ //#region packages/mcp/src/converters/markdownToYjs.ts
1484
+ /**
1485
+ * Markdown → Y.js converter.
1486
+ * Ported from cou-sh/app/utils/markdownToYjs.ts with Vue dependency removed.
1487
+ */
1488
+ function parseInlineArray(raw) {
1489
+ return raw.slice(1, -1).split(",").map((s) => s.trim()).filter(Boolean);
1490
+ }
1491
+ function parseFrontmatter(markdown) {
1492
+ const noResult = {
1493
+ meta: {},
1494
+ body: markdown
1495
+ };
1496
+ const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
1497
+ if (!match) return noResult;
1498
+ const yamlBlock = match[1];
1499
+ const body = markdown.slice(match[0].length);
1500
+ const raw = {};
1501
+ const lines = yamlBlock.split("\n");
1502
+ let i = 0;
1503
+ while (i < lines.length) {
1504
+ const line = lines[i];
1505
+ const blockSeqKey = line.match(/^(\w[\w-]*):\s*$/);
1506
+ if (blockSeqKey && i + 1 < lines.length && /^\s+-\s/.test(lines[i + 1])) {
1507
+ const key = blockSeqKey[1];
1508
+ const items = [];
1509
+ i++;
1510
+ while (i < lines.length && /^\s+-\s/.test(lines[i])) {
1511
+ items.push(lines[i].replace(/^\s+-\s/, "").trim());
1512
+ i++;
1513
+ }
1514
+ raw[key] = items;
1515
+ continue;
1516
+ }
1517
+ const kvMatch = line.match(/^(\w[\w-]*):\s*(.*)$/);
1518
+ if (kvMatch) {
1519
+ const key = kvMatch[1];
1520
+ const val = kvMatch[2].trim();
1521
+ if (val.startsWith("[") && val.endsWith("]")) raw[key] = parseInlineArray(val);
1522
+ else raw[key] = val;
1523
+ }
1524
+ i++;
1525
+ }
1526
+ const meta = {};
1527
+ const getStr = (keys) => {
1528
+ for (const k of keys) {
1529
+ const v = raw[k];
1530
+ if (typeof v === "string" && v) return v;
1531
+ }
1532
+ };
1533
+ if (raw["tags"]) meta.tags = Array.isArray(raw["tags"]) ? raw["tags"] : [raw["tags"]];
1534
+ const color = getStr(["color"]);
1535
+ if (color) meta.color = color;
1536
+ const icon = getStr(["icon"]);
1537
+ if (icon) meta.icon = icon;
1538
+ const status = getStr(["status"]);
1539
+ if (status) meta.status = status;
1540
+ const priorityRaw = getStr(["priority"]);
1541
+ if (priorityRaw !== void 0) meta.priority = {
1542
+ low: 1,
1543
+ medium: 2,
1544
+ high: 3,
1545
+ urgent: 4
1546
+ }[priorityRaw.toLowerCase()] ?? (Number(priorityRaw) || 0);
1547
+ const checkedRaw = raw["checked"] ?? raw["done"];
1548
+ if (checkedRaw !== void 0) meta.checked = checkedRaw === "true" || checkedRaw === true;
1549
+ const dateStart = getStr(["date", "created"]);
1550
+ if (dateStart) meta.dateStart = dateStart;
1551
+ const dateEnd = getStr(["due"]);
1552
+ if (dateEnd) meta.dateEnd = dateEnd;
1553
+ const subtitle = getStr(["description", "subtitle"]);
1554
+ if (subtitle) meta.subtitle = subtitle;
1555
+ const url = getStr(["url"]);
1556
+ if (url) meta.url = url;
1557
+ const email = getStr(["email"]);
1558
+ if (email) meta.email = email;
1559
+ const phone = getStr(["phone"]);
1560
+ if (phone) meta.phone = phone;
1561
+ const ratingRaw = getStr(["rating"]);
1562
+ if (ratingRaw !== void 0) {
1563
+ const n = Number(ratingRaw);
1564
+ if (!Number.isNaN(n)) meta.rating = Math.min(5, Math.max(0, n));
1565
+ }
1566
+ const datetimeStart = getStr(["datetimeStart"]);
1567
+ if (datetimeStart) meta.datetimeStart = datetimeStart;
1568
+ const datetimeEnd = getStr(["datetimeEnd"]);
1569
+ if (datetimeEnd) meta.datetimeEnd = datetimeEnd;
1570
+ const allDayRaw = raw["allDay"];
1571
+ if (allDayRaw !== void 0) meta.allDay = allDayRaw === "true" || allDayRaw === true;
1572
+ const geoLatRaw = getStr(["geoLat"]);
1573
+ if (geoLatRaw !== void 0) {
1574
+ const n = Number(geoLatRaw);
1575
+ if (!Number.isNaN(n)) meta.geoLat = n;
1576
+ }
1577
+ const geoLngRaw = getStr(["geoLng"]);
1578
+ if (geoLngRaw !== void 0) {
1579
+ const n = Number(geoLngRaw);
1580
+ if (!Number.isNaN(n)) meta.geoLng = n;
1581
+ }
1582
+ const geoType = getStr(["geoType"]);
1583
+ if (geoType && (geoType === "marker" || geoType === "line" || geoType === "measure")) meta.geoType = geoType;
1584
+ const geoDescription = getStr(["geoDescription"]);
1585
+ if (geoDescription) meta.geoDescription = geoDescription;
1586
+ const numberRaw = getStr(["number"]);
1587
+ if (numberRaw !== void 0) {
1588
+ const n = Number(numberRaw);
1589
+ if (!Number.isNaN(n)) meta.number = n;
1590
+ }
1591
+ const unit = getStr(["unit"]);
1592
+ if (unit) meta.unit = unit;
1593
+ const note = getStr(["note"]);
1594
+ if (note) meta.note = note;
1595
+ return {
1596
+ title: typeof raw["title"] === "string" ? raw["title"] : void 0,
1597
+ meta,
1598
+ body
1599
+ };
1600
+ }
1601
+ function parseInline(text) {
1602
+ const stripped = text.replace(/\{lang="[^"]*"\}/g, "").replace(/:(?!badge|icon|kbd)(\w[\w-]*)\[([^\]]*)\](\{[^}]*\})?/g, "$2").replace(/:(?!badge|icon|kbd)(\w[\w-]*)(\{[^}]*\})/g, "");
1603
+ const tokens = [];
1604
+ const re = /:badge\[([^\]]*)\](\{[^}]*\})?|:icon\{([^}]*)\}|:kbd\{([^}]*)\}|!?\[\[([^\]|]+?)(?:\|([^\]]+?))?\]\]|~~(.+?)~~|\*\*(.+?)\*\*|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)/g;
1605
+ let lastIndex = 0;
1606
+ let match;
1607
+ while ((match = re.exec(stripped)) !== null) {
1608
+ if (match.index > lastIndex) tokens.push({ text: stripped.slice(lastIndex, match.index) });
1609
+ if (match[1] !== void 0) {
1610
+ const badgeProps = parseMdcProps(match[2]);
1611
+ tokens.push({
1612
+ text: match[1] || "Badge",
1613
+ attrs: { badge: {
1614
+ label: match[1] || "Badge",
1615
+ color: badgeProps["color"] || "neutral",
1616
+ variant: badgeProps["variant"] || "subtle"
1617
+ } }
1618
+ });
1619
+ } else if (match[3] !== void 0) {
1620
+ const iconProps = parseMdcProps(`{${match[3]}}`);
1621
+ tokens.push({
1622
+ text: "​",
1623
+ attrs: { proseIcon: { name: iconProps["name"] || "i-lucide-star" } }
1624
+ });
1625
+ } else if (match[4] !== void 0) {
1626
+ const kbdProps = parseMdcProps(`{${match[4]}}`);
1627
+ tokens.push({
1628
+ text: kbdProps["value"] || "",
1629
+ attrs: { kbd: { value: kbdProps["value"] || "" } }
1630
+ });
1631
+ } else if (match[5] !== void 0) {
1632
+ const docId = match[5];
1633
+ const displayText = match[6] ?? docId;
1634
+ tokens.push({
1635
+ text: displayText,
1636
+ attrs: { link: { href: `/doc/${docId}` } }
1637
+ });
1638
+ } else if (match[7] !== void 0) tokens.push({
1639
+ text: match[7],
1640
+ attrs: { strike: true }
1641
+ });
1642
+ else if (match[8] !== void 0) tokens.push({
1643
+ text: match[8],
1644
+ attrs: { bold: true }
1645
+ });
1646
+ else if (match[9] !== void 0) tokens.push({
1647
+ text: match[9],
1648
+ attrs: { italic: true }
1649
+ });
1650
+ else if (match[10] !== void 0) tokens.push({
1651
+ text: match[10],
1652
+ attrs: { italic: true }
1653
+ });
1654
+ else if (match[11] !== void 0) tokens.push({
1655
+ text: match[11],
1656
+ attrs: { code: true }
1657
+ });
1658
+ else if (match[12] !== void 0 && match[13] !== void 0) tokens.push({
1659
+ text: match[12],
1660
+ attrs: { link: { href: match[13] } }
1661
+ });
1662
+ lastIndex = match.index + match[0].length;
1663
+ }
1664
+ if (lastIndex < stripped.length) tokens.push({ text: stripped.slice(lastIndex) });
1665
+ return tokens.filter((t) => t.text.length > 0);
1666
+ }
1667
+ function parseTableRow(line) {
1668
+ const parts = line.split("|");
1669
+ return parts.slice(1, parts.length - 1).map((c) => c.trim());
1670
+ }
1671
+ function isTableSeparator(line) {
1672
+ return /^\|[\s|:-]+\|$/.test(line.trim());
1673
+ }
1674
+ function extractFencedCode(lines) {
1675
+ const result = [];
1676
+ let i = 0;
1677
+ while (i < lines.length) {
1678
+ const fenceMatch = lines[i].match(/^(`{3,})(\w*)/);
1679
+ if (fenceMatch) {
1680
+ const fence = fenceMatch[1];
1681
+ const lang = fenceMatch[2] ?? "";
1682
+ const codeLines = [];
1683
+ i++;
1684
+ while (i < lines.length && !lines[i].startsWith(fence)) {
1685
+ codeLines.push(lines[i]);
1686
+ i++;
1687
+ }
1688
+ i++;
1689
+ result.push({
1690
+ type: "codeBlock",
1691
+ lang,
1692
+ code: codeLines.join("\n")
1693
+ });
1694
+ continue;
1695
+ }
1696
+ i++;
1697
+ }
1698
+ return result;
1699
+ }
1700
+ function parseMdcProps(propsStr) {
1701
+ if (!propsStr) return {};
1702
+ const result = {};
1703
+ const re = /(\w[\w-]*)="([^"]*)"/g;
1704
+ let m;
1705
+ while ((m = re.exec(propsStr)) !== null) result[m[1]] = m[2];
1706
+ return result;
1707
+ }
1708
+ function parseMdcChildren(innerLines, slotPrefix) {
1709
+ const items = [];
1710
+ let current = null;
1711
+ const slotRe = new RegExp(`^#${slotPrefix}(\\{[^}]*\\})?\\s*$`);
1712
+ for (const line of innerLines) {
1713
+ const slotMatch = line.match(slotRe);
1714
+ if (slotMatch) {
1715
+ if (current) items.push(current);
1716
+ const props = parseMdcProps(slotMatch[1]);
1717
+ current = {
1718
+ label: props["label"] || props["title"] || `Item ${items.length + 1}`,
1719
+ icon: props["icon"] || "",
1720
+ lines: []
1721
+ };
1722
+ continue;
1723
+ }
1724
+ if (current) current.lines.push(line);
1725
+ else if (!items.length && !current) current = {
1726
+ label: `Item 1`,
1727
+ icon: "",
1728
+ lines: [line]
1729
+ };
1730
+ }
1731
+ if (current) items.push(current);
1732
+ return items.map((item) => ({
1733
+ label: item.label,
1734
+ icon: item.icon,
1735
+ innerBlocks: parseBlocks(item.lines.join("\n"))
1736
+ }));
1737
+ }
1738
+ const TASK_RE = /^[-*+]\s+\[([ xX])\]\s+(.*)/;
1739
+ function parseBlocks(markdown) {
1740
+ const rawLines = markdown.split("\n");
1741
+ let firstContentLine = 0;
1742
+ while (firstContentLine < rawLines.length) {
1743
+ const l = rawLines[firstContentLine];
1744
+ if (l.trim() === "" || /^import\s/.test(l) || /^export\s/.test(l)) firstContentLine++;
1745
+ else break;
1746
+ }
1747
+ const stripped = rawLines.slice(firstContentLine).join("\n");
1748
+ const blocks = [];
1749
+ const lines = stripped.split("\n");
1750
+ let i = 0;
1751
+ while (i < lines.length) {
1752
+ const line = lines[i];
1753
+ const fenceBlockMatch = line.match(/^(`{3,})(.*)$/);
1754
+ if (fenceBlockMatch) {
1755
+ const fence = fenceBlockMatch[1];
1756
+ const lang = fenceBlockMatch[2].trim().replace(/\{[^}]*\}$/, "").replace(/\s*\[.*\]$/, "").trim();
1757
+ const codeLines = [];
1758
+ i++;
1759
+ while (i < lines.length && !lines[i].startsWith(fence)) {
1760
+ codeLines.push(lines[i]);
1761
+ i++;
1762
+ }
1763
+ i++;
1764
+ if (lang === "svg" || lang.startsWith("svg ")) {
1765
+ const svgTitle = lang === "svg" ? "" : lang.slice(4).trim();
1766
+ blocks.push({
1767
+ type: "svgEmbed",
1768
+ svg: codeLines.join("\n"),
1769
+ title: svgTitle
1770
+ });
1771
+ } else blocks.push({
1772
+ type: "codeBlock",
1773
+ lang,
1774
+ code: codeLines.join("\n")
1775
+ });
1776
+ continue;
1777
+ }
1778
+ const headingMatch = line.match(/^(#{1,6})\s+(.*)/);
1779
+ if (headingMatch) {
1780
+ blocks.push({
1781
+ type: "heading",
1782
+ level: headingMatch[1].length,
1783
+ text: headingMatch[2].trim()
1784
+ });
1785
+ i++;
1786
+ continue;
1787
+ }
1788
+ if (/^[-*_]{3,}\s*$/.test(line)) {
1789
+ blocks.push({ type: "hr" });
1790
+ i++;
1791
+ continue;
1792
+ }
1793
+ const docEmbedMatch = line.match(/^!\[\[([^\]|]+?)(?:\|[^\]]*?)?\]\]\s*$/);
1794
+ if (docEmbedMatch) {
1795
+ blocks.push({
1796
+ type: "docEmbed",
1797
+ docId: docEmbedMatch[1]
1798
+ });
1799
+ i++;
1800
+ continue;
1801
+ }
1802
+ const imgMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)(\{[^}]*\})?\s*$/);
1803
+ if (imgMatch) {
1804
+ const alt = imgMatch[1] ?? "";
1805
+ const src = imgMatch[2] ?? "";
1806
+ const attrs = parseMdcProps(imgMatch[3]);
1807
+ blocks.push({
1808
+ type: "image",
1809
+ src,
1810
+ alt,
1811
+ width: attrs["width"],
1812
+ height: attrs["height"]
1813
+ });
1814
+ i++;
1815
+ continue;
1816
+ }
1817
+ if (line.startsWith("> ") || line === ">") {
1818
+ const bqLines = [];
1819
+ while (i < lines.length && (lines[i].startsWith("> ") || lines[i] === ">")) {
1820
+ bqLines.push(lines[i].replace(/^>\s?/, ""));
1821
+ i++;
1822
+ }
1823
+ blocks.push({
1824
+ type: "blockquote",
1825
+ lines: bqLines
1826
+ });
1827
+ continue;
1828
+ }
1829
+ if (/^\s*\|/.test(line)) {
1830
+ const tableLines = [];
1831
+ while (i < lines.length && /^\s*\|/.test(lines[i])) {
1832
+ tableLines.push(lines[i]);
1833
+ i++;
1834
+ }
1835
+ if (tableLines.length >= 2 && isTableSeparator(tableLines[1])) {
1836
+ const headerRow = parseTableRow(tableLines[0]);
1837
+ const dataRows = tableLines.slice(2).filter((l) => !isTableSeparator(l)).map(parseTableRow);
1838
+ blocks.push({
1839
+ type: "table",
1840
+ headerRow,
1841
+ dataRows
1842
+ });
1843
+ } else for (const l of tableLines) blocks.push({
1844
+ type: "paragraph",
1845
+ text: l
1846
+ });
1847
+ continue;
1848
+ }
1849
+ const MDC_OPEN = /^\s*(:{2,})(\w[\w-]*)(\{[^}]*\})?\s*$/;
1850
+ if (MDC_OPEN.test(line)) {
1851
+ const colons = line.match(/^\s*(:+)/)?.[1]?.length ?? 2;
1852
+ const componentName = line.match(/^\s*:{2,}(\w[\w-]*)/)?.[1] ?? "";
1853
+ const innerLines = [];
1854
+ i++;
1855
+ while (i < lines.length) {
1856
+ const l = lines[i];
1857
+ if (new RegExp(`^\\s*:{${colons}}\\s*$`).test(l)) {
1858
+ i++;
1859
+ break;
1860
+ }
1861
+ const innerFence = l.match(/^(\s*`{3,})/);
1862
+ if (innerFence) {
1863
+ const fenceStr = innerFence[1].trimStart();
1864
+ innerLines.push(l);
1865
+ i++;
1866
+ while (i < lines.length && !lines[i].trimStart().startsWith(fenceStr)) {
1867
+ innerLines.push(lines[i]);
1868
+ i++;
1869
+ }
1870
+ if (i < lines.length) {
1871
+ innerLines.push(lines[i]);
1872
+ i++;
1873
+ }
1874
+ continue;
1875
+ }
1876
+ innerLines.push(l);
1877
+ i++;
1878
+ }
1879
+ const nonBlank = innerLines.filter((l) => l.trim().length > 0);
1880
+ if (nonBlank.length) {
1881
+ const minIndent = Math.min(...nonBlank.map((l) => l.match(/^(\s*)/)?.[1]?.length ?? 0));
1882
+ if (minIndent > 0) for (let j = 0; j < innerLines.length; j++) innerLines[j] = innerLines[j].slice(Math.min(minIndent, innerLines[j].length));
1883
+ }
1884
+ let contentStart = 0;
1885
+ if (innerLines[0]?.trim() === "---") {
1886
+ const fmEnd = innerLines.findIndex((l, idx) => idx > 0 && l.trim() === "---");
1887
+ if (fmEnd !== -1) contentStart = fmEnd + 1;
1888
+ }
1889
+ const contentLines = innerLines.slice(contentStart);
1890
+ const defaultSlotLines = [];
1891
+ const codeSlotLines = [];
1892
+ let currentSlot = "default";
1893
+ for (const l of contentLines) {
1894
+ if (/^#code\s*$/.test(l)) {
1895
+ currentSlot = "code";
1896
+ continue;
1897
+ }
1898
+ if (/^#\w+/.test(l) && !/^#{2,}\s/.test(l)) {
1899
+ currentSlot = "other";
1900
+ continue;
1901
+ }
1902
+ if (currentSlot === "default") defaultSlotLines.push(l);
1903
+ else if (currentSlot === "code") codeSlotLines.push(l);
1904
+ }
1905
+ const innerBlocks = parseBlocks(defaultSlotLines.join("\n"));
1906
+ const codeBlocks = extractFencedCode(codeSlotLines);
1907
+ if (new Set([
1908
+ "tip",
1909
+ "note",
1910
+ "info",
1911
+ "warning",
1912
+ "caution",
1913
+ "danger",
1914
+ "callout",
1915
+ "alert"
1916
+ ]).has(componentName.toLowerCase())) blocks.push({
1917
+ type: "callout",
1918
+ calloutType: componentName.toLowerCase(),
1919
+ innerBlocks
1920
+ });
1921
+ else {
1922
+ const mdcProps = parseMdcProps(line.match(MDC_OPEN)?.[3]);
1923
+ const lc = componentName.toLowerCase();
1924
+ if (lc === "collapsible") blocks.push({
1925
+ type: "collapsible",
1926
+ label: mdcProps["label"] || "Details",
1927
+ open: mdcProps["open"] === "true",
1928
+ innerBlocks
1929
+ });
1930
+ else if (lc === "steps") blocks.push({
1931
+ type: "steps",
1932
+ innerBlocks
1933
+ });
1934
+ else if (lc === "card") blocks.push({
1935
+ type: "card",
1936
+ title: mdcProps["title"] || "",
1937
+ icon: mdcProps["icon"] || "",
1938
+ to: mdcProps["to"] || "",
1939
+ innerBlocks
1940
+ });
1941
+ else if (lc === "card-group") {
1942
+ const cards = innerBlocks.filter((b) => b.type === "card");
1943
+ if (cards.length) blocks.push({
1944
+ type: "cardGroup",
1945
+ cards
1946
+ });
1947
+ else blocks.push(...innerBlocks);
1948
+ } else if (lc === "code-collapse") blocks.push({
1949
+ type: "codeCollapse",
1950
+ codeBlocks: codeBlocks.length ? codeBlocks : innerBlocks.filter((b) => b.type === "codeBlock")
1951
+ });
1952
+ else if (lc === "code-group") {
1953
+ const allCode = [...innerBlocks.filter((b) => b.type === "codeBlock"), ...codeBlocks];
1954
+ blocks.push({
1955
+ type: "codeGroup",
1956
+ codeBlocks: allCode
1957
+ });
1958
+ } else if (lc === "code-preview") blocks.push({
1959
+ type: "codePreview",
1960
+ innerBlocks,
1961
+ codeBlocks
1962
+ });
1963
+ else if (lc === "code-tree") blocks.push({
1964
+ type: "codeTree",
1965
+ files: mdcProps["files"] || "[]"
1966
+ });
1967
+ else if (lc === "accordion") {
1968
+ const items = parseMdcChildren(contentLines, "item");
1969
+ if (items.length) blocks.push({
1970
+ type: "accordion",
1971
+ items
1972
+ });
1973
+ else blocks.push({
1974
+ type: "accordion",
1975
+ items: [{
1976
+ label: "Item 1",
1977
+ icon: "",
1978
+ innerBlocks
1979
+ }]
1980
+ });
1981
+ } else if (lc === "tabs") {
1982
+ const items = parseMdcChildren(contentLines, "tab");
1983
+ if (items.length) blocks.push({
1984
+ type: "tabs",
1985
+ items
1986
+ });
1987
+ else blocks.push({
1988
+ type: "tabs",
1989
+ items: [{
1990
+ label: "Tab 1",
1991
+ icon: "",
1992
+ innerBlocks
1993
+ }]
1994
+ });
1995
+ } else if (lc === "field") blocks.push({
1996
+ type: "field",
1997
+ name: mdcProps["name"] || "",
1998
+ fieldType: mdcProps["type"] || "string",
1999
+ required: mdcProps["required"] === "true",
2000
+ innerBlocks
2001
+ });
2002
+ else if (lc === "field-group") {
2003
+ const fields = innerBlocks.filter((b) => b.type === "field");
2004
+ if (fields.length) blocks.push({
2005
+ type: "fieldGroup",
2006
+ fields
2007
+ });
2008
+ else blocks.push(...innerBlocks);
2009
+ } else {
2010
+ blocks.push(...innerBlocks);
2011
+ blocks.push(...codeBlocks);
2012
+ }
2013
+ }
2014
+ continue;
2015
+ }
2016
+ if (TASK_RE.test(line)) {
2017
+ const items = [];
2018
+ while (i < lines.length && TASK_RE.test(lines[i])) {
2019
+ const m = lines[i].match(TASK_RE);
2020
+ items.push({
2021
+ checked: m[1].toLowerCase() === "x",
2022
+ text: m[2]
2023
+ });
2024
+ i++;
2025
+ }
2026
+ blocks.push({
2027
+ type: "taskList",
2028
+ items
2029
+ });
2030
+ continue;
2031
+ }
2032
+ if (/^[-*+]\s+/.test(line)) {
2033
+ const items = [];
2034
+ while (i < lines.length && /^[-*+]\s+/.test(lines[i]) && !TASK_RE.test(lines[i])) {
2035
+ items.push(lines[i].replace(/^[-*+]\s+/, ""));
2036
+ i++;
2037
+ }
2038
+ if (items.length) {
2039
+ blocks.push({
2040
+ type: "bulletList",
2041
+ items
2042
+ });
2043
+ continue;
2044
+ }
2045
+ }
2046
+ if (/^\d+\.\s+/.test(line)) {
2047
+ const items = [];
2048
+ while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
2049
+ items.push(lines[i].replace(/^\d+\.\s+/, ""));
2050
+ i++;
2051
+ }
2052
+ blocks.push({
2053
+ type: "orderedList",
2054
+ items
2055
+ });
2056
+ continue;
2057
+ }
2058
+ if (line.trim() === "") {
2059
+ i++;
2060
+ continue;
2061
+ }
2062
+ const paraLines = [];
2063
+ while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6}\s|[-*+]\s|\d+\.\s|>|`{3,}|\s*\||[-*_]{3,}\s*$|\s*:{2,}\w)/.test(lines[i])) {
2064
+ paraLines.push(lines[i]);
2065
+ i++;
2066
+ }
2067
+ if (paraLines.length) blocks.push({
2068
+ type: "paragraph",
2069
+ text: paraLines.join(" ")
2070
+ });
2071
+ }
2072
+ return blocks;
2073
+ }
2074
+ function fillTextInto(el, tokens) {
2075
+ const filtered = tokens.filter((t) => t.text.length > 0);
2076
+ if (!filtered.length) return;
2077
+ const xtNodes = filtered.map(() => new yjs.XmlText());
2078
+ el.insert(0, xtNodes);
2079
+ filtered.forEach((tok, i) => {
2080
+ if (tok.attrs) xtNodes[i].insert(0, tok.text, tok.attrs);
2081
+ else xtNodes[i].insert(0, tok.text);
2082
+ });
2083
+ }
2084
+ function blockElName(b) {
2085
+ switch (b.type) {
2086
+ case "heading": return "heading";
2087
+ case "paragraph": return "paragraph";
2088
+ case "bulletList": return "bulletList";
2089
+ case "orderedList": return "orderedList";
2090
+ case "taskList": return "taskList";
2091
+ case "codeBlock": return "codeBlock";
2092
+ case "blockquote": return "blockquote";
2093
+ case "table": return "table";
2094
+ case "hr": return "horizontalRule";
2095
+ case "callout": return "callout";
2096
+ case "collapsible": return "collapsible";
2097
+ case "steps": return "steps";
2098
+ case "card": return "card";
2099
+ case "cardGroup": return "cardGroup";
2100
+ case "codeCollapse": return "codeCollapse";
2101
+ case "codeGroup": return "codeGroup";
2102
+ case "codePreview": return "codePreview";
2103
+ case "codeTree": return "codeTree";
2104
+ case "accordion": return "accordion";
2105
+ case "tabs": return "tabs";
2106
+ case "field": return "field";
2107
+ case "fieldGroup": return "fieldGroup";
2108
+ case "image": return "image";
2109
+ case "docEmbed": return "docEmbed";
2110
+ case "svgEmbed": return "svgEmbed";
2111
+ }
2112
+ }
2113
+ function fillBlock(el, block) {
2114
+ switch (block.type) {
2115
+ case "heading":
2116
+ el.setAttribute("level", block.level);
2117
+ fillTextInto(el, parseInline(block.text));
2118
+ break;
2119
+ case "paragraph":
2120
+ fillTextInto(el, parseInline(block.text));
2121
+ break;
2122
+ case "bulletList":
2123
+ case "orderedList": {
2124
+ const listItemEls = block.items.map(() => new yjs.XmlElement("listItem"));
2125
+ el.insert(0, listItemEls);
2126
+ block.items.forEach((text, i) => {
2127
+ const paraEl = new yjs.XmlElement("paragraph");
2128
+ listItemEls[i].insert(0, [paraEl]);
2129
+ fillTextInto(paraEl, parseInline(text));
2130
+ });
2131
+ break;
2132
+ }
2133
+ case "taskList": {
2134
+ const taskItemEls = block.items.map(() => new yjs.XmlElement("taskItem"));
2135
+ el.insert(0, taskItemEls);
2136
+ block.items.forEach((item, i) => {
2137
+ taskItemEls[i].setAttribute("checked", item.checked);
2138
+ const paraEl = new yjs.XmlElement("paragraph");
2139
+ taskItemEls[i].insert(0, [paraEl]);
2140
+ fillTextInto(paraEl, parseInline(item.text));
2141
+ });
2142
+ break;
2143
+ }
2144
+ case "codeBlock": {
2145
+ if (block.lang) el.setAttribute("language", block.lang);
2146
+ const xt = new yjs.XmlText();
2147
+ el.insert(0, [xt]);
2148
+ xt.insert(0, block.code);
2149
+ break;
2150
+ }
2151
+ case "blockquote": {
2152
+ const paraEls = block.lines.map(() => new yjs.XmlElement("paragraph"));
2153
+ el.insert(0, paraEls);
2154
+ block.lines.forEach((line, i) => fillTextInto(paraEls[i], parseInline(line)));
2155
+ break;
2156
+ }
2157
+ case "table": {
2158
+ const headerRowEl = new yjs.XmlElement("tableRow");
2159
+ const dataRowEls = block.dataRows.map(() => new yjs.XmlElement("tableRow"));
2160
+ el.insert(0, [headerRowEl, ...dataRowEls]);
2161
+ const headerCellEls = block.headerRow.map(() => new yjs.XmlElement("tableHeader"));
2162
+ headerRowEl.insert(0, headerCellEls);
2163
+ block.headerRow.forEach((cellText, i) => {
2164
+ const paraEl = new yjs.XmlElement("paragraph");
2165
+ headerCellEls[i].insert(0, [paraEl]);
2166
+ fillTextInto(paraEl, parseInline(cellText));
2167
+ });
2168
+ block.dataRows.forEach((row, ri) => {
2169
+ const cellEls = row.map(() => new yjs.XmlElement("tableCell"));
2170
+ dataRowEls[ri].insert(0, cellEls);
2171
+ row.forEach((cellText, ci) => {
2172
+ const paraEl = new yjs.XmlElement("paragraph");
2173
+ cellEls[ci].insert(0, [paraEl]);
2174
+ fillTextInto(paraEl, parseInline(cellText));
2175
+ });
2176
+ });
2177
+ break;
2178
+ }
2179
+ case "hr": break;
2180
+ case "callout": {
2181
+ el.setAttribute("type", block.calloutType);
2182
+ if (!block.innerBlocks.length) {
2183
+ const paraEl = new yjs.XmlElement("paragraph");
2184
+ el.insert(0, [paraEl]);
2185
+ break;
2186
+ }
2187
+ const innerEls = block.innerBlocks.map((b) => new yjs.XmlElement(blockElName(b)));
2188
+ el.insert(0, innerEls);
2189
+ block.innerBlocks.forEach((b, i) => fillBlock(innerEls[i], b));
2190
+ break;
2191
+ }
2192
+ case "collapsible": {
2193
+ el.setAttribute("label", block.label);
2194
+ el.setAttribute("open", block.open);
2195
+ const inner = block.innerBlocks.length ? block.innerBlocks : [{
2196
+ type: "paragraph",
2197
+ text: ""
2198
+ }];
2199
+ const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
2200
+ el.insert(0, innerEls);
2201
+ inner.forEach((b, i) => fillBlock(innerEls[i], b));
2202
+ break;
2203
+ }
2204
+ case "steps": {
2205
+ const inner = block.innerBlocks.length ? block.innerBlocks : [{
2206
+ type: "paragraph",
2207
+ text: ""
2208
+ }];
2209
+ const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
2210
+ el.insert(0, innerEls);
2211
+ inner.forEach((b, i) => fillBlock(innerEls[i], b));
2212
+ break;
2213
+ }
2214
+ case "card": {
2215
+ if (block.title) el.setAttribute("title", block.title);
2216
+ if (block.icon) el.setAttribute("icon", block.icon);
2217
+ if (block.to) el.setAttribute("to", block.to);
2218
+ const inner = block.innerBlocks.length ? block.innerBlocks : [{
2219
+ type: "paragraph",
2220
+ text: ""
2221
+ }];
2222
+ const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
2223
+ el.insert(0, innerEls);
2224
+ inner.forEach((b, i) => fillBlock(innerEls[i], b));
2225
+ break;
2226
+ }
2227
+ case "cardGroup": {
2228
+ const cardEls = block.cards.map((b) => new yjs.XmlElement(blockElName(b)));
2229
+ el.insert(0, cardEls);
2230
+ block.cards.forEach((b, i) => fillBlock(cardEls[i], b));
2231
+ break;
2232
+ }
2233
+ case "codeCollapse": {
2234
+ const codes = block.codeBlocks.length ? block.codeBlocks : [{
2235
+ type: "codeBlock",
2236
+ lang: "",
2237
+ code: ""
2238
+ }];
2239
+ const codeEl = new yjs.XmlElement("codeBlock");
2240
+ el.insert(0, [codeEl]);
2241
+ fillBlock(codeEl, codes[0]);
2242
+ break;
2243
+ }
2244
+ case "codeGroup": {
2245
+ const codes = block.codeBlocks.length ? block.codeBlocks : [{
2246
+ type: "codeBlock",
2247
+ lang: "",
2248
+ code: ""
2249
+ }];
2250
+ const codeEls = codes.map(() => new yjs.XmlElement("codeBlock"));
2251
+ el.insert(0, codeEls);
2252
+ codes.forEach((b, i) => fillBlock(codeEls[i], b));
2253
+ break;
2254
+ }
2255
+ case "codePreview": {
2256
+ const all = [...block.innerBlocks, ...block.codeBlocks];
2257
+ const inner = all.length ? all : [{
2258
+ type: "paragraph",
2259
+ text: ""
2260
+ }];
2261
+ const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
2262
+ el.insert(0, innerEls);
2263
+ inner.forEach((b, i) => fillBlock(innerEls[i], b));
2264
+ break;
2265
+ }
2266
+ case "codeTree":
2267
+ el.setAttribute("files", block.files);
2268
+ break;
2269
+ case "accordion": {
2270
+ const itemEls = block.items.map(() => new yjs.XmlElement("accordionItem"));
2271
+ el.insert(0, itemEls);
2272
+ block.items.forEach((item, i) => {
2273
+ itemEls[i].setAttribute("label", item.label);
2274
+ if (item.icon) itemEls[i].setAttribute("icon", item.icon);
2275
+ const inner = item.innerBlocks.length ? item.innerBlocks : [{
2276
+ type: "paragraph",
2277
+ text: ""
2278
+ }];
2279
+ const childEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
2280
+ itemEls[i].insert(0, childEls);
2281
+ inner.forEach((b, ci) => fillBlock(childEls[ci], b));
2282
+ });
2283
+ break;
2284
+ }
2285
+ case "tabs": {
2286
+ const itemEls = block.items.map(() => new yjs.XmlElement("tabsItem"));
2287
+ el.insert(0, itemEls);
2288
+ block.items.forEach((item, i) => {
2289
+ itemEls[i].setAttribute("label", item.label);
2290
+ if (item.icon) itemEls[i].setAttribute("icon", item.icon);
2291
+ const inner = item.innerBlocks.length ? item.innerBlocks : [{
2292
+ type: "paragraph",
2293
+ text: ""
2294
+ }];
2295
+ const childEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
2296
+ itemEls[i].insert(0, childEls);
2297
+ inner.forEach((b, ci) => fillBlock(childEls[ci], b));
2298
+ });
2299
+ break;
2300
+ }
2301
+ case "field": {
2302
+ if (block.name) el.setAttribute("name", block.name);
2303
+ el.setAttribute("type", block.fieldType);
2304
+ el.setAttribute("required", block.required);
2305
+ const inner = block.innerBlocks.length ? block.innerBlocks : [{
2306
+ type: "paragraph",
2307
+ text: ""
2308
+ }];
2309
+ const innerEls = inner.map((b) => new yjs.XmlElement(blockElName(b)));
2310
+ el.insert(0, innerEls);
2311
+ inner.forEach((b, i) => fillBlock(innerEls[i], b));
2312
+ break;
2313
+ }
2314
+ case "fieldGroup": {
2315
+ const fieldEls = block.fields.map((b) => new yjs.XmlElement(blockElName(b)));
2316
+ el.insert(0, fieldEls);
2317
+ block.fields.forEach((b, i) => fillBlock(fieldEls[i], b));
2318
+ break;
2319
+ }
2320
+ case "image":
2321
+ el.setAttribute("src", block.src);
2322
+ if (block.alt) el.setAttribute("alt", block.alt);
2323
+ if (block.width) el.setAttribute("width", block.width);
2324
+ if (block.height) el.setAttribute("height", block.height);
2325
+ break;
2326
+ case "docEmbed":
2327
+ el.setAttribute("docId", block.docId);
2328
+ break;
2329
+ case "svgEmbed":
2330
+ el.setAttribute("svg", block.svg);
2331
+ if (block.title) el.setAttribute("title", block.title);
2332
+ break;
2333
+ }
2334
+ }
2335
+ function populateYDocFromMarkdown(fragment, markdown, fallbackTitle = "Untitled") {
2336
+ const ydoc = fragment.doc;
2337
+ if (!ydoc) {
2338
+ console.warn("[markdownToYjs] fragment has no doc — skipping population");
2339
+ return;
2340
+ }
2341
+ const blocks = parseBlocks(markdown);
2342
+ let title = fallbackTitle;
2343
+ let contentBlocks = blocks;
2344
+ const h1 = blocks.findIndex((b) => b.type === "heading" && b.level === 1);
2345
+ if (h1 !== -1) {
2346
+ title = blocks[h1].text;
2347
+ contentBlocks = blocks.filter((_, i) => i !== h1);
2348
+ }
2349
+ if (!contentBlocks.length) contentBlocks = [{
2350
+ type: "paragraph",
2351
+ text: ""
2352
+ }];
2353
+ ydoc.transact(() => {
2354
+ const headerEl = new yjs.XmlElement("documentHeader");
2355
+ const metaEl = new yjs.XmlElement("documentMeta");
2356
+ const bodyEls = contentBlocks.map((b) => {
2357
+ switch (b.type) {
2358
+ case "heading": return new yjs.XmlElement("heading");
2359
+ case "paragraph": return new yjs.XmlElement("paragraph");
2360
+ case "bulletList": return new yjs.XmlElement("bulletList");
2361
+ case "orderedList": return new yjs.XmlElement("orderedList");
2362
+ case "taskList": return new yjs.XmlElement("taskList");
2363
+ case "codeBlock": return new yjs.XmlElement("codeBlock");
2364
+ case "blockquote": return new yjs.XmlElement("blockquote");
2365
+ case "table": return new yjs.XmlElement("table");
2366
+ case "hr": return new yjs.XmlElement("horizontalRule");
2367
+ case "callout": return new yjs.XmlElement("callout");
2368
+ case "collapsible": return new yjs.XmlElement("collapsible");
2369
+ case "steps": return new yjs.XmlElement("steps");
2370
+ case "card": return new yjs.XmlElement("card");
2371
+ case "cardGroup": return new yjs.XmlElement("cardGroup");
2372
+ case "codeCollapse": return new yjs.XmlElement("codeCollapse");
2373
+ case "codeGroup": return new yjs.XmlElement("codeGroup");
2374
+ case "codePreview": return new yjs.XmlElement("codePreview");
2375
+ case "codeTree": return new yjs.XmlElement("codeTree");
2376
+ case "accordion": return new yjs.XmlElement("accordion");
2377
+ case "tabs": return new yjs.XmlElement("tabs");
2378
+ case "field": return new yjs.XmlElement("field");
2379
+ case "fieldGroup": return new yjs.XmlElement("fieldGroup");
2380
+ case "image": return new yjs.XmlElement("image");
2381
+ case "docEmbed": return new yjs.XmlElement("docEmbed");
2382
+ case "svgEmbed": return new yjs.XmlElement("svgEmbed");
2383
+ }
2384
+ });
2385
+ fragment.insert(0, [
2386
+ headerEl,
2387
+ metaEl,
2388
+ ...bodyEls
2389
+ ]);
2390
+ const headerXt = new yjs.XmlText();
2391
+ headerEl.insert(0, [headerXt]);
2392
+ headerXt.insert(0, title);
2393
+ contentBlocks.forEach((block, i) => fillBlock(bodyEls[i], block));
2394
+ });
2395
+ }
2396
+
2397
+ //#endregion
2398
+ //#region packages/cli/src/commands/documents.ts
2399
+ /**
2400
+ * Document CRUD commands: read, create, rename, move, delete, type, doc.
2401
+ */
2402
+ /** Safely read a tree map value, converting Y.Map to plain object if needed. */
2403
+ function toPlain$1(val) {
2404
+ return val instanceof yjs.Map ? val.toJSON() : val;
2405
+ }
2406
+ registerCommand({
2407
+ name: "doc",
2408
+ aliases: ["info:doc"],
2409
+ description: "Show document metadata (label, type, meta, dates).",
2410
+ usage: "doc id=<docId> | name=<label> | path=<a/b/c>",
2411
+ async run(conn, args) {
2412
+ if (!conn) return "Not connected";
2413
+ const docId = resolveDocument(conn, args.params, args.positional);
2414
+ if (!docId) return "Document not found. Specify id=, name=, or path= to identify the document.";
2415
+ const treeMap = conn.getTreeMap();
2416
+ if (!treeMap) return "Not connected";
2417
+ const raw = treeMap.get(docId);
2418
+ if (!raw) return `Document ${docId} not found in tree.`;
2419
+ const entry = toPlain$1(raw);
2420
+ const lines = [
2421
+ `id: ${docId}`,
2422
+ `label: ${entry.label || "Untitled"}`,
2423
+ `type: ${entry.type ?? "—"}`,
2424
+ `parent: ${entry.parentId ?? "(root)"}`,
2425
+ `order: ${entry.order ?? 0}`,
2426
+ `created: ${entry.createdAt ? new Date(entry.createdAt).toISOString() : "—"}`,
2427
+ `updated: ${entry.updatedAt ? new Date(entry.updatedAt).toISOString() : "—"} (${relativeTime(entry.updatedAt)})`
2428
+ ];
2429
+ if (entry.meta && Object.keys(entry.meta).length > 0) {
2430
+ lines.push(`meta:`);
2431
+ for (const [k, v] of Object.entries(entry.meta)) lines.push(` ${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`);
2432
+ }
2433
+ const children = childrenOf(readEntries(treeMap), docId);
2434
+ lines.push(`children: ${children.length}`);
2435
+ return lines.join("\n");
2436
+ }
2437
+ });
2438
+ registerCommand({
2439
+ name: "read",
2440
+ aliases: ["cat", "r"],
2441
+ description: "Read document content as markdown.",
2442
+ usage: "read id=<docId> | name=<label> | path=<a/b/c> [--format=json|md]",
2443
+ async run(conn, args) {
2444
+ if (!conn) return "Not connected";
2445
+ const docId = resolveDocument(conn, args.params, args.positional);
2446
+ if (!docId) return "Document not found. Specify id=, name=, or path= to identify the document.";
2447
+ try {
2448
+ const { title, markdown } = yjsToMarkdown((await conn.getChildProvider(docId)).document.getXmlFragment("default"));
2449
+ if (args.flags.has("json") || args.params["format"] === "json") {
2450
+ const treeMap = conn.getTreeMap();
2451
+ let label = title;
2452
+ let type;
2453
+ let meta;
2454
+ let children = [];
2455
+ if (treeMap) {
2456
+ const entry = treeMap.get(docId);
2457
+ if (entry) {
2458
+ label = entry.label || title;
2459
+ type = entry.type;
2460
+ meta = entry.meta;
2461
+ }
2462
+ treeMap.forEach((value, id) => {
2463
+ const v = toPlain$1(value);
2464
+ if (v.parentId === docId) children.push({
2465
+ id,
2466
+ label: v.label || "Untitled",
2467
+ type: v.type
2468
+ });
2469
+ });
2470
+ children.sort((a, b) => {
2471
+ const va = treeMap.get(a.id);
2472
+ const vb = treeMap.get(b.id);
2473
+ return (va?.order ?? 0) - (vb?.order ?? 0);
2474
+ });
2475
+ }
2476
+ return printJson({
2477
+ label,
2478
+ type,
2479
+ meta,
2480
+ markdown,
2481
+ children
2482
+ });
2483
+ }
2484
+ return markdown || "(empty document)";
2485
+ } catch (error) {
2486
+ return `Error reading document: ${error.message}`;
2487
+ }
2488
+ }
2489
+ });
2490
+ registerCommand({
2491
+ name: "create",
2492
+ aliases: ["new"],
2493
+ description: "Create a new document in the tree.",
2494
+ usage: "create label=<name> [parent=<docId>] [type=<pageType>] [content=<markdown>] [--format=json]",
2495
+ async run(conn, args) {
2496
+ if (!conn) return "Not connected";
2497
+ const label = args.params["label"] || args.params["name"] || args.positional[0];
2498
+ if (!label) return "Missing required parameter: label=<name>";
2499
+ const treeMap = conn.getTreeMap();
2500
+ const rootDoc = conn.rootDoc;
2501
+ if (!treeMap || !rootDoc) return "Not connected";
2502
+ let parentId = null;
2503
+ if (args.params["parent"]) {
2504
+ parentId = resolveDocument(conn, {
2505
+ id: args.params["parent"],
2506
+ name: args.params["parent"]
2507
+ }, []);
2508
+ if (!parentId) parentId = args.params["parent"];
2509
+ }
2510
+ const normalizedParent = normalizeRootId(parentId || conn.rootDocId, conn);
2511
+ const type = args.params["type"];
2512
+ const id = crypto.randomUUID();
2513
+ const now = Date.now();
2514
+ const meta = {};
2515
+ if (args.params["icon"]) meta["icon"] = args.params["icon"];
2516
+ if (args.params["color"]) meta["color"] = args.params["color"];
2517
+ rootDoc.transact(() => {
2518
+ treeMap.set(id, {
2519
+ label,
2520
+ parentId: normalizedParent,
2521
+ order: now,
2522
+ type,
2523
+ meta: Object.keys(meta).length > 0 ? meta : void 0,
2524
+ createdAt: now,
2525
+ updatedAt: now
2526
+ });
2527
+ });
2528
+ const content = args.params["content"];
2529
+ if (content) try {
2530
+ populateYDocFromMarkdown((await conn.getChildProvider(id)).document.getXmlFragment("default"), content.replace(/\\n/g, "\n").replace(/\\t/g, " "), label);
2531
+ } catch (error) {
2532
+ return `Created ${id} "${label}" but failed to write content: ${error.message}`;
2533
+ }
2534
+ if (args.params["format"] === "json") return printJson({
2535
+ id,
2536
+ label,
2537
+ parentId: normalizedParent,
2538
+ type
2539
+ });
2540
+ return `Created: ${id.slice(0, 8)}… "${label}"${type ? ` (${type})` : ""}`;
2541
+ }
2542
+ });
2543
+ registerCommand({
2544
+ name: "rename",
2545
+ description: "Rename a document.",
2546
+ usage: "rename id=<docId> | name=<oldLabel> label=<newLabel>",
2547
+ async run(conn, args) {
2548
+ if (!conn) return "Not connected";
2549
+ const docId = resolveDocument(conn, args.params, args.positional);
2550
+ if (!docId) return "Document not found.";
2551
+ const newLabel = args.params["label"] || args.params["to"];
2552
+ if (!newLabel) return "Missing required parameter: label=<newLabel>";
2553
+ const treeMap = conn.getTreeMap();
2554
+ if (!treeMap) return "Not connected";
2555
+ const raw = treeMap.get(docId);
2556
+ if (!raw) return `Document ${docId} not found.`;
2557
+ const entry = toPlain$1(raw);
2558
+ treeMap.set(docId, {
2559
+ ...entry,
2560
+ label: newLabel,
2561
+ updatedAt: Date.now()
2562
+ });
2563
+ return `Renamed to "${newLabel}"`;
2564
+ }
2565
+ });
2566
+ registerCommand({
2567
+ name: "move",
2568
+ aliases: ["mv"],
2569
+ description: "Move a document to a new parent.",
2570
+ usage: "move id=<docId> | name=<label> to=<parentId> [order=<n>]",
2571
+ async run(conn, args) {
2572
+ if (!conn) return "Not connected";
2573
+ const docId = resolveDocument(conn, args.params, args.positional);
2574
+ if (!docId) return "Document not found.";
2575
+ const newParentId = args.params["to"] || args.params["parent"];
2576
+ if (!newParentId) return "Missing required parameter: to=<parentId>";
2577
+ const treeMap = conn.getTreeMap();
2578
+ if (!treeMap) return "Not connected";
2579
+ const raw = treeMap.get(docId);
2580
+ if (!raw) return `Document ${docId} not found.`;
2581
+ const entry = toPlain$1(raw);
2582
+ const order = args.params["order"] ? parseInt(args.params["order"], 10) : Date.now();
2583
+ treeMap.set(docId, {
2584
+ ...entry,
2585
+ parentId: normalizeRootId(newParentId, conn),
2586
+ order,
2587
+ updatedAt: Date.now()
2588
+ });
2589
+ return `Moved ${docId.slice(0, 8)}… to parent ${newParentId.slice(0, 8)}…`;
2590
+ }
2591
+ });
2592
+ registerCommand({
2593
+ name: "delete",
2594
+ aliases: ["rm", "del"],
2595
+ description: "Soft-delete a document (moves to trash).",
2596
+ usage: "delete id=<docId> | name=<label>",
2597
+ async run(conn, args) {
2598
+ if (!conn) return "Not connected";
2599
+ const docId = resolveDocument(conn, args.params, args.positional);
2600
+ if (!docId) return "Document not found.";
2601
+ const treeMap = conn.getTreeMap();
2602
+ const trashMap = conn.getTrashMap();
2603
+ const rootDoc = conn.rootDoc;
2604
+ if (!treeMap || !trashMap || !rootDoc) return "Not connected";
2605
+ const toDelete = [docId, ...descendantsOf(readEntries(treeMap), docId).map((e) => e.id)];
2606
+ const now = Date.now();
2607
+ rootDoc.transact(() => {
2608
+ for (const nid of toDelete) {
2609
+ const raw = treeMap.get(nid);
2610
+ if (!raw) continue;
2611
+ const entry = toPlain$1(raw);
2612
+ trashMap.set(nid, {
2613
+ label: entry.label || "Untitled",
2614
+ parentId: entry.parentId ?? null,
2615
+ order: entry.order ?? 0,
2616
+ type: entry.type,
2617
+ meta: entry.meta,
2618
+ deletedAt: now
2619
+ });
2620
+ treeMap.delete(nid);
2621
+ }
2622
+ });
2623
+ return `Deleted ${toDelete.length} document(s)`;
2624
+ }
2625
+ });
2626
+ registerCommand({
2627
+ name: "type",
2628
+ description: "Change the page type view of a document.",
2629
+ usage: "type id=<docId> | name=<label> type=<pageType>",
2630
+ async run(conn, args) {
2631
+ if (!conn) return "Not connected";
2632
+ const docId = resolveDocument(conn, args.params, args.positional);
2633
+ if (!docId) return "Document not found.";
2634
+ const newType = args.params["type"] || args.positional[1];
2635
+ if (!newType) return "Missing required parameter: type=<pageType>";
2636
+ const treeMap = conn.getTreeMap();
2637
+ if (!treeMap) return "Not connected";
2638
+ const raw = treeMap.get(docId);
2639
+ if (!raw) return `Document ${docId} not found.`;
2640
+ const entry = toPlain$1(raw);
2641
+ treeMap.set(docId, {
2642
+ ...entry,
2643
+ type: newType,
2644
+ updatedAt: Date.now()
2645
+ });
2646
+ return `Changed type to "${newType}"`;
2647
+ }
2648
+ });
2649
+ registerCommand({
2650
+ name: "write",
2651
+ description: "Write markdown content to a document (replace or append).",
2652
+ usage: "write id=<docId> | name=<label> content=<markdown> [mode=replace|append]",
2653
+ async run(conn, args) {
2654
+ if (!conn) return "Not connected";
2655
+ const docId = resolveDocument(conn, args.params, args.positional);
2656
+ if (!docId) return "Document not found.";
2657
+ let content = args.params["content"];
2658
+ if (!content) {
2659
+ if (!process.stdin.isTTY) content = await readStdin();
2660
+ }
2661
+ if (!content) return "Missing required parameter: content=<markdown> (or pipe via stdin)";
2662
+ content = content.replace(/\\n/g, "\n").replace(/\\t/g, " ");
2663
+ try {
2664
+ const writeMode = args.params["mode"] ?? "replace";
2665
+ const doc = (await conn.getChildProvider(docId)).document;
2666
+ const fragment = doc.getXmlFragment("default");
2667
+ const { title, meta, body } = parseFrontmatter(content);
2668
+ if (title || Object.keys(meta).length > 0) {
2669
+ const treeMap = conn.getTreeMap();
2670
+ const rootDoc = conn.rootDoc;
2671
+ if (treeMap && rootDoc) {
2672
+ const entry = treeMap.get(docId);
2673
+ if (entry) {
2674
+ const e = toPlain$1(entry);
2675
+ rootDoc.transact(() => {
2676
+ const updates = {
2677
+ ...e,
2678
+ updatedAt: Date.now()
2679
+ };
2680
+ if (title) updates.label = title;
2681
+ if (Object.keys(meta).length > 0) updates.meta = {
2682
+ ...e.meta ?? {},
2683
+ ...meta
2684
+ };
2685
+ treeMap.set(docId, updates);
2686
+ });
2687
+ }
2688
+ }
2689
+ }
2690
+ if (writeMode === "replace") doc.transact(() => {
2691
+ while (fragment.length > 0) fragment.delete(0);
2692
+ });
2693
+ populateYDocFromMarkdown(fragment, body || content, title || "Untitled");
2694
+ return `Document ${docId.slice(0, 8)}… updated (${writeMode} mode)`;
2695
+ } catch (error) {
2696
+ return `Error writing document: ${error.message}`;
2697
+ }
2698
+ }
2699
+ });
2700
+ registerCommand({
2701
+ name: "duplicate",
2702
+ aliases: ["dup"],
2703
+ description: "Shallow-clone a document.",
2704
+ usage: "duplicate id=<docId> | name=<label>",
2705
+ async run(conn, args) {
2706
+ if (!conn) return "Not connected";
2707
+ const docId = resolveDocument(conn, args.params, args.positional);
2708
+ if (!docId) return "Document not found.";
2709
+ const treeMap = conn.getTreeMap();
2710
+ if (!treeMap) return "Not connected";
2711
+ const raw = treeMap.get(docId);
2712
+ if (!raw) return `Document ${docId} not found.`;
2713
+ const entry = toPlain$1(raw);
2714
+ const newId = crypto.randomUUID();
2715
+ treeMap.set(newId, {
2716
+ ...entry,
2717
+ label: (entry.label || "Untitled") + " (copy)",
2718
+ order: Date.now()
2719
+ });
2720
+ return `Duplicated: ${newId.slice(0, 8)}… "${entry.label} (copy)"`;
2721
+ }
2722
+ });
2723
+ /** Helper to read all of stdin as a string. */
2724
+ function readStdin() {
2725
+ return new Promise((resolve, reject) => {
2726
+ let data = "";
2727
+ process.stdin.setEncoding("utf-8");
2728
+ process.stdin.on("data", (chunk) => {
2729
+ data += chunk;
2730
+ });
2731
+ process.stdin.on("end", () => resolve(data));
2732
+ process.stdin.on("error", reject);
2733
+ setTimeout(() => resolve(data), 5e3);
2734
+ });
2735
+ }
2736
+
2737
+ //#endregion
2738
+ //#region packages/cli/src/commands/content.ts
2739
+ /**
2740
+ * Content commands: append, prepend, wc, export.
2741
+ */
2742
+ registerCommand({
2743
+ name: "append",
2744
+ description: "Append content to a document.",
2745
+ usage: "append id=<docId> | name=<label> content=<text> [--inline]",
2746
+ async run(conn, args) {
2747
+ if (!conn) return "Not connected";
2748
+ const docId = resolveDocument(conn, args.params, args.positional);
2749
+ if (!docId) return "Document not found.";
2750
+ const content = args.params["content"];
2751
+ if (!content) return "Missing required parameter: content=<text>";
2752
+ try {
2753
+ populateYDocFromMarkdown((await conn.getChildProvider(docId)).document.getXmlFragment("default"), args.flags.has("inline") ? content.replace(/\\n/g, "\n").replace(/\\t/g, " ") : "\n" + content.replace(/\\n/g, "\n").replace(/\\t/g, " "), "");
2754
+ return `Appended to ${docId.slice(0, 8)}…`;
2755
+ } catch (error) {
2756
+ return `Error: ${error.message}`;
2757
+ }
2758
+ }
2759
+ });
2760
+ registerCommand({
2761
+ name: "prepend",
2762
+ description: "Prepend content to a document (after frontmatter).",
2763
+ usage: "prepend id=<docId> | name=<label> content=<text> [--inline]",
2764
+ async run(conn, args) {
2765
+ if (!conn) return "Not connected";
2766
+ const docId = resolveDocument(conn, args.params, args.positional);
2767
+ if (!docId) return "Document not found.";
2768
+ const content = args.params["content"];
2769
+ if (!content) return "Missing required parameter: content=<text>";
2770
+ try {
2771
+ const doc = (await conn.getChildProvider(docId)).document;
2772
+ const fragment = doc.getXmlFragment("default");
2773
+ const { markdown: existing } = yjsToMarkdown(fragment);
2774
+ const text = content.replace(/\\n/g, "\n").replace(/\\t/g, " ");
2775
+ const combined = args.flags.has("inline") ? text + existing : text + "\n" + existing;
2776
+ doc.transact(() => {
2777
+ while (fragment.length > 0) fragment.delete(0);
2778
+ });
2779
+ populateYDocFromMarkdown(fragment, combined, "");
2780
+ return `Prepended to ${docId.slice(0, 8)}…`;
2781
+ } catch (error) {
2782
+ return `Error: ${error.message}`;
2783
+ }
2784
+ }
2785
+ });
2786
+ registerCommand({
2787
+ name: "wc",
2788
+ aliases: ["wordcount"],
2789
+ description: "Count words and characters in a document.",
2790
+ usage: "wc id=<docId> | name=<label> [--words] [--characters]",
2791
+ async run(conn, args) {
2792
+ if (!conn) return "Not connected";
2793
+ const docId = resolveDocument(conn, args.params, args.positional);
2794
+ if (!docId) return "Document not found.";
2795
+ try {
2796
+ const { markdown } = yjsToMarkdown((await conn.getChildProvider(docId)).document.getXmlFragment("default"));
2797
+ const words = markdown.split(/\s+/).filter(Boolean).length;
2798
+ const chars = markdown.length;
2799
+ if (args.flags.has("words")) return String(words);
2800
+ if (args.flags.has("characters")) return String(chars);
2801
+ return `Words: ${words}\nCharacters: ${chars}`;
2802
+ } catch (error) {
2803
+ return `Error: ${error.message}`;
2804
+ }
2805
+ }
2806
+ });
2807
+ registerCommand({
2808
+ name: "export",
2809
+ description: "Export a document as markdown to a local file.",
2810
+ usage: "export id=<docId> | name=<label> output=<filepath>",
2811
+ async run(conn, args) {
2812
+ if (!conn) return "Not connected";
2813
+ const docId = resolveDocument(conn, args.params, args.positional);
2814
+ if (!docId) return "Document not found.";
2815
+ const outputPath = args.params["output"] || args.params["to"] || args.params["path"];
2816
+ if (!outputPath) return "Missing required parameter: output=<filepath>";
2817
+ try {
2818
+ const { title, markdown } = yjsToMarkdown((await conn.getChildProvider(docId)).document.getXmlFragment("default"));
2819
+ const resolvedPath = node_path.resolve(outputPath);
2820
+ node_fs.writeFileSync(resolvedPath, markdown, "utf-8");
2821
+ return `Exported "${title || "document"}" to ${resolvedPath} (${Buffer.byteLength(markdown)} bytes)`;
2822
+ } catch (error) {
2823
+ return `Error: ${error.message}`;
2824
+ }
2825
+ }
2826
+ });
2827
+
2828
+ //#endregion
2829
+ //#region packages/cli/src/commands/meta.ts
2830
+ /**
2831
+ * Metadata commands: meta, tags.
2832
+ */
2833
+ /** Safely read a tree map value. */
2834
+ function toPlain(val) {
2835
+ return val instanceof yjs.Map ? val.toJSON() : val;
2836
+ }
2837
+ registerCommand({
2838
+ name: "meta",
2839
+ aliases: ["metadata"],
2840
+ description: "Get or set document metadata.",
2841
+ usage: "meta id=<docId> | name=<label> [key=value ...] [--format=json]",
2842
+ async run(conn, args) {
2843
+ if (!conn) return "Not connected";
2844
+ const docId = resolveDocument(conn, args.params, args.positional);
2845
+ if (!docId) return "Document not found.";
2846
+ const treeMap = conn.getTreeMap();
2847
+ if (!treeMap) return "Not connected";
2848
+ const raw = treeMap.get(docId);
2849
+ if (!raw) return `Document ${docId} not found.`;
2850
+ const entry = toPlain(raw);
2851
+ const metaKeys = new Set([
2852
+ "icon",
2853
+ "color",
2854
+ "priority",
2855
+ "status",
2856
+ "checked",
2857
+ "rating",
2858
+ "tags",
2859
+ "dateStart",
2860
+ "dateEnd",
2861
+ "datetimeStart",
2862
+ "datetimeEnd",
2863
+ "allDay",
2864
+ "url",
2865
+ "email",
2866
+ "phone",
2867
+ "number",
2868
+ "unit",
2869
+ "subtitle",
2870
+ "note",
2871
+ "taskProgress",
2872
+ "coverUploadId",
2873
+ "geoType",
2874
+ "geoLat",
2875
+ "geoLng",
2876
+ "deskX",
2877
+ "deskY",
2878
+ "deskMode",
2879
+ "spShape",
2880
+ "spColor",
2881
+ "chartType",
2882
+ "kanbanColumnWidth",
2883
+ "galleryColumns",
2884
+ "calendarView",
2885
+ "tableMode"
2886
+ ]);
2887
+ const updates = {};
2888
+ let hasUpdates = false;
2889
+ for (const [key, value] of Object.entries(args.params)) if (metaKeys.has(key)) {
2890
+ if (value === "null") updates[key] = null;
2891
+ else if (value === "true") updates[key] = true;
2892
+ else if (value === "false") updates[key] = false;
2893
+ else if ([
2894
+ "priority",
2895
+ "rating",
2896
+ "number",
2897
+ "taskProgress",
2898
+ "geoLat",
2899
+ "geoLng",
2900
+ "deskX",
2901
+ "deskY",
2902
+ "galleryColumns"
2903
+ ].includes(key)) updates[key] = parseFloat(value);
2904
+ else if (key === "tags") updates[key] = value.split(",").map((s) => s.trim());
2905
+ else updates[key] = value;
2906
+ hasUpdates = true;
2907
+ }
2908
+ if (hasUpdates) {
2909
+ treeMap.set(docId, {
2910
+ ...entry,
2911
+ meta: {
2912
+ ...entry.meta ?? {},
2913
+ ...updates
2914
+ },
2915
+ updatedAt: Date.now()
2916
+ });
2917
+ return `Metadata updated for ${docId.slice(0, 8)}…`;
2918
+ }
2919
+ const meta = entry.meta ?? {};
2920
+ if (args.params["format"] === "json") return printJson({
2921
+ id: docId,
2922
+ label: entry.label,
2923
+ type: entry.type,
2924
+ meta
2925
+ });
2926
+ if (Object.keys(meta).length === 0) return `Document "${entry.label}" has no metadata.`;
2927
+ return Object.entries(meta).map(([k, v]) => `${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`).join("\n");
2928
+ }
2929
+ });
2930
+ registerCommand({
2931
+ name: "tags",
2932
+ description: "List tags aggregated from document metadata.",
2933
+ usage: "tags [id=<docId>] [--counts] [--total] [--format=json|tsv]",
2934
+ async run(conn, args) {
2935
+ if (!conn) return "Not connected";
2936
+ const treeMap = conn.getTreeMap();
2937
+ if (!treeMap) return "Not connected";
2938
+ const docId = resolveDocument(conn, args.params, args.positional);
2939
+ if (docId) {
2940
+ const raw = treeMap.get(docId);
2941
+ if (!raw) return `Document ${docId} not found.`;
2942
+ const tags = toPlain(raw).meta?.tags ?? [];
2943
+ if (tags.length === 0) return "No tags.";
2944
+ return tags.join("\n");
2945
+ }
2946
+ const entries = readEntries(treeMap);
2947
+ const tagCounts = /* @__PURE__ */ new Map();
2948
+ for (const entry of entries) {
2949
+ const tags = entry.meta?.tags ?? [];
2950
+ for (const tag of tags) tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
2951
+ }
2952
+ if (tagCounts.size === 0) return "No tags found.";
2953
+ if (args.flags.has("total")) return String(tagCounts.size);
2954
+ const sorted = [...tagCounts.entries()].sort((a, b) => {
2955
+ if (args.params["sort"] === "count") return b[1] - a[1];
2956
+ return a[0].localeCompare(b[0]);
2957
+ });
2958
+ if (getFormat(args, "text") === "json") return printJson(sorted.map(([tag, count]) => ({
2959
+ tag,
2960
+ count
2961
+ })));
2962
+ if (args.flags.has("counts")) return sorted.map(([tag, count]) => `${tag}\t${count}`).join("\n");
2963
+ return sorted.map(([tag]) => tag).join("\n");
2964
+ }
2965
+ });
2966
+
2967
+ //#endregion
2968
+ //#region packages/cli/src/commands/awareness.ts
2969
+ /**
2970
+ * Awareness commands: who, status, chat.
2971
+ */
2972
+ registerCommand({
2973
+ name: "who",
2974
+ aliases: ["users", "online"],
2975
+ description: "List connected users and their awareness state.",
2976
+ usage: "who [id=<docId>] [--format=json|tsv]",
2977
+ async run(conn, args) {
2978
+ if (!conn) return "Not connected";
2979
+ let awareness;
2980
+ if (args.params["id"]) try {
2981
+ awareness = (await conn.getChildProvider(args.params["id"])).awareness;
2982
+ } catch (error) {
2983
+ return `Error: ${error.message}`;
2984
+ }
2985
+ else {
2986
+ const rootProvider = conn.rootProvider;
2987
+ if (!rootProvider) return "Not connected";
2988
+ awareness = rootProvider.awareness;
2989
+ }
2990
+ const states = awareness.getStates();
2991
+ const selfId = awareness.clientID;
2992
+ const users = [];
2993
+ for (const [clientId, state] of states) {
2994
+ const user = state["user"];
2995
+ if (!user) continue;
2996
+ users.push({
2997
+ name: user.name ?? "Unknown",
2998
+ status: state["status"] ?? "—",
2999
+ docId: state["docId"] ?? "—",
3000
+ isYou: clientId === selfId,
3001
+ isAgent: user.isAgent ?? false
3002
+ });
3003
+ }
3004
+ if (getFormat(args, "text") === "json") return printJson(users);
3005
+ if (users.length === 0) return "No users connected.";
3006
+ return printTable(users.map((u) => [
3007
+ u.name + (u.isYou ? " (you)" : "") + (u.isAgent ? " 🤖" : ""),
3008
+ u.status,
3009
+ u.docId === "—" ? "—" : u.docId.slice(0, 8) + "…"
3010
+ ]), [
3011
+ "NAME",
3012
+ "STATUS",
3013
+ "DOCUMENT"
3014
+ ]);
3015
+ }
3016
+ });
3017
+ registerCommand({
3018
+ name: "status",
3019
+ description: "Set your presence status.",
3020
+ usage: "status <text> | status --clear",
3021
+ async run(conn, args) {
3022
+ if (!conn) return "Not connected";
3023
+ const rootProvider = conn.rootProvider;
3024
+ if (!rootProvider) return "Not connected";
3025
+ if (args.flags.has("clear")) {
3026
+ rootProvider.awareness.setLocalStateField("status", null);
3027
+ return "Status cleared.";
3028
+ }
3029
+ const status = args.params["text"] || args.positional[0];
3030
+ if (!status) return "Missing status text. Use: status <text> or status --clear";
3031
+ rootProvider.awareness.setLocalStateField("status", status);
3032
+ return `Status set to "${status}"`;
3033
+ }
3034
+ });
3035
+ registerCommand({
3036
+ name: "chat",
3037
+ aliases: ["send"],
3038
+ description: "Send a chat message to a channel.",
3039
+ usage: "chat channel=<group:docId> text=<message>",
3040
+ async run(conn, args) {
3041
+ if (!conn) return "Not connected";
3042
+ const rootProvider = conn.rootProvider;
3043
+ if (!rootProvider) return "Not connected";
3044
+ const channel = args.params["channel"];
3045
+ if (!channel) return "Missing required parameter: channel=<group:docId>";
3046
+ const text = args.params["text"] || args.params["content"] || args.positional[0];
3047
+ if (!text) return "Missing required parameter: text=<message>";
3048
+ rootProvider.sendStateless(JSON.stringify({
3049
+ type: "chat:send",
3050
+ channel,
3051
+ content: text,
3052
+ sender_name: conn.displayName
3053
+ }));
3054
+ return `Sent to ${channel}`;
3055
+ }
3056
+ });
3057
+
3058
+ //#endregion
3059
+ //#region packages/cli/src/commands/files.ts
3060
+ /**
3061
+ * File attachment commands: upload, uploads.
3062
+ */
3063
+ registerCommand({
3064
+ name: "uploads",
3065
+ aliases: ["attachments"],
3066
+ description: "List file attachments for a document.",
3067
+ usage: "uploads id=<docId> | name=<label> [--format=json|tsv] [--total]",
3068
+ async run(conn, args) {
3069
+ if (!conn) return "Not connected";
3070
+ const docId = resolveDocument(conn, args.params, args.positional);
3071
+ if (!docId) return "Document not found.";
3072
+ try {
3073
+ const uploads = await conn.client.listUploads(docId);
3074
+ if (args.flags.has("total")) return String(uploads.length);
3075
+ if (getFormat(args, "text") === "json") return printJson(uploads);
3076
+ if (uploads.length === 0) return "No attachments.";
3077
+ return printTable(uploads.map((u) => [
3078
+ u.id.slice(0, 8) + "…",
3079
+ u.filename,
3080
+ u.mime_type ?? "—",
3081
+ u.size ? formatBytes(u.size) : "—"
3082
+ ]), [
3083
+ "ID",
3084
+ "FILENAME",
3085
+ "TYPE",
3086
+ "SIZE"
3087
+ ]);
3088
+ } catch (error) {
3089
+ return `Error: ${error.message}`;
3090
+ }
3091
+ }
3092
+ });
3093
+ registerCommand({
3094
+ name: "upload",
3095
+ description: "Upload a local file to a document.",
3096
+ usage: "upload id=<docId> | name=<label> file=<localPath> [filename=<override>]",
3097
+ async run(conn, args) {
3098
+ if (!conn) return "Not connected";
3099
+ const docId = resolveDocument(conn, args.params, args.positional);
3100
+ if (!docId) return "Document not found.";
3101
+ const filePath = args.params["file"] || args.params["path"];
3102
+ if (!filePath) return "Missing required parameter: file=<localPath>";
3103
+ try {
3104
+ const resolvedPath = node_path.resolve(filePath);
3105
+ if (!node_fs.existsSync(resolvedPath)) return `File not found: ${resolvedPath}`;
3106
+ const data = node_fs.readFileSync(resolvedPath);
3107
+ const filename = args.params["filename"] ?? node_path.basename(resolvedPath);
3108
+ const blob = new Blob([data]);
3109
+ const result = await conn.client.upload(docId, blob, filename);
3110
+ return `Uploaded: ${filename} (${formatBytes(data.length)}) → ${result.id}`;
3111
+ } catch (error) {
3112
+ return `Error: ${error.message}`;
3113
+ }
3114
+ }
3115
+ });
3116
+ registerCommand({
3117
+ name: "download",
3118
+ description: "Download a file attachment from a document.",
3119
+ usage: "download id=<docId> | name=<label> upload=<uploadId> output=<localPath>",
3120
+ async run(conn, args) {
3121
+ if (!conn) return "Not connected";
3122
+ const docId = resolveDocument(conn, args.params, args.positional);
3123
+ if (!docId) return "Document not found.";
3124
+ const uploadId = args.params["upload"] || args.params["uploadId"];
3125
+ if (!uploadId) return "Missing required parameter: upload=<uploadId>";
3126
+ const outputPath = args.params["output"] || args.params["to"];
3127
+ if (!outputPath) return "Missing required parameter: output=<localPath>";
3128
+ try {
3129
+ const blob = await conn.client.getUpload(docId, uploadId);
3130
+ const buffer = Buffer.from(await blob.arrayBuffer());
3131
+ const resolvedPath = node_path.resolve(outputPath);
3132
+ node_fs.writeFileSync(resolvedPath, buffer);
3133
+ return `Downloaded to ${resolvedPath} (${formatBytes(buffer.length)})`;
3134
+ } catch (error) {
3135
+ return `Error: ${error.message}`;
3136
+ }
3137
+ }
3138
+ });
3139
+ function formatBytes(bytes) {
3140
+ if (bytes < 1024) return `${bytes} B`;
3141
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3142
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3143
+ }
3144
+
3145
+ //#endregion
3146
+ //#region packages/cli/src/commands/permissions.ts
3147
+ /**
3148
+ * Permission commands: permissions.
3149
+ */
3150
+ registerCommand({
3151
+ name: "permissions",
3152
+ aliases: ["perms"],
3153
+ description: "List or set document permissions.",
3154
+ usage: "permissions id=<docId> [user=<userId> role=<role>] [--effective] [--format=json|tsv]",
3155
+ async run(conn, args) {
3156
+ if (!conn) return "Not connected";
3157
+ const docId = resolveDocument(conn, args.params, args.positional);
3158
+ if (!docId) return "Document not found.";
3159
+ const targetUser = args.params["user"] || args.params["user_id"];
3160
+ const targetRole = args.params["role"];
3161
+ if (targetUser && targetRole) try {
3162
+ await conn.client.setPermission(docId, {
3163
+ user_id: targetUser,
3164
+ role: targetRole
3165
+ });
3166
+ return `Set ${targetUser.slice(0, 12)}… → ${targetRole} on ${docId.slice(0, 8)}…`;
3167
+ } catch (error) {
3168
+ return `Error: ${error.message}`;
3169
+ }
3170
+ if (targetUser && args.flags.has("remove")) try {
3171
+ await conn.client.removePermission(docId, { user_id: targetUser });
3172
+ return `Removed permissions for ${targetUser.slice(0, 12)}… on ${docId.slice(0, 8)}…`;
3173
+ } catch (error) {
3174
+ return `Error: ${error.message}`;
3175
+ }
3176
+ try {
3177
+ const format = getFormat(args, "text");
3178
+ if (args.flags.has("effective")) {
3179
+ const result = await conn.client.listEffectivePermissions(docId);
3180
+ if (format === "json") return printJson(result);
3181
+ if (result.permissions.length === 0) return `Default role: ${result.default_role}\nNo explicit permissions.`;
3182
+ const rows = result.permissions.map((p) => [
3183
+ p.display_name || p.username,
3184
+ p.role,
3185
+ p.source,
3186
+ p.inherited_from_doc_id ? p.inherited_from_doc_id.slice(0, 8) + "…" : "—"
3187
+ ]);
3188
+ return `Default role: ${result.default_role}\n\n` + printTable(rows, [
3189
+ "USER",
3190
+ "ROLE",
3191
+ "SOURCE",
3192
+ "INHERITED FROM"
3193
+ ]);
3194
+ }
3195
+ const perms = await conn.client.listPermissions(docId);
3196
+ if (format === "json") return printJson(perms);
3197
+ if (perms.length === 0) return "No explicit permissions set.";
3198
+ return printTable(perms.map((p) => [
3199
+ p.display_name || p.username,
3200
+ p.role,
3201
+ p.user_id.slice(0, 12) + "…"
3202
+ ]), [
3203
+ "USER",
3204
+ "ROLE",
3205
+ "USER ID"
3206
+ ]);
3207
+ } catch (error) {
3208
+ return `Error: ${error.message}`;
3209
+ }
3210
+ }
3211
+ });
3212
+
3213
+ //#endregion
3214
+ //#region packages/cli/src/index.ts
3215
+ /**
3216
+ * Abracadabra CLI — interact with CRDT document workspaces from the terminal.
3217
+ *
3218
+ * Usage:
3219
+ * abracadabra <command> [key=value ...] [--flags]
3220
+ *
3221
+ * Environment:
3222
+ * ABRA_URL Server URL (required)
3223
+ * ABRA_KEY_FILE Path to Ed25519 key file (~/.abracadabra/cli.key)
3224
+ * ABRA_INVITE_CODE Invite code for first-run registration
3225
+ * ABRA_NAME Display name (default: CLI User)
3226
+ * ABRA_COLOR Presence color (default: hsl(45, 90%, 55%))
3227
+ */
3228
+ const NO_CONNECT_COMMANDS = new Set([
3229
+ "help",
3230
+ "h",
3231
+ "?",
3232
+ "version",
3233
+ "v"
3234
+ ]);
3235
+ async function main() {
3236
+ const args = parseArgs(process.argv);
3237
+ const cmd = getCommand(args.command);
3238
+ if (!cmd) {
3239
+ console.error(`Unknown command: "${args.command}"`);
3240
+ console.error("Run \"abracadabra help\" to see all commands.");
3241
+ process.exit(1);
3242
+ }
3243
+ if (NO_CONNECT_COMMANDS.has(args.command)) {
3244
+ let conn = null;
3245
+ const url = process.env["ABRA_URL"];
3246
+ if (url && args.command === "version") try {
3247
+ conn = new CLIConnection({
3248
+ url,
3249
+ name: process.env["ABRA_NAME"],
3250
+ color: process.env["ABRA_COLOR"],
3251
+ inviteCode: process.env["ABRA_INVITE_CODE"],
3252
+ keyFile: process.env["ABRA_KEY_FILE"],
3253
+ quiet: true
3254
+ });
3255
+ await conn.connect();
3256
+ } catch {}
3257
+ try {
3258
+ const output = await cmd.run(conn, args);
3259
+ if (output) console.log(output);
3260
+ } finally {
3261
+ if (conn) await conn.destroy().catch(() => {});
3262
+ }
3263
+ return;
3264
+ }
3265
+ const url = process.env["ABRA_URL"];
3266
+ if (!url) {
3267
+ console.error("Error: ABRA_URL environment variable is required.");
3268
+ console.error("");
3269
+ console.error("Set it to your Abracadabra server URL:");
3270
+ console.error(" export ABRA_URL=https://your-server.example.com");
3271
+ console.error("");
3272
+ console.error("Run \"abracadabra help\" for more info.");
3273
+ process.exit(1);
3274
+ }
3275
+ const conn = new CLIConnection({
3276
+ url,
3277
+ name: process.env["ABRA_NAME"],
3278
+ color: process.env["ABRA_COLOR"],
3279
+ inviteCode: process.env["ABRA_INVITE_CODE"],
3280
+ keyFile: process.env["ABRA_KEY_FILE"],
3281
+ quiet: args.flags.has("quiet") || args.flags.has("q")
3282
+ });
3283
+ try {
3284
+ await conn.connect();
3285
+ const output = await cmd.run(conn, args);
3286
+ if (output) console.log(output);
3287
+ } catch (error) {
3288
+ console.error(`Error: ${error.message}`);
3289
+ if (args.flags.has("verbose")) console.error(error.stack);
3290
+ process.exit(1);
3291
+ } finally {
3292
+ await conn.destroy().catch(() => {});
3293
+ }
3294
+ }
3295
+ main().catch((err) => {
3296
+ console.error(`Fatal: ${err.message ?? err}`);
3297
+ process.exit(1);
3298
+ });
3299
+
3300
+ //#endregion
3301
+ //# sourceMappingURL=abracadabra-cli.cjs.map