@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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/bin/cli.js +688 -360
  3. 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 | `~/.gemini/antigravity/global_agents` | `~/.gemini/antigravity/global_skills` | `~/.gemini/antigravity/global_workflows` |
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 | `~/.codeium/windsurf/agents` | `~/.codeium/windsurf/skills` | `~/.codeium/windsurf/global_workflows` |
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 path3 from "node:path";
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 = path3.join(targetBaseDir, itemName);
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
- intro(pc.bgCyan(pc.black(` AI Manager : Add ${type} `)));
194
- const normalizedType = type.toLowerCase().endsWith("s") ? type.slice(0, -1) : type;
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 tempDir = null;
200
- let selectedItems = [];
201
- try {
202
- if (url) {
203
- const s2 = spinner();
204
- s2.start(`Fetching ${normalizedType} from GitHub...`);
205
- try {
206
- const result = await fetchSkillFromGitHub(url);
207
- tempDir = result.tempDir;
208
- selectedItems = [result.skillName];
209
- s2.stop(pc.green(`Fetched ${normalizedType}: ${result.skillName}`));
210
- } catch (e) {
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(items)) {
234
- cancel("Operation cancelled.");
235
- process.exit(0);
378
+ if (isCancel(selectedType) || selectedType === "exit") {
379
+ break;
236
380
  }
237
- selectedItems = items;
381
+ currentType = selectedType;
238
382
  }
239
- const supportedPaths = getTargetPaths(normalizedType);
240
- const currentPlatformOptions = getPlatformOptions(normalizedType);
241
- if (currentPlatformOptions.length === 0) {
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
- const platforms = await multiselect({
246
- message: "Select target platforms:",
247
- options: currentPlatformOptions,
248
- required: true
249
- });
250
- if (isCancel(platforms)) {
251
- console.log("Cleaning up...");
252
- if (tempDir) await fs2.remove(tempDir);
253
- cancel("Operation cancelled.");
254
- process.exit(0);
255
- }
256
- const selectedPlatforms = platforms;
257
- const existingItems = [];
258
- for (const platform of selectedPlatforms) {
259
- const targetBase = supportedPaths[platform];
260
- if (!targetBase) continue;
261
- for (const item of selectedItems) {
262
- const targetPath = path3.join(targetBase, item);
263
- if (await fs2.pathExists(targetPath)) {
264
- existingItems.push({ item, platform });
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
- let overwrite = true;
269
- if (existingItems.length > 0) {
270
- const limit = 5;
271
- const displayList = existingItems.slice(0, limit).map((e) => `${pc.bold(e.item)} (${e.platform})`).join(", ");
272
- const remainder = existingItems.length - limit;
273
- const message = remainder > 0 ? `The following items already exist: ${displayList}, and ${remainder} others.` : `The following items already exist: ${displayList}.`;
274
- note(message, "Attention");
275
- const shouldOverwrite = await confirm({
276
- message: "Do you want to overwrite existing items?",
277
- initialValue: false
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(shouldOverwrite)) {
445
+ if (isCancel(platforms)) {
280
446
  if (tempDir) await fs2.remove(tempDir);
281
- cancel("Operation cancelled.");
282
- process.exit(0);
447
+ if (isSingleShot) break;
448
+ currentType = void 0;
449
+ continue;
283
450
  }
284
- overwrite = shouldOverwrite;
285
- }
286
- note(
287
- `Installing ${selectedItems.length} ${normalizedType}s to ${selectedPlatforms.length} platforms...`,
288
- "Summary"
289
- );
290
- const s = spinner();
291
- s.start("Installing...");
292
- const errors = [];
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
- let currentSourcePath;
305
- if (url && tempDir) {
306
- const potentialFile = path3.join(tempDir, item);
307
- if (await fs2.pathExists(potentialFile) && (await fs2.stat(potentialFile)).isFile()) {
308
- currentSourcePath = potentialFile;
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
- currentSourcePath = tempDir;
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
- } else {
313
- const src = path3.join(TYPE_DIRS[normalizedType], item);
314
- if ((await fs2.stat(src)).isFile()) {
315
- currentSourcePath = src;
316
- } else {
317
- currentSourcePath = src;
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
- let targetItemName = item;
321
- if (platform === "windsurf" && normalizedType === "agent") {
322
- targetItemName = path3.join(path3.parse(item).name, "AGENTS.md");
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 (!overwrite && await fs2.pathExists(targetPath)) {
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
- continue;
339
- }
340
- const installed = await installItem(
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
- if (tempDir) {
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
- } else {
371
- s.stop(
372
- pc.green(
373
- `Successfully installed ${installedCount} ${normalizedType}s. (${skippedCount} skipped)`
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 path4 from "node:path";
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
- spinner as spinner2
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
- intro2(pc2.bgCyan(pc2.black(` AI Manager : Import ${type} `)));
399
- const normalizedType = type.toLowerCase().endsWith("s") ? type.slice(0, -1) : type;
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: ${type}. Supported: skill, agent, workflow`);
621
+ cancel2(`Unknown type: ${currentType}. Supported: skill, agent, workflow`);
403
622
  process.exit(1);
623
+ return;
404
624
  }
405
- if (!url) {
406
- cancel2("GitHub URL is required.");
407
- process.exit(1);
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(url);
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 : path4.join(targetBaseDir, itemName);
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(path4.join(targetBaseDir, file))) {
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
- if (tempDir) await fs3.remove(tempDir);
447
- cancel2("Operation cancelled.");
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
- if (tempDir) await fs3.remove(tempDir);
458
- cancel2("Operation cancelled.");
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(path4.join(tempDir, itemName), targetPath, {
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
- if (tempDir) {
477
- await fs3.remove(tempDir);
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 list(type, options) {
491
- try {
492
- const typesToList = type ? [type.toLowerCase()] : ["skill", "agent", "workflow"];
493
- for (const t of typesToList) {
494
- const normalizedType = t.endsWith("s") ? t.slice(0, -1) : t;
495
- if (options?.local) {
496
- console.log(pc3.bold(pc3.blue(`Installed ${normalizedType}s (Local):`)));
497
- const targetPaths = getTargetPaths(normalizedType);
498
- let foundAny = false;
499
- for (const [platform, pathStr] of Object.entries(targetPaths)) {
500
- const fullPath = pathStr;
501
- if (await fs4.pathExists(fullPath)) {
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() || normalizedType !== "skill" && entry.isFile() && entry.name.endsWith(".md")
733
+ (entry) => entry.isDirectory() || entry.isFile() && entry.name.endsWith(".md")
553
734
  ).map((entry) => entry.name).sort();
554
735
  if (items.length > 0) {
555
- console.log(pc3.bold(pc3.blue(`Available ${normalizedType}s:`)));
736
+ foundAny = true;
737
+ console.log(pc3.cyan(` ${platform}:`));
556
738
  for (const item of items) {
557
- console.log(`- ${item}`);
739
+ console.log(` - ${item}`);
558
740
  }
559
- console.log(pc3.dim(`Total: ${items.length} ${normalizedType}s
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
- } else if (type) {
562
- console.log(pc3.dim(`No ${normalizedType}s found.`));
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 path5 from "node:path";
828
+ import path6 from "node:path";
573
829
  import {
574
- cancel as cancel3,
575
830
  confirm as confirm3,
576
- intro as intro3,
577
- isCancel as isCancel3,
831
+ intro as intro4,
832
+ isCancel as isCancel4,
578
833
  multiselect as multiselect2,
579
- outro as outro3,
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
- intro3(pc4.bgCyan(pc4.black(` AI Manager : Remove ${type} `)));
586
- const normalizedType = type.toLowerCase().endsWith("s") ? type.slice(0, -1) : type;
587
- const targetPaths = getTargetPaths(normalizedType);
588
- const uniqueItems = /* @__PURE__ */ new Set();
589
- const s = spinner3();
590
- s.start("Scanning for installed items...");
591
- for (const pathStr of Object.values(targetPaths)) {
592
- if (await fs5.pathExists(pathStr)) {
593
- const entries = await fs5.readdir(pathStr, {
594
- withFileTypes: true
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
- for (const entry of entries) {
597
- if (entry.isDirectory() || normalizedType === "workflow" && entry.isFile() && entry.name.endsWith(".md") || normalizedType === "agent" && entry.isFile() && entry.name.endsWith(".md")) {
598
- uniqueItems.add(entry.name);
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
- s.stop(`Found ${uniqueItems.size} unique installed ${normalizedType}s.`);
604
- if (uniqueItems.size === 0) {
605
- cancel3(`No installed ${normalizedType}s found.`);
606
- process.exit(0);
607
- }
608
- const sortedItems = Array.from(uniqueItems).sort();
609
- const itemSelections = await multiselect2({
610
- message: `Select ${normalizedType}s to remove:`,
611
- options: sortedItems.map((item) => ({ value: item, label: item })),
612
- required: true
613
- });
614
- if (isCancel3(itemSelections)) {
615
- cancel3("Operation cancelled.");
616
- process.exit(0);
617
- }
618
- const selectedItems = itemSelections;
619
- const platformOptions = Object.entries(targetPaths).map(([platform]) => ({
620
- label: PLATFORM_LABELS[platform],
621
- value: platform
622
- }));
623
- const platformSelections = await multiselect2({
624
- message: "Select platforms to remove from:",
625
- options: platformOptions,
626
- required: true
627
- });
628
- if (isCancel3(platformSelections)) {
629
- cancel3("Operation cancelled.");
630
- process.exit(0);
631
- }
632
- const selectedPlatforms = platformSelections;
633
- const confirmDelete = await confirm3({
634
- message: `Are you sure you want to remove ${selectedItems.length} item(s) from ${selectedPlatforms.length} platform(s)?`,
635
- initialValue: false
636
- });
637
- if (isCancel3(confirmDelete) || !confirmDelete) {
638
- cancel3("Operation cancelled.");
639
- process.exit(0);
640
- }
641
- const sDel = spinner3();
642
- sDel.start("Removing items...");
643
- let removedCount = 0;
644
- let notFoundCount = 0;
645
- const errors = [];
646
- for (const platform of selectedPlatforms) {
647
- const targetBase = targetPaths[platform];
648
- if (!targetBase) continue;
649
- for (const item of selectedItems) {
650
- const targetPath = path5.join(targetBase, item);
651
- if (await fs5.pathExists(targetPath)) {
652
- sDel.message(`Removing ${pc4.bold(item)} from ${pc4.cyan(platform)}...`);
653
- try {
654
- await fs5.remove(targetPath);
655
- removedCount++;
656
- } catch (err) {
657
- const msg = err instanceof Error ? err.message : String(err);
658
- errors.push(`${item} (${platform}): ${msg}`);
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
- if (errors.length > 0) {
666
- sDel.stop(
667
- pc4.yellow(
668
- `Completed with errors. Removed: ${removedCount}, Not Found: ${notFoundCount}, Errors: ${errors.length}`
669
- )
670
- );
671
- console.error(pc4.red("\nErrors encountered:"));
672
- for (const e of errors) {
673
- console.error(pc4.red(`- ${e}`));
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
- } else {
676
- sDel.stop(
677
- pc4.green(
678
- `Successfully removed ${removedCount} items. (${notFoundCount} not found)`
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 <type> [url]").description(
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 <type> <url>").description("Import an item from GitHub to the repo").action(importItem);
693
- program.command("remove <type>").description("Remove locally installed items (skills, agents, workflows)").action(remove);
694
- program.parse();
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.8.0",
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",