diff --git a/src/commands/config-channels-command.ts b/src/commands/config-channels-command.ts index 6fffd019..68a580cb 100644 --- a/src/commands/config-channels-command.ts +++ b/src/commands/config-channels-command.ts @@ -57,7 +57,7 @@ interface ChannelsCommandOptions { setTokenChannel?: OfficialChannelId; setTokenMissing: boolean; clearTokenInvalid?: string; - setTokenInvalid?: string; + setTokenInvalid: boolean; help: boolean; } @@ -83,13 +83,13 @@ export function parseChannelsCommandArgs(args: string[]): ChannelsCommandOptions } let parsedSetTokenChannel: OfficialChannelId | undefined; - let setTokenInvalid: string | undefined; + let setTokenInvalid = false; if (setToken.found && !setToken.missingValue && setToken.value) { const channelId = setToken.value.trim().toLowerCase(); if (isOfficialChannelId(channelId)) { parsedSetTokenChannel = channelId; } else { - setTokenInvalid = setToken.value; + setTokenInvalid = true; } } @@ -378,11 +378,7 @@ export async function handleConfigChannelsCommand(args: string[]): Promise return; } if (options.setTokenInvalid) { - console.error( - fail( - `Invalid --set-token value: ${options.setTokenInvalid} (use ${getOfficialChannelChoices()})` - ) - ); + console.error(fail(`Invalid --set-token value (use ${getOfficialChannelChoices()})`)); process.exitCode = 1; return; } diff --git a/src/commands/config-command-options.ts b/src/commands/config-command-options.ts index 11e5aa5e..1fdba3c3 100644 --- a/src/commands/config-command-options.ts +++ b/src/commands/config-command-options.ts @@ -92,7 +92,7 @@ export function showConfigCommandHelp(): void { console.log(' --enable Legacy alias: add Discord'); console.log(' --disable Legacy alias: remove Discord'); console.log(' --unattended Also add --dangerously-skip-permissions at runtime'); - console.log(' --set-token Save channel token (telegram= or discord=)'); + console.log(' --set-token Save channel token from channel env var'); console.log(' --clear-token Remove all saved channel tokens'); console.log(' --clear-token Remove one saved channel token'); console.log(` ${getOfficialChannelsSupportMessage()}`); @@ -145,7 +145,7 @@ export function showConfigCommandHelp(): void { console.log(' ccs config auth setup Configure dashboard login'); console.log(' ccs config channels Show Official Channels status'); console.log(' ccs config channels --set telegram,discord Enable Telegram + Discord'); - console.log(' ccs config channels --set-token telegram=xxx Save TELEGRAM_BOT_TOKEN'); + console.log(' TELEGRAM_BOT_TOKEN=xxx ccs config channels --set-token telegram Save token'); console.log(' ccs config image-analysis Show image settings'); console.log(' ccs config image-analysis --enable Enable feature'); console.log(' ccs config thinking Show thinking settings'); diff --git a/tests/unit/commands/config-channels-command.test.ts b/tests/unit/commands/config-channels-command.test.ts index b68d7557..b21e67f8 100644 --- a/tests/unit/commands/config-channels-command.test.ts +++ b/tests/unit/commands/config-channels-command.test.ts @@ -1,5 +1,14 @@ -import { describe, expect, it } from 'bun:test'; -import { parseChannelsCommandArgs } from '../../../src/commands/config-channels-command'; +import { afterEach, describe, expect, it, spyOn } from 'bun:test'; +import { + handleConfigChannelsCommand, + parseChannelsCommandArgs, +} from '../../../src/commands/config-channels-command'; + +const originalExitCode = process.exitCode; + +afterEach(() => { + process.exitCode = originalExitCode ?? 0; +}); describe('config channels command parser', () => { it('parses selection, unattended mode, and token channel input', () => { @@ -31,4 +40,34 @@ describe('config channels command parser', () => { expect(clearAll.clearTokenAll).toBe(true); expect(clearOne.clearTokenChannel).toBe('discord'); }); + + it('marks invalid set-token values without retaining secret-like input', () => { + const secretValue = 'telegram=SECRET_BOT_TOKEN_12345'; + const result = parseChannelsCommandArgs(['--set-token', secretValue]); + + expect(result.setTokenInvalid).toBe(true); + expect(JSON.stringify(result)).not.toContain(secretValue); + expect(JSON.stringify(result)).not.toContain('SECRET_BOT_TOKEN_12345'); + }); +}); + +describe('config channels command handler', () => { + it('does not echo invalid set-token values to stderr', async () => { + const secretValue = 'telegram=SECRET_BOT_TOKEN_12345'; + const errors: string[] = []; + const errorSpy = spyOn(console, 'error').mockImplementation((...args: unknown[]) => { + errors.push(args.map(String).join(' ')); + }); + + try { + await handleConfigChannelsCommand(['--set-token', secretValue]); + } finally { + errorSpy.mockRestore(); + } + + expect(process.exitCode).toBe(1); + expect(errors.join('\n')).toContain('Invalid --set-token value'); + expect(errors.join('\n')).not.toContain(secretValue); + expect(errors.join('\n')).not.toContain('SECRET_BOT_TOKEN_12345'); + }); });