craftdesk 0.2.0 → 0.4.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 (175) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +968 -92
  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 +303 -7
  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/info.d.ts +8 -0
  12. package/dist/commands/info.d.ts.map +1 -0
  13. package/dist/commands/info.js +66 -0
  14. package/dist/commands/info.js.map +1 -0
  15. package/dist/commands/init.d.ts +5 -0
  16. package/dist/commands/init.d.ts.map +1 -1
  17. package/dist/commands/init.js +9 -3
  18. package/dist/commands/init.js.map +1 -1
  19. package/dist/commands/install.d.ts +5 -0
  20. package/dist/commands/install.d.ts.map +1 -1
  21. package/dist/commands/install.js +24 -4
  22. package/dist/commands/install.js.map +1 -1
  23. package/dist/commands/list.d.ts +5 -0
  24. package/dist/commands/list.d.ts.map +1 -1
  25. package/dist/commands/list.js +72 -7
  26. package/dist/commands/list.js.map +1 -1
  27. package/dist/commands/login.d.ts +8 -0
  28. package/dist/commands/login.d.ts.map +1 -0
  29. package/dist/commands/login.js +158 -0
  30. package/dist/commands/login.js.map +1 -0
  31. package/dist/commands/logout.d.ts +8 -0
  32. package/dist/commands/logout.d.ts.map +1 -0
  33. package/dist/commands/logout.js +51 -0
  34. package/dist/commands/logout.js.map +1 -0
  35. package/dist/commands/outdated.d.ts +23 -0
  36. package/dist/commands/outdated.d.ts.map +1 -0
  37. package/dist/commands/outdated.js +268 -0
  38. package/dist/commands/outdated.js.map +1 -0
  39. package/dist/commands/publish.d.ts +8 -0
  40. package/dist/commands/publish.d.ts.map +1 -0
  41. package/dist/commands/publish.js +198 -0
  42. package/dist/commands/publish.js.map +1 -0
  43. package/dist/commands/remove.d.ts +5 -0
  44. package/dist/commands/remove.d.ts.map +1 -1
  45. package/dist/commands/remove.js +52 -11
  46. package/dist/commands/remove.js.map +1 -1
  47. package/dist/commands/search.d.ts +8 -0
  48. package/dist/commands/search.d.ts.map +1 -0
  49. package/dist/commands/search.js +60 -0
  50. package/dist/commands/search.js.map +1 -0
  51. package/dist/commands/update.d.ts +8 -0
  52. package/dist/commands/update.d.ts.map +1 -0
  53. package/dist/commands/update.js +369 -0
  54. package/dist/commands/update.js.map +1 -0
  55. package/dist/commands/whoami.d.ts +8 -0
  56. package/dist/commands/whoami.d.ts.map +1 -0
  57. package/dist/commands/whoami.js +58 -0
  58. package/dist/commands/whoami.js.map +1 -0
  59. package/dist/converters/base-converter.d.ts +89 -0
  60. package/dist/converters/base-converter.d.ts.map +1 -0
  61. package/dist/converters/base-converter.js +249 -0
  62. package/dist/converters/base-converter.js.map +1 -0
  63. package/dist/converters/continue-converter.d.ts +53 -0
  64. package/dist/converters/continue-converter.d.ts.map +1 -0
  65. package/dist/converters/continue-converter.js +243 -0
  66. package/dist/converters/continue-converter.js.map +1 -0
  67. package/dist/converters/cursor-converter.d.ts +70 -0
  68. package/dist/converters/cursor-converter.d.ts.map +1 -0
  69. package/dist/converters/cursor-converter.js +272 -0
  70. package/dist/converters/cursor-converter.js.map +1 -0
  71. package/dist/index.js +35 -4
  72. package/dist/index.js.map +1 -1
  73. package/dist/services/config-manager.d.ts +128 -10
  74. package/dist/services/config-manager.d.ts.map +1 -1
  75. package/dist/services/config-manager.js +189 -18
  76. package/dist/services/config-manager.js.map +1 -1
  77. package/dist/services/converter.d.ts +53 -0
  78. package/dist/services/converter.d.ts.map +1 -0
  79. package/dist/services/converter.js +185 -0
  80. package/dist/services/converter.js.map +1 -0
  81. package/dist/services/craft-wrapper.d.ts +108 -0
  82. package/dist/services/craft-wrapper.d.ts.map +1 -0
  83. package/dist/services/craft-wrapper.js +268 -0
  84. package/dist/services/craft-wrapper.js.map +1 -0
  85. package/dist/services/git-resolver.d.ts +9 -3
  86. package/dist/services/git-resolver.d.ts.map +1 -1
  87. package/dist/services/git-resolver.js +35 -10
  88. package/dist/services/git-resolver.js.map +1 -1
  89. package/dist/services/installer.d.ts +51 -1
  90. package/dist/services/installer.d.ts.map +1 -1
  91. package/dist/services/installer.js +245 -14
  92. package/dist/services/installer.js.map +1 -1
  93. package/dist/services/plugin-resolver.d.ts +80 -0
  94. package/dist/services/plugin-resolver.d.ts.map +1 -0
  95. package/dist/services/plugin-resolver.js +182 -0
  96. package/dist/services/plugin-resolver.js.map +1 -0
  97. package/dist/services/registry-client.d.ts +132 -5
  98. package/dist/services/registry-client.d.ts.map +1 -1
  99. package/dist/services/registry-client.js +239 -34
  100. package/dist/services/registry-client.js.map +1 -1
  101. package/dist/services/settings-manager.d.ts +151 -0
  102. package/dist/services/settings-manager.d.ts.map +1 -0
  103. package/dist/services/settings-manager.js +333 -0
  104. package/dist/services/settings-manager.js.map +1 -0
  105. package/dist/types/claude-settings.d.ts +155 -0
  106. package/dist/types/claude-settings.d.ts.map +1 -0
  107. package/dist/types/claude-settings.js +20 -0
  108. package/dist/types/claude-settings.js.map +1 -0
  109. package/dist/types/converter.d.ts +98 -0
  110. package/dist/types/converter.d.ts.map +1 -0
  111. package/dist/types/converter.js +6 -0
  112. package/dist/types/converter.js.map +1 -0
  113. package/dist/types/craftdesk-json.d.ts +63 -3
  114. package/dist/types/craftdesk-json.d.ts.map +1 -1
  115. package/dist/types/craftdesk-lock.d.ts +62 -1
  116. package/dist/types/craftdesk-lock.d.ts.map +1 -1
  117. package/dist/utils/crypto.d.ts +23 -0
  118. package/dist/utils/crypto.d.ts.map +1 -0
  119. package/dist/utils/crypto.js +52 -0
  120. package/dist/utils/crypto.js.map +1 -0
  121. package/dist/utils/file-system.d.ts +43 -0
  122. package/dist/utils/file-system.d.ts.map +1 -1
  123. package/dist/utils/file-system.js +45 -2
  124. package/dist/utils/file-system.js.map +1 -1
  125. package/dist/utils/logger.d.ts +75 -0
  126. package/dist/utils/logger.d.ts.map +1 -1
  127. package/dist/utils/logger.js +76 -1
  128. package/dist/utils/logger.js.map +1 -1
  129. package/dist/utils/version-utils.d.ts +63 -0
  130. package/dist/utils/version-utils.d.ts.map +1 -0
  131. package/dist/utils/version-utils.js +130 -0
  132. package/dist/utils/version-utils.js.map +1 -0
  133. package/package.json +36 -9
  134. package/.claude/settings.local.json +0 -17
  135. package/.github/workflows/ci.yml +0 -94
  136. package/API.md +0 -414
  137. package/TESTING.md +0 -263
  138. package/dist/commands/config.d.ts +0 -3
  139. package/dist/commands/config.d.ts.map +0 -1
  140. package/dist/commands/config.js +0 -56
  141. package/dist/commands/config.js.map +0 -1
  142. package/dist/types/config.d.ts +0 -17
  143. package/dist/types/config.d.ts.map +0 -1
  144. package/dist/types/config.js +0 -3
  145. package/dist/types/config.js.map +0 -1
  146. package/docs/DEPENDENCY_RESOLUTION.md +0 -237
  147. package/examples/craftdesk-all-sources.json +0 -79
  148. package/examples/craftdesk-git-monorepo.json +0 -41
  149. package/examples/craftdesk-multi-registry.json +0 -40
  150. package/examples/craftdesk-with-registries.json +0 -49
  151. package/examples/simple-craftdesk.json +0 -10
  152. package/src/commands/add.ts +0 -314
  153. package/src/commands/init.ts +0 -55
  154. package/src/commands/install.ts +0 -126
  155. package/src/commands/list.ts +0 -126
  156. package/src/commands/remove.ts +0 -82
  157. package/src/index.ts +0 -56
  158. package/src/services/config-manager.ts +0 -150
  159. package/src/services/git-resolver.ts +0 -370
  160. package/src/services/installer.ts +0 -314
  161. package/src/services/registry-client.ts +0 -163
  162. package/src/types/craftdesk-json.ts +0 -53
  163. package/src/types/craftdesk-lock.ts +0 -38
  164. package/src/utils/file-system.ts +0 -55
  165. package/src/utils/logger.ts +0 -82
  166. package/tests/fixtures/craftdesk-git.json +0 -17
  167. package/tests/fixtures/craftdesk.json +0 -15
  168. package/tests/helpers/test-utils.ts +0 -60
  169. package/tests/integration/init-command.test.ts +0 -70
  170. package/tests/integration/list-command.test.ts +0 -77
  171. package/tests/unit/config-manager.test.ts +0 -228
  172. package/tests/unit/git-resolver.test.ts +0 -303
  173. package/tests/unit/installer.test.ts +0 -105
  174. package/tsconfig.json +0 -20
  175. package/vitest.config.ts +0 -29
@@ -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'];
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();
@@ -1,163 +0,0 @@
1
- import axios, { AxiosInstance } from 'axios';
2
- import { configManager } from './config-manager';
3
- import { logger } from '../utils/logger';
4
- import { LockEntry } from '../types/craftdesk-lock';
5
-
6
- export interface CraftInfo {
7
- name: string;
8
- author: string;
9
- version: string;
10
- type: 'skill' | 'agent' | 'command' | 'hook';
11
- description?: string;
12
- dependencies?: Record<string, string>;
13
- download_url?: string;
14
- integrity?: string;
15
- }
16
-
17
- export interface ResolveResponse {
18
- resolved: Record<string, any>;
19
- lockfile: any;
20
- }
21
-
22
- export class RegistryClient {
23
- private axiosInstance: AxiosInstance | null = null;
24
-
25
- private async getClient(registryUrl: string): Promise<AxiosInstance> {
26
- const token = await configManager.getAuthToken(registryUrl);
27
-
28
- return axios.create({
29
- baseURL: registryUrl,
30
- headers: {
31
- 'Content-Type': 'application/json',
32
- ...(token ? { 'Authorization': `Bearer ${token}` } : {})
33
- },
34
- timeout: 30000
35
- });
36
- }
37
-
38
- async getCraftInfo(craftName: string, version?: string, registryOverride?: string): Promise<CraftInfo | null> {
39
- // Get registry URL - can be overridden by dependency-specific registry
40
- const registryUrl = registryOverride
41
- ? await this.resolveRegistry(registryOverride)
42
- : await configManager.getRegistryForCraft(craftName);
43
-
44
- const client = await this.getClient(registryUrl);
45
-
46
- // Parse craft name (could be @scope/name or just name)
47
- const [author, name] = this.parseCraftName(craftName);
48
-
49
- try {
50
- const endpoint = version
51
- ? `/api/v1/crafts/${author}/${name}/versions/${version}`
52
- : `/api/v1/crafts/${author}/${name}`;
53
-
54
- logger.debug(`Fetching craft info from ${registryUrl}${endpoint}`);
55
-
56
- const response = await client.get(endpoint);
57
- return response.data.craft || response.data;
58
- } catch (error: any) {
59
- if (error.response?.status === 404) {
60
- logger.error(`Craft '${craftName}' not found in registry`);
61
- } else {
62
- logger.error(`Failed to fetch craft info: ${error.message}`);
63
- }
64
- return null;
65
- }
66
- }
67
-
68
- private async resolveRegistry(registry: string): Promise<string> {
69
- // If it's a URL, use it directly
70
- if (registry.startsWith('http://') || registry.startsWith('https://')) {
71
- return registry;
72
- }
73
-
74
- // Look it up in the craftdesk.json registries section
75
- const craftDeskJson = await configManager.getCraftDeskJson();
76
- if (craftDeskJson?.registries?.[registry]) {
77
- return craftDeskJson.registries[registry].url;
78
- }
79
-
80
- // If not found, assume it's a URL without protocol
81
- return `https://${registry}`;
82
- }
83
-
84
- async listVersions(craftName: string): Promise<string[]> {
85
- const registryUrl = await configManager.getRegistryForCraft(craftName);
86
- const client = await this.getClient(registryUrl);
87
-
88
- const [author, name] = this.parseCraftName(craftName);
89
-
90
- try {
91
- const response = await client.get(`/api/v1/crafts/${author}/${name}/versions`);
92
- return response.data.versions || [];
93
- } catch (error: any) {
94
- logger.error(`Failed to fetch versions: ${error.message}`);
95
- return [];
96
- }
97
- }
98
-
99
- async resolveDependencies(dependencies: Record<string, string>): Promise<ResolveResponse | null> {
100
- // Get the default registry from craftdesk.json or use fallback
101
- const craftDesk = await configManager.getCraftDeskJson();
102
- const registryUrl = craftDesk?.registries?.default?.url || 'https://craftdesk.ai';
103
-
104
- const client = await this.getClient(registryUrl);
105
-
106
- try {
107
- logger.debug('Resolving dependencies via API...');
108
- const response = await client.post('/api/v1/resolve', { dependencies });
109
- return response.data;
110
- } catch (error: any) {
111
- logger.error(`Failed to resolve dependencies: ${error.message}`);
112
- return null;
113
- }
114
- }
115
-
116
- async downloadCraft(downloadUrl: string, outputPath: string): Promise<void> {
117
- try {
118
- const response = await axios.get(downloadUrl, {
119
- responseType: 'stream'
120
- });
121
-
122
- // TODO: Save to file and extract
123
- logger.debug(`Downloaded craft from ${downloadUrl}`);
124
- } catch (error: any) {
125
- throw new Error(`Failed to download craft: ${error.message}`);
126
- }
127
- }
128
-
129
- async searchCrafts(query: string, type?: string): Promise<CraftInfo[]> {
130
- // Get the default registry from craftdesk.json or use fallback
131
- const craftDesk = await configManager.getCraftDeskJson();
132
- const registryUrl = craftDesk?.registries?.default?.url || 'https://craftdesk.ai';
133
-
134
- const client = await this.getClient(registryUrl);
135
-
136
- try {
137
- const params: any = { q: query };
138
- if (type) params.type = type;
139
-
140
- const response = await client.get('/api/v1/crafts', { params });
141
- return response.data.crafts || [];
142
- } catch (error: any) {
143
- logger.error(`Failed to search crafts: ${error.message}`);
144
- return [];
145
- }
146
- }
147
-
148
- private parseCraftName(craftName: string): [string, string] {
149
- if (craftName.startsWith('@')) {
150
- // Scoped craft: @acme/skill-name
151
- const parts = craftName.substring(1).split('/');
152
- if (parts.length === 2) {
153
- return [`@${parts[0]}`, parts[1]];
154
- }
155
- }
156
-
157
- // Assume author is 'anthropic' for unscoped crafts (default)
158
- // This should be configurable or determined by registry
159
- return ['anthropic', craftName];
160
- }
161
- }
162
-
163
- export const registryClient = new RegistryClient();
@@ -1,53 +0,0 @@
1
- export interface CraftDeskJson {
2
- name: string;
3
- version: string;
4
- type?: 'skill' | 'agent' | 'command' | 'hook';
5
- description?: string;
6
- author?: string;
7
- license?: string;
8
- homepage?: string;
9
- repository?: {
10
- type: string;
11
- url: string;
12
- };
13
- keywords?: string[];
14
-
15
- // Dependencies - type-agnostic (skills, agents, commands, hooks can all depend on each other)
16
- // Can be simple string versions or objects with version and registry
17
- dependencies?: Record<string, string | DependencyConfig>;
18
- devDependencies?: Record<string, string | DependencyConfig>;
19
- peerDependencies?: Record<string, string | DependencyConfig>;
20
- optionalDependencies?: Record<string, string | DependencyConfig>;
21
-
22
- // Registry configuration (optional, in-file configuration)
23
- registries?: Record<string, RegistryConfig>;
24
-
25
- // Commands and hooks
26
- commands?: Record<string, string | CommandConfig>;
27
- hooks?: Record<string, string[]>;
28
-
29
- // Index signature to allow dynamic property access
30
- [key: string]: any;
31
- }
32
-
33
- export interface DependencyConfig {
34
- version?: string; // Optional for git dependencies
35
- registry?: string; // Can be a registry name (from registries section) or direct URL
36
- git?: string; // Git repository URL
37
- branch?: string; // Git branch (default: main/master)
38
- tag?: string; // Git tag
39
- commit?: string; // Git commit hash
40
- path?: string; // Subdirectory path within the repository
41
- file?: string; // Direct file path within the repository
42
- }
43
-
44
- export interface RegistryConfig {
45
- url: string;
46
- scope?: string; // Optional scope like "@company"
47
- auth?: string; // Optional auth token (though better in ~/.craftrc for security)
48
- }
49
-
50
- export interface CommandConfig {
51
- version: string;
52
- config?: Record<string, any>;
53
- }
@@ -1,38 +0,0 @@
1
- export interface CraftDeskLock {
2
- version: string;
3
- lockfileVersion: number;
4
- generatedAt: string;
5
-
6
- crafts: Record<string, LockEntry>;
7
- tree?: DependencyTree;
8
- metadata?: {
9
- totalCrafts: number;
10
- totalSize: string;
11
- installPath: string;
12
- };
13
- }
14
-
15
- export interface LockEntry {
16
- version: string;
17
- resolved: string; // Download URL or git URL
18
- integrity: string; // SHA-256 hash (or commit hash for git)
19
- type: 'skill' | 'agent' | 'command' | 'hook';
20
- author?: string;
21
- registry?: string;
22
- scope?: string;
23
- dependencies?: Record<string, string>;
24
-
25
- // Git source fields
26
- git?: string; // Git repository URL
27
- branch?: string; // Git branch
28
- tag?: string; // Git tag
29
- commit?: string; // Git commit hash (resolved)
30
- path?: string; // Subdirectory path within repository
31
- file?: string; // Direct file path within repository
32
- }
33
-
34
- export interface DependencyTree {
35
- [key: string]: {
36
- dependencies?: DependencyTree;
37
- } | string; // String for "(shared)" marker
38
- }
@@ -1,55 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { CraftDeskJson } from '../types/craftdesk-json';
4
- import { CraftDeskLock } from '../types/craftdesk-lock';
5
-
6
- export async function readCraftDeskJson(dir: string = process.cwd()): Promise<CraftDeskJson | null> {
7
- const filePath = path.join(dir, 'craftdesk.json');
8
-
9
- try {
10
- const content = await fs.readFile(filePath, 'utf-8');
11
- return JSON.parse(content);
12
- } catch (error) {
13
- return null;
14
- }
15
- }
16
-
17
- export async function writeCraftDeskJson(data: CraftDeskJson, dir: string = process.cwd()): Promise<void> {
18
- const filePath = path.join(dir, 'craftdesk.json');
19
- const content = JSON.stringify(data, null, 2);
20
- await fs.writeFile(filePath, content, 'utf-8');
21
- }
22
-
23
- export async function readCraftDeskLock(dir: string = process.cwd()): Promise<CraftDeskLock | null> {
24
- const filePath = path.join(dir, 'craftdesk.lock');
25
-
26
- try {
27
- const content = await fs.readFile(filePath, 'utf-8');
28
- return JSON.parse(content);
29
- } catch (error) {
30
- return null;
31
- }
32
- }
33
-
34
- export async function writeCraftDeskLock(data: CraftDeskLock, dir: string = process.cwd()): Promise<void> {
35
- const filePath = path.join(dir, 'craftdesk.lock');
36
- const content = JSON.stringify(data, null, 2);
37
- await fs.writeFile(filePath, content, 'utf-8');
38
- }
39
-
40
- export async function ensureDir(dirPath: string): Promise<void> {
41
- await fs.ensureDir(dirPath);
42
- }
43
-
44
- export async function removeDir(dirPath: string): Promise<void> {
45
- await fs.remove(dirPath);
46
- }
47
-
48
- export async function exists(filePath: string): Promise<boolean> {
49
- try {
50
- await fs.access(filePath);
51
- return true;
52
- } catch {
53
- return false;
54
- }
55
- }
@@ -1,82 +0,0 @@
1
- // ANSI color codes
2
- const colors = {
3
- reset: '\x1b[0m',
4
- green: '\x1b[32m',
5
- red: '\x1b[31m',
6
- yellow: '\x1b[33m',
7
- blue: '\x1b[34m',
8
- cyan: '\x1b[36m',
9
- gray: '\x1b[90m',
10
- bold: '\x1b[1m',
11
- dim: '\x1b[2m',
12
- };
13
-
14
- export class Logger {
15
- private spinnerText: string | null = null;
16
-
17
- success(message: string): void {
18
- console.log(`${colors.green}✓${colors.reset}`, message);
19
- }
20
-
21
- error(message: string): void {
22
- console.error(`${colors.red}✗${colors.reset}`, message);
23
- }
24
-
25
- warn(message: string): void {
26
- console.log(`${colors.yellow}!${colors.reset}`, message);
27
- }
28
-
29
- info(message: string): void {
30
- console.log(`${colors.blue}→${colors.reset}`, message);
31
- }
32
-
33
- debug(message: string): void {
34
- if (process.env.DEBUG) {
35
- console.log(`${colors.gray}[DEBUG]${colors.reset}`, message);
36
- }
37
- }
38
-
39
- startSpinner(text: string): void {
40
- this.spinnerText = text;
41
- console.log(`${colors.cyan}⋯${colors.reset}`, text);
42
- }
43
-
44
- updateSpinner(text: string): void {
45
- if (this.spinnerText) {
46
- this.spinnerText = text;
47
- console.log(' ', text);
48
- }
49
- }
50
-
51
- succeedSpinner(text?: string): void {
52
- if (this.spinnerText) {
53
- console.log(`${colors.green}✓${colors.reset}`, text || this.spinnerText);
54
- this.spinnerText = null;
55
- }
56
- }
57
-
58
- failSpinner(text?: string): void {
59
- if (this.spinnerText) {
60
- console.log(`${colors.red}✗${colors.reset}`, text || this.spinnerText);
61
- this.spinnerText = null;
62
- }
63
- }
64
-
65
- stopSpinner(): void {
66
- this.spinnerText = null;
67
- }
68
-
69
- log(message: string): void {
70
- console.log(message);
71
- }
72
-
73
- bold(text: string): string {
74
- return `${colors.bold}${text}${colors.reset}`;
75
- }
76
-
77
- dim(text: string): string {
78
- return `${colors.dim}${text}${colors.reset}`;
79
- }
80
- }
81
-
82
- export const logger = new Logger();