mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-13 11:22:11 +02:00
feat(cli): let ktx setup --agents choose an install directory (#298)
Split the fused directory concept into projectDir (what the agent config references) and installRoot (where project-scoped files are written), so users can install .claude/, .mcp.json, skills, and rules where they open their agent instead of only in the ktx project directory. - Add --install-dir <path> (resolved against cwd, created if missing, mutually exclusive with --global/--local, rejected for claude-desktop). - Add an interactive directory menu: ktx project dir / Current directory (hidden when it equals the project dir) / Custom directory… / Global scope (shown only when every target supports it). - Expand a leading ~ in typed/quoted paths so the ~/… menu hints round-trip. - Record installRoot in the install manifest and merge key; thread it through file planning, MCP config paths, summaries, and next actions. - Refresh uv.lock to 0.12.0 for the editable ktx-sl and ktx-daemon packages.
This commit is contained in:
parent
ed44f46f2a
commit
4e61020089
10 changed files with 650 additions and 70 deletions
|
|
@ -526,6 +526,7 @@ describe('runKtxCli', () => {
|
|||
expect(stdout).toContain('--target <target>');
|
||||
expect(stdout).toContain('--global');
|
||||
expect(stdout).toContain('--local');
|
||||
expect(stdout).toContain('--install-dir <path>');
|
||||
expect(stdout).toContain('--yes');
|
||||
expect(stdout).toContain('--no-input');
|
||||
expect(stdout).toContain('Global Options:');
|
||||
|
|
@ -1486,6 +1487,94 @@ describe('runKtxCli', () => {
|
|||
expect(setup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('dispatches --install-dir as the agent install root', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const setupIo = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxCli(
|
||||
['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-code', '--install-dir', '.', '--no-input'],
|
||||
setupIo.io,
|
||||
{ setup },
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(setup).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ agents: true, target: 'claude-code', agentScope: 'project', installRoot: '.' }),
|
||||
setupIo.io,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects --install-dir together with --global', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const setupIo = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxCli(
|
||||
['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-code', '--install-dir', '.', '--global', '--no-input'],
|
||||
setupIo.io,
|
||||
{ setup },
|
||||
),
|
||||
).resolves.toBe(1);
|
||||
|
||||
expect(setupIo.stderr()).toContain('Choose either --install-dir or a scope flag (--global / --local), not both.');
|
||||
expect(setup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects --install-dir together with --local', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const setupIo = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxCli(
|
||||
['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-code', '--install-dir', '.', '--local', '--no-input'],
|
||||
setupIo.io,
|
||||
{ setup },
|
||||
),
|
||||
).resolves.toBe(1);
|
||||
|
||||
expect(setupIo.stderr()).toContain('Choose either --install-dir or a scope flag (--global / --local), not both.');
|
||||
expect(setup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('treats an empty --install-dir as not provided', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const setupIo = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxCli(
|
||||
['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-code', '--install-dir', '', '--global', '--no-input'],
|
||||
setupIo.io,
|
||||
{ setup },
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(setup).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ agents: true, target: 'claude-code', agentScope: 'global' }),
|
||||
setupIo.io,
|
||||
);
|
||||
expect(setup).toHaveBeenCalledWith(
|
||||
expect.not.objectContaining({ installRoot: expect.anything() }),
|
||||
setupIo.io,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects --install-dir with --target claude-desktop', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const setupIo = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxCli(
|
||||
['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-desktop', '--install-dir', '.', '--no-input'],
|
||||
setupIo.io,
|
||||
{ setup },
|
||||
),
|
||||
).resolves.toBe(1);
|
||||
|
||||
expect(setupIo.stderr()).toContain('--install-dir does not apply to --target claude-desktop, which is always global.');
|
||||
expect(setup).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects source-path with source-git-url', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const testIo = makeIo();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue