skillhub 0.1.16 → 0.2.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.
@@ -12,18 +12,29 @@ var PLATFORM_PATHS = {
12
12
  project: ".codex/skills"
13
13
  },
14
14
  copilot: {
15
- user: ".github/skills",
16
- project: ".github/skills"
15
+ user: ".github/instructions",
16
+ project: ".github/instructions"
17
17
  },
18
18
  cursor: {
19
- user: ".cursor/skills",
20
- project: ".cursor/skills"
19
+ user: ".cursor/rules",
20
+ project: ".cursor/rules"
21
21
  },
22
22
  windsurf: {
23
- user: ".windsurf/skills",
24
- project: ".windsurf/skills"
23
+ user: ".windsurf/rules",
24
+ project: ".windsurf/rules"
25
25
  }
26
26
  };
27
+ var FLAT_FILE_PLATFORMS = ["cursor", "windsurf", "copilot"];
28
+ function isFlatFilePlatform(platform) {
29
+ return FLAT_FILE_PLATFORMS.includes(platform);
30
+ }
31
+ function getPlatformFilePath(platform, skillName, fileName, project = false) {
32
+ const basePath = getSkillsPath(platform, project);
33
+ if (isFlatFilePlatform(platform)) {
34
+ return path.join(basePath, fileName);
35
+ }
36
+ return path.join(basePath, skillName, fileName);
37
+ }
27
38
  function getSkillsPath(platform, project = false) {
28
39
  const home = os.homedir();
29
40
  const cwd = process.cwd();
@@ -77,6 +88,8 @@ async function saveConfig(config) {
77
88
  }
78
89
 
79
90
  export {
91
+ isFlatFilePlatform,
92
+ getPlatformFilePath,
80
93
  getSkillsPath,
81
94
  getSkillPath,
82
95
  ensureSkillsDir,
package/dist/index.js CHANGED
@@ -2,12 +2,14 @@
2
2
  import {
3
3
  ensureSkillsDir,
4
4
  getConfigPath,
5
+ getPlatformFilePath,
5
6
  getSkillPath,
6
7
  getSkillsPath,
8
+ isFlatFilePlatform,
7
9
  isSkillInstalled,
8
10
  loadConfig,
9
11
  saveConfig
10
- } from "./chunk-YXEKBJKF.js";
12
+ } from "./chunk-SLCBZBS3.js";
11
13
 
12
14
  // src/index.ts
13
15
  import { Command } from "commander";
@@ -311,6 +313,108 @@ async function getDefaultBranch(owner, repo) {
311
313
  return response.data.default_branch;
312
314
  }
313
315
 
316
+ // src/utils/transform.ts
317
+ var WINDSURF_CHAR_LIMIT = 6e3;
318
+ var PLATFORM_FILE_CONFIGS = {
319
+ claude: {
320
+ getFileName: () => "SKILL.md",
321
+ keepOriginal: false,
322
+ transform: (raw) => ({ content: raw, warnings: [] })
323
+ },
324
+ codex: {
325
+ getFileName: () => "SKILL.md",
326
+ keepOriginal: false,
327
+ transform: (raw) => ({ content: raw, warnings: [] })
328
+ },
329
+ cursor: {
330
+ getFileName: (skillName) => `${skillName}.mdc`,
331
+ keepOriginal: true,
332
+ transform: transformForCursor
333
+ },
334
+ windsurf: {
335
+ getFileName: (skillName) => `${skillName}.md`,
336
+ keepOriginal: true,
337
+ transform: transformForWindsurf
338
+ },
339
+ copilot: {
340
+ getFileName: (skillName) => `${skillName}.instructions.md`,
341
+ keepOriginal: true,
342
+ transform: transformForCopilot
343
+ }
344
+ };
345
+ function getPlatformFileName(platform, skillName) {
346
+ return PLATFORM_FILE_CONFIGS[platform].getFileName(skillName);
347
+ }
348
+ function transformForPlatform(platform, rawSkillMd, parsed) {
349
+ return PLATFORM_FILE_CONFIGS[platform].transform(rawSkillMd, parsed);
350
+ }
351
+ function shouldKeepOriginal(platform) {
352
+ return PLATFORM_FILE_CONFIGS[platform].keepOriginal;
353
+ }
354
+ function transformForCursor(_raw, parsed) {
355
+ const warnings = [];
356
+ const mdcFields = [];
357
+ if (parsed.metadata.description) {
358
+ mdcFields.push(`description: ${parsed.metadata.description}`);
359
+ }
360
+ const filePatterns = parsed.metadata.triggers?.filePatterns;
361
+ if (filePatterns && filePatterns.length > 0) {
362
+ mdcFields.push(`globs: ${filePatterns.join(", ")}`);
363
+ mdcFields.push("alwaysApply: false");
364
+ } else {
365
+ mdcFields.push("alwaysApply: true");
366
+ }
367
+ const body = parsed.content.trim();
368
+ const mdcContent = `---
369
+ ${mdcFields.join("\n")}
370
+ ---
371
+ ${body}
372
+ `;
373
+ return { content: mdcContent, warnings };
374
+ }
375
+ function transformForWindsurf(_raw, parsed) {
376
+ const warnings = [];
377
+ let body = parsed.content.trim();
378
+ if (!body.startsWith("# ")) {
379
+ body = `# ${parsed.metadata.name}
380
+
381
+ ${body}`;
382
+ }
383
+ if (body.length > WINDSURF_CHAR_LIMIT) {
384
+ warnings.push(
385
+ `Content exceeds Windsurf's ${WINDSURF_CHAR_LIMIT} character limit (${body.length} chars). Truncating.`
386
+ );
387
+ body = truncateAtSectionBoundary(body, WINDSURF_CHAR_LIMIT);
388
+ }
389
+ return { content: body + "\n", warnings };
390
+ }
391
+ function transformForCopilot(_raw, parsed) {
392
+ let body = parsed.content.trim();
393
+ if (!body.startsWith("# ")) {
394
+ body = `# ${parsed.metadata.name}
395
+
396
+ ${body}`;
397
+ }
398
+ return { content: body + "\n", warnings: [] };
399
+ }
400
+ function truncateAtSectionBoundary(content, limit) {
401
+ const notice = "\n\n<!-- Truncated by SkillHub: see SKILL.md for full content -->\n";
402
+ const maxLen = limit - notice.length;
403
+ if (content.length <= maxLen) return content;
404
+ const truncated = content.slice(0, maxLen);
405
+ const lastHeading = truncated.lastIndexOf("\n## ");
406
+ const lastH1 = truncated.lastIndexOf("\n# ");
407
+ const cutPoint = Math.max(lastHeading, lastH1);
408
+ if (cutPoint > 0) {
409
+ return truncated.slice(0, cutPoint) + notice;
410
+ }
411
+ const lastParagraph = truncated.lastIndexOf("\n\n");
412
+ if (lastParagraph > 0) {
413
+ return truncated.slice(0, lastParagraph) + notice;
414
+ }
415
+ return truncated + notice;
416
+ }
417
+
314
418
  // src/commands/install.ts
315
419
  async function install(skillId, options) {
316
420
  const spinner = ora("Parsing skill ID...").start();
@@ -444,13 +548,34 @@ A different skill is already installed with the name "${actualName}":`));
444
548
  }
445
549
  spinner.text = "Installing skill...";
446
550
  await fs.ensureDir(installPath);
447
- await fs.writeFile(path.join(installPath, "SKILL.md"), content.skillMd);
551
+ const platformFileName = getPlatformFileName(options.platform, actualName);
552
+ const { content: transformedContent, warnings: transformWarnings } = transformForPlatform(options.platform, content.skillMd, parsed);
553
+ for (const warning of transformWarnings) {
554
+ console.log(chalk.yellow(` Warning: ${warning}`));
555
+ }
556
+ if (isFlatFilePlatform(options.platform)) {
557
+ const platformFilePath2 = getPlatformFilePath(
558
+ options.platform,
559
+ actualName,
560
+ platformFileName,
561
+ options.project
562
+ );
563
+ await fs.writeFile(platformFilePath2, transformedContent);
564
+ } else {
565
+ await fs.writeFile(path.join(installPath, platformFileName), transformedContent);
566
+ }
567
+ if (shouldKeepOriginal(options.platform)) {
568
+ await fs.writeFile(path.join(installPath, "SKILL.md"), content.skillMd);
569
+ }
448
570
  const canonicalId = skillInfo?.id || skillId;
571
+ const platformFilePath = isFlatFilePlatform(options.platform) ? getPlatformFilePath(options.platform, actualName, platformFileName, options.project) : null;
449
572
  await fs.writeJson(path.join(installPath, ".skillhub.json"), {
450
573
  skillId: canonicalId,
451
574
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
452
575
  platform: options.platform,
453
- version: parsed.metadata.version || null
576
+ version: parsed.metadata.version || null,
577
+ platformFileName,
578
+ platformFilePath
454
579
  });
455
580
  if (content.scripts.length > 0) {
456
581
  const scriptsDir = path.join(installPath, "scripts");
@@ -511,19 +636,16 @@ function getPlatformName(platform) {
511
636
  function getPlatformSetupInstructions(platform, installPath) {
512
637
  switch (platform) {
513
638
  case "claude":
514
- return chalk.dim(" Skills in ~/.claude/skills/ are automatically discovered by Claude Code.");
639
+ return chalk.dim(" Skills in .claude/skills/ are automatically discovered by Claude Code.");
515
640
  case "codex":
516
641
  return chalk.dim(` Reference this skill in your AGENTS.md:
517
642
  @import ${installPath}/SKILL.md`);
518
643
  case "copilot":
519
- return chalk.dim(` Reference this skill in .github/copilot-instructions.md:
520
- @import ${installPath}/SKILL.md`);
644
+ return chalk.dim(" Instructions in .github/instructions/ are automatically loaded by GitHub Copilot.");
521
645
  case "cursor":
522
- return chalk.dim(` Reference this skill in your .cursorrules file:
523
- @import ${installPath}/SKILL.md`);
646
+ return chalk.dim(" Rules in .cursor/rules/ are automatically loaded by Cursor.");
524
647
  case "windsurf":
525
- return chalk.dim(` Reference this skill in your Windsurf configuration:
526
- Path: ${installPath}/SKILL.md`);
648
+ return chalk.dim(" Rules in .windsurf/rules/ are automatically loaded by Windsurf.");
527
649
  default:
528
650
  return null;
529
651
  }
@@ -737,18 +859,31 @@ async function getInstalledSkills(platform, project = false) {
737
859
  }
738
860
  const skillPath = path2.join(skillsPath, entry.name);
739
861
  const skillMdPath = path2.join(skillPath, "SKILL.md");
740
- if (!await fs2.pathExists(skillMdPath)) {
862
+ const metadataPath = path2.join(skillPath, ".skillhub.json");
863
+ const hasSkillMd = await fs2.pathExists(skillMdPath);
864
+ const hasMetadata = await fs2.pathExists(metadataPath);
865
+ if (!hasSkillMd && !hasMetadata) {
741
866
  continue;
742
867
  }
743
868
  try {
744
- const content = await fs2.readFile(skillMdPath, "utf-8");
745
- const parsed = parseSkillMd2(content);
746
- skills.push({
747
- name: parsed.metadata.name || entry.name,
748
- description: parsed.metadata.description,
749
- version: parsed.metadata.version,
750
- path: skillPath
751
- });
869
+ if (hasSkillMd) {
870
+ const content = await fs2.readFile(skillMdPath, "utf-8");
871
+ const parsed = parseSkillMd2(content);
872
+ skills.push({
873
+ name: parsed.metadata.name || entry.name,
874
+ description: parsed.metadata.description,
875
+ version: parsed.metadata.version,
876
+ path: skillPath
877
+ });
878
+ } else if (hasMetadata) {
879
+ const metadata = await fs2.readJson(metadataPath);
880
+ skills.push({
881
+ name: entry.name,
882
+ description: void 0,
883
+ version: metadata.version || void 0,
884
+ path: skillPath
885
+ });
886
+ }
752
887
  } catch {
753
888
  skills.push({
754
889
  name: entry.name,
@@ -849,7 +984,8 @@ program.command("config").description("Manage CLI configuration").option("--set
849
984
  });
850
985
  program.command("uninstall <skill-name>").description("Uninstall a skill").option("-p, --platform <platform>", "Target platform").option("--project", "Uninstall from project instead of globally").action(async (skillName, options) => {
851
986
  const fs3 = await import("fs-extra");
852
- const { getSkillPath: getSkillPath2, isSkillInstalled: isSkillInstalled2 } = await import("./paths-BVI5WSHE.js");
987
+ const pathModule = await import("path");
988
+ const { getSkillPath: getSkillPath2, isSkillInstalled: isSkillInstalled2 } = await import("./paths-PDIRF66F.js");
853
989
  const userConfig = await loadConfig();
854
990
  const platform = options.platform || userConfig.defaultPlatform || "claude";
855
991
  const installed = await isSkillInstalled2(platform, skillName, options.project);
@@ -858,13 +994,23 @@ program.command("uninstall <skill-name>").description("Uninstall a skill").optio
858
994
  process.exit(1);
859
995
  }
860
996
  const skillPath = getSkillPath2(platform, skillName, options.project);
997
+ const metadataPath = pathModule.join(skillPath, ".skillhub.json");
998
+ if (await fs3.default.pathExists(metadataPath)) {
999
+ try {
1000
+ const metadata = await fs3.default.readJson(metadataPath);
1001
+ if (metadata.platformFilePath) {
1002
+ await fs3.default.remove(metadata.platformFilePath);
1003
+ }
1004
+ } catch {
1005
+ }
1006
+ }
861
1007
  await fs3.default.remove(skillPath);
862
1008
  console.log(chalk5.green(`Skill ${skillName} uninstalled successfully.`));
863
1009
  });
864
1010
  program.command("update [skill-name]").description("Update installed skills").option("-p, --platform <platform>", "Target platform").option("--all", "Update all installed skills").action(async (skillName, options) => {
865
1011
  const fsExtra = await import("fs-extra");
866
1012
  const pathModule = await import("path");
867
- const { getSkillsPath: getSkillsPath2, getSkillPath: getSkillPath2 } = await import("./paths-BVI5WSHE.js");
1013
+ const { getSkillsPath: getSkillsPath2, getSkillPath: getSkillPath2 } = await import("./paths-PDIRF66F.js");
868
1014
  const userConfig = await loadConfig();
869
1015
  const platform = options.platform || userConfig.defaultPlatform || "claude";
870
1016
  const ALL_PLATFORMS2 = ["claude", "codex", "copilot", "cursor", "windsurf"];
@@ -2,18 +2,22 @@ import {
2
2
  detectPlatform,
3
3
  ensureSkillsDir,
4
4
  getConfigPath,
5
+ getPlatformFilePath,
5
6
  getSkillPath,
6
7
  getSkillsPath,
8
+ isFlatFilePlatform,
7
9
  isSkillInstalled,
8
10
  loadConfig,
9
11
  saveConfig
10
- } from "./chunk-YXEKBJKF.js";
12
+ } from "./chunk-SLCBZBS3.js";
11
13
  export {
12
14
  detectPlatform,
13
15
  ensureSkillsDir,
14
16
  getConfigPath,
17
+ getPlatformFilePath,
15
18
  getSkillPath,
16
19
  getSkillsPath,
20
+ isFlatFilePlatform,
17
21
  isSkillInstalled,
18
22
  loadConfig,
19
23
  saveConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillhub",
3
- "version": "0.1.16",
3
+ "version": "0.2.0",
4
4
  "description": "CLI tool for managing AI Agent skills - search, install, and update skills for Claude, Codex, Copilot, and more",
5
5
  "author": "SkillHub Contributors",
6
6
  "license": "MIT",