@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.
Files changed (105) hide show
  1. package/assets/python/{kaelio_ktx-0.1.0rc6-py3-none-any.whl → kaelio_ktx-0.1.1-py3-none-any.whl} +0 -0
  2. package/assets/python/manifest.json +4 -4
  3. package/dist/commands/mcp-commands.js +11 -3
  4. package/dist/commands/mcp-commands.test.js +30 -1
  5. package/dist/commands/setup-commands.js +14 -26
  6. package/dist/doctor.test.js +3 -4
  7. package/dist/index.test.js +26 -10
  8. package/dist/ingest-depth.js +0 -1
  9. package/dist/ingest.test-utils.js +2 -2
  10. package/dist/ingest.test.js +6 -30
  11. package/dist/managed-local-embeddings.d.ts +2 -0
  12. package/dist/managed-local-embeddings.js +2 -0
  13. package/dist/managed-local-embeddings.test.js +2 -0
  14. package/dist/managed-mcp-daemon.js +3 -2
  15. package/dist/managed-mcp-daemon.test.js +25 -0
  16. package/dist/managed-python-command.test.js +1 -0
  17. package/dist/managed-python-daemon.js +3 -2
  18. package/dist/managed-python-daemon.test.js +20 -0
  19. package/dist/managed-python-runtime.d.ts +4 -0
  20. package/dist/managed-python-runtime.js +47 -3
  21. package/dist/managed-python-runtime.test.js +51 -21
  22. package/dist/next-steps.js +1 -1
  23. package/dist/next-steps.test.js +2 -0
  24. package/dist/proxy-env.d.ts +1 -0
  25. package/dist/proxy-env.js +23 -0
  26. package/dist/proxy-env.test.js +17 -0
  27. package/dist/runtime-requirements.d.ts +1 -2
  28. package/dist/runtime-requirements.js +0 -7
  29. package/dist/runtime-requirements.test.js +2 -2
  30. package/dist/runtime.test.js +1 -0
  31. package/dist/setup-agents.d.ts +11 -3
  32. package/dist/setup-agents.js +400 -135
  33. package/dist/setup-agents.test.js +394 -62
  34. package/dist/setup-embeddings.d.ts +1 -0
  35. package/dist/setup-embeddings.js +28 -6
  36. package/dist/setup-embeddings.test.js +46 -4
  37. package/dist/setup-models.d.ts +0 -1
  38. package/dist/setup-models.js +2 -3
  39. package/dist/setup-models.test.js +8 -10
  40. package/dist/setup-project.d.ts +9 -1
  41. package/dist/setup-project.js +52 -25
  42. package/dist/setup-project.test.js +8 -8
  43. package/dist/setup-runtime.d.ts +0 -1
  44. package/dist/setup-runtime.js +0 -1
  45. package/dist/setup-runtime.test.js +9 -13
  46. package/dist/setup.d.ts +4 -2
  47. package/dist/setup.js +72 -30
  48. package/dist/setup.test.js +271 -58
  49. package/dist/sl.test.js +2 -1
  50. package/dist/standalone-smoke.test.js +2 -3
  51. package/dist/status-project.js +1 -10
  52. package/node_modules/@ktx/connector-clickhouse/dist/package-exports.test.js +1 -1
  53. package/node_modules/@ktx/context/dist/core/git.service.d.ts +0 -1
  54. package/node_modules/@ktx/context/dist/core/git.service.js +0 -12
  55. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.d.ts +1 -2
  56. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/historic-sql.adapter.js +0 -18
  57. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/local-ingest-acceptance.test.js +7 -7
  58. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.d.ts +4 -0
  59. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.js +38 -0
  60. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.d.ts +1 -0
  61. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/post-processor.test.js +63 -0
  62. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.d.ts +0 -5
  63. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.js +0 -48
  64. package/node_modules/@ktx/context/dist/ingest/adapters/historic-sql/projection.test.js +0 -83
  65. package/node_modules/@ktx/context/dist/ingest/index.d.ts +2 -1
  66. package/node_modules/@ktx/context/dist/ingest/index.js +1 -0
  67. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.d.ts +0 -2
  68. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.isolated-diff.test.js +0 -166
  69. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.js +45 -235
  70. package/node_modules/@ktx/context/dist/ingest/ingest-bundle.runner.test.js +38 -193
  71. package/node_modules/@ktx/context/dist/ingest/local-bundle-ingest.test.js +11 -30
  72. package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +5 -1
  73. package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.test.js +3 -3
  74. package/node_modules/@ktx/context/dist/ingest/local-embedding-provider.integration.test.js +9 -10
  75. package/node_modules/@ktx/context/dist/ingest/local-ingest.js +7 -0
  76. package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.d.ts +4 -4
  77. package/node_modules/@ktx/context/dist/ingest/memory-flow/schema.js +1 -1
  78. package/node_modules/@ktx/context/dist/ingest/memory-flow/types.d.ts +1 -1
  79. package/node_modules/@ktx/context/dist/ingest/ports.d.ts +20 -1
  80. package/node_modules/@ktx/context/dist/ingest/report-snapshot.d.ts +2 -73
  81. package/node_modules/@ktx/context/dist/ingest/report-snapshot.js +0 -27
  82. package/node_modules/@ktx/context/dist/ingest/reports.d.ts +5 -23
  83. package/node_modules/@ktx/context/dist/ingest/reports.js +24 -7
  84. package/node_modules/@ktx/context/dist/ingest/types.d.ts +0 -33
  85. package/node_modules/@ktx/context/dist/llm/local-config.js +2 -15
  86. package/node_modules/@ktx/context/dist/llm/local-config.test.js +3 -7
  87. package/node_modules/@ktx/context/dist/package-exports.test.js +1 -2
  88. package/node_modules/@ktx/context/dist/project/config.d.ts +0 -5
  89. package/node_modules/@ktx/context/dist/project/config.js +5 -5
  90. package/node_modules/@ktx/context/dist/project/config.test.js +4 -7
  91. package/node_modules/@ktx/context/dist/scan/enrichment-state.test.js +4 -4
  92. package/node_modules/@ktx/context/dist/scan/index.d.ts +1 -1
  93. package/node_modules/@ktx/context/dist/scan/local-enrichment.d.ts +2 -6
  94. package/node_modules/@ktx/context/dist/scan/local-enrichment.js +31 -47
  95. package/node_modules/@ktx/context/dist/scan/local-enrichment.test.js +35 -18
  96. package/node_modules/@ktx/context/dist/scan/local-scan.test.js +2 -3
  97. package/node_modules/@ktx/llm/dist/embedding-provider.d.ts +0 -7
  98. package/node_modules/@ktx/llm/dist/embedding-provider.js +12 -138
  99. package/node_modules/@ktx/llm/dist/embedding-provider.test.js +10 -25
  100. package/node_modules/@ktx/llm/dist/types.d.ts +1 -1
  101. package/package.json +4 -4
  102. package/node_modules/@ktx/context/dist/ingest/finalization-scope.d.ts +0 -22
  103. package/node_modules/@ktx/context/dist/ingest/finalization-scope.js +0 -95
  104. package/node_modules/@ktx/context/dist/ingest/finalization-scope.test.js +0 -114
  105. /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(`Python runtime install failed. Install log: ${input.logPath}`);
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: ['venv', layout.venvDir],
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
- async function writeAsset(root, contents = 'wheel-bytes') {
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: Buffer.byteLength(contents),
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: ['venv', result.layout.venvDir] },
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(3);
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'), { stdout: 'creating\n', stderr: 'bad python\n' });
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 runtime install failed/);
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('bad python');
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
  });
@@ -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 in this directory and ask a data question.`,
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
  ];
@@ -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 = 'agent-mcp' | 'query-history' | 'looker-source' | 'database-introspection' | 'local-embeddings';
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('requires core for agent/MCP setup', () => {
6
+ it('does not require runtime for agent/MCP setup alone', () => {
7
7
  const config = buildDefaultKtxProjectConfig();
8
- expect(resolveProjectRuntimeRequirements(config, { agents: true }).features).toEqual(['core']);
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 = {
@@ -40,6 +40,7 @@ describe('runKtxRuntime', () => {
40
40
  },
41
41
  asset: {
42
42
  wheelPath: '/assets/python/kaelio_ktx-0.1.0-py3-none-any.whl',
43
+ requiresPython: { specifier: '>=3.13', minimumVersion: '3.13' },
43
44
  manifest: {
44
45
  schemaVersion: 1,
45
46
  distributionName: 'kaelio-ktx',
@@ -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-plugin' | 'launcher';
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 formatInstallSummary(installs: Array<{
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): string;
94
+ }>, entries: InstallEntry[], projectDir: string): InstallSummaryEntry[];
87
95
  export declare function runKtxSetupAgentsStep(args: KtxSetupAgentsArgs, io: KtxCliIo, deps?: KtxSetupAgentsDeps): Promise<KtxSetupAgentsResult>;
88
96
  export {};