arc402-cli 0.7.2 → 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.
Files changed (55) hide show
  1. package/TUI-SPEC.md +214 -0
  2. package/dist/index.js +55 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/repl.d.ts.map +1 -1
  5. package/dist/repl.js +46 -567
  6. package/dist/repl.js.map +1 -1
  7. package/dist/tui/App.d.ts +12 -0
  8. package/dist/tui/App.d.ts.map +1 -0
  9. package/dist/tui/App.js +154 -0
  10. package/dist/tui/App.js.map +1 -0
  11. package/dist/tui/Footer.d.ts +11 -0
  12. package/dist/tui/Footer.d.ts.map +1 -0
  13. package/dist/tui/Footer.js +13 -0
  14. package/dist/tui/Footer.js.map +1 -0
  15. package/dist/tui/Header.d.ts +14 -0
  16. package/dist/tui/Header.d.ts.map +1 -0
  17. package/dist/tui/Header.js +19 -0
  18. package/dist/tui/Header.js.map +1 -0
  19. package/dist/tui/InputLine.d.ts +11 -0
  20. package/dist/tui/InputLine.d.ts.map +1 -0
  21. package/dist/tui/InputLine.js +145 -0
  22. package/dist/tui/InputLine.js.map +1 -0
  23. package/dist/tui/Viewport.d.ts +14 -0
  24. package/dist/tui/Viewport.d.ts.map +1 -0
  25. package/dist/tui/Viewport.js +48 -0
  26. package/dist/tui/Viewport.js.map +1 -0
  27. package/dist/tui/index.d.ts +2 -0
  28. package/dist/tui/index.d.ts.map +1 -0
  29. package/dist/tui/index.js +55 -0
  30. package/dist/tui/index.js.map +1 -0
  31. package/dist/tui/useChat.d.ts +11 -0
  32. package/dist/tui/useChat.d.ts.map +1 -0
  33. package/dist/tui/useChat.js +91 -0
  34. package/dist/tui/useChat.js.map +1 -0
  35. package/dist/tui/useCommand.d.ts +12 -0
  36. package/dist/tui/useCommand.d.ts.map +1 -0
  37. package/dist/tui/useCommand.js +137 -0
  38. package/dist/tui/useCommand.js.map +1 -0
  39. package/dist/tui/useScroll.d.ts +17 -0
  40. package/dist/tui/useScroll.d.ts.map +1 -0
  41. package/dist/tui/useScroll.js +46 -0
  42. package/dist/tui/useScroll.js.map +1 -0
  43. package/package.json +5 -1
  44. package/src/index.ts +21 -1
  45. package/src/repl.ts +50 -676
  46. package/src/tui/App.tsx +214 -0
  47. package/src/tui/Footer.tsx +18 -0
  48. package/src/tui/Header.tsx +30 -0
  49. package/src/tui/InputLine.tsx +164 -0
  50. package/src/tui/Viewport.tsx +70 -0
  51. package/src/tui/index.tsx +72 -0
  52. package/src/tui/useChat.ts +103 -0
  53. package/src/tui/useCommand.ts +148 -0
  54. package/src/tui/useScroll.ts +65 -0
  55. 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 / banner helpers ──────────────────────────────────────────────────
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
- // ─── ANSI helpers ─────────────────────────────────────────────────────────────
50
- const ESC = "\x1b";
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,357 +79,47 @@ function parseTokens(input) {
115
79
  tokens.push(current);
116
80
  return tokens;
117
81
  }
118
- // ─── Tab completion logic ─────────────────────────────────────────────────────
119
- function getCompletions(line, topCmds, subCmds) {
120
- const allTop = [...BUILTIN_CMDS, ...topCmds];
121
- const trimmed = line.trimStart();
122
- const spaceIdx = trimmed.indexOf(" ");
123
- if (spaceIdx === -1) {
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
- write(ansi.hideCursor);
211
- write(ansi.clearScreen + ansi.home);
212
- // Banner
213
- for (const line of this.bannerLines) {
214
- write(line + "\n");
215
- }
216
- // Separator between banner and output area
217
- write(chalk_1.default.dim("─".repeat(this.termCols)) + "\n");
218
- // Set scroll region (output area, leaves last row free for input)
219
- if (this.scrollTop <= this.scrollBot) {
220
- write(ansi.scrollRegion(this.scrollTop, this.scrollBot));
221
- }
222
- // Position cursor at top of output area
223
- write(ansi.move(this.scrollTop, 1));
224
- write(ansi.showCursor);
225
- }
226
- // ── Banner repaint (in-place, preserves output area) ────────────────────────
227
- repaintBanner() {
228
- write(ansi.hideCursor);
229
- for (let i = 0; i < this.bannerLines.length; i++) {
230
- write(ansi.move(i + 1, 1) + ansi.clearToEol + this.bannerLines[i]);
231
- }
232
- // Separator
233
- const sepRow = this.bannerLines.length + 1;
234
- write(ansi.move(sepRow, 1) +
235
- ansi.clearToEol +
236
- chalk_1.default.dim("─".repeat(this.termCols)));
237
- write(ansi.showCursor);
238
- }
239
- // ── Input line ───────────────────────────────────────────────────────────────
240
- drawInputLine() {
241
- write(ansi.move(this.inputRow, 1) + ansi.clearLine);
242
- write(PROMPT_TEXT + this.inputBuffer);
243
- // Place cursor at correct position within the input
244
- write(ansi.move(this.inputRow, PROMPT_VIS + 1 + this.cursorPos));
245
- }
246
- handleKey(key) {
247
- // Ctrl+C
248
- if (key === "\u0003") {
249
- this.exitGracefully();
250
- return;
251
- }
252
- // Ctrl+L — refresh
253
- if (key === "\u000C") {
254
- this.setupScreen();
255
- this.drawInputLine();
256
- return;
257
- }
258
- // Enter
259
- if (key === "\r" || key === "\n") {
260
- void this.submit();
261
- return;
262
- }
263
- // Backspace
264
- if (key === "\u007F" || key === "\b") {
265
- if (this.cursorPos > 0) {
266
- this.inputBuffer =
267
- this.inputBuffer.slice(0, this.cursorPos - 1) +
268
- this.inputBuffer.slice(this.cursorPos);
269
- this.cursorPos--;
270
- this.drawInputLine();
271
- }
272
- return;
273
- }
274
- // Delete (forward)
275
- if (key === "\x1b[3~") {
276
- if (this.cursorPos < this.inputBuffer.length) {
277
- this.inputBuffer =
278
- this.inputBuffer.slice(0, this.cursorPos) +
279
- this.inputBuffer.slice(this.cursorPos + 1);
280
- this.drawInputLine();
281
- }
282
- return;
283
- }
284
- // Up arrow — history prev
285
- if (key === "\x1b[A") {
286
- if (this.historyIdx === -1) {
287
- this.historyTemp = this.inputBuffer;
288
- this.historyIdx = this.history.length - 1;
289
- }
290
- else if (this.historyIdx > 0) {
291
- this.historyIdx--;
292
- }
293
- if (this.historyIdx >= 0) {
294
- this.inputBuffer = this.history[this.historyIdx];
295
- this.cursorPos = this.inputBuffer.length;
296
- this.drawInputLine();
297
- }
298
- return;
299
- }
300
- // Down arrow — history next
301
- if (key === "\x1b[B") {
302
- if (this.historyIdx >= 0) {
303
- this.historyIdx++;
304
- if (this.historyIdx >= this.history.length) {
305
- this.historyIdx = -1;
306
- this.inputBuffer = this.historyTemp;
307
- }
308
- else {
309
- this.inputBuffer = this.history[this.historyIdx];
310
- }
311
- this.cursorPos = this.inputBuffer.length;
312
- this.drawInputLine();
313
- }
314
- return;
315
- }
316
- // Right arrow
317
- if (key === "\x1b[C") {
318
- if (this.cursorPos < this.inputBuffer.length) {
319
- this.cursorPos++;
320
- this.drawInputLine();
321
- }
322
- return;
323
- }
324
- // Left arrow
325
- if (key === "\x1b[D") {
326
- if (this.cursorPos > 0) {
327
- this.cursorPos--;
328
- this.drawInputLine();
329
- }
330
- return;
331
- }
332
- // Home / Ctrl+A
333
- if (key === "\x1b[H" || key === "\u0001") {
334
- this.cursorPos = 0;
335
- this.drawInputLine();
336
- return;
337
- }
338
- // End / Ctrl+E
339
- if (key === "\x1b[F" || key === "\u0005") {
340
- this.cursorPos = this.inputBuffer.length;
341
- this.drawInputLine();
342
- return;
343
- }
344
- // Ctrl+U — clear line
345
- if (key === "\u0015") {
346
- this.inputBuffer = "";
347
- this.cursorPos = 0;
348
- this.drawInputLine();
349
- return;
350
- }
351
- // Ctrl+K — kill to end
352
- if (key === "\u000B") {
353
- this.inputBuffer = this.inputBuffer.slice(0, this.cursorPos);
354
- this.drawInputLine();
355
- return;
356
- }
357
- // Tab — completion
358
- if (key === "\t") {
359
- this.handleTab();
360
- return;
361
- }
362
- // Printable characters
363
- if (key >= " " && !key.startsWith("\x1b")) {
364
- this.inputBuffer =
365
- this.inputBuffer.slice(0, this.cursorPos) +
366
- key +
367
- this.inputBuffer.slice(this.cursorPos);
368
- this.cursorPos += key.length;
369
- this.drawInputLine();
370
- }
371
- }
372
- // ── Tab completion ───────────────────────────────────────────────────────────
373
- handleTab() {
374
- const completions = getCompletions(this.inputBuffer, this.topCmds, this.subCmds);
375
- if (completions.length === 0)
376
- return;
377
- if (completions.length === 1) {
378
- this.inputBuffer = completions[0] + " ";
379
- this.cursorPos = this.inputBuffer.length;
380
- this.drawInputLine();
381
- return;
382
- }
383
- // Find common prefix
384
- const common = completions.reduce((a, b) => {
385
- let i = 0;
386
- while (i < a.length && i < b.length && a[i] === b[i])
387
- i++;
388
- return a.slice(0, i);
389
- });
390
- if (common.length > this.inputBuffer.trimStart().length) {
391
- this.inputBuffer = common;
392
- 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");
393
88
  }
394
- // Show options in output area
395
- this.writeOutput("\n" + chalk_1.default.dim(completions.join(" ")) + "\n");
396
- this.drawInputLine();
89
+ process.stdout.write("Interactive TUI requires a TTY. Use arc402 <command> directly.\n");
90
+ return;
397
91
  }
398
- // ── Write to output area ─────────────────────────────────────────────────────
399
- writeOutput(text) {
400
- // Move cursor to bottom of scroll region to ensure scroll-down works
401
- write(ansi.move(this.scrollBot, 1));
402
- write(text);
92
+ const bannerCfg = await loadBannerConfig();
93
+ for (const line of (0, banner_1.getBannerLines)(bannerCfg)) {
94
+ process.stdout.write(line + "\n");
403
95
  }
404
- // ── Submit line ──────────────────────────────────────────────────────────────
405
- async submit() {
406
- const input = this.inputBuffer.trim();
407
- this.inputBuffer = "";
408
- this.cursorPos = 0;
409
- this.historyIdx = -1;
410
- if (!input) {
411
- this.drawInputLine();
412
- return;
413
- }
414
- // Add to history
415
- if (input !== this.history[this.history.length - 1]) {
416
- this.history.push(input);
417
- }
418
- // Echo the input into the output area
419
- this.writeOutput("\n" + chalk_1.default.dim("◈ ") + chalk_1.default.white(input) + "\n");
420
- // ── Built-in commands ──────────────────────────────────────────────────────
421
- if (input === "exit" || input === "quit") {
422
- 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();
423
107
  return;
424
108
  }
425
- if (input === "clear") {
426
- this.bannerCfg = await loadBannerConfig();
427
- this.setupScreen();
428
- this.drawInputLine();
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
- if (input === "status") {
432
- await this.runStatus();
433
- this.afterCommand();
434
- return;
435
- }
436
- if (input === "help" || input === "/help") {
437
- await this.runHelp();
438
- this.afterCommand();
439
- return;
440
- }
441
- // ── /chat prefix — explicit chat route ────────────────────────────────────
442
- if (input.startsWith("/chat ") || input === "/chat") {
443
- const msg = input.slice(6).trim();
444
- if (msg) {
445
- this.commandRunning = true;
446
- await this.sendChat(msg);
447
- 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");
448
117
  }
449
- this.afterCommand();
450
- return;
451
- }
452
- // ── Chat mode detection ────────────────────────────────────────────────────
453
- const firstWord = input.split(/\s+/)[0];
454
- const allKnown = [...BUILTIN_CMDS, ...this.topCmds];
455
- if (!allKnown.includes(firstWord)) {
456
- this.commandRunning = true;
457
- await this.sendChat(input);
458
- this.commandRunning = false;
459
- this.afterCommand();
118
+ rl.prompt();
460
119
  return;
461
120
  }
462
- // ── Dispatch to commander ──────────────────────────────────────────────────
463
- this.commandRunning = true;
464
- // Move output cursor to bottom of scroll region
465
- write(ansi.move(this.scrollBot, 1));
466
- // Suspend TUI stdin so interactive commands (prompts, readline) work cleanly
467
- process.stdin.removeListener("data", this.boundKeyHandler);
468
- const tokens = parseTokens(input);
121
+ // Dispatch to commander
122
+ const tokens = parseTokens(trimmed);
469
123
  const prog = (0, program_1.createProgram)();
470
124
  prog.exitOverride();
471
125
  prog.configureOutput({
@@ -480,201 +134,26 @@ class TUI {
480
134
  if (e.code === "commander.helpDisplayed" ||
481
135
  e.code === "commander.version" ||
482
136
  e.code === "commander.executeSubCommandAsync") {
483
- // already written or normal exit
137
+ // already written
484
138
  }
485
139
  else if (e.code === "commander.unknownCommand") {
486
140
  process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(`Unknown command: ${chalk_1.default.white(tokens[0])}`)} \n`);
487
141
  process.stdout.write(chalk_1.default.dim(" Type 'help' for available commands\n"));
488
142
  }
489
- else if (e.code?.startsWith("commander.")) {
490
- process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(e.message ?? String(err))}\n`);
491
- }
492
143
  else {
493
144
  process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(e.message ?? String(err))}\n`);
494
145
  }
495
146
  }
496
- // Restore raw mode + our listener (interactive commands may have toggled it)
497
- if (process.stdin.isTTY) {
498
- process.stdin.setRawMode(true);
499
- }
500
- process.stdin.on("data", this.boundKeyHandler);
501
- this.commandRunning = false;
502
- this.afterCommand();
503
- }
504
- // ── OpenClaw chat ─────────────────────────────────────────────────────────────
505
- async sendChat(rawMessage) {
506
- const message = rawMessage.trim().slice(0, 10000);
507
- write(ansi.move(this.scrollBot, 1));
508
- let res;
509
- try {
510
- res = await fetch("http://localhost:19000/api/agent", {
511
- method: "POST",
512
- headers: { "Content-Type": "application/json" },
513
- body: JSON.stringify({ message, session: "arc402-repl" }),
514
- signal: AbortSignal.timeout(30000),
515
- });
516
- }
517
- catch (err) {
518
- const msg = err instanceof Error ? err.message : String(err);
519
- const isDown = msg.includes("ECONNREFUSED") ||
520
- msg.includes("fetch failed") ||
521
- msg.includes("ENOTFOUND") ||
522
- msg.includes("UND_ERR_SOCKET");
523
- if (isDown) {
524
- process.stdout.write("\n " +
525
- chalk_1.default.yellow("⚠") +
526
- " " +
527
- chalk_1.default.dim("OpenClaw gateway not running. Start with: ") +
528
- chalk_1.default.white("openclaw gateway start") +
529
- "\n");
530
- }
531
- else {
532
- process.stdout.write("\n " + colors_1.c.failure + " " + chalk_1.default.red(msg) + "\n");
533
- }
534
- return;
535
- }
536
- if (!res.body) {
537
- process.stdout.write("\n" + chalk_1.default.dim(" ◈ ") + chalk_1.default.white("(empty response)") + "\n");
538
- return;
539
- }
540
- process.stdout.write("\n");
541
- const flushLine = (line) => {
542
- // Unwrap SSE data lines
543
- if (line.startsWith("data: ")) {
544
- line = line.slice(6);
545
- if (line === "[DONE]")
546
- return;
547
- try {
548
- const j = JSON.parse(line);
549
- line = j.text ?? j.content ?? j.delta?.text ?? line;
550
- }
551
- catch {
552
- /* use raw */
553
- }
554
- }
555
- if (line.trim()) {
556
- process.stdout.write(chalk_1.default.dim(" ◈ ") + chalk_1.default.white(line) + "\n");
557
- }
558
- };
559
- const reader = res.body.getReader();
560
- const decoder = new TextDecoder();
561
- let buffer = "";
562
- while (true) {
563
- const { done, value } = await reader.read();
564
- if (done)
565
- break;
566
- buffer += decoder.decode(value, { stream: true });
567
- const lines = buffer.split("\n");
568
- buffer = lines.pop() ?? "";
569
- for (const line of lines)
570
- flushLine(line);
571
- }
572
- if (buffer.trim())
573
- flushLine(buffer);
574
- }
575
- // ── After each command: repaint banner + input ───────────────────────────────
576
- afterCommand() {
577
- this.repaintBanner();
578
- this.drawInputLine();
579
- }
580
- // ── Built-in: status ─────────────────────────────────────────────────────────
581
- async runStatus() {
582
- write(ansi.move(this.scrollBot, 1));
583
- if (!fs_1.default.existsSync(CONFIG_PATH)) {
584
- process.stdout.write(chalk_1.default.dim("\n No config found. Run 'config init' to get started.\n"));
585
- return;
586
- }
587
- try {
588
- const raw = JSON.parse(fs_1.default.readFileSync(CONFIG_PATH, "utf-8"));
589
- process.stdout.write("\n");
590
- if (raw.network)
591
- process.stdout.write(` ${chalk_1.default.dim("Network")} ${chalk_1.default.white(raw.network)}\n`);
592
- if (raw.walletContractAddress) {
593
- const w = raw.walletContractAddress;
594
- process.stdout.write(` ${chalk_1.default.dim("Wallet")} ${chalk_1.default.white(`${w.slice(0, 6)}...${w.slice(-4)}`)}\n`);
595
- }
596
- if (raw.rpcUrl && raw.walletContractAddress) {
597
- try {
598
- // eslint-disable-next-line @typescript-eslint/no-var-requires
599
- const ethersLib = require("ethers");
600
- const provider = new ethersLib.ethers.JsonRpcProvider(raw.rpcUrl);
601
- const bal = await Promise.race([
602
- provider.getBalance(raw.walletContractAddress),
603
- new Promise((_, r) => setTimeout(() => r(new Error("timeout")), 2000)),
604
- ]);
605
- process.stdout.write(` ${chalk_1.default.dim("Balance")} ${chalk_1.default.white(`${parseFloat(ethersLib.ethers.formatEther(bal)).toFixed(4)} ETH`)}\n`);
606
- }
607
- catch {
608
- /* skip */
609
- }
610
- }
611
- process.stdout.write("\n");
612
- }
613
- catch {
614
- /* skip */
615
- }
616
- }
617
- // ── Built-in: help ────────────────────────────────────────────────────────────
618
- async runHelp() {
619
- write(ansi.move(this.scrollBot, 1));
620
- process.stdin.removeListener("data", this.boundKeyHandler);
621
- const prog = (0, program_1.createProgram)();
622
- prog.exitOverride();
623
- prog.configureOutput({
624
- writeOut: (str) => process.stdout.write(str),
625
- writeErr: (str) => process.stderr.write(str),
626
- });
627
- try {
628
- await prog.parseAsync(["node", "arc402", "--help"]);
629
- }
630
- catch {
631
- /* commander throws after printing help */
632
- }
633
- if (process.stdin.isTTY)
634
- process.stdin.setRawMode(true);
635
- process.stdin.on("data", this.boundKeyHandler);
636
- process.stdout.write("\n");
637
- process.stdout.write(chalk_1.default.cyanBright("Chat") + "\n");
638
- process.stdout.write(" " +
639
- chalk_1.default.white("<message>") +
640
- chalk_1.default.dim(" Send message to OpenClaw gateway\n"));
641
- process.stdout.write(" " +
642
- chalk_1.default.white("/chat <message>") +
643
- chalk_1.default.dim(" Explicitly route to chat\n"));
644
- process.stdout.write(chalk_1.default.dim(" Gateway: http://localhost:19000 (openclaw gateway start)\n"));
645
147
  process.stdout.write("\n");
646
- }
647
- // ── Exit ──────────────────────────────────────────────────────────────────────
648
- exitGracefully() {
649
- // Save history
650
- try {
651
- const toSave = this.history.slice(-MAX_HISTORY);
652
- fs_1.default.mkdirSync(path_1.default.dirname(HISTORY_PATH), { recursive: true });
653
- fs_1.default.writeFileSync(HISTORY_PATH, toSave.join("\n") + "\n", { mode: 0o600 });
654
- }
655
- catch { /* non-fatal */ }
656
- write(ansi.move(this.inputRow, 1) + ansi.clearLine);
657
- write(" " + chalk_1.default.cyanBright("◈") + chalk_1.default.dim(" goodbye") + "\n");
658
- write(ansi.resetScroll);
659
- write(ansi.showCursor);
660
- if (process.stdin.isTTY) {
661
- process.stdin.setRawMode(false);
662
- }
148
+ rl.prompt();
149
+ });
150
+ rl.on("close", () => {
151
+ process.stdout.write("\n " + chalk_1.default.cyanBright("◈") + chalk_1.default.dim(" goodbye") + "\n");
663
152
  process.exit(0);
664
- }
665
- }
666
- // ─── REPL entry point ─────────────────────────────────────────────────────────
667
- async function startREPL() {
668
- if (!process.stdout.isTTY) {
669
- // Non-TTY (piped): fall back to minimal line-mode output
670
- const bannerCfg = await loadBannerConfig();
671
- for (const line of (0, banner_1.getBannerLines)(bannerCfg)) {
672
- process.stdout.write(line + "\n");
673
- }
674
- process.stdout.write("Interactive TUI requires a TTY. Use arc402 <command> directly.\n");
675
- return;
676
- }
677
- const tui = new TUI();
678
- await tui.start();
153
+ });
154
+ // Keep alive
155
+ await new Promise(() => {
156
+ /* readline keeps event loop alive */
157
+ });
679
158
  }
680
159
  //# sourceMappingURL=repl.js.map