aether-code 0.18.0 → 0.20.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.
@@ -24,9 +24,9 @@ import {
24
24
  suggestSimilar,
25
25
  } from "../src/mcp-registry.js";
26
26
  import readline from "node:readline";
27
- import { c, errorLine, divider } from "../src/render.js";
27
+ import { c, errorLine, divider, setTerminalTitle } from "../src/render.js";
28
28
 
29
- const VERSION = "0.18.0";
29
+ const VERSION = "0.20.0";
30
30
 
31
31
  /**
32
32
  * Try to start MCP servers from ~/.aether/mcp.json. Returns a started
@@ -134,6 +134,7 @@ function die(msg, code = 1) {
134
134
 
135
135
  async function main() {
136
136
  const args = parseArgs(process.argv.slice(2));
137
+ setTerminalTitle("Aether");
137
138
 
138
139
  if (args.flags.help) {
139
140
  process.stdout.write(HELP);
@@ -213,12 +214,12 @@ async function main() {
213
214
 
214
215
  console.log("\n" + divider());
215
216
  if (result.ok) {
216
- console.log(c.green(c.bold(" Done")) + c.gray(` ${result.turns} turn${result.turns === 1 ? "" : "s"} · ${result.totalCredits} credits · ${result.totalIn}→${result.totalOut} tokens`));
217
+ console.log(c.green(c.bold(" Done")) + c.gray(` ${result.turns} turn${result.turns === 1 ? "" : "s"} · ${result.totalCredits} credits · ${result.totalIn}→${result.totalOut} tokens`));
217
218
  if (typeof result.balance === "number") {
218
219
  console.log(c.gray(` balance: ${result.balance.toLocaleString()} credits`));
219
220
  }
220
221
  } else {
221
- console.log(c.red(c.bold(" Stopped")) + c.gray(` ${result.totalCredits} credits used · ${result.totalIn}→${result.totalOut} tokens`));
222
+ console.log(c.red(c.bold("× Stopped")) + c.gray(` ${result.totalCredits} credits used · ${result.totalIn}→${result.totalOut} tokens`));
222
223
  if (result.error) console.log(errorLine(result.error.message));
223
224
  }
224
225
  console.log(divider());
@@ -241,14 +242,14 @@ async function handleConfig(rest) {
241
242
  process.stderr.write(c.yellow("warning: keys normally start with ak_live_; saving anyway.\n"));
242
243
  }
243
244
  writeConfigFile({ apiKey: key });
244
- console.log(`${c.green("")} API key saved to ${CONFIG_PATH}`);
245
+ console.log(`${c.green("")} API key saved to ${CONFIG_PATH}`);
245
246
  return;
246
247
  }
247
248
  if (sub === "set-base") {
248
249
  const url = rest[1];
249
250
  if (!url) die("config set-base: missing URL argument.");
250
251
  writeConfigFile({ baseUrl: url });
251
- console.log(`${c.green("")} Base URL saved.`);
252
+ console.log(`${c.green("")} Base URL saved.`);
252
253
  return;
253
254
  }
254
255
  if (sub === "path") {
@@ -341,7 +342,7 @@ async function handleMcp(rest) {
341
342
  const cmdArgs = post.slice(1);
342
343
  try {
343
344
  const entry = addServer({ name, command, args: cmdArgs, env });
344
- console.log(`${c.green("")} Added MCP server "${c.cyan(name)}".`);
345
+ console.log(`${c.green("")} Added MCP server "${c.cyan(name)}".`);
345
346
  const argsStr = entry.args && entry.args.length > 0 ? " " + entry.args.join(" ") : "";
346
347
  console.log(c.gray(` ${entry.command}${argsStr}`));
347
348
  console.log(c.gray("Restart the agent (or run `aether`) to attach it."));
@@ -356,7 +357,7 @@ async function handleMcp(rest) {
356
357
  if (!name) die("aether mcp remove: missing <name>");
357
358
  try {
358
359
  removeServer({ name });
359
- console.log(`${c.green("")} Removed MCP server "${c.cyan(name)}".`);
360
+ console.log(`${c.green("")} Removed MCP server "${c.cyan(name)}".`);
360
361
  } catch (e) {
361
362
  die(e.message || String(e));
362
363
  }
@@ -429,7 +430,7 @@ async function handleMcp(rest) {
429
430
  args: resolved.args,
430
431
  env: resolved.env,
431
432
  });
432
- console.log(`${c.green("")} Installed MCP server "${c.cyan(entry.id)}".`);
433
+ console.log(`${c.green("")} Installed MCP server "${c.cyan(entry.id)}".`);
433
434
  console.log(c.gray(` ${added.command}${added.args ? " " + added.args.join(" ") : ""}`));
434
435
  console.log(c.gray("Restart aether (or run `aether`) to attach it."));
435
436
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aether-code",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
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
@@ -79,7 +79,8 @@ export async function runAgent({
79
79
  let lastBalance = null;
80
80
 
81
81
  for (let i = 0; i < maxTurns; i++) {
82
- process.stdout.write("\n" + turn(i + 1) + "\n");
82
+ // A blank line separates steps (no noisy "turn N" headers).
83
+ process.stdout.write("\n");
83
84
 
84
85
  // Stream the assistant's response. Print text deltas as they arrive,
85
86
  // along with tool-call announcements as soon as the model commits to
@@ -106,7 +107,7 @@ export async function runAgent({
106
107
  const clean = stripper.push(text);
107
108
  if (!clean) return;
108
109
  if (!lastWasText) {
109
- process.stdout.write(" ");
110
+ process.stdout.write(c.cyan(""));
110
111
  lastWasText = true;
111
112
  }
112
113
  process.stdout.write(clean);
@@ -132,7 +133,7 @@ export async function runAgent({
132
133
  // Flush any held-back partial token, then close the line.
133
134
  const tail = stripper.flush();
134
135
  if (tail) {
135
- if (!lastWasText) { process.stdout.write(" "); lastWasText = true; }
136
+ if (!lastWasText) { process.stdout.write(c.cyan("")); lastWasText = true; }
136
137
  process.stdout.write(tail);
137
138
  }
138
139
  if (lastWasText) process.stdout.write("\n");
@@ -141,9 +142,8 @@ export async function runAgent({
141
142
  totalOut += res.usage?.completion_tokens ?? 0;
142
143
  if (typeof res.balanceAfter === "number") lastBalance = res.balanceAfter;
143
144
  onTokens({ totalCredits, totalIn, totalOut, balance: lastBalance });
144
- process.stdout.write(
145
- c.dim(` ${res.creditsCharged ?? 0} cr · ${res.usage?.prompt_tokens ?? 0}→${res.usage?.completion_tokens ?? 0} tokens · finish: ${res.finish_reason}\n`),
146
- );
145
+ // Per-turn cost line removed for a cleaner look — the session summary at the
146
+ // end carries the totals.
147
147
 
148
148
  // Push assistant message into history
149
149
  messages.push({
@@ -162,8 +162,9 @@ export async function runAgent({
162
162
  for (const call of toolCalls) {
163
163
  let args = {};
164
164
  try { args = JSON.parse(call.function.arguments || "{}"); } catch { /* leave empty */ }
165
- console.log("");
166
- console.log(toolLabel(call.function.name, args));
165
+ // todo_write renders its own Plan box, so it returns an empty label.
166
+ const label = toolLabel(call.function.name, args);
167
+ if (label) console.log("\n" + c.cyan("●") + " " + label);
167
168
 
168
169
  // Route to MCP if the tool name is namespaced (mcp__server__tool);
169
170
  // otherwise execute the built-in tool. unnamespaceToolName returns
@@ -181,7 +182,8 @@ export async function runAgent({
181
182
  if (call.function.name === "read_file" || call.function.name === "edit_file" || call.function.name === "write_file") {
182
183
  if (typeof args.path === "string") referencedPaths.push(args.path);
183
184
  }
184
- console.log(toolSummary(call.function.name, result));
185
+ const summary = toolSummary(call.function.name, result);
186
+ if (summary) console.log(summary);
185
187
 
186
188
  messages.push({
187
189
  role: "tool",
package/src/diff.js CHANGED
@@ -30,11 +30,12 @@ export function unifiedDiff(oldText, newText, filename) {
30
30
  for (const l of changedNew) lines.push(c.green(`+ ${l}`));
31
31
  if (suffix > 0) lines.push(c.gray(` …${suffix} unchanged line${suffix === 1 ? "" : "s"} below…`));
32
32
 
33
- // Cap output so massive writes don't flood the terminal
34
- if (lines.length > 60) {
35
- return [...lines.slice(0, 30), c.gray(` …${lines.length - 60} more lines hidden…`), ...lines.slice(-30)].join(
36
- "\n",
37
- );
33
+ // Cap output so writes don't flood the terminal — a short preview is enough
34
+ // (skip-permissions auto-applies; the full file is on disk to inspect).
35
+ const MAX = 14;
36
+ if (lines.length > MAX) {
37
+ const shown = lines.slice(0, MAX - 1);
38
+ return [...shown, c.gray(` …${lines.length - shown.length} more lines (see the file)…`)].join("\n");
38
39
  }
39
40
  return lines.join("\n");
40
41
  }
package/src/render.js CHANGED
@@ -53,17 +53,23 @@ export function toolLabel(name, args) {
53
53
  case "run_shell": return `${verb("run")} ${arg(a.command)}`;
54
54
  case "web_search": return `${verb("search web")} ${arg(JSON.stringify(a.query ?? ""))}`;
55
55
  case "web_fetch": return `${verb("fetch")} ${arg(a.url)}`;
56
- case "todo_write": return verb("plan");
56
+ case "todo_write": return ""; // its Plan box is the label
57
57
  default: return `${verb(name)} ${arg(JSON.stringify(a))}`;
58
58
  }
59
59
  }
60
60
 
61
+ // Set the terminal window/tab title (so cmd.exe shows "Aether", not the node
62
+ // path). OSC 0 — supported by cmd.exe, Windows Terminal, and POSIX terminals.
63
+ export function setTerminalTitle(title) {
64
+ if (process.stdout.isTTY) process.stdout.write(`\x1b]0;${title}\x07`);
65
+ }
66
+
61
67
  // Terse one-line result summary instead of dumping raw JSON / file contents.
62
68
  // Tools whose handlers already render rich output (diffs, the shell stream, the
63
69
  // plan) just get a check — the detail was already printed.
64
70
  export function toolSummary(name, result) {
65
71
  const ok = result.ok;
66
- const mark = ok ? c.green("") : c.red("");
72
+ const mark = ok ? c.green("") : c.red("×");
67
73
  const out = result.output ?? "";
68
74
  const firstLine = out.split("\n").find((l) => l.trim()) ?? "";
69
75
 
@@ -72,8 +78,9 @@ export function toolSummary(name, result) {
72
78
  try { code = JSON.parse(out).exit_code; } catch { /* ignore */ }
73
79
  return ` ${mark} ${c.gray(code === null ? (ok ? "done" : "failed") : `exit ${code}`)}`;
74
80
  }
75
- if (name === "write_file" || name === "edit_file" || name === "todo_write") {
76
- // Handler already printed the diff / plan; echo its short status line.
81
+ if (name === "todo_write") return ""; // the Plan box is its own feedback
82
+ if (name === "write_file" || name === "edit_file") {
83
+ // Handler already printed the diff; echo its short status line.
77
84
  return ` ${mark} ${c.gray(ellip(firstLine, 100))}`;
78
85
  }
79
86
  let summary = "";
@@ -129,7 +136,7 @@ export function makeTokenStripper() {
129
136
  }
130
137
 
131
138
  export function toolResult(text, ok = true) {
132
- const prefix = ok ? c.green(" ") : c.red(" ");
139
+ const prefix = ok ? c.green(" ") : c.red(" × ");
133
140
  // First line bold-ish, then dim continuation
134
141
  const lines = text.split("\n");
135
142
  const head = lines[0].slice(0, 200);
package/src/repl.js CHANGED
@@ -17,7 +17,7 @@ import { c, errorLine } from "./render.js";
17
17
  import { checkForUpdate } from "./update-check.js";
18
18
  import { promptBoxed, EXIT_SIGNAL } from "./ink-input.js";
19
19
 
20
- const VERSION = "0.18.0";
20
+ const VERSION = "0.20.0";
21
21
  const MODEL_NAME = "Aether Core";
22
22
 
23
23
  const SHORTCUTS = `
package/src/setup.js CHANGED
@@ -112,7 +112,7 @@ export async function runSetup() {
112
112
  process.stdout.write(c.gray("Verifying..."));
113
113
  try {
114
114
  const me = await fetchBalance();
115
- console.log(c.green(" "));
115
+ console.log(c.green(" "));
116
116
  console.log("");
117
117
  console.log(c.green(c.bold("Setup complete.")));
118
118
  console.log(
@@ -123,7 +123,7 @@ export async function runSetup() {
123
123
  saved = true;
124
124
  break;
125
125
  } catch (err) {
126
- console.log(c.red(" "));
126
+ console.log(c.red(" ×"));
127
127
  if (err instanceof AetherError && err.status === 401) {
128
128
  console.log(errorLine("Server rejected that key (401). Double-check you copied it correctly."));
129
129
  } else {
package/src/tools.js CHANGED
@@ -787,7 +787,7 @@ function renderTodos(todos) {
787
787
  for (const t of todos) {
788
788
  const icon =
789
789
  t.status === "completed"
790
- ? c.green("")
790
+ ? c.green("")
791
791
  : t.status === "in_progress"
792
792
  ? c.yellow("→")
793
793
  : c.dim("·");