@auroraflow/code 0.0.19 → 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
@@ -37,6 +37,10 @@ aurora update-clients
37
37
  aurora install codex
38
38
  aurora install claude
39
39
  aurora install all
40
+ aurora repair codex
41
+ aurora repair claude
42
+ aurora doctor
43
+ aurora doctor --json
40
44
  ```
41
45
 
42
46
  ## Commands
@@ -50,6 +54,9 @@ node bin/aurora.js claude
50
54
  node bin/aurora.js codex
51
55
  node bin/aurora.js install codex
52
56
  node bin/aurora.js install claude
57
+ node bin/aurora.js repair codex
58
+ node bin/aurora.js doctor
59
+ node bin/aurora.js doctor --json
53
60
  node bin/aurora.js install-clients
54
61
  node bin/aurora.js update-clients
55
62
  node bin/aurora.js status
@@ -59,6 +66,10 @@ Running `aurora` without a subcommand opens an interactive client selector. Use
59
66
  Up/Down (or `j`/`k`) and Enter to launch Claude or Codex through Aurora.
60
67
  If the selected official client is missing, Aurora prompts to install that
61
68
  specific client before launch.
69
+ If the package exists but its command is not usable, Aurora reports a broken
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.
62
73
 
63
74
  The launcher stores shared local state under `~/.aurora` and starts a local
64
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.19",
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, 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
 
@@ -28,9 +28,15 @@ export async function runAuroraCLI(argv = process.argv.slice(2)) {
28
28
  officialClients: officialClientStatuses()
29
29
  }, null, 2));
30
30
  return;
31
+ case "doctor":
32
+ await doctorCommand(rest);
33
+ return;
31
34
  case "install":
32
35
  await installCommand(rest);
33
36
  return;
37
+ case "repair":
38
+ await repairCommand(rest);
39
+ return;
34
40
  case "install-clients":
35
41
  case "install-official-clients":
36
42
  case "update-clients":
@@ -93,6 +99,58 @@ async function installCommand(args) {
93
99
  }
94
100
  }
95
101
 
102
+ async function repairCommand(args) {
103
+ const target = args[0] ?? "";
104
+ switch (target) {
105
+ case "codex":
106
+ case "claude":
107
+ await repairOfficialClient(target);
108
+ console.log(JSON.stringify({ officialClients: officialClientStatuses() }, null, 2));
109
+ return;
110
+ case "all":
111
+ case "clients":
112
+ case "official-clients":
113
+ await repairOfficialClient("claude");
114
+ await repairOfficialClient("codex");
115
+ console.log(JSON.stringify({ officialClients: officialClientStatuses() }, null, 2));
116
+ return;
117
+ default:
118
+ console.error("Usage: aurora repair [codex|claude|all]");
119
+ process.exit(1);
120
+ }
121
+ }
122
+
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}`);
142
+ if (status.state === "broken") {
143
+ console.log(` fix: aurora repair ${status.id}`);
144
+ if (process.platform === "win32") {
145
+ console.log(` if locked: taskkill /F /IM ${status.id === "claude" ? "claude.exe" : "codex.exe"}`);
146
+ }
147
+ }
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
+ }
152
+ }
153
+
96
154
  export async function runAuroraClaude(argv = process.argv.slice(2)) {
97
155
  await ensureLayout();
98
156
  await runClient("claude", argv);
@@ -116,12 +174,22 @@ async function startInteractive(args) {
116
174
  async function ensureOfficialClientForInteractive(client) {
117
175
  const status = officialClientStatus(client);
118
176
  if (status?.installed) return;
119
- const action = await promptChoice(`${status?.name ?? client} is not installed`, [
120
- { label: `Install ${status?.name ?? client} now`, value: "install" },
177
+ const broken = status?.state === "broken";
178
+ const action = await promptChoice(`${status?.name ?? client} ${broken ? "is installed but not usable" : "is not installed"}`, [
179
+ { label: `${broken ? "Repair" : "Install"} ${status?.name ?? client} now`, value: broken ? "repair" : "install" },
180
+ { label: "Show fix command", value: "manual" },
121
181
  { label: "Exit", value: "exit" }
122
182
  ]);
183
+ if (action === "manual") {
184
+ const command = broken ? `aurora repair ${client}` : `aurora install ${client}`;
185
+ throw new Error(`${status?.name ?? client} is required. Run: ${command}`);
186
+ }
187
+ if (action === "repair") {
188
+ await repairOfficialClient(client);
189
+ return;
190
+ }
123
191
  if (action !== "install") {
124
- throw new Error(`${status?.name ?? client} is required. Run: aurora install ${client}`);
192
+ throw new Error(`${status?.name ?? client} is required. Run: aurora ${broken ? "repair" : "install"} ${client}`);
125
193
  }
126
194
  await installOfficialClient(client);
127
195
  }
@@ -204,6 +272,10 @@ function usage(exitCode) {
204
272
  aurora install codex
205
273
  aurora install claude
206
274
  aurora install all
275
+ aurora repair codex
276
+ aurora repair claude
277
+ aurora doctor
278
+ aurora doctor --json
207
279
  aurora install-clients
208
280
  aurora update-clients
209
281
  aurora status
@@ -67,14 +67,20 @@ export function officialClientStatuses() {
67
67
  return Object.entries(OFFICIAL_CLIENTS).map(([id, client]) => {
68
68
  const bin = resolveOfficialClientBin(client.binName);
69
69
  const packagePath = officialClientPackageJSONPath(client.packageName);
70
- const version = readOfficialClientVersion(bin) ?? (packagePath ? JSON.parse(readFileSync(packagePath, "utf8")).version : null);
70
+ const binVersion = readOfficialClientVersion(bin);
71
+ const packageVersion = packagePath ? JSON.parse(readFileSync(packagePath, "utf8")).version : null;
72
+ const state = bin && binVersion ? "installed" : packagePath ? "broken" : "missing";
71
73
  return {
72
74
  id,
73
75
  name: client.name,
74
76
  packageName: client.packageName,
75
77
  bin,
76
- installed: Boolean(bin),
77
- version
78
+ packagePath,
79
+ installed: state === "installed",
80
+ packagePresent: Boolean(packagePath),
81
+ state,
82
+ version: binVersion ?? packageVersion,
83
+ problem: officialClientProblem(state, bin, packagePath)
78
84
  };
79
85
  });
80
86
  }
@@ -88,13 +94,25 @@ export async function installOfficialClients() {
88
94
  await installOfficialClient("codex");
89
95
  }
90
96
 
91
- export async function installOfficialClient(client) {
97
+ export async function installOfficialClient(client, options = {}) {
92
98
  const spec = OFFICIAL_CLIENTS[client];
93
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));
94
102
  const npm = npmCommandSpec();
95
- const args = ["install", "-g", `${spec.packageName}@latest`];
96
- console.error(`[aurora] installing ${spec.name}: ${npm.command} ${[...npm.args, ...args].join(" ")}`);
97
- await spawnAndWait(npm.command, [...npm.args, ...args], { ...process.env });
103
+ const args = ["install", "-g"];
104
+ if (options.repair) args.push("--force");
105
+ args.push(`${spec.packageName}@latest`);
106
+ console.error(`[aurora] ${options.repair ? "repairing" : "installing"} ${spec.name}: ${npm.command} ${[...npm.args, ...args].join(" ")}`);
107
+ try {
108
+ await spawnAndWait(npm.command, [...npm.args, ...args], { ...process.env });
109
+ } catch (error) {
110
+ throw new Error(formatOfficialClientInstallError(spec, client, error));
111
+ }
112
+ }
113
+
114
+ export async function repairOfficialClient(client) {
115
+ await installOfficialClient(client, { repair: true });
98
116
  }
99
117
 
100
118
  export function isGlobalNpmLifecycle() {
@@ -105,14 +123,27 @@ export function officialClientStatus(client) {
105
123
  return officialClientStatuses().find(status => status.id === client) ?? null;
106
124
  }
107
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
+
108
136
  function officialClientBin(client) {
109
137
  const spec = OFFICIAL_CLIENTS[client];
110
138
  if (!spec) throw new Error(`unknown official client: ${client}`);
111
- const bin = resolveOfficialClientBin(spec.binName);
112
- if (!bin) {
139
+ const status = officialClientStatus(client);
140
+ if (!status?.installed) {
141
+ if (status?.state === "broken") {
142
+ throw new Error(`${spec.name} is installed but not usable. Run: aurora doctor, then aurora repair ${client}`);
143
+ }
113
144
  throw new Error(`${spec.name} is not installed. Run: aurora install ${client}`);
114
145
  }
115
- return bin;
146
+ return status.bin;
116
147
  }
117
148
 
118
149
  function resolveOfficialClientBin(binName) {
@@ -139,8 +170,136 @@ function findPathCommand(binName) {
139
170
 
140
171
  function officialClientPackageJSONPath(packageName) {
141
172
  const parts = packageName.startsWith("@") ? packageName.split("/") : [packageName];
142
- const packagePath = join(packageRoot, "node_modules", ...parts, "package.json");
143
- return existsSync(packagePath) ? packagePath : null;
173
+ for (const root of officialClientPackageRoots()) {
174
+ const packagePath = join(root, ...parts, "package.json");
175
+ if (existsSync(packagePath)) return packagePath;
176
+ }
177
+ return null;
178
+ }
179
+
180
+ function officialClientPackageRoots() {
181
+ const roots = [join(packageRoot, "node_modules")];
182
+ const npmBin = findPathCommand("npm");
183
+ if (npmBin) roots.push(join(dirname(npmBin), "node_modules"));
184
+ if (process.platform === "win32" && process.env.APPDATA) {
185
+ roots.push(join(process.env.APPDATA, "npm", "node_modules"));
186
+ }
187
+ return [...new Set(roots)];
188
+ }
189
+
190
+ function officialClientProblem(state, bin, packagePath) {
191
+ if (state === "installed") return null;
192
+ if (state === "missing") return "package_not_found";
193
+ if (!bin) return "package_present_but_command_not_found";
194
+ if (packagePath) return "command_found_but_version_check_failed";
195
+ return "unknown";
196
+ }
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}`];
144
303
  }
145
304
 
146
305
  function readOfficialClientVersion(bin) {