arc402-cli 0.5.0 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +6 -0
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +323 -20
- package/dist/commands/wallet.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +447 -148
- package/dist/repl.js.map +1 -1
- package/dist/ui/banner.d.ts +2 -0
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +27 -18
- package/dist/ui/banner.js.map +1 -1
- package/dist/ui/spinner.d.ts.map +1 -1
- package/dist/ui/spinner.js +11 -0
- package/dist/ui/spinner.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/config.ts +6 -0
- package/src/commands/wallet.ts +372 -32
- package/src/config.ts +1 -0
- package/src/index.ts +15 -1
- package/src/repl.ts +531 -179
- package/src/ui/banner.ts +26 -19
- package/src/ui/spinner.ts +10 -0
package/dist/repl.js
CHANGED
|
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.startREPL = startREPL;
|
|
7
|
-
const node_readline_1 = __importDefault(require("node:readline"));
|
|
8
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
8
|
const fs_1 = __importDefault(require("fs"));
|
|
10
9
|
const path_1 = __importDefault(require("path"));
|
|
@@ -12,14 +11,7 @@ const os_1 = __importDefault(require("os"));
|
|
|
12
11
|
const program_1 = require("./program");
|
|
13
12
|
const banner_1 = require("./ui/banner");
|
|
14
13
|
const colors_1 = require("./ui/colors");
|
|
15
|
-
// ───
|
|
16
|
-
const PROMPT = chalk_1.default.cyanBright("◈") +
|
|
17
|
-
" " +
|
|
18
|
-
chalk_1.default.dim("arc402") +
|
|
19
|
-
" " +
|
|
20
|
-
chalk_1.default.white(">") +
|
|
21
|
-
" ";
|
|
22
|
-
// ─── Sentinel thrown to intercept process.exit() from commands ───────────────
|
|
14
|
+
// ─── Sentinel to intercept process.exit() from commands ──────────────────────
|
|
23
15
|
class REPLExitSignal extends Error {
|
|
24
16
|
constructor(code = 0) {
|
|
25
17
|
super("repl-exit-signal");
|
|
@@ -59,45 +51,34 @@ async function loadBannerConfig() {
|
|
|
59
51
|
return undefined;
|
|
60
52
|
}
|
|
61
53
|
}
|
|
62
|
-
// ───
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.log(` ${chalk_1.default.dim("Wallet")} ${chalk_1.default.white(`${w.slice(0, 6)}...${w.slice(-4)}`)}`);
|
|
78
|
-
}
|
|
79
|
-
if (raw.rpcUrl && raw.walletContractAddress) {
|
|
80
|
-
try {
|
|
81
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
82
|
-
const ethersLib = require("ethers");
|
|
83
|
-
const provider = new ethersLib.ethers.JsonRpcProvider(raw.rpcUrl);
|
|
84
|
-
const bal = await Promise.race([
|
|
85
|
-
provider.getBalance(raw.walletContractAddress),
|
|
86
|
-
new Promise((_, r) => setTimeout(() => r(new Error("timeout")), 2000)),
|
|
87
|
-
]);
|
|
88
|
-
console.log(` ${chalk_1.default.dim("Balance")} ${chalk_1.default.white(`${parseFloat(ethersLib.ethers.formatEther(bal)).toFixed(4)} ETH`)}`);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
/* skip */
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
/* skip */
|
|
97
|
-
}
|
|
98
|
-
console.log();
|
|
54
|
+
// ─── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
55
|
+
const ESC = "\x1b";
|
|
56
|
+
const ansi = {
|
|
57
|
+
clearScreen: `${ESC}[2J`,
|
|
58
|
+
home: `${ESC}[H`,
|
|
59
|
+
clearLine: `${ESC}[2K`,
|
|
60
|
+
clearToEol: `${ESC}[K`,
|
|
61
|
+
hideCursor: `${ESC}[?25l`,
|
|
62
|
+
showCursor: `${ESC}[?25h`,
|
|
63
|
+
move: (r, col) => `${ESC}[${r};${col}H`,
|
|
64
|
+
scrollRegion: (top, bot) => `${ESC}[${top};${bot}r`,
|
|
65
|
+
resetScroll: `${ESC}[r`,
|
|
66
|
+
};
|
|
67
|
+
function write(s) {
|
|
68
|
+
process.stdout.write(s);
|
|
99
69
|
}
|
|
100
|
-
// ───
|
|
70
|
+
// ─── Prompt constants ─────────────────────────────────────────────────────────
|
|
71
|
+
const PROMPT_TEXT = chalk_1.default.cyanBright("◈") +
|
|
72
|
+
" " +
|
|
73
|
+
chalk_1.default.dim("arc402") +
|
|
74
|
+
" " +
|
|
75
|
+
chalk_1.default.white(">") +
|
|
76
|
+
" ";
|
|
77
|
+
// Visible character count of "◈ arc402 > "
|
|
78
|
+
const PROMPT_VIS = 11;
|
|
79
|
+
// ─── Known command detection ──────────────────────────────────────────────────
|
|
80
|
+
const BUILTIN_CMDS = ["help", "exit", "quit", "clear", "status"];
|
|
81
|
+
// ─── Shell-style tokenizer ────────────────────────────────────────────────────
|
|
101
82
|
function parseTokens(input) {
|
|
102
83
|
const tokens = [];
|
|
103
84
|
let current = "";
|
|
@@ -105,12 +86,10 @@ function parseTokens(input) {
|
|
|
105
86
|
let quoteChar = "";
|
|
106
87
|
for (const ch of input) {
|
|
107
88
|
if (inQuote) {
|
|
108
|
-
if (ch === quoteChar)
|
|
89
|
+
if (ch === quoteChar)
|
|
109
90
|
inQuote = false;
|
|
110
|
-
|
|
111
|
-
else {
|
|
91
|
+
else
|
|
112
92
|
current += ch;
|
|
113
|
-
}
|
|
114
93
|
}
|
|
115
94
|
else if (ch === '"' || ch === "'") {
|
|
116
95
|
inQuote = true;
|
|
@@ -130,107 +109,335 @@ function parseTokens(input) {
|
|
|
130
109
|
tokens.push(current);
|
|
131
110
|
return tokens;
|
|
132
111
|
}
|
|
133
|
-
// ─── Tab
|
|
134
|
-
function
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// Completing a subcommand
|
|
146
|
-
const parent = trimmed.slice(0, spaceIdx);
|
|
147
|
-
const rest = trimmed.slice(spaceIdx + 1);
|
|
148
|
-
const subs = subCmds.get(parent) ?? [];
|
|
149
|
-
const hits = subs.filter((s) => s.startsWith(rest));
|
|
150
|
-
return [
|
|
151
|
-
hits.map((s) => `${parent} ${s}`),
|
|
152
|
-
trimmed,
|
|
153
|
-
];
|
|
154
|
-
};
|
|
112
|
+
// ─── Tab completion logic ─────────────────────────────────────────────────────
|
|
113
|
+
function getCompletions(line, topCmds, subCmds) {
|
|
114
|
+
const allTop = [...BUILTIN_CMDS, ...topCmds];
|
|
115
|
+
const trimmed = line.trimStart();
|
|
116
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
117
|
+
if (spaceIdx === -1) {
|
|
118
|
+
return allTop.filter((cmd) => cmd.startsWith(trimmed));
|
|
119
|
+
}
|
|
120
|
+
const parent = trimmed.slice(0, spaceIdx);
|
|
121
|
+
const rest = trimmed.slice(spaceIdx + 1);
|
|
122
|
+
const subs = subCmds.get(parent) ?? [];
|
|
123
|
+
return subs.filter((s) => s.startsWith(rest)).map((s) => `${parent} ${s}`);
|
|
155
124
|
}
|
|
156
|
-
// ───
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
125
|
+
// ─── TUI class ────────────────────────────────────────────────────────────────
|
|
126
|
+
class TUI {
|
|
127
|
+
constructor() {
|
|
128
|
+
this.inputBuffer = "";
|
|
129
|
+
this.cursorPos = 0;
|
|
130
|
+
this.history = [];
|
|
131
|
+
this.historyIdx = -1;
|
|
132
|
+
this.historyTemp = "";
|
|
133
|
+
this.bannerLines = [];
|
|
134
|
+
this.topCmds = [];
|
|
135
|
+
this.subCmds = new Map();
|
|
136
|
+
this.commandRunning = false;
|
|
137
|
+
// ── Key handler ──────────────────────────────────────────────────────────────
|
|
138
|
+
this.boundKeyHandler = (key) => {
|
|
139
|
+
if (this.commandRunning)
|
|
140
|
+
return;
|
|
141
|
+
this.handleKey(key);
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
get termRows() {
|
|
145
|
+
return process.stdout.rows || 24;
|
|
146
|
+
}
|
|
147
|
+
get termCols() {
|
|
148
|
+
return process.stdout.columns || 80;
|
|
149
|
+
}
|
|
150
|
+
get scrollTop() {
|
|
151
|
+
// +1 for separator row after banner
|
|
152
|
+
return this.bannerLines.length + 2;
|
|
153
|
+
}
|
|
154
|
+
get scrollBot() {
|
|
155
|
+
return this.termRows - 1;
|
|
156
|
+
}
|
|
157
|
+
get inputRow() {
|
|
158
|
+
return this.termRows;
|
|
159
|
+
}
|
|
160
|
+
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
161
|
+
async start() {
|
|
162
|
+
this.bannerCfg = await loadBannerConfig();
|
|
163
|
+
// Build command metadata for completion
|
|
164
|
+
const template = (0, program_1.createProgram)();
|
|
165
|
+
this.topCmds = template.commands.map((cmd) => cmd.name());
|
|
166
|
+
for (const cmd of template.commands) {
|
|
167
|
+
if (cmd.commands.length > 0) {
|
|
168
|
+
this.subCmds.set(cmd.name(), cmd.commands.map((s) => s.name()));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Draw initial screen
|
|
172
|
+
this.setupScreen();
|
|
173
|
+
this.drawInputLine();
|
|
174
|
+
// Enter raw mode
|
|
175
|
+
if (process.stdin.isTTY) {
|
|
176
|
+
process.stdin.setRawMode(true);
|
|
168
177
|
}
|
|
178
|
+
process.stdin.resume();
|
|
179
|
+
process.stdin.setEncoding("utf8");
|
|
180
|
+
process.stdin.on("data", this.boundKeyHandler);
|
|
181
|
+
// Resize handler
|
|
182
|
+
process.stdout.on("resize", () => {
|
|
183
|
+
this.setupScreen();
|
|
184
|
+
this.drawInputLine();
|
|
185
|
+
});
|
|
186
|
+
// SIGINT (shouldn't fire in raw mode, but just in case)
|
|
187
|
+
process.on("SIGINT", () => this.exitGracefully());
|
|
188
|
+
// Keep alive — process.stdin listener keeps the event loop running
|
|
189
|
+
await new Promise(() => {
|
|
190
|
+
/* never resolves; process.exit() is called on quit */
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// ── Screen setup ────────────────────────────────────────────────────────────
|
|
194
|
+
setupScreen() {
|
|
195
|
+
this.bannerLines = (0, banner_1.getBannerLines)(this.bannerCfg);
|
|
196
|
+
write(ansi.hideCursor);
|
|
197
|
+
write(ansi.clearScreen + ansi.home);
|
|
198
|
+
// Banner
|
|
199
|
+
for (const line of this.bannerLines) {
|
|
200
|
+
write(line + "\n");
|
|
201
|
+
}
|
|
202
|
+
// Separator between banner and output area
|
|
203
|
+
write(chalk_1.default.dim("─".repeat(this.termCols)) + "\n");
|
|
204
|
+
// Set scroll region (output area, leaves last row free for input)
|
|
205
|
+
if (this.scrollTop <= this.scrollBot) {
|
|
206
|
+
write(ansi.scrollRegion(this.scrollTop, this.scrollBot));
|
|
207
|
+
}
|
|
208
|
+
// Position cursor at top of output area
|
|
209
|
+
write(ansi.move(this.scrollTop, 1));
|
|
210
|
+
write(ansi.showCursor);
|
|
169
211
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
212
|
+
// ── Banner repaint (in-place, preserves output area) ────────────────────────
|
|
213
|
+
repaintBanner() {
|
|
214
|
+
write(ansi.hideCursor);
|
|
215
|
+
for (let i = 0; i < this.bannerLines.length; i++) {
|
|
216
|
+
write(ansi.move(i + 1, 1) + ansi.clearToEol + this.bannerLines[i]);
|
|
217
|
+
}
|
|
218
|
+
// Separator
|
|
219
|
+
const sepRow = this.bannerLines.length + 1;
|
|
220
|
+
write(ansi.move(sepRow, 1) +
|
|
221
|
+
ansi.clearToEol +
|
|
222
|
+
chalk_1.default.dim("─".repeat(this.termCols)));
|
|
223
|
+
write(ansi.showCursor);
|
|
180
224
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
225
|
+
// ── Input line ───────────────────────────────────────────────────────────────
|
|
226
|
+
drawInputLine() {
|
|
227
|
+
write(ansi.move(this.inputRow, 1) + ansi.clearLine);
|
|
228
|
+
write(PROMPT_TEXT + this.inputBuffer);
|
|
229
|
+
// Place cursor at correct position within the input
|
|
230
|
+
write(ansi.move(this.inputRow, PROMPT_VIS + 1 + this.cursorPos));
|
|
231
|
+
}
|
|
232
|
+
handleKey(key) {
|
|
233
|
+
// Ctrl+C
|
|
234
|
+
if (key === "\u0003") {
|
|
235
|
+
this.exitGracefully();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// Ctrl+L — refresh
|
|
239
|
+
if (key === "\u000C") {
|
|
240
|
+
this.setupScreen();
|
|
241
|
+
this.drawInputLine();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Enter
|
|
245
|
+
if (key === "\r" || key === "\n") {
|
|
246
|
+
void this.submit();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// Backspace
|
|
250
|
+
if (key === "\u007F" || key === "\b") {
|
|
251
|
+
if (this.cursorPos > 0) {
|
|
252
|
+
this.inputBuffer =
|
|
253
|
+
this.inputBuffer.slice(0, this.cursorPos - 1) +
|
|
254
|
+
this.inputBuffer.slice(this.cursorPos);
|
|
255
|
+
this.cursorPos--;
|
|
256
|
+
this.drawInputLine();
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// Delete (forward)
|
|
261
|
+
if (key === "\x1b[3~") {
|
|
262
|
+
if (this.cursorPos < this.inputBuffer.length) {
|
|
263
|
+
this.inputBuffer =
|
|
264
|
+
this.inputBuffer.slice(0, this.cursorPos) +
|
|
265
|
+
this.inputBuffer.slice(this.cursorPos + 1);
|
|
266
|
+
this.drawInputLine();
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// Up arrow — history prev
|
|
271
|
+
if (key === "\x1b[A") {
|
|
272
|
+
if (this.historyIdx === -1) {
|
|
273
|
+
this.historyTemp = this.inputBuffer;
|
|
274
|
+
this.historyIdx = this.history.length - 1;
|
|
275
|
+
}
|
|
276
|
+
else if (this.historyIdx > 0) {
|
|
277
|
+
this.historyIdx--;
|
|
278
|
+
}
|
|
279
|
+
if (this.historyIdx >= 0) {
|
|
280
|
+
this.inputBuffer = this.history[this.historyIdx];
|
|
281
|
+
this.cursorPos = this.inputBuffer.length;
|
|
282
|
+
this.drawInputLine();
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// Down arrow — history next
|
|
287
|
+
if (key === "\x1b[B") {
|
|
288
|
+
if (this.historyIdx >= 0) {
|
|
289
|
+
this.historyIdx++;
|
|
290
|
+
if (this.historyIdx >= this.history.length) {
|
|
291
|
+
this.historyIdx = -1;
|
|
292
|
+
this.inputBuffer = this.historyTemp;
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
this.inputBuffer = this.history[this.historyIdx];
|
|
296
|
+
}
|
|
297
|
+
this.cursorPos = this.inputBuffer.length;
|
|
298
|
+
this.drawInputLine();
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
// Right arrow
|
|
303
|
+
if (key === "\x1b[C") {
|
|
304
|
+
if (this.cursorPos < this.inputBuffer.length) {
|
|
305
|
+
this.cursorPos++;
|
|
306
|
+
this.drawInputLine();
|
|
307
|
+
}
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
// Left arrow
|
|
311
|
+
if (key === "\x1b[D") {
|
|
312
|
+
if (this.cursorPos > 0) {
|
|
313
|
+
this.cursorPos--;
|
|
314
|
+
this.drawInputLine();
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
// Home / Ctrl+A
|
|
319
|
+
if (key === "\x1b[H" || key === "\u0001") {
|
|
320
|
+
this.cursorPos = 0;
|
|
321
|
+
this.drawInputLine();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
// End / Ctrl+E
|
|
325
|
+
if (key === "\x1b[F" || key === "\u0005") {
|
|
326
|
+
this.cursorPos = this.inputBuffer.length;
|
|
327
|
+
this.drawInputLine();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
// Ctrl+U — clear line
|
|
331
|
+
if (key === "\u0015") {
|
|
332
|
+
this.inputBuffer = "";
|
|
333
|
+
this.cursorPos = 0;
|
|
334
|
+
this.drawInputLine();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// Ctrl+K — kill to end
|
|
338
|
+
if (key === "\u000B") {
|
|
339
|
+
this.inputBuffer = this.inputBuffer.slice(0, this.cursorPos);
|
|
340
|
+
this.drawInputLine();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// Tab — completion
|
|
344
|
+
if (key === "\t") {
|
|
345
|
+
this.handleTab();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
// Printable characters
|
|
349
|
+
if (key >= " " && !key.startsWith("\x1b")) {
|
|
350
|
+
this.inputBuffer =
|
|
351
|
+
this.inputBuffer.slice(0, this.cursorPos) +
|
|
352
|
+
key +
|
|
353
|
+
this.inputBuffer.slice(this.cursorPos);
|
|
354
|
+
this.cursorPos += key.length;
|
|
355
|
+
this.drawInputLine();
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// ── Tab completion ───────────────────────────────────────────────────────────
|
|
359
|
+
handleTab() {
|
|
360
|
+
const completions = getCompletions(this.inputBuffer, this.topCmds, this.subCmds);
|
|
361
|
+
if (completions.length === 0)
|
|
362
|
+
return;
|
|
363
|
+
if (completions.length === 1) {
|
|
364
|
+
this.inputBuffer = completions[0] + " ";
|
|
365
|
+
this.cursorPos = this.inputBuffer.length;
|
|
366
|
+
this.drawInputLine();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
// Find common prefix
|
|
370
|
+
const common = completions.reduce((a, b) => {
|
|
371
|
+
let i = 0;
|
|
372
|
+
while (i < a.length && i < b.length && a[i] === b[i])
|
|
373
|
+
i++;
|
|
374
|
+
return a.slice(0, i);
|
|
375
|
+
});
|
|
376
|
+
if (common.length > this.inputBuffer.trimStart().length) {
|
|
377
|
+
this.inputBuffer = common;
|
|
378
|
+
this.cursorPos = common.length;
|
|
379
|
+
}
|
|
380
|
+
// Show options in output area
|
|
381
|
+
this.writeOutput("\n" + chalk_1.default.dim(completions.join(" ")) + "\n");
|
|
382
|
+
this.drawInputLine();
|
|
383
|
+
}
|
|
384
|
+
// ── Write to output area ─────────────────────────────────────────────────────
|
|
385
|
+
writeOutput(text) {
|
|
386
|
+
// Move cursor to bottom of scroll region to ensure scroll-down works
|
|
387
|
+
write(ansi.move(this.scrollBot, 1));
|
|
388
|
+
write(text);
|
|
389
|
+
}
|
|
390
|
+
// ── Submit line ──────────────────────────────────────────────────────────────
|
|
391
|
+
async submit() {
|
|
392
|
+
const input = this.inputBuffer.trim();
|
|
393
|
+
this.inputBuffer = "";
|
|
394
|
+
this.cursorPos = 0;
|
|
395
|
+
this.historyIdx = -1;
|
|
194
396
|
if (!input) {
|
|
195
|
-
|
|
196
|
-
|
|
397
|
+
this.drawInputLine();
|
|
398
|
+
return;
|
|
197
399
|
}
|
|
198
|
-
//
|
|
400
|
+
// Add to history
|
|
401
|
+
if (input !== this.history[this.history.length - 1]) {
|
|
402
|
+
this.history.push(input);
|
|
403
|
+
}
|
|
404
|
+
// Echo the input into the output area
|
|
405
|
+
this.writeOutput("\n" + chalk_1.default.dim("◈ ") + chalk_1.default.white(input) + "\n");
|
|
406
|
+
// ── Built-in commands ──────────────────────────────────────────────────────
|
|
199
407
|
if (input === "exit" || input === "quit") {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
process.exit(0);
|
|
408
|
+
this.exitGracefully();
|
|
409
|
+
return;
|
|
203
410
|
}
|
|
204
411
|
if (input === "clear") {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
continue;
|
|
412
|
+
this.bannerCfg = await loadBannerConfig();
|
|
413
|
+
this.setupScreen();
|
|
414
|
+
this.drawInputLine();
|
|
415
|
+
return;
|
|
210
416
|
}
|
|
211
417
|
if (input === "status") {
|
|
212
|
-
await
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
if (input === "help"
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
prog.configureOutput({
|
|
221
|
-
writeOut: (str) => process.stdout.write(str),
|
|
222
|
-
writeErr: (str) => process.stderr.write(str),
|
|
223
|
-
});
|
|
224
|
-
try {
|
|
225
|
-
await prog.parseAsync(["node", "arc402", "--help"]);
|
|
226
|
-
}
|
|
227
|
-
catch {
|
|
228
|
-
/* commander throws after printing help — ignore */
|
|
229
|
-
}
|
|
230
|
-
rl.prompt();
|
|
231
|
-
continue;
|
|
418
|
+
await this.runStatus();
|
|
419
|
+
this.afterCommand();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (input === "help") {
|
|
423
|
+
await this.runHelp();
|
|
424
|
+
this.afterCommand();
|
|
425
|
+
return;
|
|
232
426
|
}
|
|
233
|
-
// ──
|
|
427
|
+
// ── Chat mode detection ────────────────────────────────────────────────────
|
|
428
|
+
const firstWord = input.split(/\s+/)[0];
|
|
429
|
+
const allKnown = [...BUILTIN_CMDS, ...this.topCmds];
|
|
430
|
+
if (!allKnown.includes(firstWord)) {
|
|
431
|
+
this.writeOutput(chalk_1.default.dim("\n ◈ Chat coming soon — type a command or help\n"));
|
|
432
|
+
this.afterCommand();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
// ── Dispatch to commander ──────────────────────────────────────────────────
|
|
436
|
+
this.commandRunning = true;
|
|
437
|
+
// Move output cursor to bottom of scroll region
|
|
438
|
+
write(ansi.move(this.scrollBot, 1));
|
|
439
|
+
// Suspend TUI stdin so interactive commands (prompts, readline) work cleanly
|
|
440
|
+
process.stdin.removeListener("data", this.boundKeyHandler);
|
|
234
441
|
const tokens = parseTokens(input);
|
|
235
442
|
const prog = (0, program_1.createProgram)();
|
|
236
443
|
prog.exitOverride();
|
|
@@ -238,7 +445,6 @@ async function startREPL() {
|
|
|
238
445
|
writeOut: (str) => process.stdout.write(str),
|
|
239
446
|
writeErr: (str) => process.stderr.write(str),
|
|
240
447
|
});
|
|
241
|
-
// Intercept process.exit() so a command exiting doesn't kill the REPL
|
|
242
448
|
const origExit = process.exit;
|
|
243
449
|
process.exit = ((code) => {
|
|
244
450
|
throw new REPLExitSignal(code ?? 0);
|
|
@@ -248,30 +454,123 @@ async function startREPL() {
|
|
|
248
454
|
}
|
|
249
455
|
catch (err) {
|
|
250
456
|
if (err instanceof REPLExitSignal) {
|
|
251
|
-
// Command called process.exit() — normal
|
|
457
|
+
// Command called process.exit() — normal
|
|
252
458
|
}
|
|
253
459
|
else {
|
|
254
460
|
const e = err;
|
|
255
461
|
if (e.code === "commander.helpDisplayed" ||
|
|
256
462
|
e.code === "commander.version") {
|
|
257
|
-
//
|
|
463
|
+
// already written
|
|
258
464
|
}
|
|
259
465
|
else if (e.code === "commander.unknownCommand") {
|
|
260
|
-
|
|
261
|
-
|
|
466
|
+
process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(`Unknown command: ${chalk_1.default.white(tokens[0])}`)} \n`);
|
|
467
|
+
process.stdout.write(chalk_1.default.dim(" Type 'help' for available commands\n"));
|
|
262
468
|
}
|
|
263
469
|
else if (e.code?.startsWith("commander.")) {
|
|
264
|
-
|
|
470
|
+
process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(e.message ?? String(err))}\n`);
|
|
265
471
|
}
|
|
266
472
|
else {
|
|
267
|
-
|
|
473
|
+
process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(e.message ?? String(err))}\n`);
|
|
268
474
|
}
|
|
269
475
|
}
|
|
270
476
|
}
|
|
271
477
|
finally {
|
|
272
478
|
process.exit = origExit;
|
|
273
479
|
}
|
|
274
|
-
|
|
480
|
+
// Restore raw mode + our listener (interactive commands may have toggled it)
|
|
481
|
+
if (process.stdin.isTTY) {
|
|
482
|
+
process.stdin.setRawMode(true);
|
|
483
|
+
}
|
|
484
|
+
process.stdin.on("data", this.boundKeyHandler);
|
|
485
|
+
this.commandRunning = false;
|
|
486
|
+
this.afterCommand();
|
|
487
|
+
}
|
|
488
|
+
// ── After each command: repaint banner + input ───────────────────────────────
|
|
489
|
+
afterCommand() {
|
|
490
|
+
this.repaintBanner();
|
|
491
|
+
this.drawInputLine();
|
|
492
|
+
}
|
|
493
|
+
// ── Built-in: status ─────────────────────────────────────────────────────────
|
|
494
|
+
async runStatus() {
|
|
495
|
+
write(ansi.move(this.scrollBot, 1));
|
|
496
|
+
if (!fs_1.default.existsSync(CONFIG_PATH)) {
|
|
497
|
+
process.stdout.write(chalk_1.default.dim("\n No config found. Run 'config init' to get started.\n"));
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
try {
|
|
501
|
+
const raw = JSON.parse(fs_1.default.readFileSync(CONFIG_PATH, "utf-8"));
|
|
502
|
+
process.stdout.write("\n");
|
|
503
|
+
if (raw.network)
|
|
504
|
+
process.stdout.write(` ${chalk_1.default.dim("Network")} ${chalk_1.default.white(raw.network)}\n`);
|
|
505
|
+
if (raw.walletContractAddress) {
|
|
506
|
+
const w = raw.walletContractAddress;
|
|
507
|
+
process.stdout.write(` ${chalk_1.default.dim("Wallet")} ${chalk_1.default.white(`${w.slice(0, 6)}...${w.slice(-4)}`)}\n`);
|
|
508
|
+
}
|
|
509
|
+
if (raw.rpcUrl && raw.walletContractAddress) {
|
|
510
|
+
try {
|
|
511
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
512
|
+
const ethersLib = require("ethers");
|
|
513
|
+
const provider = new ethersLib.ethers.JsonRpcProvider(raw.rpcUrl);
|
|
514
|
+
const bal = await Promise.race([
|
|
515
|
+
provider.getBalance(raw.walletContractAddress),
|
|
516
|
+
new Promise((_, r) => setTimeout(() => r(new Error("timeout")), 2000)),
|
|
517
|
+
]);
|
|
518
|
+
process.stdout.write(` ${chalk_1.default.dim("Balance")} ${chalk_1.default.white(`${parseFloat(ethersLib.ethers.formatEther(bal)).toFixed(4)} ETH`)}\n`);
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
/* skip */
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
process.stdout.write("\n");
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
/* skip */
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
// ── Built-in: help ────────────────────────────────────────────────────────────
|
|
531
|
+
async runHelp() {
|
|
532
|
+
write(ansi.move(this.scrollBot, 1));
|
|
533
|
+
process.stdin.removeListener("data", this.boundKeyHandler);
|
|
534
|
+
const prog = (0, program_1.createProgram)();
|
|
535
|
+
prog.exitOverride();
|
|
536
|
+
prog.configureOutput({
|
|
537
|
+
writeOut: (str) => process.stdout.write(str),
|
|
538
|
+
writeErr: (str) => process.stderr.write(str),
|
|
539
|
+
});
|
|
540
|
+
try {
|
|
541
|
+
await prog.parseAsync(["node", "arc402", "--help"]);
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
/* commander throws after printing help */
|
|
545
|
+
}
|
|
546
|
+
if (process.stdin.isTTY)
|
|
547
|
+
process.stdin.setRawMode(true);
|
|
548
|
+
process.stdin.on("data", this.boundKeyHandler);
|
|
549
|
+
}
|
|
550
|
+
// ── Exit ──────────────────────────────────────────────────────────────────────
|
|
551
|
+
exitGracefully() {
|
|
552
|
+
write(ansi.move(this.inputRow, 1) + ansi.clearLine);
|
|
553
|
+
write(" " + chalk_1.default.cyanBright("◈") + chalk_1.default.dim(" goodbye") + "\n");
|
|
554
|
+
write(ansi.resetScroll);
|
|
555
|
+
write(ansi.showCursor);
|
|
556
|
+
if (process.stdin.isTTY) {
|
|
557
|
+
process.stdin.setRawMode(false);
|
|
558
|
+
}
|
|
559
|
+
process.exit(0);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// ─── REPL entry point ─────────────────────────────────────────────────────────
|
|
563
|
+
async function startREPL() {
|
|
564
|
+
if (!process.stdout.isTTY) {
|
|
565
|
+
// Non-TTY (piped): fall back to minimal line-mode output
|
|
566
|
+
const bannerCfg = await loadBannerConfig();
|
|
567
|
+
for (const line of (0, banner_1.getBannerLines)(bannerCfg)) {
|
|
568
|
+
process.stdout.write(line + "\n");
|
|
569
|
+
}
|
|
570
|
+
process.stdout.write("Interactive TUI requires a TTY. Use arc402 <command> directly.\n");
|
|
571
|
+
return;
|
|
275
572
|
}
|
|
573
|
+
const tui = new TUI();
|
|
574
|
+
await tui.start();
|
|
276
575
|
}
|
|
277
576
|
//# sourceMappingURL=repl.js.map
|