dlw-machine-setup 0.6.2 → 0.7.1

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 +212 -23
  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,106 @@ ${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
+ // TODO(phase-3): verify destination path against Copilot CLI docs.
3918
+ destination: (name) => `.github/agents/${name}.md`
3919
+ // TODO(phase-3): supply frontmatter rewriter that maps Claude's
3920
+ // {name, description, disable-model-invocation, ...} to whatever
3921
+ // Copilot expects ({name, description, category, version, ...}).
3922
+ },
3923
+ skill: {
3924
+ supported: true,
3925
+ // TODO(phase-3): verify destination path against Copilot CLI docs.
3926
+ destination: (name) => `.github/skills/${name}.skill.md`
3927
+ // TODO(phase-3): supply frontmatter rewriter.
3928
+ },
3929
+ hook: {
3930
+ // TODO(phase-3): verify whether Copilot CLI has a hook primitive.
3931
+ // If yes, flip to supported: true and fill in configFile/configKey/scriptDir.
3932
+ // If no, leave as unsupported — assets of type 'hook' are silently skipped.
3933
+ supported: false
3934
+ },
3935
+ "instructions-snippet": {
3936
+ supported: true
3937
+ }
3938
+ }
3939
+ };
3940
+
3941
+ // src/profiles/cursor.ts
3942
+ var cursorProfile = {
3943
+ agent: "cursor",
3944
+ instructionsFile: ".cursor/rules/instructions.mdc",
3945
+ handlers: {
3946
+ agent: { supported: false },
3947
+ skill: { supported: false },
3948
+ hook: { supported: false },
3949
+ "instructions-snippet": { supported: true }
3950
+ }
3951
+ };
3952
+
3953
+ // src/profiles/index.ts
3954
+ var PROFILES = {
3955
+ "claude-code": claudeCodeProfile,
3956
+ "github-copilot": githubCopilotProfile,
3957
+ "cursor": cursorProfile
3958
+ };
3959
+ function getProfile(agent) {
3960
+ const profile = PROFILES[agent];
3961
+ if (!profile) {
3962
+ const known = Object.keys(PROFILES).map((k) => `"${k}"`).join(", ");
3963
+ throw new Error(
3964
+ `No profile registered for agent "${agent}". Known agents: ${known}. Add src/profiles/${agent}.ts and register it in src/profiles/index.ts.`
3965
+ );
3966
+ }
3967
+ return profile;
3968
+ }
3969
+
3884
3970
  // src/bundles/run-bundle.ts
3885
3971
  var OWNER_KEY = "_bundleOwner";
3886
3972
  async function runBundle(manifest, ctx) {
3887
3973
  assertSchemaCompatible(manifest);
3974
+ assertNameValid(manifest);
3888
3975
  assertBundleRootExists(ctx);
3976
+ switch (manifest.schemaVersion) {
3977
+ case 1:
3978
+ return runBundleV1(manifest, ctx);
3979
+ case 2:
3980
+ return runBundleV2(manifest, ctx);
3981
+ }
3982
+ }
3983
+ function runBundleV1(manifest, ctx) {
3889
3984
  const result = {
3890
3985
  name: manifest.name,
3891
3986
  opsExecuted: 0,
@@ -3896,30 +3991,120 @@ async function runBundle(manifest, ctx) {
3896
3991
  executeOp(op, manifest.name, ctx, result);
3897
3992
  result.opsExecuted++;
3898
3993
  }
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
- }
3994
+ writeGitignoreBlock(manifest, ctx, result);
3908
3995
  const snippet = manifest.instructions?.[ctx.agent];
3909
3996
  if (snippet) {
3910
3997
  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
- }
3998
+ writeInstructionsBlock(manifest.name, snippet, ctx.instructionsFile, ctx, result);
3920
3999
  }
3921
4000
  return result;
3922
4001
  }
4002
+ function runBundleV2(manifest, ctx) {
4003
+ const profile = getProfile(ctx.agent);
4004
+ const result = {
4005
+ name: manifest.name,
4006
+ opsExecuted: 0,
4007
+ filesTouched: []
4008
+ };
4009
+ const hookPatches = /* @__PURE__ */ new Map();
4010
+ for (const asset of manifest.assets ?? []) {
4011
+ runAsset(asset, profile, hookPatches, ctx, result);
4012
+ result.opsExecuted++;
4013
+ }
4014
+ for (const [file, patch] of hookPatches) {
4015
+ runMergeJson({ op: "merge-json", file, patch }, manifest.name, ctx, result);
4016
+ }
4017
+ for (const op of manifest.ops ?? []) {
4018
+ executeOp(op, manifest.name, ctx, result);
4019
+ result.opsExecuted++;
4020
+ }
4021
+ writeGitignoreBlock(manifest, ctx, result);
4022
+ const snippet = result.instructionsSnippet;
4023
+ if (snippet) {
4024
+ const instructionsFile = profile.instructionsFile || ctx.instructionsFile;
4025
+ writeInstructionsBlock(manifest.name, snippet, instructionsFile, ctx, result);
4026
+ }
4027
+ return result;
4028
+ }
4029
+ function runAsset(asset, profile, hookPatches, ctx, result) {
4030
+ switch (asset.type) {
4031
+ case "agent":
4032
+ runAssetCopy(asset, profile.handlers.agent, ctx, result);
4033
+ return;
4034
+ case "skill":
4035
+ runAssetCopy(asset, profile.handlers.skill, ctx, result);
4036
+ return;
4037
+ case "hook":
4038
+ accumulateHook(asset, profile.handlers.hook, hookPatches, ctx, result);
4039
+ return;
4040
+ case "instructions-snippet":
4041
+ if (profile.handlers["instructions-snippet"].supported) {
4042
+ result.instructionsSnippet = asset.content;
4043
+ }
4044
+ return;
4045
+ }
4046
+ }
4047
+ function runAssetCopy(asset, handler, ctx, result) {
4048
+ if (!handler.supported || !handler.destination) return;
4049
+ const source = resolveBundlePath(asset.source, ctx);
4050
+ if (!(0, import_fs6.existsSync)(source)) return;
4051
+ const target = resolveProjectPath(handler.destination(asset.name), ctx);
4052
+ if ((0, import_fs6.statSync)(source).isDirectory()) {
4053
+ copyDirectory(source, target);
4054
+ } else {
4055
+ (0, import_fs6.mkdirSync)((0, import_path6.dirname)(target), { recursive: true });
4056
+ (0, import_fs6.copyFileSync)(source, target);
4057
+ }
4058
+ result.filesTouched.push(handler.destination(asset.name));
4059
+ }
4060
+ function accumulateHook(asset, handler, hookPatches, ctx, result) {
4061
+ if (!handler.supported) return;
4062
+ if (!handler.configFile || !handler.configKey) return;
4063
+ if (asset.script && handler.scriptDir) {
4064
+ const scriptSource = resolveBundlePath(asset.script, ctx);
4065
+ if ((0, import_fs6.existsSync)(scriptSource)) {
4066
+ const scriptName = (0, import_path6.basename)(asset.script);
4067
+ const scriptRelDest = `${handler.scriptDir}/${scriptName}`;
4068
+ const scriptDest = resolveProjectPath(scriptRelDest, ctx);
4069
+ (0, import_fs6.mkdirSync)((0, import_path6.dirname)(scriptDest), { recursive: true });
4070
+ (0, import_fs6.copyFileSync)(scriptSource, scriptDest);
4071
+ result.filesTouched.push(scriptRelDest);
4072
+ }
4073
+ }
4074
+ const command = handler.scriptDir ? asset.command.replace(/\{hookDir\}/g, handler.scriptDir) : asset.command;
4075
+ const entry = {
4076
+ hooks: [{ type: "command", command }]
4077
+ };
4078
+ if (asset.matcher) entry.matcher = asset.matcher;
4079
+ let patch = hookPatches.get(handler.configFile);
4080
+ if (!patch) {
4081
+ patch = {};
4082
+ hookPatches.set(handler.configFile, patch);
4083
+ }
4084
+ const root = patch[handler.configKey] ??= {};
4085
+ const events = root[asset.event] ??= [];
4086
+ events.push(entry);
4087
+ }
4088
+ function writeGitignoreBlock(manifest, ctx, result) {
4089
+ if (!manifest.gitignore?.length) return;
4090
+ upsertMarkerBlock(
4091
+ (0, import_path6.join)(ctx.projectPath, ".gitignore"),
4092
+ `# ${manifest.name}:start`,
4093
+ `# ${manifest.name}:end`,
4094
+ manifest.gitignore.join("\n")
4095
+ );
4096
+ result.filesTouched.push(".gitignore");
4097
+ }
4098
+ function writeInstructionsBlock(bundleName, snippet, instructionsFile, ctx, result) {
4099
+ if (ctx.skipInstructions) return;
4100
+ upsertMarkerBlock(
4101
+ (0, import_path6.join)(ctx.projectPath, instructionsFile),
4102
+ `<!-- ${bundleName}:start -->`,
4103
+ `<!-- ${bundleName}:end -->`,
4104
+ snippet
4105
+ );
4106
+ result.filesTouched.push(instructionsFile);
4107
+ }
3923
4108
  function executeOp(op, owner, ctx, result) {
3924
4109
  switch (op.op) {
3925
4110
  case "copy":
@@ -4021,11 +4206,14 @@ function resolveProjectPath(rel, ctx) {
4021
4206
  return full;
4022
4207
  }
4023
4208
  function assertSchemaCompatible(manifest) {
4024
- if (manifest.schemaVersion !== SCHEMA_VERSION) {
4209
+ if (!SUPPORTED_SCHEMA_VERSIONS.includes(manifest.schemaVersion)) {
4210
+ const supported = SUPPORTED_SCHEMA_VERSIONS.join(", ");
4025
4211
  throw new Error(
4026
- `Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${SCHEMA_VERSION}. Upgrade the installer or re-pin the bundle.`
4212
+ `Bundle "${manifest.name}" declares schemaVersion ${manifest.schemaVersion}, installer supports ${supported}. Upgrade the installer or re-pin the bundle.`
4027
4213
  );
4028
4214
  }
4215
+ }
4216
+ function assertNameValid(manifest) {
4029
4217
  if (!manifest.name || !/^[a-z][a-z0-9-]*$/.test(manifest.name)) {
4030
4218
  throw new Error(
4031
4219
  `Bundle name must match /^[a-z][a-z0-9-]*$/: got "${manifest.name}"`
@@ -4219,8 +4407,8 @@ function extractFirstHeading(filePath) {
4219
4407
  }
4220
4408
  } catch {
4221
4409
  }
4222
- const basename = filePath.split(/[/\\]/).pop() ?? "";
4223
- return basename.replace(/\.md$/i, "").replace(/[-_]+/g, " ");
4410
+ const basename2 = filePath.split(/[/\\]/).pop() ?? "";
4411
+ return basename2.replace(/\.md$/i, "").replace(/[-_]+/g, " ");
4224
4412
  }
4225
4413
  function formatPathRef(contextPath, description, agent) {
4226
4414
  if (agent === "github-copilot") {
@@ -4471,6 +4659,7 @@ var package_default = {
4471
4659
  scripts: {
4472
4660
  dev: "tsx src/index.ts",
4473
4661
  build: 'esbuild src/index.ts --bundle --platform=node --banner:js="#!/usr/bin/env node" --outfile=wrapper/bin/installer.js',
4662
+ "build:all": "npm run build",
4474
4663
  "publish:wrapper": "cd wrapper && npm publish"
4475
4664
  },
4476
4665
  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.7.1",
4
4
  "description": "One-shot installer for The Machine toolchain",
5
5
  "bin": {
6
6
  "dlw-machine-setup": "bin/installer.js"