@xyz-credit/agent-cli 1.2.2 → 1.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xyz-credit/agent-cli",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
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"
@@ -43,7 +43,7 @@ async function authCommand() {
43
43
  type: 'input',
44
44
  name: 'platformUrl',
45
45
  message: 'Platform URL:',
46
- default: config.get('platformUrl') || 'https://agent-messaging.preview.emergentagent.com',
46
+ default: config.get('platformUrl') || 'https://thread-debug.preview.emergentagent.com',
47
47
  validate: (v) => v.startsWith('http') ? true : 'Must be a valid URL',
48
48
  }]);
49
49
 
@@ -212,8 +212,9 @@ async function trySSETransport(mcpUrl, spinner) {
212
212
  }
213
213
  }
214
214
 
215
- async function discoverMcpTools(mcpUrl) {
216
- const spinner = ora(`Connecting to MCP server...`).start();
215
+ async function discoverMcpTools(mcpUrl, options = {}) {
216
+ const silent = options.silent || false;
217
+ const spinner = silent ? { start() {}, stop() {}, succeed() {}, fail() {}, warn() {}, set text(v) {} } : ora(`Connecting to MCP server...`).start();
217
218
 
218
219
  // Try streamable HTTP first (faster, no streaming needed)
219
220
  let tools = await tryStreamableHttp(mcpUrl, spinner);
@@ -224,8 +225,10 @@ async function discoverMcpTools(mcpUrl) {
224
225
  if (tools !== null) return tools;
225
226
 
226
227
  spinner.fail(`Failed to connect to MCP server at: ${mcpUrl}`);
227
- console.log(chalk.dim(' Tried both Streamable HTTP and SSE transports.'));
228
- console.log(chalk.dim(' Ensure the MCP server is running and accessible.\n'));
228
+ if (!silent) {
229
+ console.log(chalk.dim(' Tried both Streamable HTTP and SSE transports.'));
230
+ console.log(chalk.dim(' Ensure the MCP server is running and accessible.\n'));
231
+ }
229
232
  return null;
230
233
  }
231
234
 
@@ -99,14 +99,50 @@ async function setupCommand() {
99
99
  if (isAuthenticated()) {
100
100
  const creds = getCredentials();
101
101
  console.log(chalk.yellow(` Existing agent found: ${creds.agentName} (${creds.agentId.slice(0, 12)}...)`));
102
- const { reconfigure } = await inquirer.prompt([{
103
- type: 'confirm', name: 'reconfigure',
104
- message: 'Reconfigure from scratch?', default: false,
102
+ const { action } = await inquirer.prompt([{
103
+ type: 'list', name: 'action',
104
+ message: 'What would you like to do?',
105
+ choices: [
106
+ { name: 'Start agent with existing configuration', value: 'start' },
107
+ { name: 'Reconfigure from scratch (keep agent)', value: 'reconfigure' },
108
+ { name: 'Remove existing agent & create new one', value: 'remove' },
109
+ { name: 'Exit', value: 'exit' },
110
+ ],
105
111
  }]);
106
- if (!reconfigure) {
112
+
113
+ if (action === 'start') {
107
114
  console.log(chalk.green(' Using existing configuration.\n'));
115
+ console.log(chalk.dim(' Launching agent...\n'));
116
+ const { startCommand } = require('./start');
117
+ await startCommand({});
108
118
  return;
109
119
  }
120
+
121
+ if (action === 'remove') {
122
+ const { confirmRemove } = await inquirer.prompt([{
123
+ type: 'confirm', name: 'confirmRemove',
124
+ message: chalk.red('This will delete your local agent credentials. Are you sure?'),
125
+ default: false,
126
+ }]);
127
+ if (!confirmRemove) {
128
+ console.log(chalk.yellow(' Cancelled. Keeping existing agent.\n'));
129
+ return;
130
+ }
131
+ // Clear stored credentials
132
+ config.delete('agentId');
133
+ config.delete('agentName');
134
+ config.delete('apiKey');
135
+ config.delete('agentId');
136
+ console.log(chalk.green(' Agent credentials removed. Starting fresh setup...\n'));
137
+ // Fall through to full setup below
138
+ }
139
+
140
+ if (action === 'exit') {
141
+ console.log(chalk.dim(' Goodbye.\n'));
142
+ return;
143
+ }
144
+
145
+ // 'reconfigure' and 'remove' both fall through to the full setup wizard
110
146
  }
111
147
 
112
148
  // ── Step 1: Platform URL ──
@@ -114,7 +150,7 @@ async function setupCommand() {
114
150
  const { platformUrl } = await inquirer.prompt([{
115
151
  type: 'input', name: 'platformUrl',
116
152
  message: 'Platform URL:',
117
- default: config.get('platformUrl') || 'https://agent-messaging.preview.emergentagent.com',
153
+ default: config.get('platformUrl') || 'https://thread-debug.preview.emergentagent.com',
118
154
  validate: (v) => v.startsWith('http') ? true : 'Must start with http(s)://',
119
155
  }]);
120
156
 
@@ -291,16 +291,17 @@ async function dashboardStart(creds) {
291
291
  dashboard.addLog(`{red-fg}Send failed:{/red-fg} ${e.message}`);
292
292
  }
293
293
  } else if (cmd.type === 'retry-mcp') {
294
- // Retry MCP connection
294
+ // Retry MCP connection using silent mode (no ora spinners)
295
295
  if (localMcpUrl) {
296
296
  dashboard.addLog(`Retrying MCP: ${localMcpUrl}`);
297
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)' : ''}`);
298
+ const { discoverMcpTools } = require('./connect');
299
+ const tools = await discoverMcpTools(localMcpUrl, { silent: true });
300
+ if (tools && tools.length > 0) {
301
+ dashboard.addLog(`{green-fg}MCP connected:{/green-fg} ${tools.length} tools discovered`);
301
302
  dashboard.updateConnectivity(true, true);
302
303
  } else {
303
- dashboard.addLog('{red-fg}MCP retry failed:{/red-fg} Could not discover tools');
304
+ dashboard.addLog('{red-fg}MCP retry failed:{/red-fg} Could not discover tools. Check MCP URL is correct.');
304
305
  dashboard.updateConnectivity(true, false);
305
306
  }
306
307
  } catch (e) {
@@ -5,25 +5,31 @@
5
5
  * - Status bar: Online/Pending/Offline indicators
6
6
  * - Connectivity: Central Hub + Local MCP connection strength
7
7
  * - Activity Logs: Scrolling window with agent thoughts
8
- * - Priority Task Input: User can type commands at any time
8
+ * - Command Input: Raw keypress-based input (bypasses blessed input widget bugs)
9
9
  */
10
10
  const blessed = require('blessed');
11
11
  const { EventEmitter } = require('events');
12
12
 
13
+ const MAX_LOGS = 200;
14
+ const FLUSH_INTERVAL_MS = 80;
15
+
13
16
  class Dashboard {
14
17
  constructor() {
15
18
  this.ee = new EventEmitter();
16
19
  this.screen = null;
17
20
  this.logBox = null;
18
21
  this.statusBox = null;
19
- this.inputBox = null;
22
+ this.inputDisplay = null;
20
23
  this.helpText = null;
21
24
  this.statsBox = null;
22
25
  this.logs = [];
23
26
  this.status = { status: 'starting', cycleCount: 0, uptimeSeconds: 0 };
24
27
  this.unreadCount = 0;
25
- this._renderTimer = null;
26
- this._logQueue = [];
28
+ this._flushTimer = null;
29
+ this._dirty = false;
30
+
31
+ // Raw input state
32
+ this._inputBuffer = '';
27
33
  }
28
34
 
29
35
  start(agentName, platformUrl, mcpUrl) {
@@ -31,7 +37,6 @@ class Dashboard {
31
37
  smartCSR: true,
32
38
  title: `xyz-agent — ${agentName}`,
33
39
  fullUnicode: true,
34
- grabKeys: false,
35
40
  });
36
41
 
37
42
  // ── Status Bar (top) ──
@@ -42,7 +47,7 @@ class Dashboard {
42
47
  border: { type: 'line' },
43
48
  style: { border: { fg: 'cyan' }, bg: 'black' },
44
49
  });
45
- this._updateStatus(agentName);
50
+ this.statusBox.setContent(` {yellow-fg}STARTING{/yellow-fg} | Agent: {cyan-fg}${agentName}{/cyan-fg} | Initializing...`);
46
51
 
47
52
  // ── Stats Panel (right) ──
48
53
  this.statsBox = blessed.box({
@@ -70,71 +75,93 @@ class Dashboard {
70
75
  mouse: true,
71
76
  });
72
77
 
73
- // ── Input Box (bottom) ──
78
+ // ── Input Area (bottom) ──
74
79
  const inputContainer = blessed.box({
75
80
  parent: this.screen,
76
81
  bottom: 0, left: 0, width: '100%', height: 5,
77
82
  tags: true,
78
83
  border: { type: 'line' },
79
- label: ' {green-fg}Command Input{/green-fg} (type a task, press Enter) ',
84
+ label: ' {green-fg}Command Input{/green-fg} (press Enter to submit) ',
80
85
  style: { border: { fg: 'green' }, bg: 'black' },
81
86
  });
82
87
 
83
- this.inputBox = blessed.textarea({
88
+ // Display box for typed text (NOT a textbox/textarea — raw rendering)
89
+ this.inputDisplay = blessed.box({
84
90
  parent: inputContainer,
85
91
  top: 0, left: 1, right: 1, height: 1,
86
- keys: true,
87
- mouse: false,
88
- inputOnFocus: false,
92
+ tags: true,
89
93
  style: { fg: 'white', bg: 'black' },
94
+ content: '',
90
95
  });
91
96
 
92
- // Help text — use white so it's actually visible
97
+ // Help text — bright enough to see
93
98
  this.helpText = blessed.text({
94
99
  parent: inputContainer,
95
100
  top: 1, left: 1,
96
101
  tags: true,
97
- content: '{white-fg}Commands: status | sync | post | inbox | retry | msg <id> <text> | quit{/white-fg}',
98
- style: { fg: 'white', bg: 'black' },
102
+ content: '{cyan-fg}Commands:{/cyan-fg} {white-fg}status{/white-fg} | {white-fg}sync{/white-fg} | {white-fg}post{/white-fg} | {white-fg}inbox{/white-fg} | {white-fg}retry{/white-fg} | {white-fg}msg <id> <text>{/white-fg} | {white-fg}quit{/white-fg}',
103
+ style: { bg: 'black' },
99
104
  });
100
105
 
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);
106
+ // ── Raw keypress handling (avoids blessed input widget bugs) ──
107
+ this.screen.on('keypress', (ch, key) => {
108
+ if (!key) return;
109
+
110
+ // Ctrl-C / Escape → quit
111
+ if (key.full === 'C-c' || key.full === 'escape') {
112
+ this.ee.emit('quit');
113
+ return;
106
114
  }
107
- this.inputBox.clearValue();
108
- this.inputBox.focus();
109
- this._render();
110
- });
111
115
 
112
- // Escape/Ctrl-C to quit
113
- this.inputBox.key(['escape', 'C-c'], () => {
114
- this.ee.emit('quit');
115
- });
116
+ // Enter submit command
117
+ if (key.full === 'return' || key.full === 'enter') {
118
+ const val = this._inputBuffer.trim();
119
+ if (val) this._handleInput(val);
120
+ this._inputBuffer = '';
121
+ this._renderInput();
122
+ return;
123
+ }
116
124
 
117
- // Global Ctrl-C fallback
118
- this.screen.key(['C-c'], () => {
119
- this.ee.emit('quit');
120
- });
125
+ // Backspace
126
+ if (key.full === 'backspace') {
127
+ if (this._inputBuffer.length > 0) {
128
+ this._inputBuffer = this._inputBuffer.slice(0, -1);
129
+ this._renderInput();
130
+ }
131
+ return;
132
+ }
133
+
134
+ // Tab — ignore (prevent focus issues)
135
+ if (key.full === 'tab') return;
121
136
 
122
- // Tab to re-focus input
123
- this.screen.key(['tab'], () => {
124
- this.inputBox.focus();
125
- this._render();
137
+ // Arrow keys, function keys — ignore
138
+ if (key.full === 'up' || key.full === 'down' || key.full === 'left' || key.full === 'right') return;
139
+ if (key.full && key.full.startsWith('f') && key.full.length <= 3) return;
140
+
141
+ // Normal printable character
142
+ if (ch && ch.length === 1 && ch.charCodeAt(0) >= 32) {
143
+ this._inputBuffer += ch;
144
+ this._renderInput();
145
+ }
126
146
  });
127
147
 
128
- // Focus input and do initial render
129
- this.inputBox.focus();
148
+ // Initial render
130
149
  this.screen.render();
131
150
 
132
151
  this.addLog('Dashboard initialized. Type commands below.');
133
- this.addLog(`Agent: ${agentName}`);
152
+ this.addLog(`Agent: {cyan-fg}${agentName}{/cyan-fg}`);
134
153
  this.addLog(`Platform: ${platformUrl}`);
135
154
  if (mcpUrl) this.addLog(`MCP: ${mcpUrl}`);
136
155
  }
137
156
 
157
+ _renderInput() {
158
+ if (this.inputDisplay) {
159
+ // Show input with a cursor block
160
+ this.inputDisplay.setContent(this._inputBuffer + '{inverse} {/inverse}');
161
+ this._scheduleFlush();
162
+ }
163
+ }
164
+
138
165
  _handleInput(cmd) {
139
166
  const lower = cmd.toLowerCase();
140
167
 
@@ -172,6 +199,11 @@ class Dashboard {
172
199
  return;
173
200
  }
174
201
 
202
+ if (lower === 'help') {
203
+ this.addLog('Available commands: {cyan-fg}status{/cyan-fg} | {cyan-fg}sync{/cyan-fg} | {cyan-fg}post{/cyan-fg} | {cyan-fg}inbox{/cyan-fg} | {cyan-fg}retry{/cyan-fg} | {cyan-fg}msg <id> <text>{/cyan-fg} | {cyan-fg}quit{/cyan-fg}');
204
+ return;
205
+ }
206
+
175
207
  // msg <agent_id> <message text>
176
208
  if (lower.startsWith('msg ') || lower.startsWith('message ')) {
177
209
  const parts = cmd.split(/\s+/);
@@ -195,32 +227,28 @@ class Dashboard {
195
227
  const ts = new Date().toLocaleTimeString();
196
228
  const formatted = `{gray-fg}[${ts}]{/gray-fg} ${msg}`;
197
229
  this.logs.push(formatted);
198
- if (this.logs.length > 500) this.logs.shift();
199
- this._logQueue.push(formatted);
230
+ if (this.logs.length > MAX_LOGS) this.logs = this.logs.slice(-MAX_LOGS);
231
+ this._dirty = true;
200
232
  this._scheduleFlush();
201
233
  }
202
234
 
203
235
  _scheduleFlush() {
204
- if (this._renderTimer) return;
205
- this._renderTimer = setTimeout(() => {
206
- this._renderTimer = null;
207
- this._flushLogs();
208
- }, 50);
236
+ if (this._flushTimer) return;
237
+ this._flushTimer = setTimeout(() => {
238
+ this._flushTimer = null;
239
+ this._flush();
240
+ }, FLUSH_INTERVAL_MS);
209
241
  }
210
242
 
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);
243
+ _flush() {
244
+ if (this._dirty && this.logBox) {
245
+ // Use setContent with full buffer — avoids pushLine corruption
246
+ this.logBox.setContent(this.logs.join('\n'));
247
+ this.logBox.setScrollPerc(100);
248
+ this._dirty = false;
216
249
  }
217
- this.logBox.setScrollPerc(100);
218
- this._render();
219
- }
220
-
221
- _render() {
222
250
  if (this.screen) {
223
- try { this.screen.render(); } catch { /* ignore render errors */ }
251
+ try { this.screen.render(); } catch { /* ignore */ }
224
252
  }
225
253
  }
226
254
 
@@ -235,14 +263,14 @@ class Dashboard {
235
263
  const uptime = this._formatUptime(data.uptimeSeconds || 0);
236
264
  const msgBadge = this.unreadCount > 0
237
265
  ? ` | {yellow-fg}Mail: ${this.unreadCount} unread{/yellow-fg}`
238
- : ` | Mail: {gray-fg}0{/gray-fg}`;
266
+ : '';
239
267
  this.statusBox.setContent(
240
268
  ` ${indicator} | Cycles: {cyan-fg}${data.cycleCount}{/cyan-fg} | ` +
241
269
  `Uptime: {cyan-fg}${uptime}{/cyan-fg} | ` +
242
270
  `Last Ping: {gray-fg}${data.lastPing ? new Date(data.lastPing).toLocaleTimeString() : '-'}{/gray-fg}` +
243
271
  msgBadge
244
272
  );
245
- this._render();
273
+ this._scheduleFlush();
246
274
  }
247
275
  }
248
276
 
@@ -257,15 +285,8 @@ class Dashboard {
257
285
  this.addLog(`{yellow-fg}[NEW MSG]{/yellow-fg} From {cyan-fg}${from}{/cyan-fg}: ${preview}${msg.message?.length > 60 ? '...' : ''}`);
258
286
  }
259
287
 
260
- _updateStatus(agentName) {
261
- if (this.statusBox) {
262
- this.statusBox.setContent(` {yellow-fg}STARTING{/yellow-fg} | Agent: {cyan-fg}${agentName}{/cyan-fg} | Initializing...`);
263
- }
264
- }
265
-
266
288
  _updateStats(agentName, platformUrl, mcpUrl) {
267
289
  if (!this.statsBox) return;
268
-
269
290
  const lines = [
270
291
  ` {bold}Agent{/bold}`,
271
292
  ` Name: {cyan-fg}${agentName}{/cyan-fg}`,
@@ -292,7 +313,7 @@ class Dashboard {
292
313
  .replace(/Hub.*\n.*Status: .*/, `Hub\n Status: ${hub ? '{green-fg}Connected{/green-fg}' : '{red-fg}Disconnected{/red-fg}'}`)
293
314
  .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}'}`);
294
315
  this.statsBox.setContent(updated);
295
- this._render();
316
+ this._scheduleFlush();
296
317
  }
297
318
 
298
319
  _formatUptime(seconds) {
@@ -304,9 +325,9 @@ class Dashboard {
304
325
  }
305
326
 
306
327
  destroy() {
307
- if (this._renderTimer) {
308
- clearTimeout(this._renderTimer);
309
- this._renderTimer = null;
328
+ if (this._flushTimer) {
329
+ clearTimeout(this._flushTimer);
330
+ this._flushTimer = null;
310
331
  }
311
332
  if (this.screen) {
312
333
  this.screen.destroy();
@@ -77,7 +77,7 @@ async function discoverAndRegisterServices() {
77
77
  const { discoverMcpTools } = require('../commands/connect');
78
78
  let tools;
79
79
  try {
80
- tools = await discoverMcpTools(mcpUrl);
80
+ tools = await discoverMcpTools(mcpUrl, { silent: true });
81
81
  } catch {
82
82
  return null;
83
83
  }