From 739a193bab2cb96c5fd660bb6240c5fb672e49bb Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Wed, 13 May 2026 15:50:24 +0200 Subject: [PATCH] Show configured context sources in setup --- packages/cli/src/setup-sources.test.ts | 26 +++++++++++++++++++++++++ packages/cli/src/setup-sources.ts | 27 ++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/setup-sources.test.ts b/packages/cli/src/setup-sources.test.ts index 93ad854b..c0fb0227 100644 --- a/packages/cli/src/setup-sources.test.ts +++ b/packages/cli/src/setup-sources.test.ts @@ -624,6 +624,32 @@ describe('setup sources step', () => { expect(options).toContainEqual({ value: 'notion', label: 'Notion' }); }); + it('shows already configured context sources in the interactive checklist', async () => { + await addPrimarySource(); + await addConnection('notion-main', { + driver: 'notion', + auth_token_ref: 'env:NOTION_TOKEN', + crawl_mode: 'all_accessible', + }); + const io = makeIo(); + const testPrompts = prompts({ multiselect: [['back']] }); + + await expect( + runKtxSetupSourcesStep( + { projectDir, inputMode: 'auto', runInitialSourceIngest: false, skipSources: false }, + io.io, + { prompts: testPrompts }, + ), + ).resolves.toEqual({ status: 'back', projectDir }); + + expect(testPrompts.multiselect).toHaveBeenCalledWith( + expect.objectContaining({ + initialValues: ['notion'], + options: expect.arrayContaining([{ value: 'notion', label: 'Notion', hint: 'configured: notion-main' }]), + }), + ); + }); + it('uses a source-specific editable connection name for new interactive connections', async () => { await addPrimarySource(); const validateDbt = vi.fn(async () => ({ ok: true as const, detail: 'project=analytics schemas=2' })); diff --git a/packages/cli/src/setup-sources.ts b/packages/cli/src/setup-sources.ts index 313dfbe0..9ea4fb82 100644 --- a/packages/cli/src/setup-sources.ts +++ b/packages/cli/src/setup-sources.ts @@ -73,7 +73,8 @@ export type KtxSetupSourcesResult = export interface KtxSetupSourcesPromptAdapter { multiselect(options: { message: string; - options: Array<{ value: string; label: string }>; + options: Array<{ value: string; label: string; hint?: string }>; + initialValues?: string[]; required?: boolean; }): Promise; select(options: { message: string; options: Array<{ value: string; label: string }> }): Promise; @@ -1325,6 +1326,22 @@ function existingConnectionIdsBySource( .sort((left, right) => left.localeCompare(right)); } +function sourceChecklistForConnections(connections: Record): { + options: Array<{ value: KtxSetupSourceType; label: string; hint?: string }>; + initialValues: KtxSetupSourceType[]; +} { + const initialValues: KtxSetupSourceType[] = []; + const options = SOURCE_OPTIONS.map((option) => { + const existingIds = existingConnectionIdsBySource(connections, option.value); + if (existingIds.length === 0) { + return option; + } + initialValues.push(option.value); + return { ...option, hint: `configured: ${existingIds.join(', ')}` }; + }); + return { options, initialValues }; +} + function defaultConnectionIdForSource( connections: Record, source: KtxSetupSourceType, @@ -1483,13 +1500,19 @@ export async function runKtxSetupSourcesStep( } while (true) { + const contextSourceChecklist = sourceChecklistForConnections( + (await loadKtxProject({ projectDir: args.projectDir })).config.connections, + ); const selected = args.source ? [args.source] : args.inputMode === 'disabled' ? [] : await prompts.multiselect({ message: withMultiselectNavigation('Which context sources should KTX ingest?'), - options: [...SOURCE_OPTIONS], + options: contextSourceChecklist.options, + ...(contextSourceChecklist.initialValues.length > 0 + ? { initialValues: contextSourceChecklist.initialValues } + : {}), required: false, }); if (selected.includes('back')) {