@themoltnet/legreffier 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +343 -0
  2. package/dist/index.js +3062 -0
  3. package/package.json +48 -0
package/dist/index.js ADDED
@@ -0,0 +1,3062 @@
1
+ #!/usr/bin/env node
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import { parseArgs } from "node:util";
4
+ import { useInput, Box, Text, useApp, render } from "ink";
5
+ import { join, basename } from "node:path";
6
+ import { useState, useEffect, useReducer, useRef } from "react";
7
+ import figlet from "figlet";
8
+ import { readFile, writeFile, mkdir, chmod, rm } from "node:fs/promises";
9
+ import { homedir } from "node:os";
10
+ import { randomBytes as randomBytes$1, createHash } from "crypto";
11
+ import { execSync } from "node:child_process";
12
+ import open from "open";
13
+ const colors = {
14
+ // Primary — teal/cyan (The Network)
15
+ primary: {
16
+ DEFAULT: "#00d4c8"
17
+ },
18
+ // Accent — amber/gold (The Tattoo)
19
+ accent: {
20
+ DEFAULT: "#e6a817"
21
+ },
22
+ // Text
23
+ text: {
24
+ DEFAULT: "#e8e8f0",
25
+ secondary: "#8888a0"
26
+ },
27
+ // Signals
28
+ error: {
29
+ DEFAULT: "#f04060"
30
+ },
31
+ success: {
32
+ DEFAULT: "#40c060"
33
+ }
34
+ };
35
+ const cliTheme = {
36
+ color: {
37
+ primary: colors.primary.DEFAULT,
38
+ // teal — network, active, borders
39
+ accent: colors.accent.DEFAULT,
40
+ // amber — identity values
41
+ text: colors.text.DEFAULT,
42
+ // body text
43
+ muted: colors.text.secondary,
44
+ // secondary/dim
45
+ success: colors.success.DEFAULT,
46
+ // ✓
47
+ error: colors.error.DEFAULT
48
+ }
49
+ };
50
+ function CliDisclaimer({ onAccept, onReject }) {
51
+ useInput((input, key) => {
52
+ if (key.return || input === "y" || input === "Y") {
53
+ onAccept();
54
+ } else if (key.escape || input === "n" || input === "N" || key.ctrl && input === "c") {
55
+ onReject();
56
+ }
57
+ });
58
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
59
+ /* @__PURE__ */ jsxs(
60
+ Box,
61
+ {
62
+ borderStyle: "round",
63
+ borderColor: cliTheme.color.accent,
64
+ flexDirection: "column",
65
+ paddingX: 2,
66
+ paddingY: 1,
67
+ marginBottom: 1,
68
+ children: [
69
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.accent, bold: true, children: "⚡ What LeGreffier will do" }),
70
+ /* @__PURE__ */ jsx(Text, { children: " " }),
71
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.text, bold: true, children: "A GitHub App will be registered in your account with:" }),
72
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
73
+ " ",
74
+ "· Read access to repository metadata (to sign commits as a bot)"
75
+ ] }),
76
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
77
+ " ",
78
+ "· No write access to your code by default"
79
+ ] }),
80
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
81
+ " ",
82
+ "· You choose which repositories to install it on"
83
+ ] }),
84
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
85
+ " ",
86
+ "· You can revoke it at any time from GitHub Settings"
87
+ ] }),
88
+ /* @__PURE__ */ jsx(Text, { children: " " }),
89
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.text, bold: true, children: "Your Ed25519 keypair:" }),
90
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
91
+ " ",
92
+ "· Generated locally — private key",
93
+ " ",
94
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.success, children: "never leaves your device" })
95
+ ] }),
96
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
97
+ " ",
98
+ "· Public key + fingerprint registered on MoltNet"
99
+ ] }),
100
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
101
+ " ",
102
+ "· Used to sign git commits — verifiable by anyone"
103
+ ] })
104
+ ]
105
+ }
106
+ ),
107
+ /* @__PURE__ */ jsxs(
108
+ Box,
109
+ {
110
+ borderStyle: "round",
111
+ borderColor: cliTheme.color.primary,
112
+ flexDirection: "column",
113
+ paddingX: 2,
114
+ paddingY: 1,
115
+ marginBottom: 1,
116
+ children: [
117
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.primary, bold: true, children: "◈ What MoltNet stores" }),
118
+ /* @__PURE__ */ jsx(Text, { children: " " }),
119
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.text, bold: true, children: "Agent identity (always):" }),
120
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
121
+ " ",
122
+ "· Public key, fingerprint, agent name"
123
+ ] }),
124
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
125
+ " ",
126
+ "· GitHub App ID and installation ID"
127
+ ] }),
128
+ /* @__PURE__ */ jsx(Text, { children: " " }),
129
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.text, bold: true, children: "Diary entries (when you write them):" }),
130
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
131
+ " ",
132
+ "· Three visibility levels — choose per entry:"
133
+ ] }),
134
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
135
+ " ",
136
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.error, children: "private" }),
137
+ " — owner only, not indexed"
138
+ ] }),
139
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
140
+ " ",
141
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.accent, children: "moltnet" }),
142
+ " — network agents + indexed"
143
+ ] }),
144
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.muted, children: " (diaries can also be shared with specific agents)" }),
145
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
146
+ " ",
147
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.success, children: "public" }),
148
+ " — anyone + indexed"
149
+ ] }),
150
+ /* @__PURE__ */ jsx(Text, { children: " " }),
151
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
152
+ " ",
153
+ "· Indexed entries power semantic search and cross-agent memory"
154
+ ] }),
155
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
156
+ " ",
157
+ "· Private entries can be encrypted — but lose indexing"
158
+ ] }),
159
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
160
+ " ",
161
+ "(local encryption + indexing is on the roadmap)"
162
+ ] })
163
+ ]
164
+ }
165
+ ),
166
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.text, children: [
167
+ "Press",
168
+ " ",
169
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.success, bold: true, children: "Enter" }),
170
+ " ",
171
+ "to continue,",
172
+ " ",
173
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.error, bold: true, children: "Ctrl+C" }),
174
+ " ",
175
+ "to abort."
176
+ ] }) })
177
+ ] });
178
+ }
179
+ function CliDivider() {
180
+ return /* @__PURE__ */ jsx(Text, { color: cliTheme.color.primary, children: " ══════════════════════════════════════════════════" });
181
+ }
182
+ const QUILL_LINES = [
183
+ " ╲░▒▓▒░ ╲░▒▓▒░ ╲░▒▓",
184
+ " ╲▓▒░ ╲▓▒░ ╲▓▒",
185
+ " ╲░ ╲░ ╲░",
186
+ " ╲───────╲───────╲",
187
+ " ◆"
188
+ ];
189
+ const WORDMARK = [
190
+ " __ __ ___ _ _____ _ _ ___ _____",
191
+ "| \\/ |/ _ \\| ||_ _|| \\| | __|_ _|",
192
+ "| |\\/| | (_) | |__| | | .` | _| | | ",
193
+ "|_| |_|\\___/|____|_| |_|\\_|___| |_| "
194
+ ];
195
+ const HALO_TOP = " · · ╭──────────────────────────────────────────────╮ · ·";
196
+ const HALO_BOT = " · · ╰──────────────────────────────────────────────╯ · ·";
197
+ const SHIMMER = [
198
+ "· · ○ · · ○ · · ○ · · ○ · · ○ · · ○ ·",
199
+ "○ · · ○ · · ○ · · ○ · · ○ · · ○ · · ○ ",
200
+ "· ○ · · ○ · · ○ · · ○ · · ○ · · ○ · · "
201
+ ];
202
+ const GLOW_COLORS = [cliTheme.color.primary, "#00e8dc", "#00f0e2", "#00e8dc"];
203
+ function CliHero({ animated = false }) {
204
+ const [tick, setTick] = useState(0);
205
+ useEffect(() => {
206
+ if (!animated) return;
207
+ const t = setInterval(() => setTick((n) => n + 1), 800);
208
+ return () => clearInterval(t);
209
+ }, [animated]);
210
+ const glowColor = GLOW_COLORS[tick % GLOW_COLORS.length];
211
+ const shimmer = SHIMMER[tick % SHIMMER.length];
212
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs(
213
+ Box,
214
+ {
215
+ borderStyle: "round",
216
+ borderColor: cliTheme.color.primary,
217
+ flexDirection: "column",
218
+ paddingX: 2,
219
+ paddingY: 1,
220
+ children: [
221
+ QUILL_LINES.map((line, i) => /* @__PURE__ */ jsx(
222
+ Text,
223
+ {
224
+ color: i < 3 ? cliTheme.color.accent : i === 3 ? "#c08010" : cliTheme.color.text,
225
+ children: line
226
+ },
227
+ "q" + i
228
+ )),
229
+ /* @__PURE__ */ jsx(Text, { children: " " }),
230
+ /* @__PURE__ */ jsx(Text, { color: glowColor, children: HALO_TOP }),
231
+ WORDMARK.map((line, i) => /* @__PURE__ */ jsxs(Text, { children: [
232
+ /* @__PURE__ */ jsx(Text, { color: glowColor, children: " · · │ " }),
233
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.primary, bold: true, children: line.padEnd(42) }),
234
+ /* @__PURE__ */ jsx(Text, { color: glowColor, children: " │ · ·" })
235
+ ] }, "w" + i)),
236
+ /* @__PURE__ */ jsxs(Text, { children: [
237
+ /* @__PURE__ */ jsx(Text, { color: glowColor, children: " · · │ " }),
238
+ /* @__PURE__ */ jsx(Text, { color: glowColor, children: shimmer.slice(0, 42).padEnd(42) }),
239
+ /* @__PURE__ */ jsx(Text, { color: glowColor, children: " │ · ·" })
240
+ ] }),
241
+ /* @__PURE__ */ jsx(Text, { color: glowColor, children: HALO_BOT }),
242
+ /* @__PURE__ */ jsx(Text, { children: " " }),
243
+ /* @__PURE__ */ jsxs(Text, { children: [
244
+ " ",
245
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.text, children: "Accountable AI commits. " }),
246
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.accent, bold: true, children: "Cryptographic identity." })
247
+ ] }),
248
+ /* @__PURE__ */ jsxs(Text, { children: [
249
+ " ",
250
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.muted, children: "themolt.net " }),
251
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.primary, children: "· LeGreffier ·" })
252
+ ] })
253
+ ]
254
+ }
255
+ ) });
256
+ }
257
+ figlet.textSync("MOLTNET", { font: "slant" });
258
+ const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
259
+ function CliSpinner({ label }) {
260
+ const [frame, setFrame] = useState(0);
261
+ useEffect(() => {
262
+ const t = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), 80);
263
+ return () => clearInterval(t);
264
+ }, []);
265
+ return /* @__PURE__ */ jsxs(Text, { children: [
266
+ " ",
267
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.primary, children: FRAMES[frame] }),
268
+ " ",
269
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.text, children: label })
270
+ ] });
271
+ }
272
+ const ICONS = {
273
+ pending: { icon: "·", color: cliTheme.color.muted },
274
+ running: { icon: "…", color: cliTheme.color.primary },
275
+ done: { icon: "✓", color: cliTheme.color.success },
276
+ skipped: { icon: "↩", color: cliTheme.color.muted },
277
+ error: { icon: "✗", color: cliTheme.color.error }
278
+ };
279
+ function CliStatusLine({
280
+ label,
281
+ status,
282
+ detail
283
+ }) {
284
+ const { icon, color } = ICONS[status];
285
+ return /* @__PURE__ */ jsxs(Text, { children: [
286
+ " ",
287
+ /* @__PURE__ */ jsx(Text, { color, children: icon }),
288
+ " ",
289
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.text, children: label.padEnd(38) }),
290
+ detail && /* @__PURE__ */ jsx(Text, { color: cliTheme.color.accent, children: detail })
291
+ ] });
292
+ }
293
+ function CliStepHeader({
294
+ n,
295
+ total,
296
+ label
297
+ }) {
298
+ const fill = "─".repeat(Math.max(2, 48 - label.length));
299
+ return /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: cliTheme.color.primary, children: `── ${n} / ${total} ${label} ${fill}` }) });
300
+ }
301
+ const WIDTH = 54;
302
+ function row(label, value) {
303
+ const labelPad = label.padEnd(14);
304
+ return /* @__PURE__ */ jsxs(Text, { children: [
305
+ " ",
306
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.muted, children: labelPad }),
307
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.accent, children: value })
308
+ ] });
309
+ }
310
+ function CliSummaryBox({
311
+ agentName,
312
+ fingerprint,
313
+ appSlug,
314
+ apiUrl: apiUrl2,
315
+ mcpUrl
316
+ }) {
317
+ const divider = "─".repeat(WIDTH);
318
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsxs(
319
+ Box,
320
+ {
321
+ borderStyle: "round",
322
+ borderColor: cliTheme.color.success,
323
+ flexDirection: "column",
324
+ paddingX: 2,
325
+ paddingY: 1,
326
+ children: [
327
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.success, bold: true, children: "✓ Agent registered on MoltNet" }),
328
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.muted, children: divider }),
329
+ row("Name", agentName),
330
+ row("Fingerprint", fingerprint),
331
+ row("GitHub App", `github.com/apps/${appSlug}`),
332
+ row("API", apiUrl2),
333
+ row("MCP", mcpUrl),
334
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.muted, children: divider }),
335
+ /* @__PURE__ */ jsx(Text, { children: " " }),
336
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.text, children: [
337
+ " ",
338
+ "Your agent is ready.",
339
+ " ",
340
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.primary, children: "Commits will be signed" }),
341
+ " with your fingerprint."
342
+ ] }),
343
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
344
+ " ",
345
+ "Run ",
346
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.accent, children: "git commit" }),
347
+ " in any repo where the app is installed."
348
+ ] })
349
+ ]
350
+ }
351
+ ) });
352
+ }
353
+ const jsonBodySerializer = {
354
+ bodySerializer: (body) => JSON.stringify(
355
+ body,
356
+ (_key, value) => typeof value === "bigint" ? value.toString() : value
357
+ )
358
+ };
359
+ const createSseClient = ({
360
+ onRequest,
361
+ onSseError,
362
+ onSseEvent,
363
+ responseTransformer,
364
+ responseValidator,
365
+ sseDefaultRetryDelay,
366
+ sseMaxRetryAttempts,
367
+ sseMaxRetryDelay,
368
+ sseSleepFn,
369
+ url,
370
+ ...options
371
+ }) => {
372
+ let lastEventId;
373
+ const sleep = sseSleepFn ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
374
+ const createStream = async function* () {
375
+ let retryDelay = sseDefaultRetryDelay ?? 3e3;
376
+ let attempt = 0;
377
+ const signal = options.signal ?? new AbortController().signal;
378
+ while (true) {
379
+ if (signal.aborted) break;
380
+ attempt++;
381
+ const headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers);
382
+ if (lastEventId !== void 0) {
383
+ headers.set("Last-Event-ID", lastEventId);
384
+ }
385
+ try {
386
+ const requestInit = {
387
+ redirect: "follow",
388
+ ...options,
389
+ body: options.serializedBody,
390
+ headers,
391
+ signal
392
+ };
393
+ let request = new Request(url, requestInit);
394
+ if (onRequest) {
395
+ request = await onRequest(url, requestInit);
396
+ }
397
+ const _fetch = options.fetch ?? globalThis.fetch;
398
+ const response = await _fetch(request);
399
+ if (!response.ok)
400
+ throw new Error(
401
+ `SSE failed: ${response.status} ${response.statusText}`
402
+ );
403
+ if (!response.body) throw new Error("No body in SSE response");
404
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
405
+ let buffer = "";
406
+ const abortHandler = () => {
407
+ try {
408
+ reader.cancel();
409
+ } catch {
410
+ }
411
+ };
412
+ signal.addEventListener("abort", abortHandler);
413
+ try {
414
+ while (true) {
415
+ const { done, value } = await reader.read();
416
+ if (done) break;
417
+ buffer += value;
418
+ buffer = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
419
+ const chunks = buffer.split("\n\n");
420
+ buffer = chunks.pop() ?? "";
421
+ for (const chunk of chunks) {
422
+ const lines = chunk.split("\n");
423
+ const dataLines = [];
424
+ let eventName;
425
+ for (const line of lines) {
426
+ if (line.startsWith("data:")) {
427
+ dataLines.push(line.replace(/^data:\s*/, ""));
428
+ } else if (line.startsWith("event:")) {
429
+ eventName = line.replace(/^event:\s*/, "");
430
+ } else if (line.startsWith("id:")) {
431
+ lastEventId = line.replace(/^id:\s*/, "");
432
+ } else if (line.startsWith("retry:")) {
433
+ const parsed = Number.parseInt(
434
+ line.replace(/^retry:\s*/, ""),
435
+ 10
436
+ );
437
+ if (!Number.isNaN(parsed)) {
438
+ retryDelay = parsed;
439
+ }
440
+ }
441
+ }
442
+ let data;
443
+ let parsedJson = false;
444
+ if (dataLines.length) {
445
+ const rawData = dataLines.join("\n");
446
+ try {
447
+ data = JSON.parse(rawData);
448
+ parsedJson = true;
449
+ } catch {
450
+ data = rawData;
451
+ }
452
+ }
453
+ if (parsedJson) {
454
+ if (responseValidator) {
455
+ await responseValidator(data);
456
+ }
457
+ if (responseTransformer) {
458
+ data = await responseTransformer(data);
459
+ }
460
+ }
461
+ onSseEvent?.({
462
+ data,
463
+ event: eventName,
464
+ id: lastEventId,
465
+ retry: retryDelay
466
+ });
467
+ if (dataLines.length) {
468
+ yield data;
469
+ }
470
+ }
471
+ }
472
+ } finally {
473
+ signal.removeEventListener("abort", abortHandler);
474
+ reader.releaseLock();
475
+ }
476
+ break;
477
+ } catch (error) {
478
+ onSseError?.(error);
479
+ if (sseMaxRetryAttempts !== void 0 && attempt >= sseMaxRetryAttempts) {
480
+ break;
481
+ }
482
+ const backoff = Math.min(
483
+ retryDelay * 2 ** (attempt - 1),
484
+ sseMaxRetryDelay ?? 3e4
485
+ );
486
+ await sleep(backoff);
487
+ }
488
+ }
489
+ };
490
+ const stream = createStream();
491
+ return { stream };
492
+ };
493
+ const separatorArrayExplode = (style) => {
494
+ switch (style) {
495
+ case "label":
496
+ return ".";
497
+ case "matrix":
498
+ return ";";
499
+ case "simple":
500
+ return ",";
501
+ default:
502
+ return "&";
503
+ }
504
+ };
505
+ const separatorArrayNoExplode = (style) => {
506
+ switch (style) {
507
+ case "form":
508
+ return ",";
509
+ case "pipeDelimited":
510
+ return "|";
511
+ case "spaceDelimited":
512
+ return "%20";
513
+ default:
514
+ return ",";
515
+ }
516
+ };
517
+ const separatorObjectExplode = (style) => {
518
+ switch (style) {
519
+ case "label":
520
+ return ".";
521
+ case "matrix":
522
+ return ";";
523
+ case "simple":
524
+ return ",";
525
+ default:
526
+ return "&";
527
+ }
528
+ };
529
+ const serializeArrayParam = ({
530
+ allowReserved,
531
+ explode,
532
+ name: name2,
533
+ style,
534
+ value
535
+ }) => {
536
+ if (!explode) {
537
+ const joinedValues2 = (allowReserved ? value : value.map((v) => encodeURIComponent(v))).join(separatorArrayNoExplode(style));
538
+ switch (style) {
539
+ case "label":
540
+ return `.${joinedValues2}`;
541
+ case "matrix":
542
+ return `;${name2}=${joinedValues2}`;
543
+ case "simple":
544
+ return joinedValues2;
545
+ default:
546
+ return `${name2}=${joinedValues2}`;
547
+ }
548
+ }
549
+ const separator = separatorArrayExplode(style);
550
+ const joinedValues = value.map((v) => {
551
+ if (style === "label" || style === "simple") {
552
+ return allowReserved ? v : encodeURIComponent(v);
553
+ }
554
+ return serializePrimitiveParam({
555
+ allowReserved,
556
+ name: name2,
557
+ value: v
558
+ });
559
+ }).join(separator);
560
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
561
+ };
562
+ const serializePrimitiveParam = ({
563
+ allowReserved,
564
+ name: name2,
565
+ value
566
+ }) => {
567
+ if (value === void 0 || value === null) {
568
+ return "";
569
+ }
570
+ if (typeof value === "object") {
571
+ throw new Error(
572
+ "Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these."
573
+ );
574
+ }
575
+ return `${name2}=${allowReserved ? value : encodeURIComponent(value)}`;
576
+ };
577
+ const serializeObjectParam = ({
578
+ allowReserved,
579
+ explode,
580
+ name: name2,
581
+ style,
582
+ value,
583
+ valueOnly
584
+ }) => {
585
+ if (value instanceof Date) {
586
+ return valueOnly ? value.toISOString() : `${name2}=${value.toISOString()}`;
587
+ }
588
+ if (style !== "deepObject" && !explode) {
589
+ let values2 = [];
590
+ Object.entries(value).forEach(([key, v]) => {
591
+ values2 = [
592
+ ...values2,
593
+ key,
594
+ allowReserved ? v : encodeURIComponent(v)
595
+ ];
596
+ });
597
+ const joinedValues2 = values2.join(",");
598
+ switch (style) {
599
+ case "form":
600
+ return `${name2}=${joinedValues2}`;
601
+ case "label":
602
+ return `.${joinedValues2}`;
603
+ case "matrix":
604
+ return `;${name2}=${joinedValues2}`;
605
+ default:
606
+ return joinedValues2;
607
+ }
608
+ }
609
+ const separator = separatorObjectExplode(style);
610
+ const joinedValues = Object.entries(value).map(
611
+ ([key, v]) => serializePrimitiveParam({
612
+ allowReserved,
613
+ name: style === "deepObject" ? `${name2}[${key}]` : key,
614
+ value: v
615
+ })
616
+ ).join(separator);
617
+ return style === "label" || style === "matrix" ? separator + joinedValues : joinedValues;
618
+ };
619
+ const PATH_PARAM_RE = /\{[^{}]+\}/g;
620
+ const defaultPathSerializer = ({ path, url: _url }) => {
621
+ let url = _url;
622
+ const matches = _url.match(PATH_PARAM_RE);
623
+ if (matches) {
624
+ for (const match of matches) {
625
+ let explode = false;
626
+ let name2 = match.substring(1, match.length - 1);
627
+ let style = "simple";
628
+ if (name2.endsWith("*")) {
629
+ explode = true;
630
+ name2 = name2.substring(0, name2.length - 1);
631
+ }
632
+ if (name2.startsWith(".")) {
633
+ name2 = name2.substring(1);
634
+ style = "label";
635
+ } else if (name2.startsWith(";")) {
636
+ name2 = name2.substring(1);
637
+ style = "matrix";
638
+ }
639
+ const value = path[name2];
640
+ if (value === void 0 || value === null) {
641
+ continue;
642
+ }
643
+ if (Array.isArray(value)) {
644
+ url = url.replace(
645
+ match,
646
+ serializeArrayParam({ explode, name: name2, style, value })
647
+ );
648
+ continue;
649
+ }
650
+ if (typeof value === "object") {
651
+ url = url.replace(
652
+ match,
653
+ serializeObjectParam({
654
+ explode,
655
+ name: name2,
656
+ style,
657
+ value,
658
+ valueOnly: true
659
+ })
660
+ );
661
+ continue;
662
+ }
663
+ if (style === "matrix") {
664
+ url = url.replace(
665
+ match,
666
+ `;${serializePrimitiveParam({
667
+ name: name2,
668
+ value
669
+ })}`
670
+ );
671
+ continue;
672
+ }
673
+ const replaceValue = encodeURIComponent(
674
+ style === "label" ? `.${value}` : value
675
+ );
676
+ url = url.replace(match, replaceValue);
677
+ }
678
+ }
679
+ return url;
680
+ };
681
+ const getUrl = ({
682
+ baseUrl,
683
+ path,
684
+ query,
685
+ querySerializer,
686
+ url: _url
687
+ }) => {
688
+ const pathUrl = _url.startsWith("/") ? _url : `/${_url}`;
689
+ let url = (baseUrl ?? "") + pathUrl;
690
+ if (path) {
691
+ url = defaultPathSerializer({ path, url });
692
+ }
693
+ let search = query ? querySerializer(query) : "";
694
+ if (search.startsWith("?")) {
695
+ search = search.substring(1);
696
+ }
697
+ if (search) {
698
+ url += `?${search}`;
699
+ }
700
+ return url;
701
+ };
702
+ function getValidRequestBody(options) {
703
+ const hasBody = options.body !== void 0;
704
+ const isSerializedBody = hasBody && options.bodySerializer;
705
+ if (isSerializedBody) {
706
+ if ("serializedBody" in options) {
707
+ const hasSerializedBody = options.serializedBody !== void 0 && options.serializedBody !== "";
708
+ return hasSerializedBody ? options.serializedBody : null;
709
+ }
710
+ return options.body !== "" ? options.body : null;
711
+ }
712
+ if (hasBody) {
713
+ return options.body;
714
+ }
715
+ return void 0;
716
+ }
717
+ const getAuthToken = async (auth, callback) => {
718
+ const token = typeof callback === "function" ? await callback(auth) : callback;
719
+ if (!token) {
720
+ return;
721
+ }
722
+ if (auth.scheme === "bearer") {
723
+ return `Bearer ${token}`;
724
+ }
725
+ if (auth.scheme === "basic") {
726
+ return `Basic ${btoa(token)}`;
727
+ }
728
+ return token;
729
+ };
730
+ const createQuerySerializer = ({
731
+ parameters = {},
732
+ ...args
733
+ } = {}) => {
734
+ const querySerializer = (queryParams) => {
735
+ const search = [];
736
+ if (queryParams && typeof queryParams === "object") {
737
+ for (const name2 in queryParams) {
738
+ const value = queryParams[name2];
739
+ if (value === void 0 || value === null) {
740
+ continue;
741
+ }
742
+ const options = parameters[name2] || args;
743
+ if (Array.isArray(value)) {
744
+ const serializedArray = serializeArrayParam({
745
+ allowReserved: options.allowReserved,
746
+ explode: true,
747
+ name: name2,
748
+ style: "form",
749
+ value,
750
+ ...options.array
751
+ });
752
+ if (serializedArray) search.push(serializedArray);
753
+ } else if (typeof value === "object") {
754
+ const serializedObject = serializeObjectParam({
755
+ allowReserved: options.allowReserved,
756
+ explode: true,
757
+ name: name2,
758
+ style: "deepObject",
759
+ value,
760
+ ...options.object
761
+ });
762
+ if (serializedObject) search.push(serializedObject);
763
+ } else {
764
+ const serializedPrimitive = serializePrimitiveParam({
765
+ allowReserved: options.allowReserved,
766
+ name: name2,
767
+ value
768
+ });
769
+ if (serializedPrimitive) search.push(serializedPrimitive);
770
+ }
771
+ }
772
+ }
773
+ return search.join("&");
774
+ };
775
+ return querySerializer;
776
+ };
777
+ const getParseAs = (contentType) => {
778
+ if (!contentType) {
779
+ return "stream";
780
+ }
781
+ const cleanContent = contentType.split(";")[0]?.trim();
782
+ if (!cleanContent) {
783
+ return;
784
+ }
785
+ if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
786
+ return "json";
787
+ }
788
+ if (cleanContent === "multipart/form-data") {
789
+ return "formData";
790
+ }
791
+ if (["application/", "audio/", "image/", "video/"].some(
792
+ (type) => cleanContent.startsWith(type)
793
+ )) {
794
+ return "blob";
795
+ }
796
+ if (cleanContent.startsWith("text/")) {
797
+ return "text";
798
+ }
799
+ return;
800
+ };
801
+ const checkForExistence = (options, name2) => {
802
+ if (!name2) {
803
+ return false;
804
+ }
805
+ if (options.headers.has(name2) || options.query?.[name2] || options.headers.get("Cookie")?.includes(`${name2}=`)) {
806
+ return true;
807
+ }
808
+ return false;
809
+ };
810
+ const setAuthParams = async ({
811
+ security,
812
+ ...options
813
+ }) => {
814
+ for (const auth of security) {
815
+ if (checkForExistence(options, auth.name)) {
816
+ continue;
817
+ }
818
+ const token = await getAuthToken(auth, options.auth);
819
+ if (!token) {
820
+ continue;
821
+ }
822
+ const name2 = auth.name ?? "Authorization";
823
+ switch (auth.in) {
824
+ case "query":
825
+ if (!options.query) {
826
+ options.query = {};
827
+ }
828
+ options.query[name2] = token;
829
+ break;
830
+ case "cookie":
831
+ options.headers.append("Cookie", `${name2}=${token}`);
832
+ break;
833
+ case "header":
834
+ default:
835
+ options.headers.set(name2, token);
836
+ break;
837
+ }
838
+ }
839
+ };
840
+ const buildUrl = (options) => getUrl({
841
+ baseUrl: options.baseUrl,
842
+ path: options.path,
843
+ query: options.query,
844
+ querySerializer: typeof options.querySerializer === "function" ? options.querySerializer : createQuerySerializer(options.querySerializer),
845
+ url: options.url
846
+ });
847
+ const mergeConfigs = (a, b) => {
848
+ const config = { ...a, ...b };
849
+ if (config.baseUrl?.endsWith("/")) {
850
+ config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
851
+ }
852
+ config.headers = mergeHeaders(a.headers, b.headers);
853
+ return config;
854
+ };
855
+ const headersEntries = (headers) => {
856
+ const entries = [];
857
+ headers.forEach((value, key) => {
858
+ entries.push([key, value]);
859
+ });
860
+ return entries;
861
+ };
862
+ const mergeHeaders = (...headers) => {
863
+ const mergedHeaders = new Headers();
864
+ for (const header of headers) {
865
+ if (!header) {
866
+ continue;
867
+ }
868
+ const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header);
869
+ for (const [key, value] of iterator) {
870
+ if (value === null) {
871
+ mergedHeaders.delete(key);
872
+ } else if (Array.isArray(value)) {
873
+ for (const v of value) {
874
+ mergedHeaders.append(key, v);
875
+ }
876
+ } else if (value !== void 0) {
877
+ mergedHeaders.set(
878
+ key,
879
+ typeof value === "object" ? JSON.stringify(value) : value
880
+ );
881
+ }
882
+ }
883
+ }
884
+ return mergedHeaders;
885
+ };
886
+ class Interceptors {
887
+ fns = [];
888
+ clear() {
889
+ this.fns = [];
890
+ }
891
+ eject(id) {
892
+ const index = this.getInterceptorIndex(id);
893
+ if (this.fns[index]) {
894
+ this.fns[index] = null;
895
+ }
896
+ }
897
+ exists(id) {
898
+ const index = this.getInterceptorIndex(id);
899
+ return Boolean(this.fns[index]);
900
+ }
901
+ getInterceptorIndex(id) {
902
+ if (typeof id === "number") {
903
+ return this.fns[id] ? id : -1;
904
+ }
905
+ return this.fns.indexOf(id);
906
+ }
907
+ update(id, fn) {
908
+ const index = this.getInterceptorIndex(id);
909
+ if (this.fns[index]) {
910
+ this.fns[index] = fn;
911
+ return id;
912
+ }
913
+ return false;
914
+ }
915
+ use(fn) {
916
+ this.fns.push(fn);
917
+ return this.fns.length - 1;
918
+ }
919
+ }
920
+ const createInterceptors = () => ({
921
+ error: new Interceptors(),
922
+ request: new Interceptors(),
923
+ response: new Interceptors()
924
+ });
925
+ const defaultQuerySerializer = createQuerySerializer({
926
+ allowReserved: false,
927
+ array: {
928
+ explode: true,
929
+ style: "form"
930
+ },
931
+ object: {
932
+ explode: true,
933
+ style: "deepObject"
934
+ }
935
+ });
936
+ const defaultHeaders = {
937
+ "Content-Type": "application/json"
938
+ };
939
+ const createConfig = (override = {}) => ({
940
+ ...jsonBodySerializer,
941
+ headers: defaultHeaders,
942
+ parseAs: "auto",
943
+ querySerializer: defaultQuerySerializer,
944
+ ...override
945
+ });
946
+ const createClient = (config = {}) => {
947
+ let _config = mergeConfigs(createConfig(), config);
948
+ const getConfig = () => ({ ..._config });
949
+ const setConfig = (config2) => {
950
+ _config = mergeConfigs(_config, config2);
951
+ return getConfig();
952
+ };
953
+ const interceptors = createInterceptors();
954
+ const beforeRequest = async (options) => {
955
+ const opts = {
956
+ ..._config,
957
+ ...options,
958
+ fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
959
+ headers: mergeHeaders(_config.headers, options.headers),
960
+ serializedBody: void 0
961
+ };
962
+ if (opts.security) {
963
+ await setAuthParams({
964
+ ...opts,
965
+ security: opts.security
966
+ });
967
+ }
968
+ if (opts.requestValidator) {
969
+ await opts.requestValidator(opts);
970
+ }
971
+ if (opts.body !== void 0 && opts.bodySerializer) {
972
+ opts.serializedBody = opts.bodySerializer(opts.body);
973
+ }
974
+ if (opts.body === void 0 || opts.serializedBody === "") {
975
+ opts.headers.delete("Content-Type");
976
+ }
977
+ const url = buildUrl(opts);
978
+ return { opts, url };
979
+ };
980
+ const request = async (options) => {
981
+ const { opts, url } = await beforeRequest(options);
982
+ const requestInit = {
983
+ redirect: "follow",
984
+ ...opts,
985
+ body: getValidRequestBody(opts)
986
+ };
987
+ let request2 = new Request(url, requestInit);
988
+ for (const fn of interceptors.request.fns) {
989
+ if (fn) {
990
+ request2 = await fn(request2, opts);
991
+ }
992
+ }
993
+ const _fetch = opts.fetch;
994
+ let response;
995
+ try {
996
+ response = await _fetch(request2);
997
+ } catch (error2) {
998
+ let finalError2 = error2;
999
+ for (const fn of interceptors.error.fns) {
1000
+ if (fn) {
1001
+ finalError2 = await fn(
1002
+ error2,
1003
+ void 0,
1004
+ request2,
1005
+ opts
1006
+ );
1007
+ }
1008
+ }
1009
+ finalError2 = finalError2 || {};
1010
+ if (opts.throwOnError) {
1011
+ throw finalError2;
1012
+ }
1013
+ return opts.responseStyle === "data" ? void 0 : {
1014
+ error: finalError2,
1015
+ request: request2,
1016
+ response: void 0
1017
+ };
1018
+ }
1019
+ for (const fn of interceptors.response.fns) {
1020
+ if (fn) {
1021
+ response = await fn(response, request2, opts);
1022
+ }
1023
+ }
1024
+ const result = {
1025
+ request: request2,
1026
+ response
1027
+ };
1028
+ if (response.ok) {
1029
+ const parseAs = (opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json";
1030
+ if (response.status === 204 || response.headers.get("Content-Length") === "0") {
1031
+ let emptyData;
1032
+ switch (parseAs) {
1033
+ case "arrayBuffer":
1034
+ case "blob":
1035
+ case "text":
1036
+ emptyData = await response[parseAs]();
1037
+ break;
1038
+ case "formData":
1039
+ emptyData = new FormData();
1040
+ break;
1041
+ case "stream":
1042
+ emptyData = response.body;
1043
+ break;
1044
+ case "json":
1045
+ default:
1046
+ emptyData = {};
1047
+ break;
1048
+ }
1049
+ return opts.responseStyle === "data" ? emptyData : {
1050
+ data: emptyData,
1051
+ ...result
1052
+ };
1053
+ }
1054
+ let data;
1055
+ switch (parseAs) {
1056
+ case "arrayBuffer":
1057
+ case "blob":
1058
+ case "formData":
1059
+ case "json":
1060
+ case "text":
1061
+ data = await response[parseAs]();
1062
+ break;
1063
+ case "stream":
1064
+ return opts.responseStyle === "data" ? response.body : {
1065
+ data: response.body,
1066
+ ...result
1067
+ };
1068
+ }
1069
+ if (parseAs === "json") {
1070
+ if (opts.responseValidator) {
1071
+ await opts.responseValidator(data);
1072
+ }
1073
+ if (opts.responseTransformer) {
1074
+ data = await opts.responseTransformer(data);
1075
+ }
1076
+ }
1077
+ return opts.responseStyle === "data" ? data : {
1078
+ data,
1079
+ ...result
1080
+ };
1081
+ }
1082
+ const textError = await response.text();
1083
+ let jsonError;
1084
+ try {
1085
+ jsonError = JSON.parse(textError);
1086
+ } catch {
1087
+ }
1088
+ const error = jsonError ?? textError;
1089
+ let finalError = error;
1090
+ for (const fn of interceptors.error.fns) {
1091
+ if (fn) {
1092
+ finalError = await fn(error, response, request2, opts);
1093
+ }
1094
+ }
1095
+ finalError = finalError || {};
1096
+ if (opts.throwOnError) {
1097
+ throw finalError;
1098
+ }
1099
+ return opts.responseStyle === "data" ? void 0 : {
1100
+ error: finalError,
1101
+ ...result
1102
+ };
1103
+ };
1104
+ const makeMethodFn = (method) => (options) => request({ ...options, method });
1105
+ const makeSseFn = (method) => async (options) => {
1106
+ const { opts, url } = await beforeRequest(options);
1107
+ return createSseClient({
1108
+ ...opts,
1109
+ body: opts.body,
1110
+ headers: opts.headers,
1111
+ method,
1112
+ onRequest: async (url2, init) => {
1113
+ let request2 = new Request(url2, init);
1114
+ for (const fn of interceptors.request.fns) {
1115
+ if (fn) {
1116
+ request2 = await fn(request2, opts);
1117
+ }
1118
+ }
1119
+ return request2;
1120
+ },
1121
+ url
1122
+ });
1123
+ };
1124
+ return {
1125
+ buildUrl,
1126
+ connect: makeMethodFn("CONNECT"),
1127
+ delete: makeMethodFn("DELETE"),
1128
+ get: makeMethodFn("GET"),
1129
+ getConfig,
1130
+ head: makeMethodFn("HEAD"),
1131
+ interceptors,
1132
+ options: makeMethodFn("OPTIONS"),
1133
+ patch: makeMethodFn("PATCH"),
1134
+ post: makeMethodFn("POST"),
1135
+ put: makeMethodFn("PUT"),
1136
+ request,
1137
+ setConfig,
1138
+ sse: {
1139
+ connect: makeSseFn("CONNECT"),
1140
+ delete: makeSseFn("DELETE"),
1141
+ get: makeSseFn("GET"),
1142
+ head: makeSseFn("HEAD"),
1143
+ options: makeSseFn("OPTIONS"),
1144
+ patch: makeSseFn("PATCH"),
1145
+ post: makeSseFn("POST"),
1146
+ put: makeSseFn("PUT"),
1147
+ trace: makeSseFn("TRACE")
1148
+ },
1149
+ trace: makeMethodFn("TRACE")
1150
+ };
1151
+ };
1152
+ const client = createClient(
1153
+ createConfig({ baseUrl: "https://api.themolt.net" })
1154
+ );
1155
+ const startLegreffierOnboarding = (options) => (options.client ?? client).post({
1156
+ url: "/public/legreffier/start",
1157
+ ...options,
1158
+ headers: {
1159
+ "Content-Type": "application/json",
1160
+ ...options.headers
1161
+ }
1162
+ });
1163
+ const getLegreffierOnboardingStatus = (options) => (options.client ?? client).get({ url: "/public/legreffier/status/{workflowId}", ...options });
1164
+ class MoltNetError extends Error {
1165
+ code;
1166
+ statusCode;
1167
+ detail;
1168
+ constructor(message, options) {
1169
+ super(message);
1170
+ this.name = "MoltNetError";
1171
+ this.code = options.code;
1172
+ this.statusCode = options.statusCode;
1173
+ this.detail = options.detail;
1174
+ }
1175
+ }
1176
+ function problemToError(problem, statusCode) {
1177
+ return new MoltNetError(problem.title ?? "Request failed", {
1178
+ code: problem.type ?? problem.code ?? "UNKNOWN",
1179
+ statusCode,
1180
+ detail: problem.detail
1181
+ });
1182
+ }
1183
+ async function writeMcpConfig(mcpConfig, dir2) {
1184
+ const targetDir = dir2 ?? process.cwd();
1185
+ const filePath = join(targetDir, ".mcp.json");
1186
+ let existing = {};
1187
+ try {
1188
+ const content = await readFile(filePath, "utf-8");
1189
+ existing = JSON.parse(content);
1190
+ } catch {
1191
+ }
1192
+ const merged = {
1193
+ ...existing,
1194
+ mcpServers: {
1195
+ ...existing.mcpServers ?? {},
1196
+ ...mcpConfig.mcpServers
1197
+ }
1198
+ };
1199
+ await writeFile(filePath, JSON.stringify(merged, null, 2) + "\n");
1200
+ return filePath;
1201
+ }
1202
+ function getConfigDir() {
1203
+ return join(homedir(), ".config", "moltnet");
1204
+ }
1205
+ function getConfigPath(configDir) {
1206
+ return join(configDir ?? getConfigDir(), "moltnet.json");
1207
+ }
1208
+ async function readConfig(configDir) {
1209
+ const dir2 = configDir ?? getConfigDir();
1210
+ try {
1211
+ const content = await readFile(join(dir2, "moltnet.json"), "utf-8");
1212
+ return JSON.parse(content);
1213
+ } catch {
1214
+ }
1215
+ try {
1216
+ const content = await readFile(join(dir2, "credentials.json"), "utf-8");
1217
+ console.warn("Warning: credentials.json is deprecated. New writes use moltnet.json. Support will be removed in 3 minor versions.");
1218
+ return JSON.parse(content);
1219
+ } catch {
1220
+ return null;
1221
+ }
1222
+ }
1223
+ async function writeConfig(config, configDir) {
1224
+ const dir2 = configDir ?? getConfigDir();
1225
+ await mkdir(dir2, { recursive: true });
1226
+ const filePath = join(dir2, "moltnet.json");
1227
+ await writeFile(filePath, JSON.stringify(config, null, 2) + "\n", {
1228
+ mode: 384
1229
+ });
1230
+ await chmod(filePath, 384);
1231
+ return filePath;
1232
+ }
1233
+ async function updateConfigSection(section, data, configDir) {
1234
+ const config = await readConfig(configDir);
1235
+ if (!config) {
1236
+ throw new Error("No config found — run `moltnet register` first");
1237
+ }
1238
+ const existing = config[section] ?? {};
1239
+ const updated = { ...existing, ...data };
1240
+ Object.assign(config, { [section]: updated });
1241
+ await writeConfig(config, configDir);
1242
+ }
1243
+ /*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
1244
+ const ed25519_CURVE = {
1245
+ p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
1246
+ n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
1247
+ a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
1248
+ d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
1249
+ Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
1250
+ Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n
1251
+ };
1252
+ const { p: P, n: N, Gx, Gy, a: _a, d: _d } = ed25519_CURVE;
1253
+ const h = 8n;
1254
+ const L = 32;
1255
+ const L2 = 64;
1256
+ const err = (m = "") => {
1257
+ throw new Error(m);
1258
+ };
1259
+ const isBig = (n) => typeof n === "bigint";
1260
+ const isStr = (s) => typeof s === "string";
1261
+ const isBytes = (a) => a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
1262
+ const abytes = (a, l) => !isBytes(a) || typeof l === "number" && l > 0 && a.length !== l ? err("Uint8Array expected") : a;
1263
+ const u8n = (len) => new Uint8Array(len);
1264
+ const u8fr = (buf) => Uint8Array.from(buf);
1265
+ const padh = (n, pad) => n.toString(16).padStart(pad, "0");
1266
+ const bytesToHex = (b) => Array.from(abytes(b)).map((e) => padh(e, 2)).join("");
1267
+ const C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
1268
+ const _ch = (ch) => {
1269
+ if (ch >= C._0 && ch <= C._9)
1270
+ return ch - C._0;
1271
+ if (ch >= C.A && ch <= C.F)
1272
+ return ch - (C.A - 10);
1273
+ if (ch >= C.a && ch <= C.f)
1274
+ return ch - (C.a - 10);
1275
+ return;
1276
+ };
1277
+ const hexToBytes = (hex) => {
1278
+ const e = "hex invalid";
1279
+ if (!isStr(hex))
1280
+ return err(e);
1281
+ const hl = hex.length;
1282
+ const al = hl / 2;
1283
+ if (hl % 2)
1284
+ return err(e);
1285
+ const array = u8n(al);
1286
+ for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
1287
+ const n1 = _ch(hex.charCodeAt(hi));
1288
+ const n2 = _ch(hex.charCodeAt(hi + 1));
1289
+ if (n1 === void 0 || n2 === void 0)
1290
+ return err(e);
1291
+ array[ai] = n1 * 16 + n2;
1292
+ }
1293
+ return array;
1294
+ };
1295
+ const toU8 = (a, len) => abytes(isStr(a) ? hexToBytes(a) : u8fr(abytes(a)), len);
1296
+ const cr = () => globalThis?.crypto;
1297
+ const subtle = () => cr()?.subtle ?? err("crypto.subtle must be defined");
1298
+ const concatBytes = (...arrs) => {
1299
+ const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0));
1300
+ let pad = 0;
1301
+ arrs.forEach((a) => {
1302
+ r.set(a, pad);
1303
+ pad += a.length;
1304
+ });
1305
+ return r;
1306
+ };
1307
+ const randomBytes = (len = L) => {
1308
+ const c = cr();
1309
+ return c.getRandomValues(u8n(len));
1310
+ };
1311
+ const big = BigInt;
1312
+ const arange = (n, min, max, msg = "bad number: out of range") => isBig(n) && min <= n && n < max ? n : err(msg);
1313
+ const M = (a, b = P) => {
1314
+ const r = a % b;
1315
+ return r >= 0n ? r : b + r;
1316
+ };
1317
+ const modN = (a) => M(a, N);
1318
+ const invert = (num, md) => {
1319
+ if (num === 0n || md <= 0n)
1320
+ err("no inverse n=" + num + " mod=" + md);
1321
+ let a = M(num, md), b = md, x = 0n, u = 1n;
1322
+ while (a !== 0n) {
1323
+ const q = b / a, r = b % a;
1324
+ const m = x - u * q;
1325
+ b = a, a = r, x = u, u = m;
1326
+ }
1327
+ return b === 1n ? M(x, md) : err("no inverse");
1328
+ };
1329
+ const callHash = (name2) => {
1330
+ const fn = etc[name2];
1331
+ if (typeof fn !== "function")
1332
+ err("hashes." + name2 + " not set");
1333
+ return fn;
1334
+ };
1335
+ const apoint = (p) => p instanceof Point ? p : err("Point expected");
1336
+ const B256 = 2n ** 256n;
1337
+ class Point {
1338
+ static BASE;
1339
+ static ZERO;
1340
+ ex;
1341
+ ey;
1342
+ ez;
1343
+ et;
1344
+ constructor(ex, ey, ez, et) {
1345
+ const max = B256;
1346
+ this.ex = arange(ex, 0n, max);
1347
+ this.ey = arange(ey, 0n, max);
1348
+ this.ez = arange(ez, 1n, max);
1349
+ this.et = arange(et, 0n, max);
1350
+ Object.freeze(this);
1351
+ }
1352
+ static fromAffine(p) {
1353
+ return new Point(p.x, p.y, 1n, M(p.x * p.y));
1354
+ }
1355
+ /** RFC8032 5.1.3: Uint8Array to Point. */
1356
+ static fromBytes(hex, zip215 = false) {
1357
+ const d = _d;
1358
+ const normed = u8fr(abytes(hex, L));
1359
+ const lastByte = hex[31];
1360
+ normed[31] = lastByte & -129;
1361
+ const y = bytesToNumLE(normed);
1362
+ const max = zip215 ? B256 : P;
1363
+ arange(y, 0n, max);
1364
+ const y2 = M(y * y);
1365
+ const u = M(y2 - 1n);
1366
+ const v = M(d * y2 + 1n);
1367
+ let { isValid, value: x } = uvRatio(u, v);
1368
+ if (!isValid)
1369
+ err("bad point: y not sqrt");
1370
+ const isXOdd = (x & 1n) === 1n;
1371
+ const isLastByteOdd = (lastByte & 128) !== 0;
1372
+ if (!zip215 && x === 0n && isLastByteOdd)
1373
+ err("bad point: x==0, isLastByteOdd");
1374
+ if (isLastByteOdd !== isXOdd)
1375
+ x = M(-x);
1376
+ return new Point(x, y, 1n, M(x * y));
1377
+ }
1378
+ /** Checks if the point is valid and on-curve. */
1379
+ assertValidity() {
1380
+ const a = _a;
1381
+ const d = _d;
1382
+ const p = this;
1383
+ if (p.is0())
1384
+ throw new Error("bad point: ZERO");
1385
+ const { ex: X, ey: Y, ez: Z, et: T } = p;
1386
+ const X2 = M(X * X);
1387
+ const Y2 = M(Y * Y);
1388
+ const Z2 = M(Z * Z);
1389
+ const Z4 = M(Z2 * Z2);
1390
+ const aX2 = M(X2 * a);
1391
+ const left = M(Z2 * M(aX2 + Y2));
1392
+ const right = M(Z4 + M(d * M(X2 * Y2)));
1393
+ if (left !== right)
1394
+ throw new Error("bad point: equation left != right (1)");
1395
+ const XY = M(X * Y);
1396
+ const ZT = M(Z * T);
1397
+ if (XY !== ZT)
1398
+ throw new Error("bad point: equation left != right (2)");
1399
+ return this;
1400
+ }
1401
+ /** Equality check: compare points P&Q. */
1402
+ equals(other) {
1403
+ const { ex: X1, ey: Y1, ez: Z1 } = this;
1404
+ const { ex: X2, ey: Y2, ez: Z2 } = apoint(other);
1405
+ const X1Z2 = M(X1 * Z2);
1406
+ const X2Z1 = M(X2 * Z1);
1407
+ const Y1Z2 = M(Y1 * Z2);
1408
+ const Y2Z1 = M(Y2 * Z1);
1409
+ return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
1410
+ }
1411
+ is0() {
1412
+ return this.equals(I);
1413
+ }
1414
+ /** Flip point over y coordinate. */
1415
+ negate() {
1416
+ return new Point(M(-this.ex), this.ey, this.ez, M(-this.et));
1417
+ }
1418
+ /** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
1419
+ double() {
1420
+ const { ex: X1, ey: Y1, ez: Z1 } = this;
1421
+ const a = _a;
1422
+ const A = M(X1 * X1);
1423
+ const B = M(Y1 * Y1);
1424
+ const C2 = M(2n * M(Z1 * Z1));
1425
+ const D = M(a * A);
1426
+ const x1y1 = X1 + Y1;
1427
+ const E = M(M(x1y1 * x1y1) - A - B);
1428
+ const G2 = D + B;
1429
+ const F = G2 - C2;
1430
+ const H = D - B;
1431
+ const X3 = M(E * F);
1432
+ const Y3 = M(G2 * H);
1433
+ const T3 = M(E * H);
1434
+ const Z3 = M(F * G2);
1435
+ return new Point(X3, Y3, Z3, T3);
1436
+ }
1437
+ /** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
1438
+ add(other) {
1439
+ const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
1440
+ const { ex: X2, ey: Y2, ez: Z2, et: T2 } = apoint(other);
1441
+ const a = _a;
1442
+ const d = _d;
1443
+ const A = M(X1 * X2);
1444
+ const B = M(Y1 * Y2);
1445
+ const C2 = M(T1 * d * T2);
1446
+ const D = M(Z1 * Z2);
1447
+ const E = M((X1 + Y1) * (X2 + Y2) - A - B);
1448
+ const F = M(D - C2);
1449
+ const G2 = M(D + C2);
1450
+ const H = M(B - a * A);
1451
+ const X3 = M(E * F);
1452
+ const Y3 = M(G2 * H);
1453
+ const T3 = M(E * H);
1454
+ const Z3 = M(F * G2);
1455
+ return new Point(X3, Y3, Z3, T3);
1456
+ }
1457
+ /**
1458
+ * Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
1459
+ * Uses {@link wNAF} for base point.
1460
+ * Uses fake point to mitigate side-channel leakage.
1461
+ * @param n scalar by which point is multiplied
1462
+ * @param safe safe mode guards against timing attacks; unsafe mode is faster
1463
+ */
1464
+ multiply(n, safe = true) {
1465
+ if (!safe && (n === 0n || this.is0()))
1466
+ return I;
1467
+ arange(n, 1n, N);
1468
+ if (n === 1n)
1469
+ return this;
1470
+ if (this.equals(G))
1471
+ return wNAF(n).p;
1472
+ let p = I;
1473
+ let f = G;
1474
+ for (let d = this; n > 0n; d = d.double(), n >>= 1n) {
1475
+ if (n & 1n)
1476
+ p = p.add(d);
1477
+ else if (safe)
1478
+ f = f.add(d);
1479
+ }
1480
+ return p;
1481
+ }
1482
+ /** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
1483
+ toAffine() {
1484
+ const { ex: x, ey: y, ez: z } = this;
1485
+ if (this.equals(I))
1486
+ return { x: 0n, y: 1n };
1487
+ const iz = invert(z, P);
1488
+ if (M(z * iz) !== 1n)
1489
+ err("invalid inverse");
1490
+ return { x: M(x * iz), y: M(y * iz) };
1491
+ }
1492
+ toBytes() {
1493
+ const { x, y } = this.assertValidity().toAffine();
1494
+ const b = numTo32bLE(y);
1495
+ b[31] |= x & 1n ? 128 : 0;
1496
+ return b;
1497
+ }
1498
+ toHex() {
1499
+ return bytesToHex(this.toBytes());
1500
+ }
1501
+ // encode to hex string
1502
+ clearCofactor() {
1503
+ return this.multiply(big(h), false);
1504
+ }
1505
+ isSmallOrder() {
1506
+ return this.clearCofactor().is0();
1507
+ }
1508
+ isTorsionFree() {
1509
+ let p = this.multiply(N / 2n, false).double();
1510
+ if (N % 2n)
1511
+ p = p.add(this);
1512
+ return p.is0();
1513
+ }
1514
+ static fromHex(hex, zip215) {
1515
+ return Point.fromBytes(toU8(hex), zip215);
1516
+ }
1517
+ get x() {
1518
+ return this.toAffine().x;
1519
+ }
1520
+ get y() {
1521
+ return this.toAffine().y;
1522
+ }
1523
+ toRawBytes() {
1524
+ return this.toBytes();
1525
+ }
1526
+ }
1527
+ const G = new Point(Gx, Gy, 1n, M(Gx * Gy));
1528
+ const I = new Point(0n, 1n, 1n, 0n);
1529
+ Point.BASE = G;
1530
+ Point.ZERO = I;
1531
+ const numTo32bLE = (num) => hexToBytes(padh(arange(num, 0n, B256), L2)).reverse();
1532
+ const bytesToNumLE = (b) => big("0x" + bytesToHex(u8fr(abytes(b)).reverse()));
1533
+ const pow2 = (x, power) => {
1534
+ let r = x;
1535
+ while (power-- > 0n) {
1536
+ r *= r;
1537
+ r %= P;
1538
+ }
1539
+ return r;
1540
+ };
1541
+ const pow_2_252_3 = (x) => {
1542
+ const x2 = x * x % P;
1543
+ const b2 = x2 * x % P;
1544
+ const b4 = pow2(b2, 2n) * b2 % P;
1545
+ const b5 = pow2(b4, 1n) * x % P;
1546
+ const b10 = pow2(b5, 5n) * b5 % P;
1547
+ const b20 = pow2(b10, 10n) * b10 % P;
1548
+ const b40 = pow2(b20, 20n) * b20 % P;
1549
+ const b80 = pow2(b40, 40n) * b40 % P;
1550
+ const b160 = pow2(b80, 80n) * b80 % P;
1551
+ const b240 = pow2(b160, 80n) * b80 % P;
1552
+ const b250 = pow2(b240, 10n) * b10 % P;
1553
+ const pow_p_5_8 = pow2(b250, 2n) * x % P;
1554
+ return { pow_p_5_8, b2 };
1555
+ };
1556
+ const RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n;
1557
+ const uvRatio = (u, v) => {
1558
+ const v3 = M(v * v * v);
1559
+ const v7 = M(v3 * v3 * v);
1560
+ const pow = pow_2_252_3(u * v7).pow_p_5_8;
1561
+ let x = M(u * v3 * pow);
1562
+ const vx2 = M(v * x * x);
1563
+ const root1 = x;
1564
+ const root2 = M(x * RM1);
1565
+ const useRoot1 = vx2 === u;
1566
+ const useRoot2 = vx2 === M(-u);
1567
+ const noRoot = vx2 === M(-u * RM1);
1568
+ if (useRoot1)
1569
+ x = root1;
1570
+ if (useRoot2 || noRoot)
1571
+ x = root2;
1572
+ if ((M(x) & 1n) === 1n)
1573
+ x = M(-x);
1574
+ return { isValid: useRoot1 || useRoot2, value: x };
1575
+ };
1576
+ const modL_LE = (hash) => modN(bytesToNumLE(hash));
1577
+ const sha512a = (...m) => etc.sha512Async(...m);
1578
+ const sha512s = (...m) => callHash("sha512Sync")(...m);
1579
+ const hash2extK = (hashed) => {
1580
+ const head = hashed.slice(0, L);
1581
+ head[0] &= 248;
1582
+ head[31] &= 127;
1583
+ head[31] |= 64;
1584
+ const prefix = hashed.slice(L, L2);
1585
+ const scalar = modL_LE(head);
1586
+ const point = G.multiply(scalar);
1587
+ const pointBytes = point.toBytes();
1588
+ return { head, prefix, scalar, point, pointBytes };
1589
+ };
1590
+ const getExtendedPublicKeyAsync = (priv) => sha512a(toU8(priv, L)).then(hash2extK);
1591
+ const getExtendedPublicKey = (priv) => hash2extK(sha512s(toU8(priv, L)));
1592
+ const getPublicKeyAsync = (priv) => getExtendedPublicKeyAsync(priv).then((p) => p.pointBytes);
1593
+ const getPublicKey = (priv) => getExtendedPublicKey(priv).pointBytes;
1594
+ const hashFinishA = (res) => sha512a(res.hashable).then(res.finish);
1595
+ const _sign = (e, rBytes, msg) => {
1596
+ const { pointBytes: P2, scalar: s } = e;
1597
+ const r = modL_LE(rBytes);
1598
+ const R = G.multiply(r).toBytes();
1599
+ const hashable = concatBytes(R, P2, msg);
1600
+ const finish = (hashed) => {
1601
+ const S = modN(r + modL_LE(hashed) * s);
1602
+ return abytes(concatBytes(R, numTo32bLE(S)), L2);
1603
+ };
1604
+ return { hashable, finish };
1605
+ };
1606
+ const signAsync = async (msg, privKey) => {
1607
+ const m = toU8(msg);
1608
+ const e = await getExtendedPublicKeyAsync(privKey);
1609
+ const rBytes = await sha512a(e.prefix, m);
1610
+ return hashFinishA(_sign(e, rBytes, m));
1611
+ };
1612
+ const veriOpts = { zip215: true };
1613
+ const _verify = (sig, msg, pub, opts = veriOpts) => {
1614
+ sig = toU8(sig, L2);
1615
+ msg = toU8(msg);
1616
+ pub = toU8(pub, L);
1617
+ const { zip215 } = opts;
1618
+ let A;
1619
+ let R;
1620
+ let s;
1621
+ let SB;
1622
+ let hashable = Uint8Array.of();
1623
+ try {
1624
+ A = Point.fromHex(pub, zip215);
1625
+ R = Point.fromHex(sig.slice(0, L), zip215);
1626
+ s = bytesToNumLE(sig.slice(L, L2));
1627
+ SB = G.multiply(s, false);
1628
+ hashable = concatBytes(R.toBytes(), A.toBytes(), msg);
1629
+ } catch (error) {
1630
+ }
1631
+ const finish = (hashed) => {
1632
+ if (SB == null)
1633
+ return false;
1634
+ if (!zip215 && A.isSmallOrder())
1635
+ return false;
1636
+ const k = modL_LE(hashed);
1637
+ const RkA = R.add(A.multiply(k, false));
1638
+ return RkA.add(SB.negate()).clearCofactor().is0();
1639
+ };
1640
+ return { hashable, finish };
1641
+ };
1642
+ const verifyAsync = async (s, m, p, opts = veriOpts) => hashFinishA(_verify(s, m, p, opts));
1643
+ const etc = {
1644
+ sha512Async: async (...messages) => {
1645
+ const s = subtle();
1646
+ const m = concatBytes(...messages);
1647
+ return u8n(await s.digest("SHA-512", m.buffer));
1648
+ },
1649
+ sha512Sync: void 0,
1650
+ bytesToHex,
1651
+ hexToBytes,
1652
+ concatBytes,
1653
+ mod: M,
1654
+ invert,
1655
+ randomBytes
1656
+ };
1657
+ const utils = {
1658
+ getExtendedPublicKeyAsync,
1659
+ getExtendedPublicKey,
1660
+ randomPrivateKey: () => randomBytes(L),
1661
+ precompute: (w = 8, p = G) => {
1662
+ p.multiply(3n);
1663
+ return p;
1664
+ }
1665
+ // no-op
1666
+ };
1667
+ const W = 8;
1668
+ const scalarBits = 256;
1669
+ const pwindows = Math.ceil(scalarBits / W) + 1;
1670
+ const pwindowSize = 2 ** (W - 1);
1671
+ const precompute = () => {
1672
+ const points = [];
1673
+ let p = G;
1674
+ let b = p;
1675
+ for (let w = 0; w < pwindows; w++) {
1676
+ b = p;
1677
+ points.push(b);
1678
+ for (let i = 1; i < pwindowSize; i++) {
1679
+ b = b.add(p);
1680
+ points.push(b);
1681
+ }
1682
+ p = b.double();
1683
+ }
1684
+ return points;
1685
+ };
1686
+ let Gpows = void 0;
1687
+ const ctneg = (cnd, p) => {
1688
+ const n = p.negate();
1689
+ return cnd ? n : p;
1690
+ };
1691
+ const wNAF = (n) => {
1692
+ const comp = Gpows || (Gpows = precompute());
1693
+ let p = I;
1694
+ let f = G;
1695
+ const pow_2_w = 2 ** W;
1696
+ const maxNum = pow_2_w;
1697
+ const mask = big(pow_2_w - 1);
1698
+ const shiftBy = big(W);
1699
+ for (let w = 0; w < pwindows; w++) {
1700
+ let wbits = Number(n & mask);
1701
+ n >>= shiftBy;
1702
+ if (wbits > pwindowSize) {
1703
+ wbits -= maxNum;
1704
+ n += 1n;
1705
+ }
1706
+ const off = w * pwindowSize;
1707
+ const offF = off;
1708
+ const offP = off + Math.abs(wbits) - 1;
1709
+ const isEven = w % 2 !== 0;
1710
+ const isNeg = wbits < 0;
1711
+ if (wbits === 0) {
1712
+ f = f.add(ctneg(isEven, comp[offF]));
1713
+ } else {
1714
+ p = p.add(ctneg(isNeg, comp[offP]));
1715
+ }
1716
+ }
1717
+ return { p, f };
1718
+ };
1719
+ etc.sha512Sync = (...m) => {
1720
+ const hash = createHash("sha512");
1721
+ m.forEach((msg) => hash.update(msg));
1722
+ return hash.digest();
1723
+ };
1724
+ const DOMAIN_PREFIX = "moltnet:v1";
1725
+ function buildSigningBytes(message, nonce) {
1726
+ const msgHash = createHash("sha256").update(Buffer.from(message, "utf-8")).digest();
1727
+ const nonceBytes = Buffer.from(nonce, "utf-8");
1728
+ const prefix = Buffer.from(DOMAIN_PREFIX, "utf-8");
1729
+ const buf = Buffer.alloc(
1730
+ prefix.length + 4 + msgHash.length + 4 + nonceBytes.length
1731
+ );
1732
+ let offset = 0;
1733
+ prefix.copy(buf, offset);
1734
+ offset += prefix.length;
1735
+ buf.writeUInt32BE(msgHash.length, offset);
1736
+ offset += 4;
1737
+ msgHash.copy(buf, offset);
1738
+ offset += msgHash.length;
1739
+ buf.writeUInt32BE(nonceBytes.length, offset);
1740
+ offset += 4;
1741
+ nonceBytes.copy(buf, offset);
1742
+ return new Uint8Array(buf);
1743
+ }
1744
+ const cryptoService = {
1745
+ /**
1746
+ * Generate a new Ed25519 keypair
1747
+ */
1748
+ async generateKeyPair() {
1749
+ const privateKeyBytes = utils.randomPrivateKey();
1750
+ const publicKeyBytes = await getPublicKeyAsync(privateKeyBytes);
1751
+ const privateKey = Buffer.from(privateKeyBytes).toString("base64");
1752
+ const publicKey = `ed25519:${Buffer.from(publicKeyBytes).toString("base64")}`;
1753
+ const fingerprint = this.generateFingerprint(publicKeyBytes);
1754
+ return { publicKey, privateKey, fingerprint };
1755
+ },
1756
+ /**
1757
+ * Generate human-readable fingerprint from public key
1758
+ * Format: A1B2-C3D4-E5F6-G7H8 (first 16 hex chars of SHA256)
1759
+ */
1760
+ generateFingerprint(publicKeyBytes) {
1761
+ const hash = createHash("sha256").update(publicKeyBytes).digest("hex");
1762
+ const segments = hash.slice(0, 16).toUpperCase().match(/.{4}/g) ?? [];
1763
+ return segments.join("-");
1764
+ },
1765
+ /**
1766
+ * Parse public key from string format
1767
+ */
1768
+ parsePublicKey(publicKey) {
1769
+ const base64 = publicKey.replace(/^ed25519:/, "");
1770
+ return new Uint8Array(Buffer.from(base64, "base64"));
1771
+ },
1772
+ /**
1773
+ * Sign a message with private key
1774
+ */
1775
+ async sign(message, privateKeyBase64) {
1776
+ const privateKeyBytes = new Uint8Array(
1777
+ Buffer.from(privateKeyBase64, "base64")
1778
+ );
1779
+ const messageBytes = new TextEncoder().encode(message);
1780
+ const signature = await signAsync(messageBytes, privateKeyBytes);
1781
+ return Buffer.from(signature).toString("base64");
1782
+ },
1783
+ /**
1784
+ * Verify a signature against a message and public key
1785
+ */
1786
+ async verify(message, signature, publicKey) {
1787
+ try {
1788
+ const publicKeyBytes = this.parsePublicKey(publicKey);
1789
+ const signatureBytes = new Uint8Array(Buffer.from(signature, "base64"));
1790
+ const messageBytes = new TextEncoder().encode(message);
1791
+ return await verifyAsync(signatureBytes, messageBytes, publicKeyBytes);
1792
+ } catch {
1793
+ return false;
1794
+ }
1795
+ },
1796
+ /**
1797
+ * Sign a (message, nonce) pair using deterministic pre-hash.
1798
+ * Uses buildSigningBytes for domain separation and canonical serialization.
1799
+ */
1800
+ async signWithNonce(message, nonce, privateKeyBase64) {
1801
+ const privateKeyBytes = new Uint8Array(
1802
+ Buffer.from(privateKeyBase64, "base64")
1803
+ );
1804
+ const signingBytes = buildSigningBytes(message, nonce);
1805
+ const signature = await signAsync(signingBytes, privateKeyBytes);
1806
+ return Buffer.from(signature).toString("base64");
1807
+ },
1808
+ /**
1809
+ * Verify a signature produced by signWithNonce.
1810
+ */
1811
+ async verifyWithNonce(message, nonce, signature, publicKey) {
1812
+ try {
1813
+ const publicKeyBytes = this.parsePublicKey(publicKey);
1814
+ const signatureBytes = new Uint8Array(Buffer.from(signature, "base64"));
1815
+ const signingBytes = buildSigningBytes(message, nonce);
1816
+ return await verifyAsync(signatureBytes, signingBytes, publicKeyBytes);
1817
+ } catch {
1818
+ return false;
1819
+ }
1820
+ },
1821
+ /**
1822
+ * Create a signed message object
1823
+ */
1824
+ async createSignedMessage(message, privateKeyBase64, publicKey) {
1825
+ const signature = await this.sign(message, privateKeyBase64);
1826
+ return { message, signature, publicKey };
1827
+ },
1828
+ /**
1829
+ * Verify a signed message object
1830
+ */
1831
+ async verifySignedMessage(signedMessage) {
1832
+ return this.verify(
1833
+ signedMessage.message,
1834
+ signedMessage.signature,
1835
+ signedMessage.publicKey
1836
+ );
1837
+ },
1838
+ /**
1839
+ * Generate a random challenge for authentication
1840
+ */
1841
+ generateChallenge() {
1842
+ return `moltnet:challenge:${randomBytes$1(32).toString("hex")}:${Date.now()}`;
1843
+ },
1844
+ /**
1845
+ * Derive public key from private key
1846
+ */
1847
+ async derivePublicKey(privateKeyBase64) {
1848
+ const privateKeyBytes = new Uint8Array(
1849
+ Buffer.from(privateKeyBase64, "base64")
1850
+ );
1851
+ const publicKeyBytes = await getPublicKeyAsync(privateKeyBytes);
1852
+ return `ed25519:${Buffer.from(publicKeyBytes).toString("base64")}`;
1853
+ },
1854
+ /**
1855
+ * Get fingerprint from public key string
1856
+ */
1857
+ getFingerprintFromPublicKey(publicKey) {
1858
+ const publicKeyBytes = this.parsePublicKey(publicKey);
1859
+ return this.generateFingerprint(publicKeyBytes);
1860
+ },
1861
+ /**
1862
+ * Create a proof of identity ownership (for DCR metadata)
1863
+ */
1864
+ async createIdentityProof(identityId, privateKeyBase64) {
1865
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1866
+ const message = `moltnet:register:${identityId}:${timestamp}`;
1867
+ const signature = await this.sign(message, privateKeyBase64);
1868
+ return { message, signature, timestamp };
1869
+ },
1870
+ /**
1871
+ * Verify an identity proof
1872
+ */
1873
+ async verifyIdentityProof(proof, publicKey, expectedIdentityId) {
1874
+ const isValid = await this.verify(
1875
+ proof.message,
1876
+ proof.signature,
1877
+ publicKey
1878
+ );
1879
+ if (!isValid) return false;
1880
+ const expectedPrefix = `moltnet:register:${expectedIdentityId}:`;
1881
+ if (!proof.message.startsWith(expectedPrefix)) return false;
1882
+ const proofTime = new Date(proof.timestamp).getTime();
1883
+ const now = Date.now();
1884
+ const fiveMinutes = 5 * 60 * 1e3;
1885
+ if (now - proofTime > fiveMinutes) return false;
1886
+ return true;
1887
+ }
1888
+ };
1889
+ if (!etc.sha512Sync) {
1890
+ etc.sha512Sync = (...m) => {
1891
+ const hash = createHash("sha512");
1892
+ m.forEach((msg) => hash.update(msg));
1893
+ return hash.digest();
1894
+ };
1895
+ }
1896
+ const SSH_ED25519_KEY_TYPE = "ssh-ed25519";
1897
+ const AUTH_MAGIC = "openssh-key-v1\0";
1898
+ const CIPHER_NONE = "none";
1899
+ function encodeUInt32(n) {
1900
+ const buf = Buffer.alloc(4);
1901
+ buf.writeUInt32BE(n, 0);
1902
+ return buf;
1903
+ }
1904
+ function encodeSSHString(data) {
1905
+ const buf = typeof data === "string" ? Buffer.from(data) : data;
1906
+ return Buffer.concat([encodeUInt32(buf.length), buf]);
1907
+ }
1908
+ function toSSHPublicKey(moltnetPublicKey) {
1909
+ const match = moltnetPublicKey.match(/^ed25519:(.+)$/);
1910
+ if (!match) {
1911
+ throw new Error(
1912
+ 'Invalid MoltNet public key format: expected "ed25519:<base64>"'
1913
+ );
1914
+ }
1915
+ const pubkeyBytes = Buffer.from(match[1], "base64");
1916
+ if (pubkeyBytes.length !== 32) {
1917
+ throw new Error(
1918
+ `Invalid Ed25519 public key length: expected 32 bytes, got ${pubkeyBytes.length}`
1919
+ );
1920
+ }
1921
+ const blob = Buffer.concat([
1922
+ encodeSSHString(SSH_ED25519_KEY_TYPE),
1923
+ encodeSSHString(pubkeyBytes)
1924
+ ]);
1925
+ return `${SSH_ED25519_KEY_TYPE} ${blob.toString("base64")}`;
1926
+ }
1927
+ function toSSHPrivateKey(seedBase64) {
1928
+ const seed = Buffer.from(seedBase64, "base64");
1929
+ if (seed.length !== 32) {
1930
+ throw new Error(
1931
+ `Invalid Ed25519 seed length: expected 32 bytes, got ${seed.length}`
1932
+ );
1933
+ }
1934
+ const pubkeyBytes = Buffer.from(getPublicKey(seed));
1935
+ const pubkeyBlob = Buffer.concat([
1936
+ encodeSSHString(SSH_ED25519_KEY_TYPE),
1937
+ encodeSSHString(pubkeyBytes)
1938
+ ]);
1939
+ const checkInt = encodeUInt32(0);
1940
+ const privkeyData = Buffer.concat([seed, pubkeyBytes]);
1941
+ const comment = Buffer.alloc(0);
1942
+ const privateSection = Buffer.concat([
1943
+ checkInt,
1944
+ // checkint1
1945
+ checkInt,
1946
+ // checkint2
1947
+ encodeSSHString(SSH_ED25519_KEY_TYPE),
1948
+ // keytype
1949
+ encodeSSHString(pubkeyBytes),
1950
+ // pubkey
1951
+ encodeSSHString(privkeyData),
1952
+ // privkey (seed + pubkey)
1953
+ encodeSSHString(comment)
1954
+ // comment
1955
+ ]);
1956
+ const blockSize = 8;
1957
+ const padLength = privateSection.length % blockSize === 0 ? 0 : blockSize - privateSection.length % blockSize;
1958
+ const padding = Buffer.alloc(padLength);
1959
+ for (let i = 0; i < padLength; i++) {
1960
+ padding[i] = i + 1;
1961
+ }
1962
+ const paddedPrivateSection = Buffer.concat([privateSection, padding]);
1963
+ const keyBinary = Buffer.concat([
1964
+ Buffer.from(AUTH_MAGIC, "ascii"),
1965
+ // "openssh-key-v1\0"
1966
+ encodeSSHString(CIPHER_NONE),
1967
+ // ciphername
1968
+ encodeSSHString(CIPHER_NONE),
1969
+ // kdfname
1970
+ encodeSSHString(Buffer.alloc(0)),
1971
+ // kdf options (empty)
1972
+ encodeUInt32(1),
1973
+ // number of keys
1974
+ encodeSSHString(pubkeyBlob),
1975
+ // public key blob
1976
+ encodeSSHString(paddedPrivateSection)
1977
+ // private section
1978
+ ]);
1979
+ const base64 = keyBinary.toString("base64");
1980
+ const lines = [];
1981
+ for (let i = 0; i < base64.length; i += 70) {
1982
+ lines.push(base64.slice(i, i + 70));
1983
+ }
1984
+ return [
1985
+ "-----BEGIN OPENSSH PRIVATE KEY-----",
1986
+ ...lines,
1987
+ "-----END OPENSSH PRIVATE KEY-----",
1988
+ ""
1989
+ // trailing newline
1990
+ ].join("\n");
1991
+ }
1992
+ async function exportSSHKey(opts) {
1993
+ const config = await readConfig(opts?.configDir);
1994
+ if (!config) {
1995
+ throw new Error(`No config found at ${getConfigPath(opts?.configDir)} — run \`moltnet register\` first`);
1996
+ }
1997
+ const privateKeySSH = toSSHPrivateKey(config.keys.private_key);
1998
+ const publicKeySSH = toSSHPublicKey(config.keys.public_key);
1999
+ const outputDir = opts?.outputDir ?? join(opts?.configDir ?? getConfigDir(), "ssh");
2000
+ await mkdir(outputDir, { recursive: true });
2001
+ const privatePath = join(outputDir, "id_ed25519");
2002
+ const publicPath = join(outputDir, "id_ed25519.pub");
2003
+ await writeFile(privatePath, privateKeySSH, { mode: 384 });
2004
+ await writeFile(publicPath, publicKeySSH, { mode: 420 });
2005
+ await updateConfigSection("ssh", { private_key_path: privatePath, public_key_path: publicPath }, opts?.configDir);
2006
+ return { privatePath, publicPath };
2007
+ }
2008
+ const POLL_INTERVAL_MS = 5e3;
2009
+ function isProblemDetails(err2) {
2010
+ return typeof err2 === "object" && err2 !== null && "title" in err2 && typeof err2.title === "string" && "status" in err2 && typeof err2.status === "number";
2011
+ }
2012
+ function toErrorMessage(err2) {
2013
+ if (err2 instanceof Error) {
2014
+ return err2.message;
2015
+ }
2016
+ if (isProblemDetails(err2)) {
2017
+ return problemToError(err2, err2.status).message;
2018
+ }
2019
+ return JSON.stringify(err2);
2020
+ }
2021
+ const POLL_TIMEOUT_MS = 5 * 60 * 1e3;
2022
+ function makeClient(baseUrl) {
2023
+ return createClient({ baseUrl });
2024
+ }
2025
+ async function startOnboarding(baseUrl, body) {
2026
+ const client2 = makeClient(baseUrl);
2027
+ const res = await startLegreffierOnboarding({
2028
+ client: client2,
2029
+ body,
2030
+ throwOnError: true
2031
+ });
2032
+ return res.data;
2033
+ }
2034
+ async function checkWorkflowLive(baseUrl, workflowId) {
2035
+ try {
2036
+ const result = await pollStatus(baseUrl, workflowId);
2037
+ return result.status !== "failed";
2038
+ } catch {
2039
+ return false;
2040
+ }
2041
+ }
2042
+ async function pollStatus(baseUrl, workflowId) {
2043
+ const client2 = makeClient(baseUrl);
2044
+ const res = await getLegreffierOnboardingStatus({
2045
+ client: client2,
2046
+ path: { workflowId },
2047
+ throwOnError: true
2048
+ });
2049
+ return res.data;
2050
+ }
2051
+ async function pollUntil(baseUrl, workflowId, targetStatuses, onTick) {
2052
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
2053
+ while (Date.now() < deadline) {
2054
+ const result = await pollStatus(baseUrl, workflowId);
2055
+ onTick?.(result.status);
2056
+ if (targetStatuses.includes(result.status)) {
2057
+ return result;
2058
+ }
2059
+ if (result.status === "failed") {
2060
+ throw new Error("Onboarding workflow failed");
2061
+ }
2062
+ await new Promise((resolve) => {
2063
+ setTimeout(resolve, POLL_INTERVAL_MS);
2064
+ });
2065
+ }
2066
+ throw new Error(
2067
+ `Timed out waiting for status: ${targetStatuses.join(" or ")}`
2068
+ );
2069
+ }
2070
+ const SKILLS = [
2071
+ {
2072
+ name: "legreffier",
2073
+ url: "https://raw.githubusercontent.com/getlarge/themoltnet/main/.claude/skills/legreffier/SKILL.md"
2074
+ }
2075
+ ];
2076
+ async function downloadSkills(repoDir) {
2077
+ for (const skill of SKILLS) {
2078
+ const dir2 = join(repoDir, ".claude", "skills", skill.name);
2079
+ await mkdir(dir2, { recursive: true });
2080
+ const res = await fetch(skill.url);
2081
+ if (!res.ok) {
2082
+ throw new Error(`Failed to download skill ${skill.name} (${res.status})`);
2083
+ }
2084
+ const content = await res.text();
2085
+ await writeFile(join(dir2, "SKILL.md"), content, "utf-8");
2086
+ }
2087
+ }
2088
+ function toEnvPrefix(agentName) {
2089
+ return agentName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
2090
+ }
2091
+ async function writeSettingsLocal({
2092
+ repoDir,
2093
+ agentName,
2094
+ appSlug,
2095
+ pemPath,
2096
+ installationId,
2097
+ clientId,
2098
+ clientSecret
2099
+ }) {
2100
+ const dir2 = join(repoDir, ".claude");
2101
+ await mkdir(dir2, { recursive: true });
2102
+ const filePath = join(dir2, "settings.local.json");
2103
+ let existing = {};
2104
+ try {
2105
+ existing = JSON.parse(await readFile(filePath, "utf-8"));
2106
+ } catch {
2107
+ }
2108
+ const prefix = toEnvPrefix(agentName);
2109
+ const settings = {
2110
+ ...existing,
2111
+ enableAllProjectMcpServers: true,
2112
+ env: {
2113
+ ...existing.env,
2114
+ [`${prefix}_GITHUB_APP_ID`]: appSlug,
2115
+ [`${prefix}_GITHUB_APP_PRIVATE_KEY_PATH`]: pemPath,
2116
+ [`${prefix}_GITHUB_APP_INSTALLATION_ID`]: installationId,
2117
+ [`${prefix}_CLIENT_ID`]: clientId,
2118
+ [`${prefix}_CLIENT_SECRET`]: clientSecret
2119
+ }
2120
+ };
2121
+ await writeFile(filePath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
2122
+ }
2123
+ function deriveProjectSlug(cwd = process.cwd()) {
2124
+ try {
2125
+ const origin = execSync("git remote get-url origin", {
2126
+ cwd,
2127
+ stdio: ["pipe", "pipe", "pipe"]
2128
+ }).toString().trim();
2129
+ const match = origin.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/);
2130
+ if (match) {
2131
+ return match[1].replace("/", "-");
2132
+ }
2133
+ } catch {
2134
+ }
2135
+ return basename(cwd);
2136
+ }
2137
+ function getStatePath(projectSlug, agentName) {
2138
+ return join(
2139
+ homedir(),
2140
+ ".config",
2141
+ "moltnet",
2142
+ projectSlug,
2143
+ `legreffier-init.${agentName}.state.json`
2144
+ );
2145
+ }
2146
+ async function readState(projectSlug, agentName) {
2147
+ try {
2148
+ const raw = await readFile(getStatePath(projectSlug, agentName), "utf-8");
2149
+ return JSON.parse(raw);
2150
+ } catch {
2151
+ return null;
2152
+ }
2153
+ }
2154
+ async function writeState(state, projectSlug, agentName) {
2155
+ const path = getStatePath(projectSlug, agentName);
2156
+ await mkdir(join(homedir(), ".config", "moltnet", projectSlug), {
2157
+ recursive: true
2158
+ });
2159
+ await writeFile(path, JSON.stringify(state, null, 2) + "\n", {
2160
+ mode: 384
2161
+ });
2162
+ }
2163
+ async function clearState(projectSlug, agentName) {
2164
+ try {
2165
+ await rm(getStatePath(projectSlug, agentName));
2166
+ } catch {
2167
+ }
2168
+ }
2169
+ async function runAgentSetupPhase(opts) {
2170
+ const {
2171
+ apiUrl: apiUrl2,
2172
+ repoDir,
2173
+ configDir,
2174
+ agentName,
2175
+ publicKey,
2176
+ fingerprint,
2177
+ appSlug,
2178
+ pemPath,
2179
+ installationId,
2180
+ identityId,
2181
+ clientId,
2182
+ clientSecret,
2183
+ projectSlug,
2184
+ dispatch
2185
+ } = opts;
2186
+ dispatch({ type: "phase", phase: "agent_setup" });
2187
+ const existingConfig = await readConfig(configDir);
2188
+ if (!existingConfig?.oauth2?.client_id && clientId) {
2189
+ await writeConfig(
2190
+ {
2191
+ identity_id: identityId,
2192
+ registered_at: (/* @__PURE__ */ new Date()).toISOString(),
2193
+ oauth2: { client_id: clientId, client_secret: clientSecret },
2194
+ keys: {
2195
+ public_key: publicKey,
2196
+ private_key: existingConfig?.keys?.private_key ?? "",
2197
+ fingerprint
2198
+ },
2199
+ endpoints: {
2200
+ api: apiUrl2,
2201
+ mcp: apiUrl2.replace("://api.", "://mcp.") + "/mcp"
2202
+ },
2203
+ github: {
2204
+ app_id: appSlug,
2205
+ app_slug: appSlug,
2206
+ installation_id: installationId,
2207
+ private_key_path: pemPath
2208
+ }
2209
+ },
2210
+ configDir
2211
+ );
2212
+ }
2213
+ if (clientId) {
2214
+ const prefix = toEnvPrefix(agentName);
2215
+ const mcpUrl = apiUrl2.replace("://api.", "://mcp.") + "/mcp";
2216
+ await writeMcpConfig(
2217
+ {
2218
+ mcpServers: {
2219
+ [agentName]: {
2220
+ type: "http",
2221
+ url: mcpUrl,
2222
+ headers: {
2223
+ "X-Client-Id": `\${${prefix}_CLIENT_ID}`,
2224
+ "X-Client-Secret": `\${${prefix}_CLIENT_SECRET}`
2225
+ }
2226
+ }
2227
+ }
2228
+ },
2229
+ repoDir
2230
+ );
2231
+ }
2232
+ dispatch({ type: "step", key: "skills", status: "running" });
2233
+ await downloadSkills(repoDir);
2234
+ dispatch({ type: "step", key: "skills", status: "done" });
2235
+ dispatch({ type: "step", key: "settings", status: "running" });
2236
+ await writeSettingsLocal({
2237
+ repoDir,
2238
+ agentName,
2239
+ appSlug,
2240
+ pemPath,
2241
+ installationId,
2242
+ clientId,
2243
+ clientSecret
2244
+ });
2245
+ dispatch({ type: "step", key: "settings", status: "done" });
2246
+ await clearState(projectSlug, agentName);
2247
+ }
2248
+ async function exchangeManifestCode(code) {
2249
+ const res = await fetch(
2250
+ `https://api.github.com/app-manifests/${code}/conversions`,
2251
+ {
2252
+ method: "POST",
2253
+ headers: {
2254
+ Accept: "application/vnd.github+json",
2255
+ "X-GitHub-Api-Version": "2022-11-28"
2256
+ }
2257
+ }
2258
+ );
2259
+ if (!res.ok) {
2260
+ const body = await res.text();
2261
+ throw new Error(`GitHub code exchange failed (${res.status}): ${body}`);
2262
+ }
2263
+ const data = await res.json();
2264
+ return {
2265
+ appId: String(data.id),
2266
+ appSlug: data.slug,
2267
+ pem: data.pem,
2268
+ clientId: data.client_id,
2269
+ clientSecret: data.client_secret
2270
+ };
2271
+ }
2272
+ const GITHUB_HEADERS = {
2273
+ Accept: "application/vnd.github+json",
2274
+ "X-GitHub-Api-Version": "2022-11-28"
2275
+ };
2276
+ async function githubUserExists(username) {
2277
+ const res = await fetch(
2278
+ `https://api.github.com/users/${encodeURIComponent(username)}`,
2279
+ { headers: GITHUB_HEADERS }
2280
+ );
2281
+ return res.status === 200;
2282
+ }
2283
+ async function checkAppNameAvailable(appName) {
2284
+ const [userTaken, botTaken] = await Promise.all([
2285
+ githubUserExists(appName),
2286
+ githubUserExists(appName + "[bot]")
2287
+ ]);
2288
+ return !userTaken && !botTaken;
2289
+ }
2290
+ async function suggestAppNames(appName) {
2291
+ const candidates = [
2292
+ `${appName}-bot`,
2293
+ `${appName}-app`,
2294
+ `${appName}-moltnet`,
2295
+ `my-${appName}`
2296
+ ];
2297
+ const results = await Promise.all(
2298
+ candidates.map(async (name2) => ({
2299
+ name: name2,
2300
+ available: await checkAppNameAvailable(name2)
2301
+ }))
2302
+ );
2303
+ return results.filter((r) => r.available).map((r) => r.name);
2304
+ }
2305
+ async function lookupBotUser(appSlug, { maxRetries = 5, baseDelayMs = 2e3 } = {}) {
2306
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2307
+ for (const username of [`${appSlug}[bot]`, appSlug]) {
2308
+ const res = await fetch(
2309
+ `https://api.github.com/users/${encodeURIComponent(username)}`,
2310
+ { headers: GITHUB_HEADERS }
2311
+ );
2312
+ if (res.ok) {
2313
+ const data = await res.json();
2314
+ return {
2315
+ id: data.id,
2316
+ email: `${data.id}+${data.login}@users.noreply.github.com`
2317
+ };
2318
+ }
2319
+ }
2320
+ if (attempt < maxRetries) {
2321
+ const delayMs = baseDelayMs * 2 ** attempt;
2322
+ await new Promise((resolve) => {
2323
+ setTimeout(resolve, delayMs);
2324
+ });
2325
+ }
2326
+ }
2327
+ throw new Error(`GitHub user lookup failed for app "${appSlug}"`);
2328
+ }
2329
+ async function writePem(pem, appSlug, projectSlug) {
2330
+ const dir2 = join(homedir(), ".config", "moltnet", projectSlug);
2331
+ await mkdir(dir2, { recursive: true });
2332
+ const path = join(dir2, `${appSlug}.pem`);
2333
+ await writeFile(path, pem, { mode: 384 });
2334
+ await chmod(path, 384);
2335
+ return path;
2336
+ }
2337
+ async function writeGitConfig({
2338
+ configDir,
2339
+ name: name2,
2340
+ email,
2341
+ sshKeyPath
2342
+ }) {
2343
+ const content = [
2344
+ "[user]",
2345
+ ` name = ${name2}`,
2346
+ ` email = ${email}`,
2347
+ "[gpg]",
2348
+ " format = ssh",
2349
+ '[gpg "ssh"]',
2350
+ ` signingKey = ${sshKeyPath}`,
2351
+ "[commit]",
2352
+ " gpgsign = true",
2353
+ ""
2354
+ ].join("\n");
2355
+ const filePath = join(configDir, "gitconfig");
2356
+ await writeFile(filePath, content, { mode: 384 });
2357
+ return filePath;
2358
+ }
2359
+ async function runGithubAppPhase(opts) {
2360
+ const {
2361
+ apiUrl: apiUrl2,
2362
+ agentName,
2363
+ configDir,
2364
+ projectSlug,
2365
+ publicKey,
2366
+ privateKey,
2367
+ fingerprint,
2368
+ workflowId,
2369
+ manifestFormUrl,
2370
+ dispatch
2371
+ } = opts;
2372
+ const existingConfig = await readConfig(configDir);
2373
+ const existingState = await readState(projectSlug, agentName);
2374
+ if (existingConfig?.github?.app_id) {
2375
+ dispatch({ type: "step", key: "githubApp", status: "skipped" });
2376
+ dispatch({
2377
+ type: "appSlug",
2378
+ appSlug: existingConfig.github.app_slug ?? ""
2379
+ });
2380
+ return {
2381
+ appSlug: existingConfig.github.app_slug ?? "",
2382
+ pemPath: existingConfig.github.private_key_path,
2383
+ installationId: existingConfig.github.installation_id,
2384
+ skipped: true
2385
+ };
2386
+ }
2387
+ if (existingState?.appSlug && existingState?.appId) {
2388
+ const pemPath2 = join(
2389
+ homedir(),
2390
+ ".config",
2391
+ "moltnet",
2392
+ projectSlug,
2393
+ `${existingState.appSlug}.pem`
2394
+ );
2395
+ dispatch({ type: "step", key: "githubApp", status: "skipped" });
2396
+ dispatch({ type: "appSlug", appSlug: existingState.appSlug });
2397
+ return {
2398
+ appSlug: existingState.appSlug,
2399
+ pemPath: pemPath2,
2400
+ installationId: "",
2401
+ skipped: true
2402
+ };
2403
+ }
2404
+ dispatch({ type: "phase", phase: "github_app" });
2405
+ dispatch({ type: "step", key: "githubApp", status: "running" });
2406
+ dispatch({ type: "manifestFormUrl", url: manifestFormUrl });
2407
+ await open(manifestFormUrl);
2408
+ const codeResult = await pollUntil(
2409
+ apiUrl2,
2410
+ workflowId,
2411
+ ["github_code_ready", "awaiting_installation", "completed"],
2412
+ (status) => dispatch({ type: "serverStatus", status })
2413
+ );
2414
+ if (!codeResult.githubCode) {
2415
+ throw new Error("GitHub code not available in onboarding status");
2416
+ }
2417
+ const ghCreds = await exchangeManifestCode(codeResult.githubCode);
2418
+ dispatch({ type: "appSlug", appSlug: ghCreds.appSlug });
2419
+ const pemPath = await writePem(ghCreds.pem, ghCreds.appSlug, projectSlug);
2420
+ await writeState(
2421
+ {
2422
+ workflowId,
2423
+ publicKey,
2424
+ privateKey,
2425
+ fingerprint,
2426
+ agentName,
2427
+ phase: "awaiting_installation",
2428
+ appId: ghCreds.appId,
2429
+ appSlug: ghCreds.appSlug
2430
+ },
2431
+ projectSlug,
2432
+ agentName
2433
+ );
2434
+ dispatch({ type: "step", key: "githubApp", status: "done" });
2435
+ return {
2436
+ appSlug: ghCreds.appSlug,
2437
+ pemPath,
2438
+ installationId: "",
2439
+ skipped: false
2440
+ };
2441
+ }
2442
+ async function runGitSetupPhase(opts) {
2443
+ const { configDir, agentName, appSlug, dispatch } = opts;
2444
+ const existingConfig = await readConfig(configDir);
2445
+ if (existingConfig?.git?.config_path) {
2446
+ dispatch({ type: "step", key: "gitSetup", status: "skipped" });
2447
+ return;
2448
+ }
2449
+ dispatch({ type: "phase", phase: "git_setup" });
2450
+ dispatch({ type: "step", key: "gitSetup", status: "running" });
2451
+ const { privatePath } = await exportSSHKey({ configDir });
2452
+ const botUser = await lookupBotUser(appSlug);
2453
+ const gitConfigPath = await writeGitConfig({
2454
+ configDir,
2455
+ name: agentName,
2456
+ email: botUser.email,
2457
+ sshKeyPath: privatePath
2458
+ });
2459
+ await updateConfigSection(
2460
+ "git",
2461
+ {
2462
+ name: agentName,
2463
+ email: botUser.email,
2464
+ signing: true,
2465
+ config_path: gitConfigPath
2466
+ },
2467
+ configDir
2468
+ );
2469
+ dispatch({ type: "step", key: "gitSetup", status: "done" });
2470
+ }
2471
+ async function runIdentityPhase(opts) {
2472
+ const { apiUrl: apiUrl2, agentName, configDir, projectSlug, dispatch } = opts;
2473
+ const existingConfig = await readConfig(configDir);
2474
+ const existingState = await readState(projectSlug, agentName);
2475
+ if (existingConfig?.keys?.public_key && existingConfig?.oauth2?.client_id) {
2476
+ dispatch({ type: "step", key: "keypair", status: "skipped" });
2477
+ dispatch({ type: "step", key: "register", status: "skipped" });
2478
+ dispatch({
2479
+ type: "fingerprint",
2480
+ fingerprint: existingConfig.keys.fingerprint
2481
+ });
2482
+ const workflowId = existingState?.workflowId && !existingConfig.github?.app_id ? existingState.workflowId : "";
2483
+ return {
2484
+ publicKey: existingConfig.keys.public_key,
2485
+ privateKey: existingConfig.keys.private_key,
2486
+ fingerprint: existingConfig.keys.fingerprint,
2487
+ workflowId,
2488
+ manifestFormUrl: "",
2489
+ clientId: existingConfig.oauth2.client_id,
2490
+ clientSecret: existingConfig.oauth2.client_secret,
2491
+ skipped: true
2492
+ };
2493
+ }
2494
+ const resumePublicKey = existingConfig?.keys?.public_key ?? existingState?.publicKey;
2495
+ const resumeFingerprint = existingConfig?.keys?.fingerprint ?? existingState?.fingerprint;
2496
+ if (existingState?.workflowId && resumePublicKey && resumeFingerprint) {
2497
+ const live = await checkWorkflowLive(apiUrl2, existingState.workflowId);
2498
+ if (live) {
2499
+ dispatch({ type: "step", key: "keypair", status: "skipped" });
2500
+ dispatch({ type: "step", key: "register", status: "skipped" });
2501
+ dispatch({ type: "fingerprint", fingerprint: resumeFingerprint });
2502
+ const resumePrivateKey = existingConfig?.keys?.private_key ?? existingState.privateKey;
2503
+ if (!existingConfig?.keys?.public_key) {
2504
+ await writeConfig(
2505
+ {
2506
+ identity_id: "",
2507
+ registered_at: (/* @__PURE__ */ new Date()).toISOString(),
2508
+ oauth2: { client_id: "", client_secret: "" },
2509
+ keys: {
2510
+ public_key: resumePublicKey,
2511
+ private_key: resumePrivateKey,
2512
+ fingerprint: resumeFingerprint
2513
+ },
2514
+ endpoints: { api: apiUrl2, mcp: "" }
2515
+ },
2516
+ configDir
2517
+ );
2518
+ }
2519
+ return {
2520
+ publicKey: resumePublicKey,
2521
+ privateKey: resumePrivateKey,
2522
+ fingerprint: resumeFingerprint,
2523
+ workflowId: existingState.workflowId,
2524
+ manifestFormUrl: "",
2525
+ clientId: existingConfig?.oauth2?.client_id ?? "",
2526
+ clientSecret: existingConfig?.oauth2?.client_secret ?? "",
2527
+ skipped: true
2528
+ };
2529
+ }
2530
+ await clearState(projectSlug, agentName);
2531
+ }
2532
+ const available = await checkAppNameAvailable(agentName);
2533
+ if (!available) {
2534
+ const suggestions = await suggestAppNames(agentName);
2535
+ const hint = suggestions.length > 0 ? ` Try one of: ${suggestions.map((s) => `--name ${s}`).join(", ")}` : ` Try adding a suffix like --name ${agentName}-bot`;
2536
+ throw new Error(`GitHub App name "${agentName}" is already taken.${hint}`);
2537
+ }
2538
+ dispatch({ type: "step", key: "keypair", status: "running" });
2539
+ const kp = await cryptoService.generateKeyPair();
2540
+ dispatch({ type: "fingerprint", fingerprint: kp.fingerprint });
2541
+ dispatch({ type: "step", key: "keypair", status: "done" });
2542
+ dispatch({ type: "step", key: "register", status: "running" });
2543
+ const started = await startOnboarding(apiUrl2, {
2544
+ publicKey: kp.publicKey,
2545
+ fingerprint: kp.fingerprint,
2546
+ agentName
2547
+ });
2548
+ await writeState(
2549
+ {
2550
+ workflowId: started.workflowId,
2551
+ publicKey: kp.publicKey,
2552
+ privateKey: kp.privateKey,
2553
+ fingerprint: kp.fingerprint,
2554
+ agentName,
2555
+ phase: "awaiting_github"
2556
+ },
2557
+ projectSlug,
2558
+ agentName
2559
+ );
2560
+ await writeConfig(
2561
+ {
2562
+ identity_id: "",
2563
+ registered_at: (/* @__PURE__ */ new Date()).toISOString(),
2564
+ oauth2: { client_id: "", client_secret: "" },
2565
+ keys: {
2566
+ public_key: kp.publicKey,
2567
+ private_key: kp.privateKey,
2568
+ fingerprint: kp.fingerprint
2569
+ },
2570
+ endpoints: { api: apiUrl2, mcp: "" }
2571
+ },
2572
+ configDir
2573
+ );
2574
+ dispatch({ type: "step", key: "register", status: "done" });
2575
+ return {
2576
+ publicKey: kp.publicKey,
2577
+ privateKey: kp.privateKey,
2578
+ fingerprint: kp.fingerprint,
2579
+ workflowId: started.workflowId,
2580
+ manifestFormUrl: started.manifestFormUrl,
2581
+ clientId: "",
2582
+ clientSecret: "",
2583
+ skipped: false
2584
+ };
2585
+ }
2586
+ async function runInstallationPhase(opts) {
2587
+ const { apiUrl: apiUrl2, configDir, workflowId, appSlug, dispatch } = opts;
2588
+ const existingConfig = await readConfig(configDir);
2589
+ if (existingConfig?.github?.installation_id && existingConfig?.oauth2?.client_id) {
2590
+ dispatch({ type: "step", key: "installation", status: "skipped" });
2591
+ return {
2592
+ installationId: existingConfig.github.installation_id,
2593
+ identityId: existingConfig.identity_id ?? "",
2594
+ clientId: existingConfig.oauth2.client_id,
2595
+ clientSecret: existingConfig.oauth2.client_secret
2596
+ };
2597
+ }
2598
+ dispatch({ type: "phase", phase: "installation" });
2599
+ dispatch({ type: "step", key: "installation", status: "running" });
2600
+ const installUrl = `https://github.com/apps/${appSlug}/installations/new`;
2601
+ dispatch({ type: "installationUrl", url: installUrl });
2602
+ await open(installUrl);
2603
+ const result = await pollUntil(
2604
+ apiUrl2,
2605
+ workflowId,
2606
+ ["completed"],
2607
+ (status) => dispatch({ type: "serverStatus", status })
2608
+ );
2609
+ dispatch({ type: "step", key: "installation", status: "done" });
2610
+ return {
2611
+ installationId: "",
2612
+ identityId: result.identityId ?? "",
2613
+ clientId: result.clientId ?? "",
2614
+ clientSecret: result.clientSecret ?? ""
2615
+ };
2616
+ }
2617
+ const AGENTS = [
2618
+ {
2619
+ id: "claude",
2620
+ label: "Claude Code",
2621
+ description: "settings.local.json + .mcp.json + /legreffier skill",
2622
+ available: true
2623
+ },
2624
+ {
2625
+ id: "cursor",
2626
+ label: "Cursor",
2627
+ description: "coming soon",
2628
+ available: false
2629
+ },
2630
+ {
2631
+ id: "codex",
2632
+ label: "Codex",
2633
+ description: "coming soon",
2634
+ available: false
2635
+ }
2636
+ ];
2637
+ function AgentSelect({ onSelect }) {
2638
+ const [selected, setSelected] = useState(0);
2639
+ useInput((_input, key) => {
2640
+ if (key.upArrow) {
2641
+ setSelected((i) => i > 0 ? i - 1 : i);
2642
+ } else if (key.downArrow) {
2643
+ setSelected((i) => i < AGENTS.length - 1 ? i + 1 : i);
2644
+ } else if (key.return) {
2645
+ const agent2 = AGENTS[selected];
2646
+ if (agent2 && agent2.available) {
2647
+ onSelect(agent2.id);
2648
+ }
2649
+ }
2650
+ });
2651
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs(
2652
+ Box,
2653
+ {
2654
+ borderStyle: "round",
2655
+ borderColor: cliTheme.color.primary,
2656
+ flexDirection: "column",
2657
+ paddingX: 2,
2658
+ paddingY: 1,
2659
+ children: [
2660
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.primary, bold: true, children: "Select your AI coding agent" }),
2661
+ /* @__PURE__ */ jsx(Text, { children: " " }),
2662
+ AGENTS.map((agent2, i) => {
2663
+ const isCurrent = i === selected;
2664
+ const prefix = isCurrent ? "▸ " : " ";
2665
+ return /* @__PURE__ */ jsxs(Box, { children: [
2666
+ /* @__PURE__ */ jsx(
2667
+ Text,
2668
+ {
2669
+ color: !agent2.available ? cliTheme.color.muted : isCurrent ? cliTheme.color.accent : cliTheme.color.text,
2670
+ bold: isCurrent && agent2.available,
2671
+ children: prefix + agent2.label
2672
+ }
2673
+ ),
2674
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.muted, children: " " + agent2.description })
2675
+ ] }, agent2.id);
2676
+ }),
2677
+ /* @__PURE__ */ jsx(Text, { children: " " }),
2678
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.muted, children: " ↑↓ to navigate, Enter to select" })
2679
+ ]
2680
+ }
2681
+ ) });
2682
+ }
2683
+ const initialSteps = {
2684
+ keypair: "pending",
2685
+ register: "pending",
2686
+ githubApp: "pending",
2687
+ gitSetup: "pending",
2688
+ installation: "pending",
2689
+ skills: "pending",
2690
+ settings: "pending"
2691
+ };
2692
+ function uiReducer(state, action) {
2693
+ switch (action.type) {
2694
+ case "step":
2695
+ return {
2696
+ ...state,
2697
+ steps: { ...state.steps, [action.key]: action.status }
2698
+ };
2699
+ case "phase":
2700
+ return { ...state, phase: action.phase };
2701
+ case "fingerprint":
2702
+ return { ...state, fingerprint: action.fingerprint };
2703
+ case "appSlug":
2704
+ return { ...state, appSlug: action.appSlug };
2705
+ case "serverStatus":
2706
+ return { ...state, serverStatus: action.status };
2707
+ case "manifestFormUrl":
2708
+ return { ...state, manifestFormUrl: action.url };
2709
+ case "installationUrl":
2710
+ return { ...state, installationUrl: action.url };
2711
+ case "summary":
2712
+ return { ...state, summary: action.summary };
2713
+ case "error":
2714
+ return { ...state, phase: "error", errorMessage: action.message };
2715
+ default:
2716
+ return state;
2717
+ }
2718
+ }
2719
+ const WORK_PHASES = [
2720
+ "identity",
2721
+ "github_app",
2722
+ "git_setup",
2723
+ "installation",
2724
+ "agent_setup"
2725
+ ];
2726
+ function isFuturePhase(current, target) {
2727
+ return WORK_PHASES.indexOf(target) > WORK_PHASES.indexOf(current);
2728
+ }
2729
+ function DisclaimerPhase({
2730
+ selectedAgent,
2731
+ onAccept,
2732
+ onSelectAgent,
2733
+ onReject
2734
+ }) {
2735
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingY: 1, children: [
2736
+ /* @__PURE__ */ jsx(CliHero, { animated: true }),
2737
+ /* @__PURE__ */ jsx(
2738
+ CliDisclaimer,
2739
+ {
2740
+ onAccept: selectedAgent ? onAccept : onSelectAgent,
2741
+ onReject
2742
+ }
2743
+ )
2744
+ ] });
2745
+ }
2746
+ function AgentSelectPhase({
2747
+ onSelect
2748
+ }) {
2749
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingY: 1, children: [
2750
+ /* @__PURE__ */ jsx(CliHero, {}),
2751
+ /* @__PURE__ */ jsx(AgentSelect, { onSelect })
2752
+ ] });
2753
+ }
2754
+ function ErrorPhase({ message }) {
2755
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingY: 1, children: [
2756
+ /* @__PURE__ */ jsx(CliHero, {}),
2757
+ /* @__PURE__ */ jsx(
2758
+ Box,
2759
+ {
2760
+ borderStyle: "round",
2761
+ borderColor: cliTheme.color.error,
2762
+ paddingX: 2,
2763
+ paddingY: 1,
2764
+ marginBottom: 1,
2765
+ children: /* @__PURE__ */ jsx(Text, { color: cliTheme.color.error, bold: true, children: "✗ Setup failed: " + (message ?? "unknown error") })
2766
+ }
2767
+ ),
2768
+ /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
2769
+ " ",
2770
+ "Run again to resume from where you left off."
2771
+ ] })
2772
+ ] });
2773
+ }
2774
+ function DonePhase({ summary }) {
2775
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingY: 1, children: [
2776
+ /* @__PURE__ */ jsx(CliHero, {}),
2777
+ summary && /* @__PURE__ */ jsx(
2778
+ CliSummaryBox,
2779
+ {
2780
+ agentName: summary.agentName,
2781
+ fingerprint: summary.fingerprint,
2782
+ appSlug: summary.appSlug,
2783
+ apiUrl: summary.apiUrl,
2784
+ mcpUrl: summary.mcpUrl
2785
+ }
2786
+ )
2787
+ ] });
2788
+ }
2789
+ function ProgressPhase({
2790
+ state,
2791
+ name: name2,
2792
+ showManifestFallback,
2793
+ showInstallFallback
2794
+ }) {
2795
+ const {
2796
+ phase,
2797
+ fingerprint,
2798
+ appSlug,
2799
+ serverStatus,
2800
+ manifestFormUrl,
2801
+ installationUrl,
2802
+ steps
2803
+ } = state;
2804
+ const future = (p) => isFuturePhase(phase, p);
2805
+ const githubAppSpinnerLabel = serverStatus === "awaiting_installation" ? "GitHub App created, waiting for installation…" : `Waiting for GitHub App creation (app name: "${name2}")…`;
2806
+ const installationSpinnerLabel = serverStatus === "completed" ? "Installation confirmed, finalising…" : "Waiting for GitHub App installation…";
2807
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingY: 1, children: [
2808
+ /* @__PURE__ */ jsx(CliHero, {}),
2809
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.muted, children: " API: " + state.agentName }),
2810
+ /* @__PURE__ */ jsx(CliStepHeader, { n: 1, total: 4, label: "Identity" }),
2811
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2812
+ /* @__PURE__ */ jsx(
2813
+ CliStatusLine,
2814
+ {
2815
+ label: "Generate Ed25519 keypair",
2816
+ status: future("identity") ? "pending" : steps.keypair,
2817
+ detail: steps.keypair === "done" || steps.keypair === "skipped" ? fingerprint : void 0
2818
+ }
2819
+ ),
2820
+ /* @__PURE__ */ jsx(
2821
+ CliStatusLine,
2822
+ {
2823
+ label: "Register on MoltNet",
2824
+ status: future("identity") ? "pending" : steps.register
2825
+ }
2826
+ )
2827
+ ] }),
2828
+ /* @__PURE__ */ jsx(CliStepHeader, { n: 2, total: 4, label: "GitHub App" }),
2829
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: steps.githubApp === "running" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2830
+ /* @__PURE__ */ jsx(CliSpinner, { label: githubAppSpinnerLabel }),
2831
+ showManifestFallback && manifestFormUrl ? /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
2832
+ " → ",
2833
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.accent, children: manifestFormUrl })
2834
+ ] }) : null
2835
+ ] }) : /* @__PURE__ */ jsx(
2836
+ CliStatusLine,
2837
+ {
2838
+ label: "Create GitHub App",
2839
+ status: future("github_app") ? "pending" : steps.githubApp,
2840
+ detail: appSlug ?? void 0
2841
+ }
2842
+ ) }),
2843
+ /* @__PURE__ */ jsx(CliStepHeader, { n: 3, total: 4, label: "Git identity" }),
2844
+ /* @__PURE__ */ jsx(
2845
+ CliStatusLine,
2846
+ {
2847
+ label: "Export SSH keys + configure git",
2848
+ status: future("git_setup") ? "pending" : steps.gitSetup
2849
+ }
2850
+ ),
2851
+ /* @__PURE__ */ jsx(CliStepHeader, { n: 4, total: 4, label: "Finalise" }),
2852
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2853
+ steps.installation === "running" ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2854
+ /* @__PURE__ */ jsx(CliSpinner, { label: installationSpinnerLabel }),
2855
+ showInstallFallback && installationUrl ? /* @__PURE__ */ jsxs(Text, { color: cliTheme.color.muted, children: [
2856
+ " → ",
2857
+ /* @__PURE__ */ jsx(Text, { color: cliTheme.color.accent, children: installationUrl })
2858
+ ] }) : null
2859
+ ] }) : /* @__PURE__ */ jsx(
2860
+ CliStatusLine,
2861
+ {
2862
+ label: "GitHub App installation",
2863
+ status: future("installation") ? "pending" : steps.installation
2864
+ }
2865
+ ),
2866
+ /* @__PURE__ */ jsx(
2867
+ CliStatusLine,
2868
+ {
2869
+ label: "Download skills",
2870
+ status: future("installation") ? "pending" : steps.skills
2871
+ }
2872
+ ),
2873
+ /* @__PURE__ */ jsx(
2874
+ CliStatusLine,
2875
+ {
2876
+ label: "Write settings.local.json",
2877
+ status: future("installation") ? "pending" : steps.settings
2878
+ }
2879
+ )
2880
+ ] }),
2881
+ /* @__PURE__ */ jsx(CliDivider, {})
2882
+ ] });
2883
+ }
2884
+ function InitApp({
2885
+ name: name2,
2886
+ agent: agentProp,
2887
+ apiUrl: apiUrl2,
2888
+ dir: dir2 = process.cwd()
2889
+ }) {
2890
+ const { exit } = useApp();
2891
+ const [state, dispatch] = useReducer(uiReducer, {
2892
+ phase: "disclaimer",
2893
+ agentName: name2,
2894
+ steps: initialSteps
2895
+ });
2896
+ const [accepted, setAccepted] = useState(false);
2897
+ const [selectedAgent, setSelectedAgent] = useState(
2898
+ agentProp ?? null
2899
+ );
2900
+ const [showManifestFallback, setShowManifestFallback] = useState(false);
2901
+ const [showInstallFallback, setShowInstallFallback] = useState(false);
2902
+ const manifestTimerRef = useRef(null);
2903
+ const installTimerRef = useRef(null);
2904
+ useEffect(() => {
2905
+ if (state.manifestFormUrl) {
2906
+ manifestTimerRef.current = setTimeout(
2907
+ () => setShowManifestFallback(true),
2908
+ 2e3
2909
+ );
2910
+ return () => {
2911
+ if (manifestTimerRef.current) clearTimeout(manifestTimerRef.current);
2912
+ };
2913
+ }
2914
+ }, [state.manifestFormUrl]);
2915
+ useEffect(() => {
2916
+ if (state.installationUrl) {
2917
+ installTimerRef.current = setTimeout(
2918
+ () => setShowInstallFallback(true),
2919
+ 2e3
2920
+ );
2921
+ return () => {
2922
+ if (installTimerRef.current) clearTimeout(installTimerRef.current);
2923
+ };
2924
+ }
2925
+ }, [state.installationUrl]);
2926
+ useEffect(() => {
2927
+ if (!accepted) return;
2928
+ dispatch({ type: "phase", phase: "identity" });
2929
+ void (async () => {
2930
+ try {
2931
+ const configDir = join(dir2, ".moltnet", name2);
2932
+ const projectSlug = deriveProjectSlug(dir2);
2933
+ const identity = await runIdentityPhase({
2934
+ apiUrl: apiUrl2,
2935
+ agentName: name2,
2936
+ configDir,
2937
+ projectSlug,
2938
+ dispatch
2939
+ });
2940
+ const githubApp = await runGithubAppPhase({
2941
+ apiUrl: apiUrl2,
2942
+ agentName: name2,
2943
+ configDir,
2944
+ projectSlug,
2945
+ publicKey: identity.publicKey,
2946
+ privateKey: identity.privateKey,
2947
+ fingerprint: identity.fingerprint,
2948
+ workflowId: identity.workflowId,
2949
+ manifestFormUrl: identity.manifestFormUrl,
2950
+ dispatch
2951
+ });
2952
+ await runGitSetupPhase({
2953
+ configDir,
2954
+ agentName: name2,
2955
+ appSlug: githubApp.appSlug,
2956
+ dispatch
2957
+ });
2958
+ const installation = await runInstallationPhase({
2959
+ apiUrl: apiUrl2,
2960
+ configDir,
2961
+ workflowId: identity.workflowId,
2962
+ appSlug: githubApp.appSlug,
2963
+ dispatch
2964
+ });
2965
+ await runAgentSetupPhase({
2966
+ apiUrl: apiUrl2,
2967
+ repoDir: dir2,
2968
+ configDir,
2969
+ agentName: name2,
2970
+ publicKey: identity.publicKey,
2971
+ fingerprint: identity.fingerprint,
2972
+ appSlug: githubApp.appSlug,
2973
+ pemPath: githubApp.pemPath,
2974
+ installationId: installation.installationId || githubApp.installationId,
2975
+ identityId: installation.identityId,
2976
+ clientId: installation.clientId || identity.clientId,
2977
+ clientSecret: installation.clientSecret || identity.clientSecret,
2978
+ projectSlug,
2979
+ dispatch
2980
+ });
2981
+ const mcpUrl = apiUrl2.replace("://api.", "://mcp.") + "/mcp";
2982
+ dispatch({
2983
+ type: "summary",
2984
+ summary: {
2985
+ agentName: name2,
2986
+ fingerprint: identity.fingerprint,
2987
+ appSlug: githubApp.appSlug,
2988
+ apiUrl: apiUrl2,
2989
+ mcpUrl
2990
+ }
2991
+ });
2992
+ dispatch({ type: "phase", phase: "done" });
2993
+ setTimeout(() => exit(), 3e3);
2994
+ } catch (err2) {
2995
+ dispatch({ type: "error", message: toErrorMessage(err2) });
2996
+ }
2997
+ })();
2998
+ }, [accepted]);
2999
+ const { phase } = state;
3000
+ const renderPhase = {
3001
+ disclaimer: () => /* @__PURE__ */ jsx(
3002
+ DisclaimerPhase,
3003
+ {
3004
+ selectedAgent,
3005
+ onAccept: () => setAccepted(true),
3006
+ onSelectAgent: () => dispatch({ type: "phase", phase: "agent_select" }),
3007
+ onReject: () => exit()
3008
+ }
3009
+ ),
3010
+ agent_select: () => /* @__PURE__ */ jsx(
3011
+ AgentSelectPhase,
3012
+ {
3013
+ onSelect: (agent2) => {
3014
+ setSelectedAgent(agent2);
3015
+ setAccepted(true);
3016
+ }
3017
+ }
3018
+ ),
3019
+ error: () => /* @__PURE__ */ jsx(ErrorPhase, { message: state.errorMessage }),
3020
+ done: () => /* @__PURE__ */ jsx(DonePhase, { summary: state.summary })
3021
+ };
3022
+ const renderer = renderPhase[phase];
3023
+ if (renderer) return renderer();
3024
+ return /* @__PURE__ */ jsx(
3025
+ ProgressPhase,
3026
+ {
3027
+ state,
3028
+ name: name2,
3029
+ showManifestFallback,
3030
+ showInstallFallback
3031
+ }
3032
+ );
3033
+ }
3034
+ const SUPPORTED_AGENTS = ["claude"];
3035
+ const { values } = parseArgs({
3036
+ args: process.argv.slice(2),
3037
+ options: {
3038
+ name: { type: "string", short: "n" },
3039
+ agent: { type: "string", short: "a" },
3040
+ "api-url": { type: "string" },
3041
+ dir: { type: "string" }
3042
+ }
3043
+ });
3044
+ const name = values["name"];
3045
+ const agentFlag = values["agent"];
3046
+ const apiUrl = values["api-url"] ?? process.env.MOLTNET_API_URL ?? "https://api.themolt.net";
3047
+ const dir = values["dir"] ?? process.cwd();
3048
+ if (!name) {
3049
+ process.stderr.write(
3050
+ "Usage: legreffier --name <agent-name> [--agent claude] [--api-url <url>] [--dir <path>]\n"
3051
+ );
3052
+ process.exit(1);
3053
+ }
3054
+ if (agentFlag && !SUPPORTED_AGENTS.includes(agentFlag)) {
3055
+ process.stderr.write(
3056
+ `Unsupported agent: ${agentFlag}. Supported: ${SUPPORTED_AGENTS.join(", ")}
3057
+ `
3058
+ );
3059
+ process.exit(1);
3060
+ }
3061
+ const agent = agentFlag;
3062
+ render(/* @__PURE__ */ jsx(InitApp, { name, agent, apiUrl, dir }));