create-byan-agent 2.9.0 → 2.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "2.9.0",
3
+ "version": "2.9.2",
4
4
  "description": "BYAN v2.2.2 - Intelligent AI agent installer with multi-platform native support (GitHub Copilot CLI, Claude Code, Codex/OpenCode)",
5
5
  "bin": {
6
6
  "create-byan-agent": "bin/create-byan-agent-v2.js"
@@ -99,9 +99,10 @@ class ClaudeAdapter extends Bridge {
99
99
 
100
100
  _handleStderr(data) {
101
101
  const text = data.toString().trim();
102
- if (text) {
103
- this.onError(new Error(`claude stderr: ${text}`));
104
- }
102
+ if (!text) return;
103
+ // Claude dumps progress/status info to stderr — not errors
104
+ if (/^(Initializing|Loading|Connected|Session|Warming|Cost:|Token)/m.test(text)) return;
105
+ this.onError(new Error(`claude stderr: ${text}`));
105
106
  }
106
107
 
107
108
  _parseLine(line) {
@@ -50,6 +50,7 @@ async function detectCLIs() {
50
50
  const cmdPath = whichSync(def.command);
51
51
  if (!cmdPath) {
52
52
  results.push({
53
+ id: def.name,
53
54
  name: def.name,
54
55
  path: null,
55
56
  version: null,
@@ -63,6 +64,7 @@ async function detectCLIs() {
63
64
  const version = versionResult.ok ? parseVersion(versionResult.output) : null;
64
65
 
65
66
  results.push({
67
+ id: def.name,
66
68
  name: def.name,
67
69
  path: cmdPath,
68
70
  version,
@@ -32,7 +32,10 @@ class CodexAdapter extends Bridge {
32
32
 
33
33
  this.process.stderr.on('data', (data) => {
34
34
  const text = data.toString().trim();
35
- if (text) this.onError(new Error(`codex stderr: ${text}`));
35
+ if (!text) return;
36
+ // Codex dumps progress/usage info to stderr
37
+ if (/^(Running|Executing|Model:|Tokens|Cost)/m.test(text)) return;
38
+ this.onError(new Error(`codex stderr: ${text}`));
36
39
  });
37
40
 
38
41
  this.process.on('error', (err) => {
@@ -42,7 +42,10 @@ class CopilotAdapter extends Bridge {
42
42
 
43
43
  this.process.stderr.on('data', (data) => {
44
44
  const text = data.toString().trim();
45
- if (text) this.onError(new Error(`copilot stderr: ${text}`));
45
+ if (!text) return;
46
+ // Copilot dumps usage stats to stderr — not errors
47
+ if (/^(Total usage|API time|Total session|Total code|Breakdown by|claude-|gpt-|o[1-4]|Est\.|Premium request)/m.test(text)) return;
48
+ this.onError(new Error(`copilot stderr: ${text}`));
46
49
  });
47
50
 
48
51
  this.process.on('error', (err) => {
@@ -195,6 +195,34 @@ body {
195
195
  border-color: var(--accent);
196
196
  }
197
197
 
198
+ .cli-select {
199
+ background: var(--surface);
200
+ color: var(--text);
201
+ border: 1px solid var(--border);
202
+ border-radius: var(--radius);
203
+ padding: 6px 12px;
204
+ font-size: 0.8rem;
205
+ font-family: var(--font-sans);
206
+ cursor: pointer;
207
+ outline: none;
208
+ transition: border-color var(--transition);
209
+ appearance: none;
210
+ -webkit-appearance: none;
211
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2394a3b8' d='M3 4.5L6 8l3-3.5H3z'/%3E%3C/svg%3E");
212
+ background-repeat: no-repeat;
213
+ background-position: right 8px center;
214
+ padding-right: 28px;
215
+ }
216
+
217
+ .cli-select:hover,
218
+ .cli-select:focus {
219
+ border-color: var(--accent);
220
+ }
221
+
222
+ .cli-select option:disabled {
223
+ color: var(--text-dim);
224
+ }
225
+
198
226
  .topbar-right {
199
227
  display: flex;
200
228
  align-items: center;
@@ -1522,6 +1550,11 @@ body {
1522
1550
  border-left-color: var(--success);
1523
1551
  }
1524
1552
 
1553
+ .toast.error {
1554
+ border-left-color: var(--error);
1555
+ background: rgba(239, 68, 68, 0.1);
1556
+ }
1557
+
1525
1558
  .toast.warning {
1526
1559
  border-left-color: var(--warning);
1527
1560
  }
@@ -1566,6 +1599,10 @@ body {
1566
1599
  display: none;
1567
1600
  }
1568
1601
 
1602
+ .cli-select {
1603
+ display: none;
1604
+ }
1605
+
1569
1606
  .export-options {
1570
1607
  grid-template-columns: 1fr;
1571
1608
  }
@@ -19,6 +19,11 @@
19
19
  <span class="agent-icon" id="active-agent-icon"></span>
20
20
  <span class="agent-name" id="active-agent-name">No agent</span>
21
21
  </div>
22
+ <select id="cli-select" class="cli-select" aria-label="Select CLI backend">
23
+ <option value="claude">Claude Code</option>
24
+ <option value="copilot">GitHub Copilot</option>
25
+ <option value="codex">Codex</option>
26
+ </select>
22
27
  <select id="model-select" class="model-select" aria-label="Select model">
23
28
  <option value="">Default model</option>
24
29
  <option value="sonnet">Claude Sonnet</option>
@@ -62,6 +62,7 @@ class ByanChat {
62
62
  agentList: document.getElementById('agent-list'),
63
63
  sessionList: document.getElementById('session-list'),
64
64
  cliStatus: document.getElementById('cli-status'),
65
+ cliSelect: document.getElementById('cli-select'),
65
66
  modelSelect: document.getElementById('model-select'),
66
67
  agentIndicator: document.getElementById('agent-indicator'),
67
68
  activeAgentIcon: document.getElementById('active-agent-icon'),
@@ -136,9 +137,12 @@ class ByanChat {
136
137
  case 'tool-approval':
137
138
  this.showToolApproval(data.tool, data.command);
138
139
  break;
140
+ case 'chat-error':
139
141
  case 'error':
140
- this.showToast(data.message || 'An error occurred', 'error');
141
- this.finishStreaming(this.streamingContent);
142
+ this.handleChatError(data);
143
+ break;
144
+ case 'chat-tool':
145
+ this.showToolUsage(data.tool);
142
146
  break;
143
147
  case 'raw-output':
144
148
  this.appendRawOutput(data.content);
@@ -148,6 +152,19 @@ class ByanChat {
148
152
  }
149
153
  }
150
154
 
155
+ handleChatError(data) {
156
+ const errorMsg = data.error || data.message || 'An error occurred';
157
+ this.showToast(errorMsg, 'error');
158
+ this.addMessage('system', `Error: ${errorMsg}`, { agent: 'System' });
159
+ this.finishStreaming(this.streamingContent);
160
+ }
161
+
162
+ showToolUsage(tool) {
163
+ if (!tool) return;
164
+ const name = typeof tool === 'string' ? tool : tool.name || 'unknown tool';
165
+ this.appendChunk(`\n> Using tool: **${name}**\n`);
166
+ }
167
+
151
168
  // ============================================================
152
169
  // CLI Detection
153
170
  // ============================================================
@@ -155,7 +172,7 @@ class ByanChat {
155
172
  async detectCLIs() {
156
173
  const knownCLIs = [
157
174
  { id: 'claude', name: 'Claude Code', cmd: 'claude' },
158
- { id: 'copilot', name: 'GitHub Copilot', cmd: 'gh copilot' },
175
+ { id: 'copilot', name: 'GitHub Copilot', cmd: 'copilot' },
159
176
  { id: 'codex', name: 'OpenCode', cmd: 'codex' }
160
177
  ];
161
178
 
@@ -184,31 +201,48 @@ class ByanChat {
184
201
  renderCLIStatus() {
185
202
  this.dom.cliStatus.innerHTML = '';
186
203
  for (const cli of this.clis) {
204
+ const cliId = cli.id || cli.name;
187
205
  const el = document.createElement('div');
188
- el.className = 'cli-item' + (cli.available ? ' available' : '') + (this.currentCLI === cli.id ? ' active' : '');
206
+ el.className = 'cli-item' + (cli.available ? ' available' : '') + (this.currentCLI === cliId ? ' active' : '');
189
207
  el.setAttribute('role', 'button');
190
208
  el.setAttribute('tabindex', '0');
191
209
  el.setAttribute('aria-label', `${cli.name}: ${cli.available ? 'available' : 'not detected'}`);
192
210
  el.innerHTML = `<span class="cli-dot"></span>${this.escapeHtml(cli.name)}`;
193
- el.addEventListener('click', () => this.selectCLI(cli.id));
194
- el.addEventListener('keydown', (e) => { if (e.key === 'Enter') this.selectCLI(cli.id); });
211
+ el.addEventListener('click', () => this.selectCLI(cliId));
212
+ el.addEventListener('keydown', (e) => { if (e.key === 'Enter') this.selectCLI(cliId); });
195
213
  this.dom.cliStatus.appendChild(el);
196
214
  }
215
+ this.renderCLISelect();
216
+ }
217
+
218
+ renderCLISelect() {
219
+ if (!this.dom.cliSelect) return;
220
+ this.dom.cliSelect.innerHTML = '';
221
+ for (const cli of this.clis) {
222
+ const cliId = cli.id || cli.name;
223
+ const opt = document.createElement('option');
224
+ opt.value = cliId;
225
+ opt.textContent = cli.name + (cli.available ? '' : ' (not detected)');
226
+ opt.disabled = !cli.available;
227
+ this.dom.cliSelect.appendChild(opt);
228
+ }
229
+ if (this.currentCLI) this.dom.cliSelect.value = this.currentCLI;
197
230
  }
198
231
 
199
232
  pickDefaultCLI() {
200
233
  const available = this.clis.filter(c => c.available);
201
234
  if (available.length > 0) {
202
- this.selectCLI(available[0].id);
235
+ this.selectCLI(available[0].id || available[0].name);
203
236
  } else if (this.clis.length > 0) {
204
- this.selectCLI(this.clis[0].id);
237
+ this.selectCLI(this.clis[0].id || this.clis[0].name);
205
238
  }
206
239
  }
207
240
 
208
241
  selectCLI(id) {
209
242
  this.currentCLI = id;
210
- const cli = this.clis.find(c => c.id === id);
211
- this.dom.cliLabel.textContent = cli ? cli.name : id;
243
+ const cli = this.clis.find(c => (c.id || c.name) === id);
244
+ if (this.dom.cliLabel) this.dom.cliLabel.textContent = cli ? cli.name : id;
245
+ if (this.dom.cliSelect) this.dom.cliSelect.value = id;
212
246
  this.renderCLIStatus();
213
247
  this.saveState();
214
248
  }
@@ -417,6 +451,7 @@ class ByanChat {
417
451
 
418
452
  async startSession() {
419
453
  const agentName = this.currentAgent ? this.currentAgent.name : null;
454
+ let bridgeOk = false;
420
455
 
421
456
  try {
422
457
  const res = await fetch('/api/chat/start', {
@@ -427,10 +462,15 @@ class ByanChat {
427
462
  if (res.ok) {
428
463
  const data = await res.json();
429
464
  this.sessionId = data.sessionId || data.id || this.generateId();
465
+ bridgeOk = true;
430
466
  } else {
467
+ const err = await res.json().catch(() => ({}));
468
+ const errMsg = err.error || `CLI bridge failed (${this.currentCLI || 'unknown'})`;
469
+ this.showToast(errMsg, 'error');
431
470
  this.sessionId = this.generateId();
432
471
  }
433
- } catch {
472
+ } catch (e) {
473
+ this.showToast(`Cannot reach server: ${e.message || 'network error'}`, 'error');
434
474
  this.sessionId = this.generateId();
435
475
  }
436
476
 
@@ -450,7 +490,10 @@ class ByanChat {
450
490
  this.renderSessions();
451
491
 
452
492
  if (this.currentAgent) {
453
- this.addMessage('system', `Session started with ${this.currentAgent.title || this.currentAgent.name}`);
493
+ this.addMessage('system', `Session started with ${this.currentAgent.title || this.currentAgent.name}` +
494
+ (bridgeOk ? ` via ${this.currentCLI || 'default CLI'}` : ' (offline mode — CLI bridge unavailable)'));
495
+ } else if (!bridgeOk) {
496
+ this.addMessage('system', 'Session started in offline mode — CLI bridge could not be initialized');
454
497
  }
455
498
 
456
499
  this.wsSend({ type: 'join', sessionId: this.sessionId });
@@ -1324,6 +1367,11 @@ class ByanChat {
1324
1367
  // Model select
1325
1368
  this.dom.modelSelect.addEventListener('change', (e) => this.switchModel(e.target.value));
1326
1369
 
1370
+ // CLI select
1371
+ if (this.dom.cliSelect) {
1372
+ this.dom.cliSelect.addEventListener('change', (e) => this.selectCLI(e.target.value));
1373
+ }
1374
+
1327
1375
  // Sidebar overlay (close on click)
1328
1376
  this.dom.sidebarOverlay.addEventListener('click', () => this.closeSidebar());
1329
1377