dlw-machine-setup 0.5.17 → 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 +497 -232
  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);
@@ -3638,6 +3663,7 @@ var steps_exports = {};
3638
3663
  __export(steps_exports, {
3639
3664
  fetchContexts: () => fetch_contexts_default,
3640
3665
  fetchFactory: () => fetch_factory_default,
3666
+ runMcpInstallCommands: () => run_mcp_install_commands_default,
3641
3667
  updateGitignore: () => update_gitignore_default,
3642
3668
  writeInstructions: () => write_instructions_default,
3643
3669
  writeMcpConfig: () => write_mcp_config_default,
@@ -3810,9 +3836,204 @@ function getReadableError(status) {
3810
3836
  }
3811
3837
 
3812
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
3813
3844
  var import_fs3 = require("fs");
3814
3845
  var import_path3 = require("path");
3815
- 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
3816
4037
  var MIN_FILE_SIZE2 = 1024;
3817
4038
  function getFactoryAsset(agent) {
3818
4039
  switch (agent) {
@@ -3829,10 +4050,17 @@ var fetch_factory_default = defineStep({
3829
4050
  execute: async (ctx) => {
3830
4051
  const result = await fetchFactory(ctx.token, ctx.factoryRepo, ctx.config.projectPath, ctx.config.agent);
3831
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
+ }
3832
4059
  if (!result.success) {
3833
4060
  return { status: "failed", detail: result.failureReason };
3834
4061
  }
3835
- return { status: "success", message: result.filesInstalled.join(", ") };
4062
+ const tag = result.bundleManaged ? " (bundle)" : "";
4063
+ return { status: "success", message: result.filesInstalled.join(", ") + tag };
3836
4064
  }
3837
4065
  });
3838
4066
  async function fetchFactory(token, repo, targetDir, agent) {
@@ -3861,10 +4089,10 @@ async function fetchFactory(token, repo, targetDir, agent) {
3861
4089
  result.failureReason = `${assetName} not found in release`;
3862
4090
  return result;
3863
4091
  }
3864
- const tempDir = (0, import_path3.join)(targetDir, ".temp-factory-download");
4092
+ const tempDir = (0, import_path4.join)(targetDir, ".temp-factory-download");
3865
4093
  try {
3866
- if ((0, import_fs3.existsSync)(tempDir)) (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
3867
- (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 });
3868
4096
  const response = await fetchWithRetry(asset.url, {
3869
4097
  headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
3870
4098
  });
@@ -3872,10 +4100,10 @@ async function fetchFactory(token, repo, targetDir, agent) {
3872
4100
  result.failureReason = `Download failed (${response.status})`;
3873
4101
  return result;
3874
4102
  }
3875
- const archivePath = (0, import_path3.join)(tempDir, assetName);
4103
+ const archivePath = (0, import_path4.join)(tempDir, assetName);
3876
4104
  const arrayBuffer = await response.arrayBuffer();
3877
- (0, import_fs3.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
3878
- const stats = (0, import_fs3.statSync)(archivePath);
4105
+ (0, import_fs4.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
4106
+ const stats = (0, import_fs4.statSync)(archivePath);
3879
4107
  if (stats.size < MIN_FILE_SIZE2) {
3880
4108
  result.failureReason = "Downloaded file corrupted (too small)";
3881
4109
  return result;
@@ -3885,10 +4113,10 @@ async function fetchFactory(token, repo, targetDir, agent) {
3885
4113
  result.failureReason = "Failed to read archive contents";
3886
4114
  return result;
3887
4115
  }
3888
- const resolvedTempDir = (0, import_path3.resolve)(tempDir);
4116
+ const resolvedTempDir = (0, import_path4.resolve)(tempDir);
3889
4117
  for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
3890
- const entryPath = (0, import_path3.resolve)((0, import_path3.join)(tempDir, entry.replace(/\/$/, "")));
3891
- 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)) {
3892
4120
  result.failureReason = `Archive contains unsafe path: ${entry}`;
3893
4121
  return result;
3894
4122
  }
@@ -3898,14 +4126,17 @@ async function fetchFactory(token, repo, targetDir, agent) {
3898
4126
  result.failureReason = "Archive extraction failed";
3899
4127
  return result;
3900
4128
  }
3901
- const extractedEntries = (0, import_fs3.readdirSync)(tempDir);
4129
+ const extractedEntries = (0, import_fs4.readdirSync)(tempDir);
3902
4130
  const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === rootFolder.toLowerCase());
3903
4131
  if (!extractedFolder) {
3904
4132
  result.failureReason = `No ${rootFolder}/ folder in archive`;
3905
4133
  return result;
3906
4134
  }
3907
- const extractedPath = (0, import_path3.join)(tempDir, extractedFolder);
3908
- 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") {
3909
4140
  installCopilotFactory(extractedPath, targetDir, result);
3910
4141
  } else {
3911
4142
  installClaudeFactory(extractedPath, targetDir, result);
@@ -3914,53 +4145,73 @@ async function fetchFactory(token, repo, targetDir, agent) {
3914
4145
  } catch (error) {
3915
4146
  result.failureReason = error instanceof Error ? error.message : String(error);
3916
4147
  } finally {
3917
- if ((0, import_fs3.existsSync)(tempDir)) {
4148
+ if ((0, import_fs4.existsSync)(tempDir)) {
3918
4149
  try {
3919
- (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
4150
+ (0, import_fs4.rmSync)(tempDir, { recursive: true, force: true });
3920
4151
  } catch {
3921
4152
  }
3922
4153
  }
3923
4154
  }
3924
4155
  return result;
3925
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
+ }
3926
4177
  function installClaudeFactory(extractedPath, targetDir, result) {
3927
- const claudeDir = (0, import_path3.join)(targetDir, ".claude");
3928
- const factoryDir = (0, import_path3.join)(targetDir, "factory");
3929
- const agentsSrc = (0, import_path3.join)(extractedPath, "agents");
3930
- if ((0, import_fs3.existsSync)(agentsSrc)) {
3931
- 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"));
3932
4183
  result.filesInstalled.push(".claude/agents/");
3933
4184
  }
3934
- const hooksSrc = (0, import_path3.join)(extractedPath, "hooks");
3935
- if ((0, import_fs3.existsSync)(hooksSrc)) {
3936
- 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"));
3937
4188
  result.filesInstalled.push(".claude/hooks/");
3938
4189
  }
3939
- const skillsSrc = (0, import_path3.join)(extractedPath, "skills");
3940
- if ((0, import_fs3.existsSync)(skillsSrc)) {
3941
- 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"));
3942
4193
  result.filesInstalled.push(".claude/skills/");
3943
4194
  }
3944
- const workflowSrc = (0, import_path3.join)(extractedPath, "workflow");
3945
- if ((0, import_fs3.existsSync)(workflowSrc)) {
3946
- 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"));
3947
4198
  result.filesInstalled.push("factory/workflow/");
3948
4199
  }
3949
- const utilsSrc = (0, import_path3.join)(extractedPath, "utils");
3950
- if ((0, import_fs3.existsSync)(utilsSrc)) {
3951
- 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"));
3952
4203
  result.filesInstalled.push("factory/utils/");
3953
4204
  }
3954
- const docsSrc = (0, import_path3.join)(extractedPath, "docs");
3955
- if ((0, import_fs3.existsSync)(docsSrc)) {
3956
- 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"));
3957
4208
  result.filesInstalled.push("factory/docs/");
3958
4209
  }
3959
- const rootEntries = (0, import_fs3.readdirSync)(extractedPath, { withFileTypes: true });
4210
+ const rootEntries = (0, import_fs4.readdirSync)(extractedPath, { withFileTypes: true });
3960
4211
  for (const entry of rootEntries) {
3961
4212
  if (entry.isFile()) {
3962
- if (!(0, import_fs3.existsSync)(factoryDir)) (0, import_fs3.mkdirSync)(factoryDir, { recursive: true });
3963
- (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));
3964
4215
  result.filesInstalled.push(`factory/${entry.name}`);
3965
4216
  }
3966
4217
  }
@@ -3968,9 +4219,9 @@ function installClaudeFactory(extractedPath, targetDir, result) {
3968
4219
  result.filesInstalled.push(".claude/settings.json");
3969
4220
  }
3970
4221
  function installCopilotFactory(extractedPath, targetDir, result) {
3971
- const factoryDir = (0, import_path3.join)(targetDir, "factory");
3972
- copyDirectory2(extractedPath, factoryDir);
3973
- 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 });
3974
4225
  for (const entry of entries) {
3975
4226
  if (entry.isDirectory()) {
3976
4227
  result.filesInstalled.push(`factory/${entry.name}/`);
@@ -3980,11 +4231,11 @@ function installCopilotFactory(extractedPath, targetDir, result) {
3980
4231
  }
3981
4232
  }
3982
4233
  function configureSettings(claudeDir) {
3983
- const settingsPath = (0, import_path3.join)(claudeDir, "settings.json");
4234
+ const settingsPath = (0, import_path4.join)(claudeDir, "settings.json");
3984
4235
  let settings = {};
3985
- if ((0, import_fs3.existsSync)(settingsPath)) {
4236
+ if ((0, import_fs4.existsSync)(settingsPath)) {
3986
4237
  try {
3987
- settings = JSON.parse((0, import_fs3.readFileSync)(settingsPath, "utf8"));
4238
+ settings = JSON.parse((0, import_fs4.readFileSync)(settingsPath, "utf8"));
3988
4239
  } catch {
3989
4240
  }
3990
4241
  }
@@ -4035,26 +4286,26 @@ function configureSettings(claudeDir) {
4035
4286
  if (!hasMonitor) settings.hooks.UserPromptSubmit.push(contextMonitorHook);
4036
4287
  }
4037
4288
  settings.statusLine = { type: "command", command: "node .claude/hooks/factory-statusline.cjs" };
4038
- if (!(0, import_fs3.existsSync)(claudeDir)) (0, import_fs3.mkdirSync)(claudeDir, { recursive: true });
4039
- (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");
4040
4291
  }
4041
- function copyDirectory2(source, target) {
4042
- if (!(0, import_fs3.existsSync)(target)) (0, import_fs3.mkdirSync)(target, { recursive: true });
4043
- 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 });
4044
4295
  for (const entry of entries) {
4045
- const sourcePath = (0, import_path3.join)(source, entry.name);
4046
- 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);
4047
4298
  if (entry.isDirectory()) {
4048
- copyDirectory2(sourcePath, targetPath);
4299
+ copyDirectory3(sourcePath, targetPath);
4049
4300
  } else {
4050
- (0, import_fs3.copyFileSync)(sourcePath, targetPath);
4301
+ (0, import_fs4.copyFileSync)(sourcePath, targetPath);
4051
4302
  }
4052
4303
  }
4053
4304
  }
4054
4305
 
4055
4306
  // src/steps/setup/write-instructions.ts
4056
- var import_fs4 = require("fs");
4057
- var import_path4 = require("path");
4307
+ var import_fs5 = require("fs");
4308
+ var import_path5 = require("path");
4058
4309
 
4059
4310
  // src/steps/shared.ts
4060
4311
  function getFilteredMcpConfig(ctx) {
@@ -4078,65 +4329,45 @@ var red = (text) => `\x1B[31m${text}\x1B[0m`;
4078
4329
  var write_instructions_default = defineStep({
4079
4330
  name: "write-instructions",
4080
4331
  label: "Writing instruction file",
4081
- 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,
4082
4333
  execute: async (ctx) => {
4083
4334
  const filteredMcpConfig = getFilteredMcpConfig(ctx);
4084
4335
  const domains = ctx.installed.domainsInstalled ?? [];
4085
4336
  const agent = ctx.config.agent;
4086
4337
  const projectPath = ctx.config.projectPath;
4087
4338
  const factoryInstalled = ctx.installed.factoryInstalled ?? false;
4088
- const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, factoryInstalled);
4089
- switch (agent) {
4090
- case "claude-code": {
4091
- upsertBlock((0, import_path4.join)(projectPath, "CLAUDE.md"), content);
4092
- break;
4093
- }
4094
- case "github-copilot": {
4095
- const githubDir = (0, import_path4.join)(projectPath, ".github");
4096
- if (!(0, import_fs4.existsSync)(githubDir)) (0, import_fs4.mkdirSync)(githubDir, { recursive: true });
4097
- upsertBlock((0, import_path4.join)(githubDir, "copilot-instructions.md"), content);
4098
- break;
4099
- }
4100
- case "cursor": {
4101
- const cursorDir = (0, import_path4.join)(projectPath, ".cursor", "rules");
4102
- if (!(0, import_fs4.existsSync)(cursorDir)) (0, import_fs4.mkdirSync)(cursorDir, { recursive: true });
4103
- const filePath = (0, import_path4.join)(cursorDir, "instructions.mdc");
4104
- if (!(0, import_fs4.existsSync)(filePath)) {
4105
- (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, `---
4106
4348
  description: AI development instructions from One-Shot Installer
4107
4349
  alwaysApply: true
4108
4350
  ---
4109
4351
 
4110
4352
  `, "utf-8");
4111
- }
4112
- upsertBlock(filePath, content);
4113
- break;
4114
- }
4115
4353
  }
4116
- 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 };
4117
4360
  }
4118
4361
  });
4119
- function getInstructionFilePath(agent) {
4120
- switch (agent) {
4121
- case "claude-code":
4122
- return "CLAUDE.md";
4123
- case "github-copilot":
4124
- return ".github/copilot-instructions.md";
4125
- case "cursor":
4126
- return ".cursor/rules/instructions.mdc";
4127
- default:
4128
- return "CLAUDE.md";
4129
- }
4130
- }
4131
4362
  function upsertBlock(filePath, block) {
4132
4363
  const marked = `${MARKER_START}
4133
4364
  ${block}
4134
4365
  ${MARKER_END}`;
4135
- if (!(0, import_fs4.existsSync)(filePath)) {
4136
- (0, import_fs4.writeFileSync)(filePath, marked, "utf-8");
4366
+ if (!(0, import_fs5.existsSync)(filePath)) {
4367
+ (0, import_fs5.writeFileSync)(filePath, marked, "utf-8");
4137
4368
  return;
4138
4369
  }
4139
- const existing = (0, import_fs4.readFileSync)(filePath, "utf-8");
4370
+ const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
4140
4371
  const start = existing.indexOf(MARKER_START);
4141
4372
  const end = existing.indexOf(MARKER_END);
4142
4373
  const hasStart = start !== -1;
@@ -4161,23 +4392,44 @@ ${MARKER_END}`;
4161
4392
  }
4162
4393
  if (hasStart && hasEnd) {
4163
4394
  const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
4164
- (0, import_fs4.writeFileSync)(filePath, updated, "utf-8");
4395
+ (0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
4165
4396
  } else {
4166
4397
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4167
- (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");
4168
4420
  }
4169
4421
  }
4170
4422
  function collectMdFiles(dir) {
4171
- if (!(0, import_fs4.existsSync)(dir)) return [];
4172
- 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 });
4173
4425
  return entries.filter((entry) => {
4174
- const fullPath = (0, import_path4.join)(dir, entry);
4175
- 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();
4176
4428
  }).map((entry) => entry.replace(/\\/g, "/")).sort();
4177
4429
  }
4178
4430
  function extractFirstHeading(filePath) {
4179
4431
  try {
4180
- const content = (0, import_fs4.readFileSync)(filePath, "utf-8");
4432
+ const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
4181
4433
  const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
4182
4434
  const match = withoutFrontmatter.match(/^#\s+(.+)/m);
4183
4435
  if (match) {
@@ -4195,16 +4447,16 @@ function formatPathRef(contextPath, description, agent) {
4195
4447
  return `- \`${contextPath}\` \u2014 ${description}`;
4196
4448
  }
4197
4449
  function resolveDomainFolder(domain, contextsDir) {
4198
- if ((0, import_fs4.existsSync)(contextsDir)) {
4450
+ if ((0, import_fs5.existsSync)(contextsDir)) {
4199
4451
  try {
4200
- const entries = (0, import_fs4.readdirSync)(contextsDir);
4452
+ const entries = (0, import_fs5.readdirSync)(contextsDir);
4201
4453
  const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
4202
- 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) };
4203
4455
  } catch {
4204
4456
  }
4205
4457
  }
4206
4458
  const fallback = domain.toUpperCase();
4207
- return { folderName: fallback, folderPath: (0, import_path4.join)(contextsDir, fallback) };
4459
+ return { folderName: fallback, folderPath: (0, import_path5.join)(contextsDir, fallback) };
4208
4460
  }
4209
4461
  function buildContextRefsSection(domains, agent, contextsDir) {
4210
4462
  const lines2 = [
@@ -4216,34 +4468,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
4216
4468
  let hasAnyFiles = false;
4217
4469
  for (const domain of domains) {
4218
4470
  const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
4219
- if (!(0, import_fs4.existsSync)(domainPath)) continue;
4471
+ if (!(0, import_fs5.existsSync)(domainPath)) continue;
4220
4472
  const domainFiles = [];
4221
- const ctxInstructions = (0, import_path4.join)(domainPath, "context-instructions.md");
4222
- if ((0, import_fs4.existsSync)(ctxInstructions)) {
4473
+ const ctxInstructions = (0, import_path5.join)(domainPath, "context-instructions.md");
4474
+ if ((0, import_fs5.existsSync)(ctxInstructions)) {
4223
4475
  const desc = extractFirstHeading(ctxInstructions);
4224
4476
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
4225
4477
  }
4226
- const instructionsMd = (0, import_path4.join)(domainPath, "core", "instructions.md");
4227
- if ((0, import_fs4.existsSync)(instructionsMd)) {
4478
+ const instructionsMd = (0, import_path5.join)(domainPath, "core", "instructions.md");
4479
+ if ((0, import_fs5.existsSync)(instructionsMd)) {
4228
4480
  const desc = extractFirstHeading(instructionsMd);
4229
4481
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
4230
4482
  }
4231
- const coreDir = (0, import_path4.join)(domainPath, "core");
4232
- if ((0, import_fs4.existsSync)(coreDir)) {
4483
+ const coreDir = (0, import_path5.join)(domainPath, "core");
4484
+ if ((0, import_fs5.existsSync)(coreDir)) {
4233
4485
  const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
4234
4486
  for (const file of coreFiles) {
4235
- const desc = extractFirstHeading((0, import_path4.join)(coreDir, file));
4487
+ const desc = extractFirstHeading((0, import_path5.join)(coreDir, file));
4236
4488
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
4237
4489
  }
4238
4490
  }
4239
- const refDir = (0, import_path4.join)(domainPath, "reference");
4240
- if ((0, import_fs4.existsSync)(refDir)) {
4491
+ const refDir = (0, import_path5.join)(domainPath, "reference");
4492
+ if ((0, import_fs5.existsSync)(refDir)) {
4241
4493
  const refFiles = collectMdFiles(refDir);
4242
4494
  if (refFiles.length > 0) {
4243
4495
  domainFiles.push(``);
4244
4496
  domainFiles.push(`**Reference & cheat sheets:**`);
4245
4497
  for (const file of refFiles) {
4246
- const desc = extractFirstHeading((0, import_path4.join)(refDir, file));
4498
+ const desc = extractFirstHeading((0, import_path5.join)(refDir, file));
4247
4499
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
4248
4500
  }
4249
4501
  }
@@ -4321,7 +4573,7 @@ function buildFactorySection(agent) {
4321
4573
  ].join("\n");
4322
4574
  }
4323
4575
  function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled) {
4324
- const contextsDir = (0, import_path4.join)(projectPath, "_ai-context");
4576
+ const contextsDir = (0, import_path5.join)(projectPath, "_ai-context");
4325
4577
  const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
4326
4578
  lines2.push(buildContextRefsSection(domains, agent, contextsDir));
4327
4579
  if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
@@ -4330,8 +4582,8 @@ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, facto
4330
4582
  }
4331
4583
 
4332
4584
  // src/steps/setup/write-mcp-config.ts
4333
- var import_fs5 = require("fs");
4334
- var import_path5 = require("path");
4585
+ var import_fs6 = require("fs");
4586
+ var import_path6 = require("path");
4335
4587
  var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
4336
4588
  var write_mcp_config_default = defineStep({
4337
4589
  name: "write-mcp-config",
@@ -4339,23 +4591,23 @@ var write_mcp_config_default = defineStep({
4339
4591
  when: (ctx) => Object.keys(ctx.config.mcpConfig).length > 0,
4340
4592
  execute: async (ctx) => {
4341
4593
  const filteredMcpConfig = getFilteredMcpConfig(ctx);
4342
- const target = getAgentMCPTarget(ctx.config.agent);
4343
- const mcpJsonPath = (0, import_path5.join)(ctx.config.projectPath, target.filePath);
4344
- if (target.dir) {
4345
- const dir = (0, import_path5.join)(ctx.config.projectPath, target.dir);
4346
- 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 });
4347
4599
  }
4348
4600
  let existingFile = {};
4349
- if ((0, import_fs5.existsSync)(mcpJsonPath)) {
4601
+ if ((0, import_fs6.existsSync)(mcpJsonPath)) {
4350
4602
  try {
4351
- existingFile = JSON.parse((0, import_fs5.readFileSync)(mcpJsonPath, "utf-8"));
4603
+ existingFile = JSON.parse((0, import_fs6.readFileSync)(mcpJsonPath, "utf-8"));
4352
4604
  } catch {
4353
4605
  const box = [
4354
4606
  "",
4355
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"),
4356
4608
  red2(" \u2551 \u26A0 MCP CONFIG HAS INVALID JSON \u2551"),
4357
4609
  red2(" \u2551 \u2551"),
4358
- red2(` \u2551 ${target.filePath} could not be parsed.`.padEnd(52) + "\u2551"),
4610
+ red2(` \u2551 ${target.mcpConfig} could not be parsed.`.padEnd(52) + "\u2551"),
4359
4611
  red2(" \u2551 Existing MCP servers may be lost. \u2551"),
4360
4612
  red2(" \u2551 Check the file after installation completes. \u2551"),
4361
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"),
@@ -4365,16 +4617,16 @@ var write_mcp_config_default = defineStep({
4365
4617
  }
4366
4618
  }
4367
4619
  const addedServers = [];
4368
- const existingServers = existingFile[target.rootKey] ?? {};
4620
+ const existingServers = existingFile[target.mcpRootKey] ?? {};
4369
4621
  const newServers = {};
4370
4622
  for (const [serverName, serverConfig] of Object.entries(filteredMcpConfig)) {
4371
- const { description, useWhen, active, ...mcpFields } = serverConfig;
4623
+ const { description, useWhen, active, installCommand, ...mcpFields } = serverConfig;
4372
4624
  newServers[serverName] = mcpFields;
4373
4625
  addedServers.push(serverName);
4374
4626
  }
4375
4627
  const mergedServers = { ...existingServers, ...newServers };
4376
- const outputFile = { ...existingFile, [target.rootKey]: mergedServers };
4377
- (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");
4378
4630
  ctx.installed.mcpServersAdded = addedServers;
4379
4631
  return {
4380
4632
  status: "success",
@@ -4382,24 +4634,73 @@ var write_mcp_config_default = defineStep({
4382
4634
  };
4383
4635
  }
4384
4636
  });
4385
- function getAgentMCPTarget(agent) {
4386
- switch (agent) {
4387
- case "claude-code":
4388
- return { filePath: ".mcp.json", rootKey: "mcpServers", dir: null };
4389
- case "cursor":
4390
- return { filePath: ".cursor/mcp.json", rootKey: "mcpServers", dir: ".cursor" };
4391
- case "github-copilot":
4392
- default:
4393
- return { filePath: ".vscode/mcp.json", rootKey: "servers", dir: ".vscode" };
4637
+
4638
+ // src/steps/setup/run-mcp-install-commands.ts
4639
+ var import_child_process4 = require("child_process");
4640
+ var run_mcp_install_commands_default = defineStep({
4641
+ name: "run-mcp-install-commands",
4642
+ label: "Registering MCP servers with Claude Code",
4643
+ when: (ctx) => {
4644
+ if (ctx.config.agent !== "claude-code") return false;
4645
+ const filtered = getFilteredMcpConfig(ctx);
4646
+ return Object.values(filtered).some((s) => typeof s.installCommand === "string" && s.installCommand.length > 0);
4647
+ },
4648
+ execute: async (ctx) => {
4649
+ if (!isClaudeCliAvailable()) {
4650
+ return {
4651
+ status: "skipped",
4652
+ detail: "`claude` CLI not found on PATH \u2014 skipping CLI registration (project .mcp.json was still written)"
4653
+ };
4654
+ }
4655
+ const filtered = getFilteredMcpConfig(ctx);
4656
+ const succeeded = [];
4657
+ const failed = [];
4658
+ for (const [name, cfg] of Object.entries(filtered)) {
4659
+ const cmd = cfg.installCommand;
4660
+ if (typeof cmd !== "string" || cmd.length === 0) continue;
4661
+ const result = (0, import_child_process4.spawnSync)(cmd, {
4662
+ shell: true,
4663
+ stdio: "pipe",
4664
+ encoding: "utf-8",
4665
+ cwd: ctx.config.projectPath
4666
+ });
4667
+ if (result.status === 0) {
4668
+ succeeded.push(name);
4669
+ } else {
4670
+ const stderr = (result.stderr ?? "").trim();
4671
+ const reason = stderr.length > 0 ? stderr.split("\n")[0] : `exit ${result.status}`;
4672
+ failed.push({ name, reason });
4673
+ }
4674
+ }
4675
+ ctx.installed.mcpInstallCommandsRun = { succeeded, failed };
4676
+ if (failed.length === 0) {
4677
+ return {
4678
+ status: "success",
4679
+ message: succeeded.length > 0 ? succeeded.join(", ") : void 0
4680
+ };
4681
+ }
4682
+ const failedSummary = failed.map((f) => `${f.name} (${f.reason})`).join("; ");
4683
+ if (succeeded.length === 0) {
4684
+ return { status: "failed", detail: failedSummary };
4685
+ }
4686
+ return {
4687
+ status: "success",
4688
+ message: `${succeeded.join(", ")}; failed: ${failed.map((f) => f.name).join(", ")}`,
4689
+ detail: `Some registrations failed: ${failedSummary}`
4690
+ };
4394
4691
  }
4692
+ });
4693
+ function isClaudeCliAvailable() {
4694
+ const check2 = (0, import_child_process4.spawnSync)("claude --version", { shell: true, stdio: "ignore" });
4695
+ return check2.status === 0;
4395
4696
  }
4396
4697
 
4397
4698
  // src/steps/setup/update-gitignore.ts
4398
- var import_fs6 = require("fs");
4399
- var import_path6 = require("path");
4699
+ var import_fs7 = require("fs");
4700
+ var import_path7 = require("path");
4400
4701
  var MARKER_START2 = "# one-shot-installer:start";
4401
4702
  var MARKER_END2 = "# one-shot-installer:end";
4402
- var GITIGNORE_ENTRIES = [
4703
+ var CORE_GITIGNORE_ENTRIES = [
4403
4704
  "# One-Shot installer generated files",
4404
4705
  ".one-shot-state.json",
4405
4706
  ".mcp.json",
@@ -4411,8 +4712,9 @@ var GITIGNORE_ENTRIES = [
4411
4712
  ".claude/agents/",
4412
4713
  ".claude/agent-memory/",
4413
4714
  ".claude/plans/",
4414
- ".claude/settings.local.json",
4415
- "",
4715
+ ".claude/settings.local.json"
4716
+ ];
4717
+ var LEGACY_FACTORY_GITIGNORE_ENTRIES = [
4416
4718
  "# Factory runtime state (generated by workflow, not committed)",
4417
4719
  "factory/STATE.md",
4418
4720
  "factory/PROJECT.md",
@@ -4430,40 +4732,45 @@ var update_gitignore_default = defineStep({
4430
4732
  name: "update-gitignore",
4431
4733
  label: "Updating .gitignore",
4432
4734
  execute: async (ctx) => {
4433
- const gitignorePath = (0, import_path6.join)(ctx.config.projectPath, ".gitignore");
4434
- const block = [MARKER_START2, ...GITIGNORE_ENTRIES, MARKER_END2].join("\n");
4435
- if (!(0, import_fs6.existsSync)(gitignorePath)) {
4436
- (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");
4437
4743
  return { status: "success" };
4438
4744
  }
4439
- const existing = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
4745
+ const existing = (0, import_fs7.readFileSync)(gitignorePath, "utf-8");
4440
4746
  const start = existing.indexOf(MARKER_START2);
4441
4747
  const end = existing.indexOf(MARKER_END2);
4442
4748
  if (start !== -1 && end !== -1 && end > start) {
4443
4749
  const updated = existing.slice(0, start) + block + existing.slice(end + MARKER_END2.length);
4444
- (0, import_fs6.writeFileSync)(gitignorePath, updated, "utf-8");
4750
+ (0, import_fs7.writeFileSync)(gitignorePath, updated, "utf-8");
4445
4751
  } else {
4446
4752
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4447
- (0, import_fs6.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
4753
+ (0, import_fs7.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
4448
4754
  }
4449
4755
  return { status: "success" };
4450
4756
  }
4451
4757
  });
4452
4758
 
4453
4759
  // src/steps/setup/write-state.ts
4454
- var import_fs7 = require("fs");
4455
- var import_path7 = require("path");
4760
+ var import_fs8 = require("fs");
4761
+ var import_path8 = require("path");
4456
4762
  var import_os2 = require("os");
4457
- var INSTALLER_VERSION = "0.5.17";
4763
+ var INSTALLER_VERSION = "0.6.0";
4458
4764
  var write_state_default = defineStep({
4459
4765
  name: "write-state",
4460
4766
  label: "Saving installation state",
4461
4767
  execute: async (ctx) => {
4462
- 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");
4463
4769
  const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
4464
4770
  const filteredMcpConfig = Object.fromEntries(
4465
4771
  Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
4466
4772
  );
4773
+ const target = getAgentTarget(ctx.config.agent);
4467
4774
  const state = {
4468
4775
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4469
4776
  installerVersion: INSTALLER_VERSION,
@@ -4477,65 +4784,19 @@ var write_state_default = defineStep({
4477
4784
  mcpConfigs: filteredMcpConfig,
4478
4785
  factoryInstalled: ctx.installed.factoryInstalled ?? false,
4479
4786
  files: {
4480
- instructions: getInstructionFilePath2(ctx.config.agent),
4481
- mcpConfig: getMCPConfigPath(ctx.config.agent),
4787
+ instructions: target.instructions,
4788
+ mcpConfig: target.mcpConfig,
4482
4789
  contexts: "_ai-context/",
4483
4790
  factory: ctx.installed.factoryInstalled ? "factory/" : null,
4484
- 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")
4485
4792
  }
4486
4793
  };
4487
- (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");
4488
4795
  return { status: "success" };
4489
4796
  }
4490
4797
  });
4491
- function getInstructionFilePath2(agent) {
4492
- switch (agent) {
4493
- case "claude-code":
4494
- return "CLAUDE.md";
4495
- case "github-copilot":
4496
- return ".github/copilot-instructions.md";
4497
- case "cursor":
4498
- return ".cursor/rules/instructions.mdc";
4499
- default:
4500
- return "CLAUDE.md";
4501
- }
4502
- }
4503
- function getMCPConfigPath(agent) {
4504
- switch (agent) {
4505
- case "claude-code":
4506
- return ".mcp.json";
4507
- case "cursor":
4508
- return ".cursor/mcp.json";
4509
- case "github-copilot":
4510
- default:
4511
- return ".vscode/mcp.json";
4512
- }
4513
- }
4514
4798
 
4515
4799
  // src/index.ts
4516
- function getInstructionFilePath3(agent) {
4517
- switch (agent) {
4518
- case "claude-code":
4519
- return "CLAUDE.md";
4520
- case "github-copilot":
4521
- return ".github/copilot-instructions.md";
4522
- case "cursor":
4523
- return ".cursor/rules/instructions.mdc";
4524
- default:
4525
- return "CLAUDE.md";
4526
- }
4527
- }
4528
- function getMCPConfigPath2(agent) {
4529
- switch (agent) {
4530
- case "claude-code":
4531
- return ".mcp.json";
4532
- case "cursor":
4533
- return ".cursor/mcp.json";
4534
- case "github-copilot":
4535
- default:
4536
- return ".vscode/mcp.json";
4537
- }
4538
- }
4539
4800
  function formatMCPCommand(server) {
4540
4801
  if (server.url) return server.url;
4541
4802
  if (server.command) return `${server.command} ${(server.args ?? []).join(" ")}`.trimEnd();
@@ -4545,27 +4806,27 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
4545
4806
  var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
4546
4807
  var green = (text) => `\x1B[32m${text}\x1B[0m`;
4547
4808
  function detectMarkerFileMode(filePath, markerStart) {
4548
- if (!(0, import_fs8.existsSync)(filePath)) return green("create");
4549
- 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");
4550
4811
  if (content.includes(markerStart)) return yellow("update");
4551
4812
  return yellow("append");
4552
4813
  }
4553
4814
  function detectMCPFileMode(filePath) {
4554
- if (!(0, import_fs8.existsSync)(filePath)) return green("create");
4815
+ if (!(0, import_fs9.existsSync)(filePath)) return green("create");
4555
4816
  return yellow("merge");
4556
4817
  }
4557
4818
  function detectContextMode(projectPath, domain) {
4558
- const contextDir = (0, import_path8.join)(projectPath, "_ai-context", domain.toUpperCase());
4559
- const contextDirLower = (0, import_path8.join)(projectPath, "_ai-context", domain);
4560
- 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");
4561
4822
  return green("create");
4562
4823
  }
4563
4824
  function waitForEnter() {
4564
4825
  const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
4565
- return new Promise((resolve4) => {
4826
+ return new Promise((resolve5) => {
4566
4827
  rl.question(" Press Enter to close...", () => {
4567
4828
  rl.close();
4568
- resolve4();
4829
+ resolve5();
4569
4830
  });
4570
4831
  });
4571
4832
  }
@@ -4648,17 +4909,20 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4648
4909
  mcpConfig["azure-devops"].args = mcpConfig["azure-devops"].args?.map(
4649
4910
  (arg) => arg === "__AZURE_ORG__" ? azureDevOpsOrg : arg
4650
4911
  );
4912
+ if (mcpConfig["azure-devops"].installCommand) {
4913
+ mcpConfig["azure-devops"].installCommand = mcpConfig["azure-devops"].installCommand.replace(/__AZURE_ORG__/g, azureDevOpsOrg);
4914
+ }
4651
4915
  }
4652
4916
  }
4653
4917
  const projectInput = await esm_default4({
4654
4918
  message: "Project directory:",
4655
- default: (0, import_path8.resolve)(process.cwd())
4919
+ default: (0, import_path9.resolve)(process.cwd())
4656
4920
  });
4657
4921
  return {
4658
4922
  technologies: selectedTechnologies,
4659
4923
  agent,
4660
4924
  azureDevOpsOrg,
4661
- projectPath: (0, import_path8.resolve)(projectInput),
4925
+ projectPath: (0, import_path9.resolve)(projectInput),
4662
4926
  baseMcpServers: options.baseMcpServers,
4663
4927
  mcpConfig,
4664
4928
  releaseVersion,
@@ -4668,12 +4932,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4668
4932
  async function previewAndConfirm(config, options) {
4669
4933
  const technologyNames = config.technologies.length > 0 ? config.technologies.map((p) => p.name.replace(/ Developer$/, "")).join(", ") : "None";
4670
4934
  const agentDisplay = options.agents.find((a) => a.value === config.agent)?.name ?? config.agent;
4671
- const instructionFile = getInstructionFilePath3(config.agent);
4672
- const mcpConfigFile = getMCPConfigPath2(config.agent);
4935
+ const target = getAgentTarget(config.agent);
4936
+ const instructionFile = target.instructions;
4937
+ const mcpConfigFile = target.mcpConfig;
4673
4938
  const serverEntries = Object.entries(config.mcpConfig);
4674
- const instructionFilePath = (0, import_path8.join)(config.projectPath, instructionFile);
4675
- const mcpConfigFilePath = (0, import_path8.join)(config.projectPath, mcpConfigFile);
4676
- 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");
4677
4942
  const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
4678
4943
  const mcpMode = detectMCPFileMode(mcpConfigFilePath);
4679
4944
  const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
@@ -4699,7 +4964,7 @@ async function previewAndConfirm(config, options) {
4699
4964
  console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
4700
4965
  console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
4701
4966
  if (config.installFactory) {
4702
- 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"));
4703
4968
  const factoryMode = factoryExists ? yellow("overwrite") : green("create");
4704
4969
  console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
4705
4970
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dlw-machine-setup",
3
- "version": "0.5.17",
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"