mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-28 08:49:38 +02:00
rename klo to ktx
This commit is contained in:
parent
1a42152e6f
commit
3ce510b55b
704 changed files with 10205 additions and 10255 deletions
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>',
|
||||
|
|
|
|||
|
|
@ -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([]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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, [])
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue