@kaelio/ktx 0.11.0 → 0.12.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 (181) hide show
  1. package/assets/python/{kaelio_ktx-0.11.0-py3-none-any.whl → kaelio_ktx-0.12.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 +6 -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 +1 -1
  19. package/dist/connectors/clickhouse/connector.js +1 -1
  20. package/dist/connectors/mysql/connector.js +1 -1
  21. package/dist/connectors/snowflake/connector.d.ts +1 -1
  22. package/dist/connectors/sqlite/connector.js +2 -25
  23. package/dist/connectors/sqlserver/connector.js +3 -3
  24. package/dist/context/connections/connection-type.d.ts +1 -1
  25. package/dist/context/connections/read-only-sql.d.ts +1 -0
  26. package/dist/context/connections/read-only-sql.js +116 -2
  27. package/dist/context/core/git.service.d.ts +23 -0
  28. package/dist/context/core/git.service.js +71 -8
  29. package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
  30. package/dist/context/ingest/adapters/looker/client.js +7 -2
  31. package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
  32. package/dist/context/ingest/adapters/looker/factory.js +9 -0
  33. package/dist/context/ingest/adapters/looker/mapping.js +1 -1
  34. package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
  35. package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
  36. package/dist/context/ingest/adapters/metabase/client.js +1 -1
  37. package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
  38. package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
  39. package/dist/context/ingest/artifact-gates.d.ts +2 -6
  40. package/dist/context/ingest/artifact-gates.js +5 -47
  41. package/dist/context/ingest/constrained-repair.d.ts +55 -0
  42. package/dist/context/ingest/constrained-repair.js +167 -0
  43. package/dist/context/ingest/final-gate-repair.d.ts +9 -11
  44. package/dist/context/ingest/final-gate-repair.js +40 -128
  45. package/dist/context/ingest/finalization-scope.d.ts +1 -1
  46. package/dist/context/ingest/finalization-scope.js +15 -15
  47. package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
  48. package/dist/context/ingest/ingest-bundle.runner.js +101 -67
  49. package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
  50. package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
  51. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
  52. package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
  53. package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
  54. package/dist/context/ingest/local-bundle-runtime.js +9 -10
  55. package/dist/context/ingest/local-ingest.d.ts +2 -0
  56. package/dist/context/ingest/local-ingest.js +2 -0
  57. package/dist/context/ingest/memory-flow/view-model.js +1 -1
  58. package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
  59. package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
  60. package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
  61. package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
  62. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
  63. package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
  64. package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
  65. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
  66. package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
  67. package/dist/context/llm/ai-sdk-runtime.js +2 -2
  68. package/dist/context/llm/claude-code-runtime.js +1 -1
  69. package/dist/context/llm/local-config.js +1 -1
  70. package/dist/context/llm/runtime-tools.js +2 -2
  71. package/dist/context/mcp/context-tools.js +7 -7
  72. package/dist/context/mcp/local-project-ports.js +23 -54
  73. package/dist/context/memory/local-memory.js +4 -1
  74. package/dist/context/memory/memory-agent.service.js +1 -1
  75. package/dist/context/project/config.d.ts +11 -4
  76. package/dist/context/project/config.js +85 -30
  77. package/dist/context/project/driver-schemas.js +1 -1
  78. package/dist/context/project/mappings-yaml-schema.js +2 -2
  79. package/dist/context/project/project.js +12 -4
  80. package/dist/context/scan/description-generation.js +4 -4
  81. package/dist/context/scan/local-enrichment-artifacts.js +2 -1
  82. package/dist/context/scan/local-scan.js +2 -2
  83. package/dist/context/scan/local-structural-artifacts.js +5 -5
  84. package/dist/context/scan/relationship-benchmark-report.js +1 -1
  85. package/dist/context/scan/relationship-discovery.js +3 -3
  86. package/dist/context/scan/relationship-llm-proposal.js +3 -3
  87. package/dist/context/sl/local-query.js +3 -33
  88. package/dist/context/sl/local-sl.d.ts +0 -8
  89. package/dist/context/sl/local-sl.js +44 -69
  90. package/dist/context/sl/semantic-layer.service.d.ts +25 -8
  91. package/dist/context/sl/semantic-layer.service.js +109 -56
  92. package/dist/context/sl/source-files.d.ts +46 -0
  93. package/dist/context/sl/source-files.js +131 -0
  94. package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
  95. package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
  96. package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
  97. package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
  98. package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
  99. package/dist/context/sql-analysis/dialect.d.ts +2 -0
  100. package/dist/context/sql-analysis/dialect.js +20 -0
  101. package/dist/context/tools/base-tool.d.ts +6 -19
  102. package/dist/context/tools/base-tool.js +0 -14
  103. package/dist/context-build-view.js +5 -5
  104. package/dist/database-tree-picker.js +18 -3
  105. package/dist/demo-assets.js +0 -1
  106. package/dist/doctor.d.ts +1 -1
  107. package/dist/doctor.js +31 -23
  108. package/dist/errors.d.ts +31 -0
  109. package/dist/errors.js +44 -0
  110. package/dist/ingest.d.ts +1 -1
  111. package/dist/ingest.js +8 -2
  112. package/dist/io/symbols.d.ts +2 -0
  113. package/dist/io/symbols.js +2 -0
  114. package/dist/io/tty.d.ts +8 -0
  115. package/dist/io/tty.js +16 -0
  116. package/dist/llm/embedding-health.js +1 -1
  117. package/dist/llm/embedding-provider.js +3 -3
  118. package/dist/llm/model-provider.js +1 -1
  119. package/dist/local-adapters.d.ts +1 -0
  120. package/dist/local-adapters.js +2 -2
  121. package/dist/local-scan-connectors.js +1 -1
  122. package/dist/managed-local-embeddings.js +17 -8
  123. package/dist/managed-mcp-daemon.js +3 -3
  124. package/dist/managed-python-command.d.ts +7 -0
  125. package/dist/managed-python-command.js +34 -8
  126. package/dist/managed-python-daemon.js +2 -2
  127. package/dist/managed-python-http.js +3 -3
  128. package/dist/managed-python-runtime.d.ts +30 -1
  129. package/dist/managed-python-runtime.js +134 -18
  130. package/dist/managed-uv-release.d.ts +7 -0
  131. package/dist/managed-uv-release.js +11 -0
  132. package/dist/mcp-http-server.js +4 -4
  133. package/dist/mcp-server-factory.js +3 -3
  134. package/dist/mcp-stdio-server.js +1 -1
  135. package/dist/memory-flow-hud.js +2 -2
  136. package/dist/next-steps.js +2 -2
  137. package/dist/prompt-navigation.d.ts +17 -0
  138. package/dist/prompt-navigation.js +49 -3
  139. package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
  140. package/dist/prompts/memory_agent_external_ingest.md +2 -2
  141. package/dist/public-ingest-copy.js +1 -1
  142. package/dist/public-ingest.js +3 -3
  143. package/dist/release-version.js +1 -1
  144. package/dist/runtime-requirements.js +1 -1
  145. package/dist/runtime.js +9 -9
  146. package/dist/scan.js +1 -1
  147. package/dist/setup-agents.js +21 -30
  148. package/dist/setup-banner.d.ts +20 -0
  149. package/dist/setup-banner.js +39 -0
  150. package/dist/setup-context.js +24 -15
  151. package/dist/setup-databases.js +31 -59
  152. package/dist/setup-demo-tour.js +12 -8
  153. package/dist/setup-embeddings.js +9 -9
  154. package/dist/setup-interrupt.js +1 -1
  155. package/dist/setup-models.d.ts +4 -1
  156. package/dist/setup-models.js +54 -28
  157. package/dist/setup-project.js +29 -5
  158. package/dist/setup-prompts.js +16 -1
  159. package/dist/setup-ready-menu.js +1 -1
  160. package/dist/setup-sources.js +27 -7
  161. package/dist/setup.js +13 -13
  162. package/dist/skills/analytics/SKILL.md +3 -3
  163. package/dist/skills/dbt_ingest/SKILL.md +3 -3
  164. package/dist/skills/looker_ingest/SKILL.md +3 -3
  165. package/dist/skills/lookml_ingest/SKILL.md +7 -7
  166. package/dist/skills/metabase_ingest/SKILL.md +4 -4
  167. package/dist/skills/metricflow_ingest/SKILL.md +15 -15
  168. package/dist/skills/notion_synthesize/SKILL.md +1 -1
  169. package/dist/skills/sl/SKILL.md +3 -3
  170. package/dist/skills/sl_capture/SKILL.md +1 -1
  171. package/dist/skills/wiki_capture/SKILL.md +1 -1
  172. package/dist/source-mapping.js +1 -1
  173. package/dist/startup-profile.js +1 -1
  174. package/dist/status-project.d.ts +0 -2
  175. package/dist/status-project.js +4 -6
  176. package/dist/telemetry/events.d.ts +1 -1
  177. package/dist/telemetry/exception.js +14 -0
  178. package/dist/text-ingest.js +1 -1
  179. package/dist/tree-picker-tui.d.ts +0 -1
  180. package/dist/tree-picker-tui.js +2 -3
  181. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { serializeKtxProjectConfig } from './context/project/config.js';
4
4
  import { loadKtxProject } from './context/project/project.js';
5
5
  import { markKtxSetupStateStepComplete, readKtxSetupState } from './context/project/setup-config.js';
6
6
  import { runKtxEmbeddingHealthCheck } from './llm/embedding-health.js';
7
- import { createStaticCliSpinner, errorMessage, writePrefixedLines } from './clack.js';
7
+ import { createCliSpinner, errorMessage, writePrefixedLines } from './clack.js';
8
8
  import { ensureManagedLocalEmbeddingsDaemon, managedLocalEmbeddingHealthConfig, } from './managed-local-embeddings.js';
9
9
  import { ManagedPythonDaemonStartError } from './managed-python-daemon.js';
10
10
  import { withTextInputNavigation } from './prompt-navigation.js';
@@ -20,7 +20,7 @@ const DEFAULTS = {
20
20
  },
21
21
  };
22
22
  const LOCAL_EMBEDDING_BACKEND = 'sentence-transformers';
23
- const EMBEDDING_OPTION_PROMPT_CONTEXT = 'KTX uses embeddings for semantic search over semantic-layer sources, wiki context, schema metadata, ' +
23
+ const EMBEDDING_OPTION_PROMPT_CONTEXT = 'ktx uses embeddings for semantic search over semantic-layer sources, wiki context, schema metadata, ' +
24
24
  'and relationship evidence.';
25
25
  const LOCAL_EMBEDDING_HEALTH_TIMEOUT_MS = 120_000;
26
26
  const LOCAL_EMBEDDING_STDERR_TAIL_LINES = 40;
@@ -136,7 +136,7 @@ async function chooseCredentialRef(backend, args, io, deps) {
136
136
  const defaultEnv = DEFAULTS[backend].envName ?? 'EMBEDDING_API_KEY';
137
137
  const prompts = deps.prompts ?? createPromptAdapter();
138
138
  const choice = await prompts.select({
139
- message: `How should KTX find your ${embeddingBackendDisplayName(backend)} embedding API key?`,
139
+ message: `How should ktx find your ${embeddingBackendDisplayName(backend)} embedding API key?`,
140
140
  options: [
141
141
  { value: 'paste', label: 'Paste a key and save it as a local secret file' },
142
142
  { value: 'env', label: `Use ${defaultEnv} from the environment` },
@@ -148,7 +148,7 @@ async function chooseCredentialRef(backend, args, io, deps) {
148
148
  }
149
149
  if (choice === 'paste') {
150
150
  io.stdout.write(`│ ${[
151
- `KTX will save the key in .ktx/secrets/${backend}-api-key with local file permissions,`,
151
+ `ktx will save the key in .ktx/secrets/${backend}-api-key with local file permissions,`,
152
152
  'then write a file: reference in ktx.yaml.',
153
153
  ].join(' ')}\n`);
154
154
  const value = await prompts.password({ message: withTextInputNavigation(`${backend} embedding API key`) });
@@ -181,7 +181,7 @@ async function chooseEmbeddingBackend(args, deps) {
181
181
  return LOCAL_EMBEDDING_BACKEND;
182
182
  }
183
183
  const choice = await (deps.prompts ?? createPromptAdapter()).select({
184
- message: `Which embedding option should KTX use?\n\n${EMBEDDING_OPTION_PROMPT_CONTEXT}`,
184
+ message: `Which embedding option should ktx use?\n\n${EMBEDDING_OPTION_PROMPT_CONTEXT}`,
185
185
  options: [
186
186
  { value: 'sentence-transformers', label: 'Local sentence-transformers embeddings' },
187
187
  { value: 'openai', label: 'OpenAI embeddings', hint: 'recommended' },
@@ -211,19 +211,19 @@ async function readLocalEmbeddingDaemonStderrTail(stderrLog) {
211
211
  function localEmbeddingSetupMessage(message, stderrTail = []) {
212
212
  const lines = [
213
213
  `Local embedding health check failed: ${message}`,
214
- 'Local embeddings use the KTX-managed Python runtime.',
214
+ 'Local embeddings use the ktx-managed Python runtime.',
215
215
  'Prepare the runtime with: ktx admin runtime start --feature local-embeddings',
216
216
  'Use --yes with setup to install and start the runtime without prompting.',
217
217
  'The first run may download Python packages and the all-MiniLM-L6-v2 model.',
218
218
  ];
219
219
  if (stderrTail.length > 0) {
220
- lines.push('Recent KTX daemon stderr:', ...stderrTail);
220
+ lines.push('Recent ktx daemon stderr:', ...stderrTail);
221
221
  }
222
222
  return lines.join('\n');
223
223
  }
224
224
  async function promptAfterLocalEmbeddingFailure(deps) {
225
225
  const choice = await (deps.prompts ?? createPromptAdapter()).select({
226
- message: 'Local embeddings are not reachable. Start the local KTX daemon, then retry.',
226
+ message: 'Local embeddings are not reachable. Start the local ktx daemon, then retry.',
227
227
  options: [
228
228
  { value: 'retry', label: 'Retry' },
229
229
  { value: 'openai', label: 'Use OpenAI embeddings' },
@@ -329,7 +329,7 @@ export async function runKtxSetupEmbeddingsStep(args, io, deps = {}) {
329
329
  dimensions,
330
330
  credentialValue,
331
331
  });
332
- const healthSpinner = (deps.spinner ?? (() => createStaticCliSpinner(io)))();
332
+ const healthSpinner = (deps.spinner ?? (() => createCliSpinner(io)))();
333
333
  const progress = startHealthCheckProgress(healthSpinner, healthCheckStartText(selectedBackend, model, dimensions));
334
334
  let health;
335
335
  try {
@@ -2,7 +2,7 @@ import { stdin } from 'node:process';
2
2
  import { cancel, confirm, isCancel as isClackCancel } from '@clack/prompts';
3
3
  export class KtxSetupExitError extends Error {
4
4
  constructor() {
5
- super('KTX setup exit requested');
5
+ super('ktx setup exit requested');
6
6
  this.name = 'KtxSetupExitError';
7
7
  }
8
8
  }
@@ -32,7 +32,10 @@ export type KtxSetupModelResult = {
32
32
  status: 'failed';
33
33
  projectDir: string;
34
34
  };
35
- export type KtxSetupLlmBackend = 'anthropic' | 'vertex' | 'claude-code' | 'codex';
35
+ declare const KTX_SETUP_LLM_BACKENDS: readonly ["claude-code", "codex", "anthropic", "vertex"];
36
+ export type KtxSetupLlmBackend = (typeof KTX_SETUP_LLM_BACKENDS)[number];
37
+ /** Validates a raw CLI or prompt value against the setup-selectable LLM backends. */
38
+ export declare function isKtxSetupLlmBackend(value: string): value is KtxSetupLlmBackend;
36
39
  /** @internal */
37
40
  export interface KtxSetupModelPromptAdapter {
38
41
  select(options: {
@@ -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,7 +16,7 @@ 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
22
  import { runConnectionSetupWithRecovery, } from './connection-recovery.js';
@@ -65,7 +65,7 @@ function sourceAdapter(source) {
65
65
  return source;
66
66
  }
67
67
  function connectionNamePrompt(label) {
68
- return `Name this ${label} connection\nKTX will use this short name in commands and config. You can rename it now.`;
68
+ return `Name this ${label} connection\nktx will use this short name in commands and config. You can rename it now.`;
69
69
  }
70
70
  function sourceSubpathPrompt(source) {
71
71
  if (source === 'dbt') {
@@ -143,7 +143,7 @@ function assertSourceCredentialFlags(source, args) {
143
143
  async function chooseSourceCredentialRef(input) {
144
144
  while (true) {
145
145
  const choice = await input.prompts.select({
146
- message: `How should KTX find your ${input.label}?`,
146
+ message: `How should ktx find your ${input.label}?`,
147
147
  options: [
148
148
  ...(input.existingRef ? [{ value: 'keep', label: 'Keep existing credential' }] : []),
149
149
  { value: 'paste', label: 'Paste a key and save it as a local secret file' },
@@ -733,12 +733,15 @@ async function chooseMetabaseDatabaseId(input) {
733
733
  const sourceUrl = input.state.sourceUrl;
734
734
  const sourceApiKeyRef = input.state.sourceApiKeyRef;
735
735
  if (sourceUrl && sourceApiKeyRef) {
736
+ const discoverSpinner = createCliSpinner(input.io);
737
+ discoverSpinner.start('Discovering Metabase databases…');
736
738
  try {
737
739
  const discovered = await (input.deps.discoverMetabaseDatabases ?? defaultDiscoverMetabaseDatabases)({
738
740
  sourceUrl,
739
741
  sourceApiKeyRef,
740
742
  sourceConnectionId: input.state.sourceConnectionId ?? 'metabase-main',
741
743
  });
744
+ discoverSpinner.stop(`Found ${discovered.length} ${discovered.length === 1 ? 'database' : 'databases'}`);
742
745
  if (discovered.length === 1) {
743
746
  return discovered[0].id;
744
747
  }
@@ -763,6 +766,7 @@ async function chooseMetabaseDatabaseId(input) {
763
766
  catch {
764
767
  // Discovery is a convenience. Fall back to the raw id prompt when credentials
765
768
  // are unavailable locally or the Metabase API cannot be reached yet.
769
+ discoverSpinner.error('Could not reach Metabase — enter the database id manually');
766
770
  }
767
771
  }
768
772
  const databaseId = await promptText(input.prompts, { message: 'Metabase database id' });
@@ -891,6 +895,8 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
891
895
  scanDir = currentState.sourcePath;
892
896
  }
893
897
  else if (currentState.sourceLocation === 'git' && currentState.sourceGitUrl) {
898
+ const cloneSpinner = createCliSpinner(io);
899
+ cloneSpinner.start('Cloning repository to scan for dbt projects…');
894
900
  try {
895
901
  const cacheDir = await mkdtemp(join(tmpdir(), 'ktx-setup-dbt-scan-'));
896
902
  const authToken = currentState.sourceAuthTokenRef
@@ -903,8 +909,10 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
903
909
  branch: currentState.sourceBranch ?? 'main',
904
910
  });
905
911
  scanDir = cacheDir;
912
+ cloneSpinner.stop('Repository cloned');
906
913
  }
907
914
  catch {
915
+ cloneSpinner.error('Could not clone repository');
908
916
  // Clone failed — fall through to manual prompt
909
917
  }
910
918
  }
@@ -924,7 +932,7 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
924
932
  }
925
933
  if (subpaths.length > 1) {
926
934
  const selected = await prompts.select({
927
- message: 'Multiple dbt projects found — which one should KTX use?',
935
+ message: 'Multiple dbt projects found — which one should ktx use?',
928
936
  options: [
929
937
  ...subpaths.map((p) => ({ value: p || '.', label: p || '(project root)' })),
930
938
  { value: 'back', label: 'Back' },
@@ -1008,6 +1016,7 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
1008
1016
  state,
1009
1017
  prompts,
1010
1018
  deps: { discoverMetabaseDatabases: discoverMetabaseDatabaseList },
1019
+ io,
1011
1020
  });
1012
1021
  if (databaseId === 'back')
1013
1022
  return 'back';
@@ -1099,7 +1108,7 @@ async function promptForInteractiveSource(args, source, prompts, io, deps, defau
1099
1108
  },
1100
1109
  async (currentState) => {
1101
1110
  const crawlMode = await prompts.select({
1102
- message: 'Which Notion pages should KTX ingest?',
1111
+ message: 'Which Notion pages should ktx ingest?',
1103
1112
  options: [
1104
1113
  { value: 'all_accessible', label: 'All pages the integration can access' },
1105
1114
  { value: 'selected_roots', label: 'Specific pages and their subpages (choose them in a picker)' },
@@ -1449,11 +1458,22 @@ function sourceConnectionId(input) {
1449
1458
  : (input.sourceChoice.args.sourceConnectionId ?? `${input.source}-main`);
1450
1459
  }
1451
1460
  async function validateSourceConnectionAndMapping(input) {
1452
- const validation = await validateSource(input.source, { projectDir: input.args.projectDir, connectionId: input.connectionId, connection: input.connection }, input.deps);
1461
+ const validateSpinner = createCliSpinner(input.io);
1462
+ validateSpinner.start(`Validating ${sourceLabel(input.source)} source…`);
1463
+ let validation;
1464
+ try {
1465
+ validation = await validateSource(input.source, { projectDir: input.args.projectDir, connectionId: input.connectionId, connection: input.connection }, input.deps);
1466
+ }
1467
+ catch (error) {
1468
+ validateSpinner.error(`${sourceLabel(input.source)} source validation failed`);
1469
+ throw error;
1470
+ }
1453
1471
  if (!validation.ok) {
1472
+ validateSpinner.error(`${sourceLabel(input.source)} source validation failed`);
1454
1473
  input.io.stderr.write(`${validation.message}\n`);
1455
1474
  return { status: 'failed' };
1456
1475
  }
1476
+ validateSpinner.stop(`${sourceLabel(input.source)} source validated`);
1457
1477
  if (input.source === 'metabase' || input.source === 'looker') {
1458
1478
  input.prompts.log?.(`Validating ${sourceLabel(input.source)} mapping...`);
1459
1479
  const mappingCode = await (input.deps.runMapping ?? defaultRunMapping)(input.args.projectDir, input.connectionId, createSetupPrefixedIo(input.io));
@@ -1585,7 +1605,7 @@ export async function runKtxSetupSourcesStep(args, io, deps = {}) {
1585
1605
  : args.inputMode === 'disabled'
1586
1606
  ? []
1587
1607
  : await prompts.multiselect({
1588
- message: withMultiselectNavigation('Which context sources should KTX ingest?'),
1608
+ message: withMultiselectNavigation('Which context sources should ktx ingest?'),
1589
1609
  options: contextSourceChecklist.options,
1590
1610
  ...(contextSourceChecklist.initialValues.length > 0
1591
1611
  ? { initialValues: contextSourceChecklist.initialValues }
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;
@@ -684,7 +684,7 @@ async function runKtxSetupInner(args, io, deps = {}) {
684
684
  const focusedOnAgents = args.agents || entryAction === 'agents';
685
685
  if (!focusedOnAgents) {
686
686
  if (shouldPrintConciseReadySummary(status)) {
687
- setupUi.note(formatKtxSetupCompletionSummary(status, { agentNextActions }), agentNextActions ? 'Finish KTX agent setup' : 'KTX project ready', io, {
687
+ setupUi.note(formatKtxSetupCompletionSummary(status, { agentNextActions }), agentNextActions ? 'Finish ktx agent setup' : 'ktx project ready', io, {
688
688
  format: (line) => line,
689
689
  });
690
690
  }