@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
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function resolveConnectionSelection(input) {
|
|
2
|
+
if (input.all && input.connectionId !== undefined) {
|
|
3
|
+
throw new Error('--all cannot be combined with a connection id argument');
|
|
4
|
+
}
|
|
5
|
+
if (input.connectionId !== undefined) {
|
|
6
|
+
return { kind: 'single', connectionId: input.connectionId };
|
|
7
|
+
}
|
|
8
|
+
return { kind: 'all' };
|
|
9
|
+
}
|
|
@@ -2,32 +2,59 @@ import { Option } from '@commander-js/extra-typings';
|
|
|
2
2
|
import { collectOption, parsePositiveIntegerOption, resolveCommandProjectDir, } from '../cli-program.js';
|
|
3
3
|
import { runtimeInstallPolicyFromFlags } from '../managed-python-command.js';
|
|
4
4
|
import { profileMark } from '../startup-profile.js';
|
|
5
|
+
import { resolveConnectionSelection } from './connection-selection.js';
|
|
5
6
|
profileMark('module:commands/ingest-commands');
|
|
6
7
|
export function registerIngestCommands(program, context, commandOptions) {
|
|
7
8
|
const ingest = program
|
|
8
9
|
.command('ingest')
|
|
9
|
-
.description('Build or inspect KTX context')
|
|
10
|
+
.description('Build or inspect KTX context, or capture text into memory')
|
|
10
11
|
.usage('[options] [connectionId]')
|
|
11
|
-
.argument('[connectionId]', 'Configured connection id to ingest')
|
|
12
|
+
.argument('[connectionId]', 'Configured connection id to ingest (omit to ingest all)')
|
|
12
13
|
.option('--all', 'Ingest all configured connections', false)
|
|
13
14
|
.addOption(new Option('--fast', 'Use deterministic database schema ingest').conflicts('deep'))
|
|
14
15
|
.addOption(new Option('--deep', 'Use AI-enriched database ingest').conflicts('fast'))
|
|
15
16
|
.addOption(new Option('--query-history', 'Include database query-history usage patterns').conflicts('noQueryHistory'))
|
|
16
17
|
.addOption(new Option('--no-query-history', 'Skip database query-history usage patterns'))
|
|
17
18
|
.option('--query-history-window-days <days>', 'Query-history lookback window for this run', parsePositiveIntegerOption)
|
|
19
|
+
.option('--text <content>', 'Capture inline text into KTX memory; repeatable', collectOption, [])
|
|
20
|
+
.option('--file <path>', 'Capture a text file into KTX memory; use - for stdin; repeatable', collectOption, [])
|
|
21
|
+
.option('--connection-id <connectionId>', 'KTX connection id to tag captured text/file notes')
|
|
22
|
+
.option('--user-id <id>', 'Memory user id for text/file capture attribution', 'local-cli')
|
|
23
|
+
.option('--fail-fast', 'Stop after the first failed text/file item', false)
|
|
18
24
|
.addOption(new Option('--plain', 'Print plain text output').conflicts(['json']))
|
|
19
25
|
.addOption(new Option('--json', 'Print JSON output').conflicts(['plain']))
|
|
20
26
|
.option('--yes', 'Install required managed runtime features without prompting')
|
|
21
27
|
.option('--no-input', 'Disable interactive terminal input')
|
|
22
28
|
.showHelpAfterError();
|
|
23
29
|
ingest.action(async (connectionId, options, command) => {
|
|
30
|
+
const projectDir = resolveCommandProjectDir(command);
|
|
31
|
+
const hasTextCapture = options.text.length > 0 || options.file.length > 0;
|
|
32
|
+
if (hasTextCapture) {
|
|
33
|
+
if (connectionId !== undefined) {
|
|
34
|
+
command.error('error: --text/--file does not accept a positional connection id; use --connection-id <id> to tag captured notes');
|
|
35
|
+
}
|
|
36
|
+
if (options.all === true) {
|
|
37
|
+
command.error('error: --all cannot be combined with --text or --file');
|
|
38
|
+
}
|
|
39
|
+
context.setExitCode(await commandOptions.runTextIngest({
|
|
40
|
+
projectDir,
|
|
41
|
+
texts: options.text,
|
|
42
|
+
files: options.file,
|
|
43
|
+
...(options.connectionId ? { connectionId: options.connectionId } : {}),
|
|
44
|
+
userId: options.userId,
|
|
45
|
+
json: options.json === true,
|
|
46
|
+
failFast: options.failFast === true,
|
|
47
|
+
}, context.io, context.deps));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const selection = resolveConnectionSelection({ connectionId, all: options.all === true });
|
|
24
51
|
const { runKtxPublicIngest } = await import('../public-ingest.js');
|
|
25
52
|
const queryHistory = options.queryHistory === true ? 'enabled' : options.queryHistory === false ? 'disabled' : 'default';
|
|
26
53
|
const args = {
|
|
27
54
|
command: 'run',
|
|
28
|
-
projectDir
|
|
29
|
-
...(
|
|
30
|
-
all:
|
|
55
|
+
projectDir,
|
|
56
|
+
...(selection.kind === 'single' ? { targetConnectionId: selection.connectionId } : {}),
|
|
57
|
+
all: selection.kind === 'all',
|
|
31
58
|
json: options.json === true,
|
|
32
59
|
inputMode: options.input === false ? 'disabled' : 'auto',
|
|
33
60
|
...(options.fast === true ? { depth: 'fast' } : {}),
|
|
@@ -42,25 +69,4 @@ export function registerIngestCommands(program, context, commandOptions) {
|
|
|
42
69
|
ingest.hook('preAction', (_thisCommand, actionCommand) => {
|
|
43
70
|
context.writeDebug?.('ingest', actionCommand);
|
|
44
71
|
});
|
|
45
|
-
ingest
|
|
46
|
-
.command('text')
|
|
47
|
-
.description('Ingest free-form text artifacts into KTX memory')
|
|
48
|
-
.argument('[files...]', 'Files to ingest; use - to read one item from stdin')
|
|
49
|
-
.option('--text <content>', 'Text content to ingest; repeat for a batch', collectOption, [])
|
|
50
|
-
.option('--connection-id <connectionId>', 'Optional KTX connection id for semantic-layer capture')
|
|
51
|
-
.option('--user-id <id>', 'Memory user id for capture attribution', 'local-cli')
|
|
52
|
-
.option('--json', 'Print JSON output')
|
|
53
|
-
.option('--fail-fast', 'Stop after the first failed text item', false)
|
|
54
|
-
.action(async (files, options, command) => {
|
|
55
|
-
const parentOptions = command.parent?.opts();
|
|
56
|
-
context.setExitCode(await commandOptions.runTextIngest({
|
|
57
|
-
projectDir: resolveCommandProjectDir(command),
|
|
58
|
-
texts: options.text,
|
|
59
|
-
files,
|
|
60
|
-
...(options.connectionId ? { connectionId: options.connectionId } : {}),
|
|
61
|
-
userId: options.userId,
|
|
62
|
-
json: options.json === true || parentOptions?.json === true,
|
|
63
|
-
failFast: options.failFast === true,
|
|
64
|
-
}, context.io, context.deps));
|
|
65
|
-
});
|
|
66
72
|
}
|
|
@@ -11,47 +11,36 @@ function isDebugEnabled(command) {
|
|
|
11
11
|
return options.debug === true;
|
|
12
12
|
}
|
|
13
13
|
export function registerWikiCommands(program, context) {
|
|
14
|
-
|
|
14
|
+
program
|
|
15
15
|
.command('wiki')
|
|
16
16
|
.description('List or search local wiki pages')
|
|
17
|
-
.
|
|
18
|
-
.
|
|
19
|
-
wiki
|
|
20
|
-
.command('list')
|
|
21
|
-
.description('List local wiki pages')
|
|
22
|
-
.option('--user-id <id>', 'Local user id', 'local')
|
|
23
|
-
.addOption(new Option('--output <mode>', 'Output mode: pretty (default in TTY), plain (TSV), or json').choices([
|
|
24
|
-
'pretty',
|
|
25
|
-
'plain',
|
|
26
|
-
'json',
|
|
27
|
-
]))
|
|
28
|
-
.option('--json', 'Shortcut for --output=json (overrides --output)', false)
|
|
29
|
-
.action(async (options, command) => {
|
|
30
|
-
await runKnowledgeArgs(context, {
|
|
31
|
-
command: 'list',
|
|
32
|
-
projectDir: resolveCommandProjectDir(command),
|
|
33
|
-
userId: options.userId,
|
|
34
|
-
output: options.output,
|
|
35
|
-
json: options.json,
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
wiki
|
|
39
|
-
.command('search')
|
|
40
|
-
.description('Search local wiki pages')
|
|
41
|
-
.argument('<query>', 'Search query')
|
|
17
|
+
.usage('[options] [query...]')
|
|
18
|
+
.argument('[query...]', 'Search query; omit to list all pages')
|
|
42
19
|
.option('--user-id <id>', 'Local user id', 'local')
|
|
43
|
-
.option('--limit <number>', 'Maximum search results', parsePositiveIntegerOption)
|
|
20
|
+
.option('--limit <number>', 'Maximum search results (search mode only)', parsePositiveIntegerOption)
|
|
44
21
|
.addOption(new Option('--output <mode>', 'Output mode: pretty (default in TTY), plain (TSV), or json').choices([
|
|
45
22
|
'pretty',
|
|
46
23
|
'plain',
|
|
47
24
|
'json',
|
|
48
25
|
]))
|
|
49
26
|
.option('--json', 'Shortcut for --output=json (overrides --output)', false)
|
|
27
|
+
.showHelpAfterError()
|
|
28
|
+
.addHelpText('after', '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n')
|
|
50
29
|
.action(async (query, options, command) => {
|
|
30
|
+
if (query.length === 0) {
|
|
31
|
+
await runKnowledgeArgs(context, {
|
|
32
|
+
command: 'list',
|
|
33
|
+
projectDir: resolveCommandProjectDir(command),
|
|
34
|
+
userId: options.userId,
|
|
35
|
+
output: options.output,
|
|
36
|
+
json: options.json,
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
51
40
|
await runKnowledgeArgs(context, {
|
|
52
41
|
command: 'search',
|
|
53
42
|
projectDir: resolveCommandProjectDir(command),
|
|
54
|
-
query,
|
|
43
|
+
query: query.join(' '),
|
|
55
44
|
userId: options.userId,
|
|
56
45
|
output: options.output,
|
|
57
46
|
json: options.json,
|
|
@@ -21,8 +21,23 @@ function formatMcpStartResultMessage(input) {
|
|
|
21
21
|
'',
|
|
22
22
|
].join('\n');
|
|
23
23
|
}
|
|
24
|
+
async function printMcpStatus(context, projectDir) {
|
|
25
|
+
const status = await (context.deps.mcp?.readStatus ?? readKtxMcpDaemonStatus)({ projectDir });
|
|
26
|
+
context.io.stdout.write(`${status.detail}\n`);
|
|
27
|
+
if (status.kind === 'running') {
|
|
28
|
+
context.io.stdout.write(`URL: ${status.url}\n`);
|
|
29
|
+
context.io.stdout.write(`PID: ${status.state.pid}\n`);
|
|
30
|
+
context.io.stdout.write(`Token auth: ${status.state.tokenAuth ? 'enabled' : 'disabled'}\n`);
|
|
31
|
+
context.io.stdout.write(`Project: ${status.state.projectDir}\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
24
34
|
export function registerMcpCommands(program, context) {
|
|
25
|
-
const mcp = program
|
|
35
|
+
const mcp = program
|
|
36
|
+
.command('mcp')
|
|
37
|
+
.description('Manage the KTX MCP HTTP server (bare command: show status)')
|
|
38
|
+
.action(async (_options, command) => {
|
|
39
|
+
await printMcpStatus(context, resolveCommandProjectDir(command));
|
|
40
|
+
});
|
|
26
41
|
mcp
|
|
27
42
|
.command('stdio')
|
|
28
43
|
.description('Run the KTX MCP server over stdio')
|
|
@@ -91,16 +106,7 @@ export function registerMcpCommands(program, context) {
|
|
|
91
106
|
.command('status')
|
|
92
107
|
.description('Show KTX MCP daemon status')
|
|
93
108
|
.action(async (_options, command) => {
|
|
94
|
-
|
|
95
|
-
projectDir: resolveCommandProjectDir(command),
|
|
96
|
-
});
|
|
97
|
-
context.io.stdout.write(`${status.detail}\n`);
|
|
98
|
-
if (status.kind === 'running') {
|
|
99
|
-
context.io.stdout.write(`URL: ${status.url}\n`);
|
|
100
|
-
context.io.stdout.write(`PID: ${status.state.pid}\n`);
|
|
101
|
-
context.io.stdout.write(`Token auth: ${status.state.tokenAuth ? 'enabled' : 'disabled'}\n`);
|
|
102
|
-
context.io.stdout.write(`Project: ${status.state.projectDir}\n`);
|
|
103
|
-
}
|
|
109
|
+
await printMcpStatus(context, resolveCommandProjectDir(command));
|
|
104
110
|
});
|
|
105
111
|
mcp
|
|
106
112
|
.command('logs')
|
|
@@ -28,63 +28,57 @@ export function registerSlCommands(program, context, commandName = 'sl') {
|
|
|
28
28
|
const sl = program
|
|
29
29
|
.command(commandName)
|
|
30
30
|
.description('List, search, validate, or query local semantic-layer sources')
|
|
31
|
-
.
|
|
32
|
-
.
|
|
33
|
-
sl.command('list')
|
|
34
|
-
.description('List semantic-layer sources')
|
|
35
|
-
.option('--connection-id <id>', 'KTX connection id')
|
|
36
|
-
.addOption(new Option('--output <mode>', 'Output mode: pretty (default in TTY), plain (TSV), or json').choices([
|
|
37
|
-
'pretty',
|
|
38
|
-
'plain',
|
|
39
|
-
'json',
|
|
40
|
-
]))
|
|
41
|
-
.option('--json', 'Shortcut for --output=json (overrides --output)', false)
|
|
42
|
-
.action(async (options, command) => {
|
|
43
|
-
await runSlArgs(context, {
|
|
44
|
-
command: 'list',
|
|
45
|
-
projectDir: resolveCommandProjectDir(command),
|
|
46
|
-
connectionId: options.connectionId,
|
|
47
|
-
output: options.output,
|
|
48
|
-
json: options.json,
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
sl.command('search')
|
|
52
|
-
.description('Search semantic-layer sources')
|
|
53
|
-
.argument('<query>', 'Search query')
|
|
31
|
+
.usage('[options] [query...]')
|
|
32
|
+
.argument('[query...]', 'Search query; omit to list all sources')
|
|
54
33
|
.option('--connection-id <id>', 'KTX connection id')
|
|
55
|
-
.option('--limit <number>', 'Maximum search results', parsePositiveIntegerOption)
|
|
34
|
+
.option('--limit <number>', 'Maximum search results (search mode only)', parsePositiveIntegerOption)
|
|
56
35
|
.addOption(new Option('--output <mode>', 'Output mode: pretty (default in TTY), plain (TSV), or json').choices([
|
|
57
36
|
'pretty',
|
|
58
37
|
'plain',
|
|
59
38
|
'json',
|
|
60
39
|
]))
|
|
61
40
|
.option('--json', 'Shortcut for --output=json (overrides --output)', false)
|
|
41
|
+
.showHelpAfterError()
|
|
42
|
+
.addHelpText('after', '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n')
|
|
62
43
|
.action(async (query, options, command) => {
|
|
44
|
+
if (query.length === 0) {
|
|
45
|
+
await runSlArgs(context, {
|
|
46
|
+
command: 'list',
|
|
47
|
+
projectDir: resolveCommandProjectDir(command),
|
|
48
|
+
connectionId: options.connectionId,
|
|
49
|
+
output: options.output,
|
|
50
|
+
json: options.json,
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
63
54
|
await runSlArgs(context, {
|
|
64
55
|
command: 'search',
|
|
65
56
|
projectDir: resolveCommandProjectDir(command),
|
|
66
57
|
connectionId: options.connectionId,
|
|
67
|
-
query,
|
|
58
|
+
query: query.join(' '),
|
|
68
59
|
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
69
60
|
output: options.output,
|
|
70
61
|
json: options.json,
|
|
71
62
|
});
|
|
72
63
|
});
|
|
73
64
|
sl.command('validate')
|
|
74
|
-
.description('Validate a semantic-layer source')
|
|
65
|
+
.description('Validate a semantic-layer source (set --connection-id on `ktx sl`)')
|
|
75
66
|
.argument('<sourceName>', 'Semantic-layer source name')
|
|
76
|
-
.
|
|
77
|
-
|
|
67
|
+
.action(async (sourceName, _options, command) => {
|
|
68
|
+
const parentOpts = command.parent?.opts();
|
|
69
|
+
const connectionId = parentOpts?.connectionId;
|
|
70
|
+
if (connectionId === undefined) {
|
|
71
|
+
command.error("error: required option '--connection-id <id>' not specified");
|
|
72
|
+
}
|
|
78
73
|
await runSlArgs(context, {
|
|
79
74
|
command: 'validate',
|
|
80
75
|
projectDir: resolveCommandProjectDir(command),
|
|
81
|
-
connectionId:
|
|
76
|
+
connectionId: connectionId,
|
|
82
77
|
sourceName,
|
|
83
78
|
});
|
|
84
79
|
});
|
|
85
80
|
sl.command('query')
|
|
86
|
-
.description('Compile or execute a semantic-layer query')
|
|
87
|
-
.option('--connection-id <id>', 'KTX connection id')
|
|
81
|
+
.description('Compile or execute a semantic-layer query (set --connection-id on `ktx sl`)')
|
|
88
82
|
.option('--query-file <path>', 'JSON semantic-layer query file')
|
|
89
83
|
.option('--measure <measure>', 'Measure to query; repeatable', collectOption, [])
|
|
90
84
|
.option('--dimension <dimension>', 'Dimension to include; repeatable', collectOption, [])
|
|
@@ -102,10 +96,11 @@ export function registerSlCommands(program, context, commandName = 'sl') {
|
|
|
102
96
|
if (options.measure.length === 0 && !options.queryFile) {
|
|
103
97
|
throw new Error('sl query requires at least one --measure');
|
|
104
98
|
}
|
|
99
|
+
const parentOpts = command.parent?.opts();
|
|
105
100
|
const args = slQueryCommandSchema.parse({
|
|
106
101
|
command: 'query',
|
|
107
102
|
projectDir: resolveCommandProjectDir(command),
|
|
108
|
-
connectionId:
|
|
103
|
+
connectionId: parentOpts?.connectionId,
|
|
109
104
|
...(options.queryFile
|
|
110
105
|
? { queryFile: options.queryFile }
|
|
111
106
|
: {
|
package/dist/doctor.test.js
CHANGED
|
@@ -55,8 +55,8 @@ describe('formatDoctorReport', () => {
|
|
|
55
55
|
expect(output).not.toContain('v22.16.0');
|
|
56
56
|
expect(output).toContain('Everything ready.');
|
|
57
57
|
expect(output).toContain('ktx status --json');
|
|
58
|
-
expect(output).toContain('ktx sl
|
|
59
|
-
expect(output).toContain('ktx wiki
|
|
58
|
+
expect(output).toContain('ktx sl');
|
|
59
|
+
expect(output).toContain('ktx wiki');
|
|
60
60
|
expect(output).not.toContain('ktx scan');
|
|
61
61
|
expect(output).not.toContain('ktx sl ask');
|
|
62
62
|
});
|
|
@@ -412,8 +412,8 @@ describe('runKtxDoctor', () => {
|
|
|
412
412
|
expect(out).toContain('info: pg_stat_statements.max is 1000');
|
|
413
413
|
expect(out).not.toContain('Update the Postgres parameter group or config');
|
|
414
414
|
expect(out).toContain('ktx status --json');
|
|
415
|
-
expect(out).toContain('ktx sl
|
|
416
|
-
expect(out).toContain('ktx wiki
|
|
415
|
+
expect(out).toContain('ktx sl');
|
|
416
|
+
expect(out).toContain('ktx wiki');
|
|
417
417
|
expect(out).not.toContain('ktx scan');
|
|
418
418
|
expect(out).not.toContain('ktx sl ask');
|
|
419
419
|
delete process.env.ANTHROPIC_API_KEY;
|
|
@@ -51,10 +51,10 @@ describe('standalone local warehouse example', () => {
|
|
|
51
51
|
});
|
|
52
52
|
it('runs local CLI commands against the copied example project', async () => {
|
|
53
53
|
const projectDir = await copyExampleProject(tempDir);
|
|
54
|
-
const knowledgeList = await runBuiltCli(['wiki', '
|
|
54
|
+
const knowledgeList = await runBuiltCli(['wiki', 'revenue', '--json', '--project-dir', projectDir]);
|
|
55
55
|
expect(knowledgeList).toMatchObject({ code: 0, stderr: '' });
|
|
56
56
|
expect(parseJsonOutput(knowledgeList.stdout).data.items).toContainEqual(expect.objectContaining({ key: 'revenue', summary: 'Paid order value after refunds' }));
|
|
57
|
-
const slList = await runBuiltCli(['sl', '
|
|
57
|
+
const slList = await runBuiltCli(['sl', '--json', '--project-dir', projectDir, '--connection-id', 'warehouse']);
|
|
58
58
|
expect(slList).toMatchObject({ code: 0, stderr: '' });
|
|
59
59
|
expect(parseJsonOutput(slList.stdout).data.items).toContainEqual(expect.objectContaining({ connectionId: 'warehouse', name: 'orders', columnCount: 3 }));
|
|
60
60
|
const slSearch = await runBuiltCli([
|
|
@@ -78,6 +78,6 @@ describe('standalone local warehouse example', () => {
|
|
|
78
78
|
'fake',
|
|
79
79
|
]);
|
|
80
80
|
expect(ingest).toMatchObject({ code: 1, stdout: '' });
|
|
81
|
-
expect(ingest.stderr).toContain("unknown option '--
|
|
81
|
+
expect(ingest.stderr).toContain("unknown option '--adapter'");
|
|
82
82
|
}, 30_000);
|
|
83
83
|
});
|
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');
|
|
@@ -367,7 +374,7 @@ describe('runKtxCli', () => {
|
|
|
367
374
|
await initKtxProject({ projectDir });
|
|
368
375
|
const commands = [
|
|
369
376
|
['--project-dir', projectDir, 'status', '--json'],
|
|
370
|
-
['--project-dir', projectDir, 'sl', '
|
|
377
|
+
['--project-dir', projectDir, 'sl', '--json'],
|
|
371
378
|
];
|
|
372
379
|
for (const argv of commands) {
|
|
373
380
|
const testIo = makeIo();
|
|
@@ -483,8 +490,8 @@ describe('runKtxCli', () => {
|
|
|
483
490
|
it('rejects removed shell completion commands', async () => {
|
|
484
491
|
const completionIo = makeIo();
|
|
485
492
|
const hiddenIo = makeIo();
|
|
486
|
-
await expect(runKtxCli(['
|
|
487
|
-
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);
|
|
488
495
|
expect(completionIo.stderr()).toMatch(/unknown command|error:/);
|
|
489
496
|
expect(hiddenIo.stderr()).toMatch(/unknown command|error:/);
|
|
490
497
|
});
|
|
@@ -636,7 +643,8 @@ describe('runKtxCli', () => {
|
|
|
636
643
|
expect(testIo.stdout()).toContain('--query-history');
|
|
637
644
|
expect(testIo.stdout()).toContain('--no-query-history');
|
|
638
645
|
expect(testIo.stdout()).toContain('--query-history-window-days <days>');
|
|
639
|
-
expect(testIo.stdout()).toContain('text');
|
|
646
|
+
expect(testIo.stdout()).toContain('--text');
|
|
647
|
+
expect(testIo.stdout()).toContain('--file');
|
|
640
648
|
expect(testIo.stdout()).not.toMatch(/^ status\s/m);
|
|
641
649
|
expect(testIo.stdout()).not.toMatch(/^ replay\s/m);
|
|
642
650
|
expect(testIo.stdout()).not.toMatch(/^ run\s/m);
|
|
@@ -652,7 +660,6 @@ describe('runKtxCli', () => {
|
|
|
652
660
|
'--project-dir',
|
|
653
661
|
tempDir,
|
|
654
662
|
'ingest',
|
|
655
|
-
'text',
|
|
656
663
|
'--text',
|
|
657
664
|
'Revenue means gross receipts.',
|
|
658
665
|
'--text',
|
|
@@ -675,37 +682,46 @@ describe('runKtxCli', () => {
|
|
|
675
682
|
}, testIo.io);
|
|
676
683
|
expect(testIo.stderr()).toBe('');
|
|
677
684
|
});
|
|
678
|
-
it('
|
|
685
|
+
it('rejects a positional connection id when --text is supplied', async () => {
|
|
679
686
|
const textIngest = vi.fn(async () => 0);
|
|
687
|
+
const publicIngest = vi.fn(async () => 0);
|
|
680
688
|
const testIo = makeIo();
|
|
681
|
-
await expect(runKtxCli(['ingest', '
|
|
682
|
-
expect(testIo.stdout()).toContain('Usage: ktx ingest text [options] [files...]');
|
|
683
|
-
expect(testIo.stdout()).toContain('--text <content>');
|
|
684
|
-
expect(testIo.stdout()).toContain('--connection-id <connectionId>');
|
|
685
|
-
expect(testIo.stdout()).toContain('--user-id <id>');
|
|
686
|
-
expect(testIo.stdout()).toContain('--fail-fast');
|
|
687
|
-
expect(testIo.stdout()).not.toContain('--manifest');
|
|
689
|
+
await expect(runKtxCli(['--project-dir', tempDir, 'ingest', 'warehouse', '--text', 'hello'], testIo.io, { textIngest, publicIngest })).resolves.toBe(1);
|
|
688
690
|
expect(textIngest).not.toHaveBeenCalled();
|
|
691
|
+
expect(publicIngest).not.toHaveBeenCalled();
|
|
692
|
+
expect(testIo.stderr()).toMatch(/--text\/--file does not accept a positional connection id/);
|
|
693
|
+
});
|
|
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();
|
|
689
705
|
});
|
|
690
|
-
it('rejects old adapter-backed ingest flags at the top level and under
|
|
706
|
+
it('rejects old adapter-backed ingest flags at the top level and under admin', async () => {
|
|
691
707
|
const rootRunIo = makeIo();
|
|
692
708
|
const devRunIo = makeIo();
|
|
693
709
|
const publicIngest = vi.fn(async () => 0);
|
|
694
710
|
await expect(runKtxCli(['ingest', 'run', '--connection-id', 'warehouse', '--adapter', 'metabase'], rootRunIo.io, {
|
|
695
711
|
publicIngest,
|
|
696
712
|
})).resolves.toBe(1);
|
|
697
|
-
await expect(runKtxCli(['
|
|
713
|
+
await expect(runKtxCli(['admin', 'ingest', 'run', '--connection-id', 'warehouse', '--adapter', 'metabase'], devRunIo.io, {
|
|
698
714
|
publicIngest,
|
|
699
715
|
})).resolves.toBe(1);
|
|
700
716
|
expect(publicIngest).not.toHaveBeenCalled();
|
|
701
717
|
expect(rootRunIo.stderr()).toMatch(/unknown option '--connection-id'|error:/);
|
|
702
718
|
expect(devRunIo.stderr()).toMatch(/unknown command|error:/);
|
|
703
719
|
});
|
|
704
|
-
it('rejects removed
|
|
720
|
+
it('rejects removed admin doctor and removed ingest parser cases', async () => {
|
|
705
721
|
const doctor = vi.fn(async () => 0);
|
|
706
722
|
const doctorIo = makeIo();
|
|
707
723
|
const ingestRunIo = makeIo();
|
|
708
|
-
await expect(runKtxCli(['
|
|
724
|
+
await expect(runKtxCli(['admin', 'doctor', 'setup', '--json', '--no-input'], doctorIo.io, { doctor })).resolves.toBe(1);
|
|
709
725
|
await expect(runKtxCli([
|
|
710
726
|
'ingest',
|
|
711
727
|
'run',
|
|
@@ -1237,10 +1253,10 @@ describe('runKtxCli', () => {
|
|
|
1237
1253
|
], serveIo.io)).resolves.toBe(1);
|
|
1238
1254
|
expect(serveIo.stderr()).toMatch(/unknown command|error:/);
|
|
1239
1255
|
});
|
|
1240
|
-
it('prints
|
|
1256
|
+
it('prints admin help for bare admin commands', async () => {
|
|
1241
1257
|
const testIo = makeIo();
|
|
1242
|
-
await expect(runKtxCli(['
|
|
1243
|
-
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]');
|
|
1244
1260
|
expect(testIo.stdout()).toContain('Low-level project initialization');
|
|
1245
1261
|
expect(testIo.stdout()).toContain('init');
|
|
1246
1262
|
expect(testIo.stdout()).toContain('runtime');
|
|
@@ -1251,20 +1267,20 @@ describe('runKtxCli', () => {
|
|
|
1251
1267
|
expect(testIo.stdout()).not.toContain('knowledge');
|
|
1252
1268
|
expect(testIo.stderr()).toBe('');
|
|
1253
1269
|
});
|
|
1254
|
-
it('rejects removed
|
|
1270
|
+
it('rejects removed admin command groups without invoking execution', async () => {
|
|
1255
1271
|
for (const command of ['scan', 'ingest', 'mapping']) {
|
|
1256
1272
|
const testIo = makeIo();
|
|
1257
1273
|
const publicIngest = vi.fn().mockResolvedValue(0);
|
|
1258
1274
|
const sl = vi.fn().mockResolvedValue(0);
|
|
1259
|
-
await expect(runKtxCli(['
|
|
1275
|
+
await expect(runKtxCli(['admin', command], testIo.io, { publicIngest, sl })).resolves.toBe(1);
|
|
1260
1276
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
|
1261
1277
|
expect(publicIngest).not.toHaveBeenCalled();
|
|
1262
1278
|
expect(sl).not.toHaveBeenCalled();
|
|
1263
1279
|
}
|
|
1264
1280
|
});
|
|
1265
|
-
it('rejects removed reserved
|
|
1281
|
+
it('rejects removed reserved admin subcommands', async () => {
|
|
1266
1282
|
const testIo = makeIo();
|
|
1267
|
-
await expect(runKtxCli(['
|
|
1283
|
+
await expect(runKtxCli(['admin', 'artifacts'], testIo.io)).resolves.toBe(1);
|
|
1268
1284
|
expect(testIo.stderr()).toMatch(/unknown command|error:/);
|
|
1269
1285
|
});
|
|
1270
1286
|
it('rejects mutually exclusive public ingest output modes before invoking runners', async () => {
|