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.
Files changed (3) hide show
  1. package/README.md +4 -3
  2. package/dist/index.js +450 -165
  3. 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
- barLine(`${c.yellow(S.warning)} ${c.yellow(`Plugin source not found: ${entry.source}`)}`);
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 ${repoPath}`, {
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 ${repoPath}`));
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
- barEmpty();
800
- step(`Found ${c.bold(String(plugins.length))} plugin(s) in ${c.dim(source)}`);
801
- barEmpty();
802
- for (const p of plugins) {
803
- printPlugin(p);
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
- step(`Found ${c.bold(String(plugins.length))} plugin(s)`);
868
- barEmpty();
869
- for (const p of plugins) {
870
- printPlugin(p);
871
- }
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();
875
- if (!opts.yes) {
876
- const response = await readLine(`${c.cyan(S.stepActive)} Install? ${c.dim("[Y/n]")} `);
877
- if (response.trim().toLowerCase() === "n") {
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(plugins, target, scope, repoPath, source);
1139
+ await installPlugins(selectedPlugins, target, scope, repoPath, source);
886
1140
  }
887
1141
  track({
888
1142
  event: "install",
889
1143
  source,
890
- plugins: plugins.map((p) => p.name).join(","),
891
- pluginCount: String(plugins.length),
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 printPlugin(p) {
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(s)`);
904
- if (p.commands.length) parts.push(`${p.commands.length} command(s)`);
905
- if (p.agents.length) parts.push(`${p.agents.length} agent(s)`);
906
- if (p.rules.length) parts.push(`${p.rules.length} rule(s)`);
907
- if (p.hasHooks) parts.push("hooks");
908
- if (p.hasMcp) parts.push("MCP servers");
909
- if (p.hasLsp) parts.push("LSP servers");
910
- if (parts.length) barLine(`${c.dim("Components:")} ${parts.join(c.dim(", "))}`);
911
- barEmpty();
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@([^:]+):(.+)$/);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugins",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Install open-plugin format plugins into agent tools",
5
5
  "type": "module",
6
6
  "bin": {