@kaelio/ktx 0.1.0-rc.6 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/assets/python/{kaelio_ktx-0.1.0rc6-py3-none-any.whl → kaelio_ktx-0.1.1-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/commands/mcp-commands.js +11 -3
  4. package/dist/commands/mcp-commands.test.js +30 -1
  5. package/dist/commands/setup-commands.js +14 -26
  6. package/dist/doctor.test.js +3 -4
  7. package/dist/index.test.js +26 -10
  8. package/dist/ingest-depth.js +0 -1
  9. package/dist/ingest.test-utils.js +2 -2
  10. package/dist/ingest.test.js +6 -30
  11. package/dist/managed-local-embeddings.d.ts +2 -0
  12. package/dist/managed-local-embeddings.js +2 -0
  13. package/dist/managed-local-embeddings.test.js +2 -0
  14. package/dist/managed-mcp-daemon.js +3 -2
  15. package/dist/managed-mcp-daemon.test.js +25 -0
  16. package/dist/managed-python-command.test.js +1 -0
  17. package/dist/managed-python-daemon.js +3 -2
  18. package/dist/managed-python-daemon.test.js +20 -0
  19. package/dist/managed-python-runtime.d.ts +4 -0
  20. package/dist/managed-python-runtime.js +47 -3
  21. package/dist/managed-python-runtime.test.js +51 -21
  22. package/dist/next-steps.js +1 -1
  23. package/dist/next-steps.test.js +2 -0
  24. package/dist/proxy-env.d.ts +1 -0
  25. package/dist/proxy-env.js +23 -0
  26. package/dist/proxy-env.test.js +17 -0
  27. package/dist/runtime-requirements.d.ts +1 -2
  28. package/dist/runtime-requirements.js +0 -7
  29. package/dist/runtime-requirements.test.js +2 -2
  30. package/dist/runtime.test.js +1 -0
  31. package/dist/setup-agents.d.ts +11 -3
  32. package/dist/setup-agents.js +400 -135
  33. package/dist/setup-agents.test.js +394 -62
  34. package/dist/setup-embeddings.d.ts +1 -0
  35. package/dist/setup-embeddings.js +28 -6
  36. package/dist/setup-embeddings.test.js +46 -4
  37. package/dist/setup-models.d.ts +0 -1
  38. package/dist/setup-models.js +2 -3
  39. package/dist/setup-models.test.js +8 -10
  40. package/dist/setup-project.d.ts +9 -1
  41. package/dist/setup-project.js +52 -25
  42. package/dist/setup-project.test.js +8 -8
  43. package/dist/setup-runtime.d.ts +0 -1
  44. package/dist/setup-runtime.js +0 -1
  45. package/dist/setup-runtime.test.js +9 -13
  46. package/dist/setup.d.ts +4 -2
  47. package/dist/setup.js +72 -30
  48. package/dist/setup.test.js +271 -58
  49. package/dist/sl.test.js +2 -1
  50. package/dist/standalone-smoke.test.js +2 -3
  51. package/dist/status-project.js +1 -10
  52. package/node_modules/@ktx/connector-clickhouse/dist/package-exports.test.js +1 -1
  53. package/node_modules/@ktx/context/dist/core/git.service.d.ts +0 -1
  54. package/node_modules/@ktx/context/dist/core/git.service.js +0 -12
  55. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.d.ts +1 -2
  56. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.js +0 -18
  57. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +7 -7
  58. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.d.ts +4 -0
  59. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.js +38 -0
  60. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.d.ts +1 -0
  61. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.js +63 -0
  62. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.d.ts +0 -5
  63. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.js +0 -48
  64. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.test.js +0 -83
  65. package/node_modules/@ktx/context/dist/ingest/index.d.ts +2 -1
  66. package/node_modules/@ktx/context/dist/ingest/index.js +1 -0
  67. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.d.ts +0 -2
  68. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.isolated-diff.test.js +0 -166
  69. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.js +45 -235
  70. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.test.js +38 -193
  71. package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +11 -30
  72. package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +5 -1
  73. package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.test.js +3 -3
  74. package/node_modules/@ktx/context/dist/ingest/local-embedding-provider.integration.test.js +9 -10
  75. package/node_modules/@ktx/context/dist/ingest/local-ingest.js +7 -0
  76. package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.d.ts +4 -4
  77. package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.js +1 -1
  78. package/node_modules/@ktx/context/dist/ingest/memory-flow/types.d.ts +1 -1
  79. package/node_modules/@ktx/context/dist/ingest/ports.d.ts +20 -1
  80. package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +2 -73
  81. package/node_modules/@ktx/context/dist/ingest/report-snapshot.js +0 -27
  82. package/node_modules/@ktx/context/dist/ingest/reports.d.ts +5 -23
  83. package/node_modules/@ktx/context/dist/ingest/reports.js +24 -7
  84. package/node_modules/@ktx/context/dist/ingest/types.d.ts +0 -33
  85. package/node_modules/@ktx/context/dist/llm/local-config.js +2 -15
  86. package/node_modules/@ktx/context/dist/llm/local-config.test.js +3 -7
  87. package/node_modules/@ktx/context/dist/package-exports.test.js +1 -2
  88. package/node_modules/@ktx/context/dist/project/config.d.ts +0 -5
  89. package/node_modules/@ktx/context/dist/project/config.js +5 -5
  90. package/node_modules/@ktx/context/dist/project/config.test.js +4 -7
  91. package/node_modules/@ktx/context/dist/scan/enrichment-state.test.js +4 -4
  92. package/node_modules/@ktx/context/dist/scan/index.d.ts +1 -1
  93. package/node_modules/@ktx/context/dist/scan/local-enrichment.d.ts +2 -6
  94. package/node_modules/@ktx/context/dist/scan/local-enrichment.js +31 -47
  95. package/node_modules/@ktx/context/dist/scan/local-enrichment.test.js +35 -18
  96. package/node_modules/@ktx/context/dist/scan/local-scan.test.js +2 -3
  97. package/node_modules/@ktx/llm/dist/embedding-provider.d.ts +0 -7
  98. package/node_modules/@ktx/llm/dist/embedding-provider.js +12 -138
  99. package/node_modules/@ktx/llm/dist/embedding-provider.test.js +10 -25
  100. package/node_modules/@ktx/llm/dist/types.d.ts +1 -1
  101. package/package.json +4 -4
  102. package/node_modules/@ktx/context/dist/ingest/finalization-scope.d.ts +0 -22
  103. package/node_modules/@ktx/context/dist/ingest/finalization-scope.js +0 -95
  104. package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.js +0 -114
  105. /package/{node_modules/@ktx/context/dist/ingest/finalization-scope.test.d.ts → dist/proxy-env.test.d.ts} +0 -0
@@ -2,10 +2,10 @@
2
2
  "schemaVersion": 1,
3
3
  "distributionName": "kaelio-ktx",
4
4
  "normalizedName": "kaelio_ktx",
5
- "version": "0.1.0rc6",
5
+ "version": "0.1.1",
6
6
  "wheel": {
7
- "file": "kaelio_ktx-0.1.0rc6-py3-none-any.whl",
8
- "sha256": "0515d73bf89eb1c32422eb1fab0b59b01b49a23ef7bd8749dc5114361ba17ccc",
9
- "bytes": 80550
7
+ "file": "kaelio_ktx-0.1.1-py3-none-any.whl",
8
+ "sha256": "2f4089f27519dbc10b0fdc1b03d5324cd69984171589907647961d1edbcab01b",
9
+ "bytes": 80519
10
10
  }
11
11
  }
@@ -11,6 +11,16 @@ function tokenFromOption(value) {
11
11
  function binPath() {
12
12
  return fileURLToPath(new URL('../bin.js', import.meta.url));
13
13
  }
14
+ function formatMcpStartResultMessage(input) {
15
+ return [
16
+ input.status === 'started' ? `KTX MCP daemon started: ${input.url}` : `KTX MCP daemon already running: ${input.url}`,
17
+ '',
18
+ 'KTX is ready for configured agents.',
19
+ 'Open your agent for this KTX project and ask a data question, for example:',
20
+ ' "Use KTX to show me the available tables and metrics."',
21
+ '',
22
+ ].join('\n');
23
+ }
14
24
  export function registerMcpCommands(program, context) {
15
25
  const mcp = program.command('mcp').description('Run the KTX MCP HTTP server');
16
26
  mcp
@@ -66,9 +76,7 @@ export function registerMcpCommands(program, context) {
66
76
  allowedOrigins: options.allowedOrigin,
67
77
  binPath: binPath(),
68
78
  });
69
- context.io.stdout.write(result.status === 'started'
70
- ? `KTX MCP daemon started: ${result.url}\n`
71
- : `KTX MCP daemon already running: ${result.url}\n`);
79
+ context.io.stdout.write(formatMcpStartResultMessage({ status: result.status, url: result.url }));
72
80
  });
73
81
  mcp
74
82
  .command('stop')
@@ -65,7 +65,36 @@ describe('registerMcpCommands', () => {
65
65
  registerMcpCommands(program, context);
66
66
  await program.parseAsync(['--project-dir', '/tmp/ktx-already', 'mcp', 'start'], { from: 'user' });
67
67
  expect(startDaemon).toHaveBeenCalledTimes(1);
68
- expect(context.io.stdout.write).toHaveBeenCalledWith('KTX MCP daemon already running: http://127.0.0.1:7878/mcp\n');
68
+ expect(context.io.stdout.write).toHaveBeenCalledWith([
69
+ 'KTX MCP daemon already running: http://127.0.0.1:7878/mcp',
70
+ '',
71
+ 'KTX is ready for configured agents.',
72
+ 'Open your agent for this KTX project and ask a data question, for example:',
73
+ ' "Use KTX to show me the available tables and metrics."',
74
+ '',
75
+ ].join('\n'));
76
+ });
77
+ it('prints a friendly next step after starting the daemon', async () => {
78
+ const program = new Command().exitOverride().option('--project-dir <path>');
79
+ const startDaemon = vi.fn().mockResolvedValue({
80
+ status: 'started',
81
+ url: 'http://127.0.0.1:7878/mcp',
82
+ state: {
83
+ schemaVersion: 1,
84
+ pid: 4242,
85
+ host: '127.0.0.1',
86
+ port: 7878,
87
+ tokenAuth: false,
88
+ projectDir: '/tmp/ktx-started',
89
+ startedAt: '2026-05-14T00:00:00.000Z',
90
+ logPath: '/tmp/ktx-started/.ktx/logs/mcp.log',
91
+ },
92
+ });
93
+ const context = makeContext({ deps: { mcp: { startDaemon } } });
94
+ registerMcpCommands(program, context);
95
+ await program.parseAsync(['--project-dir', '/tmp/ktx-started', 'mcp', 'start'], { from: 'user' });
96
+ expect(context.io.stdout.write).toHaveBeenCalledWith(expect.stringContaining('KTX MCP daemon started: http://127.0.0.1:7878/mcp\n\nKTX is ready for configured agents.'));
97
+ expect(context.io.stdout.write).toHaveBeenCalledWith(expect.stringContaining('"Use KTX to show me the available tables and metrics."'));
69
98
  });
70
99
  it('runs the stdio server with the resolved project directory', async () => {
71
100
  const program = new Command().exitOverride().option('--project-dir <path>');
@@ -79,8 +79,6 @@ function shouldShowSetupEntryMenu(options, command) {
79
79
  return false;
80
80
  }
81
81
  return ![
82
- 'new',
83
- 'existing',
84
82
  'agents',
85
83
  'target',
86
84
  'global',
@@ -92,7 +90,6 @@ function shouldShowSetupEntryMenu(options, command) {
92
90
  'anthropicApiKeyEnv',
93
91
  'anthropicApiKeyFile',
94
92
  'llmModel',
95
- 'anthropicModel',
96
93
  'vertexProject',
97
94
  'vertexLocation',
98
95
  'skipLlm',
@@ -100,7 +97,6 @@ function shouldShowSetupEntryMenu(options, command) {
100
97
  'embeddingApiKeyEnv',
101
98
  'embeddingApiKeyFile',
102
99
  'skipEmbeddings',
103
- 'newDatabaseConnectionId',
104
100
  'databaseUrl',
105
101
  'enableQueryHistory',
106
102
  'disableQueryHistory',
@@ -132,8 +128,6 @@ export function registerSetupCommands(program, context) {
132
128
  .command('setup')
133
129
  .description('Set up or resume a local KTX project')
134
130
  .addOption(new Option('--project-dir <path>', 'KTX project directory').hideHelp())
135
- .addOption(new Option('--new', 'Create a new KTX project before setup').hideHelp().default(false))
136
- .addOption(new Option('--existing', 'Use an existing KTX project').hideHelp().default(false))
137
131
  .option('--agents', 'Install agent integration only', false)
138
132
  .addOption(new Option('--target <target>', 'Agent target').choices([
139
133
  'claude-code',
@@ -146,13 +140,12 @@ export function registerSetupCommands(program, context) {
146
140
  .option('--global', 'Install agent integration into the global target scope', false)
147
141
  .option('--local', 'Install Claude Code MCP config into the private per-project ~/.claude.json scope', false)
148
142
  .addOption(new Option('--skip-agents', 'Leave agent integration incomplete for now').hideHelp().default(false))
149
- .option('--yes', 'Accept safe defaults in non-interactive setup', false)
143
+ .option('--yes', 'Accept project creation and runtime install defaults where setup confirms', false)
150
144
  .option('--no-input', 'Disable interactive terminal input')
151
145
  .addOption(new Option('--llm-backend <backend>', 'LLM backend').argParser(llmBackend).hideHelp())
152
146
  .addOption(new Option('--anthropic-api-key-env <name>', 'Environment variable containing the Anthropic API key').hideHelp())
153
147
  .addOption(new Option('--anthropic-api-key-file <path>', 'File containing the Anthropic API key').hideHelp())
154
148
  .addOption(new Option('--llm-model <model>', 'LLM model ID or backend model alias').hideHelp())
155
- .addOption(new Option('--anthropic-model <model>', 'Anthropic model ID to validate and save').hideHelp())
156
149
  .addOption(new Option('--vertex-project <project>', 'Google Vertex AI project ID, env:NAME, or file:/path').hideHelp())
157
150
  .addOption(new Option('--vertex-location <location>', 'Google Vertex AI location, env:NAME, or file:/path').hideHelp())
158
151
  .addOption(new Option('--skip-llm', 'Leave LLM setup incomplete for now').hideHelp().default(false))
@@ -169,14 +162,6 @@ export function registerSetupCommands(program, context) {
169
162
  .addOption(new Option('--database-connection-id <id>', 'Existing selected connection id or new connection id')
170
163
  .argParser((value, previous) => [...previous, value])
171
164
  .default([])
172
- .hideHelp())
173
- .addOption(new Option('--new-database-connection-id <id>', 'Connection id for one new database connection')
174
- .argParser((value) => {
175
- if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
176
- throw new InvalidArgumentError(`Unsafe connection id: ${value}`);
177
- }
178
- return value;
179
- })
180
165
  .hideHelp())
181
166
  .addOption(new Option('--database-url <url>', 'URL, env:NAME, or file:/path for one new URL-style database connection').hideHelp())
182
167
  .addOption(new Option('--database-schema <schema>', 'Database schema to include; repeatable')
@@ -238,11 +223,6 @@ export function registerSetupCommands(program, context) {
238
223
  context.setExitCode(1);
239
224
  return;
240
225
  }
241
- if (options.llmModel && options.anthropicModel) {
242
- context.io.stderr.write('Choose only one LLM model flag: --llm-model or --anthropic-model.\n');
243
- context.setExitCode(1);
244
- return;
245
- }
246
226
  if (options.llmBackend &&
247
227
  options.llmBackend !== 'anthropic' &&
248
228
  (options.anthropicApiKeyEnv || options.anthropicApiKeyFile)) {
@@ -285,12 +265,17 @@ export function registerSetupCommands(program, context) {
285
265
  context.setExitCode(1);
286
266
  return;
287
267
  }
288
- const mode = options.new ? 'new' : options.existing ? 'existing' : 'auto';
268
+ const creatingDatabaseConnection = options.database.length > 0 || options.databaseUrl !== undefined;
269
+ if (creatingDatabaseConnection && options.databaseConnectionId.length > 1) {
270
+ context.io.stderr.write('Choose only one new database connection id when configuring a database.\n');
271
+ context.setExitCode(1);
272
+ return;
273
+ }
289
274
  const resolvedAgentScope = options.local ? 'local' : options.global ? 'global' : 'project';
290
275
  await runSetupArgs(context, {
291
276
  command: 'run',
292
277
  projectDir: resolveCommandProjectDir(command),
293
- mode,
278
+ mode: 'auto',
294
279
  agents: options.agents === true,
295
280
  ...(options.target ? { target: options.target } : {}),
296
281
  agentScope: resolvedAgentScope,
@@ -302,7 +287,6 @@ export function registerSetupCommands(program, context) {
302
287
  ...(options.anthropicApiKeyEnv ? { anthropicApiKeyEnv: options.anthropicApiKeyEnv } : {}),
303
288
  ...(options.anthropicApiKeyFile ? { anthropicApiKeyFile: options.anthropicApiKeyFile } : {}),
304
289
  ...(options.llmModel ? { llmModel: options.llmModel } : {}),
305
- ...(options.anthropicModel ? { anthropicModel: options.anthropicModel } : {}),
306
290
  ...(options.vertexProject ? { vertexProject: options.vertexProject } : {}),
307
291
  ...(options.vertexLocation ? { vertexLocation: options.vertexLocation } : {}),
308
292
  skipLlm: options.skipLlm === true,
@@ -311,8 +295,12 @@ export function registerSetupCommands(program, context) {
311
295
  ...(options.embeddingApiKeyFile ? { embeddingApiKeyFile: options.embeddingApiKeyFile } : {}),
312
296
  skipEmbeddings: options.skipEmbeddings === true,
313
297
  ...(options.database.length > 0 ? { databaseDrivers: options.database } : {}),
314
- ...(options.databaseConnectionId.length > 0 ? { databaseConnectionIds: options.databaseConnectionId } : {}),
315
- ...(options.newDatabaseConnectionId ? { databaseConnectionId: options.newDatabaseConnectionId } : {}),
298
+ ...(options.databaseConnectionId.length > 0 && creatingDatabaseConnection
299
+ ? { databaseConnectionId: options.databaseConnectionId[0] }
300
+ : {}),
301
+ ...(options.databaseConnectionId.length > 0 && !creatingDatabaseConnection
302
+ ? { databaseConnectionIds: options.databaseConnectionId }
303
+ : {}),
316
304
  ...(options.databaseUrl ? { databaseUrl: options.databaseUrl } : {}),
317
305
  databaseSchemas: options.databaseSchema,
318
306
  ...(options.enableQueryHistory ? { enableQueryHistory: true } : {}),
@@ -498,16 +498,15 @@ describe('runKtxDoctor', () => {
498
498
  ' adapters:',
499
499
  ' - live-database',
500
500
  ' embeddings:',
501
- ' backend: deterministic',
502
- ' model: deterministic',
501
+ ' backend: none',
503
502
  ' dimensions: 8',
504
503
  '',
505
504
  ].join('\n'), 'utf-8');
506
505
  const testIo = makeIo();
507
506
  await expect(runKtxDoctor({ command: 'project', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, {})).resolves.toBe(0);
508
507
  expect(testIo.stdout()).toContain('Embeddings');
509
- expect(testIo.stdout()).toContain('deterministic');
510
- expect(testIo.stdout()).toContain('semantic search degraded');
508
+ expect(testIo.stdout()).toContain('none');
509
+ expect(testIo.stdout()).toContain('semantic search will be skipped');
511
510
  delete process.env.ANTHROPIC_API_KEY;
512
511
  });
513
512
  describe('command: validate', () => {
@@ -318,8 +318,6 @@ describe('runKtxCli', () => {
318
318
  expect(stdout).not.toContain('setup demo');
319
319
  expect(stdout).not.toContain('setup context');
320
320
  for (const hiddenFlag of [
321
- '--new',
322
- '--existing',
323
321
  '--agent-scope',
324
322
  '--skip-agents',
325
323
  '--llm-backend',
@@ -328,7 +326,6 @@ describe('runKtxCli', () => {
328
326
  '--embedding-backend',
329
327
  '--database ',
330
328
  '--database-connection-id',
331
- '--new-database-connection-id',
332
329
  '--enable-historic-sql',
333
330
  '--historic-sql-min-executions',
334
331
  '--enable-query-history',
@@ -612,8 +609,12 @@ describe('runKtxCli', () => {
612
609
  it('rejects removed setup options', async () => {
613
610
  const setup = vi.fn(async () => 0);
614
611
  const cases = [
612
+ ['setup', '--new'],
613
+ ['setup', '--existing'],
615
614
  ['setup', '--project'],
616
615
  ['setup', '--agent-scope', 'global'],
616
+ ['setup', '--anthropic-model', 'claude-sonnet-4-6'],
617
+ ['setup', '--new-database-connection-id', 'warehouse'],
617
618
  ['setup', '--skip-initial-source-ingest'],
618
619
  ];
619
620
  for (const args of cases) {
@@ -778,7 +779,7 @@ describe('runKtxCli', () => {
778
779
  '--no-input',
779
780
  '--anthropic-api-key-env',
780
781
  'ANTHROPIC_API_KEY',
781
- '--anthropic-model',
782
+ '--llm-model',
782
783
  'claude-sonnet-4-6',
783
784
  ], setupIo.io, { setup })).resolves.toBe(0);
784
785
  expect(setup).toHaveBeenCalledWith(expect.objectContaining({
@@ -787,7 +788,7 @@ describe('runKtxCli', () => {
787
788
  inputMode: 'disabled',
788
789
  cliVersion: '0.1.0-rc.1',
789
790
  anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
790
- anthropicModel: 'claude-sonnet-4-6',
791
+ llmModel: 'claude-sonnet-4-6',
791
792
  skipLlm: false,
792
793
  }), setupIo.io);
793
794
  });
@@ -805,7 +806,7 @@ describe('runKtxCli', () => {
805
806
  'local-gcp-project',
806
807
  '--vertex-location',
807
808
  'us-east5',
808
- '--anthropic-model',
809
+ '--llm-model',
809
810
  'claude-sonnet-4-6',
810
811
  ], setupIo.io, { setup })).resolves.toBe(0);
811
812
  expect(setup).toHaveBeenCalledWith(expect.objectContaining({
@@ -816,7 +817,7 @@ describe('runKtxCli', () => {
816
817
  llmBackend: 'vertex',
817
818
  vertexProject: 'local-gcp-project',
818
819
  vertexLocation: 'us-east5',
819
- anthropicModel: 'claude-sonnet-4-6',
820
+ llmModel: 'claude-sonnet-4-6',
820
821
  skipLlm: false,
821
822
  }), setupIo.io);
822
823
  });
@@ -895,7 +896,7 @@ describe('runKtxCli', () => {
895
896
  '--skip-embeddings',
896
897
  '--database',
897
898
  'postgres',
898
- '--new-database-connection-id',
899
+ '--database-connection-id',
899
900
  'warehouse',
900
901
  '--database-url',
901
902
  'env:DATABASE_URL',
@@ -928,11 +929,26 @@ describe('runKtxCli', () => {
928
929
  it('dispatches setup database connection ids that match former ingest subcommand names', async () => {
929
930
  const testIo = makeIo();
930
931
  const setup = vi.fn(async () => 0);
931
- await expect(runKtxCli(['setup', '--new-database-connection-id', 'status', '--no-input'], testIo.io, { setup })).resolves.toBe(0);
932
+ await expect(runKtxCli(['setup', '--database-connection-id', 'status', '--no-input'], testIo.io, { setup })).resolves.toBe(0);
933
+ expect(setup).toHaveBeenCalledWith(expect.objectContaining({
934
+ command: 'run',
935
+ databaseConnectionIds: ['status'],
936
+ }), testIo.io);
937
+ });
938
+ it('dispatches non-TTY agents setup with target without requiring --no-input or --yes', async () => {
939
+ const testIo = makeIo({ stdoutIsTty: false });
940
+ const setup = vi.fn(async () => 0);
941
+ await expect(runKtxCli(['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-code'], testIo.io, { setup })).resolves.toBe(0);
932
942
  expect(setup).toHaveBeenCalledWith(expect.objectContaining({
933
943
  command: 'run',
934
- databaseConnectionId: 'status',
944
+ projectDir: tempDir,
945
+ agents: true,
946
+ target: 'claude-code',
947
+ agentScope: 'project',
948
+ inputMode: 'auto',
949
+ yes: false,
935
950
  }), testIo.io);
951
+ expect(testIo.stderr()).toBe('');
936
952
  });
937
953
  it('dispatches setup source flags', async () => {
938
954
  const setup = vi.fn(async () => 0);
@@ -46,7 +46,6 @@ export function deepReadinessGaps(config) {
46
46
  const embeddings = config.scan.enrichment.embeddings;
47
47
  if (!embeddings ||
48
48
  embeddings.backend === 'none' ||
49
- embeddings.backend === 'deterministic' ||
50
49
  !embeddings.model ||
51
50
  embeddings.dimensions <= 0) {
52
51
  gaps.push('scan embeddings');
@@ -84,7 +84,7 @@ export async function writeMetabaseConfig(projectDir) {
84
84
  ' adapters:',
85
85
  ' - metabase',
86
86
  ' embeddings:',
87
- ' backend: deterministic',
87
+ ' backend: none',
88
88
  '',
89
89
  ].join('\n'), 'utf-8');
90
90
  }
@@ -418,7 +418,7 @@ export async function runPublicMetabaseSyncModeCase(tempDir, input) {
418
418
  ' adapters:',
419
419
  ' - metabase',
420
420
  ' embeddings:',
421
- ' backend: deterministic',
421
+ ' backend: none',
422
422
  '',
423
423
  ].join('\n'), 'utf-8');
424
424
  const project = await loadKtxProject({ projectDir });
@@ -182,7 +182,7 @@ describe('runKtxIngest', () => {
182
182
  await expect(runKtxSetup({
183
183
  command: 'run',
184
184
  projectDir,
185
- mode: 'new',
185
+ mode: 'auto',
186
186
  agents: false,
187
187
  agentScope: 'project',
188
188
  skipAgents: true,
@@ -227,7 +227,7 @@ describe('runKtxIngest', () => {
227
227
  expect(runIo.stderr()).toContain('ktx ingest requires llm.provider.backend: anthropic, vertex, gateway, or claude-code, or an injected agentRunner.');
228
228
  expect(runIo.stderr()).toContain('Configure a local Claude Code session or API-backed LLM, then rerun ingest:');
229
229
  expect(runIo.stderr()).toContain(`ktx setup --project-dir ${projectDir} --llm-backend claude-code --no-input`);
230
- expect(runIo.stderr()).toContain(`ktx setup --project-dir ${projectDir} --llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY --anthropic-model claude-sonnet-4-6 --no-input`);
230
+ expect(runIo.stderr()).toContain(`ktx setup --project-dir ${projectDir} --llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY --llm-model claude-sonnet-4-6 --no-input`);
231
231
  });
232
232
  it('routes metabase scheduled pulls to the fan-out runner and prints child summaries', async () => {
233
233
  const projectDir = join(tempDir, 'project');
@@ -620,7 +620,7 @@ describe('runKtxIngest', () => {
620
620
  ' adapters:',
621
621
  ' - metabase',
622
622
  ' embeddings:',
623
- ' backend: deterministic',
623
+ ' backend: none',
624
624
  '',
625
625
  ].join('\n'), 'utf-8');
626
626
  const project = await loadKtxProject({ projectDir });
@@ -807,16 +807,9 @@ describe('runKtxIngest', () => {
807
807
  sourceKey: 'historic-sql',
808
808
  body: {
809
809
  workUnits: [],
810
- finalization: {
810
+ postProcessor: {
811
811
  sourceKey: 'historic-sql',
812
812
  status: 'success',
813
- commitSha: 'finalization-sha',
814
- touchedPaths: ['semantic-layer/warehouse/_schema/public.yaml', 'wiki/global/historic-sql-orders.md'],
815
- declaredTouchedSources: [{ connectionId: 'warehouse', sourceName: 'orders' }],
816
- derivedTouchedSources: [{ connectionId: 'warehouse', sourceName: 'orders' }],
817
- declaredChangedWikiPageKeys: ['historic-sql-orders'],
818
- derivedChangedWikiPageKeys: ['historic-sql-orders'],
819
- mismatches: [],
820
813
  result: {
821
814
  tableUsageMerged: 56,
822
815
  staleTablesMarked: 1,
@@ -826,24 +819,7 @@ describe('runKtxIngest', () => {
826
819
  },
827
820
  errors: [],
828
821
  warnings: [],
829
- actions: [
830
- ...Array.from({ length: 57 }, (_, index) => ({
831
- target: 'sl',
832
- type: 'updated',
833
- key: `orders-${index}`,
834
- detail: 'Merged usage',
835
- targetConnectionId: 'warehouse',
836
- rawPaths: ['tables/public/orders.json'],
837
- })),
838
- ...Array.from({ length: 35 }, (_, index) => ({
839
- target: 'wiki',
840
- type: 'updated',
841
- key: `historic-sql-orders-${index}`,
842
- detail: 'Projected pattern',
843
- rawPaths: ['patterns/orders.json'],
844
- })),
845
- ],
846
- provenanceExclusions: [],
822
+ touchedSources: [],
847
823
  },
848
824
  },
849
825
  }),
@@ -1473,7 +1449,7 @@ describe('runKtxIngest', () => {
1473
1449
  ' adapters:',
1474
1450
  ' - looker',
1475
1451
  ' embeddings:',
1476
- ' backend: deterministic',
1452
+ ' backend: none',
1477
1453
  '',
1478
1454
  ].join('\n'), 'utf-8');
1479
1455
  const project = await loadKtxProject({ projectDir });
@@ -6,6 +6,8 @@ import { type KtxManagedPythonInstallPolicy, type ManagedPythonCommandRuntime }
6
6
  import { type ManagedPythonDaemonStartResult } from './managed-python-daemon.js';
7
7
  export interface ManagedLocalEmbeddingsDaemon {
8
8
  baseUrl: string;
9
+ stdoutLog: string;
10
+ stderrLog: string;
9
11
  env: Record<typeof MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV, string>;
10
12
  }
11
13
  export interface ManagedLocalEmbeddingsOptions {
@@ -42,6 +42,8 @@ export async function ensureManagedLocalEmbeddingsDaemon(options) {
42
42
  options.io.stderr.write(`${verb} KTX local embeddings daemon: ${daemon.baseUrl}\n`);
43
43
  return {
44
44
  baseUrl: daemon.baseUrl,
45
+ stdoutLog: daemon.state.stdoutLog,
46
+ stderrLog: daemon.state.stderrLog,
45
47
  env: {
46
48
  [MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV]: daemon.baseUrl,
47
49
  },
@@ -128,6 +128,8 @@ describe('ensureManagedLocalEmbeddingsDaemon', () => {
128
128
  startDaemon,
129
129
  })).resolves.toEqual({
130
130
  baseUrl: 'http://127.0.0.1:61234',
131
+ stdoutLog: '/work/proj/.ktx/runtime/daemon.stdout.log',
132
+ stderrLog: '/work/proj/.ktx/runtime/daemon.stderr.log',
131
133
  env: {
132
134
  [MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV]: 'http://127.0.0.1:61234',
133
135
  },
@@ -4,6 +4,7 @@ import { createServer } from 'node:net';
4
4
  import { dirname, join } from 'node:path';
5
5
  import { setTimeout as delay } from 'node:timers/promises';
6
6
  import { z } from 'zod';
7
+ import { sanitizeChildProxyEnv } from './proxy-env.js';
7
8
  const stateSchema = z.object({
8
9
  schemaVersion: z.literal(1),
9
10
  pid: z.number().int().positive(),
@@ -118,11 +119,11 @@ export async function startKtxMcpDaemon(options) {
118
119
  const child = (options.spawnDaemon ?? defaultSpawnDaemon)(process.execPath, args, {
119
120
  detached: true,
120
121
  stdio: ['ignore', log.fd, log.fd],
121
- env: {
122
+ env: sanitizeChildProxyEnv({
122
123
  ...process.env,
123
124
  KTX_CLI_VERSION: options.cliVersion,
124
125
  ...(options.token ? { KTX_MCP_TOKEN: options.token } : {}),
125
- },
126
+ }),
126
127
  });
127
128
  if (!child.pid) {
128
129
  throw new Error('Failed to start KTX MCP daemon: child process pid was not available.');
@@ -28,6 +28,7 @@ describe('managed MCP daemon lifecycle', () => {
28
28
  await mkdir(projectDir, { recursive: true });
29
29
  });
30
30
  afterEach(async () => {
31
+ vi.unstubAllEnvs();
31
32
  await rm(tempDir, { recursive: true, force: true });
32
33
  });
33
34
  it('uses the spec state and log paths', () => {
@@ -72,6 +73,30 @@ describe('managed MCP daemon lifecycle', () => {
72
73
  }));
73
74
  expect(JSON.stringify(JSON.parse(await readFile(join(projectDir, '.ktx/mcp.json'), 'utf8')))).not.toContain('secret-token');
74
75
  });
76
+ it('sanitizes IPv6 CIDR entries from child NO_PROXY env', async () => {
77
+ vi.stubEnv('NO_PROXY', 'localhost,fd07:b51a:cc66:f0::/64');
78
+ vi.stubEnv('no_proxy', '::1,fd00::/8,*.orb.local');
79
+ const spawnDaemon = vi.fn(() => child(5555));
80
+ await startKtxMcpDaemon({
81
+ projectDir,
82
+ cliVersion: '0.0.0-test',
83
+ host: '127.0.0.1',
84
+ port: 7879,
85
+ allowedHosts: [],
86
+ allowedOrigins: [],
87
+ binPath: '/repo/packages/cli/dist/bin.js',
88
+ spawnDaemon,
89
+ processAlive: vi.fn(() => false),
90
+ portAvailable: vi.fn(async () => true),
91
+ now: () => new Date('2026-05-14T00:00:00.000Z'),
92
+ });
93
+ const env = spawnDaemon.mock.calls[0]?.[2].env;
94
+ if (!env) {
95
+ throw new Error('Expected MCP daemon spawn env');
96
+ }
97
+ expect(env.NO_PROXY).toBe('localhost,::1,*.orb.local');
98
+ expect(env.no_proxy).toBe(env.NO_PROXY);
99
+ });
75
100
  it('returns already-running without spawning when the daemon is alive at the same host/port', async () => {
76
101
  await mkdir(join(projectDir, '.ktx'), { recursive: true });
77
102
  await writeFile(join(projectDir, '.ktx/mcp.json'), `${JSON.stringify(state(projectDir), null, 2)}\n`);
@@ -81,6 +81,7 @@ function installResult(features = ['core']) {
81
81
  asset: {
82
82
  manifest: installedManifest.asset,
83
83
  wheelPath: '/assets/python/kaelio_ktx-0.2.0-py3-none-any.whl',
84
+ requiresPython: { specifier: '>=3.13', minimumVersion: '3.13' },
84
85
  },
85
86
  manifest: installedManifest,
86
87
  };
@@ -5,6 +5,7 @@ import { setTimeout as delay } from 'node:timers/promises';
5
5
  import { promisify } from 'node:util';
6
6
  import { z } from 'zod';
7
7
  import { installManagedPythonRuntime, managedPythonDaemonLayout, runtimeFeatureSchema, } from './managed-python-runtime.js';
8
+ import { sanitizeChildProxyEnv } from './proxy-env.js';
8
9
  const execFileAsync = promisify(execFile);
9
10
  const daemonStateSchema = z.object({
10
11
  schemaVersion: z.literal(1),
@@ -483,10 +484,10 @@ export async function startManagedPythonDaemon(options) {
483
484
  const child = spawnDaemon(installed.manifest.python.daemonExecutable, ['serve-http', '--host', '127.0.0.1', '--port', String(port)], {
484
485
  detached: true,
485
486
  stdio: ['ignore', stdout.fd, stderr.fd],
486
- env: {
487
+ env: sanitizeChildProxyEnv({
487
488
  ...process.env,
488
489
  KTX_DAEMON_VERSION: options.cliVersion,
489
- },
490
+ }),
490
491
  });
491
492
  child.unref();
492
493
  if (!child.pid) {
@@ -59,6 +59,7 @@ function installResult(root, features = ['core']) {
59
59
  asset: {
60
60
  manifest: manifest(root, features).asset,
61
61
  wheelPath: join(root, 'assets', 'python', 'kaelio_ktx-0.2.0-py3-none-any.whl'),
62
+ requiresPython: { specifier: '>=3.13', minimumVersion: '3.13' },
62
63
  },
63
64
  manifest: manifest(root, features),
64
65
  };
@@ -105,6 +106,7 @@ describe('managed Python daemon lifecycle', () => {
105
106
  tempDir = await mkdtemp(join(tmpdir(), 'ktx-managed-daemon-'));
106
107
  });
107
108
  afterEach(async () => {
109
+ vi.unstubAllEnvs();
108
110
  await rm(tempDir, { recursive: true, force: true });
109
111
  });
110
112
  it('reports stopped when no daemon state exists', async () => {
@@ -150,6 +152,24 @@ describe('managed Python daemon lifecycle', () => {
150
152
  stderrLog: layout(tempDir).daemonStderrPath,
151
153
  });
152
154
  });
155
+ it('sanitizes IPv6 CIDR entries from child NO_PROXY env', async () => {
156
+ vi.stubEnv('NO_PROXY', 'localhost,fd07:b51a:cc66:f0::/64,127.0.0.0/8');
157
+ vi.stubEnv('no_proxy', '::1,fd00::/8,*.orb.local');
158
+ const spawnDaemon = makeSpawn(5555);
159
+ await startManagedPythonDaemon({
160
+ ...daemonOptionsBase(tempDir),
161
+ features: ['local-embeddings'],
162
+ installRuntime: vi.fn(async () => installResult(tempDir, ['core', 'local-embeddings'])),
163
+ spawnDaemon,
164
+ fetch: makeFetch(),
165
+ allocatePort: vi.fn(async () => 61234),
166
+ now: () => new Date('2026-05-11T00:00:00.000Z'),
167
+ pollIntervalMs: 1,
168
+ });
169
+ const env = vi.mocked(spawnDaemon).mock.calls[0]?.[2].env;
170
+ expect(env?.NO_PROXY).toBe('localhost,127.0.0.0/8,::1,*.orb.local');
171
+ expect(env?.no_proxy).toBe(env?.NO_PROXY);
172
+ });
153
173
  it('makes a final health probe before reporting startup failure', async () => {
154
174
  const spawnDaemon = makeSpawn(5556);
155
175
  const installRuntime = vi.fn(async () => installResult(tempDir));
@@ -75,6 +75,10 @@ export interface ManagedPythonDaemonLayout extends ManagedPythonRuntimeLayout {
75
75
  export interface ManagedRuntimeAsset {
76
76
  manifest: KtxRuntimeAssetManifest;
77
77
  wheelPath: string;
78
+ requiresPython: {
79
+ specifier: string;
80
+ minimumVersion: string;
81
+ };
78
82
  }
79
83
  export type ManagedPythonRuntimeExec = (command: string, args: string[], options?: {
80
84
  cwd?: string;