@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
|
@@ -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,88 @@ describe('setup agents', () => {
|
|
|
150
163
|
expect(await readKtxSetupState(tempDir)).toEqual({ completed_steps: ['agents'] });
|
|
151
164
|
expect(io.stderr()).toBe('');
|
|
152
165
|
});
|
|
166
|
+
it('installs a specified target in non-interactive mode without --yes', async () => {
|
|
167
|
+
const io = makeIo();
|
|
168
|
+
await expect(runKtxSetupAgentsStep({
|
|
169
|
+
projectDir: tempDir,
|
|
170
|
+
inputMode: 'disabled',
|
|
171
|
+
yes: false,
|
|
172
|
+
agents: true,
|
|
173
|
+
target: 'claude-code',
|
|
174
|
+
scope: 'project',
|
|
175
|
+
mode: 'mcp',
|
|
176
|
+
skipAgents: false,
|
|
177
|
+
}, io.io)).resolves.toMatchObject({
|
|
178
|
+
status: 'ready',
|
|
179
|
+
projectDir: tempDir,
|
|
180
|
+
installs: [{ target: 'claude-code', scope: 'project', mode: 'mcp' }],
|
|
181
|
+
});
|
|
182
|
+
await expect(stat(join(tempDir, '.claude/skills/ktx-analytics/SKILL.md'))).resolves.toBeDefined();
|
|
183
|
+
const mcpConfig = JSON.parse(await readFile(join(tempDir, '.mcp.json'), 'utf-8'));
|
|
184
|
+
expect(mcpConfig.mcpServers).toHaveProperty('ktx');
|
|
185
|
+
expect(io.stderr()).toBe('');
|
|
186
|
+
});
|
|
187
|
+
it('prints concrete target guidance when non-interactive agent setup has no target', async () => {
|
|
188
|
+
const io = makeIo();
|
|
189
|
+
await expect(runKtxSetupAgentsStep({
|
|
190
|
+
projectDir: tempDir,
|
|
191
|
+
inputMode: 'disabled',
|
|
192
|
+
yes: false,
|
|
193
|
+
agents: true,
|
|
194
|
+
scope: 'project',
|
|
195
|
+
mode: 'mcp',
|
|
196
|
+
skipAgents: false,
|
|
197
|
+
}, io.io)).resolves.toEqual({ status: 'missing-input', projectDir: tempDir });
|
|
198
|
+
expect(io.stderr()).toBe('Run in a TTY, or pass --target <target>.\n');
|
|
199
|
+
});
|
|
200
|
+
it('prints standalone agent next actions after successful installation', async () => {
|
|
201
|
+
const io = makeIo();
|
|
202
|
+
const result = await runKtxSetupAgentsStep({
|
|
203
|
+
projectDir: tempDir,
|
|
204
|
+
inputMode: 'disabled',
|
|
205
|
+
yes: true,
|
|
206
|
+
agents: true,
|
|
207
|
+
target: 'claude-code',
|
|
208
|
+
scope: 'project',
|
|
209
|
+
mode: 'mcp-cli',
|
|
210
|
+
skipAgents: false,
|
|
211
|
+
}, io.io);
|
|
212
|
+
expect(result).toMatchObject({
|
|
213
|
+
status: 'ready',
|
|
214
|
+
nextActions: expect.stringContaining('Run this command before using Claude Code:'),
|
|
215
|
+
});
|
|
216
|
+
expect(io.stdout()).toContain('Required before using agents');
|
|
217
|
+
expect(io.stdout()).toContain('Run this command before using Claude Code:');
|
|
218
|
+
expect(io.stdout()).toContain('RUN:');
|
|
219
|
+
expect(io.stdout()).toContain(`ktx mcp start --project-dir ${tempDir}`);
|
|
220
|
+
expect(io.stdout()).toContain('If you need to stop MCP later:');
|
|
221
|
+
expect(io.stdout()).toContain(`ktx mcp stop --project-dir ${tempDir}`);
|
|
222
|
+
expect(io.stdout()).toContain('All set.');
|
|
223
|
+
expect(io.stdout()).not.toContain('Finish agent setup');
|
|
224
|
+
expect(io.stdout()).not.toContain('Next actions');
|
|
225
|
+
});
|
|
226
|
+
it('can return agent next actions without printing them', async () => {
|
|
227
|
+
const io = makeIo();
|
|
228
|
+
const result = await runKtxSetupAgentsStep({
|
|
229
|
+
projectDir: tempDir,
|
|
230
|
+
inputMode: 'disabled',
|
|
231
|
+
yes: true,
|
|
232
|
+
agents: true,
|
|
233
|
+
target: 'claude-code',
|
|
234
|
+
scope: 'project',
|
|
235
|
+
mode: 'mcp-cli',
|
|
236
|
+
skipAgents: false,
|
|
237
|
+
showNextActions: false,
|
|
238
|
+
}, io.io);
|
|
239
|
+
expect(result).toMatchObject({
|
|
240
|
+
status: 'ready',
|
|
241
|
+
nextActions: expect.stringContaining(`ktx mcp start --project-dir ${tempDir}`),
|
|
242
|
+
});
|
|
243
|
+
expect(io.stdout()).toContain('Claude Code · Project scope');
|
|
244
|
+
expect(io.stdout()).not.toContain('Agent integration complete');
|
|
245
|
+
expect(io.stdout()).not.toContain('Required before using agents');
|
|
246
|
+
expect(io.stdout()).not.toContain('All set.');
|
|
247
|
+
});
|
|
153
248
|
it('installs the analytics skill from the runtime asset', async () => {
|
|
154
249
|
const io = makeIo();
|
|
155
250
|
await expect(runKtxSetupAgentsStep({
|
|
@@ -207,7 +302,6 @@ describe('setup agents', () => {
|
|
|
207
302
|
expect(await readKtxAgentInstallManifest(tempDir)).toMatchObject({
|
|
208
303
|
entries: expect.arrayContaining([{ kind: 'json-key', path: join(tempDir, '.mcp.json'), jsonPath: ['mcpServers', 'ktx'] }]),
|
|
209
304
|
});
|
|
210
|
-
expect(io.stdout()).toContain('Run `ktx mcp start` to enable the configured KTX MCP server.');
|
|
211
305
|
});
|
|
212
306
|
it('prompts for MCP-first client agent connection mode in interactive setup', async () => {
|
|
213
307
|
const io = makeIo();
|
|
@@ -229,10 +323,18 @@ describe('setup agents', () => {
|
|
|
229
323
|
installs: [{ target: 'claude-code', scope: 'project', mode: 'mcp' }],
|
|
230
324
|
});
|
|
231
325
|
expect(prompts.select).toHaveBeenCalledWith({
|
|
232
|
-
message: '
|
|
326
|
+
message: 'What should agents be allowed to do with this KTX project?',
|
|
233
327
|
options: [
|
|
234
|
-
{
|
|
235
|
-
|
|
328
|
+
{
|
|
329
|
+
value: 'mcp',
|
|
330
|
+
label: 'Ask data questions with KTX MCP',
|
|
331
|
+
hint: 'Installs the MCP connection and analytics workflow skill. Best for normal use.',
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
value: 'mcp-cli',
|
|
335
|
+
label: 'Ask data questions + manage KTX with CLI commands',
|
|
336
|
+
hint: 'Adds an admin CLI skill so agents can run ktx status, sl, wiki, and setup commands.',
|
|
337
|
+
},
|
|
236
338
|
],
|
|
237
339
|
});
|
|
238
340
|
expect(prompts.multiselect).toHaveBeenCalledWith(expect.objectContaining({
|
|
@@ -263,10 +365,18 @@ describe('setup agents', () => {
|
|
|
263
365
|
installs: [{ target: 'claude-code', scope: 'global', mode: 'mcp' }],
|
|
264
366
|
});
|
|
265
367
|
expect(prompts.select).toHaveBeenCalledWith({
|
|
266
|
-
message:
|
|
368
|
+
message: `Where should KTX install supported agent config?\n\nKTX project: ${tempDir}`,
|
|
267
369
|
options: [
|
|
268
|
-
{
|
|
269
|
-
|
|
370
|
+
{
|
|
371
|
+
value: 'project',
|
|
372
|
+
label: 'Project scope (KTX project directory)',
|
|
373
|
+
hint: 'Only agents opened from this KTX project path load the project-scoped config.',
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
value: 'global',
|
|
377
|
+
label: 'Global scope (user config)',
|
|
378
|
+
hint: 'Agents can load this KTX project from any working directory.',
|
|
379
|
+
},
|
|
270
380
|
],
|
|
271
381
|
});
|
|
272
382
|
}
|
|
@@ -275,7 +385,7 @@ describe('setup agents', () => {
|
|
|
275
385
|
await rm(home, { recursive: true, force: true });
|
|
276
386
|
}
|
|
277
387
|
});
|
|
278
|
-
it('registers Claude Desktop MCP
|
|
388
|
+
it('registers Claude Desktop MCP and ships an uploadable analytics skill zip', async () => {
|
|
279
389
|
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
280
390
|
const previousHome = process.env.HOME;
|
|
281
391
|
const envSnapshot = captureEnvKeys(process.env, ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY']);
|
|
@@ -298,9 +408,11 @@ describe('setup agents', () => {
|
|
|
298
408
|
status: 'ready',
|
|
299
409
|
installs: [{ target: 'claude-desktop', scope: 'global', mode: 'mcp' }],
|
|
300
410
|
});
|
|
301
|
-
const
|
|
411
|
+
const analyticsSkillPath = join(tempDir, '.ktx/agents/claude/ktx-analytics.zip');
|
|
412
|
+
const adminSkillPath = join(tempDir, '.ktx/agents/claude/ktx.zip');
|
|
302
413
|
const launcherPath = join(tempDir, '.ktx/agents/claude/ktx-plugin-runner.sh');
|
|
303
|
-
await expect(stat(
|
|
414
|
+
await expect(stat(analyticsSkillPath)).resolves.toBeDefined();
|
|
415
|
+
await expect(stat(adminSkillPath)).rejects.toThrow();
|
|
304
416
|
const launcherStat = await stat(launcherPath);
|
|
305
417
|
expect(launcherStat.mode & 0o111).not.toBe(0);
|
|
306
418
|
const launcher = await readFile(launcherPath, 'utf-8');
|
|
@@ -312,18 +424,21 @@ describe('setup agents', () => {
|
|
|
312
424
|
command: launcherPath,
|
|
313
425
|
args: ['--project-dir', tempDir, 'mcp', 'stdio'],
|
|
314
426
|
});
|
|
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');
|
|
427
|
+
expect(await readZipText(analyticsSkillPath, 'ktx-analytics/SKILL.md')).toContain('KTX Analytics Workflow');
|
|
428
|
+
await expect(readZipText(analyticsSkillPath, 'ktx/SKILL.md')).rejects.toThrow('Missing zip entry');
|
|
429
|
+
await expect(readZipText(analyticsSkillPath, '.claude-plugin/plugin.json')).rejects.toThrow('Missing zip entry');
|
|
430
|
+
await expect(readZipText(analyticsSkillPath, 'skills/ktx-analytics/SKILL.md')).rejects.toThrow('Missing zip entry');
|
|
431
|
+
expect(io.stdout()).toContain('Claude Desktop');
|
|
432
|
+
expect(io.stdout()).toContain(analyticsSkillPath);
|
|
433
|
+
expect(io.stdout()).not.toContain(adminSkillPath);
|
|
325
434
|
expect(io.stdout()).toContain('claude_desktop_config.json');
|
|
326
|
-
expect(io.stdout()).toContain('
|
|
435
|
+
expect(io.stdout()).toContain('Required before using agents');
|
|
436
|
+
expect(io.stdout()).toContain('1. Restart Claude Desktop');
|
|
437
|
+
expect(io.stdout()).toContain('Claude Desktop loads KTX MCP after restart.');
|
|
438
|
+
expect(io.stdout()).toContain('2. Upload Claude Desktop skills');
|
|
439
|
+
expect(io.stdout()).toContain('Customize > Skills > + > Create skill > Upload a skill');
|
|
440
|
+
expect(io.stdout()).toContain('Upload this file:');
|
|
441
|
+
expect(io.stdout()).toContain('Toggle the uploaded KTX skills on.');
|
|
327
442
|
expect(io.stdout()).not.toContain('Run `ktx mcp start`');
|
|
328
443
|
}
|
|
329
444
|
finally {
|
|
@@ -374,7 +489,7 @@ describe('setup agents', () => {
|
|
|
374
489
|
await rm(home, { recursive: true, force: true });
|
|
375
490
|
}
|
|
376
491
|
});
|
|
377
|
-
it('includes
|
|
492
|
+
it('includes an uploadable admin CLI skill zip for Claude Desktop when requested', async () => {
|
|
378
493
|
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
379
494
|
const previousHome = process.env.HOME;
|
|
380
495
|
process.env.HOME = home;
|
|
@@ -393,12 +508,18 @@ describe('setup agents', () => {
|
|
|
393
508
|
status: 'ready',
|
|
394
509
|
installs: [{ target: 'claude-desktop', scope: 'global', mode: 'mcp-cli' }],
|
|
395
510
|
});
|
|
396
|
-
const
|
|
397
|
-
const
|
|
511
|
+
const analyticsSkillPath = join(tempDir, '.ktx/agents/claude/ktx-analytics.zip');
|
|
512
|
+
const adminSkillPath = join(tempDir, '.ktx/agents/claude/ktx.zip');
|
|
513
|
+
expect(await readZipText(analyticsSkillPath, 'ktx-analytics/SKILL.md')).toContain('KTX Analytics Workflow');
|
|
514
|
+
await expect(readZipText(analyticsSkillPath, 'ktx/SKILL.md')).rejects.toThrow('Missing zip entry');
|
|
515
|
+
const adminSkill = await readZipText(adminSkillPath, 'ktx/SKILL.md');
|
|
398
516
|
expect(adminSkill).toContain(`--project-dir ${tempDir}`);
|
|
399
517
|
expect(adminSkill).toContain('status --json');
|
|
400
|
-
expect(
|
|
401
|
-
await expect(readZipText(
|
|
518
|
+
await expect(readZipText(adminSkillPath, '.mcp.json')).rejects.toThrow('Missing zip entry');
|
|
519
|
+
await expect(readZipText(adminSkillPath, 'ktx-analytics/SKILL.md')).rejects.toThrow('Missing zip entry');
|
|
520
|
+
expect(io.stdout()).toContain(analyticsSkillPath);
|
|
521
|
+
expect(io.stdout()).toContain(adminSkillPath);
|
|
522
|
+
expect(io.stdout()).toContain('Upload each file separately:');
|
|
402
523
|
}
|
|
403
524
|
finally {
|
|
404
525
|
process.env.HOME = previousHome;
|
|
@@ -455,6 +576,9 @@ describe('setup agents', () => {
|
|
|
455
576
|
}, codexIo.io);
|
|
456
577
|
expect(codexIo.stdout()).toContain('[mcp_servers.ktx]');
|
|
457
578
|
expect(codexIo.stdout()).toContain('url = "http://localhost:7878/mcp"');
|
|
579
|
+
expect(codexIo.stdout()).toContain('1. Configure Codex');
|
|
580
|
+
expect(codexIo.stdout()).toContain('Open ~/.codex/config.toml, then paste this block:');
|
|
581
|
+
expect(codexIo.stdout()).toContain('PASTE:');
|
|
458
582
|
const opencodeIo = makeIo();
|
|
459
583
|
await runKtxSetupAgentsStep({
|
|
460
584
|
projectDir: tempDir,
|
|
@@ -468,6 +592,8 @@ describe('setup agents', () => {
|
|
|
468
592
|
}, opencodeIo.io);
|
|
469
593
|
expect(opencodeIo.stdout()).toContain('"mcp"');
|
|
470
594
|
expect(opencodeIo.stdout()).toContain('"type": "remote"');
|
|
595
|
+
expect(opencodeIo.stdout()).toContain('1. Configure OpenCode');
|
|
596
|
+
expect(opencodeIo.stdout()).toContain('Open opencode.json, then paste this block:');
|
|
471
597
|
await expect(readFile(join(tempDir, 'opencode.json'), 'utf-8')).rejects.toThrow();
|
|
472
598
|
const universalIo = makeIo();
|
|
473
599
|
await runKtxSetupAgentsStep({
|
|
@@ -482,6 +608,8 @@ describe('setup agents', () => {
|
|
|
482
608
|
}, universalIo.io);
|
|
483
609
|
expect(universalIo.stdout()).toContain('Universal MCP endpoint:');
|
|
484
610
|
expect(universalIo.stdout()).toContain('http://localhost:7878/mcp');
|
|
611
|
+
expect(universalIo.stdout()).toContain('1. Configure unsupported MCP clients');
|
|
612
|
+
expect(universalIo.stdout()).toContain('Use this endpoint when setting up unsupported MCP clients:');
|
|
485
613
|
});
|
|
486
614
|
it('uses MCP daemon state for port and token metadata without rendering literal tokens', async () => {
|
|
487
615
|
await mkdir(join(tempDir, '.ktx'), { recursive: true });
|
|
@@ -513,7 +641,9 @@ describe('setup agents', () => {
|
|
|
513
641
|
expect(rendered).toContain('http://127.0.0.1:8787/mcp');
|
|
514
642
|
expect(rendered).toContain('Bearer ${KTX_MCP_TOKEN}');
|
|
515
643
|
expect(rendered).not.toContain('secret-token');
|
|
516
|
-
expect(io.stdout()).toContain('Run
|
|
644
|
+
expect(io.stdout()).toContain('Run this command before using Claude Code:');
|
|
645
|
+
expect(io.stdout()).toContain('RUN:');
|
|
646
|
+
expect(io.stdout()).toContain(`ktx mcp start --project-dir ${tempDir}`);
|
|
517
647
|
}
|
|
518
648
|
finally {
|
|
519
649
|
if (previousToken === undefined) {
|
|
@@ -567,7 +697,7 @@ describe('setup agents', () => {
|
|
|
567
697
|
await expect(stat(join(tempDir, '.claude/skills/ktx/keep.txt'))).resolves.toBeDefined();
|
|
568
698
|
await expect(readKtxAgentInstallManifest(tempDir)).resolves.toEqual(null);
|
|
569
699
|
});
|
|
570
|
-
it('removes generated Claude Desktop
|
|
700
|
+
it('removes generated Claude Desktop skill zips from the manifest', async () => {
|
|
571
701
|
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
572
702
|
const previousHome = process.env.HOME;
|
|
573
703
|
process.env.HOME = home;
|
|
@@ -583,15 +713,18 @@ describe('setup agents', () => {
|
|
|
583
713
|
mode: 'mcp-cli',
|
|
584
714
|
skipAgents: false,
|
|
585
715
|
}, io.io);
|
|
586
|
-
const
|
|
716
|
+
const analyticsSkillPath = join(tempDir, '.ktx/agents/claude/ktx-analytics.zip');
|
|
717
|
+
const adminSkillPath = join(tempDir, '.ktx/agents/claude/ktx.zip');
|
|
587
718
|
const launcherPath = join(tempDir, '.ktx/agents/claude/ktx-plugin-runner.sh');
|
|
588
719
|
const configPath = join(home, 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
589
|
-
await expect(stat(
|
|
720
|
+
await expect(stat(analyticsSkillPath)).resolves.toBeDefined();
|
|
721
|
+
await expect(stat(adminSkillPath)).resolves.toBeDefined();
|
|
590
722
|
await expect(stat(launcherPath)).resolves.toBeDefined();
|
|
591
723
|
const beforeConfig = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
592
724
|
expect(beforeConfig.mcpServers.ktx).toBeDefined();
|
|
593
725
|
await expect(removeKtxAgentInstall(tempDir, io.io)).resolves.toBe(0);
|
|
594
|
-
await expect(stat(
|
|
726
|
+
await expect(stat(analyticsSkillPath)).rejects.toThrow();
|
|
727
|
+
await expect(stat(adminSkillPath)).rejects.toThrow();
|
|
595
728
|
await expect(stat(launcherPath)).rejects.toThrow();
|
|
596
729
|
const afterConfig = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
597
730
|
expect(afterConfig.mcpServers.ktx).toBeUndefined();
|
|
@@ -619,7 +752,7 @@ describe('setup agents', () => {
|
|
|
619
752
|
skipAgents: false,
|
|
620
753
|
}, io.io, { prompts })).resolves.toEqual({ status: 'skipped', projectDir: tempDir });
|
|
621
754
|
});
|
|
622
|
-
it('
|
|
755
|
+
it('prints one navigation hint before interactive agent target prompts', async () => {
|
|
623
756
|
const io = makeIo();
|
|
624
757
|
const prompts = {
|
|
625
758
|
select: vi.fn(async () => 'mcp-cli'),
|
|
@@ -635,8 +768,10 @@ describe('setup agents', () => {
|
|
|
635
768
|
mode: 'mcp-cli',
|
|
636
769
|
skipAgents: false,
|
|
637
770
|
}, io.io, { prompts })).resolves.toEqual({ status: 'back', projectDir: tempDir });
|
|
771
|
+
expect(io.stdout()).toContain('Space to select, Enter to confirm, Esc to go back.');
|
|
772
|
+
expect(io.stdout().match(/Space to select/g)).toHaveLength(1);
|
|
638
773
|
expect(prompts.multiselect).toHaveBeenCalledWith(expect.objectContaining({
|
|
639
|
-
message: 'Which agent targets should KTX install
|
|
774
|
+
message: 'Which agent targets should KTX install?',
|
|
640
775
|
}));
|
|
641
776
|
});
|
|
642
777
|
it('prints per-agent install summary after successful installation', async () => {
|
|
@@ -652,45 +787,242 @@ describe('setup agents', () => {
|
|
|
652
787
|
skipAgents: false,
|
|
653
788
|
}, io.io);
|
|
654
789
|
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('
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
790
|
+
expect(output).toContain('Claude Code · Project scope');
|
|
791
|
+
expect(output).toContain(join(tempDir, '.mcp.json'));
|
|
792
|
+
expect(output).toContain('Requires MCP to be started.');
|
|
793
|
+
expect(output).toContain('Analytics skill installed.');
|
|
794
|
+
expect(output).toContain('Admin CLI skill installed.');
|
|
795
|
+
expect(output).not.toContain('Agent integration complete');
|
|
796
|
+
expect(output).not.toContain(`KTX project\n ${tempDir}`);
|
|
797
|
+
expect(output).not.toContain('Installed agents');
|
|
798
|
+
expect(output).not.toContain('.claude/skills/ktx-analytics/SKILL.md');
|
|
799
|
+
expect(output).not.toContain('.claude/skills/ktx/SKILL.md');
|
|
800
|
+
expect(output).not.toContain('.claude/rules/ktx.md');
|
|
801
|
+
});
|
|
802
|
+
it('formats summary with explicit project-scoped config paths', () => {
|
|
803
|
+
const summary = formatInstallSummaryLines([{ target: 'cursor', scope: 'project', mode: 'mcp-cli' }], [
|
|
666
804
|
{ kind: 'file', path: join(tempDir, '.cursor/rules/ktx-analytics.mdc'), role: 'analytics-skill' },
|
|
667
805
|
{ kind: 'file', path: join(tempDir, '.cursor/rules/ktx.mdc') },
|
|
806
|
+
{ kind: 'json-key', path: join(tempDir, '.cursor/mcp.json'), jsonPath: ['mcpServers', 'ktx'] },
|
|
668
807
|
], tempDir);
|
|
669
|
-
expect(summary).
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
808
|
+
expect(summary).toEqual([
|
|
809
|
+
{
|
|
810
|
+
title: 'Cursor · Project scope',
|
|
811
|
+
lines: [
|
|
812
|
+
join(tempDir, '.cursor/mcp.json'),
|
|
813
|
+
'Requires MCP to be started.',
|
|
814
|
+
'Cursor rules installed.',
|
|
815
|
+
],
|
|
816
|
+
},
|
|
817
|
+
]);
|
|
675
818
|
});
|
|
676
819
|
it('formats summary with multiple agent targets', () => {
|
|
677
|
-
const summary =
|
|
820
|
+
const summary = formatInstallSummaryLines([
|
|
678
821
|
{ target: 'claude-code', scope: 'project', mode: 'mcp-cli' },
|
|
679
822
|
{ target: 'codex', scope: 'project', mode: 'mcp-cli' },
|
|
680
823
|
], [
|
|
681
824
|
{ kind: 'file', path: join(tempDir, '.claude/skills/ktx-analytics/SKILL.md'), role: 'analytics-skill' },
|
|
682
825
|
{ kind: 'file', path: join(tempDir, '.claude/skills/ktx/SKILL.md'), role: 'skill' },
|
|
683
826
|
{ kind: 'file', path: join(tempDir, '.claude/rules/ktx.md'), role: 'rule' },
|
|
827
|
+
{ kind: 'json-key', path: join(tempDir, '.mcp.json'), jsonPath: ['mcpServers', 'ktx'] },
|
|
684
828
|
{ kind: 'file', path: join(tempDir, '.agents/skills/ktx-analytics/SKILL.md'), role: 'analytics-skill' },
|
|
685
829
|
{ kind: 'file', path: join(tempDir, '.agents/skills/ktx/SKILL.md'), role: 'skill' },
|
|
686
830
|
{ kind: 'file', path: join(tempDir, '.codex/instructions/ktx.md'), role: 'rule' },
|
|
687
831
|
], tempDir);
|
|
688
|
-
expect(summary).
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
832
|
+
expect(summary).toEqual([
|
|
833
|
+
{
|
|
834
|
+
title: 'Claude Code · Project scope',
|
|
835
|
+
lines: [
|
|
836
|
+
join(tempDir, '.mcp.json'),
|
|
837
|
+
'Requires MCP to be started.',
|
|
838
|
+
'Analytics skill installed.',
|
|
839
|
+
'Admin CLI skill installed.',
|
|
840
|
+
],
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
title: 'Codex · Project scope',
|
|
844
|
+
lines: [
|
|
845
|
+
'Add the snippet shown below to ~/.codex/config.toml.',
|
|
846
|
+
'Requires MCP to be started.',
|
|
847
|
+
'Codex guidance installed.',
|
|
848
|
+
],
|
|
849
|
+
},
|
|
850
|
+
]);
|
|
851
|
+
});
|
|
852
|
+
it('prints one target-aware next actions block for mixed agent targets', async () => {
|
|
853
|
+
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
854
|
+
const previousHome = process.env.HOME;
|
|
855
|
+
process.env.HOME = home;
|
|
856
|
+
try {
|
|
857
|
+
const io = makeIo();
|
|
858
|
+
const prompts = {
|
|
859
|
+
select: vi.fn(async ({ message }) => message.startsWith('Where should') ? 'project' : 'mcp'),
|
|
860
|
+
multiselect: vi.fn(async () => ['claude-code', 'claude-desktop']),
|
|
861
|
+
cancel: vi.fn(),
|
|
862
|
+
};
|
|
863
|
+
await expect(runKtxSetupAgentsStep({
|
|
864
|
+
projectDir: tempDir,
|
|
865
|
+
inputMode: 'auto',
|
|
866
|
+
yes: false,
|
|
867
|
+
agents: true,
|
|
868
|
+
scope: 'project',
|
|
869
|
+
mode: 'mcp',
|
|
870
|
+
skipAgents: false,
|
|
871
|
+
}, io.io, { prompts })).resolves.toMatchObject({
|
|
872
|
+
status: 'ready',
|
|
873
|
+
installs: [
|
|
874
|
+
{ target: 'claude-code', scope: 'project', mode: 'mcp' },
|
|
875
|
+
{ target: 'claude-desktop', scope: 'global', mode: 'mcp' },
|
|
876
|
+
],
|
|
877
|
+
});
|
|
878
|
+
const output = io.stdout();
|
|
879
|
+
expect(output).toContain('Required before using agents');
|
|
880
|
+
expect(output).not.toContain('Next actions');
|
|
881
|
+
expect(output).toContain('1. Start MCP');
|
|
882
|
+
expect(output).toContain('Run this command before using Claude Code:');
|
|
883
|
+
expect(output).toContain(`ktx mcp start --project-dir ${tempDir}`);
|
|
884
|
+
expect(output).toContain(`ktx mcp stop --project-dir ${tempDir}\n\n2. Open Claude Code`);
|
|
885
|
+
expect(output).toContain('Open Claude Code from the KTX project directory');
|
|
886
|
+
expect(output).toContain('RUN:');
|
|
887
|
+
expect(output).toContain(`cd '${tempDir}'`);
|
|
888
|
+
expect(output).toContain('3. Restart Claude Desktop');
|
|
889
|
+
expect(output).toContain('Claude Desktop loads KTX MCP after restart.');
|
|
890
|
+
expect(output).toContain('4. Upload Claude Desktop skills');
|
|
891
|
+
expect(output).toContain('Customize > Skills > + > Create skill > Upload a skill');
|
|
892
|
+
expect(output).toContain(join(tempDir, '.ktx/agents/claude/ktx-analytics.zip'));
|
|
893
|
+
expect(output).not.toContain(join(tempDir, '.ktx/agents/claude/ktx.zip'));
|
|
894
|
+
expect(output).toContain('Upload this file:');
|
|
895
|
+
expect(output).toContain('All set.');
|
|
896
|
+
expect(output).not.toContain('Finish Claude Desktop setup');
|
|
897
|
+
expect(output).not.toContain('Run `ktx mcp start` to enable the configured KTX MCP server.');
|
|
898
|
+
}
|
|
899
|
+
finally {
|
|
900
|
+
process.env.HOME = previousHome;
|
|
901
|
+
await rm(home, { recursive: true, force: true });
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
it('does not tell global Claude Code installs to open from the project directory', async () => {
|
|
905
|
+
const home = await mkdtemp(join(tmpdir(), 'ktx-setup-agents-home-'));
|
|
906
|
+
const previousHome = process.env.HOME;
|
|
907
|
+
process.env.HOME = home;
|
|
908
|
+
try {
|
|
909
|
+
const io = makeIo();
|
|
910
|
+
await expect(runKtxSetupAgentsStep({
|
|
911
|
+
projectDir: tempDir,
|
|
912
|
+
inputMode: 'disabled',
|
|
913
|
+
yes: true,
|
|
914
|
+
agents: true,
|
|
915
|
+
target: 'claude-code',
|
|
916
|
+
scope: 'global',
|
|
917
|
+
mode: 'mcp',
|
|
918
|
+
skipAgents: false,
|
|
919
|
+
}, io.io)).resolves.toMatchObject({
|
|
920
|
+
status: 'ready',
|
|
921
|
+
installs: [{ target: 'claude-code', scope: 'global', mode: 'mcp' }],
|
|
922
|
+
});
|
|
923
|
+
const output = io.stdout();
|
|
924
|
+
expect(output).toContain('2. Open Claude Code');
|
|
925
|
+
expect(output).toContain('RUN:');
|
|
926
|
+
expect(output).toContain('claude');
|
|
927
|
+
expect(output).not.toContain('Open Claude Code from the KTX project directory');
|
|
928
|
+
expect(output).not.toContain(`cd '${tempDir}'`);
|
|
929
|
+
}
|
|
930
|
+
finally {
|
|
931
|
+
process.env.HOME = previousHome;
|
|
932
|
+
await rm(home, { recursive: true, force: true });
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
it('explains next actions for Codex, Cursor, OpenCode, and universal MCP targets', async () => {
|
|
936
|
+
const io = makeIo();
|
|
937
|
+
const prompts = {
|
|
938
|
+
select: vi.fn(async () => 'mcp-cli'),
|
|
939
|
+
multiselect: vi.fn(async () => ['codex', 'cursor', 'opencode', 'universal']),
|
|
940
|
+
cancel: vi.fn(),
|
|
941
|
+
};
|
|
942
|
+
await expect(runKtxSetupAgentsStep({
|
|
943
|
+
projectDir: tempDir,
|
|
944
|
+
inputMode: 'auto',
|
|
945
|
+
yes: false,
|
|
946
|
+
agents: true,
|
|
947
|
+
scope: 'project',
|
|
948
|
+
mode: 'mcp-cli',
|
|
949
|
+
skipAgents: false,
|
|
950
|
+
}, io.io, { prompts })).resolves.toMatchObject({
|
|
951
|
+
status: 'ready',
|
|
952
|
+
installs: [
|
|
953
|
+
{ target: 'codex', scope: 'project', mode: 'mcp-cli' },
|
|
954
|
+
{ target: 'cursor', scope: 'project', mode: 'mcp-cli' },
|
|
955
|
+
{ target: 'opencode', scope: 'project', mode: 'mcp-cli' },
|
|
956
|
+
{ target: 'universal', scope: 'project', mode: 'mcp-cli' },
|
|
957
|
+
],
|
|
958
|
+
});
|
|
959
|
+
const output = io.stdout();
|
|
960
|
+
expect(output).toContain('Required before using agents');
|
|
961
|
+
expect(output).toContain('1. Configure Codex');
|
|
962
|
+
expect(output).toContain('2. Configure OpenCode');
|
|
963
|
+
expect(output).toContain('3. Configure unsupported MCP clients');
|
|
964
|
+
expect(output).toContain('4. Start MCP');
|
|
965
|
+
expect(output).toContain('Run this command before using Codex, Cursor, OpenCode, and Universal .agents:');
|
|
966
|
+
expect(output).toContain('Open Cursor from the KTX project directory');
|
|
967
|
+
expect(output).toContain('Open ~/.codex/config.toml, then paste this block:\n\n PASTE:\n [mcp_servers.ktx]');
|
|
968
|
+
expect(output).toContain('Open opencode.json, then paste this block:');
|
|
969
|
+
expect(output).toContain('Use this endpoint when setting up unsupported MCP clients:');
|
|
970
|
+
expect(output).toContain('Codex guidance installed');
|
|
971
|
+
expect(output).toContain('Cursor rules installed');
|
|
972
|
+
expect(output).toContain('OpenCode commands installed');
|
|
973
|
+
expect(output).toContain('.agents guidance installed');
|
|
974
|
+
});
|
|
975
|
+
describe('createAgentNextActionsLineFormatter', () => {
|
|
976
|
+
function makeColorStdout() {
|
|
977
|
+
return { write: () => true, hasColors: () => true };
|
|
978
|
+
}
|
|
979
|
+
function makePlainStdout() {
|
|
980
|
+
return { write: () => true, hasColors: () => false };
|
|
981
|
+
}
|
|
982
|
+
const ESC = String.fromCharCode(27);
|
|
983
|
+
it('returns the line untouched when the stream cannot render colors', () => {
|
|
984
|
+
const format = createAgentNextActionsLineFormatter(makePlainStdout());
|
|
985
|
+
expect(format('2. Upload Claude Desktop skills')).toBe('2. Upload Claude Desktop skills');
|
|
986
|
+
expect(format(' /tmp/ktx/.ktx/agents/claude/ktx.zip')).toBe(' /tmp/ktx/.ktx/agents/claude/ktx.zip');
|
|
987
|
+
});
|
|
988
|
+
it('styles step headings and aligns sub-prose under the title', () => {
|
|
989
|
+
const format = createAgentNextActionsLineFormatter(makeColorStdout());
|
|
990
|
+
const heading = format('2. Upload Claude Desktop skills');
|
|
991
|
+
expect(heading).toContain(ESC);
|
|
992
|
+
expect(heading).toContain('2');
|
|
993
|
+
expect(heading).toContain('Upload Claude Desktop skills');
|
|
994
|
+
expect(heading).not.toMatch(/^2\. /);
|
|
995
|
+
const sub = format(' Toggle the uploaded KTX skills on.');
|
|
996
|
+
expect(sub).toMatch(/^ {3}/);
|
|
997
|
+
expect(sub).toContain('Toggle the uploaded KTX skills on.');
|
|
998
|
+
});
|
|
999
|
+
it('renders skill bundle .zip paths as bullets and shortens HOME to ~', () => {
|
|
1000
|
+
const previousHome = process.env.HOME;
|
|
1001
|
+
process.env.HOME = '/tmp/test-home';
|
|
1002
|
+
try {
|
|
1003
|
+
const format = createAgentNextActionsLineFormatter(makeColorStdout());
|
|
1004
|
+
const line = format(' /tmp/test-home/.ktx/agents/claude/ktx-analytics.zip');
|
|
1005
|
+
expect(line).toContain('•');
|
|
1006
|
+
expect(line).toContain('~/.ktx/agents/claude/ktx-analytics.zip');
|
|
1007
|
+
expect(line).not.toContain('/tmp/test-home/');
|
|
1008
|
+
}
|
|
1009
|
+
finally {
|
|
1010
|
+
if (previousHome === undefined)
|
|
1011
|
+
delete process.env.HOME;
|
|
1012
|
+
else
|
|
1013
|
+
process.env.HOME = previousHome;
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
it('replaces breadcrumb separators with a typographic chevron', () => {
|
|
1017
|
+
const format = createAgentNextActionsLineFormatter(makeColorStdout());
|
|
1018
|
+
const line = format(' Open Claude Desktop: Customize > Skills > + > Create skill > Upload a skill.');
|
|
1019
|
+
expect(line).toContain('›');
|
|
1020
|
+
expect(line).not.toContain(' > ');
|
|
1021
|
+
});
|
|
1022
|
+
it('leaves already-styled lines untouched to avoid double-wrapping', () => {
|
|
1023
|
+
const format = createAgentNextActionsLineFormatter(makeColorStdout());
|
|
1024
|
+
const preStyled = `${ESC}[1m2. Already styled${ESC}[22m`;
|
|
1025
|
+
expect(format(preStyled)).toBe(preStyled);
|
|
1026
|
+
});
|
|
695
1027
|
});
|
|
696
1028
|
});
|
|
@@ -49,6 +49,7 @@ export interface KtxSetupEmbeddingsDeps {
|
|
|
49
49
|
healthCheck?: (config: KtxEmbeddingConfig) => Promise<KtxEmbeddingHealthCheckResult>;
|
|
50
50
|
ensureLocalEmbeddings?: (options: {
|
|
51
51
|
cliVersion: string;
|
|
52
|
+
projectDir: string;
|
|
52
53
|
installPolicy: KtxManagedPythonInstallPolicy;
|
|
53
54
|
io: KtxCliIo;
|
|
54
55
|
}) => Promise<ManagedLocalEmbeddingsDaemon>;
|