@kaelio/ktx 0.1.1 → 0.2.0

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