@xyz-credit/agent-cli 1.2.1 → 1.2.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": "@xyz-credit/agent-cli",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "CLI for onboarding AI agents to xyz.credit — Device Flow Auth, MCP Bridge, Service Marketplace, Daemonization & Cloud Export",
5
5
  "bin": {
6
6
  "xyz-agent": "./bin/xyz-agent.js"
@@ -290,6 +290,26 @@ async function dashboardStart(creds) {
290
290
  } catch (e) {
291
291
  dashboard.addLog(`{red-fg}Send failed:{/red-fg} ${e.message}`);
292
292
  }
293
+ } else if (cmd.type === 'retry-mcp') {
294
+ // Retry MCP connection
295
+ if (localMcpUrl) {
296
+ dashboard.addLog(`Retrying MCP: ${localMcpUrl}`);
297
+ try {
298
+ const discovery = await discoverAndRegisterServices();
299
+ if (discovery) {
300
+ dashboard.addLog(`{green-fg}MCP reconnected:{/green-fg} ${discovery.tools_count} tools found${discovery.registered ? ' (auto-registered)' : ''}`);
301
+ dashboard.updateConnectivity(true, true);
302
+ } else {
303
+ dashboard.addLog('{red-fg}MCP retry failed:{/red-fg} Could not discover tools');
304
+ dashboard.updateConnectivity(true, false);
305
+ }
306
+ } catch (e) {
307
+ dashboard.addLog(`{red-fg}MCP retry error:{/red-fg} ${e.message}`);
308
+ dashboard.updateConnectivity(true, false);
309
+ }
310
+ } else {
311
+ dashboard.addLog('{yellow-fg}No MCP URL configured.{/yellow-fg} Run setup to configure.');
312
+ }
293
313
  } else if (cmd.type === 'task') {
294
314
  dashboard.addLog(`Agent thought: User asked me to "${cmd.command}". Executing...`);
295
315
  // Check for pending tasks as the user requested
@@ -8,7 +8,6 @@
8
8
  * - Priority Task Input: User can type commands at any time
9
9
  */
10
10
  const blessed = require('blessed');
11
- const chalk = require('chalk');
12
11
  const { EventEmitter } = require('events');
13
12
 
14
13
  class Dashboard {
@@ -18,11 +17,13 @@ class Dashboard {
18
17
  this.logBox = null;
19
18
  this.statusBox = null;
20
19
  this.inputBox = null;
20
+ this.helpText = null;
21
21
  this.statsBox = null;
22
22
  this.logs = [];
23
23
  this.status = { status: 'starting', cycleCount: 0, uptimeSeconds: 0 };
24
24
  this.unreadCount = 0;
25
- this.onUserCommand = null;
25
+ this._renderTimer = null;
26
+ this._logQueue = [];
26
27
  }
27
28
 
28
29
  start(agentName, platformUrl, mcpUrl) {
@@ -30,6 +31,7 @@ class Dashboard {
30
31
  smartCSR: true,
31
32
  title: `xyz-agent — ${agentName}`,
32
33
  fullUnicode: true,
34
+ grabKeys: false,
33
35
  });
34
36
 
35
37
  // ── Status Bar (top) ──
@@ -55,7 +57,7 @@ class Dashboard {
55
57
  this._updateStats(agentName, platformUrl, mcpUrl);
56
58
 
57
59
  // ── Activity Logs (left) ──
58
- this.logBox = blessed.log({
60
+ this.logBox = blessed.box({
59
61
  parent: this.screen,
60
62
  top: 3, left: 0, width: '65%', height: '60%',
61
63
  tags: true,
@@ -78,47 +80,56 @@ class Dashboard {
78
80
  style: { border: { fg: 'green' }, bg: 'black' },
79
81
  });
80
82
 
81
- this.inputBox = blessed.textbox({
83
+ this.inputBox = blessed.textarea({
82
84
  parent: inputContainer,
83
85
  top: 0, left: 1, right: 1, height: 1,
84
- inputOnFocus: true,
86
+ keys: true,
87
+ mouse: false,
88
+ inputOnFocus: false,
85
89
  style: { fg: 'white', bg: 'black' },
86
90
  });
87
91
 
88
- // Help text
89
- blessed.text({
92
+ // Help text — use white so it's actually visible
93
+ this.helpText = blessed.text({
90
94
  parent: inputContainer,
91
95
  top: 1, left: 1,
92
96
  tags: true,
93
- content: '{gray-fg}Commands: status | sync | post | inbox | msg <id> <text> | quit{/gray-fg}',
94
- style: { bg: 'black' },
97
+ content: '{white-fg}Commands: status | sync | post | inbox | retry | msg <id> <text> | quit{/white-fg}',
98
+ style: { fg: 'white', bg: 'black' },
95
99
  });
96
100
 
97
- // Handle input
98
- this.inputBox.on('submit', (value) => {
99
- if (value && value.trim()) {
100
- this._handleInput(value.trim());
101
+ // Handle enter key on textarea manually
102
+ this.inputBox.key('enter', () => {
103
+ const value = this.inputBox.getValue().replace(/\n/g, '').trim();
104
+ if (value) {
105
+ this._handleInput(value);
101
106
  }
102
107
  this.inputBox.clearValue();
103
108
  this.inputBox.focus();
104
- this.screen.render();
109
+ this._render();
110
+ });
111
+
112
+ // Escape/Ctrl-C to quit
113
+ this.inputBox.key(['escape', 'C-c'], () => {
114
+ this.ee.emit('quit');
105
115
  });
106
116
 
107
- // Key bindings
108
- this.screen.key(['escape', 'C-c'], () => {
117
+ // Global Ctrl-C fallback
118
+ this.screen.key(['C-c'], () => {
109
119
  this.ee.emit('quit');
110
120
  });
111
121
 
122
+ // Tab to re-focus input
112
123
  this.screen.key(['tab'], () => {
113
124
  this.inputBox.focus();
114
- this.screen.render();
125
+ this._render();
115
126
  });
116
127
 
117
- // Focus input
128
+ // Focus input and do initial render
118
129
  this.inputBox.focus();
119
130
  this.screen.render();
120
131
 
121
- this.addLog('Dashboard initialized. Press Tab to focus input.');
132
+ this.addLog('Dashboard initialized. Type commands below.');
122
133
  this.addLog(`Agent: ${agentName}`);
123
134
  this.addLog(`Platform: ${platformUrl}`);
124
135
  if (mcpUrl) this.addLog(`MCP: ${mcpUrl}`);
@@ -155,6 +166,12 @@ class Dashboard {
155
166
  return;
156
167
  }
157
168
 
169
+ if (lower === 'retry' || lower === 'reconnect') {
170
+ this.addLog('Retrying MCP connection...');
171
+ this.ee.emit('user-command', { type: 'retry-mcp' });
172
+ return;
173
+ }
174
+
158
175
  // msg <agent_id> <message text>
159
176
  if (lower.startsWith('msg ') || lower.startsWith('message ')) {
160
177
  const parts = cmd.split(/\s+/);
@@ -179,20 +196,32 @@ class Dashboard {
179
196
  const formatted = `{gray-fg}[${ts}]{/gray-fg} ${msg}`;
180
197
  this.logs.push(formatted);
181
198
  if (this.logs.length > 500) this.logs.shift();
182
- if (this.logBox) {
183
- this.logBox.pushLine(formatted);
184
- this.logBox.setScrollPerc(100);
185
- this._scheduleRender();
199
+ this._logQueue.push(formatted);
200
+ this._scheduleFlush();
201
+ }
202
+
203
+ _scheduleFlush() {
204
+ if (this._renderTimer) return;
205
+ this._renderTimer = setTimeout(() => {
206
+ this._renderTimer = null;
207
+ this._flushLogs();
208
+ }, 50);
209
+ }
210
+
211
+ _flushLogs() {
212
+ if (!this.logBox || this._logQueue.length === 0) return;
213
+ const lines = this._logQueue.splice(0);
214
+ for (const line of lines) {
215
+ this.logBox.pushLine(line);
186
216
  }
217
+ this.logBox.setScrollPerc(100);
218
+ this._render();
187
219
  }
188
220
 
189
- _scheduleRender() {
190
- if (this._renderPending) return;
191
- this._renderPending = true;
192
- setImmediate(() => {
193
- this._renderPending = false;
194
- if (this.screen) this.screen.render();
195
- });
221
+ _render() {
222
+ if (this.screen) {
223
+ try { this.screen.render(); } catch { /* ignore render errors */ }
224
+ }
196
225
  }
197
226
 
198
227
  updateHeartbeat(data) {
@@ -213,13 +242,12 @@ class Dashboard {
213
242
  `Last Ping: {gray-fg}${data.lastPing ? new Date(data.lastPing).toLocaleTimeString() : '-'}{/gray-fg}` +
214
243
  msgBadge
215
244
  );
216
- if (this.screen) this._scheduleRender();
245
+ this._render();
217
246
  }
218
247
  }
219
248
 
220
249
  updateUnreadCount(count) {
221
250
  this.unreadCount = count;
222
- // Re-render status bar with new count
223
251
  if (this.status) this.updateHeartbeat(this.status);
224
252
  }
225
253
 
@@ -259,13 +287,12 @@ class Dashboard {
259
287
 
260
288
  updateConnectivity(hub, mcp) {
261
289
  if (!this.statsBox) return;
262
- // Find and update connectivity lines
263
290
  const content = this.statsBox.getContent();
264
291
  let updated = content
265
292
  .replace(/Hub.*\n.*Status: .*/, `Hub\n Status: ${hub ? '{green-fg}Connected{/green-fg}' : '{red-fg}Disconnected{/red-fg}'}`)
266
293
  .replace(/MCP.*\n.*Status: .*/, `MCP\n Status: ${mcp ? '{green-fg}Connected{/green-fg}' : mcp === null ? '{gray-fg}N/A{/gray-fg}' : '{red-fg}Disconnected{/red-fg}'}`);
267
294
  this.statsBox.setContent(updated);
268
- if (this.screen) this._scheduleRender();
295
+ this._render();
269
296
  }
270
297
 
271
298
  _formatUptime(seconds) {
@@ -277,6 +304,10 @@ class Dashboard {
277
304
  }
278
305
 
279
306
  destroy() {
307
+ if (this._renderTimer) {
308
+ clearTimeout(this._renderTimer);
309
+ this._renderTimer = null;
310
+ }
280
311
  if (this.screen) {
281
312
  this.screen.destroy();
282
313
  this.screen = null;