@auroraflow/code 0.0.20 → 0.0.22

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/README.md CHANGED
@@ -40,6 +40,7 @@ aurora install all
40
40
  aurora repair codex
41
41
  aurora repair claude
42
42
  aurora doctor
43
+ aurora doctor --json
43
44
  ```
44
45
 
45
46
  ## Commands
@@ -55,6 +56,7 @@ node bin/aurora.js install codex
55
56
  node bin/aurora.js install claude
56
57
  node bin/aurora.js repair codex
57
58
  node bin/aurora.js doctor
59
+ node bin/aurora.js doctor --json
58
60
  node bin/aurora.js install-clients
59
61
  node bin/aurora.js update-clients
60
62
  node bin/aurora.js status
@@ -66,6 +68,8 @@ If the selected official client is missing, Aurora prompts to install that
66
68
  specific client before launch.
67
69
  If the package exists but its command is not usable, Aurora reports a broken
68
70
  client state and prompts to repair instead of calling it "not installed".
71
+ `aurora doctor` prints a human-readable health summary, including npm, disk
72
+ space on Windows, and repair commands for missing or broken clients.
69
73
 
70
74
  The launcher stores shared local state under `~/.aurora` and starts a local
71
75
  sidecar at `127.0.0.1:17878`. Official clients talk to the sidecar; the sidecar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auroraflow/code",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "type": "module",
5
5
  "description": "Aurora launcher and sidecar for official Codex and Claude Code clients.",
6
6
  "repository": {
@@ -1,5 +1,5 @@
1
1
  import { ensureLayout, readAccount, readState, writeAccount, writeState } from "../../state/src/index.js";
2
- import { installOfficialClient, officialClientStatus, officialClientStatuses, repairOfficialClient, runClient, updateOfficialClients } from "../../clients/src/index.js";
2
+ import { installOfficialClient, officialClientStatus, officialClientStatuses, officialClientsDoctor, repairOfficialClient, runClient, updateOfficialClients } from "../../clients/src/index.js";
3
3
  import { ensureSidecarRunning, installSidecarService, sidecarServiceStatus, uninstallSidecarService } from "../../service/src/index.js";
4
4
  import { chooseClient, promptChoice, promptFields } from "../../../lib/prompt.js";
5
5
 
@@ -29,7 +29,7 @@ export async function runAuroraCLI(argv = process.argv.slice(2)) {
29
29
  }, null, 2));
30
30
  return;
31
31
  case "doctor":
32
- await doctorCommand();
32
+ await doctorCommand(rest);
33
33
  return;
34
34
  case "install":
35
35
  await installCommand(rest);
@@ -120,18 +120,35 @@ async function repairCommand(args) {
120
120
  }
121
121
  }
122
122
 
123
- async function doctorCommand() {
124
- const statuses = officialClientStatuses();
125
- console.log(JSON.stringify({ officialClients: statuses }, null, 2));
126
- for (const status of statuses) {
123
+ async function doctorCommand(args = []) {
124
+ const report = officialClientsDoctor();
125
+ if (args.includes("--json")) {
126
+ console.log(JSON.stringify(report, null, 2));
127
+ return;
128
+ }
129
+ console.log("Aurora doctor\n");
130
+ console.log(`Platform: ${report.platform}`);
131
+ console.log(`npm: ${report.npm.ok ? `ok (${report.npm.version})` : `problem (${report.npm.error || "unknown"})`}`);
132
+ if (report.disk.checked) {
133
+ console.log(`Disk ${report.disk.drive}: ${report.disk.availableMB ?? "unknown"} MB free`);
134
+ }
135
+ console.log("");
136
+ for (const status of report.officialClients) {
137
+ console.log(`${status.name}: ${status.state}${status.version ? ` (${status.version})` : ""}`);
138
+ if (status.bin) console.log(` command: ${status.bin}`);
139
+ if (status.packagePath) console.log(` package: ${status.packagePath}`);
140
+ if (status.problem) console.log(` problem: ${status.problem}`);
141
+ if (status.state === "missing") console.log(` fix: aurora install ${status.id}`);
127
142
  if (status.state === "broken") {
128
- console.error(`\n${status.name} exists but is not usable (${status.problem}).`);
129
- console.error(`Repair: aurora repair ${status.id}`);
143
+ console.log(` fix: aurora repair ${status.id}`);
130
144
  if (process.platform === "win32") {
131
- console.error(`If repair reports EBUSY, close ${status.name} and run: taskkill /F /IM ${status.id === "claude" ? "claude.exe" : "codex.exe"}`);
145
+ console.log(` if locked: taskkill /F /IM ${status.id === "claude" ? "claude.exe" : "codex.exe"}`);
132
146
  }
133
147
  }
134
148
  }
149
+ if (report.disk.checked && report.disk.availableBytes != null && report.disk.availableBytes < 512 * 1024 * 1024) {
150
+ console.log("\nWarning: low disk space can break npm install/repair. Free space and run: npm cache clean --force");
151
+ }
135
152
  }
136
153
 
137
154
  export async function runAuroraClaude(argv = process.argv.slice(2)) {
@@ -258,6 +275,7 @@ function usage(exitCode) {
258
275
  aurora repair codex
259
276
  aurora repair claude
260
277
  aurora doctor
278
+ aurora doctor --json
261
279
  aurora install-clients
262
280
  aurora update-clients
263
281
  aurora status
@@ -5,6 +5,7 @@ import { delimiter, dirname, join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { CLAUDE_HOME, CODEX_HOME, readState } from "../../state/src/index.js";
7
7
  import { ensureSidecarRunning } from "../../service/src/index.js";
8
+ import { codexModelRuntimeLimits } from "../../protocol/src/index.js";
8
9
 
9
10
  const here = dirname(fileURLToPath(import.meta.url));
10
11
  const packageRoot = resolve(here, "..", "..", "..");
@@ -97,12 +98,18 @@ export async function installOfficialClients() {
97
98
  export async function installOfficialClient(client, options = {}) {
98
99
  const spec = OFFICIAL_CLIENTS[client];
99
100
  if (!spec) throw new Error(`unknown official client: ${client}`);
101
+ const preflight = officialClientPreflight(client);
102
+ if (!preflight.ok) throw new Error(formatOfficialClientPreflightError(spec, preflight));
100
103
  const npm = npmCommandSpec();
101
104
  const args = ["install", "-g"];
102
105
  if (options.repair) args.push("--force");
103
106
  args.push(`${spec.packageName}@latest`);
104
107
  console.error(`[aurora] ${options.repair ? "repairing" : "installing"} ${spec.name}: ${npm.command} ${[...npm.args, ...args].join(" ")}`);
105
- await spawnAndWait(npm.command, [...npm.args, ...args], { ...process.env });
108
+ try {
109
+ await spawnAndWait(npm.command, [...npm.args, ...args], { ...process.env });
110
+ } catch (error) {
111
+ throw new Error(formatOfficialClientInstallError(spec, client, error));
112
+ }
106
113
  }
107
114
 
108
115
  export async function repairOfficialClient(client) {
@@ -117,6 +124,16 @@ export function officialClientStatus(client) {
117
124
  return officialClientStatuses().find(status => status.id === client) ?? null;
118
125
  }
119
126
 
127
+ export function officialClientsDoctor() {
128
+ return {
129
+ platform: process.platform,
130
+ npm: npmDoctor(),
131
+ disk: diskDoctor(),
132
+ processHints: processHints(),
133
+ officialClients: officialClientStatuses()
134
+ };
135
+ }
136
+
120
137
  function officialClientBin(client) {
121
138
  const spec = OFFICIAL_CLIENTS[client];
122
139
  if (!spec) throw new Error(`unknown official client: ${client}`);
@@ -179,6 +196,113 @@ function officialClientProblem(state, bin, packagePath) {
179
196
  return "unknown";
180
197
  }
181
198
 
199
+ function officialClientPreflight(client) {
200
+ const disk = diskDoctor();
201
+ if (disk.availableBytes != null && disk.availableBytes < 512 * 1024 * 1024) {
202
+ return { ok: false, reason: "low_disk_space", disk };
203
+ }
204
+ const processes = processHints();
205
+ const locked = processes.find(item => item.client === client && item.running);
206
+ if (locked) return { ok: false, reason: "client_process_running", process: locked };
207
+ return { ok: true, disk, processes };
208
+ }
209
+
210
+ function npmDoctor() {
211
+ try {
212
+ const npm = npmCommandSpec();
213
+ const result = spawnSyncCompat(npm.command, [...npm.args, "--version"]);
214
+ return {
215
+ command: npm.command,
216
+ args: npm.args,
217
+ ok: result.status === 0,
218
+ version: String(result.stdout || result.stderr || "").trim() || null
219
+ };
220
+ } catch (error) {
221
+ return { ok: false, error: error?.message || String(error) };
222
+ }
223
+ }
224
+
225
+ function diskDoctor() {
226
+ if (process.platform !== "win32") return { checked: false, reason: "not_windows" };
227
+ try {
228
+ const drive = (process.env.APPDATA || process.cwd()).slice(0, 2);
229
+ const result = spawnSync("powershell.exe", [
230
+ "-NoProfile",
231
+ "-Command",
232
+ `(Get-PSDrive -Name ${JSON.stringify(drive.charAt(0))}).Free`
233
+ ], { encoding: "utf8" });
234
+ const availableBytes = Number(String(result.stdout || "").trim());
235
+ return {
236
+ checked: true,
237
+ drive,
238
+ availableBytes: Number.isFinite(availableBytes) ? availableBytes : null,
239
+ availableMB: Number.isFinite(availableBytes) ? Math.floor(availableBytes / 1024 / 1024) : null
240
+ };
241
+ } catch (error) {
242
+ return { checked: false, error: error?.message || String(error) };
243
+ }
244
+ }
245
+
246
+ function processHints() {
247
+ if (process.platform !== "win32") return [];
248
+ return [
249
+ { client: "claude", image: "claude.exe", running: isWindowsProcessRunning("claude.exe") },
250
+ { client: "codex", image: "codex.exe", running: isWindowsProcessRunning("codex.exe") },
251
+ { client: "codex", image: "node.exe", running: false, note: "Codex may run through node.exe depending on npm package version." }
252
+ ];
253
+ }
254
+
255
+ function isWindowsProcessRunning(image) {
256
+ try {
257
+ const result = spawnSync("tasklist", ["/FI", `IMAGENAME eq ${image}`], { encoding: "utf8" });
258
+ return String(result.stdout || "").toLowerCase().includes(image.toLowerCase());
259
+ } catch {
260
+ return false;
261
+ }
262
+ }
263
+
264
+ function formatOfficialClientPreflightError(spec, preflight) {
265
+ if (preflight.reason === "low_disk_space") {
266
+ return `${spec.name} repair/install blocked: low disk space on ${preflight.disk.drive} (${preflight.disk.availableMB} MB free). Free at least 512 MB, run npm cache clean --force, then retry.`;
267
+ }
268
+ if (preflight.reason === "client_process_running") {
269
+ return `${spec.name} repair/install blocked: ${preflight.process.image} is running. Close ${spec.name} or run: taskkill /F /IM ${preflight.process.image}`;
270
+ }
271
+ return `${spec.name} repair/install preflight failed: ${preflight.reason}`;
272
+ }
273
+
274
+ function formatOfficialClientInstallError(spec, client, error) {
275
+ const message = error?.message || String(error);
276
+ const suggestions = officialClientFailureSuggestions(client, message);
277
+ return [`${spec.name} install/repair failed: ${message}`, ...suggestions].join("\n");
278
+ }
279
+
280
+ function officialClientFailureSuggestions(client, message) {
281
+ const text = String(message);
282
+ const image = client === "claude" ? "claude.exe" : "codex.exe";
283
+ if (/ENOSPC|no space left/i.test(text)) {
284
+ return [
285
+ "Detected cause: disk is full.",
286
+ "Fix: free disk space, then run: npm cache clean --force",
287
+ `Retry: aurora repair ${client}`
288
+ ];
289
+ }
290
+ if (/EBUSY|EPERM|resource busy|operation not permitted/i.test(text)) {
291
+ return [
292
+ "Detected cause: files are locked by a running process or antivirus.",
293
+ process.platform === "win32" ? `Fix: close the client, then run: taskkill /F /IM ${image}` : "Fix: close the running client process.",
294
+ `Retry: aurora repair ${client}`
295
+ ];
296
+ }
297
+ if (/ENOENT|not found/i.test(text)) {
298
+ return [
299
+ "Detected cause: npm or a required command was not found.",
300
+ "Fix: reinstall Node.js/npm or add npm to PATH, then retry."
301
+ ];
302
+ }
303
+ return [`Run diagnostics: aurora doctor`, `Retry after fixing the reported issue: aurora repair ${client}`];
304
+ }
305
+
182
306
  function readOfficialClientVersion(bin) {
183
307
  if (!bin) return null;
184
308
  try {
@@ -193,11 +317,13 @@ function readOfficialClientVersion(bin) {
193
317
  async function writeCodexRuntimeFiles({ sidecar, model }) {
194
318
  await mkdir(CODEX_HOME, { recursive: true });
195
319
  const modelCatalogPath = join(CODEX_HOME, "model_catalog.json");
196
- await writeCodexModelCatalog(sidecar, modelCatalogPath);
320
+ const limits = await writeCodexModelCatalog(sidecar, modelCatalogPath, model);
197
321
  const config = `model = ${tomlString(model)}
198
322
  model_provider = "aurora"
199
323
  model_catalog_json = ${tomlString(modelCatalogPath)}
200
324
  suppress_unstable_features_warning = true
325
+ model_context_window = ${limits.contextWindow}
326
+ model_auto_compact_token_limit = ${limits.autoCompactTokenLimit}
201
327
 
202
328
  [model_providers.aurora]
203
329
  name = "Aurora"
@@ -218,7 +344,7 @@ stream_idle_timeout_ms = 300000
218
344
  await writeFile(join(CODEX_HOME, "auth.json"), `${JSON.stringify(auth, null, 2)}\n`, { mode: 0o600 });
219
345
  }
220
346
 
221
- async function writeCodexModelCatalog(sidecar, modelCatalogPath) {
347
+ async function writeCodexModelCatalog(sidecar, modelCatalogPath, selectedModelAlias) {
222
348
  const clientVersion = "0.137.0";
223
349
  let response;
224
350
  try {
@@ -239,6 +365,11 @@ async function writeCodexModelCatalog(sidecar, modelCatalogPath) {
239
365
  if (!Array.isArray(payload.models) || payload.models.length === 0) {
240
366
  throw new Error("Aurora Codex model catalog is empty");
241
367
  }
368
+ const selectedModel = payload.models.find(item => item.slug === selectedModelAlias);
369
+ if (!selectedModel) {
370
+ throw new Error(`Aurora Codex model catalog does not include selected model: ${selectedModelAlias}`);
371
+ }
372
+ const limits = codexModelRuntimeLimits(selectedModel);
242
373
  const catalog = { models: payload.models };
243
374
  const cache = {
244
375
  fetched_at: new Date().toISOString(),
@@ -247,6 +378,7 @@ async function writeCodexModelCatalog(sidecar, modelCatalogPath) {
247
378
  };
248
379
  await writeFile(modelCatalogPath, `${JSON.stringify(catalog, null, 2)}\n`, { mode: 0o600 });
249
380
  await writeFile(join(CODEX_HOME, "models_cache.json"), `${JSON.stringify(cache, null, 2)}\n`, { mode: 0o600 });
381
+ return limits;
250
382
  }
251
383
 
252
384
  function selectedModelAlias(state) {
@@ -52,7 +52,7 @@ export function toCodexModelInfo(item, priority = 0) {
52
52
  const supportsTools = Boolean(item.supports_tools);
53
53
  const supportsImages = Boolean(item.supports_images);
54
54
  const supportsWebSearch = Boolean(item.supports_web_search);
55
- const contextWindow = positiveNumber(item.context_window ?? item.context_window_tokens, 256000);
55
+ const contextWindow = positiveNumber(item.context_window ?? item.context_window_tokens, null);
56
56
  const maxContextWindow = positiveNumber(item.max_context_window, contextWindow);
57
57
  const reasoningLevels = Array.isArray(item.supported_reasoning_levels)
58
58
  ? item.supported_reasoning_levels
@@ -114,6 +114,21 @@ export function toCodexModelInfo(item, priority = 0) {
114
114
  };
115
115
  }
116
116
 
117
+ export function codexModelRuntimeLimits(model) {
118
+ const contextWindow = positiveNumber(model?.context_window ?? model?.context_window_tokens, 0);
119
+ if (!contextWindow) {
120
+ throw new Error(`Codex model metadata is missing context_window for ${model?.slug ?? model?.alias ?? model?.id ?? "unknown model"}`);
121
+ }
122
+ const publishedCompactLimit = positiveNumber(model?.auto_compact_token_limit, 0);
123
+ if (!publishedCompactLimit) {
124
+ throw new Error(`Codex model metadata is missing auto_compact_token_limit for ${model?.slug ?? model?.alias ?? model?.id ?? "unknown model"}`);
125
+ }
126
+ return {
127
+ contextWindow,
128
+ autoCompactTokenLimit: publishedCompactLimit
129
+ };
130
+ }
131
+
117
132
  function positiveNumber(value, defaultValue) {
118
133
  const parsed = Number(value);
119
134
  return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultValue;
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
 
4
- import { rewriteRuntimeModel, toCodexModelInfo } from "./index.js";
4
+ import { codexModelRuntimeLimits, rewriteRuntimeModel, toCodexModelInfo } from "./index.js";
5
5
 
6
6
  const CODEX_MODEL_INFO_FIELDS = [
7
7
  "additional_speed_tiers",
@@ -178,3 +178,28 @@ test("keeps hosted web search for supported selected models", () => {
178
178
  assert.equal(rewritten.model, "cx/gpt-5.5");
179
179
  assert.deepEqual(rewritten.tools, [{ type: "web_search" }]);
180
180
  });
181
+
182
+ test("uses published Codex compaction limit", () => {
183
+ const limits = codexModelRuntimeLimits({
184
+ slug: "cx/gpt-5.5",
185
+ context_window: 128000,
186
+ auto_compact_token_limit: 96000
187
+ });
188
+
189
+ assert.equal(limits.contextWindow, 128000);
190
+ assert.equal(limits.autoCompactTokenLimit, 96000);
191
+ });
192
+
193
+ test("rejects Codex model metadata without a context window", () => {
194
+ assert.throws(
195
+ () => codexModelRuntimeLimits({ slug: "cx/missing" }),
196
+ /missing context_window/
197
+ );
198
+ });
199
+
200
+ test("rejects Codex model metadata without a published compaction limit", () => {
201
+ assert.throws(
202
+ () => codexModelRuntimeLimits({ slug: "cx/missing", context_window: 128000 }),
203
+ /missing auto_compact_token_limit/
204
+ );
205
+ });