@danieldeusing/seedr 0.1.97 → 0.1.99

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/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ AGENT_COMPATIBILITY,
3
4
  ALL_AGENTS,
5
+ ALL_TYPES,
4
6
  CODING_AGENTS,
5
7
  MAX_SLUG_LENGTH,
6
8
  SLUG_PATTERN,
@@ -11,8 +13,12 @@ import {
11
13
  brand,
12
14
  canonicalAgent,
13
15
  canonicalAgents,
16
+ claudeUserRoot,
17
+ codexUserRoot,
14
18
  confirm,
19
+ copilotUserRoot,
15
20
  copyDirectory,
21
+ deepMerge,
16
22
  ensureDir,
17
23
  error,
18
24
  exists,
@@ -33,15 +39,18 @@ import {
33
39
  intro,
34
40
  isInteractive,
35
41
  isLegacyAgent,
42
+ isTypeSupported,
36
43
  isValidSlug,
37
44
  listItems,
38
45
  mainFileName,
39
46
  moveDirectory,
47
+ openCodeUserConfigDir,
40
48
  outro,
41
49
  p,
42
50
  parseAgentsArgStrict,
43
51
  parseGitHubRepo,
44
52
  printLogo,
53
+ readJson,
45
54
  removePathEntry,
46
55
  resolveContained,
47
56
  restoreFile,
@@ -56,15 +65,16 @@ import {
56
65
  success,
57
66
  warn,
58
67
  writeFileAtomic,
68
+ writeJson,
59
69
  writeTextFile
60
- } from "./chunk-PEGEA2KN.js";
70
+ } from "./chunk-3KPB5CCX.js";
61
71
 
62
72
  // src/cli.ts
63
73
  import { Command as Command5 } from "commander";
64
74
 
65
75
  // src/commands/add.ts
66
76
  import { Command } from "commander";
67
- import chalk8 from "chalk";
77
+ import chalk9 from "chalk";
68
78
 
69
79
  // src/handlers/registry.ts
70
80
  var handlers = /* @__PURE__ */ new Map();
@@ -104,15 +114,8 @@ function handleCommandError(error2) {
104
114
  // src/utils/validate-options.ts
105
115
  var SCOPES = ["project", "user", "local"];
106
116
  var METHODS = ["symlink", "copy"];
107
- var TYPES = [
108
- "skill",
109
- "hook",
110
- "agent",
111
- "plugin",
112
- "command",
113
- "settings",
114
- "mcp"
115
- ];
117
+ var TYPES = ALL_TYPES;
118
+ var TYPE_LIST = TYPES.join(", ");
116
119
  function validateScope(value) {
117
120
  if (value === void 0) return null;
118
121
  if (!SCOPES.includes(value)) {
@@ -144,7 +147,7 @@ function isTelemetryDisabled(env = process.env) {
144
147
  return env[TELEMETRY_OPT_OUT_VARIABLE] !== void 0;
145
148
  }
146
149
  function cliVersion() {
147
- return true ? "0.1.97" : "dev";
150
+ return true ? "0.1.99" : "dev";
148
151
  }
149
152
  async function sendEvent(event) {
150
153
  try {
@@ -165,26 +168,12 @@ function trackInstalls(slug, type, results, scope) {
165
168
  }
166
169
 
167
170
  // src/config/compatibility.ts
168
- var AGENT_COMPATIBILITY = {
169
- skill: ["claude", "copilot", "antigravity", "codex", "opencode"],
170
- command: ["claude"],
171
- agent: ["claude"],
172
- hook: ["claude"],
173
- plugin: ["claude"],
174
- settings: ["claude"],
175
- mcp: ["claude", "codex", "opencode"]
176
- };
177
- function isTypeSupported(type, agent) {
178
- const canonical = canonicalAgent(agent);
179
- return canonical !== null && AGENT_COMPATIBILITY[type].includes(canonical);
180
- }
181
171
  function filterCompatibleAgents(type, agents) {
182
172
  const compatible = AGENT_COMPATIBILITY[type];
183
173
  return canonicalAgents(agents).filter((a) => compatible.includes(a));
184
174
  }
185
175
  var MCP_UNSUPPORTED_REASONS = {
186
- copilot: "GitHub Copilot's MCP configuration format could not be verified against primary documentation, so seedr does not write it",
187
- antigravity: "Google Antigravity's MCP configuration format could not be verified against primary documentation, so seedr does not write it"
176
+ antigravity: "Google Antigravity's MCP file name is documented but its schema has never been observed \u2014 the file is empty on every machine checked \u2014 so seedr does not guess at it"
188
177
  };
189
178
  function describeIncompatibility(type, agent) {
190
179
  const canonical = canonicalAgent(agent);
@@ -201,11 +190,14 @@ import { lstat, readdir, unlink } from "fs/promises";
201
190
  import chalk2 from "chalk";
202
191
  import ora from "ora";
203
192
  var SLUG_LABEL = "agent slug";
193
+ function subagentFileName(agent, slug) {
194
+ return (canonicalAgent(agent) ?? agent) === "copilot" ? `${slug}.agent.md` : `${slug}.md`;
195
+ }
204
196
  async function resolveAgentFilePath(agent, slug, scope, cwd) {
205
197
  const destDir = getContentPath(agent, "agent", scope, cwd);
206
198
  if (!destDir) return null;
207
199
  const scopeRoot = scope === "user" ? homedir() : cwd;
208
- return resolveContained(scopeRoot, relative(scopeRoot, destDir), `${slug}.md`);
200
+ return resolveContained(scopeRoot, relative(scopeRoot, destDir), subagentFileName(agent, slug));
209
201
  }
210
202
  async function installAgentForCodingAgent(item, agent, scope, method, force, cwd) {
211
203
  const spinner = ora(
@@ -269,8 +261,9 @@ async function getInstalledAgents(agent, scope, cwd = process.cwd()) {
269
261
  if (!destDir || !await exists(destDir)) {
270
262
  return [];
271
263
  }
264
+ const suffix = subagentFileName(agent, "");
272
265
  const files = await readdir(destDir);
273
- return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
266
+ return files.filter((file) => file.endsWith(suffix)).map((file) => file.slice(0, -suffix.length));
274
267
  }
275
268
  async function planAgent(item, agents, scope, method, cwd) {
276
269
  assertValidSlug(item.slug, SLUG_LABEL);
@@ -413,60 +406,11 @@ var commandHandler = {
413
406
  };
414
407
 
415
408
  // src/handlers/hook.ts
416
- import { join as join3, basename, dirname as dirname2 } from "path";
409
+ import { join as join3, basename, dirname } from "path";
417
410
  import { homedir as homedir3 } from "os";
418
- import { lstat as lstat3, mkdir, mkdtemp, readFile as readFile2, realpath, rm, unlink as unlink3 } from "fs/promises";
411
+ import { lstat as lstat3, mkdir, mkdtemp, readFile, realpath, rm, unlink as unlink3 } from "fs/promises";
419
412
  import chalk4 from "chalk";
420
413
  import ora3 from "ora";
421
-
422
- // src/utils/json.ts
423
- import { readFile, stat } from "fs/promises";
424
- import { dirname } from "path";
425
- async function readJson(path) {
426
- try {
427
- const content = await readFile(path, "utf-8");
428
- return JSON.parse(content);
429
- } catch (error2) {
430
- if (error2.code === "ENOENT") {
431
- return {};
432
- }
433
- if (error2 instanceof SyntaxError) {
434
- throw new Error(`${path} is not valid JSON: ${error2.message}`, { cause: error2 });
435
- }
436
- throw error2;
437
- }
438
- }
439
- async function existingFileMode(path) {
440
- try {
441
- return (await stat(path)).mode & 511;
442
- } catch (error2) {
443
- if (error2.code === "ENOENT") return void 0;
444
- throw error2;
445
- }
446
- }
447
- async function writeJson(path, data) {
448
- await ensureDir(dirname(path));
449
- const mode = await existingFileMode(path);
450
- await writeFileAtomic(path, JSON.stringify(data, null, 2) + "\n", { mode });
451
- }
452
- function deepMerge(target, source) {
453
- const result = { ...target };
454
- for (const key of Object.keys(source)) {
455
- const sourceValue = source[key];
456
- const targetValue = result[key];
457
- if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue !== null && typeof targetValue === "object" && !Array.isArray(targetValue)) {
458
- result[key] = deepMerge(
459
- targetValue,
460
- sourceValue
461
- );
462
- } else if (sourceValue !== void 0) {
463
- result[key] = sourceValue;
464
- }
465
- }
466
- return result;
467
- }
468
-
469
- // src/handlers/hook.ts
470
414
  var CLAUDE_ONLY_ERROR = "Hooks are only supported for Claude Code";
471
415
  var SCRIPT_NAME_LABEL = "hook script name";
472
416
  var SCRIPT_MODE = 493;
@@ -474,7 +418,7 @@ function getScopeRoot(scope, cwd) {
474
418
  return scope === "user" ? homedir3() : cwd;
475
419
  }
476
420
  function getHooksDir(scope, cwd) {
477
- return join3(getScopeRoot(scope, cwd), ".claude", "hooks");
421
+ return scope === "user" ? join3(claudeUserRoot(), "hooks") : join3(getScopeRoot(scope, cwd), ".claude", "hooks");
478
422
  }
479
423
  function getScriptPath(scope, scriptName) {
480
424
  switch (scope) {
@@ -520,13 +464,13 @@ async function assertRegularFileOrMissing(path) {
520
464
  async function loadScriptBytes(item, scriptFile, hooksDir) {
521
465
  const sourcePath = getItemSourcePath(item);
522
466
  if (sourcePath && await exists(sourcePath)) {
523
- return readFile2(join3(sourcePath, scriptFile));
467
+ return readFile(join3(sourcePath, scriptFile));
524
468
  }
525
- const fetchDir = await mkdtemp(join3(dirname2(hooksDir), ".seedr-hook-"));
469
+ const fetchDir = await mkdtemp(join3(dirname(hooksDir), ".seedr-hook-"));
526
470
  try {
527
471
  const contentDir = join3(fetchDir, "content");
528
472
  await fetchItemToDestination(item, contentDir);
529
- return await readFile2(join3(contentDir, scriptFile));
473
+ return await readFile(join3(contentDir, scriptFile));
530
474
  } finally {
531
475
  await rm(fetchDir, { recursive: true, force: true });
532
476
  }
@@ -703,6 +647,7 @@ async function planHook(item, agents, scope, _method, cwd) {
703
647
  }
704
648
  var hookHandler = {
705
649
  type: "hook",
650
+ honoursMethod: false,
706
651
  async install(item, agents, scope, method, force, cwd) {
707
652
  return installHook(item, agents, scope, method, force, cwd);
708
653
  },
@@ -716,8 +661,8 @@ var hookHandler = {
716
661
  };
717
662
 
718
663
  // src/handlers/mcp.ts
719
- import { readFile as readFile3 } from "fs/promises";
720
- import { dirname as dirname3 } from "path";
664
+ import { readFile as readFile2 } from "fs/promises";
665
+ import { dirname as dirname2 } from "path";
721
666
  import chalk5 from "chalk";
722
667
  import ora4 from "ora";
723
668
 
@@ -1014,7 +959,7 @@ function jsonAdapter(agent, key, translate, defaults = {}) {
1014
959
  }
1015
960
  async function readTextOrEmpty(path) {
1016
961
  try {
1017
- return await readFile3(path, "utf-8");
962
+ return await readFile2(path, "utf-8");
1018
963
  } catch (error2) {
1019
964
  if (error2.code === "ENOENT") return "";
1020
965
  throw error2;
@@ -1028,7 +973,7 @@ var codexAdapter = {
1028
973
  },
1029
974
  async write(path, name, config) {
1030
975
  const text = await readTextOrEmpty(path);
1031
- await ensureDir(dirname3(path));
976
+ await ensureDir(dirname2(path));
1032
977
  await writeFileAtomic(path, upsertTomlTables(text, [CODEX_TABLE, name], toCodexTables(name, config)));
1033
978
  },
1034
979
  async remove(path, name) {
@@ -1044,7 +989,10 @@ var codexAdapter = {
1044
989
  var ADAPTERS = {
1045
990
  claude: jsonAdapter("claude", MCP_SERVERS_KEY, (config) => ({ ...config })),
1046
991
  opencode: jsonAdapter("opencode", "mcp", toOpenCodeServer, { $schema: "https://opencode.ai/config.json" }),
1047
- codex: codexAdapter
992
+ codex: codexAdapter,
993
+ // Copilot reads the same `mcpServers` map Claude does — verified from
994
+ // `copilot mcp --help` and from a populated `~/.copilot/mcp-config.json`.
995
+ copilot: jsonAdapter("copilot", MCP_SERVERS_KEY, (config) => ({ ...config }))
1048
996
  };
1049
997
  function adapterFor(agent) {
1050
998
  const adapter = ADAPTERS[agent];
@@ -1129,6 +1077,7 @@ async function planMcp(item, agents, scope, _method, cwd) {
1129
1077
  }
1130
1078
  var mcpHandler = {
1131
1079
  type: "mcp",
1080
+ honoursMethod: false,
1132
1081
  async install(item, agents, scope, method, force, cwd) {
1133
1082
  return installMcp(item, agents, scope, method, force, cwd);
1134
1083
  },
@@ -1255,6 +1204,7 @@ async function planSettings(item, agents, scope, _method, cwd) {
1255
1204
  }
1256
1205
  var settingsHandler = {
1257
1206
  type: "settings",
1207
+ honoursMethod: false,
1258
1208
  async install(item, agents, scope, method, force, cwd) {
1259
1209
  return installSettings(item, agents, scope, method, force, cwd);
1260
1210
  },
@@ -1268,23 +1218,30 @@ var settingsHandler = {
1268
1218
  };
1269
1219
 
1270
1220
  // src/handlers/plugin.ts
1221
+ import { join as join5, relative as relative3 } from "path";
1222
+ import { mkdir as mkdir3, mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
1223
+ import chalk7 from "chalk";
1224
+ import ora6 from "ora";
1225
+
1226
+ // src/handlers/pluginStores.ts
1271
1227
  import { homedir as homedir4 } from "os";
1272
- import { join as join4, relative as relative3 } from "path";
1273
- import { mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
1228
+ import { join as join4 } from "path";
1229
+ import { mkdir as mkdir2, readFile as readFile3, writeFile } from "fs/promises";
1274
1230
  import { execFile } from "child_process";
1275
1231
  import { promisify } from "util";
1276
- import chalk7 from "chalk";
1277
- import ora6 from "ora";
1278
1232
  var home = homedir4();
1279
- var PLUGINS_DIR = join4(home, ".claude", "plugins");
1280
- var PLUGINS_CACHE_DIR = join4(PLUGINS_DIR, "cache");
1281
- var INSTALLED_PLUGINS_PATH = join4(PLUGINS_DIR, "installed_plugins.json");
1282
- var KNOWN_MARKETPLACES_PATH = join4(PLUGINS_DIR, "known_marketplaces.json");
1283
- var MARKETPLACES_DIR = join4(PLUGINS_DIR, "marketplaces");
1284
- var PLUGIN_JSON_PATH = ".claude-plugin/plugin.json";
1285
- var CLAUDE_ONLY_ERROR3 = "Plugins are only supported for Claude Code";
1286
- var SLUG_LABEL3 = "plugin slug";
1287
- var GITHUB_REPO_SLUG_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
1233
+ var CLAUDE_MANIFEST = ".claude-plugin/plugin.json";
1234
+ async function readTextOrEmpty2(path) {
1235
+ try {
1236
+ return await readFile3(path, "utf-8");
1237
+ } catch {
1238
+ return "";
1239
+ }
1240
+ }
1241
+ async function writeText(path, text) {
1242
+ await mkdir2(join4(path, ".."), { recursive: true });
1243
+ await writeFile(path, text, "utf-8");
1244
+ }
1288
1245
  function getPluginId(name, marketplace) {
1289
1246
  return `${name}@${marketplace}`;
1290
1247
  }
@@ -1293,6 +1250,420 @@ function splitPluginId(pluginId) {
1293
1250
  if (at <= 0 || at === pluginId.length - 1) return null;
1294
1251
  return { name: pluginId.slice(0, at), marketplace: pluginId.slice(at + 1) };
1295
1252
  }
1253
+ function manifestsFor(ownManifest) {
1254
+ return ownManifest ? [ownManifest, CLAUDE_MANIFEST] : [CLAUDE_MANIFEST];
1255
+ }
1256
+ var CLAUDE_PLUGINS_DIR = join4(claudeUserRoot(), "plugins");
1257
+ var CLAUDE_CACHE_DIR = join4(CLAUDE_PLUGINS_DIR, "cache");
1258
+ var CLAUDE_INSTALLED_PATH = join4(CLAUDE_PLUGINS_DIR, "installed_plugins.json");
1259
+ var CLAUDE_KNOWN_MARKETPLACES_PATH = join4(CLAUDE_PLUGINS_DIR, "known_marketplaces.json");
1260
+ var CLAUDE_MARKETPLACES_DIR = join4(CLAUDE_PLUGINS_DIR, "marketplaces");
1261
+ function isScopeEntry(entry, scope, cwd) {
1262
+ return entry.scope === scope && (scope === "user" || entry.projectPath === cwd);
1263
+ }
1264
+ async function enableInJsonSettings(path, pluginId) {
1265
+ const settings = await readJson(path);
1266
+ settings.enabledPlugins = settings.enabledPlugins || {};
1267
+ settings.enabledPlugins[pluginId] = true;
1268
+ await writeJson(path, settings);
1269
+ }
1270
+ async function disableInJsonSettings(path, pluginId) {
1271
+ const settings = await readJson(path);
1272
+ if (!settings.enabledPlugins || !(pluginId in settings.enabledPlugins)) return false;
1273
+ delete settings.enabledPlugins[pluginId];
1274
+ await writeJson(path, settings);
1275
+ return true;
1276
+ }
1277
+ var claudeStore = {
1278
+ manifestPaths: manifestsFor(),
1279
+ userGlobal: false,
1280
+ cacheRoot: CLAUDE_CACHE_DIR,
1281
+ cachePath: (marketplace, name, version) => resolveContained(CLAUDE_CACHE_DIR, marketplace, name, version),
1282
+ /**
1283
+ * Claude Code reports a plugin as "orphaned" unless its marketplace is both
1284
+ * recorded and cloned, so this is the one store that fetches a second
1285
+ * repository. The other agents only record the source.
1286
+ */
1287
+ async ensureMarketplace(marketplace, item) {
1288
+ const known = await readJson(CLAUDE_KNOWN_MARKETPLACES_PATH);
1289
+ if (known[marketplace]) return [];
1290
+ const repo = marketplaceRepo(item);
1291
+ if (!repo) return [];
1292
+ const installLocation = await resolveContained(CLAUDE_MARKETPLACES_DIR, marketplace);
1293
+ const alreadyCloned = await exists(installLocation);
1294
+ return [
1295
+ {
1296
+ path: CLAUDE_KNOWN_MARKETPLACES_PATH,
1297
+ detail: `marketplace "${marketplace}"`,
1298
+ ...alreadyCloned ? {} : {
1299
+ creates: {
1300
+ path: installLocation,
1301
+ detail: `git clone https://github.com/${repo}.git`
1302
+ }
1303
+ },
1304
+ apply: async () => {
1305
+ await mkdir2(CLAUDE_MARKETPLACES_DIR, { recursive: true });
1306
+ if (!await exists(installLocation)) {
1307
+ await promisify(execFile)("git", [
1308
+ "clone",
1309
+ "--depth",
1310
+ "1",
1311
+ `https://github.com/${repo}.git`,
1312
+ installLocation
1313
+ ]);
1314
+ }
1315
+ const current = await readJson(
1316
+ CLAUDE_KNOWN_MARKETPLACES_PATH
1317
+ );
1318
+ current[marketplace] = {
1319
+ source: { source: "github", repo },
1320
+ installLocation,
1321
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1322
+ };
1323
+ await writeJson(CLAUDE_KNOWN_MARKETPLACES_PATH, current);
1324
+ }
1325
+ }
1326
+ ];
1327
+ },
1328
+ mutations: (context) => [
1329
+ {
1330
+ path: CLAUDE_INSTALLED_PATH,
1331
+ detail: `plugins["${context.pluginId}"] entry for ${context.scope} scope`,
1332
+ apply: async () => {
1333
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1334
+ const registry = await readJson(CLAUDE_INSTALLED_PATH);
1335
+ registry.version = registry.version || 2;
1336
+ registry.plugins = registry.plugins || {};
1337
+ const entry = {
1338
+ scope: context.scope,
1339
+ ...context.scope !== "user" ? { projectPath: context.cwd } : {},
1340
+ installPath: context.cachePath ?? "",
1341
+ version: context.version,
1342
+ installedAt: now,
1343
+ lastUpdated: now,
1344
+ gitCommitSha: context.gitCommitSha
1345
+ };
1346
+ const existing = registry.plugins[context.pluginId] || [];
1347
+ registry.plugins[context.pluginId] = [
1348
+ ...existing.filter((candidate) => !isScopeEntry(candidate, context.scope, context.cwd)),
1349
+ entry
1350
+ ];
1351
+ await writeJson(CLAUDE_INSTALLED_PATH, registry);
1352
+ }
1353
+ },
1354
+ {
1355
+ path: getSettingsPath(context.scope, context.cwd),
1356
+ detail: `enabledPlugins["${context.pluginId}"] = true`,
1357
+ apply: () => enableInJsonSettings(getSettingsPath(context.scope, context.cwd), context.pluginId)
1358
+ }
1359
+ ],
1360
+ async listInstalled(scope, cwd) {
1361
+ const registry = await readJson(CLAUDE_INSTALLED_PATH);
1362
+ if (!registry.plugins) return [];
1363
+ return Object.entries(registry.plugins).filter(([, entries]) => entries.some((entry) => isScopeEntry(entry, scope, cwd))).map(([pluginId]) => pluginId);
1364
+ },
1365
+ async remove(pluginId, scope, cwd) {
1366
+ const registry = await readJson(CLAUDE_INSTALLED_PATH);
1367
+ if (!registry.plugins?.[pluginId]) return false;
1368
+ const entries = registry.plugins[pluginId];
1369
+ const removed = entries.filter((entry) => isScopeEntry(entry, scope, cwd));
1370
+ if (removed.length === 0) return false;
1371
+ const remaining = entries.filter((entry) => !isScopeEntry(entry, scope, cwd));
1372
+ if (remaining.length === 0) delete registry.plugins[pluginId];
1373
+ else registry.plugins[pluginId] = remaining;
1374
+ await writeJson(CLAUDE_INSTALLED_PATH, registry);
1375
+ await disableInJsonSettings(getSettingsPath(scope, cwd), pluginId);
1376
+ for (const installPath of new Set(removed.map((entry) => entry.installPath))) {
1377
+ const stillUsed = Object.values(registry.plugins).some(
1378
+ (list) => list.some((entry) => entry.installPath === installPath)
1379
+ );
1380
+ if (stillUsed || installPath === "") continue;
1381
+ try {
1382
+ await removePathEntry(await resolveContained(CLAUDE_CACHE_DIR, installPath));
1383
+ } catch {
1384
+ }
1385
+ }
1386
+ return true;
1387
+ }
1388
+ };
1389
+ var COPILOT_DIR = copilotUserRoot();
1390
+ var COPILOT_SETTINGS_PATH = join4(COPILOT_DIR, "settings.json");
1391
+ var COPILOT_PLUGINS_DIR = join4(COPILOT_DIR, "installed-plugins");
1392
+ var copilotStore = {
1393
+ manifestPaths: manifestsFor(),
1394
+ userGlobal: true,
1395
+ cacheRoot: COPILOT_PLUGINS_DIR,
1396
+ cachePath: (marketplace, name) => resolveContained(COPILOT_PLUGINS_DIR, marketplace, name),
1397
+ async ensureMarketplace(marketplace, item) {
1398
+ const repo = marketplaceRepo(item);
1399
+ if (!repo) return [];
1400
+ const settings = await readJson(COPILOT_SETTINGS_PATH);
1401
+ if (settings.extraKnownMarketplaces?.[marketplace]) return [];
1402
+ return [
1403
+ {
1404
+ path: COPILOT_SETTINGS_PATH,
1405
+ detail: `extraKnownMarketplaces["${marketplace}"] = ${repo}`,
1406
+ apply: async () => {
1407
+ const current = await readJson(COPILOT_SETTINGS_PATH);
1408
+ current.extraKnownMarketplaces = current.extraKnownMarketplaces || {};
1409
+ current.extraKnownMarketplaces[marketplace] = { source: { source: "github", repo } };
1410
+ await writeJson(COPILOT_SETTINGS_PATH, current);
1411
+ }
1412
+ }
1413
+ ];
1414
+ },
1415
+ mutations: (context) => [
1416
+ {
1417
+ path: COPILOT_SETTINGS_PATH,
1418
+ detail: `enabledPlugins["${context.pluginId}"] = true`,
1419
+ apply: () => enableInJsonSettings(COPILOT_SETTINGS_PATH, context.pluginId)
1420
+ }
1421
+ ],
1422
+ async listInstalled() {
1423
+ const settings = await readJson(COPILOT_SETTINGS_PATH);
1424
+ return Object.entries(settings.enabledPlugins || {}).filter(([, enabled]) => enabled).map(([pluginId]) => pluginId);
1425
+ },
1426
+ async remove(pluginId) {
1427
+ const disabled = await disableInJsonSettings(COPILOT_SETTINGS_PATH, pluginId);
1428
+ if (!disabled) return false;
1429
+ const split = splitPluginId(pluginId);
1430
+ if (split) {
1431
+ try {
1432
+ await removePathEntry(await resolveContained(COPILOT_PLUGINS_DIR, split.marketplace, split.name));
1433
+ } catch {
1434
+ }
1435
+ }
1436
+ return true;
1437
+ }
1438
+ };
1439
+ var CODEX_DIR = codexUserRoot();
1440
+ var CODEX_CONFIG_PATH = join4(CODEX_DIR, "config.toml");
1441
+ var CODEX_CACHE_DIR = join4(CODEX_DIR, "plugins", "cache");
1442
+ function toCodexMarketplaceTable(marketplace, url, revision) {
1443
+ return {
1444
+ keyPath: ["marketplaces", marketplace],
1445
+ entries: {
1446
+ source_type: "git",
1447
+ source: url,
1448
+ ...revision ? { last_revision: revision } : {},
1449
+ last_updated: (/* @__PURE__ */ new Date()).toISOString()
1450
+ }
1451
+ };
1452
+ }
1453
+ function toCodexPluginTable(pluginId) {
1454
+ return { keyPath: ["plugins", pluginId], entries: { enabled: true } };
1455
+ }
1456
+ async function upsertCodex(table) {
1457
+ const text = await readTextOrEmpty2(CODEX_CONFIG_PATH);
1458
+ await writeText(CODEX_CONFIG_PATH, upsertTomlTables(text, table.keyPath, [table]));
1459
+ }
1460
+ var codexStore = {
1461
+ manifestPaths: manifestsFor(".codex-plugin/plugin.json"),
1462
+ userGlobal: true,
1463
+ cacheRoot: CODEX_CACHE_DIR,
1464
+ // Real cache entries on disk are <marketplace>/<name>/<version> — e.g.
1465
+ // claude-plugins-official/claude-code-setup/1.0.0. Dropping the version
1466
+ // segment writes a tree Codex does not look in.
1467
+ cachePath: (marketplace, name, version) => resolveContained(CODEX_CACHE_DIR, marketplace, name, version),
1468
+ async ensureMarketplace(marketplace, item) {
1469
+ const repo = marketplaceRepo(item);
1470
+ if (!repo) return [];
1471
+ const url = `https://github.com/${repo}.git`;
1472
+ return [
1473
+ {
1474
+ path: CODEX_CONFIG_PATH,
1475
+ detail: `[marketplaces.${marketplace}] source = "${url}"`,
1476
+ apply: () => upsertCodex(toCodexMarketplaceTable(marketplace, url, getEffectiveSourceRevision(item) ?? void 0))
1477
+ }
1478
+ ];
1479
+ },
1480
+ mutations: (context) => [
1481
+ {
1482
+ path: CODEX_CONFIG_PATH,
1483
+ detail: `[plugins."${context.pluginId}"] enabled = true`,
1484
+ apply: () => upsertCodex(toCodexPluginTable(context.pluginId))
1485
+ }
1486
+ ],
1487
+ async listInstalled() {
1488
+ const text = await readTextOrEmpty2(CODEX_CONFIG_PATH);
1489
+ const ids = [];
1490
+ for (const match of text.matchAll(/^\[plugins\."([^"]+)"\]/gm)) {
1491
+ if (match[1]) ids.push(match[1]);
1492
+ }
1493
+ return ids;
1494
+ },
1495
+ async remove(pluginId) {
1496
+ const text = await readTextOrEmpty2(CODEX_CONFIG_PATH);
1497
+ const { text: next, removed } = removeTomlTables(text, ["plugins", pluginId]);
1498
+ if (!removed) return false;
1499
+ await writeText(CODEX_CONFIG_PATH, next);
1500
+ const split = splitPluginId(pluginId);
1501
+ if (split) {
1502
+ try {
1503
+ await removePathEntry(await resolveContained(CODEX_CACHE_DIR, split.marketplace, split.name));
1504
+ } catch {
1505
+ }
1506
+ }
1507
+ return true;
1508
+ }
1509
+ };
1510
+ function openCodeConfigPath(scope, cwd) {
1511
+ return scope === "user" ? join4(openCodeUserConfigDir(), "opencode.json") : join4(cwd, "opencode.json");
1512
+ }
1513
+ function toOpenCodePluginSpec(item, name) {
1514
+ const source = item.pluginSource;
1515
+ if (source?.path) {
1516
+ throw new Error(
1517
+ `OpenCode cannot install "${name}": its content is the subdirectory "${source.path}" of ${source.url ?? "its repository"}, and an OpenCode plugin spec cannot name a subdirectory`
1518
+ );
1519
+ }
1520
+ const repo = githubRepoOf(source?.url) ?? githubRepoOf(item.externalUrl);
1521
+ if (!repo) return name;
1522
+ const pinned = source?.sha ?? item.sourceRevision;
1523
+ return `${name}@git+https://github.com/${repo}.git${pinned ? `#${pinned}` : ""}`;
1524
+ }
1525
+ function specName(spec) {
1526
+ const at = spec.indexOf("@", 1);
1527
+ return at > 0 ? spec.slice(0, at) : spec;
1528
+ }
1529
+ var openCodeStore = {
1530
+ manifestPaths: manifestsFor(),
1531
+ userGlobal: false,
1532
+ cacheRoot: null,
1533
+ // OpenCode installs the module from the spec; seedr writes no tree.
1534
+ cachePath: async () => null,
1535
+ mutations: (context) => {
1536
+ const path = openCodeConfigPath(context.scope, context.cwd);
1537
+ const spec = toOpenCodePluginSpec(context.item, context.name);
1538
+ return [
1539
+ {
1540
+ path,
1541
+ detail: `plugin[] += "${spec}"`,
1542
+ apply: async () => {
1543
+ const config = await readJson(path);
1544
+ config.$schema = config.$schema || "https://opencode.ai/config.json";
1545
+ const plugins = config.plugin || [];
1546
+ config.plugin = [...plugins.filter((entry) => specName(entry) !== context.name), spec];
1547
+ await writeJson(path, config);
1548
+ }
1549
+ }
1550
+ ];
1551
+ },
1552
+ async listInstalled(scope, cwd) {
1553
+ const config = await readJson(openCodeConfigPath(scope, cwd));
1554
+ return (config.plugin || []).map(specName);
1555
+ },
1556
+ async remove(pluginId, scope, cwd) {
1557
+ const path = openCodeConfigPath(scope, cwd);
1558
+ const config = await readJson(path);
1559
+ const name = splitPluginId(pluginId)?.name ?? pluginId;
1560
+ const plugins = config.plugin || [];
1561
+ const remaining = plugins.filter((entry) => specName(entry) !== name);
1562
+ if (remaining.length === plugins.length) return false;
1563
+ config.plugin = remaining;
1564
+ await writeJson(path, config);
1565
+ return true;
1566
+ }
1567
+ };
1568
+ var GEMINI_CONFIG_DIR = join4(home, ".gemini", "config");
1569
+ var GEMINI_PLUGINS_DIR = join4(GEMINI_CONFIG_DIR, "plugins");
1570
+ var GEMINI_IMPORT_MANIFEST_PATH = join4(GEMINI_CONFIG_DIR, "import_manifest.json");
1571
+ var antigravityStore = {
1572
+ manifestPaths: manifestsFor(),
1573
+ userGlobal: true,
1574
+ cacheRoot: GEMINI_PLUGINS_DIR,
1575
+ // Antigravity files plugins by name only — it has no marketplace dimension.
1576
+ cachePath: (_marketplace, name) => resolveContained(GEMINI_PLUGINS_DIR, name),
1577
+ /**
1578
+ * Antigravity discovers a plugin by a `plugin.json` at the tree ROOT, and
1579
+ * `.claude-plugin/plugin.json` is not a substitute: the cross-tool matrix
1580
+ * marks it a migration path (`agy plugin import claude`), never a reader.
1581
+ * `agy plugin validate` on a Claude-manifest-only tree answers
1582
+ * "missing plugin.json"; with a root manifest it answers "skills: 1 processed".
1583
+ *
1584
+ * Most plugins in the wild ship only the Claude manifest, so without this the
1585
+ * install lands a directory Antigravity refuses — reporting success while the
1586
+ * plugin does nothing. A tree that already carries its own root manifest
1587
+ * (compound-engineering does) is left exactly as it is.
1588
+ */
1589
+ async prepareTree(contentPath, { name, version, item }) {
1590
+ const manifestPath = join4(contentPath, "plugin.json");
1591
+ if (await exists(manifestPath)) return;
1592
+ await writeJson(manifestPath, {
1593
+ name,
1594
+ version,
1595
+ ...item.description ? { description: item.description } : {}
1596
+ });
1597
+ },
1598
+ mutations: (context) => [
1599
+ {
1600
+ path: GEMINI_IMPORT_MANIFEST_PATH,
1601
+ detail: `imports[] += "${context.name}"`,
1602
+ apply: async () => {
1603
+ const manifest = await readJson(GEMINI_IMPORT_MANIFEST_PATH);
1604
+ const imports = manifest.imports || [];
1605
+ manifest.imports = [
1606
+ ...imports.filter((entry) => entry.name !== context.name),
1607
+ {
1608
+ name: context.name,
1609
+ source: "seedr",
1610
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
1611
+ components: ["skills", "hooks"]
1612
+ }
1613
+ ];
1614
+ await writeJson(GEMINI_IMPORT_MANIFEST_PATH, manifest);
1615
+ }
1616
+ }
1617
+ ],
1618
+ async listInstalled() {
1619
+ const manifest = await readJson(GEMINI_IMPORT_MANIFEST_PATH);
1620
+ return (manifest.imports || []).map((entry) => entry.name);
1621
+ },
1622
+ async remove(pluginId) {
1623
+ const name = splitPluginId(pluginId)?.name ?? pluginId;
1624
+ const manifest = await readJson(GEMINI_IMPORT_MANIFEST_PATH);
1625
+ const imports = manifest.imports || [];
1626
+ const remaining = imports.filter((entry) => entry.name !== name);
1627
+ if (remaining.length === imports.length) return false;
1628
+ manifest.imports = remaining;
1629
+ await writeJson(GEMINI_IMPORT_MANIFEST_PATH, manifest);
1630
+ try {
1631
+ await removePathEntry(await resolveContained(GEMINI_PLUGINS_DIR, name));
1632
+ } catch {
1633
+ }
1634
+ return true;
1635
+ }
1636
+ };
1637
+ var GITHUB_REPO_SLUG_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
1638
+ function githubRepoOf(url) {
1639
+ if (!url) return null;
1640
+ const parsed = parseGitHubRepo(url);
1641
+ if (!parsed) return null;
1642
+ const repo = `${parsed.owner}/${parsed.repo}`;
1643
+ if (!GITHUB_REPO_SLUG_PATTERN.test(repo) || repo.includes("..")) return null;
1644
+ return repo;
1645
+ }
1646
+ function marketplaceRepo(item) {
1647
+ return githubRepoOf(item.marketplaceRef?.url ?? item.externalUrl);
1648
+ }
1649
+ var STORES = {
1650
+ claude: claudeStore,
1651
+ copilot: copilotStore,
1652
+ codex: codexStore,
1653
+ opencode: openCodeStore,
1654
+ antigravity: antigravityStore,
1655
+ gemini: antigravityStore
1656
+ };
1657
+ function pluginStoreFor(agent) {
1658
+ const store = STORES[canonicalAgent(agent) ?? agent];
1659
+ if (!store || !isTypeSupported("plugin", agent)) {
1660
+ throw new Error(`Plugins are not supported for ${CODING_AGENTS[agent]?.name ?? agent}`);
1661
+ }
1662
+ return store;
1663
+ }
1664
+
1665
+ // src/handlers/plugin.ts
1666
+ var SLUG_LABEL3 = "plugin slug";
1296
1667
  function resolveMarketplaceName(item) {
1297
1668
  const marketplace = item.marketplace || item.author?.name || "seedr";
1298
1669
  assertSafePathSegment(marketplace, "plugin marketplace");
@@ -1305,48 +1676,29 @@ function resolvePluginIdentity(item, pluginJson) {
1305
1676
  assertSafePathSegment(version, "plugin version");
1306
1677
  return { name, version };
1307
1678
  }
1308
- function extractGitHubRepo(url) {
1309
- const parsed = parseGitHubRepo(url);
1310
- if (!parsed) return null;
1311
- const slug = `${parsed.owner}/${parsed.repo}`;
1312
- if (!GITHUB_REPO_SLUG_PATTERN.test(slug) || slug.includes("..")) return null;
1313
- return slug;
1314
- }
1315
- function marketplaceRepoUrl(item) {
1316
- return item.marketplaceRef?.url ?? item.externalUrl;
1679
+ async function readManifestFromDir(dir, manifestPaths) {
1680
+ for (const manifest of manifestPaths) {
1681
+ const path = join5(dir, ...manifest.split("/"));
1682
+ if (await exists(path)) return readJson(path);
1683
+ }
1684
+ return {};
1317
1685
  }
1318
- async function ensureMarketplaceRegistered(marketplace, item) {
1319
- const known = await readJson(
1320
- KNOWN_MARKETPLACES_PATH
1321
- );
1322
- if (known[marketplace]) return;
1323
- const sourceUrl = marketplaceRepoUrl(item);
1324
- const repo = sourceUrl ? extractGitHubRepo(sourceUrl) : null;
1325
- if (!repo) return;
1326
- const installLocation = await resolveContained(MARKETPLACES_DIR, marketplace);
1327
- await mkdir2(MARKETPLACES_DIR, { recursive: true });
1328
- if (!await exists(installLocation)) {
1329
- const execFileAsync = promisify(execFile);
1330
- await execFileAsync("git", [
1331
- "clone",
1332
- "--depth",
1333
- "1",
1334
- `https://github.com/${repo}.git`,
1335
- installLocation
1336
- ]);
1337
- }
1338
- known[marketplace] = {
1339
- source: { source: "github", repo },
1340
- installLocation,
1341
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1342
- };
1343
- await writeJson(KNOWN_MARKETPLACES_PATH, known);
1686
+ async function readManifestForPlan(item, manifestPaths) {
1687
+ const sourcePath = getItemSourcePath(item);
1688
+ if (sourcePath) return readManifestFromDir(sourcePath, manifestPaths);
1689
+ for (const manifest of manifestPaths) {
1690
+ try {
1691
+ return JSON.parse(await fetchItemFile(item, manifest));
1692
+ } catch {
1693
+ }
1694
+ }
1695
+ return {};
1344
1696
  }
1345
- async function createStagingDir(slug) {
1346
- const tmpRoot = await resolveContained(PLUGINS_CACHE_DIR, ".tmp");
1347
- await mkdir2(tmpRoot, { recursive: true });
1348
- const staging = await mkdtemp2(join4(tmpRoot, `${slug}-`));
1349
- return resolveContained(PLUGINS_CACHE_DIR, relative3(PLUGINS_CACHE_DIR, staging));
1697
+ async function createStagingDir(cacheRoot, slug) {
1698
+ const tmpRoot = await resolveContained(cacheRoot, ".tmp");
1699
+ await mkdir3(tmpRoot, { recursive: true });
1700
+ const staging = await mkdtemp2(join5(tmpRoot, `${slug}-`));
1701
+ return resolveContained(cacheRoot, relative3(cacheRoot, staging));
1350
1702
  }
1351
1703
  async function stageContent(item, contentPath) {
1352
1704
  const sourcePath = getItemSourcePath(item);
@@ -1365,37 +1717,6 @@ function resolveGitCommitSha(item, fetched) {
1365
1717
  }
1366
1718
  return sha;
1367
1719
  }
1368
- function isScopeEntry(entry, scope, cwd) {
1369
- return entry.scope === scope && (scope === "user" || entry.projectPath === cwd);
1370
- }
1371
- async function writeInstalledEntry(params) {
1372
- const { cachePath, pluginId, version, gitCommitSha, scope, cwd } = params;
1373
- const now = (/* @__PURE__ */ new Date()).toISOString();
1374
- const registry = await readJson(INSTALLED_PLUGINS_PATH);
1375
- registry.version = registry.version || 2;
1376
- registry.plugins = registry.plugins || {};
1377
- const installInfo = {
1378
- scope,
1379
- ...scope !== "user" ? { projectPath: cwd } : {},
1380
- installPath: cachePath,
1381
- version,
1382
- installedAt: now,
1383
- lastUpdated: now,
1384
- gitCommitSha
1385
- };
1386
- const existingEntries = registry.plugins[pluginId] || [];
1387
- registry.plugins[pluginId] = [
1388
- ...existingEntries.filter((entry) => !isScopeEntry(entry, scope, cwd)),
1389
- installInfo
1390
- ];
1391
- await writeJson(INSTALLED_PLUGINS_PATH, registry);
1392
- }
1393
- async function enableInSettings(settingsPath, pluginId) {
1394
- const settings = await readJson(settingsPath);
1395
- settings.enabledPlugins = settings.enabledPlugins || {};
1396
- settings.enabledPlugins[pluginId] = true;
1397
- await writeJson(settingsPath, settings);
1398
- }
1399
1720
  async function rollback(undo, error2) {
1400
1721
  const failures = [];
1401
1722
  for (const step2 of undo.reverse()) {
@@ -1411,70 +1732,78 @@ async function rollback(undo, error2) {
1411
1732
  }
1412
1733
  throw error2;
1413
1734
  }
1414
- async function commitInstall(params) {
1415
- const { contentPath, cachePath, pluginId, scope, cwd } = params;
1735
+ async function commitInstall(contentPath, cachePath, mutations) {
1416
1736
  const undo = [];
1417
1737
  try {
1418
- await removePathEntry(cachePath);
1419
- await moveDirectory(contentPath, cachePath);
1420
- undo.push(() => removePathEntry(cachePath).then(() => void 0));
1421
- const registrySnapshot = await snapshotFile(INSTALLED_PLUGINS_PATH);
1422
- await writeInstalledEntry(params);
1423
- undo.push(() => restoreFile(INSTALLED_PLUGINS_PATH, registrySnapshot));
1424
- const settingsPath = getSettingsPath(scope, cwd);
1425
- const settingsSnapshot = await snapshotFile(settingsPath);
1426
- await enableInSettings(settingsPath, pluginId);
1427
- undo.push(() => restoreFile(settingsPath, settingsSnapshot));
1738
+ if (contentPath && cachePath) {
1739
+ await removePathEntry(cachePath);
1740
+ await moveDirectory(contentPath, cachePath);
1741
+ undo.push(() => removePathEntry(cachePath).then(() => void 0));
1742
+ }
1743
+ for (const mutation of mutations) {
1744
+ const snapshot = await snapshotFile(mutation.path);
1745
+ await mutation.apply();
1746
+ undo.push(() => restoreFile(mutation.path, snapshot));
1747
+ }
1428
1748
  } catch (error2) {
1429
1749
  await rollback(undo, error2);
1430
1750
  }
1431
1751
  }
1432
1752
  async function installPluginForAgent(item, agent, scope, _method, force, cwd) {
1433
- const spinner = ora6(
1434
- `Installing ${item.name} for ${CODING_AGENTS[agent].name}...`
1435
- ).start();
1753
+ const spinner = ora6(`Installing ${item.name} for ${CODING_AGENTS[agent].name}...`).start();
1436
1754
  try {
1437
- if (agent !== "claude") {
1438
- throw new Error(CLAUDE_ONLY_ERROR3);
1439
- }
1755
+ const store = pluginStoreFor(agent);
1440
1756
  assertValidSlug(item.slug, SLUG_LABEL3);
1441
1757
  const marketplace = resolveMarketplaceName(item);
1442
- const staging = await createStagingDir(item.slug);
1758
+ const cacheRoot = store.cacheRoot;
1759
+ const staging = cacheRoot ? await createStagingDir(cacheRoot, item.slug) : null;
1443
1760
  try {
1444
- const contentPath = join4(staging, "content");
1445
- const fetched = await stageContent(item, contentPath);
1446
- const pluginJson = await readJson(join4(contentPath, ...PLUGIN_JSON_PATH.split("/")));
1761
+ const contentPath = staging ? join5(staging, "content") : null;
1762
+ const fetched = contentPath ? await stageContent(item, contentPath) : null;
1763
+ const pluginJson = contentPath ? await readManifestFromDir(contentPath, store.manifestPaths) : await readManifestForPlan(item, store.manifestPaths);
1447
1764
  const { name, version } = resolvePluginIdentity(item, pluginJson);
1765
+ if (contentPath && store.prepareTree) {
1766
+ await store.prepareTree(contentPath, { name, version, item });
1767
+ }
1448
1768
  const gitCommitSha = resolveGitCommitSha(item, fetched);
1449
1769
  const pluginId = getPluginId(name, marketplace);
1450
- await ensureMarketplaceRegistered(marketplace, item);
1451
- const cachePath = await resolveContained(PLUGINS_CACHE_DIR, marketplace, name, version);
1452
- await assertOverwritable(cachePath, force);
1453
- await commitInstall({ contentPath, cachePath, pluginId, version, gitCommitSha, scope, cwd });
1454
- spinner.succeed(
1455
- brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`)
1456
- );
1457
- return { agent, success: true, path: cachePath };
1770
+ const cachePath = await store.cachePath(marketplace, name, version);
1771
+ const marketplaceMutations = store.ensureMarketplace ? await store.ensureMarketplace(marketplace, item) : [];
1772
+ const context = {
1773
+ item,
1774
+ marketplace,
1775
+ name,
1776
+ version,
1777
+ pluginId,
1778
+ cachePath,
1779
+ gitCommitSha,
1780
+ scope,
1781
+ cwd
1782
+ };
1783
+ if (cachePath) await assertOverwritable(cachePath, force);
1784
+ await commitInstall(contentPath, cachePath, [
1785
+ ...marketplaceMutations,
1786
+ ...store.mutations(context)
1787
+ ]);
1788
+ spinner.succeed(brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`));
1789
+ return { agent, success: true, path: cachePath ?? "" };
1458
1790
  } finally {
1459
- await rm2(staging, { recursive: true, force: true });
1791
+ if (staging) await rm2(staging, { recursive: true, force: true });
1460
1792
  }
1461
1793
  } catch (error2) {
1462
1794
  const errorMsg = error2 instanceof Error ? error2.message : "Unknown error";
1463
- spinner.fail(
1464
- chalk7.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${errorMsg}`)
1465
- );
1795
+ spinner.fail(chalk7.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${errorMsg}`));
1466
1796
  return { agent, success: false, path: "", error: errorMsg };
1467
1797
  }
1468
1798
  }
1469
1799
  async function installPlugin(item, agents, scope, method, force, cwd = process.cwd()) {
1470
1800
  const results = [];
1471
1801
  for (const agent of agents) {
1472
- const result = await installPluginForAgent(item, agent, scope, method, force, cwd);
1473
- results.push(result);
1802
+ results.push(await installPluginForAgent(item, agent, scope, method, force, cwd));
1474
1803
  }
1475
1804
  return results;
1476
1805
  }
1477
- async function findInstalledPluginId(registry, slug) {
1806
+ async function findInstalledPluginId(installed, slug) {
1478
1807
  let registryMarketplace = null;
1479
1808
  try {
1480
1809
  const item = await getItem(slug, "plugin");
@@ -1484,70 +1813,34 @@ async function findInstalledPluginId(registry, slug) {
1484
1813
  }
1485
1814
  if (registryMarketplace !== null) {
1486
1815
  const exact = getPluginId(slug, registryMarketplace);
1487
- return exact in registry.plugins ? exact : null;
1816
+ if (installed.includes(exact)) return exact;
1817
+ return installed.includes(slug) ? slug : null;
1488
1818
  }
1489
- const candidates = Object.keys(registry.plugins).filter((id) => splitPluginId(id)?.name === slug);
1819
+ const candidates = installed.filter((id) => (splitPluginId(id)?.name ?? id) === slug);
1490
1820
  return candidates.length === 1 ? candidates[0] : null;
1491
1821
  }
1492
- async function removeOrphanedCache(installPath, registry) {
1493
- const stillReferenced = Object.values(registry.plugins).some(
1494
- (entries) => entries.some((entry) => entry.installPath === installPath)
1495
- );
1496
- if (stillReferenced) return;
1497
- let contained;
1822
+ async function uninstallPlugin(slug, agent, scope, cwd = process.cwd()) {
1823
+ assertValidSlug(slug, SLUG_LABEL3);
1824
+ let store;
1498
1825
  try {
1499
- contained = await resolveContained(PLUGINS_CACHE_DIR, installPath);
1826
+ store = pluginStoreFor(agent);
1500
1827
  } catch {
1501
- return;
1828
+ return false;
1502
1829
  }
1503
- await removePathEntry(contained);
1504
- }
1505
- async function uninstallPlugin(slug, agent, scope, cwd = process.cwd()) {
1506
- assertValidSlug(slug, SLUG_LABEL3);
1507
- if (agent !== "claude") return false;
1508
- const registry = await readJson(INSTALLED_PLUGINS_PATH);
1509
- if (!registry.plugins) return false;
1510
- const pluginId = await findInstalledPluginId(registry, slug);
1830
+ const installed = await store.listInstalled(scope, cwd);
1831
+ const pluginId = await findInstalledPluginId(installed, slug);
1511
1832
  if (!pluginId) return false;
1512
- const entries = registry.plugins[pluginId] || [];
1513
- const removedEntries = entries.filter((entry) => isScopeEntry(entry, scope, cwd));
1514
- if (removedEntries.length === 0) return false;
1515
- const remaining = entries.filter((entry) => !isScopeEntry(entry, scope, cwd));
1516
- if (remaining.length === 0) {
1517
- delete registry.plugins[pluginId];
1518
- } else {
1519
- registry.plugins[pluginId] = remaining;
1520
- }
1521
- await writeJson(INSTALLED_PLUGINS_PATH, registry);
1522
- const settingsPath = getSettingsPath(scope, cwd);
1523
- const settings = await readJson(settingsPath);
1524
- if (settings.enabledPlugins && pluginId in settings.enabledPlugins) {
1525
- delete settings.enabledPlugins[pluginId];
1526
- await writeJson(settingsPath, settings);
1527
- }
1528
- for (const installPath of new Set(removedEntries.map((entry) => entry.installPath))) {
1529
- await removeOrphanedCache(installPath, registry);
1530
- }
1531
- return true;
1833
+ return store.remove(pluginId, scope, cwd);
1532
1834
  }
1533
1835
  async function getInstalledPlugins(agent, scope, cwd = process.cwd()) {
1534
- if (agent !== "claude") return [];
1535
- const registry = await readJson(INSTALLED_PLUGINS_PATH);
1536
- if (!registry.plugins) return [];
1537
- const installed = [];
1538
- for (const [pluginId, entries] of Object.entries(registry.plugins)) {
1539
- if (entries.some((entry) => isScopeEntry(entry, scope, cwd))) {
1540
- installed.push(splitPluginId(pluginId)?.name ?? pluginId);
1541
- }
1542
- }
1543
- return installed;
1544
- }
1545
- async function readPluginJsonForPlan(item) {
1546
- const sourcePath = getItemSourcePath(item);
1547
- if (sourcePath) {
1548
- return readJson(join4(sourcePath, ...PLUGIN_JSON_PATH.split("/")));
1836
+ let store;
1837
+ try {
1838
+ store = pluginStoreFor(agent);
1839
+ } catch {
1840
+ return [];
1549
1841
  }
1550
- return JSON.parse(await fetchItemFile(item, PLUGIN_JSON_PATH));
1842
+ const installed = await store.listInstalled(scope, cwd);
1843
+ return installed.map((pluginId) => splitPluginId(pluginId)?.name ?? pluginId);
1551
1844
  }
1552
1845
  async function kindFor(path) {
1553
1846
  return await exists(path) ? "modify" : "create";
@@ -1555,40 +1848,53 @@ async function kindFor(path) {
1555
1848
  async function planPlugin(item, agents, scope, _method, cwd) {
1556
1849
  const changes = [];
1557
1850
  for (const agent of agents) {
1558
- if (agent !== "claude") throw new Error(CLAUDE_ONLY_ERROR3);
1851
+ const store = pluginStoreFor(agent);
1559
1852
  assertValidSlug(item.slug, SLUG_LABEL3);
1560
1853
  const marketplace = resolveMarketplaceName(item);
1561
- const { name, version } = resolvePluginIdentity(item, await readPluginJsonForPlan(item));
1854
+ const { name, version } = resolvePluginIdentity(
1855
+ item,
1856
+ await readManifestForPlan(item, store.manifestPaths)
1857
+ );
1562
1858
  const pluginId = getPluginId(name, marketplace);
1563
- const cachePath = await resolveContained(PLUGINS_CACHE_DIR, marketplace, name, version);
1564
- const settingsPath = getSettingsPath(scope, cwd);
1565
- changes.push(
1566
- { agent, kind: await kindFor(cachePath), path: cachePath, detail: "plugin files (digest-verified download)" },
1567
- {
1859
+ const cachePath = await store.cachePath(marketplace, name, version);
1860
+ resolveGitCommitSha(item, null);
1861
+ if (cachePath) {
1862
+ changes.push({
1568
1863
  agent,
1569
- kind: await kindFor(INSTALLED_PLUGINS_PATH),
1570
- path: INSTALLED_PLUGINS_PATH,
1571
- detail: `plugins["${pluginId}"] entry for ${scope} scope`
1572
- }
1573
- );
1574
- const known = await readJson(KNOWN_MARKETPLACES_PATH);
1575
- if (!known[marketplace]) {
1576
- const sourceUrl = marketplaceRepoUrl(item);
1577
- const repo = sourceUrl ? extractGitHubRepo(sourceUrl) : null;
1578
- if (repo) {
1579
- const cloneDir = await resolveContained(MARKETPLACES_DIR, marketplace);
1580
- changes.push({ agent, kind: await kindFor(KNOWN_MARKETPLACES_PATH), path: KNOWN_MARKETPLACES_PATH, detail: `marketplace "${marketplace}"` });
1581
- if (!await exists(cloneDir)) {
1582
- changes.push({ agent, kind: "create", path: cloneDir, detail: `git clone https://github.com/${repo}.git` });
1583
- }
1864
+ kind: await kindFor(cachePath),
1865
+ path: cachePath,
1866
+ detail: "plugin files (digest-verified download)"
1867
+ });
1868
+ }
1869
+ const marketplaceMutations = store.ensureMarketplace ? await store.ensureMarketplace(marketplace, item) : [];
1870
+ const context = {
1871
+ item,
1872
+ marketplace,
1873
+ name,
1874
+ version,
1875
+ pluginId,
1876
+ cachePath,
1877
+ gitCommitSha: resolveGitCommitSha(item, null),
1878
+ scope,
1879
+ cwd
1880
+ };
1881
+ for (const mutation of [...marketplaceMutations, ...store.mutations(context)]) {
1882
+ changes.push({
1883
+ agent,
1884
+ kind: await kindFor(mutation.path),
1885
+ path: mutation.path,
1886
+ detail: store.userGlobal ? `${mutation.detail} \u2014 user-global, ${CODING_AGENTS[agent].name} has no project-scoped plugin store` : mutation.detail
1887
+ });
1888
+ if (mutation.creates) {
1889
+ changes.push({ agent, kind: "create", ...mutation.creates });
1584
1890
  }
1585
1891
  }
1586
- changes.push({ agent, kind: await kindFor(settingsPath), path: settingsPath, detail: `enabledPlugins["${pluginId}"] = true` });
1587
1892
  }
1588
1893
  return changes;
1589
1894
  }
1590
1895
  var pluginHandler = {
1591
1896
  type: "plugin",
1897
+ honoursMethod: false,
1592
1898
  async install(item, agents, scope, method, force, cwd) {
1593
1899
  return installPlugin(item, agents, scope, method, force, cwd);
1594
1900
  },
@@ -1601,6 +1907,216 @@ var pluginHandler = {
1601
1907
  plan: planPlugin
1602
1908
  };
1603
1909
 
1910
+ // src/handlers/rule.ts
1911
+ import { dirname as dirname3 } from "path";
1912
+ import { readdir as readdir3, readFile as readFile4, writeFile as writeFile2, mkdir as mkdir4 } from "fs/promises";
1913
+ import chalk8 from "chalk";
1914
+ import ora7 from "ora";
1915
+
1916
+ // src/handlers/ruleTargets.ts
1917
+ import { homedir as homedir5 } from "os";
1918
+ import { join as join6 } from "path";
1919
+ var home2 = homedir5();
1920
+ var markdownFile = (slug) => `${slug}.md`;
1921
+ var claudeTarget = {
1922
+ kind: "file",
1923
+ dir: (scope, cwd) => join6(scope === "user" ? claudeUserRoot() : join6(cwd, ".claude"), "rules"),
1924
+ fileName: markdownFile,
1925
+ keepsFrontmatter: true
1926
+ };
1927
+ var antigravityTarget = {
1928
+ kind: "file",
1929
+ dir: (scope, cwd) => join6(scope === "user" ? join6(home2, ".gemini", "config") : join6(cwd, ".agents"), "rules"),
1930
+ fileName: markdownFile,
1931
+ keepsFrontmatter: true
1932
+ };
1933
+ var copilotTarget = {
1934
+ kind: "file",
1935
+ dir: (scope, cwd) => join6(scope === "user" ? copilotUserRoot() : join6(cwd, ".github"), "instructions"),
1936
+ fileName: (slug) => `${slug}.instructions.md`,
1937
+ keepsFrontmatter: true
1938
+ };
1939
+ var codexTarget = {
1940
+ kind: "section",
1941
+ file: (scope, cwd) => scope === "user" ? join6(codexUserRoot(), "AGENTS.md") : join6(cwd, "AGENTS.md")
1942
+ };
1943
+ var openCodeTarget = {
1944
+ kind: "section",
1945
+ file: (scope, cwd) => scope === "user" ? join6(openCodeUserConfigDir(), "AGENTS.md") : join6(cwd, "AGENTS.md")
1946
+ };
1947
+ var TARGETS = {
1948
+ claude: claudeTarget,
1949
+ antigravity: antigravityTarget,
1950
+ gemini: antigravityTarget,
1951
+ copilot: copilotTarget,
1952
+ codex: codexTarget,
1953
+ opencode: openCodeTarget
1954
+ };
1955
+ function ruleTargetFor(agent) {
1956
+ const target = TARGETS[canonicalAgent(agent) ?? agent];
1957
+ if (!target || !isTypeSupported("rule", agent)) {
1958
+ throw new Error(`Rules are not supported for ${CODING_AGENTS[agent]?.name ?? agent}`);
1959
+ }
1960
+ return target;
1961
+ }
1962
+ var sectionStart = (slug) => `<!-- seedr:rule:${slug} -->`;
1963
+ var sectionEnd = (slug) => `<!-- /seedr:rule:${slug} -->`;
1964
+ function sectionPattern(slug) {
1965
+ const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1966
+ return new RegExp(`\\n*<!-- seedr:rule:${escaped} -->[\\s\\S]*?<!-- /seedr:rule:${escaped} -->\\n*`, "g");
1967
+ }
1968
+ function stripFrontmatter(content) {
1969
+ const match = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/.exec(content);
1970
+ return match ? content.slice(match[0].length) : content;
1971
+ }
1972
+ function upsertSection(document, slug, content) {
1973
+ const block = `${sectionStart(slug)}
1974
+ ${stripFrontmatter(content).trim()}
1975
+ ${sectionEnd(slug)}`;
1976
+ const existing = sectionPattern(slug);
1977
+ if (existing.test(document)) {
1978
+ return document.replace(sectionPattern(slug), `
1979
+
1980
+ ${block}
1981
+ `);
1982
+ }
1983
+ const base = document.trimEnd();
1984
+ return base.length > 0 ? `${base}
1985
+
1986
+ ${block}
1987
+ ` : `${block}
1988
+ `;
1989
+ }
1990
+ function removeSection(document, slug) {
1991
+ if (!sectionPattern(slug).test(document)) return null;
1992
+ const stripped = document.replace(sectionPattern(slug), "\n\n").trimEnd();
1993
+ return stripped.length > 0 ? `${stripped}
1994
+ ` : "";
1995
+ }
1996
+ function listSections(document) {
1997
+ return [...document.matchAll(/<!-- seedr:rule:([^\s>]+) -->/g)].map((match) => match[1]);
1998
+ }
1999
+
2000
+ // src/handlers/rule.ts
2001
+ var SLUG_LABEL4 = "rule slug";
2002
+ var RULE_FILE = "rule.md";
2003
+ async function readTextOrEmpty3(path) {
2004
+ try {
2005
+ return await readFile4(path, "utf-8");
2006
+ } catch {
2007
+ return "";
2008
+ }
2009
+ }
2010
+ async function readRuleContent(item) {
2011
+ const sourcePath = getItemSourcePath(item);
2012
+ if (sourcePath) {
2013
+ const local = await readTextOrEmpty3(`${sourcePath}/${RULE_FILE}`);
2014
+ if (local) return local;
2015
+ }
2016
+ return fetchItemFile(item, RULE_FILE);
2017
+ }
2018
+ async function destinationFor(target, slug, scope, cwd) {
2019
+ if (target.kind === "section") return target.file(scope, cwd);
2020
+ const dir = target.dir(scope, cwd);
2021
+ return resolveContained(dir, target.fileName(slug));
2022
+ }
2023
+ async function installRuleForAgent(item, agent, scope, force, cwd, content) {
2024
+ const spinner = ora7(`Installing ${item.name} for ${CODING_AGENTS[agent].name}...`).start();
2025
+ try {
2026
+ const target = ruleTargetFor(agent);
2027
+ const destination = await destinationFor(target, item.slug, scope, cwd);
2028
+ await mkdir4(dirname3(destination), { recursive: true });
2029
+ if (target.kind === "file") {
2030
+ await assertOverwritable(destination, force);
2031
+ await writeFile2(
2032
+ destination,
2033
+ target.keepsFrontmatter ? content : stripFrontmatter(content),
2034
+ "utf-8"
2035
+ );
2036
+ } else {
2037
+ const document = await readTextOrEmpty3(destination);
2038
+ await writeFile2(destination, upsertSection(document, item.slug, content), "utf-8");
2039
+ }
2040
+ spinner.succeed(brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`));
2041
+ return { agent, success: true, path: destination };
2042
+ } catch (error2) {
2043
+ const message = error2 instanceof Error ? error2.message : "Unknown error";
2044
+ spinner.fail(chalk8.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${message}`));
2045
+ return { agent, success: false, path: "", error: message };
2046
+ }
2047
+ }
2048
+ async function installRule(item, agents, scope, _method, force, cwd = process.cwd()) {
2049
+ assertValidSlug(item.slug, SLUG_LABEL4);
2050
+ const content = await readRuleContent(item);
2051
+ const results = [];
2052
+ for (const agent of agents) {
2053
+ results.push(await installRuleForAgent(item, agent, scope, force, cwd, content));
2054
+ }
2055
+ return results;
2056
+ }
2057
+ async function uninstallRule(slug, agent, scope, cwd = process.cwd()) {
2058
+ assertValidSlug(slug, SLUG_LABEL4);
2059
+ let target;
2060
+ try {
2061
+ target = ruleTargetFor(agent);
2062
+ } catch {
2063
+ return false;
2064
+ }
2065
+ const destination = await destinationFor(target, slug, scope, cwd);
2066
+ if (target.kind === "file") return removePathEntry(destination);
2067
+ const document = await readTextOrEmpty3(destination);
2068
+ const stripped = removeSection(document, slug);
2069
+ if (stripped === null) return false;
2070
+ await writeFile2(destination, stripped, "utf-8");
2071
+ return true;
2072
+ }
2073
+ async function getInstalledRules(agent, scope, cwd = process.cwd()) {
2074
+ let target;
2075
+ try {
2076
+ target = ruleTargetFor(agent);
2077
+ } catch {
2078
+ return [];
2079
+ }
2080
+ if (target.kind === "section") {
2081
+ return listSections(await readTextOrEmpty3(target.file(scope, cwd)));
2082
+ }
2083
+ const dir = target.dir(scope, cwd);
2084
+ if (!await exists(dir)) return [];
2085
+ const suffix = target.fileName("");
2086
+ const entries = await readdir3(dir, { withFileTypes: true });
2087
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(suffix) && !entry.name.startsWith(".")).map((entry) => entry.name.slice(0, -suffix.length));
2088
+ }
2089
+ async function planRule(item, agents, scope, _method, cwd) {
2090
+ assertValidSlug(item.slug, SLUG_LABEL4);
2091
+ const changes = [];
2092
+ for (const agent of agents) {
2093
+ const target = ruleTargetFor(agent);
2094
+ const destination = await destinationFor(target, item.slug, scope, cwd);
2095
+ const present = await exists(destination);
2096
+ changes.push({
2097
+ agent,
2098
+ kind: present ? "modify" : "create",
2099
+ path: destination,
2100
+ detail: target.kind === "file" ? "rule file" : `merged section <!-- seedr:rule:${item.slug} --> (the rest of the file is untouched)`
2101
+ });
2102
+ }
2103
+ return changes;
2104
+ }
2105
+ var ruleHandler = {
2106
+ type: "rule",
2107
+ honoursMethod: false,
2108
+ async install(item, agents, scope, method, force, cwd) {
2109
+ return installRule(item, agents, scope, method, force, cwd);
2110
+ },
2111
+ async uninstall(slug, agent, scope, cwd) {
2112
+ return uninstallRule(slug, agent, scope, cwd);
2113
+ },
2114
+ async listInstalled(agent, scope, cwd) {
2115
+ return getInstalledRules(agent, scope, cwd);
2116
+ },
2117
+ plan: planRule
2118
+ };
2119
+
1604
2120
  // src/handlers/index.ts
1605
2121
  registerHandler(skillHandler);
1606
2122
  registerHandler(agentHandler);
@@ -1609,6 +2125,7 @@ registerHandler(hookHandler);
1609
2125
  registerHandler(mcpHandler);
1610
2126
  registerHandler(settingsHandler);
1611
2127
  registerHandler(pluginHandler);
2128
+ registerHandler(ruleHandler);
1612
2129
 
1613
2130
  // src/commands/add.ts
1614
2131
  var CANCELLED_MESSAGE = "Operation cancelled";
@@ -1689,8 +2206,8 @@ function formatPlan(changes) {
1689
2206
  for (const [label, group] of groups) {
1690
2207
  lines.push(` ${label}:`);
1691
2208
  for (const change of group) {
1692
- const detail = change.detail ? ` ${chalk8.gray(`\u2014 ${change.detail}`)}` : "";
1693
- lines.push(` ${chalk8.gray(`[${KIND_LABEL[change.kind]}]`)} ${change.path}${detail}`);
2209
+ const detail = change.detail ? ` ${chalk9.gray(`\u2014 ${change.detail}`)}` : "";
2210
+ lines.push(` ${chalk9.gray(`[${KIND_LABEL[change.kind]}]`)} ${change.path}${detail}`);
1694
2211
  }
1695
2212
  }
1696
2213
  return lines;
@@ -1800,13 +2317,13 @@ function printInstallSummary(results) {
1800
2317
  if (successful.length > 0) {
1801
2318
  success(`Installed for ${successful.length} agent(s)`);
1802
2319
  for (const r of successful) {
1803
- console.log(chalk8.gray(` \u2192 ${r.path}`));
2320
+ console.log(chalk9.gray(` \u2192 ${r.path}`));
1804
2321
  }
1805
2322
  }
1806
2323
  if (failed.length > 0) {
1807
2324
  error(`Failed for ${failed.length} agent(s)`);
1808
2325
  for (const r of failed) {
1809
- console.log(chalk8.red(` \xD7 ${r.agent}: ${r.error}`));
2326
+ console.log(chalk9.red(` \xD7 ${r.agent}: ${r.error}`));
1810
2327
  }
1811
2328
  }
1812
2329
  return exitCode;
@@ -1820,7 +2337,7 @@ async function runAdd(name, options, cwd = process.cwd()) {
1820
2337
  const item = await resolveItem(name, options.type);
1821
2338
  if (item === CANCELLED) return 0;
1822
2339
  if (!item) return 1;
1823
- step(`Selected: ${brand(item.name)} ${chalk8.gray(`(${item.type})`)} ${chalk8.gray(`- ${item.description}`)}`);
2340
+ step(`Selected: ${brand(item.name)} ${chalk9.gray(`(${item.type})`)} ${chalk9.gray(`- ${item.description}`)}`);
1824
2341
  const handler = getHandler(item.type);
1825
2342
  if (!handler) {
1826
2343
  error(`No handler found for type "${item.type}"`);
@@ -1835,10 +2352,24 @@ async function runAdd(name, options, cwd = process.cwd()) {
1835
2352
  step(`Scope: ${brand(scope)}`);
1836
2353
  const method = await chooseMethod(options, item, agents, scope, cwd);
1837
2354
  if (method === CANCELLED) return 0;
1838
- step(`Method: ${brand(method)}`);
2355
+ if (method === "symlink" && handler.honoursMethod === false) {
2356
+ step(`Method: ${brand("copy")} (${item.type} content cannot be symlinked)`);
2357
+ } else {
2358
+ step(`Method: ${brand(method)}`);
2359
+ }
1839
2360
  if (options.dryRun) {
1840
2361
  info("Dry run - no files will be written");
1841
- printPlan("Would write:", await planInstall(handler, item, agents, scope, method, cwd));
2362
+ const planned = await planInstall(handler, item, agents, scope, method, cwd);
2363
+ printPlan("Would write:", planned);
2364
+ const blocked = decideForce(options) ? [] : planned.filter((change) => change.kind === "modify");
2365
+ if (blocked.length > 0) {
2366
+ warn(
2367
+ `${blocked.length} destination(s) already exist and would be refused without --force:`
2368
+ );
2369
+ for (const change of blocked) console.log(chalk9.gray(` ${change.path}`));
2370
+ outro("Dry run complete - the real run would fail without --force");
2371
+ return 1;
2372
+ }
1842
2373
  outro("Dry run complete");
1843
2374
  return 0;
1844
2375
  }
@@ -1857,7 +2388,7 @@ async function runAdd(name, options, cwd = process.cwd()) {
1857
2388
  if (exitCode === 0) outro("Installation complete");
1858
2389
  return exitCode;
1859
2390
  }
1860
- var addCommand = new Command("add").description("Install a skill, agent, hook, or other configuration").argument("[name]", "Name of the item to install").option("-t, --type <type>", "Content type: skill, agent, hook, mcp, plugin, settings").option(
2391
+ var addCommand = new Command("add").description("Install a skill, agent, hook, or other configuration").argument("[name]", "Name of the item to install").option("-t, --type <type>", `Content type: ${TYPE_LIST}`).option(
1861
2392
  "-a, --agents <agents>",
1862
2393
  "Comma-separated coding agents or 'all' (claude,copilot,antigravity,codex,opencode; 'gemini' is a deprecated alias of antigravity). Every named agent must support the item; 'all' means all compatible agents. MCP servers: claude, codex, opencode (copilot's and antigravity's formats are unverified)"
1863
2394
  ).option("-s, --scope <scope>", "Installation scope: project, user, or local").option("-m, --method <method>", "Installation method: symlink or copy").option("-y, --yes", "Skip confirmation prompts").option("-f, --force", "Overwrite existing files").option("-n, --dry-run", "Show exactly which files would be written, without writing or reporting anything").addHelpText("after", `
@@ -1874,17 +2405,18 @@ Telemetry: ${TELEMETRY_HELP_TEXT}.`).action(async (name, options) => {
1874
2405
 
1875
2406
  // src/commands/list.ts
1876
2407
  import { Command as Command2 } from "commander";
1877
- import chalk9 from "chalk";
2408
+ import chalk10 from "chalk";
1878
2409
  var SEPARATOR_WIDTH = 40;
1879
2410
  var SLUG_COLUMN_WIDTH = 24;
1880
2411
  var TYPE_COLORS = {
1881
- skill: chalk9.magenta,
1882
- hook: chalk9.hex("#a855f7"),
1883
- agent: chalk9.blue,
1884
- plugin: chalk9.hex("#6366f1"),
1885
- command: chalk9.hex("#f59e0b"),
1886
- settings: chalk9.hex("#f97316"),
1887
- mcp: chalk9.hex("#2dd4bf")
2412
+ skill: chalk10.magenta,
2413
+ hook: chalk10.hex("#a855f7"),
2414
+ agent: chalk10.blue,
2415
+ plugin: chalk10.hex("#6366f1"),
2416
+ command: chalk10.hex("#f59e0b"),
2417
+ settings: chalk10.hex("#f97316"),
2418
+ mcp: chalk10.hex("#2dd4bf"),
2419
+ rule: chalk10.hex("#84cc16")
1888
2420
  };
1889
2421
  async function collectInstalledItems(params) {
1890
2422
  const types = params.types ?? getRegisteredTypes();
@@ -1913,7 +2445,7 @@ function resolveListAgents(agentsArg) {
1913
2445
  async function runList(options, cwd = process.cwd()) {
1914
2446
  const optionError = validateType(options.type) || validateScope(options.scope);
1915
2447
  if (optionError) {
1916
- console.log(chalk9.red(optionError));
2448
+ console.log(chalk10.red(optionError));
1917
2449
  return 1;
1918
2450
  }
1919
2451
  const type = options.type;
@@ -1922,14 +2454,14 @@ async function runList(options, cwd = process.cwd()) {
1922
2454
  }
1923
2455
  const agents = resolveListAgents(options.agents);
1924
2456
  if (typeof agents === "string") {
1925
- console.log(chalk9.red(agents));
2457
+ console.log(chalk10.red(agents));
1926
2458
  return 1;
1927
2459
  }
1928
2460
  const scope = options.scope ?? "project";
1929
2461
  await listInstalled({ types: type ? [type] : void 0, agents, scope, cwd });
1930
2462
  return 0;
1931
2463
  }
1932
- var listCommand = new Command2("list").alias("ls").description("List available or installed items").option("-t, --type <type>", "Filter by type (skill, hook, agent, plugin, mcp, settings)").option("-i, --installed", "Show only installed items").option("-a, --agents <agents>", "Comma-separated coding agents or 'all' (installed check)", "all").option("--scope <scope>", "Scope for installed check (project, user, local)", "project").option("--label <label>", "Filter by label (see the registry's labels.json)").action(async (options) => {
2464
+ var listCommand = new Command2("list").alias("ls").description("List available or installed items").option("-t, --type <type>", `Filter by type: ${TYPE_LIST}`).option("-i, --installed", "Show only installed items").option("-a, --agents <agents>", "Comma-separated coding agents or 'all' (installed check)", "all").option("--scope <scope>", "Scope for installed check (project, user, local)", "project").option("--label <label>", "Filter by label (see the registry's labels.json)").action(async (options) => {
1933
2465
  try {
1934
2466
  const exitCode = await runList(options);
1935
2467
  if (exitCode !== 0) process.exit(exitCode);
@@ -1942,32 +2474,32 @@ async function listAvailable(type, label) {
1942
2474
  const items = label ? all.filter((item) => item.label === label) : all;
1943
2475
  if (items.length === 0 && label) {
1944
2476
  const known = [...new Set(all.map((item) => item.label).filter(Boolean))].sort();
1945
- console.log(chalk9.red(`No item carries the label "${label}".`));
1946
- if (known.length > 0) console.log(chalk9.gray(`Labels in use: ${known.join(", ")}`));
2477
+ console.log(chalk10.red(`No item carries the label "${label}".`));
2478
+ if (known.length > 0) console.log(chalk10.gray(`Labels in use: ${known.join(", ")}`));
1947
2479
  return 1;
1948
2480
  }
1949
2481
  if (items.length === 0) {
1950
- console.log(chalk9.yellow("No items found in registry"));
2482
+ console.log(chalk10.yellow("No items found in registry"));
1951
2483
  return 0;
1952
2484
  }
1953
2485
  const grouped = groupByType(items);
1954
2486
  for (const [itemType, typeItems] of Object.entries(grouped)) {
1955
- const colorFn = TYPE_COLORS[itemType] ?? chalk9.white;
2487
+ const colorFn = TYPE_COLORS[itemType] ?? chalk10.white;
1956
2488
  console.log(colorFn(`
1957
2489
  ${itemType.toUpperCase()}S`));
1958
- console.log(chalk9.gray("\u2500".repeat(SEPARATOR_WIDTH)));
2490
+ console.log(chalk10.gray("\u2500".repeat(SEPARATOR_WIDTH)));
1959
2491
  for (const item of typeItems) {
1960
2492
  const compatIcons = [...new Set(item.compatibility.map((a) => CODING_AGENTS[a]?.shortName ?? a))].join(" ");
1961
- const featured = item.featured ? chalk9.yellow("\u2605 ") : " ";
2493
+ const featured = item.featured ? chalk10.yellow("\u2605 ") : " ";
1962
2494
  console.log(
1963
- `${featured}${chalk9.white(item.slug.padEnd(SLUG_COLUMN_WIDTH))} ${chalk9.gray(compatIcons)}`
2495
+ `${featured}${chalk10.white(item.slug.padEnd(SLUG_COLUMN_WIDTH))} ${chalk10.gray(compatIcons)}`
1964
2496
  );
1965
- console.log(` ${chalk9.gray(item.description)}`);
2497
+ console.log(` ${chalk10.gray(item.description)}`);
1966
2498
  }
1967
2499
  }
1968
2500
  console.log("");
1969
2501
  console.log(
1970
- chalk9.gray(`Total: ${items.length} items. Use 'npx @danieldeusing/seedr add <name>' to install.`)
2502
+ chalk10.gray(`Total: ${items.length} items. Use 'npx @danieldeusing/seedr add <name>' to install.`)
1971
2503
  );
1972
2504
  return 0;
1973
2505
  }
@@ -1981,23 +2513,23 @@ Installed items (${params.scope} scope):
1981
2513
  for (const group of groups) {
1982
2514
  if (group.type !== currentType) {
1983
2515
  currentType = group.type;
1984
- const colorFn = TYPE_COLORS[group.type] ?? chalk9.white;
2516
+ const colorFn = TYPE_COLORS[group.type] ?? chalk10.white;
1985
2517
  console.log(colorFn(`${group.type.toUpperCase()}S`));
1986
2518
  }
1987
- console.log(chalk9.blue(` ${CODING_AGENTS[group.agent].name}`));
2519
+ console.log(chalk10.blue(` ${CODING_AGENTS[group.agent].name}`));
1988
2520
  for (const slug of group.slugs) {
1989
- console.log(` ${chalk9.white(slug)}`);
2521
+ console.log(` ${chalk10.white(slug)}`);
1990
2522
  total++;
1991
2523
  }
1992
2524
  console.log("");
1993
2525
  }
1994
2526
  if (!params.types || params.types.includes("settings")) {
1995
- console.log(chalk9.gray(`Note: ${SETTINGS_NOT_DISCOVERABLE}`));
2527
+ console.log(chalk10.gray(`Note: ${SETTINGS_NOT_DISCOVERABLE}`));
1996
2528
  }
1997
2529
  if (total === 0) {
1998
- console.log(chalk9.yellow("No items installed"));
2530
+ console.log(chalk10.yellow("No items installed"));
1999
2531
  } else {
2000
- console.log(chalk9.gray(`Total: ${total} installed`));
2532
+ console.log(chalk10.gray(`Total: ${total} installed`));
2001
2533
  }
2002
2534
  }
2003
2535
  function groupByType(items) {
@@ -2015,8 +2547,8 @@ function groupByType(items) {
2015
2547
 
2016
2548
  // src/commands/remove.ts
2017
2549
  import { Command as Command3 } from "commander";
2018
- import chalk10 from "chalk";
2019
- import ora7 from "ora";
2550
+ import chalk11 from "chalk";
2551
+ import ora8 from "ora";
2020
2552
  async function findInstalledAgents(slug, type, scope, cwd) {
2021
2553
  const handler = getHandler(type);
2022
2554
  if (!handler) return [];
@@ -2034,13 +2566,19 @@ async function removeFromAgents(slug, type, agents, scope, cwd) {
2034
2566
  if (!handler) return 0;
2035
2567
  let successCount = 0;
2036
2568
  for (const agent of agents) {
2037
- const spinner = ora7(`Removing from ${CODING_AGENTS[agent].name}...`).start();
2569
+ if (!isTypeSupported(type, agent)) {
2570
+ ora8().start().info(
2571
+ chalk11.gray(`Skipped ${CODING_AGENTS[agent].name}: ${describeIncompatibility(type, agent)}`)
2572
+ );
2573
+ continue;
2574
+ }
2575
+ const spinner = ora8(`Removing from ${CODING_AGENTS[agent].name}...`).start();
2038
2576
  const removed = await handler.uninstall(slug, agent, scope, cwd);
2039
2577
  if (removed) {
2040
2578
  spinner.succeed(brand(`Removed from ${CODING_AGENTS[agent].name}`));
2041
2579
  successCount++;
2042
2580
  } else {
2043
- spinner.info(chalk10.gray(`Not found in ${CODING_AGENTS[agent].name}`));
2581
+ spinner.info(chalk11.gray(`Not found in ${CODING_AGENTS[agent].name}`));
2044
2582
  }
2045
2583
  }
2046
2584
  return successCount;
@@ -2065,14 +2603,14 @@ function validateRemoveRequest(name, options) {
2065
2603
  async function runRemove(name, options, cwd = process.cwd()) {
2066
2604
  const requestError = validateRemoveRequest(name, options);
2067
2605
  if (requestError) {
2068
- console.log(chalk10.red(requestError));
2606
+ console.log(chalk11.red(requestError));
2069
2607
  return 1;
2070
2608
  }
2071
2609
  const scope = options.scope ?? "project";
2072
2610
  const type = options.type;
2073
2611
  const handler = getHandler(type);
2074
2612
  if (!handler) {
2075
- console.log(chalk10.red(`No handler found for type "${type}"`));
2613
+ console.log(chalk11.red(`No handler found for type "${type}"`));
2076
2614
  return 1;
2077
2615
  }
2078
2616
  let agents;
@@ -2085,11 +2623,11 @@ async function runRemove(name, options, cwd = process.cwd()) {
2085
2623
  }
2086
2624
  if (agents.length === 0) {
2087
2625
  if (type === "settings") {
2088
- console.log(chalk10.yellow(`Cannot detect where "${name}" is installed: ${SETTINGS_NOT_DISCOVERABLE}`));
2089
- console.log(chalk10.gray("Name the agents explicitly, e.g. --agents claude"));
2626
+ console.log(chalk11.yellow(`Cannot detect where "${name}" is installed: ${SETTINGS_NOT_DISCOVERABLE}`));
2627
+ console.log(chalk11.gray("Name the agents explicitly, e.g. --agents claude"));
2090
2628
  return 1;
2091
2629
  }
2092
- console.log(chalk10.yellow(`${type} "${name}" is not installed in ${scope} scope`));
2630
+ console.log(chalk11.yellow(`${type} "${name}" is not installed in ${scope} scope`));
2093
2631
  return 0;
2094
2632
  }
2095
2633
  if (!options.yes) {
@@ -2102,7 +2640,7 @@ Will remove ${type} "${name}" from:`));
2102
2640
  const answer = await confirm("Proceed with removal?");
2103
2641
  const confirmed = !p.isCancel(answer) && answer;
2104
2642
  if (!confirmed) {
2105
- console.log(chalk10.yellow("Removal cancelled"));
2643
+ console.log(chalk11.yellow("Removal cancelled"));
2106
2644
  return 0;
2107
2645
  }
2108
2646
  }
@@ -2112,10 +2650,10 @@ Will remove ${type} "${name}" from:`));
2112
2650
  console.log(brand(`Successfully removed from ${successCount} agent(s)`));
2113
2651
  return 0;
2114
2652
  }
2115
- console.log(chalk10.yellow("Nothing to remove"));
2653
+ console.log(chalk11.yellow("Nothing to remove"));
2116
2654
  return 0;
2117
2655
  }
2118
- var removeCommand = new Command3("remove").alias("rm").description("Remove an installed item (skill, plugin, agent, hook, mcp)").argument("<name>", "Name/slug of the item to remove").option("-t, --type <type>", "Content type: skill, agent, hook, mcp, plugin, settings").option(
2656
+ var removeCommand = new Command3("remove").alias("rm").description("Remove an installed item (skill, plugin, agent, hook, mcp)").argument("<name>", "Name/slug of the item to remove").option("-t, --type <type>", `Content type: ${TYPE_LIST}`).option(
2119
2657
  "-a, --agents <agents>",
2120
2658
  "Comma-separated coding agents or 'all'"
2121
2659
  ).option(
@@ -2133,9 +2671,9 @@ var removeCommand = new Command3("remove").alias("rm").description("Remove an in
2133
2671
 
2134
2672
  // src/commands/init.ts
2135
2673
  import { Command as Command4 } from "commander";
2136
- import chalk11 from "chalk";
2137
- import ora8 from "ora";
2138
- import { join as join5 } from "path";
2674
+ import chalk12 from "chalk";
2675
+ import ora9 from "ora";
2676
+ import { join as join7 } from "path";
2139
2677
  function readmeFor(agent) {
2140
2678
  return `# ${CODING_AGENTS[agent].name} Configuration
2141
2679
 
@@ -2153,7 +2691,7 @@ async function initializeAgent(agent, cwd) {
2153
2691
  const path = getAgentPath(agent, "project", cwd);
2154
2692
  if (await exists(path)) return false;
2155
2693
  await ensureDir(path);
2156
- await writeTextFile(join5(path, "README.md"), readmeFor(agent));
2694
+ await writeTextFile(join7(path, "README.md"), readmeFor(agent));
2157
2695
  return true;
2158
2696
  }
2159
2697
  async function runInit(options, cwd = process.cwd()) {
@@ -2164,13 +2702,13 @@ async function runInit(options, cwd = process.cwd()) {
2164
2702
  } else {
2165
2703
  const parsed = parseAgentsArgStrict(agentsArg);
2166
2704
  if (parsed.unknown.length > 0) {
2167
- console.error(chalk11.red(`Unknown agent(s): ${parsed.unknown.join(", ")}. Valid agents: ${ALL_AGENTS.join(", ")} or "all"`));
2705
+ console.error(chalk12.red(`Unknown agent(s): ${parsed.unknown.join(", ")}. Valid agents: ${ALL_AGENTS.join(", ")} or "all"`));
2168
2706
  return 1;
2169
2707
  }
2170
2708
  agents = parsed.agents;
2171
2709
  }
2172
2710
  if (agents.length === 0) {
2173
- console.error(chalk11.red("No valid agents specified"));
2711
+ console.error(chalk12.red("No valid agents specified"));
2174
2712
  return 1;
2175
2713
  }
2176
2714
  console.log(brand("\nWill initialize configuration for:"));
@@ -2182,16 +2720,16 @@ async function runInit(options, cwd = process.cwd()) {
2182
2720
  const answer = await confirm("Proceed?");
2183
2721
  const confirmed = !p.isCancel(answer) && answer;
2184
2722
  if (!confirmed) {
2185
- console.log(chalk11.yellow("Cancelled"));
2723
+ console.log(chalk12.yellow("Cancelled"));
2186
2724
  return 0;
2187
2725
  }
2188
2726
  }
2189
2727
  for (const agent of agents) {
2190
- const spinner = ora8(`Initializing ${CODING_AGENTS[agent].name}...`).start();
2728
+ const spinner = ora9(`Initializing ${CODING_AGENTS[agent].name}...`).start();
2191
2729
  if (await initializeAgent(agent, cwd)) {
2192
2730
  spinner.succeed(brand(`Initialized ${CODING_AGENTS[agent].name}`));
2193
2731
  } else {
2194
- spinner.info(chalk11.gray(`${CODING_AGENTS[agent].name} already initialized`));
2732
+ spinner.info(chalk12.gray(`${CODING_AGENTS[agent].name} already initialized`));
2195
2733
  }
2196
2734
  }
2197
2735
  console.log("");
@@ -2214,7 +2752,7 @@ var initCommand = new Command4("init").description("Initialize coding agent conf
2214
2752
  // package.json
2215
2753
  var package_default = {
2216
2754
  name: "@danieldeusing/seedr",
2217
- version: "0.1.97",
2755
+ version: "0.1.99",
2218
2756
  description: "Seed your projects with AI configurations",
2219
2757
  type: "module",
2220
2758
  bin: {