topchester-ai 0.8.0 → 0.9.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 +9 -1
- package/dist/cli.mjs +115 -20
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ The current `topchester kb compile` command handles L1 file knowledge:
|
|
|
13
13
|
- Requires `topchester kb init` to create the knowledge folders first.
|
|
14
14
|
- Reads workspace `.gitignore` files, lists in-scope project files, and skips generated/cache folders such as `.git/`, `node_modules/`, `dist/`, `coverage/`, `topchester-kb/`, `.agents/topchester/`, and `.agents/topchester-kb-cache/`.
|
|
15
15
|
- Queues L1 work at `.agents/topchester-kb-cache/l1-queue.json`.
|
|
16
|
-
- Processes queued files with the configured `kb.summarize` model, or `
|
|
16
|
+
- Processes queued files with the configured `kb.summarize` model, or the `default` model when `kb.summarize` is not configured.
|
|
17
17
|
- Writes the manifest at `topchester-kb/manifest.json`.
|
|
18
18
|
- Writes current L1 file entries under `topchester-kb/l1-files/`.
|
|
19
19
|
- Exits successfully only when every in-scope file has a current L1 entry.
|
|
@@ -69,11 +69,19 @@ Model settings are loaded from YAML config files and merged in this order:
|
|
|
69
69
|
|
|
70
70
|
Example configs live in `config/example.yaml` and `config/gemini.yaml`. OpenRouter configs expect `OPENROUTER_API_KEY` in the environment; do not commit API keys or other secrets.
|
|
71
71
|
|
|
72
|
+
The smallest OpenRouter config uses one model for every Topchester purpose:
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
models:
|
|
76
|
+
default: openrouter/google/gemini-3.1-flash-lite
|
|
77
|
+
```
|
|
78
|
+
|
|
72
79
|
## Docs
|
|
73
80
|
|
|
74
81
|
- [Architecture](docs/ARCHITECTURE.md)
|
|
75
82
|
- [Knowledge System](docs/KNOWLEDGE.md)
|
|
76
83
|
- [CLI Commands](docs/cli.md)
|
|
84
|
+
- [Configuration](docs/config.md)
|
|
77
85
|
- [TUI Guide](docs/tui.md)
|
|
78
86
|
- [Model Configuration](docs/MODEL_CONFIG.md)
|
|
79
87
|
- [Sessions](docs/SESSIONS.md)
|
package/dist/cli.mjs
CHANGED
|
@@ -2018,6 +2018,7 @@ const modelPurposeSchema = z.enum([
|
|
|
2018
2018
|
"kb.embed",
|
|
2019
2019
|
"fallback"
|
|
2020
2020
|
]);
|
|
2021
|
+
const modelPurposes = modelPurposeSchema.options;
|
|
2021
2022
|
const toolProtocolSchema = z.enum([
|
|
2022
2023
|
"auto",
|
|
2023
2024
|
"native",
|
|
@@ -2043,7 +2044,14 @@ const modelAssignmentSchema = z.object({
|
|
|
2043
2044
|
provider: z.string().optional(),
|
|
2044
2045
|
toolProtocol: toolProtocolSchema.optional()
|
|
2045
2046
|
});
|
|
2047
|
+
const modelRefSchema = z.union([z.string(), modelAssignmentSchema]);
|
|
2046
2048
|
const providersSchema = z.object({ default: z.string().optional() }).catchall(providerSchema.or(z.string()));
|
|
2049
|
+
const rawModelsSchema = z.object({
|
|
2050
|
+
"default": modelRefSchema.optional(),
|
|
2051
|
+
"fast": modelRefSchema.optional(),
|
|
2052
|
+
"kb.summarize": modelRefSchema.optional(),
|
|
2053
|
+
"providers": providersSchema.optional()
|
|
2054
|
+
}).strict();
|
|
2047
2055
|
const ignorePathSchema = z.string().min(1).superRefine((value, context) => {
|
|
2048
2056
|
const pattern = value.startsWith("!") ? value.slice(1) : value;
|
|
2049
2057
|
if (!pattern) {
|
|
@@ -2071,7 +2079,11 @@ const topchesterConfigSchema = z.object({
|
|
|
2071
2079
|
defaultPurpose: modelPurposeSchema.optional(),
|
|
2072
2080
|
assignments: z.partialRecord(modelPurposeSchema, modelAssignmentSchema).optional(),
|
|
2073
2081
|
providers: providersSchema.optional()
|
|
2074
|
-
}).optional(),
|
|
2082
|
+
}).strict().optional(),
|
|
2083
|
+
ignore: z.object({ paths: z.array(ignorePathSchema).optional() }).optional()
|
|
2084
|
+
});
|
|
2085
|
+
const rawTopchesterConfigSchema = z.object({
|
|
2086
|
+
models: rawModelsSchema.optional(),
|
|
2075
2087
|
ignore: z.object({ paths: z.array(ignorePathSchema).optional() }).optional()
|
|
2076
2088
|
});
|
|
2077
2089
|
function loadTopchesterConfig(options) {
|
|
@@ -2102,10 +2114,94 @@ function readConfigFile(path) {
|
|
|
2102
2114
|
}
|
|
2103
2115
|
}
|
|
2104
2116
|
function parseConfigFile(path, value) {
|
|
2105
|
-
const
|
|
2117
|
+
const raw = rawTopchesterConfigSchema.safeParse(value ?? {});
|
|
2118
|
+
if (!raw.success) throw new Error(`Invalid Topchester config at ${path}: ${raw.error.issues.map(formatZodIssue).join("; ")}`);
|
|
2119
|
+
const parsed = topchesterConfigSchema.safeParse(normalizeConfigInput(raw.data));
|
|
2106
2120
|
if (!parsed.success) throw new Error(`Invalid Topchester config at ${path}: ${parsed.error.issues.map(formatZodIssue).join("; ")}`);
|
|
2107
2121
|
return parsed.data;
|
|
2108
2122
|
}
|
|
2123
|
+
function normalizeConfigInput(value) {
|
|
2124
|
+
if (!isPlainObject(value) || !isPlainObject(value.models)) return value;
|
|
2125
|
+
const models = { ...value.models };
|
|
2126
|
+
const providers = isPlainObject(models.providers) ? { ...models.providers } : {};
|
|
2127
|
+
const assignments = {};
|
|
2128
|
+
const defaultModelRef = normalizeModelRef(models.default, typeof providers.default === "string" ? providers.default : void 0);
|
|
2129
|
+
const defaultProvider = typeof providers.default === "string" ? providers.default : defaultModelRef?.provider;
|
|
2130
|
+
const fastModelRef = normalizeModelRef(models.fast, defaultProvider);
|
|
2131
|
+
const kbSummarizeModelRef = normalizeModelRef(models["kb.summarize"], defaultProvider);
|
|
2132
|
+
if (defaultModelRef) {
|
|
2133
|
+
const assignment = modelRefToAssignment(defaultModelRef);
|
|
2134
|
+
for (const purpose of modelPurposes) assignments[purpose] ??= assignment;
|
|
2135
|
+
providers.default ??= defaultModelRef.provider;
|
|
2136
|
+
ensureKnownProvider(providers, defaultModelRef.provider);
|
|
2137
|
+
delete models.default;
|
|
2138
|
+
}
|
|
2139
|
+
if (fastModelRef) {
|
|
2140
|
+
assignments["agent.fast"] = modelRefToAssignment(fastModelRef);
|
|
2141
|
+
ensureKnownProvider(providers, fastModelRef.provider);
|
|
2142
|
+
delete models.fast;
|
|
2143
|
+
}
|
|
2144
|
+
if (kbSummarizeModelRef) {
|
|
2145
|
+
assignments["kb.summarize"] = modelRefToAssignment(kbSummarizeModelRef);
|
|
2146
|
+
ensureKnownProvider(providers, kbSummarizeModelRef.provider);
|
|
2147
|
+
delete models["kb.summarize"];
|
|
2148
|
+
}
|
|
2149
|
+
return {
|
|
2150
|
+
...value,
|
|
2151
|
+
models: {
|
|
2152
|
+
...models,
|
|
2153
|
+
assignments,
|
|
2154
|
+
providers
|
|
2155
|
+
}
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
function normalizeModelRef(ref, defaultProvider) {
|
|
2159
|
+
if (typeof ref === "string") return parseModelRef(ref, defaultProvider);
|
|
2160
|
+
if (!isPlainObject(ref) || typeof ref.name !== "string") return;
|
|
2161
|
+
return {
|
|
2162
|
+
model: ref.name,
|
|
2163
|
+
...typeof ref.provider === "string" ? { provider: ref.provider } : defaultProvider ? { provider: defaultProvider } : {},
|
|
2164
|
+
...typeof ref.toolProtocol === "string" && toolProtocolSchema.safeParse(ref.toolProtocol).success ? { toolProtocol: ref.toolProtocol } : {}
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
function modelRefToAssignment(ref) {
|
|
2168
|
+
return {
|
|
2169
|
+
name: ref.model,
|
|
2170
|
+
...ref.provider ? { provider: ref.provider } : {},
|
|
2171
|
+
...ref.toolProtocol ? { toolProtocol: ref.toolProtocol } : {}
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
function parseModelRef(ref, defaultProvider) {
|
|
2175
|
+
if (defaultProvider) {
|
|
2176
|
+
const providerPrefix = `${defaultProvider}/`;
|
|
2177
|
+
return ref.startsWith(providerPrefix) ? {
|
|
2178
|
+
provider: defaultProvider,
|
|
2179
|
+
model: ref.slice(providerPrefix.length)
|
|
2180
|
+
} : {
|
|
2181
|
+
provider: defaultProvider,
|
|
2182
|
+
model: ref
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
const [provider, ...modelParts] = ref.split("/");
|
|
2186
|
+
if (provider && modelParts.length > 0) return {
|
|
2187
|
+
provider,
|
|
2188
|
+
model: modelParts.join("/")
|
|
2189
|
+
};
|
|
2190
|
+
return { model: ref };
|
|
2191
|
+
}
|
|
2192
|
+
function ensureKnownProvider(providers, provider) {
|
|
2193
|
+
if (provider !== "openrouter" || providers.openrouter !== void 0) return;
|
|
2194
|
+
providers.openrouter = {
|
|
2195
|
+
type: "openai-compatible",
|
|
2196
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
2197
|
+
apiKeyEnv: "OPENROUTER_API_KEY",
|
|
2198
|
+
supportsStructuredOutputs: true,
|
|
2199
|
+
headers: {
|
|
2200
|
+
"HTTP-Referer": "https://topchester.com",
|
|
2201
|
+
"X-Title": "Topchester"
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2109
2205
|
function deepMerge(base, override, path = []) {
|
|
2110
2206
|
if (Array.isArray(base) && Array.isArray(override)) return path.join(".") === "ignore.paths" ? [...base, ...override] : override;
|
|
2111
2207
|
if (!isPlainObject(base) || !isPlainObject(override)) return override;
|
|
@@ -4435,7 +4531,6 @@ var ChatLayout = class {
|
|
|
4435
4531
|
messages;
|
|
4436
4532
|
folderName;
|
|
4437
4533
|
modelLabel;
|
|
4438
|
-
exitAgent;
|
|
4439
4534
|
input = new Input();
|
|
4440
4535
|
status = "ready";
|
|
4441
4536
|
knowledgeStatus;
|
|
@@ -4449,12 +4544,15 @@ var ChatLayout = class {
|
|
|
4449
4544
|
activeSlashSuggestionIndex = 0;
|
|
4450
4545
|
threadScrollOffset = 0;
|
|
4451
4546
|
promptHistory = new PromptHistory();
|
|
4452
|
-
|
|
4547
|
+
exitAgent;
|
|
4548
|
+
transcriptMode;
|
|
4549
|
+
constructor(terminal, messages, folderName, modelLabel, options = {}) {
|
|
4453
4550
|
this.terminal = terminal;
|
|
4454
4551
|
this.messages = messages;
|
|
4455
4552
|
this.folderName = folderName;
|
|
4456
4553
|
this.modelLabel = modelLabel;
|
|
4457
|
-
this.exitAgent = exitAgent;
|
|
4554
|
+
this.exitAgent = typeof options === "function" ? options : options.exitAgent ?? (() => {});
|
|
4555
|
+
this.transcriptMode = typeof options === "function" ? "viewport" : options.transcriptMode ?? "viewport";
|
|
4458
4556
|
this.input.onSubmit = (value) => {
|
|
4459
4557
|
if (value.trim().length > 0) {
|
|
4460
4558
|
const message = value.trim();
|
|
@@ -4525,8 +4623,8 @@ var ChatLayout = class {
|
|
|
4525
4623
|
}
|
|
4526
4624
|
if (this.handleModalInput(data)) return;
|
|
4527
4625
|
if (this.handleSlashSuggestionInput(data)) return;
|
|
4528
|
-
if (this.handlePromptHistoryInput(data)) return;
|
|
4529
4626
|
if (this.handleThreadScrollInput(data)) return;
|
|
4627
|
+
if (this.handlePromptHistoryInput(data)) return;
|
|
4530
4628
|
const previousInput = this.input.getValue();
|
|
4531
4629
|
this.input.handleInput(data);
|
|
4532
4630
|
if (this.input.getValue() !== previousInput) this.promptHistory.resetBrowsing();
|
|
@@ -4539,6 +4637,10 @@ var ChatLayout = class {
|
|
|
4539
4637
|
const footerLines = this.getActiveModal() ? this.renderModalHelp(safeWidth) : this.renderPrompt(safeWidth);
|
|
4540
4638
|
const threadHeight = Math.max(1, this.terminal.rows - footerLines.length);
|
|
4541
4639
|
const allThreadLines = this.renderThread(safeWidth);
|
|
4640
|
+
if (this.transcriptMode === "inline") {
|
|
4641
|
+
this.threadScrollOffset = 0;
|
|
4642
|
+
return [...allThreadLines.length < threadHeight ? padLines(allThreadLines, threadHeight, safeWidth) : allThreadLines, ...footerLines];
|
|
4643
|
+
}
|
|
4542
4644
|
const maxScrollOffset = Math.max(0, allThreadLines.length - threadHeight);
|
|
4543
4645
|
this.threadScrollOffset = Math.min(this.threadScrollOffset, maxScrollOffset);
|
|
4544
4646
|
const end = allThreadLines.length - this.threadScrollOffset;
|
|
@@ -4639,6 +4741,7 @@ var ChatLayout = class {
|
|
|
4639
4741
|
return false;
|
|
4640
4742
|
}
|
|
4641
4743
|
handleThreadScrollInput(data) {
|
|
4744
|
+
if (this.transcriptMode === "inline") return false;
|
|
4642
4745
|
const pageSize = Math.max(1, Math.floor(this.terminal.rows / 2));
|
|
4643
4746
|
const wheel = parseMouseWheel(data);
|
|
4644
4747
|
if (wheel === "up") {
|
|
@@ -5197,15 +5300,6 @@ function formatKbPathSource(status) {
|
|
|
5197
5300
|
return status.kbPathSource === "env" ? " (custom)" : "";
|
|
5198
5301
|
}
|
|
5199
5302
|
//#endregion
|
|
5200
|
-
//#region src/tui/terminal.ts
|
|
5201
|
-
function enterAlternateScreen(terminal) {
|
|
5202
|
-
terminal.write("\x1B[?1049h");
|
|
5203
|
-
terminal.clearScreen();
|
|
5204
|
-
}
|
|
5205
|
-
function exitAlternateScreen(terminal) {
|
|
5206
|
-
terminal.write("\x1B[?1049l");
|
|
5207
|
-
}
|
|
5208
|
-
//#endregion
|
|
5209
5303
|
//#region src/tui/shell.ts
|
|
5210
5304
|
var TopchesterTuiShell = class {
|
|
5211
5305
|
context;
|
|
@@ -5232,19 +5326,20 @@ var TopchesterTuiShell = class {
|
|
|
5232
5326
|
return;
|
|
5233
5327
|
}
|
|
5234
5328
|
const terminal = new ProcessTerminal();
|
|
5235
|
-
enterAlternateScreen(terminal);
|
|
5236
5329
|
const tui = new TUI(terminal, true);
|
|
5237
5330
|
let didExit = false;
|
|
5238
5331
|
const exit = () => {
|
|
5239
5332
|
if (didExit) return;
|
|
5240
5333
|
didExit = true;
|
|
5241
5334
|
tui.stop();
|
|
5242
|
-
exitAlternateScreen(terminal);
|
|
5243
5335
|
printExitBanner(session.sessionId, Date.now() - startedAt);
|
|
5244
5336
|
};
|
|
5245
|
-
const app = new ChatLayout(terminal, messages, folderName, modelLabel,
|
|
5246
|
-
|
|
5247
|
-
|
|
5337
|
+
const app = new ChatLayout(terminal, messages, folderName, modelLabel, {
|
|
5338
|
+
transcriptMode: "inline",
|
|
5339
|
+
exitAgent: () => {
|
|
5340
|
+
exit();
|
|
5341
|
+
process.exit(0);
|
|
5342
|
+
}
|
|
5248
5343
|
});
|
|
5249
5344
|
app.setSubmitMessage((message) => {
|
|
5250
5345
|
this.submitChatMessage(app, tui, message);
|