skild 0.0.7 → 0.0.8

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 (3) hide show
  1. package/README.md +11 -0
  2. package/dist/index.js +339 -73
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,4 +12,15 @@ npm i -g skild
12
12
 
13
13
  ```bash
14
14
  skild --help
15
+
16
+ # Install a skill (Git URL / degit shorthand / local dir)
17
+ skild install https://github.com/anthropics/skills/tree/main/skills/pdf
18
+ skild install anthropics/skills/skills/pdf#main
19
+ skild install ./path/to/your-skill
20
+
21
+ # Target platform + project-level install
22
+ skild install https://github.com/anthropics/skills/tree/main/skills/pdf -t codex --local
23
+
24
+ # List installed skills
25
+ skild list -t codex --local
15
26
  ```
package/dist/index.js CHANGED
@@ -3,54 +3,124 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
  import chalk3 from "chalk";
6
+ import { createRequire } from "module";
6
7
 
7
8
  // src/commands/install.ts
8
- import { execSync } from "child_process";
9
- import path2 from "path";
10
- import fs2 from "fs";
11
- import chalk from "chalk";
12
- import ora from "ora";
9
+ import chalk2 from "chalk";
13
10
 
14
- // src/utils/config.ts
15
- import os from "os";
16
- import path from "path";
11
+ // src/services/skill-installer.ts
12
+ import degit from "degit";
13
+ import path4 from "path";
14
+
15
+ // src/utils/fs-helpers.ts
17
16
  import fs from "fs";
18
- function getSkillsDir(platform = "claude", projectLevel = false) {
19
- const base = projectLevel ? process.cwd() : os.homedir();
20
- switch (platform) {
21
- case "claude":
22
- return path.join(base, projectLevel ? ".claude" : ".claude", "skills");
23
- case "codex":
24
- return path.join(base, projectLevel ? ".codex" : ".codex", "skills");
25
- case "copilot":
26
- return path.join(base, ".github", "skills");
27
- default:
28
- return path.join(base, ".claude", "skills");
17
+ import path from "path";
18
+ function isDirEmpty(dir) {
19
+ try {
20
+ const entries = fs.readdirSync(dir);
21
+ return entries.length === 0;
22
+ } catch {
23
+ return true;
29
24
  }
30
25
  }
31
- var SKILLS_DIR = getSkillsDir("claude", false);
32
- function ensureSkillsDir(platform = "claude", projectLevel = false) {
33
- const dir = getSkillsDir(platform, projectLevel);
34
- if (!fs.existsSync(dir)) {
35
- fs.mkdirSync(dir, { recursive: true });
26
+ function safeRemoveDir(dir) {
27
+ if (fs.existsSync(dir)) {
28
+ fs.rmSync(dir, { recursive: true, force: true });
36
29
  }
37
- return dir;
38
30
  }
39
- function getSkillPath(skillName, platform = "claude", projectLevel = false) {
40
- return path.join(getSkillsDir(platform, projectLevel), skillName);
31
+ function isDirectory(filePath) {
32
+ try {
33
+ return fs.statSync(filePath).isDirectory();
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ function pathExists(filePath) {
39
+ return fs.existsSync(filePath);
40
+ }
41
+ function copyDir(src, dest) {
42
+ fs.cpSync(src, dest, { recursive: true });
43
+ }
44
+ function hasSkillMd(skillPath) {
45
+ return fs.existsSync(path.join(skillPath, "SKILL.md"));
46
+ }
47
+ function getSubdirectories(dir) {
48
+ try {
49
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
50
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
51
+ } catch {
52
+ return [];
53
+ }
54
+ }
55
+ function sanitizeForPathSegment(value) {
56
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
57
+ }
58
+ function createTempDir(parentDir, prefix) {
59
+ const safePrefix = sanitizeForPathSegment(prefix || "tmp");
60
+ const template = path.join(parentDir, `.skild-${safePrefix}-`);
61
+ return fs.mkdtempSync(template);
62
+ }
63
+ function replaceDirAtomic(sourceDir, destDir) {
64
+ const backupDir = fs.existsSync(destDir) ? `${destDir}.bak-${Date.now()}` : null;
65
+ try {
66
+ if (backupDir) {
67
+ fs.renameSync(destDir, backupDir);
68
+ }
69
+ fs.renameSync(sourceDir, destDir);
70
+ if (backupDir) {
71
+ safeRemoveDir(backupDir);
72
+ }
73
+ } catch (error) {
74
+ try {
75
+ if (!fs.existsSync(destDir) && backupDir && fs.existsSync(backupDir)) {
76
+ fs.renameSync(backupDir, destDir);
77
+ }
78
+ } catch {
79
+ }
80
+ try {
81
+ if (fs.existsSync(sourceDir)) {
82
+ safeRemoveDir(sourceDir);
83
+ }
84
+ } catch {
85
+ }
86
+ throw error;
87
+ }
41
88
  }
42
89
 
43
- // src/commands/install.ts
90
+ // src/services/source-parser.ts
91
+ import path2 from "path";
92
+ function classifySource(source) {
93
+ const resolvedPath = path2.resolve(source);
94
+ if (pathExists(resolvedPath)) {
95
+ return "local";
96
+ }
97
+ if (/^https?:\/\//i.test(source) || source.includes("github.com")) {
98
+ return "github-url";
99
+ }
100
+ if (/^[^/]+\/[^/]+/.test(source)) {
101
+ return "degit-shorthand";
102
+ }
103
+ return "unknown";
104
+ }
44
105
  function extractSkillName(url) {
45
- const treeMatch = url.match(/\/tree\/[^/]+\/(.+?)(?:\/)?$/);
106
+ const maybeLocalPath = path2.resolve(url);
107
+ if (pathExists(maybeLocalPath)) {
108
+ return path2.basename(maybeLocalPath) || "unknown-skill";
109
+ }
110
+ const cleaned = url.replace(/[#?].*$/, "");
111
+ const treeMatch = cleaned.match(/\/tree\/[^/]+\/(.+?)(?:\/)?$/);
46
112
  if (treeMatch) {
47
113
  return treeMatch[1].split("/").pop() || "unknown-skill";
48
114
  }
49
- const repoMatch = url.match(/github\.com\/[^/]+\/([^/]+)/);
115
+ const repoMatch = cleaned.match(/github\.com\/[^/]+\/([^/]+)/);
50
116
  if (repoMatch) {
51
117
  return repoMatch[1].replace(/\.git$/, "");
52
118
  }
53
- return "unknown-skill";
119
+ const parts = cleaned.split("/").filter(Boolean);
120
+ if (parts.length >= 2) {
121
+ return parts[parts.length - 1] || "unknown-skill";
122
+ }
123
+ return cleaned || "unknown-skill";
54
124
  }
55
125
  function toDegitPath(url) {
56
126
  const treeMatch = url.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+?)(?:\/)?$/);
@@ -64,75 +134,271 @@ function toDegitPath(url) {
64
134
  }
65
135
  return url;
66
136
  }
67
- async function install(source, options = {}) {
68
- const platform = options.target || "claude";
137
+ function resolveLocalPath(source) {
138
+ const resolvedPath = path2.resolve(source);
139
+ return pathExists(resolvedPath) ? resolvedPath : null;
140
+ }
141
+
142
+ // src/utils/config.ts
143
+ import os from "os";
144
+ import path3 from "path";
145
+ import fs2 from "fs";
146
+
147
+ // src/constants.ts
148
+ var PLATFORMS = ["claude", "codex", "copilot"];
149
+ var DEFAULT_PLATFORM = "claude";
150
+ var ERROR_MESSAGES = {
151
+ EMPTY_INSTALL_DIR: (source) => `Installed directory is empty for source: ${source}
152
+ Source likely does not point to a valid subdirectory.
153
+ Try: https://github.com/<owner>/<repo>/tree/<branch>/skills/<skill-name>
154
+ Example: https://github.com/anthropics/skills/tree/main/skills/pdf`,
155
+ INVALID_SOURCE: (source) => `Unsupported source "${source}". Use a Git URL (e.g. https://github.com/owner/repo) or degit shorthand (e.g. owner/repo[/subdir][#ref]).`,
156
+ NOT_A_DIRECTORY: (path6) => `Source path is not a directory: ${path6}`,
157
+ NO_SKILLS_INSTALLED: "No skills installed.",
158
+ INSTALL_HINT: "Use `skild install <url>` to install a skill."
159
+ };
160
+ var SUCCESS_MESSAGES = {
161
+ SKILL_MD_FOUND: "SKILL.md found \u2713",
162
+ SKILL_MD_WARNING: "Warning: No SKILL.md found"
163
+ };
164
+
165
+ // src/utils/config.ts
166
+ function getSkillsDir(platform = DEFAULT_PLATFORM, projectLevel = false) {
167
+ const base = projectLevel ? process.cwd() : os.homedir();
168
+ switch (platform) {
169
+ case "claude":
170
+ return path3.join(base, ".claude", "skills");
171
+ case "codex":
172
+ return path3.join(base, ".codex", "skills");
173
+ case "copilot":
174
+ return path3.join(base, ".github", "skills");
175
+ default:
176
+ return path3.join(base, ".claude", "skills");
177
+ }
178
+ }
179
+ var SKILLS_DIR = getSkillsDir(DEFAULT_PLATFORM, false);
180
+ function ensureSkillsDir(platform = DEFAULT_PLATFORM, projectLevel = false) {
181
+ const dir = getSkillsDir(platform, projectLevel);
182
+ if (!fs2.existsSync(dir)) {
183
+ fs2.mkdirSync(dir, { recursive: true });
184
+ }
185
+ return dir;
186
+ }
187
+ function getSkillPath(skillName, platform = DEFAULT_PLATFORM, projectLevel = false) {
188
+ return path3.join(getSkillsDir(platform, projectLevel), skillName);
189
+ }
190
+
191
+ // src/services/skill-installer.ts
192
+ async function cloneRemote(degitSrc, targetPath) {
193
+ const emitter = degit(degitSrc, { force: true, verbose: false });
194
+ await emitter.clone(targetPath);
195
+ }
196
+ function copyLocal(sourcePath, targetPath) {
197
+ if (!isDirectory(sourcePath)) {
198
+ throw new Error(ERROR_MESSAGES.NOT_A_DIRECTORY(sourcePath));
199
+ }
200
+ copyDir(sourcePath, targetPath);
201
+ }
202
+ function validateStaging(stagingPath, source) {
203
+ if (isDirEmpty(stagingPath)) {
204
+ throw new Error(ERROR_MESSAGES.EMPTY_INSTALL_DIR(source));
205
+ }
206
+ }
207
+ function resolveInstallContext(source, options = {}) {
208
+ const platform = options.target || DEFAULT_PLATFORM;
69
209
  const projectLevel = options.local || false;
70
- ensureSkillsDir(platform, projectLevel);
71
210
  const skillName = extractSkillName(source);
211
+ const skillsDir = getSkillsDir(platform, projectLevel);
72
212
  const targetPath = getSkillPath(skillName, platform, projectLevel);
73
- const degitPath = toDegitPath(source);
74
213
  const locationLabel = projectLevel ? "project" : "global";
75
- const spinner = ora(`Installing ${chalk.cyan(skillName)} to ${chalk.dim(platform)} (${locationLabel})...`).start();
214
+ const localPath = resolveLocalPath(source);
215
+ const sourceType = localPath ? "local" : classifySource(source);
216
+ if (!localPath && sourceType === "unknown") {
217
+ throw new Error(ERROR_MESSAGES.INVALID_SOURCE(source));
218
+ }
219
+ const degitPath = localPath ? null : toDegitPath(source);
220
+ return {
221
+ source,
222
+ sourceType,
223
+ localPath,
224
+ degitPath,
225
+ skillName,
226
+ platform,
227
+ projectLevel,
228
+ skillsDir,
229
+ targetPath,
230
+ locationLabel
231
+ };
232
+ }
233
+ async function installSkillFromContext(context) {
234
+ ensureSkillsDir(context.platform, context.projectLevel);
235
+ const tempRoot = createTempDir(context.skillsDir, context.skillName);
236
+ const stagingPath = path4.join(tempRoot, "staging");
76
237
  try {
77
- execSync(`npx degit ${degitPath} "${targetPath}" --force`, {
78
- stdio: "pipe"
79
- });
80
- spinner.succeed(`Installed ${chalk.green(skillName)} to ${chalk.dim(targetPath)}`);
81
- const skillMdPath = path2.join(targetPath, "SKILL.md");
82
- const hasSkillMd = fs2.existsSync(skillMdPath);
83
- if (hasSkillMd) {
84
- console.log(chalk.dim(` \u2514\u2500 SKILL.md found \u2713`));
238
+ if (context.localPath) {
239
+ copyLocal(context.localPath, stagingPath);
85
240
  } else {
86
- console.log(chalk.yellow(` \u2514\u2500 Warning: No SKILL.md found`));
241
+ if (!context.degitPath) {
242
+ throw new Error(ERROR_MESSAGES.INVALID_SOURCE(context.source));
243
+ }
244
+ await cloneRemote(context.degitPath, stagingPath);
87
245
  }
246
+ validateStaging(stagingPath, context.source);
247
+ replaceDirAtomic(stagingPath, context.targetPath);
248
+ return {
249
+ skillName: context.skillName,
250
+ platform: context.platform,
251
+ projectLevel: context.projectLevel,
252
+ targetPath: context.targetPath,
253
+ hasSkillMd: hasSkillMd(context.targetPath)
254
+ };
255
+ } finally {
256
+ safeRemoveDir(tempRoot);
257
+ }
258
+ }
259
+
260
+ // src/utils/logger.ts
261
+ import chalk from "chalk";
262
+ import ora from "ora";
263
+ function createSpinner(message) {
264
+ return ora(message).start();
265
+ }
266
+ var logger = {
267
+ /**
268
+ * Log a success message with green checkmark.
269
+ */
270
+ success: (message) => {
271
+ console.log(chalk.green("\u2713"), message);
272
+ },
273
+ /**
274
+ * Log a warning message with yellow color.
275
+ */
276
+ warn: (message) => {
277
+ console.log(chalk.yellow("\u26A0"), message);
278
+ },
279
+ /**
280
+ * Log an error message with red color.
281
+ */
282
+ error: (message) => {
283
+ console.error(chalk.red("\u2717"), message);
284
+ },
285
+ /**
286
+ * Log an info message with cyan color.
287
+ */
288
+ info: (message) => {
289
+ console.log(chalk.cyan("\u2139"), message);
290
+ },
291
+ /**
292
+ * Log a dimmed/subtle message.
293
+ */
294
+ dim: (message) => {
295
+ console.log(chalk.dim(message));
296
+ },
297
+ /**
298
+ * Log a bold header.
299
+ */
300
+ header: (message) => {
301
+ console.log(chalk.bold(message));
302
+ },
303
+ /**
304
+ * Log a skill entry with status indicator.
305
+ */
306
+ skillEntry: (name, path6, hasSkillMd2) => {
307
+ const status = hasSkillMd2 ? chalk.green("\u2713") : chalk.yellow("\u26A0");
308
+ console.log(` ${status} ${chalk.cyan(name)}`);
309
+ console.log(chalk.dim(` \u2514\u2500 ${path6}`));
310
+ },
311
+ /**
312
+ * Log installation result details.
313
+ */
314
+ installDetail: (message, isWarning = false) => {
315
+ const color = isWarning ? chalk.yellow : chalk.dim;
316
+ console.log(color(` \u2514\u2500 ${message}`));
317
+ }
318
+ };
319
+
320
+ // src/commands/install.ts
321
+ async function install(source, options = {}) {
322
+ let context;
323
+ try {
324
+ context = resolveInstallContext(source, options);
88
325
  } catch (error) {
89
- spinner.fail(`Failed to install ${chalk.red(skillName)}`);
90
- console.error(chalk.red(error.message || error));
91
- process.exit(1);
326
+ const message = error instanceof Error ? error.message : String(error);
327
+ console.error(chalk2.red(message));
328
+ process.exitCode = 1;
329
+ return;
330
+ }
331
+ const spinner = createSpinner(
332
+ `Installing ${chalk2.cyan(context.skillName)} to ${chalk2.dim(context.platform)} (${context.locationLabel})...`
333
+ );
334
+ try {
335
+ const result = await installSkillFromContext(context);
336
+ spinner.succeed(`Installed ${chalk2.green(result.skillName)} to ${chalk2.dim(result.targetPath)}`);
337
+ if (result.hasSkillMd) {
338
+ logger.installDetail(SUCCESS_MESSAGES.SKILL_MD_FOUND);
339
+ } else {
340
+ logger.installDetail(SUCCESS_MESSAGES.SKILL_MD_WARNING, true);
341
+ }
342
+ } catch (error) {
343
+ spinner.fail(`Failed to install ${chalk2.red(context.skillName)}`);
344
+ const message = error instanceof Error ? error.message : String(error);
345
+ console.error(chalk2.red(message));
346
+ process.exitCode = 1;
92
347
  }
93
348
  }
94
349
 
95
350
  // src/commands/list.ts
96
- import fs3 from "fs";
97
- import path3 from "path";
98
- import chalk2 from "chalk";
99
- async function list() {
100
- ensureSkillsDir();
101
- const entries = fs3.readdirSync(SKILLS_DIR, { withFileTypes: true });
102
- const skills = entries.filter((e) => e.isDirectory());
351
+ import path5 from "path";
352
+ async function list(options = {}) {
353
+ const platform = options.target || DEFAULT_PLATFORM;
354
+ const projectLevel = options.local || false;
355
+ const skillsDir = ensureSkillsDir(platform, projectLevel);
356
+ const skills = getSubdirectories(skillsDir);
103
357
  if (skills.length === 0) {
104
- console.log(chalk2.dim("No skills installed."));
105
- console.log(chalk2.dim(`Use ${chalk2.cyan("skild install <url>")} to install a skill.`));
358
+ logger.dim(ERROR_MESSAGES.NO_SKILLS_INSTALLED);
359
+ logger.dim(ERROR_MESSAGES.INSTALL_HINT);
106
360
  return;
107
361
  }
108
- console.log(chalk2.bold(`
109
- \u{1F4E6} Installed Skills (${skills.length}):
110
- `));
111
- for (const skill of skills) {
112
- const skillPath = path3.join(SKILLS_DIR, skill.name);
113
- const skillMdPath = path3.join(skillPath, "SKILL.md");
114
- const hasSkillMd = fs3.existsSync(skillMdPath);
115
- const status = hasSkillMd ? chalk2.green("\u2713") : chalk2.yellow("\u26A0");
116
- console.log(` ${status} ${chalk2.cyan(skill.name)}`);
117
- console.log(chalk2.dim(` \u2514\u2500 ${skillPath}`));
362
+ const locationLabel = projectLevel ? "project" : "global";
363
+ logger.header(`
364
+ \u{1F4E6} Installed Skills (${skills.length}) \u2014 ${platform} (${locationLabel}):
365
+ `);
366
+ for (const skillName of skills) {
367
+ const skillPath = path5.join(skillsDir, skillName);
368
+ const hasSkill = hasSkillMd(skillPath);
369
+ logger.skillEntry(skillName, skillPath, hasSkill);
118
370
  }
119
371
  console.log("");
120
372
  }
121
373
 
374
+ // src/types/index.ts
375
+ function isPlatform(value) {
376
+ return typeof value === "string" && PLATFORMS.includes(value);
377
+ }
378
+
122
379
  // src/index.ts
380
+ var require2 = createRequire(import.meta.url);
381
+ var { version } = require2("../package.json");
123
382
  var program = new Command();
124
- program.name("skild").description("The npm for Agent Skills \u2014 Discover, install, manage, and publish AI Agent Skills with ease.").version("0.0.1");
125
- program.command("install <source>").alias("i").description("Install a Skill from a GitHub URL or registry name").option("-t, --target <platform>", "Target platform: claude, codex, copilot", "claude").option("-l, --local", "Install to project-level directory instead of global").action(async (source, options) => {
383
+ program.name("skild").description("The npm for Agent Skills \u2014 Discover, install, manage, and publish AI Agent Skills with ease.").version(version);
384
+ program.command("install <source>").alias("i").description("Install a Skill from a Git URL, degit shorthand, or local directory").option("-t, --target <platform>", "Target platform: claude, codex, copilot", DEFAULT_PLATFORM).option("-l, --local", "Install to project-level directory instead of global").action(async (source, options) => {
385
+ const platform = isPlatform(options.target) ? options.target : DEFAULT_PLATFORM;
126
386
  await install(source, {
127
- target: options.target,
387
+ target: platform,
128
388
  local: options.local
129
389
  });
130
390
  });
131
- program.command("list").alias("ls").description("List installed Skills").action(async () => {
132
- await list();
391
+ program.command("list").alias("ls").description("List installed Skills").option("-t, --target <platform>", "Target platform: claude, codex, copilot", DEFAULT_PLATFORM).option("-l, --local", "List project-level directory instead of global").action(async (options) => {
392
+ const platform = isPlatform(options.target) ? options.target : DEFAULT_PLATFORM;
393
+ await list({
394
+ target: platform,
395
+ local: options.local
396
+ });
133
397
  });
134
398
  program.action(() => {
135
399
  console.log(chalk3.bold("\n\u{1F6E1}\uFE0F skild \u2014 Get your agents skilled.\n"));
136
400
  program.outputHelp();
137
401
  });
138
- program.parse();
402
+ var argv = process.argv.slice();
403
+ if (argv[2] === "--") argv.splice(2, 1);
404
+ program.parse(argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skild",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "The npm for Agent Skills — Discover, install, manage, and publish AI Agent Skills with ease.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",