create-byan-agent 1.2.3 → 1.2.5

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.
package/CHANGELOG.md CHANGED
@@ -5,7 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [1.2.3] - 2026-02-03
8
+ ## [1.2.5] - 2026-02-04
9
+
10
+ ### Added
11
+ - **Interactive override**: `--interactive` flag to force prompts even without TTY (useful in npm scripts).
12
+
13
+ ### Changed
14
+ - **Interview defaults**: Preselect detected or provided platforms during the interview.
15
+ - **E2E**: Run with explicit platforms to validate stub generation.
16
+
17
+ ### Fixed
18
+ - **Silent mode**: Skip interview and wizard with safe defaults to prevent hangs in non-interactive runs.
19
+ - **Platform normalization**: Map `claude` to `claude-code` for installer/platform parity.
20
+ - **Validator**: Use YAML parser correctly and remove false warnings (permissions/templates).
21
+
22
+ ## [1.2.3] - 2026-02-03
9
23
 
10
24
  ### Added
11
25
  - **E2E Test Suite** - Automated end-to-end testing before npm publish
package/README.md CHANGED
@@ -1015,13 +1015,19 @@ const validation = await yanstaller.validate({
1015
1015
  console.log(validation.errors); // []
1016
1016
  ```
1017
1017
 
1018
- #### CLI Options
1019
- ```bash
1020
- # Silent installation with specific agents
1021
- create-byan-agent --silent --agents=byan,dev,quinn
1022
-
1023
- # Custom mode with specific platform
1024
- create-byan-agent --mode=custom --platforms=copilot-cli
1018
+ #### CLI Options
1019
+ ```bash
1020
+ # Silent installation with specific agents
1021
+ create-byan-agent --silent --agents=byan,dev,quinn
1022
+
1023
+ # Force interactive prompts (useful inside npm scripts or CI shells without TTY)
1024
+ create-byan-agent --interactive
1025
+
1026
+ # Custom mode with specific platform
1027
+ create-byan-agent --mode=custom --platforms=copilot-cli
1028
+
1029
+ # Install on all supported platforms
1030
+ create-byan-agent --platforms=all
1025
1031
 
1026
1032
  # Full installation without backup
1027
1033
  create-byan-agent --mode=full --no-backup
@@ -14,9 +14,36 @@ const wizard = require('../lib/yanstaller/wizard');
14
14
  const backuper = require('../lib/yanstaller/backuper');
15
15
  const logger = require('../lib/utils/logger');
16
16
 
17
- const YANSTALLER_VERSION = '1.2.3';
18
-
19
- // ASCII Art Banner
17
+ const YANSTALLER_VERSION = '1.2.3';
18
+
19
+ function parseList(value) {
20
+ if (!value) return [];
21
+ if (Array.isArray(value)) return value.map(v => String(v).trim()).filter(Boolean);
22
+ return String(value)
23
+ .split(',')
24
+ .map(v => v.trim())
25
+ .filter(Boolean);
26
+ }
27
+
28
+ function normalizePlatformName(name) {
29
+ if (!name) return name;
30
+ const lower = String(name).toLowerCase();
31
+ if (lower === 'claude') return 'claude-code';
32
+ return lower;
33
+ }
34
+
35
+ function normalizePlatforms(list) {
36
+ return list.map(normalizePlatformName).filter(Boolean);
37
+ }
38
+
39
+ function expandAllPlatforms(list) {
40
+ if (list.includes('all')) {
41
+ return ['copilot-cli', 'vscode', 'codex', 'claude-code'];
42
+ }
43
+ return list;
44
+ }
45
+
46
+ // ASCII Art Banner
20
47
  const banner = `
21
48
  ${chalk.blue('╔════════════════════════════════════════════════════════════╗')}
22
49
  ${chalk.blue('║')} ${chalk.blue('║')}
@@ -41,7 +68,7 @@ ${chalk.blue('╚═════════════════════
41
68
  * 6. VALIDATE - 10 automated checks
42
69
  * 7. WIZARD - Post-install actions
43
70
  */
44
- async function main() {
71
+ async function main(options = {}) {
45
72
  try {
46
73
  console.clear();
47
74
  console.log(banner);
@@ -72,11 +99,75 @@ async function main() {
72
99
  logger.info(`✓ Recommended agents: ${chalk.cyan(recommendations.agents.join(', '))}`);
73
100
  }
74
101
 
75
- // STEP 3: INTERVIEW - 7-Question Personalization
76
- logger.info(chalk.bold('\n🎙️ STEP 3/7: Interview\n'));
77
- const answers = await interviewer.ask(recommendations);
78
-
79
- // STEP 4: BACKUP (optional)
102
+ // STEP 3: INTERVIEW - 7-Question Personalization
103
+ const isSilent = !!options.silent;
104
+ const forceInteractive = !!options.interactive;
105
+ const hasTty = !!process.stdin.isTTY;
106
+ const forceSilent = !hasTty && !isSilent && !forceInteractive;
107
+
108
+ if (forceSilent) {
109
+ logger.warn('No interactive TTY detected. Falling back to silent mode.');
110
+ }
111
+ if (!hasTty && forceInteractive) {
112
+ logger.warn('Interactive mode forced without TTY. Prompts may not render correctly.');
113
+ }
114
+
115
+ let answers;
116
+
117
+ if (isSilent || forceSilent) {
118
+ logger.info(chalk.bold('\nSTEP 3/7: Interview (skipped - silent)\n'));
119
+
120
+ const parsedAgents = parseList(options.agents);
121
+ const parsedPlatforms = expandAllPlatforms(normalizePlatforms(parseList(options.platforms)));
122
+
123
+ let mode = options.mode || (parsedAgents.length > 0 ? 'custom' : (recommendations.mode || 'minimal'));
124
+ let agents = parsedAgents;
125
+
126
+ if (agents.length === 0) {
127
+ if (mode === 'recommended' && recommendations && recommendations.agents) {
128
+ agents = recommendations.agents;
129
+ } else if (mode === 'minimal' || mode === 'full') {
130
+ agents = recommender.getAgentList(mode);
131
+ } else if (mode === 'custom') {
132
+ logger.warn('Custom mode selected without agents. Falling back to recommendations.');
133
+ agents = recommendations.agents || ['byan'];
134
+ mode = 'recommended';
135
+ } else {
136
+ agents = recommendations.agents || ['byan'];
137
+ mode = recommendations.mode || 'minimal';
138
+ }
139
+ }
140
+
141
+ let targetPlatforms = parsedPlatforms;
142
+ if (targetPlatforms.length === 0) {
143
+ targetPlatforms = (detection.platforms || [])
144
+ .filter(p => p.detected)
145
+ .map(p => normalizePlatformName(p.name));
146
+ }
147
+
148
+ answers = {
149
+ userName: 'Developer',
150
+ language: 'English',
151
+ mode,
152
+ agents,
153
+ targetPlatforms,
154
+ createSampleAgent: false,
155
+ createBackup: options.backup !== false
156
+ };
157
+ } else {
158
+ logger.info(chalk.bold('\nSTEP 3/7: Interview\n'));
159
+ const preferredPlatforms = expandAllPlatforms(normalizePlatforms(parseList(options.platforms)));
160
+ answers = await interviewer.ask(recommendations, {
161
+ detection,
162
+ preferredPlatforms
163
+ });
164
+
165
+ if (options.backup === false) {
166
+ answers.createBackup = false;
167
+ }
168
+ }
169
+
170
+ // STEP 4: BACKUP (optional)
80
171
  if (answers.createBackup) {
81
172
  logger.info(chalk.bold('\n💾 STEP 4/7: Backup\n'));
82
173
  try {
@@ -122,18 +213,22 @@ async function main() {
122
213
  }
123
214
  }
124
215
 
125
- // STEP 7: WIZARD - Post-Install Actions
126
- logger.info(chalk.bold('\n🧙 STEP 7/7: Post-Install Wizard\n'));
127
- await wizard.show({
128
- agents: answers.agents,
129
- targetPlatforms: answers.targetPlatforms,
130
- mode: answers.mode,
131
- projectRoot,
132
- userName: answers.userName,
133
- language: answers.language
134
- });
135
-
136
- } catch (error) {
216
+ // STEP 7: WIZARD - Post-Install Actions
217
+ if (isSilent || forceSilent) {
218
+ logger.info(chalk.bold('\nSTEP 7/7: Post-Install Wizard (skipped - silent)\n'));
219
+ } else {
220
+ logger.info(chalk.bold('\nSTEP 7/7: Post-Install Wizard\n'));
221
+ await wizard.show({
222
+ agents: answers.agents,
223
+ targetPlatforms: answers.targetPlatforms,
224
+ mode: answers.mode,
225
+ projectRoot,
226
+ userName: answers.userName,
227
+ language: answers.language
228
+ });
229
+ }
230
+
231
+ } catch (error) {
137
232
  logger.error(chalk.red('\n❌ Installation failed:\n'));
138
233
  logger.error(error.message);
139
234
  if (error.stack) {
@@ -148,13 +243,14 @@ program
148
243
  .name('create-byan-agent')
149
244
  .description('YANSTALLER - Intelligent installer for BYAN ecosystem (29 agents, multi-platform)')
150
245
  .version(YANSTALLER_VERSION)
151
- .option('--silent', 'Silent installation (no prompts)')
246
+ .option('--silent', 'Silent installation (no prompts)')
247
+ .option('--interactive', 'Force interactive prompts even without TTY')
152
248
  .option('--agents <agents>', 'Comma-separated list of agents to install')
153
249
  .option('--platforms <platforms>', 'Comma-separated list of platforms (copilot-cli,vscode,claude-code,codex)')
154
250
  .option('--mode <mode>', 'Installation mode: recommended, custom, minimal, full')
155
251
  .option('--no-backup', 'Skip pre-install backup')
156
252
  .option('--dry-run', 'Simulate installation without making changes')
157
253
  .option('--verbose', 'Verbose logging')
158
- .action(main);
254
+ .action((opts) => main(opts));
159
255
 
160
256
  program.parse(process.argv);
@@ -6,8 +6,8 @@
6
6
  * @module utils/file-utils
7
7
  */
8
8
 
9
- const fs = require('fs-extra');
10
- const path = require('path');
9
+ const fs = require('fs-extra');
10
+ const path = require('path');
11
11
 
12
12
  /**
13
13
  * Copy file or directory
@@ -98,18 +98,20 @@ async function writeFile(filePath, content) {
98
98
  * @param {string} dirPath - Directory path
99
99
  * @returns {Promise<string[]>} - Array of file/directory names
100
100
  */
101
- async function readDir(dirPath) {
102
- return fs.readdir(dirPath);
103
- }
104
-
105
- module.exports = {
106
- copy,
107
- exists,
108
- ensureDir,
109
- remove,
110
- readJSON,
111
- writeJSON,
112
- readFile,
113
- writeFile,
114
- readDir
115
- };
101
+ async function readDir(dirPath) {
102
+ return fs.readdir(dirPath);
103
+ }
104
+
105
+ module.exports = {
106
+ constants: fs.constants,
107
+ copy,
108
+ exists,
109
+ ensureDir,
110
+ remove,
111
+ readJSON,
112
+ writeJSON,
113
+ readFile,
114
+ writeFile,
115
+ readDir,
116
+ access: fs.access
117
+ };
@@ -166,19 +166,20 @@ async function copyAgentFile(agentName, projectRoot) {
166
166
  /**
167
167
  * Generate platform stubs for all agents
168
168
  *
169
- * @param {string} platform - Platform name ('copilot-cli' | 'vscode' | 'claude' | 'codex')
169
+ * @param {string} platform - Platform name ('copilot-cli' | 'vscode' | 'claude-code' | 'codex')
170
170
  * @param {InstallConfig} config - Installation config
171
171
  * @returns {Promise<void>}
172
172
  */
173
- async function generatePlatformStubs(platform, config) {
174
- const platformModule = require(`../platforms/${platform}`);
175
-
176
- if (!platformModule || typeof platformModule.install !== 'function') {
177
- throw new Error(`Platform module not found or invalid: ${platform}`);
178
- }
179
-
180
- await platformModule.install(config.projectRoot, config.agents, config);
181
- }
173
+ async function generatePlatformStubs(platform, config) {
174
+ const normalized = platform === 'claude' ? 'claude-code' : platform;
175
+ const platformModule = require(`../platforms/${normalized}`);
176
+
177
+ if (!platformModule || typeof platformModule.install !== 'function') {
178
+ throw new Error(`Platform module not found or invalid: ${platform}`);
179
+ }
180
+
181
+ await platformModule.install(config.projectRoot, config.agents, config);
182
+ }
182
183
 
183
184
  /**
184
185
  * Create module config file
@@ -8,10 +8,34 @@
8
8
  * @module yanstaller/interviewer
9
9
  */
10
10
 
11
- const inquirer = require('inquirer');
12
- const chalk = require('chalk');
13
- const logger = require('../utils/logger');
14
-
11
+ const inquirer = require('inquirer');
12
+ const chalk = require('chalk');
13
+ const logger = require('../utils/logger');
14
+
15
+ function normalizePlatformName(name) {
16
+ if (!name) return name;
17
+ const lower = String(name).toLowerCase();
18
+ if (lower === 'claude') return 'claude-code';
19
+ return lower;
20
+ }
21
+
22
+ function buildDefaultPlatforms(options = {}) {
23
+ const preferred = Array.isArray(options.preferredPlatforms)
24
+ ? options.preferredPlatforms.map(normalizePlatformName).filter(Boolean)
25
+ : [];
26
+
27
+ if (preferred.length > 0) return preferred;
28
+
29
+ const detected = (options.detection && Array.isArray(options.detection.platforms))
30
+ ? options.detection.platforms
31
+ .filter(p => p.detected)
32
+ .map(p => normalizePlatformName(p.name))
33
+ .filter(Boolean)
34
+ : [];
35
+
36
+ return detected;
37
+ }
38
+
15
39
  /**
16
40
  * @typedef {Object} InterviewResult
17
41
  * @property {string} userName
@@ -28,7 +52,7 @@ const logger = require('../utils/logger');
28
52
  * @param {import('./recommender').Recommendation} recommendation - Recommended config
29
53
  * @returns {Promise<InterviewResult>}
30
54
  */
31
- async function ask(recommendation) {
55
+ async function ask(recommendation, options = {}) {
32
56
  logger.info(chalk.bold('\n🎙️ YANSTALLER Quick Interview\n'));
33
57
  logger.info('Just 5-7 questions to personalize your BYAN installation (<5 min)\n');
34
58
 
@@ -114,21 +138,42 @@ async function ask(recommendation) {
114
138
  selectedAgents = getAllAgents();
115
139
  }
116
140
 
117
- // Q5: Target platforms
118
- const platformAnswer = await inquirer.prompt([
119
- {
120
- type: 'checkbox',
121
- name: 'platforms',
122
- message: 'Which platforms to install on?',
123
- choices: [
124
- { name: 'GitHub Copilot CLI (.github/agents/)', value: 'copilot-cli', checked: true },
125
- { name: 'VSCode Copilot Extension', value: 'vscode', checked: true },
126
- { name: 'Codex (.codex/prompts/)', value: 'codex', checked: false },
127
- { name: 'Claude Code (MCP server)', value: 'claude-code', checked: false }
128
- ],
129
- validate: (input) => input.length > 0 || 'Select at least one platform'
130
- }
131
- ]);
141
+ // Q5: Target platforms
142
+ const defaultPlatforms = buildDefaultPlatforms(options);
143
+ const useDefaultPlatforms = defaultPlatforms.length > 0;
144
+ const isDefault = (value, fallback) => useDefaultPlatforms ? defaultPlatforms.includes(value) : fallback;
145
+
146
+ const platformAnswer = await inquirer.prompt([
147
+ {
148
+ type: 'checkbox',
149
+ name: 'platforms',
150
+ message: 'Which platforms to install on?',
151
+ choices: [
152
+ { name: 'All detected platforms', value: '__all_detected__', checked: false },
153
+ { name: 'GitHub Copilot CLI (.github/agents/)', value: 'copilot-cli', checked: isDefault('copilot-cli', true) },
154
+ { name: 'VSCode Copilot Extension', value: 'vscode', checked: isDefault('vscode', true) },
155
+ { name: 'Codex (.codex/prompts/)', value: 'codex', checked: isDefault('codex', false) },
156
+ { name: 'Claude Code (MCP server)', value: 'claude-code', checked: isDefault('claude-code', false) }
157
+ ],
158
+ validate: (input) => input.length > 0 || 'Select at least one platform'
159
+ }
160
+ ]);
161
+
162
+ let selectedPlatforms = platformAnswer.platforms;
163
+ if (selectedPlatforms.includes('__all_detected__')) {
164
+ const detected = buildDefaultPlatforms(options);
165
+ selectedPlatforms = detected.length > 0
166
+ ? detected
167
+ : ['copilot-cli', 'vscode', 'codex', 'claude-code'];
168
+ }
169
+
170
+ const isFrench = langAnswer.language === 'Francais';
171
+ const platformSummary = selectedPlatforms.join(', ');
172
+ if (isFrench) {
173
+ logger.info(`Plateformes retenues: ${platformSummary}`);
174
+ } else {
175
+ logger.info(`Selected platforms: ${platformSummary}`);
176
+ }
132
177
 
133
178
  // Q6: Create sample agent
134
179
  const sampleAnswer = await inquirer.prompt([
@@ -152,16 +197,16 @@ async function ask(recommendation) {
152
197
 
153
198
  logger.info('');
154
199
 
155
- return {
156
- userName: nameAnswer.userName,
157
- language: langAnswer.language,
158
- mode: modeAnswer.mode,
159
- agents: selectedAgents,
160
- targetPlatforms: platformAnswer.platforms,
161
- createSampleAgent: sampleAnswer.createSample,
162
- createBackup: backupAnswer.createBackup
163
- };
164
- }
200
+ return {
201
+ userName: nameAnswer.userName,
202
+ language: langAnswer.language,
203
+ mode: modeAnswer.mode,
204
+ agents: selectedAgents,
205
+ targetPlatforms: selectedPlatforms,
206
+ createSampleAgent: sampleAnswer.createSample,
207
+ createBackup: backupAnswer.createBackup
208
+ };
209
+ }
165
210
 
166
211
  /**
167
212
  * Ask single question
@@ -11,7 +11,8 @@
11
11
  const path = require('path');
12
12
  const fileUtils = require('../utils/file-utils');
13
13
  const yamlUtils = require('../utils/yaml-utils');
14
- const { execSync } = require('child_process');
14
+ const { execSync } = require('child_process');
15
+ const fs = require('fs-extra');
15
16
 
16
17
  /**
17
18
  * @typedef {Object} ValidationResult
@@ -252,7 +253,7 @@ async function checkConfigFiles(config) {
252
253
  if (await fileUtils.exists(configPath)) {
253
254
  try {
254
255
  const configContent = await fileUtils.readFile(configPath, 'utf8');
255
- const parsedConfig = yamlUtils.load(configContent);
256
+ const parsedConfig = yamlUtils.parse(configContent);
256
257
 
257
258
  // Validate required fields
258
259
  if (!parsedConfig.user_name) {
@@ -484,28 +485,51 @@ async function checkWorkflows(config) {
484
485
  /**
485
486
  * Check 9: Templates valid
486
487
  */
487
- async function checkTemplates(config) {
488
- const templatesDir = path.join(__dirname, '..', '..', 'templates', '_bmad');
489
-
490
- if (!await fileUtils.exists(templatesDir)) {
491
- return {
492
- id: 'templates',
493
- name: 'Template files',
494
- passed: false,
495
- message: 'Templates directory not found',
496
- severity: 'warning'
497
- };
498
- }
499
-
500
- const modules = ['core', 'bmm', 'bmb', 'tea', 'cis'];
501
- const issues = [];
502
-
503
- for (const module of modules) {
504
- const agentsDir = path.join(templatesDir, module, 'agents');
505
- if (!await fileUtils.exists(agentsDir)) {
506
- issues.push(`${module}/agents missing`);
507
- }
508
- }
488
+ async function checkTemplates(config) {
489
+ const templatesDir = path.join(__dirname, '..', '..', 'templates', '_bmad');
490
+
491
+ if (!await fileUtils.exists(templatesDir)) {
492
+ return {
493
+ id: 'templates',
494
+ name: 'Template files',
495
+ passed: false,
496
+ message: 'Templates directory not found',
497
+ severity: 'warning'
498
+ };
499
+ }
500
+
501
+ const entries = await fileUtils.readDir(templatesDir);
502
+ const modules = [];
503
+ for (const entry of entries) {
504
+ const entryPath = path.join(templatesDir, entry);
505
+ try {
506
+ const stat = await fs.stat(entryPath);
507
+ if (stat.isDirectory()) {
508
+ modules.push(entry);
509
+ }
510
+ } catch {
511
+ // Ignore unreadable entries
512
+ }
513
+ }
514
+
515
+ if (modules.length === 0) {
516
+ return {
517
+ id: 'templates',
518
+ name: 'Template files',
519
+ passed: false,
520
+ message: 'No template modules found',
521
+ severity: 'warning'
522
+ };
523
+ }
524
+
525
+ const issues = [];
526
+
527
+ for (const module of modules) {
528
+ const agentsDir = path.join(templatesDir, module, 'agents');
529
+ if (!await fileUtils.exists(agentsDir)) {
530
+ issues.push(`${module}/agents missing`);
531
+ }
532
+ }
509
533
 
510
534
  if (issues.length > 0) {
511
535
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "NPX installer for BYAN ecosystem - Agent creators (BYAN, BYAN-Test) with deployment (RACHID), integration (MARC), updates (PATNOTE), and optimization (CARMACK)",
5
5
  "bin": {
6
6
  "create-byan-agent": "bin/create-byan-agent.js"