@kurokeita/add-skill 1.8.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 +688 -360
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -86,10 +86,10 @@ pnpm dev import https://github.com/owner/repo/tree/main/skills/skill-name
|
|
|
86
86
|
<!-- SUPPORTED_AGENTS_START -->
|
|
87
87
|
| Platform | Agents Path | Skills Path | Workflows Path |
|
|
88
88
|
| :--- | :--- | :--- | :--- |
|
|
89
|
-
| Antigravity |
|
|
89
|
+
| Antigravity | *Not Supported* | `~/.gemini/antigravity/global_skills` | `~/.gemini/antigravity/global_workflows` |
|
|
90
90
|
| Gemini CLI | `~/.gemini/agents` | `~/.gemini/skills` | `~/.gemini/commands` |
|
|
91
91
|
| GitHub Copilot | `~/.copilot/agents` | `~/.copilot/skills` | `~/.copilot/prompts` |
|
|
92
|
-
| Windsurf |
|
|
92
|
+
| Windsurf | *Not Supported* | `~/.codeium/windsurf/skills` | `~/.codeium/windsurf/global_workflows` |
|
|
93
93
|
<!-- SUPPORTED_AGENTS_END -->
|
|
94
94
|
|
|
95
95
|
## Development
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// bin/cli.ts
|
|
4
|
+
import { intro as intro5, isCancel as isCancel5, outro as outro5, select as select5 } from "@clack/prompts";
|
|
4
5
|
import { Command } from "commander";
|
|
6
|
+
import pc5 from "picocolors";
|
|
5
7
|
|
|
6
8
|
// src/commands/add.ts
|
|
7
9
|
import os3 from "node:os";
|
|
8
|
-
import
|
|
10
|
+
import path4 from "node:path";
|
|
9
11
|
import {
|
|
10
12
|
cancel,
|
|
11
13
|
confirm,
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
multiselect,
|
|
15
17
|
note,
|
|
16
18
|
outro,
|
|
19
|
+
select,
|
|
17
20
|
spinner
|
|
18
21
|
} from "@clack/prompts";
|
|
19
22
|
import fs2 from "fs-extra";
|
|
@@ -114,10 +117,8 @@ var PLATFORM_PATHS_SKILLS = {
|
|
|
114
117
|
windsurf: path2.join(os2.homedir(), ".codeium/windsurf/skills")
|
|
115
118
|
};
|
|
116
119
|
var PLATFORM_PATHS_AGENTS = {
|
|
117
|
-
antigravity: path2.join(os2.homedir(), ".gemini/antigravity/global_agents"),
|
|
118
120
|
copilot: path2.join(os2.homedir(), ".copilot/agents"),
|
|
119
|
-
gemini: path2.join(os2.homedir(), ".gemini/agents")
|
|
120
|
-
windsurf: path2.join(os2.homedir(), ".codeium/windsurf/agents")
|
|
121
|
+
gemini: path2.join(os2.homedir(), ".gemini/agents")
|
|
121
122
|
};
|
|
122
123
|
var PLATFORM_PATHS_WORKFLOWS = {
|
|
123
124
|
antigravity: path2.join(os2.homedir(), ".gemini/antigravity/global_workflows"),
|
|
@@ -141,6 +142,78 @@ function getTargetPaths(type) {
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
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
|
+
|
|
144
217
|
// src/utils/toml.ts
|
|
145
218
|
function convertToGeminiCommandTOML(markdownContent) {
|
|
146
219
|
const lines = markdownContent.split("\n");
|
|
@@ -168,17 +241,113 @@ ${escapedContent.trim()}
|
|
|
168
241
|
`;
|
|
169
242
|
}
|
|
170
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
|
+
|
|
171
340
|
// src/commands/add.ts
|
|
172
341
|
function getPlatformOptions(type) {
|
|
173
342
|
const paths = getTargetPaths(type);
|
|
174
|
-
return Object.entries(paths).map(([platform, pathStr]) => ({
|
|
343
|
+
return Object.entries(paths).filter(([_, pathStr]) => !!pathStr).map(([platform, pathStr]) => ({
|
|
175
344
|
label: PLATFORM_LABELS[platform],
|
|
176
345
|
value: platform,
|
|
177
346
|
hint: pathStr.replace(os3.homedir(), "~")
|
|
178
347
|
}));
|
|
179
348
|
}
|
|
180
349
|
async function installItem(itemName, overwrite, sourcePath, targetBaseDir) {
|
|
181
|
-
const targetPath =
|
|
350
|
+
const targetPath = path4.join(targetBaseDir, itemName);
|
|
182
351
|
if (!await fs2.pathExists(sourcePath)) {
|
|
183
352
|
throw new Error(`Item '${itemName}' not found at ${sourcePath}`);
|
|
184
353
|
}
|
|
@@ -189,222 +358,283 @@ async function installItem(itemName, overwrite, sourcePath, targetBaseDir) {
|
|
|
189
358
|
await fs2.copy(sourcePath, targetPath, { overwrite });
|
|
190
359
|
return true;
|
|
191
360
|
}
|
|
192
|
-
async function add(type, url) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (!TYPE_DIRS[normalizedType]) {
|
|
196
|
-
cancel(`Unknown type: ${type}. Supported: skill, agent, workflow`);
|
|
197
|
-
process.exit(1);
|
|
361
|
+
async function add(type, url, options) {
|
|
362
|
+
if (!options?.skipIntro) {
|
|
363
|
+
intro(pc.bgCyan(pc.black(" AI Manager : Add ")));
|
|
198
364
|
}
|
|
199
|
-
let
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (url) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
s2.stop(pc.red("Failed to fetch"));
|
|
212
|
-
throw e;
|
|
213
|
-
}
|
|
214
|
-
} else {
|
|
215
|
-
const sourceDir = TYPE_DIRS[normalizedType];
|
|
216
|
-
if (!await fs2.pathExists(sourceDir)) {
|
|
217
|
-
cancel(`${normalizedType} directory not found!`);
|
|
218
|
-
process.exit(1);
|
|
219
|
-
}
|
|
220
|
-
const entries = await fs2.readdir(sourceDir, { withFileTypes: true });
|
|
221
|
-
const availableItems = entries.filter(
|
|
222
|
-
(entry) => entry.isDirectory() || (normalizedType === "workflow" || normalizedType === "agent") && entry.isFile() && entry.name.endsWith(".md")
|
|
223
|
-
).map((entry) => ({ label: entry.name, value: entry.name })).sort((a, b) => a.label.localeCompare(b.label));
|
|
224
|
-
if (availableItems.length === 0) {
|
|
225
|
-
cancel(`No ${normalizedType}s found in directory.`);
|
|
226
|
-
process.exit(0);
|
|
227
|
-
}
|
|
228
|
-
const items = await multiselect({
|
|
229
|
-
message: `Select ${normalizedType}s to install:`,
|
|
230
|
-
options: availableItems,
|
|
231
|
-
required: true
|
|
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
|
+
]
|
|
232
377
|
});
|
|
233
|
-
if (isCancel(
|
|
234
|
-
|
|
235
|
-
process.exit(0);
|
|
378
|
+
if (isCancel(selectedType) || selectedType === "exit") {
|
|
379
|
+
break;
|
|
236
380
|
}
|
|
237
|
-
|
|
381
|
+
currentType = selectedType;
|
|
238
382
|
}
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
cancel(`No supported platforms found for type '${normalizedType}'.`);
|
|
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`);
|
|
243
386
|
process.exit(1);
|
|
387
|
+
return;
|
|
244
388
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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;
|
|
265
430
|
}
|
|
431
|
+
selectedItems = items;
|
|
266
432
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
278
444
|
});
|
|
279
|
-
if (isCancel(
|
|
445
|
+
if (isCancel(platforms)) {
|
|
280
446
|
if (tempDir) await fs2.remove(tempDir);
|
|
281
|
-
|
|
282
|
-
|
|
447
|
+
if (isSingleShot) break;
|
|
448
|
+
currentType = void 0;
|
|
449
|
+
continue;
|
|
283
450
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
let installedCount = 0;
|
|
294
|
-
let skippedCount = 0;
|
|
295
|
-
for (const platform of selectedPlatforms) {
|
|
296
|
-
const targetBase = supportedPaths[platform];
|
|
297
|
-
if (!targetBase) continue;
|
|
298
|
-
for (const item of selectedItems) {
|
|
299
|
-
const message = `Installing ${pc.bold(item)} to ${pc.cyan(platform)}...`;
|
|
300
|
-
s.message(message);
|
|
301
|
-
try {
|
|
302
|
-
if (url && tempDir) {
|
|
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 });
|
|
303
460
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
}
|
|
309
506
|
} else {
|
|
310
|
-
|
|
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
|
+
}
|
|
311
513
|
}
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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;
|
|
318
535
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (platform === "gemini" && normalizedType === "workflow" && currentSourcePath.endsWith(".md")) {
|
|
325
|
-
const targetPath = path3.join(
|
|
326
|
-
targetBase,
|
|
327
|
-
item.replace(/\.md$/, ".toml")
|
|
536
|
+
const installed = await installItem(
|
|
537
|
+
targetItemName,
|
|
538
|
+
overwrite,
|
|
539
|
+
currentSourcePath,
|
|
540
|
+
targetBase
|
|
328
541
|
);
|
|
329
|
-
if (
|
|
330
|
-
skippedCount++;
|
|
331
|
-
} else {
|
|
332
|
-
const content = await fs2.readFile(currentSourcePath, "utf-8");
|
|
333
|
-
const tomlContent = convertToGeminiCommandTOML(content);
|
|
334
|
-
await fs2.ensureDir(targetBase);
|
|
335
|
-
await fs2.writeFile(targetPath, tomlContent);
|
|
542
|
+
if (installed) {
|
|
336
543
|
installedCount++;
|
|
544
|
+
} else {
|
|
545
|
+
skippedCount++;
|
|
337
546
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
targetItemName,
|
|
342
|
-
overwrite,
|
|
343
|
-
currentSourcePath,
|
|
344
|
-
targetBase
|
|
345
|
-
);
|
|
346
|
-
if (installed) {
|
|
347
|
-
installedCount++;
|
|
348
|
-
} else {
|
|
349
|
-
skippedCount++;
|
|
547
|
+
} catch (err) {
|
|
548
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
549
|
+
errors.push(`${item} -> ${platform}: ${errorMessage}`);
|
|
350
550
|
}
|
|
351
|
-
} catch (err) {
|
|
352
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
353
|
-
errors.push(`${item} -> ${platform}: ${errorMessage}`);
|
|
354
551
|
}
|
|
355
552
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
await fs2.remove(tempDir);
|
|
359
|
-
}
|
|
360
|
-
if (errors.length > 0) {
|
|
361
|
-
s.stop(
|
|
362
|
-
pc.yellow(
|
|
363
|
-
`Completed with errors. Installed: ${installedCount}, Skipped: ${skippedCount}, Errors: ${errors.length}`
|
|
364
|
-
)
|
|
365
|
-
);
|
|
366
|
-
console.error(pc.red("\nErrors encountered:"));
|
|
367
|
-
for (const e of errors) {
|
|
368
|
-
console.error(pc.red(`- ${e}`));
|
|
553
|
+
if (tempDir) {
|
|
554
|
+
await fs2.remove(tempDir);
|
|
369
555
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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;
|
|
376
578
|
}
|
|
579
|
+
if (isSingleShot) break;
|
|
580
|
+
currentType = void 0;
|
|
581
|
+
}
|
|
582
|
+
if (!options?.skipIntro) {
|
|
377
583
|
outro("You're all set!");
|
|
378
|
-
} catch (error) {
|
|
379
|
-
if (tempDir) await fs2.remove(tempDir);
|
|
380
|
-
cancel(`An error occurred: ${error}`);
|
|
381
|
-
process.exit(1);
|
|
382
584
|
}
|
|
383
585
|
}
|
|
384
586
|
|
|
385
587
|
// src/commands/import.ts
|
|
386
|
-
import
|
|
588
|
+
import path5 from "node:path";
|
|
387
589
|
import {
|
|
388
590
|
cancel as cancel2,
|
|
389
591
|
confirm as confirm2,
|
|
390
592
|
intro as intro2,
|
|
391
593
|
isCancel as isCancel2,
|
|
392
594
|
outro as outro2,
|
|
393
|
-
|
|
595
|
+
select as select2,
|
|
596
|
+
spinner as spinner2,
|
|
597
|
+
text
|
|
394
598
|
} from "@clack/prompts";
|
|
395
599
|
import fs3 from "fs-extra";
|
|
396
600
|
import pc2 from "picocolors";
|
|
397
|
-
async function importItem(type, url) {
|
|
398
|
-
|
|
399
|
-
|
|
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;
|
|
400
619
|
const targetBaseDir = TYPE_DIRS[normalizedType];
|
|
401
620
|
if (!targetBaseDir) {
|
|
402
|
-
cancel2(`Unknown type: ${
|
|
621
|
+
cancel2(`Unknown type: ${currentType}. Supported: skill, agent, workflow`);
|
|
403
622
|
process.exit(1);
|
|
623
|
+
return;
|
|
404
624
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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;
|
|
408
638
|
}
|
|
409
639
|
let tempDir = null;
|
|
410
640
|
try {
|
|
@@ -413,7 +643,7 @@ async function importItem(type, url) {
|
|
|
413
643
|
let itemName = "";
|
|
414
644
|
let isFile = false;
|
|
415
645
|
try {
|
|
416
|
-
const result = await fetchSkillFromGitHub(
|
|
646
|
+
const result = await fetchSkillFromGitHub(currentUrl);
|
|
417
647
|
tempDir = result.tempDir;
|
|
418
648
|
if (!tempDir) throw new Error("Failed to create temp directory");
|
|
419
649
|
itemName = result.skillName;
|
|
@@ -424,12 +654,12 @@ async function importItem(type, url) {
|
|
|
424
654
|
throw e;
|
|
425
655
|
}
|
|
426
656
|
const shouldFlatten = (normalizedType === "agent" || normalizedType === "workflow") && !isFile;
|
|
427
|
-
const targetPath = shouldFlatten ? targetBaseDir :
|
|
657
|
+
const targetPath = shouldFlatten ? targetBaseDir : path5.join(targetBaseDir, itemName);
|
|
428
658
|
if (shouldFlatten) {
|
|
429
659
|
const srcFiles = await fs3.readdir(tempDir);
|
|
430
660
|
const conflicts = [];
|
|
431
661
|
for (const file of srcFiles) {
|
|
432
|
-
if (await fs3.pathExists(
|
|
662
|
+
if (await fs3.pathExists(path5.join(targetBaseDir, file))) {
|
|
433
663
|
conflicts.push(file);
|
|
434
664
|
}
|
|
435
665
|
}
|
|
@@ -443,9 +673,8 @@ async function importItem(type, url) {
|
|
|
443
673
|
initialValue: false
|
|
444
674
|
});
|
|
445
675
|
if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
process.exit(0);
|
|
676
|
+
await fs3.remove(tempDir);
|
|
677
|
+
return;
|
|
449
678
|
}
|
|
450
679
|
}
|
|
451
680
|
} else if (await fs3.pathExists(targetPath)) {
|
|
@@ -454,15 +683,14 @@ async function importItem(type, url) {
|
|
|
454
683
|
initialValue: false
|
|
455
684
|
});
|
|
456
685
|
if (isCancel2(shouldOverwrite) || !shouldOverwrite) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
process.exit(0);
|
|
686
|
+
await fs3.remove(tempDir);
|
|
687
|
+
return;
|
|
460
688
|
}
|
|
461
689
|
}
|
|
462
690
|
s.start(`Importing ${itemName} to ${targetBaseDir}...`);
|
|
463
691
|
await fs3.ensureDir(targetBaseDir);
|
|
464
692
|
if (isFile) {
|
|
465
|
-
await fs3.copy(
|
|
693
|
+
await fs3.copy(path5.join(tempDir, itemName), targetPath, {
|
|
466
694
|
overwrite: true
|
|
467
695
|
});
|
|
468
696
|
} else {
|
|
@@ -473,222 +701,322 @@ async function importItem(type, url) {
|
|
|
473
701
|
}
|
|
474
702
|
}
|
|
475
703
|
s.stop(pc2.green(`Successfully imported ${itemName}!`));
|
|
476
|
-
|
|
477
|
-
|
|
704
|
+
await fs3.remove(tempDir);
|
|
705
|
+
if (!options?.skipIntro) {
|
|
706
|
+
outro2(`${normalizedType} available at: ${targetPath}`);
|
|
478
707
|
}
|
|
479
|
-
outro2(`${normalizedType} available at: ${targetPath}`);
|
|
480
708
|
} catch (error) {
|
|
481
709
|
if (tempDir) await fs3.remove(tempDir);
|
|
482
710
|
cancel2(`An error occurred: ${error}`);
|
|
483
711
|
process.exit(1);
|
|
712
|
+
return;
|
|
484
713
|
}
|
|
485
714
|
}
|
|
486
715
|
|
|
487
716
|
// src/commands/list.ts
|
|
717
|
+
import { intro as intro3, isCancel as isCancel3, outro as outro3, select as select3 } from "@clack/prompts";
|
|
488
718
|
import fs4 from "fs-extra";
|
|
489
719
|
import pc3 from "picocolors";
|
|
490
|
-
async function
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const entries = await fs4.readdir(fullPath, {
|
|
503
|
-
withFileTypes: true
|
|
504
|
-
});
|
|
505
|
-
const items = entries.filter(
|
|
506
|
-
(entry) => entry.isDirectory() || // For local items, agents/workflows might be directories OR .md files depending on how they were installed/flattened.
|
|
507
|
-
// Usually flattened -> directory (if unpacked) or file (if generic).
|
|
508
|
-
// Existing add logic:
|
|
509
|
-
// Agents/Workflows can be flattened (unpacked to base) or not.
|
|
510
|
-
// If flattened, they are files in the base dir? No, unpacked to base means they are files/dirs IN the base dir.
|
|
511
|
-
// Wait, if I import "agent-a" (dir) and flatten, it copies contents of agent-a to agents/.
|
|
512
|
-
// So "agents/skill.md" might exist.
|
|
513
|
-
// The user requirement says "list all installed agents/skills/workflows".
|
|
514
|
-
// In local global_skills (antigravity), they are directories.
|
|
515
|
-
// In local global_agents, they might be directories or .md files.
|
|
516
|
-
// Let's list directories and .md files.
|
|
517
|
-
entry.isFile() && entry.name.endsWith(".md")
|
|
518
|
-
).map((entry) => entry.name).sort();
|
|
519
|
-
if (items.length > 0) {
|
|
520
|
-
foundAny = true;
|
|
521
|
-
console.log(pc3.cyan(` ${platform}:`));
|
|
522
|
-
for (const item of items) {
|
|
523
|
-
console.log(` - ${item}`);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
if (!foundAny) {
|
|
529
|
-
console.log(pc3.dim(` No installed ${normalizedType}s found.`));
|
|
530
|
-
}
|
|
531
|
-
console.log("");
|
|
532
|
-
} else {
|
|
533
|
-
if (!TYPE_DIRS[normalizedType]) {
|
|
534
|
-
if (type) {
|
|
535
|
-
console.error(
|
|
536
|
-
pc3.red(
|
|
537
|
-
`Unknown type: ${type}. Supported types: skill, agent, workflow`
|
|
538
|
-
)
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
const dirPath = TYPE_DIRS[normalizedType];
|
|
544
|
-
if (!await fs4.pathExists(dirPath)) {
|
|
545
|
-
if (type) {
|
|
546
|
-
console.log(pc3.yellow(`No ${t} directory found at: ${dirPath}`));
|
|
547
|
-
}
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
const entries = await fs4.readdir(dirPath, { withFileTypes: true });
|
|
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
|
+
});
|
|
551
732
|
const items = entries.filter(
|
|
552
|
-
(entry) => entry.isDirectory() ||
|
|
733
|
+
(entry) => entry.isDirectory() || entry.isFile() && entry.name.endsWith(".md")
|
|
553
734
|
).map((entry) => entry.name).sort();
|
|
554
735
|
if (items.length > 0) {
|
|
555
|
-
|
|
736
|
+
foundAny = true;
|
|
737
|
+
console.log(pc3.cyan(` ${platform}:`));
|
|
556
738
|
for (const item of items) {
|
|
557
|
-
console.log(
|
|
739
|
+
console.log(` - ${item}`);
|
|
558
740
|
}
|
|
559
|
-
|
|
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
|
|
560
774
|
`));
|
|
561
|
-
|
|
562
|
-
|
|
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;
|
|
563
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);
|
|
564
815
|
}
|
|
816
|
+
if (isSingleShot) break;
|
|
817
|
+
currentType = void 0;
|
|
565
818
|
}
|
|
566
819
|
} catch (error) {
|
|
567
820
|
console.error(pc3.red("Error listing items:"), error);
|
|
568
821
|
}
|
|
822
|
+
if (!options?.skipIntro) {
|
|
823
|
+
outro3("Done!");
|
|
824
|
+
}
|
|
569
825
|
}
|
|
570
826
|
|
|
571
827
|
// src/commands/remove.ts
|
|
572
|
-
import
|
|
828
|
+
import path6 from "node:path";
|
|
573
829
|
import {
|
|
574
|
-
cancel as cancel3,
|
|
575
830
|
confirm as confirm3,
|
|
576
|
-
intro as
|
|
577
|
-
isCancel as
|
|
831
|
+
intro as intro4,
|
|
832
|
+
isCancel as isCancel4,
|
|
578
833
|
multiselect as multiselect2,
|
|
579
|
-
|
|
834
|
+
note as note2,
|
|
835
|
+
outro as outro4,
|
|
836
|
+
select as select4,
|
|
580
837
|
spinner as spinner3
|
|
581
838
|
} from "@clack/prompts";
|
|
582
839
|
import fs5 from "fs-extra";
|
|
583
840
|
import pc4 from "picocolors";
|
|
584
|
-
async function remove(type) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
+
]
|
|
595
857
|
});
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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
|
+
}
|
|
599
877
|
}
|
|
600
878
|
}
|
|
601
879
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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++;
|
|
659
944
|
}
|
|
660
|
-
} else {
|
|
661
|
-
notFoundCount++;
|
|
662
945
|
}
|
|
663
946
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
)
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
+
);
|
|
674
963
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
);
|
|
964
|
+
if (isSingleShot) break;
|
|
965
|
+
currentType = void 0;
|
|
966
|
+
}
|
|
967
|
+
if (!options?.skipIntro) {
|
|
968
|
+
outro4("Done!");
|
|
681
969
|
}
|
|
682
|
-
outro3("Done!");
|
|
683
970
|
}
|
|
684
971
|
|
|
685
972
|
// bin/cli.ts
|
|
686
973
|
var program = new Command();
|
|
687
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
|
+
}
|
|
688
1008
|
program.command("list [type]").description("List available items (skills, agents, workflows)").option("-l, --local", "List installed items locally").action(list);
|
|
689
|
-
program.command("add
|
|
1009
|
+
program.command("add [type] [url]").description(
|
|
690
1010
|
"Add an item (skill, agent, workflow) to platforms (Interactive or from GitHub URL)"
|
|
691
1011
|
).action(add);
|
|
692
|
-
program.command("import
|
|
693
|
-
|
|
694
|
-
|
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kurokeita/add-skill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "CLI to install AI agent skills to various platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"test": "vitest run",
|
|
19
19
|
"test:coverage": "vitest run --coverage",
|
|
20
20
|
"lint": "pnpm run lint:ts && pnpm run lint:md '**/*.md'",
|
|
21
|
-
"lint:ts": "biome check .",
|
|
21
|
+
"lint:ts": "biome check --error-on-warnings .",
|
|
22
22
|
"lint:md": "markdownlint --ignore node_modules '**/*.md'",
|
|
23
23
|
"format": "biome check --write . && pnpm run lint:md --fix",
|
|
24
24
|
"release": "semantic-release"
|
|
@@ -40,12 +40,14 @@
|
|
|
40
40
|
"@clack/prompts": "^0.11.0",
|
|
41
41
|
"commander": "^14.0.2",
|
|
42
42
|
"fs-extra": "^11.3.3",
|
|
43
|
+
"js-yaml": "^4.1.1",
|
|
43
44
|
"picocolors": "^1.1.1"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@biomejs/biome": "^2.3.11",
|
|
47
48
|
"@semantic-release/git": "^10.0.1",
|
|
48
49
|
"@types/fs-extra": "^11.0.4",
|
|
50
|
+
"@types/js-yaml": "^4.0.9",
|
|
49
51
|
"@types/node": "^25.0.9",
|
|
50
52
|
"@vitest/coverage-v8": "^4.0.18",
|
|
51
53
|
"esbuild": "^0.25.0",
|