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
18 changes: 18 additions & 0 deletions src/cli/commands/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,27 @@ pub fn parse(allocator: std.mem.Allocator, args: []const [:0]const u8) !types.Pa
if (std.mem.eql(u8, scope, "live")) {
return parseLive(allocator, args[1..]);
}
if (std.mem.eql(u8, scope, "kill")) {
return parseKill(allocator, args[1..]);
}
return common.usageErrorResult(allocator, .config, "unknown config section `{s}`.", .{scope});
}

fn parseKill(allocator: std.mem.Allocator, args: []const [:0]const u8) !types.ParseResult {
if (args.len == 1 and common.isHelpFlag(std.mem.sliceTo(args[0], 0))) {
return .{ .command = .{ .help = .config } };
}
if (args.len != 1) return common.usageErrorResult(allocator, .config, "`config kill` requires `on` or `off`.", .{});
const value = std.mem.sliceTo(args[0], 0);
const enabled = if (std.mem.eql(u8, value, "on"))
true
else if (std.mem.eql(u8, value, "off"))
false
else
return common.usageErrorResult(allocator, .config, "`config kill` requires `on` or `off`.", .{});
return .{ .command = .{ .config = .{ .kill = .{ .enabled = enabled } } } };
}

fn parseLive(allocator: std.mem.Allocator, args: []const [:0]const u8) !types.ParseResult {
if (args.len == 1 and common.isHelpFlag(std.mem.sliceTo(args[0], 0))) {
return .{ .command = .{ .help = .config } };
Expand Down
22 changes: 22 additions & 0 deletions src/cli/commands/switch.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,28 @@ pub fn parse(allocator: std.mem.Allocator, args: []const [:0]const u8) !types.Pa
}
continue;
}
if (std.mem.eql(u8, arg, "--kill")) {
if (opts.kill) |value| {
freeTarget(allocator, opts.target);
if (!value) {
return common.usageErrorResult(allocator, .switch_account, "`--kill` cannot be combined with `--no-kill` for `switch`.", .{});
}
return common.usageErrorResult(allocator, .switch_account, "duplicate `--kill` for `switch`.", .{});
}
opts.kill = true;
continue;
}
if (std.mem.eql(u8, arg, "--no-kill")) {
if (opts.kill) |value| {
freeTarget(allocator, opts.target);
if (value) {
return common.usageErrorResult(allocator, .switch_account, "`--kill` cannot be combined with `--no-kill` for `switch`.", .{});
}
return common.usageErrorResult(allocator, .switch_account, "duplicate `--no-kill` for `switch`.", .{});
}
opts.kill = false;
continue;
}
if (std.mem.eql(u8, arg, "--skip-api")) {
switch (opts.api_mode) {
.default => opts.api_mode = .skip_api,
Expand Down
23 changes: 17 additions & 6 deletions src/cli/help.zig
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ pub fn writeHelp(
try writeCommandSummary(out, use_color, "export [<dir>] [--cpa]", "Export stored account auth files");
try writeCommandSummary(out, use_color, "switch", "Switch the active account");
try writeCommandDetail(out, use_color, "switch -");
try writeCommandDetail(out, use_color, "switch [--live] [--api|--skip-api]");
try writeCommandDetail(out, use_color, "switch <alias|email|display-number|query>");
try writeCommandDetail(out, use_color, "switch [--live] [--api|--skip-api] [--kill|--no-kill]");
try writeCommandDetail(out, use_color, "switch <alias|email|display-number|query> [--kill|--no-kill]");
try writeCommandSummary(out, use_color, "remove", "Remove one or more accounts");
try writeCommandDetail(out, use_color, "remove [--live] [--api|--skip-api]");
try writeCommandDetail(out, use_color, "remove <alias|email|display-number|query>...");
Expand All @@ -59,6 +59,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 writeCommandDetail(out, use_color, "config kill on|off");
try writeCommandSummary(out, use_color, "app", "Launch Codex App with CLI overrides");

try out.writeAll("\n");
Expand Down Expand Up @@ -149,7 +150,7 @@ fn commandDescriptionForTopic(topic: HelpTopic) []const u8 {
.remove_account => "Remove one or more accounts by alias, email, display number, or partial query.",
.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.",
.config => "Manage live refresh and auto-kill configuration.",
.app => "Launch Codex App with CLI overrides.",
};
}
Expand Down Expand Up @@ -208,9 +209,9 @@ fn writeUsageLines(out: *std.Io.Writer, topic: HelpTopic) !void {
try out.writeAll(" codex-auth export --cpa [<dir>]\n");
},
.switch_account => {
try out.writeAll(" codex-auth switch -\n");
try out.writeAll(" codex-auth switch [--live] [--api|--skip-api]\n");
try out.writeAll(" codex-auth switch <alias|email|display-number|query>\n");
try out.writeAll(" codex-auth switch [-] [--kill|--no-kill]\n");
try out.writeAll(" codex-auth switch [--live] [--api|--skip-api] [--kill|--no-kill]\n");
try out.writeAll(" codex-auth switch <alias|email|display-number|query> [--kill|--no-kill]\n");
},
.remove_account => {
try out.writeAll(" codex-auth remove [--live] [--api|--skip-api]\n");
Expand All @@ -227,6 +228,7 @@ fn writeUsageLines(out: *std.Io.Writer, topic: HelpTopic) !void {
},
.config => {
try out.writeAll(" codex-auth config live --interval <seconds>\n");
try out.writeAll(" codex-auth config kill on|off\n");
},
.app => {
try out.writeAll(" codex-auth app [--id <id>] [--codex-cli-path <path>] [--codex-home <path>] [--platform win|wsl|mac]\n");
Expand Down Expand Up @@ -281,6 +283,8 @@ fn writeOptionLines(out: *std.Io.Writer, topic: HelpTopic) !void {
try out.writeAll(" --live Open the live switch UI.\n");
try out.writeAll(" --api Load usage and account data from APIs.\n");
try out.writeAll(" --skip-api Load usage and account data from local data only (may be inaccurate).\n");
try out.writeAll(" --kill Stop all running Codex processes (CLI and GUI) first; only switch if none remain.\n");
try out.writeAll(" --no-kill Skip stopping Codex even when the `auto_kill` setting is on.\n");
try out.writeAll(" <alias|email|display-number|query>\n");
try out.writeAll(" Switch directly when the target resolves to one account.\n");
try out.writeAll(" - Switch to the previous active account.\n");
Expand All @@ -302,6 +306,8 @@ fn writeOptionLines(out: *std.Io.Writer, topic: HelpTopic) !void {
.config => {
try out.writeAll(" live --interval <seconds>\n");
try out.writeAll(" Set the live TUI refresh interval from 5 to 3600 seconds.\n");
try out.writeAll(" kill on|off\n");
try out.writeAll(" Toggle stopping all Codex processes before every switch.\n");
},
.app => {
try out.writeAll(" --id <id> Windows package/AUMID or macOS bundle identifier.\n");
Expand Down Expand Up @@ -361,6 +367,9 @@ fn writeExampleLines(out: *std.Io.Writer, topic: HelpTopic) !void {
try out.writeAll(" codex-auth switch john@example.com\n");
try out.writeAll(" codex-auth switch 02\n");
try out.writeAll(" codex-auth switch work\n");
try out.writeAll(" codex-auth switch --kill\n");
try out.writeAll(" codex-auth switch work --kill\n");
try out.writeAll(" codex-auth switch --no-kill\n");
},
.remove_account => {
try out.writeAll(" codex-auth remove\n");
Expand All @@ -384,6 +393,8 @@ fn writeExampleLines(out: *std.Io.Writer, topic: HelpTopic) !void {
},
.config => {
try out.writeAll(" codex-auth config live --interval 60\n");
try out.writeAll(" codex-auth config kill on\n");
try out.writeAll(" codex-auth config kill off\n");
},
.app => {
try out.writeAll(" codex-auth app\n");
Expand Down
12 changes: 12 additions & 0 deletions src/cli/output.zig
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,18 @@ pub fn printAccountNotFoundErrors(queries: []const []const u8) !void {
try out.flush();
}

pub fn printCodexStillRunningError() !void {
var stderr: io_util.Stderr = undefined;
stderr.init();
const out = stderr.out();
const use_color = stderr.color_enabled;
try writeErrorPrefixTo(out, use_color);
try out.writeAll(" codex is still running; not switching accounts.\n");
try writeHintPrefixTo(out, use_color);
try out.writeAll(" Close all Codex windows and processes, then retry (or switch without `--kill`).\n");
try out.flush();
}

pub fn printSwitchRequiresTtyError() !void {
var stderr: io_util.Stderr = undefined;
stderr.init();
Expand Down
10 changes: 9 additions & 1 deletion src/cli/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub const SwitchOptions = struct {
target: SwitchTarget = .picker,
live: bool = false,
api_mode: ApiMode = .default,
// null = use the registry `auto_kill` setting; true/false = per-run override.
kill: ?bool = null,
};
pub const RemoveOptions = struct {
selectors: [][]const u8,
Expand All @@ -58,7 +60,13 @@ pub const CleanOptions = struct {
pub const LiveOptions = struct {
interval_seconds: u16,
};
pub const ConfigOptions = union(enum) { live: LiveOptions };
pub const KillConfigOptions = struct {
enabled: bool,
};
pub const ConfigOptions = union(enum) {
live: LiveOptions,
kill: KillConfigOptions,
};
pub const AppAction = enum { launch };
pub const AppPlatform = enum { win, wsl, mac };
pub const AppOptions = struct {
Expand Down
3 changes: 3 additions & 0 deletions src/registry/common.zig
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ pub const LiveConfig = struct {
interval_seconds: u16 = default_live_refresh_interval_seconds,
};

pub const default_auto_kill: bool = false;

pub const AccountRecord = struct {
account_key: []u8,
chatgpt_account_id: []u8,
Expand Down Expand Up @@ -139,6 +141,7 @@ pub const Registry = struct {
active_account_activated_at_ms: ?i64,
api: ApiConfig,
live: LiveConfig = defaultLiveConfig(),
auto_kill: bool = default_auto_kill,
accounts: std.ArrayList(AccountRecord),

pub fn deinit(self: *Registry, allocator: std.mem.Allocator) void {
Expand Down
12 changes: 12 additions & 0 deletions src/registry/storage.zig
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ fn loadLegacyRegistryV2(
}

parseRegistryLiveConfig(&reg.live, root_obj);
parseRegistryAutoKill(&reg, root_obj);

for (legacy_accounts.items) |*legacy| {
try migrateLegacyRecord(allocator, codex_home, &reg, legacy_active_email, legacy);
Expand Down Expand Up @@ -352,6 +353,7 @@ fn loadCurrentRegistry(allocator: std.mem.Allocator, root_obj: std.json.ObjectMa
}

parseRegistryLiveConfig(&reg.live, root_obj);
parseRegistryAutoKill(&reg, root_obj);

return reg;
}
Expand Down Expand Up @@ -383,6 +385,7 @@ fn currentLayoutNeedsRewrite(root_obj: std.json.ObjectMap) bool {
return true;
}
if (root_obj.get("previous_active_account_key") == null) return true;
if (root_obj.get("auto_kill") == null) return true;
return root_obj.get("active_account_key") != null and root_obj.get("active_account_activated_at_ms") == null;
}

Expand All @@ -398,6 +401,15 @@ fn parseRegistryLiveConfig(live: *LiveConfig, root_obj: std.json.ObjectMap) void
}
}

fn parseRegistryAutoKill(reg: *Registry, root_obj: std.json.ObjectMap) void {
if (root_obj.get("auto_kill")) |v| {
switch (v) {
.bool => |b| reg.auto_kill = b,
else => {},
}
}
}

fn detectSchemaVersion(root_obj: std.json.ObjectMap) u32 {
return schemaVersionFieldValue(root_obj) orelse if (root_obj.get("active_email") != null) 2 else current_schema_version;
}
Expand Down
2 changes: 2 additions & 0 deletions src/registry/storage_write.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub fn saveRegistry(allocator: std.mem.Allocator, codex_home: []const u8, reg: *
.previous_active_account_key = reg.previous_active_account_key,
.active_account_activated_at_ms = reg.active_account_activated_at_ms,
.interval_seconds = reg.live.interval_seconds,
.auto_kill = reg.auto_kill,
.accounts = reg.accounts.items,
};
var aw: std.Io.Writer.Allocating = .init(allocator);
Expand Down Expand Up @@ -106,5 +107,6 @@ const RegistryOut = struct {
previous_active_account_key: ?[]const u8,
active_account_activated_at_ms: ?i64,
interval_seconds: u16,
auto_kill: bool,
accounts: []const AccountRecord,
};
14 changes: 14 additions & 0 deletions src/workflows/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,23 @@ const registry = @import("../registry/root.zig");
pub fn handleConfig(allocator: std.mem.Allocator, codex_home: []const u8, opts: cli.types.ConfigOptions) !void {
switch (opts) {
.live => |live_opts| try handleLiveCommand(allocator, codex_home, live_opts),
.kill => |kill_opts| try handleKillCommand(allocator, codex_home, kill_opts),
}
}

fn handleKillCommand(allocator: std.mem.Allocator, codex_home: []const u8, opts: cli.types.KillConfigOptions) !void {
var reg = try registry.loadRegistry(allocator, codex_home);
defer reg.deinit(allocator);
reg.auto_kill = opts.enabled;
try registry.saveRegistry(allocator, codex_home, &reg);

var stdout: io_util.Stdout = undefined;
stdout.init();
const out = stdout.out();
try out.print("Auto-kill codex on switch: {s}\n", .{if (opts.enabled) "on" else "off"});
try out.flush();
}

fn handleLiveCommand(allocator: std.mem.Allocator, codex_home: []const u8, opts: cli.types.LiveOptions) !void {
var reg = try registry.loadRegistry(allocator, codex_home);
defer reg.deinit(allocator);
Expand Down
Loading