@kimbho/kimbho-cli 0.1.1 → 0.1.3

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/index.cjs CHANGED
@@ -1147,9 +1147,9 @@ var require_command = __commonJS({
1147
1147
  "../../node_modules/commander/lib/command.js"(exports2) {
1148
1148
  var EventEmitter = require("node:events").EventEmitter;
1149
1149
  var childProcess = require("node:child_process");
1150
- var path4 = require("node:path");
1150
+ var path6 = require("node:path");
1151
1151
  var fs = require("node:fs");
1152
- var process11 = require("node:process");
1152
+ var process12 = require("node:process");
1153
1153
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1154
1154
  var { CommanderError: CommanderError2 } = require_error();
1155
1155
  var { Help: Help2, stripColor } = require_help();
@@ -1196,13 +1196,13 @@ var require_command = __commonJS({
1196
1196
  this._showSuggestionAfterError = true;
1197
1197
  this._savedState = null;
1198
1198
  this._outputConfiguration = {
1199
- writeOut: (str) => process11.stdout.write(str),
1200
- writeErr: (str) => process11.stderr.write(str),
1199
+ writeOut: (str) => process12.stdout.write(str),
1200
+ writeErr: (str) => process12.stderr.write(str),
1201
1201
  outputError: (str, write) => write(str),
1202
- getOutHelpWidth: () => process11.stdout.isTTY ? process11.stdout.columns : void 0,
1203
- getErrHelpWidth: () => process11.stderr.isTTY ? process11.stderr.columns : void 0,
1204
- getOutHasColors: () => useColor() ?? (process11.stdout.isTTY && process11.stdout.hasColors?.()),
1205
- getErrHasColors: () => useColor() ?? (process11.stderr.isTTY && process11.stderr.hasColors?.()),
1202
+ getOutHelpWidth: () => process12.stdout.isTTY ? process12.stdout.columns : void 0,
1203
+ getErrHelpWidth: () => process12.stderr.isTTY ? process12.stderr.columns : void 0,
1204
+ getOutHasColors: () => useColor() ?? (process12.stdout.isTTY && process12.stdout.hasColors?.()),
1205
+ getErrHasColors: () => useColor() ?? (process12.stderr.isTTY && process12.stderr.hasColors?.()),
1206
1206
  stripColor: (str) => stripColor(str)
1207
1207
  };
1208
1208
  this._hidden = false;
@@ -1585,7 +1585,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1585
1585
  if (this._exitCallback) {
1586
1586
  this._exitCallback(new CommanderError2(exitCode, code, message));
1587
1587
  }
1588
- process11.exit(exitCode);
1588
+ process12.exit(exitCode);
1589
1589
  }
1590
1590
  /**
1591
1591
  * Register callback `fn` for the command.
@@ -1983,16 +1983,16 @@ Expecting one of '${allowedValues.join("', '")}'`);
1983
1983
  }
1984
1984
  parseOptions = parseOptions || {};
1985
1985
  if (argv === void 0 && parseOptions.from === void 0) {
1986
- if (process11.versions?.electron) {
1986
+ if (process12.versions?.electron) {
1987
1987
  parseOptions.from = "electron";
1988
1988
  }
1989
- const execArgv = process11.execArgv ?? [];
1989
+ const execArgv = process12.execArgv ?? [];
1990
1990
  if (execArgv.includes("-e") || execArgv.includes("--eval") || execArgv.includes("-p") || execArgv.includes("--print")) {
1991
1991
  parseOptions.from = "eval";
1992
1992
  }
1993
1993
  }
1994
1994
  if (argv === void 0) {
1995
- argv = process11.argv;
1995
+ argv = process12.argv;
1996
1996
  }
1997
1997
  this.rawArgs = argv.slice();
1998
1998
  let userArgs;
@@ -2003,7 +2003,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2003
2003
  userArgs = argv.slice(2);
2004
2004
  break;
2005
2005
  case "electron":
2006
- if (process11.defaultApp) {
2006
+ if (process12.defaultApp) {
2007
2007
  this._scriptPath = argv[1];
2008
2008
  userArgs = argv.slice(2);
2009
2009
  } else {
@@ -2147,9 +2147,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2147
2147
  let launchWithNode = false;
2148
2148
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2149
2149
  function findFile(baseDir, baseName) {
2150
- const localBin = path4.resolve(baseDir, baseName);
2150
+ const localBin = path6.resolve(baseDir, baseName);
2151
2151
  if (fs.existsSync(localBin)) return localBin;
2152
- if (sourceExt.includes(path4.extname(baseName))) return void 0;
2152
+ if (sourceExt.includes(path6.extname(baseName))) return void 0;
2153
2153
  const foundExt = sourceExt.find(
2154
2154
  (ext) => fs.existsSync(`${localBin}${ext}`)
2155
2155
  );
@@ -2167,17 +2167,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
2167
2167
  } catch {
2168
2168
  resolvedScriptPath = this._scriptPath;
2169
2169
  }
2170
- executableDir = path4.resolve(
2171
- path4.dirname(resolvedScriptPath),
2170
+ executableDir = path6.resolve(
2171
+ path6.dirname(resolvedScriptPath),
2172
2172
  executableDir
2173
2173
  );
2174
2174
  }
2175
2175
  if (executableDir) {
2176
2176
  let localFile = findFile(executableDir, executableFile);
2177
2177
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2178
- const legacyName = path4.basename(
2178
+ const legacyName = path6.basename(
2179
2179
  this._scriptPath,
2180
- path4.extname(this._scriptPath)
2180
+ path6.extname(this._scriptPath)
2181
2181
  );
2182
2182
  if (legacyName !== this._name) {
2183
2183
  localFile = findFile(
@@ -2188,13 +2188,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
2188
2188
  }
2189
2189
  executableFile = localFile || executableFile;
2190
2190
  }
2191
- launchWithNode = sourceExt.includes(path4.extname(executableFile));
2191
+ launchWithNode = sourceExt.includes(path6.extname(executableFile));
2192
2192
  let proc;
2193
- if (process11.platform !== "win32") {
2193
+ if (process12.platform !== "win32") {
2194
2194
  if (launchWithNode) {
2195
2195
  args.unshift(executableFile);
2196
- args = incrementNodeInspectorPort(process11.execArgv).concat(args);
2197
- proc = childProcess.spawn(process11.argv[0], args, { stdio: "inherit" });
2196
+ args = incrementNodeInspectorPort(process12.execArgv).concat(args);
2197
+ proc = childProcess.spawn(process12.argv[0], args, { stdio: "inherit" });
2198
2198
  } else {
2199
2199
  proc = childProcess.spawn(executableFile, args, { stdio: "inherit" });
2200
2200
  }
@@ -2205,13 +2205,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
2205
2205
  subcommand._name
2206
2206
  );
2207
2207
  args.unshift(executableFile);
2208
- args = incrementNodeInspectorPort(process11.execArgv).concat(args);
2209
- proc = childProcess.spawn(process11.execPath, args, { stdio: "inherit" });
2208
+ args = incrementNodeInspectorPort(process12.execArgv).concat(args);
2209
+ proc = childProcess.spawn(process12.execPath, args, { stdio: "inherit" });
2210
2210
  }
2211
2211
  if (!proc.killed) {
2212
2212
  const signals = ["SIGUSR1", "SIGUSR2", "SIGTERM", "SIGINT", "SIGHUP"];
2213
2213
  signals.forEach((signal) => {
2214
- process11.on(signal, () => {
2214
+ process12.on(signal, () => {
2215
2215
  if (proc.killed === false && proc.exitCode === null) {
2216
2216
  proc.kill(signal);
2217
2217
  }
@@ -2222,7 +2222,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2222
2222
  proc.on("close", (code) => {
2223
2223
  code = code ?? 1;
2224
2224
  if (!exitCallback) {
2225
- process11.exit(code);
2225
+ process12.exit(code);
2226
2226
  } else {
2227
2227
  exitCallback(
2228
2228
  new CommanderError2(
@@ -2244,7 +2244,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2244
2244
  throw new Error(`'${executableFile}' not executable`);
2245
2245
  }
2246
2246
  if (!exitCallback) {
2247
- process11.exit(1);
2247
+ process12.exit(1);
2248
2248
  } else {
2249
2249
  const wrappedError = new CommanderError2(
2250
2250
  1,
@@ -2739,13 +2739,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
2739
2739
  */
2740
2740
  _parseOptionsEnv() {
2741
2741
  this.options.forEach((option) => {
2742
- if (option.envVar && option.envVar in process11.env) {
2742
+ if (option.envVar && option.envVar in process12.env) {
2743
2743
  const optionKey = option.attributeName();
2744
2744
  if (this.getOptionValue(optionKey) === void 0 || ["default", "config", "env"].includes(
2745
2745
  this.getOptionValueSource(optionKey)
2746
2746
  )) {
2747
2747
  if (option.required || option.optional) {
2748
- this.emit(`optionEnv:${option.name()}`, process11.env[option.envVar]);
2748
+ this.emit(`optionEnv:${option.name()}`, process12.env[option.envVar]);
2749
2749
  } else {
2750
2750
  this.emit(`optionEnv:${option.name()}`);
2751
2751
  }
@@ -3035,7 +3035,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3035
3035
  * @return {Command}
3036
3036
  */
3037
3037
  nameFromFilename(filename) {
3038
- this._name = path4.basename(filename, path4.extname(filename));
3038
+ this._name = path6.basename(filename, path6.extname(filename));
3039
3039
  return this;
3040
3040
  }
3041
3041
  /**
@@ -3049,9 +3049,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3049
3049
  * @param {string} [path]
3050
3050
  * @return {(string|null|Command)}
3051
3051
  */
3052
- executableDir(path5) {
3053
- if (path5 === void 0) return this._executableDir;
3054
- this._executableDir = path5;
3052
+ executableDir(path7) {
3053
+ if (path7 === void 0) return this._executableDir;
3054
+ this._executableDir = path7;
3055
3055
  return this;
3056
3056
  }
3057
3057
  /**
@@ -3200,7 +3200,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3200
3200
  */
3201
3201
  help(contextOptions) {
3202
3202
  this.outputHelp(contextOptions);
3203
- let exitCode = Number(process11.exitCode ?? 0);
3203
+ let exitCode = Number(process12.exitCode ?? 0);
3204
3204
  if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
3205
3205
  exitCode = 1;
3206
3206
  }
@@ -3290,9 +3290,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3290
3290
  });
3291
3291
  }
3292
3292
  function useColor() {
3293
- if (process11.env.NO_COLOR || process11.env.FORCE_COLOR === "0" || process11.env.FORCE_COLOR === "false")
3293
+ if (process12.env.NO_COLOR || process12.env.FORCE_COLOR === "0" || process12.env.FORCE_COLOR === "false")
3294
3294
  return false;
3295
- if (process11.env.FORCE_COLOR || process11.env.CLICOLOR_FORCE !== void 0)
3295
+ if (process12.env.FORCE_COLOR || process12.env.CLICOLOR_FORCE !== void 0)
3296
3296
  return true;
3297
3297
  return void 0;
3298
3298
  }
@@ -3323,6 +3323,9 @@ var require_commander = __commonJS({
3323
3323
  }
3324
3324
  });
3325
3325
 
3326
+ // src/index.ts
3327
+ var import_node_child_process3 = require("node:child_process");
3328
+
3326
3329
  // ../../node_modules/commander/esm.mjs
3327
3330
  var import_index = __toESM(require_commander(), 1);
3328
3331
  var {
@@ -3343,7 +3346,7 @@ var {
3343
3346
  // package.json
3344
3347
  var package_default = {
3345
3348
  name: "@kimbho/kimbho-cli",
3346
- version: "0.1.1",
3349
+ version: "0.1.3",
3347
3350
  description: "Kimbho CLI is a terminal-native coding agent for planning, execution, and verification.",
3348
3351
  type: "module",
3349
3352
  engines: {
@@ -3559,6 +3562,10 @@ function listAgentProfiles() {
3559
3562
  return Object.values(AGENT_CATALOG).sort((left, right) => left.role.localeCompare(right.role));
3560
3563
  }
3561
3564
 
3565
+ // ../agent-runtime/dist/orchestrator.js
3566
+ var import_promises4 = require("node:fs/promises");
3567
+ var import_node_path4 = __toESM(require("node:path"), 1);
3568
+
3562
3569
  // ../../node_modules/zod/v3/external.js
3563
3570
  var external_exports = {};
3564
3571
  __export(external_exports, {
@@ -4037,8 +4044,8 @@ function getErrorMap() {
4037
4044
 
4038
4045
  // ../../node_modules/zod/v3/helpers/parseUtil.js
4039
4046
  var makeIssue = (params) => {
4040
- const { data, path: path4, errorMaps, issueData } = params;
4041
- const fullPath = [...path4, ...issueData.path || []];
4047
+ const { data, path: path6, errorMaps, issueData } = params;
4048
+ const fullPath = [...path6, ...issueData.path || []];
4042
4049
  const fullIssue = {
4043
4050
  ...issueData,
4044
4051
  path: fullPath
@@ -4154,11 +4161,11 @@ var errorUtil;
4154
4161
 
4155
4162
  // ../../node_modules/zod/v3/types.js
4156
4163
  var ParseInputLazyPath = class {
4157
- constructor(parent, value, path4, key) {
4164
+ constructor(parent, value, path6, key) {
4158
4165
  this._cachedPath = [];
4159
4166
  this.parent = parent;
4160
4167
  this.data = value;
4161
- this._path = path4;
4168
+ this._path = path6;
4162
4169
  this._key = key;
4163
4170
  }
4164
4171
  get path() {
@@ -7808,6 +7815,16 @@ var KimbhoConfigSchema = external_exports.object({
7808
7815
  }
7809
7816
  });
7810
7817
 
7818
+ // ../core/dist/contracts/execution.js
7819
+ var ToolResultSchema = external_exports.object({
7820
+ toolId: external_exports.string().min(1),
7821
+ success: external_exports.boolean(),
7822
+ summary: external_exports.string().min(1),
7823
+ stdout: external_exports.string().optional(),
7824
+ stderr: external_exports.string().optional(),
7825
+ artifacts: external_exports.array(external_exports.string()).default([])
7826
+ });
7827
+
7811
7828
  // ../core/dist/contracts/session.js
7812
7829
  var SessionStatusSchema = external_exports.enum([
7813
7830
  "planned",
@@ -7816,6 +7833,22 @@ var SessionStatusSchema = external_exports.enum([
7816
7833
  "blocked",
7817
7834
  "completed"
7818
7835
  ]);
7836
+ var SessionEventTypeSchema = external_exports.enum([
7837
+ "task-started",
7838
+ "task-completed",
7839
+ "task-blocked",
7840
+ "note"
7841
+ ]);
7842
+ var SessionEventSchema = external_exports.object({
7843
+ id: external_exports.string().min(1),
7844
+ timestamp: external_exports.string().datetime(),
7845
+ type: SessionEventTypeSchema,
7846
+ taskId: external_exports.string().min(1).optional(),
7847
+ agentRole: AgentRoleSchema.optional(),
7848
+ message: external_exports.string().min(1),
7849
+ toolResults: external_exports.array(ToolResultSchema).default([]),
7850
+ artifacts: external_exports.array(external_exports.string()).default([])
7851
+ });
7819
7852
  var SessionSnapshotSchema = external_exports.object({
7820
7853
  id: external_exports.string().min(1),
7821
7854
  goal: external_exports.string().min(1),
@@ -7827,8 +7860,10 @@ var SessionSnapshotSchema = external_exports.object({
7827
7860
  plan: KimbhoPlanSchema,
7828
7861
  readyTaskIds: external_exports.array(external_exports.string()).default([]),
7829
7862
  blockedTaskIds: external_exports.array(external_exports.string()).default([]),
7863
+ completedTaskIds: external_exports.array(external_exports.string()).default([]),
7830
7864
  assignedAgents: external_exports.array(AgentRoleSchema).default([]),
7831
- notes: external_exports.array(external_exports.string()).default([])
7865
+ notes: external_exports.array(external_exports.string()).default([]),
7866
+ events: external_exports.array(SessionEventSchema).default([])
7832
7867
  });
7833
7868
 
7834
7869
  // ../core/dist/config/config.js
@@ -8116,14 +8151,6 @@ var ToolDescriptorSchema = external_exports.object({
8116
8151
  producesArtifacts: external_exports.boolean().default(false),
8117
8152
  allowedRoles: external_exports.array(AgentRoleSchema).default([])
8118
8153
  });
8119
- var ToolResultSchema = external_exports.object({
8120
- toolId: external_exports.string().min(1),
8121
- success: external_exports.boolean(),
8122
- summary: external_exports.string().min(1),
8123
- stdout: external_exports.string().optional(),
8124
- stderr: external_exports.string().optional(),
8125
- artifacts: external_exports.array(external_exports.string()).default([])
8126
- });
8127
8154
 
8128
8155
  // ../tools/dist/registry.js
8129
8156
  var BUILTIN_TOOLS = [
@@ -8233,26 +8260,296 @@ function createDefaultToolRegistry() {
8233
8260
  return new ToolRegistry(BUILTIN_TOOLS);
8234
8261
  }
8235
8262
 
8263
+ // ../tools/dist/runtime.js
8264
+ var import_promises3 = require("node:fs/promises");
8265
+ var import_node_path3 = __toESM(require("node:path"), 1);
8266
+ var import_node_process = __toESM(require("node:process"), 1);
8267
+ var import_node_child_process = require("node:child_process");
8268
+ var import_node_os = require("node:os");
8269
+ var DEFAULT_CAPTURE_LIMIT = 16e3;
8270
+ function truncateOutput(value) {
8271
+ if (!value) {
8272
+ return value;
8273
+ }
8274
+ if (value.length <= DEFAULT_CAPTURE_LIMIT) {
8275
+ return value;
8276
+ }
8277
+ const omitted = value.length - DEFAULT_CAPTURE_LIMIT;
8278
+ return `${value.slice(0, DEFAULT_CAPTURE_LIMIT)}
8279
+ ... [truncated ${omitted} chars]`;
8280
+ }
8281
+ function resolveWorkspacePath(cwd, filePath) {
8282
+ const resolved = import_node_path3.default.resolve(cwd, filePath);
8283
+ const relative = import_node_path3.default.relative(cwd, resolved);
8284
+ if (relative.startsWith("..") || import_node_path3.default.isAbsolute(relative)) {
8285
+ throw new Error(`Path "${filePath}" escapes the workspace.`);
8286
+ }
8287
+ return resolved;
8288
+ }
8289
+ async function runSpawn(command, args, cwd, timeoutMs) {
8290
+ return new Promise((resolve, reject) => {
8291
+ const child = (0, import_node_child_process.spawn)(command, args, {
8292
+ cwd,
8293
+ env: import_node_process.default.env,
8294
+ stdio: [
8295
+ "ignore",
8296
+ "pipe",
8297
+ "pipe"
8298
+ ]
8299
+ });
8300
+ const stdout = [];
8301
+ const stderr = [];
8302
+ let settled = false;
8303
+ let timedOut = false;
8304
+ const timer = setTimeout(() => {
8305
+ timedOut = true;
8306
+ child.kill("SIGTERM");
8307
+ setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
8308
+ }, timeoutMs);
8309
+ child.stdout.on("data", (chunk) => {
8310
+ stdout.push(String(chunk));
8311
+ });
8312
+ child.stderr.on("data", (chunk) => {
8313
+ stderr.push(String(chunk));
8314
+ });
8315
+ child.on("error", (error) => {
8316
+ if (settled) {
8317
+ return;
8318
+ }
8319
+ settled = true;
8320
+ clearTimeout(timer);
8321
+ reject(error);
8322
+ });
8323
+ child.on("close", (code) => {
8324
+ if (settled) {
8325
+ return;
8326
+ }
8327
+ settled = true;
8328
+ clearTimeout(timer);
8329
+ resolve({
8330
+ code,
8331
+ stdout: stdout.join(""),
8332
+ stderr: stderr.join(""),
8333
+ timedOut
8334
+ });
8335
+ });
8336
+ });
8337
+ }
8338
+ async function runShellCommand(toolId, command, cwd, timeoutMs) {
8339
+ const shell = import_node_process.default.env.SHELL ?? "/bin/sh";
8340
+ const result = await runSpawn(shell, [
8341
+ "-lc",
8342
+ command
8343
+ ], cwd, timeoutMs);
8344
+ const success = !result.timedOut && result.code === 0;
8345
+ const summary = result.timedOut ? `Command timed out after ${timeoutMs}ms.` : success ? `Command completed successfully.` : `Command exited with code ${result.code ?? "unknown"}.`;
8346
+ return ToolResultSchema.parse({
8347
+ toolId,
8348
+ success,
8349
+ summary,
8350
+ stdout: truncateOutput(result.stdout),
8351
+ stderr: truncateOutput(result.stderr)
8352
+ });
8353
+ }
8354
+ async function executeFileRead(input, context) {
8355
+ const rawPath = typeof input.path === "string" ? input.path : null;
8356
+ if (!rawPath) {
8357
+ throw new Error("file.read requires a string path.");
8358
+ }
8359
+ const targetPath = resolveWorkspacePath(context.cwd, rawPath);
8360
+ const contents = await (0, import_promises3.readFile)(targetPath, "utf8");
8361
+ return ToolResultSchema.parse({
8362
+ toolId: "file.read",
8363
+ success: true,
8364
+ summary: `Read ${import_node_path3.default.relative(context.cwd, targetPath) || import_node_path3.default.basename(targetPath)}.`,
8365
+ stdout: truncateOutput(contents),
8366
+ artifacts: [
8367
+ targetPath
8368
+ ]
8369
+ });
8370
+ }
8371
+ async function executeShell(input, context, timeoutMs) {
8372
+ const command = typeof input.command === "string" ? input.command : null;
8373
+ if (!command) {
8374
+ throw new Error("shell.exec requires a command string.");
8375
+ }
8376
+ return runShellCommand("shell.exec", command, context.cwd, timeoutMs);
8377
+ }
8378
+ async function executeGitStatus(_input, context, timeoutMs) {
8379
+ return runShellCommand("git.status", "git status --short --branch", context.cwd, timeoutMs);
8380
+ }
8381
+ async function executeTests(input, context, timeoutMs) {
8382
+ const command = typeof input.command === "string" && input.command.trim().length > 0 ? input.command : "npm test";
8383
+ const result = await runShellCommand("tests.run", command, context.cwd, timeoutMs);
8384
+ return ToolResultSchema.parse({
8385
+ ...result,
8386
+ summary: result.success ? `Verification command passed: ${command}` : `Verification command failed: ${command}`
8387
+ });
8388
+ }
8389
+ async function executeFilePatch(input, context, timeoutMs) {
8390
+ const patch = typeof input.patch === "string" ? input.patch : null;
8391
+ if (!patch) {
8392
+ throw new Error("file.patch requires a unified diff in the patch field.");
8393
+ }
8394
+ const tempDir = await (0, import_promises3.mkdtemp)(import_node_path3.default.join((0, import_node_os.tmpdir)(), "kimbho-patch-"));
8395
+ const patchPath = import_node_path3.default.join(tempDir, "change.patch");
8396
+ try {
8397
+ await (0, import_promises3.writeFile)(patchPath, patch, "utf8");
8398
+ const result = await runSpawn("git", [
8399
+ "apply",
8400
+ "--recount",
8401
+ "--whitespace=nowarn",
8402
+ patchPath
8403
+ ], context.cwd, timeoutMs);
8404
+ const success = !result.timedOut && result.code === 0;
8405
+ return ToolResultSchema.parse({
8406
+ toolId: "file.patch",
8407
+ success,
8408
+ summary: success ? "Patch applied successfully." : result.timedOut ? `Patch timed out after ${timeoutMs}ms.` : `Patch failed with code ${result.code ?? "unknown"}.`,
8409
+ stdout: truncateOutput(result.stdout),
8410
+ stderr: truncateOutput(result.stderr),
8411
+ artifacts: []
8412
+ });
8413
+ } finally {
8414
+ await (0, import_promises3.rm)(tempDir, { recursive: true, force: true });
8415
+ }
8416
+ }
8417
+ var ToolRuntime = class {
8418
+ registry;
8419
+ executors;
8420
+ constructor(registry = createDefaultToolRegistry()) {
8421
+ this.registry = registry;
8422
+ this.executors = /* @__PURE__ */ new Map([
8423
+ [
8424
+ "file.read",
8425
+ (input, context) => executeFileRead(input, context)
8426
+ ],
8427
+ [
8428
+ "file.patch",
8429
+ executeFilePatch
8430
+ ],
8431
+ [
8432
+ "shell.exec",
8433
+ executeShell
8434
+ ],
8435
+ [
8436
+ "git.status",
8437
+ executeGitStatus
8438
+ ],
8439
+ [
8440
+ "tests.run",
8441
+ executeTests
8442
+ ]
8443
+ ]);
8444
+ }
8445
+ async run(toolId, input, context) {
8446
+ const descriptor = this.registry.get(toolId);
8447
+ if (!descriptor) {
8448
+ throw new Error(`Unknown tool "${toolId}".`);
8449
+ }
8450
+ const executor = this.executors.get(toolId);
8451
+ if (!executor) {
8452
+ throw new Error(`No executor registered for "${toolId}".`);
8453
+ }
8454
+ return executor(input, context, descriptor.timeoutMs);
8455
+ }
8456
+ };
8457
+
8236
8458
  // ../agent-runtime/dist/orchestrator.js
8237
8459
  function createSessionId() {
8238
8460
  return `session-${Date.now()}`;
8239
8461
  }
8462
+ function createEventId(type, taskId) {
8463
+ return `${type}-${taskId ?? "session"}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
8464
+ }
8240
8465
  function isTaskReady(task, completedTaskIds) {
8241
8466
  return task.status === "pending" && task.dependsOn.every((taskId) => completedTaskIds.has(taskId));
8242
8467
  }
8468
+ function completedTaskIdsFromPlan(plan) {
8469
+ return new Set(flattenPlanTasks(plan).filter((task) => task.status === "completed").map((task) => task.id));
8470
+ }
8471
+ function deriveStatus(plan, readyTasks, blockedTasks) {
8472
+ const tasks = flattenPlanTasks(plan);
8473
+ const completed = tasks.filter((task) => task.status === "completed").length;
8474
+ if (completed === tasks.length && tasks.length > 0) {
8475
+ return "completed";
8476
+ }
8477
+ if (readyTasks.length > 0) {
8478
+ return completed > 0 ? "running" : "ready";
8479
+ }
8480
+ if (blockedTasks.length > 0) {
8481
+ return "blocked";
8482
+ }
8483
+ return "planned";
8484
+ }
8485
+ function updateTaskStatus(plan, taskId, status) {
8486
+ const milestones = plan.milestones.map((milestone) => ({
8487
+ ...milestone,
8488
+ tasks: milestone.tasks.map((task) => task.id === taskId ? {
8489
+ ...task,
8490
+ status
8491
+ } : task)
8492
+ }));
8493
+ return {
8494
+ ...plan,
8495
+ milestones
8496
+ };
8497
+ }
8498
+ function maybeAppendNote(notes, note) {
8499
+ return notes.at(-1) === note ? notes : [
8500
+ ...notes,
8501
+ note
8502
+ ];
8503
+ }
8504
+ function renderToolResultSection(results) {
8505
+ return results.map((result) => {
8506
+ const lines = [
8507
+ `## ${result.toolId}`,
8508
+ `- success: ${result.success ? "yes" : "no"}`,
8509
+ `- summary: ${result.summary}`
8510
+ ];
8511
+ if (result.stdout) {
8512
+ lines.push("");
8513
+ lines.push("```text");
8514
+ lines.push(result.stdout);
8515
+ lines.push("```");
8516
+ }
8517
+ if (result.stderr) {
8518
+ lines.push("");
8519
+ lines.push("```text");
8520
+ lines.push(result.stderr);
8521
+ lines.push("```");
8522
+ }
8523
+ return lines.join("\n");
8524
+ }).join("\n\n");
8525
+ }
8526
+ function extractPackageScripts(toolResults) {
8527
+ const packageResult = toolResults.find((result) => result.artifacts.some((artifact) => artifact.endsWith("package.json")) && result.stdout);
8528
+ if (!packageResult?.stdout) {
8529
+ return [];
8530
+ }
8531
+ try {
8532
+ const parsed = JSON.parse(packageResult.stdout);
8533
+ return Object.keys(parsed.scripts ?? {}).sort();
8534
+ } catch {
8535
+ return [];
8536
+ }
8537
+ }
8243
8538
  var ExecutionOrchestrator = class {
8244
8539
  toolRegistry;
8245
- constructor(toolRegistry = createDefaultToolRegistry()) {
8540
+ toolRuntime;
8541
+ constructor(toolRegistry = createDefaultToolRegistry(), toolRuntime = new ToolRuntime(toolRegistry)) {
8246
8542
  this.toolRegistry = toolRegistry;
8543
+ this.toolRuntime = toolRuntime;
8247
8544
  }
8248
- buildEnvelope(request, plan) {
8545
+ buildEnvelope(request, plan, sessionId = createSessionId()) {
8249
8546
  const tasks = flattenPlanTasks(plan);
8250
- const completedTaskIds = new Set(tasks.filter((task) => task.status === "completed").map((task) => task.id));
8547
+ const completedTaskIds = completedTaskIdsFromPlan(plan);
8251
8548
  const readyTasks = tasks.filter((task) => isTaskReady(task, completedTaskIds));
8252
8549
  const blockedTasks = tasks.filter((task) => task.status !== "completed" && !isTaskReady(task, completedTaskIds));
8253
8550
  const assignedAgents = this.selectAgentsForTasks(readyTasks);
8254
8551
  return {
8255
- sessionId: createSessionId(),
8552
+ sessionId,
8256
8553
  request,
8257
8554
  plan,
8258
8555
  readyTasks,
@@ -8260,9 +8557,9 @@ var ExecutionOrchestrator = class {
8260
8557
  assignedAgents
8261
8558
  };
8262
8559
  }
8263
- createSessionSnapshot(envelope) {
8560
+ createSessionSnapshot(envelope, overrides = {}) {
8264
8561
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
8265
- const status = envelope.readyTasks.length > 0 ? "ready" : "blocked";
8562
+ const status = deriveStatus(envelope.plan, envelope.readyTasks, envelope.blockedTasks);
8266
8563
  return SessionSnapshotSchema.parse({
8267
8564
  id: envelope.sessionId,
8268
8565
  goal: envelope.request.goal,
@@ -8274,11 +8571,75 @@ var ExecutionOrchestrator = class {
8274
8571
  plan: envelope.plan,
8275
8572
  readyTaskIds: envelope.readyTasks.map((task) => task.id),
8276
8573
  blockedTaskIds: envelope.blockedTasks.map((task) => task.id),
8574
+ completedTaskIds: Array.from(completedTaskIdsFromPlan(envelope.plan)),
8277
8575
  assignedAgents: envelope.assignedAgents.map((agent) => agent.role),
8278
8576
  notes: [
8279
- "Initial session snapshot created from the current plan.",
8280
- "Execution tooling is not wired yet; this snapshot captures the ready-task frontier."
8281
- ]
8577
+ "Initial session snapshot created from the current plan."
8578
+ ],
8579
+ events: [],
8580
+ ...overrides
8581
+ });
8582
+ }
8583
+ async continueSession(session, options = {}) {
8584
+ let workingPlan = session.plan;
8585
+ let notes = [
8586
+ ...session.notes
8587
+ ];
8588
+ let events = [
8589
+ ...session.events
8590
+ ];
8591
+ const maxAutoTasks = options.maxAutoTasks ?? 2;
8592
+ let executedTasks = 0;
8593
+ while (executedTasks < maxAutoTasks) {
8594
+ const envelope = this.buildEnvelope(session.request, workingPlan, session.id);
8595
+ const autoTask = envelope.readyTasks.find((task) => this.canAutoExecuteTask(task));
8596
+ if (!autoTask) {
8597
+ const nextTask = envelope.readyTasks[0];
8598
+ if (nextTask) {
8599
+ const note = `Execution paused at ${nextTask.id} (${nextTask.title}): no built-in executor is wired for ${nextTask.agentRole} yet.`;
8600
+ notes = maybeAppendNote(notes, note);
8601
+ }
8602
+ return this.createSessionSnapshot(this.buildEnvelope(session.request, workingPlan, session.id), {
8603
+ startedAt: session.startedAt,
8604
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8605
+ notes,
8606
+ events
8607
+ });
8608
+ }
8609
+ events.push({
8610
+ id: createEventId("task-started", autoTask.id),
8611
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8612
+ type: "task-started",
8613
+ taskId: autoTask.id,
8614
+ agentRole: autoTask.agentRole,
8615
+ message: `Started ${autoTask.id}: ${autoTask.title}`,
8616
+ toolResults: [],
8617
+ artifacts: []
8618
+ });
8619
+ const outcome = await this.executeTask(session.id, autoTask, session.request, workingPlan);
8620
+ workingPlan = updateTaskStatus(workingPlan, autoTask.id, outcome.status === "completed" ? "completed" : "blocked");
8621
+ notes = maybeAppendNote(notes, outcome.summary);
8622
+ events.push({
8623
+ id: createEventId(outcome.status === "completed" ? "task-completed" : "task-blocked", autoTask.id),
8624
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8625
+ type: outcome.status === "completed" ? "task-completed" : "task-blocked",
8626
+ taskId: autoTask.id,
8627
+ agentRole: autoTask.agentRole,
8628
+ message: outcome.summary,
8629
+ toolResults: outcome.toolResults,
8630
+ artifacts: outcome.artifacts
8631
+ });
8632
+ executedTasks += 1;
8633
+ }
8634
+ const postLimitEnvelope = this.buildEnvelope(session.request, workingPlan, session.id);
8635
+ if (postLimitEnvelope.readyTasks.length > 0) {
8636
+ notes = maybeAppendNote(notes, `Auto execution limit reached after ${executedTasks} task${executedTasks === 1 ? "" : "s"}.`);
8637
+ }
8638
+ return this.createSessionSnapshot(postLimitEnvelope, {
8639
+ startedAt: session.startedAt,
8640
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8641
+ notes,
8642
+ events
8282
8643
  });
8283
8644
  }
8284
8645
  selectAgentsForTasks(tasks) {
@@ -8288,6 +8649,107 @@ var ExecutionOrchestrator = class {
8288
8649
  toolsForAgent(role) {
8289
8650
  return this.toolRegistry.byRole(role);
8290
8651
  }
8652
+ canAutoExecuteTask(task) {
8653
+ return task.agentRole === "repo-analyst" || task.agentRole === "planner";
8654
+ }
8655
+ async executeTask(sessionId, task, request, plan) {
8656
+ if (task.agentRole === "repo-analyst") {
8657
+ return this.executeRepoAnalysisTask(sessionId, task, request);
8658
+ }
8659
+ if (task.agentRole === "planner") {
8660
+ return this.executePlannerTask(sessionId, task, request, plan);
8661
+ }
8662
+ return {
8663
+ status: "blocked",
8664
+ summary: `Task ${task.id} is ready, but no executor is available for ${task.agentRole}.`,
8665
+ toolResults: [],
8666
+ artifacts: []
8667
+ };
8668
+ }
8669
+ async executeRepoAnalysisTask(sessionId, task, request) {
8670
+ const context = { cwd: request.cwd };
8671
+ const toolResults = await Promise.all([
8672
+ this.safeRunTool("git.status", {}, context),
8673
+ this.safeRunTool("file.read", { path: "package.json" }, context),
8674
+ this.safeRunTool("file.read", { path: "README.md" }, context)
8675
+ ]);
8676
+ const successfulResults = toolResults.filter((result) => result.success);
8677
+ const scripts = extractPackageScripts(successfulResults);
8678
+ const artifactPath = await this.writeLogArtifact(sessionId, "repo-analysis", request.cwd, [
8679
+ `# Repo Analysis`,
8680
+ ``,
8681
+ `Goal: ${request.goal}`,
8682
+ `Workspace: ${request.cwd}`,
8683
+ `Task: ${task.id} - ${task.title}`,
8684
+ ``,
8685
+ `## Observations`,
8686
+ `- Successful tool probes: ${successfulResults.length}/${toolResults.length}`,
8687
+ `- Workspace state: ${request.workspaceState}`,
8688
+ `- Detected scripts: ${scripts.length > 0 ? scripts.join(", ") : "none"}`,
8689
+ ``,
8690
+ renderToolResultSection(toolResults)
8691
+ ].join("\n"));
8692
+ return {
8693
+ status: "completed",
8694
+ summary: `Completed ${task.id}: captured repo analysis and saved ${import_node_path4.default.basename(artifactPath)}.`,
8695
+ toolResults,
8696
+ artifacts: [
8697
+ artifactPath
8698
+ ]
8699
+ };
8700
+ }
8701
+ async executePlannerTask(sessionId, task, request, plan) {
8702
+ const artifactPath = await this.writeLogArtifact(sessionId, "architecture-brief", request.cwd, [
8703
+ `# Architecture Brief`,
8704
+ ``,
8705
+ `Goal: ${request.goal}`,
8706
+ `Summary: ${plan.summary}`,
8707
+ `Repo Strategy: ${plan.repoStrategy.mode} - ${plan.repoStrategy.reasoning}`,
8708
+ ``,
8709
+ `## Assumptions`,
8710
+ ...plan.assumptions.map((assumption) => `- ${assumption}`),
8711
+ ``,
8712
+ `## Milestones`,
8713
+ ...plan.milestones.flatMap((milestone) => [
8714
+ `### ${milestone.title}`,
8715
+ milestone.objective,
8716
+ ...milestone.tasks.map((milestoneTask) => `- ${milestoneTask.id}: ${milestoneTask.title} [${milestoneTask.agentRole}]`),
8717
+ ``
8718
+ ]),
8719
+ `## Verification`,
8720
+ ...plan.verificationChecklist.map((item) => `- ${item}`)
8721
+ ].join("\n"));
8722
+ return {
8723
+ status: "completed",
8724
+ summary: `Completed ${task.id}: wrote architecture brief ${import_node_path4.default.basename(artifactPath)}.`,
8725
+ toolResults: [],
8726
+ artifacts: [
8727
+ artifactPath
8728
+ ]
8729
+ };
8730
+ }
8731
+ async safeRunTool(toolId, input, context) {
8732
+ try {
8733
+ return await this.toolRuntime.run(toolId, input, context);
8734
+ } catch (error) {
8735
+ const message = error instanceof Error ? error.message : String(error);
8736
+ return {
8737
+ toolId,
8738
+ success: false,
8739
+ summary: `${toolId} failed: ${message}`,
8740
+ stderr: message,
8741
+ artifacts: []
8742
+ };
8743
+ }
8744
+ }
8745
+ async writeLogArtifact(sessionId, label, cwd, content) {
8746
+ await ensureKimbhoDir(cwd);
8747
+ const logsDir = import_node_path4.default.join(resolveKimbhoDir(cwd), "logs");
8748
+ const outputPath = import_node_path4.default.join(logsDir, `${sessionId}-${label}.md`);
8749
+ await (0, import_promises4.writeFile)(outputPath, `${content}
8750
+ `, "utf8");
8751
+ return outputPath;
8752
+ }
8291
8753
  };
8292
8754
 
8293
8755
  // src/commands/agents.ts
@@ -8313,7 +8775,7 @@ function createAgentsCommand() {
8313
8775
  }
8314
8776
 
8315
8777
  // src/commands/brains.ts
8316
- var import_node_process = __toESM(require("node:process"), 1);
8778
+ var import_node_process2 = __toESM(require("node:process"), 1);
8317
8779
 
8318
8780
  // ../brains/dist/templates.js
8319
8781
  var BUILTIN_PROVIDER_TEMPLATES = [
@@ -8389,8 +8851,8 @@ function buildProviderFromTemplate(templateId, options = {}) {
8389
8851
  }
8390
8852
 
8391
8853
  // ../brains/dist/registry.js
8392
- var import_promises3 = require("node:fs/promises");
8393
- var import_node_path3 = __toESM(require("node:path"), 1);
8854
+ var import_promises5 = require("node:fs/promises");
8855
+ var import_node_path5 = __toESM(require("node:path"), 1);
8394
8856
  var import_node_url = require("node:url");
8395
8857
  function resolveApiKey(definition) {
8396
8858
  if (!definition.apiKeyEnv) {
@@ -8446,7 +8908,7 @@ function buildProviderHeaders(definition, apiKey, includeJsonContentType = true)
8446
8908
  }
8447
8909
  function filterModels(models, input = {}) {
8448
8910
  const normalized = input.search?.trim().toLowerCase();
8449
- const filtered = normalized ? models.filter((model) => [model.id, model.name, model.description].filter((value) => Boolean(value)).some((value) => value.toLowerCase().includes(normalized))) : models;
8911
+ const filtered = normalized ? models.filter((model) => [model.id, model.name].filter((value) => Boolean(value)).some((value) => value.toLowerCase().includes(normalized))) : models;
8450
8912
  return typeof input.limit === "number" ? filtered.slice(0, input.limit) : filtered;
8451
8913
  }
8452
8914
  function mapOpenAIStyleModels(providerId, payload, input) {
@@ -8828,8 +9290,8 @@ async function createCustomModuleProvider(definition, cwd) {
8828
9290
  if (!definition.modulePath) {
8829
9291
  throw new Error(`Provider "${definition.id}" requires modulePath.`);
8830
9292
  }
8831
- const modulePath = import_node_path3.default.isAbsolute(definition.modulePath) ? definition.modulePath : import_node_path3.default.join(cwd, definition.modulePath);
8832
- await (0, import_promises3.access)(modulePath);
9293
+ const modulePath = import_node_path5.default.isAbsolute(definition.modulePath) ? definition.modulePath : import_node_path5.default.join(cwd, definition.modulePath);
9294
+ await (0, import_promises5.access)(modulePath);
8833
9295
  const module2 = await import((0, import_node_url.pathToFileURL)(modulePath).href);
8834
9296
  const createProvider = typeof module2.createProvider === "function" ? module2.createProvider : typeof module2.default === "function" ? module2.default : null;
8835
9297
  if (!createProvider) {
@@ -8978,6 +9440,40 @@ function renderPlan(plan) {
8978
9440
  function renderJson(value) {
8979
9441
  return JSON.stringify(value, null, 2);
8980
9442
  }
9443
+ function renderSession(session) {
9444
+ const taskCounts = countTasksByStatus(session.plan);
9445
+ const lines = [
9446
+ `Session: ${session.id}`,
9447
+ `Goal: ${session.goal}`,
9448
+ `Status: ${session.status}`,
9449
+ `Tasks: ${flattenPlanTasks(session.plan).length} total | completed ${taskCounts.completed} | pending ${taskCounts.pending} | blocked ${taskCounts.blocked}`,
9450
+ `Assigned agents: ${session.assignedAgents.join(", ") || "none"}`,
9451
+ `Ready tasks: ${session.readyTaskIds.join(", ") || "none"}`,
9452
+ `Blocked tasks: ${session.blockedTaskIds.join(", ") || "none"}`
9453
+ ];
9454
+ if (session.notes.length > 0) {
9455
+ lines.push("");
9456
+ lines.push("Notes:");
9457
+ for (const note of session.notes.slice(-5)) {
9458
+ lines.push(`- ${note}`);
9459
+ }
9460
+ }
9461
+ if (session.events.length > 0) {
9462
+ lines.push("");
9463
+ lines.push("Recent Events:");
9464
+ for (const event of session.events.slice(-5)) {
9465
+ const target = event.taskId ? `${event.taskId}` : "session";
9466
+ lines.push(`- ${event.type} | ${target} | ${event.message}`);
9467
+ for (const artifact of event.artifacts) {
9468
+ lines.push(` artifact: ${artifact}`);
9469
+ }
9470
+ for (const toolResult of event.toolResults) {
9471
+ lines.push(` tool: ${toolResult.toolId} | ${toolResult.success ? "ok" : "fail"} | ${toolResult.summary}`);
9472
+ }
9473
+ }
9474
+ }
9475
+ return lines.join("\n");
9476
+ }
8981
9477
 
8982
9478
  // src/commands/brains.ts
8983
9479
  function parseNumber(value) {
@@ -8993,7 +9489,7 @@ function requireConfigMessage() {
8993
9489
  function createBrainsCommand() {
8994
9490
  const command = new Command("brains").description("Inspect and assign the LLM brains used by Kimbho roles.");
8995
9491
  command.command("list").description("List brain-role assignments.").option("--json", "Print roles as JSON", false).action(async (options) => {
8996
- const config = await loadConfig(import_node_process.default.cwd());
9492
+ const config = await loadConfig(import_node_process2.default.cwd());
8997
9493
  if (!config) {
8998
9494
  requireConfigMessage();
8999
9495
  }
@@ -9023,7 +9519,7 @@ function createBrainsCommand() {
9023
9519
  }
9024
9520
  });
9025
9521
  command.command("assign").description("Assign a provider/model pair to a brain role.").requiredOption("--role <role>", "Brain role: planner, coder, reviewer, or fast").requiredOption("--provider <provider>", "Provider id to assign").option("--model <model>", "Model override for this role").option("--temperature <value>", "Temperature override", parseNumber).option("--max-tokens <value>", "Max token override", parseNumber).option("--prompt-preamble <text>", "Additional prompt preamble for this role").action(async (options) => {
9026
- const config = await loadConfig(import_node_process.default.cwd());
9522
+ const config = await loadConfig(import_node_process2.default.cwd());
9027
9523
  if (!config) {
9028
9524
  requireConfigMessage();
9029
9525
  }
@@ -9047,12 +9543,12 @@ function createBrainsCommand() {
9047
9543
  promptPreamble: options.promptPreamble
9048
9544
  } : {}
9049
9545
  });
9050
- const outputPath = await saveConfig(nextConfig, import_node_process.default.cwd());
9546
+ const outputPath = await saveConfig(nextConfig, import_node_process2.default.cwd());
9051
9547
  console.log(`Updated ${outputPath}`);
9052
9548
  console.log(`Brain ${role} -> ${provider.id}/${options.model ?? provider.defaultModel ?? "model not set"}`);
9053
9549
  });
9054
9550
  command.command("test").description("Send a prompt through one configured brain role.").argument("<role>", "Brain role to invoke").argument("<prompt>", "Prompt to send").action(async (role, prompt) => {
9055
- const config = await loadConfig(import_node_process.default.cwd());
9551
+ const config = await loadConfig(import_node_process2.default.cwd());
9056
9552
  if (!config) {
9057
9553
  requireConfigMessage();
9058
9554
  }
@@ -9078,10 +9574,10 @@ function createBrainsCommand() {
9078
9574
  }
9079
9575
 
9080
9576
  // src/commands/doctor.ts
9081
- var import_node_process2 = __toESM(require("node:process"), 1);
9082
- var import_node_child_process = require("node:child_process");
9577
+ var import_node_process3 = __toESM(require("node:process"), 1);
9578
+ var import_node_child_process2 = require("node:child_process");
9083
9579
  function commandVersion(binary) {
9084
- const result = (0, import_node_child_process.spawnSync)(binary, ["--version"], {
9580
+ const result = (0, import_node_child_process2.spawnSync)(binary, ["--version"], {
9085
9581
  encoding: "utf8"
9086
9582
  });
9087
9583
  if (result.status === 0) {
@@ -9104,19 +9600,19 @@ function createDoctorCommand() {
9104
9600
  {
9105
9601
  name: "node",
9106
9602
  ok: true,
9107
- detail: import_node_process2.default.version
9603
+ detail: import_node_process3.default.version
9108
9604
  },
9109
9605
  commandVersion("npm"),
9110
9606
  commandVersion("git"),
9111
9607
  commandVersion("docker")
9112
9608
  ];
9113
- const config = await loadConfig(import_node_process2.default.cwd());
9114
- const registry = createDefaultBrainProviderRegistry(import_node_process2.default.cwd());
9609
+ const config = await loadConfig(import_node_process3.default.cwd());
9610
+ const registry = createDefaultBrainProviderRegistry(import_node_process3.default.cwd());
9115
9611
  if (config) {
9116
9612
  checks.push({
9117
9613
  name: "config",
9118
9614
  ok: true,
9119
- detail: `${resolveConfigPath(import_node_process2.default.cwd())} (${config.providers.length} providers)`
9615
+ detail: `${resolveConfigPath(import_node_process3.default.cwd())} (${config.providers.length} providers)`
9120
9616
  });
9121
9617
  for (const provider of config.providers) {
9122
9618
  try {
@@ -9153,7 +9649,7 @@ function createDoctorCommand() {
9153
9649
  checks.push({
9154
9650
  name: "config",
9155
9651
  ok: false,
9156
- detail: `missing (${resolveConfigPath(import_node_process2.default.cwd())})`
9652
+ detail: `missing (${resolveConfigPath(import_node_process3.default.cwd())})`
9157
9653
  });
9158
9654
  }
9159
9655
  for (const check of checks) {
@@ -9163,13 +9659,13 @@ function createDoctorCommand() {
9163
9659
  }
9164
9660
 
9165
9661
  // src/commands/init.ts
9166
- var import_node_process3 = __toESM(require("node:process"), 1);
9662
+ var import_node_process4 = __toESM(require("node:process"), 1);
9167
9663
  function createInitCommand() {
9168
9664
  return new Command("init").description("Create a local .kimbho/config.json file with an initial brain/provider setup.").option("--template <template>", "Provider template to use for the initial provider").option("--provider-id <id>", "Identifier for the initial provider").option("--driver <driver>", "Provider driver to use when no template is supplied").option("--label <label>", "Human-readable provider label").option("--model <model>", "Default model to use for planner/coder/reviewer brains").option("--fast-model <model>", "Model to use for the fast utility brain").option("--api-key-env <env>", "Environment variable that stores the provider API key").option("--base-url <url>", "Base URL or endpoint for the provider driver").option("--module-path <path>", "Module path for a custom provider driver").option("--force", "Overwrite an existing config file", false).action(async (options) => {
9169
- const existing = await loadConfig(import_node_process3.default.cwd());
9665
+ const existing = await loadConfig(import_node_process4.default.cwd());
9170
9666
  if (existing && !options.force) {
9171
9667
  console.error("Config already exists. Re-run with --force to overwrite it.");
9172
- import_node_process3.default.exitCode = 1;
9668
+ import_node_process4.default.exitCode = 1;
9173
9669
  return;
9174
9670
  }
9175
9671
  const templateId = options.template ?? (!options.driver ? "openai" : null);
@@ -9203,13 +9699,13 @@ function createInitCommand() {
9203
9699
  defaultModel: options.model ?? provider.defaultModel,
9204
9700
  fastModel: options.fastModel ?? options.model ?? provider.defaultModel
9205
9701
  });
9206
- const outputPath = await saveConfig(nextConfig, import_node_process3.default.cwd());
9702
+ const outputPath = await saveConfig(nextConfig, import_node_process4.default.cwd());
9207
9703
  console.log(`Created ${outputPath}`);
9208
9704
  });
9209
9705
  }
9210
9706
 
9211
9707
  // src/commands/models.ts
9212
- var import_node_process4 = __toESM(require("node:process"), 1);
9708
+ var import_node_process5 = __toESM(require("node:process"), 1);
9213
9709
  function parseInteger(value) {
9214
9710
  const parsed = Number.parseInt(value, 10);
9215
9711
  if (!Number.isFinite(parsed)) {
@@ -9249,18 +9745,26 @@ function renderModelLine(model) {
9249
9745
  ].filter((value) => Boolean(value));
9250
9746
  return details.length > 0 ? `${model.id} | ${details.join(" | ")}` : model.id;
9251
9747
  }
9748
+ function filterCachedModels(models, options) {
9749
+ const normalizedSearch = options.search?.trim().toLowerCase();
9750
+ const filtered = normalizedSearch ? models.filter((model) => model.id.toLowerCase().includes(normalizedSearch)) : models;
9751
+ return typeof options.limit === "number" ? filtered.slice(0, options.limit) : filtered;
9752
+ }
9252
9753
  async function fetchProviderModels(provider, options) {
9253
- const cachedModels = provider.models.map((modelId) => ({
9254
- id: modelId,
9255
- providerId: provider.id
9256
- }));
9754
+ const cachedModels = filterCachedModels(
9755
+ provider.models.map((modelId) => ({
9756
+ id: modelId,
9757
+ providerId: provider.id
9758
+ })),
9759
+ options
9760
+ );
9257
9761
  if (options.cached) {
9258
9762
  return {
9259
9763
  source: "cache",
9260
9764
  models: cachedModels
9261
9765
  };
9262
9766
  }
9263
- const registry = createDefaultBrainProviderRegistry(import_node_process4.default.cwd());
9767
+ const registry = createDefaultBrainProviderRegistry(import_node_process5.default.cwd());
9264
9768
  try {
9265
9769
  const models = await registry.listModels(provider, {
9266
9770
  ...options.search ? {
@@ -9287,7 +9791,7 @@ async function fetchProviderModels(provider, options) {
9287
9791
  function createModelsCommand() {
9288
9792
  const command = new Command("models").description("Discover and select models from configured providers.");
9289
9793
  command.command("list").description("List models for one provider or all configured providers.").argument("[provider]", "Optional provider id to inspect").option("--search <term>", "Filter the returned models by search text").option("--limit <count>", "Maximum number of models to print", parseInteger, 25).option("--cached", "Use cached models from config instead of remote discovery", false).option("--sync", "Persist discovered model ids back into config", false).option("--json", "Print models as JSON", false).action(async (providerId, options) => {
9290
- let config = await loadConfig(import_node_process4.default.cwd());
9794
+ let config = await loadConfig(import_node_process5.default.cwd());
9291
9795
  if (!config) {
9292
9796
  requireConfigMessage2();
9293
9797
  }
@@ -9316,7 +9820,7 @@ function createModelsCommand() {
9316
9820
  }
9317
9821
  }
9318
9822
  if (mutated) {
9319
- await saveConfig(config, import_node_process4.default.cwd());
9823
+ await saveConfig(config, import_node_process5.default.cwd());
9320
9824
  }
9321
9825
  if (options.json) {
9322
9826
  console.log(renderJson(groups));
@@ -9333,7 +9837,7 @@ function createModelsCommand() {
9333
9837
  }
9334
9838
  });
9335
9839
  command.command("sync").description("Fetch remote models for one provider and cache the ids in config.").argument("<provider>", "Provider id to sync").option("--search <term>", "Optional search filter to limit what gets cached").option("--limit <count>", "Maximum number of models to cache", parseInteger, 200).action(async (providerId, options) => {
9336
- const config = await loadConfig(import_node_process4.default.cwd());
9840
+ const config = await loadConfig(import_node_process5.default.cwd());
9337
9841
  if (!config) {
9338
9842
  requireConfigMessage2();
9339
9843
  }
@@ -9341,7 +9845,7 @@ function createModelsCommand() {
9341
9845
  if (!provider) {
9342
9846
  throw new Error(`Unknown provider "${providerId}".`);
9343
9847
  }
9344
- const registry = createDefaultBrainProviderRegistry(import_node_process4.default.cwd());
9848
+ const registry = createDefaultBrainProviderRegistry(import_node_process5.default.cwd());
9345
9849
  const models = await registry.listModels(provider, {
9346
9850
  ...options.search ? {
9347
9851
  search: options.search
@@ -9352,12 +9856,12 @@ function createModelsCommand() {
9352
9856
  ...provider,
9353
9857
  models: Array.from(new Set(models.map((model) => model.id)))
9354
9858
  });
9355
- const outputPath = await saveConfig(nextConfig, import_node_process4.default.cwd());
9859
+ const outputPath = await saveConfig(nextConfig, import_node_process5.default.cwd());
9356
9860
  console.log(`Updated ${outputPath}`);
9357
9861
  console.log(`Synced ${models.length} models for ${provider.id}`);
9358
9862
  });
9359
9863
  command.command("use").description("Select a model for a provider and optionally assign it to a brain role.").argument("<model>", "Model id to select").requiredOption("--provider <provider>", "Provider id to use").option("--role <role>", "Brain role to assign: planner, coder, reviewer, or fast").option("--set-default", "Also set this model as the provider default", false).option("--force", "Skip remote model validation", false).option("--temperature <value>", "Temperature override for the role", parseNumber2).option("--max-tokens <value>", "Max token override for the role", parseInteger).action(async (model, options) => {
9360
- let config = await loadConfig(import_node_process4.default.cwd());
9864
+ let config = await loadConfig(import_node_process5.default.cwd());
9361
9865
  if (!config) {
9362
9866
  requireConfigMessage2();
9363
9867
  }
@@ -9367,7 +9871,7 @@ function createModelsCommand() {
9367
9871
  }
9368
9872
  if (!options.force) {
9369
9873
  try {
9370
- const registry = createDefaultBrainProviderRegistry(import_node_process4.default.cwd());
9874
+ const registry = createDefaultBrainProviderRegistry(import_node_process5.default.cwd());
9371
9875
  const models = await registry.listModels(provider, {
9372
9876
  search: model,
9373
9877
  limit: 500
@@ -9407,7 +9911,7 @@ function createModelsCommand() {
9407
9911
  } : {}
9408
9912
  });
9409
9913
  }
9410
- const outputPath = await saveConfig(config, import_node_process4.default.cwd());
9914
+ const outputPath = await saveConfig(config, import_node_process5.default.cwd());
9411
9915
  console.log(`Updated ${outputPath}`);
9412
9916
  console.log(`Selected ${provider.id}/${model}`);
9413
9917
  if (options.role) {
@@ -9421,8 +9925,8 @@ function createModelsCommand() {
9421
9925
  }
9422
9926
 
9423
9927
  // src/commands/plan.ts
9424
- var import_promises4 = require("node:fs/promises");
9425
- var import_node_process5 = __toESM(require("node:process"), 1);
9928
+ var import_promises6 = require("node:fs/promises");
9929
+ var import_node_process6 = __toESM(require("node:process"), 1);
9426
9930
 
9427
9931
  // ../planner/dist/planner.js
9428
9932
  function normalizeGoal(goal) {
@@ -9726,7 +10230,7 @@ function createPlan(input) {
9726
10230
 
9727
10231
  // src/commands/plan.ts
9728
10232
  async function detectWorkspaceState(cwd) {
9729
- const entries = await (0, import_promises4.readdir)(cwd);
10233
+ const entries = await (0, import_promises6.readdir)(cwd);
9730
10234
  const meaningfulEntries = entries.filter((entry) => entry !== ".kimbho" && !entry.startsWith("."));
9731
10235
  return meaningfulEntries.length === 0 ? "empty" : "existing";
9732
10236
  }
@@ -9737,7 +10241,7 @@ function createPlanCommand() {
9737
10241
  (value, previous = []) => [...previous, value],
9738
10242
  []
9739
10243
  ).option("--json", "Print the plan as JSON", false).action(async (goal, options) => {
9740
- const cwd = import_node_process5.default.cwd();
10244
+ const cwd = import_node_process6.default.cwd();
9741
10245
  const request = {
9742
10246
  goal,
9743
10247
  mode: "plan",
@@ -9774,14 +10278,14 @@ function createPlanCommand() {
9774
10278
  }
9775
10279
 
9776
10280
  // src/commands/providers.ts
9777
- var import_node_process6 = __toESM(require("node:process"), 1);
10281
+ var import_node_process7 = __toESM(require("node:process"), 1);
9778
10282
  function requireConfigMessage3() {
9779
10283
  throw new Error("No config found. Run `kimbho init` first.");
9780
10284
  }
9781
10285
  function createProvidersCommand() {
9782
10286
  const command = new Command("providers").description("Manage configured model providers and built-in templates.");
9783
10287
  command.command("list").description("List configured providers.").option("--json", "Print providers as JSON", false).action(async (options) => {
9784
- const config = await loadConfig(import_node_process6.default.cwd());
10288
+ const config = await loadConfig(import_node_process7.default.cwd());
9785
10289
  if (!config) {
9786
10290
  requireConfigMessage3();
9787
10291
  }
@@ -9822,7 +10326,7 @@ function createProvidersCommand() {
9822
10326
  (value, previous = []) => [...previous, value],
9823
10327
  []
9824
10328
  ).option("--module-path <path>", "Module path for a custom provider driver").action(async (options) => {
9825
- const config = await loadConfig(import_node_process6.default.cwd());
10329
+ const config = await loadConfig(import_node_process7.default.cwd());
9826
10330
  if (!config) {
9827
10331
  requireConfigMessage3();
9828
10332
  }
@@ -9856,16 +10360,16 @@ function createProvidersCommand() {
9856
10360
  } : {}
9857
10361
  });
9858
10362
  const nextConfig = upsertProvider(config, provider);
9859
- const outputPath = await saveConfig(nextConfig, import_node_process6.default.cwd());
10363
+ const outputPath = await saveConfig(nextConfig, import_node_process7.default.cwd());
9860
10364
  console.log(`Updated ${outputPath}`);
9861
10365
  console.log(`Provider ${provider.id} -> ${provider.driver}`);
9862
10366
  });
9863
10367
  command.command("check").description("Run health checks for the configured providers.").action(async () => {
9864
- const config = await loadConfig(import_node_process6.default.cwd());
10368
+ const config = await loadConfig(import_node_process7.default.cwd());
9865
10369
  if (!config) {
9866
10370
  requireConfigMessage3();
9867
10371
  }
9868
- const registry = createDefaultBrainProviderRegistry(import_node_process6.default.cwd());
10372
+ const registry = createDefaultBrainProviderRegistry(import_node_process7.default.cwd());
9869
10373
  for (const provider of config.providers) {
9870
10374
  try {
9871
10375
  const result = await registry.healthCheck(provider);
@@ -9880,52 +10384,67 @@ function createProvidersCommand() {
9880
10384
  }
9881
10385
 
9882
10386
  // src/commands/resume.ts
9883
- var import_node_process7 = __toESM(require("node:process"), 1);
10387
+ var import_node_process8 = __toESM(require("node:process"), 1);
10388
+ function parseCount(value) {
10389
+ const parsed = Number(value);
10390
+ if (!Number.isInteger(parsed) || parsed <= 0) {
10391
+ throw new Error(`Expected a positive integer, received "${value}".`);
10392
+ }
10393
+ return parsed;
10394
+ }
9884
10395
  function createResumeCommand() {
9885
- return new Command("resume").description("Resume the latest saved Kimbho session snapshot.").option("--json", "Print the latest session as JSON", false).action(async (options) => {
9886
- const session = await loadLatestSession(import_node_process7.default.cwd());
10396
+ return new Command("resume").description("Resume the latest saved Kimbho session snapshot.").option("--json", "Print the latest session as JSON", false).option("--execute", "Continue auto-executing the ready-task frontier", false).option("--max-auto-tasks <count>", "Maximum ready tasks to auto-execute", parseCount, 2).action(async (options) => {
10397
+ const cwd = import_node_process8.default.cwd();
10398
+ const session = await loadLatestSession(cwd);
9887
10399
  if (!session) {
9888
10400
  console.error("No saved session found. Run `kimbho run` first.");
9889
- import_node_process7.default.exitCode = 1;
10401
+ import_node_process8.default.exitCode = 1;
9890
10402
  return;
9891
10403
  }
10404
+ const snapshot = options.execute ? await new ExecutionOrchestrator().continueSession(session, {
10405
+ maxAutoTasks: options.maxAutoTasks
10406
+ }) : session;
10407
+ if (options.execute) {
10408
+ await saveSession(snapshot, cwd);
10409
+ }
9892
10410
  if (options.json) {
9893
- console.log(renderJson(session));
10411
+ console.log(renderJson(snapshot));
9894
10412
  return;
9895
10413
  }
9896
- console.log(`Session: ${session.id}`);
9897
- console.log(`Goal: ${session.goal}`);
9898
- console.log(`Status: ${session.status}`);
9899
- console.log(`Ready tasks: ${session.readyTaskIds.join(", ") || "none"}`);
9900
- console.log(`Blocked tasks: ${session.blockedTaskIds.join(", ") || "none"}`);
9901
- console.log(`Assigned agents: ${session.assignedAgents.join(", ") || "none"}`);
9902
- console.log(`Notes: ${session.notes.join(" | ") || "none"}`);
10414
+ console.log(renderSession(snapshot));
9903
10415
  });
9904
10416
  }
9905
10417
 
9906
10418
  // src/commands/run.ts
9907
- var import_promises5 = require("node:fs/promises");
9908
- var import_node_process8 = __toESM(require("node:process"), 1);
10419
+ var import_promises7 = require("node:fs/promises");
10420
+ var import_node_process9 = __toESM(require("node:process"), 1);
9909
10421
  async function detectWorkspaceState2(cwd) {
9910
- const entries = await (0, import_promises5.readdir)(cwd);
10422
+ const entries = await (0, import_promises7.readdir)(cwd);
9911
10423
  const meaningfulEntries = entries.filter((entry) => entry !== ".kimbho" && !entry.startsWith("."));
9912
10424
  return meaningfulEntries.length === 0 ? "empty" : "existing";
9913
10425
  }
10426
+ function parseCount2(value) {
10427
+ const parsed = Number(value);
10428
+ if (!Number.isInteger(parsed) || parsed <= 0) {
10429
+ throw new Error(`Expected a positive integer, received "${value}".`);
10430
+ }
10431
+ return parsed;
10432
+ }
9914
10433
  function createRunCommand() {
9915
10434
  return new Command("run").description("Create or load a plan, then open a runnable Kimbho session snapshot.").argument("[goal]", "Optional goal; if omitted, Kimbho will use the latest saved plan").option("--stack <stack>", "Preferred stack or preset").option(
9916
10435
  "--constraint <constraint>",
9917
10436
  "Explicit execution constraint; can be provided multiple times",
9918
10437
  (value, previous = []) => [...previous, value],
9919
10438
  []
9920
- ).option("--json", "Print the session snapshot as JSON", false).action(async (goal, options) => {
9921
- const cwd = import_node_process8.default.cwd();
10439
+ ).option("--json", "Print the session snapshot as JSON", false).option("--snapshot-only", "Create the session without auto-executing ready tasks", false).option("--max-auto-tasks <count>", "Maximum ready tasks to auto-execute", parseCount2, 2).action(async (goal, options) => {
10440
+ const cwd = import_node_process9.default.cwd();
9922
10441
  const orchestrator = new ExecutionOrchestrator();
9923
10442
  let request;
9924
10443
  let planPath = null;
9925
10444
  let plan = goal ? null : await loadLatestPlan(cwd);
9926
10445
  if (!plan && !goal) {
9927
10446
  console.error("No saved plan found. Pass a goal or run `kimbho plan` first.");
9928
- import_node_process8.default.exitCode = 1;
10447
+ import_node_process9.default.exitCode = 1;
9929
10448
  return;
9930
10449
  }
9931
10450
  if (goal) {
@@ -9950,7 +10469,10 @@ function createRunCommand() {
9950
10469
  };
9951
10470
  }
9952
10471
  const envelope = orchestrator.buildEnvelope(request, plan);
9953
- const snapshot = orchestrator.createSessionSnapshot(envelope);
10472
+ const initialSnapshot = orchestrator.createSessionSnapshot(envelope);
10473
+ const snapshot = options.snapshotOnly ? initialSnapshot : await orchestrator.continueSession(initialSnapshot, {
10474
+ maxAutoTasks: options.maxAutoTasks
10475
+ });
9954
10476
  const sessionPath = await saveSession(snapshot, cwd);
9955
10477
  if (options.json) {
9956
10478
  console.log(
@@ -9964,15 +10486,12 @@ function createRunCommand() {
9964
10486
  }
9965
10487
  console.log(renderPlan(plan));
9966
10488
  console.log("");
9967
- console.log(`Session: ${snapshot.id}`);
9968
- console.log(`Saved session: ${sessionPath}`);
10489
+ console.log(renderSession(snapshot));
10490
+ console.log("");
9969
10491
  if (planPath) {
9970
10492
  console.log(`Saved plan: ${planPath}`);
9971
10493
  }
9972
- console.log(`Assigned agents: ${snapshot.assignedAgents.join(", ") || "none"}`);
9973
- console.log(`Ready tasks: ${snapshot.readyTaskIds.join(", ") || "none"}`);
9974
- console.log(`Blocked tasks: ${snapshot.blockedTaskIds.join(", ") || "none"}`);
9975
- console.log("Next step: wire executors into `run` so ready tasks can be acted on instead of only snapshotted.");
10494
+ console.log(`Saved session: ${sessionPath}`);
9976
10495
  });
9977
10496
  }
9978
10497
 
@@ -10040,14 +10559,15 @@ function createProgram(onOpenShell) {
10040
10559
  }
10041
10560
 
10042
10561
  // src/shell.ts
10043
- var import_promises6 = require("node:readline/promises");
10044
- var import_node_process9 = __toESM(require("node:process"), 1);
10562
+ var import_promises8 = require("node:readline/promises");
10563
+ var import_node_process10 = __toESM(require("node:process"), 1);
10045
10564
  var AMBER = "\x1B[38;5;214m";
10046
10565
  var BOLD = "\x1B[1m";
10047
10566
  var DIM = "\x1B[2m";
10048
10567
  var RESET = "\x1B[0m";
10049
10568
  var TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set([
10050
10569
  "agents",
10570
+ "brain",
10051
10571
  "brains",
10052
10572
  "doctor",
10053
10573
  "fix",
@@ -10057,15 +10577,30 @@ var TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set([
10057
10577
  "models",
10058
10578
  "new",
10059
10579
  "plan",
10580
+ "provider",
10060
10581
  "providers",
10061
10582
  "quit",
10062
10583
  "resume",
10063
10584
  "review",
10064
10585
  "run",
10065
10586
  "scaffold",
10587
+ "select",
10066
10588
  "shell",
10067
- "status"
10589
+ "status",
10590
+ "use-model"
10068
10591
  ]);
10592
+ var MODEL_SUBCOMMANDS = /* @__PURE__ */ new Set([
10593
+ "help",
10594
+ "list",
10595
+ "sync",
10596
+ "use"
10597
+ ]);
10598
+ var BRAIN_ROLES = [
10599
+ "planner",
10600
+ "coder",
10601
+ "reviewer",
10602
+ "fast"
10603
+ ];
10069
10604
  function color(code, value) {
10070
10605
  return `${code}${value}${RESET}`;
10071
10606
  }
@@ -10078,12 +10613,14 @@ function renderBanner() {
10078
10613
  "|_|\\_\\_|_| |_|____/|____/|_| |_|\\___/ "
10079
10614
  ].map((line) => color(AMBER, line)).join("\n");
10080
10615
  }
10081
- async function getShellSessionState(cwd) {
10616
+ async function getShellSessionState(cwd, focusRole) {
10082
10617
  const config = await loadConfig(cwd);
10083
10618
  if (!config) {
10084
10619
  return {
10620
+ focusRole,
10085
10621
  providerLabel: "unconfigured",
10086
10622
  providerId: "-",
10623
+ focusModel: "not set",
10087
10624
  coderModel: "not set",
10088
10625
  plannerModel: "not set",
10089
10626
  approvalMode: "manual",
@@ -10092,11 +10629,13 @@ async function getShellSessionState(cwd) {
10092
10629
  configured: false
10093
10630
  };
10094
10631
  }
10095
- const coderSettings = config.brains.coder;
10096
- const provider = findProviderById(config, coderSettings.providerId);
10632
+ const focusSettings = config.brains[focusRole];
10633
+ const provider = findProviderById(config, focusSettings.providerId);
10097
10634
  return {
10635
+ focusRole,
10098
10636
  providerLabel: provider?.label ?? provider?.id ?? "unknown",
10099
- providerId: provider?.id ?? coderSettings.providerId,
10637
+ providerId: provider?.id ?? focusSettings.providerId,
10638
+ focusModel: resolveBrainModel(config, focusRole) ?? "not set",
10100
10639
  coderModel: resolveBrainModel(config, "coder") ?? "not set",
10101
10640
  plannerModel: resolveBrainModel(config, "planner") ?? "not set",
10102
10641
  approvalMode: config.approvalMode,
@@ -10129,20 +10668,24 @@ function renderBox(lines) {
10129
10668
  function renderHelp() {
10130
10669
  return [
10131
10670
  `${color(DIM, "Commands")}`,
10132
- "/status Show the active model, provider, and workspace state.",
10133
- "/plan <goal> Create a structured implementation plan.",
10134
- "/run <goal> Start a Kimbho execution session for a goal.",
10135
- "/new <goal> Alias for /run <goal>.",
10136
- "/scaffold <goal> Alias for /run <goal> with scaffolding intent.",
10137
- "/resume Show the latest saved session.",
10138
- "/agents Inspect agent roles and the active session.",
10139
- "/providers Manage model providers.",
10140
- "/model Show the active planner/coder model selection.",
10141
- "/models Discover and select provider models.",
10142
- "/brains Inspect or assign planner/coder/reviewer brains.",
10143
- "/doctor Check local environment and config.",
10144
- "/clear Redraw the shell.",
10145
- "/quit, /exit Leave the shell.",
10671
+ "/status Show the active role, provider, and workspace state.",
10672
+ "/brain <role> Change shell focus to planner, coder, reviewer, or fast.",
10673
+ "/model Show current brain assignments.",
10674
+ "/providers List configured providers.",
10675
+ "/providers templates Show built-in provider templates.",
10676
+ "/providers add <tpl> [id] Add a provider from a built-in template.",
10677
+ "/providers use <id> [role] Use a provider for the active role.",
10678
+ "/providers check Run provider health checks.",
10679
+ "/models [search] Discover models for the active role's provider.",
10680
+ "/select <n> Select a model from the last numbered /models list.",
10681
+ "/use-model <id> Assign a model to the active role and provider.",
10682
+ "/plan <goal> Create a structured implementation plan.",
10683
+ "/run <goal> Start a Kimbho execution session for a goal.",
10684
+ "/resume Show the latest saved session.",
10685
+ "/agents Inspect agent roles and the active session.",
10686
+ "/doctor Check local environment and config.",
10687
+ "/clear Redraw the shell.",
10688
+ "/quit, /exit Leave the shell.",
10146
10689
  "",
10147
10690
  `${color(DIM, "Tip")}`,
10148
10691
  "Type a natural-language goal directly and Kimbho will treat it as /run <goal>."
@@ -10151,6 +10694,8 @@ function renderHelp() {
10151
10694
  function renderStartupCard(cwd, state) {
10152
10695
  const cardLines = [
10153
10696
  `${color(BOLD, "Kimbho CLI")} (v${KIMBHO_VERSION})`,
10697
+ renderCardLine("focus", state.focusRole),
10698
+ renderCardLine("model", state.focusModel),
10154
10699
  renderCardLine("coder", state.coderModel),
10155
10700
  renderCardLine("planner", state.plannerModel),
10156
10701
  renderCardLine("provider", `${state.providerLabel} [${state.providerId}]`),
@@ -10158,23 +10703,23 @@ function renderStartupCard(cwd, state) {
10158
10703
  renderCardLine("approval", state.approvalMode),
10159
10704
  renderCardLine("sandbox", state.sandboxMode),
10160
10705
  renderCardLine("preset", state.stackPreset),
10161
- renderCardLine("shortcuts", "/help /status /plan /run /models /brains /quit")
10706
+ renderCardLine("shortcuts", "/brain /providers /models /select /use-model /quit")
10162
10707
  ];
10163
10708
  if (!state.configured) {
10164
- cardLines.push("setup: run /init to create .kimbho/config.json");
10709
+ cardLines.push("setup: run /init or /providers add <template> to create .kimbho/config.json");
10165
10710
  }
10166
10711
  return renderBox(cardLines);
10167
10712
  }
10168
10713
  function formatPrompt(state) {
10169
- const coderModel = state.coderModel === "not set" ? "unconfigured" : shortenMiddle(state.coderModel, 18);
10170
- return `${color(AMBER, "kimbho")} ${color(DIM, `[${coderModel}]`)} > `;
10714
+ const model = state.focusModel === "not set" ? "unconfigured" : shortenMiddle(state.focusModel, 18);
10715
+ return `${color(AMBER, "kimbho")} ${color(DIM, `[${state.focusRole}:${model}]`)} > `;
10171
10716
  }
10172
10717
  function printHeader(cwd, state) {
10173
10718
  console.log(renderBanner());
10174
10719
  console.log(color(DIM, "Terminal-native coding agent"));
10175
10720
  console.log(renderStartupCard(cwd, state));
10176
10721
  console.log("");
10177
- console.log(color(DIM, "Tip: describe the app or repo change you want, and Kimbho will route it into /run."));
10722
+ console.log(color(DIM, "Tip: configure providers and models here, then describe the work and Kimbho will route it into /run."));
10178
10723
  console.log(renderHelp());
10179
10724
  console.log("");
10180
10725
  }
@@ -10225,21 +10770,399 @@ function tokenizeInput(input) {
10225
10770
  }
10226
10771
  return tokens;
10227
10772
  }
10228
- function toCommandTokens(input) {
10773
+ function normalizeInputTokens(input) {
10229
10774
  const trimmed = input.trim();
10230
10775
  if (!trimmed) {
10231
- return [];
10776
+ return {
10777
+ normalizedInput: "",
10778
+ tokens: [],
10779
+ head: null
10780
+ };
10232
10781
  }
10233
10782
  const normalizedInput = trimmed.startsWith("kimbho ") ? trimmed.slice("kimbho ".length).trim() : trimmed;
10234
10783
  const tokens = tokenizeInput(normalizedInput);
10235
- if (tokens.length === 0) {
10784
+ const firstToken = tokens[0];
10785
+ if (!firstToken) {
10786
+ return {
10787
+ normalizedInput,
10788
+ tokens,
10789
+ head: null
10790
+ };
10791
+ }
10792
+ return {
10793
+ normalizedInput,
10794
+ tokens,
10795
+ head: firstToken.startsWith("/") ? firstToken.slice(1) : firstToken
10796
+ };
10797
+ }
10798
+ function defaultProviderIdForTemplate(templateId) {
10799
+ switch (templateId) {
10800
+ case "openai":
10801
+ return "openai-main";
10802
+ case "anthropic":
10803
+ return "anthropic-main";
10804
+ case "openrouter":
10805
+ return "openrouter-main";
10806
+ case "ollama":
10807
+ return "ollama-local";
10808
+ case "lmstudio":
10809
+ return "lmstudio-local";
10810
+ default:
10811
+ return `${templateId}-main`;
10812
+ }
10813
+ }
10814
+ function buildCachedModels(provider, search, limit = 25) {
10815
+ const normalized = search?.trim().toLowerCase();
10816
+ const filtered = normalized ? provider.models.filter((modelId) => modelId.toLowerCase().includes(normalized)) : provider.models;
10817
+ return filtered.slice(0, limit).map((modelId) => ({
10818
+ id: modelId,
10819
+ providerId: provider.id
10820
+ }));
10821
+ }
10822
+ async function fetchModelsForProvider(cwd, config, provider, search, limit = 25) {
10823
+ const registry = createDefaultBrainProviderRegistry(cwd);
10824
+ const cachedModels = buildCachedModels(provider, search, limit);
10825
+ try {
10826
+ const models = await registry.listModels(provider, {
10827
+ ...search ? {
10828
+ search
10829
+ } : {},
10830
+ limit
10831
+ });
10832
+ const nextConfig = upsertProvider(config, {
10833
+ ...provider,
10834
+ models: Array.from(/* @__PURE__ */ new Set([
10835
+ ...provider.models,
10836
+ ...models.map((model) => model.id)
10837
+ ]))
10838
+ });
10839
+ if (nextConfig !== config) {
10840
+ await saveConfig(nextConfig, cwd);
10841
+ }
10842
+ return {
10843
+ config: nextConfig,
10844
+ source: "remote",
10845
+ models
10846
+ };
10847
+ } catch (error) {
10848
+ if (cachedModels.length > 0) {
10849
+ const message = error instanceof Error ? error.message : String(error);
10850
+ return {
10851
+ config,
10852
+ source: "cache",
10853
+ note: `Using cached models because remote discovery failed: ${message}`,
10854
+ models: cachedModels
10855
+ };
10856
+ }
10857
+ throw error;
10858
+ }
10859
+ }
10860
+ async function assignModelToRole(cwd, role, providerId, model) {
10861
+ const config = await loadConfig(cwd);
10862
+ if (!config) {
10863
+ throw new Error("No config found. Run /init or /providers add first.");
10864
+ }
10865
+ const provider = findProviderById(config, providerId);
10866
+ if (!provider) {
10867
+ throw new Error(`Unknown provider "${providerId}".`);
10868
+ }
10869
+ const currentSettings = resolveBrainSettings(config, role);
10870
+ const nextConfig = assignBrain(
10871
+ upsertProvider(config, {
10872
+ ...provider,
10873
+ defaultModel: model,
10874
+ models: Array.from(/* @__PURE__ */ new Set([
10875
+ ...provider.models,
10876
+ model
10877
+ ]))
10878
+ }),
10879
+ role,
10880
+ {
10881
+ providerId,
10882
+ model,
10883
+ ...typeof currentSettings.temperature === "number" ? {
10884
+ temperature: currentSettings.temperature
10885
+ } : {},
10886
+ ...typeof currentSettings.maxTokens === "number" ? {
10887
+ maxTokens: currentSettings.maxTokens
10888
+ } : {},
10889
+ ...currentSettings.promptPreamble ? {
10890
+ promptPreamble: currentSettings.promptPreamble
10891
+ } : {}
10892
+ }
10893
+ );
10894
+ const outputPath = await saveConfig(nextConfig, cwd);
10895
+ return outputPath;
10896
+ }
10897
+ async function useProviderForRole(cwd, role, providerId) {
10898
+ const config = await loadConfig(cwd);
10899
+ if (!config) {
10900
+ throw new Error("No config found. Run /init or /providers add first.");
10901
+ }
10902
+ const provider = findProviderById(config, providerId);
10903
+ if (!provider) {
10904
+ throw new Error(`Unknown provider "${providerId}".`);
10905
+ }
10906
+ const currentSettings = resolveBrainSettings(config, role);
10907
+ const resolvedModel = provider.defaultModel ?? provider.models[0] ?? null;
10908
+ const nextConfig = assignBrain(config, role, {
10909
+ providerId: provider.id,
10910
+ ...resolvedModel ? {
10911
+ model: resolvedModel
10912
+ } : {},
10913
+ ...typeof currentSettings.temperature === "number" ? {
10914
+ temperature: currentSettings.temperature
10915
+ } : {},
10916
+ ...typeof currentSettings.maxTokens === "number" ? {
10917
+ maxTokens: currentSettings.maxTokens
10918
+ } : {},
10919
+ ...currentSettings.promptPreamble ? {
10920
+ promptPreamble: currentSettings.promptPreamble
10921
+ } : {}
10922
+ });
10923
+ const outputPath = await saveConfig(nextConfig, cwd);
10924
+ return {
10925
+ outputPath,
10926
+ model: resolvedModel
10927
+ };
10928
+ }
10929
+ function renderModelLine2(model) {
10930
+ const details = [
10931
+ model.name,
10932
+ model.contextLength ? `ctx ${model.contextLength}` : null,
10933
+ model.promptPrice ? `in ${model.promptPrice}` : null,
10934
+ model.completionPrice ? `out ${model.completionPrice}` : null,
10935
+ model.modality
10936
+ ].filter((value) => Boolean(value));
10937
+ return details.length > 0 ? `${model.id} | ${details.join(" | ")}` : model.id;
10938
+ }
10939
+ async function printProviderList(cwd, focusRole) {
10940
+ const config = await loadConfig(cwd);
10941
+ if (!config) {
10942
+ console.log("No config found. Run /init or /providers add <template> first.");
10943
+ return;
10944
+ }
10945
+ const activeProviderId = config.brains[focusRole].providerId;
10946
+ for (const provider of config.providers) {
10947
+ const marker = provider.id === activeProviderId ? "*" : " ";
10948
+ const envState = provider.apiKeyEnv ? import_node_process10.default.env[provider.apiKeyEnv] ? "present" : `missing ${provider.apiKeyEnv}` : "no key required";
10949
+ console.log(`${marker} ${provider.id}`);
10950
+ console.log(` label: ${provider.label ?? "-"}`);
10951
+ console.log(` driver: ${provider.driver}`);
10952
+ console.log(` model: ${provider.defaultModel ?? "-"}`);
10953
+ console.log(` auth: ${envState}`);
10954
+ console.log(` cachedModels: ${provider.models.length}`);
10955
+ }
10956
+ }
10957
+ function printProviderTemplates() {
10958
+ for (const template of listProviderTemplates()) {
10959
+ console.log(`${template.id}`);
10960
+ console.log(` label: ${template.label}`);
10961
+ console.log(` driver: ${template.driver}`);
10962
+ console.log(` apiKeyEnv: ${template.defaultApiKeyEnv ?? "-"}`);
10963
+ console.log(` model: ${template.defaultModel ?? "-"}`);
10964
+ console.log(` notes: ${template.notes}`);
10965
+ }
10966
+ }
10967
+ async function addProviderFromTemplate(cwd, templateId, providerId) {
10968
+ const provider = buildProviderFromTemplate(templateId, {
10969
+ providerId: providerId ?? defaultProviderIdForTemplate(templateId)
10970
+ });
10971
+ const config = await loadConfig(cwd);
10972
+ if (!config) {
10973
+ const outputPath = await saveConfig(createDefaultConfig({ provider }), cwd);
10974
+ return outputPath;
10975
+ }
10976
+ return saveConfig(upsertProvider(config, provider), cwd);
10977
+ }
10978
+ async function printProviderHealth(cwd) {
10979
+ const config = await loadConfig(cwd);
10980
+ if (!config) {
10981
+ console.log("No config found. Run /init or /providers add <template> first.");
10982
+ return;
10983
+ }
10984
+ const registry = createDefaultBrainProviderRegistry(cwd);
10985
+ for (const provider of config.providers) {
10986
+ try {
10987
+ const result = await registry.healthCheck(provider);
10988
+ console.log(`${result.ok ? "PASS" : "FAIL"} ${provider.id}: ${provider.driver} | ${result.message}`);
10989
+ } catch (error) {
10990
+ const message = error instanceof Error ? error.message : String(error);
10991
+ console.log(`FAIL ${provider.id}: ${provider.driver} | ${message}`);
10992
+ }
10993
+ }
10994
+ }
10995
+ async function printBrainAssignments(cwd) {
10996
+ const config = await loadConfig(cwd);
10997
+ if (!config) {
10998
+ console.log("No config found. Run /init or /providers add <template> first.");
10999
+ return;
11000
+ }
11001
+ for (const role of BRAIN_ROLES) {
11002
+ const settings = resolveBrainSettings(config, role);
11003
+ console.log(`${role}`);
11004
+ console.log(` provider: ${settings.providerId}`);
11005
+ console.log(` model: ${resolveBrainModel(config, role) ?? "-"}`);
11006
+ console.log(` temperature: ${settings.temperature ?? "-"}`);
11007
+ console.log(` maxTokens: ${settings.maxTokens ?? "-"}`);
11008
+ }
11009
+ }
11010
+ function isBrainRole(value) {
11011
+ return BRAIN_ROLES.includes(value);
11012
+ }
11013
+ async function handleProvidersCommand(cwd, tokens, runtime) {
11014
+ const subcommand = tokens[1];
11015
+ if (!subcommand || subcommand === "list") {
11016
+ await printProviderList(cwd, runtime.focusRole);
11017
+ return;
11018
+ }
11019
+ if (subcommand === "templates") {
11020
+ printProviderTemplates();
11021
+ return;
11022
+ }
11023
+ if (subcommand === "check") {
11024
+ await printProviderHealth(cwd);
11025
+ return;
11026
+ }
11027
+ if (subcommand === "add") {
11028
+ const templateId = tokens[2];
11029
+ const providerId = tokens[3];
11030
+ if (!templateId) {
11031
+ throw new Error("Usage: /providers add <template> [provider-id]");
11032
+ }
11033
+ const outputPath = await addProviderFromTemplate(cwd, templateId, providerId);
11034
+ const resolvedProviderId = providerId ?? defaultProviderIdForTemplate(templateId);
11035
+ console.log(`Updated ${outputPath}`);
11036
+ console.log(`Added provider ${resolvedProviderId} from template ${templateId}`);
11037
+ console.log(`Next: /providers use ${resolvedProviderId}`);
11038
+ return;
11039
+ }
11040
+ if (subcommand === "use") {
11041
+ const providerId = tokens[2];
11042
+ const role = tokens[3];
11043
+ if (!providerId) {
11044
+ throw new Error("Usage: /providers use <provider-id> [role]");
11045
+ }
11046
+ const targetRole = role && isBrainRole(role) ? role : runtime.focusRole;
11047
+ const result = await useProviderForRole(cwd, targetRole, providerId);
11048
+ runtime.focusRole = targetRole;
11049
+ runtime.lastModels = null;
11050
+ console.log(`Updated ${result.outputPath}`);
11051
+ console.log(`Focus role ${targetRole} now uses provider ${providerId}${result.model ? ` (${result.model})` : ""}`);
11052
+ return;
11053
+ }
11054
+ throw new Error("Usage: /providers [list|templates|add|use|check]");
11055
+ }
11056
+ async function handleBrainCommand(cwd, tokens, runtime) {
11057
+ const subcommand = tokens[1];
11058
+ if (!subcommand || subcommand === "list") {
11059
+ await printBrainAssignments(cwd);
11060
+ return;
11061
+ }
11062
+ if (subcommand === "use" && tokens[2] && isBrainRole(tokens[2])) {
11063
+ runtime.focusRole = tokens[2];
11064
+ runtime.lastModels = null;
11065
+ console.log(`Shell focus role is now ${runtime.focusRole}.`);
11066
+ return;
11067
+ }
11068
+ if (isBrainRole(subcommand)) {
11069
+ runtime.focusRole = subcommand;
11070
+ runtime.lastModels = null;
11071
+ console.log(`Shell focus role is now ${runtime.focusRole}.`);
11072
+ return;
11073
+ }
11074
+ throw new Error("Usage: /brain [planner|coder|reviewer|fast]");
11075
+ }
11076
+ async function handleModelsCommand(cwd, tokens, runtime) {
11077
+ const config = await loadConfig(cwd);
11078
+ if (!config) {
11079
+ throw new Error("No config found. Run /init or /providers add <template> first.");
11080
+ }
11081
+ const providerId = config.brains[runtime.focusRole].providerId;
11082
+ const provider = findProviderById(config, providerId);
11083
+ if (!provider) {
11084
+ throw new Error(`Active role "${runtime.focusRole}" points to unknown provider "${providerId}".`);
11085
+ }
11086
+ const subcommand = tokens[1];
11087
+ if (subcommand === "use") {
11088
+ const modelId = tokens.slice(2).join(" ").trim();
11089
+ if (!modelId) {
11090
+ throw new Error("Usage: /models use <model-id>");
11091
+ }
11092
+ const outputPath = await assignModelToRole(cwd, runtime.focusRole, provider.id, modelId);
11093
+ console.log(`Updated ${outputPath}`);
11094
+ console.log(`Selected ${provider.id}/${modelId}`);
11095
+ console.log(`Assigned role ${runtime.focusRole}`);
11096
+ return;
11097
+ }
11098
+ const search = !subcommand || MODEL_SUBCOMMANDS.has(subcommand) ? void 0 : tokens.slice(1).join(" ");
11099
+ const result = await fetchModelsForProvider(cwd, config, provider, search, 25);
11100
+ runtime.lastModels = {
11101
+ providerId: provider.id,
11102
+ role: runtime.focusRole,
11103
+ source: result.source,
11104
+ ...search ? {
11105
+ search
11106
+ } : {},
11107
+ models: result.models
11108
+ };
11109
+ console.log(`${provider.id} (${result.source})`);
11110
+ if (result.note) {
11111
+ console.log(` note: ${result.note}`);
11112
+ }
11113
+ if (result.models.length === 0) {
11114
+ console.log(" no models found");
11115
+ return;
11116
+ }
11117
+ for (const [index, model] of result.models.entries()) {
11118
+ console.log(` ${index + 1}. ${renderModelLine2(model)}`);
11119
+ }
11120
+ console.log(``);
11121
+ console.log(`Use /select <number> or /use-model <model-id> to assign one to ${runtime.focusRole}.`);
11122
+ }
11123
+ async function handleModelSelection(cwd, modelId, runtime) {
11124
+ const config = await loadConfig(cwd);
11125
+ if (!config) {
11126
+ throw new Error("No config found. Run /init or /providers add <template> first.");
11127
+ }
11128
+ const providerId = config.brains[runtime.focusRole].providerId;
11129
+ const provider = findProviderById(config, providerId);
11130
+ if (!provider) {
11131
+ throw new Error(`Active role "${runtime.focusRole}" points to unknown provider "${providerId}".`);
11132
+ }
11133
+ const outputPath = await assignModelToRole(cwd, runtime.focusRole, provider.id, modelId);
11134
+ console.log(`Updated ${outputPath}`);
11135
+ console.log(`Selected ${provider.id}/${modelId}`);
11136
+ console.log(`Assigned role ${runtime.focusRole}`);
11137
+ }
11138
+ async function handleSelectCommand(cwd, tokens, runtime) {
11139
+ const rawIndex = tokens[1];
11140
+ if (!rawIndex) {
11141
+ throw new Error("Usage: /select <number>");
11142
+ }
11143
+ if (!runtime.lastModels || runtime.lastModels.models.length === 0) {
11144
+ throw new Error("No model list is active. Run /models first.");
11145
+ }
11146
+ const index = Number.parseInt(rawIndex, 10);
11147
+ if (!Number.isInteger(index) || index <= 0) {
11148
+ throw new Error(`Expected a positive model number, received "${rawIndex}".`);
11149
+ }
11150
+ const model = runtime.lastModels.models[index - 1];
11151
+ if (!model) {
11152
+ throw new Error(`Model number ${index} is out of range.`);
11153
+ }
11154
+ runtime.focusRole = runtime.lastModels.role;
11155
+ await handleModelSelection(cwd, model.id, runtime);
11156
+ }
11157
+ function toExternalCommandTokens(input, state) {
11158
+ const { normalizedInput, tokens, head } = normalizeInputTokens(input);
11159
+ if (!head || tokens.length === 0) {
10236
11160
  return [];
10237
11161
  }
10238
11162
  const firstToken = tokens[0];
10239
11163
  if (!firstToken) {
10240
11164
  return [];
10241
11165
  }
10242
- const head = firstToken.startsWith("/") ? firstToken.slice(1) : firstToken;
10243
11166
  if (head === "new") {
10244
11167
  return [
10245
11168
  "run",
@@ -10253,12 +11176,6 @@ function toCommandTokens(input) {
10253
11176
  `scaffold ${goal}`.trim()
10254
11177
  ];
10255
11178
  }
10256
- if (head === "model") {
10257
- return [
10258
- "brains",
10259
- "list"
10260
- ];
10261
- }
10262
11179
  if (!firstToken.startsWith("/") && !TOP_LEVEL_COMMANDS.has(head) && !head.startsWith("-")) {
10263
11180
  return [
10264
11181
  "run",
@@ -10268,91 +11185,157 @@ function toCommandTokens(input) {
10268
11185
  tokens[0] = head;
10269
11186
  return tokens;
10270
11187
  }
11188
+ async function handleShellCommand(cwd, input, state, runtime, execute) {
11189
+ const trimmed = input.trim();
11190
+ if (!trimmed) {
11191
+ return;
11192
+ }
11193
+ if (trimmed === "/exit" || trimmed === "exit" || trimmed === "quit" || trimmed === "/quit") {
11194
+ throw new Error("__kimbho_exit__");
11195
+ }
11196
+ if (trimmed === "/help" || trimmed === "help" || trimmed === "?") {
11197
+ console.log(renderHelp());
11198
+ return;
11199
+ }
11200
+ if (trimmed === "/status" || trimmed === "status") {
11201
+ console.log(renderStartupCard(cwd, state));
11202
+ return;
11203
+ }
11204
+ if (trimmed === "/clear" || trimmed === "clear") {
11205
+ if (import_node_process10.default.stdout.isTTY) {
11206
+ import_node_process10.default.stdout.write("\x1Bc");
11207
+ }
11208
+ const nextState = await getShellSessionState(cwd, runtime.focusRole);
11209
+ printHeader(cwd, nextState);
11210
+ return;
11211
+ }
11212
+ const { tokens, head } = normalizeInputTokens(trimmed);
11213
+ if (!head) {
11214
+ return;
11215
+ }
11216
+ if (head === "provider" || head === "providers") {
11217
+ const subcommand = tokens[1];
11218
+ const canHandleLocally = !subcommand || subcommand === "list" || subcommand === "templates" || subcommand === "check" || subcommand === "use" || subcommand === "add" && Boolean(tokens[2]) && !tokens[2]?.startsWith("-");
11219
+ if (canHandleLocally) {
11220
+ await handleProvidersCommand(cwd, [
11221
+ "providers",
11222
+ ...tokens.slice(1)
11223
+ ], runtime);
11224
+ return;
11225
+ }
11226
+ }
11227
+ if (head === "brain" || head === "brains") {
11228
+ const subcommand = tokens[1];
11229
+ const roleArg = tokens[2];
11230
+ const canHandleLocally = !subcommand || subcommand === "list" || isBrainRole(subcommand) || subcommand === "use" && (roleArg ? isBrainRole(roleArg) : false);
11231
+ if (canHandleLocally) {
11232
+ await handleBrainCommand(cwd, [
11233
+ "brain",
11234
+ ...tokens.slice(1)
11235
+ ], runtime);
11236
+ return;
11237
+ }
11238
+ }
11239
+ if (head === "model") {
11240
+ await printBrainAssignments(cwd);
11241
+ return;
11242
+ }
11243
+ if (head === "models") {
11244
+ await handleModelsCommand(cwd, [
11245
+ "models",
11246
+ ...tokens.slice(1)
11247
+ ], runtime);
11248
+ return;
11249
+ }
11250
+ if (head === "use-model") {
11251
+ const modelId = tokens.slice(1).join(" ").trim();
11252
+ if (!modelId) {
11253
+ throw new Error("Usage: /use-model <model-id>");
11254
+ }
11255
+ await handleModelSelection(cwd, modelId, runtime);
11256
+ return;
11257
+ }
11258
+ if (head === "select") {
11259
+ await handleSelectCommand(cwd, [
11260
+ "select",
11261
+ ...tokens.slice(1)
11262
+ ], runtime);
11263
+ return;
11264
+ }
11265
+ const externalTokens = toExternalCommandTokens(trimmed, state);
11266
+ if (externalTokens.length === 0) {
11267
+ return;
11268
+ }
11269
+ await execute(externalTokens);
11270
+ }
10271
11271
  async function runInteractiveShell(options) {
10272
- const readline = (0, import_promises6.createInterface)({
10273
- input: import_node_process9.default.stdin,
10274
- output: import_node_process9.default.stdout,
10275
- terminal: Boolean(import_node_process9.default.stdin.isTTY && import_node_process9.default.stdout.isTTY)
11272
+ const readline = (0, import_promises8.createInterface)({
11273
+ input: import_node_process10.default.stdin,
11274
+ output: import_node_process10.default.stdout,
11275
+ terminal: Boolean(import_node_process10.default.stdin.isTTY && import_node_process10.default.stdout.isTTY)
10276
11276
  });
11277
+ const runtime = {
11278
+ focusRole: "coder",
11279
+ lastModels: null
11280
+ };
10277
11281
  let closed = false;
10278
11282
  readline.on("SIGINT", () => {
10279
11283
  closed = true;
10280
11284
  readline.close();
10281
11285
  });
10282
- let state = await getShellSessionState(options.cwd);
11286
+ let state = await getShellSessionState(options.cwd, runtime.focusRole);
10283
11287
  printHeader(options.cwd, state);
10284
11288
  while (!closed) {
10285
11289
  let line;
10286
11290
  try {
10287
- state = await getShellSessionState(options.cwd);
11291
+ state = await getShellSessionState(options.cwd, runtime.focusRole);
10288
11292
  line = await readline.question(formatPrompt(state));
10289
11293
  } catch {
10290
11294
  break;
10291
11295
  }
10292
- const trimmed = line.trim();
10293
- if (!trimmed) {
10294
- continue;
10295
- }
10296
- if (trimmed === "/exit" || trimmed === "exit" || trimmed === "quit" || trimmed === "/quit") {
10297
- closed = true;
10298
- break;
10299
- }
10300
- if (trimmed === "/help" || trimmed === "help" || trimmed === "?") {
10301
- console.log(renderHelp());
10302
- console.log("");
10303
- continue;
10304
- }
10305
- if (trimmed === "/status" || trimmed === "status") {
10306
- state = await getShellSessionState(options.cwd);
10307
- console.log(renderStartupCard(options.cwd, state));
10308
- console.log("");
10309
- continue;
10310
- }
10311
- if (trimmed === "/clear" || trimmed === "clear") {
10312
- if (import_node_process9.default.stdout.isTTY) {
10313
- import_node_process9.default.stdout.write("\x1Bc");
10314
- }
10315
- state = await getShellSessionState(options.cwd);
10316
- printHeader(options.cwd, state);
10317
- continue;
10318
- }
10319
11296
  try {
10320
- const tokens = toCommandTokens(trimmed);
10321
- if (tokens.length === 0) {
10322
- continue;
10323
- }
10324
- await options.execute(tokens);
11297
+ await handleShellCommand(options.cwd, line, state, runtime, options.execute);
10325
11298
  } catch (error) {
10326
11299
  const message = error instanceof Error ? error.message : String(error);
11300
+ if (message === "__kimbho_exit__") {
11301
+ closed = true;
11302
+ break;
11303
+ }
10327
11304
  console.error(message);
10328
11305
  } finally {
10329
- import_node_process9.default.exitCode = 0;
11306
+ import_node_process10.default.exitCode = 0;
10330
11307
  }
10331
11308
  if (!closed) {
10332
11309
  console.log("");
10333
11310
  }
10334
11311
  }
10335
11312
  readline.close();
10336
- import_node_process9.default.exitCode = 0;
11313
+ import_node_process10.default.exitCode = 0;
10337
11314
  console.log(color(DIM, "Leaving Kimbho."));
10338
11315
  }
10339
11316
 
10340
11317
  // src/index.ts
10341
11318
  async function dispatchCliTokens(tokens) {
10342
- const program2 = createProgram();
10343
- program2.exitOverride();
10344
- try {
10345
- await program2.parseAsync([
10346
- "node",
10347
- "kimbho",
10348
- ...normalizeCliTokens(tokens)
10349
- ]);
10350
- } catch (error) {
10351
- if (error instanceof CommanderError) {
10352
- return;
10353
- }
10354
- throw error;
10355
- }
11319
+ const normalizedTokens = normalizeCliTokens(tokens);
11320
+ await new Promise((resolve, reject) => {
11321
+ const child = (0, import_node_child_process3.spawn)(
11322
+ process.execPath,
11323
+ [
11324
+ ...process.execArgv,
11325
+ process.argv[1] ?? "",
11326
+ ...normalizedTokens
11327
+ ],
11328
+ {
11329
+ cwd: process.cwd(),
11330
+ env: process.env,
11331
+ stdio: "inherit"
11332
+ }
11333
+ );
11334
+ child.on("error", reject);
11335
+ child.on("close", () => {
11336
+ resolve();
11337
+ });
11338
+ });
10356
11339
  }
10357
11340
  async function openShell() {
10358
11341
  await runInteractiveShell({