@kaelio/ktx 0.7.0 → 0.9.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 (142) hide show
  1. package/assets/python/{kaelio_ktx-0.7.0-py3-none-any.whl → kaelio_ktx-0.9.0-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/cli-program.js +7 -0
  5. package/dist/cli-runtime.js +50 -3
  6. package/dist/command-schemas.d.ts +1 -1
  7. package/dist/command-tree.js +5 -1
  8. package/dist/commands/completion-commands.d.ts +3 -0
  9. package/dist/commands/completion-commands.js +38 -0
  10. package/dist/commands/ingest-commands.js +0 -4
  11. package/dist/commands/knowledge-commands.js +15 -2
  12. package/dist/commands/setup-commands.js +3 -3
  13. package/dist/commands/sl-commands.js +19 -7
  14. package/dist/completion/complete-engine.d.ts +19 -0
  15. package/dist/completion/complete-engine.js +128 -0
  16. package/dist/completion/completion-scripts.d.ts +1 -0
  17. package/dist/completion/completion-scripts.js +36 -0
  18. package/dist/completion/dynamic-candidates.d.ts +6 -0
  19. package/dist/completion/dynamic-candidates.js +98 -0
  20. package/dist/connection-drivers.d.ts +3 -0
  21. package/dist/connection-drivers.js +17 -0
  22. package/dist/connection-recovery.d.ts +34 -0
  23. package/dist/connection-recovery.js +82 -0
  24. package/dist/connection.js +3 -1
  25. package/dist/context/ingest/adapters/historic-sql/bigquery-query-history-reader.js +71 -20
  26. package/dist/context/ingest/adapters/historic-sql/chunk-unified.js +2 -1
  27. package/dist/context/ingest/adapters/historic-sql/connection-dialect.d.ts +9 -0
  28. package/dist/context/ingest/adapters/historic-sql/connection-dialect.js +15 -4
  29. package/dist/context/ingest/adapters/historic-sql/pattern-inputs.js +8 -2
  30. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.d.ts +29 -0
  31. package/dist/context/ingest/adapters/historic-sql/query-history-filter-picker.js +190 -0
  32. package/dist/context/ingest/adapters/historic-sql/scope-floor.d.ts +18 -0
  33. package/dist/context/ingest/adapters/historic-sql/scope-floor.js +229 -0
  34. package/dist/context/ingest/adapters/historic-sql/scope-membership.d.ts +8 -0
  35. package/dist/context/ingest/adapters/historic-sql/scope-membership.js +29 -0
  36. package/dist/context/ingest/adapters/historic-sql/snowflake-query-history-reader.js +68 -19
  37. package/dist/context/ingest/adapters/historic-sql/stage-unified.js +57 -50
  38. package/dist/context/ingest/adapters/historic-sql/types.d.ts +36 -3
  39. package/dist/context/ingest/adapters/historic-sql/types.js +14 -2
  40. package/dist/context/ingest/context-evidence/sqlite-context-evidence-store.d.ts +1 -1
  41. package/dist/context/ingest/ingest-bundle.runner.d.ts +8 -0
  42. package/dist/context/ingest/ingest-bundle.runner.js +72 -15
  43. package/dist/context/ingest/ingest-profile.d.ts +102 -0
  44. package/dist/context/ingest/ingest-profile.js +306 -0
  45. package/dist/context/ingest/isolated-diff/patch-integrator.js +75 -5
  46. package/dist/context/ingest/isolated-diff/work-unit-executor.js +25 -2
  47. package/dist/context/ingest/local-adapters.js +21 -4
  48. package/dist/context/ingest/local-bundle-runtime.js +4 -2
  49. package/dist/context/ingest/local-ingest.d.ts +1 -1
  50. package/dist/context/ingest/local-ingest.js +6 -4
  51. package/dist/context/ingest/memory-flow/events.js +2 -1
  52. package/dist/context/ingest/ports.d.ts +2 -0
  53. package/dist/context/ingest/reports.d.ts +3 -0
  54. package/dist/context/ingest/reports.js +10 -0
  55. package/dist/context/ingest/stages/stage-3-work-units.d.ts +3 -1
  56. package/dist/context/ingest/stages/stage-3-work-units.js +2 -0
  57. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +2 -1
  58. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  59. package/dist/context/ingest/tools/tool-call-logger.d.ts +6 -0
  60. package/dist/context/ingest/tools/tool-call-logger.js +36 -1
  61. package/dist/context/llm/ai-sdk-runtime.js +32 -3
  62. package/dist/context/llm/claude-code-runtime.js +35 -2
  63. package/dist/context/llm/codex-exec-events.d.ts +20 -0
  64. package/dist/context/llm/codex-exec-events.js +155 -0
  65. package/dist/context/llm/codex-isolation.d.ts +3 -0
  66. package/dist/context/llm/codex-isolation.js +5 -0
  67. package/dist/context/llm/codex-mcp-runtime-server.d.ts +24 -0
  68. package/dist/context/llm/codex-mcp-runtime-server.js +51 -0
  69. package/dist/context/llm/codex-models.d.ts +2 -0
  70. package/dist/context/llm/codex-models.js +17 -0
  71. package/dist/context/llm/codex-runtime-config.d.ts +16 -0
  72. package/dist/context/llm/codex-runtime-config.js +19 -0
  73. package/dist/context/llm/codex-runtime.d.ts +37 -0
  74. package/dist/context/llm/codex-runtime.js +304 -0
  75. package/dist/context/llm/codex-sdk-runner.d.ts +21 -0
  76. package/dist/context/llm/codex-sdk-runner.js +63 -0
  77. package/dist/context/llm/local-config.d.ts +2 -0
  78. package/dist/context/llm/local-config.js +12 -1
  79. package/dist/context/llm/runtime-port.d.ts +25 -0
  80. package/dist/context/mcp/context-tools.d.ts +2 -1
  81. package/dist/context/mcp/context-tools.js +82 -15
  82. package/dist/context/mcp/server.js +4 -0
  83. package/dist/context/mcp/types.d.ts +15 -1
  84. package/dist/context/project/config.d.ts +3 -0
  85. package/dist/context/project/config.js +6 -2
  86. package/dist/context/project/driver-schemas.js +1 -1
  87. package/dist/context/search/discover.js +4 -3
  88. package/dist/context/sl/local-sl.d.ts +15 -0
  89. package/dist/context/sl/local-sl.js +30 -0
  90. package/dist/context/sql-analysis/http-sql-analysis-port.js +32 -2
  91. package/dist/context/sql-analysis/ports.d.ts +12 -2
  92. package/dist/context/tools/context-candidate-mark.tool.d.ts +2 -2
  93. package/dist/context/wiki/local-knowledge.d.ts +10 -0
  94. package/dist/context/wiki/local-knowledge.js +22 -0
  95. package/dist/context-build-view.d.ts +0 -3
  96. package/dist/context-build-view.js +5 -39
  97. package/dist/ingest.js +7 -10
  98. package/dist/io/buffered-command-io.d.ts +11 -0
  99. package/dist/io/buffered-command-io.js +28 -0
  100. package/dist/knowledge.d.ts +5 -0
  101. package/dist/knowledge.js +10 -1
  102. package/dist/llm/types.d.ts +1 -1
  103. package/dist/local-adapters.d.ts +10 -2
  104. package/dist/local-adapters.js +19 -3
  105. package/dist/next-steps.js +1 -2
  106. package/dist/progress-port-adapter.d.ts +6 -0
  107. package/dist/progress-port-adapter.js +18 -0
  108. package/dist/public-ingest-copy.js +1 -1
  109. package/dist/public-ingest.d.ts +20 -8
  110. package/dist/public-ingest.js +198 -61
  111. package/dist/scan.js +3 -1
  112. package/dist/setup-context.d.ts +2 -0
  113. package/dist/setup-context.js +138 -64
  114. package/dist/setup-databases.d.ts +17 -1
  115. package/dist/setup-databases.js +366 -326
  116. package/dist/setup-models.d.ts +10 -1
  117. package/dist/setup-models.js +90 -2
  118. package/dist/setup-ready-menu.d.ts +16 -2
  119. package/dist/setup-ready-menu.js +37 -5
  120. package/dist/setup-sources.js +141 -33
  121. package/dist/setup.js +24 -12
  122. package/dist/skills/analytics/SKILL.md +6 -1
  123. package/dist/sl.d.ts +6 -1
  124. package/dist/sl.js +32 -8
  125. package/dist/status-project.d.ts +11 -0
  126. package/dist/status-project.js +50 -1
  127. package/dist/telemetry/command-hook.d.ts +1 -0
  128. package/dist/telemetry/command-hook.js +3 -1
  129. package/dist/telemetry/emitter.js +1 -1
  130. package/dist/telemetry/events.d.ts +15 -9
  131. package/dist/telemetry/events.js +17 -5
  132. package/dist/telemetry/identity.d.ts +1 -2
  133. package/dist/telemetry/identity.js +13 -10
  134. package/dist/telemetry/index.d.ts +13 -1
  135. package/dist/telemetry/index.js +18 -3
  136. package/dist/telemetry/scrubber.d.ts +10 -0
  137. package/dist/telemetry/scrubber.js +20 -0
  138. package/package.json +20 -19
  139. package/dist/ingest-depth.d.ts +0 -8
  140. package/dist/ingest-depth.js +0 -56
  141. package/dist/setup-database-context-depth.d.ts +0 -23
  142. package/dist/setup-database-context-depth.js +0 -84
package/dist/sl.d.ts CHANGED
@@ -23,10 +23,15 @@ export type KtxSlArgs = {
23
23
  output?: string;
24
24
  json?: boolean;
25
25
  cliVersion: string;
26
+ } | {
27
+ command: 'read';
28
+ projectDir: string;
29
+ connectionId?: string;
30
+ sourceName: string;
26
31
  } | {
27
32
  command: 'validate';
28
33
  projectDir: string;
29
- connectionId: string;
34
+ connectionId?: string;
30
35
  sourceName: string;
31
36
  } | {
32
37
  command: 'query';
package/dist/sl.js CHANGED
@@ -3,7 +3,7 @@ import { createDefaultLocalQueryExecutor } from './context/connections/local-que
3
3
  import { KtxIngestEmbeddingPortAdapter } from './context/llm/embedding-port.js';
4
4
  import { loadKtxProject } from './context/project/project.js';
5
5
  import { compileLocalSlQuery } from './context/sl/local-query.js';
6
- import { listLocalSlSources, readLocalSlSource, searchLocalSlSources as defaultSearchLocalSlSources, validateLocalSlSource } from './context/sl/local-sl.js';
6
+ import { listLocalSlSources, resolveLocalSlSource, searchLocalSlSources as defaultSearchLocalSlSources, validateLocalSlSource, } from './context/sl/local-sl.js';
7
7
  import { resolveProjectEmbeddingProvider, } from './embedding-resolution.js';
8
8
  import { createManagedPythonSemanticLayerComputePort, } from './managed-python-command.js';
9
9
  import { profileMark } from './startup-profile.js';
@@ -85,6 +85,9 @@ async function readSlQueryFile(path) {
85
85
  }
86
86
  return parsed;
87
87
  }
88
+ function ambiguousSourceMessage(sourceName, connectionIds) {
89
+ return `Source '${sourceName}' exists in multiple connections: ${connectionIds.join(', ')}. Re-run with --connection-id <id>.`;
90
+ }
88
91
  export async function runKtxSl(args, io = process, deps = {}) {
89
92
  const startedAt = performance.now();
90
93
  let queryForTelemetry;
@@ -132,17 +135,38 @@ export async function runKtxSl(args, io = process, deps = {}) {
132
135
  });
133
136
  return 0;
134
137
  }
138
+ if (args.command === 'read') {
139
+ const resolved = await resolveLocalSlSource(project, {
140
+ connectionId: args.connectionId,
141
+ sourceName: args.sourceName,
142
+ });
143
+ if (resolved.kind === 'not-found') {
144
+ throw new Error(args.connectionId !== undefined
145
+ ? `No semantic-layer source '${args.sourceName}' for connection '${args.connectionId}'`
146
+ : `No semantic-layer source '${args.sourceName}'`);
147
+ }
148
+ if (resolved.kind === 'ambiguous') {
149
+ throw new Error(ambiguousSourceMessage(args.sourceName, resolved.connectionIds));
150
+ }
151
+ io.stdout.write(resolved.source.yaml);
152
+ return 0;
153
+ }
135
154
  if (args.command === 'validate') {
136
- const source = await readLocalSlSource(project, {
155
+ const resolved = await resolveLocalSlSource(project, {
137
156
  connectionId: args.connectionId,
138
157
  sourceName: args.sourceName,
139
158
  });
140
- if (!source) {
141
- throw new Error(`Semantic-layer source "${args.connectionId}/${args.sourceName}" was not found`);
159
+ if (resolved.kind === 'not-found') {
160
+ throw new Error(args.connectionId !== undefined
161
+ ? `Semantic-layer source "${args.connectionId}/${args.sourceName}" was not found`
162
+ : `Semantic-layer source "${args.sourceName}" was not found`);
142
163
  }
143
- const result = await validateLocalSlSource(source.yaml, {
164
+ if (resolved.kind === 'ambiguous') {
165
+ throw new Error(ambiguousSourceMessage(args.sourceName, resolved.connectionIds));
166
+ }
167
+ const result = await validateLocalSlSource(resolved.source.yaml, {
144
168
  project,
145
- connectionId: args.connectionId,
169
+ connectionId: resolved.source.connectionId,
146
170
  sourceName: args.sourceName,
147
171
  });
148
172
  await emitTelemetryEvent({
@@ -150,7 +174,7 @@ export async function runKtxSl(args, io = process, deps = {}) {
150
174
  projectDir: args.projectDir,
151
175
  io,
152
176
  fields: {
153
- sourceCount: source ? 1 : 0,
177
+ sourceCount: 1,
154
178
  modelCount: 0,
155
179
  validationErrorCount: result.valid ? 0 : result.errors.length,
156
180
  outcome: result.valid ? 'ok' : 'error',
@@ -163,7 +187,7 @@ export async function runKtxSl(args, io = process, deps = {}) {
163
187
  }
164
188
  return 1;
165
189
  }
166
- io.stdout.write(`Valid semantic-layer source: ${args.connectionId}/${args.sourceName}\n`);
190
+ io.stdout.write(`Valid semantic-layer source: ${resolved.source.connectionId}/${args.sourceName}\n`);
167
191
  return 0;
168
192
  }
169
193
  if (args.command === 'query') {
@@ -62,6 +62,16 @@ type ClaudeCodeAuthProbe = (input: {
62
62
  ok: false;
63
63
  message: string;
64
64
  }>;
65
+ type CodexAuthProbe = (input: {
66
+ projectDir: string;
67
+ model: string;
68
+ }) => Promise<{
69
+ ok: true;
70
+ } | {
71
+ ok: false;
72
+ message: string;
73
+ fix: string;
74
+ }>;
65
75
  interface LocalStatsIngestPerConnection {
66
76
  connectionId: string;
67
77
  adapter: string;
@@ -135,6 +145,7 @@ export interface BuildProjectStatusOptions {
135
145
  env?: NodeJS.ProcessEnv;
136
146
  queryHistoryReadinessProbe?: HistoricSqlReadinessProbe;
137
147
  claudeCodeAuthProbe?: ClaudeCodeAuthProbe;
148
+ codexAuthProbe?: CodexAuthProbe;
138
149
  configIssues?: KtxConfigIssue[];
139
150
  fast?: boolean;
140
151
  useSpinner?: boolean;
@@ -1,6 +1,8 @@
1
1
  import { stat as statAsync, readdir as readdirAsync } from 'node:fs/promises';
2
2
  import { basename, join } from 'node:path';
3
3
  import { runClaudeCodeAuthProbe } from './context/llm/claude-code-runtime.js';
4
+ import { CODEX_ISOLATION_WARNING, CODEX_ISOLATION_WARNING_FIX, } from './context/llm/codex-isolation.js';
5
+ import { runCodexAuthProbe } from './context/llm/codex-runtime.js';
4
6
  import { ktxLocalStateDbPath } from './context/project/local-state-db.js';
5
7
  import { isQueryHistoryEnabled, queryHistoryDialectForConnection, } from './context/ingest/adapters/historic-sql/connection-dialect.js';
6
8
  import { historicSqlProbeCatalogName, runHistoricSqlReadinessProbe, } from './context/ingest/historic-sql-probes.js';
@@ -49,6 +51,18 @@ async function buildLlmStatus(config, options) {
49
51
  fix: 'Run: ktx setup (choose an LLM provider)',
50
52
  };
51
53
  }
54
+ // The runtime (resolveModelSlots) hard-requires llm.models.default for every
55
+ // non-none backend; without it ingest/scan/memory throw. Report that here so
56
+ // status never marks a project ready that the runtime would refuse to run.
57
+ if (!model || model.trim().length === 0) {
58
+ return {
59
+ backend,
60
+ model,
61
+ status: 'fail',
62
+ detail: `llm.models.default is required for backend "${backend}"`,
63
+ fix: 'Set llm.models.default in ktx.yaml, then rerun `ktx status` (or rerun `ktx setup`).',
64
+ };
65
+ }
52
66
  if (backend === 'anthropic') {
53
67
  const ref = config.provider.anthropic?.api_key;
54
68
  const resolved = resolveRef(ref, env);
@@ -90,7 +104,7 @@ async function buildLlmStatus(config, options) {
90
104
  };
91
105
  }
92
106
  if (backend === 'claude-code') {
93
- const modelName = model ?? 'sonnet';
107
+ const modelName = model;
94
108
  if (options.fast === true) {
95
109
  return {
96
110
  backend,
@@ -117,6 +131,34 @@ async function buildLlmStatus(config, options) {
117
131
  fix: 'Authenticate Claude Code locally with the Claude Code CLI, then rerun `ktx status`.',
118
132
  };
119
133
  }
134
+ if (backend === 'codex') {
135
+ const modelName = model;
136
+ if (options.fast === true) {
137
+ return {
138
+ backend,
139
+ model: modelName,
140
+ status: 'skipped',
141
+ detail: 'auth probe skipped (--fast)',
142
+ };
143
+ }
144
+ const probe = options.codexAuthProbe ?? runCodexAuthProbe;
145
+ const auth = await withSpinner(options.useSpinner === true, 'Probing Codex authentication', () => probe({ projectDir: options.projectDir, model: modelName }));
146
+ if (auth.ok) {
147
+ return {
148
+ backend,
149
+ model: modelName,
150
+ status: 'ok',
151
+ detail: 'local Codex session authenticated',
152
+ };
153
+ }
154
+ return {
155
+ backend,
156
+ model: modelName,
157
+ status: 'fail',
158
+ detail: auth.message,
159
+ fix: auth.fix,
160
+ };
161
+ }
120
162
  return { backend, model, status: 'warn', detail: 'unknown LLM backend' };
121
163
  }
122
164
  function buildEmbeddingsStatus(config, env) {
@@ -378,6 +420,12 @@ function buildWarnings(config, connections, llm, embeddings) {
378
420
  fix: formatClaudeCodePromptCachingFix(),
379
421
  });
380
422
  }
423
+ if (llm.backend === 'codex') {
424
+ warnings.push({
425
+ message: CODEX_ISOLATION_WARNING,
426
+ fix: CODEX_ISOLATION_WARNING_FIX,
427
+ });
428
+ }
381
429
  return warnings;
382
430
  }
383
431
  function buildVerdict(llm, embeddings, connections, queryHistory, warnings) {
@@ -625,6 +673,7 @@ export async function buildProjectStatus(project, options = {}) {
625
673
  projectDir: project.projectDir,
626
674
  env,
627
675
  claudeCodeAuthProbe: options.claudeCodeAuthProbe,
676
+ codexAuthProbe: options.codexAuthProbe,
628
677
  fast: options.fast,
629
678
  useSpinner: options.useSpinner,
630
679
  });
@@ -12,6 +12,7 @@ export interface CompletedCommandSpan {
12
12
  durationMs: number;
13
13
  outcome: CommandOutcome;
14
14
  errorClass?: string;
15
+ errorDetail?: string;
15
16
  flagsPresent: Record<string, boolean>;
16
17
  hasProject: boolean;
17
18
  projectDir?: string;
@@ -1,4 +1,4 @@
1
- import { scrubErrorClass } from './scrubber.js';
1
+ import { formatErrorDetail, scrubErrorClass } from './scrubber.js';
2
2
  let activeCommandSpan;
3
3
  export function beginCommandSpan(input) {
4
4
  activeCommandSpan = input;
@@ -10,11 +10,13 @@ export function completeCommandSpan(input) {
10
10
  return undefined;
11
11
  }
12
12
  const errorClass = input.error ? scrubErrorClass(input.error) : undefined;
13
+ const errorDetail = input.error ? formatErrorDetail(input.error) : undefined;
13
14
  return {
14
15
  commandPath: span.commandPath,
15
16
  durationMs: Math.max(0, input.completedAt - span.startedAt),
16
17
  outcome: input.outcome,
17
18
  ...(errorClass ? { errorClass } : {}),
19
+ ...(errorDetail ? { errorDetail } : {}),
18
20
  flagsPresent: span.flagsPresent,
19
21
  hasProject: span.hasProject,
20
22
  projectDir: span.projectDir,
@@ -17,7 +17,7 @@ async function getPostHogClient(projectApiKey, host) {
17
17
  return null;
18
18
  }
19
19
  clientPromise ??= import('posthog-node')
20
- .then(({ PostHog }) => new PostHog(projectApiKey, { host, flushAt: 1, flushInterval: 0 }))
20
+ .then(({ PostHog }) => new PostHog(projectApiKey, { host, flushAt: 1, flushInterval: 0, disableGeoip: false }))
21
21
  .catch(() => null);
22
22
  return await clientPromise;
23
23
  }
@@ -44,6 +44,7 @@ export declare const telemetryEventSchemas: {
44
44
  aborted: "aborted";
45
45
  }>;
46
46
  errorClass: z.ZodOptional<z.ZodString>;
47
+ errorDetail: z.ZodOptional<z.ZodString>;
47
48
  flagsPresent: z.ZodRecord<z.ZodString, z.ZodBoolean>;
48
49
  hasProject: z.ZodBoolean;
49
50
  projectGroupAttached: z.ZodBoolean;
@@ -67,9 +68,8 @@ export declare const telemetryEventSchemas: {
67
68
  databases: "databases";
68
69
  context: "context";
69
70
  runtime: "runtime";
70
- agents: "agents";
71
71
  secrets: "secrets";
72
- "database-context-depth": "database-context-depth";
72
+ agents: "agents";
73
73
  "demo-tour": "demo-tour";
74
74
  }>;
75
75
  outcome: z.ZodEnum<{
@@ -78,6 +78,7 @@ export declare const telemetryEventSchemas: {
78
78
  abandoned: "abandoned";
79
79
  }>;
80
80
  durationMs: z.ZodNumber;
81
+ errorDetail: z.ZodOptional<z.ZodString>;
81
82
  }, z.core.$strict>;
82
83
  readonly connection_added: z.ZodObject<{
83
84
  cliVersion: z.ZodString;
@@ -111,6 +112,7 @@ export declare const telemetryEventSchemas: {
111
112
  error: "error";
112
113
  }>;
113
114
  errorClass: z.ZodOptional<z.ZodString>;
115
+ errorDetail: z.ZodOptional<z.ZodString>;
114
116
  durationMs: z.ZodNumber;
115
117
  serverVersion: z.ZodOptional<z.ZodString>;
116
118
  }, z.core.$strict>;
@@ -164,6 +166,7 @@ export declare const telemetryEventSchemas: {
164
166
  error: "error";
165
167
  }>;
166
168
  errorClass: z.ZodOptional<z.ZodString>;
169
+ errorDetail: z.ZodOptional<z.ZodString>;
167
170
  }, z.core.$strict>;
168
171
  readonly scan_completed: z.ZodObject<{
169
172
  cliVersion: z.ZodString;
@@ -187,6 +190,7 @@ export declare const telemetryEventSchemas: {
187
190
  error: "error";
188
191
  }>;
189
192
  errorClass: z.ZodOptional<z.ZodString>;
193
+ errorDetail: z.ZodOptional<z.ZodString>;
190
194
  }, z.core.$strict>;
191
195
  readonly sl_validate_completed: z.ZodObject<{
192
196
  cliVersion: z.ZodString;
@@ -299,7 +303,9 @@ export declare const telemetryEventSchemas: {
299
303
  }>;
300
304
  durationMs: z.ZodNumber;
301
305
  errorClass: z.ZodOptional<z.ZodString>;
302
- sampleRate: z.ZodLiteral<0.1>;
306
+ sampleRate: z.ZodLiteral<1>;
307
+ mcpClientName: z.ZodOptional<z.ZodString>;
308
+ mcpClientVersion: z.ZodOptional<z.ZodString>;
303
309
  }, z.core.$strict>;
304
310
  readonly daemon_started: z.ZodObject<{
305
311
  cliVersion: z.ZodString;
@@ -389,11 +395,11 @@ export declare const telemetryEventCatalog: readonly [{
389
395
  }, {
390
396
  readonly name: "command";
391
397
  readonly description: "Emitted once for each Commander action that reaches preAction.";
392
- readonly fields: readonly ["commandPath", "durationMs", "outcome", "errorClass", "flagsPresent", "hasProject", "projectGroupAttached"];
398
+ readonly fields: readonly ["commandPath", "durationMs", "outcome", "errorClass", "errorDetail", "flagsPresent", "hasProject", "projectGroupAttached"];
393
399
  }, {
394
400
  readonly name: "setup_step";
395
401
  readonly description: "Emitted after an interactive setup step completes, skips, or aborts.";
396
- readonly fields: readonly ["step", "outcome", "durationMs"];
402
+ readonly fields: readonly ["step", "outcome", "durationMs", "errorDetail"];
397
403
  }, {
398
404
  readonly name: "connection_added";
399
405
  readonly description: "Emitted when setup writes a database, source, or demo connection.";
@@ -401,7 +407,7 @@ export declare const telemetryEventCatalog: readonly [{
401
407
  }, {
402
408
  readonly name: "connection_test";
403
409
  readonly description: "Emitted after ktx connection test completes.";
404
- readonly fields: readonly ["driver", "isDemoConnection", "outcome", "errorClass", "durationMs", "serverVersion"];
410
+ readonly fields: readonly ["driver", "isDemoConnection", "outcome", "errorClass", "errorDetail", "durationMs", "serverVersion"];
405
411
  }, {
406
412
  readonly name: "project_stack_snapshot";
407
413
  readonly description: "Emitted after commands that can summarize the local project stack.";
@@ -409,11 +415,11 @@ export declare const telemetryEventCatalog: readonly [{
409
415
  }, {
410
416
  readonly name: "ingest_completed";
411
417
  readonly description: "Emitted after a public ingest target completes.";
412
- readonly fields: readonly ["driver", "isDemoConnection", "schemaCount", "tableCount", "columnCount", "rowsBucket", "durationMs", "outcome", "errorClass"];
418
+ readonly fields: readonly ["driver", "isDemoConnection", "schemaCount", "tableCount", "columnCount", "rowsBucket", "durationMs", "outcome", "errorClass", "errorDetail"];
413
419
  }, {
414
420
  readonly name: "scan_completed";
415
421
  readonly description: "Emitted after schema scan or relationship inference completes.";
416
- readonly fields: readonly ["driver", "tableCount", "columnCount", "inferredFkCount", "declaredFkCount", "durationMs", "outcome", "errorClass"];
422
+ readonly fields: readonly ["driver", "tableCount", "columnCount", "inferredFkCount", "declaredFkCount", "durationMs", "outcome", "errorClass", "errorDetail"];
417
423
  }, {
418
424
  readonly name: "sl_validate_completed";
419
425
  readonly description: "Emitted after ktx sl validate completes.";
@@ -433,7 +439,7 @@ export declare const telemetryEventCatalog: readonly [{
433
439
  }, {
434
440
  readonly name: "mcp_request_completed";
435
441
  readonly description: "Emitted for sampled MCP tool requests.";
436
- readonly fields: readonly ["toolName", "outcome", "durationMs", "errorClass", "sampleRate"];
442
+ readonly fields: readonly ["toolName", "outcome", "durationMs", "errorClass", "sampleRate", "mcpClientName", "mcpClientVersion"];
437
443
  }, {
438
444
  readonly name: "daemon_started";
439
445
  readonly description: "Emitted when the long-lived ktx-daemon HTTP server starts.";
@@ -18,6 +18,7 @@ const commandSchema = telemetryCommonEnvelopeSchema
18
18
  durationMs: z.number().nonnegative(),
19
19
  outcome: z.enum(['ok', 'error', 'aborted']),
20
20
  errorClass: z.string().optional(),
21
+ errorDetail: z.string().max(1000).optional(),
21
22
  flagsPresent: z.record(z.string(), z.boolean()),
22
23
  hasProject: z.boolean(),
23
24
  projectGroupAttached: z.boolean(),
@@ -33,7 +34,6 @@ const setupStepSchema = telemetryCommonEnvelopeSchema
33
34
  'embeddings',
34
35
  'secrets',
35
36
  'databases',
36
- 'database-context-depth',
37
37
  'sources',
38
38
  'context',
39
39
  'agents',
@@ -41,6 +41,7 @@ const setupStepSchema = telemetryCommonEnvelopeSchema
41
41
  ]),
42
42
  outcome: z.enum(['completed', 'skipped', 'abandoned']),
43
43
  durationMs: z.number().nonnegative(),
44
+ errorDetail: z.string().max(1000).optional(),
44
45
  })
45
46
  .strict();
46
47
  const connectionAddedSchema = telemetryCommonEnvelopeSchema
@@ -55,6 +56,7 @@ const connectionTestSchema = telemetryCommonEnvelopeSchema
55
56
  isDemoConnection: z.boolean(),
56
57
  outcome: outcomeSchema,
57
58
  errorClass: z.string().optional(),
59
+ errorDetail: z.string().max(1000).optional(),
58
60
  durationMs: z.number().nonnegative(),
59
61
  serverVersion: z.string().optional(),
60
62
  })
@@ -81,6 +83,7 @@ const ingestCompletedSchema = telemetryCommonEnvelopeSchema
81
83
  durationMs: z.number().nonnegative(),
82
84
  outcome: outcomeSchema,
83
85
  errorClass: z.string().optional(),
86
+ errorDetail: z.string().max(1000).optional(),
84
87
  })
85
88
  .strict();
86
89
  const scanCompletedSchema = telemetryCommonEnvelopeSchema
@@ -93,6 +96,7 @@ const scanCompletedSchema = telemetryCommonEnvelopeSchema
93
96
  durationMs: z.number().nonnegative(),
94
97
  outcome: outcomeSchema,
95
98
  errorClass: z.string().optional(),
99
+ errorDetail: z.string().max(1000).optional(),
96
100
  })
97
101
  .strict();
98
102
  const slValidateCompletedSchema = telemetryCommonEnvelopeSchema
@@ -141,7 +145,12 @@ const mcpRequestCompletedSchema = telemetryCommonEnvelopeSchema
141
145
  outcome: outcomeSchema,
142
146
  durationMs: z.number().nonnegative(),
143
147
  errorClass: z.string().optional(),
144
- sampleRate: z.literal(0.1),
148
+ sampleRate: z.literal(1),
149
+ // Raw, client-tool-controlled identity from the MCP initialize handshake
150
+ // (clientInfo.name/version). Optional: clients may omit clientInfo. Stored
151
+ // verbatim — normalize the free-form names at query time, not at write time.
152
+ mcpClientName: z.string().optional(),
153
+ mcpClientVersion: z.string().optional(),
145
154
  })
146
155
  .strict();
147
156
  const daemonStartedSchema = telemetryCommonEnvelopeSchema
@@ -211,6 +220,7 @@ export const telemetryEventCatalog = [
211
220
  'durationMs',
212
221
  'outcome',
213
222
  'errorClass',
223
+ 'errorDetail',
214
224
  'flagsPresent',
215
225
  'hasProject',
216
226
  'projectGroupAttached',
@@ -219,7 +229,7 @@ export const telemetryEventCatalog = [
219
229
  {
220
230
  name: 'setup_step',
221
231
  description: 'Emitted after an interactive setup step completes, skips, or aborts.',
222
- fields: ['step', 'outcome', 'durationMs'],
232
+ fields: ['step', 'outcome', 'durationMs', 'errorDetail'],
223
233
  },
224
234
  {
225
235
  name: 'connection_added',
@@ -229,7 +239,7 @@ export const telemetryEventCatalog = [
229
239
  {
230
240
  name: 'connection_test',
231
241
  description: 'Emitted after ktx connection test completes.',
232
- fields: ['driver', 'isDemoConnection', 'outcome', 'errorClass', 'durationMs', 'serverVersion'],
242
+ fields: ['driver', 'isDemoConnection', 'outcome', 'errorClass', 'errorDetail', 'durationMs', 'serverVersion'],
233
243
  },
234
244
  {
235
245
  name: 'project_stack_snapshot',
@@ -249,6 +259,7 @@ export const telemetryEventCatalog = [
249
259
  'durationMs',
250
260
  'outcome',
251
261
  'errorClass',
262
+ 'errorDetail',
252
263
  ],
253
264
  },
254
265
  {
@@ -263,6 +274,7 @@ export const telemetryEventCatalog = [
263
274
  'durationMs',
264
275
  'outcome',
265
276
  'errorClass',
277
+ 'errorDetail',
266
278
  ],
267
279
  },
268
280
  {
@@ -304,7 +316,7 @@ export const telemetryEventCatalog = [
304
316
  {
305
317
  name: 'mcp_request_completed',
306
318
  description: 'Emitted for sampled MCP tool requests.',
307
- fields: ['toolName', 'outcome', 'durationMs', 'errorClass', 'sampleRate'],
319
+ fields: ['toolName', 'outcome', 'durationMs', 'errorClass', 'sampleRate', 'mcpClientName', 'mcpClientVersion'],
308
320
  },
309
321
  {
310
322
  name: 'daemon_started',
@@ -1,5 +1,5 @@
1
1
  /** @internal */
2
- export declare const TELEMETRY_NOTICE = "ktx collects anonymous usage data to improve the product. Opt out: set KTX_TELEMETRY_DISABLED=1.";
2
+ export declare const TELEMETRY_NOTICE = "ktx collects usage data to improve the product. Opt out: set KTX_TELEMETRY_DISABLED=1.";
3
3
  /** @internal */
4
4
  export interface TelemetryIdentityEnv {
5
5
  KTX_TELEMETRY_DISABLED?: string;
@@ -11,7 +11,6 @@ export interface TelemetryIdentityEnv {
11
11
  export interface LoadTelemetryIdentityOptions {
12
12
  homeDir?: string;
13
13
  env?: TelemetryIdentityEnv;
14
- stdoutIsTTY: boolean;
15
14
  stderr: {
16
15
  write(chunk: string): void;
17
16
  };
@@ -4,7 +4,7 @@ import { homedir } from 'node:os';
4
4
  import { dirname, join, resolve } from 'node:path';
5
5
  import { z } from 'zod';
6
6
  /** @internal */
7
- export const TELEMETRY_NOTICE = 'ktx collects anonymous usage data to improve the product. Opt out: set KTX_TELEMETRY_DISABLED=1.';
7
+ export const TELEMETRY_NOTICE = 'ktx collects usage data to improve the product. Opt out: set KTX_TELEMETRY_DISABLED=1.';
8
8
  const NOTICE_VERSION = 1;
9
9
  const telemetryFileSchema = z
10
10
  .object({
@@ -41,16 +41,13 @@ async function writeTelemetryFile(path, value) {
41
41
  export async function loadTelemetryIdentity(options) {
42
42
  const env = options.env ?? process.env;
43
43
  const path = telemetryPath(options.homeDir ?? homedir());
44
- if (envDisablesTelemetry(env) || options.stdoutIsTTY !== true) {
45
- const existing = await readTelemetryFile(path);
46
- return {
47
- installId: existing?.installId,
48
- enabled: false,
49
- createdFile: false,
50
- noticeShown: false,
51
- path,
52
- };
44
+ if (envDisablesTelemetry(env)) {
45
+ return { enabled: false, createdFile: false, noticeShown: false, path };
53
46
  }
47
+ // Honor an already-consented identity regardless of the current surface.
48
+ // Telemetry enablement follows the persisted decision and opt-out env vars,
49
+ // not whether this invocation happens to own a TTY — MCP servers always run
50
+ // headless (stdio stubs stdout; the HTTP server runs detached).
54
51
  const existing = await readTelemetryFile(path);
55
52
  if (existing) {
56
53
  return {
@@ -61,6 +58,12 @@ export async function loadTelemetryIdentity(options) {
61
58
  path,
62
59
  };
63
60
  }
61
+ // No identity yet → mint one regardless of surface. Telemetry is opt-out, so
62
+ // a fresh install is counted even when its first run is headless (an
63
+ // IDE-launched `ktx mcp stdio`, a scripted invocation); otherwise those
64
+ // installs would be permanently invisible. Opt-out env vars are honored
65
+ // above. The one-time notice is written to stderr — safe even under MCP
66
+ // stdio, which reserves stdout for its JSON-RPC protocol.
64
67
  const timestamp = (options.now ?? (() => new Date()))().toISOString();
65
68
  const next = {
66
69
  installId: randomUUID(),
@@ -7,7 +7,7 @@ export type { CommandOutcome, CompletedCommandSpan };
7
7
  export declare function showTelemetryNoticeIfNeeded(io: KtxCliIo, packageInfo: KtxCliPackageInfo): Promise<void>;
8
8
  type TelemetryEventFields<Name extends TelemetryEventName> = Omit<TelemetryEventProperties<Name>, keyof TelemetryCommonEnvelope>;
9
9
  export declare function shouldEmitMcpTelemetry(): boolean;
10
- export declare function mcpTelemetrySampleRate(): 0.1;
10
+ export declare function mcpTelemetrySampleRate(): 1;
11
11
  export declare function emitTelemetryEvent<Name extends TelemetryEventName>(input: {
12
12
  name: Name;
13
13
  fields: TelemetryEventFields<Name>;
@@ -25,3 +25,15 @@ export declare function emitCompletedCommand(input: {
25
25
  packageInfo: KtxCliPackageInfo;
26
26
  io: KtxCliIo;
27
27
  }): Promise<void>;
28
+ /**
29
+ * Flush telemetry when the process is interrupted (Ctrl-C / kill). The normal
30
+ * `command` emit + flush lives in a `finally` that a signal skips, so without
31
+ * this an interrupted long-running command (ingest, `mcp stdio`) loses its
32
+ * `command` event and any queued events. Marks the active command span as
33
+ * `aborted`, emits it, and drains the emitter. Best-effort and idempotent: if
34
+ * the span was already completed (normal exit racing a signal) the emit no-ops.
35
+ */
36
+ export declare function emitAbortedCommandAndShutdown(input: {
37
+ packageInfo: KtxCliPackageInfo;
38
+ io: KtxCliIo;
39
+ }): Promise<void>;
@@ -8,7 +8,6 @@ import { buildProjectStackSnapshotFields } from './project-snapshot.js';
8
8
  export { beginCommandSpan, completeCommandSpan, shutdownTelemetryEmitter };
9
9
  export async function showTelemetryNoticeIfNeeded(io, packageInfo) {
10
10
  const identity = await loadTelemetryIdentity({
11
- stdoutIsTTY: io.stdout.isTTY === true,
12
11
  stderr: io.stderr,
13
12
  env: process.env,
14
13
  });
@@ -26,7 +25,11 @@ export async function showTelemetryNoticeIfNeeded(io, packageInfo) {
26
25
  });
27
26
  }
28
27
  const emittedProjectSnapshots = new Set();
29
- const MCP_SAMPLE_RATE = 0.1;
28
+ // MCP tool calls are captured at full rate while ktx is early-stage: at current
29
+ // install counts any sampling below 1.0 yields too few events to be useful, and
30
+ // the recorded sampleRate lets us dial this down (and reweight history) once
31
+ // per-session call volume justifies it.
32
+ const MCP_SAMPLE_RATE = 1;
30
33
  let mcpSampled;
31
34
  function telemetryDebugEnabled() {
32
35
  return process.env.KTX_TELEMETRY_DEBUG === '1';
@@ -41,7 +44,6 @@ export function mcpTelemetrySampleRate() {
41
44
  export async function emitTelemetryEvent(input) {
42
45
  const debug = telemetryDebugEnabled();
43
46
  const identity = await loadTelemetryIdentity({
44
- stdoutIsTTY: input.io.stdout.isTTY === true,
45
47
  stderr: input.io.stderr,
46
48
  env: process.env,
47
49
  });
@@ -96,3 +98,16 @@ export async function emitCompletedCommand(input) {
96
98
  packageInfo: input.packageInfo,
97
99
  });
98
100
  }
101
+ /**
102
+ * Flush telemetry when the process is interrupted (Ctrl-C / kill). The normal
103
+ * `command` emit + flush lives in a `finally` that a signal skips, so without
104
+ * this an interrupted long-running command (ingest, `mcp stdio`) loses its
105
+ * `command` event and any queued events. Marks the active command span as
106
+ * `aborted`, emits it, and drains the emitter. Best-effort and idempotent: if
107
+ * the span was already completed (normal exit racing a signal) the emit no-ops.
108
+ */
109
+ export async function emitAbortedCommandAndShutdown(input) {
110
+ const completed = completeCommandSpan({ completedAt: performance.now(), outcome: 'aborted' });
111
+ await emitCompletedCommand({ completed, packageInfo: input.packageInfo, io: input.io });
112
+ await shutdownTelemetryEmitter();
113
+ }
@@ -1 +1,11 @@
1
1
  export declare function scrubErrorClass(error: unknown): string | undefined;
2
+ /**
3
+ * Human-readable failure detail for telemetry: the error's `.code` (when
4
+ * present) prefixed onto its `message`, collapsed to a single line and
5
+ * length-capped. Captures the message only — never the stack.
6
+ *
7
+ * This intentionally forwards raw error text, which can include identifiers from
8
+ * the user's environment (table/column names, hostnames, usernames), so that
9
+ * funnel failures are diagnosable. Callers must gate it to the failure path.
10
+ */
11
+ export declare function formatErrorDetail(error: unknown): string | undefined;
@@ -20,3 +20,23 @@ export function scrubErrorClass(error) {
20
20
  }
21
21
  return constructorName;
22
22
  }
23
+ const MAX_ERROR_DETAIL_LENGTH = 1000;
24
+ /**
25
+ * Human-readable failure detail for telemetry: the error's `.code` (when
26
+ * present) prefixed onto its `message`, collapsed to a single line and
27
+ * length-capped. Captures the message only — never the stack.
28
+ *
29
+ * This intentionally forwards raw error text, which can include identifiers from
30
+ * the user's environment (table/column names, hostnames, usernames), so that
31
+ * funnel failures are diagnosable. Callers must gate it to the failure path.
32
+ */
33
+ export function formatErrorDetail(error) {
34
+ if (error === undefined || error === null) {
35
+ return undefined;
36
+ }
37
+ const code = error.code;
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ const prefix = typeof code === 'string' || typeof code === 'number' ? `${code}: ` : '';
40
+ const detail = `${prefix}${message}`.replace(/\s+/g, ' ').trim();
41
+ return detail.length > 0 ? detail.slice(0, MAX_ERROR_DETAIL_LENGTH) : undefined;
42
+ }