cli4ai 0.8.2 → 0.9.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.
@@ -9,7 +9,7 @@ import { createManifest, MANIFEST_FILENAME, type Manifest } from '../core/manife
9
9
 
10
10
  interface InitOptions {
11
11
  template?: string;
12
- runtime?: 'bun' | 'node';
12
+ runtime?: 'node' | 'bun';
13
13
  yes?: boolean;
14
14
  }
15
15
 
@@ -18,7 +18,7 @@ export async function initCommand(name: string | undefined, options: InitOptions
18
18
  const targetDir = name ? resolve(process.cwd(), name) : process.cwd();
19
19
  const manifestPath = resolve(targetDir, MANIFEST_FILENAME);
20
20
  const templateName = options.template || 'basic';
21
- const runtime = options.runtime || 'bun';
21
+ const runtime = options.runtime || 'node';
22
22
 
23
23
  // Check if cli4ai.json already exists
24
24
  if (existsSync(manifestPath)) {
@@ -46,7 +46,7 @@ export async function initCommand(name: string | undefined, options: InitOptions
46
46
  // Create entry file if it doesn't exist
47
47
  const entryPath = resolve(targetDir, manifest.entry);
48
48
  if (!existsSync(entryPath)) {
49
- const template = getTemplate(templateName, runtime, manifest);
49
+ const template = getTemplate(templateName, manifest);
50
50
  writeFileSync(entryPath, template);
51
51
  log(`Created ${manifest.entry}`);
52
52
  }
@@ -54,7 +54,7 @@ export async function initCommand(name: string | undefined, options: InitOptions
54
54
  // Create helpful extras (non-destructive)
55
55
  const readmePath = resolve(targetDir, 'README.md');
56
56
  if (!existsSync(readmePath)) {
57
- writeFileSync(readmePath, getReadmeTemplate(templateName, runtime, manifest));
57
+ writeFileSync(readmePath, getReadmeTemplate(templateName, manifest));
58
58
  log('Created README.md');
59
59
  }
60
60
 
@@ -66,11 +66,32 @@ export async function initCommand(name: string | undefined, options: InitOptions
66
66
 
67
67
  const pkgJsonPath = resolve(targetDir, 'package.json');
68
68
  if (!existsSync(pkgJsonPath)) {
69
- const pkgJson = getDevPackageJson(templateName, runtime, manifest);
69
+ const pkgJson = getDevPackageJson(templateName, manifest);
70
70
  writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n');
71
71
  log('Created package.json');
72
72
  }
73
73
 
74
+ // Create test file
75
+ const testPath = resolve(targetDir, manifest.entry.replace(/\.ts$/, '.test.ts'));
76
+ if (!existsSync(testPath)) {
77
+ writeFileSync(testPath, getTestTemplate(templateName, manifest));
78
+ log(`Created ${basename(testPath)}`);
79
+ }
80
+
81
+ // Create tsconfig.json for TypeScript support
82
+ const tsconfigPath = resolve(targetDir, 'tsconfig.json');
83
+ if (!existsSync(tsconfigPath)) {
84
+ writeFileSync(tsconfigPath, getTsconfigTemplate());
85
+ log('Created tsconfig.json');
86
+ }
87
+
88
+ // Create vitest.config.ts
89
+ const vitestConfigPath = resolve(targetDir, 'vitest.config.ts');
90
+ if (!existsSync(vitestConfigPath)) {
91
+ writeFileSync(vitestConfigPath, getVitestConfigTemplate());
92
+ log('Created vitest.config.ts');
93
+ }
94
+
74
95
  // Template-specific extras
75
96
  if (templateName === 'api') {
76
97
  const envExamplePath = resolve(targetDir, '.env.example');
@@ -80,14 +101,17 @@ export async function initCommand(name: string | undefined, options: InitOptions
80
101
  }
81
102
  }
82
103
 
83
- const nextSteps = getNextSteps(name, targetDir, templateName, runtime, manifest);
104
+ const nextSteps = getNextSteps(name, targetDir, templateName, manifest);
84
105
 
85
106
  output({
86
107
  created: {
87
108
  manifest: manifestPath,
88
109
  entry: entryPath,
110
+ test: testPath,
89
111
  readme: readmePath,
90
112
  gitignore: gitignorePath,
113
+ tsconfig: tsconfigPath,
114
+ vitestConfig: vitestConfigPath,
91
115
  packageJson: existsSync(resolve(targetDir, 'package.json')) ? resolve(targetDir, 'package.json') : undefined,
92
116
  },
93
117
  name: manifest.name,
@@ -109,7 +133,7 @@ function guessAuthor(): string | undefined {
109
133
  return candidates[0];
110
134
  }
111
135
 
112
- function getBaseManifest(templateName: string, runtime: 'bun' | 'node'): Partial<Manifest> {
136
+ function getBaseManifest(templateName: string, runtime: 'node' | 'bun'): Partial<Manifest> {
113
137
  const author = guessAuthor();
114
138
  const keywords = Array.from(new Set([
115
139
  'cli4ai',
@@ -118,7 +142,7 @@ function getBaseManifest(templateName: string, runtime: 'bun' | 'node'): Partial
118
142
  ]));
119
143
 
120
144
  return {
121
- entry: runtime === 'node' ? 'run.mjs' : 'run.ts',
145
+ entry: 'run.ts',
122
146
  runtime,
123
147
  description: `${templateName === 'basic' ? 'CLI' : templateName} tool`,
124
148
  author,
@@ -128,7 +152,7 @@ function getBaseManifest(templateName: string, runtime: 'bun' | 'node'): Partial
128
152
  };
129
153
  }
130
154
 
131
- function getTemplateManifest(templateName: string, runtime: 'bun' | 'node'): Partial<Manifest> {
155
+ function getTemplateManifest(templateName: string, runtime: 'node' | 'bun'): Partial<Manifest> {
132
156
  switch (templateName) {
133
157
  case 'api':
134
158
  return {
@@ -144,7 +168,7 @@ function getTemplateManifest(templateName: string, runtime: 'bun' | 'node'): Par
144
168
  description: 'API key for the service you are calling (stored via `cli4ai secrets`)',
145
169
  }
146
170
  },
147
- dependencies: getDefaultDependencies(templateName, runtime),
171
+ dependencies: getDefaultDependencies(templateName),
148
172
  };
149
173
  case 'browser':
150
174
  return {
@@ -160,7 +184,7 @@ function getTemplateManifest(templateName: string, runtime: 'bun' | 'node'): Par
160
184
  ]
161
185
  }
162
186
  },
163
- dependencies: getDefaultDependencies(templateName, runtime),
187
+ dependencies: getDefaultDependencies(templateName),
164
188
  };
165
189
  default:
166
190
  return {
@@ -170,23 +194,16 @@ function getTemplateManifest(templateName: string, runtime: 'bun' | 'node'): Par
170
194
  args: [{ name: 'name', required: false }]
171
195
  }
172
196
  },
173
- dependencies: getDefaultDependencies(templateName, runtime),
197
+ dependencies: getDefaultDependencies(templateName),
174
198
  };
175
199
  }
176
200
  }
177
201
 
178
- function getDefaultDependencies(
179
- templateName: string,
180
- runtime: 'bun' | 'node'
181
- ): Record<string, string> | undefined {
182
- const deps: Record<string, string> = {};
183
-
184
- // Prefer shared SDK helpers when running on bun (or node with TS support, if that lands later).
185
- if (runtime === 'bun') {
186
- deps['@cli4ai/lib'] = '^1.0.0';
187
- }
188
-
189
- deps['commander'] = '^14.0.0';
202
+ function getDefaultDependencies(templateName: string): Record<string, string> | undefined {
203
+ const deps: Record<string, string> = {
204
+ '@cli4ai/lib': '^1.0.0',
205
+ 'commander': '^14.0.0',
206
+ };
190
207
 
191
208
  if (templateName === 'browser') {
192
209
  deps['puppeteer'] = '^24.0.0';
@@ -195,28 +212,24 @@ function getDefaultDependencies(
195
212
  return deps;
196
213
  }
197
214
 
198
- function getTemplate(templateName: string, runtime: 'bun' | 'node', manifest: Manifest): string {
199
- if (runtime === 'node') {
200
- return getNodeTemplate(templateName, manifest);
201
- }
202
-
215
+ function getTemplate(templateName: string, manifest: Manifest): string {
203
216
  switch (templateName) {
204
217
  case 'api':
205
- return getBunApiTemplate(manifest);
218
+ return getApiTemplate(manifest);
206
219
  case 'browser':
207
- return getBunBrowserTemplate(manifest);
220
+ return getBrowserTemplate(manifest);
208
221
  default:
209
- return getBunBasicTemplate(manifest);
222
+ return getBasicTemplate(manifest);
210
223
  }
211
224
  }
212
225
 
213
- function getBunBasicTemplate(manifest: Manifest): string {
214
- return `#!/usr/bin/env bun
226
+ function getBasicTemplate(manifest: Manifest): string {
227
+ return `#!/usr/bin/env npx tsx
215
228
  /**
216
229
  * ${manifest.name} - ${manifest.description || 'A cli4ai tool'}
217
230
  */
218
231
 
219
- import { cli, output } from '@cli4ai/lib/cli.ts';
232
+ import { cli, output } from '@cli4ai/lib';
220
233
 
221
234
  const program = cli('${manifest.name}', '${manifest.version}', '${manifest.description || manifest.name}');
222
235
 
@@ -232,13 +245,13 @@ program.parse();
232
245
  `;
233
246
  }
234
247
 
235
- function getBunApiTemplate(manifest: Manifest): string {
236
- return `#!/usr/bin/env bun
248
+ function getApiTemplate(manifest: Manifest): string {
249
+ return `#!/usr/bin/env npx tsx
237
250
  /**
238
251
  * ${manifest.name} - ${manifest.description || 'API wrapper tool'}
239
252
  */
240
253
 
241
- import { cli, env, output, outputError, withErrorHandling } from '@cli4ai/lib/cli.ts';
254
+ import { cli, env, output, outputError, withErrorHandling } from '@cli4ai/lib';
242
255
 
243
256
  const program = cli('${manifest.name}', '${manifest.version}', '${manifest.description || manifest.name}');
244
257
 
@@ -249,33 +262,29 @@ program
249
262
  // Use \`cli4ai secrets set API_KEY\` to store this securely (cli4ai injects it at runtime).
250
263
  const apiKey = env('API_KEY');
251
264
 
252
- try {
253
- const res = await fetch(\`https://api.example.com/\${endpoint}\`, {
254
- headers: { 'Authorization': \`Bearer \${apiKey}\` }
255
- });
256
-
257
- if (!res.ok) {
258
- outputError('API_ERROR', \`HTTP \${res.status}: \${res.statusText}\`);
259
- }
265
+ const res = await fetch(\`https://api.example.com/\${endpoint}\`, {
266
+ headers: { 'Authorization': \`Bearer \${apiKey}\` }
267
+ });
260
268
 
261
- output(await res.json());
262
- } catch (err) {
263
- outputError('NETWORK_ERROR', err instanceof Error ? err.message : String(err));
269
+ if (!res.ok) {
270
+ outputError('API_ERROR', \`HTTP \${res.status}: \${res.statusText}\`);
264
271
  }
272
+
273
+ output(await res.json());
265
274
  }));
266
275
 
267
276
  program.parse();
268
277
  `;
269
278
  }
270
279
 
271
- function getBunBrowserTemplate(manifest: Manifest): string {
272
- return `#!/usr/bin/env bun
280
+ function getBrowserTemplate(manifest: Manifest): string {
281
+ return `#!/usr/bin/env npx tsx
273
282
  /**
274
283
  * ${manifest.name} - ${manifest.description || 'Browser automation tool'}
275
284
  */
276
285
 
277
286
  import puppeteer from 'puppeteer';
278
- import { cli, output, outputError, withErrorHandling } from '@cli4ai/lib/cli.ts';
287
+ import { cli, output, outputError, withErrorHandling } from '@cli4ai/lib';
279
288
 
280
289
  const program = cli('${manifest.name}', '${manifest.version}', '${manifest.description || manifest.name}');
281
290
 
@@ -297,161 +306,6 @@ program
297
306
  screenshot: options.output,
298
307
  timestamp: new Date().toISOString()
299
308
  });
300
- } catch (err) {
301
- outputError('API_ERROR', err instanceof Error ? err.message : String(err));
302
- } finally {
303
- await browser.close();
304
- }
305
- }));
306
-
307
- program.parse();
308
- `;
309
- }
310
-
311
- function getNodeTemplate(templateName: string, manifest: Manifest): string {
312
- switch (templateName) {
313
- case 'api':
314
- return getNodeApiTemplate(manifest);
315
- case 'browser':
316
- return getNodeBrowserTemplate(manifest);
317
- default:
318
- return getNodeBasicTemplate(manifest);
319
- }
320
- }
321
-
322
- function getNodeBasicTemplate(manifest: Manifest): string {
323
- return `#!/usr/bin/env node
324
- /**
325
- * ${manifest.name} - ${manifest.description || 'A cli4ai tool'}
326
- */
327
-
328
- import { Command } from 'commander';
329
-
330
- const output = (data) => console.log(JSON.stringify(data, null, 2));
331
- const outputError = (code, message, details) => {
332
- console.error(JSON.stringify({ error: code, message, ...(details || {}) }));
333
- process.exit(1);
334
- };
335
-
336
- const withErrorHandling = (fn) => async (...args) => {
337
- try {
338
- await fn(...args);
339
- } catch (err) {
340
- outputError('API_ERROR', err instanceof Error ? err.message : String(err));
341
- }
342
- };
343
-
344
- const program = new Command()
345
- .name('${manifest.name}')
346
- .version('${manifest.version}', '-v, --version', 'Show version')
347
- .description('${manifest.description || manifest.name}');
348
-
349
- program
350
- .command('hello [name]')
351
- .description('Say hello')
352
- .action(withErrorHandling(async (name) => {
353
- const who = (name || '').trim() || 'world';
354
- output({ message: \`Hello, \${who}!\` });
355
- }));
356
-
357
- program.parse();
358
- `;
359
- }
360
-
361
- function getNodeApiTemplate(manifest: Manifest): string {
362
- return `#!/usr/bin/env node
363
- /**
364
- * ${manifest.name} - ${manifest.description || 'API wrapper tool'}
365
- */
366
-
367
- import { Command } from 'commander';
368
-
369
- const output = (data) => console.log(JSON.stringify(data, null, 2));
370
- const outputError = (code, message, details) => {
371
- console.error(JSON.stringify({ error: code, message, ...(details || {}) }));
372
- process.exit(1);
373
- };
374
-
375
- const withErrorHandling = (fn) => async (...args) => {
376
- try {
377
- await fn(...args);
378
- } catch (err) {
379
- outputError('API_ERROR', err instanceof Error ? err.message : String(err));
380
- }
381
- };
382
-
383
- const env = (name) => {
384
- const value = process.env[name];
385
- if (!value) outputError('ENV_MISSING', \`Missing required environment variable: \${name}\`);
386
- return value;
387
- };
388
-
389
- const program = new Command()
390
- .name('${manifest.name}')
391
- .version('${manifest.version}', '-v, --version', 'Show version')
392
- .description('${manifest.description || manifest.name}');
393
-
394
- program
395
- .command('fetch <endpoint>')
396
- .description('Fetch JSON from an API endpoint')
397
- .action(withErrorHandling(async (endpoint) => {
398
- // Use \`cli4ai secrets set API_KEY\` to store this securely (cli4ai injects it at runtime).
399
- const apiKey = env('API_KEY');
400
- const res = await fetch(\`https://api.example.com/\${endpoint}\`, {
401
- headers: { Authorization: \`Bearer \${apiKey}\` },
402
- });
403
-
404
- if (!res.ok) {
405
- outputError('API_ERROR', \`HTTP \${res.status}: \${res.statusText}\`);
406
- }
407
-
408
- output(await res.json());
409
- }));
410
-
411
- program.parse();
412
- `;
413
- }
414
-
415
- function getNodeBrowserTemplate(manifest: Manifest): string {
416
- return `#!/usr/bin/env node
417
- /**
418
- * ${manifest.name} - ${manifest.description || 'Browser automation tool'}
419
- */
420
-
421
- import { Command } from 'commander';
422
- import puppeteer from 'puppeteer';
423
-
424
- const output = (data) => console.log(JSON.stringify(data, null, 2));
425
- const outputError = (code, message, details) => {
426
- console.error(JSON.stringify({ error: code, message, ...(details || {}) }));
427
- process.exit(1);
428
- };
429
-
430
- const withErrorHandling = (fn) => async (...args) => {
431
- try {
432
- await fn(...args);
433
- } catch (err) {
434
- outputError('API_ERROR', err instanceof Error ? err.message : String(err));
435
- }
436
- };
437
-
438
- const program = new Command()
439
- .name('${manifest.name}')
440
- .version('${manifest.version}', '-v, --version', 'Show version')
441
- .description('${manifest.description || manifest.name}');
442
-
443
- program
444
- .command('screenshot <url>')
445
- .description('Take a screenshot of a webpage')
446
- .option('-o, --output <file>', 'Output file', 'screenshot.png')
447
- .option('--full-page', 'Capture full page')
448
- .action(withErrorHandling(async (url, options) => {
449
- const browser = await puppeteer.launch({ headless: true });
450
- try {
451
- const page = await browser.newPage();
452
- await page.goto(url, { waitUntil: 'networkidle2' });
453
- await page.screenshot({ path: options.output, fullPage: options.fullPage });
454
- output({ url, screenshot: options.output, timestamp: new Date().toISOString() });
455
309
  } finally {
456
310
  await browser.close();
457
311
  }
@@ -461,11 +315,7 @@ program.parse();
461
315
  `;
462
316
  }
463
317
 
464
- function getReadmeTemplate(templateName: string, runtime: 'bun' | 'node', manifest: Manifest): string {
465
- const runCmd =
466
- runtime === 'node' ? `node ${manifest.entry}` :
467
- `bun run ${manifest.entry}`;
468
-
318
+ function getReadmeTemplate(templateName: string, manifest: Manifest): string {
469
319
  const exampleCmd =
470
320
  templateName === 'api' ? 'fetch users' :
471
321
  templateName === 'browser' ? 'screenshot https://example.com' :
@@ -475,10 +325,16 @@ function getReadmeTemplate(templateName: string, runtime: 'bun' | 'node', manife
475
325
 
476
326
  ${manifest.description || ''}
477
327
 
328
+ ## Setup
329
+
330
+ \`\`\`bash
331
+ npm install
332
+ \`\`\`
333
+
478
334
  ## Run directly
479
335
 
480
336
  \`\`\`bash
481
- ${runCmd} ${exampleCmd}
337
+ npx tsx run.ts ${exampleCmd}
482
338
  \`\`\`
483
339
 
484
340
  ## Install with cli4ai (project-scoped)
@@ -489,6 +345,12 @@ cli4ai add --local /path/to/${manifest.name} -y
489
345
  cli4ai run ${manifest.name} ${exampleCmd}
490
346
  \`\`\`
491
347
 
348
+ ## Testing
349
+
350
+ \`\`\`bash
351
+ npm test
352
+ \`\`\`
353
+
492
354
  ## MCP
493
355
 
494
356
  \`\`\`bash
@@ -504,12 +366,93 @@ function getGitignoreTemplate(): string {
504
366
  .env.*
505
367
  dist
506
368
  .DS_Store
369
+ *.log
370
+ coverage
371
+ `;
372
+ }
373
+
374
+ function getTsconfigTemplate(): string {
375
+ return `{
376
+ "compilerOptions": {
377
+ "target": "ES2022",
378
+ "module": "NodeNext",
379
+ "moduleResolution": "NodeNext",
380
+ "strict": true,
381
+ "esModuleInterop": true,
382
+ "skipLibCheck": true,
383
+ "forceConsistentCasingInFileNames": true,
384
+ "resolveJsonModule": true,
385
+ "declaration": false,
386
+ "noEmit": true
387
+ },
388
+ "include": ["*.ts", "lib/**/*.ts"],
389
+ "exclude": ["node_modules"]
390
+ }
391
+ `;
392
+ }
393
+
394
+ function getVitestConfigTemplate(): string {
395
+ return `import { defineConfig } from 'vitest/config';
396
+
397
+ export default defineConfig({
398
+ test: {
399
+ globals: true,
400
+ },
401
+ });
402
+ `;
403
+ }
404
+
405
+ function getTestTemplate(templateName: string, manifest: Manifest): string {
406
+ const testCmd = templateName === 'api' ? 'fetch' : templateName === 'browser' ? 'screenshot' : 'hello';
407
+ const envSetup = templateName === 'api' ? `
408
+ // Set required env vars before importing
409
+ process.env.API_KEY = 'test-api-key';
410
+ ` : '';
411
+
412
+ return `import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
413
+ ${envSetup}
414
+ describe('${manifest.name}', () => {
415
+ let consoleSpy: ReturnType<typeof vi.spyOn>;
416
+ let logs: string[];
417
+
418
+ beforeEach(() => {
419
+ logs = [];
420
+ consoleSpy = vi.spyOn(console, 'log').mockImplementation((msg: string) => {
421
+ logs.push(msg);
422
+ });
423
+ });
424
+
425
+ afterEach(() => {
426
+ consoleSpy.mockRestore();
427
+ });
428
+
429
+ describe('${testCmd} command', () => {
430
+ test('should output valid JSON', async () => {
431
+ // TODO: Import and test your command handler
432
+ // Example:
433
+ // import { ${testCmd}Command } from './run';
434
+ // await ${testCmd}Command('test-arg');
435
+ // expect(logs.length).toBeGreaterThan(0);
436
+ // const result = JSON.parse(logs[0]);
437
+ // expect(result).toBeDefined();
438
+
439
+ expect(true).toBe(true); // Placeholder - replace with real test
440
+ });
441
+
442
+ test('should handle invalid input', async () => {
443
+ // TODO: Test error handling
444
+ // Example:
445
+ // expect(() => ${testCmd}Command('')).toThrow();
446
+
447
+ expect(true).toBe(true); // Placeholder - replace with real test
448
+ });
449
+ });
450
+ });
507
451
  `;
508
452
  }
509
453
 
510
454
  function getDevPackageJson(
511
455
  templateName: string,
512
- runtime: 'bun' | 'node',
513
456
  manifest: Manifest
514
457
  ): Record<string, unknown> {
515
458
  const deps = manifest.dependencies || {};
@@ -520,11 +463,19 @@ function getDevPackageJson(
520
463
  private: true,
521
464
  type: 'module',
522
465
  bin: {
523
- [manifest.name]: `./${manifest.entry}`,
466
+ [manifest.name]: `./run.ts`,
524
467
  },
525
468
  dependencies: deps,
469
+ devDependencies: {
470
+ 'typescript': '^5.0.0',
471
+ 'tsx': '^4.0.0',
472
+ 'vitest': '^2.0.0',
473
+ '@types/node': '^22.0.0',
474
+ },
526
475
  scripts: {
527
- dev: runtime === 'node' ? `node ${manifest.entry}` : `bun run ${manifest.entry}`,
476
+ dev: 'tsx run.ts',
477
+ test: 'vitest run',
478
+ 'test:watch': 'vitest',
528
479
  },
529
480
  };
530
481
  }
@@ -533,7 +484,6 @@ function getNextSteps(
533
484
  nameArg: string | undefined,
534
485
  targetDir: string,
535
486
  templateName: string,
536
- runtime: 'bun' | 'node',
537
487
  manifest: Manifest
538
488
  ): Array<{ description: string; command: string }> {
539
489
  const exampleArgs =
@@ -541,16 +491,20 @@ function getNextSteps(
541
491
  templateName === 'browser' ? 'screenshot https://example.com' :
542
492
  'hello world';
543
493
 
544
- const runDirect =
545
- runtime === 'node' ? `node ${manifest.entry} ${exampleArgs}` :
546
- `bun run ${manifest.entry} ${exampleArgs}`;
547
-
548
494
  const installPath = nameArg ? `./${basename(targetDir)}` : '.';
549
495
 
550
496
  return [
551
497
  {
552
- description: 'Run the tool directly in this folder',
553
- command: runDirect,
498
+ description: 'Install dependencies',
499
+ command: 'npm install',
500
+ },
501
+ {
502
+ description: 'Run the tool directly',
503
+ command: `npx tsx run.ts ${exampleArgs}`,
504
+ },
505
+ {
506
+ description: 'Run tests',
507
+ command: 'npm test',
554
508
  },
555
509
  {
556
510
  description: 'Install into a project and run via cli4ai',
@@ -58,32 +58,15 @@ export async function listCommand(options: ListOptions): Promise<void> {
58
58
  }
59
59
  }
60
60
 
61
- if (options.json || !process.stdout.isTTY) {
62
- output({
63
- packages: packages.map(p => ({
64
- name: p.name,
65
- version: p.version,
66
- path: p.path,
67
- source: p.source,
68
- scope: p.scope
69
- })),
70
- count: packages.length
71
- });
72
- } else {
73
- // Human-readable output
74
- if (packages.length === 0) {
75
- console.log('No packages installed');
76
- console.log('\nRun "cli4ai browse" to find and install packages');
77
- console.log('Or: npm install -g @cli4ai/<package>');
78
- } else {
79
- console.log(`\nInstalled packages (${packages.length}):\n`);
80
- for (const pkg of packages) {
81
- const scopeTag = pkg.scope === 'local' ? '' :
82
- pkg.scope === 'npm' ? ' (npm)' : ' (cli4ai)';
83
- console.log(` ${pkg.name}@${pkg.version}${scopeTag}`);
84
- console.log(` ${pkg.path}`);
85
- }
86
- console.log('');
87
- }
88
- }
61
+ output({
62
+ packages: packages.map(p => ({
63
+ name: p.name,
64
+ version: p.version,
65
+ path: p.path,
66
+ source: p.source,
67
+ scope: p.scope
68
+ })),
69
+ count: packages.length,
70
+ location: options.global ? 'global' : 'all'
71
+ });
89
72
  }
@@ -2,11 +2,12 @@
2
2
  * cli4ai mcp-config - Generate MCP configuration for Claude Code
3
3
  */
4
4
 
5
- import { outputError } from '../lib/cli.js';
5
+ import { output, outputError } from '../lib/cli.js';
6
6
  import {
7
7
  generateClaudeCodeConfig,
8
8
  formatClaudeCodeConfig,
9
- generateConfigSnippet
9
+ generateConfigSnippet,
10
+ generateServerConfig
10
11
  } from '../mcp/config-gen.js';
11
12
  import { findPackage } from '../core/config.js';
12
13
  import { tryLoadManifest } from '../core/manifest.js';
@@ -36,8 +37,14 @@ export async function mcpConfigCommand(options: McpConfigOptions): Promise<void>
36
37
  outputError('INVALID_INPUT', `Package ${options.package} does not have MCP enabled`);
37
38
  }
38
39
 
40
+ const serverName = `cli4ai-${manifest!.name}`;
41
+ const serverConfig = generateServerConfig(manifest!, pkg!.path);
39
42
  const snippet = generateConfigSnippet(manifest!, pkg!.path);
40
- console.log(snippet);
43
+ output({
44
+ serverName,
45
+ serverConfig,
46
+ snippet
47
+ });
41
48
  return;
42
49
  }
43
50
 
@@ -55,5 +62,8 @@ export async function mcpConfigCommand(options: McpConfigOptions): Promise<void>
55
62
  }
56
63
 
57
64
  // Output formatted config
58
- console.log(formatClaudeCodeConfig(config));
65
+ output({
66
+ config,
67
+ formatted: formatClaudeCodeConfig(config)
68
+ });
59
69
  }