@supa-magic/spm 0.3.0 → 0.3.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/dist/bin/spm.js +38 -8
  2. package/package.json +1 -1
package/dist/bin/spm.js CHANGED
@@ -79,6 +79,25 @@ const writeConfig = (config, root) => {
79
79
  const configPath = getConfigPath(root);
80
80
  writeFileSync(configPath, stringify(config), "utf-8");
81
81
  };
82
+ const addConfigEntry = ({
83
+ providerPath,
84
+ kind,
85
+ name,
86
+ source
87
+ }) => {
88
+ const { config } = readConfig();
89
+ const provider = Object.values(config.providers).find(
90
+ (p) => p.path === providerPath
91
+ );
92
+ if (!provider) {
93
+ throw new Error(`Provider with path "${providerPath}" not found in config`);
94
+ }
95
+ if (!provider[kind]) {
96
+ provider[kind] = {};
97
+ }
98
+ provider[kind][name] = source;
99
+ writeConfig(config);
100
+ };
82
101
  const green = "\x1B[32m";
83
102
  const red = "\x1B[31m";
84
103
  const cyan = "\x1B[36m";
@@ -370,8 +389,8 @@ const downloadFile = async (source) => {
370
389
  const content = await getContent(source);
371
390
  return { content, source };
372
391
  };
373
- const template = '# Skillset Integration\n\nYou are integrating a new skillset into the project. Analyze the existing project setup and intelligently integrate the new files.\n\n## Context\n\n- **Downloaded files**: `{{downloadDir}}`\n- **Provider directory**: `{{providerDir}}`\n- **Skillset**: `{{skillsetName}}` v`{{skillsetVersion}}`\n- **Source**: `{{source}}`\n- **Config file**: `{{configPath}}`\n\nIdentical files have already been removed from the download folder. Only files that need action remain. If the download folder is empty, skip to the config update step.\n\n## Output Format\n\nCRITICAL — follow exactly:\n- NEVER wrap output in code blocks or backticks\n- NEVER write conversational text ("Now let me...", "Let me check...", "The files are...")\n- NEVER use emojis\n- Output ONLY structured log lines\n- Step headers are plain text ending with `...` — these are parsed by the CLI to drive spinner states\n- Items below headers are indented with 2 spaces and use `• ` prefix\n- File lists use ASCII tree: `├─`, `└─`, `│`\n- End with `Done` on its own line\n\nStep headers (use these exactly):\n\n1. `Analyzing existing setup...`\n2. `Analyzing downloaded files...`\n3. `Detecting conflicts...`\n4. `Integrating...`\n5. `Updating config...`\n6. `Running setup...` (only if a setup file exists)\n7. `Done`\n\nExample output:\n\nAnalyzing existing setup...\n • 4 skills, 2 rules, 1 hook\n\nAnalyzing downloaded files...\n • skills: git, github, implement\n • rules: coding\n • hook: biome-format\n\nDetecting conflicts...\n • No conflicts\n\nIntegrating...\n • skills/git/SKILL.md\n • skills/git/branch.md\n • skills/github/SKILL.md\n\nUpdating config...\n • skillset: skill-creator@1.0.0\n\nRunning setup...\n • Configured MCP server: biome\n\nDone\n\n## Step 1: Analyzing existing setup\n\nRead the provider directory (`{{providerDir}}`) and catalog what is already in place: skills, rules, agents, hooks, mcp servers files.\n\n## Step 2: Analyzing downloaded files\n\nRead all files in `{{downloadDir}}`. Only files present in this folder need to be installed.\n\n## Step 3: Detecting conflicts\n\nFor each downloaded file, check if a file with the same name exists in the provider directory.\n\n- **No existing file** → install as new\n- **Different skillset version** → replace silently (new version supersedes)\n- **Same version or no version info** → conflict — ask the user:\n\nDetecting conflicts...\n • rules/coding.md — local has custom rules\n • Choose: (r)eplace / (s)kip / (m)erge\n\nWait for response before proceeding.\n\n## Step 4: Integrating\n\nInstall files into `{{providerDir}}`, following the directory structure from the download folder.\n\n**Integrate, don\'t copy.** When a new skill can leverage existing project conventions, adapt it:\n\n- If the project has `rules/coding.md` with specific conventions → make new skills follow those rules instead of their own defaults\n- If existing skills handle branching or committing → reference them instead of duplicating instructions\n- If the project has naming conventions, testing patterns, or architectural rules → align new skills with them\n\n## Step 5: Updating config\n\nUpdate `{{configPath}}`:\n\n- Under the provider with path `{{providerDir}}`, add the skillset entry under `skillsets`:\n `{{skillsetName}}: "{{source}}@{{skillsetVersion}}"`\n- Do NOT add individual skill, agent, or file names to the config\n- The `skills` map is reserved for standalone skill installations\n\n{{setupSection}}\n\n## Rules\n\n- Setup files are NOT installed — they contain instructions to configure the project\n- Do not delete or modify existing files in the provider directory unless resolving a conflict\n- Do NOT delete the download folder — cleanup is handled externally\n';
374
- const skillTemplate = '# Skill Integration\n\nYou are integrating a single skill into the project. Analyze the existing project setup and intelligently integrate the new files.\n\n## Context\n\n- **Downloaded files**: `{{downloadDir}}`\n- **Provider directory**: `{{providerDir}}`\n- **Skill**: `{{skillName}}`\n- **Source**: `{{source}}`\n- **Config file**: `{{configPath}}`\n\nIdentical files have already been removed from the download folder. Only files that need action remain. If the download folder is empty, skip to the config update step.\n\n## Output Format\n\nCRITICAL — follow exactly:\n- NEVER wrap output in code blocks or backticks\n- NEVER write conversational text ("Now let me...", "Let me check...", "The files are...")\n- NEVER use emojis\n- Output ONLY structured log lines\n- Step headers are plain text ending with `...` — these are parsed by the CLI to drive spinner states\n- Items below headers are indented with 2 spaces and use `• ` prefix\n- File lists use ASCII tree: `├─`, `└─`, `│`\n- End with `Done` on its own line\n\nStep headers (use these exactly):\n\n1. `Analyzing existing setup...`\n2. `Analyzing downloaded files...`\n3. `Detecting conflicts...`\n4. `Integrating...`\n5. `Updating config...`\n6. `Done`\n\nExample output:\n\nAnalyzing existing setup...\n • 4 skills, 2 rules, 1 hook\n\nAnalyzing downloaded files...\n • skill: git (3 files)\n\nDetecting conflicts...\n • No conflicts\n\nIntegrating...\n • skills/git/SKILL.md\n • skills/git/branch.md\n\nUpdating config...\n • skill: git\n\nDone\n\n## Step 1: Analyzing existing setup\n\nRead the provider directory (`{{providerDir}}`) and catalog what is already in place: skills, rules, agents, hooks, mcp servers files.\n\n## Step 2: Analyzing downloaded files\n\nRead all files in `{{downloadDir}}`. Only files present in this folder need to be installed.\n\n## Step 3: Detecting conflicts\n\nFor each downloaded file, check if a file with the same name exists in the provider directory.\n\n- **No existing file** → install as new\n- **Different content** → replace silently\n- **Same content** → skip (should already be pruned)\n\nDetecting conflicts...\n • rules/coding.md — local has custom rules\n • Choose: (r)eplace / (s)kip / (m)erge\n\nWait for response before proceeding.\n\n## Step 4: Integrating\n\nInstall files into `{{providerDir}}/skills/{{skillName}}/`, following the directory structure from the download folder.\n\n**Integrate, don\'t copy.** When a new skill can leverage existing project conventions, adapt it:\n\n- If the project has `rules/coding.md` with specific conventions → make new skills follow those rules instead of their own defaults\n- If existing skills handle branching or committing → reference them instead of duplicating instructions\n- If the project has naming conventions, testing patterns, or architectural rules → align new skills with them\n\n## Step 5: Updating config\n\nUpdate `{{configPath}}`:\n\n- Under the provider with path `{{providerDir}}`, add the skill entry under `skills`:\n `{{skillName}}: "{{source}}"`\n- The `skillsets` map is reserved for skillset installations\n\n## Rules\n\n- Do not delete or modify existing files in the provider directory unless resolving a conflict\n- Do NOT delete the download folder — cleanup is handled externally\n';
392
+ const template = '# Skillset Integration\n\nYou are integrating a new skillset into the project. Analyze the existing project setup and intelligently integrate the new files.\n\n## Context\n\n- **Downloaded files**: `{{downloadDir}}`\n- **Provider directory**: `{{providerDir}}`\n- **Skillset**: `{{skillsetName}}` v`{{skillsetVersion}}`\n\nIdentical files have already been removed from the download folder. Only files that need action remain. If the download folder is empty, output `Done` immediately.\n\n## Output Format\n\nCRITICAL — follow exactly:\n- NEVER wrap output in code blocks or backticks\n- NEVER write conversational text ("Now let me...", "Let me check...", "The files are...")\n- NEVER use emojis\n- Output ONLY structured log lines\n- Step headers are plain text ending with `...` — these are parsed by the CLI to drive spinner states\n- Items below headers are indented with 2 spaces and use `• ` prefix\n- File lists use ASCII tree: `├─`, `└─`, `│`\n- End with `Done` on its own line\n\nStep headers (use these exactly):\n\n1. `Analyzing existing setup...`\n2. `Analyzing downloaded files...`\n3. `Detecting conflicts...`\n4. `Integrating...`\n5. `Running setup...` (only if a setup file exists)\n6. `Done`\n\nExample output:\n\nAnalyzing existing setup...\n • 4 skills, 2 rules, 1 hook\n\nAnalyzing downloaded files...\n • skills: git, github, implement\n • rules: coding\n • hook: biome-format\n\nDetecting conflicts...\n • No conflicts\n\nIntegrating...\n • skills/git/SKILL.md\n • skills/git/branch.md\n • skills/github/SKILL.md\n\nRunning setup...\n • Configured MCP server: biome\n\nDone\n\n## Step 1: Analyzing existing setup\n\nRead the provider directory (`{{providerDir}}`) and catalog what is already in place: skills, rules, agents, hooks, mcp servers files.\n\n## Step 2: Analyzing downloaded files\n\nRead all files in `{{downloadDir}}`. Only files present in this folder need to be installed.\n\n## Step 3: Detecting conflicts\n\nFor each downloaded file, check if a file with the same name exists in the provider directory.\n\n- **No existing file** → install as new\n- **Different skillset version** → replace silently (new version supersedes)\n- **Same version or no version info** → conflict — ask the user:\n\nDetecting conflicts...\n • rules/coding.md — local has custom rules\n • Choose: (r)eplace / (s)kip / (m)erge\n\nWait for response before proceeding.\n\n## Step 4: Integrating\n\nInstall files into `{{providerDir}}`, following the directory structure from the download folder.\n\n**Integrate, don\'t copy.** When a new skill can leverage existing project conventions, adapt it:\n\n- If the project has `rules/coding.md` with specific conventions → make new skills follow those rules instead of their own defaults\n- If existing skills handle branching or committing → reference them instead of duplicating instructions\n- If the project has naming conventions, testing patterns, or architectural rules → align new skills with them\n\n{{setupSection}}\n\n## Rules\n\n- Setup files are NOT installed — they contain instructions to configure the project\n- Do not delete or modify existing files in the provider directory unless resolving a conflict\n- Do NOT delete the download folder — cleanup is handled externally\n';
393
+ const skillTemplate = '# Skill Integration\n\nYou are integrating a single skill into the project. Analyze the existing project setup and intelligently integrate the new files.\n\n## Context\n\n- **Downloaded files**: `{{downloadDir}}`\n- **Provider directory**: `{{providerDir}}`\n- **Skill**: `{{skillName}}`\n\nIdentical files have already been removed from the download folder. Only files that need action remain. If the download folder is empty, output `Done` immediately.\n\n## Output Format\n\nCRITICAL — follow exactly:\n- NEVER wrap output in code blocks or backticks\n- NEVER write conversational text ("Now let me...", "Let me check...", "The files are...")\n- NEVER use emojis\n- Output ONLY structured log lines\n- Step headers are plain text ending with `...` — these are parsed by the CLI to drive spinner states\n- Items below headers are indented with 2 spaces and use `• ` prefix\n- File lists use ASCII tree: `├─`, `└─`, `│`\n- End with `Done` on its own line\n\nStep headers (use these exactly):\n\n1. `Analyzing existing setup...`\n2. `Analyzing downloaded files...`\n3. `Detecting conflicts...`\n4. `Integrating...`\n5. `Done`\n\nExample output:\n\nAnalyzing existing setup...\n • 4 skills, 2 rules, 1 hook\n\nAnalyzing downloaded files...\n • skill: git (3 files)\n\nDetecting conflicts...\n • No conflicts\n\nIntegrating...\n • skills/git/SKILL.md\n • skills/git/branch.md\n\nDone\n\n## Step 1: Analyzing existing setup\n\nRead the provider directory (`{{providerDir}}`) and catalog what is already in place: skills, rules, agents, hooks, mcp servers files.\n\n## Step 2: Analyzing downloaded files\n\nRead all files in `{{downloadDir}}`. Only files present in this folder need to be installed.\n\n## Step 3: Detecting conflicts\n\nFor each downloaded file, check if a file with the same name exists in the provider directory.\n\n- **No existing file** → install as new\n- **Different content** → replace silently\n- **Same content** → skip (should already be pruned)\n\nDetecting conflicts...\n • rules/coding.md — local has custom rules\n • Choose: (r)eplace / (s)kip / (m)erge\n\nWait for response before proceeding.\n\n## Step 4: Integrating\n\nInstall files into `{{providerDir}}/skills/{{skillName}}/`, following the directory structure from the download folder.\n\n**Integrate, don\'t copy.** When a new skill can leverage existing project conventions, adapt it:\n\n- If the project has `rules/coding.md` with specific conventions → make new skills follow those rules instead of their own defaults\n- If existing skills handle branching or committing → reference them instead of duplicating instructions\n- If the project has naming conventions, testing patterns, or architectural rules → align new skills with them\n\n## Rules\n\n- Do not delete or modify existing files in the provider directory unless resolving a conflict\n- Do NOT delete the download folder — cleanup is handled externally\n';
375
394
  const buildSetupSection = (setupFile) => setupFile ? [
376
395
  "## Step 6: Running setup",
377
396
  "",
@@ -379,14 +398,14 @@ const buildSetupSection = (setupFile) => setupFile ? [
379
398
  "Setup files configure the project environment (e.g. MCP servers, LSP, tooling).",
380
399
  "Do NOT copy the setup file into the provider directory — only execute its instructions."
381
400
  ].join("\n") : "";
382
- const buildInstructions = (input) => template.replace(/\{\{downloadDir\}\}/g, input.downloadDir).replace(/\{\{providerDir\}\}/g, input.providerDir).replace(/\{\{skillsetName\}\}/g, input.skillsetName).replace(/\{\{skillsetVersion\}\}/g, input.skillsetVersion).replace(/\{\{source\}\}/g, input.source).replace(/\{\{configPath\}\}/g, input.configPath).replace("{{setupSection}}", buildSetupSection(input.setupFile));
401
+ const buildInstructions = (input) => template.replace(/\{\{downloadDir\}\}/g, input.downloadDir).replace(/\{\{providerDir\}\}/g, input.providerDir).replace(/\{\{skillsetName\}\}/g, input.skillsetName).replace(/\{\{skillsetVersion\}\}/g, input.skillsetVersion).replace("{{setupSection}}", buildSetupSection(input.setupFile));
383
402
  const writeInstructionsFile = (input) => {
384
403
  const filePath = join(tmpdir(), "spm", `install-${input.skillsetName}.md`);
385
404
  mkdirSync(dirname(filePath), { recursive: true });
386
405
  writeFileSync(filePath, buildInstructions(input), "utf-8");
387
406
  return filePath;
388
407
  };
389
- const buildSkillInstructions = (input) => skillTemplate.replace(/\{\{downloadDir\}\}/g, input.downloadDir).replace(/\{\{providerDir\}\}/g, input.providerDir).replace(/\{\{skillName\}\}/g, input.skillName).replace(/\{\{source\}\}/g, input.source).replace(/\{\{configPath\}\}/g, input.configPath);
408
+ const buildSkillInstructions = (input) => skillTemplate.replace(/\{\{downloadDir\}\}/g, input.downloadDir).replace(/\{\{providerDir\}\}/g, input.providerDir).replace(/\{\{skillName\}\}/g, input.skillName);
390
409
  const writeSkillInstructionsFile = (input) => {
391
410
  const filePath = join(tmpdir(), "spm", `install-skill-${input.skillName}.md`);
392
411
  mkdirSync(dirname(filePath), { recursive: true });
@@ -1027,6 +1046,7 @@ const installSkillsetFlow = async (input, stepper, startedAt) => {
1027
1046
  stepper.succeed(`Skipped ${pruned} unchanged file(s)`);
1028
1047
  }
1029
1048
  const model = skillset.provider === "claude" ? "haiku" : void 0;
1049
+ const source = `https://github.com/${location.owner}/${location.repository}/blob/${location.ref}/${location.path}`;
1030
1050
  const result = await installSkillset(
1031
1051
  {
1032
1052
  downloadDir,
@@ -1034,12 +1054,17 @@ const installSkillsetFlow = async (input, stepper, startedAt) => {
1034
1054
  providerDir: providerFullPath,
1035
1055
  skillsetName: skillset.name,
1036
1056
  skillsetVersion: skillset.version,
1037
- source: `@${location.owner}/${location.repository}`,
1038
1057
  configPath: getConfigPath(),
1039
1058
  model
1040
1059
  },
1041
1060
  stepper
1042
1061
  );
1062
+ addConfigEntry({
1063
+ providerPath: provider.path,
1064
+ kind: "skillsets",
1065
+ name: skillset.name,
1066
+ source
1067
+ });
1043
1068
  await rm(downloadDir, { recursive: true, force: true });
1044
1069
  const spmDir = join(projectRoot, ".spm");
1045
1070
  const remaining = await readdir(spmDir).catch(() => []);
@@ -1089,18 +1114,23 @@ const installSkillFlow = async (identifier, stepper, startedAt) => {
1089
1114
  stepper.succeed(`Skipped ${pruned} unchanged file(s)`);
1090
1115
  }
1091
1116
  const model = providerName === "claude" ? "haiku" : void 0;
1092
- const source = `@${resolved.location.owner}/${resolved.location.repository}`;
1117
+ const source = `https://github.com/${resolved.location.owner}/${resolved.location.repository}/blob/${resolved.location.ref}/${resolved.location.path}`;
1093
1118
  const result = await installSingleSkill(
1094
1119
  {
1095
1120
  downloadDir,
1096
1121
  providerDir: providerFullPath,
1097
1122
  skillName: resolved.name,
1098
- source,
1099
1123
  configPath: getConfigPath(),
1100
1124
  model
1101
1125
  },
1102
1126
  stepper
1103
1127
  );
1128
+ addConfigEntry({
1129
+ providerPath,
1130
+ kind: "skills",
1131
+ name: resolved.name,
1132
+ source
1133
+ });
1104
1134
  await rm(downloadDir, { recursive: true, force: true });
1105
1135
  const spmDir = join(projectRoot, ".spm");
1106
1136
  const remaining = await readdir(spmDir).catch(() => []);
@@ -1153,7 +1183,7 @@ const banner = (version2) => [
1153
1183
  `${shade}▝▜${light}████▛▘ ${reset$1}${dim}v${version2} ✨ supa-magic${reset$1}`,
1154
1184
  `${shade} ▘${light} ▝`
1155
1185
  ].join("\n");
1156
- const version = "0.3.0";
1186
+ const version = "0.3.1";
1157
1187
  const program = new Command();
1158
1188
  const gray = "\x1B[90m";
1159
1189
  const reset = "\x1B[0m";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supa-magic/spm",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "CLI tool for managing AI skillsets",
5
5
  "license": "MIT",
6
6
  "contributors": [