itismyskillmarket 1.3.37 → 1.3.39
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 +4 -2
- package/dist/chunk-VRXNOGLL.js +2349 -0
- package/dist/electron-entry.d.ts +28 -0
- package/dist/electron-entry.js +7 -0
- package/dist/index.js +146 -2337
- package/electron/icon.ico +0 -0
- package/electron/icon.png +0 -0
- package/electron/main.mjs +214 -0
- package/electron/preload.mjs +18 -0
- package/electron/tray-icon.png +0 -0
- package/electron//345/220/257/345/212/250/346/241/214/351/235/242/345/272/224/347/224/250.bat +3 -0
- package/gui/app.js +38 -5
- package/package.json +60 -3
package/dist/index.js
CHANGED
|
@@ -1,294 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
LATEST_LINK,
|
|
4
|
+
PLATFORMS,
|
|
5
|
+
adminAccess,
|
|
6
|
+
adminDeprecate,
|
|
7
|
+
adminInfo,
|
|
8
|
+
adminList,
|
|
9
|
+
adminOwnerAdd,
|
|
10
|
+
adminOwnerRemove,
|
|
11
|
+
adminSearch,
|
|
12
|
+
adminStats,
|
|
13
|
+
adminTagList,
|
|
14
|
+
adminTagRemove,
|
|
15
|
+
adminTagSet,
|
|
16
|
+
adminUnpublish,
|
|
17
|
+
adminVerify,
|
|
18
|
+
detectPlatforms,
|
|
19
|
+
ensureMarketDirs,
|
|
20
|
+
fetchNpmPackage,
|
|
21
|
+
fetchSkillPackage,
|
|
22
|
+
getAdapterByPlatform,
|
|
23
|
+
getAllAdapters,
|
|
24
|
+
getInstalledSkills,
|
|
25
|
+
getPlatformLinksDir,
|
|
26
|
+
getSkillsDir,
|
|
27
|
+
installSkill,
|
|
28
|
+
isSkillInstalled,
|
|
29
|
+
loadRegistry,
|
|
30
|
+
publishSkill,
|
|
31
|
+
saveRegistry,
|
|
32
|
+
searchSkillmarketPackages,
|
|
33
|
+
startGuiServer,
|
|
34
|
+
uninstallAll,
|
|
35
|
+
uninstallSkill,
|
|
36
|
+
updateSkill
|
|
37
|
+
} from "./chunk-VRXNOGLL.js";
|
|
2
38
|
|
|
3
39
|
// src/cli.ts
|
|
4
40
|
import { Command } from "commander";
|
|
5
|
-
import { readFileSync
|
|
6
|
-
import { fileURLToPath as
|
|
7
|
-
import { dirname
|
|
8
|
-
import { execSync
|
|
9
|
-
|
|
10
|
-
// src/commands/registry.ts
|
|
11
|
-
import fs2 from "fs-extra";
|
|
12
|
-
|
|
13
|
-
// src/utils/dirs.ts
|
|
14
|
-
import os from "os";
|
|
15
|
-
import path from "path";
|
|
16
|
-
import fs from "fs-extra";
|
|
17
|
-
|
|
18
|
-
// src/constants.ts
|
|
19
|
-
var MARKET_DIR = "skillmarket";
|
|
20
|
-
var SUBDIRS = {
|
|
21
|
-
/** npm 包下载缓存目录,用于存储从 npm 下载的 skill 包 */
|
|
22
|
-
CACHE: "cache",
|
|
23
|
-
/** 已安装 skills 的主存储目录 */
|
|
24
|
-
SKILLS: "skills",
|
|
25
|
-
/** 各平台适配层软链接目录,用于跨平台共享 skills */
|
|
26
|
-
PLATFORM_LINKS: "platform-links"
|
|
27
|
-
};
|
|
28
|
-
var PLATFORMS = [
|
|
29
|
-
"cursor",
|
|
30
|
-
// Cursor IDE - AI 代码编辑器
|
|
31
|
-
"vscode",
|
|
32
|
-
// Visual Studio Code - 微软代码编辑器
|
|
33
|
-
"codex",
|
|
34
|
-
// OpenAI Codex - OpenAI 代码生成模型
|
|
35
|
-
"opencode",
|
|
36
|
-
// OpenCode - 开源 AI 编程工具
|
|
37
|
-
"claude",
|
|
38
|
-
// Claude Code - Anthropic CLI 工具
|
|
39
|
-
"antigravity",
|
|
40
|
-
// Antigravity - AI 编程助手
|
|
41
|
-
"openclaw",
|
|
42
|
-
// OpenClaw - AgentSkills compatible agent
|
|
43
|
-
"hermes"
|
|
44
|
-
// Hermes Agent - NousResearch agent framework
|
|
45
|
-
];
|
|
46
|
-
var REGISTRY_FILE = "registry.json";
|
|
47
|
-
var LATEST_LINK = "latest";
|
|
48
|
-
|
|
49
|
-
// src/utils/dirs.ts
|
|
50
|
-
function getMarketHome() {
|
|
51
|
-
return path.join(os.homedir(), MARKET_DIR);
|
|
52
|
-
}
|
|
53
|
-
function getCacheDir() {
|
|
54
|
-
return path.join(getMarketHome(), SUBDIRS.CACHE);
|
|
55
|
-
}
|
|
56
|
-
function getSkillsDir() {
|
|
57
|
-
return path.join(getMarketHome(), SUBDIRS.SKILLS);
|
|
58
|
-
}
|
|
59
|
-
function getPlatformLinksDir() {
|
|
60
|
-
return path.join(getMarketHome(), SUBDIRS.PLATFORM_LINKS);
|
|
61
|
-
}
|
|
62
|
-
function getRegistryPath() {
|
|
63
|
-
return path.join(getMarketHome(), REGISTRY_FILE);
|
|
64
|
-
}
|
|
65
|
-
async function ensureMarketDirs() {
|
|
66
|
-
await fs.ensureDir(getCacheDir());
|
|
67
|
-
await fs.ensureDir(getSkillsDir());
|
|
68
|
-
await fs.ensureDir(getPlatformLinksDir());
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// src/commands/registry.ts
|
|
72
|
-
var DEFAULT_REGISTRY = {
|
|
73
|
-
/** 空的 skills 字典,初始没有任何已安装的 skill */
|
|
74
|
-
skills: {},
|
|
75
|
-
/** 注册表创建时间 */
|
|
76
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
77
|
-
};
|
|
78
|
-
async function loadRegistry() {
|
|
79
|
-
const registryPath = getRegistryPath();
|
|
80
|
-
if (!await fs2.pathExists(registryPath)) {
|
|
81
|
-
return DEFAULT_REGISTRY;
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
const data = await fs2.readJson(registryPath);
|
|
85
|
-
return data;
|
|
86
|
-
} catch {
|
|
87
|
-
return DEFAULT_REGISTRY;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
async function saveRegistry(registry) {
|
|
91
|
-
await fs2.ensureDir(getMarketHome());
|
|
92
|
-
registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
93
|
-
await fs2.writeJson(getRegistryPath(), registry, { spaces: 2 });
|
|
94
|
-
}
|
|
95
|
-
async function getInstalledSkills() {
|
|
96
|
-
const registry = await loadRegistry();
|
|
97
|
-
return Object.values(registry.skills);
|
|
98
|
-
}
|
|
99
|
-
async function isSkillInstalled(skillId) {
|
|
100
|
-
const registry = await loadRegistry();
|
|
101
|
-
return skillId in registry.skills;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// src/commands/npm.ts
|
|
105
|
-
import https from "https";
|
|
106
|
-
import { URL as URL2 } from "url";
|
|
107
|
-
|
|
108
|
-
// src/config.ts
|
|
109
|
-
import { readFileSync, existsSync } from "fs";
|
|
110
|
-
import { join } from "path";
|
|
111
|
-
import os2 from "os";
|
|
112
|
-
var CONFIG_FILE_PATH = join(os2.homedir(), ".skillmarket", "config.json");
|
|
113
|
-
function loadConfigFileSync() {
|
|
114
|
-
try {
|
|
115
|
-
if (existsSync(CONFIG_FILE_PATH)) {
|
|
116
|
-
const raw = readFileSync(CONFIG_FILE_PATH, "utf-8");
|
|
117
|
-
return JSON.parse(raw);
|
|
118
|
-
}
|
|
119
|
-
} catch {
|
|
120
|
-
}
|
|
121
|
-
return {};
|
|
122
|
-
}
|
|
123
|
-
var fileConfig = loadConfigFileSync();
|
|
124
|
-
var NPM_SCOPE = process.env.SKM_NPM_SCOPE || fileConfig.npmScope || "@itismyskillmarket";
|
|
125
|
-
var NPM_SCOPE_FALLBACK = process.env.SKM_NPM_SCOPE_FALLBACK || fileConfig.npmScopeFallback || "@wanxuchen";
|
|
126
|
-
var NPM_REGISTRY = process.env.SKM_NPM_REGISTRY || fileConfig.npmRegistry || "https://registry.npmjs.org";
|
|
127
|
-
var DEFAULT_SCOPES = [
|
|
128
|
-
"@itismyskillmarket",
|
|
129
|
-
"@wanxuchen",
|
|
130
|
-
"@thisisskillmarket",
|
|
131
|
-
"@this-is-skillmarket",
|
|
132
|
-
"@skillmarket"
|
|
133
|
-
];
|
|
134
|
-
var fileScopes = fileConfig.npmScopes ? fileConfig.npmScopes.split(",").map((s) => s.trim()).filter(Boolean) : null;
|
|
135
|
-
var SKILL_SCOPES = process.env.SKM_NPM_SCOPES ? process.env.SKM_NPM_SCOPES.split(",").map((s) => s.trim()).filter(Boolean) : fileScopes || DEFAULT_SCOPES;
|
|
136
|
-
var SKM_URL = process.env.SKM_URL || fileConfig.skmUrl || `https://www.npmjs.com/package/${NPM_SCOPE}`;
|
|
137
|
-
|
|
138
|
-
// src/commands/npm.ts
|
|
139
|
-
var npmCache = /* @__PURE__ */ new Map();
|
|
140
|
-
function getCached(key) {
|
|
141
|
-
const entry = npmCache.get(key);
|
|
142
|
-
if (!entry) return null;
|
|
143
|
-
if (Date.now() > entry.expiry) {
|
|
144
|
-
npmCache.delete(key);
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
return entry.data;
|
|
148
|
-
}
|
|
149
|
-
function setCache(key, data, ttlMs = 3e4) {
|
|
150
|
-
npmCache.set(key, { data, expiry: Date.now() + ttlMs });
|
|
151
|
-
}
|
|
152
|
-
async function fetchNpmPackage(packageName, retries = 1) {
|
|
153
|
-
const cacheKey = `pkg:${packageName}`;
|
|
154
|
-
const cached = getCached(cacheKey);
|
|
155
|
-
if (cached) return cached;
|
|
156
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
157
|
-
try {
|
|
158
|
-
const result = await fetchNpmPackageOnce(packageName);
|
|
159
|
-
if (result !== null) {
|
|
160
|
-
setCache(cacheKey, result);
|
|
161
|
-
return result;
|
|
162
|
-
}
|
|
163
|
-
} catch {
|
|
164
|
-
if (attempt === retries) return null;
|
|
165
|
-
}
|
|
166
|
-
if (attempt < retries) {
|
|
167
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
async function fetchNpmPackageOnce(packageName) {
|
|
173
|
-
return new Promise((resolve2, reject) => {
|
|
174
|
-
const isScoped = packageName.startsWith("@");
|
|
175
|
-
let encodedName;
|
|
176
|
-
if (isScoped) {
|
|
177
|
-
const scopeAndName = packageName.substring(1);
|
|
178
|
-
const slashIndex = scopeAndName.indexOf("/");
|
|
179
|
-
if (slashIndex > 0) {
|
|
180
|
-
const scope = scopeAndName.substring(0, slashIndex);
|
|
181
|
-
const name = scopeAndName.substring(slashIndex + 1);
|
|
182
|
-
encodedName = `@${encodeURIComponent(scope)}%2F${encodeURIComponent(name)}`;
|
|
183
|
-
} else {
|
|
184
|
-
encodedName = packageName;
|
|
185
|
-
}
|
|
186
|
-
} else {
|
|
187
|
-
encodedName = encodeURIComponent(packageName);
|
|
188
|
-
}
|
|
189
|
-
const url = new URL2(`https://registry.npmjs.org/${encodedName}`);
|
|
190
|
-
const req = https.get(url.toString(), { timeout: 1e4 }, (res) => {
|
|
191
|
-
let data = "";
|
|
192
|
-
if (res.statusCode && res.statusCode >= 400) {
|
|
193
|
-
res.resume();
|
|
194
|
-
resolve2(null);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
res.on("data", (chunk) => {
|
|
198
|
-
data += chunk;
|
|
199
|
-
});
|
|
200
|
-
res.on("end", () => {
|
|
201
|
-
try {
|
|
202
|
-
const parsed = JSON.parse(data);
|
|
203
|
-
if (parsed.error) {
|
|
204
|
-
resolve2(null);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
resolve2(parsed);
|
|
208
|
-
} catch {
|
|
209
|
-
resolve2(null);
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
req.on("error", reject);
|
|
214
|
-
req.on("timeout", () => {
|
|
215
|
-
req.destroy();
|
|
216
|
-
reject(new Error("Request timeout"));
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
function getPossiblePackageNames(skillId) {
|
|
221
|
-
if (skillId.startsWith("@")) {
|
|
222
|
-
return [skillId];
|
|
223
|
-
}
|
|
224
|
-
return SKILL_SCOPES.map((scope) => `${scope}/${skillId}`);
|
|
225
|
-
}
|
|
226
|
-
async function fetchSkillPackage(skillId) {
|
|
227
|
-
const packageNames = getPossiblePackageNames(skillId);
|
|
228
|
-
for (const packageName of packageNames) {
|
|
229
|
-
try {
|
|
230
|
-
const info = await fetchNpmPackage(packageName);
|
|
231
|
-
if (info) {
|
|
232
|
-
return info;
|
|
233
|
-
}
|
|
234
|
-
} catch {
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return null;
|
|
238
|
-
}
|
|
239
|
-
async function searchSkillmarketPackages(options = {}) {
|
|
240
|
-
const { from = 0, size = 100, keyword } = options;
|
|
241
|
-
const packages = [];
|
|
242
|
-
let total = 0;
|
|
243
|
-
return new Promise((resolve2, reject) => {
|
|
244
|
-
const url = new URL2("https://registry.npmjs.org/-/v1/search");
|
|
245
|
-
url.searchParams.set("text", "keywords:skillmarket");
|
|
246
|
-
url.searchParams.set("size", String(Math.max(size, 100)));
|
|
247
|
-
url.searchParams.set("from", String(from));
|
|
248
|
-
const req = https.get(url.toString(), { timeout: 1e4 }, (res) => {
|
|
249
|
-
let data = "";
|
|
250
|
-
res.on("data", (chunk) => {
|
|
251
|
-
data += chunk;
|
|
252
|
-
});
|
|
253
|
-
res.on("end", () => {
|
|
254
|
-
try {
|
|
255
|
-
const result = JSON.parse(data);
|
|
256
|
-
let filteredTotal = 0;
|
|
257
|
-
if (result.objects) {
|
|
258
|
-
for (const item of result.objects) {
|
|
259
|
-
if (item?.package?.name) {
|
|
260
|
-
const pkgName = item.package.name;
|
|
261
|
-
const pkgDesc = item.package.description || "";
|
|
262
|
-
if (keyword) {
|
|
263
|
-
const lowerKeyword = keyword.toLowerCase();
|
|
264
|
-
const shortName = pkgName.includes("/") ? pkgName.split("/")[1] : pkgName;
|
|
265
|
-
const nameMatch = shortName.toLowerCase().includes(lowerKeyword) || pkgName.toLowerCase().includes(lowerKeyword);
|
|
266
|
-
const descMatch = pkgDesc.toLowerCase().includes(lowerKeyword);
|
|
267
|
-
if (nameMatch || descMatch) {
|
|
268
|
-
packages.push(pkgName);
|
|
269
|
-
filteredTotal++;
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
packages.push(pkgName);
|
|
273
|
-
filteredTotal++;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
total = filteredTotal;
|
|
279
|
-
resolve2({ packages, total });
|
|
280
|
-
} catch {
|
|
281
|
-
resolve2({ packages: [], total: 0 });
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
req.on("error", reject);
|
|
286
|
-
req.on("timeout", () => {
|
|
287
|
-
req.destroy();
|
|
288
|
-
reject(new Error("Request timeout"));
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
}
|
|
41
|
+
import { readFileSync } from "fs";
|
|
42
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
43
|
+
import { dirname, resolve } from "path";
|
|
44
|
+
import { execSync } from "child_process";
|
|
292
45
|
|
|
293
46
|
// src/commands/ls.ts
|
|
294
47
|
function formatDate(isoString) {
|
|
@@ -482,420 +235,9 @@ Status: Not installed (use skm install ${skillId} to install)`);
|
|
|
482
235
|
}
|
|
483
236
|
}
|
|
484
237
|
|
|
485
|
-
// src/commands/install.ts
|
|
486
|
-
import fs9 from "fs-extra";
|
|
487
|
-
import path8 from "path";
|
|
488
|
-
import { exec } from "child_process";
|
|
489
|
-
import { promisify } from "util";
|
|
490
|
-
import * as tar from "tar";
|
|
491
|
-
|
|
492
|
-
// src/adapters/base.ts
|
|
493
|
-
import fs3 from "fs-extra";
|
|
494
|
-
import path2 from "path";
|
|
495
|
-
var BaseAdapter = class {
|
|
496
|
-
/**
|
|
497
|
-
* Get the path where a specific skill should be installed
|
|
498
|
-
*/
|
|
499
|
-
getSkillPath(skillId) {
|
|
500
|
-
return path2.join(this.skillDir, skillId);
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Get the path to the SKILL.md file for a skill
|
|
504
|
-
*/
|
|
505
|
-
getSkillFilePath(skillId) {
|
|
506
|
-
return path2.join(this.getSkillPath(skillId), "SKILL.md");
|
|
507
|
-
}
|
|
508
|
-
async isAvailable() {
|
|
509
|
-
try {
|
|
510
|
-
await fs3.ensureDir(this.skillDir);
|
|
511
|
-
return true;
|
|
512
|
-
} catch {
|
|
513
|
-
return false;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
async isInstalled(skillId) {
|
|
517
|
-
const skillFile = this.getSkillFilePath(skillId);
|
|
518
|
-
return fs3.pathExists(skillFile);
|
|
519
|
-
}
|
|
520
|
-
async install(skillId, sourceDir) {
|
|
521
|
-
const targetDir = this.getSkillPath(skillId);
|
|
522
|
-
const targetFile = this.getSkillFilePath(skillId);
|
|
523
|
-
await fs3.ensureDir(targetDir);
|
|
524
|
-
const sourceFile = path2.join(sourceDir, "SKILL.md");
|
|
525
|
-
if (!await fs3.pathExists(sourceFile)) {
|
|
526
|
-
throw new Error(`SKILL.md not found in ${sourceDir}`);
|
|
527
|
-
}
|
|
528
|
-
await fs3.copy(sourceFile, targetFile, { overwrite: true });
|
|
529
|
-
}
|
|
530
|
-
async uninstall(skillId) {
|
|
531
|
-
const targetDir = this.getSkillPath(skillId);
|
|
532
|
-
if (await fs3.pathExists(targetDir)) {
|
|
533
|
-
await fs3.remove(targetDir);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
async listInstalled() {
|
|
537
|
-
if (!await fs3.pathExists(this.skillDir)) {
|
|
538
|
-
return [];
|
|
539
|
-
}
|
|
540
|
-
const entries = await fs3.readdir(this.skillDir, { withFileTypes: true });
|
|
541
|
-
const skills = [];
|
|
542
|
-
for (const entry of entries) {
|
|
543
|
-
if (entry.isDirectory()) {
|
|
544
|
-
const skillFile = path2.join(this.skillDir, entry.name, "SKILL.md");
|
|
545
|
-
if (await fs3.pathExists(skillFile)) {
|
|
546
|
-
skills.push(entry.name);
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
return skills;
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
// src/adapters/opencode.ts
|
|
555
|
-
import path3 from "path";
|
|
556
|
-
import os3 from "os";
|
|
557
|
-
import fs4 from "fs-extra";
|
|
558
|
-
var OpenCodeAdapter = class extends BaseAdapter {
|
|
559
|
-
id = "opencode";
|
|
560
|
-
name = "OpenCode";
|
|
561
|
-
get skillDir() {
|
|
562
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os3.homedir(), ".config", "opencode");
|
|
563
|
-
return path3.join(configDir, "skills");
|
|
564
|
-
}
|
|
565
|
-
async isAvailable() {
|
|
566
|
-
if (process.env.OPENCODE) return true;
|
|
567
|
-
const configDir = process.env.OPENCODE_CONFIG_DIR || path3.join(os3.homedir(), ".config", "opencode");
|
|
568
|
-
try {
|
|
569
|
-
await fs4.ensureDir(path3.join(configDir, "skills"));
|
|
570
|
-
return true;
|
|
571
|
-
} catch {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
// src/adapters/claude.ts
|
|
578
|
-
import path4 from "path";
|
|
579
|
-
import os4 from "os";
|
|
580
|
-
import fs5 from "fs-extra";
|
|
581
|
-
var ClaudeAdapter = class extends BaseAdapter {
|
|
582
|
-
id = "claude";
|
|
583
|
-
name = "Claude Code";
|
|
584
|
-
get skillDir() {
|
|
585
|
-
return path4.join(os4.homedir(), ".claude", "skills");
|
|
586
|
-
}
|
|
587
|
-
async isAvailable() {
|
|
588
|
-
if (process.env.CLAUDE_CODE) return true;
|
|
589
|
-
const claudeDir = path4.join(os4.homedir(), ".claude");
|
|
590
|
-
return fs5.pathExists(claudeDir);
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
// src/adapters/vscode.ts
|
|
595
|
-
import path5 from "path";
|
|
596
|
-
import os5 from "os";
|
|
597
|
-
import fs6 from "fs-extra";
|
|
598
|
-
var VSCodeAdapter = class extends BaseAdapter {
|
|
599
|
-
id = "vscode";
|
|
600
|
-
name = "VSCode";
|
|
601
|
-
get skillDir() {
|
|
602
|
-
return path5.join(os5.homedir(), ".copilot", "skills");
|
|
603
|
-
}
|
|
604
|
-
async isAvailable() {
|
|
605
|
-
const possibleDirs = [
|
|
606
|
-
path5.join(os5.homedir(), ".copilot", "skills"),
|
|
607
|
-
path5.join(os5.homedir(), ".claude", "skills")
|
|
608
|
-
];
|
|
609
|
-
for (const dir of possibleDirs) {
|
|
610
|
-
try {
|
|
611
|
-
await fs6.ensureDir(dir);
|
|
612
|
-
return true;
|
|
613
|
-
} catch {
|
|
614
|
-
continue;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return false;
|
|
618
|
-
}
|
|
619
|
-
async install(skillId, sourceDir) {
|
|
620
|
-
await super.install(skillId, sourceDir);
|
|
621
|
-
const claudeSkillDir = path5.join(os5.homedir(), ".claude", "skills");
|
|
622
|
-
const targetPath = this.getSkillPath(skillId);
|
|
623
|
-
const claudeTargetPath = path5.join(claudeSkillDir, skillId);
|
|
624
|
-
try {
|
|
625
|
-
await fs6.ensureDir(claudeSkillDir);
|
|
626
|
-
await fs6.remove(claudeTargetPath);
|
|
627
|
-
await fs6.symlink(targetPath, claudeTargetPath, "junction");
|
|
628
|
-
} catch {
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
};
|
|
632
|
-
|
|
633
|
-
// src/adapters/openclaw.ts
|
|
634
|
-
import path6 from "path";
|
|
635
|
-
import os6 from "os";
|
|
636
|
-
import fs7 from "fs-extra";
|
|
637
|
-
var OpenClawAdapter = class extends BaseAdapter {
|
|
638
|
-
id = "openclaw";
|
|
639
|
-
name = "OpenClaw";
|
|
640
|
-
skillDir = path6.join(os6.homedir(), ".openclaw", "skills");
|
|
641
|
-
async isAvailable() {
|
|
642
|
-
try {
|
|
643
|
-
return await fs7.pathExists(path6.join(os6.homedir(), ".openclaw"));
|
|
644
|
-
} catch {
|
|
645
|
-
return false;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
async isInstalled(skillId) {
|
|
649
|
-
try {
|
|
650
|
-
return await fs7.pathExists(path6.join(this.skillDir, skillId));
|
|
651
|
-
} catch {
|
|
652
|
-
return false;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
async install(skillId, sourceDir) {
|
|
656
|
-
await fs7.ensureDir(this.skillDir);
|
|
657
|
-
const targetDir = path6.join(this.skillDir, skillId);
|
|
658
|
-
if (await fs7.pathExists(targetDir)) {
|
|
659
|
-
await fs7.remove(targetDir);
|
|
660
|
-
}
|
|
661
|
-
await fs7.copy(sourceDir, targetDir, { recursive: true });
|
|
662
|
-
}
|
|
663
|
-
async uninstall(skillId) {
|
|
664
|
-
const targetDir = path6.join(this.skillDir, skillId);
|
|
665
|
-
if (await fs7.pathExists(targetDir)) {
|
|
666
|
-
await fs7.remove(targetDir);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
async listInstalled() {
|
|
670
|
-
try {
|
|
671
|
-
if (!await fs7.pathExists(this.skillDir)) {
|
|
672
|
-
return [];
|
|
673
|
-
}
|
|
674
|
-
const entries = await fs7.readdir(this.skillDir, { withFileTypes: true });
|
|
675
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
676
|
-
} catch {
|
|
677
|
-
return [];
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
// src/adapters/hermes.ts
|
|
683
|
-
import path7 from "path";
|
|
684
|
-
import os7 from "os";
|
|
685
|
-
import fs8 from "fs-extra";
|
|
686
|
-
var HermesAdapter = class extends BaseAdapter {
|
|
687
|
-
id = "hermes";
|
|
688
|
-
name = "Hermes Agent";
|
|
689
|
-
skillDir = path7.join(os7.homedir(), ".hermes", "skills");
|
|
690
|
-
async isAvailable() {
|
|
691
|
-
try {
|
|
692
|
-
return await fs8.pathExists(path7.join(os7.homedir(), ".hermes"));
|
|
693
|
-
} catch {
|
|
694
|
-
return false;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
async isInstalled(skillId) {
|
|
698
|
-
try {
|
|
699
|
-
return await fs8.pathExists(path7.join(this.skillDir, skillId));
|
|
700
|
-
} catch {
|
|
701
|
-
return false;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
async install(skillId, sourceDir) {
|
|
705
|
-
await fs8.ensureDir(this.skillDir);
|
|
706
|
-
const targetDir = path7.join(this.skillDir, skillId);
|
|
707
|
-
if (await fs8.pathExists(targetDir)) {
|
|
708
|
-
await fs8.remove(targetDir);
|
|
709
|
-
}
|
|
710
|
-
await fs8.copy(sourceDir, targetDir, { recursive: true });
|
|
711
|
-
}
|
|
712
|
-
async uninstall(skillId) {
|
|
713
|
-
const targetDir = path7.join(this.skillDir, skillId);
|
|
714
|
-
if (await fs8.pathExists(targetDir)) {
|
|
715
|
-
await fs8.remove(targetDir);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
async listInstalled() {
|
|
719
|
-
try {
|
|
720
|
-
if (!await fs8.pathExists(this.skillDir)) {
|
|
721
|
-
return [];
|
|
722
|
-
}
|
|
723
|
-
const entries = await fs8.readdir(this.skillDir, { withFileTypes: true });
|
|
724
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
725
|
-
} catch {
|
|
726
|
-
return [];
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
};
|
|
730
|
-
|
|
731
|
-
// src/adapters/registry.ts
|
|
732
|
-
var adapters = /* @__PURE__ */ new Map();
|
|
733
|
-
function registerAdapters() {
|
|
734
|
-
const opencode = new OpenCodeAdapter();
|
|
735
|
-
const claude = new ClaudeAdapter();
|
|
736
|
-
const vscode = new VSCodeAdapter();
|
|
737
|
-
const openclaw = new OpenClawAdapter();
|
|
738
|
-
const hermes = new HermesAdapter();
|
|
739
|
-
adapters.set(opencode.id, opencode);
|
|
740
|
-
adapters.set(claude.id, claude);
|
|
741
|
-
adapters.set(vscode.id, vscode);
|
|
742
|
-
adapters.set(openclaw.id, openclaw);
|
|
743
|
-
adapters.set(hermes.id, hermes);
|
|
744
|
-
}
|
|
745
|
-
registerAdapters();
|
|
746
|
-
async function detectPlatforms() {
|
|
747
|
-
const available = [];
|
|
748
|
-
for (const adapter of adapters.values()) {
|
|
749
|
-
if (await adapter.isAvailable()) {
|
|
750
|
-
available.push(adapter);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
return available;
|
|
754
|
-
}
|
|
755
|
-
function getAllAdapters() {
|
|
756
|
-
return Array.from(adapters.values());
|
|
757
|
-
}
|
|
758
|
-
function getAdapterByPlatform(platform) {
|
|
759
|
-
const idMap = {
|
|
760
|
-
opencode: "opencode",
|
|
761
|
-
claude: "claude",
|
|
762
|
-
vscode: "vscode",
|
|
763
|
-
cursor: "opencode",
|
|
764
|
-
// Cursor uses OpenCode-compatible structure
|
|
765
|
-
codex: "opencode",
|
|
766
|
-
// Codex uses OpenCode-compatible structure
|
|
767
|
-
antigravity: "opencode",
|
|
768
|
-
// Antigravity uses OpenCode-compatible structure
|
|
769
|
-
openclaw: "openclaw",
|
|
770
|
-
hermes: "hermes"
|
|
771
|
-
};
|
|
772
|
-
return adapters.get(idMap[platform]);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// src/commands/install.ts
|
|
776
|
-
var execAsync = promisify(exec);
|
|
777
|
-
async function installSkill(skillId, version, options) {
|
|
778
|
-
await ensureMarketDirs();
|
|
779
|
-
console.log(`Installing ${skillId}${version ? `@${version}` : ""}...`);
|
|
780
|
-
const pkgInfo = await fetchSkillPackage(skillId);
|
|
781
|
-
if (!pkgInfo) {
|
|
782
|
-
throw new Error(`Package ${skillId} not found`);
|
|
783
|
-
}
|
|
784
|
-
const packageName = pkgInfo.name;
|
|
785
|
-
const targetVersion = version || pkgInfo["dist-tags"]?.latest;
|
|
786
|
-
if (!targetVersion) {
|
|
787
|
-
throw new Error(`No version found for ${packageName}`);
|
|
788
|
-
}
|
|
789
|
-
const cacheDir = getCacheDir();
|
|
790
|
-
const targetDir = path8.join(cacheDir, `${packageName}@${targetVersion}`);
|
|
791
|
-
if (!await fs9.pathExists(targetDir)) {
|
|
792
|
-
console.log("Downloading package...");
|
|
793
|
-
await fs9.ensureDir(cacheDir);
|
|
794
|
-
try {
|
|
795
|
-
const { stdout } = await execAsync(
|
|
796
|
-
`npm pack ${packageName}@${targetVersion} --pack-destination "${cacheDir}"`
|
|
797
|
-
);
|
|
798
|
-
const tarballName = stdout.trim();
|
|
799
|
-
const tarballPath = path8.join(cacheDir, tarballName);
|
|
800
|
-
if (await fs9.pathExists(tarballPath)) {
|
|
801
|
-
await tar.extract({
|
|
802
|
-
file: tarballPath,
|
|
803
|
-
cwd: cacheDir
|
|
804
|
-
});
|
|
805
|
-
await fs9.remove(tarballPath);
|
|
806
|
-
await fs9.move(path8.join(cacheDir, "package"), targetDir, { overwrite: true });
|
|
807
|
-
}
|
|
808
|
-
} catch (err) {
|
|
809
|
-
throw new Error(`Failed to download package: ${err}`);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
const skillsDir = getSkillsDir();
|
|
813
|
-
const skillVersionDir = path8.join(skillsDir, `${skillId}@${targetVersion}`);
|
|
814
|
-
console.log("Setting up skill...");
|
|
815
|
-
await fs9.ensureDir(skillVersionDir);
|
|
816
|
-
const pkgRoot = targetDir;
|
|
817
|
-
if (await fs9.pathExists(path8.join(pkgRoot, "SKILL.md"))) {
|
|
818
|
-
await fs9.copy(
|
|
819
|
-
path8.join(pkgRoot, "SKILL.md"),
|
|
820
|
-
path8.join(skillVersionDir, "SKILL.md")
|
|
821
|
-
);
|
|
822
|
-
}
|
|
823
|
-
if (await fs9.pathExists(path8.join(pkgRoot, "metadata.json"))) {
|
|
824
|
-
await fs9.copy(
|
|
825
|
-
path8.join(pkgRoot, "metadata.json"),
|
|
826
|
-
path8.join(skillVersionDir, "metadata.json")
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
const skillDir = path8.join(skillsDir, skillId);
|
|
830
|
-
await fs9.ensureDir(skillDir);
|
|
831
|
-
const latestLink = path8.join(skillDir, LATEST_LINK);
|
|
832
|
-
try {
|
|
833
|
-
await fs9.remove(latestLink);
|
|
834
|
-
await fs9.symlink(skillVersionDir, latestLink, "junction");
|
|
835
|
-
} catch {
|
|
836
|
-
await fs9.copy(skillVersionDir, path8.join(skillDir, LATEST_LINK), { overwrite: true });
|
|
837
|
-
}
|
|
838
|
-
let targetAdapters = [];
|
|
839
|
-
if (options?.platforms && options.platforms.length > 0) {
|
|
840
|
-
for (const platformStr of options.platforms) {
|
|
841
|
-
const platform = platformStr;
|
|
842
|
-
const adapter = getAdapterByPlatform(platform);
|
|
843
|
-
if (adapter) {
|
|
844
|
-
targetAdapters.push(adapter);
|
|
845
|
-
} else {
|
|
846
|
-
console.warn(`\u26A0\uFE0F Unknown platform: ${platformStr}`);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
} else {
|
|
850
|
-
targetAdapters = await detectPlatforms();
|
|
851
|
-
}
|
|
852
|
-
if (targetAdapters.length === 0) {
|
|
853
|
-
console.log("No target platforms detected.");
|
|
854
|
-
console.log("Use --platform to specify platforms manually.");
|
|
855
|
-
} else {
|
|
856
|
-
console.log(`
|
|
857
|
-
Installing to ${targetAdapters.length} platform(s)...
|
|
858
|
-
`);
|
|
859
|
-
const results = [];
|
|
860
|
-
for (const adapter of targetAdapters) {
|
|
861
|
-
try {
|
|
862
|
-
const isInstalled = await adapter.isInstalled(skillId);
|
|
863
|
-
if (isInstalled && !options?.force) {
|
|
864
|
-
console.log(`${adapter.name.padEnd(12)} \u26A0\uFE0F Already installed (use --force to overwrite)`);
|
|
865
|
-
results.push({ name: adapter.name, status: "skipped" });
|
|
866
|
-
continue;
|
|
867
|
-
}
|
|
868
|
-
await adapter.install(skillId, skillVersionDir);
|
|
869
|
-
console.log(`${adapter.name.padEnd(12)} \u2705 Installed successfully`);
|
|
870
|
-
results.push({ name: adapter.name, status: "installed" });
|
|
871
|
-
} catch (error) {
|
|
872
|
-
console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${error}`);
|
|
873
|
-
results.push({ name: adapter.name, status: "failed", error: String(error) });
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
const installed = results.filter((r) => r.status === "installed").length;
|
|
877
|
-
const skipped = results.filter((r) => r.status === "skipped").length;
|
|
878
|
-
const failed = results.filter((r) => r.status === "failed").length;
|
|
879
|
-
console.log(`
|
|
880
|
-
\u{1F4CA} Summary: ${installed} installed, ${skipped} skipped, ${failed} failed`);
|
|
881
|
-
}
|
|
882
|
-
const registry = await loadRegistry();
|
|
883
|
-
const installedPlatforms = targetAdapters.map((a) => a.id);
|
|
884
|
-
registry.skills[skillId] = {
|
|
885
|
-
id: skillId,
|
|
886
|
-
version: targetVersion,
|
|
887
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
888
|
-
platforms: installedPlatforms
|
|
889
|
-
};
|
|
890
|
-
await saveRegistry(registry);
|
|
891
|
-
console.log(`
|
|
892
|
-
\u2705 ${skillId}@${targetVersion} installed successfully!`);
|
|
893
|
-
console.log(` Use "skm info ${skillId}" for more details`);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
238
|
// src/commands/sync.ts
|
|
897
|
-
import
|
|
898
|
-
import
|
|
239
|
+
import fs from "fs-extra";
|
|
240
|
+
import path from "path";
|
|
899
241
|
async function syncPlatformLinks() {
|
|
900
242
|
await ensureMarketDirs();
|
|
901
243
|
const skillsDir = getSkillsDir();
|
|
@@ -903,20 +245,20 @@ async function syncPlatformLinks() {
|
|
|
903
245
|
const registry = await loadRegistry();
|
|
904
246
|
console.log("Syncing platform links...\n");
|
|
905
247
|
for (const platform of PLATFORMS) {
|
|
906
|
-
const platformDir =
|
|
907
|
-
await
|
|
248
|
+
const platformDir = path.join(platformLinksDir, platform, "skills");
|
|
249
|
+
await fs.ensureDir(platformDir);
|
|
908
250
|
for (const [skillId, skillInfo] of Object.entries(registry.skills)) {
|
|
909
|
-
const skillLatestLink =
|
|
910
|
-
const targetPlatformDir =
|
|
911
|
-
const platformSkillDir =
|
|
912
|
-
if (await
|
|
913
|
-
if (await
|
|
251
|
+
const skillLatestLink = path.join(skillsDir, skillId, LATEST_LINK);
|
|
252
|
+
const targetPlatformDir = path.join(skillLatestLink, platform);
|
|
253
|
+
const platformSkillDir = path.join(platformDir, skillId);
|
|
254
|
+
if (await fs.pathExists(skillLatestLink)) {
|
|
255
|
+
if (await fs.pathExists(targetPlatformDir)) {
|
|
914
256
|
try {
|
|
915
|
-
await
|
|
916
|
-
await
|
|
257
|
+
await fs.remove(platformSkillDir);
|
|
258
|
+
await fs.symlink(targetPlatformDir, platformSkillDir, "junction");
|
|
917
259
|
console.log(` Linked: ${platform}/${skillId}`);
|
|
918
260
|
} catch {
|
|
919
|
-
await
|
|
261
|
+
await fs.copy(targetPlatformDir, platformSkillDir, { overwrite: true });
|
|
920
262
|
console.log(` Copied: ${platform}/${skillId}`);
|
|
921
263
|
}
|
|
922
264
|
}
|
|
@@ -942,319 +284,45 @@ async function syncSkill(skillId) {
|
|
|
942
284
|
\u2705 ${skillId} synced to v${latestVersion}`);
|
|
943
285
|
}
|
|
944
286
|
|
|
945
|
-
// src/commands/
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
287
|
+
// src/commands/github-install.ts
|
|
288
|
+
import fs2 from "fs-extra";
|
|
289
|
+
import path2 from "path";
|
|
290
|
+
var GITHUB_URL_PATTERNS = [
|
|
291
|
+
/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\/tree\/([^/]+)(?:\/(.+))?)?$/,
|
|
292
|
+
/^([^/]+)\/([^/@]+)@(.+)$/,
|
|
293
|
+
// owner/repo@commit (must come before # pattern)
|
|
294
|
+
/^([^/]+)\/([^/#]+)(?:#(.+))?$/
|
|
295
|
+
// owner/repo#branch
|
|
296
|
+
];
|
|
297
|
+
function parseGitHubUrl(input) {
|
|
298
|
+
input = input.replace(/\/$/, "");
|
|
299
|
+
for (const pattern of GITHUB_URL_PATTERNS) {
|
|
300
|
+
const match = input.match(pattern);
|
|
301
|
+
if (match) {
|
|
302
|
+
const owner = match[1];
|
|
303
|
+
const repo = match[2].replace(/\.git$/, "");
|
|
304
|
+
const branch = match[3] || "main";
|
|
305
|
+
const commitOrPath = match[4] || match[3];
|
|
306
|
+
const path5 = match[4] || void 0;
|
|
307
|
+
return {
|
|
308
|
+
owner,
|
|
309
|
+
repo,
|
|
310
|
+
branch: commitOrPath && !commitOrPath.includes("/") ? commitOrPath : branch,
|
|
311
|
+
commit: commitOrPath?.match(/^[0-9a-f]{40}$/) ? commitOrPath : void 0,
|
|
312
|
+
path: path5
|
|
313
|
+
};
|
|
959
314
|
}
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
const installed = await getInstalledSkills();
|
|
963
|
-
if (installed.length === 0) {
|
|
964
|
-
console.log("No skills installed to update.");
|
|
965
|
-
return;
|
|
966
315
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
})
|
|
978
|
-
);
|
|
979
|
-
const toUpdate = [];
|
|
980
|
-
let upToDate = 0;
|
|
981
|
-
let fetchFailed = 0;
|
|
982
|
-
for (const result of checkResults) {
|
|
983
|
-
if (result.status === "rejected") {
|
|
984
|
-
fetchFailed++;
|
|
985
|
-
continue;
|
|
986
|
-
}
|
|
987
|
-
const { skill, latestVersion, error } = result.value;
|
|
988
|
-
if (error) {
|
|
989
|
-
console.log(` ${skill.id}: ${skill.version} (${error})`);
|
|
990
|
-
fetchFailed++;
|
|
991
|
-
} else if (latestVersion && latestVersion !== skill.version) {
|
|
992
|
-
console.log(` ${skill.id}: ${skill.version} \u2192 ${latestVersion} [UPDATE]`);
|
|
993
|
-
toUpdate.push({ skill, latestVersion });
|
|
994
|
-
} else {
|
|
995
|
-
console.log(` ${skill.id}: ${skill.version} (up to date)`);
|
|
996
|
-
upToDate++;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
if (toUpdate.length > 0) {
|
|
1000
|
-
console.log(`
|
|
1001
|
-
Updating ${toUpdate.length} skill(s)...
|
|
1002
|
-
`);
|
|
1003
|
-
const updateResults = await Promise.allSettled(
|
|
1004
|
-
toUpdate.map(async ({ skill, latestVersion }) => {
|
|
1005
|
-
try {
|
|
1006
|
-
await installSkill(skill.id, latestVersion);
|
|
1007
|
-
return { id: skill.id, success: true };
|
|
1008
|
-
} catch (err) {
|
|
1009
|
-
return { id: skill.id, success: false, error: err };
|
|
1010
|
-
}
|
|
1011
|
-
})
|
|
1012
|
-
);
|
|
1013
|
-
for (const result of updateResults) {
|
|
1014
|
-
if (result.status === "fulfilled" && result.value.success) {
|
|
1015
|
-
console.log(` \u2705 ${result.value.id} updated`);
|
|
1016
|
-
} else if (result.status === "fulfilled" && !result.value.success) {
|
|
1017
|
-
console.error(` \u274C Failed to update ${result.value.id}:`, result.value.error);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
if (toUpdate.length === 0) {
|
|
1022
|
-
console.log("\nAll skills are up to date!");
|
|
1023
|
-
} else {
|
|
1024
|
-
console.log(`
|
|
1025
|
-
\u{1F4CA} Update summary: ${toUpdate.length} updated, ${upToDate} up-to-date, ${fetchFailed} failed`);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// src/commands/uninstall.ts
|
|
1030
|
-
import fs11 from "fs-extra";
|
|
1031
|
-
import path10 from "path";
|
|
1032
|
-
import readline from "readline";
|
|
1033
|
-
async function askConfirmation(message) {
|
|
1034
|
-
const rl = readline.createInterface({
|
|
1035
|
-
input: process.stdin,
|
|
1036
|
-
output: process.stdout
|
|
1037
|
-
});
|
|
1038
|
-
return new Promise((resolve2) => {
|
|
1039
|
-
rl.question(`${message} (y/N): `, (answer) => {
|
|
1040
|
-
rl.close();
|
|
1041
|
-
resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
1042
|
-
});
|
|
1043
|
-
});
|
|
1044
|
-
}
|
|
1045
|
-
async function getUninstallPreview(skillId, options) {
|
|
1046
|
-
const registry = await loadRegistry();
|
|
1047
|
-
const skillInfo = registry.skills[skillId];
|
|
1048
|
-
let platformNames = [];
|
|
1049
|
-
if (options?.platforms && options.platforms.length > 0) {
|
|
1050
|
-
platformNames = options.platforms;
|
|
1051
|
-
} else {
|
|
1052
|
-
const adapters2 = await detectPlatforms();
|
|
1053
|
-
platformNames = adapters2.map((a) => a.name);
|
|
1054
|
-
}
|
|
1055
|
-
const skillsDir = getSkillsDir();
|
|
1056
|
-
const localPath = path10.join(skillsDir, skillId);
|
|
1057
|
-
const platformLinksDir = getPlatformLinksDir();
|
|
1058
|
-
const platformLinks = [];
|
|
1059
|
-
for (const platform of PLATFORMS) {
|
|
1060
|
-
const linkPath = path10.join(platformLinksDir, platform, "skills", skillId);
|
|
1061
|
-
if (await fs11.pathExists(linkPath)) {
|
|
1062
|
-
platformLinks.push(linkPath);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
return { skillInfo, platforms: platformNames, localPath, platformLinks };
|
|
1066
|
-
}
|
|
1067
|
-
async function uninstallSkill(skillId, options) {
|
|
1068
|
-
const registry = await loadRegistry();
|
|
1069
|
-
if (!(skillId in registry.skills)) {
|
|
1070
|
-
console.log(`\u274C Skill "${skillId}" is not installed.`);
|
|
1071
|
-
return false;
|
|
1072
|
-
}
|
|
1073
|
-
const skillInfo = registry.skills[skillId];
|
|
1074
|
-
if (options?.dryRun) {
|
|
1075
|
-
const preview = await getUninstallPreview(skillId, options);
|
|
1076
|
-
console.log(`
|
|
1077
|
-
\u{1F4CB} Uninstall Preview for "${skillId}":`);
|
|
1078
|
-
console.log(` Version: ${skillInfo.version}`);
|
|
1079
|
-
console.log(` Installed: ${skillInfo.installedAt}`);
|
|
1080
|
-
console.log(` Platforms (from registry): ${preview.platforms.join(", ") || "none"}`);
|
|
1081
|
-
console.log(`
|
|
1082
|
-
Local files to remove:`);
|
|
1083
|
-
console.log(` - ${preview.localPath}`);
|
|
1084
|
-
if (preview.platformLinks.length > 0) {
|
|
1085
|
-
console.log(`
|
|
1086
|
-
Platform links to remove:`);
|
|
1087
|
-
for (const link of preview.platformLinks) {
|
|
1088
|
-
console.log(` - ${link}`);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
console.log(`
|
|
1092
|
-
\u26A0\uFE0F This was a dry-run. No files were actually deleted.`);
|
|
1093
|
-
return true;
|
|
1094
|
-
}
|
|
1095
|
-
if (!options?.yes) {
|
|
1096
|
-
const confirmed = await askConfirmation(`Are you sure you want to uninstall "${skillId}"?`);
|
|
1097
|
-
if (!confirmed) {
|
|
1098
|
-
console.log("Uninstall cancelled.");
|
|
1099
|
-
return false;
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
console.log(`
|
|
1103
|
-
Uninstalling ${skillId}@${skillInfo.version}...`);
|
|
1104
|
-
let targetAdapters = [];
|
|
1105
|
-
let platformUninstallErrors = [];
|
|
1106
|
-
if (options?.platforms && options.platforms.length > 0) {
|
|
1107
|
-
for (const platformStr of options.platforms) {
|
|
1108
|
-
const platform = platformStr;
|
|
1109
|
-
targetAdapters.push(getAdapterByPlatform(platform));
|
|
1110
|
-
}
|
|
1111
|
-
} else {
|
|
1112
|
-
targetAdapters = await detectPlatforms();
|
|
1113
|
-
}
|
|
1114
|
-
const validAdapters = targetAdapters.filter((a) => a !== void 0);
|
|
1115
|
-
if (validAdapters.length > 0) {
|
|
1116
|
-
console.log(`
|
|
1117
|
-
Uninstalling from ${validAdapters.length} platform(s)...
|
|
1118
|
-
`);
|
|
1119
|
-
for (const adapter of validAdapters) {
|
|
1120
|
-
try {
|
|
1121
|
-
await adapter.uninstall(skillId);
|
|
1122
|
-
console.log(`${adapter.name.padEnd(12)} \u2705 Uninstalled`);
|
|
1123
|
-
} catch (error) {
|
|
1124
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1125
|
-
console.log(`${adapter.name.padEnd(12)} \u274C Failed: ${errorMsg}`);
|
|
1126
|
-
platformUninstallErrors.push({ name: adapter.name, error: errorMsg });
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
if (platformUninstallErrors.length > 0 && !options?.yes) {
|
|
1131
|
-
const continueAnyway = await askConfirmation(
|
|
1132
|
-
`\u26A0\uFE0F ${platformUninstallErrors.length} platform(s) failed to uninstall. Continue with local cleanup?`
|
|
1133
|
-
);
|
|
1134
|
-
if (!continueAnyway) {
|
|
1135
|
-
console.log("Uninstall aborted. Platform files may still exist.");
|
|
1136
|
-
return false;
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
const skillsDir = getSkillsDir();
|
|
1140
|
-
const skillDir = path10.join(skillsDir, skillId);
|
|
1141
|
-
if (await fs11.pathExists(skillDir)) {
|
|
1142
|
-
await fs11.remove(skillDir);
|
|
1143
|
-
console.log(`\u2705 Removed local files: ${skillDir}`);
|
|
1144
|
-
}
|
|
1145
|
-
const platformLinksDir = getPlatformLinksDir();
|
|
1146
|
-
let removedLinks = 0;
|
|
1147
|
-
for (const platform of PLATFORMS) {
|
|
1148
|
-
const linkPath = path10.join(platformLinksDir, platform, "skills", skillId);
|
|
1149
|
-
if (await fs11.pathExists(linkPath)) {
|
|
1150
|
-
await fs11.remove(linkPath);
|
|
1151
|
-
removedLinks++;
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
if (removedLinks > 0) {
|
|
1155
|
-
console.log(`\u2705 Removed ${removedLinks} platform link(s)`);
|
|
1156
|
-
}
|
|
1157
|
-
delete registry.skills[skillId];
|
|
1158
|
-
await saveRegistry(registry);
|
|
1159
|
-
console.log(`\u2705 Registry updated`);
|
|
1160
|
-
console.log(`
|
|
1161
|
-
\u2705 ${skillId} uninstalled successfully!`);
|
|
1162
|
-
return true;
|
|
1163
|
-
}
|
|
1164
|
-
async function uninstallAll(options) {
|
|
1165
|
-
const registry = await loadRegistry();
|
|
1166
|
-
const installedSkills = Object.keys(registry.skills);
|
|
1167
|
-
if (installedSkills.length === 0) {
|
|
1168
|
-
console.log("No skills installed.");
|
|
1169
|
-
return { success: 0, failed: 0 };
|
|
1170
|
-
}
|
|
1171
|
-
console.log(`
|
|
1172
|
-
Found ${installedSkills.length} installed skill(s):`);
|
|
1173
|
-
for (const skillId of installedSkills) {
|
|
1174
|
-
const info = registry.skills[skillId];
|
|
1175
|
-
console.log(` - ${skillId}@${info.version}`);
|
|
1176
|
-
}
|
|
1177
|
-
if (options?.dryRun) {
|
|
1178
|
-
console.log(`
|
|
1179
|
-
\u{1F4CB} Dry-run: Would uninstall ${installedSkills.length} skill(s).`);
|
|
1180
|
-
console.log(`\u26A0\uFE0F No files were actually deleted.`);
|
|
1181
|
-
return { success: installedSkills.length, failed: 0 };
|
|
1182
|
-
}
|
|
1183
|
-
if (!options?.yes) {
|
|
1184
|
-
const confirmed = await askConfirmation(
|
|
1185
|
-
`
|
|
1186
|
-
\u26A0\uFE0F Are you sure you want to uninstall ALL ${installedSkills.length} skill(s)? This action cannot be undone.`
|
|
1187
|
-
);
|
|
1188
|
-
if (!confirmed) {
|
|
1189
|
-
console.log("Uninstall cancelled.");
|
|
1190
|
-
return { success: 0, failed: 0 };
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
console.log(`
|
|
1194
|
-
Uninstalling all skills...
|
|
1195
|
-
`);
|
|
1196
|
-
let successCount = 0;
|
|
1197
|
-
let failedCount = 0;
|
|
1198
|
-
for (const skillId of installedSkills) {
|
|
1199
|
-
try {
|
|
1200
|
-
const success = await uninstallSkill(skillId, { ...options, yes: true });
|
|
1201
|
-
if (success) {
|
|
1202
|
-
successCount++;
|
|
1203
|
-
} else {
|
|
1204
|
-
failedCount++;
|
|
1205
|
-
}
|
|
1206
|
-
} catch (error) {
|
|
1207
|
-
console.log(`\u274C Failed to uninstall ${skillId}: ${error}`);
|
|
1208
|
-
failedCount++;
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
console.log(`
|
|
1212
|
-
\u{1F4CA} Summary:`);
|
|
1213
|
-
console.log(` \u2705 Success: ${successCount}`);
|
|
1214
|
-
console.log(` \u274C Failed: ${failedCount}`);
|
|
1215
|
-
console.log(` \u{1F4E6} Total: ${installedSkills.length}`);
|
|
1216
|
-
return { success: successCount, failed: failedCount };
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// src/commands/github-install.ts
|
|
1220
|
-
import fs12 from "fs-extra";
|
|
1221
|
-
import path11 from "path";
|
|
1222
|
-
var GITHUB_URL_PATTERNS = [
|
|
1223
|
-
/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\/tree\/([^/]+)(?:\/(.+))?)?$/,
|
|
1224
|
-
/^([^/]+)\/([^/@]+)@(.+)$/,
|
|
1225
|
-
// owner/repo@commit (must come before # pattern)
|
|
1226
|
-
/^([^/]+)\/([^/#]+)(?:#(.+))?$/
|
|
1227
|
-
// owner/repo#branch
|
|
1228
|
-
];
|
|
1229
|
-
function parseGitHubUrl(input) {
|
|
1230
|
-
input = input.replace(/\/$/, "");
|
|
1231
|
-
for (const pattern of GITHUB_URL_PATTERNS) {
|
|
1232
|
-
const match = input.match(pattern);
|
|
1233
|
-
if (match) {
|
|
1234
|
-
const owner = match[1];
|
|
1235
|
-
const repo = match[2].replace(/\.git$/, "");
|
|
1236
|
-
const branch = match[3] || "main";
|
|
1237
|
-
const commitOrPath = match[4] || match[3];
|
|
1238
|
-
const path14 = match[4] || void 0;
|
|
1239
|
-
return {
|
|
1240
|
-
owner,
|
|
1241
|
-
repo,
|
|
1242
|
-
branch: commitOrPath && !commitOrPath.includes("/") ? commitOrPath : branch,
|
|
1243
|
-
commit: commitOrPath?.match(/^[0-9a-f]{40}$/) ? commitOrPath : void 0,
|
|
1244
|
-
path: path14
|
|
1245
|
-
};
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
return null;
|
|
1249
|
-
}
|
|
1250
|
-
async function fetchGitHubFile(source, filePath) {
|
|
1251
|
-
const ref = source.commit || source.branch || "main";
|
|
1252
|
-
const url = `https://api.github.com/repos/${source.owner}/${source.repo}/contents/${filePath}?ref=${ref}`;
|
|
1253
|
-
try {
|
|
1254
|
-
const response = await fetch(url, {
|
|
1255
|
-
headers: {
|
|
1256
|
-
"Accept": "application/vnd.github.v3.raw",
|
|
1257
|
-
"User-Agent": "SkillMarket"
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
async function fetchGitHubFile(source, filePath) {
|
|
319
|
+
const ref = source.commit || source.branch || "main";
|
|
320
|
+
const url = `https://api.github.com/repos/${source.owner}/${source.repo}/contents/${filePath}?ref=${ref}`;
|
|
321
|
+
try {
|
|
322
|
+
const response = await fetch(url, {
|
|
323
|
+
headers: {
|
|
324
|
+
"Accept": "application/vnd.github.v3.raw",
|
|
325
|
+
"User-Agent": "SkillMarket"
|
|
1258
326
|
}
|
|
1259
327
|
});
|
|
1260
328
|
if (!response.ok) {
|
|
@@ -1340,17 +408,17 @@ async function detectSkillFromGitHub(source) {
|
|
|
1340
408
|
}
|
|
1341
409
|
async function generatePlatformAdapters(skillId, existingPlatforms, targetPlatforms, sourceDir) {
|
|
1342
410
|
const skillsDir = getSkillsDir();
|
|
1343
|
-
const skillVersionDir =
|
|
411
|
+
const skillVersionDir = path2.join(skillsDir, `${skillId}@github`);
|
|
1344
412
|
for (const platform of targetPlatforms) {
|
|
1345
413
|
if (existingPlatforms.includes(platform)) {
|
|
1346
414
|
continue;
|
|
1347
415
|
}
|
|
1348
|
-
const platformDir =
|
|
1349
|
-
await
|
|
1350
|
-
const sourceSkillMd =
|
|
1351
|
-
const targetSkillMd =
|
|
1352
|
-
if (await
|
|
1353
|
-
await
|
|
416
|
+
const platformDir = path2.join(skillVersionDir, platform);
|
|
417
|
+
await fs2.ensureDir(platformDir);
|
|
418
|
+
const sourceSkillMd = path2.join(sourceDir, "SKILL.md");
|
|
419
|
+
const targetSkillMd = path2.join(platformDir, "SKILL.md");
|
|
420
|
+
if (await fs2.pathExists(sourceSkillMd)) {
|
|
421
|
+
await fs2.copy(sourceSkillMd, targetSkillMd);
|
|
1354
422
|
}
|
|
1355
423
|
if (platform === "opencode" || platform === "cursor" || platform === "codex" || platform === "antigravity") {
|
|
1356
424
|
} else if (platform === "vscode") {
|
|
@@ -1359,14 +427,14 @@ async function generatePlatformAdapters(skillId, existingPlatforms, targetPlatfo
|
|
|
1359
427
|
description: `Skill: ${skillId}`,
|
|
1360
428
|
version: "1.0.0"
|
|
1361
429
|
};
|
|
1362
|
-
await
|
|
430
|
+
await fs2.writeJson(path2.join(platformDir, "skill.json"), skillJson, { spaces: 2 });
|
|
1363
431
|
} else if (platform === "claude") {
|
|
1364
432
|
const skillJson = {
|
|
1365
433
|
name: skillId,
|
|
1366
434
|
description: `Skill: ${skillId}`,
|
|
1367
435
|
version: "1.0.0"
|
|
1368
436
|
};
|
|
1369
|
-
await
|
|
437
|
+
await fs2.writeJson(path2.join(platformDir, "skill.json"), skillJson, { spaces: 2 });
|
|
1370
438
|
}
|
|
1371
439
|
}
|
|
1372
440
|
}
|
|
@@ -1395,26 +463,26 @@ async function installFromGitHub(input, options) {
|
|
|
1395
463
|
Setting up skill: ${skillId}@${version}`);
|
|
1396
464
|
await ensureMarketDirs();
|
|
1397
465
|
const skillsDir = getSkillsDir();
|
|
1398
|
-
const skillVersionDir =
|
|
1399
|
-
await
|
|
466
|
+
const skillVersionDir = path2.join(skillsDir, `${skillId}@${version}`);
|
|
467
|
+
await fs2.ensureDir(skillVersionDir);
|
|
1400
468
|
console.log("Downloading files...");
|
|
1401
469
|
const basePath = source.path || "";
|
|
1402
470
|
const skillMdPath = basePath ? `${basePath}/SKILL.md` : "SKILL.md";
|
|
1403
471
|
const skillMdContent = await fetchGitHubFile(source, skillMdPath);
|
|
1404
472
|
if (skillMdContent) {
|
|
1405
|
-
await
|
|
473
|
+
await fs2.writeFile(path2.join(skillVersionDir, "SKILL.md"), skillMdContent);
|
|
1406
474
|
console.log(" \u2705 SKILL.md");
|
|
1407
475
|
}
|
|
1408
476
|
const packageJsonPath = basePath ? `${basePath}/package.json` : "package.json";
|
|
1409
477
|
const packageJsonContent = await fetchGitHubFile(source, packageJsonPath);
|
|
1410
478
|
if (packageJsonContent) {
|
|
1411
|
-
await
|
|
479
|
+
await fs2.writeFile(path2.join(skillVersionDir, "package.json"), packageJsonContent);
|
|
1412
480
|
console.log(" \u2705 package.json");
|
|
1413
481
|
}
|
|
1414
482
|
const metadataPath = basePath ? `${basePath}/metadata.json` : "metadata.json";
|
|
1415
483
|
const metadataContent = await fetchGitHubFile(source, metadataPath);
|
|
1416
484
|
if (metadataContent) {
|
|
1417
|
-
await
|
|
485
|
+
await fs2.writeFile(path2.join(skillVersionDir, "metadata.json"), metadataContent);
|
|
1418
486
|
console.log(" \u2705 metadata.json");
|
|
1419
487
|
}
|
|
1420
488
|
const targetPlatforms = options?.platforms || detected.platforms;
|
|
@@ -1426,14 +494,14 @@ Generating adapters for missing platforms: ${missingPlatforms.join(", ")}`);
|
|
|
1426
494
|
await generatePlatformAdapters(skillId, existingPlatforms, targetPlatforms, skillVersionDir);
|
|
1427
495
|
console.log(" \u2705 Platform adapters generated");
|
|
1428
496
|
}
|
|
1429
|
-
const skillDir =
|
|
1430
|
-
await
|
|
1431
|
-
const latestLink =
|
|
497
|
+
const skillDir = path2.join(skillsDir, skillId);
|
|
498
|
+
await fs2.ensureDir(skillDir);
|
|
499
|
+
const latestLink = path2.join(skillDir, "latest");
|
|
1432
500
|
try {
|
|
1433
|
-
await
|
|
1434
|
-
await
|
|
501
|
+
await fs2.remove(latestLink);
|
|
502
|
+
await fs2.symlink(skillVersionDir, latestLink, "junction");
|
|
1435
503
|
} catch {
|
|
1436
|
-
await
|
|
504
|
+
await fs2.copy(skillVersionDir, path2.join(skillDir, "latest"), { overwrite: true });
|
|
1437
505
|
}
|
|
1438
506
|
console.log(`
|
|
1439
507
|
Installing to ${targetPlatforms.length} platform(s)...
|
|
@@ -1479,112 +547,30 @@ Installing to ${targetPlatforms.length} platform(s)...
|
|
|
1479
547
|
console.log(` Source: ${source.owner}/${source.repo}`);
|
|
1480
548
|
}
|
|
1481
549
|
|
|
1482
|
-
// src/commands/publish.ts
|
|
1483
|
-
import { exec as exec2 } from "child_process";
|
|
1484
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1485
|
-
import { join as join2 } from "path";
|
|
1486
|
-
import { fileURLToPath } from "url";
|
|
1487
|
-
import { promisify as promisify2 } from "util";
|
|
1488
|
-
var execAsync2 = promisify2(exec2);
|
|
1489
|
-
async function publishSkill(skillName, options) {
|
|
1490
|
-
const __dirname4 = fileURLToPath(new URL(".", import.meta.url));
|
|
1491
|
-
const projectRoot = join2(__dirname4, "..");
|
|
1492
|
-
const skillDir = join2(projectRoot, "skills", skillName);
|
|
1493
|
-
console.log(`Publishing ${skillName}...`);
|
|
1494
|
-
if (!existsSync2(skillDir)) {
|
|
1495
|
-
throw new Error(`Skill '${skillName}' not found in skills/ directory`);
|
|
1496
|
-
}
|
|
1497
|
-
if (!options?.skipInstall) {
|
|
1498
|
-
console.log("Running npm install...");
|
|
1499
|
-
try {
|
|
1500
|
-
await execAsync2("npm install", { cwd: skillDir });
|
|
1501
|
-
} catch (err) {
|
|
1502
|
-
console.warn("Warning: npm install failed, continuing anyway...");
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
if (options?.version) {
|
|
1506
|
-
console.log(`Updating version to ${options.version}...`);
|
|
1507
|
-
try {
|
|
1508
|
-
await execAsync2(`npm version ${options.version} --no-git-tag-version`, {
|
|
1509
|
-
cwd: skillDir
|
|
1510
|
-
});
|
|
1511
|
-
} catch (err) {
|
|
1512
|
-
throw new Error(`Failed to update version: ${err}`);
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
const pkgJsonPath = join2(skillDir, "package.json");
|
|
1516
|
-
let baseName = skillName;
|
|
1517
|
-
if (existsSync2(pkgJsonPath)) {
|
|
1518
|
-
try {
|
|
1519
|
-
const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf-8"));
|
|
1520
|
-
if (pkg.name) {
|
|
1521
|
-
baseName = pkg.name.includes("/") ? pkg.name.split("/")[1] : pkg.name;
|
|
1522
|
-
}
|
|
1523
|
-
} catch {
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
const scopesToTry = [
|
|
1527
|
-
NPM_SCOPE,
|
|
1528
|
-
...SKILL_SCOPES.filter((s) => s !== NPM_SCOPE)
|
|
1529
|
-
];
|
|
1530
|
-
let lastError = null;
|
|
1531
|
-
let publishedName = "";
|
|
1532
|
-
for (const scope of scopesToTry) {
|
|
1533
|
-
const targetName = `${scope}/${baseName}`;
|
|
1534
|
-
if (existsSync2(pkgJsonPath)) {
|
|
1535
|
-
try {
|
|
1536
|
-
const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf-8"));
|
|
1537
|
-
pkg.name = targetName;
|
|
1538
|
-
writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1539
|
-
} catch {
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
console.log(`Publishing as ${targetName}...`);
|
|
1543
|
-
try {
|
|
1544
|
-
await execAsync2("npm publish --access=public", { cwd: skillDir });
|
|
1545
|
-
publishedName = targetName;
|
|
1546
|
-
break;
|
|
1547
|
-
} catch (err) {
|
|
1548
|
-
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1549
|
-
const errMsg = String(err);
|
|
1550
|
-
if (!errMsg.includes("404")) {
|
|
1551
|
-
break;
|
|
1552
|
-
}
|
|
1553
|
-
console.log(` Scope ${scope} not available, trying next...`);
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
if (!publishedName) {
|
|
1557
|
-
throw lastError || new Error("Failed to publish to npm");
|
|
1558
|
-
}
|
|
1559
|
-
console.log(`
|
|
1560
|
-
\u2705 ${skillName} published successfully as ${publishedName}!`);
|
|
1561
|
-
console.log(` View at: https://www.npmjs.com/package/${publishedName}`);
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
550
|
// src/commands/verify.ts
|
|
1565
|
-
import
|
|
1566
|
-
import
|
|
1567
|
-
import { fileURLToPath
|
|
1568
|
-
var __filename =
|
|
1569
|
-
var __dirname =
|
|
551
|
+
import fs3 from "fs-extra";
|
|
552
|
+
import path3 from "path";
|
|
553
|
+
import { fileURLToPath } from "url";
|
|
554
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
555
|
+
var __dirname = path3.dirname(__filename);
|
|
1570
556
|
async function verifySkill(skillName) {
|
|
1571
557
|
try {
|
|
1572
558
|
console.log(`
|
|
1573
559
|
\u{1F50D} Verifying skill: ${skillName}
|
|
1574
560
|
`);
|
|
1575
|
-
const skillDir =
|
|
1576
|
-
if (!await
|
|
561
|
+
const skillDir = path3.join(process.env.HOME || process.env.USERPROFILE || "", ".skillmarket", "skills", skillName);
|
|
562
|
+
if (!await fs3.pathExists(skillDir)) {
|
|
1577
563
|
console.error(`\u274C Skill "${skillName}" not found locally.`);
|
|
1578
564
|
console.log(` Try: skm install ${skillName}`);
|
|
1579
565
|
process.exit(1);
|
|
1580
566
|
}
|
|
1581
567
|
let passed = 0;
|
|
1582
568
|
let failed = 0;
|
|
1583
|
-
const skillMdPath =
|
|
1584
|
-
if (await
|
|
569
|
+
const skillMdPath = path3.join(skillDir, "SKILL.md");
|
|
570
|
+
if (await fs3.pathExists(skillMdPath)) {
|
|
1585
571
|
console.log(`\u2705 SKILL.md exists`);
|
|
1586
572
|
passed++;
|
|
1587
|
-
const content = await
|
|
573
|
+
const content = await fs3.readFile(skillMdPath, "utf-8");
|
|
1588
574
|
if (content.trim().length > 0) {
|
|
1589
575
|
console.log(`\u2705 SKILL.md is not empty (${content.length} chars)`);
|
|
1590
576
|
passed++;
|
|
@@ -1596,12 +582,12 @@ async function verifySkill(skillName) {
|
|
|
1596
582
|
console.log(`\u274C SKILL.md not found`);
|
|
1597
583
|
failed++;
|
|
1598
584
|
}
|
|
1599
|
-
const pkgPath =
|
|
1600
|
-
if (await
|
|
585
|
+
const pkgPath = path3.join(skillDir, "package.json");
|
|
586
|
+
if (await fs3.pathExists(pkgPath)) {
|
|
1601
587
|
console.log(`\u2705 package.json exists`);
|
|
1602
588
|
passed++;
|
|
1603
589
|
try {
|
|
1604
|
-
const pkg = await
|
|
590
|
+
const pkg = await fs3.readJson(pkgPath);
|
|
1605
591
|
const requiredFields = ["name", "version", "description"];
|
|
1606
592
|
for (const field of requiredFields) {
|
|
1607
593
|
if (pkg[field]) {
|
|
@@ -1619,10 +605,10 @@ async function verifySkill(skillName) {
|
|
|
1619
605
|
} else {
|
|
1620
606
|
console.log(`\u26A0\uFE0F package.json not found (optional for basic skills)`);
|
|
1621
607
|
}
|
|
1622
|
-
const registryPath =
|
|
1623
|
-
if (await
|
|
608
|
+
const registryPath = path3.join(process.env.HOME || process.env.USERPROFILE || "", ".skillmarket", "registry.json");
|
|
609
|
+
if (await fs3.pathExists(registryPath)) {
|
|
1624
610
|
try {
|
|
1625
|
-
const registry = await
|
|
611
|
+
const registry = await fs3.readJson(registryPath);
|
|
1626
612
|
const registered = registry.skills?.[skillName];
|
|
1627
613
|
if (registered) {
|
|
1628
614
|
console.log(`\u2705 Skill registered in registry (v${registered.version})`);
|
|
@@ -1655,1187 +641,10 @@ async function verifySkill(skillName) {
|
|
|
1655
641
|
}
|
|
1656
642
|
}
|
|
1657
643
|
|
|
1658
|
-
// src/commands/ui.ts
|
|
1659
|
-
import { createServer } from "http";
|
|
1660
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync, rmSync, readdirSync, renameSync } from "fs";
|
|
1661
|
-
import { join as join3, extname, dirname, basename } from "path";
|
|
1662
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1663
|
-
import AdmZip from "adm-zip";
|
|
1664
|
-
|
|
1665
|
-
// src/commands/admin.ts
|
|
1666
|
-
import { execSync as execSync2 } from "child_process";
|
|
1667
|
-
async function fetchScopePackages() {
|
|
1668
|
-
const all = /* @__PURE__ */ new Set();
|
|
1669
|
-
for (const scope of SKILL_SCOPES) {
|
|
1670
|
-
try {
|
|
1671
|
-
const { packages } = await searchSkillmarketPackages({ from: 0, size: 100, keyword: scope });
|
|
1672
|
-
for (const p of packages) {
|
|
1673
|
-
if (p.startsWith(scope)) all.add(p);
|
|
1674
|
-
}
|
|
1675
|
-
} catch {
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
return [...all].sort();
|
|
1679
|
-
}
|
|
1680
|
-
async function adminList() {
|
|
1681
|
-
console.log("\n\u{1F50D} Fetching all published skills...\n");
|
|
1682
|
-
const packages = await fetchScopePackages();
|
|
1683
|
-
if (packages.length === 0) {
|
|
1684
|
-
console.log("No published skills found.");
|
|
1685
|
-
return;
|
|
1686
|
-
}
|
|
1687
|
-
const details = await Promise.all(
|
|
1688
|
-
packages.map(async (pkg) => {
|
|
1689
|
-
try {
|
|
1690
|
-
const info = await fetchNpmPackage(pkg);
|
|
1691
|
-
if (!info) return null;
|
|
1692
|
-
const ver = info["dist-tags"]?.latest || "?";
|
|
1693
|
-
const p = info.versions?.[ver];
|
|
1694
|
-
return {
|
|
1695
|
-
name: info.name,
|
|
1696
|
-
version: ver,
|
|
1697
|
-
description: p?.description || "",
|
|
1698
|
-
hasSkillmarket: !!p?.skillmarket,
|
|
1699
|
-
platforms: (p?.skillmarket?.platforms || []).join(", "),
|
|
1700
|
-
updated: info.time?.[ver] || ""
|
|
1701
|
-
};
|
|
1702
|
-
} catch {
|
|
1703
|
-
return null;
|
|
1704
|
-
}
|
|
1705
|
-
})
|
|
1706
|
-
);
|
|
1707
|
-
const valid = details.filter(Boolean);
|
|
1708
|
-
console.log(`\u{1F4E6} ${valid.length} published skill(s):
|
|
1709
|
-
`);
|
|
1710
|
-
valid.sort((a, b) => a.name.localeCompare(b.name));
|
|
1711
|
-
for (const d of valid) {
|
|
1712
|
-
const flag = d.hasSkillmarket ? "\u2705" : "\u{1F4E6}";
|
|
1713
|
-
console.log(` ${flag} ${d.name}@${d.version}`);
|
|
1714
|
-
if (d.description) console.log(` ${d.description.slice(0, 80)}`);
|
|
1715
|
-
if (d.platforms) console.log(` Platforms: ${d.platforms}`);
|
|
1716
|
-
console.log();
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
async function adminInfo(skillId) {
|
|
1720
|
-
console.log(`
|
|
1721
|
-
\u{1F50D} Fetching detailed info for "${skillId}"...
|
|
1722
|
-
`);
|
|
1723
|
-
const info = await fetchSkillPackage(skillId);
|
|
1724
|
-
if (!info) {
|
|
1725
|
-
console.error(`\u274C Skill "${skillId}" not found in any configured scope.`);
|
|
1726
|
-
console.log(` Scopes checked: ${SKILL_SCOPES.join(", ")}`);
|
|
1727
|
-
process.exit(1);
|
|
1728
|
-
}
|
|
1729
|
-
const latestVer = info["dist-tags"]?.latest || "unknown";
|
|
1730
|
-
const latestPkg = info.versions?.[latestVer];
|
|
1731
|
-
const meta = latestPkg?.skillmarket;
|
|
1732
|
-
const allVersions = Object.keys(info.versions || {});
|
|
1733
|
-
console.log(`\u{1F4E6} ${info.name}`);
|
|
1734
|
-
console.log(` Description: ${info.description || "N/A"}`);
|
|
1735
|
-
console.log(` Latest: ${latestVer}`);
|
|
1736
|
-
console.log(` Total versions: ${allVersions.length}`);
|
|
1737
|
-
console.log(` Modified: ${info.time?.modified || "N/A"}`);
|
|
1738
|
-
console.log(` Author: ${info.author?.name || latestPkg?.author?.name || "N/A"}`);
|
|
1739
|
-
console.log(` License: ${info.license || latestPkg?.license || "N/A"}`);
|
|
1740
|
-
if (meta) {
|
|
1741
|
-
console.log(`
|
|
1742
|
-
\u{1F4CB} SkillMarket Metadata:`);
|
|
1743
|
-
if (meta.id) console.log(` ID: ${meta.id}`);
|
|
1744
|
-
if (meta.displayName) console.log(` Display Name: ${meta.displayName}`);
|
|
1745
|
-
if (meta.description) console.log(` Description: ${meta.description}`);
|
|
1746
|
-
if (meta.platforms && meta.platforms.length > 0) {
|
|
1747
|
-
console.log(` Platforms: ${meta.platforms.join(", ")}`);
|
|
1748
|
-
const unknown = meta.platforms.filter((p) => !PLATFORMS.includes(p));
|
|
1749
|
-
if (unknown.length > 0) {
|
|
1750
|
-
console.log(` \u26A0\uFE0F Unknown platforms: ${unknown.join(", ")}`);
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
} else {
|
|
1754
|
-
console.log(`
|
|
1755
|
-
\u26A0\uFE0F No SkillMarket metadata (not a skillmarket skill)`);
|
|
1756
|
-
}
|
|
1757
|
-
console.log(`
|
|
1758
|
-
\u{1F4C5} Version History:`);
|
|
1759
|
-
for (const ver of allVersions.slice().reverse()) {
|
|
1760
|
-
const v = info.versions?.[ver];
|
|
1761
|
-
const time = info.time?.[ver] ? new Date(info.time[ver]).toLocaleDateString() : "?";
|
|
1762
|
-
const size = v?.dist?.unpackedSize ? ` (${(v.dist.unpackedSize / 1024).toFixed(1)} KB)` : "";
|
|
1763
|
-
const tag = ver === latestVer ? " \u2190 latest" : "";
|
|
1764
|
-
console.log(` ${ver}${tag} \u2014 ${time}${size}`);
|
|
1765
|
-
}
|
|
1766
|
-
const tags = info["dist-tags"] || {};
|
|
1767
|
-
const otherTags = Object.entries(tags).filter(([k]) => k !== "latest");
|
|
1768
|
-
if (otherTags.length > 0) {
|
|
1769
|
-
console.log(`
|
|
1770
|
-
\u{1F3F7}\uFE0F dist-tags:`);
|
|
1771
|
-
for (const [tag, ver] of otherTags) {
|
|
1772
|
-
console.log(` ${tag}: ${ver}`);
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
console.log(`
|
|
1776
|
-
\u{1F517} Registry:`);
|
|
1777
|
-
console.log(` ${NPM_REGISTRY}/${info.name}`);
|
|
1778
|
-
console.log();
|
|
1779
|
-
}
|
|
1780
|
-
async function adminSearch(keyword, limit = 20) {
|
|
1781
|
-
console.log(`
|
|
1782
|
-
\u{1F50D} Searching published skills for "${keyword}"...
|
|
1783
|
-
`);
|
|
1784
|
-
const scopePackages = await fetchScopePackages();
|
|
1785
|
-
const matched = scopePackages.filter(
|
|
1786
|
-
(p) => p.toLowerCase().includes(keyword.toLowerCase())
|
|
1787
|
-
);
|
|
1788
|
-
if (matched.length === 0) {
|
|
1789
|
-
const { packages } = await searchSkillmarketPackages({ keyword });
|
|
1790
|
-
const filtered = packages.filter((p) => p.toLowerCase().includes(keyword.toLowerCase())).slice(0, limit);
|
|
1791
|
-
if (filtered.length === 0) {
|
|
1792
|
-
console.log(`No skills found matching "${keyword}".`);
|
|
1793
|
-
return;
|
|
1794
|
-
}
|
|
1795
|
-
console.log(`Found ${filtered.length} skill(s) (from npm keyword search):
|
|
1796
|
-
`);
|
|
1797
|
-
for (const pkg of filtered) {
|
|
1798
|
-
const info = await fetchNpmPackage(pkg);
|
|
1799
|
-
const ver = info?.["dist-tags"]?.latest || "?";
|
|
1800
|
-
const p = info?.versions?.[ver];
|
|
1801
|
-
console.log(` ${pkg}@${ver}`);
|
|
1802
|
-
if (p?.description) console.log(` ${p.description.slice(0, 80)}`);
|
|
1803
|
-
console.log();
|
|
1804
|
-
}
|
|
1805
|
-
return;
|
|
1806
|
-
}
|
|
1807
|
-
console.log(`Found ${matched.length} skill(s) in configured scopes:
|
|
1808
|
-
`);
|
|
1809
|
-
const show = matched.slice(0, limit);
|
|
1810
|
-
for (const pkg of show) {
|
|
1811
|
-
const info = await fetchNpmPackage(pkg);
|
|
1812
|
-
const ver = info?.["dist-tags"]?.latest || "?";
|
|
1813
|
-
const p = info?.versions?.[ver];
|
|
1814
|
-
console.log(` ${pkg}@${ver}`);
|
|
1815
|
-
if (p?.skillmarket) {
|
|
1816
|
-
console.log(` \u{1F4CB} ${p.skillmarket.displayName || ""} \u2014 Platforms: ${(p.skillmarket.platforms || []).join(", ") || "none"}`);
|
|
1817
|
-
}
|
|
1818
|
-
if (p?.description) console.log(` ${p.description.slice(0, 80)}`);
|
|
1819
|
-
console.log();
|
|
1820
|
-
}
|
|
1821
|
-
if (matched.length > limit) {
|
|
1822
|
-
console.log(` ... and ${matched.length - limit} more (use --limit to show more)`);
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
async function adminStats() {
|
|
1826
|
-
console.log("\n\u{1F4CA} SkillMarket Publishing Statistics\n");
|
|
1827
|
-
const packages = await fetchScopePackages();
|
|
1828
|
-
if (packages.length === 0) {
|
|
1829
|
-
console.log("No published skills found.");
|
|
1830
|
-
return;
|
|
1831
|
-
}
|
|
1832
|
-
const infos = (await Promise.all(
|
|
1833
|
-
packages.map(async (pkg) => {
|
|
1834
|
-
try {
|
|
1835
|
-
const info = await fetchNpmPackage(pkg);
|
|
1836
|
-
return info ? { name: pkg, info } : null;
|
|
1837
|
-
} catch {
|
|
1838
|
-
return null;
|
|
1839
|
-
}
|
|
1840
|
-
})
|
|
1841
|
-
)).filter(Boolean);
|
|
1842
|
-
const totalSkills = infos.length;
|
|
1843
|
-
let totalVersions = 0;
|
|
1844
|
-
let totalSize = 0;
|
|
1845
|
-
const platformSet = /* @__PURE__ */ new Set();
|
|
1846
|
-
let withMetadata = 0;
|
|
1847
|
-
let mostVersions = { name: "", count: 0 };
|
|
1848
|
-
let mostRecent = { name: "", date: "" };
|
|
1849
|
-
for (const { name, info } of infos) {
|
|
1850
|
-
const versions = Object.keys(info.versions || {});
|
|
1851
|
-
totalVersions += versions.length;
|
|
1852
|
-
if (versions.length > mostVersions.count) {
|
|
1853
|
-
mostVersions = { name, count: versions.length };
|
|
1854
|
-
}
|
|
1855
|
-
const latestVer = info["dist-tags"]?.latest;
|
|
1856
|
-
const latestPkg = latestVer ? info.versions?.[latestVer] : void 0;
|
|
1857
|
-
const meta = latestPkg?.skillmarket;
|
|
1858
|
-
if (meta) {
|
|
1859
|
-
withMetadata++;
|
|
1860
|
-
if (meta.platforms) {
|
|
1861
|
-
for (const p of meta.platforms) platformSet.add(p);
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
if (latestPkg?.dist?.unpackedSize) {
|
|
1865
|
-
totalSize += latestPkg.dist.unpackedSize;
|
|
1866
|
-
}
|
|
1867
|
-
const modTime = info.time?.modified || "";
|
|
1868
|
-
if (modTime && modTime > mostRecent.date) {
|
|
1869
|
-
mostRecent = { name, date: modTime };
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
console.log(`\u{1F4E6} Total published skills: ${totalSkills}`);
|
|
1873
|
-
console.log(`\u{1F4DD} Total versions: ${totalVersions}`);
|
|
1874
|
-
console.log(` Avg versions/skill: ${(totalVersions / totalSkills).toFixed(1)}`);
|
|
1875
|
-
console.log(`\u{1F4CB} Skills with skillmarket metadata: ${withMetadata}/${totalSkills}`);
|
|
1876
|
-
console.log(`\u{1F4BE} Total unpacked size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
|
|
1877
|
-
console.log(`\u{1F527} Platforms covered: ${platformSet.size} (${[...platformSet].join(", ")})`);
|
|
1878
|
-
console.log(`\u{1F3C6} Most versions: ${mostVersions.name} (${mostVersions.count})`);
|
|
1879
|
-
if (mostRecent.date) {
|
|
1880
|
-
console.log(`\u{1F550} Most recent update: ${mostRecent.name} (${new Date(mostRecent.date).toLocaleDateString()})`);
|
|
1881
|
-
}
|
|
1882
|
-
console.log(`\u{1F517} Registry: ${NPM_REGISTRY}`);
|
|
1883
|
-
console.log(`
|
|
1884
|
-
Configured scopes: ${SKILL_SCOPES.join(", ")}`);
|
|
1885
|
-
console.log();
|
|
1886
|
-
}
|
|
1887
|
-
async function adminVerify(skillId) {
|
|
1888
|
-
console.log(`
|
|
1889
|
-
\u{1F50D} Verifying published skill "${skillId}"...
|
|
1890
|
-
`);
|
|
1891
|
-
const info = await fetchSkillPackage(skillId);
|
|
1892
|
-
if (!info) {
|
|
1893
|
-
console.error(`\u274C Skill "${skillId}" not found.`);
|
|
1894
|
-
process.exit(1);
|
|
1895
|
-
}
|
|
1896
|
-
let passed = 0;
|
|
1897
|
-
let failed = 0;
|
|
1898
|
-
let warnings = 0;
|
|
1899
|
-
const nameValid = /^@[^/]+\/[^/]+$/.test(info.name);
|
|
1900
|
-
if (nameValid) {
|
|
1901
|
-
console.log(`\u2705 Package name format: ${info.name}`);
|
|
1902
|
-
passed++;
|
|
1903
|
-
} else {
|
|
1904
|
-
console.log(`\u26A0\uFE0F Package name format unusual: ${info.name}`);
|
|
1905
|
-
warnings++;
|
|
1906
|
-
}
|
|
1907
|
-
const tags = info["dist-tags"] || {};
|
|
1908
|
-
if (tags.latest) {
|
|
1909
|
-
console.log(`\u2705 dist-tags.latest: ${tags.latest}`);
|
|
1910
|
-
passed++;
|
|
1911
|
-
} else {
|
|
1912
|
-
console.log(`\u274C dist-tags.latest missing`);
|
|
1913
|
-
failed++;
|
|
1914
|
-
}
|
|
1915
|
-
const latestVer = tags.latest;
|
|
1916
|
-
const latestPkg = latestVer ? info.versions?.[latestVer] : void 0;
|
|
1917
|
-
if (latestPkg) {
|
|
1918
|
-
console.log(`\u2705 Latest version ${latestVer} exists in versions`);
|
|
1919
|
-
passed++;
|
|
1920
|
-
} else {
|
|
1921
|
-
console.log(`\u274C Latest version ${latestVer} not found in versions object`);
|
|
1922
|
-
failed++;
|
|
1923
|
-
}
|
|
1924
|
-
const meta = latestPkg?.skillmarket;
|
|
1925
|
-
if (meta) {
|
|
1926
|
-
console.log(`\u2705 Has skillmarket metadata`);
|
|
1927
|
-
const checks = [
|
|
1928
|
-
{ name: "id", ok: !!meta.id },
|
|
1929
|
-
{ name: "displayName", ok: !!meta.displayName },
|
|
1930
|
-
{ name: "platforms (array)", ok: Array.isArray(meta.platforms) && meta.platforms.length > 0 }
|
|
1931
|
-
];
|
|
1932
|
-
for (const c of checks) {
|
|
1933
|
-
if (c.ok) {
|
|
1934
|
-
console.log(` \u2705 skillmarket.${c.name}`);
|
|
1935
|
-
passed++;
|
|
1936
|
-
} else {
|
|
1937
|
-
console.log(` \u26A0\uFE0F skillmarket.${c.name} missing or empty`);
|
|
1938
|
-
warnings++;
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
if (meta.platforms) {
|
|
1942
|
-
const unknown = meta.platforms.filter(
|
|
1943
|
-
(p) => !PLATFORMS.includes(p)
|
|
1944
|
-
);
|
|
1945
|
-
if (unknown.length > 0) {
|
|
1946
|
-
console.log(` \u26A0\uFE0F Unknown platforms: ${unknown.join(", ")}`);
|
|
1947
|
-
warnings++;
|
|
1948
|
-
} else {
|
|
1949
|
-
console.log(` \u2705 All platforms recognized`);
|
|
1950
|
-
passed++;
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
} else {
|
|
1954
|
-
console.log(`\u26A0\uFE0F No skillmarket metadata (not a skillmarket-formatted skill)`);
|
|
1955
|
-
warnings++;
|
|
1956
|
-
}
|
|
1957
|
-
if (latestPkg?.description) {
|
|
1958
|
-
console.log(`\u2705 Has description (${latestPkg.description.length} chars)`);
|
|
1959
|
-
passed++;
|
|
1960
|
-
} else {
|
|
1961
|
-
console.log(`\u26A0\uFE0F No description`);
|
|
1962
|
-
warnings++;
|
|
1963
|
-
}
|
|
1964
|
-
if (info.license || latestPkg?.license) {
|
|
1965
|
-
console.log(`\u2705 License: ${info.license || latestPkg?.license}`);
|
|
1966
|
-
passed++;
|
|
1967
|
-
} else {
|
|
1968
|
-
console.log(`\u26A0\uFE0F No license specified`);
|
|
1969
|
-
warnings++;
|
|
1970
|
-
}
|
|
1971
|
-
if (latestPkg?.dist?.unpackedSize) {
|
|
1972
|
-
const sizeKB = (latestPkg.dist.unpackedSize / 1024).toFixed(1);
|
|
1973
|
-
console.log(`\u2705 Package size: ${sizeKB} KB (unpacked)`);
|
|
1974
|
-
passed++;
|
|
1975
|
-
}
|
|
1976
|
-
const versionCount = Object.keys(info.versions || {}).length;
|
|
1977
|
-
console.log(`\u2139\uFE0F Total versions: ${versionCount}`);
|
|
1978
|
-
const total = passed + failed;
|
|
1979
|
-
console.log(`
|
|
1980
|
-
\u{1F4CA} Verification Result:`);
|
|
1981
|
-
console.log(` \u2705 Passed: ${passed}`);
|
|
1982
|
-
console.log(` \u26A0\uFE0F Warnings: ${warnings}`);
|
|
1983
|
-
console.log(` \u274C Failed: ${failed}`);
|
|
1984
|
-
if (failed === 0) {
|
|
1985
|
-
console.log(`
|
|
1986
|
-
\u2705 Skill "${skillId}" is valid!
|
|
1987
|
-
`);
|
|
1988
|
-
} else {
|
|
1989
|
-
console.log(`
|
|
1990
|
-
\u26A0\uFE0F Skill "${skillId}" has issues that need attention.
|
|
1991
|
-
`);
|
|
1992
|
-
process.exitCode = 1;
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
async function resolveFullPackageName(skillId) {
|
|
1996
|
-
if (skillId.startsWith("@")) {
|
|
1997
|
-
const info = await fetchNpmPackage(skillId);
|
|
1998
|
-
if (info) return skillId;
|
|
1999
|
-
throw new Error(`Package "${skillId}" not found in npm registry`);
|
|
2000
|
-
}
|
|
2001
|
-
for (const scope of SKILL_SCOPES) {
|
|
2002
|
-
const fullName = `${scope}/${skillId}`;
|
|
2003
|
-
const info = await fetchNpmPackage(fullName);
|
|
2004
|
-
if (info) return fullName;
|
|
2005
|
-
}
|
|
2006
|
-
throw new Error(
|
|
2007
|
-
`Could not resolve "${skillId}" to any known scope.
|
|
2008
|
-
Scopes checked: ${SKILL_SCOPES.join(", ")}`
|
|
2009
|
-
);
|
|
2010
|
-
}
|
|
2011
|
-
function npmExec(command) {
|
|
2012
|
-
try {
|
|
2013
|
-
return execSync2(command, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
2014
|
-
} catch (err) {
|
|
2015
|
-
if (err && typeof err === "object" && "stderr" in err) {
|
|
2016
|
-
const stderr = err.stderr;
|
|
2017
|
-
const msg = Buffer.isBuffer(stderr) ? stderr.toString() : stderr;
|
|
2018
|
-
throw new Error(`npm command failed: ${msg.trim()}`);
|
|
2019
|
-
}
|
|
2020
|
-
if (err instanceof Error) {
|
|
2021
|
-
throw new Error(`npm command failed: ${err.message}`);
|
|
2022
|
-
}
|
|
2023
|
-
throw new Error("npm command failed with an unknown error");
|
|
2024
|
-
}
|
|
2025
|
-
}
|
|
2026
|
-
async function adminDeprecate(skillId, options = {}) {
|
|
2027
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2028
|
-
const version = options.version || "";
|
|
2029
|
-
const message = options.message || "This skill is deprecated. Please use an alternative.";
|
|
2030
|
-
const target = version ? `${pkgName}@${version}` : pkgName;
|
|
2031
|
-
console.log(`
|
|
2032
|
-
\u26A0\uFE0F Deprecating ${target}...
|
|
2033
|
-
`);
|
|
2034
|
-
npmExec(`npm deprecate "${target}" "${message}"`);
|
|
2035
|
-
console.log(`\u2705 Successfully deprecated ${target}`);
|
|
2036
|
-
console.log(` Message: "${message}"
|
|
2037
|
-
`);
|
|
2038
|
-
}
|
|
2039
|
-
async function adminUnpublish(skillId, options = {}) {
|
|
2040
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2041
|
-
const version = options.version;
|
|
2042
|
-
let target;
|
|
2043
|
-
if (version) {
|
|
2044
|
-
target = `${pkgName}@${version}`;
|
|
2045
|
-
console.log(`
|
|
2046
|
-
\u{1F5D1}\uFE0F Unpublishing ${target}...
|
|
2047
|
-
`);
|
|
2048
|
-
} else {
|
|
2049
|
-
target = pkgName;
|
|
2050
|
-
console.log(`
|
|
2051
|
-
\u{1F5D1}\uFE0F Unpublishing entire package ${target}...
|
|
2052
|
-
`);
|
|
2053
|
-
if (!options.force) {
|
|
2054
|
-
throw new Error(
|
|
2055
|
-
"Unpublishing entire package requires --force flag.\n Usage: skm admin unpublish <skill> --force\n Or unpublish a specific version: skm admin unpublish <skill> --version <ver>"
|
|
2056
|
-
);
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
const forceFlag = options.force ? " --force" : "";
|
|
2060
|
-
npmExec(`npm unpublish "${target}"${forceFlag}`);
|
|
2061
|
-
if (version) {
|
|
2062
|
-
console.log(`\u2705 Successfully unpublished ${target}
|
|
2063
|
-
`);
|
|
2064
|
-
} else {
|
|
2065
|
-
console.log(`\u2705 Successfully unpublished entire package ${target}
|
|
2066
|
-
`);
|
|
2067
|
-
}
|
|
2068
|
-
}
|
|
2069
|
-
async function adminTagSet(skillId, tag, version) {
|
|
2070
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2071
|
-
console.log(`
|
|
2072
|
-
\u{1F3F7}\uFE0F Setting dist-tag "${tag}" for ${pkgName}@${version}...
|
|
2073
|
-
`);
|
|
2074
|
-
npmExec(`npm dist-tag add "${pkgName}@${version}" "${tag}"`);
|
|
2075
|
-
console.log(`\u2705 dist-tag "${tag}" set to ${pkgName}@${version}
|
|
2076
|
-
`);
|
|
2077
|
-
}
|
|
2078
|
-
async function adminTagRemove(skillId, tag) {
|
|
2079
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2080
|
-
console.log(`
|
|
2081
|
-
\u{1F3F7}\uFE0F Removing dist-tag "${tag}" from ${pkgName}...
|
|
2082
|
-
`);
|
|
2083
|
-
npmExec(`npm dist-tag rm "${pkgName}" "${tag}"`);
|
|
2084
|
-
console.log(`\u2705 dist-tag "${tag}" removed from ${pkgName}
|
|
2085
|
-
`);
|
|
2086
|
-
}
|
|
2087
|
-
async function adminTagList(skillId) {
|
|
2088
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2089
|
-
console.log(`
|
|
2090
|
-
\u{1F3F7}\uFE0F dist-tags for ${pkgName}:
|
|
2091
|
-
`);
|
|
2092
|
-
const output = npmExec(`npm dist-tag ls "${pkgName}"`);
|
|
2093
|
-
if (!output) {
|
|
2094
|
-
console.log(" (no dist-tags found)\n");
|
|
2095
|
-
return;
|
|
2096
|
-
}
|
|
2097
|
-
const lines = output.split("\n").filter(Boolean);
|
|
2098
|
-
for (const line of lines) {
|
|
2099
|
-
const [tag, version] = line.split(": ").map((s) => s.trim());
|
|
2100
|
-
const isLatest = tag === "latest" ? " \u2190 latest" : "";
|
|
2101
|
-
console.log(` ${tag}: ${version}${isLatest}`);
|
|
2102
|
-
}
|
|
2103
|
-
console.log();
|
|
2104
|
-
}
|
|
2105
|
-
async function adminOwnerAdd(skillId, user) {
|
|
2106
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2107
|
-
console.log(`
|
|
2108
|
-
\u{1F465} Adding owner "${user}" to ${pkgName}...
|
|
2109
|
-
`);
|
|
2110
|
-
npmExec(`npm owner add "${user}" "${pkgName}"`);
|
|
2111
|
-
console.log(`\u2705 Owner "${user}" added to ${pkgName}
|
|
2112
|
-
`);
|
|
2113
|
-
}
|
|
2114
|
-
async function adminOwnerRemove(skillId, user) {
|
|
2115
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2116
|
-
console.log(`
|
|
2117
|
-
\u{1F465} Removing owner "${user}" from ${pkgName}...
|
|
2118
|
-
`);
|
|
2119
|
-
npmExec(`npm owner rm "${user}" "${pkgName}"`);
|
|
2120
|
-
console.log(`\u2705 Owner "${user}" removed from ${pkgName}
|
|
2121
|
-
`);
|
|
2122
|
-
}
|
|
2123
|
-
async function adminAccess(skillId, level) {
|
|
2124
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2125
|
-
console.log(`
|
|
2126
|
-
\u{1F512} Setting access for ${pkgName} to "${level}"...
|
|
2127
|
-
`);
|
|
2128
|
-
npmExec(`npm access "${level}" "${pkgName}"`);
|
|
2129
|
-
console.log(`\u2705 Access for ${pkgName} set to "${level}"
|
|
2130
|
-
`);
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
// src/commands/ui.ts
|
|
2134
|
-
var __filename2 = fileURLToPath3(import.meta.url);
|
|
2135
|
-
var __dirname2 = dirname(__filename2);
|
|
2136
|
-
var guiDir = join3(__dirname2, "..", "gui");
|
|
2137
|
-
var cache = /* @__PURE__ */ new Map();
|
|
2138
|
-
function getCached2(key) {
|
|
2139
|
-
const entry = cache.get(key);
|
|
2140
|
-
if (!entry) return null;
|
|
2141
|
-
if (Date.now() > entry.expiry) {
|
|
2142
|
-
cache.delete(key);
|
|
2143
|
-
return null;
|
|
2144
|
-
}
|
|
2145
|
-
return entry.data;
|
|
2146
|
-
}
|
|
2147
|
-
function setCache2(key, data, ttlMs = 6e4) {
|
|
2148
|
-
cache.set(key, { data, expiry: Date.now() + ttlMs });
|
|
2149
|
-
}
|
|
2150
|
-
async function throttledMap(items, fn, concurrency = 3) {
|
|
2151
|
-
const results = [];
|
|
2152
|
-
for (let i = 0; i < items.length; i += concurrency) {
|
|
2153
|
-
const batch = items.slice(i, i + concurrency);
|
|
2154
|
-
const batchResults = await Promise.all(batch.map((item, idx) => fn(item, i + idx)));
|
|
2155
|
-
results.push(...batchResults);
|
|
2156
|
-
if (i + concurrency < items.length) {
|
|
2157
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
return results;
|
|
2161
|
-
}
|
|
2162
|
-
var MIME_TYPES = {
|
|
2163
|
-
".html": "text/html; charset=utf-8",
|
|
2164
|
-
".js": "application/javascript; charset=utf-8",
|
|
2165
|
-
".css": "text/css; charset=utf-8",
|
|
2166
|
-
".json": "application/json; charset=utf-8",
|
|
2167
|
-
".png": "image/png",
|
|
2168
|
-
".svg": "image/svg+xml",
|
|
2169
|
-
".ico": "image/x-icon"
|
|
2170
|
-
};
|
|
2171
|
-
function jsonResponse(res, status, data) {
|
|
2172
|
-
res.writeHead(status, {
|
|
2173
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
2174
|
-
"Access-Control-Allow-Origin": "*"
|
|
2175
|
-
});
|
|
2176
|
-
res.end(JSON.stringify(data));
|
|
2177
|
-
}
|
|
2178
|
-
function parseBody(req) {
|
|
2179
|
-
return new Promise((resolve2, reject) => {
|
|
2180
|
-
let body = "";
|
|
2181
|
-
req.on("data", (chunk) => {
|
|
2182
|
-
body += chunk.toString();
|
|
2183
|
-
});
|
|
2184
|
-
req.on("end", () => {
|
|
2185
|
-
if (!body) return resolve2({});
|
|
2186
|
-
try {
|
|
2187
|
-
resolve2(JSON.parse(body));
|
|
2188
|
-
} catch {
|
|
2189
|
-
reject(new Error("Invalid JSON body"));
|
|
2190
|
-
}
|
|
2191
|
-
});
|
|
2192
|
-
req.on("error", reject);
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
var API_ROUTES = {
|
|
2196
|
-
GET: {},
|
|
2197
|
-
POST: {}
|
|
2198
|
-
};
|
|
2199
|
-
API_ROUTES.GET["/api/skills"] = async (_req, res, url) => {
|
|
2200
|
-
try {
|
|
2201
|
-
const page = Math.max(1, parseInt(url.searchParams.get("page") || "1"));
|
|
2202
|
-
const limit = Math.min(100, Math.max(1, parseInt(url.searchParams.get("limit") || "20")));
|
|
2203
|
-
const search = url.searchParams.get("search") || "";
|
|
2204
|
-
const sort = url.searchParams.get("sort") || "name";
|
|
2205
|
-
const platform = url.searchParams.get("platform") || "";
|
|
2206
|
-
const cacheKey = `search:${search}:limit:${limit}`;
|
|
2207
|
-
let searchResult = getCached2(cacheKey);
|
|
2208
|
-
if (!searchResult) {
|
|
2209
|
-
searchResult = await searchSkillmarketPackages({
|
|
2210
|
-
from: 0,
|
|
2211
|
-
size: 100,
|
|
2212
|
-
// 一次拉取更多,避免分页
|
|
2213
|
-
keyword: search || void 0
|
|
2214
|
-
});
|
|
2215
|
-
setCache2(cacheKey, searchResult, 3e4);
|
|
2216
|
-
}
|
|
2217
|
-
const { packages, total } = searchResult;
|
|
2218
|
-
let fetchErrors = 0;
|
|
2219
|
-
const skillDetails = await throttledMap(packages, async (pkgName) => {
|
|
2220
|
-
try {
|
|
2221
|
-
const pkgCacheKey = `pkg:${pkgName}`;
|
|
2222
|
-
let info = getCached2(pkgCacheKey);
|
|
2223
|
-
if (!info) {
|
|
2224
|
-
info = await fetchNpmPackage(pkgName);
|
|
2225
|
-
if (info) setCache2(pkgCacheKey, info, 3e4);
|
|
2226
|
-
}
|
|
2227
|
-
if (!info) {
|
|
2228
|
-
fetchErrors++;
|
|
2229
|
-
return null;
|
|
2230
|
-
}
|
|
2231
|
-
const latestVersion = info["dist-tags"]?.latest || "unknown";
|
|
2232
|
-
const pkg = info.versions?.[latestVersion];
|
|
2233
|
-
const meta = pkg?.skillmarket;
|
|
2234
|
-
return {
|
|
2235
|
-
id: meta?.id || info.name.replace(/^@[^/]+\//, ""),
|
|
2236
|
-
name: info.name,
|
|
2237
|
-
displayName: meta?.displayName || info.name,
|
|
2238
|
-
version: latestVersion,
|
|
2239
|
-
description: pkg?.description || "",
|
|
2240
|
-
platforms: meta?.platforms || [],
|
|
2241
|
-
author: info.author?.name || pkg?.author?.name || "",
|
|
2242
|
-
homepage: pkg?.homepage || "",
|
|
2243
|
-
repository: pkg?.repository?.url || "",
|
|
2244
|
-
updated: info.time?.[latestVersion] || info.time?.modified || ""
|
|
2245
|
-
};
|
|
2246
|
-
} catch {
|
|
2247
|
-
fetchErrors++;
|
|
2248
|
-
return null;
|
|
2249
|
-
}
|
|
2250
|
-
}, 3);
|
|
2251
|
-
let skills = skillDetails.filter(Boolean);
|
|
2252
|
-
if (platform) {
|
|
2253
|
-
skills = skills.filter(
|
|
2254
|
-
(s) => Array.isArray(s.platforms) && s.platforms.includes(platform)
|
|
2255
|
-
);
|
|
2256
|
-
}
|
|
2257
|
-
skills.sort((a, b) => {
|
|
2258
|
-
const nameA = (a.displayName || a.id || "").toLowerCase();
|
|
2259
|
-
const nameB = (b.displayName || b.id || "").toLowerCase();
|
|
2260
|
-
switch (sort) {
|
|
2261
|
-
case "-name":
|
|
2262
|
-
return nameB.localeCompare(nameA);
|
|
2263
|
-
case "updated":
|
|
2264
|
-
return (a.updated || "").localeCompare(b.updated || "");
|
|
2265
|
-
case "-updated":
|
|
2266
|
-
return (b.updated || "").localeCompare(a.updated || "");
|
|
2267
|
-
case "name":
|
|
2268
|
-
default:
|
|
2269
|
-
return nameA.localeCompare(nameB);
|
|
2270
|
-
}
|
|
2271
|
-
});
|
|
2272
|
-
const filteredTotal = skills.length;
|
|
2273
|
-
const totalPages = Math.ceil(filteredTotal / limit) || 1;
|
|
2274
|
-
const start = (page - 1) * limit;
|
|
2275
|
-
const pagedSkills = skills.slice(start, start + limit);
|
|
2276
|
-
jsonResponse(res, 200, { skills: pagedSkills, page, totalPages, total: filteredTotal, fetchErrors });
|
|
2277
|
-
} catch (err) {
|
|
2278
|
-
jsonResponse(res, 500, {
|
|
2279
|
-
error: String(err),
|
|
2280
|
-
skills: [],
|
|
2281
|
-
page: 1,
|
|
2282
|
-
totalPages: 1,
|
|
2283
|
-
total: 0
|
|
2284
|
-
});
|
|
2285
|
-
}
|
|
2286
|
-
};
|
|
2287
|
-
API_ROUTES.GET["/api/installed"] = async (_req, res, _url) => {
|
|
2288
|
-
try {
|
|
2289
|
-
const skills = await getInstalledSkills();
|
|
2290
|
-
jsonResponse(res, 200, skills.map((s) => ({
|
|
2291
|
-
id: s.id,
|
|
2292
|
-
displayName: s.id,
|
|
2293
|
-
version: s.version,
|
|
2294
|
-
installedAt: s.installedAt,
|
|
2295
|
-
platforms: s.platforms
|
|
2296
|
-
})));
|
|
2297
|
-
} catch (err) {
|
|
2298
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2299
|
-
}
|
|
2300
|
-
};
|
|
2301
|
-
API_ROUTES.GET["/api/platforms"] = async (_req, res, _url) => {
|
|
2302
|
-
try {
|
|
2303
|
-
const available = await detectPlatforms();
|
|
2304
|
-
const allAdapters = getAllAdapters();
|
|
2305
|
-
const platforms = await Promise.all(
|
|
2306
|
-
allAdapters.map(async (adapter) => {
|
|
2307
|
-
const isAvailable = available.find((a) => a.id === adapter.id);
|
|
2308
|
-
const installed = await adapter.listInstalled();
|
|
2309
|
-
return {
|
|
2310
|
-
id: adapter.id,
|
|
2311
|
-
name: adapter.name,
|
|
2312
|
-
available: !!isAvailable,
|
|
2313
|
-
installedCount: Array.isArray(installed) ? installed.length : 0,
|
|
2314
|
-
installedSkills: Array.isArray(installed) ? installed : []
|
|
2315
|
-
};
|
|
2316
|
-
})
|
|
2317
|
-
);
|
|
2318
|
-
jsonResponse(res, 200, platforms);
|
|
2319
|
-
} catch (err) {
|
|
2320
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2321
|
-
}
|
|
2322
|
-
};
|
|
2323
|
-
API_ROUTES.GET["/api/platform-info"] = async (_req, res, url) => {
|
|
2324
|
-
try {
|
|
2325
|
-
const platformId = url.searchParams.get("id") || "";
|
|
2326
|
-
if (!platformId) {
|
|
2327
|
-
jsonResponse(res, 400, { error: 'Missing "id" query parameter' });
|
|
2328
|
-
return;
|
|
2329
|
-
}
|
|
2330
|
-
const allAdapters = getAllAdapters();
|
|
2331
|
-
const adapter = allAdapters.find((a) => a.id === platformId);
|
|
2332
|
-
if (!adapter) {
|
|
2333
|
-
jsonResponse(res, 404, { error: `Platform "${platformId}" not found` });
|
|
2334
|
-
return;
|
|
2335
|
-
}
|
|
2336
|
-
const available = await detectPlatforms();
|
|
2337
|
-
const isAvailable = available.find((a) => a.id === adapter.id);
|
|
2338
|
-
const installed = await adapter.listInstalled();
|
|
2339
|
-
const installedSkills = Array.isArray(installed) ? installed : [];
|
|
2340
|
-
jsonResponse(res, 200, {
|
|
2341
|
-
id: adapter.id,
|
|
2342
|
-
name: adapter.name,
|
|
2343
|
-
available: !!isAvailable,
|
|
2344
|
-
installedCount: installedSkills.length,
|
|
2345
|
-
installedSkills
|
|
2346
|
-
});
|
|
2347
|
-
} catch (err) {
|
|
2348
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2349
|
-
}
|
|
2350
|
-
};
|
|
2351
|
-
API_ROUTES.GET["/api/skill-info"] = async (_req, res, url) => {
|
|
2352
|
-
try {
|
|
2353
|
-
const skillName = url.searchParams.get("skill") || "";
|
|
2354
|
-
if (!skillName) {
|
|
2355
|
-
jsonResponse(res, 400, { error: 'Missing "skill" query parameter' });
|
|
2356
|
-
return;
|
|
2357
|
-
}
|
|
2358
|
-
const cacheKey = `skill-info:${skillName}`;
|
|
2359
|
-
let info = getCached2(cacheKey);
|
|
2360
|
-
if (!info) {
|
|
2361
|
-
info = await fetchSkillPackage(skillName);
|
|
2362
|
-
if (info) setCache2(cacheKey, info, 3e4);
|
|
2363
|
-
}
|
|
2364
|
-
if (!info) {
|
|
2365
|
-
jsonResponse(res, 404, { error: `Skill "${skillName}" not found` });
|
|
2366
|
-
return;
|
|
2367
|
-
}
|
|
2368
|
-
const latestVersion = info["dist-tags"]?.latest || "unknown";
|
|
2369
|
-
const pkg = latestVersion ? info.versions?.[latestVersion] : void 0;
|
|
2370
|
-
const meta = pkg?.skillmarket;
|
|
2371
|
-
const versionKeys = Object.keys(info.versions || {});
|
|
2372
|
-
const recentVersions = versionKeys.slice(-20);
|
|
2373
|
-
jsonResponse(res, 200, {
|
|
2374
|
-
id: meta?.id || info.name.replace(/^@[^/]+\//, ""),
|
|
2375
|
-
name: info.name,
|
|
2376
|
-
displayName: meta?.displayName || info.name,
|
|
2377
|
-
description: pkg?.description || "",
|
|
2378
|
-
version: latestVersion,
|
|
2379
|
-
platforms: meta?.platforms || [],
|
|
2380
|
-
versions: recentVersions,
|
|
2381
|
-
author: info.author?.name || pkg?.author?.name || "",
|
|
2382
|
-
license: info.license || "",
|
|
2383
|
-
homepage: pkg?.homepage || "",
|
|
2384
|
-
repository: pkg?.repository?.url || "",
|
|
2385
|
-
readme: info.readme || ""
|
|
2386
|
-
});
|
|
2387
|
-
} catch (err) {
|
|
2388
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2389
|
-
}
|
|
2390
|
-
};
|
|
2391
|
-
API_ROUTES.GET["/api/version"] = async (_req, res, _url) => {
|
|
2392
|
-
try {
|
|
2393
|
-
const pkgPath = join3(__dirname2, "..", "package.json");
|
|
2394
|
-
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
2395
|
-
jsonResponse(res, 200, { version: pkg.version || "1.0.0" });
|
|
2396
|
-
} catch {
|
|
2397
|
-
jsonResponse(res, 200, { version: "1.0.0" });
|
|
2398
|
-
}
|
|
2399
|
-
};
|
|
2400
|
-
API_ROUTES.GET["/api/config"] = async (_req, res, _url) => {
|
|
2401
|
-
jsonResponse(res, 200, {
|
|
2402
|
-
npmScope: NPM_SCOPE,
|
|
2403
|
-
npmScopeFallback: NPM_SCOPE_FALLBACK,
|
|
2404
|
-
npmRegistry: NPM_REGISTRY,
|
|
2405
|
-
skmUrl: SKM_URL,
|
|
2406
|
-
skillScopes: SKILL_SCOPES
|
|
2407
|
-
});
|
|
2408
|
-
};
|
|
2409
|
-
API_ROUTES.POST["/api/install"] = async (req, res, _url) => {
|
|
2410
|
-
try {
|
|
2411
|
-
const body = await parseBody(req);
|
|
2412
|
-
const skillId = String(body.skillId || "");
|
|
2413
|
-
const version = body.version ? String(body.version) : void 0;
|
|
2414
|
-
const platform = body.platform ? String(body.platform) : void 0;
|
|
2415
|
-
if (!skillId) {
|
|
2416
|
-
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
2417
|
-
return;
|
|
2418
|
-
}
|
|
2419
|
-
await installSkill(skillId, version, {
|
|
2420
|
-
platforms: platform ? [platform] : void 0,
|
|
2421
|
-
force: true
|
|
2422
|
-
});
|
|
2423
|
-
jsonResponse(res, 200, { success: true, message: `${skillId} installed successfully` });
|
|
2424
|
-
} catch (err) {
|
|
2425
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2426
|
-
}
|
|
2427
|
-
};
|
|
2428
|
-
API_ROUTES.POST["/api/uninstall"] = async (req, res, _url) => {
|
|
2429
|
-
try {
|
|
2430
|
-
const body = await parseBody(req);
|
|
2431
|
-
const skillId = String(body.skillId || "");
|
|
2432
|
-
const platform = body.platform ? String(body.platform) : void 0;
|
|
2433
|
-
if (!skillId) {
|
|
2434
|
-
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
2435
|
-
return;
|
|
2436
|
-
}
|
|
2437
|
-
await uninstallSkill(skillId, {
|
|
2438
|
-
platforms: platform ? [platform] : void 0,
|
|
2439
|
-
yes: true
|
|
2440
|
-
});
|
|
2441
|
-
jsonResponse(res, 200, { success: true, message: `${skillId} uninstalled successfully` });
|
|
2442
|
-
} catch (err) {
|
|
2443
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2444
|
-
}
|
|
2445
|
-
};
|
|
2446
|
-
API_ROUTES.GET["/api/admin/stats"] = async (_req, res, _url) => {
|
|
2447
|
-
try {
|
|
2448
|
-
const packages = await fetchScopePackages();
|
|
2449
|
-
const infos = (await Promise.all(
|
|
2450
|
-
packages.map(async (pkg) => {
|
|
2451
|
-
try {
|
|
2452
|
-
const info = await fetchNpmPackage(pkg);
|
|
2453
|
-
return info ? { name: pkg, info } : null;
|
|
2454
|
-
} catch {
|
|
2455
|
-
return null;
|
|
2456
|
-
}
|
|
2457
|
-
})
|
|
2458
|
-
)).filter(Boolean);
|
|
2459
|
-
let totalVersions = 0;
|
|
2460
|
-
let totalSize = 0;
|
|
2461
|
-
const platformSet = /* @__PURE__ */ new Set();
|
|
2462
|
-
let withMetadata = 0;
|
|
2463
|
-
for (const { info } of infos) {
|
|
2464
|
-
const versions = Object.keys(info.versions || {});
|
|
2465
|
-
totalVersions += versions.length;
|
|
2466
|
-
const latestVer = info["dist-tags"]?.latest;
|
|
2467
|
-
const latestPkg = latestVer ? info.versions?.[latestVer] : void 0;
|
|
2468
|
-
const meta = latestPkg?.skillmarket;
|
|
2469
|
-
if (meta) {
|
|
2470
|
-
withMetadata++;
|
|
2471
|
-
if (meta.platforms) meta.platforms.forEach((p) => platformSet.add(p));
|
|
2472
|
-
}
|
|
2473
|
-
if (latestPkg?.dist?.unpackedSize) totalSize += latestPkg.dist.unpackedSize;
|
|
2474
|
-
}
|
|
2475
|
-
jsonResponse(res, 200, {
|
|
2476
|
-
totalSkills: infos.length,
|
|
2477
|
-
totalVersions,
|
|
2478
|
-
averageVersions: infos.length > 0 ? (totalVersions / infos.length).toFixed(1) : "0",
|
|
2479
|
-
withMetadata,
|
|
2480
|
-
totalSizeMB: (totalSize / 1024 / 1024).toFixed(2),
|
|
2481
|
-
platformCount: platformSet.size,
|
|
2482
|
-
platforms: [...platformSet]
|
|
2483
|
-
});
|
|
2484
|
-
} catch (err) {
|
|
2485
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2486
|
-
}
|
|
2487
|
-
};
|
|
2488
|
-
API_ROUTES.POST["/api/admin/deprecate"] = async (req, res, _url) => {
|
|
2489
|
-
try {
|
|
2490
|
-
const body = await parseBody(req);
|
|
2491
|
-
const skillId = String(body.skillId || "");
|
|
2492
|
-
const version = body.version ? String(body.version) : "";
|
|
2493
|
-
const message = body.message ? String(body.message) : "";
|
|
2494
|
-
if (!skillId) {
|
|
2495
|
-
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
2496
|
-
return;
|
|
2497
|
-
}
|
|
2498
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2499
|
-
const target = version ? `${pkgName}@${version}` : pkgName;
|
|
2500
|
-
const deprecateMsg = message || "This skill is deprecated. Please use an alternative.";
|
|
2501
|
-
npmExec(`npm deprecate "${target}" "${deprecateMsg}"`);
|
|
2502
|
-
jsonResponse(res, 200, { success: true, message: `${target} deprecated` });
|
|
2503
|
-
} catch (err) {
|
|
2504
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2505
|
-
}
|
|
2506
|
-
};
|
|
2507
|
-
API_ROUTES.POST["/api/admin/unpublish"] = async (req, res, _url) => {
|
|
2508
|
-
try {
|
|
2509
|
-
const body = await parseBody(req);
|
|
2510
|
-
const skillId = String(body.skillId || "");
|
|
2511
|
-
const version = body.version ? String(body.version) : "";
|
|
2512
|
-
const force = !!body.force;
|
|
2513
|
-
if (!skillId) {
|
|
2514
|
-
jsonResponse(res, 400, { error: "Missing skillId" });
|
|
2515
|
-
return;
|
|
2516
|
-
}
|
|
2517
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2518
|
-
let target;
|
|
2519
|
-
if (version) {
|
|
2520
|
-
target = `${pkgName}@${version}`;
|
|
2521
|
-
} else {
|
|
2522
|
-
if (!force) {
|
|
2523
|
-
jsonResponse(res, 400, { error: "Unpublishing entire package requires force=true" });
|
|
2524
|
-
return;
|
|
2525
|
-
}
|
|
2526
|
-
target = pkgName;
|
|
2527
|
-
}
|
|
2528
|
-
const forceFlag = force ? " --force" : "";
|
|
2529
|
-
npmExec(`npm unpublish "${target}"${forceFlag}`);
|
|
2530
|
-
jsonResponse(res, 200, { success: true, message: `${target} unpublished` });
|
|
2531
|
-
} catch (err) {
|
|
2532
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2533
|
-
}
|
|
2534
|
-
};
|
|
2535
|
-
API_ROUTES.POST["/api/admin/tag"] = async (req, res, _url) => {
|
|
2536
|
-
try {
|
|
2537
|
-
const body = await parseBody(req);
|
|
2538
|
-
const skillId = String(body.skillId || "");
|
|
2539
|
-
const action = String(body.action || "");
|
|
2540
|
-
const tag = body.tag ? String(body.tag) : "";
|
|
2541
|
-
const version = body.version ? String(body.version) : "";
|
|
2542
|
-
if (!skillId || !action) {
|
|
2543
|
-
jsonResponse(res, 400, { error: "Missing skillId or action" });
|
|
2544
|
-
return;
|
|
2545
|
-
}
|
|
2546
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2547
|
-
if (action === "set") {
|
|
2548
|
-
if (!tag || !version) {
|
|
2549
|
-
jsonResponse(res, 400, { error: "Tag set requires tag and version" });
|
|
2550
|
-
return;
|
|
2551
|
-
}
|
|
2552
|
-
npmExec(`npm dist-tag add "${pkgName}@${version}" "${tag}"`);
|
|
2553
|
-
jsonResponse(res, 200, { success: true, message: `Tag "${tag}" set to ${pkgName}@${version}` });
|
|
2554
|
-
} else if (action === "rm") {
|
|
2555
|
-
if (!tag) {
|
|
2556
|
-
jsonResponse(res, 400, { error: "Tag rm requires tag" });
|
|
2557
|
-
return;
|
|
2558
|
-
}
|
|
2559
|
-
npmExec(`npm dist-tag rm "${pkgName}" "${tag}"`);
|
|
2560
|
-
jsonResponse(res, 200, { success: true, message: `Tag "${tag}" removed from ${pkgName}` });
|
|
2561
|
-
} else if (action === "ls") {
|
|
2562
|
-
const output = npmExec(`npm dist-tag ls "${pkgName}"`);
|
|
2563
|
-
const tags = {};
|
|
2564
|
-
if (output) {
|
|
2565
|
-
output.split("\n").filter(Boolean).forEach((line) => {
|
|
2566
|
-
const parts = line.split(": ");
|
|
2567
|
-
if (parts.length >= 2) tags[parts[0].trim()] = parts[1].trim();
|
|
2568
|
-
});
|
|
2569
|
-
}
|
|
2570
|
-
jsonResponse(res, 200, { success: true, tags, packageName: pkgName });
|
|
2571
|
-
} else {
|
|
2572
|
-
jsonResponse(res, 400, { error: `Unknown action: ${action} (use set/rm/ls)` });
|
|
2573
|
-
}
|
|
2574
|
-
} catch (err) {
|
|
2575
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2576
|
-
}
|
|
2577
|
-
};
|
|
2578
|
-
API_ROUTES.POST["/api/admin/owner"] = async (req, res, _url) => {
|
|
2579
|
-
try {
|
|
2580
|
-
const body = await parseBody(req);
|
|
2581
|
-
const skillId = String(body.skillId || "");
|
|
2582
|
-
const action = String(body.action || "");
|
|
2583
|
-
const user = String(body.user || "");
|
|
2584
|
-
if (!skillId || !action || !user) {
|
|
2585
|
-
jsonResponse(res, 400, { error: "Missing skillId, action, or user" });
|
|
2586
|
-
return;
|
|
2587
|
-
}
|
|
2588
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2589
|
-
const npmAction = action === "add" ? "add" : "rm";
|
|
2590
|
-
npmExec(`npm owner ${npmAction} "${user}" "${pkgName}"`);
|
|
2591
|
-
jsonResponse(res, 200, {
|
|
2592
|
-
success: true,
|
|
2593
|
-
message: `Owner ${npmAction === "add" ? "added" : "removed"}: ${user} ${npmAction === "add" ? "to" : "from"} ${pkgName}`
|
|
2594
|
-
});
|
|
2595
|
-
} catch (err) {
|
|
2596
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2597
|
-
}
|
|
2598
|
-
};
|
|
2599
|
-
API_ROUTES.POST["/api/admin/access"] = async (req, res, _url) => {
|
|
2600
|
-
try {
|
|
2601
|
-
const body = await parseBody(req);
|
|
2602
|
-
const skillId = String(body.skillId || "");
|
|
2603
|
-
const level = String(body.level || "");
|
|
2604
|
-
if (!skillId || !level) {
|
|
2605
|
-
jsonResponse(res, 400, { error: "Missing skillId or level" });
|
|
2606
|
-
return;
|
|
2607
|
-
}
|
|
2608
|
-
if (level !== "public" && level !== "restricted") {
|
|
2609
|
-
jsonResponse(res, 400, { error: 'Level must be "public" or "restricted"' });
|
|
2610
|
-
return;
|
|
2611
|
-
}
|
|
2612
|
-
const pkgName = await resolveFullPackageName(skillId);
|
|
2613
|
-
npmExec(`npm access "${level}" "${pkgName}"`);
|
|
2614
|
-
jsonResponse(res, 200, { success: true, message: `Access for ${pkgName} set to "${level}"` });
|
|
2615
|
-
} catch (err) {
|
|
2616
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2617
|
-
}
|
|
2618
|
-
};
|
|
2619
|
-
API_ROUTES.POST["/api/update"] = async (req, res, _url) => {
|
|
2620
|
-
try {
|
|
2621
|
-
const body = await parseBody(req);
|
|
2622
|
-
const skillId = body.skillId ? String(body.skillId) : void 0;
|
|
2623
|
-
await updateSkill(skillId);
|
|
2624
|
-
const msg = skillId ? `${skillId} updated successfully` : "All skills updated successfully";
|
|
2625
|
-
jsonResponse(res, 200, { success: true, message: msg });
|
|
2626
|
-
} catch (err) {
|
|
2627
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2628
|
-
}
|
|
2629
|
-
};
|
|
2630
|
-
var PROJECT_ROOT = join3(__dirname2, "..");
|
|
2631
|
-
API_ROUTES.POST["/api/upload"] = async (req, res, _url) => {
|
|
2632
|
-
try {
|
|
2633
|
-
const body = await parseBody(req);
|
|
2634
|
-
const fileData = String(body.fileData || "");
|
|
2635
|
-
const fileName = String(body.fileName || "upload.zip");
|
|
2636
|
-
const skillNameOverride = body.skillNameOverride ? String(body.skillNameOverride).trim() : "";
|
|
2637
|
-
if (!fileData) {
|
|
2638
|
-
jsonResponse(res, 400, { error: "Missing fileData" });
|
|
2639
|
-
return;
|
|
2640
|
-
}
|
|
2641
|
-
const buffer = Buffer.from(fileData, "base64");
|
|
2642
|
-
if (buffer.length === 0) {
|
|
2643
|
-
jsonResponse(res, 400, { error: "Empty file data" });
|
|
2644
|
-
return;
|
|
2645
|
-
}
|
|
2646
|
-
const zip = new AdmZip(buffer);
|
|
2647
|
-
const entries = zip.getEntries();
|
|
2648
|
-
if (entries.length === 0) {
|
|
2649
|
-
jsonResponse(res, 400, { error: "ZIP archive is empty" });
|
|
2650
|
-
return;
|
|
2651
|
-
}
|
|
2652
|
-
const normalizeEntryName = (name) => name.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2653
|
-
const pkgEntry = entries.find((e) => {
|
|
2654
|
-
const normalized = normalizeEntryName(e.entryName);
|
|
2655
|
-
return normalized === "package.json" || normalized.endsWith("/package.json");
|
|
2656
|
-
});
|
|
2657
|
-
let skillName = "";
|
|
2658
|
-
let pkgInfo = {};
|
|
2659
|
-
if (pkgEntry) {
|
|
2660
|
-
try {
|
|
2661
|
-
pkgInfo = JSON.parse(pkgEntry.getData().toString("utf-8"));
|
|
2662
|
-
skillName = pkgInfo.skillmarket?.id || pkgInfo.name?.replace(/^@[^/]+\//, "") || "";
|
|
2663
|
-
} catch {
|
|
2664
|
-
}
|
|
2665
|
-
}
|
|
2666
|
-
if (skillNameOverride) {
|
|
2667
|
-
skillName = skillNameOverride;
|
|
2668
|
-
}
|
|
2669
|
-
if (!skillName) {
|
|
2670
|
-
const rootDirs = [...new Set(entries.map((e) => e.entryName.split("/")[0]))].filter(Boolean);
|
|
2671
|
-
skillName = rootDirs.length === 1 ? rootDirs[0] : basename(fileName, ".zip");
|
|
2672
|
-
}
|
|
2673
|
-
skillName = skillName.replace(/[^a-zA-Z0-9_-]/g, "_").toLowerCase();
|
|
2674
|
-
if (!skillName) skillName = "untitled-skill";
|
|
2675
|
-
const skillDir = join3(PROJECT_ROOT, "skills", skillName);
|
|
2676
|
-
if (existsSync3(skillDir)) {
|
|
2677
|
-
rmSync(skillDir, { recursive: true, force: true });
|
|
2678
|
-
}
|
|
2679
|
-
mkdirSync(skillDir, { recursive: true });
|
|
2680
|
-
zip.extractAllTo(skillDir, true);
|
|
2681
|
-
const extractedItems = readdirSync(skillDir, { withFileTypes: true });
|
|
2682
|
-
const subDirs = extractedItems.filter((i) => i.isDirectory());
|
|
2683
|
-
const files = extractedItems.filter((i) => !i.isDirectory());
|
|
2684
|
-
if (subDirs.length === 1 && files.length === 0) {
|
|
2685
|
-
const subDirPath = join3(skillDir, subDirs[0].name);
|
|
2686
|
-
const subItems = readdirSync(subDirPath, { withFileTypes: true });
|
|
2687
|
-
for (const item of subItems) {
|
|
2688
|
-
renameSync(join3(subDirPath, item.name), join3(skillDir, item.name));
|
|
2689
|
-
}
|
|
2690
|
-
rmSync(subDirPath, { recursive: true, force: true });
|
|
2691
|
-
}
|
|
2692
|
-
const skillMdPath = findFileSync(skillDir, "SKILL.md");
|
|
2693
|
-
const skillMdExists = skillMdPath !== null;
|
|
2694
|
-
const pkgJsonPath = findFileSync(skillDir, "package.json") || join3(skillDir, "package.json");
|
|
2695
|
-
if (existsSync3(pkgJsonPath)) {
|
|
2696
|
-
try {
|
|
2697
|
-
pkgInfo = JSON.parse(readFileSync3(pkgJsonPath, "utf-8"));
|
|
2698
|
-
} catch {
|
|
2699
|
-
}
|
|
2700
|
-
}
|
|
2701
|
-
const meta = pkgInfo?.skillmarket || {};
|
|
2702
|
-
const result = {
|
|
2703
|
-
skillName,
|
|
2704
|
-
displayName: meta.displayName || pkgInfo.displayName || skillName,
|
|
2705
|
-
version: pkgInfo.version || "0.0.0",
|
|
2706
|
-
description: pkgInfo.description || meta.description || "",
|
|
2707
|
-
platforms: meta.platforms || [],
|
|
2708
|
-
hasPackageJson: existsSync3(pkgJsonPath),
|
|
2709
|
-
hasSkillMd: skillMdExists,
|
|
2710
|
-
fileCount: entries.length
|
|
2711
|
-
};
|
|
2712
|
-
jsonResponse(res, 200, result);
|
|
2713
|
-
} catch (err) {
|
|
2714
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2715
|
-
}
|
|
2716
|
-
};
|
|
2717
|
-
API_ROUTES.POST["/api/upload/action"] = async (req, res, _url) => {
|
|
2718
|
-
try {
|
|
2719
|
-
const body = await parseBody(req);
|
|
2720
|
-
const skillName = String(body.skillName || "");
|
|
2721
|
-
const action = String(body.action || "");
|
|
2722
|
-
if (!skillName) {
|
|
2723
|
-
jsonResponse(res, 400, { error: "Missing skillName" });
|
|
2724
|
-
return;
|
|
2725
|
-
}
|
|
2726
|
-
if (!["publish", "install", "both"].includes(action)) {
|
|
2727
|
-
jsonResponse(res, 400, { error: 'action must be "publish", "install", or "both"' });
|
|
2728
|
-
return;
|
|
2729
|
-
}
|
|
2730
|
-
const skillDir = join3(PROJECT_ROOT, "skills", skillName);
|
|
2731
|
-
if (!existsSync3(skillDir)) {
|
|
2732
|
-
jsonResponse(res, 404, { error: `Skill "${skillName}" not found in skills/ directory. Upload first.` });
|
|
2733
|
-
return;
|
|
2734
|
-
}
|
|
2735
|
-
const results = {};
|
|
2736
|
-
if (action === "publish" || action === "both") {
|
|
2737
|
-
try {
|
|
2738
|
-
await publishSkill(skillName);
|
|
2739
|
-
results.publish = { success: true, message: `${skillName} published to npm` };
|
|
2740
|
-
} catch (err) {
|
|
2741
|
-
results.publish = { success: false, message: String(err) };
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
if (action === "install" || action === "both") {
|
|
2745
|
-
try {
|
|
2746
|
-
await installSkill(skillName, void 0, { force: true });
|
|
2747
|
-
results.install = { success: true, message: `${skillName} installed locally` };
|
|
2748
|
-
} catch (err) {
|
|
2749
|
-
results.install = { success: false, message: String(err) };
|
|
2750
|
-
}
|
|
2751
|
-
}
|
|
2752
|
-
jsonResponse(res, 200, { success: true, skillName, action, results });
|
|
2753
|
-
} catch (err) {
|
|
2754
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2755
|
-
}
|
|
2756
|
-
};
|
|
2757
|
-
function findFileSync(dir, filename) {
|
|
2758
|
-
try {
|
|
2759
|
-
const items = readdirSync(dir, { withFileTypes: true });
|
|
2760
|
-
for (const item of items) {
|
|
2761
|
-
const fullPath = join3(dir, item.name);
|
|
2762
|
-
if (item.isDirectory()) {
|
|
2763
|
-
const found = findFileSync(fullPath, filename);
|
|
2764
|
-
if (found) return found;
|
|
2765
|
-
} else if (item.name === filename) {
|
|
2766
|
-
return fullPath;
|
|
2767
|
-
}
|
|
2768
|
-
}
|
|
2769
|
-
} catch {
|
|
2770
|
-
}
|
|
2771
|
-
return null;
|
|
2772
|
-
}
|
|
2773
|
-
function serveStaticFile(res, filePath) {
|
|
2774
|
-
if (!existsSync3(filePath)) {
|
|
2775
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
2776
|
-
res.end("Not Found");
|
|
2777
|
-
return;
|
|
2778
|
-
}
|
|
2779
|
-
const content = readFileSync3(filePath);
|
|
2780
|
-
const ext = extname(filePath);
|
|
2781
|
-
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
2782
|
-
res.writeHead(200, {
|
|
2783
|
-
"Content-Type": mime,
|
|
2784
|
-
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
2785
|
-
"Pragma": "no-cache",
|
|
2786
|
-
"Expires": "0"
|
|
2787
|
-
});
|
|
2788
|
-
res.end(content);
|
|
2789
|
-
}
|
|
2790
|
-
async function handleRequest(req, res) {
|
|
2791
|
-
const method = req.method || "GET";
|
|
2792
|
-
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
2793
|
-
const pathname = url.pathname;
|
|
2794
|
-
const routeHandler = API_ROUTES[method]?.[pathname];
|
|
2795
|
-
if (routeHandler) {
|
|
2796
|
-
try {
|
|
2797
|
-
await routeHandler(req, res, url);
|
|
2798
|
-
} catch (err) {
|
|
2799
|
-
jsonResponse(res, 500, { error: String(err) });
|
|
2800
|
-
}
|
|
2801
|
-
return;
|
|
2802
|
-
}
|
|
2803
|
-
if (pathname.startsWith("/api/")) {
|
|
2804
|
-
jsonResponse(res, 404, { error: `Unknown API endpoint: ${method} ${pathname}` });
|
|
2805
|
-
return;
|
|
2806
|
-
}
|
|
2807
|
-
const filePath = join3(guiDir, pathname === "/" ? "index.html" : pathname);
|
|
2808
|
-
if (filePath.startsWith(guiDir)) {
|
|
2809
|
-
serveStaticFile(res, filePath);
|
|
2810
|
-
} else {
|
|
2811
|
-
res.writeHead(403);
|
|
2812
|
-
res.end("Forbidden");
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2815
|
-
function startGuiServer(port = 18770) {
|
|
2816
|
-
const server = createServer(handleRequest);
|
|
2817
|
-
server.listen(port, () => {
|
|
2818
|
-
console.log(`
|
|
2819
|
-
\u{1F680} SkillMarket GUI started!`);
|
|
2820
|
-
console.log(` Local: http://localhost:${port}`);
|
|
2821
|
-
console.log(`
|
|
2822
|
-
Press Ctrl+C to stop
|
|
2823
|
-
`);
|
|
2824
|
-
});
|
|
2825
|
-
server.on("error", (err) => {
|
|
2826
|
-
if (err.code === "EADDRINUSE") {
|
|
2827
|
-
console.error(`\u274C Port ${port} is already in use. Try: skm gui ${port + 1}`);
|
|
2828
|
-
} else {
|
|
2829
|
-
console.error("\u274C Failed to start GUI server:", err.message);
|
|
2830
|
-
}
|
|
2831
|
-
process.exit(1);
|
|
2832
|
-
});
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
644
|
// src/commands/config.ts
|
|
2836
|
-
import
|
|
2837
|
-
import
|
|
2838
|
-
import
|
|
645
|
+
import path4 from "path";
|
|
646
|
+
import fs4 from "fs-extra";
|
|
647
|
+
import os from "os";
|
|
2839
648
|
var CONFIG_DEFINITIONS = [
|
|
2840
649
|
{
|
|
2841
650
|
key: "npmScope",
|
|
@@ -2869,13 +678,13 @@ var CONFIG_DEFINITIONS = [
|
|
|
2869
678
|
}
|
|
2870
679
|
];
|
|
2871
680
|
function getConfigPath() {
|
|
2872
|
-
return
|
|
681
|
+
return path4.join(os.homedir(), ".skillmarket", "config.json");
|
|
2873
682
|
}
|
|
2874
683
|
async function readConfigFile() {
|
|
2875
684
|
try {
|
|
2876
685
|
const configPath = getConfigPath();
|
|
2877
|
-
if (await
|
|
2878
|
-
const data = await
|
|
686
|
+
if (await fs4.pathExists(configPath)) {
|
|
687
|
+
const data = await fs4.readJson(configPath);
|
|
2879
688
|
const valid = {};
|
|
2880
689
|
for (const def of CONFIG_DEFINITIONS) {
|
|
2881
690
|
if (data[def.key] !== void 0) {
|
|
@@ -2890,11 +699,11 @@ async function readConfigFile() {
|
|
|
2890
699
|
}
|
|
2891
700
|
async function writeConfigFile(updates) {
|
|
2892
701
|
const configPath = getConfigPath();
|
|
2893
|
-
await
|
|
702
|
+
await fs4.ensureDir(path4.dirname(configPath));
|
|
2894
703
|
let existing = {};
|
|
2895
704
|
try {
|
|
2896
|
-
if (await
|
|
2897
|
-
existing = await
|
|
705
|
+
if (await fs4.pathExists(configPath)) {
|
|
706
|
+
existing = await fs4.readJson(configPath);
|
|
2898
707
|
}
|
|
2899
708
|
} catch {
|
|
2900
709
|
}
|
|
@@ -2904,29 +713,29 @@ async function writeConfigFile(updates) {
|
|
|
2904
713
|
delete merged[key];
|
|
2905
714
|
}
|
|
2906
715
|
}
|
|
2907
|
-
await
|
|
716
|
+
await fs4.writeJson(configPath, merged, { spaces: 2 });
|
|
2908
717
|
return merged;
|
|
2909
718
|
}
|
|
2910
719
|
async function removeConfigKeys(keys) {
|
|
2911
720
|
const configPath = getConfigPath();
|
|
2912
|
-
if (!await
|
|
721
|
+
if (!await fs4.pathExists(configPath)) return;
|
|
2913
722
|
try {
|
|
2914
|
-
const existing = await
|
|
723
|
+
const existing = await fs4.readJson(configPath);
|
|
2915
724
|
for (const key of keys) {
|
|
2916
725
|
delete existing[key];
|
|
2917
726
|
}
|
|
2918
|
-
await
|
|
727
|
+
await fs4.writeJson(configPath, existing, { spaces: 2 });
|
|
2919
728
|
} catch {
|
|
2920
729
|
}
|
|
2921
730
|
}
|
|
2922
731
|
async function removeConfigFile() {
|
|
2923
732
|
const configPath = getConfigPath();
|
|
2924
|
-
if (await
|
|
2925
|
-
await
|
|
733
|
+
if (await fs4.pathExists(configPath)) {
|
|
734
|
+
await fs4.remove(configPath);
|
|
2926
735
|
}
|
|
2927
736
|
}
|
|
2928
737
|
async function getAllConfig() {
|
|
2929
|
-
const
|
|
738
|
+
const fileConfig = await readConfigFile();
|
|
2930
739
|
return CONFIG_DEFINITIONS.map((def) => {
|
|
2931
740
|
const envValue = process.env[def.envVar];
|
|
2932
741
|
if (envValue !== void 0) {
|
|
@@ -2936,7 +745,7 @@ async function getAllConfig() {
|
|
|
2936
745
|
source: "env"
|
|
2937
746
|
};
|
|
2938
747
|
}
|
|
2939
|
-
const fileValue =
|
|
748
|
+
const fileValue = fileConfig[def.key];
|
|
2940
749
|
if (fileValue !== void 0) {
|
|
2941
750
|
return {
|
|
2942
751
|
...def,
|
|
@@ -3049,9 +858,9 @@ async function resetConfig(key, all = false) {
|
|
|
3049
858
|
}
|
|
3050
859
|
|
|
3051
860
|
// src/cli.ts
|
|
3052
|
-
var
|
|
3053
|
-
var
|
|
3054
|
-
var packageJson = JSON.parse(
|
|
861
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
862
|
+
var __dirname2 = dirname(__filename2);
|
|
863
|
+
var packageJson = JSON.parse(readFileSync(resolve(__dirname2, "../package.json"), "utf-8"));
|
|
3055
864
|
var VERSION = packageJson.version || "1.3.1";
|
|
3056
865
|
var program = new Command();
|
|
3057
866
|
program.name("skm").description("SkillMarket - Cross-platform skill manager for AI coding tools").version(VERSION);
|
|
@@ -3418,7 +1227,7 @@ if (!hasArgs) {
|
|
|
3418
1227
|
const port = 18770;
|
|
3419
1228
|
setTimeout(() => {
|
|
3420
1229
|
try {
|
|
3421
|
-
|
|
1230
|
+
execSync(`start http://localhost:${port}`, { timeout: 5e3 });
|
|
3422
1231
|
} catch {
|
|
3423
1232
|
}
|
|
3424
1233
|
}, 1500);
|