Add --mode api|token support to anthropic-auth
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user