craftdesk 0.3.0 → 0.5.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 (215) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +1609 -132
  3. package/dist/commands/add.d.ts +5 -0
  4. package/dist/commands/add.d.ts.map +1 -1
  5. package/dist/commands/add.js +306 -8
  6. package/dist/commands/add.js.map +1 -1
  7. package/dist/commands/convert.d.ts +8 -0
  8. package/dist/commands/convert.d.ts.map +1 -0
  9. package/dist/commands/convert.js +146 -0
  10. package/dist/commands/convert.js.map +1 -0
  11. package/dist/commands/detect-agents.d.ts +14 -0
  12. package/dist/commands/detect-agents.d.ts.map +1 -0
  13. package/dist/commands/detect-agents.js +86 -0
  14. package/dist/commands/detect-agents.js.map +1 -0
  15. package/dist/commands/embed.d.ts +14 -0
  16. package/dist/commands/embed.d.ts.map +1 -0
  17. package/dist/commands/embed.js +147 -0
  18. package/dist/commands/embed.js.map +1 -0
  19. package/dist/commands/info.d.ts +8 -0
  20. package/dist/commands/info.d.ts.map +1 -0
  21. package/dist/commands/info.js +66 -0
  22. package/dist/commands/info.js.map +1 -0
  23. package/dist/commands/init.d.ts +5 -0
  24. package/dist/commands/init.d.ts.map +1 -1
  25. package/dist/commands/init.js +8 -2
  26. package/dist/commands/init.js.map +1 -1
  27. package/dist/commands/install.d.ts +5 -0
  28. package/dist/commands/install.d.ts.map +1 -1
  29. package/dist/commands/install.js +24 -4
  30. package/dist/commands/install.js.map +1 -1
  31. package/dist/commands/list.d.ts +5 -0
  32. package/dist/commands/list.d.ts.map +1 -1
  33. package/dist/commands/list.js +75 -7
  34. package/dist/commands/list.js.map +1 -1
  35. package/dist/commands/login.d.ts +8 -0
  36. package/dist/commands/login.d.ts.map +1 -0
  37. package/dist/commands/login.js +158 -0
  38. package/dist/commands/login.js.map +1 -0
  39. package/dist/commands/logout.d.ts +8 -0
  40. package/dist/commands/logout.d.ts.map +1 -0
  41. package/dist/commands/logout.js +51 -0
  42. package/dist/commands/logout.js.map +1 -0
  43. package/dist/commands/outdated.d.ts +23 -0
  44. package/dist/commands/outdated.d.ts.map +1 -0
  45. package/dist/commands/outdated.js +268 -0
  46. package/dist/commands/outdated.js.map +1 -0
  47. package/dist/commands/publish.d.ts +8 -0
  48. package/dist/commands/publish.d.ts.map +1 -0
  49. package/dist/commands/publish.js +203 -0
  50. package/dist/commands/publish.js.map +1 -0
  51. package/dist/commands/remove.d.ts +5 -0
  52. package/dist/commands/remove.d.ts.map +1 -1
  53. package/dist/commands/remove.js +51 -10
  54. package/dist/commands/remove.js.map +1 -1
  55. package/dist/commands/search.d.ts +8 -0
  56. package/dist/commands/search.d.ts.map +1 -0
  57. package/dist/commands/search.js +60 -0
  58. package/dist/commands/search.js.map +1 -0
  59. package/dist/commands/setup-multi-agent.d.ts +17 -0
  60. package/dist/commands/setup-multi-agent.d.ts.map +1 -0
  61. package/dist/commands/setup-multi-agent.js +188 -0
  62. package/dist/commands/setup-multi-agent.js.map +1 -0
  63. package/dist/commands/sync.d.ts +14 -0
  64. package/dist/commands/sync.d.ts.map +1 -0
  65. package/dist/commands/sync.js +144 -0
  66. package/dist/commands/sync.js.map +1 -0
  67. package/dist/commands/unembed.d.ts +14 -0
  68. package/dist/commands/unembed.d.ts.map +1 -0
  69. package/dist/commands/unembed.js +96 -0
  70. package/dist/commands/unembed.js.map +1 -0
  71. package/dist/commands/update.d.ts +8 -0
  72. package/dist/commands/update.d.ts.map +1 -0
  73. package/dist/commands/update.js +372 -0
  74. package/dist/commands/update.js.map +1 -0
  75. package/dist/commands/verify.d.ts +14 -0
  76. package/dist/commands/verify.d.ts.map +1 -0
  77. package/dist/commands/verify.js +143 -0
  78. package/dist/commands/verify.js.map +1 -0
  79. package/dist/commands/whoami.d.ts +8 -0
  80. package/dist/commands/whoami.d.ts.map +1 -0
  81. package/dist/commands/whoami.js +58 -0
  82. package/dist/commands/whoami.js.map +1 -0
  83. package/dist/converters/base-converter.d.ts +89 -0
  84. package/dist/converters/base-converter.d.ts.map +1 -0
  85. package/dist/converters/base-converter.js +249 -0
  86. package/dist/converters/base-converter.js.map +1 -0
  87. package/dist/converters/continue-converter.d.ts +53 -0
  88. package/dist/converters/continue-converter.d.ts.map +1 -0
  89. package/dist/converters/continue-converter.js +243 -0
  90. package/dist/converters/continue-converter.js.map +1 -0
  91. package/dist/converters/cursor-converter.d.ts +70 -0
  92. package/dist/converters/cursor-converter.d.ts.map +1 -0
  93. package/dist/converters/cursor-converter.js +272 -0
  94. package/dist/converters/cursor-converter.js.map +1 -0
  95. package/dist/index.js +56 -3
  96. package/dist/index.js.map +1 -1
  97. package/dist/services/agent-detector.d.ts +57 -0
  98. package/dist/services/agent-detector.d.ts.map +1 -0
  99. package/dist/services/agent-detector.js +146 -0
  100. package/dist/services/agent-detector.js.map +1 -0
  101. package/dist/services/config-manager.d.ts +128 -10
  102. package/dist/services/config-manager.d.ts.map +1 -1
  103. package/dist/services/config-manager.js +190 -18
  104. package/dist/services/config-manager.js.map +1 -1
  105. package/dist/services/converter.d.ts +53 -0
  106. package/dist/services/converter.d.ts.map +1 -0
  107. package/dist/services/converter.js +185 -0
  108. package/dist/services/converter.js.map +1 -0
  109. package/dist/services/craft-wrapper.d.ts +108 -0
  110. package/dist/services/craft-wrapper.d.ts.map +1 -0
  111. package/dist/services/craft-wrapper.js +268 -0
  112. package/dist/services/craft-wrapper.js.map +1 -0
  113. package/dist/services/git-resolver.d.ts +9 -3
  114. package/dist/services/git-resolver.d.ts.map +1 -1
  115. package/dist/services/git-resolver.js +44 -20
  116. package/dist/services/git-resolver.js.map +1 -1
  117. package/dist/services/gitignore-manager.d.ts +74 -0
  118. package/dist/services/gitignore-manager.d.ts.map +1 -0
  119. package/dist/services/gitignore-manager.js +188 -0
  120. package/dist/services/gitignore-manager.js.map +1 -0
  121. package/dist/services/installer.d.ts +60 -1
  122. package/dist/services/installer.d.ts.map +1 -1
  123. package/dist/services/installer.js +295 -20
  124. package/dist/services/installer.js.map +1 -1
  125. package/dist/services/multi-agent-sync.d.ts +88 -0
  126. package/dist/services/multi-agent-sync.d.ts.map +1 -0
  127. package/dist/services/multi-agent-sync.js +316 -0
  128. package/dist/services/multi-agent-sync.js.map +1 -0
  129. package/dist/services/plugin-resolver.d.ts +80 -0
  130. package/dist/services/plugin-resolver.d.ts.map +1 -0
  131. package/dist/services/plugin-resolver.js +183 -0
  132. package/dist/services/plugin-resolver.js.map +1 -0
  133. package/dist/services/registry-client.d.ts +132 -5
  134. package/dist/services/registry-client.d.ts.map +1 -1
  135. package/dist/services/registry-client.js +239 -34
  136. package/dist/services/registry-client.js.map +1 -1
  137. package/dist/services/settings-manager.d.ts +151 -0
  138. package/dist/services/settings-manager.d.ts.map +1 -0
  139. package/dist/services/settings-manager.js +333 -0
  140. package/dist/services/settings-manager.js.map +1 -0
  141. package/dist/types/claude-settings.d.ts +155 -0
  142. package/dist/types/claude-settings.d.ts.map +1 -0
  143. package/dist/types/claude-settings.js +20 -0
  144. package/dist/types/claude-settings.js.map +1 -0
  145. package/dist/types/converter.d.ts +98 -0
  146. package/dist/types/converter.d.ts.map +1 -0
  147. package/dist/types/converter.js +6 -0
  148. package/dist/types/converter.js.map +1 -0
  149. package/dist/types/craftdesk-json.d.ts +76 -3
  150. package/dist/types/craftdesk-json.d.ts.map +1 -1
  151. package/dist/types/craftdesk-lock.d.ts +62 -1
  152. package/dist/types/craftdesk-lock.d.ts.map +1 -1
  153. package/dist/types/multi-agent.d.ts +85 -0
  154. package/dist/types/multi-agent.d.ts.map +1 -0
  155. package/dist/types/multi-agent.js +6 -0
  156. package/dist/types/multi-agent.js.map +1 -0
  157. package/dist/utils/crypto.d.ts +23 -0
  158. package/dist/utils/crypto.d.ts.map +1 -0
  159. package/dist/utils/crypto.js +52 -0
  160. package/dist/utils/crypto.js.map +1 -0
  161. package/dist/utils/file-system.d.ts +43 -0
  162. package/dist/utils/file-system.d.ts.map +1 -1
  163. package/dist/utils/file-system.js +45 -2
  164. package/dist/utils/file-system.js.map +1 -1
  165. package/dist/utils/logger.d.ts +75 -0
  166. package/dist/utils/logger.d.ts.map +1 -1
  167. package/dist/utils/logger.js +76 -1
  168. package/dist/utils/logger.js.map +1 -1
  169. package/dist/utils/version-utils.d.ts +63 -0
  170. package/dist/utils/version-utils.d.ts.map +1 -0
  171. package/dist/utils/version-utils.js +130 -0
  172. package/dist/utils/version-utils.js.map +1 -0
  173. package/package.json +36 -9
  174. package/.claude/settings.local.json +0 -19
  175. package/.github/workflows/ci.yml +0 -94
  176. package/API.md +0 -414
  177. package/TESTING.md +0 -263
  178. package/dist/commands/config.d.ts +0 -3
  179. package/dist/commands/config.d.ts.map +0 -1
  180. package/dist/commands/config.js +0 -56
  181. package/dist/commands/config.js.map +0 -1
  182. package/dist/types/config.d.ts +0 -17
  183. package/dist/types/config.d.ts.map +0 -1
  184. package/dist/types/config.js +0 -3
  185. package/dist/types/config.js.map +0 -1
  186. package/docs/DEPENDENCY_RESOLUTION.md +0 -237
  187. package/examples/craftdesk-all-sources.json +0 -79
  188. package/examples/craftdesk-git-monorepo.json +0 -41
  189. package/examples/craftdesk-multi-registry.json +0 -40
  190. package/examples/craftdesk-with-registries.json +0 -49
  191. package/examples/simple-craftdesk.json +0 -10
  192. package/src/commands/add.ts +0 -314
  193. package/src/commands/init.ts +0 -55
  194. package/src/commands/install.ts +0 -126
  195. package/src/commands/list.ts +0 -126
  196. package/src/commands/remove.ts +0 -82
  197. package/src/index.ts +0 -56
  198. package/src/services/config-manager.ts +0 -150
  199. package/src/services/git-resolver.ts +0 -373
  200. package/src/services/installer.ts +0 -314
  201. package/src/services/registry-client.ts +0 -163
  202. package/src/types/craftdesk-json.ts +0 -53
  203. package/src/types/craftdesk-lock.ts +0 -38
  204. package/src/utils/file-system.ts +0 -55
  205. package/src/utils/logger.ts +0 -82
  206. package/tests/fixtures/craftdesk-git.json +0 -17
  207. package/tests/fixtures/craftdesk.json +0 -15
  208. package/tests/helpers/test-utils.ts +0 -60
  209. package/tests/integration/init-command.test.ts +0 -70
  210. package/tests/integration/list-command.test.ts +0 -77
  211. package/tests/unit/config-manager.test.ts +0 -228
  212. package/tests/unit/git-resolver.test.ts +0 -303
  213. package/tests/unit/installer.test.ts +0 -105
  214. package/tsconfig.json +0 -20
  215. package/vitest.config.ts +0 -29
@@ -1,373 +0,0 @@
1
- import path from 'path';
2
- import fs from 'fs-extra';
3
- import { execSync } from 'child_process';
4
- import { logger } from '../utils/logger';
5
- import { CraftDeskJson, DependencyConfig } from '../types/craftdesk-json';
6
-
7
- /**
8
- * Information about a git dependency
9
- */
10
- export interface GitDependencyInfo {
11
- /** Git repository URL */
12
- url: string;
13
- /** Optional branch name */
14
- branch?: string;
15
- /** Optional tag name */
16
- tag?: string;
17
- /** Optional commit hash */
18
- commit?: string;
19
- /** Optional subdirectory path for monorepos */
20
- path?: string;
21
- /** Optional direct file path (e.g., skill.md, agent.md) */
22
- file?: string;
23
- /** Parsed craftdesk.json from the repository */
24
- craftDeskJson?: CraftDeskJson;
25
- /** Resolved full commit hash */
26
- resolvedCommit?: string;
27
- }
28
-
29
- /**
30
- * Resolves git dependencies and extracts their metadata
31
- *
32
- * Handles cloning git repositories, reading craftdesk.json, inferring craft types
33
- * when metadata is missing, and supporting monorepo structures with subdirectory paths.
34
- *
35
- * @example
36
- * ```typescript
37
- * const resolver = new GitResolver();
38
- *
39
- * // Resolve a git dependency
40
- * const info = await resolver.resolveGitDependency({
41
- * url: 'https://github.com/company/auth.git',
42
- * branch: 'main'
43
- * });
44
- *
45
- * console.log(info.craftDeskJson.name); // 'auth-skill'
46
- * console.log(info.resolvedCommit); // Full commit hash
47
- * ```
48
- */
49
- export class GitResolver {
50
- private tempDir: string;
51
-
52
- constructor() {
53
- this.tempDir = path.join(process.cwd(), '.craftdesk', 'tmp');
54
- }
55
-
56
- /**
57
- * Resolves a git dependency by cloning and reading its craftdesk.json
58
- *
59
- * Process:
60
- * 1. Clones the repository (shallow clone for efficiency)
61
- * 2. Checks out specified branch/tag/commit
62
- * 3. Reads craftdesk.json from repository, subdirectory, or infers from direct file
63
- * 4. Generates minimal metadata if craftdesk.json is missing
64
- * 5. Infers craft type from marker files, direct file extension, or directory structure
65
- *
66
- * @param gitInfo - Git dependency information
67
- * @returns Enhanced git info with craftdesk.json and resolved commit
68
- *
69
- * @example
70
- * ```typescript
71
- * // With craftdesk.json in repository
72
- * const info = await resolver.resolveGitDependency({
73
- * url: 'https://github.com/user/skill.git',
74
- * tag: 'v1.0.0'
75
- * });
76
- *
77
- * // Monorepo with subdirectory
78
- * const info = await resolver.resolveGitDependency({
79
- * url: 'https://github.com/company/monorepo.git',
80
- * tag: 'v3.0.0',
81
- * path: 'crafts/auth'
82
- * });
83
- *
84
- * // Direct file reference
85
- * const info = await resolver.resolveGitDependency({
86
- * url: 'https://github.com/user/repo.git',
87
- * branch: 'main',
88
- * file: 'rspec-dry-agent.md'
89
- * });
90
- * ```
91
- */
92
- async resolveGitDependency(gitInfo: GitDependencyInfo): Promise<GitDependencyInfo> {
93
- const repoTempDir = path.join(this.tempDir, `git-${Date.now()}`);
94
-
95
- try {
96
- await fs.ensureDir(repoTempDir);
97
-
98
- // Clone the repository
99
- logger.debug(`Cloning ${gitInfo.url} to analyze dependencies...`);
100
-
101
- let cloneCmd = `git clone --depth 1`;
102
- if (gitInfo.branch) {
103
- cloneCmd += ` -b ${gitInfo.branch}`;
104
- } else if (gitInfo.tag) {
105
- cloneCmd += ` -b ${gitInfo.tag}`;
106
- }
107
- cloneCmd += ` ${gitInfo.url} ${repoTempDir}`;
108
-
109
- execSync(cloneCmd, { stdio: 'pipe' });
110
-
111
- // If specific commit, checkout that commit
112
- if (gitInfo.commit) {
113
- execSync(`cd ${repoTempDir} && git fetch --unshallow && git checkout ${gitInfo.commit}`, { stdio: 'pipe' });
114
- }
115
-
116
- // Get the actual commit hash for lockfile
117
- const resolvedCommit = execSync(`cd ${repoTempDir} && git rev-parse HEAD`, { encoding: 'utf8' }).trim();
118
- gitInfo.resolvedCommit = resolvedCommit;
119
-
120
- // Handle direct file reference
121
- if (gitInfo.file) {
122
- const filePath = path.join(repoTempDir, gitInfo.file);
123
-
124
- if (await fs.pathExists(filePath)) {
125
- logger.debug(`Found direct file reference: ${gitInfo.file}`);
126
-
127
- // Try to find craftdesk.json to get type and metadata
128
- let craftType: 'skill' | 'agent' | 'command' | 'hook' | 'plugin' | undefined;
129
- let craftName: string | undefined;
130
- let craftVersion: string | undefined;
131
- let craftDeps: Record<string, any> | undefined;
132
-
133
- // Check for craftdesk.json in same directory as file
134
- const fileDir = path.dirname(gitInfo.file);
135
- const fileDirCraftPath = path.join(repoTempDir, fileDir, 'craftdesk.json');
136
-
137
- // Check for craftdesk.json in repo root
138
- const rootCraftPath = path.join(repoTempDir, 'craftdesk.json');
139
-
140
- if (await fs.pathExists(fileDirCraftPath)) {
141
- const content = await fs.readFile(fileDirCraftPath, 'utf-8');
142
- const craftJson = JSON.parse(content);
143
- logger.debug(`Found craftdesk.json in file directory: ${craftJson.name}@${craftJson.version}`);
144
- craftType = craftJson.type;
145
- craftName = craftJson.name;
146
- craftVersion = craftJson.version;
147
- craftDeps = craftJson.dependencies;
148
- } else if (await fs.pathExists(rootCraftPath)) {
149
- const content = await fs.readFile(rootCraftPath, 'utf-8');
150
- const craftJson = JSON.parse(content);
151
- logger.debug(`Found craftdesk.json in repo root: ${craftJson.name}@${craftJson.version}`);
152
- craftType = craftJson.type;
153
- craftName = craftJson.name;
154
- craftVersion = craftJson.version;
155
- craftDeps = craftJson.dependencies;
156
- }
157
-
158
- // If no craftdesk.json found, infer from filename
159
- if (!craftType) {
160
- craftType = this.inferCraftTypeFromFilename(gitInfo.file);
161
- }
162
-
163
- // Create minimal craftdesk.json from file
164
- gitInfo.craftDeskJson = {
165
- name: craftName || path.basename(gitInfo.file, path.extname(gitInfo.file)),
166
- version: craftVersion || gitInfo.tag || gitInfo.branch || resolvedCommit.substring(0, 7),
167
- type: craftType,
168
- description: `Git file dependency from ${gitInfo.url}/${gitInfo.file}`,
169
- dependencies: craftDeps || {}
170
- };
171
- } else {
172
- throw new Error(`File not found: ${gitInfo.file} in ${gitInfo.url}`);
173
- }
174
- } else {
175
- // Look for craftdesk.json in the specified path or root
176
- const craftPath = gitInfo.path
177
- ? path.join(repoTempDir, gitInfo.path, 'craftdesk.json')
178
- : path.join(repoTempDir, 'craftdesk.json');
179
-
180
- if (await fs.pathExists(craftPath)) {
181
- const content = await fs.readFile(craftPath, 'utf-8');
182
- gitInfo.craftDeskJson = JSON.parse(content);
183
- logger.debug(`Found craftdesk.json in git repository: ${gitInfo.craftDeskJson!.name}@${gitInfo.craftDeskJson!.version}`);
184
- } else {
185
- // No craftdesk.json found - create a minimal one
186
- logger.warn(`No craftdesk.json found in ${gitInfo.url}${gitInfo.path ? `#path:${gitInfo.path}` : ''}`);
187
-
188
- // Try to infer the type from directory structure
189
- const type = await this.inferCraftType(repoTempDir, gitInfo.path);
190
-
191
- gitInfo.craftDeskJson = {
192
- name: path.basename(gitInfo.url, '.git'),
193
- version: gitInfo.tag || gitInfo.branch || resolvedCommit.substring(0, 7),
194
- type: type,
195
- description: `Git dependency from ${gitInfo.url}`,
196
- dependencies: {}
197
- };
198
- }
199
- }
200
-
201
- return gitInfo;
202
-
203
- } finally {
204
- // Clean up temp directory
205
- await fs.remove(repoTempDir);
206
- }
207
- }
208
-
209
- /**
210
- * Infers the craft type from a filename
211
- *
212
- * Type inference strategy:
213
- * 1. Check filename for type indicators: agent, skill, command, hook
214
- * 2. Check file extension patterns
215
- * 3. Default to 'skill' if no indicators found
216
- *
217
- * @param filename - The filename to analyze
218
- * @returns The inferred craft type
219
- * @private
220
- */
221
- private inferCraftTypeFromFilename(filename: string): 'skill' | 'agent' | 'command' | 'hook' | 'plugin' {
222
- const lower = filename.toLowerCase();
223
-
224
- // Check filename for type indicators
225
- if (lower.includes('agent')) return 'agent';
226
- if (lower.includes('command')) return 'command';
227
- if (lower.includes('hook')) return 'hook';
228
- if (lower.includes('plugin')) return 'plugin';
229
- if (lower.includes('skill')) return 'skill';
230
-
231
- // Default to skill
232
- return 'skill';
233
- }
234
-
235
- /**
236
- * Infers the craft type from the repository structure
237
- *
238
- * Type inference strategy:
239
- * 1. Check for marker files: SKILL.md, AGENT.md, COMMAND.md, HOOK.md
240
- * 2. Check directory name for type indicators
241
- * 3. Default to 'skill' if no indicators found
242
- *
243
- * @param repoPath - Path to the cloned repository
244
- * @param subPath - Optional subdirectory to check
245
- * @returns The inferred craft type
246
- * @private
247
- */
248
- private async inferCraftType(repoPath: string, subPath?: string): Promise<'skill' | 'agent' | 'command' | 'hook' | 'plugin'> {
249
- const checkPath = subPath ? path.join(repoPath, subPath) : repoPath;
250
-
251
- // Check for type-indicating files
252
- const files = await fs.readdir(checkPath);
253
-
254
- // Look for SKILL.md, AGENT.md, COMMAND.md, HOOK.md, PLUGIN.md
255
- if (files.includes('SKILL.md')) return 'skill';
256
- if (files.includes('AGENT.md')) return 'agent';
257
- if (files.includes('COMMAND.md')) return 'command';
258
- if (files.includes('HOOK.md')) return 'hook';
259
- if (files.includes('PLUGIN.md')) return 'plugin';
260
-
261
- // Check directory name hints
262
- const dirName = subPath ? path.basename(subPath) : path.basename(repoPath);
263
- if (dirName.includes('skill')) return 'skill';
264
- if (dirName.includes('agent')) return 'agent';
265
- if (dirName.includes('command')) return 'command';
266
- if (dirName.includes('hook')) return 'hook';
267
- if (dirName.includes('plugin')) return 'plugin';
268
-
269
- // Default to skill
270
- return 'skill';
271
- }
272
-
273
- /**
274
- * Resolves all dependencies including transitive dependencies from git sources
275
- *
276
- * Uses a breadth-first traversal algorithm to resolve dependencies recursively.
277
- * Tracks visited packages to avoid circular dependencies.
278
- *
279
- * @param dependencies - Map of dependency names to version strings or config objects
280
- * @returns Object containing resolved dependencies and generated lockfile
281
- *
282
- * @example
283
- * ```typescript
284
- * const result = await resolver.resolveAllDependencies({
285
- * 'ruby-on-rails': '^7.0.0',
286
- * 'custom-auth': {
287
- * git: 'https://github.com/company/auth.git',
288
- * branch: 'main'
289
- * }
290
- * });
291
- *
292
- * console.log(result.resolved); // All dependencies including transitive
293
- * console.log(result.lockfile); // Generated lockfile structure
294
- * ```
295
- */
296
- async resolveAllDependencies(dependencies: Record<string, string | DependencyConfig>): Promise<{
297
- resolved: Record<string, any>;
298
- lockfile: any;
299
- }> {
300
- const resolved: Record<string, any> = {};
301
- // Queue of dependencies to resolve (breadth-first traversal)
302
- const toResolve: Array<[string, string | DependencyConfig]> = Object.entries(dependencies);
303
- // Track visited packages to prevent infinite loops from circular dependencies
304
- const visited = new Set<string>();
305
-
306
- // Process queue until all dependencies are resolved
307
- while (toResolve.length > 0) {
308
- const [name, dep] = toResolve.shift()!;
309
-
310
- // Skip if already resolved (handles duplicates and circular deps)
311
- if (visited.has(name)) continue;
312
- visited.add(name);
313
-
314
- // Determine if this is a git or registry dependency
315
- if (typeof dep === 'object' && dep.git) {
316
- // Git dependency - clone repository and extract metadata
317
- const gitInfo = await this.resolveGitDependency({
318
- url: dep.git,
319
- branch: dep.branch,
320
- tag: dep.tag,
321
- commit: dep.commit,
322
- path: dep.path,
323
- file: dep.file
324
- });
325
-
326
- resolved[name] = {
327
- version: gitInfo.craftDeskJson?.version || '0.0.0',
328
- resolved: gitInfo.url,
329
- integrity: gitInfo.resolvedCommit || 'git',
330
- type: gitInfo.craftDeskJson?.type || 'skill',
331
- author: gitInfo.craftDeskJson?.author || 'git',
332
- git: gitInfo.url,
333
- ...(gitInfo.branch && { branch: gitInfo.branch }),
334
- ...(gitInfo.tag && { tag: gitInfo.tag }),
335
- ...(gitInfo.commit && { commit: gitInfo.resolvedCommit }),
336
- ...(gitInfo.path && { path: gitInfo.path }),
337
- ...(gitInfo.file && { file: gitInfo.file }),
338
- dependencies: gitInfo.craftDeskJson?.dependencies || {}
339
- };
340
-
341
- // Add transitive dependencies to the resolution queue
342
- // This enables recursive dependency resolution
343
- if (gitInfo.craftDeskJson?.dependencies) {
344
- for (const [depName, depVersion] of Object.entries(gitInfo.craftDeskJson.dependencies)) {
345
- // Only add if not already visited (avoids redundant resolution)
346
- if (!visited.has(depName)) {
347
- toResolve.push([depName, depVersion]);
348
- }
349
- }
350
- }
351
- } else {
352
- // Registry dependency - mark for resolution by registry client
353
- // The actual version resolution happens via the registry API
354
- resolved[name] = {
355
- needsResolution: true,
356
- version: typeof dep === 'string' ? dep : dep.version,
357
- registry: typeof dep === 'object' ? dep.registry : undefined
358
- };
359
- }
360
- }
361
-
362
- return {
363
- resolved,
364
- lockfile: {
365
- version: '1.0.0',
366
- lockfileVersion: 1,
367
- crafts: resolved
368
- }
369
- };
370
- }
371
- }
372
-
373
- export const gitResolver = new GitResolver();
@@ -1,314 +0,0 @@
1
- import path from 'path';
2
- import fs from 'fs-extra';
3
- import axios from 'axios';
4
- import { logger } from '../utils/logger';
5
- import { configManager } from './config-manager';
6
- import { CraftDeskLock, LockEntry } from '../types/craftdesk-lock';
7
- import { ensureDir } from '../utils/file-system';
8
-
9
- /**
10
- * Handles installation of crafts from various sources
11
- *
12
- * Supports installing from:
13
- * - Registry packages (via download URLs)
14
- * - Git repositories (with monorepo support)
15
- * - Local archives
16
- *
17
- * Organizes installations by craft type in the .claude directory.
18
- *
19
- * @example
20
- * ```typescript
21
- * const installer = new Installer();
22
- *
23
- * // Install from lockfile
24
- * const lockfile = await readLockfile();
25
- * await installer.installFromLockfile(lockfile);
26
- *
27
- * // Install single craft
28
- * await installer.installCraft('ruby-on-rails', lockEntry);
29
- * ```
30
- */
31
- export class Installer {
32
- private installPath: string;
33
-
34
- constructor() {
35
- this.installPath = configManager.getInstallPath();
36
- }
37
-
38
- /**
39
- * Installs all crafts defined in a lockfile
40
- *
41
- * Processes each craft sequentially, updating progress as it goes.
42
- * Fails fast if any installation errors occur.
43
- *
44
- * @param lockfile - The craftdesk.lock content with all dependencies
45
- * @throws Error if any craft fails to install
46
- *
47
- * @example
48
- * ```typescript
49
- * const lockfile = {
50
- * version: '1.0.0',
51
- * lockfileVersion: 1,
52
- * crafts: {
53
- * 'ruby-on-rails': { version: '7.1.0', ... },
54
- * 'postgres-expert': { version: '1.2.0', ... }
55
- * }
56
- * };
57
- *
58
- * await installer.installFromLockfile(lockfile);
59
- * ```
60
- */
61
- async installFromLockfile(lockfile: CraftDeskLock): Promise<void> {
62
- const installDir = path.join(process.cwd(), this.installPath);
63
- await ensureDir(installDir);
64
-
65
- const crafts = Object.entries(lockfile.crafts);
66
- let installed = 0;
67
-
68
- for (const [name, entry] of crafts) {
69
- try {
70
- logger.updateSpinner(`Installing ${name}@${entry.version} (${installed + 1}/${crafts.length})`);
71
- await this.installCraft(name, entry);
72
- installed++;
73
- } catch (error: any) {
74
- logger.failSpinner(`Failed to install ${name}: ${error.message}`);
75
- throw error;
76
- }
77
- }
78
-
79
- logger.succeedSpinner(`Installed ${installed} crafts`);
80
- }
81
-
82
- /**
83
- * Installs a single craft from registry or git source
84
- *
85
- * Installation process:
86
- * 1. Determines installation directory based on craft type
87
- * 2. For git dependencies: clones and extracts subdirectory
88
- * 3. For registry: downloads archive, verifies, and extracts
89
- * 4. Creates metadata file for tracking installation
90
- *
91
- * @param name - The name of the craft to install
92
- * @param entry - Lockfile entry containing installation details
93
- * @throws Error if download or extraction fails
94
- *
95
- * @example
96
- * ```typescript
97
- * // Install from registry
98
- * await installer.installCraft('ruby-on-rails', {
99
- * version: '7.1.0',
100
- * resolved: 'https://craftdesk.ai/download/...',
101
- * type: 'skill',
102
- * dependencies: {}
103
- * });
104
- *
105
- * // Install from git
106
- * await installer.installCraft('custom-auth', {
107
- * version: '1.0.0',
108
- * git: 'https://github.com/company/auth.git',
109
- * branch: 'main',
110
- * type: 'agent',
111
- * dependencies: {}
112
- * });
113
- * ```
114
- */
115
- async installCraft(name: string, entry: LockEntry): Promise<void> {
116
- const installDir = path.join(process.cwd(), this.installPath);
117
-
118
- // Determine install subdirectory based on type
119
- const typeDir = this.getTypeDirectory(entry.type);
120
- const craftDir = path.join(installDir, typeDir, name);
121
-
122
- // Create directory
123
- await ensureDir(craftDir);
124
-
125
- // Check if this is a git dependency
126
- if (entry.git) {
127
- await this.installFromGit(craftDir, entry);
128
- } else {
129
- // Download craft archive
130
- const archivePath = path.join(craftDir, 'archive.tar.gz');
131
- await this.downloadFile(entry.resolved, archivePath);
132
-
133
- // Verify integrity (TODO: implement SHA-256 check)
134
- // await this.verifyIntegrity(archivePath, entry.integrity);
135
-
136
- // Extract archive
137
- await this.extractArchive(archivePath, craftDir);
138
-
139
- // Clean up archive
140
- await fs.remove(archivePath);
141
- }
142
-
143
- // Create metadata file
144
- await this.createMetadata(craftDir, name, entry);
145
- }
146
-
147
- private async installFromGit(craftDir: string, entry: LockEntry): Promise<void> {
148
- const tempDir = path.join(craftDir, '.tmp-git');
149
-
150
- try {
151
- // Clone the repository
152
- const { execSync } = require('child_process');
153
-
154
- // Build clone command with appropriate ref
155
- let cloneCmd = `git clone --depth 1`;
156
-
157
- if (entry.branch) {
158
- cloneCmd += ` -b ${entry.branch}`;
159
- } else if (entry.tag) {
160
- cloneCmd += ` -b ${entry.tag}`;
161
- }
162
-
163
- cloneCmd += ` ${entry.git} ${tempDir}`;
164
-
165
- logger.debug(`Cloning git repository: ${cloneCmd}`);
166
- execSync(cloneCmd, { stdio: 'pipe' });
167
-
168
- // If specific commit, checkout that commit
169
- if (entry.commit) {
170
- execSync(`cd ${tempDir} && git fetch --unshallow && git checkout ${entry.commit}`, { stdio: 'pipe' });
171
- }
172
-
173
- // Handle direct file reference
174
- if (entry.file) {
175
- const sourceFile = path.join(tempDir, entry.file);
176
- const destFile = path.join(craftDir, path.basename(entry.file));
177
-
178
- if (await fs.pathExists(sourceFile)) {
179
- await fs.copy(sourceFile, destFile, { overwrite: true });
180
- } else {
181
- throw new Error(`File not found: ${entry.file} in ${entry.git}`);
182
- }
183
- } else {
184
- // Copy from subdirectory if specified, otherwise copy entire repo
185
- const sourcePath = entry.path ? path.join(tempDir, entry.path) : tempDir;
186
-
187
- // Copy contents to craft directory
188
- const files = await fs.readdir(sourcePath);
189
- for (const file of files) {
190
- if (file !== '.tmp-git' && file !== '.git') {
191
- await fs.copy(
192
- path.join(sourcePath, file),
193
- path.join(craftDir, file),
194
- { overwrite: true }
195
- );
196
- }
197
- }
198
- }
199
-
200
- } finally {
201
- // Clean up temp directory
202
- await fs.remove(tempDir);
203
- }
204
- }
205
-
206
- private getTypeDirectory(type: string): string {
207
- switch (type) {
208
- case 'skill':
209
- return 'skills';
210
- case 'agent':
211
- return 'agents';
212
- case 'command':
213
- return 'commands';
214
- case 'hook':
215
- return 'hooks';
216
- default:
217
- return 'crafts';
218
- }
219
- }
220
-
221
- private async downloadFile(url: string, outputPath: string): Promise<void> {
222
- const response = await axios({
223
- method: 'GET',
224
- url: url,
225
- responseType: 'stream'
226
- });
227
-
228
- const writer = fs.createWriteStream(outputPath);
229
- response.data.pipe(writer);
230
-
231
- return new Promise((resolve, reject) => {
232
- writer.on('finish', resolve);
233
- writer.on('error', reject);
234
- });
235
- }
236
-
237
- private async extractArchive(archivePath: string, outputDir: string): Promise<void> {
238
- // For now, just copy some placeholder files
239
- // In production, this would extract a tar.gz or zip archive
240
-
241
- const type = path.basename(outputDir).includes('skill') ? 'SKILL' : 'AGENT';
242
- const mainFile = `${type}.md`;
243
-
244
- await fs.writeFile(
245
- path.join(outputDir, mainFile),
246
- `# ${path.basename(outputDir)}\n\nThis is a placeholder for the ${type.toLowerCase()} content.\n`,
247
- 'utf-8'
248
- );
249
- }
250
-
251
- private async createMetadata(craftDir: string, name: string, entry: LockEntry): Promise<void> {
252
- const metadata = {
253
- name,
254
- version: entry.version,
255
- type: entry.type,
256
- author: entry.author,
257
- installedAt: new Date().toISOString(),
258
- registry: entry.registry,
259
- dependencies: entry.dependencies
260
- };
261
-
262
- await fs.writeFile(
263
- path.join(craftDir, '.craftdesk-metadata.json'),
264
- JSON.stringify(metadata, null, 2),
265
- 'utf-8'
266
- );
267
- }
268
-
269
- async removeCraft(name: string, type: string): Promise<void> {
270
- const installDir = path.join(process.cwd(), this.installPath);
271
- const typeDir = this.getTypeDirectory(type);
272
- const craftDir = path.join(installDir, typeDir, name);
273
-
274
- if (await fs.pathExists(craftDir)) {
275
- await fs.remove(craftDir);
276
- logger.success(`Removed ${name}`);
277
- } else {
278
- logger.warn(`${name} is not installed`);
279
- }
280
- }
281
-
282
- async listInstalled(): Promise<Array<{ name: string; version: string; type: string }>> {
283
- const installDir = path.join(process.cwd(), this.installPath);
284
- const installed: Array<{ name: string; version: string; type: string }> = [];
285
-
286
- if (!await fs.pathExists(installDir)) {
287
- return installed;
288
- }
289
-
290
- const typeDirs = ['skills', 'agents', 'commands', 'hooks', 'plugins'];
291
-
292
- for (const typeDir of typeDirs) {
293
- const dir = path.join(installDir, typeDir);
294
- if (!await fs.pathExists(dir)) continue;
295
-
296
- const crafts = await fs.readdir(dir);
297
- for (const craftName of crafts) {
298
- const metadataPath = path.join(dir, craftName, '.craftdesk-metadata.json');
299
- if (await fs.pathExists(metadataPath)) {
300
- const metadata = await fs.readJson(metadataPath);
301
- installed.push({
302
- name: craftName,
303
- version: metadata.version,
304
- type: metadata.type
305
- });
306
- }
307
- }
308
- }
309
-
310
- return installed;
311
- }
312
- }
313
-
314
- export const installer = new Installer();