@kaelio/ktx 0.1.0 → 0.2.0
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.0-py3-none-any.whl → kaelio_ktx-0.2.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/admin-reindex.d.ts +15 -0
- package/dist/admin-reindex.js +168 -0
- package/dist/admin-reindex.test.js +116 -0
- package/dist/{dev.d.ts → admin.d.ts} +1 -1
- package/dist/{dev.js → admin.js} +14 -12
- package/dist/admin.test.d.ts +1 -0
- package/dist/{dev.test.js → admin.test.js} +36 -31
- package/dist/cli-program.js +7 -7
- package/dist/cli-program.test.js +1 -1
- package/dist/cli-runtime.d.ts +2 -0
- package/dist/commands/connection-commands.js +11 -10
- package/dist/commands/connection-selection.d.ts +11 -0
- package/dist/commands/connection-selection.js +9 -0
- package/dist/commands/ingest-commands.js +32 -26
- package/dist/commands/knowledge-commands.js +17 -28
- package/dist/commands/mcp-commands.js +17 -11
- package/dist/commands/setup-commands.js +14 -26
- package/dist/commands/sl-commands.js +27 -32
- package/dist/doctor.test.js +7 -8
- package/dist/example-smoke.test.js +3 -3
- package/dist/index.test.js +102 -70
- package/dist/ingest-depth.js +0 -1
- package/dist/ingest.test-utils.js +2 -2
- package/dist/ingest.test.js +4 -4
- package/dist/io/print-list.test.js +4 -4
- package/dist/knowledge.js +1 -1
- 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.js +2 -2
- package/dist/managed-python-command.test.js +4 -3
- package/dist/managed-python-daemon.js +3 -2
- package/dist/managed-python-daemon.test.js +20 -0
- package/dist/managed-python-runtime.d.ts +5 -1
- package/dist/managed-python-runtime.js +50 -6
- package/dist/managed-python-runtime.test.js +53 -23
- package/dist/memory-flow-tui.test.js +2 -2
- package/dist/next-steps.d.ts +6 -6
- package/dist/next-steps.js +4 -4
- package/dist/next-steps.test.js +5 -5
- package/dist/print-command-tree.test.js +1 -1
- package/dist/proxy-env.d.ts +1 -0
- package/dist/proxy-env.js +23 -0
- package/dist/proxy-env.test.d.ts +1 -0
- package/dist/proxy-env.test.js +17 -0
- package/dist/public-ingest.js +3 -5
- package/dist/public-ingest.test.js +7 -3
- package/dist/runtime.test.js +2 -1
- package/dist/scan.test.js +2 -2
- package/dist/setup-agents.js +6 -4
- package/dist/setup-agents.test.js +35 -1
- package/dist/setup-embeddings.d.ts +1 -0
- package/dist/setup-embeddings.js +29 -7
- package/dist/setup-embeddings.test.js +49 -7
- 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.test.js +4 -2
- package/dist/setup.d.ts +1 -2
- package/dist/setup.js +21 -5
- package/dist/setup.test.js +160 -43
- package/dist/sl.js +1 -1
- package/dist/sl.test.js +2 -1
- package/dist/standalone-smoke.test.js +8 -5
- package/dist/status-project.js +1 -10
- package/node_modules/@ktx/context/dist/index-sync/index.d.ts +2 -0
- package/node_modules/@ktx/context/dist/index-sync/index.js +1 -0
- package/node_modules/@ktx/context/dist/index-sync/reindex.d.ts +20 -0
- package/node_modules/@ktx/context/dist/index-sync/reindex.js +141 -0
- package/node_modules/@ktx/context/dist/index-sync/reindex.test.d.ts +1 -0
- package/node_modules/@ktx/context/dist/index-sync/reindex.test.js +139 -0
- package/node_modules/@ktx/context/dist/index-sync/types.d.ts +29 -0
- package/node_modules/@ktx/context/dist/index-sync/types.js +1 -0
- package/node_modules/@ktx/context/dist/index.d.ts +1 -0
- package/node_modules/@ktx/context/dist/index.js +1 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +1 -1
- package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +8 -8
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +4 -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/memory-flow/schema.d.ts +2 -2
- package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +2 -2
- 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/memory/local-memory.js +9 -3
- 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/context/dist/sl/ports.d.ts +3 -3
- package/node_modules/@ktx/context/dist/sl/sl-search.service.d.ts +3 -2
- package/node_modules/@ktx/context/dist/sl/sl-search.service.js +47 -45
- package/node_modules/@ktx/context/dist/sl/sl-search.service.test.js +61 -0
- package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.d.ts +4 -3
- package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.js +15 -5
- package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.test.js +24 -0
- package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.d.ts +3 -2
- package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.js +62 -51
- package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.test.js +59 -3
- package/node_modules/@ktx/context/dist/wiki/ports.d.ts +3 -3
- package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.d.ts +33 -0
- package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.js +155 -2
- package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.test.js +26 -0
- package/node_modules/@ktx/context/package.json +5 -0
- 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 +1 -1
- /package/dist/{dev.test.d.ts → admin-reindex.test.d.ts} +0 -0
package/dist/index.test.js
CHANGED
|
@@ -98,9 +98,10 @@ describe('runKtxCli', () => {
|
|
|
98
98
|
await expect(runKtxCli(['--help'], testIo.io)).resolves.toBe(0);
|
|
99
99
|
expect(testIo.stdout()).toContain('Usage: ktx [options] [command]');
|
|
100
100
|
expect(testIo.stdout()).toContain('KTX data agent context layer CLI');
|
|
101
|
-
for (const command of ['setup', 'connection', 'ingest', 'wiki', 'sl', 'status', '
|
|
101
|
+
for (const command of ['setup', 'connection', 'ingest', 'wiki', 'sl', 'status', 'admin']) {
|
|
102
102
|
expect(testIo.stdout()).toContain(`${command}`);
|
|
103
103
|
}
|
|
104
|
+
expect(testIo.stdout()).not.toMatch(/^ dev\s/m);
|
|
104
105
|
expect(testIo.stdout()).not.toMatch(/^ scan\s/m);
|
|
105
106
|
for (const removed of ['demo', 'init', 'connect', 'ask', 'knowledge', 'agent', 'completion', 'serve']) {
|
|
106
107
|
expect(testIo.stdout()).not.toMatch(new RegExp(`^\\s+${removed}(?:\\s|\\[|$)`, 'm'));
|
|
@@ -115,7 +116,7 @@ describe('runKtxCli', () => {
|
|
|
115
116
|
it('routes supported public wiki commands', async () => {
|
|
116
117
|
const knowledge = vi.fn(async () => 0);
|
|
117
118
|
const listIo = makeIo();
|
|
118
|
-
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', '
|
|
119
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', '--json'], listIo.io, { knowledge }))
|
|
119
120
|
.resolves.toBe(0);
|
|
120
121
|
expect(knowledge).toHaveBeenCalledWith({
|
|
121
122
|
command: 'list',
|
|
@@ -124,7 +125,7 @@ describe('runKtxCli', () => {
|
|
|
124
125
|
json: true,
|
|
125
126
|
}, listIo.io);
|
|
126
127
|
const searchIo = makeIo();
|
|
127
|
-
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', '
|
|
128
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', 'revenue', '--limit', '5'], searchIo.io, { knowledge })).resolves.toBe(0);
|
|
128
129
|
expect(knowledge).toHaveBeenLastCalledWith({
|
|
129
130
|
command: 'search',
|
|
130
131
|
projectDir: tempDir,
|
|
@@ -134,7 +135,7 @@ describe('runKtxCli', () => {
|
|
|
134
135
|
limit: 5,
|
|
135
136
|
}, searchIo.io);
|
|
136
137
|
const debugSearchIo = makeIo();
|
|
137
|
-
await expect(runKtxCli(['--project-dir', tempDir, '--debug', 'wiki', '
|
|
138
|
+
await expect(runKtxCli(['--project-dir', tempDir, '--debug', 'wiki', 'revenue'], debugSearchIo.io, { knowledge })).resolves.toBe(0);
|
|
138
139
|
expect(knowledge).toHaveBeenLastCalledWith({
|
|
139
140
|
command: 'search',
|
|
140
141
|
projectDir: tempDir,
|
|
@@ -143,35 +144,32 @@ describe('runKtxCli', () => {
|
|
|
143
144
|
json: false,
|
|
144
145
|
debug: true,
|
|
145
146
|
}, debugSearchIo.io);
|
|
147
|
+
const multiWordIo = makeIo();
|
|
148
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', 'revenue', 'policy'], multiWordIo.io, { knowledge })).resolves.toBe(0);
|
|
149
|
+
expect(knowledge).toHaveBeenLastCalledWith({
|
|
150
|
+
command: 'search',
|
|
151
|
+
projectDir: tempDir,
|
|
152
|
+
query: 'revenue policy',
|
|
153
|
+
userId: 'local',
|
|
154
|
+
json: false,
|
|
155
|
+
}, multiWordIo.io);
|
|
146
156
|
});
|
|
147
|
-
it('rejects
|
|
157
|
+
it('rejects unknown write-style flags on the flattened wiki and sl commands', async () => {
|
|
148
158
|
const knowledge = vi.fn(async () => 0);
|
|
149
|
-
for (const argv of [
|
|
150
|
-
['--project-dir', tempDir, 'wiki', 'read', 'revenue', '--json'],
|
|
151
|
-
['--project-dir', tempDir, 'wiki', 'write', 'revenue', '--summary', 'Revenue', '--content', 'Revenue.'],
|
|
152
|
-
]) {
|
|
153
|
-
const io = makeIo();
|
|
154
|
-
await expect(runKtxCli(argv, io.io, { knowledge })).resolves.toBe(1);
|
|
155
|
-
expect(io.stderr()).toMatch(/unknown command|error:/);
|
|
156
|
-
}
|
|
157
|
-
expect(knowledge).not.toHaveBeenCalled();
|
|
158
|
-
});
|
|
159
|
-
it('rejects removed public sl read/write commands', async () => {
|
|
160
159
|
const sl = vi.fn(async () => 0);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
160
|
+
const wikiIo = makeIo();
|
|
161
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', 'revenue', '--summary', 'Revenue', '--content', 'Revenue.'], wikiIo.io, { knowledge })).resolves.toBe(1);
|
|
162
|
+
expect(wikiIo.stderr()).toMatch(/unknown option|error:/);
|
|
163
|
+
expect(knowledge).not.toHaveBeenCalled();
|
|
164
|
+
const slIo = makeIo();
|
|
165
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'sl', 'orders', '--yaml', 'name: orders'], slIo.io, { sl })).resolves.toBe(1);
|
|
166
|
+
expect(slIo.stderr()).toMatch(/unknown option|error:/);
|
|
169
167
|
expect(sl).not.toHaveBeenCalled();
|
|
170
168
|
});
|
|
171
|
-
it('routes sl search
|
|
169
|
+
it('routes sl search via the flattened query positional and rejects unknown flags', async () => {
|
|
172
170
|
const sl = vi.fn(async () => 0);
|
|
173
171
|
const searchIo = makeIo();
|
|
174
|
-
await expect(runKtxCli(['--project-dir', tempDir, 'sl', '
|
|
172
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'sl', 'revenue', '--connection-id', 'warehouse', '--limit', '5', '--json'], searchIo.io, { sl })).resolves.toBe(0);
|
|
175
173
|
expect(sl).toHaveBeenCalledWith({
|
|
176
174
|
command: 'search',
|
|
177
175
|
projectDir: tempDir,
|
|
@@ -181,9 +179,18 @@ describe('runKtxCli', () => {
|
|
|
181
179
|
json: true,
|
|
182
180
|
output: undefined,
|
|
183
181
|
}, searchIo.io);
|
|
184
|
-
const
|
|
185
|
-
await expect(runKtxCli(['--project-dir', tempDir, 'sl', '
|
|
186
|
-
expect(
|
|
182
|
+
const bareIo = makeIo();
|
|
183
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'sl', '--connection-id', 'warehouse', '--json'], bareIo.io, { sl })).resolves.toBe(0);
|
|
184
|
+
expect(sl).toHaveBeenLastCalledWith({
|
|
185
|
+
command: 'list',
|
|
186
|
+
projectDir: tempDir,
|
|
187
|
+
connectionId: 'warehouse',
|
|
188
|
+
json: true,
|
|
189
|
+
output: undefined,
|
|
190
|
+
}, bareIo.io);
|
|
191
|
+
const unknownIo = makeIo();
|
|
192
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'sl', '--query', 'revenue'], unknownIo.io, { sl })).resolves.toBe(1);
|
|
193
|
+
expect(unknownIo.stderr()).toContain("unknown option '--query'");
|
|
187
194
|
});
|
|
188
195
|
it('routes runtime management commands with the release runtime version', async () => {
|
|
189
196
|
const runtime = vi.fn(async () => 0);
|
|
@@ -193,14 +200,14 @@ describe('runKtxCli', () => {
|
|
|
193
200
|
const stopAllIo = makeIo();
|
|
194
201
|
const statusIo = makeIo();
|
|
195
202
|
const pruneIo = makeIo();
|
|
196
|
-
await expect(runKtxCli(['
|
|
203
|
+
await expect(runKtxCli(['admin', 'runtime', 'install', '--feature', 'local-embeddings', '--force', '--yes'], installIo.io, {
|
|
197
204
|
runtime,
|
|
198
205
|
})).resolves.toBe(0);
|
|
199
|
-
await expect(runKtxCli(['
|
|
200
|
-
await expect(runKtxCli(['
|
|
201
|
-
await expect(runKtxCli(['
|
|
202
|
-
await expect(runKtxCli(['
|
|
203
|
-
await expect(runKtxCli(['
|
|
206
|
+
await expect(runKtxCli(['admin', 'runtime', 'start', '--feature', 'local-embeddings', '--force'], startIo.io, { runtime })).resolves.toBe(0);
|
|
207
|
+
await expect(runKtxCli(['admin', 'runtime', 'stop'], stopIo.io, { runtime })).resolves.toBe(0);
|
|
208
|
+
await expect(runKtxCli(['admin', 'runtime', 'stop', '--all'], stopAllIo.io, { runtime })).resolves.toBe(0);
|
|
209
|
+
await expect(runKtxCli(['admin', 'runtime', 'status', '--json'], statusIo.io, { runtime })).resolves.toBe(0);
|
|
210
|
+
await expect(runKtxCli(['admin', 'runtime', 'prune', '--dry-run'], pruneIo.io, { runtime })).resolves.toBe(1);
|
|
204
211
|
expect(runtime).toHaveBeenNthCalledWith(1, {
|
|
205
212
|
command: 'install',
|
|
206
213
|
cliVersion: '0.1.0-rc.1',
|
|
@@ -262,7 +269,7 @@ describe('runKtxCli', () => {
|
|
|
262
269
|
});
|
|
263
270
|
it('documents runtime stop all in command help', async () => {
|
|
264
271
|
const testIo = makeIo();
|
|
265
|
-
await expect(runKtxCli(['
|
|
272
|
+
await expect(runKtxCli(['admin', 'runtime', 'stop', '--help'], testIo.io)).resolves.toBe(0);
|
|
266
273
|
expect(testIo.stdout()).toContain('--all');
|
|
267
274
|
expect(testIo.stdout()).toContain('Stop all KTX daemon processes recorded or discoverable');
|
|
268
275
|
expect(testIo.stdout()).toContain('on this machine');
|
|
@@ -318,8 +325,6 @@ describe('runKtxCli', () => {
|
|
|
318
325
|
expect(stdout).not.toContain('setup demo');
|
|
319
326
|
expect(stdout).not.toContain('setup context');
|
|
320
327
|
for (const hiddenFlag of [
|
|
321
|
-
'--new',
|
|
322
|
-
'--existing',
|
|
323
328
|
'--agent-scope',
|
|
324
329
|
'--skip-agents',
|
|
325
330
|
'--llm-backend',
|
|
@@ -328,7 +333,6 @@ describe('runKtxCli', () => {
|
|
|
328
333
|
'--embedding-backend',
|
|
329
334
|
'--database ',
|
|
330
335
|
'--database-connection-id',
|
|
331
|
-
'--new-database-connection-id',
|
|
332
336
|
'--enable-historic-sql',
|
|
333
337
|
'--historic-sql-min-executions',
|
|
334
338
|
'--enable-query-history',
|
|
@@ -370,7 +374,7 @@ describe('runKtxCli', () => {
|
|
|
370
374
|
await initKtxProject({ projectDir });
|
|
371
375
|
const commands = [
|
|
372
376
|
['--project-dir', projectDir, 'status', '--json'],
|
|
373
|
-
['--project-dir', projectDir, 'sl', '
|
|
377
|
+
['--project-dir', projectDir, 'sl', '--json'],
|
|
374
378
|
];
|
|
375
379
|
for (const argv of commands) {
|
|
376
380
|
const testIo = makeIo();
|
|
@@ -486,8 +490,8 @@ describe('runKtxCli', () => {
|
|
|
486
490
|
it('rejects removed shell completion commands', async () => {
|
|
487
491
|
const completionIo = makeIo();
|
|
488
492
|
const hiddenIo = makeIo();
|
|
489
|
-
await expect(runKtxCli(['
|
|
490
|
-
await expect(runKtxCli(['
|
|
493
|
+
await expect(runKtxCli(['admin', 'completion', 'zsh'], completionIo.io)).resolves.toBe(1);
|
|
494
|
+
await expect(runKtxCli(['admin', '__complete', '--shell', 'zsh', '--position', '2', '--', 'ktx', 'co'], hiddenIo.io)).resolves.toBe(1);
|
|
491
495
|
expect(completionIo.stderr()).toMatch(/unknown command|error:/);
|
|
492
496
|
expect(hiddenIo.stderr()).toMatch(/unknown command|error:/);
|
|
493
497
|
});
|
|
@@ -612,8 +616,12 @@ describe('runKtxCli', () => {
|
|
|
612
616
|
it('rejects removed setup options', async () => {
|
|
613
617
|
const setup = vi.fn(async () => 0);
|
|
614
618
|
const cases = [
|
|
619
|
+
['setup', '--new'],
|
|
620
|
+
['setup', '--existing'],
|
|
615
621
|
['setup', '--project'],
|
|
616
622
|
['setup', '--agent-scope', 'global'],
|
|
623
|
+
['setup', '--anthropic-model', 'claude-sonnet-4-6'],
|
|
624
|
+
['setup', '--new-database-connection-id', 'warehouse'],
|
|
617
625
|
['setup', '--skip-initial-source-ingest'],
|
|
618
626
|
];
|
|
619
627
|
for (const args of cases) {
|
|
@@ -635,7 +643,8 @@ describe('runKtxCli', () => {
|
|
|
635
643
|
expect(testIo.stdout()).toContain('--query-history');
|
|
636
644
|
expect(testIo.stdout()).toContain('--no-query-history');
|
|
637
645
|
expect(testIo.stdout()).toContain('--query-history-window-days <days>');
|
|
638
|
-
expect(testIo.stdout()).toContain('text');
|
|
646
|
+
expect(testIo.stdout()).toContain('--text');
|
|
647
|
+
expect(testIo.stdout()).toContain('--file');
|
|
639
648
|
expect(testIo.stdout()).not.toMatch(/^ status\s/m);
|
|
640
649
|
expect(testIo.stdout()).not.toMatch(/^ replay\s/m);
|
|
641
650
|
expect(testIo.stdout()).not.toMatch(/^ run\s/m);
|
|
@@ -651,7 +660,6 @@ describe('runKtxCli', () => {
|
|
|
651
660
|
'--project-dir',
|
|
652
661
|
tempDir,
|
|
653
662
|
'ingest',
|
|
654
|
-
'text',
|
|
655
663
|
'--text',
|
|
656
664
|
'Revenue means gross receipts.',
|
|
657
665
|
'--text',
|
|
@@ -674,37 +682,46 @@ describe('runKtxCli', () => {
|
|
|
674
682
|
}, testIo.io);
|
|
675
683
|
expect(testIo.stderr()).toBe('');
|
|
676
684
|
});
|
|
677
|
-
it('
|
|
685
|
+
it('rejects a positional connection id when --text is supplied', async () => {
|
|
678
686
|
const textIngest = vi.fn(async () => 0);
|
|
687
|
+
const publicIngest = vi.fn(async () => 0);
|
|
679
688
|
const testIo = makeIo();
|
|
680
|
-
await expect(runKtxCli(['ingest', '
|
|
681
|
-
expect(testIo.stdout()).toContain('Usage: ktx ingest text [options] [files...]');
|
|
682
|
-
expect(testIo.stdout()).toContain('--text <content>');
|
|
683
|
-
expect(testIo.stdout()).toContain('--connection-id <connectionId>');
|
|
684
|
-
expect(testIo.stdout()).toContain('--user-id <id>');
|
|
685
|
-
expect(testIo.stdout()).toContain('--fail-fast');
|
|
686
|
-
expect(testIo.stdout()).not.toContain('--manifest');
|
|
689
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'ingest', 'warehouse', '--text', 'hello'], testIo.io, { textIngest, publicIngest })).resolves.toBe(1);
|
|
687
690
|
expect(textIngest).not.toHaveBeenCalled();
|
|
691
|
+
expect(publicIngest).not.toHaveBeenCalled();
|
|
692
|
+
expect(testIo.stderr()).toMatch(/--text\/--file does not accept a positional connection id/);
|
|
688
693
|
});
|
|
689
|
-
it('
|
|
694
|
+
it('treats bare ingest as ingest --all', async () => {
|
|
695
|
+
const publicIngest = vi.fn().mockResolvedValue(0);
|
|
696
|
+
const testIo = makeIo();
|
|
697
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'ingest', '--no-input'], testIo.io, { publicIngest })).resolves.toBe(0);
|
|
698
|
+
expect(publicIngest).toHaveBeenCalledWith(expect.objectContaining({
|
|
699
|
+
command: 'run',
|
|
700
|
+
projectDir: tempDir,
|
|
701
|
+
all: true,
|
|
702
|
+
}), testIo.io);
|
|
703
|
+
const args = publicIngest.mock.calls[0]?.[0];
|
|
704
|
+
expect(args.targetConnectionId).toBeUndefined();
|
|
705
|
+
});
|
|
706
|
+
it('rejects old adapter-backed ingest flags at the top level and under admin', async () => {
|
|
690
707
|
const rootRunIo = makeIo();
|
|
691
708
|
const devRunIo = makeIo();
|
|
692
709
|
const publicIngest = vi.fn(async () => 0);
|
|
693
710
|
await expect(runKtxCli(['ingest', 'run', '--connection-id', 'warehouse', '--adapter', 'metabase'], rootRunIo.io, {
|
|
694
711
|
publicIngest,
|
|
695
712
|
})).resolves.toBe(1);
|
|
696
|
-
await expect(runKtxCli(['
|
|
713
|
+
await expect(runKtxCli(['admin', 'ingest', 'run', '--connection-id', 'warehouse', '--adapter', 'metabase'], devRunIo.io, {
|
|
697
714
|
publicIngest,
|
|
698
715
|
})).resolves.toBe(1);
|
|
699
716
|
expect(publicIngest).not.toHaveBeenCalled();
|
|
700
717
|
expect(rootRunIo.stderr()).toMatch(/unknown option '--connection-id'|error:/);
|
|
701
718
|
expect(devRunIo.stderr()).toMatch(/unknown command|error:/);
|
|
702
719
|
});
|
|
703
|
-
it('rejects removed
|
|
720
|
+
it('rejects removed admin doctor and removed ingest parser cases', async () => {
|
|
704
721
|
const doctor = vi.fn(async () => 0);
|
|
705
722
|
const doctorIo = makeIo();
|
|
706
723
|
const ingestRunIo = makeIo();
|
|
707
|
-
await expect(runKtxCli(['
|
|
724
|
+
await expect(runKtxCli(['admin', 'doctor', 'setup', '--json', '--no-input'], doctorIo.io, { doctor })).resolves.toBe(1);
|
|
708
725
|
await expect(runKtxCli([
|
|
709
726
|
'ingest',
|
|
710
727
|
'run',
|
|
@@ -778,7 +795,7 @@ describe('runKtxCli', () => {
|
|
|
778
795
|
'--no-input',
|
|
779
796
|
'--anthropic-api-key-env',
|
|
780
797
|
'ANTHROPIC_API_KEY',
|
|
781
|
-
'--
|
|
798
|
+
'--llm-model',
|
|
782
799
|
'claude-sonnet-4-6',
|
|
783
800
|
], setupIo.io, { setup })).resolves.toBe(0);
|
|
784
801
|
expect(setup).toHaveBeenCalledWith(expect.objectContaining({
|
|
@@ -787,7 +804,7 @@ describe('runKtxCli', () => {
|
|
|
787
804
|
inputMode: 'disabled',
|
|
788
805
|
cliVersion: '0.1.0-rc.1',
|
|
789
806
|
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
|
790
|
-
|
|
807
|
+
llmModel: 'claude-sonnet-4-6',
|
|
791
808
|
skipLlm: false,
|
|
792
809
|
}), setupIo.io);
|
|
793
810
|
});
|
|
@@ -805,7 +822,7 @@ describe('runKtxCli', () => {
|
|
|
805
822
|
'local-gcp-project',
|
|
806
823
|
'--vertex-location',
|
|
807
824
|
'us-east5',
|
|
808
|
-
'--
|
|
825
|
+
'--llm-model',
|
|
809
826
|
'claude-sonnet-4-6',
|
|
810
827
|
], setupIo.io, { setup })).resolves.toBe(0);
|
|
811
828
|
expect(setup).toHaveBeenCalledWith(expect.objectContaining({
|
|
@@ -816,7 +833,7 @@ describe('runKtxCli', () => {
|
|
|
816
833
|
llmBackend: 'vertex',
|
|
817
834
|
vertexProject: 'local-gcp-project',
|
|
818
835
|
vertexLocation: 'us-east5',
|
|
819
|
-
|
|
836
|
+
llmModel: 'claude-sonnet-4-6',
|
|
820
837
|
skipLlm: false,
|
|
821
838
|
}), setupIo.io);
|
|
822
839
|
});
|
|
@@ -895,7 +912,7 @@ describe('runKtxCli', () => {
|
|
|
895
912
|
'--skip-embeddings',
|
|
896
913
|
'--database',
|
|
897
914
|
'postgres',
|
|
898
|
-
'--
|
|
915
|
+
'--database-connection-id',
|
|
899
916
|
'warehouse',
|
|
900
917
|
'--database-url',
|
|
901
918
|
'env:DATABASE_URL',
|
|
@@ -928,12 +945,27 @@ describe('runKtxCli', () => {
|
|
|
928
945
|
it('dispatches setup database connection ids that match former ingest subcommand names', async () => {
|
|
929
946
|
const testIo = makeIo();
|
|
930
947
|
const setup = vi.fn(async () => 0);
|
|
931
|
-
await expect(runKtxCli(['setup', '--
|
|
948
|
+
await expect(runKtxCli(['setup', '--database-connection-id', 'status', '--no-input'], testIo.io, { setup })).resolves.toBe(0);
|
|
932
949
|
expect(setup).toHaveBeenCalledWith(expect.objectContaining({
|
|
933
950
|
command: 'run',
|
|
934
|
-
|
|
951
|
+
databaseConnectionIds: ['status'],
|
|
935
952
|
}), testIo.io);
|
|
936
953
|
});
|
|
954
|
+
it('dispatches non-TTY agents setup with target without requiring --no-input or --yes', async () => {
|
|
955
|
+
const testIo = makeIo({ stdoutIsTty: false });
|
|
956
|
+
const setup = vi.fn(async () => 0);
|
|
957
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-code'], testIo.io, { setup })).resolves.toBe(0);
|
|
958
|
+
expect(setup).toHaveBeenCalledWith(expect.objectContaining({
|
|
959
|
+
command: 'run',
|
|
960
|
+
projectDir: tempDir,
|
|
961
|
+
agents: true,
|
|
962
|
+
target: 'claude-code',
|
|
963
|
+
agentScope: 'project',
|
|
964
|
+
inputMode: 'auto',
|
|
965
|
+
yes: false,
|
|
966
|
+
}), testIo.io);
|
|
967
|
+
expect(testIo.stderr()).toBe('');
|
|
968
|
+
});
|
|
937
969
|
it('dispatches setup source flags', async () => {
|
|
938
970
|
const setup = vi.fn(async () => 0);
|
|
939
971
|
const testIo = makeIo();
|
|
@@ -1221,10 +1253,10 @@ describe('runKtxCli', () => {
|
|
|
1221
1253
|
], serveIo.io)).resolves.toBe(1);
|
|
1222
1254
|
expect(serveIo.stderr()).toMatch(/unknown command|error:/);
|
|
1223
1255
|
});
|
|
1224
|
-
it('prints
|
|
1256
|
+
it('prints admin help for bare admin commands', async () => {
|
|
1225
1257
|
const testIo = makeIo();
|
|
1226
|
-
await expect(runKtxCli(['
|
|
1227
|
-
expect(testIo.stdout()).toContain('Usage: ktx
|
|
1258
|
+
await expect(runKtxCli(['admin'], testIo.io)).resolves.toBe(0);
|
|
1259
|
+
expect(testIo.stdout()).toContain('Usage: ktx admin [options] [command]');
|
|
1228
1260
|
expect(testIo.stdout()).toContain('Low-level project initialization');
|
|
1229
1261
|
expect(testIo.stdout()).toContain('init');
|
|
1230
1262
|
expect(testIo.stdout()).toContain('runtime');
|
|
@@ -1235,20 +1267,20 @@ describe('runKtxCli', () => {
|
|
|
1235
1267
|
expect(testIo.stdout()).not.toContain('knowledge');
|
|
1236
1268
|
expect(testIo.stderr()).toBe('');
|
|
1237
1269
|
});
|
|
1238
|
-
it('rejects removed
|
|
1270
|
+
it('rejects removed admin command groups without invoking execution', async () => {
|
|
1239
1271
|
for (const command of ['scan', 'ingest', 'mapping']) {
|
|
1240
1272
|
const testIo = makeIo();
|
|
1241
1273
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
|
1242
1274
|
const sl = vi.fn().mockResolvedValue(0);
|
|
1243
|
-
await expect(runKtxCli(['
|
|
1275
|
+
await expect(runKtxCli(['admin', command], testIo.io, { publicIngest, sl })).resolves.toBe(1);
|
|
1244
1276
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
|
1245
1277
|
expect(publicIngest).not.toHaveBeenCalled();
|
|
1246
1278
|
expect(sl).not.toHaveBeenCalled();
|
|
1247
1279
|
}
|
|
1248
1280
|
});
|
|
1249
|
-
it('rejects removed reserved
|
|
1281
|
+
it('rejects removed reserved admin subcommands', async () => {
|
|
1250
1282
|
const testIo = makeIo();
|
|
1251
|
-
await expect(runKtxCli(['
|
|
1283
|
+
await expect(runKtxCli(['admin', 'artifacts'], testIo.io)).resolves.toBe(1);
|
|
1252
1284
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
|
1253
1285
|
});
|
|
1254
1286
|
it('rejects mutually exclusive public ingest output modes before invoking runners', async () => {
|
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 });
|
|
@@ -1449,7 +1449,7 @@ describe('runKtxIngest', () => {
|
|
|
1449
1449
|
' adapters:',
|
|
1450
1450
|
' - looker',
|
|
1451
1451
|
' embeddings:',
|
|
1452
|
-
' backend:
|
|
1452
|
+
' backend: none',
|
|
1453
1453
|
'',
|
|
1454
1454
|
].join('\n'), 'utf-8');
|
|
1455
1455
|
const project = await loadKtxProject({ projectDir });
|
|
@@ -60,13 +60,13 @@ describe('printList — plain mode', () => {
|
|
|
60
60
|
mode: 'plain',
|
|
61
61
|
command: 'sl search',
|
|
62
62
|
emptyMessage: 'No sources matched "foo"',
|
|
63
|
-
emptyHint: 'Run `ktx sl
|
|
63
|
+
emptyHint: 'Run `ktx sl` to see available sources.',
|
|
64
64
|
unit: 'source',
|
|
65
65
|
io: r.io,
|
|
66
66
|
});
|
|
67
67
|
expect(r.out()).toBe('');
|
|
68
68
|
expect(r.err()).toBe('No sources matched "foo"\n' +
|
|
69
|
-
'Run `ktx sl
|
|
69
|
+
'Run `ktx sl` to see available sources.\n');
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
describe('printList — json mode', () => {
|
|
@@ -162,13 +162,13 @@ describe('printList — pretty mode', () => {
|
|
|
162
162
|
mode: 'pretty',
|
|
163
163
|
command: 'sl search',
|
|
164
164
|
emptyMessage: 'No sources matched "foo"',
|
|
165
|
-
emptyHint: 'Run `ktx sl
|
|
165
|
+
emptyHint: 'Run `ktx sl` to see available sources.',
|
|
166
166
|
unit: 'source',
|
|
167
167
|
io: r.io,
|
|
168
168
|
});
|
|
169
169
|
const out = stripAnsi(r.out());
|
|
170
170
|
expect(out).toContain('No sources matched "foo"');
|
|
171
|
-
expect(out).toContain('Run `ktx sl
|
|
171
|
+
expect(out).toContain('Run `ktx sl` to see available sources.');
|
|
172
172
|
});
|
|
173
173
|
it('singularizes the footer when there is one row', () => {
|
|
174
174
|
const r = recorder();
|
package/dist/knowledge.js
CHANGED
|
@@ -74,7 +74,7 @@ export async function runKtxKnowledge(args, io = process, deps = {}) {
|
|
|
74
74
|
}
|
|
75
75
|
const mode = resolveOutputMode({ explicit: args.output, json: args.json, io });
|
|
76
76
|
let emptyMessage = `No local wiki pages matched "${args.query}"`;
|
|
77
|
-
let emptyHint = 'Run `ktx wiki
|
|
77
|
+
let emptyHint = 'Run `ktx wiki` to inspect available pages.';
|
|
78
78
|
if (results.length === 0 && mode !== 'json') {
|
|
79
79
|
const pages = await listLocalKnowledgePages(project, { userId: args.userId });
|
|
80
80
|
if (pages.length === 0) {
|
|
@@ -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`);
|
|
@@ -12,8 +12,8 @@ export function runtimeInstallPolicyFromFlags(options) {
|
|
|
12
12
|
}
|
|
13
13
|
export function managedRuntimeInstallCommand(feature) {
|
|
14
14
|
return feature === 'local-embeddings'
|
|
15
|
-
? 'ktx
|
|
16
|
-
: 'ktx
|
|
15
|
+
? 'ktx admin runtime install --feature local-embeddings --yes'
|
|
16
|
+
: 'ktx admin runtime install --yes';
|
|
17
17
|
}
|
|
18
18
|
function installPrompt(feature) {
|
|
19
19
|
const label = feature === 'local-embeddings' ? 'local embeddings Python runtime' : 'core Python runtime';
|
|
@@ -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
|
};
|
|
@@ -97,8 +98,8 @@ function makeSpinnerEvents() {
|
|
|
97
98
|
}
|
|
98
99
|
describe('managedRuntimeInstallCommand', () => {
|
|
99
100
|
it('prints the exact command for each managed runtime feature', () => {
|
|
100
|
-
expect(managedRuntimeInstallCommand('core')).toBe('ktx
|
|
101
|
-
expect(managedRuntimeInstallCommand('local-embeddings')).toBe('ktx
|
|
101
|
+
expect(managedRuntimeInstallCommand('core')).toBe('ktx admin runtime install --yes');
|
|
102
|
+
expect(managedRuntimeInstallCommand('local-embeddings')).toBe('ktx admin runtime install --feature local-embeddings --yes');
|
|
102
103
|
});
|
|
103
104
|
});
|
|
104
105
|
describe('runtimeInstallPolicyFromFlags', () => {
|
|
@@ -176,7 +177,7 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
|
|
|
176
177
|
io: io.io,
|
|
177
178
|
readStatus: vi.fn(async () => missingStatus()),
|
|
178
179
|
installRuntime,
|
|
179
|
-
})).rejects.toThrow('KTX Python runtime is required for this command. Run: ktx
|
|
180
|
+
})).rejects.toThrow('KTX Python runtime is required for this command. Run: ktx admin runtime install --yes');
|
|
180
181
|
expect(installRuntime).not.toHaveBeenCalled();
|
|
181
182
|
});
|
|
182
183
|
it('installs the core runtime without prompting when policy is auto', async () => {
|