From 0d05cabb383cd259d5ea7b8c18d6f3f9943c2aa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:15:41 +0000 Subject: [PATCH 1/2] Initial plan From 61f80a849cea1e610392d8df2ecfb6863bf919b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 05:18:15 +0000 Subject: [PATCH 2/2] Add delete command for removing saved accounts Co-authored-by: Sls0n <102340248+Sls0n@users.noreply.github.com> --- README.md | 7 ++++ src/commands/delete.ts | 63 +++++++++++++++++++++++++++++ src/lib/accounts/account-service.ts | 18 +++++++++ src/lib/accounts/errors.ts | 9 +++++ src/lib/accounts/index.ts | 1 + 5 files changed, 98 insertions(+) create mode 100644 src/commands/delete.ts diff --git a/README.md b/README.md index 26e6e63..3dd8e2e 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ codex-auth use # or pick interactively codex-auth use +# delete a saved account +codex-auth delete + +# or pick interactively +codex-auth delete + # list accounts codex-auth list @@ -42,6 +48,7 @@ codex-auth current - `codex-auth save ` – Validates ``, ensures `auth.json` exists, then snapshots it to `~/.codex/accounts/.json`. - `codex-auth use [name]` – Accepts a name or launches an interactive selector with the current account pre-selected. Copies on Windows, creates a symlink elsewhere, and records the active name. +- `codex-auth delete [name]` – Accepts a name or launches an interactive selector. Removes the saved account from `~/.codex/accounts/`. Cannot delete the currently active account. - `codex-auth list` – Lists all saved snapshots alphabetically and marks the active one with `*`. - `codex-auth current` – Prints the active account name, or a friendly message if none is active. diff --git a/src/commands/delete.ts b/src/commands/delete.ts new file mode 100644 index 0000000..d947c0f --- /dev/null +++ b/src/commands/delete.ts @@ -0,0 +1,63 @@ +import { Args } from "@oclif/core"; +import prompts from "prompts"; +import { BaseCommand } from "../lib/base-command"; +import { NoAccountsSavedError, PromptCancelledError } from "../lib/accounts"; + +export default class DeleteCommand extends BaseCommand { + static description = "Delete a saved account from ~/.codex/accounts"; + + static args = { + account: Args.string({ + name: "account", + required: false, + description: "Account to delete", + }), + } as const; + + async run(): Promise { + await this.runSafe(async () => { + const { args } = await this.parse(DeleteCommand); + let account = args.account as string | undefined; + + if (!account) { + account = await this.promptForAccount(); + } + + const deleted = await this.accounts.removeAccount(account); + this.log(`Deleted Codex account "${deleted}".`); + }); + } + + private async promptForAccount(): Promise { + const accounts = await this.accounts.listAccountNames(); + if (!accounts.length) { + throw new NoAccountsSavedError(); + } + + const current = await this.accounts.getCurrentAccountName(); + + const response = await prompts( + { + type: "select", + name: "account", + message: "Select account to delete", + choices: accounts.map((name) => ({ + title: current === name ? `${name} (active)` : name, + value: name, + })), + }, + { + onCancel: () => { + throw new PromptCancelledError(); + }, + }, + ); + + const picked = response.account as string | undefined; + if (!picked) { + throw new PromptCancelledError(); + } + + return picked; + } +} diff --git a/src/lib/accounts/account-service.ts b/src/lib/accounts/account-service.ts index 904d4b5..2444d0e 100644 --- a/src/lib/accounts/account-service.ts +++ b/src/lib/accounts/account-service.ts @@ -5,6 +5,7 @@ import { accountsDir, authPath, codexDir, currentNamePath } from "../config/path import { AccountNotFoundError, AuthFileMissingError, + CannotDeleteActiveAccountError, InvalidAccountNameError, } from "./errors"; @@ -72,6 +73,23 @@ export class AccountService { return name; } + public async removeAccount(rawName: string): Promise { + const name = this.normalizeAccountName(rawName); + const accountPath = this.accountFilePath(name); + + if (!(await this.pathExists(accountPath))) { + throw new AccountNotFoundError(name); + } + + const currentAccount = await this.getCurrentAccountName(); + if (currentAccount === name) { + throw new CannotDeleteActiveAccountError(name); + } + + await fsp.rm(accountPath, { force: true }); + return name; + } + private accountFilePath(name: string): string { return path.join(accountsDir, `${name}.json`); } diff --git a/src/lib/accounts/errors.ts b/src/lib/accounts/errors.ts index 7356bf5..eb6effe 100644 --- a/src/lib/accounts/errors.ts +++ b/src/lib/accounts/errors.ts @@ -40,3 +40,12 @@ export class PromptCancelledError extends CodexAuthError { super("No account selected. The operation was cancelled."); } } + +export class CannotDeleteActiveAccountError extends CodexAuthError { + constructor(accountName: string) { + super( + `Cannot delete the currently active account "${accountName}". ` + + `Switch to another account first using "codex-auth use ".`, + ); + } +} diff --git a/src/lib/accounts/index.ts b/src/lib/accounts/index.ts index c7508ca..b7096d1 100644 --- a/src/lib/accounts/index.ts +++ b/src/lib/accounts/index.ts @@ -4,6 +4,7 @@ export { AccountService } from "./account-service"; export { AccountNotFoundError, AuthFileMissingError, + CannotDeleteActiveAccountError, CodexAuthError, InvalidAccountNameError, NoAccountsSavedError,