aether-code 0.17.0 → 0.19.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.
- package/bin/aether-code.js +19 -11
- package/package.json +1 -1
- package/src/agent.js +39 -10
- package/src/render.js +10 -3
- package/src/repl.js +5 -3
- package/src/tools.js +2 -2
package/bin/aether-code.js
CHANGED
|
@@ -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.
|
|
29
|
+
const VERSION = "0.19.0";
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Try to start MCP servers from ~/.aether/mcp.json. Returns a started
|
|
@@ -84,10 +84,11 @@ ${c.bold("EXAMPLES")}
|
|
|
84
84
|
aether --cwd ./my-project "fix the failing tests"
|
|
85
85
|
|
|
86
86
|
${c.bold("FLAGS")}
|
|
87
|
-
--
|
|
87
|
+
--review Confirm (y/N) before each write + shell command.
|
|
88
|
+
Default is auto-approve (skip-permissions).
|
|
89
|
+
--sandbox Restrict file access to --cwd. Default is full access.
|
|
88
90
|
--cwd <path> Working directory for the agent (default: current dir).
|
|
89
91
|
--max-turns <n> Maximum turns before stopping (default: 25).
|
|
90
|
-
--unsafe-paths Allow the agent to read/write outside cwd.
|
|
91
92
|
--help, -h Show this help.
|
|
92
93
|
--version, -v Print version.
|
|
93
94
|
|
|
@@ -96,9 +97,10 @@ ${c.bold("CONFIG")}
|
|
|
96
97
|
Get a key at ${c.blue("https://trynoguard.com/account")}.
|
|
97
98
|
|
|
98
99
|
${c.bold("SAFETY")}
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
- By DEFAULT aether runs in skip-permissions mode (like Claude's
|
|
101
|
+
--dangerously-skip-permissions): it auto-approves file writes + shell
|
|
102
|
+
commands and can access your whole filesystem, so it just builds.
|
|
103
|
+
- Use ${c.cyan("--review")} to approve each action, ${c.cyan("--sandbox")} to clamp paths to --cwd.
|
|
102
104
|
- Each shell command has a 2-minute hard timeout.
|
|
103
105
|
|
|
104
106
|
${c.gray(`v${VERSION}`)}
|
|
@@ -132,6 +134,7 @@ function die(msg, code = 1) {
|
|
|
132
134
|
|
|
133
135
|
async function main() {
|
|
134
136
|
const args = parseArgs(process.argv.slice(2));
|
|
137
|
+
setTerminalTitle("Aether");
|
|
135
138
|
|
|
136
139
|
if (args.flags.help) {
|
|
137
140
|
process.stdout.write(HELP);
|
|
@@ -143,8 +146,12 @@ async function main() {
|
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
const cwd = args.flags.cwd ? path.resolve(args.flags.cwd) : process.cwd();
|
|
146
|
-
|
|
147
|
-
|
|
149
|
+
// Skip-permissions by DEFAULT (like `claude --dangerously-skip-permissions`):
|
|
150
|
+
// auto-approve writes/shell and don't sandbox file paths, so the agent can
|
|
151
|
+
// just build. Opt back in with --review (y/N before each action) and
|
|
152
|
+
// --sandbox (clamp file access to --cwd).
|
|
153
|
+
const autoYes = !args.flags.review;
|
|
154
|
+
const unsafePaths = !args.flags.sandbox;
|
|
148
155
|
const maxTurns = Number.isInteger(args.flags.maxTurns) ? args.flags.maxTurns : 25;
|
|
149
156
|
|
|
150
157
|
// Subcommand routing — these shadow the "task as positional arg" mode
|
|
@@ -177,7 +184,7 @@ async function main() {
|
|
|
177
184
|
if (!prompt) {
|
|
178
185
|
if (cwd !== process.cwd()) process.chdir(cwd);
|
|
179
186
|
const mcpManager = await bootMcp();
|
|
180
|
-
await runRepl({ cwd, autoYes, maxTurns, mcpManager });
|
|
187
|
+
await runRepl({ cwd, autoYes, unsafePaths, maxTurns, mcpManager });
|
|
181
188
|
return;
|
|
182
189
|
}
|
|
183
190
|
|
|
@@ -189,7 +196,8 @@ async function main() {
|
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
console.log(divider());
|
|
192
|
-
|
|
199
|
+
const modeLabel = autoYes && unsafePaths ? " · skip-permissions" : `${autoYes ? " · auto-yes" : " · review"}${unsafePaths ? "" : " · sandboxed"}`;
|
|
200
|
+
console.log(c.magenta(c.bold("aether-code")) + c.gray(` · cwd ${cwd}${modeLabel}`));
|
|
193
201
|
console.log(c.gray(`task: `) + prompt);
|
|
194
202
|
console.log(divider());
|
|
195
203
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aether-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// in real-time, executes any tool calls, loops until the model returns no
|
|
3
3
|
// tool calls (task done) or max-turns is reached.
|
|
4
4
|
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
5
7
|
import { agentTurnStream, AetherError } from "./api.js";
|
|
6
8
|
import { TOOL_DEFINITIONS, executeTool } from "./tools.js";
|
|
7
9
|
import { unnamespaceToolName } from "./mcp.js";
|
|
@@ -10,6 +12,26 @@ import { c, divider, turn, toolLabel, toolSummary, makeTokenStripper, errorLine
|
|
|
10
12
|
|
|
11
13
|
const DEFAULT_MAX_TURNS = 25;
|
|
12
14
|
|
|
15
|
+
// Environment block prepended to the first user message so the model can
|
|
16
|
+
// resolve named locations to real absolute paths.
|
|
17
|
+
function envContext(cwd) {
|
|
18
|
+
const home = os.homedir();
|
|
19
|
+
const desktop = path.join(home, "Desktop");
|
|
20
|
+
const documents = path.join(home, "Documents");
|
|
21
|
+
return (
|
|
22
|
+
`[environment]\n` +
|
|
23
|
+
`os: ${process.platform}\n` +
|
|
24
|
+
`cwd: ${cwd}\n` +
|
|
25
|
+
`home: ${home}\n` +
|
|
26
|
+
`desktop: ${desktop}\n` +
|
|
27
|
+
`documents: ${documents}\n` +
|
|
28
|
+
`When the user names a location ("my desktop", "home", "documents"), write to ` +
|
|
29
|
+
`the matching ABSOLUTE path above (e.g. desktop -> ${desktop}). Otherwise ` +
|
|
30
|
+
`work under the cwd. Use absolute paths when a specific location is named.\n` +
|
|
31
|
+
`[/environment]\n\n`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
13
35
|
export async function runAgent({
|
|
14
36
|
initialPrompt,
|
|
15
37
|
priorMessages,
|
|
@@ -43,16 +65,22 @@ export async function runAgent({
|
|
|
43
65
|
const referencedPaths = [];
|
|
44
66
|
// Two callers: one-shot (initialPrompt only, fresh conversation) and REPL
|
|
45
67
|
// (priorMessages + initialPrompt to continue an ongoing chat).
|
|
68
|
+
// On the FIRST message of a session, prepend an environment block so the
|
|
69
|
+
// model knows real absolute paths (cwd / home / desktop). Without it, "build
|
|
70
|
+
// X on my desktop" became `mkdir X` in whatever dir aether was launched from
|
|
71
|
+
// (e.g. C:\WINDOWS\system32). Only prepended once — later turns carry it in
|
|
72
|
+
// history.
|
|
46
73
|
const messages = priorMessages
|
|
47
74
|
? [...priorMessages, { role: "user", content: initialPrompt }]
|
|
48
|
-
: [{ role: "user", content: initialPrompt }];
|
|
75
|
+
: [{ role: "user", content: envContext(cwd) + initialPrompt }];
|
|
49
76
|
let totalCredits = 0;
|
|
50
77
|
let totalIn = 0;
|
|
51
78
|
let totalOut = 0;
|
|
52
79
|
let lastBalance = null;
|
|
53
80
|
|
|
54
81
|
for (let i = 0; i < maxTurns; i++) {
|
|
55
|
-
|
|
82
|
+
// A blank line separates steps (no noisy "turn N" headers).
|
|
83
|
+
process.stdout.write("\n");
|
|
56
84
|
|
|
57
85
|
// Stream the assistant's response. Print text deltas as they arrive,
|
|
58
86
|
// along with tool-call announcements as soon as the model commits to
|
|
@@ -79,7 +107,7 @@ export async function runAgent({
|
|
|
79
107
|
const clean = stripper.push(text);
|
|
80
108
|
if (!clean) return;
|
|
81
109
|
if (!lastWasText) {
|
|
82
|
-
process.stdout.write("
|
|
110
|
+
process.stdout.write(c.cyan("● "));
|
|
83
111
|
lastWasText = true;
|
|
84
112
|
}
|
|
85
113
|
process.stdout.write(clean);
|
|
@@ -105,7 +133,7 @@ export async function runAgent({
|
|
|
105
133
|
// Flush any held-back partial token, then close the line.
|
|
106
134
|
const tail = stripper.flush();
|
|
107
135
|
if (tail) {
|
|
108
|
-
if (!lastWasText) { process.stdout.write("
|
|
136
|
+
if (!lastWasText) { process.stdout.write(c.cyan("● ")); lastWasText = true; }
|
|
109
137
|
process.stdout.write(tail);
|
|
110
138
|
}
|
|
111
139
|
if (lastWasText) process.stdout.write("\n");
|
|
@@ -114,9 +142,8 @@ export async function runAgent({
|
|
|
114
142
|
totalOut += res.usage?.completion_tokens ?? 0;
|
|
115
143
|
if (typeof res.balanceAfter === "number") lastBalance = res.balanceAfter;
|
|
116
144
|
onTokens({ totalCredits, totalIn, totalOut, balance: lastBalance });
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
);
|
|
145
|
+
// Per-turn cost line removed for a cleaner look — the session summary at the
|
|
146
|
+
// end carries the totals.
|
|
120
147
|
|
|
121
148
|
// Push assistant message into history
|
|
122
149
|
messages.push({
|
|
@@ -135,8 +162,9 @@ export async function runAgent({
|
|
|
135
162
|
for (const call of toolCalls) {
|
|
136
163
|
let args = {};
|
|
137
164
|
try { args = JSON.parse(call.function.arguments || "{}"); } catch { /* leave empty */ }
|
|
138
|
-
|
|
139
|
-
|
|
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);
|
|
140
168
|
|
|
141
169
|
// Route to MCP if the tool name is namespaced (mcp__server__tool);
|
|
142
170
|
// otherwise execute the built-in tool. unnamespaceToolName returns
|
|
@@ -154,7 +182,8 @@ export async function runAgent({
|
|
|
154
182
|
if (call.function.name === "read_file" || call.function.name === "edit_file" || call.function.name === "write_file") {
|
|
155
183
|
if (typeof args.path === "string") referencedPaths.push(args.path);
|
|
156
184
|
}
|
|
157
|
-
|
|
185
|
+
const summary = toolSummary(call.function.name, result);
|
|
186
|
+
if (summary) console.log(summary);
|
|
158
187
|
|
|
159
188
|
messages.push({
|
|
160
189
|
role: "tool",
|
package/src/render.js
CHANGED
|
@@ -53,11 +53,17 @@ 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
|
|
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.
|
|
@@ -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 === "
|
|
76
|
-
|
|
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 = "";
|
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.
|
|
20
|
+
const VERSION = "0.19.0";
|
|
21
21
|
const MODEL_NAME = "Aether Core";
|
|
22
22
|
|
|
23
23
|
const SHORTCUTS = `
|
|
@@ -34,10 +34,11 @@ ${c.gray("Anything else is sent to the agent as your next message.")}
|
|
|
34
34
|
${c.gray("Conversation history is kept across messages until you /clear.")}
|
|
35
35
|
`;
|
|
36
36
|
|
|
37
|
-
export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTurns: initialMaxTurns, mcpManager = null }) {
|
|
37
|
+
export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafePaths: initialUnsafePaths, maxTurns: initialMaxTurns, mcpManager = null }) {
|
|
38
38
|
const state = {
|
|
39
39
|
cwd: initialCwd,
|
|
40
40
|
autoYes: !!initialAutoYes,
|
|
41
|
+
unsafePaths: !!initialUnsafePaths,
|
|
41
42
|
maxTurns: initialMaxTurns ?? 25,
|
|
42
43
|
messages: [], // accumulates across turns
|
|
43
44
|
balance: null,
|
|
@@ -170,6 +171,7 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTur
|
|
|
170
171
|
priorMessages: state.messages.length > 0 ? state.messages : undefined,
|
|
171
172
|
cwd: state.cwd,
|
|
172
173
|
autoYes: state.autoYes,
|
|
174
|
+
unsafePaths: state.unsafePaths,
|
|
173
175
|
maxTurns: state.maxTurns,
|
|
174
176
|
mcpManager,
|
|
175
177
|
});
|
|
@@ -275,7 +277,7 @@ function printBanner(state) {
|
|
|
275
277
|
// Brand-coloured rules top + bottom; content is indented with no right
|
|
276
278
|
// border, so nothing can misalign regardless of terminal font/width.
|
|
277
279
|
const rule = c.magenta("─".repeat(W));
|
|
278
|
-
const mode = state.autoYes ? "auto-yes" : "review mode";
|
|
280
|
+
const mode = state.autoYes ? (state.unsafePaths ? "skip-permissions" : "auto-yes") : "review mode";
|
|
279
281
|
|
|
280
282
|
console.log("");
|
|
281
283
|
console.log(rule);
|
package/src/tools.js
CHANGED
|
@@ -789,8 +789,8 @@ function renderTodos(todos) {
|
|
|
789
789
|
t.status === "completed"
|
|
790
790
|
? c.green("✓")
|
|
791
791
|
: t.status === "in_progress"
|
|
792
|
-
? c.yellow("
|
|
793
|
-
: c.dim("
|
|
792
|
+
? c.yellow("→")
|
|
793
|
+
: c.dim("·");
|
|
794
794
|
const text =
|
|
795
795
|
t.status === "completed"
|
|
796
796
|
? c.dim(t.content)
|