topchester-ai 0.44.0 → 0.45.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/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as runTopchesterCli } from "./cli-Cz5puJBe.mjs";
2
+ import { t as runTopchesterCli } from "./cli-BgMj4Ifj.mjs";
3
3
  //#region src/bin.ts
4
4
  await runTopchesterCli();
5
5
  //#endregion
@@ -7,7 +7,7 @@ import { generateText, stepCountIs, streamText, tool } from "ai";
7
7
  import { ZodError, z } from "zod";
8
8
  import { access, mkdir, open, readFile, readdir, realpath, rename, rm, stat, truncate, writeFile } from "node:fs/promises";
9
9
  import { execFile, spawn } from "node:child_process";
10
- import { constants, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
10
+ import { accessSync, constants, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
11
11
  import { createHash, randomUUID } from "node:crypto";
12
12
  import { parseDocument } from "yaml";
13
13
  import { fileURLToPath } from "node:url";
@@ -34,7 +34,7 @@ const DEFAULT_MAX_OUTPUT_LINES = 1e3;
34
34
  async function runProcess(options) {
35
35
  const startedAt = Date.now();
36
36
  const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
37
- const executablePath = options.executable.includes("/") || isAbsolute(options.executable) ? await resolveExecutablePath(options.executable) : await findExecutable$2(options.executable, pathEnv);
37
+ const executablePath = options.executable.includes("/") || isAbsolute(options.executable) ? await resolveExecutablePath(options.executable) : await findExecutable$3(options.executable, pathEnv);
38
38
  if (!executablePath) return {
39
39
  stdout: "",
40
40
  stderr: `${options.missingExecutableLabel ?? "command"} could not run because '${options.executable}' is not available on PATH.\n`,
@@ -47,7 +47,7 @@ async function runProcess(options) {
47
47
  };
48
48
  return runSpawnedProcess(executablePath, options, startedAt, pathEnv);
49
49
  }
50
- async function findExecutable$2(name, pathEnv) {
50
+ async function findExecutable$3(name, pathEnv) {
51
51
  for (const pathEntry of pathEnv.split(delimiter).filter(Boolean)) {
52
52
  const executablePath = join(pathEntry, name);
53
53
  try {
@@ -1200,7 +1200,7 @@ async function collectWorkspaceFilesWithNode(workspaceRoot, startPath) {
1200
1200
  return files;
1201
1201
  }
1202
1202
  async function createRipgrepCollector(pathEnv, relativeStartPath) {
1203
- const command = await findExecutable$1("rg", pathEnv);
1203
+ const command = await findExecutable$2("rg", pathEnv);
1204
1204
  if (!command) return;
1205
1205
  return {
1206
1206
  name: "rg",
@@ -1216,8 +1216,8 @@ async function createRipgrepCollector(pathEnv, relativeStartPath) {
1216
1216
  };
1217
1217
  }
1218
1218
  async function createFdCollector(pathEnv, relativeStartPath) {
1219
- const fdCommand = await findExecutable$1("fd", pathEnv);
1220
- const fdfindCommand = fdCommand ? void 0 : await findExecutable$1("fdfind", pathEnv);
1219
+ const fdCommand = await findExecutable$2("fd", pathEnv);
1220
+ const fdfindCommand = fdCommand ? void 0 : await findExecutable$2("fdfind", pathEnv);
1221
1221
  const command = fdCommand ?? fdfindCommand;
1222
1222
  if (!command) return;
1223
1223
  return {
@@ -1237,7 +1237,7 @@ async function createFdCollector(pathEnv, relativeStartPath) {
1237
1237
  };
1238
1238
  }
1239
1239
  async function createFindCollector(pathEnv, relativeStartPath) {
1240
- const command = await findExecutable$1("find", pathEnv);
1240
+ const command = await findExecutable$2("find", pathEnv);
1241
1241
  if (!command) return;
1242
1242
  return {
1243
1243
  name: "find",
@@ -1332,7 +1332,7 @@ function resolveWorkspaceScopedPath$3(workspaceRoot, path) {
1332
1332
  relativePath: relativePath || "."
1333
1333
  };
1334
1334
  }
1335
- async function findExecutable$1(name, pathEnv) {
1335
+ async function findExecutable$2(name, pathEnv) {
1336
1336
  for (const pathEntry of pathEnv.split(delimiter).filter(Boolean)) {
1337
1337
  const executablePath = join(pathEntry, name);
1338
1338
  try {
@@ -2188,14 +2188,14 @@ function resolveWorkspaceScopedPath$2(workspaceRoot, path) {
2188
2188
  }
2189
2189
  async function findSearchExecutable(pathEnv = process.env.PATH ?? "") {
2190
2190
  for (const name of ["rg", "grep"]) {
2191
- const executablePath = await findExecutable(name, pathEnv);
2191
+ const executablePath = await findExecutable$1(name, pathEnv);
2192
2192
  if (executablePath) return {
2193
2193
  name,
2194
2194
  path: executablePath
2195
2195
  };
2196
2196
  }
2197
2197
  }
2198
- async function findExecutable(name, pathEnv) {
2198
+ async function findExecutable$1(name, pathEnv) {
2199
2199
  for (const pathEntry of pathEnv.split(delimiter).filter(Boolean)) {
2200
2200
  const executablePath = join(pathEntry, name);
2201
2201
  try {
@@ -5677,13 +5677,7 @@ function ensureGlobalTopchesterConfigFile() {
5677
5677
  return configPath;
5678
5678
  }
5679
5679
  function loadTopchesterConfig(options) {
5680
- const globalConfigDir = getGlobalTopchesterConfigDir();
5681
- const paths = [
5682
- join(options.workspaceRoot, "topchester.jsonc"),
5683
- join(globalConfigDir, "config.jsonc"),
5684
- process.env.TOPCHESTER_CONFIG,
5685
- options.configPath
5686
- ].filter((path) => Boolean(path));
5680
+ const paths = getTopchesterConfigSources(options).map((source) => source.path).filter((path) => Boolean(path));
5687
5681
  let merged = {};
5688
5682
  for (const path of paths) {
5689
5683
  const resolvedPath = isAbsolute(path) ? path : resolve(options.workspaceRoot, path);
@@ -5693,6 +5687,33 @@ function loadTopchesterConfig(options) {
5693
5687
  }
5694
5688
  return topchesterConfigSchema.parse(merged);
5695
5689
  }
5690
+ function getTopchesterConfigSources(options) {
5691
+ return [
5692
+ {
5693
+ label: "workspace",
5694
+ path: join(options.workspaceRoot, "topchester.jsonc")
5695
+ },
5696
+ {
5697
+ label: "user",
5698
+ path: getGlobalTopchesterConfigPath()
5699
+ },
5700
+ {
5701
+ label: "env",
5702
+ path: process.env.TOPCHESTER_CONFIG || void 0
5703
+ },
5704
+ {
5705
+ label: "cli",
5706
+ path: options.configPath
5707
+ }
5708
+ ].map((source) => {
5709
+ const resolvedPath = source.path === void 0 ? void 0 : isAbsolute(source.path) ? source.path : resolve(options.workspaceRoot, source.path);
5710
+ return {
5711
+ label: source.label,
5712
+ ...resolvedPath === void 0 ? {} : { path: resolvedPath },
5713
+ exists: resolvedPath === void 0 ? false : existsSync(resolvedPath)
5714
+ };
5715
+ });
5716
+ }
5696
5717
  const openRouterProviderDefaults = {
5697
5718
  type: "openai-compatible",
5698
5719
  baseURL: "https://openrouter.ai/api/v1",
@@ -14069,6 +14090,142 @@ function defaultSelfUpdateCheckRunner(command, args) {
14069
14090
  });
14070
14091
  }
14071
14092
  //#endregion
14093
+ //#region src/cli/info.ts
14094
+ async function collectTopchesterInfo(options) {
14095
+ const sources = getTopchesterConfigSources(options);
14096
+ const lines = [
14097
+ color("Topchester info", "cyan"),
14098
+ "",
14099
+ section("summary"),
14100
+ row("version", getTopchesterVersion()),
14101
+ row("workspace", formatInfoPath(options.workspaceRoot)),
14102
+ "",
14103
+ section("config"),
14104
+ ...sources.map((source) => {
14105
+ if (!source.path) return row(formatConfigSourceLabel(source.label), status("unset", "muted"));
14106
+ return row(formatConfigSourceLabel(source.label), `${formatInfoPath(source.path)} ${statusBadge(source.exists)}`);
14107
+ })
14108
+ ];
14109
+ let config;
14110
+ try {
14111
+ config = loadTopchesterConfig(options);
14112
+ } catch (error) {
14113
+ lines.push(row("status", status("invalid", "bad")), row("error", ui.error(formatInfoError(error))));
14114
+ return {
14115
+ ok: false,
14116
+ lines
14117
+ };
14118
+ }
14119
+ lines.push(row("status", status("valid", "good")), "", ...formatModelHints(config), "", ...formatProviderHints(config));
14120
+ lines.push("", ...formatMcpHints(config), "", ...formatHooksHints(config), "", ...formatPathHints(options));
14121
+ if (options.devFlags && options.devFlags.length > 0) lines.push("", section("dev"), row("flags", options.devFlags.join(", ")));
14122
+ return {
14123
+ ok: true,
14124
+ lines
14125
+ };
14126
+ }
14127
+ function formatModelHints(config) {
14128
+ const assignments = config.models?.assignments ?? {};
14129
+ const purposes = Object.keys(assignments).sort();
14130
+ if (purposes.length === 0) return [
14131
+ section("models"),
14132
+ row("configured", status("none", "muted")),
14133
+ row("hint", "run /connect openrouter, then /model")
14134
+ ];
14135
+ return [
14136
+ section("models"),
14137
+ row("default purpose", config.models?.defaultPurpose ?? "agent.primary"),
14138
+ ...purposes.map((purpose) => row(purpose, ui.model(formatModelRef(assignments[purpose]))))
14139
+ ];
14140
+ }
14141
+ function formatProviderHints(config) {
14142
+ const providers = config.models?.providers ?? {};
14143
+ const namedProviders = Object.entries(providers).filter(([providerId]) => providerId !== "default");
14144
+ if (namedProviders.length === 0) return [section("providers"), row("configured", status("none", "muted"))];
14145
+ const lines = [section("providers")];
14146
+ if (typeof providers.default === "string") lines.push(row("default", providers.default));
14147
+ for (const [providerId, provider] of namedProviders) {
14148
+ if (typeof provider === "string") continue;
14149
+ const auth = provider.apiKeyEnv ? `env:${provider.apiKeyEnv} ${statusBadge(Boolean(process.env[provider.apiKeyEnv]), "set")}` : provider.apiKey ? status("inline", "good") : status("none", "muted");
14150
+ lines.push(row(providerId, `${provider.type} ${provider.baseURL} auth=${auth}`));
14151
+ }
14152
+ return lines;
14153
+ }
14154
+ function formatMcpHints(config) {
14155
+ const servers = Object.entries(config.mcp ?? {});
14156
+ if (servers.length === 0) return [section("mcp"), row("servers", status("none", "muted"))];
14157
+ return [section("mcp"), ...servers.map(([serverName, server]) => {
14158
+ const tools = server.enabledTools && server.enabledTools.length > 0 ? server.enabledTools.join(",") : "all under cap";
14159
+ const commandFound = Boolean(findExecutable(server.command));
14160
+ return row(serverName, `${server.enabled === false ? status("disabled", "muted") : status("enabled", "good")} command=${server.command} ${statusBadge(commandFound, "found")} tools=${tools}`);
14161
+ })];
14162
+ }
14163
+ function formatHooksHints(config) {
14164
+ const hooks = config.hooks ?? {};
14165
+ const hookEntries = Object.entries(hooks).filter((entry) => Array.isArray(entry[1]) && entry[1].length > 0);
14166
+ const commandCount = hookEntries.reduce((count, [, commands]) => count + commands.length, 0);
14167
+ if (commandCount === 0) return [section("hooks"), row("commands", status("none", "muted"))];
14168
+ return [
14169
+ section("hooks"),
14170
+ row("events", String(hookEntries.length)),
14171
+ row("commands", String(commandCount))
14172
+ ];
14173
+ }
14174
+ function formatPathHints(options) {
14175
+ const knowledgePath = join(options.workspaceRoot, "topchester-kb");
14176
+ return [
14177
+ section("paths"),
14178
+ row("sessions", formatInfoPath(getTopchesterSessionsPath(options.workspaceRoot))),
14179
+ row("log file", formatInfoPath(getTopchesterLogFilePath(options.workspaceRoot))),
14180
+ row("knowledge", `${formatInfoPath(knowledgePath)} ${statusBadge(existsSync(knowledgePath))}`)
14181
+ ];
14182
+ }
14183
+ function section(title) {
14184
+ return color(`${title}:`, "cyan");
14185
+ }
14186
+ function row(label, value) {
14187
+ return ` ${ui.label(label)}: ${value}`;
14188
+ }
14189
+ function statusBadge(ok, okText = "ok") {
14190
+ return `[${status(ok ? okText : "missing", ok ? "good" : "warn")}]`;
14191
+ }
14192
+ function status(text, tone) {
14193
+ switch (tone) {
14194
+ case "good": return ui.ok(text);
14195
+ case "warn": return ui.warn(text);
14196
+ case "bad": return ui.error(text);
14197
+ case "muted": return ui.muted(text);
14198
+ }
14199
+ }
14200
+ function formatConfigSourceLabel(label) {
14201
+ return label === "env" ? "env TOPCHESTER_CONFIG" : label === "cli" ? "cli --config" : label;
14202
+ }
14203
+ function findExecutable(command) {
14204
+ if (command.includes("/") || isAbsolute(command)) return canExecute(command) ? command : void 0;
14205
+ for (const dir of (process.env.PATH ?? "").split(delimiter)) {
14206
+ if (!dir) continue;
14207
+ const candidate = resolve(dir, command);
14208
+ if (canExecute(candidate)) return candidate;
14209
+ }
14210
+ }
14211
+ function canExecute(path) {
14212
+ try {
14213
+ accessSync(path, constants.X_OK);
14214
+ return true;
14215
+ } catch {
14216
+ return false;
14217
+ }
14218
+ }
14219
+ function formatInfoPath(path) {
14220
+ const homeRelative = relative(homedir(), path);
14221
+ if (!homeRelative) return "~";
14222
+ if (!homeRelative.startsWith("..") && !isAbsolute(homeRelative)) return `~/${homeRelative}`;
14223
+ return path;
14224
+ }
14225
+ function formatInfoError(error) {
14226
+ return error instanceof Error ? error.message : String(error);
14227
+ }
14228
+ //#endregion
14072
14229
  //#region src/cli.ts
14073
14230
  async function runTopchesterCli(argv = process.argv, options = {}) {
14074
14231
  const program = createTopchesterProgram();
@@ -14105,6 +14262,11 @@ function createTopchesterProgram() {
14105
14262
  console.log("Topchester local dev mode");
14106
14263
  printStartupSummary(context);
14107
14264
  });
14265
+ program.command("info").description("show config and local runtime hints").action(async () => {
14266
+ const result = await collectTopchesterInfo(getContextOptionsFromProgram(program));
14267
+ console.log(result.lines.join("\n"));
14268
+ if (!result.ok) process.exitCode = 1;
14269
+ });
14108
14270
  program.command("run").description("run one prompt or slash command without opening the TUI").argument("<prompt...>", "prompt text or slash command").option("--model <model>", "override the agent.primary model for this run").option("--timeout <ms>", "timeout for the run in milliseconds", parsePositiveInteger).option("--json", "write JSONL run events to stdout").option("--output-json <path>", "write JSONL run events to a file").action(async (promptParts, options) => {
14109
14271
  const context = createContextFromOptions(program);
14110
14272
  const globalOptions = program.opts();
@@ -14211,12 +14373,15 @@ function printStartupSummary(context) {
14211
14373
  }
14212
14374
  }
14213
14375
  function createContextFromOptions(program) {
14376
+ return createAppContext(getContextOptionsFromProgram(program));
14377
+ }
14378
+ function getContextOptionsFromProgram(program) {
14214
14379
  const options = program.opts();
14215
- return createAppContext({
14380
+ return {
14216
14381
  workspaceRoot: options.workspace,
14217
14382
  configPath: options.config && (isAbsolute(options.config) ? options.config : resolve(cwd(), options.config)),
14218
14383
  devFlags: options.dev
14219
- });
14384
+ };
14220
14385
  }
14221
14386
  async function executeKbSearchCommand(program, queryParts, options) {
14222
14387
  const context = createContextFromOptions(program);
@@ -14269,4 +14434,4 @@ function formatDryRunSyncStatus(status) {
14269
14434
  //#endregion
14270
14435
  export { runTopchesterCli as t };
14271
14436
 
14272
- //# sourceMappingURL=cli-Cz5puJBe.mjs.map
14437
+ //# sourceMappingURL=cli-BgMj4Ifj.mjs.map