@kaelio/ktx 0.9.0 → 0.11.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 (143) hide show
  1. package/assets/python/{kaelio_ktx-0.9.0-py3-none-any.whl → kaelio_ktx-0.11.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/clack.d.ts +6 -0
  5. package/dist/clack.js +17 -2
  6. package/dist/cli-program.d.ts +3 -0
  7. package/dist/cli-program.js +46 -2
  8. package/dist/cli-runtime.d.ts +5 -0
  9. package/dist/cli-runtime.js +50 -0
  10. package/dist/commands/setup-commands.js +2 -3
  11. package/dist/community-cta.d.ts +11 -0
  12. package/dist/community-cta.js +19 -0
  13. package/dist/connection.js +23 -1
  14. package/dist/connectors/bigquery/connector.d.ts +2 -5
  15. package/dist/connectors/bigquery/connector.js +2 -2
  16. package/dist/connectors/clickhouse/connector.d.ts +2 -5
  17. package/dist/connectors/clickhouse/connector.js +2 -2
  18. package/dist/connectors/mysql/connector.d.ts +7 -6
  19. package/dist/connectors/mysql/connector.js +25 -5
  20. package/dist/connectors/mysql/dialect.d.ts +1 -1
  21. package/dist/connectors/mysql/dialect.js +12 -2
  22. package/dist/connectors/postgres/connector.d.ts +2 -5
  23. package/dist/connectors/postgres/connector.js +2 -2
  24. package/dist/connectors/snowflake/connector.d.ts +2 -5
  25. package/dist/connectors/snowflake/connector.js +2 -2
  26. package/dist/connectors/sqlite/connector.d.ts +2 -5
  27. package/dist/connectors/sqlite/connector.js +2 -2
  28. package/dist/connectors/sqlserver/connector.d.ts +2 -5
  29. package/dist/connectors/sqlserver/connector.js +2 -2
  30. package/dist/context/connections/drivers.d.ts +0 -1
  31. package/dist/context/connections/drivers.js +0 -7
  32. package/dist/context/connections/query-executor.d.ts +2 -1
  33. package/dist/context/core/abort.d.ts +9 -0
  34. package/dist/context/core/abort.js +36 -0
  35. package/dist/context/core/git-env.d.ts +12 -1
  36. package/dist/context/core/git-env.js +17 -2
  37. package/dist/context/core/git.service.js +15 -7
  38. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +1 -0
  39. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +6 -2
  40. package/dist/context/ingest/context-candidates/curator-pagination.service.d.ts +1 -5
  41. package/dist/context/ingest/context-candidates/curator-pagination.service.js +1 -3
  42. package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
  43. package/dist/context/ingest/final-gate-repair.d.ts +1 -0
  44. package/dist/context/ingest/final-gate-repair.js +1 -0
  45. package/dist/context/ingest/ingest-bundle.runner.d.ts +3 -0
  46. package/dist/context/ingest/ingest-bundle.runner.js +127 -53
  47. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +1 -0
  48. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +1 -0
  49. package/dist/context/ingest/isolated-diff/work-unit-executor.d.ts +1 -0
  50. package/dist/context/ingest/local-bundle-runtime.js +11 -4
  51. package/dist/context/ingest/local-ingest.d.ts +1 -0
  52. package/dist/context/ingest/local-ingest.js +13 -3
  53. package/dist/context/ingest/memory-flow/events.js +1 -1
  54. package/dist/context/ingest/memory-flow/schema.js +8 -3
  55. package/dist/context/ingest/memory-flow/types.d.ts +7 -3
  56. package/dist/context/ingest/ports.d.ts +3 -5
  57. package/dist/context/ingest/stages/stage-3-work-units.d.ts +1 -4
  58. package/dist/context/ingest/stages/stage-3-work-units.js +5 -1
  59. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +1 -4
  60. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  61. package/dist/context/ingest/types.d.ts +1 -0
  62. package/dist/context/llm/ai-sdk-runtime.d.ts +3 -0
  63. package/dist/context/llm/ai-sdk-runtime.js +152 -16
  64. package/dist/context/llm/claude-code-runtime.d.ts +6 -4
  65. package/dist/context/llm/claude-code-runtime.js +127 -48
  66. package/dist/context/llm/codex-runtime.d.ts +3 -3
  67. package/dist/context/llm/codex-runtime.js +90 -47
  68. package/dist/context/llm/local-config.d.ts +15 -5
  69. package/dist/context/llm/local-config.js +6 -1
  70. package/dist/context/llm/rate-limit-governor.d.ts +103 -0
  71. package/dist/context/llm/rate-limit-governor.js +285 -0
  72. package/dist/context/llm/runtime-port.d.ts +3 -6
  73. package/dist/context/mcp/context-tools.js +43 -13
  74. package/dist/context/project/config.d.ts +12 -0
  75. package/dist/context/project/config.js +35 -0
  76. package/dist/context/scan/types.d.ts +15 -2
  77. package/dist/context/scan/types.js +12 -0
  78. package/dist/context/sl/description-normalization.js +4 -14
  79. package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
  80. package/dist/context-build-view.d.ts +13 -0
  81. package/dist/context-build-view.js +60 -1
  82. package/dist/demo-metrics.d.ts +0 -2
  83. package/dist/demo-metrics.js +1 -11
  84. package/dist/ingest.d.ts +1 -0
  85. package/dist/ingest.js +32 -3
  86. package/dist/io/symbols.d.ts +2 -0
  87. package/dist/io/symbols.js +2 -0
  88. package/dist/io/tty.d.ts +9 -0
  89. package/dist/io/tty.js +5 -0
  90. package/dist/links.d.ts +1 -0
  91. package/dist/links.js +1 -0
  92. package/dist/memory-flow-hud.js +8 -16
  93. package/dist/public-ingest.js +50 -15
  94. package/dist/reveal-password-prompt.d.ts +24 -0
  95. package/dist/reveal-password-prompt.js +78 -0
  96. package/dist/scan.js +18 -2
  97. package/dist/setup-agents.js +1 -5
  98. package/dist/setup-databases.d.ts +1 -0
  99. package/dist/setup-databases.js +23 -3
  100. package/dist/setup-demo-tour.js +1 -0
  101. package/dist/setup-embeddings.js +1 -1
  102. package/dist/setup-models.d.ts +1 -14
  103. package/dist/setup-models.js +116 -340
  104. package/dist/setup-prompts.js +4 -7
  105. package/dist/setup-sources.js +7 -7
  106. package/dist/setup.d.ts +26 -1
  107. package/dist/setup.js +78 -7
  108. package/dist/sl.d.ts +2 -2
  109. package/dist/sl.js +20 -4
  110. package/dist/sql.js +18 -2
  111. package/dist/star-prompt/cache.d.ts +16 -0
  112. package/dist/star-prompt/cache.js +45 -0
  113. package/dist/star-prompt/star-count.d.ts +7 -0
  114. package/dist/star-prompt/star-count.js +66 -0
  115. package/dist/star-prompt/star-line.d.ts +12 -0
  116. package/dist/star-prompt/star-line.js +26 -0
  117. package/dist/telemetry/command-hook.d.ts +24 -0
  118. package/dist/telemetry/command-hook.js +37 -3
  119. package/dist/telemetry/emitter.d.ts +10 -0
  120. package/dist/telemetry/emitter.js +31 -0
  121. package/dist/telemetry/events.d.ts +24 -0
  122. package/dist/telemetry/events.js +15 -0
  123. package/dist/telemetry/exception.d.ts +18 -0
  124. package/dist/telemetry/exception.js +162 -0
  125. package/dist/telemetry/index.d.ts +4 -3
  126. package/dist/telemetry/index.js +3 -2
  127. package/dist/telemetry/redaction-secrets.d.ts +11 -0
  128. package/dist/telemetry/redaction-secrets.js +92 -0
  129. package/dist/update-check/cache.d.ts +21 -0
  130. package/dist/update-check/cache.js +38 -0
  131. package/dist/update-check/channel.d.ts +15 -0
  132. package/dist/update-check/channel.js +30 -0
  133. package/dist/update-check/registry.d.ts +1 -0
  134. package/dist/update-check/registry.js +45 -0
  135. package/dist/update-check/update-check.d.ts +43 -0
  136. package/dist/update-check/update-check.js +116 -0
  137. package/package.json +8 -1
  138. package/dist/context/connections/local-query-executor.d.ts +0 -6
  139. package/dist/context/connections/local-query-executor.js +0 -39
  140. package/dist/context/connections/postgres-query-executor.d.ts +0 -25
  141. package/dist/context/connections/postgres-query-executor.js +0 -53
  142. package/dist/context/connections/sqlite-query-executor.d.ts +0 -4
  143. package/dist/context/connections/sqlite-query-executor.js +0 -74
package/dist/clack.d.ts CHANGED
@@ -1,4 +1,10 @@
1
1
  import type { KtxCliIo } from './cli-runtime.js';
2
+ export interface CliStyleEnv {
3
+ NO_COLOR?: string;
4
+ TERM?: string;
5
+ }
6
+ export declare function dim(text: string, env?: CliStyleEnv): string;
7
+ export declare function cyan(text: string, env?: CliStyleEnv): string;
2
8
  export interface RailBufferedSource {
3
9
  stdoutText(): string;
4
10
  stderrText(): string;
package/dist/clack.js CHANGED
@@ -1,5 +1,20 @@
1
1
  import { cancel, confirm, isCancel, log, spinner } from '@clack/prompts';
2
2
  const ESC = String.fromCharCode(0x1b);
3
+ function ansiEnabled(env = process.env) {
4
+ return !env.NO_COLOR && env.TERM !== 'dumb';
5
+ }
6
+ function ansiColor(text, open, close, env) {
7
+ if (!ansiEnabled(env)) {
8
+ return text;
9
+ }
10
+ return `${ESC}[${open}m${text}${ESC}[${close}m`;
11
+ }
12
+ export function dim(text, env) {
13
+ return ansiColor(text, 2, 22, env);
14
+ }
15
+ export function cyan(text, env) {
16
+ return ansiColor(text, 36, 39, env);
17
+ }
3
18
  export function errorMessage(error) {
4
19
  return error instanceof Error ? error.message : String(error);
5
20
  }
@@ -24,10 +39,10 @@ export function createClackSpinner() {
24
39
  return spinner();
25
40
  }
26
41
  function magenta(text) {
27
- return `${ESC}[35m${text}${ESC}[39m`;
42
+ return ansiColor(text, 35, 39);
28
43
  }
29
44
  function red(text) {
30
- return `${ESC}[31m${text}${ESC}[39m`;
45
+ return ansiColor(text, 31, 39);
31
46
  }
32
47
  export function createStaticCliSpinner(io) {
33
48
  return {
@@ -1,5 +1,6 @@
1
1
  import { Command, type CommandUnknownOpts } from '@commander-js/extra-typings';
2
2
  import type { KtxCliDeps, KtxCliIo, KtxCliPackageInfo } from './cli-runtime.js';
3
+ import { type PrepareUpdateCheckNoticeOptions } from './update-check/update-check.js';
3
4
  export interface KtxCliCommandContext {
4
5
  io: KtxCliIo;
5
6
  deps: KtxCliDeps;
@@ -23,6 +24,7 @@ interface KtxCommanderProgramOptions {
23
24
  force: boolean;
24
25
  }, io: KtxCliIo) => Promise<number>;
25
26
  }
27
+ type KtxCliUpdateCheckOptions = Pick<PrepareUpdateCheckNoticeOptions, 'env' | 'fetchDistTags' | 'homeDir' | 'now'>;
26
28
  export interface BuildKtxProgramOptions {
27
29
  io: KtxCliIo;
28
30
  deps: KtxCliDeps;
@@ -34,6 +36,7 @@ export interface BuildKtxProgramOptions {
34
36
  setExitCode?: (code: number) => void;
35
37
  argv?: string[];
36
38
  setTelemetryModule?: (telemetry: typeof import('./telemetry/index.js')) => void;
39
+ updateCheck?: KtxCliUpdateCheckOptions;
37
40
  }
38
41
  export interface CommandWithGlobalOptions {
39
42
  opts: () => object;
@@ -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';
@@ -14,6 +15,7 @@ import { registerAdminCommands } from './admin.js';
14
15
  import { renderMissingProjectMessage } from './doctor.js';
15
16
  import { findNearestKtxProjectDir, resolveKtxProjectDir } from './project-resolver.js';
16
17
  import { profileMark, profileSpan } from './startup-profile.js';
18
+ import { prepareUpdateCheckNotice } from './update-check/update-check.js';
17
19
  profileMark('module:cli-program');
18
20
  const PROJECT_AWARE_ROOT_COMMANDS = new Set(['setup', 'connection', 'ingest', 'wiki', 'sl', 'sql', 'status', 'mcp']);
19
21
  const PROJECT_INDEPENDENT_ADMIN_COMMANDS = new Set(['runtime', 'schema']);
@@ -167,6 +169,7 @@ function createBaseProgram(info, io) {
167
169
  .helpOption('-h, --help', 'Show this help text')
168
170
  .configureHelp({ showGlobalOptions: true })
169
171
  .showHelpAfterError()
172
+ .addHelpText('after', `\n${SLACK_HELP_FOOTER}`)
170
173
  .exitOverride()
171
174
  .configureOutput({
172
175
  writeOut: (chunk) => io.stdout.write(chunk),
@@ -319,16 +322,27 @@ export function collectCommandFlagsPresent(command) {
319
322
  }
320
323
  export function buildKtxProgram(options) {
321
324
  const program = createBaseProgram(options.packageInfo, options.io);
325
+ let pendingUpdateNotice = null;
322
326
  program.hook('preAction', async (_thisCommand, actionCommand) => {
323
327
  // The hidden completion command must stay silent and side-effect free: skip
324
- // the telemetry notice, command span, and project checks entirely.
328
+ // the telemetry notice, command span, project checks, and update checks entirely.
325
329
  if (commandPath(actionCommand).includes('__complete')) {
326
330
  return;
327
331
  }
332
+ const commandNode = actionCommand;
333
+ const updateCheck = await prepareUpdateCheckNotice({
334
+ io: options.io,
335
+ env: options.updateCheck?.env,
336
+ fetchDistTags: options.updateCheck?.fetchDistTags,
337
+ homeDir: options.updateCheck?.homeDir,
338
+ installedVersion: options.packageInfo.version,
339
+ now: options.updateCheck?.now,
340
+ commandOptions: commandOptions(commandNode),
341
+ });
342
+ pendingUpdateNotice = updateCheck.notice;
328
343
  const telemetry = await import('./telemetry/index.js');
329
344
  options.setTelemetryModule?.(telemetry);
330
345
  await telemetry.showTelemetryNoticeIfNeeded(options.io, options.packageInfo);
331
- const commandNode = actionCommand;
332
346
  const path = commandPath(commandNode);
333
347
  const projectDir = resolveCommandProjectDir(commandNode);
334
348
  const hasProject = ktxYamlExists(projectDir);
@@ -344,6 +358,12 @@ export function buildKtxProgram(options) {
344
358
  writeProjectDir(options.io, commandNode);
345
359
  ensureProjectAvailable(options.io, commandNode);
346
360
  });
361
+ program.hook('postAction', () => {
362
+ if (pendingUpdateNotice) {
363
+ options.io.stderr.write(pendingUpdateNotice);
364
+ pendingUpdateNotice = null;
365
+ }
366
+ });
347
367
  const context = {
348
368
  io: options.io,
349
369
  deps: options.deps,
@@ -407,7 +427,15 @@ export async function runCommanderKtxCli(argv, io, deps, info, options) {
407
427
  return await runBareInteractiveCommand(program, io, context);
408
428
  }
409
429
  catch (error) {
430
+ const telemetry = await import('./telemetry/index.js');
431
+ await telemetry.reportException({
432
+ error,
433
+ context: { source: 'bare-interactive', handled: true, fatal: false },
434
+ packageInfo: info,
435
+ io,
436
+ });
410
437
  io.stderr.write(`${formatCliError(error)}\n`);
438
+ writeErrorCommunityHint(io, 'error');
411
439
  return 1;
412
440
  }
413
441
  }
@@ -433,6 +461,7 @@ export async function runCommanderKtxCli(argv, io, deps, info, options) {
433
461
  }
434
462
  else {
435
463
  io.stderr.write(`${formatCliError(error)}\n`);
464
+ writeErrorCommunityHint(io, 'error');
436
465
  exitCode = 1;
437
466
  }
438
467
  }
@@ -443,6 +472,21 @@ export async function runCommanderKtxCli(argv, io, deps, info, options) {
443
472
  outcome: commandOutcomeForParseResult(parseError, exitCode),
444
473
  error: parseError,
445
474
  });
475
+ if (parseError &&
476
+ !isCommanderExit(parseError) &&
477
+ !isKtxProjectMissingAbortError(parseError)) {
478
+ await telemetryModule.reportException({
479
+ error: parseError,
480
+ context: {
481
+ source: completed?.commandPath.join(' ') ?? 'commander parseAsync',
482
+ handled: true,
483
+ fatal: false,
484
+ },
485
+ projectDir: completed?.projectGroupAttached ? completed.projectDir : undefined,
486
+ packageInfo: info,
487
+ io,
488
+ });
489
+ }
446
490
  await telemetryModule.emitCompletedCommand({ completed, packageInfo: info, io });
447
491
  await telemetryModule.shutdownTelemetryEmitter();
448
492
  }
@@ -47,4 +47,9 @@ export declare function runInitForCommander(args: {
47
47
  projectDir: string;
48
48
  force: boolean;
49
49
  }, io: KtxCliIo): Promise<number>;
50
+ /** @internal */
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;
54
+ export declare function installGlobalExceptionHandlers(io: KtxCliIo, info: KtxCliPackageInfo): () => void;
50
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() {
@@ -74,6 +75,53 @@ function installTelemetrySignalFlush(io, info) {
74
75
  process.off('SIGTERM', onSigterm);
75
76
  };
76
77
  }
78
+ /** @internal */
79
+ export function createGlobalExceptionReporter(io, info) {
80
+ return async (source, error) => {
81
+ const { reportException, shutdownTelemetryEmitter } = await import('./telemetry/index.js');
82
+ await reportException({
83
+ error,
84
+ context: { source, handled: false, fatal: true },
85
+ io,
86
+ packageInfo: info,
87
+ immediate: true,
88
+ });
89
+ await shutdownTelemetryEmitter();
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
+ }
102
+ export function installGlobalExceptionHandlers(io, info) {
103
+ const report = createGlobalExceptionReporter(io, info);
104
+ const handle = (source, error) => {
105
+ void (async () => {
106
+ try {
107
+ await report(source, error);
108
+ }
109
+ catch {
110
+ // Best-effort: preserve Node's process termination behavior.
111
+ }
112
+ writeGlobalExceptionToStderr(io, error);
113
+ process.exit(1);
114
+ })();
115
+ };
116
+ const onUncaught = (error) => handle('uncaughtException', error);
117
+ const onUnhandled = (reason) => handle('unhandledRejection', reason);
118
+ process.on('uncaughtException', onUncaught);
119
+ process.on('unhandledRejection', onUnhandled);
120
+ return () => {
121
+ process.off('uncaughtException', onUncaught);
122
+ process.off('unhandledRejection', onUnhandled);
123
+ };
124
+ }
77
125
  export async function runKtxCli(argv = process.argv.slice(2), io = process, deps = {}) {
78
126
  const info = getKtxCliPackageInfo();
79
127
  profileMark('runtime:runKtxCli');
@@ -81,12 +129,14 @@ export async function runKtxCli(argv = process.argv.slice(2), io = process, deps
81
129
  // Real-process entry only: flush telemetry if interrupted. Test/programmatic
82
130
  // callers pass their own `io`, so they never install process-level handlers.
83
131
  const removeSignalFlush = io === process ? installTelemetrySignalFlush(io, info) : undefined;
132
+ const removeGlobalExceptionHandlers = io === process ? installGlobalExceptionHandlers(io, info) : undefined;
84
133
  try {
85
134
  return await runCommanderKtxCli(argv, io, deps, info, {
86
135
  runInit: runInitForCommander,
87
136
  });
88
137
  }
89
138
  finally {
139
+ removeGlobalExceptionHandlers?.();
90
140
  removeSignalFlush?.();
91
141
  }
92
142
  }
@@ -89,7 +89,6 @@ function shouldShowSetupEntryMenu(options, command) {
89
89
  'llmBackend',
90
90
  'anthropicApiKeyEnv',
91
91
  'anthropicApiKeyFile',
92
- 'llmModel',
93
92
  'vertexProject',
94
93
  'vertexLocation',
95
94
  'skipLlm',
@@ -145,7 +144,6 @@ export function registerSetupCommands(program, context) {
145
144
  .addOption(new Option('--llm-backend <backend>', 'LLM backend').argParser(llmBackend).hideHelp())
146
145
  .addOption(new Option('--anthropic-api-key-env <name>', 'Environment variable containing the Anthropic API key').hideHelp())
147
146
  .addOption(new Option('--anthropic-api-key-file <path>', 'File containing the Anthropic API key').hideHelp())
148
- .addOption(new Option('--llm-model <model>', 'LLM model ID or backend model alias').hideHelp())
149
147
  .addOption(new Option('--vertex-project <project>', 'Google Vertex AI project ID, env:NAME, or file:/path').hideHelp())
150
148
  .addOption(new Option('--vertex-location <location>', 'Google Vertex AI location, env:NAME, or file:/path').hideHelp())
151
149
  .addOption(new Option('--skip-llm', 'Leave LLM setup incomplete for now').hideHelp().default(false))
@@ -272,6 +270,7 @@ export function registerSetupCommands(program, context) {
272
270
  return;
273
271
  }
274
272
  const resolvedAgentScope = options.local ? 'local' : options.global ? 'global' : 'project';
273
+ const debugEnabled = (command.optsWithGlobals ? command.optsWithGlobals() : command.opts()).debug === true;
275
274
  await runSetupArgs(context, {
276
275
  command: 'run',
277
276
  projectDir: resolveCommandProjectDir(command),
@@ -281,12 +280,12 @@ export function registerSetupCommands(program, context) {
281
280
  agentScope: resolvedAgentScope,
282
281
  skipAgents: options.skipAgents === true,
283
282
  inputMode: options.input === false ? 'disabled' : 'auto',
283
+ ...(debugEnabled ? { debug: true } : {}),
284
284
  yes: options.yes === true,
285
285
  cliVersion: context.packageInfo.version,
286
286
  ...(options.llmBackend ? { llmBackend: options.llmBackend } : {}),
287
287
  ...(options.anthropicApiKeyEnv ? { anthropicApiKeyEnv: options.anthropicApiKeyEnv } : {}),
288
288
  ...(options.anthropicApiKeyFile ? { anthropicApiKeyFile: options.anthropicApiKeyFile } : {}),
289
- ...(options.llmModel ? { llmModel: options.llmModel } : {}),
290
289
  ...(options.vertexProject ? { vertexProject: options.vertexProject } : {}),
291
290
  ...(options.vertexLocation ? { vertexLocation: options.vertexLocation } : {}),
292
291
  skipLlm: options.skipLlm === true,
@@ -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
+ }
@@ -12,7 +12,8 @@ import { bold, dim, green, red, SYMBOLS } from './io/symbols.js';
12
12
  import { createKtxCliScanConnector } from './local-scan-connectors.js';
13
13
  import { profileMark } from './startup-profile.js';
14
14
  import { isDemoConnection } from './telemetry/demo-detect.js';
15
- import { emitTelemetryEvent } from './telemetry/index.js';
15
+ import { emitTelemetryEvent, reportException } from './telemetry/index.js';
16
+ import { collectTelemetryRedactionSecrets } from './telemetry/redaction-secrets.js';
16
17
  import { formatErrorDetail, scrubErrorClass } from './telemetry/scrubber.js';
17
18
  profileMark('module:connection');
18
19
  const SUPPORTED_TEST_DRIVERS = [
@@ -44,6 +45,12 @@ async function testNativeConnection(project, connectionId, createScanConnector)
44
45
  }
45
46
  const result = await connector.testConnection();
46
47
  if (!result.success) {
48
+ // Re-throw the driver's original error so connection_test telemetry records
49
+ // its real class (e.g. ConnectionError) and code (e.g. ELOGIN) instead of
50
+ // collapsing every native failure to a generic Error with no code.
51
+ if (result.cause instanceof Error) {
52
+ throw result.cause;
53
+ }
47
54
  throw new Error(result.error ?? 'connection test failed');
48
55
  }
49
56
  return { driver: connector.driver };
@@ -182,6 +189,21 @@ async function emitConnectionTest(input) {
182
189
  ...(errorDetail ? { errorDetail } : {}),
183
190
  },
184
191
  });
192
+ if (input.error) {
193
+ await reportException({
194
+ error: input.error,
195
+ context: { source: 'connection test', handled: true, fatal: false },
196
+ projectDir: input.project.projectDir,
197
+ io: input.io,
198
+ redactionSecrets: await collectTelemetryRedactionSecrets({
199
+ project: input.project,
200
+ connectionId: input.connectionId,
201
+ includeLlm: false,
202
+ includeEmbeddings: false,
203
+ env: process.env,
204
+ }),
205
+ });
206
+ }
185
207
  }
186
208
  function visualWidth(text) {
187
209
  // styleText wraps content in ANSI escape sequences; strip them before measuring.
@@ -1,5 +1,5 @@
1
1
  import { type TableField } from '@google-cloud/bigquery';
2
- import { type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
2
+ import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
3
3
  export interface KtxBigQueryConnectionConfig {
4
4
  driver?: string;
5
5
  dataset_id?: string;
@@ -121,10 +121,7 @@ export declare class KtxBigQueryScanConnector implements KtxScanConnector {
121
121
  private readonly dialect;
122
122
  private client;
123
123
  constructor(options: KtxBigQueryScanConnectorOptions);
124
- testConnection(): Promise<{
125
- success: boolean;
126
- error?: string;
127
- }>;
124
+ testConnection(): Promise<KtxConnectorTestResult>;
128
125
  introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot>;
129
126
  sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxTableSampleResult & {
130
127
  headerTypes?: string[];
@@ -4,7 +4,7 @@ import { getDialectForDriver } from '../../context/connections/dialects.js';
4
4
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
5
5
  import { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
6
6
  import { scopedTableNames } from '../../context/scan/table-ref.js';
7
- import { createKtxConnectorCapabilities, } from '../../context/scan/types.js';
7
+ import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
8
8
  import { readFileSync } from 'node:fs';
9
9
  import { homedir } from 'node:os';
10
10
  import { resolve } from 'node:path';
@@ -185,7 +185,7 @@ export class KtxBigQueryScanConnector {
185
185
  return { success: true };
186
186
  }
187
187
  catch (error) {
188
- return { success: false, error: error instanceof Error ? error.message : String(error) };
188
+ return connectorTestFailure(error);
189
189
  }
190
190
  }
191
191
  async introspect(input, _ctx) {
@@ -1,5 +1,5 @@
1
1
  import { createClient } from '@clickhouse/client';
2
- import { type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableRef, type KtxTableSampleInput, type KtxTableListEntry, type KtxTableSampleResult } from '../../context/scan/types.js';
2
+ import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableRef, type KtxTableSampleInput, type KtxTableListEntry, type KtxTableSampleResult } from '../../context/scan/types.js';
3
3
  export interface KtxClickHouseConnectionConfig {
4
4
  driver?: string;
5
5
  host?: string;
@@ -94,10 +94,7 @@ export declare class KtxClickHouseScanConnector implements KtxScanConnector {
94
94
  private client;
95
95
  private resolvedEndpoint;
96
96
  constructor(options: KtxClickHouseScanConnectorOptions);
97
- testConnection(): Promise<{
98
- success: boolean;
99
- error?: string;
100
- }>;
97
+ testConnection(): Promise<KtxConnectorTestResult>;
101
98
  introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot>;
102
99
  private emptySnapshot;
103
100
  sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxTableSampleResult>;
@@ -1,7 +1,7 @@
1
1
  import { createClient } from '@clickhouse/client';
2
2
  import { getDialectForDriver } from '../../context/connections/dialects.js';
3
3
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
4
- import { createKtxConnectorCapabilities } from '../../context/scan/types.js';
4
+ import { connectorTestFailure, createKtxConnectorCapabilities } from '../../context/scan/types.js';
5
5
  import { scopedTableNames } from '../../context/scan/table-ref.js';
6
6
  import { readFileSync } from 'node:fs';
7
7
  import { Agent as HttpsAgent } from 'node:https';
@@ -162,7 +162,7 @@ export class KtxClickHouseScanConnector {
162
162
  return { success: true };
163
163
  }
164
164
  catch (error) {
165
- return { success: false, error: error instanceof Error ? error.message : String(error) };
165
+ return connectorTestFailure(error);
166
166
  }
167
167
  }
168
168
  async introspect(input, _ctx) {
@@ -1,5 +1,5 @@
1
1
  import { type FieldPacket, type RowDataPacket } from 'mysql2/promise';
2
- import { type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
2
+ import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
3
3
  export interface KtxMysqlConnectionConfig {
4
4
  driver?: string;
5
5
  host?: string;
@@ -71,6 +71,9 @@ export interface KtxMysqlColumnDistinctValuesResult {
71
71
  values: string[] | null;
72
72
  cardinality: number;
73
73
  }
74
+ export interface KtxMysqlColumnStatisticsResult {
75
+ cardinalityByColumn: Map<string, number>;
76
+ }
74
77
  /** @internal */
75
78
  export declare function prepareMysqlReadOnlyQuery(sql: string, params?: Record<string, unknown>): {
76
79
  sql: string;
@@ -97,15 +100,13 @@ export declare class KtxMysqlScanConnector implements KtxScanConnector {
97
100
  private pool;
98
101
  private resolvedEndpoint;
99
102
  constructor(options: KtxMysqlScanConnectorOptions);
100
- testConnection(): Promise<{
101
- success: boolean;
102
- error?: string;
103
- }>;
103
+ testConnection(): Promise<KtxConnectorTestResult>;
104
104
  introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot>;
105
105
  private emptySnapshot;
106
106
  sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxTableSampleResult>;
107
107
  sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise<KtxColumnSampleResult>;
108
- columnStats(_input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise<KtxColumnStatsResult | null>;
108
+ columnStats(input: KtxColumnStatsInput, _ctx: KtxScanContext): Promise<KtxColumnStatsResult | null>;
109
+ getColumnStatistics(table: KtxTableRef): Promise<KtxMysqlColumnStatisticsResult | null>;
109
110
  executeReadOnly(input: KtxMysqlReadOnlyQueryInput, _ctx: KtxScanContext): Promise<KtxQueryResult>;
110
111
  getColumnDistinctValues(table: KtxTableRef, columnName: string, options: KtxMysqlColumnDistinctValuesOptions): Promise<KtxMysqlColumnDistinctValuesResult | null>;
111
112
  getTableRowCount(tableName: string): Promise<number>;
@@ -6,7 +6,7 @@ import { getDialectForDriver } from '../../context/connections/dialects.js';
6
6
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
7
7
  import { constraintDiscoveryWarning, tryConstraintQuery, } from '../../context/scan/constraint-discovery.js';
8
8
  import { scopedTableNames } from '../../context/scan/table-ref.js';
9
- import { createKtxConnectorCapabilities, } from '../../context/scan/types.js';
9
+ import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
10
10
  class DefaultMysqlPoolFactory {
11
11
  createPool(config) {
12
12
  return mysql.createPool(config);
@@ -185,7 +185,7 @@ export class KtxMysqlScanConnector {
185
185
  capabilities = createKtxConnectorCapabilities({
186
186
  tableSampling: true,
187
187
  columnSampling: true,
188
- columnStats: false,
188
+ columnStats: true,
189
189
  readOnlySql: true,
190
190
  nestedAnalysis: true,
191
191
  formalForeignKeys: true,
@@ -219,7 +219,7 @@ export class KtxMysqlScanConnector {
219
219
  return { success: true };
220
220
  }
221
221
  catch (error) {
222
- return { success: false, error: error instanceof Error ? error.message : String(error) };
222
+ return connectorTestFailure(error);
223
223
  }
224
224
  }
225
225
  async introspect(input, _ctx) {
@@ -324,8 +324,28 @@ export class KtxMysqlScanConnector {
324
324
  const values = result.rows.filter((row) => row.length > 0 && row[0] !== null).map((row) => row[0]);
325
325
  return { values, nullCount: null, distinctCount: null };
326
326
  }
327
- async columnStats(_input, _ctx) {
328
- return null;
327
+ async columnStats(input, _ctx) {
328
+ const stats = await this.getColumnStatistics(input.table);
329
+ const value = stats?.cardinalityByColumn.get(input.column);
330
+ return value === undefined
331
+ ? null
332
+ : { min: null, max: null, average: null, nullCount: null, distinctCount: value };
333
+ }
334
+ async getColumnStatistics(table) {
335
+ const schema = table.db ?? this.poolConfig.database;
336
+ const sql = this.dialect.generateColumnStatisticsQuery(schema, table.name);
337
+ if (!sql) {
338
+ return null;
339
+ }
340
+ const rows = await this.queryRaw(sql);
341
+ const cardinalityByColumn = new Map();
342
+ for (const row of rows) {
343
+ const cardinality = Number(row.estimated_cardinality);
344
+ if (Number.isFinite(cardinality) && cardinality >= 0) {
345
+ cardinalityByColumn.set(row.column_name, cardinality);
346
+ }
347
+ }
348
+ return cardinalityByColumn.size > 0 ? { cardinalityByColumn } : null;
329
349
  }
330
350
  async executeReadOnly(input, _ctx) {
331
351
  this.assertConnection(input.connectionId);
@@ -25,7 +25,7 @@ export declare class KtxMysqlDialect implements KtxDialect {
25
25
  getSampleValueAggregation(innerSql: string): string;
26
26
  generateCardinalitySampleQuery(tableName: string, columnName: string, sampleSize: number): string;
27
27
  generateDistinctValuesQuery(tableName: string, columnName: string, limit: number): string;
28
- generateColumnStatisticsQuery(_schemaName: string, _tableName: string): string | null;
28
+ generateColumnStatisticsQuery(schemaName: string, tableName: string): string | null;
29
29
  generateRandomizedCardinalitySampleQuery(tableName: string, columnName: string, sampleSize: number): string;
30
30
  }
31
31
  export {};
@@ -135,8 +135,18 @@ export class KtxMysqlDialect {
135
135
  LIMIT ${limit}
136
136
  `;
137
137
  }
138
- generateColumnStatisticsQuery(_schemaName, _tableName) {
139
- return null;
138
+ generateColumnStatisticsQuery(schemaName, tableName) {
139
+ return `
140
+ SELECT
141
+ COLUMN_NAME AS column_name,
142
+ MAX(CARDINALITY) AS estimated_cardinality
143
+ FROM INFORMATION_SCHEMA.STATISTICS
144
+ WHERE TABLE_SCHEMA = '${schemaName.replace(/'/g, "''")}'
145
+ AND TABLE_NAME = '${tableName.replace(/'/g, "''")}'
146
+ AND CARDINALITY IS NOT NULL
147
+ AND SEQ_IN_INDEX = 1
148
+ GROUP BY COLUMN_NAME
149
+ `;
140
150
  }
141
151
  generateRandomizedCardinalitySampleQuery(tableName, columnName, sampleSize) {
142
152
  return `
@@ -1,4 +1,4 @@
1
- import { type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
1
+ import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
2
2
  export interface KtxPostgresConnectionConfig {
3
3
  driver?: string;
4
4
  host?: string;
@@ -117,10 +117,7 @@ export declare class KtxPostgresScanConnector implements KtxScanConnector {
117
117
  private lastIdlePoolError;
118
118
  private resolvedEndpoint;
119
119
  constructor(options: KtxPostgresScanConnectorOptions);
120
- testConnection(): Promise<{
121
- success: boolean;
122
- error?: string;
123
- }>;
120
+ testConnection(): Promise<KtxConnectorTestResult>;
124
121
  introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot>;
125
122
  sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxPostgresTableSampleResult>;
126
123
  sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise<KtxColumnSampleResult>;
@@ -5,7 +5,7 @@ import { getDialectForDriver } from '../../context/connections/dialects.js';
5
5
  import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
6
6
  import { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
7
7
  import { scopedTableNames } from '../../context/scan/table-ref.js';
8
- import { createKtxConnectorCapabilities, } from '../../context/scan/types.js';
8
+ import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
9
9
  import { Pool } from 'pg';
10
10
  const PG_OID_TYPE_MAP = {
11
11
  16: 'boolean',
@@ -231,7 +231,7 @@ export class KtxPostgresScanConnector {
231
231
  return { success: true };
232
232
  }
233
233
  catch (error) {
234
- return { success: false, error: error instanceof Error ? error.message : String(error) };
234
+ return connectorTestFailure(error);
235
235
  }
236
236
  }
237
237
  async introspect(input, _ctx) {
@@ -1,4 +1,4 @@
1
- import { type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
1
+ import { type KtxConnectorTestResult, type KtxColumnSampleInput, type KtxColumnSampleResult, type KtxColumnStatsInput, type KtxColumnStatsResult, type KtxQueryResult, type KtxReadOnlyQueryInput, type KtxScanConnector, type KtxScanContext, type KtxScanInput, type KtxSchemaSnapshot, type KtxTableListEntry, type KtxTableRef, type KtxTableSampleInput, type KtxTableSampleResult } from '../../context/scan/types.js';
2
2
  export interface KtxSnowflakeConnectionConfig {
3
3
  driver?: string;
4
4
  authMethod?: 'password' | 'rsa';
@@ -117,10 +117,7 @@ export declare class KtxSnowflakeScanConnector implements KtxScanConnector {
117
117
  private readonly now;
118
118
  private driverInstance;
119
119
  constructor(options: KtxSnowflakeScanConnectorOptions);
120
- testConnection(): Promise<{
121
- success: boolean;
122
- error?: string;
123
- }>;
120
+ testConnection(): Promise<KtxConnectorTestResult>;
124
121
  introspect(input: KtxScanInput, _ctx: KtxScanContext): Promise<KtxSchemaSnapshot>;
125
122
  sampleTable(input: KtxTableSampleInput, _ctx: KtxScanContext): Promise<KtxTableSampleResult>;
126
123
  sampleColumn(input: KtxColumnSampleInput, _ctx: KtxScanContext): Promise<KtxColumnSampleResult>;