@kaelio/ktx 0.1.1 → 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.1-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/sl-commands.js +27 -32
- package/dist/doctor.test.js +4 -4
- package/dist/example-smoke.test.js +3 -3
- package/dist/index.test.js +76 -60
- package/dist/io/print-list.test.js +4 -4
- package/dist/knowledge.js +1 -1
- package/dist/managed-python-command.js +2 -2
- package/dist/managed-python-command.test.js +3 -3
- package/dist/managed-python-runtime.d.ts +1 -1
- package/dist/managed-python-runtime.js +3 -3
- package/dist/managed-python-runtime.test.js +2 -2
- 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/public-ingest.js +3 -5
- package/dist/public-ingest.test.js +7 -3
- package/dist/runtime.test.js +1 -1
- package/dist/scan.test.js +2 -2
- package/dist/setup-agents.js +3 -3
- package/dist/setup-agents.test.js +1 -1
- package/dist/setup-embeddings.js +1 -1
- package/dist/setup-embeddings.test.js +3 -3
- package/dist/setup-runtime.test.js +2 -2
- package/dist/sl.js +1 -1
- package/dist/standalone-smoke.test.js +6 -2
- 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/local-bundle-runtime.js +3 -0
- 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/memory/local-memory.js +9 -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/package.json +1 -1
- /package/dist/{dev.test.d.ts → admin-reindex.test.d.ts} +0 -0
|
@@ -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) {
|
|
@@ -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';
|
|
@@ -98,8 +98,8 @@ function makeSpinnerEvents() {
|
|
|
98
98
|
}
|
|
99
99
|
describe('managedRuntimeInstallCommand', () => {
|
|
100
100
|
it('prints the exact command for each managed runtime feature', () => {
|
|
101
|
-
expect(managedRuntimeInstallCommand('core')).toBe('ktx
|
|
102
|
-
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');
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
105
|
describe('runtimeInstallPolicyFromFlags', () => {
|
|
@@ -177,7 +177,7 @@ describe('createManagedPythonSemanticLayerComputePort', () => {
|
|
|
177
177
|
io: io.io,
|
|
178
178
|
readStatus: vi.fn(async () => missingStatus()),
|
|
179
179
|
installRuntime,
|
|
180
|
-
})).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');
|
|
181
181
|
expect(installRuntime).not.toHaveBeenCalled();
|
|
182
182
|
});
|
|
183
183
|
it('installs the core runtime without prompting when policy is auto', async () => {
|
|
@@ -112,7 +112,7 @@ export interface ManagedPythonRuntimeDoctorCheck {
|
|
|
112
112
|
detail: string;
|
|
113
113
|
fix?: string;
|
|
114
114
|
}
|
|
115
|
-
export declare const MISSING_UV_RUNTIME_INSTALL_MESSAGE = "uv is required to install the KTX Python runtime. KTX does not download uv automatically. Install uv, make sure it is on PATH, and retry: ktx
|
|
115
|
+
export declare const MISSING_UV_RUNTIME_INSTALL_MESSAGE = "uv is required to install the KTX Python runtime. KTX does not download uv automatically. Install uv, make sure it is on PATH, and retry: ktx admin runtime install --yes";
|
|
116
116
|
export declare function managedPythonRuntimeLayout(options: ManagedPythonRuntimeLayoutOptions): ManagedPythonRuntimeLayout;
|
|
117
117
|
export declare function managedPythonDaemonLayout(options: ManagedPythonDaemonLayoutOptions): ManagedPythonDaemonLayout;
|
|
118
118
|
export declare function verifyRuntimeAsset(input: {
|
|
@@ -32,7 +32,7 @@ const installedRuntimeManifestSchema = z.object({
|
|
|
32
32
|
}),
|
|
33
33
|
installLog: z.string().min(1),
|
|
34
34
|
});
|
|
35
|
-
export const MISSING_UV_RUNTIME_INSTALL_MESSAGE = 'uv is required to install the KTX Python runtime. KTX does not download uv automatically. Install uv, make sure it is on PATH, and retry: ktx
|
|
35
|
+
export const MISSING_UV_RUNTIME_INSTALL_MESSAGE = 'uv is required to install the KTX Python runtime. KTX does not download uv automatically. Install uv, make sure it is on PATH, and retry: ktx admin runtime install --yes';
|
|
36
36
|
function defaultAssetDir() {
|
|
37
37
|
return fileURLToPath(new URL('../assets/python/', import.meta.url));
|
|
38
38
|
}
|
|
@@ -331,7 +331,7 @@ export async function doctorManagedPythonRuntime(options) {
|
|
|
331
331
|
id: 'uv',
|
|
332
332
|
label: 'uv',
|
|
333
333
|
detail: error instanceof Error ? error.message : String(error),
|
|
334
|
-
fix: 'Install uv, make sure it is on PATH, and run: ktx
|
|
334
|
+
fix: 'Install uv, make sure it is on PATH, and run: ktx admin runtime install --yes',
|
|
335
335
|
}));
|
|
336
336
|
}
|
|
337
337
|
try {
|
|
@@ -351,7 +351,7 @@ export async function doctorManagedPythonRuntime(options) {
|
|
|
351
351
|
id: 'runtime',
|
|
352
352
|
label: 'Managed Python runtime',
|
|
353
353
|
detail: status.detail,
|
|
354
|
-
...(status.kind === 'ready' ? {} : { fix: 'Run: ktx
|
|
354
|
+
...(status.kind === 'ready' ? {} : { fix: 'Run: ktx admin runtime install --yes' }),
|
|
355
355
|
}));
|
|
356
356
|
return checks;
|
|
357
357
|
}
|
|
@@ -402,7 +402,7 @@ describe('doctorManagedPythonRuntime', () => {
|
|
|
402
402
|
['asset', 'pass'],
|
|
403
403
|
['runtime', 'fail'],
|
|
404
404
|
]);
|
|
405
|
-
expect(checks[2]?.fix).toBe('Run: ktx
|
|
405
|
+
expect(checks[2]?.fix).toBe('Run: ktx admin runtime install --yes');
|
|
406
406
|
});
|
|
407
407
|
it('reports uv as a hard prerequisite when uv is missing', async () => {
|
|
408
408
|
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
@@ -420,7 +420,7 @@ describe('doctorManagedPythonRuntime', () => {
|
|
|
420
420
|
label: 'uv',
|
|
421
421
|
status: 'fail',
|
|
422
422
|
detail: MISSING_UV_RUNTIME_INSTALL_MESSAGE,
|
|
423
|
-
fix: 'Install uv, make sure it is on PATH, and run: ktx
|
|
423
|
+
fix: 'Install uv, make sure it is on PATH, and run: ktx admin runtime install --yes',
|
|
424
424
|
});
|
|
425
425
|
});
|
|
426
426
|
});
|
|
@@ -145,8 +145,8 @@ describe('MemoryFlowTuiApp', () => {
|
|
|
145
145
|
expect(frame).toContain('order lifecycle');
|
|
146
146
|
expect(frame).toContain('customer metrics');
|
|
147
147
|
expect(frame).toContain('KTX finished ingesting your data');
|
|
148
|
-
expect(frame).toContain('ktx sl
|
|
149
|
-
expect(frame).toContain('ktx wiki
|
|
148
|
+
expect(frame).toContain('ktx sl');
|
|
149
|
+
expect(frame).toContain('ktx wiki');
|
|
150
150
|
expect(frame).not.toContain('ktx serve --mcp stdio --user-id local');
|
|
151
151
|
expect(frame).not.toContain(['ktx', 'ask'].join(' '));
|
|
152
152
|
expect(frame).not.toContain(['ktx', 'mcp'].join(' '));
|
package/dist/next-steps.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const KTX_CONTEXT_BUILD_COMMANDS: readonly [{
|
|
2
|
-
readonly command: "ktx ingest
|
|
3
|
-
readonly description: "Build or refresh agent-ready context from configured connections";
|
|
2
|
+
readonly command: "ktx ingest";
|
|
3
|
+
readonly description: "Build or refresh agent-ready context from all configured connections";
|
|
4
4
|
}, {
|
|
5
5
|
readonly command: "ktx status";
|
|
6
6
|
readonly description: "Check setup and context readiness";
|
|
@@ -9,20 +9,20 @@ export declare const KTX_NEXT_STEP_DIRECT_COMMANDS: readonly [{
|
|
|
9
9
|
readonly command: "ktx status --json";
|
|
10
10
|
readonly description: "Verify project setup and context readiness";
|
|
11
11
|
}, {
|
|
12
|
-
readonly command: "ktx sl
|
|
12
|
+
readonly command: "ktx sl";
|
|
13
13
|
readonly description: "Inspect generated semantic-layer sources";
|
|
14
14
|
}, {
|
|
15
|
-
readonly command: "ktx wiki
|
|
15
|
+
readonly command: "ktx wiki";
|
|
16
16
|
readonly description: "Inspect generated wiki pages";
|
|
17
17
|
}];
|
|
18
18
|
export declare const KTX_NEXT_STEP_COMMANDS: readonly [{
|
|
19
19
|
readonly command: "ktx status --json";
|
|
20
20
|
readonly description: "Verify project setup and context readiness";
|
|
21
21
|
}, {
|
|
22
|
-
readonly command: "ktx sl
|
|
22
|
+
readonly command: "ktx sl";
|
|
23
23
|
readonly description: "Inspect generated semantic-layer sources";
|
|
24
24
|
}, {
|
|
25
|
-
readonly command: "ktx wiki
|
|
25
|
+
readonly command: "ktx wiki";
|
|
26
26
|
readonly description: "Inspect generated wiki pages";
|
|
27
27
|
}];
|
|
28
28
|
export declare const KTX_NEXT_STEP_COMMAND_WIDTH: number;
|
package/dist/next-steps.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const KTX_CONTEXT_BUILD_COMMANDS = [
|
|
2
2
|
{
|
|
3
|
-
command: 'ktx ingest
|
|
4
|
-
description: 'Build or refresh agent-ready context from configured connections',
|
|
3
|
+
command: 'ktx ingest',
|
|
4
|
+
description: 'Build or refresh agent-ready context from all configured connections',
|
|
5
5
|
},
|
|
6
6
|
{
|
|
7
7
|
command: 'ktx status',
|
|
@@ -14,11 +14,11 @@ export const KTX_NEXT_STEP_DIRECT_COMMANDS = [
|
|
|
14
14
|
description: 'Verify project setup and context readiness',
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
|
-
command: 'ktx sl
|
|
17
|
+
command: 'ktx sl',
|
|
18
18
|
description: 'Inspect generated semantic-layer sources',
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
|
-
command: 'ktx wiki
|
|
21
|
+
command: 'ktx wiki',
|
|
22
22
|
description: 'Inspect generated wiki pages',
|
|
23
23
|
},
|
|
24
24
|
];
|
package/dist/next-steps.test.js
CHANGED
|
@@ -4,8 +4,8 @@ describe('KTX demo next steps', () => {
|
|
|
4
4
|
it('uses supported context-build commands before agent usage', () => {
|
|
5
5
|
expect(KTX_CONTEXT_BUILD_COMMANDS).toEqual([
|
|
6
6
|
{
|
|
7
|
-
command: 'ktx ingest
|
|
8
|
-
description: 'Build or refresh agent-ready context from configured connections',
|
|
7
|
+
command: 'ktx ingest',
|
|
8
|
+
description: 'Build or refresh agent-ready context from all configured connections',
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
command: 'ktx status',
|
|
@@ -20,11 +20,11 @@ describe('KTX demo next steps', () => {
|
|
|
20
20
|
description: 'Verify project setup and context readiness',
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
|
-
command: 'ktx sl
|
|
23
|
+
command: 'ktx sl',
|
|
24
24
|
description: 'Inspect generated semantic-layer sources',
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
-
command: 'ktx wiki
|
|
27
|
+
command: 'ktx wiki',
|
|
28
28
|
description: 'Inspect generated wiki pages',
|
|
29
29
|
},
|
|
30
30
|
]);
|
|
@@ -54,7 +54,7 @@ describe('KTX demo next steps', () => {
|
|
|
54
54
|
}).join('\n');
|
|
55
55
|
expect(rendered).toContain('Build KTX context next.');
|
|
56
56
|
expect(rendered).toContain('Run ingest to build database schema context before context-source ingest.');
|
|
57
|
-
expect(rendered).toContain('ktx ingest
|
|
57
|
+
expect(rendered).toContain('ktx ingest');
|
|
58
58
|
expect(rendered).not.toContain('resume');
|
|
59
59
|
expect(rendered).not.toContain('scan');
|
|
60
60
|
expect(rendered).toContain('ktx status');
|
|
@@ -8,7 +8,7 @@ describe('renderKtxCommandTree', () => {
|
|
|
8
8
|
const topLevel = lines
|
|
9
9
|
.filter((line) => /^ {2}[├└]── \S/.test(line))
|
|
10
10
|
.map((line) => line.replace(/^ {2}[├└]── /, '').trim().split(' ')[0]);
|
|
11
|
-
for (const expected of ['setup', 'connection', 'ingest', 'sl', 'mcp', '
|
|
11
|
+
for (const expected of ['setup', 'connection', 'ingest', 'sl', 'mcp', 'admin']) {
|
|
12
12
|
expect(topLevel).toContain(expected);
|
|
13
13
|
}
|
|
14
14
|
expect(output).toContain('│ └── test [connectionId]');
|
package/dist/public-ingest.js
CHANGED
|
@@ -234,12 +234,10 @@ function targetForConnection(connectionId, connection, projectConfig, args, warn
|
|
|
234
234
|
throw new Error(`Connection "${connectionId}" uses unsupported public ingest driver "${driver || 'unknown'}"`);
|
|
235
235
|
}
|
|
236
236
|
export function buildPublicIngestPlan(project, args) {
|
|
237
|
-
|
|
238
|
-
throw new Error('Context build requires a connection id or all targets');
|
|
239
|
-
}
|
|
237
|
+
const allConnections = args.all || !args.targetConnectionId;
|
|
240
238
|
const entries = Object.entries(project.config.connections).sort(([a], [b]) => a.localeCompare(b));
|
|
241
|
-
const selected =
|
|
242
|
-
if (!
|
|
239
|
+
const selected = allConnections ? entries : entries.filter(([connectionId]) => connectionId === args.targetConnectionId);
|
|
240
|
+
if (!allConnections && selected.length === 0) {
|
|
243
241
|
throw new Error(`Connection "${args.targetConnectionId}" is not configured in ktx.yaml`);
|
|
244
242
|
}
|
|
245
243
|
if (selected.length === 0) {
|
|
@@ -109,9 +109,13 @@ describe('buildPublicIngestPlan', () => {
|
|
|
109
109
|
warnings: [],
|
|
110
110
|
});
|
|
111
111
|
});
|
|
112
|
-
it('
|
|
113
|
-
const project = projectWithConnections({
|
|
114
|
-
|
|
112
|
+
it('treats a bare invocation (no connection id, no --all) as all configured connections', () => {
|
|
113
|
+
const project = projectWithConnections({
|
|
114
|
+
warehouse: { driver: 'postgres' },
|
|
115
|
+
docs: { driver: 'notion' },
|
|
116
|
+
});
|
|
117
|
+
const plan = buildPublicIngestPlan(project, { projectDir: '/tmp/project', all: false });
|
|
118
|
+
expect(plan.targets.map((target) => target.connectionId).sort()).toEqual(['docs', 'warehouse']);
|
|
115
119
|
});
|
|
116
120
|
it('resolves database depth from flags, stored context, and defaults', () => {
|
|
117
121
|
const project = projectWithConnections({
|
package/dist/runtime.test.js
CHANGED
|
@@ -246,7 +246,7 @@ describe('runKtxRuntime', () => {
|
|
|
246
246
|
label: 'Managed Python runtime',
|
|
247
247
|
status: 'fail',
|
|
248
248
|
detail: 'No runtime manifest at /runtime/0.2.0/manifest.json',
|
|
249
|
-
fix: 'Run: ktx
|
|
249
|
+
fix: 'Run: ktx admin runtime install --yes',
|
|
250
250
|
},
|
|
251
251
|
]),
|
|
252
252
|
};
|
package/dist/scan.test.js
CHANGED
|
@@ -287,8 +287,8 @@ describe('runKtxScan', () => {
|
|
|
287
287
|
expect(io.stdout()).toContain('Report: raw-sources/warehouse/live-database/sync-1/scan-report.json');
|
|
288
288
|
expect(io.stdout()).toContain('Next:\n');
|
|
289
289
|
expect(io.stdout()).toContain('ktx status --project-dir ');
|
|
290
|
-
expect(io.stdout()).not.toContain('ktx
|
|
291
|
-
expect(io.stdout()).not.toContain('ktx
|
|
290
|
+
expect(io.stdout()).not.toContain('ktx admin scan status');
|
|
291
|
+
expect(io.stdout()).not.toContain('ktx admin scan report');
|
|
292
292
|
expect(io.stdout()).not.toContain('\u001b[');
|
|
293
293
|
expect(io.stdout()).not.toContain('✓');
|
|
294
294
|
expect(io.stdout()).not.toContain('+1');
|
package/dist/setup-agents.js
CHANGED
|
@@ -426,8 +426,8 @@ function cliInstructionContent(input) {
|
|
|
426
426
|
'Available commands:',
|
|
427
427
|
'',
|
|
428
428
|
`- \`${ktxCommandLine(input.launcher, ['status', ...jsonProjectDirArgs])}\``,
|
|
429
|
-
`- \`${ktxCommandLine(input.launcher, ['sl',
|
|
430
|
-
`- \`${ktxCommandLine(input.launcher, ['sl', '
|
|
429
|
+
`- \`${ktxCommandLine(input.launcher, ['sl', ...jsonProjectDirArgs])}\``,
|
|
430
|
+
`- \`${ktxCommandLine(input.launcher, ['sl', '<text>', ...jsonProjectDirArgs, '--connection-id', '<id>'])}\``,
|
|
431
431
|
`- \`${ktxCommandLine(input.launcher, [
|
|
432
432
|
'sl',
|
|
433
433
|
'query',
|
|
@@ -442,7 +442,7 @@ function cliInstructionContent(input) {
|
|
|
442
442
|
'--max-rows',
|
|
443
443
|
'100',
|
|
444
444
|
])}\``,
|
|
445
|
-
`- \`${ktxCommandLine(input.launcher, ['wiki', '
|
|
445
|
+
`- \`${ktxCommandLine(input.launcher, ['wiki', '<query>', ...jsonProjectDirArgs, '--limit', '10'])}\``,
|
|
446
446
|
'',
|
|
447
447
|
'Use semantic-layer queries before direct database access. Do not print secrets or credential references.',
|
|
448
448
|
'',
|
|
@@ -149,7 +149,7 @@ describe('setup agents', () => {
|
|
|
149
149
|
expect(skill).toContain(`--project-dir ${tempDir}`);
|
|
150
150
|
expect(skill).toContain('must not print secrets');
|
|
151
151
|
expect(skill).toContain('status --json');
|
|
152
|
-
expect(skill).toContain('sl
|
|
152
|
+
expect(skill).toContain('sl --json');
|
|
153
153
|
expect(skill).toContain('sl query');
|
|
154
154
|
expect(skill).toContain('--format json');
|
|
155
155
|
expect(skill).not.toContain('sl query --json');
|
package/dist/setup-embeddings.js
CHANGED
|
@@ -209,7 +209,7 @@ function localEmbeddingSetupMessage(message, stderrTail = []) {
|
|
|
209
209
|
const lines = [
|
|
210
210
|
`Local embedding health check failed: ${message}`,
|
|
211
211
|
'Local embeddings use the KTX-managed Python runtime.',
|
|
212
|
-
'Prepare the runtime with: ktx
|
|
212
|
+
'Prepare the runtime with: ktx admin runtime start --feature local-embeddings',
|
|
213
213
|
'Use --yes with setup to install and start the runtime without prompting.',
|
|
214
214
|
'The first run may download Python packages and the all-MiniLM-L6-v2 model.',
|
|
215
215
|
];
|
|
@@ -225,7 +225,7 @@ describe('setup embeddings step', () => {
|
|
|
225
225
|
it('fails non-interactive local setup when the managed local embeddings runtime is missing', async () => {
|
|
226
226
|
const io = makeIo();
|
|
227
227
|
const ensureLocalEmbeddings = vi.fn(async () => {
|
|
228
|
-
throw new Error('KTX Python runtime is required for this command. Run: ktx
|
|
228
|
+
throw new Error('KTX Python runtime is required for this command. Run: ktx admin runtime install --feature local-embeddings --yes');
|
|
229
229
|
});
|
|
230
230
|
const result = await runKtxSetupEmbeddingsStep({
|
|
231
231
|
projectDir: tempDir,
|
|
@@ -235,7 +235,7 @@ describe('setup embeddings step', () => {
|
|
|
235
235
|
skipEmbeddings: false,
|
|
236
236
|
}, io.io, { env: {}, ensureLocalEmbeddings });
|
|
237
237
|
expect(result.status).toBe('failed');
|
|
238
|
-
expect(io.stderr()).toContain('KTX Python runtime is required for this command. Run: ktx
|
|
238
|
+
expect(io.stderr()).toContain('KTX Python runtime is required for this command. Run: ktx admin runtime install --feature local-embeddings --yes');
|
|
239
239
|
});
|
|
240
240
|
it('does not persist embedding completion when the health check fails', async () => {
|
|
241
241
|
const io = makeIo();
|
|
@@ -255,7 +255,7 @@ describe('setup embeddings step', () => {
|
|
|
255
255
|
expect(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).not.toContain('completed_steps:');
|
|
256
256
|
expect(config.ingest.embeddings.backend).toBe('none');
|
|
257
257
|
expect(io.stderr()).toContain('Local embedding health check failed: 401 invalid api key [redacted]');
|
|
258
|
-
expect(io.stderr()).toContain('Prepare the runtime with: ktx
|
|
258
|
+
expect(io.stderr()).toContain('Prepare the runtime with: ktx admin runtime start --feature local-embeddings');
|
|
259
259
|
expect(io.stderr()).not.toContain('skip for now');
|
|
260
260
|
});
|
|
261
261
|
it('prints the recent daemon stderr tail when local embedding health check fails', async () => {
|
|
@@ -56,7 +56,7 @@ describe('runKtxSetupRuntimeStep', () => {
|
|
|
56
56
|
it('fails fast when required runtime features cannot be installed in no-input mode', async () => {
|
|
57
57
|
const io = makeIo();
|
|
58
58
|
const ensureRuntime = vi.fn(async () => {
|
|
59
|
-
throw new Error('KTX Python runtime is required for this command. Run: ktx
|
|
59
|
+
throw new Error('KTX Python runtime is required for this command. Run: ktx admin runtime install --yes');
|
|
60
60
|
});
|
|
61
61
|
await expect(runKtxSetupRuntimeStep({
|
|
62
62
|
projectDir: tempDir,
|
|
@@ -71,7 +71,7 @@ describe('runKtxSetupRuntimeStep', () => {
|
|
|
71
71
|
})).resolves.toMatchObject({ status: 'failed' });
|
|
72
72
|
expect(ensureRuntime).toHaveBeenCalledWith(expect.objectContaining({ installPolicy: 'never' }));
|
|
73
73
|
expect((await readKtxSetupState(tempDir)).completed_steps).not.toContain('runtime');
|
|
74
|
-
expect(io.stderr()).toContain('ktx
|
|
74
|
+
expect(io.stderr()).toContain('ktx admin runtime install --yes');
|
|
75
75
|
});
|
|
76
76
|
it('starts the managed local embeddings daemon for configured sentence-transformers embeddings', async () => {
|
|
77
77
|
const io = makeIo();
|
package/dist/sl.js
CHANGED
|
@@ -99,7 +99,7 @@ export async function runKtxSl(args, io = process, deps = {}) {
|
|
|
99
99
|
await printSlSources({
|
|
100
100
|
rows: sources,
|
|
101
101
|
emptyMessage: `No semantic-layer sources matched "${args.query}" in ${project.projectDir}`,
|
|
102
|
-
emptyHint: 'Run `ktx sl
|
|
102
|
+
emptyHint: 'Run `ktx sl` to inspect available sources.',
|
|
103
103
|
command: 'sl search',
|
|
104
104
|
output: args.output,
|
|
105
105
|
json: args.json,
|
|
@@ -116,6 +116,10 @@ describe('standalone built ktx CLI smoke', () => {
|
|
|
116
116
|
const init = await runSetupNewProject(projectDir);
|
|
117
117
|
expectSetupStderr(init);
|
|
118
118
|
expect(init.stdout).toContain(`Project: ${projectDir}`);
|
|
119
|
+
const reindex = await runBuiltCli(['--project-dir', projectDir, 'admin', 'reindex', '--output', 'plain']);
|
|
120
|
+
expect(reindex.code).toBe(0);
|
|
121
|
+
expect(reindex.stdout).toContain('reindex\t');
|
|
122
|
+
expect(reindex.stderr).toContain('wiki/global');
|
|
119
123
|
const run = await runBuiltCli([
|
|
120
124
|
'ingest',
|
|
121
125
|
'run',
|
|
@@ -125,7 +129,7 @@ describe('standalone built ktx CLI smoke', () => {
|
|
|
125
129
|
'fake',
|
|
126
130
|
]);
|
|
127
131
|
expect(run).toMatchObject({ code: 1, stdout: '' });
|
|
128
|
-
expect(run.stderr).toContain("unknown option '--
|
|
132
|
+
expect(run.stderr).toContain("unknown option '--adapter'");
|
|
129
133
|
});
|
|
130
134
|
it('rejects the removed agent command through the built binary', async () => {
|
|
131
135
|
const result = await runBuiltCli(['agent']);
|
|
@@ -235,7 +239,7 @@ describe('standalone built ktx CLI smoke', () => {
|
|
|
235
239
|
]);
|
|
236
240
|
expect(add.code).toBe(1);
|
|
237
241
|
expect(add.stdout).toBe('');
|
|
238
|
-
expect(add.stderr).
|
|
242
|
+
expect(add.stderr).toMatch(/unknown (command|option)|too many arguments/);
|
|
239
243
|
const yaml = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
|
|
240
244
|
expect(yaml).not.toContain('driver: notion');
|
|
241
245
|
expect(yaml).not.toContain('auth_token_ref: env:NOTION_TOKEN');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { discoverReindexScopes, reindexLocalIndexes } from './reindex.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type KtxLocalProject } from '../project/index.js';
|
|
2
|
+
import type { ReindexOptions, ReindexSummary } from './types.js';
|
|
3
|
+
type DiscoveredScope = {
|
|
4
|
+
kind: 'wiki';
|
|
5
|
+
scope: 'GLOBAL';
|
|
6
|
+
scopeId: null;
|
|
7
|
+
label: 'global';
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'wiki';
|
|
10
|
+
scope: 'USER';
|
|
11
|
+
scopeId: string;
|
|
12
|
+
label: `user/${string}`;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'sl';
|
|
15
|
+
connectionId: string;
|
|
16
|
+
label: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function discoverReindexScopes(project: KtxLocalProject): Promise<DiscoveredScope[]>;
|
|
19
|
+
export declare function reindexLocalIndexes(project: KtxLocalProject, options: ReindexOptions): Promise<ReindexSummary>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { ktxLocalStateDbPath } from '../project/index.js';
|
|
4
|
+
import { loadLocalSlSourceRecords, SlSearchService, SqliteSlSourcesIndex } from '../sl/index.js';
|
|
5
|
+
import { KnowledgeWikiService, SqliteKnowledgeIndex } from '../wiki/index.js';
|
|
6
|
+
const ZERO = {
|
|
7
|
+
scanned: 0,
|
|
8
|
+
updated: 0,
|
|
9
|
+
deleted: 0,
|
|
10
|
+
embeddingsRecomputed: 0,
|
|
11
|
+
embeddingsFailed: 0,
|
|
12
|
+
};
|
|
13
|
+
async function directoryExists(path) {
|
|
14
|
+
try {
|
|
15
|
+
return (await stat(path)).isDirectory();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function childDirectories(path) {
|
|
22
|
+
try {
|
|
23
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
24
|
+
return entries
|
|
25
|
+
.filter((entry) => entry.isDirectory())
|
|
26
|
+
.map((entry) => entry.name)
|
|
27
|
+
.sort((left, right) => left.localeCompare(right));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code === 'ENOENT') {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function discoverReindexScopes(project) {
|
|
37
|
+
const scopes = [];
|
|
38
|
+
if (await directoryExists(join(project.projectDir, 'wiki/global'))) {
|
|
39
|
+
scopes.push({ kind: 'wiki', scope: 'GLOBAL', scopeId: null, label: 'global' });
|
|
40
|
+
}
|
|
41
|
+
for (const userId of await childDirectories(join(project.projectDir, 'wiki/user'))) {
|
|
42
|
+
scopes.push({ kind: 'wiki', scope: 'USER', scopeId: userId, label: `user/${userId}` });
|
|
43
|
+
}
|
|
44
|
+
for (const connectionId of await childDirectories(join(project.projectDir, 'semantic-layer'))) {
|
|
45
|
+
if (connectionId !== '_schema') {
|
|
46
|
+
scopes.push({ kind: 'sl', connectionId, label: connectionId });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return scopes;
|
|
50
|
+
}
|
|
51
|
+
function errorMessage(error) {
|
|
52
|
+
if (!(error instanceof Error)) {
|
|
53
|
+
return String(error);
|
|
54
|
+
}
|
|
55
|
+
return error.name && error.name !== 'Error' ? `${error.name}: ${error.message}` : error.message;
|
|
56
|
+
}
|
|
57
|
+
function addTotals(left, right) {
|
|
58
|
+
return {
|
|
59
|
+
scanned: left.scanned + right.scanned,
|
|
60
|
+
updated: left.updated + right.updated,
|
|
61
|
+
deleted: left.deleted + right.deleted,
|
|
62
|
+
embeddingsRecomputed: left.embeddingsRecomputed + right.embeddingsRecomputed,
|
|
63
|
+
embeddingsFailed: left.embeddingsFailed + right.embeddingsFailed,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function durationSince(startedAt) {
|
|
67
|
+
return Number((process.hrtime.bigint() - startedAt) / 1000000n);
|
|
68
|
+
}
|
|
69
|
+
function embeddingFailureError(work) {
|
|
70
|
+
if (work.embeddingsFailed === 0) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
return `${work.embeddingsFailed} embedding recomputation${work.embeddingsFailed === 1 ? '' : 's'} failed`;
|
|
74
|
+
}
|
|
75
|
+
export async function reindexLocalIndexes(project, options) {
|
|
76
|
+
const startedAt = process.hrtime.bigint();
|
|
77
|
+
const dbPath = ktxLocalStateDbPath(project);
|
|
78
|
+
const scopes = await discoverReindexScopes(project);
|
|
79
|
+
const wikiIndex = new SqliteKnowledgeIndex({ dbPath });
|
|
80
|
+
const slIndex = new SqliteSlSourcesIndex({ dbPath });
|
|
81
|
+
const wikiService = new KnowledgeWikiService(project.fileStore, options.embeddingService, wikiIndex, project.git);
|
|
82
|
+
const slService = new SlSearchService(options.embeddingService, slIndex);
|
|
83
|
+
const results = [];
|
|
84
|
+
for (const scope of scopes) {
|
|
85
|
+
const scopeStartedAt = process.hrtime.bigint();
|
|
86
|
+
try {
|
|
87
|
+
let work;
|
|
88
|
+
if (scope.kind === 'wiki') {
|
|
89
|
+
if (options.force) {
|
|
90
|
+
wikiIndex.clear(scope.scope, scope.scopeId);
|
|
91
|
+
}
|
|
92
|
+
work = await wikiService.syncIndex(scope.scope, scope.scopeId);
|
|
93
|
+
results.push({
|
|
94
|
+
kind: 'wiki',
|
|
95
|
+
label: scope.label,
|
|
96
|
+
scope: scope.scope === 'GLOBAL' ? 'global' : 'user',
|
|
97
|
+
scopeId: scope.scopeId,
|
|
98
|
+
...work,
|
|
99
|
+
...(options.force ? { deleted: 0 } : {}),
|
|
100
|
+
...(options.embeddingService && work.embeddingsFailed > 0 ? { error: embeddingFailureError(work) } : {}),
|
|
101
|
+
durationMs: durationSince(scopeStartedAt),
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (options.force) {
|
|
106
|
+
await slIndex.clear(scope.connectionId);
|
|
107
|
+
}
|
|
108
|
+
const records = await loadLocalSlSourceRecords(project, { connectionId: scope.connectionId });
|
|
109
|
+
work = await slService.indexSources(scope.connectionId, records.map((record) => record.source));
|
|
110
|
+
results.push({
|
|
111
|
+
kind: 'sl',
|
|
112
|
+
label: scope.label,
|
|
113
|
+
connectionId: scope.connectionId,
|
|
114
|
+
...work,
|
|
115
|
+
...(options.force ? { deleted: 0 } : {}),
|
|
116
|
+
...(options.embeddingService && work.embeddingsFailed > 0 ? { error: embeddingFailureError(work) } : {}),
|
|
117
|
+
durationMs: durationSince(scopeStartedAt),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
results.push({
|
|
122
|
+
kind: scope.kind,
|
|
123
|
+
label: scope.label,
|
|
124
|
+
...(scope.kind === 'wiki'
|
|
125
|
+
? { scope: scope.scope === 'GLOBAL' ? 'global' : 'user', scopeId: scope.scopeId }
|
|
126
|
+
: { connectionId: scope.connectionId }),
|
|
127
|
+
...ZERO,
|
|
128
|
+
durationMs: durationSince(scopeStartedAt),
|
|
129
|
+
error: errorMessage(error),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
scopes: results,
|
|
135
|
+
totals: results.reduce(addTotals, ZERO),
|
|
136
|
+
dbPath: relative(project.projectDir, dbPath) || dbPath,
|
|
137
|
+
force: options.force,
|
|
138
|
+
embeddingsAvailable: options.embeddingService !== null,
|
|
139
|
+
durationMs: durationSince(startedAt),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|