@nimblebrain/mpak 0.1.0 → 0.2.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.
Files changed (151) hide show
  1. package/LICENSE +10 -198
  2. package/README.md +50 -383
  3. package/dist/index.d.ts +0 -2
  4. package/dist/index.js +2113 -4
  5. package/dist/index.js.map +1 -1
  6. package/package.json +32 -29
  7. package/.claude/settings.local.json +0 -19
  8. package/.env.example +0 -13
  9. package/.github/workflows/ci.yml +0 -27
  10. package/CLAUDE.md +0 -283
  11. package/dist/commands/config.d.ts +0 -31
  12. package/dist/commands/config.d.ts.map +0 -1
  13. package/dist/commands/config.js +0 -129
  14. package/dist/commands/config.js.map +0 -1
  15. package/dist/commands/packages/pull.d.ts +0 -11
  16. package/dist/commands/packages/pull.d.ts.map +0 -1
  17. package/dist/commands/packages/pull.js +0 -72
  18. package/dist/commands/packages/pull.js.map +0 -1
  19. package/dist/commands/packages/run.d.ts +0 -47
  20. package/dist/commands/packages/run.d.ts.map +0 -1
  21. package/dist/commands/packages/run.js +0 -419
  22. package/dist/commands/packages/run.js.map +0 -1
  23. package/dist/commands/packages/search.d.ts +0 -12
  24. package/dist/commands/packages/search.d.ts.map +0 -1
  25. package/dist/commands/packages/search.js +0 -63
  26. package/dist/commands/packages/search.js.map +0 -1
  27. package/dist/commands/packages/show.d.ts +0 -8
  28. package/dist/commands/packages/show.d.ts.map +0 -1
  29. package/dist/commands/packages/show.js +0 -109
  30. package/dist/commands/packages/show.js.map +0 -1
  31. package/dist/commands/search.d.ts +0 -12
  32. package/dist/commands/search.d.ts.map +0 -1
  33. package/dist/commands/search.js +0 -144
  34. package/dist/commands/search.js.map +0 -1
  35. package/dist/commands/skills/index.d.ts +0 -8
  36. package/dist/commands/skills/index.d.ts.map +0 -1
  37. package/dist/commands/skills/index.js +0 -8
  38. package/dist/commands/skills/index.js.map +0 -1
  39. package/dist/commands/skills/install.d.ts +0 -9
  40. package/dist/commands/skills/install.d.ts.map +0 -1
  41. package/dist/commands/skills/install.js +0 -110
  42. package/dist/commands/skills/install.js.map +0 -1
  43. package/dist/commands/skills/list.d.ts +0 -8
  44. package/dist/commands/skills/list.d.ts.map +0 -1
  45. package/dist/commands/skills/list.js +0 -89
  46. package/dist/commands/skills/list.js.map +0 -1
  47. package/dist/commands/skills/pack.d.ts +0 -22
  48. package/dist/commands/skills/pack.d.ts.map +0 -1
  49. package/dist/commands/skills/pack.js +0 -116
  50. package/dist/commands/skills/pack.js.map +0 -1
  51. package/dist/commands/skills/pull.d.ts +0 -9
  52. package/dist/commands/skills/pull.d.ts.map +0 -1
  53. package/dist/commands/skills/pull.js +0 -68
  54. package/dist/commands/skills/pull.js.map +0 -1
  55. package/dist/commands/skills/search.d.ts +0 -14
  56. package/dist/commands/skills/search.d.ts.map +0 -1
  57. package/dist/commands/skills/search.js +0 -53
  58. package/dist/commands/skills/search.js.map +0 -1
  59. package/dist/commands/skills/show.d.ts +0 -8
  60. package/dist/commands/skills/show.d.ts.map +0 -1
  61. package/dist/commands/skills/show.js +0 -64
  62. package/dist/commands/skills/show.js.map +0 -1
  63. package/dist/commands/skills/validate.d.ts +0 -25
  64. package/dist/commands/skills/validate.d.ts.map +0 -1
  65. package/dist/commands/skills/validate.js +0 -191
  66. package/dist/commands/skills/validate.js.map +0 -1
  67. package/dist/index.d.ts.map +0 -1
  68. package/dist/lib/api/registry-client.d.ts +0 -63
  69. package/dist/lib/api/registry-client.d.ts.map +0 -1
  70. package/dist/lib/api/registry-client.js +0 -167
  71. package/dist/lib/api/registry-client.js.map +0 -1
  72. package/dist/lib/api/skills-client.d.ts +0 -30
  73. package/dist/lib/api/skills-client.d.ts.map +0 -1
  74. package/dist/lib/api/skills-client.js +0 -110
  75. package/dist/lib/api/skills-client.js.map +0 -1
  76. package/dist/program.d.ts +0 -12
  77. package/dist/program.d.ts.map +0 -1
  78. package/dist/program.js +0 -186
  79. package/dist/program.js.map +0 -1
  80. package/dist/schemas/generated/api-responses.d.ts +0 -541
  81. package/dist/schemas/generated/api-responses.d.ts.map +0 -1
  82. package/dist/schemas/generated/api-responses.js +0 -313
  83. package/dist/schemas/generated/api-responses.js.map +0 -1
  84. package/dist/schemas/generated/auth.d.ts +0 -18
  85. package/dist/schemas/generated/auth.d.ts.map +0 -1
  86. package/dist/schemas/generated/auth.js +0 -18
  87. package/dist/schemas/generated/auth.js.map +0 -1
  88. package/dist/schemas/generated/index.d.ts +0 -5
  89. package/dist/schemas/generated/index.d.ts.map +0 -1
  90. package/dist/schemas/generated/index.js +0 -6
  91. package/dist/schemas/generated/index.js.map +0 -1
  92. package/dist/schemas/generated/package.d.ts +0 -43
  93. package/dist/schemas/generated/package.d.ts.map +0 -1
  94. package/dist/schemas/generated/package.js +0 -20
  95. package/dist/schemas/generated/package.js.map +0 -1
  96. package/dist/schemas/generated/skill.d.ts +0 -381
  97. package/dist/schemas/generated/skill.d.ts.map +0 -1
  98. package/dist/schemas/generated/skill.js +0 -216
  99. package/dist/schemas/generated/skill.js.map +0 -1
  100. package/dist/utils/config-manager.d.ts +0 -66
  101. package/dist/utils/config-manager.d.ts.map +0 -1
  102. package/dist/utils/config-manager.js +0 -193
  103. package/dist/utils/config-manager.js.map +0 -1
  104. package/dist/utils/errors.d.ts +0 -12
  105. package/dist/utils/errors.d.ts.map +0 -1
  106. package/dist/utils/errors.js +0 -27
  107. package/dist/utils/errors.js.map +0 -1
  108. package/dist/utils/version.d.ts +0 -5
  109. package/dist/utils/version.d.ts.map +0 -1
  110. package/dist/utils/version.js +0 -19
  111. package/dist/utils/version.js.map +0 -1
  112. package/eslint.config.js +0 -63
  113. package/src/commands/config.ts +0 -162
  114. package/src/commands/packages/pull.ts +0 -96
  115. package/src/commands/packages/run.test.ts +0 -261
  116. package/src/commands/packages/run.ts +0 -536
  117. package/src/commands/packages/search.ts +0 -83
  118. package/src/commands/packages/show.ts +0 -128
  119. package/src/commands/search.ts +0 -191
  120. package/src/commands/skills/index.ts +0 -7
  121. package/src/commands/skills/install.ts +0 -129
  122. package/src/commands/skills/list.ts +0 -116
  123. package/src/commands/skills/pack.test.ts +0 -260
  124. package/src/commands/skills/pack.ts +0 -145
  125. package/src/commands/skills/pull.ts +0 -88
  126. package/src/commands/skills/search.ts +0 -73
  127. package/src/commands/skills/show.ts +0 -72
  128. package/src/commands/skills/validate.test.ts +0 -466
  129. package/src/commands/skills/validate.ts +0 -227
  130. package/src/index.ts +0 -11
  131. package/src/lib/api/registry-client.ts +0 -223
  132. package/src/lib/api/schema.d.ts +0 -520
  133. package/src/lib/api/skills-client.ts +0 -148
  134. package/src/program.test.ts +0 -22
  135. package/src/program.ts +0 -226
  136. package/src/schemas/config.v1.schema.json +0 -37
  137. package/src/schemas/generated/api-responses.ts +0 -386
  138. package/src/schemas/generated/auth.ts +0 -21
  139. package/src/schemas/generated/index.ts +0 -5
  140. package/src/schemas/generated/package.ts +0 -29
  141. package/src/schemas/generated/skill.ts +0 -271
  142. package/src/utils/config-manager.test.ts +0 -330
  143. package/src/utils/config-manager.ts +0 -272
  144. package/src/utils/errors.test.ts +0 -25
  145. package/src/utils/errors.ts +0 -33
  146. package/src/utils/version.test.ts +0 -16
  147. package/src/utils/version.ts +0 -18
  148. package/test/integration/registry-client.test.ts +0 -180
  149. package/tsconfig.check.json +0 -9
  150. package/tsconfig.json +0 -25
  151. package/vitest.config.ts +0 -14
@@ -1,466 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mkdirSync, writeFileSync, rmSync } from 'fs';
3
- import { join } from 'path';
4
- import { tmpdir } from 'os';
5
- import { validateSkillDirectory, formatValidationResult } from './validate.js';
6
-
7
- describe('validateSkillDirectory', () => {
8
- let testDir: string;
9
-
10
- beforeEach(() => {
11
- testDir = join(tmpdir(), `skill-test-${Date.now()}`);
12
- mkdirSync(testDir, { recursive: true });
13
- });
14
-
15
- afterEach(() => {
16
- rmSync(testDir, { recursive: true, force: true });
17
- });
18
-
19
- describe('directory checks', () => {
20
- it('fails for non-existent directory', () => {
21
- const result = validateSkillDirectory('/non/existent/path');
22
- expect(result.valid).toBe(false);
23
- expect(result.errors).toContain('Directory not found: /non/existent/path');
24
- });
25
-
26
- it('fails for file instead of directory', () => {
27
- const filePath = join(testDir, 'not-a-dir');
28
- writeFileSync(filePath, 'content');
29
-
30
- const result = validateSkillDirectory(filePath);
31
- expect(result.valid).toBe(false);
32
- expect(result.errors[0]).toMatch(/Path is not a directory/);
33
- });
34
- });
35
-
36
- describe('SKILL.md checks', () => {
37
- it('fails when SKILL.md is missing', () => {
38
- const skillDir = join(testDir, 'test-skill');
39
- mkdirSync(skillDir);
40
-
41
- const result = validateSkillDirectory(skillDir);
42
- expect(result.valid).toBe(false);
43
- expect(result.errors).toContain('SKILL.md not found');
44
- });
45
-
46
- it('fails when frontmatter is missing', () => {
47
- const skillDir = join(testDir, 'test-skill');
48
- mkdirSync(skillDir);
49
- writeFileSync(join(skillDir, 'SKILL.md'), '# Just content\nNo frontmatter here');
50
-
51
- const result = validateSkillDirectory(skillDir);
52
- expect(result.valid).toBe(false);
53
- expect(result.errors).toContain('No frontmatter found in SKILL.md');
54
- });
55
-
56
- it('fails when frontmatter is empty', () => {
57
- const skillDir = join(testDir, 'test-skill');
58
- mkdirSync(skillDir);
59
- writeFileSync(join(skillDir, 'SKILL.md'), '---\n---\n# Content');
60
-
61
- const result = validateSkillDirectory(skillDir);
62
- expect(result.valid).toBe(false);
63
- expect(result.errors).toContain('No frontmatter found in SKILL.md');
64
- });
65
- });
66
-
67
- describe('frontmatter validation', () => {
68
- it('fails when name is missing', () => {
69
- const skillDir = join(testDir, 'test-skill');
70
- mkdirSync(skillDir);
71
- writeFileSync(
72
- join(skillDir, 'SKILL.md'),
73
- `---
74
- description: A test skill
75
- ---
76
- # Test`
77
- );
78
-
79
- const result = validateSkillDirectory(skillDir);
80
- expect(result.valid).toBe(false);
81
- expect(result.errors.some((e) => e.includes('name'))).toBe(true);
82
- });
83
-
84
- it('fails when description is missing', () => {
85
- const skillDir = join(testDir, 'test-skill');
86
- mkdirSync(skillDir);
87
- writeFileSync(
88
- join(skillDir, 'SKILL.md'),
89
- `---
90
- name: test-skill
91
- ---
92
- # Test`
93
- );
94
-
95
- const result = validateSkillDirectory(skillDir);
96
- expect(result.valid).toBe(false);
97
- expect(result.errors.some((e) => e.includes('description'))).toBe(true);
98
- });
99
-
100
- it('fails when name format is invalid', () => {
101
- const skillDir = join(testDir, 'test-skill');
102
- mkdirSync(skillDir);
103
- writeFileSync(
104
- join(skillDir, 'SKILL.md'),
105
- `---
106
- name: Test_Skill
107
- description: A test skill
108
- ---
109
- # Test`
110
- );
111
-
112
- const result = validateSkillDirectory(skillDir);
113
- expect(result.valid).toBe(false);
114
- expect(result.errors.some((e) => e.toLowerCase().includes('lowercase'))).toBe(true);
115
- });
116
-
117
- it('fails when name has uppercase', () => {
118
- const skillDir = join(testDir, 'test-skill');
119
- mkdirSync(skillDir);
120
- writeFileSync(
121
- join(skillDir, 'SKILL.md'),
122
- `---
123
- name: TestSkill
124
- description: A test skill
125
- ---
126
- # Test`
127
- );
128
-
129
- const result = validateSkillDirectory(skillDir);
130
- expect(result.valid).toBe(false);
131
- });
132
-
133
- it('fails when name starts with hyphen', () => {
134
- const skillDir = join(testDir, '-test-skill');
135
- mkdirSync(skillDir);
136
- writeFileSync(
137
- join(skillDir, 'SKILL.md'),
138
- `---
139
- name: -test-skill
140
- description: A test skill
141
- ---
142
- # Test`
143
- );
144
-
145
- const result = validateSkillDirectory(skillDir);
146
- expect(result.valid).toBe(false);
147
- });
148
-
149
- it('fails when name does not match directory', () => {
150
- const skillDir = join(testDir, 'dir-name');
151
- mkdirSync(skillDir);
152
- writeFileSync(
153
- join(skillDir, 'SKILL.md'),
154
- `---
155
- name: different-name
156
- description: A test skill
157
- ---
158
- # Test`
159
- );
160
-
161
- const result = validateSkillDirectory(skillDir);
162
- expect(result.valid).toBe(false);
163
- expect(result.errors.some((e) => e.includes('does not match directory'))).toBe(true);
164
- });
165
- });
166
-
167
- describe('valid skills', () => {
168
- it('validates minimal skill', () => {
169
- const skillDir = join(testDir, 'test-skill');
170
- mkdirSync(skillDir);
171
- writeFileSync(
172
- join(skillDir, 'SKILL.md'),
173
- `---
174
- name: test-skill
175
- description: A test skill for validation
176
- ---
177
- # Test Skill
178
-
179
- Instructions here.`
180
- );
181
-
182
- const result = validateSkillDirectory(skillDir);
183
- expect(result.valid).toBe(true);
184
- expect(result.name).toBe('test-skill');
185
- expect(result.frontmatter?.description).toBe('A test skill for validation');
186
- });
187
-
188
- it('validates skill with optional fields', () => {
189
- const skillDir = join(testDir, 'full-skill');
190
- mkdirSync(skillDir);
191
- writeFileSync(
192
- join(skillDir, 'SKILL.md'),
193
- `---
194
- name: full-skill
195
- description: A fully featured test skill
196
- license: MIT
197
- compatibility: Works with Claude Code
198
- allowed-tools: Read Grep Bash
199
- ---
200
- # Full Skill`
201
- );
202
-
203
- const result = validateSkillDirectory(skillDir);
204
- expect(result.valid).toBe(true);
205
- expect(result.frontmatter?.license).toBe('MIT');
206
- expect(result.frontmatter?.compatibility).toBe('Works with Claude Code');
207
- expect(result.frontmatter?.['allowed-tools']).toBe('Read Grep Bash');
208
- });
209
-
210
- it('validates skill with metadata', () => {
211
- const skillDir = join(testDir, 'meta-skill');
212
- mkdirSync(skillDir);
213
- writeFileSync(
214
- join(skillDir, 'SKILL.md'),
215
- `---
216
- name: meta-skill
217
- description: A skill with metadata
218
- metadata:
219
- version: "1.0.0"
220
- category: development
221
- tags:
222
- - testing
223
- - validation
224
- surfaces:
225
- - claude-code
226
- author:
227
- name: Test Author
228
- ---
229
- # Meta Skill`
230
- );
231
-
232
- const result = validateSkillDirectory(skillDir);
233
- expect(result.valid).toBe(true);
234
- expect(result.frontmatter?.metadata?.version).toBe('1.0.0');
235
- expect(result.frontmatter?.metadata?.category).toBe('development');
236
- expect(result.frontmatter?.metadata?.tags).toEqual(['testing', 'validation']);
237
- expect(result.frontmatter?.metadata?.surfaces).toEqual(['claude-code']);
238
- expect(result.frontmatter?.metadata?.author?.name).toBe('Test Author');
239
- });
240
- });
241
-
242
- describe('warnings', () => {
243
- it('warns when metadata is missing', () => {
244
- const skillDir = join(testDir, 'basic-skill');
245
- mkdirSync(skillDir);
246
- writeFileSync(
247
- join(skillDir, 'SKILL.md'),
248
- `---
249
- name: basic-skill
250
- description: A basic skill without metadata
251
- ---
252
- # Basic Skill`
253
- );
254
-
255
- const result = validateSkillDirectory(skillDir);
256
- expect(result.valid).toBe(true);
257
- expect(result.warnings.some((w) => w.includes('No metadata field'))).toBe(true);
258
- });
259
-
260
- it('warns when version is missing from metadata', () => {
261
- const skillDir = join(testDir, 'no-version-skill');
262
- mkdirSync(skillDir);
263
- writeFileSync(
264
- join(skillDir, 'SKILL.md'),
265
- `---
266
- name: no-version-skill
267
- description: A skill without version
268
- metadata:
269
- category: development
270
- ---
271
- # No Version Skill`
272
- );
273
-
274
- const result = validateSkillDirectory(skillDir);
275
- expect(result.valid).toBe(true);
276
- expect(result.warnings.some((w) => w.includes('No version'))).toBe(true);
277
- });
278
-
279
- it('warns when tags are missing', () => {
280
- const skillDir = join(testDir, 'no-tags-skill');
281
- mkdirSync(skillDir);
282
- writeFileSync(
283
- join(skillDir, 'SKILL.md'),
284
- `---
285
- name: no-tags-skill
286
- description: A skill without tags
287
- metadata:
288
- version: "1.0.0"
289
- ---
290
- # No Tags Skill`
291
- );
292
-
293
- const result = validateSkillDirectory(skillDir);
294
- expect(result.valid).toBe(true);
295
- expect(result.warnings.some((w) => w.includes('No tags'))).toBe(true);
296
- });
297
-
298
- it('warns about invalid optional directory', () => {
299
- const skillDir = join(testDir, 'file-as-dir-skill');
300
- mkdirSync(skillDir);
301
- writeFileSync(
302
- join(skillDir, 'SKILL.md'),
303
- `---
304
- name: file-as-dir-skill
305
- description: A skill with scripts as file
306
- ---
307
- # Skill`
308
- );
309
- writeFileSync(join(skillDir, 'scripts'), 'not a directory');
310
-
311
- const result = validateSkillDirectory(skillDir);
312
- expect(result.valid).toBe(true);
313
- expect(result.warnings.some((w) => w.includes('scripts'))).toBe(true);
314
- });
315
- });
316
-
317
- describe('optional directories', () => {
318
- it('accepts valid optional directories', () => {
319
- const skillDir = join(testDir, 'dirs-skill');
320
- mkdirSync(skillDir);
321
- mkdirSync(join(skillDir, 'scripts'));
322
- mkdirSync(join(skillDir, 'references'));
323
- mkdirSync(join(skillDir, 'assets'));
324
- writeFileSync(
325
- join(skillDir, 'SKILL.md'),
326
- `---
327
- name: dirs-skill
328
- description: A skill with all optional dirs
329
- ---
330
- # Skill`
331
- );
332
-
333
- const result = validateSkillDirectory(skillDir);
334
- expect(result.valid).toBe(true);
335
- expect(result.warnings.filter((w) => w.includes('not a directory'))).toHaveLength(0);
336
- });
337
- });
338
- });
339
-
340
- describe('formatValidationResult', () => {
341
- it('formats valid result correctly', () => {
342
- const result = {
343
- valid: true,
344
- name: 'test-skill',
345
- path: '/path/to/skill',
346
- frontmatter: {
347
- name: 'test-skill',
348
- description: 'A test skill description',
349
- },
350
- errors: [],
351
- warnings: [],
352
- };
353
-
354
- const output = formatValidationResult(result);
355
- expect(output).toContain('✓ Valid: test-skill');
356
- expect(output).toContain('✓ SKILL.md found');
357
- expect(output).toContain('name: test-skill');
358
- expect(output).toContain('description:');
359
- });
360
-
361
- it('formats invalid result correctly', () => {
362
- const result = {
363
- valid: false,
364
- name: null,
365
- path: '/path/to/skill',
366
- frontmatter: null,
367
- errors: ['SKILL.md not found'],
368
- warnings: [],
369
- };
370
-
371
- const output = formatValidationResult(result);
372
- expect(output).toContain('✗ Invalid: /path/to/skill');
373
- expect(output).toContain('Errors:');
374
- expect(output).toContain('SKILL.md not found');
375
- });
376
-
377
- it('formats warnings correctly', () => {
378
- const result = {
379
- valid: true,
380
- name: 'test-skill',
381
- path: '/path/to/skill',
382
- frontmatter: {
383
- name: 'test-skill',
384
- description: 'A test skill',
385
- },
386
- errors: [],
387
- warnings: ['No metadata field'],
388
- };
389
-
390
- const output = formatValidationResult(result);
391
- expect(output).toContain('Warnings:');
392
- expect(output).toContain('No metadata field');
393
- });
394
-
395
- it('formats optional fields', () => {
396
- const result = {
397
- valid: true,
398
- name: 'test-skill',
399
- path: '/path/to/skill',
400
- frontmatter: {
401
- name: 'test-skill',
402
- description: 'A test skill',
403
- license: 'MIT',
404
- compatibility: 'Claude Code',
405
- 'allowed-tools': 'Read Grep',
406
- },
407
- errors: [],
408
- warnings: [],
409
- };
410
-
411
- const output = formatValidationResult(result);
412
- expect(output).toContain('license: MIT');
413
- expect(output).toContain('compatibility: Claude Code');
414
- expect(output).toContain('allowed-tools: Read Grep');
415
- });
416
-
417
- it('formats metadata correctly', () => {
418
- const result = {
419
- valid: true,
420
- name: 'test-skill',
421
- path: '/path/to/skill',
422
- frontmatter: {
423
- name: 'test-skill',
424
- description: 'A test skill',
425
- metadata: {
426
- version: '1.0.0',
427
- category: 'development' as const,
428
- tags: ['test', 'validation'],
429
- triggers: ['test trigger'],
430
- surfaces: ['claude-code' as const],
431
- author: { name: 'Test Author' },
432
- },
433
- },
434
- errors: [],
435
- warnings: [],
436
- };
437
-
438
- const output = formatValidationResult(result);
439
- expect(output).toContain('Discovery metadata');
440
- expect(output).toContain('version: 1.0.0');
441
- expect(output).toContain('category: development');
442
- expect(output).toContain('tags: [test, validation]');
443
- expect(output).toContain('triggers: 1 defined');
444
- expect(output).toContain('surfaces: [claude-code]');
445
- expect(output).toContain('author: Test Author');
446
- });
447
-
448
- it('truncates long descriptions', () => {
449
- const longDescription = 'A'.repeat(100);
450
- const result = {
451
- valid: true,
452
- name: 'test-skill',
453
- path: '/path/to/skill',
454
- frontmatter: {
455
- name: 'test-skill',
456
- description: longDescription,
457
- },
458
- errors: [],
459
- warnings: [],
460
- };
461
-
462
- const output = formatValidationResult(result);
463
- expect(output).toContain('...');
464
- expect(output).toContain('(100 chars)');
465
- });
466
- });
@@ -1,227 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
2
- import { join, basename } from 'path';
3
- import matter from 'gray-matter';
4
- import { SkillFrontmatterSchema } from '../../schemas/generated/skill.js';
5
- import type { SkillFrontmatter } from '../../schemas/generated/skill.js';
6
-
7
- export interface ValidationResult {
8
- valid: boolean;
9
- name: string | null;
10
- path: string;
11
- frontmatter: SkillFrontmatter | null;
12
- errors: string[];
13
- warnings: string[];
14
- }
15
-
16
- /**
17
- * Validate a skill directory against the Agent Skills spec
18
- */
19
- export function validateSkillDirectory(skillPath: string): ValidationResult {
20
- const result: ValidationResult = {
21
- valid: true,
22
- name: null,
23
- path: skillPath,
24
- frontmatter: null,
25
- errors: [],
26
- warnings: [],
27
- };
28
-
29
- // Check directory exists
30
- if (!existsSync(skillPath)) {
31
- result.valid = false;
32
- result.errors.push(`Directory not found: ${skillPath}`);
33
- return result;
34
- }
35
-
36
- const stats = statSync(skillPath);
37
- if (!stats.isDirectory()) {
38
- result.valid = false;
39
- result.errors.push(`Path is not a directory: ${skillPath}`);
40
- return result;
41
- }
42
-
43
- // Check SKILL.md exists
44
- const skillMdPath = join(skillPath, 'SKILL.md');
45
- if (!existsSync(skillMdPath)) {
46
- result.valid = false;
47
- result.errors.push('SKILL.md not found');
48
- return result;
49
- }
50
-
51
- // Read and parse SKILL.md
52
- let content: string;
53
- try {
54
- content = readFileSync(skillMdPath, 'utf-8');
55
- } catch (err) {
56
- result.valid = false;
57
- result.errors.push(`Failed to read SKILL.md: ${err}`);
58
- return result;
59
- }
60
-
61
- // Parse frontmatter
62
- let parsed: matter.GrayMatterFile<string>;
63
- try {
64
- parsed = matter(content);
65
- } catch (err) {
66
- result.valid = false;
67
- result.errors.push(`Failed to parse frontmatter: ${err}`);
68
- return result;
69
- }
70
-
71
- if (!parsed.data || Object.keys(parsed.data).length === 0) {
72
- result.valid = false;
73
- result.errors.push('No frontmatter found in SKILL.md');
74
- return result;
75
- }
76
-
77
- // Validate against schema
78
- const validation = SkillFrontmatterSchema.safeParse(parsed.data);
79
-
80
- if (!validation.success) {
81
- result.valid = false;
82
- for (const issue of validation.error.issues) {
83
- const path = issue.path.join('.');
84
- result.errors.push(`${path}: ${issue.message}`);
85
- }
86
- return result;
87
- }
88
-
89
- result.frontmatter = validation.data;
90
- result.name = validation.data.name;
91
-
92
- // Validate name matches directory name
93
- const dirName = basename(skillPath);
94
- if (validation.data.name !== dirName) {
95
- result.valid = false;
96
- result.errors.push(
97
- `Skill name "${validation.data.name}" does not match directory name "${dirName}"`
98
- );
99
- }
100
-
101
- // Check for optional directories and files
102
- const contents = readdirSync(skillPath);
103
-
104
- // Standard optional directories
105
- const optionalDirs = ['scripts', 'references', 'assets'];
106
- for (const dir of optionalDirs) {
107
- if (contents.includes(dir)) {
108
- const dirPath = join(skillPath, dir);
109
- if (!statSync(dirPath).isDirectory()) {
110
- result.warnings.push(`"${dir}" exists but is not a directory`);
111
- }
112
- }
113
- }
114
-
115
- // Check for discovery metadata (not required, but recommended)
116
- if (!validation.data.metadata) {
117
- result.warnings.push('No metadata field - consider adding for better discovery');
118
- } else {
119
- const meta = validation.data.metadata;
120
- if (!meta.tags || meta.tags.length === 0) {
121
- result.warnings.push('No tags in metadata - consider adding for better discovery');
122
- }
123
- if (!meta.category) {
124
- result.warnings.push('No category in metadata - consider adding for better discovery');
125
- }
126
- if (!meta.version) {
127
- result.warnings.push('No version in metadata - required for registry publishing');
128
- }
129
- }
130
-
131
- return result;
132
- }
133
-
134
- /**
135
- * Format validation result for CLI output
136
- */
137
- export function formatValidationResult(result: ValidationResult): string {
138
- const lines: string[] = [];
139
-
140
- if (result.valid) {
141
- lines.push(`\u2713 Valid: ${result.name || result.path}`);
142
- } else {
143
- lines.push(`\u2717 Invalid: ${result.name || result.path}`);
144
- }
145
-
146
- lines.push('');
147
-
148
- if (result.frontmatter) {
149
- lines.push('\u2713 SKILL.md found');
150
- lines.push('\u2713 Required fields');
151
- lines.push(` \u251c\u2500 name: ${result.frontmatter.name}`);
152
- lines.push(
153
- ` \u2514\u2500 description: ${result.frontmatter.description.slice(0, 60)}${result.frontmatter.description.length > 60 ? '...' : ''} (${result.frontmatter.description.length} chars)`
154
- );
155
-
156
- // Optional fields
157
- const optionalFields: string[] = [];
158
- if (result.frontmatter.license) optionalFields.push(`license: ${result.frontmatter.license}`);
159
- if (result.frontmatter.compatibility)
160
- optionalFields.push(`compatibility: ${result.frontmatter.compatibility}`);
161
- if (result.frontmatter['allowed-tools'])
162
- optionalFields.push(`allowed-tools: ${result.frontmatter['allowed-tools']}`);
163
-
164
- if (optionalFields.length > 0) {
165
- lines.push('');
166
- lines.push('\u2713 Optional fields');
167
- optionalFields.forEach((field, i) => {
168
- const prefix = i === optionalFields.length - 1 ? '\u2514\u2500' : '\u251c\u2500';
169
- lines.push(` ${prefix} ${field}`);
170
- });
171
- }
172
-
173
- // Discovery metadata
174
- if (result.frontmatter.metadata) {
175
- const meta = result.frontmatter.metadata;
176
- lines.push('');
177
- lines.push('\u2713 Discovery metadata (metadata:)');
178
- if (meta.tags) lines.push(` \u251c\u2500 tags: [${meta.tags.join(', ')}]`);
179
- if (meta.category) lines.push(` \u251c\u2500 category: ${meta.category}`);
180
- if (meta.triggers) lines.push(` \u251c\u2500 triggers: ${meta.triggers.length} defined`);
181
- if (meta.version) lines.push(` \u251c\u2500 version: ${meta.version}`);
182
- if (meta.surfaces) lines.push(` \u251c\u2500 surfaces: [${meta.surfaces.join(', ')}]`);
183
- if (meta.author) lines.push(` \u2514\u2500 author: ${meta.author.name}`);
184
- }
185
- }
186
-
187
- if (result.errors.length > 0) {
188
- lines.push('');
189
- lines.push('Errors:');
190
- result.errors.forEach((err) => lines.push(` \u2717 ${err}`));
191
- }
192
-
193
- if (result.warnings.length > 0) {
194
- lines.push('');
195
- lines.push('Warnings:');
196
- result.warnings.forEach((warn) => lines.push(` \u26a0 ${warn}`));
197
- }
198
-
199
- return lines.join('\n');
200
- }
201
-
202
- export interface ValidateOptions {
203
- json?: boolean;
204
- }
205
-
206
- /**
207
- * Handle the validate command
208
- */
209
- export async function handleSkillValidate(
210
- skillPath: string,
211
- options: ValidateOptions
212
- ): Promise<void> {
213
- const result = validateSkillDirectory(skillPath);
214
-
215
- if (options.json) {
216
- console.log(JSON.stringify(result, null, 2));
217
- } else {
218
- console.log('');
219
- console.log(`Validating ${skillPath}...`);
220
- console.log('');
221
- console.log(formatValidationResult(result));
222
- }
223
-
224
- if (!result.valid) {
225
- process.exit(1);
226
- }
227
- }
package/src/index.ts DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { createProgram } from './program.js';
4
- import { handleError } from './utils/errors.js';
5
-
6
- async function main() {
7
- const program = createProgram();
8
- await program.parseAsync(process.argv);
9
- }
10
-
11
- main().catch(handleError);