Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fb28764
Add Codex app launch command
Loongphy Apr 30, 2026
9fd5d59
Add persistent Codex app CLI patch
Loongphy May 1, 2026
1106ff5
Guard persistent Codex app CLI patch by version
Loongphy May 2, 2026
13a3c64
Merge remote-tracking branch 'origin/main' into app-command
Loongphy May 14, 2026
957f492
fix: flatten managed codext app cache
Loongphy May 14, 2026
c110fdf
fix: detach app launches from terminal
Loongphy May 14, 2026
e3b72dc
fix: flatten app patch artifacts
Loongphy May 14, 2026
be0daec
fix: skip unchanged codext downloads
Loongphy May 14, 2026
69834b2
Normalize preview install command [skip ci]
Loongphy May 18, 2026
af2532b
feat: refine app launch controls
Loongphy May 18, 2026
1f2ee65
Merge remote-tracking branch 'origin/main' into app-command
Loongphy May 19, 2026
8342a9c
ci: tolerate pkg pr comment permission errors
Loongphy May 19, 2026
56e15da
ci: remove pkg pr comment normalization
Loongphy May 19, 2026
a63f15e
ci: drop preview issue write permission [skip ci]
Loongphy May 19, 2026
98421f4
feat: remove app status command
Loongphy May 19, 2026
0ac965b
feat: remove app patch commands
Loongphy May 19, 2026
1322e71
Add app command flow improvements
Loongphy May 20, 2026
0dbc473
feat: launch app by app id
Loongphy May 21, 2026
74b6062
feat(app): update launch options and desktop config
Loongphy May 22, 2026
37d0fae
docs: update README
Loongphy May 22, 2026
52e71d4
Merge branch 'main' into app-command
Loongphy May 22, 2026
9a2ffb1
fix: reject unsupported app hosts before CLI resolution
Loongphy May 22, 2026
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
73 changes: 46 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,6 @@

`codex-auth` is a command-line tool for switching Codex accounts.

> [!IMPORTANT]
> For **Codex CLI** and **Codex App** users, switch accounts, then restart the client for the new account to take effect.
>
> If you use the CLI and want seamless automatic account switching without restarting, use the forked [`codext`](https://github.com/Loongphy/codext), an enhanced Codex CLI. Install it with `npm i -g @loongphy/codext` and run `codext`.

## Supported Platforms

`codex-auth` works with these Codex clients:

- Codex CLI
- VS Code extension
- Codex App

For the best experience, install the Codex CLI even if you mainly use the VS Code extension or the App, because it makes adding accounts easier:

```shell
npm install -g @openai/codex
```

After that, you can use `codex-auth login`, or `codex-auth login --device-auth` to sign in and add accounts more easily.

## Install

Install with npm:
Expand All @@ -33,24 +12,37 @@ Install with npm:
npm install -g @loongphy/codex-auth
```

You can also run it without a global install:
You can also run it without a global install:

```shell
npx @loongphy/codex-auth list
```

npm packages currently support Linux x64, Linux arm64, macOS x64, macOS arm64, Windows x64, and Windows arm64.
## Supported Platforms

### Uninstall
`codex-auth` works with these Codex clients:

#### npm
- Codex CLI
- VS Code extension
- Codex App

Remove the npm package:
> [!IMPORTANT]
> For **Codex CLI** and **Codex App** users, switch accounts, then restart the client for the new account to take effect.
>
> If you want seamless automatic account switching without restarting, use the forked [`codext`](https://github.com/Loongphy/codext), an enhanced Codex CLI.
>
> Install it with `npm i -g @loongphy/codext` and run `codext`.
>
> Codex App users can use `codex-auth app`, but it is not stable. See [Details](#codex-app).

Install the Codex CLI even if you mainly use the VS Code extension or the App, because it makes adding accounts easier:

```shell
npm uninstall -g @loongphy/codex-auth
npm install -g @openai/codex
```

After that, you can use `codex-auth login`, or `codex-auth login --device-auth` to sign in and add accounts more easily.

## Commands

Detailed command documentation lives in [docs/commands/README.md](./docs/commands/README.md).
Expand Down Expand Up @@ -96,6 +88,33 @@ codex-auth import /path/to/auth.json --alias personal
codex-auth list --skip-api
```

## Codex App

> [!IMPORTANT]
> The `app` command is **experimental** and may never become a stable feature.
>
> It is designed to enable seamless account switching without restarting the Codex App. By leveraging the `CODEX_CLI_PATH` environment variable, it dynamically injects our managed codext CLI to handle authentication on the fly.
>
> The `app` command is constrained by ongoing changes in the official Codex App and [Codex CLI](https://github.com/openai/codex). It may not always take effect and may also break your app.

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

Support seamless account switching including:

- New Chat
- Restoring or resuming an existing conversation
- Continuing a previously completed, interrupted, or manually stopped conversation

### Uninstall

Remove the npm package:

```shell
npm uninstall -g @loongphy/codex-auth
```

## Q&A

### Why is my usage limit not refreshing?
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
98 changes: 98 additions & 0 deletions docs/commands/app.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# `codex-auth app`

## Usage

```shell
codex-auth app [--id <id>] [--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.
- If the Codex App is already running, `app` prints that status and exits before
resolving or downloading the managed CLI.
- `--id <id>` selects the packaged app to launch. On Windows it accepts an
AppX/MSIX package name such as `OpenAI.Codex` or `Loongphy.Codext`, or a full
AUMID. On macOS it accepts a bundle identifier such as `com.openai.codex`.
- If `--id` is omitted, the default is `OpenAI.Codex` on Windows and
`com.openai.codex` on macOS.
- `--codex-cli-path <path>` is injected as `CODEX_CLI_PATH` for this launch. Explicit CLI paths must exist. If it is omitted, `app` fetches the latest [`Loongphy/codext`](https://github.com/Loongphy/codext) release metadata, compares it with the managed cached CLI version for the selected platform, 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 desktop setting so the app runs the agent natively and selects the Windows managed CLI.
- `wsl` writes the Windows desktop setting so the app runs the agent inside WSL and selects the Linux managed CLI.
- `mac` launches the macOS app directly.
- `--std` resolves the packaged app executable, then starts it with stdout/stderr attached to the current terminal. Use it for debugging app logs; normal launches stay quiet and use the platform GUI launcher.

`app` prints its launch plan and managed CLI resolution to stderr before
starting the GUI launcher. Example output:

```text
Codex App is already running, launch skipped.
```

When the app is not already running, the output continues with launch planning:

```text
- Checking latest https://github.com/Loongphy/codext release...
Downloading Codext CLI for WSL (v0.3.0)
https://github.com/Loongphy/codext/releases/download/.../codext-linux-x64.tar.gz
OK Downloaded Codext CLI for WSL (v0.3.0)

- Environment Configuration ------------------------------------------------
Platform: WSL (auto-detected)
Codex Home: C:\Users\Alice\.codext (explicit)
App ID: Loongphy.Codext (explicit)
CLI Path: C:\Users\Alice\.codext\accounts\codext-cli\codex-linux-x64 (downloaded)
----------------------------------------------------------------------------
Launching Codex App...
```

See [Windows](../windows.md) for Windows console color and character rules.

If `--platform` is omitted, Windows reads
`$CODEX_HOME/config.toml` and uses `wsl` when
`[desktop].runCodexInWindowsSubsystemForLinux` is `true`; otherwise it uses
`win`. macOS defaults to `mac`. Explicit `--platform win|wsl` updates that same
desktop setting before launch.

Default downloaded CLIs are cached directly under:

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

The default download prepares only the selected platform's
[`Loongphy/codext`](https://github.com/Loongphy/codext) asset for the current
CPU architecture, such as `win32-x64`, `linux-x64`, `darwin-x64`, or
`darwin-arm64`.

Windows App launching is handled by the Windows `codex-auth.exe` build. Normal
launch resolves the package name or AUMID and opens `shell:AppsFolder\<AUMID>`.
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 app is opened with its bundle identifier. 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.

## Validation Errors

App launch validation reports every configured option issue it can detect
before printing the launch plan. New option validation should follow this
format:

```text
ERROR: --id: App ID does not exist
"OpenAI.Codex"

ERROR: --codex-cli-path: Path does not exist
"C:\Program Files\WindowsApps\OpenAI.Codext_26.519.2081.0_x64__fzsqvsr4xv3kw\app\Codex.exe"
```
59 changes: 59 additions & 0 deletions docs/windows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Windows

## CLI output

Windows console hosts vary in how they handle UTF-8 text and ANSI escape
sequences. `codex-auth` keeps Windows CLI output conservative so PowerShell,
Windows Terminal, `cmd.exe`, and CI logs stay readable.

### Color

- Color is enabled only for TTY output.
- `NO_COLOR` disables color.
- On Windows, ANSI color is emitted only after
`ENABLE_VIRTUAL_TERMINAL_PROCESSING` is already enabled or can be enabled for
the target console handle.
- If virtual-terminal processing cannot be verified, output falls back to plain
text with no ANSI escape sequences.

### Characters

- Windows-facing status markers must be ASCII by default.
- Do not use Unicode status glyphs such as check marks, warning signs, bullets,
arrows, or box-drawing characters in Windows default output.
- Unicode may be used for non-Windows output when it is already part of an
established command style.

Recommended Windows status markers:

```text
Codex App is already running, launch skipped.
- Checking latest https://github.com/Loongphy/codext release...
Downloading Codext CLI for WSL (v0.3.0)
OK Downloaded Codext CLI for WSL (v0.3.0)
```

### App command examples

Already running:

```text
Codex App is already running, launch skipped.
```

Launch with a managed CLI download:

```text
- Checking latest https://github.com/Loongphy/codext release...
Downloading Codext CLI for WSL (v0.3.0)
https://github.com/Loongphy/codext/releases/download/.../codext-linux-x64.tar.gz
OK Downloaded Codext CLI for WSL (v0.3.0)

- Environment Configuration ------------------------------------------------
Platform: WSL (auto-detected)
Codex Home: C:\Users\Loong\.codext (explicit)
App ID: Loongphy.Codext (explicit)
CLI Path: C:\Users\Loong\.codext\accounts\codext-cli\codex-linux-x64 (downloaded)
----------------------------------------------------------------------------
Launching Codex App...
```
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, "--id")) {
if (i + 1 >= args.len) return common.usageErrorResult(allocator, .app, "missing value for `--id`.", .{});
if (opts.app_id != null) return common.usageErrorResult(allocator, .app, "duplicate `--id` for `app`.", .{});
i += 1;
opts.app_id = 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;
}
Loading
Loading