@h1dr0n/skill-pool 0.1.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 (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +296 -0
  3. package/bin/cli.js +157 -0
  4. package/package.json +41 -0
  5. package/skills/api/agents/backend-specialist.md +69 -0
  6. package/skills/api/agents/database-optimizer.md +176 -0
  7. package/skills/api/manifest.yaml +20 -0
  8. package/skills/api/rules/auth-security.md +45 -0
  9. package/skills/api/skills/api-patterns/SKILL.md +81 -0
  10. package/skills/api/skills/api-patterns/api-style.md +42 -0
  11. package/skills/api/skills/api-patterns/auth.md +24 -0
  12. package/skills/api/skills/api-patterns/documentation.md +26 -0
  13. package/skills/api/skills/api-patterns/graphql.md +41 -0
  14. package/skills/api/skills/api-patterns/rate-limiting.md +31 -0
  15. package/skills/api/skills/api-patterns/response.md +37 -0
  16. package/skills/api/skills/api-patterns/rest.md +40 -0
  17. package/skills/api/skills/api-patterns/scripts/api_validator.py +211 -0
  18. package/skills/api/skills/api-patterns/security-testing.md +122 -0
  19. package/skills/api/skills/api-patterns/trpc.md +41 -0
  20. package/skills/api/skills/api-patterns/versioning.md +22 -0
  21. package/skills/api/skills/database-patterns.md +126 -0
  22. package/skills/api/skills/deployment-patterns.md +105 -0
  23. package/skills/api/skills/docker-patterns.md +135 -0
  24. package/skills/common/agents/code-reviewer.md +78 -0
  25. package/skills/common/agents/planner.md +80 -0
  26. package/skills/common/agents/security-reviewer.md +82 -0
  27. package/skills/common/agents/software-architect.md +81 -0
  28. package/skills/common/manifest.yaml +25 -0
  29. package/skills/common/rules/coding-style.md +39 -0
  30. package/skills/common/rules/git-workflow.md +33 -0
  31. package/skills/common/rules/security.md +25 -0
  32. package/skills/common/skills/architecture/SKILL.md +55 -0
  33. package/skills/common/skills/architecture/context-discovery.md +43 -0
  34. package/skills/common/skills/architecture/examples.md +94 -0
  35. package/skills/common/skills/architecture/pattern-selection.md +68 -0
  36. package/skills/common/skills/architecture/patterns-reference.md +50 -0
  37. package/skills/common/skills/architecture/trade-off-analysis.md +77 -0
  38. package/skills/common/skills/brainstorming/SKILL.md +163 -0
  39. package/skills/common/skills/brainstorming/dynamic-questioning.md +350 -0
  40. package/skills/common/skills/clean-code.md +99 -0
  41. package/skills/common/skills/code-review-checklist.md +86 -0
  42. package/skills/common/skills/plan-writing/SKILL.md +152 -0
  43. package/skills/common/skills/skill-feedback.md +94 -0
  44. package/skills/common/skills/tdd-workflow.md +130 -0
  45. package/skills/common/skills/verification-loop.md +112 -0
  46. package/skills/cpp/agents/cpp-build-resolver.md +90 -0
  47. package/skills/cpp/agents/cpp-reviewer.md +72 -0
  48. package/skills/cpp/manifest.yaml +15 -0
  49. package/skills/cpp/skills/cpp-coding-standards.md +722 -0
  50. package/skills/cpp/skills/cpp-testing.md +323 -0
  51. package/skills/devops/agents/devops-automator.md +376 -0
  52. package/skills/devops/agents/sre.md +90 -0
  53. package/skills/devops/manifest.yaml +20 -0
  54. package/skills/devops/skills/deployment-patterns.md +427 -0
  55. package/skills/devops/skills/deployment-procedures/SKILL.md +241 -0
  56. package/skills/devops/skills/docker-patterns.md +364 -0
  57. package/skills/devops/skills/e2e-testing.md +326 -0
  58. package/skills/devops/skills/github-ops.md +144 -0
  59. package/skills/django/manifest.yaml +16 -0
  60. package/skills/django/skills/django-patterns.md +734 -0
  61. package/skills/django/skills/django-security.md +593 -0
  62. package/skills/django/skills/django-tdd.md +729 -0
  63. package/skills/django/skills/django-verification.md +469 -0
  64. package/skills/dotnet/agents/csharp-reviewer.md +101 -0
  65. package/skills/dotnet/manifest.yaml +14 -0
  66. package/skills/dotnet/skills/csharp-testing.md +321 -0
  67. package/skills/dotnet/skills/dotnet-patterns.md +321 -0
  68. package/skills/go/agents/code-reviewer.md +76 -0
  69. package/skills/go/agents/go-build-resolver.md +94 -0
  70. package/skills/go/agents/go-reviewer.md +76 -0
  71. package/skills/go/manifest.yaml +17 -0
  72. package/skills/go/rules/go-style.md +55 -0
  73. package/skills/go/skills/golang-patterns.md +674 -0
  74. package/skills/go/skills/golang-testing.md +720 -0
  75. package/skills/java/agents/java-build-resolver.md +153 -0
  76. package/skills/java/agents/java-reviewer.md +92 -0
  77. package/skills/java/manifest.yaml +18 -0
  78. package/skills/java/skills/java-coding-standards.md +147 -0
  79. package/skills/java/skills/jpa-patterns.md +151 -0
  80. package/skills/java/skills/springboot-patterns.md +314 -0
  81. package/skills/java/skills/springboot-security.md +272 -0
  82. package/skills/kotlin/agents/kotlin-build-resolver.md +118 -0
  83. package/skills/kotlin/agents/kotlin-reviewer.md +159 -0
  84. package/skills/kotlin/manifest.yaml +17 -0
  85. package/skills/kotlin/skills/kotlin-coroutines-flows.md +284 -0
  86. package/skills/kotlin/skills/kotlin-patterns.md +711 -0
  87. package/skills/kotlin/skills/kotlin-testing.md +824 -0
  88. package/skills/laravel/manifest.yaml +15 -0
  89. package/skills/laravel/skills/laravel-patterns.md +409 -0
  90. package/skills/laravel/skills/laravel-security.md +279 -0
  91. package/skills/laravel/skills/laravel-tdd.md +277 -0
  92. package/skills/laravel/skills/laravel-verification.md +173 -0
  93. package/skills/mobile/agents/dart-build-resolver.md +201 -0
  94. package/skills/mobile/agents/flutter-reviewer.md +243 -0
  95. package/skills/mobile/manifest.yaml +19 -0
  96. package/skills/mobile/skills/android-clean-architecture.md +339 -0
  97. package/skills/mobile/skills/dart-flutter-patterns.md +563 -0
  98. package/skills/mobile/skills/swiftui-patterns.md +259 -0
  99. package/skills/nestjs/manifest.yaml +13 -0
  100. package/skills/nestjs/skills/nestjs-patterns.md +230 -0
  101. package/skills/perl/manifest.yaml +13 -0
  102. package/skills/perl/skills/perl-patterns.md +504 -0
  103. package/skills/perl/skills/perl-security.md +503 -0
  104. package/skills/perl/skills/perl-testing.md +475 -0
  105. package/skills/python/agents/python-reviewer.md +98 -0
  106. package/skills/python/manifest.yaml +18 -0
  107. package/skills/python/rules/python-style.md +69 -0
  108. package/skills/python/skills/python-patterns/SKILL.md +441 -0
  109. package/skills/python/skills/python-patterns.md +90 -0
  110. package/skills/python/skills/python-testing.md +81 -0
  111. package/skills/rust/agents/rust-build-resolver.md +148 -0
  112. package/skills/rust/agents/rust-reviewer.md +94 -0
  113. package/skills/rust/manifest.yaml +16 -0
  114. package/skills/rust/rules/rust-style.md +107 -0
  115. package/skills/rust/skills/rust-patterns.md +499 -0
  116. package/skills/rust/skills/rust-testing.md +500 -0
  117. package/skills/security/agents/accessibility-auditor.md +316 -0
  118. package/skills/security/agents/security-reviewer.md +108 -0
  119. package/skills/security/manifest.yaml +19 -0
  120. package/skills/security/skills/red-team-tactics/SKILL.md +199 -0
  121. package/skills/security/skills/security-bounty-hunter.md +99 -0
  122. package/skills/security/skills/security-review.md +495 -0
  123. package/skills/security/skills/security-scan.md +165 -0
  124. package/skills/security/skills/vulnerability-scanner/SKILL.md +276 -0
  125. package/skills/security/skills/vulnerability-scanner/checklists.md +121 -0
  126. package/skills/security/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
  127. package/skills/swift/manifest.yaml +16 -0
  128. package/skills/swift/skills/swift-actor-persistence.md +142 -0
  129. package/skills/swift/skills/swift-concurrency.md +216 -0
  130. package/skills/swift/skills/swift-protocol-di-testing.md +190 -0
  131. package/skills/swift/skills/swiftui-patterns.md +259 -0
  132. package/skills/unity/agents/game-designer.md +167 -0
  133. package/skills/unity/agents/unity-architect.md +52 -0
  134. package/skills/unity/agents/unity-editor-tool-developer.md +310 -0
  135. package/skills/unity/agents/unity-multiplayer-engineer.md +321 -0
  136. package/skills/unity/agents/unity-shader-graph-artist.md +269 -0
  137. package/skills/unity/manifest.yaml +21 -0
  138. package/skills/unity/rules/csharp-patterns.md +48 -0
  139. package/skills/unity/rules/unity-specific.md +53 -0
  140. package/skills/unity/skills/systematic-debugging.md +92 -0
  141. package/skills/unity/skills/unity-architecture.md +173 -0
  142. package/skills/unreal/agents/level-designer.md +208 -0
  143. package/skills/unreal/agents/technical-artist.md +229 -0
  144. package/skills/unreal/agents/unreal-multiplayer-architect.md +313 -0
  145. package/skills/unreal/agents/unreal-systems-engineer.md +310 -0
  146. package/skills/unreal/agents/unreal-technical-artist.md +256 -0
  147. package/skills/unreal/agents/unreal-world-builder.md +273 -0
  148. package/skills/unreal/manifest.yaml +21 -0
  149. package/skills/unreal/skills/unreal-patterns.md +183 -0
  150. package/skills/web/agents/frontend-specialist.md +71 -0
  151. package/skills/web/agents/ui-designer.md +383 -0
  152. package/skills/web/agents/ux-architect.md +469 -0
  153. package/skills/web/manifest.yaml +22 -0
  154. package/skills/web/rules/accessibility.md +54 -0
  155. package/skills/web/rules/css-performance.md +52 -0
  156. package/skills/web/skills/e2e-testing.md +132 -0
  157. package/skills/web/skills/frontend-design/SKILL.md +452 -0
  158. package/skills/web/skills/frontend-design/animation-guide.md +331 -0
  159. package/skills/web/skills/frontend-design/color-system.md +311 -0
  160. package/skills/web/skills/frontend-design/decision-trees.md +418 -0
  161. package/skills/web/skills/frontend-design/motion-graphics.md +306 -0
  162. package/skills/web/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  163. package/skills/web/skills/frontend-design/scripts/ux_audit.py +722 -0
  164. package/skills/web/skills/frontend-design/typography-system.md +345 -0
  165. package/skills/web/skills/frontend-design/ux-psychology.md +1116 -0
  166. package/skills/web/skills/frontend-design/visual-effects.md +383 -0
  167. package/skills/web/skills/react-nextjs.md +135 -0
  168. package/skills/web/skills/tailwind-patterns/SKILL.md +269 -0
  169. package/src/adapters/antigravity.js +164 -0
  170. package/src/adapters/claude.js +188 -0
  171. package/src/adapters/cursor.js +161 -0
  172. package/src/adapters/index.js +67 -0
  173. package/src/adapters/windsurf.js +158 -0
  174. package/src/commands/add.js +266 -0
  175. package/src/commands/create.js +127 -0
  176. package/src/commands/diff.js +78 -0
  177. package/src/commands/info.js +88 -0
  178. package/src/commands/init.js +224 -0
  179. package/src/commands/install.js +90 -0
  180. package/src/commands/list.js +54 -0
  181. package/src/commands/remove.js +101 -0
  182. package/src/commands/targets.js +32 -0
  183. package/src/commands/update.js +57 -0
  184. package/src/core/manifest.js +57 -0
  185. package/src/core/plugins.js +86 -0
  186. package/src/core/resolver.js +84 -0
  187. package/src/core/tracker.js +49 -0
  188. package/src/utils/fs.js +80 -0
  189. package/src/utils/git.js +52 -0
@@ -0,0 +1,161 @@
1
+ import path from 'node:path';
2
+ import {
3
+ ensureDir,
4
+ writeFileAtomic,
5
+ readFileContent,
6
+ removeFile,
7
+ fileExists,
8
+ listDir,
9
+ copyDir,
10
+ removeDir,
11
+ isDirectory,
12
+ } from '../utils/fs.js';
13
+ import { getSkillsDir } from '../core/manifest.js';
14
+
15
+ const MARKER_PREFIX = '<!-- skill-pool:';
16
+ const MARKER_SUFFIX = ' -->';
17
+ const MARKER_FILE = '.skill-pool-marker';
18
+
19
+ function makeMarker(packName, version) {
20
+ return `${MARKER_PREFIX}${packName}:${version}${MARKER_SUFFIX}`;
21
+ }
22
+
23
+ function parseMarker(content) {
24
+ const match = content.match(/<!-- skill-pool:([^:]+):([^ ]+) -->/);
25
+ if (!match) return null;
26
+ return { pack: match[1], version: match[2] };
27
+ }
28
+
29
+ function contentHasMarker(content, packName) {
30
+ const parsed = parseMarker(content);
31
+ return parsed && parsed.pack === packName;
32
+ }
33
+
34
+ function buildMdcContent(marker, packName, ruleContent) {
35
+ const frontmatter = [
36
+ '---',
37
+ `description: "skill-pool: ${packName} rules"`,
38
+ 'globs: ""',
39
+ 'alwaysApply: true',
40
+ '---',
41
+ ].join('\n');
42
+ return `${frontmatter}\n${marker}\n\n${ruleContent}`;
43
+ }
44
+
45
+ export async function detect(projectDir) {
46
+ return fileExists(path.join(projectDir, '.cursor'));
47
+ }
48
+
49
+ export async function install(manifest, projectDir) {
50
+ const installed = [];
51
+ const skillsDir = getSkillsDir();
52
+ const marker = makeMarker(manifest.name, manifest.version);
53
+
54
+ // Rules -> .cursor/rules/<pack>-<name>.mdc
55
+ for (const rulePath of manifest.rules) {
56
+ const srcPath = path.join(skillsDir, manifest.name, rulePath);
57
+ const content = await readFileContent(srcPath);
58
+ const basename = path.basename(rulePath, path.extname(rulePath));
59
+ const rulesDir = path.join(projectDir, '.cursor', 'rules');
60
+ await ensureDir(rulesDir);
61
+ const destPath = path.join(rulesDir, `${manifest.name}-${basename}.mdc`);
62
+ await writeFileAtomic(destPath, buildMdcContent(marker, manifest.name, content));
63
+ installed.push(destPath);
64
+ }
65
+
66
+ // Skills -> .cursor/skills/<pack>-<name>/
67
+ for (const skillPath of manifest.skills) {
68
+ const srcPath = path.join(skillsDir, manifest.name, skillPath);
69
+ const basename = path.basename(skillPath, path.extname(skillPath));
70
+ const destDir = path.join(projectDir, '.cursor', 'skills', `${manifest.name}-${basename}`);
71
+
72
+ if (await isDirectory(srcPath)) {
73
+ await copyDir(srcPath, destDir);
74
+ await writeFileAtomic(path.join(destDir, MARKER_FILE), marker);
75
+ installed.push(destDir);
76
+ } else {
77
+ await ensureDir(destDir);
78
+ const content = await readFileContent(srcPath);
79
+ await writeFileAtomic(path.join(destDir, 'SKILL.md'), `${marker}\n\n${content}`);
80
+ await writeFileAtomic(path.join(destDir, MARKER_FILE), marker);
81
+ installed.push(destDir);
82
+ }
83
+ }
84
+
85
+ // Agents -> .cursor/rules/<pack>-agent-<name>.mdc
86
+ for (const agentPath of manifest.agents) {
87
+ const srcPath = path.join(skillsDir, manifest.name, agentPath);
88
+ const content = await readFileContent(srcPath);
89
+ const basename = path.basename(agentPath, path.extname(agentPath));
90
+ const rulesDir = path.join(projectDir, '.cursor', 'rules');
91
+ await ensureDir(rulesDir);
92
+ const destPath = path.join(rulesDir, `${manifest.name}-agent-${basename}.mdc`);
93
+ await writeFileAtomic(destPath, buildMdcContent(marker, manifest.name, content));
94
+ installed.push(destPath);
95
+ }
96
+
97
+ return installed;
98
+ }
99
+
100
+ export async function remove(packName, projectDir) {
101
+ const removed = [];
102
+
103
+ // Remove rules + agent .mdc files
104
+ const rulesDir = path.join(projectDir, '.cursor', 'rules');
105
+ const ruleFiles = await listDir(rulesDir);
106
+ for (const file of ruleFiles) {
107
+ const filePath = path.join(rulesDir, file);
108
+ if (await isDirectory(filePath)) continue;
109
+ const content = await readFileContent(filePath);
110
+ if (contentHasMarker(content, packName)) {
111
+ await removeFile(filePath);
112
+ removed.push(filePath);
113
+ }
114
+ }
115
+
116
+ // Remove skills
117
+ const skillsBase = path.join(projectDir, '.cursor', 'skills');
118
+ const skillDirs = await listDir(skillsBase);
119
+ for (const dir of skillDirs) {
120
+ const markerPath = path.join(skillsBase, dir, MARKER_FILE);
121
+ if (await fileExists(markerPath)) {
122
+ const content = await readFileContent(markerPath);
123
+ if (contentHasMarker(content, packName)) {
124
+ await removeDir(path.join(skillsBase, dir));
125
+ removed.push(path.join(skillsBase, dir));
126
+ }
127
+ }
128
+ }
129
+
130
+ return removed;
131
+ }
132
+
133
+ export async function list(projectDir) {
134
+ const results = [];
135
+
136
+ const rulesDir = path.join(projectDir, '.cursor', 'rules');
137
+ const ruleFiles = await listDir(rulesDir);
138
+ for (const file of ruleFiles) {
139
+ const filePath = path.join(rulesDir, file);
140
+ if (await isDirectory(filePath)) continue;
141
+ const content = await readFileContent(filePath);
142
+ const parsed = parseMarker(content);
143
+ if (parsed) {
144
+ const type = file.includes('-agent-') ? 'agent' : 'rule';
145
+ results.push({ ...parsed, file, type });
146
+ }
147
+ }
148
+
149
+ const skillsBase = path.join(projectDir, '.cursor', 'skills');
150
+ const skillDirs = await listDir(skillsBase);
151
+ for (const dir of skillDirs) {
152
+ const markerPath = path.join(skillsBase, dir, MARKER_FILE);
153
+ if (await fileExists(markerPath)) {
154
+ const content = await readFileContent(markerPath);
155
+ const parsed = parseMarker(content);
156
+ if (parsed) results.push({ ...parsed, file: dir, type: 'skill' });
157
+ }
158
+ }
159
+
160
+ return results;
161
+ }
@@ -0,0 +1,67 @@
1
+ import * as claude from './claude.js';
2
+ import * as cursor from './cursor.js';
3
+ import * as windsurf from './windsurf.js';
4
+ import * as antigravity from './antigravity.js';
5
+ import { loadPlugins } from '../core/plugins.js';
6
+
7
+ const adapters = {
8
+ claude,
9
+ cursor,
10
+ windsurf,
11
+ antigravity,
12
+ };
13
+
14
+ export function getAdapter(name) {
15
+ const adapter = adapters[name];
16
+ if (!adapter) {
17
+ throw new Error(`Unknown target: "${name}". Available: ${Object.keys(adapters).join(', ')}`);
18
+ }
19
+ return adapter;
20
+ }
21
+
22
+ /**
23
+ * Load custom adapters from project config and merge into the registry.
24
+ * Custom adapters do not overwrite built-in adapters.
25
+ *
26
+ * @param {string} projectDir - absolute path to the project root
27
+ * @returns {Promise<Record<string, object>>} merged adapter map (built-in + custom)
28
+ */
29
+ export async function loadCustomAdapters(projectDir) {
30
+ const custom = await loadPlugins(projectDir);
31
+ return { ...custom, ...adapters };
32
+ }
33
+
34
+ /**
35
+ * Get an adapter by name, checking built-in adapters first, then plugins.
36
+ *
37
+ * @param {string} name - adapter name
38
+ * @param {string} projectDir - absolute path to the project root
39
+ * @returns {Promise<object>} the adapter module
40
+ */
41
+ export async function getAdapterWithPlugins(name, projectDir) {
42
+ if (adapters[name]) {
43
+ return adapters[name];
44
+ }
45
+
46
+ const custom = await loadPlugins(projectDir);
47
+ if (custom[name]) {
48
+ return custom[name];
49
+ }
50
+
51
+ const allNames = [...Object.keys(adapters), ...Object.keys(custom)];
52
+ throw new Error(`Unknown target: "${name}". Available: ${allNames.join(', ')}`);
53
+ }
54
+
55
+ export async function detectTargets(projectDir) {
56
+ const detected = [];
57
+ for (const [name, adapter] of Object.entries(adapters)) {
58
+ if (await adapter.detect(projectDir)) {
59
+ detected.push(name);
60
+ }
61
+ }
62
+ return detected;
63
+ }
64
+
65
+ export function getAllTargetNames() {
66
+ return Object.keys(adapters);
67
+ }
@@ -0,0 +1,158 @@
1
+ import path from 'node:path';
2
+ import {
3
+ ensureDir,
4
+ writeFileAtomic,
5
+ readFileContent,
6
+ removeFile,
7
+ fileExists,
8
+ listDir,
9
+ copyDir,
10
+ removeDir,
11
+ isDirectory,
12
+ } from '../utils/fs.js';
13
+ import { getSkillsDir } from '../core/manifest.js';
14
+
15
+ const MARKER_PREFIX = '<!-- skill-pool:';
16
+ const MARKER_SUFFIX = ' -->';
17
+ const MARKER_FILE = '.skill-pool-marker';
18
+
19
+ function makeMarker(packName, version) {
20
+ return `${MARKER_PREFIX}${packName}:${version}${MARKER_SUFFIX}`;
21
+ }
22
+
23
+ function parseMarker(line) {
24
+ if (!line.startsWith(MARKER_PREFIX)) return null;
25
+ const inner = line.slice(MARKER_PREFIX.length, line.indexOf(MARKER_SUFFIX));
26
+ const [pack, version] = inner.split(':');
27
+ return { pack, version };
28
+ }
29
+
30
+ function contentHasMarker(content, packName) {
31
+ const firstLine = content.split('\n')[0];
32
+ const parsed = parseMarker(firstLine);
33
+ return parsed && parsed.pack === packName;
34
+ }
35
+
36
+ export async function detect(projectDir) {
37
+ return fileExists(path.join(projectDir, '.windsurf'));
38
+ }
39
+
40
+ export async function install(manifest, projectDir) {
41
+ const installed = [];
42
+ const skillsDir = getSkillsDir();
43
+ const marker = makeMarker(manifest.name, manifest.version);
44
+
45
+ // Rules -> .windsurf/rules/<pack>-<name>.md
46
+ for (const rulePath of manifest.rules) {
47
+ const srcPath = path.join(skillsDir, manifest.name, rulePath);
48
+ const content = await readFileContent(srcPath);
49
+ const basename = path.basename(rulePath, path.extname(rulePath));
50
+ const rulesDir = path.join(projectDir, '.windsurf', 'rules');
51
+ await ensureDir(rulesDir);
52
+ const destPath = path.join(rulesDir, `${manifest.name}-${basename}.md`);
53
+ await writeFileAtomic(destPath, `${marker}\n\n${content}`);
54
+ installed.push(destPath);
55
+ }
56
+
57
+ // Skills -> .windsurf/skills/<pack>-<name>/
58
+ for (const skillPath of manifest.skills) {
59
+ const srcPath = path.join(skillsDir, manifest.name, skillPath);
60
+ const basename = path.basename(skillPath, path.extname(skillPath));
61
+ const destDir = path.join(projectDir, '.windsurf', 'skills', `${manifest.name}-${basename}`);
62
+
63
+ if (await isDirectory(srcPath)) {
64
+ await copyDir(srcPath, destDir);
65
+ await writeFileAtomic(path.join(destDir, MARKER_FILE), marker);
66
+ installed.push(destDir);
67
+ } else {
68
+ await ensureDir(destDir);
69
+ const content = await readFileContent(srcPath);
70
+ await writeFileAtomic(path.join(destDir, 'SKILL.md'), `${marker}\n\n${content}`);
71
+ await writeFileAtomic(path.join(destDir, MARKER_FILE), marker);
72
+ installed.push(destDir);
73
+ }
74
+ }
75
+
76
+ // Agents -> .windsurf/rules/<pack>-agent-<name>.md (Windsurf uses rules for agents)
77
+ for (const agentPath of manifest.agents) {
78
+ const srcPath = path.join(skillsDir, manifest.name, agentPath);
79
+ const content = await readFileContent(srcPath);
80
+ const basename = path.basename(agentPath, path.extname(agentPath));
81
+ const rulesDir = path.join(projectDir, '.windsurf', 'rules');
82
+ await ensureDir(rulesDir);
83
+ const destPath = path.join(rulesDir, `${manifest.name}-agent-${basename}.md`);
84
+ await writeFileAtomic(destPath, `${marker}\n\n${content}`);
85
+ installed.push(destPath);
86
+ }
87
+
88
+ return installed;
89
+ }
90
+
91
+ export async function remove(packName, projectDir) {
92
+ const removed = [];
93
+
94
+ // Remove rules + agent files
95
+ await removeMarkedFiles(path.join(projectDir, '.windsurf', 'rules'), packName, removed);
96
+
97
+ // Remove skills
98
+ const skillsBase = path.join(projectDir, '.windsurf', 'skills');
99
+ const skillDirs = await listDir(skillsBase);
100
+ for (const dir of skillDirs) {
101
+ const markerPath = path.join(skillsBase, dir, MARKER_FILE);
102
+ if (await fileExists(markerPath)) {
103
+ const content = await readFileContent(markerPath);
104
+ if (contentHasMarker(content, packName)) {
105
+ await removeDir(path.join(skillsBase, dir));
106
+ removed.push(path.join(skillsBase, dir));
107
+ }
108
+ }
109
+ }
110
+
111
+ return removed;
112
+ }
113
+
114
+ async function removeMarkedFiles(dirPath, packName, removed) {
115
+ const files = await listDir(dirPath);
116
+ for (const file of files) {
117
+ const filePath = path.join(dirPath, file);
118
+ if (await isDirectory(filePath)) continue;
119
+ const content = await readFileContent(filePath);
120
+ if (contentHasMarker(content, packName)) {
121
+ await removeFile(filePath);
122
+ removed.push(filePath);
123
+ }
124
+ }
125
+ }
126
+
127
+ export async function list(projectDir) {
128
+ const results = [];
129
+
130
+ // List rules + agents
131
+ const rulesDir = path.join(projectDir, '.windsurf', 'rules');
132
+ const ruleFiles = await listDir(rulesDir);
133
+ for (const file of ruleFiles) {
134
+ const filePath = path.join(rulesDir, file);
135
+ if (await isDirectory(filePath)) continue;
136
+ const content = await readFileContent(filePath);
137
+ const firstLine = content.split('\n')[0];
138
+ const parsed = parseMarker(firstLine);
139
+ if (parsed) {
140
+ const type = file.includes('-agent-') ? 'agent' : 'rule';
141
+ results.push({ ...parsed, file, type });
142
+ }
143
+ }
144
+
145
+ // List skills
146
+ const skillsBase = path.join(projectDir, '.windsurf', 'skills');
147
+ const skillDirs = await listDir(skillsBase);
148
+ for (const dir of skillDirs) {
149
+ const markerPath = path.join(skillsBase, dir, MARKER_FILE);
150
+ if (await fileExists(markerPath)) {
151
+ const content = await readFileContent(markerPath);
152
+ const parsed = parseMarker(content);
153
+ if (parsed) results.push({ ...parsed, file: dir, type: 'skill' });
154
+ }
155
+ }
156
+
157
+ return results;
158
+ }
@@ -0,0 +1,266 @@
1
+ import path from 'node:path';
2
+ import os from 'node:os';
3
+ import chalk from 'chalk';
4
+ import yaml from 'js-yaml';
5
+ import { cloneRepo, isGitAvailable } from '../utils/git.js';
6
+ import { fileExists, readFileContent, copyDir, removeDir, ensureDir } from '../utils/fs.js';
7
+ import {
8
+ loadTracker,
9
+ saveTracker,
10
+ getInstalledVersion,
11
+ recordInstall,
12
+ } from '../core/tracker.js';
13
+ import { getAdapter, detectTargets } from '../adapters/index.js';
14
+
15
+ /**
16
+ * Parse a GitHub URL into { url, branch, repoName }.
17
+ * Accepts:
18
+ * https://github.com/user/repo
19
+ * github.com/user/repo
20
+ * https://github.com/user/repo#branch
21
+ * https://github.com/user/repo.git
22
+ *
23
+ * @param {string} raw
24
+ * @returns {{ url: string, branch: string | null, repoName: string }}
25
+ */
26
+ function parseGithubUrl(raw) {
27
+ let input = raw.trim();
28
+
29
+ // Split off #branch fragment
30
+ let branch = null;
31
+ const hashIndex = input.indexOf('#');
32
+ if (hashIndex !== -1) {
33
+ branch = input.slice(hashIndex + 1);
34
+ input = input.slice(0, hashIndex);
35
+ }
36
+
37
+ // Normalise: add https:// if missing
38
+ if (!input.startsWith('http://') && !input.startsWith('https://')) {
39
+ input = `https://${input}`;
40
+ }
41
+
42
+ let parsed;
43
+ try {
44
+ parsed = new URL(input);
45
+ } catch {
46
+ throw new Error(`Invalid URL: "${raw}"`);
47
+ }
48
+
49
+ if (!parsed.hostname.includes('github.com')) {
50
+ throw new Error(
51
+ `Only GitHub URLs are supported. Got: "${parsed.hostname}"`
52
+ );
53
+ }
54
+
55
+ // pathname looks like /user/repo or /user/repo.git
56
+ const segments = parsed.pathname.replace(/\.git$/, '').split('/').filter(Boolean);
57
+
58
+ if (segments.length < 2) {
59
+ throw new Error(
60
+ `Cannot extract owner/repo from URL: "${raw}". Expected format: github.com/owner/repo`
61
+ );
62
+ }
63
+
64
+ const repoName = segments[1];
65
+
66
+ return { url: input, branch, repoName };
67
+ }
68
+
69
+ /**
70
+ * Locate manifest.yaml in a cloned directory.
71
+ * Checks root first, then skills/ subdirectory.
72
+ *
73
+ * @param {string} cloneDir
74
+ * @returns {Promise<{ manifestPath: string, packRoot: string } | null>}
75
+ */
76
+ async function findManifest(cloneDir) {
77
+ // Check root
78
+ const rootManifest = path.join(cloneDir, 'manifest.yaml');
79
+ if (await fileExists(rootManifest)) {
80
+ return { manifestPath: rootManifest, packRoot: cloneDir };
81
+ }
82
+
83
+ // Check skills/ subdirectory
84
+ const skillsManifest = path.join(cloneDir, 'skills', 'manifest.yaml');
85
+ if (await fileExists(skillsManifest)) {
86
+ return { manifestPath: skillsManifest, packRoot: path.join(cloneDir, 'skills') };
87
+ }
88
+
89
+ return null;
90
+ }
91
+
92
+ /**
93
+ * Load and validate a manifest from a file path.
94
+ * @param {string} manifestPath
95
+ * @returns {Promise<object>}
96
+ */
97
+ async function loadManifestFromPath(manifestPath) {
98
+ const content = await readFileContent(manifestPath);
99
+ const manifest = yaml.load(content);
100
+
101
+ if (!manifest || typeof manifest !== 'object') {
102
+ throw new Error('manifest.yaml is empty or invalid');
103
+ }
104
+
105
+ if (!manifest.name) {
106
+ throw new Error('manifest.yaml is missing required field: name');
107
+ }
108
+
109
+ const hasContent =
110
+ (manifest.rules && manifest.rules.length > 0) ||
111
+ (manifest.skills && manifest.skills.length > 0) ||
112
+ (manifest.agents && manifest.agents.length > 0);
113
+
114
+ if (!hasContent) {
115
+ throw new Error('manifest.yaml must have at least one of: rules, skills, agents');
116
+ }
117
+
118
+ return {
119
+ name: manifest.name,
120
+ version: manifest.version || '0.0.0',
121
+ description: manifest.description || '',
122
+ depends: manifest.depends || [],
123
+ tags: manifest.tags || [],
124
+ rules: manifest.rules || [],
125
+ skills: manifest.skills || [],
126
+ agents: manifest.agents || [],
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Resolve target adapters (same logic as install command).
132
+ * @param {string} projectDir
133
+ * @param {string | undefined} targetOption
134
+ * @returns {Promise<string[]>}
135
+ */
136
+ async function resolveTargets(projectDir, targetOption) {
137
+ if (targetOption) {
138
+ const names = targetOption.split(',').map((t) => t.trim());
139
+ names.forEach((n) => getAdapter(n));
140
+ return names;
141
+ }
142
+
143
+ const detected = await detectTargets(projectDir);
144
+ if (detected.length > 0) return detected;
145
+
146
+ return ['claude'];
147
+ }
148
+
149
+ /**
150
+ * Install a skill pack from a GitHub repository URL.
151
+ *
152
+ * @param {string} rawUrl - GitHub repository URL, optionally with #branch
153
+ * @param {object} [options]
154
+ * @param {string} [options.target] - Comma-separated target names
155
+ */
156
+ export async function addCommand(rawUrl, options = {}) {
157
+ // 1. Verify git is available
158
+ if (!(await isGitAvailable())) {
159
+ throw new Error(
160
+ 'git is not installed or not in PATH. Install git and try again.'
161
+ );
162
+ }
163
+
164
+ // 2. Parse URL
165
+ const { url, branch, repoName } = parseGithubUrl(rawUrl);
166
+
167
+ console.log(
168
+ chalk.gray(`Repository: ${url}${branch ? ` (branch: ${branch})` : ''}`)
169
+ );
170
+
171
+ // 3. Clone into temp directory
172
+ const tmpDir = path.join(
173
+ os.tmpdir(),
174
+ `sp-clone-${repoName}-${Date.now()}`
175
+ );
176
+
177
+ console.log(chalk.gray('Cloning repository...'));
178
+
179
+ try {
180
+ await cloneRepo(url, tmpDir, branch);
181
+ } catch (err) {
182
+ throw new Error(
183
+ `Failed to clone repository: ${err.message}`
184
+ );
185
+ }
186
+
187
+ try {
188
+ // 4. Find manifest.yaml
189
+ const found = await findManifest(tmpDir);
190
+
191
+ if (!found) {
192
+ throw new Error(
193
+ `No manifest.yaml found in repository root or skills/ subdirectory`
194
+ );
195
+ }
196
+
197
+ const { manifestPath, packRoot } = found;
198
+
199
+ // 5. Load and validate manifest
200
+ const manifest = await loadManifestFromPath(manifestPath);
201
+
202
+ console.log(
203
+ chalk.gray(`Found pack: ${manifest.name}@${manifest.version}`)
204
+ );
205
+
206
+ // 6. Copy pack to local cache
207
+ const projectDir = process.cwd();
208
+ const cacheDir = path.join(projectDir, '.skill-pool-cache', '_external', repoName);
209
+
210
+ await ensureDir(path.dirname(cacheDir));
211
+ // Remove existing cache for this pack if present
212
+ await removeDir(cacheDir);
213
+ await copyDir(packRoot, cacheDir);
214
+
215
+ console.log(chalk.gray(`Cached to .skill-pool-cache/_external/${repoName}/`));
216
+
217
+ // 7. Resolve targets
218
+ const targets = await resolveTargets(projectDir, options.target);
219
+ console.log(chalk.gray(`Targets: ${targets.join(', ')}\n`));
220
+
221
+ // 8. Check if already installed at same version
222
+ let tracker = await loadTracker(projectDir);
223
+ const currentVersion = getInstalledVersion(tracker, manifest.name);
224
+
225
+ if (currentVersion === manifest.version) {
226
+ console.log(
227
+ chalk.yellow(`${manifest.name}@${manifest.version} already installed, skipping`)
228
+ );
229
+ return;
230
+ }
231
+
232
+ // 9. Run install via adapters
233
+ let totalFiles = 0;
234
+ for (const targetName of targets) {
235
+ const adapter = getAdapter(targetName);
236
+ const files = await adapter.install(manifest, projectDir);
237
+ totalFiles += files.length;
238
+ }
239
+
240
+ // 10. Update tracker with github source
241
+ tracker = recordInstall(tracker, manifest.name, manifest.version, targets);
242
+
243
+ // Add source metadata
244
+ tracker = {
245
+ ...tracker,
246
+ installed: {
247
+ ...tracker.installed,
248
+ [manifest.name]: {
249
+ ...tracker.installed[manifest.name],
250
+ source: `github:${rawUrl}`,
251
+ },
252
+ },
253
+ };
254
+
255
+ await saveTracker(projectDir, tracker);
256
+
257
+ console.log(
258
+ chalk.green(` + ${manifest.name}@${manifest.version}`) +
259
+ chalk.gray(` (${totalFiles} rules, source: github)`)
260
+ );
261
+ console.log(chalk.green(`\nInstalled 1 pack for ${targets.join(', ')}.`));
262
+ } finally {
263
+ // 11. Cleanup temp directory
264
+ await removeDir(tmpDir);
265
+ }
266
+ }