@empjs/skill 1.1.1 → 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 +506 -445
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +506 -445
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -237,11 +237,12 @@ function agents() {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// src/commands/install.ts
|
|
240
|
-
var
|
|
241
|
-
var
|
|
242
|
-
var
|
|
243
|
-
var
|
|
244
|
-
var
|
|
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
|
|
272
|
-
let gitUrl;
|
|
272
|
+
let path8;
|
|
273
273
|
if (i === 0) {
|
|
274
274
|
branch = match[3];
|
|
275
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
307
|
+
path8 = match[4];
|
|
309
308
|
const parts = repoPath.split("/");
|
|
310
309
|
repo = parts.pop() || repoPath;
|
|
311
310
|
owner = parts.join("/") || repo;
|
|
@@ -322,31 +321,19 @@ function parseGitUrl(url) {
|
|
|
322
321
|
const parts = repoPath.split("/");
|
|
323
322
|
repo = parts.pop() || repoPath;
|
|
324
323
|
owner = parts.join("/") || repo;
|
|
325
|
-
gitUrl =
|
|
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:
|
|
331
|
+
path: path8,
|
|
333
332
|
gitUrl,
|
|
334
333
|
installUrl: gitUrl
|
|
335
334
|
};
|
|
336
335
|
}
|
|
337
336
|
}
|
|
338
|
-
if (url.startsWith("ssh://")) {
|
|
339
|
-
const repoMatch = url.match(/\/([^/]+)(?:\.git)?\/?$/);
|
|
340
|
-
const repo = repoMatch ? repoMatch[1].replace(/\.git$/, "") : "repo";
|
|
341
|
-
return {
|
|
342
|
-
type: "other",
|
|
343
|
-
owner: "",
|
|
344
|
-
repo,
|
|
345
|
-
branch: "main",
|
|
346
|
-
gitUrl: url.replace(/\.git\/?$/, ".git"),
|
|
347
|
-
installUrl: url
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
337
|
if (url.startsWith("git+") || url.startsWith("git://")) {
|
|
351
338
|
return {
|
|
352
339
|
type: "other",
|
|
@@ -358,9 +345,19 @@ function parseGitUrl(url) {
|
|
|
358
345
|
}
|
|
359
346
|
return null;
|
|
360
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
|
+
}
|
|
361
358
|
function isGitUrl(str) {
|
|
362
359
|
return str.includes("github.com") || str.includes("gitlab.com") || str.includes("/-/tree/") || // GitLab web URL pattern (self-hosted)
|
|
363
|
-
str.startsWith("git@") || str.startsWith("
|
|
360
|
+
str.startsWith("git@") || str.startsWith("git+") || str.startsWith("git://");
|
|
364
361
|
}
|
|
365
362
|
|
|
366
363
|
// src/utils/logger.ts
|
|
@@ -466,110 +463,49 @@ function extractSkillName(nameOrPath) {
|
|
|
466
463
|
return import_node_path2.default.basename(nameOrPath);
|
|
467
464
|
}
|
|
468
465
|
|
|
469
|
-
// src/utils/
|
|
470
|
-
var import_node_child_process = require("child_process");
|
|
466
|
+
// src/utils/symlink.ts
|
|
471
467
|
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
472
468
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
473
|
-
var import_node_util = require("util");
|
|
474
|
-
var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
|
|
475
|
-
function findNpmrc(startDir) {
|
|
476
|
-
let currentDir = import_node_path3.default.resolve(startDir);
|
|
477
|
-
while (currentDir !== import_node_path3.default.dirname(currentDir)) {
|
|
478
|
-
const npmrcPath = import_node_path3.default.join(currentDir, ".npmrc");
|
|
479
|
-
if (import_node_fs4.default.existsSync(npmrcPath)) {
|
|
480
|
-
return npmrcPath;
|
|
481
|
-
}
|
|
482
|
-
currentDir = import_node_path3.default.dirname(currentDir);
|
|
483
|
-
}
|
|
484
|
-
return null;
|
|
485
|
-
}
|
|
486
|
-
function parseNpmrc(npmrcPath) {
|
|
487
|
-
try {
|
|
488
|
-
const content = import_node_fs4.default.readFileSync(npmrcPath, "utf-8");
|
|
489
|
-
const lines = content.split("\n");
|
|
490
|
-
for (const line of lines) {
|
|
491
|
-
const trimmed = line.trim();
|
|
492
|
-
if (!trimmed || trimmed.startsWith("#")) {
|
|
493
|
-
continue;
|
|
494
|
-
}
|
|
495
|
-
const registryMatch = trimmed.match(/^(?:@[^:]+:)?registry\s*=\s*(.+)$/);
|
|
496
|
-
if (registryMatch) {
|
|
497
|
-
return registryMatch[1].trim();
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
} catch (error) {
|
|
501
|
-
}
|
|
502
|
-
return null;
|
|
503
|
-
}
|
|
504
|
-
async function getGlobalRegistry() {
|
|
505
|
-
try {
|
|
506
|
-
const { stdout } = await execAsync("npm config get registry");
|
|
507
|
-
const registry = stdout.trim();
|
|
508
|
-
if (registry && registry !== "undefined") {
|
|
509
|
-
return registry;
|
|
510
|
-
}
|
|
511
|
-
} catch (error) {
|
|
512
|
-
}
|
|
513
|
-
return null;
|
|
514
|
-
}
|
|
515
|
-
async function getRegistry(cwd = process.cwd()) {
|
|
516
|
-
const npmrcPath = findNpmrc(cwd);
|
|
517
|
-
if (npmrcPath) {
|
|
518
|
-
const registry = parseNpmrc(npmrcPath);
|
|
519
|
-
if (registry) {
|
|
520
|
-
return registry;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
const globalRegistry = await getGlobalRegistry();
|
|
524
|
-
if (globalRegistry) {
|
|
525
|
-
return globalRegistry;
|
|
526
|
-
}
|
|
527
|
-
return "https://registry.npmjs.org/";
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// src/utils/symlink.ts
|
|
531
|
-
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
532
|
-
var import_node_path4 = __toESM(require("path"), 1);
|
|
533
469
|
function copyDir(src, dest) {
|
|
534
|
-
|
|
535
|
-
const entries =
|
|
470
|
+
import_node_fs4.default.mkdirSync(dest, { recursive: true });
|
|
471
|
+
const entries = import_node_fs4.default.readdirSync(src, { withFileTypes: true });
|
|
536
472
|
for (const entry of entries) {
|
|
537
473
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
538
|
-
const srcPath =
|
|
539
|
-
const destPath =
|
|
474
|
+
const srcPath = import_node_path3.default.join(src, entry.name);
|
|
475
|
+
const destPath = import_node_path3.default.join(dest, entry.name);
|
|
540
476
|
if (entry.isDirectory()) {
|
|
541
477
|
copyDir(srcPath, destPath);
|
|
542
478
|
} else {
|
|
543
|
-
|
|
479
|
+
import_node_fs4.default.copyFileSync(srcPath, destPath);
|
|
544
480
|
}
|
|
545
481
|
}
|
|
546
482
|
}
|
|
547
|
-
function createSymlink(skillName, agent, cwd) {
|
|
483
|
+
function createSymlink(skillName, agent, cwd, useCopyOverride) {
|
|
548
484
|
const source = getSharedSkillPath(skillName);
|
|
549
|
-
if (!
|
|
485
|
+
if (!import_node_fs4.default.existsSync(source)) {
|
|
550
486
|
logger.error(`Skill not found: ${source}`);
|
|
551
487
|
return false;
|
|
552
488
|
}
|
|
553
489
|
const targets = getAgentSkillPaths(agent.name, skillName, cwd);
|
|
554
|
-
const useCopy = agent.useCopyInsteadOfSymlink === true;
|
|
490
|
+
const useCopy = useCopyOverride !== void 0 ? useCopyOverride : agent.useCopyInsteadOfSymlink === true;
|
|
555
491
|
let successCount = 0;
|
|
556
492
|
for (const target of targets) {
|
|
557
|
-
const targetDir =
|
|
558
|
-
if (!
|
|
493
|
+
const targetDir = import_node_path3.default.dirname(target);
|
|
494
|
+
if (!import_node_fs4.default.existsSync(targetDir)) {
|
|
559
495
|
try {
|
|
560
|
-
|
|
496
|
+
import_node_fs4.default.mkdirSync(targetDir, { recursive: true });
|
|
561
497
|
} catch (error) {
|
|
562
498
|
logger.error(`Failed to create directory ${targetDir}: ${error.message}`);
|
|
563
499
|
continue;
|
|
564
500
|
}
|
|
565
501
|
}
|
|
566
|
-
if (
|
|
502
|
+
if (import_node_fs4.default.existsSync(target)) {
|
|
567
503
|
try {
|
|
568
|
-
const stats =
|
|
504
|
+
const stats = import_node_fs4.default.lstatSync(target);
|
|
569
505
|
if (stats.isSymbolicLink()) {
|
|
570
|
-
|
|
506
|
+
import_node_fs4.default.unlinkSync(target);
|
|
571
507
|
} else if (stats.isDirectory()) {
|
|
572
|
-
|
|
508
|
+
import_node_fs4.default.rmSync(target, { recursive: true, force: true });
|
|
573
509
|
} else {
|
|
574
510
|
logger.warn(`Target exists but is not a symlink/dir, skipping: ${target}`);
|
|
575
511
|
continue;
|
|
@@ -583,7 +519,7 @@ function createSymlink(skillName, agent, cwd) {
|
|
|
583
519
|
if (useCopy) {
|
|
584
520
|
copyDir(source, target);
|
|
585
521
|
} else {
|
|
586
|
-
|
|
522
|
+
import_node_fs4.default.symlinkSync(source, target, "dir");
|
|
587
523
|
}
|
|
588
524
|
successCount++;
|
|
589
525
|
} catch (error) {
|
|
@@ -601,16 +537,16 @@ function removeSymlink(skillName, agent, cwd) {
|
|
|
601
537
|
const targets = getAgentSkillPaths(agent.name, skillName, cwd);
|
|
602
538
|
let removedCount = 0;
|
|
603
539
|
for (const target of targets) {
|
|
604
|
-
if (!
|
|
540
|
+
if (!import_node_fs4.default.existsSync(target)) {
|
|
605
541
|
continue;
|
|
606
542
|
}
|
|
607
543
|
try {
|
|
608
|
-
const stats =
|
|
544
|
+
const stats = import_node_fs4.default.lstatSync(target);
|
|
609
545
|
if (stats.isSymbolicLink()) {
|
|
610
|
-
|
|
546
|
+
import_node_fs4.default.unlinkSync(target);
|
|
611
547
|
removedCount++;
|
|
612
548
|
} else if (stats.isDirectory()) {
|
|
613
|
-
|
|
549
|
+
import_node_fs4.default.rmSync(target, { recursive: true, force: true });
|
|
614
550
|
removedCount++;
|
|
615
551
|
} else {
|
|
616
552
|
logger.warn(`Not a symlink or directory: ${target}`);
|
|
@@ -628,7 +564,7 @@ function removeSymlink(skillName, agent, cwd) {
|
|
|
628
564
|
}
|
|
629
565
|
function isSymlink(filePath) {
|
|
630
566
|
try {
|
|
631
|
-
const stats =
|
|
567
|
+
const stats = import_node_fs4.default.lstatSync(filePath);
|
|
632
568
|
return stats.isSymbolicLink();
|
|
633
569
|
} catch {
|
|
634
570
|
return false;
|
|
@@ -636,426 +572,493 @@ function isSymlink(filePath) {
|
|
|
636
572
|
}
|
|
637
573
|
function readSymlink(filePath) {
|
|
638
574
|
try {
|
|
639
|
-
return
|
|
575
|
+
return import_node_fs4.default.readlinkSync(filePath);
|
|
640
576
|
} catch {
|
|
641
577
|
return null;
|
|
642
578
|
}
|
|
643
579
|
}
|
|
644
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
|
+
|
|
645
757
|
// src/commands/install.ts
|
|
646
|
-
var
|
|
758
|
+
var { MultiSelect, Password, Select } = import_enquirer.default;
|
|
759
|
+
var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
|
|
647
760
|
async function execWithTimeout(command, timeout = 12e4, env) {
|
|
648
761
|
let timeoutId = null;
|
|
649
762
|
const timeoutPromise = new Promise((_, reject) => {
|
|
650
|
-
timeoutId = setTimeout(() => {
|
|
651
|
-
reject(new Error(`Command timeout after ${timeout / 1e3}s`));
|
|
652
|
-
}, timeout);
|
|
763
|
+
timeoutId = setTimeout(() => reject(new Error(`Timeout ${timeout / 1e3}s`)), timeout);
|
|
653
764
|
});
|
|
654
765
|
try {
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
} catch (error) {
|
|
662
|
-
if (timeoutId) {
|
|
663
|
-
clearTimeout(timeoutId);
|
|
664
|
-
}
|
|
665
|
-
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;
|
|
666
772
|
}
|
|
667
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
|
+
}
|
|
668
811
|
async function install(skillNameOrPath, options = {}) {
|
|
669
812
|
const isGit = isGitUrl(skillNameOrPath);
|
|
670
|
-
|
|
671
|
-
logger.info("Installing from Git URL");
|
|
672
|
-
logger.dim(skillNameOrPath);
|
|
673
|
-
} else {
|
|
674
|
-
logger.info(`Installing skill: ${skillNameOrPath}`);
|
|
675
|
-
}
|
|
813
|
+
logger.info(`${import_chalk3.default.cyan("\u{1F50D}")} ${t("analyzing")} ${import_chalk3.default.bold(skillNameOrPath)}`);
|
|
676
814
|
ensureSharedDir();
|
|
677
|
-
let
|
|
678
|
-
let
|
|
679
|
-
|
|
680
|
-
logger.dim(`[DEBUG] isGitUrl=${isGit}, parseGitUrl=${parseGitUrl(skillNameOrPath) ? "ok" : "null"}`);
|
|
681
|
-
}
|
|
815
|
+
let tempDir = "";
|
|
816
|
+
let cloneDir = "";
|
|
817
|
+
let availableSkills = [];
|
|
682
818
|
if (isGit) {
|
|
683
819
|
const gitInfo = parseGitUrl(skillNameOrPath);
|
|
684
820
|
if (!gitInfo) {
|
|
685
|
-
logger.error(
|
|
686
|
-
logger.info("Please ensure the URL is a valid GitHub or GitLab repository URL");
|
|
821
|
+
logger.error(t("unsupported", { url: skillNameOrPath }));
|
|
687
822
|
process.exit(1);
|
|
688
823
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
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");
|
|
692
826
|
try {
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
import_node_fs6.default.mkdirSync(tempDir, { recursive: true });
|
|
700
|
-
const branchFlag = gitInfo.branch ? `-b ${gitInfo.branch}` : "";
|
|
701
|
-
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);
|
|
702
833
|
try {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
spinner.
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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;
|
|
725
858
|
}
|
|
726
|
-
} else {
|
|
727
|
-
skillPath = cloneDir;
|
|
728
|
-
}
|
|
729
|
-
const skillMdPath = import_node_path5.default.join(skillPath, "SKILL.md");
|
|
730
|
-
if (!import_node_fs6.default.existsSync(skillMdPath)) {
|
|
731
|
-
logger.warn(`Warning: SKILL.md not found in ${skillPath}`);
|
|
732
|
-
logger.info("The directory may not be a valid skill package");
|
|
733
859
|
}
|
|
860
|
+
availableSkills = scanForSkills(gitInfo.path ? import_node_path6.default.join(cloneDir, gitInfo.path) : cloneDir);
|
|
734
861
|
} catch (error) {
|
|
735
|
-
logger.error(`
|
|
736
|
-
if (error.message.includes("timeout")) {
|
|
737
|
-
logger.error(`Clone timeout after 2 minutes`);
|
|
738
|
-
logger.info("This might be due to:");
|
|
739
|
-
logger.info(" - Slow network connection");
|
|
740
|
-
logger.info(" - Large repository size");
|
|
741
|
-
logger.info(" - Git server issues");
|
|
742
|
-
logger.info(`
|
|
743
|
-
Try again or check your network connection`);
|
|
744
|
-
}
|
|
745
|
-
logger.info(`
|
|
746
|
-
Tried to clone: ${gitInfo.gitUrl}`);
|
|
747
|
-
if (gitInfo.branch) {
|
|
748
|
-
logger.info(`Branch: ${gitInfo.branch}`);
|
|
749
|
-
}
|
|
862
|
+
logger.error(`Error: ${error.message}`);
|
|
750
863
|
process.exit(1);
|
|
751
864
|
}
|
|
752
|
-
} else
|
|
753
|
-
skillPath =
|
|
754
|
-
if (!
|
|
865
|
+
} else {
|
|
866
|
+
const skillPath = import_node_path6.default.resolve(skillNameOrPath);
|
|
867
|
+
if (!import_node_fs7.default.existsSync(skillPath)) {
|
|
755
868
|
logger.error(`Path not found: ${skillPath}`);
|
|
756
869
|
process.exit(1);
|
|
757
870
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
} else {
|
|
770
|
-
skillName = extractSkillName(skillNameOrPath);
|
|
771
|
-
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) {
|
|
772
882
|
try {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
const npmConfigPrefix = import_node_path5.default.join(homeDir, ".npm-global");
|
|
783
|
-
import_node_fs6.default.mkdirSync(npmCacheDir, { recursive: true });
|
|
784
|
-
const installCommand = `npm install ${skillNameOrPath} --prefix ${tempDir} --registry=${registry} --no-save --silent --no-bin-links --prefer-offline`;
|
|
785
|
-
const env = {
|
|
786
|
-
...process.env,
|
|
787
|
-
npm_config_cache: npmCacheDir,
|
|
788
|
-
npm_config_prefix: npmConfigPrefix,
|
|
789
|
-
npm_config_global: "false"
|
|
790
|
-
};
|
|
791
|
-
try {
|
|
792
|
-
await execWithTimeout(installCommand, timeout, env);
|
|
793
|
-
spinner.succeed(`Package ${skillNameOrPath} downloaded successfully`);
|
|
794
|
-
} catch (error) {
|
|
795
|
-
spinner.fail("Download failed");
|
|
796
|
-
const errorMessage = error.message || error.stderr || "";
|
|
797
|
-
if (errorMessage.includes("timeout")) {
|
|
798
|
-
logger.error(`Download timeout after ${timeout / 1e3} seconds`);
|
|
799
|
-
logger.info("");
|
|
800
|
-
logger.info("Possible reasons:");
|
|
801
|
-
logger.info(" - Slow network connection");
|
|
802
|
-
logger.info(" - Registry server issues or unresponsive");
|
|
803
|
-
logger.info(" - Large package size");
|
|
804
|
-
logger.info(" - Network firewall blocking the connection");
|
|
805
|
-
logger.info("");
|
|
806
|
-
logger.info("Suggestions:");
|
|
807
|
-
logger.info(` - Try again: eskill add ${skillNameOrPath}`);
|
|
808
|
-
logger.info(
|
|
809
|
-
` - Use a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`
|
|
810
|
-
);
|
|
811
|
-
logger.info(` - Increase timeout: eskill add ${skillNameOrPath} --timeout=300000`);
|
|
812
|
-
} else if (errorMessage.includes("EACCES") || errorMessage.includes("permission denied") || errorMessage.includes("Permission denied")) {
|
|
813
|
-
logger.error("Permission denied error detected");
|
|
814
|
-
logger.info("");
|
|
815
|
-
logger.info("This error occurs when npm tries to access system directories.");
|
|
816
|
-
logger.info("");
|
|
817
|
-
logger.info("\u{1F527} Quick Fix (Recommended):");
|
|
818
|
-
logger.info("");
|
|
819
|
-
logger.info("1. Configure npm to use user directory:");
|
|
820
|
-
logger.info(` mkdir -p ${npmConfigPrefix}`);
|
|
821
|
-
logger.info(` npm config set prefix '${npmConfigPrefix}'`);
|
|
822
|
-
logger.info("");
|
|
823
|
-
logger.info("2. Add to your ~/.zshrc (or ~/.bash_profile):");
|
|
824
|
-
logger.info(` export PATH="${npmConfigPrefix}/bin:$PATH"`);
|
|
825
|
-
logger.info("");
|
|
826
|
-
logger.info("3. Reload shell configuration:");
|
|
827
|
-
logger.info(" source ~/.zshrc");
|
|
828
|
-
logger.info("");
|
|
829
|
-
logger.info("\u{1F4DA} Alternative Solutions:");
|
|
830
|
-
logger.info("");
|
|
831
|
-
logger.info("Option A: Use NVM (Node Version Manager)");
|
|
832
|
-
logger.info(" curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash");
|
|
833
|
-
logger.info(" nvm install --lts");
|
|
834
|
-
logger.info("");
|
|
835
|
-
logger.info("Option B: Fix system directory permissions (requires sudo)");
|
|
836
|
-
logger.info(" sudo chown -R $(whoami) /usr/local/lib/node_modules");
|
|
837
|
-
logger.info("");
|
|
838
|
-
logger.info("Option C: Use sudo (not recommended for security reasons)");
|
|
839
|
-
logger.info(` sudo npm install -g @empjs/skill --registry=${registry}`);
|
|
840
|
-
logger.info("");
|
|
841
|
-
logger.info("After fixing, try again:");
|
|
842
|
-
logger.info(` eskill add ${skillNameOrPath}`);
|
|
843
|
-
} else if (errorMessage.includes("ENOTFOUND") || errorMessage.includes("ECONNREFUSED")) {
|
|
844
|
-
logger.error(`Network connection error: ${errorMessage}`);
|
|
845
|
-
logger.info("");
|
|
846
|
-
logger.info("Please check:");
|
|
847
|
-
logger.info(" - Your internet connection");
|
|
848
|
-
logger.info(` - Registry accessibility: ${registry}`);
|
|
849
|
-
logger.info(
|
|
850
|
-
` - Try a different registry: eskill add ${skillNameOrPath} --registry=https://registry.npmjs.org/`
|
|
851
|
-
);
|
|
852
|
-
} else {
|
|
853
|
-
logger.error(`Failed to download: ${errorMessage}`);
|
|
854
|
-
logger.info("");
|
|
855
|
-
logger.info("If this is a permission error, see the solutions above.");
|
|
856
|
-
logger.info("For other errors, please check:");
|
|
857
|
-
logger.info(" - Package name is correct");
|
|
858
|
-
logger.info(" - Registry is accessible");
|
|
859
|
-
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;
|
|
860
892
|
}
|
|
861
|
-
|
|
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;
|
|
903
|
+
}
|
|
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;
|
|
862
910
|
}
|
|
863
|
-
|
|
864
|
-
if (
|
|
865
|
-
logger.
|
|
866
|
-
process.exit(
|
|
911
|
+
} catch (e) {
|
|
912
|
+
if (step === 0 || step === -1) {
|
|
913
|
+
logger.warn("\nInstallation cancelled.");
|
|
914
|
+
process.exit(0);
|
|
867
915
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
process.exit(1);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
const targetPath = getSharedSkillPath(skillName);
|
|
874
|
-
const sourceIsTarget = import_node_path5.default.resolve(skillPath) === import_node_path5.default.resolve(targetPath);
|
|
875
|
-
const alreadyExists = import_node_fs6.default.existsSync(targetPath) && !sourceIsTarget;
|
|
876
|
-
if (alreadyExists) {
|
|
877
|
-
if (options.force) {
|
|
878
|
-
logger.warn("Removing existing installation...");
|
|
879
|
-
import_node_fs6.default.rmSync(targetPath, { recursive: true, force: true });
|
|
880
|
-
} else {
|
|
881
|
-
logger.info(`Skill already exists, updating agent links...`);
|
|
916
|
+
step--;
|
|
917
|
+
continue;
|
|
882
918
|
}
|
|
883
919
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
logger.success(`Linked to shared directory (dev mode)`);
|
|
890
|
-
} else {
|
|
891
|
-
copyDir2(skillPath, targetPath);
|
|
892
|
-
logger.success(`Installed to shared directory`);
|
|
893
|
-
}
|
|
894
|
-
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);
|
|
895
925
|
const cwd = process.cwd();
|
|
896
|
-
|
|
897
|
-
if (
|
|
898
|
-
|
|
899
|
-
logger.info("Skill installed to shared directory, but not linked to any agent");
|
|
900
|
-
logger.info("");
|
|
901
|
-
logger.info("Supported agents:");
|
|
902
|
-
logger.info(" AMP, Antigravity, Claude Code, ClawdBot, Cline, Codex, Cursor, Droid,");
|
|
903
|
-
logger.info(" Gemini, GitHub Copilot, Goose, Kilo, Kiro CLI, OpenCode, Roo, Trae, Windsurf");
|
|
904
|
-
logger.info("");
|
|
905
|
-
logger.info('Run "eskill agents" to see all agent directories');
|
|
906
|
-
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);
|
|
907
929
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
logger.error(`Unknown agent: ${options.agent}`);
|
|
913
|
-
logger.info(`
|
|
914
|
-
Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", ")}`);
|
|
915
|
-
process.exit(1);
|
|
916
|
-
}
|
|
917
|
-
targetAgents = [agent];
|
|
918
|
-
if (!installedAgents.find((a) => a.name === options.agent)) {
|
|
919
|
-
logger.info(`Note: ${agent.displayName} directory will be created if not exists`);
|
|
920
|
-
}
|
|
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);
|
|
921
934
|
}
|
|
922
|
-
logger.info("
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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
|
+
}
|
|
927
950
|
}
|
|
928
951
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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)`);
|
|
935
961
|
}
|
|
936
962
|
}
|
|
937
963
|
function copyDir2(src, dest) {
|
|
938
|
-
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
const
|
|
942
|
-
const
|
|
943
|
-
if (
|
|
944
|
-
|
|
945
|
-
}
|
|
946
|
-
if (entry.isDirectory()) {
|
|
947
|
-
copyDir2(srcPath, destPath);
|
|
948
|
-
} else {
|
|
949
|
-
import_node_fs6.default.copyFileSync(srcPath, destPath);
|
|
950
|
-
}
|
|
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);
|
|
951
971
|
}
|
|
952
972
|
}
|
|
953
973
|
|
|
954
974
|
// src/commands/list.ts
|
|
955
|
-
var
|
|
956
|
-
var
|
|
957
|
-
var
|
|
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
|
+
}
|
|
958
983
|
function list() {
|
|
959
|
-
if (!
|
|
984
|
+
if (!import_node_fs8.default.existsSync(SHARED_SKILLS_DIR)) {
|
|
960
985
|
logger.info("No skills installed");
|
|
961
986
|
logger.info(`
|
|
962
|
-
To install a skill, run: ${
|
|
987
|
+
To install a skill, run: ${import_chalk4.default.cyan("eskill install <skill-name>")}`);
|
|
963
988
|
return;
|
|
964
989
|
}
|
|
965
|
-
const skills =
|
|
990
|
+
const skills = import_node_fs8.default.readdirSync(SHARED_SKILLS_DIR).filter((s) => !s.startsWith("."));
|
|
966
991
|
if (skills.length === 0) {
|
|
967
992
|
logger.info("No skills installed");
|
|
968
993
|
logger.info(`
|
|
969
|
-
To install a skill, run: ${
|
|
994
|
+
To install a skill, run: ${import_chalk4.default.cyan("eskill install <skill-name>")}`);
|
|
970
995
|
return;
|
|
971
996
|
}
|
|
972
|
-
console.log(
|
|
973
|
-
Installed
|
|
997
|
+
console.log(import_chalk4.default.bold(`
|
|
998
|
+
\u{1F4E6} Installed Skills in ${import_chalk4.default.blue(shortenPath(SHARED_SKILLS_DIR))}:
|
|
974
999
|
`));
|
|
975
1000
|
for (const skill of skills) {
|
|
976
|
-
const skillPath =
|
|
1001
|
+
const skillPath = import_node_path7.default.join(SHARED_SKILLS_DIR, skill);
|
|
977
1002
|
try {
|
|
978
|
-
const stats =
|
|
1003
|
+
const stats = import_node_fs8.default.lstatSync(skillPath);
|
|
979
1004
|
if (!stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
980
1005
|
continue;
|
|
981
1006
|
}
|
|
982
1007
|
let version2 = "unknown";
|
|
983
|
-
|
|
984
|
-
|
|
1008
|
+
let description = "";
|
|
1009
|
+
const pkgPath = import_node_path7.default.join(skillPath, "package.json");
|
|
1010
|
+
if (import_node_fs8.default.existsSync(pkgPath)) {
|
|
985
1011
|
try {
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
if (pkg.
|
|
989
|
-
version2 = pkg.version;
|
|
990
|
-
}
|
|
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;
|
|
991
1015
|
} catch (error) {
|
|
992
1016
|
}
|
|
993
1017
|
}
|
|
994
|
-
if (
|
|
995
|
-
const skillMdPath =
|
|
996
|
-
if (
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
if (versionMatch) {
|
|
1004
|
-
version2 = versionMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
} catch (error) {
|
|
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, "");
|
|
1008
1027
|
}
|
|
1009
1028
|
}
|
|
1010
1029
|
}
|
|
1011
|
-
if (version2 === "unknown" && isSymlink(skillPath)) {
|
|
1012
|
-
try {
|
|
1013
|
-
const targetPath = readSymlink(skillPath);
|
|
1014
|
-
if (targetPath) {
|
|
1015
|
-
const targetPkgPath = import_node_path6.default.join(targetPath, "package.json");
|
|
1016
|
-
if (import_node_fs7.default.existsSync(targetPkgPath)) {
|
|
1017
|
-
const pkg = JSON.parse(import_node_fs7.default.readFileSync(targetPkgPath, "utf-8"));
|
|
1018
|
-
if (pkg.version && typeof pkg.version === "string") {
|
|
1019
|
-
version2 = pkg.version;
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
1030
|
const isDev = isSymlink(skillPath);
|
|
1027
|
-
const
|
|
1028
|
-
const versionDisplay = version2 !== "unknown" ?
|
|
1029
|
-
|
|
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}`);
|
|
1030
1035
|
const cwd = process.cwd();
|
|
1031
1036
|
const linkedAgents = [];
|
|
1032
1037
|
for (const agent of AGENTS) {
|
|
1033
1038
|
const skillsDirs = getAgentSkillsDirs(agent, cwd);
|
|
1034
1039
|
const hasRef = skillsDirs.some((dir) => {
|
|
1035
|
-
const agentSkillPath =
|
|
1036
|
-
if (!
|
|
1040
|
+
const agentSkillPath = import_node_path7.default.join(dir, skill);
|
|
1041
|
+
if (!import_node_fs8.default.existsSync(agentSkillPath)) return false;
|
|
1037
1042
|
if (isSymlink(agentSkillPath)) {
|
|
1038
1043
|
const target = readSymlink(agentSkillPath);
|
|
1039
1044
|
return target === skillPath;
|
|
1040
1045
|
}
|
|
1041
1046
|
if (agent.useCopyInsteadOfSymlink) {
|
|
1042
|
-
return
|
|
1047
|
+
return import_node_fs8.default.statSync(agentSkillPath).isDirectory();
|
|
1043
1048
|
}
|
|
1044
1049
|
return false;
|
|
1045
1050
|
});
|
|
1046
|
-
if (hasRef)
|
|
1047
|
-
linkedAgents.push(agent.displayName);
|
|
1048
|
-
}
|
|
1051
|
+
if (hasRef) linkedAgents.push(agent.displayName);
|
|
1049
1052
|
}
|
|
1050
1053
|
if (linkedAgents.length > 0) {
|
|
1051
|
-
console.log(
|
|
1054
|
+
console.log(import_chalk4.default.gray(` \u{1F517} Linked to: ${import_chalk4.default.white(linkedAgents.join(", "))}`));
|
|
1052
1055
|
} else {
|
|
1053
|
-
console.log(
|
|
1056
|
+
console.log(import_chalk4.default.red(` \u26A0\uFE0F Not linked to any agent`));
|
|
1054
1057
|
}
|
|
1055
1058
|
if (isDev) {
|
|
1056
1059
|
const target = readSymlink(skillPath);
|
|
1057
1060
|
if (target) {
|
|
1058
|
-
console.log(
|
|
1061
|
+
console.log(import_chalk4.default.gray(` \u{1F4C1} Source: ${import_chalk4.default.dim(shortenPath(target))}`));
|
|
1059
1062
|
}
|
|
1060
1063
|
}
|
|
1061
1064
|
console.log("");
|
|
@@ -1065,11 +1068,11 @@ Installed skills in ${SHARED_SKILLS_DIR}:
|
|
|
1065
1068
|
}
|
|
1066
1069
|
|
|
1067
1070
|
// src/commands/remove.ts
|
|
1068
|
-
var
|
|
1071
|
+
var import_node_fs9 = __toESM(require("fs"), 1);
|
|
1069
1072
|
async function remove(skillName, options = {}) {
|
|
1070
1073
|
const extractedName = extractSkillName(skillName);
|
|
1071
1074
|
const sharedPath = getSharedSkillPath(extractedName);
|
|
1072
|
-
const skillExists =
|
|
1075
|
+
const skillExists = import_node_fs9.default.existsSync(sharedPath);
|
|
1073
1076
|
if (!skillExists) {
|
|
1074
1077
|
logger.error(`Skill not found: ${extractedName}`);
|
|
1075
1078
|
logger.info(`Location: ${sharedPath}`);
|
|
@@ -1105,12 +1108,12 @@ Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", "
|
|
|
1105
1108
|
}
|
|
1106
1109
|
logger.info("Removing skill from shared directory...");
|
|
1107
1110
|
try {
|
|
1108
|
-
const stats =
|
|
1111
|
+
const stats = import_node_fs9.default.lstatSync(sharedPath);
|
|
1109
1112
|
if (stats.isSymbolicLink()) {
|
|
1110
|
-
|
|
1113
|
+
import_node_fs9.default.unlinkSync(sharedPath);
|
|
1111
1114
|
logger.success("Removed symlink from shared directory");
|
|
1112
1115
|
} else {
|
|
1113
|
-
|
|
1116
|
+
import_node_fs9.default.rmSync(sharedPath, { recursive: true, force: true });
|
|
1114
1117
|
logger.success("Removed skill from shared directory");
|
|
1115
1118
|
}
|
|
1116
1119
|
} catch (error) {
|
|
@@ -1120,7 +1123,7 @@ Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", "
|
|
|
1120
1123
|
const remainingRefs = [];
|
|
1121
1124
|
for (const agent of AGENTS) {
|
|
1122
1125
|
const agentSkillPaths = getAgentSkillPaths(agent.name, extractedName, cwd);
|
|
1123
|
-
const hasRef = agentSkillPaths.some((p) =>
|
|
1126
|
+
const hasRef = agentSkillPaths.some((p) => import_node_fs9.default.existsSync(p));
|
|
1124
1127
|
if (hasRef) {
|
|
1125
1128
|
remainingRefs.push(agent.displayName);
|
|
1126
1129
|
}
|
|
@@ -1137,6 +1140,56 @@ Supported agents: ${AGENTS.filter((a) => a.enabled).map((a) => a.name).join(", "
|
|
|
1137
1140
|
}
|
|
1138
1141
|
}
|
|
1139
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
|
+
|
|
1140
1193
|
// src/index.ts
|
|
1141
1194
|
var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
|
|
1142
1195
|
var __dirname = (0, import_path.dirname)(__filename2);
|
|
@@ -1145,7 +1198,7 @@ var packageJson = JSON.parse((0, import_fs.readFileSync)(packageJsonPath, "utf-8
|
|
|
1145
1198
|
var version = packageJson.version;
|
|
1146
1199
|
var program = new import_commander.Command();
|
|
1147
1200
|
program.name("eskill").description("Unified CLI tool for managing AI agent skills").version(version);
|
|
1148
|
-
program.command("install <skill>").alias("add").description("Install a skill
|
|
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(
|
|
1149
1202
|
"-t, --timeout <ms>",
|
|
1150
1203
|
"Timeout in milliseconds (default: 180000 for npm, 120000 for git)",
|
|
1151
1204
|
(value) => Number.parseInt(value, 10)
|
|
@@ -1157,6 +1210,14 @@ program.command("install <skill>").alias("add").description("Install a skill fro
|
|
|
1157
1210
|
process.exit(1);
|
|
1158
1211
|
}
|
|
1159
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
|
+
});
|
|
1160
1221
|
program.command("list").alias("ls").description("List installed skills").action(() => {
|
|
1161
1222
|
try {
|
|
1162
1223
|
list();
|