add-skill-kit 1.3.0 → 1.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.
package/bin/cli.js CHANGED
@@ -1,91 +1,80 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Install Agent Skill v1.0.0
3
+ * Install Agent Skill CLI
4
+ * @description Package manager for AI Agent Skills
4
5
  */
5
- import { c, step, fatal, intro, outro } from "./lib/ui.js";
6
- import { command, params, flags, VERSION } from "./lib/config.js";
6
+ import { c, intro } from "./lib/ui.js";
7
+ import { command, params, VERSION } from "./lib/config.js";
8
+
9
+ // --- Command Registry ---
10
+ const COMMANDS = {
11
+ // Installation
12
+ install: { module: "./lib/commands/install.js", hasParam: true, aliases: ["add", "i"] },
13
+ uninstall: { module: "./lib/commands/uninstall.js", hasParam: true, aliases: ["remove", "rm"] },
14
+ update: { module: "./lib/commands/update.js", hasParam: true },
15
+
16
+ // Workspace
17
+ init: { module: "./lib/commands/init.js", aliases: ["list", "ls"] },
18
+ lock: { module: "./lib/commands/lock.js" },
19
+ cache: { module: "./lib/commands/cache.js", hasParam: true },
20
+
21
+ // Validation
22
+ verify: { module: "./lib/commands/verify.js" },
23
+ doctor: { module: "./lib/commands/doctor.js" },
24
+ validate: { module: "./lib/commands/validate.js", hasParam: true, aliases: ["check"] },
25
+ analyze: { module: "./lib/commands/analyze.js", hasParam: true },
26
+
27
+ // Info
28
+ info: { module: "./lib/commands/info.js", hasParam: true, aliases: ["show"] },
29
+ help: { module: "./lib/commands/help.js", aliases: ["--help", "-h"] }
30
+ };
31
+
32
+ /**
33
+ * Find command config by name or alias
34
+ * @param {string} cmd - Command name or alias
35
+ * @returns {{ name: string, config: object } | null}
36
+ */
37
+ function findCommand(cmd) {
38
+ // Direct match
39
+ if (COMMANDS[cmd]) {
40
+ return { name: cmd, config: COMMANDS[cmd] };
41
+ }
42
+
43
+ // Alias match
44
+ for (const [name, config] of Object.entries(COMMANDS)) {
45
+ if (config.aliases?.includes(cmd)) {
46
+ return { name, config };
47
+ }
48
+ }
49
+
50
+ return null;
51
+ }
7
52
 
8
53
  // --- MAIN ---
9
54
  async function main() {
10
55
  intro(c.bgBlue(c.white(` Add Skill Kit v${VERSION} `)));
11
56
 
12
- // Basic command routing
13
- let cmdModule;
14
-
15
57
  try {
16
- switch (command) {
17
- case "list":
18
- case "ls":
19
- case "init":
20
- cmdModule = await import("./lib/commands/init.js");
21
- await cmdModule.run();
22
- break;
23
- case "install":
24
- case "add":
25
- case "i":
26
- cmdModule = await import("./lib/commands/install.js");
27
- await cmdModule.run(params[0]);
28
- break;
29
- case "uninstall":
30
- case "remove":
31
- case "rm":
32
- cmdModule = await import("./lib/commands/uninstall.js");
33
- await cmdModule.run(params[0]);
34
- break;
35
- case "update":
36
- cmdModule = await import("./lib/commands/update.js");
37
- await cmdModule.run(params[0]);
38
- break;
39
- case "lock":
40
- cmdModule = await import("./lib/commands/lock.js");
41
- await cmdModule.run();
42
- break;
43
- case "verify":
44
- cmdModule = await import("./lib/commands/verify.js");
45
- await cmdModule.run();
46
- break;
47
- case "doctor":
48
- cmdModule = await import("./lib/commands/doctor.js");
49
- await cmdModule.run();
50
- break;
51
- case "cache":
52
- cmdModule = await import("./lib/commands/cache.js");
53
- await cmdModule.run(params[0]);
54
- break;
55
- case "validate":
56
- case "check":
57
- cmdModule = await import("./lib/commands/validate.js");
58
- await cmdModule.run(params[0]);
59
- break;
60
- case "analyze":
61
- cmdModule = await import("./lib/commands/analyze.js");
62
- await cmdModule.run(params[0]);
63
- break;
64
- case "info":
65
- case "show":
66
- cmdModule = await import("./lib/commands/info.js");
67
- await cmdModule.run(params[0]);
68
- break;
69
- case "help":
70
- case "--help":
71
- case "-h":
72
- cmdModule = await import("./lib/commands/help.js");
73
- await cmdModule.run();
74
- break;
75
- case "--version":
76
- case "-V":
77
- console.log(VERSION);
78
- break;
79
- default:
80
- // Handle direct install via org/repo syntax
81
- if (command.includes("/")) {
82
- cmdModule = await import("./lib/commands/install.js");
83
- await cmdModule.run(command);
84
- } else {
85
- console.log(`Unknown command: ${command}`);
86
- cmdModule = await import("./lib/commands/help.js");
87
- await cmdModule.run();
88
- }
58
+ // Handle version flag
59
+ if (command === "--version" || command === "-V") {
60
+ console.log(VERSION);
61
+ return;
62
+ }
63
+
64
+ // Find command
65
+ const found = findCommand(command);
66
+
67
+ if (found) {
68
+ const cmdModule = await import(found.config.module);
69
+ await cmdModule.run(found.config.hasParam ? params[0] : undefined);
70
+ } else if (command.includes("/")) {
71
+ // Direct install via org/repo syntax
72
+ const cmdModule = await import("./lib/commands/install.js");
73
+ await cmdModule.run(command);
74
+ } else {
75
+ console.log(`Unknown command: ${command}`);
76
+ const cmdModule = await import("./lib/commands/help.js");
77
+ await cmdModule.run();
89
78
  }
90
79
  } catch (err) {
91
80
  console.error(c.red("\nError: " + err.message));
@@ -207,9 +207,17 @@ export async function run(spec) {
207
207
  step("Select skills to install");
208
208
  console.log(`${c.gray(S.branch)} ${c.dim(selectedSkills.join(", "))}`);
209
209
 
210
- // Agent selection
210
+ // Agent selection - currently only Antigravity supported
211
+ const availableAgents = [
212
+ { label: "Antigravity (.agent/skills)", value: "antigravity", available: true },
213
+ { label: "Claude Code (coming soon)", value: "claude", available: false },
214
+ { label: "Codex (coming soon)", value: "codex", available: false },
215
+ { label: "Gemini CLI (coming soon)", value: "gemini", available: false },
216
+ { label: "Windsurf (coming soon)", value: "windsurf", available: false }
217
+ ];
218
+ const activeAgentCount = availableAgents.filter(a => a.available).length;
211
219
  stepLine();
212
- step("Detected 5 agents"); // Passive info
220
+ step(`Detected ${availableAgents.length} agents (${activeAgentCount} available)`);
213
221
 
214
222
  let agents;
215
223
  while (true) {
@@ -218,13 +226,10 @@ export async function run(spec) {
218
226
 
219
227
  agents = await multiselect({
220
228
  message: `${c.cyan("space")} select · ${c.cyan("enter")} confirm`,
221
- options: [
222
- { label: "Antigravity (.agent/skills)", value: "antigravity" },
223
- { label: c.dim("Claude Code (coming soon)"), value: "claude" },
224
- { label: c.dim("Codex (coming soon)"), value: "codex" },
225
- { label: c.dim("Gemini CLI (coming soon)"), value: "gemini" },
226
- { label: c.dim("Windsurf (coming soon)"), value: "windsurf" }
227
- ],
229
+ options: availableAgents.map(a => ({
230
+ label: a.available ? a.label : c.dim(a.label),
231
+ value: a.value
232
+ })),
228
233
  initialValues: ["antigravity"],
229
234
  required: true
230
235
  });
@@ -296,9 +301,9 @@ export async function run(spec) {
296
301
  const methodVerb = installMethod === "symlink" ? "symlink" : "copy";
297
302
 
298
303
  for (const sn of selectedSkills) {
299
- // Mock path relative to home for visual appeal
300
- const mockPath = `~\\Desktop\\New Project\\.agents\\skills\\${sn}`;
301
- summaryContent += `${c.cyan(mockPath)}\n`;
304
+ // Show actual target path
305
+ const targetPath = path.relative(cwd, path.join(targetScope, sn));
306
+ summaryContent += `${c.cyan(targetPath)}\n`;
302
307
  summaryContent += ` ${c.dim(methodVerb)} ${c.gray("→")} ${c.dim(agentsString)}\n\n`;
303
308
  }
304
309
 
@@ -24,7 +24,10 @@ export function getDirSize(dir) {
24
24
  }
25
25
  };
26
26
  walk(dir);
27
- } catch { }
27
+ } catch (err) {
28
+ // Directory may not exist or be inaccessible
29
+ if (process.env.DEBUG) console.error(`getDirSize error: ${err.message}`);
30
+ }
28
31
  return size;
29
32
  }
30
33
 
@@ -78,10 +81,24 @@ export function merkleHash(dir) {
78
81
  * @returns {import('./types.js').ParsedSpec}
79
82
  */
80
83
  export function parseSkillSpec(spec) {
84
+ if (!spec || typeof spec !== 'string') {
85
+ throw new Error('Skill spec is required');
86
+ }
87
+
81
88
  const [repoPart, skillPart] = spec.split("#");
89
+
90
+ if (!repoPart.includes('/')) {
91
+ throw new Error(`Invalid spec format: "${spec}". Expected: org/repo or org/repo#skill`);
92
+ }
93
+
82
94
  const [org, repo] = repoPart.split("/");
95
+
96
+ if (!org || !repo) {
97
+ throw new Error(`Invalid spec: missing org or repo in "${spec}"`);
98
+ }
99
+
83
100
  const [skill, ref] = (skillPart || "").split("@");
84
- return { org, repo, skill, ref };
101
+ return { org, repo, skill: skill || undefined, ref: ref || undefined };
85
102
  }
86
103
 
87
104
  /**
@@ -140,8 +157,12 @@ export function loadSkillLock() {
140
157
  * @returns {string[]}
141
158
  */
142
159
  export function loadRegistries() {
143
- try { return fs.existsSync(REGISTRIES_FILE) ? JSON.parse(fs.readFileSync(REGISTRIES_FILE, "utf-8")) : []; }
144
- catch { return []; }
160
+ try {
161
+ return fs.existsSync(REGISTRIES_FILE) ? JSON.parse(fs.readFileSync(REGISTRIES_FILE, "utf-8")) : [];
162
+ } catch (err) {
163
+ if (process.env.DEBUG) console.error(`loadRegistries error: ${err.message}`);
164
+ return [];
165
+ }
145
166
  }
146
167
 
147
168
  /**
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @fileoverview Tests for helpers.js
3
+ */
4
+ import { describe, it, expect } from "vitest";
5
+ import { formatBytes, formatDate, parseSkillSpec } from "./helpers.js";
6
+
7
+ describe("formatBytes", () => {
8
+ it("formats bytes correctly", () => {
9
+ expect(formatBytes(0)).toBe("0 B");
10
+ expect(formatBytes(512)).toBe("512 B");
11
+ expect(formatBytes(1024)).toBe("1.0 KB");
12
+ expect(formatBytes(1536)).toBe("1.5 KB");
13
+ expect(formatBytes(1048576)).toBe("1.0 MB");
14
+ expect(formatBytes(1572864)).toBe("1.5 MB");
15
+ });
16
+ });
17
+
18
+ describe("formatDate", () => {
19
+ it("returns 'unknown' for undefined", () => {
20
+ expect(formatDate()).toBe("unknown");
21
+ expect(formatDate(undefined)).toBe("unknown");
22
+ });
23
+
24
+ it("formats ISO date strings", () => {
25
+ const result = formatDate("2026-01-25T00:00:00.000Z");
26
+ expect(result).toBeTruthy();
27
+ expect(typeof result).toBe("string");
28
+ });
29
+ });
30
+
31
+ describe("parseSkillSpec", () => {
32
+ it("parses org/repo format", () => {
33
+ const result = parseSkillSpec("agentskillkit/agent-skills");
34
+ expect(result.org).toBe("agentskillkit");
35
+ expect(result.repo).toBe("agent-skills");
36
+ expect(result.skill).toBeUndefined();
37
+ expect(result.ref).toBeUndefined();
38
+ });
39
+
40
+ it("parses org/repo#skill format", () => {
41
+ const result = parseSkillSpec("agentskillkit/agent-skills#react-patterns");
42
+ expect(result.org).toBe("agentskillkit");
43
+ expect(result.repo).toBe("agent-skills");
44
+ expect(result.skill).toBe("react-patterns");
45
+ });
46
+
47
+ it("parses org/repo#skill@ref format", () => {
48
+ const result = parseSkillSpec("agentskillkit/agent-skills#react-patterns@v1.0.0");
49
+ expect(result.org).toBe("agentskillkit");
50
+ expect(result.repo).toBe("agent-skills");
51
+ expect(result.skill).toBe("react-patterns");
52
+ expect(result.ref).toBe("v1.0.0");
53
+ });
54
+
55
+ it("throws error for invalid spec", () => {
56
+ expect(() => parseSkillSpec("")).toThrow("Skill spec is required");
57
+ expect(() => parseSkillSpec("invalid")).toThrow("Invalid spec format");
58
+ expect(() => parseSkillSpec(null)).toThrow("Skill spec is required");
59
+ });
60
+ });
package/bin/lib/skills.js CHANGED
@@ -30,7 +30,10 @@ export function parseSkillMdFrontmatter(p) {
30
30
  else if (key && val) meta[key] = val;
31
31
  }
32
32
  return meta;
33
- } catch { return {}; }
33
+ } catch (err) {
34
+ if (process.env.DEBUG) console.error(`parseSkillMdFrontmatter error: ${err.message}`);
35
+ return {};
36
+ }
34
37
  }
35
38
 
36
39
  /**
@@ -69,7 +72,9 @@ export function detectSkillStructure(dir) {
69
72
  s.files.push(item);
70
73
  }
71
74
  }
72
- } catch { }
75
+ } catch (err) {
76
+ if (process.env.DEBUG) console.error(`detectSkillStructure error: ${err.message}`);
77
+ }
73
78
  return s;
74
79
  }
75
80
 
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @fileoverview Tests for skills.js
3
+ */
4
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import os from "os";
8
+ import { parseSkillMdFrontmatter, detectSkillStructure } from "./skills.js";
9
+
10
+ describe("parseSkillMdFrontmatter", () => {
11
+ let tempDir;
12
+ let skillMdPath;
13
+
14
+ beforeEach(() => {
15
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "skill-test-"));
16
+ skillMdPath = path.join(tempDir, "SKILL.md");
17
+ });
18
+
19
+ afterEach(() => {
20
+ fs.rmSync(tempDir, { recursive: true, force: true });
21
+ });
22
+
23
+ it("parses valid frontmatter", () => {
24
+ const content = `---
25
+ name: test-skill
26
+ description: A test skill for testing
27
+ version: 1.0.0
28
+ author: Test Author
29
+ tags: react, testing, patterns
30
+ ---
31
+
32
+ # Test Skill
33
+
34
+ Some content here.
35
+ `;
36
+ fs.writeFileSync(skillMdPath, content);
37
+
38
+ const result = parseSkillMdFrontmatter(skillMdPath);
39
+
40
+ expect(result.name).toBe("test-skill");
41
+ expect(result.description).toBe("A test skill for testing");
42
+ expect(result.version).toBe("1.0.0");
43
+ expect(result.author).toBe("Test Author");
44
+ expect(result.tags).toEqual(["react", "testing", "patterns"]);
45
+ });
46
+
47
+ it("returns empty object for no frontmatter", () => {
48
+ fs.writeFileSync(skillMdPath, "# Just a heading\n\nNo frontmatter here.");
49
+
50
+ const result = parseSkillMdFrontmatter(skillMdPath);
51
+
52
+ expect(result).toEqual({});
53
+ });
54
+
55
+ it("returns empty object for non-existent file", () => {
56
+ const result = parseSkillMdFrontmatter("/non/existent/path/SKILL.md");
57
+ expect(result).toEqual({});
58
+ });
59
+ });
60
+
61
+ describe("detectSkillStructure", () => {
62
+ let tempDir;
63
+
64
+ beforeEach(() => {
65
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "skill-structure-"));
66
+ });
67
+
68
+ afterEach(() => {
69
+ fs.rmSync(tempDir, { recursive: true, force: true });
70
+ });
71
+
72
+ it("detects standard directories", () => {
73
+ fs.mkdirSync(path.join(tempDir, "resources"));
74
+ fs.mkdirSync(path.join(tempDir, "examples"));
75
+ fs.mkdirSync(path.join(tempDir, "scripts"));
76
+ fs.writeFileSync(path.join(tempDir, "SKILL.md"), "# Skill");
77
+
78
+ const result = detectSkillStructure(tempDir);
79
+
80
+ expect(result.hasResources).toBe(true);
81
+ expect(result.hasExamples).toBe(true);
82
+ expect(result.hasScripts).toBe(true);
83
+ expect(result.directories).toContain("resources");
84
+ expect(result.directories).toContain("examples");
85
+ expect(result.directories).toContain("scripts");
86
+ expect(result.files).toContain("SKILL.md");
87
+ });
88
+
89
+ it("detects governance directories", () => {
90
+ fs.mkdirSync(path.join(tempDir, "constitution"));
91
+ fs.mkdirSync(path.join(tempDir, "doctrines"));
92
+ fs.mkdirSync(path.join(tempDir, "enforcement"));
93
+ fs.mkdirSync(path.join(tempDir, "proposals"));
94
+
95
+ const result = detectSkillStructure(tempDir);
96
+
97
+ expect(result.hasConstitution).toBe(true);
98
+ expect(result.hasDoctrines).toBe(true);
99
+ expect(result.hasEnforcement).toBe(true);
100
+ expect(result.hasProposals).toBe(true);
101
+ });
102
+
103
+ it("returns empty structure for non-existent dir", () => {
104
+ const result = detectSkillStructure("/non/existent/path");
105
+
106
+ expect(result.directories).toEqual([]);
107
+ expect(result.files).toEqual([]);
108
+ });
109
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-skill-kit",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Enterprise-grade Agent Skill Manager with Antigravity Skills support, Progressive Disclosure detection, and semantic routing validation",
5
5
  "license": "MIT",
6
6
  "author": "agentskillkit <agentskillkit@gmail.com>",
@@ -38,9 +38,12 @@
38
38
  "antigravity"
39
39
  ],
40
40
  "scripts": {
41
- "lint": "echo \"No lint configured\"",
42
- "test": "echo \"No tests yet\"",
43
- "ci": "node bin/cli.js verify --strict && node bin/cli.js doctor --strict"
41
+ "lint": "eslint bin/",
42
+ "lint:fix": "eslint bin/ --fix",
43
+ "format": "prettier --write bin/",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "ci": "npm run lint && npm test && node bin/cli.js verify --strict && node bin/cli.js doctor --strict"
44
47
  },
45
48
  "publishConfig": {
46
49
  "access": "public"
@@ -52,5 +55,10 @@
52
55
  "kleur": "^4.1.5",
53
56
  "ora": "^8.1.0",
54
57
  "prompts": "^2.4.2"
58
+ },
59
+ "devDependencies": {
60
+ "eslint": "^8.57.0",
61
+ "prettier": "^3.2.5",
62
+ "vitest": "^1.6.0"
55
63
  }
56
64
  }