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.
Files changed (2) hide show
  1. package/dist/index.js +322 -71
  2. 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
- console.warn(` Warning: plugin source not found: ${entry.source}`);
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
- console.log("\nPreparing plugins for Claude Code...");
516
+ step("Preparing plugins for Claude Code...");
517
+ barEmpty();
393
518
  await prepareForClaudeCode(plugins, repoPath, marketplaceName);
394
519
  const claudePath = findClaude();
395
- console.log(`Adding marketplace from local path: ${repoPath}`);
396
- console.log(` Using claude binary: ${claudePath}`);
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
- console.log(` Claude Code version: ${version}`);
524
+ barLine(c.dim(`Version: ${version}`));
400
525
  } catch {
401
- console.log(` Warning: could not get claude version`);
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
- console.log(" Marketplace added.");
409
- if (result.trim()) console.log(` Output: ${result.trim()}`);
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
- console.log(" Marketplace already registered.");
539
+ stepDone(`Marketplace ${c.dim("'" + marketplaceName + "'")} already on disk`);
415
540
  } else {
416
- console.error(`Failed to add marketplace.`);
417
- console.error(` Command: ${claudePath} plugin marketplace add ${repoPath}`);
418
- console.error(` stdout: ${stdout}`);
419
- console.error(` stderr: ${stderr}`);
420
- console.error(` exit code: ${err.status}`);
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
- console.log(`
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
- console.log(` Installed.`);
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
- console.log(` Already installed.`);
563
+ stepDone(`${c.cyan(pluginRef)} ${c.dim("already installed")}`);
439
564
  } else {
440
- console.error(` Failed to install ${pluginRef}.`);
441
- console.error(` Command: ${claudePath} plugin install ${pluginRef} --scope ${scope}`);
442
- console.error(` stdout: ${stdout}`);
443
- console.error(` stderr: ${stderr}`);
444
- console.error(` exit code: ${err.status}`);
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
- console.log(" Generated .claude-plugin/marketplace.json");
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
- console.log(` ${plugin.name}: translated .plugin/ -> ${vendorDir}/`);
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
- console.log(` ${plugin.name}: generated ${vendorDir}/plugin.json`);
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
- console.log(
550
- ` ${pluginName}: translated plugin root -> \${${envVar}} in ${filePath.split("/").pop()}`
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 - Install open-plugin format plugins into agent tools
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
- console.error("Error: provide a repo path or URL");
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
- console.log("No plugins found.");
794
+ barEmpty();
795
+ step("No plugins found.");
796
+ footer();
627
797
  return;
628
798
  }
629
- console.log(`Found ${plugins.length} plugin(s) in ${source}:
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
- console.log("No supported targets detected.");
812
+ barEmpty();
813
+ step("No supported targets detected.");
814
+ footer();
639
815
  return;
640
816
  }
641
- console.log("Available install targets:\n");
817
+ barEmpty();
818
+ step("Available install targets");
819
+ barEmpty();
642
820
  for (const t of targets) {
643
- console.log(` ${t.name}`);
644
- console.log(` ${t.description}`);
645
- console.log(` Config: ${t.configPath}`);
646
- console.log(` Status: ${t.detected ? "detected" : "not found"}`);
647
- console.log();
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
- console.error("Error: provide a repo path or URL");
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
- console.log("No plugins found.");
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
- console.error(`Unknown target: ${opts.target}`);
668
- console.error(`Available: ${targets.map((t) => t.id).join(", ")}`);
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
- console.error("No supported targets detected. Use --target to specify one.");
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
- console.log(`Found ${plugins.length} plugin(s):
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
- console.log(`Targets: ${installTargets.map((t) => t.name).join(", ")}`);
684
- console.log(`Scope: ${opts.scope ?? "user"}
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("Install? [y/N] ");
688
- if (response.trim().toLowerCase() !== "y") {
689
- console.log("Aborted.");
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
- console.log("\nDone. Restart your agent tools to load the plugins.");
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
- console.log(` ${p.name} (v${p.version ?? "0.0.0"})`);
701
- if (p.description) console.log(` ${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) console.log(` Components: ${parts.join(", ")}`);
711
- console.log();
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
- console.log(`Cloning ${url}...`);
724
- execSync3(`git clone --depth 1 -q ${url} ${tmpDir}`, { stdio: "inherit" });
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugins",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Install open-plugin format plugins into agent tools",
5
5
  "type": "module",
6
6
  "bin": {