mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-01 08:59:39 +02:00
fix(cli): remove ktx setup subcommands (#42)
* fix(cli): remove ktx setup subcommands * test(scripts): update setup-dev status expectation
This commit is contained in:
parent
cc5e41f836
commit
17a2fee69a
48 changed files with 172 additions and 5011 deletions
|
|
@ -1,26 +0,0 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveDemoCommandOptions } from './demo-commands.js';
|
||||
|
||||
describe('resolveDemoCommandOptions', () => {
|
||||
it('lets parent --no-input override a child default from optsWithGlobals', () => {
|
||||
const rootCommand = {
|
||||
opts: () => ({}),
|
||||
};
|
||||
const setupCommand = {
|
||||
parent: rootCommand,
|
||||
opts: () => ({ input: false }),
|
||||
getOptionValueSource: (name: string) => (name === 'input' ? 'cli' : undefined),
|
||||
};
|
||||
const demoCommand = {
|
||||
parent: setupCommand,
|
||||
opts: () => ({ input: true, mode: 'seeded' }),
|
||||
optsWithGlobals: () => ({ input: true, mode: 'seeded' }),
|
||||
getOptionValueSource: (name: string) => (name === 'input' ? 'default' : name === 'mode' ? 'default' : undefined),
|
||||
};
|
||||
|
||||
expect(resolveDemoCommandOptions<{ input: boolean; mode: string }>(demoCommand)).toEqual({
|
||||
input: false,
|
||||
mode: 'seeded',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,273 +0,0 @@
|
|||
import { type Command, Option } from '@commander-js/extra-typings';
|
||||
import {
|
||||
type CommandWithGlobalOptions,
|
||||
type KtxCliCommandContext,
|
||||
resolveCommandProjectDirOverride,
|
||||
} from '../cli-program.js';
|
||||
import {
|
||||
type KtxDemoArgs,
|
||||
type KtxDemoInputMode,
|
||||
type KtxDemoMode,
|
||||
type KtxDemoOutputMode,
|
||||
} from '../demo.js';
|
||||
import { defaultDemoProjectDir } from '../demo-assets.js';
|
||||
import { resolveProjectDir } from '../project-dir.js';
|
||||
import { profileMark } from '../startup-profile.js';
|
||||
|
||||
profileMark('module:commands/demo-commands');
|
||||
|
||||
interface DemoOptions {
|
||||
plain?: boolean;
|
||||
json?: boolean;
|
||||
input?: boolean;
|
||||
projectDir?: string;
|
||||
}
|
||||
|
||||
function demoOutputMode(options: { plain?: boolean; json?: boolean }): KtxDemoOutputMode {
|
||||
if (options.json === true) {
|
||||
return 'json';
|
||||
}
|
||||
if (options.plain === true) {
|
||||
return 'plain';
|
||||
}
|
||||
return 'viz';
|
||||
}
|
||||
|
||||
function demoDoctorOutputMode(options: { json?: boolean }): 'plain' | 'json' {
|
||||
return options.json === true ? 'json' : 'plain';
|
||||
}
|
||||
|
||||
function demoInspectOutputMode(options: { plain?: boolean; json?: boolean }): KtxDemoOutputMode {
|
||||
if (options.json === true) {
|
||||
return 'json';
|
||||
}
|
||||
return 'plain';
|
||||
}
|
||||
|
||||
function demoInputMode(options: { input?: boolean }): { inputMode?: KtxDemoInputMode } {
|
||||
return options.input === false ? { inputMode: 'disabled' } : {};
|
||||
}
|
||||
|
||||
function demoProjectDir(options: { projectDir?: string }, command: CommandWithGlobalOptions): string {
|
||||
return resolveProjectDir(
|
||||
options.projectDir ?? resolveCommandProjectDirOverride(command),
|
||||
defaultDemoProjectDir(),
|
||||
);
|
||||
}
|
||||
|
||||
type CommandOptionSourceReader = {
|
||||
getOptionValueSource?: (name: string) => string | undefined;
|
||||
parent?: unknown;
|
||||
};
|
||||
|
||||
function inheritedOptionSource(command: CommandOptionSourceReader, key: string): string | undefined {
|
||||
let current = command.parent as (CommandOptionSourceReader & { opts?: () => Record<string, unknown> }) | undefined;
|
||||
while (current) {
|
||||
const source = current.getOptionValueSource?.(key);
|
||||
if (source !== undefined) {
|
||||
return source;
|
||||
}
|
||||
current = current.parent as (CommandOptionSourceReader & { opts?: () => Record<string, unknown> }) | undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function definedOptions(
|
||||
options: Record<string, unknown>,
|
||||
inherited: Record<string, unknown> = {},
|
||||
command?: CommandOptionSourceReader,
|
||||
): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(options).filter(([key, value]) => {
|
||||
if (value === undefined) return false;
|
||||
if (key === 'input' && value === true && inherited.input === false) return false;
|
||||
if (
|
||||
key === 'mode' &&
|
||||
command?.getOptionValueSource?.(key) === 'default' &&
|
||||
inherited[key] !== undefined &&
|
||||
inherited[key] !== value &&
|
||||
inheritedOptionSource(command, key) === 'cli'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveDemoCommandOptions<T>(command: { opts: () => T; optsWithGlobals?: () => T; parent?: unknown }): T {
|
||||
const chain: Array<{ opts?: () => Record<string, unknown>; parent?: unknown }> = [];
|
||||
let current = command.parent as { opts?: () => Record<string, unknown>; parent?: unknown } | undefined;
|
||||
while (current) {
|
||||
chain.unshift(current);
|
||||
current = current.parent as { opts?: () => Record<string, unknown>; parent?: unknown } | undefined;
|
||||
}
|
||||
const inherited = Object.assign({}, ...chain.map((parent) => definedOptions(parent.opts?.() ?? {})));
|
||||
|
||||
if (command.optsWithGlobals) {
|
||||
const withGlobals = {
|
||||
...inherited,
|
||||
...definedOptions(command.optsWithGlobals() as Record<string, unknown>, inherited, command),
|
||||
};
|
||||
return {
|
||||
...withGlobals,
|
||||
...definedOptions(command.opts() as Record<string, unknown>, withGlobals, command),
|
||||
} as T;
|
||||
}
|
||||
|
||||
return { ...inherited, ...definedOptions(command.opts() as Record<string, unknown>, inherited, command) } as T;
|
||||
}
|
||||
|
||||
async function runDemoArgs(context: KtxCliCommandContext, args: KtxDemoArgs): Promise<void> {
|
||||
const runner = context.deps.demo ?? (await import('../demo.js')).runKtxDemo;
|
||||
context.setExitCode(await runner(args, context.io));
|
||||
}
|
||||
|
||||
export function registerDemoCommands(
|
||||
program: Command,
|
||||
context: KtxCliCommandContext,
|
||||
options: { description?: string } = {},
|
||||
): void {
|
||||
const demo = program
|
||||
.command('demo')
|
||||
.description(options.description ?? 'Run the pre-seeded KTX demo or a full LLM-backed demo')
|
||||
.addOption(
|
||||
new Option('--mode <mode>', 'Demo mode: seeded (default), replay, or full')
|
||||
.choices(['seeded', 'replay', 'full'])
|
||||
.default('seeded'),
|
||||
)
|
||||
.option('--project-dir <path>', 'Demo project directory')
|
||||
.addOption(new Option('--plain', 'Print plain text output instead of the visual demo').conflicts('json'))
|
||||
.addOption(new Option('--json', 'Print JSON output').conflicts('plain'))
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.showHelpAfterError()
|
||||
.action(async (options: { mode: 'seeded' | 'replay' | 'full' } & DemoOptions, command) => {
|
||||
const resolvedOptions = resolveDemoCommandOptions<typeof options>(command);
|
||||
await runDemoArgs(context, {
|
||||
command: resolvedOptions.mode,
|
||||
projectDir: demoProjectDir(resolvedOptions, command),
|
||||
outputMode: demoOutputMode(resolvedOptions),
|
||||
...demoInputMode(resolvedOptions),
|
||||
});
|
||||
});
|
||||
|
||||
demo
|
||||
.command('init')
|
||||
.description('Initialize the packaged demo project')
|
||||
.option('--project-dir <path>', 'Demo project directory')
|
||||
.option('--force', 'Recreate an existing demo project', false)
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (_options, command: { opts: () => { projectDir?: string; force?: boolean; input?: boolean } }) => {
|
||||
const options = resolveDemoCommandOptions(command);
|
||||
await runDemoArgs(context, {
|
||||
command: 'init',
|
||||
projectDir: demoProjectDir(options, command),
|
||||
force: options.force === true,
|
||||
...demoInputMode(options),
|
||||
});
|
||||
});
|
||||
|
||||
demo
|
||||
.command('reset')
|
||||
.description('Reset the packaged demo project')
|
||||
.option('--project-dir <path>', 'Demo project directory')
|
||||
.option('--force', 'Recreate the demo project without prompting', false)
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (_options, command: { opts: () => { projectDir?: string; force?: boolean; input?: boolean } }) => {
|
||||
const options = resolveDemoCommandOptions(command);
|
||||
await runDemoArgs(context, {
|
||||
command: 'reset',
|
||||
projectDir: demoProjectDir(options, command),
|
||||
force: options.force === true,
|
||||
...demoInputMode(options),
|
||||
});
|
||||
});
|
||||
|
||||
demo
|
||||
.command('replay')
|
||||
.description('Replay the packaged demo memory-flow')
|
||||
.option('--project-dir <path>', 'Demo project directory')
|
||||
.addOption(new Option('--plain', 'Print plain text output instead of the visual demo').conflicts('json'))
|
||||
.addOption(new Option('--json', 'Print JSON output').conflicts('plain'))
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (_options, command: { opts: () => DemoOptions }) => {
|
||||
const options = resolveDemoCommandOptions(command);
|
||||
await runDemoArgs(context, {
|
||||
command: 'replay',
|
||||
projectDir: demoProjectDir(options, command),
|
||||
outputMode: demoOutputMode(options),
|
||||
...demoInputMode(options),
|
||||
});
|
||||
});
|
||||
|
||||
demo
|
||||
.command('scan')
|
||||
.description('Run the packaged demo scan')
|
||||
.option('--project-dir <path>', 'Demo project directory')
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (_options, command: { opts: () => { projectDir?: string; input?: boolean } }) => {
|
||||
const options = resolveDemoCommandOptions(command);
|
||||
await runDemoArgs(context, {
|
||||
command: 'scan',
|
||||
projectDir: demoProjectDir(options, command),
|
||||
...demoInputMode(options),
|
||||
});
|
||||
});
|
||||
|
||||
demo
|
||||
.command('inspect')
|
||||
.description('Inspect packaged demo outputs')
|
||||
.option('--project-dir <path>', 'Demo project directory')
|
||||
.addOption(new Option('--plain', 'Print plain text output').conflicts('json'))
|
||||
.addOption(new Option('--json', 'Print JSON output').conflicts('plain'))
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (_options, command: { opts: () => DemoOptions }) => {
|
||||
const options = resolveDemoCommandOptions(command);
|
||||
await runDemoArgs(context, {
|
||||
command: 'inspect',
|
||||
projectDir: demoProjectDir(options, command),
|
||||
outputMode: demoInspectOutputMode(options),
|
||||
...demoInputMode(options),
|
||||
});
|
||||
});
|
||||
|
||||
demo
|
||||
.command('doctor')
|
||||
.description('Check packaged demo readiness')
|
||||
.option('--project-dir <path>', 'Demo project directory')
|
||||
.addOption(new Option('--plain', 'Print plain text output').conflicts('json'))
|
||||
.addOption(new Option('--json', 'Print JSON output').conflicts('plain'))
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (_options, command: { opts: () => DemoOptions }) => {
|
||||
const options = resolveDemoCommandOptions(command);
|
||||
await runDemoArgs(context, {
|
||||
command: 'doctor',
|
||||
projectDir: demoProjectDir(options, command),
|
||||
outputMode: demoDoctorOutputMode(options),
|
||||
...demoInputMode(options),
|
||||
});
|
||||
});
|
||||
|
||||
demo
|
||||
.command('ingest')
|
||||
.description('Run packaged demo ingest')
|
||||
.addOption(
|
||||
new Option('--mode <mode>', 'Demo ingest mode: full or seeded')
|
||||
.choices(['full', 'seeded'])
|
||||
.default('full'),
|
||||
)
|
||||
.option('--project-dir <path>', 'Demo project directory')
|
||||
.addOption(new Option('--plain', 'Print plain text output instead of the visual demo').conflicts('json'))
|
||||
.addOption(new Option('--json', 'Print JSON output').conflicts('plain'))
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (_options, command: { opts: () => { mode: KtxDemoMode } & DemoOptions }) => {
|
||||
const options = resolveDemoCommandOptions(command);
|
||||
await runDemoArgs(context, {
|
||||
command: 'ingest',
|
||||
mode: options.mode,
|
||||
projectDir: demoProjectDir(options, command),
|
||||
outputMode: demoOutputMode(options),
|
||||
...demoInputMode(options),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ async function runDoctorArgs(context: KtxCliCommandContext, args: KtxDoctorArgs)
|
|||
export function registerDoctorCommands(program: Command, context: KtxCliCommandContext): void {
|
||||
const doctor = program
|
||||
.command('doctor')
|
||||
.description('Check KTX setup, project, and demo readiness')
|
||||
.description('Check KTX setup and project readiness')
|
||||
.option('--json', 'Print JSON output', false)
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (options: { json?: boolean; input?: boolean }, command) => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import type { KtxCliCommandContext } from '../cli-program.js';
|
|||
import { resolveCommandProjectDir } from '../cli-program.js';
|
||||
import type { KtxSetupDatabaseDriver } from '../setup-databases.js';
|
||||
import type { KtxSetupSourceType } from '../setup-sources.js';
|
||||
import { registerDemoCommands } from './demo-commands.js';
|
||||
|
||||
async function runSetupArgs(
|
||||
context: KtxCliCommandContext,
|
||||
|
|
@ -414,98 +413,4 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
showEntryMenu: shouldShowSetupEntryMenu(options, command),
|
||||
});
|
||||
});
|
||||
|
||||
registerDemoCommands(setup, context, { description: 'Run the packaged KTX demo from setup' });
|
||||
|
||||
const setupContext = setup.command('context').description('Build, inspect, and recover setup-managed KTX context');
|
||||
|
||||
function setupContextInputMode(command: {
|
||||
optsWithGlobals?: () => unknown;
|
||||
opts?: () => unknown;
|
||||
}): 'auto' | 'disabled' {
|
||||
const options = command.optsWithGlobals?.() as { input?: boolean } | undefined;
|
||||
return options?.input === false ? 'disabled' : 'auto';
|
||||
}
|
||||
|
||||
setupContext
|
||||
.command('build')
|
||||
.description('Build agent-ready KTX context for setup')
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (options: { input?: boolean }, command) => {
|
||||
await runSetupArgs(context, {
|
||||
command: 'context-build',
|
||||
projectDir: resolveCommandProjectDir(command),
|
||||
inputMode: options.input === false ? 'disabled' : setupContextInputMode(command),
|
||||
});
|
||||
});
|
||||
|
||||
setupContext
|
||||
.command('watch')
|
||||
.description('Watch a setup-managed context build')
|
||||
.argument('[runId]', 'Setup context build run id')
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.action(async (runId: string | undefined, options: { input?: boolean }, command) => {
|
||||
await runSetupArgs(context, {
|
||||
command: 'context-watch',
|
||||
projectDir: resolveCommandProjectDir(command),
|
||||
...(runId ? { runId } : {}),
|
||||
inputMode: options.input === false ? 'disabled' : setupContextInputMode(command),
|
||||
});
|
||||
});
|
||||
|
||||
setupContext
|
||||
.command('status')
|
||||
.description('Print setup-managed context build status')
|
||||
.argument('[runId]', 'Setup context build run id')
|
||||
.option('--json', 'Print JSON output', false)
|
||||
.action(async (runId: string | undefined, options: { json?: boolean }, command) => {
|
||||
await runSetupArgs(context, {
|
||||
command: 'context-status',
|
||||
projectDir: resolveCommandProjectDir(command),
|
||||
...(runId ? { runId } : {}),
|
||||
json: options.json === true,
|
||||
});
|
||||
});
|
||||
|
||||
setupContext
|
||||
.command('stop')
|
||||
.description('Request a pause for a setup-managed context build')
|
||||
.argument('[runId]', 'Setup context build run id')
|
||||
.option('--force', 'Request the pause without an interactive confirmation', false)
|
||||
.action(async (runId: string | undefined, _options: { force?: boolean }, command) => {
|
||||
await runSetupArgs(context, {
|
||||
command: 'context-stop',
|
||||
projectDir: resolveCommandProjectDir(command),
|
||||
...(runId ? { runId } : {}),
|
||||
});
|
||||
});
|
||||
|
||||
setup
|
||||
.command('remove')
|
||||
.description('Remove setup-managed local integrations')
|
||||
.option('--agents', 'Remove setup-managed agent integration files', false)
|
||||
.action(async (options: { agents?: boolean }, command) => {
|
||||
const parentOptions = command.parent?.opts() as { agents?: boolean } | undefined;
|
||||
if (options.agents !== true && parentOptions?.agents !== true) {
|
||||
context.io.stderr.write('Choose what to remove: --agents.\n');
|
||||
context.setExitCode(1);
|
||||
return;
|
||||
}
|
||||
await runSetupArgs(context, {
|
||||
command: 'remove-agents',
|
||||
projectDir: resolveCommandProjectDir(command),
|
||||
});
|
||||
});
|
||||
|
||||
setup
|
||||
.command('status')
|
||||
.description('Show setup readiness for the resolved KTX project')
|
||||
.option('--json', 'Print JSON output', false)
|
||||
.action(async (options: { json?: boolean }, command) => {
|
||||
await runSetupArgs(context, {
|
||||
command: 'status',
|
||||
projectDir: resolveCommandProjectDir(command),
|
||||
json: options.json === true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue