itismyskillmarket 1.3.3 → 1.3.5
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 +3 -1
- package/dist/index.js +128 -10
- 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 +5 -10
- package/src/constants.test.ts +18 -0
- package/src/constants.ts +3 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SkillMarket
|
|
2
2
|
|
|
3
|
-
Cross-platform skill manager for AI coding tools (Cursor, VSCode, Codex, OpenCode, Claude Code, Antigravity).
|
|
3
|
+
Cross-platform skill manager for AI coding tools (Cursor, VSCode, Codex, OpenCode, Claude Code, Antigravity, OpenClaw, Hermes Agent).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -104,6 +104,8 @@ $ skm platforms
|
|
|
104
104
|
OpenCode ✅ Available (2 skills installed)
|
|
105
105
|
Claude Code ✅ Available (1 skills installed)
|
|
106
106
|
VSCode ✅ Available (0 skills installed)
|
|
107
|
+
OpenClaw ✅ Available
|
|
108
|
+
Hermes Agent ✅ Available
|
|
107
109
|
```
|
|
108
110
|
|
|
109
111
|
## Development
|
package/dist/index.js
CHANGED
|
@@ -35,8 +35,12 @@ var PLATFORMS = [
|
|
|
35
35
|
// OpenCode - 开源 AI 编程工具
|
|
36
36
|
"claude",
|
|
37
37
|
// Claude Code - Anthropic CLI 工具
|
|
38
|
-
"antigravity"
|
|
38
|
+
"antigravity",
|
|
39
39
|
// Antigravity - AI 编程助手
|
|
40
|
+
"openclaw",
|
|
41
|
+
// OpenClaw - AgentSkills compatible agent
|
|
42
|
+
"hermes"
|
|
43
|
+
// Hermes Agent - NousResearch agent framework
|
|
40
44
|
];
|
|
41
45
|
var REGISTRY_FILE = "registry.json";
|
|
42
46
|
var LATEST_LINK = "latest";
|
|
@@ -555,15 +559,128 @@ var VSCodeAdapter = class extends BaseAdapter {
|
|
|
555
559
|
}
|
|
556
560
|
};
|
|
557
561
|
|
|
562
|
+
// src/adapters/openclaw.ts
|
|
563
|
+
import { readdirSync, existsSync, cpSync, rmSync } from "fs";
|
|
564
|
+
import { join } from "path";
|
|
565
|
+
import { homedir } from "os";
|
|
566
|
+
import { ensureDirSync } from "fs-extra";
|
|
567
|
+
var OpenClawAdapter = class {
|
|
568
|
+
id = "openclaw";
|
|
569
|
+
name = "OpenClaw";
|
|
570
|
+
skillDir = join(homedir(), ".openclaw", "skills");
|
|
571
|
+
async isAvailable() {
|
|
572
|
+
try {
|
|
573
|
+
return existsSync(join(homedir(), ".openclaw"));
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
async isInstalled(skillId) {
|
|
579
|
+
try {
|
|
580
|
+
const skillPath = join(this.skillDir, skillId);
|
|
581
|
+
return existsSync(skillPath);
|
|
582
|
+
} catch {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async install(skillId, sourceDir) {
|
|
587
|
+
ensureDirSync(this.skillDir);
|
|
588
|
+
const targetDir = join(this.skillDir, skillId);
|
|
589
|
+
if (existsSync(targetDir)) {
|
|
590
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
591
|
+
}
|
|
592
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
593
|
+
}
|
|
594
|
+
async uninstall(skillId) {
|
|
595
|
+
const targetDir = join(this.skillDir, skillId);
|
|
596
|
+
if (existsSync(targetDir)) {
|
|
597
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async listInstalled() {
|
|
601
|
+
try {
|
|
602
|
+
if (!existsSync(this.skillDir)) {
|
|
603
|
+
return [];
|
|
604
|
+
}
|
|
605
|
+
return readdirSync(this.skillDir).filter((name) => {
|
|
606
|
+
const fullPath = join(this.skillDir, name);
|
|
607
|
+
return existsSync(fullPath) && name !== ".";
|
|
608
|
+
});
|
|
609
|
+
} catch {
|
|
610
|
+
return [];
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
// src/adapters/hermes.ts
|
|
616
|
+
import { readdirSync as readdirSync2, existsSync as existsSync2, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
|
|
617
|
+
import { join as join2 } from "path";
|
|
618
|
+
import { homedir as homedir2 } from "os";
|
|
619
|
+
import { ensureDirSync as ensureDirSync2 } from "fs-extra";
|
|
620
|
+
var HermesAdapter = class {
|
|
621
|
+
id = "hermes";
|
|
622
|
+
name = "Hermes Agent";
|
|
623
|
+
skillDir = join2(homedir2(), ".hermes", "skills");
|
|
624
|
+
async isAvailable() {
|
|
625
|
+
try {
|
|
626
|
+
if (existsSync2(join2(homedir2(), ".hermes"))) {
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
return false;
|
|
630
|
+
} catch {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
async isInstalled(skillId) {
|
|
635
|
+
try {
|
|
636
|
+
const skillPath = join2(this.skillDir, skillId);
|
|
637
|
+
return existsSync2(skillPath);
|
|
638
|
+
} catch {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async install(skillId, sourceDir) {
|
|
643
|
+
ensureDirSync2(this.skillDir);
|
|
644
|
+
const targetDir = join2(this.skillDir, skillId);
|
|
645
|
+
if (existsSync2(targetDir)) {
|
|
646
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
647
|
+
}
|
|
648
|
+
cpSync2(sourceDir, targetDir, { recursive: true });
|
|
649
|
+
}
|
|
650
|
+
async uninstall(skillId) {
|
|
651
|
+
const targetDir = join2(this.skillDir, skillId);
|
|
652
|
+
if (existsSync2(targetDir)) {
|
|
653
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
async listInstalled() {
|
|
657
|
+
try {
|
|
658
|
+
if (!existsSync2(this.skillDir)) {
|
|
659
|
+
return [];
|
|
660
|
+
}
|
|
661
|
+
return readdirSync2(this.skillDir).filter((name) => {
|
|
662
|
+
const fullPath = join2(this.skillDir, name);
|
|
663
|
+
return existsSync2(fullPath) && name !== ".";
|
|
664
|
+
});
|
|
665
|
+
} catch {
|
|
666
|
+
return [];
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
|
|
558
671
|
// src/adapters/registry.ts
|
|
559
672
|
var adapters = /* @__PURE__ */ new Map();
|
|
560
673
|
function registerAdapters() {
|
|
561
674
|
const opencode = new OpenCodeAdapter();
|
|
562
675
|
const claude = new ClaudeAdapter();
|
|
563
676
|
const vscode = new VSCodeAdapter();
|
|
677
|
+
const openclaw = new OpenClawAdapter();
|
|
678
|
+
const hermes = new HermesAdapter();
|
|
564
679
|
adapters.set(opencode.id, opencode);
|
|
565
680
|
adapters.set(claude.id, claude);
|
|
566
681
|
adapters.set(vscode.id, vscode);
|
|
682
|
+
adapters.set(openclaw.id, openclaw);
|
|
683
|
+
adapters.set(hermes.id, hermes);
|
|
567
684
|
}
|
|
568
685
|
registerAdapters();
|
|
569
686
|
async function detectPlatforms() {
|
|
@@ -575,6 +692,9 @@ async function detectPlatforms() {
|
|
|
575
692
|
}
|
|
576
693
|
return available;
|
|
577
694
|
}
|
|
695
|
+
function getAllAdapters() {
|
|
696
|
+
return Array.from(adapters.values());
|
|
697
|
+
}
|
|
578
698
|
function getAdapterByPlatform(platform) {
|
|
579
699
|
const idMap = {
|
|
580
700
|
opencode: "opencode",
|
|
@@ -584,8 +704,10 @@ function getAdapterByPlatform(platform) {
|
|
|
584
704
|
// Cursor uses OpenCode-compatible structure
|
|
585
705
|
codex: "opencode",
|
|
586
706
|
// Codex uses OpenCode-compatible structure
|
|
587
|
-
antigravity: "opencode"
|
|
707
|
+
antigravity: "opencode",
|
|
588
708
|
// Antigravity uses OpenCode-compatible structure
|
|
709
|
+
openclaw: "openclaw",
|
|
710
|
+
hermes: "hermes"
|
|
589
711
|
};
|
|
590
712
|
return adapters.get(idMap[platform]);
|
|
591
713
|
}
|
|
@@ -1391,19 +1513,15 @@ var platformsCmd = program.command("platforms").description("Show available plat
|
|
|
1391
1513
|
platformsCmd.action(async () => {
|
|
1392
1514
|
try {
|
|
1393
1515
|
const available = await detectPlatforms();
|
|
1516
|
+
const allAdapters = getAllAdapters();
|
|
1394
1517
|
console.log("\n\u{1F4CD} Available Platforms:\n");
|
|
1395
|
-
const
|
|
1396
|
-
{ name: "OpenCode", adapter: new OpenCodeAdapter() },
|
|
1397
|
-
{ name: "Claude Code", adapter: new ClaudeAdapter() },
|
|
1398
|
-
{ name: "VSCode", adapter: new VSCodeAdapter() }
|
|
1399
|
-
];
|
|
1400
|
-
for (const { name, adapter } of allPlatforms) {
|
|
1518
|
+
for (const adapter of allAdapters) {
|
|
1401
1519
|
const isAvailable = available.find((a) => a.id === adapter.id);
|
|
1402
1520
|
const installed = await adapter.listInstalled();
|
|
1403
1521
|
if (isAvailable) {
|
|
1404
|
-
console.log(`${name.padEnd(
|
|
1522
|
+
console.log(`${adapter.name.padEnd(15)} \u2705 Available (${installed.length} skills installed)`);
|
|
1405
1523
|
} else {
|
|
1406
|
-
console.log(`${name.padEnd(
|
|
1524
|
+
console.log(`${adapter.name.padEnd(15)} \u274C Not detected`);
|
|
1407
1525
|
}
|
|
1408
1526
|
}
|
|
1409
1527
|
console.log("");
|
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { HermesAdapter } from './hermes.js';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
|
|
6
|
+
describe('HermesAdapter', () => {
|
|
7
|
+
let adapter: HermesAdapter;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
adapter = new HermesAdapter();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should have id "hermes"', () => {
|
|
14
|
+
expect(adapter.id).toBe('hermes');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should have name "Hermes Agent"', () => {
|
|
18
|
+
expect(adapter.name).toBe('Hermes Agent');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should have correct skillDir', () => {
|
|
22
|
+
expect(adapter.skillDir).toBe(join(homedir(), '.hermes', 'skills'));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should check availability based on ~/.hermes/ existence', async () => {
|
|
26
|
+
const result = await adapter.isAvailable();
|
|
27
|
+
expect(typeof result).toBe('boolean');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should check if skill is installed', async () => {
|
|
31
|
+
const result = await adapter.isInstalled('test-skill');
|
|
32
|
+
expect(typeof result).toBe('boolean');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should list installed skills', async () => {
|
|
36
|
+
const result = await adapter.listInstalled();
|
|
37
|
+
expect(Array.isArray(result)).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -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
|
@@ -49,7 +49,7 @@ import { syncPlatformLinks } from './commands/sync.js'; // 同步命令
|
|
|
49
49
|
import { updateSkill } from './commands/update.js'; // 更新命令
|
|
50
50
|
import { uninstallSkill, uninstallAll } from './commands/uninstall.js'; // 卸载命令
|
|
51
51
|
import { installFromGitHub, parseGitHubUrl } from './commands/github-install.js'; // GitHub 安装
|
|
52
|
-
import { detectPlatforms, getAllAdapters
|
|
52
|
+
import { detectPlatforms, getAllAdapters } from './adapters/index.js'; // 平台适配器
|
|
53
53
|
|
|
54
54
|
// -----------------------------------------------------------------------------
|
|
55
55
|
// 创建命令程序实例
|
|
@@ -417,23 +417,18 @@ platformsCmd
|
|
|
417
417
|
.action(async () => {
|
|
418
418
|
try {
|
|
419
419
|
const available = await detectPlatforms();
|
|
420
|
+
const allAdapters = getAllAdapters();
|
|
420
421
|
|
|
421
422
|
console.log('\n📍 Available Platforms:\n');
|
|
422
423
|
|
|
423
|
-
const
|
|
424
|
-
{ name: 'OpenCode', adapter: new OpenCodeAdapter() },
|
|
425
|
-
{ name: 'Claude Code', adapter: new ClaudeAdapter() },
|
|
426
|
-
{ name: 'VSCode', adapter: new VSCodeAdapter() },
|
|
427
|
-
];
|
|
428
|
-
|
|
429
|
-
for (const { name, adapter } of allPlatforms) {
|
|
424
|
+
for (const adapter of allAdapters) {
|
|
430
425
|
const isAvailable = available.find(a => a.id === adapter.id);
|
|
431
426
|
const installed = await adapter.listInstalled();
|
|
432
427
|
|
|
433
428
|
if (isAvailable) {
|
|
434
|
-
console.log(`${name.padEnd(
|
|
429
|
+
console.log(`${adapter.name.padEnd(15)} ✅ Available (${installed.length} skills installed)`);
|
|
435
430
|
} else {
|
|
436
|
-
console.log(`${name.padEnd(
|
|
431
|
+
console.log(`${adapter.name.padEnd(15)} ❌ Not detected`);
|
|
437
432
|
}
|
|
438
433
|
}
|
|
439
434
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { PLATFORMS } from './constants.js';
|
|
3
|
+
|
|
4
|
+
describe('PLATFORMS', () => {
|
|
5
|
+
it('should include openclaw', () => {
|
|
6
|
+
expect(PLATFORMS).toContain('openclaw');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should include hermes', () => {
|
|
10
|
+
expect(PLATFORMS).toContain('hermes');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should maintain existing platforms', () => {
|
|
14
|
+
expect(PLATFORMS).toContain('opencode');
|
|
15
|
+
expect(PLATFORMS).toContain('claude');
|
|
16
|
+
expect(PLATFORMS).toContain('vscode');
|
|
17
|
+
});
|
|
18
|
+
});
|
package/src/constants.ts
CHANGED
|
@@ -81,7 +81,9 @@ export const PLATFORMS = [
|
|
|
81
81
|
'codex', // OpenAI Codex - OpenAI 代码生成模型
|
|
82
82
|
'opencode', // OpenCode - 开源 AI 编程工具
|
|
83
83
|
'claude', // Claude Code - Anthropic CLI 工具
|
|
84
|
-
'antigravity' // Antigravity - AI 编程助手
|
|
84
|
+
'antigravity', // Antigravity - AI 编程助手
|
|
85
|
+
'openclaw', // OpenClaw - AgentSkills compatible agent
|
|
86
|
+
'hermes', // Hermes Agent - NousResearch agent framework
|
|
85
87
|
] as const;
|
|
86
88
|
|
|
87
89
|
/**
|