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.
- package/README.md +151 -207
- package/bin/create-byan-agent-v2.js +1 -1
- package/lib/domain-questions.js +235 -0
- package/lib/errors.js +61 -0
- package/lib/exit-codes.js +54 -0
- package/lib/phase2-chat.js +534 -0
- package/lib/platforms/claude-code.js +196 -0
- package/lib/platforms/codex.js +92 -0
- package/lib/platforms/copilot-cli.js +123 -0
- package/lib/platforms/index.js +14 -0
- package/lib/platforms/vscode.js +51 -0
- package/lib/project-agents-generator.js +238 -0
- package/lib/utils/config-loader.js +79 -0
- package/lib/utils/file-utils.js +104 -0
- package/lib/utils/git-detector.js +35 -0
- package/lib/utils/logger.js +64 -0
- package/lib/utils/node-detector.js +58 -0
- package/lib/utils/os-detector.js +74 -0
- package/lib/utils/yaml-utils.js +87 -0
- package/lib/yanstaller/agent-launcher.js +348 -0
- package/lib/yanstaller/backuper.js +108 -0
- package/lib/yanstaller/detector.js +141 -0
- package/lib/yanstaller/index.js +139 -0
- package/lib/yanstaller/installer.js +140 -0
- package/lib/yanstaller/interviewer.js +88 -0
- package/lib/yanstaller/platform-selector.js +328 -0
- package/lib/yanstaller/recommender.js +102 -0
- package/lib/yanstaller/troubleshooter.js +89 -0
- package/lib/yanstaller/validator.js +198 -0
- package/lib/yanstaller/wizard.js +109 -0
- package/package.json +3 -1
- package/setup-turbo-whisper.js +687 -0
|
@@ -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
|
+
};
|