@neuraiproject/neurai-depin-terminal 1.0.1 → 2.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neuraiproject/neurai-depin-terminal",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "Neurai DePIN terminal messaging client",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -22,17 +22,19 @@
22
22
  },
23
23
  "scripts": {
24
24
  "start": "node src/index.js",
25
- "bundle": "esbuild src/index.js --bundle --platform=node --format=cjs --outfile=dist/index.cjs --minify --alias:term.js=./src/lib/empty.js --alias:pty.js=./src/lib/empty.js --log-override:empty-import-meta=silent",
25
+ "bundle": "esbuild src/index.js --bundle --platform=node --format=cjs --outfile=dist/index.cjs --minify --external:charsm --alias:term.js=./src/lib/empty.js --alias:pty.js=./src/lib/empty.js --log-override:empty-import-meta=silent",
26
26
  "prepublishOnly": "npm run bundle",
27
27
  "build": "npm run bundle && NODE_NO_WARNINGS=1 pkg dist/index.cjs --targets node18-linux-x64,node18-macos-x64,node18-win-x64 --output bin/neurai-depin-terminal --compress GZip"
28
28
  },
29
29
  "pkg": {
30
30
  "assets": [
31
- "node_modules/@neuraiproject/neurai-depin-msg/dist/neurai-depin-msg.js"
31
+ "node_modules/@neuraiproject/neurai-depin-msg/dist/neurai-depin-msg.js",
32
+ "node_modules/charsm/dist/lip.wasm",
33
+ "node_modules/charsm/dist/binary/**/*"
32
34
  ],
33
35
  "scripts": [
34
- "node_modules/blessed/lib/**/*.js",
35
- "node_modules/blessed-contrib/lib/**/*.js"
36
+ "node_modules/charsm/dist/**/*.js",
37
+ "node_modules/charsm/dist/**/*.mjs"
36
38
  ]
37
39
  },
38
40
  "keywords": [
@@ -45,12 +47,11 @@
45
47
  "author": "Neurai Community",
46
48
  "license": "MIT",
47
49
  "dependencies": {
48
- "@neuraiproject/neurai-depin-msg": "^2.1.1",
49
- "@neuraiproject/neurai-jswallet": "0.12.8",
50
- "@neuraiproject/neurai-key": "^2.8.6",
51
- "blessed": "^0.1.81",
52
- "blessed-contrib": "^4.11.0",
50
+ "@neuraiproject/neurai-depin-msg": "^2.1.3",
51
+ "@neuraiproject/neurai-jswallet": "0.12.10",
52
+ "@neuraiproject/neurai-key": "^2.8.7",
53
53
  "chalk": "^5.3.0",
54
+ "charsm": "^0.2.0",
54
55
  "readline": "^1.3.0"
55
56
  },
56
57
  "devDependencies": {
@@ -36,22 +36,28 @@ export class ConfigManager {
36
36
  * Encrypt private key using AES-256-GCM
37
37
  * @param {string} privateKey - Plain text private key in WIF format
38
38
  * @param {string} password - Password for encryption
39
- * @returns {string} Encrypted data in format: salt:iv:authTag:encrypted (hex)
39
+ * @returns {Promise<string>} Encrypted data in format: salt:iv:authTag:encrypted (hex)
40
40
  * @throws {EncryptionError} If encryption fails
41
41
  */
42
- encryptPrivateKey(privateKey, password) {
42
+ async encryptPrivateKey(privateKey, password) {
43
43
  try {
44
44
  const salt = crypto.randomBytes(ENCRYPTION.SALT_LENGTH);
45
- const key = crypto.scryptSync(
46
- password,
47
- salt,
48
- ENCRYPTION.KEY_LENGTH,
49
- {
50
- N: ENCRYPTION.SCRYPT_COST,
51
- r: ENCRYPTION.SCRYPT_BLOCK_SIZE,
52
- p: ENCRYPTION.SCRYPT_PARALLELIZATION
53
- }
54
- );
45
+ const key = await new Promise((resolve, reject) => {
46
+ crypto.scrypt(
47
+ password,
48
+ salt,
49
+ ENCRYPTION.KEY_LENGTH,
50
+ {
51
+ N: ENCRYPTION.SCRYPT_COST,
52
+ r: ENCRYPTION.SCRYPT_BLOCK_SIZE,
53
+ p: ENCRYPTION.SCRYPT_PARALLELIZATION
54
+ },
55
+ (err, derivedKey) => {
56
+ if (err) reject(err);
57
+ else resolve(derivedKey);
58
+ }
59
+ );
60
+ });
55
61
  const iv = crypto.randomBytes(ENCRYPTION.IV_LENGTH);
56
62
  const cipher = crypto.createCipheriv(ENCRYPTION.ALGORITHM, key, iv);
57
63
 
@@ -69,10 +75,10 @@ export class ConfigManager {
69
75
  * Decrypt private key using AES-256-GCM
70
76
  * @param {string} encryptedData - Encrypted data in format: salt:iv:authTag:encrypted
71
77
  * @param {string} password - Password for decryption
72
- * @returns {string} Decrypted private key in WIF format
78
+ * @returns {Promise<string>} Decrypted private key in WIF format
73
79
  * @throws {EncryptionError} If decryption fails or password is incorrect
74
80
  */
75
- decryptPrivateKey(encryptedData, password) {
81
+ async decryptPrivateKey(encryptedData, password) {
76
82
  try {
77
83
  const parts = encryptedData.split(':');
78
84
  if (parts.length !== 4) {
@@ -84,16 +90,22 @@ export class ConfigManager {
84
90
  const authTag = Buffer.from(parts[2], 'hex');
85
91
  const encrypted = parts[3];
86
92
 
87
- const key = crypto.scryptSync(
88
- password,
89
- salt,
90
- ENCRYPTION.KEY_LENGTH,
91
- {
92
- N: ENCRYPTION.SCRYPT_COST,
93
- r: ENCRYPTION.SCRYPT_BLOCK_SIZE,
94
- p: ENCRYPTION.SCRYPT_PARALLELIZATION
95
- }
96
- );
93
+ const key = await new Promise((resolve, reject) => {
94
+ crypto.scrypt(
95
+ password,
96
+ salt,
97
+ ENCRYPTION.KEY_LENGTH,
98
+ {
99
+ N: ENCRYPTION.SCRYPT_COST,
100
+ r: ENCRYPTION.SCRYPT_BLOCK_SIZE,
101
+ p: ENCRYPTION.SCRYPT_PARALLELIZATION
102
+ },
103
+ (err, derivedKey) => {
104
+ if (err) reject(err);
105
+ else resolve(derivedKey);
106
+ }
107
+ );
108
+ });
97
109
  const decipher = crypto.createDecipheriv(ENCRYPTION.ALGORITHM, key, iv);
98
110
  decipher.setAuthTag(authTag);
99
111
 
@@ -126,10 +138,13 @@ export class ConfigManager {
126
138
  const password = await readPassword('Enter password to decrypt private key: ');
127
139
 
128
140
  try {
129
- privateKey = this.decryptPrivateKey(this.config.privateKey, password);
141
+ process.stdout.write('Verifying password...');
142
+ privateKey = await this.decryptPrivateKey(this.config.privateKey, password);
143
+ process.stdout.write('\r\x1b[K'); // Clear the "Verifying..." line
130
144
  decrypted = true;
131
145
  console.log('✓ Private key decrypted successfully\n');
132
146
  } catch (error) {
147
+ process.stdout.write('\r\x1b[K'); // Clear the "Verifying..." line
133
148
  if (attempts < maxAttempts) {
134
149
  console.log(`✗ Incorrect password. ${maxAttempts - attempts} attempts remaining.\n`);
135
150
  } else {
@@ -258,7 +273,9 @@ export class ConfigManager {
258
273
 
259
274
  // Get password and encrypt private key
260
275
  const password = await this.promptForPasswordCreation();
261
- const encryptedPrivateKey = this.encryptPrivateKey(privateKey, password);
276
+ process.stdout.write('Encrypting private key...');
277
+ const encryptedPrivateKey = await this.encryptPrivateKey(privateKey, password);
278
+ process.stdout.write('\r\x1b[K'); // Clear the "Encrypting..." line
262
279
  console.log('✓ Private key encrypted successfully\n');
263
280
 
264
281
  // Create new readline for remaining questions
package/src/constants.js CHANGED
@@ -48,6 +48,11 @@ export const MESSAGE = {
48
48
  FORCE_POLL_DELAY: 2000
49
49
  };
50
50
 
51
+ // Recipient cache
52
+ export const RECIPIENT_CACHE = {
53
+ REFRESH_MS: 60000
54
+ };
55
+
51
56
  // Token Validation
52
57
  export const TOKEN = {
53
58
  PREFIX: '&',
@@ -57,9 +62,10 @@ export const TOKEN = {
57
62
  // UI Layout
58
63
  export const UI = {
59
64
  TOP_BAR_HEIGHT: 2,
65
+ TAB_BAR_HEIGHT: 1,
60
66
  INPUT_BOX_HEIGHT: 3,
61
67
  STATUS_BAR_HEIGHT: 1,
62
- MESSAGE_BOX_OFFSET: 6, // top bar + input + status
68
+ MESSAGE_BOX_OFFSET: 7, // top bar + tab bar + input + status
63
69
  SCROLLBAR_CHAR: ' '
64
70
  };
65
71
 
@@ -94,11 +100,13 @@ export const RPC_METHODS = {
94
100
  DEPIN_SUBMIT_MSG: 'depinsubmitmsg',
95
101
  DEPIN_GET_MSG_INFO: 'depingetmsginfo',
96
102
  LIST_ADDRESSES_BY_ASSET: 'listaddressesbyasset',
97
- GET_PUBKEY: 'getpubkey'
103
+ GET_PUBKEY: 'getpubkey',
104
+ LIST_DEPIN_ADDRESSES: 'listdepinaddresses'
98
105
  };
99
106
 
100
107
  // Terminal Control Sequences
101
108
  export const TERMINAL = {
109
+ ENTER_ALT_SCREEN: '\x1b[?1049h',
102
110
  EXIT_ALT_SCREEN: '\x1b[?1049l',
103
111
  SHOW_CURSOR: '\x1b[?25h',
104
112
  RESET_ATTRIBUTES: '\x1b[0m',
@@ -143,7 +151,9 @@ export const ERROR_MESSAGES = {
143
151
  INVALID_RPC_URL: 'Invalid RPC URL',
144
152
  CONNECTION_ERROR: 'Connection error',
145
153
  TOKEN_NOT_OWNED: 'This address does not own the configured token',
146
- PUBKEY_NOT_REVEALED: 'Public key not revealed on blockchain'
154
+ PUBKEY_NOT_REVEALED: 'Public key not revealed on blockchain',
155
+ INVALID_PRIVATE_MESSAGE_FORMAT: 'Private message format: @address message',
156
+ RECIPIENT_PUBKEY_NOT_REVEALED: 'Recipient public key not revealed on blockchain'
147
157
  };
148
158
 
149
159
  // Success Messages
@@ -162,10 +172,10 @@ export const INFO_MESSAGES = {
162
172
  LOADING_LIBRARY: 'Loading DePIN library...',
163
173
  INITIALIZING_WALLET: 'Initializing wallet...',
164
174
  STARTING_UI: 'Starting terminal interface...',
165
- PRESS_CTRL_C: 'Press Ctrl+C to exit.',
175
+ PRESS_CTRL_C: 'Press ESC or CTRL+C to exit.',
166
176
  CONNECTING: 'Attempting to connect to DePIN server...',
167
177
  RECONNECTING: 'Reconnecting, check server configuration',
168
- SENDING: 'Sending message to all token holders...',
178
+ SENDING: 'Sending message...',
169
179
  VERIFYING_TOKEN: 'Verifying token ownership...',
170
180
  VERIFYING_PUBKEY: 'Verifying public key...'
171
181
  };
@@ -192,6 +202,9 @@ export const TIME = {
192
202
  export const BLESSED_KEYS = {
193
203
  QUIT: ['C-c', 'escape'],
194
204
  SEND: ['enter', 'C-s'],
205
+ TAB_NEXT: ['C-right'],
206
+ TAB_PREV: ['C-left'],
207
+ TAB_CLOSE: ['C-w'],
195
208
  SCROLL_UP: ['up'],
196
209
  SCROLL_DOWN: ['down']
197
210
  };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared message type helpers
3
+ * @module domain/messageTypes
4
+ */
5
+
6
+ export const MESSAGE_TYPES = {
7
+ GROUP: 'group',
8
+ PRIVATE: 'private'
9
+ };
10
+
11
+ export const normalizeMessageType = (rawType) => {
12
+ if (typeof rawType === 'string') {
13
+ const lowered = rawType.toLowerCase();
14
+ if (lowered === MESSAGE_TYPES.PRIVATE) {
15
+ return MESSAGE_TYPES.PRIVATE;
16
+ }
17
+ if (lowered === MESSAGE_TYPES.GROUP) {
18
+ return MESSAGE_TYPES.GROUP;
19
+ }
20
+ }
21
+ return MESSAGE_TYPES.GROUP;
22
+ };
23
+
24
+ export const isPrivateMessage = (rawType) => {
25
+ return normalizeMessageType(rawType) === MESSAGE_TYPES.PRIVATE;
26
+ };
package/src/errors.js CHANGED
@@ -147,3 +147,20 @@ export function extractErrorMessage(error, fallback = 'Unknown error') {
147
147
 
148
148
  return fallback;
149
149
  }
150
+
151
+ /**
152
+ * Check if an error is a known/expected application error
153
+ * @param {Error} error - Error object
154
+ * @returns {boolean} True if error is a known application error
155
+ */
156
+ export function isKnownError(error) {
157
+ return error instanceof DepinError;
158
+ }
159
+
160
+ /**
161
+ * Check if debug mode is enabled
162
+ * @returns {boolean} True if debug mode is enabled
163
+ */
164
+ export function isDebugMode() {
165
+ return process.env.DEPIN_DEBUG === '1' || process.env.NODE_ENV === 'development';
166
+ }
package/src/index.js CHANGED
@@ -13,21 +13,25 @@ import { RpcService } from './services/RpcService.js';
13
13
  import { MessageStore } from './messaging/MessageStore.js';
14
14
  import { MessagePoller } from './messaging/MessagePoller.js';
15
15
  import { MessageSender } from './messaging/MessageSender.js';
16
- import { TerminalUI } from './ui/TerminalUI.js';
16
+ import { RecipientDirectory } from './messaging/RecipientDirectory.js';
17
+ import { CharsmUI } from './ui/CharsmUI.js';
17
18
  import {
18
19
  INFO_MESSAGES,
19
20
  SUCCESS_MESSAGES,
20
21
  ERROR_MESSAGES,
21
22
  WARNING_MESSAGES,
22
23
  MESSAGE,
24
+ RECIPIENT_CACHE,
23
25
  HASH,
24
26
  ICONS
25
27
  } from './constants.js';
26
- import { extractErrorMessage } from './errors.js';
28
+ import { extractErrorMessage, isKnownError, isDebugMode } from './errors.js';
29
+ import { MESSAGE_TYPES } from './domain/messageTypes.js';
30
+ import { emergencyTerminalCleanup, drainInput } from './utils.js';
27
31
 
28
32
  /**
29
33
  * Global UI instance for cleanup on exit
30
- * @type {TerminalUI|null}
34
+ * @type {CharsmUI|null}
31
35
  */
32
36
  let uiInstance = null;
33
37
 
@@ -91,17 +95,31 @@ async function initializeRpc(config) {
91
95
  * @returns {Object} Messaging components (store, poller, sender)
92
96
  */
93
97
  function initializeMessaging(config, walletManager, rpcService, neuraiDepinMsg) {
98
+ const recipientDirectory = new RecipientDirectory(config, rpcService, neuraiDepinMsg);
94
99
  const messageStore = new MessageStore();
95
- const messagePoller = new MessagePoller(config, rpcService, messageStore, neuraiDepinMsg, walletManager);
96
- const messageSender = new MessageSender(config, walletManager, rpcService, neuraiDepinMsg);
97
-
98
- return { messageStore, messagePoller, messageSender };
100
+ const messagePoller = new MessagePoller(
101
+ config,
102
+ rpcService,
103
+ messageStore,
104
+ neuraiDepinMsg,
105
+ walletManager,
106
+ recipientDirectory
107
+ );
108
+ const messageSender = new MessageSender(
109
+ config,
110
+ walletManager,
111
+ rpcService,
112
+ neuraiDepinMsg,
113
+ recipientDirectory
114
+ );
115
+
116
+ return { messageStore, messagePoller, messageSender, recipientDirectory };
99
117
  }
100
118
 
101
119
  /**
102
120
  * Connect poller events to UI
103
121
  * @param {MessagePoller} messagePoller - Message poller instance
104
- * @param {TerminalUI} ui - Terminal UI instance
122
+ * @param {CharsmUI} ui - Terminal UI instance
105
123
  * @param {RpcService} rpcService - RPC service instance
106
124
  */
107
125
  function connectPollerToUI(messagePoller, ui, rpcService, onRpcDown) {
@@ -162,21 +180,39 @@ function connectPollerToUI(messagePoller, ui, rpcService, onRpcDown) {
162
180
 
163
181
  /**
164
182
  * Connect UI send action to message sender
165
- * @param {TerminalUI} ui - Terminal UI instance
183
+ * @param {CharsmUI} ui - Terminal UI instance
166
184
  * @param {MessageSender} messageSender - Message sender instance
167
185
  * @param {MessagePoller} messagePoller - Message poller instance
168
186
  */
169
- function connectSenderToUI(ui, messageSender, getMessagePoller) {
187
+ function connectSenderToUI(ui, messageSender, getMessageStore, getMessagePoller) {
170
188
  ui.onSend(async (message) => {
171
189
  ui.updateSendStatus(INFO_MESSAGES.SENDING, 'info');
172
190
 
173
191
  try {
174
192
  const result = await messageSender.send(message);
193
+ const hashPreview = result.hash
194
+ ? `${result.hash.slice(0, HASH.DISPLAY_LENGTH)}...`
195
+ : 'N/A';
196
+
197
+ if (result.messageType === MESSAGE_TYPES.PRIVATE && result.messageHash && result.recipientAddress) {
198
+ const store = getMessageStore();
199
+ if (store) {
200
+ store.registerOutgoingPrivateMessage(result.messageHash, result.recipientAddress);
201
+ }
202
+ ui.openPrivateTab(result.recipientAddress, true);
203
+ }
175
204
 
176
- ui.updateSendStatus(
177
- `Message sent to ${result.recipients} recipients. Hash: ${result.hash.slice(0, HASH.DISPLAY_LENGTH)}...`,
178
- 'success'
179
- );
205
+ if (result.messageType === MESSAGE_TYPES.PRIVATE) {
206
+ ui.updateSendStatus(
207
+ `Private message sent to ${result.recipientAddress}. Hash: ${hashPreview}`,
208
+ 'success'
209
+ );
210
+ } else {
211
+ ui.updateSendStatus(
212
+ `Message sent to ${result.recipients} recipients. Hash: ${hashPreview}`,
213
+ 'success'
214
+ );
215
+ }
180
216
 
181
217
  // Force a poll after sending to see the message
182
218
  setTimeout(() => {
@@ -195,7 +231,7 @@ function connectSenderToUI(ui, messageSender, getMessagePoller) {
195
231
  /**
196
232
  * Perform initial connection check and update UI
197
233
  * @param {RpcService} rpcService - RPC service instance
198
- * @param {TerminalUI} ui - Terminal UI instance
234
+ * @param {CharsmUI} ui - Terminal UI instance
199
235
  */
200
236
  async function performInitialConnectionCheck(rpcService, ui) {
201
237
  if (rpcService.isConnected()) {
@@ -206,7 +242,10 @@ async function performInitialConnectionCheck(rpcService, ui) {
206
242
  connected: true,
207
243
  lastPoll: null
208
244
  });
209
- ui.showSuccess(SUCCESS_MESSAGES.CONNECTED);
245
+ const connectedMsgHash = ui.showSuccess(SUCCESS_MESSAGES.CONNECTED);
246
+ setTimeout(() => {
247
+ ui.removeMessage(connectedMsgHash);
248
+ }, 10000);
210
249
  } catch (error) {
211
250
  // Pool info check failed, continue without it
212
251
  ui.updateTopBar({
@@ -231,7 +270,7 @@ async function performInitialConnectionCheck(rpcService, ui) {
231
270
  * @param {RpcService} rpcService - RPC service
232
271
  * @param {WalletManager} walletManager - Wallet manager
233
272
  * @param {Object} config - Configuration
234
- * @param {TerminalUI} ui - UI instance
273
+ * @param {CharsmUI} ui - UI instance
235
274
  * @param {MessagePoller} messagePoller - Message poller instance
236
275
  */
237
276
  function startVerificationLoop(rpcService, walletManager, config, ui, getMessagePoller, resetMessagingAfterReconnect) {
@@ -340,12 +379,28 @@ function startVerificationLoop(rpcService, walletManager, config, ui, getMessage
340
379
  */
341
380
  async function main() {
342
381
  try {
382
+ // Emergency cleanup to ensure terminal is in a clean state
383
+ emergencyTerminalCleanup();
384
+
343
385
  console.log('Neurai DePIN Terminal');
344
386
  console.log('=====================\n');
345
387
 
346
388
  // 1. Load configuration
347
389
  const config = await initializeConfig();
348
390
 
391
+ // Comprehensive stdin cleanup after password prompt
392
+ if (process.stdin.isTTY) {
393
+ process.stdin.removeAllListeners('data');
394
+ process.stdin.removeAllListeners('keypress');
395
+ process.stdin.setRawMode(false);
396
+
397
+ // Use shared robust flushing logic
398
+ await drainInput(process.stdin);
399
+
400
+ // Wait one tick for everything to stabilize
401
+ await new Promise(resolve => setImmediate(resolve));
402
+ }
403
+
349
404
  // 2. Load DePIN library
350
405
  const neuraiDepinMsg = await initializeLibrary();
351
406
 
@@ -356,7 +411,7 @@ async function main() {
356
411
  const rpcService = await initializeRpc(config);
357
412
 
358
413
  // 5. Initialize messaging components
359
- const { messageStore, messagePoller, messageSender } = initializeMessaging(
414
+ const { messageStore, messagePoller, messageSender, recipientDirectory } = initializeMessaging(
360
415
  config,
361
416
  walletManager,
362
417
  rpcService,
@@ -368,17 +423,36 @@ async function main() {
368
423
  messageStore,
369
424
  messagePoller,
370
425
  messageSender,
371
- detachPollerUi: null
426
+ recipientDirectory,
427
+ detachPollerUi: null,
428
+ recipientRefreshInterval: null
429
+ };
430
+
431
+ const refreshRecipientCache = async (force = false) => {
432
+ try {
433
+ await messaging.messageSender.refreshRecipientCache(force);
434
+ } catch (error) {
435
+ // Non-fatal: keep existing cache if any
436
+ }
372
437
  };
373
438
 
374
439
  // 6. Initialize UI
375
440
  console.log(INFO_MESSAGES.STARTING_UI);
376
441
  console.log('');
377
- const ui = new TerminalUI(config, walletManager, rpcService);
442
+ const ui = await CharsmUI.create(config, walletManager, rpcService);
378
443
  uiInstance = ui;
444
+ ui.setRecipientProvider(
445
+ () => messaging.messageSender.getPrivateRecipientAddresses(),
446
+ () => messaging.messageSender.getCachedPrivateRecipientAddresses()
447
+ );
379
448
 
380
449
  // 7. Get initial pool info and check connection
381
450
  await performInitialConnectionCheck(rpcService, ui);
451
+ refreshRecipientCache(true);
452
+ messaging.recipientRefreshInterval = setInterval(
453
+ () => refreshRecipientCache(true),
454
+ RECIPIENT_CACHE.REFRESH_MS
455
+ );
382
456
 
383
457
  let onRpcDownHandler = null;
384
458
 
@@ -416,7 +490,8 @@ async function main() {
416
490
  rpcService,
417
491
  messaging.messageStore,
418
492
  neuraiDepinMsg,
419
- walletManager
493
+ walletManager,
494
+ messaging.recipientDirectory
420
495
  );
421
496
 
422
497
  // Mark as disconnected so the first poll is a full sync
@@ -427,11 +502,13 @@ async function main() {
427
502
  // Refresh pool info like at startup
428
503
  await performInitialConnectionCheck(rpcService, ui);
429
504
 
505
+ await refreshRecipientCache(true);
430
506
  messaging.messagePoller.start();
431
507
  await messaging.messagePoller.poll();
432
508
  };
433
509
 
434
510
  const getMessagePoller = () => messaging.messagePoller;
511
+ const getMessageStore = () => messaging.messageStore;
435
512
 
436
513
  // 8. Create verification loop (Single retry mechanism)
437
514
  const verification = startVerificationLoop(
@@ -448,7 +525,7 @@ async function main() {
448
525
  attachCurrentPollerToUI();
449
526
 
450
527
  // 10. Connect message sending from UI
451
- connectSenderToUI(ui, messaging.messageSender, getMessagePoller);
528
+ connectSenderToUI(ui, messaging.messageSender, getMessageStore, getMessagePoller);
452
529
 
453
530
  // 11. Start verification loop (after wiring listeners)
454
531
  verification.start();
@@ -459,7 +536,10 @@ async function main() {
459
536
  }
460
537
 
461
538
  // 13. Show instructions
462
- ui.showInfo(INFO_MESSAGES.PRESS_CTRL_C);
539
+ const exitMsgHash = ui.showInfo(INFO_MESSAGES.PRESS_CTRL_C);
540
+ setTimeout(() => {
541
+ ui.removeMessage(exitMsgHash);
542
+ }, 10000);
463
543
 
464
544
  } catch (error) {
465
545
  if (uiInstance) {
@@ -467,10 +547,20 @@ async function main() {
467
547
  }
468
548
 
469
549
  const errorMsg = extractErrorMessage(error, 'Unknown error');
470
- console.error('Fatal error:', errorMsg);
471
550
 
472
- if (error.stack) {
473
- console.error(error.stack);
551
+ // For known errors, show a clean message. For unknown errors or debug mode, show stack trace
552
+ if (isKnownError(error)) {
553
+ console.error('\n✗ Error:', errorMsg);
554
+ if (isDebugMode() && error.stack) {
555
+ console.error('\nStack trace:');
556
+ console.error(error.stack);
557
+ }
558
+ } else {
559
+ console.error('\n✗ Fatal error:', errorMsg);
560
+ if (error.stack) {
561
+ console.error('\nStack trace:');
562
+ console.error(error.stack);
563
+ }
474
564
  }
475
565
 
476
566
  process.exit(1);
@@ -486,10 +576,19 @@ process.on('unhandledRejection', (error) => {
486
576
  }
487
577
 
488
578
  const errorMsg = extractErrorMessage(error, 'Unknown error');
489
- console.error('Unhandled error:', errorMsg);
490
579
 
491
- if (error && error.stack) {
492
- console.error(error.stack);
580
+ if (isKnownError(error)) {
581
+ console.error('\n✗ Error:', errorMsg);
582
+ if (isDebugMode() && error && error.stack) {
583
+ console.error('\nStack trace:');
584
+ console.error(error.stack);
585
+ }
586
+ } else {
587
+ console.error('\n✗ Unhandled error:', errorMsg);
588
+ if (error && error.stack) {
589
+ console.error('\nStack trace:');
590
+ console.error(error.stack);
591
+ }
493
592
  }
494
593
 
495
594
  process.exit(1);
@@ -525,4 +624,4 @@ process.on('exit', () => {
525
624
  });
526
625
 
527
626
  // Execute main function
528
- main();
627
+ main();