topchester-ai 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.mjs +190 -23
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Topchester Agent
|
|
2
2
|
|
|
3
|
+
Website: https://topchester.com
|
|
4
|
+
|
|
3
5
|
## Overview
|
|
4
6
|
|
|
5
7
|
Topchester Agent is a terminal-native TUI coding agent tightly coupled to a committed project knowledge base. The normal workflow is to compile project knowledge first, then let the agent use that knowledge while planning, editing, checking drift, and updating the repository.
|
package/dist/cli.mjs
CHANGED
|
@@ -1843,19 +1843,26 @@ function formatKnowledgeStatus(status) {
|
|
|
1843
1843
|
const lines = [
|
|
1844
1844
|
"KB status",
|
|
1845
1845
|
`workspace: ${status.workspaceRoot}`,
|
|
1846
|
-
`knowledge folder: ${
|
|
1847
|
-
`local cache folder: ${formatPathStatus$
|
|
1846
|
+
`knowledge folder: ${formatKnowledgePathStatus$2(status)} (${status.kbPathSource})`,
|
|
1847
|
+
`local cache folder: ${formatPathStatus$1(status.cachePath, status.cacheExists, status.cacheIsDirectory)} (${status.cachePathSource})`
|
|
1848
1848
|
];
|
|
1849
1849
|
if (!status.kbExists) lines.push("state: no knowledge base found yet");
|
|
1850
1850
|
else if (!status.kbIsDirectory) lines.push("state: knowledge base path is not a folder");
|
|
1851
|
+
else if (status.kbContentState !== "ready") lines.push("state: knowledge base folder is empty");
|
|
1851
1852
|
else lines.push("state: knowledge base found");
|
|
1852
1853
|
return lines;
|
|
1853
1854
|
}
|
|
1854
|
-
function formatPathStatus$
|
|
1855
|
+
function formatPathStatus$1(path, exists, isDirectory) {
|
|
1855
1856
|
if (!exists) return `${path} [missing]`;
|
|
1856
1857
|
if (!isDirectory) return `${path} [not a folder]`;
|
|
1857
1858
|
return `${path} [ok]`;
|
|
1858
1859
|
}
|
|
1860
|
+
function formatKnowledgePathStatus$2(status) {
|
|
1861
|
+
if (!status.kbExists) return `${status.kbPath} [missing]`;
|
|
1862
|
+
if (!status.kbIsDirectory) return `${status.kbPath} [not a folder]`;
|
|
1863
|
+
if (status.kbContentState !== "ready") return `${status.kbPath} [empty]`;
|
|
1864
|
+
return `${status.kbPath} [ok]`;
|
|
1865
|
+
}
|
|
1859
1866
|
//#endregion
|
|
1860
1867
|
//#region src/tui/markdown.ts
|
|
1861
1868
|
const codeFenceSentinel = "topchester-code-fence";
|
|
@@ -2058,6 +2065,48 @@ function escapeRegex(value) {
|
|
|
2058
2065
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2059
2066
|
}
|
|
2060
2067
|
//#endregion
|
|
2068
|
+
//#region src/tui/prompt-history.ts
|
|
2069
|
+
const DEFAULT_MAX_PROMPTS = 100;
|
|
2070
|
+
var PromptHistory = class {
|
|
2071
|
+
maxPrompts;
|
|
2072
|
+
prompts = [];
|
|
2073
|
+
historyIndex = -1;
|
|
2074
|
+
draft = "";
|
|
2075
|
+
constructor(maxPrompts = DEFAULT_MAX_PROMPTS) {
|
|
2076
|
+
this.maxPrompts = Math.max(1, maxPrompts);
|
|
2077
|
+
}
|
|
2078
|
+
add(value) {
|
|
2079
|
+
const prompt = value.trim();
|
|
2080
|
+
this.resetBrowsing();
|
|
2081
|
+
if (prompt.length === 0 || prompt === this.prompts[0]) return;
|
|
2082
|
+
this.prompts.unshift(prompt);
|
|
2083
|
+
this.prompts = this.prompts.slice(0, this.maxPrompts);
|
|
2084
|
+
}
|
|
2085
|
+
previous(currentDraft) {
|
|
2086
|
+
if (this.prompts.length === 0) return;
|
|
2087
|
+
if (this.historyIndex === -1) {
|
|
2088
|
+
this.draft = currentDraft;
|
|
2089
|
+
this.historyIndex = 0;
|
|
2090
|
+
return this.prompts[this.historyIndex];
|
|
2091
|
+
}
|
|
2092
|
+
this.historyIndex = Math.min(this.historyIndex + 1, this.prompts.length - 1);
|
|
2093
|
+
return this.prompts[this.historyIndex];
|
|
2094
|
+
}
|
|
2095
|
+
next() {
|
|
2096
|
+
if (this.historyIndex === -1) return;
|
|
2097
|
+
if (this.historyIndex === 0) {
|
|
2098
|
+
this.historyIndex = -1;
|
|
2099
|
+
return this.draft;
|
|
2100
|
+
}
|
|
2101
|
+
this.historyIndex -= 1;
|
|
2102
|
+
return this.prompts[this.historyIndex];
|
|
2103
|
+
}
|
|
2104
|
+
resetBrowsing() {
|
|
2105
|
+
this.historyIndex = -1;
|
|
2106
|
+
this.draft = "";
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
//#endregion
|
|
2061
2110
|
//#region src/tui/status.ts
|
|
2062
2111
|
function getStartupThreadMessages(context) {
|
|
2063
2112
|
const assignments = context.config.models?.assignments ?? {};
|
|
@@ -2136,10 +2185,17 @@ function formatKnowledgeFooterStatus(status) {
|
|
|
2136
2185
|
if (status.kbContentState !== "ready") return `${ui.label("○")} kb: ${ui.label("empty")}`;
|
|
2137
2186
|
return `${ui.ok("✅")} kb: ${ui.ok("ready")}`;
|
|
2138
2187
|
}
|
|
2139
|
-
function
|
|
2140
|
-
|
|
2141
|
-
if (!
|
|
2142
|
-
return `${
|
|
2188
|
+
function formatKnowledgePathStatus$1(status) {
|
|
2189
|
+
const pathLabel = `${ui.label("")} ${formatWorkspaceRelativePath(status.kbPath, status.workspaceRoot)}`;
|
|
2190
|
+
if (!status.kbExists) return `${pathLabel} ${ui.warn("[missing]")}`;
|
|
2191
|
+
if (!status.kbIsDirectory) return `${pathLabel} ${ui.error("[not a folder]")}`;
|
|
2192
|
+
if (status.kbContentState !== "ready") return `${pathLabel} ${ui.label("[empty]")}`;
|
|
2193
|
+
return `${pathLabel} ${ui.ok("[ok]")}`;
|
|
2194
|
+
}
|
|
2195
|
+
function formatWorkspaceRelativePath(path, workspaceRoot) {
|
|
2196
|
+
const relativePath = relative(workspaceRoot, path);
|
|
2197
|
+
if (relativePath && !relativePath.startsWith("..") && !isAbsolute(relativePath)) return relativePath;
|
|
2198
|
+
return path;
|
|
2143
2199
|
}
|
|
2144
2200
|
function getModelLabel(context) {
|
|
2145
2201
|
const purpose = context.config.models?.defaultPurpose ?? "agent.primary";
|
|
@@ -2188,6 +2244,7 @@ var ChatLayout = class {
|
|
|
2188
2244
|
activeModalActionIndex = 0;
|
|
2189
2245
|
activeSlashSuggestionIndex = 0;
|
|
2190
2246
|
threadScrollOffset = 0;
|
|
2247
|
+
promptHistory = new PromptHistory();
|
|
2191
2248
|
constructor(terminal, messages, folderName, modelLabel, exitAgent = () => {}) {
|
|
2192
2249
|
this.terminal = terminal;
|
|
2193
2250
|
this.messages = messages;
|
|
@@ -2264,8 +2321,11 @@ var ChatLayout = class {
|
|
|
2264
2321
|
}
|
|
2265
2322
|
if (this.handleModalInput(data)) return;
|
|
2266
2323
|
if (this.handleSlashSuggestionInput(data)) return;
|
|
2324
|
+
if (this.handlePromptHistoryInput(data)) return;
|
|
2267
2325
|
if (this.handleThreadScrollInput(data)) return;
|
|
2326
|
+
const previousInput = this.input.getValue();
|
|
2268
2327
|
this.input.handleInput(data);
|
|
2328
|
+
if (this.input.getValue() !== previousInput) this.promptHistory.resetBrowsing();
|
|
2269
2329
|
}
|
|
2270
2330
|
invalidate() {
|
|
2271
2331
|
this.input.invalidate();
|
|
@@ -2377,14 +2437,6 @@ var ChatLayout = class {
|
|
|
2377
2437
|
handleThreadScrollInput(data) {
|
|
2378
2438
|
const pageSize = Math.max(1, Math.floor(this.terminal.rows / 2));
|
|
2379
2439
|
const wheel = parseMouseWheel(data);
|
|
2380
|
-
if (isUpKey(data)) {
|
|
2381
|
-
this.threadScrollOffset += 3;
|
|
2382
|
-
return true;
|
|
2383
|
-
}
|
|
2384
|
-
if (isDownKey(data)) {
|
|
2385
|
-
this.threadScrollOffset = Math.max(0, this.threadScrollOffset - 3);
|
|
2386
|
-
return true;
|
|
2387
|
-
}
|
|
2388
2440
|
if (wheel === "up") {
|
|
2389
2441
|
this.threadScrollOffset += 3;
|
|
2390
2442
|
return true;
|
|
@@ -2411,6 +2463,20 @@ var ChatLayout = class {
|
|
|
2411
2463
|
}
|
|
2412
2464
|
return false;
|
|
2413
2465
|
}
|
|
2466
|
+
handlePromptHistoryInput(data) {
|
|
2467
|
+
if (this.promptHint) return false;
|
|
2468
|
+
if (isUpKey(data)) {
|
|
2469
|
+
const prompt = this.promptHistory.previous(this.input.getValue());
|
|
2470
|
+
if (prompt !== void 0) this.input.setValue(prompt);
|
|
2471
|
+
return true;
|
|
2472
|
+
}
|
|
2473
|
+
if (isDownKey(data)) {
|
|
2474
|
+
const prompt = this.promptHistory.next();
|
|
2475
|
+
if (prompt !== void 0) this.input.setValue(prompt);
|
|
2476
|
+
return true;
|
|
2477
|
+
}
|
|
2478
|
+
return false;
|
|
2479
|
+
}
|
|
2414
2480
|
handleSlashSuggestionInput(data) {
|
|
2415
2481
|
const suggestions = this.getSlashSuggestions();
|
|
2416
2482
|
if (suggestions.length === 0) {
|
|
@@ -2438,6 +2504,7 @@ var ChatLayout = class {
|
|
|
2438
2504
|
completeSlashSuggestion(suggestions) {
|
|
2439
2505
|
this.input.setValue(suggestions[this.activeSlashSuggestionIndex]?.value ?? this.input.getValue());
|
|
2440
2506
|
this.input.handleInput("\x1B[F");
|
|
2507
|
+
this.promptHistory.resetBrowsing();
|
|
2441
2508
|
}
|
|
2442
2509
|
getSlashSuggestions() {
|
|
2443
2510
|
return getSlashCommandSuggestions(this.input.getValue());
|
|
@@ -2453,6 +2520,7 @@ var ChatLayout = class {
|
|
|
2453
2520
|
this.submitUserInput(message);
|
|
2454
2521
|
}
|
|
2455
2522
|
submitUserInput(message) {
|
|
2523
|
+
this.promptHistory.add(message);
|
|
2456
2524
|
if (message.startsWith("/")) this.submitCommand?.(message);
|
|
2457
2525
|
else this.submitMessage?.(message);
|
|
2458
2526
|
}
|
|
@@ -2641,7 +2709,7 @@ const editFileTool = defineTool({
|
|
|
2641
2709
|
execute: (context, args) => editWorkspaceFile(context.workspaceRoot, args, { logger: context.logger })
|
|
2642
2710
|
});
|
|
2643
2711
|
async function editWorkspaceFile(workspaceRoot, args, options = {}) {
|
|
2644
|
-
const scopedPath = resolveWorkspaceScopedPath$
|
|
2712
|
+
const scopedPath = resolveWorkspaceScopedPath$3(workspaceRoot, args.path);
|
|
2645
2713
|
return enqueueFileMutation(scopedPath.path, async () => {
|
|
2646
2714
|
const fileStat = await statExistingFile(scopedPath.path, args.path);
|
|
2647
2715
|
const beforeBytes = await readFile(scopedPath.path);
|
|
@@ -2704,7 +2772,7 @@ function applyExactEdits(content, edits, path = "file") {
|
|
|
2704
2772
|
firstChangedLine: getFirstChangedLine(document.body, newBody)
|
|
2705
2773
|
};
|
|
2706
2774
|
}
|
|
2707
|
-
function resolveWorkspaceScopedPath$
|
|
2775
|
+
function resolveWorkspaceScopedPath$3(workspaceRoot, path) {
|
|
2708
2776
|
if (path.includes("\0")) throw new Error("edit_file path is invalid.");
|
|
2709
2777
|
const resolvedWorkspace = resolve(workspaceRoot);
|
|
2710
2778
|
const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
|
|
@@ -2908,7 +2976,7 @@ const findFileTool = defineTool({
|
|
|
2908
2976
|
})
|
|
2909
2977
|
});
|
|
2910
2978
|
async function findWorkspaceFilesByName(workspaceRoot, args, options = {}) {
|
|
2911
|
-
const scopedPath = resolveWorkspaceScopedPath$
|
|
2979
|
+
const scopedPath = resolveWorkspaceScopedPath$2(workspaceRoot, args.path);
|
|
2912
2980
|
const matches = (await collectWorkspaceFiles(scopedPath.workspaceRoot, scopedPath.path, scopedPath.relativePath, options)).map((path) => {
|
|
2913
2981
|
const score = scoreFileMatch(args.query, path);
|
|
2914
2982
|
return score > 0 ? {
|
|
@@ -3106,7 +3174,7 @@ function scoreSubsequenceMatch(query, value) {
|
|
|
3106
3174
|
function normalize(value) {
|
|
3107
3175
|
return value.trim().toLowerCase().replaceAll("\\", "/");
|
|
3108
3176
|
}
|
|
3109
|
-
function resolveWorkspaceScopedPath$
|
|
3177
|
+
function resolveWorkspaceScopedPath$2(workspaceRoot, path) {
|
|
3110
3178
|
const resolvedWorkspace = resolve(workspaceRoot);
|
|
3111
3179
|
const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
|
|
3112
3180
|
const relativePath = relative(resolvedWorkspace, resolvedPath);
|
|
@@ -3164,7 +3232,7 @@ const grepTool = defineTool({
|
|
|
3164
3232
|
})
|
|
3165
3233
|
});
|
|
3166
3234
|
async function grepWorkspace(workspaceRoot, args, options = {}) {
|
|
3167
|
-
const scopedPath = resolveWorkspaceScopedPath(workspaceRoot, args.path ?? ".");
|
|
3235
|
+
const scopedPath = resolveWorkspaceScopedPath$1(workspaceRoot, args.path ?? ".");
|
|
3168
3236
|
const executable = await findSearchExecutable(options.pathEnv);
|
|
3169
3237
|
if (!executable) {
|
|
3170
3238
|
const warning = "grep could not run because neither rg nor grep is available on PATH.";
|
|
@@ -3228,7 +3296,7 @@ async function grepWorkspace(workspaceRoot, args, options = {}) {
|
|
|
3228
3296
|
content: truncateToolOutput(result.stdout.trimEnd() || "No matches.")
|
|
3229
3297
|
};
|
|
3230
3298
|
}
|
|
3231
|
-
function resolveWorkspaceScopedPath(workspaceRoot, path) {
|
|
3299
|
+
function resolveWorkspaceScopedPath$1(workspaceRoot, path) {
|
|
3232
3300
|
const resolvedWorkspace = resolve(workspaceRoot);
|
|
3233
3301
|
const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
|
|
3234
3302
|
const relativePath = relative(resolvedWorkspace, resolvedPath);
|
|
@@ -3286,6 +3354,93 @@ function truncateToolOutput(output) {
|
|
|
3286
3354
|
function isRecord$1(value) {
|
|
3287
3355
|
return typeof value === "object" && value !== null;
|
|
3288
3356
|
}
|
|
3357
|
+
const listFilesTool = defineTool({
|
|
3358
|
+
name: "list_files",
|
|
3359
|
+
description: "List files and directories inside a workspace folder.",
|
|
3360
|
+
prompt: "list_files: list files and directories inside the workspace; top-level by default, recursive only when requested, with \"/\" after directory names. To use it, reply with only JSON: {\"tool\":\"list_files\",\"args\":{\"path\":\"src\",\"recursive\":false,\"limit\":500}}",
|
|
3361
|
+
argsSchema: z.object({
|
|
3362
|
+
path: z.string().optional().default("."),
|
|
3363
|
+
recursive: z.boolean().optional().default(false),
|
|
3364
|
+
limit: z.number().int().min(1).max(2e3).optional().default(500)
|
|
3365
|
+
}),
|
|
3366
|
+
execute: (context, args) => listWorkspaceFiles(context.workspaceRoot, args)
|
|
3367
|
+
});
|
|
3368
|
+
async function listWorkspaceFiles(workspaceRoot, args) {
|
|
3369
|
+
const scopedPath = resolveWorkspaceScopedPath(workspaceRoot, args.path);
|
|
3370
|
+
if (!(await stat(scopedPath.path)).isDirectory()) throw new Error(`list_files can only list directories inside the workspace: ${args.path}`);
|
|
3371
|
+
const entries = args.recursive ? await collectRecursiveEntries(scopedPath.workspaceRoot, scopedPath.path, args.limit) : await collectTopLevelEntries(scopedPath.workspaceRoot, scopedPath.path, args.limit);
|
|
3372
|
+
const truncated = entries.truncated;
|
|
3373
|
+
const lines = entries.items.map((entry) => formatEntryPath(entry.relativePath, entry.isDirectory));
|
|
3374
|
+
const notices = [];
|
|
3375
|
+
if (truncated) notices.push(`[${args.limit} entry limit reached. Use a narrower path or a higher limit for more.]`);
|
|
3376
|
+
return {
|
|
3377
|
+
tool: "list_files",
|
|
3378
|
+
path: scopedPath.relativePath,
|
|
3379
|
+
content: [...lines.length > 0 ? lines : ["(empty directory)"], ...notices].join("\n"),
|
|
3380
|
+
warning: truncated ? `${args.limit} entry limit reached.` : void 0
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3383
|
+
async function collectTopLevelEntries(workspaceRoot, startPath, limit) {
|
|
3384
|
+
const entries = await sortedDirectoryEntries(startPath);
|
|
3385
|
+
const items = [];
|
|
3386
|
+
for (const entry of entries) {
|
|
3387
|
+
if (items.length >= limit) return {
|
|
3388
|
+
items,
|
|
3389
|
+
truncated: true
|
|
3390
|
+
};
|
|
3391
|
+
const absolutePath = join(startPath, entry.name);
|
|
3392
|
+
items.push({
|
|
3393
|
+
relativePath: relative(workspaceRoot, absolutePath) || ".",
|
|
3394
|
+
isDirectory: entry.isDirectory()
|
|
3395
|
+
});
|
|
3396
|
+
}
|
|
3397
|
+
return {
|
|
3398
|
+
items,
|
|
3399
|
+
truncated: false
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
async function collectRecursiveEntries(workspaceRoot, startPath, limit) {
|
|
3403
|
+
const items = [];
|
|
3404
|
+
const pending = [startPath];
|
|
3405
|
+
while (pending.length > 0) {
|
|
3406
|
+
const currentPath = pending.shift() ?? startPath;
|
|
3407
|
+
const entries = await sortedDirectoryEntries(currentPath);
|
|
3408
|
+
for (const entry of entries) {
|
|
3409
|
+
if (items.length >= limit) return {
|
|
3410
|
+
items,
|
|
3411
|
+
truncated: true
|
|
3412
|
+
};
|
|
3413
|
+
const absolutePath = join(currentPath, entry.name);
|
|
3414
|
+
const item = {
|
|
3415
|
+
relativePath: relative(workspaceRoot, absolutePath) || ".",
|
|
3416
|
+
isDirectory: entry.isDirectory()
|
|
3417
|
+
};
|
|
3418
|
+
items.push(item);
|
|
3419
|
+
if (item.isDirectory) pending.push(absolutePath);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
return {
|
|
3423
|
+
items,
|
|
3424
|
+
truncated: false
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
async function sortedDirectoryEntries(path) {
|
|
3428
|
+
return (await readdir(path, { withFileTypes: true })).sort((left, right) => left.name.toLowerCase().localeCompare(right.name.toLowerCase()));
|
|
3429
|
+
}
|
|
3430
|
+
function formatEntryPath(path, isDirectory) {
|
|
3431
|
+
return isDirectory ? `${path}/` : path;
|
|
3432
|
+
}
|
|
3433
|
+
function resolveWorkspaceScopedPath(workspaceRoot, path) {
|
|
3434
|
+
const resolvedWorkspace = resolve(workspaceRoot);
|
|
3435
|
+
const resolvedPath = isAbsolute(path) ? resolve(path) : resolve(resolvedWorkspace, path);
|
|
3436
|
+
const relativePath = relative(resolvedWorkspace, resolvedPath);
|
|
3437
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) throw new Error(`list_files can only list directories inside the workspace: ${path}`);
|
|
3438
|
+
return {
|
|
3439
|
+
workspaceRoot: resolvedWorkspace,
|
|
3440
|
+
path: resolvedPath,
|
|
3441
|
+
relativePath: relativePath || "."
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3289
3444
|
const readFileTool = defineTool({
|
|
3290
3445
|
name: "read_file",
|
|
3291
3446
|
description: "Read a UTF-8 file inside the workspace.",
|
|
@@ -3311,6 +3466,7 @@ async function readWorkspaceFile(workspaceRoot, path) {
|
|
|
3311
3466
|
//#region src/agent/tools/registry.ts
|
|
3312
3467
|
const toolRegistry = {
|
|
3313
3468
|
[readFileTool.name]: readFileTool,
|
|
3469
|
+
[listFilesTool.name]: listFilesTool,
|
|
3314
3470
|
[grepTool.name]: grepTool,
|
|
3315
3471
|
[findFileTool.name]: findFileTool,
|
|
3316
3472
|
[editFileTool.name]: editFileTool
|
|
@@ -3617,6 +3773,7 @@ function formatToolResultForPrompt(result) {
|
|
|
3617
3773
|
function formatToolCallMessage(call, result) {
|
|
3618
3774
|
switch (call.tool) {
|
|
3619
3775
|
case "read_file": return `Tool read_file: ${call.args.path}`;
|
|
3776
|
+
case "list_files": return `Tool list_files: ${call.args.path}${call.args.recursive ? " (recursive)" : ""}`;
|
|
3620
3777
|
case "grep": return `Tool grep: ${call.args.pattern} in ${call.args.path ?? "."}`;
|
|
3621
3778
|
case "find_file": return `Tool find_file: ${call.args.query} in ${call.args.path}`;
|
|
3622
3779
|
case "edit_file": return `Tool edit_file: ${call.args.path}${formatEditFileChangeSummary(result)}`;
|
|
@@ -3650,7 +3807,7 @@ function renderRuntimeEvent(event) {
|
|
|
3650
3807
|
switch (event.type) {
|
|
3651
3808
|
case "message": return [event.role === "assistant" ? agentMessage(event.text, event.meta) : systemMessage(event.text)];
|
|
3652
3809
|
case "tool_call": return [systemMessage(event.label)];
|
|
3653
|
-
case "knowledge_status": return [systemMessage(`KB status: ${
|
|
3810
|
+
case "knowledge_status": return [systemMessage(`KB status: ${formatKnowledgePathStatus$1(event.status)}${formatKbPathSource(event.status)}`)];
|
|
3654
3811
|
case "choice": return [modalMessage({
|
|
3655
3812
|
tone: event.tone,
|
|
3656
3813
|
title: event.title,
|
|
@@ -3660,6 +3817,9 @@ function renderRuntimeEvent(event) {
|
|
|
3660
3817
|
case "status": return [];
|
|
3661
3818
|
}
|
|
3662
3819
|
}
|
|
3820
|
+
function formatKbPathSource(status) {
|
|
3821
|
+
return status.kbPathSource === "env" ? " (custom)" : "";
|
|
3822
|
+
}
|
|
3663
3823
|
//#endregion
|
|
3664
3824
|
//#region src/tui/terminal.ts
|
|
3665
3825
|
function enterAlternateScreen(terminal) {
|
|
@@ -4046,10 +4206,11 @@ kbCommand.command("status").description("show project knowledge base status").ac
|
|
|
4046
4206
|
const status = await ui.spinner("Checking knowledge base...", () => getKnowledgeStatus(context.workspaceRoot));
|
|
4047
4207
|
console.log(ui.heading("KB status"));
|
|
4048
4208
|
console.log(`${ui.label("workspace")}: ${status.workspaceRoot}`);
|
|
4049
|
-
console.log(`${ui.label("knowledge folder")}: ${
|
|
4209
|
+
console.log(`${ui.label("knowledge folder")}: ${formatKnowledgePathStatus(status)} ${ui.label(`(${status.kbPathSource})`)}`);
|
|
4050
4210
|
console.log(`${ui.label("local cache folder")}: ${formatPathStatus(status.cachePath, status.cacheExists, status.cacheIsDirectory)} ${ui.label(`(${status.cachePathSource})`)}`);
|
|
4051
4211
|
if (!status.kbExists) console.log(`${ui.label("state")}: ${ui.warn("no knowledge base found yet")}`);
|
|
4052
4212
|
else if (!status.kbIsDirectory) console.log(`${ui.label("state")}: ${ui.error("knowledge base path is not a folder")}`);
|
|
4213
|
+
else if (status.kbContentState !== "ready") console.log(`${ui.label("state")}: ${ui.label("knowledge base folder is empty")}`);
|
|
4053
4214
|
else console.log(`${ui.label("state")}: ${ui.ok("knowledge base found")}`);
|
|
4054
4215
|
});
|
|
4055
4216
|
await program.parseAsync();
|
|
@@ -4101,6 +4262,12 @@ function formatPathStatus(path, exists, isDirectory) {
|
|
|
4101
4262
|
if (!isDirectory) return `${path} ${ui.error("[not a folder]")}`;
|
|
4102
4263
|
return `${path} ${ui.ok("[ok]")}`;
|
|
4103
4264
|
}
|
|
4265
|
+
function formatKnowledgePathStatus(status) {
|
|
4266
|
+
if (!status.kbExists) return `${status.kbPath} ${ui.warn("[missing]")}`;
|
|
4267
|
+
if (!status.kbIsDirectory) return `${status.kbPath} ${ui.error("[not a folder]")}`;
|
|
4268
|
+
if (status.kbContentState !== "ready") return `${status.kbPath} ${ui.label("[empty]")}`;
|
|
4269
|
+
return `${status.kbPath} ${ui.ok("[ok]")}`;
|
|
4270
|
+
}
|
|
4104
4271
|
//#endregion
|
|
4105
4272
|
export {};
|
|
4106
4273
|
|