aether-code 0.13.0 → 0.15.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.
@@ -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.13.0";
29
+ const VERSION = "0.15.0";
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.13.0",
3
+ "version": "0.15.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": {
@@ -23,7 +23,7 @@
23
23
  "node": ">=18"
24
24
  },
25
25
  "scripts": {
26
- "lint": "node --check bin/aether-code.js src/agent.js src/api.js src/config.js src/render.js src/tools.js src/diff.js src/repl.js src/mcp.js src/mcp-cli.js src/mcp-registry.js src/skills.js",
26
+ "lint": "node --check bin/aether-code.js src/agent.js src/api.js src/config.js src/render.js src/tools.js src/diff.js src/repl.js src/mcp.js src/mcp-cli.js src/mcp-registry.js src/skills.js src/update-check.js",
27
27
  "test": "node --test \"test/**/*.test.js\"",
28
28
  "prepublishOnly": "npm run lint && npm test"
29
29
  },
package/src/repl.js CHANGED
@@ -14,8 +14,9 @@ import { runAgent } from "./agent.js";
14
14
  import { fetchBalance, AetherError } from "./api.js";
15
15
  import { runSetup } from "./setup.js";
16
16
  import { c, errorLine } from "./render.js";
17
+ import { checkForUpdate } from "./update-check.js";
17
18
 
18
- const VERSION = "0.13.0";
19
+ const VERSION = "0.15.0";
19
20
  const MODEL_NAME = "Aether Core";
20
21
 
21
22
  const SHORTCUTS = `
@@ -44,6 +45,10 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTur
44
45
  sessionOut: 0,
45
46
  };
46
47
 
48
+ // Kick off the npm update check concurrently with the balance fetch so it
49
+ // adds no startup latency; the nudge (if any) prints just under the banner.
50
+ const updatePromise = checkForUpdate().catch(() => null);
51
+
47
52
  // Free balance check up front. If no key configured, walk through first-time
48
53
  // setup flow (open browser → paste key → verify → save).
49
54
  let needsSetup = false;
@@ -75,6 +80,8 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, maxTur
75
80
  }
76
81
 
77
82
  printBanner(state);
83
+ const updateNudge = await updatePromise;
84
+ if (updateNudge) console.log(updateNudge + "\n");
78
85
 
79
86
  const rl = readline.createInterface({
80
87
  input: process.stdin,
@@ -221,17 +228,30 @@ async function handleSlash(line, state) {
221
228
 
222
229
  /* ───────── banner + status ───────── */
223
230
 
231
+ const visLen = (s) => s.replace(/\x1b\[[0-9;]*m/g, "").length;
232
+ const shortenPath = (p, max) => (p.length <= max ? p : "..." + p.slice(p.length - max + 3));
233
+
224
234
  function printBanner(state) {
235
+ const cols = process.stdout.columns || 80;
236
+ const W = Math.max(40, Math.min(cols - 1, 64));
237
+ // Brand-coloured rules top + bottom; content is indented with no right
238
+ // border, so nothing can misalign regardless of terminal font/width.
239
+ const rule = c.magenta("─".repeat(W));
240
+ const mode = state.autoYes ? "auto-yes" : "review mode";
241
+
225
242
  console.log("");
226
- console.log(c.bold(c.magenta(`aether-code`)) + c.gray(` v${VERSION}`));
227
- console.log(
228
- c.gray(`${MODEL_NAME} (1M context) · uncensored · `) +
229
- c.cyan(state.autoYes ? "auto-yes" : "review mode") +
230
- (state.balance != null ? c.gray(` · ${state.balance.toLocaleString()} credits`) : ""),
231
- );
232
- console.log(c.gray(state.cwd));
233
- console.log("");
234
- console.log(c.gray(`Type ${c.cyan("/help")} for shortcuts. ${c.cyan("/exit")} or Ctrl+C twice to quit.`));
243
+ console.log(rule);
244
+ console.log(` ${c.bold(c.magenta("aether-code"))}${c.gray(" v" + VERSION)}`);
245
+ console.log(` ${c.gray(`${MODEL_NAME} · 1M context · uncensored`)}`);
246
+ console.log(` ${c.gray(mode)}${state.balance != null ? c.gray(` · ${state.balance.toLocaleString()} credits`) : ""}`);
247
+ console.log(` ${c.gray(shortenPath(state.cwd, W - 2))}`);
248
+ console.log(rule);
249
+
250
+ // Bottom status bar: shortcuts on the left, mode on the right (Claude-style).
251
+ const left = ` ${c.cyan("/help")}${c.dim(" shortcuts")} ${c.cyan("/exit")}${c.dim(" quit")}`;
252
+ const right = `${c.cyan(mode)}${c.dim(" · ")}${c.gray(MODEL_NAME)} `;
253
+ const gap = Math.max(3, cols - visLen(left) - visLen(right) - 1);
254
+ console.log(left + " ".repeat(gap) + right);
235
255
  console.log("");
236
256
  }
237
257
 
@@ -0,0 +1,60 @@
1
+ // Startup update check — like Claude Code / npm itself nudging you when a newer
2
+ // version is published. Never throws, bounded by a short timeout so it can't
3
+ // slow or block startup; silent when offline or already current.
4
+
5
+ import { readFileSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import { dirname, join } from "node:path";
8
+ import { c } from "./render.js";
9
+
10
+ export function currentVersion() {
11
+ try {
12
+ const p = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
13
+ return JSON.parse(readFileSync(p, "utf8")).version || null;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ // Compare two "x.y.z" strings. Returns >0 if a is newer than b.
20
+ export function cmpVersion(a, b) {
21
+ const pa = String(a).split(".").map(Number);
22
+ const pb = String(b).split(".").map(Number);
23
+ for (let i = 0; i < 3; i++) {
24
+ const d = (pa[i] || 0) - (pb[i] || 0);
25
+ if (d !== 0) return d;
26
+ }
27
+ return 0;
28
+ }
29
+
30
+ /**
31
+ * Resolve a one-line "update available" nudge if the npm registry has a newer
32
+ * version than the installed one — otherwise null. Bounded to ~2s; any failure
33
+ * (offline, timeout, registry hiccup) resolves to null silently.
34
+ */
35
+ export async function checkForUpdate({ fetchImpl = fetch, timeoutMs = 2000 } = {}) {
36
+ const current = currentVersion();
37
+ if (!current) return null;
38
+ try {
39
+ const ac = new AbortController();
40
+ const t = setTimeout(() => ac.abort(), timeoutMs);
41
+ let res;
42
+ try {
43
+ res = await fetchImpl("https://registry.npmjs.org/aether-code/latest", { signal: ac.signal });
44
+ } finally {
45
+ clearTimeout(t);
46
+ }
47
+ if (!res || !res.ok) return null;
48
+ const latest = (await res.json())?.version;
49
+ if (latest && cmpVersion(latest, current) > 0) {
50
+ return (
51
+ c.yellow("update available: ") +
52
+ c.gray(current) + " -> " + c.bold(c.green(latest)) +
53
+ c.gray(" · run ") + c.cyan("npm i -g aether-code@latest")
54
+ );
55
+ }
56
+ } catch {
57
+ /* offline / aborted / parse error — stay silent */
58
+ }
59
+ return null;
60
+ }