create-byan-agent 2.7.9 → 2.8.1
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/bin/create-byan-agent-v2.js +135 -184
- package/lib/stt/engine.js +277 -0
- package/lib/stt/parakeet-backend.js +262 -0
- package/lib/stt/whisper-backend.js +171 -0
- package/lib/utils/file-differ.js +110 -0
- package/lib/utils/manifest.js +118 -0
- package/lib/utils/version-compare.js +69 -0
- package/lib/yanstaller/backuper.js +101 -45
- package/lib/yanstaller/index.js +41 -4
- package/lib/yanstaller/updater.js +271 -0
- package/package.json +5 -2
- package/setup-parakeet.js +260 -0
- package/src/webui/api.js +293 -0
- package/src/webui/public/app.js +455 -0
- package/src/webui/public/index.html +192 -0
- package/src/webui/public/style.css +732 -0
- package/src/webui/server.js +215 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
class ByanApp {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.currentStep = 'welcome';
|
|
4
|
+
this.stepHistory = [];
|
|
5
|
+
this.ws = null;
|
|
6
|
+
this.wsRetryDelay = 1000;
|
|
7
|
+
this.config = {
|
|
8
|
+
mode: 'auto',
|
|
9
|
+
userName: '',
|
|
10
|
+
language: 'English',
|
|
11
|
+
platforms: [],
|
|
12
|
+
modules: ['core', 'bmm']
|
|
13
|
+
};
|
|
14
|
+
this.status = null;
|
|
15
|
+
this.logCount = 0;
|
|
16
|
+
this.logsExpanded = false;
|
|
17
|
+
|
|
18
|
+
this.connectWebSocket();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// --- WebSocket ---
|
|
22
|
+
|
|
23
|
+
connectWebSocket() {
|
|
24
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
25
|
+
this.ws = new WebSocket(`${protocol}//${window.location.host}`);
|
|
26
|
+
|
|
27
|
+
this.ws.onopen = () => {
|
|
28
|
+
this.wsRetryDelay = 1000;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
this.ws.onmessage = (event) => {
|
|
32
|
+
try {
|
|
33
|
+
this.handleWSMessage(JSON.parse(event.data));
|
|
34
|
+
} catch { /* malformed message */ }
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
this.ws.onclose = () => {
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
this.wsRetryDelay = Math.min(this.wsRetryDelay * 1.5, 10000);
|
|
40
|
+
this.connectWebSocket();
|
|
41
|
+
}, this.wsRetryDelay);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this.ws.onerror = () => {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
handleWSMessage(data) {
|
|
48
|
+
switch (data.type) {
|
|
49
|
+
case 'log':
|
|
50
|
+
this.addLog(data);
|
|
51
|
+
break;
|
|
52
|
+
case 'progress':
|
|
53
|
+
this.updateProgress(data);
|
|
54
|
+
break;
|
|
55
|
+
case 'complete':
|
|
56
|
+
this.showComplete(data);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --- Navigation ---
|
|
62
|
+
|
|
63
|
+
showStep(stepId) {
|
|
64
|
+
if (this.currentStep !== stepId) {
|
|
65
|
+
this.stepHistory.push(this.currentStep);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
document.querySelectorAll('.step').forEach(el => el.classList.add('hidden'));
|
|
69
|
+
const target = document.getElementById(`step-${stepId}`);
|
|
70
|
+
if (target) {
|
|
71
|
+
target.classList.remove('hidden');
|
|
72
|
+
target.focus();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.currentStep = stepId;
|
|
76
|
+
this.updateWizardNav();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
goBack() {
|
|
80
|
+
const prev = this.stepHistory.pop();
|
|
81
|
+
if (prev) {
|
|
82
|
+
document.querySelectorAll('.step').forEach(el => el.classList.add('hidden'));
|
|
83
|
+
const target = document.getElementById(`step-${prev}`);
|
|
84
|
+
if (target) target.classList.remove('hidden');
|
|
85
|
+
this.currentStep = prev;
|
|
86
|
+
this.updateWizardNav();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
updateWizardNav() {
|
|
91
|
+
const stepOrder = ['welcome', 'detection', 'mode', 'config', 'preview', 'progress', 'done'];
|
|
92
|
+
const currentIdx = stepOrder.indexOf(this.currentStep);
|
|
93
|
+
|
|
94
|
+
document.querySelectorAll('.wizard-step').forEach(el => {
|
|
95
|
+
const step = el.dataset.step;
|
|
96
|
+
const idx = stepOrder.indexOf(step);
|
|
97
|
+
|
|
98
|
+
el.classList.remove('active', 'completed');
|
|
99
|
+
if (idx < currentIdx) {
|
|
100
|
+
el.classList.add('completed');
|
|
101
|
+
} else if (idx === currentIdx) {
|
|
102
|
+
el.classList.add('active');
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --- Install Flow ---
|
|
108
|
+
|
|
109
|
+
async startInstall() {
|
|
110
|
+
this.showStep('detection');
|
|
111
|
+
await this.runDetection();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async runDetection() {
|
|
115
|
+
const container = document.getElementById('detection-results');
|
|
116
|
+
const nextBtn = document.getElementById('btn-detection-next');
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetch('/api/status');
|
|
120
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
121
|
+
this.status = await res.json();
|
|
122
|
+
|
|
123
|
+
let html = '';
|
|
124
|
+
|
|
125
|
+
html += this.detectionRow(true, 'Node.js', this.status.detection?.nodeVersion || process.version || 'detected');
|
|
126
|
+
html += this.detectionRow(
|
|
127
|
+
this.status.detection?.hasGit !== false,
|
|
128
|
+
'Git',
|
|
129
|
+
this.status.detection?.gitVersion || (this.status.detection?.hasGit !== false ? 'detected' : 'not found')
|
|
130
|
+
);
|
|
131
|
+
html += this.detectionRow(true, 'Operating System', this.status.detection?.os || navigator.platform);
|
|
132
|
+
|
|
133
|
+
const platformNames = { 'copilot-cli': 'GitHub Copilot CLI', 'vscode': 'VSCode', 'claude': 'Claude Code', 'codex': 'Codex' };
|
|
134
|
+
const detectedPlatforms = this.status.detection?.platforms || [];
|
|
135
|
+
if (detectedPlatforms.length > 0) {
|
|
136
|
+
for (const p of detectedPlatforms) {
|
|
137
|
+
html += this.detectionRow(p.detected, platformNames[p.name] || p.name, p.detected ? 'found' : 'not detected');
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
for (const name of this.status.platforms || []) {
|
|
141
|
+
html += this.detectionRow(true, platformNames[name] || name, 'found');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
html += this.detectionRow(
|
|
146
|
+
this.status.installed,
|
|
147
|
+
'Existing BYAN installation',
|
|
148
|
+
this.status.installed ? 'found (will upgrade)' : 'clean install'
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
container.innerHTML = html;
|
|
152
|
+
nextBtn.disabled = false;
|
|
153
|
+
|
|
154
|
+
this.config.platforms = this.status.platforms || [];
|
|
155
|
+
} catch (err) {
|
|
156
|
+
container.innerHTML = `<div class="detection-item"><div class="detection-status fail"></div><div class="detection-label">Detection failed</div><div class="detection-value">${this.escapeHtml(err.message)}</div></div>`;
|
|
157
|
+
nextBtn.disabled = false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
detectionRow(ok, label, value) {
|
|
162
|
+
const status = ok ? 'ok' : (ok === false ? 'fail' : 'unknown');
|
|
163
|
+
return `<div class="detection-item"><div class="detection-status ${status}"></div><div class="detection-label">${this.escapeHtml(label)}</div><div class="detection-value">${this.escapeHtml(value)}</div></div>`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
selectMode(mode) {
|
|
167
|
+
this.config.mode = mode;
|
|
168
|
+
|
|
169
|
+
document.querySelectorAll('.mode-cards .card').forEach(card => card.classList.remove('selected'));
|
|
170
|
+
event.currentTarget.classList.add('selected');
|
|
171
|
+
|
|
172
|
+
if (mode === 'auto') {
|
|
173
|
+
this.config.userName = this.status?.detection?.userName || 'User';
|
|
174
|
+
this.config.language = 'English';
|
|
175
|
+
this.config.modules = ['core', 'bmm', 'bmb', 'tea', 'cis'];
|
|
176
|
+
this.showPreview();
|
|
177
|
+
} else {
|
|
178
|
+
this.showConfigForm(mode);
|
|
179
|
+
this.showStep('config');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
showConfigForm(mode) {
|
|
184
|
+
const form = document.getElementById('config-form');
|
|
185
|
+
const allModules = [
|
|
186
|
+
{ id: 'core', label: 'Core (foundation)', required: true },
|
|
187
|
+
{ id: 'bmm', label: 'BMM (software development)', required: false },
|
|
188
|
+
{ id: 'bmb', label: 'BMB (agent builder)', required: false },
|
|
189
|
+
{ id: 'tea', label: 'TEA (test architecture)', required: false },
|
|
190
|
+
{ id: 'cis', label: 'CIS (creative innovation)', required: false }
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
const platformOptions = [
|
|
194
|
+
{ id: 'copilot-cli', label: 'GitHub Copilot CLI' },
|
|
195
|
+
{ id: 'vscode', label: 'VSCode Extension' },
|
|
196
|
+
{ id: 'claude', label: 'Claude Code' },
|
|
197
|
+
{ id: 'codex', label: 'Codex / OpenCode' }
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
let html = `
|
|
201
|
+
<div class="form-group">
|
|
202
|
+
<label for="cfg-name">Your Name</label>
|
|
203
|
+
<input id="cfg-name" type="text" value="${this.escapeHtml(this.config.userName)}" placeholder="Yan">
|
|
204
|
+
</div>
|
|
205
|
+
<div class="form-group">
|
|
206
|
+
<label for="cfg-lang">Communication Language</label>
|
|
207
|
+
<select id="cfg-lang">
|
|
208
|
+
<option value="English" ${this.config.language === 'English' ? 'selected' : ''}>English</option>
|
|
209
|
+
<option value="Francais" ${this.config.language === 'Francais' ? 'selected' : ''}>Francais</option>
|
|
210
|
+
</select>
|
|
211
|
+
</div>
|
|
212
|
+
<div class="form-group">
|
|
213
|
+
<label>Target Platforms</label>
|
|
214
|
+
<div class="form-hint">Select the platforms you use.</div>
|
|
215
|
+
<div class="checkbox-group">`;
|
|
216
|
+
|
|
217
|
+
for (const p of platformOptions) {
|
|
218
|
+
const checked = this.config.platforms.includes(p.id) ? 'checked' : '';
|
|
219
|
+
html += `<label><input type="checkbox" name="platform" value="${p.id}" ${checked}> ${this.escapeHtml(p.label)}</label>`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
html += `</div></div>`;
|
|
223
|
+
|
|
224
|
+
if (mode === 'manual') {
|
|
225
|
+
html += `
|
|
226
|
+
<div class="form-group">
|
|
227
|
+
<label>Modules</label>
|
|
228
|
+
<div class="form-hint">Core is always included.</div>
|
|
229
|
+
<div class="checkbox-group">`;
|
|
230
|
+
for (const m of allModules) {
|
|
231
|
+
const checked = m.required || this.config.modules.includes(m.id) ? 'checked' : '';
|
|
232
|
+
const disabled = m.required ? 'disabled' : '';
|
|
233
|
+
html += `<label><input type="checkbox" name="module" value="${m.id}" ${checked} ${disabled}> ${this.escapeHtml(m.label)}</label>`;
|
|
234
|
+
}
|
|
235
|
+
html += `</div></div>`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
form.innerHTML = html;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
readConfigForm() {
|
|
242
|
+
const nameEl = document.getElementById('cfg-name');
|
|
243
|
+
const langEl = document.getElementById('cfg-lang');
|
|
244
|
+
|
|
245
|
+
if (nameEl) this.config.userName = nameEl.value.trim() || 'User';
|
|
246
|
+
if (langEl) this.config.language = langEl.value;
|
|
247
|
+
|
|
248
|
+
const platforms = [];
|
|
249
|
+
document.querySelectorAll('input[name="platform"]:checked').forEach(el => platforms.push(el.value));
|
|
250
|
+
if (platforms.length > 0) this.config.platforms = platforms;
|
|
251
|
+
|
|
252
|
+
const modules = ['core'];
|
|
253
|
+
document.querySelectorAll('input[name="module"]:checked').forEach(el => {
|
|
254
|
+
if (!modules.includes(el.value)) modules.push(el.value);
|
|
255
|
+
});
|
|
256
|
+
if (modules.length > 1) this.config.modules = modules;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
showPreview() {
|
|
260
|
+
this.readConfigForm();
|
|
261
|
+
|
|
262
|
+
const container = document.getElementById('preview-content');
|
|
263
|
+
const rows = [
|
|
264
|
+
['Mode', this.config.mode.toUpperCase()],
|
|
265
|
+
['User Name', this.config.userName || 'User'],
|
|
266
|
+
['Language', this.config.language],
|
|
267
|
+
['Platforms', this.config.platforms.join(', ') || 'auto-detect'],
|
|
268
|
+
['Modules', this.config.modules.join(', ')],
|
|
269
|
+
['Project Root', this.status?.projectRoot || '(auto)']
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
container.innerHTML = rows.map(([k, v]) =>
|
|
273
|
+
`<div class="preview-row"><span class="preview-key">${this.escapeHtml(k)}</span><span class="preview-val">${this.escapeHtml(v)}</span></div>`
|
|
274
|
+
).join('');
|
|
275
|
+
|
|
276
|
+
this.showStep('preview');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async confirmInstall() {
|
|
280
|
+
this.showStep('progress');
|
|
281
|
+
document.getElementById('progress-title').textContent = 'Installing...';
|
|
282
|
+
this.resetProgress();
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const res = await fetch('/api/install', {
|
|
286
|
+
method: 'POST',
|
|
287
|
+
headers: { 'Content-Type': 'application/json' },
|
|
288
|
+
body: JSON.stringify(this.config)
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (!res.ok) {
|
|
292
|
+
const err = await res.json().catch(() => ({ error: 'Unknown error' }));
|
|
293
|
+
this.showComplete({ success: false, summary: { message: err.error } });
|
|
294
|
+
}
|
|
295
|
+
} catch (err) {
|
|
296
|
+
this.showComplete({ success: false, summary: { message: err.message } });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// --- Update Flow ---
|
|
301
|
+
|
|
302
|
+
async startUpdate() {
|
|
303
|
+
this.showStep('update-check');
|
|
304
|
+
const container = document.getElementById('update-info');
|
|
305
|
+
const actionsEl = document.getElementById('update-check-actions');
|
|
306
|
+
const confirmBtn = document.getElementById('btn-update-confirm');
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const res = await fetch('/api/update/check');
|
|
310
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
311
|
+
const data = await res.json();
|
|
312
|
+
|
|
313
|
+
if (data.updateAvailable) {
|
|
314
|
+
container.innerHTML = `
|
|
315
|
+
<div class="detection-item">
|
|
316
|
+
<div class="detection-status ok"></div>
|
|
317
|
+
<div class="detection-label">Update available</div>
|
|
318
|
+
<div class="detection-value">${this.escapeHtml(data.installed)} → ${this.escapeHtml(data.latest)}</div>
|
|
319
|
+
</div>
|
|
320
|
+
${data.changes.length > 0 ? '<div class="code-block">' + data.changes.map(c => this.escapeHtml(c)).join('\n') + '</div>' : ''}`;
|
|
321
|
+
confirmBtn.disabled = false;
|
|
322
|
+
} else {
|
|
323
|
+
container.innerHTML = `
|
|
324
|
+
<div class="detection-item">
|
|
325
|
+
<div class="detection-status ok"></div>
|
|
326
|
+
<div class="detection-label">Up to date</div>
|
|
327
|
+
<div class="detection-value">v${this.escapeHtml(data.installed)}</div>
|
|
328
|
+
</div>
|
|
329
|
+
<p style="color:var(--text-muted);margin-top:1rem;">Your installation is already on the latest version.</p>`;
|
|
330
|
+
confirmBtn.disabled = true;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
actionsEl.style.display = 'flex';
|
|
334
|
+
} catch (err) {
|
|
335
|
+
container.innerHTML = `<div class="detection-item"><div class="detection-status fail"></div><div class="detection-label">Check failed</div><div class="detection-value">${this.escapeHtml(err.message)}</div></div>`;
|
|
336
|
+
actionsEl.style.display = 'flex';
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async confirmUpdate() {
|
|
341
|
+
this.showStep('progress');
|
|
342
|
+
document.getElementById('progress-title').textContent = 'Updating...';
|
|
343
|
+
this.resetProgress();
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const res = await fetch('/api/update', {
|
|
347
|
+
method: 'POST',
|
|
348
|
+
headers: { 'Content-Type': 'application/json' },
|
|
349
|
+
body: JSON.stringify({})
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (!res.ok) {
|
|
353
|
+
const err = await res.json().catch(() => ({ error: 'Unknown error' }));
|
|
354
|
+
this.showComplete({ success: false, summary: { message: err.error } });
|
|
355
|
+
}
|
|
356
|
+
} catch (err) {
|
|
357
|
+
this.showComplete({ success: false, summary: { message: err.message } });
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// --- UI Helpers ---
|
|
362
|
+
|
|
363
|
+
addLog(data) {
|
|
364
|
+
const container = document.getElementById('log-content');
|
|
365
|
+
const badge = document.getElementById('log-badge');
|
|
366
|
+
const level = data.level || 'info';
|
|
367
|
+
const time = data.timestamp ? new Date(data.timestamp).toLocaleTimeString() : new Date().toLocaleTimeString();
|
|
368
|
+
|
|
369
|
+
const entry = document.createElement('div');
|
|
370
|
+
entry.className = `log-entry ${level}`;
|
|
371
|
+
entry.innerHTML = `<span class="log-time">${time}</span>${this.escapeHtml(data.message)}`;
|
|
372
|
+
container.appendChild(entry);
|
|
373
|
+
|
|
374
|
+
container.scrollTop = container.scrollHeight;
|
|
375
|
+
|
|
376
|
+
this.logCount++;
|
|
377
|
+
badge.textContent = this.logCount;
|
|
378
|
+
badge.classList.remove('hidden');
|
|
379
|
+
|
|
380
|
+
if (!this.logsExpanded) {
|
|
381
|
+
const panel = document.getElementById('log-panel');
|
|
382
|
+
panel.classList.add('expanded');
|
|
383
|
+
this.logsExpanded = true;
|
|
384
|
+
document.querySelector('.log-header').setAttribute('aria-expanded', 'true');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
updateProgress(data) {
|
|
389
|
+
const fill = document.getElementById('progress-fill');
|
|
390
|
+
const label = document.getElementById('progress-label');
|
|
391
|
+
const pct = document.getElementById('progress-pct');
|
|
392
|
+
|
|
393
|
+
const percent = data.total > 0 ? Math.round((data.step / data.total) * 100) : 0;
|
|
394
|
+
|
|
395
|
+
fill.style.width = `${percent}%`;
|
|
396
|
+
label.textContent = data.label || '';
|
|
397
|
+
pct.textContent = `${percent}%`;
|
|
398
|
+
|
|
399
|
+
const bar = fill.parentElement;
|
|
400
|
+
bar.setAttribute('aria-valuenow', percent);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
resetProgress() {
|
|
404
|
+
document.getElementById('progress-fill').style.width = '0%';
|
|
405
|
+
document.getElementById('progress-label').textContent = 'Preparing...';
|
|
406
|
+
document.getElementById('progress-pct').textContent = '0%';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
showComplete(data) {
|
|
410
|
+
const icon = document.getElementById('done-icon');
|
|
411
|
+
const title = document.getElementById('done-title');
|
|
412
|
+
const summary = document.getElementById('done-summary');
|
|
413
|
+
|
|
414
|
+
if (data.success) {
|
|
415
|
+
icon.innerHTML = '✓';
|
|
416
|
+
icon.classList.remove('error');
|
|
417
|
+
title.textContent = 'Installation Complete';
|
|
418
|
+
|
|
419
|
+
const s = data.summary || {};
|
|
420
|
+
let html = `<p>${this.escapeHtml(s.message || 'Done')}</p>`;
|
|
421
|
+
if (s.projectRoot) html += `<p><strong>Project:</strong> <code>${this.escapeHtml(s.projectRoot)}</code></p>`;
|
|
422
|
+
if (s.mode) html += `<p><strong>Mode:</strong> ${this.escapeHtml(s.mode)}</p>`;
|
|
423
|
+
if (s.platforms && s.platforms.length) html += `<p><strong>Platforms:</strong> ${s.platforms.map(p => this.escapeHtml(p)).join(', ')}</p>`;
|
|
424
|
+
html += '<p style="margin-top:1rem;color:var(--text-muted);">You can close this window.</p>';
|
|
425
|
+
summary.innerHTML = html;
|
|
426
|
+
} else {
|
|
427
|
+
icon.innerHTML = '✗';
|
|
428
|
+
icon.classList.add('error');
|
|
429
|
+
title.textContent = 'Installation Failed';
|
|
430
|
+
|
|
431
|
+
const msg = data.summary?.message || 'Unknown error';
|
|
432
|
+
summary.innerHTML = `<p style="color:var(--error)">${this.escapeHtml(msg)}</p><p style="margin-top:1rem;color:var(--text-muted);">Check the logs below for details. You can try again.</p>`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
this.showStep('done');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
toggleLogs() {
|
|
439
|
+
const panel = document.getElementById('log-panel');
|
|
440
|
+
const header = document.querySelector('.log-header');
|
|
441
|
+
this.logsExpanded = !this.logsExpanded;
|
|
442
|
+
panel.classList.toggle('expanded', this.logsExpanded);
|
|
443
|
+
header.setAttribute('aria-expanded', this.logsExpanded);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
escapeHtml(str) {
|
|
447
|
+
if (!str) return '';
|
|
448
|
+
const s = String(str);
|
|
449
|
+
const div = document.createElement('div');
|
|
450
|
+
div.appendChild(document.createTextNode(s));
|
|
451
|
+
return div.innerHTML;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const app = new ByanApp();
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>BYAN - Agent Installer</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header>
|
|
11
|
+
<div class="header-inner">
|
|
12
|
+
<h1 class="logo">BYAN</h1>
|
|
13
|
+
<p class="subtitle">Builder of YAN — Agent Installer</p>
|
|
14
|
+
</div>
|
|
15
|
+
</header>
|
|
16
|
+
|
|
17
|
+
<nav class="wizard-steps" id="wizard-nav" aria-label="Installation steps">
|
|
18
|
+
<ol>
|
|
19
|
+
<li class="wizard-step active" data-step="welcome"><span class="step-num">1</span><span class="step-label">Welcome</span></li>
|
|
20
|
+
<li class="wizard-step" data-step="detection"><span class="step-num">2</span><span class="step-label">Detection</span></li>
|
|
21
|
+
<li class="wizard-step" data-step="mode"><span class="step-num">3</span><span class="step-label">Mode</span></li>
|
|
22
|
+
<li class="wizard-step" data-step="config"><span class="step-num">4</span><span class="step-label">Config</span></li>
|
|
23
|
+
<li class="wizard-step" data-step="preview"><span class="step-num">5</span><span class="step-label">Preview</span></li>
|
|
24
|
+
<li class="wizard-step" data-step="progress"><span class="step-num">6</span><span class="step-label">Install</span></li>
|
|
25
|
+
<li class="wizard-step" data-step="done"><span class="step-num">7</span><span class="step-label">Done</span></li>
|
|
26
|
+
</ol>
|
|
27
|
+
</nav>
|
|
28
|
+
|
|
29
|
+
<main>
|
|
30
|
+
<!-- Step 1: Welcome -->
|
|
31
|
+
<section class="step" id="step-welcome" role="region" aria-label="Welcome">
|
|
32
|
+
<div class="step-content">
|
|
33
|
+
<h2>Welcome to BYAN</h2>
|
|
34
|
+
<p class="lead">Intelligent AI agent platform with multi-platform support.<br>
|
|
35
|
+
GitHub Copilot CLI, Claude Code, Codex, and VSCode.</p>
|
|
36
|
+
<div class="feature-grid">
|
|
37
|
+
<div class="feature-card">
|
|
38
|
+
<div class="feature-icon" aria-hidden="true">◆</div>
|
|
39
|
+
<h3>64 Quality Mantras</h3>
|
|
40
|
+
<p>Built on Merise Agile + TDD methodology with rigorous quality standards.</p>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="feature-card">
|
|
43
|
+
<div class="feature-icon" aria-hidden="true">◈</div>
|
|
44
|
+
<h3>Multi-Platform</h3>
|
|
45
|
+
<p>Install agents across Copilot CLI, Claude Code, Codex, and VSCode simultaneously.</p>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="feature-card">
|
|
48
|
+
<div class="feature-icon" aria-hidden="true">▸</div>
|
|
49
|
+
<h3>Smart Detection</h3>
|
|
50
|
+
<p>Automatically detects your environment and recommends optimal configuration.</p>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="actions">
|
|
54
|
+
<button class="btn btn-primary" onclick="app.startInstall()" aria-label="Start new installation">Install BYAN</button>
|
|
55
|
+
<button class="btn btn-secondary" onclick="app.startUpdate()" aria-label="Update existing installation">Update Existing</button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
59
|
+
|
|
60
|
+
<!-- Step 2: Detection -->
|
|
61
|
+
<section class="step hidden" id="step-detection" role="region" aria-label="Environment detection">
|
|
62
|
+
<div class="step-content">
|
|
63
|
+
<h2>Environment Detection</h2>
|
|
64
|
+
<p class="lead">Scanning your system for installed platforms and tools.</p>
|
|
65
|
+
<div id="detection-results" class="detection-grid" aria-live="polite">
|
|
66
|
+
<div class="spinner-container"><div class="spinner"></div><p>Detecting...</p></div>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="actions">
|
|
69
|
+
<button class="btn btn-secondary" onclick="app.goBack()">Back</button>
|
|
70
|
+
<button class="btn btn-primary" onclick="app.showStep('mode')" id="btn-detection-next" disabled>Continue</button>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
<!-- Step 3: Mode Selection -->
|
|
76
|
+
<section class="step hidden" id="step-mode" role="region" aria-label="Installation mode">
|
|
77
|
+
<div class="step-content">
|
|
78
|
+
<h2>Installation Mode</h2>
|
|
79
|
+
<p class="lead">Choose how you want to install BYAN.</p>
|
|
80
|
+
<div class="mode-cards">
|
|
81
|
+
<button class="card" onclick="app.selectMode('auto')" aria-label="Auto mode: quick install with smart defaults">
|
|
82
|
+
<div class="card-badge">Recommended</div>
|
|
83
|
+
<h3>AUTO</h3>
|
|
84
|
+
<p>Quick install with smart defaults based on detected environment.</p>
|
|
85
|
+
</button>
|
|
86
|
+
<button class="card" onclick="app.selectMode('custom')" aria-label="Custom mode: guided interview">
|
|
87
|
+
<h3>CUSTOM</h3>
|
|
88
|
+
<p>Guided interview with recommendations for each option.</p>
|
|
89
|
+
</button>
|
|
90
|
+
<button class="card" onclick="app.selectMode('manual')" aria-label="Manual mode: choose agents individually">
|
|
91
|
+
<h3>MANUAL</h3>
|
|
92
|
+
<p>Full control. Choose every agent and platform individually.</p>
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="actions">
|
|
96
|
+
<button class="btn btn-secondary" onclick="app.goBack()">Back</button>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</section>
|
|
100
|
+
|
|
101
|
+
<!-- Step 4: Configuration -->
|
|
102
|
+
<section class="step hidden" id="step-config" role="region" aria-label="Configuration">
|
|
103
|
+
<div class="step-content">
|
|
104
|
+
<h2>Configuration</h2>
|
|
105
|
+
<div id="config-form" class="config-form"></div>
|
|
106
|
+
<div class="actions">
|
|
107
|
+
<button class="btn btn-secondary" onclick="app.goBack()">Back</button>
|
|
108
|
+
<button class="btn btn-primary" onclick="app.showPreview()">Preview</button>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
<!-- Step 5: Preview -->
|
|
114
|
+
<section class="step hidden" id="step-preview" role="region" aria-label="Installation preview">
|
|
115
|
+
<div class="step-content">
|
|
116
|
+
<h2>Installation Preview</h2>
|
|
117
|
+
<p class="lead">Review your configuration before installing.</p>
|
|
118
|
+
<div id="preview-content" class="preview-block"></div>
|
|
119
|
+
<div class="actions">
|
|
120
|
+
<button class="btn btn-secondary" onclick="app.goBack()">Back</button>
|
|
121
|
+
<button class="btn btn-primary" onclick="app.confirmInstall()">Confirm & Install</button>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</section>
|
|
125
|
+
|
|
126
|
+
<!-- Step 6: Progress -->
|
|
127
|
+
<section class="step hidden" id="step-progress" role="region" aria-label="Installation progress">
|
|
128
|
+
<div class="step-content">
|
|
129
|
+
<h2 id="progress-title">Installing...</h2>
|
|
130
|
+
<div class="progress-container">
|
|
131
|
+
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
|
132
|
+
<div class="progress-fill" id="progress-fill"></div>
|
|
133
|
+
</div>
|
|
134
|
+
<p class="progress-label" id="progress-label">Preparing...</p>
|
|
135
|
+
<p class="progress-pct" id="progress-pct">0%</p>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</section>
|
|
139
|
+
|
|
140
|
+
<!-- Step 7: Done -->
|
|
141
|
+
<section class="step hidden" id="step-done" role="region" aria-label="Installation complete">
|
|
142
|
+
<div class="step-content">
|
|
143
|
+
<div id="done-icon" class="done-icon" aria-hidden="true">✓</div>
|
|
144
|
+
<h2 id="done-title">Installation Complete</h2>
|
|
145
|
+
<div id="done-summary" class="done-summary"></div>
|
|
146
|
+
<div class="actions">
|
|
147
|
+
<button class="btn btn-primary" onclick="window.location.reload()">Start Over</button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</section>
|
|
151
|
+
|
|
152
|
+
<!-- Update: Check -->
|
|
153
|
+
<section class="step hidden" id="step-update-check" role="region" aria-label="Update check">
|
|
154
|
+
<div class="step-content">
|
|
155
|
+
<h2>Checking for Updates</h2>
|
|
156
|
+
<div id="update-info" aria-live="polite">
|
|
157
|
+
<div class="spinner-container"><div class="spinner"></div><p>Checking...</p></div>
|
|
158
|
+
</div>
|
|
159
|
+
<div class="actions" id="update-check-actions" style="display:none">
|
|
160
|
+
<button class="btn btn-secondary" onclick="app.showStep('welcome')">Back</button>
|
|
161
|
+
<button class="btn btn-primary" onclick="app.confirmUpdate()" id="btn-update-confirm" disabled>Update Now</button>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</section>
|
|
165
|
+
|
|
166
|
+
<!-- Update: Diff -->
|
|
167
|
+
<section class="step hidden" id="step-update-diff" role="region" aria-label="Changes preview">
|
|
168
|
+
<div class="step-content">
|
|
169
|
+
<h2>Changes Preview</h2>
|
|
170
|
+
<div id="update-diff" class="code-block"></div>
|
|
171
|
+
<div class="actions">
|
|
172
|
+
<button class="btn btn-secondary" onclick="app.showStep('update-check')">Cancel</button>
|
|
173
|
+
<button class="btn btn-primary" onclick="app.confirmUpdate()">Update Now</button>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</section>
|
|
177
|
+
</main>
|
|
178
|
+
|
|
179
|
+
<footer>
|
|
180
|
+
<div id="log-panel" class="log-panel">
|
|
181
|
+
<button class="log-header" onclick="app.toggleLogs()" aria-expanded="false" aria-controls="log-content">
|
|
182
|
+
<span>Logs</span>
|
|
183
|
+
<span class="log-badge hidden" id="log-badge">0</span>
|
|
184
|
+
<span class="log-chevron" id="log-chevron" aria-hidden="true">▲</span>
|
|
185
|
+
</button>
|
|
186
|
+
<div id="log-content" class="log-content" role="log" aria-live="polite"></div>
|
|
187
|
+
</div>
|
|
188
|
+
</footer>
|
|
189
|
+
|
|
190
|
+
<script src="app.js"></script>
|
|
191
|
+
</body>
|
|
192
|
+
</html>
|