@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.
- package/assets/python/{kaelio_ktx-0.1.0rc6-py3-none-any.whl → kaelio_ktx-0.1.1-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/commands/mcp-commands.js +11 -3
- package/dist/commands/mcp-commands.test.js +30 -1
- package/dist/commands/setup-commands.js +14 -26
- package/dist/doctor.test.js +3 -4
- package/dist/index.test.js +26 -10
- package/dist/ingest-depth.js +0 -1
- package/dist/ingest.test-utils.js +2 -2
- package/dist/ingest.test.js +6 -30
- package/dist/managed-local-embeddings.d.ts +2 -0
- package/dist/managed-local-embeddings.js +2 -0
- package/dist/managed-local-embeddings.test.js +2 -0
- package/dist/managed-mcp-daemon.js +3 -2
- package/dist/managed-mcp-daemon.test.js +25 -0
- package/dist/managed-python-command.test.js +1 -0
- package/dist/managed-python-daemon.js +3 -2
- package/dist/managed-python-daemon.test.js +20 -0
- package/dist/managed-python-runtime.d.ts +4 -0
- package/dist/managed-python-runtime.js +47 -3
- package/dist/managed-python-runtime.test.js +51 -21
- package/dist/next-steps.js +1 -1
- package/dist/next-steps.test.js +2 -0
- package/dist/proxy-env.d.ts +1 -0
- package/dist/proxy-env.js +23 -0
- package/dist/proxy-env.test.js +17 -0
- package/dist/runtime-requirements.d.ts +1 -2
- package/dist/runtime-requirements.js +0 -7
- package/dist/runtime-requirements.test.js +2 -2
- package/dist/runtime.test.js +1 -0
- package/dist/setup-agents.d.ts +11 -3
- package/dist/setup-agents.js +400 -135
- package/dist/setup-agents.test.js +394 -62
- package/dist/setup-embeddings.d.ts +1 -0
- package/dist/setup-embeddings.js +28 -6
- package/dist/setup-embeddings.test.js +46 -4
- package/dist/setup-models.d.ts +0 -1
- package/dist/setup-models.js +2 -3
- package/dist/setup-models.test.js +8 -10
- package/dist/setup-project.d.ts +9 -1
- package/dist/setup-project.js +52 -25
- package/dist/setup-project.test.js +8 -8
- package/dist/setup-runtime.d.ts +0 -1
- package/dist/setup-runtime.js +0 -1
- package/dist/setup-runtime.test.js +9 -13
- package/dist/setup.d.ts +4 -2
- package/dist/setup.js +72 -30
- package/dist/setup.test.js +271 -58
- package/dist/sl.test.js +2 -1
- package/dist/standalone-smoke.test.js +2 -3
- package/dist/status-project.js +1 -10
- package/node_modules/@ktx/connector-clickhouse/dist/package-exports.test.js +1 -1
- package/node_modules/@ktx/context/dist/core/git.service.d.ts +0 -1
- package/node_modules/@ktx/context/dist/core/git.service.js +0 -12
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.d.ts +1 -2
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.js +0 -18
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +7 -7
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.d.ts +4 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.js +38 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.d.ts +1 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.js +63 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.d.ts +0 -5
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.js +0 -48
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.test.js +0 -83
- package/node_modules/@ktx/context/dist/ingest/index.d.ts +2 -1
- package/node_modules/@ktx/context/dist/ingest/index.js +1 -0
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.d.ts +0 -2
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.isolated-diff.test.js +0 -166
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.js +45 -235
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.test.js +38 -193
- package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +11 -30
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +5 -1
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.test.js +3 -3
- package/node_modules/@ktx/context/dist/ingest/local-embedding-provider.integration.test.js +9 -10
- package/node_modules/@ktx/context/dist/ingest/local-ingest.js +7 -0
- package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.d.ts +4 -4
- package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.js +1 -1
- package/node_modules/@ktx/context/dist/ingest/memory-flow/types.d.ts +1 -1
- package/node_modules/@ktx/context/dist/ingest/ports.d.ts +20 -1
- package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +2 -73
- package/node_modules/@ktx/context/dist/ingest/report-snapshot.js +0 -27
- package/node_modules/@ktx/context/dist/ingest/reports.d.ts +5 -23
- package/node_modules/@ktx/context/dist/ingest/reports.js +24 -7
- package/node_modules/@ktx/context/dist/ingest/types.d.ts +0 -33
- package/node_modules/@ktx/context/dist/llm/local-config.js +2 -15
- package/node_modules/@ktx/context/dist/llm/local-config.test.js +3 -7
- package/node_modules/@ktx/context/dist/package-exports.test.js +1 -2
- package/node_modules/@ktx/context/dist/project/config.d.ts +0 -5
- package/node_modules/@ktx/context/dist/project/config.js +5 -5
- package/node_modules/@ktx/context/dist/project/config.test.js +4 -7
- package/node_modules/@ktx/context/dist/scan/enrichment-state.test.js +4 -4
- package/node_modules/@ktx/context/dist/scan/index.d.ts +1 -1
- package/node_modules/@ktx/context/dist/scan/local-enrichment.d.ts +2 -6
- package/node_modules/@ktx/context/dist/scan/local-enrichment.js +31 -47
- package/node_modules/@ktx/context/dist/scan/local-enrichment.test.js +35 -18
- package/node_modules/@ktx/context/dist/scan/local-scan.test.js +2 -3
- package/node_modules/@ktx/llm/dist/embedding-provider.d.ts +0 -7
- package/node_modules/@ktx/llm/dist/embedding-provider.js +12 -138
- package/node_modules/@ktx/llm/dist/embedding-provider.test.js +10 -25
- package/node_modules/@ktx/llm/dist/types.d.ts +1 -1
- package/package.json +4 -4
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.d.ts +0 -22
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.js +0 -95
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.js +0 -114
- /package/{node_modules/@ktx/context/dist/ingest/finalization-scope.test.d.ts → dist/proxy-env.test.d.ts} +0 -0
package/assets/python/{kaelio_ktx-0.1.0rc6-py3-none-any.whl → kaelio_ktx-0.1.1-py3-none-any.whl}
RENAMED
|
Binary file
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"distributionName": "kaelio-ktx",
|
|
4
4
|
"normalizedName": "kaelio_ktx",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.1",
|
|
6
6
|
"wheel": {
|
|
7
|
-
"file": "kaelio_ktx-0.1.
|
|
8
|
-
"sha256": "
|
|
9
|
-
"bytes":
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
315
|
-
|
|
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 } : {}),
|
package/dist/doctor.test.js
CHANGED
|
@@ -498,16 +498,15 @@ describe('runKtxDoctor', () => {
|
|
|
498
498
|
' adapters:',
|
|
499
499
|
' - live-database',
|
|
500
500
|
' embeddings:',
|
|
501
|
-
' backend:
|
|
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('
|
|
510
|
-
expect(testIo.stdout()).toContain('semantic search
|
|
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', () => {
|
package/dist/index.test.js
CHANGED
|
@@ -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
|
-
'--
|
|
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
|
-
|
|
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
|
-
'--
|
|
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
|
-
|
|
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
|
-
'--
|
|
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', '--
|
|
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
|
-
|
|
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);
|
package/dist/ingest-depth.js
CHANGED
|
@@ -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:
|
|
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:
|
|
421
|
+
' backend: none',
|
|
422
422
|
'',
|
|
423
423
|
].join('\n'), 'utf-8');
|
|
424
424
|
const project = await loadKtxProject({ projectDir });
|
package/dist/ingest.test.js
CHANGED
|
@@ -182,7 +182,7 @@ describe('runKtxIngest', () => {
|
|
|
182
182
|
await expect(runKtxSetup({
|
|
183
183
|
command: 'run',
|
|
184
184
|
projectDir,
|
|
185
|
-
mode: '
|
|
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 --
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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;
|