@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.
@@ -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
+ }