skill-atlas-cli 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js ADDED
@@ -0,0 +1,1128 @@
1
+ import fs, { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path, { join } from "node:path";
3
+ import os, { homedir } from "node:os";
4
+ import semver from "semver";
5
+ import * as p from "@clack/prompts";
6
+ import chalk from "chalk";
7
+ import path$1, { basename, dirname, join as join$1, normalize, relative, resolve, sep } from "path";
8
+ import { existsSync as existsSync$1, writeFileSync as writeFileSync$1 } from "fs";
9
+ import os$1, { homedir as homedir$1, platform, tmpdir } from "os";
10
+ import * as readline from "readline";
11
+ import { Writable } from "stream";
12
+ import { cp, lstat, mkdir, readdir, readlink, realpath, rename, rm, stat, symlink, unlink, writeFile } from "fs/promises";
13
+ import { execSync } from "child_process";
14
+ import { info } from "console";
15
+ import { createConsola } from "consola";
16
+ //#region \0rolldown/runtime.js
17
+ var __create = Object.create;
18
+ var __defProp = Object.defineProperty;
19
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
20
+ var __getOwnPropNames = Object.getOwnPropertyNames;
21
+ var __getProtoOf = Object.getPrototypeOf;
22
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
23
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
24
+ var __copyProps = (to, from, except, desc) => {
25
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
26
+ key = keys[i];
27
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
28
+ get: ((k) => from[k]).bind(null, key),
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
+ });
31
+ }
32
+ return to;
33
+ };
34
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
35
+ value: mod,
36
+ enumerable: true
37
+ }) : target, mod));
38
+ //#endregion
39
+ //#region src/core/update-notifier.ts
40
+ /**
41
+ * npm 包更新检查:有新版本时提示无需重装,直接 npm update 即可
42
+ */
43
+ const CACHE_DIR = process.env.XDG_CONFIG_HOME || join(homedir(), ".config", "skill-atlas");
44
+ const CACHE_FILE = join(CACHE_DIR, "update-check.json");
45
+ const CHECK_INTERVAL = 1e3 * 60 * 60 * 24;
46
+ async function fetchLatestVersion(pkgName) {
47
+ return (await (await fetch(`https://registry.npmjs.org/-/package/${pkgName}/dist-tags`)).json()).latest || "0.0.0";
48
+ }
49
+ function getLastCheck() {
50
+ try {
51
+ if (!existsSync(CACHE_FILE)) return void 0;
52
+ return JSON.parse(readFileSync(CACHE_FILE, "utf8")).lastCheck;
53
+ } catch {
54
+ return;
55
+ }
56
+ }
57
+ function saveLastCheck() {
58
+ try {
59
+ if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });
60
+ writeFileSync(CACHE_FILE, JSON.stringify({ lastCheck: Date.now() }) + "\n");
61
+ } catch {}
62
+ }
63
+ /**
64
+ * 检查更新,有新版本时输出提示(无需重装,直接 update)
65
+ */
66
+ function checkForUpdate(pkg) {
67
+ if (!process.stdout.isTTY) return;
68
+ const lastCheck = getLastCheck();
69
+ if (lastCheck && Date.now() - lastCheck < CHECK_INTERVAL) return;
70
+ fetchLatestVersion(pkg.name).then((latest) => {
71
+ saveLastCheck();
72
+ if (semver.gt(latest, pkg.version)) console.error(`\n Update available: ${pkg.version} → ${latest}\n No need to reinstall. Run: npm update -g ${pkg.name}\n`);
73
+ }).catch(() => {});
74
+ }
75
+ //#endregion
76
+ //#region src/core/config.ts
77
+ var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
78
+ let p = process || {}, argv = p.argv || [], env = p.env || {};
79
+ let isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
80
+ let formatter = (open, close, replace = open) => (input) => {
81
+ let string = "" + input, index = string.indexOf(close, open.length);
82
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
83
+ };
84
+ let replaceClose = (string, close, replace, index) => {
85
+ let result = "", cursor = 0;
86
+ do {
87
+ result += string.substring(cursor, index) + replace;
88
+ cursor = index + close.length;
89
+ index = string.indexOf(close, cursor);
90
+ } while (~index);
91
+ return result + string.substring(cursor);
92
+ };
93
+ let createColors = (enabled = isColorSupported) => {
94
+ let f = enabled ? formatter : () => String;
95
+ return {
96
+ isColorSupported: enabled,
97
+ reset: f("\x1B[0m", "\x1B[0m"),
98
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
99
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
100
+ italic: f("\x1B[3m", "\x1B[23m"),
101
+ underline: f("\x1B[4m", "\x1B[24m"),
102
+ inverse: f("\x1B[7m", "\x1B[27m"),
103
+ hidden: f("\x1B[8m", "\x1B[28m"),
104
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
105
+ black: f("\x1B[30m", "\x1B[39m"),
106
+ red: f("\x1B[31m", "\x1B[39m"),
107
+ green: f("\x1B[32m", "\x1B[39m"),
108
+ yellow: f("\x1B[33m", "\x1B[39m"),
109
+ blue: f("\x1B[34m", "\x1B[39m"),
110
+ magenta: f("\x1B[35m", "\x1B[39m"),
111
+ cyan: f("\x1B[36m", "\x1B[39m"),
112
+ white: f("\x1B[37m", "\x1B[39m"),
113
+ gray: f("\x1B[90m", "\x1B[39m"),
114
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
115
+ bgRed: f("\x1B[41m", "\x1B[49m"),
116
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
117
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
118
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
119
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
120
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
121
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
122
+ blackBright: f("\x1B[90m", "\x1B[39m"),
123
+ redBright: f("\x1B[91m", "\x1B[39m"),
124
+ greenBright: f("\x1B[92m", "\x1B[39m"),
125
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
126
+ blueBright: f("\x1B[94m", "\x1B[39m"),
127
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
128
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
129
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
130
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
131
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
132
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
133
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
134
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
135
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
136
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
137
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
138
+ };
139
+ };
140
+ module.exports = createColors();
141
+ module.exports.createColors = createColors;
142
+ })))(), 1);
143
+ const CONFIG_DIR = path.join(os.homedir(), ".openclawmp");
144
+ const AUTH_FILE = path.join(CONFIG_DIR, "auth.json");
145
+ const CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
146
+ let API_BASE = "https://pre-skillhub.aliyun-inc.com";
147
+ const OPENCLAW_STATE_DIR = process.env.OPENCLAW_STATE_DIR || path.join(os.homedir(), ".openclaw");
148
+ const LOCKFILE = path.join(OPENCLAW_STATE_DIR, "seafood-lock.json");
149
+ const DEVICE_JSON = path.join(OPENCLAW_STATE_DIR, "identity", "device.json");
150
+ const ASSET_TYPES = {
151
+ skill: "skills",
152
+ plugin: "plugins",
153
+ trigger: "triggers",
154
+ channel: "channels",
155
+ experience: "experiences"
156
+ };
157
+ /**
158
+ * Ensure config directory exists
159
+ */
160
+ function ensureConfigDir() {
161
+ if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
162
+ }
163
+ /**
164
+ * Get the install directory for a given asset type
165
+ */
166
+ function installDirForType(type) {
167
+ const subdir = ASSET_TYPES[type];
168
+ if (!subdir) throw new Error(`Unknown asset type: ${type}. Valid types: ${Object.keys(ASSET_TYPES).join(", ")}`);
169
+ return path.join(OPENCLAW_STATE_DIR, subdir);
170
+ }
171
+ /**
172
+ * Get/set API base URL
173
+ */
174
+ function getApiBase() {
175
+ return API_BASE;
176
+ }
177
+ function setApiBase(url) {
178
+ API_BASE = url.replace(/\/+$/, "");
179
+ }
180
+ /**
181
+ * Read auth token (priority: env var > auth.json > credentials.json)
182
+ */
183
+ function getAuthToken() {
184
+ if (process.env.OPENCLAWMP_TOKEN) return process.env.OPENCLAWMP_TOKEN;
185
+ ensureConfigDir();
186
+ if (fs.existsSync(AUTH_FILE)) try {
187
+ const data = JSON.parse(fs.readFileSync(AUTH_FILE, "utf-8"));
188
+ if (data.token) return data.token;
189
+ } catch {}
190
+ if (fs.existsSync(CREDENTIALS_FILE)) try {
191
+ const data = JSON.parse(fs.readFileSync(CREDENTIALS_FILE, "utf-8"));
192
+ if (data.api_key) return data.api_key;
193
+ } catch {}
194
+ return null;
195
+ }
196
+ /**
197
+ * Save auth token
198
+ */
199
+ function saveAuthToken(token, extra = {}) {
200
+ ensureConfigDir();
201
+ const data = {
202
+ token,
203
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
204
+ ...extra
205
+ };
206
+ fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2) + "\n");
207
+ }
208
+ /**
209
+ * Read the OpenClaw device ID
210
+ */
211
+ function getDeviceId() {
212
+ if (!fs.existsSync(DEVICE_JSON)) return null;
213
+ try {
214
+ return JSON.parse(fs.readFileSync(DEVICE_JSON, "utf-8")).deviceId || null;
215
+ } catch {
216
+ return null;
217
+ }
218
+ }
219
+ /**
220
+ * Initialize lockfile if it doesn't exist
221
+ */
222
+ function initLockfile() {
223
+ if (!fs.existsSync(LOCKFILE)) {
224
+ const dir = path.dirname(LOCKFILE);
225
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
226
+ fs.writeFileSync(LOCKFILE, JSON.stringify({
227
+ version: 1,
228
+ installed: {}
229
+ }, null, 2) + "\n");
230
+ }
231
+ }
232
+ /**
233
+ * Read the lockfile
234
+ */
235
+ function readLockfile() {
236
+ initLockfile();
237
+ try {
238
+ return JSON.parse(fs.readFileSync(LOCKFILE, "utf-8"));
239
+ } catch {
240
+ return {
241
+ version: 1,
242
+ installed: {}
243
+ };
244
+ }
245
+ }
246
+ /**
247
+ * Update a lockfile entry
248
+ */
249
+ function updateLockfile(key, version, location) {
250
+ const lock = readLockfile();
251
+ lock.installed[key] = {
252
+ version,
253
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
254
+ location
255
+ };
256
+ fs.writeFileSync(LOCKFILE, JSON.stringify(lock, null, 2) + "\n");
257
+ }
258
+ /**
259
+ * Remove a lockfile entry
260
+ */
261
+ function removeLockfile(key) {
262
+ const lock = readLockfile();
263
+ delete lock.installed[key];
264
+ fs.writeFileSync(LOCKFILE, JSON.stringify(lock, null, 2) + "\n");
265
+ }
266
+ var config_default = {
267
+ CONFIG_DIR,
268
+ OPENCLAW_STATE_DIR,
269
+ LOCKFILE,
270
+ DEVICE_JSON,
271
+ ASSET_TYPES,
272
+ ensureConfigDir,
273
+ installDirForType,
274
+ getApiBase,
275
+ setApiBase,
276
+ getAuthToken,
277
+ saveAuthToken,
278
+ getDeviceId,
279
+ initLockfile,
280
+ readLockfile,
281
+ updateLockfile,
282
+ removeLockfile
283
+ };
284
+ //#endregion
285
+ //#region src/core/api.ts
286
+ /**
287
+ * Make a GET request to the API
288
+ * @param {string} apiPath - API path (e.g., '/api/assets')
289
+ * @param {object} [params] - Query parameters
290
+ * @returns {Promise<object>} Parsed JSON response
291
+ */
292
+ async function get(apiPath, params = {}) {
293
+ const url = new URL(apiPath, config_default.getApiBase());
294
+ for (const [k, v] of Object.entries(params)) if (v !== void 0 && v !== null && v !== "") url.searchParams.set(k, String(v));
295
+ const res = await fetch(url.toString(), {});
296
+ if (!res.ok) {
297
+ const body = await res.text().catch(() => "");
298
+ throw new Error(`API error ${res.status}: ${body || res.statusText}`);
299
+ }
300
+ return res.json();
301
+ }
302
+ function normalizeSearchSkillsResponse(result) {
303
+ const payload = result && typeof result === "object" && "data" in result ? result.data : result;
304
+ const normalized = payload && typeof payload === "object" ? payload : {};
305
+ return {
306
+ items: Array.isArray(normalized.items) ? normalized.items : [],
307
+ page: typeof normalized.page === "number" ? normalized.page : 1,
308
+ pageSize: typeof normalized.pageSize === "number" ? normalized.pageSize : 20,
309
+ total: typeof normalized.total === "number" ? normalized.total : 0
310
+ };
311
+ }
312
+ async function searchSkills(params = {}) {
313
+ return normalizeSearchSkillsResponse(await get("/api/v1/skills", {
314
+ page: 1,
315
+ pageSize: 20,
316
+ ...params
317
+ }));
318
+ }
319
+ async function getSkillDetail(slug) {
320
+ try {
321
+ return await get(`/api/v1/skills/${slug}`);
322
+ } catch (err) {
323
+ err instanceof Error ? err.message : String(err);
324
+ return null;
325
+ }
326
+ }
327
+ async function findAsset(slug) {
328
+ return await getSkillDetail(slug);
329
+ }
330
+ async function downloadSkillPackage(slug, version) {
331
+ const url = new URL("/api/v1/download", config_default.getApiBase());
332
+ url.searchParams.set("slug", slug);
333
+ url.searchParams.set("version", version);
334
+ const res = await fetch(url.toString(), {});
335
+ if (!res.ok) {
336
+ const body = await res.text().catch(() => "");
337
+ throw new Error(`下载失败 ${res.status}: ${body || res.statusText}。`);
338
+ }
339
+ const arrayBuffer = await res.arrayBuffer();
340
+ return Buffer.from(arrayBuffer);
341
+ }
342
+ //#endregion
343
+ //#region node_modules/.pnpm/xdg-basedir@5.1.0/node_modules/xdg-basedir/index.js
344
+ const homeDirectory = os$1.homedir();
345
+ const { env } = process;
346
+ const xdgData = env.XDG_DATA_HOME || (homeDirectory ? path$1.join(homeDirectory, ".local", "share") : void 0);
347
+ const xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path$1.join(homeDirectory, ".config") : void 0);
348
+ env.XDG_STATE_HOME || homeDirectory && path$1.join(homeDirectory, ".local", "state");
349
+ env.XDG_CACHE_HOME || homeDirectory && path$1.join(homeDirectory, ".cache");
350
+ env.XDG_RUNTIME_DIR;
351
+ const xdgDataDirectories = (env.XDG_DATA_DIRS || "/usr/local/share/:/usr/share/").split(":");
352
+ if (xdgData) xdgDataDirectories.unshift(xdgData);
353
+ const xdgConfigDirectories = (env.XDG_CONFIG_DIRS || "/etc/xdg").split(":");
354
+ if (xdgConfig) xdgConfigDirectories.unshift(xdgConfig);
355
+ //#endregion
356
+ //#region src/core/agents.ts
357
+ const home = homedir$1();
358
+ const configHome = xdgConfig ?? join$1(home, ".config");
359
+ const codexHome = process.env.CODEX_HOME?.trim() || join$1(home, ".codex");
360
+ const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join$1(home, ".claude");
361
+ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync$1) {
362
+ if (pathExists(join$1(homeDir, ".openclaw"))) return join$1(homeDir, ".openclaw/skills");
363
+ if (pathExists(join$1(homeDir, ".clawdbot"))) return join$1(homeDir, ".clawdbot/skills");
364
+ if (pathExists(join$1(homeDir, ".moltbot"))) return join$1(homeDir, ".moltbot/skills");
365
+ return join$1(homeDir, ".openclaw/skills");
366
+ }
367
+ const agents = {
368
+ "claude-code": {
369
+ name: "claude-code",
370
+ displayName: "Claude Code",
371
+ skillsDir: ".claude/skills",
372
+ globalSkillsDir: join$1(claudeHome, "skills"),
373
+ detectInstalled: async () => {
374
+ return existsSync$1(claudeHome);
375
+ }
376
+ },
377
+ openclaw: {
378
+ name: "openclaw",
379
+ displayName: "OpenClaw",
380
+ skillsDir: "skills",
381
+ globalSkillsDir: getOpenClawGlobalSkillsDir(),
382
+ detectInstalled: async () => {
383
+ return existsSync$1(join$1(home, ".openclaw")) || existsSync$1(join$1(home, ".clawdbot")) || existsSync$1(join$1(home, ".moltbot"));
384
+ }
385
+ },
386
+ cline: {
387
+ name: "cline",
388
+ displayName: "Cline",
389
+ skillsDir: ".agents/skills",
390
+ globalSkillsDir: join$1(home, ".agents", "skills"),
391
+ detectInstalled: async () => {
392
+ return existsSync$1(join$1(home, ".cline"));
393
+ }
394
+ },
395
+ antigravity: {
396
+ name: "antigravity",
397
+ displayName: "Antigravity",
398
+ skillsDir: ".agent/skills",
399
+ globalSkillsDir: join$1(home, ".gemini/antigravity/skills"),
400
+ detectInstalled: async () => {
401
+ return existsSync$1(join$1(home, ".gemini/antigravity"));
402
+ }
403
+ },
404
+ codex: {
405
+ name: "codex",
406
+ displayName: "Codex",
407
+ skillsDir: ".agents/skills",
408
+ globalSkillsDir: join$1(codexHome, "skills"),
409
+ detectInstalled: async () => {
410
+ return existsSync$1(codexHome) || existsSync$1("/etc/codex");
411
+ }
412
+ },
413
+ cursor: {
414
+ name: "cursor",
415
+ displayName: "Cursor",
416
+ skillsDir: ".agents/skills",
417
+ globalSkillsDir: join$1(home, ".cursor/skills"),
418
+ detectInstalled: async () => {
419
+ return existsSync$1(join$1(home, ".cursor"));
420
+ }
421
+ },
422
+ "gemini-cli": {
423
+ name: "gemini-cli",
424
+ displayName: "Gemini CLI",
425
+ skillsDir: ".agents/skills",
426
+ globalSkillsDir: join$1(home, ".gemini/skills"),
427
+ detectInstalled: async () => {
428
+ return existsSync$1(join$1(home, ".gemini"));
429
+ }
430
+ },
431
+ "github-copilot": {
432
+ name: "github-copilot",
433
+ displayName: "GitHub Copilot",
434
+ skillsDir: ".agents/skills",
435
+ globalSkillsDir: join$1(home, ".copilot/skills"),
436
+ detectInstalled: async () => {
437
+ return existsSync$1(join$1(home, ".copilot"));
438
+ }
439
+ },
440
+ "iflow-cli": {
441
+ name: "iflow-cli",
442
+ displayName: "iFlow CLI",
443
+ skillsDir: ".iflow/skills",
444
+ globalSkillsDir: join$1(home, ".iflow/skills"),
445
+ detectInstalled: async () => {
446
+ return existsSync$1(join$1(home, ".iflow"));
447
+ }
448
+ },
449
+ "kimi-cli": {
450
+ name: "kimi-cli",
451
+ displayName: "Kimi Code CLI",
452
+ skillsDir: ".agents/skills",
453
+ globalSkillsDir: join$1(home, ".config/agents/skills"),
454
+ detectInstalled: async () => {
455
+ return existsSync$1(join$1(home, ".kimi"));
456
+ }
457
+ },
458
+ "kiro-cli": {
459
+ name: "kiro-cli",
460
+ displayName: "Kiro CLI",
461
+ skillsDir: ".kiro/skills",
462
+ globalSkillsDir: join$1(home, ".kiro/skills"),
463
+ detectInstalled: async () => {
464
+ return existsSync$1(join$1(home, ".kiro"));
465
+ }
466
+ },
467
+ opencode: {
468
+ name: "opencode",
469
+ displayName: "OpenCode",
470
+ skillsDir: ".agents/skills",
471
+ globalSkillsDir: join$1(configHome, "opencode/skills"),
472
+ detectInstalled: async () => {
473
+ return existsSync$1(join$1(configHome, "opencode"));
474
+ }
475
+ },
476
+ qoder: {
477
+ name: "qoder",
478
+ displayName: "Qoder",
479
+ skillsDir: ".qoder/skills",
480
+ globalSkillsDir: join$1(home, ".qoder/skills"),
481
+ detectInstalled: async () => {
482
+ return existsSync$1(join$1(home, ".qoder"));
483
+ }
484
+ },
485
+ "qwen-code": {
486
+ name: "qwen-code",
487
+ displayName: "Qwen Code",
488
+ skillsDir: ".qwen/skills",
489
+ globalSkillsDir: join$1(home, ".qwen/skills"),
490
+ detectInstalled: async () => {
491
+ return existsSync$1(join$1(home, ".qwen"));
492
+ }
493
+ },
494
+ trae: {
495
+ name: "trae",
496
+ displayName: "Trae",
497
+ skillsDir: ".trae/skills",
498
+ globalSkillsDir: join$1(home, ".trae/skills"),
499
+ detectInstalled: async () => {
500
+ return existsSync$1(join$1(home, ".trae"));
501
+ }
502
+ },
503
+ "trae-cn": {
504
+ name: "trae-cn",
505
+ displayName: "Trae CN",
506
+ skillsDir: ".trae/skills",
507
+ globalSkillsDir: join$1(home, ".trae-cn/skills"),
508
+ detectInstalled: async () => {
509
+ return existsSync$1(join$1(home, ".trae-cn"));
510
+ }
511
+ },
512
+ universal: {
513
+ name: "universal",
514
+ displayName: "Universal",
515
+ skillsDir: ".agents/skills",
516
+ globalSkillsDir: join$1(configHome, "agents/skills"),
517
+ showInUniversalList: false,
518
+ detectInstalled: async () => false
519
+ }
520
+ };
521
+ //#endregion
522
+ //#region src/core/search-multiselect.ts
523
+ const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
524
+ callback();
525
+ } });
526
+ const S_STEP_ACTIVE = import_picocolors.default.green("◆");
527
+ const S_STEP_CANCEL = import_picocolors.default.red("■");
528
+ const S_STEP_SUBMIT = import_picocolors.default.green("◇");
529
+ const S_RADIO_ACTIVE = import_picocolors.default.green("●");
530
+ const S_RADIO_INACTIVE = import_picocolors.default.dim("○");
531
+ import_picocolors.default.green("✓");
532
+ const S_BULLET = import_picocolors.default.green("•");
533
+ const S_BAR = import_picocolors.default.dim("│");
534
+ const S_BAR_H = import_picocolors.default.dim("─");
535
+ const cancelSymbol = Symbol("cancel");
536
+ /**
537
+ * Interactive search multiselect prompt.
538
+ * Allows users to filter a long list by typing and select multiple items.
539
+ * Optionally supports a "locked" section that displays always-selected items.
540
+ */
541
+ async function searchMultiselect(options) {
542
+ const { message, items, maxVisible = 8, initialSelected = [], required = false, lockedSection, leadingSpacer = 0 } = options;
543
+ return new Promise((resolve) => {
544
+ const rl = readline.createInterface({
545
+ input: process.stdin,
546
+ output: silentOutput,
547
+ terminal: !!process.stdin.isTTY
548
+ });
549
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
550
+ readline.emitKeypressEvents(process.stdin, rl);
551
+ let query = "";
552
+ let cursor = 0;
553
+ const selected = new Set(initialSelected);
554
+ let lastRenderHeight = 0;
555
+ const lockedValues = lockedSection ? lockedSection.items.map((i) => i.value) : [];
556
+ const filter = (item, q) => {
557
+ if (!q) return true;
558
+ const lowerQ = q.toLowerCase();
559
+ return item.label.toLowerCase().includes(lowerQ) || String(item.value).toLowerCase().includes(lowerQ);
560
+ };
561
+ const getFiltered = () => {
562
+ return items.filter((item) => filter(item, query));
563
+ };
564
+ const out = process.stderr.isTTY ? process.stderr : process.stdout;
565
+ const clearRender = () => {
566
+ if (lastRenderHeight > 0 && out.isTTY) for (let i = 0; i < lastRenderHeight; i++) out.write("\x1B[1A\x1B[2K");
567
+ };
568
+ const render = (state = "active") => {
569
+ clearRender();
570
+ const lines = [];
571
+ const filtered = getFiltered();
572
+ const icon = state === "active" ? S_STEP_ACTIVE : state === "cancel" ? S_STEP_CANCEL : S_STEP_SUBMIT;
573
+ for (let i = 0; i < leadingSpacer; i++) lines.push(`${S_BAR}`);
574
+ lines.push(`${icon} ${import_picocolors.default.bold(message)}`);
575
+ if (state === "active") {
576
+ if (lockedSection && lockedSection.items.length > 0) {
577
+ lines.push(`${S_BAR}`);
578
+ const lockedTitle = `${import_picocolors.default.bold(lockedSection.title)} ${import_picocolors.default.dim("── always included")}`;
579
+ lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${lockedTitle} ${S_BAR_H.repeat(12)}`);
580
+ for (const item of lockedSection.items) lines.push(`${S_BAR} ${S_BULLET} ${import_picocolors.default.bold(item.label)}`);
581
+ lines.push(`${S_BAR}`);
582
+ lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Additional agents")} ${S_BAR_H.repeat(29)}`);
583
+ }
584
+ const searchLine = `${S_BAR} ${import_picocolors.default.dim("Search:")} ${query}${import_picocolors.default.inverse(" ")}`;
585
+ lines.push(searchLine);
586
+ lines.push(`${S_BAR} ${import_picocolors.default.dim("↑↓ move, space select, enter confirm")}`);
587
+ lines.push(`${S_BAR}`);
588
+ const visibleStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), filtered.length - maxVisible));
589
+ const visibleEnd = Math.min(filtered.length, visibleStart + maxVisible);
590
+ const visibleItems = filtered.slice(visibleStart, visibleEnd);
591
+ if (filtered.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("No matches found")}`);
592
+ else {
593
+ for (let i = 0; i < visibleItems.length; i++) {
594
+ const item = visibleItems[i];
595
+ const actualIndex = visibleStart + i;
596
+ const isSelected = selected.has(item.value);
597
+ const isCursor = actualIndex === cursor;
598
+ const radio = isSelected ? S_RADIO_ACTIVE : S_RADIO_INACTIVE;
599
+ const label = isCursor ? import_picocolors.default.underline(item.label) : item.label;
600
+ const hint = item.hint ? import_picocolors.default.dim(` (${item.hint})`) : "";
601
+ const prefix = isCursor ? import_picocolors.default.cyan("❯") : " ";
602
+ lines.push(`${S_BAR} ${prefix} ${radio} ${label}${hint}`);
603
+ }
604
+ const hiddenBefore = visibleStart;
605
+ const hiddenAfter = filtered.length - visibleEnd;
606
+ if (hiddenBefore > 0 || hiddenAfter > 0) {
607
+ const parts = [];
608
+ if (hiddenBefore > 0) parts.push(`↑ ${hiddenBefore} more`);
609
+ if (hiddenAfter > 0) parts.push(`↓ ${hiddenAfter} more`);
610
+ lines.push(`${S_BAR} ${import_picocolors.default.dim(parts.join(" "))}`);
611
+ }
612
+ }
613
+ lines.push(`${S_BAR}`);
614
+ const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
615
+ if (allSelectedLabels.length === 0) lines.push(`${S_BAR} ${import_picocolors.default.dim("Selected: (none)")}`);
616
+ else {
617
+ const summary = allSelectedLabels.length <= 3 ? allSelectedLabels.join(", ") : `${allSelectedLabels.slice(0, 3).join(", ")} +${allSelectedLabels.length - 3} more`;
618
+ lines.push(`${S_BAR} ${import_picocolors.default.green("Selected:")} ${summary}`);
619
+ }
620
+ lines.push(`${import_picocolors.default.dim("└")}`);
621
+ } else if (state === "submit") {
622
+ const allSelectedLabels = [...lockedSection ? lockedSection.items.map((i) => i.label) : [], ...items.filter((item) => selected.has(item.value)).map((item) => item.label)];
623
+ lines.push(`${S_BAR} ${import_picocolors.default.dim(allSelectedLabels.join(", "))}`);
624
+ } else if (state === "cancel") lines.push(`${S_BAR} ${import_picocolors.default.strikethrough(import_picocolors.default.dim("Cancelled"))}`);
625
+ out.write(lines.join("\n") + "\n");
626
+ lastRenderHeight = lines.length;
627
+ };
628
+ const cleanup = () => {
629
+ process.stdin.removeListener("keypress", keypressHandler);
630
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
631
+ rl.close();
632
+ };
633
+ const submit = () => {
634
+ if (required && selected.size === 0 && lockedValues.length === 0) return;
635
+ render("submit");
636
+ cleanup();
637
+ resolve([...lockedValues, ...Array.from(selected)]);
638
+ };
639
+ const cancel = () => {
640
+ render("cancel");
641
+ cleanup();
642
+ resolve(cancelSymbol);
643
+ };
644
+ const keypressHandler = (_str, key) => {
645
+ if (!key) return;
646
+ const filtered = getFiltered();
647
+ if (key.name === "return") {
648
+ submit();
649
+ return;
650
+ }
651
+ if (key.name === "escape" || key.ctrl && key.name === "c") {
652
+ cancel();
653
+ return;
654
+ }
655
+ if (key.name === "up") {
656
+ cursor = Math.max(0, cursor - 1);
657
+ render();
658
+ return;
659
+ }
660
+ if (key.name === "down") {
661
+ cursor = Math.min(filtered.length - 1, cursor + 1);
662
+ render();
663
+ return;
664
+ }
665
+ if (key.name === "space") {
666
+ const item = filtered[cursor];
667
+ if (item) if (selected.has(item.value)) selected.delete(item.value);
668
+ else selected.add(item.value);
669
+ render();
670
+ return;
671
+ }
672
+ if (key.name === "backspace") {
673
+ query = query.slice(0, -1);
674
+ cursor = 0;
675
+ render();
676
+ return;
677
+ }
678
+ if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
679
+ query += key.sequence;
680
+ cursor = 0;
681
+ render();
682
+ return;
683
+ }
684
+ };
685
+ process.stdin.on("keypress", keypressHandler);
686
+ render();
687
+ });
688
+ }
689
+ //#endregion
690
+ //#region src/core/constants.ts
691
+ const AGENTS_DIR = ".agents";
692
+ const SKILLS_SUBDIR = "skills";
693
+ //#endregion
694
+ //#region src/core/installer.ts
695
+ /**
696
+ * Validates that a path is within an expected base directory
697
+ * @param basePath - The expected base directory
698
+ * @param targetPath - The path to validate
699
+ * @returns true if targetPath is within basePath
700
+ */
701
+ function isPathSafe(basePath, targetPath) {
702
+ const normalizedBase = normalize(resolve(basePath));
703
+ const normalizedTarget = normalize(resolve(targetPath));
704
+ return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
705
+ }
706
+ async function resolveParentSymlinks(path) {
707
+ const resolved = resolve(path);
708
+ const dir = dirname(resolved);
709
+ const base = basename(resolved);
710
+ try {
711
+ return join$1(await realpath(dir), base);
712
+ } catch {
713
+ return resolved;
714
+ }
715
+ }
716
+ function resolveSymlinkTarget(linkPath, linkTarget) {
717
+ return resolve(dirname(linkPath), linkTarget);
718
+ }
719
+ /**
720
+ * Creates a symlink, handling cross-platform differences
721
+ * Returns true if symlink was created, false if fallback to copy is needed
722
+ */
723
+ async function createSymlink(target, linkPath) {
724
+ try {
725
+ const resolvedTarget = resolve(target);
726
+ const resolvedLinkPath = resolve(linkPath);
727
+ const [realTarget, realLinkPath] = await Promise.all([realpath(resolvedTarget).catch(() => resolvedTarget), realpath(resolvedLinkPath).catch(() => resolvedLinkPath)]);
728
+ if (realTarget === realLinkPath) return true;
729
+ if (await resolveParentSymlinks(target) === await resolveParentSymlinks(linkPath)) return true;
730
+ try {
731
+ if ((await lstat(linkPath)).isSymbolicLink()) {
732
+ if (resolveSymlinkTarget(linkPath, await readlink(linkPath)) === resolvedTarget) return true;
733
+ await rm(linkPath);
734
+ } else await rm(linkPath, { recursive: true });
735
+ } catch (err) {
736
+ if (err && typeof err === "object" && "code" in err && err.code === "ELOOP") try {
737
+ await rm(linkPath, { force: true });
738
+ } catch {}
739
+ }
740
+ const linkDir = dirname(linkPath);
741
+ await mkdir(linkDir, { recursive: true });
742
+ await symlink(relative(await resolveParentSymlinks(linkDir), target), linkPath, platform() === "win32" ? "junction" : void 0);
743
+ return true;
744
+ } catch {
745
+ return false;
746
+ }
747
+ }
748
+ /**
749
+ * 清理并创建目录
750
+ * @param path - 目录路径
751
+ * @returns 创建成功返回 true
752
+ */
753
+ async function cleanAndCreateDirectory(path) {
754
+ try {
755
+ await rm(path, {
756
+ recursive: true,
757
+ force: true
758
+ });
759
+ } catch {}
760
+ await mkdir(path, { recursive: true });
761
+ }
762
+ /**
763
+ * 下载并解压包
764
+ * @param asset - 包信息
765
+ * @param targetDir - 解压目标目录
766
+ * @returns 解压成功返回 true
767
+ */
768
+ async function downloadAndExtractPackage(asset, targetDir) {
769
+ let hasPackage = false;
770
+ const version = asset.currentVersion?.version ?? "";
771
+ if (!asset.slug || !version) {
772
+ generateSkillMd(asset, targetDir);
773
+ return false;
774
+ }
775
+ const pkgBuffer = await downloadSkillPackage(asset.slug, version);
776
+ if (pkgBuffer && pkgBuffer.length > 0) hasPackage = await extractPackage(pkgBuffer, targetDir);
777
+ if (!hasPackage) {
778
+ info("No package available, generating from metadata...");
779
+ generateSkillMd(asset, targetDir);
780
+ console.log(" Generated: SKILL.md from metadata");
781
+ }
782
+ return hasPackage;
783
+ }
784
+ function getAgentBaseDir(agentType, global, cwd) {
785
+ const agent = agents[agentType];
786
+ const baseDir = global ? homedir$1() : cwd || process.cwd();
787
+ if (global) {
788
+ if (agent.globalSkillsDir === void 0) return join$1(baseDir, agent.skillsDir);
789
+ return agent.globalSkillsDir;
790
+ }
791
+ return join$1(baseDir, agent.skillsDir);
792
+ }
793
+ async function installWellKnownSkillForAgent(skill, agentType, options = {}) {
794
+ const agent = agents[agentType];
795
+ const isGlobal = options.global ?? false;
796
+ const cwd = options.cwd || process.cwd();
797
+ const installMode = options.mode ?? "symlink";
798
+ if (isGlobal && agent.globalSkillsDir === void 0) return {
799
+ success: false,
800
+ path: "",
801
+ mode: installMode,
802
+ error: `${agent.displayName} does not support global skill installation`
803
+ };
804
+ const skillName = sanitizeName(skill.slug);
805
+ const canonicalBase = getCanonicalSkillsDir(isGlobal, cwd);
806
+ const canonicalDir = join$1(canonicalBase, skillName);
807
+ const agentBase = getAgentBaseDir(agentType, isGlobal, cwd);
808
+ const agentDir = join$1(agentBase, skillName);
809
+ if (!isPathSafe(canonicalBase, canonicalDir)) return {
810
+ success: false,
811
+ path: agentDir,
812
+ mode: installMode,
813
+ error: "Invalid skill name: potential path traversal detected"
814
+ };
815
+ if (!isPathSafe(agentBase, agentDir)) return {
816
+ success: false,
817
+ path: agentDir,
818
+ mode: installMode,
819
+ error: "Invalid skill name: potential path traversal detected"
820
+ };
821
+ async function populateSkillDir(targetDir) {
822
+ await downloadAndExtractPackage(skill, targetDir);
823
+ }
824
+ try {
825
+ if (installMode === "copy") {
826
+ await cleanAndCreateDirectory(agentDir);
827
+ await populateSkillDir(agentDir);
828
+ return {
829
+ success: true,
830
+ path: agentDir,
831
+ mode: "copy"
832
+ };
833
+ }
834
+ await cleanAndCreateDirectory(canonicalDir);
835
+ await populateSkillDir(canonicalDir);
836
+ if (!await createSymlink(canonicalDir, agentDir)) {
837
+ await cleanAndCreateDirectory(agentDir);
838
+ await cp(canonicalDir, agentDir, { recursive: true });
839
+ return {
840
+ success: true,
841
+ path: agentDir,
842
+ canonicalPath: canonicalDir,
843
+ mode: "symlink",
844
+ symlinkFailed: true
845
+ };
846
+ }
847
+ return {
848
+ success: true,
849
+ path: agentDir,
850
+ canonicalPath: canonicalDir,
851
+ mode: "symlink"
852
+ };
853
+ } catch (error) {
854
+ return {
855
+ success: false,
856
+ path: agentDir,
857
+ mode: installMode,
858
+ error: error instanceof Error ? error.message : "Unknown error"
859
+ };
860
+ }
861
+ }
862
+ function sanitizeName(name) {
863
+ return name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").substring(0, 255) || "unnamed-skill";
864
+ }
865
+ function getCanonicalSkillsDir(global, cwd) {
866
+ return join$1(global ? homedir$1() : cwd || process.cwd(), AGENTS_DIR, SKILLS_SUBDIR);
867
+ }
868
+ /**
869
+ * 将包从 buffer 解压到目标目录
870
+ * 接口返回 zip 格式,兼容 tar.gz
871
+ * @param buffer - 待解压的 buffer
872
+ * @param targetDir - 解压目标目录
873
+ * @returns 解压成功返回 true
874
+ */
875
+ async function extractPackage(buffer, targetDir) {
876
+ await mkdir(targetDir, { recursive: true });
877
+ const tmpFile = join$1(tmpdir(), `openclawmp-pkg-${process.pid}-${Date.now()}`);
878
+ await writeFile(tmpFile, buffer);
879
+ try {
880
+ try {
881
+ execSync(`unzip -o -q "${tmpFile}" -d "${targetDir}" 2>/dev/null`, { stdio: "pipe" });
882
+ const entries = await readdir(targetDir);
883
+ const dirs = [];
884
+ for (const e of entries) if ((await stat(join$1(targetDir, e))).isDirectory()) dirs.push(e);
885
+ if (dirs.length === 1 && entries.length === 1) {
886
+ const subdir = join$1(targetDir, dirs[0]);
887
+ for (const f of await readdir(subdir)) await rename(join$1(subdir, f), join$1(targetDir, f));
888
+ await rm(subdir, { recursive: true });
889
+ }
890
+ return true;
891
+ } catch {
892
+ try {
893
+ execSync(`tar xzf "${tmpFile}" -C "${targetDir}" --strip-components=1 2>/dev/null`, { stdio: "pipe" });
894
+ return true;
895
+ } catch {
896
+ try {
897
+ execSync(`tar xzf "${tmpFile}" -C "${targetDir}" 2>/dev/null`, { stdio: "pipe" });
898
+ return true;
899
+ } catch {
900
+ return false;
901
+ }
902
+ }
903
+ }
904
+ } finally {
905
+ try {
906
+ await unlink(tmpFile);
907
+ } catch {}
908
+ }
909
+ }
910
+ function generateSkillMd(asset, targetDir) {
911
+ const tags = (asset.tags || []).join(", ");
912
+ const version = asset.currentVersion ?? asset.version ?? "";
913
+ const description = asset.summary || asset.description || "";
914
+ const content = `---
915
+ name: ${asset.slug || asset.name || ""}
916
+ display-name: ${asset.displayName || ""}
917
+ description: ${description}
918
+ version: ${version}
919
+ author: ${asset.author?.name || ""}
920
+ author-id: ${asset.author?.id || ""}
921
+ tags: ${tags}
922
+ category: ${asset.category || ""}
923
+ ---
924
+
925
+ # ${asset.displayName || asset.slug || asset.name || "Skill"}
926
+
927
+ ${description}
928
+
929
+ ${asset.readme || ""}
930
+ `;
931
+ writeFileSync$1(path$1.join(targetDir, "SKILL.md"), content);
932
+ }
933
+ //#endregion
934
+ //#region src/commands/install.ts
935
+ async function promptForAgents(message, choices, initialValues) {
936
+ return await searchMultiselect({
937
+ message,
938
+ items: choices,
939
+ leadingSpacer: 1,
940
+ initialSelected: initialValues
941
+ });
942
+ }
943
+ function cancelAndExit(message) {
944
+ p.cancel(message);
945
+ process.exit(0);
946
+ }
947
+ function errorAndExit(message, note) {
948
+ p.log.error(message);
949
+ if (note) p.note(note.body, note.title);
950
+ process.exit(1);
951
+ }
952
+ function buildAgentChoices(globalInstall) {
953
+ return Object.entries(agents).map(([key, config]) => ({
954
+ value: key,
955
+ label: config.displayName,
956
+ hint: globalInstall ? config.globalSkillsDir ?? config.skillsDir : config.skillsDir
957
+ }));
958
+ }
959
+ function getDefaultAgents(choices) {
960
+ return ["claude-code", "openclaw"].filter((agent) => choices.some((choice) => choice.value === agent));
961
+ }
962
+ async function resolveSkillName(inputSkill) {
963
+ if (inputSkill?.trim()) return inputSkill.trim();
964
+ const input = await p.text({
965
+ message: "Enter skill to install",
966
+ placeholder: "my-skill",
967
+ validate: (value) => {
968
+ if (!value?.trim()) return "Please enter skill name";
969
+ }
970
+ });
971
+ if (p.isCancel(input)) cancelAndExit("Cancelled");
972
+ return input.trim();
973
+ }
974
+ async function resolveTargetAgents(options) {
975
+ const allAgentChoices = buildAgentChoices(Boolean(options.global ?? options.yes));
976
+ if (options.yes) {
977
+ const defaults = getDefaultAgents(allAgentChoices);
978
+ if (defaults.length === 0) cancelAndExit("No default agents available");
979
+ return defaults;
980
+ }
981
+ const selected = await promptForAgents("Which agents do you want to install to?", allAgentChoices, getDefaultAgents(allAgentChoices));
982
+ if (p.isCancel(selected) || selected.length === 0) cancelAndExit("Installation cancelled");
983
+ return selected;
984
+ }
985
+ async function resolveInstallScope(options, targetAgents) {
986
+ const supportsGlobal = targetAgents.some((agent) => agents[agent].globalSkillsDir !== void 0);
987
+ if (options.global !== void 0 || options.yes || !supportsGlobal) return options.global ?? (options.yes ? true : false);
988
+ const scope = await p.select({
989
+ message: "Installation scope",
990
+ options: [{
991
+ value: false,
992
+ label: "Project",
993
+ hint: "Install in current directory (committed with your project)"
994
+ }, {
995
+ value: true,
996
+ label: "Global",
997
+ hint: "Install in home directory (available across all projects)"
998
+ }]
999
+ });
1000
+ if (p.isCancel(scope)) cancelAndExit("Installation cancelled");
1001
+ return scope;
1002
+ }
1003
+ const run = async (args, options = {}) => {
1004
+ p.intro(chalk.bold("skill-atlas install"));
1005
+ const skill = await resolveSkillName(args[0]);
1006
+ const s = p.spinner();
1007
+ s.start(`Searching for ${chalk.bold(skill)}...`);
1008
+ try {
1009
+ const asset = await findAsset(skill);
1010
+ if (!asset) {
1011
+ s.stop();
1012
+ errorAndExit(`Not found: ${chalk.bold(skill)}`, {
1013
+ title: "Suggest",
1014
+ body: `Try: skill-atlas search ${skill}`
1015
+ });
1016
+ }
1017
+ const displayName = asset.displayName || asset.slug;
1018
+ const version = asset.currentVersion.version ?? "0.0.0";
1019
+ s.stop(`${chalk.bold(displayName)} ${chalk.dim(`v${version}`)}`);
1020
+ const targetAgents = await resolveTargetAgents(options);
1021
+ const installGlobally = await resolveInstallScope(options, targetAgents);
1022
+ const selectedLabels = targetAgents.map((a) => agents[a].displayName);
1023
+ p.log.message(chalk.green("Selected:") + " " + selectedLabels.join(", "));
1024
+ s.start("Installing skills...");
1025
+ const installMode = options.copy ? "copy" : "symlink";
1026
+ const installResults = [];
1027
+ for (const agent of targetAgents) {
1028
+ const result = await installWellKnownSkillForAgent(asset, agent, {
1029
+ global: installGlobally,
1030
+ mode: installMode
1031
+ });
1032
+ if (!result.success) throw new Error(`Failed to install to ${agents[agent].displayName}: ${result.error || "Unknown error"}`);
1033
+ installResults.push({
1034
+ agent,
1035
+ path: result.path
1036
+ });
1037
+ }
1038
+ s.stop("Skills installed successfully");
1039
+ const title = import_picocolors.default.green("Installed 1 skill");
1040
+ const resultLines = installResults.map((r) => ` ${agents[r.agent].displayName}: ${r.path}`);
1041
+ p.note(resultLines.join("\n"), title);
1042
+ p.outro(import_picocolors.default.green("Done!") + import_picocolors.default.dim(" Skill ready. Review before use."));
1043
+ } catch (err) {
1044
+ s.stop();
1045
+ p.log.error(`Install failed: ${err.message}`);
1046
+ process.exit(1);
1047
+ }
1048
+ };
1049
+ var install_default = { run };
1050
+ //#endregion
1051
+ //#region src/commands/search.ts
1052
+ function getVersion(skill) {
1053
+ const cv = skill.currentVersion;
1054
+ if (typeof cv === "string") return cv;
1055
+ if (cv && typeof cv === "object" && "version" in cv) return cv.version ?? "—";
1056
+ return "—";
1057
+ }
1058
+ function printSkillList(items, total, page, pageSize) {
1059
+ const maxSlugLen = Math.max(...items.map((s) => (s.slug || "").length), 6);
1060
+ const maxDescLen = Math.max(...items.map((s) => Math.min((s.displayName || "—").length, 30)), 8);
1061
+ const header = ` ${chalk.bold("Description".padEnd(maxDescLen))} ${chalk.bold("SkillName".padEnd(maxSlugLen))} ${chalk.bold("Version")}`;
1062
+ const separator = " " + "-".repeat(maxSlugLen + maxDescLen + 12);
1063
+ const resultLines = items.map((s) => {
1064
+ const slug = (s.slug || "—").padEnd(maxSlugLen);
1065
+ const descPadded = ((s.displayName || "—").length > 30 ? (s.displayName || "—").slice(0, 27) + "..." : s.displayName || "—").padEnd(maxDescLen);
1066
+ const version = getVersion(s);
1067
+ return ` ${chalk.white(descPadded)} ${chalk.cyan.bold(slug)} ${chalk.dim(`v${version}`)}`;
1068
+ });
1069
+ p.note([
1070
+ header,
1071
+ separator,
1072
+ ...resultLines
1073
+ ].join("\n"), chalk.green(`Found ${total} skill(s)`));
1074
+ const start = (page - 1) * pageSize + 1;
1075
+ const end = Math.min(page * pageSize, total);
1076
+ p.log.message(chalk.dim(`Showing ${start}-${end} of ${total}`));
1077
+ p.log.message(chalk.green(`Install: npx skill-atlas install <skillName>`));
1078
+ }
1079
+ async function runSearch(options) {
1080
+ const { keyword } = options;
1081
+ try {
1082
+ const result = await searchSkills({ q: keyword });
1083
+ const { items, total } = result;
1084
+ if (items.length === 0) {
1085
+ p.log.error(`No skills found matching "${chalk.bold(keyword)}"`);
1086
+ return;
1087
+ }
1088
+ printSkillList(items, total, result.page, result.pageSize);
1089
+ } catch (err) {
1090
+ p.log.error(`Search failed: ${err.message}`);
1091
+ process.exit(1);
1092
+ }
1093
+ }
1094
+ //#endregion
1095
+ //#region src/core/logger.ts
1096
+ const consola = createConsola();
1097
+ /**
1098
+ * 设置 verbose 模式(启用 debug 输出)
1099
+ * consola level: 3=info, 4=debug
1100
+ */
1101
+ function setVerbose(enabled) {
1102
+ consola.level = enabled ? 4 : 3;
1103
+ }
1104
+ /**
1105
+ * 检查是否启用 verbose
1106
+ */
1107
+ function isVerbose() {
1108
+ return consola.level >= 4;
1109
+ }
1110
+ /**
1111
+ * 错误别名(兼容 err 调用)
1112
+ */
1113
+ function err(...args) {
1114
+ consola.error.apply(consola, args);
1115
+ }
1116
+ var logger_default = {
1117
+ setVerbose,
1118
+ isVerbose,
1119
+ info: ((...a) => consola.info(...a)),
1120
+ success: ((...a) => consola.success(...a)),
1121
+ warn: ((...a) => consola.warn(...a)),
1122
+ error: ((...a) => consola.error(...a)),
1123
+ err,
1124
+ debug: ((...a) => consola.debug(...a)),
1125
+ log: ((...a) => consola.log(...a))
1126
+ };
1127
+ //#endregion
1128
+ export { checkForUpdate, install_default as install, logger_default as logger, runSearch };