Skip to content

Commit d02a10a

Browse files
committed
refactor: use Commander.js for top-level CLI routing
- Replace manual argv parsing with proper Commander.js subcommands - Add --version flag with proper version info - Subcommands (run, server) now properly routed via executableFile - Default action launches desktop app when no subcommand given - Update docs to reflect --version flag instead of subcommand
1 parent b803ecc commit d02a10a

File tree

6 files changed

+260
-94
lines changed

6 files changed

+260
-94
lines changed

docs/cli.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Command Line Interface
22

3-
Mux provides a CLI for running agent sessions without opening the desktop app.
3+
Mux provides a CLI for running one-off agent tasks without the desktop app. Unlike the interactive desktop experience, `mux run` executes a single request to completion and exits.
44

55
## `mux run`
66

7-
Run an agent session in any directory:
7+
Execute a one-off agent task:
88

99
```bash
1010
# Basic usage - run in current directory
@@ -16,9 +16,6 @@ mux run --dir /path/to/project "Add authentication"
1616
# Use SSH runtime
1717
mux run --runtime "ssh user@myserver" "Deploy changes"
1818

19-
# Plan mode (proposes a plan, then auto-executes)
20-
mux run --mode plan "Refactor the auth module"
21-
2219
# Pipe instructions via stdin
2320
echo "Add logging to all API endpoints" | mux run
2421

@@ -67,9 +64,6 @@ mux run -r "ssh dev@staging.example.com" -d /app "Update dependencies"
6764

6865
# Scripted usage with timeout
6966
mux run --json --timeout 5m "Generate API documentation" > output.jsonl
70-
71-
# Plan first, then execute
72-
mux run --mode plan "Migrate from REST to GraphQL"
7367
```
7468

7569
## `mux server`
@@ -87,11 +81,21 @@ Options:
8781
- `--auth-token <token>` - Optional bearer token for authentication
8882
- `--add-project <path>` - Add and open project at the specified path
8983

90-
## `mux version`
84+
## `mux desktop`
85+
86+
Launch the desktop app. This is automatically invoked when running the packaged app or via `electron .`:
87+
88+
```bash
89+
mux desktop
90+
```
91+
92+
Note: Requires Electron. When running `mux` with no arguments under Electron, the desktop app launches automatically.
93+
94+
## `mux --version`
9195

9296
Print the version and git commit:
9397

9498
```bash
95-
mux version
96-
# mux v0.8.4 (abc123)
99+
mux --version
100+
# v0.8.4 (abc123)
97101
```

src/cli/index.ts

Lines changed: 47 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,60 @@
11
#!/usr/bin/env node
2-
2+
/**
3+
* Mux CLI entry point.
4+
*
5+
* LAZY LOADING REQUIREMENT:
6+
* We manually route subcommands before calling program.parse() to avoid
7+
* eagerly importing heavy modules. The desktop app imports Electron, which
8+
* fails when running CLI commands in non-GUI environments. Subcommands like
9+
* `run` and `server` import the AI SDK which has significant startup cost.
10+
*
11+
* By checking argv[2] first, we only load the code path actually needed.
12+
*
13+
* ELECTRON DETECTION:
14+
* When run via `electron .` or as a packaged app, Electron sets process.versions.electron.
15+
* In that case, we launch the desktop app automatically. When run via `bun` or `node`,
16+
* we show CLI help instead.
17+
*/
318
import { Command } from "commander";
419
import { VERSION } from "../version";
520

6-
<<<<<<< HEAD
7-
const program = new Command();
8-
9-
program
10-
.name("mux")
11-
.description("mux - coder multiplexer")
12-
.version(`mux ${VERSION.git_describe} (${VERSION.git_commit})`, "-v, --version");
13-
14-
// Subcommands with their own CLI parsers - disable help interception so --help passes through
15-
program
16-
.command("server")
17-
.description("Start the HTTP/WebSocket oRPC server")
18-
.helpOption(false)
19-
.allowUnknownOption()
20-
.allowExcessArguments()
21-
.action(() => {
22-
process.argv.splice(2, 1);
23-
// eslint-disable-next-line @typescript-eslint/no-require-imports
24-
require("./server");
25-
});
26-
27-
program
28-
.command("api")
29-
.description("Interact with the mux API via a running server")
30-
.helpOption(false)
31-
.allowUnknownOption()
32-
.allowExcessArguments()
33-
.action(() => {
34-
process.argv.splice(2, 1);
35-
// eslint-disable-next-line @typescript-eslint/no-require-imports
36-
require("./api");
37-
});
21+
const subcommand = process.argv[2];
22+
const isElectron = "electron" in process.versions;
3823

39-
program
40-
.command("version")
41-
.description("Show version information")
42-
.action(() => {
43-
console.log(`mux ${VERSION.git_describe} (${VERSION.git_commit})`);
44-
});
45-
46-
// Default action: launch desktop app when no subcommand given
47-
program.action(() => {
48-
||||||| parent of 0f258d5fc (🤖 feat: add first-class `mux run` CLI command)
49-
if (subcommand === "server") {
50-
// Remove 'server' from args since main-server doesn't expect it as a positional argument.
51-
process.argv.splice(2, 1);
24+
function launchDesktop(): void {
5225
// eslint-disable-next-line @typescript-eslint/no-require-imports
53-
require("./server");
54-
} else if (subcommand === "version") {
26+
require("../desktop/main");
27+
}
28+
29+
// Route known subcommands to their dedicated entry points (each has its own Commander instance)
30+
if (subcommand === "run") {
31+
process.argv.splice(2, 1); // Remove "run" since run.ts defines .name("mux run")
5532
// eslint-disable-next-line @typescript-eslint/no-require-imports
56-
const { VERSION } = require("../version") as {
57-
VERSION: { git_describe: string; git_commit: string };
58-
};
59-
console.log(`mux ${VERSION.git_describe} (${VERSION.git_commit})`);
60-
} else {
61-
=======
62-
if (subcommand === "server") {
63-
// Remove 'server' from args since main-server doesn't expect it as a positional argument.
33+
require("./run");
34+
} else if (subcommand === "server") {
6435
process.argv.splice(2, 1);
6536
// eslint-disable-next-line @typescript-eslint/no-require-imports
6637
require("./server");
67-
} else if (subcommand === "run") {
68-
// Remove 'run' from args since run.ts uses Commander which handles its own parsing
38+
} else if (subcommand === "api") {
6939
process.argv.splice(2, 1);
7040
// eslint-disable-next-line @typescript-eslint/no-require-imports
71-
require("./run");
72-
} else if (subcommand === "version") {
73-
// eslint-disable-next-line @typescript-eslint/no-require-imports
74-
const { VERSION } = require("../version") as {
75-
VERSION: { git_describe: string; git_commit: string };
76-
};
77-
console.log(`mux ${VERSION.git_describe} (${VERSION.git_commit})`);
41+
require("./api");
42+
} else if (subcommand === "desktop" || (subcommand === undefined && isElectron)) {
43+
// Explicit `mux desktop` or no args when running under Electron
44+
launchDesktop();
7845
} else {
79-
>>>>>>> 0f258d5fc (🤖 feat: add first-class `mux run` CLI command)
80-
// eslint-disable-next-line @typescript-eslint/no-require-imports
81-
require("../desktop/main");
82-
});
83-
84-
program.parse();
46+
// No subcommand (non-Electron), flags (--help, --version), or unknown commands
47+
const program = new Command();
48+
program
49+
.name("mux")
50+
.description("Mux - AI agent orchestration")
51+
.version(`${VERSION.git_describe} (${VERSION.git_commit})`, "-v, --version");
52+
53+
// Register subcommand stubs for help display (actual implementations are above)
54+
program.command("run").description("Run a one-off agent task");
55+
program.command("server").description("Start the HTTP/WebSocket ORPC server");
56+
program.command("api").description("Interact with the mux API via a running server");
57+
program.command("desktop").description("Launch the desktop app (requires Electron)");
58+
59+
program.parse();
60+
}

src/cli/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { ORPCContext } from "@/node/orpc/context";
1313

1414
const program = new Command();
1515
program
16-
.name("mux-server")
16+
.name("mux server")
1717
.description("HTTP/WebSocket ORPC server for mux")
1818
.option("-h, --host <host>", "bind to specific host", "localhost")
1919
.option("-p, --port <port>", "bind to specific port", "3000")

src/node/services/mock/scenarios/slashCommands.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,26 +65,26 @@ const modelStatusTurn: ScenarioTurn = {
6565
kind: "stream-start",
6666
delay: 0,
6767
messageId: "msg-slash-model-status",
68-
model: "anthropic:claude-opus-4-5",
68+
model: "anthropic:claude-sonnet-4-5",
6969
},
7070
{
7171
kind: "stream-delta",
7272
delay: STREAM_BASE_DELAY,
73-
text: "Claude Opus 4.5 is now responding with enhanced reasoning capacity.",
73+
text: "Claude Sonnet 4.5 is now responding with standard reasoning capacity.",
7474
},
7575
{
7676
kind: "stream-end",
7777
delay: STREAM_BASE_DELAY * 2,
7878
metadata: {
79-
model: "anthropic:claude-opus-4-5",
79+
model: "anthropic:claude-sonnet-4-5",
8080
inputTokens: 70,
8181
outputTokens: 54,
8282
systemMessageTokens: 12,
8383
},
8484
parts: [
8585
{
8686
type: "text",
87-
text: "I'm responding as Claude Opus 4.5, which you selected via /model opus. Let me know how to proceed.",
87+
text: "I'm responding as Claude Sonnet 4.5, which you selected via /model sonnet. Let me know how to proceed.",
8888
},
8989
],
9090
},

0 commit comments

Comments
 (0)