diff --git a/src/channels/official-channels-runtime.ts b/src/channels/official-channels-runtime.ts index bf3472aa..4cda65e7 100644 --- a/src/channels/official-channels-runtime.ts +++ b/src/channels/official-channels-runtime.ts @@ -1,10 +1,7 @@ import { spawnSync } from 'child_process'; import type { TargetType } from '../targets/target-adapter'; import type { ProfileType } from '../types/profile'; -import type { - OfficialChannelId, - OfficialChannelsConfig, -} from '../config/unified-config-types'; +import type { OfficialChannelId, OfficialChannelsConfig } from '../config/unified-config-types'; export interface OfficialChannelDefinition { id: OfficialChannelId; @@ -132,7 +129,11 @@ export function buildOfficialChannelsArgs( channels: OfficialChannelId[], includePermissionBypass: boolean ): string[] { - const nextArgs = [...args, '--channels', ...channels.map((channel) => OFFICIAL_CHANNELS[channel].pluginSpec)]; + const nextArgs = [ + ...args, + '--channels', + ...channels.map((channel) => OFFICIAL_CHANNELS[channel].pluginSpec), + ]; if (includePermissionBypass) { nextArgs.push('--dangerously-skip-permissions'); @@ -191,9 +192,7 @@ export function resolveOfficialChannelsLaunchPlan( const channel = OFFICIAL_CHANNELS[channelId]; if (channel.requiresMacOS && !isMacOS()) { - skippedMessages.push( - `${channel.displayName} auto-enable skipped because it requires macOS.` - ); + skippedMessages.push(`${channel.displayName} auto-enable skipped because it requires macOS.`); continue; } @@ -256,7 +255,9 @@ export function getOfficialChannelSummary(channelId: OfficialChannelId): string return 'macOS-only. No bot token required, but Messages permissions are required.'; } -export function getOfficialChannelUnavailableReason(channelId: OfficialChannelId): string | undefined { +export function getOfficialChannelUnavailableReason( + channelId: OfficialChannelId +): string | undefined { if (channelId === 'imessage' && !isMacOS()) { return 'Requires macOS.'; } @@ -300,7 +301,9 @@ export function isOfficialChannelSelectionValid(selection: string): boolean { .map((value) => value.trim().toLowerCase()) .filter(Boolean); - return parsed.length > 0 && parsed.every((value) => value === 'all' || isOfficialChannelId(value)); + return ( + parsed.length > 0 && parsed.every((value) => value === 'all' || isOfficialChannelId(value)) + ); } export function resolveLegacyDiscordSelection(enabled: boolean | undefined): OfficialChannelId[] { @@ -369,7 +372,10 @@ export function getOfficialChannelsDocsSummary(): string { return 'Supported official channels are Telegram, Discord, and iMessage.'; } -export function getOfficialChannelSyncFailureMessage(channelId: OfficialChannelId, targetPath: string): string { +export function getOfficialChannelSyncFailureMessage( + channelId: OfficialChannelId, + targetPath: string +): string { return `${getOfficialChannelDisplayName(channelId)} auto-enable skipped: failed to sync channel env to ${targetPath}`; } diff --git a/src/commands/config-channels-command.ts b/src/commands/config-channels-command.ts index 352c53d2..26a2d269 100644 --- a/src/commands/config-channels-command.ts +++ b/src/commands/config-channels-command.ts @@ -62,9 +62,7 @@ function parseTokenAssignment(value: string): { } | null { const separatorIndex = value.indexOf('='); if (separatorIndex === -1) { - return value.trim() - ? { channelId: 'discord', token: value.trim() } - : null; + return value.trim() ? { channelId: 'discord', token: value.trim() } : null; } const channelId = value.slice(0, separatorIndex).trim().toLowerCase(); @@ -139,14 +137,20 @@ function showHelp(): void { console.log(` ${color('--clear', 'command')} Clear all selected channels`); console.log(` ${color('--enable', 'command')} Legacy alias: add Discord`); console.log(` ${color('--disable', 'command')} Legacy alias: remove Discord`); - console.log(` ${color('--unattended', 'command')} Also add --dangerously-skip-permissions`); + console.log( + ` ${color('--unattended', 'command')} Also add --dangerously-skip-permissions` + ); console.log(` ${color('--no-unattended', 'command')} Disable unattended runtime flag`); console.log(` ${color('--set-token ', 'command')} ${getOfficialChannelTokenHelp()}`); - console.log(` ${color('--clear-token [channel]', 'command')} ${getOfficialChannelClearTokenHelp()}`); + console.log( + ` ${color('--clear-token [channel]', 'command')} ${getOfficialChannelClearTokenHelp()}` + ); console.log(` ${color('--help, -h', 'command')} Show this help`); console.log(''); console.log(subheader('Examples:')); - console.log(` $ ${color('ccs config channels', 'command')} ${dim('# Show status')}`); + console.log( + ` $ ${color('ccs config channels', 'command')} ${dim('# Show status')}` + ); console.log( ` $ ${color('ccs config channels --set telegram,discord', 'command')} ${dim('# Enable Telegram + Discord')}` ); @@ -170,13 +174,17 @@ function showStatus(): void { console.log(''); console.log(header('Official Channels Configuration')); console.log(''); - console.log(` Channels: ${selected.length > 0 ? ok(getChannelConfigSelectionLabel(selected)) : warn('Disabled')}`); + console.log( + ` Channels: ${selected.length > 0 ? ok(getChannelConfigSelectionLabel(selected)) : warn('Disabled')}` + ); console.log(` Unattended: ${config.unattended ? warn('Enabled') : info('Disabled')}`); console.log(` Bun: ${bunReady ? ok('Installed') : warn('Missing')}`); console.log(''); console.log(subheader('Applies To:')); console.log(` ${dim(getOfficialChannelsCompatibilityMessage())}`); - console.log(` ${dim(`Supported profiles: ${getOfficialChannelsSupportedProfiles().join(', ')}`)}`); + console.log( + ` ${dim(`Supported profiles: ${getOfficialChannelsSupportedProfiles().join(', ')}`)}` + ); console.log(''); console.log(subheader('Channels:')); for (const channelId of expandOfficialChannelSelection('all')) { @@ -195,7 +203,9 @@ function showStatus(): void { console.log(` ${enabled ? '[x]' : '[ ]'} ${displayName}: ${status}`); console.log(` ${dim(getOfficialChannelSummary(channelId))}`); if (envKey) { - console.log(` ${dim(`${envKey}: ${tokenConfigured ? 'configured' : 'not configured'}`)}`); + console.log( + ` ${dim(`${envKey}: ${tokenConfigured ? 'configured' : 'not configured'}`)}` + ); console.log(` ${dim(getOfficialChannelEnvPath(channelId))}`); } console.log(` ${dim(getOfficialChannelReadyMessage(channelId))}`); @@ -242,8 +252,13 @@ export async function handleConfigChannelsCommand(args: string[]): Promise process.exitCode = 1; return; } - if (options.setSelection !== undefined && !isOfficialChannelSelectionValid(options.setSelection)) { - console.error(fail(`Invalid --set value: ${options.setSelection} (${getOfficialChannelChoices()} or all)`)); + if ( + options.setSelection !== undefined && + !isOfficialChannelSelectionValid(options.setSelection) + ) { + console.error( + fail(`Invalid --set value: ${options.setSelection} (${getOfficialChannelChoices()} or all)`) + ); process.exitCode = 1; return; } @@ -261,7 +276,9 @@ export async function handleConfigChannelsCommand(args: string[]): Promise } if (options.clearTokenInvalid) { console.error( - fail(`Invalid --clear-token value: ${options.clearTokenInvalid} (use ${getOfficialChannelChoices()})`) + fail( + `Invalid --clear-token value: ${options.clearTokenInvalid} (use ${getOfficialChannelChoices()})` + ) ); process.exitCode = 1; return; diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index 70a7532e..f6aefb3d 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -471,7 +471,10 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim(); printSubSection('Official Channels (official Claude plugins)', [ ['ccs config channels', 'Show current status'], - ['ccs config channels --set telegram,discord', 'Auto-add selected channels on native Claude sessions'], + [ + 'ccs config channels --set telegram,discord', + 'Auto-add selected channels on native Claude sessions', + ], ['ccs config channels --set all', 'Enable Telegram, Discord, and iMessage'], ['ccs config channels --unattended', 'Also add --dangerously-skip-permissions'], ['ccs config channels --set-token telegram=', 'Save TELEGRAM_BOT_TOKEN'], diff --git a/src/config/unified-config-loader.ts b/src/config/unified-config-loader.ts index eb5bf431..e571219d 100644 --- a/src/config/unified-config-loader.ts +++ b/src/config/unified-config-loader.ts @@ -305,7 +305,9 @@ function normalizeOfficialChannelsConfig( partial: Partial & { discord_channels?: LegacyDiscordChannelsConfig } ): OfficialChannelsConfig { const rawSelected = Array.isArray(partial.channels?.selected) - ? partial.channels.selected.filter((value): value is OfficialChannelId => isOfficialChannelId(value)) + ? partial.channels.selected.filter((value): value is OfficialChannelId => + isOfficialChannelId(value) + ) : []; return { @@ -806,16 +808,15 @@ function generateYamlWithComments(config: UnifiedConfig): string { lines.push('# Runtime-only: CCS injects --channels at launch for compatible Claude sessions.'); lines.push('# Bot tokens live in Claude channel env files, not in config.yaml.'); lines.push('# Use selected: [telegram, discord, imessage] to choose channels.'); - lines.push('# unattended adds --dangerously-skip-permissions only when channel auto-enable is active.'); + lines.push( + '# unattended adds --dangerously-skip-permissions only when channel auto-enable is active.' + ); lines.push('# Compatible sessions: native Claude default/account profiles only.'); lines.push('# Configure via: ccs config channels or the Settings > Channels dashboard tab.'); lines.push('# ----------------------------------------------------------------------------'); lines.push( yaml - .dump( - { channels: config.channels }, - { indent: 2, lineWidth: -1, quotingType: '"' } - ) + .dump({ channels: config.channels }, { indent: 2, lineWidth: -1, quotingType: '"' }) .trim() ); lines.push(''); diff --git a/src/web-server/routes/channels-routes.ts b/src/web-server/routes/channels-routes.ts index 92507cf6..9d61f852 100644 --- a/src/web-server/routes/channels-routes.ts +++ b/src/web-server/routes/channels-routes.ts @@ -71,7 +71,8 @@ router.put('/', (req: Request, res: Response): void => { if ( selected !== undefined && - (!Array.isArray(selected) || selected.some((value) => typeof value !== 'string' || !isOfficialChannelId(value))) + (!Array.isArray(selected) || + selected.some((value) => typeof value !== 'string' || !isOfficialChannelId(value))) ) { res.status(400).json({ error: 'selected must be an array of official channel IDs' }); return; @@ -84,7 +85,7 @@ router.put('/', (req: Request, res: Response): void => { try { const updated = mutateUnifiedConfig((config) => { config.channels = { - selected: selected ? [...new Set(selected)] : config.channels?.selected ?? [], + selected: selected ? [...new Set(selected)] : (config.channels?.selected ?? []), unattended: unattended ?? config.channels?.unattended ?? false, }; });