@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.
Files changed (122) hide show
  1. package/assets/python/{kaelio_ktx-0.1.0-py3-none-any.whl → kaelio_ktx-0.2.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/admin-reindex.d.ts +15 -0
  4. package/dist/admin-reindex.js +168 -0
  5. package/dist/admin-reindex.test.js +116 -0
  6. package/dist/{dev.d.ts → admin.d.ts} +1 -1
  7. package/dist/{dev.js → admin.js} +14 -12
  8. package/dist/admin.test.d.ts +1 -0
  9. package/dist/{dev.test.js → admin.test.js} +36 -31
  10. package/dist/cli-program.js +7 -7
  11. package/dist/cli-program.test.js +1 -1
  12. package/dist/cli-runtime.d.ts +2 -0
  13. package/dist/commands/connection-commands.js +11 -10
  14. package/dist/commands/connection-selection.d.ts +11 -0
  15. package/dist/commands/connection-selection.js +9 -0
  16. package/dist/commands/ingest-commands.js +32 -26
  17. package/dist/commands/knowledge-commands.js +17 -28
  18. package/dist/commands/mcp-commands.js +17 -11
  19. package/dist/commands/setup-commands.js +14 -26
  20. package/dist/commands/sl-commands.js +27 -32
  21. package/dist/doctor.test.js +7 -8
  22. package/dist/example-smoke.test.js +3 -3
  23. package/dist/index.test.js +102 -70
  24. package/dist/ingest-depth.js +0 -1
  25. package/dist/ingest.test-utils.js +2 -2
  26. package/dist/ingest.test.js +4 -4
  27. package/dist/io/print-list.test.js +4 -4
  28. package/dist/knowledge.js +1 -1
  29. package/dist/managed-local-embeddings.d.ts +2 -0
  30. package/dist/managed-local-embeddings.js +2 -0
  31. package/dist/managed-local-embeddings.test.js +2 -0
  32. package/dist/managed-mcp-daemon.js +3 -2
  33. package/dist/managed-mcp-daemon.test.js +25 -0
  34. package/dist/managed-python-command.js +2 -2
  35. package/dist/managed-python-command.test.js +4 -3
  36. package/dist/managed-python-daemon.js +3 -2
  37. package/dist/managed-python-daemon.test.js +20 -0
  38. package/dist/managed-python-runtime.d.ts +5 -1
  39. package/dist/managed-python-runtime.js +50 -6
  40. package/dist/managed-python-runtime.test.js +53 -23
  41. package/dist/memory-flow-tui.test.js +2 -2
  42. package/dist/next-steps.d.ts +6 -6
  43. package/dist/next-steps.js +4 -4
  44. package/dist/next-steps.test.js +5 -5
  45. package/dist/print-command-tree.test.js +1 -1
  46. package/dist/proxy-env.d.ts +1 -0
  47. package/dist/proxy-env.js +23 -0
  48. package/dist/proxy-env.test.d.ts +1 -0
  49. package/dist/proxy-env.test.js +17 -0
  50. package/dist/public-ingest.js +3 -5
  51. package/dist/public-ingest.test.js +7 -3
  52. package/dist/runtime.test.js +2 -1
  53. package/dist/scan.test.js +2 -2
  54. package/dist/setup-agents.js +6 -4
  55. package/dist/setup-agents.test.js +35 -1
  56. package/dist/setup-embeddings.d.ts +1 -0
  57. package/dist/setup-embeddings.js +29 -7
  58. package/dist/setup-embeddings.test.js +49 -7
  59. package/dist/setup-models.d.ts +0 -1
  60. package/dist/setup-models.js +2 -3
  61. package/dist/setup-models.test.js +8 -10
  62. package/dist/setup-project.d.ts +9 -1
  63. package/dist/setup-project.js +52 -25
  64. package/dist/setup-project.test.js +8 -8
  65. package/dist/setup-runtime.test.js +4 -2
  66. package/dist/setup.d.ts +1 -2
  67. package/dist/setup.js +21 -5
  68. package/dist/setup.test.js +160 -43
  69. package/dist/sl.js +1 -1
  70. package/dist/sl.test.js +2 -1
  71. package/dist/standalone-smoke.test.js +8 -5
  72. package/dist/status-project.js +1 -10
  73. package/node_modules/@ktx/context/dist/index-sync/index.d.ts +2 -0
  74. package/node_modules/@ktx/context/dist/index-sync/index.js +1 -0
  75. package/node_modules/@ktx/context/dist/index-sync/reindex.d.ts +20 -0
  76. package/node_modules/@ktx/context/dist/index-sync/reindex.js +141 -0
  77. package/node_modules/@ktx/context/dist/index-sync/reindex.test.d.ts +1 -0
  78. package/node_modules/@ktx/context/dist/index-sync/reindex.test.js +139 -0
  79. package/node_modules/@ktx/context/dist/index-sync/types.d.ts +29 -0
  80. package/node_modules/@ktx/context/dist/index-sync/types.js +1 -0
  81. package/node_modules/@ktx/context/dist/index.d.ts +1 -0
  82. package/node_modules/@ktx/context/dist/index.js +1 -0
  83. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +1 -1
  84. package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +8 -8
  85. package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +4 -1
  86. package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.test.js +3 -3
  87. package/node_modules/@ktx/context/dist/ingest/local-embedding-provider.integration.test.js +9 -10
  88. package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.d.ts +2 -2
  89. package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +2 -2
  90. package/node_modules/@ktx/context/dist/llm/local-config.js +2 -15
  91. package/node_modules/@ktx/context/dist/llm/local-config.test.js +3 -7
  92. package/node_modules/@ktx/context/dist/memory/local-memory.js +9 -3
  93. package/node_modules/@ktx/context/dist/project/config.d.ts +0 -5
  94. package/node_modules/@ktx/context/dist/project/config.js +5 -5
  95. package/node_modules/@ktx/context/dist/project/config.test.js +4 -7
  96. package/node_modules/@ktx/context/dist/scan/enrichment-state.test.js +4 -4
  97. package/node_modules/@ktx/context/dist/scan/index.d.ts +1 -1
  98. package/node_modules/@ktx/context/dist/scan/local-enrichment.d.ts +2 -6
  99. package/node_modules/@ktx/context/dist/scan/local-enrichment.js +31 -47
  100. package/node_modules/@ktx/context/dist/scan/local-enrichment.test.js +35 -18
  101. package/node_modules/@ktx/context/dist/scan/local-scan.test.js +2 -3
  102. package/node_modules/@ktx/context/dist/sl/ports.d.ts +3 -3
  103. package/node_modules/@ktx/context/dist/sl/sl-search.service.d.ts +3 -2
  104. package/node_modules/@ktx/context/dist/sl/sl-search.service.js +47 -45
  105. package/node_modules/@ktx/context/dist/sl/sl-search.service.test.js +61 -0
  106. package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.d.ts +4 -3
  107. package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.js +15 -5
  108. package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.test.js +24 -0
  109. package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.d.ts +3 -2
  110. package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.js +62 -51
  111. package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.test.js +59 -3
  112. package/node_modules/@ktx/context/dist/wiki/ports.d.ts +3 -3
  113. package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.d.ts +33 -0
  114. package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.js +155 -2
  115. package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.test.js +26 -0
  116. package/node_modules/@ktx/context/package.json +5 -0
  117. package/node_modules/@ktx/llm/dist/embedding-provider.d.ts +0 -7
  118. package/node_modules/@ktx/llm/dist/embedding-provider.js +12 -138
  119. package/node_modules/@ktx/llm/dist/embedding-provider.test.js +10 -25
  120. package/node_modules/@ktx/llm/dist/types.d.ts +1 -1
  121. package/package.json +1 -1
  122. /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: resolveCommandProjectDir(command),
29
- ...(connectionId ? { targetConnectionId: connectionId } : {}),
30
- all: options.all === true,
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
- const wiki = program
14
+ program
15
15
  .command('wiki')
16
16
  .description('List or search local wiki pages')
17
- .showHelpAfterError()
18
- .addHelpText('after', '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n');
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.command('mcp').description('Run the KTX MCP HTTP server');
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
- const status = await (context.deps.mcp?.readStatus ?? readKtxMcpDaemonStatus)({
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 safe defaults in non-interactive setup', false)
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 mode = options.new ? 'new' : options.existing ? 'existing' : 'auto';
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 ? { databaseConnectionIds: options.databaseConnectionId } : {}),
315
- ...(options.newDatabaseConnectionId ? { databaseConnectionId: options.newDatabaseConnectionId } : {}),
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
- .showHelpAfterError()
32
- .addHelpText('after', '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n');
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
- .requiredOption('--connection-id <id>', 'KTX connection id')
77
- .action(async (sourceName, options, command) => {
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: options.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: options.connectionId,
103
+ connectionId: parentOpts?.connectionId,
109
104
  ...(options.queryFile
110
105
  ? { queryFile: options.queryFile }
111
106
  : {
@@ -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 list');
59
- expect(output).toContain('ktx wiki list');
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 list');
416
- expect(out).toContain('ktx wiki list');
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: deterministic',
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('deterministic');
510
- expect(testIo.stdout()).toContain('semantic search degraded');
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', 'search', 'revenue', '--json', '--project-dir', projectDir]);
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', 'list', '--json', '--project-dir', projectDir, '--connection-id', 'warehouse']);
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 '--connection-id'");
81
+ expect(ingest.stderr).toContain("unknown option '--adapter'");
82
82
  }, 30_000);
83
83
  });