@skillsmith/cli 0.2.2 → 0.2.4

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 (35) hide show
  1. package/README.md +147 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/commands/author.d.ts +44 -1
  4. package/dist/src/commands/author.d.ts.map +1 -1
  5. package/dist/src/commands/author.js +524 -4
  6. package/dist/src/commands/author.js.map +1 -1
  7. package/dist/src/commands/index.d.ts +1 -1
  8. package/dist/src/commands/index.d.ts.map +1 -1
  9. package/dist/src/commands/index.js +1 -1
  10. package/dist/src/commands/index.js.map +1 -1
  11. package/dist/src/index.js +16 -6
  12. package/dist/src/index.js.map +1 -1
  13. package/dist/src/templates/index.d.ts +2 -0
  14. package/dist/src/templates/index.d.ts.map +1 -1
  15. package/dist/src/templates/index.js +2 -0
  16. package/dist/src/templates/index.js.map +1 -1
  17. package/dist/src/templates/mcp-server.template.d.ts +97 -0
  18. package/dist/src/templates/mcp-server.template.d.ts.map +1 -0
  19. package/dist/src/templates/mcp-server.template.js +529 -0
  20. package/dist/src/templates/mcp-server.template.js.map +1 -0
  21. package/dist/src/templates/subagent.md.template.d.ts +38 -0
  22. package/dist/src/templates/subagent.md.template.d.ts.map +1 -0
  23. package/dist/src/templates/subagent.md.template.js +128 -0
  24. package/dist/src/templates/subagent.md.template.js.map +1 -0
  25. package/dist/src/utils/tool-analyzer.d.ts +54 -0
  26. package/dist/src/utils/tool-analyzer.d.ts.map +1 -0
  27. package/dist/src/utils/tool-analyzer.js +156 -0
  28. package/dist/src/utils/tool-analyzer.js.map +1 -0
  29. package/dist/tests/author.test.js +419 -0
  30. package/dist/tests/author.test.js.map +1 -1
  31. package/dist/tests/mcp-server-template.test.d.ts +7 -0
  32. package/dist/tests/mcp-server-template.test.d.ts.map +1 -0
  33. package/dist/tests/mcp-server-template.test.js +351 -0
  34. package/dist/tests/mcp-server-template.test.js.map +1 -0
  35. package/package.json +1 -1
@@ -29,5 +29,48 @@ export declare function createValidateCommand(): Command;
29
29
  * Create publish command
30
30
  */
31
31
  export declare function createPublishCommand(): Command;
32
- export { initSkill, validateSkill, publishSkill };
32
+ interface SubagentOptions {
33
+ output?: string | undefined;
34
+ tools?: string | undefined;
35
+ model?: string | undefined;
36
+ skipClaudeMd?: boolean | undefined;
37
+ force?: boolean | undefined;
38
+ }
39
+ /**
40
+ * SMI-1389: Generate a companion subagent for a skill
41
+ */
42
+ declare function generateSubagent(skillPath: string, options: SubagentOptions): Promise<void>;
43
+ interface TransformOptions {
44
+ dryRun?: boolean | undefined;
45
+ force?: boolean | undefined;
46
+ batch?: boolean | undefined;
47
+ tools?: string | undefined;
48
+ model?: string | undefined;
49
+ }
50
+ /**
51
+ * SMI-1390: Transform existing skill by generating subagent (non-destructive)
52
+ */
53
+ declare function transformSkill(skillPath: string, options: TransformOptions): Promise<void>;
54
+ /**
55
+ * Create subagent command
56
+ */
57
+ export declare function createSubagentCommand(): Command;
58
+ /**
59
+ * Create transform command
60
+ */
61
+ export declare function createTransformCommand(): Command;
62
+ interface McpInitOptions {
63
+ output?: string | undefined;
64
+ tools?: string | undefined;
65
+ force?: boolean | undefined;
66
+ }
67
+ /**
68
+ * SMI-1433: Initialize a new MCP server project
69
+ */
70
+ declare function initMcpServer(name: string | undefined, options: McpInitOptions): Promise<void>;
71
+ /**
72
+ * Create mcp-init command
73
+ */
74
+ export declare function createMcpInitCommand(): Command;
75
+ export { initSkill, validateSkill, publishSkill, generateSubagent, transformSkill, initMcpServer };
33
76
  //# sourceMappingURL=author.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"author.d.ts","sourceRoot":"","sources":["../../../src/commands/author.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAYnC;;GAEG;AACH,iBAAe,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyHpF;AA+BD;;GAEG;AACH,iBAAe,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0DhE;AAED;;;GAGG;AACH,iBAAe,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqF/D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAe3C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAa/C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAa9C;AAED,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,CAAA"}
1
+ {"version":3,"file":"author.d.ts","sourceRoot":"","sources":["../../../src/commands/author.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA2BnC;;GAEG;AACH,iBAAe,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyHpF;AA+BD;;GAEG;AACH,iBAAe,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA0DhE;AAED;;;GAGG;AACH,iBAAe,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqF/D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAe3C;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAa/C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAa9C;AAgGD,UAAU,eAAe;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAClC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC5B;AAED;;GAEG;AACH,iBAAe,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqI1F;AAED,UAAU,gBAAgB;IACxB,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5B,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B;AAED;;GAEG;AACH,iBAAe,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsGzF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAuB/C;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAuBhD;AAED,UAAU,cAAc;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC5B;AAED;;GAEG;AACH,iBAAe,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAkK7F;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAqB9C;AAED,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,CAAA"}
@@ -8,11 +8,14 @@ import { input, confirm, select } from '@inquirer/prompts';
8
8
  import chalk from 'chalk';
9
9
  import ora from 'ora';
10
10
  import { mkdir, writeFile, readFile, stat } from 'fs/promises';
11
- import { join, resolve } from 'path';
11
+ import { dirname, join, resolve } from 'path';
12
12
  import { createHash } from 'crypto';
13
13
  import { SkillParser } from '@skillsmith/core';
14
- import { SKILL_MD_TEMPLATE, README_MD_TEMPLATE } from '../templates/index.js';
14
+ import { SKILL_MD_TEMPLATE, README_MD_TEMPLATE, renderSubagentTemplate, renderClaudeMdSnippet, renderMcpServerTemplates, } from '../templates/index.js';
15
15
  import { sanitizeError } from '../utils/sanitize.js';
16
+ import { analyzeToolRequirements, formatToolList, parseToolsString, validateTools, } from '../utils/tool-analyzer.js';
17
+ import { homedir } from 'os';
18
+ import { access } from 'fs/promises';
16
19
  /**
17
20
  * Initialize a new skill directory
18
21
  */
@@ -215,7 +218,7 @@ async function publishSkill(skillPath) {
215
218
  try {
216
219
  const stats = await stat(dirPath);
217
220
  if (!stats.isDirectory()) {
218
- dirPath = join(dirPath, '..');
221
+ dirPath = dirname(dirPath);
219
222
  }
220
223
  }
221
224
  catch {
@@ -333,5 +336,522 @@ export function createPublishCommand() {
333
336
  }
334
337
  });
335
338
  }
336
- export { initSkill, validateSkill, publishSkill };
339
+ /**
340
+ * SMI-1389: Extract trigger phrases from skill description
341
+ */
342
+ function extractTriggerPhrases(description) {
343
+ const phrases = [];
344
+ // Pattern: "Use when [phrases]" or "when the user asks to [phrases]"
345
+ const patterns = [
346
+ /use when (?:the user asks to )?["']([^"']+)["']/gi,
347
+ /when (?:the user asks to )?["']([^"']+)["']/gi,
348
+ /trigger(?:ed)? (?:by|when|phrases?)[\s:]+["']([^"']+)["']/gi,
349
+ /invoke when (?:the user )?["']([^"']+)["']/gi,
350
+ ];
351
+ for (const pattern of patterns) {
352
+ const matches = description.matchAll(pattern);
353
+ for (const match of matches) {
354
+ if (match[1]) {
355
+ phrases.push(match[1]);
356
+ }
357
+ }
358
+ }
359
+ return phrases;
360
+ }
361
+ /**
362
+ * SMI-1389: Validate subagent definition structure
363
+ */
364
+ function validateSubagentDefinition(content) {
365
+ const errors = [];
366
+ const warnings = [];
367
+ // Check for YAML frontmatter
368
+ if (!content.trim().startsWith('---')) {
369
+ errors.push('Missing YAML frontmatter');
370
+ }
371
+ // Extract and validate frontmatter
372
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
373
+ if (frontmatterMatch) {
374
+ const frontmatter = frontmatterMatch[1] || '';
375
+ const requiredFields = ['name', 'description', 'skills', 'tools', 'model'];
376
+ for (const field of requiredFields) {
377
+ if (!frontmatter.includes(`${field}:`)) {
378
+ errors.push(`Missing required field: ${field}`);
379
+ }
380
+ }
381
+ }
382
+ else {
383
+ errors.push('Could not parse YAML frontmatter');
384
+ }
385
+ // Check for operating protocol section
386
+ if (!content.includes('## Operating Protocol')) {
387
+ warnings.push('Missing Operating Protocol section');
388
+ }
389
+ // Check for output format section
390
+ if (!content.includes('## Output Format')) {
391
+ warnings.push('Missing Output Format section');
392
+ }
393
+ return {
394
+ valid: errors.length === 0,
395
+ errors,
396
+ warnings,
397
+ };
398
+ }
399
+ /**
400
+ * Ensure ~/.claude/agents directory exists
401
+ */
402
+ async function ensureAgentsDirectory(customPath) {
403
+ const agentsDir = customPath
404
+ ? resolve(customPath.replace(/^~/, homedir()))
405
+ : join(homedir(), '.claude', 'agents');
406
+ await mkdir(agentsDir, { recursive: true });
407
+ return agentsDir;
408
+ }
409
+ /**
410
+ * Check if file exists
411
+ */
412
+ async function fileExists(path) {
413
+ try {
414
+ await access(path);
415
+ return true;
416
+ }
417
+ catch {
418
+ return false;
419
+ }
420
+ }
421
+ /**
422
+ * SMI-1389: Generate a companion subagent for a skill
423
+ */
424
+ async function generateSubagent(skillPath, options) {
425
+ const spinner = ora('Generating subagent...').start();
426
+ try {
427
+ // Resolve skill path
428
+ let dirPath = resolve(skillPath || '.');
429
+ let skillMdPath;
430
+ // Check if it's a directory or file
431
+ try {
432
+ const stats = await stat(dirPath);
433
+ if (stats.isDirectory()) {
434
+ skillMdPath = join(dirPath, 'SKILL.md');
435
+ }
436
+ else {
437
+ skillMdPath = dirPath;
438
+ dirPath = dirname(dirPath);
439
+ }
440
+ }
441
+ catch {
442
+ // Try adding SKILL.md
443
+ skillMdPath = dirPath.endsWith('.md') ? dirPath : join(dirPath, 'SKILL.md');
444
+ }
445
+ // Read and parse SKILL.md
446
+ spinner.text = 'Reading SKILL.md...';
447
+ const content = await readFile(skillMdPath, 'utf-8');
448
+ const parser = new SkillParser({ requireName: true });
449
+ const { validation, metadata } = parser.parseWithValidation(content);
450
+ if (!validation.valid || !metadata) {
451
+ spinner.fail('SKILL.md validation failed');
452
+ printValidationResult(validation, skillMdPath);
453
+ return;
454
+ }
455
+ // Analyze tool requirements
456
+ spinner.text = 'Analyzing tool requirements...';
457
+ const toolAnalysis = analyzeToolRequirements(content);
458
+ // Override tools if specified
459
+ let tools = toolAnalysis.requiredTools;
460
+ if (options.tools) {
461
+ const customTools = parseToolsString(options.tools);
462
+ const validation = validateTools(customTools);
463
+ if (!validation.valid) {
464
+ spinner.fail(`Unrecognized tools: ${validation.unrecognized.join(', ')}`);
465
+ return;
466
+ }
467
+ tools = customTools;
468
+ }
469
+ // Extract trigger phrases
470
+ const triggerPhrases = extractTriggerPhrases(metadata.description || '');
471
+ // Determine model
472
+ const model = options.model || 'sonnet';
473
+ if (!['sonnet', 'opus', 'haiku'].includes(model)) {
474
+ spinner.fail(`Invalid model: ${model}. Must be sonnet, opus, or haiku.`);
475
+ return;
476
+ }
477
+ // Generate subagent content
478
+ spinner.text = 'Generating subagent definition...';
479
+ const subagentContent = renderSubagentTemplate({
480
+ skillName: metadata.name,
481
+ description: metadata.description || `Specialist for ${metadata.name}`,
482
+ triggerPhrases,
483
+ tools,
484
+ model,
485
+ });
486
+ // Validate generated content
487
+ const subagentValidation = validateSubagentDefinition(subagentContent);
488
+ if (!subagentValidation.valid) {
489
+ spinner.fail('Generated subagent is invalid');
490
+ console.log(chalk.red('\nGeneration errors:'));
491
+ for (const error of subagentValidation.errors) {
492
+ console.log(chalk.red(` - ${error}`));
493
+ }
494
+ return;
495
+ }
496
+ // Ensure agents directory exists
497
+ const agentsDir = await ensureAgentsDirectory(options.output);
498
+ const subagentPath = join(agentsDir, `${metadata.name}-specialist.md`);
499
+ // Check if subagent already exists
500
+ if (await fileExists(subagentPath)) {
501
+ if (!options.force) {
502
+ spinner.warn(`Subagent already exists: ${subagentPath}`);
503
+ console.log(chalk.yellow(' Use --force to overwrite'));
504
+ return;
505
+ }
506
+ }
507
+ // Write subagent file
508
+ await writeFile(subagentPath, subagentContent, 'utf-8');
509
+ spinner.succeed(`Generated subagent: ${subagentPath}`);
510
+ // Show tool analysis
511
+ console.log(chalk.bold('\nTool Analysis:'));
512
+ console.log(chalk.dim(` Confidence: ${toolAnalysis.confidence}`));
513
+ console.log(chalk.dim(` Tools: ${formatToolList(tools)}`));
514
+ if (toolAnalysis.detectedPatterns.length > 0) {
515
+ console.log(chalk.dim(' Detected patterns:'));
516
+ for (const pattern of toolAnalysis.detectedPatterns.slice(0, 5)) {
517
+ console.log(chalk.dim(` - ${pattern}`));
518
+ }
519
+ }
520
+ // Generate and display CLAUDE.md snippet
521
+ if (!options.skipClaudeMd) {
522
+ const snippet = renderClaudeMdSnippet({
523
+ skillName: metadata.name,
524
+ description: metadata.description || '',
525
+ triggerPhrases,
526
+ tools,
527
+ model,
528
+ });
529
+ console.log(chalk.bold('\nCLAUDE.md Integration Snippet:'));
530
+ console.log(chalk.cyan('─'.repeat(50)));
531
+ console.log(snippet);
532
+ console.log(chalk.cyan('─'.repeat(50)));
533
+ console.log(chalk.dim('\nAdd this snippet to your project CLAUDE.md to enable delegation.'));
534
+ }
535
+ console.log();
536
+ }
537
+ catch (error) {
538
+ spinner.fail(`Failed to generate subagent: ${sanitizeError(error)}`);
539
+ throw error;
540
+ }
541
+ }
542
+ /**
543
+ * SMI-1390: Transform existing skill by generating subagent (non-destructive)
544
+ */
545
+ async function transformSkill(skillPath, options) {
546
+ const spinner = ora('Transforming skill...').start();
547
+ try {
548
+ const dirPath = resolve(skillPath || '.');
549
+ // Check if batch mode
550
+ if (options.batch) {
551
+ spinner.text = 'Scanning for skills...';
552
+ const entries = await readFile(dirPath, 'utf-8').catch(() => null);
553
+ if (entries === null) {
554
+ // It's a directory, scan for subdirectories with SKILL.md
555
+ const { readdir } = await import('fs/promises');
556
+ const subdirs = await readdir(dirPath, { withFileTypes: true });
557
+ const skillDirs = [];
558
+ for (const entry of subdirs) {
559
+ if (entry.isDirectory()) {
560
+ const skillMdPath = join(dirPath, entry.name, 'SKILL.md');
561
+ if (await fileExists(skillMdPath)) {
562
+ skillDirs.push(join(dirPath, entry.name));
563
+ }
564
+ }
565
+ }
566
+ if (skillDirs.length === 0) {
567
+ spinner.warn('No skills found in directory');
568
+ return;
569
+ }
570
+ spinner.succeed(`Found ${skillDirs.length} skills`);
571
+ // Process each skill
572
+ for (const skillDir of skillDirs) {
573
+ console.log(chalk.dim(`\nProcessing: ${skillDir}`));
574
+ await transformSkill(skillDir, {
575
+ ...options,
576
+ batch: false, // Don't recurse
577
+ });
578
+ }
579
+ return;
580
+ }
581
+ }
582
+ // Single skill transform
583
+ const skillMdPath = join(dirPath, 'SKILL.md');
584
+ if (!(await fileExists(skillMdPath))) {
585
+ spinner.fail(`No SKILL.md found at: ${skillMdPath}`);
586
+ return;
587
+ }
588
+ // Read and parse
589
+ spinner.text = 'Reading SKILL.md...';
590
+ const content = await readFile(skillMdPath, 'utf-8');
591
+ const parser = new SkillParser({ requireName: true });
592
+ const { validation, metadata } = parser.parseWithValidation(content);
593
+ if (!validation.valid || !metadata) {
594
+ spinner.fail('SKILL.md validation failed');
595
+ printValidationResult(validation, skillMdPath);
596
+ return;
597
+ }
598
+ // Check if subagent already exists
599
+ const agentsDir = join(homedir(), '.claude', 'agents');
600
+ const subagentPath = join(agentsDir, `${metadata.name}-specialist.md`);
601
+ if (await fileExists(subagentPath)) {
602
+ if (!options.force) {
603
+ spinner.warn(`Subagent already exists: ${subagentPath}`);
604
+ console.log(chalk.yellow(' Use --force to overwrite'));
605
+ return;
606
+ }
607
+ }
608
+ if (options.dryRun) {
609
+ spinner.succeed('Dry run - would generate:');
610
+ console.log(chalk.dim(` Subagent: ${subagentPath}`));
611
+ // Show tool analysis
612
+ const toolAnalysis = analyzeToolRequirements(content);
613
+ console.log(chalk.dim(` Tools: ${formatToolList(toolAnalysis.requiredTools)}`));
614
+ console.log(chalk.dim(` Confidence: ${toolAnalysis.confidence}`));
615
+ return;
616
+ }
617
+ spinner.stop();
618
+ // Generate subagent using existing function
619
+ await generateSubagent(dirPath, {
620
+ force: options.force,
621
+ tools: options.tools,
622
+ model: options.model,
623
+ skipClaudeMd: false,
624
+ });
625
+ }
626
+ catch (error) {
627
+ spinner.fail(`Failed to transform skill: ${sanitizeError(error)}`);
628
+ throw error;
629
+ }
630
+ }
631
+ /**
632
+ * Create subagent command
633
+ */
634
+ export function createSubagentCommand() {
635
+ return new Command('subagent')
636
+ .description('Generate a companion subagent for a skill')
637
+ .argument('[path]', 'Path to skill directory', '.')
638
+ .option('-o, --output <path>', 'Output directory', '~/.claude/agents')
639
+ .option('--tools <tools>', 'Override detected tools (comma-separated)')
640
+ .option('--model <model>', 'Model for subagent: sonnet|opus|haiku', 'sonnet')
641
+ .option('--skip-claude-md', 'Skip CLAUDE.md snippet generation')
642
+ .option('--force', 'Overwrite existing subagent definition')
643
+ .action(async (skillPath, opts) => {
644
+ try {
645
+ await generateSubagent(skillPath, {
646
+ output: opts['output'],
647
+ tools: opts['tools'],
648
+ model: opts['model'],
649
+ skipClaudeMd: opts['skipClaudeMd'],
650
+ force: opts['force'],
651
+ });
652
+ }
653
+ catch (error) {
654
+ console.error(chalk.red('Error generating subagent:'), sanitizeError(error));
655
+ process.exit(1);
656
+ }
657
+ });
658
+ }
659
+ /**
660
+ * Create transform command
661
+ */
662
+ export function createTransformCommand() {
663
+ return new Command('transform')
664
+ .description('Upgrade existing skill with subagent configuration')
665
+ .argument('[path]', 'Path to skill directory', '.')
666
+ .option('--dry-run', 'Preview what would be generated')
667
+ .option('--force', 'Overwrite existing subagent')
668
+ .option('--batch', 'Process directory of skills')
669
+ .option('--tools <tools>', 'Override detected tools (comma-separated)')
670
+ .option('--model <model>', 'Model for subagent: sonnet|opus|haiku', 'sonnet')
671
+ .action(async (skillPath, opts) => {
672
+ try {
673
+ await transformSkill(skillPath, {
674
+ dryRun: opts['dryRun'],
675
+ force: opts['force'],
676
+ batch: opts['batch'],
677
+ tools: opts['tools'],
678
+ model: opts['model'],
679
+ });
680
+ }
681
+ catch (error) {
682
+ console.error(chalk.red('Error transforming skill:'), sanitizeError(error));
683
+ process.exit(1);
684
+ }
685
+ });
686
+ }
687
+ /**
688
+ * SMI-1433: Initialize a new MCP server project
689
+ */
690
+ async function initMcpServer(name, options) {
691
+ // Interactive prompts if name not provided
692
+ const serverName = name ||
693
+ (await input({
694
+ message: 'MCP server name:',
695
+ validate: (value) => {
696
+ if (!value.trim())
697
+ return 'Name is required';
698
+ if (!/^[a-z][a-z0-9-]*$/.test(value)) {
699
+ return 'Name must be lowercase, start with a letter, and contain only letters, numbers, and hyphens';
700
+ }
701
+ return true;
702
+ },
703
+ }));
704
+ const description = await input({
705
+ message: 'Description:',
706
+ default: `An MCP server for ${serverName}`,
707
+ });
708
+ const author = await input({
709
+ message: 'Author:',
710
+ default: process.env['USER'] || 'author',
711
+ });
712
+ // Parse initial tools if provided
713
+ const initialTools = [];
714
+ const toolNameRegex = /^[a-z][a-z0-9_-]*$/;
715
+ if (options.tools) {
716
+ const toolNames = options.tools
717
+ .split(',')
718
+ .map((t) => t.trim())
719
+ .filter((t) => t.length > 0);
720
+ for (const toolName of toolNames) {
721
+ if (!toolNameRegex.test(toolName)) {
722
+ console.log(chalk.red(`Invalid tool name: ${toolName}. Must be lowercase, start with a letter, and contain only letters, numbers, underscores, and hyphens.`));
723
+ return;
724
+ }
725
+ initialTools.push({
726
+ name: toolName,
727
+ description: `${toolName} tool`,
728
+ parameters: [],
729
+ });
730
+ }
731
+ }
732
+ // Ask about tools if none specified
733
+ if (initialTools.length === 0) {
734
+ const addTools = await confirm({
735
+ message: 'Would you like to define initial tools interactively?',
736
+ default: false,
737
+ });
738
+ if (addTools) {
739
+ let addMore = true;
740
+ while (addMore) {
741
+ const toolName = await input({
742
+ message: 'Tool name:',
743
+ validate: (value) => {
744
+ if (!value.trim())
745
+ return 'Tool name is required';
746
+ if (!/^[a-z][a-z0-9_-]*$/.test(value)) {
747
+ return 'Tool name must be lowercase with letters, numbers, underscores, and hyphens';
748
+ }
749
+ return true;
750
+ },
751
+ });
752
+ const toolDescription = await input({
753
+ message: 'Tool description:',
754
+ default: `${toolName} tool`,
755
+ });
756
+ initialTools.push({
757
+ name: toolName,
758
+ description: toolDescription,
759
+ parameters: [],
760
+ });
761
+ addMore = await confirm({
762
+ message: 'Add another tool?',
763
+ default: false,
764
+ });
765
+ }
766
+ }
767
+ }
768
+ const targetDir = options.output ? resolve(options.output) : resolve('.', serverName);
769
+ // Check if directory already exists
770
+ try {
771
+ await stat(targetDir);
772
+ if (!options.force) {
773
+ const overwrite = await confirm({
774
+ message: `Directory ${targetDir} already exists. Overwrite?`,
775
+ default: false,
776
+ });
777
+ if (!overwrite) {
778
+ console.log(chalk.yellow('Initialization cancelled'));
779
+ return;
780
+ }
781
+ }
782
+ }
783
+ catch {
784
+ // Directory doesn't exist, continue
785
+ }
786
+ const spinner = ora('Creating MCP server...').start();
787
+ try {
788
+ // Generate templates
789
+ const files = renderMcpServerTemplates({
790
+ name: serverName,
791
+ description,
792
+ tools: initialTools,
793
+ author,
794
+ });
795
+ // Create directory structure
796
+ await mkdir(targetDir, { recursive: true });
797
+ await mkdir(join(targetDir, 'src'), { recursive: true });
798
+ await mkdir(join(targetDir, 'src', 'tools'), { recursive: true });
799
+ // Write all files
800
+ for (const [filePath, content] of files) {
801
+ const fullPath = join(targetDir, filePath);
802
+ const dir = dirname(fullPath);
803
+ await mkdir(dir, { recursive: true });
804
+ await writeFile(fullPath, content, 'utf-8');
805
+ }
806
+ spinner.succeed(`Created MCP server at ${targetDir}`);
807
+ console.log(chalk.bold('\nNext steps:'));
808
+ console.log(chalk.dim(` 1. cd ${targetDir}`));
809
+ console.log(chalk.dim(' 2. npm install'));
810
+ console.log(chalk.dim(' 3. npm run dev # Run in development mode'));
811
+ console.log(chalk.dim(' 4. Edit src/tools/ to add your tool implementations'));
812
+ console.log();
813
+ console.log(chalk.bold('Configure in Claude Code:'));
814
+ console.log(chalk.cyan('─'.repeat(50)));
815
+ console.log(chalk.dim(`Add to ~/.claude/settings.json:`));
816
+ console.log(chalk.white(`{
817
+ "mcpServers": {
818
+ "${serverName}": {
819
+ "command": "npx",
820
+ "args": ["tsx", "${join(targetDir, 'src', 'index.ts')}"]
821
+ }
822
+ }
823
+ }`));
824
+ console.log(chalk.cyan('─'.repeat(50)));
825
+ console.log();
826
+ }
827
+ catch (error) {
828
+ spinner.fail(`Failed to create MCP server: ${sanitizeError(error)}`);
829
+ throw error;
830
+ }
831
+ }
832
+ /**
833
+ * Create mcp-init command
834
+ */
835
+ export function createMcpInitCommand() {
836
+ return new Command('mcp-init')
837
+ .description('Scaffold a new MCP server project')
838
+ .argument('[name]', 'MCP server name')
839
+ .option('-o, --output <path>', 'Output directory')
840
+ .option('--tools <tools>', 'Initial tools (comma-separated)')
841
+ .option('--force', 'Overwrite existing directory')
842
+ .action(async (name, opts) => {
843
+ try {
844
+ await initMcpServer(name, {
845
+ output: opts['output'],
846
+ tools: opts['tools'],
847
+ force: opts['force'],
848
+ });
849
+ }
850
+ catch (error) {
851
+ console.error(chalk.red('Error creating MCP server:'), sanitizeError(error));
852
+ process.exit(1);
853
+ }
854
+ });
855
+ }
856
+ export { initSkill, validateSkill, publishSkill, generateSubagent, transformSkill, initMcpServer };
337
857
  //# sourceMappingURL=author.js.map