@kaelio/ktx 0.1.1 → 0.3.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 (146) hide show
  1. package/assets/python/{kaelio_ktx-0.1.1-py3-none-any.whl → kaelio_ktx-0.3.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 +161 -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/{dev.test.js → admin.test.js} +36 -31
  9. package/dist/cli-program.js +7 -7
  10. package/dist/cli-program.test.js +1 -3
  11. package/dist/cli-project.d.ts +18 -0
  12. package/dist/cli-project.js +52 -0
  13. package/dist/cli-project.test.d.ts +1 -0
  14. package/dist/cli-project.test.js +149 -0
  15. package/dist/cli-runtime.d.ts +2 -2
  16. package/dist/cli-runtime.js +2 -8
  17. package/dist/commands/connection-commands.js +11 -10
  18. package/dist/commands/connection-selection.d.ts +11 -0
  19. package/dist/commands/connection-selection.js +9 -0
  20. package/dist/commands/ingest-commands.js +32 -26
  21. package/dist/commands/knowledge-commands.js +17 -28
  22. package/dist/commands/mcp-commands.js +17 -11
  23. package/dist/commands/runtime-commands.js +2 -2
  24. package/dist/commands/sl-commands.js +27 -32
  25. package/dist/context-build-view.js +1 -1
  26. package/dist/doctor.test.js +4 -4
  27. package/dist/example-smoke.test.js +3 -3
  28. package/dist/index.test.js +97 -85
  29. package/dist/ingest.js +9 -2
  30. package/dist/ingest.test.js +27 -3
  31. package/dist/io/print-list.test.js +4 -4
  32. package/dist/knowledge.js +1 -1
  33. package/dist/managed-local-embeddings.d.ts +0 -2
  34. package/dist/managed-local-embeddings.js +2 -5
  35. package/dist/managed-local-embeddings.test.js +5 -8
  36. package/dist/managed-python-command.js +2 -2
  37. package/dist/managed-python-command.test.js +3 -3
  38. package/dist/managed-python-daemon.js +2 -2
  39. package/dist/managed-python-daemon.test.js +1 -1
  40. package/dist/managed-python-http.js +3 -3
  41. package/dist/managed-python-http.test.js +6 -6
  42. package/dist/managed-python-runtime.d.ts +1 -1
  43. package/dist/managed-python-runtime.js +3 -3
  44. package/dist/managed-python-runtime.test.js +2 -2
  45. package/dist/memory-flow-tui.test.js +2 -2
  46. package/dist/next-steps.d.ts +6 -6
  47. package/dist/next-steps.js +4 -4
  48. package/dist/next-steps.test.js +5 -5
  49. package/dist/print-command-tree.js +0 -2
  50. package/dist/print-command-tree.test.js +1 -1
  51. package/dist/public-ingest.d.ts +4 -2
  52. package/dist/public-ingest.js +12 -8
  53. package/dist/public-ingest.test.js +7 -3
  54. package/dist/release-version.d.ts +1 -5
  55. package/dist/release-version.js +2 -39
  56. package/dist/runtime-requirements.js +1 -1
  57. package/dist/runtime.js +6 -6
  58. package/dist/runtime.test.js +8 -8
  59. package/dist/scan.js +7 -2
  60. package/dist/scan.test.js +3 -3
  61. package/dist/setup-agents.js +3 -3
  62. package/dist/setup-agents.test.js +1 -1
  63. package/dist/setup-embeddings.js +2 -2
  64. package/dist/setup-embeddings.test.js +5 -5
  65. package/dist/setup-runtime.test.js +3 -3
  66. package/dist/sl.js +1 -1
  67. package/dist/standalone-smoke.test.js +6 -2
  68. package/node_modules/@ktx/context/dist/core/git.service.d.ts +1 -0
  69. package/node_modules/@ktx/context/dist/core/git.service.js +12 -0
  70. package/node_modules/@ktx/context/dist/index-sync/index.d.ts +2 -0
  71. package/node_modules/@ktx/context/dist/index-sync/index.js +1 -0
  72. package/node_modules/@ktx/context/dist/index-sync/reindex.d.ts +20 -0
  73. package/node_modules/@ktx/context/dist/index-sync/reindex.js +141 -0
  74. package/node_modules/@ktx/context/dist/index-sync/reindex.test.d.ts +1 -0
  75. package/node_modules/@ktx/context/dist/index-sync/reindex.test.js +139 -0
  76. package/node_modules/@ktx/context/dist/index-sync/types.d.ts +29 -0
  77. package/node_modules/@ktx/context/dist/index-sync/types.js +1 -0
  78. package/node_modules/@ktx/context/dist/index.d.ts +1 -0
  79. package/node_modules/@ktx/context/dist/index.js +1 -0
  80. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.d.ts +2 -1
  81. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.js +18 -0
  82. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +6 -6
  83. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.d.ts +5 -0
  84. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.js +48 -0
  85. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.test.js +83 -0
  86. package/node_modules/@ktx/context/dist/ingest/adapters/live-database/daemon-introspection.js +4 -1
  87. package/node_modules/@ktx/context/dist/ingest/adapters/live-database/daemon-introspection.test.js +32 -0
  88. package/node_modules/@ktx/context/dist/ingest/finalization-scope.d.ts +22 -0
  89. package/node_modules/@ktx/context/dist/ingest/finalization-scope.js +95 -0
  90. package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.d.ts +1 -0
  91. package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.js +114 -0
  92. package/node_modules/@ktx/context/dist/ingest/index.d.ts +1 -2
  93. package/node_modules/@ktx/context/dist/ingest/index.js +0 -1
  94. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.d.ts +2 -0
  95. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.isolated-diff.test.js +166 -0
  96. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.js +235 -45
  97. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.test.js +193 -38
  98. package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +22 -3
  99. package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +3 -4
  100. package/node_modules/@ktx/context/dist/ingest/local-ingest.js +0 -7
  101. package/node_modules/@ktx/context/dist/ingest/local-stage-ingest.js +15 -5
  102. package/node_modules/@ktx/context/dist/ingest/local-stage-ingest.test.js +29 -0
  103. package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.d.ts +4 -4
  104. package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.js +1 -1
  105. package/node_modules/@ktx/context/dist/ingest/memory-flow/types.d.ts +1 -1
  106. package/node_modules/@ktx/context/dist/ingest/ports.d.ts +1 -20
  107. package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +73 -2
  108. package/node_modules/@ktx/context/dist/ingest/report-snapshot.js +27 -0
  109. package/node_modules/@ktx/context/dist/ingest/reports.d.ts +23 -5
  110. package/node_modules/@ktx/context/dist/ingest/reports.js +7 -24
  111. package/node_modules/@ktx/context/dist/ingest/types.d.ts +33 -0
  112. package/node_modules/@ktx/context/dist/llm/index.d.ts +1 -1
  113. package/node_modules/@ktx/context/dist/llm/index.js +1 -1
  114. package/node_modules/@ktx/context/dist/llm/local-config.d.ts +0 -1
  115. package/node_modules/@ktx/context/dist/llm/local-config.js +2 -12
  116. package/node_modules/@ktx/context/dist/llm/local-config.test.js +2 -23
  117. package/node_modules/@ktx/context/dist/memory/local-memory.js +9 -3
  118. package/node_modules/@ktx/context/dist/package-exports.test.js +2 -2
  119. package/node_modules/@ktx/context/dist/project/config.d.ts +16 -0
  120. package/node_modules/@ktx/context/dist/project/driver-schemas.d.ts +8 -0
  121. package/node_modules/@ktx/context/dist/project/driver-schemas.js +4 -0
  122. package/node_modules/@ktx/context/dist/scan/enabled-tables.d.ts +3 -0
  123. package/node_modules/@ktx/context/dist/scan/enabled-tables.js +15 -0
  124. package/node_modules/@ktx/context/dist/scan/local-scan.d.ts +2 -4
  125. package/node_modules/@ktx/context/dist/scan/local-scan.js +2 -15
  126. package/node_modules/@ktx/context/dist/sl/ports.d.ts +3 -3
  127. package/node_modules/@ktx/context/dist/sl/sl-search.service.d.ts +3 -2
  128. package/node_modules/@ktx/context/dist/sl/sl-search.service.js +47 -45
  129. package/node_modules/@ktx/context/dist/sl/sl-search.service.test.js +61 -0
  130. package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.d.ts +4 -3
  131. package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.js +15 -5
  132. package/node_modules/@ktx/context/dist/sl/sqlite-sl-sources-index.test.js +24 -0
  133. package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.d.ts +3 -2
  134. package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.js +62 -51
  135. package/node_modules/@ktx/context/dist/wiki/knowledge-wiki.service.test.js +59 -3
  136. package/node_modules/@ktx/context/dist/wiki/ports.d.ts +3 -3
  137. package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.d.ts +33 -0
  138. package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.js +155 -2
  139. package/node_modules/@ktx/context/dist/wiki/sqlite-knowledge-index.test.js +26 -0
  140. package/node_modules/@ktx/context/package.json +5 -0
  141. package/package.json +1 -1
  142. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.d.ts +0 -4
  143. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.js +0 -38
  144. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.js +0 -63
  145. /package/dist/{dev.test.d.ts → admin-reindex.test.d.ts} +0 -0
  146. /package/{node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.d.ts → dist/admin.test.d.ts} +0 -0
@@ -0,0 +1,149 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { MANAGED_SENTENCE_TRANSFORMERS_BASE_URL } from '@ktx/context';
3
+ import { buildDefaultKtxProjectConfig } from '@ktx/context/project';
4
+ import { loadKtxCliProject, projectNeedsManagedLocalEmbeddings, substituteManagedLocalEmbeddingsUrl, } from './cli-project.js';
5
+ const RESOLVED_BASE_URL = 'http://127.0.0.1:51234';
6
+ function makeIo() {
7
+ let stderr = '';
8
+ return {
9
+ io: {
10
+ stdout: { write: (_chunk) => { } },
11
+ stderr: {
12
+ write: (chunk) => {
13
+ stderr += chunk;
14
+ },
15
+ },
16
+ },
17
+ stderr: () => stderr,
18
+ };
19
+ }
20
+ function projectWithConfig(config) {
21
+ return {
22
+ projectDir: '/work/proj',
23
+ configPath: '/work/proj/ktx.yaml',
24
+ config,
25
+ coreConfig: {},
26
+ git: {},
27
+ fileStore: {},
28
+ };
29
+ }
30
+ function withManagedIngestEmbedding(config) {
31
+ return {
32
+ ...config,
33
+ ingest: {
34
+ ...config.ingest,
35
+ embeddings: {
36
+ backend: 'sentence-transformers',
37
+ model: 'all-MiniLM-L6-v2',
38
+ dimensions: 384,
39
+ sentenceTransformers: { base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL, pathPrefix: '' },
40
+ },
41
+ },
42
+ };
43
+ }
44
+ function withManagedScanEnrichmentEmbedding(config) {
45
+ return {
46
+ ...config,
47
+ scan: {
48
+ ...config.scan,
49
+ enrichment: {
50
+ ...config.scan.enrichment,
51
+ embeddings: {
52
+ backend: 'sentence-transformers',
53
+ model: 'all-MiniLM-L6-v2',
54
+ dimensions: 384,
55
+ sentenceTransformers: { base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL, pathPrefix: '' },
56
+ },
57
+ },
58
+ },
59
+ };
60
+ }
61
+ const fakeDaemon = {
62
+ baseUrl: RESOLVED_BASE_URL,
63
+ stdoutLog: '/work/proj/.ktx/runtime/daemon.stdout.log',
64
+ stderrLog: '/work/proj/.ktx/runtime/daemon.stderr.log',
65
+ };
66
+ describe('projectNeedsManagedLocalEmbeddings', () => {
67
+ it('returns false when neither ingest nor scan embeddings reference the managed sentinel', () => {
68
+ expect(projectNeedsManagedLocalEmbeddings(buildDefaultKtxProjectConfig())).toBe(false);
69
+ });
70
+ it('returns true when ingest.embeddings uses the managed sentinel', () => {
71
+ expect(projectNeedsManagedLocalEmbeddings(withManagedIngestEmbedding(buildDefaultKtxProjectConfig()))).toBe(true);
72
+ });
73
+ it('returns true when scan.enrichment.embeddings uses the managed sentinel', () => {
74
+ expect(projectNeedsManagedLocalEmbeddings(withManagedScanEnrichmentEmbedding(buildDefaultKtxProjectConfig()))).toBe(true);
75
+ });
76
+ });
77
+ describe('substituteManagedLocalEmbeddingsUrl', () => {
78
+ it('rewrites the managed sentinel in both ingest.embeddings and scan.enrichment.embeddings', () => {
79
+ const config = withManagedScanEnrichmentEmbedding(withManagedIngestEmbedding(buildDefaultKtxProjectConfig()));
80
+ const resolved = substituteManagedLocalEmbeddingsUrl(config, RESOLVED_BASE_URL);
81
+ expect(resolved.ingest.embeddings.sentenceTransformers?.base_url).toBe(RESOLVED_BASE_URL);
82
+ expect(resolved.scan.enrichment.embeddings?.sentenceTransformers?.base_url).toBe(RESOLVED_BASE_URL);
83
+ });
84
+ it('returns the input unchanged when no sentinel is present', () => {
85
+ const config = buildDefaultKtxProjectConfig();
86
+ const resolved = substituteManagedLocalEmbeddingsUrl(config, RESOLVED_BASE_URL);
87
+ expect(resolved.ingest.embeddings).toEqual(config.ingest.embeddings);
88
+ expect(resolved.scan.enrichment.embeddings).toEqual(config.scan.enrichment.embeddings);
89
+ });
90
+ it('does not touch non-sentinel sentence-transformers URLs', () => {
91
+ const config = {
92
+ ...buildDefaultKtxProjectConfig(),
93
+ ingest: {
94
+ ...buildDefaultKtxProjectConfig().ingest,
95
+ embeddings: {
96
+ backend: 'sentence-transformers',
97
+ model: 'all-MiniLM-L6-v2',
98
+ dimensions: 384,
99
+ sentenceTransformers: { base_url: 'http://localhost:9999', pathPrefix: '' },
100
+ },
101
+ },
102
+ };
103
+ const resolved = substituteManagedLocalEmbeddingsUrl(config, RESOLVED_BASE_URL);
104
+ expect(resolved.ingest.embeddings.sentenceTransformers?.base_url).toBe('http://localhost:9999');
105
+ });
106
+ });
107
+ describe('loadKtxCliProject', () => {
108
+ it('returns the project unchanged and does not start the daemon when no sentinel is present', async () => {
109
+ const io = makeIo();
110
+ const project = projectWithConfig(buildDefaultKtxProjectConfig());
111
+ const loadProject = vi.fn(async () => project);
112
+ const ensureLocalEmbeddings = vi.fn(async () => fakeDaemon);
113
+ const result = await loadKtxCliProject({ projectDir: '/work/proj', cliVersion: '0.2.0', installPolicy: 'never', io: io.io }, { loadProject, ensureLocalEmbeddings });
114
+ expect(result).toBe(project);
115
+ expect(ensureLocalEmbeddings).not.toHaveBeenCalled();
116
+ });
117
+ it('starts the daemon and substitutes the resolved URL when ingest.embeddings uses the sentinel', async () => {
118
+ const io = makeIo();
119
+ const project = projectWithConfig(withManagedIngestEmbedding(buildDefaultKtxProjectConfig()));
120
+ const loadProject = vi.fn(async () => project);
121
+ const ensureLocalEmbeddings = vi.fn(async () => fakeDaemon);
122
+ const result = await loadKtxCliProject({ projectDir: '/work/proj', cliVersion: '0.2.0', installPolicy: 'never', io: io.io }, { loadProject, ensureLocalEmbeddings });
123
+ expect(ensureLocalEmbeddings).toHaveBeenCalledWith({
124
+ cliVersion: '0.2.0',
125
+ projectDir: '/work/proj',
126
+ installPolicy: 'never',
127
+ io: io.io,
128
+ });
129
+ expect(result.config.ingest.embeddings.sentenceTransformers?.base_url).toBe(RESOLVED_BASE_URL);
130
+ });
131
+ it('does not mutate process.env', async () => {
132
+ const io = makeIo();
133
+ const before = process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL;
134
+ delete process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL;
135
+ try {
136
+ const project = projectWithConfig(withManagedIngestEmbedding(buildDefaultKtxProjectConfig()));
137
+ await loadKtxCliProject({ projectDir: '/work/proj', cliVersion: '0.2.0', installPolicy: 'never', io: io.io }, { loadProject: vi.fn(async () => project), ensureLocalEmbeddings: vi.fn(async () => fakeDaemon) });
138
+ expect(process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL).toBeUndefined();
139
+ }
140
+ finally {
141
+ if (before === undefined) {
142
+ delete process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL;
143
+ }
144
+ else {
145
+ process.env.KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL = before;
146
+ }
147
+ }
148
+ });
149
+ });
@@ -1,4 +1,5 @@
1
1
  import type { KtxConnectionArgs } from './connection.js';
2
+ import type { KtxAdminReindexArgs } from './admin-reindex.js';
2
3
  import type { KtxDoctorArgs } from './doctor.js';
3
4
  import type { KtxKnowledgeArgs } from './knowledge.js';
4
5
  import type { KtxPublicIngestArgs } from './public-ingest.js';
@@ -10,8 +11,6 @@ import type { KtxTextIngestArgs } from './text-ingest.js';
10
11
  export interface KtxCliPackageInfo {
11
12
  name: string;
12
13
  version: string;
13
- packageVersion: string;
14
- runtimeVersion: string;
15
14
  contextPackageName: '@ktx/context';
16
15
  }
17
16
  export interface KtxCliIo {
@@ -25,6 +24,7 @@ export interface KtxCliIo {
25
24
  };
26
25
  }
27
26
  export interface KtxCliDeps {
27
+ adminReindex?: (args: KtxAdminReindexArgs, io: KtxCliIo) => Promise<number>;
28
28
  setup?: (args: KtxSetupArgs, io: KtxCliIo) => Promise<number>;
29
29
  connection?: (args: KtxConnectionArgs, io: KtxCliIo) => Promise<number>;
30
30
  doctor?: (args: KtxDoctorArgs, io: KtxCliIo) => Promise<number>;
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { profileMark, profileSpan } from './startup-profile.js';
3
- import { resolveKtxRuntimeVersion } from './release-version.js';
3
+ import { assertCliVersion } from './release-version.js';
4
4
  profileMark('module:cli-runtime');
5
5
  const requirePackageJson = createRequire(import.meta.url);
6
6
  export function getKtxCliPackageInfo() {
@@ -15,15 +15,9 @@ export function packageInfoFromJson(packageJson) {
15
15
  typeof packageJson.version !== 'string') {
16
16
  throw new Error('Invalid KTX CLI package metadata');
17
17
  }
18
- const runtimeVersion = resolveKtxRuntimeVersion({
19
- packageName: packageJson.name,
20
- packageVersion: packageJson.version,
21
- });
22
18
  return {
23
19
  name: packageJson.name,
24
- version: runtimeVersion,
25
- packageVersion: packageJson.version,
26
- runtimeVersion,
20
+ version: assertCliVersion(packageJson.version, `${packageJson.name}/package.json`),
27
21
  contextPackageName: '@ktx/context',
28
22
  };
29
23
  }
@@ -1,5 +1,6 @@
1
1
  import { resolveCommandProjectDir } from '../cli-program.js';
2
2
  import { profileMark } from '../startup-profile.js';
3
+ import { resolveConnectionSelection } from './connection-selection.js';
3
4
  profileMark('module:commands/connection-commands');
4
5
  async function runConnectionArgs(context, args) {
5
6
  const runner = context.deps.connection ?? (await import('../connection.js')).runKtxConnection;
@@ -10,7 +11,10 @@ export function registerConnectionCommands(program, context, commandName = 'conn
10
11
  .command(commandName)
11
12
  .description('List and test configured connections')
12
13
  .showHelpAfterError()
13
- .addHelpText('after', '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the nearest ktx.yaml or current working directory.\n');
14
+ .addHelpText('after', '\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the nearest ktx.yaml or current working directory.\n')
15
+ .action(async (_options, command) => {
16
+ await runConnectionArgs(context, { command: 'list', projectDir: resolveCommandProjectDir(command) });
17
+ });
14
18
  connection.hook('preAction', (_thisCommand, actionCommand) => {
15
19
  context.writeDebug?.(commandName, actionCommand);
16
20
  });
@@ -22,25 +26,22 @@ export function registerConnectionCommands(program, context, commandName = 'conn
22
26
  });
23
27
  connection
24
28
  .command('test')
25
- .description('Test a configured connection')
26
- .argument('[connectionId]', 'KTX connection id (omit when --all is set)')
29
+ .description('Test one or all configured connections (default: all)')
30
+ .argument('[connectionId]', 'KTX connection id to test (omit to test all)')
27
31
  .option('--all', 'Test every configured connection and print a summary list')
28
32
  .action(async (connectionId, options, command) => {
29
- const all = options.all === true;
30
- if (all && connectionId !== undefined) {
33
+ if (options.all === true && connectionId !== undefined) {
31
34
  command.error('error: --all cannot be combined with a connection id argument');
32
35
  }
33
- if (!all && connectionId === undefined) {
34
- command.error('error: missing required argument <connectionId> (or pass --all)');
35
- }
36
- if (all) {
36
+ const selection = resolveConnectionSelection({ connectionId, all: options.all === true });
37
+ if (selection.kind === 'all') {
37
38
  await runConnectionArgs(context, { command: 'test-all', projectDir: resolveCommandProjectDir(command) });
38
39
  return;
39
40
  }
40
41
  await runConnectionArgs(context, {
41
42
  command: 'test',
42
43
  projectDir: resolveCommandProjectDir(command),
43
- connectionId: connectionId,
44
+ connectionId: selection.connectionId,
44
45
  });
45
46
  });
46
47
  }
@@ -0,0 +1,11 @@
1
+ export type ConnectionSelection = {
2
+ kind: 'all';
3
+ } | {
4
+ kind: 'single';
5
+ connectionId: string;
6
+ };
7
+ export interface ResolveConnectionSelectionInput {
8
+ connectionId?: string | undefined;
9
+ all: boolean;
10
+ }
11
+ export declare function resolveConnectionSelection(input: ResolveConnectionSelectionInput): ConnectionSelection;
@@ -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')
@@ -30,7 +30,7 @@ export function registerRuntimeCommands(program, context) {
30
30
  });
31
31
  runtime
32
32
  .command('start')
33
- .description('Start the KTX-managed Python HTTP daemon')
33
+ .description('Start the KTX daemon')
34
34
  .addOption(createRuntimeFeatureOption())
35
35
  .option('--force', 'Restart even when a matching daemon is already running', false)
36
36
  .action(async (options, command) => {
@@ -44,7 +44,7 @@ export function registerRuntimeCommands(program, context) {
44
44
  });
45
45
  runtime
46
46
  .command('stop')
47
- .description('Stop the KTX-managed Python HTTP daemon')
47
+ .description('Stop the KTX daemon')
48
48
  .option('--all', 'Stop all KTX daemon processes recorded or discoverable on this machine', false)
49
49
  .action(async (options, command) => {
50
50
  await runRuntimeArgs(context, {
@@ -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
  : {
@@ -532,7 +532,7 @@ function failedStepDetail(result) {
532
532
  return result.steps.find((step) => step.status === 'failed')?.detail ?? null;
533
533
  }
534
534
  const INTERNAL_FAILURE_LINE_RE = /^(Report|Run|Job|Status|Adapter|Connection|Sync|Mode|Dry run|Diff|Tasks|Work units|Failed tasks|Saved memory|Provenance rows):\s*/;
535
- const ACTIONABLE_FAILURE_LINE_RE = /^(Missing bundled Python runtime manifest|KTX Python runtime is required|KTX managed daemon|Error:|Failed\b|Could not\b|Cannot\b)/;
535
+ const ACTIONABLE_FAILURE_LINE_RE = /^(Missing bundled Python runtime manifest|KTX Python runtime is required|KTX daemon HTTP|Error:|Failed\b|Could not\b|Cannot\b)/;
536
536
  function trimErrorPrefix(line) {
537
537
  return line.replace(/^Error:\s*/, '');
538
538
  }
@@ -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;
@@ -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
  });