aether-code 0.30.0 → 0.32.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.
@@ -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.30.0";
31
+ const VERSION = "0.32.0";
32
32
 
33
33
  /**
34
34
  * Try to start MCP servers from ~/.aether/mcp.json. Returns a started
@@ -90,6 +90,8 @@ ${c.bold("FLAGS")}
90
90
  Default is auto-approve (skip-permissions).
91
91
  --sandbox Restrict file access to --cwd. Default is full access.
92
92
  --cwd <path> Working directory for the agent (default: current dir).
93
+ --model <name> core (default) | ultra (Opus). Premium models need
94
+ purchased credits, else they fall back to Core.
93
95
  --max-turns <n> Maximum turns before stopping (default: 25).
94
96
  --help, -h Show this help.
95
97
  --version, -v Print version.
@@ -117,6 +119,7 @@ function parseArgs(argv) {
117
119
  else if (a === "--help" || a === "-h") { args.flags.help = true; }
118
120
  else if (a === "--version" || a === "-v") { args.flags.version = true; }
119
121
  else if (a === "--cwd") { args.flags.cwd = argv[++i]; }
122
+ else if (a === "--model") { args.flags.model = argv[++i]; }
120
123
  else if (a === "--max-turns") { args.flags.maxTurns = parseInt(argv[++i], 10); }
121
124
  else if (a.startsWith("--")) {
122
125
  const eq = a.indexOf("=");
@@ -190,6 +193,12 @@ async function main() {
190
193
  const unsafePaths = !args.flags.sandbox;
191
194
  const maxTurns = Number.isInteger(args.flags.maxTurns) ? args.flags.maxTurns : 25;
192
195
 
196
+ // --model: core (default) or ultra (Opus). Premium models are gated
197
+ // server-side — without purchased credits the server uses Core regardless.
198
+ const MODEL_IDS = { core: null, gemma: null, ultra: "claude-opus-4-6", opus: "claude-opus-4-6" };
199
+ const modelKey = args.flags.model ? String(args.flags.model).toLowerCase() : null;
200
+ const model = modelKey && modelKey in MODEL_IDS ? MODEL_IDS[modelKey] : (args.flags.model || null);
201
+
193
202
  // Subcommand routing — these shadow the "task as positional arg" mode
194
203
  const sub = args._[0]?.toLowerCase();
195
204
  if (sub === "login" || sub === "auth") {
@@ -224,7 +233,7 @@ async function main() {
224
233
  console.log(c.gray(`Note: launched from a system folder (${redirectedFrom}).`));
225
234
  console.log(c.gray(`Your files will be saved to ${c.cyan(cwd)} so you can find them. Use ${c.cyan("--cwd <path>")} or ${c.cyan("/cwd")} to change.`));
226
235
  }
227
- await runRepl({ cwd, autoYes, unsafePaths, maxTurns, mcpManager });
236
+ await runRepl({ cwd, autoYes, unsafePaths, maxTurns, model, mcpManager });
228
237
  return;
229
238
  }
230
239
 
@@ -249,6 +258,7 @@ async function main() {
249
258
  autoYes,
250
259
  unsafePaths,
251
260
  maxTurns,
261
+ model,
252
262
  mcpManager,
253
263
  });
254
264
  if (mcpManager) await mcpManager.shutdown().catch(() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aether-code",
3
- "version": "0.30.0",
3
+ "version": "0.32.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
@@ -51,6 +51,7 @@ export async function runAgent({
51
51
  autoYes = false,
52
52
  unsafePaths = false,
53
53
  maxTurns = DEFAULT_MAX_TURNS,
54
+ model = null, // null = server default (gemma). Premium models gated server-side.
54
55
  onTokens = () => {},
55
56
  // Optional MCPManager. When provided, its tools are merged into the agent's
56
57
  // toolset and tool calls prefixed `mcp__` are routed to it instead of the
@@ -135,6 +136,7 @@ export async function runAgent({
135
136
  res = await agentTurnStream({
136
137
  messages: turnMessages,
137
138
  tools,
139
+ model,
138
140
  onDelta: (text) => {
139
141
  // Buffered strip of leaked model channel/control tokens (which can
140
142
  // be split across stream chunks) before display.
package/src/api.js CHANGED
@@ -122,6 +122,7 @@ export async function agentTurnStream({
122
122
  tools,
123
123
  maxTokens,
124
124
  temperature,
125
+ model,
125
126
  onDelta,
126
127
  onToolCallDelta,
127
128
  onFinish,
@@ -150,6 +151,7 @@ export async function agentTurnStream({
150
151
  tools,
151
152
  max_tokens: maxTokens,
152
153
  temperature,
154
+ ...(model ? { model } : {}),
153
155
  }),
154
156
  }, { onRetry: defaultOnRetry });
155
157
  } catch (e) {
@@ -274,7 +276,7 @@ export async function agentTurnStream({
274
276
  * problematic (corporate proxies, weird client setups). The CLI defaults to
275
277
  * streaming.
276
278
  */
277
- export async function agentTurn({ messages, tools, maxTokens, temperature }) {
279
+ export async function agentTurn({ messages, tools, maxTokens, temperature, model }) {
278
280
  const { apiKey, baseUrl } = getConfig();
279
281
  if (!apiKey) {
280
282
  throw new AetherError(
@@ -298,6 +300,7 @@ export async function agentTurn({ messages, tools, maxTokens, temperature }) {
298
300
  tools,
299
301
  max_tokens: maxTokens,
300
302
  temperature,
303
+ ...(model ? { model } : {}),
301
304
  }),
302
305
  }, { onRetry: defaultOnRetry });
303
306
  } catch (e) {
package/src/repl.js CHANGED
@@ -16,9 +16,19 @@ import { c, errorLine, boxLines, sideBySide } from "./render.js";
16
16
  import { checkForUpdate } from "./update-check.js";
17
17
  import { promptBox, EXIT_SIGNAL } from "./box-input.js";
18
18
 
19
- const VERSION = "0.30.0";
19
+ const VERSION = "0.32.0";
20
20
  const MODEL_NAME = "Aether Core";
21
21
 
22
+ // /model aliases → { id, name }. id=null = server default (gemma / Aether Core).
23
+ // Premium models are gated SERVER-SIDE: if you haven't purchased credits, the
24
+ // server transparently uses Core instead — selecting one here can't bypass that.
25
+ const MODEL_ALIASES = {
26
+ core: { id: null, name: "Aether Core" },
27
+ gemma: { id: null, name: "Aether Core" },
28
+ ultra: { id: "claude-opus-4-6", name: "Aether Ultra" },
29
+ opus: { id: "claude-opus-4-6", name: "Aether Ultra" },
30
+ };
31
+
22
32
  const SHORTCUTS = `
23
33
  ${c.cyan("/help")} Show this help
24
34
  ${c.cyan("/exit")} Exit (or Ctrl+C twice)
@@ -27,18 +37,21 @@ const SHORTCUTS = `
27
37
  ${c.cyan("/cwd")} ${c.gray("[path]")} Show or change working directory
28
38
  ${c.cyan("/yes")} Toggle auto-approve mode (skip y/N prompts)
29
39
  ${c.cyan("/turns")} ${c.gray("<n>")} Set max turns per prompt (default 25)
30
- ${c.cyan("/model")} Show current model
40
+ ${c.cyan("/model")} ${c.gray("[core|ultra]")} Show or switch model (ultra=Opus — premium)
31
41
 
32
42
  ${c.gray("Anything else is sent to the agent as your next message.")}
33
43
  ${c.gray("Conversation history is kept across messages until you /clear.")}
34
44
  `;
35
45
 
36
- export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafePaths: initialUnsafePaths, maxTurns: initialMaxTurns, mcpManager = null }) {
46
+ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafePaths: initialUnsafePaths, maxTurns: initialMaxTurns, model: initialModel = null, mcpManager = null }) {
47
+ const initialAlias = Object.values(MODEL_ALIASES).find((a) => a.id === initialModel);
37
48
  const state = {
38
49
  cwd: initialCwd,
39
50
  autoYes: !!initialAutoYes,
40
51
  unsafePaths: !!initialUnsafePaths,
41
52
  maxTurns: initialMaxTurns ?? 25,
53
+ model: initialModel ?? null,
54
+ modelName: initialAlias?.name ?? MODEL_NAME,
42
55
  messages: [], // accumulates across turns
43
56
  balance: null,
44
57
  sessionCredits: 0,
@@ -114,6 +127,7 @@ export async function runRepl({ cwd: initialCwd, autoYes: initialAutoYes, unsafe
114
127
  cwd: state.cwd,
115
128
  autoYes: state.autoYes,
116
129
  unsafePaths: state.unsafePaths,
130
+ model: state.model,
117
131
  maxTurns: state.maxTurns,
118
132
  mcpManager,
119
133
  });
@@ -199,9 +213,24 @@ async function handleSlash(line, state) {
199
213
  }
200
214
  break;
201
215
  }
202
- case "model":
203
- console.log(c.gray(`model: ${MODEL_NAME} · 1M context · uncensored`));
216
+ case "model": {
217
+ const key = (arg || "").trim().toLowerCase();
218
+ if (!key) {
219
+ console.log(c.gray(`model: ${state.modelName}`));
220
+ console.log(c.gray("switch with /model core | ultra (ultra=Opus — premium, need credits)"));
221
+ } else if (MODEL_ALIASES[key]) {
222
+ state.model = MODEL_ALIASES[key].id;
223
+ state.modelName = MODEL_ALIASES[key].name;
224
+ const premium = state.model !== null;
225
+ console.log(
226
+ c.gray(`model → ${c.cyan(state.modelName)}`) +
227
+ (premium ? c.gray(" (premium — needs purchased credits, else falls back to Core)") : ""),
228
+ );
229
+ } else {
230
+ console.log(errorLine(`Unknown model "${key}". Try: core, ultra`));
231
+ }
204
232
  break;
233
+ }
205
234
  default:
206
235
  console.log(errorLine(`Unknown command: /${cmd}. Type ${c.cyan("/help")} for shortcuts.`));
207
236
  }
@@ -226,7 +255,7 @@ function printBanner(state) {
226
255
  const credits = state.balance != null ? `${state.balance.toLocaleString()} credits` : "";
227
256
  const left = boxLines(
228
257
  [
229
- c.bold(c.magenta("aether")) + c.gray(" · ") + c.bold(MODEL_NAME) + c.gray(" v" + VERSION),
258
+ c.bold(c.magenta("aether")) + c.gray(" · ") + c.bold(state.modelName) + c.gray(" v" + VERSION),
230
259
  c.gray("uncensored AI coding agent"),
231
260
  c.gray((credits ? credits + " · " : "") + mode),
232
261
  c.gray(shortenPath(state.cwd, 52)),