@emblemvault/agentwallet 1.1.0 → 1.2.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.
@@ -1,51 +1,82 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Hustle Incognito CLI with Headless Password Authentication
4
+ * EmblemAI - Unified CLI for Agent Hustle
5
5
  *
6
- * This CLI demonstrates using the SDK with password-based authentication,
7
- * ideal for servers, CLI tools, and AI agents without browser access.
6
+ * Agent Mode (for AI agents):
7
+ * emblemai --agent -p "password" -m "What are my balances?"
8
8
  *
9
- * Usage:
10
- * node simple-cli-auth.js --password "your-password-min-16-chars"
11
- * node simple-cli-auth.js # Will prompt for password interactively
9
+ * Interactive Mode (for humans):
10
+ * emblemai -p "password"
11
+ * emblemai # Will prompt for password
12
12
  *
13
- * Environment variables:
14
- * AGENT_PASSWORD - Password for authentication (min 16 chars)
15
- * APP_ID - App ID (default: emblem-agent-wallet)
16
- * AUTH_API_URL - Auth API URL (optional, uses SDK default)
17
- * HUSTLE_API_URL - Hustle API URL (optional, uses SDK default)
18
- * DEBUG - Enable debug logging (set to 'true')
13
+ * Reset conversation:
14
+ * emblemai --reset
15
+ * (or /reset in interactive mode)
19
16
  */
20
17
 
21
18
  async function main() {
22
19
  try {
23
- // Import dependencies
24
20
  const { HustleIncognitoClient } = await import('hustle-incognito');
25
21
  const { EmblemAuthSDK } = await import('@emblemvault/auth-sdk');
26
- const dotenv = await import('dotenv');
27
22
  const readline = await import('readline');
28
-
29
- // Load environment variables
30
- dotenv.config();
23
+ const fs = await import('fs');
24
+ const path = await import('path');
25
+ const os = await import('os');
31
26
 
32
27
  // Parse command line arguments
33
28
  const args = process.argv.slice(2);
34
- const getArg = (flag) => {
35
- const index = args.indexOf(flag);
36
- return index !== -1 && args[index + 1] ? args[index + 1] : null;
29
+ const getArg = (flags) => {
30
+ for (const flag of flags) {
31
+ const index = args.indexOf(flag);
32
+ if (index !== -1 && args[index + 1]) return args[index + 1];
33
+ }
34
+ return null;
37
35
  };
36
+ const hasFlag = (flags) => flags.some(f => args.includes(f));
37
+
38
+ const isAgentMode = hasFlag(['--agent', '-a']);
39
+ const isReset = hasFlag(['--reset']);
40
+ const initialDebug = hasFlag(['--debug']);
41
+ const initialStream = hasFlag(['--stream']);
42
+ const passwordArg = getArg(['--password', '-p']);
43
+ const messageArg = getArg(['--message', '-m']);
44
+
45
+ // Conversation history file
46
+ const historyFile = path.join(os.homedir(), '.emblemai-history.json');
47
+
48
+ // Load conversation history
49
+ function loadHistory() {
50
+ try {
51
+ if (fs.existsSync(historyFile)) {
52
+ const data = fs.readFileSync(historyFile, 'utf8');
53
+ return JSON.parse(data);
54
+ }
55
+ } catch (e) {
56
+ // Ignore errors, start fresh
57
+ }
58
+ return { messages: [], created: new Date().toISOString() };
59
+ }
60
+
61
+ // Save conversation history
62
+ function saveHistory(history) {
63
+ history.lastUpdated = new Date().toISOString();
64
+ fs.writeFileSync(historyFile, JSON.stringify(history, null, 2));
65
+ }
38
66
 
39
- const initialDebugMode = args.includes('--debug');
40
- const initialStreamMode = args.includes('--stream');
41
- const passwordArg = getArg('--password');
67
+ // Reset conversation history
68
+ function resetHistory() {
69
+ if (fs.existsSync(historyFile)) {
70
+ fs.unlinkSync(historyFile);
71
+ }
72
+ console.log('Conversation history cleared.');
73
+ }
42
74
 
43
- // Environment configuration
44
- const ENV_PASSWORD = passwordArg || process.env.AGENT_PASSWORD;
45
- const ENV_APP_ID = process.env.APP_ID || 'emblem-agent-wallet';
46
- const ENV_AUTH_API_URL = process.env.AUTH_API_URL; // Uses SDK default if not set
47
- const ENV_HUSTLE_API_URL = process.env.HUSTLE_API_URL; // Uses SDK default if not set
48
- const ENV_DEBUG = process.env.DEBUG === 'true';
75
+ // Handle --reset flag
76
+ if (isReset) {
77
+ resetHistory();
78
+ process.exit(0);
79
+ }
49
80
 
50
81
  // Create readline interface
51
82
  const rl = readline.createInterface({
@@ -53,22 +84,18 @@ async function main() {
53
84
  output: process.stdout
54
85
  });
55
86
 
56
- // Helper to prompt for input
57
87
  const prompt = (question) => new Promise((resolve) => {
58
88
  rl.question(question, resolve);
59
89
  });
60
90
 
61
- // Helper to prompt for password (hidden input)
91
+ // Hidden password input
62
92
  const promptPassword = async (question) => {
63
93
  if (process.stdin.isTTY) {
64
94
  process.stdout.write(question);
65
-
66
95
  return new Promise((resolve) => {
67
96
  let password = '';
68
-
69
97
  const onData = (char) => {
70
98
  char = char.toString();
71
-
72
99
  switch (char) {
73
100
  case '\n':
74
101
  case '\r':
@@ -92,7 +119,6 @@ async function main() {
92
119
  process.stdout.write('*');
93
120
  }
94
121
  };
95
-
96
122
  process.stdin.setRawMode(true);
97
123
  process.stdin.resume();
98
124
  process.stdin.on('data', onData);
@@ -102,105 +128,122 @@ async function main() {
102
128
  }
103
129
  };
104
130
 
105
- console.log('========================================');
106
- console.log(' Hustle Incognito CLI (Auth Edition)');
107
- console.log('========================================');
108
- console.log('');
109
- console.log('This CLI uses headless password authentication.');
110
- console.log('No API key required - authentication is via password.');
111
- console.log('');
131
+ // Get password from args, env, file, or prompt
132
+ let password = passwordArg || process.env.EMBLEM_PASSWORD || process.env.AGENT_PASSWORD;
112
133
 
113
- // Get password
114
- let password = ENV_PASSWORD;
134
+ // Try credential file
135
+ if (!password) {
136
+ const credFile = path.join(os.homedir(), '.emblem-vault');
137
+ if (fs.existsSync(credFile)) {
138
+ password = fs.readFileSync(credFile, 'utf8').trim();
139
+ }
140
+ }
115
141
 
142
+ // Prompt if still no password
116
143
  if (!password) {
117
- console.log('No password provided via --password flag or AGENT_PASSWORD env var.');
144
+ if (isAgentMode) {
145
+ console.error('Error: Password required in agent mode. Use -p or set EMBLEM_PASSWORD');
146
+ process.exit(1);
147
+ }
118
148
  console.log('');
119
- password = await promptPassword('Enter your password (min 16 chars): ');
149
+ password = await promptPassword('Enter your EmblemVault password (min 16 chars): ');
120
150
  }
121
151
 
122
- // Validate password
123
152
  if (!password || password.length < 16) {
124
- console.error('\nError: Password must be at least 16 characters.');
125
- console.error('Use a long, random string like an API key for security.');
153
+ console.error('Error: Password must be at least 16 characters.');
126
154
  process.exit(1);
127
155
  }
128
156
 
129
- console.log('');
130
- console.log('Authenticating...');
157
+ // Authenticate
158
+ if (!isAgentMode) {
159
+ console.log('');
160
+ console.log('Authenticating with Agent Hustle...');
161
+ }
162
+
163
+ const authSdk = new EmblemAuthSDK({
164
+ appId: 'emblem-agent-wallet',
165
+ persistSession: false,
166
+ });
131
167
 
132
- // Create auth SDK instance directly (so we can access all its methods)
133
- let authSdk;
134
- let client;
168
+ const session = await authSdk.authenticatePassword({ password });
169
+ if (!session) {
170
+ throw new Error('Authentication failed');
171
+ }
135
172
 
136
- try {
137
- const sdkConfig = {
138
- appId: ENV_APP_ID,
139
- persistSession: false, // No localStorage in Node.js
140
- };
173
+ // Settings (runtime state)
174
+ const settings = {
175
+ debug: initialDebug,
176
+ stream: !initialStream ? true : initialStream, // Default ON
177
+ retainHistory: true,
178
+ selectedTools: [],
179
+ model: null,
180
+ };
141
181
 
142
- if (ENV_AUTH_API_URL) {
143
- sdkConfig.apiUrl = ENV_AUTH_API_URL;
144
- }
182
+ const client = new HustleIncognitoClient({
183
+ sdk: authSdk,
184
+ debug: settings.debug,
185
+ });
145
186
 
146
- authSdk = new EmblemAuthSDK(sdkConfig);
187
+ // Intent context for auto-tools mode
188
+ let lastIntentContext = null;
147
189
 
148
- // Authenticate with password
149
- const session = await authSdk.authenticatePassword({ password });
190
+ // Load existing history (resume by default)
191
+ let history = loadHistory();
150
192
 
151
- if (!session) {
152
- throw new Error('Authentication failed - no session returned');
193
+ // ==================== AGENT MODE ====================
194
+ if (isAgentMode) {
195
+ if (!messageArg) {
196
+ console.error('Error: Message required in agent mode. Use -m "your message"');
197
+ process.exit(1);
153
198
  }
154
199
 
155
- // Create Hustle client with the authenticated SDK
156
- const clientConfig = {
157
- sdk: authSdk,
158
- debug: initialDebugMode || ENV_DEBUG,
159
- };
200
+ console.log('Agent Hustle is thinking.');
160
201
 
161
- if (ENV_HUSTLE_API_URL) {
162
- clientConfig.hustleApiUrl = ENV_HUSTLE_API_URL;
163
- }
202
+ // Add user message to history
203
+ history.messages.push({ role: 'user', content: messageArg });
164
204
 
165
- client = new HustleIncognitoClient(clientConfig);
205
+ // Progress indicator - output a dot every 5 seconds to show it's not hung
206
+ const progressInterval = setInterval(() => {
207
+ process.stdout.write('.');
208
+ }, 5000);
166
209
 
167
- } catch (error) {
168
- console.error('\nAuthentication failed:', error.message);
169
- if (error.message.includes('@emblemvault/auth-sdk')) {
170
- console.error('\nTo install the auth SDK:');
171
- console.error(' npm install @emblemvault/auth-sdk');
172
- }
173
- process.exit(1);
174
- }
210
+ try {
211
+ const response = await client.chat(history.messages);
175
212
 
176
- console.log('Authentication successful!');
177
- console.log('');
213
+ // Stop progress indicator
214
+ clearInterval(progressInterval);
178
215
 
179
- // Settings
180
- let settings = {
181
- debug: initialDebugMode || ENV_DEBUG,
182
- stream: initialStreamMode,
183
- selectedTools: [],
184
- retainHistory: true,
185
- model: null,
186
- };
216
+ // Add assistant response to history
217
+ history.messages.push({ role: 'assistant', content: response.content });
218
+ saveHistory(history);
219
+
220
+ // Output response (for agent to capture)
221
+ console.log(response.content);
222
+ } catch (error) {
223
+ clearInterval(progressInterval);
224
+ console.error('Error:', error.message);
225
+ process.exit(1);
226
+ }
187
227
 
188
- // Store conversation history
189
- const messages = [];
228
+ rl.close();
229
+ process.exit(0);
230
+ }
190
231
 
191
- // Store intent context for auto-tools mode
192
- let lastIntentContext = null;
232
+ // ==================== INTERACTIVE MODE ====================
233
+ console.log('');
234
+ console.log('========================================');
235
+ console.log(' Agent Hustle CLI');
236
+ console.log('========================================');
237
+ console.log('');
193
238
 
194
- // Spinner for loading animation
239
+ // Spinner
195
240
  const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
196
241
  let spinnerInterval = null;
197
242
  let spinnerIndex = 0;
198
- const hideCursor = '\x1B[?25l';
199
- const showCursor = '\x1B[?25h';
200
243
 
201
244
  function startSpinner() {
202
245
  spinnerIndex = 0;
203
- process.stdout.write(hideCursor + spinnerFrames[0]);
246
+ process.stdout.write('\x1B[?25l' + spinnerFrames[0]);
204
247
  spinnerInterval = setInterval(() => {
205
248
  process.stdout.write('\b' + spinnerFrames[spinnerIndex]);
206
249
  spinnerIndex = (spinnerIndex + 1) % spinnerFrames.length;
@@ -211,133 +254,44 @@ async function main() {
211
254
  if (spinnerInterval) {
212
255
  clearInterval(spinnerInterval);
213
256
  spinnerInterval = null;
214
- process.stdout.write('\b \b' + showCursor);
215
- }
216
- }
217
-
218
- // Stream response
219
- async function streamResponse(msgs) {
220
- let fullText = '';
221
- let toolCalls = [];
222
- let firstChunkReceived = false;
223
-
224
- process.stdout.write('\nAgent: ');
225
- startSpinner();
226
-
227
- try {
228
- const streamOptions = {
229
- messages: msgs,
230
- processChunks: true
231
- };
232
-
233
- if (settings.model) {
234
- streamOptions.model = settings.model;
235
- }
236
-
237
- if (settings.selectedTools.length > 0) {
238
- streamOptions.selectedToolCategories = settings.selectedTools;
239
- } else if (lastIntentContext) {
240
- streamOptions.intentContext = lastIntentContext;
241
- }
242
-
243
- const stream = client.chatStream(streamOptions);
244
-
245
- for await (const chunk of stream) {
246
- if ('type' in chunk) {
247
- switch (chunk.type) {
248
- case 'text':
249
- if (!firstChunkReceived) {
250
- stopSpinner();
251
- firstChunkReceived = true;
252
- }
253
- process.stdout.write(chunk.value);
254
- fullText += chunk.value;
255
- break;
256
-
257
- case 'intent_context':
258
- if (chunk.value?.intentContext) {
259
- lastIntentContext = chunk.value.intentContext;
260
- if (settings.debug) {
261
- console.log('[DEBUG] Captured intent context:',
262
- `activeIntent="${lastIntentContext.activeIntent || 'general'}", ` +
263
- `categories=[${lastIntentContext.categories?.join(', ') || 'none'}]`);
264
- }
265
- }
266
- break;
267
-
268
- case 'tool_call':
269
- if (!firstChunkReceived) {
270
- stopSpinner();
271
- firstChunkReceived = true;
272
- }
273
- toolCalls.push(chunk.value);
274
- break;
275
-
276
- case 'finish':
277
- stopSpinner();
278
- process.stdout.write('\n');
279
- break;
280
- }
281
- }
282
- }
283
- } catch (error) {
284
- stopSpinner();
285
- console.error(`\nError during streaming: ${error.message}`);
286
- }
287
-
288
- if (toolCalls.length > 0) {
289
- console.log('\nTools used:');
290
- toolCalls.forEach((tool, i) => {
291
- console.log(`${i+1}. ${tool.toolName || 'Unknown tool'} (ID: ${tool.toolCallId || 'unknown'})`);
292
- if (tool.args) {
293
- console.log(` Args: ${JSON.stringify(tool.args)}`);
294
- }
295
- });
257
+ process.stdout.write('\b \b\x1B[?25h');
296
258
  }
297
-
298
- return fullText;
299
259
  }
300
260
 
301
- // Display help
261
+ // ==================== HELP ====================
302
262
  function showHelp() {
303
- console.log('\nAvailable commands:');
304
- console.log(' /help - Show this help message');
305
- console.log(' /settings - Show current settings');
306
- console.log(' /auth - Open authentication menu (API key, vault info, etc.)');
263
+ console.log('\nCommands:');
264
+ console.log(' /help - Show this help');
265
+ console.log(' /settings - Show current settings');
266
+ console.log(' /auth - Open auth menu (API key, addresses, etc.)');
307
267
  console.log(' /stream on|off - Toggle streaming mode');
308
268
  console.log(' /debug on|off - Toggle debug mode');
309
- console.log(' /history on|off - Toggle message history retention');
310
- console.log(' /clear - Clear conversation history');
311
- console.log(' /models - List available models');
312
- console.log(' /model <id> - Set the model to use');
313
- console.log(' /model clear - Clear model selection');
314
- console.log(' /tools - Manage tool categories');
315
- console.log(' /tools add <id> - Add a tool category');
316
- console.log(' /tools remove <id> - Remove a tool category');
317
- console.log(' /tools clear - Enable auto-tools mode');
318
- console.log(' /exit or /quit - Exit the application');
269
+ console.log(' /history on|off - Toggle history retention');
270
+ console.log(' /reset - Clear conversation history');
271
+ console.log(' /models - List available models');
272
+ console.log(' /model <id> - Set model (or "clear" to reset)');
273
+ console.log(' /tools - List tool categories');
274
+ console.log(' /tools add|remove <id> - Manage tools');
275
+ console.log(' /tools clear - Enable auto-tools mode');
276
+ console.log(' /exit - Exit the CLI');
319
277
  }
320
278
 
321
- // Show settings
279
+ // ==================== SETTINGS ====================
322
280
  function showSettings() {
323
- const session = authSdk.getSession();
281
+ const sess = authSdk.getSession();
324
282
  console.log('\nCurrent settings:');
325
- console.log(` App ID: ${ENV_APP_ID}`);
326
- console.log(` Vault ID: ${session?.user?.vaultId || 'N/A'}`);
283
+ console.log(` App ID: emblem-agent-wallet`);
284
+ console.log(` Vault ID: ${sess?.user?.vaultId || 'N/A'}`);
327
285
  console.log(` Auth Mode: Password (headless)`);
328
286
  console.log(` Model: ${settings.model || 'API default'}`);
329
287
  console.log(` Streaming: ${settings.stream ? 'ON' : 'OFF'}`);
330
288
  console.log(` Debug: ${settings.debug ? 'ON' : 'OFF'}`);
331
289
  console.log(` History: ${settings.retainHistory ? 'ON' : 'OFF'}`);
332
- console.log(` Messages: ${messages.length}`);
333
- console.log(` Tools: ${
334
- settings.selectedTools.length > 0
335
- ? settings.selectedTools.join(', ')
336
- : 'Auto-tools mode'
337
- }`);
290
+ console.log(` Messages: ${history.messages.length}`);
291
+ console.log(` Tools: ${settings.selectedTools.length > 0 ? settings.selectedTools.join(', ') : 'Auto-tools mode'}`);
338
292
  }
339
293
 
340
- // Auth menu
294
+ // ==================== AUTH MENU ====================
341
295
  async function showAuthMenu() {
342
296
  console.log('\n========================================');
343
297
  console.log(' Authentication Menu');
@@ -357,50 +311,25 @@ async function main() {
357
311
  const choice = await prompt('Select option (1-9): ');
358
312
 
359
313
  switch (choice.trim()) {
360
- case '1':
361
- await getApiKey();
362
- break;
363
- case '2':
364
- await getVaultInfo();
365
- break;
366
- case '3':
367
- showSessionInfo();
368
- break;
369
- case '4':
370
- await refreshSession();
371
- break;
372
- case '5':
373
- await getEvmAddress();
374
- break;
375
- case '6':
376
- await getSolanaAddress();
377
- break;
378
- case '7':
379
- await getBtcAddresses();
380
- break;
381
- case '8':
382
- await doLogout();
383
- break;
384
- case '9':
385
- return;
386
- default:
387
- console.log('Invalid option');
314
+ case '1': await getApiKey(); break;
315
+ case '2': await getVaultInfo(); break;
316
+ case '3': showSessionInfo(); break;
317
+ case '4': await refreshSession(); break;
318
+ case '5': await getEvmAddress(); break;
319
+ case '6': await getSolanaAddress(); break;
320
+ case '7': await getBtcAddresses(); break;
321
+ case '8': await doLogout(); break;
322
+ case '9': return;
323
+ default: console.log('Invalid option');
388
324
  }
389
325
 
390
- // Show menu again after action
391
326
  await showAuthMenu();
392
327
  }
393
328
 
394
329
  async function getApiKey() {
395
330
  console.log('\nFetching API key...');
396
- if (settings.debug) {
397
- console.log('[DEBUG] Calling authSdk.getVaultApiKey()...');
398
- }
399
331
  try {
400
332
  const apiKey = await authSdk.getVaultApiKey();
401
- if (settings.debug) {
402
- console.log('[DEBUG] Raw response:', JSON.stringify(apiKey, null, 2));
403
- }
404
333
  console.log('\n========================================');
405
334
  console.log(' YOUR API KEY');
406
335
  console.log('========================================');
@@ -410,27 +339,15 @@ async function main() {
410
339
  console.log('========================================');
411
340
  console.log('');
412
341
  console.log('IMPORTANT: Store this key securely!');
413
- console.log('You can use this key with the regular CLI (simple-cli.js)');
414
- console.log('Set it as HUSTLE_API_KEY in your environment.');
415
342
  } catch (error) {
416
343
  console.error('Error fetching API key:', error.message);
417
- if (settings.debug && error.stack) {
418
- console.error('[DEBUG] Stack:', error.stack);
419
- }
420
344
  }
421
345
  }
422
346
 
423
347
  async function getVaultInfo() {
424
348
  console.log('\nFetching vault info...');
425
- if (settings.debug) {
426
- console.log('[DEBUG] Calling authSdk.getVaultInfo()...');
427
- }
428
349
  try {
429
350
  const vaultInfo = await authSdk.getVaultInfo();
430
- if (settings.debug) {
431
- console.log('[DEBUG] Raw response:');
432
- console.log(JSON.stringify(vaultInfo, null, 2));
433
- }
434
351
  console.log('\n========================================');
435
352
  console.log(' VAULT INFO');
436
353
  console.log('========================================');
@@ -445,52 +362,31 @@ async function main() {
445
362
  }
446
363
  if (vaultInfo.btcAddresses) {
447
364
  console.log(' BTC Addresses:');
448
- if (vaultInfo.btcAddresses.p2pkh) {
449
- console.log(` P2PKH: ${vaultInfo.btcAddresses.p2pkh}`);
450
- }
451
- if (vaultInfo.btcAddresses.p2wpkh) {
452
- console.log(` P2WPKH: ${vaultInfo.btcAddresses.p2wpkh}`);
453
- }
454
- if (vaultInfo.btcAddresses.p2tr) {
455
- console.log(` P2TR: ${vaultInfo.btcAddresses.p2tr}`);
456
- }
457
- }
458
- if (vaultInfo.createdAt) {
459
- console.log(` Created At: ${vaultInfo.createdAt}`);
460
- }
461
- if (vaultInfo.created_by) {
462
- console.log(` Created By: ${vaultInfo.created_by}`);
365
+ if (vaultInfo.btcAddresses.p2pkh) console.log(` P2PKH: ${vaultInfo.btcAddresses.p2pkh}`);
366
+ if (vaultInfo.btcAddresses.p2wpkh) console.log(` P2WPKH: ${vaultInfo.btcAddresses.p2wpkh}`);
367
+ if (vaultInfo.btcAddresses.p2tr) console.log(` P2TR: ${vaultInfo.btcAddresses.p2tr}`);
463
368
  }
369
+ if (vaultInfo.createdAt) console.log(` Created At: ${vaultInfo.createdAt}`);
464
370
  console.log('');
465
371
  console.log('========================================');
466
372
  } catch (error) {
467
373
  console.error('Error fetching vault info:', error.message);
468
- if (settings.debug && error.stack) {
469
- console.error('[DEBUG] Stack:', error.stack);
470
- }
471
374
  }
472
375
  }
473
376
 
474
377
  function showSessionInfo() {
475
- if (settings.debug) {
476
- console.log('[DEBUG] Calling authSdk.getSession()...');
477
- }
478
- const session = authSdk.getSession();
479
- if (settings.debug) {
480
- console.log('[DEBUG] Raw response:');
481
- console.log(JSON.stringify(session, null, 2));
482
- }
378
+ const sess = authSdk.getSession();
483
379
  console.log('\n========================================');
484
380
  console.log(' SESSION INFO');
485
381
  console.log('========================================');
486
382
  console.log('');
487
- if (session) {
488
- console.log(` Identifier: ${session.user?.identifier || 'N/A'}`);
489
- console.log(` Vault ID: ${session.user?.vaultId || 'N/A'}`);
490
- console.log(` App ID: ${session.appId || 'N/A'}`);
491
- console.log(` Auth Type: ${session.authType || 'N/A'}`);
492
- console.log(` Expires At: ${session.expiresAt ? new Date(session.expiresAt).toISOString() : 'N/A'}`);
493
- console.log(` Auth Token: ${session.authToken ? session.authToken.substring(0, 20) + '...' : 'N/A'}`);
383
+ if (sess) {
384
+ console.log(` Identifier: ${sess.user?.identifier || 'N/A'}`);
385
+ console.log(` Vault ID: ${sess.user?.vaultId || 'N/A'}`);
386
+ console.log(` App ID: ${sess.appId || 'N/A'}`);
387
+ console.log(` Auth Type: ${sess.authType || 'N/A'}`);
388
+ console.log(` Expires At: ${sess.expiresAt ? new Date(sess.expiresAt).toISOString() : 'N/A'}`);
389
+ console.log(` Auth Token: ${sess.authToken ? sess.authToken.substring(0, 20) + '...' : 'N/A'}`);
494
390
  } else {
495
391
  console.log(' No active session');
496
392
  }
@@ -500,15 +396,8 @@ async function main() {
500
396
 
501
397
  async function refreshSession() {
502
398
  console.log('\nRefreshing session...');
503
- if (settings.debug) {
504
- console.log('[DEBUG] Calling authSdk.refreshSession()...');
505
- }
506
399
  try {
507
400
  const newSession = await authSdk.refreshSession();
508
- if (settings.debug) {
509
- console.log('[DEBUG] Raw response:');
510
- console.log(JSON.stringify(newSession, null, 2));
511
- }
512
401
  if (newSession) {
513
402
  console.log('Session refreshed successfully!');
514
403
  console.log(`New expiry: ${new Date(newSession.expiresAt).toISOString()}`);
@@ -517,23 +406,13 @@ async function main() {
517
406
  }
518
407
  } catch (error) {
519
408
  console.error('Error refreshing session:', error.message);
520
- if (settings.debug && error.stack) {
521
- console.error('[DEBUG] Stack:', error.stack);
522
- }
523
409
  }
524
410
  }
525
411
 
526
412
  async function getEvmAddress() {
527
413
  console.log('\nFetching EVM address...');
528
- if (settings.debug) {
529
- console.log('[DEBUG] Calling authSdk.getVaultInfo() for EVM address...');
530
- }
531
414
  try {
532
415
  const vaultInfo = await authSdk.getVaultInfo();
533
- if (settings.debug) {
534
- console.log('[DEBUG] Raw response:');
535
- console.log(JSON.stringify(vaultInfo, null, 2));
536
- }
537
416
  if (vaultInfo.evmAddress) {
538
417
  console.log('\n========================================');
539
418
  console.log(' EVM ADDRESS');
@@ -547,23 +426,13 @@ async function main() {
547
426
  }
548
427
  } catch (error) {
549
428
  console.error('Error fetching EVM address:', error.message);
550
- if (settings.debug && error.stack) {
551
- console.error('[DEBUG] Stack:', error.stack);
552
- }
553
429
  }
554
430
  }
555
431
 
556
432
  async function getSolanaAddress() {
557
433
  console.log('\nFetching Solana address...');
558
- if (settings.debug) {
559
- console.log('[DEBUG] Calling authSdk.getVaultInfo() for Solana address...');
560
- }
561
434
  try {
562
435
  const vaultInfo = await authSdk.getVaultInfo();
563
- if (settings.debug) {
564
- console.log('[DEBUG] Raw response:');
565
- console.log(JSON.stringify(vaultInfo, null, 2));
566
- }
567
436
  const solanaAddr = vaultInfo.solanaAddress || vaultInfo.address;
568
437
  if (solanaAddr) {
569
438
  console.log('\n========================================');
@@ -578,42 +447,13 @@ async function main() {
578
447
  }
579
448
  } catch (error) {
580
449
  console.error('Error fetching Solana address:', error.message);
581
- if (settings.debug && error.stack) {
582
- console.error('[DEBUG] Stack:', error.stack);
583
- }
584
- }
585
- }
586
-
587
- async function doLogout() {
588
- console.log('\nLogging out...');
589
- if (settings.debug) {
590
- console.log('[DEBUG] Calling authSdk.logout()...');
591
- }
592
- try {
593
- authSdk.logout();
594
- console.log('Logged out successfully.');
595
- console.log('Session cleared. Exiting CLI...');
596
- rl.close();
597
- process.exit(0);
598
- } catch (error) {
599
- console.error('Error during logout:', error.message);
600
- if (settings.debug && error.stack) {
601
- console.error('[DEBUG] Stack:', error.stack);
602
- }
603
450
  }
604
451
  }
605
452
 
606
453
  async function getBtcAddresses() {
607
454
  console.log('\nFetching BTC addresses...');
608
- if (settings.debug) {
609
- console.log('[DEBUG] Calling authSdk.getVaultInfo() for BTC addresses...');
610
- }
611
455
  try {
612
456
  const vaultInfo = await authSdk.getVaultInfo();
613
- if (settings.debug) {
614
- console.log('[DEBUG] Raw response:');
615
- console.log(JSON.stringify(vaultInfo, null, 2));
616
- }
617
457
  if (vaultInfo.btcAddresses || vaultInfo.btcPubkey) {
618
458
  console.log('\n========================================');
619
459
  console.log(' BTC ADDRESSES');
@@ -624,15 +464,9 @@ async function main() {
624
464
  console.log('');
625
465
  }
626
466
  if (vaultInfo.btcAddresses) {
627
- if (vaultInfo.btcAddresses.p2pkh) {
628
- console.log(` P2PKH (Legacy): ${vaultInfo.btcAddresses.p2pkh}`);
629
- }
630
- if (vaultInfo.btcAddresses.p2wpkh) {
631
- console.log(` P2WPKH (SegWit): ${vaultInfo.btcAddresses.p2wpkh}`);
632
- }
633
- if (vaultInfo.btcAddresses.p2tr) {
634
- console.log(` P2TR (Taproot): ${vaultInfo.btcAddresses.p2tr}`);
635
- }
467
+ if (vaultInfo.btcAddresses.p2pkh) console.log(` P2PKH (Legacy): ${vaultInfo.btcAddresses.p2pkh}`);
468
+ if (vaultInfo.btcAddresses.p2wpkh) console.log(` P2WPKH (SegWit): ${vaultInfo.btcAddresses.p2wpkh}`);
469
+ if (vaultInfo.btcAddresses.p2tr) console.log(` P2TR (Taproot): ${vaultInfo.btcAddresses.p2tr}`);
636
470
  }
637
471
  console.log('');
638
472
  console.log('========================================');
@@ -641,13 +475,145 @@ async function main() {
641
475
  }
642
476
  } catch (error) {
643
477
  console.error('Error fetching BTC addresses:', error.message);
644
- if (settings.debug && error.stack) {
645
- console.error('[DEBUG] Stack:', error.stack);
478
+ }
479
+ }
480
+
481
+ async function doLogout() {
482
+ console.log('\nLogging out...');
483
+ try {
484
+ authSdk.logout();
485
+ console.log('Logged out successfully.');
486
+ console.log('Session cleared. Exiting CLI...');
487
+ rl.close();
488
+ process.exit(0);
489
+ } catch (error) {
490
+ console.error('Error during logout:', error.message);
491
+ }
492
+ }
493
+
494
+ // ==================== STREAM RESPONSE ====================
495
+ async function streamResponse(msgs) {
496
+ let fullText = '';
497
+ let toolCalls = [];
498
+ let firstChunk = false;
499
+
500
+ process.stdout.write('\nAgent Hustle: ');
501
+ startSpinner();
502
+
503
+ try {
504
+ const streamOptions = {
505
+ messages: msgs,
506
+ processChunks: true
507
+ };
508
+
509
+ if (settings.model) {
510
+ streamOptions.model = settings.model;
511
+ }
512
+
513
+ if (settings.selectedTools.length > 0) {
514
+ streamOptions.selectedToolCategories = settings.selectedTools;
515
+ } else if (lastIntentContext) {
516
+ streamOptions.intentContext = lastIntentContext;
646
517
  }
518
+
519
+ const stream = client.chatStream(streamOptions);
520
+
521
+ for await (const chunk of stream) {
522
+ if ('type' in chunk) {
523
+ switch (chunk.type) {
524
+ case 'text':
525
+ if (!firstChunk) {
526
+ stopSpinner();
527
+ firstChunk = true;
528
+ }
529
+ process.stdout.write(chunk.value);
530
+ fullText += chunk.value;
531
+ break;
532
+
533
+ case 'intent_context':
534
+ if (chunk.value?.intentContext) {
535
+ lastIntentContext = chunk.value.intentContext;
536
+ if (settings.debug) {
537
+ console.log('[DEBUG] Captured intent context:',
538
+ `activeIntent="${lastIntentContext.activeIntent || 'general'}", ` +
539
+ `categories=[${lastIntentContext.categories?.join(', ') || 'none'}]`);
540
+ }
541
+ }
542
+ break;
543
+
544
+ case 'tool_call':
545
+ if (!firstChunk) {
546
+ stopSpinner();
547
+ firstChunk = true;
548
+ }
549
+ toolCalls.push(chunk.value);
550
+ break;
551
+
552
+ case 'finish':
553
+ stopSpinner();
554
+ process.stdout.write('\n');
555
+ break;
556
+ }
557
+ }
558
+ }
559
+ } catch (error) {
560
+ stopSpinner();
561
+ console.error(`\nError: ${error.message}`);
562
+ }
563
+
564
+ if (toolCalls.length > 0) {
565
+ console.log('\nTools used:');
566
+ toolCalls.forEach((tool, i) => {
567
+ console.log(`${i+1}. ${tool.toolName || 'Unknown tool'} (ID: ${tool.toolCallId || 'unknown'})`);
568
+ if (settings.debug && tool.args) {
569
+ console.log(` Args: ${JSON.stringify(tool.args)}`);
570
+ }
571
+ });
572
+ }
573
+
574
+ return fullText;
575
+ }
576
+
577
+ // ==================== NON-STREAMING RESPONSE ====================
578
+ async function nonStreamResponse(msgs) {
579
+ console.log('\nAgent Hustle is thinking...');
580
+
581
+ try {
582
+ const chatOptions = {};
583
+
584
+ if (settings.model) {
585
+ chatOptions.model = settings.model;
586
+ }
587
+
588
+ if (settings.selectedTools.length > 0) {
589
+ chatOptions.selectedToolCategories = settings.selectedTools;
590
+ } else if (lastIntentContext) {
591
+ chatOptions.intentContext = lastIntentContext;
592
+ }
593
+
594
+ const response = await client.chat(msgs, chatOptions);
595
+
596
+ if (response.intentContext?.intentContext) {
597
+ lastIntentContext = response.intentContext.intentContext;
598
+ }
599
+
600
+ console.log(`\nAgent Hustle: ${response.content}`);
601
+
602
+ if (response.toolCalls && response.toolCalls.length > 0) {
603
+ console.log('\nTools used:');
604
+ response.toolCalls.forEach((tool, i) => {
605
+ console.log(`${i+1}. ${tool.toolName || 'Unknown'}`);
606
+ });
607
+ }
608
+
609
+ return response.content;
610
+ } catch (error) {
611
+ console.error(`\nError: ${error.message}`);
612
+ return '';
647
613
  }
648
614
  }
649
615
 
650
- // Process commands
616
+ // ==================== PROCESS COMMANDS ====================
651
617
  async function processCommand(command) {
652
618
  if (command === '/help') {
653
619
  showHelp();
@@ -664,10 +630,10 @@ async function main() {
664
630
  return true;
665
631
  }
666
632
 
667
- if (command === '/clear') {
668
- messages.length = 0;
633
+ if (command === '/reset' || command === '/clear') {
634
+ resetHistory();
635
+ history = { messages: [], created: new Date().toISOString() };
669
636
  lastIntentContext = null;
670
- console.log('Conversation history cleared.');
671
637
  return true;
672
638
  }
673
639
 
@@ -675,7 +641,6 @@ async function main() {
675
641
  console.log('Goodbye!');
676
642
  rl.close();
677
643
  process.exit(0);
678
- return true;
679
644
  }
680
645
 
681
646
  if (command.startsWith('/stream')) {
@@ -721,7 +686,15 @@ async function main() {
721
686
  console.log('History retention disabled');
722
687
  }
723
688
  } else {
724
- console.log(`History is currently ${settings.retainHistory ? 'ON' : 'OFF'}`);
689
+ console.log(`\nHistory is ${settings.retainHistory ? 'ON' : 'OFF'}`);
690
+ console.log(`Conversation has ${history.messages.length} messages.`);
691
+ if (history.messages.length > 0) {
692
+ console.log('Recent:');
693
+ history.messages.slice(-4).forEach((m) => {
694
+ const preview = m.content.substring(0, 60) + (m.content.length > 60 ? '...' : '');
695
+ console.log(` ${m.role}: ${preview}`);
696
+ });
697
+ }
725
698
  }
726
699
  return true;
727
700
  }
@@ -771,9 +744,9 @@ async function main() {
771
744
  console.log('\n=== Tool Categories ===\n');
772
745
  tools.forEach((tool) => {
773
746
  const isSelected = settings.selectedTools.includes(tool.id);
774
- const status = isSelected ? '' : '';
747
+ const status = isSelected ? '[x]' : '[ ]';
775
748
  console.log(`${status} ${tool.title} (${tool.id})`);
776
- console.log(` ${tool.description}`);
749
+ console.log(` ${tool.description}`);
777
750
  });
778
751
  console.log('\nCurrently selected:',
779
752
  settings.selectedTools.length > 0
@@ -799,6 +772,8 @@ async function main() {
799
772
  settings.selectedTools.push(toolId);
800
773
  lastIntentContext = null;
801
774
  console.log(`Added: ${toolId}`);
775
+ } else {
776
+ console.log(`Already added: ${toolId}`);
802
777
  }
803
778
  return true;
804
779
  }
@@ -808,6 +783,8 @@ async function main() {
808
783
  if (index > -1) {
809
784
  settings.selectedTools.splice(index, 1);
810
785
  console.log(`Removed: ${toolId}`);
786
+ } else {
787
+ console.log(`Not found: ${toolId}`);
811
788
  }
812
789
  return true;
813
790
  }
@@ -818,9 +795,17 @@ async function main() {
818
795
  return false;
819
796
  }
820
797
 
821
- // Main chat loop
798
+ // ==================== CHAT LOOP ====================
822
799
  async function chat() {
823
800
  rl.question('\nYou: ', async (input) => {
801
+ input = input.trim();
802
+
803
+ if (!input) {
804
+ chat();
805
+ return;
806
+ }
807
+
808
+ // Process commands
824
809
  if (input.startsWith('/')) {
825
810
  const isCommand = await processCommand(input);
826
811
  if (isCommand) {
@@ -829,75 +814,43 @@ async function main() {
829
814
  }
830
815
  }
831
816
 
817
+ // Handle exit/quit without slash
832
818
  if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
833
819
  console.log('Goodbye!');
834
820
  rl.close();
835
821
  return;
836
822
  }
837
823
 
838
- const currentMessages = settings.retainHistory ? [...messages] : [];
824
+ // Build message list based on history retention setting
825
+ const currentMessages = settings.retainHistory ? [...history.messages] : [];
839
826
  currentMessages.push({ role: 'user', content: input });
840
827
 
841
- if (!settings.stream) {
842
- console.log('\nAgent is thinking...');
828
+ // Send message
829
+ let response;
830
+ if (settings.stream) {
831
+ response = await streamResponse(currentMessages);
832
+ } else {
833
+ response = await nonStreamResponse(currentMessages);
843
834
  }
844
835
 
845
- try {
846
- let assistantResponse = '';
847
-
848
- if (settings.stream) {
849
- assistantResponse = await streamResponse([...currentMessages]);
850
- } else {
851
- const chatOptions = {};
852
-
853
- if (settings.model) {
854
- chatOptions.model = settings.model;
855
- }
856
-
857
- if (settings.selectedTools.length > 0) {
858
- chatOptions.selectedToolCategories = settings.selectedTools;
859
- } else if (lastIntentContext) {
860
- chatOptions.intentContext = lastIntentContext;
861
- }
862
-
863
- const response = await client.chat(currentMessages, chatOptions);
864
-
865
- if (response.intentContext?.intentContext) {
866
- lastIntentContext = response.intentContext.intentContext;
867
- }
868
-
869
- console.log(`\nAgent: ${response.content}`);
870
-
871
- if (response.toolCalls && response.toolCalls.length > 0) {
872
- console.log('\nTools used:');
873
- response.toolCalls.forEach((tool, i) => {
874
- console.log(`${i+1}. ${tool.toolName || 'Unknown'}`);
875
- });
876
- }
877
-
878
- assistantResponse = response.content;
879
- }
880
-
881
- if (settings.retainHistory && assistantResponse) {
882
- messages.push({ role: 'user', content: input });
883
- messages.push({ role: 'assistant', content: assistantResponse });
884
- }
885
-
886
- chat();
887
- } catch (error) {
888
- console.error('Error:', error.message);
889
- chat();
836
+ // Save to history
837
+ if (settings.retainHistory && response) {
838
+ history.messages.push({ role: 'user', content: input });
839
+ history.messages.push({ role: 'assistant', content: response });
840
+ saveHistory(history);
890
841
  }
842
+
843
+ chat();
891
844
  });
892
845
  }
893
846
 
894
- // Start
847
+ // Show initial settings and start chat
895
848
  console.log('Type "/help" for commands, "/auth" for auth menu, or "/exit" to quit.\n');
896
849
  showSettings();
897
850
  chat();
898
851
 
899
852
  } catch (error) {
900
- console.error('Error initializing CLI:', error);
853
+ console.error('Error:', error.message);
901
854
  process.exit(1);
902
855
  }
903
856
  }