@empjs/skill 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1036 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import { readFileSync } from "fs";
6
+ import { fileURLToPath } from "url";
7
+ import { dirname, join } from "path";
8
+
9
+ // src/commands/install.ts
10
+ import { exec as exec2 } from "child_process";
11
+ import { promisify as promisify2 } from "util";
12
+ import fs4 from "fs";
13
+ import path5 from "path";
14
+ import os2 from "os";
15
+
16
+ // src/utils/logger.ts
17
+ import chalk from "chalk";
18
+ import ora from "ora";
19
+ var Logger = class {
20
+ spinner = null;
21
+ info(message) {
22
+ if (this.spinner) this.spinner.stop();
23
+ console.log(chalk.blue("\u2139"), message);
24
+ }
25
+ success(message) {
26
+ if (this.spinner) this.spinner.stop();
27
+ console.log(chalk.green("\u2713"), message);
28
+ }
29
+ warn(message) {
30
+ if (this.spinner) this.spinner.stop();
31
+ console.log(chalk.yellow("\u26A0"), message);
32
+ }
33
+ error(message) {
34
+ if (this.spinner) this.spinner.stop();
35
+ console.log(chalk.red("\u2717"), message);
36
+ }
37
+ start(message) {
38
+ if (this.spinner) this.spinner.stop();
39
+ this.spinner = ora(message).start();
40
+ return this.spinner;
41
+ }
42
+ stopSpinner() {
43
+ if (this.spinner) {
44
+ this.spinner.stop();
45
+ this.spinner = null;
46
+ }
47
+ }
48
+ /**
49
+ * Update spinner text without stopping it
50
+ */
51
+ updateSpinner(message) {
52
+ if (this.spinner) {
53
+ this.spinner.text = message;
54
+ }
55
+ }
56
+ /**
57
+ * Log info without stopping spinner (for background info)
58
+ */
59
+ infoWithoutStop(message) {
60
+ if (this.spinner) {
61
+ const currentText = this.spinner.text;
62
+ this.spinner.stop();
63
+ console.log(chalk.blue("\u2139"), message);
64
+ this.spinner.start(currentText);
65
+ } else {
66
+ console.log(chalk.blue("\u2139"), message);
67
+ }
68
+ }
69
+ };
70
+ var logger = new Logger();
71
+
72
+ // src/utils/paths.ts
73
+ import fs from "fs";
74
+ import path2 from "path";
75
+
76
+ // src/config/agents.ts
77
+ import os from "os";
78
+ import path from "path";
79
+ var HOME = os.homedir();
80
+ function getAgentSkillsDirs(agent, cwd) {
81
+ if (agent.skillsDirs) {
82
+ if (typeof agent.skillsDirs === "function") {
83
+ return agent.skillsDirs(cwd);
84
+ }
85
+ return agent.skillsDirs;
86
+ }
87
+ if (agent.skillsDir) {
88
+ return [agent.skillsDir];
89
+ }
90
+ return [];
91
+ }
92
+ var AGENTS = [
93
+ {
94
+ name: "claude",
95
+ displayName: "Claude Code",
96
+ skillsDir: path.join(HOME, ".claude", "skills"),
97
+ enabled: true
98
+ },
99
+ {
100
+ name: "cursor",
101
+ displayName: "Cursor",
102
+ skillsDir: path.join(HOME, ".cursor", "skills"),
103
+ enabled: true
104
+ },
105
+ {
106
+ name: "windsurf",
107
+ displayName: "Windsurf",
108
+ skillsDir: path.join(HOME, ".windsurf", "skills"),
109
+ enabled: true
110
+ },
111
+ {
112
+ name: "cline",
113
+ displayName: "Cline",
114
+ skillsDir: path.join(HOME, ".cline", "skills"),
115
+ enabled: true
116
+ },
117
+ {
118
+ name: "gemini",
119
+ displayName: "Gemini Code",
120
+ skillsDir: path.join(HOME, ".gemini", "skills"),
121
+ enabled: true
122
+ },
123
+ {
124
+ name: "copilot",
125
+ displayName: "GitHub Copilot",
126
+ skillsDir: path.join(HOME, ".copilot", "skills"),
127
+ enabled: true
128
+ },
129
+ {
130
+ name: "opencode",
131
+ displayName: "OpenCode",
132
+ skillsDir: path.join(HOME, ".opencode", "skills"),
133
+ enabled: true
134
+ },
135
+ {
136
+ name: "antigravity",
137
+ displayName: "Antigravity",
138
+ skillsDirs: (cwd) => {
139
+ const dirs = [];
140
+ dirs.push(path.join(HOME, ".gemini", "antigravity", "skills"));
141
+ if (cwd) {
142
+ dirs.push(path.join(cwd, ".agent", "skills"));
143
+ }
144
+ if (cwd) {
145
+ dirs.push(path.join(cwd, ".shared", "skills"));
146
+ }
147
+ return dirs;
148
+ },
149
+ enabled: true
150
+ },
151
+ {
152
+ name: "kiro",
153
+ displayName: "Kiro",
154
+ skillsDir: path.join(HOME, ".kiro", "skills"),
155
+ enabled: true
156
+ },
157
+ {
158
+ name: "codex",
159
+ displayName: "Codex CLI",
160
+ skillsDir: path.join(HOME, ".codex", "skills"),
161
+ enabled: true
162
+ },
163
+ {
164
+ name: "qoder",
165
+ displayName: "Qoder",
166
+ skillsDir: path.join(HOME, ".qoder", "skills"),
167
+ enabled: true
168
+ },
169
+ {
170
+ name: "roocode",
171
+ displayName: "Roo Code",
172
+ skillsDir: path.join(HOME, ".roo", "skills"),
173
+ enabled: true
174
+ },
175
+ {
176
+ name: "trae",
177
+ displayName: "Trae",
178
+ skillsDir: path.join(HOME, ".trae", "skills"),
179
+ enabled: true
180
+ },
181
+ {
182
+ name: "continue",
183
+ displayName: "Continue",
184
+ skillsDir: path.join(HOME, ".continue", "skills"),
185
+ enabled: true
186
+ }
187
+ ];
188
+ var SHARED_SKILLS_DIR = path.join(HOME, ".emp-agent", "skills");
189
+ var CONFIG_FILE = path.join(HOME, ".emp-agent", "config.json");
190
+ var CACHE_DIR = path.join(HOME, ".emp-agent", "cache");
191
+
192
+ // src/utils/paths.ts
193
+ function getSharedSkillPath(skillName) {
194
+ return path2.join(SHARED_SKILLS_DIR, skillName);
195
+ }
196
+ function getAgentSkillPaths(agentName, skillName, cwd) {
197
+ const agent = AGENTS.find((a) => a.name === agentName);
198
+ if (!agent) {
199
+ throw new Error(`Unknown agent: ${agentName}`);
200
+ }
201
+ const skillsDirs = getAgentSkillsDirs(agent, cwd);
202
+ return skillsDirs.map((dir) => path2.join(dir, skillName));
203
+ }
204
+ function ensureSharedDir() {
205
+ if (!fs.existsSync(SHARED_SKILLS_DIR)) {
206
+ fs.mkdirSync(SHARED_SKILLS_DIR, { recursive: true });
207
+ }
208
+ }
209
+ function detectInstalledAgents(cwd) {
210
+ return AGENTS.filter((agent) => {
211
+ try {
212
+ const skillsDirs = getAgentSkillsDirs(agent, cwd);
213
+ return skillsDirs.some((dir) => {
214
+ return fs.existsSync(dir) || fs.existsSync(path2.dirname(dir));
215
+ });
216
+ } catch {
217
+ return false;
218
+ }
219
+ });
220
+ }
221
+ function extractSkillName(nameOrPath) {
222
+ if (nameOrPath.startsWith("@")) {
223
+ const parts = nameOrPath.split("/");
224
+ if (parts.length === 2) {
225
+ const scope = parts[0].slice(1);
226
+ const name = parts[1].replace("-skill", "");
227
+ return `${scope}-${name}`;
228
+ }
229
+ }
230
+ return path2.basename(nameOrPath);
231
+ }
232
+
233
+ // src/utils/symlink.ts
234
+ import fs2 from "fs";
235
+ import path3 from "path";
236
+ function createSymlink(skillName, agent, cwd) {
237
+ const source = getSharedSkillPath(skillName);
238
+ if (!fs2.existsSync(source)) {
239
+ logger.error(`Skill not found: ${source}`);
240
+ return false;
241
+ }
242
+ const targets = getAgentSkillPaths(agent.name, skillName, cwd);
243
+ let successCount = 0;
244
+ for (const target of targets) {
245
+ const targetDir = path3.dirname(target);
246
+ if (!fs2.existsSync(targetDir)) {
247
+ try {
248
+ fs2.mkdirSync(targetDir, { recursive: true });
249
+ } catch (error) {
250
+ logger.error(`Failed to create directory ${targetDir}: ${error.message}`);
251
+ continue;
252
+ }
253
+ }
254
+ if (fs2.existsSync(target)) {
255
+ try {
256
+ const stats = fs2.lstatSync(target);
257
+ if (stats.isSymbolicLink()) {
258
+ fs2.unlinkSync(target);
259
+ } else {
260
+ logger.warn(`Target exists but is not a symlink, skipping: ${target}`);
261
+ continue;
262
+ }
263
+ } catch (error) {
264
+ logger.error(`Failed to remove existing target: ${error.message}`);
265
+ continue;
266
+ }
267
+ }
268
+ try {
269
+ fs2.symlinkSync(source, target, "dir");
270
+ successCount++;
271
+ } catch (error) {
272
+ logger.error(`Failed to create symlink at ${target}: ${error.message}`);
273
+ }
274
+ }
275
+ if (successCount > 0) {
276
+ const dirsInfo = targets.length > 1 ? ` (${successCount}/${targets.length} dirs)` : "";
277
+ logger.success(`\u2713 ${agent.displayName}${dirsInfo}`);
278
+ return true;
279
+ }
280
+ return false;
281
+ }
282
+ function removeSymlink(skillName, agent, cwd) {
283
+ const targets = getAgentSkillPaths(agent.name, skillName, cwd);
284
+ let removedCount = 0;
285
+ for (const target of targets) {
286
+ if (!fs2.existsSync(target)) {
287
+ continue;
288
+ }
289
+ try {
290
+ const stats = fs2.lstatSync(target);
291
+ if (stats.isSymbolicLink()) {
292
+ fs2.unlinkSync(target);
293
+ removedCount++;
294
+ } else {
295
+ logger.warn(`Not a symlink: ${target}`);
296
+ }
297
+ } catch (error) {
298
+ logger.error(`Failed to remove symlink at ${target}: ${error.message}`);
299
+ }
300
+ }
301
+ if (removedCount > 0) {
302
+ const dirsInfo = targets.length > 1 ? ` (${removedCount}/${targets.length} dirs)` : "";
303
+ logger.success(`Unlinked from ${agent.displayName}${dirsInfo}`);
304
+ return true;
305
+ }
306
+ return false;
307
+ }
308
+ function isSymlink(filePath) {
309
+ try {
310
+ const stats = fs2.lstatSync(filePath);
311
+ return stats.isSymbolicLink();
312
+ } catch {
313
+ return false;
314
+ }
315
+ }
316
+ function readSymlink(filePath) {
317
+ try {
318
+ return fs2.readlinkSync(filePath);
319
+ } catch {
320
+ return null;
321
+ }
322
+ }
323
+
324
+ // src/utils/registry.ts
325
+ import { exec } from "child_process";
326
+ import { promisify } from "util";
327
+ import fs3 from "fs";
328
+ import path4 from "path";
329
+ var execAsync = promisify(exec);
330
+ function findNpmrc(startDir) {
331
+ let currentDir = path4.resolve(startDir);
332
+ while (currentDir !== path4.dirname(currentDir)) {
333
+ const npmrcPath = path4.join(currentDir, ".npmrc");
334
+ if (fs3.existsSync(npmrcPath)) {
335
+ return npmrcPath;
336
+ }
337
+ currentDir = path4.dirname(currentDir);
338
+ }
339
+ return null;
340
+ }
341
+ function parseNpmrc(npmrcPath) {
342
+ try {
343
+ const content = fs3.readFileSync(npmrcPath, "utf-8");
344
+ const lines = content.split("\n");
345
+ for (const line of lines) {
346
+ const trimmed = line.trim();
347
+ if (!trimmed || trimmed.startsWith("#")) {
348
+ continue;
349
+ }
350
+ const registryMatch = trimmed.match(/^(?:@[^:]+:)?registry\s*=\s*(.+)$/);
351
+ if (registryMatch) {
352
+ return registryMatch[1].trim();
353
+ }
354
+ }
355
+ } catch (error) {
356
+ }
357
+ return null;
358
+ }
359
+ async function getGlobalRegistry() {
360
+ try {
361
+ const { stdout } = await execAsync("npm config get registry");
362
+ const registry = stdout.trim();
363
+ if (registry && registry !== "undefined") {
364
+ return registry;
365
+ }
366
+ } catch (error) {
367
+ }
368
+ return null;
369
+ }
370
+ async function getRegistry(cwd = process.cwd()) {
371
+ const npmrcPath = findNpmrc(cwd);
372
+ if (npmrcPath) {
373
+ const registry = parseNpmrc(npmrcPath);
374
+ if (registry) {
375
+ return registry;
376
+ }
377
+ }
378
+ const globalRegistry = await getGlobalRegistry();
379
+ if (globalRegistry) {
380
+ return globalRegistry;
381
+ }
382
+ return "https://registry.npmjs.org/";
383
+ }
384
+
385
+ // src/utils/git.ts
386
+ function parseGitUrl(url) {
387
+ const githubPatterns = [
388
+ // https://github.com/owner/repo/tree/branch/path/to/dir
389
+ /^https?:\/\/github\.com\/([^\/]+)\/([^\/]+)\/tree\/([^\/]+)(?:\/(.+))?$/,
390
+ // https://github.com/owner/repo
391
+ /^https?:\/\/github\.com\/([^\/]+)\/([^\/]+)(?:\/)?$/,
392
+ // git@github.com:owner/repo.git
393
+ /^git@github\.com:([^\/]+)\/([^\/]+)(?:\.git)?$/
394
+ ];
395
+ const gitlabPatterns = [
396
+ // https://gitlab.com/owner/repo/-/tree/branch/path/to/dir
397
+ /^https?:\/\/gitlab\.com\/([^\/]+)\/([^\/]+)\/-\/tree\/([^\/]+)(?:\/(.+))?$/,
398
+ // https://gitlab.com/owner/repo
399
+ /^https?:\/\/gitlab\.com\/([^\/]+)\/([^\/]+)(?:\/)?$/,
400
+ // git@gitlab.com:owner/repo.git
401
+ /^git@gitlab\.com:([^\/]+)\/([^\/]+)(?:\.git)?$/
402
+ ];
403
+ for (let i = 0; i < githubPatterns.length; i++) {
404
+ const pattern = githubPatterns[i];
405
+ const match = url.match(pattern);
406
+ if (match) {
407
+ const owner = match[1];
408
+ const repo = match[2].replace(/\.git$/, "");
409
+ let branch;
410
+ let path7;
411
+ if (i === 0) {
412
+ branch = match[3];
413
+ path7 = match[4];
414
+ } else if (i === 1) {
415
+ branch = "main";
416
+ } else {
417
+ branch = "main";
418
+ }
419
+ const gitUrl = `https://github.com/${owner}/${repo}.git`;
420
+ return {
421
+ type: "github",
422
+ owner,
423
+ repo,
424
+ branch,
425
+ path: path7,
426
+ gitUrl,
427
+ installUrl: gitUrl
428
+ // Will be used for git clone
429
+ };
430
+ }
431
+ }
432
+ for (let i = 0; i < gitlabPatterns.length; i++) {
433
+ const pattern = gitlabPatterns[i];
434
+ const match = url.match(pattern);
435
+ if (match) {
436
+ const owner = match[1];
437
+ const repo = match[2].replace(/\.git$/, "");
438
+ let branch;
439
+ let path7;
440
+ if (i === 0) {
441
+ branch = match[3];
442
+ path7 = match[4];
443
+ } else if (i === 1) {
444
+ branch = "main";
445
+ } else {
446
+ branch = "main";
447
+ }
448
+ const gitUrl = `https://gitlab.com/${owner}/${repo}.git`;
449
+ return {
450
+ type: "gitlab",
451
+ owner,
452
+ repo,
453
+ branch,
454
+ path: path7,
455
+ gitUrl,
456
+ installUrl: gitUrl
457
+ // Will be used for git clone
458
+ };
459
+ }
460
+ }
461
+ if (url.startsWith("git+") || url.startsWith("git://")) {
462
+ return {
463
+ type: "other",
464
+ owner: "",
465
+ repo: "",
466
+ gitUrl: url.replace(/^git\+/, ""),
467
+ installUrl: url.startsWith("git+") ? url : `git+${url}`
468
+ };
469
+ }
470
+ return null;
471
+ }
472
+ function isGitUrl(str) {
473
+ return str.includes("github.com") || str.includes("gitlab.com") || str.startsWith("git@") || str.startsWith("git+") || str.startsWith("git://");
474
+ }
475
+
476
+ // src/commands/install.ts
477
+ var execAsync2 = promisify2(exec2);
478
+ async function execWithTimeout(command, timeout = 12e4, env) {
479
+ let timeoutId = null;
480
+ const timeoutPromise = new Promise((_, reject) => {
481
+ timeoutId = setTimeout(() => {
482
+ reject(new Error(`Command timeout after ${timeout / 1e3}s`));
483
+ }, timeout);
484
+ });
485
+ try {
486
+ const execOptions = env ? { env } : {};
487
+ const result = await Promise.race([
488
+ execAsync2(command, execOptions),
489
+ timeoutPromise
490
+ ]);
491
+ if (timeoutId) {
492
+ clearTimeout(timeoutId);
493
+ }
494
+ return result;
495
+ } catch (error) {
496
+ if (timeoutId) {
497
+ clearTimeout(timeoutId);
498
+ }
499
+ throw error;
500
+ }
501
+ }
502
+ async function install(skillNameOrPath, options = {}) {
503
+ logger.info(`Installing skill: ${skillNameOrPath}`);
504
+ ensureSharedDir();
505
+ let skillPath;
506
+ let skillName;
507
+ if (isGitUrl(skillNameOrPath)) {
508
+ const gitInfo = parseGitUrl(skillNameOrPath);
509
+ if (!gitInfo) {
510
+ logger.error(`Invalid git URL: ${skillNameOrPath}`);
511
+ logger.info("Please ensure the URL is a valid GitHub or GitLab repository URL");
512
+ process.exit(1);
513
+ }
514
+ skillName = gitInfo.path ? extractSkillName(path5.basename(gitInfo.path)) : extractSkillName(gitInfo.repo);
515
+ const tempDir = path5.join("/tmp", `eskill-${Date.now()}`);
516
+ const cloneDir = path5.join(tempDir, "repo");
517
+ try {
518
+ const timeout = options.timeout || 12e4;
519
+ const spinner = logger.start(`Cloning ${gitInfo.gitUrl}...`);
520
+ logger.infoWithoutStop(`Repository: ${gitInfo.gitUrl}`);
521
+ if (gitInfo.branch) {
522
+ logger.infoWithoutStop(`Branch: ${gitInfo.branch}`);
523
+ }
524
+ if (gitInfo.path) {
525
+ logger.infoWithoutStop(`Path: ${gitInfo.path}`);
526
+ }
527
+ logger.infoWithoutStop(`Timeout: ${timeout / 1e3}s`);
528
+ fs4.mkdirSync(tempDir, { recursive: true });
529
+ const branchFlag = gitInfo.branch ? `-b ${gitInfo.branch}` : "";
530
+ const cloneCommand = branchFlag ? `git clone ${branchFlag} ${gitInfo.gitUrl} ${cloneDir} --depth 1 --quiet` : `git clone ${gitInfo.gitUrl} ${cloneDir} --depth 1 --quiet`;
531
+ try {
532
+ await execWithTimeout(cloneCommand, timeout);
533
+ spinner.succeed(`Repository cloned successfully`);
534
+ } catch (error) {
535
+ spinner.fail("Clone failed");
536
+ if (error.message.includes("timeout")) {
537
+ logger.error(`Clone timeout after ${timeout / 1e3} seconds`);
538
+ logger.info("");
539
+ logger.info("Possible reasons:");
540
+ logger.info(" - Slow network connection");
541
+ logger.info(" - Large repository size");
542
+ logger.info(" - Git server issues");
543
+ logger.info("");
544
+ logger.info(`Try again or increase timeout: eskill add ${skillNameOrPath} --timeout=300000`);
545
+ }
546
+ throw error;
547
+ }
548
+ if (gitInfo.path) {
549
+ skillPath = path5.join(cloneDir, gitInfo.path);
550
+ if (!fs4.existsSync(skillPath)) {
551
+ logger.error(`Path not found in repository: ${gitInfo.path}`);
552
+ logger.info(`Repository cloned to: ${cloneDir}`);
553
+ process.exit(1);
554
+ }
555
+ } else {
556
+ skillPath = cloneDir;
557
+ }
558
+ const skillMdPath = path5.join(skillPath, "SKILL.md");
559
+ if (!fs4.existsSync(skillMdPath)) {
560
+ logger.warn(`Warning: SKILL.md not found in ${skillPath}`);
561
+ logger.info("The directory may not be a valid skill package");
562
+ }
563
+ } catch (error) {
564
+ logger.error(`Failed to clone repository: ${error.message}`);
565
+ if (error.message.includes("timeout")) {
566
+ logger.error(`Clone timeout after 2 minutes`);
567
+ logger.info("This might be due to:");
568
+ logger.info(" - Slow network connection");
569
+ logger.info(" - Large repository size");
570
+ logger.info(" - Git server issues");
571
+ logger.info(`
572
+ Try again or check your network connection`);
573
+ }
574
+ logger.info(`
575
+ Tried to clone: ${gitInfo.gitUrl}`);
576
+ if (gitInfo.branch) {
577
+ logger.info(`Branch: ${gitInfo.branch}`);
578
+ }
579
+ process.exit(1);
580
+ }
581
+ } else if (options.link || fs4.existsSync(skillNameOrPath)) {
582
+ skillPath = path5.resolve(skillNameOrPath);
583
+ if (!fs4.existsSync(skillPath)) {
584
+ logger.error(`Path not found: ${skillPath}`);
585
+ process.exit(1);
586
+ }
587
+ const pkgPath = path5.join(skillPath, "package.json");
588
+ if (fs4.existsSync(pkgPath)) {
589
+ try {
590
+ const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
591
+ skillName = extractSkillName(pkg.name);
592
+ } catch {
593
+ skillName = extractSkillName(path5.basename(skillPath));
594
+ }
595
+ } else {
596
+ skillName = extractSkillName(path5.basename(skillPath));
597
+ }
598
+ } else {
599
+ skillName = extractSkillName(skillNameOrPath);
600
+ const tempDir = path5.join("/tmp", `eskill-${Date.now()}`);
601
+ try {
602
+ const registry = options.registry || await getRegistry();
603
+ const timeout = options.timeout || 18e4;
604
+ const spinner = logger.start(`Installing ${skillNameOrPath}...`);
605
+ logger.infoWithoutStop(`Registry: ${registry}`);
606
+ logger.infoWithoutStop(`Timeout: ${timeout / 1e3}s`);
607
+ fs4.mkdirSync(tempDir, { recursive: true });
608
+ logger.updateSpinner(`Downloading ${skillNameOrPath} from ${registry}...`);
609
+ const homeDir = process.env.HOME || process.env.USERPROFILE || os2.homedir();
610
+ const npmCacheDir = path5.join(homeDir, ".npm");
611
+ const npmConfigPrefix = path5.join(homeDir, ".npm-global");
612
+ fs4.mkdirSync(npmCacheDir, { recursive: true });
613
+ const installCommand = `npm install ${skillNameOrPath} --prefix ${tempDir} --registry=${registry} --no-save --silent --no-bin-links --prefer-offline`;
614
+ const env = {
615
+ ...process.env,
616
+ npm_config_cache: npmCacheDir,
617
+ npm_config_prefix: npmConfigPrefix,
618
+ npm_config_global: "false"
619
+ };
620
+ try {
621
+ await execWithTimeout(installCommand, timeout, env);
622
+ spinner.succeed(`Package ${skillNameOrPath} downloaded successfully`);
623
+ } catch (error) {
624
+ spinner.fail("Download failed");
625
+ const errorMessage = error.message || error.stderr || "";
626
+ if (errorMessage.includes("timeout")) {
627
+ logger.error(`Download timeout after ${timeout / 1e3} seconds`);
628
+ logger.info("");
629
+ logger.info("Possible reasons:");
630
+ logger.info(" - Slow network connection");
631
+ logger.info(" - Registry server issues or unresponsive");
632
+ logger.info(" - Large package size");
633
+ logger.info(" - Network firewall blocking the connection");
634
+ logger.info("");
635
+ logger.info("Suggestions:");
636
+ logger.info(` - Try again: eskill add ${skillNameOrPath}`);
637
+ logger.info(` - Use a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`);
638
+ logger.info(` - Increase timeout: eskill add ${skillNameOrPath} --timeout=300000`);
639
+ } else if (errorMessage.includes("EACCES") || errorMessage.includes("permission denied") || errorMessage.includes("Permission denied")) {
640
+ logger.error("Permission denied error detected");
641
+ logger.info("");
642
+ logger.info("This error occurs when npm tries to access system directories.");
643
+ logger.info("");
644
+ logger.info("\u{1F527} Quick Fix (Recommended):");
645
+ logger.info("");
646
+ logger.info("1. Configure npm to use user directory:");
647
+ logger.info(` mkdir -p ${npmConfigPrefix}`);
648
+ logger.info(` npm config set prefix '${npmConfigPrefix}'`);
649
+ logger.info("");
650
+ logger.info("2. Add to your ~/.zshrc (or ~/.bash_profile):");
651
+ logger.info(` export PATH="${npmConfigPrefix}/bin:$PATH"`);
652
+ logger.info("");
653
+ logger.info("3. Reload shell configuration:");
654
+ logger.info(" source ~/.zshrc");
655
+ logger.info("");
656
+ logger.info("\u{1F4DA} Alternative Solutions:");
657
+ logger.info("");
658
+ logger.info("Option A: Use NVM (Node Version Manager)");
659
+ logger.info(" curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash");
660
+ logger.info(" nvm install --lts");
661
+ logger.info("");
662
+ logger.info("Option B: Fix system directory permissions (requires sudo)");
663
+ logger.info(" sudo chown -R $(whoami) /usr/local/lib/node_modules");
664
+ logger.info("");
665
+ logger.info("Option C: Use sudo (not recommended for security reasons)");
666
+ logger.info(` sudo npm install -g @empjs/skill --registry=${registry}`);
667
+ logger.info("");
668
+ logger.info("After fixing, try again:");
669
+ logger.info(` eskill add ${skillNameOrPath}`);
670
+ } else if (errorMessage.includes("ENOTFOUND") || errorMessage.includes("ECONNREFUSED")) {
671
+ logger.error(`Network connection error: ${errorMessage}`);
672
+ logger.info("");
673
+ logger.info("Please check:");
674
+ logger.info(" - Your internet connection");
675
+ logger.info(` - Registry accessibility: ${registry}`);
676
+ logger.info(` - Try a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`);
677
+ } else {
678
+ logger.error(`Failed to download: ${errorMessage}`);
679
+ logger.info("");
680
+ logger.info("If this is a permission error, see the solutions above.");
681
+ logger.info("For other errors, please check:");
682
+ logger.info(" - Package name is correct");
683
+ logger.info(" - Registry is accessible");
684
+ logger.info(` - Try: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`);
685
+ }
686
+ process.exit(1);
687
+ }
688
+ skillPath = path5.join(tempDir, "node_modules", skillNameOrPath);
689
+ if (!fs4.existsSync(skillPath)) {
690
+ logger.error(`Failed to download package: ${skillNameOrPath}`);
691
+ process.exit(1);
692
+ }
693
+ } catch (error) {
694
+ logger.error(`Failed to download: ${error.message}`);
695
+ process.exit(1);
696
+ }
697
+ }
698
+ const targetPath = getSharedSkillPath(skillName);
699
+ if (fs4.existsSync(targetPath)) {
700
+ if (options.force) {
701
+ logger.warn("Removing existing installation...");
702
+ fs4.rmSync(targetPath, { recursive: true, force: true });
703
+ } else {
704
+ logger.error(`Skill already exists: ${targetPath}`);
705
+ logger.info("Use --force to overwrite");
706
+ process.exit(1);
707
+ }
708
+ }
709
+ if (options.link) {
710
+ fs4.symlinkSync(skillPath, targetPath, "dir");
711
+ logger.success(`Linked to shared directory (dev mode)`);
712
+ } else {
713
+ copyDir(skillPath, targetPath);
714
+ logger.success(`Installed to shared directory`);
715
+ }
716
+ logger.info(`\u{1F4CD} Location: ${targetPath}`);
717
+ const cwd = process.cwd();
718
+ const installedAgents = detectInstalledAgents(cwd);
719
+ if (installedAgents.length === 0) {
720
+ logger.warn("No AI agents detected");
721
+ logger.info("Skill installed to shared directory, but not linked to any agent");
722
+ logger.info("");
723
+ logger.info("Supported agents:");
724
+ logger.info(" - Claude Code (~/.claude/skills)");
725
+ logger.info(" - Cursor (~/.cursor/skills)");
726
+ logger.info(" - Windsurf (~/.windsurf/skills)");
727
+ return;
728
+ }
729
+ let targetAgents = installedAgents;
730
+ if (options.agent && options.agent !== "all") {
731
+ const agent = installedAgents.find((a) => a.name === options.agent);
732
+ if (!agent) {
733
+ logger.error(`Agent not installed: ${options.agent}`);
734
+ logger.info(`
735
+ Installed agents: ${installedAgents.map((a) => a.name).join(", ")}`);
736
+ process.exit(1);
737
+ }
738
+ targetAgents = [agent];
739
+ }
740
+ logger.info("\nCreating symlinks...");
741
+ let successCount = 0;
742
+ for (const agent of targetAgents) {
743
+ if (createSymlink(skillName, agent, cwd)) {
744
+ successCount++;
745
+ }
746
+ }
747
+ logger.info("");
748
+ logger.success(`\u2705 Skill installed successfully!`);
749
+ logger.info(`
750
+ Linked to ${successCount}/${targetAgents.length} agents`);
751
+ if (options.link) {
752
+ logger.info("\n\u{1F4A1} Dev mode: changes to source files will reflect immediately");
753
+ }
754
+ }
755
+ function copyDir(src, dest) {
756
+ fs4.mkdirSync(dest, { recursive: true });
757
+ const entries = fs4.readdirSync(src, { withFileTypes: true });
758
+ for (const entry of entries) {
759
+ const srcPath = path5.join(src, entry.name);
760
+ const destPath = path5.join(dest, entry.name);
761
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) {
762
+ continue;
763
+ }
764
+ if (entry.isDirectory()) {
765
+ copyDir(srcPath, destPath);
766
+ } else {
767
+ fs4.copyFileSync(srcPath, destPath);
768
+ }
769
+ }
770
+ }
771
+
772
+ // src/commands/list.ts
773
+ import fs5 from "fs";
774
+ import path6 from "path";
775
+ import chalk2 from "chalk";
776
+ function list() {
777
+ if (!fs5.existsSync(SHARED_SKILLS_DIR)) {
778
+ logger.info("No skills installed");
779
+ logger.info(`
780
+ To install a skill, run: ${chalk2.cyan("eskill install <skill-name>")}`);
781
+ return;
782
+ }
783
+ const skills = fs5.readdirSync(SHARED_SKILLS_DIR);
784
+ if (skills.length === 0) {
785
+ logger.info("No skills installed");
786
+ logger.info(`
787
+ To install a skill, run: ${chalk2.cyan("eskill install <skill-name>")}`);
788
+ return;
789
+ }
790
+ console.log(chalk2.bold(`
791
+ Installed skills in ${SHARED_SKILLS_DIR}:
792
+ `));
793
+ for (const skill of skills) {
794
+ const skillPath = path6.join(SHARED_SKILLS_DIR, skill);
795
+ try {
796
+ const stats = fs5.lstatSync(skillPath);
797
+ if (!stats.isDirectory() && !stats.isSymbolicLink()) {
798
+ continue;
799
+ }
800
+ let version2 = "unknown";
801
+ const pkgPath = path6.join(skillPath, "package.json");
802
+ if (fs5.existsSync(pkgPath)) {
803
+ try {
804
+ const pkgContent = fs5.readFileSync(pkgPath, "utf-8");
805
+ const pkg = JSON.parse(pkgContent);
806
+ if (pkg.version && typeof pkg.version === "string") {
807
+ version2 = pkg.version;
808
+ }
809
+ } catch (error) {
810
+ }
811
+ }
812
+ if (version2 === "unknown") {
813
+ const skillMdPath = path6.join(skillPath, "SKILL.md");
814
+ if (fs5.existsSync(skillMdPath)) {
815
+ try {
816
+ const skillMdContent = fs5.readFileSync(skillMdPath, "utf-8");
817
+ const frontmatterMatch = skillMdContent.match(/^---\s*\n([\s\S]*?)\n---/);
818
+ if (frontmatterMatch) {
819
+ const frontmatter = frontmatterMatch[1];
820
+ const versionMatch = frontmatter.match(/^version:\s*(.+)$/m);
821
+ if (versionMatch) {
822
+ version2 = versionMatch[1].trim().replace(/^["']|["']$/g, "");
823
+ }
824
+ }
825
+ } catch (error) {
826
+ }
827
+ }
828
+ }
829
+ if (version2 === "unknown" && isSymlink(skillPath)) {
830
+ try {
831
+ const targetPath = readSymlink(skillPath);
832
+ if (targetPath) {
833
+ const targetPkgPath = path6.join(targetPath, "package.json");
834
+ if (fs5.existsSync(targetPkgPath)) {
835
+ const pkg = JSON.parse(fs5.readFileSync(targetPkgPath, "utf-8"));
836
+ if (pkg.version && typeof pkg.version === "string") {
837
+ version2 = pkg.version;
838
+ }
839
+ }
840
+ }
841
+ } catch (error) {
842
+ }
843
+ }
844
+ const isDev = isSymlink(skillPath);
845
+ const devTag = isDev ? chalk2.yellow(" (dev)") : "";
846
+ const versionDisplay = version2 !== "unknown" ? chalk2.gray(`(v${version2})`) : "";
847
+ console.log(chalk2.green("\u{1F4E6}") + ` ${chalk2.bold(skill)}${versionDisplay ? " " + versionDisplay : ""}${devTag}`);
848
+ const cwd = process.cwd();
849
+ const linkedAgents = [];
850
+ for (const agent of AGENTS) {
851
+ const skillsDirs = getAgentSkillsDirs(agent, cwd);
852
+ const hasSymlink = skillsDirs.some((dir) => {
853
+ const agentSkillPath = path6.join(dir, skill);
854
+ if (fs5.existsSync(agentSkillPath) && isSymlink(agentSkillPath)) {
855
+ const target = readSymlink(agentSkillPath);
856
+ return target === skillPath;
857
+ }
858
+ return false;
859
+ });
860
+ if (hasSymlink) {
861
+ linkedAgents.push(agent.displayName);
862
+ }
863
+ }
864
+ if (linkedAgents.length > 0) {
865
+ console.log(chalk2.gray(` \u2192 Linked to: ${linkedAgents.join(", ")}`));
866
+ } else {
867
+ console.log(chalk2.yellow(` \u2192 Not linked to any agent`));
868
+ }
869
+ if (isDev) {
870
+ const target = readSymlink(skillPath);
871
+ if (target) {
872
+ console.log(chalk2.gray(` \u2192 Source: ${target}`));
873
+ }
874
+ }
875
+ console.log("");
876
+ } catch (error) {
877
+ continue;
878
+ }
879
+ }
880
+ }
881
+
882
+ // src/commands/remove.ts
883
+ import fs6 from "fs";
884
+ async function remove(skillName, options = {}) {
885
+ const extractedName = extractSkillName(skillName);
886
+ const sharedPath = getSharedSkillPath(extractedName);
887
+ const skillExists = fs6.existsSync(sharedPath);
888
+ if (!skillExists) {
889
+ logger.error(`Skill not found: ${extractedName}`);
890
+ logger.info(`Location: ${sharedPath}`);
891
+ process.exit(1);
892
+ }
893
+ const cwd = process.cwd();
894
+ const installedAgents = detectInstalledAgents(cwd);
895
+ let targetAgents = installedAgents;
896
+ if (options.agent && options.agent !== "all") {
897
+ const agent = installedAgents.find((a) => a.name === options.agent);
898
+ if (!agent) {
899
+ logger.error(`Agent not installed: ${options.agent}`);
900
+ logger.info(`
901
+ Installed agents: ${installedAgents.map((a) => a.name).join(", ")}`);
902
+ process.exit(1);
903
+ }
904
+ targetAgents = [agent];
905
+ }
906
+ if (targetAgents.length > 0) {
907
+ logger.info("Removing symlinks from agents...");
908
+ let removedCount = 0;
909
+ for (const agent of targetAgents) {
910
+ if (removeSymlink(extractedName, agent, cwd)) {
911
+ removedCount++;
912
+ }
913
+ }
914
+ logger.info(`Removed ${removedCount}/${targetAgents.length} symlinks`);
915
+ }
916
+ if (options.agent && options.agent !== "all") {
917
+ logger.success(`\u2705 Removed symlinks for ${options.agent} only`);
918
+ logger.info(`Skill remains in shared directory: ${sharedPath}`);
919
+ return;
920
+ }
921
+ logger.info("Removing skill from shared directory...");
922
+ try {
923
+ const stats = fs6.lstatSync(sharedPath);
924
+ if (stats.isSymbolicLink()) {
925
+ fs6.unlinkSync(sharedPath);
926
+ logger.success("Removed symlink from shared directory");
927
+ } else {
928
+ fs6.rmSync(sharedPath, { recursive: true, force: true });
929
+ logger.success("Removed skill from shared directory");
930
+ }
931
+ } catch (error) {
932
+ logger.error(`Failed to remove skill: ${error.message}`);
933
+ process.exit(1);
934
+ }
935
+ const remainingSymlinks = [];
936
+ for (const agent of AGENTS) {
937
+ const agentSkillPaths = getAgentSkillPaths(agent.name, extractedName, cwd);
938
+ const hasSymlink = agentSkillPaths.some((path7) => {
939
+ return fs6.existsSync(path7) && isSymlink(path7);
940
+ });
941
+ if (hasSymlink) {
942
+ remainingSymlinks.push(agent.displayName);
943
+ }
944
+ }
945
+ if (remainingSymlinks.length > 0) {
946
+ logger.warn(`
947
+ \u26A0\uFE0F Warning: Found remaining symlinks in:`);
948
+ for (const agentName of remainingSymlinks) {
949
+ logger.info(` - ${agentName}`);
950
+ }
951
+ logger.info("\nYou may need to manually remove these symlinks");
952
+ } else {
953
+ logger.success(`\u2705 Skill "${extractedName}" removed successfully!`);
954
+ }
955
+ }
956
+
957
+ // src/commands/agents.ts
958
+ import chalk3 from "chalk";
959
+ import fs7 from "fs";
960
+ import os3 from "os";
961
+ function agents() {
962
+ const cwd = process.cwd();
963
+ console.log(chalk3.bold("\n\u{1F4CB} Supported AI Agents:\n"));
964
+ for (const agent of AGENTS) {
965
+ const skillsDirs = getAgentSkillsDirs(agent, cwd);
966
+ const dirsInfo = skillsDirs.length > 1 ? ` (${skillsDirs.length} directories)` : "";
967
+ console.log(chalk3.bold(agent.displayName));
968
+ console.log(chalk3.gray(` Name: ${agent.name}`));
969
+ if (skillsDirs.length === 0) {
970
+ console.log(chalk3.yellow(" \u26A0\uFE0F No directories configured"));
971
+ } else {
972
+ console.log(chalk3.gray(` Directories${dirsInfo}:`));
973
+ for (const dir of skillsDirs) {
974
+ const exists = fs7.existsSync(dir);
975
+ const status = exists ? chalk3.green("\u2713") : chalk3.gray("\u25CB");
976
+ const pathColor = exists ? chalk3.white : chalk3.gray;
977
+ console.log(` ${status} ${pathColor(dir)}`);
978
+ }
979
+ }
980
+ console.log("");
981
+ }
982
+ console.log(chalk3.bold("\u{1F4E6} Shared Skills Directory:"));
983
+ const sharedExists = fs7.existsSync(SHARED_SKILLS_DIR);
984
+ const sharedStatus = sharedExists ? chalk3.green("\u2713") : chalk3.gray("\u25CB");
985
+ const sharedPathColor = sharedExists ? chalk3.white : chalk3.gray;
986
+ console.log(` ${sharedStatus} ${sharedPathColor(SHARED_SKILLS_DIR)}`);
987
+ console.log("");
988
+ const home = os3.homedir();
989
+ if (cwd !== home) {
990
+ console.log(chalk3.gray(`Current working directory: ${cwd}`));
991
+ console.log(chalk3.gray("(Project-level directories are shown above)\n"));
992
+ }
993
+ }
994
+
995
+ // src/index.ts
996
+ var __filename2 = fileURLToPath(import.meta.url);
997
+ var __dirname2 = dirname(__filename2);
998
+ var packageJsonPath = join(__dirname2, "..", "package.json");
999
+ var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1000
+ var version = packageJson.version;
1001
+ var program = new Command();
1002
+ program.name("eskill").description("Unified CLI tool for managing AI agent skills").version(version);
1003
+ program.command("install <skill>").alias("add").description("Install a skill from NPM, Git URL, or local directory").option("-a, --agent <name>", "Install for specific agent (claude, cursor, windsurf, or all)").option("-l, --link", "Dev mode: symlink from local directory").option("-f, --force", "Force reinstall if already exists").option("-r, --registry <url>", "NPM registry URL (overrides .npmrc and global config)").option("-t, --timeout <ms>", "Timeout in milliseconds (default: 180000 for npm, 120000 for git)", (value) => parseInt(value, 10)).action(async (skill, options) => {
1004
+ try {
1005
+ await install(skill, options);
1006
+ } catch (error) {
1007
+ console.error("Error:", error.message);
1008
+ process.exit(1);
1009
+ }
1010
+ });
1011
+ program.command("list").alias("ls").description("List installed skills").action(() => {
1012
+ try {
1013
+ list();
1014
+ } catch (error) {
1015
+ console.error("Error:", error.message);
1016
+ process.exit(1);
1017
+ }
1018
+ });
1019
+ program.command("remove <skill>").alias("rm").alias("uninstall").description("Remove an installed skill").option("-a, --agent <name>", "Remove symlink for specific agent only (keeps skill in shared directory)").action(async (skill, options) => {
1020
+ try {
1021
+ await remove(skill, options);
1022
+ } catch (error) {
1023
+ console.error("Error:", error.message);
1024
+ process.exit(1);
1025
+ }
1026
+ });
1027
+ program.command("agents").alias("list-agents").description("List all supported AI agents and their skills directories").action(() => {
1028
+ try {
1029
+ agents();
1030
+ } catch (error) {
1031
+ console.error("Error:", error.message);
1032
+ process.exit(1);
1033
+ }
1034
+ });
1035
+ program.parse();
1036
+ //# sourceMappingURL=index.js.map