aether-code 0.43.0 → 0.43.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/LICENSE +21 -21
- package/bin/aether-code.js +6 -4
- package/package.json +4 -2
- package/scripts/postinstall.js +182 -0
- package/skills/adult-creative-writing.md +60 -60
- package/skills/debugging.md +51 -51
- package/skills/game-modding.md +73 -73
- package/skills/reverse-engineering.md +41 -41
- package/skills/scraping-automation.md +77 -77
- package/skills/security-research.md +67 -67
- package/src/agent.js +168 -267
- package/src/api.js +22 -32
- package/src/box-input.js +141 -36
- package/src/config.js +38 -38
- package/src/diff.js +49 -49
- package/src/ink-input.js +91 -91
- package/src/mcp-cli.js +94 -94
- package/src/mcp-registry.js +266 -266
- package/src/mcp.js +259 -260
- package/src/menu.js +83 -83
- package/src/render.js +288 -254
- package/src/repl.js +408 -444
- package/src/setup.js +6 -8
- package/src/tools.js +16 -393
- package/src/update-check.js +64 -7
- package/src/project-context.js +0 -712
- package/src/sessions.js +0 -167
- package/src/version.js +0 -15
package/src/repl.js
CHANGED
|
@@ -1,444 +1,408 @@
|
|
|
1
|
-
// Interactive REPL — Claude-CLI-style. Launched when `aether-code` is run
|
|
2
|
-
// without a task argument.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
${
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
case "
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
console.log(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
function printBanner(state
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
c.gray("uncensored AI coding agent"),
|
|
410
|
-
c.gray((credits ? credits + " · " : "") + mode),
|
|
411
|
-
c.gray(shortenPath(state.cwd, 52)),
|
|
412
|
-
];
|
|
413
|
-
if (projectLine) {
|
|
414
|
-
leftLines.push(c.gray("project: " + (projectLine.length > 48 ? projectLine.slice(0, 45) + "..." : projectLine)));
|
|
415
|
-
}
|
|
416
|
-
if (resumed) {
|
|
417
|
-
leftLines.push(c.cyan("↻ resumed session") + c.gray(` · ${state.turnCount} turns · ${state.messages.length} msgs`));
|
|
418
|
-
}
|
|
419
|
-
const left = boxLines(leftLines, { color: c.magenta, padX: 2 });
|
|
420
|
-
const right = boxLines(
|
|
421
|
-
[c.bold(c.cyan("What's new")), ...WHATS_NEW.map((s) => c.gray("• " + s))],
|
|
422
|
-
{ color: c.cyan, padX: 2 },
|
|
423
|
-
);
|
|
424
|
-
|
|
425
|
-
console.log("");
|
|
426
|
-
const cols = process.stdout.columns || 80;
|
|
427
|
-
if (cols >= visLen(left[0]) + visLen(right[0]) + 4) {
|
|
428
|
-
console.log(sideBySide(left, right, 3));
|
|
429
|
-
} else {
|
|
430
|
-
console.log(left.join("\n"));
|
|
431
|
-
}
|
|
432
|
-
console.log("");
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function printStatusLine(state) {
|
|
436
|
-
const parts = [];
|
|
437
|
-
parts.push(c.gray(`session: ${state.sessionCredits} cr · ${state.sessionIn}→${state.sessionOut} tokens`));
|
|
438
|
-
if (state.balance != null) parts.push(c.gray(`balance: ${state.balance.toLocaleString()}`));
|
|
439
|
-
if (state.messages.length > 0) {
|
|
440
|
-
parts.push(c.gray(`history: ${state.messages.length} msg${state.messages.length === 1 ? "" : "s"}`));
|
|
441
|
-
}
|
|
442
|
-
parts.push(c.cyan(state.autoYes ? "auto-yes" : "review"));
|
|
443
|
-
console.log(c.dim(parts.join(" · ")));
|
|
444
|
-
}
|
|
1
|
+
// Interactive REPL — Claude-CLI-style. Launched when `aether-code` is run
|
|
2
|
+
// without a task argument.
|
|
3
|
+
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { runAgent } from "./agent.js";
|
|
6
|
+
import { fetchBalance, AetherError } from "./api.js";
|
|
7
|
+
import { runSetup } from "./setup.js";
|
|
8
|
+
import { c, errorLine, turnStats } from "./render.js";
|
|
9
|
+
import { autoUpdate } from "./update-check.js";
|
|
10
|
+
import { promptBox, EXIT_SIGNAL } from "./box-input.js";
|
|
11
|
+
|
|
12
|
+
const VERSION = "0.43.1";
|
|
13
|
+
const MODEL_NAME = "Aether Core";
|
|
14
|
+
|
|
15
|
+
const MODEL_ALIASES = {
|
|
16
|
+
core: { id: null, name: "Aether Core" },
|
|
17
|
+
gemma: { id: null, name: "Aether Core" },
|
|
18
|
+
ultra: { id: "claude-opus-4-6", name: "Aether Ultra" },
|
|
19
|
+
opus: { id: "claude-opus-4-6", name: "Aether Ultra" },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const SHORTCUTS = `
|
|
23
|
+
${c.cyan("/help")} Show this help
|
|
24
|
+
${c.cyan("/exit")} Exit (or Ctrl+C twice)
|
|
25
|
+
${c.cyan("/clear")} Clear conversation history
|
|
26
|
+
${c.cyan("/balance")} Show credit balance
|
|
27
|
+
${c.cyan("/cwd")} ${c.gray("[path]")} Show or change working directory
|
|
28
|
+
${c.cyan("/yes")} Toggle auto-accept mode (skip confirmations)
|
|
29
|
+
${c.cyan("/turns")} ${c.gray("<n>")} Set max agent turns per prompt (default 25)
|
|
30
|
+
${c.cyan("/model")} ${c.gray("[name]")} Switch model ${c.gray("(core | ultra)")}
|
|
31
|
+
|
|
32
|
+
${c.dim("Anything else is sent as your next message. History persists until /clear.")}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
36
|
+
|
|
37
|
+
function getVisibleRows() {
|
|
38
|
+
let rows = process.stdout.rows || 24;
|
|
39
|
+
if (process.platform === "win32" && !process.env.WT_SESSION && rows > 60) {
|
|
40
|
+
rows = 24;
|
|
41
|
+
}
|
|
42
|
+
return rows;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function pushPromptToBottom(contentLines) {
|
|
46
|
+
const visibleRows = getVisibleRows();
|
|
47
|
+
const promptHeight = 4;
|
|
48
|
+
const gap = Math.max(0, visibleRows - contentLines - promptHeight);
|
|
49
|
+
if (gap > 0) process.stdout.write("\n".repeat(gap));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function drawStaticPrompt(startRow, state) {
|
|
53
|
+
const termCols = process.stdout.columns || 80;
|
|
54
|
+
const ruleStr = `\x1b[36m${"─".repeat(Math.max(0, termCols))}\x1b[0m`;
|
|
55
|
+
const modeStr = state.autoYes ? "auto-accept" : "confirm writes";
|
|
56
|
+
const blkFn = (code) => `\x1b[${code}m \x1b[0m`;
|
|
57
|
+
const statusBar = blkFn("48;2;209;107;52") + blkFn("48;2;0;178;169") +
|
|
58
|
+
`\x1b[2m ${modeStr} \xB7 /help for shortcuts \xB7 @file to include\x1b[0m`;
|
|
59
|
+
process.stdout.write(
|
|
60
|
+
`\x1b[${startRow};1H` +
|
|
61
|
+
ruleStr + "\n" +
|
|
62
|
+
`\x1b[2m> \x1b[0m` + "\n" +
|
|
63
|
+
ruleStr + "\n" +
|
|
64
|
+
statusBar,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafePaths: initialUnsafePaths, maxTurns: initialMaxTurns, model: initialModel = null, mcpManager = null }) {
|
|
69
|
+
const initialAlias = Object.values(MODEL_ALIASES).find((a) => a.id === initialModel);
|
|
70
|
+
const state = {
|
|
71
|
+
cwd: initialCwd,
|
|
72
|
+
autoYes: !!initialAutoYes,
|
|
73
|
+
unsafePaths: !!initialUnsafePaths,
|
|
74
|
+
maxTurns: initialMaxTurns ?? 25,
|
|
75
|
+
model: initialModel ?? null,
|
|
76
|
+
modelName: initialAlias?.name ?? MODEL_NAME,
|
|
77
|
+
messages: [],
|
|
78
|
+
balance: null,
|
|
79
|
+
sessionCredits: 0,
|
|
80
|
+
sessionIn: 0,
|
|
81
|
+
sessionOut: 0,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const updatePromise = autoUpdate().catch(() => null);
|
|
85
|
+
|
|
86
|
+
let needsSetup = false;
|
|
87
|
+
try {
|
|
88
|
+
const me = await fetchBalance();
|
|
89
|
+
state.balance = me.balance;
|
|
90
|
+
state.plan = me.plan || "FREE";
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err instanceof AetherError && (err.code === "NO_API_KEY" || err.status === 401)) {
|
|
93
|
+
needsSetup = true;
|
|
94
|
+
} else {
|
|
95
|
+
console.log(c.gray(`(could not fetch balance: ${err.message})`));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (needsSetup) {
|
|
100
|
+
const ok = await runSetup();
|
|
101
|
+
if (!ok) {
|
|
102
|
+
console.log(c.gray("Aborting — no valid API key."));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const me = await fetchBalance();
|
|
107
|
+
state.balance = me.balance;
|
|
108
|
+
state.plan = me.plan || "FREE";
|
|
109
|
+
} catch { /* tolerate */ }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Clear screen — clean start, no leftover shell text (matches Claude Code)
|
|
113
|
+
process.stdout.write("\x1b[2J\x1b[H");
|
|
114
|
+
|
|
115
|
+
const bannerLineCount = printBanner(state);
|
|
116
|
+
|
|
117
|
+
const updateMsg = await updatePromise;
|
|
118
|
+
let extraLines = 0;
|
|
119
|
+
if (updateMsg) { console.log(updateMsg); extraLines++; }
|
|
120
|
+
|
|
121
|
+
pushPromptToBottom(bannerLineCount + extraLines);
|
|
122
|
+
|
|
123
|
+
const inputHistory = [];
|
|
124
|
+
|
|
125
|
+
const handlePromptResize = () => {
|
|
126
|
+
const vr = getVisibleRows();
|
|
127
|
+
process.stdout.write("\x1b[2J\x1b[H");
|
|
128
|
+
printBanner(state);
|
|
129
|
+
process.stdout.write(`\x1b[${Math.max(1, vr - 3)};1H`);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
while (true) {
|
|
133
|
+
const raw = await promptBox({ history: inputHistory, mode: state.autoYes ? "auto-yes" : "review", suppressEcho: true, onResize: handlePromptResize });
|
|
134
|
+
if (raw === EXIT_SIGNAL) { console.log(c.gray("bye.")); return; }
|
|
135
|
+
const line = (raw ?? "").trim();
|
|
136
|
+
if (!line) continue;
|
|
137
|
+
|
|
138
|
+
inputHistory.push(line);
|
|
139
|
+
|
|
140
|
+
if (line.startsWith("/") || line === "?") {
|
|
141
|
+
const handled = await handleSlash(line, state);
|
|
142
|
+
if (handled === "exit") return;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Build banner + echo as one buffer, write atomically (no flash, no garbling)
|
|
147
|
+
const bLines = buildBannerLines(state);
|
|
148
|
+
const echoDisplay = line.length > 200 ? line.slice(0, 197) + "..." : line;
|
|
149
|
+
const echoCols = process.stdout.columns || 80;
|
|
150
|
+
const echoLen = echoDisplay.length + 2;
|
|
151
|
+
const echoPad = Math.max(0, echoCols - echoLen);
|
|
152
|
+
const echoBar = `\x1b[48;5;236m ${echoDisplay}${" ".repeat(echoPad)}\x1b[0m`;
|
|
153
|
+
|
|
154
|
+
const vr = getVisibleRows();
|
|
155
|
+
let scrollEnd = Math.max(4, vr - 4);
|
|
156
|
+
let promptRow = scrollEnd + 1;
|
|
157
|
+
|
|
158
|
+
// Clear screen, print banner + echo, draw static prompt at bottom,
|
|
159
|
+
// then set scroll region so agent output stays above the prompt.
|
|
160
|
+
process.stdout.write(
|
|
161
|
+
"\x1b[r" +
|
|
162
|
+
"\x1b[2J\x1b[H" +
|
|
163
|
+
bLines.join("\n") + "\n" + echoBar + "\n",
|
|
164
|
+
);
|
|
165
|
+
drawStaticPrompt(promptRow, state);
|
|
166
|
+
|
|
167
|
+
const agentRow = Math.min(bLines.length + 2, scrollEnd);
|
|
168
|
+
process.stdout.write(`\x1b[1;${scrollEnd}r\x1b[${agentRow};1H`);
|
|
169
|
+
|
|
170
|
+
const onResize = () => {
|
|
171
|
+
const newVr = getVisibleRows();
|
|
172
|
+
const newScrollEnd = Math.max(4, newVr - 4);
|
|
173
|
+
const newPromptRow = newScrollEnd + 1;
|
|
174
|
+
process.stdout.write(
|
|
175
|
+
"\x1b7" +
|
|
176
|
+
"\x1b[r" +
|
|
177
|
+
`\x1b[${promptRow};1H\x1b[J`,
|
|
178
|
+
);
|
|
179
|
+
drawStaticPrompt(newPromptRow, state);
|
|
180
|
+
process.stdout.write(
|
|
181
|
+
`\x1b[1;${newScrollEnd}r` +
|
|
182
|
+
"\x1b8",
|
|
183
|
+
);
|
|
184
|
+
scrollEnd = newScrollEnd;
|
|
185
|
+
promptRow = newPromptRow;
|
|
186
|
+
};
|
|
187
|
+
process.stdout.on("resize", onResize);
|
|
188
|
+
|
|
189
|
+
const t0 = Date.now();
|
|
190
|
+
let result;
|
|
191
|
+
try {
|
|
192
|
+
result = await runAgent({
|
|
193
|
+
initialPrompt: line,
|
|
194
|
+
priorMessages: state.messages.length > 0 ? state.messages : undefined,
|
|
195
|
+
cwd: state.cwd,
|
|
196
|
+
autoYes: state.autoYes,
|
|
197
|
+
unsafePaths: state.unsafePaths,
|
|
198
|
+
model: state.model,
|
|
199
|
+
maxTurns: state.maxTurns,
|
|
200
|
+
mcpManager,
|
|
201
|
+
});
|
|
202
|
+
} finally {
|
|
203
|
+
process.stdout.removeListener("resize", onResize);
|
|
204
|
+
process.stdout.write("\x1b[r");
|
|
205
|
+
}
|
|
206
|
+
const elapsed = Date.now() - t0;
|
|
207
|
+
|
|
208
|
+
state.sessionCredits += result.totalCredits ?? 0;
|
|
209
|
+
state.sessionIn += result.totalIn ?? 0;
|
|
210
|
+
state.sessionOut += result.totalOut ?? 0;
|
|
211
|
+
if (typeof result.balance === "number") state.balance = result.balance;
|
|
212
|
+
if (result.messages) state.messages = result.messages;
|
|
213
|
+
|
|
214
|
+
if (!result.ok && result.error) {
|
|
215
|
+
console.log("\n" + errorLine(result.error.message || String(result.error)));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const stats = turnStats({
|
|
219
|
+
elapsed,
|
|
220
|
+
tokensIn: 0,
|
|
221
|
+
tokensOut: 0,
|
|
222
|
+
tools: 0,
|
|
223
|
+
credits: result.totalCredits ?? 0,
|
|
224
|
+
});
|
|
225
|
+
if (stats) console.log(stats);
|
|
226
|
+
console.log("");
|
|
227
|
+
|
|
228
|
+
// Position cursor at prompt area for the interactive prompt
|
|
229
|
+
process.stdout.write(`\x1b[${promptRow};1H\x1b[J`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* ───────── slash commands ───────── */
|
|
234
|
+
|
|
235
|
+
async function handleSlash(line, state) {
|
|
236
|
+
const [cmd, ...rest] = line.replace(/^\//, "").split(/\s+/);
|
|
237
|
+
const arg = rest.join(" ").trim();
|
|
238
|
+
|
|
239
|
+
switch ((cmd || "").toLowerCase()) {
|
|
240
|
+
case "":
|
|
241
|
+
case "help":
|
|
242
|
+
case "?":
|
|
243
|
+
console.log(SHORTCUTS);
|
|
244
|
+
break;
|
|
245
|
+
case "exit":
|
|
246
|
+
case "quit":
|
|
247
|
+
case "q":
|
|
248
|
+
console.log(c.gray("bye."));
|
|
249
|
+
return "exit";
|
|
250
|
+
case "clear":
|
|
251
|
+
state.messages = [];
|
|
252
|
+
state.sessionCredits = 0;
|
|
253
|
+
state.sessionIn = 0;
|
|
254
|
+
state.sessionOut = 0;
|
|
255
|
+
console.log(c.gray("Conversation cleared."));
|
|
256
|
+
break;
|
|
257
|
+
case "balance": {
|
|
258
|
+
try {
|
|
259
|
+
const me = await fetchBalance();
|
|
260
|
+
state.balance = me.balance;
|
|
261
|
+
state.plan = me.plan || "FREE";
|
|
262
|
+
console.log(
|
|
263
|
+
c.cyan(`balance: ${me.balance.toLocaleString()} credits`) +
|
|
264
|
+
c.gray(` \xB7 plan: ${me.plan || "FREE"} (${me.planCredits ?? 0} plan + ${me.topupCredits ?? 0} topup)`),
|
|
265
|
+
);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
console.log(errorLine(err.message || String(err)));
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case "cwd":
|
|
272
|
+
if (arg) {
|
|
273
|
+
const next = path.resolve(arg);
|
|
274
|
+
try {
|
|
275
|
+
process.chdir(next);
|
|
276
|
+
state.cwd = next;
|
|
277
|
+
console.log(c.gray(`cwd -> ${state.cwd}`));
|
|
278
|
+
} catch (err) {
|
|
279
|
+
console.log(errorLine(err.message));
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
console.log(c.gray(`cwd: ${state.cwd}`));
|
|
283
|
+
}
|
|
284
|
+
break;
|
|
285
|
+
case "yes":
|
|
286
|
+
state.autoYes = !state.autoYes;
|
|
287
|
+
console.log(c.gray(`auto-yes: ${state.autoYes ? "on (writes/shells will skip y/N)" : "off"}`));
|
|
288
|
+
break;
|
|
289
|
+
case "turns": {
|
|
290
|
+
const n = parseInt(arg, 10);
|
|
291
|
+
if (!Number.isFinite(n) || n < 1 || n > 200) {
|
|
292
|
+
console.log(errorLine(`/turns expects 1..200, got "${arg}"`));
|
|
293
|
+
} else {
|
|
294
|
+
state.maxTurns = n;
|
|
295
|
+
console.log(c.gray(`max turns: ${state.maxTurns}`));
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case "model": {
|
|
300
|
+
const key = (arg || "").trim().toLowerCase();
|
|
301
|
+
if (!key) {
|
|
302
|
+
console.log(c.gray(`model: ${state.modelName}`));
|
|
303
|
+
console.log(c.gray("switch with /model core | ultra (ultra=Opus — premium, need credits)"));
|
|
304
|
+
} else if (MODEL_ALIASES[key]) {
|
|
305
|
+
state.model = MODEL_ALIASES[key].id;
|
|
306
|
+
state.modelName = MODEL_ALIASES[key].name;
|
|
307
|
+
const premium = state.model !== null;
|
|
308
|
+
console.log(
|
|
309
|
+
c.gray(`model -> ${c.cyan(state.modelName)}`) +
|
|
310
|
+
(premium ? c.gray(" (premium — needs purchased credits, else falls back to Core)") : ""),
|
|
311
|
+
);
|
|
312
|
+
} else {
|
|
313
|
+
console.log(errorLine(`Unknown model "${key}". Try: core, ultra`));
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
default:
|
|
318
|
+
console.log(errorLine(`Unknown command: /${cmd}. Type ${c.cyan("/help")} for shortcuts.`));
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/* ───────── banner (two-column like Claude Code) ───────── */
|
|
324
|
+
|
|
325
|
+
const shortenPath = (p, max) => (p.length <= max ? p : "..." + p.slice(p.length - max + 3));
|
|
326
|
+
|
|
327
|
+
function padRight(str, width) {
|
|
328
|
+
const visible = stripAnsi(str).length;
|
|
329
|
+
const need = Math.max(0, width - visible);
|
|
330
|
+
return str + " ".repeat(need);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function centerIn(str, width) {
|
|
334
|
+
const visible = stripAnsi(str).length;
|
|
335
|
+
if (visible >= width) return str;
|
|
336
|
+
const left = Math.floor((width - visible) / 2);
|
|
337
|
+
return " ".repeat(left) + str;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function buildBannerLines(state) {
|
|
341
|
+
const cols = process.stdout.columns || 80;
|
|
342
|
+
const credits = state.balance != null ? `${state.balance.toLocaleString()} credits` : "";
|
|
343
|
+
const plan = state.plan && state.plan !== "FREE" ? state.plan : "";
|
|
344
|
+
|
|
345
|
+
const blk = (code) => `\x1b[${code}m \x1b[0m`;
|
|
346
|
+
const O = blk("48;2;209;107;52");
|
|
347
|
+
const W = blk("48;2;255;255;255");
|
|
348
|
+
const T = blk("48;2;0;178;169");
|
|
349
|
+
|
|
350
|
+
const modelParts = [state.modelName];
|
|
351
|
+
if (credits) modelParts.push(credits);
|
|
352
|
+
if (plan) modelParts.push(c.cyan(plan));
|
|
353
|
+
const modelLine = modelParts.join(c.dim(" \xB7 "));
|
|
354
|
+
|
|
355
|
+
const ruleW = cols - 2;
|
|
356
|
+
const leftW = Math.min(Math.floor(cols * 0.5), 45);
|
|
357
|
+
const twoCol = cols >= 70;
|
|
358
|
+
|
|
359
|
+
const titleText = " Aether Code v" + VERSION + " ";
|
|
360
|
+
const titleRuleRight = Math.max(0, ruleW - 3 - titleText.length);
|
|
361
|
+
const topRule = c.cyan("╭─" + titleText + "─".repeat(titleRuleRight) + "╮");
|
|
362
|
+
|
|
363
|
+
const VL = c.cyan("│");
|
|
364
|
+
const lines = [];
|
|
365
|
+
|
|
366
|
+
lines.push("");
|
|
367
|
+
lines.push(" " + topRule);
|
|
368
|
+
|
|
369
|
+
const bottomRule = c.cyan(" ╰" + "─".repeat(ruleW) + "╯");
|
|
370
|
+
|
|
371
|
+
if (twoCol) {
|
|
372
|
+
const row = (left, right) => {
|
|
373
|
+
lines.push(" " + VL + padRight(left, leftW) + " " + VL + " " + padRight(right, Math.max(0, ruleW - leftW - 4)) + VL);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
row("", "");
|
|
377
|
+
row(centerIn(`${O}${T}${O}`, leftW), c.bold("Tips"));
|
|
378
|
+
row(centerIn(`${T}${O}${T}`, leftW), c.dim("Ask Aether to build, debug, or refactor code"));
|
|
379
|
+
row(centerIn(`${O}${T}${O}`, leftW), c.dim("Use /help for shortcuts and /balance for credits"));
|
|
380
|
+
row("", c.dim("Type @ to include file paths or image paths"));
|
|
381
|
+
row(centerIn(c.bold("/model") + c.dim(":") + c.cyan(state.modelName.toLowerCase().replace("aether ", "")), leftW), "");
|
|
382
|
+
row(centerIn(modelLine, leftW), "");
|
|
383
|
+
row("", "");
|
|
384
|
+
row(centerIn(c.dim("cwd: " + shortenPath(state.cwd, 40)), leftW), "");
|
|
385
|
+
row("", "");
|
|
386
|
+
} else {
|
|
387
|
+
const srow = (content) => {
|
|
388
|
+
lines.push(" " + VL + " " + padRight(content, Math.max(0, ruleW - 2)) + VL);
|
|
389
|
+
};
|
|
390
|
+
srow("");
|
|
391
|
+
srow(centerIn(`${O}${T}${O}`, ruleW - 2));
|
|
392
|
+
srow(centerIn(`${T}${O}${T}`, ruleW - 2));
|
|
393
|
+
srow(centerIn(`${O}${T}${O}`, ruleW - 2));
|
|
394
|
+
srow("");
|
|
395
|
+
srow(centerIn(modelLine, ruleW - 2));
|
|
396
|
+
srow(centerIn(c.dim(shortenPath(state.cwd, 50)), ruleW - 2));
|
|
397
|
+
srow("");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
lines.push(bottomRule);
|
|
401
|
+
return lines;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function printBanner(state) {
|
|
405
|
+
const lines = buildBannerLines(state);
|
|
406
|
+
for (const l of lines) console.log(l);
|
|
407
|
+
return lines.length;
|
|
408
|
+
}
|