mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
fix(cli): simplify setup flags and agents tty handling (#155)
* fix(cli): simplify setup flags and agents tty handling * fix(context): update ingest setup guidance flag
This commit is contained in:
parent
efda990de0
commit
590dd5dddb
20 changed files with 251 additions and 155 deletions
|
|
@ -67,7 +67,6 @@ Drive the existing wizard non-interactively (verify exact flag names with `ktx s
|
|||
```
|
||||
ktx setup \
|
||||
--project-dir <path> \
|
||||
--new \
|
||||
--no-input --yes \
|
||||
--llm-backend <claude-code|anthropic|vertex> --llm-model <model> \
|
||||
[--anthropic-api-key-env ANTHROPIC_API_KEY | --anthropic-api-key-file <path>] \
|
||||
|
|
@ -75,14 +74,22 @@ ktx setup \
|
|||
--embedding-backend <sentence-transformers|openai> \
|
||||
[--embedding-api-key-env OPENAI_API_KEY] \
|
||||
--skip-sources \
|
||||
--database <driver> --new-database-connection-id <name> --database-url <url|env:VAR|file:/path> \
|
||||
--database <driver> --database-connection-id <name> --database-url <url|env:VAR|file:/path> \
|
||||
[--database-schema <schema> …]
|
||||
# repeat the --database / --new-database-connection-id / --database-url / --database-schema block per connection
|
||||
```
|
||||
|
||||
Notes on the flags above:
|
||||
- **`--new`** is required when bootstrapping an empty directory; use `--existing` instead when re-running setup against a project that already has a `ktx.yaml`.
|
||||
- **There is no `--skip-agents` flag.** The agent integration step is opt-in: setup leaves it alone unless you pass `--agents --target <target>`. So you do not need to skip it — just don't pass `--agents`.
|
||||
- **Project creation is automatic with `--no-input --yes`.** When
|
||||
`ktx.yaml` exists, setup resumes it. When it doesn't exist, setup creates it
|
||||
at `--project-dir`.
|
||||
- **`--database-connection-id` is dual-purpose.** With `--database` or
|
||||
`--database-url`, it names the new connection. Without those flags, it
|
||||
selects an existing connection id.
|
||||
- **Configure one new database connection per setup command.** If the user
|
||||
wants multiple new connections, run setup again for each connection.
|
||||
- **You don't need `--skip-agents` in this flow.** The agent integration step
|
||||
is opt-in: setup leaves it alone unless you pass `--agents --target
|
||||
<target>`.
|
||||
- **`--skip-sources`** is correct and is the documented way to leave BI/metadata sources unconfigured.
|
||||
|
||||
### Known soft-failure: `ktx setup` exits 1 after a successful fast build
|
||||
|
|
@ -172,7 +179,7 @@ Verdict: ready
|
|||
|
||||
Then **Next steps** (copy-pasteable):
|
||||
1. Enrich with AI descriptions and embeddings: `ktx ingest <connection> --deep` (several minutes per connection).
|
||||
2. Add more connections later by rerunning this setup or via `ktx setup --existing --database … --new-database-connection-id …`.
|
||||
2. Add more connections later by rerunning this setup or via `ktx setup --database … --database-connection-id …`.
|
||||
3. Configure BI sources (dbt, Metabase, Looker, LookML, MetricFlow, Notion) — see `ktx setup --help` for `--source …` flags.
|
||||
4. Install agent integration: `ktx setup --agents --target <claude-code|claude-desktop|codex|cursor|opencode|universal>` (with optional `--global` for `claude-code`/`codex`).
|
||||
5. Connect the agent / MCP: see docs at `https://docs.kaelio.com/ktx/`.
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ below.
|
|||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `--agents` | Install agent configuration and rules only | `false` |
|
||||
| `--target <target>` | Agent target: `claude-code`, `codex`, `cursor`, `opencode`, or `universal` | - |
|
||||
| `--target <target>` | Agent target: `claude-code`, `claude-desktop`, `codex`, `cursor`, `opencode`, or `universal` | - |
|
||||
| `--global` | Install agent integration into the global target scope for `claude-code` or `codex` | `false` |
|
||||
| `--yes` | Accept safe defaults in non-interactive setup | `false` |
|
||||
| `--yes` | Accept project creation and runtime install defaults where setup asks for confirmation | `false` |
|
||||
| `--no-input` | Disable interactive terminal input | - |
|
||||
|
||||
Use the global `--project-dir <path>` option when setup should target a
|
||||
|
|
@ -40,12 +40,12 @@ specific directory.
|
|||
These flags are useful for repeatable setup in examples, tests, CI fixtures, and
|
||||
scripted project creation. They are not shown in `ktx setup --help`.
|
||||
|
||||
### Project Mode
|
||||
### Project Creation
|
||||
|
||||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `--new` | Create a new KTX project before setup | `false` |
|
||||
| `--existing` | Use an existing KTX project | `false` |
|
||||
Setup resumes an existing `ktx.yaml` when one is present. When no project
|
||||
exists, interactive setup prompts for where to create it. In scripts, pass
|
||||
`--project-dir <dir> --no-input --yes` to create the target directory without
|
||||
prompts.
|
||||
|
||||
### LLM Provider
|
||||
|
||||
|
|
@ -56,7 +56,6 @@ scripted project creation. They are not shown in `ktx setup --help`.
|
|||
| `--llm-model <model>` | LLM model ID or backend model alias to validate and save |
|
||||
| `--anthropic-api-key-env <name>` | Environment variable containing the Anthropic API key |
|
||||
| `--anthropic-api-key-file <path>` | File containing the Anthropic API key |
|
||||
| `--anthropic-model <model>` | Legacy alias for `--llm-model` |
|
||||
| `--vertex-project <project>` | Vertex AI project ID, `env:NAME`, or `file:/path` reference |
|
||||
| `--vertex-location <location>` | Vertex AI location, `env:NAME`, or `file:/path` reference |
|
||||
| `--skip-llm` | Leave LLM setup incomplete |
|
||||
|
|
@ -105,8 +104,7 @@ runtime features are missing.
|
|||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--database <driver>` | Database driver to configure; repeatable. Choices: `sqlite`, `postgres`, `mysql`, `clickhouse`, `sqlserver`, `bigquery`, `snowflake` |
|
||||
| `--database-connection-id <id>` | Existing selected connection id; repeatable |
|
||||
| `--new-database-connection-id <id>` | Connection id for one new database connection |
|
||||
| `--database-connection-id <id>` | Existing selected connection id; repeatable. With `--database` or `--database-url`, connection id for the new connection. |
|
||||
| `--database-url <url>` | URL, `env:NAME`, or `file:/path` for one new URL-style database connection; also used as the SQLite path |
|
||||
| `--database-schema <schema>` | Database schema or dataset to include; repeatable |
|
||||
| `--skip-databases` | Leave database setup incomplete |
|
||||
|
|
@ -177,10 +175,11 @@ ktx setup \
|
|||
ktx setup \
|
||||
--project-dir ./analytics \
|
||||
--no-input \
|
||||
--yes \
|
||||
--skip-llm \
|
||||
--skip-embeddings \
|
||||
--database postgres \
|
||||
--new-database-connection-id warehouse \
|
||||
--database-connection-id warehouse \
|
||||
--database-url env:DATABASE_URL \
|
||||
--database-schema public
|
||||
|
||||
|
|
@ -188,7 +187,7 @@ ktx setup \
|
|||
ktx setup \
|
||||
--project-dir ./analytics \
|
||||
--database postgres \
|
||||
--new-database-connection-id warehouse \
|
||||
--database-connection-id warehouse \
|
||||
--database-url env:DATABASE_URL \
|
||||
--enable-query-history \
|
||||
--query-history-min-executions 5
|
||||
|
|
@ -236,4 +235,5 @@ Use `ktx status` for repeatable readiness checks after setup exits.
|
|||
| `--enable-query-history` is rejected | The selected database driver does not support query history | Use Postgres, BigQuery, or Snowflake, or rerun without query-history flags |
|
||||
| Source setup rejects location flags | Both `--source-path` and `--source-git-url` were supplied | Choose the local path or the Git URL, not both |
|
||||
| Agent integration missing | Setup skipped the agents step | Run `ktx setup --agents --target <target>` |
|
||||
| Agent setup cannot prompt for a target | Non-TTY `ktx setup --agents` needs a target | Run `ktx setup --agents --target <target>` or rerun in a TTY |
|
||||
| Global agent install is rejected | `--global` was used with a target other than `claude-code` or `codex` | Omit `--global`, or choose `--target claude-code` or `--target codex` |
|
||||
|
|
|
|||
|
|
@ -216,10 +216,11 @@ For repeatable fixtures and automation, skip prompts with flags:
|
|||
ktx setup \
|
||||
--project-dir ./analytics \
|
||||
--no-input \
|
||||
--yes \
|
||||
--skip-llm \
|
||||
--skip-embeddings \
|
||||
--database postgres \
|
||||
--new-database-connection-id warehouse \
|
||||
--database-connection-id warehouse \
|
||||
--database-url env:DATABASE_URL \
|
||||
--database-schema public
|
||||
```
|
||||
|
|
|
|||
|
|
@ -85,8 +85,6 @@ function optionWasSpecified(command: Command, optionName: string): boolean {
|
|||
|
||||
function shouldShowSetupEntryMenu(
|
||||
options: {
|
||||
new?: boolean;
|
||||
existing?: boolean;
|
||||
agents?: boolean;
|
||||
target?: string;
|
||||
global?: boolean;
|
||||
|
|
@ -98,7 +96,6 @@ function shouldShowSetupEntryMenu(
|
|||
anthropicApiKeyEnv?: string;
|
||||
anthropicApiKeyFile?: string;
|
||||
llmModel?: string;
|
||||
anthropicModel?: string;
|
||||
vertexProject?: string;
|
||||
vertexLocation?: string;
|
||||
skipLlm?: boolean;
|
||||
|
|
@ -108,7 +105,6 @@ function shouldShowSetupEntryMenu(
|
|||
skipEmbeddings?: boolean;
|
||||
database?: KtxSetupDatabaseDriver[];
|
||||
databaseConnectionId?: string[];
|
||||
newDatabaseConnectionId?: string;
|
||||
databaseUrl?: string;
|
||||
databaseSchema?: string[];
|
||||
enableQueryHistory?: boolean;
|
||||
|
|
@ -160,8 +156,6 @@ function shouldShowSetupEntryMenu(
|
|||
}
|
||||
|
||||
return ![
|
||||
'new',
|
||||
'existing',
|
||||
'agents',
|
||||
'target',
|
||||
'global',
|
||||
|
|
@ -173,7 +167,6 @@ function shouldShowSetupEntryMenu(
|
|||
'anthropicApiKeyEnv',
|
||||
'anthropicApiKeyFile',
|
||||
'llmModel',
|
||||
'anthropicModel',
|
||||
'vertexProject',
|
||||
'vertexLocation',
|
||||
'skipLlm',
|
||||
|
|
@ -181,7 +174,6 @@ function shouldShowSetupEntryMenu(
|
|||
'embeddingApiKeyEnv',
|
||||
'embeddingApiKeyFile',
|
||||
'skipEmbeddings',
|
||||
'newDatabaseConnectionId',
|
||||
'databaseUrl',
|
||||
'enableQueryHistory',
|
||||
'disableQueryHistory',
|
||||
|
|
@ -214,8 +206,6 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
.command('setup')
|
||||
.description('Set up or resume a local KTX project')
|
||||
.addOption(new Option('--project-dir <path>', 'KTX project directory').hideHelp())
|
||||
.addOption(new Option('--new', 'Create a new KTX project before setup').hideHelp().default(false))
|
||||
.addOption(new Option('--existing', 'Use an existing KTX project').hideHelp().default(false))
|
||||
.option('--agents', 'Install agent integration only', false)
|
||||
.addOption(
|
||||
new Option('--target <target>', 'Agent target').choices([
|
||||
|
|
@ -230,7 +220,7 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
.option('--global', 'Install agent integration into the global target scope', false)
|
||||
.option('--local', 'Install Claude Code MCP config into the private per-project ~/.claude.json scope', false)
|
||||
.addOption(new Option('--skip-agents', 'Leave agent integration incomplete for now').hideHelp().default(false))
|
||||
.option('--yes', 'Accept safe defaults in non-interactive setup', false)
|
||||
.option('--yes', 'Accept project creation and runtime install defaults where setup confirms', false)
|
||||
.option('--no-input', 'Disable interactive terminal input')
|
||||
.addOption(new Option('--llm-backend <backend>', 'LLM backend').argParser(llmBackend).hideHelp())
|
||||
.addOption(
|
||||
|
|
@ -240,7 +230,6 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
new Option('--anthropic-api-key-file <path>', 'File containing the Anthropic API key').hideHelp(),
|
||||
)
|
||||
.addOption(new Option('--llm-model <model>', 'LLM model ID or backend model alias').hideHelp())
|
||||
.addOption(new Option('--anthropic-model <model>', 'Anthropic model ID to validate and save').hideHelp())
|
||||
.addOption(new Option('--vertex-project <project>', 'Google Vertex AI project ID, env:NAME, or file:/path').hideHelp())
|
||||
.addOption(new Option('--vertex-location <location>', 'Google Vertex AI location, env:NAME, or file:/path').hideHelp())
|
||||
.addOption(new Option('--skip-llm', 'Leave LLM setup incomplete for now').hideHelp().default(false))
|
||||
|
|
@ -269,16 +258,6 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
.default([] as string[])
|
||||
.hideHelp(),
|
||||
)
|
||||
.addOption(
|
||||
new Option('--new-database-connection-id <id>', 'Connection id for one new database connection')
|
||||
.argParser((value) => {
|
||||
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
|
||||
throw new InvalidArgumentError(`Unsafe connection id: ${value}`);
|
||||
}
|
||||
return value;
|
||||
})
|
||||
.hideHelp(),
|
||||
)
|
||||
.addOption(
|
||||
new Option('--database-url <url>', 'URL, env:NAME, or file:/path for one new URL-style database connection').hideHelp(),
|
||||
)
|
||||
|
|
@ -365,11 +344,6 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
context.setExitCode(1);
|
||||
return;
|
||||
}
|
||||
if (options.llmModel && options.anthropicModel) {
|
||||
context.io.stderr.write('Choose only one LLM model flag: --llm-model or --anthropic-model.\n');
|
||||
context.setExitCode(1);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
options.llmBackend &&
|
||||
options.llmBackend !== 'anthropic' &&
|
||||
|
|
@ -419,12 +393,18 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
return;
|
||||
}
|
||||
|
||||
const mode = options.new ? 'new' : options.existing ? 'existing' : 'auto';
|
||||
const creatingDatabaseConnection = options.database.length > 0 || options.databaseUrl !== undefined;
|
||||
if (creatingDatabaseConnection && options.databaseConnectionId.length > 1) {
|
||||
context.io.stderr.write('Choose only one new database connection id when configuring a database.\n');
|
||||
context.setExitCode(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedAgentScope = options.local ? 'local' : options.global ? 'global' : 'project';
|
||||
await runSetupArgs(context, {
|
||||
command: 'run',
|
||||
projectDir: resolveCommandProjectDir(command),
|
||||
mode,
|
||||
mode: 'auto',
|
||||
agents: options.agents === true,
|
||||
...(options.target ? { target: options.target } : {}),
|
||||
agentScope: resolvedAgentScope,
|
||||
|
|
@ -436,7 +416,6 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
...(options.anthropicApiKeyEnv ? { anthropicApiKeyEnv: options.anthropicApiKeyEnv } : {}),
|
||||
...(options.anthropicApiKeyFile ? { anthropicApiKeyFile: options.anthropicApiKeyFile } : {}),
|
||||
...(options.llmModel ? { llmModel: options.llmModel } : {}),
|
||||
...(options.anthropicModel ? { anthropicModel: options.anthropicModel } : {}),
|
||||
...(options.vertexProject ? { vertexProject: options.vertexProject } : {}),
|
||||
...(options.vertexLocation ? { vertexLocation: options.vertexLocation } : {}),
|
||||
skipLlm: options.skipLlm === true,
|
||||
|
|
@ -445,8 +424,12 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo
|
|||
...(options.embeddingApiKeyFile ? { embeddingApiKeyFile: options.embeddingApiKeyFile } : {}),
|
||||
skipEmbeddings: options.skipEmbeddings === true,
|
||||
...(options.database.length > 0 ? { databaseDrivers: options.database } : {}),
|
||||
...(options.databaseConnectionId.length > 0 ? { databaseConnectionIds: options.databaseConnectionId } : {}),
|
||||
...(options.newDatabaseConnectionId ? { databaseConnectionId: options.newDatabaseConnectionId } : {}),
|
||||
...(options.databaseConnectionId.length > 0 && creatingDatabaseConnection
|
||||
? { databaseConnectionId: options.databaseConnectionId[0] }
|
||||
: {}),
|
||||
...(options.databaseConnectionId.length > 0 && !creatingDatabaseConnection
|
||||
? { databaseConnectionIds: options.databaseConnectionId }
|
||||
: {}),
|
||||
...(options.databaseUrl ? { databaseUrl: options.databaseUrl } : {}),
|
||||
databaseSchemas: options.databaseSchema,
|
||||
...(options.enableQueryHistory ? { enableQueryHistory: true } : {}),
|
||||
|
|
|
|||
|
|
@ -470,8 +470,6 @@ describe('runKtxCli', () => {
|
|||
expect(stdout).not.toContain('setup context');
|
||||
|
||||
for (const hiddenFlag of [
|
||||
'--new',
|
||||
'--existing',
|
||||
'--agent-scope',
|
||||
'--skip-agents',
|
||||
'--llm-backend',
|
||||
|
|
@ -480,7 +478,6 @@ describe('runKtxCli', () => {
|
|||
'--embedding-backend',
|
||||
'--database ',
|
||||
'--database-connection-id',
|
||||
'--new-database-connection-id',
|
||||
'--enable-historic-sql',
|
||||
'--historic-sql-min-executions',
|
||||
'--enable-query-history',
|
||||
|
|
@ -842,8 +839,12 @@ describe('runKtxCli', () => {
|
|||
it('rejects removed setup options', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const cases = [
|
||||
['setup', '--new'],
|
||||
['setup', '--existing'],
|
||||
['setup', '--project'],
|
||||
['setup', '--agent-scope', 'global'],
|
||||
['setup', '--anthropic-model', 'claude-sonnet-4-6'],
|
||||
['setup', '--new-database-connection-id', 'warehouse'],
|
||||
['setup', '--skip-initial-source-ingest'],
|
||||
];
|
||||
|
||||
|
|
@ -1065,7 +1066,7 @@ describe('runKtxCli', () => {
|
|||
'--no-input',
|
||||
'--anthropic-api-key-env',
|
||||
'ANTHROPIC_API_KEY',
|
||||
'--anthropic-model',
|
||||
'--llm-model',
|
||||
'claude-sonnet-4-6',
|
||||
],
|
||||
setupIo.io,
|
||||
|
|
@ -1080,7 +1081,7 @@ describe('runKtxCli', () => {
|
|||
inputMode: 'disabled',
|
||||
cliVersion: '0.1.0-rc.1',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
}),
|
||||
setupIo.io,
|
||||
|
|
@ -1104,7 +1105,7 @@ describe('runKtxCli', () => {
|
|||
'local-gcp-project',
|
||||
'--vertex-location',
|
||||
'us-east5',
|
||||
'--anthropic-model',
|
||||
'--llm-model',
|
||||
'claude-sonnet-4-6',
|
||||
],
|
||||
setupIo.io,
|
||||
|
|
@ -1121,7 +1122,7 @@ describe('runKtxCli', () => {
|
|||
llmBackend: 'vertex',
|
||||
vertexProject: 'local-gcp-project',
|
||||
vertexLocation: 'us-east5',
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
}),
|
||||
setupIo.io,
|
||||
|
|
@ -1239,7 +1240,7 @@ describe('runKtxCli', () => {
|
|||
'--skip-embeddings',
|
||||
'--database',
|
||||
'postgres',
|
||||
'--new-database-connection-id',
|
||||
'--database-connection-id',
|
||||
'warehouse',
|
||||
'--database-url',
|
||||
'env:DATABASE_URL',
|
||||
|
|
@ -1283,18 +1284,41 @@ describe('runKtxCli', () => {
|
|||
const setup = vi.fn(async () => 0);
|
||||
|
||||
await expect(
|
||||
runKtxCli(['setup', '--new-database-connection-id', 'status', '--no-input'], testIo.io, { setup }),
|
||||
runKtxCli(['setup', '--database-connection-id', 'status', '--no-input'], testIo.io, { setup }),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(setup).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
command: 'run',
|
||||
databaseConnectionId: 'status',
|
||||
databaseConnectionIds: ['status'],
|
||||
}),
|
||||
testIo.io,
|
||||
);
|
||||
});
|
||||
|
||||
it('dispatches non-TTY agents setup with target without requiring --no-input or --yes', async () => {
|
||||
const testIo = makeIo({ stdoutIsTty: false });
|
||||
const setup = vi.fn(async () => 0);
|
||||
|
||||
await expect(
|
||||
runKtxCli(['--project-dir', tempDir, 'setup', '--agents', '--target', 'claude-code'], testIo.io, { setup }),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(setup).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
agents: true,
|
||||
target: 'claude-code',
|
||||
agentScope: 'project',
|
||||
inputMode: 'auto',
|
||||
yes: false,
|
||||
}),
|
||||
testIo.io,
|
||||
);
|
||||
expect(testIo.stderr()).toBe('');
|
||||
});
|
||||
|
||||
it('dispatches setup source flags', async () => {
|
||||
const setup = vi.fn(async () => 0);
|
||||
const testIo = makeIo();
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ describe('runKtxIngest', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
agentScope: 'project',
|
||||
skipAgents: true,
|
||||
|
|
@ -322,7 +322,7 @@ describe('runKtxIngest', () => {
|
|||
expect(runIo.stderr()).toContain('Configure a local Claude Code session or API-backed LLM, then rerun ingest:');
|
||||
expect(runIo.stderr()).toContain(`ktx setup --project-dir ${projectDir} --llm-backend claude-code --no-input`);
|
||||
expect(runIo.stderr()).toContain(
|
||||
`ktx setup --project-dir ${projectDir} --llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY --anthropic-model claude-sonnet-4-6 --no-input`,
|
||||
`ktx setup --project-dir ${projectDir} --llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY --llm-model claude-sonnet-4-6 --no-input`,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -184,6 +184,58 @@ describe('setup agents', () => {
|
|||
expect(io.stderr()).toBe('');
|
||||
});
|
||||
|
||||
it('installs a specified target in non-interactive mode without --yes', async () => {
|
||||
const io = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxSetupAgentsStep(
|
||||
{
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
yes: false,
|
||||
agents: true,
|
||||
target: 'claude-code',
|
||||
scope: 'project',
|
||||
mode: 'mcp',
|
||||
skipAgents: false,
|
||||
},
|
||||
io.io,
|
||||
),
|
||||
).resolves.toMatchObject({
|
||||
status: 'ready',
|
||||
projectDir: tempDir,
|
||||
installs: [{ target: 'claude-code', scope: 'project', mode: 'mcp' }],
|
||||
});
|
||||
|
||||
await expect(stat(join(tempDir, '.claude/skills/ktx-analytics/SKILL.md'))).resolves.toBeDefined();
|
||||
const mcpConfig = JSON.parse(await readFile(join(tempDir, '.mcp.json'), 'utf-8')) as {
|
||||
mcpServers?: Record<string, unknown>;
|
||||
};
|
||||
expect(mcpConfig.mcpServers).toHaveProperty('ktx');
|
||||
expect(io.stderr()).toBe('');
|
||||
});
|
||||
|
||||
it('prints concrete target guidance when non-interactive agent setup has no target', async () => {
|
||||
const io = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxSetupAgentsStep(
|
||||
{
|
||||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
yes: false,
|
||||
agents: true,
|
||||
scope: 'project',
|
||||
mode: 'mcp',
|
||||
skipAgents: false,
|
||||
},
|
||||
io.io,
|
||||
),
|
||||
).resolves.toEqual({ status: 'missing-input', projectDir: tempDir });
|
||||
|
||||
expect(io.stderr()).toBe('Run in a TTY, or pass --target <target>.\n');
|
||||
});
|
||||
|
||||
it('prints standalone agent next actions after successful installation', async () => {
|
||||
const io = makeIo();
|
||||
|
||||
|
|
|
|||
|
|
@ -1222,7 +1222,11 @@ export async function runKtxSetupAgentsStep(
|
|||
})) as KtxAgentTarget[]);
|
||||
if (targets.includes('back' as KtxAgentTarget)) return { status: 'back', projectDir: args.projectDir };
|
||||
if (targets.length === 0) {
|
||||
io.stderr.write('Missing agent target: pass --target or use interactive setup.\n');
|
||||
io.stderr.write(
|
||||
args.inputMode === 'disabled'
|
||||
? 'Run in a TTY, or pass --target <target>.\n'
|
||||
: 'Missing agent target: pass --target or use interactive setup.\n',
|
||||
);
|
||||
return { status: 'missing-input', projectDir: args.projectDir };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ describe('setup Anthropic model step', () => {
|
|||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
},
|
||||
io.io,
|
||||
|
|
@ -351,7 +351,7 @@ describe('setup Anthropic model step', () => {
|
|||
llmBackend: 'vertex',
|
||||
vertexProject: 'local-gcp-project',
|
||||
vertexLocation: 'us-east5',
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
},
|
||||
io.io,
|
||||
|
|
@ -658,7 +658,7 @@ describe('setup Anthropic model step', () => {
|
|||
llmBackend: 'vertex',
|
||||
vertexProject: 'kaelio-orbit-looker-20260430',
|
||||
vertexLocation: 'us-east5',
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
},
|
||||
io.io,
|
||||
|
|
@ -686,7 +686,7 @@ describe('setup Anthropic model step', () => {
|
|||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
anthropicApiKeyFile: secretPath,
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
},
|
||||
io.io,
|
||||
|
|
@ -723,7 +723,7 @@ describe('setup Anthropic model step', () => {
|
|||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
anthropicApiKeyFile: missingSecretPath,
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
},
|
||||
io.io,
|
||||
|
|
@ -1014,7 +1014,7 @@ describe('setup Anthropic model step', () => {
|
|||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
},
|
||||
io.io,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ export interface KtxSetupModelArgs {
|
|||
anthropicApiKeyEnv?: string;
|
||||
anthropicApiKeyFile?: string;
|
||||
llmModel?: string;
|
||||
anthropicModel?: string;
|
||||
vertexProject?: string;
|
||||
vertexLocation?: string;
|
||||
forcePrompt?: boolean;
|
||||
|
|
@ -478,14 +477,14 @@ function requestedBackend(args: KtxSetupModelArgs): KtxSetupLlmBackend | undefin
|
|||
if (args.vertexProject || args.vertexLocation) {
|
||||
return 'vertex';
|
||||
}
|
||||
if (args.anthropicApiKeyEnv || args.anthropicApiKeyFile || args.llmModel || args.anthropicModel) {
|
||||
if (args.anthropicApiKeyEnv || args.anthropicApiKeyFile || args.llmModel) {
|
||||
return 'anthropic';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function requestedModel(args: KtxSetupModelArgs): string | undefined {
|
||||
return args.llmModel ?? args.anthropicModel;
|
||||
return args.llmModel;
|
||||
}
|
||||
|
||||
async function chooseBackend(
|
||||
|
|
@ -929,7 +928,6 @@ export async function runKtxSetupAnthropicModelStep(
|
|||
!args.anthropicApiKeyEnv &&
|
||||
!args.anthropicApiKeyFile &&
|
||||
!args.llmModel &&
|
||||
!args.anthropicModel &&
|
||||
!args.vertexProject &&
|
||||
!args.vertexLocation
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -55,12 +55,12 @@ describe('setup project step', () => {
|
|||
await rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('creates a new project with --new and marks the project step complete', async () => {
|
||||
it('creates a new project in non-interactive auto mode with --yes and marks the project step complete', async () => {
|
||||
const projectDir = join(tempDir, 'warehouse');
|
||||
const testIo = makeIo();
|
||||
|
||||
const result = await runKtxSetupProjectStep(
|
||||
{ projectDir, mode: 'new', inputMode: 'disabled', yes: false },
|
||||
{ projectDir, mode: 'auto', inputMode: 'disabled', yes: true },
|
||||
testIo.io,
|
||||
);
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ describe('setup project step', () => {
|
|||
expect(testIo.stderr()).toBe('');
|
||||
});
|
||||
|
||||
it('loads an existing project with --existing and drops config setup progress', async () => {
|
||||
it('loads an existing project in auto mode and drops config setup progress', async () => {
|
||||
const projectDir = join(tempDir, 'warehouse');
|
||||
await initKtxProject({ projectDir });
|
||||
await writeFile(
|
||||
|
|
@ -91,7 +91,7 @@ describe('setup project step', () => {
|
|||
);
|
||||
|
||||
const result = await runKtxSetupProjectStep(
|
||||
{ projectDir, mode: 'existing', inputMode: 'disabled', yes: false },
|
||||
{ projectDir, mode: 'auto', inputMode: 'disabled', yes: false },
|
||||
makeIo().io,
|
||||
);
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ describe('setup project step', () => {
|
|||
await expect(
|
||||
runKtxSetupProjectStep({ projectDir, mode: 'auto', inputMode: 'disabled', yes: false }, rejectedIo.io),
|
||||
).resolves.toMatchObject({ status: 'missing-input' });
|
||||
expect(rejectedIo.stderr()).toContain('Missing setup choice: pass --new or --yes');
|
||||
expect(rejectedIo.stderr()).toContain('Missing setup choice: pass --yes');
|
||||
await expect(stat(join(projectDir, 'ktx.yaml'))).rejects.toThrow();
|
||||
|
||||
await expect(
|
||||
|
|
@ -121,15 +121,15 @@ describe('setup project step', () => {
|
|||
await expect(stat(join(projectDir, 'ktx.yaml'))).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it('fails --existing clearly when ktx.yaml is missing', async () => {
|
||||
it('fails clearly in no-input auto mode when ktx.yaml is missing and --yes is absent', async () => {
|
||||
const projectDir = join(tempDir, 'warehouse');
|
||||
const testIo = makeIo();
|
||||
|
||||
await expect(
|
||||
runKtxSetupProjectStep({ projectDir, mode: 'existing', inputMode: 'disabled', yes: false }, testIo.io),
|
||||
runKtxSetupProjectStep({ projectDir, mode: 'auto', inputMode: 'disabled', yes: false }, testIo.io),
|
||||
).resolves.toMatchObject({ status: 'missing-input' });
|
||||
|
||||
expect(testIo.stderr()).toContain(`No existing KTX project found at ${projectDir}`);
|
||||
expect(testIo.stderr()).toContain('Missing setup choice: pass --yes');
|
||||
});
|
||||
|
||||
it('prompts to use the current directory and creates a project in interactive auto mode', async () => {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
type KtxSetupPromptOption,
|
||||
} from './setup-prompts.js';
|
||||
|
||||
export type KtxSetupProjectMode = 'auto' | 'new' | 'existing' | 'prompt-new';
|
||||
export type KtxSetupProjectMode = 'auto' | 'prompt-new';
|
||||
export type KtxSetupInputMode = 'auto' | 'disabled';
|
||||
|
||||
export interface KtxSetupProjectArgs {
|
||||
|
|
@ -283,35 +283,14 @@ export async function runKtxSetupProjectStep(
|
|||
const homeDir = deps.homeDir ?? homedir();
|
||||
const exists = hasProjectConfig(projectDir);
|
||||
|
||||
if (args.mode === 'existing') {
|
||||
if (!exists) {
|
||||
io.stderr.write(`No existing KTX project found at ${projectDir}. Pass --new to create it.\n`);
|
||||
return { status: 'missing-input', projectDir };
|
||||
}
|
||||
const project = await loadExistingProject(projectDir, deps);
|
||||
printProjectSummary(io, projectDir);
|
||||
return { status: 'ready', projectDir, project };
|
||||
}
|
||||
|
||||
if (args.mode === 'new') {
|
||||
const { project, createdProjectCleanup } = await createProjectWithCleanup(projectDir, deps);
|
||||
printProjectSummary(io, projectDir);
|
||||
return {
|
||||
status: 'ready',
|
||||
projectDir,
|
||||
project,
|
||||
...(createdProjectCleanup ? { createdProjectCleanup } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
if (args.mode === 'prompt-new') {
|
||||
if (args.inputMode === 'disabled') {
|
||||
io.stderr.write('Missing new project folder: pass --new --project-dir to create a project without prompts.\n');
|
||||
io.stderr.write('Missing new project folder: pass --project-dir and --yes to create a project without prompts.\n');
|
||||
return { status: 'missing-input', projectDir };
|
||||
}
|
||||
if (!io.stdout.isTTY && !deps.prompts) {
|
||||
io.stderr.write(
|
||||
'Missing new project folder: pass --new --project-dir to create a project outside an interactive terminal.\n',
|
||||
'Missing new project folder: pass --project-dir and --yes to create a project outside an interactive terminal.\n',
|
||||
);
|
||||
return { status: 'missing-input', projectDir };
|
||||
}
|
||||
|
|
@ -344,7 +323,7 @@ export async function runKtxSetupProjectStep(
|
|||
|
||||
if (args.inputMode === 'disabled') {
|
||||
if (!args.yes) {
|
||||
io.stderr.write('Missing setup choice: pass --new or --yes to create a project in non-interactive setup.\n');
|
||||
io.stderr.write('Missing setup choice: pass --yes to create a project in non-interactive setup.\n');
|
||||
return { status: 'missing-input', projectDir };
|
||||
}
|
||||
const { project, createdProjectCleanup } = await createProjectWithCleanup(projectDir, deps);
|
||||
|
|
@ -358,7 +337,7 @@ export async function runKtxSetupProjectStep(
|
|||
}
|
||||
|
||||
if (!io.stdout.isTTY && !deps.prompts) {
|
||||
io.stderr.write('Missing setup choice: pass --new or --yes to create a project outside an interactive terminal.\n');
|
||||
io.stderr.write('Missing setup choice: pass --yes to create a project outside an interactive terminal.\n');
|
||||
return { status: 'missing-input', projectDir };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -480,7 +480,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
target: 'claude-code',
|
||||
skipAgents: false,
|
||||
|
|
@ -1053,7 +1053,7 @@ describe('setup status', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('creates a project through run mode when --new is selected', async () => {
|
||||
it('creates a project through run mode when --yes is selected', async () => {
|
||||
const testIo = makeIo();
|
||||
|
||||
await expect(
|
||||
|
|
@ -1061,11 +1061,11 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'disabled',
|
||||
yes: false,
|
||||
yes: true,
|
||||
cliVersion: '0.2.0',
|
||||
skipLlm: true,
|
||||
skipEmbeddings: true,
|
||||
|
|
@ -1153,14 +1153,14 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'disabled',
|
||||
yes: false,
|
||||
yes: true,
|
||||
cliVersion: '0.2.0',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
skipEmbeddings: true,
|
||||
databaseSchemas: [],
|
||||
|
|
@ -1177,7 +1177,7 @@ describe('setup status', () => {
|
|||
projectDir: tempDir,
|
||||
inputMode: 'disabled',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
}),
|
||||
testIo.io,
|
||||
|
|
@ -1193,16 +1193,16 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'disabled',
|
||||
yes: false,
|
||||
yes: true,
|
||||
cliVersion: '0.2.0',
|
||||
llmBackend: 'vertex',
|
||||
vertexProject: 'local-gcp-project',
|
||||
vertexLocation: 'us-east5',
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
skipEmbeddings: true,
|
||||
databaseSchemas: [],
|
||||
|
|
@ -1221,7 +1221,7 @@ describe('setup status', () => {
|
|||
llmBackend: 'vertex',
|
||||
vertexProject: 'local-gcp-project',
|
||||
vertexLocation: 'us-east5',
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
}),
|
||||
testIo.io,
|
||||
|
|
@ -1238,14 +1238,14 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'disabled',
|
||||
yes: true,
|
||||
cliVersion: '0.2.0',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
embeddingBackend: 'openai',
|
||||
embeddingApiKeyEnv: 'OPENAI_API_KEY', // pragma: allowlist secret
|
||||
|
|
@ -1276,13 +1276,14 @@ describe('setup status', () => {
|
|||
it('passes no-input runtime policy to the embeddings step', async () => {
|
||||
const io = makeIo();
|
||||
const embeddings = vi.fn(async () => ({ status: 'failed' as const, projectDir: tempDir }));
|
||||
await writeFile(join(tempDir, 'ktx.yaml'), 'connections: {}\n', 'utf-8');
|
||||
|
||||
await expect(
|
||||
runKtxSetup(
|
||||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
agentScope: 'project',
|
||||
skipAgents: true,
|
||||
|
|
@ -1313,13 +1314,14 @@ describe('setup status', () => {
|
|||
const io = makeIo();
|
||||
const embeddings = vi.fn(async () => ({ status: 'ready' as const, projectDir: tempDir }));
|
||||
const context = vi.fn(async () => ({ status: 'failed' as const, projectDir: tempDir }));
|
||||
await writeFile(join(tempDir, 'ktx.yaml'), 'connections: {}\n', 'utf-8');
|
||||
|
||||
await expect(
|
||||
runKtxSetup(
|
||||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
agentScope: 'project',
|
||||
skipAgents: true,
|
||||
|
|
@ -1358,6 +1360,7 @@ describe('setup status', () => {
|
|||
|
||||
it('lets Back from embedding setup return to the model step instead of exiting', async () => {
|
||||
const testIo = makeIo();
|
||||
await writeFile(join(tempDir, 'ktx.yaml'), 'connections: {}\n', 'utf-8');
|
||||
const modelResults = [
|
||||
{ status: 'ready' as const, projectDir: tempDir },
|
||||
{ status: 'back' as const, projectDir: tempDir },
|
||||
|
|
@ -1370,7 +1373,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'auto',
|
||||
|
|
@ -1394,6 +1397,7 @@ describe('setup status', () => {
|
|||
|
||||
it('lets Back from database selection return to embedding setup', async () => {
|
||||
const testIo = makeIo();
|
||||
await writeFile(join(tempDir, 'ktx.yaml'), 'connections: {}\n', 'utf-8');
|
||||
const modelResults = [
|
||||
{ status: 'ready' as const, projectDir: tempDir },
|
||||
{ status: 'back' as const, projectDir: tempDir },
|
||||
|
|
@ -1417,11 +1421,11 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'auto',
|
||||
yes: false,
|
||||
yes: true,
|
||||
cliVersion: '0.2.0',
|
||||
skipLlm: false,
|
||||
skipEmbeddings: false,
|
||||
|
|
@ -1464,7 +1468,7 @@ describe('setup status', () => {
|
|||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'auto',
|
||||
yes: false,
|
||||
yes: true,
|
||||
cliVersion: '0.2.0',
|
||||
skipLlm: false,
|
||||
skipEmbeddings: true,
|
||||
|
|
@ -1501,14 +1505,14 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'disabled',
|
||||
yes: false,
|
||||
yes: true,
|
||||
cliVersion: '0.2.0',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
embeddingBackend: 'openai',
|
||||
embeddingApiKeyEnv: 'OPENAI_API_KEY', // pragma: allowlist secret
|
||||
|
|
@ -1559,7 +1563,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'existing',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'disabled',
|
||||
|
|
@ -1635,7 +1639,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'existing',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'disabled',
|
||||
|
|
@ -1685,7 +1689,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'existing',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'disabled',
|
||||
|
|
@ -1733,7 +1737,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'existing',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
inputMode: 'disabled',
|
||||
yes: true,
|
||||
|
|
@ -1794,7 +1798,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
inputMode: 'disabled',
|
||||
yes: true,
|
||||
|
|
@ -1851,7 +1855,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'existing',
|
||||
mode: 'auto',
|
||||
agents: true,
|
||||
target: 'codex',
|
||||
agentScope: 'project',
|
||||
|
|
@ -1910,7 +1914,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'existing',
|
||||
mode: 'auto',
|
||||
agents: true,
|
||||
target: 'codex',
|
||||
agentScope: 'project',
|
||||
|
|
@ -1939,6 +1943,53 @@ describe('setup status', () => {
|
|||
expect(io.stderr()).not.toContain('KTX context is not ready for agents.');
|
||||
});
|
||||
|
||||
it('runs non-TTY --agents with a target without requiring --no-input or --yes', async () => {
|
||||
const io = makeIo();
|
||||
const agents = vi.fn(async () => ({
|
||||
status: 'ready' as const,
|
||||
projectDir: tempDir,
|
||||
installs: [{ target: 'claude-code' as const, scope: 'project' as const, mode: 'mcp' as const }],
|
||||
}));
|
||||
await writeFile(join(tempDir, 'ktx.yaml'), ['connections: {}', ''].join('\n'), 'utf-8');
|
||||
|
||||
await expect(
|
||||
runKtxSetup(
|
||||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'auto',
|
||||
agents: true,
|
||||
target: 'claude-code',
|
||||
agentScope: 'project',
|
||||
inputMode: 'auto',
|
||||
yes: false,
|
||||
cliVersion: '0.2.0',
|
||||
skipLlm: false,
|
||||
skipEmbeddings: false,
|
||||
skipDatabases: false,
|
||||
skipSources: false,
|
||||
skipAgents: false,
|
||||
databaseSchemas: [],
|
||||
},
|
||||
io.io,
|
||||
{ agents },
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(agents).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
inputMode: 'disabled',
|
||||
yes: false,
|
||||
agents: true,
|
||||
target: 'claude-code',
|
||||
scope: 'project',
|
||||
mode: 'mcp',
|
||||
}),
|
||||
io.io,
|
||||
);
|
||||
expect(io.stderr()).not.toContain('Interactive setup requires a terminal');
|
||||
});
|
||||
|
||||
it('routes a ready project menu selection to agent setup', async () => {
|
||||
const calls: string[] = [];
|
||||
const io = makeIo();
|
||||
|
|
@ -2003,7 +2054,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'existing',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
inputMode: 'auto',
|
||||
yes: false,
|
||||
|
|
@ -2106,7 +2157,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'existing',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
inputMode: 'auto',
|
||||
yes: false,
|
||||
|
|
@ -2172,7 +2223,7 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: true,
|
||||
target: 'universal',
|
||||
agentScope: 'project',
|
||||
|
|
@ -2213,14 +2264,14 @@ describe('setup status', () => {
|
|||
{
|
||||
command: 'run',
|
||||
projectDir: tempDir,
|
||||
mode: 'new',
|
||||
mode: 'auto',
|
||||
agents: false,
|
||||
skipAgents: true,
|
||||
inputMode: 'disabled',
|
||||
yes: false,
|
||||
yes: true,
|
||||
cliVersion: '0.2.0',
|
||||
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
||||
anthropicModel: 'claude-sonnet-4-6',
|
||||
llmModel: 'claude-sonnet-4-6',
|
||||
skipLlm: false,
|
||||
skipEmbeddings: false,
|
||||
databaseSchemas: [],
|
||||
|
|
@ -2231,6 +2282,7 @@ describe('setup status', () => {
|
|||
),
|
||||
).resolves.toBe(1);
|
||||
|
||||
expect(model).toHaveBeenCalledTimes(1);
|
||||
expect(embeddings).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export type KtxSetupArgs =
|
|||
| {
|
||||
command: 'run';
|
||||
projectDir: string;
|
||||
mode: 'auto' | 'new' | 'existing';
|
||||
mode: 'auto';
|
||||
agents: boolean;
|
||||
target?: KtxAgentTarget;
|
||||
agentScope?: KtxAgentScope;
|
||||
|
|
@ -93,7 +93,6 @@ export type KtxSetupArgs =
|
|||
anthropicApiKeyEnv?: string;
|
||||
anthropicApiKeyFile?: string;
|
||||
llmModel?: string;
|
||||
anthropicModel?: string;
|
||||
vertexProject?: string;
|
||||
vertexLocation?: string;
|
||||
skipLlm: boolean;
|
||||
|
|
@ -655,7 +654,6 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
|
|||
...(args.anthropicApiKeyEnv ? { anthropicApiKeyEnv: args.anthropicApiKeyEnv } : {}),
|
||||
...(args.anthropicApiKeyFile ? { anthropicApiKeyFile: args.anthropicApiKeyFile } : {}),
|
||||
...(args.llmModel ? { llmModel: args.llmModel } : {}),
|
||||
...(args.anthropicModel ? { anthropicModel: args.anthropicModel } : {}),
|
||||
...(args.vertexProject ? { vertexProject: args.vertexProject } : {}),
|
||||
...(args.vertexLocation ? { vertexLocation: args.vertexLocation } : {}),
|
||||
forcePrompt: forcePromptSteps.has('models') || runOnly === 'models',
|
||||
|
|
@ -776,7 +774,10 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
|
|||
const agentResult = await agentsRunner(
|
||||
{
|
||||
projectDir: projectResult.projectDir,
|
||||
inputMode: args.inputMode,
|
||||
inputMode:
|
||||
args.inputMode === 'auto' && io.stdout.isTTY !== true && deps.agentsDeps?.prompts === undefined
|
||||
? 'disabled'
|
||||
: args.inputMode,
|
||||
yes: args.yes,
|
||||
agents: true,
|
||||
...(args.target ? { target: args.target } : {}),
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ async function runSetupNewProject(projectDir: string): Promise<CliResult> {
|
|||
'setup',
|
||||
'--project-dir',
|
||||
projectDir,
|
||||
'--new',
|
||||
'--no-input',
|
||||
'--yes',
|
||||
'--skip-llm',
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ describe('createLocalBundleIngestRuntime', () => {
|
|||
'ktx ingest requires llm.provider.backend: anthropic, vertex, gateway, or claude-code, or an injected agentRunner.',
|
||||
'Configure a local Claude Code session or API-backed LLM, then rerun ingest:',
|
||||
` ktx setup --project-dir ${project.projectDir} --llm-backend claude-code --no-input`,
|
||||
` ktx setup --project-dir ${project.projectDir} --llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY --anthropic-model claude-sonnet-4-6 --no-input`,
|
||||
` ktx setup --project-dir ${project.projectDir} --llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY --llm-model claude-sonnet-4-6 --no-input`,
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -629,7 +629,7 @@ function localIngestLlmProviderGuardMessage(projectDir: string): string {
|
|||
'ktx ingest requires llm.provider.backend: anthropic, vertex, gateway, or claude-code, or an injected agentRunner.',
|
||||
'Configure a local Claude Code session or API-backed LLM, then rerun ingest:',
|
||||
` ktx setup --project-dir ${projectDir} --llm-backend claude-code --no-input`,
|
||||
` ktx setup --project-dir ${projectDir} --llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY --anthropic-model claude-sonnet-4-6 --no-input`,
|
||||
` ktx setup --project-dir ${projectDir} --llm-backend anthropic --anthropic-api-key-env ANTHROPIC_API_KEY --llm-model claude-sonnet-4-6 --no-input`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ export function localEmbeddingsSmokeCommands(input) {
|
|||
'setup',
|
||||
'--project-dir',
|
||||
input.projectDir,
|
||||
'--new',
|
||||
'--no-input',
|
||||
'--yes',
|
||||
'--skip-llm',
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ describe('localEmbeddingsSmokeCommands', () => {
|
|||
'setup',
|
||||
'--project-dir',
|
||||
'/tmp/ktx-local-embedding-smoke/project',
|
||||
'--new',
|
||||
'--no-input',
|
||||
'--yes',
|
||||
'--skip-llm',
|
||||
|
|
|
|||
|
|
@ -620,7 +620,6 @@ try {
|
|||
'setup',
|
||||
'--project-dir',
|
||||
projectDir,
|
||||
'--new',
|
||||
'--no-input',
|
||||
'--yes',
|
||||
'--skip-llm',
|
||||
|
|
@ -638,7 +637,6 @@ try {
|
|||
'setup',
|
||||
'--project-dir',
|
||||
emptyProjectDir,
|
||||
'--new',
|
||||
'--no-input',
|
||||
'--yes',
|
||||
'--skip-llm',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue