dlw-machine-setup 0.5.16 → 0.5.18

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 +148 -46
  2. package/package.json +1 -1
package/bin/installer.js CHANGED
@@ -3388,6 +3388,7 @@ async function tryOrgReposList(headers, topic) {
3388
3388
  var import_fs = require("fs");
3389
3389
  var import_path = require("path");
3390
3390
  var import_os = require("os");
3391
+ var import_child_process = require("child_process");
3391
3392
  var GITHUB_CLIENT_ID = "Ov23liwpMumAhwVufZ7N";
3392
3393
  var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
3393
3394
  var GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
@@ -3511,11 +3512,26 @@ async function authenticateWithGitHub() {
3511
3512
  console.log("You'll authenticate once, and the token will be cached for future use.\n");
3512
3513
  console.log("Requesting authorization code...");
3513
3514
  const deviceCodeData = await requestDeviceCode();
3515
+ let copied = false;
3516
+ try {
3517
+ const os = (0, import_os.platform)();
3518
+ if (os === "win32") {
3519
+ (0, import_child_process.execSync)(`echo ${deviceCodeData.user_code} | clip`, { stdio: "ignore" });
3520
+ copied = true;
3521
+ } else if (os === "darwin") {
3522
+ (0, import_child_process.execSync)(`echo "${deviceCodeData.user_code}" | pbcopy`, { stdio: "ignore" });
3523
+ copied = true;
3524
+ } else {
3525
+ (0, import_child_process.execSync)(`echo "${deviceCodeData.user_code}" | xclip -selection clipboard`, { stdio: "ignore" });
3526
+ copied = true;
3527
+ }
3528
+ } catch {
3529
+ }
3514
3530
  console.log("\n\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\u2557");
3515
3531
  console.log("\u2551 GitHub Authentication \u2551");
3516
3532
  console.log("\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\u255D\n");
3517
3533
  console.log(`1. Visit: ${deviceCodeData.verification_uri}`);
3518
- console.log(`2. Enter code: ${deviceCodeData.user_code}`);
3534
+ console.log(`2. Enter code: ${deviceCodeData.user_code}${copied ? " (copied to clipboard!)" : ""}`);
3519
3535
  console.log("\nWaiting for authentication...");
3520
3536
  console.log("(This window will continue automatically once you approve)\n");
3521
3537
  const accessToken = await pollForToken(
@@ -3622,6 +3638,7 @@ var steps_exports = {};
3622
3638
  __export(steps_exports, {
3623
3639
  fetchContexts: () => fetch_contexts_default,
3624
3640
  fetchFactory: () => fetch_factory_default,
3641
+ runMcpInstallCommands: () => run_mcp_install_commands_default,
3625
3642
  updateGitignore: () => update_gitignore_default,
3626
3643
  writeInstructions: () => write_instructions_default,
3627
3644
  writeMcpConfig: () => write_mcp_config_default,
@@ -3631,7 +3648,7 @@ __export(steps_exports, {
3631
3648
  // src/steps/resources/fetch-contexts.ts
3632
3649
  var import_fs2 = require("fs");
3633
3650
  var import_path2 = require("path");
3634
- var import_child_process = require("child_process");
3651
+ var import_child_process2 = require("child_process");
3635
3652
  var MIN_FILE_SIZE = 1024;
3636
3653
  var fetch_contexts_default = defineStep({
3637
3654
  name: "fetch-contexts",
@@ -3640,10 +3657,20 @@ var fetch_contexts_default = defineStep({
3640
3657
  execute: async (ctx) => {
3641
3658
  const uniqueDomains = [...new Set(ctx.config.technologies.flatMap((p) => p.domains))];
3642
3659
  const domainValues = uniqueDomains.map((d) => d.toLowerCase());
3643
- const downloadResult = await fetchContexts(domainValues, ctx.token, ctx.repo, ctx.config.projectPath);
3660
+ if (!ctx.contextRepo) {
3661
+ for (const domain of domainValues) {
3662
+ ctx.installed.domainsFailed = domainValues;
3663
+ }
3664
+ return {
3665
+ status: "failed",
3666
+ detail: "Context repo not found (topic: context-data)"
3667
+ };
3668
+ }
3669
+ const downloadResult = await fetchContexts(domainValues, ctx.token, ctx.contextRepo, ctx.config.projectPath);
3644
3670
  ctx.installed.domainsInstalled = downloadResult.successful;
3645
3671
  ctx.installed.domainsFailed = downloadResult.failed;
3646
3672
  ctx.installed.failureReasons = downloadResult.failureReasons;
3673
+ ctx.installed.contextReleaseVersion = downloadResult.releaseVersion;
3647
3674
  const domainColWidth = Math.max(...domainValues.map((d) => d.length), 8) + 2;
3648
3675
  console.log("");
3649
3676
  for (const domain of domainValues) {
@@ -3667,7 +3694,7 @@ var fetch_contexts_default = defineStep({
3667
3694
  async function fetchContexts(domains, token, repo, targetDir) {
3668
3695
  const result = { successful: [], failed: [], failureReasons: {} };
3669
3696
  if (domains.length === 0) return result;
3670
- const tarCheck = (0, import_child_process.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3697
+ const tarCheck = (0, import_child_process2.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3671
3698
  if (tarCheck.status !== 0) {
3672
3699
  throw new Error("tar command not found. Please ensure tar is installed and available in your PATH.");
3673
3700
  }
@@ -3683,52 +3710,58 @@ async function fetchContexts(domains, token, repo, targetDir) {
3683
3710
  throw new Error(`GitHub API error (${releaseResponse.status}): ${getReadableError(releaseResponse.status)}`);
3684
3711
  }
3685
3712
  const releaseData = await releaseResponse.json();
3713
+ result.releaseVersion = releaseData.tag_name;
3714
+ const asset = releaseData.assets?.find((a) => a.name.endsWith(".tar.gz"));
3715
+ if (!asset) {
3716
+ for (const domain of domains) {
3717
+ result.failed.push(domain);
3718
+ result.failureReasons[domain] = "No tar.gz asset in release";
3719
+ }
3720
+ return result;
3721
+ }
3686
3722
  const contextsDir = (0, import_path2.join)(targetDir, "_ai-context");
3687
3723
  const tempDir = (0, import_path2.join)(targetDir, ".temp-download");
3688
3724
  try {
3689
3725
  if (!(0, import_fs2.existsSync)(contextsDir)) (0, import_fs2.mkdirSync)(contextsDir, { recursive: true });
3690
3726
  if ((0, import_fs2.existsSync)(tempDir)) (0, import_fs2.rmSync)(tempDir, { recursive: true, force: true });
3691
3727
  (0, import_fs2.mkdirSync)(tempDir, { recursive: true });
3728
+ const downloadHeaders = {
3729
+ "Accept": "application/octet-stream",
3730
+ "Authorization": `Bearer ${token}`
3731
+ };
3732
+ const response = await fetchWithRetry(asset.url, { headers: downloadHeaders });
3733
+ if (!response.ok) {
3734
+ throw new Error(`Download failed: ${getReadableError(response.status)}`);
3735
+ }
3736
+ const archivePath = (0, import_path2.join)(tempDir, asset.name);
3737
+ const arrayBuffer = await response.arrayBuffer();
3738
+ (0, import_fs2.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
3739
+ const stats = (0, import_fs2.statSync)(archivePath);
3740
+ if (stats.size < MIN_FILE_SIZE) {
3741
+ throw new Error("File corrupted");
3742
+ }
3743
+ const listResult = (0, import_child_process2.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
3744
+ if (listResult.status !== 0) throw new Error("Failed to read archive contents");
3745
+ const resolvedTempDir = (0, import_path2.resolve)(tempDir);
3746
+ for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
3747
+ const entryPath = (0, import_path2.resolve)((0, import_path2.join)(tempDir, entry.replace(/\/$/, "")));
3748
+ if (!entryPath.startsWith(resolvedTempDir + import_path2.sep)) {
3749
+ throw new Error(`Archive contains unsafe path: ${entry}`);
3750
+ }
3751
+ }
3752
+ const extractResult = (0, import_child_process2.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
3753
+ if (extractResult.status !== 0) throw new Error("Archive extraction failed");
3754
+ const extractedFolders = (0, import_fs2.readdirSync)(tempDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3692
3755
  for (const domain of domains) {
3693
3756
  try {
3694
- const domainPath = (0, import_path2.join)(contextsDir, domain);
3695
- const archiveName = `${domain.toLowerCase()}.tar.gz`;
3696
- const asset = releaseData.assets?.find((a) => a.name === archiveName);
3697
- if (!asset) {
3757
+ const match = extractedFolders.find((f) => f.toLowerCase() === domain.toLowerCase());
3758
+ if (!match) {
3698
3759
  result.failed.push(domain);
3699
- result.failureReasons[domain] = "Not found in release";
3760
+ result.failureReasons[domain] = "Not found in archive";
3700
3761
  continue;
3701
3762
  }
3702
- const downloadHeaders = {
3703
- "Accept": "application/octet-stream",
3704
- "Authorization": `Bearer ${token}`
3705
- };
3706
- const response = await fetchWithRetry(asset.url, { headers: downloadHeaders });
3707
- if (!response.ok) {
3708
- throw new Error(`Download failed: ${getReadableError(response.status)}`);
3709
- }
3710
- const archivePath = (0, import_path2.join)(tempDir, archiveName);
3711
- const arrayBuffer = await response.arrayBuffer();
3712
- (0, import_fs2.writeFileSync)(archivePath, Buffer.from(arrayBuffer));
3713
- const stats = (0, import_fs2.statSync)(archivePath);
3714
- if (stats.size < MIN_FILE_SIZE) {
3715
- throw new Error("File corrupted");
3716
- }
3717
- const listResult = (0, import_child_process.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
3718
- if (listResult.status !== 0) throw new Error("Failed to read archive contents");
3719
- const resolvedTempDir = (0, import_path2.resolve)(tempDir);
3720
- for (const entry of listResult.stdout.split("\n").filter(Boolean)) {
3721
- const entryPath = (0, import_path2.resolve)((0, import_path2.join)(tempDir, entry.replace(/\/$/, "")));
3722
- if (!entryPath.startsWith(resolvedTempDir + import_path2.sep)) {
3723
- throw new Error(`Archive contains unsafe path: ${entry}`);
3724
- }
3725
- }
3726
- const extractResult = (0, import_child_process.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
3727
- if (extractResult.status !== 0) throw new Error("Archive extraction failed");
3728
- const extractedEntries = (0, import_fs2.readdirSync)(tempDir);
3729
- const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === domain.toLowerCase());
3730
- if (!extractedFolder) throw new Error("Extraction failed");
3731
- copyDirectory((0, import_path2.join)(tempDir, extractedFolder), domainPath);
3763
+ const domainPath = (0, import_path2.join)(contextsDir, domain);
3764
+ copyDirectory((0, import_path2.join)(tempDir, match), domainPath);
3732
3765
  result.successful.push(domain);
3733
3766
  } catch (error) {
3734
3767
  result.failed.push(domain);
@@ -3780,7 +3813,7 @@ function getReadableError(status) {
3780
3813
  // src/steps/resources/fetch-factory.ts
3781
3814
  var import_fs3 = require("fs");
3782
3815
  var import_path3 = require("path");
3783
- var import_child_process2 = require("child_process");
3816
+ var import_child_process3 = require("child_process");
3784
3817
  var MIN_FILE_SIZE2 = 1024;
3785
3818
  function getFactoryAsset(agent) {
3786
3819
  switch (agent) {
@@ -3805,7 +3838,7 @@ var fetch_factory_default = defineStep({
3805
3838
  });
3806
3839
  async function fetchFactory(token, repo, targetDir, agent) {
3807
3840
  const result = { success: false, filesInstalled: [] };
3808
- const tarCheck = (0, import_child_process2.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3841
+ const tarCheck = (0, import_child_process3.spawnSync)("tar", ["--version"], { stdio: "ignore" });
3809
3842
  if (tarCheck.status !== 0) {
3810
3843
  result.failureReason = "tar command not found";
3811
3844
  return result;
@@ -3848,7 +3881,7 @@ async function fetchFactory(token, repo, targetDir, agent) {
3848
3881
  result.failureReason = "Downloaded file corrupted (too small)";
3849
3882
  return result;
3850
3883
  }
3851
- const listResult = (0, import_child_process2.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
3884
+ const listResult = (0, import_child_process3.spawnSync)("tar", ["-tzf", archivePath], { encoding: "utf8" });
3852
3885
  if (listResult.status !== 0) {
3853
3886
  result.failureReason = "Failed to read archive contents";
3854
3887
  return result;
@@ -3861,7 +3894,7 @@ async function fetchFactory(token, repo, targetDir, agent) {
3861
3894
  return result;
3862
3895
  }
3863
3896
  }
3864
- const extractResult = (0, import_child_process2.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
3897
+ const extractResult = (0, import_child_process3.spawnSync)("tar", ["-xzf", archivePath, "-C", tempDir], { stdio: "ignore" });
3865
3898
  if (extractResult.status !== 0) {
3866
3899
  result.failureReason = "Archive extraction failed";
3867
3900
  return result;
@@ -4336,7 +4369,7 @@ var write_mcp_config_default = defineStep({
4336
4369
  const existingServers = existingFile[target.rootKey] ?? {};
4337
4370
  const newServers = {};
4338
4371
  for (const [serverName, serverConfig] of Object.entries(filteredMcpConfig)) {
4339
- const { description, useWhen, active, ...mcpFields } = serverConfig;
4372
+ const { description, useWhen, active, installCommand, ...mcpFields } = serverConfig;
4340
4373
  newServers[serverName] = mcpFields;
4341
4374
  addedServers.push(serverName);
4342
4375
  }
@@ -4362,6 +4395,66 @@ function getAgentMCPTarget(agent) {
4362
4395
  }
4363
4396
  }
4364
4397
 
4398
+ // src/steps/setup/run-mcp-install-commands.ts
4399
+ var import_child_process4 = require("child_process");
4400
+ var run_mcp_install_commands_default = defineStep({
4401
+ name: "run-mcp-install-commands",
4402
+ label: "Registering MCP servers with Claude Code",
4403
+ when: (ctx) => {
4404
+ if (ctx.config.agent !== "claude-code") return false;
4405
+ const filtered = getFilteredMcpConfig(ctx);
4406
+ return Object.values(filtered).some((s) => typeof s.installCommand === "string" && s.installCommand.length > 0);
4407
+ },
4408
+ execute: async (ctx) => {
4409
+ if (!isClaudeCliAvailable()) {
4410
+ return {
4411
+ status: "skipped",
4412
+ detail: "`claude` CLI not found on PATH \u2014 skipping CLI registration (project .mcp.json was still written)"
4413
+ };
4414
+ }
4415
+ const filtered = getFilteredMcpConfig(ctx);
4416
+ const succeeded = [];
4417
+ const failed = [];
4418
+ for (const [name, cfg] of Object.entries(filtered)) {
4419
+ const cmd = cfg.installCommand;
4420
+ if (typeof cmd !== "string" || cmd.length === 0) continue;
4421
+ const result = (0, import_child_process4.spawnSync)(cmd, {
4422
+ shell: true,
4423
+ stdio: "pipe",
4424
+ encoding: "utf-8",
4425
+ cwd: ctx.config.projectPath
4426
+ });
4427
+ if (result.status === 0) {
4428
+ succeeded.push(name);
4429
+ } else {
4430
+ const stderr = (result.stderr ?? "").trim();
4431
+ const reason = stderr.length > 0 ? stderr.split("\n")[0] : `exit ${result.status}`;
4432
+ failed.push({ name, reason });
4433
+ }
4434
+ }
4435
+ ctx.installed.mcpInstallCommandsRun = { succeeded, failed };
4436
+ if (failed.length === 0) {
4437
+ return {
4438
+ status: "success",
4439
+ message: succeeded.length > 0 ? succeeded.join(", ") : void 0
4440
+ };
4441
+ }
4442
+ const failedSummary = failed.map((f) => `${f.name} (${f.reason})`).join("; ");
4443
+ if (succeeded.length === 0) {
4444
+ return { status: "failed", detail: failedSummary };
4445
+ }
4446
+ return {
4447
+ status: "success",
4448
+ message: `${succeeded.join(", ")}; failed: ${failed.map((f) => f.name).join(", ")}`,
4449
+ detail: `Some registrations failed: ${failedSummary}`
4450
+ };
4451
+ }
4452
+ });
4453
+ function isClaudeCliAvailable() {
4454
+ const check2 = (0, import_child_process4.spawnSync)("claude --version", { shell: true, stdio: "ignore" });
4455
+ return check2.status === 0;
4456
+ }
4457
+
4365
4458
  // src/steps/setup/update-gitignore.ts
4366
4459
  var import_fs6 = require("fs");
4367
4460
  var import_path6 = require("path");
@@ -4422,7 +4515,7 @@ var update_gitignore_default = defineStep({
4422
4515
  var import_fs7 = require("fs");
4423
4516
  var import_path7 = require("path");
4424
4517
  var import_os2 = require("os");
4425
- var INSTALLER_VERSION = "0.5.16";
4518
+ var INSTALLER_VERSION = "0.5.18";
4426
4519
  var write_state_default = defineStep({
4427
4520
  name: "write-state",
4428
4521
  label: "Saving installation state",
@@ -4436,6 +4529,7 @@ var write_state_default = defineStep({
4436
4529
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
4437
4530
  installerVersion: INSTALLER_VERSION,
4438
4531
  releaseVersion: ctx.config.releaseVersion,
4532
+ contextReleaseVersion: ctx.installed.contextReleaseVersion ?? null,
4439
4533
  projectPath: ctx.config.projectPath,
4440
4534
  agent: ctx.config.agent,
4441
4535
  technologies: ctx.config.technologies.map((p) => p.id),
@@ -4542,10 +4636,14 @@ async function main() {
4542
4636
  try {
4543
4637
  const token = await getGitHubToken();
4544
4638
  console.log(" Locating repositories...");
4545
- const [repo, factoryRepo] = await Promise.all([
4639
+ const [repo, contextRepo, factoryRepo] = await Promise.all([
4546
4640
  discoverRepo(token),
4641
+ discoverRepo(token, "context-data").catch(() => null),
4547
4642
  discoverRepo(token, "factory-data").catch(() => null)
4548
4643
  ]);
4644
+ if (!contextRepo) {
4645
+ console.log(" \u26A0 Context repo not found \u2014 AI contexts will not be installed.");
4646
+ }
4549
4647
  if (!factoryRepo) {
4550
4648
  console.log(" \u26A0 Factory repo not found \u2014 Factory will not be installed.");
4551
4649
  }
@@ -4566,6 +4664,7 @@ async function main() {
4566
4664
  config,
4567
4665
  token,
4568
4666
  repo,
4667
+ contextRepo,
4569
4668
  factoryRepo,
4570
4669
  installed: {}
4571
4670
  };
@@ -4610,6 +4709,9 @@ async function collectInputs(options, releaseVersion, factoryAvailable = false)
4610
4709
  mcpConfig["azure-devops"].args = mcpConfig["azure-devops"].args?.map(
4611
4710
  (arg) => arg === "__AZURE_ORG__" ? azureDevOpsOrg : arg
4612
4711
  );
4712
+ if (mcpConfig["azure-devops"].installCommand) {
4713
+ mcpConfig["azure-devops"].installCommand = mcpConfig["azure-devops"].installCommand.replace(/__AZURE_ORG__/g, azureDevOpsOrg);
4714
+ }
4613
4715
  }
4614
4716
  }
4615
4717
  const projectInput = await esm_default4({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dlw-machine-setup",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
4
4
  "description": "One-shot installer for The Machine toolchain",
5
5
  "bin": {
6
6
  "dlw-machine-setup": "bin/installer.js"