@kimbho/kimbho-cli 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,7 @@ kimbho plan "build a coding agent"
36
36
  kimbho /models openrouter --search claude --limit 10
37
37
  ```
38
38
 
39
- Bare `kimbho` opens the interactive shell. Within that shell, plain-English input is treated as `run <goal>`, and slash commands like `/plan`, `/run`, `/models`, and `/brains` are supported.
39
+ Bare `kimbho` opens the interactive shell. Within that shell, plain-English input is treated as `run <goal>`, and slash commands like `/plan`, `/run`, `/models`, and `/brains` are supported. Provider and model selections apply to all agent roles by default.
40
40
 
41
41
  Useful shell commands:
42
42
 
@@ -55,10 +55,19 @@ Useful shell commands:
55
55
  /quit
56
56
  ```
57
57
 
58
+ LM Studio example:
59
+
60
+ ```bash
61
+ /providers add lmstudio lmstudio-lan --base-url http://192.168.0.70:1234
62
+ /models
63
+ /select 1
64
+ ```
65
+
58
66
  `run` now auto-executes the supported ready-task frontier and persists session events. `resume --execute` continues that frontier from the latest session snapshot.
59
67
 
60
68
  ## Notes
61
69
 
62
70
  - The published CLI bundles the internal Kimbho packages so installation does not depend on the monorepo layout.
63
71
  - Brain and provider configuration is stored in `.kimbho/config.json`.
64
- - Use `kimbho models use <model> --provider <id> --role coder --set-default` to select a provider model and assign it to a brain role.
72
+ - Use `kimbho models use <model> --provider <id> --set-default` to select a provider model for the whole system.
73
+ - Use `kimbho brains assign --role <role> ...` only when you intentionally want an advanced per-role override.
package/dist/index.cjs CHANGED
@@ -3346,7 +3346,7 @@ var {
3346
3346
  // package.json
3347
3347
  var package_default = {
3348
3348
  name: "@kimbho/kimbho-cli",
3349
- version: "0.1.3",
3349
+ version: "0.1.4",
3350
3350
  description: "Kimbho CLI is a terminal-native coding agent for planning, execution, and verification.",
3351
3351
  type: "module",
3352
3352
  engines: {
@@ -8072,6 +8072,34 @@ function assignBrain(config, role, settings) {
8072
8072
  }
8073
8073
  });
8074
8074
  }
8075
+ function buildRetainedBrainSettings(current, providerId, model) {
8076
+ return {
8077
+ providerId,
8078
+ ...model ? {
8079
+ model
8080
+ } : {},
8081
+ ...typeof current.temperature === "number" ? {
8082
+ temperature: current.temperature
8083
+ } : {},
8084
+ ...typeof current.maxTokens === "number" ? {
8085
+ maxTokens: current.maxTokens
8086
+ } : {},
8087
+ ...current.promptPreamble ? {
8088
+ promptPreamble: current.promptPreamble
8089
+ } : {}
8090
+ };
8091
+ }
8092
+ function assignProviderToAllBrains(config, providerId, model) {
8093
+ return KimbhoConfigSchema.parse({
8094
+ ...config,
8095
+ brains: {
8096
+ planner: buildRetainedBrainSettings(config.brains.planner, providerId, model),
8097
+ coder: buildRetainedBrainSettings(config.brains.coder, providerId, model),
8098
+ reviewer: buildRetainedBrainSettings(config.brains.reviewer, providerId, model),
8099
+ fast: buildRetainedBrainSettings(config.brains.fast, providerId, model)
8100
+ }
8101
+ });
8102
+ }
8075
8103
  function resolveBrainSettings(config, role) {
8076
8104
  return config.brains[role];
8077
8105
  }
@@ -8826,6 +8854,28 @@ function listProviderTemplates() {
8826
8854
  function findProviderTemplate(templateId) {
8827
8855
  return BUILTIN_PROVIDER_TEMPLATES.find((template) => template.id === templateId);
8828
8856
  }
8857
+ function trimTrailingSlash(value) {
8858
+ return value.replace(/\/+$/, "");
8859
+ }
8860
+ function normalizeTemplateBaseUrl(templateId, baseUrl) {
8861
+ if (!baseUrl) {
8862
+ return void 0;
8863
+ }
8864
+ const normalized = trimTrailingSlash(baseUrl);
8865
+ if (templateId !== "lmstudio") {
8866
+ return normalized;
8867
+ }
8868
+ try {
8869
+ const parsed = new URL(normalized);
8870
+ if (!parsed.pathname || parsed.pathname === "/") {
8871
+ parsed.pathname = "/v1";
8872
+ return trimTrailingSlash(parsed.toString());
8873
+ }
8874
+ } catch {
8875
+ return normalized;
8876
+ }
8877
+ return normalized;
8878
+ }
8829
8879
  function buildProviderFromTemplate(templateId, options = {}) {
8830
8880
  const template = findProviderTemplate(templateId);
8831
8881
  if (!template) {
@@ -8835,8 +8885,8 @@ function buildProviderFromTemplate(templateId, options = {}) {
8835
8885
  id: options.providerId ?? template.id,
8836
8886
  label: options.label ?? template.label,
8837
8887
  driver: template.driver,
8838
- ...options.baseUrl ?? template.defaultBaseUrl ? {
8839
- baseUrl: options.baseUrl ?? template.defaultBaseUrl
8888
+ ...normalizeTemplateBaseUrl(templateId, options.baseUrl) ?? template.defaultBaseUrl ? {
8889
+ baseUrl: normalizeTemplateBaseUrl(templateId, options.baseUrl) ?? template.defaultBaseUrl
8840
8890
  } : {},
8841
8891
  ...options.apiKeyEnv ?? template.defaultApiKeyEnv ? {
8842
8892
  apiKeyEnv: options.apiKeyEnv ?? template.defaultApiKeyEnv
@@ -8875,11 +8925,11 @@ async function requestJson(url, init, timeoutMs = 3e4) {
8875
8925
  }
8876
8926
  return response.json();
8877
8927
  }
8878
- function trimTrailingSlash(value) {
8928
+ function trimTrailingSlash2(value) {
8879
8929
  return value.replace(/\/+$/, "");
8880
8930
  }
8881
8931
  function joinUrl(baseUrl, suffix) {
8882
- const normalizedBase = trimTrailingSlash(baseUrl);
8932
+ const normalizedBase = trimTrailingSlash2(baseUrl);
8883
8933
  const normalizedSuffix = suffix.startsWith("/") ? suffix : `/${suffix}`;
8884
8934
  return `${normalizedBase}${normalizedSuffix}`;
8885
8935
  }
@@ -9187,9 +9237,26 @@ var OpenAICompatibleProvider = class {
9187
9237
  };
9188
9238
  }
9189
9239
  const apiKey = resolveApiKey(this.definition);
9240
+ if (this.definition.apiKeyEnv && !apiKey) {
9241
+ return {
9242
+ ok: false,
9243
+ message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"}), missing ${this.definition.apiKeyEnv}`
9244
+ };
9245
+ }
9246
+ try {
9247
+ await requestJson(joinUrl(this.definition.baseUrl, "/models"), {
9248
+ method: "GET",
9249
+ headers: buildProviderHeaders(this.definition, apiKey, false)
9250
+ }, 2e3);
9251
+ } catch (error) {
9252
+ return {
9253
+ ok: false,
9254
+ message: error instanceof Error ? error.message : String(error)
9255
+ };
9256
+ }
9190
9257
  return {
9191
- ok: !this.definition.apiKeyEnv || Boolean(apiKey),
9192
- message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"})${this.definition.apiKeyEnv && !apiKey ? `, missing ${this.definition.apiKeyEnv}` : ""}`
9258
+ ok: true,
9259
+ message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"})`
9193
9260
  };
9194
9261
  }
9195
9262
  async listModels(input) {
@@ -9860,7 +9927,7 @@ function createModelsCommand() {
9860
9927
  console.log(`Updated ${outputPath}`);
9861
9928
  console.log(`Synced ${models.length} models for ${provider.id}`);
9862
9929
  });
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) => {
9930
+ command.command("use").description("Select a model for a provider and assign it globally unless a specific role is requested.").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) => {
9864
9931
  let config = await loadConfig(import_node_process5.default.cwd());
9865
9932
  if (!config) {
9866
9933
  requireConfigMessage2();
@@ -9910,12 +9977,16 @@ function createModelsCommand() {
9910
9977
  maxTokens: options.maxTokens
9911
9978
  } : {}
9912
9979
  });
9980
+ } else {
9981
+ config = assignProviderToAllBrains(config, provider.id, model);
9913
9982
  }
9914
9983
  const outputPath = await saveConfig(config, import_node_process5.default.cwd());
9915
9984
  console.log(`Updated ${outputPath}`);
9916
9985
  console.log(`Selected ${provider.id}/${model}`);
9917
9986
  if (options.role) {
9918
9987
  console.log(`Assigned role ${options.role}`);
9988
+ } else {
9989
+ console.log("Assigned all roles");
9919
9990
  }
9920
9991
  if (shouldSetDefault) {
9921
9992
  console.log(`Set provider default model to ${model}`);
@@ -10673,12 +10744,13 @@ function renderHelp() {
10673
10744
  "/model Show current brain assignments.",
10674
10745
  "/providers List configured providers.",
10675
10746
  "/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.",
10747
+ "/providers add <tpl> [id] [--base-url <url>] [--model <id>] [--api-key-env <env>]",
10748
+ " Add a provider from a built-in template.",
10749
+ "/providers use <id> Use a provider for all agent roles.",
10678
10750
  "/providers check Run provider health checks.",
10679
10751
  "/models [search] Discover models for the active role's provider.",
10680
10752
  "/select <n> Select a model from the last numbered /models list.",
10681
- "/use-model <id> Assign a model to the active role and provider.",
10753
+ "/use-model <id> Assign a model to all agent roles.",
10682
10754
  "/plan <goal> Create a structured implementation plan.",
10683
10755
  "/run <goal> Start a Kimbho execution session for a goal.",
10684
10756
  "/resume Show the latest saved session.",
@@ -10811,6 +10883,52 @@ function defaultProviderIdForTemplate(templateId) {
10811
10883
  return `${templateId}-main`;
10812
10884
  }
10813
10885
  }
10886
+ function parseProviderAddOptions(tokens) {
10887
+ const templateId = tokens[2];
10888
+ if (!templateId) {
10889
+ throw new Error("Usage: /providers add <template> [provider-id] [--base-url <url>] [--model <id>] [--api-key-env <env>]");
10890
+ }
10891
+ let index = 3;
10892
+ let providerId;
10893
+ if (tokens[index] && !tokens[index]?.startsWith("-")) {
10894
+ providerId = tokens[index];
10895
+ index += 1;
10896
+ }
10897
+ const options = {
10898
+ templateId,
10899
+ ...providerId ? {
10900
+ providerId
10901
+ } : {}
10902
+ };
10903
+ while (index < tokens.length) {
10904
+ const flag = tokens[index];
10905
+ if (!flag?.startsWith("--")) {
10906
+ throw new Error(`Unexpected token "${flag}".`);
10907
+ }
10908
+ const value = tokens[index + 1];
10909
+ if (!value || value.startsWith("--")) {
10910
+ throw new Error(`Missing value for ${flag}.`);
10911
+ }
10912
+ switch (flag) {
10913
+ case "--label":
10914
+ options.label = value;
10915
+ break;
10916
+ case "--base-url":
10917
+ options.baseUrl = normalizeTemplateBaseUrl(templateId, value) ?? value;
10918
+ break;
10919
+ case "--api-key-env":
10920
+ options.apiKeyEnv = value;
10921
+ break;
10922
+ case "--model":
10923
+ options.model = value;
10924
+ break;
10925
+ default:
10926
+ throw new Error(`Unknown option "${flag}". Supported: --label, --base-url, --api-key-env, --model.`);
10927
+ }
10928
+ index += 2;
10929
+ }
10930
+ return options;
10931
+ }
10814
10932
  function buildCachedModels(provider, search, limit = 25) {
10815
10933
  const normalized = search?.trim().toLowerCase();
10816
10934
  const filtered = normalized ? provider.models.filter((modelId) => modelId.toLowerCase().includes(normalized)) : provider.models;
@@ -10857,7 +10975,7 @@ async function fetchModelsForProvider(cwd, config, provider, search, limit = 25)
10857
10975
  throw error;
10858
10976
  }
10859
10977
  }
10860
- async function assignModelToRole(cwd, role, providerId, model) {
10978
+ async function assignModelGlobally(cwd, providerId, model) {
10861
10979
  const config = await loadConfig(cwd);
10862
10980
  if (!config) {
10863
10981
  throw new Error("No config found. Run /init or /providers add first.");
@@ -10866,8 +10984,7 @@ async function assignModelToRole(cwd, role, providerId, model) {
10866
10984
  if (!provider) {
10867
10985
  throw new Error(`Unknown provider "${providerId}".`);
10868
10986
  }
10869
- const currentSettings = resolveBrainSettings(config, role);
10870
- const nextConfig = assignBrain(
10987
+ const nextConfig = assignProviderToAllBrains(
10871
10988
  upsertProvider(config, {
10872
10989
  ...provider,
10873
10990
  defaultModel: model,
@@ -10876,25 +10993,13 @@ async function assignModelToRole(cwd, role, providerId, model) {
10876
10993
  model
10877
10994
  ]))
10878
10995
  }),
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
- }
10996
+ providerId,
10997
+ model
10893
10998
  );
10894
10999
  const outputPath = await saveConfig(nextConfig, cwd);
10895
11000
  return outputPath;
10896
11001
  }
10897
- async function useProviderForRole(cwd, role, providerId) {
11002
+ async function useProviderGlobally(cwd, providerId) {
10898
11003
  const config = await loadConfig(cwd);
10899
11004
  if (!config) {
10900
11005
  throw new Error("No config found. Run /init or /providers add first.");
@@ -10903,23 +11008,8 @@ async function useProviderForRole(cwd, role, providerId) {
10903
11008
  if (!provider) {
10904
11009
  throw new Error(`Unknown provider "${providerId}".`);
10905
11010
  }
10906
- const currentSettings = resolveBrainSettings(config, role);
10907
11011
  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
- });
11012
+ const nextConfig = assignProviderToAllBrains(config, provider.id, resolvedModel);
10923
11013
  const outputPath = await saveConfig(nextConfig, cwd);
10924
11014
  return {
10925
11015
  outputPath,
@@ -10950,6 +11040,7 @@ async function printProviderList(cwd, focusRole) {
10950
11040
  console.log(` label: ${provider.label ?? "-"}`);
10951
11041
  console.log(` driver: ${provider.driver}`);
10952
11042
  console.log(` model: ${provider.defaultModel ?? "-"}`);
11043
+ console.log(` baseUrl: ${provider.baseUrl ?? "-"}`);
10953
11044
  console.log(` auth: ${envState}`);
10954
11045
  console.log(` cachedModels: ${provider.models.length}`);
10955
11046
  }
@@ -10964,9 +11055,21 @@ function printProviderTemplates() {
10964
11055
  console.log(` notes: ${template.notes}`);
10965
11056
  }
10966
11057
  }
10967
- async function addProviderFromTemplate(cwd, templateId, providerId) {
10968
- const provider = buildProviderFromTemplate(templateId, {
10969
- providerId: providerId ?? defaultProviderIdForTemplate(templateId)
11058
+ async function addProviderFromTemplate(cwd, options) {
11059
+ const provider = buildProviderFromTemplate(options.templateId, {
11060
+ providerId: options.providerId ?? defaultProviderIdForTemplate(options.templateId),
11061
+ ...options.label ? {
11062
+ label: options.label
11063
+ } : {},
11064
+ ...options.baseUrl ? {
11065
+ baseUrl: options.baseUrl
11066
+ } : {},
11067
+ ...options.apiKeyEnv ? {
11068
+ apiKeyEnv: options.apiKeyEnv
11069
+ } : {},
11070
+ ...options.model ? {
11071
+ model: options.model
11072
+ } : {}
10970
11073
  });
10971
11074
  const config = await loadConfig(cwd);
10972
11075
  if (!config) {
@@ -11025,30 +11128,26 @@ async function handleProvidersCommand(cwd, tokens, runtime) {
11025
11128
  return;
11026
11129
  }
11027
11130
  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);
11131
+ const options = parseProviderAddOptions(tokens);
11132
+ const outputPath = await addProviderFromTemplate(cwd, options);
11133
+ const resolvedProviderId = options.providerId ?? defaultProviderIdForTemplate(options.templateId);
11035
11134
  console.log(`Updated ${outputPath}`);
11036
- console.log(`Added provider ${resolvedProviderId} from template ${templateId}`);
11135
+ console.log(`Added provider ${resolvedProviderId} from template ${options.templateId}`);
11136
+ if (options.baseUrl) {
11137
+ console.log(`Base URL ${options.baseUrl}`);
11138
+ }
11037
11139
  console.log(`Next: /providers use ${resolvedProviderId}`);
11038
11140
  return;
11039
11141
  }
11040
11142
  if (subcommand === "use") {
11041
11143
  const providerId = tokens[2];
11042
- const role = tokens[3];
11043
11144
  if (!providerId) {
11044
- throw new Error("Usage: /providers use <provider-id> [role]");
11145
+ throw new Error("Usage: /providers use <provider-id>");
11045
11146
  }
11046
- const targetRole = role && isBrainRole(role) ? role : runtime.focusRole;
11047
- const result = await useProviderForRole(cwd, targetRole, providerId);
11048
- runtime.focusRole = targetRole;
11147
+ const result = await useProviderGlobally(cwd, providerId);
11049
11148
  runtime.lastModels = null;
11050
11149
  console.log(`Updated ${result.outputPath}`);
11051
- console.log(`Focus role ${targetRole} now uses provider ${providerId}${result.model ? ` (${result.model})` : ""}`);
11150
+ console.log(`All roles now use provider ${providerId}${result.model ? ` (${result.model})` : ""}`);
11052
11151
  return;
11053
11152
  }
11054
11153
  throw new Error("Usage: /providers [list|templates|add|use|check]");
@@ -11089,10 +11188,10 @@ async function handleModelsCommand(cwd, tokens, runtime) {
11089
11188
  if (!modelId) {
11090
11189
  throw new Error("Usage: /models use <model-id>");
11091
11190
  }
11092
- const outputPath = await assignModelToRole(cwd, runtime.focusRole, provider.id, modelId);
11191
+ const outputPath = await assignModelGlobally(cwd, provider.id, modelId);
11093
11192
  console.log(`Updated ${outputPath}`);
11094
11193
  console.log(`Selected ${provider.id}/${modelId}`);
11095
- console.log(`Assigned role ${runtime.focusRole}`);
11194
+ console.log("Assigned all roles");
11096
11195
  return;
11097
11196
  }
11098
11197
  const search = !subcommand || MODEL_SUBCOMMANDS.has(subcommand) ? void 0 : tokens.slice(1).join(" ");
@@ -11118,7 +11217,7 @@ async function handleModelsCommand(cwd, tokens, runtime) {
11118
11217
  console.log(` ${index + 1}. ${renderModelLine2(model)}`);
11119
11218
  }
11120
11219
  console.log(``);
11121
- console.log(`Use /select <number> or /use-model <model-id> to assign one to ${runtime.focusRole}.`);
11220
+ console.log("Use /select <number> or /use-model <model-id> to assign one to all roles.");
11122
11221
  }
11123
11222
  async function handleModelSelection(cwd, modelId, runtime) {
11124
11223
  const config = await loadConfig(cwd);
@@ -11130,10 +11229,10 @@ async function handleModelSelection(cwd, modelId, runtime) {
11130
11229
  if (!provider) {
11131
11230
  throw new Error(`Active role "${runtime.focusRole}" points to unknown provider "${providerId}".`);
11132
11231
  }
11133
- const outputPath = await assignModelToRole(cwd, runtime.focusRole, provider.id, modelId);
11232
+ const outputPath = await assignModelGlobally(cwd, provider.id, modelId);
11134
11233
  console.log(`Updated ${outputPath}`);
11135
11234
  console.log(`Selected ${provider.id}/${modelId}`);
11136
- console.log(`Assigned role ${runtime.focusRole}`);
11235
+ console.log("Assigned all roles");
11137
11236
  }
11138
11237
  async function handleSelectCommand(cwd, tokens, runtime) {
11139
11238
  const rawIndex = tokens[1];