aether-code 0.16.4 → 0.18.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 +17 -10
- package/package.json +1 -1
- package/src/agent.js +28 -1
- package/src/repl.js +27 -21
- package/src/tools.js +2 -2
package/bin/aether-code.js
CHANGED
|
@@ -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.
|
|
29
|
+
const VERSION = "0.18.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}`)}
|
|
@@ -143,8 +145,12 @@ async function main() {
|
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
const cwd = args.flags.cwd ? path.resolve(args.flags.cwd) : process.cwd();
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
// Skip-permissions by DEFAULT (like `claude --dangerously-skip-permissions`):
|
|
149
|
+
// auto-approve writes/shell and don't sandbox file paths, so the agent can
|
|
150
|
+
// just build. Opt back in with --review (y/N before each action) and
|
|
151
|
+
// --sandbox (clamp file access to --cwd).
|
|
152
|
+
const autoYes = !args.flags.review;
|
|
153
|
+
const unsafePaths = !args.flags.sandbox;
|
|
148
154
|
const maxTurns = Number.isInteger(args.flags.maxTurns) ? args.flags.maxTurns : 25;
|
|
149
155
|
|
|
150
156
|
// Subcommand routing — these shadow the "task as positional arg" mode
|
|
@@ -177,7 +183,7 @@ async function main() {
|
|
|
177
183
|
if (!prompt) {
|
|
178
184
|
if (cwd !== process.cwd()) process.chdir(cwd);
|
|
179
185
|
const mcpManager = await bootMcp();
|
|
180
|
-
await runRepl({ cwd, autoYes, maxTurns, mcpManager });
|
|
186
|
+
await runRepl({ cwd, autoYes, unsafePaths, maxTurns, mcpManager });
|
|
181
187
|
return;
|
|
182
188
|
}
|
|
183
189
|
|
|
@@ -189,7 +195,8 @@ async function main() {
|
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
console.log(divider());
|
|
192
|
-
|
|
198
|
+
const modeLabel = autoYes && unsafePaths ? " · skip-permissions" : `${autoYes ? " · auto-yes" : " · review"}${unsafePaths ? "" : " · sandboxed"}`;
|
|
199
|
+
console.log(c.magenta(c.bold("aether-code")) + c.gray(` · cwd ${cwd}${modeLabel}`));
|
|
193
200
|
console.log(c.gray(`task: `) + prompt);
|
|
194
201
|
console.log(divider());
|
|
195
202
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aether-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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,9 +65,14 @@ 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;
|
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.18.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,
|
|
@@ -100,28 +101,32 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTur
|
|
|
100
101
|
process.env.AETHER_NO_INK !== "1" &&
|
|
101
102
|
(process.env.AETHER_INK === "1" || !legacyWinConsole);
|
|
102
103
|
let inkBroken = false;
|
|
103
|
-
let rl = null;
|
|
104
104
|
|
|
105
105
|
// The Ink box carries its own status bar; the plain prompt doesn't, so show a
|
|
106
106
|
// one-line hint up front when we won't be using Ink.
|
|
107
107
|
if (!useInk) console.log(` ${c.cyan("/help")}${c.dim(" shortcuts")} ${c.cyan("/exit")}${c.dim(" quit")}\n`);
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
console.log(c.gray(`\n(Press Ctrl+C again within 1.5s to exit, or type ${c.cyan("/exit")})`));
|
|
118
|
-
});
|
|
119
|
-
return rl;
|
|
120
|
-
}
|
|
121
|
-
|
|
109
|
+
// Fresh readline interface PER PROMPT. A single persistent interface
|
|
110
|
+
// conflicted with the per-confirmation readline interfaces the tools open
|
|
111
|
+
// during a turn (each [y/N] prompt). When a tool's interface closed it left
|
|
112
|
+
// the REPL's stdin dead — so after finishing a request aether printed the
|
|
113
|
+
// prompt and immediately EXITED instead of waiting for the next message.
|
|
114
|
+
// Opening a fresh one each prompt guarantees only one interface exists at a
|
|
115
|
+
// time, so the REPL keeps running until the user /exits. Returns EXIT_SIGNAL
|
|
116
|
+
// on a double Ctrl+C.
|
|
122
117
|
function readlineQuestion() {
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
const r = readline.createInterface({ input: process.stdin, output: process.stdout, historySize: 200 });
|
|
120
|
+
let lastSigint = 0;
|
|
121
|
+
r.on("SIGINT", () => {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
if (now - lastSigint < 1500) { r.close(); resolve(EXIT_SIGNAL); return; }
|
|
124
|
+
lastSigint = now;
|
|
125
|
+
process.stdout.write(c.gray(`\n(Press Ctrl+C again within 1.5s to exit, or type ${c.cyan("/exit")})\n`));
|
|
126
|
+
r.prompt();
|
|
127
|
+
});
|
|
128
|
+
r.question(c.magenta("> "), (ans) => { r.close(); resolve(ans); });
|
|
129
|
+
});
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
// Returns the next raw input line, or EXIT_SIGNAL to quit.
|
|
@@ -143,7 +148,7 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTur
|
|
|
143
148
|
|
|
144
149
|
while (true) {
|
|
145
150
|
const raw = await nextLine();
|
|
146
|
-
if (raw === EXIT_SIGNAL) { console.log(c.gray("bye."));
|
|
151
|
+
if (raw === EXIT_SIGNAL) { console.log(c.gray("bye.")); return; }
|
|
147
152
|
const line = (raw ?? "").trim();
|
|
148
153
|
if (!line) continue;
|
|
149
154
|
|
|
@@ -155,7 +160,7 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTur
|
|
|
155
160
|
// Slash command?
|
|
156
161
|
if (line.startsWith("/") || line === "?") {
|
|
157
162
|
const handled = await handleSlash(line, state);
|
|
158
|
-
if (handled === "exit")
|
|
163
|
+
if (handled === "exit") return;
|
|
159
164
|
printStatusLine(state);
|
|
160
165
|
continue;
|
|
161
166
|
}
|
|
@@ -166,6 +171,7 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTur
|
|
|
166
171
|
priorMessages: state.messages.length > 0 ? state.messages : undefined,
|
|
167
172
|
cwd: state.cwd,
|
|
168
173
|
autoYes: state.autoYes,
|
|
174
|
+
unsafePaths: state.unsafePaths,
|
|
169
175
|
maxTurns: state.maxTurns,
|
|
170
176
|
mcpManager,
|
|
171
177
|
});
|
|
@@ -271,7 +277,7 @@ function printBanner(state) {
|
|
|
271
277
|
// Brand-coloured rules top + bottom; content is indented with no right
|
|
272
278
|
// border, so nothing can misalign regardless of terminal font/width.
|
|
273
279
|
const rule = c.magenta("─".repeat(W));
|
|
274
|
-
const mode = state.autoYes ? "auto-yes" : "review mode";
|
|
280
|
+
const mode = state.autoYes ? (state.unsafePaths ? "skip-permissions" : "auto-yes") : "review mode";
|
|
275
281
|
|
|
276
282
|
console.log("");
|
|
277
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)
|