Skip to content
Open
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ Detailed command documentation lives in [docs/commands/README.md](./docs/command
| [`codex-auth export --cpa [<dir>]`](./docs/commands/export.md) | Export CLIProxyAPI token JSON |
| [`codex-auth clean`](./docs/commands/clean.md) | Delete managed backup and stale account files |

### Codex App Launching

| Command | Description |
|---------|-------------|
| [`codex-auth app [--app-path <path>] [--codex-cli-path <path>]`](./docs/commands/app.md) | Launch Codex App with detected defaults, CODEX_HOME, CODEX_CLI_PATH, and platform overrides |

### Configuration

| Command | Description |
Expand Down
1 change: 1 addition & 0 deletions docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ This directory documents command behavior by command. Use `codex-auth <command>
| `alias` | [docs/commands/alias.md](./alias.md) |
| `clean` | [docs/commands/clean.md](./clean.md) |
| `config` | [docs/commands/config.md](./config.md) |
| `app` | [docs/commands/app.md](./app.md) |

## Shared Behavior

Expand Down
61 changes: 61 additions & 0 deletions docs/commands/app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# `codex-auth app`

## Usage

```shell
codex-auth app [--app-path <path>] [--codex-cli-path <path>] [--codex-home <path>] [--platform win|wsl|mac]
```

## Behavior

Launches the official Codex App with per-process environment overrides.

- `codex-auth app` launches the app. There is no `launch` subcommand.
- `--app-path <path>` points to the App executable or an installed package/app directory.
- `--codex-cli-path <path>` is injected as `CODEX_CLI_PATH` for this launch. If it is omitted, `app` fetches the latest Loongphy codext release metadata, compares it with the managed cached CLI version, downloads only when the cached version differs or is missing, and uses that file; it does not reuse an existing `CODEX_CLI_PATH` from the current shell.
- `--codex-home <path>` is injected as `CODEX_HOME` for `app` launches and selects the accounts cache used for managed CLI resolution.
- `--platform win|wsl|mac` selects the app runtime platform:
- `win` writes the Windows global setting so the app runs the agent natively.
- `wsl` writes the Windows global setting so the app runs the agent inside WSL.
- `mac` launches the macOS app directly and does not use the Windows WSL setting.
- `--std` starts the app executable directly with stdout/stderr attached to the current terminal. Use it for debugging app logs; normal launches stay quiet and use the platform GUI launcher.

If `--app-path` is omitted, `CODEX_AUTH_APP_PATH` is used when set; otherwise
the official installed app is auto-detected. On Windows this uses AppX package
lookup for `OpenAI.Codex`. On macOS it checks `/Applications/Codex.app` and
`~/Applications/Codex.app`; the latter is the standard per-user Applications
folder.

If `--platform` is omitted, Windows reads `$CODEX_HOME/.codex-global-state.json`
and uses `wsl` when `runCodexInWindowsSubsystemForLinux` is `true`; otherwise it
uses `win`. macOS defaults to `mac`.

Default downloaded CLIs are cached directly under:

```text
$CODEX_HOME/accounts/codext-cli/codex-<platform>
$CODEX_HOME/accounts/codext-cli/codex-<platform>.version
```

On Windows, the default download prepares both the Windows-native and WSL Linux
Loongphy codext assets for the current CPU architecture, such as `win32-x64`
and `linux-x64`. On macOS, it downloads only the matching macOS asset, such as
`darwin-x64` or `darwin-arm64`.

Windows App launching is handled by the Windows `codex-auth.exe` build. For the
auto-detected app, launch resolves the package AUMID and opens
`shell:AppsFolder\<AUMID>`. Use a Windows app path such as
`C:\Program Files\WindowsApps\...\app\Codex.exe` for `--app-path` only when an
explicit override is needed. The WSL build does not launch Windows App packages.

For Windows-native App launches, `--codex-cli-path` must point to something the Windows
App process can spawn. A WSL command name such as `codex-custom` is not a
Windows executable path.

For macOS App launches, the auto-detected app is opened with bundle identifier
`com.openai.codex`. `--app-path` may point to `/Applications/Codex.app` or the
app bundle path. Bundle paths are opened with `open`; direct executable paths
are not supported for app launch. The packaged macOS app normally uses
`Contents/Resources/codex` directly as its bundled CLI; setting
`--codex-cli-path` injects `CODEX_CLI_PATH` and takes precedence over that
bundled resource.
73 changes: 73 additions & 0 deletions src/cli/commands/app.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const std = @import("std");
const types = @import("../types.zig");
const common = @import("common.zig");

pub fn parse(allocator: std.mem.Allocator, args: []const [:0]const u8) !types.ParseResult {
if (args.len == 0) return parseOptions(allocator, .launch, args);
const first = std.mem.sliceTo(args[0], 0);
if (common.isHelpFlag(first)) return .{ .command = .{ .help = .app } };

return parseOptions(allocator, .launch, args);
}

fn parseOptions(
allocator: std.mem.Allocator,
action: types.AppAction,
args: []const [:0]const u8,
) !types.ParseResult {
var opts = types.AppOptions{ .action = action };
var i: usize = 0;
while (i < args.len) : (i += 1) {
const arg = std.mem.sliceTo(args[i], 0);
if (std.mem.eql(u8, arg, "--")) return common.usageErrorResult(allocator, .app, "`app` does not accept passthrough arguments.", .{});
if (common.isHelpFlag(arg)) return .{ .command = .{ .help = .app } };
if (std.mem.eql(u8, arg, "--app-path")) {
if (i + 1 >= args.len) return common.usageErrorResult(allocator, .app, "missing value for `--app-path`.", .{});
if (opts.app_path != null) return common.usageErrorResult(allocator, .app, "duplicate `--app-path` for `app`.", .{});
i += 1;
opts.app_path = std.mem.sliceTo(args[i], 0);
continue;
}
if (std.mem.eql(u8, arg, "--codex-cli-path")) {
if (i + 1 >= args.len) return common.usageErrorResult(allocator, .app, "missing value for `--codex-cli-path`.", .{});
if (opts.codex_cli_path != null) return common.usageErrorResult(allocator, .app, "duplicate `--codex-cli-path` for `app`.", .{});
i += 1;
opts.codex_cli_path = std.mem.sliceTo(args[i], 0);
continue;
}
if (std.mem.eql(u8, arg, "--codex-home")) {
if (i + 1 >= args.len) return common.usageErrorResult(allocator, .app, "missing value for `--codex-home`.", .{});
if (opts.codex_home != null) return common.usageErrorResult(allocator, .app, "duplicate `--codex-home` for `app`.", .{});
i += 1;
opts.codex_home = std.mem.sliceTo(args[i], 0);
continue;
}
if (std.mem.eql(u8, arg, "--platform")) {
if (i + 1 >= args.len) return common.usageErrorResult(allocator, .app, "missing value for `--platform`.", .{});
if (opts.platform != null) return common.usageErrorResult(allocator, .app, "duplicate `--platform` for `app`.", .{});
i += 1;
const value = std.mem.sliceTo(args[i], 0);
if (std.mem.eql(u8, value, "win")) {
opts.platform = .win;
} else if (std.mem.eql(u8, value, "wsl")) {
opts.platform = .wsl;
} else if (std.mem.eql(u8, value, "mac")) {
opts.platform = .mac;
} else {
return common.usageErrorResult(allocator, .app, "`--platform` must be `win`, `wsl`, or `mac`.", .{});
}
continue;
}
if (std.mem.eql(u8, arg, "--std")) {
if (opts.inherit_stdio) return common.usageErrorResult(allocator, .app, "duplicate `--std` for `app`.", .{});
opts.inherit_stdio = true;
continue;
}
if (std.mem.startsWith(u8, arg, "-")) {
return common.usageErrorResult(allocator, .app, "unknown flag `{s}` for `app`.", .{arg});
}
return common.usageErrorResult(allocator, .app, "unexpected argument `{s}` for `app`.", .{arg});
}

return .{ .command = .{ .app = opts } };
}
3 changes: 3 additions & 0 deletions src/cli/commands/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const std = @import("std");
const types = @import("../types.zig");
const common = @import("common.zig");

const app = @import("app.zig");
const alias = @import("alias.zig");
const clean = @import("clean.zig");
const config = @import("config.zig");
Expand Down Expand Up @@ -47,6 +48,7 @@ pub fn parseArgs(allocator: std.mem.Allocator, args: []const [:0]const u8) !type
if (std.mem.eql(u8, cmd, "alias")) return alias.parse(allocator, args[2..]);
if (std.mem.eql(u8, cmd, "clean")) return clean.parse(allocator, args[2..]);
if (std.mem.eql(u8, cmd, "config")) return config.parse(allocator, args[2..]);
if (std.mem.eql(u8, cmd, "app")) return app.parse(allocator, args[2..]);

return common.usageErrorResult(allocator, .top_level, "unknown command `{s}`.", .{cmd});
}
Expand Down Expand Up @@ -107,5 +109,6 @@ fn helpTopicForName(name: []const u8) ?types.HelpTopic {
if (std.mem.eql(u8, name, "alias")) return .alias;
if (std.mem.eql(u8, name, "clean")) return .clean;
if (std.mem.eql(u8, name, "config")) return .config;
if (std.mem.eql(u8, name, "app")) return .app;
return null;
}
25 changes: 23 additions & 2 deletions src/cli/help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub fn writeHelp(
try writeCommandDetail(out, use_color, "clean background");
try writeCommandSummary(out, use_color, "config", "Manage configuration");
try writeCommandDetail(out, use_color, "config live --interval <seconds>");
try writeCommandSummary(out, use_color, "app", "Launch Codex App with CLI overrides");

try out.writeAll("\n");
if (use_color) try out.writeAll(style.ansi.cyan);
Expand Down Expand Up @@ -131,6 +132,7 @@ fn commandNameForTopic(topic: HelpTopic) []const u8 {
.alias => "alias",
.clean => "clean",
.config => "config",
.app => "app",
};
}

Expand All @@ -146,19 +148,20 @@ fn commandDescriptionForTopic(topic: HelpTopic) []const u8 {
.alias => "Set or clear an account alias by alias, email, display number, or partial query.",
.clean => "Delete backup and stale files under accounts/.",
.config => "Manage live refresh configuration.",
.app => "Launch Codex App with CLI overrides.",
};
}

fn commandHelpHasExamples(topic: HelpTopic) bool {
return switch (topic) {
.import_auth, .export_auth, .switch_account, .remove_account, .alias, .config => true,
.import_auth, .export_auth, .switch_account, .remove_account, .alias, .config, .app => true,
else => false,
};
}

fn commandHelpHasOptions(topic: HelpTopic) bool {
return switch (topic) {
.list, .login, .import_auth, .export_auth, .switch_account, .remove_account, .alias, .config => true,
.list, .login, .import_auth, .export_auth, .switch_account, .remove_account, .alias, .config, .app => true,
else => false,
};
}
Expand Down Expand Up @@ -221,6 +224,9 @@ fn writeUsageLines(out: *std.Io.Writer, topic: HelpTopic) !void {
.config => {
try out.writeAll(" codex-auth config live --interval <seconds>\n");
},
.app => {
try out.writeAll(" codex-auth app [--app-path <path>] [--codex-cli-path <path>] [--codex-home <path>] [--platform win|wsl|mac]\n");
},
}
}

Expand All @@ -236,6 +242,7 @@ pub fn helpCommandForTopic(topic: HelpTopic) []const u8 {
.alias => "codex-auth alias --help",
.clean => "codex-auth clean --help",
.config => "codex-auth config --help",
.app => "codex-auth app --help",
};
}

Expand Down Expand Up @@ -291,6 +298,16 @@ fn writeOptionLines(out: *std.Io.Writer, topic: HelpTopic) !void {
try out.writeAll(" live --interval <seconds>\n");
try out.writeAll(" Set the live TUI refresh interval from 5 to 3600 seconds.\n");
},
.app => {
try out.writeAll(" --app-path <path> Official Codex App executable or install directory.\n");
try out.writeAll(" --codex-cli-path <path>\n");
try out.writeAll(" Value injected or persisted as CODEX_CLI_PATH. Defaults to latest managed Loongphy codext.\n");
try out.writeAll(" --codex-home <path>\n");
try out.writeAll(" Value injected as CODEX_HOME for this launch.\n");
try out.writeAll(" --platform win|wsl|mac\n");
try out.writeAll(" Preselect the app platform. Defaults to the current app setting on Windows and mac on macOS.\n");
try out.writeAll(" --std Run the app executable with stdout/stderr attached to this terminal.\n");
},
else => {},
}
}
Expand Down Expand Up @@ -362,6 +379,10 @@ fn writeExampleLines(out: *std.Io.Writer, topic: HelpTopic) !void {
.config => {
try out.writeAll(" codex-auth config live --interval 60\n");
},
.app => {
try out.writeAll(" codex-auth app\n");
try out.writeAll(" codex-auth app --platform win\n");
},
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/cli/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ pub const LiveOptions = struct {
interval_seconds: u16,
};
pub const ConfigOptions = union(enum) { live: LiveOptions };
pub const AppAction = enum { launch };
pub const AppPlatform = enum { win, wsl, mac };
pub const AppOptions = struct {
action: AppAction,
app_path: ?[]const u8 = null,
codex_cli_path: ?[]const u8 = null,
codex_home: ?[]const u8 = null,
platform: ?AppPlatform = null,
inherit_stdio: bool = false,
};
pub const HelpTopic = enum {
top_level,
list,
Expand All @@ -65,6 +75,7 @@ pub const HelpTopic = enum {
alias,
clean,
config,
app,
};

pub const Command = union(enum) {
Expand All @@ -77,6 +88,7 @@ pub const Command = union(enum) {
alias: AliasOptions,
clean: CleanOptions,
config: ConfigOptions,
app: AppOptions,
version: void,
help: HelpTopic,
};
Expand Down
2 changes: 2 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ const std = @import("std");
const codex_auth = @import("root.zig");

pub fn main(init: std.process.Init.Minimal) !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer std.debug.assert(gpa.deinit() == .ok);
return codex_auth.workflows.main(init);
}
1 change: 1 addition & 0 deletions src/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub const auth = struct {

pub const cli = @import("cli/root.zig");
pub const workflows = @import("workflows/root.zig");
pub const app_workflow = @import("workflows/app.zig");

pub const core = struct {
pub const compat_fs = @import("core/compat_fs.zig");
Expand Down
Loading
Loading