@kaelio/ktx 0.6.0 → 0.8.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 (82) hide show
  1. package/assets/python/{kaelio_ktx-0.6.0-py3-none-any.whl → kaelio_ktx-0.8.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/command-schemas.d.ts +1 -1
  6. package/dist/command-tree.js +5 -1
  7. package/dist/commands/completion-commands.d.ts +3 -0
  8. package/dist/commands/completion-commands.js +38 -0
  9. package/dist/commands/ingest-commands.js +0 -4
  10. package/dist/commands/knowledge-commands.js +15 -2
  11. package/dist/commands/setup-commands.js +2 -2
  12. package/dist/commands/sl-commands.js +19 -7
  13. package/dist/completion/complete-engine.d.ts +19 -0
  14. package/dist/completion/complete-engine.js +128 -0
  15. package/dist/completion/completion-scripts.d.ts +1 -0
  16. package/dist/completion/completion-scripts.js +36 -0
  17. package/dist/completion/dynamic-candidates.d.ts +6 -0
  18. package/dist/completion/dynamic-candidates.js +98 -0
  19. package/dist/connection-drivers.d.ts +3 -0
  20. package/dist/connection-drivers.js +17 -0
  21. package/dist/context/ingest/ingest-bundle.runner.d.ts +8 -0
  22. package/dist/context/ingest/ingest-bundle.runner.js +72 -15
  23. package/dist/context/ingest/ingest-profile.d.ts +102 -0
  24. package/dist/context/ingest/ingest-profile.js +306 -0
  25. package/dist/context/ingest/isolated-diff/work-unit-executor.js +25 -2
  26. package/dist/context/ingest/local-bundle-runtime.js +1 -0
  27. package/dist/context/ingest/local-ingest.d.ts +1 -1
  28. package/dist/context/ingest/local-ingest.js +6 -4
  29. package/dist/context/ingest/memory-flow/events.js +2 -1
  30. package/dist/context/ingest/ports.d.ts +2 -0
  31. package/dist/context/ingest/reports.d.ts +3 -0
  32. package/dist/context/ingest/reports.js +10 -0
  33. package/dist/context/ingest/stages/stage-3-work-units.d.ts +3 -1
  34. package/dist/context/ingest/stages/stage-3-work-units.js +2 -0
  35. package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +2 -1
  36. package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
  37. package/dist/context/ingest/tools/tool-call-logger.d.ts +6 -0
  38. package/dist/context/ingest/tools/tool-call-logger.js +36 -1
  39. package/dist/context/llm/ai-sdk-runtime.js +32 -3
  40. package/dist/context/llm/claude-code-runtime.js +51 -3
  41. package/dist/context/llm/runtime-port.d.ts +25 -0
  42. package/dist/context/mcp/context-tools.d.ts +2 -1
  43. package/dist/context/mcp/context-tools.js +82 -15
  44. package/dist/context/mcp/server.js +4 -0
  45. package/dist/context/mcp/types.d.ts +15 -1
  46. package/dist/context/project/config.d.ts +1 -0
  47. package/dist/context/project/config.js +4 -0
  48. package/dist/context/project/driver-schemas.js +1 -1
  49. package/dist/context/search/discover.js +4 -3
  50. package/dist/context/sl/local-sl.d.ts +15 -0
  51. package/dist/context/sl/local-sl.js +30 -0
  52. package/dist/context/wiki/local-knowledge.d.ts +10 -0
  53. package/dist/context/wiki/local-knowledge.js +22 -0
  54. package/dist/context-build-view.d.ts +0 -3
  55. package/dist/context-build-view.js +1 -7
  56. package/dist/ingest.js +7 -10
  57. package/dist/knowledge.d.ts +5 -0
  58. package/dist/knowledge.js +10 -1
  59. package/dist/public-ingest-copy.js +1 -1
  60. package/dist/public-ingest.d.ts +0 -7
  61. package/dist/public-ingest.js +20 -34
  62. package/dist/setup-context.js +6 -38
  63. package/dist/setup-databases.js +13 -82
  64. package/dist/setup-project.d.ts +0 -8
  65. package/dist/setup-project.js +3 -27
  66. package/dist/setup-sources.js +33 -5
  67. package/dist/setup.js +3 -16
  68. package/dist/skills/analytics/SKILL.md +6 -1
  69. package/dist/sl.d.ts +6 -1
  70. package/dist/sl.js +32 -8
  71. package/dist/telemetry/emitter.js +1 -1
  72. package/dist/telemetry/events.d.ts +4 -3
  73. package/dist/telemetry/events.js +7 -3
  74. package/dist/telemetry/identity.d.ts +1 -1
  75. package/dist/telemetry/identity.js +13 -10
  76. package/dist/telemetry/index.d.ts +1 -1
  77. package/dist/telemetry/index.js +5 -1
  78. package/package.json +22 -22
  79. package/dist/ingest-depth.d.ts +0 -8
  80. package/dist/ingest-depth.js +0 -56
  81. package/dist/setup-database-context-depth.d.ts +0 -23
  82. package/dist/setup-database-context-depth.js +0 -84
@@ -114,6 +114,31 @@ function credentialRef(value, label) {
114
114
  }
115
115
  return ref;
116
116
  }
117
+ // Each connector reads exactly one credential ref; the flag name mirrors the
118
+ // ktx.yaml field it writes (auth_token_ref / api_key_ref / client_secret_ref).
119
+ const SOURCE_CREDENTIAL_FLAG = {
120
+ dbt: { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
121
+ metricflow: { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
122
+ lookml: { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
123
+ notion: { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
124
+ metabase: { field: 'sourceApiKeyRef', flag: '--source-api-key-ref' },
125
+ looker: { field: 'sourceClientSecretRef', flag: '--source-client-secret-ref' },
126
+ };
127
+ const ALL_SOURCE_CREDENTIAL_FLAGS = [
128
+ { field: 'sourceAuthTokenRef', flag: '--source-auth-token-ref' },
129
+ { field: 'sourceApiKeyRef', flag: '--source-api-key-ref' },
130
+ { field: 'sourceClientSecretRef', flag: '--source-client-secret-ref' },
131
+ ];
132
+ // Reject a credential ref flag the chosen source does not read, so a wrong flag
133
+ // fails loudly instead of being silently dropped (KLO-724).
134
+ function assertSourceCredentialFlags(source, args) {
135
+ const allowed = SOURCE_CREDENTIAL_FLAG[source];
136
+ for (const { field, flag } of ALL_SOURCE_CREDENTIAL_FLAGS) {
137
+ if (args[field] && field !== allowed.field) {
138
+ throw new Error(`${flag} does not apply to --source ${source}; use ${allowed.flag}.`);
139
+ }
140
+ }
141
+ }
117
142
  async function chooseSourceCredentialRef(input) {
118
143
  while (true) {
119
144
  const choice = await input.prompts.select({
@@ -385,7 +410,7 @@ function buildNotionConnection(args) {
385
410
  }
386
411
  return {
387
412
  driver: 'notion',
388
- auth_token_ref: credentialRef(args.sourceApiKeyRef, 'Notion token ref'),
413
+ auth_token_ref: credentialRef(args.sourceAuthTokenRef, 'Notion token ref'),
389
414
  crawl_mode: crawlMode,
390
415
  ...(rootPageIds.length > 0 ? { root_page_ids: rootPageIds } : {}),
391
416
  root_database_ids: [],
@@ -1064,11 +1089,11 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
1064
1089
  label: 'Notion integration token',
1065
1090
  envName: 'NOTION_TOKEN',
1066
1091
  secretFileName: `${currentState.sourceConnectionId ?? 'notion-main'}-token`,
1067
- existingRef: currentState.sourceApiKeyRef,
1092
+ existingRef: currentState.sourceAuthTokenRef,
1068
1093
  });
1069
1094
  if (ref === 'back')
1070
1095
  return 'back';
1071
- currentState.sourceApiKeyRef = ref;
1096
+ currentState.sourceAuthTokenRef = ref;
1072
1097
  return 'next';
1073
1098
  },
1074
1099
  async (currentState) => {
@@ -1096,7 +1121,7 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
1096
1121
  connectionId,
1097
1122
  connection: {
1098
1123
  driver: 'notion',
1099
- auth_token_ref: credentialRef(currentState.sourceApiKeyRef, 'Notion token ref'),
1124
+ auth_token_ref: credentialRef(currentState.sourceAuthTokenRef, 'Notion token ref'),
1100
1125
  crawl_mode: 'selected_roots',
1101
1126
  root_page_ids: currentState.notionRootPageIds ?? [],
1102
1127
  root_database_ids: [],
@@ -1260,7 +1285,7 @@ function sourceArgsFromExistingConnection(input) {
1260
1285
  }
1261
1286
  return sourceArgs;
1262
1287
  }
1263
- sourceArgs.sourceApiKeyRef = stringField(input.connection.auth_token_ref);
1288
+ sourceArgs.sourceAuthTokenRef = stringField(input.connection.auth_token_ref);
1264
1289
  sourceArgs.notionCrawlMode =
1265
1290
  input.connection.crawl_mode === 'all_accessible' ? 'all_accessible' : 'selected_roots';
1266
1291
  if (Array.isArray(input.connection.root_page_ids)) {
@@ -1467,6 +1492,9 @@ export async function runKtxSetupSourcesStep(args, io, deps = {}) {
1467
1492
  io.stdout.write('│ Context source setup skipped.\n');
1468
1493
  return { status: 'skipped', projectDir: args.projectDir };
1469
1494
  }
1495
+ if (args.source) {
1496
+ assertSourceCredentialFlags(args.source, args);
1497
+ }
1470
1498
  const prompts = deps.prompts ?? createPromptAdapter();
1471
1499
  const project = await loadKtxProject({ projectDir: args.projectDir });
1472
1500
  if (!hasPrimarySource(project.config)) {
package/dist/setup.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { rm } from 'node:fs/promises';
3
2
  import { basename, join, resolve } from 'node:path';
4
3
  import { getLatestLocalIngestStatus } from './context/ingest/local-ingest.js';
5
- import { savedMemoryCountsForReport } from './context/ingest/reports.js';
4
+ import { ingestReportOutcome, savedMemoryCountsForReport } from './context/ingest/reports.js';
6
5
  import { ktxLocalStateDbPath } from './context/project/local-state-db.js';
7
6
  import { loadKtxProject } from './context/project/project.js';
8
7
  import { readKtxSetupState } from './context/project/setup-config.js';
@@ -16,7 +15,7 @@ import { readKtxAgentInstallManifest, runKtxSetupAgentsStep, targetDisplayName,
16
15
  import { runKtxSetupDatabasesStep, } from './setup-databases.js';
17
16
  import { runKtxSetupEmbeddingsStep } from './setup-embeddings.js';
18
17
  import { isKtxSetupLlmConfigReady, runKtxSetupAnthropicModelStep, } from './setup-models.js';
19
- import { runKtxSetupProjectStep, } from './setup-project.js';
18
+ import { runKtxSetupProjectStep } from './setup-project.js';
20
19
  import { isKtxPreAgentSetupReady, isKtxSetupReady, runKtxSetupReadyChangeMenu, } from './setup-ready-menu.js';
21
20
  import { runKtxSetupSourcesStep } from './setup-sources.js';
22
21
  import { runKtxSetupRuntimeStep, } from './setup-runtime.js';
@@ -106,7 +105,7 @@ function sourceConnections(config) {
106
105
  .sort((left, right) => left.connectionId.localeCompare(right.connectionId));
107
106
  }
108
107
  function reportHasSavedContext(report) {
109
- if (report.body.failedWorkUnits.length > 0) {
108
+ if (ingestReportOutcome(report) === 'error') {
110
109
  return false;
111
110
  }
112
111
  const counts = savedMemoryCountsForReport(report);
@@ -306,17 +305,6 @@ async function commitSetupConfigChanges(projectDir) {
306
305
  const project = await loadKtxProject({ projectDir });
307
306
  await project.git.commitFile('ktx.yaml', 'setup: update KTX project config', 'ktx setup', 'setup@ktx.local');
308
307
  }
309
- const KTX_SETUP_SCAFFOLD_PATHS = ['ktx.yaml', '.ktx', 'wiki', 'semantic-layer', 'raw-sources', '.git'];
310
- async function cleanupCreatedProjectScaffold(cleanup) {
311
- if (!cleanup) {
312
- return;
313
- }
314
- if (cleanup.kind === 'remove-project-dir') {
315
- await rm(cleanup.projectDir, { recursive: true, force: true });
316
- return;
317
- }
318
- await Promise.all(KTX_SETUP_SCAFFOLD_PATHS.map((relativePath) => rm(join(cleanup.projectDir, relativePath), { recursive: true, force: true })));
319
- }
320
308
  export async function runKtxSetup(args, io, deps = {}) {
321
309
  try {
322
310
  return await runKtxSetupInner(args, io, deps);
@@ -579,7 +567,6 @@ async function runKtxSetupInner(args, io, deps = {}) {
579
567
  cliVersion: args.cliVersion,
580
568
  });
581
569
  if (stepResult.status === 'failed') {
582
- await cleanupCreatedProjectScaffold(projectResult.createdProjectCleanup);
583
570
  return 1;
584
571
  }
585
572
  if (stepResult.status === 'missing-input') {
@@ -28,7 +28,12 @@ You have access to KTX MCP tools for data discovery, semantic-layer analysis, ra
28
28
  - Read entity details before writing SQL against an unfamiliar table. Do not assume column names.
29
29
  - Treat `sql_execution` as read-only. Writes are rejected by the server.
30
30
  - Validate value mentions with `dictionary_search` instead of guessing case or spelling. Treat a `dictionary_search` miss as non-authoritative. The index is built from profile-sampled values, so a missing value may simply have been outside the sample. Follow up with `sql_execution` against the most plausible columns before concluding the value is absent.
31
- - When `connection_list` shows multiple connections, pass an explicit `connectionId` to every tool that takes one and where user intent pins a specific warehouse. Required: `entity_details`, `sl_read_source`, and `sql_execution`. Required when user intent is warehouse-specific, including wording like "in our warehouse" or "this warehouse": `memory_ingest`; without `connectionId`, the memory agent cannot update the semantic layer and the knowledge lands as wiki-only. Pass `connectionId` when intent pins a warehouse, otherwise omit for unscoped discovery: `sl_query`, `discover_data`, and `dictionary_search`. Never pass `connectionId` to `connection_list`, `wiki_search`, `wiki_read`, or `memory_ingest_status`. If intent is ambiguous for a required-or-scoped tool, ask the user which warehouse before calling.
31
+ - `connectionId` scoping when `connection_list` shows multiple connections:
32
+ - Always pass it: `entity_details`, `sl_read_source`, `sql_execution`.
33
+ - Pass it when intent pins a warehouse, otherwise omit for unscoped discovery: `sl_query`, `discover_data`, `dictionary_search`.
34
+ - `memory_ingest`: pass it for warehouse-specific knowledge (e.g. "in our warehouse"); without it the memory lands as wiki-only and cannot update the semantic layer.
35
+ - Never pass it: `connection_list`, `wiki_search`, `wiki_read`, `memory_ingest_status`.
36
+ - If scoping is required but intent is ambiguous, ask which warehouse before calling.
32
37
  - Show compact result tables for small outputs. For broad results, summarize the top findings and mention the applied limit.
33
38
  - Ask a concise clarification only when the metric, date range, entity, or grain is genuinely ambiguous and cannot be inferred from context.
34
39
  </rules>
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') {
@@ -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
  }
@@ -69,7 +69,6 @@ export declare const telemetryEventSchemas: {
69
69
  runtime: "runtime";
70
70
  agents: "agents";
71
71
  secrets: "secrets";
72
- "database-context-depth": "database-context-depth";
73
72
  "demo-tour": "demo-tour";
74
73
  }>;
75
74
  outcome: z.ZodEnum<{
@@ -299,7 +298,9 @@ export declare const telemetryEventSchemas: {
299
298
  }>;
300
299
  durationMs: z.ZodNumber;
301
300
  errorClass: z.ZodOptional<z.ZodString>;
302
- sampleRate: z.ZodLiteral<0.1>;
301
+ sampleRate: z.ZodLiteral<1>;
302
+ mcpClientName: z.ZodOptional<z.ZodString>;
303
+ mcpClientVersion: z.ZodOptional<z.ZodString>;
303
304
  }, z.core.$strict>;
304
305
  readonly daemon_started: z.ZodObject<{
305
306
  cliVersion: z.ZodString;
@@ -433,7 +434,7 @@ export declare const telemetryEventCatalog: readonly [{
433
434
  }, {
434
435
  readonly name: "mcp_request_completed";
435
436
  readonly description: "Emitted for sampled MCP tool requests.";
436
- readonly fields: readonly ["toolName", "outcome", "durationMs", "errorClass", "sampleRate"];
437
+ readonly fields: readonly ["toolName", "outcome", "durationMs", "errorClass", "sampleRate", "mcpClientName", "mcpClientVersion"];
437
438
  }, {
438
439
  readonly name: "daemon_started";
439
440
  readonly description: "Emitted when the long-lived ktx-daemon HTTP server starts.";
@@ -33,7 +33,6 @@ const setupStepSchema = telemetryCommonEnvelopeSchema
33
33
  'embeddings',
34
34
  'secrets',
35
35
  'databases',
36
- 'database-context-depth',
37
36
  'sources',
38
37
  'context',
39
38
  'agents',
@@ -141,7 +140,12 @@ const mcpRequestCompletedSchema = telemetryCommonEnvelopeSchema
141
140
  outcome: outcomeSchema,
142
141
  durationMs: z.number().nonnegative(),
143
142
  errorClass: z.string().optional(),
144
- sampleRate: z.literal(0.1),
143
+ sampleRate: z.literal(1),
144
+ // Raw, client-tool-controlled identity from the MCP initialize handshake
145
+ // (clientInfo.name/version). Optional: clients may omit clientInfo. Stored
146
+ // verbatim — normalize the free-form names at query time, not at write time.
147
+ mcpClientName: z.string().optional(),
148
+ mcpClientVersion: z.string().optional(),
145
149
  })
146
150
  .strict();
147
151
  const daemonStartedSchema = telemetryCommonEnvelopeSchema
@@ -304,7 +308,7 @@ export const telemetryEventCatalog = [
304
308
  {
305
309
  name: 'mcp_request_completed',
306
310
  description: 'Emitted for sampled MCP tool requests.',
307
- fields: ['toolName', 'outcome', 'durationMs', 'errorClass', 'sampleRate'],
311
+ fields: ['toolName', 'outcome', 'durationMs', 'errorClass', 'sampleRate', 'mcpClientName', 'mcpClientVersion'],
308
312
  },
309
313
  {
310
314
  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;
@@ -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. Minting one means showing the one-time opt-out notice, so
62
+ // first-run creation requires an interactive surface; a headless first run
63
+ // stays disabled and defers enablement until the next interactive run.
64
+ if (options.stdoutIsTTY !== true) {
65
+ return { enabled: false, createdFile: false, noticeShown: false, path };
66
+ }
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>;
@@ -26,7 +26,11 @@ export async function showTelemetryNoticeIfNeeded(io, packageInfo) {
26
26
  });
27
27
  }
28
28
  const emittedProjectSnapshots = new Set();
29
- const MCP_SAMPLE_RATE = 0.1;
29
+ // MCP tool calls are captured at full rate while ktx is early-stage: at current
30
+ // install counts any sampling below 1.0 yields too few events to be useful, and
31
+ // the recorded sampleRate lets us dial this down (and reweight history) once
32
+ // per-session call volume justifies it.
33
+ const MCP_SAMPLE_RATE = 1;
30
34
  let mcpSampled;
31
35
  function telemetryDebugEnabled() {
32
36
  return process.env.KTX_TELEMETRY_DEBUG === '1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaelio/ktx",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Standalone ktx context layer for data agents",
5
5
  "type": "module",
6
6
  "engines": {
@@ -28,36 +28,36 @@
28
28
  "provenance": true
29
29
  },
30
30
  "dependencies": {
31
- "@ai-sdk/anthropic": "3.0.77",
32
- "@ai-sdk/devtools": "0.0.17",
33
- "@ai-sdk/google-vertex": "^4.0.128",
34
- "@anthropic-ai/claude-agent-sdk": "0.3.142",
31
+ "@ai-sdk/anthropic": "3.0.78",
32
+ "@ai-sdk/devtools": "0.0.18",
33
+ "@ai-sdk/google-vertex": "^4.0.134",
34
+ "@anthropic-ai/claude-agent-sdk": "0.3.146",
35
35
  "@clack/prompts": "1.4.0",
36
- "@clickhouse/client": "^1.18.4",
36
+ "@clickhouse/client": "^1.18.5",
37
37
  "@commander-js/extra-typings": "14.0.0",
38
38
  "@google-cloud/bigquery": "^8.3.1",
39
39
  "@looker/sdk": "^26.8.0",
40
40
  "@looker/sdk-node": "^26.8.0",
41
41
  "@looker/sdk-rtl": "^21.6.5",
42
42
  "@modelcontextprotocol/sdk": "^1.29.0",
43
- "@notionhq/client": "^5.21.0",
44
- "ai": "^6.0.180",
43
+ "@notionhq/client": "^5.22.0",
44
+ "ai": "^6.0.188",
45
45
  "better-sqlite3": "^12.10.0",
46
46
  "commander": "14.0.3",
47
- "fflate": "^0.8.2",
47
+ "fflate": "^0.8.3",
48
48
  "handlebars": "^4.7.9",
49
- "ink": "^7.0.2",
49
+ "ink": "^7.0.3",
50
50
  "lookml-parser": "7.1.0",
51
51
  "minimatch": "^10.2.5",
52
- "mssql": "^12.5.2",
52
+ "mssql": "^12.5.4",
53
53
  "mysql2": "^3.22.3",
54
- "openai": "^6.37.0",
54
+ "openai": "^6.38.0",
55
55
  "p-limit": "^7.3.0",
56
- "pg": "^8.20.0",
57
- "posthog-node": "^5.0.0",
56
+ "pg": "^8.21.0",
57
+ "posthog-node": "^5.34.9",
58
58
  "react": "^19.2.6",
59
59
  "simple-git": "3.36.0",
60
- "snowflake-sdk": "^2.4.1",
60
+ "snowflake-sdk": "^2.4.2",
61
61
  "yaml": "^2.9.0",
62
62
  "zod": "^4.4.3"
63
63
  },
@@ -66,25 +66,25 @@
66
66
  "@electric-sql/pglite-socket": "^0.1.5",
67
67
  "@types/better-sqlite3": "^7.6.13",
68
68
  "@types/mssql": "^12.3.0",
69
- "@types/node": "^25.7.0",
69
+ "@types/node": "^25.9.1",
70
70
  "@types/pg": "^8.20.0",
71
- "@types/react": "^19.2.14",
72
- "@vitest/coverage-v8": "^4.1.6",
71
+ "@types/react": "^19.2.15",
72
+ "@vitest/coverage-v8": "^4.1.7",
73
73
  "ajv": "8.20.0",
74
74
  "ink-testing-library": "^4.0.0",
75
75
  "typescript": "^6.0.3",
76
- "vitest": "^4.1.6"
76
+ "vitest": "^4.1.7"
77
77
  },
78
78
  "license": "Apache-2.0",
79
79
  "repository": {
80
80
  "type": "git",
81
- "url": "https://github.com/Kaelio/ktx",
81
+ "url": "https://github.com/Kaelio/ktx-ai-data-agents-context",
82
82
  "directory": "packages/cli"
83
83
  },
84
84
  "bugs": {
85
- "url": "https://github.com/Kaelio/ktx/issues"
85
+ "url": "https://github.com/Kaelio/ktx-ai-data-agents-context/issues"
86
86
  },
87
- "homepage": "https://github.com/Kaelio/ktx#readme",
87
+ "homepage": "https://github.com/Kaelio/ktx-ai-data-agents-context#readme",
88
88
  "scripts": {
89
89
  "assets:demo": "node scripts/build-demo-assets.mjs",
90
90
  "build": "tsc -p tsconfig.json && node dist/telemetry/schema-writer.js src/telemetry/events.schema.json ../../python/ktx-daemon/src/ktx_daemon/telemetry/events.schema.json && node scripts/copy-runtime-assets.mjs && node ../../scripts/prepare-cli-bin.mjs",
@@ -1,8 +0,0 @@
1
- import type { KtxProjectConfig, KtxProjectConnectionConfig } from './context/project/config.js';
2
- export type KtxDatabaseContextDepth = 'fast' | 'deep';
3
- export declare function normalizeConnectionDriver(connection: KtxProjectConnectionConfig): string;
4
- export declare function isDatabaseDriver(driver: string): boolean;
5
- export declare function databaseContextDepth(connection: KtxProjectConnectionConfig): KtxDatabaseContextDepth | undefined;
6
- export declare function withDatabaseContextDepth(connection: KtxProjectConnectionConfig, depth: KtxDatabaseContextDepth): KtxProjectConnectionConfig;
7
- export declare function deepReadinessGaps(config: KtxProjectConfig): string[];
8
- export declare function recommendedDatabaseContextDepth(config: KtxProjectConfig): KtxDatabaseContextDepth;
@@ -1,56 +0,0 @@
1
- const KTX_DATABASE_DRIVER_IDS = new Set([
2
- 'sqlite',
3
- 'postgres',
4
- 'mysql',
5
- 'clickhouse',
6
- 'sqlserver',
7
- 'bigquery',
8
- 'snowflake',
9
- ]);
10
- export function normalizeConnectionDriver(connection) {
11
- return String(connection.driver ?? '')
12
- .trim()
13
- .toLowerCase();
14
- }
15
- export function isDatabaseDriver(driver) {
16
- return KTX_DATABASE_DRIVER_IDS.has(driver.trim().toLowerCase());
17
- }
18
- function connectionContextRecord(connection) {
19
- const context = connection.context;
20
- return typeof context === 'object' && context !== null && !Array.isArray(context)
21
- ? context
22
- : {};
23
- }
24
- export function databaseContextDepth(connection) {
25
- const depth = connectionContextRecord(connection).depth;
26
- return depth === 'fast' || depth === 'deep' ? depth : undefined;
27
- }
28
- export function withDatabaseContextDepth(connection, depth) {
29
- return {
30
- ...connection,
31
- context: {
32
- ...connectionContextRecord(connection),
33
- depth,
34
- },
35
- };
36
- }
37
- export function deepReadinessGaps(config) {
38
- const gaps = [];
39
- if (config.llm.provider.backend === 'none' || !config.llm.models.default) {
40
- gaps.push('model configuration');
41
- }
42
- if (config.scan.enrichment.mode !== 'llm') {
43
- gaps.push('scan enrichment mode');
44
- }
45
- const embeddings = config.scan.enrichment.embeddings;
46
- if (!embeddings ||
47
- embeddings.backend === 'none' ||
48
- !embeddings.model ||
49
- embeddings.dimensions <= 0) {
50
- gaps.push('scan embeddings');
51
- }
52
- return gaps;
53
- }
54
- export function recommendedDatabaseContextDepth(config) {
55
- return deepReadinessGaps(config).length === 0 ? 'deep' : 'fast';
56
- }
@@ -1,23 +0,0 @@
1
- import { type KtxLocalProject } from './context/project/project.js';
2
- import { type KtxProjectConnectionConfig } from './context/project/config.js';
3
- import type { KtxSetupPromptOption } from './setup-prompts.js';
4
- export interface KtxSetupDatabaseContextDepthArgs {
5
- inputMode: 'auto' | 'disabled';
6
- }
7
- export interface KtxSetupDatabaseContextDepthPromptAdapter {
8
- select(options: {
9
- message: string;
10
- options: KtxSetupPromptOption[];
11
- }): Promise<string>;
12
- }
13
- export declare function ensureSetupDatabaseContextDepths(input: {
14
- project: KtxLocalProject;
15
- args: KtxSetupDatabaseContextDepthArgs;
16
- prompts: KtxSetupDatabaseContextDepthPromptAdapter;
17
- }): Promise<KtxLocalProject | 'back'>;
18
- export declare function applySetupDatabaseContextDepth(input: {
19
- project: KtxLocalProject;
20
- connection: KtxProjectConnectionConfig;
21
- args: KtxSetupDatabaseContextDepthArgs;
22
- prompts: KtxSetupDatabaseContextDepthPromptAdapter;
23
- }): Promise<KtxProjectConnectionConfig | 'back'>;