@kaelio/ktx 0.12.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.12.0-py3-none-any.whl → 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/commands/setup-commands.js +13 -0
- package/dist/connection.js +14 -2
- package/dist/connectors/bigquery/connector.js +1 -14
- package/dist/connectors/clickhouse/connector.js +1 -15
- 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 +1 -15
- 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.js +1 -14
- package/dist/connectors/sqlserver/connector.js +1 -14
- 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.js +4 -3
- 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/ingest/adapters/live-database/manifest.d.ts +3 -0
- package/dist/context/ingest/adapters/live-database/manifest.js +19 -11
- package/dist/context/llm/claude-code-runtime.js +18 -2
- package/dist/context/mcp/context-tools.js +27 -2
- package/dist/context/mcp/local-project-ports.js +55 -50
- package/dist/context/mcp/types.d.ts +2 -0
- package/dist/context/scan/local-enrichment-artifacts.js +31 -3
- package/dist/context/sl/local-query.js +29 -12
- package/dist/context/sl/local-sl.js +27 -1
- package/dist/context/sl/source-files.d.ts +2 -0
- package/dist/context/sl/source-files.js +7 -0
- package/dist/ingest-query-executor.d.ts +2 -0
- package/dist/ingest-query-executor.js +8 -22
- package/dist/setup-agents.d.ts +21 -15
- package/dist/setup-agents.js +128 -42
- package/dist/setup-databases.d.ts +3 -0
- package/dist/setup-databases.js +16 -0
- package/dist/setup-sources.js +1 -5
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +1 -0
- package/dist/sql.d.ts +2 -0
- package/dist/sql.js +35 -53
- package/dist/telemetry/events.d.ts +2 -1
- package/dist/telemetry/events.js +11 -1
- package/package.json +2 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { executeFederatedQuery } from './connectors/duckdb/federated-executor.js';
|
|
1
2
|
import type { KtxSqlQueryExecutorPort } from './context/connections/query-executor.js';
|
|
2
3
|
import type { KtxLocalProject } from './context/project/project.js';
|
|
3
4
|
import { createKtxCliScanConnector } from './local-scan-connectors.js';
|
|
4
5
|
type CreateConnector = typeof createKtxCliScanConnector;
|
|
5
6
|
export interface KtxCliIngestQueryExecutorDeps {
|
|
6
7
|
createConnector?: CreateConnector;
|
|
8
|
+
executeFederated?: typeof executeFederatedQuery;
|
|
7
9
|
}
|
|
8
10
|
export declare function createKtxCliIngestQueryExecutor(project: KtxLocalProject, deps?: KtxCliIngestQueryExecutorDeps): KtxSqlQueryExecutorPort;
|
|
9
11
|
export {};
|
|
@@ -1,30 +1,16 @@
|
|
|
1
|
+
import { executeProjectReadOnlySql } from './context/connections/project-sql-executor.js';
|
|
1
2
|
import { createKtxCliScanConnector } from './local-scan-connectors.js';
|
|
2
|
-
async function cleanupConnector(connector) {
|
|
3
|
-
await connector?.cleanup?.();
|
|
4
|
-
}
|
|
5
3
|
export function createKtxCliIngestQueryExecutor(project, deps = {}) {
|
|
6
4
|
const createConnector = deps.createConnector ?? createKtxCliScanConnector;
|
|
7
5
|
return {
|
|
8
6
|
async execute(input) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const result = await connector.executeReadOnly({ connectionId: input.connectionId, sql: input.sql, maxRows: input.maxRows }, ctx);
|
|
17
|
-
return {
|
|
18
|
-
headers: result.headers,
|
|
19
|
-
rows: result.rows,
|
|
20
|
-
totalRows: result.totalRows,
|
|
21
|
-
command: 'SELECT',
|
|
22
|
-
rowCount: result.rowCount,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
finally {
|
|
26
|
-
await cleanupConnector(connector);
|
|
27
|
-
}
|
|
7
|
+
return executeProjectReadOnlySql({
|
|
8
|
+
project,
|
|
9
|
+
input,
|
|
10
|
+
createConnector: (connectionId) => createConnector(project, connectionId),
|
|
11
|
+
executeFederated: deps.executeFederated,
|
|
12
|
+
runId: 'ingest-sql-execution',
|
|
13
|
+
});
|
|
28
14
|
},
|
|
29
15
|
};
|
|
30
16
|
}
|
package/dist/setup-agents.d.ts
CHANGED
|
@@ -14,15 +14,24 @@ export interface KtxSetupAgentsArgs {
|
|
|
14
14
|
mode: KtxAgentInstallMode;
|
|
15
15
|
skipAgents: boolean;
|
|
16
16
|
showNextActions?: boolean;
|
|
17
|
+
installRoot?: string;
|
|
18
|
+
cwd?: string;
|
|
17
19
|
}
|
|
20
|
+
/** The directory project-scoped agent files land in; equals projectDir unless an install root is chosen. */
|
|
21
|
+
interface KtxAgentInstall {
|
|
22
|
+
target: KtxAgentTarget;
|
|
23
|
+
scope: KtxAgentScope;
|
|
24
|
+
mode: KtxAgentInstallMode;
|
|
25
|
+
installRoot: string;
|
|
26
|
+
}
|
|
27
|
+
/** Install shape for formatting helpers; installRoot falls back to projectDir when absent. */
|
|
28
|
+
type KtxAgentInstallLike = Omit<KtxAgentInstall, 'installRoot'> & {
|
|
29
|
+
installRoot?: string;
|
|
30
|
+
};
|
|
18
31
|
export type KtxSetupAgentsResult = {
|
|
19
32
|
status: 'ready';
|
|
20
33
|
projectDir: string;
|
|
21
|
-
installs:
|
|
22
|
-
target: KtxAgentTarget;
|
|
23
|
-
scope: KtxAgentScope;
|
|
24
|
-
mode: KtxAgentInstallMode;
|
|
25
|
-
}>;
|
|
34
|
+
installs: KtxAgentInstall[];
|
|
26
35
|
nextActions?: string;
|
|
27
36
|
} | {
|
|
28
37
|
status: 'skipped';
|
|
@@ -41,11 +50,7 @@ export interface KtxAgentInstallManifest {
|
|
|
41
50
|
version: 1;
|
|
42
51
|
projectDir: string;
|
|
43
52
|
installedAt: string;
|
|
44
|
-
installs:
|
|
45
|
-
target: KtxAgentTarget;
|
|
46
|
-
scope: KtxAgentScope;
|
|
47
|
-
mode: KtxAgentInstallMode;
|
|
48
|
-
}>;
|
|
53
|
+
installs: KtxAgentInstall[];
|
|
49
54
|
entries: Array<{
|
|
50
55
|
kind: 'file';
|
|
51
56
|
path: string;
|
|
@@ -65,6 +70,7 @@ export declare function plannedKtxAgentFiles(input: {
|
|
|
65
70
|
target: KtxAgentTarget;
|
|
66
71
|
scope: KtxAgentScope;
|
|
67
72
|
mode: KtxAgentInstallMode;
|
|
73
|
+
installRoot?: string;
|
|
68
74
|
}): InstallEntry[];
|
|
69
75
|
export declare function readKtxAgentInstallManifest(projectDir: string): Promise<KtxAgentInstallManifest | null>;
|
|
70
76
|
/** @internal */
|
|
@@ -79,6 +85,10 @@ interface KtxSetupAgentsPromptAdapter {
|
|
|
79
85
|
options: KtxSetupPromptOption[];
|
|
80
86
|
required?: boolean;
|
|
81
87
|
}): Promise<string[]>;
|
|
88
|
+
text(options: {
|
|
89
|
+
message: string;
|
|
90
|
+
placeholder?: string;
|
|
91
|
+
}): Promise<string | undefined>;
|
|
82
92
|
cancel(message: string): void;
|
|
83
93
|
}
|
|
84
94
|
export interface KtxSetupAgentsDeps {
|
|
@@ -91,10 +101,6 @@ export interface InstallSummaryEntry {
|
|
|
91
101
|
lines: string[];
|
|
92
102
|
}
|
|
93
103
|
/** @internal */
|
|
94
|
-
export declare function formatInstallSummaryLines(installs:
|
|
95
|
-
target: KtxAgentTarget;
|
|
96
|
-
scope: KtxAgentScope;
|
|
97
|
-
mode: KtxAgentInstallMode;
|
|
98
|
-
}>, entries: InstallEntry[], projectDir: string): InstallSummaryEntry[];
|
|
104
|
+
export declare function formatInstallSummaryLines(installs: KtxAgentInstallLike[], entries: InstallEntry[], projectDir: string): InstallSummaryEntry[];
|
|
99
105
|
export declare function runKtxSetupAgentsStep(args: KtxSetupAgentsArgs, io: KtxCliIo, deps?: KtxSetupAgentsDeps): Promise<KtxSetupAgentsResult>;
|
|
100
106
|
export {};
|
package/dist/setup-agents.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { styleText } from 'node:util';
|
|
@@ -166,7 +166,7 @@ function universalMcpSnippet(endpoint) {
|
|
|
166
166
|
...(endpoint.tokenAuth ? ['Header: Authorization: Bearer ${KTX_MCP_TOKEN}'] : []),
|
|
167
167
|
].join('\n');
|
|
168
168
|
}
|
|
169
|
-
function claudeConfigPath(projectDir, scope) {
|
|
169
|
+
function claudeConfigPath(projectDir, installRoot, scope) {
|
|
170
170
|
const home = process.env.HOME ?? '';
|
|
171
171
|
if (scope === 'global') {
|
|
172
172
|
return { path: join(home, '.claude.json'), jsonPath: ['mcpServers', 'ktx'] };
|
|
@@ -174,12 +174,12 @@ function claudeConfigPath(projectDir, scope) {
|
|
|
174
174
|
if (scope === 'local') {
|
|
175
175
|
return { path: join(home, '.claude.json'), jsonPath: ['projects', resolve(projectDir), 'mcpServers', 'ktx'] };
|
|
176
176
|
}
|
|
177
|
-
return { path: join(resolve(
|
|
177
|
+
return { path: join(resolve(installRoot), '.mcp.json'), jsonPath: ['mcpServers', 'ktx'] };
|
|
178
178
|
}
|
|
179
|
-
function cursorConfigPath(
|
|
179
|
+
function cursorConfigPath(installRoot, scope) {
|
|
180
180
|
const home = process.env.HOME ?? '';
|
|
181
181
|
return {
|
|
182
|
-
path: scope === 'global' ? join(home, '.cursor/mcp.json') : join(resolve(
|
|
182
|
+
path: scope === 'global' ? join(home, '.cursor/mcp.json') : join(resolve(installRoot), '.cursor/mcp.json'),
|
|
183
183
|
jsonPath: ['mcpServers', 'ktx'],
|
|
184
184
|
};
|
|
185
185
|
}
|
|
@@ -226,12 +226,12 @@ async function installMcpClientConfig(input) {
|
|
|
226
226
|
notices.push(MCP_DAEMON_REQUIRED_NOTICE);
|
|
227
227
|
}
|
|
228
228
|
if (input.target === 'claude-code') {
|
|
229
|
-
const config = claudeConfigPath(input.projectDir, input.scope);
|
|
229
|
+
const config = claudeConfigPath(input.projectDir, input.installRoot, input.scope);
|
|
230
230
|
await writeJsonKey(config.path, config.jsonPath, claudeMcpEntry(endpoint));
|
|
231
231
|
entries.push({ kind: 'json-key', path: config.path, jsonPath: config.jsonPath });
|
|
232
232
|
}
|
|
233
233
|
else if (input.target === 'cursor') {
|
|
234
|
-
const config = cursorConfigPath(input.
|
|
234
|
+
const config = cursorConfigPath(input.installRoot, input.scope);
|
|
235
235
|
await writeJsonKey(config.path, config.jsonPath, cursorMcpEntry(endpoint));
|
|
236
236
|
entries.push({ kind: 'json-key', path: config.path, jsonPath: config.jsonPath });
|
|
237
237
|
}
|
|
@@ -241,7 +241,7 @@ async function installMcpClientConfig(input) {
|
|
|
241
241
|
else if (input.target === 'opencode') {
|
|
242
242
|
const path = input.scope === 'global'
|
|
243
243
|
? '~/.config/opencode/opencode.json'
|
|
244
|
-
: relative(input.
|
|
244
|
+
: relative(input.installRoot, join(input.installRoot, 'opencode.json'));
|
|
245
245
|
snippets.push(`Add this OpenCode MCP snippet to ${path}:\n${opencodeSnippet(endpoint)}`);
|
|
246
246
|
}
|
|
247
247
|
else if (input.target === 'universal') {
|
|
@@ -251,7 +251,7 @@ async function installMcpClientConfig(input) {
|
|
|
251
251
|
}
|
|
252
252
|
function plannedMcpJsonEntries(input) {
|
|
253
253
|
if (input.target === 'claude-code') {
|
|
254
|
-
const config = claudeConfigPath(input.projectDir, input.scope);
|
|
254
|
+
const config = claudeConfigPath(input.projectDir, input.installRoot, input.scope);
|
|
255
255
|
return [{ kind: 'json-key', path: config.path, jsonPath: config.jsonPath }];
|
|
256
256
|
}
|
|
257
257
|
if (input.target === 'claude-desktop') {
|
|
@@ -259,7 +259,7 @@ function plannedMcpJsonEntries(input) {
|
|
|
259
259
|
return [{ kind: 'json-key', path: config.path, jsonPath: config.jsonPath }];
|
|
260
260
|
}
|
|
261
261
|
if (input.target === 'cursor') {
|
|
262
|
-
const config = cursorConfigPath(input.
|
|
262
|
+
const config = cursorConfigPath(input.installRoot, input.scope);
|
|
263
263
|
return [{ kind: 'json-key', path: config.path, jsonPath: config.jsonPath }];
|
|
264
264
|
}
|
|
265
265
|
return [];
|
|
@@ -324,7 +324,7 @@ export function plannedKtxAgentFiles(input) {
|
|
|
324
324
|
}
|
|
325
325
|
throw new Error(`Global ${input.target} installation is not supported; omit --global.`);
|
|
326
326
|
}
|
|
327
|
-
const root = resolve(input.projectDir);
|
|
327
|
+
const root = resolve(input.installRoot ?? input.projectDir);
|
|
328
328
|
const analyticsEntries = {
|
|
329
329
|
'claude-code': [
|
|
330
330
|
{ kind: 'file', path: join(root, '.claude/skills/ktx-analytics/SKILL.md'), role: 'analytics-skill' },
|
|
@@ -502,7 +502,8 @@ function entryKey(entry) {
|
|
|
502
502
|
function mergeManifest(projectDir, existing, installs, entries) {
|
|
503
503
|
const installMap = new Map();
|
|
504
504
|
for (const install of [...(existing?.installs ?? []), ...installs]) {
|
|
505
|
-
|
|
505
|
+
const installRoot = install.installRoot ?? resolve(projectDir);
|
|
506
|
+
installMap.set(`${install.target}:${install.scope}:${install.mode}:${installRoot}`, { ...install, installRoot });
|
|
506
507
|
}
|
|
507
508
|
const entryMap = new Map();
|
|
508
509
|
for (const entry of [...(existing?.entries ?? []), ...entries]) {
|
|
@@ -611,18 +612,31 @@ function formatInlinePath(path) {
|
|
|
611
612
|
}
|
|
612
613
|
return path;
|
|
613
614
|
}
|
|
615
|
+
function installSummaryTitle(install, projectDir) {
|
|
616
|
+
const name = targetDisplayName(install.target);
|
|
617
|
+
if (install.scope !== 'project') {
|
|
618
|
+
return `${name} · ${scopeDisplayName(install.scope)}`;
|
|
619
|
+
}
|
|
620
|
+
const installRoot = resolve(install.installRoot ?? projectDir);
|
|
621
|
+
if (installRoot === resolve(projectDir)) {
|
|
622
|
+
return `${name} · ${scopeDisplayName('project')}`;
|
|
623
|
+
}
|
|
624
|
+
return `${name} · ${formatInlinePath(installRoot)}`;
|
|
625
|
+
}
|
|
614
626
|
/** @internal */
|
|
615
627
|
export function formatInstallSummaryLines(installs, entries, projectDir) {
|
|
616
628
|
const entriesByTarget = new Map();
|
|
617
629
|
for (const install of installs) {
|
|
618
|
-
const
|
|
630
|
+
const installRoot = install.installRoot ?? projectDir;
|
|
631
|
+
const plannedFilePaths = new Set(plannedKtxAgentFiles({ projectDir, ...install, installRoot })
|
|
619
632
|
.filter((entry) => entry.kind === 'file')
|
|
620
633
|
.map((entry) => entry.path));
|
|
621
634
|
entriesByTarget.set(install.target, entries.filter((entry) => entry.kind === 'file' && plannedFilePaths.has(entry.path)));
|
|
622
635
|
}
|
|
623
636
|
const mcpEntriesByTarget = new Map();
|
|
624
637
|
for (const install of installs) {
|
|
625
|
-
const
|
|
638
|
+
const installRoot = install.installRoot ?? projectDir;
|
|
639
|
+
const plannedMcpKeys = new Set(plannedMcpJsonEntries({ projectDir, installRoot, target: install.target, scope: install.scope }).map(entryKey));
|
|
626
640
|
mcpEntriesByTarget.set(install.target, entries.filter((entry) => entry.kind === 'json-key' && plannedMcpKeys.has(entryKey(entry))));
|
|
627
641
|
}
|
|
628
642
|
return installs.map((install) => {
|
|
@@ -663,7 +677,7 @@ export function formatInstallSummaryLines(installs, entries, projectDir) {
|
|
|
663
677
|
lines.push(`${guidanceInstallLine(install.target)}.`);
|
|
664
678
|
}
|
|
665
679
|
return {
|
|
666
|
-
title:
|
|
680
|
+
title: installSummaryTitle(install, projectDir),
|
|
667
681
|
lines,
|
|
668
682
|
};
|
|
669
683
|
});
|
|
@@ -726,6 +740,9 @@ function manualActionFromSnippet(snippet) {
|
|
|
726
740
|
body,
|
|
727
741
|
};
|
|
728
742
|
}
|
|
743
|
+
function openFromDirectoryLabel(installRoot, projectDir) {
|
|
744
|
+
return resolve(installRoot) === resolve(projectDir) ? 'the ktx project directory' : 'the install directory';
|
|
745
|
+
}
|
|
729
746
|
function formatAgentNextActions(input) {
|
|
730
747
|
const projectDir = resolve(input.projectDir);
|
|
731
748
|
const lines = [];
|
|
@@ -762,10 +779,11 @@ function formatAgentNextActions(input) {
|
|
|
762
779
|
if (claudeCodeInstall) {
|
|
763
780
|
lines.push(`${step}. Open Claude Code`);
|
|
764
781
|
if (claudeCodeInstall.scope === 'project') {
|
|
765
|
-
|
|
782
|
+
const installRoot = resolve(claudeCodeInstall.installRoot ?? projectDir);
|
|
783
|
+
lines.push(` Open Claude Code from ${openFromDirectoryLabel(installRoot, projectDir)}:`);
|
|
766
784
|
lines.push('');
|
|
767
785
|
lines.push(' RUN:');
|
|
768
|
-
lines.push(` cd ${shellScriptQuote(
|
|
786
|
+
lines.push(` cd ${shellScriptQuote(installRoot)}`);
|
|
769
787
|
lines.push(' claude');
|
|
770
788
|
}
|
|
771
789
|
else {
|
|
@@ -779,10 +797,11 @@ function formatAgentNextActions(input) {
|
|
|
779
797
|
if (cursorInstall) {
|
|
780
798
|
lines.push(`${step}. Open Cursor`);
|
|
781
799
|
if (cursorInstall.scope === 'project') {
|
|
782
|
-
|
|
800
|
+
const installRoot = resolve(cursorInstall.installRoot ?? projectDir);
|
|
801
|
+
lines.push(` Open Cursor from ${openFromDirectoryLabel(installRoot, projectDir)}:`);
|
|
783
802
|
lines.push('');
|
|
784
803
|
lines.push(' OPEN:');
|
|
785
|
-
lines.push(` ${
|
|
804
|
+
lines.push(` ${installRoot}`);
|
|
786
805
|
}
|
|
787
806
|
else {
|
|
788
807
|
lines.push(' Open Cursor.');
|
|
@@ -844,6 +863,70 @@ async function markAgentsComplete(projectDir) {
|
|
|
844
863
|
await writeFile(project.configPath, serializeKtxProjectConfig(project.config), 'utf-8');
|
|
845
864
|
await markKtxSetupStateStepComplete(projectDir, 'agents');
|
|
846
865
|
}
|
|
866
|
+
// A typed path never passes through a shell, so expand a leading ~ here; HOME
|
|
867
|
+
// matches formatInlinePath so the ~/… hints shown in the menu round-trip.
|
|
868
|
+
function resolveTypedInstallDir(cwd, raw) {
|
|
869
|
+
const home = process.env.HOME;
|
|
870
|
+
if (home && (raw === '~' || raw.startsWith('~/'))) {
|
|
871
|
+
return resolve(home, raw.slice(2));
|
|
872
|
+
}
|
|
873
|
+
return resolve(cwd, raw);
|
|
874
|
+
}
|
|
875
|
+
async function ensureInstallDir(resolvedPath) {
|
|
876
|
+
if (existsSync(resolvedPath)) {
|
|
877
|
+
if (!(await stat(resolvedPath)).isDirectory()) {
|
|
878
|
+
throw new Error(`Install directory path is a file, not a directory: ${resolvedPath}`);
|
|
879
|
+
}
|
|
880
|
+
return resolvedPath;
|
|
881
|
+
}
|
|
882
|
+
await mkdir(resolvedPath, { recursive: true });
|
|
883
|
+
return resolvedPath;
|
|
884
|
+
}
|
|
885
|
+
async function promptInstallDirectory(input) {
|
|
886
|
+
const { prompts, io, cwd, projectRoot, scopeTargets } = input;
|
|
887
|
+
const options = [
|
|
888
|
+
{ value: 'project', label: 'ktx project directory', hint: formatInlinePath(projectRoot) },
|
|
889
|
+
...(cwd !== projectRoot
|
|
890
|
+
? [{ value: 'current', label: 'Current directory', hint: formatInlinePath(cwd) }]
|
|
891
|
+
: []),
|
|
892
|
+
{ value: 'custom', label: 'Custom directory…', hint: 'Enter a path' },
|
|
893
|
+
...(scopeTargets.every(targetSupportsGlobalScope)
|
|
894
|
+
? [
|
|
895
|
+
{
|
|
896
|
+
value: 'global',
|
|
897
|
+
label: 'Global scope (user config)',
|
|
898
|
+
hint: 'Agents can load this ktx project from any working directory.',
|
|
899
|
+
},
|
|
900
|
+
]
|
|
901
|
+
: []),
|
|
902
|
+
];
|
|
903
|
+
const choice = await prompts.select({
|
|
904
|
+
message: `Where should ktx install agent config?\n\nktx project: ${projectRoot}`,
|
|
905
|
+
options,
|
|
906
|
+
});
|
|
907
|
+
if (choice === 'back')
|
|
908
|
+
return 'back';
|
|
909
|
+
if (choice === 'global')
|
|
910
|
+
return { scope: 'global', installRoot: projectRoot };
|
|
911
|
+
if (choice === 'current')
|
|
912
|
+
return { scope: 'project', installRoot: cwd };
|
|
913
|
+
if (choice === 'project')
|
|
914
|
+
return { scope: 'project', installRoot: projectRoot };
|
|
915
|
+
while (true) {
|
|
916
|
+
const typed = await prompts.text({ message: 'Enter the directory to install agent config into' });
|
|
917
|
+
if (typed === undefined)
|
|
918
|
+
return 'back';
|
|
919
|
+
const trimmed = typed.trim();
|
|
920
|
+
if (trimmed === '')
|
|
921
|
+
continue;
|
|
922
|
+
try {
|
|
923
|
+
return { scope: 'project', installRoot: await ensureInstallDir(resolveTypedInstallDir(cwd, trimmed)) };
|
|
924
|
+
}
|
|
925
|
+
catch (error) {
|
|
926
|
+
io.stderr.write(`${errorMessage(error)}\n`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
847
930
|
export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
848
931
|
if (args.skipAgents) {
|
|
849
932
|
io.stdout.write('│ Agent integration skipped.\n');
|
|
@@ -905,30 +988,32 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
905
988
|
: 'Missing agent target: pass --target or use interactive setup.\n');
|
|
906
989
|
return { status: 'missing-input', projectDir: args.projectDir };
|
|
907
990
|
}
|
|
991
|
+
const cwd = resolve(args.cwd ?? process.cwd());
|
|
992
|
+
const projectRoot = resolve(args.projectDir);
|
|
908
993
|
const scopeTargets = targets.filter((target) => target !== 'claude-desktop');
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
return {
|
|
931
|
-
|
|
994
|
+
let selectedScope = args.scope;
|
|
995
|
+
let installRoot = projectRoot;
|
|
996
|
+
if (args.installRoot !== undefined) {
|
|
997
|
+
try {
|
|
998
|
+
installRoot = await ensureInstallDir(resolveTypedInstallDir(cwd, args.installRoot));
|
|
999
|
+
}
|
|
1000
|
+
catch (error) {
|
|
1001
|
+
writePrefixedLines((chunk) => io.stderr.write(chunk), errorMessage(error));
|
|
1002
|
+
return { status: 'failed', projectDir: args.projectDir };
|
|
1003
|
+
}
|
|
1004
|
+
selectedScope = 'project';
|
|
1005
|
+
}
|
|
1006
|
+
else if (args.inputMode !== 'disabled' && args.scope === 'project' && scopeTargets.length > 0) {
|
|
1007
|
+
const decision = await promptInstallDirectory({ prompts, io, cwd, projectRoot, scopeTargets });
|
|
1008
|
+
if (decision === 'back')
|
|
1009
|
+
return { status: 'back', projectDir: args.projectDir };
|
|
1010
|
+
selectedScope = decision.scope;
|
|
1011
|
+
installRoot = decision.installRoot;
|
|
1012
|
+
}
|
|
1013
|
+
const installs = targets.map((target) => {
|
|
1014
|
+
const scope = effectiveInstallScope(target, selectedScope);
|
|
1015
|
+
return { target, scope, mode, installRoot: scope === 'project' ? installRoot : projectRoot };
|
|
1016
|
+
});
|
|
932
1017
|
const entries = [];
|
|
933
1018
|
const snippets = [];
|
|
934
1019
|
const notices = new Set();
|
|
@@ -938,6 +1023,7 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
938
1023
|
entries.push(...targetEntries);
|
|
939
1024
|
const mcpResult = await installMcpClientConfig({
|
|
940
1025
|
projectDir: args.projectDir,
|
|
1026
|
+
installRoot: install.installRoot,
|
|
941
1027
|
target: install.target,
|
|
942
1028
|
scope: install.scope,
|
|
943
1029
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { KtxLlmRuntimePort } from './context/llm/runtime-port.js';
|
|
2
2
|
import { type ProposeQueryHistoryServiceAccountFiltersInput, type QueryHistoryFilterProposal } from './context/ingest/adapters/historic-sql/query-history-filter-picker.js';
|
|
3
3
|
import { type HistoricSqlReadinessProbe } from './context/ingest/historic-sql-probes.js';
|
|
4
|
+
import { type KtxProjectConnectionConfig } from './context/project/config.js';
|
|
4
5
|
import { loadKtxProject } from './context/project/project.js';
|
|
5
6
|
import type { KtxTableListEntry } from './context/scan/types.js';
|
|
6
7
|
import { type KtxCliIo } from './cli-runtime.js';
|
|
@@ -90,6 +91,8 @@ export interface KtxSetupDatabasesDeps {
|
|
|
90
91
|
createQueryHistoryLlmRuntime?: (projectDir: string, project: Awaited<ReturnType<typeof loadKtxProject>>) => KtxLlmRuntimePort | null;
|
|
91
92
|
}
|
|
92
93
|
/** @internal */
|
|
94
|
+
export declare function federationNoticeFor(connections: Record<string, KtxProjectConnectionConfig>, projectDir: string): string | null;
|
|
95
|
+
/** @internal */
|
|
93
96
|
export declare function managedDaemonOptionsForSetupQueryHistoryPicker(input: {
|
|
94
97
|
projectDir: string;
|
|
95
98
|
args: Pick<KtxSetupDatabasesArgs, 'cliVersion' | 'runtimeInstallPolicy' | 'inputMode'>;
|
package/dist/setup-databases.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import { delimiter, dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
+
import { deriveFederatedConnection, FEDERATED_CONNECTION_ID } from './context/connections/federation.js';
|
|
6
7
|
import { getDriverRegistration } from './context/connections/drivers.js';
|
|
7
8
|
import { createLocalKtxLlmRuntimeFromConfig } from './context/llm/local-config.js';
|
|
8
9
|
import { queryHistoryDialectForConnection } from './context/ingest/adapters/historic-sql/connection-dialect.js';
|
|
@@ -845,6 +846,21 @@ async function writeConnectionConfig(input) {
|
|
|
845
846
|
if (queryHistory?.enabled === true) {
|
|
846
847
|
await ensureHistoricSqlIngestDefaults(input.projectDir);
|
|
847
848
|
}
|
|
849
|
+
if (input.io) {
|
|
850
|
+
const federationNotice = federationNoticeFor(config.connections, input.projectDir);
|
|
851
|
+
if (federationNotice) {
|
|
852
|
+
writeSetupSection(input.io, 'Federated connection available', [federationNotice]);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
/** @internal */
|
|
857
|
+
export function federationNoticeFor(connections, projectDir) {
|
|
858
|
+
const descriptor = deriveFederatedConnection(connections, projectDir);
|
|
859
|
+
if (!descriptor) {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
const names = descriptor.members.map((m) => m.connectionId).join(', ');
|
|
863
|
+
return `Detected ${descriptor.members.length} attach-compatible databases (${names}). Run a cross-database join as read-only SQL against \`${FEDERATED_CONNECTION_ID}\` (ktx sql -c ${FEDERATED_CONNECTION_ID} "SELECT ..."), using catalog-qualified table names.`;
|
|
848
864
|
}
|
|
849
865
|
async function disableConnectionQueryHistory(projectDir, connectionId) {
|
|
850
866
|
const project = await loadKtxProject({ projectDir });
|
package/dist/setup-sources.js
CHANGED
|
@@ -19,6 +19,7 @@ import { markKtxSetupStateStepComplete } from './context/project/setup-config.js
|
|
|
19
19
|
import { createCliSpinner, errorMessage, writePrefixedLines } from './clack.js';
|
|
20
20
|
import { pickNotionRootPages } from './notion-page-picker.js';
|
|
21
21
|
import { runKtxSourceMapping } from './source-mapping.js';
|
|
22
|
+
import { assertSafeConnectionId } from './context/sl/source-files.js';
|
|
22
23
|
import { runConnectionSetupWithRecovery, } from './connection-recovery.js';
|
|
23
24
|
import { withMultiselectNavigation, withTextInputNavigation } from './prompt-navigation.js';
|
|
24
25
|
import { runKtxPublicIngest } from './public-ingest.js';
|
|
@@ -100,11 +101,6 @@ async function findDbtProjectSubpaths(rootDir) {
|
|
|
100
101
|
async function promptText(prompts, options) {
|
|
101
102
|
return await prompts.text({ ...options, message: withTextInputNavigation(options.message) });
|
|
102
103
|
}
|
|
103
|
-
function assertSafeConnectionId(connectionId) {
|
|
104
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(connectionId)) {
|
|
105
|
-
throw new Error(`Unsafe connection id: ${connectionId}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
104
|
function credentialRef(value, label) {
|
|
109
105
|
const ref = value?.trim();
|
|
110
106
|
if (!ref) {
|
package/dist/setup.d.ts
CHANGED
package/dist/setup.js
CHANGED
|
@@ -624,6 +624,7 @@ async function runKtxSetupInner(args, io, deps = {}) {
|
|
|
624
624
|
agents: true,
|
|
625
625
|
...(args.target ? { target: args.target } : {}),
|
|
626
626
|
scope: args.agentScope ?? 'project',
|
|
627
|
+
...(args.installRoot ? { installRoot: args.installRoot } : {}),
|
|
627
628
|
mode: 'mcp',
|
|
628
629
|
skipAgents: false,
|
|
629
630
|
showNextActions: agentsRequested,
|
package/dist/sql.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { executeFederatedQuery } from './connectors/duckdb/federated-executor.js';
|
|
1
2
|
import { loadKtxProject } from './context/project/project.js';
|
|
2
3
|
import type { SqlAnalysisPort } from './context/sql-analysis/ports.js';
|
|
3
4
|
import type { KtxCliIo } from './cli-runtime.js';
|
|
@@ -18,6 +19,7 @@ export interface KtxSqlDeps {
|
|
|
18
19
|
loadProject?: typeof loadKtxProject;
|
|
19
20
|
createSqlAnalysis?: () => SqlAnalysisPort;
|
|
20
21
|
createScanConnector?: typeof createKtxCliScanConnector;
|
|
22
|
+
executeFederated?: typeof executeFederatedQuery;
|
|
21
23
|
}
|
|
22
24
|
export declare function runKtxSql(args: KtxSqlArgs, io?: KtxCliIo, deps?: KtxSqlDeps): Promise<number>;
|
|
23
25
|
export {};
|