plugins 1.2.5 → 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 +225 -9
  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
  // {
@@ -354,6 +363,8 @@ function detectTarget(def) {
354
363
  return detectBinary("claude");
355
364
  case "cursor":
356
365
  return detectBinary("cursor");
366
+ case "codex":
367
+ return detectBinary("codex");
357
368
  default:
358
369
  return false;
359
370
  }
@@ -679,6 +690,9 @@ async function installPlugins(plugins, target, scope, repoPath, source) {
679
690
  case "cursor":
680
691
  await installToCursor(plugins, scope, repoPath, source);
681
692
  break;
693
+ case "codex":
694
+ await installToCodex(plugins, scope, repoPath, source);
695
+ break;
682
696
  default:
683
697
  throw new Error(`Unsupported target: ${target.id}`);
684
698
  }
@@ -752,7 +766,7 @@ async function installToCursor(plugins, scope, repoPath, source) {
752
766
  await installToClaudeCode(plugins, scope, repoPath, source);
753
767
  return;
754
768
  }
755
- await installToPluginCache(plugins, scope, repoPath, source);
769
+ await installToCursorExtensions(plugins, scope, repoPath, source);
756
770
  }
757
771
  async function installToPluginCache(plugins, scope, repoPath, source) {
758
772
  const marketplaceName = plugins[0]?.marketplace ?? deriveMarketplaceName(source);
@@ -822,6 +836,207 @@ async function installToPluginCache(plugins, scope, repoPath, source) {
822
836
  barDebug(c.dim("Updated installed_plugins.json"));
823
837
  cachePopulated = true;
824
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
+ }
1039
+ }
825
1040
  async function prepareForClaudeCode(plugins, repoPath, marketplaceName) {
826
1041
  const claudePluginDir = join3(repoPath, ".claude-plugin");
827
1042
  await mkdir(claudePluginDir, { recursive: true });
@@ -903,7 +1118,8 @@ async function preparePluginDirForVendor(plugin, vendorDir, envVar) {
903
1118
  var KNOWN_PLUGIN_ROOT_VARS = [
904
1119
  "PLUGIN_ROOT",
905
1120
  "CLAUDE_PLUGIN_ROOT",
906
- "CURSOR_PLUGIN_ROOT"
1121
+ "CURSOR_PLUGIN_ROOT",
1122
+ "CODEX_PLUGIN_ROOT"
907
1123
  ];
908
1124
  async function translateEnvVars(pluginPath, pluginName, envVar) {
909
1125
  const configFiles = [
@@ -1158,7 +1374,7 @@ async function cmdInstall(source, opts) {
1158
1374
  } else if (detectedTargets.length === 0) {
1159
1375
  barEmpty();
1160
1376
  stepError("No supported targets detected.");
1161
- barLine(c.dim("Neither 'claude' nor 'cursor' binaries were found on PATH."));
1377
+ barLine(c.dim("Neither 'claude', 'cursor', nor 'codex' binaries were found on PATH."));
1162
1378
  barLine(c.dim("Use --target to specify one manually."));
1163
1379
  footer();
1164
1380
  process.exit(1);
@@ -1288,7 +1504,7 @@ function sshToHttps(sshUrl) {
1288
1504
  function resolveSource(source) {
1289
1505
  if (source.startsWith("https://") || source.startsWith("git@") || source.match(/^[\w-]+\/[\w.-]+$/)) {
1290
1506
  const url = source.match(/^[\w-]+\/[\w.-]+$/) ? `https://github.com/${source}` : source;
1291
- const cacheDir = join4(process.env.HOME ?? "~", ".cache", "plugins");
1507
+ const cacheDir = join4(homedir3(), ".cache", "plugins");
1292
1508
  mkdirSync(cacheDir, { recursive: true });
1293
1509
  const slug = url.replace(/^https?:\/\//, "").replace(/^git@/, "").replace(/\.git$/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
1294
1510
  const tmpDir = join4(cacheDir, slug);
@@ -1298,7 +1514,7 @@ function resolveSource(source) {
1298
1514
  step(`Source: ${c.dim(url)}`);
1299
1515
  barEmpty();
1300
1516
  try {
1301
- execSync3(`git clone --depth 1 -q ${url} ${tmpDir}`, { stdio: "pipe" });
1517
+ execSync3(`git clone --depth 1 -q "${url}" "${tmpDir}"`, { stdio: "pipe" });
1302
1518
  } catch (err) {
1303
1519
  const stderr = err.stderr?.toString() ?? "";
1304
1520
  if (url.startsWith("git@") && stderr.includes("Permission denied")) {
@@ -1308,7 +1524,7 @@ function resolveSource(source) {
1308
1524
  step(`Source: ${c.dim(httpsUrl)}`);
1309
1525
  barEmpty();
1310
1526
  try {
1311
- execSync3(`git clone --depth 1 -q ${httpsUrl} ${tmpDir}`, { stdio: "inherit" });
1527
+ execSync3(`git clone --depth 1 -q "${httpsUrl}" "${tmpDir}"`, { stdio: "inherit" });
1312
1528
  stepDone("Repository cloned");
1313
1529
  barEmpty();
1314
1530
  return tmpDir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugins",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "Install open-plugin format plugins into agent tools",
5
5
  "type": "module",
6
6
  "bin": {