@danieldeusing/seedr 0.1.80
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/chunk-5XFKC3T7.js +681 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1232 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +140 -0
- package/package.json +70 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1232 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ALL_AGENTS,
|
|
4
|
+
CODING_AGENTS,
|
|
5
|
+
brand,
|
|
6
|
+
cancelled,
|
|
7
|
+
confirm,
|
|
8
|
+
ensureDir,
|
|
9
|
+
error,
|
|
10
|
+
exists,
|
|
11
|
+
fetchItemToDestination,
|
|
12
|
+
getAgentPath,
|
|
13
|
+
getAgentsPath,
|
|
14
|
+
getContentPath,
|
|
15
|
+
getInstalledSkills,
|
|
16
|
+
getItem,
|
|
17
|
+
getItemContent,
|
|
18
|
+
getItemSourcePath,
|
|
19
|
+
getMcpPath,
|
|
20
|
+
getSettingsPath,
|
|
21
|
+
info,
|
|
22
|
+
installDirectory,
|
|
23
|
+
installFile,
|
|
24
|
+
intro,
|
|
25
|
+
listItems,
|
|
26
|
+
outro,
|
|
27
|
+
p,
|
|
28
|
+
parseAgentsArg,
|
|
29
|
+
printLogo,
|
|
30
|
+
removeFile,
|
|
31
|
+
searchItems,
|
|
32
|
+
selectAgents,
|
|
33
|
+
selectMethod,
|
|
34
|
+
selectScope,
|
|
35
|
+
selectSkill,
|
|
36
|
+
skillHandler,
|
|
37
|
+
step,
|
|
38
|
+
success,
|
|
39
|
+
warn,
|
|
40
|
+
writeTextFile
|
|
41
|
+
} from "./chunk-5XFKC3T7.js";
|
|
42
|
+
|
|
43
|
+
// src/cli.ts
|
|
44
|
+
import { Command as Command5 } from "commander";
|
|
45
|
+
|
|
46
|
+
// src/commands/add.ts
|
|
47
|
+
import { Command } from "commander";
|
|
48
|
+
import chalk7 from "chalk";
|
|
49
|
+
|
|
50
|
+
// src/handlers/registry.ts
|
|
51
|
+
var handlers = /* @__PURE__ */ new Map();
|
|
52
|
+
function registerHandler(handler) {
|
|
53
|
+
handlers.set(handler.type, handler);
|
|
54
|
+
}
|
|
55
|
+
function getHandler(type) {
|
|
56
|
+
return handlers.get(type);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/utils/errors.ts
|
|
60
|
+
import chalk from "chalk";
|
|
61
|
+
function handleCommandError(error2) {
|
|
62
|
+
console.error(
|
|
63
|
+
chalk.red(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`)
|
|
64
|
+
);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/utils/analytics.ts
|
|
69
|
+
var ANALYTICS_URL = "https://seedr.danieldeusing.de/api/installs";
|
|
70
|
+
function trackInstalls(slug, type, results, scope) {
|
|
71
|
+
if (process.env.SEEDR_NO_TELEMETRY) return;
|
|
72
|
+
for (const result of results) {
|
|
73
|
+
if (!result.success) continue;
|
|
74
|
+
fetch(ANALYTICS_URL, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: { "Content-Type": "application/json" },
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
slug,
|
|
79
|
+
type,
|
|
80
|
+
agent: result.agent,
|
|
81
|
+
scope,
|
|
82
|
+
version: "0.1.80"
|
|
83
|
+
}),
|
|
84
|
+
signal: AbortSignal.timeout(4e3)
|
|
85
|
+
}).catch(() => {
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/config/compatibility.ts
|
|
91
|
+
var AGENT_COMPATIBILITY = {
|
|
92
|
+
skill: ["claude", "copilot", "gemini", "codex", "opencode"],
|
|
93
|
+
command: ["claude"],
|
|
94
|
+
agent: ["claude"],
|
|
95
|
+
hook: ["claude"],
|
|
96
|
+
plugin: ["claude"],
|
|
97
|
+
settings: ["claude"],
|
|
98
|
+
mcp: ["claude", "copilot", "gemini", "codex", "opencode"]
|
|
99
|
+
};
|
|
100
|
+
function filterCompatibleAgents(type, agents) {
|
|
101
|
+
const compatible = AGENT_COMPATIBILITY[type];
|
|
102
|
+
return agents.filter((a) => compatible.includes(a));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/handlers/agent.ts
|
|
106
|
+
import { join } from "path";
|
|
107
|
+
import { readdir } from "fs/promises";
|
|
108
|
+
import chalk2 from "chalk";
|
|
109
|
+
import ora from "ora";
|
|
110
|
+
async function installAgentForCodingAgent(item, agent, scope, method, cwd) {
|
|
111
|
+
const spinner = ora(
|
|
112
|
+
`Installing ${item.name} for ${CODING_AGENTS[agent].name}...`
|
|
113
|
+
).start();
|
|
114
|
+
try {
|
|
115
|
+
const destDir = getContentPath(agent, "agent", scope, cwd);
|
|
116
|
+
if (!destDir) {
|
|
117
|
+
throw new Error(`${CODING_AGENTS[agent].name} does not support agents`);
|
|
118
|
+
}
|
|
119
|
+
const destPath = join(destDir, `${item.slug}.md`);
|
|
120
|
+
const sourcePath = getItemSourcePath(item);
|
|
121
|
+
if (sourcePath && method === "symlink") {
|
|
122
|
+
const sourceFile = join(sourcePath, "AGENT.md");
|
|
123
|
+
if (await exists(sourceFile)) {
|
|
124
|
+
await installFile(sourceFile, destPath, "symlink");
|
|
125
|
+
} else {
|
|
126
|
+
const content = await getItemContent(item);
|
|
127
|
+
await ensureDir(destDir);
|
|
128
|
+
await writeTextFile(destPath, content);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
const content = await getItemContent(item);
|
|
132
|
+
await ensureDir(destDir);
|
|
133
|
+
await writeTextFile(destPath, content);
|
|
134
|
+
}
|
|
135
|
+
spinner.succeed(
|
|
136
|
+
brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`)
|
|
137
|
+
);
|
|
138
|
+
return { agent, success: true, path: destPath };
|
|
139
|
+
} catch (error2) {
|
|
140
|
+
const errorMsg = error2 instanceof Error ? error2.message : "Unknown error";
|
|
141
|
+
spinner.fail(
|
|
142
|
+
chalk2.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${errorMsg}`)
|
|
143
|
+
);
|
|
144
|
+
return { agent, success: false, path: "", error: errorMsg };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function installAgent(item, agents, scope, method, cwd = process.cwd()) {
|
|
148
|
+
const results = [];
|
|
149
|
+
for (const agent of agents) {
|
|
150
|
+
const result = await installAgentForCodingAgent(item, agent, scope, method, cwd);
|
|
151
|
+
results.push(result);
|
|
152
|
+
}
|
|
153
|
+
return results;
|
|
154
|
+
}
|
|
155
|
+
async function uninstallAgent(slug, agent, scope, cwd = process.cwd()) {
|
|
156
|
+
const destDir = getContentPath(agent, "agent", scope, cwd);
|
|
157
|
+
if (!destDir) return false;
|
|
158
|
+
const destPath = join(destDir, `${slug}.md`);
|
|
159
|
+
if (!await exists(destPath)) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return removeFile(destPath);
|
|
163
|
+
}
|
|
164
|
+
async function getInstalledAgents(agent, scope, cwd = process.cwd()) {
|
|
165
|
+
const destDir = getContentPath(agent, "agent", scope, cwd);
|
|
166
|
+
if (!destDir || !await exists(destDir)) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
const files = await readdir(destDir);
|
|
170
|
+
return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(".md", ""));
|
|
171
|
+
}
|
|
172
|
+
var agentHandler = {
|
|
173
|
+
type: "agent",
|
|
174
|
+
async install(item, agents, scope, method, cwd) {
|
|
175
|
+
return installAgent(item, agents, scope, method, cwd);
|
|
176
|
+
},
|
|
177
|
+
async uninstall(slug, agent, scope, cwd) {
|
|
178
|
+
return uninstallAgent(slug, agent, scope, cwd);
|
|
179
|
+
},
|
|
180
|
+
async listInstalled(agent, scope, cwd) {
|
|
181
|
+
return getInstalledAgents(agent, scope, cwd);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// src/handlers/hook.ts
|
|
186
|
+
import { join as join2, basename } from "path";
|
|
187
|
+
import { homedir } from "os";
|
|
188
|
+
import { mkdir, copyFile, chmod, rm } from "fs/promises";
|
|
189
|
+
import chalk3 from "chalk";
|
|
190
|
+
import ora2 from "ora";
|
|
191
|
+
|
|
192
|
+
// src/utils/json.ts
|
|
193
|
+
import { readFile, writeFile } from "fs/promises";
|
|
194
|
+
import { dirname } from "path";
|
|
195
|
+
async function readJson(path) {
|
|
196
|
+
try {
|
|
197
|
+
const content = await readFile(path, "utf-8");
|
|
198
|
+
return JSON.parse(content);
|
|
199
|
+
} catch (error2) {
|
|
200
|
+
if (error2.code === "ENOENT") {
|
|
201
|
+
return {};
|
|
202
|
+
}
|
|
203
|
+
throw error2;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function writeJson(path, data) {
|
|
207
|
+
await ensureDir(dirname(path));
|
|
208
|
+
await writeFile(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
209
|
+
}
|
|
210
|
+
function deepMerge(target, source) {
|
|
211
|
+
const result = { ...target };
|
|
212
|
+
for (const key of Object.keys(source)) {
|
|
213
|
+
const sourceValue = source[key];
|
|
214
|
+
const targetValue = result[key];
|
|
215
|
+
if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue !== null && typeof targetValue === "object" && !Array.isArray(targetValue)) {
|
|
216
|
+
result[key] = deepMerge(
|
|
217
|
+
targetValue,
|
|
218
|
+
sourceValue
|
|
219
|
+
);
|
|
220
|
+
} else if (sourceValue !== void 0) {
|
|
221
|
+
result[key] = sourceValue;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/handlers/hook.ts
|
|
228
|
+
function getHooksDir(scope, cwd) {
|
|
229
|
+
switch (scope) {
|
|
230
|
+
case "user":
|
|
231
|
+
return join2(homedir(), ".claude", "hooks");
|
|
232
|
+
case "project":
|
|
233
|
+
case "local":
|
|
234
|
+
return join2(cwd, ".claude", "hooks");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function getScriptPath(scope, scriptName) {
|
|
238
|
+
switch (scope) {
|
|
239
|
+
case "user":
|
|
240
|
+
return `~/.claude/hooks/${scriptName}`;
|
|
241
|
+
case "project":
|
|
242
|
+
case "local":
|
|
243
|
+
return `.claude/hooks/${scriptName}`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function findScriptFile(item) {
|
|
247
|
+
const files = item.contents?.files;
|
|
248
|
+
if (!files) return null;
|
|
249
|
+
for (const file of files) {
|
|
250
|
+
if (file.type === "file" && file.name.endsWith(".sh")) {
|
|
251
|
+
return file.name;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
async function installHookForAgent(item, agent, scope, _method, cwd) {
|
|
257
|
+
const spinner = ora2(
|
|
258
|
+
`Installing ${item.name} for ${CODING_AGENTS[agent].name}...`
|
|
259
|
+
).start();
|
|
260
|
+
try {
|
|
261
|
+
if (agent !== "claude") {
|
|
262
|
+
throw new Error("Hooks are only supported for Claude Code");
|
|
263
|
+
}
|
|
264
|
+
const triggers = item.contents?.triggers;
|
|
265
|
+
if (!triggers || triggers.length === 0) {
|
|
266
|
+
throw new Error("No triggers defined for this hook");
|
|
267
|
+
}
|
|
268
|
+
const scriptFile = findScriptFile(item);
|
|
269
|
+
if (!scriptFile) {
|
|
270
|
+
throw new Error("No script file found in hook");
|
|
271
|
+
}
|
|
272
|
+
const hooksDir = getHooksDir(scope, cwd);
|
|
273
|
+
await mkdir(hooksDir, { recursive: true });
|
|
274
|
+
const sourcePath = getItemSourcePath(item);
|
|
275
|
+
const destScriptPath = join2(hooksDir, scriptFile);
|
|
276
|
+
if (sourcePath && await exists(sourcePath)) {
|
|
277
|
+
const sourceScriptPath = join2(sourcePath, scriptFile);
|
|
278
|
+
await copyFile(sourceScriptPath, destScriptPath);
|
|
279
|
+
} else {
|
|
280
|
+
const tempDir = join2(cwd, ".claude", ".tmp", item.slug);
|
|
281
|
+
await fetchItemToDestination(item, tempDir);
|
|
282
|
+
await copyFile(join2(tempDir, scriptFile), destScriptPath);
|
|
283
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
284
|
+
}
|
|
285
|
+
await chmod(destScriptPath, 493);
|
|
286
|
+
const settingsPath = getSettingsPath(scope, cwd);
|
|
287
|
+
const settings = await readJson(settingsPath);
|
|
288
|
+
settings.hooks = settings.hooks || {};
|
|
289
|
+
const scriptPath = getScriptPath(scope, scriptFile);
|
|
290
|
+
for (const trigger of triggers) {
|
|
291
|
+
const event = trigger.event;
|
|
292
|
+
const matcher = trigger.matcher;
|
|
293
|
+
if (!settings.hooks[event]) {
|
|
294
|
+
settings.hooks[event] = [];
|
|
295
|
+
}
|
|
296
|
+
const existingEntry = settings.hooks[event].find(
|
|
297
|
+
(e) => e.matcher === matcher
|
|
298
|
+
);
|
|
299
|
+
const hookCommand = { type: "command", command: scriptPath };
|
|
300
|
+
if (existingEntry) {
|
|
301
|
+
const alreadyExists = existingEntry.hooks.some(
|
|
302
|
+
(h) => h.command === scriptPath
|
|
303
|
+
);
|
|
304
|
+
if (!alreadyExists) {
|
|
305
|
+
existingEntry.hooks.push(hookCommand);
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
const newEntry = {
|
|
309
|
+
hooks: [hookCommand]
|
|
310
|
+
};
|
|
311
|
+
if (matcher) {
|
|
312
|
+
newEntry.matcher = matcher;
|
|
313
|
+
}
|
|
314
|
+
settings.hooks[event].push(newEntry);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
await writeJson(settingsPath, settings);
|
|
318
|
+
spinner.succeed(
|
|
319
|
+
brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`)
|
|
320
|
+
);
|
|
321
|
+
return { agent, success: true, path: destScriptPath };
|
|
322
|
+
} catch (error2) {
|
|
323
|
+
const errorMsg = error2 instanceof Error ? error2.message : "Unknown error";
|
|
324
|
+
spinner.fail(
|
|
325
|
+
chalk3.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${errorMsg}`)
|
|
326
|
+
);
|
|
327
|
+
return { agent, success: false, path: "", error: errorMsg };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function installHook(item, agents, scope, method, cwd = process.cwd()) {
|
|
331
|
+
const results = [];
|
|
332
|
+
for (const agent of agents) {
|
|
333
|
+
const result = await installHookForAgent(item, agent, scope, method, cwd);
|
|
334
|
+
results.push(result);
|
|
335
|
+
}
|
|
336
|
+
return results;
|
|
337
|
+
}
|
|
338
|
+
async function uninstallHook(slug, agent, scope, cwd = process.cwd()) {
|
|
339
|
+
if (agent !== "claude") return false;
|
|
340
|
+
const settingsPath = getSettingsPath(scope, cwd);
|
|
341
|
+
if (!await exists(settingsPath)) return false;
|
|
342
|
+
const item = await getItem(slug, "hook");
|
|
343
|
+
const scriptFile = item ? findScriptFile(item) : null;
|
|
344
|
+
const expectedPath = scriptFile ? getScriptPath(scope, scriptFile) : null;
|
|
345
|
+
const settings = await readJson(settingsPath);
|
|
346
|
+
if (!settings.hooks) return false;
|
|
347
|
+
let removed = false;
|
|
348
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
349
|
+
const entries = settings.hooks[event];
|
|
350
|
+
if (!entries) continue;
|
|
351
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
352
|
+
const entry = entries[i];
|
|
353
|
+
const originalLength = entry.hooks.length;
|
|
354
|
+
entry.hooks = entry.hooks.filter((h) => {
|
|
355
|
+
if (expectedPath && h.command === expectedPath) return false;
|
|
356
|
+
const cmdBasename = basename(h.command, ".sh");
|
|
357
|
+
return cmdBasename !== slug;
|
|
358
|
+
});
|
|
359
|
+
if (entry.hooks.length < originalLength) {
|
|
360
|
+
removed = true;
|
|
361
|
+
}
|
|
362
|
+
if (entry.hooks.length === 0) {
|
|
363
|
+
entries.splice(i, 1);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (entries.length === 0) {
|
|
367
|
+
delete settings.hooks[event];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
371
|
+
delete settings.hooks;
|
|
372
|
+
}
|
|
373
|
+
if (removed) {
|
|
374
|
+
await writeJson(settingsPath, settings);
|
|
375
|
+
}
|
|
376
|
+
const hooksDir = getHooksDir(scope, cwd);
|
|
377
|
+
const scriptFileName = scriptFile || `${slug}.sh`;
|
|
378
|
+
const scriptFilePath = join2(hooksDir, scriptFileName);
|
|
379
|
+
try {
|
|
380
|
+
await rm(scriptFilePath);
|
|
381
|
+
removed = true;
|
|
382
|
+
} catch (e) {
|
|
383
|
+
if (e.code !== "ENOENT") throw e;
|
|
384
|
+
}
|
|
385
|
+
return removed;
|
|
386
|
+
}
|
|
387
|
+
async function getInstalledHooks(agent, scope, cwd = process.cwd()) {
|
|
388
|
+
if (agent !== "claude") return [];
|
|
389
|
+
const settingsPath = getSettingsPath(scope, cwd);
|
|
390
|
+
if (!await exists(settingsPath)) return [];
|
|
391
|
+
const settings = await readJson(settingsPath);
|
|
392
|
+
if (!settings.hooks) return [];
|
|
393
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
394
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
395
|
+
for (const entry of entries) {
|
|
396
|
+
for (const hook of entry.hooks) {
|
|
397
|
+
const slug = basename(hook.command, ".sh");
|
|
398
|
+
slugs.add(slug);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return Array.from(slugs);
|
|
403
|
+
}
|
|
404
|
+
var hookHandler = {
|
|
405
|
+
type: "hook",
|
|
406
|
+
async install(item, agents, scope, method, cwd) {
|
|
407
|
+
return installHook(item, agents, scope, method, cwd);
|
|
408
|
+
},
|
|
409
|
+
async uninstall(slug, agent, scope, cwd) {
|
|
410
|
+
return uninstallHook(slug, agent, scope, cwd);
|
|
411
|
+
},
|
|
412
|
+
async listInstalled(agent, scope, cwd) {
|
|
413
|
+
return getInstalledHooks(agent, scope, cwd);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// src/handlers/mcp.ts
|
|
418
|
+
import chalk4 from "chalk";
|
|
419
|
+
import ora3 from "ora";
|
|
420
|
+
function parseMcpDefinition(content) {
|
|
421
|
+
try {
|
|
422
|
+
return JSON.parse(content);
|
|
423
|
+
} catch {
|
|
424
|
+
throw new Error("Invalid MCP definition: must be valid JSON");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function installMcpForAgent(item, agent, scope, _method, cwd) {
|
|
428
|
+
const spinner = ora3(
|
|
429
|
+
`Installing ${item.name} for ${CODING_AGENTS[agent].name}...`
|
|
430
|
+
).start();
|
|
431
|
+
try {
|
|
432
|
+
const content = await getItemContent(item);
|
|
433
|
+
const mcpDef = parseMcpDefinition(content);
|
|
434
|
+
const configPath = getMcpPath(scope, cwd);
|
|
435
|
+
const config = await readJson(configPath);
|
|
436
|
+
config.mcpServers = config.mcpServers || {};
|
|
437
|
+
config.mcpServers[mcpDef.name] = mcpDef.config;
|
|
438
|
+
await writeJson(configPath, config);
|
|
439
|
+
spinner.succeed(
|
|
440
|
+
brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`)
|
|
441
|
+
);
|
|
442
|
+
return { agent, success: true, path: configPath };
|
|
443
|
+
} catch (error2) {
|
|
444
|
+
const errorMsg = error2 instanceof Error ? error2.message : "Unknown error";
|
|
445
|
+
spinner.fail(
|
|
446
|
+
chalk4.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${errorMsg}`)
|
|
447
|
+
);
|
|
448
|
+
return { agent, success: false, path: "", error: errorMsg };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function installMcp(item, agents, scope, method, cwd = process.cwd()) {
|
|
452
|
+
const results = [];
|
|
453
|
+
for (const agent of agents) {
|
|
454
|
+
const result = await installMcpForAgent(item, agent, scope, method, cwd);
|
|
455
|
+
results.push(result);
|
|
456
|
+
}
|
|
457
|
+
return results;
|
|
458
|
+
}
|
|
459
|
+
async function uninstallMcp(slug, _agent, scope, cwd = process.cwd()) {
|
|
460
|
+
const configPath = getMcpPath(scope, cwd);
|
|
461
|
+
if (!await exists(configPath)) return false;
|
|
462
|
+
const config = await readJson(configPath);
|
|
463
|
+
if (!config.mcpServers || !(slug in config.mcpServers)) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
delete config.mcpServers[slug];
|
|
467
|
+
await writeJson(configPath, config);
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
async function getInstalledMcpServers(_agent, scope, cwd = process.cwd()) {
|
|
471
|
+
const configPath = getMcpPath(scope, cwd);
|
|
472
|
+
if (!await exists(configPath)) return [];
|
|
473
|
+
const config = await readJson(configPath);
|
|
474
|
+
return Object.keys(config.mcpServers || {});
|
|
475
|
+
}
|
|
476
|
+
var mcpHandler = {
|
|
477
|
+
type: "mcp",
|
|
478
|
+
async install(item, agents, scope, method, cwd) {
|
|
479
|
+
return installMcp(item, agents, scope, method, cwd);
|
|
480
|
+
},
|
|
481
|
+
async uninstall(slug, agent, scope, cwd) {
|
|
482
|
+
return uninstallMcp(slug, agent, scope, cwd);
|
|
483
|
+
},
|
|
484
|
+
async listInstalled(agent, scope, cwd) {
|
|
485
|
+
return getInstalledMcpServers(agent, scope, cwd);
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// src/handlers/settings.ts
|
|
490
|
+
import chalk5 from "chalk";
|
|
491
|
+
import ora4 from "ora";
|
|
492
|
+
function parseSettings(content) {
|
|
493
|
+
try {
|
|
494
|
+
return JSON.parse(content);
|
|
495
|
+
} catch {
|
|
496
|
+
throw new Error("Invalid settings: must be valid JSON");
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function installSettingsForAgent(item, agent, scope, _method, cwd) {
|
|
500
|
+
const spinner = ora4(
|
|
501
|
+
`Installing ${item.name} for ${CODING_AGENTS[agent].name}...`
|
|
502
|
+
).start();
|
|
503
|
+
try {
|
|
504
|
+
if (agent !== "claude") {
|
|
505
|
+
throw new Error("Settings are only supported for Claude Code");
|
|
506
|
+
}
|
|
507
|
+
const content = await getItemContent(item);
|
|
508
|
+
const newSettings = parseSettings(content);
|
|
509
|
+
const settingsPath = getSettingsPath(scope, cwd);
|
|
510
|
+
const existingSettings = await readJson(settingsPath);
|
|
511
|
+
const merged = deepMerge(existingSettings, newSettings);
|
|
512
|
+
await writeJson(settingsPath, merged);
|
|
513
|
+
spinner.succeed(
|
|
514
|
+
brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`)
|
|
515
|
+
);
|
|
516
|
+
return { agent, success: true, path: settingsPath };
|
|
517
|
+
} catch (error2) {
|
|
518
|
+
const errorMsg = error2 instanceof Error ? error2.message : "Unknown error";
|
|
519
|
+
spinner.fail(
|
|
520
|
+
chalk5.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${errorMsg}`)
|
|
521
|
+
);
|
|
522
|
+
return { agent, success: false, path: "", error: errorMsg };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async function installSettings(item, agents, scope, method, cwd = process.cwd()) {
|
|
526
|
+
const results = [];
|
|
527
|
+
for (const agent of agents) {
|
|
528
|
+
const result = await installSettingsForAgent(item, agent, scope, method, cwd);
|
|
529
|
+
results.push(result);
|
|
530
|
+
}
|
|
531
|
+
return results;
|
|
532
|
+
}
|
|
533
|
+
function deepUnmerge(target, source) {
|
|
534
|
+
let changed = false;
|
|
535
|
+
const result = { ...target };
|
|
536
|
+
for (const key of Object.keys(source)) {
|
|
537
|
+
if (!(key in result)) continue;
|
|
538
|
+
const sourceValue = source[key];
|
|
539
|
+
const targetValue = result[key];
|
|
540
|
+
if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue !== null && typeof targetValue === "object" && !Array.isArray(targetValue)) {
|
|
541
|
+
const nested = deepUnmerge(
|
|
542
|
+
targetValue,
|
|
543
|
+
sourceValue
|
|
544
|
+
);
|
|
545
|
+
if (nested.changed) {
|
|
546
|
+
changed = true;
|
|
547
|
+
if (Object.keys(nested.result).length === 0) {
|
|
548
|
+
delete result[key];
|
|
549
|
+
} else {
|
|
550
|
+
result[key] = nested.result;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
} else {
|
|
554
|
+
delete result[key];
|
|
555
|
+
changed = true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return { result, changed };
|
|
559
|
+
}
|
|
560
|
+
async function uninstallSettings(slug, agent, scope, cwd = process.cwd()) {
|
|
561
|
+
if (agent !== "claude") return false;
|
|
562
|
+
const item = await getItem(slug, "settings");
|
|
563
|
+
if (!item) return false;
|
|
564
|
+
let content;
|
|
565
|
+
try {
|
|
566
|
+
content = await getItemContent(item);
|
|
567
|
+
} catch {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
const itemSettings = parseSettings(content);
|
|
571
|
+
const settingsPath = getSettingsPath(scope, cwd);
|
|
572
|
+
if (!await exists(settingsPath)) return false;
|
|
573
|
+
const currentSettings = await readJson(settingsPath);
|
|
574
|
+
const { result, changed } = deepUnmerge(currentSettings, itemSettings);
|
|
575
|
+
if (changed) {
|
|
576
|
+
await writeJson(settingsPath, result);
|
|
577
|
+
}
|
|
578
|
+
return changed;
|
|
579
|
+
}
|
|
580
|
+
async function getInstalledSettings(agent, scope, cwd = process.cwd()) {
|
|
581
|
+
if (agent !== "claude") return [];
|
|
582
|
+
const settingsPath = getSettingsPath(scope, cwd);
|
|
583
|
+
if (!await exists(settingsPath)) return [];
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
586
|
+
var settingsHandler = {
|
|
587
|
+
type: "settings",
|
|
588
|
+
async install(item, agents, scope, method, cwd) {
|
|
589
|
+
return installSettings(item, agents, scope, method, cwd);
|
|
590
|
+
},
|
|
591
|
+
async uninstall(slug, agent, scope, cwd) {
|
|
592
|
+
return uninstallSettings(slug, agent, scope, cwd);
|
|
593
|
+
},
|
|
594
|
+
async listInstalled(agent, scope, cwd) {
|
|
595
|
+
return getInstalledSettings(agent, scope, cwd);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/handlers/plugin.ts
|
|
600
|
+
import { homedir as homedir2 } from "os";
|
|
601
|
+
import { join as join3 } from "path";
|
|
602
|
+
import { mkdir as mkdir2, rm as rm2 } from "fs/promises";
|
|
603
|
+
import { execFile } from "child_process";
|
|
604
|
+
import { promisify } from "util";
|
|
605
|
+
import chalk6 from "chalk";
|
|
606
|
+
import ora5 from "ora";
|
|
607
|
+
var home = homedir2();
|
|
608
|
+
var PLUGINS_CACHE_DIR = join3(home, ".claude/plugins/cache");
|
|
609
|
+
var INSTALLED_PLUGINS_PATH = join3(home, ".claude/plugins/installed_plugins.json");
|
|
610
|
+
var KNOWN_MARKETPLACES_PATH = join3(home, ".claude/plugins/known_marketplaces.json");
|
|
611
|
+
var MARKETPLACES_DIR = join3(home, ".claude/plugins/marketplaces");
|
|
612
|
+
function getPluginCachePath(marketplace, name, version) {
|
|
613
|
+
return join3(PLUGINS_CACHE_DIR, marketplace, name, version);
|
|
614
|
+
}
|
|
615
|
+
function getPluginId(name, marketplace) {
|
|
616
|
+
return `${name}@${marketplace}`;
|
|
617
|
+
}
|
|
618
|
+
function extractGitHubRepo(url) {
|
|
619
|
+
const match = url.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
620
|
+
return match?.[1]?.replace(/\.git$/, "") ?? null;
|
|
621
|
+
}
|
|
622
|
+
async function ensureMarketplaceRegistered(marketplace, item) {
|
|
623
|
+
const known = await readJson(
|
|
624
|
+
KNOWN_MARKETPLACES_PATH
|
|
625
|
+
);
|
|
626
|
+
if (known[marketplace]) return;
|
|
627
|
+
const repo = item.externalUrl ? extractGitHubRepo(item.externalUrl) : null;
|
|
628
|
+
if (!repo) return;
|
|
629
|
+
const installLocation = join3(MARKETPLACES_DIR, marketplace);
|
|
630
|
+
const execFileAsync = promisify(execFile);
|
|
631
|
+
await mkdir2(MARKETPLACES_DIR, { recursive: true });
|
|
632
|
+
await execFileAsync("git", [
|
|
633
|
+
"clone",
|
|
634
|
+
"--depth",
|
|
635
|
+
"1",
|
|
636
|
+
`https://github.com/${repo}.git`,
|
|
637
|
+
installLocation
|
|
638
|
+
]);
|
|
639
|
+
known[marketplace] = {
|
|
640
|
+
source: { source: "github", repo },
|
|
641
|
+
installLocation,
|
|
642
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
643
|
+
};
|
|
644
|
+
await writeJson(KNOWN_MARKETPLACES_PATH, known);
|
|
645
|
+
}
|
|
646
|
+
async function installPluginForAgent(item, agent, scope, method, cwd) {
|
|
647
|
+
const spinner = ora5(
|
|
648
|
+
`Installing ${item.name} for ${CODING_AGENTS[agent].name}...`
|
|
649
|
+
).start();
|
|
650
|
+
try {
|
|
651
|
+
if (agent !== "claude") {
|
|
652
|
+
throw new Error("Plugins are only supported for Claude Code");
|
|
653
|
+
}
|
|
654
|
+
const tmpPath = join3(PLUGINS_CACHE_DIR, ".tmp", item.slug);
|
|
655
|
+
const sourcePath = getItemSourcePath(item);
|
|
656
|
+
if (sourcePath) {
|
|
657
|
+
await installDirectory(sourcePath, tmpPath, method);
|
|
658
|
+
} else {
|
|
659
|
+
await fetchItemToDestination(item, tmpPath);
|
|
660
|
+
}
|
|
661
|
+
const pluginJson = await readJson(
|
|
662
|
+
join3(tmpPath, ".claude-plugin", "plugin.json")
|
|
663
|
+
);
|
|
664
|
+
const marketplace = item.marketplace || item.author?.name || "seedr";
|
|
665
|
+
const pluginName = pluginJson.name || item.slug;
|
|
666
|
+
const version = pluginJson.version || "1.0.0";
|
|
667
|
+
const pluginId = getPluginId(pluginName, marketplace);
|
|
668
|
+
await ensureMarketplaceRegistered(marketplace, item);
|
|
669
|
+
const cachePath = getPluginCachePath(marketplace, pluginName, version);
|
|
670
|
+
await installDirectory(tmpPath, cachePath, "copy");
|
|
671
|
+
await rm2(tmpPath, { recursive: true, force: true });
|
|
672
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
673
|
+
const registry = await readJson(INSTALLED_PLUGINS_PATH);
|
|
674
|
+
registry.version = registry.version || 2;
|
|
675
|
+
registry.plugins = registry.plugins || {};
|
|
676
|
+
const installInfo = {
|
|
677
|
+
scope,
|
|
678
|
+
...scope !== "user" ? { projectPath: cwd } : {},
|
|
679
|
+
installPath: cachePath,
|
|
680
|
+
version,
|
|
681
|
+
installedAt: now,
|
|
682
|
+
lastUpdated: now,
|
|
683
|
+
gitCommitSha: ""
|
|
684
|
+
};
|
|
685
|
+
const existingEntries = registry.plugins[pluginId] || [];
|
|
686
|
+
const filteredEntries = existingEntries.filter(
|
|
687
|
+
(e) => !(e.scope === scope && (scope === "user" || e.projectPath === cwd))
|
|
688
|
+
);
|
|
689
|
+
registry.plugins[pluginId] = [...filteredEntries, installInfo];
|
|
690
|
+
await writeJson(INSTALLED_PLUGINS_PATH, registry);
|
|
691
|
+
const settingsPath = getSettingsPath(scope, cwd);
|
|
692
|
+
const settings = await readJson(settingsPath);
|
|
693
|
+
settings.enabledPlugins = settings.enabledPlugins || {};
|
|
694
|
+
settings.enabledPlugins[pluginId] = true;
|
|
695
|
+
await writeJson(settingsPath, settings);
|
|
696
|
+
spinner.succeed(
|
|
697
|
+
brand(`Installed ${item.name} for ${CODING_AGENTS[agent].name}`)
|
|
698
|
+
);
|
|
699
|
+
return { agent, success: true, path: cachePath };
|
|
700
|
+
} catch (error2) {
|
|
701
|
+
const errorMsg = error2 instanceof Error ? error2.message : "Unknown error";
|
|
702
|
+
spinner.fail(
|
|
703
|
+
chalk6.red(`Failed to install for ${CODING_AGENTS[agent].name}: ${errorMsg}`)
|
|
704
|
+
);
|
|
705
|
+
return { agent, success: false, path: "", error: errorMsg };
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async function installPlugin(item, agents, scope, method, cwd = process.cwd()) {
|
|
709
|
+
const results = [];
|
|
710
|
+
for (const agent of agents) {
|
|
711
|
+
const result = await installPluginForAgent(item, agent, scope, method, cwd);
|
|
712
|
+
results.push(result);
|
|
713
|
+
}
|
|
714
|
+
return results;
|
|
715
|
+
}
|
|
716
|
+
async function uninstallPlugin(slug, agent, scope, cwd = process.cwd()) {
|
|
717
|
+
if (agent !== "claude") return false;
|
|
718
|
+
const registry = await readJson(INSTALLED_PLUGINS_PATH);
|
|
719
|
+
if (!registry.plugins) return false;
|
|
720
|
+
const matchingKey = Object.keys(registry.plugins).find(
|
|
721
|
+
(key) => key.startsWith(`${slug}@`)
|
|
722
|
+
);
|
|
723
|
+
if (!matchingKey) return false;
|
|
724
|
+
const entries = registry.plugins[matchingKey] || [];
|
|
725
|
+
const filteredEntries = entries.filter(
|
|
726
|
+
(e) => !(e.scope === scope && (scope === "user" || e.projectPath === cwd))
|
|
727
|
+
);
|
|
728
|
+
if (filteredEntries.length === 0) {
|
|
729
|
+
delete registry.plugins[matchingKey];
|
|
730
|
+
} else {
|
|
731
|
+
registry.plugins[matchingKey] = filteredEntries;
|
|
732
|
+
}
|
|
733
|
+
await writeJson(INSTALLED_PLUGINS_PATH, registry);
|
|
734
|
+
const settingsPath = getSettingsPath(scope, cwd);
|
|
735
|
+
const settings = await readJson(settingsPath);
|
|
736
|
+
if (settings.enabledPlugins && matchingKey in settings.enabledPlugins) {
|
|
737
|
+
delete settings.enabledPlugins[matchingKey];
|
|
738
|
+
await writeJson(settingsPath, settings);
|
|
739
|
+
}
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
async function getInstalledPlugins(agent, scope, cwd = process.cwd()) {
|
|
743
|
+
if (agent !== "claude") return [];
|
|
744
|
+
const registry = await readJson(INSTALLED_PLUGINS_PATH);
|
|
745
|
+
if (!registry.plugins) return [];
|
|
746
|
+
const installed = [];
|
|
747
|
+
for (const [pluginId, entries] of Object.entries(registry.plugins)) {
|
|
748
|
+
for (const entry of entries) {
|
|
749
|
+
if (entry.scope === scope && (scope === "user" || entry.projectPath === cwd)) {
|
|
750
|
+
const slug = pluginId.split("@")[0] || pluginId;
|
|
751
|
+
installed.push(slug);
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return installed;
|
|
757
|
+
}
|
|
758
|
+
var pluginHandler = {
|
|
759
|
+
type: "plugin",
|
|
760
|
+
async install(item, agents, scope, method, cwd) {
|
|
761
|
+
return installPlugin(item, agents, scope, method, cwd);
|
|
762
|
+
},
|
|
763
|
+
async uninstall(slug, agent, scope, cwd) {
|
|
764
|
+
return uninstallPlugin(slug, agent, scope, cwd);
|
|
765
|
+
},
|
|
766
|
+
async listInstalled(agent, scope, cwd) {
|
|
767
|
+
return getInstalledPlugins(agent, scope, cwd);
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// src/handlers/index.ts
|
|
772
|
+
registerHandler(skillHandler);
|
|
773
|
+
registerHandler(agentHandler);
|
|
774
|
+
registerHandler(hookHandler);
|
|
775
|
+
registerHandler(mcpHandler);
|
|
776
|
+
registerHandler(settingsHandler);
|
|
777
|
+
registerHandler(pluginHandler);
|
|
778
|
+
|
|
779
|
+
// src/commands/add.ts
|
|
780
|
+
async function resolveItem(itemName, type) {
|
|
781
|
+
if (itemName) {
|
|
782
|
+
const item = await getItem(itemName, type);
|
|
783
|
+
if (item) return item;
|
|
784
|
+
const results = await searchItems(itemName);
|
|
785
|
+
const filtered = type ? results.filter((r) => r.type === type) : results;
|
|
786
|
+
if (filtered.length === 0) {
|
|
787
|
+
if (type && results.length > 0) {
|
|
788
|
+
const match = results.find((r) => r.slug === itemName);
|
|
789
|
+
if (match) {
|
|
790
|
+
error(`"${itemName}" is a ${match.type}, not a ${type}. Run: seedr add ${itemName} --type ${match.type}`);
|
|
791
|
+
} else {
|
|
792
|
+
error(`No ${type} matching "${itemName}". Found: ${results.map((r) => `${r.slug} (${r.type})`).join(", ")}`);
|
|
793
|
+
}
|
|
794
|
+
} else {
|
|
795
|
+
error(`"${itemName}" not found`);
|
|
796
|
+
}
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
if (filtered.length === 1) {
|
|
800
|
+
return filtered[0];
|
|
801
|
+
}
|
|
802
|
+
warn(`Multiple matches for "${itemName}"`);
|
|
803
|
+
const selected2 = await selectSkill(filtered);
|
|
804
|
+
if (p.isCancel(selected2)) {
|
|
805
|
+
cancelled();
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
return selected2;
|
|
809
|
+
}
|
|
810
|
+
const items = await listItems(type || "skill");
|
|
811
|
+
if (items.length === 0) {
|
|
812
|
+
error(`No ${type || "skill"}s available in registry`);
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
const selected = await selectSkill(items);
|
|
816
|
+
if (p.isCancel(selected)) {
|
|
817
|
+
cancelled();
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
return selected;
|
|
821
|
+
}
|
|
822
|
+
function resolveAgents(agentsArg, item) {
|
|
823
|
+
const typeCompatible = filterCompatibleAgents(item.type, item.compatibility);
|
|
824
|
+
if (!agentsArg) return [];
|
|
825
|
+
const agents = parseAgentsArg(agentsArg, typeCompatible);
|
|
826
|
+
if (agentsArg === "all") return agents;
|
|
827
|
+
const incompatible = agents.filter((a) => !typeCompatible.includes(a));
|
|
828
|
+
if (incompatible.length > 0) {
|
|
829
|
+
warn(`${incompatible.join(", ")} not compatible with this ${item.type}`);
|
|
830
|
+
return agents.filter((a) => typeCompatible.includes(a));
|
|
831
|
+
}
|
|
832
|
+
return agents;
|
|
833
|
+
}
|
|
834
|
+
function printDryRunSummary(item, agents, scope, method, cwd) {
|
|
835
|
+
info("Dry run - no files will be written\n");
|
|
836
|
+
console.log(brand(" Would install:"));
|
|
837
|
+
console.log(` ${item.type}: ${chalk7.white(item.name)}`);
|
|
838
|
+
console.log(` Scope: ${chalk7.white(scope)}`);
|
|
839
|
+
console.log(` Method: ${chalk7.white(method)}`);
|
|
840
|
+
console.log();
|
|
841
|
+
if (method === "symlink" && item.type === "skill") {
|
|
842
|
+
const centralPath = getAgentsPath("skill", item.slug, cwd);
|
|
843
|
+
console.log(brand(" Central storage:"));
|
|
844
|
+
console.log(` ${chalk7.gray("\u2192")} ${chalk7.gray(centralPath)}`);
|
|
845
|
+
console.log();
|
|
846
|
+
console.log(brand(" Symlinks from agent folders:"));
|
|
847
|
+
} else {
|
|
848
|
+
console.log(brand(" Target locations:"));
|
|
849
|
+
}
|
|
850
|
+
for (const agent of agents) {
|
|
851
|
+
const config = CODING_AGENTS[agent];
|
|
852
|
+
const contentPath = getContentPath(agent, item.type, scope, cwd);
|
|
853
|
+
if (!contentPath) {
|
|
854
|
+
console.log(` ${chalk7.gray("\u2192")} ${chalk7.white(config.name)}: ${chalk7.red("not supported")}`);
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const targetPath = `${contentPath}/${item.slug}`;
|
|
858
|
+
console.log(` ${chalk7.gray("\u2192")} ${chalk7.white(config.name)}: ${chalk7.gray(targetPath)}`);
|
|
859
|
+
}
|
|
860
|
+
console.log();
|
|
861
|
+
}
|
|
862
|
+
function printInstallSummary(results) {
|
|
863
|
+
const successful = results.filter((r) => r.success);
|
|
864
|
+
const failed = results.filter((r) => !r.success);
|
|
865
|
+
if (successful.length > 0) {
|
|
866
|
+
success(`Installed for ${successful.length} agent(s)`);
|
|
867
|
+
for (const r of successful) {
|
|
868
|
+
console.log(chalk7.gray(` \u2192 ${r.path}`));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (failed.length > 0) {
|
|
872
|
+
error(`Failed for ${failed.length} agent(s)`);
|
|
873
|
+
for (const r of failed) {
|
|
874
|
+
console.log(chalk7.red(` \xD7 ${r.agent}: ${r.error}`));
|
|
875
|
+
}
|
|
876
|
+
process.exit(1);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
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(
|
|
880
|
+
"-a, --agents <agents>",
|
|
881
|
+
"Comma-separated coding agents or 'all' (claude,copilot,gemini,codex,opencode)"
|
|
882
|
+
).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 what would be installed without making changes").action(async (name, options) => {
|
|
883
|
+
try {
|
|
884
|
+
printLogo();
|
|
885
|
+
intro("Seedr");
|
|
886
|
+
const itemName = name;
|
|
887
|
+
const contentType = options.type;
|
|
888
|
+
const item = await resolveItem(itemName, contentType);
|
|
889
|
+
if (!item) process.exit(1);
|
|
890
|
+
step(`Selected: ${brand(item.name)} ${chalk7.gray(`(${item.type})`)} ${chalk7.gray(`- ${item.description}`)}`);
|
|
891
|
+
const handler = getHandler(item.type);
|
|
892
|
+
if (!handler) {
|
|
893
|
+
error(`No handler found for type "${item.type}"`);
|
|
894
|
+
process.exit(1);
|
|
895
|
+
}
|
|
896
|
+
let agents = resolveAgents(options.agents, item);
|
|
897
|
+
const typeCompatible = filterCompatibleAgents(item.type, item.compatibility);
|
|
898
|
+
if (agents.length === 0) {
|
|
899
|
+
if (typeCompatible.length === 1) {
|
|
900
|
+
agents = typeCompatible;
|
|
901
|
+
} else {
|
|
902
|
+
const selected = await selectAgents(typeCompatible);
|
|
903
|
+
if (p.isCancel(selected)) {
|
|
904
|
+
cancelled();
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
agents = selected;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (agents.length === 0) {
|
|
911
|
+
error("No valid agents selected");
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
step(`Agents: ${brand(agents.join(", "))}`);
|
|
915
|
+
let scope;
|
|
916
|
+
if (options.scope) {
|
|
917
|
+
scope = options.scope;
|
|
918
|
+
} else {
|
|
919
|
+
const supportsLocal = ["plugin", "settings", "hook"].includes(item.type);
|
|
920
|
+
const selected = await selectScope(supportsLocal);
|
|
921
|
+
if (p.isCancel(selected)) {
|
|
922
|
+
cancelled();
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
scope = selected;
|
|
926
|
+
}
|
|
927
|
+
step(`Scope: ${brand(scope)}`);
|
|
928
|
+
let method;
|
|
929
|
+
if (options.method) {
|
|
930
|
+
method = options.method;
|
|
931
|
+
} else if (agents.length === 1) {
|
|
932
|
+
method = "copy";
|
|
933
|
+
} else {
|
|
934
|
+
const symlinkPath = getAgentsPath(item.type, item.slug, process.cwd());
|
|
935
|
+
const selected = await selectMethod(symlinkPath);
|
|
936
|
+
if (p.isCancel(selected)) {
|
|
937
|
+
cancelled();
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
method = selected;
|
|
941
|
+
}
|
|
942
|
+
step(`Method: ${brand(method)}`);
|
|
943
|
+
if (options.dryRun) {
|
|
944
|
+
console.log();
|
|
945
|
+
printDryRunSummary(item, agents, scope, method, process.cwd());
|
|
946
|
+
outro("Dry run complete");
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
if (!options.yes) {
|
|
950
|
+
console.log();
|
|
951
|
+
const confirmed = await confirm("Proceed with installation?");
|
|
952
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
953
|
+
cancelled();
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
console.log();
|
|
958
|
+
const results = await handler.install(item, agents, scope, method, process.cwd());
|
|
959
|
+
trackInstalls(item.slug, item.type, results, scope);
|
|
960
|
+
printInstallSummary(results);
|
|
961
|
+
outro("Installation complete");
|
|
962
|
+
} catch (error2) {
|
|
963
|
+
handleCommandError(error2);
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
// src/commands/list.ts
|
|
968
|
+
import { Command as Command2 } from "commander";
|
|
969
|
+
import chalk8 from "chalk";
|
|
970
|
+
var SEPARATOR_WIDTH = 40;
|
|
971
|
+
var SLUG_COLUMN_WIDTH = 24;
|
|
972
|
+
var TYPE_COLORS = {
|
|
973
|
+
skill: chalk8.magenta,
|
|
974
|
+
hook: chalk8.hex("#a855f7"),
|
|
975
|
+
agent: chalk8.blue,
|
|
976
|
+
plugin: chalk8.hex("#6366f1"),
|
|
977
|
+
command: chalk8.hex("#f59e0b"),
|
|
978
|
+
settings: chalk8.hex("#f97316"),
|
|
979
|
+
mcp: chalk8.hex("#2dd4bf")
|
|
980
|
+
};
|
|
981
|
+
var listCommand = new Command2("list").alias("ls").description("List available or installed skills").option("-t, --type <type>", "Filter by type (skill, hook, agent, plugin)").option("-i, --installed", "Show only installed items").option("--scope <scope>", "Scope for installed check (project, user)", "project").action(async (options) => {
|
|
982
|
+
try {
|
|
983
|
+
if (options.installed) {
|
|
984
|
+
await listInstalled(options.scope);
|
|
985
|
+
} else {
|
|
986
|
+
await listAvailable(options.type);
|
|
987
|
+
}
|
|
988
|
+
} catch (error2) {
|
|
989
|
+
handleCommandError(error2);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
async function listAvailable(type) {
|
|
993
|
+
const items = await listItems(type);
|
|
994
|
+
if (items.length === 0) {
|
|
995
|
+
console.log(chalk8.yellow("No items found in registry"));
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
const grouped = groupByType(items);
|
|
999
|
+
for (const [itemType, typeItems] of Object.entries(grouped)) {
|
|
1000
|
+
const colorFn = TYPE_COLORS[itemType] ?? chalk8.white;
|
|
1001
|
+
console.log(colorFn(`
|
|
1002
|
+
${itemType.toUpperCase()}S`));
|
|
1003
|
+
console.log(chalk8.gray("\u2500".repeat(SEPARATOR_WIDTH)));
|
|
1004
|
+
for (const item of typeItems) {
|
|
1005
|
+
const compatIcons = item.compatibility.map((a) => CODING_AGENTS[a].shortName).join(" ");
|
|
1006
|
+
const featured = item.featured ? chalk8.yellow("\u2605 ") : " ";
|
|
1007
|
+
console.log(
|
|
1008
|
+
`${featured}${chalk8.white(item.slug.padEnd(SLUG_COLUMN_WIDTH))} ${chalk8.gray(compatIcons)}`
|
|
1009
|
+
);
|
|
1010
|
+
console.log(` ${chalk8.gray(item.description)}`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
console.log("");
|
|
1014
|
+
console.log(
|
|
1015
|
+
chalk8.gray(`Total: ${items.length} items. Use 'npx @danieldeusing/seedr add <name>' to install.`)
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
async function listInstalled(scope) {
|
|
1019
|
+
console.log(brand(`
|
|
1020
|
+
Installed skills (${scope} scope):
|
|
1021
|
+
`));
|
|
1022
|
+
let total = 0;
|
|
1023
|
+
for (const agent of ALL_AGENTS) {
|
|
1024
|
+
const installed = await getInstalledSkills(
|
|
1025
|
+
agent,
|
|
1026
|
+
scope
|
|
1027
|
+
);
|
|
1028
|
+
if (installed.length > 0) {
|
|
1029
|
+
console.log(chalk8.blue(CODING_AGENTS[agent].name));
|
|
1030
|
+
for (const skill of installed) {
|
|
1031
|
+
console.log(` ${chalk8.white(skill)}`);
|
|
1032
|
+
total++;
|
|
1033
|
+
}
|
|
1034
|
+
console.log("");
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (total === 0) {
|
|
1038
|
+
console.log(chalk8.yellow("No skills installed"));
|
|
1039
|
+
} else {
|
|
1040
|
+
console.log(chalk8.gray(`Total: ${total} installed`));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function groupByType(items) {
|
|
1044
|
+
return items.reduce(
|
|
1045
|
+
(acc, item) => {
|
|
1046
|
+
if (!acc[item.type]) {
|
|
1047
|
+
acc[item.type] = [];
|
|
1048
|
+
}
|
|
1049
|
+
acc[item.type].push(item);
|
|
1050
|
+
return acc;
|
|
1051
|
+
},
|
|
1052
|
+
{}
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// src/commands/remove.ts
|
|
1057
|
+
import { Command as Command3 } from "commander";
|
|
1058
|
+
import chalk9 from "chalk";
|
|
1059
|
+
import ora6 from "ora";
|
|
1060
|
+
|
|
1061
|
+
// src/utils/prompts.ts
|
|
1062
|
+
import inquirer from "inquirer";
|
|
1063
|
+
async function promptConfirm(message, defaultValue = true) {
|
|
1064
|
+
const { confirmed } = await inquirer.prompt([
|
|
1065
|
+
{
|
|
1066
|
+
type: "confirm",
|
|
1067
|
+
name: "confirmed",
|
|
1068
|
+
message,
|
|
1069
|
+
default: defaultValue
|
|
1070
|
+
}
|
|
1071
|
+
]);
|
|
1072
|
+
return confirmed;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// src/commands/remove.ts
|
|
1076
|
+
async function findInstalledAgents(slug, type, scope) {
|
|
1077
|
+
const handler = getHandler(type);
|
|
1078
|
+
if (!handler) return [];
|
|
1079
|
+
const agents = [];
|
|
1080
|
+
for (const agent of ALL_AGENTS) {
|
|
1081
|
+
const installed = await handler.listInstalled(agent, scope);
|
|
1082
|
+
if (installed.includes(slug)) {
|
|
1083
|
+
agents.push(agent);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return agents;
|
|
1087
|
+
}
|
|
1088
|
+
async function removeFromAgents(slug, type, agents, scope) {
|
|
1089
|
+
const handler = getHandler(type);
|
|
1090
|
+
if (!handler) return 0;
|
|
1091
|
+
let successCount = 0;
|
|
1092
|
+
for (const agent of agents) {
|
|
1093
|
+
const spinner = ora6(`Removing from ${CODING_AGENTS[agent].name}...`).start();
|
|
1094
|
+
const removed = await handler.uninstall(slug, agent, scope);
|
|
1095
|
+
if (removed) {
|
|
1096
|
+
spinner.succeed(brand(`Removed from ${CODING_AGENTS[agent].name}`));
|
|
1097
|
+
successCount++;
|
|
1098
|
+
} else {
|
|
1099
|
+
spinner.info(chalk9.gray(`Not found in ${CODING_AGENTS[agent].name}`));
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
return successCount;
|
|
1103
|
+
}
|
|
1104
|
+
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(
|
|
1105
|
+
"-a, --agents <agents>",
|
|
1106
|
+
"Comma-separated coding agents or 'all'"
|
|
1107
|
+
).option(
|
|
1108
|
+
"--scope <scope>",
|
|
1109
|
+
"Installation scope: project, user, or global",
|
|
1110
|
+
"project"
|
|
1111
|
+
).option("-y, --yes", "Skip confirmation prompts").action(async (name, options) => {
|
|
1112
|
+
try {
|
|
1113
|
+
const scope = options.scope;
|
|
1114
|
+
const type = options.type;
|
|
1115
|
+
if (!type) {
|
|
1116
|
+
console.log(
|
|
1117
|
+
chalk9.yellow(`Please specify the content type with --type (skill, plugin, agent, hook, mcp, settings)`)
|
|
1118
|
+
);
|
|
1119
|
+
process.exit(1);
|
|
1120
|
+
}
|
|
1121
|
+
const handler = getHandler(type);
|
|
1122
|
+
if (!handler) {
|
|
1123
|
+
console.log(chalk9.red(`No handler found for type "${type}"`));
|
|
1124
|
+
process.exit(1);
|
|
1125
|
+
}
|
|
1126
|
+
const agents = options.agents ? parseAgentsArg(options.agents, ALL_AGENTS) : await findInstalledAgents(name, type, scope);
|
|
1127
|
+
if (agents.length === 0) {
|
|
1128
|
+
console.log(
|
|
1129
|
+
chalk9.yellow(`${type} "${name}" is not installed in ${scope} scope`)
|
|
1130
|
+
);
|
|
1131
|
+
process.exit(0);
|
|
1132
|
+
}
|
|
1133
|
+
if (!options.yes) {
|
|
1134
|
+
console.log(brand(`
|
|
1135
|
+
Will remove ${type} "${name}" from:`));
|
|
1136
|
+
for (const agent of agents) {
|
|
1137
|
+
console.log(` - ${CODING_AGENTS[agent].name}`);
|
|
1138
|
+
}
|
|
1139
|
+
console.log("");
|
|
1140
|
+
const confirmed = await promptConfirm("Proceed with removal?");
|
|
1141
|
+
if (!confirmed) {
|
|
1142
|
+
console.log(chalk9.yellow("Removal cancelled"));
|
|
1143
|
+
process.exit(0);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
const successCount = await removeFromAgents(name, type, agents, scope);
|
|
1147
|
+
console.log("");
|
|
1148
|
+
if (successCount > 0) {
|
|
1149
|
+
console.log(
|
|
1150
|
+
brand(`Successfully removed from ${successCount} agent(s)`)
|
|
1151
|
+
);
|
|
1152
|
+
} else {
|
|
1153
|
+
console.log(chalk9.yellow("Nothing to remove"));
|
|
1154
|
+
}
|
|
1155
|
+
} catch (error2) {
|
|
1156
|
+
handleCommandError(error2);
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
// src/commands/init.ts
|
|
1161
|
+
import { Command as Command4 } from "commander";
|
|
1162
|
+
import chalk10 from "chalk";
|
|
1163
|
+
import ora7 from "ora";
|
|
1164
|
+
import { join as join4 } from "path";
|
|
1165
|
+
var initCommand = new Command4("init").description("Initialize coding agent configuration directories").option(
|
|
1166
|
+
"-a, --agents <agents>",
|
|
1167
|
+
"Comma-separated coding agents or 'all'",
|
|
1168
|
+
"claude"
|
|
1169
|
+
).option("-y, --yes", "Skip confirmation prompts").action(async (options) => {
|
|
1170
|
+
try {
|
|
1171
|
+
const agents = parseAgentsArg(options.agents, ALL_AGENTS);
|
|
1172
|
+
if (agents.length === 0) {
|
|
1173
|
+
console.error(chalk10.red("No valid agents specified"));
|
|
1174
|
+
process.exit(1);
|
|
1175
|
+
}
|
|
1176
|
+
console.log(brand("\nWill initialize configuration for:"));
|
|
1177
|
+
for (const agent of agents) {
|
|
1178
|
+
const path = getAgentPath(agent, "project");
|
|
1179
|
+
console.log(` - ${CODING_AGENTS[agent].name} \u2192 ${path}`);
|
|
1180
|
+
}
|
|
1181
|
+
console.log("");
|
|
1182
|
+
if (!options.yes) {
|
|
1183
|
+
const confirmed = await promptConfirm("Proceed?");
|
|
1184
|
+
if (!confirmed) {
|
|
1185
|
+
console.log(chalk10.yellow("Cancelled"));
|
|
1186
|
+
process.exit(0);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
for (const agent of agents) {
|
|
1190
|
+
const spinner = ora7(`Initializing ${CODING_AGENTS[agent].name}...`).start();
|
|
1191
|
+
const path = getAgentPath(agent, "project");
|
|
1192
|
+
if (await exists(path)) {
|
|
1193
|
+
spinner.info(
|
|
1194
|
+
chalk10.gray(`${CODING_AGENTS[agent].name} already initialized`)
|
|
1195
|
+
);
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
await ensureDir(path);
|
|
1199
|
+
const readmePath = join4(path, "README.md");
|
|
1200
|
+
await writeTextFile(
|
|
1201
|
+
readmePath,
|
|
1202
|
+
`# ${CODING_AGENTS[agent].name} Configuration
|
|
1203
|
+
|
|
1204
|
+
This directory contains AI configuration files for ${CODING_AGENTS[agent].name}.
|
|
1205
|
+
|
|
1206
|
+
Add skills with:
|
|
1207
|
+
\`\`\`bash
|
|
1208
|
+
npx @danieldeusing/seedr add <skill-name> --agents ${agent}
|
|
1209
|
+
\`\`\`
|
|
1210
|
+
|
|
1211
|
+
Browse available skills at https://seedr.danieldeusing.de
|
|
1212
|
+
`
|
|
1213
|
+
);
|
|
1214
|
+
spinner.succeed(brand(`Initialized ${CODING_AGENTS[agent].name}`));
|
|
1215
|
+
}
|
|
1216
|
+
console.log("");
|
|
1217
|
+
console.log(
|
|
1218
|
+
brand("Done! Use 'npx @danieldeusing/seedr add <skill>' to install skills.")
|
|
1219
|
+
);
|
|
1220
|
+
} catch (error2) {
|
|
1221
|
+
handleCommandError(error2);
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
// src/cli.ts
|
|
1226
|
+
var program = new Command5();
|
|
1227
|
+
program.name("seedr").description("Seed your Coding Agents with capabilities").version("0.1.0").addCommand(addCommand).addCommand(listCommand).addCommand(removeCommand).addCommand(initCommand);
|
|
1228
|
+
program.action(() => {
|
|
1229
|
+
printLogo();
|
|
1230
|
+
program.help();
|
|
1231
|
+
});
|
|
1232
|
+
program.parse();
|