mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-01 08:59:39 +02:00
feat(cli): unify wiki and sl search output with clack-style box and score badge (#89)
Routes `ktx wiki list` and `ktx wiki search` through the shared printList()
renderer so all four list/search commands now produce the same Clack-style
pretty box, TSV plain output, and JSON envelope. Adds a `--output` flag to
the wiki commands mirroring sl, and surfaces relevance score as a leading
dim badge ("87%") in pretty mode and a `score=` prefix in plain mode for
both wiki search and sl search. Empty results now emit a consistent
actionable hint across commands.
This commit is contained in:
parent
52dd89481c
commit
77cce79237
5 changed files with 440 additions and 86 deletions
|
|
@ -6,17 +6,28 @@ import {
|
|||
import { loadKtxProject } from '@ktx/context/project';
|
||||
import {
|
||||
type LocalKnowledgeScope,
|
||||
type LocalKnowledgeSearchResult,
|
||||
type LocalKnowledgeSummary,
|
||||
listLocalKnowledgePages,
|
||||
readLocalKnowledgePage,
|
||||
searchLocalKnowledgePages,
|
||||
writeLocalKnowledgePage,
|
||||
} from '@ktx/context/wiki';
|
||||
import { writeJsonResult } from './io/print-list.js';
|
||||
import { resolveOutputMode } from './io/mode.js';
|
||||
import { printList, type PrintListColumn, writeJsonResult } from './io/print-list.js';
|
||||
|
||||
export type KtxKnowledgeArgs =
|
||||
| { command: 'list'; projectDir: string; userId: string; json?: boolean }
|
||||
| { command: 'list'; projectDir: string; userId: string; output?: string; json?: boolean }
|
||||
| { command: 'read'; projectDir: string; key: string; userId: string; json?: boolean }
|
||||
| { command: 'search'; projectDir: string; query: string; userId: string; json?: boolean; limit?: number }
|
||||
| {
|
||||
command: 'search';
|
||||
projectDir: string;
|
||||
query: string;
|
||||
userId: string;
|
||||
output?: string;
|
||||
json?: boolean;
|
||||
limit?: number;
|
||||
}
|
||||
| {
|
||||
command: 'write';
|
||||
projectDir: string;
|
||||
|
|
@ -30,10 +41,27 @@ export type KtxKnowledgeArgs =
|
|||
slRefs: string[];
|
||||
};
|
||||
|
||||
interface KtxKnowledgeIo {
|
||||
stdout: { write(chunk: string): void };
|
||||
stderr: { write(chunk: string): void };
|
||||
}
|
||||
type KtxKnowledgeIo = import('./cli-runtime.js').KtxCliIo;
|
||||
|
||||
const WIKI_LIST_COLUMNS: ReadonlyArray<PrintListColumn<LocalKnowledgeSummary>> = [
|
||||
{ key: 'scope', label: 'SCOPE', plain: '' },
|
||||
{ key: 'key', label: 'KEY', plain: '' },
|
||||
{ key: 'summary', label: 'SUMMARY', plain: '', optional: true, dim: true },
|
||||
];
|
||||
|
||||
const WIKI_SEARCH_COLUMNS: ReadonlyArray<PrintListColumn<LocalKnowledgeSearchResult>> = [
|
||||
{
|
||||
key: 'score',
|
||||
label: 'SCORE',
|
||||
plain: 'score=',
|
||||
role: 'badge',
|
||||
prettyFormat: (value) => `${Math.round(Number(value) * 100)}%`,
|
||||
dim: true,
|
||||
},
|
||||
{ key: 'scope', label: 'SCOPE', plain: '' },
|
||||
{ key: 'key', label: 'KEY', plain: '' },
|
||||
{ key: 'summary', label: 'SUMMARY', plain: '', optional: true, dim: true },
|
||||
];
|
||||
|
||||
interface KtxKnowledgeDeps {
|
||||
embeddingService?: KtxEmbeddingPort | null;
|
||||
|
|
@ -62,17 +90,18 @@ export async function runKtxKnowledge(
|
|||
const project = await loadKtxProject({ projectDir: args.projectDir });
|
||||
if (args.command === 'list') {
|
||||
const pages = await listLocalKnowledgePages(project, { userId: args.userId });
|
||||
if (args.json) {
|
||||
writeJsonResult(io, {
|
||||
kind: 'list',
|
||||
data: { items: pages },
|
||||
meta: { command: 'wiki list' },
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
for (const page of pages) {
|
||||
io.stdout.write(`${page.scope}\t${page.key}\t${page.summary}\n`);
|
||||
}
|
||||
const mode = resolveOutputMode({ explicit: args.output, json: args.json, io });
|
||||
printList<LocalKnowledgeSummary>({
|
||||
rows: pages,
|
||||
columns: WIKI_LIST_COLUMNS,
|
||||
groupBy: 'scope',
|
||||
emptyMessage: `No local wiki pages found in ${project.projectDir}`,
|
||||
emptyHint: 'Add Markdown files under wiki/ or run `ktx ingest <connectionId>`.',
|
||||
unit: 'page',
|
||||
command: 'wiki list',
|
||||
mode,
|
||||
io,
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
if (args.command === 'read') {
|
||||
|
|
@ -101,30 +130,27 @@ export async function runKtxKnowledge(
|
|||
embeddingService: wikiSearchEmbeddingService(project, deps),
|
||||
limit: args.limit,
|
||||
});
|
||||
if (args.json) {
|
||||
writeJsonResult(io, {
|
||||
kind: 'list',
|
||||
data: { items: results },
|
||||
meta: { command: 'wiki search' },
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
if (results.length === 0) {
|
||||
const mode = resolveOutputMode({ explicit: args.output, json: args.json, io });
|
||||
let emptyMessage = `No local wiki pages matched "${args.query}"`;
|
||||
let emptyHint = 'Run `ktx wiki list` to inspect available pages.';
|
||||
if (results.length === 0 && mode !== 'json') {
|
||||
const pages = await listLocalKnowledgePages(project, { userId: args.userId });
|
||||
if (pages.length === 0) {
|
||||
io.stderr.write(
|
||||
`No local wiki pages found in ${project.projectDir}. Add Markdown files under wiki/ or run \`ktx ingest <connectionId>\`.\n`,
|
||||
);
|
||||
} else {
|
||||
io.stderr.write(
|
||||
`No local wiki pages matched "${args.query}". Run \`ktx wiki list\` to inspect available pages.\n`,
|
||||
);
|
||||
emptyMessage = `No local wiki pages found in ${project.projectDir}`;
|
||||
emptyHint = 'Add Markdown files under wiki/ or run `ktx ingest <connectionId>`.';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
for (const result of results) {
|
||||
io.stdout.write(`${result.score}\t${result.scope}\t${result.key}\t${result.summary}\n`);
|
||||
}
|
||||
printList<LocalKnowledgeSearchResult>({
|
||||
rows: results,
|
||||
columns: WIKI_SEARCH_COLUMNS,
|
||||
groupBy: 'scope',
|
||||
emptyMessage,
|
||||
emptyHint,
|
||||
unit: 'page',
|
||||
command: 'wiki search',
|
||||
mode,
|
||||
io,
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue