@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
|
@@ -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')
|
|
@@ -79,8 +79,6 @@ function shouldShowSetupEntryMenu(options, command) {
|
|
|
79
79
|
return false;
|
|
80
80
|
}
|
|
81
81
|
return ![
|
|
82
|
-
'new',
|
|
83
|
-
'existing',
|
|
84
82
|
'agents',
|
|
85
83
|
'target',
|
|
86
84
|
'global',
|
|
@@ -92,7 +90,6 @@ function shouldShowSetupEntryMenu(options, command) {
|
|
|
92
90
|
'anthropicApiKeyEnv',
|
|
93
91
|
'anthropicApiKeyFile',
|
|
94
92
|
'llmModel',
|
|
95
|
-
'anthropicModel',
|
|
96
93
|
'vertexProject',
|
|
97
94
|
'vertexLocation',
|
|
98
95
|
'skipLlm',
|
|
@@ -100,7 +97,6 @@ function shouldShowSetupEntryMenu(options, command) {
|
|
|
100
97
|
'embeddingApiKeyEnv',
|
|
101
98
|
'embeddingApiKeyFile',
|
|
102
99
|
'skipEmbeddings',
|
|
103
|
-
'newDatabaseConnectionId',
|
|
104
100
|
'databaseUrl',
|
|
105
101
|
'enableQueryHistory',
|
|
106
102
|
'disableQueryHistory',
|
|
@@ -132,8 +128,6 @@ export function registerSetupCommands(program, context) {
|
|
|
132
128
|
.command('setup')
|
|
133
129
|
.description('Set up or resume a local KTX project')
|
|
134
130
|
.addOption(new Option('--project-dir <path>', 'KTX project directory').hideHelp())
|
|
135
|
-
.addOption(new Option('--new', 'Create a new KTX project before setup').hideHelp().default(false))
|
|
136
|
-
.addOption(new Option('--existing', 'Use an existing KTX project').hideHelp().default(false))
|
|
137
131
|
.option('--agents', 'Install agent integration only', false)
|
|
138
132
|
.addOption(new Option('--target <target>', 'Agent target').choices([
|
|
139
133
|
'claude-code',
|
|
@@ -146,13 +140,12 @@ export function registerSetupCommands(program, context) {
|
|
|
146
140
|
.option('--global', 'Install agent integration into the global target scope', false)
|
|
147
141
|
.option('--local', 'Install Claude Code MCP config into the private per-project ~/.claude.json scope', false)
|
|
148
142
|
.addOption(new Option('--skip-agents', 'Leave agent integration incomplete for now').hideHelp().default(false))
|
|
149
|
-
.option('--yes', 'Accept
|
|
143
|
+
.option('--yes', 'Accept project creation and runtime install defaults where setup confirms', false)
|
|
150
144
|
.option('--no-input', 'Disable interactive terminal input')
|
|
151
145
|
.addOption(new Option('--llm-backend <backend>', 'LLM backend').argParser(llmBackend).hideHelp())
|
|
152
146
|
.addOption(new Option('--anthropic-api-key-env <name>', 'Environment variable containing the Anthropic API key').hideHelp())
|
|
153
147
|
.addOption(new Option('--anthropic-api-key-file <path>', 'File containing the Anthropic API key').hideHelp())
|
|
154
148
|
.addOption(new Option('--llm-model <model>', 'LLM model ID or backend model alias').hideHelp())
|
|
155
|
-
.addOption(new Option('--anthropic-model <model>', 'Anthropic model ID to validate and save').hideHelp())
|
|
156
149
|
.addOption(new Option('--vertex-project <project>', 'Google Vertex AI project ID, env:NAME, or file:/path').hideHelp())
|
|
157
150
|
.addOption(new Option('--vertex-location <location>', 'Google Vertex AI location, env:NAME, or file:/path').hideHelp())
|
|
158
151
|
.addOption(new Option('--skip-llm', 'Leave LLM setup incomplete for now').hideHelp().default(false))
|
|
@@ -169,14 +162,6 @@ export function registerSetupCommands(program, context) {
|
|
|
169
162
|
.addOption(new Option('--database-connection-id <id>', 'Existing selected connection id or new connection id')
|
|
170
163
|
.argParser((value, previous) => [...previous, value])
|
|
171
164
|
.default([])
|
|
172
|
-
.hideHelp())
|
|
173
|
-
.addOption(new Option('--new-database-connection-id <id>', 'Connection id for one new database connection')
|
|
174
|
-
.argParser((value) => {
|
|
175
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
|
|
176
|
-
throw new InvalidArgumentError(`Unsafe connection id: ${value}`);
|
|
177
|
-
}
|
|
178
|
-
return value;
|
|
179
|
-
})
|
|
180
165
|
.hideHelp())
|
|
181
166
|
.addOption(new Option('--database-url <url>', 'URL, env:NAME, or file:/path for one new URL-style database connection').hideHelp())
|
|
182
167
|
.addOption(new Option('--database-schema <schema>', 'Database schema to include; repeatable')
|
|
@@ -238,11 +223,6 @@ export function registerSetupCommands(program, context) {
|
|
|
238
223
|
context.setExitCode(1);
|
|
239
224
|
return;
|
|
240
225
|
}
|
|
241
|
-
if (options.llmModel && options.anthropicModel) {
|
|
242
|
-
context.io.stderr.write('Choose only one LLM model flag: --llm-model or --anthropic-model.\n');
|
|
243
|
-
context.setExitCode(1);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
226
|
if (options.llmBackend &&
|
|
247
227
|
options.llmBackend !== 'anthropic' &&
|
|
248
228
|
(options.anthropicApiKeyEnv || options.anthropicApiKeyFile)) {
|
|
@@ -285,12 +265,17 @@ export function registerSetupCommands(program, context) {
|
|
|
285
265
|
context.setExitCode(1);
|
|
286
266
|
return;
|
|
287
267
|
}
|
|
288
|
-
const
|
|
268
|
+
const creatingDatabaseConnection = options.database.length > 0 || options.databaseUrl !== undefined;
|
|
269
|
+
if (creatingDatabaseConnection && options.databaseConnectionId.length > 1) {
|
|
270
|
+
context.io.stderr.write('Choose only one new database connection id when configuring a database.\n');
|
|
271
|
+
context.setExitCode(1);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
289
274
|
const resolvedAgentScope = options.local ? 'local' : options.global ? 'global' : 'project';
|
|
290
275
|
await runSetupArgs(context, {
|
|
291
276
|
command: 'run',
|
|
292
277
|
projectDir: resolveCommandProjectDir(command),
|
|
293
|
-
mode,
|
|
278
|
+
mode: 'auto',
|
|
294
279
|
agents: options.agents === true,
|
|
295
280
|
...(options.target ? { target: options.target } : {}),
|
|
296
281
|
agentScope: resolvedAgentScope,
|
|
@@ -302,7 +287,6 @@ export function registerSetupCommands(program, context) {
|
|
|
302
287
|
...(options.anthropicApiKeyEnv ? { anthropicApiKeyEnv: options.anthropicApiKeyEnv } : {}),
|
|
303
288
|
...(options.anthropicApiKeyFile ? { anthropicApiKeyFile: options.anthropicApiKeyFile } : {}),
|
|
304
289
|
...(options.llmModel ? { llmModel: options.llmModel } : {}),
|
|
305
|
-
...(options.anthropicModel ? { anthropicModel: options.anthropicModel } : {}),
|
|
306
290
|
...(options.vertexProject ? { vertexProject: options.vertexProject } : {}),
|
|
307
291
|
...(options.vertexLocation ? { vertexLocation: options.vertexLocation } : {}),
|
|
308
292
|
skipLlm: options.skipLlm === true,
|
|
@@ -311,8 +295,12 @@ export function registerSetupCommands(program, context) {
|
|
|
311
295
|
...(options.embeddingApiKeyFile ? { embeddingApiKeyFile: options.embeddingApiKeyFile } : {}),
|
|
312
296
|
skipEmbeddings: options.skipEmbeddings === true,
|
|
313
297
|
...(options.database.length > 0 ? { databaseDrivers: options.database } : {}),
|
|
314
|
-
...(options.databaseConnectionId.length > 0
|
|
315
|
-
|
|
298
|
+
...(options.databaseConnectionId.length > 0 && creatingDatabaseConnection
|
|
299
|
+
? { databaseConnectionId: options.databaseConnectionId[0] }
|
|
300
|
+
: {}),
|
|
301
|
+
...(options.databaseConnectionId.length > 0 && !creatingDatabaseConnection
|
|
302
|
+
? { databaseConnectionIds: options.databaseConnectionId }
|
|
303
|
+
: {}),
|
|
316
304
|
...(options.databaseUrl ? { databaseUrl: options.databaseUrl } : {}),
|
|
317
305
|
databaseSchemas: options.databaseSchema,
|
|
318
306
|
...(options.enableQueryHistory ? { enableQueryHistory: true } : {}),
|
|
@@ -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;
|
|
@@ -498,16 +498,15 @@ describe('runKtxDoctor', () => {
|
|
|
498
498
|
' adapters:',
|
|
499
499
|
' - live-database',
|
|
500
500
|
' embeddings:',
|
|
501
|
-
' backend:
|
|
502
|
-
' model: deterministic',
|
|
501
|
+
' backend: none',
|
|
503
502
|
' dimensions: 8',
|
|
504
503
|
'',
|
|
505
504
|
].join('\n'), 'utf-8');
|
|
506
505
|
const testIo = makeIo();
|
|
507
506
|
await expect(runKtxDoctor({ command: 'project', projectDir: tempDir, outputMode: 'plain', inputMode: 'disabled' }, testIo.io, {})).resolves.toBe(0);
|
|
508
507
|
expect(testIo.stdout()).toContain('Embeddings');
|
|
509
|
-
expect(testIo.stdout()).toContain('
|
|
510
|
-
expect(testIo.stdout()).toContain('semantic search
|
|
508
|
+
expect(testIo.stdout()).toContain('none');
|
|
509
|
+
expect(testIo.stdout()).toContain('semantic search will be skipped');
|
|
511
510
|
delete process.env.ANTHROPIC_API_KEY;
|
|
512
511
|
});
|
|
513
512
|
describe('command: validate', () => {
|
|
@@ -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
|
});
|