@kaelio/ktx 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/python/kaelio_ktx-0.13.0-py3-none-any.whl +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/.tsbuildinfo +1 -1
- package/dist/admin.js +1 -1
- package/dist/clack.d.ts +16 -0
- package/dist/clack.js +37 -6
- package/dist/claude-code-prompt-caching.js +1 -1
- package/dist/cli-program.js +3 -3
- package/dist/cli-runtime.js +2 -2
- package/dist/commands/connection-commands.js +1 -1
- package/dist/commands/ingest-commands.js +4 -4
- package/dist/commands/mcp-commands.js +12 -12
- package/dist/commands/runtime-commands.js +4 -4
- package/dist/commands/setup-commands.js +19 -5
- package/dist/commands/sl-commands.js +1 -1
- package/dist/commands/sql-commands.js +1 -1
- package/dist/commands/status-commands.js +1 -1
- package/dist/connection.js +15 -3
- package/dist/connectors/bigquery/connector.js +1 -14
- package/dist/connectors/clickhouse/connector.js +2 -16
- package/dist/connectors/duckdb/federated-attach.d.ts +7 -0
- package/dist/connectors/duckdb/federated-attach.js +86 -0
- package/dist/connectors/duckdb/federated-executor.d.ts +5 -0
- package/dist/connectors/duckdb/federated-executor.js +59 -0
- package/dist/connectors/mysql/connector.js +2 -16
- package/dist/connectors/postgres/connector.js +1 -14
- package/dist/connectors/shared/string-reference.d.ts +6 -0
- package/dist/connectors/shared/string-reference.js +19 -0
- package/dist/connectors/snowflake/connector.d.ts +1 -1
- package/dist/connectors/snowflake/connector.js +1 -14
- package/dist/connectors/sqlite/connector.js +2 -25
- package/dist/connectors/sqlserver/connector.js +4 -17
- package/dist/context/connections/connection-type.d.ts +1 -1
- package/dist/context/connections/federation.d.ts +33 -0
- package/dist/context/connections/federation.js +51 -0
- package/dist/context/connections/local-warehouse-descriptor.d.ts +2 -0
- package/dist/context/connections/project-sql-executor.d.ts +18 -0
- package/dist/context/connections/project-sql-executor.js +39 -0
- package/dist/context/connections/query-executor.d.ts +2 -2
- package/dist/context/connections/read-only-sql.d.ts +1 -0
- package/dist/context/connections/read-only-sql.js +119 -4
- package/dist/context/connections/resolve-connection.d.ts +12 -0
- package/dist/context/connections/resolve-connection.js +37 -0
- package/dist/context/core/git-env.d.ts +4 -0
- package/dist/context/core/git-env.js +5 -1
- package/dist/context/core/git.service.d.ts +23 -0
- package/dist/context/core/git.service.js +71 -8
- package/dist/context/ingest/adapters/historic-sql/projection.js +2 -1
- package/dist/context/ingest/adapters/live-database/manifest.d.ts +3 -0
- package/dist/context/ingest/adapters/live-database/manifest.js +19 -11
- package/dist/context/ingest/adapters/looker/client.js +7 -2
- package/dist/context/ingest/adapters/looker/factory.d.ts +8 -1
- package/dist/context/ingest/adapters/looker/factory.js +9 -0
- package/dist/context/ingest/adapters/looker/mapping.js +1 -1
- package/dist/context/ingest/adapters/looker/types.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.d.ts +1 -1
- package/dist/context/ingest/adapters/metabase/client.js +1 -1
- package/dist/context/ingest/adapters/metabase/local-metabase.adapter.js +1 -1
- package/dist/context/ingest/adapters/metabase/mapping.js +6 -6
- package/dist/context/ingest/artifact-gates.d.ts +2 -6
- package/dist/context/ingest/artifact-gates.js +5 -47
- package/dist/context/ingest/constrained-repair.d.ts +55 -0
- package/dist/context/ingest/constrained-repair.js +167 -0
- package/dist/context/ingest/final-gate-repair.d.ts +9 -11
- package/dist/context/ingest/final-gate-repair.js +40 -128
- package/dist/context/ingest/finalization-scope.d.ts +1 -1
- package/dist/context/ingest/finalization-scope.js +15 -15
- package/dist/context/ingest/ingest-bundle.runner.d.ts +1 -0
- package/dist/context/ingest/ingest-bundle.runner.js +101 -67
- package/dist/context/ingest/isolated-diff/patch-integrator.d.ts +6 -13
- package/dist/context/ingest/isolated-diff/patch-integrator.js +32 -109
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.d.ts +8 -9
- package/dist/context/ingest/isolated-diff/textual-conflict-resolver.js +63 -141
- package/dist/context/ingest/local-bundle-runtime.d.ts +2 -0
- package/dist/context/ingest/local-bundle-runtime.js +9 -10
- package/dist/context/ingest/local-ingest.d.ts +2 -0
- package/dist/context/ingest/local-ingest.js +2 -0
- package/dist/context/ingest/memory-flow/view-model.js +1 -1
- package/dist/context/ingest/stages/stage-3-work-units.d.ts +2 -6
- package/dist/context/ingest/stages/stage-3-work-units.js +2 -1
- package/dist/context/ingest/stages/validate-wu-sources.d.ts +7 -1
- package/dist/context/ingest/stages/validate-wu-sources.js +109 -4
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.d.ts +2 -0
- package/dist/context/ingest/tools/warehouse-verification/create-warehouse-verification-tools.js +1 -1
- package/dist/context/ingest/tools/warehouse-verification/discover-data.tool.js +3 -3
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.d.ts +3 -1
- package/dist/context/ingest/tools/warehouse-verification/sql-execution.tool.js +15 -1
- package/dist/context/llm/ai-sdk-runtime.js +2 -2
- package/dist/context/llm/claude-code-runtime.js +19 -3
- package/dist/context/llm/local-config.js +1 -1
- package/dist/context/llm/runtime-tools.js +2 -2
- package/dist/context/mcp/context-tools.js +33 -8
- package/dist/context/mcp/local-project-ports.js +63 -89
- package/dist/context/mcp/types.d.ts +2 -0
- package/dist/context/memory/local-memory.js +4 -1
- package/dist/context/memory/memory-agent.service.js +1 -1
- package/dist/context/project/config.d.ts +11 -4
- package/dist/context/project/config.js +85 -30
- package/dist/context/project/driver-schemas.js +1 -1
- package/dist/context/project/mappings-yaml-schema.js +2 -2
- package/dist/context/project/project.js +12 -4
- package/dist/context/scan/description-generation.js +4 -4
- package/dist/context/scan/local-enrichment-artifacts.js +33 -4
- package/dist/context/scan/local-scan.js +2 -2
- package/dist/context/scan/local-structural-artifacts.js +5 -5
- package/dist/context/scan/relationship-benchmark-report.js +1 -1
- package/dist/context/scan/relationship-discovery.js +3 -3
- package/dist/context/scan/relationship-llm-proposal.js +3 -3
- package/dist/context/sl/local-query.js +31 -44
- package/dist/context/sl/local-sl.d.ts +0 -8
- package/dist/context/sl/local-sl.js +71 -70
- package/dist/context/sl/semantic-layer.service.d.ts +25 -8
- package/dist/context/sl/semantic-layer.service.js +109 -56
- package/dist/context/sl/source-files.d.ts +48 -0
- package/dist/context/sl/source-files.js +138 -0
- package/dist/context/sl/tools/base-semantic-layer.tool.d.ts +2 -2
- package/dist/context/sl/tools/base-semantic-layer.tool.js +2 -7
- package/dist/context/sl/tools/sl-edit-source.tool.js +10 -8
- package/dist/context/sl/tools/sl-warehouse-validation.js +55 -27
- package/dist/context/sl/tools/sl-write-source.tool.js +12 -9
- package/dist/context/sql-analysis/dialect.d.ts +2 -0
- package/dist/context/sql-analysis/dialect.js +20 -0
- package/dist/context/tools/base-tool.d.ts +6 -19
- package/dist/context/tools/base-tool.js +0 -14
- package/dist/context-build-view.js +5 -5
- package/dist/database-tree-picker.js +18 -3
- package/dist/demo-assets.js +0 -1
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -23
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +44 -0
- package/dist/ingest-query-executor.d.ts +2 -0
- package/dist/ingest-query-executor.js +8 -22
- package/dist/ingest.d.ts +1 -1
- package/dist/ingest.js +8 -2
- package/dist/io/symbols.d.ts +2 -0
- package/dist/io/symbols.js +2 -0
- package/dist/io/tty.d.ts +8 -0
- package/dist/io/tty.js +16 -0
- package/dist/llm/embedding-health.js +1 -1
- package/dist/llm/embedding-provider.js +3 -3
- package/dist/llm/model-provider.js +1 -1
- package/dist/local-adapters.d.ts +1 -0
- package/dist/local-adapters.js +2 -2
- package/dist/local-scan-connectors.js +1 -1
- package/dist/managed-local-embeddings.js +17 -8
- package/dist/managed-mcp-daemon.js +3 -3
- package/dist/managed-python-command.d.ts +7 -0
- package/dist/managed-python-command.js +34 -8
- package/dist/managed-python-daemon.js +2 -2
- package/dist/managed-python-http.js +3 -3
- package/dist/managed-python-runtime.d.ts +30 -1
- package/dist/managed-python-runtime.js +134 -18
- package/dist/managed-uv-release.d.ts +7 -0
- package/dist/managed-uv-release.js +11 -0
- package/dist/mcp-http-server.js +4 -4
- package/dist/mcp-server-factory.js +3 -3
- package/dist/mcp-stdio-server.js +1 -1
- package/dist/memory-flow-hud.js +2 -2
- package/dist/next-steps.js +2 -2
- package/dist/prompt-navigation.d.ts +17 -0
- package/dist/prompt-navigation.js +49 -3
- package/dist/prompts/memory_agent_bundle_ingest_work_unit.md +2 -2
- package/dist/prompts/memory_agent_external_ingest.md +2 -2
- package/dist/public-ingest-copy.js +1 -1
- package/dist/public-ingest.js +3 -3
- package/dist/release-version.js +1 -1
- package/dist/runtime-requirements.js +1 -1
- package/dist/runtime.js +9 -9
- package/dist/scan.js +1 -1
- package/dist/setup-agents.d.ts +21 -15
- package/dist/setup-agents.js +143 -66
- package/dist/setup-banner.d.ts +20 -0
- package/dist/setup-banner.js +39 -0
- package/dist/setup-context.js +24 -15
- package/dist/setup-databases.d.ts +3 -0
- package/dist/setup-databases.js +47 -59
- package/dist/setup-demo-tour.js +12 -8
- package/dist/setup-embeddings.js +9 -9
- package/dist/setup-interrupt.js +1 -1
- package/dist/setup-models.d.ts +4 -1
- package/dist/setup-models.js +54 -28
- package/dist/setup-project.js +29 -5
- package/dist/setup-prompts.js +16 -1
- package/dist/setup-ready-menu.js +1 -1
- package/dist/setup-sources.js +28 -12
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +14 -13
- package/dist/skills/analytics/SKILL.md +3 -3
- package/dist/skills/dbt_ingest/SKILL.md +3 -3
- package/dist/skills/looker_ingest/SKILL.md +3 -3
- package/dist/skills/lookml_ingest/SKILL.md +7 -7
- package/dist/skills/metabase_ingest/SKILL.md +4 -4
- package/dist/skills/metricflow_ingest/SKILL.md +15 -15
- package/dist/skills/notion_synthesize/SKILL.md +1 -1
- package/dist/skills/sl/SKILL.md +3 -3
- package/dist/skills/sl_capture/SKILL.md +1 -1
- package/dist/skills/wiki_capture/SKILL.md +1 -1
- package/dist/source-mapping.js +1 -1
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +35 -53
- package/dist/startup-profile.js +1 -1
- package/dist/status-project.d.ts +0 -2
- package/dist/status-project.js +4 -6
- package/dist/telemetry/events.d.ts +3 -2
- package/dist/telemetry/events.js +11 -1
- package/dist/telemetry/exception.js +14 -0
- package/dist/text-ingest.js +1 -1
- package/dist/tree-picker-tui.d.ts +0 -1
- package/dist/tree-picker-tui.js +2 -3
- package/package.json +2 -1
- package/assets/python/kaelio_ktx-0.11.0-py3-none-any.whl +0 -0
package/dist/admin.js
CHANGED
|
@@ -18,7 +18,7 @@ export function registerAdminCommands(program, context) {
|
|
|
18
18
|
});
|
|
19
19
|
admin
|
|
20
20
|
.command('init')
|
|
21
|
-
.description('Initialize a Git-backed
|
|
21
|
+
.description('Initialize a Git-backed ktx project directory for maintenance scripts')
|
|
22
22
|
.argument('[directory]', 'Project directory')
|
|
23
23
|
.option('--force', 'Rewrite ktx.yaml and scaffold files in an existing project', false)
|
|
24
24
|
.action(async (projectDir, commandOptions, command) => {
|
package/dist/clack.d.ts
CHANGED
|
@@ -39,5 +39,21 @@ export interface KtxCliPromptAdapter {
|
|
|
39
39
|
spinner(): KtxCliSpinner;
|
|
40
40
|
}
|
|
41
41
|
export declare function createClackSpinner(): KtxCliSpinner;
|
|
42
|
+
/**
|
|
43
|
+
* Stderr-only, non-animated spinner. Use this instead of {@link createCliSpinner}
|
|
44
|
+
* when the next step reads stdin in raw mode (an Ink TUI or a keypress wait):
|
|
45
|
+
* the animated clack spinner seizes stdin via `@clack/core`'s `block()` and
|
|
46
|
+
* leaves it dirty, which the following raw-mode reader misreads as a stray key.
|
|
47
|
+
*/
|
|
42
48
|
export declare function createStaticCliSpinner(io: KtxCliSpinnerIo): KtxCliSpinner;
|
|
49
|
+
/**
|
|
50
|
+
* Animated spinner in an interactive terminal, static `◐/◇/■` lines otherwise
|
|
51
|
+
* (scripts, CI, piped output) so logs stay clean and uncluttered by frames.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createCliSpinner(io: KtxCliIo): KtxCliSpinner;
|
|
54
|
+
export declare function runWithCliSpinner<T>(spinner: KtxCliSpinner, text: {
|
|
55
|
+
start: string;
|
|
56
|
+
success: string;
|
|
57
|
+
failure: string;
|
|
58
|
+
}, run: () => Promise<T>): Promise<T>;
|
|
43
59
|
export declare function createClackPromptAdapter(): KtxCliPromptAdapter;
|
package/dist/clack.js
CHANGED
|
@@ -36,30 +36,61 @@ class KtxCliPromptCancelledError extends Error {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
export function createClackSpinner() {
|
|
39
|
-
|
|
39
|
+
// clack colors the animated spinner frame magenta by default; styleFrame
|
|
40
|
+
// (typed in SpinnerOptions, absent from the README) recolors it ktx orange.
|
|
41
|
+
return spinner({ styleFrame: orange });
|
|
40
42
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
// ktx mascot orange (#FF8A4C) via 24-bit truecolor.
|
|
44
|
+
function orange(text) {
|
|
45
|
+
if (!ansiEnabled()) {
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
return `${ESC}[38;2;255;138;76m${text}${ESC}[39m`;
|
|
43
49
|
}
|
|
44
50
|
function red(text) {
|
|
45
51
|
return ansiColor(text, 31, 39);
|
|
46
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Stderr-only, non-animated spinner. Use this instead of {@link createCliSpinner}
|
|
55
|
+
* when the next step reads stdin in raw mode (an Ink TUI or a keypress wait):
|
|
56
|
+
* the animated clack spinner seizes stdin via `@clack/core`'s `block()` and
|
|
57
|
+
* leaves it dirty, which the following raw-mode reader misreads as a stray key.
|
|
58
|
+
*/
|
|
47
59
|
export function createStaticCliSpinner(io) {
|
|
48
60
|
return {
|
|
49
61
|
start(message) {
|
|
50
|
-
io.stderr.write(`${
|
|
62
|
+
io.stderr.write(`${orange('◐')} ${message}\n`);
|
|
51
63
|
},
|
|
52
64
|
message(message) {
|
|
53
|
-
io.stderr.write(`${
|
|
65
|
+
io.stderr.write(`${orange('│')} ${message}\n`);
|
|
54
66
|
},
|
|
55
67
|
stop(message) {
|
|
56
|
-
io.stderr.write(`${
|
|
68
|
+
io.stderr.write(`${orange('◇')} ${message}\n`);
|
|
57
69
|
},
|
|
58
70
|
error(message) {
|
|
59
71
|
io.stderr.write(`${red('■')} ${message}\n`);
|
|
60
72
|
},
|
|
61
73
|
};
|
|
62
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Animated spinner in an interactive terminal, static `◐/◇/■` lines otherwise
|
|
77
|
+
* (scripts, CI, piped output) so logs stay clean and uncluttered by frames.
|
|
78
|
+
*/
|
|
79
|
+
export function createCliSpinner(io) {
|
|
80
|
+
return io.stdout.isTTY === true ? createClackSpinner() : createStaticCliSpinner(io);
|
|
81
|
+
}
|
|
82
|
+
export async function runWithCliSpinner(spinner, text, run) {
|
|
83
|
+
spinner.start(text.start);
|
|
84
|
+
try {
|
|
85
|
+
const value = await run();
|
|
86
|
+
spinner.stop(text.success);
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
spinner.error(text.failure);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
63
94
|
export function createClackPromptAdapter() {
|
|
64
95
|
return {
|
|
65
96
|
async confirm(options) {
|
|
@@ -15,7 +15,7 @@ export function formatClaudeCodePromptCachingWarning(fields) {
|
|
|
15
15
|
if (fields.length === 0) {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
-
return `claude-code ignores ${fields.join(', ')} because the Claude Agent SDK does not expose
|
|
18
|
+
return `claude-code ignores ${fields.join(', ')} because the Claude Agent SDK does not expose ktx prompt-cache TTL, tool, or history markers.`;
|
|
19
19
|
}
|
|
20
20
|
export function formatClaudeCodePromptCachingFix() {
|
|
21
21
|
return 'Remove those promptCaching fields or use anthropic, vertex, or gateway when those cache knobs are required.';
|
package/dist/cli-program.js
CHANGED
|
@@ -162,8 +162,8 @@ export function resolveCommandProjectDirOverride(command) {
|
|
|
162
162
|
function createBaseProgram(info, io) {
|
|
163
163
|
return new Command()
|
|
164
164
|
.name('ktx')
|
|
165
|
-
.description('
|
|
166
|
-
.option('--project-dir <path>', '
|
|
165
|
+
.description('ktx data agent context layer CLI')
|
|
166
|
+
.option('--project-dir <path>', 'ktx project directory (default: KTX_PROJECT_DIR, nearest ktx.yaml, or cwd)')
|
|
167
167
|
.option('--debug', 'Enable diagnostic logging to stderr')
|
|
168
168
|
.version(`${info.name} ${info.version}`, '-v, --version', 'Show CLI version')
|
|
169
169
|
.helpOption('-h, --help', 'Show this help text')
|
|
@@ -349,7 +349,7 @@ export function buildKtxProgram(options) {
|
|
|
349
349
|
const attachProjectGroup = shouldAttachCommandProjectGroup(path, hasProject);
|
|
350
350
|
telemetry.beginCommandSpan({
|
|
351
351
|
commandPath: path,
|
|
352
|
-
flagsPresent: collectCommandFlagsPresent(
|
|
352
|
+
flagsPresent: collectCommandFlagsPresent(actionCommand),
|
|
353
353
|
projectDir: attachProjectGroup ? projectDir : undefined,
|
|
354
354
|
hasProject,
|
|
355
355
|
attachProjectGroup,
|
package/dist/cli-runtime.js
CHANGED
|
@@ -14,7 +14,7 @@ export function packageInfoFromJson(packageJson) {
|
|
|
14
14
|
!('version' in packageJson) ||
|
|
15
15
|
typeof packageJson.name !== 'string' ||
|
|
16
16
|
typeof packageJson.version !== 'string') {
|
|
17
|
-
throw new Error('Invalid
|
|
17
|
+
throw new Error('Invalid ktx CLI package metadata');
|
|
18
18
|
}
|
|
19
19
|
return {
|
|
20
20
|
name: packageJson.name,
|
|
@@ -28,7 +28,7 @@ async function runInit(args, io) {
|
|
|
28
28
|
projectDir: args.projectDir,
|
|
29
29
|
force: args.force,
|
|
30
30
|
});
|
|
31
|
-
io.stdout.write(`Initialized
|
|
31
|
+
io.stdout.write(`Initialized ktx project at ${result.projectDir}\n`);
|
|
32
32
|
io.stdout.write(`Config: ${result.configPath}\n`);
|
|
33
33
|
io.stdout.write(`Commit: ${result.commitHash ?? 'none'}\n`);
|
|
34
34
|
return 0;
|
|
@@ -27,7 +27,7 @@ export function registerConnectionCommands(program, context, commandName = 'conn
|
|
|
27
27
|
connection
|
|
28
28
|
.command('test')
|
|
29
29
|
.description('Test one or all configured connections (default: all)')
|
|
30
|
-
.argument('[connectionId]', '
|
|
30
|
+
.argument('[connectionId]', 'ktx connection id to test (omit to test all)')
|
|
31
31
|
.option('--all', 'Test every configured connection and print a summary list')
|
|
32
32
|
.action(async (connectionId, options, command) => {
|
|
33
33
|
if (options.all === true && connectionId !== undefined) {
|
|
@@ -7,16 +7,16 @@ profileMark('module:commands/ingest-commands');
|
|
|
7
7
|
export function registerIngestCommands(program, context, commandOptions) {
|
|
8
8
|
const ingest = program
|
|
9
9
|
.command('ingest')
|
|
10
|
-
.description('Build or inspect
|
|
10
|
+
.description('Build or inspect ktx context, or capture text into memory')
|
|
11
11
|
.usage('[options] [connectionId]')
|
|
12
12
|
.argument('[connectionId]', 'Configured connection id to ingest (omit to ingest all)')
|
|
13
13
|
.option('--all', 'Ingest all configured connections', false)
|
|
14
14
|
.addOption(new Option('--query-history', 'Include database query-history usage patterns').conflicts('noQueryHistory'))
|
|
15
15
|
.addOption(new Option('--no-query-history', 'Skip database query-history usage patterns'))
|
|
16
16
|
.option('--query-history-window-days <days>', 'Query-history lookback window for this run', parsePositiveIntegerOption)
|
|
17
|
-
.option('--text <content>', 'Capture inline text into
|
|
18
|
-
.option('--file <path>', 'Capture a text file into
|
|
19
|
-
.option('--connection-id <connectionId>', '
|
|
17
|
+
.option('--text <content>', 'Capture inline text into ktx memory; repeatable', collectOption, [])
|
|
18
|
+
.option('--file <path>', 'Capture a text file into ktx memory; use - for stdin; repeatable', collectOption, [])
|
|
19
|
+
.option('--connection-id <connectionId>', 'ktx connection id to tag captured text/file notes')
|
|
20
20
|
.option('--user-id <id>', 'Memory user id for text/file capture attribution', 'local-cli')
|
|
21
21
|
.option('--fail-fast', 'Stop after the first failed text/file item', false)
|
|
22
22
|
.addOption(new Option('--plain', 'Print plain text output').conflicts(['json']))
|
|
@@ -13,11 +13,11 @@ function binPath() {
|
|
|
13
13
|
}
|
|
14
14
|
function formatMcpStartResultMessage(input) {
|
|
15
15
|
return [
|
|
16
|
-
input.status === 'started' ? `
|
|
16
|
+
input.status === 'started' ? `ktx MCP daemon started: ${input.url}` : `ktx MCP daemon already running: ${input.url}`,
|
|
17
17
|
'',
|
|
18
|
-
'
|
|
19
|
-
'Open your agent for this
|
|
20
|
-
' "Use
|
|
18
|
+
'ktx is ready for configured agents.',
|
|
19
|
+
'Open your agent for this ktx project and ask a data question, for example:',
|
|
20
|
+
' "Use ktx to show me the available tables and metrics."',
|
|
21
21
|
'',
|
|
22
22
|
].join('\n');
|
|
23
23
|
}
|
|
@@ -34,13 +34,13 @@ async function printMcpStatus(context, projectDir) {
|
|
|
34
34
|
export function registerMcpCommands(program, context) {
|
|
35
35
|
const mcp = program
|
|
36
36
|
.command('mcp')
|
|
37
|
-
.description('Manage the
|
|
37
|
+
.description('Manage the ktx MCP HTTP server (bare command: show status)')
|
|
38
38
|
.action(async (_options, command) => {
|
|
39
39
|
await printMcpStatus(context, resolveCommandProjectDir(command));
|
|
40
40
|
});
|
|
41
41
|
mcp
|
|
42
42
|
.command('stdio')
|
|
43
|
-
.description('Run the
|
|
43
|
+
.description('Run the ktx MCP server over stdio')
|
|
44
44
|
.action(async (_options, command) => {
|
|
45
45
|
await (context.deps.mcp?.runStdioServer ?? runKtxMcpStdioServer)({
|
|
46
46
|
projectDir: resolveCommandProjectDir(command),
|
|
@@ -50,7 +50,7 @@ export function registerMcpCommands(program, context) {
|
|
|
50
50
|
});
|
|
51
51
|
mcp
|
|
52
52
|
.command('start')
|
|
53
|
-
.description('Start the
|
|
53
|
+
.description('Start the ktx MCP HTTP server')
|
|
54
54
|
.option('--host <host>', 'Host to bind', '127.0.0.1')
|
|
55
55
|
.option('--port <n>', 'Port to bind', parsePositiveIntegerOption, 7878)
|
|
56
56
|
.option('--token <token>', 'Bearer token required for non-loopback binding')
|
|
@@ -78,7 +78,7 @@ export function registerMcpCommands(program, context) {
|
|
|
78
78
|
allowedOrigins: options.allowedOrigin,
|
|
79
79
|
io: context.io,
|
|
80
80
|
});
|
|
81
|
-
context.io.stdout.write(`
|
|
81
|
+
context.io.stdout.write(`ktx MCP server listening at http://${options.host}:${options.port}/mcp\n`);
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
84
|
const result = await (context.deps.mcp?.startDaemon ?? startKtxMcpDaemon)({
|
|
@@ -95,22 +95,22 @@ export function registerMcpCommands(program, context) {
|
|
|
95
95
|
});
|
|
96
96
|
mcp
|
|
97
97
|
.command('stop')
|
|
98
|
-
.description('Stop the
|
|
98
|
+
.description('Stop the ktx MCP daemon')
|
|
99
99
|
.action(async (_options, command) => {
|
|
100
100
|
const result = await (context.deps.mcp?.stopDaemon ?? stopKtxMcpDaemon)({
|
|
101
101
|
projectDir: resolveCommandProjectDir(command),
|
|
102
102
|
});
|
|
103
|
-
context.io.stdout.write(result.status === 'stopped' ? '
|
|
103
|
+
context.io.stdout.write(result.status === 'stopped' ? 'ktx MCP daemon stopped.\n' : 'ktx MCP daemon is not running.\n');
|
|
104
104
|
});
|
|
105
105
|
mcp
|
|
106
106
|
.command('status')
|
|
107
|
-
.description('Show
|
|
107
|
+
.description('Show ktx MCP daemon status')
|
|
108
108
|
.action(async (_options, command) => {
|
|
109
109
|
await printMcpStatus(context, resolveCommandProjectDir(command));
|
|
110
110
|
});
|
|
111
111
|
mcp
|
|
112
112
|
.command('logs')
|
|
113
|
-
.description('Print the
|
|
113
|
+
.description('Print the ktx MCP daemon log')
|
|
114
114
|
.option('--follow', 'Follow log output', false)
|
|
115
115
|
.action(async (options, command) => {
|
|
116
116
|
const logPath = mcpDaemonLayout(resolveCommandProjectDir(command)).logPath;
|
|
@@ -12,7 +12,7 @@ async function runRuntimeArgs(context, args) {
|
|
|
12
12
|
export function registerRuntimeCommands(program, context) {
|
|
13
13
|
const runtime = program
|
|
14
14
|
.command('runtime')
|
|
15
|
-
.description('Install, start, stop, and inspect the
|
|
15
|
+
.description('Install, start, stop, and inspect the ktx-managed Python runtime')
|
|
16
16
|
.showHelpAfterError();
|
|
17
17
|
runtime
|
|
18
18
|
.command('install')
|
|
@@ -30,7 +30,7 @@ export function registerRuntimeCommands(program, context) {
|
|
|
30
30
|
});
|
|
31
31
|
runtime
|
|
32
32
|
.command('start')
|
|
33
|
-
.description('Start the
|
|
33
|
+
.description('Start the ktx daemon')
|
|
34
34
|
.addOption(createRuntimeFeatureOption())
|
|
35
35
|
.option('--force', 'Restart even when a matching daemon is already running', false)
|
|
36
36
|
.action(async (options, command) => {
|
|
@@ -44,8 +44,8 @@ export function registerRuntimeCommands(program, context) {
|
|
|
44
44
|
});
|
|
45
45
|
runtime
|
|
46
46
|
.command('stop')
|
|
47
|
-
.description('Stop the
|
|
48
|
-
.option('--all', 'Stop all
|
|
47
|
+
.description('Stop the ktx daemon')
|
|
48
|
+
.option('--all', 'Stop all ktx daemon processes recorded or discoverable on this machine', false)
|
|
49
49
|
.action(async (options, command) => {
|
|
50
50
|
await runRuntimeArgs(context, {
|
|
51
51
|
command: 'stop',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InvalidArgumentError, Option } from '@commander-js/extra-typings';
|
|
2
2
|
import { resolveCommandProjectDir } from '../cli-program.js';
|
|
3
|
+
import { isKtxSetupLlmBackend } from '../setup-models.js';
|
|
3
4
|
async function runSetupArgs(context, args) {
|
|
4
5
|
const runner = context.deps.setup ?? (await import('../setup.js')).runKtxSetup;
|
|
5
6
|
context.setExitCode(await runner(args, context.io));
|
|
@@ -7,7 +8,7 @@ async function runSetupArgs(context, args) {
|
|
|
7
8
|
function positiveInteger(value) {
|
|
8
9
|
const parsed = Number.parseInt(value, 10);
|
|
9
10
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
10
|
-
throw new
|
|
11
|
+
throw new InvalidArgumentError(`Expected a positive integer, received ${value}`);
|
|
11
12
|
}
|
|
12
13
|
return parsed;
|
|
13
14
|
}
|
|
@@ -18,7 +19,7 @@ function embeddingBackend(value) {
|
|
|
18
19
|
throw new InvalidArgumentError(`invalid choice '${value}'`);
|
|
19
20
|
}
|
|
20
21
|
function llmBackend(value) {
|
|
21
|
-
if (value
|
|
22
|
+
if (isKtxSetupLlmBackend(value)) {
|
|
22
23
|
return value;
|
|
23
24
|
}
|
|
24
25
|
throw new InvalidArgumentError(`invalid choice '${value}'`);
|
|
@@ -83,6 +84,7 @@ function shouldShowSetupEntryMenu(options, command) {
|
|
|
83
84
|
'target',
|
|
84
85
|
'global',
|
|
85
86
|
'local',
|
|
87
|
+
'installDir',
|
|
86
88
|
'skipAgents',
|
|
87
89
|
'yes',
|
|
88
90
|
'input',
|
|
@@ -125,8 +127,8 @@ function shouldShowSetupEntryMenu(options, command) {
|
|
|
125
127
|
export function registerSetupCommands(program, context) {
|
|
126
128
|
const setup = program
|
|
127
129
|
.command('setup')
|
|
128
|
-
.description('Set up or resume a local
|
|
129
|
-
.addOption(new Option('--project-dir <path>', '
|
|
130
|
+
.description('Set up or resume a local ktx project')
|
|
131
|
+
.addOption(new Option('--project-dir <path>', 'ktx project directory').hideHelp())
|
|
130
132
|
.option('--agents', 'Install agent integration only', false)
|
|
131
133
|
.addOption(new Option('--target <target>', 'Agent target').choices([
|
|
132
134
|
'claude-code',
|
|
@@ -138,6 +140,7 @@ export function registerSetupCommands(program, context) {
|
|
|
138
140
|
]))
|
|
139
141
|
.option('--global', 'Install agent integration into the global target scope', false)
|
|
140
142
|
.option('--local', 'Install Claude Code MCP config into the private per-project ~/.claude.json scope', false)
|
|
143
|
+
.option('--install-dir <path>', 'Directory to install project-scoped agent config into (defaults to the ktx project directory)')
|
|
141
144
|
.addOption(new Option('--skip-agents', 'Leave agent integration incomplete for now').hideHelp().default(false))
|
|
142
145
|
.option('--yes', 'Accept project creation and runtime install defaults where setup confirms', false)
|
|
143
146
|
.option('--no-input', 'Disable interactive terminal input')
|
|
@@ -184,7 +187,7 @@ export function registerSetupCommands(program, context) {
|
|
|
184
187
|
.argParser((value, previous) => [...previous, value])
|
|
185
188
|
.default([])
|
|
186
189
|
.hideHelp())
|
|
187
|
-
.addOption(new Option('--skip-databases', 'Leave database setup incomplete;
|
|
190
|
+
.addOption(new Option('--skip-databases', 'Leave database setup incomplete; ktx cannot work until a database is added')
|
|
188
191
|
.hideHelp()
|
|
189
192
|
.default(false))
|
|
190
193
|
.addOption(new Option('--source <type>', 'Source connector type').argParser(sourceType).hideHelp())
|
|
@@ -263,6 +266,16 @@ export function registerSetupCommands(program, context) {
|
|
|
263
266
|
context.setExitCode(1);
|
|
264
267
|
return;
|
|
265
268
|
}
|
|
269
|
+
if (options.installDir && (options.global || options.local)) {
|
|
270
|
+
context.io.stderr.write('Choose either --install-dir or a scope flag (--global / --local), not both.\n');
|
|
271
|
+
context.setExitCode(1);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (options.installDir && options.target === 'claude-desktop') {
|
|
275
|
+
context.io.stderr.write('--install-dir does not apply to --target claude-desktop, which is always global.\n');
|
|
276
|
+
context.setExitCode(1);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
266
279
|
const creatingDatabaseConnection = options.database.length > 0 || options.databaseUrl !== undefined;
|
|
267
280
|
if (creatingDatabaseConnection && options.databaseConnectionId.length > 1) {
|
|
268
281
|
context.io.stderr.write('Choose only one new database connection id when configuring a database.\n');
|
|
@@ -278,6 +291,7 @@ export function registerSetupCommands(program, context) {
|
|
|
278
291
|
agents: options.agents === true,
|
|
279
292
|
...(options.target ? { target: options.target } : {}),
|
|
280
293
|
agentScope: resolvedAgentScope,
|
|
294
|
+
...(options.installDir ? { installRoot: options.installDir } : {}),
|
|
281
295
|
skipAgents: options.skipAgents === true,
|
|
282
296
|
inputMode: options.input === false ? 'disabled' : 'auto',
|
|
283
297
|
...(debugEnabled ? { debug: true } : {}),
|
|
@@ -30,7 +30,7 @@ export function registerSlCommands(program, context, commandName = 'sl') {
|
|
|
30
30
|
.description('List, search, validate, or query local semantic-layer sources')
|
|
31
31
|
.usage('[options] [query...]')
|
|
32
32
|
.argument('[query...]', 'Search query; omit to list all sources')
|
|
33
|
-
.option('--connection-id <id>', '
|
|
33
|
+
.option('--connection-id <id>', 'ktx connection id')
|
|
34
34
|
.option('--limit <number>', 'Maximum search results (search mode only)', parsePositiveIntegerOption)
|
|
35
35
|
.addOption(new Option('--output <mode>', 'Output mode: pretty (default in TTY), plain (TSV), or json').choices([
|
|
36
36
|
'pretty',
|
|
@@ -20,7 +20,7 @@ export function registerSqlCommands(program, context) {
|
|
|
20
20
|
.command('sql')
|
|
21
21
|
.description('Execute parser-validated read-only SQL against a configured connection')
|
|
22
22
|
.argument('<sql...>', 'SQL query to execute')
|
|
23
|
-
.requiredOption('-c, --connection <id>', '
|
|
23
|
+
.requiredOption('-c, --connection <id>', 'ktx connection id')
|
|
24
24
|
.option('--max-rows <n>', 'Maximum rows to return', parseSqlMaxRowsOption, DEFAULT_MAX_ROWS)
|
|
25
25
|
.addOption(new Option('--output <mode>', 'Output mode: pretty (default), plain (TSV), or json').choices([
|
|
26
26
|
'pretty',
|
|
@@ -10,7 +10,7 @@ function inputMode(options) {
|
|
|
10
10
|
export function registerStatusCommands(program, context) {
|
|
11
11
|
program
|
|
12
12
|
.command('status')
|
|
13
|
-
.description('Check current
|
|
13
|
+
.description('Check current ktx setup and project readiness')
|
|
14
14
|
.option('--json', 'Print JSON output', false)
|
|
15
15
|
.option('-v, --verbose', 'Show every check, including passing ones', false)
|
|
16
16
|
.option('--validate', 'Only validate the ktx.yaml schema; skip readiness checks', false)
|
package/dist/connection.js
CHANGED
|
@@ -4,6 +4,7 @@ import { NotionClient } from './context/ingest/adapters/notion/notion-client.js'
|
|
|
4
4
|
import { createLocalLookerCredentialResolver } from './context/ingest/adapters/looker/local-looker.adapter.js';
|
|
5
5
|
import { metabaseRuntimeConfigFromLocalConnection } from './context/ingest/adapters/metabase/local-metabase.adapter.js';
|
|
6
6
|
import { testRepoConnection } from './context/ingest/repo-fetch.js';
|
|
7
|
+
import { federatedConnectionListing } from './context/connections/federation.js';
|
|
7
8
|
import { getDriverRegistration } from './context/connections/drivers.js';
|
|
8
9
|
import { parseNotionConnectionConfig, resolveNotionConnectionAuthToken } from './context/connections/notion-config.js';
|
|
9
10
|
import { resolveKtxConfigReference } from './context/core/config-reference.js';
|
|
@@ -86,7 +87,7 @@ async function testMetabaseConnection(project, connectionId, createClient) {
|
|
|
86
87
|
}
|
|
87
88
|
async function createDefaultLookerClient(project, connectionId) {
|
|
88
89
|
const factory = new DefaultLookerConnectionClientFactory(createLocalLookerCredentialResolver(project));
|
|
89
|
-
return
|
|
90
|
+
return factory.createLookerClient(connectionId);
|
|
90
91
|
}
|
|
91
92
|
async function testLookerConnection(project, connectionId, createClient) {
|
|
92
93
|
const client = await createClient(project, connectionId);
|
|
@@ -292,12 +293,23 @@ export async function runKtxConnection(args, io = process, deps = {}) {
|
|
|
292
293
|
io.stdout.write('No connections configured. Run `ktx setup` to add one.\n');
|
|
293
294
|
return 0;
|
|
294
295
|
}
|
|
295
|
-
const
|
|
296
|
-
const
|
|
296
|
+
const federated = federatedConnectionListing(project.config.connections, args.projectDir);
|
|
297
|
+
const idCandidates = [...entries.map(([id]) => id), ...(federated ? [federated.id] : [])];
|
|
298
|
+
const driverLengths = [
|
|
299
|
+
...entries.map(([, c]) => (c.driver ?? 'unknown').length),
|
|
300
|
+
...(federated ? [federated.driver.length] : []),
|
|
301
|
+
];
|
|
302
|
+
const idWidth = Math.max('ID'.length, ...idCandidates.map((id) => id.length));
|
|
303
|
+
const driverWidth = Math.max('DRIVER'.length, ...driverLengths);
|
|
297
304
|
io.stdout.write(`${'ID'.padEnd(idWidth)} ${'DRIVER'.padEnd(driverWidth)}\n`);
|
|
298
305
|
for (const [id, connection] of entries) {
|
|
299
306
|
io.stdout.write(`${id.padEnd(idWidth)} ${(connection.driver ?? 'unknown').padEnd(driverWidth)}\n`);
|
|
300
307
|
}
|
|
308
|
+
if (federated) {
|
|
309
|
+
io.stdout.write(`${federated.id.padEnd(idWidth)} ${federated.driver.padEnd(driverWidth)}\n`);
|
|
310
|
+
io.stdout.write(` federates: ${federated.members.join(', ')}\n`);
|
|
311
|
+
io.stdout.write(` ${federated.hint}\n`);
|
|
312
|
+
}
|
|
301
313
|
return 0;
|
|
302
314
|
}
|
|
303
315
|
if (args.command === 'test-all') {
|
|
@@ -5,9 +5,7 @@ import { assertReadOnlySql, limitSqlForExecution } from '../../context/connectio
|
|
|
5
5
|
import { tryConstraintQuery } from '../../context/scan/constraint-discovery.js';
|
|
6
6
|
import { scopedTableNames } from '../../context/scan/table-ref.js';
|
|
7
7
|
import { connectorTestFailure, createKtxConnectorCapabilities, } from '../../context/scan/types.js';
|
|
8
|
-
import {
|
|
9
|
-
import { homedir } from 'node:os';
|
|
10
|
-
import { resolve } from 'node:path';
|
|
8
|
+
import { resolveStringReference } from '../shared/string-reference.js';
|
|
11
9
|
class DefaultBigQueryClientFactory {
|
|
12
10
|
createClient(input) {
|
|
13
11
|
const client = new BigQuery(input);
|
|
@@ -24,17 +22,6 @@ class DefaultBigQueryClientFactory {
|
|
|
24
22
|
};
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
|
-
function resolveStringReference(value, env) {
|
|
28
|
-
if (value.startsWith('env:')) {
|
|
29
|
-
return env[value.slice('env:'.length)] ?? '';
|
|
30
|
-
}
|
|
31
|
-
if (value.startsWith('file:')) {
|
|
32
|
-
const rawPath = value.slice('file:'.length);
|
|
33
|
-
const path = rawPath.startsWith('~') ? resolve(homedir(), rawPath.slice(1)) : rawPath;
|
|
34
|
-
return readFileSync(path, 'utf-8').trim();
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
38
25
|
function stringConfigValue(connection, key, env) {
|
|
39
26
|
const value = connection?.[key];
|
|
40
27
|
return typeof value === 'string' && value.trim().length > 0 ? resolveStringReference(value.trim(), env) : undefined;
|
|
@@ -3,10 +3,8 @@ import { getDialectForDriver } from '../../context/connections/dialects.js';
|
|
|
3
3
|
import { assertReadOnlySql, limitSqlForExecution } from '../../context/connections/read-only-sql.js';
|
|
4
4
|
import { connectorTestFailure, createKtxConnectorCapabilities } from '../../context/scan/types.js';
|
|
5
5
|
import { scopedTableNames } from '../../context/scan/table-ref.js';
|
|
6
|
-
import {
|
|
6
|
+
import { resolveStringReference } from '../shared/string-reference.js';
|
|
7
7
|
import { Agent as HttpsAgent } from 'node:https';
|
|
8
|
-
import { homedir } from 'node:os';
|
|
9
|
-
import { resolve } from 'node:path';
|
|
10
8
|
class DefaultClickHouseClientFactory {
|
|
11
9
|
createClient(config) {
|
|
12
10
|
return createClient(config);
|
|
@@ -16,18 +14,6 @@ function stringConfigValue(connection, key, env) {
|
|
|
16
14
|
const value = connection?.[key];
|
|
17
15
|
return typeof value === 'string' && value.trim().length > 0 ? resolveStringReference(value.trim(), env) : undefined;
|
|
18
16
|
}
|
|
19
|
-
function resolveStringReference(value, env) {
|
|
20
|
-
if (value.startsWith('env:')) {
|
|
21
|
-
const envName = value.slice('env:'.length);
|
|
22
|
-
return env[envName] ?? '';
|
|
23
|
-
}
|
|
24
|
-
if (value.startsWith('file:')) {
|
|
25
|
-
const rawPath = value.slice('file:'.length);
|
|
26
|
-
const path = rawPath.startsWith('~') ? resolve(homedir(), rawPath.slice(1)) : rawPath;
|
|
27
|
-
return readFileSync(path, 'utf-8').trim();
|
|
28
|
-
}
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
17
|
function maybeNumber(value) {
|
|
32
18
|
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
33
19
|
}
|
|
@@ -432,7 +418,7 @@ export class KtxClickHouseScanConnector {
|
|
|
432
418
|
}
|
|
433
419
|
assertConnection(connectionId) {
|
|
434
420
|
if (connectionId !== this.connectionId) {
|
|
435
|
-
throw new Error(`
|
|
421
|
+
throw new Error(`ktx ClickHouse connector ${this.id} cannot serve connection ${connectionId}`);
|
|
436
422
|
}
|
|
437
423
|
}
|
|
438
424
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FederatedMember } from '../../context/connections/federation.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a federated member's ktx.yaml config into the connection target
|
|
4
|
+
* DuckDB's ATTACH wants for that driver, reusing each connector's canonical
|
|
5
|
+
* resolver so federation and standalone scans agree on config interpretation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function federatedAttachTarget(member: FederatedMember, env: NodeJS.ProcessEnv): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { sqliteDatabasePathFromConfig } from '../sqlite/connector.js';
|
|
2
|
+
import { postgresPoolConfigFromConfig } from '../postgres/connector.js';
|
|
3
|
+
import { mysqlConnectionPoolConfigFromConfig, } from '../mysql/connector.js';
|
|
4
|
+
function kvKeyword(value) {
|
|
5
|
+
// libpq/DuckDB key-value values quote with single quotes and backslash-escape.
|
|
6
|
+
return /[\s'\\]/.test(value) ? `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'` : value;
|
|
7
|
+
}
|
|
8
|
+
function withRequiredSslMode(connectionString) {
|
|
9
|
+
// DuckDB passes this libpq URL straight to the server, so an ssl:true member
|
|
10
|
+
// must carry sslmode in the URL itself; keep a stronger mode the URL already pins.
|
|
11
|
+
const url = new URL(connectionString);
|
|
12
|
+
if (url.searchParams.has('sslmode')) {
|
|
13
|
+
return connectionString;
|
|
14
|
+
}
|
|
15
|
+
url.searchParams.set('sslmode', 'require');
|
|
16
|
+
return url.toString();
|
|
17
|
+
}
|
|
18
|
+
function postgresAttachString(member, env) {
|
|
19
|
+
const cfg = postgresPoolConfigFromConfig({
|
|
20
|
+
connectionId: member.connectionId,
|
|
21
|
+
connection: member.connection,
|
|
22
|
+
env,
|
|
23
|
+
});
|
|
24
|
+
if (cfg.connectionString) {
|
|
25
|
+
return cfg.ssl ? withRequiredSslMode(cfg.connectionString) : cfg.connectionString;
|
|
26
|
+
}
|
|
27
|
+
const parts = [];
|
|
28
|
+
if (cfg.host)
|
|
29
|
+
parts.push(`host=${kvKeyword(cfg.host)}`);
|
|
30
|
+
if (cfg.port)
|
|
31
|
+
parts.push(`port=${cfg.port}`);
|
|
32
|
+
if (cfg.database)
|
|
33
|
+
parts.push(`dbname=${kvKeyword(cfg.database)}`);
|
|
34
|
+
if (cfg.user)
|
|
35
|
+
parts.push(`user=${kvKeyword(cfg.user)}`);
|
|
36
|
+
if (cfg.password)
|
|
37
|
+
parts.push(`password=${kvKeyword(cfg.password)}`);
|
|
38
|
+
if (cfg.ssl) {
|
|
39
|
+
parts.push('sslmode=require');
|
|
40
|
+
}
|
|
41
|
+
if (cfg.options) {
|
|
42
|
+
parts.push(`options=${kvKeyword(cfg.options)}`);
|
|
43
|
+
}
|
|
44
|
+
return parts.join(' ');
|
|
45
|
+
}
|
|
46
|
+
function mysqlAttachString(member, env) {
|
|
47
|
+
const cfg = mysqlConnectionPoolConfigFromConfig({
|
|
48
|
+
connectionId: member.connectionId,
|
|
49
|
+
connection: member.connection,
|
|
50
|
+
env,
|
|
51
|
+
});
|
|
52
|
+
const parts = [
|
|
53
|
+
`host=${kvKeyword(cfg.host)}`,
|
|
54
|
+
`port=${cfg.port}`,
|
|
55
|
+
`database=${kvKeyword(cfg.database)}`,
|
|
56
|
+
`user=${kvKeyword(cfg.user)}`,
|
|
57
|
+
];
|
|
58
|
+
if (cfg.password) {
|
|
59
|
+
parts.push(`password=${kvKeyword(cfg.password)}`);
|
|
60
|
+
}
|
|
61
|
+
if (cfg.ssl) {
|
|
62
|
+
parts.push('ssl_mode=REQUIRED');
|
|
63
|
+
}
|
|
64
|
+
return parts.join(' ');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolves a federated member's ktx.yaml config into the connection target
|
|
68
|
+
* DuckDB's ATTACH wants for that driver, reusing each connector's canonical
|
|
69
|
+
* resolver so federation and standalone scans agree on config interpretation.
|
|
70
|
+
*/
|
|
71
|
+
export function federatedAttachTarget(member, env) {
|
|
72
|
+
switch (member.driver.toLowerCase()) {
|
|
73
|
+
case 'sqlite':
|
|
74
|
+
return sqliteDatabasePathFromConfig({
|
|
75
|
+
connectionId: member.connectionId,
|
|
76
|
+
projectDir: member.projectDir,
|
|
77
|
+
connection: member.connection,
|
|
78
|
+
});
|
|
79
|
+
case 'postgres':
|
|
80
|
+
return postgresAttachString(member, env);
|
|
81
|
+
case 'mysql':
|
|
82
|
+
return mysqlAttachString(member, env);
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`Driver "${member.driver}" cannot be attached by DuckDB federation.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { KtxSqlQueryExecutionInput, KtxSqlQueryExecutionResult } from '../../context/connections/query-executor.js';
|
|
2
|
+
import { type FederatedMember } from '../../context/connections/federation.js';
|
|
3
|
+
/** @internal */
|
|
4
|
+
export declare function buildAttachStatements(members: FederatedMember[], env: NodeJS.ProcessEnv): string[];
|
|
5
|
+
export declare function executeFederatedQuery(members: FederatedMember[], input: KtxSqlQueryExecutionInput, env?: NodeJS.ProcessEnv): Promise<KtxSqlQueryExecutionResult>;
|