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