@botconnector/bccli 0.4.0-beta.35 → 0.4.0-beta.37

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.
@@ -1,20 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- hasKey,
4
- providerName,
5
- removeGlobalProvider,
6
- writeGlobalConfig
7
- } from "./chunk-AOVIQXXO.js";
8
2
  import {
9
3
  reasoningPayload
10
- } from "./chunk-UJH2JT6H.js";
4
+ } from "./chunk-YURAIW7A.js";
11
5
  import {
12
- PRESETS,
13
- loadConfig,
14
- readCredentials,
15
- saveCredential,
16
6
  t
17
- } from "./chunk-IJGTX7RC.js";
7
+ } from "./chunk-4RMP4CNT.js";
18
8
 
19
9
  // src/provider.ts
20
10
  import OpenAI from "openai";
@@ -98,15 +88,15 @@ function detectRepetition(text) {
98
88
  }
99
89
  function sleep(ms, signal) {
100
90
  return new Promise((resolve, reject) => {
101
- const timer = setTimeout(resolve, ms);
102
- signal?.addEventListener(
103
- "abort",
104
- () => {
105
- clearTimeout(timer);
106
- reject(signal.reason);
107
- },
108
- { once: true }
109
- );
91
+ const onAbort = () => {
92
+ clearTimeout(timer);
93
+ reject(signal?.reason);
94
+ };
95
+ const timer = setTimeout(() => {
96
+ signal?.removeEventListener("abort", onAbort);
97
+ resolve();
98
+ }, ms);
99
+ signal?.addEventListener("abort", onAbort, { once: true });
110
100
  });
111
101
  }
112
102
  function errorMessage(body) {
@@ -205,11 +195,38 @@ async function readJson(res) {
205
195
  const thinking = (choice?.message?.reasoning_content || choice?.message?.reasoning || "") + split.thinking;
206
196
  return { text: split.text, toolCalls, thinking: thinking || void 0, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
207
197
  }
198
+ function contentText(content) {
199
+ if (typeof content === "string") return content;
200
+ const text = content.filter((part) => part.type === "text").map((part) => part.text).join(" ").trim();
201
+ const hasImage = content.some((part) => part.type === "image_url");
202
+ if (!hasImage) return text;
203
+ return text ? `${text} [image]` : "[image]";
204
+ }
205
+ function imageUserMessage(images) {
206
+ const paths = images.map((image) => image.path);
207
+ const caption = paths.length === 1 ? `Image read from ${paths[0]}.` : `Images read from ${paths.join(", ")}.`;
208
+ return {
209
+ role: "user",
210
+ content: [
211
+ { type: "text", text: caption },
212
+ ...images.map((image) => ({ type: "image_url", image_url: { url: `data:${image.mediaType};base64,${image.data}` } }))
213
+ ]
214
+ };
215
+ }
208
216
  function responseInputFromMessages(messages) {
209
217
  const input = [];
210
218
  for (const message of messages) {
211
219
  if (message.role === "system" || message.role === "user") {
212
- input.push({ role: message.role, content: message.content });
220
+ if (Array.isArray(message.content)) {
221
+ input.push({
222
+ role: message.role,
223
+ content: message.content.map(
224
+ (part) => part.type === "image_url" ? { type: "input_image", image_url: part.image_url.url } : { type: "input_text", text: part.text }
225
+ )
226
+ });
227
+ } else {
228
+ input.push({ role: message.role, content: message.content });
229
+ }
213
230
  continue;
214
231
  }
215
232
  if (message.role === "assistant") {
@@ -249,7 +266,7 @@ function shouldForceLunaPtc(messages, enabled) {
249
266
  for (let i = messages.length - 1; i >= 0; i--) {
250
267
  const message = messages[i];
251
268
  if (message.role === "assistant" && message.responses_output_items?.some((item) => item.type === "program")) return true;
252
- if (message.role === "user") return LUNA_PTC_FORCE_CUE.test(message.content);
269
+ if (message.role === "user") return LUNA_PTC_FORCE_CUE.test(contentText(message.content));
253
270
  }
254
271
  return false;
255
272
  }
@@ -264,7 +281,7 @@ function shouldEnableLunaPtc(messages, enabled, programmaticToolNames = []) {
264
281
  }
265
282
  const turn = messages.slice(Math.max(0, lastUser));
266
283
  const latestUser = lastUser >= 0 ? messages[lastUser] : void 0;
267
- const prompt = latestUser?.role === "user" ? latestUser.content : "";
284
+ const prompt = latestUser?.role === "user" ? contentText(latestUser.content) : "";
268
285
  if (LUNA_PTC_MULTI_WORK_CUE.test(prompt)) return true;
269
286
  const safeToolNames = /* @__PURE__ */ new Set([...LUNA_PTC_SAFE_FUNCTIONS, ...programmaticToolNames]);
270
287
  const safeCalls = /* @__PURE__ */ new Set();
@@ -857,101 +874,10 @@ function createProvider(options) {
857
874
  };
858
875
  }
859
876
 
860
- // src/models.ts
861
- function order(ids) {
862
- const presetIds = PRESETS.map((p) => p.id);
863
- const presets = presetIds.filter((id) => ids.includes(id));
864
- const custom = ids.filter((id) => !presetIds.includes(id)).sort();
865
- return [...presets, ...custom];
866
- }
867
- async function listAllModels(config, env = process.env, opts = {}) {
868
- const timeoutMs = opts.timeoutMs ?? 5e3;
869
- const baseFetch = opts.fetch ?? fetch;
870
- const untrusted = new Set(config.projectProviders ?? []);
871
- const ids = order(Object.keys(config.providers)).filter((id) => opts.only ? id === opts.only : !untrusted.has(id) && hasKey(config, id, env));
872
- const results = await Promise.all(
873
- ids.map(async (id) => {
874
- const provider = config.providers[id];
875
- const apiKey = (opts.keyOverride && id === opts.only ? opts.keyOverride : void 0) || (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || readCredentials(env)[id] || void 0;
876
- const local = !provider.apiKeyEnv;
877
- const timedFetch = ((input, init) => baseFetch(input, { ...init, signal: AbortSignal.timeout(timeoutMs) }));
878
- const list = createProvider({ baseURL: provider.baseURL.replace(/\/+$/, ""), apiKey, model: "", fetch: timedFetch }).listModels();
879
- let timer;
880
- const timeout = new Promise((_, reject) => {
881
- timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
882
- });
883
- try {
884
- const models = await Promise.race([list, timeout]);
885
- return { providerId: id, providerName: providerName(config, id), models };
886
- } catch {
887
- if (local && !opts.only) return void 0;
888
- return { providerId: id, providerName: providerName(config, id), models: [], error: t("could not be reached") };
889
- } finally {
890
- clearTimeout(timer);
891
- }
892
- })
893
- );
894
- return results.filter((g) => !!g);
895
- }
896
-
897
- // src/providerCli.ts
898
- async function addProviderKey(providerId, key, deps) {
899
- const config = loadConfig(deps.cwd, deps.env);
900
- const [group] = await listAllModels(config, deps.env, { only: providerId, fetch: deps.fetch, keyOverride: key || void 0 });
901
- if (!group || group.error) return { ok: false, error: t("The API key was rejected or {provider} could not be reached.", { provider: providerName(config, providerId) }) };
902
- if (key) saveCredential(providerId, key, deps.env);
903
- return { ok: true, models: group.models.length };
904
- }
905
- async function runProviderCommand(args, deps) {
906
- const [action = "list", id] = args.subArgs;
907
- const config = loadConfig(deps.cwd, deps.env);
908
- if (action === "list") {
909
- for (const pid of Object.keys(config.providers)) {
910
- const mark = hasKey(config, pid, deps.env) ? "\u2713" : "\u25CB";
911
- deps.out(`${mark} ${pid.padEnd(14)} ${providerName(config, pid).padEnd(20)} ${config.providers[pid].baseURL}`);
912
- }
913
- return 0;
914
- }
915
- if (!id) {
916
- deps.err(t("Usage: bccli provider {action} <id>", { action }));
917
- return 1;
918
- }
919
- if (action === "remove") {
920
- removeGlobalProvider(id, deps.env);
921
- deps.out(t("{id} removed.", { id }));
922
- return 0;
923
- }
924
- if (action !== "add") {
925
- deps.err(t("Unknown action: {action}. Use list, add, or remove.", { action }));
926
- return 1;
927
- }
928
- const preset = PRESETS.find((p) => p.id === id);
929
- if (!preset && !config.providers[id]) {
930
- if (!args.url) {
931
- deps.err(t("{id} is not a preset. For a custom provider add --url <OpenAI-compatible base URL>.", { id }));
932
- return 1;
933
- }
934
- writeGlobalConfig(
935
- { providers: { [id]: { baseURL: args.url, ...args.name ? { name: args.name } : {}, ...args.keyEnv ? { apiKeyEnv: args.keyEnv } : {} } } },
936
- deps.env
937
- );
938
- }
939
- const needsKey = !!loadConfig(deps.cwd, deps.env).providers[id].apiKeyEnv || !preset;
940
- const key = needsKey ? await deps.readSecret(t("API key for {id} (leave empty if not needed): ", { id })) : "";
941
- const result = await addProviderKey(id, key, deps);
942
- if (!result.ok) {
943
- deps.err(result.error);
944
- return 1;
945
- }
946
- deps.out(t("{id} is ready. {n} models available.", { id, n: result.models }));
947
- return 0;
948
- }
949
-
950
877
  export {
878
+ contentText,
879
+ imageUserMessage,
951
880
  hasPendingProgrammaticReplay,
952
881
  completionAssistantMessage,
953
- createProvider,
954
- listAllModels,
955
- addProviderKey,
956
- runProviderCommand
882
+ createProvider
957
883
  };
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  MODE_ORDER
4
- } from "./chunk-ELML4KVQ.js";
4
+ } from "./chunk-R3ND76LW.js";
5
5
  import {
6
6
  REASONING_LEVELS,
7
7
  isReasoningLevel
8
- } from "./chunk-UJH2JT6H.js";
8
+ } from "./chunk-YURAIW7A.js";
9
+ import {
10
+ ConfigError
11
+ } from "./chunk-H5N4ST6B.js";
9
12
  import {
10
- ConfigError,
11
13
  parseLang,
12
14
  t
13
- } from "./chunk-IJGTX7RC.js";
15
+ } from "./chunk-4RMP4CNT.js";
14
16
 
15
17
  // src/args.ts
16
18
  import { parseArgs } from "util";
@@ -22,9 +24,9 @@ Usage:
22
24
  bccli login [provider] save an API key (default: bc-cloud)
23
25
  bccli models list models from the active provider
24
26
  bccli provider list|add <id>|remove <id> manage providers (custom: --url <url> [--name N] [--key-env ENV])
25
- bccli mcp list|add <name>|remove <name> manage MCP servers (catalog, or --url <url>)
27
+ bccli mcp list|add <name>|remove <name>|auth <name>|logout <name> manage MCP servers (catalog, --url <url>, OAuth)
26
28
  bccli integrations show the status of external agent integrations
27
- bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible
29
+ bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible, openclaw, hermes
28
30
  bccli disconnect <agent> remove an integration and restore the previous config
29
31
 
30
32
  Options:
@@ -34,6 +36,8 @@ Options:
34
36
  -r, --resume pick a session to continue
35
37
  --allow-all run all tools without asking for permission
36
38
  --allowed-tools <a,b> tools allowed without asking: bash, edit, fetch
39
+ -w, --worktree <name> run in git worktree ../<repo>.worktrees/<name> (created, or reused if it exists)
40
+ --output-format <fmt> text | json | stream-json for -p (machine-readable output)
37
41
  --permission-mode <mode> default | acceptEdits | plan | allowAll
38
42
  --lang <en|id> interface language (default: en; or BCCLI_LANG, or "language" in config)
39
43
  -v, --version version
@@ -52,6 +56,8 @@ function parseCliArgs(argv) {
52
56
  resume: { type: "boolean", short: "r" },
53
57
  "allow-all": { type: "boolean" },
54
58
  "allowed-tools": { type: "string" },
59
+ worktree: { type: "string", short: "w" },
60
+ "output-format": { type: "string" },
55
61
  "permission-mode": { type: "string" },
56
62
  help: { type: "boolean", short: "h" },
57
63
  version: { type: "boolean", short: "v" },
@@ -70,6 +76,10 @@ function parseCliArgs(argv) {
70
76
  throw new ConfigError(t("--permission-mode must be one of: {modes}", { modes: MODE_ORDER.join(", ") }));
71
77
  }
72
78
  if (values.lang !== void 0 && !parseLang(values.lang)) throw new ConfigError(t("--lang must be one of: en, id"));
79
+ const outputFormat = values["output-format"];
80
+ if (outputFormat !== void 0 && !["text", "json", "stream-json"].includes(outputFormat)) {
81
+ throw new ConfigError(t("--output-format must be one of: {formats}", { formats: "text, json, stream-json" }));
82
+ }
73
83
  const [first, ...rest] = positionals;
74
84
  const SUBCOMMANDS = ["login", "models", "provider", "mcp", "connect", "disconnect", "integrations"];
75
85
  const command = SUBCOMMANDS.includes(first) ? first : "run";
@@ -85,6 +95,8 @@ function parseCliArgs(argv) {
85
95
  resume: !!values.resume,
86
96
  allowAll: !!values["allow-all"],
87
97
  allowedTools: (values["allowed-tools"] ?? "").split(",").map((s) => s.trim()).filter(Boolean),
98
+ worktree: values.worktree,
99
+ outputFormat,
88
100
  permissionMode: mode,
89
101
  help: !!values.help,
90
102
  version: !!values.version,
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ hasKey,
4
+ providerName,
5
+ removeGlobalProvider,
6
+ writeGlobalConfig
7
+ } from "./chunk-U66JJ6RN.js";
8
+ import {
9
+ createProvider
10
+ } from "./chunk-7TABTC32.js";
11
+ import {
12
+ PRESETS,
13
+ loadConfig,
14
+ readCredentials,
15
+ saveCredential
16
+ } from "./chunk-H5N4ST6B.js";
17
+ import {
18
+ t
19
+ } from "./chunk-4RMP4CNT.js";
20
+
21
+ // src/models.ts
22
+ function order(ids) {
23
+ const presetIds = PRESETS.map((p) => p.id);
24
+ const presets = presetIds.filter((id) => ids.includes(id));
25
+ const custom = ids.filter((id) => !presetIds.includes(id)).sort();
26
+ return [...presets, ...custom];
27
+ }
28
+ async function listAllModels(config, env = process.env, opts = {}) {
29
+ const timeoutMs = opts.timeoutMs ?? 5e3;
30
+ const baseFetch = opts.fetch ?? fetch;
31
+ const untrusted = new Set(config.projectProviders ?? []);
32
+ const ids = order(Object.keys(config.providers)).filter((id) => opts.only ? id === opts.only : !untrusted.has(id) && hasKey(config, id, env));
33
+ const results = await Promise.all(
34
+ ids.map(async (id) => {
35
+ const provider = config.providers[id];
36
+ const apiKey = (opts.keyOverride && id === opts.only ? opts.keyOverride : void 0) || (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || readCredentials(env)[id] || void 0;
37
+ const local = !provider.apiKeyEnv;
38
+ const timedFetch = ((input, init) => baseFetch(input, { ...init, signal: AbortSignal.timeout(timeoutMs) }));
39
+ const list = createProvider({ baseURL: provider.baseURL.replace(/\/+$/, ""), apiKey, model: "", fetch: timedFetch }).listModels();
40
+ let timer;
41
+ const timeout = new Promise((_, reject) => {
42
+ timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
43
+ });
44
+ try {
45
+ const models = await Promise.race([list, timeout]);
46
+ return { providerId: id, providerName: providerName(config, id), models };
47
+ } catch {
48
+ if (local && !opts.only) return void 0;
49
+ return { providerId: id, providerName: providerName(config, id), models: [], error: t("could not be reached") };
50
+ } finally {
51
+ clearTimeout(timer);
52
+ }
53
+ })
54
+ );
55
+ return results.filter((g) => !!g);
56
+ }
57
+
58
+ // src/providerCli.ts
59
+ async function addProviderKey(providerId, key, deps) {
60
+ const config = loadConfig(deps.cwd, deps.env);
61
+ const [group] = await listAllModels(config, deps.env, { only: providerId, fetch: deps.fetch, keyOverride: key || void 0 });
62
+ if (!group || group.error) return { ok: false, error: t("The API key was rejected or {provider} could not be reached.", { provider: providerName(config, providerId) }) };
63
+ if (key) saveCredential(providerId, key, deps.env);
64
+ return { ok: true, models: group.models.length };
65
+ }
66
+ async function runProviderCommand(args, deps) {
67
+ const [action = "list", id] = args.subArgs;
68
+ const config = loadConfig(deps.cwd, deps.env);
69
+ if (action === "list") {
70
+ for (const pid of Object.keys(config.providers)) {
71
+ const mark = hasKey(config, pid, deps.env) ? "\u2713" : "\u25CB";
72
+ deps.out(`${mark} ${pid.padEnd(14)} ${providerName(config, pid).padEnd(20)} ${config.providers[pid].baseURL}`);
73
+ }
74
+ return 0;
75
+ }
76
+ if (!id) {
77
+ deps.err(t("Usage: bccli provider {action} <id>", { action }));
78
+ return 1;
79
+ }
80
+ if (action === "remove") {
81
+ removeGlobalProvider(id, deps.env);
82
+ deps.out(t("{id} removed.", { id }));
83
+ return 0;
84
+ }
85
+ if (action !== "add") {
86
+ deps.err(t("Unknown action: {action}. Use list, add, or remove.", { action }));
87
+ return 1;
88
+ }
89
+ const preset = PRESETS.find((p) => p.id === id);
90
+ if (!preset && !config.providers[id]) {
91
+ if (!args.url) {
92
+ deps.err(t("{id} is not a preset. For a custom provider add --url <OpenAI-compatible base URL>.", { id }));
93
+ return 1;
94
+ }
95
+ writeGlobalConfig(
96
+ { providers: { [id]: { baseURL: args.url, ...args.name ? { name: args.name } : {}, ...args.keyEnv ? { apiKeyEnv: args.keyEnv } : {} } } },
97
+ deps.env
98
+ );
99
+ }
100
+ const needsKey = !!loadConfig(deps.cwd, deps.env).providers[id].apiKeyEnv || !preset;
101
+ const key = needsKey ? await deps.readSecret(t("API key for {id} (leave empty if not needed): ", { id })) : "";
102
+ const result = await addProviderKey(id, key, deps);
103
+ if (!result.ok) {
104
+ deps.err(result.error);
105
+ return 1;
106
+ }
107
+ deps.out(t("{id} is ready. {n} models available.", { id, n: result.models }));
108
+ return 0;
109
+ }
110
+
111
+ export {
112
+ listAllModels,
113
+ addProviderKey,
114
+ runProviderCommand
115
+ };
@@ -35,8 +35,8 @@ function readSecret(prompt) {
35
35
  });
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-4JTIW3YA.js");
39
- const { parseCliArgs } = await import("./args-EFFR44JW.js");
38
+ const { runProviderCommand } = await import("./providerCli-Z3TZ5XSZ.js");
39
+ const { parseCliArgs } = await import("./args-MNVI2OJI.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
42
42
  cwd,
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ t
4
+ } from "./chunk-4RMP4CNT.js";
5
+
6
+ // src/presets.ts
7
+ var PRESETS = [
8
+ { id: "bc-cloud", name: "BotConnector Cloud", baseURL: "https://api.botconnector.id/v1", apiKeyEnv: "BOTCONNECTOR_API_KEY" },
9
+ { id: "openrouter", name: "OpenRouter", baseURL: "https://openrouter.ai/api/v1", apiKeyEnv: "OPENROUTER_API_KEY" },
10
+ { id: "openai", name: "OpenAI", baseURL: "https://api.openai.com/v1", apiKeyEnv: "OPENAI_API_KEY" },
11
+ { id: "gemini", name: "Google Gemini", baseURL: "https://generativelanguage.googleapis.com/v1beta/openai", apiKeyEnv: "GEMINI_API_KEY" },
12
+ { id: "deepseek", name: "DeepSeek", baseURL: "https://api.deepseek.com/v1", apiKeyEnv: "DEEPSEEK_API_KEY" },
13
+ { id: "groq", name: "Groq", baseURL: "https://api.groq.com/openai/v1", apiKeyEnv: "GROQ_API_KEY" },
14
+ { id: "ollama-cloud", name: "Ollama Cloud", baseURL: "https://ollama.com/v1", apiKeyEnv: "OLLAMA_API_KEY" },
15
+ { id: "ollama", name: "Ollama lokal", baseURL: "http://127.0.0.1:11434/v1", local: true },
16
+ { id: "lmstudio", name: "LM Studio lokal", baseURL: "http://127.0.0.1:1234/v1", local: true }
17
+ ];
18
+
19
+ // src/config.ts
20
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
21
+ import { homedir } from "os";
22
+ import { join } from "path";
23
+ var ConfigError = class extends Error {
24
+ };
25
+ var DEFAULT_MAX_STEPS = 100;
26
+ var DEFAULT_CONFIG = {
27
+ model: "bc-cloud/glm-5.3-flash",
28
+ permissionMode: "default",
29
+ reasoning: "auto",
30
+ vision: true,
31
+ networkPolicy: "allow",
32
+ providers: Object.fromEntries(PRESETS.map((p) => [p.id, { baseURL: p.baseURL, apiKeyEnv: p.apiKeyEnv }])),
33
+ allow: []
34
+ };
35
+ function bccliHome(env = process.env) {
36
+ return env.BCCLI_HOME ?? join(homedir(), ".bccli");
37
+ }
38
+ function readJsonConfig(path) {
39
+ if (!existsSync(path)) return {};
40
+ try {
41
+ return JSON.parse(readFileSync(path, "utf8"));
42
+ } catch (error) {
43
+ throw new ConfigError(t("Invalid config: {path} ({error})", { path, error: error.message }));
44
+ }
45
+ }
46
+ function resolvedMaxSteps(global) {
47
+ const value = global.maxSteps;
48
+ if (value === void 0) return DEFAULT_MAX_STEPS;
49
+ if (value === null || value === 0) return null;
50
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
51
+ throw new ConfigError(t("maxSteps must be a positive number, or 0/null to disable the limit."));
52
+ }
53
+ return Math.max(1, Math.floor(value));
54
+ }
55
+ function resolvedNetworkPolicy(global) {
56
+ const value = global.networkPolicy ?? "allow";
57
+ if (value !== "allow" && value !== "offline") {
58
+ throw new ConfigError(t('networkPolicy must be "allow" or "offline".'));
59
+ }
60
+ return value;
61
+ }
62
+ function resolvedPlugins(global) {
63
+ const value = global.plugins;
64
+ if (value === void 0) return [];
65
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
66
+ throw new ConfigError(t("plugins must be an array of strings."));
67
+ }
68
+ return [...value];
69
+ }
70
+ function untrustedProviders(project, known) {
71
+ const out = {};
72
+ for (const [id, provider] of Object.entries(project.providers ?? {})) {
73
+ if (known[id] || typeof provider?.baseURL !== "string") continue;
74
+ out[id] = { baseURL: provider.baseURL };
75
+ }
76
+ return out;
77
+ }
78
+ function loadConfig(cwd, env = process.env) {
79
+ const global = readJsonConfig(join(bccliHome(env), "config.json"));
80
+ const project = readJsonConfig(join(cwd, ".bccli", "config.json"));
81
+ const known = { ...DEFAULT_CONFIG.providers, ...global.providers };
82
+ const fromProject = untrustedProviders(project, known);
83
+ return {
84
+ model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
85
+ permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
86
+ reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
87
+ maxSteps: resolvedMaxSteps(global),
88
+ hooks: global.hooks,
89
+ vision: global.vision ?? DEFAULT_CONFIG.vision,
90
+ networkPolicy: resolvedNetworkPolicy(global),
91
+ plugins: resolvedPlugins(global),
92
+ providers: { ...known, ...fromProject },
93
+ projectProviders: Object.keys(fromProject),
94
+ allow: [...global.allow ?? []]
95
+ };
96
+ }
97
+ function readCredentials(env = process.env) {
98
+ const path = join(bccliHome(env), "credentials");
99
+ if (!existsSync(path)) return {};
100
+ try {
101
+ return JSON.parse(readFileSync(path, "utf8"));
102
+ } catch {
103
+ return {};
104
+ }
105
+ }
106
+ function saveCredential(providerId, key, env = process.env) {
107
+ const home = bccliHome(env);
108
+ mkdirSync(home, { recursive: true });
109
+ const path = join(home, "credentials");
110
+ writeFileSync(path, JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), { mode: 384 });
111
+ chmodSync(path, 384);
112
+ }
113
+ function resolveModel(config, modelRef, env = process.env) {
114
+ const slash = modelRef.indexOf("/");
115
+ if (slash <= 0 || slash === modelRef.length - 1) {
116
+ throw new ConfigError(t("Model must be in provider/model format, e.g. bc-cloud/glm-5.3-flash (got: {ref})", { ref: modelRef }));
117
+ }
118
+ const providerId = modelRef.slice(0, slash);
119
+ const provider = config.providers[providerId];
120
+ if (!provider) {
121
+ throw new ConfigError(t('Provider "{id}" is not in the config. Available: {list}', { id: providerId, list: Object.keys(config.providers).join(", ") }));
122
+ }
123
+ const apiKey = (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || readCredentials(env)[providerId] || void 0;
124
+ if (provider.apiKeyEnv && !apiKey) {
125
+ throw new ConfigError(t("No API key for {id} yet. Run `bccli login` or set the env var {env}.", { id: providerId, env: provider.apiKeyEnv }));
126
+ }
127
+ return { providerId, model: modelRef.slice(slash + 1), baseURL: provider.baseURL.replace(/\/+$/, ""), apiKey };
128
+ }
129
+ function removeCredential(providerId, env = process.env) {
130
+ const creds = readCredentials(env);
131
+ if (!(providerId in creds)) return false;
132
+ delete creds[providerId];
133
+ const path = join(bccliHome(env), "credentials");
134
+ writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 384 });
135
+ chmodSync(path, 384);
136
+ return true;
137
+ }
138
+
139
+ export {
140
+ PRESETS,
141
+ ConfigError,
142
+ bccliHome,
143
+ readJsonConfig,
144
+ loadConfig,
145
+ readCredentials,
146
+ saveCredential,
147
+ resolveModel,
148
+ removeCredential
149
+ };
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ConfigError,
3
+ ConfigError
4
+ } from "./chunk-H5N4ST6B.js";
5
+ import {
4
6
  t
5
- } from "./chunk-IJGTX7RC.js";
7
+ } from "./chunk-4RMP4CNT.js";
6
8
 
7
9
  // src/mcp/catalog.ts
8
10
  var CATALOG = [
@@ -12,6 +14,7 @@ var CATALOG = [
12
14
  {
13
15
  name: "filesystem",
14
16
  description: "access folders outside the project",
17
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: ${dir} is filled by fillTemplate, not a template literal
15
18
  config: { command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "${dir}"] },
16
19
  inputs: [{ key: "dir", label: "Folder the server may access" }]
17
20
  },
@@ -19,6 +22,7 @@ var CATALOG = [
19
22
  {
20
23
  name: "github",
21
24
  description: "GitHub issues, PRs, and repos",
25
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: ${token} is filled by fillTemplate, not a template literal
22
26
  config: { type: "http", url: "https://api.githubcopilot.com/mcp/", headers: { Authorization: "Bearer ${token}" } },
23
27
  inputs: [{ key: "token", label: "GitHub Personal Access Token", secret: true }]
24
28
  }