From 540dcd8419399d95d7bb3dd89ea7d4d171fb51f1 Mon Sep 17 00:00:00 2001 From: ethan Date: Mon, 15 Dec 2025 15:58:42 +1100 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=A4=96=20fix:=20AppImage=20CLI=20argu?= =?UTF-8?q?ment=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix CLI subcommands (server, api, run) not working when invoked from packaged AppImage. The issue was incorrect argv offset detection. Problem: - In packaged Electron apps, argv = [app, ...args] (first arg at index 1) - In dev mode (electron .), argv = [electron, ".", ...args] (index 2) - In bun/node, argv = [node, script, ...args] (index 2) The original code used process.argv[2] unconditionally, which broke packaged apps. The initial fix used process.defaultApp which is undefined in both packaged Electron AND bun/node, breaking non-Electron CLI. Solution: - Use isPackagedElectron = isElectron && !process.defaultApp - Only packaged Electron uses index 1; all others use index 2 - Update Commander parse mode in server.ts and run.ts Additional improvements: - Hide 'run' command from packaged app help (not bundled) - Friendly error when trying to run 'mux run' from AppImage - Remove '(requires Electron)' suffix in Electron context --- _Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking: `high`_ --- src/cli/index.ts | 40 +++++++++++++++++++++++++++++++--------- src/cli/run.ts | 5 ++++- src/cli/server.ts | 5 ++++- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index 201b280681..d063e4d511 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -8,18 +8,26 @@ * fails when running CLI commands in non-GUI environments. Subcommands like * `run` and `server` import the AI SDK which has significant startup cost. * - * By checking argv[2] first, we only load the code path actually needed. + * By checking argv first, we only load the code path actually needed. * * ELECTRON DETECTION: * When run via `electron .` or as a packaged app, Electron sets process.versions.electron. * In that case, we launch the desktop app automatically. When run via `bun` or `node`, * we show CLI help instead. + * + * ARGV OFFSET: + * In development (`electron .`), argv = [electron, ".", ...args] so first arg is at index 2. + * In packaged apps (`./mux.AppImage`), argv = [app, ...args] so first arg is at index 1. + * process.defaultApp is true in dev mode and undefined in packaged apps. */ import { Command } from "commander"; import { VERSION } from "../version"; -const subcommand = process.argv[2]; const isElectron = "electron" in process.versions; +// Only packaged Electron has args at index 1; all others (bun, node, electron .) use index 2 +const isPackagedElectron = isElectron && !process.defaultApp; +const firstArgIndex = isPackagedElectron ? 1 : 2; +const subcommand = process.argv[firstArgIndex]; function launchDesktop(): void { // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -27,19 +35,26 @@ function launchDesktop(): void { } // Route known subcommands to their dedicated entry points (each has its own Commander instance) -// When Electron launches us (e.g., `bunx electron --flags .`), argv[2] may be a flag or "." - not a subcommand -const isElectronLaunchArg = subcommand?.startsWith("-") || subcommand === "."; +// In dev mode (electron --inspect .), argv may contain flags or "." before the subcommand. +// Only treat these as "launch args" in dev mode; in packaged apps, --help is a legitimate CLI flag. +const isElectronLaunchArg = + process.defaultApp && (subcommand?.startsWith("-") || subcommand === "."); if (subcommand === "run") { - process.argv.splice(2, 1); // Remove "run" since run.ts defines .name("mux run") + if (isPackagedElectron) { + console.error("The 'run' command is only available via the CLI (bun mux run)."); + console.error("It is not included in the packaged Electron app."); + process.exit(1); + } + process.argv.splice(firstArgIndex, 1); // Remove "run" since run.ts defines .name("mux run") // eslint-disable-next-line @typescript-eslint/no-require-imports require("./run"); } else if (subcommand === "server") { - process.argv.splice(2, 1); + process.argv.splice(firstArgIndex, 1); // eslint-disable-next-line @typescript-eslint/no-require-imports require("./server"); } else if (subcommand === "api") { - process.argv.splice(2, 1); + process.argv.splice(firstArgIndex, 1); // Must use native import() to load ESM module - trpc-cli requires ESM with top-level await. // Using Function constructor prevents TypeScript from converting this to require(). // The .mjs extension is critical for Node.js to treat it as ESM. @@ -70,10 +85,17 @@ if (subcommand === "run") { .version(`${gitDescribe} (${gitCommit})`, "-v, --version"); // Register subcommand stubs for help display (actual implementations are above) - program.command("run").description("Run a one-off agent task"); + // `run` is only available via bun/node CLI, not bundled in packaged Electron app + if (!isPackagedElectron) { + program.command("run").description("Run a one-off agent task"); + } program.command("server").description("Start the HTTP/WebSocket ORPC server"); program.command("api").description("Interact with the mux API via a running server"); - program.command("desktop").description("Launch the desktop app (requires Electron)"); + program + .command("desktop") + .description( + isElectron ? "Launch the desktop app" : "Launch the desktop app (requires Electron)" + ); program.parse(); } diff --git a/src/cli/run.ts b/src/cli/run.ts index f5ef06c943..f61fc0b4d7 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -44,6 +44,9 @@ import assert from "@/common/utils/assert"; import parseDuration from "parse-duration"; import { log, type LogLevel } from "@/node/services/log"; +// Only packaged Electron needs 'electron' parse mode; bun/node/electron-dev all use 'node' +const isPackagedElectron = "electron" in process.versions && !process.defaultApp; + type CLIMode = "plan" | "exec"; function parseRuntimeConfig(value: string | undefined, srcBaseDir: string): RuntimeConfig { @@ -212,7 +215,7 @@ Examples: ` ); -program.parse(process.argv); +program.parse(process.argv, { from: isPackagedElectron ? "electron" : "node" }); interface CLIOptions { dir: string; diff --git a/src/cli/server.ts b/src/cli/server.ts index 3651520cde..b45e3fd657 100644 --- a/src/cli/server.ts +++ b/src/cli/server.ts @@ -12,6 +12,9 @@ import { validateProjectPath } from "@/node/utils/pathUtils"; import { createOrpcServer } from "@/node/orpc/server"; import type { ORPCContext } from "@/node/orpc/context"; +// Only packaged Electron needs 'electron' parse mode; bun/node/electron-dev all use 'node' +const isPackagedElectron = "electron" in process.versions && !process.defaultApp; + const program = new Command(); program .name("mux server") @@ -21,7 +24,7 @@ program .option("--auth-token ", "optional bearer token for HTTP/WS auth") .option("--ssh-host ", "SSH hostname/alias for editor deep links (e.g., devbox)") .option("--add-project ", "add and open project at the specified path (idempotent)") - .parse(process.argv); + .parse(process.argv, { from: isPackagedElectron ? "electron" : "node" }); const options = program.opts(); const HOST = options.host as string; From daf28bc6995221f4d0e1aa1a09efa0e1b3956692 Mon Sep 17 00:00:00 2001 From: ethan Date: Tue, 16 Dec 2025 12:43:38 +1100 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=A4=96=20fix:=20add=20isCommandAvaila?= =?UTF-8?q?ble()=20for=20Electron=20run=20command=20guard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract isCommandAvailable() to argv.ts for testable command availability - Fix run command check: use isElectron (not isPackagedElectron) - Add 5 unit tests for isCommandAvailable() - bunx electron . run now shows friendly error instead of crash --- src/cli/api.ts | 29 +++++-- src/cli/argv.test.ts | 194 +++++++++++++++++++++++++++++++++++++++++++ src/cli/argv.ts | 102 +++++++++++++++++++++++ src/cli/index.ts | 38 ++++----- src/cli/run.ts | 6 +- src/cli/server.ts | 6 +- 6 files changed, 343 insertions(+), 32 deletions(-) create mode 100644 src/cli/argv.test.ts create mode 100644 src/cli/argv.ts diff --git a/src/cli/api.ts b/src/cli/api.ts index 3618d83c87..5cc25aec8a 100644 --- a/src/cli/api.ts +++ b/src/cli/api.ts @@ -15,7 +15,11 @@ import { router } from "@/node/orpc/router"; import { proxifyOrpc } from "./proxifyOrpc"; import { ServerLockfile } from "@/node/services/serverLockfile"; import { getMuxHome } from "@/common/constants/paths"; -import type { Command } from "commander"; +import { getArgsAfterSplice } from "./argv"; + +// index.ts already splices "api" from argv before importing this module, +// so we just need to get the remaining args after the splice point. +const args = getArgsAfterSplice(); interface ServerDiscovery { baseUrl: string; @@ -57,9 +61,24 @@ async function discoverServer(): Promise { const { baseUrl, authToken } = await discoverServer(); const proxiedRouter = proxifyOrpc(router(), { baseUrl, authToken }); - const cli = createCli({ router: proxiedRouter }).buildProgram() as Command; - cli.name("mux api"); - cli.description("Interact with the mux API via a running server"); - cli.parse(); + // Use trpc-cli's run() method instead of buildProgram().parse() + // run() sets exitOverride on root, uses parseAsync, and handles process exit properly + const { run } = createCli({ + router: proxiedRouter, + name: "mux api", + description: "Interact with the mux API via a running server", + }); + + try { + await run({ argv: args }); + } catch (error) { + // trpc-cli throws FailedToExitError after calling process.exit() + // In Electron, process.exit() doesn't immediately terminate, so the error surfaces. + // This is expected and safe to ignore since exit was already requested. + if (error instanceof Error && error.constructor.name === "FailedToExitError") { + return; + } + throw error; + } })(); diff --git a/src/cli/argv.test.ts b/src/cli/argv.test.ts new file mode 100644 index 0000000000..8a687fb23e --- /dev/null +++ b/src/cli/argv.test.ts @@ -0,0 +1,194 @@ +import { describe, expect, test } from "bun:test"; +import { + detectCliEnvironment, + getParseOptions, + getSubcommand, + getArgsAfterSplice, + isCommandAvailable, + isElectronLaunchArg, +} from "./argv"; + +describe("detectCliEnvironment", () => { + test("bun/node: firstArgIndex=2", () => { + const env = detectCliEnvironment({}, undefined); + expect(env).toEqual({ + isElectron: false, + isPackagedElectron: false, + firstArgIndex: 2, + }); + }); + + test("electron dev: firstArgIndex=2", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + expect(env).toEqual({ + isElectron: true, + isPackagedElectron: false, + firstArgIndex: 2, + }); + }); + + test("packaged electron: firstArgIndex=1", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, undefined); + expect(env).toEqual({ + isElectron: true, + isPackagedElectron: true, + firstArgIndex: 1, + }); + }); +}); + +describe("getParseOptions", () => { + test("returns node for bun/node", () => { + const env = detectCliEnvironment({}, undefined); + expect(getParseOptions(env)).toEqual({ from: "node" }); + }); + + test("returns node for electron dev", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + expect(getParseOptions(env)).toEqual({ from: "node" }); + }); + + test("returns electron for packaged", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, undefined); + expect(getParseOptions(env)).toEqual({ from: "electron" }); + }); +}); + +describe("getSubcommand", () => { + test("bun: gets arg at index 2", () => { + const env = detectCliEnvironment({}, undefined); + expect(getSubcommand(["bun", "script.ts", "server", "--help"], env)).toBe("server"); + }); + + test("electron dev: gets arg at index 2", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + expect(getSubcommand(["electron", ".", "api", "--help"], env)).toBe("api"); + }); + + test("packaged: gets arg at index 1", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, undefined); + expect(getSubcommand(["mux", "server", "-p", "3001"], env)).toBe("server"); + }); + + test("returns undefined when no subcommand", () => { + const env = detectCliEnvironment({}, undefined); + expect(getSubcommand(["bun", "script.ts"], env)).toBeUndefined(); + }); +}); + +describe("getArgsAfterSplice", () => { + // These tests simulate what happens AFTER index.ts splices out the subcommand name + // Original argv: ["electron", ".", "api", "--help"] + // After splice: ["electron", ".", "--help"] + + test("bun: returns args after firstArgIndex", () => { + const env = detectCliEnvironment({}, undefined); + // Simulates: bun script.ts api --help -> after splice -> bun script.ts --help + const argvAfterSplice = ["bun", "script.ts", "--help"]; + expect(getArgsAfterSplice(argvAfterSplice, env)).toEqual(["--help"]); + }); + + test("electron dev: returns args after firstArgIndex", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + // Simulates: electron . api --help -> after splice -> electron . --help + const argvAfterSplice = ["electron", ".", "--help"]; + expect(getArgsAfterSplice(argvAfterSplice, env)).toEqual(["--help"]); + }); + + test("packaged electron: returns args after firstArgIndex", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, undefined); + // Simulates: ./mux api --help -> after splice -> ./mux --help + const argvAfterSplice = ["./mux", "--help"]; + expect(getArgsAfterSplice(argvAfterSplice, env)).toEqual(["--help"]); + }); + + test("handles multiple args", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + // Simulates: electron . server -p 3001 --host 0.0.0.0 + // After splice: electron . -p 3001 --host 0.0.0.0 + const argvAfterSplice = ["electron", ".", "-p", "3001", "--host", "0.0.0.0"]; + expect(getArgsAfterSplice(argvAfterSplice, env)).toEqual(["-p", "3001", "--host", "0.0.0.0"]); + }); + + test("returns empty array when no args after splice", () => { + const env = detectCliEnvironment({}, undefined); + // Simulates: bun script.ts server -> after splice -> bun script.ts + const argvAfterSplice = ["bun", "script.ts"]; + expect(getArgsAfterSplice(argvAfterSplice, env)).toEqual([]); + }); +}); + +describe("isElectronLaunchArg", () => { + test("returns false for bun/node (not Electron)", () => { + const env = detectCliEnvironment({}, undefined); + expect(isElectronLaunchArg(".", env)).toBe(false); + expect(isElectronLaunchArg("--help", env)).toBe(false); + }); + + test("returns false for packaged Electron (flags are real CLI args)", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, undefined); + expect(isElectronLaunchArg("--help", env)).toBe(false); + expect(isElectronLaunchArg(".", env)).toBe(false); + }); + + test("returns true for '.' in electron dev mode", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + expect(isElectronLaunchArg(".", env)).toBe(true); + }); + + test("returns true for flags in electron dev mode", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + expect(isElectronLaunchArg("--help", env)).toBe(true); + expect(isElectronLaunchArg("--inspect", env)).toBe(true); + expect(isElectronLaunchArg("-v", env)).toBe(true); + }); + + test("returns false for real subcommands in electron dev mode", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + expect(isElectronLaunchArg("server", env)).toBe(false); + expect(isElectronLaunchArg("api", env)).toBe(false); + expect(isElectronLaunchArg("desktop", env)).toBe(false); + }); + + test("returns false for undefined subcommand", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + expect(isElectronLaunchArg(undefined, env)).toBe(false); + }); +}); + +describe("isCommandAvailable", () => { + test("run is available in bun/node", () => { + const env = detectCliEnvironment({}, undefined); + expect(isCommandAvailable("run", env)).toBe(true); + }); + + test("run is NOT available in electron dev", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, true); + expect(isCommandAvailable("run", env)).toBe(false); + }); + + test("run is NOT available in packaged electron", () => { + const env = detectCliEnvironment({ electron: "33.0.0" }, undefined); + expect(isCommandAvailable("run", env)).toBe(false); + }); + + test("server is available everywhere", () => { + expect(isCommandAvailable("server", detectCliEnvironment({}, undefined))).toBe(true); + expect(isCommandAvailable("server", detectCliEnvironment({ electron: "33.0.0" }, true))).toBe( + true + ); + expect( + isCommandAvailable("server", detectCliEnvironment({ electron: "33.0.0" }, undefined)) + ).toBe(true); + }); + + test("api is available everywhere", () => { + expect(isCommandAvailable("api", detectCliEnvironment({}, undefined))).toBe(true); + expect(isCommandAvailable("api", detectCliEnvironment({ electron: "33.0.0" }, true))).toBe( + true + ); + expect(isCommandAvailable("api", detectCliEnvironment({ electron: "33.0.0" }, undefined))).toBe( + true + ); + }); +}); diff --git a/src/cli/argv.ts b/src/cli/argv.ts new file mode 100644 index 0000000000..6d6963465d --- /dev/null +++ b/src/cli/argv.ts @@ -0,0 +1,102 @@ +/** + * CLI environment detection for correct argv parsing across: + * - bun/node direct invocation + * - Electron dev mode (electron .) + * - Packaged Electron app (./mux.AppImage) + */ + +export interface CliEnvironment { + /** Running under Electron runtime */ + isElectron: boolean; + /** Running as packaged Electron app (not dev mode) */ + isPackagedElectron: boolean; + /** Index of first user argument in process.argv */ + firstArgIndex: number; +} + +/** + * Detect CLI environment from process state. + * + * | Environment | isElectron | defaultApp | firstArgIndex | + * |-------------------|------------|------------|---------------| + * | bun/node | false | undefined | 2 | + * | electron dev | true | true | 2 | + * | packaged electron | true | undefined | 1 | + */ +export function detectCliEnvironment( + versions: Record = process.versions, + defaultApp: boolean | undefined = process.defaultApp +): CliEnvironment { + const isElectron = "electron" in versions; + const isPackagedElectron = isElectron && !defaultApp; + const firstArgIndex = isPackagedElectron ? 1 : 2; + return { isElectron, isPackagedElectron, firstArgIndex }; +} + +/** + * Get Commander parse options for current environment. + * Use with: program.parse(process.argv, getParseOptions()) + */ +export function getParseOptions(env: CliEnvironment = detectCliEnvironment()): { + from: "electron" | "node"; +} { + return { from: env.isPackagedElectron ? "electron" : "node" }; +} + +/** + * Get the subcommand from argv (e.g., "server", "api", "run"). + */ +export function getSubcommand( + argv: string[] = process.argv, + env: CliEnvironment = detectCliEnvironment() +): string | undefined { + return argv[env.firstArgIndex]; +} + +/** + * Get args for a subcommand after the subcommand name has been spliced out. + * This is what subcommand handlers (server.ts, api.ts, run.ts) use after + * index.ts removes the subcommand name from process.argv. + * + * @example + * // Original: ["electron", ".", "api", "--help"] + * // After index.ts splices: ["electron", ".", "--help"] + * // getArgsAfterSplice returns: ["--help"] + */ +export function getArgsAfterSplice( + argv: string[] = process.argv, + env: CliEnvironment = detectCliEnvironment() +): string[] { + return argv.slice(env.firstArgIndex); +} + +/** + * Check if the subcommand is an Electron launch arg (not a real CLI command). + * In dev mode (electron --inspect .), argv may contain flags or "." before the subcommand. + * These should trigger desktop launch, not CLI processing. + */ +export function isElectronLaunchArg( + subcommand: string | undefined, + env: CliEnvironment = detectCliEnvironment() +): boolean { + if (env.isPackagedElectron || !env.isElectron) { + return false; + } + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional: false from startsWith should still check "." + return subcommand?.startsWith("-") || subcommand === "."; +} + +/** + * Check if a command is available in the current environment. + * The "run" command requires bun/node - it's not bundled in Electron. + */ +export function isCommandAvailable( + command: string, + env: CliEnvironment = detectCliEnvironment() +): boolean { + if (command === "run") { + // run.ts is only available in bun/node, not bundled in Electron (dev or packaged) + return !env.isElectron; + } + return true; +} diff --git a/src/cli/index.ts b/src/cli/index.ts index d063e4d511..a321cd852e 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -22,12 +22,16 @@ */ import { Command } from "commander"; import { VERSION } from "../version"; +import { + detectCliEnvironment, + getParseOptions, + getSubcommand, + isCommandAvailable, + isElectronLaunchArg, +} from "./argv"; -const isElectron = "electron" in process.versions; -// Only packaged Electron has args at index 1; all others (bun, node, electron .) use index 2 -const isPackagedElectron = isElectron && !process.defaultApp; -const firstArgIndex = isPackagedElectron ? 1 : 2; -const subcommand = process.argv[firstArgIndex]; +const env = detectCliEnvironment(); +const subcommand = getSubcommand(process.argv, env); function launchDesktop(): void { // eslint-disable-next-line @typescript-eslint/no-require-imports @@ -35,26 +39,22 @@ function launchDesktop(): void { } // Route known subcommands to their dedicated entry points (each has its own Commander instance) -// In dev mode (electron --inspect .), argv may contain flags or "." before the subcommand. -// Only treat these as "launch args" in dev mode; in packaged apps, --help is a legitimate CLI flag. -const isElectronLaunchArg = - process.defaultApp && (subcommand?.startsWith("-") || subcommand === "."); if (subcommand === "run") { - if (isPackagedElectron) { + if (!isCommandAvailable("run", env)) { console.error("The 'run' command is only available via the CLI (bun mux run)."); - console.error("It is not included in the packaged Electron app."); + console.error("It is not bundled in Electron."); process.exit(1); } - process.argv.splice(firstArgIndex, 1); // Remove "run" since run.ts defines .name("mux run") + process.argv.splice(env.firstArgIndex, 1); // Remove "run" since run.ts defines .name("mux run") // eslint-disable-next-line @typescript-eslint/no-require-imports require("./run"); } else if (subcommand === "server") { - process.argv.splice(firstArgIndex, 1); + process.argv.splice(env.firstArgIndex, 1); // eslint-disable-next-line @typescript-eslint/no-require-imports require("./server"); } else if (subcommand === "api") { - process.argv.splice(firstArgIndex, 1); + process.argv.splice(env.firstArgIndex, 1); // Must use native import() to load ESM module - trpc-cli requires ESM with top-level await. // Using Function constructor prevents TypeScript from converting this to require(). // The .mjs extension is critical for Node.js to treat it as ESM. @@ -62,7 +62,7 @@ if (subcommand === "run") { void new Function("return import('./api.mjs')")(); } else if ( subcommand === "desktop" || - (isElectron && (subcommand === undefined || isElectronLaunchArg)) + (env.isElectron && (subcommand === undefined || isElectronLaunchArg(subcommand, env))) ) { // Explicit `mux desktop`, or Electron runtime with no subcommand / Electron launch args launchDesktop(); @@ -85,8 +85,8 @@ if (subcommand === "run") { .version(`${gitDescribe} (${gitCommit})`, "-v, --version"); // Register subcommand stubs for help display (actual implementations are above) - // `run` is only available via bun/node CLI, not bundled in packaged Electron app - if (!isPackagedElectron) { + // `run` is only available via bun/node CLI, not bundled in Electron + if (isCommandAvailable("run", env)) { program.command("run").description("Run a one-off agent task"); } program.command("server").description("Start the HTTP/WebSocket ORPC server"); @@ -94,8 +94,8 @@ if (subcommand === "run") { program .command("desktop") .description( - isElectron ? "Launch the desktop app" : "Launch the desktop app (requires Electron)" + env.isElectron ? "Launch the desktop app" : "Launch the desktop app (requires Electron)" ); - program.parse(); + program.parse(process.argv, getParseOptions(env)); } diff --git a/src/cli/run.ts b/src/cli/run.ts index f61fc0b4d7..b31ca01bf9 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -43,9 +43,7 @@ import { parseRuntimeModeAndHost, RUNTIME_MODE } from "@/common/types/runtime"; import assert from "@/common/utils/assert"; import parseDuration from "parse-duration"; import { log, type LogLevel } from "@/node/services/log"; - -// Only packaged Electron needs 'electron' parse mode; bun/node/electron-dev all use 'node' -const isPackagedElectron = "electron" in process.versions && !process.defaultApp; +import { getParseOptions } from "./argv"; type CLIMode = "plan" | "exec"; @@ -215,7 +213,7 @@ Examples: ` ); -program.parse(process.argv, { from: isPackagedElectron ? "electron" : "node" }); +program.parse(process.argv, getParseOptions()); interface CLIOptions { dir: string; diff --git a/src/cli/server.ts b/src/cli/server.ts index b45e3fd657..3040a37aba 100644 --- a/src/cli/server.ts +++ b/src/cli/server.ts @@ -11,9 +11,7 @@ import { Command } from "commander"; import { validateProjectPath } from "@/node/utils/pathUtils"; import { createOrpcServer } from "@/node/orpc/server"; import type { ORPCContext } from "@/node/orpc/context"; - -// Only packaged Electron needs 'electron' parse mode; bun/node/electron-dev all use 'node' -const isPackagedElectron = "electron" in process.versions && !process.defaultApp; +import { getParseOptions } from "./argv"; const program = new Command(); program @@ -24,7 +22,7 @@ program .option("--auth-token ", "optional bearer token for HTTP/WS auth") .option("--ssh-host ", "SSH hostname/alias for editor deep links (e.g., devbox)") .option("--add-project ", "add and open project at the specified path (idempotent)") - .parse(process.argv, { from: isPackagedElectron ? "electron" : "node" }); + .parse(process.argv, getParseOptions()); const options = program.opts(); const HOST = options.host as string;