arc402-cli 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +17 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +205 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/wallet.js +467 -23
- package/dist/commands/wallet.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -2
- package/dist/config.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +294 -208
- package/dist/daemon/index.js.map +1 -1
- package/dist/endpoint-notify.d.ts +7 -0
- package/dist/endpoint-notify.d.ts.map +1 -1
- package/dist/endpoint-notify.js +104 -0
- package/dist/endpoint-notify.js.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +2 -0
- package/dist/program.js.map +1 -1
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +565 -162
- 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/format.d.ts.map +1 -1
- package/dist/ui/format.js +2 -0
- package/dist/ui/format.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 +18 -2
- package/src/commands/doctor.ts +172 -0
- package/src/commands/wallet.ts +512 -35
- package/src/config.ts +10 -1
- package/src/daemon/index.ts +234 -140
- package/src/endpoint-notify.ts +73 -0
- package/src/index.ts +15 -1
- package/src/program.ts +2 -0
- package/src/repl.ts +673 -197
- package/src/ui/banner.ts +26 -19
- package/src/ui/format.ts +1 -0
- 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,22 +11,10 @@ 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
|
-
// ─── Prompt ───────────────────────────────────────────────────────────────────
|
|
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 ───────────────
|
|
23
|
-
class REPLExitSignal extends Error {
|
|
24
|
-
constructor(code = 0) {
|
|
25
|
-
super("repl-exit-signal");
|
|
26
|
-
this.code = code;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
14
|
// ─── Config / banner helpers ──────────────────────────────────────────────────
|
|
30
15
|
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;
|
|
31
18
|
async function loadBannerConfig() {
|
|
32
19
|
if (!fs_1.default.existsSync(CONFIG_PATH))
|
|
33
20
|
return undefined;
|
|
@@ -59,53 +46,51 @@ async function loadBannerConfig() {
|
|
|
59
46
|
return undefined;
|
|
60
47
|
}
|
|
61
48
|
}
|
|
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();
|
|
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);
|
|
99
64
|
}
|
|
100
|
-
// ───
|
|
65
|
+
// ─── Prompt constants ─────────────────────────────────────────────────────────
|
|
66
|
+
const PROMPT_TEXT = chalk_1.default.cyanBright("◈") +
|
|
67
|
+
" " +
|
|
68
|
+
chalk_1.default.dim("arc402") +
|
|
69
|
+
" " +
|
|
70
|
+
chalk_1.default.white(">") +
|
|
71
|
+
" ";
|
|
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
|
+
// ─── Shell-style tokenizer ────────────────────────────────────────────────────
|
|
101
77
|
function parseTokens(input) {
|
|
102
78
|
const tokens = [];
|
|
103
79
|
let current = "";
|
|
104
80
|
let inQuote = false;
|
|
105
81
|
let quoteChar = "";
|
|
82
|
+
let escape = false;
|
|
106
83
|
for (const ch of input) {
|
|
84
|
+
if (escape) {
|
|
85
|
+
current += ch;
|
|
86
|
+
escape = false;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
107
89
|
if (inQuote) {
|
|
108
|
-
if (ch ===
|
|
90
|
+
if (quoteChar === '"' && ch === "\\") {
|
|
91
|
+
escape = true;
|
|
92
|
+
}
|
|
93
|
+
else if (ch === quoteChar) {
|
|
109
94
|
inQuote = false;
|
|
110
95
|
}
|
|
111
96
|
else {
|
|
@@ -130,107 +115,356 @@ function parseTokens(input) {
|
|
|
130
115
|
tokens.push(current);
|
|
131
116
|
return tokens;
|
|
132
117
|
}
|
|
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
|
-
};
|
|
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}`);
|
|
155
130
|
}
|
|
156
|
-
// ───
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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));
|
|
168
221
|
}
|
|
222
|
+
// Position cursor at top of output area
|
|
223
|
+
write(ansi.move(this.scrollTop, 1));
|
|
224
|
+
write(ansi.showCursor);
|
|
169
225
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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);
|
|
180
238
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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;
|
|
393
|
+
}
|
|
394
|
+
// Show options in output area
|
|
395
|
+
this.writeOutput("\n" + chalk_1.default.dim(completions.join(" ")) + "\n");
|
|
396
|
+
this.drawInputLine();
|
|
397
|
+
}
|
|
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);
|
|
403
|
+
}
|
|
404
|
+
// ── Submit line ──────────────────────────────────────────────────────────────
|
|
405
|
+
async submit() {
|
|
406
|
+
const input = this.inputBuffer.trim();
|
|
407
|
+
this.inputBuffer = "";
|
|
408
|
+
this.cursorPos = 0;
|
|
409
|
+
this.historyIdx = -1;
|
|
194
410
|
if (!input) {
|
|
195
|
-
|
|
196
|
-
|
|
411
|
+
this.drawInputLine();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
// Add to history
|
|
415
|
+
if (input !== this.history[this.history.length - 1]) {
|
|
416
|
+
this.history.push(input);
|
|
197
417
|
}
|
|
198
|
-
//
|
|
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 ──────────────────────────────────────────────────────
|
|
199
421
|
if (input === "exit" || input === "quit") {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
process.exit(0);
|
|
422
|
+
this.exitGracefully();
|
|
423
|
+
return;
|
|
203
424
|
}
|
|
204
425
|
if (input === "clear") {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
continue;
|
|
426
|
+
this.bannerCfg = await loadBannerConfig();
|
|
427
|
+
this.setupScreen();
|
|
428
|
+
this.drawInputLine();
|
|
429
|
+
return;
|
|
210
430
|
}
|
|
211
431
|
if (input === "status") {
|
|
212
|
-
await
|
|
213
|
-
|
|
214
|
-
|
|
432
|
+
await this.runStatus();
|
|
433
|
+
this.afterCommand();
|
|
434
|
+
return;
|
|
215
435
|
}
|
|
216
|
-
if (input === "help" || input === "help
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
/* commander throws after printing help — ignore */
|
|
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;
|
|
229
448
|
}
|
|
230
|
-
|
|
231
|
-
|
|
449
|
+
this.afterCommand();
|
|
450
|
+
return;
|
|
232
451
|
}
|
|
233
|
-
// ──
|
|
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();
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
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);
|
|
234
468
|
const tokens = parseTokens(input);
|
|
235
469
|
const prog = (0, program_1.createProgram)();
|
|
236
470
|
prog.exitOverride();
|
|
@@ -238,40 +472,209 @@ async function startREPL() {
|
|
|
238
472
|
writeOut: (str) => process.stdout.write(str),
|
|
239
473
|
writeErr: (str) => process.stderr.write(str),
|
|
240
474
|
});
|
|
241
|
-
// Intercept process.exit() so a command exiting doesn't kill the REPL
|
|
242
|
-
const origExit = process.exit;
|
|
243
|
-
process.exit = ((code) => {
|
|
244
|
-
throw new REPLExitSignal(code ?? 0);
|
|
245
|
-
});
|
|
246
475
|
try {
|
|
247
476
|
await prog.parseAsync(["node", "arc402", ...tokens]);
|
|
248
477
|
}
|
|
249
478
|
catch (err) {
|
|
250
|
-
|
|
251
|
-
|
|
479
|
+
const e = err;
|
|
480
|
+
if (e.code === "commander.helpDisplayed" ||
|
|
481
|
+
e.code === "commander.version" ||
|
|
482
|
+
e.code === "commander.executeSubCommandAsync") {
|
|
483
|
+
// already written or normal exit
|
|
484
|
+
}
|
|
485
|
+
else if (e.code === "commander.unknownCommand") {
|
|
486
|
+
process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(`Unknown command: ${chalk_1.default.white(tokens[0])}`)} \n`);
|
|
487
|
+
process.stdout.write(chalk_1.default.dim(" Type 'help' for available commands\n"));
|
|
488
|
+
}
|
|
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
|
+
else {
|
|
493
|
+
process.stdout.write(`\n ${colors_1.c.failure} ${chalk_1.default.red(e.message ?? String(err))}\n`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
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");
|
|
252
530
|
}
|
|
253
531
|
else {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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;
|
|
258
550
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
console.log(chalk_1.default.dim(" Type 'help' for available commands\n"));
|
|
551
|
+
catch {
|
|
552
|
+
/* use raw */
|
|
262
553
|
}
|
|
263
|
-
|
|
264
|
-
|
|
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`);
|
|
265
606
|
}
|
|
266
|
-
|
|
267
|
-
|
|
607
|
+
catch {
|
|
608
|
+
/* skip */
|
|
268
609
|
}
|
|
269
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"]);
|
|
270
629
|
}
|
|
271
|
-
|
|
272
|
-
|
|
630
|
+
catch {
|
|
631
|
+
/* commander throws after printing help */
|
|
273
632
|
}
|
|
274
|
-
|
|
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
|
+
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
|
+
}
|
|
663
|
+
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;
|
|
275
676
|
}
|
|
677
|
+
const tui = new TUI();
|
|
678
|
+
await tui.start();
|
|
276
679
|
}
|
|
277
680
|
//# sourceMappingURL=repl.js.map
|