@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/runtime.js
CHANGED
|
@@ -5,7 +5,7 @@ function writeJson(io, value) {
|
|
|
5
5
|
}
|
|
6
6
|
function writeInstallResult(io, result) {
|
|
7
7
|
const verb = result.status === 'ready' ? 'Using existing' : 'Installed';
|
|
8
|
-
io.stdout.write(`${verb}
|
|
8
|
+
io.stdout.write(`${verb} ktx Python runtime\n`);
|
|
9
9
|
io.stdout.write(`version: ${result.manifest.cliVersion}\n`);
|
|
10
10
|
io.stdout.write(`features: ${result.manifest.features.join(', ')}\n`);
|
|
11
11
|
io.stdout.write(`python: ${result.manifest.python.executable}\n`);
|
|
@@ -15,7 +15,7 @@ function writeInstallResult(io, result) {
|
|
|
15
15
|
}
|
|
16
16
|
function writeDaemonStart(io, result) {
|
|
17
17
|
const verb = result.status === 'reused' ? 'Using existing' : 'Started';
|
|
18
|
-
io.stdout.write(`${verb}
|
|
18
|
+
io.stdout.write(`${verb} ktx daemon\n`);
|
|
19
19
|
io.stdout.write(`url: ${result.baseUrl}\n`);
|
|
20
20
|
io.stdout.write(`pid: ${result.state.pid}\n`);
|
|
21
21
|
io.stdout.write(`version: ${result.state.version}\n`);
|
|
@@ -26,10 +26,10 @@ function writeDaemonStart(io, result) {
|
|
|
26
26
|
}
|
|
27
27
|
function writeDaemonStop(io, result) {
|
|
28
28
|
if (result.status === 'already-stopped') {
|
|
29
|
-
io.stdout.write('
|
|
29
|
+
io.stdout.write('ktx daemon already stopped\n');
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
|
-
io.stdout.write('Stopped
|
|
32
|
+
io.stdout.write('Stopped ktx daemon\n');
|
|
33
33
|
io.stdout.write(`pid: ${result.state?.pid ?? 'unknown'}\n`);
|
|
34
34
|
io.stdout.write(`state: ${result.layout.daemonStatePath}\n`);
|
|
35
35
|
}
|
|
@@ -42,11 +42,11 @@ function writeDaemonStopAll(io, result) {
|
|
|
42
42
|
result.stale.length === 0 &&
|
|
43
43
|
result.failed.length === 0 &&
|
|
44
44
|
result.scanErrors.length === 0) {
|
|
45
|
-
io.stdout.write('No
|
|
45
|
+
io.stdout.write('No ktx daemons found\n');
|
|
46
46
|
return 0;
|
|
47
47
|
}
|
|
48
48
|
if (failed === 0) {
|
|
49
|
-
io.stdout.write(`Stopped ${result.stopped.length}
|
|
49
|
+
io.stdout.write(`Stopped ${result.stopped.length} ktx daemons\n`);
|
|
50
50
|
if (result.stale.length > 0) {
|
|
51
51
|
io.stdout.write(`Cleaned ${result.stale.length} stale daemon states\n`);
|
|
52
52
|
}
|
|
@@ -58,7 +58,7 @@ function writeDaemonStopAll(io, result) {
|
|
|
58
58
|
}
|
|
59
59
|
return 0;
|
|
60
60
|
}
|
|
61
|
-
io.stderr.write(`Stopped ${result.stopped.length}
|
|
61
|
+
io.stderr.write(`Stopped ${result.stopped.length} ktx daemons; failed ${result.failed.length}${result.stale.length > 0 ? `; cleaned stale ${result.stale.length}` : ''}\n`);
|
|
62
62
|
for (const entry of result.failed) {
|
|
63
63
|
io.stderr.write(`pid: ${entry.pid} source: ${entry.source}${entry.url ? ` url: ${entry.url}` : ''}${entry.health ? ` health: ${entry.health}` : ''} detail: ${entry.detail}\n`);
|
|
64
64
|
}
|
|
@@ -68,7 +68,7 @@ function writeDaemonStopAll(io, result) {
|
|
|
68
68
|
return 1;
|
|
69
69
|
}
|
|
70
70
|
function writeStatus(io, status) {
|
|
71
|
-
io.stdout.write('
|
|
71
|
+
io.stdout.write('ktx Python runtime\n');
|
|
72
72
|
io.stdout.write(`status: ${status.kind}\n`);
|
|
73
73
|
io.stdout.write(`detail: ${status.detail}\n`);
|
|
74
74
|
io.stdout.write(`runtime root: ${status.layout.runtimeRoot}\n`);
|
|
@@ -80,7 +80,7 @@ function writeStatus(io, status) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
function writeRuntimeChecks(io, checks) {
|
|
83
|
-
io.stdout.write('
|
|
83
|
+
io.stdout.write('ktx Python runtime checks\n');
|
|
84
84
|
for (const check of checks) {
|
|
85
85
|
io.stdout.write(`${check.status.toUpperCase()} ${check.label}: ${check.detail}\n`);
|
|
86
86
|
if (check.fix) {
|
package/dist/scan.js
CHANGED
|
@@ -206,7 +206,7 @@ function writeHumanReportBody(report, io) {
|
|
|
206
206
|
}
|
|
207
207
|
function writeRunSummary(report, projectDir, io) {
|
|
208
208
|
const styled = shouldUseStyledOutput(io);
|
|
209
|
-
io.stdout.write(`${styled ? green('✓') : ''}${styled ? ' ' : ''}
|
|
209
|
+
io.stdout.write(`${styled ? green('✓') : ''}${styled ? ' ' : ''}ktx scan completed\n`);
|
|
210
210
|
io.stdout.write('Status: done\n');
|
|
211
211
|
writeHumanReportBody(report, io);
|
|
212
212
|
const projectDirArg = quoteCliArg(projectDir);
|
package/dist/setup-agents.js
CHANGED
|
@@ -12,14 +12,8 @@ import { errorMessage, writePrefixedLines } from './clack.js';
|
|
|
12
12
|
import { isWritableTtyOutput } from './io/tty.js';
|
|
13
13
|
import { createKtxSetupPromptAdapter, createKtxSetupUiAdapter, } from './setup-prompts.js';
|
|
14
14
|
import { readKtxMcpDaemonStatus } from './managed-mcp-daemon.js';
|
|
15
|
+
import { withMultiselectNavigation } from './prompt-navigation.js';
|
|
15
16
|
const MCP_DAEMON_REQUIRED_NOTICE = 'mcp-daemon-required';
|
|
16
|
-
function writeSetupInfo(io, message) {
|
|
17
|
-
if (isWritableTtyOutput(io.stdout)) {
|
|
18
|
-
log.info(message, { output: io.stdout });
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
io.stdout.write(`${message}\n`);
|
|
22
|
-
}
|
|
23
17
|
function writeSetupStep(io, message) {
|
|
24
18
|
if (isWritableTtyOutput(io.stdout)) {
|
|
25
19
|
log.step(message, { output: io.stdout });
|
|
@@ -148,7 +142,7 @@ function codexSnippet(endpoint) {
|
|
|
148
142
|
if (endpoint.tokenAuth) {
|
|
149
143
|
return [
|
|
150
144
|
'Codex MCP config does not currently document HTTP headers.',
|
|
151
|
-
'Run
|
|
145
|
+
'Run ktx on loopback without token auth for Codex, or configure headers after Codex documents support.',
|
|
152
146
|
].join('\n');
|
|
153
147
|
}
|
|
154
148
|
return [`[mcp_servers.ktx]`, `url = "${endpoint.url}"`].join('\n');
|
|
@@ -406,16 +400,16 @@ function cliInstructionContent(input) {
|
|
|
406
400
|
return [
|
|
407
401
|
'---',
|
|
408
402
|
'name: ktx',
|
|
409
|
-
'description: Use local
|
|
403
|
+
'description: Use local ktx semantic context and wiki knowledge for this project.',
|
|
410
404
|
'---',
|
|
411
405
|
'',
|
|
412
|
-
'#
|
|
406
|
+
'# ktx Local Context',
|
|
413
407
|
'',
|
|
414
|
-
'This is an admin/developer CLI helper. End-user data agents should use the
|
|
408
|
+
'This is an admin/developer CLI helper. End-user data agents should use the ktx MCP tools when available.',
|
|
415
409
|
'',
|
|
416
410
|
`Use this project with \`--project-dir ${input.projectDir}\`.`,
|
|
417
|
-
'Commands are pinned to the local
|
|
418
|
-
'If the CLI path no longer exists after moving this checkout or reinstalling
|
|
411
|
+
'Commands are pinned to the local ktx CLI path that created this file, so agents do not need `ktx` in PATH.',
|
|
412
|
+
'If the CLI path no longer exists after moving this checkout or reinstalling ktx, rerun `ktx setup --agents`.',
|
|
419
413
|
'',
|
|
420
414
|
'Agents must not print secrets, credential references, environment variable values, or file contents from ' +
|
|
421
415
|
'`.ktx/secrets`.',
|
|
@@ -526,7 +520,7 @@ function mergeManifest(projectDir, existing, installs, entries) {
|
|
|
526
520
|
export async function removeKtxAgentInstall(projectDir, io) {
|
|
527
521
|
const manifest = await readKtxAgentInstallManifest(projectDir);
|
|
528
522
|
if (!manifest) {
|
|
529
|
-
io.stdout.write('No
|
|
523
|
+
io.stdout.write('No ktx agent installation manifest found.\n');
|
|
530
524
|
return 0;
|
|
531
525
|
}
|
|
532
526
|
for (const entry of manifest.entries) {
|
|
@@ -536,7 +530,7 @@ export async function removeKtxAgentInstall(projectDir, io) {
|
|
|
536
530
|
await removeJsonKey(entry.path, entry.jsonPath).catch(() => undefined);
|
|
537
531
|
}
|
|
538
532
|
await rm(agentInstallManifestPath(projectDir), { force: true });
|
|
539
|
-
io.stdout.write('Removed
|
|
533
|
+
io.stdout.write('Removed ktx agent integration files from manifest.\n');
|
|
540
534
|
return 0;
|
|
541
535
|
}
|
|
542
536
|
function createPromptAdapter() {
|
|
@@ -768,7 +762,7 @@ function formatAgentNextActions(input) {
|
|
|
768
762
|
if (claudeCodeInstall) {
|
|
769
763
|
lines.push(`${step}. Open Claude Code`);
|
|
770
764
|
if (claudeCodeInstall.scope === 'project') {
|
|
771
|
-
lines.push(' Open Claude Code from the
|
|
765
|
+
lines.push(' Open Claude Code from the ktx project directory:');
|
|
772
766
|
lines.push('');
|
|
773
767
|
lines.push(' RUN:');
|
|
774
768
|
lines.push(` cd ${shellScriptQuote(projectDir)}`);
|
|
@@ -785,7 +779,7 @@ function formatAgentNextActions(input) {
|
|
|
785
779
|
if (cursorInstall) {
|
|
786
780
|
lines.push(`${step}. Open Cursor`);
|
|
787
781
|
if (cursorInstall.scope === 'project') {
|
|
788
|
-
lines.push(' Open Cursor from the
|
|
782
|
+
lines.push(' Open Cursor from the ktx project directory:');
|
|
789
783
|
lines.push('');
|
|
790
784
|
lines.push(' OPEN:');
|
|
791
785
|
lines.push(` ${projectDir}`);
|
|
@@ -798,7 +792,7 @@ function formatAgentNextActions(input) {
|
|
|
798
792
|
}
|
|
799
793
|
if (input.installs.some((install) => install.target === 'claude-desktop')) {
|
|
800
794
|
lines.push(`${step}. Restart Claude Desktop`);
|
|
801
|
-
lines.push(' Claude Desktop loads
|
|
795
|
+
lines.push(' Claude Desktop loads ktx MCP after restart.');
|
|
802
796
|
pushBlankLine(lines);
|
|
803
797
|
step += 1;
|
|
804
798
|
const skillBundlePaths = claudeDesktopSkillBundlePathsForInstalls(projectDir, input.installs);
|
|
@@ -809,7 +803,7 @@ function formatAgentNextActions(input) {
|
|
|
809
803
|
for (const path of skillBundlePaths) {
|
|
810
804
|
lines.push(` ${path}`);
|
|
811
805
|
}
|
|
812
|
-
lines.push(' Toggle the uploaded
|
|
806
|
+
lines.push(' Toggle the uploaded ktx skills on.');
|
|
813
807
|
pushBlankLine(lines);
|
|
814
808
|
step += 1;
|
|
815
809
|
}
|
|
@@ -859,22 +853,19 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
859
853
|
return { status: 'skipped', projectDir: args.projectDir };
|
|
860
854
|
}
|
|
861
855
|
const prompts = deps.prompts ?? createPromptAdapter();
|
|
862
|
-
if (args.inputMode === 'auto' && args.target === undefined) {
|
|
863
|
-
writeSetupInfo(io, 'Space to select, Enter to confirm, Esc to go back.');
|
|
864
|
-
}
|
|
865
856
|
const mode = args.inputMode === 'disabled'
|
|
866
857
|
? args.mode
|
|
867
858
|
: (await prompts.select({
|
|
868
|
-
message: 'What should agents be allowed to do with this
|
|
859
|
+
message: 'What should agents be allowed to do with this ktx project?',
|
|
869
860
|
options: [
|
|
870
861
|
{
|
|
871
862
|
value: 'mcp',
|
|
872
|
-
label: 'Ask data questions with
|
|
863
|
+
label: 'Ask data questions with ktx MCP',
|
|
873
864
|
hint: 'Installs the MCP connection and analytics workflow skill. Best for normal use.',
|
|
874
865
|
},
|
|
875
866
|
{
|
|
876
867
|
value: 'mcp-cli',
|
|
877
|
-
label: 'Ask data questions + manage
|
|
868
|
+
label: 'Ask data questions + manage ktx with CLI commands',
|
|
878
869
|
hint: 'Adds an admin CLI skill so agents can run ktx status, sl, wiki, and setup commands.',
|
|
879
870
|
},
|
|
880
871
|
{
|
|
@@ -895,7 +886,7 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
895
886
|
: args.inputMode === 'disabled'
|
|
896
887
|
? []
|
|
897
888
|
: (await prompts.multiselect({
|
|
898
|
-
message: 'Which agent targets should
|
|
889
|
+
message: withMultiselectNavigation('Which agent targets should ktx install?'),
|
|
899
890
|
options: [
|
|
900
891
|
{ value: 'claude-code', label: 'Claude Code' },
|
|
901
892
|
{ value: 'claude-desktop', label: 'Claude Desktop' },
|
|
@@ -920,17 +911,17 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
920
911
|
scopeTargets.length > 0 &&
|
|
921
912
|
scopeTargets.every(targetSupportsGlobalScope)
|
|
922
913
|
? (await prompts.select({
|
|
923
|
-
message: `Where should
|
|
914
|
+
message: `Where should ktx install supported agent config?\n\nktx project: ${resolve(args.projectDir)}`,
|
|
924
915
|
options: [
|
|
925
916
|
{
|
|
926
917
|
value: 'project',
|
|
927
|
-
label: 'Project scope (
|
|
928
|
-
hint: 'Only agents opened from this
|
|
918
|
+
label: 'Project scope (ktx project directory)',
|
|
919
|
+
hint: 'Only agents opened from this ktx project path load the project-scoped config.',
|
|
929
920
|
},
|
|
930
921
|
{
|
|
931
922
|
value: 'global',
|
|
932
923
|
label: 'Global scope (user config)',
|
|
933
|
-
hint: 'Agents can load this
|
|
924
|
+
hint: 'Agents can load this ktx project from any working directory.',
|
|
934
925
|
},
|
|
935
926
|
],
|
|
936
927
|
}))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `ktx setup` intro banner: a lowercase ktx wordmark drawn with
|
|
3
|
+
* half-block glyphs over the brand-orange gradient, followed by the product
|
|
4
|
+
* tagline. Rendering is a pure function of the options so output stays
|
|
5
|
+
* deterministic in tests.
|
|
6
|
+
*/
|
|
7
|
+
export interface KtxSetupBannerOptions {
|
|
8
|
+
/** Terminal width in columns. */
|
|
9
|
+
columns: number;
|
|
10
|
+
/** Color depth in bits, as reported by `tty.WriteStream#getColorDepth`; 1 disables color. */
|
|
11
|
+
colorDepth: number;
|
|
12
|
+
/** Whether the terminal renders Unicode block glyphs. */
|
|
13
|
+
unicode: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns the banner block ending right above the clack intro line, or an
|
|
17
|
+
* empty string when the terminal cannot display it (no Unicode support or
|
|
18
|
+
* too narrow).
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderKtxSetupBanner(options: KtxSetupBannerOptions): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `ktx setup` intro banner: a lowercase ktx wordmark drawn with
|
|
3
|
+
* half-block glyphs over the brand-orange gradient, followed by the product
|
|
4
|
+
* tagline. Rendering is a pure function of the options so output stays
|
|
5
|
+
* deterministic in tests.
|
|
6
|
+
*/
|
|
7
|
+
const WORDMARK = [
|
|
8
|
+
{ art: '███ ███', rgb: [253, 186, 116], ansi256: 215 },
|
|
9
|
+
{ art: '███ ▄██▀ ███████ ▀██▄ ▄██▀', rgb: [251, 146, 60], ansi256: 214 },
|
|
10
|
+
{ art: '███▄██▀ ███ ▀████▀', rgb: [249, 115, 22], ansi256: 208 },
|
|
11
|
+
{ art: '███▀██▄ ███ ▄████▄', rgb: [234, 88, 12], ansi256: 202 },
|
|
12
|
+
{ art: '███ ▀██▄ ███ ▄██▀ ▀██▄', rgb: [194, 65, 12], ansi256: 166 },
|
|
13
|
+
];
|
|
14
|
+
const TAGLINE = 'context layer for data agents';
|
|
15
|
+
const INDENT = ' ';
|
|
16
|
+
const BANNER_WIDTH = Math.max(...WORDMARK.map((row) => row.art.length), TAGLINE.length) + INDENT.length;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the banner block ending right above the clack intro line, or an
|
|
19
|
+
* empty string when the terminal cannot display it (no Unicode support or
|
|
20
|
+
* too narrow).
|
|
21
|
+
*/
|
|
22
|
+
export function renderKtxSetupBanner(options) {
|
|
23
|
+
if (!options.unicode || options.columns < BANNER_WIDTH) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
const art = WORDMARK.map((row) => INDENT + colorizeBannerRow(row, options.colorDepth));
|
|
27
|
+
const tagline = INDENT + (options.colorDepth > 1 ? `\u001b[2m${TAGLINE}\u001b[22m` : TAGLINE);
|
|
28
|
+
return `\n${art.join('\n')}\n\n${tagline}\n\n`;
|
|
29
|
+
}
|
|
30
|
+
function colorizeBannerRow(row, colorDepth) {
|
|
31
|
+
if (colorDepth >= 24) {
|
|
32
|
+
const [r, g, b] = row.rgb;
|
|
33
|
+
return `\u001b[38;2;${r};${g};${b}m${row.art}\u001b[39m`;
|
|
34
|
+
}
|
|
35
|
+
if (colorDepth >= 8) {
|
|
36
|
+
return `\u001b[38;5;${row.ansi256}m${row.art}\u001b[39m`;
|
|
37
|
+
}
|
|
38
|
+
return row.art;
|
|
39
|
+
}
|
package/dist/setup-context.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join, resolve } from 'node:path';
|
|
|
4
4
|
import { loadKtxProject } from './context/project/project.js';
|
|
5
5
|
import { markKtxSetupStateStepComplete, readKtxSetupState } from './context/project/setup-config.js';
|
|
6
6
|
import { serializeKtxProjectConfig } from './context/project/config.js';
|
|
7
|
-
import { errorMessage, writePrefixedLines } from './clack.js';
|
|
7
|
+
import { createCliSpinner, errorMessage, writePrefixedLines } from './clack.js';
|
|
8
8
|
import { formatErrorDetail } from './telemetry/scrubber.js';
|
|
9
9
|
import { buildPublicIngestPlan } from './public-ingest.js';
|
|
10
10
|
import { runKtxConnection } from './connection.js';
|
|
@@ -189,13 +189,22 @@ async function defaultGateTestConnection(projectDir, connectionId, io) {
|
|
|
189
189
|
* test's output is captured in a buffer and discarded so raw error text never
|
|
190
190
|
* reaches the user — callers surface only the connection id and connector type.
|
|
191
191
|
*/
|
|
192
|
-
async function testRequiredConnections(projectDir, project, targets, testConnection) {
|
|
192
|
+
async function testRequiredConnections(projectDir, project, targets, testConnection, io) {
|
|
193
193
|
const failures = [];
|
|
194
|
-
|
|
194
|
+
const connectionIds = requiredConnectionIds(targets);
|
|
195
|
+
for (const [index, connectionId] of connectionIds.entries()) {
|
|
196
|
+
const driver = connectorTypeLabel(project, connectionId);
|
|
197
|
+
const counter = connectionIds.length > 1 ? ` (${index + 1}/${connectionIds.length})` : '';
|
|
198
|
+
const spinner = createCliSpinner(io);
|
|
199
|
+
spinner.start(`Testing connection ${connectionId}${counter}…`);
|
|
195
200
|
const buffered = createBufferedCommandIo();
|
|
196
201
|
const exitCode = await testConnection(projectDir, connectionId, buffered);
|
|
197
|
-
if (exitCode
|
|
198
|
-
|
|
202
|
+
if (exitCode === 0) {
|
|
203
|
+
spinner.stop(`Connection ${connectionId} (${driver}) is reachable`);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
spinner.error(`Connection ${connectionId} (${driver}) is not reachable`);
|
|
207
|
+
failures.push({ connectionId, driver });
|
|
199
208
|
}
|
|
200
209
|
}
|
|
201
210
|
return failures.length === 0 ? { ok: true } : { ok: false, failures };
|
|
@@ -213,7 +222,7 @@ async function prepareBuildTargets(args, io) {
|
|
|
213
222
|
if (args.allowEmpty === true) {
|
|
214
223
|
return { kind: 'result', result: { status: 'skipped', projectDir: args.projectDir } };
|
|
215
224
|
}
|
|
216
|
-
io.stderr.write('No databases or context sources are configured for a
|
|
225
|
+
io.stderr.write('No databases or context sources are configured for a ktx context build.\n');
|
|
217
226
|
return { kind: 'result', result: { status: 'failed', projectDir: args.projectDir } };
|
|
218
227
|
}
|
|
219
228
|
const preflightPlan = buildPublicIngestPlan(project, { projectDir: project.projectDir, all: true });
|
|
@@ -228,12 +237,12 @@ async function prepareBuildTargets(args, io) {
|
|
|
228
237
|
return { kind: 'ready', project, targets };
|
|
229
238
|
}
|
|
230
239
|
function writeConnectionGateFailureLines(io, projectDir, failures) {
|
|
231
|
-
io.stderr.write('
|
|
240
|
+
io.stderr.write('ktx cannot build context: a required connection failed its live test.\n\n');
|
|
232
241
|
io.stderr.write('Failed connections:\n');
|
|
233
242
|
for (const failure of failures) {
|
|
234
243
|
io.stderr.write(` ${failure.connectionId} (${failure.driver})\n`);
|
|
235
244
|
}
|
|
236
|
-
io.stderr.write('\nEach connection must be reachable before
|
|
245
|
+
io.stderr.write('\nEach connection must be reachable before ktx builds context.\n');
|
|
237
246
|
io.stderr.write(`Run \`ktx connection test <id> --project-dir ${resolve(projectDir)}\` to see the error, fix the connection, then retry.\n`);
|
|
238
247
|
}
|
|
239
248
|
function connectionGateFailureReason(failures) {
|
|
@@ -391,7 +400,7 @@ async function markContextComplete(projectDir) {
|
|
|
391
400
|
await markKtxSetupStateStepComplete(projectDir, 'context');
|
|
392
401
|
}
|
|
393
402
|
function writeMissingCapabilities(missing, io) {
|
|
394
|
-
io.stderr.write('
|
|
403
|
+
io.stderr.write('ktx cannot build agent-ready context yet.\n\n');
|
|
395
404
|
io.stderr.write('Missing:\n');
|
|
396
405
|
for (const item of missing) {
|
|
397
406
|
io.stderr.write(` ${item}\n`);
|
|
@@ -404,7 +413,7 @@ function writeSkippedContext(io) {
|
|
|
404
413
|
io.stdout.write('\nLeaving context unbuilt for now.\n');
|
|
405
414
|
}
|
|
406
415
|
function writeSuccess(readiness, targets, io) {
|
|
407
|
-
io.stdout.write('\
|
|
416
|
+
io.stdout.write('\nktx context is ready for agents.\n\n');
|
|
408
417
|
io.stdout.write('Databases:\n');
|
|
409
418
|
if (targets.primarySourceConnectionIds.length === 0) {
|
|
410
419
|
io.stdout.write(' none\n');
|
|
@@ -428,7 +437,7 @@ function writeSuccess(readiness, targets, io) {
|
|
|
428
437
|
io.stdout.write(` Semantic search: ${readiness.semanticSearchReady ? 'ready' : 'not ready'}\n`);
|
|
429
438
|
}
|
|
430
439
|
function writeExistingContextSuccess(readiness, io) {
|
|
431
|
-
io.stdout.write('\
|
|
440
|
+
io.stdout.write('\nktx context is ready for agents.\n\n');
|
|
432
441
|
io.stdout.write('Existing context artifacts were found from setup ingest.\n\n');
|
|
433
442
|
io.stdout.write('Verification:\n');
|
|
434
443
|
io.stdout.write(` Agent context: ${readiness.agentContextReady ? 'ready' : 'not ready'}\n`);
|
|
@@ -436,8 +445,8 @@ function writeExistingContextSuccess(readiness, io) {
|
|
|
436
445
|
}
|
|
437
446
|
async function promptForBuild(prompts) {
|
|
438
447
|
return (await prompts.select({
|
|
439
|
-
message: 'Build
|
|
440
|
-
'
|
|
448
|
+
message: 'Build ktx context for agents?\n\n' +
|
|
449
|
+
'ktx is fully configured and ready to build context. This may take a few minutes to a few hours.',
|
|
441
450
|
options: [
|
|
442
451
|
{ value: 'build', label: 'Build context now (recommended)' },
|
|
443
452
|
{ value: 'skip', label: 'Leave context unbuilt and exit setup' },
|
|
@@ -517,7 +526,7 @@ async function runBuild(args, io, deps, project, targets) {
|
|
|
517
526
|
failureReason: readiness.details.join(' '),
|
|
518
527
|
...(lastSourceProgress ? { sourceProgress: lastSourceProgress } : {}),
|
|
519
528
|
});
|
|
520
|
-
io.stderr.write('
|
|
529
|
+
io.stderr.write('ktx context build did not pass agent-readiness verification.\n');
|
|
521
530
|
for (const detail of readiness.details) {
|
|
522
531
|
io.stderr.write(` ${detail}\n`);
|
|
523
532
|
}
|
|
@@ -607,7 +616,7 @@ export async function runKtxSetupContextStep(args, io, deps = {}) {
|
|
|
607
616
|
// error text.
|
|
608
617
|
const testConnection = deps.testConnection ?? defaultGateTestConnection;
|
|
609
618
|
while (true) {
|
|
610
|
-
const gate = await testRequiredConnections(args.projectDir, project, targets, testConnection);
|
|
619
|
+
const gate = await testRequiredConnections(args.projectDir, project, targets, testConnection, io);
|
|
611
620
|
if (gate.ok) {
|
|
612
621
|
return await runBuild(args, io, deps, project, targets);
|
|
613
622
|
}
|
package/dist/setup-databases.js
CHANGED
|
@@ -119,7 +119,7 @@ function driverLabel(driver) {
|
|
|
119
119
|
return DRIVER_LABELS[driver];
|
|
120
120
|
}
|
|
121
121
|
function connectionNamePrompt(label) {
|
|
122
|
-
return `Name this ${label} connection\
|
|
122
|
+
return `Name this ${label} connection\nktx will use this short name in commands and config. You can rename it now.`;
|
|
123
123
|
}
|
|
124
124
|
function missingConnectionDetailsPrompt(label, canReturnToDriverSelection) {
|
|
125
125
|
const backDestination = canReturnToDriverSelection ? 'database selection' : 'the previous setup step';
|
|
@@ -163,12 +163,6 @@ function numberConfigField(connection, field) {
|
|
|
163
163
|
const value = connection?.[field];
|
|
164
164
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
165
165
|
}
|
|
166
|
-
function historicSqlConfigRecord(connection) {
|
|
167
|
-
const historicSql = connection?.historicSql;
|
|
168
|
-
return historicSql && typeof historicSql === 'object' && !Array.isArray(historicSql)
|
|
169
|
-
? historicSql
|
|
170
|
-
: null;
|
|
171
|
-
}
|
|
172
166
|
function contextRecord(connection) {
|
|
173
167
|
const context = connection?.context;
|
|
174
168
|
return context && typeof context === 'object' && !Array.isArray(context) ? context : {};
|
|
@@ -179,28 +173,15 @@ function queryHistoryConfigRecord(connection) {
|
|
|
179
173
|
? queryHistory
|
|
180
174
|
: null;
|
|
181
175
|
}
|
|
182
|
-
function stripLegacyHistoricSql(connection) {
|
|
183
|
-
const { historicSql: _historicSql, ...rest } = connection;
|
|
184
|
-
return rest;
|
|
185
|
-
}
|
|
186
176
|
function withQueryHistoryConfig(connection, queryHistory) {
|
|
187
177
|
return {
|
|
188
|
-
...
|
|
178
|
+
...connection,
|
|
189
179
|
context: {
|
|
190
180
|
...contextRecord(connection),
|
|
191
181
|
queryHistory,
|
|
192
182
|
},
|
|
193
183
|
};
|
|
194
184
|
}
|
|
195
|
-
function migrateLegacyHistoricSqlConnection(connection) {
|
|
196
|
-
const existingQueryHistory = queryHistoryConfigRecord(connection);
|
|
197
|
-
const legacy = historicSqlConfigRecord(connection);
|
|
198
|
-
if (existingQueryHistory || !legacy) {
|
|
199
|
-
return existingQueryHistory ? stripLegacyHistoricSql(connection) : connection;
|
|
200
|
-
}
|
|
201
|
-
const { dialect: _dialect, ...queryHistory } = legacy;
|
|
202
|
-
return withQueryHistoryConfig(connection, queryHistory);
|
|
203
|
-
}
|
|
204
185
|
function setupHistoricSqlProbeResult(outcome) {
|
|
205
186
|
if (!outcome) {
|
|
206
187
|
return { ok: true, lines: [] };
|
|
@@ -871,7 +852,7 @@ async function disableConnectionQueryHistory(projectDir, connectionId) {
|
|
|
871
852
|
if (!connection) {
|
|
872
853
|
return;
|
|
873
854
|
}
|
|
874
|
-
const existing = queryHistoryConfigRecord(connection) ??
|
|
855
|
+
const existing = queryHistoryConfigRecord(connection) ?? {};
|
|
875
856
|
await writeConnectionConfig({
|
|
876
857
|
projectDir,
|
|
877
858
|
connectionId,
|
|
@@ -1004,32 +985,29 @@ async function maybeConfigureDatabaseScope(input) {
|
|
|
1004
985
|
}
|
|
1005
986
|
const cliSchemas = input.args.databaseSchemas;
|
|
1006
987
|
if (input.args.inputMode === 'disabled') {
|
|
1007
|
-
if (spec) {
|
|
1008
|
-
|
|
1009
|
-
if (scopeToWrite.length === 0) {
|
|
1010
|
-
try {
|
|
1011
|
-
scopeToWrite = unique(await (input.deps.listSchemas ?? defaultListSchemas)(input.projectDir, input.connectionId));
|
|
1012
|
-
}
|
|
1013
|
-
catch (error) {
|
|
1014
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
1015
|
-
input.io.stderr.write(`Could not discover ${spec.promptLabel.toLowerCase()} for ${input.connectionId}; ${detail}\n`);
|
|
1016
|
-
return okValidateResult();
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
if (scopeToWrite.length > 0) {
|
|
1020
|
-
await writeScopeConfig({
|
|
1021
|
-
projectDir: input.projectDir,
|
|
1022
|
-
connectionId: input.connectionId,
|
|
1023
|
-
values: scopeToWrite,
|
|
1024
|
-
spec,
|
|
1025
|
-
});
|
|
1026
|
-
const capitalNounPlural = spec.nounPlural[0].toUpperCase() + spec.nounPlural.slice(1);
|
|
1027
|
-
writeSetupSection(input.io, `${capitalNounPlural} saved for ${input.connectionId}`, [
|
|
1028
|
-
`✓ ${scopeToWrite.join(', ')}`,
|
|
1029
|
-
]);
|
|
1030
|
-
}
|
|
988
|
+
if (!spec) {
|
|
989
|
+
return okValidateResult();
|
|
1031
990
|
}
|
|
1032
|
-
|
|
991
|
+
if (cliSchemas.length > 0) {
|
|
992
|
+
await writeScopeConfig({
|
|
993
|
+
projectDir: input.projectDir,
|
|
994
|
+
connectionId: input.connectionId,
|
|
995
|
+
values: cliSchemas,
|
|
996
|
+
spec,
|
|
997
|
+
});
|
|
998
|
+
const capitalNounPlural = spec.nounPlural[0].toUpperCase() + spec.nounPlural.slice(1);
|
|
999
|
+
writeSetupSection(input.io, `${capitalNounPlural} saved for ${input.connectionId}`, [
|
|
1000
|
+
`✓ ${cliSchemas.join(', ')}`,
|
|
1001
|
+
]);
|
|
1002
|
+
return okValidateResult();
|
|
1003
|
+
}
|
|
1004
|
+
if (existingScope.length > 0) {
|
|
1005
|
+
return okValidateResult();
|
|
1006
|
+
}
|
|
1007
|
+
writePrefixedLines((chunk) => input.io.stderr.write(chunk), `No ${spec.nounPlural} configured for ${input.connectionId}. ` +
|
|
1008
|
+
`Pass --database-schema <${spec.noun}> (repeatable) or set ` +
|
|
1009
|
+
`connections.${input.connectionId}.${spec.configArrayField} in ktx.yaml.`);
|
|
1010
|
+
return failedValidateResult();
|
|
1033
1011
|
}
|
|
1034
1012
|
if (spec && cliSchemas.length > 0) {
|
|
1035
1013
|
await writeScopeConfig({
|
|
@@ -1158,20 +1136,14 @@ async function ensureHistoricSqlIngestDefaults(projectDir) {
|
|
|
1158
1136
|
}
|
|
1159
1137
|
async function markDatabasesComplete(projectDir, connectionIds) {
|
|
1160
1138
|
const project = await loadKtxProject({ projectDir });
|
|
1161
|
-
const config = setKtxSetupDatabaseConnectionIds(
|
|
1162
|
-
...project.config,
|
|
1163
|
-
connections: Object.fromEntries(Object.entries(project.config.connections).map(([connectionId, connection]) => [
|
|
1164
|
-
connectionId,
|
|
1165
|
-
migrateLegacyHistoricSqlConnection(connection),
|
|
1166
|
-
])),
|
|
1167
|
-
}, unique(connectionIds));
|
|
1139
|
+
const config = setKtxSetupDatabaseConnectionIds(project.config, unique(connectionIds));
|
|
1168
1140
|
await writeFile(project.configPath, serializeKtxProjectConfig(config), 'utf-8');
|
|
1169
1141
|
await markKtxSetupStateStepComplete(projectDir, 'databases');
|
|
1170
1142
|
}
|
|
1171
1143
|
async function maybeRunHistoricSqlSetupProbe(input) {
|
|
1172
1144
|
const project = await loadKtxProject({ projectDir: input.projectDir });
|
|
1173
1145
|
const connection = project.config.connections[input.connectionId];
|
|
1174
|
-
const queryHistory = queryHistoryConfigRecord(connection)
|
|
1146
|
+
const queryHistory = queryHistoryConfigRecord(connection);
|
|
1175
1147
|
if (queryHistory?.enabled !== true) {
|
|
1176
1148
|
return true;
|
|
1177
1149
|
}
|
|
@@ -1494,13 +1466,13 @@ async function chooseDrivers(args, io, prompts, options) {
|
|
|
1494
1466
|
return [];
|
|
1495
1467
|
}
|
|
1496
1468
|
if (args.inputMode === 'disabled') {
|
|
1497
|
-
io.stderr.write('
|
|
1469
|
+
io.stderr.write('ktx cannot work without a database. Pass --database or --database-connection-id, or pass --skip-databases to leave setup incomplete.\n');
|
|
1498
1470
|
return 'missing-input';
|
|
1499
1471
|
}
|
|
1500
1472
|
const initialValues = unique(options?.initialDrivers ?? []);
|
|
1501
1473
|
createKtxSetupUiAdapter().note(`Get demo credentials: ${KTX_DEMO_START_URL}`, '🎁 Need a warehouse to play with?', io);
|
|
1502
1474
|
const choices = await prompts.multiselect({
|
|
1503
|
-
message: withMultiselectNavigation('Which databases should
|
|
1475
|
+
message: withMultiselectNavigation('Which databases should ktx connect to?'),
|
|
1504
1476
|
options: [...DRIVER_OPTIONS],
|
|
1505
1477
|
...(initialValues.length > 0 ? { initialValues } : {}),
|
|
1506
1478
|
required: true,
|
|
@@ -1722,7 +1694,7 @@ async function runPrimarySourceFullEdit(input) {
|
|
|
1722
1694
|
}
|
|
1723
1695
|
export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
1724
1696
|
if (args.skipDatabases) {
|
|
1725
|
-
io.stdout.write('│ Database setup skipped.
|
|
1697
|
+
io.stdout.write('│ Database setup skipped. ktx cannot work until you add a database.\n');
|
|
1726
1698
|
return { status: 'skipped', projectDir: args.projectDir };
|
|
1727
1699
|
}
|
|
1728
1700
|
const prompts = deps.prompts ?? createPromptAdapter();
|
|
@@ -1836,7 +1808,7 @@ export async function runKtxSetupDatabasesStep(args, io, deps = {}) {
|
|
|
1836
1808
|
return { status: 'missing-input', projectDir: args.projectDir };
|
|
1837
1809
|
if (drivers.length === 0) {
|
|
1838
1810
|
await markDatabasesComplete(args.projectDir, []);
|
|
1839
|
-
io.stdout.write('│
|
|
1811
|
+
io.stdout.write('│ ktx cannot work without a database.\n');
|
|
1840
1812
|
return { status: 'skipped', projectDir: args.projectDir };
|
|
1841
1813
|
}
|
|
1842
1814
|
let returnToDriverSelection = false;
|
package/dist/setup-demo-tour.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createStaticCliSpinner, runWithCliSpinner } from './clack.js';
|
|
1
2
|
import { createRepainter, renderContextBuildView } from './context-build-view.js';
|
|
2
3
|
import { defaultDemoProjectDir, ensureSeededDemoProject } from './demo-assets.js';
|
|
3
4
|
import { runKtxSetupAgentsStep } from './setup-agents.js';
|
|
@@ -48,7 +49,7 @@ function createTargetState(target) {
|
|
|
48
49
|
export function renderDemoBanner(projectDir) {
|
|
49
50
|
const lines = [
|
|
50
51
|
'',
|
|
51
|
-
`┌ ${cyan('Demo mode')} — data has been pre-processed and
|
|
52
|
+
`┌ ${cyan('Demo mode')} — data has been pre-processed and ktx context is already built.`,
|
|
52
53
|
'│ This walkthrough illustrates the setup steps. Selections are pre-filled and read-only.',
|
|
53
54
|
];
|
|
54
55
|
if (projectDir) {
|
|
@@ -73,7 +74,7 @@ export function renderDemoAgentTransition() {
|
|
|
73
74
|
const lines = [
|
|
74
75
|
'┌ Demo project is ready — let\'s connect your agent',
|
|
75
76
|
'│',
|
|
76
|
-
'│ Your
|
|
77
|
+
'│ Your ktx context has been built with demo data.',
|
|
77
78
|
'│ Select an agent to start using it.',
|
|
78
79
|
'└',
|
|
79
80
|
];
|
|
@@ -83,17 +84,17 @@ export function renderDemoAgentTransition() {
|
|
|
83
84
|
export function renderDemoCompletionSummary(projectDir, agentInstalled) {
|
|
84
85
|
const lines = [
|
|
85
86
|
'',
|
|
86
|
-
`${cyan('★')}
|
|
87
|
+
`${cyan('★')} ktx demo is ready`,
|
|
87
88
|
'',
|
|
88
89
|
];
|
|
89
90
|
if (agentInstalled) {
|
|
90
|
-
lines.push(' Your agent is connected to a demo
|
|
91
|
+
lines.push(' Your agent is connected to a demo ktx project.');
|
|
91
92
|
}
|
|
92
93
|
else {
|
|
93
94
|
lines.push(' Demo project created. Connect an agent to start using it:');
|
|
94
95
|
lines.push(` $ ${cyan(`ktx setup --agents --project-dir ${projectDir}`)}`);
|
|
95
96
|
}
|
|
96
|
-
lines.push('', ` ${dim('⚠')} This project is in a temporary directory and will be`, ' cleaned up by your system. To set up
|
|
97
|
+
lines.push('', ` ${dim('⚠')} This project is in a temporary directory and will be`, ' cleaned up by your system. To set up ktx with your own', ' data, run: ktx setup', '', ` Project: ${projectDir}`);
|
|
97
98
|
return lines.join('\n');
|
|
98
99
|
}
|
|
99
100
|
// ---------------------------------------------------------------------------
|
|
@@ -171,9 +172,9 @@ export function buildDemoReplayTimeline() {
|
|
|
171
172
|
function renderDemoContextCompletionSummary() {
|
|
172
173
|
const lines = [
|
|
173
174
|
'',
|
|
174
|
-
`${cyan('★')}
|
|
175
|
+
`${cyan('★')} ktx finished building context`,
|
|
175
176
|
'',
|
|
176
|
-
'
|
|
177
|
+
' ktx created:',
|
|
177
178
|
` ${cyan('📊')} 46 semantic layer definitions`,
|
|
178
179
|
` ${cyan('📝')} 28 wiki pages`,
|
|
179
180
|
'',
|
|
@@ -249,7 +250,10 @@ export async function runDemoTour(args, io, deps = {}) {
|
|
|
249
250
|
const waitNav = deps.waitForNavigation ?? waitForDemoNavigation;
|
|
250
251
|
const ensureProject = deps.ensureProject ?? ensureSeededDemoProject;
|
|
251
252
|
const projectDir = defaultDemoProjectDir();
|
|
252
|
-
|
|
253
|
+
// Static (stderr-only) spinner: the demo navigation below reads stdin in raw mode,
|
|
254
|
+
// and an animated clack spinner would leave stdin dirty so the first keypress wait
|
|
255
|
+
// sees a stray key and skips the intro.
|
|
256
|
+
await runWithCliSpinner(createStaticCliSpinner(io), { start: 'Preparing demo project…', success: 'Demo project ready', failure: 'Could not prepare demo project' }, () => ensureProject({ projectDir, force: false, io, cliVersion: args.cliVersion }));
|
|
253
257
|
io.stdout.write(renderDemoBanner(projectDir) + '\n');
|
|
254
258
|
io.stdout.write(`\n│ ${dim('Press Enter to continue, Escape to go back')}\n└\n`);
|
|
255
259
|
const introDirection = await waitNav();
|