diff --git a/docs-site/content/docs/cli-reference/ktx-setup.mdx b/docs-site/content/docs/cli-reference/ktx-setup.mdx index 562b5f28..a4e28e9a 100644 --- a/docs-site/content/docs/cli-reference/ktx-setup.mdx +++ b/docs-site/content/docs/cli-reference/ktx-setup.mdx @@ -26,7 +26,7 @@ below. | Flag | Description | Default | |------|-------------|---------| -| `--agents` | Install agent integration only | `false` | +| `--agents` | Install agent configuration and rules only | `false` | | `--target ` | Agent target: `claude-code`, `codex`, `cursor`, `opencode`, or `universal` | - | | `--global` | Install agent integration into the global target scope for `claude-code` or `codex` | `false` | | `--yes` | Accept safe defaults in non-interactive setup | `false` | @@ -82,15 +82,19 @@ embedding credential source. ### Runtime Setup prepares the managed Python runtime when your selected configuration -needs it. The runtime step runs after database and source setup and before the -initial context build. +needs it. In the full setup flow, the runtime step runs after database and +source setup and before the initial context build. -KTX prepares the `core` runtime feature when agent integration, query-history -ingest, Looker source ingest, or daemon-backed context build paths need it. KTX -prepares the `local-embeddings` runtime feature when you choose managed local -`sentence-transformers` embeddings. Existing external daemon URLs, such as -`KTX_DAEMON_URL` or `KTX_SQL_ANALYSIS_URL`, satisfy the matching dependency and -skip managed runtime installation for that dependency. +KTX prepares the `core` runtime feature when query-history ingest, Looker +source ingest, database introspection fallback, or daemon-backed context build +paths need it. KTX prepares the `local-embeddings` runtime feature when you +choose managed local `sentence-transformers` embeddings. Existing external +daemon URLs, such as `KTX_DAEMON_URL` or `KTX_SQL_ANALYSIS_URL`, satisfy the +matching dependency and skip managed runtime installation for that dependency. + +`ktx setup --agents` doesn't prepare runtime features or build context. It only +installs agent configuration and rules. Start MCP with `ktx mcp start` before +using HTTP-based agents; MCP startup prepares the runtime it needs. Interactive setup prompts before installing runtime features. Use `--yes` to install them without prompting. Use `--no-input` to fail fast when required diff --git a/docs-site/content/docs/integrations/agent-clients.mdx b/docs-site/content/docs/integrations/agent-clients.mdx index ecea5881..2b096640 100644 --- a/docs-site/content/docs/integrations/agent-clients.mdx +++ b/docs-site/content/docs/integrations/agent-clients.mdx @@ -13,18 +13,18 @@ a developer or operator agent also needs pinned `ktx` admin commands. ## Install with setup -Start the MCP server before connecting an end-user agent: - -```bash -ktx mcp start -``` - -Then install client integration: +Install client integration first: ```bash ktx setup --agents ``` +Then start the MCP server before using HTTP-based clients: + +```bash +ktx mcp start +``` + Use `--target` for one target: ```bash diff --git a/docs-site/next-env.d.ts b/docs-site/next-env.d.ts index 9edff1c7..c4b7818f 100644 --- a/docs-site/next-env.d.ts +++ b/docs-site/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/packages/cli/src/runtime-requirements.test.ts b/packages/cli/src/runtime-requirements.test.ts index 7d36e86c..1a8f2d43 100644 --- a/packages/cli/src/runtime-requirements.test.ts +++ b/packages/cli/src/runtime-requirements.test.ts @@ -7,10 +7,10 @@ import { } from './runtime-requirements.js'; describe('runtime requirement detection', () => { - it('requires core for agent/MCP setup', () => { + it('does not require runtime for agent/MCP setup alone', () => { const config = buildDefaultKtxProjectConfig(); - expect(resolveProjectRuntimeRequirements(config, { agents: true }).features).toEqual(['core']); + expect(resolveProjectRuntimeRequirements(config).features).toEqual([]); }); it('requires core for Looker source ingest unless an external daemon is configured', () => { diff --git a/packages/cli/src/runtime-requirements.ts b/packages/cli/src/runtime-requirements.ts index 086f86af..1e35d90e 100644 --- a/packages/cli/src/runtime-requirements.ts +++ b/packages/cli/src/runtime-requirements.ts @@ -8,7 +8,6 @@ import type { KtxRuntimeFeature } from './managed-python-runtime.js'; import type { KtxPublicIngestPlan } from './public-ingest.js'; type KtxRuntimeRequirementReason = - | 'agent-mcp' | 'query-history' | 'looker-source' | 'database-introspection' @@ -26,7 +25,6 @@ export interface KtxRuntimeRequirements { } export interface KtxProjectRuntimeRequirementOptions { - agents?: boolean; databaseIntrospectionFallback?: boolean; env?: NodeJS.ProcessEnv | Record; } @@ -92,14 +90,6 @@ export function resolveProjectRuntimeRequirements( const env = options.env ?? process.env; const requirements: KtxRuntimeRequirement[] = []; - if (options.agents === true) { - requirements.push({ - feature: 'core', - reason: 'agent-mcp', - detail: 'Agent MCP setup uses semantic-layer query tools and SQL validation.', - }); - } - if (options.databaseIntrospectionFallback === true && !hasDaemonOverride(env)) { requirements.push({ feature: 'core', diff --git a/packages/cli/src/setup-runtime.test.ts b/packages/cli/src/setup-runtime.test.ts index 0c1b129e..e6046379 100644 --- a/packages/cli/src/setup-runtime.test.ts +++ b/packages/cli/src/setup-runtime.test.ts @@ -4,7 +4,6 @@ import { join } from 'node:path'; import { MANAGED_SENTENCE_TRANSFORMERS_BASE_URL } from '@ktx/context'; import { buildDefaultKtxProjectConfig, readKtxSetupState, type KtxProjectConfig } from '@ktx/context/project'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import type { ManagedPythonCommandRuntime } from './managed-python-command.js'; import { runKtxSetupRuntimeStep } from './setup-runtime.js'; function makeIo() { @@ -43,9 +42,9 @@ describe('runKtxSetupRuntimeStep', () => { await rm(tempDir, { recursive: true, force: true }); }); - it('ensures core runtime for agent setup and records the runtime step', async () => { + it('skips runtime setup when the project has no direct runtime requirements', async () => { const io = makeIo(); - const ensureRuntime = vi.fn(async (): Promise => ({} as ManagedPythonCommandRuntime)); + const ensureRuntime = vi.fn(); await expect( runKtxSetupRuntimeStep( @@ -54,7 +53,6 @@ describe('runKtxSetupRuntimeStep', () => { inputMode: 'auto', cliVersion: '0.2.0', runtimeInstallPolicy: 'prompt', - agents: true, }, io.io, { @@ -63,17 +61,11 @@ describe('runKtxSetupRuntimeStep', () => { env: {}, }, ), - ).resolves.toMatchObject({ status: 'ready' }); + ).resolves.toMatchObject({ status: 'skipped' }); - expect(ensureRuntime).toHaveBeenCalledWith( - expect.objectContaining({ - cliVersion: '0.2.0', - installPolicy: 'prompt', - feature: 'core', - }), - ); - expect((await readKtxSetupState(tempDir)).completed_steps).toContain('runtime'); - expect(io.stdout()).toContain('Runtime ready: yes (core)'); + expect(ensureRuntime).not.toHaveBeenCalled(); + expect((await readKtxSetupState(tempDir)).completed_steps).not.toContain('runtime'); + expect(io.stdout()).toContain('Runtime setup skipped.'); }); it('fails fast when required runtime features cannot be installed in no-input mode', async () => { @@ -89,7 +81,7 @@ describe('runKtxSetupRuntimeStep', () => { inputMode: 'disabled', cliVersion: '0.2.0', runtimeInstallPolicy: 'never', - agents: true, + databaseIntrospectionFallback: true, }, io.io, { @@ -131,7 +123,6 @@ describe('runKtxSetupRuntimeStep', () => { inputMode: 'auto', cliVersion: '0.2.0', runtimeInstallPolicy: 'auto', - agents: false, }, io.io, { diff --git a/packages/cli/src/setup-runtime.ts b/packages/cli/src/setup-runtime.ts index 07124fe8..cedcd3d0 100644 --- a/packages/cli/src/setup-runtime.ts +++ b/packages/cli/src/setup-runtime.ts @@ -24,7 +24,6 @@ export interface KtxSetupRuntimeArgs { inputMode: 'auto' | 'disabled'; cliVersion: string; runtimeInstallPolicy: KtxManagedPythonInstallPolicy; - agents: boolean; databaseIntrospectionFallback?: boolean; } @@ -62,7 +61,6 @@ export async function runKtxSetupRuntimeStep( const loadProjectForRuntime = deps.loadProject ?? loadKtxProject; const project = await loadProjectForRuntime({ projectDir: args.projectDir }); const requirements = resolveProjectRuntimeRequirements(project.config, { - agents: args.agents, databaseIntrospectionFallback: args.databaseIntrospectionFallback, env: deps.env ?? process.env, }); diff --git a/packages/cli/src/setup.test.ts b/packages/cli/src/setup.test.ts index 0b079f0d..722b7e71 100644 --- a/packages/cli/src/setup.test.ts +++ b/packages/cli/src/setup.test.ts @@ -1733,7 +1733,7 @@ describe('setup status', () => { expect(committedConfig.stdout).toContain('warehouse:'); }); - it('runs agent setup after context succeeds in --agents mode', async () => { + it('runs agent setup without runtime or context in --agents mode', async () => { const calls: string[] = []; const io = makeIo(); await writeFile(join(tempDir, 'ktx.yaml'), ['connections: {}', ''].join('\n'), 'utf-8'); @@ -1765,11 +1765,11 @@ describe('setup status', () => { sources: async () => ({ status: 'skipped', projectDir: tempDir }), runtime: async () => { calls.push('runtime'); - return runtimeReady(tempDir); + throw new Error('runtime should not run'); }, context: async () => { calls.push('context'); - return { status: 'ready', projectDir: tempDir, runId: 'setup-context-local-test' }; + throw new Error('context should not run'); }, agents: async () => { calls.push('agents'); @@ -1783,11 +1783,13 @@ describe('setup status', () => { ), ).resolves.toBe(0); - expect(calls).toEqual(['runtime', 'context', 'agents']); + expect(calls).toEqual(['agents']); }); - it('does not install agents when non-interactive --agents finds context incomplete', async () => { + it('installs agents when non-interactive --agents finds context incomplete', async () => { const io = makeIo(); + const runtime = vi.fn(async () => runtimeReady(tempDir)); + const context = vi.fn(async () => ({ status: 'skipped' as const, projectDir: tempDir })); const agents = vi.fn(async () => ({ status: 'ready' as const, projectDir: tempDir, @@ -1816,15 +1818,17 @@ describe('setup status', () => { }, io.io, { - runtime: async () => runtimeReady(tempDir), - context: async () => ({ status: 'skipped', projectDir: tempDir }), + runtime, + context, agents, }, ), - ).resolves.toBe(1); + ).resolves.toBe(0); - expect(agents).not.toHaveBeenCalled(); - expect(io.stderr()).toContain('KTX context is not ready for agents.'); + expect(runtime).not.toHaveBeenCalled(); + expect(context).not.toHaveBeenCalled(); + expect(agents).toHaveBeenCalledTimes(1); + expect(io.stderr()).not.toContain('KTX context is not ready for agents.'); }); it('routes a ready project menu selection to agent setup', async () => { @@ -1945,7 +1949,7 @@ describe('setup status', () => { } } - expect(calls).toEqual(['runtime', 'agents']); + expect(calls).toEqual(['agents']); }); it('skips to agent setup when context is ready but agents are not configured', async () => { @@ -2042,10 +2046,10 @@ describe('setup status', () => { ).resolves.toBe(0); expect(readyMenuSelect).not.toHaveBeenCalled(); - expect(calls).toEqual(['runtime', 'agents']); + expect(calls).toEqual(['agents']); }); - it('runs only project resolution, runtime, context gate, and agent setup in --agents mode', async () => { + it('runs only project resolution and agent setup in --agents mode', async () => { const io = makeIo(); const runtime = vi.fn(async () => runtimeReady(tempDir)); const context = vi.fn(async () => ({ status: 'ready' as const, projectDir: tempDir, runId: 'setup-context-local-test' })); @@ -2086,8 +2090,8 @@ describe('setup status', () => { ), ).resolves.toBe(0); - expect(runtime).toHaveBeenCalledTimes(1); - expect(context).toHaveBeenCalledTimes(1); + expect(runtime).not.toHaveBeenCalled(); + expect(context).not.toHaveBeenCalled(); expect(agents).toHaveBeenCalledTimes(1); }); diff --git a/packages/cli/src/setup.ts b/packages/cli/src/setup.ts index 60713d51..02a81771 100644 --- a/packages/cli/src/setup.ts +++ b/packages/cli/src/setup.ts @@ -339,7 +339,6 @@ export async function readKtxSetupStatus( } const agents = [...agentMap.values()]; const runtimeRequirements = resolveProjectRuntimeRequirements(project.config, { - agents: agents.length > 0, env: options.env ?? process.env, }); let runtimeReady = runtimeRequirements.features.length === 0 || completedSteps.includes('runtime'); @@ -493,12 +492,6 @@ function shouldPrintConciseReadySummary(status: KtxSetupStatus): boolean { return setupStatusReady(status) && setupContextReady(status) && status.agents.some((agent) => agent.ready); } -function writeContextNotReadyForAgents(projectDir: string, io: KtxCliIo): void { - io.stderr.write('KTX context is not ready for agents.\n\n'); - io.stderr.write(`Build context first:\n ktx setup --project-dir ${resolve(projectDir)}\n\n`); - io.stderr.write(`Then install agent integration:\n ktx setup --agents --project-dir ${resolve(projectDir)}\n`); -} - function setupRuntimeInstallPolicy(args: Extract): 'prompt' | 'auto' | 'never' { if (args.yes) { return 'auto'; @@ -587,18 +580,19 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup } const runOnly = readyAction; + const agentOnlySetup = agentsRequested || runOnly === 'agents'; const shouldRunModels = !runOnly || runOnly === 'models'; const shouldRunEmbeddings = !runOnly || runOnly === 'embeddings'; const shouldRunDatabases = !runOnly || runOnly === 'databases'; const shouldRunSources = !runOnly || runOnly === 'sources'; const shouldRunRuntime = - agentsRequested || !runOnly || runOnly === 'runtime' || runOnly === 'context' || runOnly === 'agents'; - const shouldRunContext = agentsRequested || !runOnly || runOnly === 'context'; + !agentOnlySetup && (!runOnly || runOnly === 'runtime' || runOnly === 'context'); + const shouldRunContext = !agentOnlySetup && (!runOnly || runOnly === 'context'); const shouldRunAgents = agentsRequested || !runOnly || runOnly === 'agents'; const showPromptInstructions = projectResult.confirmedCreation !== true; - const setupSteps: KtxSetupFlowStep[] = agentsRequested - ? ['runtime', 'context'] + const setupSteps: KtxSetupFlowStep[] = agentOnlySetup + ? [] : ['models', 'embeddings', 'databases', 'sources', 'runtime', 'context']; if (shouldRunAgents && args.skipAgents !== true) { setupSteps.push('agents'); @@ -737,7 +731,6 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup inputMode: args.inputMode, cliVersion: args.cliVersion, runtimeInstallPolicy: setupRuntimeInstallPolicy(args), - agents: shouldRunAgents && args.skipAgents !== true, }, io, ); @@ -799,10 +792,6 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup } if (step === 'context' && stepResult.status !== 'ready') { if (shouldRunAgents && args.skipAgents !== true) { - if (agentsRequested) { - writeContextNotReadyForAgents(projectResult.projectDir, io); - return args.inputMode === 'disabled' ? 1 : 0; - } return 0; } }