itismyskillmarket 1.2.2 → 1.2.4
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/.github/workflows/publish-npm.yml +54 -0
- package/.github/workflows/publish-skill.yml +72 -0
- package/5e51cb7aa8b8e60d49d86f4689f5d4d1.png +0 -0
- package/CHANGELOG.md +143 -0
- package/DEVELOPMENT.md +376 -0
- package/README.md +70 -4
- package/SKILLMARKET-GUIDE.md +277 -0
- package/dist/index.js +478 -177
- package/docs/plans/2026-04-01-skillmarket-design.md +267 -0
- package/docs/plans/2026-04-01-skillmarket-implementation.md +1031 -0
- package/docs/plans/2026-04-15-cross-platform-adapter-design.md +416 -0
- package/docs/plans/2026-04-15-cross-platform-adapter-plan.md +833 -0
- package/package.json +1 -6
- package/skills/README.md +52 -0
- package/skills/test-skill/SKILL.md +25 -0
- package/skills/test-skill/index.js +66 -0
- package/skills/test-skill/metadata.json +9 -0
- package/skills/test-skill/package.json +19 -0
- package/skills/test-skill-1/SKILL.md +24 -0
- package/skills/test-skill-1/index.js +13 -0
- package/skills/test-skill-1/metadata.json +9 -0
- package/skills/test-skill-1/package.json +16 -0
- package/skills/test-skill-2/SKILL.md +25 -0
- package/skills/test-skill-2/index.js +13 -0
- package/skills/test-skill-2/metadata.json +9 -0
- package/skills/test-skill-2/package.json +16 -0
- package/src/adapters/base.ts +87 -0
- package/src/adapters/claude.ts +31 -0
- package/src/adapters/index.ts +9 -0
- package/src/adapters/opencode.ts +40 -0
- package/src/adapters/registry.ts +77 -0
- package/src/adapters/vscode.ts +62 -0
- package/src/cli.ts +113 -49
- package/src/commands/info.ts +4 -15
- package/src/commands/install.ts +93 -54
- package/src/commands/ls.ts +69 -13
- package/src/commands/npm.ts +87 -12
- package/src/commands/sync.ts +6 -27
- package/src/commands/uninstall.ts +60 -7
- package/src/commands/update.ts +2 -2
- package/src/index.ts +27 -0
- package/src/types.ts +35 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +22 -0
- package/wanxuchen-skillmarket-1.0.1.tgz +0 -0
package/package.json
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "itismyskillmarket",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Cross-platform skill manager for AI coding tools",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"files": [
|
|
7
|
-
"dist",
|
|
8
|
-
"src",
|
|
9
|
-
"README.md"
|
|
10
|
-
],
|
|
11
6
|
"bin": {
|
|
12
7
|
"skm": "dist/index.js"
|
|
13
8
|
},
|
package/skills/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# SkillMarket Skills
|
|
2
|
+
|
|
3
|
+
This directory contains the skill packages that can be published to npm.
|
|
4
|
+
|
|
5
|
+
## Adding a New Skill
|
|
6
|
+
|
|
7
|
+
1. Create a new directory under `skills/<skill-name>/`
|
|
8
|
+
2. Add the required files:
|
|
9
|
+
- `package.json` - Package configuration with `skillmarket` metadata
|
|
10
|
+
- `SKILL.md` - Skill documentation
|
|
11
|
+
- `metadata.json` - Skill metadata
|
|
12
|
+
- `index.js` - Main entry point (OpenCode plugin)
|
|
13
|
+
|
|
14
|
+
3. Update the GitHub Actions workflow to include your skill (if using dropdown options)
|
|
15
|
+
|
|
16
|
+
## Publishing a Skill
|
|
17
|
+
|
|
18
|
+
### Option 1: GitHub Actions (Recommended)
|
|
19
|
+
1. Go to repository Actions tab
|
|
20
|
+
2. Select "Publish Skill" workflow
|
|
21
|
+
3. Click "Run workflow"
|
|
22
|
+
4. Enter skill name (e.g., `test-skill`)
|
|
23
|
+
5. Optionally specify version
|
|
24
|
+
|
|
25
|
+
### Option 2: Manual Publish
|
|
26
|
+
```bash
|
|
27
|
+
cd skills/<skill-name>
|
|
28
|
+
npm install
|
|
29
|
+
npm publish --access=public
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Skill Package Requirements
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"name": "@itismyskillmarket/<skill-name>",
|
|
37
|
+
"version": "1.0.0",
|
|
38
|
+
"type": "module",
|
|
39
|
+
"main": "index.js",
|
|
40
|
+
"keywords": ["skillmarket"],
|
|
41
|
+
"skillmarket": {
|
|
42
|
+
"id": "<skill-name>",
|
|
43
|
+
"displayName": "Display Name",
|
|
44
|
+
"description": "Description",
|
|
45
|
+
"platforms": ["opencode", "cursor", "vscode"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Current Skills
|
|
51
|
+
|
|
52
|
+
- `test-skill` - Test skill for validating the installation flow
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Test Skill
|
|
2
|
+
|
|
3
|
+
这是一个用于测试 SkillMarket 安装流程的 skill。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- `test.greet` - 向用户打招呼
|
|
8
|
+
- `test.randomNumber` - 生成随机数
|
|
9
|
+
- `test.systemInfo` - 获取系统信息
|
|
10
|
+
|
|
11
|
+
## 使用方法
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
skm install test-skill
|
|
15
|
+
skm info test-skill
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 平台支持
|
|
19
|
+
|
|
20
|
+
- OpenCode
|
|
21
|
+
- Cursor
|
|
22
|
+
- VSCode
|
|
23
|
+
- Claude Code
|
|
24
|
+
- Codex
|
|
25
|
+
- Antigravity
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Skill - SkillMarket 测试技能
|
|
3
|
+
*
|
|
4
|
+
* 这是一个用于验证 SkillMarket 安装流程的示例 skill
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { tool } from "@opencode-ai/plugin";
|
|
8
|
+
|
|
9
|
+
export default async function TestSkill() {
|
|
10
|
+
console.log("✅ Test Skill 加载成功!");
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
tool: {
|
|
14
|
+
// 1. 简单的问候工具
|
|
15
|
+
greet: tool({
|
|
16
|
+
description: "向用户打招呼",
|
|
17
|
+
args: {
|
|
18
|
+
name: tool.schema.string().describe("你的名字")
|
|
19
|
+
},
|
|
20
|
+
async execute({ name }, context) {
|
|
21
|
+
const { agent, sessionID } = context;
|
|
22
|
+
return `你好 ${name}!我是 ${agent} 代理,欢迎使用测试插件!会话ID: ${sessionID}`;
|
|
23
|
+
}
|
|
24
|
+
}),
|
|
25
|
+
|
|
26
|
+
// 2. 随机数生成工具
|
|
27
|
+
randomNumber: tool({
|
|
28
|
+
description: "生成指定范围的随机数",
|
|
29
|
+
args: {
|
|
30
|
+
min: tool.schema.number().describe("最小值").default(1),
|
|
31
|
+
max: tool.schema.number().describe("最大值").default(100)
|
|
32
|
+
},
|
|
33
|
+
async execute({ min, max }, context) {
|
|
34
|
+
const randomNum = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
35
|
+
return `随机数(${min}-${max}): ${randomNum}`;
|
|
36
|
+
}
|
|
37
|
+
}),
|
|
38
|
+
|
|
39
|
+
// 3. 系统信息工具
|
|
40
|
+
systemInfo: tool({
|
|
41
|
+
description: "获取系统信息",
|
|
42
|
+
args: {},
|
|
43
|
+
async execute(args, context) {
|
|
44
|
+
const { sessionID, agent } = context;
|
|
45
|
+
const info = {
|
|
46
|
+
agent: agent,
|
|
47
|
+
sessionID: sessionID,
|
|
48
|
+
timestamp: new Date().toISOString(),
|
|
49
|
+
platform: process.platform,
|
|
50
|
+
nodeVersion: process.version
|
|
51
|
+
};
|
|
52
|
+
return JSON.stringify(info, null, 2);
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// 钩子示例
|
|
58
|
+
"tool.execute.before": async (input, output) => {
|
|
59
|
+
console.log(`🛠️ 工具即将执行: ${input.tool}`);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
"tool.execute.after": async (input, output) => {
|
|
63
|
+
console.log(`✅ 工具执行完成: ${input.tool}`);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@itismyskillmarket/test-skill",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "SkillMarket 测试技能 - 用于验证安装流程",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"keywords": ["skillmarket", "test"],
|
|
8
|
+
"author": "SkillMarket",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@opencode-ai/plugin": "^1.1.31"
|
|
12
|
+
},
|
|
13
|
+
"skillmarket": {
|
|
14
|
+
"id": "test-skill",
|
|
15
|
+
"displayName": "测试技能",
|
|
16
|
+
"description": "这是一个用于测试 SkillMarket 安装流程的 skill",
|
|
17
|
+
"platforms": ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Test Skill 1
|
|
2
|
+
|
|
3
|
+
用于测试 SkillMarket 安装和 info 功能的 skill。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 验证 skm install 功能
|
|
8
|
+
- 验证 skm info 功能
|
|
9
|
+
|
|
10
|
+
## 使用方法
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
skm install test-skill-1
|
|
14
|
+
skm info test-skill-1
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 平台支持
|
|
18
|
+
|
|
19
|
+
- OpenCode
|
|
20
|
+
- Cursor
|
|
21
|
+
- VSCode
|
|
22
|
+
- Claude Code
|
|
23
|
+
- Codex
|
|
24
|
+
- Antigravity
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "test-skill-1",
|
|
3
|
+
"displayName": "测试技能1",
|
|
4
|
+
"description": "用于测试 SkillMarket 安装和 info 功能",
|
|
5
|
+
"version": "1.0.3",
|
|
6
|
+
"author": "SkillMarket",
|
|
7
|
+
"platforms": ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"],
|
|
8
|
+
"tags": ["test", "install", "info"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wanxuchen/test-skill-1",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Test Skill 1 - 用于测试 SkillMarket 安装流程",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"keywords": ["skillmarket", "test"],
|
|
8
|
+
"author": "SkillMarket",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"skillmarket": {
|
|
11
|
+
"id": "test-skill-1",
|
|
12
|
+
"displayName": "测试技能1",
|
|
13
|
+
"description": "用于测试 SkillMarket 安装和 info 功能",
|
|
14
|
+
"platforms": ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Test Skill 2
|
|
2
|
+
|
|
3
|
+
用于测试 SkillMarket 卸载和更新流程的 skill。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 验证 skm uninstall 功能
|
|
8
|
+
- 验证 skm update 功能
|
|
9
|
+
|
|
10
|
+
## 使用方法
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
skm install test-skill-2
|
|
14
|
+
skm update test-skill-2
|
|
15
|
+
skm uninstall test-skill-2
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 平台支持
|
|
19
|
+
|
|
20
|
+
- OpenCode
|
|
21
|
+
- Cursor
|
|
22
|
+
- VSCode
|
|
23
|
+
- Claude Code
|
|
24
|
+
- Codex
|
|
25
|
+
- Antigravity
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "test-skill-2",
|
|
3
|
+
"displayName": "测试技能2",
|
|
4
|
+
"description": "用于测试 SkillMarket 卸载和更新功能",
|
|
5
|
+
"version": "1.0.3",
|
|
6
|
+
"author": "SkillMarket",
|
|
7
|
+
"platforms": ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"],
|
|
8
|
+
"tags": ["test", "uninstall", "update"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wanxuchen/test-skill-2",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "Test Skill 2 - 用于测试 SkillMarket 卸载和更新流程",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"keywords": ["skillmarket", "test"],
|
|
8
|
+
"author": "SkillMarket",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"skillmarket": {
|
|
11
|
+
"id": "test-skill-2",
|
|
12
|
+
"displayName": "测试技能2",
|
|
13
|
+
"description": "用于测试 SkillMarket 卸载和更新功能",
|
|
14
|
+
"platforms": ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* Base Platform Adapter
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Abstract base class for platform-specific skill adapters.
|
|
7
|
+
* Provides common functionality for all platforms.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import type { PlatformAdapter } from '../types.js';
|
|
13
|
+
|
|
14
|
+
export abstract class BaseAdapter implements PlatformAdapter {
|
|
15
|
+
abstract readonly id: string;
|
|
16
|
+
abstract readonly name: string;
|
|
17
|
+
abstract readonly skillDir: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the path where a specific skill should be installed
|
|
21
|
+
*/
|
|
22
|
+
protected getSkillPath(skillId: string): string {
|
|
23
|
+
return path.join(this.skillDir, skillId);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the path to the SKILL.md file for a skill
|
|
28
|
+
*/
|
|
29
|
+
protected getSkillFilePath(skillId: string): string {
|
|
30
|
+
return path.join(this.getSkillPath(skillId), 'SKILL.md');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async isAvailable(): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
await fs.ensureDir(this.skillDir);
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async isInstalled(skillId: string): Promise<boolean> {
|
|
43
|
+
const skillFile = this.getSkillFilePath(skillId);
|
|
44
|
+
return fs.pathExists(skillFile);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async install(skillId: string, sourceDir: string): Promise<void> {
|
|
48
|
+
const targetDir = this.getSkillPath(skillId);
|
|
49
|
+
const targetFile = this.getSkillFilePath(skillId);
|
|
50
|
+
|
|
51
|
+
await fs.ensureDir(targetDir);
|
|
52
|
+
|
|
53
|
+
const sourceFile = path.join(sourceDir, 'SKILL.md');
|
|
54
|
+
if (!(await fs.pathExists(sourceFile))) {
|
|
55
|
+
throw new Error(`SKILL.md not found in ${sourceDir}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await fs.copy(sourceFile, targetFile, { overwrite: true });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async uninstall(skillId: string): Promise<void> {
|
|
62
|
+
const targetDir = this.getSkillPath(skillId);
|
|
63
|
+
if (await fs.pathExists(targetDir)) {
|
|
64
|
+
await fs.remove(targetDir);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async listInstalled(): Promise<string[]> {
|
|
69
|
+
if (!(await fs.pathExists(this.skillDir))) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const entries = await fs.readdir(this.skillDir, { withFileTypes: true });
|
|
74
|
+
const skills: string[] = [];
|
|
75
|
+
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
const skillFile = path.join(this.skillDir, entry.name, 'SKILL.md');
|
|
79
|
+
if (await fs.pathExists(skillFile)) {
|
|
80
|
+
skills.push(entry.name);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return skills;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* Claude Code Platform Adapter
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Handles skill installation for Claude Code CLI.
|
|
7
|
+
* Skills are installed to ~/.claude/skills/<skill-id>/
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import fs from 'fs-extra';
|
|
13
|
+
import { BaseAdapter } from './base.js';
|
|
14
|
+
|
|
15
|
+
export class ClaudeAdapter extends BaseAdapter {
|
|
16
|
+
readonly id = 'claude';
|
|
17
|
+
readonly name = 'Claude Code';
|
|
18
|
+
|
|
19
|
+
get skillDir(): string {
|
|
20
|
+
return path.join(os.homedir(), '.claude', 'skills');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async isAvailable(): Promise<boolean> {
|
|
24
|
+
// Check for environment variable or directory
|
|
25
|
+
if (process.env.CLAUDE_CODE) return true;
|
|
26
|
+
|
|
27
|
+
// Check if .claude directory exists
|
|
28
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
29
|
+
return fs.pathExists(claudeDir);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform adapters index
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { BaseAdapter } from './base.js';
|
|
6
|
+
export { OpenCodeAdapter } from './opencode.js';
|
|
7
|
+
export { ClaudeAdapter } from './claude.js';
|
|
8
|
+
export { VSCodeAdapter } from './vscode.js';
|
|
9
|
+
export { detectPlatforms, getPlatformAdapter, getAllAdapters, getAdapterByPlatform } from './registry.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* OpenCode Platform Adapter
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Handles skill installation for OpenCode AI coding tool.
|
|
7
|
+
* Skills are installed to ~/.config/opencode/skills/<skill-id>/
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import os from 'os';
|
|
12
|
+
import fs from 'fs-extra';
|
|
13
|
+
import { BaseAdapter } from './base.js';
|
|
14
|
+
|
|
15
|
+
export class OpenCodeAdapter extends BaseAdapter {
|
|
16
|
+
readonly id = 'opencode';
|
|
17
|
+
readonly name = 'OpenCode';
|
|
18
|
+
|
|
19
|
+
get skillDir(): string {
|
|
20
|
+
// Respect OPENCODE_CONFIG_DIR environment variable
|
|
21
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR
|
|
22
|
+
|| path.join(os.homedir(), '.config', 'opencode');
|
|
23
|
+
return path.join(configDir, 'skills');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async isAvailable(): Promise<boolean> {
|
|
27
|
+
// Check for environment variable or directory
|
|
28
|
+
if (process.env.OPENCODE) return true;
|
|
29
|
+
|
|
30
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR
|
|
31
|
+
|| path.join(os.homedir(), '.config', 'opencode');
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await fs.ensureDir(path.join(configDir, 'skills'));
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* Platform Registry
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Central registry for platform adapters.
|
|
7
|
+
* Handles platform detection and selection.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { OpenCodeAdapter } from './opencode.js';
|
|
11
|
+
import { ClaudeAdapter } from './claude.js';
|
|
12
|
+
import { VSCodeAdapter } from './vscode.js';
|
|
13
|
+
import type { PlatformAdapter } from '../types.js';
|
|
14
|
+
import type { Platform } from '../constants.js';
|
|
15
|
+
|
|
16
|
+
const adapters: Map<string, PlatformAdapter> = new Map();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Register all built-in platform adapters
|
|
20
|
+
*/
|
|
21
|
+
function registerAdapters(): void {
|
|
22
|
+
const opencode = new OpenCodeAdapter();
|
|
23
|
+
const claude = new ClaudeAdapter();
|
|
24
|
+
const vscode = new VSCodeAdapter();
|
|
25
|
+
|
|
26
|
+
adapters.set(opencode.id, opencode);
|
|
27
|
+
adapters.set(claude.id, claude);
|
|
28
|
+
adapters.set(vscode.id, vscode);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Register adapters on module load
|
|
32
|
+
registerAdapters();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Detect which platforms are available on the current system
|
|
36
|
+
*/
|
|
37
|
+
export async function detectPlatforms(): Promise<PlatformAdapter[]> {
|
|
38
|
+
const available: PlatformAdapter[] = [];
|
|
39
|
+
|
|
40
|
+
for (const adapter of adapters.values()) {
|
|
41
|
+
if (await adapter.isAvailable()) {
|
|
42
|
+
available.push(adapter);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return available;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get adapter for a specific platform
|
|
51
|
+
*/
|
|
52
|
+
export function getPlatformAdapter(platformId: string): PlatformAdapter | undefined {
|
|
53
|
+
return adapters.get(platformId);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get all registered adapters
|
|
58
|
+
*/
|
|
59
|
+
export function getAllAdapters(): PlatformAdapter[] {
|
|
60
|
+
return Array.from(adapters.values());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get adapter by platform type
|
|
65
|
+
*/
|
|
66
|
+
export function getAdapterByPlatform(platform: Platform): PlatformAdapter | undefined {
|
|
67
|
+
const idMap: Record<Platform, string> = {
|
|
68
|
+
opencode: 'opencode',
|
|
69
|
+
claude: 'claude',
|
|
70
|
+
vscode: 'vscode',
|
|
71
|
+
cursor: 'opencode', // Cursor uses OpenCode-compatible structure
|
|
72
|
+
codex: 'opencode', // Codex uses OpenCode-compatible structure
|
|
73
|
+
antigravity: 'opencode', // Antigravity uses OpenCode-compatible structure
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return adapters.get(idMap[platform]);
|
|
77
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* VSCode (Copilot) Platform Adapter
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Handles skill installation for VSCode GitHub Copilot Agent Skills.
|
|
7
|
+
* Skills are installed to ~/.copilot/skills/<skill-id>/
|
|
8
|
+
*
|
|
9
|
+
* Note: Also supports ~/.claude/skills/ for cross-compatibility.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
import fs from 'fs-extra';
|
|
15
|
+
import { BaseAdapter } from './base.js';
|
|
16
|
+
|
|
17
|
+
export class VSCodeAdapter extends BaseAdapter {
|
|
18
|
+
readonly id = 'vscode';
|
|
19
|
+
readonly name = 'VSCode';
|
|
20
|
+
|
|
21
|
+
get skillDir(): string {
|
|
22
|
+
// Try ~/.copilot/skills first, fallback to ~/.claude/skills
|
|
23
|
+
return path.join(os.homedir(), '.copilot', 'skills');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async isAvailable(): Promise<boolean> {
|
|
27
|
+
// Check multiple possible locations
|
|
28
|
+
const possibleDirs = [
|
|
29
|
+
path.join(os.homedir(), '.copilot', 'skills'),
|
|
30
|
+
path.join(os.homedir(), '.claude', 'skills'),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const dir of possibleDirs) {
|
|
34
|
+
try {
|
|
35
|
+
await fs.ensureDir(dir);
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async install(skillId: string, sourceDir: string): Promise<void> {
|
|
46
|
+
// Install to ~/.copilot/skills, but also create symlink in ~/.claude/skills
|
|
47
|
+
await super.install(skillId, sourceDir);
|
|
48
|
+
|
|
49
|
+
// Create cross-compatible symlink in ~/.claude/skills
|
|
50
|
+
const claudeSkillDir = path.join(os.homedir(), '.claude', 'skills');
|
|
51
|
+
const targetPath = this.getSkillPath(skillId);
|
|
52
|
+
const claudeTargetPath = path.join(claudeSkillDir, skillId);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
await fs.ensureDir(claudeSkillDir);
|
|
56
|
+
await fs.remove(claudeTargetPath);
|
|
57
|
+
await fs.symlink(targetPath, claudeTargetPath, 'junction');
|
|
58
|
+
} catch {
|
|
59
|
+
// Silently fail if symlink not possible (cross-platform compatibility)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|