create-byan-agent 2.7.0 → 2.7.2

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.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * YANSTALLER - Main Orchestrator
3
+ *
4
+ * Coordinates all YANSTALLER modules to perform intelligent BYAN installation.
5
+ *
6
+ * @module yanstaller
7
+ */
8
+
9
+ const detector = require('./detector');
10
+ const recommender = require('./recommender');
11
+ const installer = require('./installer');
12
+ const validator = require('./validator');
13
+ const troubleshooter = require('./troubleshooter');
14
+ const interviewer = require('./interviewer');
15
+ const backuper = require('./backuper');
16
+ const wizard = require('./wizard');
17
+ const platformSelector = require('./platform-selector');
18
+ const logger = require('../utils/logger');
19
+
20
+ /**
21
+ * @typedef {Object} YanInstallerOptions
22
+ * @property {boolean} [yes] - Skip confirmations (--yes flag)
23
+ * @property {string} [mode] - Installation mode: 'full' | 'minimal' | 'custom'
24
+ * @property {string[]} [platforms] - Target platforms (override detection)
25
+ * @property {boolean} [verbose] - Verbose output
26
+ * @property {boolean} [quiet] - Minimal output
27
+ */
28
+
29
+ /**
30
+ * Main installation flow
31
+ *
32
+ * @param {YanInstallerOptions} [options={}] - Installation options
33
+ * @returns {Promise<void>}
34
+ */
35
+ async function install(options = {}) {
36
+ let backupPath = null;
37
+
38
+ try {
39
+ // Phase 1: Detect environment
40
+ logger.info('šŸ” Detecting environment...');
41
+ const detection = await detector.detect();
42
+
43
+ // Phase 2: Validate Node version (FAIL FAST)
44
+ if (!detector.isNodeVersionValid(detection.nodeVersion, '18.0.0')) {
45
+ throw new Error(`Node.js >= 18.0.0 required. Found: ${detection.nodeVersion}`);
46
+ }
47
+
48
+ // Phase 3: Platform Selection
49
+ let platformSelection;
50
+ if (options.platforms) {
51
+ // CLI override
52
+ platformSelection = {
53
+ platforms: options.platforms,
54
+ mode: 'manual'
55
+ };
56
+ } else if (options.yes) {
57
+ // Auto mode
58
+ platformSelection = {
59
+ platforms: detection.platforms.filter(p => p.detected).map(p => p.name),
60
+ mode: 'auto'
61
+ };
62
+ } else {
63
+ // Interactive selection
64
+ platformSelection = await platformSelector.select(detection);
65
+ }
66
+
67
+ logger.info(`\nāœ“ Selected ${platformSelection.platforms.length} platform(s)`);
68
+ logger.info(` Mode: ${platformSelection.mode}`);
69
+ if (platformSelection.specialist) {
70
+ logger.info(` Specialist: @bmad-agent-${platformSelection.specialist}`);
71
+ }
72
+
73
+ // Phase 4: Recommend configuration
74
+ // TODO: Implement
75
+
76
+ // Phase 5: Run interview (unless --yes)
77
+ // TODO: Implement
78
+
79
+ // Phase 6: Backup existing installation
80
+ // backupPath = await backuper.backup('_bmad');
81
+
82
+ // Phase 7: Install agents
83
+ // TODO: Implement
84
+
85
+ // Phase 8: Validate installation
86
+ // TODO: Implement
87
+
88
+ // Phase 9: Show post-install wizard
89
+ // TODO: Implement
90
+ } catch (error) {
91
+ // ROLLBACK STRATEGY: Leave partial state + clear message
92
+ // Rationale (Mantra #37 Ockham's Razor):
93
+ // - Installation = mostly file copies (low risk)
94
+ // - User can re-run (idempotent)
95
+ // - Backup exists for manual restore
96
+ // - Auto-rollback risks losing working partial install
97
+
98
+ logger.error('Installation failed:', error.message);
99
+
100
+ if (backupPath) {
101
+ logger.info('\nPartial installation completed.');
102
+ logger.info(`Backup available at: ${backupPath}`);
103
+ logger.info('\nOptions:');
104
+ logger.info('1. Re-run: npx create-byan-agent');
105
+ logger.info(`2. Restore backup: yanstaller restore ${backupPath}`);
106
+ logger.info('3. Troubleshoot: yanstaller doctor');
107
+ }
108
+
109
+ throw error; // Re-throw for exit code handling
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Uninstall BYAN
115
+ *
116
+ * @returns {Promise<void>}
117
+ */
118
+ async function uninstall() {
119
+ // TODO: Remove _bmad/, .github/agents/ stubs
120
+ }
121
+
122
+ /**
123
+ * Update existing BYAN installation
124
+ *
125
+ * @param {string} version - Target version
126
+ * @returns {Promise<void>}
127
+ */
128
+ async function update(version) {
129
+ // TODO: Backup → Update agents → Merge configs
130
+ }
131
+
132
+ module.exports = {
133
+ install,
134
+ uninstall,
135
+ update,
136
+ // Expose for testing
137
+ detector,
138
+ platformSelector
139
+ };
@@ -0,0 +1,140 @@
1
+ /**
2
+ * INSTALLER Module
3
+ *
4
+ * Installs BYAN agents across multiple platforms.
5
+ * Most complex module: 56h development.
6
+ *
7
+ * Phase 3: 56h development
8
+ *
9
+ * @module yanstaller/installer
10
+ */
11
+
12
+ const path = require('path');
13
+ const fileUtils = require('../utils/file-utils');
14
+ const logger = require('../utils/logger');
15
+
16
+ /**
17
+ * @typedef {Object} InstallConfig
18
+ * @property {string} mode - 'full' | 'minimal' | 'custom'
19
+ * @property {string[]} agents - Agent names to install
20
+ * @property {string} userName - User's name
21
+ * @property {string} language - 'Francais' | 'English'
22
+ * @property {string[]} targetPlatforms - Platforms to install on
23
+ * @property {string} outputFolder - Output folder path
24
+ * @property {string} projectRoot - Project root directory
25
+ */
26
+
27
+ /**
28
+ * @typedef {Object} InstallResult
29
+ * @property {boolean} success
30
+ * @property {number} agentsInstalled - Number of agents installed
31
+ * @property {string[]} platforms - Platforms installed on
32
+ * @property {string[]} errors - Installation errors
33
+ * @property {number} duration - Duration in ms
34
+ */
35
+
36
+ /**
37
+ * Install BYAN agents
38
+ *
39
+ * @param {InstallConfig} config - Installation configuration
40
+ * @returns {Promise<InstallResult>}
41
+ */
42
+ async function install(config) {
43
+ const startTime = Date.now();
44
+ const errors = [];
45
+
46
+ try {
47
+ // TODO: Implement installation
48
+ // 1. Create _bmad/ structure
49
+ // 2. Copy agent files from templates/
50
+ // 3. Generate platform-specific stubs
51
+ // 4. Create config files
52
+
53
+ logger.info(`Installing ${config.agents.length} agents...`);
54
+
55
+ // Placeholder
56
+ await new Promise(resolve => setTimeout(resolve, 1000));
57
+
58
+ return {
59
+ success: true,
60
+ agentsInstalled: config.agents.length,
61
+ platforms: config.targetPlatforms,
62
+ errors,
63
+ duration: Date.now() - startTime
64
+ };
65
+ } catch (error) {
66
+ errors.push(error.message);
67
+ return {
68
+ success: false,
69
+ agentsInstalled: 0,
70
+ platforms: [],
71
+ errors,
72
+ duration: Date.now() - startTime
73
+ };
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Create _bmad/ directory structure
79
+ *
80
+ * @param {string} projectRoot - Project root directory
81
+ * @returns {Promise<void>}
82
+ */
83
+ async function createBmadStructure(projectRoot) {
84
+ // TODO: Create directories
85
+ // _bmad/
86
+ // ā”œā”€ā”€ _config/
87
+ // ā”œā”€ā”€ _memory/
88
+ // ā”œā”€ā”€ core/
89
+ // ā”œā”€ā”€ bmm/
90
+ // ā”œā”€ā”€ bmb/
91
+ // ā”œā”€ā”€ tea/
92
+ // └── cis/
93
+ }
94
+
95
+ /**
96
+ * Copy agent file from template
97
+ *
98
+ * @param {string} agentName - Agent name
99
+ * @param {string} targetPath - Target directory
100
+ * @returns {Promise<void>}
101
+ */
102
+ async function copyAgentFile(agentName, targetPath) {
103
+ // TODO: Copy from lib/templates/agents/{agentName}.md
104
+ }
105
+
106
+ /**
107
+ * Generate platform-specific stub
108
+ *
109
+ * @param {string} agentName - Agent name
110
+ * @param {string} platform - Platform name
111
+ * @param {string} targetPath - Target directory
112
+ * @returns {Promise<void>}
113
+ */
114
+ async function generateStub(agentName, platform, targetPath) {
115
+ // TODO: Generate stub based on platform
116
+ // - Copilot CLI: .github/agents/{agentName}.md with YAML frontmatter
117
+ // - VSCode: Same as Copilot CLI
118
+ // - Claude Code: MCP config JSON
119
+ // - Codex: .codex/prompts/{agentName}.md
120
+ }
121
+
122
+ /**
123
+ * Create module config file
124
+ *
125
+ * @param {string} moduleName - Module name (core, bmm, bmb, tea, cis)
126
+ * @param {InstallConfig} config - Installation config
127
+ * @param {string} targetPath - Target directory
128
+ * @returns {Promise<void>}
129
+ */
130
+ async function createModuleConfig(moduleName, config, targetPath) {
131
+ // TODO: Generate config.yaml with user settings
132
+ }
133
+
134
+ module.exports = {
135
+ install,
136
+ createBmadStructure,
137
+ copyAgentFile,
138
+ generateStub,
139
+ createModuleConfig
140
+ };
@@ -0,0 +1,88 @@
1
+ /**
2
+ * INTERVIEWER Module
3
+ *
4
+ * Conducts quick interview (5-7 questions, <5 min) to personalize installation.
5
+ *
6
+ * Phase 6 (part of 7): 16h development
7
+ *
8
+ * @module yanstaller/interviewer
9
+ */
10
+
11
+ const inquirer = require('inquirer');
12
+
13
+ /**
14
+ * @typedef {Object} InterviewResult
15
+ * @property {string} userName
16
+ * @property {string} language - 'Francais' | 'English'
17
+ * @property {string} mode - 'full' | 'minimal' | 'custom'
18
+ * @property {string[]} agents - Selected agents (if custom mode)
19
+ * @property {string[]} targetPlatforms - Platforms to install on
20
+ * @property {boolean} createSampleAgent - Whether to create sample agent after install
21
+ */
22
+
23
+ /**
24
+ * Run quick interview
25
+ *
26
+ * @param {import('./recommender').Recommendation} recommendation - Recommended config
27
+ * @returns {Promise<InterviewResult>}
28
+ */
29
+ async function ask(recommendation) {
30
+ // TODO: Implement inquirer prompts
31
+ // Q1: Your name?
32
+ // Q2: Preferred language?
33
+ // Q3: Installation mode? (with recommendation)
34
+ // Q4: (if custom) Which agents?
35
+ // Q5: Which platforms to install on?
36
+ // Q6: Create sample agent after install?
37
+
38
+ return {
39
+ userName: 'User',
40
+ language: 'English',
41
+ mode: recommendation.mode,
42
+ agents: recommendation.agents,
43
+ targetPlatforms: ['copilot-cli'],
44
+ createSampleAgent: false
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Ask single question
50
+ *
51
+ * @param {string} question - Question text
52
+ * @param {string} type - 'input' | 'list' | 'confirm' | 'checkbox'
53
+ * @param {Array} [choices] - Choices for list/checkbox
54
+ * @returns {Promise<any>}
55
+ */
56
+ async function askQuestion(question, type, choices = []) {
57
+ // TODO: Use inquirer
58
+ const answer = await inquirer.prompt([
59
+ {
60
+ type,
61
+ name: 'answer',
62
+ message: question,
63
+ choices
64
+ }
65
+ ]);
66
+
67
+ return answer.answer;
68
+ }
69
+
70
+ /**
71
+ * Get available agents list for custom selection
72
+ *
73
+ * @returns {Array<{name: string, value: string, checked: boolean}>}
74
+ */
75
+ function getAgentChoices() {
76
+ // TODO: Return all 29 agents with descriptions
77
+ return [
78
+ { name: 'BYAN - Agent Creator', value: 'byan', checked: true },
79
+ { name: 'RACHID - NPM Deployment', value: 'rachid', checked: true },
80
+ // ... 27 more
81
+ ];
82
+ }
83
+
84
+ module.exports = {
85
+ ask,
86
+ askQuestion,
87
+ getAgentChoices
88
+ };
@@ -0,0 +1,328 @@
1
+ /**
2
+ * PLATFORM SELECTOR Module
3
+ *
4
+ * Interactive platform selection for BYAN installation.
5
+ * Detects available platforms and lets user choose target(s).
6
+ *
7
+ * @module yanstaller/platform-selector
8
+ */
9
+
10
+ const inquirer = require('inquirer');
11
+ const logger = require('../utils/logger');
12
+ const platforms = require('../platforms');
13
+
14
+ /**
15
+ * @typedef {Object} PlatformChoice
16
+ * @property {string} name - Display name
17
+ * @property {string} id - Platform ID ('copilot-cli' | 'vscode' | 'claude' | 'codex')
18
+ * @property {boolean} detected - Is platform installed?
19
+ * @property {string} [path] - Installation path if detected
20
+ * @property {boolean} native - Native integration available?
21
+ * @property {string} [agentSpecialist] - Agent specialist for this platform
22
+ */
23
+
24
+ /**
25
+ * @typedef {Object} PlatformSelectionResult
26
+ * @property {string[]} platforms - Selected platform IDs
27
+ * @property {string} mode - 'native' | 'conversational' | 'auto'
28
+ * @property {string} [specialist] - Agent specialist to use
29
+ */
30
+
31
+ const PLATFORM_INFO = {
32
+ 'copilot-cli': {
33
+ displayName: 'GitHub Copilot CLI',
34
+ native: true,
35
+ specialist: 'marc',
36
+ icon: 'šŸ¤–'
37
+ },
38
+ 'claude': {
39
+ displayName: 'Claude Code',
40
+ native: true,
41
+ specialist: 'claude',
42
+ icon: 'šŸŽ­'
43
+ },
44
+ 'codex': {
45
+ displayName: 'OpenCode/Codex',
46
+ native: true, // NOW NATIVE!
47
+ specialist: 'codex',
48
+ icon: 'šŸ“'
49
+ },
50
+ 'vscode': {
51
+ displayName: 'VS Code',
52
+ native: false,
53
+ specialist: null,
54
+ icon: 'šŸ’»'
55
+ }
56
+ };
57
+
58
+ /**
59
+ * Detect and select platforms interactively
60
+ *
61
+ * @param {Object} detectionResult - Result from detector.detect()
62
+ * @returns {Promise<PlatformSelectionResult>}
63
+ */
64
+ async function select(detectionResult) {
65
+ logger.info('\nšŸŽÆ Platform Selection\n');
66
+
67
+ // Build platform choices from detection
68
+ const choices = buildChoices(detectionResult.platforms);
69
+
70
+ // Check if any native integration available
71
+ const hasNative = choices.some(c => c.native && c.detected);
72
+
73
+ if (choices.length === 0) {
74
+ logger.warn('No platforms detected. Will create _byan/ structure only.');
75
+ return {
76
+ platforms: [],
77
+ mode: 'manual'
78
+ };
79
+ }
80
+
81
+ // Show primary platform selection first
82
+ const nativePlatforms = choices.filter(c => c.native && c.detected);
83
+
84
+ // Step 1: Choose primary platform (if native available)
85
+ if (nativePlatforms.length > 0) {
86
+ const primaryAnswer = await inquirer.prompt([
87
+ {
88
+ type: 'list',
89
+ name: 'primary',
90
+ message: 'šŸŽÆ Choose your PRIMARY platform for native agent invocation:',
91
+ choices: [
92
+ ...nativePlatforms.map(c => ({
93
+ name: `${c.icon} ${c.name} - ${c.agentSpecialist ? `@bmad-agent-${c.agentSpecialist}` : 'No specialist'}`,
94
+ value: c.id,
95
+ short: c.name
96
+ })),
97
+ new inquirer.Separator(),
98
+ {
99
+ name: 'šŸ”§ Advanced: Install on multiple platforms',
100
+ value: 'multi'
101
+ },
102
+ {
103
+ name: 'ā­ļø Skip native integration (manual install only)',
104
+ value: 'skip'
105
+ }
106
+ ]
107
+ }
108
+ ]);
109
+
110
+ // If user chose single platform, return immediately
111
+ if (primaryAnswer.primary !== 'multi' && primaryAnswer.primary !== 'skip') {
112
+ const platform = nativePlatforms.find(c => c.id === primaryAnswer.primary);
113
+ return {
114
+ platforms: [primaryAnswer.primary],
115
+ mode: 'native',
116
+ specialist: platform.agentSpecialist,
117
+ primary: primaryAnswer.primary
118
+ };
119
+ }
120
+
121
+ // If skip, fall through to conversational mode
122
+ if (primaryAnswer.primary === 'skip') {
123
+ return {
124
+ platforms: choices.map(c => c.id),
125
+ mode: 'conversational'
126
+ };
127
+ }
128
+
129
+ // If multi, show full menu
130
+ }
131
+
132
+ // Step 2: Full menu (multi-platform or no native available)
133
+ const answer = await inquirer.prompt([
134
+ {
135
+ type: 'list',
136
+ name: 'selection',
137
+ message: 'Choose installation target:',
138
+ choices: [
139
+ {
140
+ name: `šŸš€ Auto (detect & install all available) - ${choices.length} platform(s)`,
141
+ value: 'auto'
142
+ },
143
+ ...choices.map(c => ({
144
+ name: formatPlatformChoice(c),
145
+ value: `single:${c.id}`
146
+ })),
147
+ {
148
+ name: 'šŸ”§ Custom (select multiple)',
149
+ value: 'custom'
150
+ }
151
+ ]
152
+ }
153
+ ]);
154
+
155
+ if (answer.selection === 'auto') {
156
+ return handleAutoMode(choices);
157
+ }
158
+
159
+ if (answer.selection === 'custom') {
160
+ return handleCustomMode(choices);
161
+ }
162
+
163
+ // Single platform selection
164
+ const platformId = answer.selection.replace('single:', '');
165
+ return handleSinglePlatform(platformId, choices);
166
+ }
167
+
168
+ /**
169
+ * Build platform choices from detection result
170
+ *
171
+ * @param {Array} detectedPlatforms - From detector
172
+ * @returns {PlatformChoice[]}
173
+ */
174
+ function buildChoices(detectedPlatforms) {
175
+ return detectedPlatforms
176
+ .filter(p => p.detected)
177
+ .map(p => {
178
+ const info = PLATFORM_INFO[p.name] || {
179
+ displayName: p.name,
180
+ native: false,
181
+ specialist: null,
182
+ icon: 'ā“'
183
+ };
184
+
185
+ return {
186
+ name: info.displayName,
187
+ id: p.name,
188
+ detected: p.detected,
189
+ path: p.path,
190
+ native: info.native,
191
+ agentSpecialist: info.specialist,
192
+ icon: info.icon
193
+ };
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Format platform choice for display
199
+ *
200
+ * @param {PlatformChoice} choice
201
+ * @returns {string}
202
+ */
203
+ function formatPlatformChoice(choice) {
204
+ const nativeBadge = choice.native ? '✨ Native' : 'šŸ’¬ Conversational';
205
+ const statusBadge = choice.detected ? 'āœ“' : 'āœ—';
206
+
207
+ return `${choice.icon} ${choice.name} (${nativeBadge}) ${statusBadge}`;
208
+ }
209
+
210
+ /**
211
+ * Handle auto mode - install on all detected platforms
212
+ *
213
+ * @param {PlatformChoice[]} choices
214
+ * @returns {Promise<PlatformSelectionResult>}
215
+ */
216
+ async function handleAutoMode(choices) {
217
+ const nativePlatforms = choices.filter(c => c.native);
218
+
219
+ if (nativePlatforms.length > 0) {
220
+ // Use native integration for first platform (or all?)
221
+ return {
222
+ platforms: choices.map(c => c.id),
223
+ mode: 'native',
224
+ specialist: nativePlatforms[0].agentSpecialist
225
+ };
226
+ }
227
+
228
+ return {
229
+ platforms: choices.map(c => c.id),
230
+ mode: 'conversational'
231
+ };
232
+ }
233
+
234
+ /**
235
+ * Handle custom mode - user selects multiple platforms
236
+ *
237
+ * @param {PlatformChoice[]} choices
238
+ * @returns {Promise<PlatformSelectionResult>}
239
+ */
240
+ async function handleCustomMode(choices) {
241
+ const answer = await inquirer.prompt([
242
+ {
243
+ type: 'checkbox',
244
+ name: 'platforms',
245
+ message: 'Select platforms to install on:',
246
+ choices: choices.map(c => ({
247
+ name: formatPlatformChoice(c),
248
+ value: c.id,
249
+ checked: c.detected
250
+ }))
251
+ }
252
+ ]);
253
+
254
+ if (answer.platforms.length === 0) {
255
+ logger.warn('No platforms selected. Installation cancelled.');
256
+ return {
257
+ platforms: [],
258
+ mode: 'manual'
259
+ };
260
+ }
261
+
262
+ // Check if any selected platform has native integration
263
+ const selectedChoices = choices.filter(c => answer.platforms.includes(c.id));
264
+ const nativePlatform = selectedChoices.find(c => c.native);
265
+
266
+ if (nativePlatform) {
267
+ return {
268
+ platforms: answer.platforms,
269
+ mode: 'native',
270
+ specialist: nativePlatform.agentSpecialist
271
+ };
272
+ }
273
+
274
+ return {
275
+ platforms: answer.platforms,
276
+ mode: 'conversational'
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Handle single platform selection
282
+ *
283
+ * @param {string} platformId
284
+ * @param {PlatformChoice[]} choices
285
+ * @returns {PlatformSelectionResult}
286
+ */
287
+ function handleSinglePlatform(platformId, choices) {
288
+ const choice = choices.find(c => c.id === platformId);
289
+
290
+ if (!choice) {
291
+ throw new Error(`Platform ${platformId} not found`);
292
+ }
293
+
294
+ return {
295
+ platforms: [platformId],
296
+ mode: choice.native ? 'native' : 'conversational',
297
+ specialist: choice.agentSpecialist
298
+ };
299
+ }
300
+
301
+ /**
302
+ * Get specialist agent for platform
303
+ *
304
+ * @param {string} platformId
305
+ * @returns {string|null}
306
+ */
307
+ function getSpecialist(platformId) {
308
+ const info = PLATFORM_INFO[platformId];
309
+ return info ? (info.specialist || null) : null;
310
+ }
311
+
312
+ /**
313
+ * Check if platform has native integration
314
+ *
315
+ * @param {string} platformId
316
+ * @returns {boolean}
317
+ */
318
+ function hasNativeIntegration(platformId) {
319
+ const info = PLATFORM_INFO[platformId];
320
+ return info ? (info.native || false) : false;
321
+ }
322
+
323
+ module.exports = {
324
+ select,
325
+ getSpecialist,
326
+ hasNativeIntegration,
327
+ PLATFORM_INFO
328
+ };