arc402-cli 0.7.3 → 0.7.4
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/TUI-SPEC.md +214 -0
- package/dist/index.js +55 -1
- package/dist/index.js.map +1 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +46 -558
- package/dist/repl.js.map +1 -1
- package/dist/tui/App.d.ts +12 -0
- package/dist/tui/App.d.ts.map +1 -0
- package/dist/tui/App.js +154 -0
- package/dist/tui/App.js.map +1 -0
- package/dist/tui/Footer.d.ts +11 -0
- package/dist/tui/Footer.d.ts.map +1 -0
- package/dist/tui/Footer.js +13 -0
- package/dist/tui/Footer.js.map +1 -0
- package/dist/tui/Header.d.ts +14 -0
- package/dist/tui/Header.d.ts.map +1 -0
- package/dist/tui/Header.js +19 -0
- package/dist/tui/Header.js.map +1 -0
- package/dist/tui/InputLine.d.ts +11 -0
- package/dist/tui/InputLine.d.ts.map +1 -0
- package/dist/tui/InputLine.js +145 -0
- package/dist/tui/InputLine.js.map +1 -0
- package/dist/tui/Viewport.d.ts +14 -0
- package/dist/tui/Viewport.d.ts.map +1 -0
- package/dist/tui/Viewport.js +48 -0
- package/dist/tui/Viewport.js.map +1 -0
- package/dist/tui/index.d.ts +2 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +55 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/useChat.d.ts +11 -0
- package/dist/tui/useChat.d.ts.map +1 -0
- package/dist/tui/useChat.js +91 -0
- package/dist/tui/useChat.js.map +1 -0
- package/dist/tui/useCommand.d.ts +12 -0
- package/dist/tui/useCommand.d.ts.map +1 -0
- package/dist/tui/useCommand.js +137 -0
- package/dist/tui/useCommand.js.map +1 -0
- package/dist/tui/useScroll.d.ts +17 -0
- package/dist/tui/useScroll.d.ts.map +1 -0
- package/dist/tui/useScroll.js +46 -0
- package/dist/tui/useScroll.js.map +1 -0
- package/package.json +5 -1
- package/src/index.ts +21 -1
- package/src/repl.ts +50 -663
- package/src/tui/App.tsx +214 -0
- package/src/tui/Footer.tsx +18 -0
- package/src/tui/Header.tsx +30 -0
- package/src/tui/InputLine.tsx +164 -0
- package/src/tui/Viewport.tsx +70 -0
- package/src/tui/index.tsx +72 -0
- package/src/tui/useChat.ts +103 -0
- package/src/tui/useCommand.ts +148 -0
- package/src/tui/useScroll.ts +65 -0
- package/tsconfig.json +6 -1
package/dist/repl.js
CHANGED
|
@@ -8,13 +8,12 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const readline_1 = __importDefault(require("readline"));
|
|
11
12
|
const program_1 = require("./program");
|
|
12
13
|
const banner_1 = require("./ui/banner");
|
|
13
14
|
const colors_1 = require("./ui/colors");
|
|
14
|
-
// ─── Config
|
|
15
|
+
// ─── Config helpers ────────────────────────────────────────────────────────────
|
|
15
16
|
const CONFIG_PATH = path_1.default.join(os_1.default.homedir(), ".arc402", "config.json");
|
|
16
|
-
const HISTORY_PATH = path_1.default.join(os_1.default.homedir(), ".arc402", "repl_history");
|
|
17
|
-
const MAX_HISTORY = 1000;
|
|
18
17
|
async function loadBannerConfig() {
|
|
19
18
|
if (!fs_1.default.existsSync(CONFIG_PATH))
|
|
20
19
|
return undefined;
|
|
@@ -25,54 +24,19 @@ async function loadBannerConfig() {
|
|
|
25
24
|
const w = raw.walletContractAddress;
|
|
26
25
|
cfg.wallet = `${w.slice(0, 6)}...${w.slice(-4)}`;
|
|
27
26
|
}
|
|
28
|
-
if (raw.rpcUrl && raw.walletContractAddress) {
|
|
29
|
-
try {
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
31
|
-
const ethersLib = require("ethers");
|
|
32
|
-
const provider = new ethersLib.ethers.JsonRpcProvider(raw.rpcUrl);
|
|
33
|
-
const bal = await Promise.race([
|
|
34
|
-
provider.getBalance(raw.walletContractAddress),
|
|
35
|
-
new Promise((_, r) => setTimeout(() => r(new Error("timeout")), 2000)),
|
|
36
|
-
]);
|
|
37
|
-
cfg.balance = `${parseFloat(ethersLib.ethers.formatEther(bal)).toFixed(4)} ETH`;
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
/* skip balance on timeout */
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
27
|
return cfg;
|
|
44
28
|
}
|
|
45
29
|
catch {
|
|
46
30
|
return undefined;
|
|
47
31
|
}
|
|
48
32
|
}
|
|
49
|
-
// ───
|
|
50
|
-
const
|
|
51
|
-
const ansi = {
|
|
52
|
-
clearScreen: `${ESC}[2J`,
|
|
53
|
-
home: `${ESC}[H`,
|
|
54
|
-
clearLine: `${ESC}[2K`,
|
|
55
|
-
clearToEol: `${ESC}[K`,
|
|
56
|
-
hideCursor: `${ESC}[?25l`,
|
|
57
|
-
showCursor: `${ESC}[?25h`,
|
|
58
|
-
move: (r, col) => `${ESC}[${r};${col}H`,
|
|
59
|
-
scrollRegion: (top, bot) => `${ESC}[${top};${bot}r`,
|
|
60
|
-
resetScroll: `${ESC}[r`,
|
|
61
|
-
};
|
|
62
|
-
function write(s) {
|
|
63
|
-
process.stdout.write(s);
|
|
64
|
-
}
|
|
65
|
-
// ─── Prompt constants ─────────────────────────────────────────────────────────
|
|
66
|
-
const PROMPT_TEXT = chalk_1.default.cyanBright("◈") +
|
|
33
|
+
// ─── Prompt ────────────────────────────────────────────────────────────────────
|
|
34
|
+
const PROMPT = chalk_1.default.cyanBright("◈") +
|
|
67
35
|
" " +
|
|
68
36
|
chalk_1.default.dim("arc402") +
|
|
69
37
|
" " +
|
|
70
38
|
chalk_1.default.white(">") +
|
|
71
39
|
" ";
|
|
72
|
-
// Visible character count of "◈ arc402 > "
|
|
73
|
-
const PROMPT_VIS = 11;
|
|
74
|
-
// ─── Known command detection ──────────────────────────────────────────────────
|
|
75
|
-
const BUILTIN_CMDS = ["help", "exit", "quit", "clear", "status"];
|
|
76
40
|
// ─── Shell-style tokenizer ────────────────────────────────────────────────────
|
|
77
41
|
function parseTokens(input) {
|
|
78
42
|
const tokens = [];
|
|
@@ -115,347 +79,47 @@ function parseTokens(input) {
|
|
|
115
79
|
tokens.push(current);
|
|
116
80
|
return tokens;
|
|
117
81
|
}
|
|
118
|
-
// ───
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return allTop.filter((cmd) => cmd.startsWith(trimmed));
|
|
125
|
-
}
|
|
126
|
-
const parent = trimmed.slice(0, spaceIdx);
|
|
127
|
-
const rest = trimmed.slice(spaceIdx + 1);
|
|
128
|
-
const subs = subCmds.get(parent) ?? [];
|
|
129
|
-
return subs.filter((s) => s.startsWith(rest)).map((s) => `${parent} ${s}`);
|
|
130
|
-
}
|
|
131
|
-
// ─── TUI class ────────────────────────────────────────────────────────────────
|
|
132
|
-
class TUI {
|
|
133
|
-
constructor() {
|
|
134
|
-
this.inputBuffer = "";
|
|
135
|
-
this.cursorPos = 0;
|
|
136
|
-
this.history = [];
|
|
137
|
-
this.historyIdx = -1;
|
|
138
|
-
this.historyTemp = "";
|
|
139
|
-
this.bannerLines = [];
|
|
140
|
-
this.topCmds = [];
|
|
141
|
-
this.subCmds = new Map();
|
|
142
|
-
this.commandRunning = false;
|
|
143
|
-
// ── Key handler ──────────────────────────────────────────────────────────────
|
|
144
|
-
this.boundKeyHandler = (key) => {
|
|
145
|
-
if (this.commandRunning)
|
|
146
|
-
return;
|
|
147
|
-
this.handleKey(key);
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
get termRows() {
|
|
151
|
-
return process.stdout.rows || 24;
|
|
152
|
-
}
|
|
153
|
-
get termCols() {
|
|
154
|
-
return process.stdout.columns || 80;
|
|
155
|
-
}
|
|
156
|
-
get scrollTop() {
|
|
157
|
-
// +1 for separator row after banner
|
|
158
|
-
return this.bannerLines.length + 2;
|
|
159
|
-
}
|
|
160
|
-
get scrollBot() {
|
|
161
|
-
return this.termRows - 1;
|
|
162
|
-
}
|
|
163
|
-
get inputRow() {
|
|
164
|
-
return this.termRows;
|
|
165
|
-
}
|
|
166
|
-
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
167
|
-
async start() {
|
|
168
|
-
this.bannerCfg = await loadBannerConfig();
|
|
169
|
-
// Load persisted history
|
|
170
|
-
try {
|
|
171
|
-
if (fs_1.default.existsSync(HISTORY_PATH)) {
|
|
172
|
-
const lines = fs_1.default.readFileSync(HISTORY_PATH, "utf-8").split("\n").filter(Boolean);
|
|
173
|
-
this.history = lines.slice(-MAX_HISTORY);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
catch { /* non-fatal */ }
|
|
177
|
-
// Build command metadata for completion
|
|
178
|
-
const template = (0, program_1.createProgram)();
|
|
179
|
-
this.topCmds = template.commands.map((cmd) => cmd.name());
|
|
180
|
-
for (const cmd of template.commands) {
|
|
181
|
-
if (cmd.commands.length > 0) {
|
|
182
|
-
this.subCmds.set(cmd.name(), cmd.commands.map((s) => s.name()));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// Draw initial screen
|
|
186
|
-
this.setupScreen();
|
|
187
|
-
this.drawInputLine();
|
|
188
|
-
// Enter raw mode
|
|
189
|
-
if (process.stdin.isTTY) {
|
|
190
|
-
process.stdin.setRawMode(true);
|
|
191
|
-
}
|
|
192
|
-
process.stdin.resume();
|
|
193
|
-
process.stdin.setEncoding("utf8");
|
|
194
|
-
process.stdin.on("data", this.boundKeyHandler);
|
|
195
|
-
// Resize handler
|
|
196
|
-
process.stdout.on("resize", () => {
|
|
197
|
-
this.setupScreen();
|
|
198
|
-
this.drawInputLine();
|
|
199
|
-
});
|
|
200
|
-
// SIGINT (shouldn't fire in raw mode, but just in case)
|
|
201
|
-
process.on("SIGINT", () => this.exitGracefully());
|
|
202
|
-
// Keep alive — process.stdin listener keeps the event loop running
|
|
203
|
-
await new Promise(() => {
|
|
204
|
-
/* never resolves; process.exit() is called on quit */
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
// ── Screen setup ────────────────────────────────────────────────────────────
|
|
208
|
-
setupScreen() {
|
|
209
|
-
this.bannerLines = (0, banner_1.getBannerLines)(this.bannerCfg);
|
|
210
|
-
// Simple setup — print banner once, no scroll regions (breaks on macOS Terminal)
|
|
211
|
-
for (const line of this.bannerLines) {
|
|
212
|
-
write(line + "\n");
|
|
213
|
-
}
|
|
214
|
-
write(chalk_1.default.dim("─".repeat(Math.min(this.termCols, 60))) + "\n\n");
|
|
215
|
-
}
|
|
216
|
-
// ── Banner repaint (in-place, preserves output area) ────────────────────────
|
|
217
|
-
repaintBanner() {
|
|
218
|
-
write(ansi.hideCursor);
|
|
219
|
-
for (let i = 0; i < this.bannerLines.length; i++) {
|
|
220
|
-
write(ansi.move(i + 1, 1) + ansi.clearToEol + this.bannerLines[i]);
|
|
221
|
-
}
|
|
222
|
-
// Separator
|
|
223
|
-
const sepRow = this.bannerLines.length + 1;
|
|
224
|
-
write(ansi.move(sepRow, 1) +
|
|
225
|
-
ansi.clearToEol +
|
|
226
|
-
chalk_1.default.dim("─".repeat(this.termCols)));
|
|
227
|
-
write(ansi.showCursor);
|
|
228
|
-
}
|
|
229
|
-
// ── Input line ───────────────────────────────────────────────────────────────
|
|
230
|
-
drawInputLine() {
|
|
231
|
-
write(ansi.move(this.inputRow, 1) + ansi.clearLine);
|
|
232
|
-
write(PROMPT_TEXT + this.inputBuffer);
|
|
233
|
-
// Place cursor at correct position within the input
|
|
234
|
-
write(ansi.move(this.inputRow, PROMPT_VIS + 1 + this.cursorPos));
|
|
235
|
-
}
|
|
236
|
-
handleKey(key) {
|
|
237
|
-
// Ctrl+C
|
|
238
|
-
if (key === "\u0003") {
|
|
239
|
-
this.exitGracefully();
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
// Ctrl+L — refresh
|
|
243
|
-
if (key === "\u000C") {
|
|
244
|
-
this.setupScreen();
|
|
245
|
-
this.drawInputLine();
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
// Enter
|
|
249
|
-
if (key === "\r" || key === "\n") {
|
|
250
|
-
void this.submit();
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
// Backspace
|
|
254
|
-
if (key === "\u007F" || key === "\b") {
|
|
255
|
-
if (this.cursorPos > 0) {
|
|
256
|
-
this.inputBuffer =
|
|
257
|
-
this.inputBuffer.slice(0, this.cursorPos - 1) +
|
|
258
|
-
this.inputBuffer.slice(this.cursorPos);
|
|
259
|
-
this.cursorPos--;
|
|
260
|
-
this.drawInputLine();
|
|
261
|
-
}
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
// Delete (forward)
|
|
265
|
-
if (key === "\x1b[3~") {
|
|
266
|
-
if (this.cursorPos < this.inputBuffer.length) {
|
|
267
|
-
this.inputBuffer =
|
|
268
|
-
this.inputBuffer.slice(0, this.cursorPos) +
|
|
269
|
-
this.inputBuffer.slice(this.cursorPos + 1);
|
|
270
|
-
this.drawInputLine();
|
|
271
|
-
}
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
// Up arrow — history prev
|
|
275
|
-
if (key === "\x1b[A") {
|
|
276
|
-
if (this.historyIdx === -1) {
|
|
277
|
-
this.historyTemp = this.inputBuffer;
|
|
278
|
-
this.historyIdx = this.history.length - 1;
|
|
279
|
-
}
|
|
280
|
-
else if (this.historyIdx > 0) {
|
|
281
|
-
this.historyIdx--;
|
|
282
|
-
}
|
|
283
|
-
if (this.historyIdx >= 0) {
|
|
284
|
-
this.inputBuffer = this.history[this.historyIdx];
|
|
285
|
-
this.cursorPos = this.inputBuffer.length;
|
|
286
|
-
this.drawInputLine();
|
|
287
|
-
}
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
// Down arrow — history next
|
|
291
|
-
if (key === "\x1b[B") {
|
|
292
|
-
if (this.historyIdx >= 0) {
|
|
293
|
-
this.historyIdx++;
|
|
294
|
-
if (this.historyIdx >= this.history.length) {
|
|
295
|
-
this.historyIdx = -1;
|
|
296
|
-
this.inputBuffer = this.historyTemp;
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
this.inputBuffer = this.history[this.historyIdx];
|
|
300
|
-
}
|
|
301
|
-
this.cursorPos = this.inputBuffer.length;
|
|
302
|
-
this.drawInputLine();
|
|
303
|
-
}
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
// Right arrow
|
|
307
|
-
if (key === "\x1b[C") {
|
|
308
|
-
if (this.cursorPos < this.inputBuffer.length) {
|
|
309
|
-
this.cursorPos++;
|
|
310
|
-
this.drawInputLine();
|
|
311
|
-
}
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
// Left arrow
|
|
315
|
-
if (key === "\x1b[D") {
|
|
316
|
-
if (this.cursorPos > 0) {
|
|
317
|
-
this.cursorPos--;
|
|
318
|
-
this.drawInputLine();
|
|
319
|
-
}
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
// Home / Ctrl+A
|
|
323
|
-
if (key === "\x1b[H" || key === "\u0001") {
|
|
324
|
-
this.cursorPos = 0;
|
|
325
|
-
this.drawInputLine();
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
// End / Ctrl+E
|
|
329
|
-
if (key === "\x1b[F" || key === "\u0005") {
|
|
330
|
-
this.cursorPos = this.inputBuffer.length;
|
|
331
|
-
this.drawInputLine();
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
// Ctrl+U — clear line
|
|
335
|
-
if (key === "\u0015") {
|
|
336
|
-
this.inputBuffer = "";
|
|
337
|
-
this.cursorPos = 0;
|
|
338
|
-
this.drawInputLine();
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
// Ctrl+K — kill to end
|
|
342
|
-
if (key === "\u000B") {
|
|
343
|
-
this.inputBuffer = this.inputBuffer.slice(0, this.cursorPos);
|
|
344
|
-
this.drawInputLine();
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
// Tab — completion
|
|
348
|
-
if (key === "\t") {
|
|
349
|
-
this.handleTab();
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
// Printable characters
|
|
353
|
-
if (key >= " " && !key.startsWith("\x1b")) {
|
|
354
|
-
this.inputBuffer =
|
|
355
|
-
this.inputBuffer.slice(0, this.cursorPos) +
|
|
356
|
-
key +
|
|
357
|
-
this.inputBuffer.slice(this.cursorPos);
|
|
358
|
-
this.cursorPos += key.length;
|
|
359
|
-
this.drawInputLine();
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
// ── Tab completion ───────────────────────────────────────────────────────────
|
|
363
|
-
handleTab() {
|
|
364
|
-
const completions = getCompletions(this.inputBuffer, this.topCmds, this.subCmds);
|
|
365
|
-
if (completions.length === 0)
|
|
366
|
-
return;
|
|
367
|
-
if (completions.length === 1) {
|
|
368
|
-
this.inputBuffer = completions[0] + " ";
|
|
369
|
-
this.cursorPos = this.inputBuffer.length;
|
|
370
|
-
this.drawInputLine();
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
// Find common prefix
|
|
374
|
-
const common = completions.reduce((a, b) => {
|
|
375
|
-
let i = 0;
|
|
376
|
-
while (i < a.length && i < b.length && a[i] === b[i])
|
|
377
|
-
i++;
|
|
378
|
-
return a.slice(0, i);
|
|
379
|
-
});
|
|
380
|
-
if (common.length > this.inputBuffer.trimStart().length) {
|
|
381
|
-
this.inputBuffer = common;
|
|
382
|
-
this.cursorPos = common.length;
|
|
82
|
+
// ─── REPL entry point (basic readline fallback) ────────────────────────────────
|
|
83
|
+
async function startREPL() {
|
|
84
|
+
if (!process.stdout.isTTY) {
|
|
85
|
+
const bannerCfg = await loadBannerConfig();
|
|
86
|
+
for (const line of (0, banner_1.getBannerLines)(bannerCfg)) {
|
|
87
|
+
process.stdout.write(line + "\n");
|
|
383
88
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
this.drawInputLine();
|
|
89
|
+
process.stdout.write("Interactive TUI requires a TTY. Use arc402 <command> directly.\n");
|
|
90
|
+
return;
|
|
387
91
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
write(ansi.move(this.scrollBot, 1));
|
|
392
|
-
write(text);
|
|
92
|
+
const bannerCfg = await loadBannerConfig();
|
|
93
|
+
for (const line of (0, banner_1.getBannerLines)(bannerCfg)) {
|
|
94
|
+
process.stdout.write(line + "\n");
|
|
393
95
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (input !== this.history[this.history.length - 1]) {
|
|
406
|
-
this.history.push(input);
|
|
407
|
-
}
|
|
408
|
-
// Echo the input into the output area
|
|
409
|
-
this.writeOutput("\n" + chalk_1.default.dim("◈ ") + chalk_1.default.white(input) + "\n");
|
|
410
|
-
// ── Built-in commands ──────────────────────────────────────────────────────
|
|
411
|
-
if (input === "exit" || input === "quit") {
|
|
412
|
-
this.exitGracefully();
|
|
96
|
+
const rl = readline_1.default.createInterface({
|
|
97
|
+
input: process.stdin,
|
|
98
|
+
output: process.stdout,
|
|
99
|
+
prompt: PROMPT,
|
|
100
|
+
terminal: true,
|
|
101
|
+
});
|
|
102
|
+
rl.prompt();
|
|
103
|
+
rl.on("line", async (input) => {
|
|
104
|
+
const trimmed = input.trim();
|
|
105
|
+
if (!trimmed) {
|
|
106
|
+
rl.prompt();
|
|
413
107
|
return;
|
|
414
108
|
}
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
this.drawInputLine();
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
if (input === "status") {
|
|
422
|
-
await this.runStatus();
|
|
423
|
-
this.afterCommand();
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
if (input === "help" || input === "/help") {
|
|
427
|
-
await this.runHelp();
|
|
428
|
-
this.afterCommand();
|
|
429
|
-
return;
|
|
109
|
+
if (trimmed === "exit" || trimmed === "quit") {
|
|
110
|
+
process.stdout.write(" " + chalk_1.default.cyanBright("◈") + chalk_1.default.dim(" goodbye") + "\n");
|
|
111
|
+
process.exit(0);
|
|
430
112
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
this.commandRunning = true;
|
|
436
|
-
await this.sendChat(msg);
|
|
437
|
-
this.commandRunning = false;
|
|
113
|
+
if (trimmed === "clear") {
|
|
114
|
+
process.stdout.write("\x1b[2J\x1b[H");
|
|
115
|
+
for (const line of (0, banner_1.getBannerLines)(bannerCfg)) {
|
|
116
|
+
process.stdout.write(line + "\n");
|
|
438
117
|
}
|
|
439
|
-
|
|
118
|
+
rl.prompt();
|
|
440
119
|
return;
|
|
441
120
|
}
|
|
442
|
-
//
|
|
443
|
-
const
|
|
444
|
-
const allKnown = [...BUILTIN_CMDS, ...this.topCmds];
|
|
445
|
-
if (!allKnown.includes(firstWord)) {
|
|
446
|
-
this.commandRunning = true;
|
|
447
|
-
await this.sendChat(input);
|
|
448
|
-
this.commandRunning = false;
|
|
449
|
-
this.afterCommand();
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
// ── Dispatch to commander ──────────────────────────────────────────────────
|
|
453
|
-
this.commandRunning = true;
|
|
454
|
-
// Move output cursor to bottom of scroll region
|
|
455
|
-
write(ansi.move(this.scrollBot, 1));
|
|
456
|
-
// Suspend TUI stdin so interactive commands (prompts, readline) work cleanly
|
|
457
|
-
process.stdin.removeListener("data", this.boundKeyHandler);
|
|
458
|
-
const tokens = parseTokens(input);
|
|
121
|
+
// Dispatch to commander
|
|
122
|
+
const tokens = parseTokens(trimmed);
|
|
459
123
|
const prog = (0, program_1.createProgram)();
|
|
460
124
|
prog.exitOverride();
|
|
461
125
|
prog.configureOutput({
|
|
@@ -470,202 +134,26 @@ class TUI {
|
|
|
470
134
|
if (e.code === "commander.helpDisplayed" ||
|
|
471
135
|
e.code === "commander.version" ||
|
|
472
136
|
e.code === "commander.executeSubCommandAsync") {
|
|
473
|
-
// already written
|
|
137
|
+
// already written
|
|
474
138
|
}
|
|
475
139
|
else if (e.code === "commander.unknownCommand") {
|
|
476
140
|
process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(`Unknown command: ${chalk_1.default.white(tokens[0])}`)} \n`);
|
|
477
141
|
process.stdout.write(chalk_1.default.dim(" Type 'help' for available commands\n"));
|
|
478
142
|
}
|
|
479
|
-
else if (e.code?.startsWith("commander.")) {
|
|
480
|
-
process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(e.message ?? String(err))}\n`);
|
|
481
|
-
}
|
|
482
143
|
else {
|
|
483
144
|
process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(e.message ?? String(err))}\n`);
|
|
484
145
|
}
|
|
485
146
|
}
|
|
486
|
-
// Restore raw mode + our listener (interactive commands may have toggled it)
|
|
487
|
-
if (process.stdin.isTTY) {
|
|
488
|
-
process.stdin.setRawMode(true);
|
|
489
|
-
}
|
|
490
|
-
process.stdin.on("data", this.boundKeyHandler);
|
|
491
|
-
this.commandRunning = false;
|
|
492
|
-
this.afterCommand();
|
|
493
|
-
}
|
|
494
|
-
// ── OpenClaw chat ─────────────────────────────────────────────────────────────
|
|
495
|
-
async sendChat(rawMessage) {
|
|
496
|
-
const message = rawMessage.trim().slice(0, 10000);
|
|
497
|
-
write(ansi.move(this.scrollBot, 1));
|
|
498
|
-
let res;
|
|
499
|
-
try {
|
|
500
|
-
res = await fetch("http://localhost:19000/api/agent", {
|
|
501
|
-
method: "POST",
|
|
502
|
-
headers: { "Content-Type": "application/json" },
|
|
503
|
-
body: JSON.stringify({ message, session: "arc402-repl" }),
|
|
504
|
-
signal: AbortSignal.timeout(30000),
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
catch (err) {
|
|
508
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
509
|
-
const isDown = msg.includes("ECONNREFUSED") ||
|
|
510
|
-
msg.includes("fetch failed") ||
|
|
511
|
-
msg.includes("ENOTFOUND") ||
|
|
512
|
-
msg.includes("UND_ERR_SOCKET");
|
|
513
|
-
if (isDown) {
|
|
514
|
-
process.stdout.write("\n " +
|
|
515
|
-
chalk_1.default.yellow("⚠") +
|
|
516
|
-
" " +
|
|
517
|
-
chalk_1.default.dim("OpenClaw gateway not running. Start with: ") +
|
|
518
|
-
chalk_1.default.white("openclaw gateway start") +
|
|
519
|
-
"\n");
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
process.stdout.write("\n " + colors_1.c.failure + " " + chalk_1.default.red(msg) + "\n");
|
|
523
|
-
}
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
if (!res.body) {
|
|
527
|
-
process.stdout.write("\n" + chalk_1.default.dim(" ◈ ") + chalk_1.default.white("(empty response)") + "\n");
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
147
|
process.stdout.write("\n");
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (line === "[DONE]")
|
|
536
|
-
return;
|
|
537
|
-
try {
|
|
538
|
-
const j = JSON.parse(line);
|
|
539
|
-
line = j.text ?? j.content ?? j.delta?.text ?? line;
|
|
540
|
-
}
|
|
541
|
-
catch {
|
|
542
|
-
/* use raw */
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
if (line.trim()) {
|
|
546
|
-
process.stdout.write(chalk_1.default.dim(" ◈ ") + chalk_1.default.white(line) + "\n");
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
const reader = res.body.getReader();
|
|
550
|
-
const decoder = new TextDecoder();
|
|
551
|
-
let buffer = "";
|
|
552
|
-
while (true) {
|
|
553
|
-
const { done, value } = await reader.read();
|
|
554
|
-
if (done)
|
|
555
|
-
break;
|
|
556
|
-
buffer += decoder.decode(value, { stream: true });
|
|
557
|
-
const lines = buffer.split("\n");
|
|
558
|
-
buffer = lines.pop() ?? "";
|
|
559
|
-
for (const line of lines)
|
|
560
|
-
flushLine(line);
|
|
561
|
-
}
|
|
562
|
-
if (buffer.trim())
|
|
563
|
-
flushLine(buffer);
|
|
564
|
-
}
|
|
565
|
-
// ── After each command: show prompt ──────────────────────────────────────────
|
|
566
|
-
afterCommand() {
|
|
567
|
-
// Simple prompt — no banner repaint (causes scrollback issues on macOS Terminal)
|
|
568
|
-
write("\n");
|
|
569
|
-
this.drawInputLine();
|
|
570
|
-
}
|
|
571
|
-
// ── Built-in: status ─────────────────────────────────────────────────────────
|
|
572
|
-
async runStatus() {
|
|
573
|
-
write(ansi.move(this.scrollBot, 1));
|
|
574
|
-
if (!fs_1.default.existsSync(CONFIG_PATH)) {
|
|
575
|
-
process.stdout.write(chalk_1.default.dim("\n No config found. Run 'config init' to get started.\n"));
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
try {
|
|
579
|
-
const raw = JSON.parse(fs_1.default.readFileSync(CONFIG_PATH, "utf-8"));
|
|
580
|
-
process.stdout.write("\n");
|
|
581
|
-
if (raw.network)
|
|
582
|
-
process.stdout.write(` ${chalk_1.default.dim("Network")} ${chalk_1.default.white(raw.network)}\n`);
|
|
583
|
-
if (raw.walletContractAddress) {
|
|
584
|
-
const w = raw.walletContractAddress;
|
|
585
|
-
process.stdout.write(` ${chalk_1.default.dim("Wallet")} ${chalk_1.default.white(`${w.slice(0, 6)}...${w.slice(-4)}`)}\n`);
|
|
586
|
-
}
|
|
587
|
-
if (raw.rpcUrl && raw.walletContractAddress) {
|
|
588
|
-
try {
|
|
589
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
590
|
-
const ethersLib = require("ethers");
|
|
591
|
-
const provider = new ethersLib.ethers.JsonRpcProvider(raw.rpcUrl);
|
|
592
|
-
const bal = await Promise.race([
|
|
593
|
-
provider.getBalance(raw.walletContractAddress),
|
|
594
|
-
new Promise((_, r) => setTimeout(() => r(new Error("timeout")), 2000)),
|
|
595
|
-
]);
|
|
596
|
-
process.stdout.write(` ${chalk_1.default.dim("Balance")} ${chalk_1.default.white(`${parseFloat(ethersLib.ethers.formatEther(bal)).toFixed(4)} ETH`)}\n`);
|
|
597
|
-
}
|
|
598
|
-
catch {
|
|
599
|
-
/* skip */
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
process.stdout.write("\n");
|
|
603
|
-
}
|
|
604
|
-
catch {
|
|
605
|
-
/* skip */
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
// ── Built-in: help ────────────────────────────────────────────────────────────
|
|
609
|
-
async runHelp() {
|
|
610
|
-
write(ansi.move(this.scrollBot, 1));
|
|
611
|
-
process.stdin.removeListener("data", this.boundKeyHandler);
|
|
612
|
-
const prog = (0, program_1.createProgram)();
|
|
613
|
-
prog.exitOverride();
|
|
614
|
-
prog.configureOutput({
|
|
615
|
-
writeOut: (str) => process.stdout.write(str),
|
|
616
|
-
writeErr: (str) => process.stderr.write(str),
|
|
617
|
-
});
|
|
618
|
-
try {
|
|
619
|
-
await prog.parseAsync(["node", "arc402", "--help"]);
|
|
620
|
-
}
|
|
621
|
-
catch {
|
|
622
|
-
/* commander throws after printing help */
|
|
623
|
-
}
|
|
624
|
-
if (process.stdin.isTTY)
|
|
625
|
-
process.stdin.setRawMode(true);
|
|
626
|
-
process.stdin.on("data", this.boundKeyHandler);
|
|
627
|
-
process.stdout.write("\n");
|
|
628
|
-
process.stdout.write(chalk_1.default.cyanBright("Chat") + "\n");
|
|
629
|
-
process.stdout.write(" " +
|
|
630
|
-
chalk_1.default.white("<message>") +
|
|
631
|
-
chalk_1.default.dim(" Send message to OpenClaw gateway\n"));
|
|
632
|
-
process.stdout.write(" " +
|
|
633
|
-
chalk_1.default.white("/chat <message>") +
|
|
634
|
-
chalk_1.default.dim(" Explicitly route to chat\n"));
|
|
635
|
-
process.stdout.write(chalk_1.default.dim(" Gateway: http://localhost:19000 (openclaw gateway start)\n"));
|
|
636
|
-
process.stdout.write("\n");
|
|
637
|
-
}
|
|
638
|
-
// ── Exit ──────────────────────────────────────────────────────────────────────
|
|
639
|
-
exitGracefully() {
|
|
640
|
-
// Save history
|
|
641
|
-
try {
|
|
642
|
-
const toSave = this.history.slice(-MAX_HISTORY);
|
|
643
|
-
fs_1.default.mkdirSync(path_1.default.dirname(HISTORY_PATH), { recursive: true });
|
|
644
|
-
fs_1.default.writeFileSync(HISTORY_PATH, toSave.join("\n") + "\n", { mode: 0o600 });
|
|
645
|
-
}
|
|
646
|
-
catch { /* non-fatal */ }
|
|
647
|
-
write(ansi.move(this.inputRow, 1) + ansi.clearLine);
|
|
648
|
-
write(" " + chalk_1.default.cyanBright("◈") + chalk_1.default.dim(" goodbye") + "\n");
|
|
649
|
-
write(ansi.resetScroll);
|
|
650
|
-
write(ansi.showCursor);
|
|
651
|
-
if (process.stdin.isTTY) {
|
|
652
|
-
process.stdin.setRawMode(false);
|
|
653
|
-
}
|
|
148
|
+
rl.prompt();
|
|
149
|
+
});
|
|
150
|
+
rl.on("close", () => {
|
|
151
|
+
process.stdout.write("\n " + chalk_1.default.cyanBright("◈") + chalk_1.default.dim(" goodbye") + "\n");
|
|
654
152
|
process.exit(0);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
// Non-TTY (piped): fall back to minimal line-mode output
|
|
661
|
-
const bannerCfg = await loadBannerConfig();
|
|
662
|
-
for (const line of (0, banner_1.getBannerLines)(bannerCfg)) {
|
|
663
|
-
process.stdout.write(line + "\n");
|
|
664
|
-
}
|
|
665
|
-
process.stdout.write("Interactive TUI requires a TTY. Use arc402 <command> directly.\n");
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
const tui = new TUI();
|
|
669
|
-
await tui.start();
|
|
153
|
+
});
|
|
154
|
+
// Keep alive
|
|
155
|
+
await new Promise(() => {
|
|
156
|
+
/* readline keeps event loop alive */
|
|
157
|
+
});
|
|
670
158
|
}
|
|
671
159
|
//# sourceMappingURL=repl.js.map
|