@kurokeita/add-skill 1.4.0 → 1.6.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 (40) hide show
  1. package/README.md +56 -20
  2. package/dist/agents/backend-specialist.md +277 -0
  3. package/dist/agents/code-archaeologist.md +114 -0
  4. package/dist/agents/database-architect.md +238 -0
  5. package/dist/agents/debugger.md +231 -0
  6. package/dist/agents/devops-engineer.md +242 -0
  7. package/dist/agents/documentation-writer.md +104 -0
  8. package/dist/agents/explorer-agent.md +79 -0
  9. package/dist/agents/frontend-specialist.md +594 -0
  10. package/dist/agents/game-developer.md +162 -0
  11. package/dist/agents/mobile-developer.md +383 -0
  12. package/dist/agents/orchestrator.md +435 -0
  13. package/dist/agents/penetration-tester.md +188 -0
  14. package/dist/agents/performance-optimizer.md +191 -0
  15. package/dist/agents/product-manager.md +122 -0
  16. package/dist/agents/product-owner.md +106 -0
  17. package/dist/agents/project-planner.md +408 -0
  18. package/dist/agents/qa-automation-engineer.md +109 -0
  19. package/dist/agents/security-auditor.md +171 -0
  20. package/dist/agents/test-engineer.md +158 -0
  21. package/dist/bin/cli.js +29 -0
  22. package/dist/src/commands/add.js +115 -77
  23. package/dist/src/commands/import.js +73 -23
  24. package/dist/src/commands/list.js +84 -23
  25. package/dist/src/commands/remove.js +118 -0
  26. package/dist/src/utils/github.js +22 -11
  27. package/dist/src/utils/paths.js +45 -0
  28. package/dist/workflows/brainstorm.md +113 -0
  29. package/dist/workflows/create.md +60 -0
  30. package/dist/workflows/debug.md +107 -0
  31. package/dist/workflows/deploy.md +178 -0
  32. package/dist/workflows/enhance.md +64 -0
  33. package/dist/workflows/orchestrate.md +246 -0
  34. package/dist/workflows/plan.md +90 -0
  35. package/dist/workflows/preview.md +83 -0
  36. package/dist/workflows/status.md +87 -0
  37. package/dist/workflows/test.md +144 -0
  38. package/dist/workflows/ui-ux-pro-max.md +315 -0
  39. package/package.json +4 -4
  40. package/dist/bin/skills.js +0 -17
@@ -0,0 +1,158 @@
1
+ ---
2
+ name: test-engineer
3
+ description: Expert in testing, TDD, and test automation. Use for writing tests, improving coverage, debugging test failures. Triggers on test, spec, coverage, jest, pytest, playwright, e2e, unit test.
4
+ tools: Read, Grep, Glob, Bash, Edit, Write
5
+ model: inherit
6
+ skills: clean-code, testing-patterns, tdd-workflow, webapp-testing, code-review-checklist, lint-and-validate
7
+ ---
8
+
9
+ # Test Engineer
10
+
11
+ Expert in test automation, TDD, and comprehensive testing strategies.
12
+
13
+ ## Core Philosophy
14
+
15
+ > "Find what the developer forgot. Test behavior, not implementation."
16
+
17
+ ## Your Mindset
18
+
19
+ - **Proactive**: Discover untested paths
20
+ - **Systematic**: Follow testing pyramid
21
+ - **Behavior-focused**: Test what matters to users
22
+ - **Quality-driven**: Coverage is a guide, not a goal
23
+
24
+ ---
25
+
26
+ ## Testing Pyramid
27
+
28
+ ```
29
+ /\ E2E (Few)
30
+ / \ Critical user flows
31
+ /----\
32
+ / \ Integration (Some)
33
+ /--------\ API, DB, services
34
+ / \
35
+ /------------\ Unit (Many)
36
+ Functions, logic
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Framework Selection
42
+
43
+ | Language | Unit | Integration | E2E |
44
+ |----------|------|-------------|-----|
45
+ | TypeScript | Vitest, Jest | Supertest | Playwright |
46
+ | Python | Pytest | Pytest | Playwright |
47
+ | React | Testing Library | MSW | Playwright |
48
+
49
+ ---
50
+
51
+ ## TDD Workflow
52
+
53
+ ```
54
+ 🔴 RED → Write failing test
55
+ 🟢 GREEN → Minimal code to pass
56
+ 🔵 REFACTOR → Improve code quality
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Test Type Selection
62
+
63
+ | Scenario | Test Type |
64
+ |----------|-----------|
65
+ | Business logic | Unit |
66
+ | API endpoints | Integration |
67
+ | User flows | E2E |
68
+ | Components | Component/Unit |
69
+
70
+ ---
71
+
72
+ ## AAA Pattern
73
+
74
+ | Step | Purpose |
75
+ |------|---------|
76
+ | **Arrange** | Set up test data |
77
+ | **Act** | Execute code |
78
+ | **Assert** | Verify outcome |
79
+
80
+ ---
81
+
82
+ ## Coverage Strategy
83
+
84
+ | Area | Target |
85
+ |------|--------|
86
+ | Critical paths | 100% |
87
+ | Business logic | 80%+ |
88
+ | Utilities | 70%+ |
89
+ | UI layout | As needed |
90
+
91
+ ---
92
+
93
+ ## Deep Audit Approach
94
+
95
+ ### Discovery
96
+
97
+ | Target | Find |
98
+ |--------|------|
99
+ | Routes | Scan app directories |
100
+ | APIs | Grep HTTP methods |
101
+ | Components | Find UI files |
102
+
103
+ ### Systematic Testing
104
+
105
+ 1. Map all endpoints
106
+ 2. Verify responses
107
+ 3. Cover critical paths
108
+
109
+ ---
110
+
111
+ ## Mocking Principles
112
+
113
+ | Mock | Don't Mock |
114
+ |------|------------|
115
+ | External APIs | Code under test |
116
+ | Database (unit) | Simple deps |
117
+ | Network | Pure functions |
118
+
119
+ ---
120
+
121
+ ## Review Checklist
122
+
123
+ - [ ] Coverage 80%+ on critical paths
124
+ - [ ] AAA pattern followed
125
+ - [ ] Tests are isolated
126
+ - [ ] Descriptive naming
127
+ - [ ] Edge cases covered
128
+ - [ ] External deps mocked
129
+ - [ ] Cleanup after tests
130
+ - [ ] Fast unit tests (<100ms)
131
+
132
+ ---
133
+
134
+ ## Anti-Patterns
135
+
136
+ | ❌ Don't | ✅ Do |
137
+ |----------|-------|
138
+ | Test implementation | Test behavior |
139
+ | Multiple asserts | One per test |
140
+ | Dependent tests | Independent |
141
+ | Ignore flaky | Fix root cause |
142
+ | Skip cleanup | Always reset |
143
+
144
+ ---
145
+
146
+ ## When You Should Be Used
147
+
148
+ - Writing unit tests
149
+ - TDD implementation
150
+ - E2E test creation
151
+ - Improving coverage
152
+ - Debugging test failures
153
+ - Test infrastructure setup
154
+ - API integration tests
155
+
156
+ ---
157
+
158
+ > **Remember:** Good tests are documentation. They explain what the code should do.
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { add } from "../src/commands/add.js";
4
+ import { importItem } from "../src/commands/import.js";
5
+ import { list } from "../src/commands/list.js";
6
+ import { remove } from "../src/commands/remove.js";
7
+ const program = new Command();
8
+ program
9
+ .name("ai-agent")
10
+ .description("CLI to manage AI agents, skills, and workflows")
11
+ .version("1.4.0");
12
+ program
13
+ .command("list [type]")
14
+ .description("List available items (skills, agents, workflows)")
15
+ .option("-l, --local", "List installed items locally")
16
+ .action(list);
17
+ program
18
+ .command("add <type> [url]")
19
+ .description("Add an item (skill, agent, workflow) to platforms (Interactive or from GitHub URL)")
20
+ .action(add);
21
+ program
22
+ .command("import <type> <url>")
23
+ .description("Import an item from GitHub to the repo")
24
+ .action(importItem);
25
+ program
26
+ .command("remove <type>")
27
+ .description("Remove locally installed items (skills, agents, workflows)")
28
+ .action(remove);
29
+ program.parse();
@@ -1,39 +1,22 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
3
  import { cancel, confirm, intro, isCancel, multiselect, note, outro, spinner, } from "@clack/prompts";
5
4
  import fs from "fs-extra";
6
5
  import pc from "picocolors";
7
6
  import { fetchSkillFromGitHub } from "../utils/github.js";
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
- const PROJECT_ROOT = path.resolve(__dirname, "../..");
11
- const SKILLS_DIR = path.join(PROJECT_ROOT, "skills");
12
- const PLATFORM_PATHS = {
13
- copilot: path.join(os.homedir(), ".copilot/skills"),
14
- windsurf: path.join(os.homedir(), ".codeium/windsurf/skills"),
15
- antigravity: path.join(os.homedir(), ".gemini/antigravity/global_skills"),
16
- gemini: path.join(os.homedir(), ".gemini/skills"),
17
- };
18
- export const PLATFORM_OPTIONS = [
19
- {
20
- label: "Antigravity",
21
- value: "antigravity",
22
- hint: "~/.gemini/antigravity/global_skills",
23
- },
24
- { label: "Gemini CLI", value: "gemini", hint: "~/.gemini/skills" },
25
- { label: "GitHub Copilot", value: "copilot", hint: "~/.copilot/skills" },
26
- {
27
- label: "Windsurf",
28
- value: "windsurf",
29
- hint: "~/.codeium/windsurf/skills",
30
- },
31
- ];
32
- async function installSkill(skillName, platform, overwrite, sourcePath = path.join(SKILLS_DIR, skillName)) {
33
- const targetBaseDir = PLATFORM_PATHS[platform];
34
- const targetPath = path.join(targetBaseDir, skillName);
7
+ import { getTargetPaths, PLATFORM_LABELS, TYPE_DIRS, } from "../utils/paths.js";
8
+ function getPlatformOptions(type) {
9
+ const paths = getTargetPaths(type);
10
+ return Object.entries(paths).map(([platform, pathStr]) => ({
11
+ label: PLATFORM_LABELS[platform],
12
+ value: platform,
13
+ hint: pathStr.replace(os.homedir(), "~"),
14
+ }));
15
+ }
16
+ async function installItem(itemName, overwrite, sourcePath, targetBaseDir) {
17
+ const targetPath = path.join(targetBaseDir, itemName);
35
18
  if (!(await fs.pathExists(sourcePath))) {
36
- throw new Error(`Skill '${skillName}' not found at ${sourcePath}`);
19
+ throw new Error(`Item '${itemName}' not found at ${sourcePath}`);
37
20
  }
38
21
  if (!overwrite && (await fs.pathExists(targetPath))) {
39
22
  return false;
@@ -42,58 +25,76 @@ async function installSkill(skillName, platform, overwrite, sourcePath = path.jo
42
25
  await fs.copy(sourcePath, targetPath, { overwrite });
43
26
  return true;
44
27
  }
45
- export async function addSkill(url) {
46
- console.clear();
47
- intro(pc.bgCyan(pc.black(" AI Skills Manager ")));
28
+ export async function add(type, url) {
29
+ intro(pc.bgCyan(pc.black(` AI Manager : Add ${type} `)));
30
+ // Normalize type
31
+ const normalizedType = type.toLowerCase().endsWith("s")
32
+ ? type.slice(0, -1)
33
+ : type;
34
+ if (!TYPE_DIRS[normalizedType]) {
35
+ cancel(`Unknown type: ${type}. Supported: skill, agent, workflow`);
36
+ process.exit(1);
37
+ }
48
38
  let tempDir = null;
49
- let selectedSkills = [];
39
+ let selectedItems = [];
50
40
  try {
51
41
  // 1. Determine Source (Local vs GitHub)
52
42
  if (url) {
53
43
  const s = spinner();
54
- s.start("Fetching skill from GitHub...");
44
+ s.start(`Fetching ${normalizedType} from GitHub...`);
55
45
  try {
56
46
  const result = await fetchSkillFromGitHub(url);
57
47
  tempDir = result.tempDir;
58
- selectedSkills = [result.skillName];
59
- s.stop(pc.green(`Fetched skill: ${result.skillName}`));
48
+ selectedItems = [result.skillName];
49
+ s.stop(pc.green(`Fetched ${normalizedType}: ${result.skillName}`));
60
50
  }
61
51
  catch (e) {
62
- s.stop(pc.red("Failed to fetch skill"));
52
+ s.stop(pc.red("Failed to fetch"));
63
53
  throw e;
64
54
  }
65
55
  }
66
56
  else {
67
57
  // Local Selection Logic
68
- if (!(await fs.pathExists(SKILLS_DIR))) {
69
- cancel(pc.red("Skills directory not found!"));
58
+ const sourceDir = TYPE_DIRS[normalizedType];
59
+ if (!(await fs.pathExists(sourceDir))) {
60
+ cancel(`${normalizedType} directory not found!`);
70
61
  process.exit(1);
71
62
  }
72
- const entries = await fs.readdir(SKILLS_DIR, { withFileTypes: true });
73
- const availableSkills = entries
74
- .filter((entry) => entry.isDirectory())
63
+ const entries = await fs.readdir(sourceDir, { withFileTypes: true });
64
+ const availableItems = entries
65
+ .filter((entry) => entry.isDirectory() ||
66
+ ((normalizedType === "workflow" || normalizedType === "agent") &&
67
+ entry.isFile() &&
68
+ entry.name.endsWith(".md")))
75
69
  .map((entry) => ({ label: entry.name, value: entry.name }))
76
70
  .sort((a, b) => a.label.localeCompare(b.label));
77
- if (availableSkills.length === 0) {
78
- cancel("No skills found in the skills directory.");
71
+ if (availableItems.length === 0) {
72
+ cancel(`No ${normalizedType}s found in directory.`);
79
73
  process.exit(0);
80
74
  }
81
- // Select Skills
82
- const skills = await multiselect({
83
- message: "Select skills to install:",
84
- options: availableSkills,
75
+ // Select Items
76
+ const items = await multiselect({
77
+ message: `Select ${normalizedType}s to install:`,
78
+ options: availableItems,
85
79
  required: true,
86
80
  });
87
- if (isCancel(skills)) {
81
+ if (isCancel(items)) {
88
82
  cancel("Operation cancelled.");
89
83
  process.exit(0);
90
84
  }
91
- selectedSkills = skills;
85
+ selectedItems = items;
92
86
  }
93
87
  // 2. Select Platforms
88
+ // Filter platform options based on support for the type
89
+ const supportedPaths = getTargetPaths(normalizedType);
90
+ const currentPlatformOptions = getPlatformOptions(normalizedType);
91
+ if (currentPlatformOptions.length === 0) {
92
+ cancel(`No supported platforms found for type '${normalizedType}'.`);
93
+ process.exit(1);
94
+ }
94
95
  const platforms = await multiselect({
95
96
  message: "Select target platforms:",
96
- options: PLATFORM_OPTIONS,
97
+ options: currentPlatformOptions,
97
98
  required: true,
98
99
  });
99
100
  if (isCancel(platforms)) {
@@ -104,30 +105,33 @@ export async function addSkill(url) {
104
105
  process.exit(0);
105
106
  }
106
107
  const selectedPlatforms = platforms;
107
- // 3. Check for existing skills
108
- const existingSkills = [];
108
+ // 3. Check for existing items
109
+ const existingItems = [];
109
110
  for (const platform of selectedPlatforms) {
110
- for (const skill of selectedSkills) {
111
- const targetPath = path.join(PLATFORM_PATHS[platform], skill);
111
+ const targetBase = supportedPaths[platform];
112
+ if (!targetBase)
113
+ continue;
114
+ for (const item of selectedItems) {
115
+ const targetPath = path.join(targetBase, item);
112
116
  if (await fs.pathExists(targetPath)) {
113
- existingSkills.push({ skill, platform });
117
+ existingItems.push({ item, platform });
114
118
  }
115
119
  }
116
120
  }
117
121
  let overwrite = true;
118
- if (existingSkills.length > 0) {
122
+ if (existingItems.length > 0) {
119
123
  const limit = 5;
120
- const displayList = existingSkills
124
+ const displayList = existingItems
121
125
  .slice(0, limit)
122
- .map((e) => `${pc.bold(e.skill)} (${e.platform})`)
126
+ .map((e) => `${pc.bold(e.item)} (${e.platform})`)
123
127
  .join(", ");
124
- const remainder = existingSkills.length - limit;
128
+ const remainder = existingItems.length - limit;
125
129
  const message = remainder > 0
126
- ? `The following skills already exist: ${displayList}, and ${remainder} others.`
127
- : `The following skills already exist: ${displayList}.`;
130
+ ? `The following items already exist: ${displayList}, and ${remainder} others.`
131
+ : `The following items already exist: ${displayList}.`;
128
132
  note(message, "Attention");
129
133
  const shouldOverwrite = await confirm({
130
- message: "Do you want to overwrite existing skills?",
134
+ message: "Do you want to overwrite existing items?",
131
135
  initialValue: false,
132
136
  });
133
137
  if (isCancel(shouldOverwrite)) {
@@ -139,26 +143,60 @@ export async function addSkill(url) {
139
143
  overwrite = shouldOverwrite;
140
144
  }
141
145
  // 4. Confirmation Note
142
- note(`Installing ${selectedSkills.length} skills to ${selectedPlatforms.length} platforms...`, "Summary");
146
+ note(`Installing ${selectedItems.length} ${normalizedType}s to ${selectedPlatforms.length} platforms...`, "Summary");
143
147
  // 5. Installation Loop with Spinner
144
148
  const s = spinner();
145
- s.start("Installing skills...");
149
+ s.start("Installing...");
146
150
  const errors = [];
147
151
  let installedCount = 0;
148
152
  let skippedCount = 0;
149
153
  for (const platform of selectedPlatforms) {
150
- for (const skill of selectedSkills) {
151
- const message = `Installing ${pc.bold(skill)} to ${pc.cyan(platform)}...`;
154
+ const targetBase = supportedPaths[platform];
155
+ if (!targetBase)
156
+ continue;
157
+ for (const item of selectedItems) {
158
+ const message = `Installing ${pc.bold(item)} to ${pc.cyan(platform)}...`;
152
159
  s.message(message);
153
160
  try {
154
- // Use specific source path for each skill
155
- // If tempDir is set, it means we have a single skill fetched from GitHub
156
- // The sourceDir was set to the parent of tempDir, so joining sourceDir + skillName works
157
- // However, for local skills, sourceDir is SKILLS_DIR
158
- const currentSourcePath = url && tempDir
159
- ? tempDir // Special handling for single downloaded skill
160
- : path.join(SKILLS_DIR, skill);
161
- const installed = await installSkill(skill, platform, overwrite, currentSourcePath);
161
+ if (url && tempDir) {
162
+ // We already fetched it, check if it was a file
163
+ // Wait, fetchSkillFromGitHub returned isFile, but add command logic separated fetch from install loop.
164
+ // We need to store isFile state from fetch if using URL.
165
+ // However, selectedItems only stores strings.
166
+ // Let's assume if it is a URL, we re-verify or change logic slightly.
167
+ // Better: modify fetch logic above to store isFile.
168
+ }
169
+ // Re-implementing logic to be consistent with import.ts
170
+ // But wait, add command handles multiple items if LOCAL.
171
+ // If URL, it is single item `selectedItems = [result.skillName]`.
172
+ let currentSourcePath;
173
+ if (url && tempDir) {
174
+ // For URL fetch, we need to know if it was a file.
175
+ // We can check if tempDir/item exists as file.
176
+ const potentialFile = path.join(tempDir, item);
177
+ if ((await fs.pathExists(potentialFile)) &&
178
+ (await fs.stat(potentialFile)).isFile()) {
179
+ currentSourcePath = potentialFile;
180
+ }
181
+ else {
182
+ currentSourcePath = tempDir;
183
+ }
184
+ }
185
+ else {
186
+ // Local source
187
+ const src = path.join(TYPE_DIRS[normalizedType], item);
188
+ if ((await fs.stat(src)).isFile()) {
189
+ currentSourcePath = src;
190
+ }
191
+ else {
192
+ currentSourcePath = src; // direct path to dir
193
+ }
194
+ }
195
+ let targetItemName = item;
196
+ if (platform === "windsurf" && normalizedType === "agent") {
197
+ targetItemName = path.join(path.parse(item).name, "AGENTS.md");
198
+ }
199
+ const installed = await installItem(targetItemName, overwrite, currentSourcePath, targetBase);
162
200
  if (installed) {
163
201
  installedCount++;
164
202
  }
@@ -168,7 +206,7 @@ export async function addSkill(url) {
168
206
  }
169
207
  catch (err) {
170
208
  const errorMessage = err instanceof Error ? err.message : String(err);
171
- errors.push(`${skill} -> ${platform}: ${errorMessage}`);
209
+ errors.push(`${item} -> ${platform}: ${errorMessage}`);
172
210
  }
173
211
  }
174
212
  }
@@ -184,7 +222,7 @@ export async function addSkill(url) {
184
222
  }
185
223
  }
186
224
  else {
187
- s.stop(pc.green(`Successfully installed ${installedCount} skills. (${skippedCount} skipped)`));
225
+ s.stop(pc.green(`Successfully installed ${installedCount} ${normalizedType}s. (${skippedCount} skipped)`));
188
226
  }
189
227
  outro("You're all set!");
190
228
  }
@@ -1,42 +1,80 @@
1
1
  import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
2
  import { cancel, confirm, intro, isCancel, outro, spinner, } from "@clack/prompts";
4
3
  import fs from "fs-extra";
5
4
  import pc from "picocolors";
6
5
  import { fetchSkillFromGitHub } from "../utils/github.js";
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
- const PROJECT_ROOT = path.resolve(__dirname, "../..");
10
- const SKILLS_DIR = path.join(PROJECT_ROOT, "skills");
11
- export async function importSkill(url) {
12
- console.clear();
13
- intro(pc.bgCyan(pc.black(" AI Skills Manager : Import ")));
6
+ import { TYPE_DIRS } from "../utils/paths.js";
7
+ export async function importItem(type, url) {
8
+ intro(pc.bgCyan(pc.black(` AI Manager : Import ${type} `)));
9
+ // Normalize type
10
+ const normalizedType = type.toLowerCase().endsWith("s")
11
+ ? type.slice(0, -1)
12
+ : type;
13
+ const targetBaseDir = TYPE_DIRS[normalizedType];
14
+ if (!targetBaseDir) {
15
+ cancel(`Unknown type: ${type}. Supported: skill, agent, workflow`);
16
+ process.exit(1);
17
+ }
14
18
  if (!url) {
15
19
  cancel("GitHub URL is required.");
16
20
  process.exit(1);
17
21
  }
18
22
  let tempDir = null;
19
23
  try {
20
- // 1. Fetch Skill
24
+ // 1. Fetch Item (using fetchSkillFromGitHub as generic fetcher)
21
25
  const s = spinner();
22
- s.start("Fetching skill from GitHub...");
23
- let skillName = "";
26
+ s.start(`Fetching ${normalizedType} from GitHub...`);
27
+ let itemName = "";
28
+ let isFile = false;
24
29
  try {
25
30
  const result = await fetchSkillFromGitHub(url);
26
31
  tempDir = result.tempDir;
27
- skillName = result.skillName;
28
- s.stop(pc.green(`Fetched skill: ${skillName}`));
32
+ if (!tempDir)
33
+ throw new Error("Failed to create temp directory");
34
+ itemName = result.skillName; // reuse property or rename if possible. assuming generic repo structure
35
+ isFile = result.isFile;
36
+ s.stop(pc.green(`Fetched: ${itemName}`));
29
37
  }
30
38
  catch (e) {
31
- s.stop(pc.red("Failed to fetch skill"));
39
+ s.stop(pc.red("Failed to fetch"));
32
40
  throw e;
33
41
  }
34
42
  // 2. Determine Target
35
- const targetPath = path.join(SKILLS_DIR, skillName);
43
+ const shouldFlatten = (normalizedType === "agent" || normalizedType === "workflow") && !isFile;
44
+ const targetPath = shouldFlatten
45
+ ? targetBaseDir
46
+ : path.join(targetBaseDir, itemName);
36
47
  // 3. Check Existence & Confirm Overwrite
37
- if (await fs.pathExists(targetPath)) {
48
+ if (shouldFlatten) {
49
+ const srcFiles = await fs.readdir(tempDir);
50
+ const conflicts = [];
51
+ for (const file of srcFiles) {
52
+ if (await fs.pathExists(path.join(targetBaseDir, file))) {
53
+ conflicts.push(file);
54
+ }
55
+ }
56
+ if (conflicts.length > 0) {
57
+ const limit = 5;
58
+ const displayList = conflicts.slice(0, limit).join(", ");
59
+ const remainder = conflicts.length - limit;
60
+ const message = remainder > 0
61
+ ? `Files ${displayList} (and ${remainder} others) already exist in ${targetBaseDir}. Overwrite?`
62
+ : `Files ${displayList} already exist in ${targetBaseDir}. Overwrite?`;
63
+ const shouldOverwrite = await confirm({
64
+ message,
65
+ initialValue: false,
66
+ });
67
+ if (isCancel(shouldOverwrite) || !shouldOverwrite) {
68
+ if (tempDir)
69
+ await fs.remove(tempDir);
70
+ cancel("Operation cancelled.");
71
+ process.exit(0);
72
+ }
73
+ }
74
+ }
75
+ else if (await fs.pathExists(targetPath)) {
38
76
  const shouldOverwrite = await confirm({
39
- message: `Skill '${skillName}' already exists in the repo. Overwrite?`,
77
+ message: `${normalizedType} '${itemName}' already exists in the repo. Overwrite?`,
40
78
  initialValue: false,
41
79
  });
42
80
  if (isCancel(shouldOverwrite) || !shouldOverwrite) {
@@ -46,16 +84,28 @@ export async function importSkill(url) {
46
84
  process.exit(0);
47
85
  }
48
86
  }
49
- // 4. Move/Copy to Skills Directory
50
- s.start(`Importing ${skillName} to ${SKILLS_DIR}...`);
51
- await fs.ensureDir(SKILLS_DIR);
52
- await fs.copy(tempDir, targetPath, { overwrite: true });
53
- s.stop(pc.green(`Successfully imported ${skillName}!`));
87
+ // 4. Move/Copy to Target Directory
88
+ s.start(`Importing ${itemName} to ${targetBaseDir}...`);
89
+ await fs.ensureDir(targetBaseDir);
90
+ if (isFile) {
91
+ await fs.copy(path.join(tempDir, itemName), targetPath, {
92
+ overwrite: true,
93
+ });
94
+ }
95
+ else {
96
+ if (shouldFlatten) {
97
+ await fs.copy(tempDir, targetBaseDir, { overwrite: true });
98
+ }
99
+ else {
100
+ await fs.copy(tempDir, targetPath, { overwrite: true });
101
+ }
102
+ }
103
+ s.stop(pc.green(`Successfully imported ${itemName}!`));
54
104
  // Cleanup
55
105
  if (tempDir) {
56
106
  await fs.remove(tempDir);
57
107
  }
58
- outro(`Skill available at: ${targetPath}`);
108
+ outro(`${normalizedType} available at: ${targetPath}`);
59
109
  }
60
110
  catch (error) {
61
111
  if (tempDir)