@spardutti/claude-skills 1.0.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.
package/bin/cli.mjs ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { fetchSkills } from "../lib/github.mjs";
4
+ import { promptSkillSelection } from "../lib/prompt.mjs";
5
+ import { installSkills } from "../lib/install.mjs";
6
+
7
+ async function main() {
8
+ console.log("\n Claude Skills Installer\n");
9
+
10
+ console.log(" Fetching available skills...\n");
11
+ const skills = await fetchSkills();
12
+
13
+ if (skills.length === 0) {
14
+ console.log(" No skills found.");
15
+ process.exit(0);
16
+ }
17
+
18
+ const selected = await promptSkillSelection(skills);
19
+
20
+ if (selected.length === 0) {
21
+ console.log("\n No skills selected.");
22
+ process.exit(0);
23
+ }
24
+
25
+ console.log();
26
+ await installSkills(selected);
27
+ console.log(`\n Done! ${selected.length} skill(s) installed.\n`);
28
+ }
29
+
30
+ main().catch((err) => {
31
+ if (err.name === "ExitPromptError") {
32
+ console.log("\n Cancelled.\n");
33
+ process.exit(0);
34
+ }
35
+ console.error(`\n Error: ${err.message}\n`);
36
+ process.exit(1);
37
+ });
package/lib/github.mjs ADDED
@@ -0,0 +1,61 @@
1
+ const REPO_OWNER = "Spardutti";
2
+ const REPO_NAME = "claude-skills";
3
+ const CONTENTS_API = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/contents/skills`;
4
+ const RAW_BASE = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/main/skills`;
5
+
6
+ export async function fetchSkills() {
7
+ const res = await fetch(CONTENTS_API, {
8
+ headers: { "User-Agent": "claude-skills-cli" },
9
+ });
10
+
11
+ if (!res.ok) {
12
+ if (res.status === 403) {
13
+ throw new Error("GitHub API rate limit exceeded. Try again later.");
14
+ }
15
+ throw new Error(`Failed to list skills: ${res.status} ${res.statusText}`);
16
+ }
17
+
18
+ const entries = await res.json();
19
+ const dirs = entries.filter((e) => e.type === "dir");
20
+
21
+ const results = await Promise.all(
22
+ dirs.map(async (dir) => {
23
+ try {
24
+ const url = `${RAW_BASE}/${dir.name}/SKILL.md`;
25
+ const r = await fetch(url, {
26
+ headers: { "User-Agent": "claude-skills-cli" },
27
+ });
28
+
29
+ if (!r.ok) {
30
+ console.warn(` Warning: No SKILL.md found in ${dir.name}, skipping`);
31
+ return null;
32
+ }
33
+
34
+ const content = await r.text();
35
+ const { name, description } = parseFrontmatter(content, dir.name);
36
+
37
+ return { dirName: dir.name, name, description, content };
38
+ } catch {
39
+ console.warn(` Warning: Failed to fetch ${dir.name}, skipping`);
40
+ return null;
41
+ }
42
+ })
43
+ );
44
+
45
+ return results.filter(Boolean);
46
+ }
47
+
48
+ function parseFrontmatter(content, fallbackName) {
49
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
50
+ if (!match) {
51
+ return { name: fallbackName, description: "" };
52
+ }
53
+
54
+ const block = match[1];
55
+ const name =
56
+ block.match(/^name:\s*(.+)$/m)?.[1]?.trim() || fallbackName;
57
+ const description =
58
+ block.match(/^description:\s*(.+)$/m)?.[1]?.trim() || "";
59
+
60
+ return { name, description };
61
+ }
@@ -0,0 +1,13 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+
4
+ export async function installSkills(skills, targetDir = process.cwd()) {
5
+ const baseDir = join(targetDir, ".claude", "skills");
6
+
7
+ for (const skill of skills) {
8
+ const skillDir = join(baseDir, skill.dirName);
9
+ await mkdir(skillDir, { recursive: true });
10
+ await writeFile(join(skillDir, "SKILL.md"), skill.content);
11
+ console.log(` Installed: ${skill.name} → .claude/skills/${skill.dirName}/SKILL.md`);
12
+ }
13
+ }
package/lib/prompt.mjs ADDED
@@ -0,0 +1,13 @@
1
+ import { checkbox } from "@inquirer/prompts";
2
+
3
+ export async function promptSkillSelection(skills) {
4
+ const selected = await checkbox({
5
+ message: "Select skills to install:",
6
+ choices: skills.map((skill) => ({
7
+ name: `${skill.name} - ${skill.description}`,
8
+ value: skill,
9
+ })),
10
+ });
11
+
12
+ return selected;
13
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@spardutti/claude-skills",
3
+ "version": "1.0.0",
4
+ "description": "CLI to install Claude Code skills from the claude-skills collection",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-skills": "bin/cli.mjs"
8
+ },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "lib"
15
+ ],
16
+ "keywords": [
17
+ "claude",
18
+ "claude-code",
19
+ "skills",
20
+ "cli"
21
+ ],
22
+ "author": "spardutti",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@inquirer/prompts": "^7.0.0"
26
+ }
27
+ }