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