universal-plugin 0.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.
- package/bin/universal-plugin.mjs +6 -0
- package/dist/cli.mjs +814 -0
- package/dist/data/vendors.json +46 -0
- package/package.json +60 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command, Option } from "commander";
|
|
3
|
+
import * as fsNode from "node:fs";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import * as childProcess from "node:child_process";
|
|
8
|
+
//#region src/cli-options.ts
|
|
9
|
+
/** Repo root; resolves to cwd when omitted. */
|
|
10
|
+
const ROOT_OPTION = new Option("--root <path>", "Plugin root directory");
|
|
11
|
+
function resolveRoot(root) {
|
|
12
|
+
return root ?? process.cwd();
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/state/state.ts
|
|
16
|
+
const KNOWN_ACTION_TYPES = new Set([
|
|
17
|
+
"install",
|
|
18
|
+
"upgrade",
|
|
19
|
+
"remove"
|
|
20
|
+
]);
|
|
21
|
+
function emptyState() {
|
|
22
|
+
return {
|
|
23
|
+
schemaVersion: 1,
|
|
24
|
+
snapshots: {},
|
|
25
|
+
dismissed: {},
|
|
26
|
+
pendingActions: [],
|
|
27
|
+
uniPluginUpdates: {},
|
|
28
|
+
plugins: {},
|
|
29
|
+
assets: {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function mergeSafeState(raw) {
|
|
33
|
+
return {
|
|
34
|
+
...raw,
|
|
35
|
+
plugins: raw.plugins ?? {},
|
|
36
|
+
assets: raw.assets ?? {},
|
|
37
|
+
pendingActions: (raw.pendingActions ?? []).filter((a) => KNOWN_ACTION_TYPES.has(a.type))
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function writePluginIndex(state, vendorId, pluginName, entry) {
|
|
41
|
+
return {
|
|
42
|
+
...state,
|
|
43
|
+
plugins: {
|
|
44
|
+
...state.plugins,
|
|
45
|
+
[vendorId]: {
|
|
46
|
+
...state.plugins[vendorId] ?? {},
|
|
47
|
+
[pluginName]: entry
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function writeAssetIndex(state, pluginName, entry) {
|
|
53
|
+
return {
|
|
54
|
+
...state,
|
|
55
|
+
assets: {
|
|
56
|
+
...state.assets,
|
|
57
|
+
[pluginName]: entry
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function takeSnapshot(state, vendorId, scope, plugins, takenAt) {
|
|
62
|
+
return {
|
|
63
|
+
...state,
|
|
64
|
+
snapshots: {
|
|
65
|
+
...state.snapshots,
|
|
66
|
+
[vendorId]: {
|
|
67
|
+
...state.snapshots[vendorId] ?? {},
|
|
68
|
+
[scope]: {
|
|
69
|
+
takenAt,
|
|
70
|
+
plugins
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function isDismissed(state, vendorId, scope, plugin, version) {
|
|
77
|
+
const entry = state.dismissed[`${vendorId}/${scope}/${plugin}`];
|
|
78
|
+
if (!entry) return false;
|
|
79
|
+
if (entry.reason === "keep") return true;
|
|
80
|
+
return entry.version === version;
|
|
81
|
+
}
|
|
82
|
+
function addPendingAction(state, action) {
|
|
83
|
+
const key = `${action.type}|${action.plugin}|${action.toVendor}|${action.scope}`;
|
|
84
|
+
if (state.pendingActions.some((a) => `${a.type}|${a.plugin}|${a.toVendor}|${a.scope}` === key)) return state;
|
|
85
|
+
return {
|
|
86
|
+
...state,
|
|
87
|
+
pendingActions: [...state.pendingActions, action]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/asset-store/asset-store.ts
|
|
92
|
+
const ASSET_DIRS = [
|
|
93
|
+
"governances",
|
|
94
|
+
"disciplines",
|
|
95
|
+
"guidelines",
|
|
96
|
+
"templates"
|
|
97
|
+
];
|
|
98
|
+
function globalStorePath() {
|
|
99
|
+
return path.join(os.homedir(), ".agents", ".universal-plugin", "plugins");
|
|
100
|
+
}
|
|
101
|
+
function projectStorePath(root) {
|
|
102
|
+
return path.join(root, ".agents", ".universal-plugin", "plugins");
|
|
103
|
+
}
|
|
104
|
+
function storeEntryPath(storePath, segment) {
|
|
105
|
+
return path.join(storePath, segment);
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/asset-store/fs.ts
|
|
109
|
+
function entryExists(entryPath) {
|
|
110
|
+
return fsNode.existsSync(entryPath);
|
|
111
|
+
}
|
|
112
|
+
function populateEntry(entryPath, pluginRoot) {
|
|
113
|
+
for (const dir of ASSET_DIRS) {
|
|
114
|
+
const src = path.join(pluginRoot, dir);
|
|
115
|
+
const dest = path.join(entryPath, dir);
|
|
116
|
+
if (!fsNode.existsSync(src)) continue;
|
|
117
|
+
fsNode.mkdirSync(dest, { recursive: true });
|
|
118
|
+
for (const file of fsNode.readdirSync(src)) fsNode.copyFileSync(path.join(src, file), path.join(dest, file));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function removeStore(storePath) {
|
|
122
|
+
fsNode.rmSync(storePath, {
|
|
123
|
+
recursive: true,
|
|
124
|
+
force: true
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/asset-store/cli.ts
|
|
129
|
+
function globalStatePath$3() {
|
|
130
|
+
return path.join(os.homedir(), ".agents", "universal-plugin.json");
|
|
131
|
+
}
|
|
132
|
+
function clearStateIndex(statePath) {
|
|
133
|
+
try {
|
|
134
|
+
const cleared = {
|
|
135
|
+
...mergeSafeState(JSON.parse(fsNode.readFileSync(statePath, "utf8"))),
|
|
136
|
+
plugins: {},
|
|
137
|
+
assets: {}
|
|
138
|
+
};
|
|
139
|
+
fsNode.writeFileSync(statePath, JSON.stringify(cleared, null, 2) + "\n", "utf8");
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (err.code !== "ENOENT") throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function cleanCommand() {
|
|
145
|
+
return new Command("clean").description("Remove the asset store").addOption(new Option("--state", "Also clear plugins and assets from state JSON")).addOption(new Option("--scope <scope>", "global or project").choices(["global", "project"]).default("global")).option("--root <path>", "Project root (for --scope project)").action((opts) => {
|
|
146
|
+
const storePath = opts.scope === "project" ? projectStorePath(resolveRoot(opts.root)) : globalStorePath();
|
|
147
|
+
removeStore(storePath);
|
|
148
|
+
process.stdout.write(`Removed ${storePath}\n`);
|
|
149
|
+
if (opts.state) {
|
|
150
|
+
clearStateIndex(globalStatePath$3());
|
|
151
|
+
process.stdout.write("Cleared plugins and assets from state JSON\n");
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/output.ts
|
|
157
|
+
function printJson(data) {
|
|
158
|
+
console.log(JSON.stringify(data, null, 2));
|
|
159
|
+
}
|
|
160
|
+
function printFields(fields) {
|
|
161
|
+
const entries = Object.entries(fields).filter(([, v]) => v != null);
|
|
162
|
+
const width = Math.max(...entries.map(([k]) => k.length));
|
|
163
|
+
for (const [key, val] of entries) console.log(`${key.padEnd(width)} ${val}`);
|
|
164
|
+
}
|
|
165
|
+
function printTable(items, cols) {
|
|
166
|
+
if (items.length === 0) {
|
|
167
|
+
console.log("(none)");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const widths = cols.map((c) => Math.max(c.label.length, ...items.map((i) => c.get(i).length)));
|
|
171
|
+
console.log(cols.map((c, i) => c.label.toUpperCase().padEnd(widths[i])).join(" "));
|
|
172
|
+
console.log(widths.map((w) => "-".repeat(w)).join(" "));
|
|
173
|
+
for (const item of items) console.log(cols.map((c, i) => c.get(item).padEnd(widths[i])).join(" "));
|
|
174
|
+
}
|
|
175
|
+
function getFormat() {
|
|
176
|
+
const argv = process.argv;
|
|
177
|
+
const fmtIdx = argv.indexOf("--format");
|
|
178
|
+
if (fmtIdx !== -1) return argv[fmtIdx + 1];
|
|
179
|
+
if (argv.includes("--json")) return "json";
|
|
180
|
+
}
|
|
181
|
+
function isJsonOutput() {
|
|
182
|
+
return getFormat() === "json";
|
|
183
|
+
}
|
|
184
|
+
function output(data, readable) {
|
|
185
|
+
if (isJsonOutput()) printJson(data);
|
|
186
|
+
else readable();
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/build/build.ts
|
|
190
|
+
const VENDOR_OUTPUT = {
|
|
191
|
+
"claude-code": ".claude-plugin/plugin.json",
|
|
192
|
+
cursor: ".cursor-plugin/plugin.json",
|
|
193
|
+
codex: ".codex-plugin/plugin.json",
|
|
194
|
+
"copilot-cli": "plugin.json"
|
|
195
|
+
};
|
|
196
|
+
const KNOWN_VENDORS = new Set(Object.keys(VENDOR_OUTPUT));
|
|
197
|
+
function readManifest(root) {
|
|
198
|
+
const manifestPath = path.join(root, ".plugin", "plugin.json");
|
|
199
|
+
if (!fsNode.existsSync(manifestPath)) throw new Error(`No .plugin/plugin.json found at ${root}`);
|
|
200
|
+
return JSON.parse(fsNode.readFileSync(manifestPath, "utf8"));
|
|
201
|
+
}
|
|
202
|
+
function validateManifest(manifest) {
|
|
203
|
+
const errors = [];
|
|
204
|
+
if (!manifest.name) errors.push("name is required");
|
|
205
|
+
if (manifest.vendorExtensions?.codex && !manifest.description) errors.push("description is required when targeting codex");
|
|
206
|
+
if (manifest.vendorExtensions?.codex && !manifest.version) errors.push("version is required when targeting codex");
|
|
207
|
+
return errors;
|
|
208
|
+
}
|
|
209
|
+
function buildPlugin(root, opts = {}) {
|
|
210
|
+
const manifest = readManifest(root);
|
|
211
|
+
const errors = validateManifest(manifest);
|
|
212
|
+
if (errors.length > 0) throw new Error(`plugin.json validation failed:\n${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
213
|
+
const warnings = [];
|
|
214
|
+
const vendorExtensions = manifest.vendorExtensions ?? {};
|
|
215
|
+
let vendors = Object.keys(vendorExtensions).filter((v) => {
|
|
216
|
+
if (!KNOWN_VENDORS.has(v)) {
|
|
217
|
+
warnings.push(`Unknown vendor "${v}" in vendorExtensions — skipped`);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
return true;
|
|
221
|
+
});
|
|
222
|
+
if (opts.vendor) {
|
|
223
|
+
if (!vendors.includes(opts.vendor)) throw new Error(`Vendor "${opts.vendor}" not declared in vendorExtensions`);
|
|
224
|
+
vendors = [opts.vendor];
|
|
225
|
+
}
|
|
226
|
+
if (vendors.length === 0) {
|
|
227
|
+
warnings.push("No vendors declared in vendorExtensions — nothing to build");
|
|
228
|
+
return {
|
|
229
|
+
vendors: [],
|
|
230
|
+
written: [],
|
|
231
|
+
warnings
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const written = [];
|
|
235
|
+
const { vendorExtensions: _ext, $schema: _schema, ...canonical } = manifest;
|
|
236
|
+
for (const vendor of vendors) {
|
|
237
|
+
const outputPath = path.join(root, VENDOR_OUTPUT[vendor]);
|
|
238
|
+
const outputDir = path.dirname(outputPath);
|
|
239
|
+
const vendorFields = vendorExtensions[vendor] ?? {};
|
|
240
|
+
const vendorManifest = {
|
|
241
|
+
...canonical,
|
|
242
|
+
...vendorFields
|
|
243
|
+
};
|
|
244
|
+
if (opts.verbose) {
|
|
245
|
+
console.log(`[${vendor}] → ${outputPath}`);
|
|
246
|
+
for (const key of Object.keys(vendorFields)) console.log(` + ${key} (from vendorExtensions)`);
|
|
247
|
+
}
|
|
248
|
+
if (!opts.dryRun) {
|
|
249
|
+
if (opts.clean && fsNode.existsSync(outputPath)) fsNode.unlinkSync(outputPath);
|
|
250
|
+
fsNode.mkdirSync(outputDir, { recursive: true });
|
|
251
|
+
fsNode.writeFileSync(outputPath, `${JSON.stringify(vendorManifest, null, 2)}\n`);
|
|
252
|
+
}
|
|
253
|
+
written.push(outputPath);
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
vendors,
|
|
257
|
+
written,
|
|
258
|
+
warnings
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/build/cli.ts
|
|
263
|
+
function buildCommand() {
|
|
264
|
+
const cmd = new Command("build").description("Generate vendor manifests from .plugin/plugin.json");
|
|
265
|
+
cmd.option("--vendor <id>", "Build only the named vendor").option("--dry-run", "Print what would be written without writing").option("--verbose", "Print field-by-field transformation decisions").option("--clean", "Delete generated manifests before building").option("--format <format>", "Output format: json or text (default: text)").addOption(new Option("--json").hideHelp()).addOption(ROOT_OPTION).action((opts) => {
|
|
266
|
+
try {
|
|
267
|
+
const result = buildPlugin(resolveRoot(opts.root), {
|
|
268
|
+
vendor: opts.vendor,
|
|
269
|
+
dryRun: opts.dryRun,
|
|
270
|
+
verbose: opts.verbose,
|
|
271
|
+
clean: opts.clean
|
|
272
|
+
});
|
|
273
|
+
for (const warning of result.warnings) process.stderr.write(`warn: ${warning}\n`);
|
|
274
|
+
output(result, () => {
|
|
275
|
+
if (result.vendors.length === 0) return;
|
|
276
|
+
printFields({ vendors: result.vendors.join(", ") });
|
|
277
|
+
printTable(result.written.map((p) => ({ path: p })), [{
|
|
278
|
+
label: "output",
|
|
279
|
+
get: (r) => opts.dryRun ? `(dry-run) ${r.path}` : r.path
|
|
280
|
+
}]);
|
|
281
|
+
});
|
|
282
|
+
} catch (err) {
|
|
283
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return cmd;
|
|
288
|
+
}
|
|
289
|
+
//#endregion
|
|
290
|
+
//#region src/governance/fs.ts
|
|
291
|
+
const realGovernanceFs = {
|
|
292
|
+
exists: (p) => fsNode.existsSync(p),
|
|
293
|
+
read: (p) => fsNode.readFileSync(p, "utf8"),
|
|
294
|
+
list: (dir) => {
|
|
295
|
+
if (!fsNode.existsSync(dir)) return [];
|
|
296
|
+
return fsNode.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3));
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/governance/governance.ts
|
|
301
|
+
function getManagedDir() {
|
|
302
|
+
if (process.platform === "darwin") return "/Library/Application Support/UniPlugin/governances";
|
|
303
|
+
if (process.platform === "win32") return path.join(process.env["ProgramData"] ?? "C:\\ProgramData", "UniPlugin", "governances");
|
|
304
|
+
return "/etc/universal-plugin/governances";
|
|
305
|
+
}
|
|
306
|
+
function getUserDir() {
|
|
307
|
+
return path.join(os.homedir(), ".agents", "governances");
|
|
308
|
+
}
|
|
309
|
+
function getProjectDir(root) {
|
|
310
|
+
return path.join(root, "governances");
|
|
311
|
+
}
|
|
312
|
+
function getPackageDir() {
|
|
313
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
314
|
+
return path.join(path.dirname(thisFile), "..", "governances");
|
|
315
|
+
}
|
|
316
|
+
function getScopedPaths(root) {
|
|
317
|
+
return [
|
|
318
|
+
{
|
|
319
|
+
scope: "managed",
|
|
320
|
+
dir: getManagedDir()
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
scope: "project",
|
|
324
|
+
dir: getProjectDir(root)
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
scope: "user",
|
|
328
|
+
dir: getUserDir()
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
scope: "package",
|
|
332
|
+
dir: getPackageDir()
|
|
333
|
+
}
|
|
334
|
+
];
|
|
335
|
+
}
|
|
336
|
+
function showGovernance(name, root, govFs, storeOpts) {
|
|
337
|
+
const slashIdx = name.indexOf("/");
|
|
338
|
+
if (slashIdx !== -1 && storeOpts) {
|
|
339
|
+
const pluginName = name.slice(0, slashIdx);
|
|
340
|
+
const assetName = name.slice(slashIdx + 1);
|
|
341
|
+
for (const scope of [
|
|
342
|
+
"managed",
|
|
343
|
+
"project",
|
|
344
|
+
"user"
|
|
345
|
+
]) {
|
|
346
|
+
let dir;
|
|
347
|
+
if (scope === "managed") dir = getManagedDir();
|
|
348
|
+
else if (scope === "project") dir = getProjectDir(root);
|
|
349
|
+
else dir = getUserDir();
|
|
350
|
+
const filePath = path.join(dir, pluginName, `${assetName}.md`);
|
|
351
|
+
if (govFs.exists(filePath)) return {
|
|
352
|
+
content: govFs.read(filePath),
|
|
353
|
+
scope
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
const entry = storeOpts.state.assets[pluginName];
|
|
357
|
+
if (!entry) return null;
|
|
358
|
+
const segment = path.join(entry.source, `${pluginName}@${entry.version}`);
|
|
359
|
+
const storePath = path.join(storeOpts.globalStorePath, segment, "governances", `${assetName}.md`);
|
|
360
|
+
if (govFs.exists(storePath)) return {
|
|
361
|
+
content: govFs.read(storePath),
|
|
362
|
+
scope: "store"
|
|
363
|
+
};
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
for (const { scope, dir } of getScopedPaths(root)) {
|
|
367
|
+
const filePath = path.join(dir, `${name}.md`);
|
|
368
|
+
if (govFs.exists(filePath)) return {
|
|
369
|
+
content: govFs.read(filePath),
|
|
370
|
+
scope
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
function listGovernances(root, govFs) {
|
|
376
|
+
const seen = /* @__PURE__ */ new Set();
|
|
377
|
+
const entries = [];
|
|
378
|
+
for (const { scope, dir } of getScopedPaths(root)) for (const name of govFs.list(dir)) if (!seen.has(name)) {
|
|
379
|
+
seen.add(name);
|
|
380
|
+
entries.push({
|
|
381
|
+
name,
|
|
382
|
+
scope
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
386
|
+
}
|
|
387
|
+
//#endregion
|
|
388
|
+
//#region src/governance/cli.ts
|
|
389
|
+
function readGlobalState() {
|
|
390
|
+
const p = path.join(os.homedir(), ".agents", "universal-plugin.json");
|
|
391
|
+
try {
|
|
392
|
+
return mergeSafeState(JSON.parse(fsNode.readFileSync(p, "utf8")));
|
|
393
|
+
} catch {
|
|
394
|
+
return emptyState();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function governanceCommand() {
|
|
398
|
+
const cmd = new Command("governance").description("Manage plugin governances").addHelpCommand(false);
|
|
399
|
+
cmd.command("show <name>").description("Show a governance by name").addOption(ROOT_OPTION).addOption(new Option("--json").hideHelp()).action((name, opts) => {
|
|
400
|
+
const result = showGovernance(name, resolveRoot(opts.root), realGovernanceFs, {
|
|
401
|
+
state: readGlobalState(),
|
|
402
|
+
globalStorePath: globalStorePath()
|
|
403
|
+
});
|
|
404
|
+
if (!result) {
|
|
405
|
+
process.stderr.write(`Governance "${name}" not found\n`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
output(result, () => {
|
|
409
|
+
process.stdout.write(result.content);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
cmd.command("list").description("List available governances").addOption(ROOT_OPTION).addOption(new Option("--json").hideHelp()).action((opts) => {
|
|
413
|
+
const entries = listGovernances(resolveRoot(opts.root), realGovernanceFs);
|
|
414
|
+
output(entries, () => {
|
|
415
|
+
printTable(entries, [{
|
|
416
|
+
label: "name",
|
|
417
|
+
get: (e) => e.name
|
|
418
|
+
}, {
|
|
419
|
+
label: "scope",
|
|
420
|
+
get: (e) => e.scope
|
|
421
|
+
}]);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
return cmd;
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/vendor-registry/vendor-registry.ts
|
|
428
|
+
function lookupVendor(registry, vendorId) {
|
|
429
|
+
return registry[vendorId] ?? null;
|
|
430
|
+
}
|
|
431
|
+
function mergeRegistries(base, override) {
|
|
432
|
+
const result = { ...base };
|
|
433
|
+
for (const [id, config] of Object.entries(override)) result[id] = {
|
|
434
|
+
...base[id] ?? {},
|
|
435
|
+
...config
|
|
436
|
+
};
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/vendor-registry/fs.ts
|
|
441
|
+
function bundledRegistryPath() {
|
|
442
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
443
|
+
return path.join(path.dirname(thisFile), "data", "vendors.json");
|
|
444
|
+
}
|
|
445
|
+
function userOverridePath() {
|
|
446
|
+
return path.join(os.homedir(), ".agents", "universal-plugin-vendors.json");
|
|
447
|
+
}
|
|
448
|
+
function loadRegistry() {
|
|
449
|
+
const bundled = JSON.parse(fsNode.readFileSync(bundledRegistryPath(), "utf8"));
|
|
450
|
+
try {
|
|
451
|
+
return mergeRegistries(bundled, JSON.parse(fsNode.readFileSync(userOverridePath(), "utf8")));
|
|
452
|
+
} catch (err) {
|
|
453
|
+
if (err.code === "ENOENT") return bundled;
|
|
454
|
+
throw err;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region src/prepare/fs.ts
|
|
459
|
+
function expandHome(p) {
|
|
460
|
+
return p.startsWith("~") ? path.join(os.homedir(), p.slice(1)) : p;
|
|
461
|
+
}
|
|
462
|
+
function globalStatePath$2() {
|
|
463
|
+
return path.join(os.homedir(), ".agents", "universal-plugin.json");
|
|
464
|
+
}
|
|
465
|
+
function projectStatePath(root) {
|
|
466
|
+
return path.join(root, ".agents", "universal-plugin.json");
|
|
467
|
+
}
|
|
468
|
+
function readStateFile(filePath) {
|
|
469
|
+
try {
|
|
470
|
+
return mergeSafeState(JSON.parse(fsNode.readFileSync(filePath, "utf8")));
|
|
471
|
+
} catch (err) {
|
|
472
|
+
if (err.code === "ENOENT") return null;
|
|
473
|
+
throw err;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function writeStateFile(filePath, state) {
|
|
477
|
+
fsNode.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
478
|
+
fsNode.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
479
|
+
}
|
|
480
|
+
function realPrepareFs(vendor, projectRoot) {
|
|
481
|
+
return {
|
|
482
|
+
readManifest() {
|
|
483
|
+
if (!vendor.globalManifest) return {};
|
|
484
|
+
const manifestPath = expandHome(vendor.globalManifest);
|
|
485
|
+
try {
|
|
486
|
+
const raw = JSON.parse(fsNode.readFileSync(manifestPath, "utf8"));
|
|
487
|
+
return Object.fromEntries(Object.entries(raw).map(([k, v]) => [k, typeof v === "object" && v !== null && "version" in v ? String(v.version) : String(v)]));
|
|
488
|
+
} catch (err) {
|
|
489
|
+
if (err.code === "ENOENT") return {};
|
|
490
|
+
throw err;
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
readPluginRoots() {
|
|
494
|
+
if (!vendor.globalPluginDir) return {};
|
|
495
|
+
const pluginDir = expandHome(vendor.globalPluginDir);
|
|
496
|
+
const manifest = this.readManifest();
|
|
497
|
+
const home = os.homedir();
|
|
498
|
+
return Object.fromEntries(Object.keys(manifest).map((name) => {
|
|
499
|
+
const absPath = path.join(pluginDir, name);
|
|
500
|
+
return [name, absPath.startsWith(home) ? "~" + absPath.slice(home.length) : absPath];
|
|
501
|
+
}));
|
|
502
|
+
},
|
|
503
|
+
readGlobalState: () => readStateFile(globalStatePath$2()) ?? emptyState(),
|
|
504
|
+
readProjectState: () => projectRoot ? readStateFile(projectStatePath(projectRoot)) : null,
|
|
505
|
+
writeGlobalState: (s) => writeStateFile(globalStatePath$2(), s),
|
|
506
|
+
writeProjectState: (s) => {
|
|
507
|
+
if (projectRoot) writeStateFile(projectStatePath(projectRoot), s);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function populateStoreFromVendorCache(pluginRoots, versions) {
|
|
512
|
+
const storePath = globalStorePath();
|
|
513
|
+
for (const [pluginName, pluginRoot] of Object.entries(pluginRoots)) {
|
|
514
|
+
const entryPath = storeEntryPath(storePath, `npm/${pluginName}@${versions[pluginName] ?? "unknown"}`);
|
|
515
|
+
if (entryExists(entryPath)) continue;
|
|
516
|
+
populateEntry(entryPath, pluginRoot);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
//#endregion
|
|
520
|
+
//#region src/prepare/delta.ts
|
|
521
|
+
function computeDelta(input) {
|
|
522
|
+
const { vendorId, scope, currentPlugins, state, now } = input;
|
|
523
|
+
const previous = state.snapshots[vendorId]?.[scope];
|
|
524
|
+
if (!previous) return [];
|
|
525
|
+
const previousPlugins = previous.plugins;
|
|
526
|
+
const added = Object.keys(currentPlugins).filter((p) => !(p in previousPlugins));
|
|
527
|
+
const removed = Object.keys(previousPlugins).filter((p) => !(p in currentPlugins));
|
|
528
|
+
const otherVendors = Object.entries(state.snapshots).filter(([id]) => id !== vendorId);
|
|
529
|
+
const actions = [];
|
|
530
|
+
let counter = 0;
|
|
531
|
+
const makeId = () => `delta-${now}-${++counter}`;
|
|
532
|
+
for (const plugin of added) {
|
|
533
|
+
const version = currentPlugins[plugin];
|
|
534
|
+
for (const [otherId, otherScopes] of otherVendors) {
|
|
535
|
+
const otherScope = otherScopes[scope];
|
|
536
|
+
if (!otherScope) continue;
|
|
537
|
+
if (plugin in otherScope.plugins) continue;
|
|
538
|
+
if (isDismissed(state, otherId, scope, plugin, version)) continue;
|
|
539
|
+
actions.push({
|
|
540
|
+
id: makeId(),
|
|
541
|
+
type: "install",
|
|
542
|
+
plugin,
|
|
543
|
+
version,
|
|
544
|
+
fromVendor: vendorId,
|
|
545
|
+
toVendor: otherId,
|
|
546
|
+
scope,
|
|
547
|
+
detectedAt: now
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
for (const plugin of removed) for (const [otherId, otherScopes] of otherVendors) {
|
|
552
|
+
const otherScope = otherScopes[scope];
|
|
553
|
+
if (!otherScope) continue;
|
|
554
|
+
if (!(plugin in otherScope.plugins)) continue;
|
|
555
|
+
const otherVersion = otherScope.plugins[plugin];
|
|
556
|
+
if (isDismissed(state, otherId, scope, plugin, otherVersion)) continue;
|
|
557
|
+
actions.push({
|
|
558
|
+
id: makeId(),
|
|
559
|
+
type: "remove",
|
|
560
|
+
plugin,
|
|
561
|
+
version: otherVersion,
|
|
562
|
+
fromVendor: vendorId,
|
|
563
|
+
toVendor: otherId,
|
|
564
|
+
scope,
|
|
565
|
+
detectedAt: now
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
for (const [otherId, otherScopes] of otherVendors) {
|
|
569
|
+
const otherScope = otherScopes[scope];
|
|
570
|
+
if (!otherScope) continue;
|
|
571
|
+
for (const [plugin, version] of Object.entries(currentPlugins)) {
|
|
572
|
+
const otherVersion = otherScope.plugins[plugin];
|
|
573
|
+
if (!otherVersion || otherVersion === version) continue;
|
|
574
|
+
if (!isNewerSemver(version, otherVersion)) continue;
|
|
575
|
+
if (isDismissed(state, otherId, scope, plugin, version)) continue;
|
|
576
|
+
actions.push({
|
|
577
|
+
id: makeId(),
|
|
578
|
+
type: "upgrade",
|
|
579
|
+
plugin,
|
|
580
|
+
version,
|
|
581
|
+
fromVendor: vendorId,
|
|
582
|
+
toVendor: otherId,
|
|
583
|
+
scope,
|
|
584
|
+
detectedAt: now
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return actions;
|
|
589
|
+
}
|
|
590
|
+
function isNewerSemver(a, b) {
|
|
591
|
+
const [aMajor = 0, aMinor = 0, aPatch = 0] = a.split(".").map(Number);
|
|
592
|
+
const [bMajor = 0, bMinor = 0, bPatch = 0] = b.split(".").map(Number);
|
|
593
|
+
if (aMajor !== bMajor) return aMajor > bMajor;
|
|
594
|
+
if (aMinor !== bMinor) return aMinor > bMinor;
|
|
595
|
+
return aPatch > bPatch;
|
|
596
|
+
}
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/prepare/prepare.ts
|
|
599
|
+
function runPrepare(opts) {
|
|
600
|
+
const { vendorId, scope, fs: prepareFs, now, dryRun } = opts;
|
|
601
|
+
const currentPlugins = prepareFs.readManifest();
|
|
602
|
+
const state = scope === "global" ? prepareFs.readGlobalState() : prepareFs.readProjectState() ?? emptyState();
|
|
603
|
+
const actions = computeDelta({
|
|
604
|
+
vendorId,
|
|
605
|
+
scope,
|
|
606
|
+
currentPlugins,
|
|
607
|
+
state,
|
|
608
|
+
now
|
|
609
|
+
});
|
|
610
|
+
let updatedState = takeSnapshot(state, vendorId, scope, currentPlugins, now);
|
|
611
|
+
for (const action of actions) updatedState = addPendingAction(updatedState, action);
|
|
612
|
+
const pluginRoots = prepareFs.readPluginRoots();
|
|
613
|
+
for (const [pluginName, pluginPath] of Object.entries(pluginRoots)) {
|
|
614
|
+
const version = currentPlugins[pluginName] ?? "unknown";
|
|
615
|
+
updatedState = writePluginIndex(updatedState, vendorId, pluginName, {
|
|
616
|
+
source: "npm",
|
|
617
|
+
path: pluginPath,
|
|
618
|
+
version
|
|
619
|
+
});
|
|
620
|
+
updatedState = writeAssetIndex(updatedState, pluginName, {
|
|
621
|
+
source: "npm",
|
|
622
|
+
version
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
if (!dryRun) if (scope === "global") prepareFs.writeGlobalState(updatedState);
|
|
626
|
+
else prepareFs.writeProjectState(updatedState);
|
|
627
|
+
return { newActionCount: actions.length };
|
|
628
|
+
}
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region src/prepare/cli.ts
|
|
631
|
+
function prepareCommand() {
|
|
632
|
+
return new Command("prepare").description("Detect cross-vendor plugin sync actions").argument("<vendor-id>", "Vendor to read manifest from (e.g. claude-code)").addOption(new Option("--scope <scope>", "global or project").default("global").choices(["global", "project"])).option("--root <path>", "Project root for project-scope state file").option("--dry-run", "Print action count without writing state").action((vendorId, opts) => {
|
|
633
|
+
const vendor = lookupVendor(loadRegistry(), vendorId);
|
|
634
|
+
if (!vendor) {
|
|
635
|
+
process.stderr.write(`Unknown vendor: ${vendorId}\n`);
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
if (opts.scope === "project" && !opts.root) {
|
|
639
|
+
process.stderr.write("--root is required when --scope is project\n");
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
643
|
+
const prepareFs = realPrepareFs(vendor, opts.root);
|
|
644
|
+
const pluginRoots = prepareFs.readPluginRoots();
|
|
645
|
+
const manifest = prepareFs.readManifest();
|
|
646
|
+
const { newActionCount } = runPrepare({
|
|
647
|
+
vendorId,
|
|
648
|
+
scope: opts.scope,
|
|
649
|
+
fs: prepareFs,
|
|
650
|
+
now,
|
|
651
|
+
dryRun: opts.dryRun
|
|
652
|
+
});
|
|
653
|
+
populateStoreFromVendorCache(pluginRoots, manifest);
|
|
654
|
+
if (newActionCount > 0) process.stdout.write(`${newActionCount} plugin sync action(s) pending. Run /sync to review.\n`);
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
//#endregion
|
|
658
|
+
//#region src/self-update/fs.ts
|
|
659
|
+
function globalStatePath$1() {
|
|
660
|
+
return path.join(os.homedir(), ".agents", "universal-plugin.json");
|
|
661
|
+
}
|
|
662
|
+
function realSelfUpdateFs(hookFilePaths) {
|
|
663
|
+
return {
|
|
664
|
+
globHookFiles: () => hookFilePaths,
|
|
665
|
+
readFile: (p) => fsNode.readFileSync(p, "utf8"),
|
|
666
|
+
writeFile: (p, c) => fsNode.writeFileSync(p, c, "utf8"),
|
|
667
|
+
readGlobalState: () => {
|
|
668
|
+
try {
|
|
669
|
+
return mergeSafeState(JSON.parse(fsNode.readFileSync(globalStatePath$1(), "utf8")));
|
|
670
|
+
} catch (err) {
|
|
671
|
+
if (err.code === "ENOENT") return emptyState();
|
|
672
|
+
throw err;
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
writeGlobalState: (s) => {
|
|
676
|
+
fsNode.mkdirSync(path.dirname(globalStatePath$1()), { recursive: true });
|
|
677
|
+
fsNode.writeFileSync(globalStatePath$1(), JSON.stringify(s, null, 2) + "\n");
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
//#endregion
|
|
682
|
+
//#region src/self-update/self-update.ts
|
|
683
|
+
function runSelfUpdate(opts) {
|
|
684
|
+
const { toVersion, fs } = opts;
|
|
685
|
+
const [major] = toVersion.split(".");
|
|
686
|
+
if (!major || !/^\d+$/.test(major)) throw new Error(`Invalid version format: ${toVersion}`);
|
|
687
|
+
const pattern = new RegExp(`(npx universal-plugin@)${major}\\.[0-9]+\\.[0-9]+`, "g");
|
|
688
|
+
let updatedCount = 0;
|
|
689
|
+
for (const filePath of fs.globHookFiles()) {
|
|
690
|
+
const content = fs.readFile(filePath);
|
|
691
|
+
pattern.lastIndex = 0;
|
|
692
|
+
if (!pattern.test(content)) continue;
|
|
693
|
+
pattern.lastIndex = 0;
|
|
694
|
+
const updated = content.replace(pattern, `$1${toVersion}`);
|
|
695
|
+
if (updated !== content) {
|
|
696
|
+
fs.writeFile(filePath, updated);
|
|
697
|
+
updatedCount++;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const state = fs.readGlobalState();
|
|
701
|
+
const updates = { ...state.uniPluginUpdates };
|
|
702
|
+
delete updates[major];
|
|
703
|
+
fs.writeGlobalState({
|
|
704
|
+
...state,
|
|
705
|
+
uniPluginUpdates: updates
|
|
706
|
+
});
|
|
707
|
+
return { updatedCount };
|
|
708
|
+
}
|
|
709
|
+
//#endregion
|
|
710
|
+
//#region src/self-update/cli.ts
|
|
711
|
+
function selfUpdateCommand() {
|
|
712
|
+
return new Command("self-update").description("Update universal-plugin version pin in universal-plugin hook files").argument("<version>", "Target version (e.g. 1.5.0)").action((toVersion) => {
|
|
713
|
+
const registry = loadRegistry();
|
|
714
|
+
const { updatedCount } = runSelfUpdate({
|
|
715
|
+
toVersion,
|
|
716
|
+
fs: realSelfUpdateFs(Object.values(registry).map((v) => v.hookGlob).filter((g) => g !== null).map((g) => g.replace("~", os.homedir())).filter((p) => {
|
|
717
|
+
try {
|
|
718
|
+
fsNode.accessSync(p);
|
|
719
|
+
return true;
|
|
720
|
+
} catch {
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
}))
|
|
724
|
+
});
|
|
725
|
+
process.stdout.write(`Updated ${updatedCount} hook file(s).\n`);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
//#endregion
|
|
729
|
+
//#region src/sync/sync.ts
|
|
730
|
+
function applySyncAction(opts) {
|
|
731
|
+
const { actionId, registry, fs } = opts;
|
|
732
|
+
const state = fs.readGlobalState();
|
|
733
|
+
const action = state.pendingActions.find((a) => a.id === actionId);
|
|
734
|
+
if (!action) return { outcome: "not-found" };
|
|
735
|
+
const vendor = registry[action.toVendor];
|
|
736
|
+
const templateCommand = action.type === "install" ? vendor?.installCommand : action.type === "remove" ? vendor?.removeCommand : vendor?.updateCommand;
|
|
737
|
+
const withoutAction = {
|
|
738
|
+
...state,
|
|
739
|
+
pendingActions: state.pendingActions.filter((a) => a.id !== actionId)
|
|
740
|
+
};
|
|
741
|
+
if (!templateCommand) {
|
|
742
|
+
fs.writeGlobalState(withoutAction);
|
|
743
|
+
return {
|
|
744
|
+
outcome: "manual",
|
|
745
|
+
instruction: `Install ${action.plugin}@${action.version} in ${action.toVendor} via its marketplace, then run: sync apply ${action.id}`
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
const command = templateCommand.replace("{name}", action.plugin).replace("{version}", action.version);
|
|
749
|
+
if (fs.shell(command) !== 0) return { outcome: "failed" };
|
|
750
|
+
fs.writeGlobalState(withoutAction);
|
|
751
|
+
return { outcome: "applied" };
|
|
752
|
+
}
|
|
753
|
+
//#endregion
|
|
754
|
+
//#region src/sync/cli.ts
|
|
755
|
+
function globalStatePath() {
|
|
756
|
+
return path.join(os.homedir(), ".agents", "universal-plugin.json");
|
|
757
|
+
}
|
|
758
|
+
function realSyncFs() {
|
|
759
|
+
return {
|
|
760
|
+
readGlobalState: () => {
|
|
761
|
+
try {
|
|
762
|
+
return mergeSafeState(JSON.parse(fsNode.readFileSync(globalStatePath(), "utf8")));
|
|
763
|
+
} catch (err) {
|
|
764
|
+
if (err.code === "ENOENT") return emptyState();
|
|
765
|
+
throw err;
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
writeGlobalState: (s) => {
|
|
769
|
+
fsNode.mkdirSync(path.dirname(globalStatePath()), { recursive: true });
|
|
770
|
+
fsNode.writeFileSync(globalStatePath(), JSON.stringify(s, null, 2) + "\n");
|
|
771
|
+
},
|
|
772
|
+
shell: (cmd) => childProcess.spawnSync(cmd, {
|
|
773
|
+
shell: true,
|
|
774
|
+
stdio: "inherit"
|
|
775
|
+
}).status ?? 1
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function syncCommand() {
|
|
779
|
+
const cmd = new Command("sync").description("Manage cross-vendor plugin sync").addHelpCommand(false);
|
|
780
|
+
cmd.command("apply").description("Apply a pending sync action").argument("<action-id>", "Action ID from ~/.agents/universal-plugin.json").action((actionId) => {
|
|
781
|
+
const result = applySyncAction({
|
|
782
|
+
actionId,
|
|
783
|
+
registry: loadRegistry(),
|
|
784
|
+
fs: realSyncFs(),
|
|
785
|
+
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
786
|
+
});
|
|
787
|
+
if (result.outcome === "applied") process.stdout.write("Applied.\n");
|
|
788
|
+
else if (result.outcome === "manual") process.stdout.write(`${result.instruction}\n`);
|
|
789
|
+
else if (result.outcome === "not-found") {
|
|
790
|
+
process.stderr.write(`Action "${actionId}" not found in state file.\n`);
|
|
791
|
+
process.exit(1);
|
|
792
|
+
} else {
|
|
793
|
+
process.stderr.write("Apply failed. Try again or install manually.\n");
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
return cmd;
|
|
798
|
+
}
|
|
799
|
+
//#endregion
|
|
800
|
+
//#region src/cli.ts
|
|
801
|
+
const program = new Command();
|
|
802
|
+
program.name("universal-plugin").description("Universal AI agent plugin build tool").version("0.0.0").addHelpCommand(false);
|
|
803
|
+
program.addCommand(buildCommand());
|
|
804
|
+
program.addCommand(cleanCommand());
|
|
805
|
+
program.addCommand(governanceCommand());
|
|
806
|
+
program.addCommand(prepareCommand());
|
|
807
|
+
program.addCommand(syncCommand());
|
|
808
|
+
program.addCommand(selfUpdateCommand());
|
|
809
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
810
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
811
|
+
process.exit(1);
|
|
812
|
+
});
|
|
813
|
+
//#endregion
|
|
814
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"claude-code": {
|
|
3
|
+
"sessionStartEvent": "SessionStart",
|
|
4
|
+
"globalManifest": "~/.claude/plugins/installed_plugins.json",
|
|
5
|
+
"projectManifest": null,
|
|
6
|
+
"hookGlob": "~/.claude/plugins/universal-plugin/hooks/hooks.json",
|
|
7
|
+
"globalPluginDir": "~/.claude/plugins/",
|
|
8
|
+
"pluginRootSuffix": ".claude-plugin/plugin.json",
|
|
9
|
+
"installCommand": "claude plugin install {name}",
|
|
10
|
+
"removeCommand": "claude plugin remove {name}",
|
|
11
|
+
"updateCommand": "claude plugin update {name}@{version}"
|
|
12
|
+
},
|
|
13
|
+
"cursor": {
|
|
14
|
+
"sessionStartEvent": "sessionStart",
|
|
15
|
+
"globalManifest": null,
|
|
16
|
+
"projectManifest": null,
|
|
17
|
+
"hookGlob": null,
|
|
18
|
+
"globalPluginDir": null,
|
|
19
|
+
"pluginRootSuffix": ".cursor-plugin/plugin.json",
|
|
20
|
+
"installCommand": null,
|
|
21
|
+
"removeCommand": null,
|
|
22
|
+
"updateCommand": null
|
|
23
|
+
},
|
|
24
|
+
"codex": {
|
|
25
|
+
"sessionStartEvent": "SessionStart",
|
|
26
|
+
"globalManifest": null,
|
|
27
|
+
"projectManifest": null,
|
|
28
|
+
"hookGlob": null,
|
|
29
|
+
"globalPluginDir": null,
|
|
30
|
+
"pluginRootSuffix": ".codex-plugin/plugin.json",
|
|
31
|
+
"installCommand": null,
|
|
32
|
+
"removeCommand": null,
|
|
33
|
+
"updateCommand": null
|
|
34
|
+
},
|
|
35
|
+
"copilot-cli": {
|
|
36
|
+
"sessionStartEvent": "sessionStart",
|
|
37
|
+
"globalManifest": null,
|
|
38
|
+
"projectManifest": null,
|
|
39
|
+
"hookGlob": null,
|
|
40
|
+
"globalPluginDir": null,
|
|
41
|
+
"pluginRootSuffix": "plugin.json",
|
|
42
|
+
"installCommand": null,
|
|
43
|
+
"removeCommand": null,
|
|
44
|
+
"updateCommand": null
|
|
45
|
+
}
|
|
46
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "universal-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Universal AI agent plugin build tool",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent-plugin",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"cursor",
|
|
9
|
+
"codex",
|
|
10
|
+
"copilot-cli"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/cyberuni/cyber-universal-agent-plugin.git",
|
|
15
|
+
"directory": "packages/universal-plugin"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "unional <homawong@gmail.com>",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"bin": {
|
|
21
|
+
"universal-plugin": "bin/universal-plugin.mjs"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin",
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsdown",
|
|
32
|
+
"dev": "tsx src/cli.ts",
|
|
33
|
+
"knip": "knip",
|
|
34
|
+
"lint": "biome check .",
|
|
35
|
+
"prepack": "pnpm build",
|
|
36
|
+
"test": "pnpm build && vitest run src",
|
|
37
|
+
"test:watch": "vitest",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"verify": "pnpm typecheck && pnpm lint && pnpm test"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"commander": "^14.0.3"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^24.10.1",
|
|
46
|
+
"knip": "^6.14.1",
|
|
47
|
+
"tsdown": "^0.22.0",
|
|
48
|
+
"tsx": "^4.22.3",
|
|
49
|
+
"typescript": "^6.0.0",
|
|
50
|
+
"vitest": "^4.1.7"
|
|
51
|
+
},
|
|
52
|
+
"packageManager": "pnpm@11.0.0",
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=22"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public",
|
|
58
|
+
"provenance": true
|
|
59
|
+
}
|
|
60
|
+
}
|