aether-code 0.43.2 → 0.43.3
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 +19 -12
- package/package.json +1 -1
- package/src/agent.js +14 -13
- package/src/render.js +20 -20
- package/src/repl.js +4 -7
package/bin/aether-code.js
CHANGED
|
@@ -31,6 +31,14 @@ import { getVersion } from "../src/version.js";
|
|
|
31
31
|
|
|
32
32
|
const VERSION = getVersion();
|
|
33
33
|
|
|
34
|
+
function formatElapsed(ms) {
|
|
35
|
+
const s = Math.round(ms / 1000);
|
|
36
|
+
if (s < 60) return `${s}s`;
|
|
37
|
+
const m = Math.floor(s / 60);
|
|
38
|
+
const rem = s % 60;
|
|
39
|
+
return rem > 0 ? `${m}m ${rem}s` : `${m}m`;
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
/**
|
|
35
43
|
* Try to start MCP servers from ~/.aether/mcp.json. Returns a started
|
|
36
44
|
* MCPManager (possibly with zero servers) or null if no config exists.
|
|
@@ -245,12 +253,12 @@ async function main() {
|
|
|
245
253
|
if (!ok) process.exit(1);
|
|
246
254
|
}
|
|
247
255
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
console.log(c.magenta(c.bold("aether
|
|
256
|
+
const modeLabel = autoYes && unsafePaths ? "skip-permissions" : `${autoYes ? "auto-yes" : "review"}${unsafePaths ? "" : " · sandboxed"}`;
|
|
257
|
+
console.log("");
|
|
258
|
+
console.log(c.magenta(c.bold("aether")) + c.gray(` · ${modeLabel} · cwd ${cwd}`));
|
|
251
259
|
if (redirectedFrom) console.log(c.gray(`(launched from ${redirectedFrom} — saving to ${cwd} instead)`));
|
|
252
|
-
console.log(c.
|
|
253
|
-
console.log(
|
|
260
|
+
console.log(c.dim(prompt));
|
|
261
|
+
console.log("");
|
|
254
262
|
|
|
255
263
|
const mcpManager = await bootMcp();
|
|
256
264
|
const result = await runAgent({
|
|
@@ -264,17 +272,16 @@ async function main() {
|
|
|
264
272
|
});
|
|
265
273
|
if (mcpManager) await mcpManager.shutdown().catch(() => {});
|
|
266
274
|
|
|
267
|
-
|
|
275
|
+
const elapsed = result.elapsed ? formatElapsed(result.elapsed) : "";
|
|
276
|
+
console.log("");
|
|
268
277
|
if (result.ok) {
|
|
269
|
-
|
|
270
|
-
if (typeof result.balance === "number") {
|
|
271
|
-
|
|
272
|
-
}
|
|
278
|
+
const parts = [elapsed, `${result.totalCredits} credits`];
|
|
279
|
+
if (typeof result.balance === "number") parts.push(`balance: ${result.balance.toLocaleString()}`);
|
|
280
|
+
console.log(c.dim(`Done in ${parts.filter(Boolean).join(" · ")}`));
|
|
273
281
|
} else {
|
|
274
|
-
console.log(c.red(
|
|
282
|
+
console.log(c.red("Stopped") + c.gray(` ${result.totalCredits} credits used` + (elapsed ? ` · ${elapsed}` : "")));
|
|
275
283
|
if (result.error) console.log(errorLine(result.error.message));
|
|
276
284
|
}
|
|
277
|
-
console.log(divider());
|
|
278
285
|
}
|
|
279
286
|
|
|
280
287
|
async function handleConfig(rest) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aether-code",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.3",
|
|
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/agent.js
CHANGED
|
@@ -129,6 +129,7 @@ export async function runAgent({
|
|
|
129
129
|
let totalIn = 0;
|
|
130
130
|
let totalOut = 0;
|
|
131
131
|
let lastBalance = null;
|
|
132
|
+
const t0 = Date.now();
|
|
132
133
|
// Loop guard: count identical (name+args) tool calls across the whole run so a
|
|
133
134
|
// confused model can't burn turns re-running the same call (e.g. glob *.md x9).
|
|
134
135
|
const callCounts = new Map();
|
|
@@ -188,7 +189,7 @@ export async function runAgent({
|
|
|
188
189
|
if (!lastWasText && !clean.trim()) return;
|
|
189
190
|
stopSpin();
|
|
190
191
|
if (!lastWasText) {
|
|
191
|
-
process.stdout.write("\n"
|
|
192
|
+
process.stdout.write("\n");
|
|
192
193
|
lastWasText = true;
|
|
193
194
|
}
|
|
194
195
|
process.stdout.write(clean);
|
|
@@ -208,7 +209,7 @@ export async function runAgent({
|
|
|
208
209
|
} catch (err) {
|
|
209
210
|
stopSpin();
|
|
210
211
|
if (err instanceof AetherError) {
|
|
211
|
-
return { ok: false, error: err, totalCredits, totalIn, totalOut, balance: lastBalance, messages };
|
|
212
|
+
return { ok: false, error: err, totalCredits, totalIn, totalOut, balance: lastBalance, messages, elapsed: Date.now() - t0 };
|
|
212
213
|
}
|
|
213
214
|
throw err;
|
|
214
215
|
}
|
|
@@ -217,7 +218,7 @@ export async function runAgent({
|
|
|
217
218
|
// Flush any held-back partial token, then close the line.
|
|
218
219
|
const tail = stripper.flush();
|
|
219
220
|
if (tail && (lastWasText || tail.trim())) {
|
|
220
|
-
if (!lastWasText) { process.stdout.write("\n"
|
|
221
|
+
if (!lastWasText) { process.stdout.write("\n"); lastWasText = true; }
|
|
221
222
|
process.stdout.write(tail);
|
|
222
223
|
}
|
|
223
224
|
if (lastWasText) process.stdout.write("\n");
|
|
@@ -228,10 +229,10 @@ export async function runAgent({
|
|
|
228
229
|
if (typeof res.balanceAfter === "number") lastBalance = res.balanceAfter;
|
|
229
230
|
onTokens({ totalCredits, totalIn, totalOut, balance: lastBalance });
|
|
230
231
|
|
|
231
|
-
// Per-turn cost —
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
// Per-turn cost — only shown when credits are high enough to warrant attention
|
|
233
|
+
if (turnCredits > 20) {
|
|
234
|
+
console.log(c.gray(` ${turnCredits} credits this turn (${totalCredits} total)`));
|
|
235
|
+
}
|
|
235
236
|
|
|
236
237
|
// Push assistant message into history
|
|
237
238
|
messages.push({
|
|
@@ -252,19 +253,19 @@ export async function runAgent({
|
|
|
252
253
|
// Credit cap — hard stop to prevent runaway cost
|
|
253
254
|
if (maxCredits > 0 && totalCredits >= maxCredits) {
|
|
254
255
|
console.log(c.red(`\n[Credit budget exhausted: ${totalCredits} credits used, limit was ${maxCredits}. Stopping.]`));
|
|
255
|
-
return { ok: true, totalCredits, totalIn, totalOut, balance: lastBalance, messages };
|
|
256
|
+
return { ok: true, totalCredits, totalIn, totalOut, balance: lastBalance, messages, elapsed: Date.now() - t0 };
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
// Tool call count cap
|
|
259
260
|
if (toolCalls.length > 0 && totalToolCalls + toolCalls.length > MAX_TOTAL_TOOL_CALLS) {
|
|
260
261
|
console.log(c.red(`\n[Tool call limit reached (${MAX_TOTAL_TOOL_CALLS}). Stopping.]`));
|
|
261
|
-
return { ok: true, totalCredits, totalIn, totalOut, balance: lastBalance, messages };
|
|
262
|
+
return { ok: true, totalCredits, totalIn, totalOut, balance: lastBalance, messages, elapsed: Date.now() - t0 };
|
|
262
263
|
}
|
|
263
264
|
|
|
264
265
|
// Consecutive tool-only turns cap (model stuck in a tool loop with no text)
|
|
265
266
|
if (consecutiveToolOnlyTurns >= MAX_CONSECUTIVE_TOOL_ONLY) {
|
|
266
267
|
console.log(c.red(`\n[Too many consecutive tool-only turns (${MAX_CONSECUTIVE_TOOL_ONLY}). Stopping.]`));
|
|
267
|
-
return { ok: true, totalCredits, totalIn, totalOut, balance: lastBalance, messages };
|
|
268
|
+
return { ok: true, totalCredits, totalIn, totalOut, balance: lastBalance, messages, elapsed: Date.now() - t0 };
|
|
268
269
|
}
|
|
269
270
|
if (toolCalls.length === 0) {
|
|
270
271
|
// Completeness guard: on a modification task, if the model read files it
|
|
@@ -373,7 +374,7 @@ export async function runAgent({
|
|
|
373
374
|
console.log(" " + c.green("●") + " " + p + c.gray(` — ${action}`));
|
|
374
375
|
}
|
|
375
376
|
}
|
|
376
|
-
return { ok: true, totalCredits, totalIn, totalOut, turns: i + 1, balance: lastBalance, messages };
|
|
377
|
+
return { ok: true, totalCredits, totalIn, totalOut, turns: i + 1, balance: lastBalance, messages, elapsed: Date.now() - t0 };
|
|
377
378
|
}
|
|
378
379
|
|
|
379
380
|
// Execute each tool call. Show the actual args (now that we have them
|
|
@@ -384,7 +385,7 @@ export async function runAgent({
|
|
|
384
385
|
try { args = JSON.parse(call.function.arguments || "{}"); } catch { /* leave empty */ }
|
|
385
386
|
// todo_write renders its own Plan box, so it returns an empty label.
|
|
386
387
|
const label = toolLabel(call.function.name, args);
|
|
387
|
-
if (label) console.log("\n" + c.
|
|
388
|
+
if (label) console.log("\n" + c.green("●") + " " + label);
|
|
388
389
|
|
|
389
390
|
// Loop guard — short-circuit a tool call we've already run 3+ times with
|
|
390
391
|
// the same args, and tell the model to change approach instead of looping.
|
|
@@ -445,7 +446,7 @@ export async function runAgent({
|
|
|
445
446
|
}
|
|
446
447
|
|
|
447
448
|
console.log(c.yellow(`\nReached max turns (${maxTurns}). Stopping.`));
|
|
448
|
-
return { ok: false, error: new Error("Max turns reached"), totalCredits, totalIn, totalOut, balance: lastBalance, messages };
|
|
449
|
+
return { ok: false, error: new Error("Max turns reached"), totalCredits, totalIn, totalOut, balance: lastBalance, messages, elapsed: Date.now() - t0 };
|
|
449
450
|
}
|
|
450
451
|
|
|
451
452
|
// A build/verify command failing because the TOOLCHAIN ITSELF is missing (e.g.
|
package/src/render.js
CHANGED
|
@@ -74,27 +74,27 @@ const ellip = (s, n) => (s && s.length > n ? s.slice(0, n - 1) + "…" : s || ""
|
|
|
74
74
|
// dumping raw JSON (which buried file contents / queries in noise).
|
|
75
75
|
export function toolLabel(name, args) {
|
|
76
76
|
const a = args || {};
|
|
77
|
-
const verb = (v) => c.
|
|
78
|
-
const arg = (s) =>
|
|
77
|
+
const verb = (v) => c.bold(v);
|
|
78
|
+
const arg = (s) => ellip(String(s), 72);
|
|
79
79
|
switch (name) {
|
|
80
|
-
case "read_file": return `${verb("
|
|
81
|
-
case "write_file": return `${verb("
|
|
82
|
-
case "edit_file": return `${verb("
|
|
83
|
-
case "list_dir": return `${verb("
|
|
84
|
-
case "glob_files": return `${verb("
|
|
85
|
-
case "search_files": return `${verb("
|
|
86
|
-
case "run_shell": return `${verb("
|
|
87
|
-
case "web_search": return `${verb("
|
|
88
|
-
case "web_fetch": return `${verb("
|
|
89
|
-
case "todo_write": return "";
|
|
90
|
-
case "ask_user": return "";
|
|
91
|
-
case "git_status": return `${verb("
|
|
92
|
-
case "git_diff": return `${verb("
|
|
93
|
-
case "git_log": return `${verb("
|
|
94
|
-
case "git_commit": return `${verb("
|
|
95
|
-
case "git_branch": return `${verb("
|
|
96
|
-
case "git_push": return `${verb("
|
|
97
|
-
case "git_create_pr": return `${verb("
|
|
80
|
+
case "read_file": return `${verb("Read")} ${arg(a.path)}`;
|
|
81
|
+
case "write_file": return `${verb("Write")} ${arg(a.path)}`;
|
|
82
|
+
case "edit_file": return `${verb("Edit")} ${arg(a.path)}`;
|
|
83
|
+
case "list_dir": return `${verb("List")} ${arg(a.path)}`;
|
|
84
|
+
case "glob_files": return `${verb("Glob")} ${arg(a.pattern)}`;
|
|
85
|
+
case "search_files": return `${verb("Grep")} ${arg(`/${a.pattern}/${a.path ? ` in ${a.path}` : ""}`)}`;
|
|
86
|
+
case "run_shell": return `${verb("Bash")} ${c.dim(ellip(String(a.command), 72))}`;
|
|
87
|
+
case "web_search": return `${verb("Search")} ${arg(a.query ?? "")}`;
|
|
88
|
+
case "web_fetch": return `${verb("Fetch")} ${arg(a.url)}`;
|
|
89
|
+
case "todo_write": return "";
|
|
90
|
+
case "ask_user": return "";
|
|
91
|
+
case "git_status": return `${verb("Git")} ${arg("status")}`;
|
|
92
|
+
case "git_diff": return `${verb("Git")} ${arg("diff" + (a.staged ? " --staged" : "") + (a.base ? ` ${a.base}...${a.head || "HEAD"}` : ""))}`;
|
|
93
|
+
case "git_log": return `${verb("Git")} ${arg("log" + (a.file ? ` -- ${a.file}` : "") + (a.branch ? ` ${a.branch}` : ""))}`;
|
|
94
|
+
case "git_commit": return `${verb("Git")} ${arg(`commit "${ellip(a.message || "", 50)}"`)}`
|
|
95
|
+
case "git_branch": return `${verb("Git")} ${arg(`branch ${a.name}`)}`;
|
|
96
|
+
case "git_push": return `${verb("Git")} ${arg("push" + (a.force ? " --force" : ""))}`;
|
|
97
|
+
case "git_create_pr": return `${verb("Git")} ${arg(`pr "${ellip(a.title || "", 50)}"`)}`
|
|
98
98
|
default: return `${verb(name)} ${arg(JSON.stringify(a))}`;
|
|
99
99
|
}
|
|
100
100
|
}
|
package/src/repl.js
CHANGED
|
@@ -434,11 +434,8 @@ function printBanner(state, projectLine = "", resumed = false) {
|
|
|
434
434
|
|
|
435
435
|
function printStatusLine(state) {
|
|
436
436
|
const parts = [];
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (state.messages.length > 0) {
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
parts.push(c.cyan(state.autoYes ? "auto-yes" : "review"));
|
|
443
|
-
console.log(c.dim(parts.join(" · ")));
|
|
437
|
+
if (state.balance != null) parts.push(`${state.balance.toLocaleString()} credits`);
|
|
438
|
+
parts.push(state.autoYes ? "skip-permissions" : "review mode");
|
|
439
|
+
if (state.messages.length > 0) parts.push(`${state.messages.length} msgs`);
|
|
440
|
+
console.log(c.dim(parts.join(" · ")));
|
|
444
441
|
}
|