@danieldeusing/seedr 0.1.97 → 0.1.98

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,10 @@ import {
11
13
  brand,
12
14
  canonicalAgent,
13
15
  canonicalAgents,
16
+ claudeUserRoot,
14
17
  confirm,
15
18
  copyDirectory,
19
+ deepMerge,
16
20
  ensureDir,
17
21
  error,
18
22
  exists,
@@ -33,6 +37,7 @@ import {
33
37
  intro,
34
38
  isInteractive,
35
39
  isLegacyAgent,
40
+ isTypeSupported,
36
41
  isValidSlug,
37
42
  listItems,
38
43
  mainFileName,
@@ -42,6 +47,7 @@ import {
42
47
  parseAgentsArgStrict,
43
48
  parseGitHubRepo,
44
49
  printLogo,
50
+ readJson,
45
51
  removePathEntry,
46
52
  resolveContained,
47
53
  restoreFile,
@@ -56,15 +62,16 @@ import {
56
62
  success,
57
63
  warn,
58
64
  writeFileAtomic,
65
+ writeJson,
59
66
  writeTextFile
60
- } from "./chunk-PEGEA2KN.js";
67
+ } from "./chunk-NQOS5TI7.js";
61
68
 
62
69
  // src/cli.ts
63
70
  import { Command as Command5 } from "commander";
64
71
 
65
72
  // src/commands/add.ts
66
73
  import { Command } from "commander";
67
- import chalk8 from "chalk";
74
+ import chalk9 from "chalk";
68
75
 
69
76
  // src/handlers/registry.ts
70
77
  var handlers = /* @__PURE__ */ new Map();
@@ -104,15 +111,8 @@ function handleCommandError(error2) {
104
111
  // src/utils/validate-options.ts
105
112
  var SCOPES = ["project", "user", "local"];
106
113
  var METHODS = ["symlink", "copy"];
107
- var TYPES = [
108
- "skill",
109
- "hook",
110
- "agent",
111
- "plugin",
112
- "command",
113
- "settings",
114
- "mcp"
115
- ];
114
+ var TYPES = ALL_TYPES;
115
+ var TYPE_LIST = TYPES.join(", ");
116
116
  function validateScope(value) {
117
117
  if (value === void 0) return null;
118
118
  if (!SCOPES.includes(value)) {
@@ -144,7 +144,7 @@ function isTelemetryDisabled(env = process.env) {
144
144
  return env[TELEMETRY_OPT_OUT_VARIABLE] !== void 0;
145
145
  }
146
146
  function cliVersion() {
147
- return true ? "0.1.97" : "dev";
147
+ return true ? "0.1.98" : "dev";
148
148
  }
149
149
  async function sendEvent(event) {
150
150
  try {
@@ -165,26 +165,12 @@ function trackInstalls(slug, type, results, scope) {
165
165
  }
166
166
 
167
167
  // 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
168
  function filterCompatibleAgents(type, agents) {
182
169
  const compatible = AGENT_COMPATIBILITY[type];
183
170
  return canonicalAgents(agents).filter((a) => compatible.includes(a));
184
171
  }
185
172
  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"
173
+ 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
174
  };
189
175
  function describeIncompatibility(type, agent) {
190
176
  const canonical = canonicalAgent(agent);
@@ -201,11 +187,14 @@ import { lstat, readdir, unlink } from "fs/promises";
201
187
  import chalk2 from "chalk";
202
188
  import ora from "ora";
203
189
  var SLUG_LABEL = "agent slug";
190
+ function subagentFileName(agent, slug) {
191
+ return (canonicalAgent(agent) ?? agent) === "copilot" ? `${slug}.agent.md` : `${slug}.md`;
192
+ }
204
193
  async function resolveAgentFilePath(agent, slug, scope, cwd) {
205
194
  const destDir = getContentPath(agent, "agent", scope, cwd);
206
195
  if (!destDir) return null;
207
196
  const scopeRoot = scope === "user" ? homedir() : cwd;
208
- return resolveContained(scopeRoot, relative(scopeRoot, destDir), `${slug}.md`);
197
+ return resolveContained(scopeRoot, relative(scopeRoot, destDir), subagentFileName(agent, slug));
209
198
  }
210
199
  async function installAgentForCodingAgent(item, agent, scope, method, force, cwd) {
211
200
  const spinner = ora(
@@ -413,60 +402,11 @@ var commandHandler = {
413
402
  };
414
403
 
415
404
  // src/handlers/hook.ts
416
- import { join as join3, basename, dirname as dirname2 } from "path";
405
+ import { join as join3, basename, dirname } from "path";
417
406
  import { homedir as homedir3 } from "os";
418
- import { lstat as lstat3, mkdir, mkdtemp, readFile as readFile2, realpath, rm, unlink as unlink3 } from "fs/promises";
407
+ import { lstat as lstat3, mkdir, mkdtemp, readFile, realpath, rm, unlink as unlink3 } from "fs/promises";
419
408
  import chalk4 from "chalk";
420
409
  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
410
  var CLAUDE_ONLY_ERROR = "Hooks are only supported for Claude Code";
471
411
  var SCRIPT_NAME_LABEL = "hook script name";
472
412
  var SCRIPT_MODE = 493;
@@ -520,13 +460,13 @@ async function assertRegularFileOrMissing(path) {
520
460
  async function loadScriptBytes(item, scriptFile, hooksDir) {
521
461
  const sourcePath = getItemSourcePath(item);
522
462
  if (sourcePath && await exists(sourcePath)) {
523
- return readFile2(join3(sourcePath, scriptFile));
463
+ return readFile(join3(sourcePath, scriptFile));
524
464
  }
525
- const fetchDir = await mkdtemp(join3(dirname2(hooksDir), ".seedr-hook-"));
465
+ const fetchDir = await mkdtemp(join3(dirname(hooksDir), ".seedr-hook-"));
526
466
  try {
527
467
  const contentDir = join3(fetchDir, "content");
528
468
  await fetchItemToDestination(item, contentDir);
529
- return await readFile2(join3(contentDir, scriptFile));
469
+ return await readFile(join3(contentDir, scriptFile));
530
470
  } finally {
531
471
  await rm(fetchDir, { recursive: true, force: true });
532
472
  }
@@ -703,6 +643,7 @@ async function planHook(item, agents, scope, _method, cwd) {
703
643
  }
704
644
  var hookHandler = {
705
645
  type: "hook",
646
+ honoursMethod: false,
706
647
  async install(item, agents, scope, method, force, cwd) {
707
648
  return installHook(item, agents, scope, method, force, cwd);
708
649
  },
@@ -716,8 +657,8 @@ var hookHandler = {
716
657
  };
717
658
 
718
659
  // src/handlers/mcp.ts
719
- import { readFile as readFile3 } from "fs/promises";
720
- import { dirname as dirname3 } from "path";
660
+ import { readFile as readFile2 } from "fs/promises";
661
+ import { dirname as dirname2 } from "path";
721
662
  import chalk5 from "chalk";
722
663
  import ora4 from "ora";
723
664
 
@@ -1014,7 +955,7 @@ function jsonAdapter(agent, key, translate, defaults = {}) {
1014
955
  }
1015
956
  async function readTextOrEmpty(path) {
1016
957
  try {
1017
- return await readFile3(path, "utf-8");
958
+ return await readFile2(path, "utf-8");
1018
959
  } catch (error2) {
1019
960
  if (error2.code === "ENOENT") return "";
1020
961
  throw error2;
@@ -1028,7 +969,7 @@ var codexAdapter = {
1028
969
  },
1029
970
  async write(path, name, config) {
1030
971
  const text = await readTextOrEmpty(path);
1031
- await ensureDir(dirname3(path));
972
+ await ensureDir(dirname2(path));
1032
973
  await writeFileAtomic(path, upsertTomlTables(text, [CODEX_TABLE, name], toCodexTables(name, config)));
1033
974
  },
1034
975
  async remove(path, name) {
@@ -1044,7 +985,10 @@ var codexAdapter = {
1044
985
  var ADAPTERS = {
1045
986
  claude: jsonAdapter("claude", MCP_SERVERS_KEY, (config) => ({ ...config })),
1046
987
  opencode: jsonAdapter("opencode", "mcp", toOpenCodeServer, { $schema: "https://opencode.ai/config.json" }),
1047
- codex: codexAdapter
988
+ codex: codexAdapter,
989
+ // Copilot reads the same `mcpServers` map Claude does — verified from
990
+ // `copilot mcp --help` and from a populated `~/.copilot/mcp-config.json`.
991
+ copilot: jsonAdapter("copilot", MCP_SERVERS_KEY, (config) => ({ ...config }))
1048
992
  };
1049
993
  function adapterFor(agent) {
1050
994
  const adapter = ADAPTERS[agent];
@@ -1129,6 +1073,7 @@ async function planMcp(item, agents, scope, _method, cwd) {
1129
1073
  }
1130
1074
  var mcpHandler = {
1131
1075
  type: "mcp",
1076
+ honoursMethod: false,
1132
1077
  async install(item, agents, scope, method, force, cwd) {
1133
1078
  return installMcp(item, agents, scope, method, force, cwd);
1134
1079
  },
@@ -1255,6 +1200,7 @@ async function planSettings(item, agents, scope, _method, cwd) {
1255
1200
  }
1256
1201
  var settingsHandler = {
1257
1202
  type: "settings",
1203
+ honoursMethod: false,
1258
1204
  async install(item, agents, scope, method, force, cwd) {
1259
1205
  return installSettings(item, agents, scope, method, force, cwd);
1260
1206
  },
@@ -1268,23 +1214,30 @@ var settingsHandler = {
1268
1214
  };
1269
1215
 
1270
1216
  // src/handlers/plugin.ts
1217
+ import { join as join5, relative as relative3 } from "path";
1218
+ import { mkdir as mkdir3, mkdtemp as mkdtemp2, rm as rm2 } from "fs/promises";
1219
+ import chalk7 from "chalk";
1220
+ import ora6 from "ora";
1221
+
1222
+ // src/handlers/pluginStores.ts
1271
1223
  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";
1224
+ import { join as join4 } from "path";
1225
+ import { mkdir as mkdir2, readFile as readFile3, writeFile } from "fs/promises";
1274
1226
  import { execFile } from "child_process";
1275
1227
  import { promisify } from "util";
1276
- import chalk7 from "chalk";
1277
- import ora6 from "ora";
1278
1228
  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_.-]+$/;
1229
+ var CLAUDE_MANIFEST = ".claude-plugin/plugin.json";
1230
+ async function readTextOrEmpty2(path) {
1231
+ try {
1232
+ return await readFile3(path, "utf-8");
1233
+ } catch {
1234
+ return "";
1235
+ }
1236
+ }
1237
+ async function writeText(path, text) {
1238
+ await mkdir2(join4(path, ".."), { recursive: true });
1239
+ await writeFile(path, text, "utf-8");
1240
+ }
1288
1241
  function getPluginId(name, marketplace) {
1289
1242
  return `${name}@${marketplace}`;
1290
1243
  }
@@ -1293,6 +1246,399 @@ function splitPluginId(pluginId) {
1293
1246
  if (at <= 0 || at === pluginId.length - 1) return null;
1294
1247
  return { name: pluginId.slice(0, at), marketplace: pluginId.slice(at + 1) };
1295
1248
  }
1249
+ function manifestsFor(ownManifest) {
1250
+ return ownManifest ? [ownManifest, CLAUDE_MANIFEST] : [CLAUDE_MANIFEST];
1251
+ }
1252
+ var CLAUDE_PLUGINS_DIR = join4(claudeUserRoot(), "plugins");
1253
+ var CLAUDE_CACHE_DIR = join4(CLAUDE_PLUGINS_DIR, "cache");
1254
+ var CLAUDE_INSTALLED_PATH = join4(CLAUDE_PLUGINS_DIR, "installed_plugins.json");
1255
+ var CLAUDE_KNOWN_MARKETPLACES_PATH = join4(CLAUDE_PLUGINS_DIR, "known_marketplaces.json");
1256
+ var CLAUDE_MARKETPLACES_DIR = join4(CLAUDE_PLUGINS_DIR, "marketplaces");
1257
+ function isScopeEntry(entry, scope, cwd) {
1258
+ return entry.scope === scope && (scope === "user" || entry.projectPath === cwd);
1259
+ }
1260
+ async function enableInJsonSettings(path, pluginId) {
1261
+ const settings = await readJson(path);
1262
+ settings.enabledPlugins = settings.enabledPlugins || {};
1263
+ settings.enabledPlugins[pluginId] = true;
1264
+ await writeJson(path, settings);
1265
+ }
1266
+ async function disableInJsonSettings(path, pluginId) {
1267
+ const settings = await readJson(path);
1268
+ if (!settings.enabledPlugins || !(pluginId in settings.enabledPlugins)) return false;
1269
+ delete settings.enabledPlugins[pluginId];
1270
+ await writeJson(path, settings);
1271
+ return true;
1272
+ }
1273
+ var claudeStore = {
1274
+ manifestPaths: manifestsFor(),
1275
+ userGlobal: false,
1276
+ cacheRoot: CLAUDE_CACHE_DIR,
1277
+ cachePath: (marketplace, name, version) => resolveContained(CLAUDE_CACHE_DIR, marketplace, name, version),
1278
+ /**
1279
+ * Claude Code reports a plugin as "orphaned" unless its marketplace is both
1280
+ * recorded and cloned, so this is the one store that fetches a second
1281
+ * repository. The other agents only record the source.
1282
+ */
1283
+ async ensureMarketplace(marketplace, item) {
1284
+ const known = await readJson(CLAUDE_KNOWN_MARKETPLACES_PATH);
1285
+ if (known[marketplace]) return [];
1286
+ const repo = marketplaceRepo(item);
1287
+ if (!repo) return [];
1288
+ const installLocation = await resolveContained(CLAUDE_MARKETPLACES_DIR, marketplace);
1289
+ const alreadyCloned = await exists(installLocation);
1290
+ return [
1291
+ {
1292
+ path: CLAUDE_KNOWN_MARKETPLACES_PATH,
1293
+ detail: `marketplace "${marketplace}"`,
1294
+ ...alreadyCloned ? {} : {
1295
+ creates: {
1296
+ path: installLocation,
1297
+ detail: `git clone https://github.com/${repo}.git`
1298
+ }
1299
+ },
1300
+ apply: async () => {
1301
+ await mkdir2(CLAUDE_MARKETPLACES_DIR, { recursive: true });
1302
+ if (!await exists(installLocation)) {
1303
+ await promisify(execFile)("git", [
1304
+ "clone",
1305
+ "--depth",
1306
+ "1",
1307
+ `https://github.com/${repo}.git`,
1308
+ installLocation
1309
+ ]);
1310
+ }
1311
+ const current = await readJson(
1312
+ CLAUDE_KNOWN_MARKETPLACES_PATH
1313
+ );
1314
+ current[marketplace] = {
1315
+ source: { source: "github", repo },
1316
+ installLocation,
1317
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1318
+ };
1319
+ await writeJson(CLAUDE_KNOWN_MARKETPLACES_PATH, current);
1320
+ }
1321
+ }
1322
+ ];
1323
+ },
1324
+ mutations: (context) => [
1325
+ {
1326
+ path: CLAUDE_INSTALLED_PATH,
1327
+ detail: `plugins["${context.pluginId}"] entry for ${context.scope} scope`,
1328
+ apply: async () => {
1329
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1330
+ const registry = await readJson(CLAUDE_INSTALLED_PATH);
1331
+ registry.version = registry.version || 2;
1332
+ registry.plugins = registry.plugins || {};
1333
+ const entry = {
1334
+ scope: context.scope,
1335
+ ...context.scope !== "user" ? { projectPath: context.cwd } : {},
1336
+ installPath: context.cachePath ?? "",
1337
+ version: context.version,
1338
+ installedAt: now,
1339
+ lastUpdated: now,
1340
+ gitCommitSha: context.gitCommitSha
1341
+ };
1342
+ const existing = registry.plugins[context.pluginId] || [];
1343
+ registry.plugins[context.pluginId] = [
1344
+ ...existing.filter((candidate) => !isScopeEntry(candidate, context.scope, context.cwd)),
1345
+ entry
1346
+ ];
1347
+ await writeJson(CLAUDE_INSTALLED_PATH, registry);
1348
+ }
1349
+ },
1350
+ {
1351
+ path: getSettingsPath(context.scope, context.cwd),
1352
+ detail: `enabledPlugins["${context.pluginId}"] = true`,
1353
+ apply: () => enableInJsonSettings(getSettingsPath(context.scope, context.cwd), context.pluginId)
1354
+ }
1355
+ ],
1356
+ async listInstalled(scope, cwd) {
1357
+ const registry = await readJson(CLAUDE_INSTALLED_PATH);
1358
+ if (!registry.plugins) return [];
1359
+ return Object.entries(registry.plugins).filter(([, entries]) => entries.some((entry) => isScopeEntry(entry, scope, cwd))).map(([pluginId]) => pluginId);
1360
+ },
1361
+ async remove(pluginId, scope, cwd) {
1362
+ const registry = await readJson(CLAUDE_INSTALLED_PATH);
1363
+ if (!registry.plugins?.[pluginId]) return false;
1364
+ const entries = registry.plugins[pluginId];
1365
+ const removed = entries.filter((entry) => isScopeEntry(entry, scope, cwd));
1366
+ if (removed.length === 0) return false;
1367
+ const remaining = entries.filter((entry) => !isScopeEntry(entry, scope, cwd));
1368
+ if (remaining.length === 0) delete registry.plugins[pluginId];
1369
+ else registry.plugins[pluginId] = remaining;
1370
+ await writeJson(CLAUDE_INSTALLED_PATH, registry);
1371
+ await disableInJsonSettings(getSettingsPath(scope, cwd), pluginId);
1372
+ for (const installPath of new Set(removed.map((entry) => entry.installPath))) {
1373
+ const stillUsed = Object.values(registry.plugins).some(
1374
+ (list) => list.some((entry) => entry.installPath === installPath)
1375
+ );
1376
+ if (stillUsed || installPath === "") continue;
1377
+ try {
1378
+ await removePathEntry(await resolveContained(CLAUDE_CACHE_DIR, installPath));
1379
+ } catch {
1380
+ }
1381
+ }
1382
+ return true;
1383
+ }
1384
+ };
1385
+ var COPILOT_DIR = join4(home, ".copilot");
1386
+ var COPILOT_SETTINGS_PATH = join4(COPILOT_DIR, "settings.json");
1387
+ var COPILOT_PLUGINS_DIR = join4(COPILOT_DIR, "installed-plugins");
1388
+ var copilotStore = {
1389
+ manifestPaths: manifestsFor(),
1390
+ userGlobal: true,
1391
+ cacheRoot: COPILOT_PLUGINS_DIR,
1392
+ cachePath: (marketplace, name) => resolveContained(COPILOT_PLUGINS_DIR, marketplace, name),
1393
+ async ensureMarketplace(marketplace, item) {
1394
+ const repo = marketplaceRepo(item);
1395
+ if (!repo) return [];
1396
+ const settings = await readJson(COPILOT_SETTINGS_PATH);
1397
+ if (settings.extraKnownMarketplaces?.[marketplace]) return [];
1398
+ return [
1399
+ {
1400
+ path: COPILOT_SETTINGS_PATH,
1401
+ detail: `extraKnownMarketplaces["${marketplace}"] = ${repo}`,
1402
+ apply: async () => {
1403
+ const current = await readJson(COPILOT_SETTINGS_PATH);
1404
+ current.extraKnownMarketplaces = current.extraKnownMarketplaces || {};
1405
+ current.extraKnownMarketplaces[marketplace] = { source: { source: "github", repo } };
1406
+ await writeJson(COPILOT_SETTINGS_PATH, current);
1407
+ }
1408
+ }
1409
+ ];
1410
+ },
1411
+ mutations: (context) => [
1412
+ {
1413
+ path: COPILOT_SETTINGS_PATH,
1414
+ detail: `enabledPlugins["${context.pluginId}"] = true`,
1415
+ apply: () => enableInJsonSettings(COPILOT_SETTINGS_PATH, context.pluginId)
1416
+ }
1417
+ ],
1418
+ async listInstalled() {
1419
+ const settings = await readJson(COPILOT_SETTINGS_PATH);
1420
+ return Object.entries(settings.enabledPlugins || {}).filter(([, enabled]) => enabled).map(([pluginId]) => pluginId);
1421
+ },
1422
+ async remove(pluginId) {
1423
+ const disabled = await disableInJsonSettings(COPILOT_SETTINGS_PATH, pluginId);
1424
+ if (!disabled) return false;
1425
+ const split = splitPluginId(pluginId);
1426
+ if (split) {
1427
+ try {
1428
+ await removePathEntry(await resolveContained(COPILOT_PLUGINS_DIR, split.marketplace, split.name));
1429
+ } catch {
1430
+ }
1431
+ }
1432
+ return true;
1433
+ }
1434
+ };
1435
+ var CODEX_DIR = join4(home, ".codex");
1436
+ var CODEX_CONFIG_PATH = join4(CODEX_DIR, "config.toml");
1437
+ var CODEX_CACHE_DIR = join4(CODEX_DIR, "plugins", "cache");
1438
+ function toCodexMarketplaceTable(marketplace, url, revision) {
1439
+ return {
1440
+ keyPath: ["marketplaces", marketplace],
1441
+ entries: {
1442
+ source_type: "git",
1443
+ source: url,
1444
+ ...revision ? { last_revision: revision } : {},
1445
+ last_updated: (/* @__PURE__ */ new Date()).toISOString()
1446
+ }
1447
+ };
1448
+ }
1449
+ function toCodexPluginTable(pluginId) {
1450
+ return { keyPath: ["plugins", pluginId], entries: { enabled: true } };
1451
+ }
1452
+ async function upsertCodex(table) {
1453
+ const text = await readTextOrEmpty2(CODEX_CONFIG_PATH);
1454
+ await writeText(CODEX_CONFIG_PATH, upsertTomlTables(text, table.keyPath, [table]));
1455
+ }
1456
+ var codexStore = {
1457
+ manifestPaths: manifestsFor(".codex-plugin/plugin.json"),
1458
+ userGlobal: true,
1459
+ cacheRoot: CODEX_CACHE_DIR,
1460
+ // Real cache entries on disk are <marketplace>/<name>/<version> — e.g.
1461
+ // claude-plugins-official/claude-code-setup/1.0.0. Dropping the version
1462
+ // segment writes a tree Codex does not look in.
1463
+ cachePath: (marketplace, name, version) => resolveContained(CODEX_CACHE_DIR, marketplace, name, version),
1464
+ async ensureMarketplace(marketplace, item) {
1465
+ const repo = marketplaceRepo(item);
1466
+ if (!repo) return [];
1467
+ const url = `https://github.com/${repo}.git`;
1468
+ return [
1469
+ {
1470
+ path: CODEX_CONFIG_PATH,
1471
+ detail: `[marketplaces.${marketplace}] source = "${url}"`,
1472
+ apply: () => upsertCodex(toCodexMarketplaceTable(marketplace, url, getEffectiveSourceRevision(item) ?? void 0))
1473
+ }
1474
+ ];
1475
+ },
1476
+ mutations: (context) => [
1477
+ {
1478
+ path: CODEX_CONFIG_PATH,
1479
+ detail: `[plugins."${context.pluginId}"] enabled = true`,
1480
+ apply: () => upsertCodex(toCodexPluginTable(context.pluginId))
1481
+ }
1482
+ ],
1483
+ async listInstalled() {
1484
+ const text = await readTextOrEmpty2(CODEX_CONFIG_PATH);
1485
+ const ids = [];
1486
+ for (const match of text.matchAll(/^\[plugins\."([^"]+)"\]/gm)) {
1487
+ if (match[1]) ids.push(match[1]);
1488
+ }
1489
+ return ids;
1490
+ },
1491
+ async remove(pluginId) {
1492
+ const text = await readTextOrEmpty2(CODEX_CONFIG_PATH);
1493
+ const { text: next, removed } = removeTomlTables(text, ["plugins", pluginId]);
1494
+ if (!removed) return false;
1495
+ await writeText(CODEX_CONFIG_PATH, next);
1496
+ const split = splitPluginId(pluginId);
1497
+ if (split) {
1498
+ try {
1499
+ await removePathEntry(await resolveContained(CODEX_CACHE_DIR, split.marketplace, split.name));
1500
+ } catch {
1501
+ }
1502
+ }
1503
+ return true;
1504
+ }
1505
+ };
1506
+ function openCodeConfigPath(scope, cwd) {
1507
+ return scope === "user" ? join4(home, ".config", "opencode", "opencode.json") : join4(cwd, "opencode.json");
1508
+ }
1509
+ function toOpenCodePluginSpec(item, name) {
1510
+ const source = item.pluginSource;
1511
+ if (source?.path) {
1512
+ throw new Error(
1513
+ `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`
1514
+ );
1515
+ }
1516
+ const repo = githubRepoOf(source?.url) ?? githubRepoOf(item.externalUrl);
1517
+ if (!repo) return name;
1518
+ const pinned = source?.sha ?? item.sourceRevision;
1519
+ return `${name}@git+https://github.com/${repo}.git${pinned ? `#${pinned}` : ""}`;
1520
+ }
1521
+ function specName(spec) {
1522
+ const at = spec.indexOf("@", 1);
1523
+ return at > 0 ? spec.slice(0, at) : spec;
1524
+ }
1525
+ var openCodeStore = {
1526
+ manifestPaths: manifestsFor(),
1527
+ userGlobal: false,
1528
+ cacheRoot: null,
1529
+ // OpenCode installs the module from the spec; seedr writes no tree.
1530
+ cachePath: async () => null,
1531
+ mutations: (context) => {
1532
+ const path = openCodeConfigPath(context.scope, context.cwd);
1533
+ const spec = toOpenCodePluginSpec(context.item, context.name);
1534
+ return [
1535
+ {
1536
+ path,
1537
+ detail: `plugin[] += "${spec}"`,
1538
+ apply: async () => {
1539
+ const config = await readJson(path);
1540
+ config.$schema = config.$schema || "https://opencode.ai/config.json";
1541
+ const plugins = config.plugin || [];
1542
+ config.plugin = [...plugins.filter((entry) => specName(entry) !== context.name), spec];
1543
+ await writeJson(path, config);
1544
+ }
1545
+ }
1546
+ ];
1547
+ },
1548
+ async listInstalled(scope, cwd) {
1549
+ const config = await readJson(openCodeConfigPath(scope, cwd));
1550
+ return (config.plugin || []).map(specName);
1551
+ },
1552
+ async remove(pluginId, scope, cwd) {
1553
+ const path = openCodeConfigPath(scope, cwd);
1554
+ const config = await readJson(path);
1555
+ const name = splitPluginId(pluginId)?.name ?? pluginId;
1556
+ const plugins = config.plugin || [];
1557
+ const remaining = plugins.filter((entry) => specName(entry) !== name);
1558
+ if (remaining.length === plugins.length) return false;
1559
+ config.plugin = remaining;
1560
+ await writeJson(path, config);
1561
+ return true;
1562
+ }
1563
+ };
1564
+ var GEMINI_CONFIG_DIR = join4(home, ".gemini", "config");
1565
+ var GEMINI_PLUGINS_DIR = join4(GEMINI_CONFIG_DIR, "plugins");
1566
+ var GEMINI_IMPORT_MANIFEST_PATH = join4(GEMINI_CONFIG_DIR, "import_manifest.json");
1567
+ var antigravityStore = {
1568
+ manifestPaths: manifestsFor(),
1569
+ userGlobal: true,
1570
+ cacheRoot: GEMINI_PLUGINS_DIR,
1571
+ // Antigravity files plugins by name only — it has no marketplace dimension.
1572
+ cachePath: (_marketplace, name) => resolveContained(GEMINI_PLUGINS_DIR, name),
1573
+ mutations: (context) => [
1574
+ {
1575
+ path: GEMINI_IMPORT_MANIFEST_PATH,
1576
+ detail: `imports[] += "${context.name}"`,
1577
+ apply: async () => {
1578
+ const manifest = await readJson(GEMINI_IMPORT_MANIFEST_PATH);
1579
+ const imports = manifest.imports || [];
1580
+ manifest.imports = [
1581
+ ...imports.filter((entry) => entry.name !== context.name),
1582
+ {
1583
+ name: context.name,
1584
+ source: "seedr",
1585
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
1586
+ components: ["skills", "hooks"]
1587
+ }
1588
+ ];
1589
+ await writeJson(GEMINI_IMPORT_MANIFEST_PATH, manifest);
1590
+ }
1591
+ }
1592
+ ],
1593
+ async listInstalled() {
1594
+ const manifest = await readJson(GEMINI_IMPORT_MANIFEST_PATH);
1595
+ return (manifest.imports || []).map((entry) => entry.name);
1596
+ },
1597
+ async remove(pluginId) {
1598
+ const name = splitPluginId(pluginId)?.name ?? pluginId;
1599
+ const manifest = await readJson(GEMINI_IMPORT_MANIFEST_PATH);
1600
+ const imports = manifest.imports || [];
1601
+ const remaining = imports.filter((entry) => entry.name !== name);
1602
+ if (remaining.length === imports.length) return false;
1603
+ manifest.imports = remaining;
1604
+ await writeJson(GEMINI_IMPORT_MANIFEST_PATH, manifest);
1605
+ try {
1606
+ await removePathEntry(await resolveContained(GEMINI_PLUGINS_DIR, name));
1607
+ } catch {
1608
+ }
1609
+ return true;
1610
+ }
1611
+ };
1612
+ var GITHUB_REPO_SLUG_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
1613
+ function githubRepoOf(url) {
1614
+ if (!url) return null;
1615
+ const parsed = parseGitHubRepo(url);
1616
+ if (!parsed) return null;
1617
+ const repo = `${parsed.owner}/${parsed.repo}`;
1618
+ if (!GITHUB_REPO_SLUG_PATTERN.test(repo) || repo.includes("..")) return null;
1619
+ return repo;
1620
+ }
1621
+ function marketplaceRepo(item) {
1622
+ return githubRepoOf(item.marketplaceRef?.url ?? item.externalUrl);
1623
+ }
1624
+ var STORES = {
1625
+ claude: claudeStore,
1626
+ copilot: copilotStore,
1627
+ codex: codexStore,
1628
+ opencode: openCodeStore,
1629
+ antigravity: antigravityStore,
1630
+ gemini: antigravityStore
1631
+ };
1632
+ function pluginStoreFor(agent) {
1633
+ const store = STORES[canonicalAgent(agent) ?? agent];
1634
+ if (!store || !isTypeSupported("plugin", agent)) {
1635
+ throw new Error(`Plugins are not supported for ${CODING_AGENTS[agent]?.name ?? agent}`);
1636
+ }
1637
+ return store;
1638
+ }
1639
+
1640
+ // src/handlers/plugin.ts
1641
+ var SLUG_LABEL3 = "plugin slug";
1296
1642
  function resolveMarketplaceName(item) {
1297
1643
  const marketplace = item.marketplace || item.author?.name || "seedr";
1298
1644
  assertSafePathSegment(marketplace, "plugin marketplace");
@@ -1305,48 +1651,29 @@ function resolvePluginIdentity(item, pluginJson) {
1305
1651
  assertSafePathSegment(version, "plugin version");
1306
1652
  return { name, version };
1307
1653
  }
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;
1654
+ async function readManifestFromDir(dir, manifestPaths) {
1655
+ for (const manifest of manifestPaths) {
1656
+ const path = join5(dir, ...manifest.split("/"));
1657
+ if (await exists(path)) return readJson(path);
1658
+ }
1659
+ return {};
1317
1660
  }
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);
1661
+ async function readManifestForPlan(item, manifestPaths) {
1662
+ const sourcePath = getItemSourcePath(item);
1663
+ if (sourcePath) return readManifestFromDir(sourcePath, manifestPaths);
1664
+ for (const manifest of manifestPaths) {
1665
+ try {
1666
+ return JSON.parse(await fetchItemFile(item, manifest));
1667
+ } catch {
1668
+ }
1669
+ }
1670
+ throw new Error(`No plugin manifest for "${item.slug}" (looked for ${manifestPaths.join(", ")})`);
1344
1671
  }
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));
1672
+ async function createStagingDir(cacheRoot, slug) {
1673
+ const tmpRoot = await resolveContained(cacheRoot, ".tmp");
1674
+ await mkdir3(tmpRoot, { recursive: true });
1675
+ const staging = await mkdtemp2(join5(tmpRoot, `${slug}-`));
1676
+ return resolveContained(cacheRoot, relative3(cacheRoot, staging));
1350
1677
  }
1351
1678
  async function stageContent(item, contentPath) {
1352
1679
  const sourcePath = getItemSourcePath(item);
@@ -1365,37 +1692,6 @@ function resolveGitCommitSha(item, fetched) {
1365
1692
  }
1366
1693
  return sha;
1367
1694
  }
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
1695
  async function rollback(undo, error2) {
1400
1696
  const failures = [];
1401
1697
  for (const step2 of undo.reverse()) {
@@ -1411,70 +1707,75 @@ async function rollback(undo, error2) {
1411
1707
  }
1412
1708
  throw error2;
1413
1709
  }
1414
- async function commitInstall(params) {
1415
- const { contentPath, cachePath, pluginId, scope, cwd } = params;
1710
+ async function commitInstall(contentPath, cachePath, mutations) {
1416
1711
  const undo = [];
1417
1712
  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));
1713
+ if (contentPath && cachePath) {
1714
+ await removePathEntry(cachePath);
1715
+ await moveDirectory(contentPath, cachePath);
1716
+ undo.push(() => removePathEntry(cachePath).then(() => void 0));
1717
+ }
1718
+ for (const mutation of mutations) {
1719
+ const snapshot = await snapshotFile(mutation.path);
1720
+ await mutation.apply();
1721
+ undo.push(() => restoreFile(mutation.path, snapshot));
1722
+ }
1428
1723
  } catch (error2) {
1429
1724
  await rollback(undo, error2);
1430
1725
  }
1431
1726
  }
1432
1727
  async function installPluginForAgent(item, agent, scope, _method, force, cwd) {
1433
- const spinner = ora6(
1434
- `Installing ${item.name} for ${CODING_AGENTS[agent].name}...`
1435
- ).start();
1728
+ const spinner = ora6(`Installing ${item.name} for ${CODING_AGENTS[agent].name}...`).start();
1436
1729
  try {
1437
- if (agent !== "claude") {
1438
- throw new Error(CLAUDE_ONLY_ERROR3);
1439
- }
1730
+ const store = pluginStoreFor(agent);
1440
1731
  assertValidSlug(item.slug, SLUG_LABEL3);
1441
1732
  const marketplace = resolveMarketplaceName(item);
1442
- const staging = await createStagingDir(item.slug);
1733
+ const cacheRoot = store.cacheRoot;
1734
+ const staging = cacheRoot ? await createStagingDir(cacheRoot, item.slug) : null;
1443
1735
  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("/")));
1736
+ const contentPath = staging ? join5(staging, "content") : null;
1737
+ const fetched = contentPath ? await stageContent(item, contentPath) : null;
1738
+ const pluginJson = contentPath ? await readManifestFromDir(contentPath, store.manifestPaths) : await readManifestForPlan(item, store.manifestPaths);
1447
1739
  const { name, version } = resolvePluginIdentity(item, pluginJson);
1448
1740
  const gitCommitSha = resolveGitCommitSha(item, fetched);
1449
1741
  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 };
1742
+ const cachePath = await store.cachePath(marketplace, name, version);
1743
+ const marketplaceMutations = store.ensureMarketplace ? await store.ensureMarketplace(marketplace, item) : [];
1744
+ const context = {
1745
+ item,
1746
+ marketplace,
1747
+ name,
1748
+ version,
1749
+ pluginId,
1750
+ cachePath,
1751
+ gitCommitSha,
1752
+ scope,
1753
+ cwd
1754
+ };
1755
+ if (cachePath) await assertOverwritable(cachePath, force);
1756
+ await commitInstall(contentPath, cachePath, [
1757
+ ...marketplaceMutations,
1758
+ ...store.mutations(context)
1759
+ ]);
1760
+ spinner.succeed(brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`));
1761
+ return { agent, success: true, path: cachePath ?? "" };
1458
1762
  } finally {
1459
- await rm2(staging, { recursive: true, force: true });
1763
+ if (staging) await rm2(staging, { recursive: true, force: true });
1460
1764
  }
1461
1765
  } catch (error2) {
1462
1766
  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
- );
1767
+ spinner.fail(chalk7.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${errorMsg}`));
1466
1768
  return { agent, success: false, path: "", error: errorMsg };
1467
1769
  }
1468
1770
  }
1469
1771
  async function installPlugin(item, agents, scope, method, force, cwd = process.cwd()) {
1470
1772
  const results = [];
1471
1773
  for (const agent of agents) {
1472
- const result = await installPluginForAgent(item, agent, scope, method, force, cwd);
1473
- results.push(result);
1774
+ results.push(await installPluginForAgent(item, agent, scope, method, force, cwd));
1474
1775
  }
1475
1776
  return results;
1476
1777
  }
1477
- async function findInstalledPluginId(registry, slug) {
1778
+ async function findInstalledPluginId(installed, slug) {
1478
1779
  let registryMarketplace = null;
1479
1780
  try {
1480
1781
  const item = await getItem(slug, "plugin");
@@ -1484,70 +1785,34 @@ async function findInstalledPluginId(registry, slug) {
1484
1785
  }
1485
1786
  if (registryMarketplace !== null) {
1486
1787
  const exact = getPluginId(slug, registryMarketplace);
1487
- return exact in registry.plugins ? exact : null;
1788
+ if (installed.includes(exact)) return exact;
1789
+ return installed.includes(slug) ? slug : null;
1488
1790
  }
1489
- const candidates = Object.keys(registry.plugins).filter((id) => splitPluginId(id)?.name === slug);
1791
+ const candidates = installed.filter((id) => (splitPluginId(id)?.name ?? id) === slug);
1490
1792
  return candidates.length === 1 ? candidates[0] : null;
1491
1793
  }
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;
1794
+ async function uninstallPlugin(slug, agent, scope, cwd = process.cwd()) {
1795
+ assertValidSlug(slug, SLUG_LABEL3);
1796
+ let store;
1498
1797
  try {
1499
- contained = await resolveContained(PLUGINS_CACHE_DIR, installPath);
1798
+ store = pluginStoreFor(agent);
1500
1799
  } catch {
1501
- return;
1800
+ return false;
1502
1801
  }
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);
1802
+ const installed = await store.listInstalled(scope, cwd);
1803
+ const pluginId = await findInstalledPluginId(installed, slug);
1511
1804
  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;
1805
+ return store.remove(pluginId, scope, cwd);
1532
1806
  }
1533
1807
  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("/")));
1808
+ let store;
1809
+ try {
1810
+ store = pluginStoreFor(agent);
1811
+ } catch {
1812
+ return [];
1549
1813
  }
1550
- return JSON.parse(await fetchItemFile(item, PLUGIN_JSON_PATH));
1814
+ const installed = await store.listInstalled(scope, cwd);
1815
+ return installed.map((pluginId) => splitPluginId(pluginId)?.name ?? pluginId);
1551
1816
  }
1552
1817
  async function kindFor(path) {
1553
1818
  return await exists(path) ? "modify" : "create";
@@ -1555,40 +1820,53 @@ async function kindFor(path) {
1555
1820
  async function planPlugin(item, agents, scope, _method, cwd) {
1556
1821
  const changes = [];
1557
1822
  for (const agent of agents) {
1558
- if (agent !== "claude") throw new Error(CLAUDE_ONLY_ERROR3);
1823
+ const store = pluginStoreFor(agent);
1559
1824
  assertValidSlug(item.slug, SLUG_LABEL3);
1560
1825
  const marketplace = resolveMarketplaceName(item);
1561
- const { name, version } = resolvePluginIdentity(item, await readPluginJsonForPlan(item));
1826
+ const { name, version } = resolvePluginIdentity(
1827
+ item,
1828
+ await readManifestForPlan(item, store.manifestPaths)
1829
+ );
1562
1830
  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
- {
1831
+ const cachePath = await store.cachePath(marketplace, name, version);
1832
+ resolveGitCommitSha(item, null);
1833
+ if (cachePath) {
1834
+ changes.push({
1568
1835
  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
- }
1836
+ kind: await kindFor(cachePath),
1837
+ path: cachePath,
1838
+ detail: "plugin files (digest-verified download)"
1839
+ });
1840
+ }
1841
+ const marketplaceMutations = store.ensureMarketplace ? await store.ensureMarketplace(marketplace, item) : [];
1842
+ const context = {
1843
+ item,
1844
+ marketplace,
1845
+ name,
1846
+ version,
1847
+ pluginId,
1848
+ cachePath,
1849
+ gitCommitSha: resolveGitCommitSha(item, null),
1850
+ scope,
1851
+ cwd
1852
+ };
1853
+ for (const mutation of [...marketplaceMutations, ...store.mutations(context)]) {
1854
+ changes.push({
1855
+ agent,
1856
+ kind: await kindFor(mutation.path),
1857
+ path: mutation.path,
1858
+ detail: store.userGlobal ? `${mutation.detail} \u2014 user-global, ${CODING_AGENTS[agent].name} has no project-scoped plugin store` : mutation.detail
1859
+ });
1860
+ if (mutation.creates) {
1861
+ changes.push({ agent, kind: "create", ...mutation.creates });
1584
1862
  }
1585
1863
  }
1586
- changes.push({ agent, kind: await kindFor(settingsPath), path: settingsPath, detail: `enabledPlugins["${pluginId}"] = true` });
1587
1864
  }
1588
1865
  return changes;
1589
1866
  }
1590
1867
  var pluginHandler = {
1591
1868
  type: "plugin",
1869
+ honoursMethod: false,
1592
1870
  async install(item, agents, scope, method, force, cwd) {
1593
1871
  return installPlugin(item, agents, scope, method, force, cwd);
1594
1872
  },
@@ -1601,6 +1879,216 @@ var pluginHandler = {
1601
1879
  plan: planPlugin
1602
1880
  };
1603
1881
 
1882
+ // src/handlers/rule.ts
1883
+ import { dirname as dirname3 } from "path";
1884
+ import { readdir as readdir3, readFile as readFile4, writeFile as writeFile2, mkdir as mkdir4 } from "fs/promises";
1885
+ import chalk8 from "chalk";
1886
+ import ora7 from "ora";
1887
+
1888
+ // src/handlers/ruleTargets.ts
1889
+ import { homedir as homedir5 } from "os";
1890
+ import { join as join6 } from "path";
1891
+ var home2 = homedir5();
1892
+ var markdownFile = (slug) => `${slug}.md`;
1893
+ var claudeTarget = {
1894
+ kind: "file",
1895
+ dir: (scope, cwd) => join6(scope === "user" ? claudeUserRoot() : join6(cwd, ".claude"), "rules"),
1896
+ fileName: markdownFile,
1897
+ keepsFrontmatter: true
1898
+ };
1899
+ var antigravityTarget = {
1900
+ kind: "file",
1901
+ dir: (scope, cwd) => join6(scope === "user" ? join6(home2, ".gemini", "config") : join6(cwd, ".agents"), "rules"),
1902
+ fileName: markdownFile,
1903
+ keepsFrontmatter: true
1904
+ };
1905
+ var copilotTarget = {
1906
+ kind: "file",
1907
+ dir: (scope, cwd) => join6(scope === "user" ? join6(home2, ".copilot") : join6(cwd, ".github"), "instructions"),
1908
+ fileName: (slug) => `${slug}.instructions.md`,
1909
+ keepsFrontmatter: true
1910
+ };
1911
+ var codexTarget = {
1912
+ kind: "section",
1913
+ file: (scope, cwd) => scope === "user" ? join6(home2, ".codex", "AGENTS.md") : join6(cwd, "AGENTS.md")
1914
+ };
1915
+ var openCodeTarget = {
1916
+ kind: "section",
1917
+ file: (scope, cwd) => scope === "user" ? join6(home2, ".config", "opencode", "AGENTS.md") : join6(cwd, "AGENTS.md")
1918
+ };
1919
+ var TARGETS = {
1920
+ claude: claudeTarget,
1921
+ antigravity: antigravityTarget,
1922
+ gemini: antigravityTarget,
1923
+ copilot: copilotTarget,
1924
+ codex: codexTarget,
1925
+ opencode: openCodeTarget
1926
+ };
1927
+ function ruleTargetFor(agent) {
1928
+ const target = TARGETS[canonicalAgent(agent) ?? agent];
1929
+ if (!target || !isTypeSupported("rule", agent)) {
1930
+ throw new Error(`Rules are not supported for ${CODING_AGENTS[agent]?.name ?? agent}`);
1931
+ }
1932
+ return target;
1933
+ }
1934
+ var sectionStart = (slug) => `<!-- seedr:rule:${slug} -->`;
1935
+ var sectionEnd = (slug) => `<!-- /seedr:rule:${slug} -->`;
1936
+ function sectionPattern(slug) {
1937
+ const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1938
+ return new RegExp(`\\n*<!-- seedr:rule:${escaped} -->[\\s\\S]*?<!-- /seedr:rule:${escaped} -->\\n*`, "g");
1939
+ }
1940
+ function stripFrontmatter(content) {
1941
+ const match = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/.exec(content);
1942
+ return match ? content.slice(match[0].length) : content;
1943
+ }
1944
+ function upsertSection(document, slug, content) {
1945
+ const block = `${sectionStart(slug)}
1946
+ ${stripFrontmatter(content).trim()}
1947
+ ${sectionEnd(slug)}`;
1948
+ const existing = sectionPattern(slug);
1949
+ if (existing.test(document)) {
1950
+ return document.replace(sectionPattern(slug), `
1951
+
1952
+ ${block}
1953
+ `);
1954
+ }
1955
+ const base = document.trimEnd();
1956
+ return base.length > 0 ? `${base}
1957
+
1958
+ ${block}
1959
+ ` : `${block}
1960
+ `;
1961
+ }
1962
+ function removeSection(document, slug) {
1963
+ if (!sectionPattern(slug).test(document)) return null;
1964
+ const stripped = document.replace(sectionPattern(slug), "\n\n").trimEnd();
1965
+ return stripped.length > 0 ? `${stripped}
1966
+ ` : "";
1967
+ }
1968
+ function listSections(document) {
1969
+ return [...document.matchAll(/<!-- seedr:rule:([^\s>]+) -->/g)].map((match) => match[1]);
1970
+ }
1971
+
1972
+ // src/handlers/rule.ts
1973
+ var SLUG_LABEL4 = "rule slug";
1974
+ var RULE_FILE = "rule.md";
1975
+ async function readTextOrEmpty3(path) {
1976
+ try {
1977
+ return await readFile4(path, "utf-8");
1978
+ } catch {
1979
+ return "";
1980
+ }
1981
+ }
1982
+ async function readRuleContent(item) {
1983
+ const sourcePath = getItemSourcePath(item);
1984
+ if (sourcePath) {
1985
+ const local = await readTextOrEmpty3(`${sourcePath}/${RULE_FILE}`);
1986
+ if (local) return local;
1987
+ }
1988
+ return fetchItemFile(item, RULE_FILE);
1989
+ }
1990
+ async function destinationFor(target, slug, scope, cwd) {
1991
+ if (target.kind === "section") return target.file(scope, cwd);
1992
+ const dir = target.dir(scope, cwd);
1993
+ return resolveContained(dir, target.fileName(slug));
1994
+ }
1995
+ async function installRuleForAgent(item, agent, scope, force, cwd, content) {
1996
+ const spinner = ora7(`Installing ${item.name} for ${CODING_AGENTS[agent].name}...`).start();
1997
+ try {
1998
+ const target = ruleTargetFor(agent);
1999
+ const destination = await destinationFor(target, item.slug, scope, cwd);
2000
+ await mkdir4(dirname3(destination), { recursive: true });
2001
+ if (target.kind === "file") {
2002
+ await assertOverwritable(destination, force);
2003
+ await writeFile2(
2004
+ destination,
2005
+ target.keepsFrontmatter ? content : stripFrontmatter(content),
2006
+ "utf-8"
2007
+ );
2008
+ } else {
2009
+ const document = await readTextOrEmpty3(destination);
2010
+ await writeFile2(destination, upsertSection(document, item.slug, content), "utf-8");
2011
+ }
2012
+ spinner.succeed(brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`));
2013
+ return { agent, success: true, path: destination };
2014
+ } catch (error2) {
2015
+ const message = error2 instanceof Error ? error2.message : "Unknown error";
2016
+ spinner.fail(chalk8.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${message}`));
2017
+ return { agent, success: false, path: "", error: message };
2018
+ }
2019
+ }
2020
+ async function installRule(item, agents, scope, _method, force, cwd = process.cwd()) {
2021
+ assertValidSlug(item.slug, SLUG_LABEL4);
2022
+ const content = await readRuleContent(item);
2023
+ const results = [];
2024
+ for (const agent of agents) {
2025
+ results.push(await installRuleForAgent(item, agent, scope, force, cwd, content));
2026
+ }
2027
+ return results;
2028
+ }
2029
+ async function uninstallRule(slug, agent, scope, cwd = process.cwd()) {
2030
+ assertValidSlug(slug, SLUG_LABEL4);
2031
+ let target;
2032
+ try {
2033
+ target = ruleTargetFor(agent);
2034
+ } catch {
2035
+ return false;
2036
+ }
2037
+ const destination = await destinationFor(target, slug, scope, cwd);
2038
+ if (target.kind === "file") return removePathEntry(destination);
2039
+ const document = await readTextOrEmpty3(destination);
2040
+ const stripped = removeSection(document, slug);
2041
+ if (stripped === null) return false;
2042
+ await writeFile2(destination, stripped, "utf-8");
2043
+ return true;
2044
+ }
2045
+ async function getInstalledRules(agent, scope, cwd = process.cwd()) {
2046
+ let target;
2047
+ try {
2048
+ target = ruleTargetFor(agent);
2049
+ } catch {
2050
+ return [];
2051
+ }
2052
+ if (target.kind === "section") {
2053
+ return listSections(await readTextOrEmpty3(target.file(scope, cwd)));
2054
+ }
2055
+ const dir = target.dir(scope, cwd);
2056
+ if (!await exists(dir)) return [];
2057
+ const suffix = target.fileName("");
2058
+ const entries = await readdir3(dir, { withFileTypes: true });
2059
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(suffix) && !entry.name.startsWith(".")).map((entry) => entry.name.slice(0, -suffix.length));
2060
+ }
2061
+ async function planRule(item, agents, scope, _method, cwd) {
2062
+ assertValidSlug(item.slug, SLUG_LABEL4);
2063
+ const changes = [];
2064
+ for (const agent of agents) {
2065
+ const target = ruleTargetFor(agent);
2066
+ const destination = await destinationFor(target, item.slug, scope, cwd);
2067
+ const present = await exists(destination);
2068
+ changes.push({
2069
+ agent,
2070
+ kind: present ? "modify" : "create",
2071
+ path: destination,
2072
+ detail: target.kind === "file" ? "rule file" : `merged section <!-- seedr:rule:${item.slug} --> (the rest of the file is untouched)`
2073
+ });
2074
+ }
2075
+ return changes;
2076
+ }
2077
+ var ruleHandler = {
2078
+ type: "rule",
2079
+ honoursMethod: false,
2080
+ async install(item, agents, scope, method, force, cwd) {
2081
+ return installRule(item, agents, scope, method, force, cwd);
2082
+ },
2083
+ async uninstall(slug, agent, scope, cwd) {
2084
+ return uninstallRule(slug, agent, scope, cwd);
2085
+ },
2086
+ async listInstalled(agent, scope, cwd) {
2087
+ return getInstalledRules(agent, scope, cwd);
2088
+ },
2089
+ plan: planRule
2090
+ };
2091
+
1604
2092
  // src/handlers/index.ts
1605
2093
  registerHandler(skillHandler);
1606
2094
  registerHandler(agentHandler);
@@ -1609,6 +2097,7 @@ registerHandler(hookHandler);
1609
2097
  registerHandler(mcpHandler);
1610
2098
  registerHandler(settingsHandler);
1611
2099
  registerHandler(pluginHandler);
2100
+ registerHandler(ruleHandler);
1612
2101
 
1613
2102
  // src/commands/add.ts
1614
2103
  var CANCELLED_MESSAGE = "Operation cancelled";
@@ -1689,8 +2178,8 @@ function formatPlan(changes) {
1689
2178
  for (const [label, group] of groups) {
1690
2179
  lines.push(` ${label}:`);
1691
2180
  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}`);
2181
+ const detail = change.detail ? ` ${chalk9.gray(`\u2014 ${change.detail}`)}` : "";
2182
+ lines.push(` ${chalk9.gray(`[${KIND_LABEL[change.kind]}]`)} ${change.path}${detail}`);
1694
2183
  }
1695
2184
  }
1696
2185
  return lines;
@@ -1800,13 +2289,13 @@ function printInstallSummary(results) {
1800
2289
  if (successful.length > 0) {
1801
2290
  success(`Installed for ${successful.length} agent(s)`);
1802
2291
  for (const r of successful) {
1803
- console.log(chalk8.gray(` \u2192 ${r.path}`));
2292
+ console.log(chalk9.gray(` \u2192 ${r.path}`));
1804
2293
  }
1805
2294
  }
1806
2295
  if (failed.length > 0) {
1807
2296
  error(`Failed for ${failed.length} agent(s)`);
1808
2297
  for (const r of failed) {
1809
- console.log(chalk8.red(` \xD7 ${r.agent}: ${r.error}`));
2298
+ console.log(chalk9.red(` \xD7 ${r.agent}: ${r.error}`));
1810
2299
  }
1811
2300
  }
1812
2301
  return exitCode;
@@ -1820,7 +2309,7 @@ async function runAdd(name, options, cwd = process.cwd()) {
1820
2309
  const item = await resolveItem(name, options.type);
1821
2310
  if (item === CANCELLED) return 0;
1822
2311
  if (!item) return 1;
1823
- step(`Selected: ${brand(item.name)} ${chalk8.gray(`(${item.type})`)} ${chalk8.gray(`- ${item.description}`)}`);
2312
+ step(`Selected: ${brand(item.name)} ${chalk9.gray(`(${item.type})`)} ${chalk9.gray(`- ${item.description}`)}`);
1824
2313
  const handler = getHandler(item.type);
1825
2314
  if (!handler) {
1826
2315
  error(`No handler found for type "${item.type}"`);
@@ -1835,7 +2324,11 @@ async function runAdd(name, options, cwd = process.cwd()) {
1835
2324
  step(`Scope: ${brand(scope)}`);
1836
2325
  const method = await chooseMethod(options, item, agents, scope, cwd);
1837
2326
  if (method === CANCELLED) return 0;
1838
- step(`Method: ${brand(method)}`);
2327
+ if (method === "symlink" && handler.honoursMethod === false) {
2328
+ step(`Method: ${brand("copy")} (${item.type} content cannot be symlinked)`);
2329
+ } else {
2330
+ step(`Method: ${brand(method)}`);
2331
+ }
1839
2332
  if (options.dryRun) {
1840
2333
  info("Dry run - no files will be written");
1841
2334
  printPlan("Would write:", await planInstall(handler, item, agents, scope, method, cwd));
@@ -1857,7 +2350,7 @@ async function runAdd(name, options, cwd = process.cwd()) {
1857
2350
  if (exitCode === 0) outro("Installation complete");
1858
2351
  return exitCode;
1859
2352
  }
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(
2353
+ 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
2354
  "-a, --agents <agents>",
1862
2355
  "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
2356
  ).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 +2367,18 @@ Telemetry: ${TELEMETRY_HELP_TEXT}.`).action(async (name, options) => {
1874
2367
 
1875
2368
  // src/commands/list.ts
1876
2369
  import { Command as Command2 } from "commander";
1877
- import chalk9 from "chalk";
2370
+ import chalk10 from "chalk";
1878
2371
  var SEPARATOR_WIDTH = 40;
1879
2372
  var SLUG_COLUMN_WIDTH = 24;
1880
2373
  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")
2374
+ skill: chalk10.magenta,
2375
+ hook: chalk10.hex("#a855f7"),
2376
+ agent: chalk10.blue,
2377
+ plugin: chalk10.hex("#6366f1"),
2378
+ command: chalk10.hex("#f59e0b"),
2379
+ settings: chalk10.hex("#f97316"),
2380
+ mcp: chalk10.hex("#2dd4bf"),
2381
+ rule: chalk10.hex("#84cc16")
1888
2382
  };
1889
2383
  async function collectInstalledItems(params) {
1890
2384
  const types = params.types ?? getRegisteredTypes();
@@ -1913,7 +2407,7 @@ function resolveListAgents(agentsArg) {
1913
2407
  async function runList(options, cwd = process.cwd()) {
1914
2408
  const optionError = validateType(options.type) || validateScope(options.scope);
1915
2409
  if (optionError) {
1916
- console.log(chalk9.red(optionError));
2410
+ console.log(chalk10.red(optionError));
1917
2411
  return 1;
1918
2412
  }
1919
2413
  const type = options.type;
@@ -1922,14 +2416,14 @@ async function runList(options, cwd = process.cwd()) {
1922
2416
  }
1923
2417
  const agents = resolveListAgents(options.agents);
1924
2418
  if (typeof agents === "string") {
1925
- console.log(chalk9.red(agents));
2419
+ console.log(chalk10.red(agents));
1926
2420
  return 1;
1927
2421
  }
1928
2422
  const scope = options.scope ?? "project";
1929
2423
  await listInstalled({ types: type ? [type] : void 0, agents, scope, cwd });
1930
2424
  return 0;
1931
2425
  }
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) => {
2426
+ 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
2427
  try {
1934
2428
  const exitCode = await runList(options);
1935
2429
  if (exitCode !== 0) process.exit(exitCode);
@@ -1942,32 +2436,32 @@ async function listAvailable(type, label) {
1942
2436
  const items = label ? all.filter((item) => item.label === label) : all;
1943
2437
  if (items.length === 0 && label) {
1944
2438
  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(", ")}`));
2439
+ console.log(chalk10.red(`No item carries the label "${label}".`));
2440
+ if (known.length > 0) console.log(chalk10.gray(`Labels in use: ${known.join(", ")}`));
1947
2441
  return 1;
1948
2442
  }
1949
2443
  if (items.length === 0) {
1950
- console.log(chalk9.yellow("No items found in registry"));
2444
+ console.log(chalk10.yellow("No items found in registry"));
1951
2445
  return 0;
1952
2446
  }
1953
2447
  const grouped = groupByType(items);
1954
2448
  for (const [itemType, typeItems] of Object.entries(grouped)) {
1955
- const colorFn = TYPE_COLORS[itemType] ?? chalk9.white;
2449
+ const colorFn = TYPE_COLORS[itemType] ?? chalk10.white;
1956
2450
  console.log(colorFn(`
1957
2451
  ${itemType.toUpperCase()}S`));
1958
- console.log(chalk9.gray("\u2500".repeat(SEPARATOR_WIDTH)));
2452
+ console.log(chalk10.gray("\u2500".repeat(SEPARATOR_WIDTH)));
1959
2453
  for (const item of typeItems) {
1960
2454
  const compatIcons = [...new Set(item.compatibility.map((a) => CODING_AGENTS[a]?.shortName ?? a))].join(" ");
1961
- const featured = item.featured ? chalk9.yellow("\u2605 ") : " ";
2455
+ const featured = item.featured ? chalk10.yellow("\u2605 ") : " ";
1962
2456
  console.log(
1963
- `${featured}${chalk9.white(item.slug.padEnd(SLUG_COLUMN_WIDTH))} ${chalk9.gray(compatIcons)}`
2457
+ `${featured}${chalk10.white(item.slug.padEnd(SLUG_COLUMN_WIDTH))} ${chalk10.gray(compatIcons)}`
1964
2458
  );
1965
- console.log(` ${chalk9.gray(item.description)}`);
2459
+ console.log(` ${chalk10.gray(item.description)}`);
1966
2460
  }
1967
2461
  }
1968
2462
  console.log("");
1969
2463
  console.log(
1970
- chalk9.gray(`Total: ${items.length} items. Use 'npx @danieldeusing/seedr add <name>' to install.`)
2464
+ chalk10.gray(`Total: ${items.length} items. Use 'npx @danieldeusing/seedr add <name>' to install.`)
1971
2465
  );
1972
2466
  return 0;
1973
2467
  }
@@ -1981,23 +2475,23 @@ Installed items (${params.scope} scope):
1981
2475
  for (const group of groups) {
1982
2476
  if (group.type !== currentType) {
1983
2477
  currentType = group.type;
1984
- const colorFn = TYPE_COLORS[group.type] ?? chalk9.white;
2478
+ const colorFn = TYPE_COLORS[group.type] ?? chalk10.white;
1985
2479
  console.log(colorFn(`${group.type.toUpperCase()}S`));
1986
2480
  }
1987
- console.log(chalk9.blue(` ${CODING_AGENTS[group.agent].name}`));
2481
+ console.log(chalk10.blue(` ${CODING_AGENTS[group.agent].name}`));
1988
2482
  for (const slug of group.slugs) {
1989
- console.log(` ${chalk9.white(slug)}`);
2483
+ console.log(` ${chalk10.white(slug)}`);
1990
2484
  total++;
1991
2485
  }
1992
2486
  console.log("");
1993
2487
  }
1994
2488
  if (!params.types || params.types.includes("settings")) {
1995
- console.log(chalk9.gray(`Note: ${SETTINGS_NOT_DISCOVERABLE}`));
2489
+ console.log(chalk10.gray(`Note: ${SETTINGS_NOT_DISCOVERABLE}`));
1996
2490
  }
1997
2491
  if (total === 0) {
1998
- console.log(chalk9.yellow("No items installed"));
2492
+ console.log(chalk10.yellow("No items installed"));
1999
2493
  } else {
2000
- console.log(chalk9.gray(`Total: ${total} installed`));
2494
+ console.log(chalk10.gray(`Total: ${total} installed`));
2001
2495
  }
2002
2496
  }
2003
2497
  function groupByType(items) {
@@ -2015,8 +2509,8 @@ function groupByType(items) {
2015
2509
 
2016
2510
  // src/commands/remove.ts
2017
2511
  import { Command as Command3 } from "commander";
2018
- import chalk10 from "chalk";
2019
- import ora7 from "ora";
2512
+ import chalk11 from "chalk";
2513
+ import ora8 from "ora";
2020
2514
  async function findInstalledAgents(slug, type, scope, cwd) {
2021
2515
  const handler = getHandler(type);
2022
2516
  if (!handler) return [];
@@ -2034,13 +2528,19 @@ async function removeFromAgents(slug, type, agents, scope, cwd) {
2034
2528
  if (!handler) return 0;
2035
2529
  let successCount = 0;
2036
2530
  for (const agent of agents) {
2037
- const spinner = ora7(`Removing from ${CODING_AGENTS[agent].name}...`).start();
2531
+ if (!isTypeSupported(type, agent)) {
2532
+ ora8().start().info(
2533
+ chalk11.gray(`Skipped ${CODING_AGENTS[agent].name}: ${describeIncompatibility(type, agent)}`)
2534
+ );
2535
+ continue;
2536
+ }
2537
+ const spinner = ora8(`Removing from ${CODING_AGENTS[agent].name}...`).start();
2038
2538
  const removed = await handler.uninstall(slug, agent, scope, cwd);
2039
2539
  if (removed) {
2040
2540
  spinner.succeed(brand(`Removed from ${CODING_AGENTS[agent].name}`));
2041
2541
  successCount++;
2042
2542
  } else {
2043
- spinner.info(chalk10.gray(`Not found in ${CODING_AGENTS[agent].name}`));
2543
+ spinner.info(chalk11.gray(`Not found in ${CODING_AGENTS[agent].name}`));
2044
2544
  }
2045
2545
  }
2046
2546
  return successCount;
@@ -2065,14 +2565,14 @@ function validateRemoveRequest(name, options) {
2065
2565
  async function runRemove(name, options, cwd = process.cwd()) {
2066
2566
  const requestError = validateRemoveRequest(name, options);
2067
2567
  if (requestError) {
2068
- console.log(chalk10.red(requestError));
2568
+ console.log(chalk11.red(requestError));
2069
2569
  return 1;
2070
2570
  }
2071
2571
  const scope = options.scope ?? "project";
2072
2572
  const type = options.type;
2073
2573
  const handler = getHandler(type);
2074
2574
  if (!handler) {
2075
- console.log(chalk10.red(`No handler found for type "${type}"`));
2575
+ console.log(chalk11.red(`No handler found for type "${type}"`));
2076
2576
  return 1;
2077
2577
  }
2078
2578
  let agents;
@@ -2085,11 +2585,11 @@ async function runRemove(name, options, cwd = process.cwd()) {
2085
2585
  }
2086
2586
  if (agents.length === 0) {
2087
2587
  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"));
2588
+ console.log(chalk11.yellow(`Cannot detect where "${name}" is installed: ${SETTINGS_NOT_DISCOVERABLE}`));
2589
+ console.log(chalk11.gray("Name the agents explicitly, e.g. --agents claude"));
2090
2590
  return 1;
2091
2591
  }
2092
- console.log(chalk10.yellow(`${type} "${name}" is not installed in ${scope} scope`));
2592
+ console.log(chalk11.yellow(`${type} "${name}" is not installed in ${scope} scope`));
2093
2593
  return 0;
2094
2594
  }
2095
2595
  if (!options.yes) {
@@ -2102,7 +2602,7 @@ Will remove ${type} "${name}" from:`));
2102
2602
  const answer = await confirm("Proceed with removal?");
2103
2603
  const confirmed = !p.isCancel(answer) && answer;
2104
2604
  if (!confirmed) {
2105
- console.log(chalk10.yellow("Removal cancelled"));
2605
+ console.log(chalk11.yellow("Removal cancelled"));
2106
2606
  return 0;
2107
2607
  }
2108
2608
  }
@@ -2112,10 +2612,10 @@ Will remove ${type} "${name}" from:`));
2112
2612
  console.log(brand(`Successfully removed from ${successCount} agent(s)`));
2113
2613
  return 0;
2114
2614
  }
2115
- console.log(chalk10.yellow("Nothing to remove"));
2615
+ console.log(chalk11.yellow("Nothing to remove"));
2116
2616
  return 0;
2117
2617
  }
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(
2618
+ 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
2619
  "-a, --agents <agents>",
2120
2620
  "Comma-separated coding agents or 'all'"
2121
2621
  ).option(
@@ -2133,9 +2633,9 @@ var removeCommand = new Command3("remove").alias("rm").description("Remove an in
2133
2633
 
2134
2634
  // src/commands/init.ts
2135
2635
  import { Command as Command4 } from "commander";
2136
- import chalk11 from "chalk";
2137
- import ora8 from "ora";
2138
- import { join as join5 } from "path";
2636
+ import chalk12 from "chalk";
2637
+ import ora9 from "ora";
2638
+ import { join as join7 } from "path";
2139
2639
  function readmeFor(agent) {
2140
2640
  return `# ${CODING_AGENTS[agent].name} Configuration
2141
2641
 
@@ -2153,7 +2653,7 @@ async function initializeAgent(agent, cwd) {
2153
2653
  const path = getAgentPath(agent, "project", cwd);
2154
2654
  if (await exists(path)) return false;
2155
2655
  await ensureDir(path);
2156
- await writeTextFile(join5(path, "README.md"), readmeFor(agent));
2656
+ await writeTextFile(join7(path, "README.md"), readmeFor(agent));
2157
2657
  return true;
2158
2658
  }
2159
2659
  async function runInit(options, cwd = process.cwd()) {
@@ -2164,13 +2664,13 @@ async function runInit(options, cwd = process.cwd()) {
2164
2664
  } else {
2165
2665
  const parsed = parseAgentsArgStrict(agentsArg);
2166
2666
  if (parsed.unknown.length > 0) {
2167
- console.error(chalk11.red(`Unknown agent(s): ${parsed.unknown.join(", ")}. Valid agents: ${ALL_AGENTS.join(", ")} or "all"`));
2667
+ console.error(chalk12.red(`Unknown agent(s): ${parsed.unknown.join(", ")}. Valid agents: ${ALL_AGENTS.join(", ")} or "all"`));
2168
2668
  return 1;
2169
2669
  }
2170
2670
  agents = parsed.agents;
2171
2671
  }
2172
2672
  if (agents.length === 0) {
2173
- console.error(chalk11.red("No valid agents specified"));
2673
+ console.error(chalk12.red("No valid agents specified"));
2174
2674
  return 1;
2175
2675
  }
2176
2676
  console.log(brand("\nWill initialize configuration for:"));
@@ -2182,16 +2682,16 @@ async function runInit(options, cwd = process.cwd()) {
2182
2682
  const answer = await confirm("Proceed?");
2183
2683
  const confirmed = !p.isCancel(answer) && answer;
2184
2684
  if (!confirmed) {
2185
- console.log(chalk11.yellow("Cancelled"));
2685
+ console.log(chalk12.yellow("Cancelled"));
2186
2686
  return 0;
2187
2687
  }
2188
2688
  }
2189
2689
  for (const agent of agents) {
2190
- const spinner = ora8(`Initializing ${CODING_AGENTS[agent].name}...`).start();
2690
+ const spinner = ora9(`Initializing ${CODING_AGENTS[agent].name}...`).start();
2191
2691
  if (await initializeAgent(agent, cwd)) {
2192
2692
  spinner.succeed(brand(`Initialized ${CODING_AGENTS[agent].name}`));
2193
2693
  } else {
2194
- spinner.info(chalk11.gray(`${CODING_AGENTS[agent].name} already initialized`));
2694
+ spinner.info(chalk12.gray(`${CODING_AGENTS[agent].name} already initialized`));
2195
2695
  }
2196
2696
  }
2197
2697
  console.log("");
@@ -2214,7 +2714,7 @@ var initCommand = new Command4("init").description("Initialize coding agent conf
2214
2714
  // package.json
2215
2715
  var package_default = {
2216
2716
  name: "@danieldeusing/seedr",
2217
- version: "0.1.97",
2717
+ version: "0.1.98",
2218
2718
  description: "Seed your projects with AI configurations",
2219
2719
  type: "module",
2220
2720
  bin: {