myskill 1.0.0 → 1.2.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 +2 -0
- package/bin/myskill.js +19 -1
- package/package.json +1 -1
- package/src/commands/detect.js +32 -0
- package/src/commands/install.js +109 -45
- package/src/commands/platforms.js +10 -0
- package/src/commands/uninstall.js +100 -83
- package/src/utils/skills.js +101 -0
package/README.md
CHANGED
|
@@ -125,6 +125,8 @@ myskill doctor
|
|
|
125
125
|
| ----------- | ---------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
126
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
127
|
| `list` | List all installed skills for a platform. | `myskill list [options]` | `-p, --platform <platform>`: Filter by platform |
|
|
128
|
+
| `platforms` | List all supported platforms. | `myskill platforms` | None |
|
|
129
|
+
| `detect` | Detect skills in directory and identify their platforms. | `myskill detect [path]` | `[path]`: Path to directory to scan (default: current directory) |
|
|
128
130
|
| `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
131
|
| `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
132
|
| `run` | Run a skill script (experimental). | `myskill run <skill> [args...]` | `<skill>`: Skill name or path<br>`[args...]`: Arguments to pass to the skill script |
|
package/bin/myskill.js
CHANGED
|
@@ -51,8 +51,9 @@ program
|
|
|
51
51
|
program
|
|
52
52
|
.command("install")
|
|
53
53
|
.description("Install a skill")
|
|
54
|
-
.argument("
|
|
54
|
+
.argument("[path]", "Path to skill directory")
|
|
55
55
|
.option("-p, --platform <platform>", "Target platform")
|
|
56
|
+
|
|
56
57
|
.option("-f, --force", "Force overwrite if installed")
|
|
57
58
|
.option("--non-interactive", "Run without interactive prompts")
|
|
58
59
|
.action(async (pathStr, options) => {
|
|
@@ -134,4 +135,21 @@ program
|
|
|
134
135
|
run(skillName, args);
|
|
135
136
|
});
|
|
136
137
|
|
|
138
|
+
program
|
|
139
|
+
.command("platforms")
|
|
140
|
+
.description("List all supported platforms")
|
|
141
|
+
.action(async () => {
|
|
142
|
+
const { platformsCommand } = await import("../src/commands/platforms.js");
|
|
143
|
+
platformsCommand();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
program
|
|
147
|
+
.command("detect")
|
|
148
|
+
.description("Detect skills in directory")
|
|
149
|
+
.argument("[path]", "Path to directory to scan", ".")
|
|
150
|
+
.action(async (pathStr) => {
|
|
151
|
+
const { detect } = await import("../src/commands/detect.js");
|
|
152
|
+
detect(pathStr);
|
|
153
|
+
});
|
|
154
|
+
|
|
137
155
|
program.parse();
|
package/package.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { findSkills } from "../utils/skills.js";
|
|
5
|
+
|
|
6
|
+
export async function detect(pathStr = ".", options = {}) {
|
|
7
|
+
const targetDir = pathStr === "." ? process.cwd() : path.resolve(pathStr);
|
|
8
|
+
|
|
9
|
+
console.log(chalk.blue(`Detecting skills in: ${targetDir}\n`));
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const skills = await findSkills(targetDir);
|
|
13
|
+
|
|
14
|
+
if (skills.length === 0) {
|
|
15
|
+
console.log(chalk.yellow("No skills detected in current directory."));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
for (const skill of skills) {
|
|
20
|
+
if (skill.error) {
|
|
21
|
+
console.log(`${chalk.bold(skill.name)}: ${chalk.red(skill.error)}`);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
console.log(
|
|
25
|
+
`${chalk.bold(skill.name)}: ${chalk.green(skill.platform.name)} (${skill.platform.id})`,
|
|
26
|
+
);
|
|
27
|
+
console.log(` Description: ${skill.description}`);
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error(chalk.red(`Error scanning directory: ${e.message}`));
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/commands/install.js
CHANGED
|
@@ -3,48 +3,10 @@ import path from "path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { platforms, getPlatformPath } from "../platforms/index.js";
|
|
5
5
|
import { promptWithCancellation } from "../utils/prompt.js";
|
|
6
|
+
import { findSkills } from "../utils/skills.js";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
if (!(await fs.pathExists(resolvedSource))) {
|
|
10
|
-
console.error(
|
|
11
|
-
chalk.red(`Error: Source path ${resolvedSource} does not exist`),
|
|
12
|
-
);
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let platform;
|
|
17
|
-
if (options.platform) {
|
|
18
|
-
platform = platforms[options.platform];
|
|
19
|
-
if (!platform) {
|
|
20
|
-
console.error(chalk.red(`Error: Unknown platform '${options.platform}'`));
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
} else {
|
|
24
|
-
console.log(
|
|
25
|
-
chalk.yellow("Platform not specified. Attempting to detect..."),
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const skillMdPath = path.join(resolvedSource, "SKILL.md");
|
|
29
|
-
if (await fs.pathExists(skillMdPath)) {
|
|
30
|
-
// Exists
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const answers = await promptWithCancellation([
|
|
34
|
-
{
|
|
35
|
-
type: "list",
|
|
36
|
-
name: "platform",
|
|
37
|
-
message: "Select target platform to install to:",
|
|
38
|
-
choices: Object.values(platforms).map((p) => ({
|
|
39
|
-
name: p.name,
|
|
40
|
-
value: p.id,
|
|
41
|
-
})),
|
|
42
|
-
},
|
|
43
|
-
]);
|
|
44
|
-
platform = platforms[answers.platform];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const skillName = path.basename(resolvedSource);
|
|
8
|
+
async function installToPlatform(sourcePath, platform, options = {}) {
|
|
9
|
+
const skillName = path.basename(sourcePath);
|
|
48
10
|
const globalPath = await getPlatformPath(platform.id);
|
|
49
11
|
const targetDir = path.join(globalPath, skillName);
|
|
50
12
|
|
|
@@ -69,7 +31,9 @@ export async function install(sourcePath, options = {}) {
|
|
|
69
31
|
]);
|
|
70
32
|
|
|
71
33
|
if (!overwrite) {
|
|
72
|
-
console.log(
|
|
34
|
+
console.log(
|
|
35
|
+
chalk.red("Installation aborted for platform:", platform.name),
|
|
36
|
+
);
|
|
73
37
|
return;
|
|
74
38
|
}
|
|
75
39
|
}
|
|
@@ -77,17 +41,117 @@ export async function install(sourcePath, options = {}) {
|
|
|
77
41
|
|
|
78
42
|
try {
|
|
79
43
|
await fs.ensureDir(globalPath);
|
|
80
|
-
await fs.copy(
|
|
44
|
+
await fs.copy(sourcePath, targetDir, {
|
|
81
45
|
filter: (src) => {
|
|
82
46
|
const basename = path.basename(src);
|
|
83
47
|
return basename !== "node_modules" && basename !== ".git";
|
|
84
48
|
},
|
|
85
49
|
});
|
|
86
50
|
console.log(
|
|
87
|
-
chalk.green(
|
|
51
|
+
chalk.green(
|
|
52
|
+
`Successfully installed '${skillName}' to ${targetDir} (${platform.name})`,
|
|
53
|
+
),
|
|
88
54
|
);
|
|
89
55
|
} catch (e) {
|
|
90
|
-
console.error(
|
|
56
|
+
console.error(
|
|
57
|
+
chalk.red(`Installation failed for ${platform.name}: ${e.message}`),
|
|
58
|
+
);
|
|
59
|
+
// Don't exit, continue to next platform
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function install(pathStr, options = {}) {
|
|
64
|
+
let sourcePath = pathStr;
|
|
65
|
+
|
|
66
|
+
if (!sourcePath) {
|
|
67
|
+
if (options.nonInteractive) {
|
|
68
|
+
console.error(
|
|
69
|
+
chalk.red("Error: Path is required in non-interactive mode"),
|
|
70
|
+
);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const skills = await findSkills(".");
|
|
75
|
+
const validSkills = skills.filter((s) => !s.error);
|
|
76
|
+
|
|
77
|
+
if (validSkills.length === 0) {
|
|
78
|
+
console.error(
|
|
79
|
+
chalk.red("Error: No valid skills detected in current directory"),
|
|
80
|
+
);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (validSkills.length === 1) {
|
|
86
|
+
sourcePath = validSkills[0].path;
|
|
87
|
+
console.log(chalk.blue(`Detected skill: ${validSkills[0].name}`));
|
|
88
|
+
} else {
|
|
89
|
+
const { selectedSkillPath } = await promptWithCancellation([
|
|
90
|
+
{
|
|
91
|
+
type: "list",
|
|
92
|
+
name: "selectedSkillPath",
|
|
93
|
+
message: "Select a skill to install:",
|
|
94
|
+
choices: validSkills.map((s) => ({
|
|
95
|
+
name: `${s.name} (${s.platform.name})`,
|
|
96
|
+
value: s.path,
|
|
97
|
+
})),
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
sourcePath = selectedSkillPath;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const resolvedSource = path.resolve(sourcePath);
|
|
105
|
+
if (!(await fs.pathExists(resolvedSource))) {
|
|
106
|
+
console.error(
|
|
107
|
+
chalk.red(`Error: Source path ${resolvedSource} does not exist`),
|
|
108
|
+
);
|
|
91
109
|
process.exit(1);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let selectedPlatforms = [];
|
|
114
|
+
if (options.platform) {
|
|
115
|
+
const platform = platforms[options.platform];
|
|
116
|
+
if (!platform) {
|
|
117
|
+
console.error(chalk.red(`Error: Unknown platform '${options.platform}'`));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
selectedPlatforms = [platform];
|
|
121
|
+
} else {
|
|
122
|
+
let detectedPlatformId = null;
|
|
123
|
+
const skillMdPath = path.join(resolvedSource, "SKILL.md");
|
|
124
|
+
if (await fs.pathExists(skillMdPath)) {
|
|
125
|
+
const skills = await findSkills(path.dirname(resolvedSource));
|
|
126
|
+
const currentSkill = skills.find((s) => s.path === resolvedSource);
|
|
127
|
+
if (currentSkill) {
|
|
128
|
+
detectedPlatformId = currentSkill.platform.id;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const answers = await promptWithCancellation([
|
|
133
|
+
{
|
|
134
|
+
type: "list",
|
|
135
|
+
name: "platform",
|
|
136
|
+
message: "Select target platform to install to:",
|
|
137
|
+
default: detectedPlatformId,
|
|
138
|
+
choices: [
|
|
139
|
+
...Object.values(platforms).map((p) => ({
|
|
140
|
+
name: p.name,
|
|
141
|
+
value: p.id,
|
|
142
|
+
})),
|
|
143
|
+
{ name: "ALL platforms", value: "all" },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
if (answers.platform === "all") {
|
|
148
|
+
selectedPlatforms = Object.values(platforms);
|
|
149
|
+
} else {
|
|
150
|
+
selectedPlatforms = [platforms[answers.platform]];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const platform of selectedPlatforms) {
|
|
155
|
+
await installToPlatform(resolvedSource, platform, options);
|
|
92
156
|
}
|
|
93
157
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { platforms } from "../platforms/index.js";
|
|
3
|
+
|
|
4
|
+
export async function platformsCommand(options = {}) {
|
|
5
|
+
console.log(chalk.blue("Available Platforms:\n"));
|
|
6
|
+
|
|
7
|
+
for (const [id, platform] of Object.entries(platforms)) {
|
|
8
|
+
console.log(`${chalk.bold(id)}: ${platform.name}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -3,114 +3,135 @@ import path from "path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { platforms, getPlatform, getPlatformPath } from "../platforms/index.js";
|
|
5
5
|
import { promptWithCancellation } from "../utils/prompt.js";
|
|
6
|
+
import { getAllInstalledSkills } from "../utils/skills.js";
|
|
7
|
+
|
|
8
|
+
async function uninstallSkill(targetPath, options = {}) {
|
|
9
|
+
try {
|
|
10
|
+
await fs.remove(targetPath);
|
|
11
|
+
console.log(chalk.green(`Successfully removed skill from ${targetPath}`));
|
|
12
|
+
} catch (e) {
|
|
13
|
+
console.error(chalk.red(`Error removing skill: ${e.message}`));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
6
16
|
|
|
7
17
|
export async function uninstall(name, options = {}) {
|
|
8
|
-
// If name is not provided, try to infer from current directory
|
|
9
18
|
let skillName = name;
|
|
10
|
-
let
|
|
11
|
-
let platform;
|
|
19
|
+
let targetPaths = [];
|
|
12
20
|
|
|
13
21
|
if (!skillName) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
skillName = path.basename(process.cwd());
|
|
18
|
-
targetPath = process.cwd();
|
|
19
|
-
console.log(
|
|
20
|
-
chalk.blue(`Detected skill '${skillName}' in current directory.`),
|
|
22
|
+
if (options.nonInteractive) {
|
|
23
|
+
console.error(
|
|
24
|
+
chalk.red("Error: Skill name is required in non-interactive mode."),
|
|
21
25
|
);
|
|
22
|
-
} else {
|
|
23
|
-
console.error(chalk.red("Error: Skill name is required."));
|
|
24
26
|
process.exit(1);
|
|
27
|
+
return;
|
|
25
28
|
}
|
|
26
|
-
}
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (!platform) {
|
|
32
|
-
console.error(chalk.red(`Error: Unknown platform '${options.platform}'`));
|
|
30
|
+
const allSkills = await getAllInstalledSkills();
|
|
31
|
+
if (allSkills.length === 0) {
|
|
32
|
+
console.error(chalk.red("Error: No installed skills found."));
|
|
33
33
|
process.exit(1);
|
|
34
|
+
return;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const { selectedSkills } = await promptWithCancellation([
|
|
38
|
+
{
|
|
39
|
+
type: "checkbox",
|
|
40
|
+
name: "selectedSkills",
|
|
41
|
+
message: "Select skills to uninstall:",
|
|
42
|
+
choices: allSkills.map((s) => ({
|
|
43
|
+
name: `${chalk.bold(s.name)} [${s.platform.name}] (${s.location}) - ${s.description}`,
|
|
44
|
+
value: s.path,
|
|
45
|
+
})),
|
|
46
|
+
validate: (input) =>
|
|
47
|
+
input.length > 0 ? true : "You must select at least one skill.",
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
targetPaths = selectedSkills;
|
|
52
|
+
} else {
|
|
53
|
+
let targetPath;
|
|
54
|
+
if (options.platform) {
|
|
55
|
+
const platform = getPlatform(options.platform);
|
|
56
|
+
if (!platform) {
|
|
57
|
+
console.error(
|
|
58
|
+
chalk.red(`Error: Unknown platform '${options.platform}'`),
|
|
59
|
+
);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
41
62
|
|
|
42
|
-
|
|
43
|
-
if (!targetPath) {
|
|
44
|
-
// Search in all global locations
|
|
45
|
-
const found = [];
|
|
46
|
-
for (const p of Object.values(platforms)) {
|
|
47
|
-
const globalPath = await getPlatformPath(p.id);
|
|
63
|
+
const globalPath = await getPlatformPath(platform.id);
|
|
48
64
|
const pPath = path.join(globalPath, skillName);
|
|
49
65
|
if (await fs.pathExists(pPath)) {
|
|
50
|
-
|
|
66
|
+
targetPath = pPath;
|
|
51
67
|
}
|
|
52
68
|
}
|
|
53
69
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
if (!targetPath) {
|
|
71
|
+
const found = [];
|
|
72
|
+
for (const p of Object.values(platforms)) {
|
|
73
|
+
const globalPath = await getPlatformPath(p.id);
|
|
74
|
+
const pPath = path.join(globalPath, skillName);
|
|
75
|
+
if (await fs.pathExists(pPath)) {
|
|
76
|
+
found.push({ platform: p, path: pPath, location: "Global" });
|
|
77
|
+
}
|
|
60
78
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
found.push({ platform: p, path: localPath, location: "Project" });
|
|
79
|
+
const localBase =
|
|
80
|
+
p.id === "opencode" ? ".opencode/skill" : `.${p.id}/skills`;
|
|
81
|
+
const localPath = path.join(process.cwd(), localBase, skillName);
|
|
82
|
+
if (await fs.pathExists(localPath)) {
|
|
83
|
+
found.push({ platform: p, path: localPath, location: "Project" });
|
|
84
|
+
}
|
|
68
85
|
}
|
|
69
|
-
}
|
|
70
86
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
process.exit(1);
|
|
74
|
-
} else if (found.length === 1) {
|
|
75
|
-
targetPath = found[0].path;
|
|
76
|
-
console.log(
|
|
77
|
-
chalk.blue(
|
|
78
|
-
`Found skill in ${found[0].platform.name} (${found[0].location})`,
|
|
79
|
-
),
|
|
80
|
-
);
|
|
81
|
-
} else {
|
|
82
|
-
// Multiple found, ask user
|
|
83
|
-
if (options.nonInteractive) {
|
|
84
|
-
console.error(
|
|
85
|
-
chalk.red(
|
|
86
|
-
`Error: Multiple skills found with name '${skillName}'. Specify --platform or use interactive mode.`,
|
|
87
|
-
),
|
|
88
|
-
);
|
|
87
|
+
if (found.length === 0) {
|
|
88
|
+
console.error(chalk.red(`Error: Skill '${skillName}' not found.`));
|
|
89
89
|
process.exit(1);
|
|
90
|
+
} else if (found.length === 1) {
|
|
91
|
+
targetPath = found[0].path;
|
|
92
|
+
} else {
|
|
93
|
+
if (options.nonInteractive) {
|
|
94
|
+
console.error(
|
|
95
|
+
chalk.red(
|
|
96
|
+
`Error: Multiple skills found with name '${skillName}'. Specify --platform or use interactive mode.`,
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const answer = await promptWithCancellation([
|
|
103
|
+
{
|
|
104
|
+
type: "list",
|
|
105
|
+
name: "target",
|
|
106
|
+
message: "Multiple skills found. Which one to uninstall?",
|
|
107
|
+
choices: [
|
|
108
|
+
...found.map((f) => ({
|
|
109
|
+
name: `${f.platform.name} (${f.location}) - ${f.path}`,
|
|
110
|
+
value: f.path,
|
|
111
|
+
})),
|
|
112
|
+
{ name: "ALL skills", value: "all" },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
if (answer.target === "all") {
|
|
117
|
+
targetPaths = found.map((f) => f.path);
|
|
118
|
+
} else {
|
|
119
|
+
targetPath = answer.target;
|
|
120
|
+
}
|
|
90
121
|
}
|
|
122
|
+
}
|
|
91
123
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
type: "list",
|
|
95
|
-
name: "target",
|
|
96
|
-
message: "Multiple skills found. Which one to uninstall?",
|
|
97
|
-
choices: found.map((f) => ({
|
|
98
|
-
name: `${f.platform.name} (${f.location}) - ${f.path}`,
|
|
99
|
-
value: f.path,
|
|
100
|
-
})),
|
|
101
|
-
},
|
|
102
|
-
]);
|
|
103
|
-
targetPath = answer.target;
|
|
124
|
+
if (targetPath) {
|
|
125
|
+
targetPaths = [targetPath];
|
|
104
126
|
}
|
|
105
127
|
}
|
|
106
128
|
|
|
107
|
-
|
|
108
|
-
if (!options.nonInteractive) {
|
|
129
|
+
if (targetPaths.length > 0 && !options.nonInteractive) {
|
|
109
130
|
const { confirm } = await promptWithCancellation([
|
|
110
131
|
{
|
|
111
132
|
type: "confirm",
|
|
112
133
|
name: "confirm",
|
|
113
|
-
message: `Are you sure you want to delete ${
|
|
134
|
+
message: `Are you sure you want to delete ${targetPaths.length} skill(s)? This cannot be undone.`,
|
|
114
135
|
default: false,
|
|
115
136
|
},
|
|
116
137
|
]);
|
|
@@ -121,11 +142,7 @@ export async function uninstall(name, options = {}) {
|
|
|
121
142
|
}
|
|
122
143
|
}
|
|
123
144
|
|
|
124
|
-
|
|
125
|
-
await
|
|
126
|
-
console.log(chalk.green(`Successfully removed skill from ${targetPath}`));
|
|
127
|
-
} catch (e) {
|
|
128
|
-
console.error(chalk.red(`Error removing skill: ${e.message}`));
|
|
129
|
-
process.exit(1);
|
|
145
|
+
for (const p of targetPaths) {
|
|
146
|
+
await uninstallSkill(p, options);
|
|
130
147
|
}
|
|
131
148
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import { platforms, getPlatformPath } from "../platforms/index.js";
|
|
5
|
+
|
|
6
|
+
export function detectPlatform(frontmatter) {
|
|
7
|
+
if (
|
|
8
|
+
frontmatter["allowed-tools"] ||
|
|
9
|
+
frontmatter.context ||
|
|
10
|
+
frontmatter.hooks ||
|
|
11
|
+
frontmatter.agent ||
|
|
12
|
+
frontmatter["user-invocable"]
|
|
13
|
+
) {
|
|
14
|
+
return platforms.claude;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (frontmatter.license || frontmatter.compatibility) {
|
|
18
|
+
return platforms.opencode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (frontmatter.metadata && frontmatter.metadata["short-description"]) {
|
|
22
|
+
return platforms.codex;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return platforms.gemini;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function findSkills(dirPath) {
|
|
29
|
+
const targetDir = path.resolve(dirPath);
|
|
30
|
+
if (!(await fs.pathExists(targetDir))) return [];
|
|
31
|
+
const items = await fs.readdir(targetDir, { withFileTypes: true });
|
|
32
|
+
const skills = [];
|
|
33
|
+
|
|
34
|
+
for (const item of items) {
|
|
35
|
+
if (item.isDirectory()) {
|
|
36
|
+
const skillPath = path.join(targetDir, item.name, "SKILL.md");
|
|
37
|
+
if (await fs.pathExists(skillPath)) {
|
|
38
|
+
try {
|
|
39
|
+
const content = await fs.readFile(skillPath, "utf8");
|
|
40
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
41
|
+
|
|
42
|
+
if (!match) {
|
|
43
|
+
skills.push({
|
|
44
|
+
name: item.name,
|
|
45
|
+
path: path.join(targetDir, item.name),
|
|
46
|
+
error: "Invalid frontmatter format",
|
|
47
|
+
});
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const fm = yaml.load(match[1]);
|
|
52
|
+
const platform = detectPlatform(fm);
|
|
53
|
+
const description = fm.description || "No description";
|
|
54
|
+
|
|
55
|
+
skills.push({
|
|
56
|
+
name: item.name,
|
|
57
|
+
path: path.join(targetDir, item.name),
|
|
58
|
+
platform,
|
|
59
|
+
description,
|
|
60
|
+
valid: true,
|
|
61
|
+
});
|
|
62
|
+
} catch (e) {
|
|
63
|
+
skills.push({
|
|
64
|
+
name: item.name,
|
|
65
|
+
path: path.join(targetDir, item.name),
|
|
66
|
+
error: "Read error",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return skills;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function getAllInstalledSkills() {
|
|
77
|
+
const allSkills = [];
|
|
78
|
+
for (const platform of Object.values(platforms)) {
|
|
79
|
+
const globalPath = await getPlatformPath(platform.id);
|
|
80
|
+
const localBase =
|
|
81
|
+
platform.id === "opencode" ? ".opencode/skill" : `.${platform.id}/skills`;
|
|
82
|
+
const locations = [
|
|
83
|
+
{ name: "Global", path: globalPath },
|
|
84
|
+
{ name: "Project", path: path.join(process.cwd(), localBase) },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
for (const loc of locations) {
|
|
88
|
+
if (await fs.pathExists(loc.path)) {
|
|
89
|
+
const skills = await findSkills(loc.path);
|
|
90
|
+
for (const skill of skills) {
|
|
91
|
+
allSkills.push({
|
|
92
|
+
...skill,
|
|
93
|
+
platform,
|
|
94
|
+
location: loc.name,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return allSkills;
|
|
101
|
+
}
|