@kaelio/ktx 0.1.0-rc.6 → 0.1.1
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.1.0rc6-py3-none-any.whl → kaelio_ktx-0.1.1-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/commands/mcp-commands.js +11 -3
- package/dist/commands/mcp-commands.test.js +30 -1
- package/dist/commands/setup-commands.js +14 -26
- package/dist/doctor.test.js +3 -4
- package/dist/index.test.js +26 -10
- package/dist/ingest-depth.js +0 -1
- package/dist/ingest.test-utils.js +2 -2
- package/dist/ingest.test.js +6 -30
- package/dist/managed-local-embeddings.d.ts +2 -0
- package/dist/managed-local-embeddings.js +2 -0
- package/dist/managed-local-embeddings.test.js +2 -0
- package/dist/managed-mcp-daemon.js +3 -2
- package/dist/managed-mcp-daemon.test.js +25 -0
- package/dist/managed-python-command.test.js +1 -0
- package/dist/managed-python-daemon.js +3 -2
- package/dist/managed-python-daemon.test.js +20 -0
- package/dist/managed-python-runtime.d.ts +4 -0
- package/dist/managed-python-runtime.js +47 -3
- package/dist/managed-python-runtime.test.js +51 -21
- package/dist/next-steps.js +1 -1
- package/dist/next-steps.test.js +2 -0
- package/dist/proxy-env.d.ts +1 -0
- package/dist/proxy-env.js +23 -0
- package/dist/proxy-env.test.js +17 -0
- package/dist/runtime-requirements.d.ts +1 -2
- package/dist/runtime-requirements.js +0 -7
- package/dist/runtime-requirements.test.js +2 -2
- package/dist/runtime.test.js +1 -0
- package/dist/setup-agents.d.ts +11 -3
- package/dist/setup-agents.js +400 -135
- package/dist/setup-agents.test.js +394 -62
- package/dist/setup-embeddings.d.ts +1 -0
- package/dist/setup-embeddings.js +28 -6
- package/dist/setup-embeddings.test.js +46 -4
- package/dist/setup-models.d.ts +0 -1
- package/dist/setup-models.js +2 -3
- package/dist/setup-models.test.js +8 -10
- package/dist/setup-project.d.ts +9 -1
- package/dist/setup-project.js +52 -25
- package/dist/setup-project.test.js +8 -8
- package/dist/setup-runtime.d.ts +0 -1
- package/dist/setup-runtime.js +0 -1
- package/dist/setup-runtime.test.js +9 -13
- package/dist/setup.d.ts +4 -2
- package/dist/setup.js +72 -30
- package/dist/setup.test.js +271 -58
- package/dist/sl.test.js +2 -1
- package/dist/standalone-smoke.test.js +2 -3
- package/dist/status-project.js +1 -10
- package/node_modules/@ktx/connector-clickhouse/dist/package-exports.test.js +1 -1
- package/node_modules/@ktx/context/dist/core/git.service.d.ts +0 -1
- package/node_modules/@ktx/context/dist/core/git.service.js +0 -12
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.d.ts +1 -2
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.js +0 -18
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +7 -7
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.d.ts +4 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.js +38 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.d.ts +1 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.js +63 -0
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.d.ts +0 -5
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.js +0 -48
- package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.test.js +0 -83
- package/node_modules/@ktx/context/dist/ingest/index.d.ts +2 -1
- package/node_modules/@ktx/context/dist/ingest/index.js +1 -0
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.d.ts +0 -2
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.isolated-diff.test.js +0 -166
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.js +45 -235
- package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.test.js +38 -193
- package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +11 -30
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +5 -1
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.test.js +3 -3
- package/node_modules/@ktx/context/dist/ingest/local-embedding-provider.integration.test.js +9 -10
- package/node_modules/@ktx/context/dist/ingest/local-ingest.js +7 -0
- package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.d.ts +4 -4
- package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.js +1 -1
- package/node_modules/@ktx/context/dist/ingest/memory-flow/types.d.ts +1 -1
- package/node_modules/@ktx/context/dist/ingest/ports.d.ts +20 -1
- package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +2 -73
- package/node_modules/@ktx/context/dist/ingest/report-snapshot.js +0 -27
- package/node_modules/@ktx/context/dist/ingest/reports.d.ts +5 -23
- package/node_modules/@ktx/context/dist/ingest/reports.js +24 -7
- package/node_modules/@ktx/context/dist/ingest/types.d.ts +0 -33
- package/node_modules/@ktx/context/dist/llm/local-config.js +2 -15
- package/node_modules/@ktx/context/dist/llm/local-config.test.js +3 -7
- package/node_modules/@ktx/context/dist/package-exports.test.js +1 -2
- package/node_modules/@ktx/context/dist/project/config.d.ts +0 -5
- package/node_modules/@ktx/context/dist/project/config.js +5 -5
- package/node_modules/@ktx/context/dist/project/config.test.js +4 -7
- package/node_modules/@ktx/context/dist/scan/enrichment-state.test.js +4 -4
- package/node_modules/@ktx/context/dist/scan/index.d.ts +1 -1
- package/node_modules/@ktx/context/dist/scan/local-enrichment.d.ts +2 -6
- package/node_modules/@ktx/context/dist/scan/local-enrichment.js +31 -47
- package/node_modules/@ktx/context/dist/scan/local-enrichment.test.js +35 -18
- package/node_modules/@ktx/context/dist/scan/local-scan.test.js +2 -3
- package/node_modules/@ktx/llm/dist/embedding-provider.d.ts +0 -7
- package/node_modules/@ktx/llm/dist/embedding-provider.js +12 -138
- package/node_modules/@ktx/llm/dist/embedding-provider.test.js +10 -25
- package/node_modules/@ktx/llm/dist/types.d.ts +1 -1
- package/package.json +4 -4
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.d.ts +0 -22
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.js +0 -95
- package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.js +0 -114
- /package/{node_modules/@ktx/context/dist/ingest/finalization-scope.test.d.ts → dist/proxy-env.test.d.ts} +0 -0
|
@@ -5,6 +5,7 @@ import { homedir } from 'node:os';
|
|
|
5
5
|
import { basename, join } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
|
+
import { strFromU8, unzipSync } from 'fflate';
|
|
8
9
|
import { z } from 'zod';
|
|
9
10
|
const execFileAsync = promisify(execFile);
|
|
10
11
|
export const runtimeFeatureSchema = z.enum(['core', 'local-embeddings']);
|
|
@@ -100,6 +101,35 @@ async function readJsonFile(path) {
|
|
|
100
101
|
function isErrnoException(error, code) {
|
|
101
102
|
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
|
|
102
103
|
}
|
|
104
|
+
function parseRequiresPythonFromWheel(input) {
|
|
105
|
+
let files;
|
|
106
|
+
try {
|
|
107
|
+
files = unzipSync(new Uint8Array(input.contents));
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
throw new Error(`Unable to read bundled Python runtime wheel metadata: ${error instanceof Error ? error.message : String(error)}`);
|
|
111
|
+
}
|
|
112
|
+
const metadataEntry = Object.entries(files).find(([path]) => path.endsWith('.dist-info/METADATA'));
|
|
113
|
+
if (!metadataEntry) {
|
|
114
|
+
throw new Error(`Bundled Python runtime wheel metadata is missing: ${input.wheelPath}`);
|
|
115
|
+
}
|
|
116
|
+
const metadata = strFromU8(metadataEntry[1]);
|
|
117
|
+
const requiresPython = metadata
|
|
118
|
+
.split(/\r?\n/)
|
|
119
|
+
.map((line) => line.match(/^Requires-Python:\s*(.+)\s*$/i)?.[1]?.trim())
|
|
120
|
+
.find((value) => typeof value === 'string' && value.length > 0);
|
|
121
|
+
if (!requiresPython) {
|
|
122
|
+
throw new Error('Bundled Python runtime wheel metadata is missing Requires-Python');
|
|
123
|
+
}
|
|
124
|
+
const minimumMatch = requiresPython.match(/(?:^|[,\s])>=\s*([0-9]+)\.([0-9]+)(?:\.[0-9]+)?\b/);
|
|
125
|
+
if (!minimumMatch) {
|
|
126
|
+
throw new Error(`Unsupported bundled Python runtime Requires-Python: ${requiresPython}`);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
specifier: requiresPython,
|
|
130
|
+
minimumVersion: `${minimumMatch[1]}.${minimumMatch[2]}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
103
133
|
export async function verifyRuntimeAsset(input) {
|
|
104
134
|
const manifestPath = join(input.assetDir, 'manifest.json');
|
|
105
135
|
let manifestData;
|
|
@@ -124,7 +154,7 @@ export async function verifyRuntimeAsset(input) {
|
|
|
124
154
|
if (sha256 !== manifest.wheel.sha256 || wheel.byteLength !== manifest.wheel.bytes) {
|
|
125
155
|
throw new Error(`Bundled Python runtime wheel checksum mismatch: ${wheelPath}`);
|
|
126
156
|
}
|
|
127
|
-
return { manifest, wheelPath };
|
|
157
|
+
return { manifest, wheelPath, requiresPython: parseRequiresPythonFromWheel({ wheelPath, contents: wheel }) };
|
|
128
158
|
}
|
|
129
159
|
function normalizeFeatures(features) {
|
|
130
160
|
const requested = new Set(['core', ...features]);
|
|
@@ -155,6 +185,13 @@ function errorOutput(error) {
|
|
|
155
185
|
stderr: typeof value.stderr === 'string' ? value.stderr : '',
|
|
156
186
|
};
|
|
157
187
|
}
|
|
188
|
+
function installFailureMessage(input) {
|
|
189
|
+
const output = [input.stderr.trim(), input.stdout.trim()].filter((part) => part.length > 0).join('\n');
|
|
190
|
+
if (!output) {
|
|
191
|
+
return `Python runtime install failed. Install log: ${input.logPath}`;
|
|
192
|
+
}
|
|
193
|
+
return `Python runtime install failed.\n${output}\nInstall log: ${input.logPath}`;
|
|
194
|
+
}
|
|
158
195
|
async function runLogged(input) {
|
|
159
196
|
await appendFile(input.logPath, `$ ${input.command} ${input.args.join(' ')}\n`);
|
|
160
197
|
try {
|
|
@@ -175,7 +212,7 @@ async function runLogged(input) {
|
|
|
175
212
|
if (output.stderr) {
|
|
176
213
|
await appendFile(input.logPath, output.stderr.endsWith('\n') ? output.stderr : `${output.stderr}\n`);
|
|
177
214
|
}
|
|
178
|
-
throw new Error(
|
|
215
|
+
throw new Error(installFailureMessage({ logPath: input.logPath, stdout: output.stdout, stderr: output.stderr }));
|
|
179
216
|
}
|
|
180
217
|
}
|
|
181
218
|
function managedRuntimeUvEnv(baseEnv) {
|
|
@@ -214,7 +251,14 @@ export async function installManagedPythonRuntime(options) {
|
|
|
214
251
|
exec,
|
|
215
252
|
logPath: layout.installLogPath,
|
|
216
253
|
command: 'uv',
|
|
217
|
-
args: ['
|
|
254
|
+
args: ['python', 'install', asset.requiresPython.minimumVersion],
|
|
255
|
+
env: uvEnv,
|
|
256
|
+
});
|
|
257
|
+
await runLogged({
|
|
258
|
+
exec,
|
|
259
|
+
logPath: layout.installLogPath,
|
|
260
|
+
command: 'uv',
|
|
261
|
+
args: ['venv', '--python', asset.requiresPython.minimumVersion, layout.venvDir],
|
|
218
262
|
env: uvEnv,
|
|
219
263
|
});
|
|
220
264
|
const wheelSpec = features.includes('local-embeddings') ? `${asset.wheelPath}[local-embeddings]` : asset.wheelPath;
|
|
@@ -2,12 +2,28 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { strToU8, zipSync } from 'fflate';
|
|
5
6
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
7
|
import { MISSING_UV_RUNTIME_INSTALL_MESSAGE, doctorManagedPythonRuntime, installManagedPythonRuntime, managedPythonDaemonLayout, managedPythonRuntimeLayout, readManagedPythonRuntimeStatus, verifyRuntimeAsset, } from './managed-python-runtime.js';
|
|
7
|
-
|
|
8
|
+
function runtimeWheelContents(input = {}) {
|
|
9
|
+
const label = input.label ?? 'runtime-wheel';
|
|
10
|
+
const requiresPython = input.requiresPython === null ? [] : [`Requires-Python: ${input.requiresPython ?? '>=3.13'}`];
|
|
11
|
+
return Buffer.from(zipSync({
|
|
12
|
+
'kaelio_ktx-0.1.0.dist-info/METADATA': strToU8([
|
|
13
|
+
'Metadata-Version: 2.4',
|
|
14
|
+
'Name: kaelio-ktx',
|
|
15
|
+
'Version: 0.1.0',
|
|
16
|
+
...requiresPython,
|
|
17
|
+
`Summary: ${label}`,
|
|
18
|
+
'',
|
|
19
|
+
].join('\n')),
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
async function writeAsset(root, options = {}) {
|
|
8
23
|
const assetDir = join(root, 'assets', 'python');
|
|
9
24
|
await mkdir(assetDir, { recursive: true });
|
|
10
25
|
const wheelPath = join(assetDir, 'kaelio_ktx-0.1.0-py3-none-any.whl');
|
|
26
|
+
const contents = options.contents ?? runtimeWheelContents(options);
|
|
11
27
|
await writeFile(wheelPath, contents);
|
|
12
28
|
await writeFile(join(assetDir, 'manifest.json'), `${JSON.stringify({
|
|
13
29
|
schemaVersion: 1,
|
|
@@ -17,7 +33,7 @@ async function writeAsset(root, contents = 'wheel-bytes') {
|
|
|
17
33
|
wheel: {
|
|
18
34
|
file: 'kaelio_ktx-0.1.0-py3-none-any.whl',
|
|
19
35
|
sha256: createHash('sha256').update(contents).digest('hex'),
|
|
20
|
-
bytes:
|
|
36
|
+
bytes: contents.byteLength,
|
|
21
37
|
},
|
|
22
38
|
}, null, 2)}\n`);
|
|
23
39
|
return { assetDir, wheelPath };
|
|
@@ -112,19 +128,20 @@ describe('verifyRuntimeAsset', () => {
|
|
|
112
128
|
await rm(tempDir, { recursive: true, force: true });
|
|
113
129
|
});
|
|
114
130
|
it('reads the manifest and verifies the wheel checksum', async () => {
|
|
115
|
-
const { assetDir, wheelPath } = await writeAsset(tempDir, 'valid-wheel');
|
|
131
|
+
const { assetDir, wheelPath } = await writeAsset(tempDir, { label: 'valid-wheel' });
|
|
116
132
|
const asset = await verifyRuntimeAsset({ assetDir });
|
|
117
133
|
expect(asset.manifest.distributionName).toBe('kaelio-ktx');
|
|
118
134
|
expect(asset.manifest.normalizedName).toBe('kaelio_ktx');
|
|
119
135
|
expect(asset.wheelPath).toBe(wheelPath);
|
|
136
|
+
expect(asset.requiresPython).toEqual({ specifier: '>=3.13', minimumVersion: '3.13' });
|
|
120
137
|
});
|
|
121
138
|
it('rejects a wheel whose checksum does not match the manifest', async () => {
|
|
122
|
-
const { assetDir, wheelPath } = await writeAsset(tempDir, 'original');
|
|
139
|
+
const { assetDir, wheelPath } = await writeAsset(tempDir, { label: 'original' });
|
|
123
140
|
await writeFile(wheelPath, 'tampered');
|
|
124
141
|
await expect(verifyRuntimeAsset({ assetDir })).rejects.toThrow(/Bundled Python runtime wheel checksum mismatch/);
|
|
125
142
|
});
|
|
126
143
|
it('rejects an unsafe wheel filename in the manifest', async () => {
|
|
127
|
-
const { assetDir } = await writeAsset(tempDir, 'valid-wheel');
|
|
144
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'valid-wheel' });
|
|
128
145
|
await writeFile(join(assetDir, 'manifest.json'), `${JSON.stringify({
|
|
129
146
|
schemaVersion: 1,
|
|
130
147
|
distributionName: 'kaelio-ktx',
|
|
@@ -142,6 +159,14 @@ describe('verifyRuntimeAsset', () => {
|
|
|
142
159
|
const assetDir = join(tempDir, 'packages', 'cli', 'assets', 'python');
|
|
143
160
|
await expect(verifyRuntimeAsset({ assetDir })).rejects.toThrow(/Missing bundled Python runtime manifest.*pnpm run artifacts:build/s);
|
|
144
161
|
});
|
|
162
|
+
it('rejects a bundled wheel without Requires-Python metadata', async () => {
|
|
163
|
+
const { assetDir } = await writeAsset(tempDir, { requiresPython: null });
|
|
164
|
+
await expect(verifyRuntimeAsset({ assetDir })).rejects.toThrow(/Bundled Python runtime wheel metadata is missing Requires-Python/);
|
|
165
|
+
});
|
|
166
|
+
it('rejects a bundled wheel without a supported minimum Python version', async () => {
|
|
167
|
+
const { assetDir } = await writeAsset(tempDir, { requiresPython: '<4' });
|
|
168
|
+
await expect(verifyRuntimeAsset({ assetDir })).rejects.toThrow(/Unsupported bundled Python runtime Requires-Python: <4/);
|
|
169
|
+
});
|
|
145
170
|
});
|
|
146
171
|
describe('installManagedPythonRuntime', () => {
|
|
147
172
|
let tempDir;
|
|
@@ -152,7 +177,7 @@ describe('installManagedPythonRuntime', () => {
|
|
|
152
177
|
await rm(tempDir, { recursive: true, force: true });
|
|
153
178
|
});
|
|
154
179
|
it('creates a venv, installs the core wheel, and writes a manifest', async () => {
|
|
155
|
-
const { assetDir } = await writeAsset(tempDir, 'core-wheel');
|
|
180
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
156
181
|
const commands = [];
|
|
157
182
|
const exec = vi.fn(async (command, args) => {
|
|
158
183
|
commands.push({ command, args });
|
|
@@ -168,7 +193,8 @@ describe('installManagedPythonRuntime', () => {
|
|
|
168
193
|
expect(result.status).toBe('installed');
|
|
169
194
|
expect(commands).toEqual([
|
|
170
195
|
{ command: 'uv', args: ['--version'] },
|
|
171
|
-
{ command: 'uv', args: ['
|
|
196
|
+
{ command: 'uv', args: ['python', 'install', '3.13'] },
|
|
197
|
+
{ command: 'uv', args: ['venv', '--python', '3.13', result.layout.venvDir] },
|
|
172
198
|
{
|
|
173
199
|
command: 'uv',
|
|
174
200
|
args: ['pip', 'install', '--python', result.layout.pythonPath, result.asset.wheelPath],
|
|
@@ -181,7 +207,7 @@ describe('installManagedPythonRuntime', () => {
|
|
|
181
207
|
expect(manifest.python.daemonExecutable).toBe(result.layout.daemonPath);
|
|
182
208
|
});
|
|
183
209
|
it('disables repo uv config for managed runtime uv commands', async () => {
|
|
184
|
-
const { assetDir } = await writeAsset(tempDir, 'core-wheel');
|
|
210
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
185
211
|
const commands = [];
|
|
186
212
|
const exec = vi.fn(async (command, args, options) => {
|
|
187
213
|
commands.push({ command, args, env: options?.env });
|
|
@@ -197,12 +223,13 @@ describe('installManagedPythonRuntime', () => {
|
|
|
197
223
|
});
|
|
198
224
|
expect(commands.map((call) => [call.command, call.args[0], call.env?.UV_NO_CONFIG, call.env?.PATH])).toEqual([
|
|
199
225
|
['uv', '--version', '1', '/opt/homebrew/bin'],
|
|
226
|
+
['uv', 'python', '1', '/opt/homebrew/bin'],
|
|
200
227
|
['uv', 'venv', '1', '/opt/homebrew/bin'],
|
|
201
228
|
['uv', 'pip', '1', '/opt/homebrew/bin'],
|
|
202
229
|
]);
|
|
203
230
|
});
|
|
204
231
|
it('installs the local-embeddings extra when requested', async () => {
|
|
205
|
-
const { assetDir } = await writeAsset(tempDir, 'embedding-wheel');
|
|
232
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'embedding-wheel' });
|
|
206
233
|
const commands = [];
|
|
207
234
|
const exec = vi.fn(async (command, args) => {
|
|
208
235
|
commands.push({ command, args });
|
|
@@ -223,7 +250,7 @@ describe('installManagedPythonRuntime', () => {
|
|
|
223
250
|
expect(manifest.features).toEqual(['core', 'local-embeddings']);
|
|
224
251
|
});
|
|
225
252
|
it('fails with the hard-prerequisite message when uv is missing', async () => {
|
|
226
|
-
const { assetDir } = await writeAsset(tempDir, 'core-wheel');
|
|
253
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
227
254
|
const commands = [];
|
|
228
255
|
const exec = vi.fn(async (command, args) => {
|
|
229
256
|
commands.push({ command, args });
|
|
@@ -239,7 +266,7 @@ describe('installManagedPythonRuntime', () => {
|
|
|
239
266
|
expect(commands).toEqual([{ command: 'uv', args: ['--version'] }]);
|
|
240
267
|
});
|
|
241
268
|
it('reuses an existing compatible runtime when force is false', async () => {
|
|
242
|
-
const { assetDir } = await writeAsset(tempDir, 'core-wheel');
|
|
269
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
243
270
|
const exec = vi.fn(async (command, args) => ({
|
|
244
271
|
stdout: command === 'uv' && args[0] === '--version' ? 'uv 0.9.5\n' : '',
|
|
245
272
|
stderr: '',
|
|
@@ -262,13 +289,16 @@ describe('installManagedPythonRuntime', () => {
|
|
|
262
289
|
exec,
|
|
263
290
|
});
|
|
264
291
|
expect(second.status).toBe('ready');
|
|
265
|
-
expect(exec).toHaveBeenCalledTimes(
|
|
292
|
+
expect(exec).toHaveBeenCalledTimes(4);
|
|
266
293
|
});
|
|
267
294
|
it('keeps failed install logs in the versioned runtime directory', async () => {
|
|
268
|
-
const { assetDir } = await writeAsset(tempDir, 'core-wheel');
|
|
295
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
269
296
|
const exec = vi.fn(async (command, args) => {
|
|
270
297
|
if (command === 'uv' && args[0] === 'venv') {
|
|
271
|
-
throw Object.assign(new Error('uv venv failed'), {
|
|
298
|
+
throw Object.assign(new Error('uv venv failed'), {
|
|
299
|
+
stdout: 'creating\n',
|
|
300
|
+
stderr: '× No solution found\n╰─▶ current Python version (3.12.3) does not satisfy Python>=3.13\n',
|
|
301
|
+
});
|
|
272
302
|
}
|
|
273
303
|
return { stdout: command === 'uv' && args[0] === '--version' ? 'uv 0.9.5\n' : '', stderr: '' };
|
|
274
304
|
});
|
|
@@ -278,10 +308,10 @@ describe('installManagedPythonRuntime', () => {
|
|
|
278
308
|
assetDir,
|
|
279
309
|
features: ['core'],
|
|
280
310
|
exec,
|
|
281
|
-
})).rejects.toThrow(/Python
|
|
311
|
+
})).rejects.toThrow(/current Python version \(3\.12\.3\) does not satisfy Python>=3\.13/);
|
|
282
312
|
const log = await readFile(join(tempDir, 'runtime', '0.2.0', 'install.log'), 'utf8');
|
|
283
|
-
expect(log).toContain('$ uv venv');
|
|
284
|
-
expect(log).toContain('
|
|
313
|
+
expect(log).toContain('$ uv venv --python 3.13');
|
|
314
|
+
expect(log).toContain('current Python version (3.12.3) does not satisfy Python>=3.13');
|
|
285
315
|
});
|
|
286
316
|
});
|
|
287
317
|
describe('readManagedPythonRuntimeStatus', () => {
|
|
@@ -302,7 +332,7 @@ describe('readManagedPythonRuntimeStatus', () => {
|
|
|
302
332
|
expect(status.detail).toContain('No runtime manifest');
|
|
303
333
|
});
|
|
304
334
|
it('reports ready when manifest and executables exist', async () => {
|
|
305
|
-
const { assetDir } = await writeAsset(tempDir, 'core-wheel');
|
|
335
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
306
336
|
const exec = vi.fn(async (command, args) => ({
|
|
307
337
|
stdout: command === 'uv' && args[0] === '--version' ? 'uv 0.9.5\n' : '',
|
|
308
338
|
stderr: '',
|
|
@@ -326,7 +356,7 @@ describe('readManagedPythonRuntimeStatus', () => {
|
|
|
326
356
|
expect(status.manifest?.features).toEqual(['core']);
|
|
327
357
|
});
|
|
328
358
|
it('reports broken when an executable is missing', async () => {
|
|
329
|
-
const { assetDir } = await writeAsset(tempDir, 'core-wheel');
|
|
359
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
330
360
|
const exec = vi.fn(async (command, args) => ({
|
|
331
361
|
stdout: command === 'uv' && args[0] === '--version' ? 'uv 0.9.5\n' : '',
|
|
332
362
|
stderr: '',
|
|
@@ -356,7 +386,7 @@ describe('doctorManagedPythonRuntime', () => {
|
|
|
356
386
|
await rm(tempDir, { recursive: true, force: true });
|
|
357
387
|
});
|
|
358
388
|
it('checks uv, bundled assets, and installed runtime status', async () => {
|
|
359
|
-
const { assetDir } = await writeAsset(tempDir, 'core-wheel');
|
|
389
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
360
390
|
const exec = vi.fn(async (command, args) => ({
|
|
361
391
|
stdout: command === 'uv' && args[0] === '--version' ? 'uv 0.9.5\n' : '',
|
|
362
392
|
stderr: '',
|
|
@@ -375,7 +405,7 @@ describe('doctorManagedPythonRuntime', () => {
|
|
|
375
405
|
expect(checks[2]?.fix).toBe('Run: ktx dev runtime install --yes');
|
|
376
406
|
});
|
|
377
407
|
it('reports uv as a hard prerequisite when uv is missing', async () => {
|
|
378
|
-
const { assetDir } = await writeAsset(tempDir, 'core-wheel');
|
|
408
|
+
const { assetDir } = await writeAsset(tempDir, { label: 'core-wheel' });
|
|
379
409
|
const exec = vi.fn(async () => {
|
|
380
410
|
throw new Error('spawn uv ENOENT');
|
|
381
411
|
});
|
package/dist/next-steps.js
CHANGED
|
@@ -29,7 +29,7 @@ function commandLines(commands, indent) {
|
|
|
29
29
|
}
|
|
30
30
|
export function formatNextStepLines(indent = ' ') {
|
|
31
31
|
return [
|
|
32
|
-
`${indent}KTX context is ready for agents. Open your coding agent
|
|
32
|
+
`${indent}KTX context is ready for agents. Open your coding agent from the KTX project directory and ask a data question.`,
|
|
33
33
|
`${indent}Verify with:`,
|
|
34
34
|
...commandLines(KTX_NEXT_STEP_DIRECT_COMMANDS, indent),
|
|
35
35
|
];
|
package/dist/next-steps.test.js
CHANGED
|
@@ -38,8 +38,10 @@ describe('KTX demo next steps', () => {
|
|
|
38
38
|
it('explains what the next-step commands are for', () => {
|
|
39
39
|
const rendered = formatNextStepLines().join('\n');
|
|
40
40
|
expect(rendered).toContain('KTX context is ready for agents.');
|
|
41
|
+
expect(rendered).toContain('KTX project directory');
|
|
41
42
|
expect(rendered).toContain('ask a data question');
|
|
42
43
|
expect(rendered).toContain('Verify with:');
|
|
44
|
+
expect(rendered).not.toContain('this directory');
|
|
43
45
|
expect(rendered).not.toContain('Preferred route');
|
|
44
46
|
expect(rendered).not.toContain('Optional MCP:');
|
|
45
47
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sanitizeChildProxyEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const NO_PROXY_KEYS = ['NO_PROXY', 'no_proxy'];
|
|
2
|
+
function isIpv6CidrNoProxyEntry(entry) {
|
|
3
|
+
return entry.includes('/') && entry.includes(':');
|
|
4
|
+
}
|
|
5
|
+
function cleanedNoProxyValue(env) {
|
|
6
|
+
const entries = NO_PROXY_KEYS.flatMap((key) => (env[key] ?? '').split(','))
|
|
7
|
+
.map((entry) => entry.trim())
|
|
8
|
+
.filter((entry) => entry.length > 0 && !isIpv6CidrNoProxyEntry(entry));
|
|
9
|
+
if (!NO_PROXY_KEYS.some((key) => env[key] !== undefined)) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
return [...new Set(entries)].join(',');
|
|
13
|
+
}
|
|
14
|
+
export function sanitizeChildProxyEnv(env) {
|
|
15
|
+
const sanitized = { ...env };
|
|
16
|
+
const noProxy = cleanedNoProxyValue(env);
|
|
17
|
+
if (noProxy === undefined) {
|
|
18
|
+
return sanitized;
|
|
19
|
+
}
|
|
20
|
+
sanitized.NO_PROXY = noProxy;
|
|
21
|
+
sanitized.no_proxy = noProxy;
|
|
22
|
+
return sanitized;
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { sanitizeChildProxyEnv } from './proxy-env.js';
|
|
3
|
+
describe('sanitizeChildProxyEnv', () => {
|
|
4
|
+
it('drops IPv6 CIDR no-proxy entries and normalizes both env keys', () => {
|
|
5
|
+
const env = sanitizeChildProxyEnv({
|
|
6
|
+
NO_PROXY: 'localhost,127.0.0.1,127.0.0.0/8,fd07:b51a:cc66:f0::/64,*.orb.local',
|
|
7
|
+
no_proxy: '::1,0.250.250.0/24,fd00::/8,*.orb.internal',
|
|
8
|
+
});
|
|
9
|
+
expect(env.NO_PROXY).toBe('localhost,127.0.0.1,127.0.0.0/8,*.orb.local,::1,0.250.250.0/24,*.orb.internal');
|
|
10
|
+
expect(env.no_proxy).toBe(env.NO_PROXY);
|
|
11
|
+
});
|
|
12
|
+
it('preserves the input object and leaves missing proxy env unset', () => {
|
|
13
|
+
const input = { PATH: '/usr/bin' };
|
|
14
|
+
expect(sanitizeChildProxyEnv(input)).toEqual({ PATH: '/usr/bin' });
|
|
15
|
+
expect(input).toEqual({ PATH: '/usr/bin' });
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { KtxProjectConfig } from '@ktx/context/project';
|
|
2
2
|
import type { KtxRuntimeFeature } from './managed-python-runtime.js';
|
|
3
3
|
import type { KtxPublicIngestPlan } from './public-ingest.js';
|
|
4
|
-
type KtxRuntimeRequirementReason = '
|
|
4
|
+
type KtxRuntimeRequirementReason = 'query-history' | 'looker-source' | 'database-introspection' | 'local-embeddings';
|
|
5
5
|
interface KtxRuntimeRequirement {
|
|
6
6
|
feature: KtxRuntimeFeature;
|
|
7
7
|
reason: KtxRuntimeRequirementReason;
|
|
@@ -12,7 +12,6 @@ export interface KtxRuntimeRequirements {
|
|
|
12
12
|
requirements: KtxRuntimeRequirement[];
|
|
13
13
|
}
|
|
14
14
|
export interface KtxProjectRuntimeRequirementOptions {
|
|
15
|
-
agents?: boolean;
|
|
16
15
|
databaseIntrospectionFallback?: boolean;
|
|
17
16
|
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
18
17
|
}
|
|
@@ -41,13 +41,6 @@ function uniqueRequirements(requirements) {
|
|
|
41
41
|
export function resolveProjectRuntimeRequirements(config, options = {}) {
|
|
42
42
|
const env = options.env ?? process.env;
|
|
43
43
|
const requirements = [];
|
|
44
|
-
if (options.agents === true) {
|
|
45
|
-
requirements.push({
|
|
46
|
-
feature: 'core',
|
|
47
|
-
reason: 'agent-mcp',
|
|
48
|
-
detail: 'Agent MCP setup uses semantic-layer query tools and SQL validation.',
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
44
|
if (options.databaseIntrospectionFallback === true && !hasDaemonOverride(env)) {
|
|
52
45
|
requirements.push({
|
|
53
46
|
feature: 'core',
|
|
@@ -3,9 +3,9 @@ import { buildDefaultKtxProjectConfig } from '@ktx/context/project';
|
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
4
|
import { resolveProjectRuntimeRequirements, resolvePublicIngestRuntimeRequirements, } from './runtime-requirements.js';
|
|
5
5
|
describe('runtime requirement detection', () => {
|
|
6
|
-
it('
|
|
6
|
+
it('does not require runtime for agent/MCP setup alone', () => {
|
|
7
7
|
const config = buildDefaultKtxProjectConfig();
|
|
8
|
-
expect(resolveProjectRuntimeRequirements(config
|
|
8
|
+
expect(resolveProjectRuntimeRequirements(config).features).toEqual([]);
|
|
9
9
|
});
|
|
10
10
|
it('requires core for Looker source ingest unless an external daemon is configured', () => {
|
|
11
11
|
const config = {
|
package/dist/runtime.test.js
CHANGED
package/dist/setup-agents.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface KtxSetupAgentsArgs {
|
|
|
12
12
|
scope: KtxAgentScope;
|
|
13
13
|
mode: KtxAgentInstallMode;
|
|
14
14
|
skipAgents: boolean;
|
|
15
|
+
showNextActions?: boolean;
|
|
15
16
|
}
|
|
16
17
|
export type KtxSetupAgentsResult = {
|
|
17
18
|
status: 'ready';
|
|
@@ -21,6 +22,7 @@ export type KtxSetupAgentsResult = {
|
|
|
21
22
|
scope: KtxAgentScope;
|
|
22
23
|
mode: KtxAgentInstallMode;
|
|
23
24
|
}>;
|
|
25
|
+
nextActions?: string;
|
|
24
26
|
} | {
|
|
25
27
|
status: 'skipped';
|
|
26
28
|
projectDir: string;
|
|
@@ -46,7 +48,7 @@ export interface KtxAgentInstallManifest {
|
|
|
46
48
|
entries: Array<{
|
|
47
49
|
kind: 'file';
|
|
48
50
|
path: string;
|
|
49
|
-
role?: 'skill' | 'rule' | 'analytics-skill' | 'claude-
|
|
51
|
+
role?: 'skill' | 'rule' | 'analytics-skill' | 'claude-desktop-skill-bundle' | 'launcher';
|
|
50
52
|
} | {
|
|
51
53
|
kind: 'json-key';
|
|
52
54
|
path: string;
|
|
@@ -54,6 +56,7 @@ export interface KtxAgentInstallManifest {
|
|
|
54
56
|
}>;
|
|
55
57
|
}
|
|
56
58
|
type InstallEntry = KtxAgentInstallManifest['entries'][number];
|
|
59
|
+
export declare function createAgentNextActionsLineFormatter(stdout: KtxCliIo['stdout']): (line: string) => string;
|
|
57
60
|
export declare function collectClaudeDesktopForwardedEnv(source: NodeJS.ProcessEnv): Record<string, string>;
|
|
58
61
|
export declare function agentInstallManifestPath(projectDir: string): string;
|
|
59
62
|
export declare function plannedKtxAgentFiles(input: {
|
|
@@ -79,10 +82,15 @@ export interface KtxSetupAgentsPromptAdapter {
|
|
|
79
82
|
export interface KtxSetupAgentsDeps {
|
|
80
83
|
prompts?: KtxSetupAgentsPromptAdapter;
|
|
81
84
|
}
|
|
82
|
-
export declare function
|
|
85
|
+
export declare function targetDisplayName(target: string): string;
|
|
86
|
+
export interface InstallSummaryEntry {
|
|
87
|
+
title: string;
|
|
88
|
+
lines: string[];
|
|
89
|
+
}
|
|
90
|
+
export declare function formatInstallSummaryLines(installs: Array<{
|
|
83
91
|
target: KtxAgentTarget;
|
|
84
92
|
scope: KtxAgentScope;
|
|
85
93
|
mode: KtxAgentInstallMode;
|
|
86
|
-
}>, entries: InstallEntry[], projectDir: string):
|
|
94
|
+
}>, entries: InstallEntry[], projectDir: string): InstallSummaryEntry[];
|
|
87
95
|
export declare function runKtxSetupAgentsStep(args: KtxSetupAgentsArgs, io: KtxCliIo, deps?: KtxSetupAgentsDeps): Promise<KtxSetupAgentsResult>;
|
|
88
96
|
export {};
|