create-byan-agent 1.2.3 → 1.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.
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.4] - 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,16 @@ 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
1025
1028
 
1026
1029
  # Full installation without backup
1027
1030
  create-byan-agent --mode=full --no-backup
@@ -14,9 +14,29 @@ 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
+ // ASCII Art Banner
20
40
  const banner = `
21
41
  ${chalk.blue('╔════════════════════════════════════════════════════════════╗')}
22
42
  ${chalk.blue('║')} ${chalk.blue('║')}
@@ -41,7 +61,7 @@ ${chalk.blue('╚═════════════════════
41
61
  * 6. VALIDATE - 10 automated checks
42
62
  * 7. WIZARD - Post-install actions
43
63
  */
44
- async function main() {
64
+ async function main(options = {}) {
45
65
  try {
46
66
  console.clear();
47
67
  console.log(banner);
@@ -72,11 +92,75 @@ async function main() {
72
92
  logger.info(`✓ Recommended agents: ${chalk.cyan(recommendations.agents.join(', '))}`);
73
93
  }
74
94
 
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)
95
+ // STEP 3: INTERVIEW - 7-Question Personalization
96
+ const isSilent = !!options.silent;
97
+ const forceInteractive = !!options.interactive;
98
+ const hasTty = !!process.stdin.isTTY;
99
+ const forceSilent = !hasTty && !isSilent && !forceInteractive;
100
+
101
+ if (forceSilent) {
102
+ logger.warn('No interactive TTY detected. Falling back to silent mode.');
103
+ }
104
+ if (!hasTty && forceInteractive) {
105
+ logger.warn('Interactive mode forced without TTY. Prompts may not render correctly.');
106
+ }
107
+
108
+ let answers;
109
+
110
+ if (isSilent || forceSilent) {
111
+ logger.info(chalk.bold('\nSTEP 3/7: Interview (skipped - silent)\n'));
112
+
113
+ const parsedAgents = parseList(options.agents);
114
+ const parsedPlatforms = normalizePlatforms(parseList(options.platforms));
115
+
116
+ let mode = options.mode || (parsedAgents.length > 0 ? 'custom' : (recommendations.mode || 'minimal'));
117
+ let agents = parsedAgents;
118
+
119
+ if (agents.length === 0) {
120
+ if (mode === 'recommended' && recommendations && recommendations.agents) {
121
+ agents = recommendations.agents;
122
+ } else if (mode === 'minimal' || mode === 'full') {
123
+ agents = recommender.getAgentList(mode);
124
+ } else if (mode === 'custom') {
125
+ logger.warn('Custom mode selected without agents. Falling back to recommendations.');
126
+ agents = recommendations.agents || ['byan'];
127
+ mode = 'recommended';
128
+ } else {
129
+ agents = recommendations.agents || ['byan'];
130
+ mode = recommendations.mode || 'minimal';
131
+ }
132
+ }
133
+
134
+ let targetPlatforms = parsedPlatforms;
135
+ if (targetPlatforms.length === 0) {
136
+ targetPlatforms = (detection.platforms || [])
137
+ .filter(p => p.detected)
138
+ .map(p => normalizePlatformName(p.name));
139
+ }
140
+
141
+ answers = {
142
+ userName: 'Developer',
143
+ language: 'English',
144
+ mode,
145
+ agents,
146
+ targetPlatforms,
147
+ createSampleAgent: false,
148
+ createBackup: options.backup !== false
149
+ };
150
+ } else {
151
+ logger.info(chalk.bold('\nSTEP 3/7: Interview\n'));
152
+ const preferredPlatforms = normalizePlatforms(parseList(options.platforms));
153
+ answers = await interviewer.ask(recommendations, {
154
+ detection,
155
+ preferredPlatforms
156
+ });
157
+
158
+ if (options.backup === false) {
159
+ answers.createBackup = false;
160
+ }
161
+ }
162
+
163
+ // STEP 4: BACKUP (optional)
80
164
  if (answers.createBackup) {
81
165
  logger.info(chalk.bold('\n💾 STEP 4/7: Backup\n'));
82
166
  try {
@@ -122,18 +206,22 @@ async function main() {
122
206
  }
123
207
  }
124
208
 
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) {
209
+ // STEP 7: WIZARD - Post-Install Actions
210
+ if (isSilent || forceSilent) {
211
+ logger.info(chalk.bold('\nSTEP 7/7: Post-Install Wizard (skipped - silent)\n'));
212
+ } else {
213
+ logger.info(chalk.bold('\nSTEP 7/7: Post-Install Wizard\n'));
214
+ await wizard.show({
215
+ agents: answers.agents,
216
+ targetPlatforms: answers.targetPlatforms,
217
+ mode: answers.mode,
218
+ projectRoot,
219
+ userName: answers.userName,
220
+ language: answers.language
221
+ });
222
+ }
223
+
224
+ } catch (error) {
137
225
  logger.error(chalk.red('\n❌ Installation failed:\n'));
138
226
  logger.error(error.message);
139
227
  if (error.stack) {
@@ -148,13 +236,14 @@ program
148
236
  .name('create-byan-agent')
149
237
  .description('YANSTALLER - Intelligent installer for BYAN ecosystem (29 agents, multi-platform)')
150
238
  .version(YANSTALLER_VERSION)
151
- .option('--silent', 'Silent installation (no prompts)')
239
+ .option('--silent', 'Silent installation (no prompts)')
240
+ .option('--interactive', 'Force interactive prompts even without TTY')
152
241
  .option('--agents <agents>', 'Comma-separated list of agents to install')
153
242
  .option('--platforms <platforms>', 'Comma-separated list of platforms (copilot-cli,vscode,claude-code,codex)')
154
243
  .option('--mode <mode>', 'Installation mode: recommended, custom, minimal, full')
155
244
  .option('--no-backup', 'Skip pre-install backup')
156
245
  .option('--dry-run', 'Simulate installation without making changes')
157
246
  .option('--verbose', 'Verbose logging')
158
- .action(main);
247
+ .action((opts) => main(opts));
159
248
 
160
249
  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,25 @@ 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: 'GitHub Copilot CLI (.github/agents/)', value: 'copilot-cli', checked: isDefault('copilot-cli', true) },
153
+ { name: 'VSCode Copilot Extension', value: 'vscode', checked: isDefault('vscode', true) },
154
+ { name: 'Codex (.codex/prompts/)', value: 'codex', checked: isDefault('codex', false) },
155
+ { name: 'Claude Code (MCP server)', value: 'claude-code', checked: isDefault('claude-code', false) }
156
+ ],
157
+ validate: (input) => input.length > 0 || 'Select at least one platform'
158
+ }
159
+ ]);
132
160
 
133
161
  // Q6: Create sample agent
134
162
  const sampleAnswer = await inquirer.prompt([
@@ -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.4",
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"