@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.
Files changed (212) hide show
  1. package/assets/python/kaelio_ktx-0.13.0-py3-none-any.whl +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/admin.js +1 -1
  5. package/dist/clack.d.ts +16 -0
  6. package/dist/clack.js +37 -6
  7. package/dist/claude-code-prompt-caching.js +1 -1
  8. package/dist/cli-program.js +3 -3
  9. package/dist/cli-runtime.js +2 -2
  10. package/dist/commands/connection-commands.js +1 -1
  11. package/dist/commands/ingest-commands.js +4 -4
  12. package/dist/commands/mcp-commands.js +12 -12
  13. package/dist/commands/runtime-commands.js +4 -4
  14. package/dist/commands/setup-commands.js +19 -5
  15. package/dist/commands/sl-commands.js +1 -1
  16. package/dist/commands/sql-commands.js +1 -1
  17. package/dist/commands/status-commands.js +1 -1
  18. package/dist/connection.js +15 -3
  19. package/dist/connectors/bigquery/connector.js +1 -14
  20. package/dist/connectors/clickhouse/connector.js +2 -16
  21. package/dist/connectors/duckdb/federated-attach.d.ts +7 -0
  22. package/dist/connectors/duckdb/federated-attach.js +86 -0
  23. package/dist/connectors/duckdb/federated-executor.d.ts +5 -0
  24. package/dist/connectors/duckdb/federated-executor.js +59 -0
  25. package/dist/connectors/mysql/connector.js +2 -16
  26. package/dist/connectors/postgres/connector.js +1 -14
  27. package/dist/connectors/shared/string-reference.d.ts +6 -0
  28. package/dist/connectors/shared/string-reference.js +19 -0
  29. package/dist/connectors/snowflake/connector.d.ts +1 -1
  30. package/dist/connectors/snowflake/connector.js +1 -14
  31. package/dist/connectors/sqlite/connector.js +2 -25
  32. package/dist/connectors/sqlserver/connector.js +4 -17
  33. package/dist/context/connections/connection-type.d.ts +1 -1
  34. package/dist/context/connections/federation.d.ts +33 -0
  35. package/dist/context/connections/federation.js +51 -0
  36. package/dist/context/connections/local-warehouse-descriptor.d.ts +2 -0
  37. package/dist/context/connections/project-sql-executor.d.ts +18 -0
  38. package/dist/context/connections/project-sql-executor.js +39 -0
  39. package/dist/context/connections/query-executor.d.ts +2 -2
  40. package/dist/context/connections/read-only-sql.d.ts +1 -0
  41. package/dist/context/connections/read-only-sql.js +119 -4
  42. package/dist/context/connections/resolve-connection.d.ts +12 -0
  43. package/dist/context/connections/resolve-connection.js +37 -0
  44. package/dist/context/core/git-env.d.ts +4 -0
  45. package/dist/context/core/git-env.js +5 -1
  46. package/dist/context/core/git.service.d.ts +23 -0
  47. package/dist/context/core/git.service.js +71 -8
  48. package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
  49. package/dist/context/ingest/adapters/live-database/manifest.d.ts +3 -0
  50. package/dist/context/ingest/adapters/live-database/manifest.js +19 -11
  51. package/dist/context/ingest/adapters/looker/client.js +7 -2
  52. package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
  53. package/dist/context/ingest/adapters/looker/factory.js +9 -0
  54. package/dist/context/ingest/adapters/looker/mapping.js +1 -1
  55. package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
  56. package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
  57. package/dist/context/ingest/adapters/metabase/client.js +1 -1
  58. package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
  59. package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
  60. package/dist/context/ingest/artifact-gates.d.ts +2 -6
  61. package/dist/context/ingest/artifact-gates.js +5 -47
  62. package/dist/context/ingest/constrained-repair.d.ts +55 -0
  63. package/dist/context/ingest/constrained-repair.js +167 -0
  64. package/dist/context/ingest/final-gate-repair.d.ts +9 -11
  65. package/dist/context/ingest/final-gate-repair.js +40 -128
  66. package/dist/context/ingest/finalization-scope.d.ts +1 -1
  67. package/dist/context/ingest/finalization-scope.js +15 -15
  68. package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
  69. package/dist/context/ingest/ingest-bundle.runner.js +101 -67
  70. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
  71. package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
  72. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
  73. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
  74. package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
  75. package/dist/context/ingest/local-bundle-runtime.js +9 -10
  76. package/dist/context/ingest/local-ingest.d.ts +2 -0
  77. package/dist/context/ingest/local-ingest.js +2 -0
  78. package/dist/context/ingest/memory-flow/view-model.js +1 -1
  79. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
  80. package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
  81. package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
  82. package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
  83. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
  84. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
  85. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
  86. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
  87. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
  88. package/dist/context/llm/ai-sdk-runtime.js +2 -2
  89. package/dist/context/llm/claude-code-runtime.js +19 -3
  90. package/dist/context/llm/local-config.js +1 -1
  91. package/dist/context/llm/runtime-tools.js +2 -2
  92. package/dist/context/mcp/context-tools.js +33 -8
  93. package/dist/context/mcp/local-project-ports.js +63 -89
  94. package/dist/context/mcp/types.d.ts +2 -0
  95. package/dist/context/memory/local-memory.js +4 -1
  96. package/dist/context/memory/memory-agent.service.js +1 -1
  97. package/dist/context/project/config.d.ts +11 -4
  98. package/dist/context/project/config.js +85 -30
  99. package/dist/context/project/driver-schemas.js +1 -1
  100. package/dist/context/project/mappings-yaml-schema.js +2 -2
  101. package/dist/context/project/project.js +12 -4
  102. package/dist/context/scan/description-generation.js +4 -4
  103. package/dist/context/scan/local-enrichment-artifacts.js +33 -4
  104. package/dist/context/scan/local-scan.js +2 -2
  105. package/dist/context/scan/local-structural-artifacts.js +5 -5
  106. package/dist/context/scan/relationship-benchmark-report.js +1 -1
  107. package/dist/context/scan/relationship-discovery.js +3 -3
  108. package/dist/context/scan/relationship-llm-proposal.js +3 -3
  109. package/dist/context/sl/local-query.js +31 -44
  110. package/dist/context/sl/local-sl.d.ts +0 -8
  111. package/dist/context/sl/local-sl.js +71 -70
  112. package/dist/context/sl/semantic-layer.service.d.ts +25 -8
  113. package/dist/context/sl/semantic-layer.service.js +109 -56
  114. package/dist/context/sl/source-files.d.ts +48 -0
  115. package/dist/context/sl/source-files.js +138 -0
  116. package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
  117. package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
  118. package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
  119. package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
  120. package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
  121. package/dist/context/sql-analysis/dialect.d.ts +2 -0
  122. package/dist/context/sql-analysis/dialect.js +20 -0
  123. package/dist/context/tools/base-tool.d.ts +6 -19
  124. package/dist/context/tools/base-tool.js +0 -14
  125. package/dist/context-build-view.js +5 -5
  126. package/dist/database-tree-picker.js +18 -3
  127. package/dist/demo-assets.js +0 -1
  128. package/dist/doctor.d.ts +1 -1
  129. package/dist/doctor.js +31 -23
  130. package/dist/errors.d.ts +31 -0
  131. package/dist/errors.js +44 -0
  132. package/dist/ingest-query-executor.d.ts +2 -0
  133. package/dist/ingest-query-executor.js +8 -22
  134. package/dist/ingest.d.ts +1 -1
  135. package/dist/ingest.js +8 -2
  136. package/dist/io/symbols.d.ts +2 -0
  137. package/dist/io/symbols.js +2 -0
  138. package/dist/io/tty.d.ts +8 -0
  139. package/dist/io/tty.js +16 -0
  140. package/dist/llm/embedding-health.js +1 -1
  141. package/dist/llm/embedding-provider.js +3 -3
  142. package/dist/llm/model-provider.js +1 -1
  143. package/dist/local-adapters.d.ts +1 -0
  144. package/dist/local-adapters.js +2 -2
  145. package/dist/local-scan-connectors.js +1 -1
  146. package/dist/managed-local-embeddings.js +17 -8
  147. package/dist/managed-mcp-daemon.js +3 -3
  148. package/dist/managed-python-command.d.ts +7 -0
  149. package/dist/managed-python-command.js +34 -8
  150. package/dist/managed-python-daemon.js +2 -2
  151. package/dist/managed-python-http.js +3 -3
  152. package/dist/managed-python-runtime.d.ts +30 -1
  153. package/dist/managed-python-runtime.js +134 -18
  154. package/dist/managed-uv-release.d.ts +7 -0
  155. package/dist/managed-uv-release.js +11 -0
  156. package/dist/mcp-http-server.js +4 -4
  157. package/dist/mcp-server-factory.js +3 -3
  158. package/dist/mcp-stdio-server.js +1 -1
  159. package/dist/memory-flow-hud.js +2 -2
  160. package/dist/next-steps.js +2 -2
  161. package/dist/prompt-navigation.d.ts +17 -0
  162. package/dist/prompt-navigation.js +49 -3
  163. package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
  164. package/dist/prompts/memory_agent_external_ingest.md +2 -2
  165. package/dist/public-ingest-copy.js +1 -1
  166. package/dist/public-ingest.js +3 -3
  167. package/dist/release-version.js +1 -1
  168. package/dist/runtime-requirements.js +1 -1
  169. package/dist/runtime.js +9 -9
  170. package/dist/scan.js +1 -1
  171. package/dist/setup-agents.d.ts +21 -15
  172. package/dist/setup-agents.js +143 -66
  173. package/dist/setup-banner.d.ts +20 -0
  174. package/dist/setup-banner.js +39 -0
  175. package/dist/setup-context.js +24 -15
  176. package/dist/setup-databases.d.ts +3 -0
  177. package/dist/setup-databases.js +47 -59
  178. package/dist/setup-demo-tour.js +12 -8
  179. package/dist/setup-embeddings.js +9 -9
  180. package/dist/setup-interrupt.js +1 -1
  181. package/dist/setup-models.d.ts +4 -1
  182. package/dist/setup-models.js +54 -28
  183. package/dist/setup-project.js +29 -5
  184. package/dist/setup-prompts.js +16 -1
  185. package/dist/setup-ready-menu.js +1 -1
  186. package/dist/setup-sources.js +28 -12
  187. package/dist/setup.d.ts +1 -0
  188. package/dist/setup.js +14 -13
  189. package/dist/skills/analytics/SKILL.md +3 -3
  190. package/dist/skills/dbt_ingest/SKILL.md +3 -3
  191. package/dist/skills/looker_ingest/SKILL.md +3 -3
  192. package/dist/skills/lookml_ingest/SKILL.md +7 -7
  193. package/dist/skills/metabase_ingest/SKILL.md +4 -4
  194. package/dist/skills/metricflow_ingest/SKILL.md +15 -15
  195. package/dist/skills/notion_synthesize/SKILL.md +1 -1
  196. package/dist/skills/sl/SKILL.md +3 -3
  197. package/dist/skills/sl_capture/SKILL.md +1 -1
  198. package/dist/skills/wiki_capture/SKILL.md +1 -1
  199. package/dist/source-mapping.js +1 -1
  200. package/dist/sql.d.ts +2 -0
  201. package/dist/sql.js +35 -53
  202. package/dist/startup-profile.js +1 -1
  203. package/dist/status-project.d.ts +0 -2
  204. package/dist/status-project.js +4 -6
  205. package/dist/telemetry/events.d.ts +3 -2
  206. package/dist/telemetry/events.js +11 -1
  207. package/dist/telemetry/exception.js +14 -0
  208. package/dist/text-ingest.js +1 -1
  209. package/dist/tree-picker-tui.d.ts +0 -1
  210. package/dist/tree-picker-tui.js +2 -3
  211. package/package.json +2 -1
  212. package/assets/python/kaelio_ktx-0.11.0-py3-none-any.whl +0 -0
@@ -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
- const ANTHROPIC_CREDENTIAL_PROMPT_CONTEXT = 'KTX uses the key to verify Anthropic model access now and to run ingest agents that turn schemas, SQL, ' +
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 = 'KTX stores the selected Google Cloud project ID in ktx.yaml and uses Application Default Credentials for ' +
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 llmHealthCheckStartText(provider, model) {
200
+ function llmCheckStartText(provider, model) {
175
201
  return `Checking ${provider} LLM (${model}).`;
176
202
  }
177
- function startLlmHealthCheckProgress(spinner, message) {
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 runLlmHealthCheckWithProgress(config, provider, model, healthCheck, deps) {
189
- const progress = startLlmHealthCheckProgress((deps.spinner ?? createClackSpinner)(), llmHealthCheckStartText(provider, model));
190
- let health;
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
- health = await healthCheck(config);
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 (health.ok) {
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 health;
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 KTX find your Anthropic API key?\n\n${ANTHROPIC_CREDENTIAL_PROMPT_CONTEXT}`,
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('│ KTX will save the key in .ktx/secrets/anthropic-api-key with local file permissions, then write a file: reference in ktx.yaml.\n');
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
- return { status: 'ready', backend: 'anthropic', prompted: false };
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 KTX use?',
338
+ message: 'Which LLM provider should ktx use?',
312
339
  options: [
313
- { value: 'claude-code', label: 'Claude subscription (Pro/Max)' },
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
- return {
324
- status: 'ready',
325
- backend: choice === 'vertex' || choice === 'claude-code' || choice === 'codex' ? choice : 'anthropic',
326
- prompted: true,
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 KTX use for Vertex AI?\n\n${[
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, async (model) => runLlmHealthCheckWithProgress(buildVertexHealthConfig(vertex.values, model), 'Vertex AI', model, healthCheck, deps), io);
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, async (model) => probe({ projectDir: args.projectDir, model, env: deps.env ?? process.env }), io);
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, async (model) => probe({ projectDir: args.projectDir, model }), io);
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, async (model) => runLlmHealthCheckWithProgress(buildAnthropicHealthConfig(credential.value, model), 'Anthropic API', model, healthCheck, deps), io);
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') {
@@ -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 KTX files there' },
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(`│ KTX will create:\n│ ${selectedDir}\n`);
83
+ io.stdout.write(`│ ktx will create:\n│ ${selectedDir}\n`);
66
84
  const action = await prompts.select({
67
- message: `Create KTX project at ${selectedDir}?`,
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 KTX create the project?',
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 KTX create the project?',
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 {
@@ -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 { isWritableTtyOutput } from './io/tty.js';
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
  }
@@ -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 KTX context' },
59
+ { value: 'context', label: 'Rebuild ktx context' },
60
60
  { value: 'agents', label: 'Agent integration' },
61
61
  { value: 'exit', label: 'Exit' },
62
62
  ],
@@ -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\nKTX will use this short name in commands and config. You can rename it now.`;
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 KTX find your ${input.label}?`,
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 KTX use?',
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 KTX ingest?',
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 validation = await validateSource(input.source, { projectDir: input.args.projectDir, connectionId: input.connectionId, connection: input.connection }, input.deps);
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 KTX ingest?'),
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
@@ -57,6 +57,7 @@ export type KtxSetupArgs = {
57
57
  agents: boolean;
58
58
  target?: KtxAgentTarget;
59
59
  agentScope?: KtxAgentScope;
60
+ installRoot?: string;
60
61
  skipAgents?: boolean;
61
62
  inputMode: 'auto' | 'disabled';
62
63
  debug?: boolean;
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 KTX project' },
116
- { value: 'agents', label: 'Connect a coding agent to KTX' },
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 KTX project' },
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 KTX for my data' },
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 KTX project' },
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 KTX project found at ${status.project.path}.`,
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 KTX project here: ktx setup',
289
+ 'Create a new ktx project here: ktx setup',
290
290
  '',
291
291
  ].join('\n');
292
292
  }
293
293
  const lines = [
294
- `KTX project: ${status.project.path}`,
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
- `KTX context built: ${formatContextBuilt(status.context)}`,
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 KTX to show me the available tables."');
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 KTX project config', 'ktx setup', 'setup@ktx.local');
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('KTX setup', io);
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 KTX agent setup' : 'KTX project ready', io, {
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 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.
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
- # KTX Analytics Workflow
6
+ # ktx Analytics Workflow
7
7
 
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.
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 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/`.
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 → KTX (bundle ingest)
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 | KTX | Notes |
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 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/.
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 KTX memory. Runtime entities are evidence. They are not themselves the final knowledge shape.
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 KTX backing target. Before writing:
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.