aether-code 0.16.1 → 0.16.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.
@@ -26,7 +26,7 @@ import {
26
26
  import readline from "node:readline";
27
27
  import { c, errorLine, divider } from "../src/render.js";
28
28
 
29
- const VERSION = "0.16.1";
29
+ const VERSION = "0.16.3";
30
30
 
31
31
  /**
32
32
  * 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.16.1",
3
+ "version": "0.16.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
@@ -6,7 +6,7 @@ import { agentTurnStream, AetherError } from "./api.js";
6
6
  import { TOOL_DEFINITIONS, executeTool } from "./tools.js";
7
7
  import { unnamespaceToolName } from "./mcp.js";
8
8
  import { loadAllSkills, selectSkills, renderSkillsBlock } from "./skills.js";
9
- import { c, divider, turn, toolHeader, toolResult, errorLine } from "./render.js";
9
+ import { c, divider, turn, toolLabel, toolSummary, stripModelTokens, errorLine } from "./render.js";
10
10
 
11
11
  const DEFAULT_MAX_TURNS = 25;
12
12
 
@@ -73,19 +73,23 @@ export async function runAgent({
73
73
  messages: turnMessages,
74
74
  tools,
75
75
  onDelta: (text) => {
76
+ // Strip any leaked model channel/control tokens before display.
77
+ const clean = stripModelTokens(text);
78
+ if (!clean) return;
76
79
  if (!lastWasText) {
77
80
  process.stdout.write(" ");
78
81
  lastWasText = true;
79
82
  }
80
- process.stdout.write(text);
83
+ process.stdout.write(clean);
81
84
  },
82
85
  onToolCallDelta: (delta) => {
83
- // Print the tool header once we know the name (first chunk for that index)
86
+ // Just close the streamed text line when the model starts a tool
87
+ // call — the clean label is printed at execution time, so no noisy
88
+ // "preparing args" placeholder here.
84
89
  if (delta.name && !announced.has(delta.index)) {
85
90
  announced.add(delta.index);
86
91
  if (lastWasText) process.stdout.write("\n");
87
92
  lastWasText = false;
88
- process.stdout.write(c.cyan(c.bold(delta.name)) + c.gray("(...)") + c.gray(" preparing args\n"));
89
93
  }
90
94
  },
91
95
  });
@@ -125,7 +129,7 @@ export async function runAgent({
125
129
  let args = {};
126
130
  try { args = JSON.parse(call.function.arguments || "{}"); } catch { /* leave empty */ }
127
131
  console.log("");
128
- console.log(toolHeader(call.function.name, args));
132
+ console.log(toolLabel(call.function.name, args));
129
133
 
130
134
  // Route to MCP if the tool name is namespaced (mcp__server__tool);
131
135
  // otherwise execute the built-in tool. unnamespaceToolName returns
@@ -143,10 +147,7 @@ export async function runAgent({
143
147
  if (call.function.name === "read_file" || call.function.name === "edit_file" || call.function.name === "write_file") {
144
148
  if (typeof args.path === "string") referencedPaths.push(args.path);
145
149
  }
146
- if (result.output) {
147
- const preview = result.output.length > 800 ? result.output.slice(0, 800) + "\n…(truncated)" : result.output;
148
- console.log(toolResult(preview, result.ok));
149
- }
150
+ console.log(toolSummary(call.function.name, result));
150
151
 
151
152
  messages.push({
152
153
  role: "tool",
package/src/render.js CHANGED
@@ -35,6 +35,70 @@ export function toolHeader(name, args) {
35
35
  return `${c.cyan(c.bold(name))}${c.gray("(")}${c.gray(trimmed)}${c.gray(")")}`;
36
36
  }
37
37
 
38
+ const ellip = (s, n) => (s && s.length > n ? s.slice(0, n - 1) + "…" : s || "");
39
+
40
+ // Clean one-line label for a tool call — a verb + its key argument, instead of
41
+ // dumping raw JSON (which buried file contents / queries in noise).
42
+ export function toolLabel(name, args) {
43
+ const a = args || {};
44
+ const verb = (v) => c.cyan(c.bold(v));
45
+ const arg = (s) => c.gray(ellip(String(s), 72));
46
+ switch (name) {
47
+ case "read_file": return `${verb("read")} ${arg(a.path)}`;
48
+ case "write_file": return `${verb("write")} ${arg(a.path)}`;
49
+ case "edit_file": return `${verb("edit")} ${arg(a.path)}`;
50
+ case "list_dir": return `${verb("list")} ${arg(a.path)}`;
51
+ case "glob_files": return `${verb("glob")} ${arg(a.pattern)}`;
52
+ case "search_files": return `${verb("search")} ${arg(`/${a.pattern}/ in ${a.path ?? "."}`)}`;
53
+ case "run_shell": return `${verb("run")} ${arg(a.command)}`;
54
+ case "web_search": return `${verb("search web")} ${arg(JSON.stringify(a.query ?? ""))}`;
55
+ case "web_fetch": return `${verb("fetch")} ${arg(a.url)}`;
56
+ case "todo_write": return verb("plan");
57
+ default: return `${verb(name)} ${arg(JSON.stringify(a))}`;
58
+ }
59
+ }
60
+
61
+ // Terse one-line result summary instead of dumping raw JSON / file contents.
62
+ // Tools whose handlers already render rich output (diffs, the shell stream, the
63
+ // plan) just get a check — the detail was already printed.
64
+ export function toolSummary(name, result) {
65
+ const ok = result.ok;
66
+ const mark = ok ? c.green("✓") : c.red("✗");
67
+ const out = result.output ?? "";
68
+ const firstLine = out.split("\n").find((l) => l.trim()) ?? "";
69
+
70
+ if (name === "run_shell") {
71
+ let code = null;
72
+ try { code = JSON.parse(out).exit_code; } catch { /* ignore */ }
73
+ return ` ${mark} ${c.gray(code === null ? (ok ? "done" : "failed") : `exit ${code}`)}`;
74
+ }
75
+ if (name === "write_file" || name === "edit_file" || name === "todo_write") {
76
+ // Handler already printed the diff / plan; echo its short status line.
77
+ return ` ${mark} ${c.gray(ellip(firstLine, 100))}`;
78
+ }
79
+ let summary = "";
80
+ try {
81
+ const j = JSON.parse(out);
82
+ if (Array.isArray(j)) summary = `${j.length} result${j.length === 1 ? "" : "s"}`;
83
+ else if (Array.isArray(j.files)) summary = `${j.files.length} file${j.files.length === 1 ? "" : "s"}`;
84
+ else if (Array.isArray(j.matches)) summary = `${j.matches.length} match${j.matches.length === 1 ? "" : "es"}`;
85
+ } catch { /* not JSON */ }
86
+ if (!summary) {
87
+ summary = name === "read_file" ? `${out.split("\n").length} lines` : ellip(firstLine, 100);
88
+ }
89
+ return ` ${mark} ${c.gray(summary)}`;
90
+ }
91
+
92
+ // Strip model "harmony"/channel control tokens (<|channel|>, <|message|>,
93
+ // <|tool_response|>, <channel|>, …) that occasionally leak into the text
94
+ // stream. Belt-and-suspenders alongside the server-side scrub.
95
+ export function stripModelTokens(text) {
96
+ return text
97
+ .replace(/<\|[a-z_]*\|?>/gi, "")
98
+ .replace(/<\/?[a-z_]*\|>/gi, "")
99
+ .replace(/<\|[a-z_]*>/gi, "");
100
+ }
101
+
38
102
  export function toolResult(text, ok = true) {
39
103
  const prefix = ok ? c.green(" ✓ ") : c.red(" ✗ ");
40
104
  // First line bold-ish, then dim continuation
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.16.1";
20
+ const VERSION = "0.16.3";
21
21
  const MODEL_NAME = "Aether Core";
22
22
 
23
23
  const SHORTCUTS = `
@@ -84,11 +84,21 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTur
84
84
  const updateNudge = await updatePromise;
85
85
  if (updateNudge) console.log(updateNudge + "\n");
86
86
 
87
- // Input: the Ink boxed input (bordered, Claude-style) when we have a TTY,
88
- // with a readline fallback for non-TTY/CI or if Ink can't init raw mode.
89
- // Set AETHER_NO_INK=1 to force the plain prompt.
87
+ // Input: the Ink boxed input (bordered, Claude-style) when the terminal
88
+ // renders it cleanly, with a readline fallback otherwise.
89
+ //
90
+ // The legacy Windows console (cmd.exe / conhost) mishandles Ink's live
91
+ // redraw + raw-mode: typed characters ghost (the console echoes what Ink
92
+ // already drew) and the box border tears on each keystroke. Windows Terminal
93
+ // (sets WT_SESSION) and non-Windows render Ink correctly. So we default to
94
+ // the clean prompt on legacy Windows console; AETHER_INK=1 forces Ink there
95
+ // for anyone who wants to try it, AETHER_NO_INK=1 forces the plain prompt.
90
96
  const inputHistory = [];
91
- const useInk = !!process.stdin.isTTY && process.env.AETHER_NO_INK !== "1";
97
+ const legacyWinConsole = process.platform === "win32" && !process.env.WT_SESSION;
98
+ const useInk =
99
+ !!process.stdin.isTTY &&
100
+ process.env.AETHER_NO_INK !== "1" &&
101
+ (process.env.AETHER_INK === "1" || !legacyWinConsole);
92
102
  let inkBroken = false;
93
103
  let rl = null;
94
104