@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
package/dist/setup-agents.js
CHANGED
|
@@ -2,12 +2,83 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { styleText } from 'node:util';
|
|
6
|
+
import { log, outro } from '@clack/prompts';
|
|
5
7
|
import { loadKtxProject, markKtxSetupStateStepComplete, serializeKtxProjectConfig, } from '@ktx/context/project';
|
|
6
8
|
import { strToU8, zipSync } from 'fflate';
|
|
7
|
-
import { bold, dim, green } from './io/symbols.js';
|
|
8
|
-
import { withMultiselectNavigation } from './prompt-navigation.js';
|
|
9
9
|
import { createKtxSetupPromptAdapter, createKtxSetupUiAdapter, } from './setup-prompts.js';
|
|
10
10
|
import { readKtxMcpDaemonStatus } from './managed-mcp-daemon.js';
|
|
11
|
+
const MCP_DAEMON_REQUIRED_NOTICE = 'mcp-daemon-required';
|
|
12
|
+
function isWritableTtyOutput(output) {
|
|
13
|
+
return (output.isTTY === true &&
|
|
14
|
+
typeof output.on === 'function' &&
|
|
15
|
+
typeof output.columns !== 'undefined');
|
|
16
|
+
}
|
|
17
|
+
function writeSetupInfo(io, message) {
|
|
18
|
+
if (isWritableTtyOutput(io.stdout)) {
|
|
19
|
+
log.info(message, { output: io.stdout });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
io.stdout.write(`${message}\n`);
|
|
23
|
+
}
|
|
24
|
+
function writeSetupStep(io, message) {
|
|
25
|
+
if (isWritableTtyOutput(io.stdout)) {
|
|
26
|
+
log.step(message, { output: io.stdout });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
io.stdout.write(`\n${message}\n`);
|
|
30
|
+
}
|
|
31
|
+
function writeSetupOutro(io, message) {
|
|
32
|
+
if (isWritableTtyOutput(io.stdout)) {
|
|
33
|
+
outro(message, { output: io.stdout });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
io.stdout.write(`\n${message}\n`);
|
|
37
|
+
}
|
|
38
|
+
const STEP_HEADING_RE = /^(\d+)\. (.+)$/;
|
|
39
|
+
const ACTION_MARKER_RE = /^(RUN|PASTE|USE|OPEN):$/;
|
|
40
|
+
export function createAgentNextActionsLineFormatter(stdout) {
|
|
41
|
+
const maybeHasColors = stdout.hasColors;
|
|
42
|
+
const supportsColor = typeof maybeHasColors === 'function' && Boolean(maybeHasColors.call(stdout));
|
|
43
|
+
if (!supportsColor)
|
|
44
|
+
return (line) => line;
|
|
45
|
+
const homeDir = process.env.HOME ? resolve(process.env.HOME) : '';
|
|
46
|
+
const styleOptions = { validateStream: false };
|
|
47
|
+
const dim = (s) => styleText('dim', s, styleOptions);
|
|
48
|
+
const bold = (s) => styleText('bold', s, styleOptions);
|
|
49
|
+
const cyanBold = (s) => styleText(['cyan', 'bold'], s, styleOptions);
|
|
50
|
+
const dimCyan = (s) => styleText(['dim', 'cyan'], s, styleOptions);
|
|
51
|
+
const shortenPath = (path) => {
|
|
52
|
+
if (!homeDir)
|
|
53
|
+
return path;
|
|
54
|
+
if (path === homeDir)
|
|
55
|
+
return '~';
|
|
56
|
+
if (path.startsWith(`${homeDir}/`))
|
|
57
|
+
return `~/${path.slice(homeDir.length + 1)}`;
|
|
58
|
+
return path;
|
|
59
|
+
};
|
|
60
|
+
return (rawLine) => {
|
|
61
|
+
if (rawLine.length === 0 || rawLine.includes('['))
|
|
62
|
+
return rawLine;
|
|
63
|
+
const heading = rawLine.match(STEP_HEADING_RE);
|
|
64
|
+
if (heading) {
|
|
65
|
+
return `${cyanBold(heading[1])} ${bold(heading[2])}`;
|
|
66
|
+
}
|
|
67
|
+
if (!rawLine.startsWith(' '))
|
|
68
|
+
return rawLine;
|
|
69
|
+
const body = rawLine.slice(2);
|
|
70
|
+
if (ACTION_MARKER_RE.test(body)) {
|
|
71
|
+
return ` ${dim(body)}`;
|
|
72
|
+
}
|
|
73
|
+
if (body.endsWith('.zip') && (body.startsWith('/') || body.startsWith('~'))) {
|
|
74
|
+
return ` ${dimCyan('•')} ${shortenPath(body)}`;
|
|
75
|
+
}
|
|
76
|
+
if (body.includes(' > ')) {
|
|
77
|
+
return ` ${body.replaceAll(' > ', ` ${dim('›')} `)}`;
|
|
78
|
+
}
|
|
79
|
+
return ` ${dim(body)}`;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
11
82
|
async function readJsonObject(path) {
|
|
12
83
|
if (!existsSync(path))
|
|
13
84
|
return {};
|
|
@@ -158,7 +229,7 @@ async function installMcpClientConfig(input) {
|
|
|
158
229
|
}
|
|
159
230
|
const endpoint = await resolveMcpEndpoint(input.projectDir);
|
|
160
231
|
if (!endpoint.running) {
|
|
161
|
-
notices.push(
|
|
232
|
+
notices.push(MCP_DAEMON_REQUIRED_NOTICE);
|
|
162
233
|
}
|
|
163
234
|
if (input.target === 'claude-code') {
|
|
164
235
|
const config = claudeConfigPath(input.projectDir, input.scope);
|
|
@@ -171,16 +242,16 @@ async function installMcpClientConfig(input) {
|
|
|
171
242
|
entries.push({ kind: 'json-key', path: config.path, jsonPath: config.jsonPath });
|
|
172
243
|
}
|
|
173
244
|
else if (input.target === 'codex') {
|
|
174
|
-
snippets.push(`Codex MCP snippet
|
|
245
|
+
snippets.push(`Add this Codex MCP snippet to ~/.codex/config.toml:\n${codexSnippet(endpoint)}`);
|
|
175
246
|
}
|
|
176
247
|
else if (input.target === 'opencode') {
|
|
177
248
|
const path = input.scope === 'global'
|
|
178
249
|
? '~/.config/opencode/opencode.json'
|
|
179
250
|
: relative(input.projectDir, join(input.projectDir, 'opencode.json'));
|
|
180
|
-
snippets.push(`
|
|
251
|
+
snippets.push(`Add this OpenCode MCP snippet to ${path}:\n${opencodeSnippet(endpoint)}`);
|
|
181
252
|
}
|
|
182
253
|
else if (input.target === 'universal') {
|
|
183
|
-
snippets.push(universalMcpSnippet(endpoint));
|
|
254
|
+
snippets.push(`Use this universal MCP endpoint with unsupported MCP clients:\n${universalMcpSnippet(endpoint)}`);
|
|
184
255
|
}
|
|
185
256
|
return { entries, snippets, notices };
|
|
186
257
|
}
|
|
@@ -202,8 +273,11 @@ function plannedMcpJsonEntries(input) {
|
|
|
202
273
|
export function agentInstallManifestPath(projectDir) {
|
|
203
274
|
return join(resolve(projectDir), '.ktx/agents/install-manifest.json');
|
|
204
275
|
}
|
|
205
|
-
function
|
|
206
|
-
return join(resolve(projectDir), '.ktx/agents/claude/ktx-
|
|
276
|
+
function claudeDesktopAnalyticsSkillBundlePath(projectDir) {
|
|
277
|
+
return join(resolve(projectDir), '.ktx/agents/claude/ktx-analytics.zip');
|
|
278
|
+
}
|
|
279
|
+
function claudeDesktopAdminSkillBundlePath(projectDir) {
|
|
280
|
+
return join(resolve(projectDir), '.ktx/agents/claude/ktx.zip');
|
|
207
281
|
}
|
|
208
282
|
function claudeDesktopLauncherPath(projectDir) {
|
|
209
283
|
return join(resolve(projectDir), '.ktx/agents/claude/ktx-plugin-runner.sh');
|
|
@@ -241,7 +315,20 @@ export function plannedKtxAgentFiles(input) {
|
|
|
241
315
|
if (input.target === 'claude-desktop') {
|
|
242
316
|
return [
|
|
243
317
|
{ kind: 'file', path: claudeDesktopLauncherPath(input.projectDir), role: 'launcher' },
|
|
244
|
-
{
|
|
318
|
+
{
|
|
319
|
+
kind: 'file',
|
|
320
|
+
path: claudeDesktopAnalyticsSkillBundlePath(input.projectDir),
|
|
321
|
+
role: 'claude-desktop-skill-bundle',
|
|
322
|
+
},
|
|
323
|
+
...(withAdminCli
|
|
324
|
+
? [
|
|
325
|
+
{
|
|
326
|
+
kind: 'file',
|
|
327
|
+
path: claudeDesktopAdminSkillBundlePath(input.projectDir),
|
|
328
|
+
role: 'claude-desktop-skill-bundle',
|
|
329
|
+
},
|
|
330
|
+
]
|
|
331
|
+
: []),
|
|
245
332
|
];
|
|
246
333
|
}
|
|
247
334
|
throw new Error(`Global ${input.target} installation is not supported; omit --global.`);
|
|
@@ -361,36 +448,7 @@ function cliInstructionContent(input) {
|
|
|
361
448
|
'',
|
|
362
449
|
].join('\n');
|
|
363
450
|
}
|
|
364
|
-
function
|
|
365
|
-
return `${JSON.stringify({
|
|
366
|
-
name: 'ktx',
|
|
367
|
-
version: '0.0.0-local',
|
|
368
|
-
description: 'KTX analytics workflow guidance and local MCP tools.',
|
|
369
|
-
}, null, 2)}\n`;
|
|
370
|
-
}
|
|
371
|
-
function claudePluginVersionContent() {
|
|
372
|
-
return `${JSON.stringify({ version: '0.0.0-local' }, null, 2)}\n`;
|
|
373
|
-
}
|
|
374
|
-
function claudePluginSetupContent(input) {
|
|
375
|
-
return [
|
|
376
|
-
'# KTX Claude Plugin',
|
|
377
|
-
'',
|
|
378
|
-
'Install this plugin ZIP from Claude Desktop to load the KTX analytics skill.',
|
|
379
|
-
'',
|
|
380
|
-
`KTX project: \`${input.projectDir}\``,
|
|
381
|
-
'',
|
|
382
|
-
'Included:',
|
|
383
|
-
'',
|
|
384
|
-
'- `ktx-analytics` skill for the MCP analytics workflow',
|
|
385
|
-
...(input.withAdminCli ? ['- `ktx` admin CLI skill for KTX maintenance commands'] : []),
|
|
386
|
-
'',
|
|
387
|
-
'The KTX MCP server is registered separately in `claude_desktop_config.json` by `ktx setup` and runs as a local stdio child of Claude Desktop — no daemon to start.',
|
|
388
|
-
'',
|
|
389
|
-
'If this checkout or project directory moves, rerun `ktx setup --agents` and reinstall the regenerated plugin.',
|
|
390
|
-
'',
|
|
391
|
-
].join('\n');
|
|
392
|
-
}
|
|
393
|
-
function claudePluginLauncherContent(input) {
|
|
451
|
+
function claudeDesktopLauncherContent(input) {
|
|
394
452
|
const binPath = input.launcher.args[0];
|
|
395
453
|
if (!binPath) {
|
|
396
454
|
throw new Error('Expected KTX CLI launcher to include a bin path.');
|
|
@@ -437,28 +495,33 @@ function claudePluginLauncherContent(input) {
|
|
|
437
495
|
' run_with_node "$(command -v node)" "$@"',
|
|
438
496
|
'fi',
|
|
439
497
|
'',
|
|
440
|
-
'echo "KTX
|
|
498
|
+
'echo "KTX Claude Desktop launcher could not find Node.js. Set KTX_NODE to a Node executable and rerun ktx setup --agents." >&2',
|
|
441
499
|
'exit 127',
|
|
442
500
|
'',
|
|
443
501
|
].join('\n');
|
|
444
502
|
}
|
|
445
|
-
async function
|
|
446
|
-
const
|
|
503
|
+
async function writeClaudeDesktopSkillBundle(input) {
|
|
504
|
+
const content = input.skillName === 'ktx-analytics'
|
|
505
|
+
? await readAnalyticsSkillContent()
|
|
506
|
+
: cliInstructionContent({ projectDir: input.projectDir, launcher: input.launcher });
|
|
447
507
|
const files = {
|
|
448
|
-
|
|
449
|
-
'version.json': strToU8(claudePluginVersionContent()),
|
|
450
|
-
'skills/ktx-analytics/SKILL.md': strToU8(await readAnalyticsSkillContent()),
|
|
451
|
-
'SETUP.md': strToU8(claudePluginSetupContent({ projectDir: input.projectDir, withAdminCli })),
|
|
508
|
+
[`${input.skillName}/SKILL.md`]: strToU8(content),
|
|
452
509
|
};
|
|
453
|
-
if (withAdminCli) {
|
|
454
|
-
files['skills/ktx/SKILL.md'] = strToU8(cliInstructionContent({ projectDir: input.projectDir, launcher: input.launcher }));
|
|
455
|
-
}
|
|
456
510
|
await mkdir(dirname(input.path), { recursive: true });
|
|
457
511
|
await writeFile(input.path, Buffer.from(zipSync(files)));
|
|
458
512
|
}
|
|
513
|
+
function claudeDesktopSkillNameForBundle(path) {
|
|
514
|
+
if (path.endsWith('/ktx-analytics.zip')) {
|
|
515
|
+
return 'ktx-analytics';
|
|
516
|
+
}
|
|
517
|
+
if (path.endsWith('/ktx.zip')) {
|
|
518
|
+
return 'ktx';
|
|
519
|
+
}
|
|
520
|
+
throw new Error(`Unsupported Claude Desktop skill bundle path: ${path}`);
|
|
521
|
+
}
|
|
459
522
|
async function writeClaudeDesktopLauncher(input) {
|
|
460
523
|
await mkdir(dirname(input.path), { recursive: true });
|
|
461
|
-
await writeFile(input.path,
|
|
524
|
+
await writeFile(input.path, claudeDesktopLauncherContent({ launcher: input.launcher }), 'utf-8');
|
|
462
525
|
await chmod(input.path, 0o755);
|
|
463
526
|
}
|
|
464
527
|
function ruleInstructionContent(input) {
|
|
@@ -550,16 +613,8 @@ const targetDisplayNames = {
|
|
|
550
613
|
opencode: 'OpenCode',
|
|
551
614
|
universal: 'Universal .agents',
|
|
552
615
|
};
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
'claude-desktop': 'Skill installed',
|
|
556
|
-
codex: 'Skill installed',
|
|
557
|
-
cursor: 'Rule installed',
|
|
558
|
-
opencode: 'Command installed',
|
|
559
|
-
universal: 'Skill installed',
|
|
560
|
-
};
|
|
561
|
-
function mcpEntryLabel(entry) {
|
|
562
|
-
return `MCP config installed — connects client agents to KTX MCP tools (${entry.jsonPath.join('.')})`;
|
|
616
|
+
export function targetDisplayName(target) {
|
|
617
|
+
return Object.hasOwn(targetDisplayNames, target) ? targetDisplayNames[target] : target;
|
|
563
618
|
}
|
|
564
619
|
function targetSupportsGlobalScope(target) {
|
|
565
620
|
return target === 'claude-code' || target === 'codex';
|
|
@@ -567,7 +622,61 @@ function targetSupportsGlobalScope(target) {
|
|
|
567
622
|
function effectiveInstallScope(target, requestedScope) {
|
|
568
623
|
return target === 'claude-desktop' ? 'global' : requestedScope;
|
|
569
624
|
}
|
|
570
|
-
|
|
625
|
+
function scopeDisplayName(scope) {
|
|
626
|
+
if (scope === 'project')
|
|
627
|
+
return 'Project scope';
|
|
628
|
+
if (scope === 'global')
|
|
629
|
+
return 'Global scope';
|
|
630
|
+
return 'Local scope';
|
|
631
|
+
}
|
|
632
|
+
function targetUsesHttpMcpDaemon(target) {
|
|
633
|
+
return target !== 'claude-desktop';
|
|
634
|
+
}
|
|
635
|
+
function manualMcpConfigInstruction(target, scope) {
|
|
636
|
+
if (target === 'codex') {
|
|
637
|
+
return 'Add the snippet shown below to ~/.codex/config.toml.';
|
|
638
|
+
}
|
|
639
|
+
if (target === 'opencode') {
|
|
640
|
+
return scope === 'global'
|
|
641
|
+
? 'Add the snippet shown below to ~/.config/opencode/opencode.json.'
|
|
642
|
+
: 'Add the snippet shown below to opencode.json.';
|
|
643
|
+
}
|
|
644
|
+
if (target === 'universal') {
|
|
645
|
+
return 'Use the printed endpoint with unsupported MCP clients.';
|
|
646
|
+
}
|
|
647
|
+
return 'Add the printed snippet manually.';
|
|
648
|
+
}
|
|
649
|
+
function guidanceInstallLine(target) {
|
|
650
|
+
if (target === 'codex')
|
|
651
|
+
return 'Codex guidance installed';
|
|
652
|
+
if (target === 'cursor')
|
|
653
|
+
return 'Cursor rules installed';
|
|
654
|
+
if (target === 'opencode')
|
|
655
|
+
return 'OpenCode commands installed';
|
|
656
|
+
if (target === 'universal')
|
|
657
|
+
return '.agents guidance installed';
|
|
658
|
+
return 'Agent guidance installed';
|
|
659
|
+
}
|
|
660
|
+
function hasEntryRole(entries, role) {
|
|
661
|
+
return entries.some((entry) => entry.kind === 'file' && entry.role === role);
|
|
662
|
+
}
|
|
663
|
+
function hasAdminCliEntries(entries) {
|
|
664
|
+
return entries.some((entry) => entry.kind === 'file' &&
|
|
665
|
+
(entry.role === 'skill' || entry.role === 'rule' || entry.role === undefined));
|
|
666
|
+
}
|
|
667
|
+
function formatInlinePath(path) {
|
|
668
|
+
const home = process.env.HOME;
|
|
669
|
+
if (!home)
|
|
670
|
+
return path;
|
|
671
|
+
const resolvedHome = resolve(home);
|
|
672
|
+
if (path === resolvedHome)
|
|
673
|
+
return '~';
|
|
674
|
+
if (path.startsWith(`${resolvedHome}/`)) {
|
|
675
|
+
return `~/${relative(resolvedHome, path)}`;
|
|
676
|
+
}
|
|
677
|
+
return path;
|
|
678
|
+
}
|
|
679
|
+
export function formatInstallSummaryLines(installs, entries, projectDir) {
|
|
571
680
|
const entriesByTarget = new Map();
|
|
572
681
|
for (const install of installs) {
|
|
573
682
|
const plannedFilePaths = new Set(plannedKtxAgentFiles({ projectDir, ...install })
|
|
@@ -580,45 +689,196 @@ export function formatInstallSummary(installs, entries, projectDir) {
|
|
|
580
689
|
const plannedMcpKeys = new Set(plannedMcpJsonEntries({ projectDir, ...install }).map(entryKey));
|
|
581
690
|
mcpEntriesByTarget.set(install.target, entries.filter((entry) => entry.kind === 'json-key' && plannedMcpKeys.has(entryKey(entry))));
|
|
582
691
|
}
|
|
583
|
-
|
|
584
|
-
skill: 'teaches admin agents which KTX CLI commands to run',
|
|
585
|
-
rule: 'tells admin agents when to use KTX CLI',
|
|
586
|
-
'analytics-skill': 'teaches your agent the KTX MCP analytics workflow',
|
|
587
|
-
'claude-plugin': 'bundles KTX skills for Claude Desktop (MCP server is registered in claude_desktop_config.json)',
|
|
588
|
-
launcher: 'runs the local KTX CLI with an available Node.js for Claude Desktop',
|
|
589
|
-
};
|
|
590
|
-
const lines = [];
|
|
591
|
-
for (const install of installs) {
|
|
692
|
+
return installs.map((install) => {
|
|
592
693
|
const targetEntries = entriesByTarget.get(install.target) ?? [];
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
694
|
+
const mcpEntry = mcpEntriesByTarget
|
|
695
|
+
.get(install.target)
|
|
696
|
+
?.find((entry) => entry.kind === 'json-key');
|
|
697
|
+
const lines = [];
|
|
698
|
+
if (mcpEntry) {
|
|
699
|
+
lines.push(formatInlinePath(mcpEntry.path));
|
|
700
|
+
}
|
|
701
|
+
else if (install.target !== 'claude-desktop') {
|
|
702
|
+
lines.push(manualMcpConfigInstruction(install.target, install.scope));
|
|
703
|
+
}
|
|
704
|
+
if (targetUsesHttpMcpDaemon(install.target)) {
|
|
705
|
+
lines.push('Requires MCP to be started.');
|
|
706
|
+
}
|
|
707
|
+
const hasAnalytics = hasEntryRole(targetEntries, 'analytics-skill');
|
|
708
|
+
const hasAdmin = hasAdminCliEntries(targetEntries);
|
|
709
|
+
const claudeDesktopSkillBundles = targetEntries.filter((entry) => entry.kind === 'file' && entry.role === 'claude-desktop-skill-bundle');
|
|
710
|
+
if (install.target === 'claude-code') {
|
|
711
|
+
if (hasAnalytics) {
|
|
712
|
+
lines.push('Analytics skill installed.');
|
|
713
|
+
}
|
|
714
|
+
if (hasAdmin) {
|
|
715
|
+
lines.push('Admin CLI skill installed.');
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
else if (install.target === 'claude-desktop') {
|
|
719
|
+
if (claudeDesktopSkillBundles.length > 0) {
|
|
720
|
+
lines.push('Skill bundles:');
|
|
721
|
+
for (const bundle of claudeDesktopSkillBundles) {
|
|
722
|
+
lines.push(` ${bundle.path}`);
|
|
611
723
|
}
|
|
612
724
|
}
|
|
613
725
|
}
|
|
614
|
-
|
|
615
|
-
.
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
lines.push(
|
|
619
|
-
|
|
726
|
+
else if (hasAnalytics || hasAdmin) {
|
|
727
|
+
lines.push(`${guidanceInstallLine(install.target)}.`);
|
|
728
|
+
}
|
|
729
|
+
if (hasEntryRole(targetEntries, 'launcher')) {
|
|
730
|
+
lines.push('Starts KTX over stdio from Claude Desktop.');
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
title: `${targetDisplayName(install.target)} · ${scopeDisplayName(install.scope)}`,
|
|
734
|
+
lines,
|
|
735
|
+
};
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
function claudeDesktopSkillBundlePathsForInstalls(projectDir, installs) {
|
|
739
|
+
return installs
|
|
740
|
+
.filter((install) => install.target === 'claude-desktop')
|
|
741
|
+
.flatMap((install) => plannedKtxAgentFiles({ projectDir, ...install }))
|
|
742
|
+
.filter((entry) => entry.kind === 'file' && entry.role === 'claude-desktop-skill-bundle')
|
|
743
|
+
.map((entry) => entry.path);
|
|
744
|
+
}
|
|
745
|
+
function humanList(values) {
|
|
746
|
+
if (values.length <= 2) {
|
|
747
|
+
return values.join(' and ');
|
|
748
|
+
}
|
|
749
|
+
return `${values.slice(0, -1).join(', ')}, and ${values[values.length - 1]}`;
|
|
750
|
+
}
|
|
751
|
+
function pushBlankLine(lines) {
|
|
752
|
+
if (lines.length > 0 && lines[lines.length - 1] !== '') {
|
|
753
|
+
lines.push('');
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function trimTrailingBlankLines(lines) {
|
|
757
|
+
while (lines[lines.length - 1] === '') {
|
|
758
|
+
lines.pop();
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function manualActionFromSnippet(snippet) {
|
|
762
|
+
const [label = '', ...body] = snippet.split('\n');
|
|
763
|
+
const codexPrefix = 'Add this Codex MCP snippet to ~/.codex/config.toml:';
|
|
764
|
+
if (label === codexPrefix) {
|
|
765
|
+
return {
|
|
766
|
+
title: 'Configure Codex',
|
|
767
|
+
instruction: 'Open ~/.codex/config.toml, then paste this block:',
|
|
768
|
+
marker: 'PASTE',
|
|
769
|
+
body,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
const opencodeMatch = label.match(/^Add this OpenCode MCP snippet to (.+):$/);
|
|
773
|
+
if (opencodeMatch) {
|
|
774
|
+
return {
|
|
775
|
+
title: 'Configure OpenCode',
|
|
776
|
+
instruction: `Open ${opencodeMatch[1]}, then paste this block:`,
|
|
777
|
+
marker: 'PASTE',
|
|
778
|
+
body,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
if (label === 'Use this universal MCP endpoint with unsupported MCP clients:') {
|
|
782
|
+
return {
|
|
783
|
+
title: 'Configure unsupported MCP clients',
|
|
784
|
+
instruction: 'Use this endpoint when setting up unsupported MCP clients:',
|
|
785
|
+
marker: 'USE',
|
|
786
|
+
body,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
title: 'Configure MCP client',
|
|
791
|
+
instruction: label,
|
|
792
|
+
marker: 'PASTE',
|
|
793
|
+
body,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function formatAgentNextActions(input) {
|
|
797
|
+
const projectDir = resolve(input.projectDir);
|
|
798
|
+
const lines = [];
|
|
799
|
+
let step = 1;
|
|
800
|
+
for (const snippet of input.snippets) {
|
|
801
|
+
const action = manualActionFromSnippet(snippet);
|
|
802
|
+
lines.push(`${step}. ${action.title}`);
|
|
803
|
+
lines.push(` ${action.instruction}`);
|
|
804
|
+
if (action.body.length > 0) {
|
|
805
|
+
lines.push('', ` ${action.marker}:`);
|
|
806
|
+
}
|
|
807
|
+
for (const line of action.body) {
|
|
808
|
+
lines.push(` ${line}`);
|
|
620
809
|
}
|
|
810
|
+
pushBlankLine(lines);
|
|
811
|
+
step += 1;
|
|
621
812
|
}
|
|
813
|
+
const httpTargets = input.installs
|
|
814
|
+
.filter((install) => targetUsesHttpMcpDaemon(install.target))
|
|
815
|
+
.map((install) => targetDisplayName(install.target));
|
|
816
|
+
if (input.notices.length > 0 && httpTargets.length > 0) {
|
|
817
|
+
lines.push(`${step}. Start MCP`);
|
|
818
|
+
lines.push(` Run this command before using ${humanList(httpTargets)}:`);
|
|
819
|
+
lines.push('');
|
|
820
|
+
lines.push(' RUN:');
|
|
821
|
+
lines.push(` ktx mcp start --project-dir ${projectDir}`);
|
|
822
|
+
lines.push('');
|
|
823
|
+
lines.push(' If you need to stop MCP later:');
|
|
824
|
+
lines.push(` ktx mcp stop --project-dir ${projectDir}`);
|
|
825
|
+
pushBlankLine(lines);
|
|
826
|
+
step += 1;
|
|
827
|
+
}
|
|
828
|
+
const claudeCodeInstall = input.installs.find((install) => install.target === 'claude-code');
|
|
829
|
+
if (claudeCodeInstall) {
|
|
830
|
+
lines.push(`${step}. Open Claude Code`);
|
|
831
|
+
if (claudeCodeInstall.scope === 'project') {
|
|
832
|
+
lines.push(' Open Claude Code from the KTX project directory:');
|
|
833
|
+
lines.push('');
|
|
834
|
+
lines.push(' RUN:');
|
|
835
|
+
lines.push(` cd ${shellScriptQuote(projectDir)}`);
|
|
836
|
+
lines.push(' claude');
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
lines.push(' RUN:');
|
|
840
|
+
lines.push(' claude');
|
|
841
|
+
}
|
|
842
|
+
pushBlankLine(lines);
|
|
843
|
+
step += 1;
|
|
844
|
+
}
|
|
845
|
+
const cursorInstall = input.installs.find((install) => install.target === 'cursor');
|
|
846
|
+
if (cursorInstall) {
|
|
847
|
+
lines.push(`${step}. Open Cursor`);
|
|
848
|
+
if (cursorInstall.scope === 'project') {
|
|
849
|
+
lines.push(' Open Cursor from the KTX project directory:');
|
|
850
|
+
lines.push('');
|
|
851
|
+
lines.push(' OPEN:');
|
|
852
|
+
lines.push(` ${projectDir}`);
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
lines.push(' Open Cursor.');
|
|
856
|
+
}
|
|
857
|
+
pushBlankLine(lines);
|
|
858
|
+
step += 1;
|
|
859
|
+
}
|
|
860
|
+
if (input.installs.some((install) => install.target === 'claude-desktop')) {
|
|
861
|
+
lines.push(`${step}. Restart Claude Desktop`);
|
|
862
|
+
lines.push(' Claude Desktop loads KTX MCP after restart.');
|
|
863
|
+
pushBlankLine(lines);
|
|
864
|
+
step += 1;
|
|
865
|
+
const skillBundlePaths = claudeDesktopSkillBundlePathsForInstalls(projectDir, input.installs);
|
|
866
|
+
if (skillBundlePaths.length > 0) {
|
|
867
|
+
lines.push(`${step}. Upload Claude Desktop skills`);
|
|
868
|
+
lines.push(' Open Claude Desktop: Customize > Skills > + > Create skill > Upload a skill.');
|
|
869
|
+
lines.push(skillBundlePaths.length === 1 ? ' Upload this file:' : ' Upload each file separately:');
|
|
870
|
+
for (const path of skillBundlePaths) {
|
|
871
|
+
lines.push(` ${path}`);
|
|
872
|
+
}
|
|
873
|
+
lines.push(' Toggle the uploaded KTX skills on.');
|
|
874
|
+
pushBlankLine(lines);
|
|
875
|
+
step += 1;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (lines.length === 0) {
|
|
879
|
+
lines.push('Open your configured agent and ask a data question.');
|
|
880
|
+
}
|
|
881
|
+
trimTrailingBlankLines(lines);
|
|
622
882
|
return lines.join('\n');
|
|
623
883
|
}
|
|
624
884
|
async function installTarget(input) {
|
|
@@ -631,11 +891,11 @@ async function installTarget(input) {
|
|
|
631
891
|
await writeClaudeDesktopLauncher({ path: entry.path, launcher });
|
|
632
892
|
continue;
|
|
633
893
|
}
|
|
634
|
-
if (entry.role === 'claude-
|
|
635
|
-
await
|
|
894
|
+
if (entry.role === 'claude-desktop-skill-bundle') {
|
|
895
|
+
await writeClaudeDesktopSkillBundle({
|
|
636
896
|
projectDir: input.projectDir,
|
|
637
897
|
path: entry.path,
|
|
638
|
-
|
|
898
|
+
skillName: claudeDesktopSkillNameForBundle(entry.path),
|
|
639
899
|
launcher,
|
|
640
900
|
});
|
|
641
901
|
continue;
|
|
@@ -664,13 +924,24 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
664
924
|
return { status: 'skipped', projectDir: args.projectDir };
|
|
665
925
|
}
|
|
666
926
|
const prompts = deps.prompts ?? createPromptAdapter();
|
|
927
|
+
if (args.inputMode === 'auto' && args.target === undefined) {
|
|
928
|
+
writeSetupInfo(io, 'Space to select, Enter to confirm, Esc to go back.');
|
|
929
|
+
}
|
|
667
930
|
const mode = args.inputMode === 'disabled'
|
|
668
931
|
? args.mode
|
|
669
932
|
: (await prompts.select({
|
|
670
|
-
message: '
|
|
933
|
+
message: 'What should agents be allowed to do with this KTX project?',
|
|
671
934
|
options: [
|
|
672
|
-
{
|
|
673
|
-
|
|
935
|
+
{
|
|
936
|
+
value: 'mcp',
|
|
937
|
+
label: 'Ask data questions with KTX MCP',
|
|
938
|
+
hint: 'Installs the MCP connection and analytics workflow skill. Best for normal use.',
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
value: 'mcp-cli',
|
|
942
|
+
label: 'Ask data questions + manage KTX with CLI commands',
|
|
943
|
+
hint: 'Adds an admin CLI skill so agents can run ktx status, sl, wiki, and setup commands.',
|
|
944
|
+
},
|
|
674
945
|
],
|
|
675
946
|
}));
|
|
676
947
|
if (mode === 'back')
|
|
@@ -680,7 +951,7 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
680
951
|
: args.inputMode === 'disabled'
|
|
681
952
|
? []
|
|
682
953
|
: (await prompts.multiselect({
|
|
683
|
-
message:
|
|
954
|
+
message: 'Which agent targets should KTX install?',
|
|
684
955
|
options: [
|
|
685
956
|
{ value: 'claude-code', label: 'Claude Code' },
|
|
686
957
|
{ value: 'claude-desktop', label: 'Claude Desktop' },
|
|
@@ -694,7 +965,9 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
694
965
|
if (targets.includes('back'))
|
|
695
966
|
return { status: 'back', projectDir: args.projectDir };
|
|
696
967
|
if (targets.length === 0) {
|
|
697
|
-
io.stderr.write(
|
|
968
|
+
io.stderr.write(args.inputMode === 'disabled'
|
|
969
|
+
? 'Run in a TTY, or pass --target <target>.\n'
|
|
970
|
+
: 'Missing agent target: pass --target or use interactive setup.\n');
|
|
698
971
|
return { status: 'missing-input', projectDir: args.projectDir };
|
|
699
972
|
}
|
|
700
973
|
const scopeTargets = targets.filter((target) => target !== 'claude-desktop');
|
|
@@ -703,10 +976,18 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
703
976
|
scopeTargets.length > 0 &&
|
|
704
977
|
scopeTargets.every(targetSupportsGlobalScope)
|
|
705
978
|
? (await prompts.select({
|
|
706
|
-
message:
|
|
979
|
+
message: `Where should KTX install supported agent config?\n\nKTX project: ${resolve(args.projectDir)}`,
|
|
707
980
|
options: [
|
|
708
|
-
{
|
|
709
|
-
|
|
981
|
+
{
|
|
982
|
+
value: 'project',
|
|
983
|
+
label: 'Project scope (KTX project directory)',
|
|
984
|
+
hint: 'Only agents opened from this KTX project path load the project-scoped config.',
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
value: 'global',
|
|
988
|
+
label: 'Global scope (user config)',
|
|
989
|
+
hint: 'Agents can load this KTX project from any working directory.',
|
|
990
|
+
},
|
|
710
991
|
],
|
|
711
992
|
}))
|
|
712
993
|
: args.scope;
|
|
@@ -716,7 +997,6 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
716
997
|
const entries = [];
|
|
717
998
|
const snippets = [];
|
|
718
999
|
const notices = new Set();
|
|
719
|
-
let claudeDesktopTutorial;
|
|
720
1000
|
try {
|
|
721
1001
|
for (const install of installs) {
|
|
722
1002
|
const targetEntries = await installTarget({ projectDir: args.projectDir, ...install });
|
|
@@ -731,41 +1011,26 @@ export async function runKtxSetupAgentsStep(args, io, deps = {}) {
|
|
|
731
1011
|
snippets.push(snippet);
|
|
732
1012
|
for (const notice of mcpResult.notices)
|
|
733
1013
|
notices.add(notice);
|
|
734
|
-
if (install.target === 'claude-desktop') {
|
|
735
|
-
const pluginEntry = targetEntries.find((entry) => entry.kind === 'file' && entry.role === 'claude-plugin');
|
|
736
|
-
const pluginPath = pluginEntry?.path ?? '';
|
|
737
|
-
const configPath = claudeDesktopConfigPath().path;
|
|
738
|
-
claudeDesktopTutorial = [
|
|
739
|
-
`${green('✓')} ${bold('KTX MCP server registered')}`,
|
|
740
|
-
` ${dim(configPath)}`,
|
|
741
|
-
'',
|
|
742
|
-
bold('1. Restart Claude Desktop'),
|
|
743
|
-
' Quit and reopen so it picks up the new MCP server.',
|
|
744
|
-
'',
|
|
745
|
-
bold('2. Install the KTX plugin'),
|
|
746
|
-
' Open Claude Desktop → Settings → Plugins and install from file:',
|
|
747
|
-
` 📦 ${dim(pluginPath)}`,
|
|
748
|
-
].join('\n');
|
|
749
|
-
}
|
|
750
1014
|
}
|
|
751
1015
|
await writeManifest(args.projectDir, mergeManifest(args.projectDir, await readKtxAgentInstallManifest(args.projectDir), installs, entries));
|
|
752
1016
|
await markAgentsComplete(args.projectDir);
|
|
753
1017
|
const setupUi = createKtxSetupUiAdapter();
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
setupUi.note(claudeDesktopTutorial, 'Finish Claude Desktop setup', io, {
|
|
757
|
-
format: (line) => line,
|
|
758
|
-
});
|
|
1018
|
+
for (const summary of formatInstallSummaryLines(installs, entries, args.projectDir)) {
|
|
1019
|
+
writeSetupStep(io, summary.lines.length > 0 ? `${summary.title}\n${summary.lines.join('\n')}` : summary.title);
|
|
759
1020
|
}
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1021
|
+
const nextActions = formatAgentNextActions({
|
|
1022
|
+
projectDir: args.projectDir,
|
|
1023
|
+
installs,
|
|
1024
|
+
notices: [...notices],
|
|
1025
|
+
snippets,
|
|
1026
|
+
});
|
|
1027
|
+
if (args.showNextActions !== false) {
|
|
1028
|
+
setupUi.note(nextActions, 'Required before using agents', io, {
|
|
1029
|
+
format: createAgentNextActionsLineFormatter(io.stdout),
|
|
1030
|
+
});
|
|
1031
|
+
writeSetupOutro(io, 'All set.');
|
|
767
1032
|
}
|
|
768
|
-
return { status: 'ready', projectDir: args.projectDir, installs };
|
|
1033
|
+
return { status: 'ready', projectDir: args.projectDir, installs, nextActions };
|
|
769
1034
|
}
|
|
770
1035
|
catch (error) {
|
|
771
1036
|
io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|