Skip to content

Commit b8d7a90

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 44d5183 commit b8d7a90

File tree

5 files changed

+35
-42
lines changed

5 files changed

+35
-42
lines changed

docs/cli.md

Lines changed: 5 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,11 @@ 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 --version`
9185

9286
Print the version and git commit:
9387

9488
```bash
95-
mux version
96-
# mux v0.8.4 (abc123)
89+
mux --version
90+
# v0.8.4 (abc123)
9791
```

src/cli/index.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
#!/usr/bin/env node
2+
import { Command } from "commander";
3+
import { VERSION } from "../version";
24

3-
const subcommand = process.argv.length > 2 ? process.argv[2] : null;
5+
const program = new Command();
46

5-
if (subcommand === "server") {
6-
// Remove 'server' from args since main-server doesn't expect it as a positional argument.
7-
process.argv.splice(2, 1);
8-
// eslint-disable-next-line @typescript-eslint/no-require-imports
9-
require("./server");
10-
} else if (subcommand === "run") {
11-
// Remove 'run' from args since run.ts uses Commander which handles its own parsing
12-
process.argv.splice(2, 1);
13-
// eslint-disable-next-line @typescript-eslint/no-require-imports
14-
require("./run");
15-
} else if (subcommand === "version") {
16-
// eslint-disable-next-line @typescript-eslint/no-require-imports
17-
const { VERSION } = require("../version") as {
18-
VERSION: { git_describe: string; git_commit: string };
19-
};
20-
console.log(`mux ${VERSION.git_describe} (${VERSION.git_commit})`);
21-
} else {
7+
program
8+
.name("mux")
9+
.description("Mux - AI agent orchestration")
10+
.version(`${VERSION.git_describe} (${VERSION.git_commit})`, "-v, --version");
11+
12+
program
13+
.command("run", "Run a one-off agent session", { executableFile: "./run" })
14+
.command("server", "Start the HTTP/WebSocket ORPC server", { executableFile: "./server" });
15+
16+
// Default to desktop app when no subcommand given
17+
program.action(() => {
2218
// eslint-disable-next-line @typescript-eslint/no-require-imports
2319
require("../desktop/main");
24-
}
20+
});
21+
22+
program.parse();

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
},

tests/e2e/scenarios/slashCommands.spec.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,26 +99,27 @@ test.describe("slash command flows", () => {
9999
await expect(transcript).not.toContainText("Directory listing:");
100100
});
101101

102-
test("slash command /model opus switches models for subsequent turns", async ({ ui, page }) => {
102+
test("slash command /model sonnet switches models for subsequent turns", async ({ ui, page }) => {
103103
await ui.projects.openFirstWorkspace();
104104

105105
const modeToggles = page.locator('[data-component="ChatModeToggles"]');
106+
// Default model is now Opus
107+
await expect(modeToggles.getByText("anthropic:claude-opus-4-5", { exact: true })).toBeVisible();
108+
109+
await ui.chat.sendMessage("/model sonnet");
110+
await ui.chat.expectStatusMessageContains("Model changed to anthropic:claude-sonnet-4-5");
106111
await expect(
107112
modeToggles.getByText("anthropic:claude-sonnet-4-5", { exact: true })
108113
).toBeVisible();
109114

110-
await ui.chat.sendMessage("/model opus");
111-
await ui.chat.expectStatusMessageContains("Model changed to anthropic:claude-opus-4-5");
112-
await expect(modeToggles.getByText("anthropic:claude-opus-4-5", { exact: true })).toBeVisible();
113-
114115
const timeline = await ui.chat.captureStreamTimeline(async () => {
115116
await ui.chat.sendMessage(SLASH_COMMAND_PROMPTS.MODEL_STATUS);
116117
});
117118

118119
const streamStart = timeline.events.find((event) => event.type === "stream-start");
119-
expect(streamStart?.model).toBe("anthropic:claude-opus-4-5");
120+
expect(streamStart?.model).toBe("anthropic:claude-sonnet-4-5");
120121
await ui.chat.expectTranscriptContains(
121-
"Claude Opus 4.5 is now responding with enhanced reasoning capacity."
122+
"Claude Sonnet 4.5 is now responding with standard reasoning capacity."
122123
);
123124
});
124125

0 commit comments

Comments
 (0)