mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
feat(cli): prefix text-input continuation lines with box-drawing characters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f091f948ee
commit
509f9f5301
7 changed files with 48 additions and 14 deletions
|
|
@ -28,12 +28,12 @@ describe('prompt navigation helpers', () => {
|
|||
'Name this PostgreSQL connection\nKTX will use this short name in commands and config. You can rename it now.',
|
||||
),
|
||||
).toBe(
|
||||
'Name this PostgreSQL connection\n\nKTX will use this short name in commands and config. You can rename it now.\nPress Escape to go back.\n',
|
||||
'Name this PostgreSQL connection\n│\n│ KTX will use this short name in commands and config. You can rename it now.\n│ Press Escape to go back.\n│',
|
||||
);
|
||||
});
|
||||
|
||||
it('adds a blank separator before compact text input values', () => {
|
||||
expect(withTextInputNavigation('Project folder path')).toBe('Project folder path\nPress Escape to go back.\n');
|
||||
expect(withTextInputNavigation('Project folder path')).toBe('Project folder path\n│ Press Escape to go back.\n│');
|
||||
});
|
||||
|
||||
it('normalizes already hinted text input prompts without duplicating the hint', () => {
|
||||
|
|
@ -42,7 +42,19 @@ describe('prompt navigation helpers', () => {
|
|||
'Name this PostgreSQL connection\nKTX will use this short name in commands and config. You can rename it now.\nPress Escape to go back.',
|
||||
),
|
||||
).toBe(
|
||||
'Name this PostgreSQL connection\n\nKTX will use this short name in commands and config. You can rename it now.\nPress Escape to go back.\n',
|
||||
'Name this PostgreSQL connection\n│\n│ KTX will use this short name in commands and config. You can rename it now.\n│ Press Escape to go back.\n│',
|
||||
);
|
||||
});
|
||||
|
||||
it('is idempotent when text input navigation is applied twice', () => {
|
||||
const once = withTextInputNavigation('Project folder path');
|
||||
expect(withTextInputNavigation(once)).toBe(once);
|
||||
});
|
||||
|
||||
it('is idempotent when text input navigation with body is applied twice', () => {
|
||||
const once = withTextInputNavigation(
|
||||
'Name this PostgreSQL connection\nKTX will use this short name in commands and config.',
|
||||
);
|
||||
expect(withTextInputNavigation(once)).toBe(once);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,26 @@ function removeTrailingBlankLines(message: string): string {
|
|||
return message.replace(/\n+$/, '');
|
||||
}
|
||||
|
||||
function prefixContinuationLines(message: string): string {
|
||||
const lines = message.split('\n');
|
||||
if (lines.length <= 1) return message;
|
||||
const [title, ...body] = lines;
|
||||
let trailingEmptyCount = 0;
|
||||
while (trailingEmptyCount < body.length && body[body.length - 1 - trailingEmptyCount] === '') {
|
||||
trailingEmptyCount++;
|
||||
}
|
||||
const contentBody = trailingEmptyCount > 0 ? body.slice(0, -trailingEmptyCount) : body;
|
||||
const trailingBody = trailingEmptyCount > 0 ? body.slice(-trailingEmptyCount) : [];
|
||||
return [
|
||||
title,
|
||||
...contentBody.map((line) => {
|
||||
const stripped = line.replace(/^│\s*/, '');
|
||||
return stripped === '' ? '│' : `│ ${stripped}`;
|
||||
}),
|
||||
...trailingBody,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function withTextInputBodySpacing(message: string): string {
|
||||
const normalized = removeTrailingBlankLines(message);
|
||||
if (!normalized.includes('\n')) {
|
||||
|
|
@ -39,7 +59,9 @@ export function withMultiselectNavigation(message: string): string {
|
|||
export function withTextInputNavigation(message: string): string {
|
||||
const messageWithoutHint = removeTrailingBlankLines(message)
|
||||
.split('\n')
|
||||
.filter((line) => line !== TEXT_INPUT_NAVIGATION_HINT)
|
||||
.filter((line) => !line.includes(TEXT_INPUT_NAVIGATION_HINT))
|
||||
.map((line) => line.replace(/^│\s*/, ''))
|
||||
.join('\n');
|
||||
return `${withTextInputBodySpacing(messageWithoutHint)}\n${TEXT_INPUT_NAVIGATION_HINT}\n`;
|
||||
const full = `${withTextInputBodySpacing(messageWithoutHint)}\n${TEXT_INPUT_NAVIGATION_HINT}`;
|
||||
return `${prefixContinuationLines(full)}\n│`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ function connectionNamePrompt(label: string): string {
|
|||
function textInputPrompt(message: string): string {
|
||||
const normalized = message.replace(/\n+$/, '');
|
||||
if (!normalized.includes('\n')) {
|
||||
return `${normalized}\nPress Escape to go back.\n`;
|
||||
return `${normalized}\n│ Press Escape to go back.\n│`;
|
||||
}
|
||||
const [title, ...bodyLines] = normalized.split('\n');
|
||||
return `${title}\n\n${bodyLines.join('\n')}\nPress Escape to go back.\n`;
|
||||
return `${title}\n│\n│ ${bodyLines.join('\n│ ')}\n│ Press Escape to go back.\n│`;
|
||||
}
|
||||
|
||||
const legacyHistoricSqlServiceAccountPatternsKey = ['serviceAccount', 'UserPatterns'].join('');
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ describe('setup Anthropic model step', () => {
|
|||
expect(result.status).toBe('ready');
|
||||
expect(prompts.select).not.toHaveBeenCalledWith(expect.objectContaining({ message: 'Paste Anthropic API key now?' }));
|
||||
expect(prompts.password).toHaveBeenCalledWith({
|
||||
message: 'Anthropic API key\nPress Escape to go back.\n',
|
||||
message: 'Anthropic API key\n│ Press Escape to go back.\n│',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -462,7 +462,7 @@ describe('setup Anthropic model step', () => {
|
|||
);
|
||||
expect(prompts.text).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Anthropic model ID\nPress Escape to go back.\n',
|
||||
message: 'Anthropic model ID\n│ Press Escape to go back.\n│',
|
||||
placeholder: 'claude-sonnet-4-6',
|
||||
}),
|
||||
);
|
||||
|
|
@ -626,7 +626,7 @@ describe('setup Anthropic model step', () => {
|
|||
|
||||
expect(result.status).toBe('ready');
|
||||
expect(prompts.password).toHaveBeenCalledWith({
|
||||
message: 'Anthropic API key\nPress Escape to go back.\n',
|
||||
message: 'Anthropic API key\n│ Press Escape to go back.\n│',
|
||||
});
|
||||
await expect(readFile(join(tempDir, '.ktx/secrets/anthropic-api-key'), 'utf-8')).rejects.toMatchObject({
|
||||
code: 'ENOENT',
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ describe('setup project step', () => {
|
|||
expect(result.projectDir).toBe(projectDir);
|
||||
expect(prompts.text).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Project folder path\nPress Escape to go back.\n',
|
||||
message: 'Project folder path\n│ Press Escape to go back.\n│',
|
||||
placeholder: './analytics-ktx, ~/analytics-ktx, or /Users/you/projects/analytics-ktx',
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -65,10 +65,10 @@ function connectionNamePrompt(label: string): string {
|
|||
function textInputPrompt(message: string): string {
|
||||
const normalized = message.replace(/\n+$/, '');
|
||||
if (!normalized.includes('\n')) {
|
||||
return `${normalized}\nPress Escape to go back.\n`;
|
||||
return `${normalized}\n│ Press Escape to go back.\n│`;
|
||||
}
|
||||
const [title, ...bodyLines] = normalized.split('\n');
|
||||
return `${title}\n\n${bodyLines.join('\n')}\nPress Escape to go back.\n`;
|
||||
return `${title}\n│\n│ ${bodyLines.join('\n│ ')}\n│ Press Escape to go back.\n│`;
|
||||
}
|
||||
|
||||
describe('setup sources step', () => {
|
||||
|
|
|
|||
|
|
@ -723,7 +723,7 @@ describe('setup status', () => {
|
|||
|
||||
expect(projectPrompts.text).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'Project folder path\nPress Escape to go back.\n',
|
||||
message: 'Project folder path\n│ Press Escape to go back.\n│',
|
||||
placeholder: './analytics-ktx, ~/analytics-ktx, or /Users/you/projects/analytics-ktx',
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue