skillscokac 1.0.0 → 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/README.md +119 -5
- package/bin/skillscokac.js +340 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -30,6 +30,9 @@ The CLI will:
|
|
|
30
30
|
|---------|-------------|
|
|
31
31
|
| `-i, --install-skill <skillName>` | Install a single skill |
|
|
32
32
|
| `-c, --install-collection <collectionId>` | Install all skills from a collection |
|
|
33
|
+
| `-d, --download <skillName> [path]` | Download a skill to a directory (defaults to current directory) |
|
|
34
|
+
| `-u, --upload <skillDir>` | Upload a skill to skills.cokac.com (requires `--apikey`) |
|
|
35
|
+
| `--apikey <key>` | API key for uploading skills |
|
|
33
36
|
| `-l, --list-installed-skills` | List all installed skills |
|
|
34
37
|
| `-r, --remove-skill <skillName>` | Remove an installed skill (with confirmation) |
|
|
35
38
|
| `-f, --remove-skill-force <skillName>` | Remove a skill without confirmation |
|
|
@@ -48,6 +51,24 @@ npx skillscokac -i my-awesome-skill
|
|
|
48
51
|
npx skillscokac -c collection-id-here
|
|
49
52
|
```
|
|
50
53
|
|
|
54
|
+
**Download a skill to a specific directory:**
|
|
55
|
+
```bash
|
|
56
|
+
# Download to a specific path
|
|
57
|
+
npx skillscokac -d my-awesome-skill ./downloads
|
|
58
|
+
|
|
59
|
+
# Download to current directory (path is optional)
|
|
60
|
+
npx skillscokac -d my-awesome-skill
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This will download the skill without installing it to Claude Code's skill directories.
|
|
64
|
+
|
|
65
|
+
**Upload a skill to skills.cokac.com:**
|
|
66
|
+
```bash
|
|
67
|
+
npx skillscokac --upload ./my-skill --apikey ck_live_xxxxx
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This will upload your skill to the marketplace. Requires an API key from skills.cokac.com.
|
|
71
|
+
|
|
51
72
|
**List installed skills:**
|
|
52
73
|
```bash
|
|
53
74
|
npx skillscokac -l
|
|
@@ -77,6 +98,97 @@ When installing, you'll be prompted to choose between:
|
|
|
77
98
|
- **Scope**: Available only in the current project
|
|
78
99
|
- **Use case**: Project-specific skills or testing before making them global
|
|
79
100
|
|
|
101
|
+
## Download to Custom Location
|
|
102
|
+
|
|
103
|
+
If you want to download a skill without installing it to Claude Code's skill directories, use the download command:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx skillscokac -d <skill-name> [path]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The `path` parameter is optional and defaults to the current directory.
|
|
110
|
+
|
|
111
|
+
**Examples:**
|
|
112
|
+
```bash
|
|
113
|
+
# Download to current directory
|
|
114
|
+
npx skillscokac -d my-skill
|
|
115
|
+
|
|
116
|
+
# Download to specific path
|
|
117
|
+
npx skillscokac -d my-skill ./my-downloads
|
|
118
|
+
|
|
119
|
+
# Download to absolute path
|
|
120
|
+
npx skillscokac -d my-skill /path/to/directory
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This will:
|
|
124
|
+
1. Fetch the skill from skills.cokac.com
|
|
125
|
+
2. Display skill information
|
|
126
|
+
3. Download all skill files to `<path>/my-skill/`
|
|
127
|
+
4. **Not** install it to Claude Code's skill directories
|
|
128
|
+
|
|
129
|
+
**Use cases:**
|
|
130
|
+
- Inspecting skill contents before installation
|
|
131
|
+
- Backing up skills locally
|
|
132
|
+
- Sharing skill files with team members
|
|
133
|
+
- Custom deployment workflows
|
|
134
|
+
|
|
135
|
+
## Upload Skills to Marketplace
|
|
136
|
+
|
|
137
|
+
You can upload your own skills to skills.cokac.com using the upload command:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npx skillscokac --upload <skillDir> --apikey <your-api-key>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Requirements
|
|
144
|
+
|
|
145
|
+
1. **API Key**: Get your API key from [skills.cokac.com](https://skills.cokac.com) (format: `ck_live_xxxxx`)
|
|
146
|
+
2. **SKILL.md**: Your skill directory must contain a `SKILL.md` file with frontmatter:
|
|
147
|
+
|
|
148
|
+
```markdown
|
|
149
|
+
---
|
|
150
|
+
name: my-skill
|
|
151
|
+
description: A brief description of what this skill does
|
|
152
|
+
version: 1.0.0
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
# My Skill
|
|
156
|
+
|
|
157
|
+
Your skill instructions here...
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Upload Process
|
|
161
|
+
|
|
162
|
+
The CLI will:
|
|
163
|
+
1. Parse the `SKILL.md` file and extract metadata (name, description)
|
|
164
|
+
2. Create a new skill post on skills.cokac.com
|
|
165
|
+
3. Upload all additional files in the directory (excluding hidden files and common ignore patterns)
|
|
166
|
+
4. Return the skill URL
|
|
167
|
+
|
|
168
|
+
### Example
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# Upload a skill from current directory
|
|
172
|
+
npx skillscokac --upload . --apikey ck_live_xxxxx
|
|
173
|
+
|
|
174
|
+
# Upload a skill from specific directory
|
|
175
|
+
npx skillscokac --upload ./skills/my-awesome-skill --apikey ck_live_xxxxx
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### What Gets Uploaded
|
|
179
|
+
|
|
180
|
+
- **SKILL.md**: Main skill file (required, used as primary content)
|
|
181
|
+
- **Additional files**: Any other files in the directory (e.g., scripts, configs, examples)
|
|
182
|
+
- **Excluded**: Hidden files (`.git`, `.env`), `node_modules`, `__pycache__`
|
|
183
|
+
|
|
184
|
+
### API Key
|
|
185
|
+
|
|
186
|
+
To get an API key:
|
|
187
|
+
1. Visit [skills.cokac.com](https://skills.cokac.com)
|
|
188
|
+
2. Sign in to your account
|
|
189
|
+
3. Go to Settings → API Keys
|
|
190
|
+
4. Generate a new API key
|
|
191
|
+
|
|
80
192
|
## Using Installed Skills
|
|
81
193
|
|
|
82
194
|
After installation, use the skill in Claude Code with:
|
|
@@ -166,11 +278,13 @@ When you install a skill, the CLI downloads and extracts:
|
|
|
166
278
|
|
|
167
279
|
This CLI communicates with the following endpoints:
|
|
168
280
|
|
|
169
|
-
| Endpoint | Purpose |
|
|
170
|
-
|
|
171
|
-
|
|
|
172
|
-
|
|
|
173
|
-
|
|
|
281
|
+
| Endpoint | Method | Purpose |
|
|
282
|
+
|----------|--------|---------|
|
|
283
|
+
| `/api/marketplace` | GET | Fetch marketplace data to find skills |
|
|
284
|
+
| `/api/posts/{postId}/export-skill-zip` | GET | Download skill ZIP package |
|
|
285
|
+
| `/api/collections/{collectionId}` | GET | Fetch collection metadata and skills |
|
|
286
|
+
| `/api/posts` | POST | Create a new skill (requires API key) |
|
|
287
|
+
| `/api/posts/{postId}/files` | POST | Upload additional files to a skill (requires API key) |
|
|
174
288
|
|
|
175
289
|
**Base URL**: `https://skills.cokac.com`
|
|
176
290
|
|
package/bin/skillscokac.js
CHANGED
|
@@ -675,6 +675,83 @@ async function removeAllSkillsCommand(force = false) {
|
|
|
675
675
|
}
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
+
/**
|
|
679
|
+
* Download skill command handler
|
|
680
|
+
*/
|
|
681
|
+
async function downloadSkillCommand(skillName, downloadPath) {
|
|
682
|
+
// Use current directory if no path specified
|
|
683
|
+
if (!downloadPath) {
|
|
684
|
+
downloadPath = process.cwd()
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Fetch skill
|
|
688
|
+
const skill = await fetchSkill(skillName)
|
|
689
|
+
|
|
690
|
+
// Display skill info
|
|
691
|
+
displaySkillInfo(skill)
|
|
692
|
+
console.log()
|
|
693
|
+
|
|
694
|
+
// Resolve download path
|
|
695
|
+
const resolvedPath = path.resolve(downloadPath)
|
|
696
|
+
const targetDir = path.join(resolvedPath, skill.skillName)
|
|
697
|
+
|
|
698
|
+
const spinner = ora('Downloading skill...').start()
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
// Create target directory
|
|
702
|
+
if (fs.existsSync(targetDir)) {
|
|
703
|
+
spinner.text = 'Removing existing directory...'
|
|
704
|
+
fs.rmSync(targetDir, { recursive: true, force: true })
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
spinner.text = 'Creating directory...'
|
|
708
|
+
fs.mkdirSync(targetDir, { recursive: true })
|
|
709
|
+
|
|
710
|
+
// Write SKILL.md
|
|
711
|
+
spinner.text = 'Writing files...'
|
|
712
|
+
const skillMdPath = path.join(targetDir, 'SKILL.md')
|
|
713
|
+
fs.writeFileSync(skillMdPath, skill.skillMd || '', 'utf8')
|
|
714
|
+
|
|
715
|
+
// Write additional files
|
|
716
|
+
if (skill.files && skill.files.length > 0) {
|
|
717
|
+
for (const file of skill.files) {
|
|
718
|
+
// Security: Prevent path traversal attacks
|
|
719
|
+
const normalizedPath = path.normalize(file.path).replace(/^(\.\.(\/|\\|$))+/, '')
|
|
720
|
+
const filePath = path.join(targetDir, normalizedPath)
|
|
721
|
+
|
|
722
|
+
// Verify that the final path is still within targetDir
|
|
723
|
+
const resolvedFilePath = path.resolve(filePath)
|
|
724
|
+
const resolvedTargetDir = path.resolve(targetDir)
|
|
725
|
+
|
|
726
|
+
if (!resolvedFilePath.startsWith(resolvedTargetDir + path.sep) && resolvedFilePath !== resolvedTargetDir) {
|
|
727
|
+
console.warn(chalk.yellow(`⚠ Skipping potentially unsafe file path: ${file.path}`))
|
|
728
|
+
continue
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const fileDir = path.dirname(filePath)
|
|
732
|
+
|
|
733
|
+
// Create subdirectories if needed
|
|
734
|
+
if (!fs.existsSync(fileDir)) {
|
|
735
|
+
fs.mkdirSync(fileDir, { recursive: true })
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
fs.writeFileSync(filePath, file.content || '', 'utf8')
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
spinner.succeed(chalk.green('Downloaded successfully!'))
|
|
743
|
+
console.log()
|
|
744
|
+
console.log(chalk.dim(' Location: ') + chalk.cyan(targetDir))
|
|
745
|
+
console.log(chalk.dim(' Files: ') + chalk.white(`${1 + (skill.files ? skill.files.length : 0)} file${skill.files && skill.files.length !== 0 ? 's' : ''}`))
|
|
746
|
+
console.log()
|
|
747
|
+
|
|
748
|
+
} catch (error) {
|
|
749
|
+
spinner.fail(chalk.red('Failed to download skill'))
|
|
750
|
+
console.error(chalk.red('\nError:'), error.message)
|
|
751
|
+
process.exit(1)
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
678
755
|
/**
|
|
679
756
|
* List installed skills command handler
|
|
680
757
|
*/
|
|
@@ -767,6 +844,254 @@ async function listInstalledSkillsCommand() {
|
|
|
767
844
|
}
|
|
768
845
|
}
|
|
769
846
|
|
|
847
|
+
/**
|
|
848
|
+
* Create skill on SkillsCokac API
|
|
849
|
+
*/
|
|
850
|
+
async function createSkill(skillData, apiKey) {
|
|
851
|
+
const payload = {
|
|
852
|
+
type: 'SKILL',
|
|
853
|
+
title: skillData.name,
|
|
854
|
+
description: skillData.description,
|
|
855
|
+
content: skillData.content,
|
|
856
|
+
skillMd: skillData.content,
|
|
857
|
+
visibility: skillData.visibility || 'PUBLIC',
|
|
858
|
+
tags: skillData.tags || ['claude-code', 'agent-skill']
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
const spinner = ora('Creating skill on SkillsCokac...').start()
|
|
862
|
+
|
|
863
|
+
try {
|
|
864
|
+
const response = await axios.post(`${API_BASE_URL}/api/posts`, payload, {
|
|
865
|
+
headers: {
|
|
866
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
867
|
+
'Content-Type': 'application/json',
|
|
868
|
+
'User-Agent': `skillscokac-cli/${VERSION}`
|
|
869
|
+
}
|
|
870
|
+
})
|
|
871
|
+
|
|
872
|
+
spinner.succeed(chalk.green('Skill created successfully!'))
|
|
873
|
+
const skill = response.data
|
|
874
|
+
console.log(chalk.dim(' ID: ') + chalk.cyan(skill.id))
|
|
875
|
+
console.log(chalk.dim(' URL: ') + chalk.cyan(`https://skills.cokac.com/s/${skill.id}`))
|
|
876
|
+
console.log()
|
|
877
|
+
|
|
878
|
+
return skill
|
|
879
|
+
} catch (error) {
|
|
880
|
+
spinner.fail(chalk.red('Failed to create skill'))
|
|
881
|
+
if (error.response) {
|
|
882
|
+
console.error(chalk.red(' Status:'), error.response.status)
|
|
883
|
+
console.error(chalk.red(' Response:'), error.response.data)
|
|
884
|
+
} else {
|
|
885
|
+
console.error(chalk.red(' Error:'), error.message)
|
|
886
|
+
}
|
|
887
|
+
throw error
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Upload skill files to SkillsCokac API
|
|
893
|
+
*/
|
|
894
|
+
async function uploadSkillFiles(skillId, skillDir, apiKey) {
|
|
895
|
+
const spinner = ora('Uploading additional files...').start()
|
|
896
|
+
|
|
897
|
+
let uploadedCount = 0
|
|
898
|
+
let failedCount = 0
|
|
899
|
+
const files = []
|
|
900
|
+
|
|
901
|
+
// Recursively find all files in the directory
|
|
902
|
+
function findFiles(dir, baseDir) {
|
|
903
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
904
|
+
|
|
905
|
+
for (const entry of entries) {
|
|
906
|
+
const fullPath = path.join(dir, entry.name)
|
|
907
|
+
|
|
908
|
+
if (entry.isDirectory()) {
|
|
909
|
+
// Skip hidden directories and common ignore patterns
|
|
910
|
+
if (entry.name.startsWith('.') || entry.name === '__pycache__' || entry.name === 'node_modules') {
|
|
911
|
+
continue
|
|
912
|
+
}
|
|
913
|
+
findFiles(fullPath, baseDir)
|
|
914
|
+
} else if (entry.isFile()) {
|
|
915
|
+
// Skip SKILL.md at root level (already uploaded as main content)
|
|
916
|
+
const relativePath = path.relative(baseDir, fullPath)
|
|
917
|
+
if (relativePath === 'SKILL.md') {
|
|
918
|
+
continue
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Skip hidden files
|
|
922
|
+
if (entry.name.startsWith('.')) {
|
|
923
|
+
continue
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
files.push({ fullPath, relativePath })
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
try {
|
|
932
|
+
findFiles(skillDir, skillDir)
|
|
933
|
+
|
|
934
|
+
if (files.length === 0) {
|
|
935
|
+
spinner.succeed(chalk.green('No additional files to upload'))
|
|
936
|
+
return
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
spinner.text = `Found ${files.length} file${files.length !== 1 ? 's' : ''} to upload...`
|
|
940
|
+
|
|
941
|
+
for (const file of files) {
|
|
942
|
+
try {
|
|
943
|
+
// Try to read as text
|
|
944
|
+
let content
|
|
945
|
+
try {
|
|
946
|
+
content = fs.readFileSync(file.fullPath, 'utf8')
|
|
947
|
+
} catch (err) {
|
|
948
|
+
// Skip binary files
|
|
949
|
+
spinner.warn(chalk.yellow(` Skipping binary file: ${file.relativePath}`))
|
|
950
|
+
continue
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const filePayload = {
|
|
954
|
+
path: file.relativePath.replace(/\\/g, '/'), // Normalize Windows paths
|
|
955
|
+
content: content
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
spinner.text = `Uploading: ${file.relativePath}`
|
|
959
|
+
|
|
960
|
+
const response = await axios.post(
|
|
961
|
+
`${API_BASE_URL}/api/posts/${skillId}/files`,
|
|
962
|
+
filePayload,
|
|
963
|
+
{
|
|
964
|
+
headers: {
|
|
965
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
966
|
+
'Content-Type': 'application/json',
|
|
967
|
+
'User-Agent': `skillscokac-cli/${VERSION}`
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
if (response.status === 201) {
|
|
973
|
+
uploadedCount++
|
|
974
|
+
} else {
|
|
975
|
+
failedCount++
|
|
976
|
+
}
|
|
977
|
+
} catch (error) {
|
|
978
|
+
failedCount++
|
|
979
|
+
console.error(chalk.red(`\n ✗ Failed to upload: ${file.relativePath}`))
|
|
980
|
+
if (error.response) {
|
|
981
|
+
console.error(chalk.red(' Status:'), error.response.status)
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (failedCount === 0) {
|
|
987
|
+
spinner.succeed(chalk.green(`Uploaded ${uploadedCount} file${uploadedCount !== 1 ? 's' : ''}`))
|
|
988
|
+
} else {
|
|
989
|
+
spinner.warn(chalk.yellow(`Uploaded ${uploadedCount}, Failed ${failedCount}`))
|
|
990
|
+
}
|
|
991
|
+
console.log()
|
|
992
|
+
|
|
993
|
+
} catch (error) {
|
|
994
|
+
spinner.fail(chalk.red('Failed to upload files'))
|
|
995
|
+
throw error
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Upload skill command handler
|
|
1001
|
+
*/
|
|
1002
|
+
async function uploadSkillCommand(skillDir, apiKey) {
|
|
1003
|
+
// Validate API key
|
|
1004
|
+
if (!apiKey) {
|
|
1005
|
+
console.log(chalk.red('✗ API key is required'))
|
|
1006
|
+
console.log(chalk.dim('Usage: npx skillscokac --upload <skillDir> --apikey <key>'))
|
|
1007
|
+
console.log()
|
|
1008
|
+
process.exit(1)
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Resolve skill directory
|
|
1012
|
+
const resolvedSkillDir = path.resolve(skillDir)
|
|
1013
|
+
const skillMdPath = path.join(resolvedSkillDir, 'SKILL.md')
|
|
1014
|
+
|
|
1015
|
+
// Validate directory exists
|
|
1016
|
+
if (!fs.existsSync(resolvedSkillDir)) {
|
|
1017
|
+
console.log(chalk.red(`✗ Directory not found: ${resolvedSkillDir}`))
|
|
1018
|
+
console.log()
|
|
1019
|
+
process.exit(1)
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Validate SKILL.md exists
|
|
1023
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
1024
|
+
console.log(chalk.red(`✗ SKILL.md not found in: ${resolvedSkillDir}`))
|
|
1025
|
+
console.log(chalk.dim(' The skill directory must contain a SKILL.md file'))
|
|
1026
|
+
console.log()
|
|
1027
|
+
process.exit(1)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
console.log(boxen(
|
|
1031
|
+
chalk.bold.cyan('SkillsCokac Skill Uploader'),
|
|
1032
|
+
{
|
|
1033
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
1034
|
+
borderStyle: 'round',
|
|
1035
|
+
borderColor: 'cyan'
|
|
1036
|
+
}
|
|
1037
|
+
))
|
|
1038
|
+
console.log()
|
|
1039
|
+
console.log(chalk.dim(' Directory: ') + chalk.white(resolvedSkillDir))
|
|
1040
|
+
console.log()
|
|
1041
|
+
|
|
1042
|
+
try {
|
|
1043
|
+
// Step 1: Parse SKILL.md
|
|
1044
|
+
const spinner = ora('Parsing SKILL.md...').start()
|
|
1045
|
+
const skillMdContent = fs.readFileSync(skillMdPath, 'utf8')
|
|
1046
|
+
const { metadata } = parseFrontmatter(skillMdContent)
|
|
1047
|
+
|
|
1048
|
+
if (!metadata.name) {
|
|
1049
|
+
spinner.fail(chalk.red('SKILL.md must have a "name" in frontmatter'))
|
|
1050
|
+
process.exit(1)
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (!metadata.description) {
|
|
1054
|
+
spinner.fail(chalk.red('SKILL.md must have a "description" in frontmatter'))
|
|
1055
|
+
process.exit(1)
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const skillData = {
|
|
1059
|
+
name: metadata.name,
|
|
1060
|
+
description: metadata.description,
|
|
1061
|
+
content: skillMdContent,
|
|
1062
|
+
visibility: 'PUBLIC',
|
|
1063
|
+
tags: ['claude-code', 'agent-skill']
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
spinner.succeed(chalk.green('SKILL.md parsed'))
|
|
1067
|
+
console.log(chalk.dim(' Name: ') + chalk.white(skillData.name))
|
|
1068
|
+
console.log(chalk.dim(' Description: ') + chalk.white(skillData.description))
|
|
1069
|
+
console.log()
|
|
1070
|
+
|
|
1071
|
+
// Step 2: Create skill
|
|
1072
|
+
const skill = await createSkill(skillData, apiKey)
|
|
1073
|
+
|
|
1074
|
+
// Step 3: Upload additional files
|
|
1075
|
+
await uploadSkillFiles(skill.id, resolvedSkillDir, apiKey)
|
|
1076
|
+
|
|
1077
|
+
// Success summary
|
|
1078
|
+
console.log(boxen(
|
|
1079
|
+
chalk.bold.green('✓ Upload complete!\n\n') +
|
|
1080
|
+
chalk.dim('Skill URL: ') + chalk.cyan(`https://skills.cokac.com/s/${skill.id}`),
|
|
1081
|
+
{
|
|
1082
|
+
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
1083
|
+
borderStyle: 'round',
|
|
1084
|
+
borderColor: 'green'
|
|
1085
|
+
}
|
|
1086
|
+
))
|
|
1087
|
+
console.log()
|
|
1088
|
+
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
console.error(chalk.red('\n✗ Upload failed:'), error.message)
|
|
1091
|
+
process.exit(1)
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
770
1095
|
/**
|
|
771
1096
|
* Setup CLI with Commander
|
|
772
1097
|
*/
|
|
@@ -780,6 +1105,9 @@ program
|
|
|
780
1105
|
program
|
|
781
1106
|
.option('-i, --install-skill <skillName>', 'Install a skill by name')
|
|
782
1107
|
.option('-c, --install-collection <collectionId>', 'Install all skills from a collection')
|
|
1108
|
+
.option('-d, --download <args...>', 'Download a skill to a directory (usage: --download <skillName> [path], defaults to current directory)')
|
|
1109
|
+
.option('-u, --upload <skillDir>', 'Upload a skill from a directory (requires --apikey)')
|
|
1110
|
+
.option('--apikey <key>', 'API key for uploading skills')
|
|
783
1111
|
.option('-r, --remove-skill <skillName>', 'Remove an installed skill')
|
|
784
1112
|
.option('-f, --remove-skill-force <skillName>', 'Remove skill from all locations without confirmation')
|
|
785
1113
|
.option('-a, --remove-all-skills', 'Remove all installed skills')
|
|
@@ -796,6 +1124,18 @@ const options = program.opts()
|
|
|
796
1124
|
await installSkillCommand(options.installSkill)
|
|
797
1125
|
} else if (options.installCollection) {
|
|
798
1126
|
await installCollectionCommand(options.installCollection)
|
|
1127
|
+
} else if (options.download) {
|
|
1128
|
+
// Download expects one or two arguments: skillName and optional path
|
|
1129
|
+
if (options.download.length < 1 || options.download.length > 2) {
|
|
1130
|
+
console.log(chalk.red('✗ Invalid arguments for --download'))
|
|
1131
|
+
console.log(chalk.dim('Usage: npx skillscokac --download <skillName> [path]'))
|
|
1132
|
+
console.log()
|
|
1133
|
+
process.exit(1)
|
|
1134
|
+
}
|
|
1135
|
+
const [skillName, downloadPath] = options.download
|
|
1136
|
+
await downloadSkillCommand(skillName, downloadPath)
|
|
1137
|
+
} else if (options.upload) {
|
|
1138
|
+
await uploadSkillCommand(options.upload, options.apikey)
|
|
799
1139
|
} else if (options.removeAllSkillsForce) {
|
|
800
1140
|
await removeAllSkillsCommand(true)
|
|
801
1141
|
} else if (options.removeAllSkills) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillscokac",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CLI tool to install and manage Claude Code skills from skills.cokac.com",
|
|
5
5
|
"main": "bin/skillscokac.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"skillscokac": "
|
|
7
|
+
"skillscokac": "bin/skillscokac.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
33
|
-
"url": "https://github.com/kstost/skillscokac.git"
|
|
33
|
+
"url": "git+https://github.com/kstost/skillscokac.git"
|
|
34
34
|
},
|
|
35
35
|
"bugs": {
|
|
36
36
|
"url": "https://github.com/kstost/skillscokac/issues"
|