@empjs/skill 1.1.0 → 1.2.0

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 CHANGED
@@ -210,11 +210,12 @@ function agents() {
210
210
  }
211
211
 
212
212
  // src/commands/install.ts
213
- import { exec as exec2 } from "child_process";
214
- import fs6 from "fs";
215
- import os3 from "os";
216
- import path5 from "path";
217
- import { promisify as promisify2 } from "util";
213
+ import { exec } from "child_process";
214
+ import fs7 from "fs";
215
+ import path6 from "path";
216
+ import { promisify } from "util";
217
+ import enquirer from "enquirer";
218
+ import chalk3 from "chalk";
218
219
 
219
220
  // src/utils/git.ts
220
221
  function parseGitUrl(url) {
@@ -241,27 +242,25 @@ function parseGitUrl(url) {
241
242
  const owner = match[1];
242
243
  const repo = match[2].replace(/\.git$/, "");
243
244
  let branch;
244
- let path7;
245
- let gitUrl;
245
+ let path8;
246
246
  if (i === 0) {
247
247
  branch = match[3];
248
- path7 = match[4];
249
- gitUrl = `https://github.com/${owner}/${repo}.git`;
248
+ path8 = match[4];
250
249
  } else if (i === 1) {
251
250
  branch = "main";
252
- gitUrl = `https://github.com/${owner}/${repo}.git`;
253
251
  } else {
254
252
  branch = "main";
255
- gitUrl = url.includes(".git") ? url : `git@github.com:${owner}/${repo}.git`;
256
253
  }
254
+ const gitUrl = `https://github.com/${owner}/${repo}.git`;
257
255
  return {
258
256
  type: "github",
259
257
  owner,
260
258
  repo,
261
259
  branch,
262
- path: path7,
260
+ path: path8,
263
261
  gitUrl,
264
262
  installUrl: gitUrl
263
+ // Will be used for git clone
265
264
  };
266
265
  }
267
266
  }
@@ -272,13 +271,13 @@ function parseGitUrl(url) {
272
271
  let owner;
273
272
  let repo;
274
273
  let branch;
275
- let path7;
274
+ let path8;
276
275
  let gitUrl;
277
276
  if (i === 0) {
278
277
  const host = match[1];
279
278
  const repoPath = match[2].replace(/\.git$/, "");
280
279
  branch = match[3];
281
- path7 = match[4];
280
+ path8 = match[4];
282
281
  const parts = repoPath.split("/");
283
282
  repo = parts.pop() || repoPath;
284
283
  owner = parts.join("/") || repo;
@@ -295,14 +294,14 @@ function parseGitUrl(url) {
295
294
  const parts = repoPath.split("/");
296
295
  repo = parts.pop() || repoPath;
297
296
  owner = parts.join("/") || repo;
298
- gitUrl = url.includes(".git") ? url : `git@${host}:${repoPath}.git`;
297
+ gitUrl = `https://${host}/${repoPath}.git`;
299
298
  }
300
299
  return {
301
300
  type: "gitlab",
302
301
  owner,
303
302
  repo,
304
303
  branch,
305
- path: path7,
304
+ path: path8,
306
305
  gitUrl,
307
306
  installUrl: gitUrl
308
307
  };
@@ -319,6 +318,16 @@ function parseGitUrl(url) {
319
318
  }
320
319
  return null;
321
320
  }
321
+ function convertToSshUrl(httpsUrl) {
322
+ try {
323
+ const url = new URL(httpsUrl);
324
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
325
+ const path8 = url.pathname.startsWith("/") ? url.pathname.substring(1) : url.pathname;
326
+ return `git@${url.hostname}:${path8}`;
327
+ } catch {
328
+ return null;
329
+ }
330
+ }
322
331
  function isGitUrl(str) {
323
332
  return str.includes("github.com") || str.includes("gitlab.com") || str.includes("/-/tree/") || // GitLab web URL pattern (self-hosted)
324
333
  str.startsWith("git@") || str.startsWith("git+") || str.startsWith("git://");
@@ -427,110 +436,49 @@ function extractSkillName(nameOrPath) {
427
436
  return path2.basename(nameOrPath);
428
437
  }
429
438
 
430
- // src/utils/registry.ts
431
- import { exec } from "child_process";
439
+ // src/utils/symlink.ts
432
440
  import fs4 from "fs";
433
441
  import path3 from "path";
434
- import { promisify } from "util";
435
- var execAsync = promisify(exec);
436
- function findNpmrc(startDir) {
437
- let currentDir = path3.resolve(startDir);
438
- while (currentDir !== path3.dirname(currentDir)) {
439
- const npmrcPath = path3.join(currentDir, ".npmrc");
440
- if (fs4.existsSync(npmrcPath)) {
441
- return npmrcPath;
442
- }
443
- currentDir = path3.dirname(currentDir);
444
- }
445
- return null;
446
- }
447
- function parseNpmrc(npmrcPath) {
448
- try {
449
- const content = fs4.readFileSync(npmrcPath, "utf-8");
450
- const lines = content.split("\n");
451
- for (const line of lines) {
452
- const trimmed = line.trim();
453
- if (!trimmed || trimmed.startsWith("#")) {
454
- continue;
455
- }
456
- const registryMatch = trimmed.match(/^(?:@[^:]+:)?registry\s*=\s*(.+)$/);
457
- if (registryMatch) {
458
- return registryMatch[1].trim();
459
- }
460
- }
461
- } catch (error) {
462
- }
463
- return null;
464
- }
465
- async function getGlobalRegistry() {
466
- try {
467
- const { stdout } = await execAsync("npm config get registry");
468
- const registry = stdout.trim();
469
- if (registry && registry !== "undefined") {
470
- return registry;
471
- }
472
- } catch (error) {
473
- }
474
- return null;
475
- }
476
- async function getRegistry(cwd = process.cwd()) {
477
- const npmrcPath = findNpmrc(cwd);
478
- if (npmrcPath) {
479
- const registry = parseNpmrc(npmrcPath);
480
- if (registry) {
481
- return registry;
482
- }
483
- }
484
- const globalRegistry = await getGlobalRegistry();
485
- if (globalRegistry) {
486
- return globalRegistry;
487
- }
488
- return "https://registry.npmjs.org/";
489
- }
490
-
491
- // src/utils/symlink.ts
492
- import fs5 from "fs";
493
- import path4 from "path";
494
442
  function copyDir(src, dest) {
495
- fs5.mkdirSync(dest, { recursive: true });
496
- const entries = fs5.readdirSync(src, { withFileTypes: true });
443
+ fs4.mkdirSync(dest, { recursive: true });
444
+ const entries = fs4.readdirSync(src, { withFileTypes: true });
497
445
  for (const entry of entries) {
498
446
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
499
- const srcPath = path4.join(src, entry.name);
500
- const destPath = path4.join(dest, entry.name);
447
+ const srcPath = path3.join(src, entry.name);
448
+ const destPath = path3.join(dest, entry.name);
501
449
  if (entry.isDirectory()) {
502
450
  copyDir(srcPath, destPath);
503
451
  } else {
504
- fs5.copyFileSync(srcPath, destPath);
452
+ fs4.copyFileSync(srcPath, destPath);
505
453
  }
506
454
  }
507
455
  }
508
- function createSymlink(skillName, agent, cwd) {
456
+ function createSymlink(skillName, agent, cwd, useCopyOverride) {
509
457
  const source = getSharedSkillPath(skillName);
510
- if (!fs5.existsSync(source)) {
458
+ if (!fs4.existsSync(source)) {
511
459
  logger.error(`Skill not found: ${source}`);
512
460
  return false;
513
461
  }
514
462
  const targets = getAgentSkillPaths(agent.name, skillName, cwd);
515
- const useCopy = agent.useCopyInsteadOfSymlink === true;
463
+ const useCopy = useCopyOverride !== void 0 ? useCopyOverride : agent.useCopyInsteadOfSymlink === true;
516
464
  let successCount = 0;
517
465
  for (const target of targets) {
518
- const targetDir = path4.dirname(target);
519
- if (!fs5.existsSync(targetDir)) {
466
+ const targetDir = path3.dirname(target);
467
+ if (!fs4.existsSync(targetDir)) {
520
468
  try {
521
- fs5.mkdirSync(targetDir, { recursive: true });
469
+ fs4.mkdirSync(targetDir, { recursive: true });
522
470
  } catch (error) {
523
471
  logger.error(`Failed to create directory ${targetDir}: ${error.message}`);
524
472
  continue;
525
473
  }
526
474
  }
527
- if (fs5.existsSync(target)) {
475
+ if (fs4.existsSync(target)) {
528
476
  try {
529
- const stats = fs5.lstatSync(target);
477
+ const stats = fs4.lstatSync(target);
530
478
  if (stats.isSymbolicLink()) {
531
- fs5.unlinkSync(target);
479
+ fs4.unlinkSync(target);
532
480
  } else if (stats.isDirectory()) {
533
- fs5.rmSync(target, { recursive: true, force: true });
481
+ fs4.rmSync(target, { recursive: true, force: true });
534
482
  } else {
535
483
  logger.warn(`Target exists but is not a symlink/dir, skipping: ${target}`);
536
484
  continue;
@@ -544,7 +492,7 @@ function createSymlink(skillName, agent, cwd) {
544
492
  if (useCopy) {
545
493
  copyDir(source, target);
546
494
  } else {
547
- fs5.symlinkSync(source, target, "dir");
495
+ fs4.symlinkSync(source, target, "dir");
548
496
  }
549
497
  successCount++;
550
498
  } catch (error) {
@@ -562,16 +510,16 @@ function removeSymlink(skillName, agent, cwd) {
562
510
  const targets = getAgentSkillPaths(agent.name, skillName, cwd);
563
511
  let removedCount = 0;
564
512
  for (const target of targets) {
565
- if (!fs5.existsSync(target)) {
513
+ if (!fs4.existsSync(target)) {
566
514
  continue;
567
515
  }
568
516
  try {
569
- const stats = fs5.lstatSync(target);
517
+ const stats = fs4.lstatSync(target);
570
518
  if (stats.isSymbolicLink()) {
571
- fs5.unlinkSync(target);
519
+ fs4.unlinkSync(target);
572
520
  removedCount++;
573
521
  } else if (stats.isDirectory()) {
574
- fs5.rmSync(target, { recursive: true, force: true });
522
+ fs4.rmSync(target, { recursive: true, force: true });
575
523
  removedCount++;
576
524
  } else {
577
525
  logger.warn(`Not a symlink or directory: ${target}`);
@@ -589,7 +537,7 @@ function removeSymlink(skillName, agent, cwd) {
589
537
  }
590
538
  function isSymlink(filePath) {
591
539
  try {
592
- const stats = fs5.lstatSync(filePath);
540
+ const stats = fs4.lstatSync(filePath);
593
541
  return stats.isSymbolicLink();
594
542
  } catch {
595
543
  return false;
@@ -597,426 +545,493 @@ function isSymlink(filePath) {
597
545
  }
598
546
  function readSymlink(filePath) {
599
547
  try {
600
- return fs5.readlinkSync(filePath);
548
+ return fs4.readlinkSync(filePath);
601
549
  } catch {
602
550
  return null;
603
551
  }
604
552
  }
605
553
 
554
+ // src/utils/collection.ts
555
+ import fs5 from "fs";
556
+ import path4 from "path";
557
+ function scanForSkills(dir) {
558
+ const skills = [];
559
+ const skillMdPath = path4.join(dir, "SKILL.md");
560
+ if (fs5.existsSync(skillMdPath)) {
561
+ skills.push(parseSkillDir(dir));
562
+ }
563
+ const entries = fs5.readdirSync(dir, { withFileTypes: true });
564
+ for (const entry of entries) {
565
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
566
+ const subDir = path4.join(dir, entry.name);
567
+ const subSkillMd = path4.join(subDir, "SKILL.md");
568
+ if (fs5.existsSync(subSkillMd)) {
569
+ skills.push(parseSkillDir(subDir));
570
+ }
571
+ }
572
+ }
573
+ return skills;
574
+ }
575
+ function parseSkillDir(dir) {
576
+ const name = path4.basename(dir);
577
+ let description = "";
578
+ let version2 = "1.0.0";
579
+ const pkgPath = path4.join(dir, "package.json");
580
+ if (fs5.existsSync(pkgPath)) {
581
+ try {
582
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
583
+ if (pkg.description) description = pkg.description;
584
+ if (pkg.version) version2 = pkg.version;
585
+ if (pkg.name) {
586
+ return {
587
+ name: pkg.name.replace("@empjs/", ""),
588
+ path: dir,
589
+ description,
590
+ version: version2
591
+ };
592
+ }
593
+ } catch {
594
+ }
595
+ }
596
+ const skillMdPath = path4.join(dir, "SKILL.md");
597
+ if (fs5.existsSync(skillMdPath)) {
598
+ const content = fs5.readFileSync(skillMdPath, "utf-8");
599
+ const descMatch = content.match(/description:\s*(?:"([^"]+)"|'([^']+)'|([^'"\n\r]+))/i);
600
+ if (descMatch) {
601
+ description = descMatch[1] || descMatch[2] || descMatch[3];
602
+ }
603
+ }
604
+ return {
605
+ name,
606
+ path: dir,
607
+ description: description.trim(),
608
+ version: version2
609
+ };
610
+ }
611
+
612
+ // src/utils/config.ts
613
+ import fs6 from "fs";
614
+ import path5 from "path";
615
+ import os3 from "os";
616
+ var CONFIG_DIR = path5.join(os3.homedir(), ".emp-agent");
617
+ var CONFIG_FILE2 = path5.join(CONFIG_DIR, "config.json");
618
+ var DEFAULT_LANG = Intl.DateTimeFormat().resolvedOptions().locale.startsWith("zh") ? "zh" : "en";
619
+ var DEFAULT_CONFIG = {
620
+ sources: [],
621
+ tokens: {},
622
+ lang: DEFAULT_LANG
623
+ };
624
+ function getLang() {
625
+ return loadConfig().lang || DEFAULT_LANG;
626
+ }
627
+ function saveToken(domain, token) {
628
+ const config = loadConfig();
629
+ config.tokens[domain] = token;
630
+ saveConfig(config);
631
+ }
632
+ function getToken(domain) {
633
+ const config = loadConfig();
634
+ return config.tokens[domain];
635
+ }
636
+ function ensureConfigDir() {
637
+ if (!fs6.existsSync(CONFIG_DIR)) {
638
+ fs6.mkdirSync(CONFIG_DIR, { recursive: true });
639
+ }
640
+ }
641
+ function loadConfig() {
642
+ ensureConfigDir();
643
+ if (!fs6.existsSync(CONFIG_FILE2)) {
644
+ return DEFAULT_CONFIG;
645
+ }
646
+ try {
647
+ const content = fs6.readFileSync(CONFIG_FILE2, "utf-8");
648
+ return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
649
+ } catch {
650
+ return DEFAULT_CONFIG;
651
+ }
652
+ }
653
+ function saveConfig(config) {
654
+ ensureConfigDir();
655
+ fs6.writeFileSync(CONFIG_FILE2, JSON.stringify(config, null, 2));
656
+ }
657
+
658
+ // src/utils/i18n.ts
659
+ var translations = {
660
+ en: {
661
+ analyzing: "Analyzing:",
662
+ fetching: "Fetching skills from source...",
663
+ sourceReady: "Source ready",
664
+ foundSkills: "Found {count} skills:",
665
+ selectSkills: "Select skills to install",
666
+ selectScope: "Where do you want to install?",
667
+ selectMethod: "How do you want to install?",
668
+ globalScope: "Global (~/.claude, ~/.cursor, etc.)",
669
+ localScope: "Local Project (./.agent/skills)",
670
+ linkMethod: "Symlink (Changes reflect instantly)",
671
+ copyMethod: "Full Copy (Self-contained)",
672
+ installing: "Installing {name}...",
673
+ processing: "Processing {name}...",
674
+ success: "{count} skill(s) installed successfully!",
675
+ authRequired: "Authentication required for {domain}",
676
+ enterToken: "Please enter Access Token for {domain}:",
677
+ authenticating: "Authenticating with {domain}...",
678
+ clonedSuccess: "Cloned successfully",
679
+ noAgents: "No AI agents detected for the selected scope.",
680
+ linkedTo: "Linked to: {agents}",
681
+ notLinked: "Not linked to any agent",
682
+ source: "Source:",
683
+ installed: "INSTALLED",
684
+ devLink: "DEV LINK",
685
+ unsupported: "Invalid git URL: {url}",
686
+ back: "Back",
687
+ backHint: "(Press Esc or select Back to go back)",
688
+ selectAtLeastOne: "Please select at least one skill."
689
+ },
690
+ zh: {
691
+ analyzing: "\u6B63\u5728\u5206\u6790:",
692
+ fetching: "\u6B63\u5728\u4ECE\u6E90\u83B7\u53D6\u6280\u80FD...",
693
+ sourceReady: "\u83B7\u53D6\u6210\u529F",
694
+ foundSkills: "\u53D1\u73B0 {count} \u4E2A\u6280\u80FD:",
695
+ selectSkills: "\u8BF7\u9009\u62E9\u8981\u5B89\u88C5\u7684\u6280\u80FD",
696
+ selectScope: "\u60A8\u5E0C\u671B\u5B89\u88C5\u5230\u54EA\u91CC\uFF1F",
697
+ selectMethod: "\u60A8\u5E0C\u671B\u5982\u4F55\u5B89\u88C5\uFF1F",
698
+ globalScope: "\u5168\u5C40\u76EE\u5F55 (~/.claude, ~/.cursor \u7B49)",
699
+ localScope: "\u5F53\u524D\u9879\u76EE (./.agent/skills)",
700
+ linkMethod: "\u8F6F\u94FE\u63A5 (\u4FEE\u6539\u5B9E\u65F6\u751F\u6548)",
701
+ copyMethod: "\u5168\u91CF\u590D\u5236 (\u79BB\u7EBF/\u72EC\u7ACB)",
702
+ installing: "\u6B63\u5728\u5B89\u88C5 {name}...",
703
+ processing: "\u6B63\u5728\u5904\u7406 {name}...",
704
+ success: "\u6210\u529F\u5B89\u88C5\u4E86 {count} \u4E2A\u6280\u80FD\uFF01",
705
+ authRequired: "\u9700\u8981\u9274\u6743: {domain}",
706
+ enterToken: "\u8BF7\u8F93\u5165 {domain} \u7684\u8BBF\u95EE\u4EE4\u724C (Access Token):",
707
+ authenticating: "\u6B63\u5728\u9A8C\u8BC1 {domain} \u7684\u9274\u6743\u4FE1\u606F...",
708
+ clonedSuccess: "\u83B7\u53D6\u6210\u529F",
709
+ noAgents: "\u5728\u9009\u5B9A\u8303\u56F4\u5185\u672A\u68C0\u6D4B\u5230 AI Agent \u76EE\u5F55\u3002",
710
+ linkedTo: "\u5DF2\u94FE\u63A5\u81F3: {agents}",
711
+ notLinked: "\u672A\u94FE\u63A5\u5230\u4EFB\u4F55 Agent",
712
+ source: "\u6E90\u8DEF\u5F84:",
713
+ installed: "\u5DF2\u5B89\u88C5",
714
+ devLink: "\u5F00\u53D1\u94FE\u63A5",
715
+ unsupported: "\u65E0\u6548\u7684 Git URL: {url}",
716
+ back: "\u8FD4\u56DE",
717
+ backHint: "(\u6309 Esc \u6216\u9009\u62E9\u201C\u8FD4\u56DE\u201D\u4EE5\u56DE\u5230\u4E0A\u4E00\u6B65)",
718
+ selectAtLeastOne: "\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u6280\u80FD\u3002"
719
+ }
720
+ };
721
+ function t(key, params = {}) {
722
+ const lang = getLang();
723
+ let text = translations[lang][key] || translations.en[key] || key;
724
+ for (const [p, val] of Object.entries(params)) {
725
+ text = text.replace(`{${p}}`, String(val));
726
+ }
727
+ return text;
728
+ }
729
+
606
730
  // src/commands/install.ts
607
- var execAsync2 = promisify2(exec2);
731
+ var { MultiSelect, Password, Select } = enquirer;
732
+ var execAsync = promisify(exec);
608
733
  async function execWithTimeout(command, timeout = 12e4, env) {
609
734
  let timeoutId = null;
610
735
  const timeoutPromise = new Promise((_, reject) => {
611
- timeoutId = setTimeout(() => {
612
- reject(new Error(`Command timeout after ${timeout / 1e3}s`));
613
- }, timeout);
736
+ timeoutId = setTimeout(() => reject(new Error(`Timeout ${timeout / 1e3}s`)), timeout);
614
737
  });
615
738
  try {
616
- const execOptions = env ? { env } : {};
617
- const result = await Promise.race([execAsync2(command, execOptions), timeoutPromise]);
618
- if (timeoutId) {
619
- clearTimeout(timeoutId);
620
- }
621
- return result;
622
- } catch (error) {
623
- if (timeoutId) {
624
- clearTimeout(timeoutId);
625
- }
626
- throw error;
739
+ const res = await Promise.race([execAsync(command, env ? { env } : {}), timeoutPromise]);
740
+ if (timeoutId) clearTimeout(timeoutId);
741
+ return res;
742
+ } catch (e) {
743
+ if (timeoutId) clearTimeout(timeoutId);
744
+ throw e;
627
745
  }
628
746
  }
747
+ async function promptSkills(skills) {
748
+ const prompt = new MultiSelect({
749
+ name: "value",
750
+ message: t("selectSkills"),
751
+ choices: skills.map((s) => ({
752
+ name: s.name,
753
+ enabled: true,
754
+ message: `${s.name.substring(0, 20)}${chalk3.dim((s.description ? " - " + s.description : "").substring(0, 40))}`
755
+ })),
756
+ pointer(state, choice) {
757
+ return state.index === choice.index ? chalk3.cyan("\u276F") : " ";
758
+ },
759
+ indicator(state, choice) {
760
+ return choice.enabled ? chalk3.green("[\u2714]") : chalk3.gray("[ ]");
761
+ },
762
+ footer: chalk3.dim(`
763
+ (\u7A7A\u683C\u52FE\u9009, A \u5168\u9009, Enter \u786E\u8BA4, Esc \u9000\u51FA)`),
764
+ validate(value) {
765
+ if (value.length === 0) return chalk3.red(t("selectAtLeastOne"));
766
+ return true;
767
+ }
768
+ });
769
+ return prompt.run();
770
+ }
771
+ async function promptSelect(message, choices) {
772
+ const prompt = new Select({
773
+ name: "value",
774
+ message,
775
+ choices: [...choices, { name: "back", message: chalk3.yellow("\u2190 " + t("back")) }],
776
+ pointer(state, choice) {
777
+ return state.index === choice.index ? chalk3.cyan("\u276F") : " ";
778
+ },
779
+ footer: chalk3.dim(`
780
+ ${t("backHint")}`)
781
+ });
782
+ return prompt.run();
783
+ }
629
784
  async function install(skillNameOrPath, options = {}) {
630
785
  const isGit = isGitUrl(skillNameOrPath);
631
- if (isGit) {
632
- logger.info("Installing from Git URL");
633
- logger.dim(skillNameOrPath);
634
- } else {
635
- logger.info(`Installing skill: ${skillNameOrPath}`);
636
- }
786
+ logger.info(`${chalk3.cyan("\u{1F50D}")} ${t("analyzing")} ${chalk3.bold(skillNameOrPath)}`);
637
787
  ensureSharedDir();
638
- let skillPath;
639
- let skillName;
640
- if (process.env.DEBUG_ESKILL) {
641
- logger.dim(`[DEBUG] isGitUrl=${isGit}, parseGitUrl=${parseGitUrl(skillNameOrPath) ? "ok" : "null"}`);
642
- }
788
+ let tempDir = "";
789
+ let cloneDir = "";
790
+ let availableSkills = [];
643
791
  if (isGit) {
644
792
  const gitInfo = parseGitUrl(skillNameOrPath);
645
793
  if (!gitInfo) {
646
- logger.error(`Invalid git URL: ${skillNameOrPath}`);
647
- logger.info("Please ensure the URL is a valid GitHub or GitLab repository URL");
794
+ logger.error(t("unsupported", { url: skillNameOrPath }));
648
795
  process.exit(1);
649
796
  }
650
- skillName = gitInfo.path ? extractSkillName(path5.basename(gitInfo.path)) : extractSkillName(gitInfo.repo);
651
- const tempDir = path5.join("/tmp", `eskill-${Date.now()}`);
652
- const cloneDir = path5.join(tempDir, "repo");
797
+ tempDir = path6.join("/tmp", `eskill-${Date.now()}`);
798
+ cloneDir = path6.join(tempDir, "repo");
653
799
  try {
654
- const timeout = options.timeout || 12e4;
655
- const gitDetails = [`${gitInfo.gitUrl}`];
656
- if (gitInfo.branch) gitDetails.push(`branch: ${gitInfo.branch}`);
657
- if (gitInfo.path) gitDetails.push(`path: ${gitInfo.path}`);
658
- logger.dim(gitDetails.join(" \xB7 "));
659
- const spinner = logger.start(`Cloning...`);
660
- fs6.mkdirSync(tempDir, { recursive: true });
661
- const branchFlag = gitInfo.branch ? `-b ${gitInfo.branch}` : "";
662
- const cloneCommand = branchFlag ? `git clone ${branchFlag} ${gitInfo.gitUrl} ${cloneDir} --depth 1 --quiet` : `git clone ${gitInfo.gitUrl} ${cloneDir} --depth 1 --quiet`;
800
+ const spinner = logger.start(t("fetching"));
801
+ fs7.mkdirSync(tempDir, { recursive: true });
802
+ const domain = new URL(gitInfo.gitUrl).hostname;
803
+ let gitUrl = gitInfo.gitUrl;
804
+ const token = getToken(domain) || process.env[`ESKILL_TOKEN_${domain.toUpperCase().replace(/\./g, "_")}`];
805
+ const doClone = async (u) => execWithTimeout(`git clone ${gitInfo.branch ? "-b " + gitInfo.branch : ""} ${u} ${cloneDir} --depth 1 --quiet`, options.timeout || 12e4);
663
806
  try {
664
- await execWithTimeout(cloneCommand, timeout);
665
- spinner.succeed(`Cloned successfully`);
666
- } catch (error) {
667
- spinner.fail("Clone failed");
668
- if (error.message.includes("timeout")) {
669
- logger.error(`Clone timeout after ${timeout / 1e3} seconds`);
670
- logger.info("");
671
- logger.info("Possible reasons:");
672
- logger.info(" - Slow network connection");
673
- logger.info(" - Large repository size");
674
- logger.info(" - Git server issues");
675
- logger.info("");
676
- logger.info(`Try again or increase timeout: eskill add ${skillNameOrPath} --timeout=300000`);
677
- }
678
- throw error;
679
- }
680
- if (gitInfo.path) {
681
- skillPath = path5.join(cloneDir, gitInfo.path);
682
- if (!fs6.existsSync(skillPath)) {
683
- logger.error(`Path not found in repository: ${gitInfo.path}`);
684
- logger.info(`Repository cloned to: ${cloneDir}`);
685
- process.exit(1);
807
+ let finalUrl = gitUrl;
808
+ if (token && gitUrl.startsWith("https://")) finalUrl = gitUrl.replace("https://", `https://oauth2:${token}@`);
809
+ await doClone(finalUrl);
810
+ spinner.succeed(t("sourceReady"));
811
+ } catch (e) {
812
+ const sshUrl = convertToSshUrl(gitUrl);
813
+ if (sshUrl) {
814
+ try {
815
+ await doClone(sshUrl);
816
+ spinner.succeed(t("sourceReady"));
817
+ } catch {
818
+ spinner.stop();
819
+ logger.warn(`
820
+ \u{1F512} ${t("authRequired", { domain })}`);
821
+ const newToken = await new Password({ message: t("enterToken", { domain }), validate: (v) => v.length > 0 }).run();
822
+ saveToken(domain, newToken);
823
+ const authedUrl = gitUrl.replace("https://", `https://oauth2:${newToken}@`);
824
+ const fs10 = logger.start(t("fetching"));
825
+ await performClone(authedUrl);
826
+ fs10.succeed(t("sourceReady"));
827
+ }
828
+ } else {
829
+ spinner.fail("Clone failed");
830
+ throw e;
686
831
  }
687
- } else {
688
- skillPath = cloneDir;
689
- }
690
- const skillMdPath = path5.join(skillPath, "SKILL.md");
691
- if (!fs6.existsSync(skillMdPath)) {
692
- logger.warn(`Warning: SKILL.md not found in ${skillPath}`);
693
- logger.info("The directory may not be a valid skill package");
694
832
  }
833
+ availableSkills = scanForSkills(gitInfo.path ? path6.join(cloneDir, gitInfo.path) : cloneDir);
695
834
  } catch (error) {
696
- logger.error(`Failed to clone repository: ${error.message}`);
697
- if (error.message.includes("timeout")) {
698
- logger.error(`Clone timeout after 2 minutes`);
699
- logger.info("This might be due to:");
700
- logger.info(" - Slow network connection");
701
- logger.info(" - Large repository size");
702
- logger.info(" - Git server issues");
703
- logger.info(`
704
- Try again or check your network connection`);
705
- }
706
- logger.info(`
707
- Tried to clone: ${gitInfo.gitUrl}`);
708
- if (gitInfo.branch) {
709
- logger.info(`Branch: ${gitInfo.branch}`);
710
- }
835
+ logger.error(`Error: ${error.message}`);
711
836
  process.exit(1);
712
837
  }
713
- } else if (options.link || fs6.existsSync(skillNameOrPath)) {
714
- skillPath = path5.resolve(skillNameOrPath);
715
- if (!fs6.existsSync(skillPath)) {
838
+ } else {
839
+ const skillPath = path6.resolve(skillNameOrPath);
840
+ if (!fs7.existsSync(skillPath)) {
716
841
  logger.error(`Path not found: ${skillPath}`);
717
842
  process.exit(1);
718
843
  }
719
- const pkgPath = path5.join(skillPath, "package.json");
720
- if (fs6.existsSync(pkgPath)) {
721
- try {
722
- const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
723
- skillName = extractSkillName(pkg.name);
724
- } catch {
725
- skillName = extractSkillName(path5.basename(skillPath));
726
- }
727
- } else {
728
- skillName = extractSkillName(path5.basename(skillPath));
729
- }
730
- } else {
731
- skillName = extractSkillName(skillNameOrPath);
732
- const tempDir = path5.join("/tmp", `eskill-${Date.now()}`);
844
+ availableSkills = scanForSkills(skillPath);
845
+ }
846
+ if (availableSkills.length === 0) {
847
+ logger.error("No skills found");
848
+ process.exit(1);
849
+ }
850
+ let step = availableSkills.length === 1 ? 1 : 0;
851
+ let selectedNames = [availableSkills[0]?.name];
852
+ let installScope = options.local ? "local" : "global";
853
+ let installMethod = options.copy ? "copy" : "link";
854
+ while (step < 3) {
733
855
  try {
734
- const registry = options.registry || await getRegistry();
735
- const timeout = options.timeout || 18e4;
736
- const spinner = logger.start(`Installing ${skillNameOrPath}...`);
737
- logger.infoWithoutStop(`Registry: ${registry}`);
738
- logger.infoWithoutStop(`Timeout: ${timeout / 1e3}s`);
739
- fs6.mkdirSync(tempDir, { recursive: true });
740
- logger.updateSpinner(`Downloading ${skillNameOrPath} from ${registry}...`);
741
- const homeDir = process.env.HOME || process.env.USERPROFILE || os3.homedir();
742
- const npmCacheDir = path5.join(homeDir, ".npm");
743
- const npmConfigPrefix = path5.join(homeDir, ".npm-global");
744
- fs6.mkdirSync(npmCacheDir, { recursive: true });
745
- const installCommand = `npm install ${skillNameOrPath} --prefix ${tempDir} --registry=${registry} --no-save --silent --no-bin-links --prefer-offline`;
746
- const env = {
747
- ...process.env,
748
- npm_config_cache: npmCacheDir,
749
- npm_config_prefix: npmConfigPrefix,
750
- npm_config_global: "false"
751
- };
752
- try {
753
- await execWithTimeout(installCommand, timeout, env);
754
- spinner.succeed(`Package ${skillNameOrPath} downloaded successfully`);
755
- } catch (error) {
756
- spinner.fail("Download failed");
757
- const errorMessage = error.message || error.stderr || "";
758
- if (errorMessage.includes("timeout")) {
759
- logger.error(`Download timeout after ${timeout / 1e3} seconds`);
760
- logger.info("");
761
- logger.info("Possible reasons:");
762
- logger.info(" - Slow network connection");
763
- logger.info(" - Registry server issues or unresponsive");
764
- logger.info(" - Large package size");
765
- logger.info(" - Network firewall blocking the connection");
766
- logger.info("");
767
- logger.info("Suggestions:");
768
- logger.info(` - Try again: eskill add ${skillNameOrPath}`);
769
- logger.info(
770
- ` - Use a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`
771
- );
772
- logger.info(` - Increase timeout: eskill add ${skillNameOrPath} --timeout=300000`);
773
- } else if (errorMessage.includes("EACCES") || errorMessage.includes("permission denied") || errorMessage.includes("Permission denied")) {
774
- logger.error("Permission denied error detected");
775
- logger.info("");
776
- logger.info("This error occurs when npm tries to access system directories.");
777
- logger.info("");
778
- logger.info("\u{1F527} Quick Fix (Recommended):");
779
- logger.info("");
780
- logger.info("1. Configure npm to use user directory:");
781
- logger.info(` mkdir -p ${npmConfigPrefix}`);
782
- logger.info(` npm config set prefix '${npmConfigPrefix}'`);
783
- logger.info("");
784
- logger.info("2. Add to your ~/.zshrc (or ~/.bash_profile):");
785
- logger.info(` export PATH="${npmConfigPrefix}/bin:$PATH"`);
786
- logger.info("");
787
- logger.info("3. Reload shell configuration:");
788
- logger.info(" source ~/.zshrc");
789
- logger.info("");
790
- logger.info("\u{1F4DA} Alternative Solutions:");
791
- logger.info("");
792
- logger.info("Option A: Use NVM (Node Version Manager)");
793
- logger.info(" curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash");
794
- logger.info(" nvm install --lts");
795
- logger.info("");
796
- logger.info("Option B: Fix system directory permissions (requires sudo)");
797
- logger.info(" sudo chown -R $(whoami) /usr/local/lib/node_modules");
798
- logger.info("");
799
- logger.info("Option C: Use sudo (not recommended for security reasons)");
800
- logger.info(` sudo npm install -g @empjs/skill --registry=${registry}`);
801
- logger.info("");
802
- logger.info("After fixing, try again:");
803
- logger.info(` eskill add ${skillNameOrPath}`);
804
- } else if (errorMessage.includes("ENOTFOUND") || errorMessage.includes("ECONNREFUSED")) {
805
- logger.error(`Network connection error: ${errorMessage}`);
806
- logger.info("");
807
- logger.info("Please check:");
808
- logger.info(" - Your internet connection");
809
- logger.info(` - Registry accessibility: ${registry}`);
810
- logger.info(
811
- ` - Try a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`
812
- );
813
- } else {
814
- logger.error(`Failed to download: ${errorMessage}`);
815
- logger.info("");
816
- logger.info("If this is a permission error, see the solutions above.");
817
- logger.info("For other errors, please check:");
818
- logger.info(" - Package name is correct");
819
- logger.info(" - Registry is accessible");
820
- logger.info(` - Try: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`);
856
+ if (step === 0) {
857
+ logger.info(`
858
+ \u{1F4E6} ${t("foundSkills", { count: availableSkills.length })}`);
859
+ selectedNames = await promptSkills(availableSkills);
860
+ step = 1;
861
+ } else if (step === 1) {
862
+ if (options.global || options.local) {
863
+ step = 2;
864
+ continue;
865
+ }
866
+ installScope = await promptSelect(t("selectScope"), [{ name: "global", message: t("globalScope") }, { name: "local", message: t("localScope") }]);
867
+ if (installScope === "back") {
868
+ step = availableSkills.length === 1 ? -1 : 0;
869
+ continue;
870
+ }
871
+ step = 2;
872
+ } else if (step === 2) {
873
+ if (options.link || options.copy) {
874
+ step = 3;
875
+ continue;
821
876
  }
822
- process.exit(1);
877
+ installMethod = await promptSelect(t("selectMethod"), [{ name: "link", message: t("linkMethod") }, { name: "copy", message: t("copyMethod") }]);
878
+ if (installMethod === "back") {
879
+ step = 1;
880
+ continue;
881
+ }
882
+ step = 3;
823
883
  }
824
- skillPath = path5.join(tempDir, "node_modules", skillNameOrPath);
825
- if (!fs6.existsSync(skillPath)) {
826
- logger.error(`Failed to download package: ${skillNameOrPath}`);
827
- process.exit(1);
884
+ } catch (e) {
885
+ if (step === 0 || step === -1) {
886
+ logger.warn("\nInstallation cancelled.");
887
+ process.exit(0);
828
888
  }
829
- } catch (error) {
830
- logger.error(`Failed to download: ${error.message}`);
831
- process.exit(1);
832
- }
833
- }
834
- const targetPath = getSharedSkillPath(skillName);
835
- const sourceIsTarget = path5.resolve(skillPath) === path5.resolve(targetPath);
836
- const alreadyExists = fs6.existsSync(targetPath) && !sourceIsTarget;
837
- if (alreadyExists) {
838
- if (options.force) {
839
- logger.warn("Removing existing installation...");
840
- fs6.rmSync(targetPath, { recursive: true, force: true });
841
- } else {
842
- logger.info(`Skill already exists, updating agent links...`);
889
+ step--;
890
+ continue;
843
891
  }
844
892
  }
845
- if (sourceIsTarget) {
846
- logger.info(`Skill already in shared directory, updating agent links...`);
847
- } else if (alreadyExists && !options.force) {
848
- } else if (options.link) {
849
- fs6.symlinkSync(skillPath, targetPath, "dir");
850
- logger.success(`Linked to shared directory (dev mode)`);
851
- } else {
852
- copyDir2(skillPath, targetPath);
853
- logger.success(`Installed to shared directory`);
854
- }
855
- logger.info(`\u{1F4CD} Location: ${targetPath}`);
893
+ options.local = installScope === "local";
894
+ options.global = installScope === "global";
895
+ options.copy = installMethod === "copy";
896
+ options.link = installMethod === "link";
897
+ const selectedSkills = selectedNames.map((n) => availableSkills.find((s) => s.name === n)).filter(Boolean);
856
898
  const cwd = process.cwd();
857
- const installedAgents = detectInstalledAgents(cwd);
858
- if (installedAgents.length === 0) {
859
- logger.warn("No AI agents detected");
860
- logger.info("Skill installed to shared directory, but not linked to any agent");
861
- logger.info("");
862
- logger.info("Supported agents:");
863
- logger.info(" AMP, Antigravity, Claude Code, ClawdBot, Cline, Codex, Cursor, Droid,");
864
- logger.info(" Gemini, GitHub Copilot, Goose, Kilo, Kiro CLI, OpenCode, Roo, Trae, Windsurf");
865
- logger.info("");
866
- logger.info('Run "eskill agents" to see all agent directories');
867
- return;
899
+ let agents2 = detectInstalledAgents(cwd);
900
+ if (options.local) {
901
+ agents2 = agents2.map((a) => ({ ...a, skillsDirs: (c) => (typeof a.skillsDirs === "function" ? a.skillsDirs(c) : []).filter((d) => d.includes(c || cwd)) })).filter((a) => (typeof a.skillsDirs === "function" ? a.skillsDirs(cwd) : []).length > 0);
868
902
  }
869
- let targetAgents = installedAgents;
870
- if (options.agent && options.agent !== "all") {
871
- const agent = AGENTS.find((a) => a.name === options.agent && a.enabled);
872
- if (!agent) {
873
- logger.error(`Unknown agent: ${options.agent}`);
874
- logger.info(`
875
- Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", ")}`);
876
- process.exit(1);
877
- }
878
- targetAgents = [agent];
879
- if (!installedAgents.find((a) => a.name === options.agent)) {
880
- logger.info(`Note: ${agent.displayName} directory will be created if not exists`);
881
- }
903
+ for (const skill of selectedSkills) {
904
+ logger.info(`
905
+ \u{1F680} ${t("installing", { name: skill.name })}`);
906
+ await installFromLocalPath(skill.path, skill.name, options, agents2, cwd);
882
907
  }
883
- logger.info("\nCreating symlinks...");
884
- let successCount = 0;
885
- for (const agent of targetAgents) {
886
- if (createSymlink(skillName, agent, cwd)) {
887
- successCount++;
908
+ logger.info("");
909
+ logger.success(`\u2705 ${t("success", { count: selectedSkills.length })}`);
910
+ }
911
+ async function installFromLocalPath(skillPath, skillName, options, installedAgents, cwd) {
912
+ const targetPath = getSharedSkillPath(skillName);
913
+ const sourceIsTarget = path6.resolve(skillPath) === path6.resolve(targetPath);
914
+ if (fs7.existsSync(targetPath) && !sourceIsTarget && options.force) fs7.rmSync(targetPath, { recursive: true, force: true });
915
+ if (!sourceIsTarget && (!fs7.existsSync(targetPath) || options.force)) {
916
+ if (options.copy) copyDir2(skillPath, targetPath);
917
+ else {
918
+ try {
919
+ fs7.symlinkSync(skillPath, targetPath, "dir");
920
+ } catch {
921
+ copyDir2(skillPath, targetPath);
922
+ }
888
923
  }
889
924
  }
890
- logger.info("");
891
- logger.success(`\u2705 Skill installed successfully!`);
892
- logger.info(`
893
- Linked to ${successCount}/${targetAgents.length} agents`);
894
- if (options.link) {
895
- logger.info("\n\u{1F4A1} Dev mode: changes to source files will reflect immediately");
925
+ let targets = installedAgents;
926
+ if (options.agent && options.agent !== "all") {
927
+ const a = AGENTS.find((a2) => a2.name === options.agent && a2.enabled);
928
+ if (a) targets = [a];
929
+ }
930
+ if (targets.length > 0) {
931
+ let count = 0;
932
+ for (const a of targets) if (createSymlink(skillName, a, cwd, options.copy || a.useCopyInsteadOfSymlink)) count++;
933
+ logger.dim(`${skillName} -> ${count} agent(s)`);
896
934
  }
897
935
  }
898
936
  function copyDir2(src, dest) {
899
- fs6.mkdirSync(dest, { recursive: true });
900
- const entries = fs6.readdirSync(src, { withFileTypes: true });
901
- for (const entry of entries) {
902
- const srcPath = path5.join(src, entry.name);
903
- const destPath = path5.join(dest, entry.name);
904
- if (entry.name === "node_modules" || entry.name.startsWith(".")) {
905
- continue;
906
- }
907
- if (entry.isDirectory()) {
908
- copyDir2(srcPath, destPath);
909
- } else {
910
- fs6.copyFileSync(srcPath, destPath);
911
- }
937
+ fs7.mkdirSync(dest, { recursive: true });
938
+ for (const e of fs7.readdirSync(src, { withFileTypes: true })) {
939
+ if (e.name === "node_modules" || e.name.startsWith(".")) continue;
940
+ const s = path6.join(src, e.name);
941
+ const d = path6.join(dest, e.name);
942
+ if (e.isDirectory()) copyDir2(s, d);
943
+ else fs7.copyFileSync(s, d);
912
944
  }
913
945
  }
914
946
 
915
947
  // src/commands/list.ts
916
- import fs7 from "fs";
917
- import path6 from "path";
918
- import chalk3 from "chalk";
948
+ import fs8 from "fs";
949
+ import path7 from "path";
950
+ import os4 from "os";
951
+ import chalk4 from "chalk";
952
+ function shortenPath(p) {
953
+ const home = os4.homedir();
954
+ return p.startsWith(home) ? p.replace(home, "~") : p;
955
+ }
919
956
  function list() {
920
- if (!fs7.existsSync(SHARED_SKILLS_DIR)) {
957
+ if (!fs8.existsSync(SHARED_SKILLS_DIR)) {
921
958
  logger.info("No skills installed");
922
959
  logger.info(`
923
- To install a skill, run: ${chalk3.cyan("eskill install <skill-name>")}`);
960
+ To install a skill, run: ${chalk4.cyan("eskill install <skill-name>")}`);
924
961
  return;
925
962
  }
926
- const skills = fs7.readdirSync(SHARED_SKILLS_DIR);
963
+ const skills = fs8.readdirSync(SHARED_SKILLS_DIR).filter((s) => !s.startsWith("."));
927
964
  if (skills.length === 0) {
928
965
  logger.info("No skills installed");
929
966
  logger.info(`
930
- To install a skill, run: ${chalk3.cyan("eskill install <skill-name>")}`);
967
+ To install a skill, run: ${chalk4.cyan("eskill install <skill-name>")}`);
931
968
  return;
932
969
  }
933
- console.log(chalk3.bold(`
934
- Installed skills in ${SHARED_SKILLS_DIR}:
970
+ console.log(chalk4.bold(`
971
+ \u{1F4E6} Installed Skills in ${chalk4.blue(shortenPath(SHARED_SKILLS_DIR))}:
935
972
  `));
936
973
  for (const skill of skills) {
937
- const skillPath = path6.join(SHARED_SKILLS_DIR, skill);
974
+ const skillPath = path7.join(SHARED_SKILLS_DIR, skill);
938
975
  try {
939
- const stats = fs7.lstatSync(skillPath);
976
+ const stats = fs8.lstatSync(skillPath);
940
977
  if (!stats.isDirectory() && !stats.isSymbolicLink()) {
941
978
  continue;
942
979
  }
943
980
  let version2 = "unknown";
944
- const pkgPath = path6.join(skillPath, "package.json");
945
- if (fs7.existsSync(pkgPath)) {
981
+ let description = "";
982
+ const pkgPath = path7.join(skillPath, "package.json");
983
+ if (fs8.existsSync(pkgPath)) {
946
984
  try {
947
- const pkgContent = fs7.readFileSync(pkgPath, "utf-8");
948
- const pkg = JSON.parse(pkgContent);
949
- if (pkg.version && typeof pkg.version === "string") {
950
- version2 = pkg.version;
951
- }
985
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
986
+ if (pkg.version) version2 = pkg.version;
987
+ if (pkg.description) description = pkg.description;
952
988
  } catch (error) {
953
989
  }
954
990
  }
955
- if (version2 === "unknown") {
956
- const skillMdPath = path6.join(skillPath, "SKILL.md");
957
- if (fs7.existsSync(skillMdPath)) {
958
- try {
959
- const skillMdContent = fs7.readFileSync(skillMdPath, "utf-8");
960
- const frontmatterMatch = skillMdContent.match(/^---\s*\n([\s\S]*?)\n---/);
961
- if (frontmatterMatch) {
962
- const frontmatter = frontmatterMatch[1];
963
- const versionMatch = frontmatter.match(/^version:\s*(.+)$/m);
964
- if (versionMatch) {
965
- version2 = versionMatch[1].trim().replace(/^["']|["']$/g, "");
966
- }
967
- }
968
- } catch (error) {
969
- }
970
- }
971
- }
972
- if (version2 === "unknown" && isSymlink(skillPath)) {
973
- try {
974
- const targetPath = readSymlink(skillPath);
975
- if (targetPath) {
976
- const targetPkgPath = path6.join(targetPath, "package.json");
977
- if (fs7.existsSync(targetPkgPath)) {
978
- const pkg = JSON.parse(fs7.readFileSync(targetPkgPath, "utf-8"));
979
- if (pkg.version && typeof pkg.version === "string") {
980
- version2 = pkg.version;
981
- }
982
- }
991
+ if (!description) {
992
+ const skillMdPath = path7.join(skillPath, "SKILL.md");
993
+ if (fs8.existsSync(skillMdPath)) {
994
+ const content = fs8.readFileSync(skillMdPath, "utf-8");
995
+ const descMatch = content.match(/description:\s*["']?([^"'\n]+)["']?/);
996
+ if (descMatch) description = descMatch[1];
997
+ if (version2 === "unknown") {
998
+ const verMatch = content.match(/version:\s*(.+)$/m);
999
+ if (verMatch) version2 = verMatch[1].trim().replace(/^["']|["']$/g, "");
983
1000
  }
984
- } catch (error) {
985
1001
  }
986
1002
  }
987
1003
  const isDev = isSymlink(skillPath);
988
- const devTag = isDev ? chalk3.yellow(" (dev)") : "";
989
- const versionDisplay = version2 !== "unknown" ? chalk3.gray(`(v${version2})`) : "";
990
- console.log(chalk3.green("\u{1F4E6}") + ` ${chalk3.bold(skill)}${versionDisplay ? " " + versionDisplay : ""}${devTag}`);
1004
+ const typeLabel = isDev ? chalk4.bgYellow.black(" DEV LINK ") : chalk4.bgBlue.white(" INSTALLED ");
1005
+ const versionDisplay = version2 !== "unknown" ? chalk4.gray(`v${version2}`) : "";
1006
+ const descDisplay = description ? chalk4.dim(` - ${description}`) : "";
1007
+ console.log(`${chalk4.green("\u25CF")} ${chalk4.bold(skill)} ${versionDisplay} ${typeLabel}${descDisplay}`);
991
1008
  const cwd = process.cwd();
992
1009
  const linkedAgents = [];
993
1010
  for (const agent of AGENTS) {
994
1011
  const skillsDirs = getAgentSkillsDirs(agent, cwd);
995
1012
  const hasRef = skillsDirs.some((dir) => {
996
- const agentSkillPath = path6.join(dir, skill);
997
- if (!fs7.existsSync(agentSkillPath)) return false;
1013
+ const agentSkillPath = path7.join(dir, skill);
1014
+ if (!fs8.existsSync(agentSkillPath)) return false;
998
1015
  if (isSymlink(agentSkillPath)) {
999
1016
  const target = readSymlink(agentSkillPath);
1000
1017
  return target === skillPath;
1001
1018
  }
1002
1019
  if (agent.useCopyInsteadOfSymlink) {
1003
- return fs7.statSync(agentSkillPath).isDirectory();
1020
+ return fs8.statSync(agentSkillPath).isDirectory();
1004
1021
  }
1005
1022
  return false;
1006
1023
  });
1007
- if (hasRef) {
1008
- linkedAgents.push(agent.displayName);
1009
- }
1024
+ if (hasRef) linkedAgents.push(agent.displayName);
1010
1025
  }
1011
1026
  if (linkedAgents.length > 0) {
1012
- console.log(chalk3.gray(` \u2192 Linked to: ${linkedAgents.join(", ")}`));
1027
+ console.log(chalk4.gray(` \u{1F517} Linked to: ${chalk4.white(linkedAgents.join(", "))}`));
1013
1028
  } else {
1014
- console.log(chalk3.yellow(` \u2192 Not linked to any agent`));
1029
+ console.log(chalk4.red(` \u26A0\uFE0F Not linked to any agent`));
1015
1030
  }
1016
1031
  if (isDev) {
1017
1032
  const target = readSymlink(skillPath);
1018
1033
  if (target) {
1019
- console.log(chalk3.gray(` \u2192 Source: ${target}`));
1034
+ console.log(chalk4.gray(` \u{1F4C1} Source: ${chalk4.dim(shortenPath(target))}`));
1020
1035
  }
1021
1036
  }
1022
1037
  console.log("");
@@ -1026,11 +1041,11 @@ Installed skills in ${SHARED_SKILLS_DIR}:
1026
1041
  }
1027
1042
 
1028
1043
  // src/commands/remove.ts
1029
- import fs8 from "fs";
1044
+ import fs9 from "fs";
1030
1045
  async function remove(skillName, options = {}) {
1031
1046
  const extractedName = extractSkillName(skillName);
1032
1047
  const sharedPath = getSharedSkillPath(extractedName);
1033
- const skillExists = fs8.existsSync(sharedPath);
1048
+ const skillExists = fs9.existsSync(sharedPath);
1034
1049
  if (!skillExists) {
1035
1050
  logger.error(`Skill not found: ${extractedName}`);
1036
1051
  logger.info(`Location: ${sharedPath}`);
@@ -1066,12 +1081,12 @@ Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", "
1066
1081
  }
1067
1082
  logger.info("Removing skill from shared directory...");
1068
1083
  try {
1069
- const stats = fs8.lstatSync(sharedPath);
1084
+ const stats = fs9.lstatSync(sharedPath);
1070
1085
  if (stats.isSymbolicLink()) {
1071
- fs8.unlinkSync(sharedPath);
1086
+ fs9.unlinkSync(sharedPath);
1072
1087
  logger.success("Removed symlink from shared directory");
1073
1088
  } else {
1074
- fs8.rmSync(sharedPath, { recursive: true, force: true });
1089
+ fs9.rmSync(sharedPath, { recursive: true, force: true });
1075
1090
  logger.success("Removed skill from shared directory");
1076
1091
  }
1077
1092
  } catch (error) {
@@ -1081,7 +1096,7 @@ Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", "
1081
1096
  const remainingRefs = [];
1082
1097
  for (const agent of AGENTS) {
1083
1098
  const agentSkillPaths = getAgentSkillPaths(agent.name, extractedName, cwd);
1084
- const hasRef = agentSkillPaths.some((p) => fs8.existsSync(p));
1099
+ const hasRef = agentSkillPaths.some((p) => fs9.existsSync(p));
1085
1100
  if (hasRef) {
1086
1101
  remainingRefs.push(agent.displayName);
1087
1102
  }
@@ -1098,6 +1113,56 @@ Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", "
1098
1113
  }
1099
1114
  }
1100
1115
 
1116
+ // src/commands/auth.ts
1117
+ import enquirer2 from "enquirer";
1118
+ var { Input, Password: Password2 } = enquirer2;
1119
+ async function auth(domain, options = {}) {
1120
+ if (options.list) {
1121
+ const config = loadConfig();
1122
+ if (Object.keys(config.tokens).length === 0) {
1123
+ logger.info("No tokens configured.");
1124
+ return;
1125
+ }
1126
+ logger.info("\n\u{1F510} Configured Tokens:");
1127
+ for (const [dom, token] of Object.entries(config.tokens)) {
1128
+ const masked = token.substring(0, 4) + "*".repeat(token.length - 8) + token.substring(token.length - 4);
1129
+ logger.info(` - ${dom}: ${masked}`);
1130
+ }
1131
+ return;
1132
+ }
1133
+ if (options.remove) {
1134
+ const config = loadConfig();
1135
+ if (config.tokens[options.remove]) {
1136
+ delete config.tokens[options.remove];
1137
+ saveToken(options.remove, "");
1138
+ logger.success(`Removed token for ${options.remove}`);
1139
+ } else {
1140
+ logger.error(`No token found for ${options.remove}`);
1141
+ }
1142
+ return;
1143
+ }
1144
+ let targetDomain = domain;
1145
+ let targetToken = options.token;
1146
+ if (!targetDomain) {
1147
+ const prompt = new Input({
1148
+ message: "Enter the domain (e.g., git.internal.corp):",
1149
+ validate: (value) => value.length > 0
1150
+ });
1151
+ targetDomain = await prompt.run();
1152
+ }
1153
+ if (!targetToken) {
1154
+ const prompt = new Password2({
1155
+ message: `Enter Access Token for ${targetDomain}:`,
1156
+ validate: (value) => value.length > 0
1157
+ });
1158
+ targetToken = await prompt.run();
1159
+ }
1160
+ if (targetDomain && targetToken) {
1161
+ saveToken(targetDomain, targetToken);
1162
+ logger.success(`Token saved for ${targetDomain}`);
1163
+ }
1164
+ }
1165
+
1101
1166
  // src/index.ts
1102
1167
  var __filename2 = fileURLToPath(import.meta.url);
1103
1168
  var __dirname2 = dirname(__filename2);
@@ -1106,7 +1171,7 @@ var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1106
1171
  var version = packageJson.version;
1107
1172
  var program = new Command();
1108
1173
  program.name("eskill").description("Unified CLI tool for managing AI agent skills").version(version);
1109
- 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(
1174
+ program.command("install <skill>").alias("add").description("Install a skill (Supports selective install, local/global scope, and link/copy methods)").option("-a, --agent <name>", "Install for specific agent (claude, cursor, etc.)").option("--all", "Install all skills if multiple are found in a collection").option("--global", "Install to global agent directories (default)").option("--local", "Install to current project directory (.agent/skills)").option("--link", "Use symlink for installation (changes reflect instantly)").option("--copy", "Use full copy for installation (portable)").option("-f, --force", "Force reinstall if already exists").option("-r, --registry <url>", "NPM registry URL").option(
1110
1175
  "-t, --timeout <ms>",
1111
1176
  "Timeout in milliseconds (default: 180000 for npm, 120000 for git)",
1112
1177
  (value) => Number.parseInt(value, 10)
@@ -1118,6 +1183,14 @@ program.command("install <skill>").alias("add").description("Install a skill fro
1118
1183
  process.exit(1);
1119
1184
  }
1120
1185
  });
1186
+ program.command("auth [domain]").description("Manage access tokens for private repositories").option("-t, --token <token>", "Access token").option("-l, --list", "List all saved tokens").option("-r, --remove <domain>", "Remove token for a domain").action(async (domain, options) => {
1187
+ try {
1188
+ await auth(domain, options);
1189
+ } catch (error) {
1190
+ console.error("Error:", error.message);
1191
+ process.exit(1);
1192
+ }
1193
+ });
1121
1194
  program.command("list").alias("ls").description("List installed skills").action(() => {
1122
1195
  try {
1123
1196
  list();