openmagic 0.41.2 → 0.42.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.
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { resolve as resolve3, join as join6 } from "path";
8
8
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
9
9
  import { spawn as spawn5, execSync as execSync2 } from "child_process";
10
10
  import http2 from "http";
11
- import { createInterface } from "readline";
11
+ import { createInterface, clearLine, cursorTo } from "readline";
12
12
 
13
13
  // src/proxy.ts
14
14
  import http from "http";
@@ -2868,13 +2868,108 @@ process.emitWarning = function(warning, ...args) {
2868
2868
  if (typeof warning === "string" && warning.includes("util._extend")) return;
2869
2869
  return origEmitWarning.call(process, warning, ...args);
2870
2870
  };
2871
+ var INDENT = " ";
2872
+ var LABEL_WIDTH = 10;
2873
+ var activeStatusLine = false;
2874
+ function clearActiveStatus() {
2875
+ if (!activeStatusLine || !process.stdout.isTTY) return;
2876
+ clearLine(process.stdout, 0);
2877
+ cursorTo(process.stdout, 0);
2878
+ activeStatusLine = false;
2879
+ }
2880
+ function writeLine(line = "") {
2881
+ clearActiveStatus();
2882
+ process.stdout.write(line ? `${line}
2883
+ ` : "\n");
2884
+ }
2885
+ function formatInfo(message) {
2886
+ return chalk.dim(`${INDENT}${message}`);
2887
+ }
2888
+ function formatPending(message) {
2889
+ return chalk.dim(`${INDENT}\u25CF ${message}`);
2890
+ }
2891
+ function formatSuccess(message) {
2892
+ return chalk.greenBright(`${INDENT}\u2713 ${message}`);
2893
+ }
2894
+ function formatReady(seconds = process.uptime()) {
2895
+ return chalk.greenBright(`${INDENT}\u2713 Ready in ${seconds.toFixed(1)}s`);
2896
+ }
2897
+ function formatWarning(message) {
2898
+ return chalk.yellow(`${INDENT}\u25B2 ${message}`);
2899
+ }
2900
+ function formatError(message) {
2901
+ return chalk.red(`${INDENT}\u2717 ${message}`);
2902
+ }
2903
+ function printInfo(message) {
2904
+ writeLine(formatInfo(message));
2905
+ }
2906
+ function printSuccess(message) {
2907
+ writeLine(formatSuccess(message));
2908
+ }
2909
+ function printWarning(message) {
2910
+ writeLine(formatWarning(message));
2911
+ }
2912
+ function printError(message) {
2913
+ writeLine(formatError(message));
2914
+ }
2915
+ function printDetail(message, formatter = chalk.dim) {
2916
+ writeLine(formatter(`${INDENT} ${message}`));
2917
+ }
2918
+ function printCommand(message) {
2919
+ printDetail(message, chalk.cyan);
2920
+ }
2921
+ function printLocation(label, value, brightValue = false) {
2922
+ const prefix = chalk.dim(`${INDENT}\u279C ${`${label}:`.padEnd(LABEL_WIDTH)}`);
2923
+ const renderedValue = brightValue ? chalk.whiteBright(value) : chalk.dim(value);
2924
+ writeLine(`${prefix}${renderedValue}`);
2925
+ }
2926
+ function startInlineStatus(message) {
2927
+ clearActiveStatus();
2928
+ const line = formatPending(message);
2929
+ if (!process.stdout.isTTY) {
2930
+ writeLine(line);
2931
+ return;
2932
+ }
2933
+ process.stdout.write(line);
2934
+ activeStatusLine = true;
2935
+ }
2936
+ function replaceInlineStatus(line) {
2937
+ if (!process.stdout.isTTY) {
2938
+ writeLine(line);
2939
+ return;
2940
+ }
2941
+ clearLine(process.stdout, 0);
2942
+ cursorTo(process.stdout, 0);
2943
+ process.stdout.write(`${line}
2944
+ `);
2945
+ activeStatusLine = false;
2946
+ }
2947
+ function finishInlineStatus(message) {
2948
+ replaceInlineStatus(formatSuccess(message));
2949
+ }
2950
+ function warnInlineStatus(message) {
2951
+ replaceInlineStatus(formatWarning(message));
2952
+ }
2953
+ function failInlineStatus(message) {
2954
+ replaceInlineStatus(formatError(message));
2955
+ }
2956
+ function finishInlineReady() {
2957
+ replaceInlineStatus(formatReady());
2958
+ }
2959
+ function getDetectedFrameworkLabel() {
2960
+ if (detectedFramework) return detectedFramework;
2961
+ const scripts = detectDevScripts();
2962
+ return scripts.length > 0 ? scripts[0].framework : null;
2963
+ }
2871
2964
  process.on("unhandledRejection", (err) => {
2872
- console.error(chalk.red("\n [OpenMagic] Unhandled error:"), err?.message || err);
2873
- console.error(chalk.dim(" Please report this at https://github.com/Kalmuraee/OpenMagic/issues"));
2965
+ writeLine();
2966
+ printError(`Unhandled error: ${err?.message || err}`);
2967
+ printDetail("Please report this at https://github.com/Kalmuraee/OpenMagic/issues");
2874
2968
  });
2875
2969
  process.on("uncaughtException", (err) => {
2876
- console.error(chalk.red("\n [OpenMagic] Fatal error:"), err.message);
2877
- console.error(chalk.dim(" Please report this at https://github.com/Kalmuraee/OpenMagic/issues"));
2970
+ writeLine();
2971
+ printError(`Fatal error: ${err.message}`);
2972
+ printDetail("Please report this at https://github.com/Kalmuraee/OpenMagic/issues");
2878
2973
  process.exit(1);
2879
2974
  });
2880
2975
  try {
@@ -2886,11 +2981,13 @@ try {
2886
2981
  }
2887
2982
  const newLimit = parseInt(execSync2("ulimit -n", { encoding: "utf-8", shell: true }).trim(), 10);
2888
2983
  if (newLimit < 4096) {
2889
- console.log(chalk.yellow("\n \u26A0 File descriptor limit is " + fdLimit + " (need 4096+)."));
2890
- console.log(chalk.dim(" This causes EMFILE errors in large Next.js/Turbopack projects."));
2891
- console.log(chalk.dim(" Fix: add this to your ~/.zshrc (or ~/.bashrc):"));
2892
- console.log(chalk.cyan(" ulimit -n 65536"));
2893
- console.log(chalk.dim(" Then restart your terminal.\n"));
2984
+ writeLine();
2985
+ printWarning(`File descriptor limit is ${fdLimit} (need 4096+).`);
2986
+ printDetail("This can cause EMFILE errors in large Next.js and Turbopack projects.");
2987
+ printDetail("Add this to your shell profile:");
2988
+ printCommand("ulimit -n 65536");
2989
+ printDetail("Then restart your terminal.");
2990
+ writeLine();
2894
2991
  }
2895
2992
  }
2896
2993
  } catch {
@@ -2995,15 +3092,13 @@ function runCommand(cmd, args, cwd = process.cwd()) {
2995
3092
  child.stdout?.on("data", (data) => {
2996
3093
  const lines = data.toString().trim().split("\n");
2997
3094
  for (const line of lines) {
2998
- if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
2999
- `));
3095
+ if (line.trim()) writeLine(chalk.dim(`${INDENT}\u2502 ${line}`));
3000
3096
  }
3001
3097
  });
3002
3098
  child.stderr?.on("data", (data) => {
3003
3099
  const lines = data.toString().trim().split("\n");
3004
3100
  for (const line of lines) {
3005
- if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
3006
- `));
3101
+ if (line.trim()) writeLine(chalk.dim(`${INDENT}\u2502 ${line}`));
3007
3102
  }
3008
3103
  });
3009
3104
  child.on("error", () => resolve4(false));
@@ -3021,20 +3116,17 @@ async function healthCheck(proxyPort, _targetPort) {
3021
3116
  signal: controller.signal
3022
3117
  });
3023
3118
  clearTimeout(timeout);
3024
- if (res.ok) {
3025
- console.log(chalk.green(" \u2713 Toolbar ready."));
3026
- } else {
3027
- console.log(chalk.yellow(" \u26A0 Proxy started but toolbar health check failed."));
3028
- }
3119
+ if (res.ok) return null;
3120
+ return {
3121
+ message: "Proxy started, but the toolbar health check failed.",
3122
+ details: ["Try refreshing the page in a few seconds."]
3123
+ };
3029
3124
  } catch {
3030
- console.log(
3031
- chalk.yellow(" \u26A0 Could not verify proxy. The dev server may still be starting.")
3032
- );
3033
- console.log(
3034
- chalk.dim(" Try refreshing the page in a few seconds.")
3035
- );
3125
+ return {
3126
+ message: "Could not verify the proxy while it was starting.",
3127
+ details: ["Try refreshing the page in a few seconds."]
3128
+ };
3036
3129
  }
3037
- console.log("");
3038
3130
  }
3039
3131
  var detectedFramework = null;
3040
3132
  async function validateAppHealth(targetHost, targetPort) {
@@ -3055,39 +3147,36 @@ async function validateAppHealth(targetHost, targetPort) {
3055
3147
  continue;
3056
3148
  }
3057
3149
  if (status === 404) {
3058
- console.log(chalk.yellow(' \u26A0 Your app returned 404 for the root path ("/").'));
3059
- console.log(chalk.dim(" The dev server is running, but no page matched."));
3060
- console.log("");
3150
+ printWarning("Your app returned 404 for the root path (/).");
3151
+ printDetail("The dev server is running, but no page matched the root path.");
3061
3152
  if (detectedFramework === "Next.js") {
3062
3153
  const strayLockfiles = scanParentLockfiles(process.cwd());
3063
3154
  if (strayLockfiles.length > 0) {
3064
- console.log(chalk.yellow(" Found lockfiles in parent directories that confuse Turbopack:"));
3155
+ printDetail("Found lockfiles in parent directories that can confuse Turbopack.");
3065
3156
  for (const f of strayLockfiles) {
3066
- console.log(chalk.dim(` \u2022 ${f}`));
3157
+ printDetail(`- ${f}`);
3067
3158
  }
3068
- console.log("");
3069
- console.log(chalk.dim(" Fix: remove them, or add to your next.config:"));
3070
- console.log(chalk.cyan(" turbopack: { root: __dirname }"));
3159
+ printDetail("Fix it by removing those lockfiles, or add this to next.config:");
3160
+ printCommand("turbopack: { root: __dirname }");
3071
3161
  } else {
3072
- console.log(chalk.dim(" Common Next.js causes:"));
3073
- console.log(chalk.dim(" \u2022 Missing src/app/page.tsx (App Router) or pages/index.tsx"));
3074
- console.log(chalk.dim(" \u2022 Middleware redirecting all routes to an auth provider"));
3162
+ printDetail("Common Next.js causes:");
3163
+ printDetail("- Missing src/app/page.tsx (App Router) or pages/index.tsx");
3164
+ printDetail("- Middleware redirecting all routes to an auth provider");
3075
3165
  }
3076
3166
  } else if (detectedFramework === "Angular") {
3077
- console.log(chalk.dim(" Angular hint: ensure the base href matches the proxy path."));
3167
+ printDetail("Angular hint: make sure the base href matches the proxy path.");
3078
3168
  } else if (detectedFramework === "Vite") {
3079
- console.log(chalk.dim(" Vite hint: check that index.html exists in the project root."));
3169
+ printDetail("Vite hint: check that index.html exists in the project root.");
3080
3170
  } else {
3081
- console.log(chalk.dim(" Check your framework's routing configuration."));
3171
+ printDetail("Check your framework's routing configuration.");
3082
3172
  }
3083
- console.log("");
3084
- console.log(chalk.dim(" The toolbar is still available \u2014 navigate to a working route."));
3085
- console.log("");
3173
+ printDetail("The toolbar is still available \u2014 navigate to a working route.");
3174
+ writeLine();
3086
3175
  return false;
3087
3176
  } else if (status >= 500) {
3088
- console.log(chalk.yellow(` \u26A0 Your app returned HTTP ${status} on the root path.`));
3089
- console.log(chalk.dim(" There may be a server-side error. Check your dev server output."));
3090
- console.log("");
3177
+ printWarning(`Your app returned HTTP ${status} on the root path.`);
3178
+ printDetail("There may be a server-side error. Check your dev server output.");
3179
+ writeLine();
3091
3180
  if (attempt < 4) {
3092
3181
  await new Promise((r) => setTimeout(r, 2e3));
3093
3182
  continue;
@@ -3112,12 +3201,9 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
3112
3201
  "-r, --root <paths...>",
3113
3202
  "Project root directories (defaults to cwd)"
3114
3203
  ).option("--no-open", "Don't auto-open browser").option("--host <host>", "Dev server host", "localhost").action(async (opts) => {
3115
- console.log("");
3116
- console.log(
3117
- " \u{1FA84} " + chalk.bold.hex("#6c5ce7")("OpenMagic") + chalk.dim(` v${VERSION2}`) + " \u2728"
3118
- );
3119
- console.log(chalk.dim(" AI coding toolbar for any web app"));
3120
- console.log("");
3204
+ writeLine();
3205
+ writeLine(`${INDENT}${chalk.white("OpenMagic")} ${chalk.dim(`v${VERSION2}`)}`);
3206
+ writeLine();
3121
3207
  let targetPort;
3122
3208
  let targetHost = opts.host;
3123
3209
  if (opts.port) {
@@ -3137,18 +3223,22 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
3137
3223
  targetHost = recheck.host;
3138
3224
  }
3139
3225
  }
3226
+ const frameworkLabel = getDetectedFrameworkLabel() ?? "dev server";
3227
+ printSuccess(`Found ${frameworkLabel} on port ${targetPort}`);
3140
3228
  }
3141
3229
  } else {
3142
- console.log(chalk.dim(" Scanning for dev server..."));
3230
+ startInlineStatus("Scanning for dev server...");
3143
3231
  const detected = await detectDevServer();
3144
3232
  if (detected && detected.fromScripts) {
3145
3233
  const healthy = await isPortHealthy(detected.host, detected.port);
3146
3234
  if (healthy) {
3147
3235
  targetPort = detected.port;
3148
3236
  targetHost = detected.host;
3237
+ const frameworkLabel = getDetectedFrameworkLabel() ?? "dev server";
3238
+ finishInlineStatus(`Found ${frameworkLabel} on port ${detected.port}`);
3149
3239
  } else {
3150
- console.log(chalk.yellow(` \u26A0 Dev server on port ${detected.port} is not responding properly.`));
3151
- console.log(chalk.dim(" Cleaning up orphaned process..."));
3240
+ warnInlineStatus(`Dev server on port ${detected.port} is not responding`);
3241
+ printDetail("Cleaning up the orphaned process...");
3152
3242
  killPortProcess(detected.port);
3153
3243
  const freed = await waitForPortClose(detected.port, 5e3);
3154
3244
  if (!freed) {
@@ -3179,27 +3269,33 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
3179
3269
  targetPort = redetected.port;
3180
3270
  targetHost = redetected.host;
3181
3271
  } else {
3182
- console.log(chalk.red(" \u2717 Could not detect the dev server after starting."));
3183
- console.log(chalk.dim(" Try specifying the port: npx openmagic --port 3000"));
3272
+ printError("Could not detect the dev server after starting.");
3273
+ printDetail("Try specifying the port manually:");
3274
+ printCommand("npx openmagic --port 3000");
3184
3275
  process.exit(1);
3185
3276
  }
3186
3277
  }
3278
+ const frameworkLabel = getDetectedFrameworkLabel() ?? "dev server";
3279
+ printSuccess(`Found ${frameworkLabel} on port ${targetPort}`);
3187
3280
  }
3188
3281
  } else if (detected && !detected.fromScripts) {
3282
+ finishInlineStatus(`Found dev server on port ${detected.port}`);
3189
3283
  const answer = await ask(
3190
- chalk.yellow(` Found a server on port ${detected.port}. Is this your project's dev server? `) + chalk.dim("(y/n) ")
3284
+ chalk.yellow(`${INDENT}\u25B2 Found a server on port ${detected.port}. Is this your project's dev server? `) + chalk.dim("(Y/n) ")
3191
3285
  );
3192
3286
  if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes" || answer === "") {
3193
3287
  targetPort = detected.port;
3194
3288
  targetHost = detected.host;
3195
3289
  } else {
3196
- console.log("");
3197
- console.log(chalk.dim(" Start your dev server, then run:"));
3198
- console.log(chalk.cyan(" npx openmagic --port <your-port>"));
3199
- console.log("");
3290
+ writeLine(formatInfo("Cancelled dev server selection."));
3291
+ writeLine();
3292
+ printInfo("Start your dev server, then run:");
3293
+ printCommand("npx openmagic --port <your-port>");
3294
+ writeLine();
3200
3295
  process.exit(0);
3201
3296
  }
3202
3297
  } else {
3298
+ warnInlineStatus("No dev server found. Starting one...");
3203
3299
  const started = await offerToStartDevServer();
3204
3300
  if (!started) {
3205
3301
  process.exit(1);
@@ -3209,34 +3305,34 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
3209
3305
  } else {
3210
3306
  const redetected = await detectDevServer();
3211
3307
  if (!redetected) {
3212
- console.log(chalk.red(" \u2717 Could not detect the dev server after starting."));
3213
- console.log(chalk.dim(" Try specifying the port: npx openmagic --port 3000"));
3214
- console.log("");
3308
+ printError("Could not detect the dev server after starting.");
3309
+ printDetail("Try specifying the port manually:");
3310
+ printCommand("npx openmagic --port 3000");
3311
+ writeLine();
3215
3312
  process.exit(1);
3216
3313
  }
3217
3314
  targetPort = redetected.port;
3218
3315
  targetHost = redetected.host;
3219
3316
  }
3317
+ const frameworkLabel = getDetectedFrameworkLabel() ?? "dev server";
3318
+ printSuccess(`Found ${frameworkLabel} on port ${targetPort}`);
3220
3319
  }
3221
3320
  }
3222
3321
  if (!detectedFramework) {
3223
3322
  const scripts = detectDevScripts();
3224
3323
  if (scripts.length > 0) detectedFramework = scripts[0].framework;
3225
3324
  }
3226
- console.log(
3227
- chalk.green(` \u2713 Dev server running at ${targetHost}:${targetPort}`)
3228
- );
3229
3325
  if (detectedFramework === "Next.js") {
3230
3326
  const strayLockfiles = scanParentLockfiles(process.cwd());
3231
3327
  if (strayLockfiles.length > 0) {
3232
- console.log("");
3233
- console.log(chalk.yellow(" \u26A0 Lockfiles found in parent directories:"));
3328
+ writeLine();
3329
+ printWarning("Lockfiles found in parent directories.");
3234
3330
  for (const f of strayLockfiles) {
3235
- console.log(chalk.dim(` \u2022 ${f}`));
3331
+ printDetail(`- ${f}`);
3236
3332
  }
3237
- console.log(chalk.dim(" Next.js Turbopack may use the wrong workspace root, causing 404s."));
3238
- console.log(chalk.dim(" Fix: remove them, or add to next.config:"));
3239
- console.log(chalk.cyan(" turbopack: { root: __dirname }"));
3333
+ printDetail("Next.js Turbopack may use the wrong workspace root and cause 404s.");
3334
+ printDetail("Fix it by removing those lockfiles, or add this to next.config:");
3335
+ printCommand("turbopack: { root: __dirname }");
3240
3336
  }
3241
3337
  }
3242
3338
  const roots = (opts.root || [process.cwd()]).map(
@@ -3245,35 +3341,41 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
3245
3341
  const config = loadConfig();
3246
3342
  saveConfig({ ...config, roots, targetPort });
3247
3343
  generateSessionToken();
3248
- let proxyPort = parseInt(opts.listen, 10);
3344
+ const requestedProxyPort = parseInt(opts.listen, 10);
3345
+ let proxyPort = requestedProxyPort;
3249
3346
  while (await isPortOpen(proxyPort)) {
3250
3347
  proxyPort++;
3251
- if (proxyPort > parseInt(opts.listen, 10) + 100) {
3252
- console.log(chalk.red(" Could not find an available port."));
3348
+ if (proxyPort > requestedProxyPort + 100) {
3349
+ printError("Could not find an available port for the OpenMagic proxy.");
3253
3350
  process.exit(1);
3254
3351
  }
3255
3352
  }
3353
+ if (proxyPort !== requestedProxyPort) {
3354
+ writeLine();
3355
+ printWarning(`Port ${requestedProxyPort} is in use \u2014 starting on ${proxyPort}`);
3356
+ }
3256
3357
  const proxyServer = createProxyServer(targetHost, targetPort, roots);
3257
3358
  proxyServer.listen(proxyPort, "localhost", async () => {
3258
3359
  const proxyUrl = `http://localhost:${proxyPort}`;
3259
- console.log("");
3260
- console.log(chalk.bold.green(" Ready!"));
3261
- console.log("");
3262
- console.log(
3263
- chalk.bold(" \u2192 ") + chalk.bold.underline.cyan(proxyUrl)
3264
- );
3265
- console.log("");
3266
- await healthCheck(proxyPort, targetPort);
3267
- console.log(chalk.dim(" Waiting for app to compile..."));
3268
- const appReady = await validateAppHealth(targetHost, targetPort);
3269
- if (appReady) {
3270
- console.log(chalk.green(" \u2713 App is ready."));
3271
- }
3272
- console.log(chalk.dim(" Press Ctrl+C to stop."));
3273
- console.log(
3274
- chalk.dim(" Errors below are from your dev server, not OpenMagic.")
3275
- );
3276
- console.log("");
3360
+ const proxyWarning = await healthCheck(proxyPort, targetPort);
3361
+ const frameworkLabel = getDetectedFrameworkLabel();
3362
+ const targetLabel = frameworkLabel ? `${targetHost}:${targetPort} (${frameworkLabel})` : `${targetHost}:${targetPort}`;
3363
+ printLocation("Local", proxyUrl, true);
3364
+ printLocation("Target", targetLabel);
3365
+ writeLine();
3366
+ startInlineStatus("Waiting for app to compile...");
3367
+ await validateAppHealth(targetHost, targetPort);
3368
+ finishInlineReady();
3369
+ if (proxyWarning) {
3370
+ writeLine();
3371
+ printWarning(proxyWarning.message);
3372
+ for (const detail of proxyWarning.details || []) {
3373
+ printDetail(detail);
3374
+ }
3375
+ }
3376
+ writeLine();
3377
+ printInfo("Press Ctrl+C to stop");
3378
+ writeLine();
3277
3379
  if (opts.open !== false) {
3278
3380
  open(`http://localhost:${proxyPort}`).catch(() => {
3279
3381
  });
@@ -3283,8 +3385,8 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
3283
3385
  const shutdown = async () => {
3284
3386
  if (shuttingDown) return;
3285
3387
  shuttingDown = true;
3286
- console.log("");
3287
- console.log(chalk.dim(" Shutting down OpenMagic..."));
3388
+ writeLine();
3389
+ printInfo("Shutting down OpenMagic...");
3288
3390
  cleanupBackups();
3289
3391
  proxyServer.close();
3290
3392
  const alive = childProcesses.filter((cp) => cp.exitCode === null);
@@ -3300,7 +3402,7 @@ program.name("openmagic").description("AI-powered coding toolbar for any web app
3300
3402
  if (targetPort && await isPortOpen(targetPort)) {
3301
3403
  const freed = await waitForPortClose(targetPort, 4e3);
3302
3404
  if (!freed) {
3303
- console.log(chalk.dim(" Force-killing remaining processes..."));
3405
+ printInfo("Force-killing remaining processes...");
3304
3406
  if (targetPort) {
3305
3407
  try {
3306
3408
  const pids = execSync2(`lsof -i :${targetPort} -sTCP:LISTEN -t 2>/dev/null`, {
@@ -3363,18 +3465,16 @@ async function offerToStartDevServer(expectedPort) {
3363
3465
  if (scripts.length === 0) {
3364
3466
  const htmlPath = join6(process.cwd(), "index.html");
3365
3467
  if (existsSync6(htmlPath)) {
3366
- console.log(
3367
- chalk.dim(" No dev scripts found, but index.html detected.")
3368
- );
3369
- console.log("");
3468
+ printInfo("No dev scripts found, but index.html was detected.");
3469
+ writeLine();
3370
3470
  const answer = await ask(
3371
- chalk.white(" Serve this directory as a static site? ") + chalk.dim("(Y/n) ")
3471
+ chalk.dim(`${INDENT}Serve this directory as a static site? `) + chalk.dim("(Y/n) ")
3372
3472
  );
3373
3473
  if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
3374
3474
  return false;
3375
3475
  }
3376
3476
  const staticPort = expectedPort || 8080;
3377
- console.log(chalk.dim(` Starting static server on port ${staticPort}...`));
3477
+ startInlineStatus(`Starting static server on port ${staticPort}...`);
3378
3478
  const staticChild = spawn5("node", ["-e", `
3379
3479
  const http = require("http");
3380
3480
  const fs = require("fs");
@@ -3389,7 +3489,7 @@ async function offerToStartDevServer(expectedPort) {
3389
3489
  res.writeHead(200, {"Content-Type": mimes[ext] || "application/octet-stream"});
3390
3490
  res.end(data);
3391
3491
  });
3392
- }).listen(${staticPort}, "localhost", () => console.log("Static server ready on port ${staticPort}"));
3492
+ }).listen(${staticPort}, "localhost");
3393
3493
  `], {
3394
3494
  cwd: process.cwd(),
3395
3495
  stdio: ["ignore", "pipe", "pipe"],
@@ -3398,99 +3498,90 @@ async function offerToStartDevServer(expectedPort) {
3398
3498
  childProcesses.push(staticChild);
3399
3499
  staticChild.stdout?.on("data", (d) => {
3400
3500
  for (const line of d.toString().trim().split("\n")) {
3401
- if (line.trim()) process.stdout.write(chalk.dim(` \u2502 ${line}
3402
- `));
3501
+ if (line.trim()) writeLine(chalk.dim(`${INDENT}\u2502 ${line}`));
3403
3502
  }
3404
3503
  });
3405
3504
  const up = await waitForPort(staticPort, 5e3);
3406
3505
  if (up) {
3407
3506
  lastDetectedPort = staticPort;
3408
- console.log(chalk.green(` \u2713 Static server running on port ${staticPort}`));
3507
+ finishInlineStatus(`Static server running on port ${staticPort}`);
3409
3508
  return true;
3410
3509
  }
3411
- console.log(chalk.red(" \u2717 Failed to start static server."));
3510
+ failInlineStatus("Static server failed to start");
3412
3511
  return false;
3413
3512
  }
3414
- console.log(
3415
- chalk.yellow(" \u26A0 No dev server detected and no dev scripts found.")
3416
- );
3417
- console.log("");
3418
- console.log(chalk.white(" Start your dev server manually, then run:"));
3419
- console.log(chalk.cyan(" npx openmagic --port <your-port>"));
3420
- console.log("");
3513
+ printWarning("No dev server detected and no dev scripts were found.");
3514
+ writeLine();
3515
+ printInfo("Start your dev server manually, then run:");
3516
+ printCommand("npx openmagic --port <your-port>");
3517
+ writeLine();
3421
3518
  return false;
3422
3519
  }
3423
3520
  const deps = checkDependenciesInstalled();
3424
3521
  if (!deps.installed) {
3425
- console.log(
3426
- chalk.yellow(" \u26A0 node_modules/ not found. Dependencies need to be installed.")
3427
- );
3428
- console.log("");
3522
+ printWarning("node_modules was not found. Dependencies need to be installed.");
3523
+ writeLine();
3429
3524
  const answer = await ask(
3430
- chalk.white(` Run `) + chalk.cyan(deps.installCommand) + chalk.white("? ") + chalk.dim("(Y/n) ")
3525
+ chalk.white(`${INDENT}Run `) + chalk.cyan(deps.installCommand) + chalk.white("? ") + chalk.dim("(Y/n) ")
3431
3526
  );
3432
3527
  if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
3433
- console.log("");
3434
- console.log(chalk.dim(` Run ${deps.installCommand} manually, then try again.`));
3435
- console.log("");
3528
+ writeLine();
3529
+ printInfo(`Run ${deps.installCommand} manually, then try again.`);
3530
+ writeLine();
3436
3531
  return false;
3437
3532
  }
3438
- console.log("");
3439
- console.log(chalk.dim(` Installing dependencies with ${deps.packageManager}...`));
3533
+ writeLine();
3534
+ printInfo(`Installing dependencies with ${deps.packageManager}...`);
3440
3535
  const [installCmd, ...installArgs] = deps.installCommand.split(" ");
3441
3536
  const installed = await runCommand(installCmd, installArgs);
3442
3537
  if (!installed) {
3443
- console.log(chalk.red(" \u2717 Dependency installation failed."));
3444
- console.log(chalk.dim(` Try running ${deps.installCommand} manually.`));
3445
- console.log("");
3538
+ printError("Dependency installation failed.");
3539
+ printDetail(`Try running ${deps.installCommand} manually.`);
3540
+ writeLine();
3446
3541
  return false;
3447
3542
  }
3448
- console.log(chalk.green(" \u2713 Dependencies installed."));
3449
- console.log("");
3543
+ printSuccess("Dependencies installed.");
3544
+ writeLine();
3450
3545
  }
3451
3546
  let chosen = scripts[0];
3452
3547
  if (scripts.length === 1) {
3453
- console.log(
3454
- chalk.yellow(" \u26A0 No dev server detected.")
3455
- );
3456
- console.log("");
3457
- console.log(
3458
- chalk.white(` Found `) + chalk.cyan(`npm run ${chosen.name}`) + chalk.white(` in ${projectName}`) + chalk.dim(` (${chosen.framework})`)
3548
+ printWarning("No dev server detected.");
3549
+ writeLine();
3550
+ writeLine(
3551
+ chalk.white(`${INDENT}Found `) + chalk.cyan(`npm run ${chosen.name}`) + chalk.white(` in ${projectName}`) + chalk.dim(` (${chosen.framework})`)
3459
3552
  );
3460
- console.log(chalk.dim(` \u2192 ${chosen.command}`));
3461
- console.log("");
3553
+ printDetail(`\u279C ${chosen.command}`);
3554
+ writeLine();
3462
3555
  const answer = await ask(
3463
- chalk.white(` Start it now? `) + chalk.dim("(Y/n) ")
3556
+ chalk.white(`${INDENT}Start it now? `) + chalk.dim("(Y/n) ")
3464
3557
  );
3465
3558
  if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
3466
- console.log("");
3467
- console.log(chalk.dim(" Start your dev server first, then run openmagic again."));
3468
- console.log("");
3559
+ writeLine();
3560
+ printInfo("Start your dev server first, then run OpenMagic again.");
3561
+ writeLine();
3469
3562
  return false;
3470
3563
  }
3471
3564
  } else {
3472
- console.log(
3473
- chalk.yellow(" \u26A0 No dev server detected.")
3474
- );
3475
- console.log("");
3476
- console.log(
3477
- chalk.white(` Found ${scripts.length} dev scripts in ${projectName}:`)
3565
+ printWarning("No dev server detected.");
3566
+ writeLine();
3567
+ writeLine(
3568
+ chalk.white(`${INDENT}Found ${scripts.length} dev scripts in ${projectName}:`)
3478
3569
  );
3479
- console.log("");
3570
+ writeLine();
3480
3571
  scripts.forEach((s, i) => {
3481
- console.log(
3482
- chalk.cyan(` ${i + 1}) `) + chalk.white(`npm run ${s.name}`) + chalk.dim(` \u2014 ${s.framework} (port ${s.defaultPort})`)
3572
+ writeLine(
3573
+ chalk.cyan(`${INDENT}${i + 1}. `) + chalk.white(`npm run ${s.name}`) + chalk.dim(` (${s.framework}, port ${s.defaultPort})`)
3483
3574
  );
3484
- console.log(chalk.dim(` ${s.command}`));
3575
+ printDetail(s.command);
3485
3576
  });
3486
- console.log("");
3577
+ writeLine();
3487
3578
  const answer = await ask(
3488
- chalk.white(` Which one to start? `) + chalk.dim(`(1-${scripts.length}, or n to cancel) `)
3579
+ chalk.white(`${INDENT}Which one should OpenMagic start? `) + chalk.dim(`(1-${scripts.length}, or n to cancel) `)
3489
3580
  );
3490
3581
  if (answer.toLowerCase() === "n" || answer.toLowerCase() === "no" || answer === "") {
3491
- console.log("");
3492
- console.log(chalk.dim(" Start your dev server first, then run openmagic again."));
3493
- console.log("");
3582
+ writeLine();
3583
+ printInfo("Start your dev server first, then run OpenMagic again.");
3584
+ writeLine();
3494
3585
  return false;
3495
3586
  }
3496
3587
  const idx = parseInt(answer, 10) - 1;
@@ -3503,13 +3594,13 @@ async function offerToStartDevServer(expectedPort) {
3503
3594
  detectedFramework = chosen.framework;
3504
3595
  const compat = checkNodeCompatibility(chosen.framework);
3505
3596
  if (!compat.ok) {
3506
- console.log(chalk.red(`
3507
- \u2717 ${compat.message}`));
3508
- console.log("");
3509
- console.log(chalk.white(" Switch Node.js version before running:"));
3510
- console.log(chalk.cyan(" nvm use 20"));
3511
- console.log(chalk.dim(" # then re-run: npx openmagic"));
3512
- console.log("");
3597
+ writeLine();
3598
+ printError(compat.message);
3599
+ writeLine();
3600
+ printInfo("Switch Node.js before running:");
3601
+ printCommand("nvm use 20");
3602
+ printDetail("Then re-run: npx openmagic");
3603
+ writeLine();
3513
3604
  return false;
3514
3605
  }
3515
3606
  let port = expectedPort || chosen.defaultPort;
@@ -3517,24 +3608,19 @@ async function offerToStartDevServer(expectedPort) {
3517
3608
  if (await isPortOpen(port)) {
3518
3609
  const owned = verifyPortOwnership(port, process.cwd());
3519
3610
  if (owned === true) {
3520
- console.log(chalk.green(` \u2713 Dev server already running on port ${port}`));
3611
+ printSuccess(`Dev server already running on port ${port}`);
3521
3612
  lastDetectedPort = port;
3522
3613
  return true;
3523
3614
  }
3524
3615
  const altPort = await findAvailablePort(port + 1);
3525
- console.log("");
3526
- console.log(
3527
- chalk.yellow(` \u26A0 Port ${port} is already in use by another process.`)
3528
- );
3529
- console.log(
3530
- chalk.dim(` Starting on port ${altPort} instead.`)
3531
- );
3616
+ writeLine();
3617
+ printWarning(`Port ${port} is in use \u2014 starting on ${altPort}`);
3532
3618
  port = altPort;
3533
3619
  portChanged = true;
3534
3620
  }
3535
- console.log("");
3536
- console.log(
3537
- chalk.dim(` Starting `) + chalk.cyan(`npm run ${chosen.name}`) + (portChanged ? chalk.dim(` (port ${port})`) : "") + chalk.dim("...")
3621
+ writeLine();
3622
+ writeLine(
3623
+ chalk.dim(`${INDENT}\u25CF Starting `) + chalk.cyan(`npm run ${chosen.name}`) + (portChanged ? chalk.dim(` (port ${port})`) : "") + chalk.dim("...")
3538
3624
  );
3539
3625
  const depsInfo = checkDependenciesInstalled();
3540
3626
  const runCmd = depsInfo.packageManager === "yarn" ? "yarn" : depsInfo.packageManager === "pnpm" ? "pnpm" : depsInfo.packageManager === "bun" ? "bun" : "npm";
@@ -3562,21 +3648,21 @@ async function offerToStartDevServer(expectedPort) {
3562
3648
  }
3563
3649
  });
3564
3650
  } catch (e) {
3565
- console.log(chalk.red(` \u2717 Failed to start: ${e.message}`));
3651
+ printError(`Failed to start: ${e.message}`);
3566
3652
  return false;
3567
3653
  }
3568
3654
  childProcesses.push(child);
3569
3655
  let childExited = false;
3570
3656
  child.on("error", (err) => {
3571
3657
  childExited = true;
3572
- console.log(chalk.red(`
3573
- \u2717 Failed to start: ${err.message}`));
3658
+ writeLine();
3659
+ printError(`Failed to start: ${err.message}`);
3574
3660
  });
3575
3661
  child.on("exit", (code) => {
3576
3662
  childExited = true;
3577
3663
  if (code !== null && code !== 0) {
3578
- console.log(chalk.red(`
3579
- \u2717 Dev server exited with code ${code}`));
3664
+ writeLine();
3665
+ printError(`Dev server exited with code ${code}`);
3580
3666
  }
3581
3667
  });
3582
3668
  process.once("exit", () => {
@@ -3589,13 +3675,11 @@ async function offerToStartDevServer(expectedPort) {
3589
3675
  }
3590
3676
  }
3591
3677
  });
3592
- console.log(
3593
- chalk.dim(` Waiting for dev server on port ${port}...`)
3594
- );
3678
+ writeLine(formatPending(`Waiting for ${chosen.framework} on port ${port}...`));
3595
3679
  const isUp = await waitForPort(port, 6e4, () => childExited);
3596
3680
  if (isUp) {
3597
3681
  lastDetectedPort = port;
3598
- console.log("");
3682
+ printSuccess(`${chosen.framework} is listening on port ${port}`);
3599
3683
  return true;
3600
3684
  }
3601
3685
  if (!childExited) {
@@ -3604,28 +3688,23 @@ async function offerToStartDevServer(expectedPort) {
3604
3688
  if (await isPortOpen(scanPort)) {
3605
3689
  const owned = verifyPortOwnership(scanPort, process.cwd());
3606
3690
  if (owned === false) continue;
3607
- console.log(
3608
- chalk.green(`
3609
- \u2713 Dev server found on port ${scanPort}.`)
3610
- );
3691
+ printSuccess(`Found ${chosen.framework} on port ${scanPort}`);
3611
3692
  lastDetectedPort = scanPort;
3612
3693
  return true;
3613
3694
  }
3614
3695
  }
3615
3696
  }
3616
3697
  if (childExited) {
3617
- console.log(
3618
- chalk.red(` \u2717 Dev server failed to start.`)
3619
- );
3620
- console.log("");
3698
+ printError("Dev server failed to start");
3699
+ writeLine();
3621
3700
  try {
3622
3701
  const pkgPath = join6(process.cwd(), "package.json");
3623
3702
  if (existsSync6(pkgPath)) {
3624
3703
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
3625
3704
  if (pkg.engines?.node) {
3626
- console.log(chalk.yellow(` This project requires Node.js ${pkg.engines.node}`));
3627
- console.log(chalk.dim(` You are running Node.js ${process.version}`));
3628
- console.log("");
3705
+ printWarning(`This project requires Node.js ${pkg.engines.node}`);
3706
+ printDetail(`You are running Node.js ${process.version}`);
3707
+ writeLine();
3629
3708
  }
3630
3709
  }
3631
3710
  } catch {
@@ -3633,26 +3712,24 @@ async function offerToStartDevServer(expectedPort) {
3633
3712
  if (chosen?.framework) {
3634
3713
  const compat2 = checkNodeCompatibility(chosen.framework);
3635
3714
  if (!compat2.ok) {
3636
- console.log(chalk.yellow(` ${compat2.message}`));
3637
- console.log(chalk.dim(" Switch with: nvm use 20"));
3638
- console.log("");
3715
+ printWarning(compat2.message);
3716
+ printDetail("Switch with:");
3717
+ printCommand("nvm use 20");
3718
+ writeLine();
3639
3719
  }
3640
3720
  }
3641
- console.log(chalk.white(" Options:"));
3642
- console.log(chalk.dim(" 1. Fix the error above and try again"));
3643
- console.log(chalk.dim(" 2. Start the server manually, then run:"));
3644
- console.log(chalk.cyan(" npx openmagic --port <your-port>"));
3645
- console.log("");
3721
+ writeLine(chalk.white(`${INDENT}Options:`));
3722
+ printDetail("1. Fix the error above and try again");
3723
+ printDetail("2. Start the server manually, then run:");
3724
+ printCommand("npx openmagic --port <your-port>");
3725
+ writeLine();
3646
3726
  return false;
3647
3727
  }
3648
- console.log(
3649
- chalk.yellow(`
3650
- \u26A0 Could not find the dev server after 60s.`)
3651
- );
3652
- console.log(chalk.dim(` Check the output above for errors.`));
3653
- console.log(chalk.dim(` Or start the server manually, then run:`));
3654
- console.log(chalk.cyan(` npx openmagic --port <your-port>`));
3655
- console.log("");
3728
+ printWarning("Could not find the dev server after 60s");
3729
+ printDetail("Check the output above for errors.");
3730
+ printDetail("Or start the server manually, then run:");
3731
+ printCommand("npx openmagic --port <your-port>");
3732
+ writeLine();
3656
3733
  return false;
3657
3734
  }
3658
3735
  program.parse();