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 CHANGED
@@ -1,3 +1,150 @@
1
+ # SkillMarket v1.3.3 更新日志
2
+
3
+ **日期**: 2026-04-30
4
+ **版本**: 1.3.3
5
+
6
+ ---
7
+
8
+ ## 🚀 新功能:GitHub 第三方库支持 (Beta)
9
+
10
+ ### 1. 支持 GitHub URL 和简写格式
11
+
12
+ 现在可以直接从 GitHub 仓库安装 skills:
13
+
14
+ ```bash
15
+ # GitHub URL 格式
16
+ skm install https://github.com/owner/repo
17
+ skm install https://github.com/owner/repo/tree/main/skills/my-skill
18
+
19
+ # 简写格式
20
+ skm install owner/repo
21
+ skm install owner/repo#branch
22
+ skm install owner/repo@commit-hash
23
+ ```
24
+
25
+ ### 2. 自动检测 Skill 本体
26
+
27
+ 安装时会自动检测仓库中的 skill 文件:
28
+
29
+ - ✅ `SKILL.md` - skill 定义文件(必须)
30
+ - ✅ `package.json` - 包配置文件(可选)
31
+ - ✅ `metadata.json` - 元数据文件(可选)
32
+ - ✅ 平台目录(`opencode/`, `cursor/`, `vscode/`, `claude/` 等)
33
+
34
+ **检测输出示例**:
35
+ ```
36
+ Detecting skill...
37
+ SKILL.md: ✅
38
+ package.json: ✅
39
+ Detected platforms: opencode, vscode
40
+ ```
41
+
42
+ ### 3. 平台判断和格式转换
43
+
44
+ - 自动判断 skill 支持的平台
45
+ - 如果某些平台文件缺失,会自动生成适配文件
46
+ - 支持的平台:OpenCode, Cursor, VSCode, Claude Code, Codex, Antigravity
47
+
48
+ ```bash
49
+ # 安装到指定平台(自动生成缺失的平台文件)
50
+ skm install owner/repo --platform opencode,claude
51
+
52
+ # 指定分支
53
+ skm install owner/repo#dev --platform vscode
54
+ ```
55
+
56
+ ### 4. 版本控制
57
+
58
+ 支持指定分支、tag 或 commit:
59
+
60
+ ```bash
61
+ # 指定分支
62
+ skm install owner/repo#main
63
+ skm install owner/repo -b develop
64
+
65
+ # 指定 commit
66
+ skm install owner/repo@abc1234
67
+
68
+ # 指定 tag(通过分支名)
69
+ skm install owner/repo#v1.0.0
70
+ ```
71
+
72
+ ### 5. 技术实现
73
+
74
+ **新增模块**:`src/commands/github-install.ts`
75
+
76
+ | 函数名 | 功能 |
77
+ |--------|------|
78
+ | `parseGitHubUrl()` | 解析 GitHub URL 和简写格式 |
79
+ | `detectSkillFromGitHub()` | 从 GitHub API 检测 skill |
80
+ | `installFromGitHub()` | 从 GitHub 安装 skill |
81
+ | `generatePlatformAdapters()` | 为缺失平台生成适配文件 |
82
+
83
+ **支持的 URL 格式**:
84
+ | 格式 | 示例 |
85
+ |------|------|
86
+ | 完整 URL | `https://github.com/owner/repo` |
87
+ | 完整 URL + 路径 | `https://github.com/owner/repo/tree/main/path` |
88
+ | 简写 | `owner/repo` |
89
+ | 简写 + 分支 | `owner/repo#branch` |
90
+ | 简写 + commit | `owner/repo@commit` |
91
+
92
+ ---
93
+
94
+ ## 🔧 技术实现
95
+
96
+ ### GitHub 安装新增函数
97
+
98
+ | 函数名 | 功能 |
99
+ |--------|------|
100
+ | `parseGitHubUrl()` | 解析 GitHub URL |
101
+ | `detectSkillFromGitHub()` | 检测 skill 本体 |
102
+ | `installFromGitHub()` | 主安装函数 |
103
+ | `generatePlatformAdapters()` | 格式转换 |
104
+
105
+ ### 更新接口
106
+
107
+ **GitHubInstallOptions** 接口:
108
+ ```typescript
109
+ export interface GitHubInstallOptions {
110
+ platforms?: string[]; // 目标平台
111
+ force?: boolean; // 强制覆盖
112
+ branch?: string; // 指定分支
113
+ commit?: string; // 指定 commit
114
+ }
115
+ ```
116
+
117
+ ### CLI 参数更新
118
+
119
+ | 命令 | 参数 | 说明 |
120
+ |------|------|------|
121
+ | `skm install` | `-b, --branch` | GitHub 分支 |
122
+ | `skm install` | `-c, --commit` | GitHub commit hash |
123
+
124
+ ---
125
+
126
+ ## 📦 完整版本历史
127
+
128
+ | 版本 | 日期 | 描述 |
129
+ |------|------|------|
130
+ | 1.3.3 | 2026-04-30 | GitHub 第三方库支持 |
131
+ | 1.3.2 | 2026-04-30 | 增强卸载命令:--all, --dry-run, --yes |
132
+ | 1.3.1 | 2026-04-29 | Bug 修复,workflow 改进 |
133
+ | 1.3.0 | 2026-04-23 | 独立搜索命令,改进分页逻辑 |
134
+ | 1.2.6 | 2026-04-22 | 添加搜索功能(--search) |
135
+ | 1.2.5 | 2026-04-16 | 文档更新 |
136
+ | 1.2.4 | 2026-04-16 | 修复版本号硬编码问题 |
137
+ | 1.2.3 | 2026-04-15 | 跨平台 Skill 安装支持 |
138
+
139
+ ---
140
+
141
+ ## 贡献者
142
+
143
+ - wxc2004 (wanxuchen)
144
+ - Sisyphus Agent
145
+
146
+ ---
147
+
1
148
  # SkillMarket v1.3.2 更新日志
2
149
 
3
150
  **日期**: 2026-04-30
@@ -90,14 +237,6 @@ VSCode ✅ Uninstalled
90
237
  ⚠️ 1 platform(s) failed to uninstall. Continue with local cleanup? (y/N): _
91
238
  ```
92
239
 
93
- ### 5. 更新帮助文档
94
-
95
- `skm --help` 现在包含卸载命令的完整说明:
96
-
97
- ```bash
98
- skm uninstall --help
99
- ```
100
-
101
240
  ---
102
241
 
103
242
  ## 🔧 技术实现
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() {
@@ -584,8 +701,10 @@ function getAdapterByPlatform(platform) {
584
701
  // Cursor uses OpenCode-compatible structure
585
702
  codex: "opencode",
586
703
  // Codex uses OpenCode-compatible structure
587
- antigravity: "opencode"
704
+ antigravity: "opencode",
588
705
  // Antigravity uses OpenCode-compatible structure
706
+ openclaw: "openclaw",
707
+ hermes: "hermes"
589
708
  };
590
709
  return adapters.get(idMap[platform]);
591
710
  }
@@ -974,6 +1093,272 @@ Uninstalling all skills...
974
1093
  return { success: successCount, failed: failedCount };
975
1094
  }
976
1095
 
1096
+ // src/commands/github-install.ts
1097
+ import fs10 from "fs-extra";
1098
+ import path9 from "path";
1099
+ var GITHUB_URL_PATTERNS = [
1100
+ /^https?:\/\/github\.com\/([^/]+)\/([^/]+)(?:\/tree\/([^/]+)(?:\/(.+))?)?$/,
1101
+ /^([^/]+)\/([^/]+)(?:#(.+))?$/,
1102
+ // owner/repo#branch
1103
+ /^([^/]+)\/([^/]+)@(.+)$/
1104
+ // owner/repo@commit
1105
+ ];
1106
+ function parseGitHubUrl(input) {
1107
+ input = input.replace(/\/$/, "");
1108
+ for (const pattern of GITHUB_URL_PATTERNS) {
1109
+ const match = input.match(pattern);
1110
+ if (match) {
1111
+ const owner = match[1];
1112
+ const repo = match[2].replace(/\.git$/, "");
1113
+ const branch = match[3] || "main";
1114
+ const commitOrPath = match[4] || match[3];
1115
+ const path10 = match[5] || void 0;
1116
+ return {
1117
+ owner,
1118
+ repo,
1119
+ branch: commitOrPath && !commitOrPath.includes("/") ? commitOrPath : branch,
1120
+ commit: commitOrPath?.match(/^[0-9a-f]{40}$/) ? commitOrPath : void 0,
1121
+ path: path10
1122
+ };
1123
+ }
1124
+ }
1125
+ return null;
1126
+ }
1127
+ async function fetchGitHubFile(source, filePath) {
1128
+ const ref = source.commit || source.branch || "main";
1129
+ const url = `https://api.github.com/repos/${source.owner}/${source.repo}/contents/${filePath}?ref=${ref}`;
1130
+ try {
1131
+ const response = await fetch(url, {
1132
+ headers: {
1133
+ "Accept": "application/vnd.github.v3.raw",
1134
+ "User-Agent": "SkillMarket"
1135
+ }
1136
+ });
1137
+ if (!response.ok) {
1138
+ return null;
1139
+ }
1140
+ return await response.text();
1141
+ } catch {
1142
+ return null;
1143
+ }
1144
+ }
1145
+ async function fetchGitHubDir(source, dirPath = "") {
1146
+ const ref = source.commit || source.branch || "main";
1147
+ const url = `https://api.github.com/repos/${source.owner}/${source.repo}/contents/${dirPath}?ref=${ref}`;
1148
+ try {
1149
+ const response = await fetch(url, {
1150
+ headers: {
1151
+ "Accept": "application/vnd.github.v3+json",
1152
+ "User-Agent": "SkillMarket"
1153
+ }
1154
+ });
1155
+ if (!response.ok) {
1156
+ return [];
1157
+ }
1158
+ const data = await response.json();
1159
+ return Array.isArray(data) ? data : [];
1160
+ } catch {
1161
+ return [];
1162
+ }
1163
+ }
1164
+ async function detectSkillFromGitHub(source) {
1165
+ const result = {
1166
+ hasSkillMd: false,
1167
+ hasPackageJson: false,
1168
+ hasMetadataJson: false,
1169
+ platforms: [],
1170
+ files: []
1171
+ };
1172
+ const basePath = source.path || "";
1173
+ const skillMdPath = basePath ? `${basePath}/SKILL.md` : "SKILL.md";
1174
+ const skillMdContent = await fetchGitHubFile(source, skillMdPath);
1175
+ if (skillMdContent !== null) {
1176
+ result.hasSkillMd = true;
1177
+ result.files.push(skillMdPath);
1178
+ }
1179
+ const packageJsonPath = basePath ? `${basePath}/package.json` : "package.json";
1180
+ const packageJsonContent = await fetchGitHubFile(source, packageJsonPath);
1181
+ if (packageJsonContent !== null) {
1182
+ result.hasPackageJson = true;
1183
+ result.files.push(packageJsonPath);
1184
+ try {
1185
+ const pkg = JSON.parse(packageJsonContent);
1186
+ result.skillId = pkg.name?.split("/").pop() || pkg.skillmarket?.id;
1187
+ result.displayName = pkg.skillmarket?.displayName;
1188
+ result.description = pkg.description;
1189
+ if (pkg.skillmarket?.platforms) {
1190
+ result.platforms = pkg.skillmarket.platforms;
1191
+ }
1192
+ } catch {
1193
+ }
1194
+ }
1195
+ const metadataPath = basePath ? `${basePath}/metadata.json` : "metadata.json";
1196
+ const metadataContent = await fetchGitHubFile(source, metadataPath);
1197
+ if (metadataContent !== null) {
1198
+ result.hasMetadataJson = true;
1199
+ result.files.push(metadataPath);
1200
+ }
1201
+ const platformDirs = ["opencode", "cursor", "vscode", "claude", "codex", "antigravity"];
1202
+ for (const dir of platformDirs) {
1203
+ const dirPath = basePath ? `${basePath}/${dir}` : dir;
1204
+ const files = await fetchGitHubDir(source, dirPath);
1205
+ if (files.length > 0) {
1206
+ const platform = dir;
1207
+ if (!result.platforms.includes(platform)) {
1208
+ result.platforms.push(platform);
1209
+ }
1210
+ result.files.push(dirPath);
1211
+ }
1212
+ }
1213
+ if (result.platforms.length === 0) {
1214
+ result.platforms.push("opencode");
1215
+ }
1216
+ return result;
1217
+ }
1218
+ async function generatePlatformAdapters(skillId, existingPlatforms, targetPlatforms, sourceDir) {
1219
+ const skillsDir = getSkillsDir();
1220
+ const skillVersionDir = path9.join(skillsDir, `${skillId}@github`);
1221
+ for (const platform of targetPlatforms) {
1222
+ if (existingPlatforms.includes(platform)) {
1223
+ continue;
1224
+ }
1225
+ const platformDir = path9.join(skillVersionDir, platform);
1226
+ await fs10.ensureDir(platformDir);
1227
+ const sourceSkillMd = path9.join(sourceDir, "SKILL.md");
1228
+ const targetSkillMd = path9.join(platformDir, "SKILL.md");
1229
+ if (await fs10.pathExists(sourceSkillMd)) {
1230
+ await fs10.copy(sourceSkillMd, targetSkillMd);
1231
+ }
1232
+ if (platform === "opencode" || platform === "cursor" || platform === "codex" || platform === "antigravity") {
1233
+ } else if (platform === "vscode") {
1234
+ const skillJson = {
1235
+ name: skillId,
1236
+ description: `Skill: ${skillId}`,
1237
+ version: "1.0.0"
1238
+ };
1239
+ await fs10.writeJson(path9.join(platformDir, "skill.json"), skillJson, { spaces: 2 });
1240
+ } else if (platform === "claude") {
1241
+ const skillJson = {
1242
+ name: skillId,
1243
+ description: `Skill: ${skillId}`,
1244
+ version: "1.0.0"
1245
+ };
1246
+ await fs10.writeJson(path9.join(platformDir, "skill.json"), skillJson, { spaces: 2 });
1247
+ }
1248
+ }
1249
+ }
1250
+ async function installFromGitHub(input, options) {
1251
+ let source = parseGitHubUrl(input);
1252
+ if (!source) {
1253
+ throw new Error(`Invalid GitHub URL or format: ${input}`);
1254
+ }
1255
+ if (options?.branch) source.branch = options.branch;
1256
+ if (options?.commit) source.commit = options.commit;
1257
+ console.log(`Installing from GitHub: ${source.owner}/${source.repo}`);
1258
+ if (source.branch) console.log(` Branch: ${source.branch}`);
1259
+ if (source.commit) console.log(` Commit: ${source.commit}`);
1260
+ if (source.path) console.log(` Path: ${source.path}`);
1261
+ console.log("\nDetecting skill...");
1262
+ const detected = await detectSkillFromGitHub(source);
1263
+ if (!detected.hasSkillMd && !detected.hasPackageJson) {
1264
+ throw new Error("No skill found in this repository (missing SKILL.md and package.json)");
1265
+ }
1266
+ console.log(` SKILL.md: ${detected.hasSkillMd ? "\u2705" : "\u274C"}`);
1267
+ console.log(` package.json: ${detected.hasPackageJson ? "\u2705" : "\u274C"}`);
1268
+ console.log(` Detected platforms: ${detected.platforms.join(", ") || "none"}`);
1269
+ const skillId = detected.skillId || source.repo;
1270
+ const version = `github-${source.commit?.substring(0, 7) || source.branch || "main"}`;
1271
+ console.log(`
1272
+ Setting up skill: ${skillId}@${version}`);
1273
+ await ensureMarketDirs();
1274
+ const skillsDir = getSkillsDir();
1275
+ const skillVersionDir = path9.join(skillsDir, `${skillId}@${version}`);
1276
+ await fs10.ensureDir(skillVersionDir);
1277
+ console.log("Downloading files...");
1278
+ const basePath = source.path || "";
1279
+ const skillMdPath = basePath ? `${basePath}/SKILL.md` : "SKILL.md";
1280
+ const skillMdContent = await fetchGitHubFile(source, skillMdPath);
1281
+ if (skillMdContent) {
1282
+ await fs10.writeFile(path9.join(skillVersionDir, "SKILL.md"), skillMdContent);
1283
+ console.log(" \u2705 SKILL.md");
1284
+ }
1285
+ const packageJsonPath = basePath ? `${basePath}/package.json` : "package.json";
1286
+ const packageJsonContent = await fetchGitHubFile(source, packageJsonPath);
1287
+ if (packageJsonContent) {
1288
+ await fs10.writeFile(path9.join(skillVersionDir, "package.json"), packageJsonContent);
1289
+ console.log(" \u2705 package.json");
1290
+ }
1291
+ const metadataPath = basePath ? `${basePath}/metadata.json` : "metadata.json";
1292
+ const metadataContent = await fetchGitHubFile(source, metadataPath);
1293
+ if (metadataContent) {
1294
+ await fs10.writeFile(path9.join(skillVersionDir, "metadata.json"), metadataContent);
1295
+ console.log(" \u2705 metadata.json");
1296
+ }
1297
+ const targetPlatforms = options?.platforms || detected.platforms;
1298
+ const existingPlatforms = detected.platforms;
1299
+ const missingPlatforms = targetPlatforms.filter((p) => !existingPlatforms.includes(p));
1300
+ if (missingPlatforms.length > 0) {
1301
+ console.log(`
1302
+ Generating adapters for missing platforms: ${missingPlatforms.join(", ")}`);
1303
+ await generatePlatformAdapters(skillId, existingPlatforms, targetPlatforms, skillVersionDir);
1304
+ console.log(" \u2705 Platform adapters generated");
1305
+ }
1306
+ const skillDir = path9.join(skillsDir, skillId);
1307
+ await fs10.ensureDir(skillDir);
1308
+ const latestLink = path9.join(skillDir, "latest");
1309
+ try {
1310
+ await fs10.remove(latestLink);
1311
+ await fs10.symlink(skillVersionDir, latestLink, "junction");
1312
+ } catch {
1313
+ await fs10.copy(skillVersionDir, path9.join(skillDir, "latest"), { overwrite: true });
1314
+ }
1315
+ console.log(`
1316
+ Installing to ${targetPlatforms.length} platform(s)...
1317
+ `);
1318
+ const results = [];
1319
+ for (const platform of targetPlatforms) {
1320
+ const adapter = getAdapterByPlatform(platform);
1321
+ if (!adapter) {
1322
+ console.log(`${platform.padEnd(12)} \u274C Unknown platform`);
1323
+ results.push({ name: platform, status: "failed", error: "Unknown platform" });
1324
+ continue;
1325
+ }
1326
+ try {
1327
+ const isInstalled = await adapter.isInstalled(skillId);
1328
+ if (isInstalled && !options?.force) {
1329
+ console.log(`${adapter.name.padEnd(12)} \u26A0\uFE0F Already installed (use --force to overwrite)`);
1330
+ results.push({ name: adapter.name, status: "skipped" });
1331
+ continue;
1332
+ }
1333
+ await adapter.install(skillId, skillVersionDir);
1334
+ console.log(`${adapter.name.padEnd(12)} \u2705 Installed successfully`);
1335
+ results.push({ name: adapter.name, status: "installed" });
1336
+ } catch (error) {
1337
+ console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${error}`);
1338
+ results.push({ name: adapter.name, status: "failed", error: String(error) });
1339
+ }
1340
+ }
1341
+ const installed = results.filter((r) => r.status === "installed").length;
1342
+ const skipped = results.filter((r) => r.status === "skipped").length;
1343
+ const failed = results.filter((r) => r.status === "failed").length;
1344
+ console.log(`
1345
+ \u{1F4CA} Summary: ${installed} installed, ${skipped} skipped, ${failed} failed`);
1346
+ console.log(`
1347
+ Installing to platforms: ${targetPlatforms.join(", ")}`);
1348
+ console.log(" (Platform installation logic needs to be completed)");
1349
+ const registry = await loadRegistry();
1350
+ registry.skills[skillId] = {
1351
+ id: skillId,
1352
+ version,
1353
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1354
+ platforms: targetPlatforms
1355
+ };
1356
+ await saveRegistry(registry);
1357
+ console.log(`
1358
+ \u2705 ${skillId}@${version} installed successfully from GitHub!`);
1359
+ console.log(` Source: ${source.owner}/${source.repo}`);
1360
+ }
1361
+
977
1362
  // src/cli.ts
978
1363
  var __filename = fileURLToPath(import.meta.url);
979
1364
  var __dirname = dirname(__filename);
@@ -1051,14 +1436,24 @@ var infoCmd = program.command("info").description("Display skill information");
1051
1436
  infoCmd.argument("<skill-id>", "Skill ID to show info").action((skillId) => {
1052
1437
  showSkillInfo(skillId);
1053
1438
  });
1054
- var installCmd = program.command("install").description("Install a skill to local and platform directories");
1055
- installCmd.argument("<skill>", "Skill ID to install (e.g., brainstorming or @scope/name)").option("-p, --platform <platforms>", "Target platforms (comma-separated: opencode,claude,vscode)").option("-f, --force", "Overwrite if already installed").option("-v, --version <version>", "Specific version to install").action(async (skill, opts) => {
1439
+ var installCmd = program.command("install").description("Install a skill from npm or GitHub");
1440
+ installCmd.argument("<skill>", "Skill ID, npm package, or GitHub URL (owner/repo, https://github.com/owner/repo)").option("-p, --platform <platforms>", "Target platforms (comma-separated: opencode,claude,vscode)").option("-f, --force", "Overwrite if already installed").option("-v, --version <version>", "Specific version to install (npm only)").option("-b, --branch <branch>", "GitHub branch to install from").option("-c, --commit <commit>", "GitHub commit hash to install from").action(async (skill, opts) => {
1056
1441
  try {
1057
1442
  const platforms = opts.platform ? opts.platform.split(",").map((p) => p.trim()) : void 0;
1058
- await installSkill(skill, opts.version, {
1059
- platforms,
1060
- force: opts.force
1061
- });
1443
+ const githubSource = parseGitHubUrl(skill);
1444
+ if (githubSource) {
1445
+ await installFromGitHub(skill, {
1446
+ platforms,
1447
+ force: opts.force,
1448
+ branch: opts.branch,
1449
+ commit: opts.commit
1450
+ });
1451
+ } else {
1452
+ await installSkill(skill, opts.version, {
1453
+ platforms,
1454
+ force: opts.force
1455
+ });
1456
+ }
1062
1457
  } catch (err) {
1063
1458
  console.error("Installation failed:", err);
1064
1459
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itismyskillmarket",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Cross-platform skill manager for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
+ });