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 +21 -0
- package/README.md +171 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +12 -0
- package/dist/commands/scaffold.d.ts +3 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/scaffold.js +147 -0
- package/dist/generators/scaffold.d.ts +37 -0
- package/dist/generators/scaffold.d.ts.map +1 -0
- package/dist/generators/scaffold.js +161 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/templates/skill-md.d.ts +15 -0
- package/dist/templates/skill-md.d.ts.map +1 -0
- package/dist/templates/skill-md.js +150 -0
- package/dist/utils/errors.d.ts +28 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +44 -0
- package/dist/utils/output.d.ts +40 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +86 -0
- package/dist/validators/description.d.ts +11 -0
- package/dist/validators/description.d.ts.map +1 -0
- package/dist/validators/description.js +36 -0
- package/dist/validators/frontmatter.d.ts +13 -0
- package/dist/validators/frontmatter.d.ts.map +1 -0
- package/dist/validators/frontmatter.js +37 -0
- package/dist/validators/index.d.ts +7 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/index.js +12 -0
- package/dist/validators/name.d.ts +14 -0
- package/dist/validators/name.d.ts.map +1 -0
- package/dist/validators/name.js +49 -0
- package/package.json +75 -0
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 @@
|
|
|
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 @@
|
|
|
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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,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 @@
|
|
|
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
|
+
}
|