itismyskillmarket 1.3.2 → 1.3.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/CHANGELOG.md +147 -8
- package/README.md +3 -1
- package/dist/index.js +403 -8
- package/package.json +1 -1
- package/src/adapters/hermes.test.ts +39 -0
- package/src/adapters/hermes.ts +77 -0
- package/src/adapters/index.ts +2 -0
- package/src/adapters/openclaw.test.ts +40 -0
- package/src/adapters/openclaw.ts +69 -0
- package/src/adapters/registry.test.ts +29 -0
- package/src/adapters/registry.ts +8 -0
- package/src/cli.ts +24 -7
- package/src/commands/github-install.ts +538 -0
- package/src/constants.test.ts +18 -0
- package/src/constants.ts +3 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermes Agent Platform Adapter
|
|
3
|
+
*
|
|
4
|
+
* Installs skills to ~/.hermes/skills/
|
|
5
|
+
* Hermes also supports project-level skills/ directory
|
|
6
|
+
* Hermes uses AgentSkills-compatible SKILL.md format with metadata.hermes
|
|
7
|
+
*/
|
|
8
|
+
import { PlatformAdapter } from './base.js';
|
|
9
|
+
import { readdirSync, existsSync, cpSync, rmSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
import { ensureDirSync } from 'fs-extra';
|
|
13
|
+
|
|
14
|
+
export class HermesAdapter implements PlatformAdapter {
|
|
15
|
+
readonly id = 'hermes';
|
|
16
|
+
readonly name = 'Hermes Agent';
|
|
17
|
+
readonly skillDir = join(homedir(), '.hermes', 'skills');
|
|
18
|
+
|
|
19
|
+
async isAvailable(): Promise<boolean> {
|
|
20
|
+
try {
|
|
21
|
+
// Check global hermes directory
|
|
22
|
+
if (existsSync(join(homedir(), '.hermes'))) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Could also check for project-level skills/ directory
|
|
27
|
+
// but for SkillMarket, we focus on global installation
|
|
28
|
+
return false;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async isInstalled(skillId: string): Promise<boolean> {
|
|
35
|
+
try {
|
|
36
|
+
const skillPath = join(this.skillDir, skillId);
|
|
37
|
+
return existsSync(skillPath);
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async install(skillId: string, sourceDir: string): Promise<void> {
|
|
44
|
+
ensureDirSync(this.skillDir);
|
|
45
|
+
const targetDir = join(this.skillDir, skillId);
|
|
46
|
+
|
|
47
|
+
// Remove existing skill directory if present
|
|
48
|
+
if (existsSync(targetDir)) {
|
|
49
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Copy entire skill directory (SKILL.md + supporting files)
|
|
53
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async uninstall(skillId: string): Promise<void> {
|
|
57
|
+
const targetDir = join(this.skillDir, skillId);
|
|
58
|
+
if (existsSync(targetDir)) {
|
|
59
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async listInstalled(): Promise<string[]> {
|
|
64
|
+
try {
|
|
65
|
+
if (!existsSync(this.skillDir)) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
return readdirSync(this.skillDir)
|
|
69
|
+
.filter(name => {
|
|
70
|
+
const fullPath = join(this.skillDir, name);
|
|
71
|
+
return existsSync(fullPath) && name !== '.';
|
|
72
|
+
});
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/adapters/index.ts
CHANGED
|
@@ -6,4 +6,6 @@ export { BaseAdapter } from './base.js';
|
|
|
6
6
|
export { OpenCodeAdapter } from './opencode.js';
|
|
7
7
|
export { ClaudeAdapter } from './claude.js';
|
|
8
8
|
export { VSCodeAdapter } from './vscode.js';
|
|
9
|
+
export { OpenClawAdapter } from './openclaw.js';
|
|
10
|
+
export { HermesAdapter } from './hermes.js';
|
|
9
11
|
export { detectPlatforms, getPlatformAdapter, getAllAdapters, getAdapterByPlatform } from './registry.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { OpenClawAdapter } from './openclaw.js';
|
|
3
|
+
import { readdirSync, existsSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
|
|
7
|
+
describe('OpenClawAdapter', () => {
|
|
8
|
+
let adapter: OpenClawAdapter;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
adapter = new OpenClawAdapter();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should have id "openclaw"', () => {
|
|
15
|
+
expect(adapter.id).toBe('openclaw');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should have name "OpenClaw"', () => {
|
|
19
|
+
expect(adapter.name).toBe('OpenClaw');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should have correct skillDir', () => {
|
|
23
|
+
expect(adapter.skillDir).toBe(join(homedir(), '.openclaw', 'skills'));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should check availability based on ~/.openclaw/ existence', async () => {
|
|
27
|
+
const result = await adapter.isAvailable();
|
|
28
|
+
expect(typeof result).toBe('boolean');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should check if skill is installed', async () => {
|
|
32
|
+
const result = await adapter.isInstalled('test-skill');
|
|
33
|
+
expect(typeof result).toBe('boolean');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should list installed skills', async () => {
|
|
37
|
+
const result = await adapter.listInstalled();
|
|
38
|
+
expect(Array.isArray(result)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Platform Adapter
|
|
3
|
+
*
|
|
4
|
+
* Installs skills to ~/.openclaw/skills/
|
|
5
|
+
* OpenClaw uses AgentSkills-compatible SKILL.md format
|
|
6
|
+
*/
|
|
7
|
+
import { PlatformAdapter } from './base.js';
|
|
8
|
+
import { readdirSync, existsSync, cpSync, rmSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { ensureDirSync } from 'fs-extra';
|
|
12
|
+
|
|
13
|
+
export class OpenClawAdapter implements PlatformAdapter {
|
|
14
|
+
readonly id = 'openclaw';
|
|
15
|
+
readonly name = 'OpenClaw';
|
|
16
|
+
readonly skillDir = join(homedir(), '.openclaw', 'skills');
|
|
17
|
+
|
|
18
|
+
async isAvailable(): Promise<boolean> {
|
|
19
|
+
try {
|
|
20
|
+
return existsSync(join(homedir(), '.openclaw'));
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async isInstalled(skillId: string): Promise<boolean> {
|
|
27
|
+
try {
|
|
28
|
+
const skillPath = join(this.skillDir, skillId);
|
|
29
|
+
return existsSync(skillPath);
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async install(skillId: string, sourceDir: string): Promise<void> {
|
|
36
|
+
ensureDirSync(this.skillDir);
|
|
37
|
+
const targetDir = join(this.skillDir, skillId);
|
|
38
|
+
|
|
39
|
+
// Remove existing skill directory if present
|
|
40
|
+
if (existsSync(targetDir)) {
|
|
41
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Copy entire skill directory (SKILL.md + supporting files)
|
|
45
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async uninstall(skillId: string): Promise<void> {
|
|
49
|
+
const targetDir = join(this.skillDir, skillId);
|
|
50
|
+
if (existsSync(targetDir)) {
|
|
51
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async listInstalled(): Promise<string[]> {
|
|
56
|
+
try {
|
|
57
|
+
if (!existsSync(this.skillDir)) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
return readdirSync(this.skillDir)
|
|
61
|
+
.filter(name => {
|
|
62
|
+
const fullPath = join(this.skillDir, name);
|
|
63
|
+
return existsSync(fullPath) && name !== '.';
|
|
64
|
+
});
|
|
65
|
+
} catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getAdapterByPlatform } from './registry.js';
|
|
3
|
+
import { OpenClawAdapter } from './openclaw.js';
|
|
4
|
+
import { HermesAdapter } from './hermes.js';
|
|
5
|
+
|
|
6
|
+
describe('getAdapterByPlatform', () => {
|
|
7
|
+
it('should return OpenClawAdapter for "openclaw"', () => {
|
|
8
|
+
const adapter = getAdapterByPlatform('openclaw');
|
|
9
|
+
expect(adapter.id).toBe('openclaw');
|
|
10
|
+
expect(adapter.name).toBe('OpenClaw');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should return HermesAdapter for "hermes"', () => {
|
|
14
|
+
const adapter = getAdapterByPlatform('hermes');
|
|
15
|
+
expect(adapter.id).toBe('hermes');
|
|
16
|
+
expect(adapter.name).toBe('Hermes Agent');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return adapter for "opencode"', () => {
|
|
20
|
+
const adapter = getAdapterByPlatform('opencode');
|
|
21
|
+
expect(adapter).toBeDefined();
|
|
22
|
+
expect(adapter.id).toBe('opencode');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should return undefined for unknown platform', () => {
|
|
26
|
+
const adapter = getAdapterByPlatform('unknown' as any);
|
|
27
|
+
expect(adapter).toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
});
|
package/src/adapters/registry.ts
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import { OpenCodeAdapter } from './opencode.js';
|
|
11
11
|
import { ClaudeAdapter } from './claude.js';
|
|
12
12
|
import { VSCodeAdapter } from './vscode.js';
|
|
13
|
+
import { OpenClawAdapter } from './openclaw.js';
|
|
14
|
+
import { HermesAdapter } from './hermes.js';
|
|
13
15
|
import type { PlatformAdapter } from '../types.js';
|
|
14
16
|
import type { Platform } from '../constants.js';
|
|
15
17
|
|
|
@@ -22,10 +24,14 @@ function registerAdapters(): void {
|
|
|
22
24
|
const opencode = new OpenCodeAdapter();
|
|
23
25
|
const claude = new ClaudeAdapter();
|
|
24
26
|
const vscode = new VSCodeAdapter();
|
|
27
|
+
const openclaw = new OpenClawAdapter();
|
|
28
|
+
const hermes = new HermesAdapter();
|
|
25
29
|
|
|
26
30
|
adapters.set(opencode.id, opencode);
|
|
27
31
|
adapters.set(claude.id, claude);
|
|
28
32
|
adapters.set(vscode.id, vscode);
|
|
33
|
+
adapters.set(openclaw.id, openclaw);
|
|
34
|
+
adapters.set(hermes.id, hermes);
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
// Register adapters on module load
|
|
@@ -71,6 +77,8 @@ export function getAdapterByPlatform(platform: Platform): PlatformAdapter | unde
|
|
|
71
77
|
cursor: 'opencode', // Cursor uses OpenCode-compatible structure
|
|
72
78
|
codex: 'opencode', // Codex uses OpenCode-compatible structure
|
|
73
79
|
antigravity: 'opencode', // Antigravity uses OpenCode-compatible structure
|
|
80
|
+
openclaw: 'openclaw',
|
|
81
|
+
hermes: 'hermes',
|
|
74
82
|
};
|
|
75
83
|
|
|
76
84
|
return adapters.get(idMap[platform]);
|
package/src/cli.ts
CHANGED
|
@@ -48,6 +48,7 @@ import { installSkill } from './commands/install.js'; // 安装命令
|
|
|
48
48
|
import { syncPlatformLinks } from './commands/sync.js'; // 同步命令
|
|
49
49
|
import { updateSkill } from './commands/update.js'; // 更新命令
|
|
50
50
|
import { uninstallSkill, uninstallAll } from './commands/uninstall.js'; // 卸载命令
|
|
51
|
+
import { installFromGitHub, parseGitHubUrl } from './commands/github-install.js'; // GitHub 安装
|
|
51
52
|
import { detectPlatforms, getAllAdapters, OpenCodeAdapter, ClaudeAdapter, VSCodeAdapter } from './adapters/index.js'; // 平台适配器
|
|
52
53
|
|
|
53
54
|
// -----------------------------------------------------------------------------
|
|
@@ -236,22 +237,38 @@ infoCmd
|
|
|
236
237
|
* skm install brainstorming@1.0.0
|
|
237
238
|
* skm install brainstorming --platform opencode
|
|
238
239
|
*/
|
|
239
|
-
const installCmd = program.command('install').description('Install a skill
|
|
240
|
+
const installCmd = program.command('install').description('Install a skill from npm or GitHub');
|
|
240
241
|
installCmd
|
|
241
|
-
.argument('<skill>', 'Skill ID
|
|
242
|
+
.argument('<skill>', 'Skill ID, npm package, or GitHub URL (owner/repo, https://github.com/owner/repo)')
|
|
242
243
|
.option('-p, --platform <platforms>', 'Target platforms (comma-separated: opencode,claude,vscode)')
|
|
243
244
|
.option('-f, --force', 'Overwrite if already installed')
|
|
244
|
-
.option('-v, --version <version>', 'Specific version to install')
|
|
245
|
+
.option('-v, --version <version>', 'Specific version to install (npm only)')
|
|
246
|
+
.option('-b, --branch <branch>', 'GitHub branch to install from')
|
|
247
|
+
.option('-c, --commit <commit>', 'GitHub commit hash to install from')
|
|
245
248
|
.action(async (skill, opts) => {
|
|
246
249
|
try {
|
|
247
250
|
const platforms = opts.platform
|
|
248
251
|
? opts.platform.split(',').map((p: string) => p.trim())
|
|
249
252
|
: undefined;
|
|
250
253
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
254
|
+
// 检测是否为 GitHub URL 或 owner/repo 格式
|
|
255
|
+
const githubSource = parseGitHubUrl(skill);
|
|
256
|
+
|
|
257
|
+
if (githubSource) {
|
|
258
|
+
// GitHub 安装
|
|
259
|
+
await installFromGitHub(skill, {
|
|
260
|
+
platforms,
|
|
261
|
+
force: opts.force,
|
|
262
|
+
branch: opts.branch,
|
|
263
|
+
commit: opts.commit
|
|
264
|
+
});
|
|
265
|
+
} else {
|
|
266
|
+
// npm 安装
|
|
267
|
+
await installSkill(skill, opts.version, {
|
|
268
|
+
platforms,
|
|
269
|
+
force: opts.force
|
|
270
|
+
});
|
|
271
|
+
}
|
|
255
272
|
} catch (err) {
|
|
256
273
|
console.error('Installation failed:', err);
|
|
257
274
|
process.exit(1);
|