@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.
- package/assets/python/{kaelio_ktx-0.11.0-py3-none-any.whl → kaelio_ktx-0.12.0-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/admin.js +1 -1
- package/dist/clack.d.ts +16 -0
- package/dist/clack.js +37 -6
- package/dist/claude-code-prompt-caching.js +1 -1
- package/dist/cli-program.js +3 -3
- package/dist/cli-runtime.js +2 -2
- package/dist/commands/connection-commands.js +1 -1
- package/dist/commands/ingest-commands.js +4 -4
- package/dist/commands/mcp-commands.js +12 -12
- package/dist/commands/runtime-commands.js +4 -4
- package/dist/commands/setup-commands.js +6 -5
- package/dist/commands/sl-commands.js +1 -1
- package/dist/commands/sql-commands.js +1 -1
- package/dist/commands/status-commands.js +1 -1
- package/dist/connection.js +1 -1
- package/dist/connectors/clickhouse/connector.js +1 -1
- package/dist/connectors/mysql/connector.js +1 -1
- package/dist/connectors/snowflake/connector.d.ts +1 -1
- package/dist/connectors/sqlite/connector.js +2 -25
- package/dist/connectors/sqlserver/connector.js +3 -3
- package/dist/context/connections/connection-type.d.ts +1 -1
- package/dist/context/connections/read-only-sql.d.ts +1 -0
- package/dist/context/connections/read-only-sql.js +116 -2
- package/dist/context/core/git.service.d.ts +23 -0
- package/dist/context/core/git.service.js +71 -8
- package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
- package/dist/context/ingest/adapters/looker/client.js +7 -2
- package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
- package/dist/context/ingest/adapters/looker/factory.js +9 -0
- package/dist/context/ingest/adapters/looker/mapping.js +1 -1
- package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.js +1 -1
- package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
- package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
- package/dist/context/ingest/artifact-gates.d.ts +2 -6
- package/dist/context/ingest/artifact-gates.js +5 -47
- package/dist/context/ingest/constrained-repair.d.ts +55 -0
- package/dist/context/ingest/constrained-repair.js +167 -0
- package/dist/context/ingest/final-gate-repair.d.ts +9 -11
- package/dist/context/ingest/final-gate-repair.js +40 -128
- package/dist/context/ingest/finalization-scope.d.ts +1 -1
- package/dist/context/ingest/finalization-scope.js +15 -15
- package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
- package/dist/context/ingest/ingest-bundle.runner.js +101 -67
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
- package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
- package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
- package/dist/context/ingest/local-bundle-runtime.js +9 -10
- package/dist/context/ingest/local-ingest.d.ts +2 -0
- package/dist/context/ingest/local-ingest.js +2 -0
- package/dist/context/ingest/memory-flow/view-model.js +1 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
- package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
- package/dist/context/llm/ai-sdk-runtime.js +2 -2
- package/dist/context/llm/claude-code-runtime.js +1 -1
- package/dist/context/llm/local-config.js +1 -1
- package/dist/context/llm/runtime-tools.js +2 -2
- package/dist/context/mcp/context-tools.js +7 -7
- package/dist/context/mcp/local-project-ports.js +23 -54
- package/dist/context/memory/local-memory.js +4 -1
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +11 -4
- package/dist/context/project/config.js +85 -30
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/project/mappings-yaml-schema.js +2 -2
- package/dist/context/project/project.js +12 -4
- package/dist/context/scan/description-generation.js +4 -4
- package/dist/context/scan/local-enrichment-artifacts.js +2 -1
- package/dist/context/scan/local-scan.js +2 -2
- package/dist/context/scan/local-structural-artifacts.js +5 -5
- package/dist/context/scan/relationship-benchmark-report.js +1 -1
- package/dist/context/scan/relationship-discovery.js +3 -3
- package/dist/context/scan/relationship-llm-proposal.js +3 -3
- package/dist/context/sl/local-query.js +3 -33
- package/dist/context/sl/local-sl.d.ts +0 -8
- package/dist/context/sl/local-sl.js +44 -69
- package/dist/context/sl/semantic-layer.service.d.ts +25 -8
- package/dist/context/sl/semantic-layer.service.js +109 -56
- package/dist/context/sl/source-files.d.ts +46 -0
- package/dist/context/sl/source-files.js +131 -0
- package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
- package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
- package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
- package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
- package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
- package/dist/context/sql-analysis/dialect.d.ts +2 -0
- package/dist/context/sql-analysis/dialect.js +20 -0
- package/dist/context/tools/base-tool.d.ts +6 -19
- package/dist/context/tools/base-tool.js +0 -14
- package/dist/context-build-view.js +5 -5
- package/dist/database-tree-picker.js +18 -3
- package/dist/demo-assets.js +0 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -23
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +44 -0
- package/dist/ingest.d.ts +1 -1
- package/dist/ingest.js +8 -2
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/io/tty.d.ts +8 -0
- package/dist/io/tty.js +16 -0
- package/dist/llm/embedding-health.js +1 -1
- package/dist/llm/embedding-provider.js +3 -3
- package/dist/llm/model-provider.js +1 -1
- package/dist/local-adapters.d.ts +1 -0
- package/dist/local-adapters.js +2 -2
- package/dist/local-scan-connectors.js +1 -1
- package/dist/managed-local-embeddings.js +17 -8
- package/dist/managed-mcp-daemon.js +3 -3
- package/dist/managed-python-command.d.ts +7 -0
- package/dist/managed-python-command.js +34 -8
- package/dist/managed-python-daemon.js +2 -2
- package/dist/managed-python-http.js +3 -3
- package/dist/managed-python-runtime.d.ts +30 -1
- package/dist/managed-python-runtime.js +134 -18
- package/dist/managed-uv-release.d.ts +7 -0
- package/dist/managed-uv-release.js +11 -0
- package/dist/mcp-http-server.js +4 -4
- package/dist/mcp-server-factory.js +3 -3
- package/dist/mcp-stdio-server.js +1 -1
- package/dist/memory-flow-hud.js +2 -2
- package/dist/next-steps.js +2 -2
- package/dist/prompt-navigation.d.ts +17 -0
- package/dist/prompt-navigation.js +49 -3
- package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
- package/dist/prompts/memory_agent_external_ingest.md +2 -2
- package/dist/public-ingest-copy.js +1 -1
- package/dist/public-ingest.js +3 -3
- package/dist/release-version.js +1 -1
- package/dist/runtime-requirements.js +1 -1
- package/dist/runtime.js +9 -9
- package/dist/scan.js +1 -1
- package/dist/setup-agents.js +21 -30
- package/dist/setup-banner.d.ts +20 -0
- package/dist/setup-banner.js +39 -0
- package/dist/setup-context.js +24 -15
- package/dist/setup-databases.js +31 -59
- package/dist/setup-demo-tour.js +12 -8
- package/dist/setup-embeddings.js +9 -9
- package/dist/setup-interrupt.js +1 -1
- package/dist/setup-models.d.ts +4 -1
- package/dist/setup-models.js +54 -28
- package/dist/setup-project.js +29 -5
- package/dist/setup-prompts.js +16 -1
- package/dist/setup-ready-menu.js +1 -1
- package/dist/setup-sources.js +27 -7
- package/dist/setup.js +13 -13
- package/dist/skills/analytics/SKILL.md +3 -3
- package/dist/skills/dbt_ingest/SKILL.md +3 -3
- package/dist/skills/looker_ingest/SKILL.md +3 -3
- package/dist/skills/lookml_ingest/SKILL.md +7 -7
- package/dist/skills/metabase_ingest/SKILL.md +4 -4
- package/dist/skills/metricflow_ingest/SKILL.md +15 -15
- package/dist/skills/notion_synthesize/SKILL.md +1 -1
- package/dist/skills/sl/SKILL.md +3 -3
- package/dist/skills/sl_capture/SKILL.md +1 -1
- package/dist/skills/wiki_capture/SKILL.md +1 -1
- package/dist/source-mapping.js +1 -1
- package/dist/startup-profile.js +1 -1
- package/dist/status-project.d.ts +0 -2
- package/dist/status-project.js +4 -6
- package/dist/telemetry/events.d.ts +1 -1
- package/dist/telemetry/exception.js +14 -0
- package/dist/text-ingest.js +1 -1
- package/dist/tree-picker-tui.d.ts +0 -1
- package/dist/tree-picker-tui.js +2 -3
- package/package.json +1 -1
package/dist/setup-embeddings.js
CHANGED
|
@@ -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 {
|
|
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 = '
|
|
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
|
|
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
|
-
`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ?? (() =>
|
|
332
|
+
const healthSpinner = (deps.spinner ?? (() => createCliSpinner(io)))();
|
|
333
333
|
const progress = startHealthCheckProgress(healthSpinner, healthCheckStartText(selectedBackend, model, dimensions));
|
|
334
334
|
let health;
|
|
335
335
|
try {
|
package/dist/setup-interrupt.js
CHANGED
|
@@ -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('
|
|
5
|
+
super('ktx setup exit requested');
|
|
6
6
|
this.name = 'KtxSetupExitError';
|
|
7
7
|
}
|
|
8
8
|
}
|
package/dist/setup-models.d.ts
CHANGED
|
@@ -32,7 +32,10 @@ export type KtxSetupModelResult = {
|
|
|
32
32
|
status: 'failed';
|
|
33
33
|
projectDir: string;
|
|
34
34
|
};
|
|
35
|
-
|
|
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: {
|
package/dist/setup-models.js
CHANGED
|
@@ -21,10 +21,28 @@ const ESC = String.fromCharCode(0x1b);
|
|
|
21
21
|
function yellow(text) {
|
|
22
22
|
return `${ESC}[33m${text}${ESC}[39m`;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
// Single source of truth for the LLM backends a user can pick during setup.
|
|
25
|
+
// The CLI arg parser, the interactive prompt, and the missing-backend error all
|
|
26
|
+
// derive from this list, so adding a backend is one edit. Order is the prompt's
|
|
27
|
+
// preference order (subscription backends first).
|
|
28
|
+
const KTX_SETUP_LLM_BACKENDS = ['claude-code', 'codex', 'anthropic', 'vertex'];
|
|
29
|
+
/** Validates a raw CLI or prompt value against the setup-selectable LLM backends. */
|
|
30
|
+
export function isKtxSetupLlmBackend(value) {
|
|
31
|
+
return KTX_SETUP_LLM_BACKENDS.some((backend) => backend === value);
|
|
32
|
+
}
|
|
33
|
+
// Display labels for the interactive provider prompt. The Record key type forces
|
|
34
|
+
// every backend to carry a label, so adding one to KTX_SETUP_LLM_BACKENDS fails
|
|
35
|
+
// to compile until its prompt option exists here.
|
|
36
|
+
const KTX_SETUP_LLM_BACKEND_LABELS = {
|
|
37
|
+
'claude-code': 'Claude subscription (Pro/Max)',
|
|
38
|
+
codex: 'Codex subscription',
|
|
39
|
+
anthropic: 'Anthropic API key',
|
|
40
|
+
vertex: 'Google Vertex AI for Anthropic Claude',
|
|
41
|
+
};
|
|
42
|
+
const ANTHROPIC_CREDENTIAL_PROMPT_CONTEXT = 'ktx uses the key to verify Anthropic model access now and to run ingest agents that turn schemas, SQL, ' +
|
|
25
43
|
'BI metadata, and docs into semantic-layer sources and wiki context. ktx.yaml stores an env: or file: ' +
|
|
26
44
|
'reference, not the raw key.';
|
|
27
|
-
const VERTEX_PROJECT_PROMPT_CONTEXT = '
|
|
45
|
+
const VERTEX_PROJECT_PROMPT_CONTEXT = 'ktx stores the selected Google Cloud project ID in ktx.yaml and uses Application Default Credentials for ' +
|
|
28
46
|
'access. Project visibility depends on the signed-in Google account and organization permissions.';
|
|
29
47
|
const DEFAULT_VERTEX_LOCATION = 'us-east5';
|
|
30
48
|
const ANTHROPIC_PRESET = {
|
|
@@ -61,6 +79,14 @@ function presetForBackend(backend) {
|
|
|
61
79
|
return MODEL_PRESETS[backend];
|
|
62
80
|
}
|
|
63
81
|
const execFileAsync = promisify(execFile);
|
|
82
|
+
// Non-interactive setup cannot pick a provider safely: every backend needs
|
|
83
|
+
// something the user must supply (an API key, gcloud ADC, or a logged-in local
|
|
84
|
+
// CLI), so there is no credential-free default to fall back to. Name the hidden
|
|
85
|
+
// --llm-backend flag and its choices here instead, mirroring how the other
|
|
86
|
+
// automation errors guide users to the flag they need.
|
|
87
|
+
const MISSING_LLM_BACKEND_MESSAGE = `Missing LLM backend: pass --llm-backend with one of ${KTX_SETUP_LLM_BACKENDS.join(', ')}. ` +
|
|
88
|
+
'claude-code and codex use local CLI authentication; anthropic also needs --anthropic-api-key-env or ' +
|
|
89
|
+
'--anthropic-api-key-file, and vertex also needs --vertex-project.';
|
|
64
90
|
function createPromptAdapter() {
|
|
65
91
|
return createKtxSetupPromptAdapter({ selectCancelValue: 'back' });
|
|
66
92
|
}
|
|
@@ -171,10 +197,10 @@ function buildVertexHealthConfig(vertex, model) {
|
|
|
171
197
|
promptCaching: { enabled: true, vertexFallbackTo5m: true },
|
|
172
198
|
};
|
|
173
199
|
}
|
|
174
|
-
function
|
|
200
|
+
function llmCheckStartText(provider, model) {
|
|
175
201
|
return `Checking ${provider} LLM (${model}).`;
|
|
176
202
|
}
|
|
177
|
-
function
|
|
203
|
+
function startLlmCheckProgress(spinner, message) {
|
|
178
204
|
spinner.start(message);
|
|
179
205
|
return {
|
|
180
206
|
succeed(msg) {
|
|
@@ -185,23 +211,23 @@ function startLlmHealthCheckProgress(spinner, message) {
|
|
|
185
211
|
},
|
|
186
212
|
};
|
|
187
213
|
}
|
|
188
|
-
async function
|
|
189
|
-
const progress =
|
|
190
|
-
let
|
|
214
|
+
async function validateModelWithProgress(provider, model, deps, run) {
|
|
215
|
+
const progress = startLlmCheckProgress((deps.spinner ?? createClackSpinner)(), llmCheckStartText(provider, model));
|
|
216
|
+
let result;
|
|
191
217
|
try {
|
|
192
|
-
|
|
218
|
+
result = await run();
|
|
193
219
|
}
|
|
194
220
|
catch (error) {
|
|
195
221
|
progress.fail('LLM test failed');
|
|
196
222
|
throw error;
|
|
197
223
|
}
|
|
198
|
-
if (
|
|
224
|
+
if (result.ok) {
|
|
199
225
|
progress.succeed(`LLM test passed (${provider}, ${model})`);
|
|
200
226
|
}
|
|
201
227
|
else {
|
|
202
228
|
progress.fail('LLM test failed');
|
|
203
229
|
}
|
|
204
|
-
return
|
|
230
|
+
return result;
|
|
205
231
|
}
|
|
206
232
|
function formatVertexHealthFailure(message, vertex) {
|
|
207
233
|
const trimmed = message.trim() || 'unknown error';
|
|
@@ -248,7 +274,7 @@ async function chooseCredentialRef(args, io, deps) {
|
|
|
248
274
|
}
|
|
249
275
|
while (true) {
|
|
250
276
|
const choice = await prompts.select({
|
|
251
|
-
message: `How should
|
|
277
|
+
message: `How should ktx find your Anthropic API key?\n\n${ANTHROPIC_CREDENTIAL_PROMPT_CONTEXT}`,
|
|
252
278
|
options: [
|
|
253
279
|
{ value: 'paste', label: 'Paste a key and save it as a local secret file' },
|
|
254
280
|
{ value: 'env', label: 'Use ANTHROPIC_API_KEY from the environment' },
|
|
@@ -259,7 +285,7 @@ async function chooseCredentialRef(args, io, deps) {
|
|
|
259
285
|
return { status: 'back' };
|
|
260
286
|
}
|
|
261
287
|
if (choice === 'paste') {
|
|
262
|
-
io.stdout.write('│
|
|
288
|
+
io.stdout.write('│ ktx will save the key in .ktx/secrets/anthropic-api-key with local file permissions, then write a file: reference in ktx.yaml.\n');
|
|
263
289
|
const value = await prompts.password({ message: withTextInputNavigation('Anthropic API key') });
|
|
264
290
|
if (value === undefined) {
|
|
265
291
|
continue;
|
|
@@ -301,30 +327,30 @@ async function chooseBackend(args, io, deps) {
|
|
|
301
327
|
return { status: 'ready', backend: explicit, prompted: false };
|
|
302
328
|
}
|
|
303
329
|
if (args.inputMode === 'disabled') {
|
|
304
|
-
|
|
330
|
+
io.stderr.write(`${MISSING_LLM_BACKEND_MESSAGE}\n`);
|
|
331
|
+
return { status: 'missing-input' };
|
|
305
332
|
}
|
|
306
333
|
const prompts = deps.prompts ?? createPromptAdapter();
|
|
307
334
|
if (args.showPromptInstructions !== false) {
|
|
308
335
|
io.stdout.write('│ Use Up/Down to move, Enter to confirm the current selection, choose Back to return to the previous step, Ctrl+C to exit.\n');
|
|
309
336
|
}
|
|
310
337
|
const choice = await prompts.select({
|
|
311
|
-
message: 'Which LLM provider should
|
|
338
|
+
message: 'Which LLM provider should ktx use?',
|
|
312
339
|
options: [
|
|
313
|
-
{ value:
|
|
314
|
-
{ value: 'codex', label: 'Codex subscription' },
|
|
315
|
-
{ value: 'anthropic', label: 'Anthropic API key' },
|
|
316
|
-
{ value: 'vertex', label: 'Google Vertex AI for Anthropic Claude' },
|
|
340
|
+
...KTX_SETUP_LLM_BACKENDS.map((backend) => ({ value: backend, label: KTX_SETUP_LLM_BACKEND_LABELS[backend] })),
|
|
317
341
|
{ value: 'back', label: 'Back' },
|
|
318
342
|
],
|
|
319
343
|
});
|
|
320
344
|
if (choice === 'back') {
|
|
321
345
|
return { status: 'back' };
|
|
322
346
|
}
|
|
323
|
-
|
|
324
|
-
status: 'ready',
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
347
|
+
if (isKtxSetupLlmBackend(choice)) {
|
|
348
|
+
return { status: 'ready', backend: choice, prompted: true };
|
|
349
|
+
}
|
|
350
|
+
// Options are derived from KTX_SETUP_LLM_BACKENDS, so the only other value is
|
|
351
|
+
// 'back' (handled above). Treat any unexpected value as a cancel rather than
|
|
352
|
+
// silently assuming a provider.
|
|
353
|
+
return { status: 'back' };
|
|
328
354
|
}
|
|
329
355
|
function resolveProvidedVertexRef(label, ref, env, io) {
|
|
330
356
|
let value;
|
|
@@ -404,7 +430,7 @@ async function chooseInteractiveVertexProject(currentProject, io, deps) {
|
|
|
404
430
|
io.stdout.write('│ gcloud did not return any visible Google Cloud projects. Enter a project ID manually or choose Back.\n');
|
|
405
431
|
}
|
|
406
432
|
const choice = await prompts.autocomplete({
|
|
407
|
-
message: `Which Google Cloud project should
|
|
433
|
+
message: `Which Google Cloud project should ktx use for Vertex AI?\n\n${[
|
|
408
434
|
VERTEX_PROJECT_PROMPT_CONTEXT,
|
|
409
435
|
listFailureMessage,
|
|
410
436
|
]
|
|
@@ -616,7 +642,7 @@ export async function runKtxSetupAnthropicModelStep(args, io, deps = {}) {
|
|
|
616
642
|
return { status: vertex.status, projectDir: args.projectDir };
|
|
617
643
|
}
|
|
618
644
|
const preset = presetForBackend('vertex');
|
|
619
|
-
const validation = await validatePresetModels(preset,
|
|
645
|
+
const validation = await validatePresetModels(preset, (model) => validateModelWithProgress('Vertex AI', model, deps, () => healthCheck(buildVertexHealthConfig(vertex.values, model))), io);
|
|
620
646
|
if (validation.status !== 'ready') {
|
|
621
647
|
io.stderr.write(`Vertex AI Anthropic model health check failed: ${formatVertexHealthFailure(validation.message, vertex.values)}\n`);
|
|
622
648
|
if (args.inputMode === 'disabled') {
|
|
@@ -633,7 +659,7 @@ export async function runKtxSetupAnthropicModelStep(args, io, deps = {}) {
|
|
|
633
659
|
if (backendChoice.backend === 'claude-code') {
|
|
634
660
|
const preset = presetForBackend('claude-code');
|
|
635
661
|
const probe = deps.claudeCodeAuthProbe ?? runClaudeCodeAuthProbe;
|
|
636
|
-
const validation = await validatePresetModels(preset,
|
|
662
|
+
const validation = await validatePresetModels(preset, (model) => validateModelWithProgress('Claude subscription', model, deps, () => probe({ projectDir: args.projectDir, model, env: deps.env ?? process.env })), io);
|
|
637
663
|
if (validation.status !== 'ready') {
|
|
638
664
|
io.stderr.write(`${validation.message}\n`);
|
|
639
665
|
return { status: 'failed', projectDir: args.projectDir };
|
|
@@ -649,7 +675,7 @@ export async function runKtxSetupAnthropicModelStep(args, io, deps = {}) {
|
|
|
649
675
|
if (backendChoice.backend === 'codex') {
|
|
650
676
|
const preset = presetForBackend('codex');
|
|
651
677
|
const probe = deps.codexAuthProbe ?? runCodexAuthProbe;
|
|
652
|
-
const validation = await validatePresetModels(preset,
|
|
678
|
+
const validation = await validatePresetModels(preset, (model) => validateModelWithProgress('Codex', model, deps, () => probe({ projectDir: args.projectDir, model })), io);
|
|
653
679
|
if (validation.status !== 'ready') {
|
|
654
680
|
io.stderr.write(`${validation.message}\n`);
|
|
655
681
|
return { status: 'failed', projectDir: args.projectDir };
|
|
@@ -670,7 +696,7 @@ export async function runKtxSetupAnthropicModelStep(args, io, deps = {}) {
|
|
|
670
696
|
return { status: credential.status, projectDir: args.projectDir };
|
|
671
697
|
}
|
|
672
698
|
const preset = presetForBackend('anthropic');
|
|
673
|
-
const validation = await validatePresetModels(preset,
|
|
699
|
+
const validation = await validatePresetModels(preset, (model) => validateModelWithProgress('Anthropic API', model, deps, () => healthCheck(buildAnthropicHealthConfig(credential.value, model))), io);
|
|
674
700
|
if (validation.status !== 'ready') {
|
|
675
701
|
io.stderr.write(`Anthropic model health check failed: ${validation.message}\n`);
|
|
676
702
|
if (args.inputMode === 'disabled') {
|
package/dist/setup-project.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
|
+
import { classifyKtxRepoOwnership } from './context/core/git.service.js';
|
|
5
6
|
import { initKtxProject, loadKtxProject } from './context/project/project.js';
|
|
6
7
|
import { markKtxSetupStateStepComplete, mergeKtxSetupGitignoreEntries } from './context/project/setup-config.js';
|
|
7
8
|
import { serializeKtxProjectConfig } from './context/project/config.js';
|
|
@@ -39,7 +40,24 @@ async function existingFolderState(projectDir) {
|
|
|
39
40
|
throw error;
|
|
40
41
|
}
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* ktx owns the git repository at the project dir, so it refuses to create a
|
|
45
|
+
* project inside a repository it did not create (which it would otherwise have
|
|
46
|
+
* to adopt or fail on at first commit). Guides the user toward a dedicated
|
|
47
|
+
* directory instead of letting `GitService.initialize()` throw mid-setup.
|
|
48
|
+
*/
|
|
49
|
+
async function ensureProjectDirIsOwnable(selectedDir, io) {
|
|
50
|
+
if ((await classifyKtxRepoOwnership(selectedDir)) === 'foreign') {
|
|
51
|
+
io.stderr.write(`${selectedDir} is already a git repository that ktx did not create.\n` +
|
|
52
|
+
'ktx keeps its context in a repository it owns. Choose a new subfolder or an empty directory instead.\n');
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
42
57
|
async function confirmProjectDir(selectedDir, io, prompts) {
|
|
58
|
+
if (!(await ensureProjectDirIsOwnable(selectedDir, io))) {
|
|
59
|
+
return { status: 'choose-another' };
|
|
60
|
+
}
|
|
43
61
|
const state = await existingFolderState(selectedDir);
|
|
44
62
|
if (state === 'not-directory') {
|
|
45
63
|
io.stderr.write(`Project folder path exists and is not a directory: ${selectedDir}\n`);
|
|
@@ -49,7 +67,7 @@ async function confirmProjectDir(selectedDir, io, prompts) {
|
|
|
49
67
|
const action = await prompts.select({
|
|
50
68
|
message: `That folder already exists and is not empty: ${selectedDir}`,
|
|
51
69
|
options: [
|
|
52
|
-
{ value: 'use-existing', label: 'Yes, create
|
|
70
|
+
{ value: 'use-existing', label: 'Yes, create ktx files there' },
|
|
53
71
|
{ value: 'choose-another', label: 'Choose another folder' },
|
|
54
72
|
{ value: 'back', label: 'Back' },
|
|
55
73
|
],
|
|
@@ -62,9 +80,9 @@ async function confirmProjectDir(selectedDir, io, prompts) {
|
|
|
62
80
|
return { status: 'cancelled' };
|
|
63
81
|
return { status: 'confirmed', confirmedCreation: true };
|
|
64
82
|
}
|
|
65
|
-
io.stdout.write(`│
|
|
83
|
+
io.stdout.write(`│ ktx will create:\n│ ${selectedDir}\n`);
|
|
66
84
|
const action = await prompts.select({
|
|
67
|
-
message: `Create
|
|
85
|
+
message: `Create ktx project at ${selectedDir}?`,
|
|
68
86
|
options: [
|
|
69
87
|
{ value: 'create', label: 'Create project' },
|
|
70
88
|
{ value: 'choose-another', label: 'Choose another folder' },
|
|
@@ -108,7 +126,7 @@ async function promptForNewProjectDir(projectDir, homeDir, io, prompts) {
|
|
|
108
126
|
const defaultProjectDir = join(projectDir, DEFAULT_NEW_PROJECT_FOLDER_NAME);
|
|
109
127
|
while (true) {
|
|
110
128
|
const destinationChoice = await prompts.select({
|
|
111
|
-
message: 'Where should
|
|
129
|
+
message: 'Where should ktx create the project?',
|
|
112
130
|
options: [
|
|
113
131
|
{ value: 'default', label: `Create the default project folder: ${defaultProjectDir}` },
|
|
114
132
|
{ value: 'custom', label: 'Enter a custom path' },
|
|
@@ -196,6 +214,9 @@ export async function runKtxSetupProjectStep(args, io, deps = {}) {
|
|
|
196
214
|
io.stderr.write('Missing setup choice: pass --yes to create a project in non-interactive setup.\n');
|
|
197
215
|
return { status: 'missing-input', projectDir };
|
|
198
216
|
}
|
|
217
|
+
if (!(await ensureProjectDirIsOwnable(projectDir, io))) {
|
|
218
|
+
return { status: 'missing-input', projectDir };
|
|
219
|
+
}
|
|
199
220
|
const project = await createProject(projectDir, deps);
|
|
200
221
|
printProjectSummary(io, projectDir);
|
|
201
222
|
return {
|
|
@@ -217,7 +238,7 @@ export async function runKtxSetupProjectStep(args, io, deps = {}) {
|
|
|
217
238
|
io.stdout.write('│ Use Up/Down to move, Enter to confirm the current selection, choose Back to return to the previous step, Ctrl+C to exit.\n');
|
|
218
239
|
while (true) {
|
|
219
240
|
const choice = await prompts.select({
|
|
220
|
-
message: 'Where should
|
|
241
|
+
message: 'Where should ktx create the project?',
|
|
221
242
|
options: [
|
|
222
243
|
{ value: 'current', label: `Current directory (${projectDir})` },
|
|
223
244
|
{ value: 'new-default', label: `New subfolder (${defaultProjectDirLabel})` },
|
|
@@ -234,6 +255,9 @@ export async function runKtxSetupProjectStep(args, io, deps = {}) {
|
|
|
234
255
|
return { status: 'cancelled', projectDir };
|
|
235
256
|
}
|
|
236
257
|
if (choice === 'current') {
|
|
258
|
+
if (!(await ensureProjectDirIsOwnable(projectDir, io))) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
237
261
|
const project = await createProject(projectDir, deps);
|
|
238
262
|
printProjectSummary(io, projectDir);
|
|
239
263
|
return {
|
package/dist/setup-prompts.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { updateSettings } from '@clack/core';
|
|
1
2
|
import { autocomplete, autocompleteMultiselect, cancel, confirm, intro, isCancel, log, multiselect, note, select, text, } from '@clack/prompts';
|
|
2
|
-
import {
|
|
3
|
+
import { unicodeSupported } from './io/symbols.js';
|
|
4
|
+
import { colorDepthForOutput, isWritableTtyOutput } from './io/tty.js';
|
|
3
5
|
import { withMenuOptionsSpacing, withTextInputNavigation } from './prompt-navigation.js';
|
|
6
|
+
import { renderKtxSetupBanner } from './setup-banner.js';
|
|
4
7
|
import { revealPassword } from './reveal-password-prompt.js';
|
|
5
8
|
import { withSetupInterruptConfirmation } from './setup-interrupt.js';
|
|
9
|
+
// clack remaps Tab to Space only on non-text prompts (flat multiselect/select/
|
|
10
|
+
// confirm); text inputs and autocomplete search set _track, so typed Tab is
|
|
11
|
+
// untouched. This makes Tab the single documented select key across setup.
|
|
12
|
+
updateSettings({ aliases: { tab: 'space' } });
|
|
6
13
|
const DEFAULT_SETUP_CANCEL_MESSAGE = 'Setup cancelled.';
|
|
7
14
|
export function createKtxSetupPromptAdapter(options) {
|
|
8
15
|
const cancelMessage = options.cancelMessage ?? DEFAULT_SETUP_CANCEL_MESSAGE;
|
|
@@ -106,6 +113,14 @@ export function createKtxSetupUiAdapter() {
|
|
|
106
113
|
return {
|
|
107
114
|
intro(title, io) {
|
|
108
115
|
if (isWritableTtyOutput(io.stdout)) {
|
|
116
|
+
const banner = renderKtxSetupBanner({
|
|
117
|
+
columns: io.stdout.columns ?? 80,
|
|
118
|
+
colorDepth: colorDepthForOutput(io.stdout),
|
|
119
|
+
unicode: unicodeSupported,
|
|
120
|
+
});
|
|
121
|
+
if (banner !== '') {
|
|
122
|
+
io.stdout.write(banner);
|
|
123
|
+
}
|
|
109
124
|
intro(title, { output: io.stdout });
|
|
110
125
|
return;
|
|
111
126
|
}
|
package/dist/setup-ready-menu.js
CHANGED
|
@@ -56,7 +56,7 @@ export async function runKtxSetupReadyChangeMenu(status, deps = {}) {
|
|
|
56
56
|
{ value: 'databases', label: 'Databases' },
|
|
57
57
|
{ value: 'sources', label: 'Context sources' },
|
|
58
58
|
...(status.runtime.required ? [{ value: 'runtime', label: 'Runtime' }] : []),
|
|
59
|
-
{ value: 'context', label: 'Rebuild
|
|
59
|
+
{ value: 'context', label: 'Rebuild ktx context' },
|
|
60
60
|
{ value: 'agents', label: 'Agent integration' },
|
|
61
61
|
{ value: 'exit', label: 'Exit' },
|
|
62
62
|
],
|
package/dist/setup-sources.js
CHANGED
|
@@ -16,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\
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
116
|
-
{ value: 'agents', label: 'Connect a coding agent to
|
|
115
|
+
{ value: 'new-project', label: 'Create a new ktx project' },
|
|
116
|
+
{ value: 'agents', label: 'Connect a coding agent to ktx' },
|
|
117
117
|
{ value: 'status', label: 'Check setup status' },
|
|
118
|
-
{ value: 'demo', label: 'Explore a pre-built
|
|
118
|
+
{ value: 'demo', label: 'Explore a pre-built ktx project' },
|
|
119
119
|
{ value: 'exit', label: 'Exit' },
|
|
120
120
|
]
|
|
121
121
|
: [
|
|
122
|
-
{ value: 'setup', label: 'Set up
|
|
122
|
+
{ value: 'setup', label: 'Set up ktx for my data' },
|
|
123
123
|
{ value: 'status', label: 'Check setup status' },
|
|
124
|
-
{ value: 'demo', label: 'Explore a pre-built
|
|
124
|
+
{ value: 'demo', label: 'Explore a pre-built ktx project' },
|
|
125
125
|
{ value: 'exit', label: 'Exit' },
|
|
126
126
|
];
|
|
127
127
|
const action = (await prompts.select({
|
|
@@ -282,16 +282,16 @@ function formatContextBuilt(status) {
|
|
|
282
282
|
export function formatKtxSetupStatus(status) {
|
|
283
283
|
if (!status.project.ready) {
|
|
284
284
|
return [
|
|
285
|
-
`No
|
|
285
|
+
`No ktx project found at ${status.project.path}.`,
|
|
286
286
|
'',
|
|
287
287
|
'Check another project: ktx --project-dir <folder> status',
|
|
288
288
|
'Or from that folder: ktx status',
|
|
289
|
-
'Create a new
|
|
289
|
+
'Create a new ktx project here: ktx setup',
|
|
290
290
|
'',
|
|
291
291
|
].join('\n');
|
|
292
292
|
}
|
|
293
293
|
const lines = [
|
|
294
|
-
`
|
|
294
|
+
`ktx project: ${status.project.path}`,
|
|
295
295
|
`Project ready: ${formatReady(status.project.ready)}`,
|
|
296
296
|
`LLM ready: ${formatReady(status.llm.ready)}${status.llm.model ? ` (${status.llm.model})` : ''}`,
|
|
297
297
|
`Embeddings ready: ${formatReady(status.embeddings.ready)}${status.embeddings.model ? ` (${status.embeddings.model})` : ''}`,
|
|
@@ -302,7 +302,7 @@ export function formatKtxSetupStatus(status) {
|
|
|
302
302
|
`Runtime ready: ${formatReady(status.runtime.ready)}${status.runtime.features.length > 0 ? ` (${status.runtime.features.join(', ')})` : ''}`,
|
|
303
303
|
]
|
|
304
304
|
: []),
|
|
305
|
-
`
|
|
305
|
+
`ktx context built: ${formatContextBuilt(status.context)}`,
|
|
306
306
|
`Agent integration ready: ${formatReady(status.agents.some((agent) => agent.ready))}${status.agents.length > 0 ? ` (${status.agents.map((agent) => `${agent.target}:${agent.scope}`).join(', ')})` : ''}`,
|
|
307
307
|
];
|
|
308
308
|
if (!status.context.ready && status.context.status === 'failed' && status.context.detail) {
|
|
@@ -327,7 +327,7 @@ export function formatKtxSetupCompletionSummary(status, options = {}) {
|
|
|
327
327
|
lines.push('', 'REQUIRED BEFORE USING AGENTS', '', ...agentNextActions.split('\n').map((line) => (line ? ` ${line}` : '')));
|
|
328
328
|
}
|
|
329
329
|
lines.push('', agentNextActions ? 'After that, try' : 'Try it');
|
|
330
|
-
lines.push(' Ask your agent: "Use
|
|
330
|
+
lines.push(' Ask your agent: "Use ktx to show me the available tables."');
|
|
331
331
|
return lines.join('\n');
|
|
332
332
|
}
|
|
333
333
|
function setupStatusReady(status) {
|
|
@@ -357,7 +357,7 @@ function setupRuntimeInstallPolicy(args) {
|
|
|
357
357
|
}
|
|
358
358
|
async function commitSetupConfigChanges(projectDir) {
|
|
359
359
|
const project = await loadKtxProject({ projectDir });
|
|
360
|
-
await project.git.commitFile('ktx.yaml', 'setup: update
|
|
360
|
+
await project.git.commitFile('ktx.yaml', 'setup: update ktx project config', 'ktx setup', 'setup@ktx.local');
|
|
361
361
|
}
|
|
362
362
|
export async function runKtxSetup(args, io, deps = {}) {
|
|
363
363
|
try {
|
|
@@ -372,7 +372,7 @@ export async function runKtxSetup(args, io, deps = {}) {
|
|
|
372
372
|
}
|
|
373
373
|
async function runKtxSetupInner(args, io, deps = {}) {
|
|
374
374
|
const setupUi = deps.setupUi ?? createKtxSetupUiAdapter();
|
|
375
|
-
setupUi.intro('
|
|
375
|
+
setupUi.intro('ktx setup', io);
|
|
376
376
|
setupUi.note(KTX_DOCS_URL, '📚 Docs', io);
|
|
377
377
|
let entryAction;
|
|
378
378
|
let projectResult;
|
|
@@ -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
|
|
687
|
+
setupUi.note(formatKtxSetupCompletionSummary(status, { agentNextActions }), agentNextActions ? 'Finish ktx agent setup' : 'ktx project ready', io, {
|
|
688
688
|
format: (line) => line,
|
|
689
689
|
});
|
|
690
690
|
}
|