codeskill 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/bin/codeskill.js +691 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# codeskill
|
|
2
|
+
|
|
3
|
+
CLI tool to initialize and manage skill packages.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g codeskill
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or use directly with npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx codeskill --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Usage: codeskill <command> [options]
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
init [name] Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)
|
|
24
|
+
add <package> Add a skill package
|
|
25
|
+
get <package> Alias for add
|
|
26
|
+
install <package> Alias for add
|
|
27
|
+
e.g. anthropics/skills
|
|
28
|
+
https://github.com/anthropics/skills
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--help, -h Show this help message
|
|
32
|
+
--version, -v Show version number
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
### init
|
|
38
|
+
|
|
39
|
+
Initialize a new skill with a `SKILL.md` template.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Create SKILL.md in current directory
|
|
43
|
+
codeskill init
|
|
44
|
+
|
|
45
|
+
# Create my-skill/SKILL.md
|
|
46
|
+
codeskill init my-skill
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### add / get / install
|
|
50
|
+
|
|
51
|
+
Add a skill package from GitHub. The package will be cloned into the `skills/` directory.
|
|
52
|
+
|
|
53
|
+
`get` and `install` are aliases for `add`.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Using shorthand format
|
|
57
|
+
codeskill add anthropics/skills
|
|
58
|
+
codeskill get anthropics/skills
|
|
59
|
+
codeskill install anthropics/skills
|
|
60
|
+
|
|
61
|
+
# Using full URL
|
|
62
|
+
codeskill add https://github.com/anthropics/skills
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Initialize a new skill project
|
|
69
|
+
$ codeskill init my-awesome-skill
|
|
70
|
+
✓ Created directory: my-awesome-skill/
|
|
71
|
+
✓ Created my-awesome-skill/SKILL.md
|
|
72
|
+
|
|
73
|
+
# Add an existing skill package
|
|
74
|
+
$ codeskill add anthropics/skills
|
|
75
|
+
Adding skill package: anthropics/skills
|
|
76
|
+
Cloning from: https://github.com/anthropics/skills.git
|
|
77
|
+
✓ Added skill package to: skills/agent-skills
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
package/bin/codeskill.js
ADDED
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const { writeFileSync, existsSync, mkdirSync, rmSync, cpSync, readdirSync, readFileSync } = require('fs');
|
|
5
|
+
const { basename, join, dirname, relative } = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const prompts = require('prompts');
|
|
8
|
+
|
|
9
|
+
const VERSION = '1.0.0';
|
|
10
|
+
|
|
11
|
+
// Detect system language
|
|
12
|
+
function getSystemLanguage() {
|
|
13
|
+
// Method 1: Check environment variables (Unix/Linux/macOS)
|
|
14
|
+
const envLang = process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || '';
|
|
15
|
+
if (envLang.toLowerCase().includes('zh') || envLang.toLowerCase().includes('cn')) {
|
|
16
|
+
return 'zh';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Method 2: Check Intl API (works on all platforms)
|
|
20
|
+
try {
|
|
21
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
22
|
+
if (locale.toLowerCase().startsWith('zh')) {
|
|
23
|
+
return 'zh';
|
|
24
|
+
}
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// Fallback if Intl is not available
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Method 3: Check Windows locale (Windows specific)
|
|
30
|
+
if (process.platform === 'win32') {
|
|
31
|
+
const userLang = process.env.LANG || process.env.LANGUAGE || '';
|
|
32
|
+
if (userLang.toLowerCase().includes('zh') || userLang.toLowerCase().includes('cn')) {
|
|
33
|
+
return 'zh';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return 'en';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const isChinese = getSystemLanguage() === 'zh';
|
|
41
|
+
|
|
42
|
+
// Translations
|
|
43
|
+
const t = {
|
|
44
|
+
banner: {
|
|
45
|
+
ecosystem: isChinese ? 'Share skills. Build together.' : 'Share skills. Build together.',
|
|
46
|
+
try: isChinese ? '试试:' : 'try:',
|
|
47
|
+
discover: isChinese ? '在以下地址发现更多技能:' : 'Discover more skills at'
|
|
48
|
+
},
|
|
49
|
+
help: {
|
|
50
|
+
usage: isChinese ? '用法' : 'Usage',
|
|
51
|
+
commands: isChinese ? '命令' : 'Commands',
|
|
52
|
+
options: isChinese ? '选项' : 'Options',
|
|
53
|
+
examples: isChinese ? '示例' : 'Examples',
|
|
54
|
+
initDesc: isChinese ? '初始化一个技能(创建 <name>/SKILL.md 或 ./SKILL.md)' : 'Initialize a skill (creates <name>/SKILL.md or ./SKILL.md)',
|
|
55
|
+
addDesc: isChinese ? '添加技能包' : 'Add a skill package',
|
|
56
|
+
skillDesc: isChinese ? '指定要安装的技能(逗号分隔,跳过选择)' : 'Specify skill(s) to install (comma-separated, skip selection)',
|
|
57
|
+
pathDesc: isChinese ? '指定安装目录(用于 init)' : 'Specify installation directory (for init)',
|
|
58
|
+
helpDesc: isChinese ? '显示此帮助信息' : 'Show this help message',
|
|
59
|
+
versionDesc: isChinese ? '显示版本号' : 'Show version number'
|
|
60
|
+
},
|
|
61
|
+
skills: {
|
|
62
|
+
foundOne: isChinese ? '发现 1 个技能:' : 'Found 1 skill:',
|
|
63
|
+
foundMany: isChinese ? '在此仓库中发现' : 'Found',
|
|
64
|
+
skillsInRepo: isChinese ? '个技能' : 'skills in this repository',
|
|
65
|
+
selectSkills: isChinese ? '选择要安装的技能' : 'Select skills to install',
|
|
66
|
+
noSkillsSelected: isChinese ? '未选择技能。退出。' : 'No skills selected. Exiting.',
|
|
67
|
+
noSkillsFound: isChinese ? '在此仓库中未找到 SKILL.md 文件' : 'No SKILL.md files found in this repository',
|
|
68
|
+
installAsSingle: isChinese ? '整个仓库将作为单个技能安装。' : 'The entire repository will be installed as a single skill.',
|
|
69
|
+
noMatching: isChinese ? '未找到匹配的技能:' : 'No matching skills found for:',
|
|
70
|
+
availableSkills: isChinese ? '可用技能:' : 'Available skills:',
|
|
71
|
+
selected: isChinese ? '已选择' : 'Selected',
|
|
72
|
+
skill: isChinese ? '个技能:' : 'skill(s):'
|
|
73
|
+
},
|
|
74
|
+
agents: {
|
|
75
|
+
selectAgents: isChinese ? '选择要安装技能的 AI 助手' : 'Select agents to install skills to',
|
|
76
|
+
noAgentsSelected: isChinese ? '未选择 AI 助手。退出。' : 'No agents selected. Exiting.'
|
|
77
|
+
},
|
|
78
|
+
add: {
|
|
79
|
+
invalidFormat: isChinese ? '错误:无效的包格式:' : 'Error: Invalid package format:',
|
|
80
|
+
expectedFormats: isChinese ? '预期格式:' : 'Expected formats:',
|
|
81
|
+
fetching: isChinese ? '正在获取' : 'Fetching',
|
|
82
|
+
failedToClone: isChinese ? '错误:克隆仓库失败' : 'Error: Failed to clone repository',
|
|
83
|
+
makeSureAccessible: isChinese ? '请确保仓库存在且可访问。' : 'Make sure the repository exists and is accessible.',
|
|
84
|
+
skipped: isChinese ? '已跳过' : 'Skipped',
|
|
85
|
+
alreadyExists: isChinese ? ':已存在' : ': already exists',
|
|
86
|
+
installed: isChinese ? '已安装' : 'Installed',
|
|
87
|
+
to: isChinese ? '到' : 'to',
|
|
88
|
+
successfullyInstalled: isChinese ? '成功安装' : 'Successfully installed',
|
|
89
|
+
skill: isChinese ? '个技能到' : 'skill(s) to',
|
|
90
|
+
agent: isChinese ? '个 AI 助手' : 'agent(s)',
|
|
91
|
+
noNewInstallations: isChinese ? '没有新安装。技能可能已存在。' : 'No new installations. Skills may already exist.',
|
|
92
|
+
viewSkill: isChinese ? '查看技能:' : 'View the skill at',
|
|
93
|
+
discoverMore: isChinese ? '发现更多技能:' : 'Discover more skills at'
|
|
94
|
+
},
|
|
95
|
+
init: {
|
|
96
|
+
alreadyExists: isChinese ? '技能已存在于' : 'Skill already exists at',
|
|
97
|
+
initialized: isChinese ? '已初始化技能:' : 'Initialized skill:',
|
|
98
|
+
created: isChinese ? '已创建:' : 'Created:',
|
|
99
|
+
nextSteps: isChinese ? '下一步:' : 'Next steps:',
|
|
100
|
+
editToDefine: isChinese ? '编辑' : 'Edit',
|
|
101
|
+
toDefineInstructions: isChinese ? '以定义你的技能说明' : 'to define your skill instructions',
|
|
102
|
+
updateFrontmatter: isChinese ? '更新 frontmatter 中的' : 'Update the',
|
|
103
|
+
and: isChinese ? '和' : 'and',
|
|
104
|
+
inFrontmatter: isChinese ? '在 frontmatter 中' : 'in the frontmatter',
|
|
105
|
+
publishing: isChinese ? '发布:' : 'Publishing:',
|
|
106
|
+
pushToRepo: isChinese ? '推送到仓库,然后' : 'Push to a repo, then',
|
|
107
|
+
hostFile: isChinese ? '托管文件,然后' : 'Host the file, then',
|
|
108
|
+
browseForInspiration: isChinese ? '浏览现有技能以获取灵感:' : 'Browse existing skills for inspiration at'
|
|
109
|
+
},
|
|
110
|
+
error: {
|
|
111
|
+
specifyPackage: isChinese ? '错误:请指定要添加的包' : 'Error: Please specify a package to add',
|
|
112
|
+
example: isChinese ? '示例:' : 'Example:',
|
|
113
|
+
unknownCommand: isChinese ? '未知命令:' : 'Unknown command:',
|
|
114
|
+
runHelp: isChinese ? '运行' : 'Run',
|
|
115
|
+
forUsage: isChinese ? '查看用法。' : 'for usage.'
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Colors
|
|
120
|
+
const RESET = '\x1B[0m';
|
|
121
|
+
const BOLD = '\x1B[1m';
|
|
122
|
+
const DIM = '\x1B[38;5;102m';
|
|
123
|
+
const TEXT = '\x1B[38;5;145m';
|
|
124
|
+
const GREEN = '\x1B[38;5;82m';
|
|
125
|
+
const CYAN = '\x1B[38;5;87m';
|
|
126
|
+
const YELLOW = '\x1B[38;5;220m';
|
|
127
|
+
|
|
128
|
+
const LOGO_LINES = [
|
|
129
|
+
' ██████ ██████ ██████ ███████ ███████ ██ ██ ██ ██ ██ ',
|
|
130
|
+
'██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ',
|
|
131
|
+
'██ ██ ██ ██ ██ █████ ███████ █████ ██ ██ ██ ',
|
|
132
|
+
'██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ',
|
|
133
|
+
' ██████ ██████ ██████ ███████ ███████ ██ ██ ██ ███████ ███████ '
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const GRAYS = [
|
|
137
|
+
'\x1B[38;5;250m',
|
|
138
|
+
'\x1B[38;5;248m',
|
|
139
|
+
'\x1B[38;5;245m',
|
|
140
|
+
'\x1B[38;5;243m',
|
|
141
|
+
'\x1B[38;5;240m'
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
// Platform detection
|
|
145
|
+
const isWindows = process.platform === 'win32';
|
|
146
|
+
const isMac = process.platform === 'darwin';
|
|
147
|
+
const isLinux = process.platform === 'linux';
|
|
148
|
+
|
|
149
|
+
// Get platform-specific path for an agent
|
|
150
|
+
function getAgentSkillsPath(agentId) {
|
|
151
|
+
const home = os.homedir();
|
|
152
|
+
const appData = process.env.APPDATA || join(home, 'AppData', 'Roaming');
|
|
153
|
+
const localAppData = process.env.LOCALAPPDATA || join(home, 'AppData', 'Local');
|
|
154
|
+
|
|
155
|
+
const paths = {
|
|
156
|
+
claude: {
|
|
157
|
+
win32: join(home, '.claude', 'skills'),
|
|
158
|
+
darwin: join(home, '.claude', 'skills'),
|
|
159
|
+
linux: join(home, '.claude', 'skills')
|
|
160
|
+
},
|
|
161
|
+
cursor: {
|
|
162
|
+
win32: join(home, '.cursor', 'skills'),
|
|
163
|
+
darwin: join(home, '.cursor', 'skills'),
|
|
164
|
+
linux: join(home, '.cursor', 'skills')
|
|
165
|
+
},
|
|
166
|
+
opencode: {
|
|
167
|
+
win32: join(home, '.opencode', 'skills'),
|
|
168
|
+
darwin: join(home, '.opencode', 'skills'),
|
|
169
|
+
linux: join(home, '.opencode', 'skills')
|
|
170
|
+
},
|
|
171
|
+
trae: {
|
|
172
|
+
win32: join(localAppData, 'Trae', 'skills'),
|
|
173
|
+
darwin: join(home, '.trae', 'skills'),
|
|
174
|
+
linux: join(home, '.trae', 'skills')
|
|
175
|
+
},
|
|
176
|
+
windsurf: {
|
|
177
|
+
win32: join(home, '.windsurf', 'skills'),
|
|
178
|
+
darwin: join(home, '.windsurf', 'skills'),
|
|
179
|
+
linux: join(home, '.windsurf', 'skills')
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return paths[agentId]?.[process.platform] || join(home, `.${agentId}`, 'skills');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Get display path (for showing to user)
|
|
187
|
+
function getDisplayPath(agentId) {
|
|
188
|
+
const fullPath = getAgentSkillsPath(agentId);
|
|
189
|
+
const home = os.homedir();
|
|
190
|
+
|
|
191
|
+
// Replace home directory with ~ for display
|
|
192
|
+
if (fullPath.startsWith(home)) {
|
|
193
|
+
return '~' + fullPath.slice(home.length).replace(/\\/g, '/');
|
|
194
|
+
}
|
|
195
|
+
return fullPath;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// AI Agent targets
|
|
199
|
+
const AGENT_TARGETS = [
|
|
200
|
+
{
|
|
201
|
+
title: 'Claude Code',
|
|
202
|
+
value: 'claude'
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
title: 'Cursor',
|
|
206
|
+
value: 'cursor'
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
title: 'OpenCode',
|
|
210
|
+
value: 'opencode'
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
title: 'Trae',
|
|
214
|
+
value: 'trae'
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
title: 'Windsurf',
|
|
218
|
+
value: 'windsurf'
|
|
219
|
+
}
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
function showLogo() {
|
|
223
|
+
console.log();
|
|
224
|
+
LOGO_LINES.forEach((line, i) => {
|
|
225
|
+
console.log(`${GRAYS[i]}${line}${RESET}`);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function showBanner() {
|
|
230
|
+
showLogo();
|
|
231
|
+
console.log();
|
|
232
|
+
console.log(`${DIM}${t.banner.ecosystem}${RESET}`);
|
|
233
|
+
console.log();
|
|
234
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx codeskill --help${RESET}`);
|
|
235
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx codeskill init ${DIM}[name]${RESET}`);
|
|
236
|
+
console.log(` ${DIM}$${RESET} ${TEXT}npx codeskill add ${DIM}[package]${RESET}`);
|
|
237
|
+
console.log();
|
|
238
|
+
console.log(`${DIM}${t.banner.try}${RESET} npx codeskill add anthropics/skills`);
|
|
239
|
+
console.log();
|
|
240
|
+
console.log(`${t.banner.discover} ${TEXT}https://codeskill.ai/${RESET}`);
|
|
241
|
+
console.log();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function showHelp() {
|
|
245
|
+
console.log(`
|
|
246
|
+
${BOLD}${t.help.usage}${RESET}: codeskill <command> [options]
|
|
247
|
+
|
|
248
|
+
${BOLD}${t.help.commands}${RESET}:
|
|
249
|
+
init [name] ${t.help.initDesc}
|
|
250
|
+
add <package> ${t.help.addDesc}
|
|
251
|
+
get <package> ${isChinese ? 'add 的别名' : 'Alias for add'}
|
|
252
|
+
install <package> ${isChinese ? 'add 的别名' : 'Alias for add'}
|
|
253
|
+
${isChinese ? '例如:' : 'e.g.'} anthropics/skills
|
|
254
|
+
https://github.com/anthropics/skills
|
|
255
|
+
|
|
256
|
+
${BOLD}${t.help.options}${RESET}:
|
|
257
|
+
--skill, -s <name> ${t.help.skillDesc}
|
|
258
|
+
--path, -p <dir> ${t.help.pathDesc}
|
|
259
|
+
--help, -h ${t.help.helpDesc}
|
|
260
|
+
--version, -v ${t.help.versionDesc}
|
|
261
|
+
|
|
262
|
+
${BOLD}${t.help.examples}${RESET}:
|
|
263
|
+
${DIM}$${RESET} codeskill init my-skill
|
|
264
|
+
${DIM}$${RESET} codeskill init my-skill --path ./custom/path
|
|
265
|
+
${DIM}$${RESET} codeskill add anthropics/skills
|
|
266
|
+
${DIM}$${RESET} codeskill add anthropics/skills --skill web-search
|
|
267
|
+
${DIM}$${RESET} codeskill add anthropics/skills -s web-search,code-review
|
|
268
|
+
|
|
269
|
+
${t.banner.discover} ${TEXT}https://codeskill.ai/${RESET}
|
|
270
|
+
`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function getOwnerRepo(pkg) {
|
|
274
|
+
const repoMatch = pkg.match(/^([^\/]+)\/([^\/]+)$/);
|
|
275
|
+
if (repoMatch && repoMatch[1] && repoMatch[2]) {
|
|
276
|
+
return { owner: repoMatch[1], repo: repoMatch[2] };
|
|
277
|
+
}
|
|
278
|
+
const githubMatch = pkg.match(/github\.com\/([^\/]+)\/([^\/]+)/);
|
|
279
|
+
if (githubMatch && githubMatch[1] && githubMatch[2]) {
|
|
280
|
+
return { owner: githubMatch[1], repo: githubMatch[2].replace(/\.git$/, '') };
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function isRepoPublic(owner, repo) {
|
|
286
|
+
try {
|
|
287
|
+
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
|
|
288
|
+
if (!res.ok) return false;
|
|
289
|
+
const data = await res.json();
|
|
290
|
+
return data.private === false;
|
|
291
|
+
} catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Parse skill name from SKILL.md frontmatter
|
|
297
|
+
function parseSkillName(skillMdPath) {
|
|
298
|
+
try {
|
|
299
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
300
|
+
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
301
|
+
if (frontmatterMatch) {
|
|
302
|
+
const nameMatch = frontmatterMatch[1].match(/^name:\s*(.+)$/m);
|
|
303
|
+
if (nameMatch) {
|
|
304
|
+
return nameMatch[1].trim();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Fallback to directory name
|
|
308
|
+
return basename(dirname(skillMdPath));
|
|
309
|
+
} catch {
|
|
310
|
+
return basename(dirname(skillMdPath));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Discover all skills (SKILL.md files) in a directory
|
|
315
|
+
function discoverSkills(repoDir) {
|
|
316
|
+
const skills = [];
|
|
317
|
+
|
|
318
|
+
function scan(dir, depth = 0) {
|
|
319
|
+
if (depth > 5) return; // Max depth to prevent infinite loops
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const items = readdirSync(dir, { withFileTypes: true });
|
|
323
|
+
|
|
324
|
+
for (const item of items) {
|
|
325
|
+
if (item.name === 'node_modules' || item.name === '.git' || item.name.startsWith('.')) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const fullPath = join(dir, item.name);
|
|
330
|
+
|
|
331
|
+
if (item.isDirectory()) {
|
|
332
|
+
// Check if this directory has a SKILL.md
|
|
333
|
+
const skillMd = join(fullPath, 'SKILL.md');
|
|
334
|
+
if (existsSync(skillMd)) {
|
|
335
|
+
const skillName = parseSkillName(skillMd);
|
|
336
|
+
skills.push({
|
|
337
|
+
name: skillName,
|
|
338
|
+
dirName: item.name,
|
|
339
|
+
path: fullPath,
|
|
340
|
+
relativePath: relative(repoDir, fullPath)
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
// Continue scanning subdirectories
|
|
344
|
+
scan(fullPath, depth + 1);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch (e) {
|
|
348
|
+
// Ignore errors
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Check if root directory has SKILL.md
|
|
353
|
+
const rootSkillMd = join(repoDir, 'SKILL.md');
|
|
354
|
+
if (existsSync(rootSkillMd)) {
|
|
355
|
+
const skillName = parseSkillName(rootSkillMd);
|
|
356
|
+
skills.push({
|
|
357
|
+
name: skillName,
|
|
358
|
+
dirName: basename(repoDir),
|
|
359
|
+
path: repoDir,
|
|
360
|
+
relativePath: '.',
|
|
361
|
+
isRoot: true
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
scan(repoDir);
|
|
366
|
+
return skills;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Prompt user to select which skills to install
|
|
370
|
+
async function selectSkills(skills) {
|
|
371
|
+
if (skills.length === 0) {
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (skills.length === 1) {
|
|
376
|
+
console.log(`${TEXT}${t.skills.foundOne} ${CYAN}${skills[0].name}${RESET}`);
|
|
377
|
+
return skills;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
console.log(`${TEXT}${t.skills.foundMany} ${YELLOW}${skills.length}${TEXT} ${t.skills.skillsInRepo}${RESET}`);
|
|
381
|
+
console.log();
|
|
382
|
+
|
|
383
|
+
const response = await prompts({
|
|
384
|
+
type: 'multiselect',
|
|
385
|
+
name: 'skills',
|
|
386
|
+
message: t.skills.selectSkills,
|
|
387
|
+
choices: skills.map(skill => ({
|
|
388
|
+
title: `${skill.name} ${DIM}(${skill.relativePath})${RESET}`,
|
|
389
|
+
value: skill,
|
|
390
|
+
selected: true // Default select all
|
|
391
|
+
})),
|
|
392
|
+
hint: '- Space to select. Return to submit',
|
|
393
|
+
instructions: false,
|
|
394
|
+
min: 1
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
if (!response.skills || response.skills.length === 0) {
|
|
398
|
+
console.log(`${DIM}${t.skills.noSkillsSelected}${RESET}`);
|
|
399
|
+
process.exit(0);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return response.skills;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Prompt user to select which agents to install skills to
|
|
406
|
+
async function selectAgents() {
|
|
407
|
+
console.log();
|
|
408
|
+
|
|
409
|
+
const response = await prompts({
|
|
410
|
+
type: 'multiselect',
|
|
411
|
+
name: 'agents',
|
|
412
|
+
message: t.agents.selectAgents,
|
|
413
|
+
choices: AGENT_TARGETS.map(agent => ({
|
|
414
|
+
title: `${agent.title} ${DIM}(${getDisplayPath(agent.value)})${RESET}`,
|
|
415
|
+
value: agent,
|
|
416
|
+
selected: agent.value === 'cursor' || agent.value === 'claude' // Default select Cursor and Claude
|
|
417
|
+
})),
|
|
418
|
+
hint: '- Space to select. Return to submit',
|
|
419
|
+
instructions: false,
|
|
420
|
+
min: 1
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
if (!response.agents || response.agents.length === 0) {
|
|
424
|
+
console.log(`${DIM}${t.agents.noAgentsSelected}${RESET}`);
|
|
425
|
+
process.exit(0);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return response.agents;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function runAddSkill(pkg, specifiedSkills) {
|
|
432
|
+
const info = getOwnerRepo(pkg);
|
|
433
|
+
if (!info) {
|
|
434
|
+
console.log(`${RESET}${t.add.invalidFormat} ${pkg}`);
|
|
435
|
+
console.log();
|
|
436
|
+
console.log(`${DIM}${t.add.expectedFormats}${RESET}`);
|
|
437
|
+
console.log(` owner/repo`);
|
|
438
|
+
console.log(` https://github.com/owner/repo`);
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const { owner, repo } = info;
|
|
443
|
+
const url = `https://github.com/${owner}/${repo}.git`;
|
|
444
|
+
|
|
445
|
+
// 1. Clone to temp directory first
|
|
446
|
+
const tempDir = join(os.tmpdir(), `codeskill-${repo}-${Date.now()}`);
|
|
447
|
+
|
|
448
|
+
console.log(`${TEXT}${t.add.fetching} ${DIM}${owner}/${repo}${TEXT}...${RESET}`);
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
execSync(`git clone --depth 1 ${url} "${tempDir}"`, { stdio: 'pipe' });
|
|
452
|
+
} catch (error) {
|
|
453
|
+
console.log(`${RESET}${t.add.failedToClone}${RESET}`);
|
|
454
|
+
console.log(`${DIM}${t.add.makeSureAccessible}${RESET}`);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Remove .git directory from cloned repo
|
|
459
|
+
const gitDir = join(tempDir, '.git');
|
|
460
|
+
if (existsSync(gitDir)) {
|
|
461
|
+
rmSync(gitDir, { recursive: true, force: true });
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
console.log();
|
|
465
|
+
|
|
466
|
+
// 2. Discover all skills in the repo
|
|
467
|
+
const allSkills = discoverSkills(tempDir);
|
|
468
|
+
|
|
469
|
+
if (allSkills.length === 0) {
|
|
470
|
+
console.log(`${YELLOW}⚠${RESET} ${TEXT}${t.skills.noSkillsFound}${RESET}`);
|
|
471
|
+
console.log(`${DIM}${t.skills.installAsSingle}${RESET}`);
|
|
472
|
+
// Treat the whole repo as a single skill
|
|
473
|
+
allSkills.push({
|
|
474
|
+
name: repo,
|
|
475
|
+
dirName: repo,
|
|
476
|
+
path: tempDir,
|
|
477
|
+
relativePath: '.',
|
|
478
|
+
isRoot: true
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// 3. Select skills (via --skill flag or interactive)
|
|
483
|
+
let selectedSkills;
|
|
484
|
+
|
|
485
|
+
if (specifiedSkills && specifiedSkills.length > 0) {
|
|
486
|
+
// Filter skills by specified names
|
|
487
|
+
selectedSkills = allSkills.filter(skill =>
|
|
488
|
+
specifiedSkills.some(s =>
|
|
489
|
+
s.toLowerCase() === skill.name.toLowerCase() ||
|
|
490
|
+
s.toLowerCase() === skill.dirName.toLowerCase()
|
|
491
|
+
)
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
if (selectedSkills.length === 0) {
|
|
495
|
+
console.log(`${YELLOW}⚠${RESET} ${TEXT}${t.skills.noMatching} ${specifiedSkills.join(', ')}${RESET}`);
|
|
496
|
+
console.log();
|
|
497
|
+
console.log(`${DIM}${t.skills.availableSkills}${RESET}`);
|
|
498
|
+
allSkills.forEach(s => console.log(` - ${s.name} (${s.relativePath})`));
|
|
499
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
console.log(`${TEXT}${t.skills.selected} ${selectedSkills.length} ${t.skills.skill} ${selectedSkills.map(s => s.name).join(', ')}${RESET}`);
|
|
504
|
+
} else {
|
|
505
|
+
// Interactive selection
|
|
506
|
+
selectedSkills = await selectSkills(allSkills);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// 4. Select agents
|
|
510
|
+
const selectedAgents = await selectAgents();
|
|
511
|
+
|
|
512
|
+
console.log();
|
|
513
|
+
|
|
514
|
+
// 5. Copy selected skills to each selected agent's skills directory
|
|
515
|
+
let installedCount = 0;
|
|
516
|
+
|
|
517
|
+
for (const agent of selectedAgents) {
|
|
518
|
+
const agentSkillsDir = getAgentSkillsPath(agent.value);
|
|
519
|
+
const displayBasePath = getDisplayPath(agent.value);
|
|
520
|
+
|
|
521
|
+
// Create skills directory if it doesn't exist
|
|
522
|
+
if (!existsSync(agentSkillsDir)) {
|
|
523
|
+
mkdirSync(agentSkillsDir, { recursive: true });
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
for (const skill of selectedSkills) {
|
|
527
|
+
const skillDirName = skill.isRoot ? repo : skill.dirName;
|
|
528
|
+
const targetDir = join(agentSkillsDir, skillDirName);
|
|
529
|
+
|
|
530
|
+
if (existsSync(targetDir)) {
|
|
531
|
+
console.log(`${DIM}⚠ ${t.add.skipped} ${agent.title}/${skill.name}${t.add.alreadyExists}${RESET}`);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Copy the skill
|
|
536
|
+
cpSync(skill.path, targetDir, { recursive: true });
|
|
537
|
+
console.log(`${GREEN}✓${RESET} ${TEXT}${t.add.installed} ${CYAN}${skill.name}${RESET} ${t.add.to} ${CYAN}${agent.title}${RESET} ${DIM}(${displayBasePath}/${skillDirName})${RESET}`);
|
|
538
|
+
installedCount++;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 6. Clean up temp directory
|
|
543
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
544
|
+
|
|
545
|
+
// 7. Show summary
|
|
546
|
+
console.log();
|
|
547
|
+
if (installedCount > 0) {
|
|
548
|
+
console.log(`${GREEN}✓${RESET} ${TEXT}${t.add.successfullyInstalled} ${BOLD}${selectedSkills.length}${RESET}${TEXT} ${t.add.skill} ${BOLD}${selectedAgents.length}${RESET}${TEXT} ${t.add.agent}${RESET}`);
|
|
549
|
+
} else {
|
|
550
|
+
console.log(`${DIM}${t.add.noNewInstallations}${RESET}`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
console.log();
|
|
554
|
+
if (await isRepoPublic(owner, repo)) {
|
|
555
|
+
console.log(`${DIM}${t.add.viewSkill}${RESET} ${TEXT}https://codeskill.ai/${owner}/${repo}${RESET}`);
|
|
556
|
+
} else {
|
|
557
|
+
console.log(`${DIM}${t.add.discoverMore}${RESET} ${TEXT}https://codeskill.ai/${RESET}`);
|
|
558
|
+
}
|
|
559
|
+
console.log();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function runInit(name, customPath) {
|
|
563
|
+
const baseDir = customPath ? join(process.cwd(), customPath) : process.cwd();
|
|
564
|
+
const skillName = name || basename(baseDir);
|
|
565
|
+
const hasName = name !== undefined;
|
|
566
|
+
const skillDir = hasName ? join(baseDir, skillName) : baseDir;
|
|
567
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
568
|
+
|
|
569
|
+
// Calculate display path relative to cwd
|
|
570
|
+
const relativePath = customPath
|
|
571
|
+
? (hasName ? join(customPath, skillName, 'SKILL.md') : join(customPath, 'SKILL.md'))
|
|
572
|
+
: (hasName ? `${skillName}/SKILL.md` : 'SKILL.md');
|
|
573
|
+
const displayPath = relativePath;
|
|
574
|
+
|
|
575
|
+
if (existsSync(skillFile)) {
|
|
576
|
+
console.log(`${TEXT}${t.init.alreadyExists} ${DIM}${displayPath}${RESET}`);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Create directory if needed (either custom path or named skill)
|
|
581
|
+
if (hasName || customPath) {
|
|
582
|
+
mkdirSync(skillDir, { recursive: true });
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const skillContent = `---
|
|
586
|
+
name: ${skillName}
|
|
587
|
+
description: A brief description of what this skill does
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
# ${skillName}
|
|
591
|
+
|
|
592
|
+
Instructions for the agent to follow when this skill is activated.
|
|
593
|
+
|
|
594
|
+
## When to use
|
|
595
|
+
|
|
596
|
+
Describe when this skill should be used.
|
|
597
|
+
|
|
598
|
+
## Instructions
|
|
599
|
+
`;
|
|
600
|
+
|
|
601
|
+
writeFileSync(skillFile, skillContent);
|
|
602
|
+
console.log(`${TEXT}${t.init.initialized} ${DIM}${skillName}${RESET}`);
|
|
603
|
+
console.log();
|
|
604
|
+
console.log(`${DIM}${t.init.created}${RESET}`);
|
|
605
|
+
console.log(` ${displayPath}`);
|
|
606
|
+
console.log();
|
|
607
|
+
console.log(`${DIM}${t.init.nextSteps}${RESET}`);
|
|
608
|
+
console.log(` 1. ${t.init.editToDefine} ${TEXT}${displayPath}${RESET} ${t.init.toDefineInstructions}`);
|
|
609
|
+
console.log(` 2. ${t.init.updateFrontmatter} ${TEXT}name${RESET} ${t.init.and} ${TEXT}description${RESET} ${t.init.inFrontmatter}`);
|
|
610
|
+
console.log();
|
|
611
|
+
console.log(`${DIM}${t.init.publishing}${RESET}`);
|
|
612
|
+
console.log(` ${DIM}GitHub:${RESET} ${t.init.pushToRepo} ${TEXT}npx codeskill add <owner>/<repo>${RESET}`);
|
|
613
|
+
console.log(` ${DIM}URL:${RESET} ${t.init.hostFile} ${TEXT}npx codeskill add https://example.com/${displayPath}${RESET}`);
|
|
614
|
+
console.log();
|
|
615
|
+
console.log(`${t.init.browseForInspiration} ${TEXT}https://codeskill.ai/${RESET}`);
|
|
616
|
+
console.log();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function parseArgs(args) {
|
|
620
|
+
const result = {
|
|
621
|
+
positional: [],
|
|
622
|
+
path: null,
|
|
623
|
+
skills: null
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
for (let i = 0; i < args.length; i++) {
|
|
627
|
+
if (args[i] === '--path' || args[i] === '-p') {
|
|
628
|
+
result.path = args[i + 1];
|
|
629
|
+
i++; // skip next arg
|
|
630
|
+
} else if (args[i] === '--skill' || args[i] === '-s') {
|
|
631
|
+
// Support comma-separated skills
|
|
632
|
+
const skillArg = args[i + 1];
|
|
633
|
+
if (skillArg) {
|
|
634
|
+
result.skills = skillArg.split(',').map(s => s.trim()).filter(Boolean);
|
|
635
|
+
}
|
|
636
|
+
i++; // skip next arg
|
|
637
|
+
} else if (!args[i].startsWith('-')) {
|
|
638
|
+
result.positional.push(args[i]);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return result;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
async function main() {
|
|
646
|
+
const args = process.argv.slice(2);
|
|
647
|
+
|
|
648
|
+
if (args.length === 0) {
|
|
649
|
+
showBanner();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const command = args[0];
|
|
654
|
+
const restArgs = args.slice(1);
|
|
655
|
+
const parsed = parseArgs(restArgs);
|
|
656
|
+
|
|
657
|
+
switch (command) {
|
|
658
|
+
case 'init':
|
|
659
|
+
showLogo();
|
|
660
|
+
console.log();
|
|
661
|
+
runInit(parsed.positional[0], parsed.path);
|
|
662
|
+
break;
|
|
663
|
+
case 'i':
|
|
664
|
+
case 'install':
|
|
665
|
+
case 'a':
|
|
666
|
+
case 'add':
|
|
667
|
+
case 'get':
|
|
668
|
+
showLogo();
|
|
669
|
+
console.log();
|
|
670
|
+
if (!parsed.positional[0]) {
|
|
671
|
+
console.log(`${TEXT}${t.error.specifyPackage}${RESET}`);
|
|
672
|
+
console.log(`${DIM}${t.error.example} codeskill add anthropics/skills${RESET}`);
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
await runAddSkill(parsed.positional[0], parsed.skills);
|
|
676
|
+
break;
|
|
677
|
+
case '--help':
|
|
678
|
+
case '-h':
|
|
679
|
+
showHelp();
|
|
680
|
+
break;
|
|
681
|
+
case '--version':
|
|
682
|
+
case '-v':
|
|
683
|
+
console.log(VERSION);
|
|
684
|
+
break;
|
|
685
|
+
default:
|
|
686
|
+
console.log(`${t.error.unknownCommand} ${command}`);
|
|
687
|
+
console.log(`${t.error.runHelp} ${BOLD}codeskill --help${RESET} ${t.error.forUsage}`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codeskill",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "CLI tool to initialize and manage skill packages",
|
|
5
|
+
"bin": {
|
|
6
|
+
"codeskill": "./bin/codeskill.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"cli",
|
|
13
|
+
"skill",
|
|
14
|
+
"agent",
|
|
15
|
+
"ai"
|
|
16
|
+
],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": ""
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16.0.0"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"prompts": "^2.4.2"
|
|
28
|
+
}
|
|
29
|
+
}
|