Skip to content
Draft
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ codex-auth use <name>
# or pick interactively
codex-auth use

# delete a saved account
codex-auth delete <name>

# or pick interactively
codex-auth delete

# list accounts
codex-auth list

Expand All @@ -42,6 +48,7 @@ codex-auth current

- `codex-auth save <name>` – Validates `<name>`, ensures `auth.json` exists, then snapshots it to `~/.codex/accounts/<name>.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.

Expand Down
63 changes: 63 additions & 0 deletions src/commands/delete.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<string> {
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;
}
}
18 changes: 18 additions & 0 deletions src/lib/accounts/account-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { accountsDir, authPath, codexDir, currentNamePath } from "../config/path
import {
AccountNotFoundError,
AuthFileMissingError,
CannotDeleteActiveAccountError,
InvalidAccountNameError,
} from "./errors";

Expand Down Expand Up @@ -72,6 +73,23 @@ export class AccountService {
return name;
}

public async removeAccount(rawName: string): Promise<string> {
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`);
}
Expand Down
9 changes: 9 additions & 0 deletions src/lib/accounts/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>".`,
);
}
}
1 change: 1 addition & 0 deletions src/lib/accounts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { AccountService } from "./account-service";
export {
AccountNotFoundError,
AuthFileMissingError,
CannotDeleteActiveAccountError,
CodexAuthError,
InvalidAccountNameError,
NoAccountsSavedError,
Expand Down