@kaelio/ktx 0.10.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 (193) hide show
  1. package/assets/python/{kaelio_ktx-0.10.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 +7 -3
  9. package/dist/cli-runtime.d.ts +2 -0
  10. package/dist/cli-runtime.js +14 -8
  11. package/dist/commands/connection-commands.js +1 -1
  12. package/dist/commands/ingest-commands.js +4 -4
  13. package/dist/commands/mcp-commands.js +12 -12
  14. package/dist/commands/runtime-commands.js +4 -4
  15. package/dist/commands/setup-commands.js +6 -5
  16. package/dist/commands/sl-commands.js +1 -1
  17. package/dist/commands/sql-commands.js +1 -1
  18. package/dist/commands/status-commands.js +1 -1
  19. package/dist/community-cta.d.ts +11 -0
  20. package/dist/community-cta.js +19 -0
  21. package/dist/connection.js +1 -1
  22. package/dist/connectors/clickhouse/connector.js +1 -1
  23. package/dist/connectors/mysql/connector.js +1 -1
  24. package/dist/connectors/snowflake/connector.d.ts +1 -1
  25. package/dist/connectors/sqlite/connector.js +2 -25
  26. package/dist/connectors/sqlserver/connector.js +3 -3
  27. package/dist/context/connections/connection-type.d.ts +1 -1
  28. package/dist/context/connections/read-only-sql.d.ts +1 -0
  29. package/dist/context/connections/read-only-sql.js +116 -2
  30. package/dist/context/core/git-env.d.ts +12 -1
  31. package/dist/context/core/git-env.js +17 -2
  32. package/dist/context/core/git.service.d.ts +23 -0
  33. package/dist/context/core/git.service.js +86 -15
  34. package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
  35. package/dist/context/ingest/adapters/looker/client.js +7 -2
  36. package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
  37. package/dist/context/ingest/adapters/looker/factory.js +9 -0
  38. package/dist/context/ingest/adapters/looker/mapping.js +1 -1
  39. package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
  40. package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
  41. package/dist/context/ingest/adapters/metabase/client.js +1 -1
  42. package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
  43. package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
  44. package/dist/context/ingest/artifact-gates.d.ts +2 -6
  45. package/dist/context/ingest/artifact-gates.js +5 -47
  46. package/dist/context/ingest/constrained-repair.d.ts +55 -0
  47. package/dist/context/ingest/constrained-repair.js +167 -0
  48. package/dist/context/ingest/final-gate-repair.d.ts +9 -11
  49. package/dist/context/ingest/final-gate-repair.js +40 -128
  50. package/dist/context/ingest/finalization-scope.d.ts +1 -1
  51. package/dist/context/ingest/finalization-scope.js +15 -15
  52. package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
  53. package/dist/context/ingest/ingest-bundle.runner.js +101 -67
  54. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
  55. package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
  56. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
  57. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
  58. package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
  59. package/dist/context/ingest/local-bundle-runtime.js +9 -10
  60. package/dist/context/ingest/local-ingest.d.ts +2 -0
  61. package/dist/context/ingest/local-ingest.js +2 -0
  62. package/dist/context/ingest/memory-flow/view-model.js +1 -1
  63. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
  64. package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
  65. package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
  66. package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
  67. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
  68. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
  69. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
  70. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
  71. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
  72. package/dist/context/llm/ai-sdk-runtime.js +2 -2
  73. package/dist/context/llm/claude-code-runtime.js +1 -1
  74. package/dist/context/llm/local-config.js +1 -1
  75. package/dist/context/llm/runtime-tools.js +2 -2
  76. package/dist/context/mcp/context-tools.js +7 -7
  77. package/dist/context/mcp/local-project-ports.js +23 -54
  78. package/dist/context/memory/local-memory.js +4 -1
  79. package/dist/context/memory/memory-agent.service.js +1 -1
  80. package/dist/context/project/config.d.ts +11 -4
  81. package/dist/context/project/config.js +85 -30
  82. package/dist/context/project/driver-schemas.js +1 -1
  83. package/dist/context/project/mappings-yaml-schema.js +2 -2
  84. package/dist/context/project/project.js +12 -4
  85. package/dist/context/scan/description-generation.js +4 -4
  86. package/dist/context/scan/local-enrichment-artifacts.js +2 -1
  87. package/dist/context/scan/local-scan.js +2 -2
  88. package/dist/context/scan/local-structural-artifacts.js +5 -5
  89. package/dist/context/scan/relationship-benchmark-report.js +1 -1
  90. package/dist/context/scan/relationship-discovery.js +3 -3
  91. package/dist/context/scan/relationship-llm-proposal.js +3 -3
  92. package/dist/context/sl/local-query.js +3 -33
  93. package/dist/context/sl/local-sl.d.ts +0 -8
  94. package/dist/context/sl/local-sl.js +44 -69
  95. package/dist/context/sl/semantic-layer.service.d.ts +25 -8
  96. package/dist/context/sl/semantic-layer.service.js +109 -56
  97. package/dist/context/sl/source-files.d.ts +46 -0
  98. package/dist/context/sl/source-files.js +131 -0
  99. package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
  100. package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
  101. package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
  102. package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
  103. package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
  104. package/dist/context/sql-analysis/dialect.d.ts +2 -0
  105. package/dist/context/sql-analysis/dialect.js +20 -0
  106. package/dist/context/tools/base-tool.d.ts +6 -19
  107. package/dist/context/tools/base-tool.js +0 -14
  108. package/dist/context-build-view.js +5 -5
  109. package/dist/database-tree-picker.js +18 -3
  110. package/dist/demo-assets.js +0 -1
  111. package/dist/doctor.d.ts +1 -1
  112. package/dist/doctor.js +31 -23
  113. package/dist/errors.d.ts +31 -0
  114. package/dist/errors.js +44 -0
  115. package/dist/ingest.d.ts +1 -1
  116. package/dist/ingest.js +8 -2
  117. package/dist/io/symbols.d.ts +2 -0
  118. package/dist/io/symbols.js +2 -0
  119. package/dist/io/tty.d.ts +17 -0
  120. package/dist/io/tty.js +21 -0
  121. package/dist/links.d.ts +1 -0
  122. package/dist/links.js +1 -0
  123. package/dist/llm/embedding-health.js +1 -1
  124. package/dist/llm/embedding-provider.js +3 -3
  125. package/dist/llm/model-provider.js +1 -1
  126. package/dist/local-adapters.d.ts +1 -0
  127. package/dist/local-adapters.js +2 -2
  128. package/dist/local-scan-connectors.js +1 -1
  129. package/dist/managed-local-embeddings.js +17 -8
  130. package/dist/managed-mcp-daemon.js +3 -3
  131. package/dist/managed-python-command.d.ts +7 -0
  132. package/dist/managed-python-command.js +34 -8
  133. package/dist/managed-python-daemon.js +2 -2
  134. package/dist/managed-python-http.js +3 -3
  135. package/dist/managed-python-runtime.d.ts +30 -1
  136. package/dist/managed-python-runtime.js +134 -18
  137. package/dist/managed-uv-release.d.ts +7 -0
  138. package/dist/managed-uv-release.js +11 -0
  139. package/dist/mcp-http-server.js +4 -4
  140. package/dist/mcp-server-factory.js +3 -3
  141. package/dist/mcp-stdio-server.js +1 -1
  142. package/dist/memory-flow-hud.js +2 -2
  143. package/dist/next-steps.js +2 -2
  144. package/dist/prompt-navigation.d.ts +17 -0
  145. package/dist/prompt-navigation.js +49 -3
  146. package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
  147. package/dist/prompts/memory_agent_external_ingest.md +2 -2
  148. package/dist/public-ingest-copy.js +1 -1
  149. package/dist/public-ingest.js +3 -3
  150. package/dist/release-version.js +1 -1
  151. package/dist/runtime-requirements.js +1 -1
  152. package/dist/runtime.js +9 -9
  153. package/dist/scan.js +1 -1
  154. package/dist/setup-agents.js +22 -35
  155. package/dist/setup-banner.d.ts +20 -0
  156. package/dist/setup-banner.js +39 -0
  157. package/dist/setup-context.js +24 -15
  158. package/dist/setup-databases.js +31 -59
  159. package/dist/setup-demo-tour.js +12 -8
  160. package/dist/setup-embeddings.js +9 -9
  161. package/dist/setup-interrupt.js +1 -1
  162. package/dist/setup-models.d.ts +4 -1
  163. package/dist/setup-models.js +54 -28
  164. package/dist/setup-project.js +29 -5
  165. package/dist/setup-prompts.js +16 -5
  166. package/dist/setup-ready-menu.js +1 -1
  167. package/dist/setup-sources.js +27 -7
  168. package/dist/setup.d.ts +25 -0
  169. package/dist/setup.js +90 -19
  170. package/dist/skills/analytics/SKILL.md +3 -3
  171. package/dist/skills/dbt_ingest/SKILL.md +3 -3
  172. package/dist/skills/looker_ingest/SKILL.md +3 -3
  173. package/dist/skills/lookml_ingest/SKILL.md +7 -7
  174. package/dist/skills/metabase_ingest/SKILL.md +4 -4
  175. package/dist/skills/metricflow_ingest/SKILL.md +15 -15
  176. package/dist/skills/notion_synthesize/SKILL.md +1 -1
  177. package/dist/skills/sl/SKILL.md +3 -3
  178. package/dist/skills/sl_capture/SKILL.md +1 -1
  179. package/dist/skills/wiki_capture/SKILL.md +1 -1
  180. package/dist/source-mapping.js +1 -1
  181. package/dist/startup-profile.js +1 -1
  182. package/dist/status-project.d.ts +0 -2
  183. package/dist/status-project.js +4 -6
  184. package/dist/telemetry/command-hook.d.ts +24 -0
  185. package/dist/telemetry/command-hook.js +37 -3
  186. package/dist/telemetry/events.d.ts +1 -1
  187. package/dist/telemetry/exception.js +14 -0
  188. package/dist/telemetry/index.d.ts +2 -2
  189. package/dist/telemetry/index.js +2 -2
  190. package/dist/text-ingest.js +1 -1
  191. package/dist/tree-picker-tui.d.ts +0 -1
  192. package/dist/tree-picker-tui.js +2 -3
  193. package/package.json +1 -1
package/dist/admin.js CHANGED
@@ -18,7 +18,7 @@ export function registerAdminCommands(program, context) {
18
18
  });
19
19
  admin
20
20
  .command('init')
21
- .description('Initialize a Git-backed KTX project directory for maintenance scripts')
21
+ .description('Initialize a Git-backed ktx project directory for maintenance scripts')
22
22
  .argument('[directory]', 'Project directory')
23
23
  .option('--force', 'Rewrite ktx.yaml and scaffold files in an existing project', false)
24
24
  .action(async (projectDir, commandOptions, command) => {
package/dist/clack.d.ts CHANGED
@@ -39,5 +39,21 @@ export interface KtxCliPromptAdapter {
39
39
  spinner(): KtxCliSpinner;
40
40
  }
41
41
  export declare function createClackSpinner(): KtxCliSpinner;
42
+ /**
43
+ * Stderr-only, non-animated spinner. Use this instead of {@link createCliSpinner}
44
+ * when the next step reads stdin in raw mode (an Ink TUI or a keypress wait):
45
+ * the animated clack spinner seizes stdin via `@clack/core`'s `block()` and
46
+ * leaves it dirty, which the following raw-mode reader misreads as a stray key.
47
+ */
42
48
  export declare function createStaticCliSpinner(io: KtxCliSpinnerIo): KtxCliSpinner;
49
+ /**
50
+ * Animated spinner in an interactive terminal, static `◐/◇/■` lines otherwise
51
+ * (scripts, CI, piped output) so logs stay clean and uncluttered by frames.
52
+ */
53
+ export declare function createCliSpinner(io: KtxCliIo): KtxCliSpinner;
54
+ export declare function runWithCliSpinner<T>(spinner: KtxCliSpinner, text: {
55
+ start: string;
56
+ success: string;
57
+ failure: string;
58
+ }, run: () => Promise<T>): Promise<T>;
43
59
  export declare function createClackPromptAdapter(): KtxCliPromptAdapter;
package/dist/clack.js CHANGED
@@ -36,30 +36,61 @@ class KtxCliPromptCancelledError extends Error {
36
36
  }
37
37
  }
38
38
  export function createClackSpinner() {
39
- return spinner();
39
+ // clack colors the animated spinner frame magenta by default; styleFrame
40
+ // (typed in SpinnerOptions, absent from the README) recolors it ktx orange.
41
+ return spinner({ styleFrame: orange });
40
42
  }
41
- function magenta(text) {
42
- return ansiColor(text, 35, 39);
43
+ // ktx mascot orange (#FF8A4C) via 24-bit truecolor.
44
+ function orange(text) {
45
+ if (!ansiEnabled()) {
46
+ return text;
47
+ }
48
+ return `${ESC}[38;2;255;138;76m${text}${ESC}[39m`;
43
49
  }
44
50
  function red(text) {
45
51
  return ansiColor(text, 31, 39);
46
52
  }
53
+ /**
54
+ * Stderr-only, non-animated spinner. Use this instead of {@link createCliSpinner}
55
+ * when the next step reads stdin in raw mode (an Ink TUI or a keypress wait):
56
+ * the animated clack spinner seizes stdin via `@clack/core`'s `block()` and
57
+ * leaves it dirty, which the following raw-mode reader misreads as a stray key.
58
+ */
47
59
  export function createStaticCliSpinner(io) {
48
60
  return {
49
61
  start(message) {
50
- io.stderr.write(`${magenta('◐')} ${message}\n`);
62
+ io.stderr.write(`${orange('◐')} ${message}\n`);
51
63
  },
52
64
  message(message) {
53
- io.stderr.write(`${magenta('│')} ${message}\n`);
65
+ io.stderr.write(`${orange('│')} ${message}\n`);
54
66
  },
55
67
  stop(message) {
56
- io.stderr.write(`${magenta('◇')} ${message}\n`);
68
+ io.stderr.write(`${orange('◇')} ${message}\n`);
57
69
  },
58
70
  error(message) {
59
71
  io.stderr.write(`${red('■')} ${message}\n`);
60
72
  },
61
73
  };
62
74
  }
75
+ /**
76
+ * Animated spinner in an interactive terminal, static `◐/◇/■` lines otherwise
77
+ * (scripts, CI, piped output) so logs stay clean and uncluttered by frames.
78
+ */
79
+ export function createCliSpinner(io) {
80
+ return io.stdout.isTTY === true ? createClackSpinner() : createStaticCliSpinner(io);
81
+ }
82
+ export async function runWithCliSpinner(spinner, text, run) {
83
+ spinner.start(text.start);
84
+ try {
85
+ const value = await run();
86
+ spinner.stop(text.success);
87
+ return value;
88
+ }
89
+ catch (error) {
90
+ spinner.error(text.failure);
91
+ throw error;
92
+ }
93
+ }
63
94
  export function createClackPromptAdapter() {
64
95
  return {
65
96
  async confirm(options) {
@@ -15,7 +15,7 @@ export function formatClaudeCodePromptCachingWarning(fields) {
15
15
  if (fields.length === 0) {
16
16
  return null;
17
17
  }
18
- return `claude-code ignores ${fields.join(', ')} because the Claude Agent SDK does not expose KTX prompt-cache TTL, tool, or history markers.`;
18
+ return `claude-code ignores ${fields.join(', ')} because the Claude Agent SDK does not expose ktx prompt-cache TTL, tool, or history markers.`;
19
19
  }
20
20
  export function formatClaudeCodePromptCachingFix() {
21
21
  return 'Remove those promptCaching fields or use anthropic, vertex, or gateway when those cache knobs are required.';
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { Command, InvalidArgumentError } from '@commander-js/extra-typings';
4
+ import { SLACK_HELP_FOOTER, writeErrorCommunityHint } from './community-cta.js';
4
5
  import { registerCompletionCommands } from './commands/completion-commands.js';
5
6
  import { registerConnectionCommands } from './commands/connection-commands.js';
6
7
  import { registerIngestCommands } from './commands/ingest-commands.js';
@@ -161,13 +162,14 @@ export function resolveCommandProjectDirOverride(command) {
161
162
  function createBaseProgram(info, io) {
162
163
  return new Command()
163
164
  .name('ktx')
164
- .description('KTX data agent context layer CLI')
165
- .option('--project-dir <path>', 'KTX project directory (default: KTX_PROJECT_DIR, nearest ktx.yaml, or cwd)')
165
+ .description('ktx data agent context layer CLI')
166
+ .option('--project-dir <path>', 'ktx project directory (default: KTX_PROJECT_DIR, nearest ktx.yaml, or cwd)')
166
167
  .option('--debug', 'Enable diagnostic logging to stderr')
167
168
  .version(`${info.name} ${info.version}`, '-v, --version', 'Show CLI version')
168
169
  .helpOption('-h, --help', 'Show this help text')
169
170
  .configureHelp({ showGlobalOptions: true })
170
171
  .showHelpAfterError()
172
+ .addHelpText('after', `\n${SLACK_HELP_FOOTER}`)
171
173
  .exitOverride()
172
174
  .configureOutput({
173
175
  writeOut: (chunk) => io.stdout.write(chunk),
@@ -347,7 +349,7 @@ export function buildKtxProgram(options) {
347
349
  const attachProjectGroup = shouldAttachCommandProjectGroup(path, hasProject);
348
350
  telemetry.beginCommandSpan({
349
351
  commandPath: path,
350
- flagsPresent: collectCommandFlagsPresent(commandNode),
352
+ flagsPresent: collectCommandFlagsPresent(actionCommand),
351
353
  projectDir: attachProjectGroup ? projectDir : undefined,
352
354
  hasProject,
353
355
  attachProjectGroup,
@@ -433,6 +435,7 @@ export async function runCommanderKtxCli(argv, io, deps, info, options) {
433
435
  io,
434
436
  });
435
437
  io.stderr.write(`${formatCliError(error)}\n`);
438
+ writeErrorCommunityHint(io, 'error');
436
439
  return 1;
437
440
  }
438
441
  }
@@ -458,6 +461,7 @@ export async function runCommanderKtxCli(argv, io, deps, info, options) {
458
461
  }
459
462
  else {
460
463
  io.stderr.write(`${formatCliError(error)}\n`);
464
+ writeErrorCommunityHint(io, 'error');
461
465
  exitCode = 1;
462
466
  }
463
467
  }
@@ -49,5 +49,7 @@ export declare function runInitForCommander(args: {
49
49
  }, io: KtxCliIo): Promise<number>;
50
50
  /** @internal */
51
51
  export declare function createGlobalExceptionReporter(io: KtxCliIo, info: KtxCliPackageInfo): (source: "uncaughtException" | "unhandledRejection", error: unknown) => Promise<void>;
52
+ /** @internal */
53
+ export declare function writeGlobalExceptionToStderr(io: KtxCliIo, error: unknown): void;
52
54
  export declare function installGlobalExceptionHandlers(io: KtxCliIo, info: KtxCliPackageInfo): () => void;
53
55
  export declare function runKtxCli(argv?: string[], io?: KtxCliIo, deps?: KtxCliDeps): Promise<number>;
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { profileMark, profileSpan } from './startup-profile.js';
3
3
  import { assertCliVersion } from './release-version.js';
4
+ import { writeErrorCommunityHint } from './community-cta.js';
4
5
  profileMark('module:cli-runtime');
5
6
  const requirePackageJson = createRequire(import.meta.url);
6
7
  export function getKtxCliPackageInfo() {
@@ -13,7 +14,7 @@ export function packageInfoFromJson(packageJson) {
13
14
  !('version' in packageJson) ||
14
15
  typeof packageJson.name !== 'string' ||
15
16
  typeof packageJson.version !== 'string') {
16
- throw new Error('Invalid KTX CLI package metadata');
17
+ throw new Error('Invalid ktx CLI package metadata');
17
18
  }
18
19
  return {
19
20
  name: packageJson.name,
@@ -27,7 +28,7 @@ async function runInit(args, io) {
27
28
  projectDir: args.projectDir,
28
29
  force: args.force,
29
30
  });
30
- io.stdout.write(`Initialized KTX project at ${result.projectDir}\n`);
31
+ io.stdout.write(`Initialized ktx project at ${result.projectDir}\n`);
31
32
  io.stdout.write(`Config: ${result.configPath}\n`);
32
33
  io.stdout.write(`Commit: ${result.commitHash ?? 'none'}\n`);
33
34
  return 0;
@@ -88,6 +89,16 @@ export function createGlobalExceptionReporter(io, info) {
88
89
  await shutdownTelemetryEmitter();
89
90
  };
90
91
  }
92
+ /** @internal */
93
+ export function writeGlobalExceptionToStderr(io, error) {
94
+ if (error instanceof Error && error.stack) {
95
+ io.stderr.write(`${error.stack}\n`);
96
+ }
97
+ else {
98
+ io.stderr.write(`${String(error)}\n`);
99
+ }
100
+ writeErrorCommunityHint(io, 'crash');
101
+ }
91
102
  export function installGlobalExceptionHandlers(io, info) {
92
103
  const report = createGlobalExceptionReporter(io, info);
93
104
  const handle = (source, error) => {
@@ -98,12 +109,7 @@ export function installGlobalExceptionHandlers(io, info) {
98
109
  catch {
99
110
  // Best-effort: preserve Node's process termination behavior.
100
111
  }
101
- if (error instanceof Error && error.stack) {
102
- io.stderr.write(`${error.stack}\n`);
103
- }
104
- else {
105
- io.stderr.write(`${String(error)}\n`);
106
- }
112
+ writeGlobalExceptionToStderr(io, error);
107
113
  process.exit(1);
108
114
  })();
109
115
  };
@@ -27,7 +27,7 @@ export function registerConnectionCommands(program, context, commandName = 'conn
27
27
  connection
28
28
  .command('test')
29
29
  .description('Test one or all configured connections (default: all)')
30
- .argument('[connectionId]', 'KTX connection id to test (omit to test all)')
30
+ .argument('[connectionId]', 'ktx connection id to test (omit to test all)')
31
31
  .option('--all', 'Test every configured connection and print a summary list')
32
32
  .action(async (connectionId, options, command) => {
33
33
  if (options.all === true && connectionId !== undefined) {
@@ -7,16 +7,16 @@ profileMark('module:commands/ingest-commands');
7
7
  export function registerIngestCommands(program, context, commandOptions) {
8
8
  const ingest = program
9
9
  .command('ingest')
10
- .description('Build or inspect KTX context, or capture text into memory')
10
+ .description('Build or inspect ktx context, or capture text into memory')
11
11
  .usage('[options] [connectionId]')
12
12
  .argument('[connectionId]', 'Configured connection id to ingest (omit to ingest all)')
13
13
  .option('--all', 'Ingest all configured connections', false)
14
14
  .addOption(new Option('--query-history', 'Include database query-history usage patterns').conflicts('noQueryHistory'))
15
15
  .addOption(new Option('--no-query-history', 'Skip database query-history usage patterns'))
16
16
  .option('--query-history-window-days <days>', 'Query-history lookback window for this run', parsePositiveIntegerOption)
17
- .option('--text <content>', 'Capture inline text into KTX memory; repeatable', collectOption, [])
18
- .option('--file <path>', 'Capture a text file into KTX memory; use - for stdin; repeatable', collectOption, [])
19
- .option('--connection-id <connectionId>', 'KTX connection id to tag captured text/file notes')
17
+ .option('--text <content>', 'Capture inline text into ktx memory; repeatable', collectOption, [])
18
+ .option('--file <path>', 'Capture a text file into ktx memory; use - for stdin; repeatable', collectOption, [])
19
+ .option('--connection-id <connectionId>', 'ktx connection id to tag captured text/file notes')
20
20
  .option('--user-id <id>', 'Memory user id for text/file capture attribution', 'local-cli')
21
21
  .option('--fail-fast', 'Stop after the first failed text/file item', false)
22
22
  .addOption(new Option('--plain', 'Print plain text output').conflicts(['json']))
@@ -13,11 +13,11 @@ function binPath() {
13
13
  }
14
14
  function formatMcpStartResultMessage(input) {
15
15
  return [
16
- input.status === 'started' ? `KTX MCP daemon started: ${input.url}` : `KTX MCP daemon already running: ${input.url}`,
16
+ input.status === 'started' ? `ktx MCP daemon started: ${input.url}` : `ktx MCP daemon already running: ${input.url}`,
17
17
  '',
18
- 'KTX is ready for configured agents.',
19
- 'Open your agent for this KTX project and ask a data question, for example:',
20
- ' "Use KTX to show me the available tables and metrics."',
18
+ 'ktx is ready for configured agents.',
19
+ 'Open your agent for this ktx project and ask a data question, for example:',
20
+ ' "Use ktx to show me the available tables and metrics."',
21
21
  '',
22
22
  ].join('\n');
23
23
  }
@@ -34,13 +34,13 @@ async function printMcpStatus(context, projectDir) {
34
34
  export function registerMcpCommands(program, context) {
35
35
  const mcp = program
36
36
  .command('mcp')
37
- .description('Manage the KTX MCP HTTP server (bare command: show status)')
37
+ .description('Manage the ktx MCP HTTP server (bare command: show status)')
38
38
  .action(async (_options, command) => {
39
39
  await printMcpStatus(context, resolveCommandProjectDir(command));
40
40
  });
41
41
  mcp
42
42
  .command('stdio')
43
- .description('Run the KTX MCP server over stdio')
43
+ .description('Run the ktx MCP server over stdio')
44
44
  .action(async (_options, command) => {
45
45
  await (context.deps.mcp?.runStdioServer ?? runKtxMcpStdioServer)({
46
46
  projectDir: resolveCommandProjectDir(command),
@@ -50,7 +50,7 @@ export function registerMcpCommands(program, context) {
50
50
  });
51
51
  mcp
52
52
  .command('start')
53
- .description('Start the KTX MCP HTTP server')
53
+ .description('Start the ktx MCP HTTP server')
54
54
  .option('--host <host>', 'Host to bind', '127.0.0.1')
55
55
  .option('--port <n>', 'Port to bind', parsePositiveIntegerOption, 7878)
56
56
  .option('--token <token>', 'Bearer token required for non-loopback binding')
@@ -78,7 +78,7 @@ export function registerMcpCommands(program, context) {
78
78
  allowedOrigins: options.allowedOrigin,
79
79
  io: context.io,
80
80
  });
81
- context.io.stdout.write(`KTX MCP server listening at http://${options.host}:${options.port}/mcp\n`);
81
+ context.io.stdout.write(`ktx MCP server listening at http://${options.host}:${options.port}/mcp\n`);
82
82
  return;
83
83
  }
84
84
  const result = await (context.deps.mcp?.startDaemon ?? startKtxMcpDaemon)({
@@ -95,22 +95,22 @@ export function registerMcpCommands(program, context) {
95
95
  });
96
96
  mcp
97
97
  .command('stop')
98
- .description('Stop the KTX MCP daemon')
98
+ .description('Stop the ktx MCP daemon')
99
99
  .action(async (_options, command) => {
100
100
  const result = await (context.deps.mcp?.stopDaemon ?? stopKtxMcpDaemon)({
101
101
  projectDir: resolveCommandProjectDir(command),
102
102
  });
103
- context.io.stdout.write(result.status === 'stopped' ? 'KTX MCP daemon stopped.\n' : 'KTX MCP daemon is not running.\n');
103
+ context.io.stdout.write(result.status === 'stopped' ? 'ktx MCP daemon stopped.\n' : 'ktx MCP daemon is not running.\n');
104
104
  });
105
105
  mcp
106
106
  .command('status')
107
- .description('Show KTX MCP daemon status')
107
+ .description('Show ktx MCP daemon status')
108
108
  .action(async (_options, command) => {
109
109
  await printMcpStatus(context, resolveCommandProjectDir(command));
110
110
  });
111
111
  mcp
112
112
  .command('logs')
113
- .description('Print the KTX MCP daemon log')
113
+ .description('Print the ktx MCP daemon log')
114
114
  .option('--follow', 'Follow log output', false)
115
115
  .action(async (options, command) => {
116
116
  const logPath = mcpDaemonLayout(resolveCommandProjectDir(command)).logPath;
@@ -12,7 +12,7 @@ async function runRuntimeArgs(context, args) {
12
12
  export function registerRuntimeCommands(program, context) {
13
13
  const runtime = program
14
14
  .command('runtime')
15
- .description('Install, start, stop, and inspect the KTX-managed Python runtime')
15
+ .description('Install, start, stop, and inspect the ktx-managed Python runtime')
16
16
  .showHelpAfterError();
17
17
  runtime
18
18
  .command('install')
@@ -30,7 +30,7 @@ export function registerRuntimeCommands(program, context) {
30
30
  });
31
31
  runtime
32
32
  .command('start')
33
- .description('Start the KTX 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,8 +44,8 @@ export function registerRuntimeCommands(program, context) {
44
44
  });
45
45
  runtime
46
46
  .command('stop')
47
- .description('Stop the KTX daemon')
48
- .option('--all', 'Stop all KTX daemon processes recorded or discoverable on this machine', false)
47
+ .description('Stop the ktx daemon')
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, {
51
51
  command: 'stop',
@@ -1,5 +1,6 @@
1
1
  import { InvalidArgumentError, Option } from '@commander-js/extra-typings';
2
2
  import { resolveCommandProjectDir } from '../cli-program.js';
3
+ import { isKtxSetupLlmBackend } from '../setup-models.js';
3
4
  async function runSetupArgs(context, args) {
4
5
  const runner = context.deps.setup ?? (await import('../setup.js')).runKtxSetup;
5
6
  context.setExitCode(await runner(args, context.io));
@@ -7,7 +8,7 @@ async function runSetupArgs(context, args) {
7
8
  function positiveInteger(value) {
8
9
  const parsed = Number.parseInt(value, 10);
9
10
  if (!Number.isInteger(parsed) || parsed <= 0) {
10
- throw new Error(`Expected a positive integer, received ${value}`);
11
+ throw new InvalidArgumentError(`Expected a positive integer, received ${value}`);
11
12
  }
12
13
  return parsed;
13
14
  }
@@ -18,7 +19,7 @@ function embeddingBackend(value) {
18
19
  throw new InvalidArgumentError(`invalid choice '${value}'`);
19
20
  }
20
21
  function llmBackend(value) {
21
- if (value === 'anthropic' || value === 'vertex' || value === 'claude-code' || value === 'codex') {
22
+ if (isKtxSetupLlmBackend(value)) {
22
23
  return value;
23
24
  }
24
25
  throw new InvalidArgumentError(`invalid choice '${value}'`);
@@ -125,8 +126,8 @@ function shouldShowSetupEntryMenu(options, command) {
125
126
  export function registerSetupCommands(program, context) {
126
127
  const setup = program
127
128
  .command('setup')
128
- .description('Set up or resume a local KTX project')
129
- .addOption(new Option('--project-dir <path>', 'KTX project directory').hideHelp())
129
+ .description('Set up or resume a local ktx project')
130
+ .addOption(new Option('--project-dir <path>', 'ktx project directory').hideHelp())
130
131
  .option('--agents', 'Install agent integration only', false)
131
132
  .addOption(new Option('--target <target>', 'Agent target').choices([
132
133
  'claude-code',
@@ -184,7 +185,7 @@ export function registerSetupCommands(program, context) {
184
185
  .argParser((value, previous) => [...previous, value])
185
186
  .default([])
186
187
  .hideHelp())
187
- .addOption(new Option('--skip-databases', 'Leave database setup incomplete; KTX cannot work until a database is added')
188
+ .addOption(new Option('--skip-databases', 'Leave database setup incomplete; ktx cannot work until a database is added')
188
189
  .hideHelp()
189
190
  .default(false))
190
191
  .addOption(new Option('--source <type>', 'Source connector type').argParser(sourceType).hideHelp())
@@ -30,7 +30,7 @@ export function registerSlCommands(program, context, commandName = 'sl') {
30
30
  .description('List, search, validate, or query local semantic-layer sources')
31
31
  .usage('[options] [query...]')
32
32
  .argument('[query...]', 'Search query; omit to list all sources')
33
- .option('--connection-id <id>', 'KTX connection id')
33
+ .option('--connection-id <id>', 'ktx connection id')
34
34
  .option('--limit <number>', 'Maximum search results (search mode only)', parsePositiveIntegerOption)
35
35
  .addOption(new Option('--output <mode>', 'Output mode: pretty (default in TTY), plain (TSV), or json').choices([
36
36
  'pretty',
@@ -20,7 +20,7 @@ export function registerSqlCommands(program, context) {
20
20
  .command('sql')
21
21
  .description('Execute parser-validated read-only SQL against a configured connection')
22
22
  .argument('<sql...>', 'SQL query to execute')
23
- .requiredOption('-c, --connection <id>', 'KTX connection id')
23
+ .requiredOption('-c, --connection <id>', 'ktx connection id')
24
24
  .option('--max-rows <n>', 'Maximum rows to return', parseSqlMaxRowsOption, DEFAULT_MAX_ROWS)
25
25
  .addOption(new Option('--output <mode>', 'Output mode: pretty (default), plain (TSV), or json').choices([
26
26
  'pretty',
@@ -10,7 +10,7 @@ function inputMode(options) {
10
10
  export function registerStatusCommands(program, context) {
11
11
  program
12
12
  .command('status')
13
- .description('Check current KTX setup and project readiness')
13
+ .description('Check current ktx setup and project readiness')
14
14
  .option('--json', 'Print JSON output', false)
15
15
  .option('-v, --verbose', 'Show every check, including passing ones', false)
16
16
  .option('--validate', 'Only validate the ktx.yaml schema; skip readiness checks', false)
@@ -0,0 +1,11 @@
1
+ import type { KtxCliIo } from './cli-runtime.js';
2
+ type ErrorCtaVariant = 'error' | 'crash';
3
+ /** @internal */
4
+ export declare const SLACK_HELP_FOOTER = "Community & support: https://ktx.sh/slack";
5
+ /** @internal */
6
+ export declare const SLACK_SETUP_NOTE: {
7
+ readonly title: "Community";
8
+ readonly body: "Questions or feedback? Join the ktx Slack: https://ktx.sh/slack";
9
+ };
10
+ export declare function writeErrorCommunityHint(io: KtxCliIo, variant: ErrorCtaVariant): void;
11
+ export {};
@@ -0,0 +1,19 @@
1
+ import { isWritableTtyOutput } from './io/tty.js';
2
+ import { dim } from './io/symbols.js';
3
+ import { SLACK_URL } from './links.js';
4
+ /** @internal */
5
+ export const SLACK_HELP_FOOTER = `Community & support: ${SLACK_URL}`;
6
+ /** @internal */
7
+ export const SLACK_SETUP_NOTE = {
8
+ title: 'Community',
9
+ body: `Questions or feedback? Join the ktx Slack: ${SLACK_URL}`,
10
+ };
11
+ export function writeErrorCommunityHint(io, variant) {
12
+ if (!isWritableTtyOutput(io.stderr)) {
13
+ return;
14
+ }
15
+ const line = variant === 'crash'
16
+ ? `This may be a bug - report it or ask in the ktx community: ${SLACK_URL}`
17
+ : `Stuck? The ktx community can help: ${SLACK_URL}`;
18
+ io.stderr.write(`${dim(line)}\n`);
19
+ }
@@ -86,7 +86,7 @@ async function testMetabaseConnection(project, connectionId, createClient) {
86
86
  }
87
87
  async function createDefaultLookerClient(project, connectionId) {
88
88
  const factory = new DefaultLookerConnectionClientFactory(createLocalLookerCredentialResolver(project));
89
- return (await factory.createClient(connectionId));
89
+ return factory.createLookerClient(connectionId);
90
90
  }
91
91
  async function testLookerConnection(project, connectionId, createClient) {
92
92
  const client = await createClient(project, connectionId);
@@ -432,7 +432,7 @@ export class KtxClickHouseScanConnector {
432
432
  }
433
433
  assertConnection(connectionId) {
434
434
  if (connectionId !== this.connectionId) {
435
- throw new Error(`KTX ClickHouse connector ${this.id} cannot serve connection ${connectionId}`);
435
+ throw new Error(`ktx ClickHouse connector ${this.id} cannot serve connection ${connectionId}`);
436
436
  }
437
437
  }
438
438
  }
@@ -508,7 +508,7 @@ export class KtxMysqlScanConnector {
508
508
  }
509
509
  assertConnection(connectionId) {
510
510
  if (connectionId !== this.connectionId) {
511
- throw new Error(`KTX MySQL connector ${this.id} cannot serve connection ${connectionId}`);
511
+ throw new Error(`ktx MySQL connector ${this.id} cannot serve connection ${connectionId}`);
512
512
  }
513
513
  }
514
514
  }
@@ -72,7 +72,7 @@ export interface KtxSnowflakeScanConnectorOptions {
72
72
  connectionId: string;
73
73
  connection: KtxSnowflakeConnectionConfig | undefined;
74
74
  /**
75
- * KTX project directory. When provided, snowflake-sdk's logger is redirected to
75
+ * ktx project directory. When provided, snowflake-sdk's logger is redirected to
76
76
  * `<projectDir>/.ktx/logs/snowflake.log` so its JSON output does not bleed into
77
77
  * the CLI's TTY. Tests that use a fake driverFactory can leave this undefined.
78
78
  */
@@ -35,29 +35,6 @@ function sqlitePathFromUrl(url) {
35
35
  }
36
36
  return url;
37
37
  }
38
- function stripLeadingSqlComments(sql) {
39
- let index = 0;
40
- while (index < sql.length) {
41
- while (/\s/.test(sql[index] ?? '')) {
42
- index += 1;
43
- }
44
- if (sql.startsWith('--', index)) {
45
- const end = sql.indexOf('\n', index + 2);
46
- index = end === -1 ? sql.length : end + 1;
47
- continue;
48
- }
49
- if (sql.startsWith('/*', index)) {
50
- const end = sql.indexOf('*/', index + 2);
51
- if (end === -1) {
52
- return sql.slice(index);
53
- }
54
- index = end + 2;
55
- continue;
56
- }
57
- break;
58
- }
59
- return sql.slice(index);
60
- }
61
38
  export function isKtxSqliteConnectionConfig(connection) {
62
39
  const driver = String(connection?.driver ?? '').toLowerCase();
63
40
  return driver === 'sqlite';
@@ -171,7 +148,7 @@ export class KtxSqliteScanConnector {
171
148
  }
172
149
  async executeReadOnly(input, _ctx) {
173
150
  this.assertConnection(input.connectionId);
174
- const result = this.query(limitSqlForExecution(stripLeadingSqlComments(input.sql), input.maxRows), input.params);
151
+ const result = this.query(limitSqlForExecution(input.sql, input.maxRows), input.params);
175
152
  return { ...result, rowCount: result.rows.length };
176
153
  }
177
154
  async getColumnDistinctValues(table, columnName, options) {
@@ -274,7 +251,7 @@ export class KtxSqliteScanConnector {
274
251
  }
275
252
  assertConnection(connectionId) {
276
253
  if (connectionId !== this.connectionId) {
277
- throw new Error(`KTX SQLite connector ${this.id} cannot serve connection ${connectionId}`);
254
+ throw new Error(`ktx SQLite connector ${this.id} cannot serve connection ${connectionId}`);
278
255
  }
279
256
  }
280
257
  }
@@ -1,4 +1,4 @@
1
- import { assertReadOnlySql } from '../../context/connections/read-only-sql.js';
1
+ import { assertReadOnlySql, stripTrailingSqlNoise } from '../../context/connections/read-only-sql.js';
2
2
  import { getDialectForDriver } from '../../context/connections/dialects.js';
3
3
  import { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
4
4
  import { scopedTableNames } from '../../context/scan/table-ref.js';
@@ -142,7 +142,7 @@ function isDeniedError(error) {
142
142
  return number === 229 || number === 230 || number === 297;
143
143
  }
144
144
  function limitSqlForSqlServerExecution(sqlText, maxRows) {
145
- const trimmed = assertReadOnlySql(sqlText).replace(/;+\s*$/, '');
145
+ const trimmed = stripTrailingSqlNoise(assertReadOnlySql(sqlText));
146
146
  if (!maxRows) {
147
147
  return trimmed;
148
148
  }
@@ -575,7 +575,7 @@ export class KtxSqlServerScanConnector {
575
575
  }
576
576
  assertConnection(connectionId) {
577
577
  if (connectionId !== this.connectionId) {
578
- throw new Error(`KTX SQL Server connector ${this.id} cannot serve connection ${connectionId}`);
578
+ throw new Error(`ktx SQL Server connector ${this.id} cannot serve connection ${connectionId}`);
579
579
  }
580
580
  }
581
581
  }
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  export declare const connectionTypeSchema: z.ZodEnum<{
3
+ PLAIN: "PLAIN";
3
4
  BIGQUERY: "BIGQUERY";
4
5
  SNOWFLAKE: "SNOWFLAKE";
5
6
  MYSQL: "MYSQL";
@@ -19,7 +20,6 @@ export declare const connectionTypeSchema: z.ZodEnum<{
19
20
  METABASE: "METABASE";
20
21
  LOOKER: "LOOKER";
21
22
  NOTION: "NOTION";
22
- PLAIN: "PLAIN";
23
23
  BETTERSTACK: "BETTERSTACK";
24
24
  }>;
25
25
  export type ConnectionType = z.infer<typeof connectionTypeSchema>;
@@ -1,2 +1,3 @@
1
1
  export declare function assertReadOnlySql(sql: string): string;
2
+ export declare function stripTrailingSqlNoise(sql: string): string;
2
3
  export declare function limitSqlForExecution(sql: string, maxRows: number | undefined): string;