dlw-machine-setup 0.6.0 → 0.6.1

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 (2) hide show
  1. package/bin/installer.js +329 -405
  2. package/package.json +1 -1
package/bin/installer.js CHANGED
@@ -2753,13 +2753,13 @@ var PromisePolyfill = class extends Promise {
2753
2753
  // Available starting from Node 22
2754
2754
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
2755
2755
  static withResolver() {
2756
- let resolve5;
2756
+ let resolve4;
2757
2757
  let reject;
2758
2758
  const promise = new Promise((res, rej) => {
2759
- resolve5 = res;
2759
+ resolve4 = res;
2760
2760
  reject = rej;
2761
2761
  });
2762
- return { promise, resolve: resolve5, reject };
2762
+ return { promise, resolve: resolve4, reject };
2763
2763
  }
2764
2764
  };
2765
2765
 
@@ -2776,7 +2776,7 @@ function createPrompt(view) {
2776
2776
  output
2777
2777
  });
2778
2778
  const screen = new ScreenManager(rl);
2779
- const { promise, resolve: resolve5, reject } = PromisePolyfill.withResolver();
2779
+ const { promise, resolve: resolve4, reject } = PromisePolyfill.withResolver();
2780
2780
  const cancel = () => reject(new CancelPromptError());
2781
2781
  if (signal) {
2782
2782
  const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
@@ -2800,7 +2800,7 @@ function createPrompt(view) {
2800
2800
  cycle(() => {
2801
2801
  try {
2802
2802
  const nextView = view(config, (value) => {
2803
- setImmediate(() => resolve5(value));
2803
+ setImmediate(() => resolve4(value));
2804
2804
  });
2805
2805
  const [content, bottomContent] = typeof nextView === "string" ? [nextView] : nextView;
2806
2806
  screen.render(content, bottomContent);
@@ -3244,14 +3244,32 @@ ${page}${helpTipBottom}${choiceDescription}${import_ansi_escapes3.default.cursor
3244
3244
  });
3245
3245
 
3246
3246
  // src/index.ts
3247
- var import_fs9 = require("fs");
3247
+ var import_fs11 = require("fs");
3248
3248
  var import_readline = require("readline");
3249
- var import_path9 = require("path");
3249
+ var import_path12 = require("path");
3250
3250
 
3251
3251
  // src/utils/fetch.ts
3252
3252
  var DEFAULT_TIMEOUT_MS = 3e4;
3253
3253
  var DEFAULT_MAX_RETRIES = 3;
3254
3254
  var DEFAULT_RETRY_DELAY_MS = 2e3;
3255
+ function getReadableError(status) {
3256
+ switch (status) {
3257
+ case 401:
3258
+ return "Authentication failed. Check your GitHub token.";
3259
+ case 403:
3260
+ return "Access denied. Verify token permissions and rate limits.";
3261
+ case 404:
3262
+ return "Resource not found. Verify the repository and release exist.";
3263
+ case 429:
3264
+ return "Rate limit exceeded. Please wait and try again.";
3265
+ case 500:
3266
+ case 502:
3267
+ case 503:
3268
+ return "GitHub server error. Please try again later.";
3269
+ default:
3270
+ return `Unexpected error (${status})`;
3271
+ }
3272
+ }
3255
3273
  async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
3256
3274
  const controller = new AbortController();
3257
3275
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -3283,7 +3301,7 @@ async function fetchWithRetry(url, options = {}, maxRetries = DEFAULT_MAX_RETRIE
3283
3301
  }
3284
3302
  if (attempt < maxRetries) {
3285
3303
  const delay = retryDelayMs * Math.pow(2, attempt - 1);
3286
- await new Promise((resolve5) => setTimeout(resolve5, delay));
3304
+ await new Promise((resolve4) => setTimeout(resolve4, delay));
3287
3305
  }
3288
3306
  }
3289
3307
  throw lastError || new Error("Failed after maximum retries");
@@ -3451,7 +3469,7 @@ async function pollForToken(deviceCode, interval) {
3451
3469
  const maxAttempts = 60;
3452
3470
  let attempts = 0;
3453
3471
  while (attempts < maxAttempts) {
3454
- await new Promise((resolve5) => setTimeout(resolve5, interval * 1e3));
3472
+ await new Promise((resolve4) => setTimeout(resolve4, interval * 1e3));
3455
3473
  const response = await fetchWithTimeout(GITHUB_TOKEN_URL, {
3456
3474
  method: "POST",
3457
3475
  headers: {
@@ -3671,10 +3689,86 @@ __export(steps_exports, {
3671
3689
  });
3672
3690
 
3673
3691
  // src/steps/resources/fetch-contexts.ts
3692
+ var import_fs4 = require("fs");
3693
+ var import_path4 = require("path");
3694
+
3695
+ // src/utils/fs-copy.ts
3674
3696
  var import_fs2 = require("fs");
3675
3697
  var import_path2 = require("path");
3698
+ function copyDirectory(source, target) {
3699
+ if (!(0, import_fs2.existsSync)(target)) (0, import_fs2.mkdirSync)(target, { recursive: true });
3700
+ for (const entry of (0, import_fs2.readdirSync)(source, { withFileTypes: true })) {
3701
+ const s = (0, import_path2.join)(source, entry.name);
3702
+ const t = (0, import_path2.join)(target, entry.name);
3703
+ if (entry.isDirectory()) copyDirectory(s, t);
3704
+ else (0, import_fs2.copyFileSync)(s, t);
3705
+ }
3706
+ }
3707
+
3708
+ // src/utils/download-archive.ts
3709
+ var import_fs3 = require("fs");
3710
+ var import_path3 = require("path");
3676
3711
  var import_child_process2 = require("child_process");
3677
3712
  var MIN_FILE_SIZE = 1024;
3713
+ async function fetchLatestRelease(token, repo) {
3714
+ const headers = {
3715
+ "Accept": "application/vnd.github+json",
3716
+ "Authorization": `Bearer ${token}`
3717
+ };
3718
+ const res = await fetchWithRetry(`https://api.github.com/repos/${repo}/releases/latest`, { headers });
3719
+ if (!res.ok) {
3720
+ throw new Error(`GitHub API error (${res.status}): ${getReadableError(res.status)}`);
3721
+ }
3722
+ const data = await res.json();
3723
+ return { tagName: data.tag_name ?? "unknown", assets: data.assets ?? [] };
3724
+ }
3725
+ async function downloadAndExtractAsset(token, asset, projectPath, tempDirName) {
3726
+ const tarCheck = (0, import_child_process2.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3727
+ if (tarCheck.status !== 0) {
3728
+ throw new Error("tar command not found. Please ensure tar is installed and available in your PATH.");
3729
+ }
3730
+ const tempDir = (0, import_path3.join)(projectPath, tempDirName);
3731
+ if ((0, import_fs3.existsSync)(tempDir)) (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
3732
+ (0, import_fs3.mkdirSync)(tempDir, { recursive: true });
3733
+ const cleanup = () => {
3734
+ if ((0, import_fs3.existsSync)(tempDir)) {
3735
+ try {
3736
+ (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
3737
+ } catch {
3738
+ }
3739
+ }
3740
+ };
3741
+ try {
3742
+ const dlRes = await fetchWithRetry(asset.url, {
3743
+ headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
3744
+ });
3745
+ if (!dlRes.ok) {
3746
+ throw new Error(`Download failed: ${getReadableError(dlRes.status)}`);
3747
+ }
3748
+ const archivePath = (0, import_path3.join)(tempDir, asset.name);
3749
+ (0, import_fs3.writeFileSync)(archivePath, Buffer.from(await dlRes.arrayBuffer()));
3750
+ if ((0, import_fs3.statSync)(archivePath).size < MIN_FILE_SIZE) {
3751
+ throw new Error("File corrupted");
3752
+ }
3753
+ const list = (0, import_child_process2.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
3754
+ if (list.status !== 0) throw new Error("Failed to read archive contents");
3755
+ const resolvedTemp = (0, import_path3.resolve)(tempDir);
3756
+ for (const entry of list.stdout.split("\n").filter(Boolean)) {
3757
+ const path = (0, import_path3.resolve)((0, import_path3.join)(tempDir, entry.replace(/\/$/, "")));
3758
+ if (!path.startsWith(resolvedTemp + import_path3.sep)) {
3759
+ throw new Error(`Archive contains unsafe path: ${entry}`);
3760
+ }
3761
+ }
3762
+ const ex = (0, import_child_process2.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
3763
+ if (ex.status !== 0) throw new Error("Archive extraction failed");
3764
+ return { extractedRoot: tempDir, cleanup };
3765
+ } catch (err) {
3766
+ cleanup();
3767
+ throw err;
3768
+ }
3769
+ }
3770
+
3771
+ // src/steps/resources/fetch-contexts.ts
3678
3772
  var fetch_contexts_default = defineStep({
3679
3773
  name: "fetch-contexts",
3680
3774
  label: "Downloading contexts",
@@ -3683,9 +3777,6 @@ var fetch_contexts_default = defineStep({
3683
3777
  const uniqueDomains = [...new Set(ctx.config.technologies.flatMap((p) => p.domains))];
3684
3778
  const domainValues = uniqueDomains.map((d) => d.toLowerCase());
3685
3779
  if (!ctx.contextRepo) {
3686
- for (const domain of domainValues) {
3687
- ctx.installed.domainsFailed = domainValues;
3688
- }
3689
3780
  return {
3690
3781
  status: "failed",
3691
3782
  detail: "Context repo not found (topic: context-data)"
@@ -3693,8 +3784,6 @@ var fetch_contexts_default = defineStep({
3693
3784
  }
3694
3785
  const downloadResult = await fetchContexts(domainValues, ctx.token, ctx.contextRepo, ctx.config.projectPath);
3695
3786
  ctx.installed.domainsInstalled = downloadResult.successful;
3696
- ctx.installed.domainsFailed = downloadResult.failed;
3697
- ctx.installed.failureReasons = downloadResult.failureReasons;
3698
3787
  ctx.installed.contextReleaseVersion = downloadResult.releaseVersion;
3699
3788
  const domainColWidth = Math.max(...domainValues.map((d) => d.length), 8) + 2;
3700
3789
  console.log("");
@@ -3719,24 +3808,9 @@ var fetch_contexts_default = defineStep({
3719
3808
  async function fetchContexts(domains, token, repo, targetDir) {
3720
3809
  const result = { successful: [], failed: [], failureReasons: {} };
3721
3810
  if (domains.length === 0) return result;
3722
- const tarCheck = (0, import_child_process2.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3723
- if (tarCheck.status !== 0) {
3724
- throw new Error("tar command not found. Please ensure tar is installed and available in your PATH.");
3725
- }
3726
- const headers = {
3727
- "Accept": "application/vnd.github+json",
3728
- "Authorization": `Bearer ${token}`
3729
- };
3730
- const releaseResponse = await fetchWithRetry(
3731
- `https://api.github.com/repos/${repo}/releases/latest`,
3732
- { headers }
3733
- );
3734
- if (!releaseResponse.ok) {
3735
- throw new Error(`GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`);
3736
- }
3737
- const releaseData = await releaseResponse.json();
3738
- result.releaseVersion = releaseData.tag_name;
3739
- const asset = releaseData.assets?.find((a) => a.name.endsWith(".tar.gz"));
3811
+ const release = await fetchLatestRelease(token, repo);
3812
+ result.releaseVersion = release.tagName;
3813
+ const asset = release.assets.find((a) => a.name.endsWith(".tar.gz"));
3740
3814
  if (!asset) {
3741
3815
  for (const domain of domains) {
3742
3816
  result.failed.push(domain);
@@ -3744,39 +3818,11 @@ async function fetchContexts(domains, token, repo, targetDir) {
3744
3818
  }
3745
3819
  return result;
3746
3820
  }
3747
- const contextsDir = (0, import_path2.join)(targetDir, "_ai-context");
3748
- const tempDir = (0, import_path2.join)(targetDir, ".temp-download");
3821
+ const contextsDir = (0, import_path4.join)(targetDir, "_ai-context");
3822
+ if (!(0, import_fs4.existsSync)(contextsDir)) (0, import_fs4.mkdirSync)(contextsDir, { recursive: true });
3823
+ const archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-download");
3749
3824
  try {
3750
- if (!(0, import_fs2.existsSync)(contextsDir)) (0, import_fs2.mkdirSync)(contextsDir, { recursive: true });
3751
- if ((0, import_fs2.existsSync)(tempDir)) (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
3752
- (0, import_fs2.mkdirSync)(tempDir, { recursive: true });
3753
- const downloadHeaders = {
3754
- "Accept": "application/octet-stream",
3755
- "Authorization": `Bearer ${token}`
3756
- };
3757
- const response = await fetchWithRetry(asset.url, { headers: downloadHeaders });
3758
- if (!response.ok) {
3759
- throw new Error(`Download failed: ${getReadableError(response.status)}`);
3760
- }
3761
- const archivePath = (0, import_path2.join)(tempDir, asset.name);
3762
- const arrayBuffer = await response.arrayBuffer();
3763
- (0, import_fs2.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
3764
- const stats = (0, import_fs2.statSync)(archivePath);
3765
- if (stats.size < MIN_FILE_SIZE) {
3766
- throw new Error("File corrupted");
3767
- }
3768
- const listResult = (0, import_child_process2.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
3769
- if (listResult.status !== 0) throw new Error("Failed to read archive contents");
3770
- const resolvedTempDir = (0, import_path2.resolve)(tempDir);
3771
- for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
3772
- const entryPath = (0, import_path2.resolve)((0, import_path2.join)(tempDir, entry.replace(/\/$/, "")));
3773
- if (!entryPath.startsWith(resolvedTempDir + import_path2.sep)) {
3774
- throw new Error(`Archive contains unsafe path: ${entry}`);
3775
- }
3776
- }
3777
- const extractResult = (0, import_child_process2.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
3778
- if (extractResult.status !== 0) throw new Error("Archive extraction failed");
3779
- const extractedFolders = (0, import_fs2.readdirSync)(tempDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3825
+ const extractedFolders = (0, import_fs4.readdirSync)(archive.extractedRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3780
3826
  for (const domain of domains) {
3781
3827
  try {
3782
3828
  const match = extractedFolders.find((f) => f.toLowerCase() === domain.toLowerCase());
@@ -3785,8 +3831,8 @@ async function fetchContexts(domains, token, repo, targetDir) {
3785
3831
  result.failureReasons[domain] = "Not found in archive";
3786
3832
  continue;
3787
3833
  }
3788
- const domainPath = (0, import_path2.join)(contextsDir, domain);
3789
- copyDirectory((0, import_path2.join)(tempDir, match), domainPath);
3834
+ const domainPath = (0, import_path4.join)(contextsDir, domain);
3835
+ copyDirectory((0, import_path4.join)(archive.extractedRoot, match), domainPath);
3790
3836
  result.successful.push(domain);
3791
3837
  } catch (error) {
3792
3838
  result.failed.push(domain);
@@ -3794,59 +3840,51 @@ async function fetchContexts(domains, token, repo, targetDir) {
3794
3840
  }
3795
3841
  }
3796
3842
  } finally {
3797
- if ((0, import_fs2.existsSync)(tempDir)) {
3798
- try {
3799
- (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
3800
- } catch {
3801
- }
3802
- }
3843
+ archive.cleanup();
3803
3844
  }
3804
3845
  return result;
3805
3846
  }
3806
- function copyDirectory(source, target) {
3807
- if (!(0, import_fs2.existsSync)(target)) (0, import_fs2.mkdirSync)(target, { recursive: true });
3808
- const entries = (0, import_fs2.readdirSync)(source, { withFileTypes: true });
3809
- for (const entry of entries) {
3810
- const sourcePath = (0, import_path2.join)(source, entry.name);
3811
- const targetPath = (0, import_path2.join)(target, entry.name);
3812
- if (entry.isDirectory()) {
3813
- copyDirectory(sourcePath, targetPath);
3814
- } else {
3815
- (0, import_fs2.copyFileSync)(sourcePath, targetPath);
3816
- }
3817
- }
3818
- }
3819
- function getReadableError(status) {
3820
- switch (status) {
3821
- case 401:
3822
- return "Authentication failed. Check your GitHub token.";
3823
- case 403:
3824
- return "Access denied. Verify token permissions and rate limits.";
3825
- case 404:
3826
- return "Resource not found. Verify the repository and release exist.";
3827
- case 429:
3828
- return "Rate limit exceeded. Please wait and try again.";
3829
- case 500:
3830
- case 502:
3831
- case 503:
3832
- return "GitHub server error. Please try again later.";
3833
- default:
3834
- return `Unexpected error (${status})`;
3835
- }
3836
- }
3837
3847
 
3838
3848
  // src/steps/resources/fetch-factory.ts
3839
- var import_fs4 = require("fs");
3840
- var import_path4 = require("path");
3841
- var import_child_process3 = require("child_process");
3849
+ var import_fs7 = require("fs");
3850
+ var import_path7 = require("path");
3842
3851
 
3843
3852
  // src/bundles/run-bundle.ts
3844
- var import_fs3 = require("fs");
3845
- var import_path3 = require("path");
3853
+ var import_fs6 = require("fs");
3854
+ var import_path6 = require("path");
3846
3855
 
3847
3856
  // src/bundles/types.ts
3848
3857
  var SCHEMA_VERSION = 1;
3849
3858
 
3859
+ // src/utils/marker-block.ts
3860
+ var import_fs5 = require("fs");
3861
+ var import_path5 = require("path");
3862
+ function upsertMarkerBlock(filePath, startMarker, endMarker, body, opts = {}) {
3863
+ const block = `${startMarker}
3864
+ ${body}
3865
+ ${endMarker}`;
3866
+ (0, import_fs5.mkdirSync)((0, import_path5.dirname)(filePath), { recursive: true });
3867
+ if (!(0, import_fs5.existsSync)(filePath)) {
3868
+ (0, import_fs5.writeFileSync)(filePath, block + "\n", "utf-8");
3869
+ return;
3870
+ }
3871
+ const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
3872
+ const s = existing.indexOf(startMarker);
3873
+ const e = existing.indexOf(endMarker);
3874
+ const hasS = s !== -1;
3875
+ const hasE = e !== -1;
3876
+ if (hasS && !hasE || !hasS && hasE || hasS && hasE && e < s) {
3877
+ opts.onCorrupt?.(filePath, startMarker, endMarker);
3878
+ }
3879
+ if (hasS && hasE && e > s) {
3880
+ const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
3881
+ (0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
3882
+ } else {
3883
+ const sep3 = existing.endsWith("\n") ? "\n" : "\n\n";
3884
+ (0, import_fs5.writeFileSync)(filePath, existing + sep3 + block + "\n", "utf-8");
3885
+ }
3886
+ }
3887
+
3850
3888
  // src/bundles/run-bundle.ts
3851
3889
  var OWNER_KEY = "_bundleOwner";
3852
3890
  async function runBundle(manifest, ctx) {
@@ -3864,7 +3902,7 @@ async function runBundle(manifest, ctx) {
3864
3902
  }
3865
3903
  if (manifest.gitignore?.length) {
3866
3904
  upsertMarkerBlock(
3867
- (0, import_path3.join)(ctx.projectPath, ".gitignore"),
3905
+ (0, import_path6.join)(ctx.projectPath, ".gitignore"),
3868
3906
  `# ${manifest.name}:start`,
3869
3907
  `# ${manifest.name}:end`,
3870
3908
  manifest.gitignore.join("\n")
@@ -3876,7 +3914,7 @@ async function runBundle(manifest, ctx) {
3876
3914
  result.instructionsSnippet = snippet;
3877
3915
  if (!ctx.skipInstructions) {
3878
3916
  upsertMarkerBlock(
3879
- (0, import_path3.join)(ctx.projectPath, ctx.instructionsFile),
3917
+ (0, import_path6.join)(ctx.projectPath, ctx.instructionsFile),
3880
3918
  `<!-- ${manifest.name}:start -->`,
3881
3919
  `<!-- ${manifest.name}:end -->`,
3882
3920
  snippet
@@ -3903,12 +3941,12 @@ function executeOp(op, owner, ctx, result) {
3903
3941
  function runCopy(op, ctx, result) {
3904
3942
  const source = resolveBundlePath(op.from, ctx);
3905
3943
  const target = resolveProjectPath(op.to, ctx);
3906
- if (!(0, import_fs3.existsSync)(source)) return;
3907
- if ((0, import_fs3.statSync)(source).isDirectory()) {
3908
- copyDirectory2(source, target);
3944
+ if (!(0, import_fs6.existsSync)(source)) return;
3945
+ if ((0, import_fs6.statSync)(source).isDirectory()) {
3946
+ copyDirectory(source, target);
3909
3947
  } else {
3910
- (0, import_fs3.mkdirSync)((0, import_path3.dirname)(target), { recursive: true });
3911
- (0, import_fs3.copyFileSync)(source, target);
3948
+ (0, import_fs6.mkdirSync)((0, import_path6.dirname)(target), { recursive: true });
3949
+ (0, import_fs6.copyFileSync)(source, target);
3912
3950
  }
3913
3951
  result.filesTouched.push(op.to);
3914
3952
  }
@@ -3918,14 +3956,14 @@ function runMergeJson(op, owner, ctx, result) {
3918
3956
  const stripped = stripOwnedEntries(existing, owner);
3919
3957
  const tagged = tagEntries(op.patch, owner);
3920
3958
  const merged = deepMerge2(stripped, tagged);
3921
- (0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
3922
- (0, import_fs3.writeFileSync)(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3959
+ (0, import_fs6.mkdirSync)((0, import_path6.dirname)(filePath), { recursive: true });
3960
+ (0, import_fs6.writeFileSync)(filePath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
3923
3961
  result.filesTouched.push(op.file);
3924
3962
  }
3925
3963
  function readJsonOrEmpty(path) {
3926
- if (!(0, import_fs3.existsSync)(path)) return {};
3964
+ if (!(0, import_fs6.existsSync)(path)) return {};
3927
3965
  try {
3928
- return JSON.parse((0, import_fs3.readFileSync)(path, "utf-8"));
3966
+ return JSON.parse((0, import_fs6.readFileSync)(path, "utf-8"));
3929
3967
  } catch {
3930
3968
  return {};
3931
3969
  }
@@ -3970,38 +4008,18 @@ function deepMerge2(a, b) {
3970
4008
  }
3971
4009
  return b;
3972
4010
  }
3973
- function upsertMarkerBlock(filePath, startMarker, endMarker, body) {
3974
- const block = `${startMarker}
3975
- ${body}
3976
- ${endMarker}`;
3977
- (0, import_fs3.mkdirSync)((0, import_path3.dirname)(filePath), { recursive: true });
3978
- if (!(0, import_fs3.existsSync)(filePath)) {
3979
- (0, import_fs3.writeFileSync)(filePath, block + "\n", "utf-8");
3980
- return;
3981
- }
3982
- const existing = (0, import_fs3.readFileSync)(filePath, "utf-8");
3983
- const s = existing.indexOf(startMarker);
3984
- const e = existing.indexOf(endMarker);
3985
- if (s !== -1 && e !== -1 && e > s) {
3986
- const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
3987
- (0, import_fs3.writeFileSync)(filePath, updated, "utf-8");
3988
- } else {
3989
- const joiner = existing.endsWith("\n") ? "\n" : "\n\n";
3990
- (0, import_fs3.writeFileSync)(filePath, existing + joiner + block + "\n", "utf-8");
3991
- }
3992
- }
3993
4011
  function resolveBundlePath(rel, ctx) {
3994
- const base = (0, import_path3.resolve)(ctx.bundleRoot);
3995
- const full = (0, import_path3.resolve)((0, import_path3.join)(base, rel));
3996
- if (full !== base && !full.startsWith(base + import_path3.sep)) {
4012
+ const base = (0, import_path6.resolve)(ctx.bundleRoot);
4013
+ const full = (0, import_path6.resolve)((0, import_path6.join)(base, rel));
4014
+ if (full !== base && !full.startsWith(base + import_path6.sep)) {
3997
4015
  throw new Error(`Bundle op "from" escapes bundle root: ${rel}`);
3998
4016
  }
3999
4017
  return full;
4000
4018
  }
4001
4019
  function resolveProjectPath(rel, ctx) {
4002
- const base = (0, import_path3.resolve)(ctx.projectPath);
4003
- const full = (0, import_path3.resolve)((0, import_path3.join)(base, rel));
4004
- if (full !== base && !full.startsWith(base + import_path3.sep)) {
4020
+ const base = (0, import_path6.resolve)(ctx.projectPath);
4021
+ const full = (0, import_path6.resolve)((0, import_path6.join)(base, rel));
4022
+ if (full !== base && !full.startsWith(base + import_path6.sep)) {
4005
4023
  throw new Error(`Bundle op "to" escapes project path: ${rel}`);
4006
4024
  }
4007
4025
  return full;
@@ -4019,22 +4037,12 @@ function assertSchemaCompatible(manifest) {
4019
4037
  }
4020
4038
  }
4021
4039
  function assertBundleRootExists(ctx) {
4022
- if (!(0, import_fs3.existsSync)(ctx.bundleRoot) || !(0, import_fs3.statSync)(ctx.bundleRoot).isDirectory()) {
4040
+ if (!(0, import_fs6.existsSync)(ctx.bundleRoot) || !(0, import_fs6.statSync)(ctx.bundleRoot).isDirectory()) {
4023
4041
  throw new Error(`Bundle root not found or not a directory: ${ctx.bundleRoot}`);
4024
4042
  }
4025
4043
  }
4026
- function copyDirectory2(source, target) {
4027
- if (!(0, import_fs3.existsSync)(target)) (0, import_fs3.mkdirSync)(target, { recursive: true });
4028
- for (const entry of (0, import_fs3.readdirSync)(source, { withFileTypes: true })) {
4029
- const s = (0, import_path3.join)(source, entry.name);
4030
- const t = (0, import_path3.join)(target, entry.name);
4031
- if (entry.isDirectory()) copyDirectory2(s, t);
4032
- else (0, import_fs3.copyFileSync)(s, t);
4033
- }
4034
- }
4035
4044
 
4036
4045
  // src/steps/resources/fetch-factory.ts
4037
- var MIN_FILE_SIZE2 = 1024;
4038
4046
  function getFactoryAsset(agent) {
4039
4047
  switch (agent) {
4040
4048
  case "github-copilot":
@@ -4065,76 +4073,31 @@ var fetch_factory_default = defineStep({
4065
4073
  });
4066
4074
  async function fetchFactory(token, repo, targetDir, agent) {
4067
4075
  const result = { success: false, filesInstalled: [] };
4068
- const tarCheck = (0, import_child_process3.spawnSync)("tar", ["--version"], { stdio: "ignore" });
4069
- if (tarCheck.status !== 0) {
4070
- result.failureReason = "tar command not found";
4071
- return result;
4072
- }
4073
4076
  const { assetName, rootFolder } = getFactoryAsset(agent);
4074
- const headers = {
4075
- "Accept": "application/vnd.github+json",
4076
- "Authorization": `Bearer ${token}`
4077
- };
4078
- const releaseResponse = await fetchWithRetry(
4079
- `https://api.github.com/repos/${repo}/releases/latest`,
4080
- { headers }
4081
- );
4082
- if (!releaseResponse.ok) {
4083
- result.failureReason = `GitHub API error (${releaseResponse.status})`;
4077
+ let release;
4078
+ try {
4079
+ release = await fetchLatestRelease(token, repo);
4080
+ } catch (err) {
4081
+ result.failureReason = err instanceof Error ? err.message : String(err);
4084
4082
  return result;
4085
4083
  }
4086
- const releaseData = await releaseResponse.json();
4087
- const asset = releaseData.assets?.find((a) => a.name === assetName);
4084
+ const asset = release.assets.find((a) => a.name === assetName);
4088
4085
  if (!asset) {
4089
4086
  result.failureReason = `${assetName} not found in release`;
4090
4087
  return result;
4091
4088
  }
4092
- const tempDir = (0, import_path4.join)(targetDir, ".temp-factory-download");
4089
+ let archive = null;
4093
4090
  try {
4094
- if ((0, import_fs4.existsSync)(tempDir)) (0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
4095
- (0, import_fs4.mkdirSync)(tempDir, { recursive: true });
4096
- const response = await fetchWithRetry(asset.url, {
4097
- headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
4098
- });
4099
- if (!response.ok) {
4100
- result.failureReason = `Download failed (${response.status})`;
4101
- return result;
4102
- }
4103
- const archivePath = (0, import_path4.join)(tempDir, assetName);
4104
- const arrayBuffer = await response.arrayBuffer();
4105
- (0, import_fs4.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
4106
- const stats = (0, import_fs4.statSync)(archivePath);
4107
- if (stats.size < MIN_FILE_SIZE2) {
4108
- result.failureReason = "Downloaded file corrupted (too small)";
4109
- return result;
4110
- }
4111
- const listResult = (0, import_child_process3.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
4112
- if (listResult.status !== 0) {
4113
- result.failureReason = "Failed to read archive contents";
4114
- return result;
4115
- }
4116
- const resolvedTempDir = (0, import_path4.resolve)(tempDir);
4117
- for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
4118
- const entryPath = (0, import_path4.resolve)((0, import_path4.join)(tempDir, entry.replace(/\/$/, "")));
4119
- if (!entryPath.startsWith(resolvedTempDir + import_path4.sep)) {
4120
- result.failureReason = `Archive contains unsafe path: ${entry}`;
4121
- return result;
4122
- }
4123
- }
4124
- const extractResult = (0, import_child_process3.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
4125
- if (extractResult.status !== 0) {
4126
- result.failureReason = "Archive extraction failed";
4127
- return result;
4128
- }
4129
- const extractedEntries = (0, import_fs4.readdirSync)(tempDir);
4091
+ archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-factory-download");
4092
+ const extractedEntries = (0, import_fs7.readdirSync)(archive.extractedRoot);
4130
4093
  const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === rootFolder.toLowerCase());
4131
4094
  if (!extractedFolder) {
4132
4095
  result.failureReason = `No ${rootFolder}/ folder in archive`;
4133
4096
  return result;
4134
4097
  }
4135
- const extractedPath = (0, import_path4.join)(tempDir, extractedFolder);
4136
- const manifestPath = (0, import_path4.join)(extractedPath, "install.json");
4137
- if ((0, import_fs4.existsSync)(manifestPath)) {
4098
+ const extractedPath = (0, import_path7.join)(archive.extractedRoot, extractedFolder);
4099
+ const manifestPath = (0, import_path7.join)(extractedPath, "install.json");
4100
+ if ((0, import_fs7.existsSync)(manifestPath)) {
4138
4101
  await installViaBundle(manifestPath, extractedPath, targetDir, agent, result);
4139
4102
  } else if (agent === "github-copilot") {
4140
4103
  installCopilotFactory(extractedPath, targetDir, result);
@@ -4145,19 +4108,14 @@ async function fetchFactory(token, repo, targetDir, agent) {
4145
4108
  } catch (error) {
4146
4109
  result.failureReason = error instanceof Error ? error.message : String(error);
4147
4110
  } finally {
4148
- if ((0, import_fs4.existsSync)(tempDir)) {
4149
- try {
4150
- (0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
4151
- } catch {
4152
- }
4153
- }
4111
+ archive?.cleanup();
4154
4112
  }
4155
4113
  return result;
4156
4114
  }
4157
4115
  async function installViaBundle(manifestPath, extractedPath, targetDir, agent, result) {
4158
4116
  let manifest;
4159
4117
  try {
4160
- manifest = JSON.parse((0, import_fs4.readFileSync)(manifestPath, "utf-8"));
4118
+ manifest = JSON.parse((0, import_fs7.readFileSync)(manifestPath, "utf-8"));
4161
4119
  } catch (e) {
4162
4120
  throw new Error(`install.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
4163
4121
  }
@@ -4175,43 +4133,43 @@ async function installViaBundle(manifestPath, extractedPath, targetDir, agent, r
4175
4133
  result.filesInstalled = runResult.filesTouched;
4176
4134
  }
4177
4135
  function installClaudeFactory(extractedPath, targetDir, result) {
4178
- const claudeDir = (0, import_path4.join)(targetDir, ".claude");
4179
- const factoryDir = (0, import_path4.join)(targetDir, "factory");
4180
- const agentsSrc = (0, import_path4.join)(extractedPath, "agents");
4181
- if ((0, import_fs4.existsSync)(agentsSrc)) {
4182
- copyDirectory3(agentsSrc, (0, import_path4.join)(claudeDir, "agents"));
4136
+ const claudeDir = (0, import_path7.join)(targetDir, ".claude");
4137
+ const factoryDir = (0, import_path7.join)(targetDir, "factory");
4138
+ const agentsSrc = (0, import_path7.join)(extractedPath, "agents");
4139
+ if ((0, import_fs7.existsSync)(agentsSrc)) {
4140
+ copyDirectory(agentsSrc, (0, import_path7.join)(claudeDir, "agents"));
4183
4141
  result.filesInstalled.push(".claude/agents/");
4184
4142
  }
4185
- const hooksSrc = (0, import_path4.join)(extractedPath, "hooks");
4186
- if ((0, import_fs4.existsSync)(hooksSrc)) {
4187
- copyDirectory3(hooksSrc, (0, import_path4.join)(claudeDir, "hooks"));
4143
+ const hooksSrc = (0, import_path7.join)(extractedPath, "hooks");
4144
+ if ((0, import_fs7.existsSync)(hooksSrc)) {
4145
+ copyDirectory(hooksSrc, (0, import_path7.join)(claudeDir, "hooks"));
4188
4146
  result.filesInstalled.push(".claude/hooks/");
4189
4147
  }
4190
- const skillsSrc = (0, import_path4.join)(extractedPath, "skills");
4191
- if ((0, import_fs4.existsSync)(skillsSrc)) {
4192
- copyDirectory3(skillsSrc, (0, import_path4.join)(claudeDir, "skills"));
4148
+ const skillsSrc = (0, import_path7.join)(extractedPath, "skills");
4149
+ if ((0, import_fs7.existsSync)(skillsSrc)) {
4150
+ copyDirectory(skillsSrc, (0, import_path7.join)(claudeDir, "skills"));
4193
4151
  result.filesInstalled.push(".claude/skills/");
4194
4152
  }
4195
- const workflowSrc = (0, import_path4.join)(extractedPath, "workflow");
4196
- if ((0, import_fs4.existsSync)(workflowSrc)) {
4197
- copyDirectory3(workflowSrc, (0, import_path4.join)(factoryDir, "workflow"));
4153
+ const workflowSrc = (0, import_path7.join)(extractedPath, "workflow");
4154
+ if ((0, import_fs7.existsSync)(workflowSrc)) {
4155
+ copyDirectory(workflowSrc, (0, import_path7.join)(factoryDir, "workflow"));
4198
4156
  result.filesInstalled.push("factory/workflow/");
4199
4157
  }
4200
- const utilsSrc = (0, import_path4.join)(extractedPath, "utils");
4201
- if ((0, import_fs4.existsSync)(utilsSrc)) {
4202
- copyDirectory3(utilsSrc, (0, import_path4.join)(factoryDir, "utils"));
4158
+ const utilsSrc = (0, import_path7.join)(extractedPath, "utils");
4159
+ if ((0, import_fs7.existsSync)(utilsSrc)) {
4160
+ copyDirectory(utilsSrc, (0, import_path7.join)(factoryDir, "utils"));
4203
4161
  result.filesInstalled.push("factory/utils/");
4204
4162
  }
4205
- const docsSrc = (0, import_path4.join)(extractedPath, "docs");
4206
- if ((0, import_fs4.existsSync)(docsSrc)) {
4207
- copyDirectory3(docsSrc, (0, import_path4.join)(factoryDir, "docs"));
4163
+ const docsSrc = (0, import_path7.join)(extractedPath, "docs");
4164
+ if ((0, import_fs7.existsSync)(docsSrc)) {
4165
+ copyDirectory(docsSrc, (0, import_path7.join)(factoryDir, "docs"));
4208
4166
  result.filesInstalled.push("factory/docs/");
4209
4167
  }
4210
- const rootEntries = (0, import_fs4.readdirSync)(extractedPath, { withFileTypes: true });
4168
+ const rootEntries = (0, import_fs7.readdirSync)(extractedPath, { withFileTypes: true });
4211
4169
  for (const entry of rootEntries) {
4212
4170
  if (entry.isFile()) {
4213
- if (!(0, import_fs4.existsSync)(factoryDir)) (0, import_fs4.mkdirSync)(factoryDir, { recursive: true });
4214
- (0, import_fs4.copyFileSync)((0, import_path4.join)(extractedPath, entry.name), (0, import_path4.join)(factoryDir, entry.name));
4171
+ if (!(0, import_fs7.existsSync)(factoryDir)) (0, import_fs7.mkdirSync)(factoryDir, { recursive: true });
4172
+ (0, import_fs7.copyFileSync)((0, import_path7.join)(extractedPath, entry.name), (0, import_path7.join)(factoryDir, entry.name));
4215
4173
  result.filesInstalled.push(`factory/${entry.name}`);
4216
4174
  }
4217
4175
  }
@@ -4219,9 +4177,9 @@ function installClaudeFactory(extractedPath, targetDir, result) {
4219
4177
  result.filesInstalled.push(".claude/settings.json");
4220
4178
  }
4221
4179
  function installCopilotFactory(extractedPath, targetDir, result) {
4222
- const factoryDir = (0, import_path4.join)(targetDir, "factory");
4223
- copyDirectory3(extractedPath, factoryDir);
4224
- const entries = (0, import_fs4.readdirSync)(extractedPath, { withFileTypes: true });
4180
+ const factoryDir = (0, import_path7.join)(targetDir, "factory");
4181
+ copyDirectory(extractedPath, factoryDir);
4182
+ const entries = (0, import_fs7.readdirSync)(extractedPath, { withFileTypes: true });
4225
4183
  for (const entry of entries) {
4226
4184
  if (entry.isDirectory()) {
4227
4185
  result.filesInstalled.push(`factory/${entry.name}/`);
@@ -4231,11 +4189,11 @@ function installCopilotFactory(extractedPath, targetDir, result) {
4231
4189
  }
4232
4190
  }
4233
4191
  function configureSettings(claudeDir) {
4234
- const settingsPath = (0, import_path4.join)(claudeDir, "settings.json");
4192
+ const settingsPath = (0, import_path7.join)(claudeDir, "settings.json");
4235
4193
  let settings = {};
4236
- if ((0, import_fs4.existsSync)(settingsPath)) {
4194
+ if ((0, import_fs7.existsSync)(settingsPath)) {
4237
4195
  try {
4238
- settings = JSON.parse((0, import_fs4.readFileSync)(settingsPath, "utf8"));
4196
+ settings = JSON.parse((0, import_fs7.readFileSync)(settingsPath, "utf8"));
4239
4197
  } catch {
4240
4198
  }
4241
4199
  }
@@ -4286,26 +4244,13 @@ function configureSettings(claudeDir) {
4286
4244
  if (!hasMonitor) settings.hooks.UserPromptSubmit.push(contextMonitorHook);
4287
4245
  }
4288
4246
  settings.statusLine = { type: "command", command: "node .claude/hooks/factory-statusline.cjs" };
4289
- if (!(0, import_fs4.existsSync)(claudeDir)) (0, import_fs4.mkdirSync)(claudeDir, { recursive: true });
4290
- (0, import_fs4.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4291
- }
4292
- function copyDirectory3(source, target) {
4293
- if (!(0, import_fs4.existsSync)(target)) (0, import_fs4.mkdirSync)(target, { recursive: true });
4294
- const entries = (0, import_fs4.readdirSync)(source, { withFileTypes: true });
4295
- for (const entry of entries) {
4296
- const sourcePath = (0, import_path4.join)(source, entry.name);
4297
- const targetPath = (0, import_path4.join)(target, entry.name);
4298
- if (entry.isDirectory()) {
4299
- copyDirectory3(sourcePath, targetPath);
4300
- } else {
4301
- (0, import_fs4.copyFileSync)(sourcePath, targetPath);
4302
- }
4303
- }
4247
+ if (!(0, import_fs7.existsSync)(claudeDir)) (0, import_fs7.mkdirSync)(claudeDir, { recursive: true });
4248
+ (0, import_fs7.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4304
4249
  }
4305
4250
 
4306
4251
  // src/steps/setup/write-instructions.ts
4307
- var import_fs5 = require("fs");
4308
- var import_path5 = require("path");
4252
+ var import_fs8 = require("fs");
4253
+ var import_path8 = require("path");
4309
4254
 
4310
4255
  // src/steps/shared.ts
4311
4256
  function getFilteredMcpConfig(ctx) {
@@ -4340,96 +4285,60 @@ var write_instructions_default = defineStep({
4340
4285
  const includeFactorySection = factoryInstalled && !factoryBundleManaged;
4341
4286
  const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, includeFactorySection);
4342
4287
  const target = getAgentTarget(agent);
4343
- const filePath = (0, import_path5.join)(projectPath, target.instructions);
4344
- const fileDir = (0, import_path5.dirname)(filePath);
4345
- if (!(0, import_fs5.existsSync)(fileDir)) (0, import_fs5.mkdirSync)(fileDir, { recursive: true });
4346
- if (agent === "cursor" && !(0, import_fs5.existsSync)(filePath)) {
4347
- (0, import_fs5.writeFileSync)(filePath, `---
4288
+ const filePath = (0, import_path8.join)(projectPath, target.instructions);
4289
+ const fileDir = (0, import_path8.dirname)(filePath);
4290
+ if (!(0, import_fs8.existsSync)(fileDir)) (0, import_fs8.mkdirSync)(fileDir, { recursive: true });
4291
+ if (agent === "cursor" && !(0, import_fs8.existsSync)(filePath)) {
4292
+ (0, import_fs8.writeFileSync)(filePath, `---
4348
4293
  description: AI development instructions from One-Shot Installer
4349
4294
  alwaysApply: true
4350
4295
  ---
4351
4296
 
4352
4297
  `, "utf-8");
4353
4298
  }
4354
- upsertBlock(filePath, content);
4299
+ upsertMarkerBlock(filePath, MARKER_START, MARKER_END, content, {
4300
+ onCorrupt: (file) => {
4301
+ const fileName = file.split(/[/\\]/).pop() ?? file;
4302
+ const box = [
4303
+ "",
4304
+ red(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),
4305
+ red(" \u2551 \u26A0 INSTRUCTION FILE COULD NOT BE UPDATED \u2551"),
4306
+ red(" \u2551 \u2551"),
4307
+ red(` \u2551 ${fileName} has corrupted markers.`.padEnd(52) + "\u2551"),
4308
+ red(" \u2551 Fix: delete the lines containing \u2551"),
4309
+ red(" \u2551 <!-- one-shot-installer:start --> \u2551"),
4310
+ red(" \u2551 <!-- one-shot-installer:end --> \u2551"),
4311
+ red(" \u2551 Then re-run the installer. \u2551"),
4312
+ red(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),
4313
+ ""
4314
+ ].join("\n");
4315
+ console.log(box);
4316
+ throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
4317
+ }
4318
+ });
4355
4319
  const bundleSnippets = ctx.installed.bundleInstructions ?? {};
4356
4320
  for (const bundleName of Object.keys(bundleSnippets).sort()) {
4357
- upsertBundleBlock(filePath, bundleName, bundleSnippets[bundleName]);
4321
+ upsertMarkerBlock(
4322
+ filePath,
4323
+ `<!-- ${bundleName}:start -->`,
4324
+ `<!-- ${bundleName}:end -->`,
4325
+ bundleSnippets[bundleName]
4326
+ );
4358
4327
  }
4359
4328
  return { status: "success", message: target.instructions };
4360
4329
  }
4361
4330
  });
4362
- function upsertBlock(filePath, block) {
4363
- const marked = `${MARKER_START}
4364
- ${block}
4365
- ${MARKER_END}`;
4366
- if (!(0, import_fs5.existsSync)(filePath)) {
4367
- (0, import_fs5.writeFileSync)(filePath, marked, "utf-8");
4368
- return;
4369
- }
4370
- const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
4371
- const start = existing.indexOf(MARKER_START);
4372
- const end = existing.indexOf(MARKER_END);
4373
- const hasStart = start !== -1;
4374
- const hasEnd = end !== -1;
4375
- if (hasStart && !hasEnd || !hasStart && hasEnd || hasStart && hasEnd && end < start) {
4376
- const fileName = filePath.split(/[/\\]/).pop() ?? filePath;
4377
- const box = [
4378
- "",
4379
- red(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),
4380
- red(" \u2551 \u26A0 INSTRUCTION FILE COULD NOT BE UPDATED \u2551"),
4381
- red(" \u2551 \u2551"),
4382
- red(` \u2551 ${fileName} has corrupted markers.`.padEnd(52) + "\u2551"),
4383
- red(" \u2551 Fix: delete the lines containing \u2551"),
4384
- red(" \u2551 <!-- one-shot-installer:start --> \u2551"),
4385
- red(" \u2551 <!-- one-shot-installer:end --> \u2551"),
4386
- red(" \u2551 Then re-run the installer. \u2551"),
4387
- red(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),
4388
- ""
4389
- ].join("\n");
4390
- console.log(box);
4391
- throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
4392
- }
4393
- if (hasStart && hasEnd) {
4394
- const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
4395
- (0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
4396
- } else {
4397
- const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4398
- (0, import_fs5.writeFileSync)(filePath, existing + separator + marked, "utf-8");
4399
- }
4400
- }
4401
- function upsertBundleBlock(filePath, bundleName, snippet) {
4402
- const startMarker = `<!-- ${bundleName}:start -->`;
4403
- const endMarker = `<!-- ${bundleName}:end -->`;
4404
- const block = `${startMarker}
4405
- ${snippet}
4406
- ${endMarker}`;
4407
- if (!(0, import_fs5.existsSync)(filePath)) {
4408
- (0, import_fs5.writeFileSync)(filePath, block + "\n", "utf-8");
4409
- return;
4410
- }
4411
- const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
4412
- const s = existing.indexOf(startMarker);
4413
- const e = existing.indexOf(endMarker);
4414
- if (s !== -1 && e !== -1 && e > s) {
4415
- const updated = existing.slice(0, s) + block + existing.slice(e + endMarker.length);
4416
- (0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
4417
- } else {
4418
- const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4419
- (0, import_fs5.writeFileSync)(filePath, existing + separator + block + "\n", "utf-8");
4420
- }
4421
- }
4422
4331
  function collectMdFiles(dir) {
4423
- if (!(0, import_fs5.existsSync)(dir)) return [];
4424
- const entries = (0, import_fs5.readdirSync)(dir, { recursive: true });
4332
+ if (!(0, import_fs8.existsSync)(dir)) return [];
4333
+ const entries = (0, import_fs8.readdirSync)(dir, { recursive: true });
4425
4334
  return entries.filter((entry) => {
4426
- const fullPath = (0, import_path5.join)(dir, entry);
4427
- return entry.endsWith(".md") && (0, import_fs5.statSync)(fullPath).isFile();
4335
+ const fullPath = (0, import_path8.join)(dir, entry);
4336
+ return entry.endsWith(".md") && (0, import_fs8.statSync)(fullPath).isFile();
4428
4337
  }).map((entry) => entry.replace(/\\/g, "/")).sort();
4429
4338
  }
4430
4339
  function extractFirstHeading(filePath) {
4431
4340
  try {
4432
- const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
4341
+ const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
4433
4342
  const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
4434
4343
  const match = withoutFrontmatter.match(/^#\s+(.+)/m);
4435
4344
  if (match) {
@@ -4447,16 +4356,16 @@ function formatPathRef(contextPath, description, agent) {
4447
4356
  return `- \`${contextPath}\` \u2014 ${description}`;
4448
4357
  }
4449
4358
  function resolveDomainFolder(domain, contextsDir) {
4450
- if ((0, import_fs5.existsSync)(contextsDir)) {
4359
+ if ((0, import_fs8.existsSync)(contextsDir)) {
4451
4360
  try {
4452
- const entries = (0, import_fs5.readdirSync)(contextsDir);
4361
+ const entries = (0, import_fs8.readdirSync)(contextsDir);
4453
4362
  const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
4454
- if (match) return { folderName: match, folderPath: (0, import_path5.join)(contextsDir, match) };
4363
+ if (match) return { folderName: match, folderPath: (0, import_path8.join)(contextsDir, match) };
4455
4364
  } catch {
4456
4365
  }
4457
4366
  }
4458
4367
  const fallback = domain.toUpperCase();
4459
- return { folderName: fallback, folderPath: (0, import_path5.join)(contextsDir, fallback) };
4368
+ return { folderName: fallback, folderPath: (0, import_path8.join)(contextsDir, fallback) };
4460
4369
  }
4461
4370
  function buildContextRefsSection(domains, agent, contextsDir) {
4462
4371
  const lines2 = [
@@ -4468,34 +4377,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
4468
4377
  let hasAnyFiles = false;
4469
4378
  for (const domain of domains) {
4470
4379
  const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
4471
- if (!(0, import_fs5.existsSync)(domainPath)) continue;
4380
+ if (!(0, import_fs8.existsSync)(domainPath)) continue;
4472
4381
  const domainFiles = [];
4473
- const ctxInstructions = (0, import_path5.join)(domainPath, "context-instructions.md");
4474
- if ((0, import_fs5.existsSync)(ctxInstructions)) {
4382
+ const ctxInstructions = (0, import_path8.join)(domainPath, "context-instructions.md");
4383
+ if ((0, import_fs8.existsSync)(ctxInstructions)) {
4475
4384
  const desc = extractFirstHeading(ctxInstructions);
4476
4385
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
4477
4386
  }
4478
- const instructionsMd = (0, import_path5.join)(domainPath, "core", "instructions.md");
4479
- if ((0, import_fs5.existsSync)(instructionsMd)) {
4387
+ const instructionsMd = (0, import_path8.join)(domainPath, "core", "instructions.md");
4388
+ if ((0, import_fs8.existsSync)(instructionsMd)) {
4480
4389
  const desc = extractFirstHeading(instructionsMd);
4481
4390
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
4482
4391
  }
4483
- const coreDir = (0, import_path5.join)(domainPath, "core");
4484
- if ((0, import_fs5.existsSync)(coreDir)) {
4392
+ const coreDir = (0, import_path8.join)(domainPath, "core");
4393
+ if ((0, import_fs8.existsSync)(coreDir)) {
4485
4394
  const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
4486
4395
  for (const file of coreFiles) {
4487
- const desc = extractFirstHeading((0, import_path5.join)(coreDir, file));
4396
+ const desc = extractFirstHeading((0, import_path8.join)(coreDir, file));
4488
4397
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
4489
4398
  }
4490
4399
  }
4491
- const refDir = (0, import_path5.join)(domainPath, "reference");
4492
- if ((0, import_fs5.existsSync)(refDir)) {
4400
+ const refDir = (0, import_path8.join)(domainPath, "reference");
4401
+ if ((0, import_fs8.existsSync)(refDir)) {
4493
4402
  const refFiles = collectMdFiles(refDir);
4494
4403
  if (refFiles.length > 0) {
4495
4404
  domainFiles.push(``);
4496
4405
  domainFiles.push(`**Reference & cheat sheets:**`);
4497
4406
  for (const file of refFiles) {
4498
- const desc = extractFirstHeading((0, import_path5.join)(refDir, file));
4407
+ const desc = extractFirstHeading((0, import_path8.join)(refDir, file));
4499
4408
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
4500
4409
  }
4501
4410
  }
@@ -4573,7 +4482,7 @@ function buildFactorySection(agent) {
4573
4482
  ].join("\n");
4574
4483
  }
4575
4484
  function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled) {
4576
- const contextsDir = (0, import_path5.join)(projectPath, "_ai-context");
4485
+ const contextsDir = (0, import_path8.join)(projectPath, "_ai-context");
4577
4486
  const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
4578
4487
  lines2.push(buildContextRefsSection(domains, agent, contextsDir));
4579
4488
  if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
@@ -4582,8 +4491,8 @@ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, facto
4582
4491
  }
4583
4492
 
4584
4493
  // src/steps/setup/write-mcp-config.ts
4585
- var import_fs6 = require("fs");
4586
- var import_path6 = require("path");
4494
+ var import_fs9 = require("fs");
4495
+ var import_path9 = require("path");
4587
4496
  var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
4588
4497
  var write_mcp_config_default = defineStep({
4589
4498
  name: "write-mcp-config",
@@ -4592,15 +4501,15 @@ var write_mcp_config_default = defineStep({
4592
4501
  execute: async (ctx) => {
4593
4502
  const filteredMcpConfig = getFilteredMcpConfig(ctx);
4594
4503
  const target = getAgentTarget(ctx.config.agent);
4595
- const mcpJsonPath = (0, import_path6.join)(ctx.config.projectPath, target.mcpConfig);
4504
+ const mcpJsonPath = (0, import_path9.join)(ctx.config.projectPath, target.mcpConfig);
4596
4505
  if (target.mcpDir) {
4597
- const dir = (0, import_path6.join)(ctx.config.projectPath, target.mcpDir);
4598
- if (!(0, import_fs6.existsSync)(dir)) (0, import_fs6.mkdirSync)(dir, { recursive: true });
4506
+ const dir = (0, import_path9.join)(ctx.config.projectPath, target.mcpDir);
4507
+ if (!(0, import_fs9.existsSync)(dir)) (0, import_fs9.mkdirSync)(dir, { recursive: true });
4599
4508
  }
4600
4509
  let existingFile = {};
4601
- if ((0, import_fs6.existsSync)(mcpJsonPath)) {
4510
+ if ((0, import_fs9.existsSync)(mcpJsonPath)) {
4602
4511
  try {
4603
- existingFile = JSON.parse((0, import_fs6.readFileSync)(mcpJsonPath, "utf-8"));
4512
+ existingFile = JSON.parse((0, import_fs9.readFileSync)(mcpJsonPath, "utf-8"));
4604
4513
  } catch {
4605
4514
  const box = [
4606
4515
  "",
@@ -4626,7 +4535,7 @@ var write_mcp_config_default = defineStep({
4626
4535
  }
4627
4536
  const mergedServers = { ...existingServers, ...newServers };
4628
4537
  const outputFile = { ...existingFile, [target.mcpRootKey]: mergedServers };
4629
- (0, import_fs6.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
4538
+ (0, import_fs9.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
4630
4539
  ctx.installed.mcpServersAdded = addedServers;
4631
4540
  return {
4632
4541
  status: "success",
@@ -4636,7 +4545,7 @@ var write_mcp_config_default = defineStep({
4636
4545
  });
4637
4546
 
4638
4547
  // src/steps/setup/run-mcp-install-commands.ts
4639
- var import_child_process4 = require("child_process");
4548
+ var import_child_process3 = require("child_process");
4640
4549
  var run_mcp_install_commands_default = defineStep({
4641
4550
  name: "run-mcp-install-commands",
4642
4551
  label: "Registering MCP servers with Claude Code",
@@ -4658,7 +4567,7 @@ var run_mcp_install_commands_default = defineStep({
4658
4567
  for (const [name, cfg] of Object.entries(filtered)) {
4659
4568
  const cmd = cfg.installCommand;
4660
4569
  if (typeof cmd !== "string" || cmd.length === 0) continue;
4661
- const result = (0, import_child_process4.spawnSync)(cmd, {
4570
+ const result = (0, import_child_process3.spawnSync)(cmd, {
4662
4571
  shell: true,
4663
4572
  stdio: "pipe",
4664
4573
  encoding: "utf-8",
@@ -4672,7 +4581,6 @@ var run_mcp_install_commands_default = defineStep({
4672
4581
  failed.push({ name, reason });
4673
4582
  }
4674
4583
  }
4675
- ctx.installed.mcpInstallCommandsRun = { succeeded, failed };
4676
4584
  if (failed.length === 0) {
4677
4585
  return {
4678
4586
  status: "success",
@@ -4691,13 +4599,12 @@ var run_mcp_install_commands_default = defineStep({
4691
4599
  }
4692
4600
  });
4693
4601
  function isClaudeCliAvailable() {
4694
- const check2 = (0, import_child_process4.spawnSync)("claude --version", { shell: true, stdio: "ignore" });
4602
+ const check2 = (0, import_child_process3.spawnSync)("claude --version", { shell: true, stdio: "ignore" });
4695
4603
  return check2.status === 0;
4696
4604
  }
4697
4605
 
4698
4606
  // src/steps/setup/update-gitignore.ts
4699
- var import_fs7 = require("fs");
4700
- var import_path7 = require("path");
4607
+ var import_path10 = require("path");
4701
4608
  var MARKER_START2 = "# one-shot-installer:start";
4702
4609
  var MARKER_END2 = "# one-shot-installer:end";
4703
4610
  var CORE_GITIGNORE_ENTRIES = [
@@ -4732,40 +4639,52 @@ var update_gitignore_default = defineStep({
4732
4639
  name: "update-gitignore",
4733
4640
  label: "Updating .gitignore",
4734
4641
  execute: async (ctx) => {
4735
- const gitignorePath = (0, import_path7.join)(ctx.config.projectPath, ".gitignore");
4642
+ const gitignorePath = (0, import_path10.join)(ctx.config.projectPath, ".gitignore");
4736
4643
  const factoryInstalled = ctx.installed.factoryInstalled ?? false;
4737
4644
  const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
4738
4645
  const includeLegacyFactory = factoryInstalled && !factoryBundleManaged;
4739
4646
  const entries = includeLegacyFactory ? [...CORE_GITIGNORE_ENTRIES, "", ...LEGACY_FACTORY_GITIGNORE_ENTRIES] : CORE_GITIGNORE_ENTRIES;
4740
- const block = [MARKER_START2, ...entries, MARKER_END2].join("\n");
4741
- if (!(0, import_fs7.existsSync)(gitignorePath)) {
4742
- (0, import_fs7.writeFileSync)(gitignorePath, block + "\n", "utf-8");
4743
- return { status: "success" };
4744
- }
4745
- const existing = (0, import_fs7.readFileSync)(gitignorePath, "utf-8");
4746
- const start = existing.indexOf(MARKER_START2);
4747
- const end = existing.indexOf(MARKER_END2);
4748
- if (start !== -1 && end !== -1 && end > start) {
4749
- const updated = existing.slice(0, start) + block + existing.slice(end + MARKER_END2.length);
4750
- (0, import_fs7.writeFileSync)(gitignorePath, updated, "utf-8");
4751
- } else {
4752
- const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4753
- (0, import_fs7.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
4754
- }
4647
+ upsertMarkerBlock(gitignorePath, MARKER_START2, MARKER_END2, entries.join("\n"));
4755
4648
  return { status: "success" };
4756
4649
  }
4757
4650
  });
4758
4651
 
4759
4652
  // src/steps/setup/write-state.ts
4760
- var import_fs8 = require("fs");
4761
- var import_path8 = require("path");
4653
+ var import_fs10 = require("fs");
4654
+ var import_path11 = require("path");
4762
4655
  var import_os2 = require("os");
4763
- var INSTALLER_VERSION = "0.6.0";
4656
+
4657
+ // package.json
4658
+ var package_default = {
4659
+ name: "one-shot-setup",
4660
+ version: "0.6.0",
4661
+ description: "One-shot installation tool for The Machine toolchain",
4662
+ type: "module",
4663
+ main: "src/index.ts",
4664
+ scripts: {
4665
+ dev: "tsx src/index.ts",
4666
+ build: 'esbuild src/index.ts --bundle --platform=node --banner:js="#!/usr/bin/env node" --outfile=wrapper/bin/installer.js',
4667
+ "publish:wrapper": "cd wrapper && npm publish"
4668
+ },
4669
+ dependencies: {
4670
+ "@inquirer/prompts": "^5.0.0"
4671
+ },
4672
+ devDependencies: {
4673
+ "@types/node": "^20.11.0",
4674
+ esbuild: "latest",
4675
+ tsx: "latest"
4676
+ },
4677
+ engines: {
4678
+ node: ">=18.0.0"
4679
+ }
4680
+ };
4681
+
4682
+ // src/steps/setup/write-state.ts
4764
4683
  var write_state_default = defineStep({
4765
4684
  name: "write-state",
4766
4685
  label: "Saving installation state",
4767
4686
  execute: async (ctx) => {
4768
- const statePath = (0, import_path8.join)(ctx.config.projectPath, ".one-shot-state.json");
4687
+ const statePath = (0, import_path11.join)(ctx.config.projectPath, ".one-shot-state.json");
4769
4688
  const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
4770
4689
  const filteredMcpConfig = Object.fromEntries(
4771
4690
  Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
@@ -4773,7 +4692,7 @@ var write_state_default = defineStep({
4773
4692
  const target = getAgentTarget(ctx.config.agent);
4774
4693
  const state = {
4775
4694
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4776
- installerVersion: INSTALLER_VERSION,
4695
+ installerVersion: package_default.version,
4777
4696
  releaseVersion: ctx.config.releaseVersion,
4778
4697
  contextReleaseVersion: ctx.installed.contextReleaseVersion ?? null,
4779
4698
  projectPath: ctx.config.projectPath,
@@ -4788,10 +4707,10 @@ var write_state_default = defineStep({
4788
4707
  mcpConfig: target.mcpConfig,
4789
4708
  contexts: "_ai-context/",
4790
4709
  factory: ctx.installed.factoryInstalled ? "factory/" : null,
4791
- globalConfig: (0, import_path8.join)((0, import_os2.homedir)(), ".one-shot-installer")
4710
+ globalConfig: (0, import_path11.join)((0, import_os2.homedir)(), ".one-shot-installer")
4792
4711
  }
4793
4712
  };
4794
- (0, import_fs8.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
4713
+ (0, import_fs10.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
4795
4714
  return { status: "success" };
4796
4715
  }
4797
4716
  });
@@ -4806,27 +4725,27 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
4806
4725
  var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
4807
4726
  var green = (text) => `\x1B[32m${text}\x1B[0m`;
4808
4727
  function detectMarkerFileMode(filePath, markerStart) {
4809
- if (!(0, import_fs9.existsSync)(filePath)) return green("create");
4810
- const content = (0, import_fs9.readFileSync)(filePath, "utf-8");
4728
+ if (!(0, import_fs11.existsSync)(filePath)) return green("create");
4729
+ const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
4811
4730
  if (content.includes(markerStart)) return yellow("update");
4812
4731
  return yellow("append");
4813
4732
  }
4814
4733
  function detectMCPFileMode(filePath) {
4815
- if (!(0, import_fs9.existsSync)(filePath)) return green("create");
4734
+ if (!(0, import_fs11.existsSync)(filePath)) return green("create");
4816
4735
  return yellow("merge");
4817
4736
  }
4818
4737
  function detectContextMode(projectPath, domain) {
4819
- const contextDir = (0, import_path9.join)(projectPath, "_ai-context", domain.toUpperCase());
4820
- const contextDirLower = (0, import_path9.join)(projectPath, "_ai-context", domain);
4821
- if ((0, import_fs9.existsSync)(contextDir) || (0, import_fs9.existsSync)(contextDirLower)) return yellow("overwrite");
4738
+ const contextDir = (0, import_path12.join)(projectPath, "_ai-context", domain.toUpperCase());
4739
+ const contextDirLower = (0, import_path12.join)(projectPath, "_ai-context", domain);
4740
+ if ((0, import_fs11.existsSync)(contextDir) || (0, import_fs11.existsSync)(contextDirLower)) return yellow("overwrite");
4822
4741
  return green("create");
4823
4742
  }
4824
4743
  function waitForEnter() {
4825
4744
  const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
4826
- return new Promise((resolve5) => {
4745
+ return new Promise((resolve4) => {
4827
4746
  rl.question(" Press Enter to close...", () => {
4828
4747
  rl.close();
4829
- resolve5();
4748
+ resolve4();
4830
4749
  });
4831
4750
  });
4832
4751
  }
@@ -4916,13 +4835,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4916
4835
  }
4917
4836
  const projectInput = await esm_default4({
4918
4837
  message: "Project directory:",
4919
- default: (0, import_path9.resolve)(process.cwd())
4838
+ default: (0, import_path12.resolve)(process.cwd())
4920
4839
  });
4921
4840
  return {
4922
4841
  technologies: selectedTechnologies,
4923
4842
  agent,
4924
4843
  azureDevOpsOrg,
4925
- projectPath: (0, import_path9.resolve)(projectInput),
4844
+ projectPath: (0, import_path12.resolve)(projectInput),
4926
4845
  baseMcpServers: options.baseMcpServers,
4927
4846
  mcpConfig,
4928
4847
  releaseVersion,
@@ -4936,9 +4855,9 @@ async function previewAndConfirm(config, options) {
4936
4855
  const instructionFile = target.instructions;
4937
4856
  const mcpConfigFile = target.mcpConfig;
4938
4857
  const serverEntries = Object.entries(config.mcpConfig);
4939
- const instructionFilePath = (0, import_path9.join)(config.projectPath, instructionFile);
4940
- const mcpConfigFilePath = (0, import_path9.join)(config.projectPath, mcpConfigFile);
4941
- const gitignorePath = (0, import_path9.join)(config.projectPath, ".gitignore");
4858
+ const instructionFilePath = (0, import_path12.join)(config.projectPath, instructionFile);
4859
+ const mcpConfigFilePath = (0, import_path12.join)(config.projectPath, mcpConfigFile);
4860
+ const gitignorePath = (0, import_path12.join)(config.projectPath, ".gitignore");
4942
4861
  const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
4943
4862
  const mcpMode = detectMCPFileMode(mcpConfigFilePath);
4944
4863
  const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
@@ -4964,7 +4883,7 @@ async function previewAndConfirm(config, options) {
4964
4883
  console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
4965
4884
  console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
4966
4885
  if (config.installFactory) {
4967
- const factoryExists = (0, import_fs9.existsSync)((0, import_path9.join)(config.projectPath, "factory"));
4886
+ const factoryExists = (0, import_fs11.existsSync)((0, import_path12.join)(config.projectPath, "factory"));
4968
4887
  const factoryMode = factoryExists ? yellow("overwrite") : green("create");
4969
4888
  console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
4970
4889
  }
@@ -4983,6 +4902,7 @@ async function previewAndConfirm(config, options) {
4983
4902
  function printSummary(result, config) {
4984
4903
  const failed = result.entries.filter((e) => e.result.status === "failed");
4985
4904
  const succeeded = result.entries.filter((e) => e.result.status === "success");
4905
+ const skipped = result.entries.filter((e) => e.result.status === "skipped");
4986
4906
  const hasErrors = failed.length > 0;
4987
4907
  console.log("\u2500".repeat(48));
4988
4908
  console.log(hasErrors ? " Done (with errors).\n" : " Done.\n");
@@ -4990,6 +4910,10 @@ function printSummary(result, config) {
4990
4910
  const msg = entry.result.message ? ` ${entry.result.message}` : "";
4991
4911
  console.log(` ${entry.label}${msg}`);
4992
4912
  }
4913
+ for (const entry of skipped) {
4914
+ const detail = entry.result.detail ? ` ${entry.result.detail}` : "";
4915
+ console.log(` ${entry.label} (skipped)${detail}`);
4916
+ }
4993
4917
  if (hasErrors) {
4994
4918
  console.log("\n What went wrong:");
4995
4919
  for (const entry of failed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dlw-machine-setup",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "One-shot installer for The Machine toolchain",
5
5
  "bin": {
6
6
  "dlw-machine-setup": "bin/installer.js"