@raketa-cloud/cli 1.1.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/CLAUDE.md +248 -0
- package/README.md +153 -0
- package/bin/tmux +27 -0
- package/package.json +20 -0
- package/src/commands/commands/index.js +27 -0
- package/src/commands/commands/list/index.js +43 -0
- package/src/commands/commands/new/index.js +109 -0
- package/src/commands/skills/index.js +89 -0
- package/src/commands/skills/install/index.js +40 -0
- package/src/commands/skills/install-global/index.js +40 -0
- package/src/commands/skills/list/index.js +43 -0
- package/src/commands/skills/list-all/index.js +56 -0
- package/src/commands/skills/list-remote/index.js +35 -0
- package/src/commands/skills/new/index.js +109 -0
- package/src/commands/skills/rm/index.js +65 -0
- package/src/commands/skills/rm-global/index.js +66 -0
- package/src/commands/widgets/index.js +29 -0
- package/src/commands/widgets/list/index.js +41 -0
- package/src/commands/widgets/new/index.js +186 -0
- package/src/index.js +17 -0
- package/src/lib/Command.js +14 -0
- package/src/lib/skillsUtils.js +246 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createCommand } from 'commander';
|
|
2
|
+
import { ListSkillsCommand } from './list/index.js';
|
|
3
|
+
import { ListAllSkillsCommand } from './list-all/index.js';
|
|
4
|
+
import { ListRemoteSkillsCommand } from './list-remote/index.js';
|
|
5
|
+
import { NewSkillCommand } from './new/index.js';
|
|
6
|
+
import { InstallSkillCommand } from './install/index.js';
|
|
7
|
+
import { InstallGlobalSkillCommand } from './install-global/index.js';
|
|
8
|
+
import { RemoveSkillCommand } from './rm/index.js';
|
|
9
|
+
import { RemoveGlobalSkillCommand } from './rm-global/index.js';
|
|
10
|
+
|
|
11
|
+
export function createSkillsCommand() {
|
|
12
|
+
const skills = createCommand('skills');
|
|
13
|
+
skills.description('Manage skills');
|
|
14
|
+
|
|
15
|
+
// LIST command
|
|
16
|
+
skills
|
|
17
|
+
.command('list')
|
|
18
|
+
.description('List local skills')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const command = new ListSkillsCommand(options);
|
|
21
|
+
await command.process();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// LIST-ALL command
|
|
25
|
+
skills
|
|
26
|
+
.command('list-all')
|
|
27
|
+
.description('List both local and global skills')
|
|
28
|
+
.action(async (options) => {
|
|
29
|
+
const command = new ListAllSkillsCommand(options);
|
|
30
|
+
await command.process();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// LIST-REMOTE command
|
|
34
|
+
skills
|
|
35
|
+
.command('list-remote')
|
|
36
|
+
.description('List skills from remote repository')
|
|
37
|
+
.action(async (options) => {
|
|
38
|
+
const command = new ListRemoteSkillsCommand(options);
|
|
39
|
+
await command.process();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// NEW command
|
|
43
|
+
skills
|
|
44
|
+
.command('new')
|
|
45
|
+
.description('Create a new local skill')
|
|
46
|
+
.option('--name <value>', 'Skill name')
|
|
47
|
+
.action(async (options) => {
|
|
48
|
+
const command = new NewSkillCommand(options);
|
|
49
|
+
await command.process();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// INSTALL command
|
|
53
|
+
skills
|
|
54
|
+
.command('install <skillName>')
|
|
55
|
+
.description('Install a skill from remote repository to local .claude/skills/')
|
|
56
|
+
.action(async (skillName, options) => {
|
|
57
|
+
const command = new InstallSkillCommand({ skillName });
|
|
58
|
+
await command.process();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// INSTALL-GLOBAL command
|
|
62
|
+
skills
|
|
63
|
+
.command('install-global <skillName>')
|
|
64
|
+
.description('Install a skill from remote repository to global ~/.claude/skills/')
|
|
65
|
+
.action(async (skillName, options) => {
|
|
66
|
+
const command = new InstallGlobalSkillCommand({ skillName });
|
|
67
|
+
await command.process();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// RM command
|
|
71
|
+
skills
|
|
72
|
+
.command('rm <skillName>')
|
|
73
|
+
.description('Remove a skill from local .claude/skills/')
|
|
74
|
+
.action(async (skillName, options) => {
|
|
75
|
+
const command = new RemoveSkillCommand({ skillName });
|
|
76
|
+
await command.process();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// RM-GLOBAL command
|
|
80
|
+
skills
|
|
81
|
+
.command('rm-global <skillName>')
|
|
82
|
+
.description('Remove a skill from global ~/.claude/skills/')
|
|
83
|
+
.action(async (skillName, options) => {
|
|
84
|
+
const command = new RemoveGlobalSkillCommand({ skillName });
|
|
85
|
+
await command.process();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return skills;
|
|
89
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Command } from '../../../lib/Command.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { ensureRemoteRepo, copySkillFromCache } from '../../../lib/skillsUtils.js';
|
|
5
|
+
|
|
6
|
+
export class InstallSkillCommand extends Command {
|
|
7
|
+
async process() {
|
|
8
|
+
const options = this.getOptions();
|
|
9
|
+
|
|
10
|
+
// Validate skillName
|
|
11
|
+
if (!options.skillName) {
|
|
12
|
+
console.error('Error: skill name is required');
|
|
13
|
+
console.error('Usage: skills install <skillName>');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const skillName = options.skillName;
|
|
18
|
+
const REMOTE_REPO = 'git@github.com:studioraketa/claude-skills.git';
|
|
19
|
+
const cachePath = join(homedir(), '.claude', 'cache', 'remote-skills');
|
|
20
|
+
const destPath = join(this.getCWD(), '.claude', 'skills');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Ensure repo is cloned/updated
|
|
24
|
+
await ensureRemoteRepo(REMOTE_REPO, cachePath);
|
|
25
|
+
|
|
26
|
+
// Copy skill from cache to local
|
|
27
|
+
await copySkillFromCache(skillName, cachePath, destPath, true);
|
|
28
|
+
|
|
29
|
+
// Output success message
|
|
30
|
+
const installedPath = join(destPath, skillName);
|
|
31
|
+
console.log(`✓ Installed skill '${skillName}' to ${installedPath}`);
|
|
32
|
+
console.log();
|
|
33
|
+
console.log('The skill is now available to Claude in this directory.');
|
|
34
|
+
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`Error: ${error.message}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Command } from '../../../lib/Command.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { ensureRemoteRepo, copySkillFromCache } from '../../../lib/skillsUtils.js';
|
|
5
|
+
|
|
6
|
+
export class InstallGlobalSkillCommand extends Command {
|
|
7
|
+
async process() {
|
|
8
|
+
const options = this.getOptions();
|
|
9
|
+
|
|
10
|
+
// Validate skillName
|
|
11
|
+
if (!options.skillName) {
|
|
12
|
+
console.error('Error: skill name is required');
|
|
13
|
+
console.error('Usage: skills install-global <skillName>');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const skillName = options.skillName;
|
|
18
|
+
const REMOTE_REPO = 'git@github.com:studioraketa/claude-skills.git';
|
|
19
|
+
const cachePath = join(homedir(), '.claude', 'cache', 'remote-skills');
|
|
20
|
+
const destPath = join(homedir(), '.claude', 'skills');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Ensure repo is cloned/updated
|
|
24
|
+
await ensureRemoteRepo(REMOTE_REPO, cachePath);
|
|
25
|
+
|
|
26
|
+
// Copy skill from cache to global
|
|
27
|
+
await copySkillFromCache(skillName, cachePath, destPath, true);
|
|
28
|
+
|
|
29
|
+
// Output success message
|
|
30
|
+
const installedPath = join(destPath, skillName);
|
|
31
|
+
console.log(`✓ Installed skill '${skillName}' globally to ${installedPath}`);
|
|
32
|
+
console.log();
|
|
33
|
+
console.log('The skill is now available to Claude globally.');
|
|
34
|
+
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`Error: ${error.message}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Command } from '../../../lib/Command.js';
|
|
2
|
+
import { access } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { getSkillsWithDescriptions, formatSkillWithDescription } from '../../../lib/skillsUtils.js';
|
|
5
|
+
|
|
6
|
+
export class ListSkillsCommand extends Command {
|
|
7
|
+
async process() {
|
|
8
|
+
try {
|
|
9
|
+
const cwd = this.getCWD();
|
|
10
|
+
const skillsPath = join(cwd, '.claude', 'skills');
|
|
11
|
+
|
|
12
|
+
// Check if skills directory exists (graceful handling)
|
|
13
|
+
try {
|
|
14
|
+
await access(skillsPath);
|
|
15
|
+
} catch {
|
|
16
|
+
console.log('No local skills found');
|
|
17
|
+
console.log(`(Looked in: ${skillsPath})`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Get skills with descriptions
|
|
22
|
+
const validSkills = await getSkillsWithDescriptions(skillsPath);
|
|
23
|
+
|
|
24
|
+
if (validSkills.length === 0) {
|
|
25
|
+
console.log('No local skills found');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Print header
|
|
30
|
+
console.log(`Local skills (${validSkills.length}):`);
|
|
31
|
+
console.log();
|
|
32
|
+
|
|
33
|
+
// Print each skill with description
|
|
34
|
+
validSkills.forEach(({ name, description }) => {
|
|
35
|
+
console.log(formatSkillWithDescription(name, description));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error listing local skills:', error.message);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Command } from '../../../lib/Command.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { getSkillsWithDescriptions, formatSkillWithDescription } from '../../../lib/skillsUtils.js';
|
|
5
|
+
|
|
6
|
+
export class ListAllSkillsCommand extends Command {
|
|
7
|
+
async process() {
|
|
8
|
+
try {
|
|
9
|
+
const cwd = this.getCWD();
|
|
10
|
+
const localSkillsPath = join(cwd, '.claude', 'skills');
|
|
11
|
+
const globalSkillsPath = join(homedir(), '.claude', 'skills');
|
|
12
|
+
|
|
13
|
+
// Get local skills
|
|
14
|
+
const localSkills = await this.getSkillsFromPath(localSkillsPath);
|
|
15
|
+
|
|
16
|
+
// Get global skills
|
|
17
|
+
const globalSkills = await this.getSkillsFromPath(globalSkillsPath);
|
|
18
|
+
|
|
19
|
+
// Display local skills section
|
|
20
|
+
if (localSkills.length > 0) {
|
|
21
|
+
console.log(`Local skills (${localSkills.length}):`);
|
|
22
|
+
console.log();
|
|
23
|
+
localSkills.forEach(({ name, description }) => {
|
|
24
|
+
console.log(formatSkillWithDescription(name, description));
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
console.log('Local skills (0):');
|
|
28
|
+
console.log();
|
|
29
|
+
console.log(' No local skills');
|
|
30
|
+
}
|
|
31
|
+
console.log();
|
|
32
|
+
|
|
33
|
+
// Display global skills section
|
|
34
|
+
if (globalSkills.length > 0) {
|
|
35
|
+
console.log(`Global skills (${globalSkills.length}):`);
|
|
36
|
+
console.log();
|
|
37
|
+
globalSkills.forEach(({ name, description }) => {
|
|
38
|
+
console.log(formatSkillWithDescription(name, description));
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
console.log('Global skills (0):');
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(' No global skills');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error listing skills:', error.message);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async getSkillsFromPath(skillsPath) {
|
|
53
|
+
// Use shared utility to get skills with descriptions
|
|
54
|
+
return await getSkillsWithDescriptions(skillsPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Command } from '../../../lib/Command.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { ensureRemoteRepo, getSkillsWithDescriptions, formatSkillWithDescription } from '../../../lib/skillsUtils.js';
|
|
5
|
+
|
|
6
|
+
export class ListRemoteSkillsCommand extends Command {
|
|
7
|
+
async process() {
|
|
8
|
+
const REMOTE_REPO = 'git@github.com:studioraketa/claude-skills.git';
|
|
9
|
+
const cachePath = join(homedir(), '.claude', 'cache', 'remote-skills');
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// Ensure repo is cloned/updated
|
|
13
|
+
await ensureRemoteRepo(REMOTE_REPO, cachePath);
|
|
14
|
+
|
|
15
|
+
// Get skills with descriptions
|
|
16
|
+
const skills = await getSkillsWithDescriptions(cachePath);
|
|
17
|
+
|
|
18
|
+
// Display results
|
|
19
|
+
if (skills.length === 0) {
|
|
20
|
+
console.log('No remote skills found');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(`Remote skills (${skills.length}):`);
|
|
25
|
+
console.log();
|
|
26
|
+
skills.forEach(({ name, description }) => {
|
|
27
|
+
console.log(formatSkillWithDescription(name, description));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(`Error: ${error.message}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Command } from '../../../lib/Command.js';
|
|
2
|
+
import { mkdir, writeFile, access } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
export class NewSkillCommand extends Command {
|
|
6
|
+
async process() {
|
|
7
|
+
const options = this.getOptions();
|
|
8
|
+
|
|
9
|
+
// Validate --name option
|
|
10
|
+
if (!options.name) {
|
|
11
|
+
console.error('Error: --name option is required');
|
|
12
|
+
console.error('Usage: skills new --name "my-skill"');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Normalize and validate skill name
|
|
17
|
+
const skillName = normalizeSkillName(options.name);
|
|
18
|
+
|
|
19
|
+
if (!isValidSkillName(skillName)) {
|
|
20
|
+
console.error('Error: Skill name must contain only lowercase letters, numbers, and hyphens');
|
|
21
|
+
console.error('Examples: my-skill, code-review, bug-finder');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Get CWD and build target path
|
|
26
|
+
const cwd = this.getCWD();
|
|
27
|
+
const skillsDir = join(cwd, '.claude', 'skills');
|
|
28
|
+
const skillPath = join(skillsDir, skillName);
|
|
29
|
+
const skillMdPath = join(skillPath, 'SKILL.md');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Create .claude/skills directory if it doesn't exist
|
|
33
|
+
await mkdir(skillsDir, { recursive: true });
|
|
34
|
+
|
|
35
|
+
// Check if skill already exists
|
|
36
|
+
try {
|
|
37
|
+
await access(skillPath);
|
|
38
|
+
console.error(`Error: Skill already exists at ${skillPath}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
} catch {
|
|
41
|
+
// Skill doesn't exist, proceed
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create skill directory
|
|
45
|
+
await mkdir(skillPath);
|
|
46
|
+
|
|
47
|
+
// Generate SKILL.md content
|
|
48
|
+
const skillContent = generateSkillTemplate(skillName);
|
|
49
|
+
|
|
50
|
+
// Write SKILL.md file
|
|
51
|
+
await writeFile(skillMdPath, skillContent, 'utf-8');
|
|
52
|
+
|
|
53
|
+
// Output success message
|
|
54
|
+
console.log(`✓ Created skill at: ${skillPath}`);
|
|
55
|
+
console.log();
|
|
56
|
+
console.log('File created:');
|
|
57
|
+
console.log(' - SKILL.md');
|
|
58
|
+
console.log();
|
|
59
|
+
console.log('Next steps:');
|
|
60
|
+
console.log(`1. Edit ${skillMdPath}`);
|
|
61
|
+
console.log('2. Update the name, description, and instructions');
|
|
62
|
+
console.log('3. The skill will be automatically available to Claude');
|
|
63
|
+
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Error creating skill:', error.message);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Helper function to normalize skill name
|
|
72
|
+
function normalizeSkillName(name) {
|
|
73
|
+
// Convert to lowercase
|
|
74
|
+
let normalized = name.toLowerCase();
|
|
75
|
+
|
|
76
|
+
// Replace underscores and spaces with hyphens
|
|
77
|
+
normalized = normalized.replace(/[_\s]+/g, '-');
|
|
78
|
+
|
|
79
|
+
// Remove any non-alphanumeric characters except hyphens
|
|
80
|
+
normalized = normalized.replace(/[^a-z0-9-]/g, '');
|
|
81
|
+
|
|
82
|
+
// Remove leading/trailing hyphens
|
|
83
|
+
normalized = normalized.replace(/^-+|-+$/g, '');
|
|
84
|
+
|
|
85
|
+
// Collapse multiple hyphens
|
|
86
|
+
normalized = normalized.replace(/-+/g, '-');
|
|
87
|
+
|
|
88
|
+
return normalized;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Helper function to validate skill name
|
|
92
|
+
function isValidSkillName(name) {
|
|
93
|
+
// Must match: lowercase letters, numbers, hyphens only
|
|
94
|
+
// Must not be empty
|
|
95
|
+
// Must not start or end with hyphen
|
|
96
|
+
const validNameRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
97
|
+
return validNameRegex.test(name);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Template generator
|
|
101
|
+
function generateSkillTemplate(skillName) {
|
|
102
|
+
return `---
|
|
103
|
+
name: ${skillName}
|
|
104
|
+
description: Replace with description of the skill and when Claude should use it.
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
# Insert instructions below
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Command } from '../../../lib/Command.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { access, rm } from 'fs/promises';
|
|
4
|
+
import { parseSkillMetadata } from '../../../lib/skillsUtils.js';
|
|
5
|
+
|
|
6
|
+
export class RemoveSkillCommand extends Command {
|
|
7
|
+
async process() {
|
|
8
|
+
const options = this.getOptions();
|
|
9
|
+
|
|
10
|
+
// Validate skillName
|
|
11
|
+
if (!options.skillName) {
|
|
12
|
+
console.error('Error: skill name is required');
|
|
13
|
+
console.error('Usage: skills rm <skillName>');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const skillName = options.skillName;
|
|
18
|
+
const skillsPath = join(this.getCWD(), '.claude', 'skills');
|
|
19
|
+
const skillPath = join(skillsPath, skillName);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Check if skills directory exists
|
|
23
|
+
try {
|
|
24
|
+
await access(skillsPath);
|
|
25
|
+
} catch {
|
|
26
|
+
console.error(`Error: No skills directory found at ${skillsPath}`);
|
|
27
|
+
console.error();
|
|
28
|
+
console.error('There are no skills to remove');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if specific skill directory exists
|
|
33
|
+
try {
|
|
34
|
+
await access(skillPath);
|
|
35
|
+
} catch {
|
|
36
|
+
console.error(`Error: Skill '${skillName}' not found at ${skillPath}`);
|
|
37
|
+
console.error();
|
|
38
|
+
console.error(`Run 'skills list' to see available local skills`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Optional: Check for valid SKILL.md (warning only)
|
|
43
|
+
try {
|
|
44
|
+
const skillMdPath = join(skillPath, 'SKILL.md');
|
|
45
|
+
await access(skillMdPath);
|
|
46
|
+
const metadata = await parseSkillMetadata(skillMdPath);
|
|
47
|
+
if (!metadata) {
|
|
48
|
+
console.warn(`Warning: ${skillName} has invalid SKILL.md, removing anyway`);
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
console.warn(`Warning: ${skillName} does not have SKILL.md, removing anyway`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Remove skill directory
|
|
55
|
+
await rm(skillPath, { recursive: true, force: true });
|
|
56
|
+
|
|
57
|
+
// Output success message
|
|
58
|
+
console.log(`✓ Removed skill '${skillName}' from ${skillPath}`);
|
|
59
|
+
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(`Error: Failed to remove skill '${skillName}': ${error.message}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Command } from '../../../lib/Command.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { access, rm } from 'fs/promises';
|
|
5
|
+
import { parseSkillMetadata } from '../../../lib/skillsUtils.js';
|
|
6
|
+
|
|
7
|
+
export class RemoveGlobalSkillCommand extends Command {
|
|
8
|
+
async process() {
|
|
9
|
+
const options = this.getOptions();
|
|
10
|
+
|
|
11
|
+
// Validate skillName
|
|
12
|
+
if (!options.skillName) {
|
|
13
|
+
console.error('Error: skill name is required');
|
|
14
|
+
console.error('Usage: skills rm-global <skillName>');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const skillName = options.skillName;
|
|
19
|
+
const skillsPath = join(homedir(), '.claude', 'skills');
|
|
20
|
+
const skillPath = join(skillsPath, skillName);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Check if skills directory exists
|
|
24
|
+
try {
|
|
25
|
+
await access(skillsPath);
|
|
26
|
+
} catch {
|
|
27
|
+
console.error(`Error: No skills directory found at ${skillsPath}`);
|
|
28
|
+
console.error();
|
|
29
|
+
console.error('There are no skills to remove');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check if specific skill directory exists
|
|
34
|
+
try {
|
|
35
|
+
await access(skillPath);
|
|
36
|
+
} catch {
|
|
37
|
+
console.error(`Error: Skill '${skillName}' not found at ${skillPath}`);
|
|
38
|
+
console.error();
|
|
39
|
+
console.error(`Run 'skills list-all' to see available global skills`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Optional: Check for valid SKILL.md (warning only)
|
|
44
|
+
try {
|
|
45
|
+
const skillMdPath = join(skillPath, 'SKILL.md');
|
|
46
|
+
await access(skillMdPath);
|
|
47
|
+
const metadata = await parseSkillMetadata(skillMdPath);
|
|
48
|
+
if (!metadata) {
|
|
49
|
+
console.warn(`Warning: ${skillName} has invalid SKILL.md, removing anyway`);
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
console.warn(`Warning: ${skillName} does not have SKILL.md, removing anyway`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Remove skill directory
|
|
56
|
+
await rm(skillPath, { recursive: true, force: true });
|
|
57
|
+
|
|
58
|
+
// Output success message
|
|
59
|
+
console.log(`✓ Removed global skill '${skillName}' from ${skillPath}`);
|
|
60
|
+
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error(`Error: Failed to remove skill '${skillName}': ${error.message}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createCommand } from 'commander';
|
|
2
|
+
import { ListWidgetsCommand } from './list/index.js';
|
|
3
|
+
import { NewWidgetCommand } from './new/index.js';
|
|
4
|
+
|
|
5
|
+
export function createWidgetsCommand() {
|
|
6
|
+
const widgets = createCommand('widgets');
|
|
7
|
+
widgets.description('Manage widgets');
|
|
8
|
+
|
|
9
|
+
// LIST command
|
|
10
|
+
widgets
|
|
11
|
+
.command('list')
|
|
12
|
+
.description('List all widgets')
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
const command = new ListWidgetsCommand(options);
|
|
15
|
+
await command.process();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// NEW command
|
|
19
|
+
widgets
|
|
20
|
+
.command('new')
|
|
21
|
+
.description('Create a new widget')
|
|
22
|
+
.option('--name <value>', 'Widget name')
|
|
23
|
+
.action(async (options) => {
|
|
24
|
+
const command = new NewWidgetCommand(options);
|
|
25
|
+
await command.process();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return widgets;
|
|
29
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command } from '../../../lib/Command.js';
|
|
2
|
+
import { readdir, access } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
export class ListWidgetsCommand extends Command {
|
|
6
|
+
async process() {
|
|
7
|
+
try {
|
|
8
|
+
const cwd = this.getCWD();
|
|
9
|
+
const widgetsPath = join(cwd, 'src', 'components', 'widgets');
|
|
10
|
+
|
|
11
|
+
// Check if widgets directory exists
|
|
12
|
+
try {
|
|
13
|
+
await access(widgetsPath);
|
|
14
|
+
} catch {
|
|
15
|
+
console.error('Error: src/components/widgets directory not found');
|
|
16
|
+
console.error(`Looking in: ${widgetsPath}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Read directory contents
|
|
21
|
+
const items = await readdir(widgetsPath, { withFileTypes: true });
|
|
22
|
+
|
|
23
|
+
// Filter for directories only
|
|
24
|
+
const widgets = items.filter(dirent => dirent.isDirectory());
|
|
25
|
+
|
|
26
|
+
if (widgets.length === 0) {
|
|
27
|
+
console.log('No widgets found');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Print each widget name
|
|
32
|
+
widgets.forEach(widget => {
|
|
33
|
+
console.log(widget.name);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Error listing widgets:', error.message);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|