create-byan-agent 2.7.9 → 2.8.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.
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Parakeet TDT Setup Script
5
+ *
6
+ * Installs NVIDIA Parakeet TDT as the preferred STT engine.
7
+ * Falls back to Whisper when hardware requirements are not met.
8
+ *
9
+ * Usage:
10
+ * node setup-parakeet.js [mode]
11
+ * mode: local | docker | auto (default: auto)
12
+ */
13
+
14
+ const fs = require('fs-extra');
15
+ const path = require('path');
16
+ const { execSync } = require('child_process');
17
+ const chalk = require('chalk');
18
+ const ora = require('ora');
19
+
20
+ const { detect, detectGPU, commandExists, checkPython, printDetectionSummary } = require('./lib/stt/engine');
21
+ const parakeet = require('./lib/stt/parakeet-backend');
22
+ const whisper = require('./lib/stt/whisper-backend');
23
+
24
+ class ParakeetInstaller {
25
+ /**
26
+ * @param {string} projectRoot
27
+ * @param {'local'|'docker'|'auto'} mode
28
+ */
29
+ constructor(projectRoot, mode = 'auto') {
30
+ this.projectRoot = projectRoot;
31
+ this.mode = mode;
32
+ this.scriptsDir = path.join(projectRoot, 'scripts');
33
+ }
34
+
35
+ /**
36
+ * Run the full setup flow.
37
+ *
38
+ * @returns {Promise<{ success: boolean, engine: string, mode: string, fallback: boolean }>}
39
+ */
40
+ async setup() {
41
+ console.log(chalk.blue('\n Parakeet TDT — STT Engine Setup'));
42
+ console.log(chalk.gray(' ================================\n'));
43
+
44
+ const detection = detect();
45
+ console.log(chalk.bold(' Detection:'));
46
+ printDetectionSummary(detection);
47
+ console.log('');
48
+
49
+ const resolvedMode = this.resolveMode(detection);
50
+
51
+ // Attempt Parakeet first
52
+ if (detection.engine === 'parakeet') {
53
+ try {
54
+ const result = await parakeet.setup({
55
+ mode: resolvedMode,
56
+ projectRoot: this.projectRoot
57
+ });
58
+ await this.createLaunchScript('parakeet', resolvedMode);
59
+ this.printSuccess('parakeet', result);
60
+ return { success: true, engine: 'parakeet', mode: resolvedMode, fallback: false };
61
+ } catch (err) {
62
+ console.log(chalk.yellow(`\n Parakeet setup failed: ${err.message}`));
63
+ console.log(chalk.yellow(' Falling back to Whisper...\n'));
64
+ }
65
+ }
66
+
67
+ // Fallback to Whisper
68
+ if (detection.engine !== 'none') {
69
+ try {
70
+ const result = await whisper.setup({
71
+ mode: resolvedMode,
72
+ projectRoot: this.projectRoot
73
+ });
74
+ await this.createLaunchScript('whisper', resolvedMode);
75
+ this.printSuccess('whisper', result);
76
+ return { success: true, engine: 'whisper', mode: resolvedMode, fallback: detection.engine === 'parakeet' };
77
+ } catch (err) {
78
+ console.error(chalk.red(`\n Whisper setup also failed: ${err.message}`));
79
+ return { success: false, engine: 'none', mode: resolvedMode, fallback: true };
80
+ }
81
+ }
82
+
83
+ console.log(chalk.red('\n No STT engine could be installed.'));
84
+ console.log(chalk.gray(' Install Docker or Python 3 and try again.'));
85
+ return { success: false, engine: 'none', mode: 'none', fallback: false };
86
+ }
87
+
88
+ /**
89
+ * Resolve auto mode into a concrete mode based on what's available.
90
+ *
91
+ * @param {{ engine: string, gpu: object }} detection
92
+ * @returns {'local'|'docker'}
93
+ */
94
+ resolveMode(detection) {
95
+ if (this.mode !== 'auto') return this.mode;
96
+
97
+ // Prefer Docker when available — cleaner isolation
98
+ if (commandExists('docker')) return 'docker';
99
+
100
+ const python = checkPython();
101
+ if (python.available) return 'local';
102
+
103
+ return 'docker';
104
+ }
105
+
106
+ /**
107
+ * Create a convenience launch script for the installed engine.
108
+ *
109
+ * @param {'parakeet'|'whisper'} engine
110
+ * @param {'local'|'docker'} mode
111
+ */
112
+ async createLaunchScript(engine, mode) {
113
+ await fs.ensureDir(this.scriptsDir);
114
+
115
+ const port = engine === 'parakeet' ? parakeet.PARAKEET_PORT : whisper.WHISPER_PORT;
116
+ const composeFile = engine === 'parakeet'
117
+ ? 'docker-compose.parakeet.yml'
118
+ : 'docker-compose.turbo-whisper.yml';
119
+
120
+ const script = `#!/bin/bash
121
+ # Launch ${engine} STT server
122
+ # Generated by setup-parakeet.js
123
+
124
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
125
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
126
+ PORT=${port}
127
+
128
+ echo "Checking ${engine} STT server on port $PORT..."
129
+
130
+ if curl -sf http://localhost:$PORT/health > /dev/null 2>&1; then
131
+ echo "Server already running on port $PORT"
132
+ exit 0
133
+ fi
134
+
135
+ ${mode === 'docker' ? `
136
+ COMPOSE_FILE="$PROJECT_DIR/${composeFile}"
137
+ if [ ! -f "$COMPOSE_FILE" ]; then
138
+ echo "Error: $COMPOSE_FILE not found"
139
+ exit 1
140
+ fi
141
+ echo "Starting ${engine} via Docker..."
142
+ docker compose -f "$COMPOSE_FILE" up -d
143
+
144
+ echo "Waiting for server (up to 60s)..."
145
+ for i in $(seq 1 12); do
146
+ sleep 5
147
+ if curl -sf http://localhost:$PORT/health > /dev/null 2>&1; then
148
+ echo "Server ready on port $PORT"
149
+ exit 0
150
+ fi
151
+ done
152
+ echo "Server did not become ready in time. Check: docker compose -f $COMPOSE_FILE logs"
153
+ exit 1
154
+ ` : `
155
+ echo "Starting ${engine} locally..."
156
+ ${engine === 'parakeet'
157
+ ? `python3 -c "
158
+ from nemo.collections.asr.models import EncDecRNNTBPEModel
159
+ import json, http.server, io, soundfile as sf, numpy as np
160
+ model = EncDecRNNTBPEModel.from_pretrained('${parakeet.DEFAULT_MODEL}')
161
+
162
+ class Handler(http.server.BaseHTTPRequestHandler):
163
+ def do_GET(self):
164
+ if self.path == '/health':
165
+ self.send_response(200); self.end_headers(); self.wfile.write(b'ok')
166
+ else:
167
+ self.send_response(404); self.end_headers()
168
+ def do_POST(self):
169
+ length = int(self.headers['Content-Length'])
170
+ audio_bytes = self.rfile.read(length)
171
+ audio, sr = sf.read(io.BytesIO(audio_bytes))
172
+ transcription = model.transcribe([audio])
173
+ result = json.dumps({'text': transcription[0] if transcription else ''})
174
+ self.send_response(200)
175
+ self.send_header('Content-Type', 'application/json')
176
+ self.end_headers()
177
+ self.wfile.write(result.encode())
178
+ def log_message(self, format, *args): pass
179
+
180
+ httpd = http.server.HTTPServer(('', $PORT), Handler)
181
+ print('Parakeet TDT server listening on port $PORT')
182
+ httpd.serve_forever()
183
+ " &`
184
+ : `cd "$HOME/faster-whisper-server"
185
+ source .venv/bin/activate
186
+ uvicorn --factory faster_whisper_server.main:create_app --port $PORT &`}
187
+ SERVER_PID=$!
188
+
189
+ echo "Waiting for server (up to 60s)..."
190
+ for i in $(seq 1 12); do
191
+ sleep 5
192
+ if curl -sf http://localhost:$PORT/health > /dev/null 2>&1; then
193
+ echo "Server ready on port $PORT (PID: $SERVER_PID)"
194
+ exit 0
195
+ fi
196
+ done
197
+ echo "Server did not start in time."
198
+ exit 1
199
+ `}
200
+ `;
201
+
202
+ const scriptPath = path.join(this.scriptsDir, `launch-${engine}-stt.sh`);
203
+ await fs.writeFile(scriptPath, script, 'utf-8');
204
+ await fs.chmod(scriptPath, '755');
205
+
206
+ // Stop script for Docker mode
207
+ if (mode === 'docker') {
208
+ const stopScript = `#!/bin/bash
209
+ # Stop ${engine} STT Docker container
210
+
211
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
212
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
213
+ COMPOSE_FILE="$PROJECT_DIR/${composeFile}"
214
+
215
+ if [ ! -f "$COMPOSE_FILE" ]; then
216
+ echo "Error: $COMPOSE_FILE not found"
217
+ exit 1
218
+ fi
219
+
220
+ echo "Stopping ${engine} server..."
221
+ docker compose -f "$COMPOSE_FILE" down
222
+ echo "Done."
223
+ `;
224
+
225
+ const stopPath = path.join(this.scriptsDir, `stop-${engine}-stt.sh`);
226
+ await fs.writeFile(stopPath, stopScript, 'utf-8');
227
+ await fs.chmod(stopPath, '755');
228
+ }
229
+ }
230
+
231
+ /**
232
+ * @param {string} engine
233
+ * @param {{ model: string, mode: string }} result
234
+ */
235
+ printSuccess(engine, result) {
236
+ console.log(chalk.green(`\n STT engine installed: ${engine}`));
237
+ console.log(chalk.gray(` Model: ${result.model}`));
238
+ console.log(chalk.gray(` Mode: ${result.mode}`));
239
+ console.log(chalk.gray(` Port: ${engine === 'parakeet' ? parakeet.PARAKEET_PORT : whisper.WHISPER_PORT}`));
240
+ console.log(chalk.blue('\n Launch:'));
241
+ console.log(chalk.white(` ./scripts/launch-${engine}-stt.sh\n`));
242
+ }
243
+ }
244
+
245
+ module.exports = ParakeetInstaller;
246
+
247
+ // CLI entry point
248
+ if (require.main === module) {
249
+ const args = process.argv.slice(2);
250
+ const mode = args[0] || 'auto';
251
+ const projectRoot = path.resolve(__dirname, '..');
252
+
253
+ const installer = new ParakeetInstaller(projectRoot, mode);
254
+ installer.setup()
255
+ .then(result => process.exit(result.success ? 0 : 1))
256
+ .catch(err => {
257
+ console.error(chalk.red('Fatal:'), err.message);
258
+ process.exit(1);
259
+ });
260
+ }
@@ -0,0 +1,293 @@
1
+ /**
2
+ * BYAN WebUI REST API handlers.
3
+ * Each handler receives (req, res, server) where server is ByanWebUI instance.
4
+ */
5
+
6
+ let yanstaller = null;
7
+ let detector = null;
8
+ let backuper = null;
9
+ let sttEngine = null;
10
+
11
+ try { yanstaller = require('../../lib/yanstaller'); } catch { yanstaller = null; }
12
+ try { detector = require('../../lib/yanstaller/detector'); } catch { detector = null; }
13
+ try { backuper = require('../../lib/yanstaller/backuper'); } catch { backuper = null; }
14
+ try { sttEngine = require('../../lib/stt/engine'); } catch { sttEngine = null; }
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ function json(res, statusCode, data) {
20
+ res.writeHead(statusCode);
21
+ res.end(JSON.stringify(data));
22
+ }
23
+
24
+ function readPackageVersion() {
25
+ try {
26
+ const pkg = JSON.parse(
27
+ fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'), 'utf8')
28
+ );
29
+ return pkg.version || 'unknown';
30
+ } catch {
31
+ return 'unknown';
32
+ }
33
+ }
34
+
35
+ function isByanInstalled(projectRoot) {
36
+ return fs.existsSync(path.join(projectRoot, '_bmad'));
37
+ }
38
+
39
+ function detectPlatforms(projectRoot) {
40
+ const found = [];
41
+ const checks = [
42
+ { name: 'copilot-cli', path: '.github/agents' },
43
+ { name: 'vscode', path: '.vscode' },
44
+ { name: 'claude', path: '.claude' },
45
+ { name: 'codex', path: '.codex/prompts' }
46
+ ];
47
+ for (const check of checks) {
48
+ if (fs.existsSync(path.join(projectRoot, check.path))) {
49
+ found.push(check.name);
50
+ }
51
+ }
52
+ return found;
53
+ }
54
+
55
+ const routes = {
56
+ 'GET status': async (req, res, server) => {
57
+ const projectRoot = server.projectRoot;
58
+ const version = readPackageVersion();
59
+ const installed = isByanInstalled(projectRoot);
60
+
61
+ let platforms = detectPlatforms(projectRoot);
62
+ let detectionResult = null;
63
+
64
+ if (detector) {
65
+ try {
66
+ detectionResult = await detector.detect();
67
+ platforms = detectionResult.platforms
68
+ .filter(p => p.detected)
69
+ .map(p => p.name);
70
+ } catch {
71
+ // Fallback to fs-based detection
72
+ }
73
+ }
74
+
75
+ json(res, 200, {
76
+ version,
77
+ installed,
78
+ platforms,
79
+ detection: detectionResult,
80
+ sttEngine: sttEngine ? 'available' : 'not-installed',
81
+ projectRoot
82
+ });
83
+ },
84
+
85
+ 'GET update/check': async (req, res, server) => {
86
+ const version = readPackageVersion();
87
+
88
+ // Simulated update check; real implementation would query npm registry
89
+ json(res, 200, {
90
+ updateAvailable: false,
91
+ installed: version,
92
+ latest: version,
93
+ changes: []
94
+ });
95
+ },
96
+
97
+ 'POST install': async (req, res, server) => {
98
+ json(res, 200, { status: 'started' });
99
+
100
+ const config = req.body || {};
101
+ const projectRoot = server.projectRoot;
102
+ const steps = [
103
+ 'Detecting environment',
104
+ 'Validating prerequisites',
105
+ 'Creating directory structure',
106
+ 'Installing core module',
107
+ 'Installing selected modules',
108
+ 'Configuring platforms',
109
+ 'Generating agent stubs',
110
+ 'Writing configuration',
111
+ 'Validating installation'
112
+ ];
113
+
114
+ try {
115
+ for (let i = 0; i < steps.length; i++) {
116
+ server.broadcastProgress(i + 1, steps.length, steps[i]);
117
+ server.broadcastLog('info', steps[i] + '...');
118
+ await sleep(300);
119
+ }
120
+
121
+ if (yanstaller) {
122
+ try {
123
+ await yanstaller.install({
124
+ mode: config.mode || 'full',
125
+ platforms: config.platforms,
126
+ yes: true,
127
+ projectRoot
128
+ });
129
+ } catch (err) {
130
+ server.broadcastLog('warn', `Yanstaller: ${err.message} (continuing with stub install)`);
131
+ }
132
+ }
133
+
134
+ ensureDirectoryStructure(projectRoot);
135
+ writeBaseConfig(projectRoot, config);
136
+
137
+ server.broadcastProgress(steps.length, steps.length, 'Complete');
138
+ server.broadcastComplete(true, {
139
+ message: 'BYAN installed successfully',
140
+ projectRoot,
141
+ mode: config.mode || 'auto',
142
+ platforms: config.platforms || detectPlatforms(projectRoot)
143
+ });
144
+ } catch (err) {
145
+ server.broadcastLog('error', err.message);
146
+ server.broadcastComplete(false, { message: err.message });
147
+ }
148
+ },
149
+
150
+ 'POST update': async (req, res, server) => {
151
+ json(res, 200, { status: 'started' });
152
+
153
+ const steps = [
154
+ 'Checking current version',
155
+ 'Creating backup',
156
+ 'Downloading update',
157
+ 'Applying changes',
158
+ 'Merging configuration',
159
+ 'Validating update'
160
+ ];
161
+
162
+ try {
163
+ for (let i = 0; i < steps.length; i++) {
164
+ server.broadcastProgress(i + 1, steps.length, steps[i]);
165
+ server.broadcastLog('info', steps[i] + '...');
166
+ await sleep(400);
167
+ }
168
+
169
+ if (yanstaller && yanstaller.update) {
170
+ try {
171
+ await yanstaller.update('latest');
172
+ } catch (err) {
173
+ server.broadcastLog('warn', `Update module: ${err.message}`);
174
+ }
175
+ }
176
+
177
+ server.broadcastComplete(true, { message: 'BYAN updated successfully' });
178
+ } catch (err) {
179
+ server.broadcastLog('error', err.message);
180
+ server.broadcastComplete(false, { message: err.message });
181
+ }
182
+ },
183
+
184
+ 'POST rollback': async (req, res, server) => {
185
+ const { backupPath } = req.body || {};
186
+
187
+ if (!backuper) {
188
+ json(res, 503, { error: 'Backup module not available' });
189
+ return;
190
+ }
191
+
192
+ try {
193
+ const targetPath = path.join(server.projectRoot, '_bmad');
194
+ await backuper.restore(backupPath, targetPath);
195
+ json(res, 200, { success: true, message: 'Rollback complete' });
196
+ } catch (err) {
197
+ json(res, 500, { error: err.message });
198
+ }
199
+ },
200
+
201
+ 'GET backups': async (req, res, server) => {
202
+ if (!backuper) {
203
+ json(res, 200, { backups: [] });
204
+ return;
205
+ }
206
+
207
+ try {
208
+ const list = await backuper.listBackups(server.projectRoot);
209
+ json(res, 200, { backups: list });
210
+ } catch (err) {
211
+ json(res, 500, { error: err.message });
212
+ }
213
+ },
214
+
215
+ 'GET stt/status': async (req, res) => {
216
+ if (!sttEngine) {
217
+ json(res, 200, { available: false, reason: 'STT engine module not installed' });
218
+ return;
219
+ }
220
+
221
+ try {
222
+ const status = typeof sttEngine.status === 'function'
223
+ ? await sttEngine.status()
224
+ : { available: true };
225
+ json(res, 200, status);
226
+ } catch (err) {
227
+ json(res, 500, { error: err.message });
228
+ }
229
+ },
230
+
231
+ 'POST stt/test': async (req, res) => {
232
+ if (!sttEngine) {
233
+ json(res, 503, { error: 'STT engine not available' });
234
+ return;
235
+ }
236
+
237
+ try {
238
+ const result = typeof sttEngine.test === 'function'
239
+ ? await sttEngine.test()
240
+ : { success: false, reason: 'Test not implemented' };
241
+ json(res, 200, result);
242
+ } catch (err) {
243
+ json(res, 500, { error: err.message });
244
+ }
245
+ }
246
+ };
247
+
248
+ function resolve(method, route) {
249
+ const key = `${method} ${route}`;
250
+ return routes[key] || null;
251
+ }
252
+
253
+ function sleep(ms) {
254
+ return new Promise(r => setTimeout(r, ms));
255
+ }
256
+
257
+ function ensureDirectoryStructure(projectRoot) {
258
+ const dirs = [
259
+ '_bmad',
260
+ '_bmad/_config',
261
+ '_bmad/_memory',
262
+ '_bmad/core',
263
+ '_bmad/core/agents',
264
+ '_bmad/core/workflows',
265
+ '_bmad/core/tasks',
266
+ '_bmad-output',
267
+ '_bmad-output/planning-artifacts',
268
+ '_bmad-output/implementation-artifacts'
269
+ ];
270
+
271
+ for (const dir of dirs) {
272
+ const full = path.join(projectRoot, dir);
273
+ if (!fs.existsSync(full)) {
274
+ fs.mkdirSync(full, { recursive: true });
275
+ }
276
+ }
277
+ }
278
+
279
+ function writeBaseConfig(projectRoot, config) {
280
+ const configPath = path.join(projectRoot, '_bmad', 'core', 'config.yaml');
281
+ if (fs.existsSync(configPath)) return;
282
+
283
+ const content = [
284
+ `user_name: ${config.userName || 'User'}`,
285
+ `communication_language: ${config.language || 'English'}`,
286
+ `document_output_language: ${config.language || 'English'}`,
287
+ `output_folder: "{project-root}/_bmad-output"`
288
+ ].join('\n') + '\n';
289
+
290
+ fs.writeFileSync(configPath, content, 'utf8');
291
+ }
292
+
293
+ module.exports = { resolve, routes };