@neuraiproject/neurai-depin-terminal 1.0.0 → 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.
@@ -1,272 +0,0 @@
1
- /**
2
- * Terminal UI for Neurai DePIN Terminal
3
- * Full-screen blessed-based interface with top bar, message area, input, and status bar
4
- * @module TerminalUI
5
- */
6
-
7
- import blessed from 'blessed';
8
- import {
9
- COLORS,
10
- ICONS,
11
- BLESSED_KEYS,
12
- ADDRESS,
13
- PRIVACY,
14
- TIME
15
- } from '../constants.js';
16
- import { formatTimestamp, resetTerminal } from '../utils.js';
17
- import { TopBar } from './components/TopBar.js';
18
- import { MessageBox } from './components/MessageBox.js';
19
- import { InputBox } from './components/InputBox.js';
20
- import { StatusBar } from './components/StatusBar.js';
21
- import { ErrorOverlay } from './components/ErrorOverlay.js';
22
-
23
- /**
24
- * Terminal UI manager using blessed library
25
- */
26
- export class TerminalUI {
27
- /**
28
- * Create a new TerminalUI instance
29
- * @param {Object} config - Configuration object
30
- * @param {string} config.rpc_url - RPC server URL
31
- * @param {string} config.token - DePIN token name
32
- * @param {WalletManager} walletManager - Wallet manager instance
33
- * @param {RpcService} rpcService - RPC service instance
34
- */
35
- constructor(config, walletManager, rpcService) {
36
- this.config = config;
37
- this.walletManager = walletManager;
38
- this.rpcService = rpcService;
39
- this.myAddress = walletManager.getAddress();
40
- this.sendCallback = null;
41
- this.displayedMessages = [];
42
- this.hasPrivacy = false;
43
- this.encryptionType = PRIVACY.DEFAULT_ENCRYPTION;
44
- this.totalMessages = 0;
45
- this.lastConnectionStatus = false;
46
- this.lastPollTime = null;
47
-
48
- this.initializeScreen();
49
- this.createComponents();
50
- this.setupKeybindings();
51
- this.inputBox.focus();
52
- this.screen.render();
53
- this.updateTopBar({ connected: false, lastPoll: null });
54
- }
55
-
56
- /**
57
- * Initialize blessed screen
58
- */
59
- initializeScreen() {
60
- this.screen = blessed.screen({
61
- smartCSR: true,
62
- title: 'Neurai DePIN Terminal'
63
- });
64
- }
65
-
66
- /**
67
- * Create UI components (top bar, message box, input box, status bar)
68
- */
69
- createComponents() {
70
- this.topBar = new TopBar(this.screen, this.config, this.myAddress);
71
- this.messageBox = new MessageBox(this.screen);
72
- this.inputBox = new InputBox(this.screen, (msg) => this.handleSend(msg));
73
- this.statusBar = new StatusBar(this.screen);
74
- this.errorOverlay = new ErrorOverlay(this.screen);
75
- }
76
-
77
- /**
78
- * Show blocking error overlay
79
- * @param {string[]} errors - List of error messages
80
- */
81
- showBlockingErrors(errors) {
82
- this.inputBox.disable();
83
- this.errorOverlay.show(errors);
84
- }
85
-
86
- /**
87
- * Clear blocking error overlay
88
- */
89
- clearBlockingErrors() {
90
- if (this.errorOverlay.isVisible()) {
91
- this.errorOverlay.hide();
92
- this.inputBox.enable();
93
- this.inputBox.focus();
94
- }
95
- }
96
-
97
- /**
98
- * Setup keyboard bindings
99
- */
100
- setupKeybindings() {
101
- // Exit on Ctrl+C or Escape
102
- this.screen.key(BLESSED_KEYS.QUIT, () => {
103
- this.cleanup();
104
- process.exit(0);
105
- });
106
- }
107
-
108
- /**
109
- * Update top bar with connection status and info
110
- * @param {Object} status - Status object
111
- * @param {boolean} status.connected - Connection status
112
- * @param {Date|null} status.lastPoll - Last poll time
113
- */
114
- updateTopBar(status) {
115
- this.lastConnectionStatus = status.connected;
116
-
117
- if (status.lastPoll) {
118
- this.lastPollTime = status.lastPoll;
119
- }
120
-
121
- this.topBar.update({
122
- connected: status.connected,
123
- lastPoll: status.lastPoll
124
- });
125
- }
126
-
127
- /**
128
- * Format message line for display
129
- * @param {Object} msg - Message object
130
- * @returns {string} Formatted message line with blessed tags
131
- */
132
- formatMessageLine(msg) {
133
- const time = formatTimestamp(msg.timestamp, this.config.timezone);
134
- const isMe = msg.sender === this.myAddress;
135
- const senderLabel = isMe ? 'YOU' : msg.sender.slice(0, ADDRESS.TRUNCATE_LENGTH);
136
- const color = isMe ? COLORS.MY_MESSAGE : COLORS.OTHER_MESSAGE;
137
-
138
- return `{${color}}[${time}] ${senderLabel}: ${msg.message}{/}`;
139
- }
140
-
141
- /**
142
- * Add a new message to the display
143
- * Maintains chronological order (oldest to newest)
144
- * @param {Object} msg - Message object
145
- * @param {string} msg.sender - Sender address
146
- * @param {string} msg.message - Message content
147
- * @param {number} msg.timestamp - Unix timestamp in seconds
148
- * @param {string} msg.hash - Message hash
149
- */
150
- addMessage(msg) {
151
- this.displayedMessages.push(msg);
152
-
153
- // Sort by timestamp (oldest to newest)
154
- this.displayedMessages.sort((a, b) => a.timestamp - b.timestamp);
155
-
156
- // Redraw all messages
157
- this.redrawMessages();
158
- }
159
-
160
- /**
161
- * Redraw all messages in the message box
162
- */
163
- redrawMessages() {
164
- const formattedMessages = this.displayedMessages.map(msg => this.formatMessageLine(msg));
165
- // Clear and rebuild message box content
166
- this.messageBox.component.setContent(formattedMessages.join('\n'));
167
- this.messageBox.component.setScrollPerc(100);
168
- this.screen.render();
169
- }
170
-
171
- /**
172
- * Show error message in message box
173
- * @param {string} errorMsg - Error message
174
- */
175
- showError(errorMsg) {
176
- const line = `{${COLORS.ERROR}}[ERROR] ${errorMsg}{/}`;
177
- this.messageBox.addMessage(line);
178
- }
179
-
180
- /**
181
- * Show info message in message box
182
- * @param {string} infoMsg - Info message
183
- */
184
- showInfo(infoMsg) {
185
- const line = `{${COLORS.INFO}}[INFO] ${infoMsg}{/}`;
186
- this.messageBox.addMessage(line);
187
- }
188
-
189
- /**
190
- * Show success message in message box
191
- * @param {string} successMsg - Success message
192
- */
193
- showSuccess(successMsg) {
194
- const line = `{${COLORS.SUCCESS}}[${ICONS.SUCCESS}] ${successMsg}{/}`;
195
- this.messageBox.addMessage(line);
196
- }
197
-
198
- /**
199
- * Update status bar with send status
200
- * @param {string} message - Status message
201
- * @param {string} [type='info'] - Message type: 'success', 'error', or 'info'
202
- */
203
- updateSendStatus(message, type = 'info') {
204
- this.statusBar.update(message, type);
205
- }
206
-
207
- /**
208
- * Clear status bar
209
- */
210
- clearSendStatus() {
211
- this.statusBar.update('');
212
- }
213
-
214
- /**
215
- * Handle send message action
216
- * @param {string} message - Message to send
217
- */
218
- handleSend(message) {
219
- if (!message) return;
220
-
221
- if (this.sendCallback) {
222
- this.sendCallback(message);
223
- }
224
- }
225
-
226
- /**
227
- * Register callback for send action
228
- * @param {Function} callback - Callback function(message)
229
- */
230
- onSend(callback) {
231
- this.sendCallback = callback;
232
- }
233
-
234
- /**
235
- * Update pool information from depingetmsginfo RPC call
236
- * @param {Object} poolInfo - Pool information
237
- * @param {number} [poolInfo.messages] - Total messages in pool
238
- * @param {string} [poolInfo.cipher] - Encryption cipher name
239
- * @param {string} [poolInfo.depinpoolpkey] - Server privacy public key
240
- */
241
- updatePoolInfo(poolInfo) {
242
- if (poolInfo) {
243
- this.totalMessages = poolInfo.messages || 0;
244
- this.encryptionType = poolInfo.cipher || PRIVACY.DEFAULT_ENCRYPTION;
245
- this.hasPrivacy = poolInfo.depinpoolpkey && poolInfo.depinpoolpkey !== PRIVACY.NO_KEY_VALUE;
246
-
247
- this.topBar.setTotalMessages(this.totalMessages);
248
- this.topBar.setEncryptionType(this.encryptionType);
249
-
250
- // Refresh top bar
251
- this.updateTopBar({
252
- connected: this.lastConnectionStatus,
253
- lastPoll: this.lastPollTime
254
- });
255
- }
256
- }
257
-
258
- /**
259
- * Cleanup terminal state
260
- * Removes listeners, destroys screen, resets terminal
261
- */
262
- cleanup() {
263
- if (this.screen) {
264
- try {
265
- this.screen.destroy();
266
- } catch (err) {
267
- // Ignore cleanup errors
268
- }
269
- resetTerminal();
270
- }
271
- }
272
- }
@@ -1,99 +0,0 @@
1
- import blessed from 'blessed';
2
- import { COLORS } from '../../constants.js';
3
-
4
- export class ErrorOverlay {
5
- constructor(screen) {
6
- this.screen = screen;
7
- this.timer = null;
8
- this.timeLeft = 30;
9
- this.currentErrors = [];
10
-
11
- this.component = blessed.box({
12
- top: 'center',
13
- left: 'center',
14
- width: '50%',
15
- height: 'shrink',
16
- content: '',
17
- tags: true,
18
- border: {
19
- type: 'line'
20
- },
21
- style: {
22
- fg: COLORS.FG_WHITE,
23
- bg: COLORS.ERROR,
24
- border: {
25
- fg: COLORS.FG_WHITE
26
- }
27
- },
28
- hidden: true
29
- });
30
-
31
- this.screen.append(this.component);
32
- }
33
-
34
- /**
35
- * Show blocking error overlay
36
- * @param {string[]} errors - List of error messages
37
- */
38
- show(errors) {
39
- if (!errors || errors.length === 0) {
40
- this.hide();
41
- return;
42
- }
43
-
44
- this.currentErrors = errors;
45
- this.timeLeft = 30;
46
-
47
- // Clear existing timer if any
48
- if (this.timer) {
49
- clearInterval(this.timer);
50
- }
51
-
52
- this.updateContent();
53
- this.component.show();
54
- this.component.setFront();
55
- this.screen.render();
56
-
57
- // Start countdown
58
- this.timer = setInterval(() => {
59
- this.timeLeft--;
60
- if (this.timeLeft < 0) this.timeLeft = 0;
61
- this.updateContent();
62
- }, 1000);
63
- }
64
-
65
- /**
66
- * Update overlay content with current timer
67
- */
68
- updateContent() {
69
- const content = `\n{bold}CRITICAL ERRORS:{/bold}\n\n` +
70
- this.currentErrors.map(e => `• ${e}`).join('\n\n') +
71
- `\n\n{center}Retrying in ${this.timeLeft}s...{/center}\n`;
72
-
73
- this.component.setContent(content);
74
- this.screen.render();
75
- }
76
-
77
- /**
78
- * Hide blocking error overlay
79
- */
80
- hide() {
81
- if (this.timer) {
82
- clearInterval(this.timer);
83
- this.timer = null;
84
- }
85
-
86
- if (!this.component.hidden) {
87
- this.component.hide();
88
- this.screen.render();
89
- }
90
- }
91
-
92
- /**
93
- * Check if overlay is visible
94
- * @returns {boolean} True if visible
95
- */
96
- isVisible() {
97
- return !this.component.hidden;
98
- }
99
- }
@@ -1,63 +0,0 @@
1
- import blessed from 'blessed';
2
- import { UI, COLORS, KEY_CODES } from '../../constants.js';
3
-
4
- export class InputBox {
5
- constructor(screen, onSend) {
6
- this.screen = screen;
7
- this.onSend = onSend;
8
-
9
- this.component = blessed.textarea({
10
- bottom: UI.STATUS_BAR_HEIGHT,
11
- left: 0,
12
- width: '100%',
13
- height: UI.INPUT_BOX_HEIGHT,
14
- inputOnFocus: true,
15
- keys: true,
16
- style: {
17
- fg: COLORS.FG_WHITE,
18
- bg: COLORS.BG_BLACK,
19
- border: {
20
- fg: COLORS.BORDER
21
- }
22
- },
23
- border: {
24
- type: 'line'
25
- }
26
- });
27
-
28
- this.setupEvents();
29
- this.screen.append(this.component);
30
- }
31
-
32
- setupEvents() {
33
- this.component.key('enter', () => {
34
- const message = this.component.getValue().trim();
35
- if (message) {
36
- this.onSend(message);
37
- this.component.clearValue();
38
- this.screen.render();
39
- }
40
- });
41
- }
42
-
43
- focus() {
44
- if (!this.disabled) {
45
- this.component.focus();
46
- }
47
- }
48
-
49
- disable() {
50
- this.disabled = true;
51
- this.component.inputOnFocus = false;
52
- // Optional: Change style to indicate disabled state
53
- this.component.style.border.fg = 'gray';
54
- this.screen.render();
55
- }
56
-
57
- enable() {
58
- this.disabled = false;
59
- this.component.inputOnFocus = true;
60
- this.component.style.border.fg = COLORS.BORDER;
61
- this.screen.render();
62
- }
63
- }
@@ -1,51 +0,0 @@
1
- import blessed from 'blessed';
2
- import { UI, COLORS } from '../../constants.js';
3
-
4
- export class MessageBox {
5
- constructor(screen) {
6
- this.screen = screen;
7
-
8
- this.component = blessed.box({
9
- top: UI.TOP_BAR_HEIGHT,
10
- left: 0,
11
- width: '100%',
12
- height: `100%-${UI.MESSAGE_BOX_OFFSET}`,
13
- scrollable: true,
14
- alwaysScroll: true,
15
- keys: true,
16
- vi: true,
17
- mouse: true,
18
- tags: true,
19
- scrollbar: {
20
- ch: UI.SCROLLBAR_CHAR,
21
- style: {
22
- bg: COLORS.BG_BLUE
23
- }
24
- },
25
- style: {
26
- fg: COLORS.FG_WHITE,
27
- bg: COLORS.BG_BLACK
28
- }
29
- });
30
-
31
- this.screen.append(this.component);
32
- }
33
-
34
- addMessage(formattedLine) {
35
- const current = this.component.getContent();
36
- const next = current && current.length > 0 ? `${current}\n${formattedLine}` : formattedLine;
37
- this.component.setContent(next);
38
- this.component.setScrollPerc(100);
39
- this.screen.render();
40
- }
41
-
42
- scrollUp() {
43
- this.component.scroll(-1);
44
- this.screen.render();
45
- }
46
-
47
- scrollDown() {
48
- this.component.scroll(1);
49
- this.screen.render();
50
- }
51
- }
@@ -1,32 +0,0 @@
1
- import blessed from 'blessed';
2
- import { UI, COLORS } from '../../constants.js';
3
-
4
- export class StatusBar {
5
- constructor(screen) {
6
- this.screen = screen;
7
-
8
- this.component = blessed.box({
9
- bottom: 0,
10
- left: 0,
11
- width: '100%',
12
- height: UI.STATUS_BAR_HEIGHT,
13
- content: ' Ready',
14
- tags: true,
15
- style: {
16
- fg: COLORS.FG_WHITE,
17
- bg: COLORS.BG_BLUE
18
- }
19
- });
20
-
21
- this.screen.append(this.component);
22
- }
23
-
24
- update(message, type = 'info') {
25
- let color = COLORS.INFO;
26
- if (type === 'error') color = COLORS.ERROR;
27
- if (type === 'success') color = COLORS.SUCCESS;
28
-
29
- this.component.setContent(` {${color}}${message}{/${color}}`);
30
- this.screen.render();
31
- }
32
- }
@@ -1,63 +0,0 @@
1
- import blessed from 'blessed';
2
- import { UI, COLORS, ICONS } from '../../constants.js';
3
- import { parseRpcHost, formatTimestamp } from '../../utils.js';
4
-
5
- export class TopBar {
6
- constructor(screen, config, myAddress) {
7
- this.screen = screen;
8
- this.config = config;
9
- this.myAddress = myAddress;
10
- this.totalMessages = 0;
11
- this.encryptionType = 'N/A';
12
-
13
- this.component = blessed.box({
14
- top: 0,
15
- left: 0,
16
- width: '100%',
17
- height: UI.TOP_BAR_HEIGHT,
18
- content: 'Loading...',
19
- tags: true,
20
- style: {
21
- fg: COLORS.FG_WHITE,
22
- bg: COLORS.BG_BLUE
23
- }
24
- });
25
-
26
- this.screen.append(this.component);
27
- }
28
-
29
- update(status) {
30
- const rpcUrl = parseRpcHost(this.config.rpc_url);
31
- const connectedIndicator = status.connected ?
32
- `{${COLORS.CONNECTED}}${ICONS.CONNECTED}{/${COLORS.CONNECTED}}` :
33
- `{${COLORS.DISCONNECTED}}${ICONS.DISCONNECTED}{/${COLORS.DISCONNECTED}}`;
34
-
35
- const lastPollStr = status.lastPoll ?
36
- formatTimestamp(status.lastPoll, this.config.timezone) :
37
- '--:--:--';
38
-
39
- // Format timezone display
40
- let timezoneDisplay = this.config.timezone || 'UTC';
41
- if (timezoneDisplay !== 'UTC') {
42
- if (!timezoneDisplay.startsWith('+') && !timezoneDisplay.startsWith('-')) {
43
- timezoneDisplay = `+${timezoneDisplay}`;
44
- }
45
- timezoneDisplay = `UTC${timezoneDisplay}`;
46
- }
47
-
48
- this.component.setContent(
49
- `Neurai DePIN | ${connectedIndicator} RPC: ${rpcUrl} | Token: ${this.config.token} | Time: ${timezoneDisplay}\n` +
50
- `Address: ${this.myAddress} | Total: ${this.totalMessages} | Encryption: ${this.encryptionType} | Last poll: ${lastPollStr}`
51
- );
52
-
53
- this.screen.render();
54
- }
55
-
56
- setTotalMessages(count) {
57
- this.totalMessages = count;
58
- }
59
-
60
- setEncryptionType(type) {
61
- this.encryptionType = type;
62
- }
63
- }