dlw-machine-setup 0.5.18 → 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 +541 -416
  2. package/package.json +1 -1
package/bin/installer.js CHANGED
@@ -3244,14 +3244,32 @@ ${page}${helpTipBottom}${choiceDescription}${import_ansi_escapes3.default.cursor
3244
3244
  });
3245
3245
 
3246
3246
  // src/index.ts
3247
- var import_fs8 = require("fs");
3247
+ var import_fs11 = require("fs");
3248
3248
  var import_readline = require("readline");
3249
- var import_path8 = 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);
@@ -3577,6 +3595,31 @@ function buildMCPConfiguration(selectedItems, baseMcpServers, allMcpServers) {
3577
3595
  return config;
3578
3596
  }
3579
3597
 
3598
+ // src/utils/agent-targets.ts
3599
+ var AGENT_TARGETS = {
3600
+ "claude-code": {
3601
+ instructions: "CLAUDE.md",
3602
+ mcpConfig: ".mcp.json",
3603
+ mcpRootKey: "mcpServers",
3604
+ mcpDir: null
3605
+ },
3606
+ "github-copilot": {
3607
+ instructions: ".github/copilot-instructions.md",
3608
+ mcpConfig: ".vscode/mcp.json",
3609
+ mcpRootKey: "servers",
3610
+ mcpDir: ".vscode"
3611
+ },
3612
+ "cursor": {
3613
+ instructions: ".cursor/rules/instructions.mdc",
3614
+ mcpConfig: ".cursor/mcp.json",
3615
+ mcpRootKey: "mcpServers",
3616
+ mcpDir: ".cursor"
3617
+ }
3618
+ };
3619
+ function getAgentTarget(agent) {
3620
+ return AGENT_TARGETS[agent] ?? AGENT_TARGETS["claude-code"];
3621
+ }
3622
+
3580
3623
  // src/utils/mod.ts
3581
3624
  async function loadWizardOptions(token, repo) {
3582
3625
  const remote = await fetchWizardOptions(token, repo);
@@ -3646,10 +3689,86 @@ __export(steps_exports, {
3646
3689
  });
3647
3690
 
3648
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
3649
3696
  var import_fs2 = require("fs");
3650
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");
3651
3711
  var import_child_process2 = require("child_process");
3652
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
3653
3772
  var fetch_contexts_default = defineStep({
3654
3773
  name: "fetch-contexts",
3655
3774
  label: "Downloading contexts",
@@ -3658,9 +3777,6 @@ var fetch_contexts_default = defineStep({
3658
3777
  const uniqueDomains = [...new Set(ctx.config.technologies.flatMap((p) => p.domains))];
3659
3778
  const domainValues = uniqueDomains.map((d) => d.toLowerCase());
3660
3779
  if (!ctx.contextRepo) {
3661
- for (const domain of domainValues) {
3662
- ctx.installed.domainsFailed = domainValues;
3663
- }
3664
3780
  return {
3665
3781
  status: "failed",
3666
3782
  detail: "Context repo not found (topic: context-data)"
@@ -3668,8 +3784,6 @@ var fetch_contexts_default = defineStep({
3668
3784
  }
3669
3785
  const downloadResult = await fetchContexts(domainValues, ctx.token, ctx.contextRepo, ctx.config.projectPath);
3670
3786
  ctx.installed.domainsInstalled = downloadResult.successful;
3671
- ctx.installed.domainsFailed = downloadResult.failed;
3672
- ctx.installed.failureReasons = downloadResult.failureReasons;
3673
3787
  ctx.installed.contextReleaseVersion = downloadResult.releaseVersion;
3674
3788
  const domainColWidth = Math.max(...domainValues.map((d) => d.length), 8) + 2;
3675
3789
  console.log("");
@@ -3694,24 +3808,9 @@ var fetch_contexts_default = defineStep({
3694
3808
  async function fetchContexts(domains, token, repo, targetDir) {
3695
3809
  const result = { successful: [], failed: [], failureReasons: {} };
3696
3810
  if (domains.length === 0) return result;
3697
- const tarCheck = (0, import_child_process2.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3698
- if (tarCheck.status !== 0) {
3699
- throw new Error("tar command not found. Please ensure tar is installed and available in your PATH.");
3700
- }
3701
- const headers = {
3702
- "Accept": "application/vnd.github+json",
3703
- "Authorization": `Bearer ${token}`
3704
- };
3705
- const releaseResponse = await fetchWithRetry(
3706
- `https://api.github.com/repos/${repo}/releases/latest`,
3707
- { headers }
3708
- );
3709
- if (!releaseResponse.ok) {
3710
- throw new Error(`GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`);
3711
- }
3712
- const releaseData = await releaseResponse.json();
3713
- result.releaseVersion = releaseData.tag_name;
3714
- 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"));
3715
3814
  if (!asset) {
3716
3815
  for (const domain of domains) {
3717
3816
  result.failed.push(domain);
@@ -3719,39 +3818,11 @@ async function fetchContexts(domains, token, repo, targetDir) {
3719
3818
  }
3720
3819
  return result;
3721
3820
  }
3722
- const contextsDir = (0, import_path2.join)(targetDir, "_ai-context");
3723
- 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");
3724
3824
  try {
3725
- if (!(0, import_fs2.existsSync)(contextsDir)) (0, import_fs2.mkdirSync)(contextsDir, { recursive: true });
3726
- if ((0, import_fs2.existsSync)(tempDir)) (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
3727
- (0, import_fs2.mkdirSync)(tempDir, { recursive: true });
3728
- const downloadHeaders = {
3729
- "Accept": "application/octet-stream",
3730
- "Authorization": `Bearer ${token}`
3731
- };
3732
- const response = await fetchWithRetry(asset.url, { headers: downloadHeaders });
3733
- if (!response.ok) {
3734
- throw new Error(`Download failed: ${getReadableError(response.status)}`);
3735
- }
3736
- const archivePath = (0, import_path2.join)(tempDir, asset.name);
3737
- const arrayBuffer = await response.arrayBuffer();
3738
- (0, import_fs2.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
3739
- const stats = (0, import_fs2.statSync)(archivePath);
3740
- if (stats.size < MIN_FILE_SIZE) {
3741
- throw new Error("File corrupted");
3742
- }
3743
- const listResult = (0, import_child_process2.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
3744
- if (listResult.status !== 0) throw new Error("Failed to read archive contents");
3745
- const resolvedTempDir = (0, import_path2.resolve)(tempDir);
3746
- for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
3747
- const entryPath = (0, import_path2.resolve)((0, import_path2.join)(tempDir, entry.replace(/\/$/, "")));
3748
- if (!entryPath.startsWith(resolvedTempDir + import_path2.sep)) {
3749
- throw new Error(`Archive contains unsafe path: ${entry}`);
3750
- }
3751
- }
3752
- const extractResult = (0, import_child_process2.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
3753
- if (extractResult.status !== 0) throw new Error("Archive extraction failed");
3754
- 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);
3755
3826
  for (const domain of domains) {
3756
3827
  try {
3757
3828
  const match = extractedFolders.find((f) => f.toLowerCase() === domain.toLowerCase());
@@ -3760,8 +3831,8 @@ async function fetchContexts(domains, token, repo, targetDir) {
3760
3831
  result.failureReasons[domain] = "Not found in archive";
3761
3832
  continue;
3762
3833
  }
3763
- const domainPath = (0, import_path2.join)(contextsDir, domain);
3764
- 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);
3765
3836
  result.successful.push(domain);
3766
3837
  } catch (error) {
3767
3838
  result.failed.push(domain);
@@ -3769,52 +3840,209 @@ async function fetchContexts(domains, token, repo, targetDir) {
3769
3840
  }
3770
3841
  }
3771
3842
  } finally {
3772
- if ((0, import_fs2.existsSync)(tempDir)) {
3773
- try {
3774
- (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
3775
- } catch {
3776
- }
3843
+ archive.cleanup();
3844
+ }
3845
+ return result;
3846
+ }
3847
+
3848
+ // src/steps/resources/fetch-factory.ts
3849
+ var import_fs7 = require("fs");
3850
+ var import_path7 = require("path");
3851
+
3852
+ // src/bundles/run-bundle.ts
3853
+ var import_fs6 = require("fs");
3854
+ var import_path6 = require("path");
3855
+
3856
+ // src/bundles/types.ts
3857
+ var SCHEMA_VERSION = 1;
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
+
3888
+ // src/bundles/run-bundle.ts
3889
+ var OWNER_KEY = "_bundleOwner";
3890
+ async function runBundle(manifest, ctx) {
3891
+ assertSchemaCompatible(manifest);
3892
+ assertBundleRootExists(ctx);
3893
+ const result = {
3894
+ name: manifest.name,
3895
+ opsExecuted: 0,
3896
+ filesTouched: []
3897
+ };
3898
+ const ops = manifest.targets?.[ctx.agent] ?? [];
3899
+ for (const op of ops) {
3900
+ executeOp(op, manifest.name, ctx, result);
3901
+ result.opsExecuted++;
3902
+ }
3903
+ if (manifest.gitignore?.length) {
3904
+ upsertMarkerBlock(
3905
+ (0, import_path6.join)(ctx.projectPath, ".gitignore"),
3906
+ `# ${manifest.name}:start`,
3907
+ `# ${manifest.name}:end`,
3908
+ manifest.gitignore.join("\n")
3909
+ );
3910
+ result.filesTouched.push(".gitignore");
3911
+ }
3912
+ const snippet = manifest.instructions?.[ctx.agent];
3913
+ if (snippet) {
3914
+ result.instructionsSnippet = snippet;
3915
+ if (!ctx.skipInstructions) {
3916
+ upsertMarkerBlock(
3917
+ (0, import_path6.join)(ctx.projectPath, ctx.instructionsFile),
3918
+ `<!-- ${manifest.name}:start -->`,
3919
+ `<!-- ${manifest.name}:end -->`,
3920
+ snippet
3921
+ );
3922
+ result.filesTouched.push(ctx.instructionsFile);
3777
3923
  }
3778
3924
  }
3779
3925
  return result;
3780
3926
  }
3781
- function copyDirectory(source, target) {
3782
- if (!(0, import_fs2.existsSync)(target)) (0, import_fs2.mkdirSync)(target, { recursive: true });
3783
- const entries = (0, import_fs2.readdirSync)(source, { withFileTypes: true });
3784
- for (const entry of entries) {
3785
- const sourcePath = (0, import_path2.join)(source, entry.name);
3786
- const targetPath = (0, import_path2.join)(target, entry.name);
3787
- if (entry.isDirectory()) {
3788
- copyDirectory(sourcePath, targetPath);
3789
- } else {
3790
- (0, import_fs2.copyFileSync)(sourcePath, targetPath);
3927
+ function executeOp(op, owner, ctx, result) {
3928
+ switch (op.op) {
3929
+ case "copy":
3930
+ return runCopy(op, ctx, result);
3931
+ case "merge-json":
3932
+ return runMergeJson(op, owner, ctx, result);
3933
+ default: {
3934
+ const unknown = op.op;
3935
+ throw new Error(
3936
+ `Bundle "${owner}" uses unknown op "${unknown}". Installer supports: copy, merge-json. Upgrade the installer or check the bundle's schemaVersion.`
3937
+ );
3791
3938
  }
3792
3939
  }
3793
3940
  }
3794
- function getReadableError(status) {
3795
- switch (status) {
3796
- case 401:
3797
- return "Authentication failed. Check your GitHub token.";
3798
- case 403:
3799
- return "Access denied. Verify token permissions and rate limits.";
3800
- case 404:
3801
- return "Resource not found. Verify the repository and release exist.";
3802
- case 429:
3803
- return "Rate limit exceeded. Please wait and try again.";
3804
- case 500:
3805
- case 502:
3806
- case 503:
3807
- return "GitHub server error. Please try again later.";
3808
- default:
3809
- return `Unexpected error (${status})`;
3941
+ function runCopy(op, ctx, result) {
3942
+ const source = resolveBundlePath(op.from, ctx);
3943
+ const target = resolveProjectPath(op.to, ctx);
3944
+ if (!(0, import_fs6.existsSync)(source)) return;
3945
+ if ((0, import_fs6.statSync)(source).isDirectory()) {
3946
+ copyDirectory(source, target);
3947
+ } else {
3948
+ (0, import_fs6.mkdirSync)((0, import_path6.dirname)(target), { recursive: true });
3949
+ (0, import_fs6.copyFileSync)(source, target);
3950
+ }
3951
+ result.filesTouched.push(op.to);
3952
+ }
3953
+ function runMergeJson(op, owner, ctx, result) {
3954
+ const filePath = resolveProjectPath(op.file, ctx);
3955
+ const existing = readJsonOrEmpty(filePath);
3956
+ const stripped = stripOwnedEntries(existing, owner);
3957
+ const tagged = tagEntries(op.patch, owner);
3958
+ const merged = deepMerge2(stripped, tagged);
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");
3961
+ result.filesTouched.push(op.file);
3962
+ }
3963
+ function readJsonOrEmpty(path) {
3964
+ if (!(0, import_fs6.existsSync)(path)) return {};
3965
+ try {
3966
+ return JSON.parse((0, import_fs6.readFileSync)(path, "utf-8"));
3967
+ } catch {
3968
+ return {};
3969
+ }
3970
+ }
3971
+ function isPlainObject2(v) {
3972
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3973
+ }
3974
+ function tagEntries(value, owner) {
3975
+ if (Array.isArray(value)) {
3976
+ return value.map(
3977
+ (item) => isPlainObject2(item) ? { ...item, [OWNER_KEY]: owner } : item
3978
+ );
3979
+ }
3980
+ if (isPlainObject2(value)) {
3981
+ return Object.fromEntries(
3982
+ Object.entries(value).map(([k, v]) => [k, tagEntries(v, owner)])
3983
+ );
3984
+ }
3985
+ return value;
3986
+ }
3987
+ function stripOwnedEntries(value, owner) {
3988
+ if (Array.isArray(value)) {
3989
+ return value.filter((item) => !(isPlainObject2(item) && item[OWNER_KEY] === owner)).map((item) => stripOwnedEntries(item, owner));
3990
+ }
3991
+ if (isPlainObject2(value)) {
3992
+ return Object.fromEntries(
3993
+ Object.entries(value).map(([k, v]) => [k, stripOwnedEntries(v, owner)])
3994
+ );
3995
+ }
3996
+ return value;
3997
+ }
3998
+ function deepMerge2(a, b) {
3999
+ if (isPlainObject2(a) && isPlainObject2(b)) {
4000
+ const out = { ...a };
4001
+ for (const [k, v] of Object.entries(b)) {
4002
+ out[k] = k in a ? deepMerge2(a[k], v) : v;
4003
+ }
4004
+ return out;
4005
+ }
4006
+ if (Array.isArray(a) && Array.isArray(b)) {
4007
+ return [...a, ...b];
4008
+ }
4009
+ return b;
4010
+ }
4011
+ function resolveBundlePath(rel, ctx) {
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)) {
4015
+ throw new Error(`Bundle op "from" escapes bundle root: ${rel}`);
4016
+ }
4017
+ return full;
4018
+ }
4019
+ function resolveProjectPath(rel, ctx) {
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)) {
4023
+ throw new Error(`Bundle op "to" escapes project path: ${rel}`);
4024
+ }
4025
+ return full;
4026
+ }
4027
+ function assertSchemaCompatible(manifest) {
4028
+ if (manifest.schemaVersion !== SCHEMA_VERSION) {
4029
+ throw new Error(
4030
+ `Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${SCHEMA_VERSION}. Upgrade the installer or re-pin the bundle.`
4031
+ );
4032
+ }
4033
+ if (!manifest.name || !/^[a-z][a-z0-9-]*$/.test(manifest.name)) {
4034
+ throw new Error(
4035
+ `Bundle name must match /^[a-z][a-z0-9-]*$/: got "${manifest.name}"`
4036
+ );
4037
+ }
4038
+ }
4039
+ function assertBundleRootExists(ctx) {
4040
+ if (!(0, import_fs6.existsSync)(ctx.bundleRoot) || !(0, import_fs6.statSync)(ctx.bundleRoot).isDirectory()) {
4041
+ throw new Error(`Bundle root not found or not a directory: ${ctx.bundleRoot}`);
3810
4042
  }
3811
4043
  }
3812
4044
 
3813
4045
  // src/steps/resources/fetch-factory.ts
3814
- var import_fs3 = require("fs");
3815
- var import_path3 = require("path");
3816
- var import_child_process3 = require("child_process");
3817
- var MIN_FILE_SIZE2 = 1024;
3818
4046
  function getFactoryAsset(agent) {
3819
4047
  switch (agent) {
3820
4048
  case "github-copilot":
@@ -3830,83 +4058,48 @@ var fetch_factory_default = defineStep({
3830
4058
  execute: async (ctx) => {
3831
4059
  const result = await fetchFactory(ctx.token, ctx.factoryRepo, ctx.config.projectPath, ctx.config.agent);
3832
4060
  ctx.installed.factoryInstalled = result.success;
4061
+ ctx.installed.factoryBundleManaged = result.bundleManaged ?? false;
4062
+ if (result.instructionsSnippet) {
4063
+ const stash = ctx.installed.bundleInstructions ?? {};
4064
+ stash["factory"] = result.instructionsSnippet;
4065
+ ctx.installed.bundleInstructions = stash;
4066
+ }
3833
4067
  if (!result.success) {
3834
4068
  return { status: "failed", detail: result.failureReason };
3835
4069
  }
3836
- return { status: "success", message: result.filesInstalled.join(", ") };
4070
+ const tag = result.bundleManaged ? " (bundle)" : "";
4071
+ return { status: "success", message: result.filesInstalled.join(", ") + tag };
3837
4072
  }
3838
4073
  });
3839
4074
  async function fetchFactory(token, repo, targetDir, agent) {
3840
4075
  const result = { success: false, filesInstalled: [] };
3841
- const tarCheck = (0, import_child_process3.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3842
- if (tarCheck.status !== 0) {
3843
- result.failureReason = "tar command not found";
3844
- return result;
3845
- }
3846
4076
  const { assetName, rootFolder } = getFactoryAsset(agent);
3847
- const headers = {
3848
- "Accept": "application/vnd.github+json",
3849
- "Authorization": `Bearer ${token}`
3850
- };
3851
- const releaseResponse = await fetchWithRetry(
3852
- `https://api.github.com/repos/${repo}/releases/latest`,
3853
- { headers }
3854
- );
3855
- if (!releaseResponse.ok) {
3856
- 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);
3857
4082
  return result;
3858
4083
  }
3859
- const releaseData = await releaseResponse.json();
3860
- const asset = releaseData.assets?.find((a) => a.name === assetName);
4084
+ const asset = release.assets.find((a) => a.name === assetName);
3861
4085
  if (!asset) {
3862
4086
  result.failureReason = `${assetName} not found in release`;
3863
4087
  return result;
3864
4088
  }
3865
- const tempDir = (0, import_path3.join)(targetDir, ".temp-factory-download");
4089
+ let archive = null;
3866
4090
  try {
3867
- if ((0, import_fs3.existsSync)(tempDir)) (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
3868
- (0, import_fs3.mkdirSync)(tempDir, { recursive: true });
3869
- const response = await fetchWithRetry(asset.url, {
3870
- headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
3871
- });
3872
- if (!response.ok) {
3873
- result.failureReason = `Download failed (${response.status})`;
3874
- return result;
3875
- }
3876
- const archivePath = (0, import_path3.join)(tempDir, assetName);
3877
- const arrayBuffer = await response.arrayBuffer();
3878
- (0, import_fs3.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
3879
- const stats = (0, import_fs3.statSync)(archivePath);
3880
- if (stats.size < MIN_FILE_SIZE2) {
3881
- result.failureReason = "Downloaded file corrupted (too small)";
3882
- return result;
3883
- }
3884
- const listResult = (0, import_child_process3.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
3885
- if (listResult.status !== 0) {
3886
- result.failureReason = "Failed to read archive contents";
3887
- return result;
3888
- }
3889
- const resolvedTempDir = (0, import_path3.resolve)(tempDir);
3890
- for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
3891
- const entryPath = (0, import_path3.resolve)((0, import_path3.join)(tempDir, entry.replace(/\/$/, "")));
3892
- if (!entryPath.startsWith(resolvedTempDir + import_path3.sep)) {
3893
- result.failureReason = `Archive contains unsafe path: ${entry}`;
3894
- return result;
3895
- }
3896
- }
3897
- const extractResult = (0, import_child_process3.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
3898
- if (extractResult.status !== 0) {
3899
- result.failureReason = "Archive extraction failed";
3900
- return result;
3901
- }
3902
- const extractedEntries = (0, import_fs3.readdirSync)(tempDir);
4091
+ archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-factory-download");
4092
+ const extractedEntries = (0, import_fs7.readdirSync)(archive.extractedRoot);
3903
4093
  const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === rootFolder.toLowerCase());
3904
4094
  if (!extractedFolder) {
3905
4095
  result.failureReason = `No ${rootFolder}/ folder in archive`;
3906
4096
  return result;
3907
4097
  }
3908
- const extractedPath = (0, import_path3.join)(tempDir, extractedFolder);
3909
- if (agent === "github-copilot") {
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)) {
4101
+ await installViaBundle(manifestPath, extractedPath, targetDir, agent, result);
4102
+ } else if (agent === "github-copilot") {
3910
4103
  installCopilotFactory(extractedPath, targetDir, result);
3911
4104
  } else {
3912
4105
  installClaudeFactory(extractedPath, targetDir, result);
@@ -3915,53 +4108,68 @@ async function fetchFactory(token, repo, targetDir, agent) {
3915
4108
  } catch (error) {
3916
4109
  result.failureReason = error instanceof Error ? error.message : String(error);
3917
4110
  } finally {
3918
- if ((0, import_fs3.existsSync)(tempDir)) {
3919
- try {
3920
- (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
3921
- } catch {
3922
- }
3923
- }
4111
+ archive?.cleanup();
3924
4112
  }
3925
4113
  return result;
3926
4114
  }
4115
+ async function installViaBundle(manifestPath, extractedPath, targetDir, agent, result) {
4116
+ let manifest;
4117
+ try {
4118
+ manifest = JSON.parse((0, import_fs7.readFileSync)(manifestPath, "utf-8"));
4119
+ } catch (e) {
4120
+ throw new Error(`install.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
4121
+ }
4122
+ const target = getAgentTarget(agent);
4123
+ const runResult = await runBundle(manifest, {
4124
+ bundleRoot: extractedPath,
4125
+ projectPath: targetDir,
4126
+ agent,
4127
+ instructionsFile: target.instructions,
4128
+ skipInstructions: true
4129
+ // write-instructions.ts commits the snippet for correct ordering
4130
+ });
4131
+ result.bundleManaged = true;
4132
+ result.instructionsSnippet = runResult.instructionsSnippet;
4133
+ result.filesInstalled = runResult.filesTouched;
4134
+ }
3927
4135
  function installClaudeFactory(extractedPath, targetDir, result) {
3928
- const claudeDir = (0, import_path3.join)(targetDir, ".claude");
3929
- const factoryDir = (0, import_path3.join)(targetDir, "factory");
3930
- const agentsSrc = (0, import_path3.join)(extractedPath, "agents");
3931
- if ((0, import_fs3.existsSync)(agentsSrc)) {
3932
- copyDirectory2(agentsSrc, (0, import_path3.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"));
3933
4141
  result.filesInstalled.push(".claude/agents/");
3934
4142
  }
3935
- const hooksSrc = (0, import_path3.join)(extractedPath, "hooks");
3936
- if ((0, import_fs3.existsSync)(hooksSrc)) {
3937
- copyDirectory2(hooksSrc, (0, import_path3.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"));
3938
4146
  result.filesInstalled.push(".claude/hooks/");
3939
4147
  }
3940
- const skillsSrc = (0, import_path3.join)(extractedPath, "skills");
3941
- if ((0, import_fs3.existsSync)(skillsSrc)) {
3942
- copyDirectory2(skillsSrc, (0, import_path3.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"));
3943
4151
  result.filesInstalled.push(".claude/skills/");
3944
4152
  }
3945
- const workflowSrc = (0, import_path3.join)(extractedPath, "workflow");
3946
- if ((0, import_fs3.existsSync)(workflowSrc)) {
3947
- copyDirectory2(workflowSrc, (0, import_path3.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"));
3948
4156
  result.filesInstalled.push("factory/workflow/");
3949
4157
  }
3950
- const utilsSrc = (0, import_path3.join)(extractedPath, "utils");
3951
- if ((0, import_fs3.existsSync)(utilsSrc)) {
3952
- copyDirectory2(utilsSrc, (0, import_path3.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"));
3953
4161
  result.filesInstalled.push("factory/utils/");
3954
4162
  }
3955
- const docsSrc = (0, import_path3.join)(extractedPath, "docs");
3956
- if ((0, import_fs3.existsSync)(docsSrc)) {
3957
- copyDirectory2(docsSrc, (0, import_path3.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"));
3958
4166
  result.filesInstalled.push("factory/docs/");
3959
4167
  }
3960
- const rootEntries = (0, import_fs3.readdirSync)(extractedPath, { withFileTypes: true });
4168
+ const rootEntries = (0, import_fs7.readdirSync)(extractedPath, { withFileTypes: true });
3961
4169
  for (const entry of rootEntries) {
3962
4170
  if (entry.isFile()) {
3963
- if (!(0, import_fs3.existsSync)(factoryDir)) (0, import_fs3.mkdirSync)(factoryDir, { recursive: true });
3964
- (0, import_fs3.copyFileSync)((0, import_path3.join)(extractedPath, entry.name), (0, import_path3.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));
3965
4173
  result.filesInstalled.push(`factory/${entry.name}`);
3966
4174
  }
3967
4175
  }
@@ -3969,9 +4177,9 @@ function installClaudeFactory(extractedPath, targetDir, result) {
3969
4177
  result.filesInstalled.push(".claude/settings.json");
3970
4178
  }
3971
4179
  function installCopilotFactory(extractedPath, targetDir, result) {
3972
- const factoryDir = (0, import_path3.join)(targetDir, "factory");
3973
- copyDirectory2(extractedPath, factoryDir);
3974
- const entries = (0, import_fs3.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 });
3975
4183
  for (const entry of entries) {
3976
4184
  if (entry.isDirectory()) {
3977
4185
  result.filesInstalled.push(`factory/${entry.name}/`);
@@ -3981,11 +4189,11 @@ function installCopilotFactory(extractedPath, targetDir, result) {
3981
4189
  }
3982
4190
  }
3983
4191
  function configureSettings(claudeDir) {
3984
- const settingsPath = (0, import_path3.join)(claudeDir, "settings.json");
4192
+ const settingsPath = (0, import_path7.join)(claudeDir, "settings.json");
3985
4193
  let settings = {};
3986
- if ((0, import_fs3.existsSync)(settingsPath)) {
4194
+ if ((0, import_fs7.existsSync)(settingsPath)) {
3987
4195
  try {
3988
- settings = JSON.parse((0, import_fs3.readFileSync)(settingsPath, "utf8"));
4196
+ settings = JSON.parse((0, import_fs7.readFileSync)(settingsPath, "utf8"));
3989
4197
  } catch {
3990
4198
  }
3991
4199
  }
@@ -4036,26 +4244,13 @@ function configureSettings(claudeDir) {
4036
4244
  if (!hasMonitor) settings.hooks.UserPromptSubmit.push(contextMonitorHook);
4037
4245
  }
4038
4246
  settings.statusLine = { type: "command", command: "node .claude/hooks/factory-statusline.cjs" };
4039
- if (!(0, import_fs3.existsSync)(claudeDir)) (0, import_fs3.mkdirSync)(claudeDir, { recursive: true });
4040
- (0, import_fs3.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4041
- }
4042
- function copyDirectory2(source, target) {
4043
- if (!(0, import_fs3.existsSync)(target)) (0, import_fs3.mkdirSync)(target, { recursive: true });
4044
- const entries = (0, import_fs3.readdirSync)(source, { withFileTypes: true });
4045
- for (const entry of entries) {
4046
- const sourcePath = (0, import_path3.join)(source, entry.name);
4047
- const targetPath = (0, import_path3.join)(target, entry.name);
4048
- if (entry.isDirectory()) {
4049
- copyDirectory2(sourcePath, targetPath);
4050
- } else {
4051
- (0, import_fs3.copyFileSync)(sourcePath, targetPath);
4052
- }
4053
- }
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");
4054
4249
  }
4055
4250
 
4056
4251
  // src/steps/setup/write-instructions.ts
4057
- var import_fs4 = require("fs");
4058
- var import_path4 = require("path");
4252
+ var import_fs8 = require("fs");
4253
+ var import_path8 = require("path");
4059
4254
 
4060
4255
  // src/steps/shared.ts
4061
4256
  function getFilteredMcpConfig(ctx) {
@@ -4079,106 +4274,71 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
4079
4274
  var write_instructions_default = defineStep({
4080
4275
  name: "write-instructions",
4081
4276
  label: "Writing instruction file",
4082
- when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || ctx.installed.factoryInstalled,
4277
+ when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || ctx.installed.factoryInstalled || Object.keys(ctx.installed.bundleInstructions ?? {}).length > 0,
4083
4278
  execute: async (ctx) => {
4084
4279
  const filteredMcpConfig = getFilteredMcpConfig(ctx);
4085
4280
  const domains = ctx.installed.domainsInstalled ?? [];
4086
4281
  const agent = ctx.config.agent;
4087
4282
  const projectPath = ctx.config.projectPath;
4088
4283
  const factoryInstalled = ctx.installed.factoryInstalled ?? false;
4089
- const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, factoryInstalled);
4090
- switch (agent) {
4091
- case "claude-code": {
4092
- upsertBlock((0, import_path4.join)(projectPath, "CLAUDE.md"), content);
4093
- break;
4094
- }
4095
- case "github-copilot": {
4096
- const githubDir = (0, import_path4.join)(projectPath, ".github");
4097
- if (!(0, import_fs4.existsSync)(githubDir)) (0, import_fs4.mkdirSync)(githubDir, { recursive: true });
4098
- upsertBlock((0, import_path4.join)(githubDir, "copilot-instructions.md"), content);
4099
- break;
4100
- }
4101
- case "cursor": {
4102
- const cursorDir = (0, import_path4.join)(projectPath, ".cursor", "rules");
4103
- if (!(0, import_fs4.existsSync)(cursorDir)) (0, import_fs4.mkdirSync)(cursorDir, { recursive: true });
4104
- const filePath = (0, import_path4.join)(cursorDir, "instructions.mdc");
4105
- if (!(0, import_fs4.existsSync)(filePath)) {
4106
- (0, import_fs4.writeFileSync)(filePath, `---
4284
+ const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
4285
+ const includeFactorySection = factoryInstalled && !factoryBundleManaged;
4286
+ const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, includeFactorySection);
4287
+ const target = getAgentTarget(agent);
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, `---
4107
4293
  description: AI development instructions from One-Shot Installer
4108
4294
  alwaysApply: true
4109
4295
  ---
4110
4296
 
4111
4297
  `, "utf-8");
4112
- }
4113
- upsertBlock(filePath, content);
4114
- break;
4298
+ }
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`);
4115
4317
  }
4318
+ });
4319
+ const bundleSnippets = ctx.installed.bundleInstructions ?? {};
4320
+ for (const bundleName of Object.keys(bundleSnippets).sort()) {
4321
+ upsertMarkerBlock(
4322
+ filePath,
4323
+ `<!-- ${bundleName}:start -->`,
4324
+ `<!-- ${bundleName}:end -->`,
4325
+ bundleSnippets[bundleName]
4326
+ );
4116
4327
  }
4117
- return { status: "success", message: getInstructionFilePath(agent) };
4328
+ return { status: "success", message: target.instructions };
4118
4329
  }
4119
4330
  });
4120
- function getInstructionFilePath(agent) {
4121
- switch (agent) {
4122
- case "claude-code":
4123
- return "CLAUDE.md";
4124
- case "github-copilot":
4125
- return ".github/copilot-instructions.md";
4126
- case "cursor":
4127
- return ".cursor/rules/instructions.mdc";
4128
- default:
4129
- return "CLAUDE.md";
4130
- }
4131
- }
4132
- function upsertBlock(filePath, block) {
4133
- const marked = `${MARKER_START}
4134
- ${block}
4135
- ${MARKER_END}`;
4136
- if (!(0, import_fs4.existsSync)(filePath)) {
4137
- (0, import_fs4.writeFileSync)(filePath, marked, "utf-8");
4138
- return;
4139
- }
4140
- const existing = (0, import_fs4.readFileSync)(filePath, "utf-8");
4141
- const start = existing.indexOf(MARKER_START);
4142
- const end = existing.indexOf(MARKER_END);
4143
- const hasStart = start !== -1;
4144
- const hasEnd = end !== -1;
4145
- if (hasStart && !hasEnd || !hasStart && hasEnd || hasStart && hasEnd && end < start) {
4146
- const fileName = filePath.split(/[/\\]/).pop() ?? filePath;
4147
- const box = [
4148
- "",
4149
- 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"),
4150
- red(" \u2551 \u26A0 INSTRUCTION FILE COULD NOT BE UPDATED \u2551"),
4151
- red(" \u2551 \u2551"),
4152
- red(` \u2551 ${fileName} has corrupted markers.`.padEnd(52) + "\u2551"),
4153
- red(" \u2551 Fix: delete the lines containing \u2551"),
4154
- red(" \u2551 <!-- one-shot-installer:start --> \u2551"),
4155
- red(" \u2551 <!-- one-shot-installer:end --> \u2551"),
4156
- red(" \u2551 Then re-run the installer. \u2551"),
4157
- 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"),
4158
- ""
4159
- ].join("\n");
4160
- console.log(box);
4161
- throw new Error(`Corrupted markers in ${fileName} \u2014 fix manually and re-run`);
4162
- }
4163
- if (hasStart && hasEnd) {
4164
- const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
4165
- (0, import_fs4.writeFileSync)(filePath, updated, "utf-8");
4166
- } else {
4167
- const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4168
- (0, import_fs4.writeFileSync)(filePath, existing + separator + marked, "utf-8");
4169
- }
4170
- }
4171
4331
  function collectMdFiles(dir) {
4172
- if (!(0, import_fs4.existsSync)(dir)) return [];
4173
- const entries = (0, import_fs4.readdirSync)(dir, { recursive: true });
4332
+ if (!(0, import_fs8.existsSync)(dir)) return [];
4333
+ const entries = (0, import_fs8.readdirSync)(dir, { recursive: true });
4174
4334
  return entries.filter((entry) => {
4175
- const fullPath = (0, import_path4.join)(dir, entry);
4176
- return entry.endsWith(".md") && (0, import_fs4.statSync)(fullPath).isFile();
4335
+ const fullPath = (0, import_path8.join)(dir, entry);
4336
+ return entry.endsWith(".md") && (0, import_fs8.statSync)(fullPath).isFile();
4177
4337
  }).map((entry) => entry.replace(/\\/g, "/")).sort();
4178
4338
  }
4179
4339
  function extractFirstHeading(filePath) {
4180
4340
  try {
4181
- const content = (0, import_fs4.readFileSync)(filePath, "utf-8");
4341
+ const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
4182
4342
  const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
4183
4343
  const match = withoutFrontmatter.match(/^#\s+(.+)/m);
4184
4344
  if (match) {
@@ -4196,16 +4356,16 @@ function formatPathRef(contextPath, description, agent) {
4196
4356
  return `- \`${contextPath}\` \u2014 ${description}`;
4197
4357
  }
4198
4358
  function resolveDomainFolder(domain, contextsDir) {
4199
- if ((0, import_fs4.existsSync)(contextsDir)) {
4359
+ if ((0, import_fs8.existsSync)(contextsDir)) {
4200
4360
  try {
4201
- const entries = (0, import_fs4.readdirSync)(contextsDir);
4361
+ const entries = (0, import_fs8.readdirSync)(contextsDir);
4202
4362
  const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
4203
- if (match) return { folderName: match, folderPath: (0, import_path4.join)(contextsDir, match) };
4363
+ if (match) return { folderName: match, folderPath: (0, import_path8.join)(contextsDir, match) };
4204
4364
  } catch {
4205
4365
  }
4206
4366
  }
4207
4367
  const fallback = domain.toUpperCase();
4208
- return { folderName: fallback, folderPath: (0, import_path4.join)(contextsDir, fallback) };
4368
+ return { folderName: fallback, folderPath: (0, import_path8.join)(contextsDir, fallback) };
4209
4369
  }
4210
4370
  function buildContextRefsSection(domains, agent, contextsDir) {
4211
4371
  const lines2 = [
@@ -4217,34 +4377,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
4217
4377
  let hasAnyFiles = false;
4218
4378
  for (const domain of domains) {
4219
4379
  const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
4220
- if (!(0, import_fs4.existsSync)(domainPath)) continue;
4380
+ if (!(0, import_fs8.existsSync)(domainPath)) continue;
4221
4381
  const domainFiles = [];
4222
- const ctxInstructions = (0, import_path4.join)(domainPath, "context-instructions.md");
4223
- if ((0, import_fs4.existsSync)(ctxInstructions)) {
4382
+ const ctxInstructions = (0, import_path8.join)(domainPath, "context-instructions.md");
4383
+ if ((0, import_fs8.existsSync)(ctxInstructions)) {
4224
4384
  const desc = extractFirstHeading(ctxInstructions);
4225
4385
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
4226
4386
  }
4227
- const instructionsMd = (0, import_path4.join)(domainPath, "core", "instructions.md");
4228
- if ((0, import_fs4.existsSync)(instructionsMd)) {
4387
+ const instructionsMd = (0, import_path8.join)(domainPath, "core", "instructions.md");
4388
+ if ((0, import_fs8.existsSync)(instructionsMd)) {
4229
4389
  const desc = extractFirstHeading(instructionsMd);
4230
4390
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
4231
4391
  }
4232
- const coreDir = (0, import_path4.join)(domainPath, "core");
4233
- if ((0, import_fs4.existsSync)(coreDir)) {
4392
+ const coreDir = (0, import_path8.join)(domainPath, "core");
4393
+ if ((0, import_fs8.existsSync)(coreDir)) {
4234
4394
  const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
4235
4395
  for (const file of coreFiles) {
4236
- const desc = extractFirstHeading((0, import_path4.join)(coreDir, file));
4396
+ const desc = extractFirstHeading((0, import_path8.join)(coreDir, file));
4237
4397
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
4238
4398
  }
4239
4399
  }
4240
- const refDir = (0, import_path4.join)(domainPath, "reference");
4241
- if ((0, import_fs4.existsSync)(refDir)) {
4400
+ const refDir = (0, import_path8.join)(domainPath, "reference");
4401
+ if ((0, import_fs8.existsSync)(refDir)) {
4242
4402
  const refFiles = collectMdFiles(refDir);
4243
4403
  if (refFiles.length > 0) {
4244
4404
  domainFiles.push(``);
4245
4405
  domainFiles.push(`**Reference & cheat sheets:**`);
4246
4406
  for (const file of refFiles) {
4247
- const desc = extractFirstHeading((0, import_path4.join)(refDir, file));
4407
+ const desc = extractFirstHeading((0, import_path8.join)(refDir, file));
4248
4408
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
4249
4409
  }
4250
4410
  }
@@ -4322,7 +4482,7 @@ function buildFactorySection(agent) {
4322
4482
  ].join("\n");
4323
4483
  }
4324
4484
  function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled) {
4325
- const contextsDir = (0, import_path4.join)(projectPath, "_ai-context");
4485
+ const contextsDir = (0, import_path8.join)(projectPath, "_ai-context");
4326
4486
  const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
4327
4487
  lines2.push(buildContextRefsSection(domains, agent, contextsDir));
4328
4488
  if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
@@ -4331,8 +4491,8 @@ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, facto
4331
4491
  }
4332
4492
 
4333
4493
  // src/steps/setup/write-mcp-config.ts
4334
- var import_fs5 = require("fs");
4335
- var import_path5 = require("path");
4494
+ var import_fs9 = require("fs");
4495
+ var import_path9 = require("path");
4336
4496
  var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
4337
4497
  var write_mcp_config_default = defineStep({
4338
4498
  name: "write-mcp-config",
@@ -4340,23 +4500,23 @@ var write_mcp_config_default = defineStep({
4340
4500
  when: (ctx) => Object.keys(ctx.config.mcpConfig).length > 0,
4341
4501
  execute: async (ctx) => {
4342
4502
  const filteredMcpConfig = getFilteredMcpConfig(ctx);
4343
- const target = getAgentMCPTarget(ctx.config.agent);
4344
- const mcpJsonPath = (0, import_path5.join)(ctx.config.projectPath, target.filePath);
4345
- if (target.dir) {
4346
- const dir = (0, import_path5.join)(ctx.config.projectPath, target.dir);
4347
- if (!(0, import_fs5.existsSync)(dir)) (0, import_fs5.mkdirSync)(dir, { recursive: true });
4503
+ const target = getAgentTarget(ctx.config.agent);
4504
+ const mcpJsonPath = (0, import_path9.join)(ctx.config.projectPath, target.mcpConfig);
4505
+ if (target.mcpDir) {
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 });
4348
4508
  }
4349
4509
  let existingFile = {};
4350
- if ((0, import_fs5.existsSync)(mcpJsonPath)) {
4510
+ if ((0, import_fs9.existsSync)(mcpJsonPath)) {
4351
4511
  try {
4352
- existingFile = JSON.parse((0, import_fs5.readFileSync)(mcpJsonPath, "utf-8"));
4512
+ existingFile = JSON.parse((0, import_fs9.readFileSync)(mcpJsonPath, "utf-8"));
4353
4513
  } catch {
4354
4514
  const box = [
4355
4515
  "",
4356
4516
  red2(" \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"),
4357
4517
  red2(" \u2551 \u26A0 MCP CONFIG HAS INVALID JSON \u2551"),
4358
4518
  red2(" \u2551 \u2551"),
4359
- red2(` \u2551 ${target.filePath} could not be parsed.`.padEnd(52) + "\u2551"),
4519
+ red2(` \u2551 ${target.mcpConfig} could not be parsed.`.padEnd(52) + "\u2551"),
4360
4520
  red2(" \u2551 Existing MCP servers may be lost. \u2551"),
4361
4521
  red2(" \u2551 Check the file after installation completes. \u2551"),
4362
4522
  red2(" \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"),
@@ -4366,7 +4526,7 @@ var write_mcp_config_default = defineStep({
4366
4526
  }
4367
4527
  }
4368
4528
  const addedServers = [];
4369
- const existingServers = existingFile[target.rootKey] ?? {};
4529
+ const existingServers = existingFile[target.mcpRootKey] ?? {};
4370
4530
  const newServers = {};
4371
4531
  for (const [serverName, serverConfig] of Object.entries(filteredMcpConfig)) {
4372
4532
  const { description, useWhen, active, installCommand, ...mcpFields } = serverConfig;
@@ -4374,8 +4534,8 @@ var write_mcp_config_default = defineStep({
4374
4534
  addedServers.push(serverName);
4375
4535
  }
4376
4536
  const mergedServers = { ...existingServers, ...newServers };
4377
- const outputFile = { ...existingFile, [target.rootKey]: mergedServers };
4378
- (0, import_fs5.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
4537
+ const outputFile = { ...existingFile, [target.mcpRootKey]: mergedServers };
4538
+ (0, import_fs9.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
4379
4539
  ctx.installed.mcpServersAdded = addedServers;
4380
4540
  return {
4381
4541
  status: "success",
@@ -4383,20 +4543,9 @@ var write_mcp_config_default = defineStep({
4383
4543
  };
4384
4544
  }
4385
4545
  });
4386
- function getAgentMCPTarget(agent) {
4387
- switch (agent) {
4388
- case "claude-code":
4389
- return { filePath: ".mcp.json", rootKey: "mcpServers", dir: null };
4390
- case "cursor":
4391
- return { filePath: ".cursor/mcp.json", rootKey: "mcpServers", dir: ".cursor" };
4392
- case "github-copilot":
4393
- default:
4394
- return { filePath: ".vscode/mcp.json", rootKey: "servers", dir: ".vscode" };
4395
- }
4396
- }
4397
4546
 
4398
4547
  // src/steps/setup/run-mcp-install-commands.ts
4399
- var import_child_process4 = require("child_process");
4548
+ var import_child_process3 = require("child_process");
4400
4549
  var run_mcp_install_commands_default = defineStep({
4401
4550
  name: "run-mcp-install-commands",
4402
4551
  label: "Registering MCP servers with Claude Code",
@@ -4418,7 +4567,7 @@ var run_mcp_install_commands_default = defineStep({
4418
4567
  for (const [name, cfg] of Object.entries(filtered)) {
4419
4568
  const cmd = cfg.installCommand;
4420
4569
  if (typeof cmd !== "string" || cmd.length === 0) continue;
4421
- const result = (0, import_child_process4.spawnSync)(cmd, {
4570
+ const result = (0, import_child_process3.spawnSync)(cmd, {
4422
4571
  shell: true,
4423
4572
  stdio: "pipe",
4424
4573
  encoding: "utf-8",
@@ -4432,7 +4581,6 @@ var run_mcp_install_commands_default = defineStep({
4432
4581
  failed.push({ name, reason });
4433
4582
  }
4434
4583
  }
4435
- ctx.installed.mcpInstallCommandsRun = { succeeded, failed };
4436
4584
  if (failed.length === 0) {
4437
4585
  return {
4438
4586
  status: "success",
@@ -4451,16 +4599,15 @@ var run_mcp_install_commands_default = defineStep({
4451
4599
  }
4452
4600
  });
4453
4601
  function isClaudeCliAvailable() {
4454
- 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" });
4455
4603
  return check2.status === 0;
4456
4604
  }
4457
4605
 
4458
4606
  // src/steps/setup/update-gitignore.ts
4459
- var import_fs6 = require("fs");
4460
- var import_path6 = require("path");
4607
+ var import_path10 = require("path");
4461
4608
  var MARKER_START2 = "# one-shot-installer:start";
4462
4609
  var MARKER_END2 = "# one-shot-installer:end";
4463
- var GITIGNORE_ENTRIES = [
4610
+ var CORE_GITIGNORE_ENTRIES = [
4464
4611
  "# One-Shot installer generated files",
4465
4612
  ".one-shot-state.json",
4466
4613
  ".mcp.json",
@@ -4472,8 +4619,9 @@ var GITIGNORE_ENTRIES = [
4472
4619
  ".claude/agents/",
4473
4620
  ".claude/agent-memory/",
4474
4621
  ".claude/plans/",
4475
- ".claude/settings.local.json",
4476
- "",
4622
+ ".claude/settings.local.json"
4623
+ ];
4624
+ var LEGACY_FACTORY_GITIGNORE_ENTRIES = [
4477
4625
  "# Factory runtime state (generated by workflow, not committed)",
4478
4626
  "factory/STATE.md",
4479
4627
  "factory/PROJECT.md",
@@ -4491,43 +4639,60 @@ var update_gitignore_default = defineStep({
4491
4639
  name: "update-gitignore",
4492
4640
  label: "Updating .gitignore",
4493
4641
  execute: async (ctx) => {
4494
- const gitignorePath = (0, import_path6.join)(ctx.config.projectPath, ".gitignore");
4495
- const block = [MARKER_START2, ...GITIGNORE_ENTRIES, MARKER_END2].join("\n");
4496
- if (!(0, import_fs6.existsSync)(gitignorePath)) {
4497
- (0, import_fs6.writeFileSync)(gitignorePath, block + "\n", "utf-8");
4498
- return { status: "success" };
4499
- }
4500
- const existing = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
4501
- const start = existing.indexOf(MARKER_START2);
4502
- const end = existing.indexOf(MARKER_END2);
4503
- if (start !== -1 && end !== -1 && end > start) {
4504
- const updated = existing.slice(0, start) + block + existing.slice(end + MARKER_END2.length);
4505
- (0, import_fs6.writeFileSync)(gitignorePath, updated, "utf-8");
4506
- } else {
4507
- const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4508
- (0, import_fs6.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
4509
- }
4642
+ const gitignorePath = (0, import_path10.join)(ctx.config.projectPath, ".gitignore");
4643
+ const factoryInstalled = ctx.installed.factoryInstalled ?? false;
4644
+ const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
4645
+ const includeLegacyFactory = factoryInstalled && !factoryBundleManaged;
4646
+ const entries = includeLegacyFactory ? [...CORE_GITIGNORE_ENTRIES, "", ...LEGACY_FACTORY_GITIGNORE_ENTRIES] : CORE_GITIGNORE_ENTRIES;
4647
+ upsertMarkerBlock(gitignorePath, MARKER_START2, MARKER_END2, entries.join("\n"));
4510
4648
  return { status: "success" };
4511
4649
  }
4512
4650
  });
4513
4651
 
4514
4652
  // src/steps/setup/write-state.ts
4515
- var import_fs7 = require("fs");
4516
- var import_path7 = require("path");
4653
+ var import_fs10 = require("fs");
4654
+ var import_path11 = require("path");
4517
4655
  var import_os2 = require("os");
4518
- var INSTALLER_VERSION = "0.5.18";
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
4519
4683
  var write_state_default = defineStep({
4520
4684
  name: "write-state",
4521
4685
  label: "Saving installation state",
4522
4686
  execute: async (ctx) => {
4523
- const statePath = (0, import_path7.join)(ctx.config.projectPath, ".one-shot-state.json");
4687
+ const statePath = (0, import_path11.join)(ctx.config.projectPath, ".one-shot-state.json");
4524
4688
  const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
4525
4689
  const filteredMcpConfig = Object.fromEntries(
4526
4690
  Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
4527
4691
  );
4692
+ const target = getAgentTarget(ctx.config.agent);
4528
4693
  const state = {
4529
4694
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4530
- installerVersion: INSTALLER_VERSION,
4695
+ installerVersion: package_default.version,
4531
4696
  releaseVersion: ctx.config.releaseVersion,
4532
4697
  contextReleaseVersion: ctx.installed.contextReleaseVersion ?? null,
4533
4698
  projectPath: ctx.config.projectPath,
@@ -4538,65 +4703,19 @@ var write_state_default = defineStep({
4538
4703
  mcpConfigs: filteredMcpConfig,
4539
4704
  factoryInstalled: ctx.installed.factoryInstalled ?? false,
4540
4705
  files: {
4541
- instructions: getInstructionFilePath2(ctx.config.agent),
4542
- mcpConfig: getMCPConfigPath(ctx.config.agent),
4706
+ instructions: target.instructions,
4707
+ mcpConfig: target.mcpConfig,
4543
4708
  contexts: "_ai-context/",
4544
4709
  factory: ctx.installed.factoryInstalled ? "factory/" : null,
4545
- globalConfig: (0, import_path7.join)((0, import_os2.homedir)(), ".one-shot-installer")
4710
+ globalConfig: (0, import_path11.join)((0, import_os2.homedir)(), ".one-shot-installer")
4546
4711
  }
4547
4712
  };
4548
- (0, import_fs7.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
4713
+ (0, import_fs10.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
4549
4714
  return { status: "success" };
4550
4715
  }
4551
4716
  });
4552
- function getInstructionFilePath2(agent) {
4553
- switch (agent) {
4554
- case "claude-code":
4555
- return "CLAUDE.md";
4556
- case "github-copilot":
4557
- return ".github/copilot-instructions.md";
4558
- case "cursor":
4559
- return ".cursor/rules/instructions.mdc";
4560
- default:
4561
- return "CLAUDE.md";
4562
- }
4563
- }
4564
- function getMCPConfigPath(agent) {
4565
- switch (agent) {
4566
- case "claude-code":
4567
- return ".mcp.json";
4568
- case "cursor":
4569
- return ".cursor/mcp.json";
4570
- case "github-copilot":
4571
- default:
4572
- return ".vscode/mcp.json";
4573
- }
4574
- }
4575
4717
 
4576
4718
  // src/index.ts
4577
- function getInstructionFilePath3(agent) {
4578
- switch (agent) {
4579
- case "claude-code":
4580
- return "CLAUDE.md";
4581
- case "github-copilot":
4582
- return ".github/copilot-instructions.md";
4583
- case "cursor":
4584
- return ".cursor/rules/instructions.mdc";
4585
- default:
4586
- return "CLAUDE.md";
4587
- }
4588
- }
4589
- function getMCPConfigPath2(agent) {
4590
- switch (agent) {
4591
- case "claude-code":
4592
- return ".mcp.json";
4593
- case "cursor":
4594
- return ".cursor/mcp.json";
4595
- case "github-copilot":
4596
- default:
4597
- return ".vscode/mcp.json";
4598
- }
4599
- }
4600
4719
  function formatMCPCommand(server) {
4601
4720
  if (server.url) return server.url;
4602
4721
  if (server.command) return `${server.command} ${(server.args ?? []).join(" ")}`.trimEnd();
@@ -4606,19 +4725,19 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
4606
4725
  var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
4607
4726
  var green = (text) => `\x1B[32m${text}\x1B[0m`;
4608
4727
  function detectMarkerFileMode(filePath, markerStart) {
4609
- if (!(0, import_fs8.existsSync)(filePath)) return green("create");
4610
- const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
4728
+ if (!(0, import_fs11.existsSync)(filePath)) return green("create");
4729
+ const content = (0, import_fs11.readFileSync)(filePath, "utf-8");
4611
4730
  if (content.includes(markerStart)) return yellow("update");
4612
4731
  return yellow("append");
4613
4732
  }
4614
4733
  function detectMCPFileMode(filePath) {
4615
- if (!(0, import_fs8.existsSync)(filePath)) return green("create");
4734
+ if (!(0, import_fs11.existsSync)(filePath)) return green("create");
4616
4735
  return yellow("merge");
4617
4736
  }
4618
4737
  function detectContextMode(projectPath, domain) {
4619
- const contextDir = (0, import_path8.join)(projectPath, "_ai-context", domain.toUpperCase());
4620
- const contextDirLower = (0, import_path8.join)(projectPath, "_ai-context", domain);
4621
- if ((0, import_fs8.existsSync)(contextDir) || (0, import_fs8.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");
4622
4741
  return green("create");
4623
4742
  }
4624
4743
  function waitForEnter() {
@@ -4716,13 +4835,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4716
4835
  }
4717
4836
  const projectInput = await esm_default4({
4718
4837
  message: "Project directory:",
4719
- default: (0, import_path8.resolve)(process.cwd())
4838
+ default: (0, import_path12.resolve)(process.cwd())
4720
4839
  });
4721
4840
  return {
4722
4841
  technologies: selectedTechnologies,
4723
4842
  agent,
4724
4843
  azureDevOpsOrg,
4725
- projectPath: (0, import_path8.resolve)(projectInput),
4844
+ projectPath: (0, import_path12.resolve)(projectInput),
4726
4845
  baseMcpServers: options.baseMcpServers,
4727
4846
  mcpConfig,
4728
4847
  releaseVersion,
@@ -4732,12 +4851,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4732
4851
  async function previewAndConfirm(config, options) {
4733
4852
  const technologyNames = config.technologies.length > 0 ? config.technologies.map((p) => p.name.replace(/ Developer$/, "")).join(", ") : "None";
4734
4853
  const agentDisplay = options.agents.find((a) => a.value === config.agent)?.name ?? config.agent;
4735
- const instructionFile = getInstructionFilePath3(config.agent);
4736
- const mcpConfigFile = getMCPConfigPath2(config.agent);
4854
+ const target = getAgentTarget(config.agent);
4855
+ const instructionFile = target.instructions;
4856
+ const mcpConfigFile = target.mcpConfig;
4737
4857
  const serverEntries = Object.entries(config.mcpConfig);
4738
- const instructionFilePath = (0, import_path8.join)(config.projectPath, instructionFile);
4739
- const mcpConfigFilePath = (0, import_path8.join)(config.projectPath, mcpConfigFile);
4740
- const gitignorePath = (0, import_path8.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");
4741
4861
  const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
4742
4862
  const mcpMode = detectMCPFileMode(mcpConfigFilePath);
4743
4863
  const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
@@ -4763,7 +4883,7 @@ async function previewAndConfirm(config, options) {
4763
4883
  console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
4764
4884
  console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
4765
4885
  if (config.installFactory) {
4766
- const factoryExists = (0, import_fs8.existsSync)((0, import_path8.join)(config.projectPath, "factory"));
4886
+ const factoryExists = (0, import_fs11.existsSync)((0, import_path12.join)(config.projectPath, "factory"));
4767
4887
  const factoryMode = factoryExists ? yellow("overwrite") : green("create");
4768
4888
  console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
4769
4889
  }
@@ -4782,6 +4902,7 @@ async function previewAndConfirm(config, options) {
4782
4902
  function printSummary(result, config) {
4783
4903
  const failed = result.entries.filter((e) => e.result.status === "failed");
4784
4904
  const succeeded = result.entries.filter((e) => e.result.status === "success");
4905
+ const skipped = result.entries.filter((e) => e.result.status === "skipped");
4785
4906
  const hasErrors = failed.length > 0;
4786
4907
  console.log("\u2500".repeat(48));
4787
4908
  console.log(hasErrors ? " Done (with errors).\n" : " Done.\n");
@@ -4789,6 +4910,10 @@ function printSummary(result, config) {
4789
4910
  const msg = entry.result.message ? ` ${entry.result.message}` : "";
4790
4911
  console.log(` ${entry.label}${msg}`);
4791
4912
  }
4913
+ for (const entry of skipped) {
4914
+ const detail = entry.result.detail ? ` ${entry.result.detail}` : "";
4915
+ console.log(` ${entry.label} (skipped)${detail}`);
4916
+ }
4792
4917
  if (hasErrors) {
4793
4918
  console.log("\n What went wrong:");
4794
4919
  for (const entry of failed) {