aether-code 0.33.1 → 0.33.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.
@@ -28,7 +28,7 @@ import {
28
28
  import readline from "node:readline";
29
29
  import { c, errorLine, divider, setTerminalTitle } from "../src/render.js";
30
30
 
31
- const VERSION = "0.33.1";
31
+ const VERSION = "0.33.3";
32
32
 
33
33
  /**
34
34
  * 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.33.1",
3
+ "version": "0.33.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/box-input.js CHANGED
@@ -1,7 +1,8 @@
1
- // Bordered single-line text input — raw-mode, cmd.exe-safe.
1
+ // Minimal prompt input — raw-mode, cmd.exe-safe.
2
+ // Bare "> " prompt with bottom status bar, matching Claude Code's layout.
2
3
  //
3
4
  // Features:
4
- // - Ctrl+V / Alt+V: paste image from clipboard (saves to temp, attaches)
5
+ // - Ctrl+V / Alt+V: paste image from clipboard
5
6
  // - @file.png: reference images by path (parsed by agent.js)
6
7
  // - Arrow keys, Home/End, Ctrl+A/E, Ctrl+U, history navigation
7
8
  // - Horizontal scroll for long input
@@ -22,8 +23,6 @@ const HIDE = "\x1b[?25l";
22
23
  const SHOW = "\x1b[?25h";
23
24
  const INV = (s) => `\x1b[7m${s}\x1b[27m`;
24
25
 
25
- // Save clipboard image to a temp file. Returns the file path or null.
26
- // Platform-specific: PowerShell on Windows, pngpaste on macOS, xclip on Linux.
27
26
  function grabClipboardImage() {
28
27
  const tmpFile = path.join(os.tmpdir(), `aether-paste-${Date.now()}.png`);
29
28
 
@@ -42,9 +41,8 @@ function grabClipboardImage() {
42
41
  try {
43
42
  execFileSync("pngpaste", [tmpFile], { timeout: 3000, stdio: "pipe" });
44
43
  if (fs.existsSync(tmpFile) && fs.statSync(tmpFile).size > 0) return tmpFile;
45
- } catch { /* pngpaste not installed — hint below */ }
44
+ } catch { /* pngpaste not installed */ }
46
45
  } else {
47
- // Linux: xclip
48
46
  try {
49
47
  const fd = fs.openSync(tmpFile, "w");
50
48
  try {
@@ -59,14 +57,12 @@ function grabClipboardImage() {
59
57
  }
60
58
  } catch { /* any failure — fall through */ }
61
59
 
62
- // Clean up empty/failed temp file
63
60
  try { if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile); } catch { /* ignore */ }
64
61
  return null;
65
62
  }
66
63
 
67
- export function promptBox({ history = [], placeholder = "Message Aether…" } = {}) {
64
+ export function promptBox({ history = [], placeholder = "Message Aether...", mode = "review" } = {}) {
68
65
  return new Promise((resolve) => {
69
- // Non-interactive (piped / tests): plain readline, no box.
70
66
  if (!process.stdin.isTTY) {
71
67
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
72
68
  rl.question(c.cyan("> "), (a) => { rl.close(); resolve(a); });
@@ -80,46 +76,53 @@ export function promptBox({ history = [], placeholder = "Message Aether…" } =
80
76
  let draft = "";
81
77
  let drawn = 0;
82
78
  let lastSigint = 0;
83
- const clipImages = []; // temp file paths from clipboard pastes
79
+ const clipImages = [];
84
80
 
85
- const innerW = () => Math.max(24, Math.min(process.stdout.columns || 80, 100) - 6);
81
+ const termW = () => Math.max(30, (process.stdout.columns || 80) - 2);
86
82
 
87
83
  const draw = (first) => {
88
- const W = innerW();
84
+ const W = termW();
89
85
  if (!first) process.stdout.write(`\x1b[${drawn}A`);
90
86
  process.stdout.write("\x1b[J");
91
87
 
92
- // Horizontal scroll: keep the caret within the visible window.
93
- const maxVisible = W - 1;
88
+ const prefixStr = "> ";
89
+ const prefixLen = 2;
90
+ const maxVisible = W - prefixLen;
94
91
  const start = pos > maxVisible ? pos - maxVisible : 0;
95
- const view = buf.slice(start, start + W);
92
+ const view = buf.slice(start, start + maxVisible);
96
93
  const rel = pos - start;
97
94
 
98
95
  let content;
99
- if (buf.length === 0) {
100
- const ph = placeholder.slice(0, W - 1);
101
- content = INV(" ") + c.dim(ph) + " ".repeat(Math.max(0, W - 1 - ph.length));
96
+ if (buf.length === 0 && clipImages.length === 0) {
97
+ content = INV(" ") + c.dim(placeholder.slice(0, maxVisible - 1));
98
+ } else if (buf.length === 0 && clipImages.length > 0) {
99
+ const tag = `[${clipImages.length} image${clipImages.length > 1 ? "s" : ""} pasted]`;
100
+ content = INV(" ") + c.cyan(tag);
102
101
  } else {
103
102
  let s = "";
104
- for (let i = 0; i < W; i++) {
103
+ for (let i = 0; i < maxVisible; i++) {
105
104
  const ch = view[i] ?? " ";
106
105
  s += i === rel ? INV(ch) : ch;
107
106
  }
107
+ if (clipImages.length > 0) {
108
+ s += " " + c.cyan(`[+${clipImages.length} img]`);
109
+ }
108
110
  content = s;
109
111
  }
110
112
 
111
- const top = c.cyan("╭" + "─".repeat(W + 4) + "╮");
112
- const mid = c.cyan("│") + " " + c.cyan(">") + " " + content + " " + c.cyan("│");
113
- const bot = c.cyan("╰" + "─".repeat(W + 4) + "╯");
113
+ // Bare prompt — just "> " like Claude Code
114
+ const promptLine = c.dim(prefixStr) + content;
114
115
 
115
- // Hint line — show paste indicator when images are queued
116
- const imgTag = clipImages.length > 0
117
- ? " " + c.cyan(`[${clipImages.length} image${clipImages.length > 1 ? "s" : ""} pasted]`)
118
- : "";
119
- const hint = c.dim(" Enter send · Alt+V paste image · @file · /help") + imgTag;
116
+ // Bottom status bar — matches Claude's "manual mode on · ? for shortcuts · + for agents"
117
+ const imgNote = clipImages.length > 0 ? c.cyan(" images queued") + c.dim(" \xB7 ") : "";
118
+ const statusBar = c.dim(
119
+ mode + " mode" +
120
+ " \xB7 " + "? for shortcuts" +
121
+ " \xB7 " + "@file for images",
122
+ ) + imgNote;
120
123
 
121
- process.stdout.write(top + "\n" + mid + "\n" + bot + "\n" + hint + "\n");
122
- drawn = 4;
124
+ process.stdout.write(promptLine + "\n" + statusBar + "\n");
125
+ drawn = 2;
123
126
  };
124
127
 
125
128
  const finish = (result) => {
@@ -128,7 +131,6 @@ export function promptBox({ history = [], placeholder = "Message Aether…" } =
128
131
  process.stdin.pause();
129
132
  process.stdout.write(`\x1b[${drawn}A\x1b[J` + SHOW);
130
133
 
131
- // Append clipboard image temp paths so agent.js's @-parser picks them up.
132
134
  let finalResult = result;
133
135
  if (result !== null && result !== EXIT_SIGNAL && clipImages.length > 0) {
134
136
  const userText = (result || "").trim();
@@ -136,7 +138,6 @@ export function promptBox({ history = [], placeholder = "Message Aether…" } =
136
138
  finalResult = userText ? `${userText} ${imgRefs}` : `What is in this image? ${imgRefs}`;
137
139
  }
138
140
 
139
- // Echo the submitted line so it persists in scrollback.
140
141
  if (finalResult && finalResult !== EXIT_SIGNAL) {
141
142
  const echoText = (result || "").trim();
142
143
  const suffix = clipImages.length > 0
@@ -163,7 +164,6 @@ export function promptBox({ history = [], placeholder = "Message Aether…" } =
163
164
  }
164
165
  if (key.ctrl && k === "d") return finish(buf.length === 0 ? EXIT_SIGNAL : buf);
165
166
 
166
- // Alt+V or Ctrl+V: paste image from clipboard
167
167
  if ((key.meta && k === "v") || (key.ctrl && k === "v")) {
168
168
  const imgPath = grabClipboardImage();
169
169
  if (imgPath) {
@@ -195,7 +195,7 @@ export function promptBox({ history = [], placeholder = "Message Aether…" } =
195
195
  buf = buf.slice(0, pos) + str + buf.slice(pos);
196
196
  pos += str.length;
197
197
  }
198
- else return; // ignore unhandled keys without a redraw
198
+ else return;
199
199
  draw(false);
200
200
  };
201
201
 
package/src/repl.js CHANGED
@@ -5,11 +5,11 @@ import path from "node:path";
5
5
  import { runAgent } from "./agent.js";
6
6
  import { fetchBalance, AetherError } from "./api.js";
7
7
  import { runSetup } from "./setup.js";
8
- import { c, boxLines, errorLine } from "./render.js";
8
+ import { c, errorLine } from "./render.js";
9
9
  import { autoUpdate } from "./update-check.js";
10
10
  import { promptBox, EXIT_SIGNAL } from "./box-input.js";
11
11
 
12
- const VERSION = "0.33.1";
12
+ const VERSION = "0.33.3";
13
13
  const MODEL_NAME = "Aether Core";
14
14
 
15
15
  const MODEL_ALIASES = {
@@ -49,7 +49,6 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafe
49
49
  sessionOut: 0,
50
50
  };
51
51
 
52
- // Auto-update + balance fetch in parallel — zero startup latency added.
53
52
  const updatePromise = autoUpdate().catch(() => null);
54
53
 
55
54
  let needsSetup = false;
@@ -80,12 +79,13 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafe
80
79
 
81
80
  printBanner(state);
82
81
  const updateMsg = await updatePromise;
83
- if (updateMsg) console.log(updateMsg + "\n");
82
+ if (updateMsg) console.log(updateMsg);
83
+ console.log("");
84
84
 
85
85
  const inputHistory = [];
86
86
 
87
87
  while (true) {
88
- const raw = await promptBox({ history: inputHistory });
88
+ const raw = await promptBox({ history: inputHistory, mode: state.autoYes ? "auto-yes" : "review" });
89
89
  if (raw === EXIT_SIGNAL) { console.log(c.gray("bye.")); return; }
90
90
  const line = (raw ?? "").trim();
91
91
  if (!line) continue;
@@ -95,7 +95,6 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafe
95
95
  if (line.startsWith("/") || line === "?") {
96
96
  const handled = await handleSlash(line, state);
97
97
  if (handled === "exit") return;
98
- printStatusLine(state);
99
98
  continue;
100
99
  }
101
100
 
@@ -119,8 +118,6 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafe
119
118
  if (!result.ok && result.error) {
120
119
  console.log("\n" + errorLine(result.error.message || String(result.error)));
121
120
  }
122
-
123
- printStatusLine(state);
124
121
  }
125
122
  }
126
123
 
@@ -168,7 +165,7 @@ async function handleSlash(line, state) {
168
165
  try {
169
166
  process.chdir(next);
170
167
  state.cwd = next;
171
- console.log(c.gray(`cwd → ${state.cwd}`));
168
+ console.log(c.gray(`cwd -> ${state.cwd}`));
172
169
  } catch (err) {
173
170
  console.log(errorLine(err.message));
174
171
  }
@@ -200,7 +197,7 @@ async function handleSlash(line, state) {
200
197
  state.modelName = MODEL_ALIASES[key].name;
201
198
  const premium = state.model !== null;
202
199
  console.log(
203
- c.gray(`model → ${c.cyan(state.modelName)}`) +
200
+ c.gray(`model -> ${c.cyan(state.modelName)}`) +
204
201
  (premium ? c.gray(" (premium — needs purchased credits, else falls back to Core)") : ""),
205
202
  );
206
203
  } else {
@@ -214,36 +211,36 @@ async function handleSlash(line, state) {
214
211
  return null;
215
212
  }
216
213
 
217
- /* ───────── banner + status ───────── */
214
+ /* ───────── banner ───────── */
218
215
 
219
216
  const shortenPath = (p, max) => (p.length <= max ? p : "..." + p.slice(p.length - max + 3));
220
217
 
221
218
  function printBanner(state) {
222
219
  const credits = state.balance != null ? `${state.balance.toLocaleString()} credits` : "";
220
+ const plan = state.plan && state.plan !== "FREE" ? state.plan : "";
223
221
 
224
- // Single clean banner box — Claude Code style
225
- const lines = [
226
- c.bold(c.cyan("✦ Aether Code")) + c.gray(" v" + VERSION),
227
- "",
228
- c.gray(" " + state.modelName + (credits ? " · " + credits : "")),
229
- c.gray(" cwd: " + shortenPath(state.cwd, 52)),
230
- "",
231
- c.dim(" Ctrl+V paste · @file image · /help"),
232
- ];
233
-
234
- const banner = boxLines(lines, { color: c.cyan, padX: 1, minInner: 50 });
235
- console.log("");
236
- console.log(banner.join("\n"));
237
- console.log("");
238
- }
222
+ // Block logo — CP437-safe colored squares, like Claude Code's logo
223
+ const b1 = c.cyan("\xDB\xDB"); // ██ full blocks
224
+ const b2 = c.blue("\xDB\xDB"); // ██ darker accent
225
+ const gap = " ";
239
226
 
240
- function printStatusLine(state) {
241
- const parts = [];
242
- parts.push(c.gray(`session: ${state.sessionCredits} cr · ${state.sessionIn}→${state.sessionOut} tokens`));
243
- if (state.balance != null) parts.push(c.gray(`balance: ${state.balance.toLocaleString()}`));
244
- if (state.messages.length > 0) {
245
- parts.push(c.gray(`history: ${state.messages.length} msg${state.messages.length === 1 ? "" : "s"}`));
246
- }
247
- parts.push(c.cyan(state.autoYes ? "auto-yes" : "review"));
248
- console.log(c.dim(parts.join(" · ")));
227
+ // 3-line logo + text, no boxes — matches Claude Code layout
228
+ console.log("");
229
+ console.log(
230
+ " " + b1 + " " + b2 + gap +
231
+ c.bold("Aether Code") + " " + c.dim("v" + VERSION),
232
+ );
233
+
234
+ const modelParts = [state.modelName];
235
+ if (credits) modelParts.push(credits);
236
+ if (plan) modelParts.push(c.cyan(plan));
237
+ console.log(
238
+ " " + b2 + " " + b1 + gap +
239
+ modelParts.join(c.dim(" \xB7 ")),
240
+ );
241
+
242
+ console.log(
243
+ " " + b1 + " " + b2 + gap +
244
+ c.dim(shortenPath(state.cwd, 60)),
245
+ );
249
246
  }