dlw-machine-setup 0.5.18 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/installer.js +434 -233
  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 resolve4;
2756
+ let resolve5;
2757
2757
  let reject;
2758
2758
  const promise = new Promise((res, rej) => {
2759
- resolve4 = res;
2759
+ resolve5 = res;
2760
2760
  reject = rej;
2761
2761
  });
2762
- return { promise, resolve: resolve4, reject };
2762
+ return { promise, resolve: resolve5, 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: resolve4, reject } = PromisePolyfill.withResolver();
2779
+ const { promise, resolve: resolve5, 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(() => resolve4(value));
2803
+ setImmediate(() => resolve5(value));
2804
2804
  });
2805
2805
  const [content, bottomContent] = typeof nextView === "string" ? [nextView] : nextView;
2806
2806
  screen.render(content, bottomContent);
@@ -3244,9 +3244,9 @@ ${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_fs9 = require("fs");
3248
3248
  var import_readline = require("readline");
3249
- var import_path8 = require("path");
3249
+ var import_path9 = require("path");
3250
3250
 
3251
3251
  // src/utils/fetch.ts
3252
3252
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -3283,7 +3283,7 @@ async function fetchWithRetry(url, options = {}, maxRetries = DEFAULT_MAX_RETRIE
3283
3283
  }
3284
3284
  if (attempt < maxRetries) {
3285
3285
  const delay = retryDelayMs * Math.pow(2, attempt - 1);
3286
- await new Promise((resolve4) => setTimeout(resolve4, delay));
3286
+ await new Promise((resolve5) => setTimeout(resolve5, delay));
3287
3287
  }
3288
3288
  }
3289
3289
  throw lastError || new Error("Failed after maximum retries");
@@ -3451,7 +3451,7 @@ async function pollForToken(deviceCode, interval) {
3451
3451
  const maxAttempts = 60;
3452
3452
  let attempts = 0;
3453
3453
  while (attempts < maxAttempts) {
3454
- await new Promise((resolve4) => setTimeout(resolve4, interval * 1e3));
3454
+ await new Promise((resolve5) => setTimeout(resolve5, interval * 1e3));
3455
3455
  const response = await fetchWithTimeout(GITHUB_TOKEN_URL, {
3456
3456
  method: "POST",
3457
3457
  headers: {
@@ -3577,6 +3577,31 @@ function buildMCPConfiguration(selectedItems, baseMcpServers, allMcpServers) {
3577
3577
  return config;
3578
3578
  }
3579
3579
 
3580
+ // src/utils/agent-targets.ts
3581
+ var AGENT_TARGETS = {
3582
+ "claude-code": {
3583
+ instructions: "CLAUDE.md",
3584
+ mcpConfig: ".mcp.json",
3585
+ mcpRootKey: "mcpServers",
3586
+ mcpDir: null
3587
+ },
3588
+ "github-copilot": {
3589
+ instructions: ".github/copilot-instructions.md",
3590
+ mcpConfig: ".vscode/mcp.json",
3591
+ mcpRootKey: "servers",
3592
+ mcpDir: ".vscode"
3593
+ },
3594
+ "cursor": {
3595
+ instructions: ".cursor/rules/instructions.mdc",
3596
+ mcpConfig: ".cursor/mcp.json",
3597
+ mcpRootKey: "mcpServers",
3598
+ mcpDir: ".cursor"
3599
+ }
3600
+ };
3601
+ function getAgentTarget(agent) {
3602
+ return AGENT_TARGETS[agent] ?? AGENT_TARGETS["claude-code"];
3603
+ }
3604
+
3580
3605
  // src/utils/mod.ts
3581
3606
  async function loadWizardOptions(token, repo) {
3582
3607
  const remote = await fetchWizardOptions(token, repo);
@@ -3811,9 +3836,204 @@ function getReadableError(status) {
3811
3836
  }
3812
3837
 
3813
3838
  // 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");
3842
+
3843
+ // src/bundles/run-bundle.ts
3814
3844
  var import_fs3 = require("fs");
3815
3845
  var import_path3 = require("path");
3816
- var import_child_process3 = require("child_process");
3846
+
3847
+ // src/bundles/types.ts
3848
+ var SCHEMA_VERSION = 1;
3849
+
3850
+ // src/bundles/run-bundle.ts
3851
+ var OWNER_KEY = "_bundleOwner";
3852
+ async function runBundle(manifest, ctx) {
3853
+ assertSchemaCompatible(manifest);
3854
+ assertBundleRootExists(ctx);
3855
+ const result = {
3856
+ name: manifest.name,
3857
+ opsExecuted: 0,
3858
+ filesTouched: []
3859
+ };
3860
+ const ops = manifest.targets?.[ctx.agent] ?? [];
3861
+ for (const op of ops) {
3862
+ executeOp(op, manifest.name, ctx, result);
3863
+ result.opsExecuted++;
3864
+ }
3865
+ if (manifest.gitignore?.length) {
3866
+ upsertMarkerBlock(
3867
+ (0, import_path3.join)(ctx.projectPath, ".gitignore"),
3868
+ `# ${manifest.name}:start`,
3869
+ `# ${manifest.name}:end`,
3870
+ manifest.gitignore.join("\n")
3871
+ );
3872
+ result.filesTouched.push(".gitignore");
3873
+ }
3874
+ const snippet = manifest.instructions?.[ctx.agent];
3875
+ if (snippet) {
3876
+ result.instructionsSnippet = snippet;
3877
+ if (!ctx.skipInstructions) {
3878
+ upsertMarkerBlock(
3879
+ (0, import_path3.join)(ctx.projectPath, ctx.instructionsFile),
3880
+ `<!-- ${manifest.name}:start -->`,
3881
+ `<!-- ${manifest.name}:end -->`,
3882
+ snippet
3883
+ );
3884
+ result.filesTouched.push(ctx.instructionsFile);
3885
+ }
3886
+ }
3887
+ return result;
3888
+ }
3889
+ function executeOp(op, owner, ctx, result) {
3890
+ switch (op.op) {
3891
+ case "copy":
3892
+ return runCopy(op, ctx, result);
3893
+ case "merge-json":
3894
+ return runMergeJson(op, owner, ctx, result);
3895
+ default: {
3896
+ const unknown = op.op;
3897
+ throw new Error(
3898
+ `Bundle "${owner}" uses unknown op "${unknown}". Installer supports: copy, merge-json. Upgrade the installer or check the bundle's schemaVersion.`
3899
+ );
3900
+ }
3901
+ }
3902
+ }
3903
+ function runCopy(op, ctx, result) {
3904
+ const source = resolveBundlePath(op.from, ctx);
3905
+ 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);
3909
+ } else {
3910
+ (0, import_fs3.mkdirSync)((0, import_path3.dirname)(target), { recursive: true });
3911
+ (0, import_fs3.copyFileSync)(source, target);
3912
+ }
3913
+ result.filesTouched.push(op.to);
3914
+ }
3915
+ function runMergeJson(op, owner, ctx, result) {
3916
+ const filePath = resolveProjectPath(op.file, ctx);
3917
+ const existing = readJsonOrEmpty(filePath);
3918
+ const stripped = stripOwnedEntries(existing, owner);
3919
+ const tagged = tagEntries(op.patch, owner);
3920
+ 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");
3923
+ result.filesTouched.push(op.file);
3924
+ }
3925
+ function readJsonOrEmpty(path) {
3926
+ if (!(0, import_fs3.existsSync)(path)) return {};
3927
+ try {
3928
+ return JSON.parse((0, import_fs3.readFileSync)(path, "utf-8"));
3929
+ } catch {
3930
+ return {};
3931
+ }
3932
+ }
3933
+ function isPlainObject2(v) {
3934
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3935
+ }
3936
+ function tagEntries(value, owner) {
3937
+ if (Array.isArray(value)) {
3938
+ return value.map(
3939
+ (item) => isPlainObject2(item) ? { ...item, [OWNER_KEY]: owner } : item
3940
+ );
3941
+ }
3942
+ if (isPlainObject2(value)) {
3943
+ return Object.fromEntries(
3944
+ Object.entries(value).map(([k, v]) => [k, tagEntries(v, owner)])
3945
+ );
3946
+ }
3947
+ return value;
3948
+ }
3949
+ function stripOwnedEntries(value, owner) {
3950
+ if (Array.isArray(value)) {
3951
+ return value.filter((item) => !(isPlainObject2(item) && item[OWNER_KEY] === owner)).map((item) => stripOwnedEntries(item, owner));
3952
+ }
3953
+ if (isPlainObject2(value)) {
3954
+ return Object.fromEntries(
3955
+ Object.entries(value).map(([k, v]) => [k, stripOwnedEntries(v, owner)])
3956
+ );
3957
+ }
3958
+ return value;
3959
+ }
3960
+ function deepMerge2(a, b) {
3961
+ if (isPlainObject2(a) && isPlainObject2(b)) {
3962
+ const out = { ...a };
3963
+ for (const [k, v] of Object.entries(b)) {
3964
+ out[k] = k in a ? deepMerge2(a[k], v) : v;
3965
+ }
3966
+ return out;
3967
+ }
3968
+ if (Array.isArray(a) && Array.isArray(b)) {
3969
+ return [...a, ...b];
3970
+ }
3971
+ return b;
3972
+ }
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
+ 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)) {
3997
+ throw new Error(`Bundle op "from" escapes bundle root: ${rel}`);
3998
+ }
3999
+ return full;
4000
+ }
4001
+ 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)) {
4005
+ throw new Error(`Bundle op "to" escapes project path: ${rel}`);
4006
+ }
4007
+ return full;
4008
+ }
4009
+ function assertSchemaCompatible(manifest) {
4010
+ if (manifest.schemaVersion !== SCHEMA_VERSION) {
4011
+ throw new Error(
4012
+ `Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${SCHEMA_VERSION}. Upgrade the installer or re-pin the bundle.`
4013
+ );
4014
+ }
4015
+ if (!manifest.name || !/^[a-z][a-z0-9-]*$/.test(manifest.name)) {
4016
+ throw new Error(
4017
+ `Bundle name must match /^[a-z][a-z0-9-]*$/: got "${manifest.name}"`
4018
+ );
4019
+ }
4020
+ }
4021
+ function assertBundleRootExists(ctx) {
4022
+ if (!(0, import_fs3.existsSync)(ctx.bundleRoot) || !(0, import_fs3.statSync)(ctx.bundleRoot).isDirectory()) {
4023
+ throw new Error(`Bundle root not found or not a directory: ${ctx.bundleRoot}`);
4024
+ }
4025
+ }
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
+
4036
+ // src/steps/resources/fetch-factory.ts
3817
4037
  var MIN_FILE_SIZE2 = 1024;
3818
4038
  function getFactoryAsset(agent) {
3819
4039
  switch (agent) {
@@ -3830,10 +4050,17 @@ var fetch_factory_default = defineStep({
3830
4050
  execute: async (ctx) => {
3831
4051
  const result = await fetchFactory(ctx.token, ctx.factoryRepo, ctx.config.projectPath, ctx.config.agent);
3832
4052
  ctx.installed.factoryInstalled = result.success;
4053
+ ctx.installed.factoryBundleManaged = result.bundleManaged ?? false;
4054
+ if (result.instructionsSnippet) {
4055
+ const stash = ctx.installed.bundleInstructions ?? {};
4056
+ stash["factory"] = result.instructionsSnippet;
4057
+ ctx.installed.bundleInstructions = stash;
4058
+ }
3833
4059
  if (!result.success) {
3834
4060
  return { status: "failed", detail: result.failureReason };
3835
4061
  }
3836
- return { status: "success", message: result.filesInstalled.join(", ") };
4062
+ const tag = result.bundleManaged ? " (bundle)" : "";
4063
+ return { status: "success", message: result.filesInstalled.join(", ") + tag };
3837
4064
  }
3838
4065
  });
3839
4066
  async function fetchFactory(token, repo, targetDir, agent) {
@@ -3862,10 +4089,10 @@ async function fetchFactory(token, repo, targetDir, agent) {
3862
4089
  result.failureReason = `${assetName} not found in release`;
3863
4090
  return result;
3864
4091
  }
3865
- const tempDir = (0, import_path3.join)(targetDir, ".temp-factory-download");
4092
+ const tempDir = (0, import_path4.join)(targetDir, ".temp-factory-download");
3866
4093
  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 });
4094
+ if ((0, import_fs4.existsSync)(tempDir)) (0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
4095
+ (0, import_fs4.mkdirSync)(tempDir, { recursive: true });
3869
4096
  const response = await fetchWithRetry(asset.url, {
3870
4097
  headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
3871
4098
  });
@@ -3873,10 +4100,10 @@ async function fetchFactory(token, repo, targetDir, agent) {
3873
4100
  result.failureReason = `Download failed (${response.status})`;
3874
4101
  return result;
3875
4102
  }
3876
- const archivePath = (0, import_path3.join)(tempDir, assetName);
4103
+ const archivePath = (0, import_path4.join)(tempDir, assetName);
3877
4104
  const arrayBuffer = await response.arrayBuffer();
3878
- (0, import_fs3.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
3879
- const stats = (0, import_fs3.statSync)(archivePath);
4105
+ (0, import_fs4.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
4106
+ const stats = (0, import_fs4.statSync)(archivePath);
3880
4107
  if (stats.size < MIN_FILE_SIZE2) {
3881
4108
  result.failureReason = "Downloaded file corrupted (too small)";
3882
4109
  return result;
@@ -3886,10 +4113,10 @@ async function fetchFactory(token, repo, targetDir, agent) {
3886
4113
  result.failureReason = "Failed to read archive contents";
3887
4114
  return result;
3888
4115
  }
3889
- const resolvedTempDir = (0, import_path3.resolve)(tempDir);
4116
+ const resolvedTempDir = (0, import_path4.resolve)(tempDir);
3890
4117
  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)) {
4118
+ const entryPath = (0, import_path4.resolve)((0, import_path4.join)(tempDir, entry.replace(/\/$/, "")));
4119
+ if (!entryPath.startsWith(resolvedTempDir + import_path4.sep)) {
3893
4120
  result.failureReason = `Archive contains unsafe path: ${entry}`;
3894
4121
  return result;
3895
4122
  }
@@ -3899,14 +4126,17 @@ async function fetchFactory(token, repo, targetDir, agent) {
3899
4126
  result.failureReason = "Archive extraction failed";
3900
4127
  return result;
3901
4128
  }
3902
- const extractedEntries = (0, import_fs3.readdirSync)(tempDir);
4129
+ const extractedEntries = (0, import_fs4.readdirSync)(tempDir);
3903
4130
  const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === rootFolder.toLowerCase());
3904
4131
  if (!extractedFolder) {
3905
4132
  result.failureReason = `No ${rootFolder}/ folder in archive`;
3906
4133
  return result;
3907
4134
  }
3908
- const extractedPath = (0, import_path3.join)(tempDir, extractedFolder);
3909
- if (agent === "github-copilot") {
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)) {
4138
+ await installViaBundle(manifestPath, extractedPath, targetDir, agent, result);
4139
+ } else if (agent === "github-copilot") {
3910
4140
  installCopilotFactory(extractedPath, targetDir, result);
3911
4141
  } else {
3912
4142
  installClaudeFactory(extractedPath, targetDir, result);
@@ -3915,53 +4145,73 @@ async function fetchFactory(token, repo, targetDir, agent) {
3915
4145
  } catch (error) {
3916
4146
  result.failureReason = error instanceof Error ? error.message : String(error);
3917
4147
  } finally {
3918
- if ((0, import_fs3.existsSync)(tempDir)) {
4148
+ if ((0, import_fs4.existsSync)(tempDir)) {
3919
4149
  try {
3920
- (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
4150
+ (0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
3921
4151
  } catch {
3922
4152
  }
3923
4153
  }
3924
4154
  }
3925
4155
  return result;
3926
4156
  }
4157
+ async function installViaBundle(manifestPath, extractedPath, targetDir, agent, result) {
4158
+ let manifest;
4159
+ try {
4160
+ manifest = JSON.parse((0, import_fs4.readFileSync)(manifestPath, "utf-8"));
4161
+ } catch (e) {
4162
+ throw new Error(`install.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
4163
+ }
4164
+ const target = getAgentTarget(agent);
4165
+ const runResult = await runBundle(manifest, {
4166
+ bundleRoot: extractedPath,
4167
+ projectPath: targetDir,
4168
+ agent,
4169
+ instructionsFile: target.instructions,
4170
+ skipInstructions: true
4171
+ // write-instructions.ts commits the snippet for correct ordering
4172
+ });
4173
+ result.bundleManaged = true;
4174
+ result.instructionsSnippet = runResult.instructionsSnippet;
4175
+ result.filesInstalled = runResult.filesTouched;
4176
+ }
3927
4177
  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"));
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"));
3933
4183
  result.filesInstalled.push(".claude/agents/");
3934
4184
  }
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"));
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"));
3938
4188
  result.filesInstalled.push(".claude/hooks/");
3939
4189
  }
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"));
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"));
3943
4193
  result.filesInstalled.push(".claude/skills/");
3944
4194
  }
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"));
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"));
3948
4198
  result.filesInstalled.push("factory/workflow/");
3949
4199
  }
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"));
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"));
3953
4203
  result.filesInstalled.push("factory/utils/");
3954
4204
  }
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"));
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"));
3958
4208
  result.filesInstalled.push("factory/docs/");
3959
4209
  }
3960
- const rootEntries = (0, import_fs3.readdirSync)(extractedPath, { withFileTypes: true });
4210
+ const rootEntries = (0, import_fs4.readdirSync)(extractedPath, { withFileTypes: true });
3961
4211
  for (const entry of rootEntries) {
3962
4212
  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));
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));
3965
4215
  result.filesInstalled.push(`factory/${entry.name}`);
3966
4216
  }
3967
4217
  }
@@ -3969,9 +4219,9 @@ function installClaudeFactory(extractedPath, targetDir, result) {
3969
4219
  result.filesInstalled.push(".claude/settings.json");
3970
4220
  }
3971
4221
  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 });
4222
+ const factoryDir = (0, import_path4.join)(targetDir, "factory");
4223
+ copyDirectory3(extractedPath, factoryDir);
4224
+ const entries = (0, import_fs4.readdirSync)(extractedPath, { withFileTypes: true });
3975
4225
  for (const entry of entries) {
3976
4226
  if (entry.isDirectory()) {
3977
4227
  result.filesInstalled.push(`factory/${entry.name}/`);
@@ -3981,11 +4231,11 @@ function installCopilotFactory(extractedPath, targetDir, result) {
3981
4231
  }
3982
4232
  }
3983
4233
  function configureSettings(claudeDir) {
3984
- const settingsPath = (0, import_path3.join)(claudeDir, "settings.json");
4234
+ const settingsPath = (0, import_path4.join)(claudeDir, "settings.json");
3985
4235
  let settings = {};
3986
- if ((0, import_fs3.existsSync)(settingsPath)) {
4236
+ if ((0, import_fs4.existsSync)(settingsPath)) {
3987
4237
  try {
3988
- settings = JSON.parse((0, import_fs3.readFileSync)(settingsPath, "utf8"));
4238
+ settings = JSON.parse((0, import_fs4.readFileSync)(settingsPath, "utf8"));
3989
4239
  } catch {
3990
4240
  }
3991
4241
  }
@@ -4036,26 +4286,26 @@ function configureSettings(claudeDir) {
4036
4286
  if (!hasMonitor) settings.hooks.UserPromptSubmit.push(contextMonitorHook);
4037
4287
  }
4038
4288
  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");
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");
4041
4291
  }
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 });
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 });
4045
4295
  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);
4296
+ const sourcePath = (0, import_path4.join)(source, entry.name);
4297
+ const targetPath = (0, import_path4.join)(target, entry.name);
4048
4298
  if (entry.isDirectory()) {
4049
- copyDirectory2(sourcePath, targetPath);
4299
+ copyDirectory3(sourcePath, targetPath);
4050
4300
  } else {
4051
- (0, import_fs3.copyFileSync)(sourcePath, targetPath);
4301
+ (0, import_fs4.copyFileSync)(sourcePath, targetPath);
4052
4302
  }
4053
4303
  }
4054
4304
  }
4055
4305
 
4056
4306
  // src/steps/setup/write-instructions.ts
4057
- var import_fs4 = require("fs");
4058
- var import_path4 = require("path");
4307
+ var import_fs5 = require("fs");
4308
+ var import_path5 = require("path");
4059
4309
 
4060
4310
  // src/steps/shared.ts
4061
4311
  function getFilteredMcpConfig(ctx) {
@@ -4079,65 +4329,45 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
4079
4329
  var write_instructions_default = defineStep({
4080
4330
  name: "write-instructions",
4081
4331
  label: "Writing instruction file",
4082
- when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0 || Object.keys(ctx.config.mcpConfig).length > 0 || ctx.installed.factoryInstalled,
4332
+ 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
4333
  execute: async (ctx) => {
4084
4334
  const filteredMcpConfig = getFilteredMcpConfig(ctx);
4085
4335
  const domains = ctx.installed.domainsInstalled ?? [];
4086
4336
  const agent = ctx.config.agent;
4087
4337
  const projectPath = ctx.config.projectPath;
4088
4338
  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, `---
4339
+ const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
4340
+ const includeFactorySection = factoryInstalled && !factoryBundleManaged;
4341
+ const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, includeFactorySection);
4342
+ 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, `---
4107
4348
  description: AI development instructions from One-Shot Installer
4108
4349
  alwaysApply: true
4109
4350
  ---
4110
4351
 
4111
4352
  `, "utf-8");
4112
- }
4113
- upsertBlock(filePath, content);
4114
- break;
4115
- }
4116
4353
  }
4117
- return { status: "success", message: getInstructionFilePath(agent) };
4354
+ upsertBlock(filePath, content);
4355
+ const bundleSnippets = ctx.installed.bundleInstructions ?? {};
4356
+ for (const bundleName of Object.keys(bundleSnippets).sort()) {
4357
+ upsertBundleBlock(filePath, bundleName, bundleSnippets[bundleName]);
4358
+ }
4359
+ return { status: "success", message: target.instructions };
4118
4360
  }
4119
4361
  });
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
4362
  function upsertBlock(filePath, block) {
4133
4363
  const marked = `${MARKER_START}
4134
4364
  ${block}
4135
4365
  ${MARKER_END}`;
4136
- if (!(0, import_fs4.existsSync)(filePath)) {
4137
- (0, import_fs4.writeFileSync)(filePath, marked, "utf-8");
4366
+ if (!(0, import_fs5.existsSync)(filePath)) {
4367
+ (0, import_fs5.writeFileSync)(filePath, marked, "utf-8");
4138
4368
  return;
4139
4369
  }
4140
- const existing = (0, import_fs4.readFileSync)(filePath, "utf-8");
4370
+ const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
4141
4371
  const start = existing.indexOf(MARKER_START);
4142
4372
  const end = existing.indexOf(MARKER_END);
4143
4373
  const hasStart = start !== -1;
@@ -4162,23 +4392,44 @@ ${MARKER_END}`;
4162
4392
  }
4163
4393
  if (hasStart && hasEnd) {
4164
4394
  const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
4165
- (0, import_fs4.writeFileSync)(filePath, updated, "utf-8");
4395
+ (0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
4166
4396
  } else {
4167
4397
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4168
- (0, import_fs4.writeFileSync)(filePath, existing + separator + marked, "utf-8");
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");
4169
4420
  }
4170
4421
  }
4171
4422
  function collectMdFiles(dir) {
4172
- if (!(0, import_fs4.existsSync)(dir)) return [];
4173
- const entries = (0, import_fs4.readdirSync)(dir, { recursive: true });
4423
+ if (!(0, import_fs5.existsSync)(dir)) return [];
4424
+ const entries = (0, import_fs5.readdirSync)(dir, { recursive: true });
4174
4425
  return entries.filter((entry) => {
4175
- const fullPath = (0, import_path4.join)(dir, entry);
4176
- return entry.endsWith(".md") && (0, import_fs4.statSync)(fullPath).isFile();
4426
+ const fullPath = (0, import_path5.join)(dir, entry);
4427
+ return entry.endsWith(".md") && (0, import_fs5.statSync)(fullPath).isFile();
4177
4428
  }).map((entry) => entry.replace(/\\/g, "/")).sort();
4178
4429
  }
4179
4430
  function extractFirstHeading(filePath) {
4180
4431
  try {
4181
- const content = (0, import_fs4.readFileSync)(filePath, "utf-8");
4432
+ const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
4182
4433
  const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
4183
4434
  const match = withoutFrontmatter.match(/^#\s+(.+)/m);
4184
4435
  if (match) {
@@ -4196,16 +4447,16 @@ function formatPathRef(contextPath, description, agent) {
4196
4447
  return `- \`${contextPath}\` \u2014 ${description}`;
4197
4448
  }
4198
4449
  function resolveDomainFolder(domain, contextsDir) {
4199
- if ((0, import_fs4.existsSync)(contextsDir)) {
4450
+ if ((0, import_fs5.existsSync)(contextsDir)) {
4200
4451
  try {
4201
- const entries = (0, import_fs4.readdirSync)(contextsDir);
4452
+ const entries = (0, import_fs5.readdirSync)(contextsDir);
4202
4453
  const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
4203
- if (match) return { folderName: match, folderPath: (0, import_path4.join)(contextsDir, match) };
4454
+ if (match) return { folderName: match, folderPath: (0, import_path5.join)(contextsDir, match) };
4204
4455
  } catch {
4205
4456
  }
4206
4457
  }
4207
4458
  const fallback = domain.toUpperCase();
4208
- return { folderName: fallback, folderPath: (0, import_path4.join)(contextsDir, fallback) };
4459
+ return { folderName: fallback, folderPath: (0, import_path5.join)(contextsDir, fallback) };
4209
4460
  }
4210
4461
  function buildContextRefsSection(domains, agent, contextsDir) {
4211
4462
  const lines2 = [
@@ -4217,34 +4468,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
4217
4468
  let hasAnyFiles = false;
4218
4469
  for (const domain of domains) {
4219
4470
  const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
4220
- if (!(0, import_fs4.existsSync)(domainPath)) continue;
4471
+ if (!(0, import_fs5.existsSync)(domainPath)) continue;
4221
4472
  const domainFiles = [];
4222
- const ctxInstructions = (0, import_path4.join)(domainPath, "context-instructions.md");
4223
- if ((0, import_fs4.existsSync)(ctxInstructions)) {
4473
+ const ctxInstructions = (0, import_path5.join)(domainPath, "context-instructions.md");
4474
+ if ((0, import_fs5.existsSync)(ctxInstructions)) {
4224
4475
  const desc = extractFirstHeading(ctxInstructions);
4225
4476
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
4226
4477
  }
4227
- const instructionsMd = (0, import_path4.join)(domainPath, "core", "instructions.md");
4228
- if ((0, import_fs4.existsSync)(instructionsMd)) {
4478
+ const instructionsMd = (0, import_path5.join)(domainPath, "core", "instructions.md");
4479
+ if ((0, import_fs5.existsSync)(instructionsMd)) {
4229
4480
  const desc = extractFirstHeading(instructionsMd);
4230
4481
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
4231
4482
  }
4232
- const coreDir = (0, import_path4.join)(domainPath, "core");
4233
- if ((0, import_fs4.existsSync)(coreDir)) {
4483
+ const coreDir = (0, import_path5.join)(domainPath, "core");
4484
+ if ((0, import_fs5.existsSync)(coreDir)) {
4234
4485
  const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
4235
4486
  for (const file of coreFiles) {
4236
- const desc = extractFirstHeading((0, import_path4.join)(coreDir, file));
4487
+ const desc = extractFirstHeading((0, import_path5.join)(coreDir, file));
4237
4488
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
4238
4489
  }
4239
4490
  }
4240
- const refDir = (0, import_path4.join)(domainPath, "reference");
4241
- if ((0, import_fs4.existsSync)(refDir)) {
4491
+ const refDir = (0, import_path5.join)(domainPath, "reference");
4492
+ if ((0, import_fs5.existsSync)(refDir)) {
4242
4493
  const refFiles = collectMdFiles(refDir);
4243
4494
  if (refFiles.length > 0) {
4244
4495
  domainFiles.push(``);
4245
4496
  domainFiles.push(`**Reference & cheat sheets:**`);
4246
4497
  for (const file of refFiles) {
4247
- const desc = extractFirstHeading((0, import_path4.join)(refDir, file));
4498
+ const desc = extractFirstHeading((0, import_path5.join)(refDir, file));
4248
4499
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
4249
4500
  }
4250
4501
  }
@@ -4322,7 +4573,7 @@ function buildFactorySection(agent) {
4322
4573
  ].join("\n");
4323
4574
  }
4324
4575
  function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled) {
4325
- const contextsDir = (0, import_path4.join)(projectPath, "_ai-context");
4576
+ const contextsDir = (0, import_path5.join)(projectPath, "_ai-context");
4326
4577
  const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
4327
4578
  lines2.push(buildContextRefsSection(domains, agent, contextsDir));
4328
4579
  if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
@@ -4331,8 +4582,8 @@ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, facto
4331
4582
  }
4332
4583
 
4333
4584
  // src/steps/setup/write-mcp-config.ts
4334
- var import_fs5 = require("fs");
4335
- var import_path5 = require("path");
4585
+ var import_fs6 = require("fs");
4586
+ var import_path6 = require("path");
4336
4587
  var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
4337
4588
  var write_mcp_config_default = defineStep({
4338
4589
  name: "write-mcp-config",
@@ -4340,23 +4591,23 @@ var write_mcp_config_default = defineStep({
4340
4591
  when: (ctx) => Object.keys(ctx.config.mcpConfig).length > 0,
4341
4592
  execute: async (ctx) => {
4342
4593
  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 });
4594
+ const target = getAgentTarget(ctx.config.agent);
4595
+ const mcpJsonPath = (0, import_path6.join)(ctx.config.projectPath, target.mcpConfig);
4596
+ 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 });
4348
4599
  }
4349
4600
  let existingFile = {};
4350
- if ((0, import_fs5.existsSync)(mcpJsonPath)) {
4601
+ if ((0, import_fs6.existsSync)(mcpJsonPath)) {
4351
4602
  try {
4352
- existingFile = JSON.parse((0, import_fs5.readFileSync)(mcpJsonPath, "utf-8"));
4603
+ existingFile = JSON.parse((0, import_fs6.readFileSync)(mcpJsonPath, "utf-8"));
4353
4604
  } catch {
4354
4605
  const box = [
4355
4606
  "",
4356
4607
  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
4608
  red2(" \u2551 \u26A0 MCP CONFIG HAS INVALID JSON \u2551"),
4358
4609
  red2(" \u2551 \u2551"),
4359
- red2(` \u2551 ${target.filePath} could not be parsed.`.padEnd(52) + "\u2551"),
4610
+ red2(` \u2551 ${target.mcpConfig} could not be parsed.`.padEnd(52) + "\u2551"),
4360
4611
  red2(" \u2551 Existing MCP servers may be lost. \u2551"),
4361
4612
  red2(" \u2551 Check the file after installation completes. \u2551"),
4362
4613
  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 +4617,7 @@ var write_mcp_config_default = defineStep({
4366
4617
  }
4367
4618
  }
4368
4619
  const addedServers = [];
4369
- const existingServers = existingFile[target.rootKey] ?? {};
4620
+ const existingServers = existingFile[target.mcpRootKey] ?? {};
4370
4621
  const newServers = {};
4371
4622
  for (const [serverName, serverConfig] of Object.entries(filteredMcpConfig)) {
4372
4623
  const { description, useWhen, active, installCommand, ...mcpFields } = serverConfig;
@@ -4374,8 +4625,8 @@ var write_mcp_config_default = defineStep({
4374
4625
  addedServers.push(serverName);
4375
4626
  }
4376
4627
  const mergedServers = { ...existingServers, ...newServers };
4377
- const outputFile = { ...existingFile, [target.rootKey]: mergedServers };
4378
- (0, import_fs5.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
4628
+ const outputFile = { ...existingFile, [target.mcpRootKey]: mergedServers };
4629
+ (0, import_fs6.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
4379
4630
  ctx.installed.mcpServersAdded = addedServers;
4380
4631
  return {
4381
4632
  status: "success",
@@ -4383,17 +4634,6 @@ var write_mcp_config_default = defineStep({
4383
4634
  };
4384
4635
  }
4385
4636
  });
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
4637
 
4398
4638
  // src/steps/setup/run-mcp-install-commands.ts
4399
4639
  var import_child_process4 = require("child_process");
@@ -4456,11 +4696,11 @@ function isClaudeCliAvailable() {
4456
4696
  }
4457
4697
 
4458
4698
  // src/steps/setup/update-gitignore.ts
4459
- var import_fs6 = require("fs");
4460
- var import_path6 = require("path");
4699
+ var import_fs7 = require("fs");
4700
+ var import_path7 = require("path");
4461
4701
  var MARKER_START2 = "# one-shot-installer:start";
4462
4702
  var MARKER_END2 = "# one-shot-installer:end";
4463
- var GITIGNORE_ENTRIES = [
4703
+ var CORE_GITIGNORE_ENTRIES = [
4464
4704
  "# One-Shot installer generated files",
4465
4705
  ".one-shot-state.json",
4466
4706
  ".mcp.json",
@@ -4472,8 +4712,9 @@ var GITIGNORE_ENTRIES = [
4472
4712
  ".claude/agents/",
4473
4713
  ".claude/agent-memory/",
4474
4714
  ".claude/plans/",
4475
- ".claude/settings.local.json",
4476
- "",
4715
+ ".claude/settings.local.json"
4716
+ ];
4717
+ var LEGACY_FACTORY_GITIGNORE_ENTRIES = [
4477
4718
  "# Factory runtime state (generated by workflow, not committed)",
4478
4719
  "factory/STATE.md",
4479
4720
  "factory/PROJECT.md",
@@ -4491,40 +4732,45 @@ var update_gitignore_default = defineStep({
4491
4732
  name: "update-gitignore",
4492
4733
  label: "Updating .gitignore",
4493
4734
  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");
4735
+ const gitignorePath = (0, import_path7.join)(ctx.config.projectPath, ".gitignore");
4736
+ const factoryInstalled = ctx.installed.factoryInstalled ?? false;
4737
+ const factoryBundleManaged = ctx.installed.factoryBundleManaged ?? false;
4738
+ const includeLegacyFactory = factoryInstalled && !factoryBundleManaged;
4739
+ 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");
4498
4743
  return { status: "success" };
4499
4744
  }
4500
- const existing = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
4745
+ const existing = (0, import_fs7.readFileSync)(gitignorePath, "utf-8");
4501
4746
  const start = existing.indexOf(MARKER_START2);
4502
4747
  const end = existing.indexOf(MARKER_END2);
4503
4748
  if (start !== -1 && end !== -1 && end > start) {
4504
4749
  const updated = existing.slice(0, start) + block + existing.slice(end + MARKER_END2.length);
4505
- (0, import_fs6.writeFileSync)(gitignorePath, updated, "utf-8");
4750
+ (0, import_fs7.writeFileSync)(gitignorePath, updated, "utf-8");
4506
4751
  } else {
4507
4752
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4508
- (0, import_fs6.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
4753
+ (0, import_fs7.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
4509
4754
  }
4510
4755
  return { status: "success" };
4511
4756
  }
4512
4757
  });
4513
4758
 
4514
4759
  // src/steps/setup/write-state.ts
4515
- var import_fs7 = require("fs");
4516
- var import_path7 = require("path");
4760
+ var import_fs8 = require("fs");
4761
+ var import_path8 = require("path");
4517
4762
  var import_os2 = require("os");
4518
- var INSTALLER_VERSION = "0.5.18";
4763
+ var INSTALLER_VERSION = "0.6.0";
4519
4764
  var write_state_default = defineStep({
4520
4765
  name: "write-state",
4521
4766
  label: "Saving installation state",
4522
4767
  execute: async (ctx) => {
4523
- const statePath = (0, import_path7.join)(ctx.config.projectPath, ".one-shot-state.json");
4768
+ const statePath = (0, import_path8.join)(ctx.config.projectPath, ".one-shot-state.json");
4524
4769
  const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
4525
4770
  const filteredMcpConfig = Object.fromEntries(
4526
4771
  Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
4527
4772
  );
4773
+ const target = getAgentTarget(ctx.config.agent);
4528
4774
  const state = {
4529
4775
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4530
4776
  installerVersion: INSTALLER_VERSION,
@@ -4538,65 +4784,19 @@ var write_state_default = defineStep({
4538
4784
  mcpConfigs: filteredMcpConfig,
4539
4785
  factoryInstalled: ctx.installed.factoryInstalled ?? false,
4540
4786
  files: {
4541
- instructions: getInstructionFilePath2(ctx.config.agent),
4542
- mcpConfig: getMCPConfigPath(ctx.config.agent),
4787
+ instructions: target.instructions,
4788
+ mcpConfig: target.mcpConfig,
4543
4789
  contexts: "_ai-context/",
4544
4790
  factory: ctx.installed.factoryInstalled ? "factory/" : null,
4545
- globalConfig: (0, import_path7.join)((0, import_os2.homedir)(), ".one-shot-installer")
4791
+ globalConfig: (0, import_path8.join)((0, import_os2.homedir)(), ".one-shot-installer")
4546
4792
  }
4547
4793
  };
4548
- (0, import_fs7.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
4794
+ (0, import_fs8.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
4549
4795
  return { status: "success" };
4550
4796
  }
4551
4797
  });
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
4798
 
4576
4799
  // 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
4800
  function formatMCPCommand(server) {
4601
4801
  if (server.url) return server.url;
4602
4802
  if (server.command) return `${server.command} ${(server.args ?? []).join(" ")}`.trimEnd();
@@ -4606,27 +4806,27 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
4606
4806
  var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
4607
4807
  var green = (text) => `\x1B[32m${text}\x1B[0m`;
4608
4808
  function detectMarkerFileMode(filePath, markerStart) {
4609
- if (!(0, import_fs8.existsSync)(filePath)) return green("create");
4610
- const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
4809
+ if (!(0, import_fs9.existsSync)(filePath)) return green("create");
4810
+ const content = (0, import_fs9.readFileSync)(filePath, "utf-8");
4611
4811
  if (content.includes(markerStart)) return yellow("update");
4612
4812
  return yellow("append");
4613
4813
  }
4614
4814
  function detectMCPFileMode(filePath) {
4615
- if (!(0, import_fs8.existsSync)(filePath)) return green("create");
4815
+ if (!(0, import_fs9.existsSync)(filePath)) return green("create");
4616
4816
  return yellow("merge");
4617
4817
  }
4618
4818
  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");
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");
4622
4822
  return green("create");
4623
4823
  }
4624
4824
  function waitForEnter() {
4625
4825
  const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
4626
- return new Promise((resolve4) => {
4826
+ return new Promise((resolve5) => {
4627
4827
  rl.question(" Press Enter to close...", () => {
4628
4828
  rl.close();
4629
- resolve4();
4829
+ resolve5();
4630
4830
  });
4631
4831
  });
4632
4832
  }
@@ -4716,13 +4916,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4716
4916
  }
4717
4917
  const projectInput = await esm_default4({
4718
4918
  message: "Project directory:",
4719
- default: (0, import_path8.resolve)(process.cwd())
4919
+ default: (0, import_path9.resolve)(process.cwd())
4720
4920
  });
4721
4921
  return {
4722
4922
  technologies: selectedTechnologies,
4723
4923
  agent,
4724
4924
  azureDevOpsOrg,
4725
- projectPath: (0, import_path8.resolve)(projectInput),
4925
+ projectPath: (0, import_path9.resolve)(projectInput),
4726
4926
  baseMcpServers: options.baseMcpServers,
4727
4927
  mcpConfig,
4728
4928
  releaseVersion,
@@ -4732,12 +4932,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4732
4932
  async function previewAndConfirm(config, options) {
4733
4933
  const technologyNames = config.technologies.length > 0 ? config.technologies.map((p) => p.name.replace(/ Developer$/, "")).join(", ") : "None";
4734
4934
  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);
4935
+ const target = getAgentTarget(config.agent);
4936
+ const instructionFile = target.instructions;
4937
+ const mcpConfigFile = target.mcpConfig;
4737
4938
  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");
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");
4741
4942
  const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
4742
4943
  const mcpMode = detectMCPFileMode(mcpConfigFilePath);
4743
4944
  const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
@@ -4763,7 +4964,7 @@ async function previewAndConfirm(config, options) {
4763
4964
  console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
4764
4965
  console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
4765
4966
  if (config.installFactory) {
4766
- const factoryExists = (0, import_fs8.existsSync)((0, import_path8.join)(config.projectPath, "factory"));
4967
+ const factoryExists = (0, import_fs9.existsSync)((0, import_path9.join)(config.projectPath, "factory"));
4767
4968
  const factoryMode = factoryExists ? yellow("overwrite") : green("create");
4768
4969
  console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
4769
4970
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dlw-machine-setup",
3
- "version": "0.5.18",
3
+ "version": "0.6.0",
4
4
  "description": "One-shot installer for The Machine toolchain",
5
5
  "bin": {
6
6
  "dlw-machine-setup": "bin/installer.js"