feat(cli): clean up command surface

This commit is contained in:
Andrey Avtomonov 2026-05-12 23:51:46 +02:00
parent 60457e9407
commit e15a4ebaec
61 changed files with 406 additions and 2076 deletions

View file

@ -28,7 +28,7 @@ describe('Conductor workspace scripts', () => {
assert.match(setupScript, /pnpm install --frozen-lockfile --prefer-offline/);
assert.match(setupScript, /pnpm run native:rebuild/);
assert.match(setupScript, /pnpm run build/);
assert.match(setupScript, /packages\/cli\/dist\/bin\.js dev doctor setup --no-input/);
assert.match(setupScript, /packages\/cli\/dist\/bin\.js status --no-input/);
assert.doesNotMatch(setupScript, /scripts\/conductor\//);
});

View file

@ -137,6 +137,6 @@ echo "Building KTX packages..."
pnpm run build
echo "Running KTX setup doctor..."
node packages/cli/dist/bin.js dev doctor setup --no-input
node packages/cli/dist/bin.js status --no-input
echo "=== Setup complete ==="

View file

@ -71,7 +71,7 @@ describe('standalone example docs', () => {
assert.match(examples, /unified Historic SQL artifacts/);
assert.match(readme, /--enable-historic-sql/);
assert.match(readme, /--historic-sql-min-executions 2/);
assert.match(readme, /ktx dev doctor --project-dir/);
assert.match(readme, /ktx status --project-dir/);
assert.match(readme, /Postgres Historic SQL/);
assert.match(readme, /manifest\.json/);
assert.match(readme, /tables\/\*\.json/);
@ -152,33 +152,20 @@ describe('standalone example docs', () => {
assert.match(contributing, /ktx-daemon\/\s+# Daemon/);
});
it('documents every standalone MCP tool that the CLI server exposes', async () => {
it('documents agent-facing CLI commands', async () => {
const servingAgents = await readText('docs-site/content/docs/guides/serving-agents.mdx');
for (const tool of [
'connection_list',
'connection_test',
'knowledge_search',
'knowledge_read',
'knowledge_write',
'sl_list_sources',
'sl_read_source',
'sl_write_source',
'sl_validate',
'sl_query',
'scan_trigger',
'scan_status',
'scan_report',
'scan_list_artifacts',
'scan_read_artifact',
'ingest_trigger',
'ingest_status',
'ingest_report',
'ingest_replay',
'memory_capture',
'memory_capture_status',
for (const command of [
'ktx agent tools --json',
'ktx agent context --json',
'ktx agent sl list --json',
'ktx agent sl read orders --json',
'ktx agent sl query --json',
'ktx agent wiki search "revenue recognition" --json',
'ktx agent wiki read order-status-definitions --json',
'ktx agent sql execute --json',
]) {
assert.match(servingAgents, new RegExp(`\`${tool}\``));
assert.match(servingAgents, new RegExp(command.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
}
});
@ -199,14 +186,14 @@ describe('standalone example docs', () => {
assert.match(rootReadme, publicPackagePattern('npm install -g {package}'));
assert.match(quickstart, publicPackagePattern('npm install -g {package}'));
assert.match(quickstart, /ktx runtime install --feature local-embeddings --yes/);
assert.match(quickstart, /ktx runtime start --feature local-embeddings/);
assert.match(quickstart, /Install `uv`, run `ktx runtime doctor`/);
assert.match(quickstart, /ktx dev runtime install --feature local-embeddings --yes/);
assert.match(quickstart, /ktx dev runtime start --feature local-embeddings/);
assert.match(quickstart, /Install `uv`, run `ktx dev runtime doctor`/);
assert.match(packageArtifacts, /requires `uv` on `PATH`/);
assert.match(packageArtifacts, /ktx runtime status/);
assert.match(packageArtifacts, /ktx runtime doctor/);
assert.match(packageArtifacts, /ktx runtime prune --dry-run/);
assert.match(packageArtifacts, /ktx runtime prune --yes/);
assert.match(packageArtifacts, /ktx dev runtime status/);
assert.match(packageArtifacts, /ktx dev runtime doctor/);
assert.match(packageArtifacts, /ktx dev runtime prune --dry-run/);
assert.match(packageArtifacts, /ktx dev runtime prune --yes/);
assert.match(
packageArtifacts,
new RegExp(
@ -215,7 +202,7 @@ describe('standalone example docs', () => {
)}\` runtime wheel`,
),
);
assert.match(rootReadme, /ktx serve --mcp stdio/);
assert.doesNotMatch(rootReadme, /ktx serve --mcp stdio/);
assert.doesNotMatch(rootReadme, /uv run ktx-daemon serve-http/);
assert.doesNotMatch(rootReadme, /--semantic-compute-url http:\/\/127\.0\.0\.1:8765/);
});
@ -237,10 +224,10 @@ describe('standalone example docs', () => {
assert.doesNotMatch(readme, /standalone Python distributions/);
assert.doesNotMatch(readme, /installs the Python artifacts directly/);
assert.match(readme, /requires `uv` on `PATH`/);
assert.match(readme, /ktx runtime status/);
assert.match(readme, /ktx runtime doctor/);
assert.match(readme, /ktx runtime prune --dry-run/);
assert.match(readme, /ktx runtime prune --yes/);
assert.match(readme, /ktx dev runtime status/);
assert.match(readme, /ktx dev runtime doctor/);
assert.match(readme, /ktx dev runtime prune --dry-run/);
assert.match(readme, /ktx dev runtime prune --yes/);
assert.doesNotMatch(readme, /@ktx\/context/);
assert.doesNotMatch(readme, /@ktx\/cli/);
assert.doesNotMatch(readme, /python -m ktx_daemon semantic-validate/);

View file

@ -250,17 +250,17 @@ function parseDaemonBaseUrl(stdout) {
}
async function startDaemon(cleanInstallDir) {
const result = await run('pnpm', ['exec', 'ktx', 'runtime', 'start'], {
const result = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'start'], {
cwd: cleanInstallDir,
env: managedRuntimeEnv(cleanInstallDir),
timeout: 120_000,
});
requireSuccess('ktx runtime start', result);
requireSuccess('ktx dev runtime start', result);
return parseDaemonBaseUrl(result.stdout);
}
async function stopDaemon(cleanInstallDir) {
await run('pnpm', ['exec', 'ktx', 'runtime', 'stop'], {
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'stop'], {
cwd: cleanInstallDir,
env: managedRuntimeEnv(cleanInstallDir),
timeout: 30_000,
@ -283,7 +283,7 @@ async function prepareCleanInstall(layout, cleanInstallDir) {
await run('pnpm', ['install'], { cwd: cleanInstallDir, timeout: 120_000 }).then((result) =>
requireSuccess('pnpm install clean artifact project', result),
);
await run('pnpm', ['exec', 'ktx', 'runtime', 'install', '--yes'], {
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'install', '--yes'], {
cwd: cleanInstallDir,
env: managedRuntimeEnv(cleanInstallDir),
timeout: 120_000,

View file

@ -73,27 +73,27 @@ export function localEmbeddingsSmokeCommands(input) {
timeoutMs: 60_000,
},
{
label: 'ktx runtime status missing',
label: 'ktx dev runtime status missing',
command: 'pnpm',
args: ['exec', 'ktx', 'runtime', 'status', '--json'],
args: ['exec', 'ktx', 'dev', 'runtime', 'status', '--json'],
timeoutMs: 60_000,
},
{
label: 'ktx runtime install local embeddings',
label: 'ktx dev runtime install local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'runtime', 'install', '--feature', 'local-embeddings', '--yes'],
args: ['exec', 'ktx', 'dev', 'runtime', 'install', '--feature', 'local-embeddings', '--yes'],
timeoutMs: 1_200_000,
},
{
label: 'ktx runtime status local embeddings ready',
label: 'ktx dev runtime status local embeddings ready',
command: 'pnpm',
args: ['exec', 'ktx', 'runtime', 'status', '--json'],
args: ['exec', 'ktx', 'dev', 'runtime', 'status', '--json'],
timeoutMs: 60_000,
},
{
label: 'ktx runtime start local embeddings',
label: 'ktx dev runtime start local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'runtime', 'start', '--feature', 'local-embeddings'],
args: ['exec', 'ktx', 'dev', 'runtime', 'start', '--feature', 'local-embeddings'],
timeoutMs: 300_000,
},
{
@ -118,9 +118,9 @@ export function localEmbeddingsSmokeCommands(input) {
timeoutMs: 900_000,
},
{
label: 'ktx runtime stop local embeddings',
label: 'ktx dev runtime stop local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'runtime', 'stop'],
args: ['exec', 'ktx', 'dev', 'runtime', 'stop'],
timeoutMs: 60_000,
},
];
@ -361,7 +361,7 @@ export async function runLocalEmbeddingsRuntimeSmoke(options = {}) {
process.stdout.write('KTX local embeddings runtime smoke verified\n');
} finally {
if (daemonStarted) {
await run('pnpm', ['exec', 'ktx', 'runtime', 'stop'], {
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'stop'], {
cwd: installDir,
env: smokeEnv,
timeoutMs: 60_000,

View file

@ -89,23 +89,23 @@ describe('localEmbeddingsSmokeCommands', () => {
assert.deepEqual(commands.map((command) => command.label), [
'ktx public package version',
'ktx runtime status missing',
'ktx runtime install local embeddings',
'ktx runtime status local embeddings ready',
'ktx runtime start local embeddings',
'ktx dev runtime status missing',
'ktx dev runtime install local embeddings',
'ktx dev runtime status local embeddings ready',
'ktx dev runtime start local embeddings',
'ktx setup local embeddings',
'ktx runtime stop local embeddings',
'ktx dev runtime stop local embeddings',
]);
assert.deepEqual(commands[2], {
label: 'ktx runtime install local embeddings',
label: 'ktx dev runtime install local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'runtime', 'install', '--feature', 'local-embeddings', '--yes'],
args: ['exec', 'ktx', 'dev', 'runtime', 'install', '--feature', 'local-embeddings', '--yes'],
timeoutMs: 1_200_000,
});
assert.deepEqual(commands[4], {
label: 'ktx runtime start local embeddings',
label: 'ktx dev runtime start local embeddings',
command: 'pnpm',
args: ['exec', 'ktx', 'runtime', 'start', '--feature', 'local-embeddings'],
args: ['exec', 'ktx', 'dev', 'runtime', 'start', '--feature', 'local-embeddings'],
timeoutMs: 300_000,
});
assert.deepEqual(commands[5].args, [

View file

@ -149,7 +149,7 @@ export async function findPythonArtifacts(pythonDir) {
files,
RUNTIME_WHEEL_DISTRIBUTION_NAME,
'.whl',
'kaelio-ktx runtime wheel',
'kaelio-ktx dev runtime wheel',
pythonDir,
RUNTIME_WHEEL_PACKAGE_VERSION,
),
@ -594,8 +594,8 @@ try {
requireOutput('ktx public package version', version, /@kaelio\\/ktx 0\\.1\\.0/);
const runtimeStatusBefore = parseJsonResult(
'ktx runtime status missing',
await run('pnpm', ['exec', 'ktx', 'runtime', 'status', '--json']),
'ktx dev runtime status missing',
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'status', '--json']),
);
assert.equal(runtimeStatusBefore.kind, 'missing');
assert.equal(runtimeStatusBefore.layout.runtimeRoot, process.env.KTX_RUNTIME_ROOT);
@ -835,8 +835,8 @@ try {
requireOutput('ktx sl query first managed runtime install', slQuery, /orders/);
const runtimeStatusAfter = parseJsonResult(
'ktx runtime status ready',
await run('pnpm', ['exec', 'ktx', 'runtime', 'status', '--json']),
'ktx dev runtime status ready',
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'status', '--json']),
);
assert.equal(runtimeStatusAfter.kind, 'ready');
assert.deepEqual(runtimeStatusAfter.manifest.features, ['core']);
@ -864,51 +864,51 @@ try {
requireOutput('ktx sl query sqlite execute', sqliteSlQuery, /"rows": \\[\\s*\\[\\s*3\\s*\\]\\s*\\]/);
process.stdout.write('ktx sl query sqlite execute verified\\n');
const runtimeDoctor = await run('pnpm', ['exec', 'ktx', 'runtime', 'doctor']);
requireSuccess('ktx runtime doctor', runtimeDoctor);
requireOutput('ktx runtime doctor', runtimeDoctor, /PASS uv/);
requireOutput('ktx runtime doctor', runtimeDoctor, /PASS Bundled Python wheel/);
requireOutput('ktx runtime doctor', runtimeDoctor, /PASS Managed Python runtime/);
process.stdout.write('ktx runtime doctor verified\\n');
const runtimeDoctor = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'doctor']);
requireSuccess('ktx dev runtime doctor', runtimeDoctor);
requireOutput('ktx dev runtime doctor', runtimeDoctor, /PASS uv/);
requireOutput('ktx dev runtime doctor', runtimeDoctor, /PASS Bundled Python wheel/);
requireOutput('ktx dev runtime doctor', runtimeDoctor, /PASS Managed Python runtime/);
process.stdout.write('ktx dev runtime doctor verified\\n');
const runtimeStart = await run('pnpm', ['exec', 'ktx', 'runtime', 'start']);
requireSuccess('ktx runtime start', runtimeStart);
const runtimeStart = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'start']);
requireSuccess('ktx dev runtime start', runtimeStart);
daemonStarted = true;
requireOutput('ktx runtime start', runtimeStart, /Started KTX Python daemon/);
requireOutput('ktx runtime start', runtimeStart, /url: http:\\/\\/127\\.0\\.0\\.1:\\d+/);
requireOutput('ktx runtime start', runtimeStart, /features: core/);
requireOutput('ktx dev runtime start', runtimeStart, /Started KTX Python daemon/);
requireOutput('ktx dev runtime start', runtimeStart, /url: http:\\/\\/127\\.0\\.0\\.1:\\d+/);
requireOutput('ktx dev runtime start', runtimeStart, /features: core/);
const runtimeStartReuse = await run('pnpm', ['exec', 'ktx', 'runtime', 'start']);
requireSuccess('ktx runtime start reuse', runtimeStartReuse);
requireOutput('ktx runtime start reuse', runtimeStartReuse, /Using existing KTX Python daemon/);
requireOutput('ktx runtime start reuse', runtimeStartReuse, /features: core/);
const runtimeStartReuse = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'start']);
requireSuccess('ktx dev runtime start reuse', runtimeStartReuse);
requireOutput('ktx dev runtime start reuse', runtimeStartReuse, /Using existing KTX Python daemon/);
requireOutput('ktx dev runtime start reuse', runtimeStartReuse, /features: core/);
const runtimeStop = await run('pnpm', ['exec', 'ktx', 'runtime', 'stop']);
requireSuccess('ktx runtime stop', runtimeStop);
const runtimeStop = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'stop']);
requireSuccess('ktx dev runtime stop', runtimeStop);
daemonStarted = false;
requireOutput('ktx runtime stop', runtimeStop, /Stopped KTX Python daemon/);
process.stdout.write('ktx runtime daemon lifecycle verified\\n');
requireOutput('ktx dev runtime stop', runtimeStop, /Stopped KTX Python daemon/);
process.stdout.write('ktx dev runtime daemon lifecycle verified\\n');
const staleRuntimeDir = join(process.env.KTX_RUNTIME_ROOT, '0.0.0');
await mkdir(staleRuntimeDir, { recursive: true });
const runtimePruneDryRun = await run('pnpm', ['exec', 'ktx', 'runtime', 'prune', '--dry-run']);
requireSuccess('ktx runtime prune dry run', runtimePruneDryRun);
requireOutput('ktx runtime prune dry run', runtimePruneDryRun, /Stale KTX Python runtimes/);
requireOutput('ktx runtime prune dry run', runtimePruneDryRun, /0\\.0\\.0/);
const runtimePruneDryRun = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'prune', '--dry-run']);
requireSuccess('ktx dev runtime prune dry run', runtimePruneDryRun);
requireOutput('ktx dev runtime prune dry run', runtimePruneDryRun, /Stale KTX Python runtimes/);
requireOutput('ktx dev runtime prune dry run', runtimePruneDryRun, /0\\.0\\.0/);
await access(staleRuntimeDir);
const runtimePruneNeedsConfirmation = await run('pnpm', ['exec', 'ktx', 'runtime', 'prune']);
assert.equal(runtimePruneNeedsConfirmation.code, 1, 'ktx runtime prune needs confirmation');
assert.equal(runtimePruneNeedsConfirmation.stdout, '', 'ktx runtime prune needs confirmation wrote stdout');
const runtimePruneNeedsConfirmation = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'prune']);
assert.equal(runtimePruneNeedsConfirmation.code, 1, 'ktx dev runtime prune needs confirmation');
assert.equal(runtimePruneNeedsConfirmation.stdout, '', 'ktx dev runtime prune needs confirmation wrote stdout');
assert.match(runtimePruneNeedsConfirmation.stderr, /Refusing to prune without --yes/);
const runtimePruneConfirmed = await run('pnpm', ['exec', 'ktx', 'runtime', 'prune', '--yes']);
requireSuccess('ktx runtime prune confirmed', runtimePruneConfirmed);
requireOutput('ktx runtime prune confirmed', runtimePruneConfirmed, /Removed stale KTX Python runtimes/);
requireOutput('ktx runtime prune confirmed', runtimePruneConfirmed, /0\\.0\\.0/);
const runtimePruneConfirmed = await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'prune', '--yes']);
requireSuccess('ktx dev runtime prune confirmed', runtimePruneConfirmed);
requireOutput('ktx dev runtime prune confirmed', runtimePruneConfirmed, /Removed stale KTX Python runtimes/);
requireOutput('ktx dev runtime prune confirmed', runtimePruneConfirmed, /0\\.0\\.0/);
await assert.rejects(() => access(staleRuntimeDir));
process.stdout.write('ktx runtime prune verified\\n');
process.stdout.write('ktx dev runtime prune verified\\n');
const structuralScan = await run('pnpm', ['exec', 'ktx', 'dev', 'scan', 'warehouse',
'--project-dir',
@ -992,7 +992,7 @@ try {
process.stdout.write('ktx dev ingest provider guard verified\\n');
} finally {
if (daemonStarted) {
await run('pnpm', ['exec', 'ktx', 'runtime', 'stop']);
await run('pnpm', ['exec', 'ktx', 'dev', 'runtime', 'stop']);
}
if (previousRuntimeRoot === undefined) {
delete process.env.KTX_RUNTIME_ROOT;
@ -1122,11 +1122,11 @@ try {
);
process.stdout.write('ktx seeded demo agent sl search verified\\n');
const doctor = await run('pnpm', ['exec', 'ktx', 'dev', 'doctor', 'setup', '--no-input']);
assert.ok([0, 1].includes(doctor.code), 'ktx dev doctor setup exit code must be 0 or 1');
requireStdout('ktx dev doctor setup', doctor, /KTX setup doctor/);
requireStdout('ktx dev doctor setup', doctor, /Node 22\\+/);
assert.equal(doctor.stderr, 'Project: ' + process.cwd() + '\\n', 'ktx dev doctor setup wrote unexpected stderr');
const doctor = await run('pnpm', ['exec', 'ktx', 'status', '--no-input']);
assert.ok([0, 1].includes(doctor.code), 'ktx status setup exit code must be 0 or 1');
requireStdout('ktx status setup', doctor, /KTX setup doctor/);
requireStdout('ktx status setup', doctor, /Node 22\\+/);
assert.equal(doctor.stderr, '', 'ktx status setup wrote unexpected stderr');
} finally {
await rm(root, { recursive: true, force: true });
}

View file

@ -151,7 +151,7 @@ describe('findPythonArtifacts', () => {
it('throws when a required Python artifact is missing', async () => {
const root = await mkdtemp(join(tmpdir(), 'ktx-artifacts-test-'));
try {
await assert.rejects(() => findPythonArtifacts(root), /Missing Python artifact: kaelio-ktx runtime wheel/);
await assert.rejects(() => findPythonArtifacts(root), /Missing Python artifact: kaelio-ktx dev runtime wheel/);
} finally {
await rm(root, { recursive: true, force: true });
}
@ -472,24 +472,24 @@ describe('verification snippets', () => {
assert.doesNotMatch(source, /run\('python'/);
assert.match(source, /KTX_RUNTIME_ROOT/);
assert.match(source, /managed-runtime/);
assert.match(source, /ktx runtime status missing/);
assert.match(source, /ktx dev runtime status missing/);
assert.match(source, /runtimeStatusBefore\.kind, 'missing'/);
assert.ok(source.includes(String.raw`Installing KTX Python runtime \(core\) with uv`));
assert.match(source, /KTX Python runtime ready:/);
assert.match(source, /ktx runtime status ready/);
assert.match(source, /ktx dev runtime status ready/);
assert.match(source, /runtimeStatusAfter\.kind, 'ready'/);
assert.match(source, /runtimeStatusAfter\.manifest\.features/);
assert.match(source, /ktx runtime doctor/);
assert.match(source, /ktx dev runtime doctor/);
assert.match(source, /PASS Managed Python runtime/);
assert.match(source, /ktx runtime start/);
assert.match(source, /ktx runtime start reuse/);
assert.match(source, /ktx dev runtime start/);
assert.match(source, /ktx dev runtime start reuse/);
assert.match(source, /Using existing KTX Python daemon/);
assert.match(source, /ktx runtime stop/);
assert.match(source, /ktx runtime prune dry run/);
assert.match(source, /ktx dev runtime stop/);
assert.match(source, /ktx dev runtime prune dry run/);
assert.match(source, /0\.0\.0/);
assert.match(source, /ktx runtime prune needs confirmation/);
assert.match(source, /ktx dev runtime prune needs confirmation/);
assert.match(source, /Refusing to prune without --yes/);
assert.match(source, /ktx runtime prune confirmed/);
assert.match(source, /ktx dev runtime prune confirmed/);
assert.match(source, /Removed stale KTX Python runtimes/);
assert.match(source, /assert\.rejects\(\(\) => access\(staleRuntimeDir\)\)/);
assert.match(source, /run\('pnpm', \[\s*'exec',\s*'ktx',\s*'dev',\s*'scan',\s*'warehouse'/);
@ -519,7 +519,7 @@ describe('verification snippets', () => {
assert.match(source, /LLM calls: none/);
assert.match(source, /ktx agent context --json/);
assert.doesNotMatch(source, new RegExp(["'demo'", "'--mode'", "'deterministic'"].join(', ')));
assert.match(source, /'dev', 'doctor', 'setup', '--no-input'/);
assert.match(source, /'status', '--no-input'/);
assert.match(source, /'--plain'/);
assert.match(source, /function requireProjectStderr/);
assert.match(source, /requireProjectStderr\('ktx setup demo seeded', seeded, projectDir\)/);

View file

@ -44,8 +44,8 @@ export async function runSetupDev(options = {}) {
{
name: 'doctor setup',
command: process.execPath,
args: ['packages/cli/dist/bin.js', 'dev', 'doctor', 'setup', '--no-input'],
retry: 'pnpm run ktx -- dev doctor setup --no-input',
args: ['packages/cli/dist/bin.js', 'status', '--no-input'],
retry: 'pnpm run ktx -- status --no-input',
},
];