@auroraflow/code 0.0.20 → 0.0.21

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.21",
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
@@ -97,12 +97,18 @@ export async function installOfficialClients() {
97
97
  export async function installOfficialClient(client, options = {}) {
98
98
  const spec = OFFICIAL_CLIENTS[client];
99
99
  if (!spec) throw new Error(`unknown official client: ${client}`);
100
+ const preflight = officialClientPreflight(client);
101
+ if (!preflight.ok) throw new Error(formatOfficialClientPreflightError(spec, preflight));
100
102
  const npm = npmCommandSpec();
101
103
  const args = ["install", "-g"];
102
104
  if (options.repair) args.push("--force");
103
105
  args.push(`${spec.packageName}@latest`);
104
106
  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 });
107
+ try {
108
+ await spawnAndWait(npm.command, [...npm.args, ...args], { ...process.env });
109
+ } catch (error) {
110
+ throw new Error(formatOfficialClientInstallError(spec, client, error));
111
+ }
106
112
  }
107
113
 
108
114
  export async function repairOfficialClient(client) {
@@ -117,6 +123,16 @@ export function officialClientStatus(client) {
117
123
  return officialClientStatuses().find(status => status.id === client) ?? null;
118
124
  }
119
125
 
126
+ export function officialClientsDoctor() {
127
+ return {
128
+ platform: process.platform,
129
+ npm: npmDoctor(),
130
+ disk: diskDoctor(),
131
+ processHints: processHints(),
132
+ officialClients: officialClientStatuses()
133
+ };
134
+ }
135
+
120
136
  function officialClientBin(client) {
121
137
  const spec = OFFICIAL_CLIENTS[client];
122
138
  if (!spec) throw new Error(`unknown official client: ${client}`);
@@ -179,6 +195,113 @@ function officialClientProblem(state, bin, packagePath) {
179
195
  return "unknown";
180
196
  }
181
197
 
198
+ function officialClientPreflight(client) {
199
+ const disk = diskDoctor();
200
+ if (disk.availableBytes != null && disk.availableBytes < 512 * 1024 * 1024) {
201
+ return { ok: false, reason: "low_disk_space", disk };
202
+ }
203
+ const processes = processHints();
204
+ const locked = processes.find(item => item.client === client && item.running);
205
+ if (locked) return { ok: false, reason: "client_process_running", process: locked };
206
+ return { ok: true, disk, processes };
207
+ }
208
+
209
+ function npmDoctor() {
210
+ try {
211
+ const npm = npmCommandSpec();
212
+ const result = spawnSyncCompat(npm.command, [...npm.args, "--version"]);
213
+ return {
214
+ command: npm.command,
215
+ args: npm.args,
216
+ ok: result.status === 0,
217
+ version: String(result.stdout || result.stderr || "").trim() || null
218
+ };
219
+ } catch (error) {
220
+ return { ok: false, error: error?.message || String(error) };
221
+ }
222
+ }
223
+
224
+ function diskDoctor() {
225
+ if (process.platform !== "win32") return { checked: false, reason: "not_windows" };
226
+ try {
227
+ const drive = (process.env.APPDATA || process.cwd()).slice(0, 2);
228
+ const result = spawnSync("powershell.exe", [
229
+ "-NoProfile",
230
+ "-Command",
231
+ `(Get-PSDrive -Name ${JSON.stringify(drive.charAt(0))}).Free`
232
+ ], { encoding: "utf8" });
233
+ const availableBytes = Number(String(result.stdout || "").trim());
234
+ return {
235
+ checked: true,
236
+ drive,
237
+ availableBytes: Number.isFinite(availableBytes) ? availableBytes : null,
238
+ availableMB: Number.isFinite(availableBytes) ? Math.floor(availableBytes / 1024 / 1024) : null
239
+ };
240
+ } catch (error) {
241
+ return { checked: false, error: error?.message || String(error) };
242
+ }
243
+ }
244
+
245
+ function processHints() {
246
+ if (process.platform !== "win32") return [];
247
+ return [
248
+ { client: "claude", image: "claude.exe", running: isWindowsProcessRunning("claude.exe") },
249
+ { client: "codex", image: "codex.exe", running: isWindowsProcessRunning("codex.exe") },
250
+ { client: "codex", image: "node.exe", running: false, note: "Codex may run through node.exe depending on npm package version." }
251
+ ];
252
+ }
253
+
254
+ function isWindowsProcessRunning(image) {
255
+ try {
256
+ const result = spawnSync("tasklist", ["/FI", `IMAGENAME eq ${image}`], { encoding: "utf8" });
257
+ return String(result.stdout || "").toLowerCase().includes(image.toLowerCase());
258
+ } catch {
259
+ return false;
260
+ }
261
+ }
262
+
263
+ function formatOfficialClientPreflightError(spec, preflight) {
264
+ if (preflight.reason === "low_disk_space") {
265
+ 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.`;
266
+ }
267
+ if (preflight.reason === "client_process_running") {
268
+ return `${spec.name} repair/install blocked: ${preflight.process.image} is running. Close ${spec.name} or run: taskkill /F /IM ${preflight.process.image}`;
269
+ }
270
+ return `${spec.name} repair/install preflight failed: ${preflight.reason}`;
271
+ }
272
+
273
+ function formatOfficialClientInstallError(spec, client, error) {
274
+ const message = error?.message || String(error);
275
+ const suggestions = officialClientFailureSuggestions(client, message);
276
+ return [`${spec.name} install/repair failed: ${message}`, ...suggestions].join("\n");
277
+ }
278
+
279
+ function officialClientFailureSuggestions(client, message) {
280
+ const text = String(message);
281
+ const image = client === "claude" ? "claude.exe" : "codex.exe";
282
+ if (/ENOSPC|no space left/i.test(text)) {
283
+ return [
284
+ "Detected cause: disk is full.",
285
+ "Fix: free disk space, then run: npm cache clean --force",
286
+ `Retry: aurora repair ${client}`
287
+ ];
288
+ }
289
+ if (/EBUSY|EPERM|resource busy|operation not permitted/i.test(text)) {
290
+ return [
291
+ "Detected cause: files are locked by a running process or antivirus.",
292
+ process.platform === "win32" ? `Fix: close the client, then run: taskkill /F /IM ${image}` : "Fix: close the running client process.",
293
+ `Retry: aurora repair ${client}`
294
+ ];
295
+ }
296
+ if (/ENOENT|not found/i.test(text)) {
297
+ return [
298
+ "Detected cause: npm or a required command was not found.",
299
+ "Fix: reinstall Node.js/npm or add npm to PATH, then retry."
300
+ ];
301
+ }
302
+ return [`Run diagnostics: aurora doctor`, `Retry after fixing the reported issue: aurora repair ${client}`];
303
+ }
304
+
182
305
  function readOfficialClientVersion(bin) {
183
306
  if (!bin) return null;
184
307
  try {