plugins 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +280 -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);
@@ -600,62 +732,79 @@ switch (command) {
600
732
  }
601
733
  function printUsage() {
602
734
  console.log(`
603
- plugins - Install open-plugin format plugins into agent tools
735
+ ${c.bold("plugins")} \u2014 Install open-plugin format plugins into agent tools
604
736
 
605
- Usage:
606
- plugins add <repo-path-or-url> Install plugins from a repo
607
- plugins discover <repo-path-or-url> Discover plugins in a repo
608
- plugins targets List available install targets
609
- plugins <repo-path-or-url> Shorthand for add
737
+ ${c.dim("Usage:")}
738
+ ${c.cyan("plugins add")} <repo-path-or-url> Install plugins from a repo
739
+ ${c.cyan("plugins discover")} <repo-path-or-url> Discover plugins in a repo
740
+ ${c.cyan("plugins targets")} List available install targets
741
+ ${c.cyan("plugins")} <repo-path-or-url> Shorthand for add
610
742
 
611
- Options:
612
- -t, --target <target> Target tool (e.g. claude-code). Default: auto-detect
613
- -s, --scope <scope> Install scope: user, project, local. Default: user
614
- -y, --yes Skip confirmation prompts
615
- -h, --help Show this help
743
+ ${c.dim("Options:")}
744
+ ${c.yellow("-t, --target")} <target> Target tool (e.g. claude-code). Default: auto-detect
745
+ ${c.yellow("-s, --scope")} <scope> Install scope: user, project, local. Default: user
746
+ ${c.yellow("-y, --yes")} Skip confirmation prompts
747
+ ${c.yellow("-h, --help")} Show this help
616
748
  `);
617
749
  }
618
750
  async function cmdDiscover(source) {
619
751
  if (!source) {
620
- console.error("Error: provide a repo path or URL");
752
+ error("Provide a repo path or URL");
621
753
  process.exit(1);
622
754
  }
755
+ banner();
756
+ header("plugins");
623
757
  const repoPath = resolveSource(source);
624
758
  const plugins = await discover(repoPath);
625
759
  if (plugins.length === 0) {
626
- console.log("No plugins found.");
760
+ barEmpty();
761
+ step("No plugins found.");
762
+ footer();
627
763
  return;
628
764
  }
629
- console.log(`Found ${plugins.length} plugin(s) in ${source}:
630
- `);
765
+ barEmpty();
766
+ step(`Found ${c.bold(String(plugins.length))} plugin(s) in ${c.dim(source)}`);
767
+ barEmpty();
631
768
  for (const p of plugins) {
632
769
  printPlugin(p);
633
770
  }
771
+ footer();
634
772
  }
635
773
  async function cmdTargets() {
636
774
  const targets = await getTargets();
775
+ banner();
776
+ header("plugins");
637
777
  if (targets.length === 0) {
638
- console.log("No supported targets detected.");
778
+ barEmpty();
779
+ step("No supported targets detected.");
780
+ footer();
639
781
  return;
640
782
  }
641
- console.log("Available install targets:\n");
783
+ barEmpty();
784
+ step("Available install targets");
785
+ barEmpty();
642
786
  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();
787
+ barLine(` ${c.bold(t.name)}`);
788
+ barLine(` ${c.dim(t.description)}`);
789
+ barLine(` Config: ${c.dim(t.configPath)}`);
790
+ barLine(` Status: ${t.detected ? c.green("detected") : c.dim("not found")}`);
791
+ barEmpty();
648
792
  }
793
+ footer();
649
794
  }
650
795
  async function cmdInstall(source, opts) {
651
796
  if (!source) {
652
- console.error("Error: provide a repo path or URL");
797
+ error("Provide a repo path or URL");
653
798
  process.exit(1);
654
799
  }
800
+ banner();
801
+ header("plugins");
655
802
  const repoPath = resolveSource(source);
656
803
  const plugins = await discover(repoPath);
657
804
  if (plugins.length === 0) {
658
- console.log("No plugins found.");
805
+ barEmpty();
806
+ step("No plugins found.");
807
+ footer();
659
808
  return;
660
809
  }
661
810
  const targets = await getTargets();
@@ -664,29 +813,36 @@ async function cmdInstall(source, opts) {
664
813
  if (opts.target) {
665
814
  const found = targets.find((t) => t.id === opts.target);
666
815
  if (!found) {
667
- console.error(`Unknown target: ${opts.target}`);
668
- console.error(`Available: ${targets.map((t) => t.id).join(", ")}`);
816
+ barEmpty();
817
+ stepError(`Unknown target: ${c.bold(opts.target)}`);
818
+ barLine(c.dim(`Available: ${targets.map((t) => t.id).join(", ")}`));
819
+ footer();
669
820
  process.exit(1);
670
821
  }
671
822
  installTargets = [found];
672
823
  } else if (detectedTargets.length === 0) {
673
- console.error("No supported targets detected. Use --target to specify one.");
824
+ barEmpty();
825
+ stepError("No supported targets detected.");
826
+ barLine(c.dim("Use --target to specify one."));
827
+ footer();
674
828
  process.exit(1);
675
829
  } else {
676
830
  installTargets = detectedTargets;
677
831
  }
678
- console.log(`Found ${plugins.length} plugin(s):
679
- `);
832
+ barEmpty();
833
+ step(`Found ${c.bold(String(plugins.length))} plugin(s)`);
834
+ barEmpty();
680
835
  for (const p of plugins) {
681
836
  printPlugin(p);
682
837
  }
683
- console.log(`Targets: ${installTargets.map((t) => t.name).join(", ")}`);
684
- console.log(`Scope: ${opts.scope ?? "user"}
685
- `);
838
+ barLine(`${c.dim("Targets:")} ${installTargets.map((t) => c.cyan(t.name)).join(c.dim(", "))}`);
839
+ barLine(`${c.dim("Scope:")} ${c.cyan(opts.scope ?? "user")}`);
840
+ barEmpty();
686
841
  if (!opts.yes) {
687
- const response = await readLine("Install? [y/N] ");
688
- if (response.trim().toLowerCase() !== "y") {
689
- console.log("Aborted.");
842
+ const response = await readLine(`${c.cyan(S.stepActive)} Install? ${c.dim("[Y/n]")} `);
843
+ if (response.trim().toLowerCase() === "n") {
844
+ step("Aborted.");
845
+ footer();
690
846
  return;
691
847
  }
692
848
  }
@@ -694,11 +850,13 @@ async function cmdInstall(source, opts) {
694
850
  for (const target of installTargets) {
695
851
  await installPlugins(plugins, target, scope, repoPath, source);
696
852
  }
697
- console.log("\nDone. Restart your agent tools to load the plugins.");
853
+ barEmpty();
854
+ stepDone(c.green("Done.") + " Restart your agent tools to load the plugins.");
855
+ footer();
698
856
  }
699
857
  function printPlugin(p) {
700
- console.log(` ${p.name} (v${p.version ?? "0.0.0"})`);
701
- if (p.description) console.log(` ${p.description}`);
858
+ barLine(`${c.bold(p.name)} ${p.version ? c.dim(`(v${p.version})`) : ""}`);
859
+ if (p.description) barLine(`${c.dim(p.description)}`);
702
860
  const parts = [];
703
861
  if (p.skills.length) parts.push(`${p.skills.length} skill(s)`);
704
862
  if (p.commands.length) parts.push(`${p.commands.length} command(s)`);
@@ -707,21 +865,71 @@ function printPlugin(p) {
707
865
  if (p.hasHooks) parts.push("hooks");
708
866
  if (p.hasMcp) parts.push("MCP servers");
709
867
  if (p.hasLsp) parts.push("LSP servers");
710
- if (parts.length) console.log(` Components: ${parts.join(", ")}`);
711
- console.log();
868
+ if (parts.length) barLine(`${c.dim("Components:")} ${parts.join(c.dim(", "))}`);
869
+ barEmpty();
870
+ }
871
+ function sshToHttps(sshUrl) {
872
+ const m = sshUrl.match(/^git@([^:]+):(.+)$/);
873
+ if (!m) return null;
874
+ return `https://${m[1]}/${m[2]}`;
712
875
  }
713
876
  function resolveSource(source) {
714
877
  if (source.startsWith("https://") || source.startsWith("git@") || source.match(/^[\w-]+\/[\w.-]+$/)) {
715
878
  const url = source.match(/^[\w-]+\/[\w.-]+$/) ? `https://github.com/${source}` : source;
716
879
  const cacheDir = join4(process.env.HOME ?? "~", ".cache", "plugins");
717
880
  mkdirSync(cacheDir, { recursive: true });
718
- const slug = url.replace(/^https?:\/\//, "").replace(/\.git$/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
881
+ const slug = url.replace(/^https?:\/\//, "").replace(/^git@/, "").replace(/\.git$/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
719
882
  const tmpDir = join4(cacheDir, slug);
720
883
  if (existsSync3(join4(tmpDir, ".git", "HEAD"))) {
721
884
  rmSync(tmpDir, { recursive: true, force: true });
722
885
  }
723
- console.log(`Cloning ${url}...`);
724
- execSync3(`git clone --depth 1 -q ${url} ${tmpDir}`, { stdio: "inherit" });
886
+ step(`Source: ${c.dim(url)}`);
887
+ barEmpty();
888
+ try {
889
+ execSync3(`git clone --depth 1 -q ${url} ${tmpDir}`, { stdio: "pipe" });
890
+ } catch (err) {
891
+ const stderr = err.stderr?.toString() ?? "";
892
+ if (url.startsWith("git@") && stderr.includes("Permission denied")) {
893
+ const httpsUrl = sshToHttps(url);
894
+ if (httpsUrl) {
895
+ barLine(c.yellow("SSH authentication failed. Retrying over HTTPS..."));
896
+ step(`Source: ${c.dim(httpsUrl)}`);
897
+ barEmpty();
898
+ try {
899
+ execSync3(`git clone --depth 1 -q ${httpsUrl} ${tmpDir}`, { stdio: "inherit" });
900
+ stepDone("Repository cloned");
901
+ barEmpty();
902
+ return tmpDir;
903
+ } catch {
904
+ }
905
+ }
906
+ }
907
+ if (existsSync3(tmpDir)) {
908
+ rmSync(tmpDir, { recursive: true, force: true });
909
+ }
910
+ if (stderr.includes("Permission denied") || stderr.includes("Could not read from remote repository")) {
911
+ barEmpty();
912
+ stepError(`Could not access ${c.bold(url)}`);
913
+ barEmpty();
914
+ barLine(c.dim("Make sure you have access to this repository. For private repos, try:"));
915
+ barLine(` ${c.dim("HTTPS:")} plugins add https://github.com/owner/repo`);
916
+ barLine(` ${c.dim(" (uses git credential helper / browser auth)")}`);
917
+ barLine(` ${c.dim("SSH:")} plugins add git@github.com:owner/repo.git`);
918
+ barLine(` ${c.dim(" (requires SSH keys)")}`);
919
+ } else if (stderr.includes("not found") || stderr.includes("does not exist") || err.status === 128) {
920
+ barEmpty();
921
+ stepError(`Repository not found: ${c.bold(url)}`);
922
+ barLine(c.dim("Check that the URL is correct and the repository exists."));
923
+ } else {
924
+ barEmpty();
925
+ stepError("git clone failed.");
926
+ if (stderr.trim()) barLine(c.dim(stderr.trim()));
927
+ }
928
+ footer();
929
+ process.exit(1);
930
+ }
931
+ stepDone("Repository cloned");
932
+ barEmpty();
725
933
  return tmpDir;
726
934
  }
727
935
  return resolve(source);
@@ -731,6 +939,7 @@ function readLine(prompt) {
731
939
  return new Promise((resolve2) => {
732
940
  rl.question(prompt, (answer) => {
733
941
  rl.close();
942
+ if (!process.stdin.isTTY) process.stdout.write("\n");
734
943
  resolve2(answer);
735
944
  });
736
945
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugins",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Install open-plugin format plugins into agent tools",
5
5
  "type": "module",
6
6
  "bin": {