@kurokeita/add-skill 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/bin/cli.js +1020 -27
- package/dist/skills/skill-creator/scripts/init_skill.cjs +31 -31
- package/dist/skills/skill-creator/scripts/package_skill.cjs +29 -29
- package/dist/skills/skill-creator/scripts/validate_skill.cjs +41 -41
- package/package.json +11 -4
- package/dist/src/commands/add.js +0 -253
- package/dist/src/commands/import.js +0 -116
- package/dist/src/commands/list.js +0 -92
- package/dist/src/commands/remove.js +0 -118
- package/dist/src/utils/github.js +0 -65
- package/dist/src/utils/paths.js +0 -45
- package/dist/src/utils/toml.js +0 -60
package/dist/bin/cli.js
CHANGED
|
@@ -1,29 +1,1022 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// bin/cli.ts
|
|
4
|
+
import { intro as intro5, isCancel as isCancel5, outro as outro5, select as select5 } from "@clack/prompts";
|
|
2
5
|
import { Command } from "commander";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
6
|
+
import pc5 from "picocolors";
|
|
7
|
+
|
|
8
|
+
// src/commands/add.ts
|
|
9
|
+
import os3 from "node:os";
|
|
10
|
+
import path4 from "node:path";
|
|
11
|
+
import {
|
|
12
|
+
cancel,
|
|
13
|
+
confirm,
|
|
14
|
+
intro,
|
|
15
|
+
isCancel,
|
|
16
|
+
multiselect,
|
|
17
|
+
note,
|
|
18
|
+
outro,
|
|
19
|
+
select,
|
|
20
|
+
spinner
|
|
21
|
+
} from "@clack/prompts";
|
|
22
|
+
import fs2 from "fs-extra";
|
|
23
|
+
import pc from "picocolors";
|
|
24
|
+
|
|
25
|
+
// src/utils/github.ts
|
|
26
|
+
import os from "node:os";
|
|
27
|
+
import path from "node:path";
|
|
28
|
+
import fs from "fs-extra";
|
|
29
|
+
async function fetchSkillFromGitHub(url) {
|
|
30
|
+
const regex = /github\.com\/([^/]+)\/([^/]+)\/(?:tree|blob)\/([^/]+)\/(.+)/;
|
|
31
|
+
const match = url.match(regex);
|
|
32
|
+
if (!match) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"Invalid GitHub URL. Format: https://github.com/owner/repo/tree/branch/path/to/skill or blob/branch/path/to/file"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const [, owner, repo, ref, skillPath] = match;
|
|
38
|
+
const skillName = path.basename(skillPath);
|
|
39
|
+
const initialApiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${skillPath}?ref=${ref}`;
|
|
40
|
+
const tempDir = path.join(os.tmpdir(), "ai-agents-install", skillName);
|
|
41
|
+
await fs.ensureDir(tempDir);
|
|
42
|
+
await fs.emptyDir(tempDir);
|
|
43
|
+
let isFile = false;
|
|
44
|
+
async function downloadDirectory(apiUrl, localDir) {
|
|
45
|
+
const response = await fetch(apiUrl, {
|
|
46
|
+
headers: {
|
|
47
|
+
"User-Agent": "ai-agents-cli",
|
|
48
|
+
Accept: "application/vnd.github.v3+json"
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Failed to fetch from GitHub (${response.status}): ${response.statusText}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
if (!Array.isArray(data) && data.type === "file") {
|
|
58
|
+
isFile = true;
|
|
59
|
+
const item = data;
|
|
60
|
+
if (item.download_url) {
|
|
61
|
+
const fileContentResponse = await fetch(item.download_url);
|
|
62
|
+
if (!fileContentResponse.ok) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Failed to download ${item.name}: ${fileContentResponse.statusText}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const content = await fileContentResponse.text();
|
|
68
|
+
await fs.writeFile(path.join(localDir, item.name), content);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!Array.isArray(data)) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"Invalid response from GitHub API. Expected directory listing or file object."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const files = data;
|
|
78
|
+
for (const item of files) {
|
|
79
|
+
if (item.type === "file" && item.download_url) {
|
|
80
|
+
const fileContentResponse = await fetch(item.download_url);
|
|
81
|
+
if (!fileContentResponse.ok) {
|
|
82
|
+
console.warn(
|
|
83
|
+
`Failed to download ${item.name}: ${fileContentResponse.statusText}`
|
|
84
|
+
);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const content = await fileContentResponse.text();
|
|
88
|
+
await fs.writeFile(path.join(localDir, item.name), content);
|
|
89
|
+
} else if (item.type === "dir") {
|
|
90
|
+
const newLocalDir = path.join(localDir, item.name);
|
|
91
|
+
await fs.ensureDir(newLocalDir);
|
|
92
|
+
await downloadDirectory(item.url, newLocalDir);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
await downloadDirectory(initialApiUrl, tempDir);
|
|
97
|
+
return { tempDir, skillName, isFile };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/utils/paths.ts
|
|
101
|
+
import os2 from "node:os";
|
|
102
|
+
import path2 from "node:path";
|
|
103
|
+
import { fileURLToPath } from "node:url";
|
|
104
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
105
|
+
var __dirname = path2.dirname(__filename);
|
|
106
|
+
var PROJECT_ROOT = path2.resolve(__dirname, "../..");
|
|
107
|
+
var PLATFORM_LABELS = {
|
|
108
|
+
antigravity: "Antigravity",
|
|
109
|
+
gemini: "Gemini CLI",
|
|
110
|
+
copilot: "GitHub Copilot",
|
|
111
|
+
windsurf: "Windsurf"
|
|
112
|
+
};
|
|
113
|
+
var PLATFORM_PATHS_SKILLS = {
|
|
114
|
+
antigravity: path2.join(os2.homedir(), ".gemini/antigravity/global_skills"),
|
|
115
|
+
copilot: path2.join(os2.homedir(), ".copilot/skills"),
|
|
116
|
+
gemini: path2.join(os2.homedir(), ".gemini/skills"),
|
|
117
|
+
windsurf: path2.join(os2.homedir(), ".codeium/windsurf/skills")
|
|
118
|
+
};
|
|
119
|
+
var PLATFORM_PATHS_AGENTS = {
|
|
120
|
+
copilot: path2.join(os2.homedir(), ".copilot/agents"),
|
|
121
|
+
gemini: path2.join(os2.homedir(), ".gemini/agents")
|
|
122
|
+
};
|
|
123
|
+
var PLATFORM_PATHS_WORKFLOWS = {
|
|
124
|
+
antigravity: path2.join(os2.homedir(), ".gemini/antigravity/global_workflows"),
|
|
125
|
+
copilot: path2.join(os2.homedir(), ".copilot/prompts"),
|
|
126
|
+
gemini: path2.join(os2.homedir(), ".gemini/commands"),
|
|
127
|
+
windsurf: path2.join(os2.homedir(), ".codeium/windsurf/global_workflows")
|
|
128
|
+
};
|
|
129
|
+
var TYPE_DIRS = {
|
|
130
|
+
skill: path2.join(PROJECT_ROOT, "skills"),
|
|
131
|
+
agent: path2.join(PROJECT_ROOT, "agents"),
|
|
132
|
+
workflow: path2.join(PROJECT_ROOT, "workflows")
|
|
133
|
+
};
|
|
134
|
+
function getTargetPaths(type) {
|
|
135
|
+
switch (type) {
|
|
136
|
+
case "agent":
|
|
137
|
+
return PLATFORM_PATHS_AGENTS;
|
|
138
|
+
case "workflow":
|
|
139
|
+
return PLATFORM_PATHS_WORKFLOWS;
|
|
140
|
+
default:
|
|
141
|
+
return PLATFORM_PATHS_SKILLS;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/utils/platforms/antigravity.ts
|
|
146
|
+
var AntigravityHandler = class {
|
|
147
|
+
platform = "antigravity";
|
|
148
|
+
getTargetFileName(itemName, _) {
|
|
149
|
+
return itemName;
|
|
150
|
+
}
|
|
151
|
+
transform(content, _, __) {
|
|
152
|
+
return content;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/utils/platforms/copilot.ts
|
|
157
|
+
import yaml from "js-yaml";
|
|
158
|
+
var CopilotHandler = class {
|
|
159
|
+
platform = "copilot";
|
|
160
|
+
getTargetFileName(itemName, type) {
|
|
161
|
+
if (type === "agent") {
|
|
162
|
+
return `${itemName.replace(/\.md$/, "")}.md`;
|
|
163
|
+
}
|
|
164
|
+
return itemName;
|
|
165
|
+
}
|
|
166
|
+
transform(content, type, itemName) {
|
|
167
|
+
if (type !== "agent") return content;
|
|
168
|
+
const frontmatterRegex = /^---\n([\s\S]*?)\n---/;
|
|
169
|
+
const match = content.match(frontmatterRegex);
|
|
170
|
+
let frontmatter = {
|
|
171
|
+
name: itemName,
|
|
172
|
+
description: "",
|
|
173
|
+
target: "github-copilot",
|
|
174
|
+
tools: ["edit", "terminal", "ls", "grep_search", "githubRepo"]
|
|
175
|
+
};
|
|
176
|
+
let body = content;
|
|
177
|
+
if (match) {
|
|
178
|
+
try {
|
|
179
|
+
const parsed = yaml.load(match[1]);
|
|
180
|
+
frontmatter = { ...frontmatter, ...parsed };
|
|
181
|
+
frontmatter.target = "github-copilot";
|
|
182
|
+
frontmatter.tools = [
|
|
183
|
+
"edit",
|
|
184
|
+
"terminal",
|
|
185
|
+
"ls",
|
|
186
|
+
"grep_search",
|
|
187
|
+
"githubRepo"
|
|
188
|
+
];
|
|
189
|
+
body = content.replace(frontmatterRegex, "").trim();
|
|
190
|
+
} catch (_) {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const yamlStr = yaml.dump(frontmatter);
|
|
194
|
+
return `---
|
|
195
|
+
${yamlStr}---
|
|
196
|
+
|
|
197
|
+
${body}`;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/utils/platforms/default.ts
|
|
202
|
+
var DefaultHandler = class {
|
|
203
|
+
constructor(platform = "default") {
|
|
204
|
+
this.platform = platform;
|
|
205
|
+
}
|
|
206
|
+
getTargetFileName(itemName, _type) {
|
|
207
|
+
return itemName;
|
|
208
|
+
}
|
|
209
|
+
transform(content, _type, _itemName) {
|
|
210
|
+
return content;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/utils/platforms/gemini.ts
|
|
215
|
+
import yaml2 from "js-yaml";
|
|
216
|
+
|
|
217
|
+
// src/utils/toml.ts
|
|
218
|
+
function convertToGeminiCommandTOML(markdownContent) {
|
|
219
|
+
const lines = markdownContent.split("\n");
|
|
220
|
+
let description = "";
|
|
221
|
+
for (let i = 0; i < lines.length; i++) {
|
|
222
|
+
const trimmedLine = lines[i].trim();
|
|
223
|
+
if (trimmedLine && trimmedLine !== "---" && !trimmedLine.startsWith("<!--")) {
|
|
224
|
+
if (trimmedLine.startsWith("#")) {
|
|
225
|
+
description = trimmedLine.replace(/^#+\s*/, "");
|
|
226
|
+
} else {
|
|
227
|
+
description = trimmedLine;
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (description.length > 100) {
|
|
233
|
+
description = `${description.substring(0, 97)}...`;
|
|
234
|
+
}
|
|
235
|
+
const escapedContent = markdownContent.replace(/"""/g, '\\"\\"\\"');
|
|
236
|
+
return `description = ${JSON.stringify(description)}
|
|
237
|
+
|
|
238
|
+
prompt = """
|
|
239
|
+
${escapedContent.trim()}
|
|
240
|
+
"""
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/utils/platforms/gemini.ts
|
|
245
|
+
var GeminiHandler = class {
|
|
246
|
+
platform = "gemini";
|
|
247
|
+
getTargetFileName(itemName, type) {
|
|
248
|
+
if (type === "workflow") {
|
|
249
|
+
return `${itemName.replace(/\.md$/, "")}.toml`;
|
|
250
|
+
}
|
|
251
|
+
return itemName;
|
|
252
|
+
}
|
|
253
|
+
transform(content, type, itemName) {
|
|
254
|
+
if (type === "workflow") {
|
|
255
|
+
return convertToGeminiCommandTOML(content);
|
|
256
|
+
}
|
|
257
|
+
if (type === "agent") {
|
|
258
|
+
const frontmatterRegex = /^---\n([\s\S]*?)\n---/;
|
|
259
|
+
const match = content.match(frontmatterRegex);
|
|
260
|
+
let frontmatter = {
|
|
261
|
+
name: itemName,
|
|
262
|
+
description: "",
|
|
263
|
+
tools: [
|
|
264
|
+
"list_directory",
|
|
265
|
+
"read_file",
|
|
266
|
+
"write_file",
|
|
267
|
+
"glob",
|
|
268
|
+
"grep_search",
|
|
269
|
+
"replace",
|
|
270
|
+
"run_shell_command",
|
|
271
|
+
"google_web_search",
|
|
272
|
+
"web_fetch",
|
|
273
|
+
"ask_user",
|
|
274
|
+
"save_memory",
|
|
275
|
+
"activate_skill"
|
|
276
|
+
]
|
|
277
|
+
};
|
|
278
|
+
let body = content;
|
|
279
|
+
if (match) {
|
|
280
|
+
try {
|
|
281
|
+
const parsed = yaml2.load(match[1]);
|
|
282
|
+
frontmatter = { ...frontmatter, ...parsed };
|
|
283
|
+
frontmatter.tools = [
|
|
284
|
+
"list_directory",
|
|
285
|
+
"read_file",
|
|
286
|
+
"write_file",
|
|
287
|
+
"glob",
|
|
288
|
+
"grep_search",
|
|
289
|
+
"replace",
|
|
290
|
+
"run_shell_command",
|
|
291
|
+
"google_web_search",
|
|
292
|
+
"web_fetch",
|
|
293
|
+
"ask_user",
|
|
294
|
+
"save_memory",
|
|
295
|
+
"activate_skill"
|
|
296
|
+
];
|
|
297
|
+
delete frontmatter.skills;
|
|
298
|
+
body = content.replace(frontmatterRegex, "").trim();
|
|
299
|
+
} catch (_) {
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const yamlStr = yaml2.dump(frontmatter);
|
|
303
|
+
return `---
|
|
304
|
+
${yamlStr}---
|
|
305
|
+
|
|
306
|
+
${body}`;
|
|
307
|
+
}
|
|
308
|
+
return content;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/utils/platforms/windsurf.ts
|
|
313
|
+
import path3 from "node:path";
|
|
314
|
+
var WindsurfHandler = class {
|
|
315
|
+
platform = "windsurf";
|
|
316
|
+
getTargetFileName(itemName, type) {
|
|
317
|
+
if (type === "agent") {
|
|
318
|
+
return path3.join(path3.parse(itemName).name, "AGENTS.md");
|
|
319
|
+
}
|
|
320
|
+
return itemName;
|
|
321
|
+
}
|
|
322
|
+
transform(content, _type, _itemName) {
|
|
323
|
+
return content;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// src/utils/platforms/index.ts
|
|
328
|
+
var handlers = /* @__PURE__ */ new Map();
|
|
329
|
+
function registerHandler(handler) {
|
|
330
|
+
handlers.set(handler.platform, handler);
|
|
331
|
+
}
|
|
332
|
+
registerHandler(new AntigravityHandler());
|
|
333
|
+
registerHandler(new CopilotHandler());
|
|
334
|
+
registerHandler(new GeminiHandler());
|
|
335
|
+
registerHandler(new WindsurfHandler());
|
|
336
|
+
function getHandler(platform) {
|
|
337
|
+
return handlers.get(platform) || new DefaultHandler(platform);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/commands/add.ts
|
|
341
|
+
function getPlatformOptions(type) {
|
|
342
|
+
const paths = getTargetPaths(type);
|
|
343
|
+
return Object.entries(paths).filter(([_, pathStr]) => !!pathStr).map(([platform, pathStr]) => ({
|
|
344
|
+
label: PLATFORM_LABELS[platform],
|
|
345
|
+
value: platform,
|
|
346
|
+
hint: pathStr.replace(os3.homedir(), "~")
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
async function installItem(itemName, overwrite, sourcePath, targetBaseDir) {
|
|
350
|
+
const targetPath = path4.join(targetBaseDir, itemName);
|
|
351
|
+
if (!await fs2.pathExists(sourcePath)) {
|
|
352
|
+
throw new Error(`Item '${itemName}' not found at ${sourcePath}`);
|
|
353
|
+
}
|
|
354
|
+
if (!overwrite && await fs2.pathExists(targetPath)) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
await fs2.ensureDir(targetBaseDir);
|
|
358
|
+
await fs2.copy(sourcePath, targetPath, { overwrite });
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
async function add(type, url, options) {
|
|
362
|
+
if (!options?.skipIntro) {
|
|
363
|
+
intro(pc.bgCyan(pc.black(" AI Manager : Add ")));
|
|
364
|
+
}
|
|
365
|
+
let currentType = type;
|
|
366
|
+
const isSingleShot = !!type || !!url;
|
|
367
|
+
while (true) {
|
|
368
|
+
if (!currentType && !url) {
|
|
369
|
+
const selectedType = await select({
|
|
370
|
+
message: "What would you like to add?",
|
|
371
|
+
options: [
|
|
372
|
+
{ label: "Skills", value: "skill" },
|
|
373
|
+
{ label: "Agents", value: "agent" },
|
|
374
|
+
{ label: "Workflows", value: "workflow" },
|
|
375
|
+
{ label: "Exit", value: "exit" }
|
|
376
|
+
]
|
|
377
|
+
});
|
|
378
|
+
if (isCancel(selectedType) || selectedType === "exit") {
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
currentType = selectedType;
|
|
382
|
+
}
|
|
383
|
+
const normalizedType = currentType?.toLowerCase().endsWith("s") && currentType.length > 1 ? currentType.slice(0, -1) : currentType ?? "";
|
|
384
|
+
if (!TYPE_DIRS[normalizedType] && !url) {
|
|
385
|
+
cancel(`Unknown type: ${currentType}. Supported: skill, agent, workflow`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
let tempDir = null;
|
|
390
|
+
let selectedItems = [];
|
|
391
|
+
try {
|
|
392
|
+
if (url) {
|
|
393
|
+
const s2 = spinner();
|
|
394
|
+
s2.start(`Fetching ${normalizedType} from GitHub...`);
|
|
395
|
+
try {
|
|
396
|
+
const result = await fetchSkillFromGitHub(url);
|
|
397
|
+
tempDir = result.tempDir;
|
|
398
|
+
selectedItems = [result.skillName];
|
|
399
|
+
s2.stop(pc.green(`Fetched ${normalizedType}: ${result.skillName}`));
|
|
400
|
+
} catch (e) {
|
|
401
|
+
s2.stop(pc.red("Failed to fetch"));
|
|
402
|
+
throw e;
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
const sourceDir = TYPE_DIRS[normalizedType];
|
|
406
|
+
if (!await fs2.pathExists(sourceDir)) {
|
|
407
|
+
cancel(`${normalizedType} directory not found!`);
|
|
408
|
+
process.exit(1);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const entries = await fs2.readdir(sourceDir, { withFileTypes: true });
|
|
412
|
+
const availableItems = entries.filter(
|
|
413
|
+
(entry) => entry.isDirectory() || (normalizedType === "workflow" || normalizedType === "agent") && entry.isFile() && entry.name.endsWith(".md")
|
|
414
|
+
).map((entry) => ({ label: entry.name, value: entry.name })).sort((a, b) => a.label.localeCompare(b.label));
|
|
415
|
+
if (availableItems.length === 0) {
|
|
416
|
+
note(`No ${normalizedType}s found in directory.`, "Information");
|
|
417
|
+
if (isSingleShot) break;
|
|
418
|
+
currentType = void 0;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const items = await multiselect({
|
|
422
|
+
message: `Select ${normalizedType}s to install:`,
|
|
423
|
+
options: availableItems,
|
|
424
|
+
required: true
|
|
425
|
+
});
|
|
426
|
+
if (isCancel(items)) {
|
|
427
|
+
if (isSingleShot) break;
|
|
428
|
+
currentType = void 0;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
selectedItems = items;
|
|
432
|
+
}
|
|
433
|
+
const supportedPaths = getTargetPaths(normalizedType);
|
|
434
|
+
const currentPlatformOptions = getPlatformOptions(normalizedType);
|
|
435
|
+
if (currentPlatformOptions.length === 0) {
|
|
436
|
+
cancel(`No supported platforms found for type '${normalizedType}'.`);
|
|
437
|
+
process.exit(1);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const platforms = await multiselect({
|
|
441
|
+
message: "Select target platforms:",
|
|
442
|
+
options: currentPlatformOptions,
|
|
443
|
+
required: true
|
|
444
|
+
});
|
|
445
|
+
if (isCancel(platforms)) {
|
|
446
|
+
if (tempDir) await fs2.remove(tempDir);
|
|
447
|
+
if (isSingleShot) break;
|
|
448
|
+
currentType = void 0;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
const selectedPlatforms = platforms;
|
|
452
|
+
const existingItems = [];
|
|
453
|
+
for (const platform of selectedPlatforms) {
|
|
454
|
+
const targetBase = supportedPaths[platform];
|
|
455
|
+
if (!targetBase) continue;
|
|
456
|
+
for (const item of selectedItems) {
|
|
457
|
+
const targetPath = path4.join(targetBase, item);
|
|
458
|
+
if (await fs2.pathExists(targetPath)) {
|
|
459
|
+
existingItems.push({ item, platform });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
let overwrite = true;
|
|
464
|
+
if (existingItems.length > 0) {
|
|
465
|
+
const limit = 5;
|
|
466
|
+
const displayList = existingItems.slice(0, limit).map((e) => `${pc.bold(e.item)} (${e.platform})`).join(", ");
|
|
467
|
+
const remainder = existingItems.length - limit;
|
|
468
|
+
const message = remainder > 0 ? `The following items already exist: ${displayList}, and ${remainder} others.` : `The following items already exist: ${displayList}.`;
|
|
469
|
+
note(message, "Attention");
|
|
470
|
+
const shouldOverwrite = await confirm({
|
|
471
|
+
message: "Do you want to overwrite existing items?",
|
|
472
|
+
initialValue: false
|
|
473
|
+
});
|
|
474
|
+
if (isCancel(shouldOverwrite)) {
|
|
475
|
+
if (tempDir) await fs2.remove(tempDir);
|
|
476
|
+
if (isSingleShot) break;
|
|
477
|
+
currentType = void 0;
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
overwrite = shouldOverwrite;
|
|
481
|
+
}
|
|
482
|
+
note(
|
|
483
|
+
`Installing ${selectedItems.length} ${normalizedType}s to ${selectedPlatforms.length} platforms...`,
|
|
484
|
+
"Summary"
|
|
485
|
+
);
|
|
486
|
+
const s = spinner();
|
|
487
|
+
s.start("Installing...");
|
|
488
|
+
const errors = [];
|
|
489
|
+
let installedCount = 0;
|
|
490
|
+
let skippedCount = 0;
|
|
491
|
+
for (const platform of selectedPlatforms) {
|
|
492
|
+
const targetBase = supportedPaths[platform];
|
|
493
|
+
if (!targetBase) continue;
|
|
494
|
+
for (const item of selectedItems) {
|
|
495
|
+
const message = `Installing ${pc.bold(item)} to ${pc.cyan(platform)}...`;
|
|
496
|
+
s.message(message);
|
|
497
|
+
try {
|
|
498
|
+
let currentSourcePath;
|
|
499
|
+
if (url && tempDir) {
|
|
500
|
+
const potentialFile = path4.join(tempDir, item);
|
|
501
|
+
if (await fs2.pathExists(potentialFile) && (await fs2.stat(potentialFile)).isFile()) {
|
|
502
|
+
currentSourcePath = potentialFile;
|
|
503
|
+
} else {
|
|
504
|
+
currentSourcePath = tempDir;
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
const src = path4.join(TYPE_DIRS[normalizedType], item);
|
|
508
|
+
if ((await fs2.stat(src)).isFile()) {
|
|
509
|
+
currentSourcePath = src;
|
|
510
|
+
} else {
|
|
511
|
+
currentSourcePath = src;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const handler = getHandler(platform);
|
|
515
|
+
const targetItemName = handler.getTargetFileName(
|
|
516
|
+
item,
|
|
517
|
+
normalizedType
|
|
518
|
+
);
|
|
519
|
+
if ((normalizedType === "agent" || normalizedType === "workflow") && currentSourcePath.endsWith(".md")) {
|
|
520
|
+
const targetPath = path4.join(targetBase, targetItemName);
|
|
521
|
+
if (!overwrite && await fs2.pathExists(targetPath)) {
|
|
522
|
+
skippedCount++;
|
|
523
|
+
} else {
|
|
524
|
+
let content = await fs2.readFile(currentSourcePath, "utf-8");
|
|
525
|
+
content = handler.transform(
|
|
526
|
+
content,
|
|
527
|
+
normalizedType,
|
|
528
|
+
path4.parse(item).name
|
|
529
|
+
);
|
|
530
|
+
await fs2.ensureDir(path4.dirname(targetPath));
|
|
531
|
+
await fs2.writeFile(targetPath, content);
|
|
532
|
+
installedCount++;
|
|
533
|
+
}
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
const installed = await installItem(
|
|
537
|
+
targetItemName,
|
|
538
|
+
overwrite,
|
|
539
|
+
currentSourcePath,
|
|
540
|
+
targetBase
|
|
541
|
+
);
|
|
542
|
+
if (installed) {
|
|
543
|
+
installedCount++;
|
|
544
|
+
} else {
|
|
545
|
+
skippedCount++;
|
|
546
|
+
}
|
|
547
|
+
} catch (err) {
|
|
548
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
549
|
+
errors.push(`${item} -> ${platform}: ${errorMessage}`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (tempDir) {
|
|
554
|
+
await fs2.remove(tempDir);
|
|
555
|
+
}
|
|
556
|
+
if (errors.length > 0) {
|
|
557
|
+
s.stop(
|
|
558
|
+
pc.yellow(
|
|
559
|
+
`Completed with errors. Installed: ${installedCount}, Skipped: ${skippedCount}, Errors: ${errors.length}`
|
|
560
|
+
)
|
|
561
|
+
);
|
|
562
|
+
console.error(pc.red("\nErrors encountered:"));
|
|
563
|
+
for (const e of errors) {
|
|
564
|
+
console.error(pc.red(`- ${e}`));
|
|
565
|
+
}
|
|
566
|
+
} else {
|
|
567
|
+
s.stop(
|
|
568
|
+
pc.green(
|
|
569
|
+
`Successfully installed ${installedCount} ${normalizedType}s. (${skippedCount} skipped)`
|
|
570
|
+
)
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if (tempDir) await fs2.remove(tempDir);
|
|
575
|
+
cancel(`An error occurred: ${error}`);
|
|
576
|
+
process.exit(1);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (isSingleShot) break;
|
|
580
|
+
currentType = void 0;
|
|
581
|
+
}
|
|
582
|
+
if (!options?.skipIntro) {
|
|
583
|
+
outro("You're all set!");
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/commands/import.ts
|
|
588
|
+
import path5 from "node:path";
|
|
589
|
+
import {
|
|
590
|
+
cancel as cancel2,
|
|
591
|
+
confirm as confirm2,
|
|
592
|
+
intro as intro2,
|
|
593
|
+
isCancel as isCancel2,
|
|
594
|
+
outro as outro2,
|
|
595
|
+
select as select2,
|
|
596
|
+
spinner as spinner2,
|
|
597
|
+
text
|
|
598
|
+
} from "@clack/prompts";
|
|
599
|
+
import fs3 from "fs-extra";
|
|
600
|
+
import pc2 from "picocolors";
|
|
601
|
+
async function importItem(type, url, options) {
|
|
602
|
+
if (!options?.skipIntro) {
|
|
603
|
+
intro2(pc2.bgCyan(pc2.black(" AI Manager : Import ")));
|
|
604
|
+
}
|
|
605
|
+
let currentType = type;
|
|
606
|
+
if (!currentType) {
|
|
607
|
+
const selectedType = await select2({
|
|
608
|
+
message: "What would you like to import?",
|
|
609
|
+
options: [
|
|
610
|
+
{ label: "Skill", value: "skill" },
|
|
611
|
+
{ label: "Agent", value: "agent" },
|
|
612
|
+
{ label: "Workflow", value: "workflow" }
|
|
613
|
+
]
|
|
614
|
+
});
|
|
615
|
+
if (isCancel2(selectedType)) return;
|
|
616
|
+
currentType = selectedType;
|
|
617
|
+
}
|
|
618
|
+
const normalizedType = currentType.toLowerCase().endsWith("s") ? currentType.slice(0, -1) : currentType;
|
|
619
|
+
const targetBaseDir = TYPE_DIRS[normalizedType];
|
|
620
|
+
if (!targetBaseDir) {
|
|
621
|
+
cancel2(`Unknown type: ${currentType}. Supported: skill, agent, workflow`);
|
|
622
|
+
process.exit(1);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
let currentUrl = url;
|
|
626
|
+
if (!currentUrl) {
|
|
627
|
+
const inputUrl = await text({
|
|
628
|
+
message: "Enter the GitHub URL:",
|
|
629
|
+
placeholder: "https://github.com/owner/repo/tree/main/path/to/item",
|
|
630
|
+
validate: (value) => {
|
|
631
|
+
if (!value) return "URL is required.";
|
|
632
|
+
if (!value.startsWith("https://github.com/"))
|
|
633
|
+
return "Must be a GitHub URL.";
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
if (isCancel2(inputUrl)) return;
|
|
637
|
+
currentUrl = inputUrl;
|
|
638
|
+
}
|
|
639
|
+
let tempDir = null;
|
|
640
|
+
try {
|
|
641
|
+
const s = spinner2();
|
|
642
|
+
s.start(`Fetching ${normalizedType} from GitHub...`);
|
|
643
|
+
let itemName = "";
|
|
644
|
+
let isFile = false;
|
|
645
|
+
try {
|
|
646
|
+
const result = await fetchSkillFromGitHub(currentUrl);
|
|
647
|
+
tempDir = result.tempDir;
|
|
648
|
+
if (!tempDir) throw new Error("Failed to create temp directory");
|
|
649
|
+
itemName = result.skillName;
|
|
650
|
+
isFile = result.isFile;
|
|
651
|
+
s.stop(pc2.green(`Fetched: ${itemName}`));
|
|
652
|
+
} catch (e) {
|
|
653
|
+
s.stop(pc2.red("Failed to fetch"));
|
|
654
|
+
throw e;
|
|
655
|
+
}
|
|
656
|
+
const shouldFlatten = (normalizedType === "agent" || normalizedType === "workflow") && !isFile;
|
|
657
|
+
const targetPath = shouldFlatten ? targetBaseDir : path5.join(targetBaseDir, itemName);
|
|
658
|
+
if (shouldFlatten) {
|
|
659
|
+
const srcFiles = await fs3.readdir(tempDir);
|
|
660
|
+
const conflicts = [];
|
|
661
|
+
for (const file of srcFiles) {
|
|
662
|
+
if (await fs3.pathExists(path5.join(targetBaseDir, file))) {
|
|
663
|
+
conflicts.push(file);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (conflicts.length > 0) {
|
|
667
|
+
const limit = 5;
|
|
668
|
+
const displayList = conflicts.slice(0, limit).join(", ");
|
|
669
|
+
const remainder = conflicts.length - limit;
|
|
670
|
+
const message = remainder > 0 ? `Files ${displayList} (and ${remainder} others) already exist in ${targetBaseDir}. Overwrite?` : `Files ${displayList} already exist in ${targetBaseDir}. Overwrite?`;
|
|
671
|
+
const shouldOverwrite = await confirm2({
|
|
672
|
+
message,
|
|
673
|
+
initialValue: false
|
|
674
|
+
});
|
|
675
|
+
if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
|
|
676
|
+
await fs3.remove(tempDir);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
} else if (await fs3.pathExists(targetPath)) {
|
|
681
|
+
const shouldOverwrite = await confirm2({
|
|
682
|
+
message: `${normalizedType} '${itemName}' already exists in the repo. Overwrite?`,
|
|
683
|
+
initialValue: false
|
|
684
|
+
});
|
|
685
|
+
if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
|
|
686
|
+
await fs3.remove(tempDir);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
s.start(`Importing ${itemName} to ${targetBaseDir}...`);
|
|
691
|
+
await fs3.ensureDir(targetBaseDir);
|
|
692
|
+
if (isFile) {
|
|
693
|
+
await fs3.copy(path5.join(tempDir, itemName), targetPath, {
|
|
694
|
+
overwrite: true
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
if (shouldFlatten) {
|
|
698
|
+
await fs3.copy(tempDir, targetBaseDir, { overwrite: true });
|
|
699
|
+
} else {
|
|
700
|
+
await fs3.copy(tempDir, targetPath, { overwrite: true });
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
s.stop(pc2.green(`Successfully imported ${itemName}!`));
|
|
704
|
+
await fs3.remove(tempDir);
|
|
705
|
+
if (!options?.skipIntro) {
|
|
706
|
+
outro2(`${normalizedType} available at: ${targetPath}`);
|
|
707
|
+
}
|
|
708
|
+
} catch (error) {
|
|
709
|
+
if (tempDir) await fs3.remove(tempDir);
|
|
710
|
+
cancel2(`An error occurred: ${error}`);
|
|
711
|
+
process.exit(1);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/commands/list.ts
|
|
717
|
+
import { intro as intro3, isCancel as isCancel3, outro as outro3, select as select3 } from "@clack/prompts";
|
|
718
|
+
import fs4 from "fs-extra";
|
|
719
|
+
import pc3 from "picocolors";
|
|
720
|
+
async function performListing(type, local) {
|
|
721
|
+
const normalizedType = type.toLowerCase().endsWith("s") ? type.slice(0, -1) : type.toLowerCase();
|
|
722
|
+
if (local) {
|
|
723
|
+
console.log(pc3.bold(pc3.blue(`Installed ${normalizedType}s (Local):`)));
|
|
724
|
+
const targetPaths = getTargetPaths(normalizedType);
|
|
725
|
+
let foundAny = false;
|
|
726
|
+
for (const [platform, pathStr] of Object.entries(targetPaths)) {
|
|
727
|
+
const fullPath = pathStr;
|
|
728
|
+
if (await fs4.pathExists(fullPath)) {
|
|
729
|
+
const entries = await fs4.readdir(fullPath, {
|
|
730
|
+
withFileTypes: true
|
|
731
|
+
});
|
|
732
|
+
const items = entries.filter(
|
|
733
|
+
(entry) => entry.isDirectory() || entry.isFile() && entry.name.endsWith(".md")
|
|
734
|
+
).map((entry) => entry.name).sort();
|
|
735
|
+
if (items.length > 0) {
|
|
736
|
+
foundAny = true;
|
|
737
|
+
console.log(pc3.cyan(` ${platform}:`));
|
|
738
|
+
for (const item of items) {
|
|
739
|
+
console.log(` - ${item}`);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (!foundAny) {
|
|
745
|
+
console.log(pc3.dim(` No installed ${normalizedType}s found.`));
|
|
746
|
+
}
|
|
747
|
+
console.log("");
|
|
748
|
+
} else {
|
|
749
|
+
if (!TYPE_DIRS[normalizedType]) {
|
|
750
|
+
console.error(
|
|
751
|
+
pc3.red(
|
|
752
|
+
`Unknown type: ${type}. Supported types: skill, agent, workflow`
|
|
753
|
+
)
|
|
754
|
+
);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const dirPath = TYPE_DIRS[normalizedType];
|
|
758
|
+
if (!await fs4.pathExists(dirPath)) {
|
|
759
|
+
console.log(
|
|
760
|
+
pc3.yellow(`No ${normalizedType} directory found at: ${dirPath}`)
|
|
761
|
+
);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const entries = await fs4.readdir(dirPath, { withFileTypes: true });
|
|
765
|
+
const items = entries.filter(
|
|
766
|
+
(entry) => entry.isDirectory() || normalizedType !== "skill" && entry.isFile() && entry.name.endsWith(".md")
|
|
767
|
+
).map((entry) => entry.name).sort();
|
|
768
|
+
if (items.length > 0) {
|
|
769
|
+
console.log(pc3.bold(pc3.blue(`Available ${normalizedType}s:`)));
|
|
770
|
+
for (const item of items) {
|
|
771
|
+
console.log(`- ${item}`);
|
|
772
|
+
}
|
|
773
|
+
console.log(pc3.dim(`Total: ${items.length} ${normalizedType}s
|
|
774
|
+
`));
|
|
775
|
+
} else {
|
|
776
|
+
console.log(pc3.dim(`No ${normalizedType}s found.`));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
async function list(type, options) {
|
|
781
|
+
if (!options?.skipIntro) {
|
|
782
|
+
intro3(pc3.bgCyan(pc3.black(" AI Manager : List ")));
|
|
783
|
+
}
|
|
784
|
+
let currentType = type;
|
|
785
|
+
let isLocal = !!options?.local;
|
|
786
|
+
const isSingleShot = !!type;
|
|
787
|
+
try {
|
|
788
|
+
while (true) {
|
|
789
|
+
if (!currentType) {
|
|
790
|
+
const selected = await select3({
|
|
791
|
+
message: `What would you like to list? (View: ${isLocal ? "Local" : "Repository"})`,
|
|
792
|
+
options: [
|
|
793
|
+
{ label: "All Skills", value: "skill" },
|
|
794
|
+
{ label: "All Agents", value: "agent" },
|
|
795
|
+
{ label: "All Workflows", value: "workflow" },
|
|
796
|
+
{
|
|
797
|
+
label: `Switch to ${isLocal ? "Repository" : "Local"} View`,
|
|
798
|
+
value: "toggle"
|
|
799
|
+
},
|
|
800
|
+
{ label: "Exit", value: "exit" }
|
|
801
|
+
]
|
|
802
|
+
});
|
|
803
|
+
if (isCancel3(selected) || selected === "exit") {
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
if (selected === "toggle") {
|
|
807
|
+
isLocal = !isLocal;
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
currentType = selected;
|
|
811
|
+
}
|
|
812
|
+
const typesToList = currentType === "all" ? ["skill", "agent", "workflow"] : [currentType];
|
|
813
|
+
for (const t of typesToList) {
|
|
814
|
+
await performListing(t, isLocal);
|
|
815
|
+
}
|
|
816
|
+
if (isSingleShot) break;
|
|
817
|
+
currentType = void 0;
|
|
818
|
+
}
|
|
819
|
+
} catch (error) {
|
|
820
|
+
console.error(pc3.red("Error listing items:"), error);
|
|
821
|
+
}
|
|
822
|
+
if (!options?.skipIntro) {
|
|
823
|
+
outro3("Done!");
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/commands/remove.ts
|
|
828
|
+
import path6 from "node:path";
|
|
829
|
+
import {
|
|
830
|
+
confirm as confirm3,
|
|
831
|
+
intro as intro4,
|
|
832
|
+
isCancel as isCancel4,
|
|
833
|
+
multiselect as multiselect2,
|
|
834
|
+
note as note2,
|
|
835
|
+
outro as outro4,
|
|
836
|
+
select as select4,
|
|
837
|
+
spinner as spinner3
|
|
838
|
+
} from "@clack/prompts";
|
|
839
|
+
import fs5 from "fs-extra";
|
|
840
|
+
import pc4 from "picocolors";
|
|
841
|
+
async function remove(type, options) {
|
|
842
|
+
if (!options?.skipIntro) {
|
|
843
|
+
intro4(pc4.bgCyan(pc4.black(" AI Manager : Remove ")));
|
|
844
|
+
}
|
|
845
|
+
let currentType = type;
|
|
846
|
+
const isSingleShot = !!type;
|
|
847
|
+
while (true) {
|
|
848
|
+
if (!currentType) {
|
|
849
|
+
const selectedType = await select4({
|
|
850
|
+
message: "What would you like to remove?",
|
|
851
|
+
options: [
|
|
852
|
+
{ label: "Skills", value: "skill" },
|
|
853
|
+
{ label: "Agents", value: "agent" },
|
|
854
|
+
{ label: "Workflows", value: "workflow" },
|
|
855
|
+
{ label: "Exit", value: "exit" }
|
|
856
|
+
]
|
|
857
|
+
});
|
|
858
|
+
if (isCancel4(selectedType) || selectedType === "exit") {
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
currentType = selectedType;
|
|
862
|
+
}
|
|
863
|
+
const normalizedType = currentType.toLowerCase().endsWith("s") ? currentType.slice(0, -1) : currentType;
|
|
864
|
+
const targetPaths = getTargetPaths(normalizedType);
|
|
865
|
+
const uniqueItems = /* @__PURE__ */ new Set();
|
|
866
|
+
const s = spinner3();
|
|
867
|
+
s.start("Scanning for installed items...");
|
|
868
|
+
for (const pathStr of Object.values(targetPaths)) {
|
|
869
|
+
if (await fs5.pathExists(pathStr)) {
|
|
870
|
+
const entries = await fs5.readdir(pathStr, {
|
|
871
|
+
withFileTypes: true
|
|
872
|
+
});
|
|
873
|
+
for (const entry of entries) {
|
|
874
|
+
if (entry.isDirectory() || normalizedType === "workflow" && entry.isFile() && entry.name.endsWith(".md") || normalizedType === "agent" && entry.isFile() && entry.name.endsWith(".md")) {
|
|
875
|
+
uniqueItems.add(entry.name);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
s.stop(`Found ${uniqueItems.size} unique installed ${normalizedType}s.`);
|
|
881
|
+
if (uniqueItems.size === 0) {
|
|
882
|
+
note2(`No installed ${normalizedType}s found.`, "Information");
|
|
883
|
+
if (isSingleShot) break;
|
|
884
|
+
currentType = void 0;
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
const sortedItems = Array.from(uniqueItems).sort();
|
|
888
|
+
const itemSelections = await multiselect2({
|
|
889
|
+
message: `Select ${normalizedType}s to remove:`,
|
|
890
|
+
options: sortedItems.map((item) => ({ value: item, label: item })),
|
|
891
|
+
required: true
|
|
892
|
+
});
|
|
893
|
+
if (isCancel4(itemSelections)) {
|
|
894
|
+
if (isSingleShot) break;
|
|
895
|
+
currentType = void 0;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
const selectedItems = itemSelections;
|
|
899
|
+
const platformOptions = Object.entries(targetPaths).filter(([_, pathStr]) => !!pathStr).map(([platform]) => ({
|
|
900
|
+
label: PLATFORM_LABELS[platform],
|
|
901
|
+
value: platform
|
|
902
|
+
}));
|
|
903
|
+
const platformSelections = await multiselect2({
|
|
904
|
+
message: "Select platforms to remove from:",
|
|
905
|
+
options: platformOptions,
|
|
906
|
+
required: true
|
|
907
|
+
});
|
|
908
|
+
if (isCancel4(platformSelections)) {
|
|
909
|
+
if (isSingleShot) break;
|
|
910
|
+
currentType = void 0;
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const selectedPlatforms = platformSelections;
|
|
914
|
+
const confirmDelete = await confirm3({
|
|
915
|
+
message: `Are you sure you want to remove ${selectedItems.length} item(s) from ${selectedPlatforms.length} platform(s)?`,
|
|
916
|
+
initialValue: false
|
|
917
|
+
});
|
|
918
|
+
if (isCancel4(confirmDelete) || !confirmDelete) {
|
|
919
|
+
if (isSingleShot) break;
|
|
920
|
+
currentType = void 0;
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
const sDel = spinner3();
|
|
924
|
+
sDel.start("Removing items...");
|
|
925
|
+
let removedCount = 0;
|
|
926
|
+
let notFoundCount = 0;
|
|
927
|
+
const errors = [];
|
|
928
|
+
for (const platform of selectedPlatforms) {
|
|
929
|
+
const targetBase = targetPaths[platform];
|
|
930
|
+
if (!targetBase) continue;
|
|
931
|
+
for (const item of selectedItems) {
|
|
932
|
+
const targetPath = path6.join(targetBase, item);
|
|
933
|
+
if (await fs5.pathExists(targetPath)) {
|
|
934
|
+
sDel.message(`Removing ${pc4.bold(item)} from ${pc4.cyan(platform)}...`);
|
|
935
|
+
try {
|
|
936
|
+
await fs5.remove(targetPath);
|
|
937
|
+
removedCount++;
|
|
938
|
+
} catch (err) {
|
|
939
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
940
|
+
errors.push(`${item} (${platform}): ${msg}`);
|
|
941
|
+
}
|
|
942
|
+
} else {
|
|
943
|
+
notFoundCount++;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
if (errors.length > 0) {
|
|
948
|
+
sDel.stop(
|
|
949
|
+
pc4.yellow(
|
|
950
|
+
`Completed with errors. Removed: ${removedCount}, Not Found: ${notFoundCount}, Errors: ${errors.length}`
|
|
951
|
+
)
|
|
952
|
+
);
|
|
953
|
+
console.error(pc4.red("\nErrors encountered:"));
|
|
954
|
+
for (const e of errors) {
|
|
955
|
+
console.error(pc4.red(`- ${e}`));
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
sDel.stop(
|
|
959
|
+
pc4.green(
|
|
960
|
+
`Successfully removed ${removedCount} items. (${notFoundCount} not found)`
|
|
961
|
+
)
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
if (isSingleShot) break;
|
|
965
|
+
currentType = void 0;
|
|
966
|
+
}
|
|
967
|
+
if (!options?.skipIntro) {
|
|
968
|
+
outro4("Done!");
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// bin/cli.ts
|
|
973
|
+
var program = new Command();
|
|
974
|
+
program.name("ai-agent").description("CLI to manage AI agents, skills, and workflows").version("1.4.0");
|
|
975
|
+
async function interactiveMain() {
|
|
976
|
+
intro5(pc5.bgCyan(pc5.black(" AI Manager ")));
|
|
977
|
+
while (true) {
|
|
978
|
+
const action = await select5({
|
|
979
|
+
message: "What would you like to do?",
|
|
980
|
+
options: [
|
|
981
|
+
{ label: "Add Item (Install)", value: "add" },
|
|
982
|
+
{ label: "List Items", value: "list" },
|
|
983
|
+
{ label: "Import from GitHub", value: "import" },
|
|
984
|
+
{ label: "Remove Items", value: "remove" },
|
|
985
|
+
{ label: "Exit", value: "exit" }
|
|
986
|
+
]
|
|
987
|
+
});
|
|
988
|
+
if (isCancel5(action) || action === "exit") {
|
|
989
|
+
break;
|
|
990
|
+
}
|
|
991
|
+
switch (action) {
|
|
992
|
+
case "add":
|
|
993
|
+
await add(void 0, void 0, { skipIntro: true });
|
|
994
|
+
break;
|
|
995
|
+
case "list":
|
|
996
|
+
await list(void 0, { skipIntro: true });
|
|
997
|
+
break;
|
|
998
|
+
case "import":
|
|
999
|
+
await importItem(void 0, void 0, { skipIntro: true });
|
|
1000
|
+
break;
|
|
1001
|
+
case "remove":
|
|
1002
|
+
await remove(void 0, { skipIntro: true });
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
outro5("Goodbye!");
|
|
1007
|
+
}
|
|
1008
|
+
program.command("list [type]").description("List available items (skills, agents, workflows)").option("-l, --local", "List installed items locally").action(list);
|
|
1009
|
+
program.command("add [type] [url]").description(
|
|
1010
|
+
"Add an item (skill, agent, workflow) to platforms (Interactive or from GitHub URL)"
|
|
1011
|
+
).action(add);
|
|
1012
|
+
program.command("import [type] [url]").description("Import an item from GitHub to the repo").action(async (type, url) => {
|
|
1013
|
+
await importItem(type || "", url || "");
|
|
1014
|
+
});
|
|
1015
|
+
program.command("remove [type]").description("Remove locally installed items (skills, agents, workflows)").action(async (type) => {
|
|
1016
|
+
await remove(type || "");
|
|
1017
|
+
});
|
|
1018
|
+
if (process.argv.length <= 2) {
|
|
1019
|
+
await interactiveMain();
|
|
1020
|
+
} else {
|
|
1021
|
+
await program.parseAsync();
|
|
1022
|
+
}
|