@wipal/agent-team 1.0.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 (89) hide show
  1. package/.claude/rules/common/general-rules.md +141 -0
  2. package/.claude/rules/lessons/lessons.md +91 -0
  3. package/.claude/rules/role-rules/dev-fe-rules.md +146 -0
  4. package/.claude/rules/role-rules/sa-rules.md +226 -0
  5. package/.claude/skills/SKILL-INDEX.md +299 -0
  6. package/.claude/skills/community/security-validator/SKILL.md +392 -0
  7. package/.claude/skills/core/agent-creation/SKILL.md +338 -0
  8. package/.claude/skills/core/code-review/SKILL.md +154 -0
  9. package/.claude/skills/core/git-automation/SKILL.md +93 -0
  10. package/.claude/skills/core/retrospect-work/SKILL.md +172 -0
  11. package/.claude/skills/domain/architecture/adr-writing/SKILL.md +254 -0
  12. package/.claude/skills/domain/architecture/adr-writing/references/adr-best-practices.md +257 -0
  13. package/.claude/skills/domain/architecture/adr-writing/references/adr-examples.md +246 -0
  14. package/.claude/skills/domain/architecture/adr-writing/references/adr-template.md +160 -0
  15. package/.claude/skills/domain/architecture/architecture-patterns/SKILL.md +316 -0
  16. package/.claude/skills/domain/architecture/architecture-patterns/references/event-driven.md +393 -0
  17. package/.claude/skills/domain/architecture/architecture-patterns/references/microservices.md +315 -0
  18. package/.claude/skills/domain/architecture/architecture-patterns/references/monolith.md +321 -0
  19. package/.claude/skills/domain/architecture/architecture-patterns/references/serverless.md +457 -0
  20. package/.claude/skills/domain/architecture/performance-engineering/SKILL.md +227 -0
  21. package/.claude/skills/domain/architecture/performance-engineering/references/benchmarking.md +336 -0
  22. package/.claude/skills/domain/architecture/performance-engineering/references/caching-strategies.md +284 -0
  23. package/.claude/skills/domain/architecture/performance-engineering/references/optimization.md +298 -0
  24. package/.claude/skills/domain/architecture/security-architecture/SKILL.md +206 -0
  25. package/.claude/skills/domain/architecture/security-architecture/references/auth-patterns.md +209 -0
  26. package/.claude/skills/domain/architecture/security-architecture/references/compliance.md +246 -0
  27. package/.claude/skills/domain/architecture/security-architecture/references/threat-modeling.md +219 -0
  28. package/.claude/skills/domain/architecture/system-design/SKILL.md +227 -0
  29. package/.claude/skills/domain/architecture/system-design/references/distributed-systems.md +231 -0
  30. package/.claude/skills/domain/architecture/system-design/references/resilience.md +344 -0
  31. package/.claude/skills/domain/architecture/system-design/references/scalability.md +303 -0
  32. package/.claude/skills/domain/architecture/tech-selection/SKILL.md +192 -0
  33. package/.claude/skills/domain/architecture/tech-selection/references/build-vs-buy.md +258 -0
  34. package/.claude/skills/domain/architecture/tech-selection/references/evaluation-framework.md +203 -0
  35. package/.claude/skills/domain/architecture/tech-selection/references/tech-radar.md +257 -0
  36. package/.claude/skills/domain/backend/api-design/SKILL.md +121 -0
  37. package/.claude/skills/domain/backend/database-design/SKILL.md +156 -0
  38. package/.claude/skills/domain/backend/performance-be/SKILL.md +210 -0
  39. package/.claude/skills/domain/backend/security/SKILL.md +138 -0
  40. package/.claude/skills/domain/backend/testing-be/SKILL.md +203 -0
  41. package/.claude/skills/domain/devops/ci-cd/SKILL.md +188 -0
  42. package/.claude/skills/domain/devops/containerization/SKILL.md +177 -0
  43. package/.claude/skills/domain/devops/deployment/SKILL.md +198 -0
  44. package/.claude/skills/domain/devops/infrastructure-as-code/SKILL.md +178 -0
  45. package/.claude/skills/domain/devops/monitoring/SKILL.md +163 -0
  46. package/.claude/skills/domain/frontend/accessibility/SKILL.md +179 -0
  47. package/.claude/skills/domain/frontend/frontend-design/SKILL.md +138 -0
  48. package/.claude/skills/domain/frontend/performance-fe/SKILL.md +195 -0
  49. package/.claude/skills/domain/frontend/state-management/SKILL.md +190 -0
  50. package/.claude/skills/domain/frontend/testing-fe/SKILL.md +193 -0
  51. package/.claude/skills/domain/product/requirements-gathering/SKILL.md +136 -0
  52. package/.claude/skills/domain/product/roadmap-planning/SKILL.md +169 -0
  53. package/.claude/skills/domain/product/sprint-planning/SKILL.md +151 -0
  54. package/.claude/skills/domain/product/stakeholder-communication/SKILL.md +162 -0
  55. package/.claude/skills/domain/product/user-stories/SKILL.md +141 -0
  56. package/.claude/skills/domain/quality/bug-reporting/SKILL.md +150 -0
  57. package/.claude/skills/domain/quality/regression-testing/SKILL.md +178 -0
  58. package/.claude/skills/domain/quality/test-automation/SKILL.md +185 -0
  59. package/.claude/skills/domain/quality/test-planning/SKILL.md +177 -0
  60. package/.claude/skills/leadership/code-review-advanced/SKILL.md +167 -0
  61. package/.claude/skills/leadership/mentoring/SKILL.md +151 -0
  62. package/.claude/skills/leadership/technical-debt/SKILL.md +166 -0
  63. package/.claude/skills/leadership/technical-decision/SKILL.md +160 -0
  64. package/.claude/skills/security-reports/.gitkeep +0 -0
  65. package/.claude/skills/skills-registry.yaml +441 -0
  66. package/README.md +232 -0
  67. package/bin/agent-team.js +107 -0
  68. package/package.json +51 -0
  69. package/src/commands/add.js +227 -0
  70. package/src/commands/init.js +136 -0
  71. package/src/commands/list.js +66 -0
  72. package/src/commands/remove.js +71 -0
  73. package/src/commands/switch.js +53 -0
  74. package/src/index.js +11 -0
  75. package/src/interactive/prompts.js +153 -0
  76. package/src/server/api/agents.js +150 -0
  77. package/src/server/api/roles.js +97 -0
  78. package/src/server/api/skills.js +79 -0
  79. package/src/server/index.js +78 -0
  80. package/src/ui/agents.html +174 -0
  81. package/src/ui/css/styles.css +470 -0
  82. package/src/ui/index.html +107 -0
  83. package/src/ui/roles.html +371 -0
  84. package/src/ui/skills.html +332 -0
  85. package/src/utils/file-utils.js +193 -0
  86. package/src/utils/skill-resolver.js +594 -0
  87. package/src/utils/skill-scanner.js +154 -0
  88. package/templates/CLAUDE.md.tmpl +42 -0
  89. package/templates/knowledge.md.tmpl +31 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Init command - Initialize .claude/ structure in project
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+ import { getClaudeDir, getProjectRoot, copyRules } from '../utils/file-utils.js';
9
+
10
+ /**
11
+ * Initialize .claude/ structure
12
+ */
13
+ export async function initCommand(options) {
14
+ const projectRoot = getProjectRoot();
15
+ const claudeDir = getClaudeDir(projectRoot);
16
+
17
+ console.log(chalk.blue('📁 Initializing project...'));
18
+ console.log(chalk.gray(` Project: ${projectRoot}`));
19
+ console.log('');
20
+
21
+ // Check if already initialized
22
+ if (await fs.exists(claudeDir)) {
23
+ if (!options.force) {
24
+ console.log(chalk.yellow('⚠️ .claude/ directory already exists'));
25
+ console.log(chalk.gray(' Use --force to overwrite'));
26
+ return;
27
+ }
28
+ console.log(chalk.yellow(' Removing existing .claude/ directory...'));
29
+ await fs.remove(claudeDir);
30
+ }
31
+
32
+ // Create directory structure
33
+ console.log(chalk.blue(' Creating directory structure...'));
34
+
35
+ await fs.ensureDir(path.join(claudeDir, 'agents'));
36
+ await fs.ensureDir(path.join(claudeDir, 'shared-skills'));
37
+ await fs.ensureDir(path.join(claudeDir, 'rules', 'common'));
38
+ await fs.ensureDir(path.join(claudeDir, 'rules', 'role-rules'));
39
+ await fs.ensureDir(path.join(claudeDir, 'rules', 'lessons'));
40
+ await fs.ensureDir(path.join(claudeDir, 'commands', 'roles'));
41
+ await fs.ensureDir(path.join(claudeDir, 'commands', 'utils'));
42
+ await fs.ensureDir(path.join(claudeDir, 'commands', 'workflows'));
43
+
44
+ // Create CLAUDE.md
45
+ const claudeMdPath = path.join(claudeDir, 'CLAUDE.md');
46
+ if (!(await fs.exists(claudeMdPath)) || options.force) {
47
+ const projectName = path.basename(projectRoot);
48
+ const claudeMdContent = `# Project Context: ${projectName}
49
+
50
+ ## Description
51
+ [Brief description of the project]
52
+
53
+ ## Tech Stack
54
+ - Frontend:
55
+ - Backend:
56
+ - Database:
57
+ - Infrastructure:
58
+
59
+ ## Team Members (Agents)
60
+ [List of configured agents - run 'npx @wipal/agent-team list' to see]
61
+
62
+ ## Getting Started
63
+ 1. Add agents: \`npx @wipal/agent-team add <name> <role> [options]\`
64
+ 2. List agents: \`npx @wipal/agent-team list\`
65
+ 3. Switch agent: Use the agent's CLAUDE.md file
66
+
67
+ ## Notes
68
+ - Use Context7 MCP for documentation lookup
69
+ - Run \`/retrospect-work\` at end of session
70
+ - Update this file with project-specific context
71
+ `;
72
+ await fs.writeFile(claudeMdPath, claudeMdContent);
73
+ console.log(chalk.green(' ✓ Created CLAUDE.md'));
74
+ }
75
+
76
+ // Create settings.json
77
+ const settingsPath = path.join(claudeDir, 'settings.json');
78
+ if (!(await fs.exists(settingsPath)) || options.force) {
79
+ const settingsContent = {
80
+ mcpServers: {
81
+ context7: {
82
+ command: 'npx',
83
+ args: ['-y', '@context7/mcp-server']
84
+ },
85
+ github: {
86
+ command: 'npx',
87
+ args: ['-y', '@modelcontextprotocol/server-github'],
88
+ env: {
89
+ GITHUB_TOKEN: '${GITHUB_TOKEN}'
90
+ }
91
+ }
92
+ }
93
+ };
94
+ await fs.writeJson(settingsPath, settingsContent, { spaces: 2 });
95
+ console.log(chalk.green(' ✓ Created settings.json'));
96
+ }
97
+
98
+ // Copy rules
99
+ console.log(chalk.blue(' Copying rules...'));
100
+ try {
101
+ await copyRules(projectRoot);
102
+ console.log(chalk.green(' ✓ Copied rules'));
103
+ } catch (error) {
104
+ console.log(chalk.yellow(' ⚠ Could not copy rules (package may not have rules)'));
105
+ }
106
+
107
+ // Create lessons.md template
108
+ const lessonsPath = path.join(claudeDir, 'rules', 'lessons', 'lessons.md');
109
+ if (!(await fs.exists(lessonsPath))) {
110
+ const lessonsContent = `# Lessons Learned
111
+
112
+ ## Format
113
+ ### YYYY-MM-DD: Lesson Title
114
+ **What happened:** Description
115
+ **Lesson:** What we learned
116
+ **Rule added:** Rule to prevent same mistake
117
+
118
+ ## Recent Lessons
119
+ (No lessons yet - run /retrospect-work after sessions)
120
+ `;
121
+ await fs.writeFile(lessonsPath, lessonsContent);
122
+ console.log(chalk.green(' ✓ Created lessons.md'));
123
+ }
124
+
125
+ console.log('');
126
+ console.log(chalk.green('✅ Project initialized successfully!'));
127
+ console.log('');
128
+ console.log(chalk.blue('Next steps:'));
129
+ console.log(chalk.gray(' 1. Add an agent:'));
130
+ console.log(chalk.cyan(' npx @wipal/agent-team add <name> <role> [options]'));
131
+ console.log('');
132
+ console.log(chalk.gray(' 2. Or use interactive mode:'));
133
+ console.log(chalk.cyan(' npx @wipal/agent-team add'));
134
+ console.log('');
135
+ console.log(chalk.gray(' 3. Available roles: dev-fe, dev-be, sa, tech-lead, devops, pm, qa'));
136
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * List command - List all agents in the project
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { isInitialized, getAgents } from '../utils/file-utils.js';
7
+
8
+ /**
9
+ * List all agents
10
+ */
11
+ export async function listCommand() {
12
+ const projectRoot = process.cwd();
13
+
14
+ // Check if initialized
15
+ if (!(await isInitialized(projectRoot))) {
16
+ console.log(chalk.yellow('⚠️ Project not initialized'));
17
+ console.log(chalk.gray(' Run `npx @wipal/agent-team init` first'));
18
+ return;
19
+ }
20
+
21
+ // Get agents
22
+ const agents = await getAgents(projectRoot);
23
+
24
+ if (agents.length === 0) {
25
+ console.log(chalk.yellow('📋 No agents found'));
26
+ console.log('');
27
+ console.log(chalk.gray(' Create one with:'));
28
+ console.log(chalk.cyan(' npx @wipal/agent-team add <name> <role>'));
29
+ return;
30
+ }
31
+
32
+ console.log(chalk.blue(`📋 Agents in project (${agents.length})`));
33
+ console.log('');
34
+
35
+ // Table header
36
+ console.log(chalk.gray('┌─────────────────┬─────────────┬──────────────────────────────────────┐'));
37
+ console.log(chalk.gray('│') + chalk.white(' Agent ') + chalk.gray('│') + chalk.white(' Role ') + chalk.gray('│') + chalk.white(' Variants ') + chalk.gray('│'));
38
+ console.log(chalk.gray('├─────────────────┼─────────────┼──────────────────────────────────────┤'));
39
+
40
+ // Table rows
41
+ for (const agent of agents) {
42
+ const name = agent.name.padEnd(15).slice(0, 15);
43
+ const role = (agent.variants?.base_role || 'unknown').padEnd(11).slice(0, 11);
44
+
45
+ // Format variants
46
+ let variantsStr = '';
47
+ if (agent.variants?.installed_variants) {
48
+ const v = Object.entries(agent.variants.installed_variants)
49
+ .filter(([_, val]) => val)
50
+ .map(([k, val]) => `${k}=${val}`)
51
+ .join(', ');
52
+ variantsStr = v.padEnd(36).slice(0, 36);
53
+ } else {
54
+ variantsStr = '(none)'.padEnd(36);
55
+ }
56
+
57
+ console.log(chalk.gray('│') + chalk.cyan(name) + chalk.gray('│') + chalk.yellow(role) + chalk.gray('│') + chalk.gray(variantsStr) + chalk.gray('│'));
58
+ }
59
+
60
+ // Table footer
61
+ console.log(chalk.gray('└─────────────────┴─────────────┴──────────────────────────────────────┘'));
62
+
63
+ console.log('');
64
+ console.log(chalk.gray('Switch to an agent:'));
65
+ console.log(chalk.cyan(' npx @wipal/agent-team switch <name>'));
66
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Remove command - Remove an agent from the project
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import inquirer from 'inquirer';
7
+ import fs from 'fs-extra';
8
+ import { isInitialized, agentExists, getAgentDir, getAgents } from '../utils/file-utils.js';
9
+
10
+ /**
11
+ * Remove an agent
12
+ */
13
+ export async function removeCommand(name, options) {
14
+ const projectRoot = process.cwd();
15
+
16
+ // Check if initialized
17
+ if (!(await isInitialized(projectRoot))) {
18
+ console.log(chalk.red('❌ Project not initialized'));
19
+ console.log(chalk.gray(' Run `npx @wipal/agent-team init` first'));
20
+ return;
21
+ }
22
+
23
+ // Check if agent exists
24
+ if (!(await agentExists(name, projectRoot))) {
25
+ console.log(chalk.red(`❌ Agent '${name}' not found`));
26
+ console.log('');
27
+ console.log(chalk.gray('List available agents:'));
28
+ console.log(chalk.cyan(' npx @wipal/agent-team list'));
29
+ return;
30
+ }
31
+
32
+ const agentDir = getAgentDir(name, projectRoot);
33
+
34
+ // Confirm deletion unless --force
35
+ if (!options.force) {
36
+ console.log(chalk.yellow(`⚠️ This will remove agent '${name}' and all its data`));
37
+ console.log(chalk.gray(` Location: ${agentDir}`));
38
+ console.log('');
39
+
40
+ const answer = await inquirer.prompt([
41
+ {
42
+ type: 'confirm',
43
+ name: 'proceed',
44
+ message: `Remove agent '${name}'?`,
45
+ default: false
46
+ }
47
+ ]);
48
+
49
+ if (!answer.proceed) {
50
+ console.log(chalk.gray('Cancelled.'));
51
+ return;
52
+ }
53
+ }
54
+
55
+ // Remove the agent
56
+ console.log(chalk.blue(`Removing agent '${name}'...`));
57
+ await fs.remove(agentDir);
58
+
59
+ console.log(chalk.green(`✅ Agent '${name}' removed`));
60
+ console.log('');
61
+
62
+ // Show remaining agents
63
+ const remainingAgents = await getAgents(projectRoot);
64
+ if (remainingAgents.length > 0) {
65
+ console.log(chalk.gray(`Remaining agents: ${remainingAgents.length}`));
66
+ console.log(chalk.cyan(' npx @wipal/agent-team list'));
67
+ } else {
68
+ console.log(chalk.gray('No agents remaining'));
69
+ console.log(chalk.cyan(' npx @wipal/agent-team add <name> <role>'));
70
+ }
71
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Switch command - Show instructions to switch to an agent
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { isInitialized, agentExists, getAgentDir } from '../utils/file-utils.js';
7
+
8
+ /**
9
+ * Switch to an agent
10
+ */
11
+ export async function switchCommand(name) {
12
+ const projectRoot = process.cwd();
13
+
14
+ // Check if initialized
15
+ if (!(await isInitialized(projectRoot))) {
16
+ console.log(chalk.red('❌ Project not initialized'));
17
+ console.log(chalk.gray(' Run `npx @wipal/agent-team init` first'));
18
+ return;
19
+ }
20
+
21
+ // Check if agent exists
22
+ if (!(await agentExists(name, projectRoot))) {
23
+ console.log(chalk.red(`❌ Agent '${name}' not found`));
24
+ console.log('');
25
+ console.log(chalk.gray('List available agents:'));
26
+ console.log(chalk.cyan(' npx @wipal/agent-team list'));
27
+ return;
28
+ }
29
+
30
+ const agentDir = getAgentDir(name, projectRoot);
31
+ const claudeMdPath = `${agentDir}/CLAUDE.md`;
32
+
33
+ console.log(chalk.green(`✓ Switching to agent: ${name}`));
34
+ console.log('');
35
+ console.log(chalk.blue('Agent configuration:'));
36
+ console.log(chalk.gray(` ${claudeMdPath}`));
37
+ console.log('');
38
+ console.log(chalk.blue('How to use in Claude Code:'));
39
+ console.log('');
40
+ console.log(chalk.white('Option 1: Use the agent\'s CLAUDE.md directly'));
41
+ console.log(chalk.gray(' Claude Code will read instructions from:'));
42
+ console.log(chalk.cyan(` ${claudeMdPath}`));
43
+ console.log('');
44
+ console.log(chalk.white('Option 2: Copy content to project root CLAUDE.md'));
45
+ console.log(chalk.gray(' If you want this agent to be the default:'));
46
+ console.log(chalk.cyan(` cp ${claudeMdPath} .claude/CLAUDE.md`));
47
+ console.log('');
48
+ console.log(chalk.white('Option 3: Use /switch command (if available)'));
49
+ console.log(chalk.cyan(` /switch ${name}`));
50
+ console.log('');
51
+ console.log(chalk.gray('The agent has the following skills available:'));
52
+ console.log(chalk.gray(` ${agentDir}/skills/`));
53
+ }
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @wipal/agent-team
3
+ * Main entry point for programmatic usage
4
+ */
5
+
6
+ export { initCommand } from './commands/init.js';
7
+ export { addCommand } from './commands/add.js';
8
+ export { listCommand } from './commands/list.js';
9
+ export { switchCommand } from './commands/switch.js';
10
+ export { removeCommand } from './commands/remove.js';
11
+ export { resolveSkills } from './utils/skill-resolver.js';
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Interactive prompts for agent-team CLI
3
+ */
4
+
5
+ import inquirer from 'inquirer';
6
+ import chalk from 'chalk';
7
+ import {
8
+ AVAILABLE_ROLES,
9
+ VARIANT_CATEGORIES,
10
+ getVariantsForRole,
11
+ getAllSkills
12
+ } from '../utils/skill-resolver.js';
13
+ import { createAgent } from '../commands/add.js';
14
+
15
+ /**
16
+ * Run interactive add mode
17
+ */
18
+ export async function runInteractiveAdd(projectRoot, options = {}) {
19
+ console.log(chalk.blue('🎮 Interactive Mode'));
20
+ console.log('');
21
+
22
+ // Step 1: Agent name
23
+ const nameAnswer = await inquirer.prompt([
24
+ {
25
+ type: 'input',
26
+ name: 'name',
27
+ message: 'Agent name:',
28
+ default: options.name || 'my-agent',
29
+ validate: (input) => {
30
+ if (!input.trim()) return 'Agent name is required';
31
+ if (!/^[a-z0-9-]+$/.test(input)) {
32
+ return 'Use only lowercase letters, numbers, and hyphens';
33
+ }
34
+ return true;
35
+ }
36
+ }
37
+ ]);
38
+
39
+ // Step 2: Select role
40
+ const roleAnswer = await inquirer.prompt([
41
+ {
42
+ type: 'list',
43
+ name: 'role',
44
+ message: 'Select role:',
45
+ choices: AVAILABLE_ROLES.map(r => ({
46
+ name: `${r.name.padEnd(12)} - ${r.description}`,
47
+ value: r.name
48
+ })),
49
+ default: options.role
50
+ }
51
+ ]);
52
+
53
+ const selectedRole = roleAnswer.role;
54
+
55
+ // Step 3: Select variants based on role
56
+ const availableVariants = getVariantsForRole(selectedRole);
57
+ const selectedVariants = {};
58
+
59
+ console.log('');
60
+ console.log(chalk.blue('📦 Select variants (press Enter to skip a category):'));
61
+ console.log('');
62
+
63
+ for (const [category, config] of Object.entries(availableVariants)) {
64
+ const choices = config.options.map(opt => ({
65
+ name: opt.label,
66
+ value: opt.value
67
+ }));
68
+
69
+ // Add "None" option
70
+ choices.unshift({ name: 'None (skip)', value: null });
71
+
72
+ if (config.type === 'select') {
73
+ const answer = await inquirer.prompt([
74
+ {
75
+ type: 'list',
76
+ name: category,
77
+ message: `${config.label}:`,
78
+ choices,
79
+ default: null
80
+ }
81
+ ]);
82
+
83
+ if (answer[category]) {
84
+ selectedVariants[category] = answer[category];
85
+ }
86
+ } else if (config.type === 'checkbox') {
87
+ const answer = await inquirer.prompt([
88
+ {
89
+ type: 'checkbox',
90
+ name: category,
91
+ message: `${config.label}:`,
92
+ choices: config.options.map(opt => ({
93
+ name: opt.label,
94
+ value: opt.value,
95
+ checked: false
96
+ }))
97
+ }
98
+ ]);
99
+
100
+ if (answer[category] && answer[category].length > 0) {
101
+ // For checkbox, store as array or join if single
102
+ selectedVariants[category] = answer[category].join(',');
103
+ }
104
+ }
105
+ }
106
+
107
+ // Step 4: Show summary and confirm
108
+ console.log('');
109
+ console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
110
+ console.log(chalk.cyan(' Summary'));
111
+ console.log(chalk.cyan('═══════════════════════════════════════════════════════════════'));
112
+ console.log('');
113
+ console.log(chalk.white(` Agent Name: ${chalk.yellow(nameAnswer.name)}`));
114
+ console.log(chalk.white(` Role: ${chalk.yellow(selectedRole)}`));
115
+ console.log('');
116
+
117
+ // Show selected variants
118
+ const variantEntries = Object.entries(selectedVariants).filter(([_, v]) => v);
119
+ if (variantEntries.length > 0) {
120
+ console.log(chalk.white(' Variants:'));
121
+ for (const [category, value] of variantEntries) {
122
+ console.log(chalk.gray(` - ${category}: ${value}`));
123
+ }
124
+ } else {
125
+ console.log(chalk.gray(' Variants: None'));
126
+ }
127
+
128
+ // Show skills that will be installed
129
+ const skills = getAllSkills(selectedRole, selectedVariants);
130
+ console.log('');
131
+ console.log(chalk.white(` Skills to install (${skills.length}):`));
132
+ console.log(chalk.gray(` ${skills.join(', ')}`));
133
+
134
+ console.log('');
135
+
136
+ // Confirm
137
+ const confirmAnswer = await inquirer.prompt([
138
+ {
139
+ type: 'confirm',
140
+ name: 'proceed',
141
+ message: 'Create this agent?',
142
+ default: true
143
+ }
144
+ ]);
145
+
146
+ if (!confirmAnswer.proceed) {
147
+ console.log(chalk.yellow('Cancelled.'));
148
+ return;
149
+ }
150
+
151
+ // Create the agent
152
+ await createAgent(nameAnswer.name, selectedRole, selectedVariants, projectRoot);
153
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Agents API Routes
3
+ */
4
+
5
+ import { Router } from 'express';
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+ import {
9
+ getClaudeDir,
10
+ getAgentsDir,
11
+ getAgentDir,
12
+ isInitialized,
13
+ agentExists,
14
+ getAgents
15
+ } from '../../utils/file-utils.js';
16
+ import {
17
+ resolveSkills,
18
+ getAllSkills,
19
+ AVAILABLE_ROLES,
20
+ VARIANT_CATEGORIES,
21
+ getVariantsForRole
22
+ } from '../../utils/skill-resolver.js';
23
+ import { createAgent } from '../../commands/add.js';
24
+
25
+ const router = Router();
26
+
27
+ /**
28
+ * List all agents
29
+ */
30
+ router.get('/', async (req, res) => {
31
+ try {
32
+ const projectRoot = req.app.get('projectRoot');
33
+
34
+ if (!(await isInitialized(projectRoot))) {
35
+ return res.json({ initialized: false, agents: [] });
36
+ }
37
+
38
+ const agents = await getAgents(projectRoot);
39
+ res.json({ initialized: true, agents });
40
+ } catch (error) {
41
+ res.status(500).json({ error: error.message });
42
+ }
43
+ });
44
+
45
+ /**
46
+ * Get single agent
47
+ */
48
+ router.get('/:name', async (req, res) => {
49
+ try {
50
+ const { name } = req.params;
51
+ const projectRoot = req.app.get('projectRoot');
52
+ const agentDir = getAgentDir(name, projectRoot);
53
+
54
+ if (!(await fs.exists(agentDir))) {
55
+ return res.status(404).json({ error: 'Agent not found' });
56
+ }
57
+
58
+ const variants = await fs.readJson(path.join(agentDir, 'variants.json'));
59
+ const claudeMd = await fs.readFile(path.join(agentDir, 'CLAUDE.md'), 'utf-8');
60
+
61
+ // Get skills list
62
+ const skillsDir = path.join(agentDir, 'skills');
63
+ let skills = [];
64
+ if (await fs.exists(skillsDir)) {
65
+ const skillEntries = await fs.readdir(skillsDir, { withFileTypes: true });
66
+ skills = skillEntries.filter(e => e.isDirectory()).map(e => e.name);
67
+ }
68
+
69
+ res.json({
70
+ name,
71
+ path: agentDir,
72
+ variants,
73
+ skills,
74
+ claudeMd
75
+ });
76
+ } catch (error) {
77
+ res.status(500).json({ error: error.message });
78
+ }
79
+ });
80
+
81
+ /**
82
+ * Create new agent
83
+ */
84
+ router.post('/', async (req, res) => {
85
+ try {
86
+ const { name, role, variants } = req.body;
87
+ const projectRoot = req.app.get('projectRoot');
88
+
89
+ if (!(await isInitialized(projectRoot))) {
90
+ return res.status(400).json({ error: 'Project not initialized' });
91
+ }
92
+
93
+ if (await agentExists(name, projectRoot)) {
94
+ return res.status(400).json({ error: 'Agent already exists' });
95
+ }
96
+
97
+ await createAgent(name, role, variants || {}, projectRoot);
98
+
99
+ res.json({ success: true, name, role });
100
+ } catch (error) {
101
+ res.status(500).json({ error: error.message });
102
+ }
103
+ });
104
+
105
+ /**
106
+ * Delete agent
107
+ */
108
+ router.delete('/:name', async (req, res) => {
109
+ try {
110
+ const { name } = req.params;
111
+ const projectRoot = req.app.get('projectRoot');
112
+ const agentDir = getAgentDir(name, projectRoot);
113
+
114
+ if (!(await fs.exists(agentDir))) {
115
+ return res.status(404).json({ error: 'Agent not found' });
116
+ }
117
+
118
+ await fs.remove(agentDir);
119
+ res.json({ success: true });
120
+ } catch (error) {
121
+ res.status(500).json({ error: error.message });
122
+ }
123
+ });
124
+
125
+ /**
126
+ * Get available roles
127
+ */
128
+ router.get('/meta/roles', (req, res) => {
129
+ res.json(AVAILABLE_ROLES);
130
+ });
131
+
132
+ /**
133
+ * Get available variants for a role
134
+ */
135
+ router.get('/meta/variants/:role', (req, res) => {
136
+ const { role } = req.params;
137
+ const variants = getVariantsForRole(role);
138
+ res.json(variants);
139
+ });
140
+
141
+ /**
142
+ * Preview skills for role + variants
143
+ */
144
+ router.post('/preview-skills', (req, res) => {
145
+ const { role, variants } = req.body;
146
+ const skills = resolveSkills(role, variants || {});
147
+ res.json(skills);
148
+ });
149
+
150
+ export default router;