feat(cli): add 'skip context sources' option to database setup menu

After databases are configured, the post-setup menu now offers a 'Skip
context sources' choice equivalent to passing --skip-sources, which
plumbs through KtxSetupDatabasesResult.skipSources to bypass the
context-source step in the same run.
This commit is contained in:
Andrey Avtomonov 2026-05-24 19:11:11 +02:00
parent 394a985d2a
commit 556d9eff5a
5 changed files with 137 additions and 4 deletions

View file

@ -136,6 +136,10 @@ Enabling query history makes deep ingest readiness matter for later
### Context Sources
In interactive setup, after you configure a database, choose
**Skip context sources** to leave optional context-source setup complete with no
sources. This is equivalent to passing `--skip-sources` in scripted setup.
| Flag | Description |
|------|-------------|
| `--source <type>` | Context-source connector type: `dbt`, `metricflow`, `metabase`, `looker`, `lookml`, or `notion` |

View file

@ -695,6 +695,7 @@ describe('setup databases step', () => {
message: 'Databases configured: warehouse\nWhat would you like to do?',
options: [
{ value: 'continue', label: 'Continue to context sources' },
{ value: 'skip-sources', label: 'Skip context sources' },
{ value: 'edit', label: 'Edit an existing database' },
{ value: 'add', label: 'Add another database' },
],
@ -703,6 +704,48 @@ describe('setup databases step', () => {
expect(scanConnection).not.toHaveBeenCalled();
});
it('can skip context sources from the configured database menu', async () => {
await writeFile(
join(tempDir, 'ktx.yaml'),
[
'connections:',
' warehouse:',
' driver: postgres',
' url: env:DATABASE_URL',
'setup:',
' database_connection_ids:',
' - warehouse',
'',
].join('\n'),
'utf-8',
);
await writeKtxSetupState(tempDir, { completed_steps: ['databases'] });
const prompts = makePromptAdapter({ selectValues: ['skip-sources'] });
const testConnection = vi.fn(async () => 0);
const scanConnection = vi.fn(async () => 0);
const result = await runKtxSetupDatabasesStep(
{
projectDir: tempDir,
inputMode: 'auto',
skipDatabases: false,
databaseSchemas: [],
disableQueryHistory: true,
},
makeIo().io,
{ prompts, testConnection, scanConnection },
);
expect(result).toEqual({
status: 'ready',
projectDir: tempDir,
connectionIds: ['warehouse'],
skipSources: true,
});
expect(testConnection).not.toHaveBeenCalled();
expect(scanConnection).not.toHaveBeenCalled();
});
it('preserves existing database ids when adding another database from the configured menu', async () => {
await writeFile(
join(tempDir, 'ktx.yaml'),
@ -753,6 +796,7 @@ describe('setup databases step', () => {
message: 'Databases configured: warehouse\nWhat would you like to do?',
options: [
{ value: 'continue', label: 'Continue to context sources' },
{ value: 'skip-sources', label: 'Skip context sources' },
{ value: 'edit', label: 'Edit an existing database' },
{ value: 'add', label: 'Add another database' },
],
@ -801,6 +845,7 @@ describe('setup databases step', () => {
message: 'Databases configured: postgres-warehouse\nWhat would you like to do?',
options: [
{ value: 'continue', label: 'Continue to context sources' },
{ value: 'skip-sources', label: 'Skip context sources' },
{ value: 'edit', label: 'Edit an existing database' },
{ value: 'add', label: 'Add another database' },
],
@ -846,6 +891,7 @@ describe('setup databases step', () => {
message: 'Databases configured: postgres-warehouse\nWhat would you like to do?',
options: [
{ value: 'continue', label: 'Continue to context sources' },
{ value: 'skip-sources', label: 'Skip context sources' },
{ value: 'edit', label: 'Edit an existing database' },
{ value: 'add', label: 'Add another database' },
],
@ -890,6 +936,7 @@ describe('setup databases step', () => {
message: 'Databases configured: warehouse\nWhat would you like to do?',
options: [
{ value: 'continue', label: 'Continue to context sources' },
{ value: 'skip-sources', label: 'Skip context sources' },
{ value: 'edit', label: 'Edit an existing database' },
{ value: 'add', label: 'Add another database' },
],
@ -936,6 +983,7 @@ describe('setup databases step', () => {
message: 'Databases configured: warehouse\nWhat would you like to do?',
options: [
{ value: 'continue', label: 'Continue to context sources' },
{ value: 'skip-sources', label: 'Skip context sources' },
{ value: 'edit', label: 'Edit an existing database' },
{ value: 'add', label: 'Add another database' },
],

View file

@ -56,7 +56,12 @@ export interface KtxSetupDatabasesArgs {
}
export type KtxSetupDatabasesResult =
| { status: 'ready'; projectDir: string; connectionIds: string[] }
| {
status: 'ready';
projectDir: string;
connectionIds: string[];
skipSources?: boolean;
}
| { status: 'skipped'; projectDir: string }
| { status: 'back'; projectDir: string }
| { status: 'missing-input'; projectDir: string }
@ -659,6 +664,7 @@ function configuredPrimarySourcesPrompt(connectionIds: string[]): {
message: `Databases configured: ${connectionIds.join(', ')}\nWhat would you like to do?`,
options: [
{ value: 'continue', label: 'Continue to context sources' },
{ value: 'skip-sources', label: 'Skip context sources' },
{ value: 'edit', label: 'Edit an existing database' },
{ value: 'add', label: 'Add another database' },
],
@ -2217,6 +2223,15 @@ export async function runKtxSetupDatabasesStep(
await markDatabasesComplete(args.projectDir, selectedConnectionIds);
return { status: 'ready', projectDir: args.projectDir, connectionIds: selectedConnectionIds };
}
if (action === 'skip-sources') {
await markDatabasesComplete(args.projectDir, selectedConnectionIds);
return {
status: 'ready',
projectDir: args.projectDir,
connectionIds: selectedConnectionIds,
skipSources: true,
};
}
if (action === 'edit') {
const connectionId = await choosePrimarySourceToEdit({
projectDir: args.projectDir,

View file

@ -1641,6 +1641,67 @@ describe('setup status', () => {
expect(calls).toEqual(['model', 'embeddings', 'databases', 'sources']);
});
it('passes context-source skip selection from database setup into the sources step', async () => {
const calls: string[] = [];
const io = makeIo();
await writeFile(join(tempDir, 'ktx.yaml'), ['connections: {}', ''].join('\n'), 'utf-8');
await expect(
runKtxSetup(
{
command: 'run',
projectDir: tempDir,
mode: 'auto',
agents: false,
skipAgents: true,
inputMode: 'disabled',
yes: true,
cliVersion: '0.2.0',
skipLlm: true,
skipEmbeddings: true,
skipDatabases: false,
skipSources: false,
databaseSchemas: [],
},
io.io,
{
model: async () => {
calls.push('model');
return { status: 'skipped', projectDir: tempDir };
},
embeddings: async () => {
calls.push('embeddings');
return { status: 'skipped', projectDir: tempDir };
},
databases: async () => {
calls.push('databases');
return {
status: 'ready',
projectDir: tempDir,
connectionIds: ['warehouse'],
skipSources: true,
};
},
sources: async (args) => {
expect(args.skipSources).toBe(true);
calls.push('sources');
return { status: 'skipped', projectDir: tempDir };
},
runtime: async () => {
calls.push('runtime');
return runtimeReady(tempDir);
},
context: async () => {
calls.push('context');
return { status: 'ready', projectDir: tempDir, runId: 'setup-context-local-test' };
},
},
),
).resolves.toBe(0);
expect(calls).toEqual(['model', 'embeddings', 'databases', 'sources', 'runtime', 'context']);
});
it.each([
{
backend: 'vertex',

View file

@ -667,6 +667,7 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
const shouldRunContext = !agentOnlySetup && (!runOnly || runOnly === 'context');
const shouldRunAgents = agentsRequested || !runOnly || runOnly === 'agents';
const showPromptInstructions = projectResult.confirmedCreation !== true;
let skipSourcesFromDatabaseMenu = false;
const setupSteps: KtxSetupFlowStep[] = agentOnlySetup
? []
@ -680,7 +681,9 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
if (step === 'models') return !args.skipLlm && shouldRunModels;
if (step === 'embeddings') return !args.skipEmbeddings && shouldRunEmbeddings;
if (step === 'databases') return !args.skipDatabases && shouldRunDatabases;
if (step === 'sources') return args.skipSources !== true && shouldRunSources;
if (step === 'sources') {
return args.skipSources !== true && !skipSourcesFromDatabaseMenu && shouldRunSources;
}
if (step === 'runtime') return shouldRunRuntime;
if (step === 'context') return shouldRunContext;
return shouldRunAgents && args.skipAgents !== true;
@ -743,7 +746,7 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
const databasesRunner =
deps.databases ??
((databaseArgs, databaseIo) => runKtxSetupDatabasesStep(databaseArgs, databaseIo, deps.databasesDeps));
stepResult = await databasesRunner(
const databaseResult = await databasesRunner(
{
projectDir: projectResult.projectDir,
inputMode: args.inputMode,
@ -768,6 +771,8 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
},
io,
);
skipSourcesFromDatabaseMenu = databaseResult.status === 'ready' && databaseResult.skipSources === true;
stepResult = databaseResult;
} else if (step === 'sources') {
const sourcesRunner =
deps.sources ?? ((sourceArgs, sourceIo) => runKtxSetupSourcesStep(sourceArgs, sourceIo, deps.sourcesDeps));
@ -794,7 +799,7 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
...(args.notionCrawlMode ? { notionCrawlMode: args.notionCrawlMode } : {}),
...(args.notionRootPageIds ? { notionRootPageIds: args.notionRootPageIds } : {}),
runInitialSourceIngest: args.runInitialSourceIngest ?? false,
skipSources: args.skipSources === true || !shouldRunSources,
skipSources: args.skipSources === true || !shouldRunSources || skipSourcesFromDatabaseMenu,
},
io,
);