@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.
- package/assets/python/{kaelio_ktx-0.6.0-py3-none-any.whl → kaelio_ktx-0.8.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli-program.js +7 -0
- package/dist/command-schemas.d.ts +1 -1
- package/dist/command-tree.js +5 -1
- package/dist/commands/completion-commands.d.ts +3 -0
- package/dist/commands/completion-commands.js +38 -0
- package/dist/commands/ingest-commands.js +0 -4
- package/dist/commands/knowledge-commands.js +15 -2
- package/dist/commands/setup-commands.js +2 -2
- package/dist/commands/sl-commands.js +19 -7
- package/dist/completion/complete-engine.d.ts +19 -0
- package/dist/completion/complete-engine.js +128 -0
- package/dist/completion/completion-scripts.d.ts +1 -0
- package/dist/completion/completion-scripts.js +36 -0
- package/dist/completion/dynamic-candidates.d.ts +6 -0
- package/dist/completion/dynamic-candidates.js +98 -0
- package/dist/connection-drivers.d.ts +3 -0
- package/dist/connection-drivers.js +17 -0
- package/dist/context/ingest/ingest-bundle.runner.d.ts +8 -0
- package/dist/context/ingest/ingest-bundle.runner.js +72 -15
- package/dist/context/ingest/ingest-profile.d.ts +102 -0
- package/dist/context/ingest/ingest-profile.js +306 -0
- package/dist/context/ingest/isolated-diff/work-unit-executor.js +25 -2
- package/dist/context/ingest/local-bundle-runtime.js +1 -0
- package/dist/context/ingest/local-ingest.d.ts +1 -1
- package/dist/context/ingest/local-ingest.js +6 -4
- package/dist/context/ingest/memory-flow/events.js +2 -1
- package/dist/context/ingest/ports.d.ts +2 -0
- package/dist/context/ingest/reports.d.ts +3 -0
- package/dist/context/ingest/reports.js +10 -0
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +3 -1
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -0
- package/dist/context/ingest/stages/stage-4-reconciliation.d.ts +2 -1
- package/dist/context/ingest/stages/stage-4-reconciliation.js +1 -1
- package/dist/context/ingest/tools/tool-call-logger.d.ts +6 -0
- package/dist/context/ingest/tools/tool-call-logger.js +36 -1
- package/dist/context/llm/ai-sdk-runtime.js +32 -3
- package/dist/context/llm/claude-code-runtime.js +51 -3
- package/dist/context/llm/runtime-port.d.ts +25 -0
- package/dist/context/mcp/context-tools.d.ts +2 -1
- package/dist/context/mcp/context-tools.js +82 -15
- package/dist/context/mcp/server.js +4 -0
- package/dist/context/mcp/types.d.ts +15 -1
- package/dist/context/project/config.d.ts +1 -0
- package/dist/context/project/config.js +4 -0
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/search/discover.js +4 -3
- package/dist/context/sl/local-sl.d.ts +15 -0
- package/dist/context/sl/local-sl.js +30 -0
- package/dist/context/wiki/local-knowledge.d.ts +10 -0
- package/dist/context/wiki/local-knowledge.js +22 -0
- package/dist/context-build-view.d.ts +0 -3
- package/dist/context-build-view.js +1 -7
- package/dist/ingest.js +7 -10
- package/dist/knowledge.d.ts +5 -0
- package/dist/knowledge.js +10 -1
- package/dist/public-ingest-copy.js +1 -1
- package/dist/public-ingest.d.ts +0 -7
- package/dist/public-ingest.js +20 -34
- package/dist/setup-context.js +6 -38
- package/dist/setup-databases.js +13 -82
- package/dist/setup-project.d.ts +0 -8
- package/dist/setup-project.js +3 -27
- package/dist/setup-sources.js +33 -5
- package/dist/setup.js +3 -16
- package/dist/skills/analytics/SKILL.md +6 -1
- package/dist/sl.d.ts +6 -1
- package/dist/sl.js +32 -8
- package/dist/telemetry/emitter.js +1 -1
- package/dist/telemetry/events.d.ts +4 -3
- package/dist/telemetry/events.js +7 -3
- package/dist/telemetry/identity.d.ts +1 -1
- package/dist/telemetry/identity.js +13 -10
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.js +5 -1
- package/package.json +22 -22
- package/dist/ingest-depth.d.ts +0 -8
- package/dist/ingest-depth.js +0 -56
- package/dist/setup-database-context-depth.d.ts +0 -23
- package/dist/setup-database-context-depth.js +0 -84
package/dist/setup-sources.js
CHANGED
|
@@ -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.
|
|
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.
|
|
1092
|
+
existingRef: currentState.sourceAuthTokenRef,
|
|
1068
1093
|
});
|
|
1069
1094
|
if (ref === 'back')
|
|
1070
1095
|
return 'back';
|
|
1071
|
-
currentState.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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,
|
|
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
|
|
155
|
+
const resolved = await resolveLocalSlSource(project, {
|
|
137
156
|
connectionId: args.connectionId,
|
|
138
157
|
sourceName: args.sourceName,
|
|
139
158
|
});
|
|
140
|
-
if (
|
|
141
|
-
throw new Error(
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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: ${
|
|
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<
|
|
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.";
|
package/dist/telemetry/events.js
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
|
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)
|
|
45
|
-
|
|
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():
|
|
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>;
|
package/dist/telemetry/index.js
CHANGED
|
@@ -26,7 +26,11 @@ export async function showTelemetryNoticeIfNeeded(io, packageInfo) {
|
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
const emittedProjectSnapshots = new Set();
|
|
29
|
-
|
|
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.
|
|
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.
|
|
32
|
-
"@ai-sdk/devtools": "0.0.
|
|
33
|
-
"@ai-sdk/google-vertex": "^4.0.
|
|
34
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
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.
|
|
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.
|
|
44
|
-
"ai": "^6.0.
|
|
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.
|
|
47
|
+
"fflate": "^0.8.3",
|
|
48
48
|
"handlebars": "^4.7.9",
|
|
49
|
-
"ink": "^7.0.
|
|
49
|
+
"ink": "^7.0.3",
|
|
50
50
|
"lookml-parser": "7.1.0",
|
|
51
51
|
"minimatch": "^10.2.5",
|
|
52
|
-
"mssql": "^12.5.
|
|
52
|
+
"mssql": "^12.5.4",
|
|
53
53
|
"mysql2": "^3.22.3",
|
|
54
|
-
"openai": "^6.
|
|
54
|
+
"openai": "^6.38.0",
|
|
55
55
|
"p-limit": "^7.3.0",
|
|
56
|
-
"pg": "^8.
|
|
57
|
-
"posthog-node": "^5.
|
|
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.
|
|
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.
|
|
69
|
+
"@types/node": "^25.9.1",
|
|
70
70
|
"@types/pg": "^8.20.0",
|
|
71
|
-
"@types/react": "^19.2.
|
|
72
|
-
"@vitest/coverage-v8": "^4.1.
|
|
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.
|
|
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",
|
package/dist/ingest-depth.d.ts
DELETED
|
@@ -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;
|
package/dist/ingest-depth.js
DELETED
|
@@ -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'>;
|