aether-code 0.33.4 → 0.33.6
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/bin/aether-code.js +1 -1
- package/package.json +1 -1
- package/src/box-input.js +13 -7
- package/src/render.js +268 -257
- package/src/repl.js +64 -14
package/bin/aether-code.js
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import readline from "node:readline";
|
|
29
29
|
import { c, errorLine, divider, setTerminalTitle } from "../src/render.js";
|
|
30
30
|
|
|
31
|
-
const VERSION = "0.33.
|
|
31
|
+
const VERSION = "0.33.6";
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Try to start MCP servers from ~/.aether/mcp.json. Returns a started
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aether-code",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.6",
|
|
4
4
|
"description": "Uncensored AI coding agent for your terminal — Claude Code alternative with MCP support. Reads code, writes files, runs commands. Drives IDA Pro, Roblox Studio, Wireshark, Blender, and any MCP server. No refusal layer.",
|
|
5
5
|
"homepage": "https://trynoguard.com",
|
|
6
6
|
"repository": {
|
package/src/box-input.js
CHANGED
|
@@ -113,15 +113,17 @@ export function promptBox({ history = [], placeholder = "Message Aether...", mod
|
|
|
113
113
|
// Bare prompt — just "> " like Claude Code
|
|
114
114
|
const promptLine = c.dim(prefixStr) + content;
|
|
115
115
|
|
|
116
|
-
// Bottom status bar —
|
|
116
|
+
// Bottom status bar — colored block prefix like Claude's
|
|
117
|
+
const blk = (code) => `\x1b[${code}m \x1b[0m`;
|
|
118
|
+
const barPrefix = blk(46) + blk(44);
|
|
117
119
|
const imgNote = clipImages.length > 0 ? c.cyan(" images queued") + c.dim(" \xB7 ") : "";
|
|
118
|
-
const
|
|
119
|
-
mode + " mode" +
|
|
120
|
+
const statusText = c.dim(
|
|
121
|
+
" " + mode + " mode" +
|
|
120
122
|
" \xB7 " + "? for shortcuts" +
|
|
121
123
|
" \xB7 " + "@file for images",
|
|
122
124
|
) + imgNote;
|
|
123
125
|
|
|
124
|
-
process.stdout.write(promptLine + "\n" +
|
|
126
|
+
process.stdout.write(promptLine + "\n" + barPrefix + statusText + "\n");
|
|
125
127
|
drawn = 2;
|
|
126
128
|
};
|
|
127
129
|
|
|
@@ -140,11 +142,15 @@ export function promptBox({ history = [], placeholder = "Message Aether...", mod
|
|
|
140
142
|
|
|
141
143
|
if (finalResult && finalResult !== EXIT_SIGNAL) {
|
|
142
144
|
const echoText = (result || "").trim();
|
|
143
|
-
const
|
|
144
|
-
?
|
|
145
|
+
const imgTag = clipImages.length > 0
|
|
146
|
+
? ` [+${clipImages.length} image${clipImages.length > 1 ? "s" : ""}]`
|
|
145
147
|
: "";
|
|
146
148
|
if (echoText || clipImages.length > 0) {
|
|
147
|
-
|
|
149
|
+
const displayText = echoText || "(image)";
|
|
150
|
+
const cols = process.stdout.columns || 80;
|
|
151
|
+
const textLen = displayText.length + imgTag.length + 2;
|
|
152
|
+
const pad = Math.max(0, cols - textLen);
|
|
153
|
+
process.stdout.write(`\x1b[48;5;236m ${displayText}${imgTag}${" ".repeat(pad)}\x1b[0m\n`);
|
|
148
154
|
}
|
|
149
155
|
}
|
|
150
156
|
resolve(finalResult);
|
package/src/render.js
CHANGED
|
@@ -1,257 +1,268 @@
|
|
|
1
|
-
// ANSI helpers — no chalk dependency.
|
|
2
|
-
|
|
3
|
-
const isTty = process.stdout.isTTY;
|
|
4
|
-
const noColor = !!process.env.NO_COLOR || !isTty;
|
|
5
|
-
|
|
6
|
-
function wrap(open, close) {
|
|
7
|
-
return (s) => (noColor ? String(s) : `\x1b[${open}m${s}\x1b[${close}m`);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const c = {
|
|
11
|
-
bold: wrap(1, 22),
|
|
12
|
-
dim: wrap(2, 22),
|
|
13
|
-
red: wrap(31, 39),
|
|
14
|
-
green: wrap(32, 39),
|
|
15
|
-
yellow: wrap(33, 39),
|
|
16
|
-
blue: wrap(34, 39),
|
|
17
|
-
magenta: wrap(35, 39),
|
|
18
|
-
cyan: wrap(36, 39),
|
|
19
|
-
gray: wrap(90, 39),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export function divider() {
|
|
23
|
-
const w = process.stdout.columns || 60;
|
|
24
|
-
return c.gray("─".repeat(Math.min(60, w)));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
case "
|
|
81
|
-
case "
|
|
82
|
-
case "
|
|
83
|
-
case "
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
1
|
+
// ANSI helpers — no chalk dependency.
|
|
2
|
+
|
|
3
|
+
const isTty = process.stdout.isTTY;
|
|
4
|
+
const noColor = !!process.env.NO_COLOR || !isTty;
|
|
5
|
+
|
|
6
|
+
function wrap(open, close) {
|
|
7
|
+
return (s) => (noColor ? String(s) : `\x1b[${open}m${s}\x1b[${close}m`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const c = {
|
|
11
|
+
bold: wrap(1, 22),
|
|
12
|
+
dim: wrap(2, 22),
|
|
13
|
+
red: wrap(31, 39),
|
|
14
|
+
green: wrap(32, 39),
|
|
15
|
+
yellow: wrap(33, 39),
|
|
16
|
+
blue: wrap(34, 39),
|
|
17
|
+
magenta: wrap(35, 39),
|
|
18
|
+
cyan: wrap(36, 39),
|
|
19
|
+
gray: wrap(90, 39),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function divider() {
|
|
23
|
+
const w = process.stdout.columns || 60;
|
|
24
|
+
return c.gray("─".repeat(Math.min(60, w)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
28
|
+
|
|
29
|
+
export function boxLines(lines, { color = c.gray, padX = 2, minInner = 0 } = {}) {
|
|
30
|
+
const inner = Math.max(minInner, 0, ...lines.map((l) => stripAnsi(l).length));
|
|
31
|
+
const w = inner + padX * 2;
|
|
32
|
+
const top = color("╭" + "─".repeat(w) + "╮");
|
|
33
|
+
const bot = color("╰" + "─".repeat(w) + "╯");
|
|
34
|
+
const mid = lines.map(
|
|
35
|
+
(l) => color("│") + " ".repeat(padX) + l + " ".repeat(inner - stripAnsi(l).length + padX) + color("│"),
|
|
36
|
+
);
|
|
37
|
+
return [top, ...mid, bot];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function box(lines, opts) {
|
|
41
|
+
return boxLines(lines, opts).join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function sideBySide(a, b, gap = 3) {
|
|
45
|
+
const wa = Math.max(0, ...a.map((l) => stripAnsi(l).length));
|
|
46
|
+
const h = Math.max(a.length, b.length);
|
|
47
|
+
const out = [];
|
|
48
|
+
for (let i = 0; i < h; i++) {
|
|
49
|
+
const la = a[i] ?? "";
|
|
50
|
+
const lb = b[i] ?? "";
|
|
51
|
+
out.push(la + " ".repeat(wa - stripAnsi(la).length + gap) + lb);
|
|
52
|
+
}
|
|
53
|
+
return out.join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function turn(n) {
|
|
57
|
+
return c.gray(`turn ${n}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function toolHeader(name, args) {
|
|
61
|
+
const compact = JSON.stringify(args);
|
|
62
|
+
const trimmed = compact.length > 120 ? compact.slice(0, 117) + "..." : compact;
|
|
63
|
+
return `${c.cyan(c.bold(name))}${c.gray("(")}${c.gray(trimmed)}${c.gray(")")}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const ellip = (s, n) => (s && s.length > n ? s.slice(0, n - 1) + "…" : s || "");
|
|
67
|
+
|
|
68
|
+
export function toolLabel(name, args) {
|
|
69
|
+
const a = args || {};
|
|
70
|
+
const verb = (v) => c.cyan(c.bold(v));
|
|
71
|
+
const arg = (s) => c.gray(ellip(s == null ? "" : String(s), 72));
|
|
72
|
+
switch (name) {
|
|
73
|
+
case "read_file": return `${verb("read")} ${arg(a.path)}`;
|
|
74
|
+
case "write_file": return `${verb("write")} ${arg(a.path)}`;
|
|
75
|
+
case "edit_file": return `${verb("edit")} ${arg(a.path)}`;
|
|
76
|
+
case "list_dir": return `${verb("list")} ${arg(a.path)}`;
|
|
77
|
+
case "glob_files": return `${verb("find")} ${arg(a.pattern)}`;
|
|
78
|
+
case "search_files": return `${verb("search")} ${arg(`/${a.pattern}/ in ${a.path ?? "."}`)}`;
|
|
79
|
+
case "run_shell": return `${verb("run")} ${arg(a.command)}`;
|
|
80
|
+
case "web_search": return `${verb("search web")} ${arg(JSON.stringify(a.query ?? ""))}`;
|
|
81
|
+
case "web_fetch": return `${verb("fetch")} ${arg(a.url)}`;
|
|
82
|
+
case "todo_write": return "";
|
|
83
|
+
case "ask_user": return "";
|
|
84
|
+
default: return `${verb(name)} ${arg(JSON.stringify(a))}`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function setTerminalTitle(title) {
|
|
89
|
+
if (process.stdout.isTTY) process.stdout.write(`\x1b]0;${title}\x07`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fun thinking words — like Claude Code's "Hullaballooing...", "Cogitating..."
|
|
93
|
+
const THINKING_WORDS = [
|
|
94
|
+
"Thinking",
|
|
95
|
+
"Pondering",
|
|
96
|
+
"Mulling",
|
|
97
|
+
"Cogitating",
|
|
98
|
+
"Ruminating",
|
|
99
|
+
"Deliberating",
|
|
100
|
+
"Contemplating",
|
|
101
|
+
"Noodling",
|
|
102
|
+
"Musing",
|
|
103
|
+
"Brainstorming",
|
|
104
|
+
"Percolating",
|
|
105
|
+
"Brainwaving",
|
|
106
|
+
"Daydreaming",
|
|
107
|
+
"Hullaballooing",
|
|
108
|
+
"Churning",
|
|
109
|
+
"Brewing",
|
|
110
|
+
"Conjuring",
|
|
111
|
+
"Scheming",
|
|
112
|
+
"Hatching",
|
|
113
|
+
"Concocting",
|
|
114
|
+
"Simmering",
|
|
115
|
+
"Marinating",
|
|
116
|
+
"Fermenting",
|
|
117
|
+
"Stewing",
|
|
118
|
+
"Whittling",
|
|
119
|
+
"Tinkering",
|
|
120
|
+
"Puzzling",
|
|
121
|
+
"Deciphering",
|
|
122
|
+
"Unraveling",
|
|
123
|
+
"Distilling",
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// Pick a random thinking word (seeded by current second so it varies per call)
|
|
127
|
+
function pickThinkingWord() {
|
|
128
|
+
const idx = (Date.now() % (THINKING_WORDS.length * 1000)) / 1000 | 0;
|
|
129
|
+
return THINKING_WORDS[idx % THINKING_WORDS.length];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function startSpinner(label) {
|
|
133
|
+
if (!process.stdout.isTTY) return { stop: () => {}, update: () => {} };
|
|
134
|
+
const frames = ["◐", "◓", "◑", "◒"];
|
|
135
|
+
const t0 = Date.now();
|
|
136
|
+
let i = 0;
|
|
137
|
+
let tokenCount = 0;
|
|
138
|
+
let statusText = "";
|
|
139
|
+
const isThinking = label === "thinking";
|
|
140
|
+
const thinkWord = isThinking ? pickThinkingWord() : null;
|
|
141
|
+
process.stdout.write("\x1b[?25l");
|
|
142
|
+
const render = () => {
|
|
143
|
+
const ms = Date.now() - t0;
|
|
144
|
+
const secs = Math.floor(ms / 1000);
|
|
145
|
+
const timeFmt = secs >= 60
|
|
146
|
+
? `${Math.floor(secs / 60)}m ${String(secs % 60).padStart(2, "0")}s`
|
|
147
|
+
: `${secs}s`;
|
|
148
|
+
const parts = [];
|
|
149
|
+
if (secs > 0) parts.push(timeFmt);
|
|
150
|
+
if (tokenCount > 0) parts.push(`${fmtTokens(tokenCount)} tokens`);
|
|
151
|
+
if (statusText) parts.push(statusText);
|
|
152
|
+
const metrics = parts.length > 0 ? c.gray(` (${parts.join(" \xB7 ")})`) : "";
|
|
153
|
+
const displayLabel = isThinking ? thinkWord : label;
|
|
154
|
+
process.stdout.write(`\r\x1b[K${c.cyan(frames[i % frames.length])} ${c.dim(displayLabel + "...")}${metrics}`);
|
|
155
|
+
i++;
|
|
156
|
+
};
|
|
157
|
+
render();
|
|
158
|
+
const timer = setInterval(render, 120);
|
|
159
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
160
|
+
return {
|
|
161
|
+
update(data) {
|
|
162
|
+
if (data.tokens != null) tokenCount = data.tokens;
|
|
163
|
+
if (data.status != null) statusText = data.status;
|
|
164
|
+
},
|
|
165
|
+
stop() {
|
|
166
|
+
clearInterval(timer);
|
|
167
|
+
process.stdout.write("\r\x1b[K\x1b[?25h");
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function fmtTokens(n) {
|
|
173
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M";
|
|
174
|
+
if (n >= 1_000) return (n / 1_000).toFixed(1) + "k";
|
|
175
|
+
return String(n);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function turnStats({ elapsed, tokensIn, tokensOut, tools, credits }) {
|
|
179
|
+
const parts = [];
|
|
180
|
+
if (elapsed > 0) {
|
|
181
|
+
const s = Math.floor(elapsed / 1000);
|
|
182
|
+
parts.push(s >= 60 ? `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, "0")}s` : `${s}s`);
|
|
183
|
+
}
|
|
184
|
+
if (tokensIn > 0 || tokensOut > 0) {
|
|
185
|
+
parts.push(`${fmtTokens(tokensIn)}->${fmtTokens(tokensOut)} tokens`);
|
|
186
|
+
}
|
|
187
|
+
if (tools > 0) parts.push(`${tools} tool call${tools === 1 ? "" : "s"}`);
|
|
188
|
+
if (credits > 0) parts.push(`${credits} cr`);
|
|
189
|
+
return parts.length > 0 ? c.dim(` Brewed for ${parts.join(" \xB7 ")}`) : "";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function toolResult(text, ok = true) {
|
|
193
|
+
const prefix = ok ? c.green(" ● ") : c.red(" × ");
|
|
194
|
+
const lines = text.split("\n");
|
|
195
|
+
const head = lines[0].slice(0, 200);
|
|
196
|
+
const rest = lines.slice(1, 6).join("\n").slice(0, 600);
|
|
197
|
+
return `${prefix}${head}${rest ? "\n" + c.dim(rest) : ""}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function assistant(text) {
|
|
201
|
+
return text.split("\n").map((l) => ` ${l}`).join("\n");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function errorLine(msg) {
|
|
205
|
+
return `${c.red(c.bold("Error:"))} ${msg}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function note(msg) {
|
|
209
|
+
return c.dim(msg);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function toolSummary(name, result) {
|
|
213
|
+
const ok = result.ok;
|
|
214
|
+
const branch = ok ? c.green("└─") : c.red("└─");
|
|
215
|
+
const out = result.output ?? "";
|
|
216
|
+
const firstLine = out.split("\n").find((l) => l.trim()) ?? "";
|
|
217
|
+
|
|
218
|
+
if (name === "run_shell") {
|
|
219
|
+
let code = null;
|
|
220
|
+
try { code = JSON.parse(out).exit_code; } catch { /* ignore */ }
|
|
221
|
+
return ` ${branch} ${c.gray(code === null ? (ok ? "done" : "failed") : `exit ${code}`)}`;
|
|
222
|
+
}
|
|
223
|
+
if (name === "todo_write") return "";
|
|
224
|
+
if (name === "ask_user") return "";
|
|
225
|
+
if (name === "write_file" || name === "edit_file") {
|
|
226
|
+
return ` ${branch} ${c.gray(ellip(firstLine, 100))}`;
|
|
227
|
+
}
|
|
228
|
+
let summary = "";
|
|
229
|
+
try {
|
|
230
|
+
const j = JSON.parse(out);
|
|
231
|
+
if (Array.isArray(j)) summary = `${j.length} result${j.length === 1 ? "" : "s"}`;
|
|
232
|
+
else if (Array.isArray(j.files)) summary = `${j.files.length} file${j.files.length === 1 ? "" : "s"}`;
|
|
233
|
+
else if (Array.isArray(j.matches)) summary = `${j.matches.length} match${j.matches.length === 1 ? "" : "es"}`;
|
|
234
|
+
} catch { /* not JSON */ }
|
|
235
|
+
if (!summary) {
|
|
236
|
+
summary = name === "read_file" ? `${out.split("\n").length} lines` : ellip(firstLine, 100);
|
|
237
|
+
}
|
|
238
|
+
return ` ${branch} ${c.gray(summary)}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const MODEL_TOKEN_RE = /<\|[a-z_]*\|?>|<[a-z_]+\|>/gi;
|
|
242
|
+
|
|
243
|
+
export function stripModelTokens(text) {
|
|
244
|
+
return text.replace(MODEL_TOKEN_RE, "");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function makeTokenStripper() {
|
|
248
|
+
let buf = "";
|
|
249
|
+
return {
|
|
250
|
+
push(text) {
|
|
251
|
+
buf = (buf + text).replace(MODEL_TOKEN_RE, "");
|
|
252
|
+
const partial = buf.match(/<[|a-z_/]*$/i);
|
|
253
|
+
if (partial) {
|
|
254
|
+
const emit = buf.slice(0, partial.index);
|
|
255
|
+
buf = buf.slice(partial.index);
|
|
256
|
+
return emit;
|
|
257
|
+
}
|
|
258
|
+
const emit = buf;
|
|
259
|
+
buf = "";
|
|
260
|
+
return emit;
|
|
261
|
+
},
|
|
262
|
+
flush() {
|
|
263
|
+
const out = buf.replace(MODEL_TOKEN_RE, "");
|
|
264
|
+
buf = "";
|
|
265
|
+
return out;
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
package/src/repl.js
CHANGED
|
@@ -9,7 +9,7 @@ import { c, errorLine } from "./render.js";
|
|
|
9
9
|
import { autoUpdate } from "./update-check.js";
|
|
10
10
|
import { promptBox, EXIT_SIGNAL } from "./box-input.js";
|
|
11
11
|
|
|
12
|
-
const VERSION = "0.33.
|
|
12
|
+
const VERSION = "0.33.6";
|
|
13
13
|
const MODEL_NAME = "Aether Core";
|
|
14
14
|
|
|
15
15
|
const MODEL_ALIASES = {
|
|
@@ -33,6 +33,8 @@ ${c.gray("Anything else is sent to the agent as your next message.")}
|
|
|
33
33
|
${c.gray("Conversation history is kept across messages until you /clear.")}
|
|
34
34
|
`;
|
|
35
35
|
|
|
36
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
37
|
+
|
|
36
38
|
export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafePaths: initialUnsafePaths, maxTurns: initialMaxTurns, model: initialModel = null, mcpManager = null }) {
|
|
37
39
|
const initialAlias = Object.values(MODEL_ALIASES).find((a) => a.id === initialModel);
|
|
38
40
|
const state = {
|
|
@@ -80,7 +82,7 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafe
|
|
|
80
82
|
// Clear screen — clean start, no leftover shell text (matches Claude Code)
|
|
81
83
|
process.stdout.write("\x1b[2J\x1b[H");
|
|
82
84
|
|
|
83
|
-
printBanner(state);
|
|
85
|
+
const bannerLineCount = printBanner(state);
|
|
84
86
|
|
|
85
87
|
const updateMsg = await updatePromise;
|
|
86
88
|
let extraLines = 0;
|
|
@@ -88,9 +90,8 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafe
|
|
|
88
90
|
|
|
89
91
|
// Push prompt to bottom of screen — big empty gap like Claude Code
|
|
90
92
|
const rows = process.stdout.rows || 24;
|
|
91
|
-
const bannerLines = 5; // 1 blank + 3 logo lines + 1 blank after
|
|
92
93
|
const promptLines = 2; // "> " input + status bar
|
|
93
|
-
const filler = Math.max(0, rows -
|
|
94
|
+
const filler = Math.max(0, rows - bannerLineCount - extraLines - promptLines - 1);
|
|
94
95
|
for (let i = 0; i < filler; i++) console.log("");
|
|
95
96
|
|
|
96
97
|
const inputHistory = [];
|
|
@@ -129,6 +130,11 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafe
|
|
|
129
130
|
if (!result.ok && result.error) {
|
|
130
131
|
console.log("\n" + errorLine(result.error.message || String(result.error)));
|
|
131
132
|
}
|
|
133
|
+
|
|
134
|
+
// Push prompt back toward the bottom — content scrolls UP like Claude Code.
|
|
135
|
+
const rowsNow = process.stdout.rows || 24;
|
|
136
|
+
const gap = Math.max(0, Math.floor(rowsNow * 0.4));
|
|
137
|
+
for (let g = 0; g < gap; g++) console.log("");
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -222,30 +228,74 @@ async function handleSlash(line, state) {
|
|
|
222
228
|
return null;
|
|
223
229
|
}
|
|
224
230
|
|
|
225
|
-
/* ───────── banner ───────── */
|
|
231
|
+
/* ───────── banner (two-column like Claude Code) ───────── */
|
|
226
232
|
|
|
227
233
|
const shortenPath = (p, max) => (p.length <= max ? p : "..." + p.slice(p.length - max + 3));
|
|
228
234
|
|
|
235
|
+
function padRight(str, width) {
|
|
236
|
+
const visible = stripAnsi(str).length;
|
|
237
|
+
const need = Math.max(0, width - visible);
|
|
238
|
+
return str + " ".repeat(need);
|
|
239
|
+
}
|
|
240
|
+
|
|
229
241
|
function printBanner(state) {
|
|
242
|
+
const cols = process.stdout.columns || 80;
|
|
230
243
|
const credits = state.balance != null ? `${state.balance.toLocaleString()} credits` : "";
|
|
231
244
|
const plan = state.plan && state.plan !== "FREE" ? state.plan : "";
|
|
232
245
|
|
|
233
|
-
// Block logo using ANSI background colors — renders correctly in ANY terminal.
|
|
234
|
-
// Each block = 2 spaces with colored background. No special chars needed.
|
|
235
246
|
const blk = (code) => `\x1b[${code}m \x1b[0m`;
|
|
236
|
-
const C = blk(46);
|
|
237
|
-
const B = blk(44);
|
|
238
|
-
|
|
239
|
-
const gap = " ";
|
|
247
|
+
const C = blk(46);
|
|
248
|
+
const B = blk(44);
|
|
240
249
|
|
|
241
250
|
const modelParts = [state.modelName];
|
|
242
251
|
if (credits) modelParts.push(credits);
|
|
243
252
|
if (plan) modelParts.push(c.cyan(plan));
|
|
244
253
|
const modelLine = modelParts.join(c.dim(" \xB7 "));
|
|
245
254
|
|
|
255
|
+
const leftW = Math.min(Math.floor(cols * 0.52), 50);
|
|
256
|
+
const pathMax = Math.max(20, leftW - 14);
|
|
257
|
+
|
|
258
|
+
const leftLines = [
|
|
259
|
+
c.dim("──") + " " + c.bold("Aether Code") + " " + c.dim("v" + VERSION),
|
|
260
|
+
" " + c.bold("Welcome back!"),
|
|
261
|
+
"",
|
|
262
|
+
` ${C}${B} ${C}`,
|
|
263
|
+
` ${B}${C} ${B} ${modelLine}`,
|
|
264
|
+
` ${C}${B} ${C} ${c.dim(shortenPath(state.cwd, pathMax))}`,
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
const rightLines = [
|
|
268
|
+
c.bold("Tips for getting started"),
|
|
269
|
+
c.dim("Ask Aether to build, debug, or refactor your code"),
|
|
270
|
+
"",
|
|
271
|
+
c.yellow("What's new"),
|
|
272
|
+
c.dim("Creative thinking words while processing"),
|
|
273
|
+
c.dim("User message highlight bar + prompt at bottom"),
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
const twoCol = cols >= 70;
|
|
277
|
+
let printed = 0;
|
|
278
|
+
|
|
246
279
|
console.log("");
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
280
|
+
printed++;
|
|
281
|
+
|
|
282
|
+
if (twoCol) {
|
|
283
|
+
const h = Math.max(leftLines.length, rightLines.length);
|
|
284
|
+
for (let i = 0; i < h; i++) {
|
|
285
|
+
const left = leftLines[i] || "";
|
|
286
|
+
const right = rightLines[i] || "";
|
|
287
|
+
console.log(" " + padRight(left, leftW) + right);
|
|
288
|
+
printed++;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
for (const line of leftLines) {
|
|
292
|
+
console.log(" " + line);
|
|
293
|
+
printed++;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
250
297
|
console.log("");
|
|
298
|
+
printed++;
|
|
299
|
+
|
|
300
|
+
return printed;
|
|
251
301
|
}
|