@amadeus-dlc/setup 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,2250 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { readFileSync } from "node:fs";
5
+ import { dirname as dirname4, join as join10, resolve as resolve3 } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ // src/domain/command.ts
9
+ import { parseArgs } from "node:util";
10
+
11
+ // src/shared/result.ts
12
+ var Result;
13
+ ((Result) => {
14
+ function ok(value) {
15
+ return Object.freeze({ type: "ok", value });
16
+ }
17
+ Result.ok = ok;
18
+ function err(error) {
19
+ return Object.freeze({ type: "err", error });
20
+ }
21
+ Result.err = err;
22
+ })(Result ||= {});
23
+ Object.freeze(Result);
24
+
25
+ // src/domain/harness.ts
26
+ var HarnessName;
27
+ ((HarnessName) => {
28
+ HarnessName.all = Object.freeze([
29
+ "claude",
30
+ "codex",
31
+ "kiro",
32
+ "kiro-ide"
33
+ ]);
34
+ function parse(raw) {
35
+ if (HarnessName.all.includes(raw)) {
36
+ return Result.ok(raw);
37
+ }
38
+ return Result.err(Object.freeze({ raw }));
39
+ }
40
+ HarnessName.parse = parse;
41
+ })(HarnessName ||= {});
42
+ Object.freeze(HarnessName);
43
+
44
+ // src/internal/version-spec-factory.ts
45
+ function createVersionSpec(kind, exactVersion) {
46
+ return Object.freeze({
47
+ kind,
48
+ admits(candidate) {
49
+ if (kind === "latest") {
50
+ return candidate.isStable();
51
+ }
52
+ return exactVersion !== null && candidate.equals(exactVersion);
53
+ },
54
+ describe() {
55
+ return kind === "latest" ? "latest stable version" : `version ${exactVersion?.format() ?? "?"}`;
56
+ }
57
+ });
58
+ }
59
+
60
+ // src/internal/semver-factory.ts
61
+ function createSemVer(major, minor, patch, prerelease) {
62
+ return Object.freeze({
63
+ major,
64
+ minor,
65
+ patch,
66
+ prerelease,
67
+ isStable() {
68
+ return prerelease === null;
69
+ },
70
+ isLaterThan(other) {
71
+ if (major !== other.major)
72
+ return major > other.major;
73
+ if (minor !== other.minor)
74
+ return minor > other.minor;
75
+ if (patch !== other.patch)
76
+ return patch > other.patch;
77
+ return false;
78
+ },
79
+ equals(other) {
80
+ return major === other.major && minor === other.minor && patch === other.patch && prerelease === other.prerelease;
81
+ },
82
+ format() {
83
+ return prerelease ? `v${major}.${minor}.${patch}-${prerelease}` : `v${major}.${minor}.${patch}`;
84
+ }
85
+ });
86
+ }
87
+
88
+ // src/domain/semver.ts
89
+ var SEMVER_PATTERN = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
90
+ var SemVer;
91
+ ((SemVer) => {
92
+ function parse(raw) {
93
+ const match = SEMVER_PATTERN.exec(raw.trim());
94
+ if (!match) {
95
+ return Result.err(VersionError.invalidFormat(raw, "expected MAJOR.MINOR.PATCH[-PRERELEASE], optionally prefixed with 'v'"));
96
+ }
97
+ const [, majorRaw, minorRaw, patchRaw, prerelease] = match;
98
+ return Result.ok(createSemVer(Number(majorRaw), Number(minorRaw), Number(patchRaw), prerelease ?? null));
99
+ }
100
+ SemVer.parse = parse;
101
+ function latestStableOf(list) {
102
+ let best;
103
+ for (const candidate of list) {
104
+ if (!candidate.isStable())
105
+ continue;
106
+ if (!best || candidate.isLaterThan(best))
107
+ best = candidate;
108
+ }
109
+ return best;
110
+ }
111
+ SemVer.latestStableOf = latestStableOf;
112
+ })(SemVer ||= {});
113
+ Object.freeze(SemVer);
114
+ var VersionError;
115
+ ((VersionError) => {
116
+ function invalidFormat(raw, reason) {
117
+ return Object.freeze({ type: "invalid-format", raw, reason });
118
+ }
119
+ VersionError.invalidFormat = invalidFormat;
120
+ })(VersionError ||= {});
121
+ Object.freeze(VersionError);
122
+
123
+ // src/domain/version-spec.ts
124
+ var VersionSpec;
125
+ ((VersionSpec) => {
126
+ function latest() {
127
+ return createVersionSpec("latest", null);
128
+ }
129
+ VersionSpec.latest = latest;
130
+ function exact(raw) {
131
+ const parsed = SemVer.parse(raw);
132
+ if (parsed.type === "err")
133
+ return parsed;
134
+ return Result.ok(createVersionSpec("exact", parsed.value));
135
+ }
136
+ VersionSpec.exact = exact;
137
+ })(VersionSpec ||= {});
138
+ Object.freeze(VersionSpec);
139
+
140
+ // src/domain/command.ts
141
+ var UsageError;
142
+ ((UsageError) => {
143
+ function unknownSubcommand(raw) {
144
+ return Object.freeze({ type: "unknown-subcommand", raw });
145
+ }
146
+ UsageError.unknownSubcommand = unknownSubcommand;
147
+ function unknownFlag(raw) {
148
+ return Object.freeze({ type: "unknown-flag", raw });
149
+ }
150
+ UsageError.unknownFlag = unknownFlag;
151
+ function invalidHarness(raw) {
152
+ return Object.freeze({ type: "invalid-harness", raw });
153
+ }
154
+ UsageError.invalidHarness = invalidHarness;
155
+ function multipleHarnesses(raws) {
156
+ return Object.freeze({ type: "multiple-harnesses", raws });
157
+ }
158
+ UsageError.multipleHarnesses = multipleHarnesses;
159
+ function missingRequired(fields) {
160
+ return Object.freeze({ type: "missing-required", fields });
161
+ }
162
+ UsageError.missingRequired = missingRequired;
163
+ function invalidVersion(cause) {
164
+ return Object.freeze({ type: "invalid-version", cause });
165
+ }
166
+ UsageError.invalidVersion = invalidVersion;
167
+ })(UsageError ||= {});
168
+ Object.freeze(UsageError);
169
+ function createParsedCommand(state) {
170
+ return Object.freeze({
171
+ ...state,
172
+ isNonInteractive(stdinIsTty) {
173
+ return state.yes || !stdinIsTty;
174
+ },
175
+ missingRequiredFor(_mode) {
176
+ const missing = [];
177
+ if (state.harness === null)
178
+ missing.push("harness");
179
+ if (state.target === null)
180
+ missing.push("target");
181
+ return missing;
182
+ }
183
+ });
184
+ }
185
+ var ParsedCommand;
186
+ ((ParsedCommand) => {
187
+ function parse(argv) {
188
+ const [rawSubcommand, ...rest] = argv;
189
+ if (rawSubcommand === undefined) {
190
+ return Result.ok(createParsedCommand({
191
+ subcommand: "help",
192
+ harness: null,
193
+ target: null,
194
+ version: VersionSpec.latest(),
195
+ yes: false,
196
+ force: false
197
+ }));
198
+ }
199
+ if (rawSubcommand !== "install" && rawSubcommand !== "upgrade") {
200
+ return Result.err(UsageError.unknownSubcommand(rawSubcommand));
201
+ }
202
+ let values;
203
+ try {
204
+ const parsed = parseArgs({
205
+ args: rest,
206
+ options: {
207
+ harness: { type: "string", multiple: true },
208
+ target: { type: "string" },
209
+ version: { type: "string" },
210
+ yes: { type: "boolean", default: false },
211
+ force: { type: "boolean", default: false }
212
+ },
213
+ strict: true,
214
+ allowPositionals: false
215
+ });
216
+ values = parsed.values;
217
+ } catch (cause) {
218
+ return Result.err(UsageError.unknownFlag(describeParseArgsFailure(cause)));
219
+ }
220
+ const harnessRaws = values.harness ?? [];
221
+ if (harnessRaws.length > 1) {
222
+ return Result.err(UsageError.multipleHarnesses(harnessRaws));
223
+ }
224
+ let harness = null;
225
+ if (harnessRaws.length === 1) {
226
+ const parsedHarness = HarnessName.parse(harnessRaws[0]);
227
+ if (parsedHarness.type === "err")
228
+ return Result.err(UsageError.invalidHarness(parsedHarness.error.raw));
229
+ harness = parsedHarness.value;
230
+ }
231
+ let version = VersionSpec.latest();
232
+ if (values.version !== undefined) {
233
+ const parsedVersion = VersionSpec.exact(values.version);
234
+ if (parsedVersion.type === "err")
235
+ return Result.err(UsageError.invalidVersion(parsedVersion.error));
236
+ version = parsedVersion.value;
237
+ }
238
+ return Result.ok(createParsedCommand({
239
+ subcommand: rawSubcommand,
240
+ harness,
241
+ target: values.target ?? null,
242
+ version,
243
+ yes: values.yes === true,
244
+ force: values.force === true
245
+ }));
246
+ }
247
+ ParsedCommand.parse = parse;
248
+ })(ParsedCommand ||= {});
249
+ Object.freeze(ParsedCommand);
250
+ function describeParseArgsFailure(cause) {
251
+ if (cause instanceof Error) {
252
+ const match = /'(--?[^']+)'/.exec(cause.message);
253
+ if (match)
254
+ return match[1];
255
+ return cause.message;
256
+ }
257
+ return String(cause);
258
+ }
259
+ var InstallInputs;
260
+ ((InstallInputs) => {
261
+ function fromFlags(parsed) {
262
+ if (parsed.harness === null || parsed.target === null) {
263
+ throw new Error("InstallInputs.fromFlags requires harness and target to already be resolved (BR-I03 precondition)");
264
+ }
265
+ return Object.freeze({ harness: parsed.harness, target: parsed.target });
266
+ }
267
+ InstallInputs.fromFlags = fromFlags;
268
+ })(InstallInputs ||= {});
269
+ Object.freeze(InstallInputs);
270
+
271
+ // src/domain/installation.ts
272
+ import { readFile, stat } from "node:fs/promises";
273
+ import { join } from "node:path";
274
+
275
+ // src/domain/engine-layout.ts
276
+ var ENGINE_DIR_BY_HARNESS = Object.freeze({
277
+ claude: ".claude",
278
+ codex: ".codex",
279
+ kiro: ".kiro",
280
+ "kiro-ide": ".kiro"
281
+ });
282
+ function engineDirNameFor(harness) {
283
+ const dir = ENGINE_DIR_BY_HARNESS[harness];
284
+ if (dir === undefined) {
285
+ throw new Error(`no engine directory mapping is defined for harness "${harness}"`);
286
+ }
287
+ return dir;
288
+ }
289
+ function allEngineDirNames() {
290
+ return Object.freeze([...new Set(Object.values(ENGINE_DIR_BY_HARNESS))]);
291
+ }
292
+
293
+ // src/domain/installation.ts
294
+ var Installation;
295
+ ((Installation) => {
296
+ async function detect(target, manifestIo) {
297
+ const manifestResult = await manifestIo.read(target);
298
+ if (manifestResult.type === "ok" && manifestResult.value !== null) {
299
+ return manifestedInstallation(manifestResult.value);
300
+ }
301
+ const evidence = await scanEvidence(target);
302
+ if (evidence.paths.length === 0) {
303
+ return noneInstallation();
304
+ }
305
+ const missing = [];
306
+ if (!evidence.anchors.toolsDir)
307
+ missing.push("tools directory");
308
+ if (!evidence.anchors.amadeusCommon)
309
+ missing.push("amadeus-common directory");
310
+ if (missing.length > 0) {
311
+ return partialInstallation(missing);
312
+ }
313
+ return manualOrUnknownInstallation(evidence);
314
+ }
315
+ Installation.detect = detect;
316
+ })(Installation ||= {});
317
+ Object.freeze(Installation);
318
+ function admissionFor(kind, force, detected) {
319
+ if (kind === "none")
320
+ return Object.freeze({ type: "proceed" });
321
+ if (force)
322
+ return Object.freeze({ type: "proceed-forced" });
323
+ return Object.freeze({ type: "refuse-suggest-upgrade", detected });
324
+ }
325
+ function noneInstallation() {
326
+ return Object.freeze({
327
+ kind: "none",
328
+ admitsInstall(force) {
329
+ return admissionFor("none", force, "");
330
+ }
331
+ });
332
+ }
333
+ function manifestedInstallation(manifest) {
334
+ const detected = `${manifest.harness} ${manifest.sourceTag} (installed ${manifest.installedAt})`;
335
+ return Object.freeze({
336
+ kind: "manifested",
337
+ manifest,
338
+ admitsInstall(force) {
339
+ return admissionFor("manifested", force, detected);
340
+ }
341
+ });
342
+ }
343
+ function manualOrUnknownInstallation(evidence) {
344
+ const detected = `a manual or unrecognized Amadeus installation (found: ${evidence.paths.join(", ")})`;
345
+ return Object.freeze({
346
+ kind: "manual-or-unknown",
347
+ evidence,
348
+ admitsInstall(force) {
349
+ return admissionFor("manual-or-unknown", force, detected);
350
+ }
351
+ });
352
+ }
353
+ function partialInstallation(missing) {
354
+ const detected = `a partial Amadeus installation (missing: ${missing.join(", ")})`;
355
+ return Object.freeze({
356
+ kind: "partial",
357
+ missing,
358
+ admitsInstall(force) {
359
+ return admissionFor("partial", force, detected);
360
+ }
361
+ });
362
+ }
363
+ async function scanEvidence(target) {
364
+ const paths = [];
365
+ let toolsDir = false;
366
+ let amadeusCommon = false;
367
+ let versionFileContent = null;
368
+ for (const engineDir of allEngineDirNames()) {
369
+ const hasToolsDir = await dirExists(join(target, engineDir, "tools"));
370
+ const hasAmadeusCommon = await dirExists(join(target, engineDir, "amadeus-common"));
371
+ let versionContent = null;
372
+ try {
373
+ versionContent = await readFile(join(target, engineDir, "VERSION"), "utf8");
374
+ } catch {}
375
+ if (!hasToolsDir && !hasAmadeusCommon && versionContent === null)
376
+ continue;
377
+ if (hasToolsDir) {
378
+ paths.push(`${engineDir}/tools`);
379
+ toolsDir = true;
380
+ }
381
+ if (hasAmadeusCommon) {
382
+ paths.push(`${engineDir}/amadeus-common`);
383
+ amadeusCommon = true;
384
+ }
385
+ if (versionContent !== null && versionFileContent === null) {
386
+ versionFileContent = versionContent;
387
+ paths.push(`${engineDir}/VERSION`);
388
+ }
389
+ }
390
+ return Object.freeze({
391
+ paths: Object.freeze(paths),
392
+ versionFileContent,
393
+ anchors: Object.freeze({ toolsDir, amadeusCommon })
394
+ });
395
+ }
396
+ async function dirExists(path) {
397
+ try {
398
+ const info = await stat(path);
399
+ return info.isDirectory();
400
+ } catch {
401
+ return false;
402
+ }
403
+ }
404
+
405
+ // src/internal/manifest-files-factory.ts
406
+ function createManifestFiles(entries) {
407
+ const byPath = new Map(entries.map((entry) => [entry.path, entry]));
408
+ return Object.freeze({
409
+ requiredPaths() {
410
+ return entries.filter((entry) => entry.required).map((entry) => entry.path);
411
+ },
412
+ dispositionFor(path, actualMd5) {
413
+ const entry = byPath.get(path);
414
+ if (!entry || entry.class === "owned") {
415
+ return Object.freeze({ type: "overwrite" });
416
+ }
417
+ if (entry.class === "user-preserved") {
418
+ return Object.freeze({ type: "preserve" });
419
+ }
420
+ return actualMd5 === entry.md5 ? Object.freeze({ type: "overwrite" }) : Object.freeze({ type: "backup-then-copy" });
421
+ },
422
+ entries() {
423
+ return entries;
424
+ }
425
+ });
426
+ }
427
+
428
+ // src/internal/manifest-factory.ts
429
+ function createManifest(state) {
430
+ return Object.freeze({
431
+ schemaVersion: 1,
432
+ installerPackageVersion: state.installerPackageVersion,
433
+ distributionVersion: state.distributionVersion,
434
+ sourceTag: state.sourceTag,
435
+ installedAt: state.installedAt,
436
+ harness: state.harness,
437
+ dispositionFor(path, actualMd5) {
438
+ return state.files.dispositionFor(path, actualMd5);
439
+ },
440
+ isNewerThan(candidate) {
441
+ return state.distributionVersion.isLaterThan(candidate);
442
+ },
443
+ requiredPaths() {
444
+ return state.files.requiredPaths();
445
+ },
446
+ upgradedTo(next) {
447
+ return createManifest({
448
+ installerPackageVersion: next.meta.installerPackageVersion,
449
+ distributionVersion: next.payload.version.semver,
450
+ sourceTag: next.payload.version.tag,
451
+ installedAt: next.meta.installStartedAt,
452
+ harness: next.meta.harness,
453
+ files: next.files
454
+ });
455
+ },
456
+ toJSON() {
457
+ return Object.freeze({
458
+ schemaVersion: 1,
459
+ installerPackageVersion: state.installerPackageVersion,
460
+ distributionVersion: state.distributionVersion.format(),
461
+ sourceTag: state.sourceTag,
462
+ installedAt: state.installedAt,
463
+ harness: state.harness,
464
+ files: state.files.entries().map((entry) => ({
465
+ path: entry.path,
466
+ class: entry.class,
467
+ required: entry.required,
468
+ md5: entry.md5
469
+ }))
470
+ });
471
+ }
472
+ });
473
+ }
474
+
475
+ // src/domain/manifest.ts
476
+ var ManifestFiles;
477
+ ((ManifestFiles) => {
478
+ function fromEntries(entries) {
479
+ const seen = new Set;
480
+ for (const entry of entries) {
481
+ if (seen.has(entry.path))
482
+ return Result.err(ManifestError.duplicatePath(entry.path));
483
+ seen.add(entry.path);
484
+ }
485
+ return Result.ok(createManifestFiles(entries));
486
+ }
487
+ ManifestFiles.fromEntries = fromEntries;
488
+ })(ManifestFiles ||= {});
489
+ Object.freeze(ManifestFiles);
490
+ var Manifest;
491
+ ((Manifest) => {
492
+ function parse(json) {
493
+ return parseManifestJson(json);
494
+ }
495
+ Manifest.parse = parse;
496
+ function build(payload, files, meta) {
497
+ return createManifest({
498
+ installerPackageVersion: meta.installerPackageVersion,
499
+ distributionVersion: payload.version.semver,
500
+ sourceTag: payload.version.tag,
501
+ installedAt: meta.installStartedAt,
502
+ harness: meta.harness,
503
+ files
504
+ });
505
+ }
506
+ Manifest.build = build;
507
+ })(Manifest ||= {});
508
+ Object.freeze(Manifest);
509
+ var ManifestError;
510
+ ((ManifestError) => {
511
+ function schemaUnsupported(found) {
512
+ return Object.freeze({ type: "schema-unsupported", found });
513
+ }
514
+ ManifestError.schemaUnsupported = schemaUnsupported;
515
+ function malformed(detail) {
516
+ return Object.freeze({ type: "malformed", detail });
517
+ }
518
+ ManifestError.malformed = malformed;
519
+ function unknownHarness(raw) {
520
+ return Object.freeze({ type: "unknown-harness", raw });
521
+ }
522
+ ManifestError.unknownHarness = unknownHarness;
523
+ function duplicatePath(path) {
524
+ return Object.freeze({ type: "duplicate-path", path });
525
+ }
526
+ ManifestError.duplicatePath = duplicatePath;
527
+ function io(detail) {
528
+ return Object.freeze({ type: "io", detail });
529
+ }
530
+ ManifestError.io = io;
531
+ })(ManifestError ||= {});
532
+ Object.freeze(ManifestError);
533
+ function parseManifestJson(json) {
534
+ if (typeof json !== "object" || json === null) {
535
+ return Result.err(ManifestError.malformed("expected a JSON object"));
536
+ }
537
+ const obj = json;
538
+ if (obj.schemaVersion !== 1) {
539
+ return Result.err(ManifestError.schemaUnsupported(obj.schemaVersion));
540
+ }
541
+ const { installerPackageVersion, distributionVersion: distributionVersionRaw, sourceTag, installedAt, harness: harnessRaw, files: filesRaw } = obj;
542
+ if (typeof installerPackageVersion !== "string" || typeof distributionVersionRaw !== "string" || typeof sourceTag !== "string" || typeof installedAt !== "string" || typeof harnessRaw !== "string" || !Array.isArray(filesRaw)) {
543
+ return Result.err(ManifestError.malformed("one or more required fields are missing or the wrong type"));
544
+ }
545
+ if (!sourceTag.startsWith("v")) {
546
+ return Result.err(ManifestError.malformed(`sourceTag must start with "v", got ${sourceTag}`));
547
+ }
548
+ if (!HarnessName.all.includes(harnessRaw)) {
549
+ return Result.err(ManifestError.unknownHarness(harnessRaw));
550
+ }
551
+ const distributionVersion = SemVer.parse(distributionVersionRaw);
552
+ if (distributionVersion.type === "err") {
553
+ return Result.err(ManifestError.malformed(`invalid distributionVersion: ${distributionVersion.error.reason}`));
554
+ }
555
+ const parsedFiles = [];
556
+ for (const entry of filesRaw) {
557
+ const parsedEntry = parseManifestFile(entry);
558
+ if (parsedEntry === null) {
559
+ return Result.err(ManifestError.malformed("a files[] entry is missing a required field or has the wrong type"));
560
+ }
561
+ parsedFiles.push(parsedEntry);
562
+ }
563
+ const files = ManifestFiles.fromEntries(parsedFiles);
564
+ if (files.type === "err")
565
+ return files;
566
+ return Result.ok(createManifest({
567
+ installerPackageVersion,
568
+ distributionVersion: distributionVersion.value,
569
+ sourceTag,
570
+ installedAt,
571
+ harness: harnessRaw,
572
+ files: files.value
573
+ }));
574
+ }
575
+ function parseManifestFile(entry) {
576
+ if (typeof entry !== "object" || entry === null)
577
+ return null;
578
+ const e = entry;
579
+ if (typeof e.path !== "string" || typeof e.md5 !== "string" || typeof e.required !== "boolean")
580
+ return null;
581
+ if (e.class !== "owned" && e.class !== "shared" && e.class !== "user-preserved")
582
+ return null;
583
+ return { path: e.path, class: e.class, required: e.required, md5: e.md5 };
584
+ }
585
+
586
+ // src/domain/plan.ts
587
+ import { createHash } from "node:crypto";
588
+ import { closeSync, existsSync, openSync, readSync, readdirSync, statSync } from "node:fs";
589
+ import { join as join2, relative, sep } from "node:path";
590
+
591
+ // src/shared/timestamps.ts
592
+ var Timestamps;
593
+ ((Timestamps) => {
594
+ function of(now) {
595
+ const iso = now.toISOString();
596
+ const token = iso.replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
597
+ return Object.freeze({ iso, token });
598
+ }
599
+ Timestamps.of = of;
600
+ })(Timestamps ||= {});
601
+ Object.freeze(Timestamps);
602
+
603
+ // src/domain/plan.ts
604
+ var PlanRefusal;
605
+ ((PlanRefusal) => {
606
+ function alreadyInstalled(admission) {
607
+ return Object.freeze({ type: "already-installed", admission });
608
+ }
609
+ PlanRefusal.alreadyInstalled = alreadyInstalled;
610
+ function harnessNotInPayload(harness) {
611
+ return Object.freeze({ type: "harness-not-in-payload", harness });
612
+ }
613
+ PlanRefusal.harnessNotInPayload = harnessNotInPayload;
614
+ })(PlanRefusal ||= {});
615
+ Object.freeze(PlanRefusal);
616
+ function createPlan(entries, startedAtIso, backupTimestamp, root) {
617
+ return Object.freeze({
618
+ startedAtIso,
619
+ backupTimestamp,
620
+ entries() {
621
+ return entries;
622
+ },
623
+ entriesBy(action) {
624
+ return entries.filter((entry) => entry.action === action);
625
+ },
626
+ hasConflicts() {
627
+ return entries.some((entry) => entry.action === "conflict");
628
+ },
629
+ isNoop() {
630
+ return entries.every((entry) => entry.action === "skip");
631
+ },
632
+ summary() {
633
+ return Object.freeze({
634
+ add: entries.filter((entry) => entry.action === "add").length,
635
+ update: entries.filter((entry) => entry.action === "update").length,
636
+ skip: entries.filter((entry) => entry.action === "skip").length,
637
+ backup: entries.filter((entry) => entry.action === "backup").length,
638
+ conflict: entries.filter((entry) => entry.action === "conflict").length
639
+ });
640
+ },
641
+ harnessRoot() {
642
+ return root;
643
+ }
644
+ });
645
+ }
646
+ var Plan;
647
+ ((Plan) => {
648
+ function forInstall(payload, harness, target, opts) {
649
+ const rootResult = payload.harnessRoot(harness);
650
+ if (rootResult.type === "err") {
651
+ return Result.err(PlanRefusal.harnessNotInPayload(harness));
652
+ }
653
+ const entries = buildEntries(rootResult.value, target, opts);
654
+ const { iso, token } = Timestamps.of(new Date(opts.startedAt));
655
+ return Result.ok(createPlan(entries, iso, token, rootResult.value));
656
+ }
657
+ Plan.forInstall = forInstall;
658
+ function forUpgrade(payload, source, harness, target, opts) {
659
+ const rootResult = payload.harnessRoot(harness);
660
+ if (rootResult.type === "err") {
661
+ return Result.err(PlanRefusal.harnessNotInPayload(harness));
662
+ }
663
+ const entries = buildUpgradeEntries(rootResult.value, target, source, opts);
664
+ const { iso, token } = Timestamps.of(new Date(opts.startedAt));
665
+ return Result.ok(createPlan(entries, iso, token, rootResult.value));
666
+ }
667
+ Plan.forUpgrade = forUpgrade;
668
+ })(Plan ||= {});
669
+ Object.freeze(Plan);
670
+ function buildEntries(root, target, opts) {
671
+ const entries = [];
672
+ for (const relPath of walkFiles(root)) {
673
+ const cls = classify(relPath);
674
+ const exists = existsSync(join2(target, relPath));
675
+ const action = classifyAction(exists, opts.force, cls);
676
+ entries.push(Object.freeze({
677
+ path: relPath,
678
+ action,
679
+ class: cls,
680
+ forced: action === "update" || action === "backup",
681
+ md5: md5OfFileSync(join2(root, relPath)),
682
+ required: cls === "owned"
683
+ }));
684
+ }
685
+ return entries;
686
+ }
687
+ function classifyAction(exists, force, cls) {
688
+ if (!exists)
689
+ return "add";
690
+ if (!force)
691
+ return "conflict";
692
+ if (cls === "owned")
693
+ return "update";
694
+ if (cls === "user-preserved")
695
+ return "skip";
696
+ return "backup";
697
+ }
698
+ function buildUpgradeEntries(root, target, source, opts) {
699
+ const entries = [];
700
+ for (const relPath of walkFiles(root)) {
701
+ const cls = classify(relPath);
702
+ const newMd5 = md5OfFileSync(join2(root, relPath));
703
+ if (!existsSync(join2(target, relPath))) {
704
+ entries.push(Object.freeze({ path: relPath, action: "add", class: cls, forced: false, md5: newMd5, required: cls === "owned" }));
705
+ continue;
706
+ }
707
+ const actualMd5 = md5OfFileSync(join2(target, relPath));
708
+ const disposition = source.dispositionFor(relPath, cls, actualMd5);
709
+ const action = toPlanAction(disposition);
710
+ entries.push(Object.freeze({
711
+ path: relPath,
712
+ action,
713
+ class: cls,
714
+ forced: opts.force && disposition.type === "backup-then-copy",
715
+ md5: newMd5,
716
+ required: cls === "owned"
717
+ }));
718
+ }
719
+ return entries;
720
+ }
721
+ function toPlanAction(disposition) {
722
+ switch (disposition.type) {
723
+ case "overwrite":
724
+ return "update";
725
+ case "backup-then-copy":
726
+ return "backup";
727
+ case "preserve":
728
+ return "skip";
729
+ }
730
+ }
731
+ function classify(relPath) {
732
+ const segments = relPath.split("/");
733
+ const basename = segments[segments.length - 1] ?? relPath;
734
+ if (basename.startsWith("amadeus-"))
735
+ return "owned";
736
+ if (segments.includes("memory"))
737
+ return "user-preserved";
738
+ return "shared";
739
+ }
740
+ function walkFiles(root) {
741
+ const results = [];
742
+ function walk(dir) {
743
+ for (const name of readdirSync(dir)) {
744
+ const full = join2(dir, name);
745
+ if (statSync(full).isDirectory()) {
746
+ walk(full);
747
+ } else {
748
+ results.push(relative(root, full).split(sep).join("/"));
749
+ }
750
+ }
751
+ }
752
+ walk(root);
753
+ return results;
754
+ }
755
+ var MD5_CHUNK_SIZE = 64 * 1024;
756
+ function md5OfFileSync(path) {
757
+ const fd = openSync(path, "r");
758
+ const hash = createHash("md5");
759
+ const buffer = Buffer.alloc(MD5_CHUNK_SIZE);
760
+ try {
761
+ for (;; ) {
762
+ const bytesRead = readSync(fd, buffer, 0, MD5_CHUNK_SIZE, null);
763
+ if (bytesRead === 0)
764
+ break;
765
+ hash.update(buffer.subarray(0, bytesRead));
766
+ }
767
+ } finally {
768
+ closeSync(fd);
769
+ }
770
+ return hash.digest("hex");
771
+ }
772
+
773
+ // src/domain/upgrade.ts
774
+ function createUpgradeAssessment(outcome) {
775
+ return Object.freeze({
776
+ outcome() {
777
+ return outcome;
778
+ },
779
+ isActionable() {
780
+ return outcome.type === "proceed";
781
+ }
782
+ });
783
+ }
784
+ var UpgradeAssessment;
785
+ ((UpgradeAssessment) => {
786
+ function of(installed, resolved, spec) {
787
+ if (installed.equals(resolved.semver)) {
788
+ return createUpgradeAssessment({ type: "already-up-to-date", installed });
789
+ }
790
+ if (resolved.semver.isLaterThan(installed)) {
791
+ return createUpgradeAssessment({ type: "proceed", to: resolved });
792
+ }
793
+ if (spec.kind === "latest") {
794
+ return createUpgradeAssessment({ type: "installed-newer-than-latest", installed, latest: resolved.semver });
795
+ }
796
+ return createUpgradeAssessment({ type: "downgrade-unsupported", installed, requested: resolved.semver });
797
+ }
798
+ UpgradeAssessment.of = of;
799
+ })(UpgradeAssessment ||= {});
800
+ Object.freeze(UpgradeAssessment);
801
+ var UpgradeRefusal;
802
+ ((UpgradeRefusal) => {
803
+ function fromOutcome(outcome) {
804
+ return outcome;
805
+ }
806
+ UpgradeRefusal.fromOutcome = fromOutcome;
807
+ function noInstallation() {
808
+ return Object.freeze({ type: "no-installation" });
809
+ }
810
+ UpgradeRefusal.noInstallation = noInstallation;
811
+ function unsupportedLayout(detail) {
812
+ return Object.freeze({ type: "unsupported-layout", detail });
813
+ }
814
+ UpgradeRefusal.unsupportedLayout = unsupportedLayout;
815
+ function partialRefused(missing) {
816
+ return Object.freeze({ type: "partial-refused", missing });
817
+ }
818
+ UpgradeRefusal.partialRefused = partialRefused;
819
+ })(UpgradeRefusal ||= {});
820
+ Object.freeze(UpgradeRefusal);
821
+ var LegacyLayout;
822
+ ((LegacyLayout) => {
823
+ function isUnsupported(evidence) {
824
+ if (evidence.versionFileContent !== null) {
825
+ const parsed = SemVer.parse(evidence.versionFileContent);
826
+ if (parsed.type === "err") {
827
+ return Object.freeze({
828
+ unsupported: true,
829
+ detail: `VERSION file content predates the current versioning convention: ${evidence.versionFileContent.trim()}`
830
+ });
831
+ }
832
+ }
833
+ const hasAmadeusPrefixedPath = evidence.paths.some((path) => {
834
+ const segments = path.split("/");
835
+ const basename = segments[segments.length - 1] ?? path;
836
+ return basename.startsWith("amadeus-");
837
+ });
838
+ if (hasAmadeusPrefixedPath && !evidence.anchors.toolsDir && !evidence.anchors.amadeusCommon) {
839
+ return Object.freeze({
840
+ unsupported: true,
841
+ detail: "amadeus-* files were found without either current layout anchor (tools/ or amadeus-common/) present"
842
+ });
843
+ }
844
+ return Object.freeze({ unsupported: false, detail: "" });
845
+ }
846
+ LegacyLayout.isUnsupported = isUnsupported;
847
+ })(LegacyLayout ||= {});
848
+ Object.freeze(LegacyLayout);
849
+ function manifestedSource(manifest) {
850
+ return Object.freeze({
851
+ kind: "manifested",
852
+ dispositionFor(path, _cls, actualMd5) {
853
+ return manifest.dispositionFor(path, actualMd5);
854
+ },
855
+ assess(resolved, spec) {
856
+ return UpgradeAssessment.of(manifest.distributionVersion, resolved, spec);
857
+ },
858
+ nextManifest(input) {
859
+ return manifest.upgradedTo(input);
860
+ },
861
+ strategyNote() {
862
+ return `Upgrading a manifest-tracked ${manifest.harness} installation from ${manifest.sourceTag}.`;
863
+ }
864
+ });
865
+ }
866
+ function conservativeSource(kind) {
867
+ return Object.freeze({
868
+ kind,
869
+ dispositionFor(_path, cls, _actualMd5) {
870
+ if (cls === "owned")
871
+ return Object.freeze({ type: "overwrite" });
872
+ if (cls === "user-preserved")
873
+ return Object.freeze({ type: "preserve" });
874
+ return Object.freeze({ type: "backup-then-copy" });
875
+ },
876
+ assess(_resolved, _spec) {
877
+ return null;
878
+ },
879
+ nextManifest(input) {
880
+ return Manifest.build(input.payload, input.files, input.meta);
881
+ },
882
+ strategyNote() {
883
+ return kind === "manual-or-unknown" ? "No installer manifest was found; using a conservative strategy that backs up every modified shared file before copying." : "Partial installation forced with --force; proceeding with the same conservative backup strategy.";
884
+ }
885
+ });
886
+ }
887
+ var UpgradeSource;
888
+ ((UpgradeSource) => {
889
+ function fromInstallation(installation, force) {
890
+ if (installation.kind === "none") {
891
+ return Result.err(UpgradeRefusal.noInstallation());
892
+ }
893
+ if (installation.kind === "partial") {
894
+ if (!force)
895
+ return Result.err(UpgradeRefusal.partialRefused(installation.missing));
896
+ return Result.ok(conservativeSource("partial-forced"));
897
+ }
898
+ if (installation.kind === "manual-or-unknown") {
899
+ const verdict = LegacyLayout.isUnsupported(installation.evidence);
900
+ if (verdict.unsupported)
901
+ return Result.err(UpgradeRefusal.unsupportedLayout(verdict.detail));
902
+ return Result.ok(conservativeSource("manual-or-unknown"));
903
+ }
904
+ return Result.ok(manifestedSource(installation.manifest));
905
+ }
906
+ UpgradeSource.fromInstallation = fromInstallation;
907
+ })(UpgradeSource ||= {});
908
+ Object.freeze(UpgradeSource);
909
+
910
+ // src/domain/verify-result.ts
911
+ var VerifyResult;
912
+ ((VerifyResult) => {
913
+ function of(checks) {
914
+ return Object.freeze({
915
+ allPassed() {
916
+ return checks.every((check) => check.ok);
917
+ },
918
+ failures() {
919
+ return checks.filter((check) => !check.ok);
920
+ },
921
+ checks() {
922
+ return checks;
923
+ }
924
+ });
925
+ }
926
+ VerifyResult.of = of;
927
+ })(VerifyResult ||= {});
928
+ Object.freeze(VerifyResult);
929
+ var NextSteps;
930
+ ((NextSteps) => {
931
+ function of(harness, version, target) {
932
+ return Object.freeze({
933
+ harness,
934
+ version,
935
+ target,
936
+ lines() {
937
+ return Object.freeze([
938
+ `Installed ${harness} ${version.tag} to ${target}.`,
939
+ "Next steps:",
940
+ ` 1. cd ${target}`,
941
+ " 2. Open your AI harness and run /amadeus (or the equivalent slash command).",
942
+ " 3. Describe what you want to build — the engine starts your first intent automatically."
943
+ ]);
944
+ }
945
+ });
946
+ }
947
+ NextSteps.of = of;
948
+ })(NextSteps ||= {});
949
+ Object.freeze(NextSteps);
950
+
951
+ // src/modules/applier.ts
952
+ import { dirname, join as join3, resolve, sep as sep2 } from "node:path";
953
+ var Applier;
954
+ ((Applier) => {
955
+ function create(fsWrite) {
956
+ return Object.freeze({
957
+ async apply(plan, target) {
958
+ const targetRoot = resolve(target);
959
+ const applied = [];
960
+ const backupPaths = [];
961
+ const failures = [];
962
+ const madeDirs = new Set;
963
+ for (const entry of plan.entries()) {
964
+ if (entry.action === "skip") {
965
+ applied.push(entry);
966
+ continue;
967
+ }
968
+ const disposition = dispositionFor(entry);
969
+ if (disposition === "skip") {
970
+ applied.push(entry);
971
+ continue;
972
+ }
973
+ const destResult = resolveWithin(targetRoot, entry.path, "copy");
974
+ if (destResult.type === "err") {
975
+ failures.push(destResult.error);
976
+ break;
977
+ }
978
+ const dest = destResult.value;
979
+ const destDir = dirname(dest);
980
+ if (!madeDirs.has(destDir)) {
981
+ const mkdirResult = await fsWrite.mkdir(destDir);
982
+ if (mkdirResult.type === "err") {
983
+ failures.push({ path: entry.path, operation: "mkdir", detail: mkdirResult.error.detail });
984
+ break;
985
+ }
986
+ madeDirs.add(destDir);
987
+ }
988
+ if (disposition === "backup-then-copy" && await fsWrite.exists(dest)) {
989
+ const backupResult = resolveWithin(targetRoot, `${entry.path}.${plan.backupTimestamp}.bk`, "backup");
990
+ if (backupResult.type === "err") {
991
+ failures.push(backupResult.error);
992
+ break;
993
+ }
994
+ const bkPath = backupResult.value;
995
+ if (await fsWrite.exists(bkPath)) {
996
+ failures.push({ path: entry.path, operation: "backup", detail: `backup path already exists: ${entry.path}.${plan.backupTimestamp}.bk` });
997
+ break;
998
+ }
999
+ const backedUp = await fsWrite.backup(dest, bkPath);
1000
+ if (backedUp.type === "err") {
1001
+ failures.push({ path: entry.path, operation: "backup", detail: backedUp.error.detail });
1002
+ break;
1003
+ }
1004
+ backupPaths.push(`${entry.path}.${plan.backupTimestamp}.bk`);
1005
+ }
1006
+ const copied = await fsWrite.copyFile(join3(plan.harnessRoot(), entry.path), dest);
1007
+ if (copied.type === "err") {
1008
+ failures.push({ path: entry.path, operation: "copy", detail: copied.error.detail });
1009
+ break;
1010
+ }
1011
+ applied.push(entry);
1012
+ }
1013
+ return Object.freeze({
1014
+ hasFailures() {
1015
+ return failures.length > 0;
1016
+ },
1017
+ failures() {
1018
+ return failures;
1019
+ },
1020
+ appliedEntries() {
1021
+ return applied;
1022
+ },
1023
+ backupPaths() {
1024
+ return backupPaths;
1025
+ },
1026
+ manifestFiles() {
1027
+ return ManifestFiles.fromEntries(applied.map((entry) => ({ path: entry.path, class: entry.class, required: entry.required, md5: entry.md5 })));
1028
+ }
1029
+ });
1030
+ }
1031
+ });
1032
+ }
1033
+ Applier.create = create;
1034
+ })(Applier ||= {});
1035
+ Object.freeze(Applier);
1036
+ function dispositionFor(entry) {
1037
+ if (entry.action === "add" || entry.action === "update")
1038
+ return "copy";
1039
+ if (entry.action === "backup")
1040
+ return "backup-then-copy";
1041
+ if (entry.class === "owned")
1042
+ return "copy";
1043
+ if (entry.class === "user-preserved")
1044
+ return "skip";
1045
+ return "backup-then-copy";
1046
+ }
1047
+ function resolveWithin(targetRoot, relPath, operation) {
1048
+ const resolved = resolve(targetRoot, relPath);
1049
+ if (resolved !== targetRoot && !resolved.startsWith(`${targetRoot}${sep2}`)) {
1050
+ return Result.err({ path: relPath, operation, detail: `resolved path escapes the install target: ${relPath}` });
1051
+ }
1052
+ return Result.ok(resolved);
1053
+ }
1054
+
1055
+ // src/modules/manifest-io.ts
1056
+ import { join as join4 } from "node:path";
1057
+ var MANIFEST_RELATIVE_PATH = join4("amadeus", ".installer", "amadeus-setup-manifest.json");
1058
+ function createManifestIo(fsRead, fsWrite) {
1059
+ return Object.freeze({
1060
+ async read(targetDir) {
1061
+ const path = join4(targetDir, MANIFEST_RELATIVE_PATH);
1062
+ const contents = await fsRead.readText(path);
1063
+ if (contents.type === "err") {
1064
+ if (contents.error.notFound)
1065
+ return Result.ok(null);
1066
+ return Result.err(ManifestError.io(contents.error.detail));
1067
+ }
1068
+ let json;
1069
+ try {
1070
+ json = JSON.parse(contents.value);
1071
+ } catch (cause) {
1072
+ return Result.err(ManifestError.malformed(`manifest is not valid JSON: ${String(cause)}`));
1073
+ }
1074
+ return Manifest.parse(json);
1075
+ },
1076
+ async write(targetDir, manifest) {
1077
+ const path = join4(targetDir, MANIFEST_RELATIVE_PATH);
1078
+ const written = await fsWrite.writeText(path, JSON.stringify(manifest.toJSON(), null, 2));
1079
+ if (written.type === "err")
1080
+ return Result.err(ManifestError.io(written.error.detail));
1081
+ return Result.ok(undefined);
1082
+ }
1083
+ });
1084
+ }
1085
+
1086
+ // src/modules/reporter.ts
1087
+ function renderHelp() {
1088
+ return [
1089
+ "amadeus-setup",
1090
+ "",
1091
+ "Usage:",
1092
+ " amadeus-setup install [--harness <claude|codex|kiro|kiro-ide>] [--target <path>] [--version <semver|tag>] [--yes] [--force]",
1093
+ " amadeus-setup upgrade [--harness <claude|codex|kiro|kiro-ide>] [--target <path>] [--version <semver|tag>] [--yes] [--force]",
1094
+ " amadeus-setup # this help; install/upgrade are never run implicitly"
1095
+ ].join(`
1096
+ `);
1097
+ }
1098
+ var ACTION_LABELS = {
1099
+ add: "Add",
1100
+ update: "Update",
1101
+ skip: "Skip",
1102
+ backup: "Backup then copy",
1103
+ conflict: "Conflict (needs confirmation)"
1104
+ };
1105
+ function renderPlanReport(plan, note) {
1106
+ const lines = [];
1107
+ if (note !== undefined)
1108
+ lines.push(note, "");
1109
+ lines.push("Plan:");
1110
+ const actionsInReportOrder = ["add", "update", "backup", "conflict", "skip"];
1111
+ for (const action of actionsInReportOrder) {
1112
+ const entries = plan.entriesBy(action);
1113
+ if (entries.length === 0)
1114
+ continue;
1115
+ lines.push(` ${ACTION_LABELS[action]}:`);
1116
+ for (const entry of entries) {
1117
+ lines.push(` ${entry.path}${entry.forced ? " (forced)" : ""}`);
1118
+ }
1119
+ }
1120
+ const summary = plan.summary();
1121
+ lines.push(`Summary: add=${summary.add} update=${summary.update} backup=${summary.backup} conflict=${summary.conflict} skip=${summary.skip}`);
1122
+ return lines.join(`
1123
+ `);
1124
+ }
1125
+ function renderAlreadyInstalled(admission) {
1126
+ if (admission.type !== "refuse-suggest-upgrade") {
1127
+ return "Amadeus is already installed here.";
1128
+ }
1129
+ return [
1130
+ `Amadeus is already installed here (${admission.detected}).`,
1131
+ "Run `amadeus-setup upgrade` instead, or pass --force to reinstall."
1132
+ ].join(`
1133
+ `);
1134
+ }
1135
+ function renderApplyFailure(applied) {
1136
+ const lines = ["Install failed while applying files:"];
1137
+ for (const failure of applied.failures()) {
1138
+ lines.push(` [${failure.operation}] ${failure.path}: ${failure.detail}`);
1139
+ }
1140
+ lines.push("No manifest was written; re-run once the issue above is resolved.");
1141
+ return lines.join(`
1142
+ `);
1143
+ }
1144
+ function renderVerifyFailure(verify) {
1145
+ const lines = ["Verification failed after install:"];
1146
+ for (const check of verify.failures()) {
1147
+ lines.push(` [${check.name}] ${check.detail}`);
1148
+ }
1149
+ return lines.join(`
1150
+ `);
1151
+ }
1152
+ function renderSuccess(_applied, verify, next) {
1153
+ const lines = ["Install complete.", "", "Verification:"];
1154
+ for (const check of verify.checks()) {
1155
+ lines.push(` [${check.ok ? "ok" : "FAIL"}] ${check.name}: ${check.detail}`);
1156
+ }
1157
+ lines.push("", ...next.lines());
1158
+ return lines.join(`
1159
+ `);
1160
+ }
1161
+ function renderWizardAborted() {
1162
+ return "Install aborted: selection was not confirmed. No files were changed.";
1163
+ }
1164
+ function renderTmpDirFailure(detail) {
1165
+ return `could not prepare a temp directory: ${detail}`;
1166
+ }
1167
+ function renderError(err) {
1168
+ if (isFetchErrorLike(err)) {
1169
+ return [`Network error (${err.type}): ${err.detail}`, err.guidance()].join(`
1170
+ `);
1171
+ }
1172
+ switch (err.type) {
1173
+ case "unknown-subcommand":
1174
+ return `Unknown command: "${err.raw}". Run \`amadeus-setup\` with no arguments for usage.`;
1175
+ case "unknown-flag":
1176
+ return `Unknown option: ${err.raw}. Run \`amadeus-setup\` with no arguments for usage.`;
1177
+ case "invalid-harness":
1178
+ return `Invalid --harness value: "${err.raw}". Expected one of claude, codex, kiro, kiro-ide.`;
1179
+ case "multiple-harnesses":
1180
+ return `Only one --harness is supported per run (got: ${err.raws.join(", ")}). Run once per harness.`;
1181
+ case "missing-required":
1182
+ return `Missing required option(s) in non-interactive mode: ${err.fields.join(", ")}.`;
1183
+ case "invalid-version":
1184
+ return `Invalid --version value: "${err.cause.raw}" (${err.cause.reason}).`;
1185
+ case "no-stable-version":
1186
+ return `Could not resolve a version to install: ${err.detail}`;
1187
+ case "not-found":
1188
+ return `Requested version not found: ${err.requested}`;
1189
+ case "schema-unsupported":
1190
+ return "The existing installer manifest uses an unsupported schema version.";
1191
+ case "malformed":
1192
+ return `The existing installer manifest is malformed: ${err.detail}`;
1193
+ case "unknown-harness":
1194
+ return `The existing installer manifest references an unknown harness: "${err.raw}".`;
1195
+ case "duplicate-path":
1196
+ return `Internal error: duplicate manifest path "${err.path}".`;
1197
+ case "io":
1198
+ return `I/O error: ${err.detail}`;
1199
+ case "already-installed":
1200
+ return renderAlreadyInstalled(err.admission);
1201
+ case "harness-not-in-payload":
1202
+ return `The requested harness "${err.harness}" is not present in this distribution.`;
1203
+ case "no-installation":
1204
+ return "No Amadeus installation was found here. Run `amadeus-setup install` instead.";
1205
+ case "unsupported-layout":
1206
+ return `This installation cannot be upgraded: ${err.detail}. No files were changed.`;
1207
+ case "partial-refused":
1208
+ return `A partial Amadeus installation was found (missing: ${err.missing.join(", ")}). Re-run with --force to proceed conservatively, or restore the missing files first.`;
1209
+ case "already-up-to-date":
1210
+ return `Already up to date: the installed version (${err.installed.format()}) matches the requested version.`;
1211
+ case "downgrade-unsupported":
1212
+ return `Cannot downgrade: requested ${err.requested.format()} is older than the installed version ${err.installed.format()}.`;
1213
+ case "installed-newer-than-latest":
1214
+ return `The installed version (${err.installed.format()}) is newer than the latest resolved version (${err.latest.format()}). Pass --version to target a specific release.`;
1215
+ default:
1216
+ return "An unexpected error occurred.";
1217
+ }
1218
+ }
1219
+ function isFetchErrorLike(err) {
1220
+ return typeof err.guidance === "function";
1221
+ }
1222
+
1223
+ // src/modules/fetcher.ts
1224
+ import { join as join7 } from "node:path";
1225
+
1226
+ // src/internal/tar-archive-extractor.ts
1227
+ import { createGunzip } from "node:zlib";
1228
+ import { createReadStream } from "node:fs";
1229
+ import { join as join6, sep as sep3 } from "node:path";
1230
+
1231
+ // src/internal/payload-factory.ts
1232
+ import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
1233
+ import { join as join5 } from "node:path";
1234
+
1235
+ // src/internal/fetch-error-factory.ts
1236
+ var GUIDANCE = {
1237
+ dns: "Check your network connection and try again.",
1238
+ conn: "Check your network connection and try again.",
1239
+ timeout: "The request timed out; try again on a more stable connection.",
1240
+ http: "The GitHub archive could not be retrieved; verify the requested version exists.",
1241
+ "rate-limit": "GitHub API rate limit reached; wait a while before retrying.",
1242
+ "payload-invalid": "The downloaded archive is not a valid Amadeus distribution."
1243
+ };
1244
+ function createFetchError(type, detail, status = null) {
1245
+ return Object.freeze({
1246
+ type,
1247
+ detail,
1248
+ status,
1249
+ isTransient() {
1250
+ switch (type) {
1251
+ case "dns":
1252
+ case "conn":
1253
+ case "timeout":
1254
+ return true;
1255
+ case "http":
1256
+ return status !== null && status >= 500;
1257
+ case "rate-limit":
1258
+ case "payload-invalid":
1259
+ return false;
1260
+ default: {
1261
+ const exhaustive = type;
1262
+ throw new Error(`unreachable FetchErrorType: ${exhaustive}`);
1263
+ }
1264
+ }
1265
+ },
1266
+ guidance() {
1267
+ return GUIDANCE[type];
1268
+ }
1269
+ });
1270
+ }
1271
+
1272
+ // src/internal/payload-factory.ts
1273
+ function resolveWrapperDir(extractedDir) {
1274
+ let entries;
1275
+ try {
1276
+ entries = readdirSync2(extractedDir);
1277
+ } catch (cause) {
1278
+ return Result.err(createFetchError("payload-invalid", `could not read extracted archive at ${extractedDir}: ${String(cause)}`));
1279
+ }
1280
+ const dirs = entries.filter((name) => statSync2(join5(extractedDir, name)).isDirectory());
1281
+ if (dirs.length !== 1) {
1282
+ return Result.err(createFetchError("payload-invalid", `expected exactly one top-level directory in the extracted archive, found ${dirs.length}`));
1283
+ }
1284
+ return Result.ok(join5(extractedDir, dirs[0]));
1285
+ }
1286
+ function createExtractedPayload(extractedDir, version, harnessNames) {
1287
+ const wrapper = resolveWrapperDir(extractedDir);
1288
+ if (wrapper.type === "err")
1289
+ return wrapper;
1290
+ const distDir = join5(wrapper.value, "dist");
1291
+ let distEntries;
1292
+ try {
1293
+ distEntries = readdirSync2(distDir);
1294
+ } catch {
1295
+ return Result.err(createFetchError("payload-invalid", `missing dist/ directory in extracted archive (expected ${distDir})`));
1296
+ }
1297
+ const available = harnessNames.filter((name) => distEntries.includes(name) && statSync2(join5(distDir, name)).isDirectory());
1298
+ return Result.ok(Object.freeze({
1299
+ version,
1300
+ harnessRoot(harness) {
1301
+ if (!available.includes(harness)) {
1302
+ return Result.err(createFetchError("payload-invalid", `harness "${harness}" is not present in this distribution`));
1303
+ }
1304
+ return Result.ok(join5(distDir, harness));
1305
+ },
1306
+ availableHarnesses() {
1307
+ return available;
1308
+ }
1309
+ }));
1310
+ }
1311
+
1312
+ // src/domain/payload.ts
1313
+ var FetchError;
1314
+ ((FetchError) => {
1315
+ function classify2(cause, meta) {
1316
+ const type = classifyType(cause, meta);
1317
+ return createFetchError(type, describeCause(cause, meta), meta?.status ?? null);
1318
+ }
1319
+ FetchError.classify = classify2;
1320
+ function payloadInvalid(detail) {
1321
+ return createFetchError("payload-invalid", detail, null);
1322
+ }
1323
+ FetchError.payloadInvalid = payloadInvalid;
1324
+ })(FetchError ||= {});
1325
+ Object.freeze(FetchError);
1326
+ function classifyType(cause, meta) {
1327
+ if (meta?.status === 403 || meta?.status === 429)
1328
+ return "rate-limit";
1329
+ if (meta && meta.status !== null && meta.status >= 400)
1330
+ return "http";
1331
+ if (isTimeoutError(cause))
1332
+ return "timeout";
1333
+ if (isDnsError(cause))
1334
+ return "dns";
1335
+ return "conn";
1336
+ }
1337
+ function isTimeoutError(cause) {
1338
+ return cause instanceof Error && (cause.name === "TimeoutError" || cause.name === "AbortError");
1339
+ }
1340
+ function isDnsError(cause) {
1341
+ return cause instanceof Error && "code" in cause && cause.code === "ENOTFOUND";
1342
+ }
1343
+ function describeCause(cause, meta) {
1344
+ if (meta)
1345
+ return `${meta.url} responded with status ${meta.status ?? "unknown"}`;
1346
+ if (cause instanceof Error)
1347
+ return cause.message;
1348
+ return String(cause);
1349
+ }
1350
+ var ExtractedPayload;
1351
+ ((ExtractedPayload) => {
1352
+ function locate(extractedDir, version) {
1353
+ return createExtractedPayload(extractedDir, version, HarnessName.all);
1354
+ }
1355
+ ExtractedPayload.locate = locate;
1356
+ })(ExtractedPayload ||= {});
1357
+ Object.freeze(ExtractedPayload);
1358
+
1359
+ // src/internal/tar-archive-extractor.ts
1360
+ var EXTRACT_RELATIVE_DIR = "extracted";
1361
+ var BLOCK_SIZE = 512;
1362
+ async function extractTarGz(archivePath, extractDir, tmpWrite) {
1363
+ const gunzip = createReadStream(archivePath).pipe(createGunzip());
1364
+ let carry = Buffer.alloc(0);
1365
+ let pendingLongName = null;
1366
+ let current = null;
1367
+ try {
1368
+ for await (const chunk of gunzip) {
1369
+ const bufferChunk = Buffer.from(chunk);
1370
+ carry = carry.length > 0 ? Buffer.concat([carry, bufferChunk]) : bufferChunk;
1371
+ const result2 = await drain(false);
1372
+ if (result2)
1373
+ return result2;
1374
+ }
1375
+ const result = await drain(true);
1376
+ if (result)
1377
+ return result;
1378
+ } catch (cause) {
1379
+ return Result.err(FetchError.payloadInvalid(`archive is not valid gzip/tar: ${String(cause)}`));
1380
+ }
1381
+ return Result.ok(undefined);
1382
+ async function drain(final) {
1383
+ for (;; ) {
1384
+ if (current) {
1385
+ const take = Math.min(current.remaining, carry.length);
1386
+ if (take > 0) {
1387
+ current.chunks.push(carry.subarray(0, take));
1388
+ carry = carry.subarray(take);
1389
+ current.remaining -= take;
1390
+ }
1391
+ if (current.remaining > 0) {
1392
+ if (final)
1393
+ return Result.err(FetchError.payloadInvalid("truncated tar entry"));
1394
+ return null;
1395
+ }
1396
+ const dataLen = current.chunks.reduce((n, b) => n + b.length, 0);
1397
+ const pad = paddingFor(dataLen);
1398
+ if (carry.length < pad) {
1399
+ if (final)
1400
+ return Result.err(FetchError.payloadInvalid("truncated tar padding"));
1401
+ return null;
1402
+ }
1403
+ carry = carry.subarray(pad);
1404
+ const written = await tmpWrite.writeFile(current.path, Buffer.concat(current.chunks));
1405
+ if (written.type === "err") {
1406
+ return Result.err(FetchError.payloadInvalid(`could not write extracted file: ${written.error.detail}`));
1407
+ }
1408
+ current = null;
1409
+ continue;
1410
+ }
1411
+ if (carry.length < BLOCK_SIZE) {
1412
+ if (final && carry.length > 0)
1413
+ return Result.err(FetchError.payloadInvalid("truncated tar header"));
1414
+ return null;
1415
+ }
1416
+ const block = carry.subarray(0, BLOCK_SIZE);
1417
+ carry = carry.subarray(BLOCK_SIZE);
1418
+ if (isZeroBlock(block))
1419
+ continue;
1420
+ const header = parseHeader(block);
1421
+ if (!header)
1422
+ return Result.err(FetchError.payloadInvalid("malformed tar header"));
1423
+ if (header.typeflag === "x" || header.typeflag === "g") {
1424
+ const pad = paddingFor(header.size);
1425
+ if (carry.length < header.size + pad) {
1426
+ if (final)
1427
+ return Result.err(FetchError.payloadInvalid("truncated PAX header"));
1428
+ return null;
1429
+ }
1430
+ const paxData = carry.subarray(0, header.size);
1431
+ carry = carry.subarray(header.size + pad);
1432
+ if (header.typeflag === "x")
1433
+ pendingLongName = parsePaxPath(paxData) ?? pendingLongName;
1434
+ continue;
1435
+ }
1436
+ if (header.typeflag === "L") {
1437
+ const pad = paddingFor(header.size);
1438
+ if (carry.length < header.size + pad) {
1439
+ if (final)
1440
+ return Result.err(FetchError.payloadInvalid("truncated GNU long-name header"));
1441
+ return null;
1442
+ }
1443
+ pendingLongName = carry.subarray(0, header.size).toString("utf8").replace(/\0+$/, "");
1444
+ carry = carry.subarray(header.size + pad);
1445
+ continue;
1446
+ }
1447
+ const rawName = pendingLongName ?? header.name;
1448
+ pendingLongName = null;
1449
+ if (header.typeflag === "5" || rawName.endsWith("/")) {
1450
+ const validated2 = validateEntry(rawName, extractDir);
1451
+ if (validated2.type === "err")
1452
+ return validated2;
1453
+ const dirResult = await tmpWrite.mkdir(validated2.value);
1454
+ if (dirResult.type === "err") {
1455
+ return Result.err(FetchError.payloadInvalid(`could not create directory: ${dirResult.error.detail}`));
1456
+ }
1457
+ continue;
1458
+ }
1459
+ if (header.typeflag !== "0" && header.typeflag !== "\x00") {
1460
+ return Result.err(FetchError.payloadInvalid(`unsupported tar entry type "${header.typeflag}" for ${rawName}`));
1461
+ }
1462
+ const validated = validateEntry(rawName, extractDir);
1463
+ if (validated.type === "err")
1464
+ return validated;
1465
+ if (header.size === 0) {
1466
+ const written = await tmpWrite.writeFile(validated.value, Buffer.alloc(0));
1467
+ if (written.type === "err") {
1468
+ return Result.err(FetchError.payloadInvalid(`could not write extracted file: ${written.error.detail}`));
1469
+ }
1470
+ continue;
1471
+ }
1472
+ current = { path: validated.value, remaining: header.size, chunks: [] };
1473
+ }
1474
+ }
1475
+ }
1476
+ function validateEntry(rawName, extractDir) {
1477
+ if (rawName.length === 0) {
1478
+ return Result.err(FetchError.payloadInvalid("tar entry has an empty name"));
1479
+ }
1480
+ if (isAbsolutePath(rawName)) {
1481
+ return Result.err(FetchError.payloadInvalid(`tar entry has an absolute path: ${rawName}`));
1482
+ }
1483
+ const relative2 = rawName.replace(/\/+$/, "");
1484
+ const resolved = join6(extractDir, relative2);
1485
+ if (resolved !== extractDir && !resolved.startsWith(`${extractDir}${sep3}`)) {
1486
+ return Result.err(FetchError.payloadInvalid(`tar entry escapes the extraction root: ${rawName}`));
1487
+ }
1488
+ return Result.ok(join6(EXTRACT_RELATIVE_DIR, relative2));
1489
+ }
1490
+ function isAbsolutePath(name) {
1491
+ return name.startsWith("/") || name.startsWith("\\") || /^[a-zA-Z]:[\\/]/.test(name);
1492
+ }
1493
+ function parseHeader(block) {
1494
+ const name = readCString(block, 0, 100);
1495
+ const sizeRaw = readCString(block, 124, 12);
1496
+ const typeflagByte = block[156] ?? 0;
1497
+ const prefix = readCString(block, 345, 155);
1498
+ const size = parseOctal(sizeRaw);
1499
+ if (size === null)
1500
+ return null;
1501
+ const fullName = prefix ? `${prefix}/${name}` : name;
1502
+ const typeflag = typeflagByte === 0 ? "\x00" : String.fromCharCode(typeflagByte);
1503
+ return { name: fullName, size, typeflag };
1504
+ }
1505
+ function readCString(block, offset, length) {
1506
+ const slice = block.subarray(offset, offset + length);
1507
+ const zeroIdx = slice.indexOf(0);
1508
+ const trimmed = zeroIdx === -1 ? slice : slice.subarray(0, zeroIdx);
1509
+ return trimmed.toString("utf8");
1510
+ }
1511
+ function parseOctal(raw) {
1512
+ const trimmed = raw.trim();
1513
+ if (trimmed.length === 0)
1514
+ return 0;
1515
+ if (!/^[0-7]+$/.test(trimmed))
1516
+ return null;
1517
+ return Number.parseInt(trimmed, 8);
1518
+ }
1519
+ function isZeroBlock(block) {
1520
+ return block.every((byte) => byte === 0);
1521
+ }
1522
+ function paddingFor(dataLen) {
1523
+ const remainder = dataLen % BLOCK_SIZE;
1524
+ return remainder === 0 ? 0 : BLOCK_SIZE - remainder;
1525
+ }
1526
+ function parsePaxPath(data) {
1527
+ let offset = 0;
1528
+ while (offset < data.length) {
1529
+ const spaceIdx = data.indexOf(32, offset);
1530
+ if (spaceIdx === -1)
1531
+ break;
1532
+ const lenStr = data.subarray(offset, spaceIdx).toString("utf8");
1533
+ const len = Number.parseInt(lenStr, 10);
1534
+ if (!Number.isFinite(len) || len <= 0)
1535
+ break;
1536
+ const record = data.subarray(offset, offset + len).toString("utf8");
1537
+ const eq = record.indexOf("=");
1538
+ if (eq !== -1) {
1539
+ const key = record.slice(lenStr.length + 1, eq);
1540
+ if (key === "path")
1541
+ return record.slice(eq + 1).replace(/\n$/, "");
1542
+ }
1543
+ offset += len;
1544
+ }
1545
+ return null;
1546
+ }
1547
+
1548
+ // src/modules/fetcher.ts
1549
+ var ARCHIVE_RELATIVE_PATH = "archive.tar.gz";
1550
+ function createFetcher(http, tmpWrite) {
1551
+ async function downloadOnce(url) {
1552
+ const response = await http.downloadArchive(url);
1553
+ if (response.type === "err")
1554
+ return response;
1555
+ const written = await tmpWrite.writeStream(ARCHIVE_RELATIVE_PATH, response.value);
1556
+ if (written.type === "err") {
1557
+ return Result.err(FetchError.payloadInvalid(`could not save downloaded archive: ${written.error.detail}`));
1558
+ }
1559
+ return Result.ok(undefined);
1560
+ }
1561
+ return Object.freeze({
1562
+ async fetchArchive(version) {
1563
+ const url = version.archiveUrl();
1564
+ let downloaded = await downloadOnce(url);
1565
+ if (downloaded.type === "err") {
1566
+ if (!downloaded.error.isTransient())
1567
+ return downloaded;
1568
+ downloaded = await downloadOnce(url);
1569
+ if (downloaded.type === "err")
1570
+ return downloaded;
1571
+ }
1572
+ const archivePath = join7(tmpWrite.root, ARCHIVE_RELATIVE_PATH);
1573
+ const extractDir = join7(tmpWrite.root, EXTRACT_RELATIVE_DIR);
1574
+ const extraction = await extractTarGz(archivePath, extractDir, tmpWrite);
1575
+ if (extraction.type === "err")
1576
+ return extraction;
1577
+ return ExtractedPayload.locate(extractDir, version);
1578
+ }
1579
+ });
1580
+ }
1581
+
1582
+ // src/internal/resolved-version-factory.ts
1583
+ var CODELOAD_BASE = "https://codeload.github.com/amadeus-dlc/amadeus/tar.gz/refs/tags";
1584
+ function createResolvedVersion(semver, source) {
1585
+ const tag = semver.format();
1586
+ return Object.freeze({
1587
+ tag,
1588
+ semver,
1589
+ source,
1590
+ archiveUrl() {
1591
+ return new URL(`${CODELOAD_BASE}/${tag}`);
1592
+ },
1593
+ isSameAs(other) {
1594
+ return semver.equals(other);
1595
+ }
1596
+ });
1597
+ }
1598
+
1599
+ // src/domain/resolved-version.ts
1600
+ var ResolvedVersion;
1601
+ ((ResolvedVersion) => {
1602
+ function fromRelease(semver) {
1603
+ return createResolvedVersion(semver, "release");
1604
+ }
1605
+ ResolvedVersion.fromRelease = fromRelease;
1606
+ function fromTag(semver) {
1607
+ return createResolvedVersion(semver, "tag");
1608
+ }
1609
+ ResolvedVersion.fromTag = fromTag;
1610
+ })(ResolvedVersion ||= {});
1611
+ Object.freeze(ResolvedVersion);
1612
+ var ResolveError;
1613
+ ((ResolveError) => {
1614
+ function noStableVersion(detail = "no stable release or tag was found") {
1615
+ return Object.freeze({ type: "no-stable-version", detail });
1616
+ }
1617
+ ResolveError.noStableVersion = noStableVersion;
1618
+ function notFound(spec) {
1619
+ return Object.freeze({ type: "not-found", requested: spec.describe() });
1620
+ }
1621
+ ResolveError.notFound = notFound;
1622
+ })(ResolveError ||= {});
1623
+ Object.freeze(ResolveError);
1624
+
1625
+ // src/modules/resolver.ts
1626
+ var RELEASES_PATH = "/repos/amadeus-dlc/amadeus/releases";
1627
+ var TAGS_PATH = "/repos/amadeus-dlc/amadeus/tags";
1628
+ function createResolver(http) {
1629
+ async function fetchNames(apiPath, nameField, includeEntry = () => true) {
1630
+ const response = await http.getJson(apiPath);
1631
+ if (response.type === "err")
1632
+ return response;
1633
+ if (!Array.isArray(response.value)) {
1634
+ return Result.err(FetchError.payloadInvalid(`GitHub API response at ${apiPath} was not an array`));
1635
+ }
1636
+ const names = response.value.filter(includeEntry).map((entry) => entry[nameField]).filter((name) => typeof name === "string");
1637
+ return Result.ok(names);
1638
+ }
1639
+ function fetchTagNames() {
1640
+ return fetchNames(TAGS_PATH, "name");
1641
+ }
1642
+ function fetchReleaseTagNames() {
1643
+ return fetchNames(RELEASES_PATH, "tag_name", (entry) => entry.draft !== true && entry.prerelease !== true);
1644
+ }
1645
+ function parseAllStable(names) {
1646
+ const parsed = [];
1647
+ for (const name of names) {
1648
+ const result = SemVer.parse(name);
1649
+ if (result.type === "ok")
1650
+ parsed.push(result.value);
1651
+ }
1652
+ return parsed;
1653
+ }
1654
+ return Object.freeze({
1655
+ async resolveVersion(spec) {
1656
+ if (spec.kind === "exact") {
1657
+ const tagNames2 = await fetchTagNames();
1658
+ if (tagNames2.type === "err")
1659
+ return tagNames2;
1660
+ const hit = parseAllStable(tagNames2.value).find((candidate) => spec.admits(candidate));
1661
+ return hit ? Result.ok(ResolvedVersion.fromTag(hit)) : Result.err(ResolveError.notFound(spec));
1662
+ }
1663
+ const releaseTagNames = await fetchReleaseTagNames();
1664
+ if (releaseTagNames.type === "err")
1665
+ return releaseTagNames;
1666
+ const bestRelease = SemVer.latestStableOf(parseAllStable(releaseTagNames.value));
1667
+ if (bestRelease)
1668
+ return Result.ok(ResolvedVersion.fromRelease(bestRelease));
1669
+ const tagNames = await fetchTagNames();
1670
+ if (tagNames.type === "err")
1671
+ return tagNames;
1672
+ const bestTag = SemVer.latestStableOf(parseAllStable(tagNames.value));
1673
+ if (bestTag)
1674
+ return Result.ok(ResolvedVersion.fromTag(bestTag));
1675
+ return Result.err(ResolveError.noStableVersion());
1676
+ }
1677
+ });
1678
+ }
1679
+
1680
+ // src/modules/verifier.ts
1681
+ import { join as join8 } from "node:path";
1682
+ var Verifier;
1683
+ ((Verifier) => {
1684
+ function create(fsRead) {
1685
+ return Object.freeze({
1686
+ async verify(target, manifest) {
1687
+ const checks = [];
1688
+ const requiredPaths = manifest.requiredPaths();
1689
+ const missing = [];
1690
+ for (const path of requiredPaths) {
1691
+ if (!await fsRead.fileExists(join8(target, path)))
1692
+ missing.push(path);
1693
+ }
1694
+ checks.push({
1695
+ name: "required-files",
1696
+ ok: missing.length === 0,
1697
+ detail: missing.length === 0 ? `all ${requiredPaths.length} required files are present` : `missing required files: ${missing.join(", ")}`
1698
+ });
1699
+ const engineDir = engineDirNameFor(manifest.harness);
1700
+ const harnessDirOk = await fsRead.dirExists(join8(target, engineDir));
1701
+ checks.push({
1702
+ name: "harness-dir",
1703
+ ok: harnessDirOk,
1704
+ detail: harnessDirOk ? `${engineDir} exists` : `${engineDir} is missing`
1705
+ });
1706
+ const toolsDirOk = await fsRead.dirExists(join8(target, engineDir, "tools"));
1707
+ checks.push({
1708
+ name: "tools-dir",
1709
+ ok: toolsDirOk,
1710
+ detail: toolsDirOk ? `${engineDir}/tools exists` : `${engineDir}/tools is missing`
1711
+ });
1712
+ const memoryDirOk = await fsRead.dirExists(join8(target, "amadeus", "spaces", "default", "memory"));
1713
+ checks.push({
1714
+ name: "memory-shell",
1715
+ ok: memoryDirOk,
1716
+ detail: memoryDirOk ? "amadeus/spaces/default/memory exists" : "amadeus/spaces/default/memory is missing"
1717
+ });
1718
+ const activeIntentExists = await fsRead.fileExists(join8(target, "amadeus", "spaces", "default", "intents", "active-intent"));
1719
+ checks.push({
1720
+ name: "state-absence",
1721
+ ok: true,
1722
+ detail: activeIntentExists ? "an existing intent state was found and left untouched" : "no active intent state found (expected for a fresh install)"
1723
+ });
1724
+ return VerifyResult.of(checks);
1725
+ }
1726
+ });
1727
+ }
1728
+ Verifier.create = create;
1729
+ })(Verifier ||= {});
1730
+ Object.freeze(Verifier);
1731
+
1732
+ // src/modules/wizard.ts
1733
+ async function runWizard(parsed, missing, tty) {
1734
+ let harness = parsed.harness;
1735
+ if (missing.includes("harness")) {
1736
+ const selected = await tty.select("Select a harness to install", HarnessName.all);
1737
+ const parsedHarness = HarnessName.parse(selected);
1738
+ harness = parsedHarness.type === "ok" ? parsedHarness.value : parsed.harness;
1739
+ }
1740
+ let target = parsed.target;
1741
+ if (missing.includes("target")) {
1742
+ target = await tty.input("Install target directory", process.cwd());
1743
+ }
1744
+ if (harness === null || target === null) {
1745
+ return Result.err("wizard-aborted");
1746
+ }
1747
+ const confirmed = await tty.confirm(summaryPrompt(parsed.subcommand, harness, target));
1748
+ if (!confirmed) {
1749
+ return Result.err("wizard-aborted");
1750
+ }
1751
+ return Result.ok(Object.freeze({ harness, target }));
1752
+ }
1753
+ function summaryPrompt(subcommand, harness, target) {
1754
+ if (subcommand === "upgrade")
1755
+ return `Upgrade ${harness} in ${target}?`;
1756
+ return `Install ${harness} into ${target}?`;
1757
+ }
1758
+
1759
+ // src/ports/apply-write.ts
1760
+ import { mkdir as mkdirAsync, copyFile as copyFileAsync, rename as renameAsync, stat as stat2 } from "node:fs/promises";
1761
+ import { dirname as dirname3 } from "node:path";
1762
+
1763
+ // src/ports/fsops.ts
1764
+ import { createWriteStream } from "node:fs";
1765
+ import { mkdir, mkdtemp, readFile as readFile2, rm, writeFile } from "node:fs/promises";
1766
+ import { tmpdir } from "node:os";
1767
+ import { dirname as dirname2, join as join9, resolve as resolve2 } from "node:path";
1768
+ import { Readable } from "node:stream";
1769
+ import { pipeline } from "node:stream/promises";
1770
+ var IoError;
1771
+ ((IoError) => {
1772
+ function of(detail, notFound = false) {
1773
+ return Object.freeze({ type: "io", detail, notFound });
1774
+ }
1775
+ IoError.of = of;
1776
+ })(IoError ||= {});
1777
+ Object.freeze(IoError);
1778
+ function createFsRead() {
1779
+ return Object.freeze({
1780
+ async readText(path) {
1781
+ try {
1782
+ return Result.ok(await readFile2(path, "utf8"));
1783
+ } catch (cause) {
1784
+ return Result.err(IoError.of(`could not read ${path}: ${String(cause)}`, isEnoent(cause)));
1785
+ }
1786
+ }
1787
+ });
1788
+ }
1789
+ function isEnoent(cause) {
1790
+ return cause instanceof Error && "code" in cause && cause.code === "ENOENT";
1791
+ }
1792
+ function createFsWrite() {
1793
+ return Object.freeze({
1794
+ async writeText(path, content) {
1795
+ try {
1796
+ await mkdir(dirname2(path), { recursive: true });
1797
+ await writeFile(path, content, "utf8");
1798
+ return Result.ok(undefined);
1799
+ } catch (cause) {
1800
+ return Result.err(IoError.of(`could not write ${path}: ${String(cause)}`));
1801
+ }
1802
+ }
1803
+ });
1804
+ }
1805
+ async function createTmpWrite(prefix) {
1806
+ let root;
1807
+ try {
1808
+ root = await mkdtemp(join9(tmpdir(), prefix));
1809
+ } catch (cause) {
1810
+ return Result.err(IoError.of(`could not create temp directory: ${String(cause)}`));
1811
+ }
1812
+ function resolveUnderRoot(relPath) {
1813
+ const target = resolve2(root, relPath);
1814
+ if (target !== root && !target.startsWith(`${root}/`)) {
1815
+ throw new Error(`path escapes temp root: ${relPath}`);
1816
+ }
1817
+ return target;
1818
+ }
1819
+ return Result.ok(Object.freeze({
1820
+ root,
1821
+ async writeFile(relPath, data) {
1822
+ try {
1823
+ const target = resolveUnderRoot(relPath);
1824
+ await mkdir(dirname2(target), { recursive: true });
1825
+ await writeFile(target, data);
1826
+ return Result.ok(undefined);
1827
+ } catch (cause) {
1828
+ return Result.err(IoError.of(`could not write ${relPath} under temp root: ${String(cause)}`));
1829
+ }
1830
+ },
1831
+ async writeStream(relPath, stream) {
1832
+ try {
1833
+ const target = resolveUnderRoot(relPath);
1834
+ await mkdir(dirname2(target), { recursive: true });
1835
+ await pipeline(Readable.fromWeb(stream), createWriteStream(target));
1836
+ return Result.ok(undefined);
1837
+ } catch (cause) {
1838
+ return Result.err(IoError.of(`could not stream ${relPath} under temp root: ${String(cause)}`));
1839
+ }
1840
+ },
1841
+ async mkdir(relPath) {
1842
+ try {
1843
+ await mkdir(resolveUnderRoot(relPath), { recursive: true });
1844
+ return Result.ok(undefined);
1845
+ } catch (cause) {
1846
+ return Result.err(IoError.of(`could not create directory ${relPath} under temp root: ${String(cause)}`));
1847
+ }
1848
+ },
1849
+ async remove() {
1850
+ await rm(root, { recursive: true, force: true });
1851
+ }
1852
+ }));
1853
+ }
1854
+
1855
+ // src/ports/apply-write.ts
1856
+ function createApplyWrite() {
1857
+ return Object.freeze({
1858
+ async exists(path) {
1859
+ try {
1860
+ await stat2(path);
1861
+ return true;
1862
+ } catch {
1863
+ return false;
1864
+ }
1865
+ },
1866
+ async mkdir(path) {
1867
+ try {
1868
+ await mkdirAsync(path, { recursive: true });
1869
+ return Result.ok(undefined);
1870
+ } catch (cause) {
1871
+ return Result.err(IoError.of(`could not create directory ${path}: ${String(cause)}`));
1872
+ }
1873
+ },
1874
+ async copyFile(src, dest) {
1875
+ try {
1876
+ await mkdirAsync(dirname3(dest), { recursive: true });
1877
+ await copyFileAsync(src, dest);
1878
+ return Result.ok(undefined);
1879
+ } catch (cause) {
1880
+ return Result.err(IoError.of(`could not copy ${src} to ${dest}: ${String(cause)}`));
1881
+ }
1882
+ },
1883
+ async backup(path, backupPath) {
1884
+ try {
1885
+ await renameAsync(path, backupPath);
1886
+ return Result.ok(undefined);
1887
+ } catch (cause) {
1888
+ return Result.err(IoError.of(`could not back up ${path} to ${backupPath}: ${String(cause)}`));
1889
+ }
1890
+ }
1891
+ });
1892
+ }
1893
+
1894
+ // src/ports/http.ts
1895
+ var ALLOWED_HOSTS = new Set(["api.github.com", "codeload.github.com"]);
1896
+ var API_BASE = "https://api.github.com";
1897
+ var MAX_REDIRECTS = 5;
1898
+ function createHttp(options) {
1899
+ return Object.freeze({
1900
+ async getJson(apiPath) {
1901
+ const url = new URL(apiPath, API_BASE);
1902
+ const checked = await fetchChecked(url, options.apiTimeoutMs);
1903
+ if (checked.type === "err")
1904
+ return checked;
1905
+ return Result.ok(await checked.value.json());
1906
+ },
1907
+ async downloadArchive(url) {
1908
+ const checked = await fetchChecked(url, options.archiveTimeoutMs);
1909
+ if (checked.type === "err")
1910
+ return checked;
1911
+ const response = checked.value;
1912
+ if (!response.body) {
1913
+ return Result.err(FetchError.classify(new Error("empty response body"), { status: response.status, url: url.toString() }));
1914
+ }
1915
+ return Result.ok(response.body);
1916
+ }
1917
+ });
1918
+ }
1919
+ async function fetchChecked(url, timeoutMs) {
1920
+ if (!ALLOWED_HOSTS.has(url.host)) {
1921
+ return Result.err(FetchError.payloadInvalid(`refusing to contact untrusted host: ${url.host}`));
1922
+ }
1923
+ try {
1924
+ const response = await fetchFollowingAllowedHosts(url, timeoutMs);
1925
+ if (!response.ok) {
1926
+ const meta = { status: response.status, url: url.toString() };
1927
+ return Result.err(FetchError.classify(new Error(`HTTP ${response.status}`), meta));
1928
+ }
1929
+ return Result.ok(response);
1930
+ } catch (cause) {
1931
+ return Result.err(FetchError.classify(cause, { status: null, url: url.toString() }));
1932
+ }
1933
+ }
1934
+ async function fetchFollowingAllowedHosts(initialUrl, timeoutMs) {
1935
+ const signal = AbortSignal.timeout(timeoutMs);
1936
+ let current = initialUrl;
1937
+ for (let hop = 0;hop <= MAX_REDIRECTS; hop++) {
1938
+ const response = await fetch(current, { signal, redirect: "manual" });
1939
+ if (response.status >= 300 && response.status < 400) {
1940
+ const location = response.headers.get("location");
1941
+ if (!location)
1942
+ throw new Error(`redirect response ${response.status} had no Location header`);
1943
+ const next = new URL(location, current);
1944
+ if (!ALLOWED_HOSTS.has(next.host)) {
1945
+ throw new Error(`refusing to follow redirect to untrusted host: ${next.host}`);
1946
+ }
1947
+ current = next;
1948
+ continue;
1949
+ }
1950
+ return response;
1951
+ }
1952
+ throw new Error("too many redirects");
1953
+ }
1954
+
1955
+ // src/ports/tty.ts
1956
+ import { createInterface } from "node:readline/promises";
1957
+ function createTtyIO() {
1958
+ return Object.freeze({
1959
+ isTTY: process.stdin.isTTY === true,
1960
+ async select(prompt, options) {
1961
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1962
+ try {
1963
+ const menu = options.map((option, index) => ` ${index + 1}) ${option}`).join(`
1964
+ `);
1965
+ for (;; ) {
1966
+ const answer = (await rl.question(`${prompt}
1967
+ ${menu}
1968
+ Enter a number: `)).trim();
1969
+ const index = Number.parseInt(answer, 10) - 1;
1970
+ if (Number.isInteger(index) && index >= 0 && index < options.length) {
1971
+ return options[index];
1972
+ }
1973
+ }
1974
+ } finally {
1975
+ rl.close();
1976
+ }
1977
+ },
1978
+ async input(prompt, defaultValue) {
1979
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1980
+ try {
1981
+ const answer = (await rl.question(`${prompt} [${defaultValue}]: `)).trim();
1982
+ return answer.length === 0 ? defaultValue : answer;
1983
+ } finally {
1984
+ rl.close();
1985
+ }
1986
+ },
1987
+ async confirm(prompt) {
1988
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1989
+ try {
1990
+ const answer = (await rl.question(`${prompt} [y/N]: `)).trim().toLowerCase();
1991
+ return answer === "y" || answer === "yes";
1992
+ } finally {
1993
+ rl.close();
1994
+ }
1995
+ }
1996
+ });
1997
+ }
1998
+
1999
+ // src/ports/verify-read.ts
2000
+ import { stat as stat3 } from "node:fs/promises";
2001
+ function createVerifyRead() {
2002
+ return Object.freeze({
2003
+ async fileExists(path) {
2004
+ try {
2005
+ const info = await stat3(path);
2006
+ return info.isFile();
2007
+ } catch {
2008
+ return false;
2009
+ }
2010
+ },
2011
+ async dirExists(path) {
2012
+ try {
2013
+ const info = await stat3(path);
2014
+ return info.isDirectory();
2015
+ } catch {
2016
+ return false;
2017
+ }
2018
+ }
2019
+ });
2020
+ }
2021
+
2022
+ // src/cli.ts
2023
+ var PACKAGE_JSON_PATH = join10(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
2024
+ var SETUP_VERSION = JSON.parse(readFileSync(PACKAGE_JSON_PATH, "utf8")).version;
2025
+ var API_TIMEOUT_MS = 1e4;
2026
+ var ARCHIVE_TIMEOUT_MS = 30000;
2027
+ function createDefaultPorts() {
2028
+ return Object.freeze({
2029
+ tty: createTtyIO(),
2030
+ manifestIo: createManifestIo(createFsRead(), createFsWrite()),
2031
+ http: createHttp({ apiTimeoutMs: API_TIMEOUT_MS, archiveTimeoutMs: ARCHIVE_TIMEOUT_MS }),
2032
+ createTmpWrite,
2033
+ applyWrite: createApplyWrite(),
2034
+ verifyRead: createVerifyRead()
2035
+ });
2036
+ }
2037
+ async function main(argv, ports = createDefaultPorts()) {
2038
+ const parsed = ParsedCommand.parse(argv);
2039
+ if (parsed.type === "err") {
2040
+ console.error(renderError(parsed.error));
2041
+ return 2;
2042
+ }
2043
+ if (parsed.value.subcommand === "help") {
2044
+ console.log(renderHelp());
2045
+ return 0;
2046
+ }
2047
+ if (parsed.value.subcommand === "upgrade") {
2048
+ return runUpgrade(parsed.value, ports);
2049
+ }
2050
+ return runInstall(parsed.value, ports);
2051
+ }
2052
+ async function resolveInputs(parsed, ports) {
2053
+ const { tty } = ports;
2054
+ const mode = parsed.isNonInteractive(tty.isTTY) ? "non-interactive" : "interactive";
2055
+ const missing = parsed.missingRequiredFor(mode);
2056
+ if (mode === "non-interactive") {
2057
+ if (missing.length > 0) {
2058
+ console.error(renderError(UsageError.missingRequired(missing)));
2059
+ return { type: "exit", code: 2 };
2060
+ }
2061
+ return { type: "ok", inputs: InstallInputs.fromFlags(parsed), mode };
2062
+ }
2063
+ if (missing.length > 0) {
2064
+ const wizardResult = await runWizard(parsed, missing, tty);
2065
+ if (wizardResult.type === "err") {
2066
+ console.log(renderWizardAborted());
2067
+ return { type: "exit", code: 1 };
2068
+ }
2069
+ return { type: "ok", inputs: wizardResult.value, mode };
2070
+ }
2071
+ return { type: "ok", inputs: InstallInputs.fromFlags(parsed), mode };
2072
+ }
2073
+ async function withTmpWrite(ports, prefix, fn) {
2074
+ const tmpWriteResult = await ports.createTmpWrite(prefix);
2075
+ if (tmpWriteResult.type === "err") {
2076
+ console.error(renderTmpDirFailure(tmpWriteResult.error.detail));
2077
+ return 1;
2078
+ }
2079
+ const tmpWrite = tmpWriteResult.value;
2080
+ let cleaned = false;
2081
+ const cleanup = () => {
2082
+ if (cleaned)
2083
+ return;
2084
+ cleaned = true;
2085
+ tmpWrite.remove();
2086
+ };
2087
+ const onSignal = () => {
2088
+ cleanup();
2089
+ process.exit(1);
2090
+ };
2091
+ process.once("SIGINT", onSignal);
2092
+ process.once("SIGTERM", onSignal);
2093
+ try {
2094
+ return await fn(tmpWrite);
2095
+ } finally {
2096
+ process.removeListener("SIGINT", onSignal);
2097
+ process.removeListener("SIGTERM", onSignal);
2098
+ cleanup();
2099
+ }
2100
+ }
2101
+ async function runInstall(parsed, ports) {
2102
+ const resolvedInputs = await resolveInputs(parsed, ports);
2103
+ if (resolvedInputs.type === "exit")
2104
+ return resolvedInputs.code;
2105
+ const { inputs, mode } = resolvedInputs;
2106
+ const installation = await Installation.detect(inputs.target, ports.manifestIo);
2107
+ const admission = installation.admitsInstall(parsed.force);
2108
+ if (admission.type === "refuse-suggest-upgrade") {
2109
+ console.error(renderAlreadyInstalled(admission));
2110
+ return 1;
2111
+ }
2112
+ const resolved = await createResolver(ports.http).resolveVersion(parsed.version);
2113
+ if (resolved.type === "err") {
2114
+ console.error(renderError(resolved.error));
2115
+ return 1;
2116
+ }
2117
+ return withTmpWrite(ports, "amadeus-setup-install-", async (tmpWrite) => {
2118
+ const fetched = await createFetcher(ports.http, tmpWrite).fetchArchive(resolved.value);
2119
+ if (fetched.type === "err") {
2120
+ console.error(renderError(fetched.error));
2121
+ return 1;
2122
+ }
2123
+ const startedAt = new Date().toISOString();
2124
+ const planResult = Plan.forInstall(fetched.value, inputs.harness, inputs.target, { force: parsed.force, startedAt });
2125
+ if (planResult.type === "err") {
2126
+ console.error(renderError(planResult.error));
2127
+ return 1;
2128
+ }
2129
+ const plan = planResult.value;
2130
+ console.log(renderPlanReport(plan));
2131
+ if (plan.hasConflicts() && !parsed.force) {
2132
+ if (mode === "interactive") {
2133
+ const proceed = await ports.tty.confirm("Continue past the conflicts listed above?");
2134
+ if (!proceed)
2135
+ return 1;
2136
+ } else {
2137
+ return 1;
2138
+ }
2139
+ }
2140
+ const applied = await Applier.create(ports.applyWrite).apply(plan, inputs.target);
2141
+ if (applied.hasFailures()) {
2142
+ console.error(renderApplyFailure(applied));
2143
+ return 1;
2144
+ }
2145
+ const filesResult = applied.manifestFiles();
2146
+ if (filesResult.type === "err") {
2147
+ console.error(renderError(filesResult.error));
2148
+ return 1;
2149
+ }
2150
+ const manifest = Manifest.build(fetched.value, filesResult.value, {
2151
+ installerPackageVersion: SETUP_VERSION,
2152
+ harness: inputs.harness,
2153
+ installStartedAt: plan.startedAtIso
2154
+ });
2155
+ const written = await ports.manifestIo.write(inputs.target, manifest);
2156
+ if (written.type === "err") {
2157
+ console.error(renderError(written.error));
2158
+ return 1;
2159
+ }
2160
+ const verify = await Verifier.create(ports.verifyRead).verify(inputs.target, manifest);
2161
+ if (!verify.allPassed()) {
2162
+ console.error(renderVerifyFailure(verify));
2163
+ return 1;
2164
+ }
2165
+ console.log(renderSuccess(applied, verify, NextSteps.of(inputs.harness, resolved.value, inputs.target)));
2166
+ return 0;
2167
+ });
2168
+ }
2169
+ async function runUpgrade(parsed, ports) {
2170
+ const resolvedInputs = await resolveInputs(parsed, ports);
2171
+ if (resolvedInputs.type === "exit")
2172
+ return resolvedInputs.code;
2173
+ const { inputs } = resolvedInputs;
2174
+ const installation = await Installation.detect(inputs.target, ports.manifestIo);
2175
+ const sourceResult = UpgradeSource.fromInstallation(installation, parsed.force);
2176
+ if (sourceResult.type === "err") {
2177
+ console.error(renderError(sourceResult.error));
2178
+ return 1;
2179
+ }
2180
+ const source = sourceResult.value;
2181
+ const resolved = await createResolver(ports.http).resolveVersion(parsed.version);
2182
+ if (resolved.type === "err") {
2183
+ console.error(renderError(resolved.error));
2184
+ return 1;
2185
+ }
2186
+ const assessment = source.assess(resolved.value, parsed.version);
2187
+ if (assessment !== null) {
2188
+ const outcome = assessment.outcome();
2189
+ if (outcome.type !== "proceed") {
2190
+ console.error(renderError(UpgradeRefusal.fromOutcome(outcome)));
2191
+ return outcome.type === "already-up-to-date" ? 0 : 1;
2192
+ }
2193
+ }
2194
+ return withTmpWrite(ports, "amadeus-setup-upgrade-", async (tmpWrite) => {
2195
+ const fetched = await createFetcher(ports.http, tmpWrite).fetchArchive(resolved.value);
2196
+ if (fetched.type === "err") {
2197
+ console.error(renderError(fetched.error));
2198
+ return 1;
2199
+ }
2200
+ const startedAt = new Date().toISOString();
2201
+ const planResult = Plan.forUpgrade(fetched.value, source, inputs.harness, inputs.target, { force: parsed.force, startedAt });
2202
+ if (planResult.type === "err") {
2203
+ console.error(renderError(planResult.error));
2204
+ return 1;
2205
+ }
2206
+ const plan = planResult.value;
2207
+ console.log(renderPlanReport(plan, source.strategyNote()));
2208
+ if (resolvedInputs.mode === "interactive" && !parsed.force) {
2209
+ const proceed = await ports.tty.confirm("Continue with the upgrade plan above?");
2210
+ if (!proceed)
2211
+ return 1;
2212
+ }
2213
+ const applied = await Applier.create(ports.applyWrite).apply(plan, inputs.target);
2214
+ if (applied.hasFailures()) {
2215
+ console.error(renderApplyFailure(applied));
2216
+ return 1;
2217
+ }
2218
+ const filesResult = applied.manifestFiles();
2219
+ if (filesResult.type === "err") {
2220
+ console.error(renderError(filesResult.error));
2221
+ return 1;
2222
+ }
2223
+ const newManifest = source.nextManifest({
2224
+ payload: fetched.value,
2225
+ files: filesResult.value,
2226
+ meta: { installerPackageVersion: SETUP_VERSION, harness: inputs.harness, installStartedAt: plan.startedAtIso }
2227
+ });
2228
+ const written = await ports.manifestIo.write(inputs.target, newManifest);
2229
+ if (written.type === "err") {
2230
+ console.error(renderError(written.error));
2231
+ return 1;
2232
+ }
2233
+ const verify = await Verifier.create(ports.verifyRead).verify(inputs.target, newManifest);
2234
+ if (!verify.allPassed()) {
2235
+ console.error(renderVerifyFailure(verify));
2236
+ return 1;
2237
+ }
2238
+ console.log(renderSuccess(applied, verify, NextSteps.of(inputs.harness, resolved.value, inputs.target)));
2239
+ return 0;
2240
+ });
2241
+ }
2242
+ var isEntryPoint = process.argv[1] !== undefined && resolve3(process.argv[1]) === fileURLToPath(import.meta.url);
2243
+ if (isEntryPoint) {
2244
+ const exitCode = await main(process.argv.slice(2));
2245
+ process.exit(exitCode);
2246
+ }
2247
+ export {
2248
+ main,
2249
+ createDefaultPorts
2250
+ };