ai-skills-manager 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 lwndev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # AI Skills Manager
2
+
3
+ AI Skills Manager (ASM) enables team members to create, test, distribute, install, update, and remove skills. It focuses on the [Claude Code Agent Skills](https://docs.claude.com/en/docs/claude-code/skills) system developed by Anthropic.
4
+
5
+ ## Installation
6
+
7
+ ### Prerequisites
8
+
9
+ - [Node.js](https://nodejs.org/) 18 or later
10
+ - [npm](https://www.npmjs.com/) (comes with Node.js)
11
+
12
+ ### From Source
13
+
14
+ ```bash
15
+ # Clone the repository
16
+ git clone https://github.com/lwndev/ai-skills-manager.git
17
+ cd ai-skills-manager
18
+
19
+ # Install dependencies
20
+ npm install
21
+
22
+ # Build the project
23
+ npm run build
24
+
25
+ # Link the CLI globally (optional)
26
+ npm link
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ### Create a New Skill
32
+
33
+ Use the `scaffold` command to create a new Claude Code skill:
34
+
35
+ ```bash
36
+ # Create a basic skill in .claude/skills/ (project scope)
37
+ asm scaffold my-skill
38
+
39
+ # Create a skill with a description
40
+ asm scaffold my-skill --description "A helpful skill for code reviews"
41
+
42
+ # Create a personal skill in ~/.claude/skills/
43
+ asm scaffold my-skill --personal
44
+
45
+ # Create a skill in a custom location
46
+ asm scaffold my-skill --output ./custom/path
47
+
48
+ # Specify allowed tools
49
+ asm scaffold my-skill --allowed-tools "Read,Write,Bash"
50
+
51
+ # Overwrite existing directory without prompting
52
+ asm scaffold my-skill --force
53
+ ```
54
+
55
+ #### Scaffold Options
56
+
57
+ | Option | Description |
58
+ |--------|-------------|
59
+ | `-d, --description <text>` | Short description of the skill |
60
+ | `-o, --output <path>` | Output directory path (overrides --project and --personal) |
61
+ | `-p, --project` | Create as a project skill in `.claude/skills/` (default) |
62
+ | `--personal` | Create as a personal skill in `~/.claude/skills/` |
63
+ | `-a, --allowed-tools <tools>` | Comma-separated list of allowed tools |
64
+ | `-f, --force` | Overwrite existing directory without prompting |
65
+
66
+ #### Skill Name Requirements
67
+
68
+ - Lowercase letters, numbers, and hyphens only
69
+ - Cannot start or end with a hyphen
70
+ - Cannot contain consecutive hyphens
71
+ - Maximum 64 characters
72
+ - Cannot use reserved words: "anthropic", "claude"
73
+
74
+ #### Generated Structure
75
+
76
+ ```
77
+ my-skill/
78
+ SKILL.md # Skill instructions and metadata
79
+ scripts/ # Directory for skill scripts
80
+ .gitkeep
81
+ ```
82
+
83
+ ### Install a Skill
84
+
85
+ *Coming soon*
86
+
87
+ ### Update a Skill
88
+
89
+ *Coming soon*
90
+
91
+ ### Remove a Skill
92
+
93
+ *Coming soon*
94
+
95
+ ## Development
96
+
97
+ ### Commands
98
+
99
+ ```bash
100
+ # Install dependencies
101
+ npm install
102
+
103
+ # Build the project
104
+ npm run build
105
+
106
+ # Run tests
107
+ npm test
108
+
109
+ # Run tests with coverage
110
+ npm run test:coverage
111
+
112
+ # Run linting
113
+ npm run lint
114
+
115
+ # Run all quality checks
116
+ npm run quality
117
+ ```
118
+
119
+ ### Project Structure
120
+
121
+ ```
122
+ src/
123
+ cli.ts # CLI entry point
124
+ commands/ # Command implementations
125
+ generators/ # File/directory generation
126
+ templates/ # Template generation
127
+ validators/ # Input validation
128
+ utils/ # Shared utilities
129
+
130
+ tests/
131
+ unit/ # Unit tests (mirrors src/ structure)
132
+ generators/
133
+ templates/
134
+ utils/
135
+ validators/
136
+ integration/ # End-to-end tests
137
+ ```
138
+
139
+ ## Contributing
140
+
141
+ Contributions are welcome! Please ensure:
142
+
143
+ 1. Tests pass: `npm test`
144
+ 2. Code is linted: `npm run lint`
145
+ 3. Coverage is maintained above 80%
146
+
147
+ ## FAQs
148
+
149
+ **Q: Where are project skills stored?**
150
+ A: In `.claude/skills/` within your project directory.
151
+
152
+ **Q: Where are personal skills stored?**
153
+ A: In `~/.claude/skills/` in your home directory.
154
+
155
+ **Q: How do I test my skill?**
156
+ A: After creating a skill, invoke it in Claude Code by name. Claude will discover skills in the standard locations.
157
+
158
+ ## Troubleshooting
159
+
160
+ **Error: Invalid skill name**
161
+ Ensure your skill name uses only lowercase letters, numbers, and hyphens. It cannot start or end with a hyphen.
162
+
163
+ **Error: Directory already exists**
164
+ Use the `--force` flag to overwrite, or choose a different name.
165
+
166
+ **Command not found: asm**
167
+ Run `npm link` after building, or use `node dist/cli.js` directly.
168
+
169
+ ## License
170
+
171
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const scaffold_1 = require("./commands/scaffold");
6
+ const program = new commander_1.Command();
7
+ program
8
+ .name('asm')
9
+ .description('AI Skills Manager - CLI tool for managing Claude Code Agent Skills')
10
+ .version('1.0.0');
11
+ (0, scaffold_1.registerScaffoldCommand)(program);
12
+ program.parse();
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerScaffoldCommand(program: Command): void;
3
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/commands/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmC9D"}
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.registerScaffoldCommand = registerScaffoldCommand;
46
+ const validators_1 = require("../validators");
47
+ const scaffold_1 = require("../generators/scaffold");
48
+ const errors_1 = require("../utils/errors");
49
+ const output = __importStar(require("../utils/output"));
50
+ function registerScaffoldCommand(program) {
51
+ program
52
+ .command('scaffold <name>')
53
+ .description('Create a new Claude Code skill with the required directory structure')
54
+ .option('-d, --description <description>', 'Short description of the skill')
55
+ .option('-o, --output <path>', 'Output directory path (overrides --project and --personal)')
56
+ .option('-p, --project', 'Create as a project skill in .claude/skills/')
57
+ .option('--personal', 'Create as a personal skill in ~/.claude/skills/')
58
+ .option('-a, --allowed-tools <tools>', 'Comma-separated list of allowed tools')
59
+ .option('-f, --force', 'Overwrite existing directory without prompting')
60
+ .addHelpText('after', `
61
+ Examples:
62
+ $ asm scaffold my-skill
63
+ $ asm scaffold my-skill --description "A helpful skill"
64
+ $ asm scaffold my-skill --project --description "Project-specific skill"
65
+ $ asm scaffold my-skill --personal --description "Personal skill"
66
+ $ asm scaffold my-skill --output ./custom/path
67
+ $ asm scaffold my-skill --allowed-tools "Read,Write,Bash"
68
+ $ asm scaffold my-skill --force
69
+
70
+ Note:
71
+ By default, skills are created in .claude/skills/ (project scope).
72
+ Use --personal to create in ~/.claude/skills/ (user scope).
73
+ Use --output to specify a custom directory.`)
74
+ .action((name, options) => __awaiter(this, void 0, void 0, function* () {
75
+ try {
76
+ yield handleScaffold(name, options);
77
+ }
78
+ catch (error) {
79
+ handleError(error);
80
+ process.exit(1);
81
+ }
82
+ }));
83
+ }
84
+ function handleScaffold(name, options) {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ // Validate skill name
87
+ const nameValidation = (0, validators_1.validateName)(name);
88
+ if (!nameValidation.valid) {
89
+ throw new errors_1.ValidationError(nameValidation.error || 'Invalid skill name');
90
+ }
91
+ // Validate description if provided
92
+ if (options.description) {
93
+ const descValidation = (0, validators_1.validateDescription)(options.description);
94
+ if (!descValidation.valid) {
95
+ throw new errors_1.ValidationError(descValidation.error || 'Invalid description');
96
+ }
97
+ }
98
+ // Parse allowed tools if provided
99
+ let allowedTools;
100
+ if (options.allowedTools) {
101
+ allowedTools = options.allowedTools
102
+ .split(',')
103
+ .map((tool) => tool.trim())
104
+ .filter((tool) => tool.length > 0);
105
+ if (allowedTools.length === 0) {
106
+ throw new errors_1.ValidationError('Allowed tools list cannot be empty');
107
+ }
108
+ }
109
+ // Create the scaffold
110
+ const result = yield (0, scaffold_1.createScaffold)({
111
+ name,
112
+ description: options.description,
113
+ allowedTools,
114
+ output: options.output,
115
+ project: options.project,
116
+ personal: options.personal,
117
+ force: options.force,
118
+ });
119
+ // Handle the result
120
+ if (!result.success) {
121
+ if (result.error === 'Operation cancelled by user') {
122
+ throw new errors_1.UserCancelledError();
123
+ }
124
+ throw new errors_1.FileSystemError(result.error || 'Unknown error occurred');
125
+ }
126
+ // Display success message
127
+ output.displayCreatedFiles(result.skillPath, result.filesCreated);
128
+ output.displayNextSteps(result.skillPath, name);
129
+ });
130
+ }
131
+ function handleError(error) {
132
+ if (error instanceof errors_1.ValidationError) {
133
+ output.displayValidationError('input', error.message);
134
+ }
135
+ else if (error instanceof errors_1.UserCancelledError) {
136
+ output.displayError(error.message);
137
+ }
138
+ else if (error instanceof errors_1.FileSystemError) {
139
+ output.displayError('File system error', error.message);
140
+ }
141
+ else if (error instanceof Error) {
142
+ output.displayError('Unexpected error', error.message);
143
+ }
144
+ else {
145
+ output.displayError('An unexpected error occurred', String(error));
146
+ }
147
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Scaffold generator
3
+ *
4
+ * Creates the directory structure and files for a new skill.
5
+ */
6
+ export interface ScaffoldOptions {
7
+ name: string;
8
+ description?: string;
9
+ allowedTools?: string[];
10
+ output?: string;
11
+ project?: boolean;
12
+ personal?: boolean;
13
+ force?: boolean;
14
+ }
15
+ export interface ScaffoldResult {
16
+ success: boolean;
17
+ skillPath: string;
18
+ filesCreated: string[];
19
+ error?: string;
20
+ }
21
+ /**
22
+ * Resolve the output directory based on options
23
+ */
24
+ export declare function resolveOutputPath(options: ScaffoldOptions): string;
25
+ /**
26
+ * Check if a directory exists
27
+ */
28
+ export declare function directoryExists(dirPath: string): Promise<boolean>;
29
+ /**
30
+ * Prompt user for confirmation (used when directory exists)
31
+ */
32
+ export declare function promptConfirmation(message: string): Promise<boolean>;
33
+ /**
34
+ * Create the skill scaffold
35
+ */
36
+ export declare function createScaffold(options: ScaffoldOptions): Promise<ScaffoldResult>;
37
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/generators/scaffold.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAalE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOvE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAY1E;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAyDtF"}
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ /**
3
+ * Scaffold generator
4
+ *
5
+ * Creates the directory structure and files for a new skill.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
41
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
42
+ return new (P || (P = Promise))(function (resolve, reject) {
43
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
44
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
45
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
46
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
47
+ });
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.resolveOutputPath = resolveOutputPath;
51
+ exports.directoryExists = directoryExists;
52
+ exports.promptConfirmation = promptConfirmation;
53
+ exports.createScaffold = createScaffold;
54
+ const fs = __importStar(require("fs/promises"));
55
+ const path = __importStar(require("path"));
56
+ const os = __importStar(require("os"));
57
+ const readline = __importStar(require("readline"));
58
+ const skill_md_1 = require("../templates/skill-md");
59
+ /**
60
+ * Resolve the output directory based on options
61
+ */
62
+ function resolveOutputPath(options) {
63
+ // --output takes precedence
64
+ if (options.output) {
65
+ return path.resolve(options.output, options.name);
66
+ }
67
+ // --personal uses ~/.claude/skills/
68
+ if (options.personal) {
69
+ return path.join(os.homedir(), '.claude', 'skills', options.name);
70
+ }
71
+ // --project (default) uses .claude/skills/ in current directory
72
+ return path.join(process.cwd(), '.claude', 'skills', options.name);
73
+ }
74
+ /**
75
+ * Check if a directory exists
76
+ */
77
+ function directoryExists(dirPath) {
78
+ return __awaiter(this, void 0, void 0, function* () {
79
+ try {
80
+ const stat = yield fs.stat(dirPath);
81
+ return stat.isDirectory();
82
+ }
83
+ catch (_a) {
84
+ return false;
85
+ }
86
+ });
87
+ }
88
+ /**
89
+ * Prompt user for confirmation (used when directory exists)
90
+ */
91
+ function promptConfirmation(message) {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ const rl = readline.createInterface({
94
+ input: process.stdin,
95
+ output: process.stdout,
96
+ });
97
+ return new Promise((resolve) => {
98
+ rl.question(`${message} (y/N): `, (answer) => {
99
+ rl.close();
100
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
101
+ });
102
+ });
103
+ });
104
+ }
105
+ /**
106
+ * Create the skill scaffold
107
+ */
108
+ function createScaffold(options) {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ const skillPath = resolveOutputPath(options);
111
+ const filesCreated = [];
112
+ // Check if directory already exists
113
+ if (yield directoryExists(skillPath)) {
114
+ if (!options.force) {
115
+ const confirmed = yield promptConfirmation(`Directory "${skillPath}" already exists. Overwrite?`);
116
+ if (!confirmed) {
117
+ return {
118
+ success: false,
119
+ skillPath,
120
+ filesCreated: [],
121
+ error: 'Operation cancelled by user',
122
+ };
123
+ }
124
+ }
125
+ }
126
+ try {
127
+ // Create skill directory
128
+ yield fs.mkdir(skillPath, { recursive: true });
129
+ // Create scripts directory with .gitkeep
130
+ const scriptsPath = path.join(skillPath, 'scripts');
131
+ yield fs.mkdir(scriptsPath, { recursive: true });
132
+ const gitkeepPath = path.join(scriptsPath, '.gitkeep');
133
+ yield fs.writeFile(gitkeepPath, '');
134
+ filesCreated.push(gitkeepPath);
135
+ // Generate and write SKILL.md
136
+ const templateParams = {
137
+ name: options.name,
138
+ description: options.description,
139
+ allowedTools: options.allowedTools,
140
+ };
141
+ const skillMdContent = (0, skill_md_1.generateSkillMd)(templateParams);
142
+ const skillMdPath = path.join(skillPath, 'SKILL.md');
143
+ yield fs.writeFile(skillMdPath, skillMdContent, 'utf-8');
144
+ filesCreated.push(skillMdPath);
145
+ return {
146
+ success: true,
147
+ skillPath,
148
+ filesCreated,
149
+ };
150
+ }
151
+ catch (error) {
152
+ const errorMessage = error instanceof Error ? error.message : String(error);
153
+ return {
154
+ success: false,
155
+ skillPath,
156
+ filesCreated,
157
+ error: `Failed to create scaffold: ${errorMessage}`,
158
+ };
159
+ }
160
+ });
161
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ console.log('Happy developing ✨');
@@ -0,0 +1,15 @@
1
+ /**
2
+ * SKILL.md template generator
3
+ *
4
+ * Generates the SKILL.md file content with YAML frontmatter and markdown body.
5
+ */
6
+ export interface SkillTemplateParams {
7
+ name: string;
8
+ description?: string;
9
+ allowedTools?: string[];
10
+ }
11
+ /**
12
+ * Generate the complete SKILL.md content
13
+ */
14
+ export declare function generateSkillMd(params: SkillTemplateParams): string;
15
+ //# sourceMappingURL=skill-md.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-md.d.ts","sourceRoot":"","sources":["../../src/templates/skill-md.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAgJD;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAInE"}
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ /**
3
+ * SKILL.md template generator
4
+ *
5
+ * Generates the SKILL.md file content with YAML frontmatter and markdown body.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.generateSkillMd = generateSkillMd;
9
+ /**
10
+ * Escape a string for use in YAML values.
11
+ * Wraps in double quotes if it contains special characters.
12
+ */
13
+ function escapeYamlString(value) {
14
+ const needsQuotes = value.includes(':') ||
15
+ value.includes('#') ||
16
+ value.includes("'") ||
17
+ value.includes('"') ||
18
+ value.includes('\n') ||
19
+ value.includes('\\') ||
20
+ value.startsWith(' ') ||
21
+ value.endsWith(' ') ||
22
+ value.startsWith('-') ||
23
+ value.startsWith('[') ||
24
+ value.startsWith('{') ||
25
+ /^(true|false|null|yes|no|on|off)$/i.test(value);
26
+ if (!needsQuotes) {
27
+ return value;
28
+ }
29
+ // Use double quotes and escape internal double quotes and backslashes
30
+ const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
31
+ return `"${escaped}"`;
32
+ }
33
+ /**
34
+ * Generate YAML frontmatter for the skill
35
+ */
36
+ function generateFrontmatter(params) {
37
+ const lines = ['---'];
38
+ lines.push(`name: ${escapeYamlString(params.name)}`);
39
+ if (params.description) {
40
+ lines.push(`description: ${escapeYamlString(params.description)}`);
41
+ }
42
+ else {
43
+ lines.push('description: "TODO: Add a short description of what this skill does"');
44
+ }
45
+ if (params.allowedTools && params.allowedTools.length > 0) {
46
+ lines.push('allowed-tools:');
47
+ for (const tool of params.allowedTools) {
48
+ lines.push(` - ${escapeYamlString(tool.trim())}`);
49
+ }
50
+ }
51
+ else {
52
+ lines.push('# allowed-tools:');
53
+ lines.push('# - Bash');
54
+ lines.push('# - Read');
55
+ lines.push('# - Write');
56
+ lines.push('# - Edit');
57
+ lines.push('# - Glob');
58
+ lines.push('# - Grep');
59
+ }
60
+ lines.push('---');
61
+ return lines.join('\n');
62
+ }
63
+ /**
64
+ * Generate the markdown body with guidance and TODO placeholders
65
+ */
66
+ function generateBody(params) {
67
+ return `
68
+ # ${params.name}
69
+
70
+ ## Overview
71
+
72
+ TODO: Describe what this skill does and when Claude should use it.
73
+
74
+ ## Usage
75
+
76
+ TODO: Explain how to invoke this skill and any required context.
77
+
78
+ ## Examples
79
+
80
+ TODO: Provide examples of prompts that trigger this skill.
81
+
82
+ ### Example 1
83
+
84
+ \`\`\`
85
+ User: [example prompt]
86
+ Claude: [expected behavior]
87
+ \`\`\`
88
+
89
+ ## Implementation Notes
90
+
91
+ TODO: Add any implementation details, edge cases, or important considerations.
92
+
93
+ <!--
94
+ ================================================================================
95
+ SKILL DEVELOPMENT GUIDANCE
96
+ ================================================================================
97
+
98
+ This file defines a Claude Code skill. Skills are markdown files with YAML
99
+ frontmatter that teach Claude how to perform specific tasks.
100
+
101
+ FRONTMATTER FIELDS:
102
+ - name: (required) Unique identifier for this skill
103
+ - description: (required) Brief description shown in skill listings. This is the
104
+ PRIMARY triggering mechanism - include all "when to use" information here.
105
+ - allowed-tools: (optional) List of tools this skill can use
106
+ - license: (optional) License for the skill (e.g., "MIT", "Apache-2.0")
107
+
108
+ BEST PRACTICES:
109
+ 1. Keep skills focused on a single task or related set of tasks
110
+ 2. Put ALL trigger conditions in the description field, not the body
111
+ 3. Provide clear examples of expected behavior
112
+ 4. Include edge cases and error handling guidance
113
+ 5. Keep the total skill file under 500 lines for optimal performance
114
+
115
+ DESCRIPTION PATTERNS:
116
+ Use these patterns in your description for reliable triggering:
117
+ - "Use when the user wants to..."
118
+ - "Apply this skill for..."
119
+ - "This skill should be used when..."
120
+
121
+ ALLOWED TOOLS:
122
+ Common tools you can specify in allowed-tools:
123
+ - Bash: Execute shell commands
124
+ - Read: Read file contents
125
+ - Write: Write/create files
126
+ - Edit: Edit existing files
127
+ - Glob: Find files by pattern
128
+ - Grep: Search file contents
129
+ - WebFetch: Fetch web content
130
+ - WebSearch: Search the web
131
+
132
+ If no allowed-tools are specified, the skill inherits default tool access.
133
+
134
+ SCRIPTS DIRECTORY:
135
+ The scripts/ subdirectory can contain helper scripts that your skill
136
+ references. These are executed via the Bash tool when needed.
137
+
138
+ For more information, see: https://docs.anthropic.com/en/docs/claude-code
139
+ ================================================================================
140
+ -->
141
+ `;
142
+ }
143
+ /**
144
+ * Generate the complete SKILL.md content
145
+ */
146
+ function generateSkillMd(params) {
147
+ const frontmatter = generateFrontmatter(params);
148
+ const body = generateBody(params);
149
+ return frontmatter + '\n' + body;
150
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Error types for the AI Skills Manager CLI
3
+ */
4
+ /**
5
+ * Base error class for ASM errors
6
+ */
7
+ export declare class ASMError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /**
11
+ * Validation error - thrown when user input fails validation
12
+ */
13
+ export declare class ValidationError extends ASMError {
14
+ constructor(message: string);
15
+ }
16
+ /**
17
+ * File system error - thrown when file operations fail
18
+ */
19
+ export declare class FileSystemError extends ASMError {
20
+ constructor(message: string);
21
+ }
22
+ /**
23
+ * User cancelled error - thrown when user cancels an operation
24
+ */
25
+ export declare class UserCancelledError extends ASMError {
26
+ constructor(message?: string);
27
+ }
28
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,qBAAa,QAAS,SAAQ,KAAK;gBACrB,OAAO,EAAE,MAAM;CAK5B;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,QAAQ;gBAC/B,OAAO,EAAE,MAAM;CAG5B;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,QAAQ;gBAC/B,OAAO,EAAE,MAAM;CAG5B;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,QAAQ;gBAClC,OAAO,GAAE,MAAsC;CAG5D"}
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * Error types for the AI Skills Manager CLI
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.UserCancelledError = exports.FileSystemError = exports.ValidationError = exports.ASMError = void 0;
7
+ /**
8
+ * Base error class for ASM errors
9
+ */
10
+ class ASMError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = this.constructor.name;
14
+ Error.captureStackTrace(this, this.constructor);
15
+ }
16
+ }
17
+ exports.ASMError = ASMError;
18
+ /**
19
+ * Validation error - thrown when user input fails validation
20
+ */
21
+ class ValidationError extends ASMError {
22
+ constructor(message) {
23
+ super(message);
24
+ }
25
+ }
26
+ exports.ValidationError = ValidationError;
27
+ /**
28
+ * File system error - thrown when file operations fail
29
+ */
30
+ class FileSystemError extends ASMError {
31
+ constructor(message) {
32
+ super(message);
33
+ }
34
+ }
35
+ exports.FileSystemError = FileSystemError;
36
+ /**
37
+ * User cancelled error - thrown when user cancels an operation
38
+ */
39
+ class UserCancelledError extends ASMError {
40
+ constructor(message = 'Operation cancelled by user') {
41
+ super(message);
42
+ }
43
+ }
44
+ exports.UserCancelledError = UserCancelledError;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Output formatting utilities for the CLI
3
+ */
4
+ /**
5
+ * Format a success message with a checkmark
6
+ */
7
+ export declare function success(message: string): string;
8
+ /**
9
+ * Format an error message
10
+ */
11
+ export declare function error(message: string): string;
12
+ /**
13
+ * Format a warning message
14
+ */
15
+ export declare function warning(message: string): string;
16
+ /**
17
+ * Format an info message
18
+ */
19
+ export declare function info(message: string): string;
20
+ /**
21
+ * Format a file path display
22
+ */
23
+ export declare function filePath(path: string): string;
24
+ /**
25
+ * Display created files structure
26
+ */
27
+ export declare function displayCreatedFiles(skillPath: string, filesCreated: string[]): void;
28
+ /**
29
+ * Display next steps after successful scaffold
30
+ */
31
+ export declare function displayNextSteps(skillPath: string, _skillName: string): void;
32
+ /**
33
+ * Display error with context
34
+ */
35
+ export declare function displayError(message: string, context?: string): void;
36
+ /**
37
+ * Display validation error with examples
38
+ */
39
+ export declare function displayValidationError(field: string, errorMessage: string): void;
40
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,CASnF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAS5E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAKpE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAGhF"}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * Output formatting utilities for the CLI
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.success = success;
7
+ exports.error = error;
8
+ exports.warning = warning;
9
+ exports.info = info;
10
+ exports.filePath = filePath;
11
+ exports.displayCreatedFiles = displayCreatedFiles;
12
+ exports.displayNextSteps = displayNextSteps;
13
+ exports.displayError = displayError;
14
+ exports.displayValidationError = displayValidationError;
15
+ /**
16
+ * Format a success message with a checkmark
17
+ */
18
+ function success(message) {
19
+ return `✓ ${message}`;
20
+ }
21
+ /**
22
+ * Format an error message
23
+ */
24
+ function error(message) {
25
+ return `✗ Error: ${message}`;
26
+ }
27
+ /**
28
+ * Format a warning message
29
+ */
30
+ function warning(message) {
31
+ return `⚠ ${message}`;
32
+ }
33
+ /**
34
+ * Format an info message
35
+ */
36
+ function info(message) {
37
+ return `ℹ ${message}`;
38
+ }
39
+ /**
40
+ * Format a file path display
41
+ */
42
+ function filePath(path) {
43
+ return ` ${path}`;
44
+ }
45
+ /**
46
+ * Display created files structure
47
+ */
48
+ function displayCreatedFiles(skillPath, filesCreated) {
49
+ console.log(success('Skill scaffolded successfully!'));
50
+ console.log('\nCreated:');
51
+ console.log(filePath(skillPath + '/'));
52
+ for (const file of filesCreated) {
53
+ // Show relative path from skill directory
54
+ const relativePath = file.replace(skillPath + '/', '');
55
+ console.log(filePath(' ' + relativePath));
56
+ }
57
+ }
58
+ /**
59
+ * Display next steps after successful scaffold
60
+ */
61
+ function displayNextSteps(skillPath, _skillName) {
62
+ console.log('\nNext steps:');
63
+ console.log(' 1. Edit the SKILL.md file to add your skill instructions');
64
+ console.log(' 2. Add any scripts to the scripts/ directory');
65
+ console.log(' 3. Test your skill by invoking it in Claude Code');
66
+ console.log('\nDocumentation:');
67
+ console.log(' https://docs.claude.com/en/docs/claude-code/skills');
68
+ console.log('\nSkill location:');
69
+ console.log(filePath(skillPath));
70
+ }
71
+ /**
72
+ * Display error with context
73
+ */
74
+ function displayError(message, context) {
75
+ console.error(error(message));
76
+ if (context) {
77
+ console.error(` ${context}`);
78
+ }
79
+ }
80
+ /**
81
+ * Display validation error with examples
82
+ */
83
+ function displayValidationError(field, errorMessage) {
84
+ console.error(error(`Invalid ${field}`));
85
+ console.error(` ${errorMessage}`);
86
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Skill description validation
3
+ *
4
+ * Rules:
5
+ * - Non-empty
6
+ * - Maximum length: 1024 characters
7
+ * - Cannot contain angle brackets (< or >) to prevent XML injection
8
+ */
9
+ import { ValidationResult } from './name';
10
+ export declare function validateDescription(description: string): ValidationResult;
11
+ //# sourceMappingURL=description.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"description.d.ts","sourceRoot":"","sources":["../../src/validators/description.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAI1C,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CA0BzE"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * Skill description validation
4
+ *
5
+ * Rules:
6
+ * - Non-empty
7
+ * - Maximum length: 1024 characters
8
+ * - Cannot contain angle brackets (< or >) to prevent XML injection
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.validateDescription = validateDescription;
12
+ const MAX_LENGTH = 1024;
13
+ function validateDescription(description) {
14
+ // Check for empty description
15
+ if (!description || description.trim() === '') {
16
+ return {
17
+ valid: false,
18
+ error: 'Description cannot be empty',
19
+ };
20
+ }
21
+ // Check maximum length
22
+ if (description.length > MAX_LENGTH) {
23
+ return {
24
+ valid: false,
25
+ error: `Description must be ${MAX_LENGTH} characters or less (got ${description.length})`,
26
+ };
27
+ }
28
+ // Check for angle brackets (XML tag prevention)
29
+ if (description.includes('<') || description.includes('>')) {
30
+ return {
31
+ valid: false,
32
+ error: 'Description cannot contain angle brackets (< or >)',
33
+ };
34
+ }
35
+ return { valid: true };
36
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Frontmatter key validation
3
+ *
4
+ * Rules:
5
+ * - Only allowed top-level keys: name, description, license, allowed-tools, metadata
6
+ * - Reject any unexpected top-level keys
7
+ */
8
+ import { ValidationResult } from './name';
9
+ export interface FrontmatterData {
10
+ [key: string]: unknown;
11
+ }
12
+ export declare function validateFrontmatterKeys(frontmatter: FrontmatterData): ValidationResult;
13
+ //# sourceMappingURL=frontmatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/validators/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAU1C,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,eAAe,GAC3B,gBAAgB,CAwBlB"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /**
3
+ * Frontmatter key validation
4
+ *
5
+ * Rules:
6
+ * - Only allowed top-level keys: name, description, license, allowed-tools, metadata
7
+ * - Reject any unexpected top-level keys
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.validateFrontmatterKeys = validateFrontmatterKeys;
11
+ const ALLOWED_KEYS = new Set([
12
+ 'name',
13
+ 'description',
14
+ 'license',
15
+ 'allowed-tools',
16
+ 'metadata',
17
+ ]);
18
+ function validateFrontmatterKeys(frontmatter) {
19
+ const keys = Object.keys(frontmatter);
20
+ // Check for empty frontmatter
21
+ if (keys.length === 0) {
22
+ return {
23
+ valid: false,
24
+ error: 'Frontmatter cannot be empty',
25
+ };
26
+ }
27
+ // Check for unexpected keys
28
+ const unexpectedKeys = keys.filter((key) => !ALLOWED_KEYS.has(key));
29
+ if (unexpectedKeys.length > 0) {
30
+ return {
31
+ valid: false,
32
+ error: `Unexpected frontmatter keys: ${unexpectedKeys.join(', ')}. ` +
33
+ `Allowed keys are: ${Array.from(ALLOWED_KEYS).join(', ')}`,
34
+ };
35
+ }
36
+ return { valid: true };
37
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Validators barrel export
3
+ */
4
+ export { ValidationResult, validateName } from './name';
5
+ export { validateDescription } from './description';
6
+ export { FrontmatterData, validateFrontmatterKeys } from './frontmatter';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validators/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * Validators barrel export
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateFrontmatterKeys = exports.validateDescription = exports.validateName = void 0;
7
+ var name_1 = require("./name");
8
+ Object.defineProperty(exports, "validateName", { enumerable: true, get: function () { return name_1.validateName; } });
9
+ var description_1 = require("./description");
10
+ Object.defineProperty(exports, "validateDescription", { enumerable: true, get: function () { return description_1.validateDescription; } });
11
+ var frontmatter_1 = require("./frontmatter");
12
+ Object.defineProperty(exports, "validateFrontmatterKeys", { enumerable: true, get: function () { return frontmatter_1.validateFrontmatterKeys; } });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Skill name validation
3
+ *
4
+ * Rules:
5
+ * - Pattern: ^[a-z0-9]+(-[a-z0-9]+)*$ (lowercase alphanumeric with hyphens, no leading/trailing/consecutive hyphens)
6
+ * - Maximum length: 64 characters
7
+ * - Reserved words: "anthropic", "claude" (cannot be used as the name or as part of the name)
8
+ */
9
+ export interface ValidationResult {
10
+ valid: boolean;
11
+ error?: string;
12
+ }
13
+ export declare function validateName(name: string): ValidationResult;
14
+ //# sourceMappingURL=name.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"name.d.ts","sourceRoot":"","sources":["../../src/validators/name.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAuC3D"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * Skill name validation
4
+ *
5
+ * Rules:
6
+ * - Pattern: ^[a-z0-9]+(-[a-z0-9]+)*$ (lowercase alphanumeric with hyphens, no leading/trailing/consecutive hyphens)
7
+ * - Maximum length: 64 characters
8
+ * - Reserved words: "anthropic", "claude" (cannot be used as the name or as part of the name)
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.validateName = validateName;
12
+ const NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
13
+ const MAX_LENGTH = 64;
14
+ const RESERVED_WORDS = ['anthropic', 'claude'];
15
+ function validateName(name) {
16
+ // Check for empty name
17
+ if (!name || name.trim() === '') {
18
+ return {
19
+ valid: false,
20
+ error: 'Skill name cannot be empty',
21
+ };
22
+ }
23
+ // Check maximum length
24
+ if (name.length > MAX_LENGTH) {
25
+ return {
26
+ valid: false,
27
+ error: `Skill name must be ${MAX_LENGTH} characters or less (got ${name.length})`,
28
+ };
29
+ }
30
+ // Check pattern (lowercase alphanumeric with hyphens)
31
+ if (!NAME_PATTERN.test(name)) {
32
+ return {
33
+ valid: false,
34
+ error: 'Skill name must contain only lowercase letters, numbers, and hyphens. ' +
35
+ 'Cannot start or end with a hyphen, or have consecutive hyphens. ' +
36
+ `Example: "my-skill-name" (got "${name}")`,
37
+ };
38
+ }
39
+ // Check reserved words
40
+ for (const reserved of RESERVED_WORDS) {
41
+ if (name === reserved || name.includes(reserved)) {
42
+ return {
43
+ valid: false,
44
+ error: `Skill name cannot contain reserved word "${reserved}"`,
45
+ };
46
+ }
47
+ }
48
+ return { valid: true };
49
+ }
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "ai-skills-manager",
3
+ "version": "1.0.0",
4
+ "description": "AI Skills Manager - CLI tool for managing Claude Code Agent Skills",
5
+ "author": "lwndev",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/lwndev/ai-skills-manager.git"
10
+ },
11
+ "homepage": "https://github.com/lwndev/ai-skills-manager#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/lwndev/ai-skills-manager/issues"
14
+ },
15
+ "keywords": [
16
+ "claude",
17
+ "claude-code",
18
+ "ai",
19
+ "skills",
20
+ "cli",
21
+ "anthropic"
22
+ ],
23
+ "main": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "bin": {
26
+ "asm": "./dist/cli.js"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "engines": {
34
+ "node": ">=18.0.0"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "test": "jest",
39
+ "test:watch": "jest --watch",
40
+ "test:coverage": "jest --coverage",
41
+ "lint": "eslint src tests",
42
+ "lint:fix": "eslint src tests --fix",
43
+ "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
44
+ "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
45
+ "audit": "npm audit --audit-level=moderate",
46
+ "audit:fix": "npm audit fix",
47
+ "quality": "npm run lint && npm run test:coverage && npm run audit",
48
+ "prepare": "husky",
49
+ "prepublishOnly": "npm run quality && npm run build",
50
+ "preversion": "npm run build && npm test && npm run lint",
51
+ "version": "git add package.json package-lock.json",
52
+ "postversion": "git push && git push --tags",
53
+ "release:patch": "npm version patch",
54
+ "release:minor": "npm version minor",
55
+ "release:major": "npm version major"
56
+ },
57
+ "dependencies": {
58
+ "commander": "^14.0.2"
59
+ },
60
+ "devDependencies": {
61
+ "@eslint/js": "^9.39.2",
62
+ "@types/jest": "^30.0.0",
63
+ "@types/node": "^20.17.10",
64
+ "@typescript-eslint/eslint-plugin": "^8.50.1",
65
+ "@typescript-eslint/parser": "^8.50.1",
66
+ "eslint": "^9.39.2",
67
+ "eslint-config-prettier": "^10.1.8",
68
+ "husky": "^9.1.7",
69
+ "jest": "^30.2.0",
70
+ "lint-staged": "^16.2.7",
71
+ "prettier": "^3.7.4",
72
+ "ts-jest": "^29.4.6",
73
+ "typescript": "^5.5.3"
74
+ }
75
+ }