magicpath-ai 1.3.0-beta.8 → 1.3.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 (54) hide show
  1. package/README.md +24 -26
  2. package/dist/cli.js +15 -8
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/add.d.ts +0 -3
  5. package/dist/commands/add.js +347 -246
  6. package/dist/commands/add.js.map +1 -1
  7. package/dist/commands/auth.js +14 -2
  8. package/dist/commands/auth.js.map +1 -1
  9. package/dist/commands/info.js +9 -5
  10. package/dist/commands/info.js.map +1 -1
  11. package/dist/commands/inspect.d.ts +53 -0
  12. package/dist/commands/inspect.js +122 -0
  13. package/dist/commands/inspect.js.map +1 -0
  14. package/dist/commands/list-installed.d.ts +2 -0
  15. package/dist/commands/list-installed.js +112 -0
  16. package/dist/commands/list-installed.js.map +1 -0
  17. package/dist/commands/list.js +6 -2
  18. package/dist/commands/list.js.map +1 -1
  19. package/dist/commands/schema.js +11 -24
  20. package/dist/commands/schema.js.map +1 -1
  21. package/dist/commands/search.js +2 -1
  22. package/dist/commands/search.js.map +1 -1
  23. package/dist/commands/setup-skills.d.ts +2 -0
  24. package/dist/commands/setup-skills.js +13 -0
  25. package/dist/commands/setup-skills.js.map +1 -0
  26. package/dist/commands/whoami.js +5 -13
  27. package/dist/commands/whoami.js.map +1 -1
  28. package/dist/util/auth.d.ts +19 -6
  29. package/dist/util/auth.js +70 -10
  30. package/dist/util/auth.js.map +1 -1
  31. package/dist/util/banner.d.ts +1 -0
  32. package/dist/util/banner.js +79 -0
  33. package/dist/util/banner.js.map +1 -0
  34. package/package.json +3 -5
  35. package/dist/commands/init.d.ts +0 -2
  36. package/dist/commands/init.js +0 -113
  37. package/dist/commands/init.js.map +0 -1
  38. package/dist/commands/integrate.d.ts +0 -2
  39. package/dist/commands/integrate.js +0 -202
  40. package/dist/commands/integrate.js.map +0 -1
  41. package/dist/commands/retheme.d.ts +0 -2
  42. package/dist/commands/retheme.js +0 -327
  43. package/dist/commands/retheme.js.map +0 -1
  44. package/dist/commands/skills.d.ts +0 -2
  45. package/dist/commands/skills.js +0 -55
  46. package/dist/commands/skills.js.map +0 -1
  47. package/dist/util/diff.d.ts +0 -13
  48. package/dist/util/diff.js +0 -63
  49. package/dist/util/diff.js.map +0 -1
  50. package/dist/util/integrate.d.ts +0 -84
  51. package/dist/util/integrate.js +0 -762
  52. package/dist/util/integrate.js.map +0 -1
  53. package/skills/cli-reference.md +0 -146
  54. package/skills/guide.md +0 -63
@@ -16,7 +16,6 @@ export const addOptionsSchema = z.object({
16
16
  path: z.string().optional(),
17
17
  debug: z.boolean().default(false),
18
18
  dryRun: z.boolean().default(false),
19
- inspect: z.boolean().default(false),
20
19
  });
21
20
  export const addOutputSchema = z.object({
22
21
  component: z.string(),
@@ -31,14 +30,13 @@ export function registerAddCommand(program) {
31
30
  program
32
31
  .command('add')
33
32
  .description('Add a MagicPath component to your project')
34
- .argument('<generatedName>', 'The generated name of the component (e.g., wispy-river-5234)')
33
+ .argument('<generatedNames...>', 'One or more generated names of components (e.g., wispy-river-5234 bold-moon-2031)')
35
34
  .option('-y, --yes', 'Skip confirmation prompts', false)
36
35
  .option('--overwrite', 'Overwrite existing files', false)
37
36
  .option('-p, --path <path>', `Custom path for components (default: ${DEFAULT_COMPONENTS_PATH})`)
38
37
  .option('-d, --debug', 'Enable debug logging', false)
39
38
  .option('--dry-run', 'Show what would happen without writing files', false)
40
- .option('--inspect', 'Show component source code without installing (implies --dry-run)', false)
41
- .action(async (generatedName, options) => {
39
+ .action(async (generatedNames, options) => {
42
40
  try {
43
41
  let addOptions;
44
42
  try {
@@ -49,16 +47,16 @@ export function registerAddCommand(program) {
49
47
  }
50
48
  if (addOptions.debug)
51
49
  enableDebugLogger();
52
- // --inspect implies --dry-run and --yes (no prompts, no writing)
53
- if (addOptions.inspect) {
54
- addOptions.dryRun = true;
55
- addOptions.yes = true;
56
- }
57
50
  // In JSON mode, imply --yes (skip prompts)
58
51
  const json = isJsonMode();
59
52
  if (json)
60
53
  addOptions.yes = true;
61
- logger.debug({ generatedName, options: addOptions });
54
+ const isBatch = generatedNames.length > 1;
55
+ // For batch mode, force --yes to avoid repetitive prompts
56
+ if (isBatch) {
57
+ addOptions.yes = true;
58
+ }
59
+ logger.debug({ generatedNames, options: addOptions });
62
60
  const cwd = process.cwd();
63
61
  // Check if we're in a valid project directory
64
62
  const packageJsonPath = path.join(cwd, 'package.json');
@@ -68,271 +66,374 @@ export function registerAddCommand(program) {
68
66
  suggestion: 'Run this command from the root of your project where package.json is located.',
69
67
  });
70
68
  }
71
- // Step 1: Fetch component from registry
72
- const fetchSpinner = json
73
- ? null
74
- : brandSpinner(`Fetching component "${generatedName}"...`).start();
75
- let componentData;
76
- try {
77
- componentData = await fetchComponent(generatedName);
78
- fetchSpinner?.succeed(`Found component: ${componentData.name}`);
69
+ // Batch results collector
70
+ const batchResults = [];
71
+ if (isBatch && !json) {
72
+ console.log(`\n Adding ${generatedNames.length} components...\n`);
79
73
  }
80
- catch (err) {
81
- if (err instanceof AuthRequiredError) {
82
- fetchSpinner?.fail('Not logged in');
83
- if (json) {
84
- throw new MagicPathError('Not authenticated. Set MAGICPATH_TOKEN or run `magicpath-ai login`.', {
85
- code: 'NOT_AUTHENTICATED',
86
- suggestion: 'Run `magicpath-ai login` or set the MAGICPATH_TOKEN environment variable.',
87
- });
74
+ for (const generatedName of generatedNames) {
75
+ try {
76
+ // Step 1: Fetch component from registry
77
+ const fetchSpinner = json
78
+ ? null
79
+ : brandSpinner(`Fetching component "${generatedName}"...`).start();
80
+ let componentData;
81
+ try {
82
+ componentData = await fetchComponent(generatedName);
83
+ fetchSpinner?.succeed(`Found component: ${componentData.name}`);
88
84
  }
89
- const { shouldLogin } = await prompts({
90
- type: 'confirm',
91
- name: 'shouldLogin',
92
- message: 'You need to be logged in to install components. Log in now?',
93
- initial: true,
85
+ catch (err) {
86
+ if (err instanceof AuthRequiredError) {
87
+ fetchSpinner?.fail('Not logged in');
88
+ if (json || isBatch) {
89
+ throw new MagicPathError('Not authenticated. Set MAGICPATH_TOKEN or run `magicpath-ai login`.', {
90
+ code: 'NOT_AUTHENTICATED',
91
+ suggestion: 'Run `magicpath-ai login` or set the MAGICPATH_TOKEN environment variable.',
92
+ });
93
+ }
94
+ const { shouldLogin } = await prompts({
95
+ type: 'confirm',
96
+ name: 'shouldLogin',
97
+ message: 'You need to be logged in to install components. Log in now?',
98
+ initial: true,
99
+ });
100
+ if (!shouldLogin) {
101
+ console.log('Installation cancelled.');
102
+ return;
103
+ }
104
+ const loggedIn = await runLoginFlow();
105
+ if (!loggedIn) {
106
+ console.log('Installation cancelled.');
107
+ return;
108
+ }
109
+ // Retry fetch after login
110
+ const retrySpinner = brandSpinner(`Fetching component "${generatedName}"...`).start();
111
+ try {
112
+ componentData = await fetchComponent(generatedName);
113
+ retrySpinner.succeed(`Found component: ${componentData.name}`);
114
+ }
115
+ catch (retryErr) {
116
+ retrySpinner.fail('Failed to fetch component');
117
+ if (retryErr instanceof MagicPathError) {
118
+ throw retryErr;
119
+ }
120
+ throw new MagicPathError('Something went wrong!');
121
+ }
122
+ }
123
+ else {
124
+ fetchSpinner?.fail('Failed to fetch component');
125
+ if (isBatch) {
126
+ const errMsg = err instanceof MagicPathError
127
+ ? err.message
128
+ : 'Something went wrong!';
129
+ batchResults.push({
130
+ component: generatedName,
131
+ generatedName,
132
+ filesWritten: [],
133
+ dependenciesInstalled: [],
134
+ dryRun: addOptions.dryRun,
135
+ error: errMsg,
136
+ });
137
+ if (!json)
138
+ console.log(` āš ļø Skipping "${generatedName}": ${errMsg}`);
139
+ continue;
140
+ }
141
+ if (err instanceof MagicPathError) {
142
+ throw err;
143
+ }
144
+ throw new MagicPathError('Something went wrong!');
145
+ }
146
+ }
147
+ // Resolve paths - include component-specific subfolder
148
+ const basePath = resolveComponentsPath(addOptions.path, cwd);
149
+ const componentFolderName = sanitizeComponentFolderName(componentData.name);
150
+ const componentsPath = path.join(basePath, componentFolderName);
151
+ const utilsPath = resolveUtilsPath(cwd);
152
+ logger.debug({
153
+ basePath,
154
+ componentFolderName,
155
+ componentsPath,
156
+ utilsPath,
94
157
  });
95
- if (!shouldLogin) {
96
- console.log('Installation cancelled.');
97
- return;
158
+ // Step 2: Check for existing files
159
+ const existingFiles = checkExistingFiles(componentData.files, componentsPath);
160
+ if (existingFiles.length > 0 && !addOptions.overwrite) {
161
+ if (!json) {
162
+ console.log('\nThe following files already exist:');
163
+ existingFiles.forEach((file) => console.log(` - ${file}`));
164
+ }
165
+ if (!addOptions.yes) {
166
+ const { shouldOverwrite } = await prompts({
167
+ type: 'confirm',
168
+ name: 'shouldOverwrite',
169
+ message: 'Do you want to overwrite these files?',
170
+ initial: false,
171
+ });
172
+ if (!shouldOverwrite) {
173
+ console.log('Installation cancelled.');
174
+ return;
175
+ }
176
+ }
177
+ else {
178
+ if (isBatch) {
179
+ if (!json)
180
+ console.log(` āš ļø Skipping "${componentData.name}": files already exist. Use --overwrite.`);
181
+ batchResults.push({
182
+ component: componentData.name,
183
+ generatedName: componentData.generatedName,
184
+ filesWritten: [],
185
+ dependenciesInstalled: [],
186
+ dryRun: addOptions.dryRun,
187
+ error: 'Files already exist. Use --overwrite to replace.',
188
+ });
189
+ continue;
190
+ }
191
+ if (json) {
192
+ throw new MagicPathError(`Files already exist: ${existingFiles.join(', ')}. Use --overwrite to replace.`, {
193
+ code: 'FILES_EXIST',
194
+ suggestion: 'Pass `--overwrite` to replace existing files.',
195
+ });
196
+ }
197
+ console.log('Use --overwrite to replace existing files.');
198
+ return;
199
+ }
98
200
  }
99
- const loggedIn = await runLoginFlow();
100
- if (!loggedIn) {
101
- console.log('Installation cancelled.');
102
- return;
201
+ // Step 3: Confirm installation (unless --yes flag)
202
+ if (!addOptions.yes && existingFiles.length === 0) {
203
+ console.log(`\nThis will install:`);
204
+ console.log(` Component: ${componentData.name}`);
205
+ console.log(` Files: ${componentData.files.length} file(s) to ${path.relative(cwd, componentsPath) || '.'}`);
206
+ console.log(` Dependencies: ${componentData.dependencies.length} package(s)`);
207
+ const { shouldProceed } = await prompts({
208
+ type: 'confirm',
209
+ name: 'shouldProceed',
210
+ message: 'Proceed with installation?',
211
+ initial: true,
212
+ });
213
+ if (!shouldProceed) {
214
+ console.log('Installation cancelled.');
215
+ return;
216
+ }
103
217
  }
104
- // Retry fetch after login
105
- const retrySpinner = brandSpinner(`Fetching component "${generatedName}"...`).start();
106
- try {
107
- componentData = await fetchComponent(generatedName);
108
- retrySpinner.succeed(`Found component: ${componentData.name}`);
218
+ // Build result data for JSON output
219
+ const filesWritten = componentData.files.map((f) => path.relative(cwd, path.join(componentsPath, f.path)));
220
+ // Extract import info
221
+ const mainFile = componentData.files[0];
222
+ let importStatement;
223
+ let usageStr;
224
+ if (mainFile) {
225
+ const fileName = mainFile.name.replace(/\.tsx?$/, '');
226
+ const importPath = addOptions.path
227
+ ? `@/${addOptions.path}/${componentFolderName}/${fileName}`
228
+ : `@/components/magicpath/${componentFolderName}/${fileName}`;
229
+ const exportInfo = extractComponentExports(mainFile.content);
230
+ if (exportInfo) {
231
+ importStatement = generateImportStatement(exportInfo, importPath);
232
+ usageStr = generateUsageExample(exportInfo);
233
+ }
234
+ else {
235
+ importStatement = `import { ${fileName} } from '${importPath}';`;
236
+ }
109
237
  }
110
- catch (retryErr) {
111
- retrySpinner.fail('Failed to fetch component');
112
- if (retryErr instanceof MagicPathError) {
113
- throw retryErr;
238
+ // Dry run: return what would happen without writing
239
+ if (addOptions.dryRun) {
240
+ const fileContents = json
241
+ ? componentData.files.map((f) => ({
242
+ path: f.path,
243
+ name: f.name,
244
+ content: f.content,
245
+ }))
246
+ : undefined;
247
+ if (!isBatch && json) {
248
+ jsonResult({
249
+ component: componentData.name,
250
+ generatedName: componentData.generatedName,
251
+ filesWritten,
252
+ files: fileContents,
253
+ dependenciesInstalled: componentData.dependencies,
254
+ importStatement,
255
+ usage: usageStr,
256
+ dryRun: true,
257
+ });
114
258
  }
115
- throw new MagicPathError('Something went wrong!');
259
+ batchResults.push({
260
+ component: componentData.name,
261
+ generatedName: componentData.generatedName,
262
+ filesWritten,
263
+ dependenciesInstalled: componentData.dependencies,
264
+ importStatement,
265
+ usage: usageStr,
266
+ dryRun: true,
267
+ });
268
+ if (!json) {
269
+ console.log('\n[Dry run] Would install:');
270
+ console.log(` Component: ${componentData.name}`);
271
+ console.log(` Files: ${filesWritten.join(', ')}`);
272
+ console.log(` Dependencies: ${componentData.dependencies.join(', ') || 'none'}`);
273
+ if (importStatement)
274
+ console.log(` Import: ${importStatement}`);
275
+ }
276
+ continue;
116
277
  }
117
- }
118
- else {
119
- fetchSpinner?.fail('Failed to fetch component');
120
- if (err instanceof MagicPathError) {
121
- throw err;
278
+ // Step 4: Write component files
279
+ const writeSpinner = json
280
+ ? null
281
+ : brandSpinner('Writing component files...').start();
282
+ try {
283
+ writeComponentFiles(componentData.files, componentsPath, true);
284
+ writeSpinner?.succeed(`Wrote ${componentData.files.length} file(s) to ${path.relative(cwd, componentsPath) || '.'}`);
122
285
  }
123
- throw new MagicPathError('Something went wrong!');
124
- }
125
- }
126
- // Resolve paths - include component-specific subfolder
127
- const basePath = resolveComponentsPath(addOptions.path, cwd);
128
- const componentFolderName = sanitizeComponentFolderName(componentData.name);
129
- const componentsPath = path.join(basePath, componentFolderName);
130
- const utilsPath = resolveUtilsPath(cwd);
131
- logger.debug({
132
- basePath,
133
- componentFolderName,
134
- componentsPath,
135
- utilsPath,
136
- });
137
- // Step 2: Check for existing files
138
- const existingFiles = checkExistingFiles(componentData.files, componentsPath);
139
- if (existingFiles.length > 0 && !addOptions.overwrite) {
140
- if (!json) {
141
- console.log('\nThe following files already exist:');
142
- existingFiles.forEach((file) => console.log(` - ${file}`));
143
- }
144
- if (!addOptions.yes) {
145
- const { shouldOverwrite } = await prompts({
146
- type: 'confirm',
147
- name: 'shouldOverwrite',
148
- message: 'Do you want to overwrite these files?',
149
- initial: false,
150
- });
151
- if (!shouldOverwrite) {
152
- console.log('Installation cancelled.');
153
- return;
286
+ catch (err) {
287
+ writeSpinner?.fail('Failed to write component files');
288
+ if (isBatch) {
289
+ batchResults.push({
290
+ component: componentData.name,
291
+ generatedName: componentData.generatedName,
292
+ filesWritten: [],
293
+ dependenciesInstalled: [],
294
+ dryRun: false,
295
+ error: 'Failed to write component files.',
296
+ });
297
+ continue;
298
+ }
299
+ throw new MagicPathError('Failed to write component files. Please check permissions and try again.');
154
300
  }
155
- }
156
- else {
157
- if (json) {
158
- throw new MagicPathError(`Files already exist: ${existingFiles.join(', ')}. Use --overwrite to replace.`, {
159
- code: 'FILES_EXIST',
160
- suggestion: 'Pass `--overwrite` to replace existing files.',
161
- });
301
+ // Step 5: Install utils if needed
302
+ const utilsExists = checkUtilsExists(utilsPath);
303
+ if (!utilsExists && componentData.utils.content) {
304
+ const utilsSpinner = json
305
+ ? null
306
+ : brandSpinner('Installing utilities...').start();
307
+ try {
308
+ writeUtilsFile(componentData.utils.content, utilsPath);
309
+ utilsSpinner?.succeed(`Wrote utils.ts to ${path.relative(cwd, utilsPath) || '.'}`);
310
+ }
311
+ catch (err) {
312
+ utilsSpinner?.fail('Failed to write utils file');
313
+ logger.debug({ err });
314
+ if (!json) {
315
+ console.log(' Warning: Could not write utils.ts. You may need to create it manually.');
316
+ }
317
+ }
162
318
  }
163
- console.log('Use --overwrite to replace existing files.');
164
- return;
165
- }
166
- }
167
- // Step 3: Confirm installation (unless --yes flag)
168
- if (!addOptions.yes && existingFiles.length === 0) {
169
- console.log(`\nThis will install:`);
170
- console.log(` Component: ${componentData.name}`);
171
- console.log(` Files: ${componentData.files.length} file(s) to ${path.relative(cwd, componentsPath) || '.'}`);
172
- console.log(` Dependencies: ${componentData.dependencies.length} package(s)`);
173
- const { shouldProceed } = await prompts({
174
- type: 'confirm',
175
- name: 'shouldProceed',
176
- message: 'Proceed with installation?',
177
- initial: true,
178
- });
179
- if (!shouldProceed) {
180
- console.log('Installation cancelled.');
181
- return;
182
- }
183
- }
184
- // Build result data for JSON output
185
- const filesWritten = componentData.files.map((f) => path.relative(cwd, path.join(componentsPath, f.path)));
186
- // Extract import info
187
- const mainFile = componentData.files[0];
188
- let importStatement;
189
- let usageStr;
190
- if (mainFile) {
191
- const fileName = mainFile.name.replace(/\.tsx?$/, '');
192
- const importPath = addOptions.path
193
- ? `@/${addOptions.path}/${componentFolderName}/${fileName}`
194
- : `@/components/magicpath/${componentFolderName}/${fileName}`;
195
- const exportInfo = extractComponentExports(mainFile.content);
196
- if (exportInfo) {
197
- importStatement = generateImportStatement(exportInfo, importPath);
198
- usageStr = generateUsageExample(exportInfo);
199
- }
200
- else {
201
- importStatement = `import { ${fileName} } from '${importPath}';`;
202
- }
203
- }
204
- // Dry run: return what would happen without writing
205
- if (addOptions.dryRun) {
206
- const fileContents = addOptions.inspect || json
207
- ? componentData.files.map((f) => ({
208
- path: f.path,
209
- name: f.name,
210
- content: f.content,
211
- }))
212
- : undefined;
213
- if (json) {
214
- jsonResult({
319
+ // Step 6: Install dependencies
320
+ const installedDeps = [];
321
+ if (componentData.dependencies.length > 0) {
322
+ try {
323
+ const { installed, skipped } = installPackages(componentData.dependencies, cwd);
324
+ installedDeps.push(...installed);
325
+ if (skipped.length > 0) {
326
+ logger.debug({
327
+ message: 'Skipped already installed packages',
328
+ skipped,
329
+ });
330
+ }
331
+ }
332
+ catch (err) {
333
+ if (err instanceof MagicPathError) {
334
+ if (!json)
335
+ console.log(`\nāš ļø ${err.message}`);
336
+ }
337
+ }
338
+ }
339
+ batchResults.push({
215
340
  component: componentData.name,
216
341
  generatedName: componentData.generatedName,
217
342
  filesWritten,
218
- files: fileContents,
219
- dependenciesInstalled: componentData.dependencies,
343
+ dependenciesInstalled: installedDeps,
220
344
  importStatement,
221
345
  usage: usageStr,
222
- dryRun: true,
346
+ dryRun: false,
223
347
  });
224
- }
225
- console.log('\n[Dry run] Would install:');
226
- console.log(` Component: ${componentData.name}`);
227
- console.log(` Files: ${filesWritten.join(', ')}`);
228
- console.log(` Dependencies: ${componentData.dependencies.join(', ') || 'none'}`);
229
- if (importStatement)
230
- console.log(` Import: ${importStatement}`);
231
- // --inspect: show file contents
232
- if (addOptions.inspect) {
233
- for (const f of componentData.files) {
234
- console.log(`\n${'─'.repeat(60)}`);
235
- console.log(` ${f.path}`);
236
- console.log(`${'─'.repeat(60)}`);
237
- console.log(f.content);
348
+ // Single-component JSON output (non-batch)
349
+ if (!isBatch && json) {
350
+ jsonResult({
351
+ component: componentData.name,
352
+ generatedName: componentData.generatedName,
353
+ filesWritten,
354
+ dependenciesInstalled: installedDeps,
355
+ importStatement,
356
+ usage: usageStr,
357
+ dryRun: false,
358
+ });
238
359
  }
239
- }
240
- return;
241
- }
242
- // Step 4: Write component files
243
- const writeSpinner = json
244
- ? null
245
- : brandSpinner('Writing component files...').start();
246
- try {
247
- writeComponentFiles(componentData.files, componentsPath, true);
248
- writeSpinner?.succeed(`Wrote ${componentData.files.length} file(s) to ${path.relative(cwd, componentsPath) || '.'}`);
249
- }
250
- catch (err) {
251
- writeSpinner?.fail('Failed to write component files');
252
- throw new MagicPathError('Failed to write component files. Please check permissions and try again.');
253
- }
254
- // Step 5: Install utils if needed
255
- const utilsExists = checkUtilsExists(utilsPath);
256
- if (!utilsExists && componentData.utils.content) {
257
- const utilsSpinner = json
258
- ? null
259
- : brandSpinner('Installing utilities...').start();
260
- try {
261
- writeUtilsFile(componentData.utils.content, utilsPath);
262
- utilsSpinner?.succeed(`Wrote utils.ts to ${path.relative(cwd, utilsPath) || '.'}`);
263
- }
264
- catch (err) {
265
- utilsSpinner?.fail('Failed to write utils file');
266
- logger.debug({ err });
360
+ // Step 7: Show success message and write usage file
267
361
  if (!json) {
268
- console.log(' Warning: Could not write utils.ts. You may need to create it manually.');
362
+ console.log(`\nāœ… Successfully added "${componentData.name}" to your project!`);
363
+ if (!isBatch) {
364
+ console.log(`\nUsage:`);
365
+ }
269
366
  }
270
- }
271
- }
272
- // Step 6: Install dependencies
273
- const installedDeps = [];
274
- if (componentData.dependencies.length > 0) {
275
- try {
276
- const { installed, skipped } = installPackages(componentData.dependencies, cwd);
277
- installedDeps.push(...installed);
278
- if (skipped.length > 0) {
279
- logger.debug({
280
- message: 'Skipped already installed packages',
281
- skipped,
282
- });
367
+ if (mainFile) {
368
+ const fileName = mainFile.name.replace(/\.tsx?$/, '');
369
+ const importPath = addOptions.path
370
+ ? `@/${addOptions.path}/${componentFolderName}/${fileName}`
371
+ : `@/components/magicpath/${componentFolderName}/${fileName}`;
372
+ const exportInfo = extractComponentExports(mainFile.content);
373
+ if (!isBatch && !json) {
374
+ if (exportInfo) {
375
+ console.log(` ${generateImportStatement(exportInfo, importPath)}`);
376
+ console.log('');
377
+ console.log(` ${generateUsageExample(exportInfo)}`);
378
+ if (exportInfo.requiredProps.length > 0) {
379
+ console.log('');
380
+ console.log(` Required props: ${exportInfo.requiredProps.join(', ')}`);
381
+ }
382
+ }
383
+ else {
384
+ console.log(` import { ${fileName} } from '${importPath}';`);
385
+ }
386
+ }
387
+ try {
388
+ writeUsageFile(componentsPath, {
389
+ componentName: componentData.name,
390
+ importPath,
391
+ exportInfo,
392
+ fileName,
393
+ });
394
+ if (!isBatch && !json) {
395
+ console.log(`\nšŸ“„ Usage documentation written to ${path.relative(cwd, path.join(componentsPath, 'usage.md'))}`);
396
+ }
397
+ }
398
+ catch (err) {
399
+ logger.debug({ err });
400
+ }
283
401
  }
284
402
  }
285
403
  catch (err) {
286
- if (err instanceof MagicPathError) {
404
+ // In batch mode, record error and continue
405
+ if (isBatch) {
406
+ const errMsg = err instanceof Error ? err.message : String(err);
407
+ batchResults.push({
408
+ component: generatedName,
409
+ generatedName,
410
+ filesWritten: [],
411
+ dependenciesInstalled: [],
412
+ dryRun: addOptions.dryRun,
413
+ error: errMsg,
414
+ });
287
415
  if (!json)
288
- console.log(`\nāš ļø ${err.message}`);
416
+ console.log(` āš ļø Failed "${generatedName}": ${errMsg}`);
417
+ continue;
289
418
  }
419
+ throw err;
290
420
  }
291
421
  }
292
- // JSON output
293
- if (json) {
294
- jsonResult({
295
- component: componentData.name,
296
- generatedName: componentData.generatedName,
297
- filesWritten,
298
- dependenciesInstalled: installedDeps,
299
- importStatement,
300
- usage: usageStr,
301
- dryRun: false,
302
- });
303
- }
304
- // Step 7: Show success message and write usage file
305
- console.log(`\nāœ… Successfully added "${componentData.name}" to your project!`);
306
- console.log(`\nUsage:`);
307
- if (mainFile) {
308
- const fileName = mainFile.name.replace(/\.tsx?$/, '');
309
- const importPath = addOptions.path
310
- ? `@/${addOptions.path}/${componentFolderName}/${fileName}`
311
- : `@/components/magicpath/${componentFolderName}/${fileName}`;
312
- const exportInfo = extractComponentExports(mainFile.content);
313
- if (exportInfo) {
314
- console.log(` ${generateImportStatement(exportInfo, importPath)}`);
315
- console.log('');
316
- console.log(` ${generateUsageExample(exportInfo)}`);
317
- if (exportInfo.requiredProps.length > 0) {
318
- console.log('');
319
- console.log(` Required props: ${exportInfo.requiredProps.join(', ')}`);
320
- }
321
- }
322
- else {
323
- console.log(` import { ${fileName} } from '${importPath}';`);
324
- }
325
- try {
326
- writeUsageFile(componentsPath, {
327
- componentName: componentData.name,
328
- importPath,
329
- exportInfo,
330
- fileName,
422
+ // Batch summary
423
+ if (isBatch) {
424
+ if (json) {
425
+ jsonResult({
426
+ results: batchResults,
427
+ total: batchResults.length,
428
+ succeeded: batchResults.filter((r) => !r.error).length,
429
+ failed: batchResults.filter((r) => r.error).length,
331
430
  });
332
- console.log(`\nšŸ“„ Usage documentation written to ${path.relative(cwd, path.join(componentsPath, 'usage.md'))}`);
333
431
  }
334
- catch (err) {
335
- logger.debug({ err });
432
+ const succeeded = batchResults.filter((r) => !r.error);
433
+ const failed = batchResults.filter((r) => r.error);
434
+ console.log(`\n ${succeeded.length} of ${batchResults.length} component(s) added successfully.`);
435
+ if (failed.length > 0) {
436
+ console.log(` ${failed.length} failed: ${failed.map((f) => f.generatedName).join(', ')}`);
336
437
  }
337
438
  }
338
439
  }