add-skill-kit 1.3.0 → 1.3.2

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
 
@@ -383,6 +388,78 @@ export async function run(spec) {
383
388
  geminiInstalled = true;
384
389
  }
385
390
 
391
+ // Install agents if they exist
392
+ const agentsDir = path.join(tmp, ".agent", "agents");
393
+ const targetAgentsDir = path.join(WORKSPACE, "..", "agents");
394
+ let agentsInstalled = 0;
395
+
396
+ if (fs.existsSync(agentsDir)) {
397
+ stepLine();
398
+ const as = spinner();
399
+ as.start("Installing agents");
400
+
401
+ fs.mkdirSync(targetAgentsDir, { recursive: true });
402
+ const agents = fs.readdirSync(agentsDir).filter(f => f.endsWith(".md"));
403
+
404
+ for (const agent of agents) {
405
+ const src = path.join(agentsDir, agent);
406
+ const dest = path.join(targetAgentsDir, agent);
407
+
408
+ if (!fs.existsSync(dest)) {
409
+ fs.copyFileSync(src, dest);
410
+ agentsInstalled++;
411
+ }
412
+ }
413
+
414
+ as.stop(`Installed ${agentsInstalled} agents`);
415
+ }
416
+
417
+ // Install ARCHITECTURE.md if it exists
418
+ const archSrc = path.join(tmp, ".agent", "ARCHITECTURE.md");
419
+ const archDest = path.join(WORKSPACE, "..", "ARCHITECTURE.md");
420
+ let archInstalled = false;
421
+
422
+ if (fs.existsSync(archSrc) && !fs.existsSync(archDest)) {
423
+ fs.copyFileSync(archSrc, archDest);
424
+ step("Installed ARCHITECTURE.md");
425
+ archInstalled = true;
426
+ }
427
+
428
+ // Install knowledge if it exists
429
+ const knowledgeDir = path.join(tmp, ".agent", "knowledge");
430
+ const targetKnowledgeDir = path.join(WORKSPACE, "..", "knowledge");
431
+ let knowledgeInstalled = false;
432
+
433
+ if (fs.existsSync(knowledgeDir) && !fs.existsSync(targetKnowledgeDir)) {
434
+ fs.cpSync(knowledgeDir, targetKnowledgeDir, { recursive: true });
435
+ step("Installed knowledge/");
436
+ knowledgeInstalled = true;
437
+ }
438
+
439
+ // Install rules if they exist
440
+ const rulesDir = path.join(tmp, ".agent", "rules");
441
+ const targetRulesDir = path.join(WORKSPACE, "..", "rules");
442
+ let rulesInstalled = 0;
443
+
444
+ if (fs.existsSync(rulesDir)) {
445
+ fs.mkdirSync(targetRulesDir, { recursive: true });
446
+ const rules = fs.readdirSync(rulesDir).filter(f => f.endsWith(".md"));
447
+
448
+ for (const rule of rules) {
449
+ const src = path.join(rulesDir, rule);
450
+ const dest = path.join(targetRulesDir, rule);
451
+
452
+ if (!fs.existsSync(dest)) {
453
+ fs.copyFileSync(src, dest);
454
+ rulesInstalled++;
455
+ }
456
+ }
457
+
458
+ if (rulesInstalled > 0) {
459
+ step(`Installed ${rulesInstalled} rules`);
460
+ }
461
+ }
462
+
386
463
  // Installation complete step
387
464
  stepLine();
388
465
  step("Installation complete");
@@ -404,14 +481,35 @@ export async function run(spec) {
404
481
  successContent += `${c.cyan("✓")} ${c.dim(`.agent/workflows/ (${workflowsInstalled} files)`)}\n`;
405
482
  }
406
483
 
484
+ // Agents summary
485
+ if (agentsInstalled > 0) {
486
+ successContent += `${c.cyan("✓")} ${c.dim(`.agent/agents/ (${agentsInstalled} files)`)}\n`;
487
+ }
488
+
407
489
  // GEMINI.md summary
408
490
  if (geminiInstalled) {
409
491
  successContent += `${c.cyan("✓")} ${c.dim(".agent/GEMINI.md (Agent Rules)")}\n`;
410
492
  }
411
493
 
494
+ // ARCHITECTURE.md summary
495
+ if (archInstalled) {
496
+ successContent += `${c.cyan("✓")} ${c.dim(".agent/ARCHITECTURE.md")}\n`;
497
+ }
498
+
499
+ // Knowledge summary
500
+ if (knowledgeInstalled) {
501
+ successContent += `${c.cyan("✓")} ${c.dim(".agent/knowledge/")}\n`;
502
+ }
503
+
504
+ // Rules summary
505
+ if (rulesInstalled > 0) {
506
+ successContent += `${c.cyan("✓")} ${c.dim(`.agent/rules/ (${rulesInstalled} files)`)}\n`;
507
+ }
508
+
412
509
  // Build title
413
510
  const parts = [`${selectedSkills.length} skills`];
414
511
  if (workflowsInstalled > 0) parts.push(`${workflowsInstalled} workflows`);
512
+ if (agentsInstalled > 0) parts.push(`${agentsInstalled} agents`);
415
513
  if (geminiInstalled) parts.push("GEMINI.md");
416
514
 
417
515
  console.log(boxen(successContent.trim(), {
@@ -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.2",
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
  }