@emblemvault/agentwallet 1.1.1 → 1.3.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,90 @@
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
+ const hustleUrlArg = getArg(['--hustle-url']);
45
+ const authUrlArg = getArg(['--auth-url']);
46
+ const apiUrlArg = getArg(['--api-url']);
47
+
48
+ // Endpoint overrides (CLI args > env vars > defaults)
49
+ const hustleApiUrl = hustleUrlArg || process.env.HUSTLE_API_URL || undefined;
50
+ const authUrl = authUrlArg || process.env.EMBLEM_AUTH_URL || undefined;
51
+ const apiUrl = apiUrlArg || process.env.EMBLEM_API_URL || undefined;
52
+
53
+ // Conversation history file
54
+ const historyFile = path.join(os.homedir(), '.emblemai-history.json');
55
+
56
+ // Load conversation history
57
+ function loadHistory() {
58
+ try {
59
+ if (fs.existsSync(historyFile)) {
60
+ const data = fs.readFileSync(historyFile, 'utf8');
61
+ return JSON.parse(data);
62
+ }
63
+ } catch (e) {
64
+ // Ignore errors, start fresh
65
+ }
66
+ return { messages: [], created: new Date().toISOString() };
67
+ }
38
68
 
39
- const initialDebugMode = args.includes('--debug');
40
- const initialStreamMode = args.includes('--stream');
41
- const passwordArg = getArg('--password');
69
+ // Save conversation history
70
+ function saveHistory(history) {
71
+ history.lastUpdated = new Date().toISOString();
72
+ fs.writeFileSync(historyFile, JSON.stringify(history, null, 2));
73
+ }
74
+
75
+ // Reset conversation history
76
+ function resetHistory() {
77
+ if (fs.existsSync(historyFile)) {
78
+ fs.unlinkSync(historyFile);
79
+ }
80
+ console.log('Conversation history cleared.');
81
+ }
42
82
 
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';
83
+ // Handle --reset flag
84
+ if (isReset) {
85
+ resetHistory();
86
+ process.exit(0);
87
+ }
49
88
 
50
89
  // Create readline interface
51
90
  const rl = readline.createInterface({
@@ -53,22 +92,18 @@ async function main() {
53
92
  output: process.stdout
54
93
  });
55
94
 
56
- // Helper to prompt for input
57
95
  const prompt = (question) => new Promise((resolve) => {
58
96
  rl.question(question, resolve);
59
97
  });
60
98
 
61
- // Helper to prompt for password (hidden input)
99
+ // Hidden password input
62
100
  const promptPassword = async (question) => {
63
101
  if (process.stdin.isTTY) {
64
102
  process.stdout.write(question);
65
-
66
103
  return new Promise((resolve) => {
67
104
  let password = '';
68
-
69
105
  const onData = (char) => {
70
106
  char = char.toString();
71
-
72
107
  switch (char) {
73
108
  case '\n':
74
109
  case '\r':
@@ -92,7 +127,6 @@ async function main() {
92
127
  process.stdout.write('*');
93
128
  }
94
129
  };
95
-
96
130
  process.stdin.setRawMode(true);
97
131
  process.stdin.resume();
98
132
  process.stdin.on('data', onData);
@@ -102,105 +136,129 @@ async function main() {
102
136
  }
103
137
  };
104
138
 
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('');
139
+ // Get password from args, env, file, or prompt
140
+ let password = passwordArg || process.env.EMBLEM_PASSWORD || process.env.AGENT_PASSWORD;
112
141
 
113
- // Get password
114
- let password = ENV_PASSWORD;
142
+ // Try credential file
143
+ if (!password) {
144
+ const credFile = path.join(os.homedir(), '.emblem-vault');
145
+ if (fs.existsSync(credFile)) {
146
+ password = fs.readFileSync(credFile, 'utf8').trim();
147
+ }
148
+ }
115
149
 
150
+ // Prompt if still no password
116
151
  if (!password) {
117
- console.log('No password provided via --password flag or AGENT_PASSWORD env var.');
152
+ if (isAgentMode) {
153
+ console.error('Error: Password required in agent mode. Use -p or set EMBLEM_PASSWORD');
154
+ process.exit(1);
155
+ }
118
156
  console.log('');
119
- password = await promptPassword('Enter your password (min 16 chars): ');
157
+ password = await promptPassword('Enter your EmblemVault password (min 16 chars): ');
120
158
  }
121
159
 
122
- // Validate password
123
160
  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.');
161
+ console.error('Error: Password must be at least 16 characters.');
126
162
  process.exit(1);
127
163
  }
128
164
 
129
- console.log('');
130
- console.log('Authenticating...');
165
+ // Authenticate
166
+ if (!isAgentMode) {
167
+ console.log('');
168
+ console.log('Authenticating with Agent Hustle...');
169
+ }
131
170
 
132
- // Create auth SDK instance directly (so we can access all its methods)
133
- let authSdk;
134
- let client;
171
+ const authSdkConfig = {
172
+ appId: 'emblem-agent-wallet',
173
+ persistSession: false,
174
+ };
175
+ if (authUrl) authSdkConfig.authUrl = authUrl;
176
+ if (apiUrl) authSdkConfig.apiUrl = apiUrl;
135
177
 
136
- try {
137
- const sdkConfig = {
138
- appId: ENV_APP_ID,
139
- persistSession: false, // No localStorage in Node.js
140
- };
178
+ const authSdk = new EmblemAuthSDK(authSdkConfig);
141
179
 
142
- if (ENV_AUTH_API_URL) {
143
- sdkConfig.apiUrl = ENV_AUTH_API_URL;
144
- }
180
+ const session = await authSdk.authenticatePassword({ password });
181
+ if (!session) {
182
+ throw new Error('Authentication failed');
183
+ }
145
184
 
146
- authSdk = new EmblemAuthSDK(sdkConfig);
185
+ // Settings (runtime state)
186
+ const settings = {
187
+ debug: initialDebug,
188
+ stream: !initialStream ? true : initialStream, // Default ON
189
+ retainHistory: true,
190
+ selectedTools: [],
191
+ model: null,
192
+ };
147
193
 
148
- // Authenticate with password
149
- const session = await authSdk.authenticatePassword({ password });
194
+ const hustleClientConfig = {
195
+ sdk: authSdk,
196
+ debug: settings.debug,
197
+ };
198
+ if (hustleApiUrl) hustleClientConfig.hustleApiUrl = hustleApiUrl;
150
199
 
151
- if (!session) {
152
- throw new Error('Authentication failed - no session returned');
153
- }
200
+ const client = new HustleIncognitoClient(hustleClientConfig);
154
201
 
155
- // Create Hustle client with the authenticated SDK
156
- const clientConfig = {
157
- sdk: authSdk,
158
- debug: initialDebugMode || ENV_DEBUG,
159
- };
202
+ // Intent context for auto-tools mode
203
+ let lastIntentContext = null;
204
+
205
+ // Load existing history (resume by default)
206
+ let history = loadHistory();
160
207
 
161
- if (ENV_HUSTLE_API_URL) {
162
- clientConfig.hustleApiUrl = ENV_HUSTLE_API_URL;
208
+ // ==================== AGENT MODE ====================
209
+ if (isAgentMode) {
210
+ if (!messageArg) {
211
+ console.error('Error: Message required in agent mode. Use -m "your message"');
212
+ process.exit(1);
163
213
  }
164
214
 
165
- client = new HustleIncognitoClient(clientConfig);
215
+ console.log('Agent Hustle is thinking.');
166
216
 
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
- }
217
+ // Add user message to history
218
+ history.messages.push({ role: 'user', content: messageArg });
175
219
 
176
- console.log('Authentication successful!');
177
- console.log('');
220
+ // Progress indicator - output a dot every 5 seconds to show it's not hung
221
+ const progressInterval = setInterval(() => {
222
+ process.stdout.write('.');
223
+ }, 5000);
178
224
 
179
- // Settings
180
- let settings = {
181
- debug: initialDebugMode || ENV_DEBUG,
182
- stream: initialStreamMode,
183
- selectedTools: [],
184
- retainHistory: true,
185
- model: null,
186
- };
225
+ try {
226
+ const response = await client.chat(history.messages);
187
227
 
188
- // Store conversation history
189
- const messages = [];
228
+ // Stop progress indicator
229
+ clearInterval(progressInterval);
190
230
 
191
- // Store intent context for auto-tools mode
192
- let lastIntentContext = null;
231
+ // Add assistant response to history
232
+ history.messages.push({ role: 'assistant', content: response.content });
233
+ saveHistory(history);
234
+
235
+ // Output response (for agent to capture)
236
+ console.log(response.content);
237
+ } catch (error) {
238
+ clearInterval(progressInterval);
239
+ console.error('Error:', error.message);
240
+ process.exit(1);
241
+ }
242
+
243
+ rl.close();
244
+ process.exit(0);
245
+ }
193
246
 
194
- // Spinner for loading animation
247
+ // ==================== INTERACTIVE MODE ====================
248
+ console.log('');
249
+ console.log('========================================');
250
+ console.log(' Agent Hustle CLI');
251
+ console.log('========================================');
252
+ console.log('');
253
+
254
+ // Spinner
195
255
  const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
196
256
  let spinnerInterval = null;
197
257
  let spinnerIndex = 0;
198
- const hideCursor = '\x1B[?25l';
199
- const showCursor = '\x1B[?25h';
200
258
 
201
259
  function startSpinner() {
202
260
  spinnerIndex = 0;
203
- process.stdout.write(hideCursor + spinnerFrames[0]);
261
+ process.stdout.write('\x1B[?25l' + spinnerFrames[0]);
204
262
  spinnerInterval = setInterval(() => {
205
263
  process.stdout.write('\b' + spinnerFrames[spinnerIndex]);
206
264
  spinnerIndex = (spinnerIndex + 1) % spinnerFrames.length;
@@ -211,133 +269,53 @@ async function main() {
211
269
  if (spinnerInterval) {
212
270
  clearInterval(spinnerInterval);
213
271
  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}`);
272
+ process.stdout.write('\b \b\x1B[?25h');
286
273
  }
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
- });
296
- }
297
-
298
- return fullText;
299
274
  }
300
275
 
301
- // Display help
276
+ // ==================== HELP ====================
302
277
  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.)');
278
+ console.log('\nCommands:');
279
+ console.log(' /help - Show this help');
280
+ console.log(' /settings - Show current settings');
281
+ console.log(' /auth - Open auth menu (API key, addresses, etc.)');
307
282
  console.log(' /stream on|off - Toggle streaming mode');
308
283
  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');
284
+ console.log(' /history on|off - Toggle history retention');
285
+ console.log(' /reset - Clear conversation history');
286
+ console.log(' /models - List available models');
287
+ console.log(' /model <id> - Set model (or "clear" to reset)');
288
+ console.log(' /tools - List tool categories');
289
+ console.log(' /tools add|remove <id> - Manage tools');
290
+ console.log(' /tools clear - Enable auto-tools mode');
291
+ console.log(' /payment - Show PAYG billing status');
292
+ console.log(' /payment enable - Enable pay-as-you-go billing');
293
+ console.log(' /payment disable - Disable pay-as-you-go billing');
294
+ console.log(' /payment token <T> - Set payment token (SOL, ETH, HUSTLE, etc.)');
295
+ console.log(' /payment mode <M> - Set payment mode (pay_per_request, debt_accumulation)');
296
+ console.log(' /exit - Exit the CLI');
319
297
  }
320
298
 
321
- // Show settings
299
+ // ==================== SETTINGS ====================
322
300
  function showSettings() {
323
- const session = authSdk.getSession();
301
+ const sess = authSdk.getSession();
324
302
  console.log('\nCurrent settings:');
325
- console.log(` App ID: ${ENV_APP_ID}`);
326
- console.log(` Vault ID: ${session?.user?.vaultId || 'N/A'}`);
303
+ console.log(` App ID: emblem-agent-wallet`);
304
+ console.log(` Vault ID: ${sess?.user?.vaultId || 'N/A'}`);
327
305
  console.log(` Auth Mode: Password (headless)`);
306
+ if (hustleApiUrl) console.log(` Hustle API: ${hustleApiUrl}`);
307
+ if (authUrl) console.log(` Auth URL: ${authUrl}`);
308
+ if (apiUrl) console.log(` API URL: ${apiUrl}`);
328
309
  console.log(` Model: ${settings.model || 'API default'}`);
329
310
  console.log(` Streaming: ${settings.stream ? 'ON' : 'OFF'}`);
330
311
  console.log(` Debug: ${settings.debug ? 'ON' : 'OFF'}`);
331
312
  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
- }`);
313
+ console.log(` Messages: ${history.messages.length}`);
314
+ console.log(` Tools: ${settings.selectedTools.length > 0 ? settings.selectedTools.join(', ') : 'Auto-tools mode'}`);
315
+ console.log(' PAYG: Use /payment to view billing status');
338
316
  }
339
317
 
340
- // Auth menu
318
+ // ==================== AUTH MENU ====================
341
319
  async function showAuthMenu() {
342
320
  console.log('\n========================================');
343
321
  console.log(' Authentication Menu');
@@ -357,50 +335,25 @@ async function main() {
357
335
  const choice = await prompt('Select option (1-9): ');
358
336
 
359
337
  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');
338
+ case '1': await getApiKey(); break;
339
+ case '2': await getVaultInfo(); break;
340
+ case '3': showSessionInfo(); break;
341
+ case '4': await refreshSession(); break;
342
+ case '5': await getEvmAddress(); break;
343
+ case '6': await getSolanaAddress(); break;
344
+ case '7': await getBtcAddresses(); break;
345
+ case '8': await doLogout(); break;
346
+ case '9': return;
347
+ default: console.log('Invalid option');
388
348
  }
389
349
 
390
- // Show menu again after action
391
350
  await showAuthMenu();
392
351
  }
393
352
 
394
353
  async function getApiKey() {
395
354
  console.log('\nFetching API key...');
396
- if (settings.debug) {
397
- console.log('[DEBUG] Calling authSdk.getVaultApiKey()...');
398
- }
399
355
  try {
400
356
  const apiKey = await authSdk.getVaultApiKey();
401
- if (settings.debug) {
402
- console.log('[DEBUG] Raw response:', JSON.stringify(apiKey, null, 2));
403
- }
404
357
  console.log('\n========================================');
405
358
  console.log(' YOUR API KEY');
406
359
  console.log('========================================');
@@ -410,27 +363,15 @@ async function main() {
410
363
  console.log('========================================');
411
364
  console.log('');
412
365
  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
366
  } catch (error) {
416
367
  console.error('Error fetching API key:', error.message);
417
- if (settings.debug && error.stack) {
418
- console.error('[DEBUG] Stack:', error.stack);
419
- }
420
368
  }
421
369
  }
422
370
 
423
371
  async function getVaultInfo() {
424
372
  console.log('\nFetching vault info...');
425
- if (settings.debug) {
426
- console.log('[DEBUG] Calling authSdk.getVaultInfo()...');
427
- }
428
373
  try {
429
374
  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
375
  console.log('\n========================================');
435
376
  console.log(' VAULT INFO');
436
377
  console.log('========================================');
@@ -445,52 +386,31 @@ async function main() {
445
386
  }
446
387
  if (vaultInfo.btcAddresses) {
447
388
  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}`);
389
+ if (vaultInfo.btcAddresses.p2pkh) console.log(` P2PKH: ${vaultInfo.btcAddresses.p2pkh}`);
390
+ if (vaultInfo.btcAddresses.p2wpkh) console.log(` P2WPKH: ${vaultInfo.btcAddresses.p2wpkh}`);
391
+ if (vaultInfo.btcAddresses.p2tr) console.log(` P2TR: ${vaultInfo.btcAddresses.p2tr}`);
463
392
  }
393
+ if (vaultInfo.createdAt) console.log(` Created At: ${vaultInfo.createdAt}`);
464
394
  console.log('');
465
395
  console.log('========================================');
466
396
  } catch (error) {
467
397
  console.error('Error fetching vault info:', error.message);
468
- if (settings.debug && error.stack) {
469
- console.error('[DEBUG] Stack:', error.stack);
470
- }
471
398
  }
472
399
  }
473
400
 
474
401
  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
- }
402
+ const sess = authSdk.getSession();
483
403
  console.log('\n========================================');
484
404
  console.log(' SESSION INFO');
485
405
  console.log('========================================');
486
406
  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'}`);
407
+ if (sess) {
408
+ console.log(` Identifier: ${sess.user?.identifier || 'N/A'}`);
409
+ console.log(` Vault ID: ${sess.user?.vaultId || 'N/A'}`);
410
+ console.log(` App ID: ${sess.appId || 'N/A'}`);
411
+ console.log(` Auth Type: ${sess.authType || 'N/A'}`);
412
+ console.log(` Expires At: ${sess.expiresAt ? new Date(sess.expiresAt).toISOString() : 'N/A'}`);
413
+ console.log(` Auth Token: ${sess.authToken ? sess.authToken.substring(0, 20) + '...' : 'N/A'}`);
494
414
  } else {
495
415
  console.log(' No active session');
496
416
  }
@@ -500,15 +420,8 @@ async function main() {
500
420
 
501
421
  async function refreshSession() {
502
422
  console.log('\nRefreshing session...');
503
- if (settings.debug) {
504
- console.log('[DEBUG] Calling authSdk.refreshSession()...');
505
- }
506
423
  try {
507
424
  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
425
  if (newSession) {
513
426
  console.log('Session refreshed successfully!');
514
427
  console.log(`New expiry: ${new Date(newSession.expiresAt).toISOString()}`);
@@ -517,23 +430,13 @@ async function main() {
517
430
  }
518
431
  } catch (error) {
519
432
  console.error('Error refreshing session:', error.message);
520
- if (settings.debug && error.stack) {
521
- console.error('[DEBUG] Stack:', error.stack);
522
- }
523
433
  }
524
434
  }
525
435
 
526
436
  async function getEvmAddress() {
527
437
  console.log('\nFetching EVM address...');
528
- if (settings.debug) {
529
- console.log('[DEBUG] Calling authSdk.getVaultInfo() for EVM address...');
530
- }
531
438
  try {
532
439
  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
440
  if (vaultInfo.evmAddress) {
538
441
  console.log('\n========================================');
539
442
  console.log(' EVM ADDRESS');
@@ -547,23 +450,13 @@ async function main() {
547
450
  }
548
451
  } catch (error) {
549
452
  console.error('Error fetching EVM address:', error.message);
550
- if (settings.debug && error.stack) {
551
- console.error('[DEBUG] Stack:', error.stack);
552
- }
553
453
  }
554
454
  }
555
455
 
556
456
  async function getSolanaAddress() {
557
457
  console.log('\nFetching Solana address...');
558
- if (settings.debug) {
559
- console.log('[DEBUG] Calling authSdk.getVaultInfo() for Solana address...');
560
- }
561
458
  try {
562
459
  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
460
  const solanaAddr = vaultInfo.solanaAddress || vaultInfo.address;
568
461
  if (solanaAddr) {
569
462
  console.log('\n========================================');
@@ -578,42 +471,13 @@ async function main() {
578
471
  }
579
472
  } catch (error) {
580
473
  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
474
  }
604
475
  }
605
476
 
606
477
  async function getBtcAddresses() {
607
478
  console.log('\nFetching BTC addresses...');
608
- if (settings.debug) {
609
- console.log('[DEBUG] Calling authSdk.getVaultInfo() for BTC addresses...');
610
- }
611
479
  try {
612
480
  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
481
  if (vaultInfo.btcAddresses || vaultInfo.btcPubkey) {
618
482
  console.log('\n========================================');
619
483
  console.log(' BTC ADDRESSES');
@@ -624,15 +488,9 @@ async function main() {
624
488
  console.log('');
625
489
  }
626
490
  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
- }
491
+ if (vaultInfo.btcAddresses.p2pkh) console.log(` P2PKH (Legacy): ${vaultInfo.btcAddresses.p2pkh}`);
492
+ if (vaultInfo.btcAddresses.p2wpkh) console.log(` P2WPKH (SegWit): ${vaultInfo.btcAddresses.p2wpkh}`);
493
+ if (vaultInfo.btcAddresses.p2tr) console.log(` P2TR (Taproot): ${vaultInfo.btcAddresses.p2tr}`);
636
494
  }
637
495
  console.log('');
638
496
  console.log('========================================');
@@ -641,13 +499,181 @@ async function main() {
641
499
  }
642
500
  } catch (error) {
643
501
  console.error('Error fetching BTC addresses:', error.message);
644
- if (settings.debug && error.stack) {
645
- console.error('[DEBUG] Stack:', error.stack);
502
+ }
503
+ }
504
+
505
+ async function doLogout() {
506
+ console.log('\nLogging out...');
507
+ try {
508
+ authSdk.logout();
509
+ console.log('Logged out successfully.');
510
+ console.log('Session cleared. Exiting CLI...');
511
+ rl.close();
512
+ process.exit(0);
513
+ } catch (error) {
514
+ console.error('Error during logout:', error.message);
515
+ }
516
+ }
517
+
518
+ // ==================== PAYG MANAGEMENT ====================
519
+ async function showPaygStatus() {
520
+ console.log('\nFetching PAYG billing status...');
521
+ try {
522
+ const status = await client.getPaygStatus();
523
+ console.log('\n========================================');
524
+ console.log(' PAYG Billing Status');
525
+ console.log('========================================');
526
+ console.log('');
527
+ console.log(` Enabled: ${status.enabled ? 'YES' : 'NO'}`);
528
+ console.log(` Mode: ${status.mode || 'N/A'}`);
529
+ console.log(` Payment Token: ${status.payment_token || 'N/A'}`);
530
+ console.log(` Payment Chain: ${status.payment_chain || 'N/A'}`);
531
+ console.log(` Blocked: ${status.is_blocked ? 'YES' : 'NO'}`);
532
+ console.log(` Total Debt: $${(status.total_debt_usd || 0).toFixed(4)}`);
533
+ console.log(` Total Paid: $${(status.total_paid_usd || 0).toFixed(4)}`);
534
+ console.log(` Debt Ceiling: $${(status.debt_ceiling_usd || 0).toFixed(2)}`);
535
+ console.log(` Pending Charges: ${status.pending_charges || 0}`);
536
+ console.log('');
537
+ if (status.available_tokens && status.available_tokens.length > 0) {
538
+ console.log(' Available Tokens:');
539
+ status.available_tokens.forEach(t => console.log(` - ${t}`));
646
540
  }
541
+ console.log('');
542
+ console.log('========================================');
543
+ console.log('');
544
+ console.log('Commands:');
545
+ console.log(' /payment enable - Enable PAYG billing');
546
+ console.log(' /payment disable - Disable PAYG billing');
547
+ console.log(' /payment token <TOKEN> - Set payment token');
548
+ console.log(' /payment mode <MODE> - Set payment mode');
549
+ } catch (error) {
550
+ console.error('Error fetching PAYG status:', error.message);
647
551
  }
648
552
  }
649
553
 
650
- // Process commands
554
+ // ==================== STREAM RESPONSE ====================
555
+ async function streamResponse(msgs) {
556
+ let fullText = '';
557
+ let toolCalls = [];
558
+ let firstChunk = false;
559
+
560
+ process.stdout.write('\nAgent Hustle: ');
561
+ startSpinner();
562
+
563
+ try {
564
+ const streamOptions = {
565
+ messages: msgs,
566
+ processChunks: true
567
+ };
568
+
569
+ if (settings.model) {
570
+ streamOptions.model = settings.model;
571
+ }
572
+
573
+ if (settings.selectedTools.length > 0) {
574
+ streamOptions.selectedToolCategories = settings.selectedTools;
575
+ } else if (lastIntentContext) {
576
+ streamOptions.intentContext = lastIntentContext;
577
+ }
578
+
579
+ const stream = client.chatStream(streamOptions);
580
+
581
+ for await (const chunk of stream) {
582
+ if ('type' in chunk) {
583
+ switch (chunk.type) {
584
+ case 'text':
585
+ if (!firstChunk) {
586
+ stopSpinner();
587
+ firstChunk = true;
588
+ }
589
+ process.stdout.write(chunk.value);
590
+ fullText += chunk.value;
591
+ break;
592
+
593
+ case 'intent_context':
594
+ if (chunk.value?.intentContext) {
595
+ lastIntentContext = chunk.value.intentContext;
596
+ if (settings.debug) {
597
+ console.log('[DEBUG] Captured intent context:',
598
+ `activeIntent="${lastIntentContext.activeIntent || 'general'}", ` +
599
+ `categories=[${lastIntentContext.categories?.join(', ') || 'none'}]`);
600
+ }
601
+ }
602
+ break;
603
+
604
+ case 'tool_call':
605
+ if (!firstChunk) {
606
+ stopSpinner();
607
+ firstChunk = true;
608
+ }
609
+ toolCalls.push(chunk.value);
610
+ break;
611
+
612
+ case 'finish':
613
+ stopSpinner();
614
+ process.stdout.write('\n');
615
+ break;
616
+ }
617
+ }
618
+ }
619
+ } catch (error) {
620
+ stopSpinner();
621
+ console.error(`\nError: ${error.message}`);
622
+ }
623
+
624
+ if (toolCalls.length > 0) {
625
+ console.log('\nTools used:');
626
+ toolCalls.forEach((tool, i) => {
627
+ console.log(`${i+1}. ${tool.toolName || 'Unknown tool'} (ID: ${tool.toolCallId || 'unknown'})`);
628
+ if (settings.debug && tool.args) {
629
+ console.log(` Args: ${JSON.stringify(tool.args)}`);
630
+ }
631
+ });
632
+ }
633
+
634
+ return fullText;
635
+ }
636
+
637
+ // ==================== NON-STREAMING RESPONSE ====================
638
+ async function nonStreamResponse(msgs) {
639
+ console.log('\nAgent Hustle is thinking...');
640
+
641
+ try {
642
+ const chatOptions = {};
643
+
644
+ if (settings.model) {
645
+ chatOptions.model = settings.model;
646
+ }
647
+
648
+ if (settings.selectedTools.length > 0) {
649
+ chatOptions.selectedToolCategories = settings.selectedTools;
650
+ } else if (lastIntentContext) {
651
+ chatOptions.intentContext = lastIntentContext;
652
+ }
653
+
654
+ const response = await client.chat(msgs, chatOptions);
655
+
656
+ if (response.intentContext?.intentContext) {
657
+ lastIntentContext = response.intentContext.intentContext;
658
+ }
659
+
660
+ console.log(`\nAgent Hustle: ${response.content}`);
661
+
662
+ if (response.toolCalls && response.toolCalls.length > 0) {
663
+ console.log('\nTools used:');
664
+ response.toolCalls.forEach((tool, i) => {
665
+ console.log(`${i+1}. ${tool.toolName || 'Unknown'}`);
666
+ });
667
+ }
668
+
669
+ return response.content;
670
+ } catch (error) {
671
+ console.error(`\nError: ${error.message}`);
672
+ return '';
673
+ }
674
+ }
675
+
676
+ // ==================== PROCESS COMMANDS ====================
651
677
  async function processCommand(command) {
652
678
  if (command === '/help') {
653
679
  showHelp();
@@ -664,10 +690,10 @@ async function main() {
664
690
  return true;
665
691
  }
666
692
 
667
- if (command === '/clear') {
668
- messages.length = 0;
693
+ if (command === '/reset' || command === '/clear') {
694
+ resetHistory();
695
+ history = { messages: [], created: new Date().toISOString() };
669
696
  lastIntentContext = null;
670
- console.log('Conversation history cleared.');
671
697
  return true;
672
698
  }
673
699
 
@@ -675,7 +701,6 @@ async function main() {
675
701
  console.log('Goodbye!');
676
702
  rl.close();
677
703
  process.exit(0);
678
- return true;
679
704
  }
680
705
 
681
706
  if (command.startsWith('/stream')) {
@@ -721,7 +746,15 @@ async function main() {
721
746
  console.log('History retention disabled');
722
747
  }
723
748
  } else {
724
- console.log(`History is currently ${settings.retainHistory ? 'ON' : 'OFF'}`);
749
+ console.log(`\nHistory is ${settings.retainHistory ? 'ON' : 'OFF'}`);
750
+ console.log(`Conversation has ${history.messages.length} messages.`);
751
+ if (history.messages.length > 0) {
752
+ console.log('Recent:');
753
+ history.messages.slice(-4).forEach((m) => {
754
+ const preview = m.content.substring(0, 60) + (m.content.length > 60 ? '...' : '');
755
+ console.log(` ${m.role}: ${preview}`);
756
+ });
757
+ }
725
758
  }
726
759
  return true;
727
760
  }
@@ -771,9 +804,9 @@ async function main() {
771
804
  console.log('\n=== Tool Categories ===\n');
772
805
  tools.forEach((tool) => {
773
806
  const isSelected = settings.selectedTools.includes(tool.id);
774
- const status = isSelected ? '' : '';
807
+ const status = isSelected ? '[x]' : '[ ]';
775
808
  console.log(`${status} ${tool.title} (${tool.id})`);
776
- console.log(` ${tool.description}`);
809
+ console.log(` ${tool.description}`);
777
810
  });
778
811
  console.log('\nCurrently selected:',
779
812
  settings.selectedTools.length > 0
@@ -799,6 +832,8 @@ async function main() {
799
832
  settings.selectedTools.push(toolId);
800
833
  lastIntentContext = null;
801
834
  console.log(`Added: ${toolId}`);
835
+ } else {
836
+ console.log(`Already added: ${toolId}`);
802
837
  }
803
838
  return true;
804
839
  }
@@ -808,19 +843,89 @@ async function main() {
808
843
  if (index > -1) {
809
844
  settings.selectedTools.splice(index, 1);
810
845
  console.log(`Removed: ${toolId}`);
846
+ } else {
847
+ console.log(`Not found: ${toolId}`);
848
+ }
849
+ return true;
850
+ }
851
+
852
+ return true;
853
+ }
854
+
855
+ if (command.startsWith('/payment')) {
856
+ const parts = command.split(' ');
857
+
858
+ if (parts.length === 1) {
859
+ await showPaygStatus();
860
+ return true;
861
+ }
862
+
863
+ const subCommand = parts[1];
864
+
865
+ if (subCommand === 'enable') {
866
+ try {
867
+ const result = await client.configurePayg({ enabled: true });
868
+ console.log(result.success ? 'PAYG billing enabled.' : 'Failed to enable PAYG.');
869
+ } catch (error) {
870
+ console.error('Error enabling PAYG:', error.message);
871
+ }
872
+ return true;
873
+ }
874
+
875
+ if (subCommand === 'disable') {
876
+ try {
877
+ const result = await client.configurePayg({ enabled: false });
878
+ console.log(result.success ? 'PAYG billing disabled.' : 'Failed to disable PAYG.');
879
+ } catch (error) {
880
+ console.error('Error disabling PAYG:', error.message);
881
+ }
882
+ return true;
883
+ }
884
+
885
+ if (subCommand === 'token' && parts[2]) {
886
+ const token = parts[2].toUpperCase();
887
+ try {
888
+ const result = await client.configurePayg({ payment_token: token });
889
+ console.log(result.success ? `Payment token set to: ${token}` : 'Failed to set payment token.');
890
+ } catch (error) {
891
+ console.error('Error setting payment token:', error.message);
811
892
  }
812
893
  return true;
813
894
  }
814
895
 
896
+ if (subCommand === 'mode' && parts[2]) {
897
+ const mode = parts[2];
898
+ if (mode !== 'pay_per_request' && mode !== 'debt_accumulation') {
899
+ console.log('Invalid mode. Use: pay_per_request or debt_accumulation');
900
+ return true;
901
+ }
902
+ try {
903
+ const result = await client.configurePayg({ mode });
904
+ console.log(result.success ? `Payment mode set to: ${mode}` : 'Failed to set payment mode.');
905
+ } catch (error) {
906
+ console.error('Error setting payment mode:', error.message);
907
+ }
908
+ return true;
909
+ }
910
+
911
+ console.log('Invalid /payment command. Use /payment for status, or /payment enable|disable|token|mode');
815
912
  return true;
816
913
  }
817
914
 
818
915
  return false;
819
916
  }
820
917
 
821
- // Main chat loop
918
+ // ==================== CHAT LOOP ====================
822
919
  async function chat() {
823
920
  rl.question('\nYou: ', async (input) => {
921
+ input = input.trim();
922
+
923
+ if (!input) {
924
+ chat();
925
+ return;
926
+ }
927
+
928
+ // Process commands
824
929
  if (input.startsWith('/')) {
825
930
  const isCommand = await processCommand(input);
826
931
  if (isCommand) {
@@ -829,75 +934,43 @@ async function main() {
829
934
  }
830
935
  }
831
936
 
937
+ // Handle exit/quit without slash
832
938
  if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
833
939
  console.log('Goodbye!');
834
940
  rl.close();
835
941
  return;
836
942
  }
837
943
 
838
- const currentMessages = settings.retainHistory ? [...messages] : [];
944
+ // Build message list based on history retention setting
945
+ const currentMessages = settings.retainHistory ? [...history.messages] : [];
839
946
  currentMessages.push({ role: 'user', content: input });
840
947
 
841
- if (!settings.stream) {
842
- console.log('\nAgent is thinking...');
948
+ // Send message
949
+ let response;
950
+ if (settings.stream) {
951
+ response = await streamResponse(currentMessages);
952
+ } else {
953
+ response = await nonStreamResponse(currentMessages);
843
954
  }
844
955
 
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();
956
+ // Save to history
957
+ if (settings.retainHistory && response) {
958
+ history.messages.push({ role: 'user', content: input });
959
+ history.messages.push({ role: 'assistant', content: response });
960
+ saveHistory(history);
890
961
  }
962
+
963
+ chat();
891
964
  });
892
965
  }
893
966
 
894
- // Start
967
+ // Show initial settings and start chat
895
968
  console.log('Type "/help" for commands, "/auth" for auth menu, or "/exit" to quit.\n');
896
969
  showSettings();
897
970
  chat();
898
971
 
899
972
  } catch (error) {
900
- console.error('Error initializing CLI:', error);
973
+ console.error('Error:', error.message);
901
974
  process.exit(1);
902
975
  }
903
976
  }