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.
Files changed (3) hide show
  1. package/README.md +82 -0
  2. package/bin/codeskill.js +691 -0
  3. 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
@@ -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
+ }