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.
- package/dist/index.js +225 -9
- 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
|
|
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 '
|
|
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(
|
|
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;
|