plugins 1.2.4 → 1.2.7

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 +337 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { parseArgs } from "util";
5
5
  import { resolve, join as join4 } from "path";
6
6
  import { execSync as execSync3 } from "child_process";
7
7
  import { existsSync as existsSync3, rmSync, mkdirSync } from "fs";
8
+ import { homedir as homedir3 } from "os";
8
9
  import { createInterface } from "readline";
9
10
 
10
11
  // lib/discover.ts
@@ -16,7 +17,8 @@ async function discover(repoPath) {
16
17
  join(repoPath, "marketplace.json"),
17
18
  join(repoPath, ".plugin", "marketplace.json"),
18
19
  join(repoPath, ".claude-plugin", "marketplace.json"),
19
- join(repoPath, ".cursor-plugin", "marketplace.json")
20
+ join(repoPath, ".cursor-plugin", "marketplace.json"),
21
+ join(repoPath, ".codex-plugin", "marketplace.json")
20
22
  ];
21
23
  for (const mp of marketplacePaths) {
22
24
  if (await fileExists(mp)) {
@@ -86,7 +88,7 @@ async function discoverFromMarketplace(repoPath, marketplace) {
86
88
  skills = await discoverSkills(sourcePath);
87
89
  }
88
90
  let manifest = null;
89
- for (const manifestDir of [".plugin", ".claude-plugin", ".cursor-plugin"]) {
91
+ for (const manifestDir of [".plugin", ".claude-plugin", ".cursor-plugin", ".codex-plugin"]) {
90
92
  const manifestPath = join(sourcePath, manifestDir, "plugin.json");
91
93
  if (await fileExists(manifestPath)) {
92
94
  manifest = await readJson(manifestPath);
@@ -127,6 +129,7 @@ async function isPluginDir(dirPath) {
127
129
  join(dirPath, ".plugin", "plugin.json"),
128
130
  join(dirPath, ".claude-plugin", "plugin.json"),
129
131
  join(dirPath, ".cursor-plugin", "plugin.json"),
132
+ join(dirPath, ".codex-plugin", "plugin.json"),
130
133
  join(dirPath, "skills"),
131
134
  join(dirPath, "commands"),
132
135
  join(dirPath, "agents"),
@@ -139,7 +142,7 @@ async function isPluginDir(dirPath) {
139
142
  }
140
143
  async function inspectPlugin(pluginPath) {
141
144
  let manifest = null;
142
- for (const manifestDir of [".plugin", ".claude-plugin", ".cursor-plugin"]) {
145
+ for (const manifestDir of [".plugin", ".claude-plugin", ".cursor-plugin", ".codex-plugin"]) {
143
146
  const manifestPath = join(pluginPath, manifestDir, "plugin.json");
144
147
  if (await fileExists(manifestPath)) {
145
148
  manifest = await readJson(manifestPath);
@@ -331,6 +334,12 @@ var TARGET_DEFS = [
331
334
  name: "Cursor",
332
335
  description: "AI-powered code editor",
333
336
  configPath: join2(HOME, ".cursor")
337
+ },
338
+ {
339
+ id: "codex",
340
+ name: "Codex",
341
+ description: "OpenAI's coding agent",
342
+ configPath: join2(HOME, ".codex")
334
343
  }
335
344
  // Future targets can be added here:
336
345
  // {
@@ -353,7 +362,9 @@ function detectTarget(def) {
353
362
  case "claude-code":
354
363
  return detectBinary("claude");
355
364
  case "cursor":
356
- return detectBinary("cursor") && detectBinary("claude");
365
+ return detectBinary("cursor");
366
+ case "codex":
367
+ return detectBinary("codex");
357
368
  default:
358
369
  return false;
359
370
  }
@@ -441,6 +452,13 @@ function barLine(content = "") {
441
452
  function barEmpty() {
442
453
  console.log(`${c.gray(S.bar)}`);
443
454
  }
455
+ var _debug = false;
456
+ function setDebug(enabled) {
457
+ _debug = enabled;
458
+ }
459
+ function barDebug(content = "") {
460
+ if (_debug) barLine(content);
461
+ }
444
462
  function step(content) {
445
463
  console.log(`${c.gray(S.step)} ${content}`);
446
464
  }
@@ -663,29 +681,21 @@ function banner() {
663
681
  }
664
682
 
665
683
  // lib/install.ts
666
- function installerKey(targetId) {
667
- switch (targetId) {
668
- case "claude-code":
669
- case "cursor":
670
- return "claude-code";
671
- default:
672
- return targetId;
673
- }
674
- }
675
- var completedInstallers = /* @__PURE__ */ new Set();
684
+ var cachePopulated = false;
676
685
  async function installPlugins(plugins, target, scope, repoPath, source) {
677
- const key = installerKey(target.id);
678
- if (completedInstallers.has(key)) {
679
- return;
680
- }
681
- switch (key) {
686
+ switch (target.id) {
682
687
  case "claude-code":
683
688
  await installToClaudeCode(plugins, scope, repoPath, source);
684
689
  break;
690
+ case "cursor":
691
+ await installToCursor(plugins, scope, repoPath, source);
692
+ break;
693
+ case "codex":
694
+ await installToCodex(plugins, scope, repoPath, source);
695
+ break;
685
696
  default:
686
697
  throw new Error(`Unsupported target: ${target.id}`);
687
698
  }
688
- completedInstallers.add(key);
689
699
  }
690
700
  async function installToClaudeCode(plugins, scope, repoPath, source) {
691
701
  const marketplaceName = plugins[0]?.marketplace ?? deriveMarketplaceName(source);
@@ -695,19 +705,19 @@ async function installToClaudeCode(plugins, scope, repoPath, source) {
695
705
  const marketplaceSource = isAnthropicSource(source) ? normalizeGitUrl(source) : repoPath;
696
706
  const claudePath = findClaude();
697
707
  step("Adding marketplace");
698
- barLine(c.dim(`Binary: ${claudePath}`));
708
+ barDebug(c.dim(`Binary: ${claudePath}`));
699
709
  try {
700
710
  const version = execSync2(`${claudePath} --version`, { encoding: "utf-8", stdio: "pipe" }).trim();
701
- barLine(c.dim(`Version: ${version}`));
711
+ barDebug(c.dim(`Version: ${version}`));
702
712
  } catch {
703
- barLine(c.dim(`Warning: could not get claude version`));
713
+ barDebug(c.dim(`Warning: could not get claude version`));
704
714
  }
705
715
  try {
706
716
  const result = execSync2(`${claudePath} plugin marketplace add ${marketplaceSource}`, {
707
717
  encoding: "utf-8",
708
718
  stdio: "pipe"
709
719
  });
710
- if (result.trim()) barLine(c.dim(result.trim()));
720
+ if (result.trim()) barDebug(c.dim(result.trim()));
711
721
  stepDone("Marketplace added");
712
722
  } catch (err) {
713
723
  const stderr = err.stderr?.toString().trim() ?? "";
@@ -747,6 +757,285 @@ async function installToClaudeCode(plugins, scope, repoPath, source) {
747
757
  }
748
758
  }
749
759
  }
760
+ cachePopulated = true;
761
+ }
762
+ async function installToCursor(plugins, scope, repoPath, source) {
763
+ if (cachePopulated) return;
764
+ const claudePath = findClaudeOrNull();
765
+ if (claudePath) {
766
+ await installToClaudeCode(plugins, scope, repoPath, source);
767
+ return;
768
+ }
769
+ await installToCursorExtensions(plugins, scope, repoPath, source);
770
+ }
771
+ async function installToPluginCache(plugins, scope, repoPath, source) {
772
+ const marketplaceName = plugins[0]?.marketplace ?? deriveMarketplaceName(source);
773
+ const home = homedir2();
774
+ const pluginsDir = join3(home, ".claude", "plugins");
775
+ const cacheDir = join3(pluginsDir, "cache");
776
+ step("Preparing plugins for Cursor...");
777
+ barEmpty();
778
+ await prepareForClaudeCode(plugins, repoPath, marketplaceName);
779
+ step("Registering marketplace");
780
+ await mkdir(pluginsDir, { recursive: true });
781
+ const knownPath = join3(pluginsDir, "known_marketplaces.json");
782
+ let knownMarketplaces = {};
783
+ if (existsSync2(knownPath)) {
784
+ try {
785
+ knownMarketplaces = JSON.parse(await readFile2(knownPath, "utf-8"));
786
+ } catch {
787
+ }
788
+ }
789
+ if (knownMarketplaces[marketplaceName]) {
790
+ stepDone(`Marketplace ${c.dim("'" + marketplaceName + "'")} already registered`);
791
+ } else {
792
+ knownMarketplaces[marketplaceName] = {
793
+ source: { source: "directory", path: repoPath },
794
+ installLocation: repoPath,
795
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
796
+ };
797
+ await writeFile(knownPath, JSON.stringify(knownMarketplaces, null, 2));
798
+ stepDone("Marketplace registered");
799
+ }
800
+ barEmpty();
801
+ const installedPath = join3(pluginsDir, "installed_plugins.json");
802
+ let installedData = { version: 2, plugins: {} };
803
+ if (existsSync2(installedPath)) {
804
+ try {
805
+ installedData = JSON.parse(await readFile2(installedPath, "utf-8"));
806
+ } catch {
807
+ }
808
+ }
809
+ let gitSha;
810
+ try {
811
+ gitSha = execSync2("git rev-parse HEAD", { cwd: repoPath, encoding: "utf-8", stdio: "pipe" }).trim();
812
+ } catch {
813
+ }
814
+ for (const plugin of plugins) {
815
+ const pluginRef = `${plugin.name}@${marketplaceName}`;
816
+ const version = plugin.version ?? "0.0.0";
817
+ step(`Installing ${c.bold(pluginRef)}...`);
818
+ const cacheDest = join3(cacheDir, marketplaceName, plugin.name, version);
819
+ await mkdir(cacheDest, { recursive: true });
820
+ await cp(plugin.path, cacheDest, { recursive: true });
821
+ barDebug(c.dim(`Cached to ${cacheDest}`));
822
+ const pluginKey = `${plugin.name}@${marketplaceName}`;
823
+ const now = (/* @__PURE__ */ new Date()).toISOString();
824
+ const entry = {
825
+ scope,
826
+ installPath: cacheDest,
827
+ version,
828
+ installedAt: now,
829
+ lastUpdated: now
830
+ };
831
+ if (gitSha) entry.gitCommitSha = gitSha;
832
+ installedData.plugins[pluginKey] = [entry];
833
+ stepDone(`Installed ${c.cyan(pluginRef)}`);
834
+ }
835
+ await writeFile(installedPath, JSON.stringify(installedData, null, 2));
836
+ barDebug(c.dim("Updated installed_plugins.json"));
837
+ cachePopulated = true;
838
+ }
839
+ async function installToCursorExtensions(plugins, scope, repoPath, source) {
840
+ if (process.platform !== "win32") {
841
+ await installToPluginCache(plugins, scope, repoPath, source);
842
+ return;
843
+ }
844
+ const marketplaceName = plugins[0]?.marketplace ?? deriveMarketplaceName(source);
845
+ const home = homedir2();
846
+ const extensionsDir = join3(home, ".cursor", "extensions");
847
+ step("Preparing plugins for Cursor...");
848
+ barEmpty();
849
+ await prepareForClaudeCode(plugins, repoPath, marketplaceName);
850
+ await mkdir(extensionsDir, { recursive: true });
851
+ const extensionsJsonPath = join3(extensionsDir, "extensions.json");
852
+ let extensions = [];
853
+ if (existsSync2(extensionsJsonPath)) {
854
+ try {
855
+ const parsed = JSON.parse(await readFile2(extensionsJsonPath, "utf-8"));
856
+ if (Array.isArray(parsed)) extensions = parsed;
857
+ } catch {
858
+ }
859
+ }
860
+ let gitSha;
861
+ try {
862
+ gitSha = execSync2("git rev-parse HEAD", { cwd: repoPath, encoding: "utf-8", stdio: "pipe" }).trim();
863
+ } catch {
864
+ }
865
+ for (const plugin of plugins) {
866
+ const pluginRef = `${plugin.name}@${marketplaceName}`;
867
+ const version = plugin.version ?? "0.0.0";
868
+ const folderName = `${marketplaceName}.${plugin.name}-${version}`;
869
+ const destDir = join3(extensionsDir, folderName);
870
+ step(`Installing ${c.bold(pluginRef)}...`);
871
+ await mkdir(destDir, { recursive: true });
872
+ await cp(plugin.path, destDir, { recursive: true });
873
+ barDebug(c.dim(`Copied to ${destDir}`));
874
+ const identifier = `${marketplaceName}.${plugin.name}`;
875
+ extensions = extensions.filter(
876
+ (e) => e?.identifier?.id !== identifier
877
+ );
878
+ const uriPath = "/" + destDir.replace(/\\/g, "/");
879
+ extensions.push({
880
+ identifier: { id: identifier },
881
+ version,
882
+ location: { $mid: 1, path: uriPath, scheme: "file" },
883
+ relativeLocation: folderName,
884
+ metadata: {
885
+ installedTimestamp: Date.now(),
886
+ ...gitSha ? { gitCommitSha: gitSha } : {}
887
+ }
888
+ });
889
+ stepDone(`Installed ${c.cyan(pluginRef)}`);
890
+ }
891
+ await writeFile(extensionsJsonPath, JSON.stringify(extensions, null, 2));
892
+ barDebug(c.dim("Updated extensions.json"));
893
+ cachePopulated = true;
894
+ }
895
+ async function installToCodex(plugins, scope, repoPath, source) {
896
+ const marketplaceName = plugins[0]?.marketplace ?? deriveMarketplaceName(source);
897
+ const home = homedir2();
898
+ const cacheDir = join3(home, ".codex", "plugins", "cache");
899
+ const configPath = join3(home, ".codex", "config.toml");
900
+ const marketplaceDir = join3(home, ".agents", "plugins");
901
+ const marketplacePath = join3(marketplaceDir, "marketplace.json");
902
+ const marketplaceRoot = home;
903
+ step("Preparing plugins for Codex...");
904
+ barEmpty();
905
+ for (const plugin of plugins) {
906
+ await preparePluginDirForVendor(plugin, ".codex-plugin", "CODEX_PLUGIN_ROOT");
907
+ await enrichForCodex(plugin);
908
+ }
909
+ let gitSha;
910
+ try {
911
+ gitSha = execSync2("git rev-parse HEAD", { cwd: repoPath, encoding: "utf-8", stdio: "pipe" }).trim();
912
+ } catch {
913
+ }
914
+ const versionKey = gitSha ?? "local";
915
+ const pluginPaths = {};
916
+ for (const plugin of plugins) {
917
+ const pluginRef = `${plugin.name}@${marketplaceName}`;
918
+ step(`Installing ${c.bold(pluginRef)}...`);
919
+ const cacheDest = join3(cacheDir, marketplaceName, plugin.name, versionKey);
920
+ await mkdir(cacheDest, { recursive: true });
921
+ await cp(plugin.path, cacheDest, { recursive: true });
922
+ pluginPaths[plugin.name] = cacheDest;
923
+ barDebug(c.dim(`Cached to ${cacheDest}`));
924
+ stepDone(`Installed ${c.cyan(pluginRef)}`);
925
+ }
926
+ step("Updating marketplace...");
927
+ await mkdir(marketplaceDir, { recursive: true });
928
+ let marketplace = {
929
+ name: "plugins-cli",
930
+ interface: { displayName: "Plugins CLI" },
931
+ plugins: []
932
+ };
933
+ if (existsSync2(marketplacePath)) {
934
+ try {
935
+ const existing = JSON.parse(await readFile2(marketplacePath, "utf-8"));
936
+ if (existing && typeof existing === "object" && Array.isArray(existing.plugins)) {
937
+ marketplace = existing;
938
+ }
939
+ } catch {
940
+ }
941
+ }
942
+ for (const plugin of plugins) {
943
+ const cacheDest = pluginPaths[plugin.name];
944
+ const relPath = relative(marketplaceRoot, cacheDest);
945
+ marketplace.plugins = marketplace.plugins.filter(
946
+ (e) => e.name !== plugin.name
947
+ );
948
+ marketplace.plugins.push({
949
+ name: plugin.name,
950
+ source: {
951
+ source: "local",
952
+ path: `./${relPath}`
953
+ },
954
+ policy: {
955
+ installation: "AVAILABLE",
956
+ authentication: "ON_INSTALL"
957
+ },
958
+ category: "Coding"
959
+ });
960
+ }
961
+ await writeFile(marketplacePath, JSON.stringify(marketplace, null, 2));
962
+ stepDone("Marketplace updated");
963
+ step("Updating config.toml...");
964
+ await mkdir(join3(home, ".codex"), { recursive: true });
965
+ let configContent = "";
966
+ if (existsSync2(configPath)) {
967
+ configContent = await readFile2(configPath, "utf-8");
968
+ }
969
+ let configChanged = false;
970
+ for (const plugin of plugins) {
971
+ const pluginKey = `${plugin.name}@plugins-cli`;
972
+ const tomlSection = `[plugins."${pluginKey}"]`;
973
+ if (configContent.includes(tomlSection)) {
974
+ barDebug(c.dim(`${pluginKey} already in config.toml`));
975
+ continue;
976
+ }
977
+ const entry = `
978
+ ${tomlSection}
979
+ enabled = true
980
+ `;
981
+ configContent += entry;
982
+ configChanged = true;
983
+ barDebug(c.dim(`Added ${pluginKey} to config.toml`));
984
+ }
985
+ if (configChanged) {
986
+ await writeFile(configPath, configContent);
987
+ }
988
+ stepDone("Config updated");
989
+ }
990
+ async function enrichForCodex(plugin) {
991
+ const codexManifestPath = join3(plugin.path, ".codex-plugin", "plugin.json");
992
+ if (!existsSync2(codexManifestPath)) return;
993
+ let manifest;
994
+ try {
995
+ manifest = JSON.parse(await readFile2(codexManifestPath, "utf-8"));
996
+ } catch {
997
+ return;
998
+ }
999
+ if (manifest.interface) return;
1000
+ let changed = false;
1001
+ if (!manifest.skills && existsSync2(join3(plugin.path, "skills"))) {
1002
+ manifest.skills = "./skills/";
1003
+ changed = true;
1004
+ }
1005
+ if (!manifest.mcpServers && existsSync2(join3(plugin.path, ".mcp.json"))) {
1006
+ manifest.mcpServers = "./.mcp.json";
1007
+ changed = true;
1008
+ }
1009
+ if (!manifest.apps && existsSync2(join3(plugin.path, ".app.json"))) {
1010
+ manifest.apps = "./.app.json";
1011
+ changed = true;
1012
+ }
1013
+ const name = manifest.name ?? plugin.name;
1014
+ const description = manifest.description ?? plugin.description ?? "";
1015
+ const author = manifest.author;
1016
+ const iface = {
1017
+ displayName: name.charAt(0).toUpperCase() + name.slice(1),
1018
+ shortDescription: description,
1019
+ developerName: author?.name ?? "Unknown",
1020
+ category: "Coding",
1021
+ capabilities: ["Interactive", "Write"]
1022
+ };
1023
+ if (manifest.homepage) iface.websiteURL = manifest.homepage;
1024
+ else if (manifest.repository) iface.websiteURL = manifest.repository;
1025
+ const assetCandidates = ["assets/app-icon.png", "assets/icon.png", "assets/logo.png", "assets/logo.svg"];
1026
+ for (const candidate of assetCandidates) {
1027
+ if (existsSync2(join3(plugin.path, candidate))) {
1028
+ iface.logo = `./${candidate}`;
1029
+ iface.composerIcon = `./${candidate}`;
1030
+ break;
1031
+ }
1032
+ }
1033
+ manifest.interface = iface;
1034
+ changed = true;
1035
+ if (changed) {
1036
+ await writeFile(codexManifestPath, JSON.stringify(manifest, null, 2));
1037
+ barDebug(c.dim(`${plugin.name}: enriched .codex-plugin/plugin.json for Codex`));
1038
+ }
750
1039
  }
751
1040
  async function prepareForClaudeCode(plugins, repoPath, marketplaceName) {
752
1041
  const claudePluginDir = join3(repoPath, ".claude-plugin");
@@ -773,12 +1062,12 @@ async function prepareForClaudeCode(plugins, repoPath, marketplaceName) {
773
1062
  join3(claudePluginDir, "marketplace.json"),
774
1063
  JSON.stringify(marketplaceJson, null, 2)
775
1064
  );
776
- barLine(c.dim("Generated .claude-plugin/marketplace.json"));
1065
+ barDebug(c.dim("Generated .claude-plugin/marketplace.json"));
777
1066
  for (const plugin of plugins) {
778
1067
  await preparePluginDirForVendor(plugin, ".claude-plugin", "CLAUDE_PLUGIN_ROOT");
779
1068
  }
780
1069
  }
781
- function findClaude() {
1070
+ function findClaudeOrNull() {
782
1071
  try {
783
1072
  const path = execSync2("which claude", { encoding: "utf-8", stdio: "pipe" }).trim();
784
1073
  if (path) return path;
@@ -793,7 +1082,10 @@ function findClaude() {
793
1082
  for (const candidate of candidates) {
794
1083
  if (existsSync2(candidate)) return candidate;
795
1084
  }
796
- return "claude";
1085
+ return null;
1086
+ }
1087
+ function findClaude() {
1088
+ return findClaudeOrNull() ?? "claude";
797
1089
  }
798
1090
  async function preparePluginDirForVendor(plugin, vendorDir, envVar) {
799
1091
  const pluginPath = plugin.path;
@@ -803,7 +1095,7 @@ async function preparePluginDirForVendor(plugin, vendorDir, envVar) {
803
1095
  const hasVendorPlugin = existsSync2(join3(vendorPluginDir, "plugin.json"));
804
1096
  if (hasOpenPlugin && !hasVendorPlugin) {
805
1097
  await cp(openPluginDir, vendorPluginDir, { recursive: true });
806
- barLine(c.dim(`${plugin.name}: translated .plugin/ \u2192 ${vendorDir}/`));
1098
+ barDebug(c.dim(`${plugin.name}: translated .plugin/ \u2192 ${vendorDir}/`));
807
1099
  }
808
1100
  if (!hasOpenPlugin && !hasVendorPlugin) {
809
1101
  await mkdir(vendorPluginDir, { recursive: true });
@@ -819,14 +1111,15 @@ async function preparePluginDirForVendor(plugin, vendorDir, envVar) {
819
1111
  2
820
1112
  )
821
1113
  );
822
- barLine(c.dim(`${plugin.name}: generated ${vendorDir}/plugin.json`));
1114
+ barDebug(c.dim(`${plugin.name}: generated ${vendorDir}/plugin.json`));
823
1115
  }
824
1116
  await translateEnvVars(pluginPath, plugin.name, envVar);
825
1117
  }
826
1118
  var KNOWN_PLUGIN_ROOT_VARS = [
827
1119
  "PLUGIN_ROOT",
828
1120
  "CLAUDE_PLUGIN_ROOT",
829
- "CURSOR_PLUGIN_ROOT"
1121
+ "CURSOR_PLUGIN_ROOT",
1122
+ "CODEX_PLUGIN_ROOT"
830
1123
  ];
831
1124
  async function translateEnvVars(pluginPath, pluginName, envVar) {
832
1125
  const configFiles = [
@@ -848,7 +1141,7 @@ async function translateEnvVars(pluginPath, pluginName, envVar) {
848
1141
  }
849
1142
  if (changed) {
850
1143
  await writeFile(filePath, content);
851
- barLine(
1144
+ barDebug(
852
1145
  c.dim(`${pluginName}: translated plugin root \u2192 \${${envVar}} in ${filePath.split("/").pop()}`)
853
1146
  );
854
1147
  }
@@ -935,12 +1228,14 @@ var { values, positionals } = parseArgs({
935
1228
  target: { type: "string", short: "t" },
936
1229
  scope: { type: "string", short: "s", default: "user" },
937
1230
  yes: { type: "boolean", short: "y" },
938
- remote: { type: "boolean" }
1231
+ remote: { type: "boolean" },
1232
+ debug: { type: "boolean" }
939
1233
  },
940
1234
  allowPositionals: true,
941
1235
  strict: true
942
1236
  });
943
1237
  var [command, ...rest] = positionals;
1238
+ if (values.debug) setDebug(true);
944
1239
  if (values.help || !command) {
945
1240
  printUsage();
946
1241
  process.exit(0);
@@ -973,6 +1268,7 @@ ${c.dim("Options:")}
973
1268
  ${c.yellow("-s, --scope")} <scope> Install scope: user, project, local. Default: user
974
1269
  ${c.yellow("-y, --yes")} Skip confirmation prompts
975
1270
  ${c.yellow("--remote")} Include remote-source plugins in output
1271
+ ${c.yellow("--debug")} Show verbose installation output
976
1272
  ${c.yellow("-h, --help")} Show this help
977
1273
  `);
978
1274
  }
@@ -1070,11 +1366,16 @@ async function cmdInstall(source, opts) {
1070
1366
  footer();
1071
1367
  process.exit(1);
1072
1368
  }
1369
+ if (!found.detected) {
1370
+ barEmpty();
1371
+ barLine(c.yellow(`Warning: ${found.name} was not detected on this system.`));
1372
+ }
1073
1373
  installTargets = [found];
1074
1374
  } else if (detectedTargets.length === 0) {
1075
1375
  barEmpty();
1076
1376
  stepError("No supported targets detected.");
1077
- barLine(c.dim("Use --target to specify one."));
1377
+ barLine(c.dim("Neither 'claude', 'cursor', nor 'codex' binaries were found on PATH."));
1378
+ barLine(c.dim("Use --target to specify one manually."));
1078
1379
  footer();
1079
1380
  process.exit(1);
1080
1381
  } else {
@@ -1203,7 +1504,7 @@ function sshToHttps(sshUrl) {
1203
1504
  function resolveSource(source) {
1204
1505
  if (source.startsWith("https://") || source.startsWith("git@") || source.match(/^[\w-]+\/[\w.-]+$/)) {
1205
1506
  const url = source.match(/^[\w-]+\/[\w.-]+$/) ? `https://github.com/${source}` : source;
1206
- const cacheDir = join4(process.env.HOME ?? "~", ".cache", "plugins");
1507
+ const cacheDir = join4(homedir3(), ".cache", "plugins");
1207
1508
  mkdirSync(cacheDir, { recursive: true });
1208
1509
  const slug = url.replace(/^https?:\/\//, "").replace(/^git@/, "").replace(/\.git$/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
1209
1510
  const tmpDir = join4(cacheDir, slug);
@@ -1213,7 +1514,7 @@ function resolveSource(source) {
1213
1514
  step(`Source: ${c.dim(url)}`);
1214
1515
  barEmpty();
1215
1516
  try {
1216
- execSync3(`git clone --depth 1 -q ${url} ${tmpDir}`, { stdio: "pipe" });
1517
+ execSync3(`git clone --depth 1 -q "${url}" "${tmpDir}"`, { stdio: "pipe" });
1217
1518
  } catch (err) {
1218
1519
  const stderr = err.stderr?.toString() ?? "";
1219
1520
  if (url.startsWith("git@") && stderr.includes("Permission denied")) {
@@ -1223,7 +1524,7 @@ function resolveSource(source) {
1223
1524
  step(`Source: ${c.dim(httpsUrl)}`);
1224
1525
  barEmpty();
1225
1526
  try {
1226
- execSync3(`git clone --depth 1 -q ${httpsUrl} ${tmpDir}`, { stdio: "inherit" });
1527
+ execSync3(`git clone --depth 1 -q "${httpsUrl}" "${tmpDir}"`, { stdio: "inherit" });
1227
1528
  stepDone("Repository cloned");
1228
1529
  barEmpty();
1229
1530
  return tmpDir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugins",
3
- "version": "1.2.4",
3
+ "version": "1.2.7",
4
4
  "description": "Install open-plugin format plugins into agent tools",
5
5
  "type": "module",
6
6
  "bin": {