dlw-machine-setup 0.5.7 → 0.5.9

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 +510 -545
  2. package/package.json +1 -1
package/bin/installer.js CHANGED
@@ -9,6 +9,10 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __commonJS = (cb, mod) => function __require() {
10
10
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
11
  };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
12
16
  var __copyProps = (to, from, except, desc) => {
13
17
  if (from && typeof from === "object" || typeof from === "function") {
14
18
  for (let key of __getOwnPropNames(from))
@@ -3240,10 +3244,9 @@ ${page}${helpTipBottom}${choiceDescription}${import_ansi_escapes3.default.cursor
3240
3244
  });
3241
3245
 
3242
3246
  // src/index.ts
3243
- var import_fs7 = require("fs");
3247
+ var import_fs8 = require("fs");
3244
3248
  var import_readline = require("readline");
3245
- var import_path7 = require("path");
3246
- var import_os2 = require("os");
3249
+ var import_path8 = require("path");
3247
3250
 
3248
3251
  // src/utils/fetch.ts
3249
3252
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -3286,7 +3289,7 @@ async function fetchWithRetry(url, options = {}, maxRetries = DEFAULT_MAX_RETRIE
3286
3289
  throw lastError || new Error("Failed after maximum retries");
3287
3290
  }
3288
3291
 
3289
- // src/utils/data/fetch-wizard-options.ts
3292
+ // src/utils/wizard-options.ts
3290
3293
  async function fetchWizardOptions(token, repo) {
3291
3294
  try {
3292
3295
  const headers = {
@@ -3315,7 +3318,7 @@ async function fetchWizardOptions(token, repo) {
3315
3318
  }
3316
3319
  }
3317
3320
 
3318
- // src/utils/data/discover-repo.ts
3321
+ // src/utils/discover-repo.ts
3319
3322
  var GITHUB_ORG = "DLW-INT-SAP-DEV";
3320
3323
  var DEFAULT_TOPIC = "one-shot-data";
3321
3324
  async function discoverRepo(token, topic = DEFAULT_TOPIC) {
@@ -3380,12 +3383,7 @@ async function tryOrgReposList(headers, topic) {
3380
3383
  }
3381
3384
  }
3382
3385
 
3383
- // src/utils/data/fetch-contexts.ts
3384
- var import_fs2 = require("fs");
3385
- var import_path2 = require("path");
3386
- var import_child_process = require("child_process");
3387
-
3388
- // src/utils/auth/github-auth.ts
3386
+ // src/utils/github-auth.ts
3389
3387
  var import_fs = require("fs");
3390
3388
  var import_path = require("path");
3391
3389
  var import_os = require("os");
@@ -3544,56 +3542,154 @@ async function getGitHubToken() {
3544
3542
  return await authenticateWithGitHub();
3545
3543
  }
3546
3544
 
3547
- // src/utils/data/fetch-contexts.ts
3548
- var MIN_FILE_SIZE = 1024;
3549
- async function fetchContexts(options = {}) {
3550
- const { domains = [], targetDir = process.cwd(), force = false, token, repo } = options;
3551
- if (!repo) {
3552
- throw new Error("Repository not specified. Discovery may have failed.");
3545
+ // src/utils/mcp-config.ts
3546
+ function buildMCPConfiguration(selectedItems, baseMcpServers, allMcpServers) {
3547
+ const config = {};
3548
+ for (const serverName of baseMcpServers) {
3549
+ if (allMcpServers[serverName]) {
3550
+ config[serverName] = allMcpServers[serverName];
3551
+ }
3553
3552
  }
3554
- const result = {
3555
- successful: [],
3556
- failed: [],
3557
- skipped: [],
3558
- failureReasons: {}
3559
- };
3560
- if (domains.length === 0) {
3561
- return result;
3553
+ for (const item of selectedItems) {
3554
+ for (const serverName of item.mcpServers) {
3555
+ if (allMcpServers[serverName] && !config[serverName]) {
3556
+ config[serverName] = allMcpServers[serverName];
3557
+ }
3558
+ }
3562
3559
  }
3563
- await checkPrerequisites();
3564
- const githubToken = token ?? await getGitHubToken();
3565
- try {
3566
- const releaseUrl = `https://api.github.com/repos/${repo}/releases/latest`;
3567
- const headers = {
3568
- "Accept": "application/vnd.github+json",
3569
- "Authorization": `Bearer ${githubToken}`
3570
- };
3571
- const releaseResponse = await fetchWithRetry(releaseUrl, { headers });
3572
- if (!releaseResponse.ok) {
3573
- throw new Error(
3574
- `GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`
3575
- );
3560
+ return config;
3561
+ }
3562
+
3563
+ // src/utils/mod.ts
3564
+ async function loadWizardOptions(token, repo) {
3565
+ const remote = await fetchWizardOptions(token, repo);
3566
+ if (!remote) {
3567
+ throw new Error("Failed to load configuration from GitHub release. Check your network and token.");
3568
+ }
3569
+ const { options, releaseVersion } = remote;
3570
+ const filteredMcpServers = Object.fromEntries(
3571
+ Object.entries(options.mcpServers).filter(([, config]) => config.active !== false)
3572
+ );
3573
+ return {
3574
+ personas: options.personas.personas.filter((p) => p.active !== false),
3575
+ agents: options.agents.filter((a) => a.active !== false),
3576
+ baseMcpServers: options.personas.baseMcpServers,
3577
+ mcpServers: filteredMcpServers,
3578
+ releaseVersion
3579
+ };
3580
+ }
3581
+
3582
+ // src/pipeline.ts
3583
+ function defineStep(def) {
3584
+ return def;
3585
+ }
3586
+ async function runPipeline(steps, ctx) {
3587
+ const entries = [];
3588
+ for (const step of steps) {
3589
+ if (step.when && !step.when(ctx)) {
3590
+ entries.push({ name: step.name, label: step.label, result: { status: "skipped" } });
3591
+ continue;
3576
3592
  }
3577
- const releaseData = await releaseResponse.json();
3578
- const contextsDir = (0, import_path2.join)(targetDir, "_ai-context");
3579
- const tempDir = (0, import_path2.join)(targetDir, ".temp-download");
3593
+ process.stdout.write(` ${step.label}... `);
3580
3594
  try {
3581
- if (!(0, import_fs2.existsSync)(contextsDir)) {
3582
- (0, import_fs2.mkdirSync)(contextsDir, { recursive: true });
3595
+ const result = await step.execute(ctx);
3596
+ entries.push({ name: step.name, label: step.label, result });
3597
+ if (result.status === "success") {
3598
+ console.log("\u2713");
3599
+ } else {
3600
+ console.log("\u2717");
3583
3601
  }
3584
- if ((0, import_fs2.existsSync)(tempDir)) {
3585
- (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
3602
+ if (result.detail) {
3603
+ console.log(` ${result.detail}`);
3586
3604
  }
3587
- (0, import_fs2.mkdirSync)(tempDir, { recursive: true });
3588
- } catch (error) {
3589
- throw new Error(`Failed to create directories: ${error instanceof Error ? error.message : "Unknown error"}`);
3605
+ } catch (err) {
3606
+ const detail = err instanceof Error ? err.message : String(err);
3607
+ entries.push({
3608
+ name: step.name,
3609
+ label: step.label,
3610
+ result: { status: "failed", detail }
3611
+ });
3612
+ console.log("\u2717");
3613
+ console.log(` ${detail}`);
3590
3614
  }
3615
+ }
3616
+ return { entries };
3617
+ }
3618
+
3619
+ // src/steps/index.ts
3620
+ var steps_exports = {};
3621
+ __export(steps_exports, {
3622
+ fetchContexts: () => fetch_contexts_default,
3623
+ fetchFactory: () => fetch_factory_default,
3624
+ updateGitignore: () => update_gitignore_default,
3625
+ writeInstructions: () => write_instructions_default,
3626
+ writeMcpConfig: () => write_mcp_config_default,
3627
+ writeState: () => write_state_default
3628
+ });
3629
+
3630
+ // src/steps/resources/fetch-contexts.ts
3631
+ var import_fs2 = require("fs");
3632
+ var import_path2 = require("path");
3633
+ var import_child_process = require("child_process");
3634
+ var MIN_FILE_SIZE = 1024;
3635
+ var fetch_contexts_default = defineStep({
3636
+ name: "fetch-contexts",
3637
+ label: "Downloading contexts",
3638
+ execute: async (ctx) => {
3639
+ const uniqueDomains = [...new Set(ctx.config.personas.flatMap((p) => p.domains))];
3640
+ const domainValues = uniqueDomains.map((d) => d.toLowerCase());
3641
+ const downloadResult = await fetchContexts(domainValues, ctx.token, ctx.repo, ctx.config.projectPath);
3642
+ ctx.installed.domainsInstalled = downloadResult.successful;
3643
+ ctx.installed.domainsFailed = downloadResult.failed;
3644
+ ctx.installed.failureReasons = downloadResult.failureReasons;
3645
+ const domainColWidth = Math.max(...domainValues.map((d) => d.length), 8) + 2;
3646
+ console.log("");
3647
+ for (const domain of domainValues) {
3648
+ const ok = downloadResult.successful.includes(domain);
3649
+ const reason = downloadResult.failureReasons[domain];
3650
+ const status = ok ? "\u2713" : `\u2717 ${reason ?? "Unknown error"}`;
3651
+ console.log(` ${domain.padEnd(domainColWidth)}${status}`);
3652
+ }
3653
+ if (downloadResult.failed.length > 0) {
3654
+ return {
3655
+ status: "failed",
3656
+ message: `${downloadResult.successful.join(", ")} succeeded; ${downloadResult.failed.join(", ")} failed`
3657
+ };
3658
+ }
3659
+ return {
3660
+ status: "success",
3661
+ message: downloadResult.successful.join(", ")
3662
+ };
3663
+ }
3664
+ });
3665
+ async function fetchContexts(domains, token, repo, targetDir) {
3666
+ const result = { successful: [], failed: [], failureReasons: {} };
3667
+ if (domains.length === 0) return result;
3668
+ const tarCheck = (0, import_child_process.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3669
+ if (tarCheck.status !== 0) {
3670
+ throw new Error("tar command not found. Please ensure tar is installed and available in your PATH.");
3671
+ }
3672
+ const headers = {
3673
+ "Accept": "application/vnd.github+json",
3674
+ "Authorization": `Bearer ${token}`
3675
+ };
3676
+ const releaseResponse = await fetchWithRetry(
3677
+ `https://api.github.com/repos/${repo}/releases/latest`,
3678
+ { headers }
3679
+ );
3680
+ if (!releaseResponse.ok) {
3681
+ throw new Error(`GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`);
3682
+ }
3683
+ const releaseData = await releaseResponse.json();
3684
+ const contextsDir = (0, import_path2.join)(targetDir, "_ai-context");
3685
+ const tempDir = (0, import_path2.join)(targetDir, ".temp-download");
3686
+ try {
3687
+ if (!(0, import_fs2.existsSync)(contextsDir)) (0, import_fs2.mkdirSync)(contextsDir, { recursive: true });
3688
+ if ((0, import_fs2.existsSync)(tempDir)) (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
3689
+ (0, import_fs2.mkdirSync)(tempDir, { recursive: true });
3591
3690
  for (const domain of domains) {
3592
3691
  try {
3593
3692
  const domainPath = (0, import_path2.join)(contextsDir, domain);
3594
- if ((0, import_fs2.existsSync)(domainPath) && force) {
3595
- (0, import_fs2.rmSync)(domainPath, { recursive: true, force: true });
3596
- }
3597
3693
  const archiveName = `${domain.toLowerCase()}.tar.gz`;
3598
3694
  const asset = releaseData.assets?.find((a) => a.name === archiveName);
3599
3695
  if (!asset) {
@@ -3603,10 +3699,9 @@ async function fetchContexts(options = {}) {
3603
3699
  }
3604
3700
  const downloadHeaders = {
3605
3701
  "Accept": "application/octet-stream",
3606
- "Authorization": `Bearer ${githubToken}`
3702
+ "Authorization": `Bearer ${token}`
3607
3703
  };
3608
- const downloadUrl = asset.url;
3609
- const response = await fetchWithRetry(downloadUrl, { headers: downloadHeaders });
3704
+ const response = await fetchWithRetry(asset.url, { headers: downloadHeaders });
3610
3705
  if (!response.ok) {
3611
3706
  throw new Error(`Download failed: ${getReadableError(response.status)}`);
3612
3707
  }
@@ -3618,9 +3713,7 @@ async function fetchContexts(options = {}) {
3618
3713
  throw new Error("File corrupted");
3619
3714
  }
3620
3715
  const listResult = (0, import_child_process.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
3621
- if (listResult.status !== 0) {
3622
- throw new Error("Failed to read archive contents");
3623
- }
3716
+ if (listResult.status !== 0) throw new Error("Failed to read archive contents");
3624
3717
  const resolvedTempDir = (0, import_path2.resolve)(tempDir);
3625
3718
  for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
3626
3719
  const entryPath = (0, import_path2.resolve)((0, import_path2.join)(tempDir, entry.replace(/\/$/, "")));
@@ -3629,47 +3722,38 @@ async function fetchContexts(options = {}) {
3629
3722
  }
3630
3723
  }
3631
3724
  const extractResult = (0, import_child_process.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
3632
- if (extractResult.status !== 0) {
3633
- throw new Error("Archive extraction failed");
3634
- }
3725
+ if (extractResult.status !== 0) throw new Error("Archive extraction failed");
3635
3726
  const extractedEntries = (0, import_fs2.readdirSync)(tempDir);
3636
3727
  const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === domain.toLowerCase());
3637
- if (!extractedFolder) {
3638
- throw new Error("Extraction failed");
3639
- }
3640
- const extractedPath = (0, import_path2.join)(tempDir, extractedFolder);
3641
- copyDirectory(extractedPath, domainPath);
3728
+ if (!extractedFolder) throw new Error("Extraction failed");
3729
+ copyDirectory((0, import_path2.join)(tempDir, extractedFolder), domainPath);
3642
3730
  result.successful.push(domain);
3643
3731
  } catch (error) {
3644
- const reason = error instanceof Error ? error.message : "Unknown error";
3645
3732
  result.failed.push(domain);
3646
- result.failureReasons[domain] = reason;
3647
- }
3648
- }
3649
- try {
3650
- if ((0, import_fs2.existsSync)(tempDir)) {
3651
- (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
3733
+ result.failureReasons[domain] = error instanceof Error ? error.message : "Unknown error";
3652
3734
  }
3653
- } catch (error) {
3654
3735
  }
3655
- } catch (error) {
3656
- const tempDir = (0, import_path2.join)(targetDir, ".temp-download");
3736
+ } finally {
3657
3737
  if ((0, import_fs2.existsSync)(tempDir)) {
3658
3738
  try {
3659
3739
  (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
3660
3740
  } catch {
3661
3741
  }
3662
3742
  }
3663
- throw error;
3664
3743
  }
3665
3744
  return result;
3666
3745
  }
3667
- async function checkPrerequisites() {
3668
- const check2 = (0, import_child_process.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3669
- if (check2.status !== 0) {
3670
- throw new Error(
3671
- "tar command not found. Please ensure tar is installed and available in your PATH."
3672
- );
3746
+ function copyDirectory(source, target) {
3747
+ if (!(0, import_fs2.existsSync)(target)) (0, import_fs2.mkdirSync)(target, { recursive: true });
3748
+ const entries = (0, import_fs2.readdirSync)(source, { withFileTypes: true });
3749
+ for (const entry of entries) {
3750
+ const sourcePath = (0, import_path2.join)(source, entry.name);
3751
+ const targetPath = (0, import_path2.join)(target, entry.name);
3752
+ if (entry.isDirectory()) {
3753
+ copyDirectory(sourcePath, targetPath);
3754
+ } else {
3755
+ (0, import_fs2.copyFileSync)(sourcePath, targetPath);
3756
+ }
3673
3757
  }
3674
3758
  }
3675
3759
  function getReadableError(status) {
@@ -3690,44 +3774,28 @@ function getReadableError(status) {
3690
3774
  return `Unexpected error (${status})`;
3691
3775
  }
3692
3776
  }
3693
- function copyDirectory(source, target) {
3694
- let added = 0;
3695
- let overwritten = 0;
3696
- if (!(0, import_fs2.existsSync)(target)) {
3697
- (0, import_fs2.mkdirSync)(target, { recursive: true });
3698
- }
3699
- const entries = (0, import_fs2.readdirSync)(source, { withFileTypes: true });
3700
- for (const entry of entries) {
3701
- const sourcePath = (0, import_path2.join)(source, entry.name);
3702
- const targetPath = (0, import_path2.join)(target, entry.name);
3703
- if (entry.isDirectory()) {
3704
- const result = copyDirectory(sourcePath, targetPath);
3705
- added += result.added;
3706
- overwritten += result.overwritten;
3707
- } else {
3708
- if ((0, import_fs2.existsSync)(targetPath)) {
3709
- overwritten++;
3710
- } else {
3711
- added++;
3712
- }
3713
- (0, import_fs2.copyFileSync)(sourcePath, targetPath);
3714
- }
3715
- }
3716
- return { added, overwritten };
3717
- }
3718
3777
 
3719
- // src/utils/data/fetch-factory.ts
3778
+ // src/steps/resources/fetch-factory.ts
3720
3779
  var import_fs3 = require("fs");
3721
3780
  var import_path3 = require("path");
3722
3781
  var import_child_process2 = require("child_process");
3723
3782
  var MIN_FILE_SIZE2 = 1024;
3724
3783
  var FACTORY_ASSET_NAME = "factory.tar.gz";
3725
- async function fetchFactory(options) {
3726
- const { token, repo, targetDir } = options;
3727
- const result = {
3728
- success: false,
3729
- filesInstalled: []
3730
- };
3784
+ var fetch_factory_default = defineStep({
3785
+ name: "fetch-factory",
3786
+ label: "Installing Factory framework",
3787
+ when: (ctx) => ctx.config.installFactory && !!ctx.factoryRepo,
3788
+ execute: async (ctx) => {
3789
+ const result = await fetchFactory(ctx.token, ctx.factoryRepo, ctx.config.projectPath);
3790
+ ctx.installed.factoryInstalled = result.success;
3791
+ if (!result.success) {
3792
+ return { status: "failed", detail: result.failureReason };
3793
+ }
3794
+ return { status: "success", message: result.filesInstalled.join(", ") };
3795
+ }
3796
+ });
3797
+ async function fetchFactory(token, repo, targetDir) {
3798
+ const result = { success: false, filesInstalled: [] };
3731
3799
  const tarCheck = (0, import_child_process2.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3732
3800
  if (tarCheck.status !== 0) {
3733
3801
  result.failureReason = "tar command not found";
@@ -3737,8 +3805,10 @@ async function fetchFactory(options) {
3737
3805
  "Accept": "application/vnd.github+json",
3738
3806
  "Authorization": `Bearer ${token}`
3739
3807
  };
3740
- const releaseUrl = `https://api.github.com/repos/${repo}/releases/latest`;
3741
- const releaseResponse = await fetchWithRetry(releaseUrl, { headers });
3808
+ const releaseResponse = await fetchWithRetry(
3809
+ `https://api.github.com/repos/${repo}/releases/latest`,
3810
+ { headers }
3811
+ );
3742
3812
  if (!releaseResponse.ok) {
3743
3813
  result.failureReason = `GitHub API error (${releaseResponse.status})`;
3744
3814
  return result;
@@ -3751,15 +3821,11 @@ async function fetchFactory(options) {
3751
3821
  }
3752
3822
  const tempDir = (0, import_path3.join)(targetDir, ".temp-factory-download");
3753
3823
  try {
3754
- if ((0, import_fs3.existsSync)(tempDir)) {
3755
- (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
3756
- }
3824
+ if ((0, import_fs3.existsSync)(tempDir)) (0, import_fs3.rmSync)(tempDir, { recursive: true, force: true });
3757
3825
  (0, import_fs3.mkdirSync)(tempDir, { recursive: true });
3758
- const downloadHeaders = {
3759
- "Accept": "application/octet-stream",
3760
- "Authorization": `Bearer ${token}`
3761
- };
3762
- const response = await fetchWithRetry(asset.url, { headers: downloadHeaders });
3826
+ const response = await fetchWithRetry(asset.url, {
3827
+ headers: { "Accept": "application/octet-stream", "Authorization": `Bearer ${token}` }
3828
+ });
3763
3829
  if (!response.ok) {
3764
3830
  result.failureReason = `Download failed (${response.status})`;
3765
3831
  return result;
@@ -3845,6 +3911,13 @@ function configureSettings(claudeDir) {
3845
3911
  }
3846
3912
  }
3847
3913
  if (!settings.hooks) settings.hooks = {};
3914
+ const welcomeHook = {
3915
+ hooks: [{ type: "command", command: "node .claude/hooks/factory-welcome.cjs" }]
3916
+ };
3917
+ const immutableGuardHook = {
3918
+ matcher: "Write|Edit",
3919
+ hooks: [{ type: "command", command: "node .claude/hooks/factory-immutable-guard.cjs" }]
3920
+ };
3848
3921
  const stateAdvanceHook = {
3849
3922
  matcher: "Write|Edit",
3850
3923
  hooks: [{ type: "command", command: "node .claude/hooks/factory-state-advance.cjs" }]
@@ -3855,16 +3928,20 @@ function configureSettings(claudeDir) {
3855
3928
  const stateGuardHook = {
3856
3929
  hooks: [{ type: "command", command: "node .claude/hooks/factory-state-guard.cjs" }]
3857
3930
  };
3858
- for (const event of ["PostToolUse", "UserPromptSubmit"]) {
3931
+ for (const event of ["SessionStart", "PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
3859
3932
  if (settings.hooks[event]) {
3860
3933
  settings.hooks[event] = settings.hooks[event].filter(
3861
3934
  (h) => !h.hooks?.some(
3862
- (hh) => hh.command?.includes("factory-state-advance") || hh.command?.includes("factory-context-monitor") || hh.command?.includes("factory-state-guard")
3935
+ (hh) => hh.command?.includes("factory-welcome") || hh.command?.includes("factory-immutable-guard") || hh.command?.includes("factory-state-advance") || hh.command?.includes("factory-context-monitor") || hh.command?.includes("factory-state-guard")
3863
3936
  )
3864
3937
  );
3865
3938
  if (settings.hooks[event].length === 0) delete settings.hooks[event];
3866
3939
  }
3867
3940
  }
3941
+ if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
3942
+ settings.hooks.SessionStart.unshift(welcomeHook);
3943
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
3944
+ settings.hooks.PreToolUse.unshift(immutableGuardHook);
3868
3945
  if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
3869
3946
  settings.hooks.PostToolUse.unshift(stateAdvanceHook);
3870
3947
  if (!settings.hooks.UserPromptSubmit) {
@@ -3880,31 +3957,23 @@ function configureSettings(claudeDir) {
3880
3957
  if (!hasMonitor) settings.hooks.UserPromptSubmit.push(contextMonitorHook);
3881
3958
  }
3882
3959
  settings.statusLine = { type: "command", command: "node .claude/hooks/factory-statusline.cjs" };
3883
- if (!(0, import_fs3.existsSync)(claudeDir)) {
3884
- (0, import_fs3.mkdirSync)(claudeDir, { recursive: true });
3885
- }
3960
+ if (!(0, import_fs3.existsSync)(claudeDir)) (0, import_fs3.mkdirSync)(claudeDir, { recursive: true });
3886
3961
  (0, import_fs3.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
3887
3962
  }
3888
3963
  function copyAgentStubs(source, target) {
3889
- if (!(0, import_fs3.existsSync)(target)) {
3890
- (0, import_fs3.mkdirSync)(target, { recursive: true });
3891
- }
3964
+ if (!(0, import_fs3.existsSync)(target)) (0, import_fs3.mkdirSync)(target, { recursive: true });
3892
3965
  const entries = (0, import_fs3.readdirSync)(source, { withFileTypes: true });
3893
3966
  for (const entry of entries) {
3894
- if (entry.isDirectory()) continue;
3895
- if (!entry.name.endsWith(".md")) continue;
3967
+ if (entry.isDirectory() || !entry.name.endsWith(".md")) continue;
3896
3968
  const content = (0, import_fs3.readFileSync)((0, import_path3.join)(source, entry.name), "utf8");
3897
3969
  const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?\r?\n)---/);
3898
3970
  if (frontmatterMatch) {
3899
3971
  (0, import_fs3.writeFileSync)((0, import_path3.join)(target, entry.name), frontmatterMatch[0] + "\n");
3900
- } else {
3901
3972
  }
3902
3973
  }
3903
3974
  }
3904
3975
  function copyDirectory2(source, target) {
3905
- if (!(0, import_fs3.existsSync)(target)) {
3906
- (0, import_fs3.mkdirSync)(target, { recursive: true });
3907
- }
3976
+ if (!(0, import_fs3.existsSync)(target)) (0, import_fs3.mkdirSync)(target, { recursive: true });
3908
3977
  const entries = (0, import_fs3.readdirSync)(source, { withFileTypes: true });
3909
3978
  for (const entry of entries) {
3910
3979
  const sourcePath = (0, import_path3.join)(source, entry.name);
@@ -3917,96 +3986,99 @@ function copyDirectory2(source, target) {
3917
3986
  }
3918
3987
  }
3919
3988
 
3920
- // src/utils/setup/setup-mcp.ts
3989
+ // src/steps/setup/write-instructions.ts
3921
3990
  var import_fs4 = require("fs");
3922
3991
  var import_path4 = require("path");
3923
- function getAgentMCPTarget(agent) {
3992
+
3993
+ // src/steps/shared.ts
3994
+ function getFilteredMcpConfig(ctx) {
3995
+ const successfulDomains = new Set(ctx.installed.domainsInstalled ?? []);
3996
+ const successfulPersonas = ctx.config.personas.filter(
3997
+ (p) => p.domains.every((d) => successfulDomains.has(d.toLowerCase()))
3998
+ );
3999
+ const successfulMcpServers = /* @__PURE__ */ new Set([
4000
+ ...ctx.config.baseMcpServers,
4001
+ ...successfulPersonas.flatMap((p) => p.mcpServers)
4002
+ ]);
4003
+ return Object.fromEntries(
4004
+ Object.entries(ctx.config.mcpConfig).filter(([name]) => successfulMcpServers.has(name))
4005
+ );
4006
+ }
4007
+
4008
+ // src/steps/setup/write-instructions.ts
4009
+ var MARKER_START = "<!-- one-shot-installer:start -->";
4010
+ var MARKER_END = "<!-- one-shot-installer:end -->";
4011
+ var red = (text) => `\x1B[31m${text}\x1B[0m`;
4012
+ var write_instructions_default = defineStep({
4013
+ name: "write-instructions",
4014
+ label: "Writing instruction file",
4015
+ when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0,
4016
+ execute: async (ctx) => {
4017
+ const filteredMcpConfig = getFilteredMcpConfig(ctx);
4018
+ const domains = ctx.installed.domainsInstalled;
4019
+ const agent = ctx.config.agent;
4020
+ const projectPath = ctx.config.projectPath;
4021
+ const factoryInstalled = ctx.installed.factoryInstalled ?? false;
4022
+ const content = buildCombinedInstructions(domains, filteredMcpConfig, agent, projectPath, factoryInstalled);
4023
+ switch (agent) {
4024
+ case "claude-code": {
4025
+ upsertBlock((0, import_path4.join)(projectPath, "CLAUDE.md"), content);
4026
+ break;
4027
+ }
4028
+ case "github-copilot": {
4029
+ const agentsDir = (0, import_path4.join)(projectPath, ".github", "agents");
4030
+ if (!(0, import_fs4.existsSync)(agentsDir)) (0, import_fs4.mkdirSync)(agentsDir, { recursive: true });
4031
+ const filePath = (0, import_path4.join)(agentsDir, "instructions.md");
4032
+ if (!(0, import_fs4.existsSync)(filePath)) {
4033
+ (0, import_fs4.writeFileSync)(filePath, `---
4034
+ applyTo: "**"
4035
+ ---
4036
+
4037
+ `, "utf-8");
4038
+ }
4039
+ upsertBlock(filePath, content);
4040
+ break;
4041
+ }
4042
+ case "cursor": {
4043
+ const cursorDir = (0, import_path4.join)(projectPath, ".cursor", "rules");
4044
+ if (!(0, import_fs4.existsSync)(cursorDir)) (0, import_fs4.mkdirSync)(cursorDir, { recursive: true });
4045
+ const filePath = (0, import_path4.join)(cursorDir, "instructions.mdc");
4046
+ if (!(0, import_fs4.existsSync)(filePath)) {
4047
+ (0, import_fs4.writeFileSync)(filePath, `---
4048
+ description: AI development instructions from One-Shot Installer
4049
+ alwaysApply: true
4050
+ ---
4051
+
4052
+ `, "utf-8");
4053
+ }
4054
+ upsertBlock(filePath, content);
4055
+ break;
4056
+ }
4057
+ }
4058
+ return { status: "success", message: getInstructionFilePath(agent) };
4059
+ }
4060
+ });
4061
+ function getInstructionFilePath(agent) {
3924
4062
  switch (agent) {
3925
4063
  case "claude-code":
3926
- return { filePath: ".mcp.json", rootKey: "mcpServers", dir: null };
3927
- case "cursor":
3928
- return { filePath: ".cursor/mcp.json", rootKey: "mcpServers", dir: ".cursor" };
4064
+ return "CLAUDE.md";
3929
4065
  case "github-copilot":
4066
+ return ".github/agents/instructions.md";
4067
+ case "cursor":
4068
+ return ".cursor/rules/instructions.mdc";
3930
4069
  default:
3931
- return { filePath: ".vscode/mcp.json", rootKey: "servers", dir: ".vscode" };
3932
- }
3933
- }
3934
- async function setupMCPConfiguration(projectPath, mcpConfig, agent) {
3935
- const target = getAgentMCPTarget(agent);
3936
- const mcpJsonPath = (0, import_path4.join)(projectPath, target.filePath);
3937
- if (target.dir) {
3938
- const dir = (0, import_path4.join)(projectPath, target.dir);
3939
- if (!(0, import_fs4.existsSync)(dir)) {
3940
- (0, import_fs4.mkdirSync)(dir, { recursive: true });
3941
- }
3942
- }
3943
- const red2 = (text) => `\x1B[31m${text}\x1B[0m`;
3944
- let existingFile = {};
3945
- if ((0, import_fs4.existsSync)(mcpJsonPath)) {
3946
- try {
3947
- const content = (0, import_fs4.readFileSync)(mcpJsonPath, "utf-8");
3948
- existingFile = JSON.parse(content);
3949
- } catch {
3950
- const box = [
3951
- "",
3952
- 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"),
3953
- red2(" \u2551 \u26A0 MCP CONFIG HAS INVALID JSON \u2551"),
3954
- red2(" \u2551 \u2551"),
3955
- red2(` \u2551 ${target.filePath} could not be parsed.`.padEnd(52) + "\u2551"),
3956
- red2(" \u2551 Existing MCP servers may be lost. \u2551"),
3957
- red2(" \u2551 Check the file after installation completes. \u2551"),
3958
- 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"),
3959
- ""
3960
- ].join("\n");
3961
- console.log(box);
3962
- }
3963
- }
3964
- const addedServers = [];
3965
- const skippedServers = [];
3966
- const existingServers = existingFile[target.rootKey] ?? {};
3967
- const newServers = {};
3968
- for (const [serverName, serverConfig] of Object.entries(mcpConfig)) {
3969
- const { description, useWhen, active, ...mcpFields } = serverConfig;
3970
- newServers[serverName] = mcpFields;
3971
- addedServers.push(serverName);
3972
- }
3973
- const mergedServers = { ...existingServers, ...newServers };
3974
- const outputFile = { ...existingFile, [target.rootKey]: mergedServers };
3975
- (0, import_fs4.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
3976
- return { addedServers, skippedServers };
3977
- }
3978
- function buildMCPConfiguration(selectedItems, baseMcpServers, allMcpServers) {
3979
- const config = {};
3980
- for (const serverName of baseMcpServers) {
3981
- if (allMcpServers[serverName]) {
3982
- config[serverName] = allMcpServers[serverName];
3983
- }
3984
- }
3985
- for (const item of selectedItems) {
3986
- for (const serverName of item.mcpServers) {
3987
- if (allMcpServers[serverName] && !config[serverName]) {
3988
- config[serverName] = allMcpServers[serverName];
3989
- }
3990
- }
4070
+ return "CLAUDE.md";
3991
4071
  }
3992
- return config;
3993
4072
  }
3994
-
3995
- // src/utils/setup/setup-instructions.ts
3996
- var import_fs5 = require("fs");
3997
- var import_path5 = require("path");
3998
- var MARKER_START = "<!-- one-shot-installer:start -->";
3999
- var MARKER_END = "<!-- one-shot-installer:end -->";
4000
- var red = (text) => `\x1B[31m${text}\x1B[0m`;
4001
4073
  function upsertBlock(filePath, block) {
4002
4074
  const marked = `${MARKER_START}
4003
4075
  ${block}
4004
4076
  ${MARKER_END}`;
4005
- if (!(0, import_fs5.existsSync)(filePath)) {
4006
- (0, import_fs5.writeFileSync)(filePath, marked, "utf-8");
4077
+ if (!(0, import_fs4.existsSync)(filePath)) {
4078
+ (0, import_fs4.writeFileSync)(filePath, marked, "utf-8");
4007
4079
  return;
4008
4080
  }
4009
- const existing = (0, import_fs5.readFileSync)(filePath, "utf-8");
4081
+ const existing = (0, import_fs4.readFileSync)(filePath, "utf-8");
4010
4082
  const start = existing.indexOf(MARKER_START);
4011
4083
  const end = existing.indexOf(MARKER_END);
4012
4084
  const hasStart = start !== -1;
@@ -4031,23 +4103,23 @@ ${MARKER_END}`;
4031
4103
  }
4032
4104
  if (hasStart && hasEnd) {
4033
4105
  const updated = existing.slice(0, start) + marked + existing.slice(end + MARKER_END.length);
4034
- (0, import_fs5.writeFileSync)(filePath, updated, "utf-8");
4106
+ (0, import_fs4.writeFileSync)(filePath, updated, "utf-8");
4035
4107
  } else {
4036
4108
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4037
- (0, import_fs5.writeFileSync)(filePath, existing + separator + marked, "utf-8");
4109
+ (0, import_fs4.writeFileSync)(filePath, existing + separator + marked, "utf-8");
4038
4110
  }
4039
4111
  }
4040
4112
  function collectMdFiles(dir) {
4041
- if (!(0, import_fs5.existsSync)(dir)) return [];
4042
- const entries = (0, import_fs5.readdirSync)(dir, { recursive: true });
4113
+ if (!(0, import_fs4.existsSync)(dir)) return [];
4114
+ const entries = (0, import_fs4.readdirSync)(dir, { recursive: true });
4043
4115
  return entries.filter((entry) => {
4044
- const fullPath = (0, import_path5.join)(dir, entry);
4045
- return entry.endsWith(".md") && (0, import_fs5.statSync)(fullPath).isFile();
4116
+ const fullPath = (0, import_path4.join)(dir, entry);
4117
+ return entry.endsWith(".md") && (0, import_fs4.statSync)(fullPath).isFile();
4046
4118
  }).map((entry) => entry.replace(/\\/g, "/")).sort();
4047
4119
  }
4048
4120
  function extractFirstHeading(filePath) {
4049
4121
  try {
4050
- const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
4122
+ const content = (0, import_fs4.readFileSync)(filePath, "utf-8");
4051
4123
  const withoutFrontmatter = content.replace(/^---[\s\S]*?---\s*/, "");
4052
4124
  const match = withoutFrontmatter.match(/^#\s+(.+)/m);
4053
4125
  if (match) {
@@ -4058,47 +4130,23 @@ function extractFirstHeading(filePath) {
4058
4130
  const basename = filePath.split(/[/\\]/).pop() ?? "";
4059
4131
  return basename.replace(/\.md$/i, "").replace(/[-_]+/g, " ");
4060
4132
  }
4061
- function buildMCPSection(mcpConfig) {
4062
- const entries = Object.entries(mcpConfig);
4063
- if (entries.length === 0) return "";
4064
- const lines2 = [
4065
- `## MCP Servers`,
4066
- ``,
4067
- `The following MCP servers are available. Use them proactively when relevant:`,
4068
- ``
4069
- ];
4070
- for (const [name, config] of entries) {
4071
- const description = config.description;
4072
- const useWhen = config.useWhen;
4073
- if (!description) continue;
4074
- lines2.push(`### ${name}`);
4075
- lines2.push(description);
4076
- if (useWhen) {
4077
- lines2.push(`**Use when**: ${useWhen}`);
4078
- }
4079
- lines2.push(``);
4133
+ function formatPathRef(contextPath, description, agent) {
4134
+ if (agent === "github-copilot") {
4135
+ return `- [${contextPath}](../../${contextPath}) \u2014 ${description}`;
4080
4136
  }
4081
- return lines2.join("\n");
4137
+ return `- \`${contextPath}\` \u2014 ${description}`;
4082
4138
  }
4083
4139
  function resolveDomainFolder(domain, contextsDir) {
4084
- if ((0, import_fs5.existsSync)(contextsDir)) {
4140
+ if ((0, import_fs4.existsSync)(contextsDir)) {
4085
4141
  try {
4086
- const entries = (0, import_fs5.readdirSync)(contextsDir);
4142
+ const entries = (0, import_fs4.readdirSync)(contextsDir);
4087
4143
  const match = entries.find((e) => e.toLowerCase() === domain.toLowerCase());
4088
- if (match) {
4089
- return { folderName: match, folderPath: (0, import_path5.join)(contextsDir, match) };
4090
- }
4144
+ if (match) return { folderName: match, folderPath: (0, import_path4.join)(contextsDir, match) };
4091
4145
  } catch {
4092
4146
  }
4093
4147
  }
4094
4148
  const fallback = domain.toUpperCase();
4095
- return { folderName: fallback, folderPath: (0, import_path5.join)(contextsDir, fallback) };
4096
- }
4097
- function formatPathRef(contextPath, description, agent) {
4098
- if (agent === "github-copilot") {
4099
- return `- [${contextPath}](../../${contextPath}) \u2014 ${description}`;
4100
- }
4101
- return `- \`${contextPath}\` \u2014 ${description}`;
4149
+ return { folderName: fallback, folderPath: (0, import_path4.join)(contextsDir, fallback) };
4102
4150
  }
4103
4151
  function buildContextRefsSection(domains, agent, contextsDir) {
4104
4152
  const lines2 = [
@@ -4110,34 +4158,34 @@ function buildContextRefsSection(domains, agent, contextsDir) {
4110
4158
  let hasAnyFiles = false;
4111
4159
  for (const domain of domains) {
4112
4160
  const { folderName, folderPath: domainPath } = resolveDomainFolder(domain, contextsDir);
4113
- if (!(0, import_fs5.existsSync)(domainPath)) continue;
4161
+ if (!(0, import_fs4.existsSync)(domainPath)) continue;
4114
4162
  const domainFiles = [];
4115
- const ctxInstructions = (0, import_path5.join)(domainPath, "context-instructions.md");
4116
- if ((0, import_fs5.existsSync)(ctxInstructions)) {
4163
+ const ctxInstructions = (0, import_path4.join)(domainPath, "context-instructions.md");
4164
+ if ((0, import_fs4.existsSync)(ctxInstructions)) {
4117
4165
  const desc = extractFirstHeading(ctxInstructions);
4118
4166
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/context-instructions.md`, desc, agent));
4119
4167
  }
4120
- const instructionsMd = (0, import_path5.join)(domainPath, "core", "instructions.md");
4121
- if ((0, import_fs5.existsSync)(instructionsMd)) {
4168
+ const instructionsMd = (0, import_path4.join)(domainPath, "core", "instructions.md");
4169
+ if ((0, import_fs4.existsSync)(instructionsMd)) {
4122
4170
  const desc = extractFirstHeading(instructionsMd);
4123
4171
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/instructions.md`, `${desc} (start here)`, agent));
4124
4172
  }
4125
- const coreDir = (0, import_path5.join)(domainPath, "core");
4126
- if ((0, import_fs5.existsSync)(coreDir)) {
4173
+ const coreDir = (0, import_path4.join)(domainPath, "core");
4174
+ if ((0, import_fs4.existsSync)(coreDir)) {
4127
4175
  const coreFiles = collectMdFiles(coreDir).filter((f) => f !== "instructions.md" && !f.startsWith("instructions/"));
4128
4176
  for (const file of coreFiles) {
4129
- const desc = extractFirstHeading((0, import_path5.join)(coreDir, file));
4177
+ const desc = extractFirstHeading((0, import_path4.join)(coreDir, file));
4130
4178
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/core/${file}`, desc, agent));
4131
4179
  }
4132
4180
  }
4133
- const refDir = (0, import_path5.join)(domainPath, "reference");
4134
- if ((0, import_fs5.existsSync)(refDir)) {
4181
+ const refDir = (0, import_path4.join)(domainPath, "reference");
4182
+ if ((0, import_fs4.existsSync)(refDir)) {
4135
4183
  const refFiles = collectMdFiles(refDir);
4136
4184
  if (refFiles.length > 0) {
4137
4185
  domainFiles.push(``);
4138
4186
  domainFiles.push(`**Reference & cheat sheets:**`);
4139
4187
  for (const file of refFiles) {
4140
- const desc = extractFirstHeading((0, import_path5.join)(refDir, file));
4188
+ const desc = extractFirstHeading((0, import_path4.join)(refDir, file));
4141
4189
  domainFiles.push(formatPathRef(`_ai-context/${folderName}/reference/${file}`, desc, agent));
4142
4190
  }
4143
4191
  }
@@ -4154,6 +4202,21 @@ function buildContextRefsSection(domains, agent, contextsDir) {
4154
4202
  }
4155
4203
  return lines2.join("\n");
4156
4204
  }
4205
+ function buildMCPSection(mcpConfig) {
4206
+ const entries = Object.entries(mcpConfig);
4207
+ if (entries.length === 0) return "";
4208
+ const lines2 = [`## MCP Servers`, ``, `The following MCP servers are available. Use them proactively when relevant:`, ``];
4209
+ for (const [name, config] of entries) {
4210
+ const description = config.description;
4211
+ const useWhen = config.useWhen;
4212
+ if (!description) continue;
4213
+ lines2.push(`### ${name}`);
4214
+ lines2.push(description);
4215
+ if (useWhen) lines2.push(`**Use when**: ${useWhen}`);
4216
+ lines2.push(``);
4217
+ }
4218
+ return lines2.join("\n");
4219
+ }
4157
4220
  function buildFactorySection() {
4158
4221
  return [
4159
4222
  `## Factory Dev Workflow`,
@@ -4172,68 +4235,81 @@ function buildFactorySection() {
4172
4235
  ``
4173
4236
  ].join("\n");
4174
4237
  }
4175
- function buildCombinedInstructions(domains, mcpConfig, agent = "", projectPath = process.cwd(), factoryInstalled = false) {
4176
- const contextsDir = (0, import_path5.join)(projectPath, "_ai-context");
4177
- const lines2 = [
4178
- `# AI Development Instructions`,
4179
- ``,
4180
- `> Generated by One-Shot Installer`,
4181
- ``
4182
- ];
4238
+ function buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled) {
4239
+ const contextsDir = (0, import_path4.join)(projectPath, "_ai-context");
4240
+ const lines2 = [`# AI Development Instructions`, ``, `> Generated by One-Shot Installer`, ``];
4183
4241
  lines2.push(buildContextRefsSection(domains, agent, contextsDir));
4184
- if (mcpConfig && Object.keys(mcpConfig).length > 0) {
4185
- lines2.push(buildMCPSection(mcpConfig));
4186
- }
4187
- if (factoryInstalled) {
4188
- lines2.push(buildFactorySection());
4189
- }
4242
+ if (Object.keys(mcpConfig).length > 0) lines2.push(buildMCPSection(mcpConfig));
4243
+ if (factoryInstalled) lines2.push(buildFactorySection());
4190
4244
  return lines2.join("\n");
4191
4245
  }
4192
- async function setupInstructions(config) {
4193
- const { domains, agent, mcpConfig, projectPath = process.cwd(), factoryInstalled = false } = config;
4194
- switch (agent) {
4195
- case "claude-code": {
4196
- const content = buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled);
4197
- upsertBlock((0, import_path5.join)(projectPath, "CLAUDE.md"), content);
4198
- break;
4199
- }
4200
- case "github-copilot": {
4201
- const agentsDir = (0, import_path5.join)(projectPath, ".github", "agents");
4202
- if (!(0, import_fs5.existsSync)(agentsDir)) (0, import_fs5.mkdirSync)(agentsDir, { recursive: true });
4203
- const filePath = (0, import_path5.join)(agentsDir, "instructions.md");
4204
- if (!(0, import_fs5.existsSync)(filePath)) {
4205
- (0, import_fs5.writeFileSync)(filePath, `---
4206
- applyTo: "**"
4207
- ---
4208
4246
 
4209
- `, "utf-8");
4210
- }
4211
- const body = buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled);
4212
- upsertBlock(filePath, body);
4213
- break;
4247
+ // src/steps/setup/write-mcp-config.ts
4248
+ var import_fs5 = require("fs");
4249
+ var import_path5 = require("path");
4250
+ var red2 = (text) => `\x1B[31m${text}\x1B[0m`;
4251
+ var write_mcp_config_default = defineStep({
4252
+ name: "write-mcp-config",
4253
+ label: "Writing MCP configuration",
4254
+ when: (ctx) => (ctx.installed.domainsInstalled?.length ?? 0) > 0,
4255
+ execute: async (ctx) => {
4256
+ const filteredMcpConfig = getFilteredMcpConfig(ctx);
4257
+ const target = getAgentMCPTarget(ctx.config.agent);
4258
+ const mcpJsonPath = (0, import_path5.join)(ctx.config.projectPath, target.filePath);
4259
+ if (target.dir) {
4260
+ const dir = (0, import_path5.join)(ctx.config.projectPath, target.dir);
4261
+ if (!(0, import_fs5.existsSync)(dir)) (0, import_fs5.mkdirSync)(dir, { recursive: true });
4214
4262
  }
4215
- case "cursor": {
4216
- const cursorDir = (0, import_path5.join)(projectPath, ".cursor", "rules");
4217
- if (!(0, import_fs5.existsSync)(cursorDir)) (0, import_fs5.mkdirSync)(cursorDir, { recursive: true });
4218
- const filePath = (0, import_path5.join)(cursorDir, "instructions.mdc");
4219
- if (!(0, import_fs5.existsSync)(filePath)) {
4220
- (0, import_fs5.writeFileSync)(filePath, `---
4221
- description: AI development instructions from One-Shot Installer
4222
- alwaysApply: true
4223
- ---
4224
-
4225
- `, "utf-8");
4263
+ let existingFile = {};
4264
+ if ((0, import_fs5.existsSync)(mcpJsonPath)) {
4265
+ try {
4266
+ existingFile = JSON.parse((0, import_fs5.readFileSync)(mcpJsonPath, "utf-8"));
4267
+ } catch {
4268
+ const box = [
4269
+ "",
4270
+ 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"),
4271
+ red2(" \u2551 \u26A0 MCP CONFIG HAS INVALID JSON \u2551"),
4272
+ red2(" \u2551 \u2551"),
4273
+ red2(` \u2551 ${target.filePath} could not be parsed.`.padEnd(52) + "\u2551"),
4274
+ red2(" \u2551 Existing MCP servers may be lost. \u2551"),
4275
+ red2(" \u2551 Check the file after installation completes. \u2551"),
4276
+ 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"),
4277
+ ""
4278
+ ].join("\n");
4279
+ console.log(box);
4226
4280
  }
4227
- const body = buildCombinedInstructions(domains, mcpConfig, agent, projectPath, factoryInstalled);
4228
- upsertBlock(filePath, body);
4229
- break;
4230
4281
  }
4282
+ const addedServers = [];
4283
+ const existingServers = existingFile[target.rootKey] ?? {};
4284
+ const newServers = {};
4285
+ for (const [serverName, serverConfig] of Object.entries(filteredMcpConfig)) {
4286
+ const { description, useWhen, active, ...mcpFields } = serverConfig;
4287
+ newServers[serverName] = mcpFields;
4288
+ addedServers.push(serverName);
4289
+ }
4290
+ const mergedServers = { ...existingServers, ...newServers };
4291
+ const outputFile = { ...existingFile, [target.rootKey]: mergedServers };
4292
+ (0, import_fs5.writeFileSync)(mcpJsonPath, JSON.stringify(outputFile, null, 2), "utf-8");
4293
+ ctx.installed.mcpServersAdded = addedServers;
4294
+ return {
4295
+ status: "success",
4296
+ message: addedServers.length > 0 ? addedServers.join(", ") : void 0
4297
+ };
4298
+ }
4299
+ });
4300
+ function getAgentMCPTarget(agent) {
4301
+ switch (agent) {
4302
+ case "claude-code":
4303
+ return { filePath: ".mcp.json", rootKey: "mcpServers", dir: null };
4304
+ case "cursor":
4305
+ return { filePath: ".cursor/mcp.json", rootKey: "mcpServers", dir: ".cursor" };
4306
+ case "github-copilot":
4231
4307
  default:
4232
- break;
4308
+ return { filePath: ".vscode/mcp.json", rootKey: "servers", dir: ".vscode" };
4233
4309
  }
4234
4310
  }
4235
4311
 
4236
- // src/utils/setup/setup-gitignore.ts
4312
+ // src/steps/setup/update-gitignore.ts
4237
4313
  var import_fs6 = require("fs");
4238
4314
  var import_path6 = require("path");
4239
4315
  var MARKER_START2 = "# one-shot-installer:start";
@@ -4265,47 +4341,93 @@ var GITIGNORE_ENTRIES = [
4265
4341
  "factory/summaries/",
4266
4342
  "factory/ai-context/"
4267
4343
  ];
4268
- function setupGitignore(projectPath) {
4269
- const gitignorePath = (0, import_path6.join)(projectPath, ".gitignore");
4270
- const block = [MARKER_START2, ...GITIGNORE_ENTRIES, MARKER_END2].join("\n");
4271
- if (!(0, import_fs6.existsSync)(gitignorePath)) {
4272
- (0, import_fs6.writeFileSync)(gitignorePath, block + "\n", "utf-8");
4273
- return;
4344
+ var update_gitignore_default = defineStep({
4345
+ name: "update-gitignore",
4346
+ label: "Updating .gitignore",
4347
+ execute: async (ctx) => {
4348
+ const gitignorePath = (0, import_path6.join)(ctx.config.projectPath, ".gitignore");
4349
+ const block = [MARKER_START2, ...GITIGNORE_ENTRIES, MARKER_END2].join("\n");
4350
+ if (!(0, import_fs6.existsSync)(gitignorePath)) {
4351
+ (0, import_fs6.writeFileSync)(gitignorePath, block + "\n", "utf-8");
4352
+ return { status: "success" };
4353
+ }
4354
+ const existing = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
4355
+ const start = existing.indexOf(MARKER_START2);
4356
+ const end = existing.indexOf(MARKER_END2);
4357
+ if (start !== -1 && end !== -1 && end > start) {
4358
+ const updated = existing.slice(0, start) + block + existing.slice(end + MARKER_END2.length);
4359
+ (0, import_fs6.writeFileSync)(gitignorePath, updated, "utf-8");
4360
+ } else {
4361
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4362
+ (0, import_fs6.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
4363
+ }
4364
+ return { status: "success" };
4274
4365
  }
4275
- const existing = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
4276
- const start = existing.indexOf(MARKER_START2);
4277
- const end = existing.indexOf(MARKER_END2);
4278
- if (start !== -1 && end !== -1 && end > start) {
4279
- const updated = existing.slice(0, start) + block + existing.slice(end + MARKER_END2.length);
4280
- (0, import_fs6.writeFileSync)(gitignorePath, updated, "utf-8");
4281
- } else {
4282
- const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4283
- (0, import_fs6.writeFileSync)(gitignorePath, existing + separator + block + "\n", "utf-8");
4366
+ });
4367
+
4368
+ // src/steps/setup/write-state.ts
4369
+ var import_fs7 = require("fs");
4370
+ var import_path7 = require("path");
4371
+ var import_os2 = require("os");
4372
+ var INSTALLER_VERSION = "0.5.9";
4373
+ var write_state_default = defineStep({
4374
+ name: "write-state",
4375
+ label: "Saving installation state",
4376
+ execute: async (ctx) => {
4377
+ const statePath = (0, import_path7.join)(ctx.config.projectPath, ".one-shot-state.json");
4378
+ const mcpServersAdded = ctx.installed.mcpServersAdded ?? [];
4379
+ const filteredMcpConfig = Object.fromEntries(
4380
+ Object.entries(ctx.config.mcpConfig).filter(([name]) => mcpServersAdded.includes(name))
4381
+ );
4382
+ const state = {
4383
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4384
+ installerVersion: INSTALLER_VERSION,
4385
+ releaseVersion: ctx.config.releaseVersion,
4386
+ projectPath: ctx.config.projectPath,
4387
+ agent: ctx.config.agent,
4388
+ personas: ctx.config.personas.map((p) => p.id),
4389
+ domains: ctx.installed.domainsInstalled ?? [],
4390
+ mcpServers: mcpServersAdded,
4391
+ mcpConfigs: filteredMcpConfig,
4392
+ factoryInstalled: ctx.installed.factoryInstalled ?? false,
4393
+ files: {
4394
+ instructions: getInstructionFilePath2(ctx.config.agent),
4395
+ mcpConfig: getMCPConfigPath(ctx.config.agent),
4396
+ contexts: "_ai-context/",
4397
+ factory: ctx.installed.factoryInstalled ? "factory/" : null,
4398
+ globalConfig: (0, import_path7.join)((0, import_os2.homedir)(), ".one-shot-installer")
4399
+ }
4400
+ };
4401
+ (0, import_fs7.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
4402
+ return { status: "success" };
4403
+ }
4404
+ });
4405
+ function getInstructionFilePath2(agent) {
4406
+ switch (agent) {
4407
+ case "claude-code":
4408
+ return "CLAUDE.md";
4409
+ case "github-copilot":
4410
+ return ".github/agents/instructions.md";
4411
+ case "cursor":
4412
+ return ".cursor/rules/instructions.mdc";
4413
+ default:
4414
+ return "CLAUDE.md";
4284
4415
  }
4285
4416
  }
4286
-
4287
- // src/utils/mod.ts
4288
- async function loadWizardOptions(token, repo) {
4289
- const remote = await fetchWizardOptions(token, repo);
4290
- if (!remote) {
4291
- throw new Error("Failed to load configuration from GitHub release. Check your network and token.");
4417
+ function getMCPConfigPath(agent) {
4418
+ switch (agent) {
4419
+ case "claude-code":
4420
+ return ".mcp.json";
4421
+ case "cursor":
4422
+ return ".cursor/mcp.json";
4423
+ case "github-copilot":
4424
+ default:
4425
+ return ".vscode/mcp.json";
4292
4426
  }
4293
- const { options, releaseVersion } = remote;
4294
- const filteredMcpServers = Object.fromEntries(
4295
- Object.entries(options.mcpServers).filter(([, config]) => config.active !== false)
4296
- );
4297
- return {
4298
- personas: options.personas.personas.filter((p) => p.active !== false),
4299
- agents: options.agents.filter((a) => a.active !== false),
4300
- baseMcpServers: options.personas.baseMcpServers,
4301
- mcpServers: filteredMcpServers,
4302
- releaseVersion
4303
- };
4304
4427
  }
4305
4428
 
4306
4429
  // src/index.ts
4307
- var INSTALLER_VERSION = "0.5.7";
4308
- function getInstructionFilePath(agent) {
4430
+ function getInstructionFilePath3(agent) {
4309
4431
  switch (agent) {
4310
4432
  case "claude-code":
4311
4433
  return "CLAUDE.md";
@@ -4317,7 +4439,7 @@ function getInstructionFilePath(agent) {
4317
4439
  return "CLAUDE.md";
4318
4440
  }
4319
4441
  }
4320
- function getMCPConfigPath(agent) {
4442
+ function getMCPConfigPath2(agent) {
4321
4443
  switch (agent) {
4322
4444
  case "claude-code":
4323
4445
  return ".mcp.json";
@@ -4337,19 +4459,19 @@ var dim = (text) => `\x1B[2m${text}\x1B[0m`;
4337
4459
  var yellow = (text) => `\x1B[33m${text}\x1B[0m`;
4338
4460
  var green = (text) => `\x1B[32m${text}\x1B[0m`;
4339
4461
  function detectMarkerFileMode(filePath, markerStart) {
4340
- if (!(0, import_fs7.existsSync)(filePath)) return green("create");
4341
- const content = (0, import_fs7.readFileSync)(filePath, "utf-8");
4462
+ if (!(0, import_fs8.existsSync)(filePath)) return green("create");
4463
+ const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
4342
4464
  if (content.includes(markerStart)) return yellow("update");
4343
4465
  return yellow("append");
4344
4466
  }
4345
4467
  function detectMCPFileMode(filePath) {
4346
- if (!(0, import_fs7.existsSync)(filePath)) return green("create");
4468
+ if (!(0, import_fs8.existsSync)(filePath)) return green("create");
4347
4469
  return yellow("merge");
4348
4470
  }
4349
4471
  function detectContextMode(projectPath, domain) {
4350
- const contextDir = (0, import_path7.join)(projectPath, "_ai-context", domain.toUpperCase());
4351
- const contextDirLower = (0, import_path7.join)(projectPath, "_ai-context", domain);
4352
- if ((0, import_fs7.existsSync)(contextDir) || (0, import_fs7.existsSync)(contextDirLower)) return yellow("overwrite");
4472
+ const contextDir = (0, import_path8.join)(projectPath, "_ai-context", domain.toUpperCase());
4473
+ const contextDirLower = (0, import_path8.join)(projectPath, "_ai-context", domain);
4474
+ if ((0, import_fs8.existsSync)(contextDir) || (0, import_fs8.existsSync)(contextDirLower)) return yellow("overwrite");
4353
4475
  return green("create");
4354
4476
  }
4355
4477
  function waitForEnter() {
@@ -4387,8 +4509,17 @@ async function main() {
4387
4509
  await waitForEnter();
4388
4510
  return;
4389
4511
  }
4390
- const result = await execute(config, token, repo, factoryRepo);
4391
- printSummary(result);
4512
+ const ctx = {
4513
+ config,
4514
+ token,
4515
+ repo,
4516
+ factoryRepo,
4517
+ installed: {}
4518
+ };
4519
+ const stepList = Object.values(steps_exports);
4520
+ console.log("");
4521
+ const result = await runPipeline(stepList, ctx);
4522
+ printSummary(result, config);
4392
4523
  return;
4393
4524
  } catch (error) {
4394
4525
  console.error("\n[ERROR]", error instanceof Error ? error.message : String(error));
@@ -4427,13 +4558,13 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4427
4558
  }
4428
4559
  const projectInput = await esm_default4({
4429
4560
  message: "Project directory:",
4430
- default: (0, import_path7.resolve)(process.cwd())
4561
+ default: (0, import_path8.resolve)(process.cwd())
4431
4562
  });
4432
4563
  return {
4433
4564
  personas: selectedPersonas,
4434
4565
  agent,
4435
4566
  azureDevOpsOrg,
4436
- projectPath: (0, import_path7.resolve)(projectInput),
4567
+ projectPath: (0, import_path8.resolve)(projectInput),
4437
4568
  baseMcpServers: options.baseMcpServers,
4438
4569
  mcpConfig,
4439
4570
  releaseVersion,
@@ -4443,12 +4574,12 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4443
4574
  async function previewAndConfirm(config, options) {
4444
4575
  const personaNames = config.personas.map((p) => p.name.replace(/ Developer$/, "")).join(", ");
4445
4576
  const agentDisplay = options.agents.find((a) => a.value === config.agent)?.name ?? config.agent;
4446
- const instructionFile = getInstructionFilePath(config.agent);
4447
- const mcpConfigFile = getMCPConfigPath(config.agent);
4577
+ const instructionFile = getInstructionFilePath3(config.agent);
4578
+ const mcpConfigFile = getMCPConfigPath2(config.agent);
4448
4579
  const serverEntries = Object.entries(config.mcpConfig);
4449
- const instructionFilePath = (0, import_path7.join)(config.projectPath, instructionFile);
4450
- const mcpConfigFilePath = (0, import_path7.join)(config.projectPath, mcpConfigFile);
4451
- const gitignorePath = (0, import_path7.join)(config.projectPath, ".gitignore");
4580
+ const instructionFilePath = (0, import_path8.join)(config.projectPath, instructionFile);
4581
+ const mcpConfigFilePath = (0, import_path8.join)(config.projectPath, mcpConfigFile);
4582
+ const gitignorePath = (0, import_path8.join)(config.projectPath, ".gitignore");
4452
4583
  const instructionMode = detectMarkerFileMode(instructionFilePath, "<!-- one-shot-installer:start -->");
4453
4584
  const mcpMode = detectMCPFileMode(mcpConfigFilePath);
4454
4585
  const gitignoreMode = detectMarkerFileMode(gitignorePath, "# one-shot-installer:start");
@@ -4474,7 +4605,7 @@ async function previewAndConfirm(config, options) {
4474
4605
  console.log(` ${mcpConfigFile.padEnd(domainColWidth + 14)}${mcpMode}`);
4475
4606
  console.log(` ${".gitignore".padEnd(domainColWidth + 14)}${gitignoreMode}`);
4476
4607
  if (config.installFactory) {
4477
- const factoryExists = (0, import_fs7.existsSync)((0, import_path7.join)(config.projectPath, "factory"));
4608
+ const factoryExists = (0, import_fs8.existsSync)((0, import_path8.join)(config.projectPath, "factory"));
4478
4609
  const factoryMode = factoryExists ? yellow("overwrite") : green("create");
4479
4610
  console.log(` ${"factory/".padEnd(domainColWidth + 14)}${factoryMode}`);
4480
4611
  }
@@ -4490,192 +4621,26 @@ async function previewAndConfirm(config, options) {
4490
4621
  console.log("\n" + "\u2500".repeat(48));
4491
4622
  return esm_default3({ message: "Proceed?", default: true });
4492
4623
  }
4493
- async function execute(config, token, repo, factoryRepo = null) {
4494
- const instructionFilePath = getInstructionFilePath(config.agent);
4495
- const mcpConfigPath = getMCPConfigPath(config.agent);
4496
- const result = {
4497
- success: false,
4498
- domainsInstalled: [],
4499
- domainsFailed: [],
4500
- failureReasons: {},
4501
- instructionsCreated: false,
4502
- instructionFilePath,
4503
- mcpConfigured: false,
4504
- mcpConfigPath,
4505
- mcpServersAdded: [],
4506
- gitignoreUpdated: false,
4507
- factoryInstalled: false
4508
- };
4509
- console.log("");
4510
- const uniqueDomains = [...new Set(config.personas.flatMap((p) => p.domains))];
4511
- const domainValues = uniqueDomains.map((d) => d.toLowerCase());
4512
- console.log(` Downloading contexts...`);
4513
- try {
4514
- const downloadResult = await fetchContexts({ domains: domainValues, token, repo, targetDir: config.projectPath });
4515
- result.domainsInstalled = downloadResult.successful;
4516
- result.domainsFailed = downloadResult.failed;
4517
- result.failureReasons = downloadResult.failureReasons;
4518
- } catch (error) {
4519
- result.domainsFailed = domainValues;
4520
- const reason = error instanceof Error ? error.message : String(error);
4521
- for (const d of domainValues) result.failureReasons[d] = reason;
4522
- }
4523
- const domainColWidth = Math.max(...domainValues.map((d) => d.length), 8) + 2;
4524
- for (const domain of domainValues) {
4525
- const ok = result.domainsInstalled.includes(domain);
4526
- const reason = result.failureReasons[domain];
4527
- const status = ok ? "\u2713" : `\u2717 ${reason ?? "Unknown error"}`;
4528
- console.log(` ${domain.padEnd(domainColWidth)}${status}`);
4529
- }
4530
- console.log("");
4531
- if (config.installFactory && factoryRepo) {
4532
- process.stdout.write(` Installing Factory framework... `);
4533
- try {
4534
- const factoryResult = await fetchFactory({ token, repo: factoryRepo, targetDir: config.projectPath });
4535
- if (factoryResult.success) {
4536
- result.factoryInstalled = true;
4537
- console.log("\u2713");
4538
- } else {
4539
- console.log("\u2717");
4540
- console.log(` ${factoryResult.failureReason}`);
4541
- }
4542
- } catch (error) {
4543
- console.log("\u2717");
4544
- console.log(` ${error instanceof Error ? error.message : String(error)}`);
4545
- }
4546
- }
4547
- if (result.domainsInstalled.length === 0) {
4548
- result.success = false;
4549
- return result;
4550
- }
4551
- const successfulDomains = new Set(result.domainsInstalled);
4552
- const successfulPersonas = config.personas.filter(
4553
- (p) => p.domains.every((d) => successfulDomains.has(d.toLowerCase()))
4554
- );
4555
- const successfulMcpServers = /* @__PURE__ */ new Set([
4556
- ...config.baseMcpServers,
4557
- ...successfulPersonas.flatMap((p) => p.mcpServers)
4558
- ]);
4559
- const filteredMcpConfig = Object.fromEntries(
4560
- Object.entries(config.mcpConfig).filter(([name]) => successfulMcpServers.has(name))
4561
- );
4562
- const statePath = (0, import_path7.join)(config.projectPath, ".one-shot-state.json");
4563
- const allDomains = result.domainsInstalled;
4564
- process.stdout.write(` Writing ${instructionFilePath}... `);
4565
- try {
4566
- await setupInstructions({
4567
- domains: allDomains,
4568
- agent: config.agent,
4569
- mcpConfig: filteredMcpConfig,
4570
- projectPath: config.projectPath,
4571
- factoryInstalled: result.factoryInstalled
4572
- });
4573
- result.instructionsCreated = true;
4574
- console.log("\u2713");
4575
- } catch (error) {
4576
- console.log("\u2717");
4577
- console.log(` Error: ${error instanceof Error ? error.message : String(error)}`);
4578
- console.log(` Path: ${instructionFilePath}`);
4579
- }
4580
- process.stdout.write(` Writing ${mcpConfigPath}... `);
4581
- try {
4582
- const mcpResult = await setupMCPConfiguration(
4583
- config.projectPath,
4584
- filteredMcpConfig,
4585
- config.agent
4586
- );
4587
- result.mcpConfigured = true;
4588
- result.mcpServersAdded = mcpResult.addedServers;
4589
- console.log("\u2713");
4590
- } catch (error) {
4591
- console.log("\u2717");
4592
- console.log(` Error: ${error instanceof Error ? error.message : String(error)}`);
4593
- console.log(` Path: ${mcpConfigPath}`);
4594
- }
4595
- process.stdout.write(` Updating .gitignore... `);
4596
- try {
4597
- setupGitignore(config.projectPath);
4598
- result.gitignoreUpdated = true;
4599
- console.log("\u2713");
4600
- } catch (error) {
4601
- console.log("\u2717");
4602
- console.log(` Error: ${error instanceof Error ? error.message : String(error)}`);
4603
- }
4604
- result.success = result.domainsFailed.length === 0 && result.instructionsCreated && result.mcpConfigured;
4605
- const allPersonas = config.personas.map((p) => p.id);
4606
- const state = {
4607
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4608
- installerVersion: INSTALLER_VERSION,
4609
- releaseVersion: config.releaseVersion,
4610
- projectPath: config.projectPath,
4611
- agent: config.agent,
4612
- personas: allPersonas,
4613
- domains: allDomains,
4614
- mcpServers: Object.keys(filteredMcpConfig),
4615
- mcpConfigs: filteredMcpConfig,
4616
- factoryInstalled: result.factoryInstalled,
4617
- files: {
4618
- instructions: instructionFilePath,
4619
- mcpConfig: mcpConfigPath,
4620
- contexts: "_ai-context/",
4621
- factory: result.factoryInstalled ? "factory/" : null,
4622
- globalConfig: (0, import_path7.join)((0, import_os2.homedir)(), ".one-shot-installer")
4623
- }
4624
- };
4625
- try {
4626
- (0, import_fs7.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf-8");
4627
- } catch {
4628
- }
4629
- return result;
4630
- }
4631
- function printSummary(result) {
4632
- const hasErrors = !result.success;
4624
+ function printSummary(result, config) {
4625
+ const failed = result.entries.filter((e) => e.result.status === "failed");
4626
+ const succeeded = result.entries.filter((e) => e.result.status === "success");
4627
+ const hasErrors = failed.length > 0;
4633
4628
  console.log("\u2500".repeat(48));
4634
4629
  console.log(hasErrors ? " Done (with errors).\n" : " Done.\n");
4635
- if (result.domainsInstalled.length > 0) {
4636
- console.log(` _ai-context/ ${result.domainsInstalled.join(", ")}`);
4637
- }
4638
- if (result.instructionsCreated) {
4639
- console.log(` ${result.instructionFilePath} written`);
4640
- }
4641
- if (result.mcpConfigured) {
4642
- const serverList = result.mcpServersAdded.length > 0 ? ` (${result.mcpServersAdded.join(", ")})` : "";
4643
- console.log(` ${result.mcpConfigPath} written${serverList}`);
4644
- }
4645
- if (result.factoryInstalled) {
4646
- console.log(` Factory installed (.claude/ + factory/)`);
4647
- }
4648
- if (result.gitignoreUpdated) {
4649
- console.log(` .gitignore updated`);
4630
+ for (const entry of succeeded) {
4631
+ const msg = entry.result.message ? ` ${entry.result.message}` : "";
4632
+ console.log(` ${entry.label}${msg}`);
4650
4633
  }
4651
4634
  if (hasErrors) {
4652
4635
  console.log("\n What went wrong:");
4653
- if (result.domainsFailed.length > 0) {
4654
- console.log("");
4655
- console.log(" Context downloads failed:");
4656
- for (const domain of result.domainsFailed) {
4657
- const reason = result.failureReasons[domain];
4658
- console.log(` ${domain} \u2014 ${reason ?? "Unknown error"}`);
4659
- }
4660
- }
4661
- if (!result.instructionsCreated) {
4662
- console.log("");
4663
- console.log(` Instruction file not written: ${result.instructionFilePath}`);
4664
- }
4665
- if (!result.mcpConfigured) {
4666
- console.log("");
4667
- console.log(` MCP config not written: ${result.mcpConfigPath}`);
4636
+ for (const entry of failed) {
4637
+ console.log(` ${entry.label}: ${entry.result.detail ?? "Unknown error"}`);
4668
4638
  }
4669
4639
  console.log("\n Troubleshooting:");
4670
- if (result.domainsFailed.length > 0) {
4671
- console.log(" - Re-authenticate: delete ~/.one-shot-installer/github-token.json");
4672
- console.log(" - Check network connectivity");
4673
- console.log(" - Confirm your GitHub account has access to the repository");
4674
- }
4675
- if (!result.instructionsCreated || !result.mcpConfigured) {
4676
- console.log(" - Check write permissions for the project directory");
4677
- console.log(` - Project directory: ${result.instructionFilePath.includes("/") ? "check parent folders exist" : "current directory"}`);
4678
- }
4640
+ console.log(" - Re-authenticate: delete ~/.one-shot-installer/github-token.json");
4641
+ console.log(" - Check network connectivity");
4642
+ console.log(" - Confirm your GitHub account has access to the repository");
4643
+ console.log(" - Check write permissions for the project directory");
4679
4644
  }
4680
4645
  console.log("\n" + "\u2500".repeat(48) + "\n");
4681
4646
  }