plugins 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +280 -71
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,6 +11,130 @@ import { createInterface } from "readline";
|
|
|
11
11
|
import { join } from "path";
|
|
12
12
|
import { readFile, readdir, stat } from "fs/promises";
|
|
13
13
|
import { existsSync } from "fs";
|
|
14
|
+
|
|
15
|
+
// lib/ui.ts
|
|
16
|
+
var isColorSupported = process.env.FORCE_COLOR !== "0" && !process.env.NO_COLOR && (process.env.FORCE_COLOR !== void 0 || process.stdout.isTTY);
|
|
17
|
+
function ansi(code) {
|
|
18
|
+
return isColorSupported ? `\x1B[${code}m` : "";
|
|
19
|
+
}
|
|
20
|
+
var reset = ansi("0");
|
|
21
|
+
var bold = ansi("1");
|
|
22
|
+
var dim = ansi("2");
|
|
23
|
+
var italic = ansi("3");
|
|
24
|
+
var underline = ansi("4");
|
|
25
|
+
var red = ansi("31");
|
|
26
|
+
var green = ansi("32");
|
|
27
|
+
var yellow = ansi("33");
|
|
28
|
+
var blue = ansi("34");
|
|
29
|
+
var magenta = ansi("35");
|
|
30
|
+
var cyan = ansi("36");
|
|
31
|
+
var gray = ansi("90");
|
|
32
|
+
var bgGreen = ansi("42");
|
|
33
|
+
var bgRed = ansi("41");
|
|
34
|
+
var bgYellow = ansi("43");
|
|
35
|
+
var bgCyan = ansi("46");
|
|
36
|
+
var black = ansi("30");
|
|
37
|
+
var c = {
|
|
38
|
+
bold: (s) => `${bold}${s}${reset}`,
|
|
39
|
+
dim: (s) => `${dim}${s}${reset}`,
|
|
40
|
+
italic: (s) => `${italic}${s}${reset}`,
|
|
41
|
+
underline: (s) => `${underline}${s}${reset}`,
|
|
42
|
+
red: (s) => `${red}${s}${reset}`,
|
|
43
|
+
green: (s) => `${green}${s}${reset}`,
|
|
44
|
+
yellow: (s) => `${yellow}${s}${reset}`,
|
|
45
|
+
blue: (s) => `${blue}${s}${reset}`,
|
|
46
|
+
magenta: (s) => `${magenta}${s}${reset}`,
|
|
47
|
+
cyan: (s) => `${cyan}${s}${reset}`,
|
|
48
|
+
gray: (s) => `${gray}${s}${reset}`,
|
|
49
|
+
bgGreen: (s) => `${bgGreen}${black}${s}${reset}`,
|
|
50
|
+
bgRed: (s) => `${bgRed}${black}${s}${reset}`,
|
|
51
|
+
bgYellow: (s) => `${bgYellow}${black}${s}${reset}`,
|
|
52
|
+
bgCyan: (s) => `${bgCyan}${black}${s}${reset}`
|
|
53
|
+
};
|
|
54
|
+
var S = {
|
|
55
|
+
// Box drawing
|
|
56
|
+
bar: "\u2502",
|
|
57
|
+
barEnd: "\u2514",
|
|
58
|
+
barStart: "\u250C",
|
|
59
|
+
barH: "\u2500",
|
|
60
|
+
corner: "\u256E",
|
|
61
|
+
// Bullets
|
|
62
|
+
diamond: "\u25C7",
|
|
63
|
+
diamondFilled: "\u25C6",
|
|
64
|
+
bullet: "\u25CF",
|
|
65
|
+
circle: "\u25CB",
|
|
66
|
+
check: "\u2714",
|
|
67
|
+
cross: "\u2716",
|
|
68
|
+
arrow: "\u2192",
|
|
69
|
+
warning: "\u25B2",
|
|
70
|
+
info: "\u2139",
|
|
71
|
+
step: "\u25C7",
|
|
72
|
+
stepActive: "\u25C6",
|
|
73
|
+
stepComplete: "\u25CF",
|
|
74
|
+
stepError: "\u25A0"
|
|
75
|
+
};
|
|
76
|
+
function barLine(content = "") {
|
|
77
|
+
console.log(`${c.gray(S.bar)} ${content}`);
|
|
78
|
+
}
|
|
79
|
+
function barEmpty() {
|
|
80
|
+
console.log(`${c.gray(S.bar)}`);
|
|
81
|
+
}
|
|
82
|
+
function step(content) {
|
|
83
|
+
console.log(`${c.gray(S.step)} ${content}`);
|
|
84
|
+
}
|
|
85
|
+
function stepDone(content) {
|
|
86
|
+
console.log(`${c.green(S.stepComplete)} ${content}`);
|
|
87
|
+
}
|
|
88
|
+
function stepError(content) {
|
|
89
|
+
console.log(`${c.red(S.stepError)} ${content}`);
|
|
90
|
+
}
|
|
91
|
+
function header(label) {
|
|
92
|
+
console.log();
|
|
93
|
+
console.log(`${c.gray(S.barStart)} ${c.bgCyan(` ${label} `)}`);
|
|
94
|
+
}
|
|
95
|
+
function footer(message) {
|
|
96
|
+
if (message) {
|
|
97
|
+
console.log(`${c.gray(S.barEnd)} ${message}`);
|
|
98
|
+
} else {
|
|
99
|
+
console.log(`${c.gray(S.barEnd)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function error(title, details) {
|
|
103
|
+
console.log(`${c.red(S.stepError)} ${c.red(c.bold(title))}`);
|
|
104
|
+
if (details) {
|
|
105
|
+
for (const line of details) {
|
|
106
|
+
barLine(c.dim(line));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
var BANNER_LINES = [
|
|
111
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
112
|
+
"\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
|
|
113
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
114
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551",
|
|
115
|
+
"\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551",
|
|
116
|
+
"\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
117
|
+
];
|
|
118
|
+
var GRADIENT = [
|
|
119
|
+
[60, 60, 60],
|
|
120
|
+
[90, 90, 90],
|
|
121
|
+
[125, 125, 125],
|
|
122
|
+
[160, 160, 160],
|
|
123
|
+
[200, 200, 200],
|
|
124
|
+
[240, 240, 240]
|
|
125
|
+
];
|
|
126
|
+
function rgb(r, g, b) {
|
|
127
|
+
return isColorSupported ? `\x1B[38;2;${r};${g};${b}m` : "";
|
|
128
|
+
}
|
|
129
|
+
function banner() {
|
|
130
|
+
console.log();
|
|
131
|
+
for (let i = 0; i < BANNER_LINES.length; i++) {
|
|
132
|
+
const [r, g, b] = GRADIENT[i];
|
|
133
|
+
console.log(`${rgb(r, g, b)}${BANNER_LINES[i]}${reset}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// lib/discover.ts
|
|
14
138
|
async function discover(repoPath) {
|
|
15
139
|
const marketplacePaths = [
|
|
16
140
|
join(repoPath, "marketplace.json"),
|
|
@@ -54,7 +178,7 @@ async function discoverFromMarketplace(repoPath, marketplace) {
|
|
|
54
178
|
for (const entry of marketplace.plugins) {
|
|
55
179
|
const sourcePath = join(repoPath, root, entry.source.replace(/^\.\//, ""));
|
|
56
180
|
if (!await dirExists(sourcePath)) {
|
|
57
|
-
|
|
181
|
+
barLine(`${c.yellow(S.warning)} ${c.yellow(`Plugin source not found: ${entry.source}`)}`);
|
|
58
182
|
continue;
|
|
59
183
|
}
|
|
60
184
|
let skills;
|
|
@@ -389,59 +513,60 @@ async function installPlugins(plugins, target, scope, repoPath, source) {
|
|
|
389
513
|
}
|
|
390
514
|
async function installToClaudeCode(plugins, scope, repoPath, source) {
|
|
391
515
|
const marketplaceName = plugins[0]?.marketplace ?? deriveMarketplaceName(source);
|
|
392
|
-
|
|
516
|
+
step("Preparing plugins for Claude Code...");
|
|
517
|
+
barEmpty();
|
|
393
518
|
await prepareForClaudeCode(plugins, repoPath, marketplaceName);
|
|
394
519
|
const claudePath = findClaude();
|
|
395
|
-
|
|
396
|
-
|
|
520
|
+
step("Adding marketplace");
|
|
521
|
+
barLine(c.dim(`Binary: ${claudePath}`));
|
|
397
522
|
try {
|
|
398
523
|
const version = execSync2(`${claudePath} --version`, { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
399
|
-
|
|
524
|
+
barLine(c.dim(`Version: ${version}`));
|
|
400
525
|
} catch {
|
|
401
|
-
|
|
526
|
+
barLine(c.dim(`Warning: could not get claude version`));
|
|
402
527
|
}
|
|
403
528
|
try {
|
|
404
529
|
const result = execSync2(`${claudePath} plugin marketplace add ${repoPath}`, {
|
|
405
530
|
encoding: "utf-8",
|
|
406
531
|
stdio: "pipe"
|
|
407
532
|
});
|
|
408
|
-
|
|
409
|
-
|
|
533
|
+
if (result.trim()) barLine(c.dim(result.trim()));
|
|
534
|
+
stepDone("Marketplace added");
|
|
410
535
|
} catch (err) {
|
|
411
536
|
const stderr = err.stderr?.toString().trim() ?? "";
|
|
412
537
|
const stdout = err.stdout?.toString().trim() ?? "";
|
|
413
538
|
if (stderr.includes("already") || stdout.includes("already")) {
|
|
414
|
-
|
|
539
|
+
stepDone(`Marketplace ${c.dim("'" + marketplaceName + "'")} already on disk`);
|
|
415
540
|
} else {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
541
|
+
stepError("Failed to add marketplace.");
|
|
542
|
+
barLine(c.dim(`Command: ${claudePath} plugin marketplace add ${repoPath}`));
|
|
543
|
+
if (stdout) barLine(c.dim(`stdout: ${stdout}`));
|
|
544
|
+
if (stderr) barLine(c.dim(`stderr: ${stderr}`));
|
|
545
|
+
barLine(c.dim(`exit code: ${err.status}`));
|
|
421
546
|
process.exit(1);
|
|
422
547
|
}
|
|
423
548
|
}
|
|
549
|
+
barEmpty();
|
|
424
550
|
for (const plugin of plugins) {
|
|
425
551
|
const pluginRef = `${plugin.name}@${marketplaceName}`;
|
|
426
|
-
|
|
427
|
-
Installing ${pluginRef}...`);
|
|
552
|
+
step(`Installing ${c.bold(pluginRef)}...`);
|
|
428
553
|
try {
|
|
429
554
|
execSync2(`${claudePath} plugin install ${pluginRef} --scope ${scope}`, {
|
|
430
555
|
encoding: "utf-8",
|
|
431
556
|
stdio: "pipe"
|
|
432
557
|
});
|
|
433
|
-
|
|
558
|
+
stepDone(`Installed ${c.cyan(pluginRef)}`);
|
|
434
559
|
} catch (err) {
|
|
435
560
|
const stderr = err.stderr?.toString().trim() ?? "";
|
|
436
561
|
const stdout = err.stdout?.toString().trim() ?? "";
|
|
437
562
|
if (stderr.includes("already") || stdout.includes("already")) {
|
|
438
|
-
|
|
563
|
+
stepDone(`${c.cyan(pluginRef)} ${c.dim("already installed")}`);
|
|
439
564
|
} else {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
565
|
+
stepError(`Failed to install ${pluginRef}`);
|
|
566
|
+
barLine(c.dim(`Command: ${claudePath} plugin install ${pluginRef} --scope ${scope}`));
|
|
567
|
+
if (stdout) barLine(c.dim(`stdout: ${stdout}`));
|
|
568
|
+
if (stderr) barLine(c.dim(`stderr: ${stderr}`));
|
|
569
|
+
barLine(c.dim(`exit code: ${err.status}`));
|
|
445
570
|
}
|
|
446
571
|
}
|
|
447
572
|
}
|
|
@@ -471,7 +596,7 @@ async function prepareForClaudeCode(plugins, repoPath, marketplaceName) {
|
|
|
471
596
|
join3(claudePluginDir, "marketplace.json"),
|
|
472
597
|
JSON.stringify(marketplaceJson, null, 2)
|
|
473
598
|
);
|
|
474
|
-
|
|
599
|
+
barLine(c.dim("Generated .claude-plugin/marketplace.json"));
|
|
475
600
|
for (const plugin of plugins) {
|
|
476
601
|
await preparePluginDirForVendor(plugin, ".claude-plugin", "CLAUDE_PLUGIN_ROOT");
|
|
477
602
|
}
|
|
@@ -501,7 +626,7 @@ async function preparePluginDirForVendor(plugin, vendorDir, envVar) {
|
|
|
501
626
|
const hasVendorPlugin = existsSync2(join3(vendorPluginDir, "plugin.json"));
|
|
502
627
|
if (hasOpenPlugin && !hasVendorPlugin) {
|
|
503
628
|
await cp(openPluginDir, vendorPluginDir, { recursive: true });
|
|
504
|
-
|
|
629
|
+
barLine(c.dim(`${plugin.name}: translated .plugin/ \u2192 ${vendorDir}/`));
|
|
505
630
|
}
|
|
506
631
|
if (!hasOpenPlugin && !hasVendorPlugin) {
|
|
507
632
|
await mkdir(vendorPluginDir, { recursive: true });
|
|
@@ -517,7 +642,7 @@ async function preparePluginDirForVendor(plugin, vendorDir, envVar) {
|
|
|
517
642
|
2
|
|
518
643
|
)
|
|
519
644
|
);
|
|
520
|
-
|
|
645
|
+
barLine(c.dim(`${plugin.name}: generated ${vendorDir}/plugin.json`));
|
|
521
646
|
}
|
|
522
647
|
await translateEnvVars(pluginPath, plugin.name, envVar);
|
|
523
648
|
}
|
|
@@ -546,8 +671,8 @@ async function translateEnvVars(pluginPath, pluginName, envVar) {
|
|
|
546
671
|
}
|
|
547
672
|
if (changed) {
|
|
548
673
|
await writeFile(filePath, content);
|
|
549
|
-
|
|
550
|
-
|
|
674
|
+
barLine(
|
|
675
|
+
c.dim(`${pluginName}: translated plugin root \u2192 \${${envVar}} in ${filePath.split("/").pop()}`)
|
|
551
676
|
);
|
|
552
677
|
}
|
|
553
678
|
}
|
|
@@ -556,6 +681,13 @@ function deriveMarketplaceName(source) {
|
|
|
556
681
|
if (source.match(/^[\w-]+\/[\w.-]+$/)) {
|
|
557
682
|
return source.replace("/", "-");
|
|
558
683
|
}
|
|
684
|
+
const sshMatch = source.match(/^git@[^:]+:(.+?)(?:\.git)?$/);
|
|
685
|
+
if (sshMatch) {
|
|
686
|
+
const parts2 = sshMatch[1].split("/").filter(Boolean);
|
|
687
|
+
if (parts2.length >= 2) {
|
|
688
|
+
return `${parts2[parts2.length - 2]}-${parts2[parts2.length - 1]}`;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
559
691
|
try {
|
|
560
692
|
const url = new URL(source);
|
|
561
693
|
const parts2 = url.pathname.replace(/\.git$/, "").split("/").filter(Boolean);
|
|
@@ -600,62 +732,79 @@ switch (command) {
|
|
|
600
732
|
}
|
|
601
733
|
function printUsage() {
|
|
602
734
|
console.log(`
|
|
603
|
-
plugins
|
|
735
|
+
${c.bold("plugins")} \u2014 Install open-plugin format plugins into agent tools
|
|
604
736
|
|
|
605
|
-
Usage:
|
|
606
|
-
plugins add <repo-path-or-url> Install plugins from a repo
|
|
607
|
-
plugins discover <repo-path-or-url> Discover plugins in a repo
|
|
608
|
-
plugins targets List available install targets
|
|
609
|
-
plugins <repo-path-or-url> Shorthand for add
|
|
737
|
+
${c.dim("Usage:")}
|
|
738
|
+
${c.cyan("plugins add")} <repo-path-or-url> Install plugins from a repo
|
|
739
|
+
${c.cyan("plugins discover")} <repo-path-or-url> Discover plugins in a repo
|
|
740
|
+
${c.cyan("plugins targets")} List available install targets
|
|
741
|
+
${c.cyan("plugins")} <repo-path-or-url> Shorthand for add
|
|
610
742
|
|
|
611
|
-
Options:
|
|
612
|
-
-t, --target <target> Target tool (e.g. claude-code). Default: auto-detect
|
|
613
|
-
-s, --scope <scope> Install scope: user, project, local. Default: user
|
|
614
|
-
-y, --yes Skip confirmation prompts
|
|
615
|
-
-h, --help Show this help
|
|
743
|
+
${c.dim("Options:")}
|
|
744
|
+
${c.yellow("-t, --target")} <target> Target tool (e.g. claude-code). Default: auto-detect
|
|
745
|
+
${c.yellow("-s, --scope")} <scope> Install scope: user, project, local. Default: user
|
|
746
|
+
${c.yellow("-y, --yes")} Skip confirmation prompts
|
|
747
|
+
${c.yellow("-h, --help")} Show this help
|
|
616
748
|
`);
|
|
617
749
|
}
|
|
618
750
|
async function cmdDiscover(source) {
|
|
619
751
|
if (!source) {
|
|
620
|
-
|
|
752
|
+
error("Provide a repo path or URL");
|
|
621
753
|
process.exit(1);
|
|
622
754
|
}
|
|
755
|
+
banner();
|
|
756
|
+
header("plugins");
|
|
623
757
|
const repoPath = resolveSource(source);
|
|
624
758
|
const plugins = await discover(repoPath);
|
|
625
759
|
if (plugins.length === 0) {
|
|
626
|
-
|
|
760
|
+
barEmpty();
|
|
761
|
+
step("No plugins found.");
|
|
762
|
+
footer();
|
|
627
763
|
return;
|
|
628
764
|
}
|
|
629
|
-
|
|
630
|
-
`);
|
|
765
|
+
barEmpty();
|
|
766
|
+
step(`Found ${c.bold(String(plugins.length))} plugin(s) in ${c.dim(source)}`);
|
|
767
|
+
barEmpty();
|
|
631
768
|
for (const p of plugins) {
|
|
632
769
|
printPlugin(p);
|
|
633
770
|
}
|
|
771
|
+
footer();
|
|
634
772
|
}
|
|
635
773
|
async function cmdTargets() {
|
|
636
774
|
const targets = await getTargets();
|
|
775
|
+
banner();
|
|
776
|
+
header("plugins");
|
|
637
777
|
if (targets.length === 0) {
|
|
638
|
-
|
|
778
|
+
barEmpty();
|
|
779
|
+
step("No supported targets detected.");
|
|
780
|
+
footer();
|
|
639
781
|
return;
|
|
640
782
|
}
|
|
641
|
-
|
|
783
|
+
barEmpty();
|
|
784
|
+
step("Available install targets");
|
|
785
|
+
barEmpty();
|
|
642
786
|
for (const t of targets) {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
787
|
+
barLine(` ${c.bold(t.name)}`);
|
|
788
|
+
barLine(` ${c.dim(t.description)}`);
|
|
789
|
+
barLine(` Config: ${c.dim(t.configPath)}`);
|
|
790
|
+
barLine(` Status: ${t.detected ? c.green("detected") : c.dim("not found")}`);
|
|
791
|
+
barEmpty();
|
|
648
792
|
}
|
|
793
|
+
footer();
|
|
649
794
|
}
|
|
650
795
|
async function cmdInstall(source, opts) {
|
|
651
796
|
if (!source) {
|
|
652
|
-
|
|
797
|
+
error("Provide a repo path or URL");
|
|
653
798
|
process.exit(1);
|
|
654
799
|
}
|
|
800
|
+
banner();
|
|
801
|
+
header("plugins");
|
|
655
802
|
const repoPath = resolveSource(source);
|
|
656
803
|
const plugins = await discover(repoPath);
|
|
657
804
|
if (plugins.length === 0) {
|
|
658
|
-
|
|
805
|
+
barEmpty();
|
|
806
|
+
step("No plugins found.");
|
|
807
|
+
footer();
|
|
659
808
|
return;
|
|
660
809
|
}
|
|
661
810
|
const targets = await getTargets();
|
|
@@ -664,29 +813,36 @@ async function cmdInstall(source, opts) {
|
|
|
664
813
|
if (opts.target) {
|
|
665
814
|
const found = targets.find((t) => t.id === opts.target);
|
|
666
815
|
if (!found) {
|
|
667
|
-
|
|
668
|
-
|
|
816
|
+
barEmpty();
|
|
817
|
+
stepError(`Unknown target: ${c.bold(opts.target)}`);
|
|
818
|
+
barLine(c.dim(`Available: ${targets.map((t) => t.id).join(", ")}`));
|
|
819
|
+
footer();
|
|
669
820
|
process.exit(1);
|
|
670
821
|
}
|
|
671
822
|
installTargets = [found];
|
|
672
823
|
} else if (detectedTargets.length === 0) {
|
|
673
|
-
|
|
824
|
+
barEmpty();
|
|
825
|
+
stepError("No supported targets detected.");
|
|
826
|
+
barLine(c.dim("Use --target to specify one."));
|
|
827
|
+
footer();
|
|
674
828
|
process.exit(1);
|
|
675
829
|
} else {
|
|
676
830
|
installTargets = detectedTargets;
|
|
677
831
|
}
|
|
678
|
-
|
|
679
|
-
`);
|
|
832
|
+
barEmpty();
|
|
833
|
+
step(`Found ${c.bold(String(plugins.length))} plugin(s)`);
|
|
834
|
+
barEmpty();
|
|
680
835
|
for (const p of plugins) {
|
|
681
836
|
printPlugin(p);
|
|
682
837
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
838
|
+
barLine(`${c.dim("Targets:")} ${installTargets.map((t) => c.cyan(t.name)).join(c.dim(", "))}`);
|
|
839
|
+
barLine(`${c.dim("Scope:")} ${c.cyan(opts.scope ?? "user")}`);
|
|
840
|
+
barEmpty();
|
|
686
841
|
if (!opts.yes) {
|
|
687
|
-
const response = await readLine(
|
|
688
|
-
if (response.trim().toLowerCase()
|
|
689
|
-
|
|
842
|
+
const response = await readLine(`${c.cyan(S.stepActive)} Install? ${c.dim("[Y/n]")} `);
|
|
843
|
+
if (response.trim().toLowerCase() === "n") {
|
|
844
|
+
step("Aborted.");
|
|
845
|
+
footer();
|
|
690
846
|
return;
|
|
691
847
|
}
|
|
692
848
|
}
|
|
@@ -694,11 +850,13 @@ async function cmdInstall(source, opts) {
|
|
|
694
850
|
for (const target of installTargets) {
|
|
695
851
|
await installPlugins(plugins, target, scope, repoPath, source);
|
|
696
852
|
}
|
|
697
|
-
|
|
853
|
+
barEmpty();
|
|
854
|
+
stepDone(c.green("Done.") + " Restart your agent tools to load the plugins.");
|
|
855
|
+
footer();
|
|
698
856
|
}
|
|
699
857
|
function printPlugin(p) {
|
|
700
|
-
|
|
701
|
-
if (p.description)
|
|
858
|
+
barLine(`${c.bold(p.name)} ${p.version ? c.dim(`(v${p.version})`) : ""}`);
|
|
859
|
+
if (p.description) barLine(`${c.dim(p.description)}`);
|
|
702
860
|
const parts = [];
|
|
703
861
|
if (p.skills.length) parts.push(`${p.skills.length} skill(s)`);
|
|
704
862
|
if (p.commands.length) parts.push(`${p.commands.length} command(s)`);
|
|
@@ -707,21 +865,71 @@ function printPlugin(p) {
|
|
|
707
865
|
if (p.hasHooks) parts.push("hooks");
|
|
708
866
|
if (p.hasMcp) parts.push("MCP servers");
|
|
709
867
|
if (p.hasLsp) parts.push("LSP servers");
|
|
710
|
-
if (parts.length)
|
|
711
|
-
|
|
868
|
+
if (parts.length) barLine(`${c.dim("Components:")} ${parts.join(c.dim(", "))}`);
|
|
869
|
+
barEmpty();
|
|
870
|
+
}
|
|
871
|
+
function sshToHttps(sshUrl) {
|
|
872
|
+
const m = sshUrl.match(/^git@([^:]+):(.+)$/);
|
|
873
|
+
if (!m) return null;
|
|
874
|
+
return `https://${m[1]}/${m[2]}`;
|
|
712
875
|
}
|
|
713
876
|
function resolveSource(source) {
|
|
714
877
|
if (source.startsWith("https://") || source.startsWith("git@") || source.match(/^[\w-]+\/[\w.-]+$/)) {
|
|
715
878
|
const url = source.match(/^[\w-]+\/[\w.-]+$/) ? `https://github.com/${source}` : source;
|
|
716
879
|
const cacheDir = join4(process.env.HOME ?? "~", ".cache", "plugins");
|
|
717
880
|
mkdirSync(cacheDir, { recursive: true });
|
|
718
|
-
const slug = url.replace(/^https?:\/\//, "").replace(/\.git$/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
881
|
+
const slug = url.replace(/^https?:\/\//, "").replace(/^git@/, "").replace(/\.git$/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
719
882
|
const tmpDir = join4(cacheDir, slug);
|
|
720
883
|
if (existsSync3(join4(tmpDir, ".git", "HEAD"))) {
|
|
721
884
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
722
885
|
}
|
|
723
|
-
|
|
724
|
-
|
|
886
|
+
step(`Source: ${c.dim(url)}`);
|
|
887
|
+
barEmpty();
|
|
888
|
+
try {
|
|
889
|
+
execSync3(`git clone --depth 1 -q ${url} ${tmpDir}`, { stdio: "pipe" });
|
|
890
|
+
} catch (err) {
|
|
891
|
+
const stderr = err.stderr?.toString() ?? "";
|
|
892
|
+
if (url.startsWith("git@") && stderr.includes("Permission denied")) {
|
|
893
|
+
const httpsUrl = sshToHttps(url);
|
|
894
|
+
if (httpsUrl) {
|
|
895
|
+
barLine(c.yellow("SSH authentication failed. Retrying over HTTPS..."));
|
|
896
|
+
step(`Source: ${c.dim(httpsUrl)}`);
|
|
897
|
+
barEmpty();
|
|
898
|
+
try {
|
|
899
|
+
execSync3(`git clone --depth 1 -q ${httpsUrl} ${tmpDir}`, { stdio: "inherit" });
|
|
900
|
+
stepDone("Repository cloned");
|
|
901
|
+
barEmpty();
|
|
902
|
+
return tmpDir;
|
|
903
|
+
} catch {
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (existsSync3(tmpDir)) {
|
|
908
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
909
|
+
}
|
|
910
|
+
if (stderr.includes("Permission denied") || stderr.includes("Could not read from remote repository")) {
|
|
911
|
+
barEmpty();
|
|
912
|
+
stepError(`Could not access ${c.bold(url)}`);
|
|
913
|
+
barEmpty();
|
|
914
|
+
barLine(c.dim("Make sure you have access to this repository. For private repos, try:"));
|
|
915
|
+
barLine(` ${c.dim("HTTPS:")} plugins add https://github.com/owner/repo`);
|
|
916
|
+
barLine(` ${c.dim(" (uses git credential helper / browser auth)")}`);
|
|
917
|
+
barLine(` ${c.dim("SSH:")} plugins add git@github.com:owner/repo.git`);
|
|
918
|
+
barLine(` ${c.dim(" (requires SSH keys)")}`);
|
|
919
|
+
} else if (stderr.includes("not found") || stderr.includes("does not exist") || err.status === 128) {
|
|
920
|
+
barEmpty();
|
|
921
|
+
stepError(`Repository not found: ${c.bold(url)}`);
|
|
922
|
+
barLine(c.dim("Check that the URL is correct and the repository exists."));
|
|
923
|
+
} else {
|
|
924
|
+
barEmpty();
|
|
925
|
+
stepError("git clone failed.");
|
|
926
|
+
if (stderr.trim()) barLine(c.dim(stderr.trim()));
|
|
927
|
+
}
|
|
928
|
+
footer();
|
|
929
|
+
process.exit(1);
|
|
930
|
+
}
|
|
931
|
+
stepDone("Repository cloned");
|
|
932
|
+
barEmpty();
|
|
725
933
|
return tmpDir;
|
|
726
934
|
}
|
|
727
935
|
return resolve(source);
|
|
@@ -731,6 +939,7 @@ function readLine(prompt) {
|
|
|
731
939
|
return new Promise((resolve2) => {
|
|
732
940
|
rl.question(prompt, (answer) => {
|
|
733
941
|
rl.close();
|
|
942
|
+
if (!process.stdin.isTTY) process.stdout.write("\n");
|
|
734
943
|
resolve2(answer);
|
|
735
944
|
});
|
|
736
945
|
});
|