plugins 1.1.3 → 1.1.5
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/README.md +4 -3
- package/dist/index.js +450 -165
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# plugins
|
|
2
2
|
|
|
3
|
-
Install [open-plugin](https://github.com/anthropics/open-plugin) format plugins into agent tools. Works with [Claude Code](https://code.claude.com).
|
|
3
|
+
Install [open-plugin](https://github.com/anthropics/open-plugin) format plugins into agent tools. Works with [Claude Code](https://code.claude.com) and [Cursor](https://cursor.com).
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx plugins add owner/repo
|
|
@@ -54,18 +54,19 @@ If no subcommand is given, `plugins <source>` defaults to `add`.
|
|
|
54
54
|
|
|
55
55
|
| Flag | Short | Default | Description |
|
|
56
56
|
|---|---|---|---|
|
|
57
|
-
| `--target` | `-t` | auto-detect | Install to a specific agent tool (`claude-code`) |
|
|
57
|
+
| `--target` | `-t` | auto-detect | Install to a specific agent tool (`claude-code`, `cursor`) |
|
|
58
58
|
| `--scope` | `-s` | `user` | Installation scope: `user`, `project`, or `local` |
|
|
59
59
|
| `--yes` | `-y` | `false` | Skip the confirmation prompt |
|
|
60
60
|
| `--help` | `-h` | | Show usage information |
|
|
61
61
|
|
|
62
62
|
## Supported targets
|
|
63
63
|
|
|
64
|
-
The CLI auto-detects which agent tools are installed.
|
|
64
|
+
The CLI auto-detects which agent tools are installed and installs to all of them.
|
|
65
65
|
|
|
66
66
|
| Target | Detection |
|
|
67
67
|
|---|---|
|
|
68
68
|
| [Claude Code](https://code.claude.com) | `claude` binary on PATH |
|
|
69
|
+
| [Cursor](https://cursor.com) | `cursor` + `claude` binaries on PATH |
|
|
69
70
|
|
|
70
71
|
## How it works
|
|
71
72
|
|
package/dist/index.js
CHANGED
|
@@ -11,130 +11,6 @@ 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
|
|
138
14
|
async function discover(repoPath) {
|
|
139
15
|
const marketplacePaths = [
|
|
140
16
|
join(repoPath, "marketplace.json"),
|
|
@@ -152,11 +28,11 @@ async function discover(repoPath) {
|
|
|
152
28
|
}
|
|
153
29
|
if (await isPluginDir(repoPath)) {
|
|
154
30
|
const plugin = await inspectPlugin(repoPath);
|
|
155
|
-
return plugin ? [plugin] : [];
|
|
31
|
+
return { plugins: plugin ? [plugin] : [], remotePlugins: [], missingPaths: [] };
|
|
156
32
|
}
|
|
157
33
|
const plugins = [];
|
|
158
34
|
await scanForPlugins(repoPath, plugins, 2);
|
|
159
|
-
return plugins;
|
|
35
|
+
return { plugins, remotePlugins: [], missingPaths: [] };
|
|
160
36
|
}
|
|
161
37
|
async function scanForPlugins(dirPath, results, depth) {
|
|
162
38
|
if (depth <= 0) return;
|
|
@@ -174,11 +50,21 @@ async function scanForPlugins(dirPath, results, depth) {
|
|
|
174
50
|
}
|
|
175
51
|
async function discoverFromMarketplace(repoPath, marketplace) {
|
|
176
52
|
const plugins = [];
|
|
53
|
+
const remotePlugins = [];
|
|
54
|
+
const missingPaths = [];
|
|
177
55
|
const root = marketplace.metadata?.pluginRoot ?? ".";
|
|
178
56
|
for (const entry of marketplace.plugins) {
|
|
57
|
+
if (typeof entry.source !== "string") {
|
|
58
|
+
remotePlugins.push({
|
|
59
|
+
name: entry.name,
|
|
60
|
+
description: entry.description || void 0,
|
|
61
|
+
source: entry.source
|
|
62
|
+
});
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
179
65
|
const sourcePath = join(repoPath, root, entry.source.replace(/^\.\//, ""));
|
|
180
66
|
if (!await dirExists(sourcePath)) {
|
|
181
|
-
|
|
67
|
+
missingPaths.push(entry.source);
|
|
182
68
|
continue;
|
|
183
69
|
}
|
|
184
70
|
let skills;
|
|
@@ -234,7 +120,7 @@ async function discoverFromMarketplace(repoPath, marketplace) {
|
|
|
234
120
|
marketplaceEntry: entry
|
|
235
121
|
});
|
|
236
122
|
}
|
|
237
|
-
return plugins;
|
|
123
|
+
return { plugins, remotePlugins, missingPaths };
|
|
238
124
|
}
|
|
239
125
|
async function isPluginDir(dirPath) {
|
|
240
126
|
const checks = [
|
|
@@ -487,6 +373,296 @@ import { mkdir, cp, readFile as readFile2, writeFile } from "fs/promises";
|
|
|
487
373
|
import { existsSync as existsSync2 } from "fs";
|
|
488
374
|
import { execSync as execSync2 } from "child_process";
|
|
489
375
|
import { homedir as homedir2 } from "os";
|
|
376
|
+
|
|
377
|
+
// lib/ui.ts
|
|
378
|
+
var isColorSupported = process.env.FORCE_COLOR !== "0" && !process.env.NO_COLOR && (process.env.FORCE_COLOR !== void 0 || process.stdout.isTTY);
|
|
379
|
+
function ansi(code) {
|
|
380
|
+
return isColorSupported ? `\x1B[${code}m` : "";
|
|
381
|
+
}
|
|
382
|
+
var reset = ansi("0");
|
|
383
|
+
var bold = ansi("1");
|
|
384
|
+
var dim = ansi("2");
|
|
385
|
+
var italic = ansi("3");
|
|
386
|
+
var underline = ansi("4");
|
|
387
|
+
var red = ansi("31");
|
|
388
|
+
var green = ansi("32");
|
|
389
|
+
var yellow = ansi("33");
|
|
390
|
+
var blue = ansi("34");
|
|
391
|
+
var magenta = ansi("35");
|
|
392
|
+
var cyan = ansi("36");
|
|
393
|
+
var gray = ansi("90");
|
|
394
|
+
var bgGreen = ansi("42");
|
|
395
|
+
var bgRed = ansi("41");
|
|
396
|
+
var bgYellow = ansi("43");
|
|
397
|
+
var bgCyan = ansi("46");
|
|
398
|
+
var black = ansi("30");
|
|
399
|
+
var c = {
|
|
400
|
+
bold: (s) => `${bold}${s}${reset}`,
|
|
401
|
+
dim: (s) => `${dim}${s}${reset}`,
|
|
402
|
+
italic: (s) => `${italic}${s}${reset}`,
|
|
403
|
+
underline: (s) => `${underline}${s}${reset}`,
|
|
404
|
+
red: (s) => `${red}${s}${reset}`,
|
|
405
|
+
green: (s) => `${green}${s}${reset}`,
|
|
406
|
+
yellow: (s) => `${yellow}${s}${reset}`,
|
|
407
|
+
blue: (s) => `${blue}${s}${reset}`,
|
|
408
|
+
magenta: (s) => `${magenta}${s}${reset}`,
|
|
409
|
+
cyan: (s) => `${cyan}${s}${reset}`,
|
|
410
|
+
gray: (s) => `${gray}${s}${reset}`,
|
|
411
|
+
bgGreen: (s) => `${bgGreen}${black}${s}${reset}`,
|
|
412
|
+
bgRed: (s) => `${bgRed}${black}${s}${reset}`,
|
|
413
|
+
bgYellow: (s) => `${bgYellow}${black}${s}${reset}`,
|
|
414
|
+
bgCyan: (s) => `${bgCyan}${black}${s}${reset}`
|
|
415
|
+
};
|
|
416
|
+
var S = {
|
|
417
|
+
// Box drawing
|
|
418
|
+
bar: "\u2502",
|
|
419
|
+
barEnd: "\u2514",
|
|
420
|
+
barStart: "\u250C",
|
|
421
|
+
barH: "\u2500",
|
|
422
|
+
corner: "\u256E",
|
|
423
|
+
// Bullets
|
|
424
|
+
diamond: "\u25C7",
|
|
425
|
+
diamondFilled: "\u25C6",
|
|
426
|
+
bullet: "\u25CF",
|
|
427
|
+
circle: "\u25CB",
|
|
428
|
+
check: "\u2714",
|
|
429
|
+
cross: "\u2716",
|
|
430
|
+
arrow: "\u2192",
|
|
431
|
+
warning: "\u25B2",
|
|
432
|
+
info: "\u2139",
|
|
433
|
+
step: "\u25C7",
|
|
434
|
+
stepActive: "\u25C6",
|
|
435
|
+
stepComplete: "\u25CF",
|
|
436
|
+
stepError: "\u25A0"
|
|
437
|
+
};
|
|
438
|
+
function barLine(content = "") {
|
|
439
|
+
console.log(`${c.gray(S.bar)} ${content}`);
|
|
440
|
+
}
|
|
441
|
+
function barEmpty() {
|
|
442
|
+
console.log(`${c.gray(S.bar)}`);
|
|
443
|
+
}
|
|
444
|
+
function step(content) {
|
|
445
|
+
console.log(`${c.gray(S.step)} ${content}`);
|
|
446
|
+
}
|
|
447
|
+
function stepDone(content) {
|
|
448
|
+
console.log(`${c.green(S.stepComplete)} ${content}`);
|
|
449
|
+
}
|
|
450
|
+
function stepError(content) {
|
|
451
|
+
console.log(`${c.red(S.stepError)} ${content}`);
|
|
452
|
+
}
|
|
453
|
+
function header(label) {
|
|
454
|
+
console.log();
|
|
455
|
+
console.log(`${c.gray(S.barStart)} ${c.bgCyan(` ${label} `)}`);
|
|
456
|
+
}
|
|
457
|
+
function footer(message) {
|
|
458
|
+
if (message) {
|
|
459
|
+
console.log(`${c.gray(S.barEnd)} ${message}`);
|
|
460
|
+
} else {
|
|
461
|
+
console.log(`${c.gray(S.barEnd)}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function error(title, details) {
|
|
465
|
+
console.log(`${c.red(S.stepError)} ${c.red(c.bold(title))}`);
|
|
466
|
+
if (details) {
|
|
467
|
+
for (const line of details) {
|
|
468
|
+
barLine(c.dim(line));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function multiSelect(title, options, maxVisible = 8) {
|
|
473
|
+
if (!process.stdin.isTTY) {
|
|
474
|
+
return options.map((o) => o.value);
|
|
475
|
+
}
|
|
476
|
+
const { createInterface: createInterface2, emitKeypressEvents } = await import("readline");
|
|
477
|
+
const { Writable } = await import("stream");
|
|
478
|
+
const silentOutput = new Writable({
|
|
479
|
+
write(_chunk, _encoding, callback) {
|
|
480
|
+
callback();
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
return new Promise((resolve2) => {
|
|
484
|
+
const rl = createInterface2({
|
|
485
|
+
input: process.stdin,
|
|
486
|
+
output: silentOutput,
|
|
487
|
+
terminal: false
|
|
488
|
+
});
|
|
489
|
+
if (process.stdin.isTTY) {
|
|
490
|
+
process.stdin.setRawMode(true);
|
|
491
|
+
}
|
|
492
|
+
emitKeypressEvents(process.stdin, rl);
|
|
493
|
+
let query = "";
|
|
494
|
+
let cursor = 0;
|
|
495
|
+
const selected = new Set(options.map((o) => o.value));
|
|
496
|
+
let lastRenderHeight = 0;
|
|
497
|
+
const filter = (item, q) => {
|
|
498
|
+
if (!q) return true;
|
|
499
|
+
const lq = q.toLowerCase();
|
|
500
|
+
return item.label.toLowerCase().includes(lq) || (item.hint?.toLowerCase().includes(lq) ?? false);
|
|
501
|
+
};
|
|
502
|
+
const getFiltered = () => options.filter((item) => filter(item, query));
|
|
503
|
+
const clearRender = () => {
|
|
504
|
+
if (lastRenderHeight > 0) {
|
|
505
|
+
process.stdout.write(`\x1B[${lastRenderHeight}A`);
|
|
506
|
+
for (let i = 0; i < lastRenderHeight; i++) {
|
|
507
|
+
process.stdout.write("\x1B[2K\x1B[1B");
|
|
508
|
+
}
|
|
509
|
+
process.stdout.write(`\x1B[${lastRenderHeight}A`);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
const render = (state = "active") => {
|
|
513
|
+
clearRender();
|
|
514
|
+
const lines = [];
|
|
515
|
+
const filtered = getFiltered();
|
|
516
|
+
const icon = state === "active" ? c.cyan(S.stepActive) : state === "cancel" ? c.red(S.stepError) : c.green(S.stepComplete);
|
|
517
|
+
lines.push(`${icon} ${state === "active" ? title : c.dim(title)}`);
|
|
518
|
+
if (state === "active") {
|
|
519
|
+
const blockCursor = isColorSupported ? `\x1B[7m \x1B[0m` : "_";
|
|
520
|
+
lines.push(`${c.gray(S.bar)} ${c.dim("Search:")} ${query}${blockCursor}`);
|
|
521
|
+
lines.push(`${c.gray(S.bar)} ${c.dim("\u2191\u2193 move, space toggle, a all, n none, enter confirm")}`);
|
|
522
|
+
lines.push(`${c.gray(S.bar)}`);
|
|
523
|
+
const visibleStart = Math.max(
|
|
524
|
+
0,
|
|
525
|
+
Math.min(cursor - Math.floor(maxVisible / 2), filtered.length - maxVisible)
|
|
526
|
+
);
|
|
527
|
+
const visibleEnd = Math.min(filtered.length, visibleStart + maxVisible);
|
|
528
|
+
const visibleItems = filtered.slice(visibleStart, visibleEnd);
|
|
529
|
+
if (filtered.length === 0) {
|
|
530
|
+
lines.push(`${c.gray(S.bar)} ${c.dim("No matches found")}`);
|
|
531
|
+
} else {
|
|
532
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
533
|
+
const item = visibleItems[i];
|
|
534
|
+
const actualIndex = visibleStart + i;
|
|
535
|
+
const isSelected = selected.has(item.value);
|
|
536
|
+
const isCursor = actualIndex === cursor;
|
|
537
|
+
const radio = isSelected ? c.green(S.stepComplete) : c.dim(S.circle);
|
|
538
|
+
const label = isCursor ? c.underline(item.label) : item.label;
|
|
539
|
+
const hint = item.hint ? c.dim(` (${item.hint})`) : "";
|
|
540
|
+
const pointer = isCursor ? c.cyan("\u276F") : " ";
|
|
541
|
+
lines.push(`${c.gray(S.bar)} ${pointer} ${radio} ${label}${hint}`);
|
|
542
|
+
}
|
|
543
|
+
const hiddenBefore = visibleStart;
|
|
544
|
+
const hiddenAfter = filtered.length - visibleEnd;
|
|
545
|
+
if (hiddenBefore > 0 || hiddenAfter > 0) {
|
|
546
|
+
const parts = [];
|
|
547
|
+
if (hiddenBefore > 0) parts.push(`\u2191 ${hiddenBefore} more`);
|
|
548
|
+
if (hiddenAfter > 0) parts.push(`\u2193 ${hiddenAfter} more`);
|
|
549
|
+
lines.push(`${c.gray(S.bar)} ${c.dim(parts.join(" "))}`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
lines.push(`${c.gray(S.bar)}`);
|
|
553
|
+
const selectedLabels = options.filter((o) => selected.has(o.value)).map((o) => o.label);
|
|
554
|
+
if (selectedLabels.length === 0) {
|
|
555
|
+
lines.push(`${c.gray(S.bar)} ${c.dim("Selected: (none)")}`);
|
|
556
|
+
} else {
|
|
557
|
+
const summary = selectedLabels.length <= 3 ? selectedLabels.join(", ") : `${selectedLabels.slice(0, 3).join(", ")} +${selectedLabels.length - 3} more`;
|
|
558
|
+
lines.push(`${c.gray(S.bar)} ${c.green("Selected:")} ${summary}`);
|
|
559
|
+
}
|
|
560
|
+
lines.push(c.gray(S.barEnd));
|
|
561
|
+
} else if (state === "submit") {
|
|
562
|
+
const selectedLabels = options.filter((o) => selected.has(o.value)).map((o) => o.label);
|
|
563
|
+
lines.push(`${c.gray(S.bar)} ${c.dim(selectedLabels.join(", "))}`);
|
|
564
|
+
} else if (state === "cancel") {
|
|
565
|
+
lines.push(`${c.gray(S.bar)} ${c.dim("Cancelled")}`);
|
|
566
|
+
}
|
|
567
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
568
|
+
lastRenderHeight = lines.length;
|
|
569
|
+
};
|
|
570
|
+
const cleanup = () => {
|
|
571
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
572
|
+
if (process.stdin.isTTY) {
|
|
573
|
+
process.stdin.setRawMode(false);
|
|
574
|
+
}
|
|
575
|
+
rl.close();
|
|
576
|
+
};
|
|
577
|
+
const onKeypress = (_str, key) => {
|
|
578
|
+
if (!key) return;
|
|
579
|
+
const filtered = getFiltered();
|
|
580
|
+
if (key.name === "return") {
|
|
581
|
+
render("submit");
|
|
582
|
+
cleanup();
|
|
583
|
+
resolve2([...selected]);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (key.name === "escape" || key.ctrl && key.name === "c") {
|
|
587
|
+
render("cancel");
|
|
588
|
+
cleanup();
|
|
589
|
+
resolve2(null);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (key.name === "up") {
|
|
593
|
+
cursor = Math.max(0, cursor - 1);
|
|
594
|
+
render();
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (key.name === "down") {
|
|
598
|
+
cursor = Math.min(filtered.length - 1, cursor + 1);
|
|
599
|
+
render();
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (key.name === "space") {
|
|
603
|
+
const item = filtered[cursor];
|
|
604
|
+
if (item) {
|
|
605
|
+
if (selected.has(item.value)) selected.delete(item.value);
|
|
606
|
+
else selected.add(item.value);
|
|
607
|
+
}
|
|
608
|
+
render();
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (key.name === "backspace") {
|
|
612
|
+
query = query.slice(0, -1);
|
|
613
|
+
cursor = 0;
|
|
614
|
+
render();
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (key.sequence && !key.ctrl && !key.meta && key.sequence.length === 1) {
|
|
618
|
+
if (key.sequence === "a" && query === "") {
|
|
619
|
+
for (const o of options) selected.add(o.value);
|
|
620
|
+
render();
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
if (key.sequence === "n" && query === "") {
|
|
624
|
+
selected.clear();
|
|
625
|
+
render();
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
query += key.sequence;
|
|
629
|
+
cursor = 0;
|
|
630
|
+
render();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
process.stdin.on("keypress", onKeypress);
|
|
635
|
+
render();
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
var BANNER_LINES = [
|
|
639
|
+
"\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",
|
|
640
|
+
"\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",
|
|
641
|
+
"\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",
|
|
642
|
+
"\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",
|
|
643
|
+
"\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",
|
|
644
|
+
"\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"
|
|
645
|
+
];
|
|
646
|
+
var GRADIENT = [
|
|
647
|
+
[60, 60, 60],
|
|
648
|
+
[90, 90, 90],
|
|
649
|
+
[125, 125, 125],
|
|
650
|
+
[160, 160, 160],
|
|
651
|
+
[200, 200, 200],
|
|
652
|
+
[240, 240, 240]
|
|
653
|
+
];
|
|
654
|
+
function rgb(r, g, b) {
|
|
655
|
+
return isColorSupported ? `\x1B[38;2;${r};${g};${b}m` : "";
|
|
656
|
+
}
|
|
657
|
+
function banner() {
|
|
658
|
+
console.log();
|
|
659
|
+
for (let i = 0; i < BANNER_LINES.length; i++) {
|
|
660
|
+
const [r, g, b] = GRADIENT[i];
|
|
661
|
+
console.log(`${rgb(r, g, b)}${BANNER_LINES[i]}${reset}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// lib/install.ts
|
|
490
666
|
function installerKey(targetId) {
|
|
491
667
|
switch (targetId) {
|
|
492
668
|
case "claude-code":
|
|
@@ -516,6 +692,7 @@ async function installToClaudeCode(plugins, scope, repoPath, source) {
|
|
|
516
692
|
step("Preparing plugins for Claude Code...");
|
|
517
693
|
barEmpty();
|
|
518
694
|
await prepareForClaudeCode(plugins, repoPath, marketplaceName);
|
|
695
|
+
const marketplaceSource = isAnthropicSource(source) ? normalizeGitUrl(source) : repoPath;
|
|
519
696
|
const claudePath = findClaude();
|
|
520
697
|
step("Adding marketplace");
|
|
521
698
|
barLine(c.dim(`Binary: ${claudePath}`));
|
|
@@ -526,7 +703,7 @@ async function installToClaudeCode(plugins, scope, repoPath, source) {
|
|
|
526
703
|
barLine(c.dim(`Warning: could not get claude version`));
|
|
527
704
|
}
|
|
528
705
|
try {
|
|
529
|
-
const result = execSync2(`${claudePath} plugin marketplace add ${
|
|
706
|
+
const result = execSync2(`${claudePath} plugin marketplace add ${marketplaceSource}`, {
|
|
530
707
|
encoding: "utf-8",
|
|
531
708
|
stdio: "pipe"
|
|
532
709
|
});
|
|
@@ -539,7 +716,7 @@ async function installToClaudeCode(plugins, scope, repoPath, source) {
|
|
|
539
716
|
stepDone(`Marketplace ${c.dim("'" + marketplaceName + "'")} already on disk`);
|
|
540
717
|
} else {
|
|
541
718
|
stepError("Failed to add marketplace.");
|
|
542
|
-
barLine(c.dim(`Command: ${claudePath} plugin marketplace add ${
|
|
719
|
+
barLine(c.dim(`Command: ${claudePath} plugin marketplace add ${marketplaceSource}`));
|
|
543
720
|
if (stdout) barLine(c.dim(`stdout: ${stdout}`));
|
|
544
721
|
if (stderr) barLine(c.dim(`stderr: ${stderr}`));
|
|
545
722
|
barLine(c.dim(`exit code: ${err.status}`));
|
|
@@ -699,6 +876,22 @@ function deriveMarketplaceName(source) {
|
|
|
699
876
|
const parts = source.replace(/\/$/, "").split("/");
|
|
700
877
|
return parts[parts.length - 1] ?? "plugins";
|
|
701
878
|
}
|
|
879
|
+
function isAnthropicSource(source) {
|
|
880
|
+
if (source.match(/^anthropics\/[\w.-]+$/)) return true;
|
|
881
|
+
if (source.startsWith("https://github.com/anthropics/")) return true;
|
|
882
|
+
if (source.startsWith("git@github.com:anthropics/")) return true;
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
function normalizeGitUrl(source) {
|
|
886
|
+
if (source.match(/^[\w-]+\/[\w.-]+$/)) {
|
|
887
|
+
return `https://github.com/${source}`;
|
|
888
|
+
}
|
|
889
|
+
const sshMatch = source.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
|
|
890
|
+
if (sshMatch) {
|
|
891
|
+
return `https://${sshMatch[1]}/${sshMatch[2]}`;
|
|
892
|
+
}
|
|
893
|
+
return source;
|
|
894
|
+
}
|
|
702
895
|
|
|
703
896
|
// lib/telemetry.ts
|
|
704
897
|
var TELEMETRY_URL = "https://plugins-telemetry.labs.vercel.dev/t";
|
|
@@ -741,7 +934,8 @@ var { values, positionals } = parseArgs({
|
|
|
741
934
|
help: { type: "boolean", short: "h" },
|
|
742
935
|
target: { type: "string", short: "t" },
|
|
743
936
|
scope: { type: "string", short: "s", default: "user" },
|
|
744
|
-
yes: { type: "boolean", short: "y" }
|
|
937
|
+
yes: { type: "boolean", short: "y" },
|
|
938
|
+
remote: { type: "boolean" }
|
|
745
939
|
},
|
|
746
940
|
allowPositionals: true,
|
|
747
941
|
strict: true
|
|
@@ -778,6 +972,7 @@ ${c.dim("Options:")}
|
|
|
778
972
|
${c.yellow("-t, --target")} <target> Target tool (e.g. claude-code). Default: auto-detect
|
|
779
973
|
${c.yellow("-s, --scope")} <scope> Install scope: user, project, local. Default: user
|
|
780
974
|
${c.yellow("-y, --yes")} Skip confirmation prompts
|
|
975
|
+
${c.yellow("--remote")} Include remote-source plugins in output
|
|
781
976
|
${c.yellow("-h, --help")} Show this help
|
|
782
977
|
`);
|
|
783
978
|
}
|
|
@@ -789,18 +984,33 @@ async function cmdDiscover(source) {
|
|
|
789
984
|
banner();
|
|
790
985
|
header("plugins");
|
|
791
986
|
const repoPath = resolveSource(source);
|
|
792
|
-
const plugins = await discover(repoPath);
|
|
793
|
-
if (plugins.length === 0) {
|
|
987
|
+
const { plugins, remotePlugins, missingPaths } = await discover(repoPath);
|
|
988
|
+
if (plugins.length === 0 && remotePlugins.length === 0) {
|
|
794
989
|
barEmpty();
|
|
795
990
|
step("No plugins found.");
|
|
796
991
|
footer();
|
|
797
992
|
return;
|
|
798
993
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
994
|
+
if (plugins.length > 0) {
|
|
995
|
+
barEmpty();
|
|
996
|
+
step(`Found ${c.bold(String(plugins.length))} local plugin(s)`);
|
|
997
|
+
barEmpty();
|
|
998
|
+
printPluginTable(plugins);
|
|
999
|
+
}
|
|
1000
|
+
if (remotePlugins.length > 0) {
|
|
1001
|
+
if (values.remote) {
|
|
1002
|
+
barEmpty();
|
|
1003
|
+
step(`${c.bold(String(remotePlugins.length))} remote plugin(s) ${c.dim("(hosted in external repos)")}`);
|
|
1004
|
+
barEmpty();
|
|
1005
|
+
printRemotePluginTable(remotePlugins);
|
|
1006
|
+
} else {
|
|
1007
|
+
barEmpty();
|
|
1008
|
+
barLine(
|
|
1009
|
+
c.dim(`${remotePlugins.length} remote plugin(s) not shown. Run:`)
|
|
1010
|
+
);
|
|
1011
|
+
barLine(` ${c.cyan(`npx plugins discover ${source} --remote`)}`);
|
|
1012
|
+
}
|
|
1013
|
+
printMissingPaths(missingPaths);
|
|
804
1014
|
}
|
|
805
1015
|
footer();
|
|
806
1016
|
}
|
|
@@ -834,10 +1044,17 @@ async function cmdInstall(source, opts) {
|
|
|
834
1044
|
banner();
|
|
835
1045
|
header("plugins");
|
|
836
1046
|
const repoPath = resolveSource(source);
|
|
837
|
-
const plugins = await discover(repoPath);
|
|
1047
|
+
const { plugins, remotePlugins, missingPaths } = await discover(repoPath);
|
|
838
1048
|
if (plugins.length === 0) {
|
|
839
1049
|
barEmpty();
|
|
840
1050
|
step("No plugins found.");
|
|
1051
|
+
if (remotePlugins.length > 0) {
|
|
1052
|
+
barLine(
|
|
1053
|
+
c.dim(`${remotePlugins.length} remote plugin(s) not shown. Run:`)
|
|
1054
|
+
);
|
|
1055
|
+
barLine(` ${c.cyan(`npx plugins discover ${source} --remote`)}`);
|
|
1056
|
+
printMissingPaths(missingPaths);
|
|
1057
|
+
}
|
|
841
1058
|
footer();
|
|
842
1059
|
return;
|
|
843
1060
|
}
|
|
@@ -864,31 +1081,68 @@ async function cmdInstall(source, opts) {
|
|
|
864
1081
|
installTargets = detectedTargets;
|
|
865
1082
|
}
|
|
866
1083
|
barEmpty();
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1084
|
+
let selectedPlugins;
|
|
1085
|
+
if (plugins.length === 1 || opts.yes) {
|
|
1086
|
+
step(`Found ${c.bold(String(plugins.length))} plugin(s)`);
|
|
1087
|
+
barEmpty();
|
|
1088
|
+
printPluginTable(plugins);
|
|
1089
|
+
if (remotePlugins.length > 0) {
|
|
1090
|
+
barEmpty();
|
|
1091
|
+
barLine(
|
|
1092
|
+
c.dim(`+ ${remotePlugins.length} remote plugin(s) not included. Run:`)
|
|
1093
|
+
);
|
|
1094
|
+
barLine(` ${c.cyan(`npx plugins discover ${source} --remote`)}`);
|
|
1095
|
+
printMissingPaths(missingPaths);
|
|
1096
|
+
}
|
|
1097
|
+
barEmpty();
|
|
1098
|
+
barLine(`${c.dim("Targets:")} ${installTargets.map((t) => c.cyan(t.name)).join(c.dim(", "))}`);
|
|
1099
|
+
barLine(`${c.dim("Scope:")} ${c.cyan(opts.scope ?? "user")}`);
|
|
1100
|
+
barEmpty();
|
|
1101
|
+
if (!opts.yes) {
|
|
1102
|
+
const response = await readLine(`${c.cyan(S.stepActive)} Install? ${c.dim("[Y/n]")} `);
|
|
1103
|
+
if (response.trim().toLowerCase() === "n") {
|
|
1104
|
+
step("Aborted.");
|
|
1105
|
+
footer();
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
selectedPlugins = plugins;
|
|
1110
|
+
} else {
|
|
1111
|
+
step(`Found ${c.bold(String(plugins.length))} plugin(s)`);
|
|
1112
|
+
barEmpty();
|
|
1113
|
+
const options = plugins.map((p) => {
|
|
1114
|
+
const parts = pluginComponents(p);
|
|
1115
|
+
const hint = parts.length ? parts.join(", ") : void 0;
|
|
1116
|
+
return { label: p.name, value: p.name, hint };
|
|
1117
|
+
});
|
|
1118
|
+
const selected = await multiSelect("Select plugins to install", options);
|
|
1119
|
+
if (!selected || selected.length === 0) {
|
|
878
1120
|
step("Aborted.");
|
|
879
1121
|
footer();
|
|
880
1122
|
return;
|
|
881
1123
|
}
|
|
1124
|
+
selectedPlugins = plugins.filter((p) => selected.includes(p.name));
|
|
1125
|
+
if (remotePlugins.length > 0) {
|
|
1126
|
+
barLine(
|
|
1127
|
+
c.dim(`+ ${remotePlugins.length} remote plugin(s) not included. Run:`)
|
|
1128
|
+
);
|
|
1129
|
+
barLine(` ${c.cyan(`npx plugins discover ${source} --remote`)}`);
|
|
1130
|
+
printMissingPaths(missingPaths);
|
|
1131
|
+
}
|
|
1132
|
+
barEmpty();
|
|
1133
|
+
barLine(`${c.dim("Targets:")} ${installTargets.map((t) => c.cyan(t.name)).join(c.dim(", "))}`);
|
|
1134
|
+
barLine(`${c.dim("Scope:")} ${c.cyan(opts.scope ?? "user")}`);
|
|
1135
|
+
barEmpty();
|
|
882
1136
|
}
|
|
883
1137
|
const scope = opts.scope ?? "user";
|
|
884
1138
|
for (const target of installTargets) {
|
|
885
|
-
await installPlugins(
|
|
1139
|
+
await installPlugins(selectedPlugins, target, scope, repoPath, source);
|
|
886
1140
|
}
|
|
887
1141
|
track({
|
|
888
1142
|
event: "install",
|
|
889
1143
|
source,
|
|
890
|
-
plugins:
|
|
891
|
-
pluginCount: String(
|
|
1144
|
+
plugins: selectedPlugins.map((p) => p.name).join(","),
|
|
1145
|
+
pluginCount: String(selectedPlugins.length),
|
|
892
1146
|
targets: installTargets.map((t) => t.id).join(","),
|
|
893
1147
|
scope
|
|
894
1148
|
});
|
|
@@ -896,19 +1150,50 @@ async function cmdInstall(source, opts) {
|
|
|
896
1150
|
stepDone(c.green("Done.") + " Restart your agent tools to load the plugins.");
|
|
897
1151
|
footer();
|
|
898
1152
|
}
|
|
899
|
-
function
|
|
900
|
-
barLine(`${c.bold(p.name)} ${p.version ? c.dim(`(v${p.version})`) : ""}`);
|
|
901
|
-
if (p.description) barLine(`${c.dim(p.description)}`);
|
|
1153
|
+
function pluginComponents(p) {
|
|
902
1154
|
const parts = [];
|
|
903
|
-
if (p.skills.length) parts.push(`${p.skills.length} skill
|
|
904
|
-
if (p.commands.length) parts.push(`${p.commands.length}
|
|
905
|
-
if (p.agents.length) parts.push(`${p.agents.length} agent
|
|
906
|
-
if (p.rules.length) parts.push(`${p.rules.length} rule
|
|
907
|
-
if (p.hasHooks) parts.push("
|
|
908
|
-
if (p.hasMcp) parts.push("
|
|
909
|
-
if (p.hasLsp) parts.push("
|
|
910
|
-
|
|
911
|
-
|
|
1155
|
+
if (p.skills.length) parts.push(`${p.skills.length} skill`);
|
|
1156
|
+
if (p.commands.length) parts.push(`${p.commands.length} cmd`);
|
|
1157
|
+
if (p.agents.length) parts.push(`${p.agents.length} agent`);
|
|
1158
|
+
if (p.rules.length) parts.push(`${p.rules.length} rule`);
|
|
1159
|
+
if (p.hasHooks) parts.push("hook");
|
|
1160
|
+
if (p.hasMcp) parts.push("mcp");
|
|
1161
|
+
if (p.hasLsp) parts.push("lsp");
|
|
1162
|
+
return parts;
|
|
1163
|
+
}
|
|
1164
|
+
function printPluginTable(plugins) {
|
|
1165
|
+
const nameWidth = Math.max(...plugins.map((p) => p.name.length), 4);
|
|
1166
|
+
const compStrs = plugins.map((p) => pluginComponents(p).join(", "));
|
|
1167
|
+
const compWidth = Math.max(...compStrs.map((s) => s.length), 0);
|
|
1168
|
+
const termWidth = process.stdout.columns || 80;
|
|
1169
|
+
const descWidth = Math.max(termWidth - 3 - nameWidth - 2 - compWidth - 2, 20);
|
|
1170
|
+
for (let i = 0; i < plugins.length; i++) {
|
|
1171
|
+
const p = plugins[i];
|
|
1172
|
+
const name = p.name.padEnd(nameWidth);
|
|
1173
|
+
const comp = compStrs[i];
|
|
1174
|
+
const desc = truncate(p.description ?? "", descWidth);
|
|
1175
|
+
barLine(`${c.bold(name)} ${comp ? c.cyan(comp.padEnd(compWidth)) : " ".repeat(compWidth)} ${c.dim(desc)}`);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
function printRemotePluginTable(plugins) {
|
|
1179
|
+
const nameWidth = Math.max(...plugins.map((p) => p.name.length), 4);
|
|
1180
|
+
const termWidth = process.stdout.columns || 80;
|
|
1181
|
+
const descWidth = Math.max(termWidth - 3 - nameWidth - 2, 20);
|
|
1182
|
+
for (const p of plugins) {
|
|
1183
|
+
const name = p.name.padEnd(nameWidth);
|
|
1184
|
+
const desc = truncate(p.description ?? "", descWidth);
|
|
1185
|
+
barLine(`${c.bold(name)} ${c.dim(desc)}`);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
function printMissingPaths(paths) {
|
|
1189
|
+
if (paths.length === 0) return;
|
|
1190
|
+
for (const p of paths) {
|
|
1191
|
+
barLine(c.dim(` source not found: ${p}`));
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function truncate(s, max) {
|
|
1195
|
+
if (s.length <= max) return s;
|
|
1196
|
+
return s.slice(0, max - 1) + "\u2026";
|
|
912
1197
|
}
|
|
913
1198
|
function sshToHttps(sshUrl) {
|
|
914
1199
|
const m = sshUrl.match(/^git@([^:]+):(.+)$/);
|