dlw-machine-setup 0.6.2 → 0.8.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 +211 -36
  2. package/package.json +1 -1
package/bin/installer.js CHANGED
@@ -3850,7 +3850,7 @@ var import_fs6 = require("fs");
3850
3850
  var import_path6 = require("path");
3851
3851
 
3852
3852
  // src/bundles/types.ts
3853
- var SCHEMA_VERSION = 1;
3853
+ var SUPPORTED_SCHEMA_VERSIONS = [1, 2];
3854
3854
 
3855
3855
  // src/utils/marker-block.ts
3856
3856
  var import_fs5 = require("fs");
@@ -3881,11 +3881,99 @@ ${endMarker}`;
3881
3881
  }
3882
3882
  }
3883
3883
 
3884
+ // src/profiles/claude-code.ts
3885
+ var claudeCodeProfile = {
3886
+ agent: "claude-code",
3887
+ instructionsFile: "CLAUDE.md",
3888
+ handlers: {
3889
+ agent: {
3890
+ supported: true,
3891
+ destination: (name) => `.claude/agents/${name}.md`
3892
+ },
3893
+ skill: {
3894
+ supported: true,
3895
+ // Claude reads skills from a folder per skill: `.claude/skills/<name>/SKILL.md`.
3896
+ destination: (name) => `.claude/skills/${name}/SKILL.md`
3897
+ },
3898
+ hook: {
3899
+ supported: true,
3900
+ configFile: ".claude/settings.json",
3901
+ configKey: "hooks",
3902
+ scriptDir: ".claude/hooks"
3903
+ },
3904
+ "instructions-snippet": {
3905
+ supported: true
3906
+ }
3907
+ }
3908
+ };
3909
+
3910
+ // src/profiles/github-copilot.ts
3911
+ var githubCopilotProfile = {
3912
+ agent: "github-copilot",
3913
+ instructionsFile: ".github/copilot-instructions.md",
3914
+ handlers: {
3915
+ agent: {
3916
+ supported: true,
3917
+ destination: (name) => `.github/agents/${name}.agent.md`
3918
+ },
3919
+ skill: {
3920
+ supported: true,
3921
+ destination: (name) => `.github/skills/${name}/SKILL.md`
3922
+ },
3923
+ hook: {
3924
+ // See header docblock for the three HookHandler extensions needed
3925
+ // to flip this on. Until then, hook assets are skipped on Copilot.
3926
+ supported: false
3927
+ },
3928
+ "instructions-snippet": {
3929
+ supported: true
3930
+ }
3931
+ }
3932
+ };
3933
+
3934
+ // src/profiles/cursor.ts
3935
+ var cursorProfile = {
3936
+ agent: "cursor",
3937
+ instructionsFile: ".cursor/rules/instructions.mdc",
3938
+ handlers: {
3939
+ agent: { supported: false },
3940
+ skill: { supported: false },
3941
+ hook: { supported: false },
3942
+ "instructions-snippet": { supported: true }
3943
+ }
3944
+ };
3945
+
3946
+ // src/profiles/index.ts
3947
+ var PROFILES = {
3948
+ "claude-code": claudeCodeProfile,
3949
+ "github-copilot": githubCopilotProfile,
3950
+ "cursor": cursorProfile
3951
+ };
3952
+ function getProfile(agent) {
3953
+ const profile = PROFILES[agent];
3954
+ if (!profile) {
3955
+ const known = Object.keys(PROFILES).map((k) => `"${k}"`).join(", ");
3956
+ throw new Error(
3957
+ `No profile registered for agent "${agent}". Known agents: ${known}. Add src/profiles/${agent}.ts and register it in src/profiles/index.ts.`
3958
+ );
3959
+ }
3960
+ return profile;
3961
+ }
3962
+
3884
3963
  // src/bundles/run-bundle.ts
3885
3964
  var OWNER_KEY = "_bundleOwner";
3886
3965
  async function runBundle(manifest, ctx) {
3887
3966
  assertSchemaCompatible(manifest);
3967
+ assertNameValid(manifest);
3888
3968
  assertBundleRootExists(ctx);
3969
+ switch (manifest.schemaVersion) {
3970
+ case 1:
3971
+ return runBundleV1(manifest, ctx);
3972
+ case 2:
3973
+ return runBundleV2(manifest, ctx);
3974
+ }
3975
+ }
3976
+ function runBundleV1(manifest, ctx) {
3889
3977
  const result = {
3890
3978
  name: manifest.name,
3891
3979
  opsExecuted: 0,
@@ -3896,30 +3984,120 @@ async function runBundle(manifest, ctx) {
3896
3984
  executeOp(op, manifest.name, ctx, result);
3897
3985
  result.opsExecuted++;
3898
3986
  }
3899
- if (manifest.gitignore?.length) {
3900
- upsertMarkerBlock(
3901
- (0, import_path6.join)(ctx.projectPath, ".gitignore"),
3902
- `# ${manifest.name}:start`,
3903
- `# ${manifest.name}:end`,
3904
- manifest.gitignore.join("\n")
3905
- );
3906
- result.filesTouched.push(".gitignore");
3907
- }
3987
+ writeGitignoreBlock(manifest, ctx, result);
3908
3988
  const snippet = manifest.instructions?.[ctx.agent];
3909
3989
  if (snippet) {
3910
3990
  result.instructionsSnippet = snippet;
3911
- if (!ctx.skipInstructions) {
3912
- upsertMarkerBlock(
3913
- (0, import_path6.join)(ctx.projectPath, ctx.instructionsFile),
3914
- `<!-- ${manifest.name}:start -->`,
3915
- `<!-- ${manifest.name}:end -->`,
3916
- snippet
3917
- );
3918
- result.filesTouched.push(ctx.instructionsFile);
3919
- }
3991
+ writeInstructionsBlock(manifest.name, snippet, ctx.instructionsFile, ctx, result);
3992
+ }
3993
+ return result;
3994
+ }
3995
+ function runBundleV2(manifest, ctx) {
3996
+ const profile = getProfile(ctx.agent);
3997
+ const result = {
3998
+ name: manifest.name,
3999
+ opsExecuted: 0,
4000
+ filesTouched: []
4001
+ };
4002
+ const hookPatches = /* @__PURE__ */ new Map();
4003
+ for (const asset of manifest.assets ?? []) {
4004
+ runAsset(asset, profile, hookPatches, ctx, result);
4005
+ result.opsExecuted++;
4006
+ }
4007
+ for (const [file, patch] of hookPatches) {
4008
+ runMergeJson({ op: "merge-json", file, patch }, manifest.name, ctx, result);
4009
+ }
4010
+ for (const op of manifest.ops ?? []) {
4011
+ executeOp(op, manifest.name, ctx, result);
4012
+ result.opsExecuted++;
4013
+ }
4014
+ writeGitignoreBlock(manifest, ctx, result);
4015
+ const snippet = result.instructionsSnippet;
4016
+ if (snippet) {
4017
+ const instructionsFile = profile.instructionsFile || ctx.instructionsFile;
4018
+ writeInstructionsBlock(manifest.name, snippet, instructionsFile, ctx, result);
3920
4019
  }
3921
4020
  return result;
3922
4021
  }
4022
+ function runAsset(asset, profile, hookPatches, ctx, result) {
4023
+ switch (asset.type) {
4024
+ case "agent":
4025
+ runAssetCopy(asset, profile.handlers.agent, ctx, result);
4026
+ return;
4027
+ case "skill":
4028
+ runAssetCopy(asset, profile.handlers.skill, ctx, result);
4029
+ return;
4030
+ case "hook":
4031
+ accumulateHook(asset, profile.handlers.hook, hookPatches, ctx, result);
4032
+ return;
4033
+ case "instructions-snippet":
4034
+ if (profile.handlers["instructions-snippet"].supported) {
4035
+ result.instructionsSnippet = asset.content;
4036
+ }
4037
+ return;
4038
+ }
4039
+ }
4040
+ function runAssetCopy(asset, handler, ctx, result) {
4041
+ if (!handler.supported || !handler.destination) return;
4042
+ const source = resolveBundlePath(asset.source, ctx);
4043
+ if (!(0, import_fs6.existsSync)(source)) return;
4044
+ const target = resolveProjectPath(handler.destination(asset.name), ctx);
4045
+ if ((0, import_fs6.statSync)(source).isDirectory()) {
4046
+ copyDirectory(source, target);
4047
+ } else {
4048
+ (0, import_fs6.mkdirSync)((0, import_path6.dirname)(target), { recursive: true });
4049
+ (0, import_fs6.copyFileSync)(source, target);
4050
+ }
4051
+ result.filesTouched.push(handler.destination(asset.name));
4052
+ }
4053
+ function accumulateHook(asset, handler, hookPatches, ctx, result) {
4054
+ if (!handler.supported) return;
4055
+ if (!handler.configFile || !handler.configKey) return;
4056
+ if (asset.script && handler.scriptDir) {
4057
+ const scriptSource = resolveBundlePath(asset.script, ctx);
4058
+ if ((0, import_fs6.existsSync)(scriptSource)) {
4059
+ const scriptName = (0, import_path6.basename)(asset.script);
4060
+ const scriptRelDest = `${handler.scriptDir}/${scriptName}`;
4061
+ const scriptDest = resolveProjectPath(scriptRelDest, ctx);
4062
+ (0, import_fs6.mkdirSync)((0, import_path6.dirname)(scriptDest), { recursive: true });
4063
+ (0, import_fs6.copyFileSync)(scriptSource, scriptDest);
4064
+ result.filesTouched.push(scriptRelDest);
4065
+ }
4066
+ }
4067
+ const command = handler.scriptDir ? asset.command.replace(/\{hookDir\}/g, handler.scriptDir) : asset.command;
4068
+ const entry = {
4069
+ hooks: [{ type: "command", command }]
4070
+ };
4071
+ if (asset.matcher) entry.matcher = asset.matcher;
4072
+ let patch = hookPatches.get(handler.configFile);
4073
+ if (!patch) {
4074
+ patch = {};
4075
+ hookPatches.set(handler.configFile, patch);
4076
+ }
4077
+ const root = patch[handler.configKey] ??= {};
4078
+ const events = root[asset.event] ??= [];
4079
+ events.push(entry);
4080
+ }
4081
+ function writeGitignoreBlock(manifest, ctx, result) {
4082
+ if (!manifest.gitignore?.length) return;
4083
+ upsertMarkerBlock(
4084
+ (0, import_path6.join)(ctx.projectPath, ".gitignore"),
4085
+ `# ${manifest.name}:start`,
4086
+ `# ${manifest.name}:end`,
4087
+ manifest.gitignore.join("\n")
4088
+ );
4089
+ result.filesTouched.push(".gitignore");
4090
+ }
4091
+ function writeInstructionsBlock(bundleName, snippet, instructionsFile, ctx, result) {
4092
+ if (ctx.skipInstructions) return;
4093
+ upsertMarkerBlock(
4094
+ (0, import_path6.join)(ctx.projectPath, instructionsFile),
4095
+ `<!-- ${bundleName}:start -->`,
4096
+ `<!-- ${bundleName}:end -->`,
4097
+ snippet
4098
+ );
4099
+ result.filesTouched.push(instructionsFile);
4100
+ }
3923
4101
  function executeOp(op, owner, ctx, result) {
3924
4102
  switch (op.op) {
3925
4103
  case "copy":
@@ -4021,11 +4199,14 @@ function resolveProjectPath(rel, ctx) {
4021
4199
  return full;
4022
4200
  }
4023
4201
  function assertSchemaCompatible(manifest) {
4024
- if (manifest.schemaVersion !== SCHEMA_VERSION) {
4202
+ if (!SUPPORTED_SCHEMA_VERSIONS.includes(manifest.schemaVersion)) {
4203
+ const supported = SUPPORTED_SCHEMA_VERSIONS.join(", ");
4025
4204
  throw new Error(
4026
- `Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${SCHEMA_VERSION}. Upgrade the installer or re-pin the bundle.`
4205
+ `Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${supported}. Upgrade the installer or re-pin the bundle.`
4027
4206
  );
4028
4207
  }
4208
+ }
4209
+ function assertNameValid(manifest) {
4029
4210
  if (!manifest.name || !/^[a-z][a-z0-9-]*$/.test(manifest.name)) {
4030
4211
  throw new Error(
4031
4212
  `Bundle name must match /^[a-z][a-z0-9-]*$/: got "${manifest.name}"`
@@ -4039,14 +4220,8 @@ function assertBundleRootExists(ctx) {
4039
4220
  }
4040
4221
 
4041
4222
  // src/steps/resources/fetch-factory.ts
4042
- function getFactoryAsset(agent) {
4043
- switch (agent) {
4044
- case "github-copilot":
4045
- return { assetName: "factory-copilot.tar.gz", rootFolder: "factory-copilot" };
4046
- default:
4047
- return { assetName: "factory.tar.gz", rootFolder: "factory" };
4048
- }
4049
- }
4223
+ var FACTORY_ASSET_NAME = "factory.tar.gz";
4224
+ var FACTORY_ROOT_FOLDER = "factory";
4050
4225
  var fetch_factory_default = defineStep({
4051
4226
  name: "fetch-factory",
4052
4227
  label: "Installing Factory framework",
@@ -4065,7 +4240,6 @@ var fetch_factory_default = defineStep({
4065
4240
  });
4066
4241
  async function fetchFactory(token, repo, targetDir, agent) {
4067
4242
  const result = { success: false, filesInstalled: [] };
4068
- const { assetName, rootFolder } = getFactoryAsset(agent);
4069
4243
  let release;
4070
4244
  try {
4071
4245
  release = await fetchLatestRelease(token, repo);
@@ -4073,18 +4247,18 @@ async function fetchFactory(token, repo, targetDir, agent) {
4073
4247
  result.failureReason = err instanceof Error ? err.message : String(err);
4074
4248
  return result;
4075
4249
  }
4076
- const asset = release.assets.find((a) => a.name === assetName);
4250
+ const asset = release.assets.find((a) => a.name === FACTORY_ASSET_NAME);
4077
4251
  if (!asset) {
4078
- result.failureReason = `${assetName} not found in release`;
4252
+ result.failureReason = `${FACTORY_ASSET_NAME} not found in release`;
4079
4253
  return result;
4080
4254
  }
4081
4255
  let archive = null;
4082
4256
  try {
4083
4257
  archive = await downloadAndExtractAsset(token, asset, targetDir, ".temp-factory-download");
4084
4258
  const extractedEntries = (0, import_fs7.readdirSync)(archive.extractedRoot);
4085
- const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === rootFolder.toLowerCase());
4259
+ const extractedFolder = extractedEntries.find((e) => e.toLowerCase() === FACTORY_ROOT_FOLDER.toLowerCase());
4086
4260
  if (!extractedFolder) {
4087
- result.failureReason = `No ${rootFolder}/ folder in archive`;
4261
+ result.failureReason = `No ${FACTORY_ROOT_FOLDER}/ folder in archive`;
4088
4262
  return result;
4089
4263
  }
4090
4264
  const extractedPath = (0, import_path7.join)(archive.extractedRoot, extractedFolder);
@@ -4219,8 +4393,8 @@ function extractFirstHeading(filePath) {
4219
4393
  }
4220
4394
  } catch {
4221
4395
  }
4222
- const basename = filePath.split(/[/\\]/).pop() ?? "";
4223
- return basename.replace(/\.md$/i, "").replace(/[-_]+/g, " ");
4396
+ const basename2 = filePath.split(/[/\\]/).pop() ?? "";
4397
+ return basename2.replace(/\.md$/i, "").replace(/[-_]+/g, " ");
4224
4398
  }
4225
4399
  function formatPathRef(contextPath, description, agent) {
4226
4400
  if (agent === "github-copilot") {
@@ -4471,6 +4645,7 @@ var package_default = {
4471
4645
  scripts: {
4472
4646
  dev: "tsx src/index.ts",
4473
4647
  build: 'esbuild src/index.ts --bundle --platform=node --banner:js="#!/usr/bin/env node" --outfile=wrapper/bin/installer.js',
4648
+ "build:all": "npm run build",
4474
4649
  "publish:wrapper": "cd wrapper && npm publish"
4475
4650
  },
4476
4651
  dependencies: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dlw-machine-setup",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "description": "One-shot installer for The Machine toolchain",
5
5
  "bin": {
6
6
  "dlw-machine-setup": "bin/installer.js"