myskill 1.0.0 → 1.2.1
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/AGENTS.md +102 -0
- package/README.md +35 -58
- package/bin/myskill.js +35 -1
- package/package.json +3 -2
- package/src/commands/create.js +0 -1
- package/src/commands/detect.js +51 -0
- package/src/commands/docs.js +12 -0
- package/src/commands/install.js +109 -45
- package/src/commands/onboard.js +32 -0
- package/src/commands/platforms.js +10 -0
- package/src/commands/uninstall.js +100 -83
- package/src/platforms/claude.js +1 -0
- package/src/platforms/codex.js +1 -0
- package/src/platforms/gemini.js +1 -0
- package/src/platforms/opencode.js +1 -0
- package/src/utils/skills.js +101 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Agent Guide for `myskill`
|
|
2
|
+
|
|
3
|
+
This document is designed to help AI agents (and human developers) understand the architecture, conventions, and workflows of the `myskill` project.
|
|
4
|
+
|
|
5
|
+
## 1. Project Overview
|
|
6
|
+
`myskill` is a CLI tool built in **Node.js** to unify the creation and management of "Agent Skills" across different AI coding platforms (Claude Code, OpenCode, OpenAI Codex, Gemini CLI).
|
|
7
|
+
|
|
8
|
+
**Core Mission**: Abstract away the differences in YAML frontmatter, directory structures, and configuration files between different AI tools.
|
|
9
|
+
|
|
10
|
+
## 2. Tech Stack
|
|
11
|
+
- **Runtime**: Node.js (ES Modules)
|
|
12
|
+
- **CLI Framework**: `commander`
|
|
13
|
+
- **Interactivity**: `inquirer`
|
|
14
|
+
- **Validation**: `zod` (Strict schema validation is a core tenet)
|
|
15
|
+
- **File System**: `fs-extra`
|
|
16
|
+
- **Search**: `fuse.js`
|
|
17
|
+
- **Git Integration**: `simple-git`
|
|
18
|
+
- **Path Management**: `env-paths` (for cross-platform config locations)
|
|
19
|
+
- **Formatting**: `prettier` (for generated code)
|
|
20
|
+
- **Testing**: `vitest`
|
|
21
|
+
|
|
22
|
+
## 3. Architecture
|
|
23
|
+
|
|
24
|
+
### Directory Structure
|
|
25
|
+
```text
|
|
26
|
+
myskill/
|
|
27
|
+
├── bin/
|
|
28
|
+
│ └── myskill.js # CLI Entry point & Command Registration
|
|
29
|
+
├── src/
|
|
30
|
+
│ ├── commands/ # Individual command logic
|
|
31
|
+
│ │ ├── create.js # Logic for 'myskill create'
|
|
32
|
+
│ │ ├── validate.js # Logic for 'myskill validate'
|
|
33
|
+
│ │ └── ... # All command implementations
|
|
34
|
+
│ ├── platforms/ # Platform Definitions (The "Brain")
|
|
35
|
+
│ │ ├── index.js # Exports all platforms and getPlatformPath
|
|
36
|
+
│ │ ├── claude.js # Claude Code specific config & schema
|
|
37
|
+
│ │ └── ...
|
|
38
|
+
│ ├── templates/ # File generation templates (generateSkill.js)
|
|
39
|
+
│ └── utils/ # Shared utilities (config.js)
|
|
40
|
+
└── test/ # Unit and Integration tests
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Key Concepts
|
|
44
|
+
|
|
45
|
+
#### Platform Definitions (`src/platforms/*.js`)
|
|
46
|
+
Every supported tool (e.g., Claude, OpenCode) is defined as a module exporting:
|
|
47
|
+
1. **`id`**: Internal key.
|
|
48
|
+
2. **`name`**: Display name.
|
|
49
|
+
3. **`defaultPath`**: Default location where skills live globally on the OS (fallback, now configurable).
|
|
50
|
+
4. **`schema`**: A **Zod** schema defining valid frontmatter fields.
|
|
51
|
+
5. **`prompts`**: Inquirer prompt definitions for platform-specific fields.
|
|
52
|
+
|
|
53
|
+
#### Command Pattern
|
|
54
|
+
Commands are defined in `bin/myskill.js` but implemented in `src/commands/*.js`.
|
|
55
|
+
- **Dependency Injection**: Commands should lazily import logic to keep startup fast.
|
|
56
|
+
- **Interactivity**: Commands must support both interactive (Prompts) and non-interactive (Flags) modes.
|
|
57
|
+
- **Error Handling**: Commands throw errors; `bin/myskill.js` catches and handles them.
|
|
58
|
+
|
|
59
|
+
#### Configuration System (`src/utils/config.js`)
|
|
60
|
+
- Uses `env-paths` for cross-platform config directories (e.g., `~/.config/myskill` on Linux/Mac, `%APPDATA%\myskill` on Windows).
|
|
61
|
+
- Platform paths can be overridden via `myskill config set claude.path "/custom/path"`.
|
|
62
|
+
- `getPlatformPath()` in `platforms/index.js` checks config first, falls back to default.
|
|
63
|
+
|
|
64
|
+
#### Current Commands
|
|
65
|
+
- `create`: Generate new skills
|
|
66
|
+
- `validate`: Check skill validity
|
|
67
|
+
- `list`: List installed skills
|
|
68
|
+
- `find`: Fuzzy search skills
|
|
69
|
+
- `install`: Install local skills to global paths
|
|
70
|
+
- `pull`: Clone/pull skills from Git repos
|
|
71
|
+
- `convert`: Convert skills between platforms
|
|
72
|
+
- `uninstall`: Remove skills
|
|
73
|
+
- `run`: Execute skill scripts (experimental)
|
|
74
|
+
- `config`: Manage configuration
|
|
75
|
+
- `doctor`: System health check
|
|
76
|
+
|
|
77
|
+
## 4. Development Guidelines
|
|
78
|
+
|
|
79
|
+
### Adding a New Platform
|
|
80
|
+
To add support for a new tool (e.g., "SuperAI"):
|
|
81
|
+
1. Create `src/platforms/superai.js`.
|
|
82
|
+
2. Define its Zod schema and default paths.
|
|
83
|
+
3. Export it in `src/platforms/index.js`.
|
|
84
|
+
4. Add unit tests in `test/schemas.test.js`.
|
|
85
|
+
|
|
86
|
+
### Adding a New Command
|
|
87
|
+
1. Create `src/commands/mycommand.js`.
|
|
88
|
+
2. Register it in `bin/myskill.js`.
|
|
89
|
+
3. Ensure it handles `options` for non-interactive usage and throws errors appropriately.
|
|
90
|
+
4. Add tests in `test/mycommand.test.js`.
|
|
91
|
+
|
|
92
|
+
### Testing Rules
|
|
93
|
+
- **Run Tests**: `npm test` (uses Vitest).
|
|
94
|
+
- **Mocking**: Use `vi.mock` for `fs-extra`, `inquirer`, `simple-git`, and other I/O libraries to avoid file system side effects during unit tests.
|
|
95
|
+
- **Coverage**: Ensure both success paths and error paths (e.g., invalid YAML, missing files) are tested.
|
|
96
|
+
- **Cross-Platform**: Tests should work on Windows/Linux/macOS; use `path.join` in mocks.
|
|
97
|
+
|
|
98
|
+
## 5. Common Pitfalls
|
|
99
|
+
- **ESM**: This project uses `"type": "module"`. Use `import/export`, not `require` (unless constructing a `require` via `module`).
|
|
100
|
+
- **Paths**: Always use `path.join` or `path.resolve` for cross-platform compatibility (Windows/Linux/macOS). Config paths use `env-paths`.
|
|
101
|
+
- **Zod Strictness**: When defining schemas, be precise. `myskill validate` relies on these schemas to catch user errors.
|
|
102
|
+
- **Error Propagation**: Commands should throw errors, not call `process.exit`. Let the CLI entry point handle exits.
|
package/README.md
CHANGED
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
- **Cross-Platform**: Works seamlessly on Windows, macOS, and Linux.
|
|
12
12
|
- **Workspace Management**: Initialize skill workspaces and run experimental skills locally.
|
|
13
13
|
- **Discovery**: Fuzzy find installed skills across all global and local scopes.
|
|
14
|
+
- **Smart Detection**: Automatically identify skills in any directory, including nested platform-specific folders.
|
|
14
15
|
- **Git Integration**: Pull and update skills directly from remote repositories.
|
|
15
|
-
- **Interactive Experience**:
|
|
16
|
+
- **Interactive Experience**: Step-by-step guides with **Multi-selection** support for bulk actions.
|
|
16
17
|
- **Configurable Paths**: Override default platform paths for custom setups.
|
|
18
|
+
- **Onboarding**: Built-in guide for AI agents to understand the codebase.
|
|
17
19
|
|
|
18
20
|
## 📦 Installation
|
|
19
21
|
|
|
@@ -41,99 +43,74 @@ myskill create
|
|
|
41
43
|
# Cancel anytime with Escape key or Ctrl+C
|
|
42
44
|
```
|
|
43
45
|
|
|
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
46
|
### Discovering Skills
|
|
56
47
|
|
|
57
|
-
|
|
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:
|
|
48
|
+
Detect skills in the current directory and platform-specific subfolders:
|
|
70
49
|
|
|
71
50
|
```bash
|
|
72
|
-
myskill
|
|
51
|
+
myskill detect
|
|
73
52
|
```
|
|
74
53
|
|
|
75
|
-
|
|
54
|
+
List all installed skills for a platform:
|
|
76
55
|
|
|
77
56
|
```bash
|
|
78
|
-
myskill
|
|
57
|
+
myskill list --platform claude
|
|
79
58
|
```
|
|
80
59
|
|
|
81
60
|
### Sharing Skills
|
|
82
61
|
|
|
83
|
-
Install a local skill directory to the global platform path:
|
|
62
|
+
Install a local skill directory to the global platform path (supports auto-detection and interactive selection):
|
|
84
63
|
|
|
85
64
|
```bash
|
|
86
|
-
myskill install
|
|
65
|
+
myskill install
|
|
87
66
|
```
|
|
88
67
|
|
|
89
|
-
|
|
68
|
+
Uninstall skills with **multi-selection** support:
|
|
90
69
|
|
|
91
70
|
```bash
|
|
92
|
-
myskill
|
|
71
|
+
myskill uninstall
|
|
93
72
|
```
|
|
94
73
|
|
|
95
|
-
|
|
74
|
+
### Configuration & Help
|
|
75
|
+
|
|
76
|
+
List documentation URLs for all platforms:
|
|
96
77
|
|
|
97
78
|
```bash
|
|
98
|
-
myskill
|
|
79
|
+
myskill docs
|
|
99
80
|
```
|
|
100
81
|
|
|
101
|
-
|
|
82
|
+
Display onboarding guide for AI agents:
|
|
102
83
|
|
|
103
84
|
```bash
|
|
104
|
-
myskill
|
|
85
|
+
myskill onboard
|
|
105
86
|
```
|
|
106
87
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
Override default paths (useful for custom setups):
|
|
88
|
+
Override default paths:
|
|
110
89
|
|
|
111
90
|
```bash
|
|
112
91
|
myskill config set claude.path "/custom/path/to/skills"
|
|
113
92
|
myskill config list
|
|
114
93
|
```
|
|
115
94
|
|
|
116
|
-
Check system health (Git, permissions, paths):
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
myskill doctor
|
|
120
|
-
```
|
|
121
|
-
|
|
122
95
|
## 🔧 Command Reference
|
|
123
96
|
|
|
124
|
-
| Command | Description | Usage | Options
|
|
125
|
-
| ----------- | ---------------------------------------------------------------------- | --------------------------------------- |
|
|
126
|
-
| `create` | Create a new skill interactively or via flags. | `myskill create [options]` | `-n, --name <name>`: Skill name
|
|
127
|
-
| `list` | List all installed skills for a platform. | `myskill list [options]` | `-p, --platform <platform>`: Filter by platform
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
97
|
+
| Command | Description | Usage | Options |
|
|
98
|
+
| ----------- | ---------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| `create` | Create a new skill interactively or via flags. | `myskill create [options]` | `-n, --name <name>`: Skill name<br>`-p, --platform <platform>`: Target platform<br>`-d, --description <description>`: Skill description<br>`-s, --scope <scope>`: Scope (global, project). Default: project<br>`--non-interactive`: Run without interactive prompts |
|
|
100
|
+
| `list` | List all installed skills for a platform. | `myskill list [options]` | `-p, --platform <platform>`: Filter by platform |
|
|
101
|
+
| `platforms` | List all supported platforms. | `myskill platforms` | None |
|
|
102
|
+
| `detect` | Detect skills in directory and platform folders. | `myskill detect [path]` | `[path]`: Path to directory to scan (default: current directory) |
|
|
103
|
+
| `docs` | List documentation URLs for all platforms. | `myskill docs` | None |
|
|
104
|
+
| `onboard` | Display onboarding guide for AI agents. | `myskill onboard` | None |
|
|
105
|
+
| `find` | Find skills by name or description with fuzzy search. | `myskill find [query] [options]` | `[query]`: Search query<br>`-p, --platform <platform>`: Filter by platform |
|
|
106
|
+
| `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 |
|
|
107
|
+
| `run` | Run a skill script (experimental). | `myskill run <skill> [args...]` | `<skill>`: Skill name or path<br>`[args...]`: Arguments to pass to the skill script |
|
|
108
|
+
| `install` | Install a local skill to the global platform path. | `myskill install [path] [options]` | `[path]`: Path to skill directory (optional - auto-detects if omitted)<br>`-p, --platform <platform>`: Target platform<br>`-f, --force`: Force overwrite<br>`--non-interactive`: Run without prompts |
|
|
109
|
+
| `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 |
|
|
110
|
+
| `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<br>`--non-interactive`: Fail if output exists |
|
|
111
|
+
| `uninstall` | Uninstall one or more skills from global or local paths. | `myskill uninstall [name] [options]` | `[name]`: Skill name (optional - triggers multi-select if omitted)<br>`-p, --platform <platform>`: Platform context<br>`--non-interactive`: Skip confirmation |
|
|
112
|
+
| `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) |
|
|
113
|
+
| `doctor` | Check system health (Git, permissions, paths). | `myskill doctor` | None |
|
|
137
114
|
|
|
138
115
|
## 🗺️ Roadmap
|
|
139
116
|
|
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,37 @@ 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
|
+
|
|
155
|
+
program
|
|
156
|
+
.command("docs")
|
|
157
|
+
.description("List documentation URLs for all platforms")
|
|
158
|
+
.action(async () => {
|
|
159
|
+
const { docs } = await import("../src/commands/docs.js");
|
|
160
|
+
docs();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
program
|
|
164
|
+
.command("onboard")
|
|
165
|
+
.description("Display onboarding guide for AI agents")
|
|
166
|
+
.action(async () => {
|
|
167
|
+
const { onboard } = await import("../src/commands/onboard.js");
|
|
168
|
+
await onboard();
|
|
169
|
+
});
|
|
170
|
+
|
|
137
171
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myskill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "CLI tool for creating and managing AI agent skills",
|
|
5
5
|
"author": "Sarfraz Ahmed <sarfraznawaz2005@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"bin",
|
|
20
20
|
"src",
|
|
21
21
|
"README.md",
|
|
22
|
-
"LICENSE"
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"AGENTS.md"
|
|
23
24
|
],
|
|
24
25
|
"main": "./bin/myskill.js",
|
|
25
26
|
"bin": {
|
package/src/commands/create.js
CHANGED
|
@@ -194,7 +194,6 @@ export async function create(options = {}) {
|
|
|
194
194
|
try {
|
|
195
195
|
await fs.ensureDir(targetDir);
|
|
196
196
|
await fs.writeFile(path.join(targetDir, "SKILL.md"), fileContent);
|
|
197
|
-
await fs.ensureDir(path.join(targetDir, "scripts"));
|
|
198
197
|
console.log(chalk.green(`Skill created successfully at ${targetDir}`));
|
|
199
198
|
} catch (error) {
|
|
200
199
|
console.error(chalk.red(`Error creating skill: ${error.message}`));
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { findSkills } from "../utils/skills.js";
|
|
5
|
+
import { platforms } from "../platforms/index.js";
|
|
6
|
+
|
|
7
|
+
export async function detect(pathStr = ".", options = {}) {
|
|
8
|
+
const targetDir = pathStr === "." ? process.cwd() : path.resolve(pathStr);
|
|
9
|
+
|
|
10
|
+
console.log(chalk.blue(`Detecting skills in: ${targetDir}\n`));
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
let allSkills = await findSkills(targetDir);
|
|
14
|
+
|
|
15
|
+
for (const platform of Object.values(platforms)) {
|
|
16
|
+
const localBase =
|
|
17
|
+
platform.id === "opencode"
|
|
18
|
+
? ".opencode/skill"
|
|
19
|
+
: `.${platform.id}/skills`;
|
|
20
|
+
const platformDir = path.join(targetDir, localBase);
|
|
21
|
+
|
|
22
|
+
if (await fs.pathExists(platformDir)) {
|
|
23
|
+
const platformSkills = await findSkills(platformDir);
|
|
24
|
+
for (const skill of platformSkills) {
|
|
25
|
+
if (!allSkills.some((s) => s.path === skill.path)) {
|
|
26
|
+
allSkills.push(skill);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (allSkills.length === 0) {
|
|
33
|
+
console.log(chalk.yellow("No skills detected in current directory."));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const skill of allSkills) {
|
|
38
|
+
if (skill.error) {
|
|
39
|
+
console.log(`${chalk.bold(skill.name)}: ${chalk.red(skill.error)}`);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
console.log(
|
|
43
|
+
`${chalk.bold(skill.name)}: ${chalk.green(skill.platform.name)} (${skill.platform.id})`,
|
|
44
|
+
);
|
|
45
|
+
console.log(` Description: ${skill.description}`);
|
|
46
|
+
console.log(` Path: ${path.relative(targetDir, skill.path)}`);
|
|
47
|
+
}
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.error(chalk.red(`Error scanning directory: ${e.message}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { platforms } from "../platforms/index.js";
|
|
3
|
+
|
|
4
|
+
export async function docs() {
|
|
5
|
+
console.log(chalk.blue("=== Platform Documentation ===\n"));
|
|
6
|
+
|
|
7
|
+
for (const platform of Object.values(platforms)) {
|
|
8
|
+
console.log(`${chalk.bold(platform.name)}:`);
|
|
9
|
+
console.log(` ${chalk.cyan(platform.docsUrl)}`);
|
|
10
|
+
console.log();
|
|
11
|
+
}
|
|
12
|
+
}
|
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,32 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
export async function onboard() {
|
|
9
|
+
const agentsMdPath = path.join(__dirname, "..", "..", "AGENTS.md");
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
if (await fs.pathExists(agentsMdPath)) {
|
|
13
|
+
const content = await fs.readFile(agentsMdPath, "utf8");
|
|
14
|
+
console.log(content);
|
|
15
|
+
} else {
|
|
16
|
+
console.error(chalk.red("Error: AGENTS.md not found."));
|
|
17
|
+
if (process.env.NODE_ENV === "test") {
|
|
18
|
+
throw new Error("AGENTS_MD_NOT_FOUND");
|
|
19
|
+
}
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error.message === "AGENTS_MD_NOT_FOUND") throw error;
|
|
24
|
+
console.error(
|
|
25
|
+
chalk.red(`Error reading onboarding guide: ${error.message}`),
|
|
26
|
+
);
|
|
27
|
+
if (process.env.NODE_ENV === "test") {
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -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
|
}
|
package/src/platforms/claude.js
CHANGED
package/src/platforms/codex.js
CHANGED
package/src/platforms/gemini.js
CHANGED
|
@@ -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
|
+
}
|