@kaelio/ktx 0.11.0 → 0.13.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.13.0-py3-none-any.whl +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/admin.js +1 -1
- package/dist/clack.d.ts +16 -0
- package/dist/clack.js +37 -6
- package/dist/claude-code-prompt-caching.js +1 -1
- package/dist/cli-program.js +3 -3
- package/dist/cli-runtime.js +2 -2
- package/dist/commands/connection-commands.js +1 -1
- package/dist/commands/ingest-commands.js +4 -4
- package/dist/commands/mcp-commands.js +12 -12
- package/dist/commands/runtime-commands.js +4 -4
- package/dist/commands/setup-commands.js +19 -5
- package/dist/commands/sl-commands.js +1 -1
- package/dist/commands/sql-commands.js +1 -1
- package/dist/commands/status-commands.js +1 -1
- package/dist/connection.js +15 -3
- package/dist/connectors/bigquery/connector.js +1 -14
- package/dist/connectors/clickhouse/connector.js +2 -16
- package/dist/connectors/duckdb/federated-attach.d.ts +7 -0
- package/dist/connectors/duckdb/federated-attach.js +86 -0
- package/dist/connectors/duckdb/federated-executor.d.ts +5 -0
- package/dist/connectors/duckdb/federated-executor.js +59 -0
- package/dist/connectors/mysql/connector.js +2 -16
- package/dist/connectors/postgres/connector.js +1 -14
- package/dist/connectors/shared/string-reference.d.ts +6 -0
- package/dist/connectors/shared/string-reference.js +19 -0
- package/dist/connectors/snowflake/connector.d.ts +1 -1
- package/dist/connectors/snowflake/connector.js +1 -14
- package/dist/connectors/sqlite/connector.js +2 -25
- package/dist/connectors/sqlserver/connector.js +4 -17
- package/dist/context/connections/connection-type.d.ts +1 -1
- package/dist/context/connections/federation.d.ts +33 -0
- package/dist/context/connections/federation.js +51 -0
- package/dist/context/connections/local-warehouse-descriptor.d.ts +2 -0
- package/dist/context/connections/project-sql-executor.d.ts +18 -0
- package/dist/context/connections/project-sql-executor.js +39 -0
- package/dist/context/connections/query-executor.d.ts +2 -2
- package/dist/context/connections/read-only-sql.d.ts +1 -0
- package/dist/context/connections/read-only-sql.js +119 -4
- package/dist/context/connections/resolve-connection.d.ts +12 -0
- package/dist/context/connections/resolve-connection.js +37 -0
- package/dist/context/core/git-env.d.ts +4 -0
- package/dist/context/core/git-env.js +5 -1
- package/dist/context/core/git.service.d.ts +23 -0
- package/dist/context/core/git.service.js +71 -8
- package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
- package/dist/context/ingest/adapters/live-database/manifest.d.ts +3 -0
- package/dist/context/ingest/adapters/live-database/manifest.js +19 -11
- package/dist/context/ingest/adapters/looker/client.js +7 -2
- package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
- package/dist/context/ingest/adapters/looker/factory.js +9 -0
- package/dist/context/ingest/adapters/looker/mapping.js +1 -1
- package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.js +1 -1
- package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
- package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
- package/dist/context/ingest/artifact-gates.d.ts +2 -6
- package/dist/context/ingest/artifact-gates.js +5 -47
- package/dist/context/ingest/constrained-repair.d.ts +55 -0
- package/dist/context/ingest/constrained-repair.js +167 -0
- package/dist/context/ingest/final-gate-repair.d.ts +9 -11
- package/dist/context/ingest/final-gate-repair.js +40 -128
- package/dist/context/ingest/finalization-scope.d.ts +1 -1
- package/dist/context/ingest/finalization-scope.js +15 -15
- package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
- package/dist/context/ingest/ingest-bundle.runner.js +101 -67
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
- package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
- package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
- package/dist/context/ingest/local-bundle-runtime.js +9 -10
- package/dist/context/ingest/local-ingest.d.ts +2 -0
- package/dist/context/ingest/local-ingest.js +2 -0
- package/dist/context/ingest/memory-flow/view-model.js +1 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
- package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
- package/dist/context/llm/ai-sdk-runtime.js +2 -2
- package/dist/context/llm/claude-code-runtime.js +19 -3
- package/dist/context/llm/local-config.js +1 -1
- package/dist/context/llm/runtime-tools.js +2 -2
- package/dist/context/mcp/context-tools.js +33 -8
- package/dist/context/mcp/local-project-ports.js +63 -89
- package/dist/context/mcp/types.d.ts +2 -0
- package/dist/context/memory/local-memory.js +4 -1
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +11 -4
- package/dist/context/project/config.js +85 -30
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/project/mappings-yaml-schema.js +2 -2
- package/dist/context/project/project.js +12 -4
- package/dist/context/scan/description-generation.js +4 -4
- package/dist/context/scan/local-enrichment-artifacts.js +33 -4
- package/dist/context/scan/local-scan.js +2 -2
- package/dist/context/scan/local-structural-artifacts.js +5 -5
- package/dist/context/scan/relationship-benchmark-report.js +1 -1
- package/dist/context/scan/relationship-discovery.js +3 -3
- package/dist/context/scan/relationship-llm-proposal.js +3 -3
- package/dist/context/sl/local-query.js +31 -44
- package/dist/context/sl/local-sl.d.ts +0 -8
- package/dist/context/sl/local-sl.js +71 -70
- package/dist/context/sl/semantic-layer.service.d.ts +25 -8
- package/dist/context/sl/semantic-layer.service.js +109 -56
- package/dist/context/sl/source-files.d.ts +48 -0
- package/dist/context/sl/source-files.js +138 -0
- package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
- package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
- package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
- package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
- package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
- package/dist/context/sql-analysis/dialect.d.ts +2 -0
- package/dist/context/sql-analysis/dialect.js +20 -0
- package/dist/context/tools/base-tool.d.ts +6 -19
- package/dist/context/tools/base-tool.js +0 -14
- package/dist/context-build-view.js +5 -5
- package/dist/database-tree-picker.js +18 -3
- package/dist/demo-assets.js +0 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -23
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +44 -0
- package/dist/ingest-query-executor.d.ts +2 -0
- package/dist/ingest-query-executor.js +8 -22
- package/dist/ingest.d.ts +1 -1
- package/dist/ingest.js +8 -2
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/io/tty.d.ts +8 -0
- package/dist/io/tty.js +16 -0
- package/dist/llm/embedding-health.js +1 -1
- package/dist/llm/embedding-provider.js +3 -3
- package/dist/llm/model-provider.js +1 -1
- package/dist/local-adapters.d.ts +1 -0
- package/dist/local-adapters.js +2 -2
- package/dist/local-scan-connectors.js +1 -1
- package/dist/managed-local-embeddings.js +17 -8
- package/dist/managed-mcp-daemon.js +3 -3
- package/dist/managed-python-command.d.ts +7 -0
- package/dist/managed-python-command.js +34 -8
- package/dist/managed-python-daemon.js +2 -2
- package/dist/managed-python-http.js +3 -3
- package/dist/managed-python-runtime.d.ts +30 -1
- package/dist/managed-python-runtime.js +134 -18
- package/dist/managed-uv-release.d.ts +7 -0
- package/dist/managed-uv-release.js +11 -0
- package/dist/mcp-http-server.js +4 -4
- package/dist/mcp-server-factory.js +3 -3
- package/dist/mcp-stdio-server.js +1 -1
- package/dist/memory-flow-hud.js +2 -2
- package/dist/next-steps.js +2 -2
- package/dist/prompt-navigation.d.ts +17 -0
- package/dist/prompt-navigation.js +49 -3
- package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
- package/dist/prompts/memory_agent_external_ingest.md +2 -2
- package/dist/public-ingest-copy.js +1 -1
- package/dist/public-ingest.js +3 -3
- package/dist/release-version.js +1 -1
- package/dist/runtime-requirements.js +1 -1
- package/dist/runtime.js +9 -9
- package/dist/scan.js +1 -1
- package/dist/setup-agents.d.ts +21 -15
- package/dist/setup-agents.js +143 -66
- package/dist/setup-banner.d.ts +20 -0
- package/dist/setup-banner.js +39 -0
- package/dist/setup-context.js +24 -15
- package/dist/setup-databases.d.ts +3 -0
- package/dist/setup-databases.js +47 -59
- package/dist/setup-demo-tour.js +12 -8
- package/dist/setup-embeddings.js +9 -9
- package/dist/setup-interrupt.js +1 -1
- package/dist/setup-models.d.ts +4 -1
- package/dist/setup-models.js +54 -28
- package/dist/setup-project.js +29 -5
- package/dist/setup-prompts.js +16 -1
- package/dist/setup-ready-menu.js +1 -1
- package/dist/setup-sources.js +28 -12
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +14 -13
- package/dist/skills/analytics/SKILL.md +3 -3
- package/dist/skills/dbt_ingest/SKILL.md +3 -3
- package/dist/skills/looker_ingest/SKILL.md +3 -3
- package/dist/skills/lookml_ingest/SKILL.md +7 -7
- package/dist/skills/metabase_ingest/SKILL.md +4 -4
- package/dist/skills/metricflow_ingest/SKILL.md +15 -15
- package/dist/skills/notion_synthesize/SKILL.md +1 -1
- package/dist/skills/sl/SKILL.md +3 -3
- package/dist/skills/sl_capture/SKILL.md +1 -1
- package/dist/skills/wiki_capture/SKILL.md +1 -1
- package/dist/source-mapping.js +1 -1
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +35 -53
- package/dist/startup-profile.js +1 -1
- package/dist/status-project.d.ts +0 -2
- package/dist/status-project.js +4 -6
- package/dist/telemetry/events.d.ts +3 -2
- package/dist/telemetry/events.js +11 -1
- package/dist/telemetry/exception.js +14 -0
- package/dist/text-ingest.js +1 -1
- package/dist/tree-picker-tui.d.ts +0 -1
- package/dist/tree-picker-tui.js +2 -3
- package/package.json +2 -1
- package/assets/python/kaelio_ktx-0.11.0-py3-none-any.whl +0 -0
package/dist/setup-models.js
CHANGED
|
@@ -21,10 +21,28 @@ const ESC = String.fromCharCode(0x1b);
|
|
|
21
21
|
function yellow(text) {
|
|
22
22
|
return `${ESC}[33m${text}${ESC}[39m`;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
// Single source of truth for the LLM backends a user can pick during setup.
|
|
25
|
+
// The CLI arg parser, the interactive prompt, and the missing-backend error all
|
|
26
|
+
// derive from this list, so adding a backend is one edit. Order is the prompt's
|
|
27
|
+
// preference order (subscription backends first).
|
|
28
|
+
const KTX_SETUP_LLM_BACKENDS = ['claude-code', 'codex', 'anthropic', 'vertex'];
|
|
29
|
+
/** Validates a raw CLI or prompt value against the setup-selectable LLM backends. */
|
|
30
|
+
export function isKtxSetupLlmBackend(value) {
|
|
31
|
+
return KTX_SETUP_LLM_BACKENDS.some((backend) => backend === value);
|
|
32
|
+
}
|
|
33
|
+
// Display labels for the interactive provider prompt. The Record key type forces
|
|
34
|
+
// every backend to carry a label, so adding one to KTX_SETUP_LLM_BACKENDS fails
|
|
35
|
+
// to compile until its prompt option exists here.
|
|
36
|
+
const KTX_SETUP_LLM_BACKEND_LABELS = {
|
|
37
|
+
'claude-code': 'Claude subscription (Pro/Max)',
|
|
38
|
+
codex: 'Codex subscription',
|
|
39
|
+
anthropic: 'Anthropic API key',
|
|
40
|
+
vertex: 'Google Vertex AI for Anthropic Claude',
|
|
41
|
+
};
|
|
42
|
+
const ANTHROPIC_CREDENTIAL_PROMPT_CONTEXT = 'ktx uses the key to verify Anthropic model access now and to run ingest agents that turn schemas, SQL, ' +
|
|
25
43
|
'BI metadata, and docs into semantic-layer sources and wiki context. ktx.yaml stores an env: or file: ' +
|
|
26
44
|
'reference, not the raw key.';
|
|
27
|
-
const VERTEX_PROJECT_PROMPT_CONTEXT = '
|
|
45
|
+
const VERTEX_PROJECT_PROMPT_CONTEXT = 'ktx stores the selected Google Cloud project ID in ktx.yaml and uses Application Default Credentials for ' +
|
|
28
46
|
'access. Project visibility depends on the signed-in Google account and organization permissions.';
|
|
29
47
|
const DEFAULT_VERTEX_LOCATION = 'us-east5';
|
|
30
48
|
const ANTHROPIC_PRESET = {
|
|
@@ -61,6 +79,14 @@ function presetForBackend(backend) {
|
|
|
61
79
|
return MODEL_PRESETS[backend];
|
|
62
80
|
}
|
|
63
81
|
const execFileAsync = promisify(execFile);
|
|
82
|
+
// Non-interactive setup cannot pick a provider safely: every backend needs
|
|
83
|
+
// something the user must supply (an API key, gcloud ADC, or a logged-in local
|
|
84
|
+
// CLI), so there is no credential-free default to fall back to. Name the hidden
|
|
85
|
+
// --llm-backend flag and its choices here instead, mirroring how the other
|
|
86
|
+
// automation errors guide users to the flag they need.
|
|
87
|
+
const MISSING_LLM_BACKEND_MESSAGE = `Missing LLM backend: pass --llm-backend with one of ${KTX_SETUP_LLM_BACKENDS.join(', ')}. ` +
|
|
88
|
+
'claude-code and codex use local CLI authentication; anthropic also needs --anthropic-api-key-env or ' +
|
|
89
|
+
'--anthropic-api-key-file, and vertex also needs --vertex-project.';
|
|
64
90
|
function createPromptAdapter() {
|
|
65
91
|
return createKtxSetupPromptAdapter({ selectCancelValue: 'back' });
|
|
66
92
|
}
|
|
@@ -171,10 +197,10 @@ function buildVertexHealthConfig(vertex, model) {
|
|
|
171
197
|
promptCaching: { enabled: true, vertexFallbackTo5m: true },
|
|
172
198
|
};
|
|
173
199
|
}
|
|
174
|
-
function
|
|
200
|
+
function llmCheckStartText(provider, model) {
|
|
175
201
|
return `Checking ${provider} LLM (${model}).`;
|
|
176
202
|
}
|
|
177
|
-
function
|
|
203
|
+
function startLlmCheckProgress(spinner, message) {
|
|
178
204
|
spinner.start(message);
|
|
179
205
|
return {
|
|
180
206
|
succeed(msg) {
|
|
@@ -185,23 +211,23 @@ function startLlmHealthCheckProgress(spinner, message) {
|
|
|
185
211
|
},
|
|
186
212
|
};
|
|
187
213
|
}
|
|
188
|
-
async function
|
|
189
|
-
const progress =
|
|
190
|
-
let
|
|
214
|
+
async function validateModelWithProgress(provider, model, deps, run) {
|
|
215
|
+
const progress = startLlmCheckProgress((deps.spinner ?? createClackSpinner)(), llmCheckStartText(provider, model));
|
|
216
|
+
let result;
|
|
191
217
|
try {
|
|
192
|
-
|
|
218
|
+
result = await run();
|
|
193
219
|
}
|
|
194
220
|
catch (error) {
|
|
195
221
|
progress.fail('LLM test failed');
|
|
196
222
|
throw error;
|
|
197
223
|
}
|
|
198
|
-
if (
|
|
224
|
+
if (result.ok) {
|
|
199
225
|
progress.succeed(`LLM test passed (${provider}, ${model})`);
|
|
200
226
|
}
|
|
201
227
|
else {
|
|
202
228
|
progress.fail('LLM test failed');
|
|
203
229
|
}
|
|
204
|
-
return
|
|
230
|
+
return result;
|
|
205
231
|
}
|
|
206
232
|
function formatVertexHealthFailure(message, vertex) {
|
|
207
233
|
const trimmed = message.trim() || 'unknown error';
|
|
@@ -248,7 +274,7 @@ async function chooseCredentialRef(args, io, deps) {
|
|
|
248
274
|
}
|
|
249
275
|
while (true) {
|
|
250
276
|
const choice = await prompts.select({
|
|
251
|
-
message: `How should
|
|
277
|
+
message: `How should ktx find your Anthropic API key?\n\n${ANTHROPIC_CREDENTIAL_PROMPT_CONTEXT}`,
|
|
252
278
|
options: [
|
|
253
279
|
{ value: 'paste', label: 'Paste a key and save it as a local secret file' },
|
|
254
280
|
{ value: 'env', label: 'Use ANTHROPIC_API_KEY from the environment' },
|
|
@@ -259,7 +285,7 @@ async function chooseCredentialRef(args, io, deps) {
|
|
|
259
285
|
return { status: 'back' };
|
|
260
286
|
}
|
|
261
287
|
if (choice === 'paste') {
|
|
262
|
-
io.stdout.write('│
|
|
288
|
+
io.stdout.write('│ ktx will save the key in .ktx/secrets/anthropic-api-key with local file permissions, then write a file: reference in ktx.yaml.\n');
|
|
263
289
|
const value = await prompts.password({ message: withTextInputNavigation('Anthropic API key') });
|
|
264
290
|
if (value === undefined) {
|
|
265
291
|
continue;
|
|
@@ -301,30 +327,30 @@ async function chooseBackend(args, io, deps) {
|
|
|
301
327
|
return { status: 'ready', backend: explicit, prompted: false };
|
|
302
328
|
}
|
|
303
329
|
if (args.inputMode === 'disabled') {
|
|
304
|
-
|
|
330
|
+
io.stderr.write(`${MISSING_LLM_BACKEND_MESSAGE}\n`);
|
|
331
|
+
return { status: 'missing-input' };
|
|
305
332
|
}
|
|
306
333
|
const prompts = deps.prompts ?? createPromptAdapter();
|
|
307
334
|
if (args.showPromptInstructions !== false) {
|
|
308
335
|
io.stdout.write('│ Use Up/Down to move, Enter to confirm the current selection, choose Back to return to the previous step, Ctrl+C to exit.\n');
|
|
309
336
|
}
|
|
310
337
|
const choice = await prompts.select({
|
|
311
|
-
message: 'Which LLM provider should
|
|
338
|
+
message: 'Which LLM provider should ktx use?',
|
|
312
339
|
options: [
|
|
313
|
-
{ value:
|
|
314
|
-
{ value: 'codex', label: 'Codex subscription' },
|
|
315
|
-
{ value: 'anthropic', label: 'Anthropic API key' },
|
|
316
|
-
{ value: 'vertex', label: 'Google Vertex AI for Anthropic Claude' },
|
|
340
|
+
...KTX_SETUP_LLM_BACKENDS.map((backend) => ({ value: backend, label: KTX_SETUP_LLM_BACKEND_LABELS[backend] })),
|
|
317
341
|
{ value: 'back', label: 'Back' },
|
|
318
342
|
],
|
|
319
343
|
});
|
|
320
344
|
if (choice === 'back') {
|
|
321
345
|
return { status: 'back' };
|
|
322
346
|
}
|
|
323
|
-
|
|
324
|
-
status: 'ready',
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
347
|
+
if (isKtxSetupLlmBackend(choice)) {
|
|
348
|
+
return { status: 'ready', backend: choice, prompted: true };
|
|
349
|
+
}
|
|
350
|
+
// Options are derived from KTX_SETUP_LLM_BACKENDS, so the only other value is
|
|
351
|
+
// 'back' (handled above). Treat any unexpected value as a cancel rather than
|
|
352
|
+
// silently assuming a provider.
|
|
353
|
+
return { status: 'back' };
|
|
328
354
|
}
|
|
329
355
|
function resolveProvidedVertexRef(label, ref, env, io) {
|
|
330
356
|
let value;
|
|
@@ -404,7 +430,7 @@ async function chooseInteractiveVertexProject(currentProject, io, deps) {
|
|
|
404
430
|
io.stdout.write('│ gcloud did not return any visible Google Cloud projects. Enter a project ID manually or choose Back.\n');
|
|
405
431
|
}
|
|
406
432
|
const choice = await prompts.autocomplete({
|
|
407
|
-
message: `Which Google Cloud project should
|
|
433
|
+
message: `Which Google Cloud project should ktx use for Vertex AI?\n\n${[
|
|
408
434
|
VERTEX_PROJECT_PROMPT_CONTEXT,
|
|
409
435
|
listFailureMessage,
|
|
410
436
|
]
|
|
@@ -616,7 +642,7 @@ export async function runKtxSetupAnthropicModelStep(args, io, deps = {}) {
|
|
|
616
642
|
return { status: vertex.status, projectDir: args.projectDir };
|
|
617
643
|
}
|
|
618
644
|
const preset = presetForBackend('vertex');
|
|
619
|
-
const validation = await validatePresetModels(preset,
|
|
645
|
+
const validation = await validatePresetModels(preset, (model) => validateModelWithProgress('Vertex AI', model, deps, () => healthCheck(buildVertexHealthConfig(vertex.values, model))), io);
|
|
620
646
|
if (validation.status !== 'ready') {
|
|
621
647
|
io.stderr.write(`Vertex AI Anthropic model health check failed: ${formatVertexHealthFailure(validation.message, vertex.values)}\n`);
|
|
622
648
|
if (args.inputMode === 'disabled') {
|
|
@@ -633,7 +659,7 @@ export async function runKtxSetupAnthropicModelStep(args, io, deps = {}) {
|
|
|
633
659
|
if (backendChoice.backend === 'claude-code') {
|
|
634
660
|
const preset = presetForBackend('claude-code');
|
|
635
661
|
const probe = deps.claudeCodeAuthProbe ?? runClaudeCodeAuthProbe;
|
|
636
|
-
const validation = await validatePresetModels(preset,
|
|
662
|
+
const validation = await validatePresetModels(preset, (model) => validateModelWithProgress('Claude subscription', model, deps, () => probe({ projectDir: args.projectDir, model, env: deps.env ?? process.env })), io);
|
|
637
663
|
if (validation.status !== 'ready') {
|
|
638
664
|
io.stderr.write(`${validation.message}\n`);
|
|
639
665
|
return { status: 'failed', projectDir: args.projectDir };
|
|
@@ -649,7 +675,7 @@ export async function runKtxSetupAnthropicModelStep(args, io, deps = {}) {
|
|
|
649
675
|
if (backendChoice.backend === 'codex') {
|
|
650
676
|
const preset = presetForBackend('codex');
|
|
651
677
|
const probe = deps.codexAuthProbe ?? runCodexAuthProbe;
|
|
652
|
-
const validation = await validatePresetModels(preset,
|
|
678
|
+
const validation = await validatePresetModels(preset, (model) => validateModelWithProgress('Codex', model, deps, () => probe({ projectDir: args.projectDir, model })), io);
|
|
653
679
|
if (validation.status !== 'ready') {
|
|
654
680
|
io.stderr.write(`${validation.message}\n`);
|
|
655
681
|
return { status: 'failed', projectDir: args.projectDir };
|
|
@@ -670,7 +696,7 @@ export async function runKtxSetupAnthropicModelStep(args, io, deps = {}) {
|
|
|
670
696
|
return { status: credential.status, projectDir: args.projectDir };
|
|
671
697
|
}
|
|
672
698
|
const preset = presetForBackend('anthropic');
|
|
673
|
-
const validation = await validatePresetModels(preset,
|
|
699
|
+
const validation = await validatePresetModels(preset, (model) => validateModelWithProgress('Anthropic API', model, deps, () => healthCheck(buildAnthropicHealthConfig(credential.value, model))), io);
|
|
674
700
|
if (validation.status !== 'ready') {
|
|
675
701
|
io.stderr.write(`Anthropic model health check failed: ${validation.message}\n`);
|
|
676
702
|
if (args.inputMode === 'disabled') {
|
package/dist/setup-project.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
|
+
import { classifyKtxRepoOwnership } from './context/core/git.service.js';
|
|
5
6
|
import { initKtxProject, loadKtxProject } from './context/project/project.js';
|
|
6
7
|
import { markKtxSetupStateStepComplete, mergeKtxSetupGitignoreEntries } from './context/project/setup-config.js';
|
|
7
8
|
import { serializeKtxProjectConfig } from './context/project/config.js';
|
|
@@ -39,7 +40,24 @@ async function existingFolderState(projectDir) {
|
|
|
39
40
|
throw error;
|
|
40
41
|
}
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* ktx owns the git repository at the project dir, so it refuses to create a
|
|
45
|
+
* project inside a repository it did not create (which it would otherwise have
|
|
46
|
+
* to adopt or fail on at first commit). Guides the user toward a dedicated
|
|
47
|
+
* directory instead of letting `GitService.initialize()` throw mid-setup.
|
|
48
|
+
*/
|
|
49
|
+
async function ensureProjectDirIsOwnable(selectedDir, io) {
|
|
50
|
+
if ((await classifyKtxRepoOwnership(selectedDir)) === 'foreign') {
|
|
51
|
+
io.stderr.write(`${selectedDir} is already a git repository that ktx did not create.\n` +
|
|
52
|
+
'ktx keeps its context in a repository it owns. Choose a new subfolder or an empty directory instead.\n');
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
42
57
|
async function confirmProjectDir(selectedDir, io, prompts) {
|
|
58
|
+
if (!(await ensureProjectDirIsOwnable(selectedDir, io))) {
|
|
59
|
+
return { status: 'choose-another' };
|
|
60
|
+
}
|
|
43
61
|
const state = await existingFolderState(selectedDir);
|
|
44
62
|
if (state === 'not-directory') {
|
|
45
63
|
io.stderr.write(`Project folder path exists and is not a directory: ${selectedDir}\n`);
|
|
@@ -49,7 +67,7 @@ async function confirmProjectDir(selectedDir, io, prompts) {
|
|
|
49
67
|
const action = await prompts.select({
|
|
50
68
|
message: `That folder already exists and is not empty: ${selectedDir}`,
|
|
51
69
|
options: [
|
|
52
|
-
{ value: 'use-existing', label: 'Yes, create
|
|
70
|
+
{ value: 'use-existing', label: 'Yes, create ktx files there' },
|
|
53
71
|
{ value: 'choose-another', label: 'Choose another folder' },
|
|
54
72
|
{ value: 'back', label: 'Back' },
|
|
55
73
|
],
|
|
@@ -62,9 +80,9 @@ async function confirmProjectDir(selectedDir, io, prompts) {
|
|
|
62
80
|
return { status: 'cancelled' };
|
|
63
81
|
return { status: 'confirmed', confirmedCreation: true };
|
|
64
82
|
}
|
|
65
|
-
io.stdout.write(`│
|
|
83
|
+
io.stdout.write(`│ ktx will create:\n│ ${selectedDir}\n`);
|
|
66
84
|
const action = await prompts.select({
|
|
67
|
-
message: `Create
|
|
85
|
+
message: `Create ktx project at ${selectedDir}?`,
|
|
68
86
|
options: [
|
|
69
87
|
{ value: 'create', label: 'Create project' },
|
|
70
88
|
{ value: 'choose-another', label: 'Choose another folder' },
|
|
@@ -108,7 +126,7 @@ async function promptForNewProjectDir(projectDir, homeDir, io, prompts) {
|
|
|
108
126
|
const defaultProjectDir = join(projectDir, DEFAULT_NEW_PROJECT_FOLDER_NAME);
|
|
109
127
|
while (true) {
|
|
110
128
|
const destinationChoice = await prompts.select({
|
|
111
|
-
message: 'Where should
|
|
129
|
+
message: 'Where should ktx create the project?',
|
|
112
130
|
options: [
|
|
113
131
|
{ value: 'default', label: `Create the default project folder: ${defaultProjectDir}` },
|
|
114
132
|
{ value: 'custom', label: 'Enter a custom path' },
|
|
@@ -196,6 +214,9 @@ export async function runKtxSetupProjectStep(args, io, deps = {}) {
|
|
|
196
214
|
io.stderr.write('Missing setup choice: pass --yes to create a project in non-interactive setup.\n');
|
|
197
215
|
return { status: 'missing-input', projectDir };
|
|
198
216
|
}
|
|
217
|
+
if (!(await ensureProjectDirIsOwnable(projectDir, io))) {
|
|
218
|
+
return { status: 'missing-input', projectDir };
|
|
219
|
+
}
|
|
199
220
|
const project = await createProject(projectDir, deps);
|
|
200
221
|
printProjectSummary(io, projectDir);
|
|
201
222
|
return {
|
|
@@ -217,7 +238,7 @@ export async function runKtxSetupProjectStep(args, io, deps = {}) {
|
|
|
217
238
|
io.stdout.write('│ Use Up/Down to move, Enter to confirm the current selection, choose Back to return to the previous step, Ctrl+C to exit.\n');
|
|
218
239
|
while (true) {
|
|
219
240
|
const choice = await prompts.select({
|
|
220
|
-
message: 'Where should
|
|
241
|
+
message: 'Where should ktx create the project?',
|
|
221
242
|
options: [
|
|
222
243
|
{ value: 'current', label: `Current directory (${projectDir})` },
|
|
223
244
|
{ value: 'new-default', label: `New subfolder (${defaultProjectDirLabel})` },
|
|
@@ -234,6 +255,9 @@ export async function runKtxSetupProjectStep(args, io, deps = {}) {
|
|
|
234
255
|
return { status: 'cancelled', projectDir };
|
|
235
256
|
}
|
|
236
257
|
if (choice === 'current') {
|
|
258
|
+
if (!(await ensureProjectDirIsOwnable(projectDir, io))) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
237
261
|
const project = await createProject(projectDir, deps);
|
|
238
262
|
printProjectSummary(io, projectDir);
|
|
239
263
|
return {
|
package/dist/setup-prompts.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { updateSettings } from '@clack/core';
|
|
1
2
|
import { autocomplete, autocompleteMultiselect, cancel, confirm, intro, isCancel, log, multiselect, note, select, text, } from '@clack/prompts';
|
|
2
|
-
import {
|
|
3
|
+
import { unicodeSupported } from './io/symbols.js';
|
|
4
|
+
import { colorDepthForOutput, isWritableTtyOutput } from './io/tty.js';
|
|
3
5
|
import { withMenuOptionsSpacing, withTextInputNavigation } from './prompt-navigation.js';
|
|
6
|
+
import { renderKtxSetupBanner } from './setup-banner.js';
|
|
4
7
|
import { revealPassword } from './reveal-password-prompt.js';
|
|
5
8
|
import { withSetupInterruptConfirmation } from './setup-interrupt.js';
|
|
9
|
+
// clack remaps Tab to Space only on non-text prompts (flat multiselect/select/
|
|
10
|
+
// confirm); text inputs and autocomplete search set _track, so typed Tab is
|
|
11
|
+
// untouched. This makes Tab the single documented select key across setup.
|
|
12
|
+
updateSettings({ aliases: { tab: 'space' } });
|
|
6
13
|
const DEFAULT_SETUP_CANCEL_MESSAGE = 'Setup cancelled.';
|
|
7
14
|
export function createKtxSetupPromptAdapter(options) {
|
|
8
15
|
const cancelMessage = options.cancelMessage ?? DEFAULT_SETUP_CANCEL_MESSAGE;
|
|
@@ -106,6 +113,14 @@ export function createKtxSetupUiAdapter() {
|
|
|
106
113
|
return {
|
|
107
114
|
intro(title, io) {
|
|
108
115
|
if (isWritableTtyOutput(io.stdout)) {
|
|
116
|
+
const banner = renderKtxSetupBanner({
|
|
117
|
+
columns: io.stdout.columns ?? 80,
|
|
118
|
+
colorDepth: colorDepthForOutput(io.stdout),
|
|
119
|
+
unicode: unicodeSupported,
|
|
120
|
+
});
|
|
121
|
+
if (banner !== '') {
|
|
122
|
+
io.stdout.write(banner);
|
|
123
|
+
}
|
|
109
124
|
intro(title, { output: io.stdout });
|
|
110
125
|
return;
|
|
111
126
|
}
|
package/dist/setup-ready-menu.js
CHANGED
|
@@ -56,7 +56,7 @@ export async function runKtxSetupReadyChangeMenu(status, deps = {}) {
|
|
|
56
56
|
{ value: 'databases', label: 'Databases' },
|
|
57
57
|
{ value: 'sources', label: 'Context sources' },
|
|
58
58
|
...(status.runtime.required ? [{ value: 'runtime', label: 'Runtime' }] : []),
|
|
59
|
-
{ value: 'context', label: 'Rebuild
|
|
59
|
+
{ value: 'context', label: 'Rebuild ktx context' },
|
|
60
60
|
{ value: 'agents', label: 'Agent integration' },
|
|
61
61
|
{ value: 'exit', label: 'Exit' },
|
|
62
62
|
],
|
package/dist/setup-sources.js
CHANGED
|
@@ -16,9 +16,10 @@ import { parseMetricflowFiles } from './context/ingest/adapters/metricflow/deep-
|
|
|
16
16
|
import { serializeKtxProjectConfig } from './context/project/config.js';
|
|
17
17
|
import { loadKtxProject } from './context/project/project.js';
|
|
18
18
|
import { markKtxSetupStateStepComplete } from './context/project/setup-config.js';
|
|
19
|
-
import { errorMessage, writePrefixedLines } from './clack.js';
|
|
19
|
+
import { createCliSpinner, errorMessage, writePrefixedLines } from './clack.js';
|
|
20
20
|
import { pickNotionRootPages } from './notion-page-picker.js';
|
|
21
21
|
import { runKtxSourceMapping } from './source-mapping.js';
|
|
22
|
+
import { assertSafeConnectionId } from './context/sl/source-files.js';
|
|
22
23
|
import { runConnectionSetupWithRecovery, } from './connection-recovery.js';
|
|
23
24
|
import { withMultiselectNavigation, withTextInputNavigation } from './prompt-navigation.js';
|
|
24
25
|
import { runKtxPublicIngest } from './public-ingest.js';
|
|
@@ -65,7 +66,7 @@ function sourceAdapter(source) {
|
|
|
65
66
|
return source;
|
|
66
67
|
}
|
|
67
68
|
function connectionNamePrompt(label) {
|
|
68
|
-
return `Name this ${label} connection\
|
|
69
|
+
return `Name this ${label} connection\nktx will use this short name in commands and config. You can rename it now.`;
|
|
69
70
|
}
|
|
70
71
|
function sourceSubpathPrompt(source) {
|
|
71
72
|
if (source === 'dbt') {
|
|
@@ -100,11 +101,6 @@ async function findDbtProjectSubpaths(rootDir) {
|
|
|
100
101
|
async function promptText(prompts, options) {
|
|
101
102
|
return await prompts.text({ ...options, message: withTextInputNavigation(options.message) });
|
|
102
103
|
}
|
|
103
|
-
function assertSafeConnectionId(connectionId) {
|
|
104
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId)) {
|
|
105
|
-
throw new Error(`Unsafe connection id: ${connectionId}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
104
|
function credentialRef(value, label) {
|
|
109
105
|
const ref = value?.trim();
|
|
110
106
|
if (!ref) {
|
|
@@ -143,7 +139,7 @@ function assertSourceCredentialFlags(source, args) {
|
|
|
143
139
|
async function chooseSourceCredentialRef(input) {
|
|
144
140
|
while (true) {
|
|
145
141
|
const choice = await input.prompts.select({
|
|
146
|
-
message: `How should
|
|
142
|
+
message: `How should ktx find your ${input.label}?`,
|
|
147
143
|
options: [
|
|
148
144
|
...(input.existingRef ? [{ value: 'keep', label: 'Keep existing credential' }] : []),
|
|
149
145
|
{ value: 'paste', label: 'Paste a key and save it as a local secret file' },
|
|
@@ -733,12 +729,15 @@ async function chooseMetabaseDatabaseId(input) {
|
|
|
733
729
|
const sourceUrl = input.state.sourceUrl;
|
|
734
730
|
const sourceApiKeyRef = input.state.sourceApiKeyRef;
|
|
735
731
|
if (sourceUrl && sourceApiKeyRef) {
|
|
732
|
+
const discoverSpinner = createCliSpinner(input.io);
|
|
733
|
+
discoverSpinner.start('Discovering Metabase databases…');
|
|
736
734
|
try {
|
|
737
735
|
const discovered = await (input.deps.discoverMetabaseDatabases ?? defaultDiscoverMetabaseDatabases)({
|
|
738
736
|
sourceUrl,
|
|
739
737
|
sourceApiKeyRef,
|
|
740
738
|
sourceConnectionId: input.state.sourceConnectionId ?? 'metabase-main',
|
|
741
739
|
});
|
|
740
|
+
discoverSpinner.stop(`Found ${discovered.length} ${discovered.length === 1 ? 'database' : 'databases'}`);
|
|
742
741
|
if (discovered.length === 1) {
|
|
743
742
|
return discovered[0].id;
|
|
744
743
|
}
|
|
@@ -763,6 +762,7 @@ async function chooseMetabaseDatabaseId(input) {
|
|
|
763
762
|
catch {
|
|
764
763
|
// Discovery is a convenience. Fall back to the raw id prompt when credentials
|
|
765
764
|
// are unavailable locally or the Metabase API cannot be reached yet.
|
|
765
|
+
discoverSpinner.error('Could not reach Metabase — enter the database id manually');
|
|
766
766
|
}
|
|
767
767
|
}
|
|
768
768
|
const databaseId = await promptText(input.prompts, { message: 'Metabase database id' });
|
|
@@ -891,6 +891,8 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
|
|
|
891
891
|
scanDir = currentState.sourcePath;
|
|
892
892
|
}
|
|
893
893
|
else if (currentState.sourceLocation === 'git' && currentState.sourceGitUrl) {
|
|
894
|
+
const cloneSpinner = createCliSpinner(io);
|
|
895
|
+
cloneSpinner.start('Cloning repository to scan for dbt projects…');
|
|
894
896
|
try {
|
|
895
897
|
const cacheDir = await mkdtemp(join(tmpdir(), 'ktx-setup-dbt-scan-'));
|
|
896
898
|
const authToken = currentState.sourceAuthTokenRef
|
|
@@ -903,8 +905,10 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
|
|
|
903
905
|
branch: currentState.sourceBranch ?? 'main',
|
|
904
906
|
});
|
|
905
907
|
scanDir = cacheDir;
|
|
908
|
+
cloneSpinner.stop('Repository cloned');
|
|
906
909
|
}
|
|
907
910
|
catch {
|
|
911
|
+
cloneSpinner.error('Could not clone repository');
|
|
908
912
|
// Clone failed — fall through to manual prompt
|
|
909
913
|
}
|
|
910
914
|
}
|
|
@@ -924,7 +928,7 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
|
|
|
924
928
|
}
|
|
925
929
|
if (subpaths.length > 1) {
|
|
926
930
|
const selected = await prompts.select({
|
|
927
|
-
message: 'Multiple dbt projects found — which one should
|
|
931
|
+
message: 'Multiple dbt projects found — which one should ktx use?',
|
|
928
932
|
options: [
|
|
929
933
|
...subpaths.map((p) => ({ value: p || '.', label: p || '(project root)' })),
|
|
930
934
|
{ value: 'back', label: 'Back' },
|
|
@@ -1008,6 +1012,7 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
|
|
|
1008
1012
|
state,
|
|
1009
1013
|
prompts,
|
|
1010
1014
|
deps: { discoverMetabaseDatabases: discoverMetabaseDatabaseList },
|
|
1015
|
+
io,
|
|
1011
1016
|
});
|
|
1012
1017
|
if (databaseId === 'back')
|
|
1013
1018
|
return 'back';
|
|
@@ -1099,7 +1104,7 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
|
|
|
1099
1104
|
},
|
|
1100
1105
|
async (currentState) => {
|
|
1101
1106
|
const crawlMode = await prompts.select({
|
|
1102
|
-
message: 'Which Notion pages should
|
|
1107
|
+
message: 'Which Notion pages should ktx ingest?',
|
|
1103
1108
|
options: [
|
|
1104
1109
|
{ value: 'all_accessible', label: 'All pages the integration can access' },
|
|
1105
1110
|
{ value: 'selected_roots', label: 'Specific pages and their subpages (choose them in a picker)' },
|
|
@@ -1449,11 +1454,22 @@ function sourceConnectionId(input) {
|
|
|
1449
1454
|
: (input.sourceChoice.args.sourceConnectionId ?? `${input.source}-main`);
|
|
1450
1455
|
}
|
|
1451
1456
|
async function validateSourceConnectionAndMapping(input) {
|
|
1452
|
-
const
|
|
1457
|
+
const validateSpinner = createCliSpinner(input.io);
|
|
1458
|
+
validateSpinner.start(`Validating ${sourceLabel(input.source)} source…`);
|
|
1459
|
+
let validation;
|
|
1460
|
+
try {
|
|
1461
|
+
validation = await validateSource(input.source, { projectDir: input.args.projectDir, connectionId: input.connectionId, connection: input.connection }, input.deps);
|
|
1462
|
+
}
|
|
1463
|
+
catch (error) {
|
|
1464
|
+
validateSpinner.error(`${sourceLabel(input.source)} source validation failed`);
|
|
1465
|
+
throw error;
|
|
1466
|
+
}
|
|
1453
1467
|
if (!validation.ok) {
|
|
1468
|
+
validateSpinner.error(`${sourceLabel(input.source)} source validation failed`);
|
|
1454
1469
|
input.io.stderr.write(`${validation.message}\n`);
|
|
1455
1470
|
return { status: 'failed' };
|
|
1456
1471
|
}
|
|
1472
|
+
validateSpinner.stop(`${sourceLabel(input.source)} source validated`);
|
|
1457
1473
|
if (input.source === 'metabase' || input.source === 'looker') {
|
|
1458
1474
|
input.prompts.log?.(`Validating ${sourceLabel(input.source)} mapping...`);
|
|
1459
1475
|
const mappingCode = await (input.deps.runMapping ?? defaultRunMapping)(input.args.projectDir, input.connectionId, createSetupPrefixedIo(input.io));
|
|
@@ -1585,7 +1601,7 @@ export async function runKtxSetupSourcesStep(args, io, deps = {}) {
|
|
|
1585
1601
|
: args.inputMode === 'disabled'
|
|
1586
1602
|
? []
|
|
1587
1603
|
: await prompts.multiselect({
|
|
1588
|
-
message: withMultiselectNavigation('Which context sources should
|
|
1604
|
+
message: withMultiselectNavigation('Which context sources should ktx ingest?'),
|
|
1589
1605
|
options: contextSourceChecklist.options,
|
|
1590
1606
|
...(contextSourceChecklist.initialValues.length > 0
|
|
1591
1607
|
? { initialValues: contextSourceChecklist.initialValues }
|
package/dist/setup.d.ts
CHANGED
package/dist/setup.js
CHANGED
|
@@ -112,16 +112,16 @@ async function runKtxSetupEntryMenu(status, deps = {}) {
|
|
|
112
112
|
const options = status.project.ready
|
|
113
113
|
? [
|
|
114
114
|
{ value: 'setup', label: 'Resume or change an existing setup' },
|
|
115
|
-
{ value: 'new-project', label: 'Create a new
|
|
116
|
-
{ value: 'agents', label: 'Connect a coding agent to
|
|
115
|
+
{ value: 'new-project', label: 'Create a new ktx project' },
|
|
116
|
+
{ value: 'agents', label: 'Connect a coding agent to ktx' },
|
|
117
117
|
{ value: 'status', label: 'Check setup status' },
|
|
118
|
-
{ value: 'demo', label: 'Explore a pre-built
|
|
118
|
+
{ value: 'demo', label: 'Explore a pre-built ktx project' },
|
|
119
119
|
{ value: 'exit', label: 'Exit' },
|
|
120
120
|
]
|
|
121
121
|
: [
|
|
122
|
-
{ value: 'setup', label: 'Set up
|
|
122
|
+
{ value: 'setup', label: 'Set up ktx for my data' },
|
|
123
123
|
{ value: 'status', label: 'Check setup status' },
|
|
124
|
-
{ value: 'demo', label: 'Explore a pre-built
|
|
124
|
+
{ value: 'demo', label: 'Explore a pre-built ktx project' },
|
|
125
125
|
{ value: 'exit', label: 'Exit' },
|
|
126
126
|
];
|
|
127
127
|
const action = (await prompts.select({
|
|
@@ -282,16 +282,16 @@ function formatContextBuilt(status) {
|
|
|
282
282
|
export function formatKtxSetupStatus(status) {
|
|
283
283
|
if (!status.project.ready) {
|
|
284
284
|
return [
|
|
285
|
-
`No
|
|
285
|
+
`No ktx project found at ${status.project.path}.`,
|
|
286
286
|
'',
|
|
287
287
|
'Check another project: ktx --project-dir <folder> status',
|
|
288
288
|
'Or from that folder: ktx status',
|
|
289
|
-
'Create a new
|
|
289
|
+
'Create a new ktx project here: ktx setup',
|
|
290
290
|
'',
|
|
291
291
|
].join('\n');
|
|
292
292
|
}
|
|
293
293
|
const lines = [
|
|
294
|
-
`
|
|
294
|
+
`ktx project: ${status.project.path}`,
|
|
295
295
|
`Project ready: ${formatReady(status.project.ready)}`,
|
|
296
296
|
`LLM ready: ${formatReady(status.llm.ready)}${status.llm.model ? ` (${status.llm.model})` : ''}`,
|
|
297
297
|
`Embeddings ready: ${formatReady(status.embeddings.ready)}${status.embeddings.model ? ` (${status.embeddings.model})` : ''}`,
|
|
@@ -302,7 +302,7 @@ export function formatKtxSetupStatus(status) {
|
|
|
302
302
|
`Runtime ready: ${formatReady(status.runtime.ready)}${status.runtime.features.length > 0 ? ` (${status.runtime.features.join(', ')})` : ''}`,
|
|
303
303
|
]
|
|
304
304
|
: []),
|
|
305
|
-
`
|
|
305
|
+
`ktx context built: ${formatContextBuilt(status.context)}`,
|
|
306
306
|
`Agent integration ready: ${formatReady(status.agents.some((agent) => agent.ready))}${status.agents.length > 0 ? ` (${status.agents.map((agent) => `${agent.target}:${agent.scope}`).join(', ')})` : ''}`,
|
|
307
307
|
];
|
|
308
308
|
if (!status.context.ready && status.context.status === 'failed' && status.context.detail) {
|
|
@@ -327,7 +327,7 @@ export function formatKtxSetupCompletionSummary(status, options = {}) {
|
|
|
327
327
|
lines.push('', 'REQUIRED BEFORE USING AGENTS', '', ...agentNextActions.split('\n').map((line) => (line ? ` ${line}` : '')));
|
|
328
328
|
}
|
|
329
329
|
lines.push('', agentNextActions ? 'After that, try' : 'Try it');
|
|
330
|
-
lines.push(' Ask your agent: "Use
|
|
330
|
+
lines.push(' Ask your agent: "Use ktx to show me the available tables."');
|
|
331
331
|
return lines.join('\n');
|
|
332
332
|
}
|
|
333
333
|
function setupStatusReady(status) {
|
|
@@ -357,7 +357,7 @@ function setupRuntimeInstallPolicy(args) {
|
|
|
357
357
|
}
|
|
358
358
|
async function commitSetupConfigChanges(projectDir) {
|
|
359
359
|
const project = await loadKtxProject({ projectDir });
|
|
360
|
-
await project.git.commitFile('ktx.yaml', 'setup: update
|
|
360
|
+
await project.git.commitFile('ktx.yaml', 'setup: update ktx project config', 'ktx setup', 'setup@ktx.local');
|
|
361
361
|
}
|
|
362
362
|
export async function runKtxSetup(args, io, deps = {}) {
|
|
363
363
|
try {
|
|
@@ -372,7 +372,7 @@ export async function runKtxSetup(args, io, deps = {}) {
|
|
|
372
372
|
}
|
|
373
373
|
async function runKtxSetupInner(args, io, deps = {}) {
|
|
374
374
|
const setupUi = deps.setupUi ?? createKtxSetupUiAdapter();
|
|
375
|
-
setupUi.intro('
|
|
375
|
+
setupUi.intro('ktx setup', io);
|
|
376
376
|
setupUi.note(KTX_DOCS_URL, '📚 Docs', io);
|
|
377
377
|
let entryAction;
|
|
378
378
|
let projectResult;
|
|
@@ -624,6 +624,7 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
624
624
|
agents: true,
|
|
625
625
|
...(args.target ? { target: args.target } : {}),
|
|
626
626
|
scope: args.agentScope ?? 'project',
|
|
627
|
+
...(args.installRoot ? { installRoot: args.installRoot } : {}),
|
|
627
628
|
mode: 'mcp',
|
|
628
629
|
skipAgents: false,
|
|
629
630
|
showNextActions: agentsRequested,
|
|
@@ -684,7 +685,7 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
684
685
|
const focusedOnAgents = args.agents || entryAction === 'agents';
|
|
685
686
|
if (!focusedOnAgents) {
|
|
686
687
|
if (shouldPrintConciseReadySummary(status)) {
|
|
687
|
-
setupUi.note(formatKtxSetupCompletionSummary(status, { agentNextActions }), agentNextActions ? 'Finish
|
|
688
|
+
setupUi.note(formatKtxSetupCompletionSummary(status, { agentNextActions }), agentNextActions ? 'Finish ktx agent setup' : 'ktx project ready', io, {
|
|
688
689
|
format: (line) => line,
|
|
689
690
|
});
|
|
690
691
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ktx-analytics
|
|
3
|
-
description: Use when answering a question that needs data from a
|
|
3
|
+
description: Use when answering a question that needs data from a ktx-connected database - investigating, analyzing, "how many", "show me", "what's the breakdown of", finding records by value, exploring tables, comparing periods, explaining metrics, or any data-analysis request. Triggers even when the user does not say "analytics"; if the answer requires querying a configured ktx connection, this skill applies.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# ktx Analytics Workflow
|
|
7
7
|
|
|
8
|
-
You have access to
|
|
8
|
+
You have access to ktx MCP tools for data discovery, semantic-layer analysis, raw read-only SQL, wiki context, and memory ingest. Follow this workflow.
|
|
9
9
|
|
|
10
10
|
<workflow>
|
|
11
11
|
1. **Discover** - call `discover_data` first to see what exists across wiki pages, semantic-layer sources, metrics, dimensions, raw tables, and columns. Returns refs only.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dbt_ingest
|
|
3
|
-
description: Map dbt `schema.yml` / `properties.yml` models and sources into
|
|
3
|
+
description: Map dbt `schema.yml` / `properties.yml` models and sources into ktx semantic-layer overlays and column notes. Covers `sources:` vs `models:`, column `data_tests` (not_null, unique, accepted_values, relationships), and how bundle-time writes complement manifest backfill from git sync. Load when the WorkUnit's `skillNames` includes `dbt_ingest` or when raw files are dbt YAML under `models/` / `sources/`.
|
|
4
4
|
callers: [memory_agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# dbt →
|
|
7
|
+
# dbt → ktx (bundle ingest)
|
|
8
8
|
|
|
9
9
|
Use this skill for **uploaded** dbt projects (`dbt_project.yml` at stage root, `models/**`, `sources/**`, `schema.yml`). There is **no** `fetch()` in v1 - scheduled `dbt parse` / `manifest.json` pulls are out of scope; host-provided dbt sync may still backfill structured test metadata into `_schema` on the next sync.
|
|
10
10
|
|
|
11
11
|
## Mapping (models / sources → SL)
|
|
12
12
|
|
|
13
|
-
| dbt |
|
|
13
|
+
| dbt | ktx | Notes |
|
|
14
14
|
|-----|--------|--------|
|
|
15
15
|
| `models:` entry with `columns:` | **Overlay** on the manifest table with the same name (after `discover_data` / `entity_details`) | One SL source per physical table; model name may differ from DB name - resolve with `read_raw_file` + warehouse context. |
|
|
16
16
|
| `sources:` → `tables:` | Same as models; use `identifier` when present instead of logical `name`. | Schema + name must match how the connection sees tables. |
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: looker_ingest
|
|
3
|
-
description: Extract durable
|
|
3
|
+
description: Extract durable ktx knowledge and semantic-layer contribution proposals from staged Looker runtime dashboard, Look, and explore JSON. Load for WorkUnits whose raw files are under explores/, dashboards/, or looks/.
|
|
4
4
|
callers: [memory_agent]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Looker Runtime Ingest
|
|
8
8
|
|
|
9
|
-
Looker runtime ingest turns API-staged dashboards, Looks, and explores into durable
|
|
9
|
+
Looker runtime ingest turns API-staged dashboards, Looks, and explores into durable ktx memory. Runtime entities are evidence. They are not themselves the final knowledge shape.
|
|
10
10
|
|
|
11
11
|
## Required Workflow
|
|
12
12
|
|
|
@@ -103,7 +103,7 @@ The staged explore file carries warehouse target fields populated before the WU
|
|
|
103
103
|
- `rawSqlTableName`: Looker's verbatim `sql_table_name`. Keep it as provenance only.
|
|
104
104
|
- `targetTable`: the parsed target-table union. Use this as the sole branch condition.
|
|
105
105
|
|
|
106
|
-
When `targetTable.ok === true`, the explore has a complete
|
|
106
|
+
When `targetTable.ok === true`, the explore has a complete ktx backing target. Before writing:
|
|
107
107
|
|
|
108
108
|
1. Use `targetTable.catalog`, `targetTable.schema`, and `targetTable.name` for `source_tables` preflight matching through `sl_discover` or `sl_read_source`.
|
|
109
109
|
2. Use Looker field `sql`, labels, descriptions, and type metadata to derive source columns, measures, segments, joins, and grain.
|