@kaelio/ktx 0.1.0-rc.6 → 0.1.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.1.0rc6-py3-none-any.whl → kaelio_ktx-0.1.0-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/ingest.test.js +2 -26
- package/dist/next-steps.js +1 -1
- package/dist/next-steps.test.js +2 -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/setup-agents.d.ts +11 -3
- package/dist/setup-agents.js +397 -134
- package/dist/setup-agents.test.js +359 -61
- package/dist/setup-runtime.d.ts +0 -1
- package/dist/setup-runtime.js +0 -1
- package/dist/setup-runtime.test.js +7 -13
- package/dist/setup.d.ts +3 -0
- package/dist/setup.js +51 -25
- package/dist/setup.test.js +112 -16
- 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 +6 -6
- 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.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 +3 -22
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +4 -0
- 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/package-exports.test.js +1 -2
- 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 → adapters/historic-sql/post-processor.test.d.ts} +0 -0
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { readKtxSetupState } from '@ktx/context/project';
|
|
5
5
|
import { strFromU8, unzipSync } from 'fflate';
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
|
-
import {
|
|
7
|
+
import { createAgentNextActionsLineFormatter, formatInstallSummaryLines, plannedKtxAgentFiles, readKtxAgentInstallManifest, removeKtxAgentInstall, runKtxSetupAgentsStep, } from './setup-agents.js';
|
|
8
8
|
function makeIo() {
|
|
9
9
|
let stdout = '';
|
|
10
10
|
let stderr = '';
|
|
@@ -72,7 +72,11 @@ describe('setup agents', () => {
|
|
|
72
72
|
]);
|
|
73
73
|
expect(plannedKtxAgentFiles({ projectDir: tempDir, target: 'claude-desktop', scope: 'global', mode: 'mcp' })).toEqual([
|
|
74
74
|
{ kind: 'file', path: join(tempDir, '.ktx/agents/claude/ktx-plugin-runner.sh'), role: 'launcher' },
|
|
75
|
-
{
|
|
75
|
+
{
|
|
76
|
+
kind: 'file',
|
|
77
|
+
path: join(tempDir, '.ktx/agents/claude/ktx-analytics.zip'),
|
|
78
|
+
role: 'claude-desktop-skill-bundle',
|
|
79
|
+
},
|
|
76
80
|
]);
|
|
77
81
|
expect(plannedKtxAgentFiles({ projectDir: tempDir, target: 'codex', scope: 'project', mode: 'mcp' })).toEqual([
|
|
78
82
|
{ kind: 'file', path: join(tempDir, '.agents/skills/ktx-analytics/SKILL.md'), role: 'analytics-skill' },
|
|
@@ -112,7 +116,16 @@ describe('setup agents', () => {
|
|
|
112
116
|
]);
|
|
113
117
|
expect(plannedKtxAgentFiles({ projectDir: tempDir, target: 'claude-desktop', scope: 'global', mode: 'mcp-cli' })).toEqual([
|
|
114
118
|
{ kind: 'file', path: join(tempDir, '.ktx/agents/claude/ktx-plugin-runner.sh'), role: 'launcher' },
|
|
115
|
-
{
|
|
119
|
+
{
|
|
120
|
+
kind: 'file',
|
|
121
|
+
path: join(tempDir, '.ktx/agents/claude/ktx-analytics.zip'),
|
|
122
|
+
role: 'claude-desktop-skill-bundle',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
kind: 'file',
|
|
126
|
+
path: join(tempDir, '.ktx/agents/claude/ktx.zip'),
|
|
127
|
+
role: 'claude-desktop-skill-bundle',
|
|
128
|
+
},
|
|
116
129
|
]);
|
|
117
130
|
});
|
|
118
131
|
it('installs target files, writes a manifest, and marks agents complete', async () => {
|
|
@@ -126,7 +139,7 @@ describe('setup agents', () => {
|
|
|
126
139
|
scope: 'project',
|
|
127
140
|
mode: 'mcp-cli',
|
|
128
141
|
skipAgents: false,
|
|
129
|
-
}, io.io)).resolves.
|
|
142
|
+
}, io.io)).resolves.toMatchObject({
|
|
130
143
|
status: 'ready',
|
|
131
144
|
projectDir: tempDir,
|
|
132
145
|
installs: [{ target: 'universal', scope: 'project', mode: 'mcp-cli' }],
|
|
@@ -150,6 +163,54 @@ describe('setup agents', () => {
|
|
|
150
163
|
expect(await readKtxSetupState(tempDir)).toEqual({ completed_steps: ['agents'] });
|
|
151
164
|
expect(io.stderr()).toBe('');
|
|
152
165
|
});
|
|
166
|
+
it('prints standalone agent next actions after successful installation', async () => {
|
|
167
|
+
const io = makeIo();
|
|
168
|
+
const result = await runKtxSetupAgentsStep({
|
|
169
|
+
projectDir: tempDir,
|
|
170
|
+
inputMode: 'disabled',
|
|
171
|
+
yes: true,
|
|
172
|
+
agents: true,
|
|
173
|
+
target: 'claude-code',
|
|
174
|
+
scope: 'project',
|
|
175
|
+
mode: 'mcp-cli',
|
|
176
|
+
skipAgents: false,
|
|
177
|
+
}, io.io);
|
|
178
|
+
expect(result).toMatchObject({
|
|
179
|
+
status: 'ready',
|
|
180
|
+
nextActions: expect.stringContaining('Run this command before using Claude Code:'),
|
|
181
|
+
});
|
|
182
|
+
expect(io.stdout()).toContain('Required before using agents');
|
|
183
|
+
expect(io.stdout()).toContain('Run this command before using Claude Code:');
|
|
184
|
+
expect(io.stdout()).toContain('RUN:');
|
|
185
|
+
expect(io.stdout()).toContain(`ktx mcp start --project-dir ${tempDir}`);
|
|
186
|
+
expect(io.stdout()).toContain('If you need to stop MCP later:');
|
|
187
|
+
expect(io.stdout()).toContain(`ktx mcp stop --project-dir ${tempDir}`);
|
|
188
|
+
expect(io.stdout()).toContain('All set.');
|
|
189
|
+
expect(io.stdout()).not.toContain('Finish agent setup');
|
|
190
|
+
expect(io.stdout()).not.toContain('Next actions');
|
|
191
|
+
});
|
|
192
|
+
it('can return agent next actions without printing them', async () => {
|
|
193
|
+
const io = makeIo();
|
|
194
|
+
const result = await runKtxSetupAgentsStep({
|
|
195
|
+
projectDir: tempDir,
|
|
196
|
+
inputMode: 'disabled',
|
|
197
|
+
yes: true,
|
|
198
|
+
agents: true,
|
|
199
|
+
target: 'claude-code',
|
|
200
|
+
scope: 'project',
|
|
201
|
+
mode: 'mcp-cli',
|
|
202
|
+
skipAgents: false,
|
|
203
|
+
showNextActions: false,
|
|
204
|
+
}, io.io);
|
|
205
|
+
expect(result).toMatchObject({
|
|
206
|
+
status: 'ready',
|
|
207
|
+
nextActions: expect.stringContaining(`ktx mcp start --project-dir ${tempDir}`),
|
|
208
|
+
});
|
|
209
|
+
expect(io.stdout()).toContain('Claude Code · Project scope');
|
|
210
|
+
expect(io.stdout()).not.toContain('Agent integration complete');
|
|
211
|
+
expect(io.stdout()).not.toContain('Required before using agents');
|
|
212
|
+
expect(io.stdout()).not.toContain('All set.');
|
|
213
|
+
});
|
|
153
214
|
it('installs the analytics skill from the runtime asset', async () => {
|
|
154
215
|
const io = makeIo();
|
|
155
216
|
await expect(runKtxSetupAgentsStep({
|
|
@@ -207,7 +268,6 @@ describe('setup agents', () => {
|
|
|
207
268
|
expect(await readKtxAgentInstallManifest(tempDir)).toMatchObject({
|
|
208
269
|
entries: expect.arrayContaining([{ kind: 'json-key', path: join(tempDir, '.mcp.json'), jsonPath: ['mcpServers', 'ktx'] }]),
|
|
209
270
|
});
|
|
210
|
-
expect(io.stdout()).toContain('Run `ktx mcp start` to enable the configured KTX MCP server.');
|
|
211
271
|
});
|
|
212
272
|
it('prompts for MCP-first client agent connection mode in interactive setup', async () => {
|
|
213
273
|
const io = makeIo();
|
|
@@ -229,10 +289,18 @@ describe('setup agents', () => {
|
|
|
229
289
|
installs: [{ target: 'claude-code', scope: 'project', mode: 'mcp' }],
|
|
230
290
|
});
|
|
231
291
|
expect(prompts.select).toHaveBeenCalledWith({
|
|
232
|
-
message: '
|
|
292
|
+
message: 'What should agents be allowed to do with this KTX project?',
|
|
233
293
|
options: [
|
|
234
|
-
{
|
|
235
|
-
|
|
294
|
+
{
|
|
295
|
+
value: 'mcp',
|
|
296
|
+
label: 'Ask data questions with KTX MCP',
|
|
297
|
+
hint: 'Installs the MCP connection and analytics workflow skill. Best for normal use.',
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
value: 'mcp-cli',
|
|
301
|
+
label: 'Ask data questions + manage KTX with CLI commands',
|
|
302
|
+
hint: 'Adds an admin CLI skill so agents can run ktx status, sl, wiki, and setup commands.',
|
|
303
|
+
},
|
|
236
304
|
],
|
|
237
305
|
});
|
|
238
306
|
expect(prompts.multiselect).toHaveBeenCalledWith(expect.objectContaining({
|
|
@@ -263,10 +331,18 @@ describe('setup agents', () => {
|
|
|
263
331
|
installs: [{ target: 'claude-code', scope: 'global', mode: 'mcp' }],
|
|
264
332
|
});
|
|
265
333
|
expect(prompts.select).toHaveBeenCalledWith({
|
|
266
|
-
message:
|
|
334
|
+
message: `Where should KTX install supported agent config?\n\nKTX project: ${tempDir}`,
|
|
267
335
|
options: [
|
|
268
|
-
{
|
|
269
|
-
|
|
336
|
+
{
|
|
337
|
+
value: 'project',
|
|
338
|
+
label: 'Project scope (KTX project directory)',
|
|
339
|
+
hint: 'Only agents opened from this KTX project path load the project-scoped config.',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
value: 'global',
|
|
343
|
+
label: 'Global scope (user config)',
|
|
344
|
+
hint: 'Agents can load this KTX project from any working directory.',
|
|
345
|
+
},
|
|
270
346
|
],
|
|
271
347
|
});
|
|
272
348
|
}
|
|
@@ -275,7 +351,7 @@ describe('setup agents', () => {
|
|
|
275
351
|
await rm(home, { recursive: true, force: true });
|
|
276
352
|
}
|
|
277
353
|
});
|
|
278
|
-
it('registers Claude Desktop MCP
|
|
354
|
+
it('registers Claude Desktop MCP and ships an uploadable analytics skill zip', async () => {
|
|
279
355
|
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
280
356
|
const previousHome = process.env.HOME;
|
|
281
357
|
const envSnapshot = captureEnvKeys(process.env, ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY']);
|
|
@@ -298,9 +374,11 @@ describe('setup agents', () => {
|
|
|
298
374
|
status: 'ready',
|
|
299
375
|
installs: [{ target: 'claude-desktop', scope: 'global', mode: 'mcp' }],
|
|
300
376
|
});
|
|
301
|
-
const
|
|
377
|
+
const analyticsSkillPath = join(tempDir, '.ktx/agents/claude/ktx-analytics.zip');
|
|
378
|
+
const adminSkillPath = join(tempDir, '.ktx/agents/claude/ktx.zip');
|
|
302
379
|
const launcherPath = join(tempDir, '.ktx/agents/claude/ktx-plugin-runner.sh');
|
|
303
|
-
await expect(stat(
|
|
380
|
+
await expect(stat(analyticsSkillPath)).resolves.toBeDefined();
|
|
381
|
+
await expect(stat(adminSkillPath)).rejects.toThrow();
|
|
304
382
|
const launcherStat = await stat(launcherPath);
|
|
305
383
|
expect(launcherStat.mode & 0o111).not.toBe(0);
|
|
306
384
|
const launcher = await readFile(launcherPath, 'utf-8');
|
|
@@ -312,18 +390,21 @@ describe('setup agents', () => {
|
|
|
312
390
|
command: launcherPath,
|
|
313
391
|
args: ['--project-dir', tempDir, 'mcp', 'stdio'],
|
|
314
392
|
});
|
|
315
|
-
expect(await readZipText(
|
|
316
|
-
await expect(readZipText(
|
|
317
|
-
expect(
|
|
318
|
-
|
|
319
|
-
expect(
|
|
320
|
-
expect(
|
|
321
|
-
|
|
322
|
-
expect(io.stdout()).toContain('Claude plugin generated');
|
|
323
|
-
expect(io.stdout()).toContain('.ktx/agents/claude/ktx-plugin.zip');
|
|
324
|
-
expect(io.stdout()).toContain('KTX MCP server registered');
|
|
393
|
+
expect(await readZipText(analyticsSkillPath, 'ktx-analytics/SKILL.md')).toContain('KTX Analytics Workflow');
|
|
394
|
+
await expect(readZipText(analyticsSkillPath, 'ktx/SKILL.md')).rejects.toThrow('Missing zip entry');
|
|
395
|
+
await expect(readZipText(analyticsSkillPath, '.claude-plugin/plugin.json')).rejects.toThrow('Missing zip entry');
|
|
396
|
+
await expect(readZipText(analyticsSkillPath, 'skills/ktx-analytics/SKILL.md')).rejects.toThrow('Missing zip entry');
|
|
397
|
+
expect(io.stdout()).toContain('Claude Desktop');
|
|
398
|
+
expect(io.stdout()).toContain(analyticsSkillPath);
|
|
399
|
+
expect(io.stdout()).not.toContain(adminSkillPath);
|
|
325
400
|
expect(io.stdout()).toContain('claude_desktop_config.json');
|
|
326
|
-
expect(io.stdout()).toContain('
|
|
401
|
+
expect(io.stdout()).toContain('Required before using agents');
|
|
402
|
+
expect(io.stdout()).toContain('1. Restart Claude Desktop');
|
|
403
|
+
expect(io.stdout()).toContain('Claude Desktop loads KTX MCP after restart.');
|
|
404
|
+
expect(io.stdout()).toContain('2. Upload Claude Desktop skills');
|
|
405
|
+
expect(io.stdout()).toContain('Customize > Skills > + > Create skill > Upload a skill');
|
|
406
|
+
expect(io.stdout()).toContain('Upload this file:');
|
|
407
|
+
expect(io.stdout()).toContain('Toggle the uploaded KTX skills on.');
|
|
327
408
|
expect(io.stdout()).not.toContain('Run `ktx mcp start`');
|
|
328
409
|
}
|
|
329
410
|
finally {
|
|
@@ -374,7 +455,7 @@ describe('setup agents', () => {
|
|
|
374
455
|
await rm(home, { recursive: true, force: true });
|
|
375
456
|
}
|
|
376
457
|
});
|
|
377
|
-
it('includes
|
|
458
|
+
it('includes an uploadable admin CLI skill zip for Claude Desktop when requested', async () => {
|
|
378
459
|
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
379
460
|
const previousHome = process.env.HOME;
|
|
380
461
|
process.env.HOME = home;
|
|
@@ -393,12 +474,18 @@ describe('setup agents', () => {
|
|
|
393
474
|
status: 'ready',
|
|
394
475
|
installs: [{ target: 'claude-desktop', scope: 'global', mode: 'mcp-cli' }],
|
|
395
476
|
});
|
|
396
|
-
const
|
|
397
|
-
const
|
|
477
|
+
const analyticsSkillPath = join(tempDir, '.ktx/agents/claude/ktx-analytics.zip');
|
|
478
|
+
const adminSkillPath = join(tempDir, '.ktx/agents/claude/ktx.zip');
|
|
479
|
+
expect(await readZipText(analyticsSkillPath, 'ktx-analytics/SKILL.md')).toContain('KTX Analytics Workflow');
|
|
480
|
+
await expect(readZipText(analyticsSkillPath, 'ktx/SKILL.md')).rejects.toThrow('Missing zip entry');
|
|
481
|
+
const adminSkill = await readZipText(adminSkillPath, 'ktx/SKILL.md');
|
|
398
482
|
expect(adminSkill).toContain(`--project-dir ${tempDir}`);
|
|
399
483
|
expect(adminSkill).toContain('status --json');
|
|
400
|
-
expect(
|
|
401
|
-
await expect(readZipText(
|
|
484
|
+
await expect(readZipText(adminSkillPath, '.mcp.json')).rejects.toThrow('Missing zip entry');
|
|
485
|
+
await expect(readZipText(adminSkillPath, 'ktx-analytics/SKILL.md')).rejects.toThrow('Missing zip entry');
|
|
486
|
+
expect(io.stdout()).toContain(analyticsSkillPath);
|
|
487
|
+
expect(io.stdout()).toContain(adminSkillPath);
|
|
488
|
+
expect(io.stdout()).toContain('Upload each file separately:');
|
|
402
489
|
}
|
|
403
490
|
finally {
|
|
404
491
|
process.env.HOME = previousHome;
|
|
@@ -455,6 +542,9 @@ describe('setup agents', () => {
|
|
|
455
542
|
}, codexIo.io);
|
|
456
543
|
expect(codexIo.stdout()).toContain('[mcp_servers.ktx]');
|
|
457
544
|
expect(codexIo.stdout()).toContain('url = "http://localhost:7878/mcp"');
|
|
545
|
+
expect(codexIo.stdout()).toContain('1. Configure Codex');
|
|
546
|
+
expect(codexIo.stdout()).toContain('Open ~/.codex/config.toml, then paste this block:');
|
|
547
|
+
expect(codexIo.stdout()).toContain('PASTE:');
|
|
458
548
|
const opencodeIo = makeIo();
|
|
459
549
|
await runKtxSetupAgentsStep({
|
|
460
550
|
projectDir: tempDir,
|
|
@@ -468,6 +558,8 @@ describe('setup agents', () => {
|
|
|
468
558
|
}, opencodeIo.io);
|
|
469
559
|
expect(opencodeIo.stdout()).toContain('"mcp"');
|
|
470
560
|
expect(opencodeIo.stdout()).toContain('"type": "remote"');
|
|
561
|
+
expect(opencodeIo.stdout()).toContain('1. Configure OpenCode');
|
|
562
|
+
expect(opencodeIo.stdout()).toContain('Open opencode.json, then paste this block:');
|
|
471
563
|
await expect(readFile(join(tempDir, 'opencode.json'), 'utf-8')).rejects.toThrow();
|
|
472
564
|
const universalIo = makeIo();
|
|
473
565
|
await runKtxSetupAgentsStep({
|
|
@@ -482,6 +574,8 @@ describe('setup agents', () => {
|
|
|
482
574
|
}, universalIo.io);
|
|
483
575
|
expect(universalIo.stdout()).toContain('Universal MCP endpoint:');
|
|
484
576
|
expect(universalIo.stdout()).toContain('http://localhost:7878/mcp');
|
|
577
|
+
expect(universalIo.stdout()).toContain('1. Configure unsupported MCP clients');
|
|
578
|
+
expect(universalIo.stdout()).toContain('Use this endpoint when setting up unsupported MCP clients:');
|
|
485
579
|
});
|
|
486
580
|
it('uses MCP daemon state for port and token metadata without rendering literal tokens', async () => {
|
|
487
581
|
await mkdir(join(tempDir, '.ktx'), { recursive: true });
|
|
@@ -513,7 +607,9 @@ describe('setup agents', () => {
|
|
|
513
607
|
expect(rendered).toContain('http://127.0.0.1:8787/mcp');
|
|
514
608
|
expect(rendered).toContain('Bearer ${KTX_MCP_TOKEN}');
|
|
515
609
|
expect(rendered).not.toContain('secret-token');
|
|
516
|
-
expect(io.stdout()).toContain('Run
|
|
610
|
+
expect(io.stdout()).toContain('Run this command before using Claude Code:');
|
|
611
|
+
expect(io.stdout()).toContain('RUN:');
|
|
612
|
+
expect(io.stdout()).toContain(`ktx mcp start --project-dir ${tempDir}`);
|
|
517
613
|
}
|
|
518
614
|
finally {
|
|
519
615
|
if (previousToken === undefined) {
|
|
@@ -567,7 +663,7 @@ describe('setup agents', () => {
|
|
|
567
663
|
await expect(stat(join(tempDir, '.claude/skills/ktx/keep.txt'))).resolves.toBeDefined();
|
|
568
664
|
await expect(readKtxAgentInstallManifest(tempDir)).resolves.toEqual(null);
|
|
569
665
|
});
|
|
570
|
-
it('removes generated Claude Desktop
|
|
666
|
+
it('removes generated Claude Desktop skill zips from the manifest', async () => {
|
|
571
667
|
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
572
668
|
const previousHome = process.env.HOME;
|
|
573
669
|
process.env.HOME = home;
|
|
@@ -583,15 +679,18 @@ describe('setup agents', () => {
|
|
|
583
679
|
mode: 'mcp-cli',
|
|
584
680
|
skipAgents: false,
|
|
585
681
|
}, io.io);
|
|
586
|
-
const
|
|
682
|
+
const analyticsSkillPath = join(tempDir, '.ktx/agents/claude/ktx-analytics.zip');
|
|
683
|
+
const adminSkillPath = join(tempDir, '.ktx/agents/claude/ktx.zip');
|
|
587
684
|
const launcherPath = join(tempDir, '.ktx/agents/claude/ktx-plugin-runner.sh');
|
|
588
685
|
const configPath = join(home, 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
589
|
-
await expect(stat(
|
|
686
|
+
await expect(stat(analyticsSkillPath)).resolves.toBeDefined();
|
|
687
|
+
await expect(stat(adminSkillPath)).resolves.toBeDefined();
|
|
590
688
|
await expect(stat(launcherPath)).resolves.toBeDefined();
|
|
591
689
|
const beforeConfig = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
592
690
|
expect(beforeConfig.mcpServers.ktx).toBeDefined();
|
|
593
691
|
await expect(removeKtxAgentInstall(tempDir, io.io)).resolves.toBe(0);
|
|
594
|
-
await expect(stat(
|
|
692
|
+
await expect(stat(analyticsSkillPath)).rejects.toThrow();
|
|
693
|
+
await expect(stat(adminSkillPath)).rejects.toThrow();
|
|
595
694
|
await expect(stat(launcherPath)).rejects.toThrow();
|
|
596
695
|
const afterConfig = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
597
696
|
expect(afterConfig.mcpServers.ktx).toBeUndefined();
|
|
@@ -619,7 +718,7 @@ describe('setup agents', () => {
|
|
|
619
718
|
skipAgents: false,
|
|
620
719
|
}, io.io, { prompts })).resolves.toEqual({ status: 'skipped', projectDir: tempDir });
|
|
621
720
|
});
|
|
622
|
-
it('
|
|
721
|
+
it('prints one navigation hint before interactive agent target prompts', async () => {
|
|
623
722
|
const io = makeIo();
|
|
624
723
|
const prompts = {
|
|
625
724
|
select: vi.fn(async () => 'mcp-cli'),
|
|
@@ -635,8 +734,10 @@ describe('setup agents', () => {
|
|
|
635
734
|
mode: 'mcp-cli',
|
|
636
735
|
skipAgents: false,
|
|
637
736
|
}, io.io, { prompts })).resolves.toEqual({ status: 'back', projectDir: tempDir });
|
|
737
|
+
expect(io.stdout()).toContain('Space to select, Enter to confirm, Esc to go back.');
|
|
738
|
+
expect(io.stdout().match(/Space to select/g)).toHaveLength(1);
|
|
638
739
|
expect(prompts.multiselect).toHaveBeenCalledWith(expect.objectContaining({
|
|
639
|
-
message: 'Which agent targets should KTX install
|
|
740
|
+
message: 'Which agent targets should KTX install?',
|
|
640
741
|
}));
|
|
641
742
|
});
|
|
642
743
|
it('prints per-agent install summary after successful installation', async () => {
|
|
@@ -652,45 +753,242 @@ describe('setup agents', () => {
|
|
|
652
753
|
skipAgents: false,
|
|
653
754
|
}, io.io);
|
|
654
755
|
const output = io.stdout();
|
|
655
|
-
expect(output).toContain('
|
|
656
|
-
expect(output).toContain(
|
|
657
|
-
expect(output).toContain('
|
|
658
|
-
expect(output).toContain('.
|
|
659
|
-
expect(output).toContain('
|
|
660
|
-
expect(output).toContain('
|
|
661
|
-
expect(output).toContain(
|
|
662
|
-
expect(output).toContain('
|
|
756
|
+
expect(output).toContain('Claude Code · Project scope');
|
|
757
|
+
expect(output).toContain(join(tempDir, '.mcp.json'));
|
|
758
|
+
expect(output).toContain('Requires MCP to be started.');
|
|
759
|
+
expect(output).toContain('Analytics skill installed.');
|
|
760
|
+
expect(output).toContain('Admin CLI skill installed.');
|
|
761
|
+
expect(output).not.toContain('Agent integration complete');
|
|
762
|
+
expect(output).not.toContain(`KTX project\n ${tempDir}`);
|
|
763
|
+
expect(output).not.toContain('Installed agents');
|
|
764
|
+
expect(output).not.toContain('.claude/skills/ktx-analytics/SKILL.md');
|
|
765
|
+
expect(output).not.toContain('.claude/skills/ktx/SKILL.md');
|
|
766
|
+
expect(output).not.toContain('.claude/rules/ktx.md');
|
|
663
767
|
});
|
|
664
|
-
it('formats summary with
|
|
665
|
-
const summary =
|
|
768
|
+
it('formats summary with explicit project-scoped config paths', () => {
|
|
769
|
+
const summary = formatInstallSummaryLines([{ target: 'cursor', scope: 'project', mode: 'mcp-cli' }], [
|
|
666
770
|
{ kind: 'file', path: join(tempDir, '.cursor/rules/ktx-analytics.mdc'), role: 'analytics-skill' },
|
|
667
771
|
{ kind: 'file', path: join(tempDir, '.cursor/rules/ktx.mdc') },
|
|
772
|
+
{ kind: 'json-key', path: join(tempDir, '.cursor/mcp.json'), jsonPath: ['mcpServers', 'ktx'] },
|
|
668
773
|
], tempDir);
|
|
669
|
-
expect(summary).
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
774
|
+
expect(summary).toEqual([
|
|
775
|
+
{
|
|
776
|
+
title: 'Cursor · Project scope',
|
|
777
|
+
lines: [
|
|
778
|
+
join(tempDir, '.cursor/mcp.json'),
|
|
779
|
+
'Requires MCP to be started.',
|
|
780
|
+
'Cursor rules installed.',
|
|
781
|
+
],
|
|
782
|
+
},
|
|
783
|
+
]);
|
|
675
784
|
});
|
|
676
785
|
it('formats summary with multiple agent targets', () => {
|
|
677
|
-
const summary =
|
|
786
|
+
const summary = formatInstallSummaryLines([
|
|
678
787
|
{ target: 'claude-code', scope: 'project', mode: 'mcp-cli' },
|
|
679
788
|
{ target: 'codex', scope: 'project', mode: 'mcp-cli' },
|
|
680
789
|
], [
|
|
681
790
|
{ kind: 'file', path: join(tempDir, '.claude/skills/ktx-analytics/SKILL.md'), role: 'analytics-skill' },
|
|
682
791
|
{ kind: 'file', path: join(tempDir, '.claude/skills/ktx/SKILL.md'), role: 'skill' },
|
|
683
792
|
{ kind: 'file', path: join(tempDir, '.claude/rules/ktx.md'), role: 'rule' },
|
|
793
|
+
{ kind: 'json-key', path: join(tempDir, '.mcp.json'), jsonPath: ['mcpServers', 'ktx'] },
|
|
684
794
|
{ kind: 'file', path: join(tempDir, '.agents/skills/ktx-analytics/SKILL.md'), role: 'analytics-skill' },
|
|
685
795
|
{ kind: 'file', path: join(tempDir, '.agents/skills/ktx/SKILL.md'), role: 'skill' },
|
|
686
796
|
{ kind: 'file', path: join(tempDir, '.codex/instructions/ktx.md'), role: 'rule' },
|
|
687
797
|
], tempDir);
|
|
688
|
-
expect(summary).
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
798
|
+
expect(summary).toEqual([
|
|
799
|
+
{
|
|
800
|
+
title: 'Claude Code · Project scope',
|
|
801
|
+
lines: [
|
|
802
|
+
join(tempDir, '.mcp.json'),
|
|
803
|
+
'Requires MCP to be started.',
|
|
804
|
+
'Analytics skill installed.',
|
|
805
|
+
'Admin CLI skill installed.',
|
|
806
|
+
],
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
title: 'Codex · Project scope',
|
|
810
|
+
lines: [
|
|
811
|
+
'Add the snippet shown below to ~/.codex/config.toml.',
|
|
812
|
+
'Requires MCP to be started.',
|
|
813
|
+
'Codex guidance installed.',
|
|
814
|
+
],
|
|
815
|
+
},
|
|
816
|
+
]);
|
|
817
|
+
});
|
|
818
|
+
it('prints one target-aware next actions block for mixed agent targets', async () => {
|
|
819
|
+
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
820
|
+
const previousHome = process.env.HOME;
|
|
821
|
+
process.env.HOME = home;
|
|
822
|
+
try {
|
|
823
|
+
const io = makeIo();
|
|
824
|
+
const prompts = {
|
|
825
|
+
select: vi.fn(async ({ message }) => message.startsWith('Where should') ? 'project' : 'mcp'),
|
|
826
|
+
multiselect: vi.fn(async () => ['claude-code', 'claude-desktop']),
|
|
827
|
+
cancel: vi.fn(),
|
|
828
|
+
};
|
|
829
|
+
await expect(runKtxSetupAgentsStep({
|
|
830
|
+
projectDir: tempDir,
|
|
831
|
+
inputMode: 'auto',
|
|
832
|
+
yes: false,
|
|
833
|
+
agents: true,
|
|
834
|
+
scope: 'project',
|
|
835
|
+
mode: 'mcp',
|
|
836
|
+
skipAgents: false,
|
|
837
|
+
}, io.io, { prompts })).resolves.toMatchObject({
|
|
838
|
+
status: 'ready',
|
|
839
|
+
installs: [
|
|
840
|
+
{ target: 'claude-code', scope: 'project', mode: 'mcp' },
|
|
841
|
+
{ target: 'claude-desktop', scope: 'global', mode: 'mcp' },
|
|
842
|
+
],
|
|
843
|
+
});
|
|
844
|
+
const output = io.stdout();
|
|
845
|
+
expect(output).toContain('Required before using agents');
|
|
846
|
+
expect(output).not.toContain('Next actions');
|
|
847
|
+
expect(output).toContain('1. Start MCP');
|
|
848
|
+
expect(output).toContain('Run this command before using Claude Code:');
|
|
849
|
+
expect(output).toContain(`ktx mcp start --project-dir ${tempDir}`);
|
|
850
|
+
expect(output).toContain(`ktx mcp stop --project-dir ${tempDir}\n\n2. Open Claude Code`);
|
|
851
|
+
expect(output).toContain('Open Claude Code from the KTX project directory');
|
|
852
|
+
expect(output).toContain('RUN:');
|
|
853
|
+
expect(output).toContain(`cd '${tempDir}'`);
|
|
854
|
+
expect(output).toContain('3. Restart Claude Desktop');
|
|
855
|
+
expect(output).toContain('Claude Desktop loads KTX MCP after restart.');
|
|
856
|
+
expect(output).toContain('4. Upload Claude Desktop skills');
|
|
857
|
+
expect(output).toContain('Customize > Skills > + > Create skill > Upload a skill');
|
|
858
|
+
expect(output).toContain(join(tempDir, '.ktx/agents/claude/ktx-analytics.zip'));
|
|
859
|
+
expect(output).not.toContain(join(tempDir, '.ktx/agents/claude/ktx.zip'));
|
|
860
|
+
expect(output).toContain('Upload this file:');
|
|
861
|
+
expect(output).toContain('All set.');
|
|
862
|
+
expect(output).not.toContain('Finish Claude Desktop setup');
|
|
863
|
+
expect(output).not.toContain('Run `ktx mcp start` to enable the configured KTX MCP server.');
|
|
864
|
+
}
|
|
865
|
+
finally {
|
|
866
|
+
process.env.HOME = previousHome;
|
|
867
|
+
await rm(home, { recursive: true, force: true });
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
it('does not tell global Claude Code installs to open from the project directory', async () => {
|
|
871
|
+
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
872
|
+
const previousHome = process.env.HOME;
|
|
873
|
+
process.env.HOME = home;
|
|
874
|
+
try {
|
|
875
|
+
const io = makeIo();
|
|
876
|
+
await expect(runKtxSetupAgentsStep({
|
|
877
|
+
projectDir: tempDir,
|
|
878
|
+
inputMode: 'disabled',
|
|
879
|
+
yes: true,
|
|
880
|
+
agents: true,
|
|
881
|
+
target: 'claude-code',
|
|
882
|
+
scope: 'global',
|
|
883
|
+
mode: 'mcp',
|
|
884
|
+
skipAgents: false,
|
|
885
|
+
}, io.io)).resolves.toMatchObject({
|
|
886
|
+
status: 'ready',
|
|
887
|
+
installs: [{ target: 'claude-code', scope: 'global', mode: 'mcp' }],
|
|
888
|
+
});
|
|
889
|
+
const output = io.stdout();
|
|
890
|
+
expect(output).toContain('2. Open Claude Code');
|
|
891
|
+
expect(output).toContain('RUN:');
|
|
892
|
+
expect(output).toContain('claude');
|
|
893
|
+
expect(output).not.toContain('Open Claude Code from the KTX project directory');
|
|
894
|
+
expect(output).not.toContain(`cd '${tempDir}'`);
|
|
895
|
+
}
|
|
896
|
+
finally {
|
|
897
|
+
process.env.HOME = previousHome;
|
|
898
|
+
await rm(home, { recursive: true, force: true });
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
it('explains next actions for Codex, Cursor, OpenCode, and universal MCP targets', async () => {
|
|
902
|
+
const io = makeIo();
|
|
903
|
+
const prompts = {
|
|
904
|
+
select: vi.fn(async () => 'mcp-cli'),
|
|
905
|
+
multiselect: vi.fn(async () => ['codex', 'cursor', 'opencode', 'universal']),
|
|
906
|
+
cancel: vi.fn(),
|
|
907
|
+
};
|
|
908
|
+
await expect(runKtxSetupAgentsStep({
|
|
909
|
+
projectDir: tempDir,
|
|
910
|
+
inputMode: 'auto',
|
|
911
|
+
yes: false,
|
|
912
|
+
agents: true,
|
|
913
|
+
scope: 'project',
|
|
914
|
+
mode: 'mcp-cli',
|
|
915
|
+
skipAgents: false,
|
|
916
|
+
}, io.io, { prompts })).resolves.toMatchObject({
|
|
917
|
+
status: 'ready',
|
|
918
|
+
installs: [
|
|
919
|
+
{ target: 'codex', scope: 'project', mode: 'mcp-cli' },
|
|
920
|
+
{ target: 'cursor', scope: 'project', mode: 'mcp-cli' },
|
|
921
|
+
{ target: 'opencode', scope: 'project', mode: 'mcp-cli' },
|
|
922
|
+
{ target: 'universal', scope: 'project', mode: 'mcp-cli' },
|
|
923
|
+
],
|
|
924
|
+
});
|
|
925
|
+
const output = io.stdout();
|
|
926
|
+
expect(output).toContain('Required before using agents');
|
|
927
|
+
expect(output).toContain('1. Configure Codex');
|
|
928
|
+
expect(output).toContain('2. Configure OpenCode');
|
|
929
|
+
expect(output).toContain('3. Configure unsupported MCP clients');
|
|
930
|
+
expect(output).toContain('4. Start MCP');
|
|
931
|
+
expect(output).toContain('Run this command before using Codex, Cursor, OpenCode, and Universal .agents:');
|
|
932
|
+
expect(output).toContain('Open Cursor from the KTX project directory');
|
|
933
|
+
expect(output).toContain('Open ~/.codex/config.toml, then paste this block:\n\n PASTE:\n [mcp_servers.ktx]');
|
|
934
|
+
expect(output).toContain('Open opencode.json, then paste this block:');
|
|
935
|
+
expect(output).toContain('Use this endpoint when setting up unsupported MCP clients:');
|
|
936
|
+
expect(output).toContain('Codex guidance installed');
|
|
937
|
+
expect(output).toContain('Cursor rules installed');
|
|
938
|
+
expect(output).toContain('OpenCode commands installed');
|
|
939
|
+
expect(output).toContain('.agents guidance installed');
|
|
940
|
+
});
|
|
941
|
+
describe('createAgentNextActionsLineFormatter', () => {
|
|
942
|
+
function makeColorStdout() {
|
|
943
|
+
return { write: () => true, hasColors: () => true };
|
|
944
|
+
}
|
|
945
|
+
function makePlainStdout() {
|
|
946
|
+
return { write: () => true, hasColors: () => false };
|
|
947
|
+
}
|
|
948
|
+
const ESC = String.fromCharCode(27);
|
|
949
|
+
it('returns the line untouched when the stream cannot render colors', () => {
|
|
950
|
+
const format = createAgentNextActionsLineFormatter(makePlainStdout());
|
|
951
|
+
expect(format('2. Upload Claude Desktop skills')).toBe('2. Upload Claude Desktop skills');
|
|
952
|
+
expect(format(' /tmp/ktx/.ktx/agents/claude/ktx.zip')).toBe(' /tmp/ktx/.ktx/agents/claude/ktx.zip');
|
|
953
|
+
});
|
|
954
|
+
it('styles step headings and aligns sub-prose under the title', () => {
|
|
955
|
+
const format = createAgentNextActionsLineFormatter(makeColorStdout());
|
|
956
|
+
const heading = format('2. Upload Claude Desktop skills');
|
|
957
|
+
expect(heading).toContain(ESC);
|
|
958
|
+
expect(heading).toContain('2');
|
|
959
|
+
expect(heading).toContain('Upload Claude Desktop skills');
|
|
960
|
+
expect(heading).not.toMatch(/^2\. /);
|
|
961
|
+
const sub = format(' Toggle the uploaded KTX skills on.');
|
|
962
|
+
expect(sub).toMatch(/^ {3}/);
|
|
963
|
+
expect(sub).toContain('Toggle the uploaded KTX skills on.');
|
|
964
|
+
});
|
|
965
|
+
it('renders skill bundle .zip paths as bullets and shortens HOME to ~', () => {
|
|
966
|
+
const previousHome = process.env.HOME;
|
|
967
|
+
process.env.HOME = '/tmp/test-home';
|
|
968
|
+
try {
|
|
969
|
+
const format = createAgentNextActionsLineFormatter(makeColorStdout());
|
|
970
|
+
const line = format(' /tmp/test-home/.ktx/agents/claude/ktx-analytics.zip');
|
|
971
|
+
expect(line).toContain('•');
|
|
972
|
+
expect(line).toContain('~/.ktx/agents/claude/ktx-analytics.zip');
|
|
973
|
+
expect(line).not.toContain('/tmp/test-home/');
|
|
974
|
+
}
|
|
975
|
+
finally {
|
|
976
|
+
if (previousHome === undefined)
|
|
977
|
+
delete process.env.HOME;
|
|
978
|
+
else
|
|
979
|
+
process.env.HOME = previousHome;
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
it('replaces breadcrumb separators with a typographic chevron', () => {
|
|
983
|
+
const format = createAgentNextActionsLineFormatter(makeColorStdout());
|
|
984
|
+
const line = format(' Open Claude Desktop: Customize > Skills > + > Create skill > Upload a skill.');
|
|
985
|
+
expect(line).toContain('›');
|
|
986
|
+
expect(line).not.toContain(' > ');
|
|
987
|
+
});
|
|
988
|
+
it('leaves already-styled lines untouched to avoid double-wrapping', () => {
|
|
989
|
+
const format = createAgentNextActionsLineFormatter(makeColorStdout());
|
|
990
|
+
const preStyled = `${ESC}[1m2. Already styled${ESC}[22m`;
|
|
991
|
+
expect(format(preStyled)).toBe(preStyled);
|
|
992
|
+
});
|
|
695
993
|
});
|
|
696
994
|
});
|
package/dist/setup-runtime.d.ts
CHANGED
package/dist/setup-runtime.js
CHANGED
|
@@ -9,7 +9,6 @@ export async function runKtxSetupRuntimeStep(args, io, deps = {}) {
|
|
|
9
9
|
const loadProjectForRuntime = deps.loadProject ?? loadKtxProject;
|
|
10
10
|
const project = await loadProjectForRuntime({ projectDir: args.projectDir });
|
|
11
11
|
const requirements = resolveProjectRuntimeRequirements(project.config, {
|
|
12
|
-
agents: args.agents,
|
|
13
12
|
databaseIntrospectionFallback: args.databaseIntrospectionFallback,
|
|
14
13
|
env: deps.env ?? process.env,
|
|
15
14
|
});
|