rename klo to ktx

This commit is contained in:
Andrey Avtomonov 2026-05-10 23:51:24 +02:00
parent 1a42152e6f
commit 3ce510b55b
704 changed files with 10205 additions and 10255 deletions

View file

@ -1,10 +1,10 @@
import { Option, type Command } from '@commander-js/extra-typings';
import type { KloAgentArgs } from '../agent.js';
import type { KloCliCommandContext } from '../cli-program.js';
import type { KtxAgentArgs } from '../agent.js';
import type { KtxCliCommandContext } from '../cli-program.js';
import { parsePositiveIntegerOption, resolveCommandProjectDir } from '../cli-program.js';
async function runAgent(context: KloCliCommandContext, args: KloAgentArgs): Promise<void> {
const runner = context.deps.agent ?? (await import('../agent.js')).runKloAgent;
async function runAgent(context: KtxCliCommandContext, args: KtxAgentArgs): Promise<void> {
const runner = context.deps.agent ?? (await import('../agent.js')).runKtxAgent;
context.setExitCode(await runner(args, context.io));
}
@ -12,10 +12,10 @@ function jsonOption(): Option {
return new Option('--json', 'Print JSON output').makeOptionMandatory();
}
export function registerAgentCommands(program: Command, context: KloCliCommandContext): void {
export function registerAgentCommands(program: Command, context: KtxCliCommandContext): void {
const agent = program
.command('agent', { hidden: true })
.description('Machine-readable KLO commands for coding agents')
.description('Machine-readable KTX commands for coding agents')
.showHelpAfterError();
agent.hook('preAction', (_thisCommand, actionCommand) => {
@ -24,7 +24,7 @@ export function registerAgentCommands(program: Command, context: KloCliCommandCo
agent
.command('tools')
.description('Print available agent-facing KLO tools')
.description('Print available agent-facing KTX tools')
.addOption(jsonOption())
.action(async (_options, command) => {
await runAgent(context, { command: 'tools', projectDir: resolveCommandProjectDir(command), json: true });
@ -91,10 +91,10 @@ export function registerAgentCommands(program: Command, context: KloCliCommandCo
},
);
const wiki = agent.command('wiki').description('KLO wiki agent commands');
const wiki = agent.command('wiki').description('KTX wiki agent commands');
wiki
.command('search')
.description('Search KLO wiki pages')
.description('Search KTX wiki pages')
.argument('<query>')
.addOption(jsonOption())
.option('--limit <number>', 'Maximum search results', parsePositiveIntegerOption, 10)
@ -109,7 +109,7 @@ export function registerAgentCommands(program: Command, context: KloCliCommandCo
});
wiki
.command('read')
.description('Read one KLO wiki page')
.description('Read one KTX wiki page')
.argument('<pageId>')
.addOption(jsonOption())
.action(async (pageId: string, _options, command) => {

View file

@ -1,10 +1,10 @@
import type { CommandUnknownOpts } from '@commander-js/extra-typings';
import type { KloCliCommandContext } from '../cli-program.js';
import type { KtxCliCommandContext } from '../cli-program.js';
import { completeCommanderInput, installZshCompletion, zshCompletionScript } from '../completion.js';
export function registerCompletionCommands(
program: CommandUnknownOpts,
context: KloCliCommandContext,
context: KtxCliCommandContext,
completionRoot: CommandUnknownOpts = program,
): void {
program

View file

@ -1,7 +1,7 @@
import { type Command, InvalidArgumentError, Option } from '@commander-js/extra-typings';
import {
collectOption,
type KloCliCommandContext,
type KtxCliCommandContext,
parseBooleanStringOption,
parseNonEmptyAssignmentOption,
parseNonNegativeIntegerOption,
@ -10,9 +10,9 @@ import {
resolveCommandProjectDir,
} from '../cli-program.js';
import { connectionAddCommandSchema } from '../command-schemas.js';
import type { KloConnectionArgs } from '../connection.js';
import type { KtxConnectionArgs } from '../connection.js';
import { profileMark } from '../startup-profile.js';
import type { KloConnectionMappingArgs } from './connection-mapping.js';
import type { KtxConnectionMappingArgs } from './connection-mapping.js';
import { registerConnectionMetabaseCommands } from './connection-metabase-commands.js';
import { registerConnectionNotionCommands } from './connection-notion-commands.js';
@ -42,24 +42,24 @@ function parseMappingFieldOption(value: string): 'databaseMappings' | 'connectio
throw new InvalidArgumentError('must be databaseMappings or connectionMappings');
}
async function runConnectionArgs(context: KloCliCommandContext, args: KloConnectionArgs): Promise<void> {
const runner = context.deps.connection ?? (await import('../connection.js')).runKloConnection;
async function runConnectionArgs(context: KtxCliCommandContext, args: KtxConnectionArgs): Promise<void> {
const runner = context.deps.connection ?? (await import('../connection.js')).runKtxConnection;
context.setExitCode(await runner(args, context.io));
}
async function runMappingArgs(context: KloCliCommandContext, args: KloConnectionMappingArgs): Promise<void> {
const { runKloConnectionMapping } = await import('./connection-mapping.js');
context.setExitCode(await runKloConnectionMapping(args, context.io));
async function runMappingArgs(context: KtxCliCommandContext, args: KtxConnectionMappingArgs): Promise<void> {
const { runKtxConnectionMapping } = await import('./connection-mapping.js');
context.setExitCode(await runKtxConnectionMapping(args, context.io));
}
export function registerConnectionCommands(program: Command, context: KloCliCommandContext, commandName = 'connection'): void {
export function registerConnectionCommands(program: Command, context: KtxCliCommandContext, commandName = 'connection'): void {
const connection = program
.command(commandName)
.description('Add, list, test, and map data sources')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the nearest klo.yaml or current working directory.\n',
'\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the nearest ktx.yaml or current working directory.\n',
);
connection.hook('preAction', (_thisCommand, actionCommand) => {
context.writeDebug?.(commandName, actionCommand);
@ -75,7 +75,7 @@ export function registerConnectionCommands(program: Command, context: KloCliComm
connection
.command('test')
.description('Test a configured connection')
.argument('<connectionId>', 'KLO connection id')
.argument('<connectionId>', 'KTX connection id')
.action(async (connectionId: string, _options: unknown, command) => {
await runConnectionArgs(context, {
command: 'test',
@ -88,12 +88,12 @@ export function registerConnectionCommands(program: Command, context: KloCliComm
.command('add')
.description('Add or replace a configured connection')
.argument('<driver>', 'Connection driver')
.argument('<connectionId>', 'KLO connection id')
.argument('<connectionId>', 'KTX connection id')
.option('--url <url>', 'Connection URL, env:NAME, or file:/path reference')
.option('--schema <schema>', 'Schema to include; repeatable', collectOption, [])
.option('--readonly', 'Mark the connection as read-only', false)
.option('--force', 'Replace an existing connection', false)
.option('--allow-literal-credentials', 'Allow writing a literal credential URL to klo.yaml', false)
.option('--allow-literal-credentials', 'Allow writing a literal credential URL to ktx.yaml', false)
.addOption(new Option('--token-env <name>', 'Environment variable containing Notion auth token').conflicts('tokenFile'))
.addOption(new Option('--token-file <path>', 'File containing Notion auth token').conflicts('tokenEnv'))
.addOption(
@ -155,8 +155,8 @@ export function registerConnectionCommands(program: Command, context: KloCliComm
connection
.command('remove')
.description('Remove a configured connection from klo.yaml')
.argument('<connectionId>', 'KLO connection id')
.description('Remove a configured connection from ktx.yaml')
.argument('<connectionId>', 'KTX connection id')
.option('--force', 'Remove without prompting', false)
.option('--no-input', 'Disable interactive terminal input')
.action(async (connectionId: string, options: { force?: boolean; input?: boolean }, command) => {
@ -188,14 +188,14 @@ export function registerConnectionCommands(program: Command, context: KloCliComm
registerConnectionNotionCommands(connection, context);
}
export function registerConnectionMappingCommands(connection: Command, context: KloCliCommandContext): void {
export function registerConnectionMappingCommands(connection: Command, context: KtxCliCommandContext): void {
const mapping = connection
.command('mapping')
.description('Manage Metabase warehouse mappings')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n',
'\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n',
);
mapping

View file

@ -1,10 +1,10 @@
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { LocalMetabaseSourceStateReader } from '@klo/context/ingest';
import { initKloProject, loadKloProject, serializeKloProjectConfig } from '@klo/context/project';
import { LocalMetabaseSourceStateReader } from '@ktx/context/ingest';
import { initKtxProject, loadKtxProject, serializeKtxProjectConfig } from '@ktx/context/project';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { runKloConnectionMapping } from './connection-mapping.js';
import { runKtxConnectionMapping } from './connection-mapping.js';
function makeIo() {
let stdout = '';
@ -27,18 +27,18 @@ function makeIo() {
};
}
describe('runKloConnectionMapping', () => {
describe('runKtxConnectionMapping', () => {
let tempDir: string;
let projectDir: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-metabase-mapping-'));
tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-metabase-mapping-'));
projectDir = join(tempDir, 'project');
await initKloProject({ projectDir, projectName: 'mapping' });
const project = await loadKloProject({ projectDir });
await initKtxProject({ projectDir, projectName: 'mapping' });
const project = await loadKtxProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig({
'ktx.yaml',
serializeKtxProjectConfig({
...project.config,
connections: {
'prod-metabase': {
@ -53,22 +53,22 @@ describe('runKloConnectionMapping', () => {
},
},
}),
'klo',
'klo@example.com',
'ktx',
'ktx@example.com',
'Seed Metabase mapping test connections',
);
});
async function replaceConnections(connections: Record<string, { driver: string; [key: string]: unknown }>) {
const project = await loadKloProject({ projectDir });
const project = await loadKtxProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig({
'ktx.yaml',
serializeKtxProjectConfig({
...project.config,
connections,
}),
'klo',
'klo@example.com',
'ktx',
'ktx@example.com',
'Replace mapping test connections',
);
}
@ -80,7 +80,7 @@ describe('runKloConnectionMapping', () => {
it('sets, lists, disables, and clears local Metabase mappings', async () => {
const io = makeIo();
await expect(
runKloConnectionMapping(
runKtxConnectionMapping(
{
command: 'set',
projectDir,
@ -95,13 +95,13 @@ describe('runKloConnectionMapping', () => {
const listIo = makeIo();
await expect(
runKloConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-metabase', json: false }, listIo.io),
runKtxConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-metabase', json: false }, listIo.io),
).resolves.toBe(0);
expect(listIo.stdout()).toContain('1 -> prod-warehouse');
expect(listIo.stdout()).toContain('unhydrated');
await expect(
runKloConnectionMapping(
runKtxConnectionMapping(
{
command: 'set-sync-enabled',
projectDir,
@ -114,7 +114,7 @@ describe('runKloConnectionMapping', () => {
).resolves.toBe(0);
await expect(
runKloConnectionMapping(
runKtxConnectionMapping(
{
command: 'clear',
projectDir,
@ -127,12 +127,12 @@ describe('runKloConnectionMapping', () => {
});
it('lists Metabase yaml mapping bootstrap rows before any SQLite command writes', async () => {
const projectDir = await mkdtemp(join(tmpdir(), 'klo-cli-yaml-mapping-'));
await initKloProject({ projectDir, projectName: 'yaml-mapping' });
const project = await loadKloProject({ projectDir });
const projectDir = await mkdtemp(join(tmpdir(), 'ktx-cli-yaml-mapping-'));
await initKtxProject({ projectDir, projectName: 'yaml-mapping' });
const project = await loadKtxProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig({
'ktx.yaml',
serializeKtxProjectConfig({
...project.config,
connections: {
'prod-metabase': {
@ -145,21 +145,21 @@ describe('runKloConnectionMapping', () => {
'prod-warehouse': { driver: 'postgres', url: 'postgresql://readonly@db.test/analytics' },
},
}),
'klo',
'klo@example.com',
'ktx',
'ktx@example.com',
'Seed yaml mappings',
);
const io = makeIo();
await expect(
runKloConnectionMapping(
runKtxConnectionMapping(
{ command: 'list', projectDir, connectionId: 'prod-metabase', json: false },
io.io,
),
).resolves.toBe(0);
expect(io.stdout()).toContain('1 -> prod-warehouse');
expect(io.stdout()).toContain('source: klo.yaml');
expect(io.stdout()).toContain('source: ktx.yaml');
});
it('refreshes Metabase discovery metadata through the injected runtime client', async () => {
@ -178,7 +178,7 @@ describe('runKloConnectionMapping', () => {
const io = makeIo();
await expect(
runKloConnectionMapping(
runKtxConnectionMapping(
{
command: 'refresh',
projectDir,
@ -194,7 +194,7 @@ describe('runKloConnectionMapping', () => {
expect(io.stdout()).toContain('Discovery: 1 database');
expect(client.cleanup).toHaveBeenCalledTimes(1);
const store = new LocalMetabaseSourceStateReader({ dbPath: join(projectDir, '.klo', 'db.sqlite') });
const store = new LocalMetabaseSourceStateReader({ dbPath: join(projectDir, '.ktx', 'db.sqlite') });
await expect(store.listDatabaseMappings('prod-metabase')).resolves.toMatchObject([
{ metabaseDatabaseId: 1, metabaseDatabaseName: 'Analytics', source: 'refresh' },
]);
@ -215,7 +215,7 @@ describe('runKloConnectionMapping', () => {
const io = makeIo();
await expect(
runKloConnectionMapping(
runKtxConnectionMapping(
{
command: 'set',
projectDir,
@ -228,7 +228,7 @@ describe('runKloConnectionMapping', () => {
),
).resolves.toBe(0);
await expect(
runKloConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-looker', json: false }, io.io),
runKtxConnectionMapping({ command: 'list', projectDir, connectionId: 'prod-looker', json: false }, io.io),
).resolves.toBe(0);
expect(io.stdout()).toContain('analytics -> prod-warehouse');
@ -242,7 +242,7 @@ describe('runKloConnectionMapping', () => {
const io = makeIo();
await expect(
runKloConnectionMapping(
runKtxConnectionMapping(
{
command: 'set',
projectDir,
@ -273,7 +273,7 @@ describe('runKloConnectionMapping', () => {
const io = makeIo();
await expect(
runKloConnectionMapping(
runKtxConnectionMapping(
{ command: 'refresh', projectDir, connectionId: 'prod-looker', autoAccept: true },
io.io,
{
@ -298,12 +298,12 @@ describe('runKloConnectionMapping', () => {
});
it('validates Looker mappings through the canonical local warehouse descriptor', async () => {
const projectDir = await mkdtemp(join(tmpdir(), 'klo-cli-descriptor-validation-'));
await initKloProject({ projectDir, projectName: 'descriptor-validation' });
const project = await loadKloProject({ projectDir });
const projectDir = await mkdtemp(join(tmpdir(), 'ktx-cli-descriptor-validation-'));
await initKtxProject({ projectDir, projectName: 'descriptor-validation' });
const project = await loadKtxProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig({
'ktx.yaml',
serializeKtxProjectConfig({
...project.config,
connections: {
'prod-looker': {
@ -313,14 +313,14 @@ describe('runKloConnectionMapping', () => {
'prod-warehouse': { driver: 'postgresql', url: 'postgresql://readonly@db.test/analytics' },
},
}),
'klo',
'klo@example.com',
'ktx',
'ktx@example.com',
'Seed descriptor validation',
);
const io = makeIo();
await expect(
runKloConnectionMapping({ command: 'validate', projectDir, connectionId: 'prod-looker' }, io.io),
runKtxConnectionMapping({ command: 'validate', projectDir, connectionId: 'prod-looker' }, io.io),
).resolves.toBe(0);
expect(io.stdout()).toContain('Mapping validation passed: prod-looker');

View file

@ -1,5 +1,5 @@
import { readFile } from 'node:fs/promises';
import { localConnectionToWarehouseDescriptor } from '@klo/context/connections';
import { localConnectionToWarehouseDescriptor } from '@ktx/context/connections';
import {
DEFAULT_METABASE_CLIENT_CONFIG,
DefaultLookerConnectionClientFactory,
@ -12,20 +12,20 @@ import {
discoverMetabaseDatabases,
lookerCredentialsFromLocalConnection,
metabaseRuntimeConfigFromLocalConnection,
seedLocalMappingStateFromKloYaml,
seedLocalMappingStateFromKtxYaml,
validateLookerMappings,
validateMappingPhysicalMatch,
type LookerMappingClient,
type MetabaseRuntimeClient,
type MetabaseSyncMode,
} from '@klo/context/ingest';
import { type KloLocalProject, kloLocalStateDbPath, loadKloProject } from '@klo/context/project';
import type { KloCliIo } from '../index.js';
} from '@ktx/context/ingest';
import { type KtxLocalProject, ktxLocalStateDbPath, loadKtxProject } from '@ktx/context/project';
import type { KtxCliIo } from '../index.js';
import { profileMark } from '../startup-profile.js';
profileMark('module:commands/connection-mapping');
export type KloConnectionMappingArgs =
export type KtxConnectionMappingArgs =
| { command: 'list'; projectDir: string; connectionId: string; json: boolean }
| {
command: 'set';
@ -57,13 +57,13 @@ export type KloConnectionMappingArgs =
| { command: 'validate'; projectDir: string; connectionId: string }
| { command: 'clear'; projectDir: string; connectionId: string; metabaseDatabaseId?: number; mappingKey?: string };
interface KloConnectionMappingDeps {
interface KtxConnectionMappingDeps {
createMetabaseClient?: (
project: KloLocalProject,
project: KtxLocalProject,
connectionId: string,
) => Promise<Pick<MetabaseRuntimeClient, 'getDatabases' | 'cleanup'>>;
createLookerClient?: (
project: KloLocalProject,
project: KtxLocalProject,
connectionId: string,
) => Promise<Pick<LookerMappingClient, 'listLookerConnections'> & { cleanup?(): Promise<void> }>;
}
@ -85,7 +85,7 @@ function parseId(value: string, label: string): number {
}
async function createDefaultMetabaseClient(
project: KloLocalProject,
project: KtxLocalProject,
connectionId: string,
): Promise<Pick<MetabaseRuntimeClient, 'getDatabases' | 'cleanup'>> {
const factory = new DefaultMetabaseConnectionClientFactory(
@ -97,7 +97,7 @@ async function createDefaultMetabaseClient(
}
async function createDefaultLookerClient(
project: KloLocalProject,
project: KtxLocalProject,
connectionId: string,
): Promise<Pick<LookerMappingClient, 'listLookerConnections'> & { cleanup?(): Promise<void> }> {
const factory = new DefaultLookerConnectionClientFactory({
@ -110,30 +110,30 @@ async function createDefaultLookerClient(
};
}
function isLookerConnection(project: KloLocalProject, connectionId: string): boolean {
function isLookerConnection(project: KtxLocalProject, connectionId: string): boolean {
return String(project.config.connections[connectionId]?.driver ?? '').toLowerCase() === 'looker';
}
function assertLookerConnection(project: KloLocalProject, connectionId: string): void {
function assertLookerConnection(project: KtxLocalProject, connectionId: string): void {
if (!isLookerConnection(project, connectionId)) {
throw new Error(`Connection "${connectionId}" is not a Looker connection`);
}
}
function assertMetabaseConnection(project: KloLocalProject, connectionId: string): void {
function assertMetabaseConnection(project: KtxLocalProject, connectionId: string): void {
const connection = project.config.connections[connectionId];
if (!connection || String(connection.driver).toLowerCase() !== 'metabase') {
throw new Error(`Connection "${connectionId}" is not a Metabase connection`);
}
}
function assertTargetConnection(project: KloLocalProject, connectionId: string): void {
function assertTargetConnection(project: KtxLocalProject, connectionId: string): void {
if (!project.config.connections[connectionId]) {
throw new Error(`Target connection "${connectionId}" does not exist`);
}
}
function targetPhysicalInfo(project: KloLocalProject, connectionId: string) {
function targetPhysicalInfo(project: KtxLocalProject, connectionId: string) {
const descriptor = localConnectionToWarehouseDescriptor(connectionId, project.config.connections[connectionId]);
if (!descriptor) {
return { connection_type: 'UNKNOWN' };
@ -160,22 +160,22 @@ function renderMapping(
}
function renderLookerMapping(row: Awaited<ReturnType<LocalLookerRuntimeStore['listConnectionMappings']>>[number]): string {
const target = row.kloConnectionId ?? '[unmapped]';
const target = row.ktxConnectionId ?? '[unmapped]';
const metadata = [row.lookerDialect, row.lookerHost, row.lookerDatabase].filter(Boolean).join(', ');
return `${row.lookerConnectionName} -> ${target}${metadata ? ` (${metadata}, source: ${row.source})` : ` (source: ${row.source})`}`;
}
export async function runKloConnectionMapping(
args: KloConnectionMappingArgs,
io: KloCliIo = process,
deps: KloConnectionMappingDeps = {},
export async function runKtxConnectionMapping(
args: KtxConnectionMappingArgs,
io: KtxCliIo = process,
deps: KtxConnectionMappingDeps = {},
): Promise<number> {
try {
const project = await loadKloProject({ projectDir: args.projectDir });
await seedLocalMappingStateFromKloYaml(project, args.connectionId);
const project = await loadKtxProject({ projectDir: args.projectDir });
await seedLocalMappingStateFromKtxYaml(project, args.connectionId);
if (isLookerConnection(project, args.connectionId)) {
assertLookerConnection(project, args.connectionId);
const store = new LocalLookerRuntimeStore({ dbPath: kloLocalStateDbPath(project) });
const store = new LocalLookerRuntimeStore({ dbPath: ktxLocalStateDbPath(project) });
if (args.command === 'list') {
const rows = await store.listConnectionMappings(args.connectionId);
@ -191,7 +191,7 @@ export async function runKloConnectionMapping(
await store.upsertConnectionMapping({
lookerConnectionId: args.connectionId,
lookerConnectionName: args.key,
kloConnectionId: args.value,
ktxConnectionId: args.value,
source: 'cli',
});
io.stdout.write(`Set connectionMappings.${args.key} = ${args.value}\n`);
@ -219,13 +219,13 @@ export async function runKloConnectionMapping(
}
if (args.command === 'validate') {
const knownKloConnectionIds = new Set(Object.keys(project.config.connections));
const knownKtxConnectionIds = new Set(Object.keys(project.config.connections));
const knownConnectionTypes = new Map(
Object.entries(project.config.connections).map(([id, _config]) => [id, targetPhysicalInfo(project, id).connection_type]),
);
const validation = validateLookerMappings({
mappings: await store.readMappings(args.connectionId),
knownKloConnectionIds,
knownKtxConnectionIds,
knownConnectionTypes,
});
if (!validation.ok) {
@ -255,7 +255,7 @@ export async function runKloConnectionMapping(
}
assertMetabaseConnection(project, args.connectionId);
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(project) });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(project) });
if (args.command === 'list') {
const rows = await store.listDatabaseMappings(args.connectionId);

View file

@ -1,17 +1,17 @@
import { type Command, Option } from '@commander-js/extra-typings';
import {
type KloCliCommandContext,
type KtxCliCommandContext,
parseNonEmptyAssignmentOption,
parsePositiveIntegerOption,
parseSafeConnectionIdOption,
resolveCommandProjectDir,
} from '../cli-program.js';
import {
type KloConnectionMetabaseSetupArgs,
type KtxConnectionMetabaseSetupArgs,
type MetabaseSetupMappingAssignment,
type MetabaseSetupSyncMode,
runKloConnectionMetabaseSetup,
runKtxConnectionMetabaseSetup,
} from './connection-metabase-setup.js';
const SYNC_MODE_CHOICES = ['ALL', 'ONLY', 'EXCEPT'] as const satisfies readonly MetabaseSetupSyncMode[];
@ -51,21 +51,21 @@ function collectMappingOption(
}
async function runMetabaseSetupArgs(
context: KloCliCommandContext,
args: KloConnectionMetabaseSetupArgs,
context: KtxCliCommandContext,
args: KtxConnectionMetabaseSetupArgs,
): Promise<void> {
const runner = context.deps.connectionMetabaseSetup ?? runKloConnectionMetabaseSetup;
const runner = context.deps.connectionMetabaseSetup ?? runKtxConnectionMetabaseSetup;
context.setExitCode(await runner(args, context.io));
}
export function registerConnectionMetabaseCommands(connection: Command, context: KloCliCommandContext): void {
export function registerConnectionMetabaseCommands(connection: Command, context: KtxCliCommandContext): void {
const metabase = connection
.command('metabase')
.description('Configure Metabase connections')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n',
'\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n',
);
metabase.action(() => {
@ -76,7 +76,7 @@ export function registerConnectionMetabaseCommands(connection: Command, context:
metabase
.command('setup')
.description('Guided setup for a Metabase connection')
.option('--id <connectionId>', 'KLO connection id to write', parseSafeConnectionIdOption)
.option('--id <connectionId>', 'KTX connection id to write', parseSafeConnectionIdOption)
.option('--url <url>', 'Metabase API URL')
.addOption(new Option('--api-key <key>', 'Metabase API key').conflicts('mintApiKey'))
.option('--mint-api-key', 'Mint a Metabase API key with credentials', false)
@ -85,10 +85,10 @@ export function registerConnectionMetabaseCommands(connection: Command, context:
.addHelpText(
'after',
'\nGuided equivalent of:\n' +
' klo connection mapping refresh <connectionId> --auto-accept\n' +
' klo connection mapping set <connectionId> databaseMappings <id>=<target>\n' +
' klo connection mapping set-sync-enabled <connectionId> <id> --enabled true\n' +
' klo ingest <connectionId>\n',
' ktx connection mapping refresh <connectionId> --auto-accept\n' +
' ktx connection mapping set <connectionId> databaseMappings <id>=<target>\n' +
' ktx connection mapping set-sync-enabled <connectionId> <id> --enabled true\n' +
' ktx ingest <connectionId>\n',
)
.option(
'--map <metabaseDatabaseId=targetConnectionId>',

View file

@ -1,11 +1,11 @@
import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { LocalMetabaseSourceStateReader } from '@klo/context/ingest';
import { initKloProject, kloLocalStateDbPath, loadKloProject, serializeKloProjectConfig } from '@klo/context/project';
import { LocalMetabaseSourceStateReader } from '@ktx/context/ingest';
import { initKtxProject, ktxLocalStateDbPath, loadKtxProject, serializeKtxProjectConfig } from '@ktx/context/project';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { runKloConnectionMetabaseSetup } from './connection-metabase-setup.js';
import { runKtxConnectionMetabaseSetup } from './connection-metabase-setup.js';
const CANCEL_PROMPT = Symbol('cancel');
@ -135,7 +135,7 @@ function makeIo(options: { isTTY?: boolean; stdinIsTTY?: boolean } = {}) {
};
}
describe('runKloConnectionMetabaseSetup', () => {
describe('runKtxConnectionMetabaseSetup', () => {
const fakeMetabaseCredential = 'mb_example';
const existingMetabaseCredential = 'mb_existing';
const fakeAdminCredential = 'pw';
@ -144,9 +144,9 @@ describe('runKloConnectionMetabaseSetup', () => {
let projectDir: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-metabase-setup-'));
tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-metabase-setup-'));
projectDir = join(tempDir, 'project');
await initKloProject({ projectDir, projectName: 'metabase-setup' });
await initKtxProject({ projectDir, projectName: 'metabase-setup' });
});
afterEach(async () => {
@ -154,15 +154,15 @@ describe('runKloConnectionMetabaseSetup', () => {
});
async function writeConnections(connections: Record<string, { driver: string; [key: string]: unknown }>) {
const project = await loadKloProject({ projectDir });
const project = await loadKtxProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig({
'ktx.yaml',
serializeKtxProjectConfig({
...project.config,
connections,
}),
'klo',
'klo@example.com',
'ktx',
'ktx@example.com',
'Seed Metabase setup test connections',
);
}
@ -208,7 +208,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -230,17 +230,17 @@ describe('runKloConnectionMetabaseSetup', () => {
expect(io.stdout()).toContain('Connection: metabase');
expect(io.stdout()).toContain('Discovered 1 database');
expect(io.stdout()).toContain(`klo ingest metabase --project-dir ${projectDir}`);
expect(io.stdout()).toContain(`ktx ingest metabase --project-dir ${projectDir}`);
expect(io.stdout()).not.toContain('mb_example');
expect(io.stderr()).not.toContain('mb_example');
const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
expect(config).toContain('driver: metabase');
expect(config).toContain('api_url: http://metabase.example.test:3000');
expect(config).toContain('api_key: mb_example');
const updatedProject = await loadKloProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
const updatedProject = await loadKtxProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) });
await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([
{
metabaseDatabaseId: 2,
@ -275,7 +275,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -295,8 +295,8 @@ describe('runKloConnectionMetabaseSetup', () => {
),
).resolves.toBe(0);
const updatedProject = await loadKloProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
const updatedProject = await loadKtxProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) });
await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([
{ metabaseDatabaseId: 2, targetConnectionId: 'orbit', syncEnabled: true },
]);
@ -314,7 +314,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -350,7 +350,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -370,8 +370,8 @@ describe('runKloConnectionMetabaseSetup', () => {
),
).resolves.toBe(0);
const updatedProject = await loadKloProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
const updatedProject = await loadKtxProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) });
await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([
{ metabaseDatabaseId: 2, targetConnectionId: 'orbit', syncEnabled: true },
]);
@ -384,7 +384,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -412,7 +412,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -440,7 +440,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const missingUsernameIo = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -462,7 +462,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const missingPasswordIo = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -500,7 +500,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const mintingIo = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -537,7 +537,7 @@ describe('runKloConnectionMetabaseSetup', () => {
expect(mintingIo.stdout()).not.toContain(fakeAdminCredential);
expect(mintingIo.stderr()).not.toContain(fakeAdminCredential);
const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
expect(config).toContain('driver: metabase');
expect(config).toContain('api_url: http://metabase.example.test:3000');
expect(config).toContain(`api_key: ${mintedMetabaseCredential}`);
@ -548,7 +548,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -590,7 +590,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -640,7 +640,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -660,8 +660,8 @@ describe('runKloConnectionMetabaseSetup', () => {
),
).resolves.toBe(0);
const updatedProject = await loadKloProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
const updatedProject = await loadKtxProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) });
await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([
{ metabaseDatabaseId: 1, targetConnectionId: 'orbit', syncEnabled: true },
{ metabaseDatabaseId: 2, targetConnectionId: null, syncEnabled: false },
@ -676,7 +676,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -712,7 +712,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -759,7 +759,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -782,12 +782,12 @@ describe('runKloConnectionMetabaseSetup', () => {
),
).resolves.toBe(1);
const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
expect(config).toContain('driver: metabase');
expect(io.stderr()).toContain(`klo ingest metabase --project-dir ${projectDir}`);
expect(io.stderr()).toContain(`ktx ingest metabase --project-dir ${projectDir}`);
const updatedProject = await loadKloProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
const updatedProject = await loadKtxProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) });
await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([
{ metabaseDatabaseId: 2, targetConnectionId: 'orbit' },
]);
@ -810,7 +810,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -857,7 +857,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const interactiveMetabaseCredential = 'mb_interactive_fixture';
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -882,13 +882,13 @@ describe('runKloConnectionMetabaseSetup', () => {
),
).resolves.toBe(0);
const config = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const config = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
expect(config).toContain('driver: metabase');
expect(config).toContain('api_url: http://metabase.example.test:3000');
expect(config).toContain(`api_key: ${interactiveMetabaseCredential}`);
const updatedProject = await loadKloProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
const updatedProject = await loadKtxProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) });
await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([
{
metabaseDatabaseId: 2,
@ -931,7 +931,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const events: string[] = [];
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -958,8 +958,8 @@ describe('runKloConnectionMetabaseSetup', () => {
),
).resolves.toBe(0);
const updatedProject = await loadKloProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
const updatedProject = await loadKtxProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) });
await expect(store.listDatabaseMappings('metabase')).resolves.toMatchObject([
{ metabaseDatabaseId: 2, targetConnectionId: 'orbit', syncEnabled: true },
{ metabaseDatabaseId: 3, targetConnectionId: 'warehouse2', syncEnabled: false },
@ -997,7 +997,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const events: string[] = [];
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -1023,7 +1023,7 @@ describe('runKloConnectionMetabaseSetup', () => {
),
).resolves.toBe(0);
expect(events).toContain('intro:KLO Metabase setup');
expect(events).toContain('intro:KTX Metabase setup');
expect(events.some((event) => event.startsWith('spinner.start:Testing Metabase connection'))).toBe(true);
expect(events.some((event) => event.startsWith('spinner.stop:Metabase reachable'))).toBe(true);
expect(events.some((event) => event.startsWith('spinner.start:Discovering Metabase databases'))).toBe(true);
@ -1053,7 +1053,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const io = makeIo();
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -1081,7 +1081,7 @@ describe('runKloConnectionMetabaseSetup', () => {
},
});
const beforeConfig = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const beforeConfig = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
const metabaseClient = makeMetabaseClient({
testConnectionSuccess: true,
databases: [
@ -1098,7 +1098,7 @@ describe('runKloConnectionMetabaseSetup', () => {
const cancelMetabaseCredential = 'mb_cancel_fixture';
await expect(
runKloConnectionMetabaseSetup(
runKtxConnectionMetabaseSetup(
{
command: 'setup',
projectDir,
@ -1126,11 +1126,11 @@ describe('runKloConnectionMetabaseSetup', () => {
expect(io.stderr()).toContain('Setup cancelled.');
expect(io.stderr()).not.toContain(cancelMetabaseCredential);
const afterConfig = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const afterConfig = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
expect(afterConfig).toBe(beforeConfig);
const updatedProject = await loadKloProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
const updatedProject = await loadKtxProject({ projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) });
await expect(store.listDatabaseMappings('metabase')).resolves.toEqual([]);
});
});

View file

@ -12,7 +12,7 @@ import {
select,
text,
} from '@clack/prompts';
import { localConnectionToWarehouseDescriptor } from '@klo/context/connections';
import { localConnectionToWarehouseDescriptor } from '@ktx/context/connections';
import {
DEFAULT_METABASE_CLIENT_CONFIG,
DefaultMetabaseConnectionClientFactory,
@ -23,21 +23,21 @@ import {
type MetabaseSyncMode,
metabaseRuntimeConfigFromLocalConnection,
validateMappingPhysicalMatch,
} from '@klo/context/ingest';
} from '@ktx/context/ingest';
import {
type KloLocalProject,
type KloProjectConnectionConfig,
kloLocalStateDbPath,
loadKloProject,
serializeKloProjectConfig,
} from '@klo/context/project';
type KtxLocalProject,
type KtxProjectConnectionConfig,
ktxLocalStateDbPath,
loadKtxProject,
serializeKtxProjectConfig,
} from '@ktx/context/project';
import { createClackSpinner, type KloCliSpinner } from '../clack.js';
import type { KloCliIo } from '../cli-runtime.js';
import { createClackSpinner, type KtxCliSpinner } from '../clack.js';
import type { KtxCliIo } from '../cli-runtime.js';
import { withMenuOptionsSpacing, withMultiselectNavigation } from '../prompt-navigation.js';
import { type KloPublicIngestArgs, runKloPublicIngest } from '../public-ingest.js';
import { type KtxPublicIngestArgs, runKtxPublicIngest } from '../public-ingest.js';
export type KloMetabaseSetupInputMode = 'auto' | 'disabled';
export type KtxMetabaseSetupInputMode = 'auto' | 'disabled';
export type MetabaseSetupSyncMode = MetabaseSyncMode;
@ -56,7 +56,7 @@ export interface MetabaseSetupPromptAdapter {
outro(message?: string): void;
note(message: string, title: string): void;
log: MetabaseSetupLogger;
spinner(): KloCliSpinner;
spinner(): KtxCliSpinner;
select<T extends string>(options: { message: string; options: Array<MetabaseSetupPromptOption<T>> }): Promise<T>;
multiselect<Value extends number | string>(options: {
message: string;
@ -71,7 +71,7 @@ export interface MetabaseSetupPromptAdapter {
cancel(message: string): void;
}
type KloMetabaseSetupInteractiveIo = KloCliIo & {
type KtxMetabaseSetupInteractiveIo = KtxCliIo & {
stdin?: { isTTY?: boolean };
};
@ -86,9 +86,9 @@ export interface MintMetabaseApiKeyArgs {
password: string;
}
export type MintMetabaseApiKey = (args: MintMetabaseApiKeyArgs, io: KloCliIo) => Promise<string>;
export type MintMetabaseApiKey = (args: MintMetabaseApiKeyArgs, io: KtxCliIo) => Promise<string>;
export interface KloConnectionMetabaseSetupArgs {
export interface KtxConnectionMetabaseSetupArgs {
command: 'setup';
projectDir: string;
connectionId?: string;
@ -102,20 +102,20 @@ export interface KloConnectionMetabaseSetupArgs {
syncMode: MetabaseSetupSyncMode;
runIngest: boolean;
yes: boolean;
inputMode: KloMetabaseSetupInputMode;
inputMode: KtxMetabaseSetupInputMode;
}
export interface KloConnectionMetabaseSetupDeps {
export interface KtxConnectionMetabaseSetupDeps {
createMetabaseClient?: (
project: KloLocalProject,
project: KtxLocalProject,
connectionId: string,
) => Promise<Pick<MetabaseRuntimeClient, 'testConnection' | 'getDatabases' | 'cleanup'>>;
mintMetabaseApiKey?: MintMetabaseApiKey;
prompts?: MetabaseSetupPromptAdapter;
runPublicIngest?: (args: Extract<KloPublicIngestArgs, { command: 'run' }>, io: KloCliIo) => Promise<number>;
runPublicIngest?: (args: Extract<KtxPublicIngestArgs, { command: 'run' }>, io: KtxCliIo) => Promise<number>;
}
function isMetabaseConnection(connection: KloProjectConnectionConfig | undefined): boolean {
function isMetabaseConnection(connection: KtxProjectConnectionConfig | undefined): boolean {
return (
String(connection?.driver ?? '')
.trim()
@ -131,22 +131,22 @@ function uniqueSorted(values: number[]): number[] {
return [...new Set(values)].sort((a, b) => a - b);
}
function resolveMetabaseUrl(connection: KloProjectConnectionConfig | undefined): string | undefined {
function resolveMetabaseUrl(connection: KtxProjectConnectionConfig | undefined): string | undefined {
return stringField(connection?.api_url) ?? stringField(connection?.apiUrl) ?? stringField(connection?.url);
}
function resolveLiteralMetabaseApiKey(connection: KloProjectConnectionConfig | undefined): string | undefined {
function resolveLiteralMetabaseApiKey(connection: KtxProjectConnectionConfig | undefined): string | undefined {
return stringField(connection?.api_key) ?? stringField(connection?.apiKey);
}
function listMetabaseConnectionIds(project: KloLocalProject): string[] {
function listMetabaseConnectionIds(project: KtxLocalProject): string[] {
return Object.entries(project.config.connections)
.filter(([_connectionId, connection]) => isMetabaseConnection(connection))
.map(([connectionId]) => connectionId)
.sort();
}
function listWarehouseConnectionIds(project: KloLocalProject): string[] {
function listWarehouseConnectionIds(project: KtxLocalProject): string[] {
return Object.entries(project.config.connections)
.filter(([connectionId, connection]) => localConnectionToWarehouseDescriptor(connectionId, connection) != null)
.map(([connectionId]) => connectionId)
@ -165,7 +165,7 @@ function redactSecrets(message: string, secrets: string[]): string {
}
async function createDefaultMetabaseClient(
project: KloLocalProject,
project: KtxLocalProject,
connectionId: string,
): Promise<Pick<MetabaseRuntimeClient, 'testConnection' | 'getDatabases' | 'cleanup'>> {
const factory = new DefaultMetabaseConnectionClientFactory(
@ -192,7 +192,7 @@ async function defaultMintMetabaseApiKey(args: MintMetabaseApiKeyArgs): Promise<
const mintedKey = await sessionClient.createApiKey({
groupId: adminGroup.id,
name: `KLO CLI ${new Date().toISOString()}`,
name: `KTX CLI ${new Date().toISOString()}`,
});
const trimmedKey = stringField(mintedKey);
if (!trimmedKey) {
@ -237,7 +237,7 @@ export function createClackMetabaseSetupPromptAdapter(): MetabaseSetupPromptAdap
log.error(message);
},
},
spinner(): KloCliSpinner {
spinner(): KtxCliSpinner {
return createClackSpinner();
},
async select<T extends string>(options: {
@ -271,8 +271,8 @@ export function createClackMetabaseSetupPromptAdapter(): MetabaseSetupPromptAdap
}
function isInteractiveMetabaseSetupIo(
args: Pick<KloConnectionMetabaseSetupArgs, 'inputMode'>,
io: KloMetabaseSetupInteractiveIo,
args: Pick<KtxConnectionMetabaseSetupArgs, 'inputMode'>,
io: KtxMetabaseSetupInteractiveIo,
): boolean {
return args.inputMode !== 'disabled' && io.stdin?.isTTY === true && io.stdout.isTTY === true;
}
@ -295,7 +295,7 @@ function normalizeDiscoveredDatabases(databases: MetabaseDatabase[]): Array<{
}));
}
function targetPhysicalInfo(project: KloLocalProject, connectionId: string) {
function targetPhysicalInfo(project: KtxLocalProject, connectionId: string) {
const descriptor = localConnectionToWarehouseDescriptor(connectionId, project.config.connections[connectionId]);
if (!descriptor) {
return { connection_type: 'UNKNOWN' };
@ -338,23 +338,23 @@ function noteMetabaseSetupSummary(options: {
);
}
export async function runKloConnectionMetabaseSetup(
args: KloConnectionMetabaseSetupArgs,
io: KloCliIo,
deps: KloConnectionMetabaseSetupDeps = {},
export async function runKtxConnectionMetabaseSetup(
args: KtxConnectionMetabaseSetupArgs,
io: KtxCliIo,
deps: KtxConnectionMetabaseSetupDeps = {},
): Promise<number> {
let apiKeyForRedaction = args.apiKey;
let passwordForRedaction = args.metabasePassword;
const interactiveIo = io as KloMetabaseSetupInteractiveIo;
const interactiveIo = io as KtxMetabaseSetupInteractiveIo;
const isInteractive = isInteractiveMetabaseSetupIo(args, interactiveIo);
const prompts = deps.prompts ?? (isInteractive ? createClackMetabaseSetupPromptAdapter() : undefined);
try {
if (isInteractive && prompts) {
prompts.intro('KLO Metabase setup');
prompts.intro('KTX Metabase setup');
}
const project = await loadKloProject({ projectDir: args.projectDir });
const project = await loadKtxProject({ projectDir: args.projectDir });
const existingMetabaseConnectionIds = listMetabaseConnectionIds(project);
let connectionId: string;
@ -491,7 +491,7 @@ export async function runKloConnectionMetabaseSetup(
throw new Error('Metabase API key is required (use --api-key)');
}
const transientConnectionConfig: KloProjectConnectionConfig = {
const transientConnectionConfig: KtxProjectConnectionConfig = {
...(existingConnection ?? {}),
driver: 'metabase',
api_url: url,
@ -504,7 +504,7 @@ export async function runKloConnectionMetabaseSetup(
[connectionId]: transientConnectionConfig,
},
};
const discoveryProject: KloLocalProject = { ...project, config: configWithTransient };
const discoveryProject: KtxLocalProject = { ...project, config: configWithTransient };
for (const mapping of args.mappings) {
if (!configWithTransient.connections[mapping.targetConnectionId]) {
@ -618,7 +618,7 @@ export async function runKloConnectionMetabaseSetup(
}
const targetConnectionId = await prompts.select({
message: `Map Metabase database ${database.id} ("${database.name}") to which KLO connection?`,
message: `Map Metabase database ${database.id} ("${database.name}") to which KTX connection?`,
options: warehouseConnectionIds.map((warehouseId) => ({ value: warehouseId, label: warehouseId })),
});
resolvedMappings.push({ metabaseDatabaseId: databaseId, targetConnectionId });
@ -641,7 +641,7 @@ export async function runKloConnectionMetabaseSetup(
syncEnabledDatabaseIds: resolvedSyncEnabledDatabaseIds,
});
const confirmed = await prompts.confirm({
message: 'Write changes to klo.yaml and enable sync?',
message: 'Write changes to ktx.yaml and enable sync?',
initialValue: true,
});
if (!confirmed) {
@ -675,15 +675,15 @@ export async function runKloConnectionMetabaseSetup(
}
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig(configWithTransient),
'klo',
'klo@example.com',
'ktx.yaml',
serializeKtxProjectConfig(configWithTransient),
'ktx',
'ktx@example.com',
`Setup Metabase connection ${connectionId}`,
);
const updatedProject = await loadKloProject({ projectDir: args.projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: kloLocalStateDbPath(updatedProject) });
const updatedProject = await loadKtxProject({ projectDir: args.projectDir });
const store = new LocalMetabaseSourceStateReader({ dbPath: ktxLocalStateDbPath(updatedProject) });
await store.refreshDiscoveredDatabases({ connectionId, discovered });
@ -716,7 +716,7 @@ export async function runKloConnectionMetabaseSetup(
const unhydrated = await store.getUnhydratedSyncEnabledMappingIds(connectionId);
if (unhydrated.length > 0) {
io.stderr.write(
`Sync-enabled mappings are missing discovery metadata; run klo connection mapping refresh ${connectionId} --auto-accept\n`,
`Sync-enabled mappings are missing discovery metadata; run ktx connection mapping refresh ${connectionId} --auto-accept\n`,
);
return 1;
}
@ -743,10 +743,10 @@ export async function runKloConnectionMetabaseSetup(
io.stdout.write(`Connection: ${connectionId}\n`);
io.stdout.write(`Discovered ${discovered.length} ${discovered.length === 1 ? 'database' : 'databases'}\n`);
io.stdout.write(`Next: klo ingest ${connectionId} --project-dir ${args.projectDir}\n`);
io.stdout.write(`Next: ktx ingest ${connectionId} --project-dir ${args.projectDir}\n`);
if (args.runIngest) {
const ingestRunner = deps.runPublicIngest ?? runKloPublicIngest;
const ingestRunner = deps.runPublicIngest ?? runKtxPublicIngest;
const exitCode = await ingestRunner(
{
command: 'run',
@ -759,7 +759,7 @@ export async function runKloConnectionMetabaseSetup(
io,
);
if (exitCode !== 0) {
io.stderr.write(`Ingest failed; re-run: klo ingest ${connectionId} --project-dir ${args.projectDir}\n`);
io.stderr.write(`Ingest failed; re-run: ktx ingest ${connectionId} --project-dir ${args.projectDir}\n`);
return 1;
}
}

View file

@ -1,6 +1,6 @@
import { type Command, InvalidArgumentError } from '@commander-js/extra-typings';
import { collectOption, type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import type { KloConnectionNotionArgs } from './connection-notion.js';
import { collectOption, type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import type { KtxConnectionNotionArgs } from './connection-notion.js';
interface NotionPickOptions {
input?: boolean;
@ -36,7 +36,7 @@ function normalizeNotionPageId(value: string): string {
return `${lower.slice(0, 8)}-${lower.slice(8, 12)}-${lower.slice(12, 16)}-${lower.slice(16, 20)}-${lower.slice(20)}`;
}
function buildPickArgs(connectionId: string, projectDir: string, options: NotionPickOptions): KloConnectionNotionArgs {
function buildPickArgs(connectionId: string, projectDir: string, options: NotionPickOptions): KtxConnectionNotionArgs {
if (options.input !== false) {
return {
command: 'pick',
@ -59,19 +59,19 @@ function buildPickArgs(connectionId: string, projectDir: string, options: Notion
};
}
async function runConnectionNotionArgs(context: KloCliCommandContext, args: KloConnectionNotionArgs): Promise<void> {
const runner = context.deps.connectionNotion ?? (await import('./connection-notion.js')).runKloConnectionNotion;
async function runConnectionNotionArgs(context: KtxCliCommandContext, args: KtxConnectionNotionArgs): Promise<void> {
const runner = context.deps.connectionNotion ?? (await import('./connection-notion.js')).runKtxConnectionNotion;
context.setExitCode(await runner(args, context.io));
}
export function registerConnectionNotionCommands(connect: Command, context: KloCliCommandContext): void {
export function registerConnectionNotionCommands(connect: Command, context: KtxCliCommandContext): void {
const notion = connect
.command('notion')
.description('Configure Notion source selection')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n',
'\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n',
);
notion.action(() => {

View file

@ -10,7 +10,7 @@ import {
type PickerCommand,
type PickerState,
} from './connection-notion-tree.js';
import type { KloCliIo } from '../index.js';
import type { KtxCliIo } from '../index.js';
const COLOR_THEME = {
text: 'white',
@ -28,9 +28,9 @@ const NO_COLOR_THEME = {
type NotionPickerTheme = Record<keyof typeof COLOR_THEME, string>;
export interface NotionPickerTuiIo extends KloCliIo {
export interface NotionPickerTuiIo extends KtxCliIo {
stdin?: { isTTY?: boolean; setRawMode?(value: boolean): void };
stdout: KloCliIo['stdout'] & { isTTY?: boolean; columns?: number; rows?: number };
stdout: KtxCliIo['stdout'] & { isTTY?: boolean; columns?: number; rows?: number };
}
interface InkKey {

View file

@ -2,11 +2,11 @@ import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
initKloProject,
loadKloProject,
serializeKloProjectConfig,
type KloProjectConfig,
} from '@klo/context/project';
initKtxProject,
loadKtxProject,
serializeKtxProjectConfig,
type KtxProjectConfig,
} from '@ktx/context/project';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
applyNotionPickerWriteback,
@ -14,7 +14,7 @@ import {
notionPickerPageFromSearchResult,
normalizeNotionPageId,
resolveNotionWorkspaceLabel,
runKloConnectionNotion,
runKtxConnectionNotion,
type NotionPickerApi,
type PickerRenderInput,
type PickerRenderResult,
@ -91,24 +91,24 @@ describe('normalizeNotionPageId', () => {
});
});
describe('runKloConnectionNotion', () => {
describe('runKtxConnectionNotion', () => {
let tempDir: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'klo-cli-notion-pick-'));
tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-notion-pick-'));
});
afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});
async function writeProjectConfig(projectDir: string, config: KloProjectConfig): Promise<void> {
const project = await loadKloProject({ projectDir });
async function writeProjectConfig(projectDir: string, config: KtxProjectConfig): Promise<void> {
const project = await loadKtxProject({ projectDir });
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig(config),
'klo',
'klo@example.com',
'ktx.yaml',
serializeKtxProjectConfig(config),
'ktx',
'ktx@example.com',
'seed test config',
);
}
@ -120,7 +120,7 @@ describe('runKloConnectionNotion', () => {
});
await expect(
runKloConnectionNotion(
runKtxConnectionNotion(
{
command: 'pick',
projectDir: '/tmp/project',
@ -138,7 +138,7 @@ describe('runKloConnectionNotion', () => {
it('writes selected root_page_ids while preserving every other Notion connection field', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
@ -160,7 +160,7 @@ describe('runKloConnectionNotion', () => {
const io = makeIo();
await expect(
runKloConnectionNotion(
runKtxConnectionNotion(
{
command: 'pick',
projectDir,
@ -175,7 +175,7 @@ describe('runKloConnectionNotion', () => {
),
).resolves.toBe(0);
const yaml = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const yaml = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
expect(yaml).toContain('crawl_mode: selected_roots');
expect(yaml).toContain('root_page_ids:');
expect(yaml).toContain('11111111-2222-3333-4444-555555555555');
@ -193,7 +193,7 @@ describe('runKloConnectionNotion', () => {
it('rejects empty writeback, missing connections, and non-Notion connections', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
@ -204,7 +204,7 @@ describe('runKloConnectionNotion', () => {
},
},
});
const project = await loadKloProject({ projectDir });
const project = await loadKtxProject({ projectDir });
await expect(applyNotionPickerWriteback(project, 'warehouse', [])).rejects.toThrow(
'connection notion pick requires at least one root page id',
@ -297,7 +297,7 @@ describe('runKloConnectionNotion', () => {
it('runs interactive discovery, warns about stale roots, renders the TUI, and saves selected roots', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
@ -330,7 +330,7 @@ describe('runKloConnectionNotion', () => {
const io = makeIo();
await expect(
runKloConnectionNotion(
runKtxConnectionNotion(
{
command: 'pick',
projectDir,
@ -346,7 +346,7 @@ describe('runKloConnectionNotion', () => {
),
).resolves.toBe(0);
const yaml = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const yaml = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
expect(yaml).toContain('crawl_mode: selected_roots');
expect(yaml).toContain(PAGE_IDS.engineering);
expect(yaml).not.toContain(PAGE_IDS.stale);
@ -357,7 +357,7 @@ describe('runKloConnectionNotion', () => {
it('passes partial-discovery warnings into the TUI banner state', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
@ -394,7 +394,7 @@ describe('runKloConnectionNotion', () => {
const io = makeIo();
await expect(
runKloConnectionNotion(
runKtxConnectionNotion(
{
command: 'pick',
projectDir,
@ -422,7 +422,7 @@ describe('runKloConnectionNotion', () => {
it('quits interactive mode without writing when the TUI returns quit', async () => {
const projectDir = join(tempDir, 'project');
const initialized = await initKloProject({ projectDir, projectName: 'warehouse' });
const initialized = await initKtxProject({ projectDir, projectName: 'warehouse' });
await writeProjectConfig(projectDir, {
...initialized.config,
connections: {
@ -440,11 +440,11 @@ describe('runKloConnectionNotion', () => {
},
},
});
const before = await readFile(join(projectDir, 'klo.yaml'), 'utf-8');
const before = await readFile(join(projectDir, 'ktx.yaml'), 'utf-8');
const io = makeIo();
await expect(
runKloConnectionNotion(
runKtxConnectionNotion(
{
command: 'pick',
projectDir,
@ -460,7 +460,7 @@ describe('runKloConnectionNotion', () => {
),
).resolves.toBe(0);
await expect(readFile(join(projectDir, 'klo.yaml'), 'utf-8')).resolves.toBe(before);
await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.toBe(before);
expect(io.stdout()).toContain('No changes saved.');
});
});

View file

@ -1,12 +1,12 @@
import { parseNotionConnectionConfig, resolveNotionAuthToken } from '@klo/context/connections';
import { type NotionApi, type NotionBotInfo, NotionClient } from '@klo/context/ingest';
import { parseNotionConnectionConfig, resolveNotionAuthToken } from '@ktx/context/connections';
import { type NotionApi, type NotionBotInfo, NotionClient } from '@ktx/context/ingest';
import {
type KloLocalProject,
type KloProjectConnectionConfig,
loadKloProject,
serializeKloProjectConfig,
} from '@klo/context/project';
import type { KloCliIo } from '../index.js';
type KtxLocalProject,
type KtxProjectConnectionConfig,
loadKtxProject,
serializeKtxProjectConfig,
} from '@ktx/context/project';
import type { KtxCliIo } from '../index.js';
import { profileMark } from '../startup-profile.js';
import { buildInitialState, buildPickerTree, type NotionPickerPageInput } from './connection-notion-tree.js';
import {
@ -18,7 +18,7 @@ import {
profileMark('module:commands/connection-notion');
export type KloConnectionNotionArgs =
export type KtxConnectionNotionArgs =
| {
command: 'pick';
projectDir: string;
@ -36,9 +36,9 @@ export type KloConnectionNotionArgs =
export type NotionPickerApi = Pick<NotionApi, 'search' | 'retrieveBotUser'>;
export type { PickerRenderInput, PickerRenderResult };
interface KloConnectionNotionDeps {
interface KtxConnectionNotionDeps {
env?: Record<string, string | undefined>;
loadProject?: typeof loadKloProject;
loadProject?: typeof loadKtxProject;
createNotionApi?: (authToken: string) => NotionPickerApi;
renderPicker?: (input: PickerRenderInput, io: NotionPickerTuiIo) => Promise<PickerRenderResult>;
}
@ -168,7 +168,7 @@ export async function resolveNotionWorkspaceLabel(api: NotionPickerApi, connecti
}
}
function notionConnection(project: KloLocalProject, connectionId: string): KloProjectConnectionConfig {
function notionConnection(project: KtxLocalProject, connectionId: string): KtxProjectConnectionConfig {
const connection = project.config.connections[connectionId];
if (!connection) {
throw new Error(`Connection "${connectionId}" not found`);
@ -180,7 +180,7 @@ function notionConnection(project: KloLocalProject, connectionId: string): KloPr
}
export async function applyNotionPickerWriteback(
project: KloLocalProject,
project: KtxLocalProject,
connectionId: string,
rootPageIds: string[],
): Promise<void> {
@ -202,22 +202,22 @@ export async function applyNotionPickerWriteback(
};
await project.fileStore.writeFile(
'klo.yaml',
serializeKloProjectConfig(nextConfig),
'klo',
'klo@example.com',
'ktx.yaml',
serializeKtxProjectConfig(nextConfig),
'ktx',
'ktx@example.com',
`Pick Notion roots: ${connectionId} (${rootPageIds.length} pages)`,
);
}
export async function runKloConnectionNotion(
args: KloConnectionNotionArgs,
io: KloCliIo = process,
deps: KloConnectionNotionDeps = {},
export async function runKtxConnectionNotion(
args: KtxConnectionNotionArgs,
io: KtxCliIo = process,
deps: KtxConnectionNotionDeps = {},
): Promise<number> {
try {
assertSafeConnectionId(args.connectionId);
const loadProject = deps.loadProject ?? loadKloProject;
const loadProject = deps.loadProject ?? loadKtxProject;
if (args.mode === 'interactive') {
const project = await loadProject({ projectDir: args.projectDir });

View file

@ -1,14 +1,14 @@
import { type Command, Option } from '@commander-js/extra-typings';
import {
type CommandWithGlobalOptions,
type KloCliCommandContext,
type KtxCliCommandContext,
resolveCommandProjectDirOverride,
} from '../cli-program.js';
import {
type KloDemoArgs,
type KloDemoInputMode,
type KloDemoMode,
type KloDemoOutputMode,
type KtxDemoArgs,
type KtxDemoInputMode,
type KtxDemoMode,
type KtxDemoOutputMode,
} from '../demo.js';
import { defaultDemoProjectDir } from '../demo-assets.js';
import { resolveProjectDir } from '../project-dir.js';
@ -23,7 +23,7 @@ interface DemoOptions {
projectDir?: string;
}
function demoOutputMode(options: { plain?: boolean; json?: boolean }): KloDemoOutputMode {
function demoOutputMode(options: { plain?: boolean; json?: boolean }): KtxDemoOutputMode {
if (options.json === true) {
return 'json';
}
@ -37,14 +37,14 @@ function demoDoctorOutputMode(options: { json?: boolean }): 'plain' | 'json' {
return options.json === true ? 'json' : 'plain';
}
function demoInspectOutputMode(options: { plain?: boolean; json?: boolean }): KloDemoOutputMode {
function demoInspectOutputMode(options: { plain?: boolean; json?: boolean }): KtxDemoOutputMode {
if (options.json === true) {
return 'json';
}
return 'plain';
}
function demoInputMode(options: { input?: boolean }): { inputMode?: KloDemoInputMode } {
function demoInputMode(options: { input?: boolean }): { inputMode?: KtxDemoInputMode } {
return options.input === false ? { inputMode: 'disabled' } : {};
}
@ -118,19 +118,19 @@ export function resolveDemoCommandOptions<T>(command: { opts: () => T; optsWithG
return { ...inherited, ...definedOptions(command.opts() as Record<string, unknown>, inherited, command) } as T;
}
async function runDemoArgs(context: KloCliCommandContext, args: KloDemoArgs): Promise<void> {
const runner = context.deps.demo ?? (await import('../demo.js')).runKloDemo;
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: KloCliCommandContext,
context: KtxCliCommandContext,
options: { description?: string } = {},
): void {
const demo = program
.command('demo')
.description(options.description ?? 'Run the pre-seeded KLO demo or a full LLM-backed 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'])
@ -260,7 +260,7 @@ export function registerDemoCommands(
.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: KloDemoMode } & DemoOptions }) => {
.action(async (_options, command: { opts: () => { mode: KtxDemoMode } & DemoOptions }) => {
const options = resolveDemoCommandOptions(command);
await runDemoArgs(context, {
command: 'ingest',

View file

@ -1,6 +1,6 @@
import type { Command } from '@commander-js/extra-typings';
import { type CommandWithGlobalOptions, type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import type { KloDoctorArgs } from '../doctor.js';
import { type CommandWithGlobalOptions, type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import type { KtxDoctorArgs } from '../doctor.js';
import { profileMark } from '../startup-profile.js';
profileMark('module:commands/doctor-commands');
@ -13,15 +13,15 @@ function inputMode(options: { input?: boolean }): { inputMode?: 'disabled' } {
return options.input === false ? { inputMode: 'disabled' } : {};
}
async function runDoctorArgs(context: KloCliCommandContext, args: KloDoctorArgs): Promise<void> {
const runner = context.deps.doctor ?? (await import('../doctor.js')).runKloDoctor;
async function runDoctorArgs(context: KtxCliCommandContext, args: KtxDoctorArgs): Promise<void> {
const runner = context.deps.doctor ?? (await import('../doctor.js')).runKtxDoctor;
context.setExitCode(await runner(args, context.io));
}
export function registerDoctorCommands(program: Command, context: KloCliCommandContext): void {
export function registerDoctorCommands(program: Command, context: KtxCliCommandContext): void {
const doctor = program
.command('doctor')
.description('Check KLO setup, project, and demo readiness')
.description('Check KTX setup, project, and demo readiness')
.option('--json', 'Print JSON output', false)
.option('--no-input', 'Disable interactive terminal input')
.action(async (options: { json?: boolean; input?: boolean }, command) => {
@ -35,7 +35,7 @@ export function registerDoctorCommands(program: Command, context: KloCliCommandC
doctor
.command('setup')
.description('Check KLO install, build, and local runtime readiness')
.description('Check KTX install, build, and local runtime readiness')
.option('--json', 'Print JSON output', false)
.option('--no-input', 'Disable interactive terminal input')
.action(

View file

@ -1,22 +1,22 @@
import { resolve } from 'node:path';
import { type Command, Option } from '@commander-js/extra-typings';
import { type KloCliCommandContext, type OutputModeOptions, resolveCommandProjectDir } from '../cli-program.js';
import type { KloCliDeps, KloCliIo } from '../index.js';
import type { KloIngestArgs, KloIngestOutputMode } from '../ingest.js';
import { type KtxCliCommandContext, type OutputModeOptions, resolveCommandProjectDir } from '../cli-program.js';
import type { KtxCliDeps, KtxCliIo } from '../index.js';
import type { KtxIngestArgs, KtxIngestOutputMode } from '../ingest.js';
import { profileMark } from '../startup-profile.js';
profileMark('module:commands/ingest-commands');
interface IngestCommandOptions {
runIngestWithProgress: (
args: KloIngestArgs,
io: KloCliIo,
deps: KloCliDeps,
defaultRunIngest: (args: KloIngestArgs, io: KloCliIo) => Promise<number>,
args: KtxIngestArgs,
io: KtxCliIo,
deps: KtxCliDeps,
defaultRunIngest: (args: KtxIngestArgs, io: KtxCliIo) => Promise<number>,
) => Promise<number>;
}
function outputMode(options: OutputModeOptions): KloIngestOutputMode {
function outputMode(options: OutputModeOptions): KtxIngestOutputMode {
if (options.json === true) {
return 'json';
}
@ -26,7 +26,7 @@ function outputMode(options: OutputModeOptions): KloIngestOutputMode {
return 'plain';
}
function watchOutputMode(options: OutputModeOptions): KloIngestOutputMode {
function watchOutputMode(options: OutputModeOptions): KtxIngestOutputMode {
if (options.json === true) {
return 'json';
}
@ -36,22 +36,22 @@ function watchOutputMode(options: OutputModeOptions): KloIngestOutputMode {
return 'viz';
}
function inputMode(options: OutputModeOptions): Pick<KloIngestArgs, 'inputMode'> {
function inputMode(options: OutputModeOptions): Pick<KtxIngestArgs, 'inputMode'> {
return options.input === false ? { inputMode: 'disabled' } : {};
}
async function runIngestArgs(
context: KloCliCommandContext,
args: KloIngestArgs,
context: KtxCliCommandContext,
args: KtxIngestArgs,
options: IngestCommandOptions,
): Promise<void> {
const { runKloIngest } = await import('../ingest.js');
context.setExitCode(await options.runIngestWithProgress(args, context.io, context.deps, runKloIngest));
const { runKtxIngest } = await import('../ingest.js');
context.setExitCode(await options.runIngestWithProgress(args, context.io, context.deps, runKtxIngest));
}
export function registerIngestCommands(
program: Command,
context: KloCliCommandContext,
context: KtxCliCommandContext,
commandOptions: IngestCommandOptions,
): void {
const ingest = program
@ -66,7 +66,7 @@ export function registerIngestCommands(
ingest
.command('run')
.description('Run local ingest for one configured connection and source adapter')
.requiredOption('--connection-id <connectionId>', 'KLO connection id')
.requiredOption('--connection-id <connectionId>', 'KTX connection id')
.requiredOption('--adapter <adapter>', 'Ingest source adapter name')
.option('--source-dir <path>', 'Directory containing source files')
.option('--database-introspection-url <url>', 'Daemon URL for live-database introspection')

View file

@ -1,24 +1,24 @@
import { type Command, Option } from '@commander-js/extra-typings';
import { collectOption, type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import { collectOption, type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import { wikiWriteCommandSchema } from '../command-schemas.js';
import type { KloKnowledgeArgs } from '../knowledge.js';
import type { KtxKnowledgeArgs } from '../knowledge.js';
import { profileMark } from '../startup-profile.js';
profileMark('module:commands/knowledge-commands');
async function runKnowledgeArgs(context: KloCliCommandContext, args: KloKnowledgeArgs): Promise<void> {
const runner = context.deps.knowledge ?? (await import('../knowledge.js')).runKloKnowledge;
async function runKnowledgeArgs(context: KtxCliCommandContext, args: KtxKnowledgeArgs): Promise<void> {
const runner = context.deps.knowledge ?? (await import('../knowledge.js')).runKtxKnowledge;
context.setExitCode(await runner(args, context.io));
}
export function registerWikiCommands(program: Command, context: KloCliCommandContext): void {
export function registerWikiCommands(program: Command, context: KtxCliCommandContext): void {
const wiki = program
.command('wiki')
.description('List, read, search, or write local wiki pages')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n',
'\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n',
);
wiki

View file

@ -1,7 +1,7 @@
import { InvalidArgumentError, type Command } from '@commander-js/extra-typings';
import { type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import { type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import { publicIngestReadCommandSchema, publicIngestRunCommandSchema } from '../command-schemas.js';
import type { KloPublicIngestArgs, KloPublicIngestInputMode } from '../public-ingest.js';
import type { KtxPublicIngestArgs, KtxPublicIngestInputMode } from '../public-ingest.js';
import { profileMark } from '../startup-profile.js';
profileMark('module:commands/public-ingest-commands');
@ -12,26 +12,26 @@ interface PublicIngestOptions {
input?: boolean;
}
function inputMode(options: { input?: boolean }): KloPublicIngestInputMode {
function inputMode(options: { input?: boolean }): KtxPublicIngestInputMode {
return options.input === false ? 'disabled' : 'auto';
}
async function runPublicIngestArgs(context: KloCliCommandContext, args: KloPublicIngestArgs): Promise<void> {
const runner = context.deps.publicIngest ?? (await import('../public-ingest.js')).runKloPublicIngest;
async function runPublicIngestArgs(context: KtxCliCommandContext, args: KtxPublicIngestArgs): Promise<void> {
const runner = context.deps.publicIngest ?? (await import('../public-ingest.js')).runKtxPublicIngest;
context.setExitCode(await runner(args, context.io));
}
function parsePublicIngestConnectionId(value: string): string {
if (value === 'run') {
throw new InvalidArgumentError('run is reserved; use klo dev ingest run for low-level adapter syntax');
throw new InvalidArgumentError('run is reserved; use ktx dev ingest run for low-level adapter syntax');
}
return value;
}
export function registerPublicIngestCommands(program: Command, context: KloCliCommandContext): void {
export function registerPublicIngestCommands(program: Command, context: KtxCliCommandContext): void {
const ingest = program
.command('ingest')
.description('Build and refresh KLO context from configured sources')
.description('Build and refresh KTX context from configured sources')
.usage('[options] [connectionId]')
.argument('[connectionId]', 'Connection id to ingest', parsePublicIngestConnectionId)
.option('--all', 'Ingest every eligible configured source', false)
@ -42,12 +42,12 @@ export function registerPublicIngestCommands(program: Command, context: KloCliCo
[
'',
'Examples:',
' klo ingest <connectionId> [options]',
' klo ingest --all [options]',
' klo ingest status [runId] [options]',
' klo ingest watch [runId] [options]',
' ktx ingest <connectionId> [options]',
' ktx ingest --all [options]',
' ktx ingest status [runId] [options]',
' ktx ingest watch [runId] [options]',
'',
'Project directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.',
'Project directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.',
'',
].join('\n'),
)
@ -58,7 +58,7 @@ export function registerPublicIngestCommands(program: Command, context: KloCliCo
.action(async (connectionId: string | undefined, _options: PublicIngestOptions, command) => {
const options = command.opts();
if (options.all === true && connectionId) {
throw new Error('klo ingest accepts either --all or <connectionId>, not both');
throw new Error('ktx ingest accepts either --all or <connectionId>, not both');
}
const args = publicIngestRunCommandSchema.parse({
command: 'run',

View file

@ -1,35 +1,35 @@
import { type Command, InvalidArgumentError, Option } from '@commander-js/extra-typings';
import { type KloCliCommandContext, parsePositiveIntegerOption, resolveCommandProjectDir } from '../cli-program.js';
import type { KloScanArgs } from '../scan.js';
import { type KtxCliCommandContext, parsePositiveIntegerOption, resolveCommandProjectDir } from '../cli-program.js';
import type { KtxScanArgs } from '../scan.js';
import { profileMark } from '../startup-profile.js';
profileMark('module:commands/scan-commands');
async function runScanArgs(context: KloCliCommandContext, args: KloScanArgs): Promise<void> {
const runner = context.deps.scan ?? (await import('../scan.js')).runKloScan;
async function runScanArgs(context: KtxCliCommandContext, args: KtxScanArgs): Promise<void> {
const runner = context.deps.scan ?? (await import('../scan.js')).runKtxScan;
context.setExitCode(await runner(args, context.io));
}
type KloScanModeOption = Extract<KloScanArgs, { command: 'run' }>['mode'];
type KtxScanModeOption = Extract<KtxScanArgs, { command: 'run' }>['mode'];
function parseScanModeOption(value: string): KloScanModeOption {
function parseScanModeOption(value: string): KtxScanModeOption {
if (value === 'structural' || value === 'enriched' || value === 'relationships') {
return value;
}
throw new InvalidArgumentError('Allowed choices are structural, enriched, relationships');
}
type KloRelationshipStatusOption = Extract<KloScanArgs, { command: 'relationships' }>['status'];
type KloRelationshipFeedbackDecisionOption = Extract<KloScanArgs, { command: 'relationshipFeedback' }>['decision'];
type KtxRelationshipStatusOption = Extract<KtxScanArgs, { command: 'relationships' }>['status'];
type KtxRelationshipFeedbackDecisionOption = Extract<KtxScanArgs, { command: 'relationshipFeedback' }>['decision'];
function parseRelationshipStatusOption(value: string): KloRelationshipStatusOption {
function parseRelationshipStatusOption(value: string): KtxRelationshipStatusOption {
if (value === 'accepted' || value === 'review' || value === 'rejected' || value === 'skipped' || value === 'all') {
return value;
}
throw new InvalidArgumentError('Allowed choices are accepted, review, rejected, skipped, all');
}
function parseRelationshipFeedbackDecisionOption(value: string): KloRelationshipFeedbackDecisionOption {
function parseRelationshipFeedbackDecisionOption(value: string): KtxRelationshipFeedbackDecisionOption {
if (value === 'accepted' || value === 'rejected' || value === 'all') {
return value;
}
@ -58,7 +58,7 @@ function relationshipDecisionArgs(options: {
note?: string;
json?: boolean;
}): Pick<
Extract<KloScanArgs, { command: 'relationshipDecision' }>,
Extract<KtxScanArgs, { command: 'relationshipDecision' }>,
'candidateId' | 'decision' | 'reviewer' | 'note' | 'json'
> | null {
const decisionCount = [options.accept !== undefined, options.reject !== undefined].filter(Boolean).length;
@ -69,7 +69,7 @@ function relationshipDecisionArgs(options: {
return {
candidateId: options.accept,
decision: 'accepted',
reviewer: options.reviewer ?? 'klo',
reviewer: options.reviewer ?? 'ktx',
note: options.note ?? null,
json: options.json === true,
};
@ -78,7 +78,7 @@ function relationshipDecisionArgs(options: {
return {
candidateId: options.reject,
decision: 'rejected',
reviewer: options.reviewer ?? 'klo',
reviewer: options.reviewer ?? 'ktx',
note: options.note ?? null,
json: options.json === true,
};
@ -90,11 +90,11 @@ function collectRelationshipCandidateOption(value: string, previous: string[]):
return [...previous, parseNonEmptyOption(value)];
}
export function registerScanCommands(program: Command, context: KloCliCommandContext): void {
export function registerScanCommands(program: Command, context: KtxCliCommandContext): void {
const scan = program
.command('scan')
.description('Run or inspect standalone connection scans')
.argument('[connectionId]', 'KLO connection id to scan')
.argument('[connectionId]', 'KTX connection id to scan')
.option(
'--mode <mode>',
'Scan mode: structural, enriched, relationships (default: structural)',
@ -105,7 +105,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n',
'\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n',
)
.hook('preAction', (_thisCommand, actionCommand) => {
context.writeDebug?.('scan', actionCommand);
@ -113,7 +113,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
.action(async (connectionId: string | undefined, options, command) => {
if (!connectionId) {
scan.outputHelp();
context.io.stderr.write('klo dev scan requires <connectionId> or a subcommand\n');
context.io.stderr.write('ktx dev scan requires <connectionId> or a subcommand\n');
context.setExitCode(1);
return;
}
@ -135,7 +135,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
.argument('<runId>', 'Local scan run id')
.addHelpText(
'after',
'\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n',
'\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n',
)
.action(async (runId: string, _options: unknown, command) => {
await runScanArgs(context, {
@ -152,7 +152,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
.option('--json', 'Print the raw scan report JSON', false)
.addHelpText(
'after',
'\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n',
'\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n',
)
.action(async (runId: string, options, command) => {
await runScanArgs(context, {
@ -189,7 +189,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
.option('--json', 'Print relationship artifacts as JSON', false)
.addHelpText(
'after',
'\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n',
'\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n',
)
.action(async (runId: string, options, command) => {
const decision = relationshipDecisionArgs(options);
@ -231,7 +231,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
.option('--json', 'Print the apply result as JSON', false)
.addHelpText(
'after',
'\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n',
'\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n',
)
.action(async (runId: string, options, command) => {
const parentOptions = command.parent?.opts() as { dryRun?: boolean } | undefined;
@ -249,7 +249,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
scan
.command('relationship-feedback')
.description('Export persisted relationship review decisions as calibration labels')
.option('--connection <connectionId>', 'Only export labels for one KLO connection')
.option('--connection <connectionId>', 'Only export labels for one KTX connection')
.option(
'--decision <decision>',
'Relationship feedback decision: accepted, rejected, all',
@ -260,7 +260,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
.addOption(new Option('--jsonl', 'Print labels as newline-delimited JSON').default(false).conflicts('json'))
.addHelpText(
'after',
'\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n',
'\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n',
)
.action(async (options, command) => {
await runScanArgs(context, {
@ -276,7 +276,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
scan
.command('relationship-calibration')
.description('Summarize relationship feedback labels against current score thresholds')
.option('--connection <connectionId>', 'Only calibrate labels for one KLO connection')
.option('--connection <connectionId>', 'Only calibrate labels for one KTX connection')
.option(
'--decision <decision>',
'Relationship feedback decision: accepted, rejected, all',
@ -298,7 +298,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
.option('--json', 'Print the calibration report as JSON', false)
.addHelpText(
'after',
'\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n',
'\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n',
)
.action(async (options, command) => {
await runScanArgs(context, {
@ -315,7 +315,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
scan
.command('relationship-thresholds')
.description('Evaluate relationship feedback labels for offline threshold advice')
.option('--connection <connectionId>', 'Only evaluate labels for one KLO connection')
.option('--connection <connectionId>', 'Only evaluate labels for one KTX connection')
.option(
'--min-total-labels <count>',
'Minimum scored labels before advice can be ready',
@ -337,7 +337,7 @@ export function registerScanCommands(program: Command, context: KloCliCommandCon
.option('--json', 'Print the threshold advice report as JSON', false)
.addHelpText(
'after',
'\n--project-dir is inherited from `klo dev scan` (default: KLO_PROJECT_DIR or current working directory).\n',
'\n--project-dir is inherited from `ktx dev scan` (default: KTX_PROJECT_DIR or current working directory).\n',
)
.action(async (options, command) => {
await runScanArgs(context, {

View file

@ -1,6 +1,6 @@
import { type Command, InvalidArgumentError } from '@commander-js/extra-typings';
import { type KloCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import type { KloServeArgs } from '../serve.js';
import { type KtxCliCommandContext, resolveCommandProjectDir } from '../cli-program.js';
import type { KtxServeArgs } from '../serve.js';
import { profileMark } from '../startup-profile.js';
profileMark('module:commands/serve-commands');
@ -12,10 +12,10 @@ function parseMcp(value: string): 'stdio' {
throw new InvalidArgumentError('Only stdio is supported in this phase');
}
export function registerServeCommands(program: Command, context: KloCliCommandContext): void {
export function registerServeCommands(program: Command, context: KtxCliCommandContext): void {
program
.command('serve')
.description('Run standalone KLO services such as MCP stdio')
.description('Run standalone KTX services such as MCP stdio')
.requiredOption('--mcp <mode>', 'MCP transport mode', parseMcp)
.option('--user-id <id>', 'Local user id', 'local')
.option('--semantic-compute', 'Enable semantic-layer compute', false)
@ -30,7 +30,7 @@ export function registerServeCommands(program: Command, context: KloCliCommandCo
if (options.executeQueries === true && !semanticCompute) {
throw new Error('--execute-queries requires --semantic-compute');
}
const args: KloServeArgs = {
const args: KtxServeArgs = {
mcp: options.mcp,
projectDir: resolveCommandProjectDir(command),
userId: options.userId,
@ -41,7 +41,7 @@ export function registerServeCommands(program: Command, context: KloCliCommandCo
memoryCapture: options.memoryCapture === true,
memoryModel: options.memoryModel,
};
const runner = context.deps.serveStdio ?? (await import('../serve.js')).runKloServeStdio;
const runner = context.deps.serveStdio ?? (await import('../serve.js')).runKtxServeStdio;
context.setExitCode(await runner(args));
});
}

View file

@ -1,15 +1,15 @@
import { type Command, InvalidArgumentError, Option } from '@commander-js/extra-typings';
import type { KloCliCommandContext } from '../cli-program.js';
import type { KtxCliCommandContext } from '../cli-program.js';
import { resolveCommandProjectDir } from '../cli-program.js';
import type { KloSetupDatabaseDriver } from '../setup-databases.js';
import type { KloSetupSourceType } from '../setup-sources.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: KloCliCommandContext,
context: KtxCliCommandContext,
args: Parameters<NonNullable<typeof context.deps.setup>>[0],
) {
const runner = context.deps.setup ?? (await import('../setup.js')).runKloSetup;
const runner = context.deps.setup ?? (await import('../setup.js')).runKtxSetup;
context.setExitCode(await runner(args, context.io));
}
@ -28,7 +28,7 @@ function embeddingBackend(value: string): 'openai' | 'sentence-transformers' {
throw new InvalidArgumentError(`invalid choice '${value}'`);
}
function databaseDriver(value: string): KloSetupDatabaseDriver {
function databaseDriver(value: string): KtxSetupDatabaseDriver {
if (
value === 'sqlite' ||
value === 'postgres' ||
@ -43,7 +43,7 @@ function databaseDriver(value: string): KloSetupDatabaseDriver {
throw new InvalidArgumentError(`invalid choice '${value}'`);
}
function sourceType(value: string): KloSetupSourceType {
function sourceType(value: string): KtxSetupSourceType {
if (
value === 'dbt' ||
value === 'metricflow' ||
@ -109,7 +109,7 @@ function shouldShowSetupEntryMenu(
embeddingApiKeyEnv?: string;
embeddingApiKeyFile?: string;
skipEmbeddings?: boolean;
database?: KloSetupDatabaseDriver[];
database?: KtxSetupDatabaseDriver[];
databaseConnectionId?: string[];
newDatabaseConnectionId?: string;
databaseUrl?: string;
@ -121,7 +121,7 @@ function shouldShowSetupEntryMenu(
historicSqlServiceAccountPattern?: string[];
historicSqlRedactionPattern?: string[];
skipDatabases?: boolean;
source?: KloSetupSourceType;
source?: KtxSetupSourceType;
sourceConnectionId?: string;
sourcePath?: string;
sourceGitUrl?: string;
@ -210,13 +210,13 @@ function shouldShowSetupEntryMenu(
].some((optionName) => optionWasSpecified(command, optionName));
}
export function registerSetupCommands(program: Command, context: KloCliCommandContext): void {
export function registerSetupCommands(program: Command, context: KtxCliCommandContext): void {
const setup = program
.command('setup')
.description('Set up or resume a local KLO project')
.option('--project-dir <path>', 'KLO project directory')
.option('--new', 'Create a new KLO project before setup', false)
.option('--existing', 'Use an existing KLO project', false)
.description('Set up or resume a local KTX project')
.option('--project-dir <path>', 'KTX project directory')
.option('--new', 'Create a new KTX project before setup', false)
.option('--existing', 'Use an existing KTX project', false)
.option('--agents', 'Install agent integration only', false)
.addOption(
new Option('--target <target>', 'Agent target').choices([
@ -247,10 +247,10 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo
.option(
'--database <driver>',
'Database driver to configure; repeatable',
(value, previous: KloSetupDatabaseDriver[]) => {
(value, previous: KtxSetupDatabaseDriver[]) => {
return [...previous, databaseDriver(value)];
},
[] as KloSetupDatabaseDriver[],
[] as KtxSetupDatabaseDriver[],
)
.option(
'--database-connection-id <id>',
@ -291,7 +291,7 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo
(value, previous: string[]) => [...previous, value],
[],
)
.option('--skip-databases', 'Leave database setup incomplete; KLO cannot work until a primary source is added', false)
.option('--skip-databases', 'Leave database setup incomplete; KTX cannot work until a primary source is added', false)
.addOption(new Option('--source <type>', 'Source connector type').argParser(sourceType))
.option('--source-connection-id <id>', 'Connection id for source setup')
.option('--source-path <path>', 'Local source path for dbt, MetricFlow, or LookML')
@ -421,9 +421,9 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo
});
});
registerDemoCommands(setup, context, { description: 'Run the packaged KLO demo from setup' });
registerDemoCommands(setup, context, { description: 'Run the packaged KTX demo from setup' });
const setupContext = setup.command('context').description('Build, inspect, and recover setup-managed KLO context');
const setupContext = setup.command('context').description('Build, inspect, and recover setup-managed KTX context');
function setupContextInputMode(command: {
optsWithGlobals?: () => unknown;
@ -435,7 +435,7 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo
setupContext
.command('build')
.description('Build agent-ready KLO context for setup')
.description('Build agent-ready KTX context for setup')
.option('--no-input', 'Disable interactive terminal input')
.action(async (options: { input?: boolean }, command) => {
await runSetupArgs(context, {
@ -505,7 +505,7 @@ export function registerSetupCommands(program: Command, context: KloCliCommandCo
setup
.command('status')
.description('Show setup readiness for the resolved KLO project')
.description('Show setup readiness for the resolved KTX project')
.option('--json', 'Print JSON output', false)
.action(async (options: { json?: boolean }, command) => {
await runSetupArgs(context, {

View file

@ -1,12 +1,12 @@
import { type Command, InvalidArgumentError, Option } from '@commander-js/extra-typings';
import {
collectOption,
type KloCliCommandContext,
type KtxCliCommandContext,
parsePositiveIntegerOption,
resolveCommandProjectDir,
} from '../cli-program.js';
import { slQueryCommandSchema } from '../command-schemas.js';
import type { KloSlArgs } from '../sl.js';
import type { KtxSlArgs } from '../sl.js';
import { profileMark } from '../startup-profile.js';
profileMark('module:commands/sl-commands');
@ -32,24 +32,24 @@ function collectOrderBy(
return [...previous, parseOrderBy(value)];
}
async function runSlArgs(context: KloCliCommandContext, args: KloSlArgs): Promise<void> {
const runner = context.deps.sl ?? (await import('../sl.js')).runKloSl;
async function runSlArgs(context: KtxCliCommandContext, args: KtxSlArgs): Promise<void> {
const runner = context.deps.sl ?? (await import('../sl.js')).runKtxSl;
context.setExitCode(await runner(args, context.io));
}
export function registerSlCommands(program: Command, context: KloCliCommandContext, commandName = 'sl'): void {
export function registerSlCommands(program: Command, context: KtxCliCommandContext, commandName = 'sl'): void {
const sl = program
.command(commandName)
.description('List, read, validate, query, or write local semantic-layer sources')
.showHelpAfterError()
.addHelpText(
'after',
'\nProject directory defaults to KLO_PROJECT_DIR when set, otherwise the current working directory.\n',
'\nProject directory defaults to KTX_PROJECT_DIR when set, otherwise the current working directory.\n',
);
sl.command('list')
.description('List semantic-layer sources')
.option('--connection-id <id>', 'KLO connection id')
.option('--connection-id <id>', 'KTX connection id')
.addOption(
new Option('--output <mode>', 'Output mode: pretty (default in TTY), plain (TSV), or json').choices([
'pretty',
@ -71,7 +71,7 @@ export function registerSlCommands(program: Command, context: KloCliCommandConte
sl.command('read')
.description('Read a semantic-layer source')
.argument('<sourceName>', 'Semantic-layer source name')
.requiredOption('--connection-id <id>', 'KLO connection id')
.requiredOption('--connection-id <id>', 'KTX connection id')
.action(async (sourceName: string, options: { connectionId: string }, command) => {
await runSlArgs(context, {
command: 'read',
@ -84,7 +84,7 @@ export function registerSlCommands(program: Command, context: KloCliCommandConte
sl.command('validate')
.description('Validate a semantic-layer source')
.argument('<sourceName>', 'Semantic-layer source name')
.requiredOption('--connection-id <id>', 'KLO connection id')
.requiredOption('--connection-id <id>', 'KTX connection id')
.action(async (sourceName: string, options: { connectionId: string }, command) => {
await runSlArgs(context, {
command: 'validate',
@ -97,7 +97,7 @@ export function registerSlCommands(program: Command, context: KloCliCommandConte
sl.command('write')
.description('Write a semantic-layer source')
.argument('<sourceName>', 'Semantic-layer source name')
.requiredOption('--connection-id <id>', 'KLO connection id')
.requiredOption('--connection-id <id>', 'KTX connection id')
.requiredOption('--yaml <yaml>', 'Semantic-layer source YAML')
.action(async (sourceName: string, options: { connectionId: string; yaml: string }, command) => {
await runSlArgs(context, {
@ -111,7 +111,7 @@ export function registerSlCommands(program: Command, context: KloCliCommandConte
sl.command('query')
.description('Compile or execute a semantic-layer query')
.option('--connection-id <id>', 'KLO connection id')
.option('--connection-id <id>', 'KTX connection id')
.option('--measure <measure>', 'Measure to query; repeatable', collectOption, [])
.option('--dimension <dimension>', 'Dimension to include; repeatable', collectOption, [])
.option('--filter <filter>', 'Filter expression; repeatable', collectOption, [])

View file

@ -1,14 +1,14 @@
import type { Command } from '@commander-js/extra-typings';
import type { KloCliCommandContext } from '../cli-program.js';
import type { KtxCliCommandContext } from '../cli-program.js';
import { resolveCommandProjectDir } from '../cli-program.js';
export function registerStatusCommands(program: Command, context: KloCliCommandContext): void {
export function registerStatusCommands(program: Command, context: KtxCliCommandContext): void {
program
.command('status')
.description('Show current KLO project setup status')
.description('Show current KTX project setup status')
.option('--json', 'Print JSON output', false)
.action(async (options: { json?: boolean }, command) => {
const runner = context.deps.setup ?? (await import('../setup.js')).runKloSetup;
const runner = context.deps.setup ?? (await import('../setup.js')).runKtxSetup;
context.setExitCode(
await runner(
{