Add --mode api|token support to anthropic-auth

This commit is contained in:
William Valentin
2026-02-15 20:20:39 -08:00
parent 42c526bce9
commit 4b8007c90d
3 changed files with 92 additions and 3 deletions
+12
View File
@@ -135,6 +135,18 @@
"test_status": "pnpm test:run src/frontends/tui/minimal.login.test.ts src/frontends/tui/minimal.test.ts + pnpm typecheck passing"
},
"anthropic-auth-mode-flag": {
"status": "completed",
"date": "2026-02-16",
"updated": "2026-02-16",
"summary": "Added `--mode api|token` to `anthropic-auth` for parity with explicit mode-based auth commands, while keeping `--token` backward compatible. Includes conflict validation for `--token --mode api`.",
"files_modified": [
"src/cli/anthropic-auth.ts",
"src/cli/anthropic-auth.test.ts"
],
"test_status": "pnpm test:run src/cli/anthropic-auth.test.ts src/cli/index.test.ts + pnpm typecheck passing"
},
"deployment-port-env-override": {
"status": "completed",
"date": "2026-02-16",
+52
View File
@@ -129,4 +129,56 @@ describe('anthropic-auth command', () => {
consoleLog.mockRestore();
consoleError.mockRestore();
});
it('accepts --mode api and stores API key', async () => {
mockLoadStoredAnthropicAuth.mockReturnValue(null);
mockReadlineAnswers(['sk-ant-from-mode']);
const program = new Command();
const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js');
registerAnthropicAuthCommand(program);
const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined);
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
await program.parseAsync(['node', 'test', 'anthropic-auth', '--mode', 'api']);
expect(mockStoreAnthropicAuth).toHaveBeenCalledWith('sk-ant-from-mode');
expect(mockStoreAnthropicAuthToken).not.toHaveBeenCalled();
expect(consoleError).not.toHaveBeenCalled();
consoleLog.mockRestore();
consoleError.mockRestore();
});
it('accepts --mode token and stores auth token', async () => {
mockLoadStoredAnthropicAuthToken.mockReturnValue(null);
mockReadlineAnswers(['tok-from-mode']);
const program = new Command();
const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js');
registerAnthropicAuthCommand(program);
const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => undefined);
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
await program.parseAsync(['node', 'test', 'anthropic-auth', '--mode', 'token']);
expect(mockStoreAnthropicAuthToken).toHaveBeenCalledWith('tok-from-mode');
expect(mockStoreAnthropicAuth).not.toHaveBeenCalled();
expect(consoleError).not.toHaveBeenCalled();
consoleLog.mockRestore();
consoleError.mockRestore();
});
it('fails on conflicting --token and --mode api options', async () => {
const program = new Command();
const { registerAnthropicAuthCommand } = await import('./anthropic-auth.js');
registerAnthropicAuthCommand(program);
await expect(
program.parseAsync(['node', 'test', 'anthropic-auth', '--token', '--mode', 'api']),
).rejects.toThrow(/Conflicting options/);
});
});
+28 -3
View File
@@ -7,6 +7,8 @@ import {
storeAnthropicAuthToken,
} from '../auth/index.js';
type AnthropicAuthMode = 'api' | 'token';
async function promptHidden(question: string): Promise<string> {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
const rlAny = rl as unknown as { stdoutMuted?: boolean; _writeToOutput?: (s: string) => void };
@@ -39,14 +41,37 @@ async function promptYesNo(question: string): Promise<boolean> {
return normalized === 'y' || normalized === 'yes';
}
function parseAnthropicAuthMode(value: string): AnthropicAuthMode {
const mode = value.trim().toLowerCase();
if (mode === 'api' || mode === 'token') {
return mode;
}
throw new Error(`Invalid mode "${value}". Expected: api or token.`);
}
function resolveAuthMode(opts: { token?: boolean; mode?: AnthropicAuthMode }): AnthropicAuthMode {
if (opts.mode) {
if (opts.token && opts.mode !== 'token') {
throw new Error('Conflicting options: --token implies --mode token, but --mode api was provided.');
}
return opts.mode;
}
if (opts.token) {
return 'token';
}
return 'api';
}
export function registerAnthropicAuthCommand(program: Command): void {
program
.command('anthropic-auth')
.description('Store an Anthropic API key or auth token (auth.json)')
.option('--mode <mode>', 'Credential mode: api or token', parseAnthropicAuthMode)
.option('--token', 'Store an Anthropic auth token instead of an API key')
.action(async (opts: { token?: boolean }) => {
.action(async (opts: { token?: boolean; mode?: AnthropicAuthMode }) => {
const mode = resolveAuthMode(opts);
if (opts.token) {
if (mode === 'token') {
if (loadStoredAnthropicAuthToken()) {
console.log('Anthropic auth token already exists.');
const confirmed = await promptYesNo('Re-authenticate and replace it? (y/N): ');
@@ -72,7 +97,7 @@ export function registerAnthropicAuthCommand(program: Command): void {
console.log('');
try {
if (opts.token) {
if (mode === 'token') {
const token = await promptHidden('Enter Anthropic auth token: ');
storeAnthropicAuthToken(token);
} else {