myskill 1.0.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/LICENSE +21 -0
- package/README.md +141 -0
- package/bin/myskill.js +137 -0
- package/package.json +56 -0
- package/src/commands/config.js +38 -0
- package/src/commands/convert.js +111 -0
- package/src/commands/create.js +204 -0
- package/src/commands/doctor.js +60 -0
- package/src/commands/find.js +108 -0
- package/src/commands/install.js +93 -0
- package/src/commands/list.js +60 -0
- package/src/commands/pull.js +93 -0
- package/src/commands/run.js +83 -0
- package/src/commands/uninstall.js +131 -0
- package/src/commands/validate.js +87 -0
- package/src/platforms/claude.js +70 -0
- package/src/platforms/codex.js +25 -0
- package/src/platforms/gemini.js +19 -0
- package/src/platforms/index.js +27 -0
- package/src/platforms/opencode.js +36 -0
- package/src/templates/generateSkill.js +20 -0
- package/src/utils/config.js +41 -0
- package/src/utils/prompt.js +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sarfraz Ahmed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# myskill
|
|
2
|
+
|
|
3
|
+
> The Universal CLI for AI Agent Skills
|
|
4
|
+
|
|
5
|
+
`myskill` is a powerful command-line tool designed to standardize the creation, management, and sharing of "Agent Skills" across the modern AI coding ecosystem. It abstracts away the differences between platforms like **Claude Code**, **OpenCode**, **OpenAI Codex**, and **Gemini CLI**, allowing developers to write skills once and deploy them anywhere.
|
|
6
|
+
|
|
7
|
+
## 🚀 Key Features
|
|
8
|
+
|
|
9
|
+
- **Unified Creation**: Generate valid, schema-compliant skills for any supported platform.
|
|
10
|
+
- **Strict Validation**: Ensure your skills meet platform requirements (YAML frontmatter, file structure).
|
|
11
|
+
- **Cross-Platform**: Works seamlessly on Windows, macOS, and Linux.
|
|
12
|
+
- **Workspace Management**: Initialize skill workspaces and run experimental skills locally.
|
|
13
|
+
- **Discovery**: Fuzzy find installed skills across all global and local scopes.
|
|
14
|
+
- **Git Integration**: Pull and update skills directly from remote repositories.
|
|
15
|
+
- **Interactive Experience**: Cancel any interactive prompt with **Escape** key or **Ctrl+C**.
|
|
16
|
+
- **Configurable Paths**: Override default platform paths for custom setups.
|
|
17
|
+
|
|
18
|
+
## 📦 Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g myskill
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 🛠️ Supported Platforms
|
|
25
|
+
|
|
26
|
+
| Platform | ID | Key Features Supported |
|
|
27
|
+
| :--------------- | :--------- | :---------------------------------------- |
|
|
28
|
+
| **Claude Code** | `claude` | `allowed-tools`, `context: fork`, `hooks` |
|
|
29
|
+
| **OpenCode** | `opencode` | `license`, `compatibility`, `metadata` |
|
|
30
|
+
| **OpenAI Codex** | `codex` | `short-description` metadata |
|
|
31
|
+
| **Gemini CLI** | `gemini` | Standard skill structure |
|
|
32
|
+
|
|
33
|
+
## 📖 Usage Guide
|
|
34
|
+
|
|
35
|
+
### Creating Skills
|
|
36
|
+
|
|
37
|
+
Create a new skill interactively:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
myskill create
|
|
41
|
+
# Cancel anytime with Escape key or Ctrl+C
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or use flags for automation:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
myskill create \
|
|
48
|
+
--name "git-helper" \
|
|
49
|
+
--platform claude \
|
|
50
|
+
--description "Automates complex git workflows" \
|
|
51
|
+
--scope project \
|
|
52
|
+
--non-interactive
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Discovering Skills
|
|
56
|
+
|
|
57
|
+
List all installed skills for a platform:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
myskill list --platform claude
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Find a skill by name or description (supports fuzzy search):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
myskill find "deploy"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Validate a skill's structure and frontmatter:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
myskill validate ./git-helper
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Run a skill script (experimental):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
myskill run ./git-helper -- arg1 arg2
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Sharing Skills
|
|
82
|
+
|
|
83
|
+
Install a local skill directory to the global platform path:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
myskill install ./my-local-skill --platform claude
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Pull (clone/update) a skill from a Git repository:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
myskill pull https://github.com/username/awesome-skill.git --platform opencode
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Convert a skill from one platform format to another:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
myskill convert ./claude-skill --to opencode
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Uninstall a skill:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
myskill uninstall git-helper --platform claude
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Configuration & Health
|
|
108
|
+
|
|
109
|
+
Override default paths (useful for custom setups):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
myskill config set claude.path "/custom/path/to/skills"
|
|
113
|
+
myskill config list
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Check system health (Git, permissions, paths):
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
myskill doctor
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## 🔧 Command Reference
|
|
123
|
+
|
|
124
|
+
| Command | Description | Usage | Options |
|
|
125
|
+
| ----------- | ---------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
126
|
+
| `create` | Create a new skill interactively or via flags. | `myskill create [options]` | `-n, --name <name>`: Skill name (lowercase alphanumeric with hyphens)<br>`-p, --platform <platform>`: Target platform (claude, opencode, codex, gemini)<br>`-d, --description <description>`: Skill description<br>`-s, --scope <scope>`: Scope (global, project). Default: project<br>`--non-interactive`: Run without interactive prompts |
|
|
127
|
+
| `list` | List all installed skills for a platform. | `myskill list [options]` | `-p, --platform <platform>`: Filter by platform |
|
|
128
|
+
| `find` | Find skills by name or description with fuzzy search. | `myskill find [query] [options]` | `[query]`: Search query (supports fuzzy matching)<br>`-p, --platform <platform>`: Filter by platform |
|
|
129
|
+
| `validate` | Validate a skill's structure and frontmatter against platform schemas. | `myskill validate [path] [options]` | `[path]`: Path to skill directory. Default: current directory<br>`-p, --platform <platform>`: Validate against specific platform |
|
|
130
|
+
| `run` | Run a skill script (experimental). | `myskill run <skill> [args...]` | `<skill>`: Skill name or path<br>`[args...]`: Arguments to pass to the skill script |
|
|
131
|
+
| `install` | Install a local skill directory to the global platform path. | `myskill install <path> [options]` | `<path>`: Path to skill directory<br>`-p, --platform <platform>`: Target platform<br>`-f, --force`: Force overwrite if already installed<br>`--non-interactive`: Run without interactive prompts |
|
|
132
|
+
| `pull` | Pull (clone/update) a skill from a Git repository. | `myskill pull <repoUrl> [options]` | `<repoUrl>`: Repository URL<br>`-p, --platform <platform>`: Target platform<br>`-n, --name <name>`: Custom skill name<br>`--non-interactive`: Skip prompts |
|
|
133
|
+
| `convert` | Convert a skill from one platform format to another. | `myskill convert <path> [options]` | `<path>`: Path to source skill<br>`-t, --to <platform>`: Target platform<br>`-f, --force`: Force overwrite if output exists<br>`--non-interactive`: Reserved for future interactive features |
|
|
134
|
+
| `uninstall` | Uninstall a skill from global or local paths. | `myskill uninstall [name] [options]` | `[name]`: Skill name<br>`-p, --platform <platform>`: Platform context<br>`--non-interactive`: Skip confirmation |
|
|
135
|
+
| `config` | Manage configuration settings (e.g., custom paths). | `myskill config <action> [key] [value]` | `<action>`: Action (get, set, list)<br>`[key]`: Config key (e.g., claude.path)<br>`[value]`: Config value (for set) |
|
|
136
|
+
| `doctor` | Check system health (Git, permissions, paths). | `myskill doctor` | None |
|
|
137
|
+
|
|
138
|
+
## 🗺️ Roadmap
|
|
139
|
+
|
|
140
|
+
- [ ] **Ecosystem & Sharing**: Implement `myskill publish` to upload skills to a central registry (S3, GitHub Packages, or a custom index).
|
|
141
|
+
- [ ] **Generate from Prompt**: `myskill create --prompt "A skill that checks AWS S3 buckets"`. Use an LLM API to generate the `SKILL.md` content and initial script code.
|
package/bin/myskill.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const packageJson = require("../package.json");
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name("myskill")
|
|
13
|
+
.description("CLI tool for creating and managing AI agent skills")
|
|
14
|
+
.version(packageJson.version);
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command("create")
|
|
18
|
+
.description("Create a new skill")
|
|
19
|
+
.option("-n, --name <name>", "Skill name")
|
|
20
|
+
.option(
|
|
21
|
+
"-p, --platform <platform>",
|
|
22
|
+
"Target platform (claude, opencode, codex, gemini)",
|
|
23
|
+
)
|
|
24
|
+
.option("-d, --description <description>", "Skill description")
|
|
25
|
+
.option("-s, --scope <scope>", "Scope (global, project)", "project")
|
|
26
|
+
.option("--non-interactive", "Run without interactive prompts")
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
const { create } = await import("../src/commands/create.js");
|
|
29
|
+
create(options);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command("validate")
|
|
34
|
+
.description("Validate a skill")
|
|
35
|
+
.argument("[path]", "Path to skill directory", ".")
|
|
36
|
+
.option("-p, --platform <platform>", "Validate against specific platform")
|
|
37
|
+
.action(async (pathStr, options) => {
|
|
38
|
+
const { validate } = await import("../src/commands/validate.js");
|
|
39
|
+
validate(pathStr, options);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command("list")
|
|
44
|
+
.description("List installed skills")
|
|
45
|
+
.option("-p, --platform <platform>", "Filter by platform")
|
|
46
|
+
.action(async (options) => {
|
|
47
|
+
const { list } = await import("../src/commands/list.js");
|
|
48
|
+
list(options);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
program
|
|
52
|
+
.command("install")
|
|
53
|
+
.description("Install a skill")
|
|
54
|
+
.argument("<path>", "Path to skill directory")
|
|
55
|
+
.option("-p, --platform <platform>", "Target platform")
|
|
56
|
+
.option("-f, --force", "Force overwrite if installed")
|
|
57
|
+
.option("--non-interactive", "Run without interactive prompts")
|
|
58
|
+
.action(async (pathStr, options) => {
|
|
59
|
+
const { install } = await import("../src/commands/install.js");
|
|
60
|
+
install(pathStr, options);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
program
|
|
64
|
+
.command("convert")
|
|
65
|
+
.description("Convert a skill to another format")
|
|
66
|
+
.argument("<path>", "Path to source skill")
|
|
67
|
+
.option("-t, --to <platform>", "Target platform")
|
|
68
|
+
.option("-f, --force", "Force overwrite if output exists")
|
|
69
|
+
.option("--non-interactive", "Fail if output exists (unless forced)")
|
|
70
|
+
.action(async (pathStr, options) => {
|
|
71
|
+
const { convert } = await import("../src/commands/convert.js");
|
|
72
|
+
convert(pathStr, options);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
program
|
|
76
|
+
.command("find")
|
|
77
|
+
.description("Find skills")
|
|
78
|
+
.argument("[query]", "Search query")
|
|
79
|
+
.option("-p, --platform <platform>", "Filter by platform")
|
|
80
|
+
.action(async (query, options) => {
|
|
81
|
+
const { find } = await import("../src/commands/find.js");
|
|
82
|
+
find(query, options);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
program
|
|
86
|
+
.command("uninstall")
|
|
87
|
+
.description("Uninstall a skill")
|
|
88
|
+
.argument("[name]", "Skill name")
|
|
89
|
+
.option("-p, --platform <platform>", "Platform context")
|
|
90
|
+
.option("--non-interactive", "Skip confirmation")
|
|
91
|
+
.action(async (name, options) => {
|
|
92
|
+
const { uninstall } = await import("../src/commands/uninstall.js");
|
|
93
|
+
uninstall(name, options);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
program
|
|
97
|
+
.command("config")
|
|
98
|
+
.description("Manage configuration")
|
|
99
|
+
.argument("<action>", "Action: get, set, list")
|
|
100
|
+
.argument("[key]", "Config key (e.g. claude.path)")
|
|
101
|
+
.argument("[value]", "Config value")
|
|
102
|
+
.action(async (action, key, value) => {
|
|
103
|
+
const { config } = await import("../src/commands/config.js");
|
|
104
|
+
config(action, key, value);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
program
|
|
108
|
+
.command("pull")
|
|
109
|
+
.description("Pull or clone a skill from a git repository")
|
|
110
|
+
.argument("<repoUrl>", "Repository URL")
|
|
111
|
+
.option("-p, --platform <platform>", "Target platform")
|
|
112
|
+
.option("-n, --name <name>", "Custom skill name")
|
|
113
|
+
.option("--non-interactive", "Skip prompts")
|
|
114
|
+
.action(async (repoUrl, options) => {
|
|
115
|
+
const { pull } = await import("../src/commands/pull.js");
|
|
116
|
+
pull(repoUrl, options);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
program
|
|
120
|
+
.command("doctor")
|
|
121
|
+
.description("Check system health and configuration")
|
|
122
|
+
.action(async () => {
|
|
123
|
+
const { doctor } = await import("../src/commands/doctor.js");
|
|
124
|
+
doctor();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
program
|
|
128
|
+
.command("run")
|
|
129
|
+
.description("Run a skill script (experimental)")
|
|
130
|
+
.argument("<skill>", "Skill name or path")
|
|
131
|
+
.argument("[args...]", "Arguments to pass to the skill script")
|
|
132
|
+
.action(async (skillName, args) => {
|
|
133
|
+
const { run } = await import("../src/commands/run.js");
|
|
134
|
+
run(skillName, args);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "myskill",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for creating and managing AI agent skills",
|
|
5
|
+
"author": "Sarfraz Ahmed <sarfraznawaz2005@gmail.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/sarfraznawaz2005/myskill#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/sarfraznawaz2005/myskill.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/sarfraznawaz2005/myskill/issues"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18.0.0"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin",
|
|
20
|
+
"src",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"main": "./bin/myskill.js",
|
|
25
|
+
"bin": {
|
|
26
|
+
"myskill": "bin/myskill.js"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "prettier --write \"src/**/*.js\" \"bin/**/*.js\"",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"prepublishOnly": "npm run build && npm test"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"cli",
|
|
36
|
+
"ai",
|
|
37
|
+
"skills",
|
|
38
|
+
"claude",
|
|
39
|
+
"opencode"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"chalk": "^5.4.1",
|
|
43
|
+
"commander": "^13.1.0",
|
|
44
|
+
"env-paths": "^3.0.0",
|
|
45
|
+
"fs-extra": "^11.3.0",
|
|
46
|
+
"fuse.js": "^7.1.0",
|
|
47
|
+
"inquirer": "^12.4.2",
|
|
48
|
+
"js-yaml": "^4.1.0",
|
|
49
|
+
"prettier": "^3.8.0",
|
|
50
|
+
"simple-git": "^3.30.0",
|
|
51
|
+
"zod": "^3.24.2"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"vitest": "^4.0.17"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getConfig, setConfig } from "../utils/config.js";
|
|
3
|
+
|
|
4
|
+
export async function config(action, key, value) {
|
|
5
|
+
if (action === "list") {
|
|
6
|
+
const cfg = await getConfig();
|
|
7
|
+
console.log(JSON.stringify(cfg, null, 2));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (action === "get") {
|
|
12
|
+
if (!key) {
|
|
13
|
+
console.error(chalk.red("Error: Key required for get"));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const cfg = await getConfig();
|
|
17
|
+
// Simple deep get support
|
|
18
|
+
const keys = key.split(".");
|
|
19
|
+
let val = cfg;
|
|
20
|
+
for (const k of keys) {
|
|
21
|
+
val = val ? val[k] : undefined;
|
|
22
|
+
}
|
|
23
|
+
console.log(val !== undefined ? val : chalk.gray("undefined"));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (action === "set") {
|
|
28
|
+
if (!key || !value) {
|
|
29
|
+
console.error(chalk.red("Error: Key and Value required for set"));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
await setConfig(key, value);
|
|
33
|
+
console.log(chalk.green(`Set ${key} = ${value}`));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.error(chalk.red(`Unknown action: ${action}`));
|
|
38
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import yaml from "js-yaml";
|
|
5
|
+
import { platforms } from "../platforms/index.js";
|
|
6
|
+
import { generateSkill } from "../templates/generateSkill.js";
|
|
7
|
+
|
|
8
|
+
export async function convert(sourcePath, options = {}) {
|
|
9
|
+
const resolvedSource = path.resolve(sourcePath);
|
|
10
|
+
|
|
11
|
+
if (!options.to) {
|
|
12
|
+
console.error(chalk.red("Error: Target platform (--to) is required"));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const targetPlatform = platforms[options.to];
|
|
17
|
+
if (!targetPlatform) {
|
|
18
|
+
console.error(chalk.red(`Error: Unknown target platform '${options.to}'`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!(await fs.pathExists(path.join(resolvedSource, "SKILL.md")))) {
|
|
23
|
+
console.error(chalk.red(`Error: SKILL.md not found in ${resolvedSource}`));
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const content = await fs.readFile(
|
|
28
|
+
path.join(resolvedSource, "SKILL.md"),
|
|
29
|
+
"utf8",
|
|
30
|
+
);
|
|
31
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
32
|
+
|
|
33
|
+
if (!match) {
|
|
34
|
+
console.error(chalk.red("Error: Invalid front matter in source file"));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let sourceFm;
|
|
39
|
+
try {
|
|
40
|
+
sourceFm = yaml.load(match[1]);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.error(chalk.red("Error: YAML parsing failed"));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const markdownBody = content.replace(/^---\n[\s\S]*?\n---/, "").trim();
|
|
47
|
+
|
|
48
|
+
const newFm = {
|
|
49
|
+
name: sourceFm.name,
|
|
50
|
+
description: sourceFm.description,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (targetPlatform.id === "opencode") {
|
|
54
|
+
newFm.compatibility = "opencode";
|
|
55
|
+
if (sourceFm.license) newFm.license = sourceFm.license;
|
|
56
|
+
|
|
57
|
+
const standardKeys = ["name", "description", "license", "compatibility"];
|
|
58
|
+
const extras = {};
|
|
59
|
+
for (const key of Object.keys(sourceFm)) {
|
|
60
|
+
if (!standardKeys.includes(key)) extras[key] = sourceFm[key];
|
|
61
|
+
}
|
|
62
|
+
if (Object.keys(extras).length > 0) newFm.metadata = extras;
|
|
63
|
+
} else if (targetPlatform.id === "codex") {
|
|
64
|
+
newFm.metadata = { "short-description": sourceFm.description.slice(0, 50) };
|
|
65
|
+
} else if (targetPlatform.id === "claude") {
|
|
66
|
+
if (sourceFm["allowed-tools"])
|
|
67
|
+
newFm["allowed-tools"] = sourceFm["allowed-tools"];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const newContent = generateSkill(newFm, markdownBody);
|
|
71
|
+
const targetDirName = `${sourceFm.name}_${targetPlatform.id}`;
|
|
72
|
+
const targetDir = path.join(path.dirname(resolvedSource), targetDirName);
|
|
73
|
+
|
|
74
|
+
if (await fs.pathExists(targetDir)) {
|
|
75
|
+
if (options.force) {
|
|
76
|
+
// Continue
|
|
77
|
+
} else if (options.nonInteractive) {
|
|
78
|
+
console.error(
|
|
79
|
+
chalk.red(
|
|
80
|
+
`Error: Target directory ${targetDir} already exists. Use --force to overwrite.`,
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
} else {
|
|
85
|
+
console.error(
|
|
86
|
+
chalk.red(`Error: Target directory ${targetDir} already exists`),
|
|
87
|
+
);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
await fs.ensureDir(targetDir);
|
|
94
|
+
await fs.writeFile(path.join(targetDir, "SKILL.md"), newContent);
|
|
95
|
+
|
|
96
|
+
const items = await fs.readdir(resolvedSource);
|
|
97
|
+
for (const item of items) {
|
|
98
|
+
if (item !== "SKILL.md" && item !== ".git" && item !== "node_modules") {
|
|
99
|
+
await fs.copy(
|
|
100
|
+
path.join(resolvedSource, item),
|
|
101
|
+
path.join(targetDir, item),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(chalk.green(`Successfully converted skill to ${targetDir}`));
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.error(chalk.red(`Conversion failed: ${e.message}`));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|