plugins 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +322 -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);
|
|
@@ -568,7 +700,41 @@ function deriveMarketplaceName(source) {
|
|
|
568
700
|
return parts[parts.length - 1] ?? "plugins";
|
|
569
701
|
}
|
|
570
702
|
|
|
703
|
+
// lib/telemetry.ts
|
|
704
|
+
var TELEMETRY_URL = "https://plugins-telemetry.labs.vercel.dev/t";
|
|
705
|
+
var cliVersion = null;
|
|
706
|
+
function isCI() {
|
|
707
|
+
return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.BUILDKITE || process.env.JENKINS_URL || process.env.TEAMCITY_VERSION);
|
|
708
|
+
}
|
|
709
|
+
function isEnabled() {
|
|
710
|
+
return !process.env.DISABLE_TELEMETRY && !process.env.DO_NOT_TRACK;
|
|
711
|
+
}
|
|
712
|
+
function setVersion(version) {
|
|
713
|
+
cliVersion = version;
|
|
714
|
+
}
|
|
715
|
+
function track(data) {
|
|
716
|
+
if (!isEnabled()) return;
|
|
717
|
+
try {
|
|
718
|
+
const params = new URLSearchParams();
|
|
719
|
+
if (cliVersion) {
|
|
720
|
+
params.set("v", cliVersion);
|
|
721
|
+
}
|
|
722
|
+
if (isCI()) {
|
|
723
|
+
params.set("ci", "1");
|
|
724
|
+
}
|
|
725
|
+
for (const [key, value] of Object.entries(data)) {
|
|
726
|
+
if (value !== void 0 && value !== null) {
|
|
727
|
+
params.set(key, String(value));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {
|
|
731
|
+
});
|
|
732
|
+
} catch {
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
571
736
|
// index.ts
|
|
737
|
+
setVersion("1.0.1");
|
|
572
738
|
var { values, positionals } = parseArgs({
|
|
573
739
|
args: process.argv.slice(2),
|
|
574
740
|
options: {
|
|
@@ -600,62 +766,79 @@ switch (command) {
|
|
|
600
766
|
}
|
|
601
767
|
function printUsage() {
|
|
602
768
|
console.log(`
|
|
603
|
-
plugins
|
|
769
|
+
${c.bold("plugins")} \u2014 Install open-plugin format plugins into agent tools
|
|
604
770
|
|
|
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
|
|
771
|
+
${c.dim("Usage:")}
|
|
772
|
+
${c.cyan("plugins add")} <repo-path-or-url> Install plugins from a repo
|
|
773
|
+
${c.cyan("plugins discover")} <repo-path-or-url> Discover plugins in a repo
|
|
774
|
+
${c.cyan("plugins targets")} List available install targets
|
|
775
|
+
${c.cyan("plugins")} <repo-path-or-url> Shorthand for add
|
|
610
776
|
|
|
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
|
|
777
|
+
${c.dim("Options:")}
|
|
778
|
+
${c.yellow("-t, --target")} <target> Target tool (e.g. claude-code). Default: auto-detect
|
|
779
|
+
${c.yellow("-s, --scope")} <scope> Install scope: user, project, local. Default: user
|
|
780
|
+
${c.yellow("-y, --yes")} Skip confirmation prompts
|
|
781
|
+
${c.yellow("-h, --help")} Show this help
|
|
616
782
|
`);
|
|
617
783
|
}
|
|
618
784
|
async function cmdDiscover(source) {
|
|
619
785
|
if (!source) {
|
|
620
|
-
|
|
786
|
+
error("Provide a repo path or URL");
|
|
621
787
|
process.exit(1);
|
|
622
788
|
}
|
|
789
|
+
banner();
|
|
790
|
+
header("plugins");
|
|
623
791
|
const repoPath = resolveSource(source);
|
|
624
792
|
const plugins = await discover(repoPath);
|
|
625
793
|
if (plugins.length === 0) {
|
|
626
|
-
|
|
794
|
+
barEmpty();
|
|
795
|
+
step("No plugins found.");
|
|
796
|
+
footer();
|
|
627
797
|
return;
|
|
628
798
|
}
|
|
629
|
-
|
|
630
|
-
`);
|
|
799
|
+
barEmpty();
|
|
800
|
+
step(`Found ${c.bold(String(plugins.length))} plugin(s) in ${c.dim(source)}`);
|
|
801
|
+
barEmpty();
|
|
631
802
|
for (const p of plugins) {
|
|
632
803
|
printPlugin(p);
|
|
633
804
|
}
|
|
805
|
+
footer();
|
|
634
806
|
}
|
|
635
807
|
async function cmdTargets() {
|
|
636
808
|
const targets = await getTargets();
|
|
809
|
+
banner();
|
|
810
|
+
header("plugins");
|
|
637
811
|
if (targets.length === 0) {
|
|
638
|
-
|
|
812
|
+
barEmpty();
|
|
813
|
+
step("No supported targets detected.");
|
|
814
|
+
footer();
|
|
639
815
|
return;
|
|
640
816
|
}
|
|
641
|
-
|
|
817
|
+
barEmpty();
|
|
818
|
+
step("Available install targets");
|
|
819
|
+
barEmpty();
|
|
642
820
|
for (const t of targets) {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
821
|
+
barLine(` ${c.bold(t.name)}`);
|
|
822
|
+
barLine(` ${c.dim(t.description)}`);
|
|
823
|
+
barLine(` Config: ${c.dim(t.configPath)}`);
|
|
824
|
+
barLine(` Status: ${t.detected ? c.green("detected") : c.dim("not found")}`);
|
|
825
|
+
barEmpty();
|
|
648
826
|
}
|
|
827
|
+
footer();
|
|
649
828
|
}
|
|
650
829
|
async function cmdInstall(source, opts) {
|
|
651
830
|
if (!source) {
|
|
652
|
-
|
|
831
|
+
error("Provide a repo path or URL");
|
|
653
832
|
process.exit(1);
|
|
654
833
|
}
|
|
834
|
+
banner();
|
|
835
|
+
header("plugins");
|
|
655
836
|
const repoPath = resolveSource(source);
|
|
656
837
|
const plugins = await discover(repoPath);
|
|
657
838
|
if (plugins.length === 0) {
|
|
658
|
-
|
|
839
|
+
barEmpty();
|
|
840
|
+
step("No plugins found.");
|
|
841
|
+
footer();
|
|
659
842
|
return;
|
|
660
843
|
}
|
|
661
844
|
const targets = await getTargets();
|
|
@@ -664,29 +847,36 @@ async function cmdInstall(source, opts) {
|
|
|
664
847
|
if (opts.target) {
|
|
665
848
|
const found = targets.find((t) => t.id === opts.target);
|
|
666
849
|
if (!found) {
|
|
667
|
-
|
|
668
|
-
|
|
850
|
+
barEmpty();
|
|
851
|
+
stepError(`Unknown target: ${c.bold(opts.target)}`);
|
|
852
|
+
barLine(c.dim(`Available: ${targets.map((t) => t.id).join(", ")}`));
|
|
853
|
+
footer();
|
|
669
854
|
process.exit(1);
|
|
670
855
|
}
|
|
671
856
|
installTargets = [found];
|
|
672
857
|
} else if (detectedTargets.length === 0) {
|
|
673
|
-
|
|
858
|
+
barEmpty();
|
|
859
|
+
stepError("No supported targets detected.");
|
|
860
|
+
barLine(c.dim("Use --target to specify one."));
|
|
861
|
+
footer();
|
|
674
862
|
process.exit(1);
|
|
675
863
|
} else {
|
|
676
864
|
installTargets = detectedTargets;
|
|
677
865
|
}
|
|
678
|
-
|
|
679
|
-
`);
|
|
866
|
+
barEmpty();
|
|
867
|
+
step(`Found ${c.bold(String(plugins.length))} plugin(s)`);
|
|
868
|
+
barEmpty();
|
|
680
869
|
for (const p of plugins) {
|
|
681
870
|
printPlugin(p);
|
|
682
871
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
872
|
+
barLine(`${c.dim("Targets:")} ${installTargets.map((t) => c.cyan(t.name)).join(c.dim(", "))}`);
|
|
873
|
+
barLine(`${c.dim("Scope:")} ${c.cyan(opts.scope ?? "user")}`);
|
|
874
|
+
barEmpty();
|
|
686
875
|
if (!opts.yes) {
|
|
687
|
-
const response = await readLine(
|
|
688
|
-
if (response.trim().toLowerCase()
|
|
689
|
-
|
|
876
|
+
const response = await readLine(`${c.cyan(S.stepActive)} Install? ${c.dim("[Y/n]")} `);
|
|
877
|
+
if (response.trim().toLowerCase() === "n") {
|
|
878
|
+
step("Aborted.");
|
|
879
|
+
footer();
|
|
690
880
|
return;
|
|
691
881
|
}
|
|
692
882
|
}
|
|
@@ -694,11 +884,21 @@ async function cmdInstall(source, opts) {
|
|
|
694
884
|
for (const target of installTargets) {
|
|
695
885
|
await installPlugins(plugins, target, scope, repoPath, source);
|
|
696
886
|
}
|
|
697
|
-
|
|
887
|
+
track({
|
|
888
|
+
event: "install",
|
|
889
|
+
source,
|
|
890
|
+
plugins: plugins.map((p) => p.name).join(","),
|
|
891
|
+
pluginCount: String(plugins.length),
|
|
892
|
+
targets: installTargets.map((t) => t.id).join(","),
|
|
893
|
+
scope
|
|
894
|
+
});
|
|
895
|
+
barEmpty();
|
|
896
|
+
stepDone(c.green("Done.") + " Restart your agent tools to load the plugins.");
|
|
897
|
+
footer();
|
|
698
898
|
}
|
|
699
899
|
function printPlugin(p) {
|
|
700
|
-
|
|
701
|
-
if (p.description)
|
|
900
|
+
barLine(`${c.bold(p.name)} ${p.version ? c.dim(`(v${p.version})`) : ""}`);
|
|
901
|
+
if (p.description) barLine(`${c.dim(p.description)}`);
|
|
702
902
|
const parts = [];
|
|
703
903
|
if (p.skills.length) parts.push(`${p.skills.length} skill(s)`);
|
|
704
904
|
if (p.commands.length) parts.push(`${p.commands.length} command(s)`);
|
|
@@ -707,21 +907,71 @@ function printPlugin(p) {
|
|
|
707
907
|
if (p.hasHooks) parts.push("hooks");
|
|
708
908
|
if (p.hasMcp) parts.push("MCP servers");
|
|
709
909
|
if (p.hasLsp) parts.push("LSP servers");
|
|
710
|
-
if (parts.length)
|
|
711
|
-
|
|
910
|
+
if (parts.length) barLine(`${c.dim("Components:")} ${parts.join(c.dim(", "))}`);
|
|
911
|
+
barEmpty();
|
|
912
|
+
}
|
|
913
|
+
function sshToHttps(sshUrl) {
|
|
914
|
+
const m = sshUrl.match(/^git@([^:]+):(.+)$/);
|
|
915
|
+
if (!m) return null;
|
|
916
|
+
return `https://${m[1]}/${m[2]}`;
|
|
712
917
|
}
|
|
713
918
|
function resolveSource(source) {
|
|
714
919
|
if (source.startsWith("https://") || source.startsWith("git@") || source.match(/^[\w-]+\/[\w.-]+$/)) {
|
|
715
920
|
const url = source.match(/^[\w-]+\/[\w.-]+$/) ? `https://github.com/${source}` : source;
|
|
716
921
|
const cacheDir = join4(process.env.HOME ?? "~", ".cache", "plugins");
|
|
717
922
|
mkdirSync(cacheDir, { recursive: true });
|
|
718
|
-
const slug = url.replace(/^https?:\/\//, "").replace(/\.git$/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
923
|
+
const slug = url.replace(/^https?:\/\//, "").replace(/^git@/, "").replace(/\.git$/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
719
924
|
const tmpDir = join4(cacheDir, slug);
|
|
720
925
|
if (existsSync3(join4(tmpDir, ".git", "HEAD"))) {
|
|
721
926
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
722
927
|
}
|
|
723
|
-
|
|
724
|
-
|
|
928
|
+
step(`Source: ${c.dim(url)}`);
|
|
929
|
+
barEmpty();
|
|
930
|
+
try {
|
|
931
|
+
execSync3(`git clone --depth 1 -q ${url} ${tmpDir}`, { stdio: "pipe" });
|
|
932
|
+
} catch (err) {
|
|
933
|
+
const stderr = err.stderr?.toString() ?? "";
|
|
934
|
+
if (url.startsWith("git@") && stderr.includes("Permission denied")) {
|
|
935
|
+
const httpsUrl = sshToHttps(url);
|
|
936
|
+
if (httpsUrl) {
|
|
937
|
+
barLine(c.yellow("SSH authentication failed. Retrying over HTTPS..."));
|
|
938
|
+
step(`Source: ${c.dim(httpsUrl)}`);
|
|
939
|
+
barEmpty();
|
|
940
|
+
try {
|
|
941
|
+
execSync3(`git clone --depth 1 -q ${httpsUrl} ${tmpDir}`, { stdio: "inherit" });
|
|
942
|
+
stepDone("Repository cloned");
|
|
943
|
+
barEmpty();
|
|
944
|
+
return tmpDir;
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (existsSync3(tmpDir)) {
|
|
950
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
951
|
+
}
|
|
952
|
+
if (stderr.includes("Permission denied") || stderr.includes("Could not read from remote repository")) {
|
|
953
|
+
barEmpty();
|
|
954
|
+
stepError(`Could not access ${c.bold(url)}`);
|
|
955
|
+
barEmpty();
|
|
956
|
+
barLine(c.dim("Make sure you have access to this repository. For private repos, try:"));
|
|
957
|
+
barLine(` ${c.dim("HTTPS:")} plugins add https://github.com/owner/repo`);
|
|
958
|
+
barLine(` ${c.dim(" (uses git credential helper / browser auth)")}`);
|
|
959
|
+
barLine(` ${c.dim("SSH:")} plugins add git@github.com:owner/repo.git`);
|
|
960
|
+
barLine(` ${c.dim(" (requires SSH keys)")}`);
|
|
961
|
+
} else if (stderr.includes("not found") || stderr.includes("does not exist") || err.status === 128) {
|
|
962
|
+
barEmpty();
|
|
963
|
+
stepError(`Repository not found: ${c.bold(url)}`);
|
|
964
|
+
barLine(c.dim("Check that the URL is correct and the repository exists."));
|
|
965
|
+
} else {
|
|
966
|
+
barEmpty();
|
|
967
|
+
stepError("git clone failed.");
|
|
968
|
+
if (stderr.trim()) barLine(c.dim(stderr.trim()));
|
|
969
|
+
}
|
|
970
|
+
footer();
|
|
971
|
+
process.exit(1);
|
|
972
|
+
}
|
|
973
|
+
stepDone("Repository cloned");
|
|
974
|
+
barEmpty();
|
|
725
975
|
return tmpDir;
|
|
726
976
|
}
|
|
727
977
|
return resolve(source);
|
|
@@ -731,6 +981,7 @@ function readLine(prompt) {
|
|
|
731
981
|
return new Promise((resolve2) => {
|
|
732
982
|
rl.question(prompt, (answer) => {
|
|
733
983
|
rl.close();
|
|
984
|
+
if (!process.stdin.isTTY) process.stdout.write("\n");
|
|
734
985
|
resolve2(answer);
|
|
735
986
|
});
|
|
736
987
|
});
|