@kimbho/kimbho-cli 0.1.2 → 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 +17 -2
- package/dist/index.cjs +646 -81
- package/dist/index.cjs.map +3 -3
- package/package.json +1 -1
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.
|
|
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
|
|
8928
|
+
function trimTrailingSlash2(value) {
|
|
8879
8929
|
return value.replace(/\/+$/, "");
|
|
8880
8930
|
}
|
|
8881
8931
|
function joinUrl(baseUrl, suffix) {
|
|
8882
|
-
const normalizedBase =
|
|
8932
|
+
const normalizedBase = trimTrailingSlash2(baseUrl);
|
|
8883
8933
|
const normalizedSuffix = suffix.startsWith("/") ? suffix : `/${suffix}`;
|
|
8884
8934
|
return `${normalizedBase}${normalizedSuffix}`;
|
|
8885
8935
|
}
|
|
@@ -8908,7 +8958,7 @@ function buildProviderHeaders(definition, apiKey, includeJsonContentType = true)
|
|
|
8908
8958
|
}
|
|
8909
8959
|
function filterModels(models, input = {}) {
|
|
8910
8960
|
const normalized = input.search?.trim().toLowerCase();
|
|
8911
|
-
const filtered = normalized ? models.filter((model) => [model.id, model.name
|
|
8961
|
+
const filtered = normalized ? models.filter((model) => [model.id, model.name].filter((value) => Boolean(value)).some((value) => value.toLowerCase().includes(normalized))) : models;
|
|
8912
8962
|
return typeof input.limit === "number" ? filtered.slice(0, input.limit) : filtered;
|
|
8913
8963
|
}
|
|
8914
8964
|
function mapOpenAIStyleModels(providerId, payload, input) {
|
|
@@ -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:
|
|
9192
|
-
message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"})
|
|
9258
|
+
ok: true,
|
|
9259
|
+
message: `${this.definition.baseUrl} (${this.definition.defaultModel ?? "model not set"})`
|
|
9193
9260
|
};
|
|
9194
9261
|
}
|
|
9195
9262
|
async listModels(input) {
|
|
@@ -9745,11 +9812,19 @@ function renderModelLine(model) {
|
|
|
9745
9812
|
].filter((value) => Boolean(value));
|
|
9746
9813
|
return details.length > 0 ? `${model.id} | ${details.join(" | ")}` : model.id;
|
|
9747
9814
|
}
|
|
9815
|
+
function filterCachedModels(models, options) {
|
|
9816
|
+
const normalizedSearch = options.search?.trim().toLowerCase();
|
|
9817
|
+
const filtered = normalizedSearch ? models.filter((model) => model.id.toLowerCase().includes(normalizedSearch)) : models;
|
|
9818
|
+
return typeof options.limit === "number" ? filtered.slice(0, options.limit) : filtered;
|
|
9819
|
+
}
|
|
9748
9820
|
async function fetchProviderModels(provider, options) {
|
|
9749
|
-
const cachedModels =
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9821
|
+
const cachedModels = filterCachedModels(
|
|
9822
|
+
provider.models.map((modelId) => ({
|
|
9823
|
+
id: modelId,
|
|
9824
|
+
providerId: provider.id
|
|
9825
|
+
})),
|
|
9826
|
+
options
|
|
9827
|
+
);
|
|
9753
9828
|
if (options.cached) {
|
|
9754
9829
|
return {
|
|
9755
9830
|
source: "cache",
|
|
@@ -9852,7 +9927,7 @@ function createModelsCommand() {
|
|
|
9852
9927
|
console.log(`Updated ${outputPath}`);
|
|
9853
9928
|
console.log(`Synced ${models.length} models for ${provider.id}`);
|
|
9854
9929
|
});
|
|
9855
|
-
command.command("use").description("Select a model for a provider and
|
|
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) => {
|
|
9856
9931
|
let config = await loadConfig(import_node_process5.default.cwd());
|
|
9857
9932
|
if (!config) {
|
|
9858
9933
|
requireConfigMessage2();
|
|
@@ -9902,12 +9977,16 @@ function createModelsCommand() {
|
|
|
9902
9977
|
maxTokens: options.maxTokens
|
|
9903
9978
|
} : {}
|
|
9904
9979
|
});
|
|
9980
|
+
} else {
|
|
9981
|
+
config = assignProviderToAllBrains(config, provider.id, model);
|
|
9905
9982
|
}
|
|
9906
9983
|
const outputPath = await saveConfig(config, import_node_process5.default.cwd());
|
|
9907
9984
|
console.log(`Updated ${outputPath}`);
|
|
9908
9985
|
console.log(`Selected ${provider.id}/${model}`);
|
|
9909
9986
|
if (options.role) {
|
|
9910
9987
|
console.log(`Assigned role ${options.role}`);
|
|
9988
|
+
} else {
|
|
9989
|
+
console.log("Assigned all roles");
|
|
9911
9990
|
}
|
|
9912
9991
|
if (shouldSetDefault) {
|
|
9913
9992
|
console.log(`Set provider default model to ${model}`);
|
|
@@ -10559,6 +10638,7 @@ var DIM = "\x1B[2m";
|
|
|
10559
10638
|
var RESET = "\x1B[0m";
|
|
10560
10639
|
var TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set([
|
|
10561
10640
|
"agents",
|
|
10641
|
+
"brain",
|
|
10562
10642
|
"brains",
|
|
10563
10643
|
"doctor",
|
|
10564
10644
|
"fix",
|
|
@@ -10568,15 +10648,30 @@ var TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
10568
10648
|
"models",
|
|
10569
10649
|
"new",
|
|
10570
10650
|
"plan",
|
|
10651
|
+
"provider",
|
|
10571
10652
|
"providers",
|
|
10572
10653
|
"quit",
|
|
10573
10654
|
"resume",
|
|
10574
10655
|
"review",
|
|
10575
10656
|
"run",
|
|
10576
10657
|
"scaffold",
|
|
10658
|
+
"select",
|
|
10577
10659
|
"shell",
|
|
10578
|
-
"status"
|
|
10660
|
+
"status",
|
|
10661
|
+
"use-model"
|
|
10579
10662
|
]);
|
|
10663
|
+
var MODEL_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
10664
|
+
"help",
|
|
10665
|
+
"list",
|
|
10666
|
+
"sync",
|
|
10667
|
+
"use"
|
|
10668
|
+
]);
|
|
10669
|
+
var BRAIN_ROLES = [
|
|
10670
|
+
"planner",
|
|
10671
|
+
"coder",
|
|
10672
|
+
"reviewer",
|
|
10673
|
+
"fast"
|
|
10674
|
+
];
|
|
10580
10675
|
function color(code, value) {
|
|
10581
10676
|
return `${code}${value}${RESET}`;
|
|
10582
10677
|
}
|
|
@@ -10589,12 +10684,14 @@ function renderBanner() {
|
|
|
10589
10684
|
"|_|\\_\\_|_| |_|____/|____/|_| |_|\\___/ "
|
|
10590
10685
|
].map((line) => color(AMBER, line)).join("\n");
|
|
10591
10686
|
}
|
|
10592
|
-
async function getShellSessionState(cwd) {
|
|
10687
|
+
async function getShellSessionState(cwd, focusRole) {
|
|
10593
10688
|
const config = await loadConfig(cwd);
|
|
10594
10689
|
if (!config) {
|
|
10595
10690
|
return {
|
|
10691
|
+
focusRole,
|
|
10596
10692
|
providerLabel: "unconfigured",
|
|
10597
10693
|
providerId: "-",
|
|
10694
|
+
focusModel: "not set",
|
|
10598
10695
|
coderModel: "not set",
|
|
10599
10696
|
plannerModel: "not set",
|
|
10600
10697
|
approvalMode: "manual",
|
|
@@ -10603,11 +10700,13 @@ async function getShellSessionState(cwd) {
|
|
|
10603
10700
|
configured: false
|
|
10604
10701
|
};
|
|
10605
10702
|
}
|
|
10606
|
-
const
|
|
10607
|
-
const provider = findProviderById(config,
|
|
10703
|
+
const focusSettings = config.brains[focusRole];
|
|
10704
|
+
const provider = findProviderById(config, focusSettings.providerId);
|
|
10608
10705
|
return {
|
|
10706
|
+
focusRole,
|
|
10609
10707
|
providerLabel: provider?.label ?? provider?.id ?? "unknown",
|
|
10610
|
-
providerId: provider?.id ??
|
|
10708
|
+
providerId: provider?.id ?? focusSettings.providerId,
|
|
10709
|
+
focusModel: resolveBrainModel(config, focusRole) ?? "not set",
|
|
10611
10710
|
coderModel: resolveBrainModel(config, "coder") ?? "not set",
|
|
10612
10711
|
plannerModel: resolveBrainModel(config, "planner") ?? "not set",
|
|
10613
10712
|
approvalMode: config.approvalMode,
|
|
@@ -10640,20 +10739,25 @@ function renderBox(lines) {
|
|
|
10640
10739
|
function renderHelp() {
|
|
10641
10740
|
return [
|
|
10642
10741
|
`${color(DIM, "Commands")}`,
|
|
10643
|
-
"/status
|
|
10644
|
-
"/
|
|
10645
|
-
"/
|
|
10646
|
-
"/
|
|
10647
|
-
"/
|
|
10648
|
-
"/
|
|
10649
|
-
"
|
|
10650
|
-
"/providers
|
|
10651
|
-
"/
|
|
10652
|
-
"/models
|
|
10653
|
-
"/
|
|
10654
|
-
"/
|
|
10655
|
-
"/
|
|
10656
|
-
"/
|
|
10742
|
+
"/status Show the active role, provider, and workspace state.",
|
|
10743
|
+
"/brain <role> Change shell focus to planner, coder, reviewer, or fast.",
|
|
10744
|
+
"/model Show current brain assignments.",
|
|
10745
|
+
"/providers List configured providers.",
|
|
10746
|
+
"/providers templates Show built-in provider templates.",
|
|
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.",
|
|
10750
|
+
"/providers check Run provider health checks.",
|
|
10751
|
+
"/models [search] Discover models for the active role's provider.",
|
|
10752
|
+
"/select <n> Select a model from the last numbered /models list.",
|
|
10753
|
+
"/use-model <id> Assign a model to all agent roles.",
|
|
10754
|
+
"/plan <goal> Create a structured implementation plan.",
|
|
10755
|
+
"/run <goal> Start a Kimbho execution session for a goal.",
|
|
10756
|
+
"/resume Show the latest saved session.",
|
|
10757
|
+
"/agents Inspect agent roles and the active session.",
|
|
10758
|
+
"/doctor Check local environment and config.",
|
|
10759
|
+
"/clear Redraw the shell.",
|
|
10760
|
+
"/quit, /exit Leave the shell.",
|
|
10657
10761
|
"",
|
|
10658
10762
|
`${color(DIM, "Tip")}`,
|
|
10659
10763
|
"Type a natural-language goal directly and Kimbho will treat it as /run <goal>."
|
|
@@ -10662,6 +10766,8 @@ function renderHelp() {
|
|
|
10662
10766
|
function renderStartupCard(cwd, state) {
|
|
10663
10767
|
const cardLines = [
|
|
10664
10768
|
`${color(BOLD, "Kimbho CLI")} (v${KIMBHO_VERSION})`,
|
|
10769
|
+
renderCardLine("focus", state.focusRole),
|
|
10770
|
+
renderCardLine("model", state.focusModel),
|
|
10665
10771
|
renderCardLine("coder", state.coderModel),
|
|
10666
10772
|
renderCardLine("planner", state.plannerModel),
|
|
10667
10773
|
renderCardLine("provider", `${state.providerLabel} [${state.providerId}]`),
|
|
@@ -10669,23 +10775,23 @@ function renderStartupCard(cwd, state) {
|
|
|
10669
10775
|
renderCardLine("approval", state.approvalMode),
|
|
10670
10776
|
renderCardLine("sandbox", state.sandboxMode),
|
|
10671
10777
|
renderCardLine("preset", state.stackPreset),
|
|
10672
|
-
renderCardLine("shortcuts", "/
|
|
10778
|
+
renderCardLine("shortcuts", "/brain /providers /models /select /use-model /quit")
|
|
10673
10779
|
];
|
|
10674
10780
|
if (!state.configured) {
|
|
10675
|
-
cardLines.push("setup: run /init to create .kimbho/config.json");
|
|
10781
|
+
cardLines.push("setup: run /init or /providers add <template> to create .kimbho/config.json");
|
|
10676
10782
|
}
|
|
10677
10783
|
return renderBox(cardLines);
|
|
10678
10784
|
}
|
|
10679
10785
|
function formatPrompt(state) {
|
|
10680
|
-
const
|
|
10681
|
-
return `${color(AMBER, "kimbho")} ${color(DIM, `[${
|
|
10786
|
+
const model = state.focusModel === "not set" ? "unconfigured" : shortenMiddle(state.focusModel, 18);
|
|
10787
|
+
return `${color(AMBER, "kimbho")} ${color(DIM, `[${state.focusRole}:${model}]`)} > `;
|
|
10682
10788
|
}
|
|
10683
10789
|
function printHeader(cwd, state) {
|
|
10684
10790
|
console.log(renderBanner());
|
|
10685
10791
|
console.log(color(DIM, "Terminal-native coding agent"));
|
|
10686
10792
|
console.log(renderStartupCard(cwd, state));
|
|
10687
10793
|
console.log("");
|
|
10688
|
-
console.log(color(DIM, "Tip:
|
|
10794
|
+
console.log(color(DIM, "Tip: configure providers and models here, then describe the work and Kimbho will route it into /run."));
|
|
10689
10795
|
console.log(renderHelp());
|
|
10690
10796
|
console.log("");
|
|
10691
10797
|
}
|
|
@@ -10736,21 +10842,426 @@ function tokenizeInput(input) {
|
|
|
10736
10842
|
}
|
|
10737
10843
|
return tokens;
|
|
10738
10844
|
}
|
|
10739
|
-
function
|
|
10845
|
+
function normalizeInputTokens(input) {
|
|
10740
10846
|
const trimmed = input.trim();
|
|
10741
10847
|
if (!trimmed) {
|
|
10742
|
-
return
|
|
10848
|
+
return {
|
|
10849
|
+
normalizedInput: "",
|
|
10850
|
+
tokens: [],
|
|
10851
|
+
head: null
|
|
10852
|
+
};
|
|
10743
10853
|
}
|
|
10744
10854
|
const normalizedInput = trimmed.startsWith("kimbho ") ? trimmed.slice("kimbho ".length).trim() : trimmed;
|
|
10745
10855
|
const tokens = tokenizeInput(normalizedInput);
|
|
10746
|
-
|
|
10856
|
+
const firstToken = tokens[0];
|
|
10857
|
+
if (!firstToken) {
|
|
10858
|
+
return {
|
|
10859
|
+
normalizedInput,
|
|
10860
|
+
tokens,
|
|
10861
|
+
head: null
|
|
10862
|
+
};
|
|
10863
|
+
}
|
|
10864
|
+
return {
|
|
10865
|
+
normalizedInput,
|
|
10866
|
+
tokens,
|
|
10867
|
+
head: firstToken.startsWith("/") ? firstToken.slice(1) : firstToken
|
|
10868
|
+
};
|
|
10869
|
+
}
|
|
10870
|
+
function defaultProviderIdForTemplate(templateId) {
|
|
10871
|
+
switch (templateId) {
|
|
10872
|
+
case "openai":
|
|
10873
|
+
return "openai-main";
|
|
10874
|
+
case "anthropic":
|
|
10875
|
+
return "anthropic-main";
|
|
10876
|
+
case "openrouter":
|
|
10877
|
+
return "openrouter-main";
|
|
10878
|
+
case "ollama":
|
|
10879
|
+
return "ollama-local";
|
|
10880
|
+
case "lmstudio":
|
|
10881
|
+
return "lmstudio-local";
|
|
10882
|
+
default:
|
|
10883
|
+
return `${templateId}-main`;
|
|
10884
|
+
}
|
|
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
|
+
}
|
|
10932
|
+
function buildCachedModels(provider, search, limit = 25) {
|
|
10933
|
+
const normalized = search?.trim().toLowerCase();
|
|
10934
|
+
const filtered = normalized ? provider.models.filter((modelId) => modelId.toLowerCase().includes(normalized)) : provider.models;
|
|
10935
|
+
return filtered.slice(0, limit).map((modelId) => ({
|
|
10936
|
+
id: modelId,
|
|
10937
|
+
providerId: provider.id
|
|
10938
|
+
}));
|
|
10939
|
+
}
|
|
10940
|
+
async function fetchModelsForProvider(cwd, config, provider, search, limit = 25) {
|
|
10941
|
+
const registry = createDefaultBrainProviderRegistry(cwd);
|
|
10942
|
+
const cachedModels = buildCachedModels(provider, search, limit);
|
|
10943
|
+
try {
|
|
10944
|
+
const models = await registry.listModels(provider, {
|
|
10945
|
+
...search ? {
|
|
10946
|
+
search
|
|
10947
|
+
} : {},
|
|
10948
|
+
limit
|
|
10949
|
+
});
|
|
10950
|
+
const nextConfig = upsertProvider(config, {
|
|
10951
|
+
...provider,
|
|
10952
|
+
models: Array.from(/* @__PURE__ */ new Set([
|
|
10953
|
+
...provider.models,
|
|
10954
|
+
...models.map((model) => model.id)
|
|
10955
|
+
]))
|
|
10956
|
+
});
|
|
10957
|
+
if (nextConfig !== config) {
|
|
10958
|
+
await saveConfig(nextConfig, cwd);
|
|
10959
|
+
}
|
|
10960
|
+
return {
|
|
10961
|
+
config: nextConfig,
|
|
10962
|
+
source: "remote",
|
|
10963
|
+
models
|
|
10964
|
+
};
|
|
10965
|
+
} catch (error) {
|
|
10966
|
+
if (cachedModels.length > 0) {
|
|
10967
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10968
|
+
return {
|
|
10969
|
+
config,
|
|
10970
|
+
source: "cache",
|
|
10971
|
+
note: `Using cached models because remote discovery failed: ${message}`,
|
|
10972
|
+
models: cachedModels
|
|
10973
|
+
};
|
|
10974
|
+
}
|
|
10975
|
+
throw error;
|
|
10976
|
+
}
|
|
10977
|
+
}
|
|
10978
|
+
async function assignModelGlobally(cwd, providerId, model) {
|
|
10979
|
+
const config = await loadConfig(cwd);
|
|
10980
|
+
if (!config) {
|
|
10981
|
+
throw new Error("No config found. Run /init or /providers add first.");
|
|
10982
|
+
}
|
|
10983
|
+
const provider = findProviderById(config, providerId);
|
|
10984
|
+
if (!provider) {
|
|
10985
|
+
throw new Error(`Unknown provider "${providerId}".`);
|
|
10986
|
+
}
|
|
10987
|
+
const nextConfig = assignProviderToAllBrains(
|
|
10988
|
+
upsertProvider(config, {
|
|
10989
|
+
...provider,
|
|
10990
|
+
defaultModel: model,
|
|
10991
|
+
models: Array.from(/* @__PURE__ */ new Set([
|
|
10992
|
+
...provider.models,
|
|
10993
|
+
model
|
|
10994
|
+
]))
|
|
10995
|
+
}),
|
|
10996
|
+
providerId,
|
|
10997
|
+
model
|
|
10998
|
+
);
|
|
10999
|
+
const outputPath = await saveConfig(nextConfig, cwd);
|
|
11000
|
+
return outputPath;
|
|
11001
|
+
}
|
|
11002
|
+
async function useProviderGlobally(cwd, providerId) {
|
|
11003
|
+
const config = await loadConfig(cwd);
|
|
11004
|
+
if (!config) {
|
|
11005
|
+
throw new Error("No config found. Run /init or /providers add first.");
|
|
11006
|
+
}
|
|
11007
|
+
const provider = findProviderById(config, providerId);
|
|
11008
|
+
if (!provider) {
|
|
11009
|
+
throw new Error(`Unknown provider "${providerId}".`);
|
|
11010
|
+
}
|
|
11011
|
+
const resolvedModel = provider.defaultModel ?? provider.models[0] ?? null;
|
|
11012
|
+
const nextConfig = assignProviderToAllBrains(config, provider.id, resolvedModel);
|
|
11013
|
+
const outputPath = await saveConfig(nextConfig, cwd);
|
|
11014
|
+
return {
|
|
11015
|
+
outputPath,
|
|
11016
|
+
model: resolvedModel
|
|
11017
|
+
};
|
|
11018
|
+
}
|
|
11019
|
+
function renderModelLine2(model) {
|
|
11020
|
+
const details = [
|
|
11021
|
+
model.name,
|
|
11022
|
+
model.contextLength ? `ctx ${model.contextLength}` : null,
|
|
11023
|
+
model.promptPrice ? `in ${model.promptPrice}` : null,
|
|
11024
|
+
model.completionPrice ? `out ${model.completionPrice}` : null,
|
|
11025
|
+
model.modality
|
|
11026
|
+
].filter((value) => Boolean(value));
|
|
11027
|
+
return details.length > 0 ? `${model.id} | ${details.join(" | ")}` : model.id;
|
|
11028
|
+
}
|
|
11029
|
+
async function printProviderList(cwd, focusRole) {
|
|
11030
|
+
const config = await loadConfig(cwd);
|
|
11031
|
+
if (!config) {
|
|
11032
|
+
console.log("No config found. Run /init or /providers add <template> first.");
|
|
11033
|
+
return;
|
|
11034
|
+
}
|
|
11035
|
+
const activeProviderId = config.brains[focusRole].providerId;
|
|
11036
|
+
for (const provider of config.providers) {
|
|
11037
|
+
const marker = provider.id === activeProviderId ? "*" : " ";
|
|
11038
|
+
const envState = provider.apiKeyEnv ? import_node_process10.default.env[provider.apiKeyEnv] ? "present" : `missing ${provider.apiKeyEnv}` : "no key required";
|
|
11039
|
+
console.log(`${marker} ${provider.id}`);
|
|
11040
|
+
console.log(` label: ${provider.label ?? "-"}`);
|
|
11041
|
+
console.log(` driver: ${provider.driver}`);
|
|
11042
|
+
console.log(` model: ${provider.defaultModel ?? "-"}`);
|
|
11043
|
+
console.log(` baseUrl: ${provider.baseUrl ?? "-"}`);
|
|
11044
|
+
console.log(` auth: ${envState}`);
|
|
11045
|
+
console.log(` cachedModels: ${provider.models.length}`);
|
|
11046
|
+
}
|
|
11047
|
+
}
|
|
11048
|
+
function printProviderTemplates() {
|
|
11049
|
+
for (const template of listProviderTemplates()) {
|
|
11050
|
+
console.log(`${template.id}`);
|
|
11051
|
+
console.log(` label: ${template.label}`);
|
|
11052
|
+
console.log(` driver: ${template.driver}`);
|
|
11053
|
+
console.log(` apiKeyEnv: ${template.defaultApiKeyEnv ?? "-"}`);
|
|
11054
|
+
console.log(` model: ${template.defaultModel ?? "-"}`);
|
|
11055
|
+
console.log(` notes: ${template.notes}`);
|
|
11056
|
+
}
|
|
11057
|
+
}
|
|
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
|
+
} : {}
|
|
11073
|
+
});
|
|
11074
|
+
const config = await loadConfig(cwd);
|
|
11075
|
+
if (!config) {
|
|
11076
|
+
const outputPath = await saveConfig(createDefaultConfig({ provider }), cwd);
|
|
11077
|
+
return outputPath;
|
|
11078
|
+
}
|
|
11079
|
+
return saveConfig(upsertProvider(config, provider), cwd);
|
|
11080
|
+
}
|
|
11081
|
+
async function printProviderHealth(cwd) {
|
|
11082
|
+
const config = await loadConfig(cwd);
|
|
11083
|
+
if (!config) {
|
|
11084
|
+
console.log("No config found. Run /init or /providers add <template> first.");
|
|
11085
|
+
return;
|
|
11086
|
+
}
|
|
11087
|
+
const registry = createDefaultBrainProviderRegistry(cwd);
|
|
11088
|
+
for (const provider of config.providers) {
|
|
11089
|
+
try {
|
|
11090
|
+
const result = await registry.healthCheck(provider);
|
|
11091
|
+
console.log(`${result.ok ? "PASS" : "FAIL"} ${provider.id}: ${provider.driver} | ${result.message}`);
|
|
11092
|
+
} catch (error) {
|
|
11093
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11094
|
+
console.log(`FAIL ${provider.id}: ${provider.driver} | ${message}`);
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11097
|
+
}
|
|
11098
|
+
async function printBrainAssignments(cwd) {
|
|
11099
|
+
const config = await loadConfig(cwd);
|
|
11100
|
+
if (!config) {
|
|
11101
|
+
console.log("No config found. Run /init or /providers add <template> first.");
|
|
11102
|
+
return;
|
|
11103
|
+
}
|
|
11104
|
+
for (const role of BRAIN_ROLES) {
|
|
11105
|
+
const settings = resolveBrainSettings(config, role);
|
|
11106
|
+
console.log(`${role}`);
|
|
11107
|
+
console.log(` provider: ${settings.providerId}`);
|
|
11108
|
+
console.log(` model: ${resolveBrainModel(config, role) ?? "-"}`);
|
|
11109
|
+
console.log(` temperature: ${settings.temperature ?? "-"}`);
|
|
11110
|
+
console.log(` maxTokens: ${settings.maxTokens ?? "-"}`);
|
|
11111
|
+
}
|
|
11112
|
+
}
|
|
11113
|
+
function isBrainRole(value) {
|
|
11114
|
+
return BRAIN_ROLES.includes(value);
|
|
11115
|
+
}
|
|
11116
|
+
async function handleProvidersCommand(cwd, tokens, runtime) {
|
|
11117
|
+
const subcommand = tokens[1];
|
|
11118
|
+
if (!subcommand || subcommand === "list") {
|
|
11119
|
+
await printProviderList(cwd, runtime.focusRole);
|
|
11120
|
+
return;
|
|
11121
|
+
}
|
|
11122
|
+
if (subcommand === "templates") {
|
|
11123
|
+
printProviderTemplates();
|
|
11124
|
+
return;
|
|
11125
|
+
}
|
|
11126
|
+
if (subcommand === "check") {
|
|
11127
|
+
await printProviderHealth(cwd);
|
|
11128
|
+
return;
|
|
11129
|
+
}
|
|
11130
|
+
if (subcommand === "add") {
|
|
11131
|
+
const options = parseProviderAddOptions(tokens);
|
|
11132
|
+
const outputPath = await addProviderFromTemplate(cwd, options);
|
|
11133
|
+
const resolvedProviderId = options.providerId ?? defaultProviderIdForTemplate(options.templateId);
|
|
11134
|
+
console.log(`Updated ${outputPath}`);
|
|
11135
|
+
console.log(`Added provider ${resolvedProviderId} from template ${options.templateId}`);
|
|
11136
|
+
if (options.baseUrl) {
|
|
11137
|
+
console.log(`Base URL ${options.baseUrl}`);
|
|
11138
|
+
}
|
|
11139
|
+
console.log(`Next: /providers use ${resolvedProviderId}`);
|
|
11140
|
+
return;
|
|
11141
|
+
}
|
|
11142
|
+
if (subcommand === "use") {
|
|
11143
|
+
const providerId = tokens[2];
|
|
11144
|
+
if (!providerId) {
|
|
11145
|
+
throw new Error("Usage: /providers use <provider-id>");
|
|
11146
|
+
}
|
|
11147
|
+
const result = await useProviderGlobally(cwd, providerId);
|
|
11148
|
+
runtime.lastModels = null;
|
|
11149
|
+
console.log(`Updated ${result.outputPath}`);
|
|
11150
|
+
console.log(`All roles now use provider ${providerId}${result.model ? ` (${result.model})` : ""}`);
|
|
11151
|
+
return;
|
|
11152
|
+
}
|
|
11153
|
+
throw new Error("Usage: /providers [list|templates|add|use|check]");
|
|
11154
|
+
}
|
|
11155
|
+
async function handleBrainCommand(cwd, tokens, runtime) {
|
|
11156
|
+
const subcommand = tokens[1];
|
|
11157
|
+
if (!subcommand || subcommand === "list") {
|
|
11158
|
+
await printBrainAssignments(cwd);
|
|
11159
|
+
return;
|
|
11160
|
+
}
|
|
11161
|
+
if (subcommand === "use" && tokens[2] && isBrainRole(tokens[2])) {
|
|
11162
|
+
runtime.focusRole = tokens[2];
|
|
11163
|
+
runtime.lastModels = null;
|
|
11164
|
+
console.log(`Shell focus role is now ${runtime.focusRole}.`);
|
|
11165
|
+
return;
|
|
11166
|
+
}
|
|
11167
|
+
if (isBrainRole(subcommand)) {
|
|
11168
|
+
runtime.focusRole = subcommand;
|
|
11169
|
+
runtime.lastModels = null;
|
|
11170
|
+
console.log(`Shell focus role is now ${runtime.focusRole}.`);
|
|
11171
|
+
return;
|
|
11172
|
+
}
|
|
11173
|
+
throw new Error("Usage: /brain [planner|coder|reviewer|fast]");
|
|
11174
|
+
}
|
|
11175
|
+
async function handleModelsCommand(cwd, tokens, runtime) {
|
|
11176
|
+
const config = await loadConfig(cwd);
|
|
11177
|
+
if (!config) {
|
|
11178
|
+
throw new Error("No config found. Run /init or /providers add <template> first.");
|
|
11179
|
+
}
|
|
11180
|
+
const providerId = config.brains[runtime.focusRole].providerId;
|
|
11181
|
+
const provider = findProviderById(config, providerId);
|
|
11182
|
+
if (!provider) {
|
|
11183
|
+
throw new Error(`Active role "${runtime.focusRole}" points to unknown provider "${providerId}".`);
|
|
11184
|
+
}
|
|
11185
|
+
const subcommand = tokens[1];
|
|
11186
|
+
if (subcommand === "use") {
|
|
11187
|
+
const modelId = tokens.slice(2).join(" ").trim();
|
|
11188
|
+
if (!modelId) {
|
|
11189
|
+
throw new Error("Usage: /models use <model-id>");
|
|
11190
|
+
}
|
|
11191
|
+
const outputPath = await assignModelGlobally(cwd, provider.id, modelId);
|
|
11192
|
+
console.log(`Updated ${outputPath}`);
|
|
11193
|
+
console.log(`Selected ${provider.id}/${modelId}`);
|
|
11194
|
+
console.log("Assigned all roles");
|
|
11195
|
+
return;
|
|
11196
|
+
}
|
|
11197
|
+
const search = !subcommand || MODEL_SUBCOMMANDS.has(subcommand) ? void 0 : tokens.slice(1).join(" ");
|
|
11198
|
+
const result = await fetchModelsForProvider(cwd, config, provider, search, 25);
|
|
11199
|
+
runtime.lastModels = {
|
|
11200
|
+
providerId: provider.id,
|
|
11201
|
+
role: runtime.focusRole,
|
|
11202
|
+
source: result.source,
|
|
11203
|
+
...search ? {
|
|
11204
|
+
search
|
|
11205
|
+
} : {},
|
|
11206
|
+
models: result.models
|
|
11207
|
+
};
|
|
11208
|
+
console.log(`${provider.id} (${result.source})`);
|
|
11209
|
+
if (result.note) {
|
|
11210
|
+
console.log(` note: ${result.note}`);
|
|
11211
|
+
}
|
|
11212
|
+
if (result.models.length === 0) {
|
|
11213
|
+
console.log(" no models found");
|
|
11214
|
+
return;
|
|
11215
|
+
}
|
|
11216
|
+
for (const [index, model] of result.models.entries()) {
|
|
11217
|
+
console.log(` ${index + 1}. ${renderModelLine2(model)}`);
|
|
11218
|
+
}
|
|
11219
|
+
console.log(``);
|
|
11220
|
+
console.log("Use /select <number> or /use-model <model-id> to assign one to all roles.");
|
|
11221
|
+
}
|
|
11222
|
+
async function handleModelSelection(cwd, modelId, runtime) {
|
|
11223
|
+
const config = await loadConfig(cwd);
|
|
11224
|
+
if (!config) {
|
|
11225
|
+
throw new Error("No config found. Run /init or /providers add <template> first.");
|
|
11226
|
+
}
|
|
11227
|
+
const providerId = config.brains[runtime.focusRole].providerId;
|
|
11228
|
+
const provider = findProviderById(config, providerId);
|
|
11229
|
+
if (!provider) {
|
|
11230
|
+
throw new Error(`Active role "${runtime.focusRole}" points to unknown provider "${providerId}".`);
|
|
11231
|
+
}
|
|
11232
|
+
const outputPath = await assignModelGlobally(cwd, provider.id, modelId);
|
|
11233
|
+
console.log(`Updated ${outputPath}`);
|
|
11234
|
+
console.log(`Selected ${provider.id}/${modelId}`);
|
|
11235
|
+
console.log("Assigned all roles");
|
|
11236
|
+
}
|
|
11237
|
+
async function handleSelectCommand(cwd, tokens, runtime) {
|
|
11238
|
+
const rawIndex = tokens[1];
|
|
11239
|
+
if (!rawIndex) {
|
|
11240
|
+
throw new Error("Usage: /select <number>");
|
|
11241
|
+
}
|
|
11242
|
+
if (!runtime.lastModels || runtime.lastModels.models.length === 0) {
|
|
11243
|
+
throw new Error("No model list is active. Run /models first.");
|
|
11244
|
+
}
|
|
11245
|
+
const index = Number.parseInt(rawIndex, 10);
|
|
11246
|
+
if (!Number.isInteger(index) || index <= 0) {
|
|
11247
|
+
throw new Error(`Expected a positive model number, received "${rawIndex}".`);
|
|
11248
|
+
}
|
|
11249
|
+
const model = runtime.lastModels.models[index - 1];
|
|
11250
|
+
if (!model) {
|
|
11251
|
+
throw new Error(`Model number ${index} is out of range.`);
|
|
11252
|
+
}
|
|
11253
|
+
runtime.focusRole = runtime.lastModels.role;
|
|
11254
|
+
await handleModelSelection(cwd, model.id, runtime);
|
|
11255
|
+
}
|
|
11256
|
+
function toExternalCommandTokens(input, state) {
|
|
11257
|
+
const { normalizedInput, tokens, head } = normalizeInputTokens(input);
|
|
11258
|
+
if (!head || tokens.length === 0) {
|
|
10747
11259
|
return [];
|
|
10748
11260
|
}
|
|
10749
11261
|
const firstToken = tokens[0];
|
|
10750
11262
|
if (!firstToken) {
|
|
10751
11263
|
return [];
|
|
10752
11264
|
}
|
|
10753
|
-
const head = firstToken.startsWith("/") ? firstToken.slice(1) : firstToken;
|
|
10754
11265
|
if (head === "new") {
|
|
10755
11266
|
return [
|
|
10756
11267
|
"run",
|
|
@@ -10764,12 +11275,6 @@ function toCommandTokens(input) {
|
|
|
10764
11275
|
`scaffold ${goal}`.trim()
|
|
10765
11276
|
];
|
|
10766
11277
|
}
|
|
10767
|
-
if (head === "model") {
|
|
10768
|
-
return [
|
|
10769
|
-
"brains",
|
|
10770
|
-
"list"
|
|
10771
|
-
];
|
|
10772
|
-
}
|
|
10773
11278
|
if (!firstToken.startsWith("/") && !TOP_LEVEL_COMMANDS.has(head) && !head.startsWith("-")) {
|
|
10774
11279
|
return [
|
|
10775
11280
|
"run",
|
|
@@ -10779,62 +11284,122 @@ function toCommandTokens(input) {
|
|
|
10779
11284
|
tokens[0] = head;
|
|
10780
11285
|
return tokens;
|
|
10781
11286
|
}
|
|
11287
|
+
async function handleShellCommand(cwd, input, state, runtime, execute) {
|
|
11288
|
+
const trimmed = input.trim();
|
|
11289
|
+
if (!trimmed) {
|
|
11290
|
+
return;
|
|
11291
|
+
}
|
|
11292
|
+
if (trimmed === "/exit" || trimmed === "exit" || trimmed === "quit" || trimmed === "/quit") {
|
|
11293
|
+
throw new Error("__kimbho_exit__");
|
|
11294
|
+
}
|
|
11295
|
+
if (trimmed === "/help" || trimmed === "help" || trimmed === "?") {
|
|
11296
|
+
console.log(renderHelp());
|
|
11297
|
+
return;
|
|
11298
|
+
}
|
|
11299
|
+
if (trimmed === "/status" || trimmed === "status") {
|
|
11300
|
+
console.log(renderStartupCard(cwd, state));
|
|
11301
|
+
return;
|
|
11302
|
+
}
|
|
11303
|
+
if (trimmed === "/clear" || trimmed === "clear") {
|
|
11304
|
+
if (import_node_process10.default.stdout.isTTY) {
|
|
11305
|
+
import_node_process10.default.stdout.write("\x1Bc");
|
|
11306
|
+
}
|
|
11307
|
+
const nextState = await getShellSessionState(cwd, runtime.focusRole);
|
|
11308
|
+
printHeader(cwd, nextState);
|
|
11309
|
+
return;
|
|
11310
|
+
}
|
|
11311
|
+
const { tokens, head } = normalizeInputTokens(trimmed);
|
|
11312
|
+
if (!head) {
|
|
11313
|
+
return;
|
|
11314
|
+
}
|
|
11315
|
+
if (head === "provider" || head === "providers") {
|
|
11316
|
+
const subcommand = tokens[1];
|
|
11317
|
+
const canHandleLocally = !subcommand || subcommand === "list" || subcommand === "templates" || subcommand === "check" || subcommand === "use" || subcommand === "add" && Boolean(tokens[2]) && !tokens[2]?.startsWith("-");
|
|
11318
|
+
if (canHandleLocally) {
|
|
11319
|
+
await handleProvidersCommand(cwd, [
|
|
11320
|
+
"providers",
|
|
11321
|
+
...tokens.slice(1)
|
|
11322
|
+
], runtime);
|
|
11323
|
+
return;
|
|
11324
|
+
}
|
|
11325
|
+
}
|
|
11326
|
+
if (head === "brain" || head === "brains") {
|
|
11327
|
+
const subcommand = tokens[1];
|
|
11328
|
+
const roleArg = tokens[2];
|
|
11329
|
+
const canHandleLocally = !subcommand || subcommand === "list" || isBrainRole(subcommand) || subcommand === "use" && (roleArg ? isBrainRole(roleArg) : false);
|
|
11330
|
+
if (canHandleLocally) {
|
|
11331
|
+
await handleBrainCommand(cwd, [
|
|
11332
|
+
"brain",
|
|
11333
|
+
...tokens.slice(1)
|
|
11334
|
+
], runtime);
|
|
11335
|
+
return;
|
|
11336
|
+
}
|
|
11337
|
+
}
|
|
11338
|
+
if (head === "model") {
|
|
11339
|
+
await printBrainAssignments(cwd);
|
|
11340
|
+
return;
|
|
11341
|
+
}
|
|
11342
|
+
if (head === "models") {
|
|
11343
|
+
await handleModelsCommand(cwd, [
|
|
11344
|
+
"models",
|
|
11345
|
+
...tokens.slice(1)
|
|
11346
|
+
], runtime);
|
|
11347
|
+
return;
|
|
11348
|
+
}
|
|
11349
|
+
if (head === "use-model") {
|
|
11350
|
+
const modelId = tokens.slice(1).join(" ").trim();
|
|
11351
|
+
if (!modelId) {
|
|
11352
|
+
throw new Error("Usage: /use-model <model-id>");
|
|
11353
|
+
}
|
|
11354
|
+
await handleModelSelection(cwd, modelId, runtime);
|
|
11355
|
+
return;
|
|
11356
|
+
}
|
|
11357
|
+
if (head === "select") {
|
|
11358
|
+
await handleSelectCommand(cwd, [
|
|
11359
|
+
"select",
|
|
11360
|
+
...tokens.slice(1)
|
|
11361
|
+
], runtime);
|
|
11362
|
+
return;
|
|
11363
|
+
}
|
|
11364
|
+
const externalTokens = toExternalCommandTokens(trimmed, state);
|
|
11365
|
+
if (externalTokens.length === 0) {
|
|
11366
|
+
return;
|
|
11367
|
+
}
|
|
11368
|
+
await execute(externalTokens);
|
|
11369
|
+
}
|
|
10782
11370
|
async function runInteractiveShell(options) {
|
|
10783
11371
|
const readline = (0, import_promises8.createInterface)({
|
|
10784
11372
|
input: import_node_process10.default.stdin,
|
|
10785
11373
|
output: import_node_process10.default.stdout,
|
|
10786
11374
|
terminal: Boolean(import_node_process10.default.stdin.isTTY && import_node_process10.default.stdout.isTTY)
|
|
10787
11375
|
});
|
|
11376
|
+
const runtime = {
|
|
11377
|
+
focusRole: "coder",
|
|
11378
|
+
lastModels: null
|
|
11379
|
+
};
|
|
10788
11380
|
let closed = false;
|
|
10789
11381
|
readline.on("SIGINT", () => {
|
|
10790
11382
|
closed = true;
|
|
10791
11383
|
readline.close();
|
|
10792
11384
|
});
|
|
10793
|
-
let state = await getShellSessionState(options.cwd);
|
|
11385
|
+
let state = await getShellSessionState(options.cwd, runtime.focusRole);
|
|
10794
11386
|
printHeader(options.cwd, state);
|
|
10795
11387
|
while (!closed) {
|
|
10796
11388
|
let line;
|
|
10797
11389
|
try {
|
|
10798
|
-
state = await getShellSessionState(options.cwd);
|
|
11390
|
+
state = await getShellSessionState(options.cwd, runtime.focusRole);
|
|
10799
11391
|
line = await readline.question(formatPrompt(state));
|
|
10800
11392
|
} catch {
|
|
10801
11393
|
break;
|
|
10802
11394
|
}
|
|
10803
|
-
const trimmed = line.trim();
|
|
10804
|
-
if (!trimmed) {
|
|
10805
|
-
continue;
|
|
10806
|
-
}
|
|
10807
|
-
if (trimmed === "/exit" || trimmed === "exit" || trimmed === "quit" || trimmed === "/quit") {
|
|
10808
|
-
closed = true;
|
|
10809
|
-
break;
|
|
10810
|
-
}
|
|
10811
|
-
if (trimmed === "/help" || trimmed === "help" || trimmed === "?") {
|
|
10812
|
-
console.log(renderHelp());
|
|
10813
|
-
console.log("");
|
|
10814
|
-
continue;
|
|
10815
|
-
}
|
|
10816
|
-
if (trimmed === "/status" || trimmed === "status") {
|
|
10817
|
-
state = await getShellSessionState(options.cwd);
|
|
10818
|
-
console.log(renderStartupCard(options.cwd, state));
|
|
10819
|
-
console.log("");
|
|
10820
|
-
continue;
|
|
10821
|
-
}
|
|
10822
|
-
if (trimmed === "/clear" || trimmed === "clear") {
|
|
10823
|
-
if (import_node_process10.default.stdout.isTTY) {
|
|
10824
|
-
import_node_process10.default.stdout.write("\x1Bc");
|
|
10825
|
-
}
|
|
10826
|
-
state = await getShellSessionState(options.cwd);
|
|
10827
|
-
printHeader(options.cwd, state);
|
|
10828
|
-
continue;
|
|
10829
|
-
}
|
|
10830
11395
|
try {
|
|
10831
|
-
|
|
10832
|
-
if (tokens.length === 0) {
|
|
10833
|
-
continue;
|
|
10834
|
-
}
|
|
10835
|
-
await options.execute(tokens);
|
|
11396
|
+
await handleShellCommand(options.cwd, line, state, runtime, options.execute);
|
|
10836
11397
|
} catch (error) {
|
|
10837
11398
|
const message = error instanceof Error ? error.message : String(error);
|
|
11399
|
+
if (message === "__kimbho_exit__") {
|
|
11400
|
+
closed = true;
|
|
11401
|
+
break;
|
|
11402
|
+
}
|
|
10838
11403
|
console.error(message);
|
|
10839
11404
|
} finally {
|
|
10840
11405
|
import_node_process10.default.exitCode = 0;
|