skillhub 0.1.7 → 0.1.9

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/index.js +103 -26
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -135,6 +135,7 @@ async function trackInstall(skillId, platform, method = "cli") {
135
135
 
136
136
  // src/utils/github.ts
137
137
  import { Octokit } from "@octokit/rest";
138
+ import https2 from "https";
138
139
  var octokit = null;
139
140
  function getOctokit() {
140
141
  if (!octokit) {
@@ -149,25 +150,73 @@ function getOctokit() {
149
150
  }
150
151
  return octokit;
151
152
  }
153
+ async function fetchRawFile(owner, repo, path3, branch) {
154
+ return new Promise((resolve, reject) => {
155
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path3}`;
156
+ https2.get(url, { timeout: 1e4 }, (res) => {
157
+ if (res.statusCode === 404) {
158
+ reject(new Error("File not found"));
159
+ return;
160
+ }
161
+ if (res.statusCode !== 200) {
162
+ reject(new Error(`HTTP ${res.statusCode}`));
163
+ return;
164
+ }
165
+ let data = "";
166
+ res.on("data", (chunk) => data += chunk);
167
+ res.on("end", () => resolve(data));
168
+ }).on("error", (err) => {
169
+ reject(err);
170
+ }).on("timeout", () => {
171
+ reject(new Error("Request timeout"));
172
+ });
173
+ });
174
+ }
152
175
  async function fetchSkillContent(owner, repo, skillPath, branch = "main") {
153
176
  const client = getOctokit();
154
177
  const skillMdPath = skillPath ? `${skillPath}/SKILL.md` : "SKILL.md";
155
178
  let skillMdResponse;
156
- try {
157
- skillMdResponse = await client.repos.getContent({
158
- owner,
159
- repo,
160
- path: skillMdPath,
161
- ref: branch
162
- });
163
- } catch (error) {
164
- if (error.status === 404) {
165
- throw new Error(`SKILL.md not found at ${owner}/${repo}/${skillMdPath}`);
166
- }
167
- if (error.message?.includes("timeout")) {
168
- throw new Error(`GitHub request timeout. Check your internet connection or try again later.`);
179
+ let lastError;
180
+ const pathsToTry = [
181
+ skillMdPath,
182
+ // Common skill directories
183
+ ...skillPath && !skillPath.startsWith("skills/") ? [`skills/${skillPath}/SKILL.md`] : [],
184
+ ...skillPath && !skillPath.startsWith(".claude/") ? [`.claude/skills/${skillPath}/SKILL.md`] : [],
185
+ ...skillPath && !skillPath.startsWith(".github/") ? [`.github/skills/${skillPath}/SKILL.md`] : []
186
+ ];
187
+ for (const pathToTry of pathsToTry) {
188
+ try {
189
+ skillMdResponse = await client.repos.getContent({
190
+ owner,
191
+ repo,
192
+ path: pathToTry,
193
+ ref: branch
194
+ });
195
+ break;
196
+ } catch (error) {
197
+ lastError = error;
198
+ if (error.message?.includes("timeout") || error.message?.includes("network")) {
199
+ try {
200
+ const rawContent = await fetchRawFile(owner, repo, pathToTry, branch);
201
+ skillMdResponse = {
202
+ data: {
203
+ content: Buffer.from(rawContent).toString("base64"),
204
+ encoding: "base64"
205
+ }
206
+ };
207
+ break;
208
+ } catch (rawError) {
209
+ throw new Error(`GitHub API timeout. Try using --no-api flag or check your network connection.`);
210
+ }
211
+ }
212
+ if (error.status === 404) {
213
+ continue;
214
+ }
215
+ throw new Error(`Failed to fetch from GitHub: ${error.message}`);
169
216
  }
170
- throw new Error(`Failed to fetch from GitHub: ${error.message}`);
217
+ }
218
+ if (!skillMdResponse) {
219
+ throw new Error(`SKILL.md not found at ${owner}/${repo} (tried ${pathsToTry.length} paths)`);
171
220
  }
172
221
  if (!("content" in skillMdResponse.data)) {
173
222
  throw new Error("SKILL.md not found");
@@ -322,6 +371,28 @@ async function install(skillId, options) {
322
371
  }
323
372
  const actualName = parsed.metadata.name || skillName;
324
373
  const installPath = getSkillPath(options.platform, actualName, options.project);
374
+ const metadataPath = path.join(installPath, ".skillhub.json");
375
+ if (await fs.pathExists(metadataPath)) {
376
+ try {
377
+ const existingMetadata = await fs.readJson(metadataPath);
378
+ if (existingMetadata.skillId && existingMetadata.skillId !== skillId) {
379
+ spinner.warn(`Name collision detected!`);
380
+ console.log(chalk.yellow(`
381
+ A different skill is already installed with the name "${actualName}":`));
382
+ console.log(chalk.dim(` Existing: ${existingMetadata.skillId}`));
383
+ console.log(chalk.dim(` New: ${skillId}`));
384
+ console.log();
385
+ if (!options.force) {
386
+ console.log(chalk.red("Installation cancelled to prevent overwriting."));
387
+ console.log(chalk.dim("Use --force to overwrite the existing skill."));
388
+ process.exit(1);
389
+ } else {
390
+ console.log(chalk.yellow("Overwriting existing skill (--force flag used).\n"));
391
+ }
392
+ }
393
+ } catch {
394
+ }
395
+ }
325
396
  if (installed && options.force) {
326
397
  await fs.remove(installPath);
327
398
  }
@@ -589,9 +660,11 @@ var pkg = require2("../package.json");
589
660
  var VERSION = pkg.version;
590
661
  var program = new Command();
591
662
  program.name("skillhub").description("CLI for managing AI Agent skills").version(VERSION);
592
- program.command("install <skill-id>").description("Install a skill from the registry").option("-p, --platform <platform>", "Target platform (claude, codex, copilot, cursor, windsurf)", "claude").option("--project", "Install in the current project instead of globally").option("-f, --force", "Overwrite existing skill").option("--no-api", "Skip API lookup and fetch directly from GitHub").action(async (skillId, options) => {
663
+ program.command("install <skill-id>").description("Install a skill from the registry").option("-p, --platform <platform>", "Target platform (claude, codex, copilot, cursor, windsurf)").option("--project", "Install in the current project instead of globally").option("-f, --force", "Overwrite existing skill").option("--no-api", "Skip API lookup and fetch directly from GitHub").action(async (skillId, options) => {
664
+ const userConfig = await loadConfig();
665
+ const platform = options.platform || userConfig.defaultPlatform || "claude";
593
666
  await install(skillId, {
594
- platform: options.platform,
667
+ platform,
595
668
  project: options.project,
596
669
  force: options.force,
597
670
  noApi: !options.api
@@ -607,31 +680,35 @@ program.command("list").description("List installed skills").option("-p, --platf
607
680
  program.command("config").description("Manage CLI configuration").option("--set <key=value>", "Set a configuration value").option("--get <key>", "Get a configuration value").option("--list", "List all configuration values").action(async (options) => {
608
681
  await config(options);
609
682
  });
610
- program.command("uninstall <skill-name>").description("Uninstall a skill").option("-p, --platform <platform>", "Target platform", "claude").option("--project", "Uninstall from project instead of globally").action(async (skillName, options) => {
683
+ 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) => {
611
684
  const fs3 = await import("fs-extra");
612
685
  const { getSkillPath: getSkillPath2, isSkillInstalled: isSkillInstalled2 } = await import("./paths-BVI5WSHE.js");
613
- const installed = await isSkillInstalled2(options.platform, skillName, options.project);
686
+ const userConfig = await loadConfig();
687
+ const platform = options.platform || userConfig.defaultPlatform || "claude";
688
+ const installed = await isSkillInstalled2(platform, skillName, options.project);
614
689
  if (!installed) {
615
690
  console.log(chalk5.yellow(`Skill ${skillName} is not installed.`));
616
691
  process.exit(1);
617
692
  }
618
- const skillPath = getSkillPath2(options.platform, skillName, options.project);
693
+ const skillPath = getSkillPath2(platform, skillName, options.project);
619
694
  await fs3.default.remove(skillPath);
620
695
  console.log(chalk5.green(`Skill ${skillName} uninstalled successfully.`));
621
696
  });
622
- program.command("update [skill-name]").description("Update installed skills").option("-p, --platform <platform>", "Target platform", "claude").option("--all", "Update all installed skills").action(async (skillName, options) => {
697
+ 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) => {
623
698
  const fsExtra = await import("fs-extra");
624
699
  const pathModule = await import("path");
625
700
  const { getSkillsPath: getSkillsPath2, getSkillPath: getSkillPath2 } = await import("./paths-BVI5WSHE.js");
701
+ const userConfig = await loadConfig();
702
+ const platform = options.platform || userConfig.defaultPlatform || "claude";
626
703
  const ALL_PLATFORMS2 = ["claude", "codex", "copilot", "cursor", "windsurf"];
627
704
  if (options.all) {
628
705
  console.log(chalk5.cyan("\nUpdating all installed skills...\n"));
629
- const platforms = options.platform ? [options.platform] : ALL_PLATFORMS2;
706
+ const platforms = options.platform ? [platform] : ALL_PLATFORMS2;
630
707
  let updated = 0;
631
708
  let failed = 0;
632
709
  let skipped = 0;
633
- for (const platform of platforms) {
634
- const skillsPath = getSkillsPath2(platform);
710
+ for (const p of platforms) {
711
+ const skillsPath = getSkillsPath2(p);
635
712
  if (!await fsExtra.default.pathExists(skillsPath)) {
636
713
  continue;
637
714
  }
@@ -655,7 +732,7 @@ program.command("update [skill-name]").description("Update installed skills").op
655
732
  }
656
733
  console.log(chalk5.dim(` Updating ${entry.name}...`));
657
734
  await install(skillId, {
658
- platform,
735
+ platform: p,
659
736
  force: true
660
737
  });
661
738
  updated++;
@@ -675,7 +752,7 @@ program.command("update [skill-name]").description("Update installed skills").op
675
752
  console.log(chalk5.red("Please specify a skill name or use --all."));
676
753
  process.exit(1);
677
754
  }
678
- const skillPath = getSkillPath2(options.platform, skillName);
755
+ const skillPath = getSkillPath2(platform, skillName);
679
756
  const metadataPath = pathModule.join(skillPath, ".skillhub.json");
680
757
  if (!await fsExtra.default.pathExists(metadataPath)) {
681
758
  console.log(chalk5.yellow(`Skill ${skillName} was not installed via CLI.`));
@@ -691,7 +768,7 @@ program.command("update [skill-name]").description("Update installed skills").op
691
768
  }
692
769
  console.log(chalk5.dim(`Updating ${skillName}...`));
693
770
  await install(skillId, {
694
- platform: options.platform,
771
+ platform,
695
772
  force: true
696
773
  });
697
774
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillhub",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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",