@kaelio/ktx 0.11.0 → 0.12.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 (181) hide show
  1. package/assets/python/{kaelio_ktx-0.11.0-py3-none-any.whl → kaelio_ktx-0.12.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/admin.js +1 -1
  5. package/dist/clack.d.ts +16 -0
  6. package/dist/clack.js +37 -6
  7. package/dist/claude-code-prompt-caching.js +1 -1
  8. package/dist/cli-program.js +3 -3
  9. package/dist/cli-runtime.js +2 -2
  10. package/dist/commands/connection-commands.js +1 -1
  11. package/dist/commands/ingest-commands.js +4 -4
  12. package/dist/commands/mcp-commands.js +12 -12
  13. package/dist/commands/runtime-commands.js +4 -4
  14. package/dist/commands/setup-commands.js +6 -5
  15. package/dist/commands/sl-commands.js +1 -1
  16. package/dist/commands/sql-commands.js +1 -1
  17. package/dist/commands/status-commands.js +1 -1
  18. package/dist/connection.js +1 -1
  19. package/dist/connectors/clickhouse/connector.js +1 -1
  20. package/dist/connectors/mysql/connector.js +1 -1
  21. package/dist/connectors/snowflake/connector.d.ts +1 -1
  22. package/dist/connectors/sqlite/connector.js +2 -25
  23. package/dist/connectors/sqlserver/connector.js +3 -3
  24. package/dist/context/connections/connection-type.d.ts +1 -1
  25. package/dist/context/connections/read-only-sql.d.ts +1 -0
  26. package/dist/context/connections/read-only-sql.js +116 -2
  27. package/dist/context/core/git.service.d.ts +23 -0
  28. package/dist/context/core/git.service.js +71 -8
  29. package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
  30. package/dist/context/ingest/adapters/looker/client.js +7 -2
  31. package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
  32. package/dist/context/ingest/adapters/looker/factory.js +9 -0
  33. package/dist/context/ingest/adapters/looker/mapping.js +1 -1
  34. package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
  35. package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
  36. package/dist/context/ingest/adapters/metabase/client.js +1 -1
  37. package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
  38. package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
  39. package/dist/context/ingest/artifact-gates.d.ts +2 -6
  40. package/dist/context/ingest/artifact-gates.js +5 -47
  41. package/dist/context/ingest/constrained-repair.d.ts +55 -0
  42. package/dist/context/ingest/constrained-repair.js +167 -0
  43. package/dist/context/ingest/final-gate-repair.d.ts +9 -11
  44. package/dist/context/ingest/final-gate-repair.js +40 -128
  45. package/dist/context/ingest/finalization-scope.d.ts +1 -1
  46. package/dist/context/ingest/finalization-scope.js +15 -15
  47. package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
  48. package/dist/context/ingest/ingest-bundle.runner.js +101 -67
  49. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
  50. package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
  51. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
  52. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
  53. package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
  54. package/dist/context/ingest/local-bundle-runtime.js +9 -10
  55. package/dist/context/ingest/local-ingest.d.ts +2 -0
  56. package/dist/context/ingest/local-ingest.js +2 -0
  57. package/dist/context/ingest/memory-flow/view-model.js +1 -1
  58. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
  59. package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
  60. package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
  61. package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
  62. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
  63. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
  64. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
  65. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
  66. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
  67. package/dist/context/llm/ai-sdk-runtime.js +2 -2
  68. package/dist/context/llm/claude-code-runtime.js +1 -1
  69. package/dist/context/llm/local-config.js +1 -1
  70. package/dist/context/llm/runtime-tools.js +2 -2
  71. package/dist/context/mcp/context-tools.js +7 -7
  72. package/dist/context/mcp/local-project-ports.js +23 -54
  73. package/dist/context/memory/local-memory.js +4 -1
  74. package/dist/context/memory/memory-agent.service.js +1 -1
  75. package/dist/context/project/config.d.ts +11 -4
  76. package/dist/context/project/config.js +85 -30
  77. package/dist/context/project/driver-schemas.js +1 -1
  78. package/dist/context/project/mappings-yaml-schema.js +2 -2
  79. package/dist/context/project/project.js +12 -4
  80. package/dist/context/scan/description-generation.js +4 -4
  81. package/dist/context/scan/local-enrichment-artifacts.js +2 -1
  82. package/dist/context/scan/local-scan.js +2 -2
  83. package/dist/context/scan/local-structural-artifacts.js +5 -5
  84. package/dist/context/scan/relationship-benchmark-report.js +1 -1
  85. package/dist/context/scan/relationship-discovery.js +3 -3
  86. package/dist/context/scan/relationship-llm-proposal.js +3 -3
  87. package/dist/context/sl/local-query.js +3 -33
  88. package/dist/context/sl/local-sl.d.ts +0 -8
  89. package/dist/context/sl/local-sl.js +44 -69
  90. package/dist/context/sl/semantic-layer.service.d.ts +25 -8
  91. package/dist/context/sl/semantic-layer.service.js +109 -56
  92. package/dist/context/sl/source-files.d.ts +46 -0
  93. package/dist/context/sl/source-files.js +131 -0
  94. package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
  95. package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
  96. package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
  97. package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
  98. package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
  99. package/dist/context/sql-analysis/dialect.d.ts +2 -0
  100. package/dist/context/sql-analysis/dialect.js +20 -0
  101. package/dist/context/tools/base-tool.d.ts +6 -19
  102. package/dist/context/tools/base-tool.js +0 -14
  103. package/dist/context-build-view.js +5 -5
  104. package/dist/database-tree-picker.js +18 -3
  105. package/dist/demo-assets.js +0 -1
  106. package/dist/doctor.d.ts +1 -1
  107. package/dist/doctor.js +31 -23
  108. package/dist/errors.d.ts +31 -0
  109. package/dist/errors.js +44 -0
  110. package/dist/ingest.d.ts +1 -1
  111. package/dist/ingest.js +8 -2
  112. package/dist/io/symbols.d.ts +2 -0
  113. package/dist/io/symbols.js +2 -0
  114. package/dist/io/tty.d.ts +8 -0
  115. package/dist/io/tty.js +16 -0
  116. package/dist/llm/embedding-health.js +1 -1
  117. package/dist/llm/embedding-provider.js +3 -3
  118. package/dist/llm/model-provider.js +1 -1
  119. package/dist/local-adapters.d.ts +1 -0
  120. package/dist/local-adapters.js +2 -2
  121. package/dist/local-scan-connectors.js +1 -1
  122. package/dist/managed-local-embeddings.js +17 -8
  123. package/dist/managed-mcp-daemon.js +3 -3
  124. package/dist/managed-python-command.d.ts +7 -0
  125. package/dist/managed-python-command.js +34 -8
  126. package/dist/managed-python-daemon.js +2 -2
  127. package/dist/managed-python-http.js +3 -3
  128. package/dist/managed-python-runtime.d.ts +30 -1
  129. package/dist/managed-python-runtime.js +134 -18
  130. package/dist/managed-uv-release.d.ts +7 -0
  131. package/dist/managed-uv-release.js +11 -0
  132. package/dist/mcp-http-server.js +4 -4
  133. package/dist/mcp-server-factory.js +3 -3
  134. package/dist/mcp-stdio-server.js +1 -1
  135. package/dist/memory-flow-hud.js +2 -2
  136. package/dist/next-steps.js +2 -2
  137. package/dist/prompt-navigation.d.ts +17 -0
  138. package/dist/prompt-navigation.js +49 -3
  139. package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
  140. package/dist/prompts/memory_agent_external_ingest.md +2 -2
  141. package/dist/public-ingest-copy.js +1 -1
  142. package/dist/public-ingest.js +3 -3
  143. package/dist/release-version.js +1 -1
  144. package/dist/runtime-requirements.js +1 -1
  145. package/dist/runtime.js +9 -9
  146. package/dist/scan.js +1 -1
  147. package/dist/setup-agents.js +21 -30
  148. package/dist/setup-banner.d.ts +20 -0
  149. package/dist/setup-banner.js +39 -0
  150. package/dist/setup-context.js +24 -15
  151. package/dist/setup-databases.js +31 -59
  152. package/dist/setup-demo-tour.js +12 -8
  153. package/dist/setup-embeddings.js +9 -9
  154. package/dist/setup-interrupt.js +1 -1
  155. package/dist/setup-models.d.ts +4 -1
  156. package/dist/setup-models.js +54 -28
  157. package/dist/setup-project.js +29 -5
  158. package/dist/setup-prompts.js +16 -1
  159. package/dist/setup-ready-menu.js +1 -1
  160. package/dist/setup-sources.js +27 -7
  161. package/dist/setup.js +13 -13
  162. package/dist/skills/analytics/SKILL.md +3 -3
  163. package/dist/skills/dbt_ingest/SKILL.md +3 -3
  164. package/dist/skills/looker_ingest/SKILL.md +3 -3
  165. package/dist/skills/lookml_ingest/SKILL.md +7 -7
  166. package/dist/skills/metabase_ingest/SKILL.md +4 -4
  167. package/dist/skills/metricflow_ingest/SKILL.md +15 -15
  168. package/dist/skills/notion_synthesize/SKILL.md +1 -1
  169. package/dist/skills/sl/SKILL.md +3 -3
  170. package/dist/skills/sl_capture/SKILL.md +1 -1
  171. package/dist/skills/wiki_capture/SKILL.md +1 -1
  172. package/dist/source-mapping.js +1 -1
  173. package/dist/startup-profile.js +1 -1
  174. package/dist/status-project.d.ts +0 -2
  175. package/dist/status-project.js +4 -6
  176. package/dist/telemetry/events.d.ts +1 -1
  177. package/dist/telemetry/exception.js +14 -0
  178. package/dist/text-ingest.js +1 -1
  179. package/dist/tree-picker-tui.d.ts +0 -1
  180. package/dist/tree-picker-tui.js +2 -3
  181. package/package.json +1 -1
package/dist/doctor.js CHANGED
@@ -4,6 +4,7 @@ import { access, readFile } from 'node:fs/promises';
4
4
  import { join, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { promisify } from 'node:util';
7
+ import { shouldUseColorOutput } from './io/tty.js';
7
8
  import { KTX_NEXT_STEP_DIRECT_COMMANDS } from './next-steps.js';
8
9
  const execFileAsync = promisify(execFile);
9
10
  function workspaceRootDir() {
@@ -121,12 +122,6 @@ const GROUP_LABEL = {
121
122
  search: 'Semantic search',
122
123
  history: 'Query history',
123
124
  };
124
- function shouldUseColor(io) {
125
- if (io.stdout.isTTY !== true)
126
- return false;
127
- const env = process.env;
128
- return !env.NO_COLOR && env.TERM !== 'dumb' && !env.CI;
129
- }
130
125
  function styleStatus(useColor, status, text) {
131
126
  if (!useColor)
132
127
  return text;
@@ -322,17 +317,20 @@ export function renderInvalidConfigMessage(projectDir, issues, outputMode, io) {
322
317
  }, null, 2)}\n`);
323
318
  return;
324
319
  }
325
- const useColor = shouldUseColor(io);
320
+ const useColor = shouldUseColorOutput(io.stdout);
326
321
  const dim = (text) => styleDim(useColor, text);
327
322
  const bold = (text) => styleBold(useColor, text);
328
323
  const status = (s, text) => styleStatus(useColor, s, text);
329
324
  const abbreviated = abbreviateHome(projectDir) ?? projectDir;
325
+ const errorCount = issues.filter((issue) => issue.severity === 'error').length;
326
+ const warningCount = issues.length - errorCount;
330
327
  const lines = [];
331
- lines.push(`${bold('KTX status')} ${dim('·')} ${abbreviated}`);
328
+ lines.push(`${bold('ktx status')} ${dim('·')} ${abbreviated}`);
332
329
  lines.push('');
333
- lines.push(` ${status('fail', '✗')} ${bold('Config')} ktx.yaml has ${issues.length} schema issue${issues.length === 1 ? '' : 's'}`);
330
+ lines.push(` ${status('fail', '✗')} ${bold('Config')} ktx.yaml has ${errorCount} schema issue${errorCount === 1 ? '' : 's'}${warningCount > 0 ? ` · ${warningCount} ignored field${warningCount === 1 ? '' : 's'}` : ''}`);
334
331
  for (const issue of issues) {
335
- lines.push(` ${status('fail', '✗')} ${issue.message}`);
332
+ const glyph = issue.severity === 'error' ? status('fail', '✗') : status('warn', '⚠');
333
+ lines.push(` ${glyph} ${issue.message}`);
336
334
  if (issue.fix) {
337
335
  lines.push(` ${dim(`→ ${issue.fix}`)}`);
338
336
  }
@@ -342,23 +340,35 @@ export function renderInvalidConfigMessage(projectDir, issues, outputMode, io) {
342
340
  lines.push('');
343
341
  io.stdout.write(lines.join('\n'));
344
342
  }
345
- export function renderValidConfigMessage(projectDir, outputMode, io) {
343
+ export function renderValidConfigMessage(projectDir, outputMode, io, warnings = []) {
346
344
  if (outputMode === 'json') {
347
345
  io.stdout.write(`${JSON.stringify({
348
346
  ok: true,
349
347
  projectDir,
348
+ ...(warnings.length > 0 ? { warnings } : {}),
350
349
  }, null, 2)}\n`);
351
350
  return;
352
351
  }
353
- const useColor = shouldUseColor(io);
352
+ const useColor = shouldUseColorOutput(io.stdout);
354
353
  const dim = (text) => styleDim(useColor, text);
355
354
  const bold = (text) => styleBold(useColor, text);
356
355
  const status = (s, text) => styleStatus(useColor, s, text);
357
356
  const abbreviated = abbreviateHome(projectDir) ?? projectDir;
358
357
  const lines = [];
359
- lines.push(`${bold('KTX status')} ${dim('·')} ${abbreviated}`);
358
+ lines.push(`${bold('ktx status')} ${dim('·')} ${abbreviated}`);
360
359
  lines.push('');
361
- lines.push(` ${status('pass', '✓')} ${bold('Config')} ${dim('ktx.yaml schema valid')}`);
360
+ if (warnings.length > 0) {
361
+ lines.push(` ${status('warn', '⚠')} ${bold('Config')} ktx.yaml schema valid · ${warnings.length} ignored field${warnings.length === 1 ? '' : 's'}`);
362
+ for (const warning of warnings) {
363
+ lines.push(` ${status('warn', '⚠')} ${warning.message}`);
364
+ if (warning.fix) {
365
+ lines.push(` ${dim(`→ ${warning.fix}`)}`);
366
+ }
367
+ }
368
+ }
369
+ else {
370
+ lines.push(` ${status('pass', '✓')} ${bold('Config')} ${dim('ktx.yaml schema valid')}`);
371
+ }
362
372
  lines.push('');
363
373
  io.stdout.write(lines.join('\n'));
364
374
  }
@@ -371,15 +381,15 @@ export function renderMissingProjectMessage(projectDir, outputMode, io) {
371
381
  }, null, 2)}\n`);
372
382
  return;
373
383
  }
374
- const useColor = shouldUseColor(io);
384
+ const useColor = shouldUseColorOutput(io.stdout);
375
385
  const dim = (text) => styleDim(useColor, text);
376
386
  const bold = (text) => styleBold(useColor, text);
377
387
  const abbreviated = abbreviateHome(projectDir) ?? projectDir;
378
388
  const envProjectDir = process.env.KTX_PROJECT_DIR;
379
389
  const lines = [];
380
- lines.push(`${bold('KTX status')} ${dim('·')} ${abbreviated}`);
390
+ lines.push(`${bold('ktx status')} ${dim('·')} ${abbreviated}`);
381
391
  lines.push('');
382
- lines.push(` No KTX project here yet. ${dim('(ktx.yaml not found)')}`);
392
+ lines.push(` No ktx project here yet. ${dim('(ktx.yaml not found)')}`);
383
393
  lines.push('');
384
394
  lines.push(` Run ${bold('ktx setup')} to create one.`);
385
395
  if (envProjectDir !== undefined) {
@@ -402,14 +412,13 @@ export async function runKtxDoctor(args, io = process, deps = {}) {
402
412
  return 1;
403
413
  }
404
414
  const { validateKtxProjectConfig } = await import('./context/project/config.js');
405
- ;
406
415
  const rawConfig = await readFile(configPath, 'utf-8');
407
416
  const validation = validateKtxProjectConfig(rawConfig);
408
417
  if (!validation.ok) {
409
418
  renderInvalidConfigMessage(args.projectDir, validation.issues, args.outputMode, io);
410
419
  return 1;
411
420
  }
412
- renderValidConfigMessage(args.projectDir, args.outputMode, io);
421
+ renderValidConfigMessage(args.projectDir, args.outputMode, io, validation.issues);
413
422
  return 0;
414
423
  }
415
424
  if (args.command === 'project') {
@@ -420,7 +429,6 @@ export async function runKtxDoctor(args, io = process, deps = {}) {
420
429
  }
421
430
  const { loadKtxProject } = await import('./context/project/project.js');
422
431
  const { validateKtxProjectConfig } = await import('./context/project/config.js');
423
- ;
424
432
  const { buildProjectStatus, renderProjectStatus } = await import('./status-project.js');
425
433
  const rawConfig = await readFile(configPath, 'utf-8');
426
434
  const validation = validateKtxProjectConfig(rawConfig);
@@ -445,7 +453,7 @@ export async function runKtxDoctor(args, io = process, deps = {}) {
445
453
  else {
446
454
  io.stdout.write(renderProjectStatus(projectStatus, {
447
455
  verbose,
448
- useColor: shouldUseColor(io),
456
+ useColor: shouldUseColorOutput(io.stdout),
449
457
  durationMs: Date.now() - startedAt,
450
458
  toolchainChecks,
451
459
  }));
@@ -453,10 +461,10 @@ export async function runKtxDoctor(args, io = process, deps = {}) {
453
461
  return projectStatus.verdict === 'blocked' ? 1 : 0;
454
462
  }
455
463
  const setupChecks = await runSetupChecks();
456
- const report = { title: 'KTX status', checks: setupChecks };
464
+ const report = { title: 'ktx status', checks: setupChecks };
457
465
  const renderOptions = {
458
466
  verbose: args.verbose ?? false,
459
- useColor: shouldUseColor(io),
467
+ useColor: shouldUseColorOutput(io.stdout),
460
468
  durationMs: Date.now() - startedAt,
461
469
  command: args.command,
462
470
  };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Marks an error as an expected operational outcome that ktx surfaces to its
3
+ * caller (a connected agent or the CLI user) rather than an unexpected ktx
4
+ * fault. Examples: invalid agent input, a warehouse rejecting a query, or a
5
+ * validation guard rejecting a request.
6
+ *
7
+ * `reportException` skips PostHog Error Tracking for these so the bug stream
8
+ * stays free of routine, caller-driven failures. The failure is still surfaced
9
+ * to the caller (as a tool-error result or CLI error) and still recorded by the
10
+ * outcome-tagged telemetry events, so no diagnostic signal is lost.
11
+ */
12
+ export declare class KtxExpectedError extends Error {
13
+ constructor(message: string, options?: ErrorOptions);
14
+ }
15
+ /**
16
+ * A query was rejected at the warehouse/driver boundary — the warehouse refused
17
+ * to compile or run it, or a read-only guard rejected it. Reuses the underlying
18
+ * error's message so the caller still sees the original warehouse diagnostics,
19
+ * and keeps the driver error as `cause`.
20
+ */
21
+ export declare class KtxQueryError extends KtxExpectedError {
22
+ constructor(message: string, options?: ErrorOptions);
23
+ }
24
+ /**
25
+ * True for the native JavaScript error types that signal a programming fault — a
26
+ * bug in ktx code rather than an operational outcome. These are universal
27
+ * language invariants (a `TypeError` never means "the warehouse rejected the
28
+ * query"), so callers can use this to keep genuine faults out of the
29
+ * expected-error classification and let them reach Error Tracking unchanged.
30
+ */
31
+ export declare function isNativeProgrammingFault(error: unknown): boolean;
package/dist/errors.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Marks an error as an expected operational outcome that ktx surfaces to its
3
+ * caller (a connected agent or the CLI user) rather than an unexpected ktx
4
+ * fault. Examples: invalid agent input, a warehouse rejecting a query, or a
5
+ * validation guard rejecting a request.
6
+ *
7
+ * `reportException` skips PostHog Error Tracking for these so the bug stream
8
+ * stays free of routine, caller-driven failures. The failure is still surfaced
9
+ * to the caller (as a tool-error result or CLI error) and still recorded by the
10
+ * outcome-tagged telemetry events, so no diagnostic signal is lost.
11
+ */
12
+ export class KtxExpectedError extends Error {
13
+ constructor(message, options) {
14
+ super(message, options);
15
+ this.name = 'KtxExpectedError';
16
+ }
17
+ }
18
+ /**
19
+ * A query was rejected at the warehouse/driver boundary — the warehouse refused
20
+ * to compile or run it, or a read-only guard rejected it. Reuses the underlying
21
+ * error's message so the caller still sees the original warehouse diagnostics,
22
+ * and keeps the driver error as `cause`.
23
+ */
24
+ export class KtxQueryError extends KtxExpectedError {
25
+ constructor(message, options) {
26
+ super(message, options);
27
+ this.name = 'KtxQueryError';
28
+ }
29
+ }
30
+ /**
31
+ * True for the native JavaScript error types that signal a programming fault — a
32
+ * bug in ktx code rather than an operational outcome. These are universal
33
+ * language invariants (a `TypeError` never means "the warehouse rejected the
34
+ * query"), so callers can use this to keep genuine faults out of the
35
+ * expected-error classification and let them reach Error Tracking unchanged.
36
+ */
37
+ export function isNativeProgrammingFault(error) {
38
+ return (error instanceof TypeError ||
39
+ error instanceof RangeError ||
40
+ error instanceof ReferenceError ||
41
+ error instanceof SyntaxError ||
42
+ error instanceof EvalError ||
43
+ error instanceof URIError);
44
+ }
package/dist/ingest.d.ts CHANGED
@@ -60,7 +60,7 @@ export interface KtxIngestDeps {
60
60
  startLiveMemoryFlow?: typeof startLiveMemoryFlowTui;
61
61
  abortSignal?: AbortSignal;
62
62
  env?: NodeJS.ProcessEnv;
63
- localIngestOptions?: Pick<RunLocalIngestOptions, 'agentRunner' | 'llmRuntime' | 'memoryModel' | 'semanticLayerCompute' | 'queryExecutor' | 'logger' | 'pullConfigOptions'>;
63
+ localIngestOptions?: Pick<RunLocalIngestOptions, 'agentRunner' | 'llmRuntime' | 'memoryModel' | 'semanticLayerCompute' | 'queryExecutor' | 'sqlAnalysis' | 'logger' | 'pullConfigOptions'>;
64
64
  progress?: (update: KtxIngestProgressUpdate) => void;
65
65
  runtimeIo?: KtxIngestIo;
66
66
  }
package/dist/ingest.js CHANGED
@@ -11,7 +11,7 @@ import { resolveProjectEmbeddingProvider } from './embedding-resolution.js';
11
11
  import { createKtxCliIngestQueryExecutor } from './ingest-query-executor.js';
12
12
  import { readIngestReportSnapshotFile } from './ingest-report-file.js';
13
13
  import { createCliOperationalLogger } from './io/logger.js';
14
- import { createKtxCliLocalIngestAdapters } from './local-adapters.js';
14
+ import { createKtxCliLocalIngestAdapters, resolveKtxCliSqlAnalysis } from './local-adapters.js';
15
15
  import { renderMemoryFlowInteractively } from './memory-flow-interactive.js';
16
16
  import { renderMemoryFlowTui, startLiveMemoryFlowTui, } from './memory-flow-tui.js';
17
17
  import { resolveVizFallback, warnVizFallbackOnce } from './viz-fallback.js';
@@ -525,7 +525,7 @@ export async function runKtxIngest(args, io = process, deps = {}) {
525
525
  const localIngestOptions = deps.localIngestOptions ?? {};
526
526
  const managedDaemon = managedDaemonOptionsForIngestRun(args, deps.runtimeIo ?? io);
527
527
  const operationalLogger = createCliOperationalLogger(io, args.outputMode);
528
- const adapterOptions = {
528
+ const baseAdapterOptions = {
529
529
  ...(localIngestOptions.pullConfigOptions ?? {}),
530
530
  ...(args.databaseIntrospectionUrl ? { databaseIntrospectionUrl: args.databaseIntrospectionUrl } : {}),
531
531
  ...(managedDaemon ? { managedDaemon } : {}),
@@ -535,6 +535,10 @@ export async function runKtxIngest(args, io = process, deps = {}) {
535
535
  : {}),
536
536
  logger: operationalLogger,
537
537
  };
538
+ // One parser-backed SQL analysis port per run: the historic-sql adapter and
539
+ // the ingest sql_execution tool share the same daemon-backed validator.
540
+ const sqlAnalysis = localIngestOptions.sqlAnalysis ?? resolveKtxCliSqlAnalysis(baseAdapterOptions);
541
+ const adapterOptions = { ...baseAdapterOptions, sqlAnalysis };
538
542
  const queryExecutor = localIngestOptions.queryExecutor ??
539
543
  (deps.createQueryExecutor ?? createKtxCliIngestQueryExecutor)(ingestProject);
540
544
  if (args.adapter === 'metabase' && args.sourceDir) {
@@ -577,6 +581,7 @@ export async function runKtxIngest(args, io = process, deps = {}) {
577
581
  metabaseConnectionId: args.connectionId,
578
582
  ...localIngestOptions,
579
583
  queryExecutor,
584
+ sqlAnalysis,
580
585
  trigger: 'manual_resync',
581
586
  jobIdFactory: deps.jobIdFactory,
582
587
  embeddingProvider,
@@ -650,6 +655,7 @@ export async function runKtxIngest(args, io = process, deps = {}) {
650
655
  jobId,
651
656
  ...localIngestOptions,
652
657
  queryExecutor,
658
+ sqlAnalysis,
653
659
  pullConfigOptions: adapterOptions,
654
660
  embeddingProvider,
655
661
  ...(args.debugLlmRequestFile ? { llmDebugRequestFile: args.debugLlmRequestFile } : {}),
@@ -1,3 +1,5 @@
1
+ /** Whether the active terminal renders Unicode glyphs (block/box drawing, arrows). */
2
+ export declare const unicodeSupported: boolean;
1
3
  export declare const SYMBOLS: {
2
4
  readonly middot: "-" | "·";
3
5
  readonly emDash: "--" | "—";
@@ -9,6 +9,8 @@ function detectUnicodeSupport(env = process.env) {
9
9
  env.TERM === 'alacritty');
10
10
  }
11
11
  const unicode = detectUnicodeSupport();
12
+ /** Whether the active terminal renders Unicode glyphs (block/box drawing, arrows). */
13
+ export const unicodeSupported = unicode;
12
14
  export const SYMBOLS = {
13
15
  middot: unicode ? '·' : '-',
14
16
  emDash: unicode ? '—' : '--',
package/dist/io/tty.d.ts CHANGED
@@ -6,4 +6,12 @@ type KtxCliOutput = (KtxCliIo['stdout'] | KtxCliIo['stderr']) & {
6
6
  on?: unknown;
7
7
  };
8
8
  export declare function isWritableTtyOutput(output: KtxCliOutput): output is KtxCliOutput & Writable;
9
+ export declare function shouldUseColorOutput(output: {
10
+ isTTY?: boolean;
11
+ }): boolean;
12
+ /**
13
+ * Color depth in bits for the given output: 1 when color is disabled, the
14
+ * stream-reported depth when available, and a 16-color baseline otherwise.
15
+ */
16
+ export declare function colorDepthForOutput(output: KtxCliOutput): number;
9
17
  export {};
package/dist/io/tty.js CHANGED
@@ -3,3 +3,19 @@ export function isWritableTtyOutput(output) {
3
3
  typeof output.on === 'function' &&
4
4
  typeof output.columns !== 'undefined');
5
5
  }
6
+ export function shouldUseColorOutput(output) {
7
+ if (output.isTTY !== true)
8
+ return false;
9
+ const env = process.env;
10
+ return !env.NO_COLOR && env.TERM !== 'dumb' && !env.CI;
11
+ }
12
+ /**
13
+ * Color depth in bits for the given output: 1 when color is disabled, the
14
+ * stream-reported depth when available, and a 16-color baseline otherwise.
15
+ */
16
+ export function colorDepthForOutput(output) {
17
+ if (!shouldUseColorOutput(output))
18
+ return 1;
19
+ const getColorDepth = output.getColorDepth;
20
+ return typeof getColorDepth === 'function' ? getColorDepth.call(output) : 4;
21
+ }
@@ -21,7 +21,7 @@ async function withTimeout(promise, timeoutMs) {
21
21
  export async function runKtxEmbeddingHealthCheck(config, options = {}) {
22
22
  try {
23
23
  const provider = createKtxEmbeddingProvider(config, options.deps);
24
- const embedding = await withTimeout(provider.embed(options.text ?? 'KTX embedding health check'), options.timeoutMs ?? 15_000);
24
+ const embedding = await withTimeout(provider.embed(options.text ?? 'ktx embedding health check'), options.timeoutMs ?? 15_000);
25
25
  if (embedding.length !== config.dimensions) {
26
26
  return {
27
27
  ok: false,
@@ -49,7 +49,7 @@ class OpenAIEmbeddingProvider {
49
49
  this.dimensions = config.dimensions;
50
50
  this.maxBatchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
51
51
  if (!config.openai?.apiKey) {
52
- throw new Error('openai.apiKey is required when KTX embedding backend is openai');
52
+ throw new Error('openai.apiKey is required when ktx embedding backend is openai');
53
53
  }
54
54
  this.client = deps.createOpenAIClient
55
55
  ? deps.createOpenAIClient({ apiKey: config.openai.apiKey, baseURL: config.openai.baseURL })
@@ -90,7 +90,7 @@ class SentenceTransformersEmbeddingProvider {
90
90
  startupProbe;
91
91
  constructor(config, deps) {
92
92
  if (!config.sentenceTransformers?.baseURL) {
93
- throw new Error('sentenceTransformers.baseURL is required when KTX embedding backend is sentence-transformers');
93
+ throw new Error('sentenceTransformers.baseURL is required when ktx embedding backend is sentence-transformers');
94
94
  }
95
95
  this.dimensions = config.dimensions;
96
96
  this.maxBatchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
@@ -156,6 +156,6 @@ export function createKtxEmbeddingProvider(config, deps = {}) {
156
156
  case 'sentence-transformers':
157
157
  return new SentenceTransformersEmbeddingProvider(config, deps);
158
158
  default:
159
- throw new Error(`Unsupported KTX embedding backend: ${String(config.backend)}`);
159
+ throw new Error(`Unsupported ktx embedding backend: ${String(config.backend)}`);
160
160
  }
161
161
  }
@@ -121,7 +121,7 @@ class DefaultKtxLlmProvider {
121
121
  }
122
122
  if (config.backend === 'vertex') {
123
123
  if (!config.vertex?.location) {
124
- throw new Error('vertex.location is required when KTX LLM backend is vertex');
124
+ throw new Error('vertex.location is required when ktx LLM backend is vertex');
125
125
  }
126
126
  const vertex = (deps.createVertexAnthropic ?? createVertexAnthropic)({
127
127
  ...(config.vertex.project ? { project: config.vertex.project } : {}),
@@ -5,6 +5,7 @@ import type { KtxLocalProject } from './context/project/project.js';
5
5
  import type { SqlAnalysisPort } from './context/sql-analysis/ports.js';
6
6
  import { type ManagedPythonDaemonHttpOptions } from './managed-python-http.js';
7
7
  import type { KtxOperationalLogger } from './io/logger.js';
8
+ export declare function resolveKtxCliSqlAnalysis(options: KtxCliLocalIngestAdaptersOptions): SqlAnalysisPort;
8
9
  export interface KtxCliLocalIngestAdaptersOptions extends DefaultLocalIngestAdaptersOptions {
9
10
  historicSqlConnectionId?: string;
10
11
  sqlAnalysis?: SqlAnalysisPort;
@@ -45,7 +45,7 @@ function ktxCliLookerOptions(options) {
45
45
  parser: createManagedDaemonLookerTableIdentifierParser(options.managedDaemon),
46
46
  };
47
47
  }
48
- function ktxCliHistoricSqlAnalysis(options) {
48
+ export function resolveKtxCliSqlAnalysis(options) {
49
49
  if (options.sqlAnalysis) {
50
50
  return options.sqlAnalysis;
51
51
  }
@@ -234,7 +234,7 @@ function historicSqlOptionsForLocalRun(project, options) {
234
234
  return undefined;
235
235
  }
236
236
  const base = {
237
- sqlAnalysis: ktxCliHistoricSqlAnalysis(options),
237
+ sqlAnalysis: resolveKtxCliSqlAnalysis(options),
238
238
  };
239
239
  if (dialect === 'postgres') {
240
240
  return {
@@ -11,7 +11,7 @@ export async function createKtxCliScanConnector(project, connectionId) {
11
11
  }
12
12
  const registration = getDriverRegistration(driver);
13
13
  if (!registration) {
14
- throw new Error(`Connection "${connectionId}" uses driver "${driver}", which has no native standalone KTX scan connector. Supported drivers: ${SUPPORTED_DRIVERS}.`);
14
+ throw new Error(`Connection "${connectionId}" uses driver "${driver}", which has no native standalone ktx scan connector. Supported drivers: ${SUPPORTED_DRIVERS}.`);
15
15
  }
16
16
  const connectorModule = await registration.load();
17
17
  if (!connectorModule.isConnectionConfig(connection)) {
@@ -1,4 +1,4 @@
1
- import { writePrefixedLines } from './clack.js';
1
+ import { createCliSpinner } from './clack.js';
2
2
  import { ensureManagedPythonCommandRuntime, } from './managed-python-command.js';
3
3
  import { readManagedPythonDaemonStatus, startManagedPythonDaemon, } from './managed-python-daemon.js';
4
4
  export function managedLocalEmbeddingHealthConfig(input) {
@@ -21,14 +21,23 @@ export async function ensureManagedLocalEmbeddingsDaemon(options) {
21
21
  io: options.io,
22
22
  feature: 'local-embeddings',
23
23
  });
24
- const daemon = await startDaemon({
25
- cliVersion: options.cliVersion,
26
- projectDir: options.projectDir,
27
- features: ['local-embeddings'],
28
- force: false,
29
- });
24
+ const spinner = createCliSpinner(options.io);
25
+ spinner.start('Starting ktx embedding daemon (first run downloads the model)…');
26
+ let daemon;
27
+ try {
28
+ daemon = await startDaemon({
29
+ cliVersion: options.cliVersion,
30
+ projectDir: options.projectDir,
31
+ features: ['local-embeddings'],
32
+ force: false,
33
+ });
34
+ }
35
+ catch (error) {
36
+ spinner.error('ktx embedding daemon failed to start');
37
+ throw error;
38
+ }
30
39
  const verb = daemon.status === 'started' ? 'Started' : 'Using';
31
- writePrefixedLines((chunk) => options.io.stderr.write(chunk), `${verb} KTX daemon: ${daemon.baseUrl}`);
40
+ spinner.stop(`${verb} ktx daemon: ${daemon.baseUrl}`);
32
41
  return {
33
42
  baseUrl: daemon.baseUrl,
34
43
  stdoutLog: daemon.state.stdoutLog,
@@ -92,7 +92,7 @@ export async function startKtxMcpDaemon(options) {
92
92
  url: `http://${existing.host}:${existing.port}/mcp`,
93
93
  };
94
94
  }
95
- throw new Error(`KTX MCP daemon is already running at http://${existing.host}:${existing.port}/mcp ` +
95
+ throw new Error(`ktx MCP daemon is already running at http://${existing.host}:${existing.port}/mcp ` +
96
96
  'with a different configuration. Run `ktx mcp stop` first, then start again.');
97
97
  }
98
98
  const portAvailable = options.portAvailable ?? defaultPortAvailable;
@@ -126,7 +126,7 @@ export async function startKtxMcpDaemon(options) {
126
126
  }),
127
127
  });
128
128
  if (!child.pid) {
129
- throw new Error('Failed to start KTX MCP daemon: child process pid was not available.');
129
+ throw new Error('Failed to start ktx MCP daemon: child process pid was not available.');
130
130
  }
131
131
  child.unref();
132
132
  const state = {
@@ -167,7 +167,7 @@ export async function readKtxMcpDaemonStatus(options) {
167
167
  }
168
168
  return {
169
169
  kind: 'running',
170
- detail: `KTX MCP daemon running at http://${state.host}:${state.port}/mcp`,
170
+ detail: `ktx MCP daemon running at http://${state.host}:${state.port}/mcp`,
171
171
  state,
172
172
  url: `http://${state.host}:${state.port}/mcp`,
173
173
  };
@@ -31,4 +31,11 @@ export interface ManagedPythonSemanticLayerComputeOptions extends ManagedPythonC
31
31
  export declare function managedRuntimeInstallCommand(feature: KtxRuntimeFeature): string;
32
32
  export declare function ensureManagedPythonCommandRuntime(options: ManagedPythonCommandOptions): Promise<ManagedPythonCommandRuntime>;
33
33
  export declare function createManagedPythonSemanticLayerComputePort(options: ManagedPythonSemanticLayerComputeOptions): Promise<KtxSemanticLayerComputePort>;
34
+ /**
35
+ * Defers the managed-runtime install to the first semantic-layer call so a
36
+ * long-lived server (the MCP server) can start and serve context tools that
37
+ * need no Python even when uv is absent. Caches on success only, so a runtime
38
+ * installed mid-session is picked up on the next call.
39
+ */
40
+ export declare function createLazyManagedPythonSemanticLayerComputePort(options: ManagedPythonSemanticLayerComputeOptions): KtxSemanticLayerComputePort;
34
41
  export {};
@@ -1,5 +1,5 @@
1
1
  import { createPythonSemanticLayerComputePort } from './context/daemon/semantic-layer-compute.js';
2
- import { createClackPromptAdapter, createStaticCliSpinner } from './clack.js';
2
+ import { createClackPromptAdapter, createCliSpinner } from './clack.js';
3
3
  import { installManagedPythonRuntime, readManagedPythonRuntimeStatus, } from './managed-python-runtime.js';
4
4
  import { readExistingTelemetryProjectId } from './telemetry/identity.js';
5
5
  export function runtimeInstallPolicyFromFlags(options) {
@@ -19,10 +19,10 @@ export function managedRuntimeInstallCommand(feature) {
19
19
  }
20
20
  function installPrompt(feature) {
21
21
  const label = feature === 'local-embeddings' ? 'local embeddings Python runtime' : 'core Python runtime';
22
- return `KTX needs to install the ${label}. This downloads Python dependencies with uv. Continue?`;
22
+ return `ktx needs to install the ${label}. This downloads a pinned, checksum-verified uv build, Python, and dependencies. Continue?`;
23
23
  }
24
24
  function runtimeRequiredMessage(feature) {
25
- return `KTX Python runtime is required for this command. Run: ${managedRuntimeInstallCommand(feature)}`;
25
+ return `ktx Python runtime is required for this command. Run: ${managedRuntimeInstallCommand(feature)}`;
26
26
  }
27
27
  function hasFeature(manifest, feature) {
28
28
  return manifest.features.includes(feature);
@@ -49,22 +49,22 @@ export async function ensureManagedPythonCommandRuntime(options) {
49
49
  const confirmInstall = options.confirmInstall ?? defaultConfirmInstall;
50
50
  const confirmed = await confirmInstall(installPrompt(feature), options.io);
51
51
  if (!confirmed) {
52
- throw new Error(`KTX Python runtime installation was cancelled. Run: ${managedRuntimeInstallCommand(feature)}`);
52
+ throw new Error(`ktx Python runtime installation was cancelled. Run: ${managedRuntimeInstallCommand(feature)}`);
53
53
  }
54
54
  }
55
- const progress = (options.spinner ?? (() => createStaticCliSpinner(options.io)))();
56
- progress.start(`Installing KTX Python runtime (${feature}) with uv...`);
55
+ const progress = (options.spinner ?? (() => createCliSpinner(options.io)))();
56
+ progress.start(`Installing ktx Python runtime (${feature}) with uv...`);
57
57
  try {
58
58
  const installed = await installRuntime({
59
59
  cliVersion: options.cliVersion,
60
60
  features: [feature],
61
61
  force: false,
62
62
  });
63
- progress.stop(`KTX Python runtime ready: ${installed.layout.versionDir}`);
63
+ progress.stop(`ktx Python runtime ready: ${installed.layout.versionDir}`);
64
64
  return { layout: installed.layout, manifest: installed.manifest };
65
65
  }
66
66
  catch (error) {
67
- progress.error(`KTX Python runtime install failed: ${error instanceof Error ? error.message : String(error)}`);
67
+ progress.error(`ktx Python runtime install failed: ${error instanceof Error ? error.message : String(error)}`);
68
68
  throw error;
69
69
  }
70
70
  }
@@ -89,3 +89,29 @@ export async function createManagedPythonSemanticLayerComputePort(options) {
89
89
  ...(projectId ? { projectId } : {}),
90
90
  });
91
91
  }
92
+ /**
93
+ * Defers the managed-runtime install to the first semantic-layer call so a
94
+ * long-lived server (the MCP server) can start and serve context tools that
95
+ * need no Python even when uv is absent. Caches on success only, so a runtime
96
+ * installed mid-session is picked up on the next call.
97
+ */
98
+ export function createLazyManagedPythonSemanticLayerComputePort(options) {
99
+ let cached;
100
+ const resolve = async () => {
101
+ if (!cached) {
102
+ cached = await createManagedPythonSemanticLayerComputePort(options);
103
+ }
104
+ return cached;
105
+ };
106
+ return {
107
+ async query(input) {
108
+ return (await resolve()).query(input);
109
+ },
110
+ async validateSources(input) {
111
+ return (await resolve()).validateSources(input);
112
+ },
113
+ async generateSources(input) {
114
+ return (await resolve()).generateSources(input);
115
+ },
116
+ };
117
+ }
@@ -11,7 +11,7 @@ export class ManagedPythonDaemonStartError extends Error {
11
11
  detail;
12
12
  stderrLog;
13
13
  constructor(detail, stderrLog) {
14
- super(`KTX daemon failed to start: ${detail}. stderr: ${stderrLog}`);
14
+ super(`ktx daemon failed to start: ${detail}. stderr: ${stderrLog}`);
15
15
  this.name = 'ManagedPythonDaemonStartError';
16
16
  this.detail = detail;
17
17
  this.stderrLog = stderrLog;
@@ -502,7 +502,7 @@ export async function startManagedPythonDaemon(options) {
502
502
  });
503
503
  child.unref();
504
504
  if (!child.pid) {
505
- throw new Error(`KTX daemon did not report a pid. stderr: ${layout.daemonStderrPath}`);
505
+ throw new Error(`ktx daemon did not report a pid. stderr: ${layout.daemonStderrPath}`);
506
506
  }
507
507
  const state = {
508
508
  schemaVersion: 1,
@@ -12,7 +12,7 @@ function normalizedBaseUrl(baseUrl) {
12
12
  function parseJsonObject(raw, path) {
13
13
  const parsed = JSON.parse(raw);
14
14
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
15
- throw new Error(`KTX daemon HTTP ${path} returned non-object JSON`);
15
+ throw new Error(`ktx daemon HTTP ${path} returned non-object JSON`);
16
16
  }
17
17
  return parsed;
18
18
  }
@@ -35,7 +35,7 @@ async function postManagedDaemonJson(baseUrl, path, payload) {
35
35
  const text = Buffer.concat(chunks).toString('utf8');
36
36
  const statusCode = response.statusCode ?? 0;
37
37
  if (statusCode < 200 || statusCode >= 300) {
38
- reject(new Error(`KTX daemon HTTP ${path} failed with ${statusCode}: ${text}`));
38
+ reject(new Error(`ktx daemon HTTP ${path} failed with ${statusCode}: ${text}`));
39
39
  return;
40
40
  }
41
41
  try {
@@ -72,7 +72,7 @@ export function createManagedPythonDaemonBaseUrlResolver(options) {
72
72
  force: false,
73
73
  });
74
74
  const verb = daemon.status === 'started' ? 'Started' : 'Using existing';
75
- writePrefixedLines((chunk) => options.io.stderr.write(chunk), `${verb} KTX daemon: ${daemon.baseUrl}`);
75
+ writePrefixedLines((chunk) => options.io.stderr.write(chunk), `${verb} ktx daemon: ${daemon.baseUrl}`);
76
76
  cachedBaseUrl = daemon.baseUrl;
77
77
  return cachedBaseUrl;
78
78
  };