Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion src/cli/argv.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from "bun:test";
import {
CLI_GLOBAL_FLAGS,
detectCliEnvironment,
getParseOptions,
getSubcommand,
Expand All @@ -8,6 +9,19 @@ import {
isElectronLaunchArg,
} from "./argv";

describe("CLI_GLOBAL_FLAGS", () => {
test("contains expected help and version flags", () => {
expect(CLI_GLOBAL_FLAGS).toContain("--help");
expect(CLI_GLOBAL_FLAGS).toContain("-h");
expect(CLI_GLOBAL_FLAGS).toContain("--version");
expect(CLI_GLOBAL_FLAGS).toContain("-v");
});

test("has exactly 4 flags", () => {
expect(CLI_GLOBAL_FLAGS).toHaveLength(4);
});
});

describe("detectCliEnvironment", () => {
test("bun/node: firstArgIndex=2", () => {
const env = detectCliEnvironment({}, undefined);
Expand Down Expand Up @@ -123,11 +137,26 @@ describe("isElectronLaunchArg", () => {
const env = detectCliEnvironment({}, undefined);
expect(isElectronLaunchArg(".", env)).toBe(false);
expect(isElectronLaunchArg("--help", env)).toBe(false);
expect(isElectronLaunchArg("--no-sandbox", env)).toBe(false);
});

test("returns true for Electron flags in packaged mode (--no-sandbox, etc.)", () => {
const env = detectCliEnvironment({ electron: "33.0.0" }, undefined);
expect(isElectronLaunchArg("--no-sandbox", env)).toBe(true);
expect(isElectronLaunchArg("--disable-gpu", env)).toBe(true);
expect(isElectronLaunchArg("--enable-logging", env)).toBe(true);
});

test("returns false for packaged Electron (flags are real CLI args)", () => {
test("returns false for CLI flags in packaged mode (--help, --version)", () => {
const env = detectCliEnvironment({ electron: "33.0.0" }, undefined);
expect(isElectronLaunchArg("--help", env)).toBe(false);
expect(isElectronLaunchArg("-h", env)).toBe(false);
expect(isElectronLaunchArg("--version", env)).toBe(false);
expect(isElectronLaunchArg("-v", env)).toBe(false);
});

test("returns false for '.' in packaged mode", () => {
const env = detectCliEnvironment({ electron: "33.0.0" }, undefined);
expect(isElectronLaunchArg(".", env)).toBe(false);
});

Expand All @@ -154,6 +183,11 @@ describe("isElectronLaunchArg", () => {
const env = detectCliEnvironment({ electron: "33.0.0" }, true);
expect(isElectronLaunchArg(undefined, env)).toBe(false);
});

test("returns false for undefined subcommand in packaged mode", () => {
const env = detectCliEnvironment({ electron: "33.0.0" }, undefined);
expect(isElectronLaunchArg(undefined, env)).toBe(false);
});
});

describe("isCommandAvailable", () => {
Expand Down
29 changes: 23 additions & 6 deletions src/cli/argv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,37 @@ export function getArgsAfterSplice(
return argv.slice(env.firstArgIndex);
}

/**
* Global CLI flags that should show help/version, not launch desktop.
* Commander auto-adds --help/-h. We add --version/-v in index.ts.
*
* IMPORTANT: If you add new global flags to the CLI in index.ts,
* add them here too so packaged Electron routes them correctly.
*/
export const CLI_GLOBAL_FLAGS = ["--help", "-h", "--version", "-v"] as const;

/**
* 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.
* In dev mode, "." or flags before the app path should launch desktop.
* In packaged mode, Electron flags (--no-sandbox, etc.) should launch desktop,
* but CLI flags (--help, --version) should show CLI help.
*/
export function isElectronLaunchArg(
subcommand: string | undefined,
env: CliEnvironment = detectCliEnvironment()
): boolean {
if (env.isPackagedElectron || !env.isElectron) {
return false;
if (!env.isElectron) return false;

if (env.isPackagedElectron) {
// In packaged: flags that aren't CLI flags should launch desktop
return Boolean(
subcommand?.startsWith("-") &&
!CLI_GLOBAL_FLAGS.includes(subcommand as (typeof CLI_GLOBAL_FLAGS)[number])
);
}
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- intentional: false from startsWith should still check "."
return subcommand?.startsWith("-") || subcommand === ".";

// Dev mode: "." or any flag launches desktop
return subcommand === "." || subcommand?.startsWith("-") === true;
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import { Command } from "commander";
import { VERSION } from "../version";
import {
CLI_GLOBAL_FLAGS,
detectCliEnvironment,
getParseOptions,
getSubcommand,
Expand Down Expand Up @@ -79,11 +80,23 @@ if (subcommand === "run") {
const gitCommit =
typeof versionRecord.git_commit === "string" ? versionRecord.git_commit : "unknown";

// Global flags are defined in CLI_GLOBAL_FLAGS (argv.ts) for routing logic.
// Commander auto-adds --help/-h. We define --version/-v below.
program
.name("mux")
.description("Mux - AI agent orchestration")
.version(`${gitDescribe} (${gitCommit})`, "-v, --version");

// Sanity check: ensure version flags match CLI_GLOBAL_FLAGS
if (process.env.NODE_ENV !== "production") {
const versionFlags = ["-v", "--version"];
for (const flag of versionFlags) {
if (!CLI_GLOBAL_FLAGS.includes(flag as (typeof CLI_GLOBAL_FLAGS)[number])) {
console.warn(`Warning: version flag "${flag}" not in CLI_GLOBAL_FLAGS`);
}
}
}

// Register subcommand stubs for help display (actual implementations are above)
// `run` is only available via bun/node CLI, not bundled in Electron
if (isCommandAvailable("run", env)) {
Expand Down