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.
@@ -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
+ }
@@ -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
+ });
@@ -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 to local and platform directories');
240
+ const installCmd = program.command('install').description('Install a skill from npm or GitHub');
240
241
  installCmd
241
- .argument('<skill>', 'Skill ID to install (e.g., brainstorming or @scope/name)')
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
- await installSkill(skill, opts.version, {
252
- platforms,
253
- force: opts.force
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);