@botconnector/bccli 0.4.0-beta.4 → 0.4.0-beta.40

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.
Files changed (46) hide show
  1. package/README.md +80 -1
  2. package/dist/acp-5A7EXLCG.js +114 -0
  3. package/dist/args-4SMOHFN5.js +13 -0
  4. package/dist/chunk-3HCOAJEQ.js +190 -0
  5. package/dist/chunk-AS7VV7D3.js +195 -0
  6. package/dist/{chunk-NGLKANMD.js → chunk-AVB6M4JE.js} +24 -2
  7. package/dist/{chunk-GW36UMHZ.js → chunk-DSFHN5CH.js} +2 -2
  8. package/dist/{chunk-ELML4KVQ.js → chunk-GAFJEQTJ.js} +32 -2
  9. package/dist/chunk-GGNXHJL6.js +884 -0
  10. package/dist/chunk-HE33XAPZ.js +341 -0
  11. package/dist/{chunk-UESQNU4A.js → chunk-ISCT3PN3.js} +14 -9
  12. package/dist/chunk-JROUQ5YS.js +76 -0
  13. package/dist/chunk-LN32JIZR.js +56 -0
  14. package/dist/chunk-M23QBX3V.js +419 -0
  15. package/dist/chunk-M7LLJQ7M.js +310 -0
  16. package/dist/chunk-NI5JZUZ3.js +63 -0
  17. package/dist/chunk-PDVS4UWM.js +8 -0
  18. package/dist/chunk-PSO7R5AD.js +10 -0
  19. package/dist/chunk-RV74TCEP.js +231 -0
  20. package/dist/{chunk-U7DLMVPZ.js → chunk-SESBH44L.js} +2 -1
  21. package/dist/{chunk-SJH3YLW4.js → chunk-VM7HYAJS.js} +4 -1
  22. package/dist/chunk-WH7XLDGZ.js +2125 -0
  23. package/dist/chunk-ZMU7MXCH.js +674 -0
  24. package/dist/cli.js +94 -1278
  25. package/dist/{config-NPSYFAFD.js → config-C4KLLDC6.js} +5 -2
  26. package/dist/integrationCli-HR7CDUSI.js +685 -0
  27. package/dist/{login-F4G5QXOU.js → login-T5S6SPZA.js} +1 -1
  28. package/dist/{mcpCli-Q2H63ISP.js → mcpCli-5QTXH7XJ.js} +42 -9
  29. package/dist/modelsCli-DKTT3UZD.js +36 -0
  30. package/dist/providerCli-DDH4ZZUY.js +15 -0
  31. package/dist/sdk.js +5130 -0
  32. package/dist/serve-I2VQUTHR.js +178 -0
  33. package/dist/serve-JNQUP7CZ.js +514 -0
  34. package/dist/session-H2YL5RSB.js +12 -0
  35. package/dist/setup-I7UY7X7C.js +23 -0
  36. package/dist/ui-GVIMUZNS.js +2280 -0
  37. package/dist/update-SZUG3YSB.js +109 -0
  38. package/package.json +24 -3
  39. package/dist/args-FCLPQ7TO.js +0 -11
  40. package/dist/chunk-C5AFWX7H.js +0 -328
  41. package/dist/chunk-OSWQPSA4.js +0 -252
  42. package/dist/chunk-RZUJYAQV.js +0 -112
  43. package/dist/chunk-UN7QXPCB.js +0 -82
  44. package/dist/providerCli-MXV6PD6F.js +0 -11
  45. package/dist/session-7UB77CEG.js +0 -9
  46. package/dist/ui-7FXPXXGN.js +0 -1333
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ t
4
+ } from "./chunk-M23QBX3V.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/ui/theme.ts
20
+ var THEMES = ["default", "blue", "amber", "magenta", "mono"];
21
+ var REMAP = {
22
+ blue: { green: "blue" },
23
+ amber: { green: "yellow" },
24
+ magenta: { green: "magenta" }
25
+ };
26
+ var theme = "default";
27
+ function setTheme(name) {
28
+ theme = name;
29
+ }
30
+ function color(name) {
31
+ if (process.env.NO_COLOR) return void 0;
32
+ if (theme === "mono") return void 0;
33
+ return REMAP[theme]?.[name] ?? name;
34
+ }
35
+ var ACCENT = "green";
36
+
37
+ // src/config.ts
38
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
39
+ import { homedir } from "os";
40
+ import { join } from "path";
41
+
42
+ // src/keybinds.ts
43
+ var KEYBIND_ACTIONS = ["thinking", "toolOutput", "pasteImage"];
44
+ var DEFAULT_KEYBINDS = {
45
+ thinking: "ctrl+t",
46
+ toolOutput: "ctrl+o",
47
+ pasteImage: "alt+v"
48
+ };
49
+ function defaultKeybinds() {
50
+ return Object.fromEntries(KEYBIND_ACTIONS.map((action) => [action, parseKeybind(DEFAULT_KEYBINDS[action])]));
51
+ }
52
+ function parseKeybind(raw) {
53
+ const spec = raw.trim().toLowerCase();
54
+ const parts = spec.split("+");
55
+ const key = parts.pop() ?? "";
56
+ if (!/^[a-z]$/.test(key)) return void 0;
57
+ if (!parts.length || new Set(parts).size !== parts.length) return void 0;
58
+ if (parts.some((p) => p !== "ctrl" && p !== "alt")) return void 0;
59
+ return { spec, ctrl: parts.includes("ctrl"), meta: parts.includes("alt"), key };
60
+ }
61
+ function matchKeybind(kb, input, key) {
62
+ return kb.ctrl === !!key.ctrl && kb.meta === !!key.meta && input.toLowerCase() === kb.key;
63
+ }
64
+
65
+ // src/config.ts
66
+ var ConfigError = class extends Error {
67
+ };
68
+ var DEFAULT_MAX_STEPS = 100;
69
+ var DEFAULT_CONFIG = {
70
+ model: "bc-cloud/glm-5.3-flash",
71
+ permissionMode: "default",
72
+ reasoning: "auto",
73
+ vision: true,
74
+ networkPolicy: "allow",
75
+ keybinds: defaultKeybinds(),
76
+ verifyCommands: [],
77
+ repoMap: false,
78
+ theme: "default",
79
+ editor: "emacs",
80
+ sandbox: "off",
81
+ providers: Object.fromEntries(PRESETS.map((p) => [p.id, { baseURL: p.baseURL, apiKeyEnv: p.apiKeyEnv }])),
82
+ allow: []
83
+ };
84
+ function bccliHome(env = process.env) {
85
+ return env.BCCLI_HOME ?? join(homedir(), ".bccli");
86
+ }
87
+ function readJsonConfig(path) {
88
+ if (!existsSync(path)) return {};
89
+ try {
90
+ return JSON.parse(readFileSync(path, "utf8"));
91
+ } catch (error) {
92
+ throw new ConfigError(t("Invalid config: {path} ({error})", { path, error: error.message }));
93
+ }
94
+ }
95
+ function resolvedMaxSteps(global) {
96
+ const value = global.maxSteps;
97
+ if (value === void 0) return DEFAULT_MAX_STEPS;
98
+ if (value === null || value === 0) return null;
99
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
100
+ throw new ConfigError(t("maxSteps must be a positive number, or 0/null to disable the limit."));
101
+ }
102
+ return Math.max(1, Math.floor(value));
103
+ }
104
+ function resolvedNetworkPolicy(global) {
105
+ const value = global.networkPolicy ?? "allow";
106
+ if (value !== "allow" && value !== "offline") {
107
+ throw new ConfigError(t('networkPolicy must be "allow" or "offline".'));
108
+ }
109
+ return value;
110
+ }
111
+ function resolvedPlugins(global) {
112
+ const value = global.plugins;
113
+ if (value === void 0) return [];
114
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
115
+ throw new ConfigError(t("plugins must be an array of strings."));
116
+ }
117
+ return [...value];
118
+ }
119
+ function resolvedUpdateCheck(global) {
120
+ const value = global.updateCheck;
121
+ if (value === void 0) return void 0;
122
+ if (value !== "on" && value !== "off") throw new ConfigError(t('updateCheck must be "on" or "off".'));
123
+ return value;
124
+ }
125
+ function resolvedUsageCap(global) {
126
+ const cap = global.usageCap;
127
+ if (cap === void 0) return void 0;
128
+ if (typeof cap !== "object" || cap === null || Array.isArray(cap)) {
129
+ throw new ConfigError(t('usageCap must be an object, e.g. { "tokens": 2000000 }.'));
130
+ }
131
+ const { tokens, usd, prices } = cap;
132
+ const positive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
133
+ const nonNegative = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
134
+ if (tokens !== void 0 && !positive(tokens)) throw new ConfigError(t("usageCap.tokens must be a number greater than 0."));
135
+ if (usd !== void 0 && !positive(usd)) throw new ConfigError(t("usageCap.usd must be a number greater than 0."));
136
+ if (usd !== void 0 && (prices === void 0 || !Object.keys(prices).length)) {
137
+ throw new ConfigError(t("usageCap.usd requires usageCap.prices (USD per 1M tokens)."));
138
+ }
139
+ if (prices !== void 0) {
140
+ for (const [key, price] of Object.entries(prices)) {
141
+ if (!price || !nonNegative(price.input) || !nonNegative(price.output)) {
142
+ throw new ConfigError(t('usageCap.prices["{key}"] must have numeric input and output (USD per 1M tokens).', { key }));
143
+ }
144
+ }
145
+ }
146
+ if (tokens === void 0 && usd === void 0) throw new ConfigError(t("usageCap needs at least one of: tokens, usd."));
147
+ return { ...tokens !== void 0 ? { tokens } : {}, ...usd !== void 0 ? { usd } : {}, ...prices !== void 0 ? { prices } : {} };
148
+ }
149
+ function resolvedLsp(global) {
150
+ if (global.lsp === void 0) return void 0;
151
+ const lsp = global.lsp;
152
+ if (typeof global.lsp !== "object" || global.lsp === null || !Array.isArray(lsp.servers)) {
153
+ throw new ConfigError(t('lsp must be an object with a "servers" array.'));
154
+ }
155
+ for (const s of lsp.servers) {
156
+ const server = s;
157
+ if (typeof server?.command !== "string" || !server.command || !Array.isArray(server.extensions) || server.extensions.length === 0 || server.extensions.some((e) => typeof e !== "string" || !e.startsWith(".")) || server.args !== void 0 && (!Array.isArray(server.args) || server.args.some((a) => typeof a !== "string"))) {
158
+ throw new ConfigError(t('Each lsp server needs { "extensions": [".ts"], "command": "...", "args": ["..."] }.'));
159
+ }
160
+ }
161
+ return { servers: lsp.servers };
162
+ }
163
+ function resolvedSandbox(global) {
164
+ const value = global.sandbox ?? "off";
165
+ if (value !== "off" && value !== "on") throw new ConfigError(t('sandbox must be "off" or "on".'));
166
+ return value;
167
+ }
168
+ function resolvedEditor(global) {
169
+ const value = global.editor ?? "emacs";
170
+ if (value !== "emacs" && value !== "vim") throw new ConfigError(t('editor must be "emacs" or "vim".'));
171
+ return value;
172
+ }
173
+ function resolvedTheme(global) {
174
+ const value = global.theme ?? "default";
175
+ if (typeof value !== "string" || !THEMES.includes(value)) {
176
+ throw new ConfigError(t("theme must be one of: {themes}.", { themes: THEMES.join(", ") }));
177
+ }
178
+ return value;
179
+ }
180
+ function resolvedRepoMap(global) {
181
+ const value = global.repoMap ?? false;
182
+ if (typeof value !== "boolean") throw new ConfigError(t("repoMap must be true or false."));
183
+ return value;
184
+ }
185
+ function resolvedVerifyCommands(global) {
186
+ const value = global.verifyCommands;
187
+ if (value === void 0) return [];
188
+ if (!Array.isArray(value) || value.some((c) => typeof c !== "string" || !c.trim())) {
189
+ throw new ConfigError(t("verifyCommands must be an array of non-empty strings."));
190
+ }
191
+ return value.map((c) => c.trim());
192
+ }
193
+ function resolvedKeybinds(global) {
194
+ const raw = global.keybinds ?? {};
195
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
196
+ throw new ConfigError(t("keybinds must be an object mapping action to binding."));
197
+ }
198
+ const out = {};
199
+ for (const action of KEYBIND_ACTIONS) {
200
+ const spec = raw[action];
201
+ const value = spec === void 0 ? DEFAULT_KEYBINDS[action] : spec;
202
+ if (typeof value !== "string") {
203
+ throw new ConfigError(t('Keybind for "{action}" must be a string like "ctrl+t".', { action }));
204
+ }
205
+ const parsed = parseKeybind(value);
206
+ if (!parsed) {
207
+ throw new ConfigError(t('Keybind "{spec}" for "{action}" is invalid: use ctrl or alt plus one letter, e.g. "ctrl+t".', { spec: value, action }));
208
+ }
209
+ out[action] = parsed;
210
+ }
211
+ for (const key of Object.keys(raw)) {
212
+ if (!KEYBIND_ACTIONS.includes(key)) {
213
+ throw new ConfigError(t('Unknown keybind action "{action}". Known actions: {list}.', { action: key, list: KEYBIND_ACTIONS.join(", ") }));
214
+ }
215
+ }
216
+ return out;
217
+ }
218
+ function untrustedProviders(project, known) {
219
+ const out = {};
220
+ for (const [id, provider] of Object.entries(project.providers ?? {})) {
221
+ if (known[id] || typeof provider?.baseURL !== "string") continue;
222
+ out[id] = { baseURL: provider.baseURL };
223
+ }
224
+ return out;
225
+ }
226
+ function loadConfig(cwd, env = process.env) {
227
+ const global = readJsonConfig(join(bccliHome(env), "config.json"));
228
+ const project = readJsonConfig(join(cwd, ".bccli", "config.json"));
229
+ const known = { ...DEFAULT_CONFIG.providers, ...global.providers };
230
+ const fromProject = untrustedProviders(project, known);
231
+ return {
232
+ model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
233
+ permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
234
+ reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
235
+ maxSteps: resolvedMaxSteps(global),
236
+ hooks: global.hooks,
237
+ vision: global.vision ?? DEFAULT_CONFIG.vision,
238
+ networkPolicy: resolvedNetworkPolicy(global),
239
+ plugins: resolvedPlugins(global),
240
+ keybinds: resolvedKeybinds(global),
241
+ verifyCommands: resolvedVerifyCommands(global),
242
+ repoMap: resolvedRepoMap(global),
243
+ theme: resolvedTheme(global),
244
+ editor: resolvedEditor(global),
245
+ sandbox: resolvedSandbox(global),
246
+ lsp: resolvedLsp(global),
247
+ usageCap: resolvedUsageCap(global),
248
+ updateCheck: resolvedUpdateCheck(global),
249
+ providers: { ...known, ...fromProject },
250
+ projectProviders: Object.keys(fromProject),
251
+ allow: [...global.allow ?? []]
252
+ };
253
+ }
254
+ function readCredentials(env = process.env) {
255
+ const path = join(bccliHome(env), "credentials");
256
+ if (!existsSync(path)) return {};
257
+ try {
258
+ return JSON.parse(readFileSync(path, "utf8"));
259
+ } catch {
260
+ return {};
261
+ }
262
+ }
263
+ function saveCredential(providerId, key, env = process.env) {
264
+ const home = bccliHome(env);
265
+ mkdirSync(home, { recursive: true });
266
+ const path = join(home, "credentials");
267
+ writeFileSync(path, JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), { mode: 384 });
268
+ chmodSync(path, 384);
269
+ }
270
+ function resolveModel(config, modelRef, env = process.env) {
271
+ const slash = modelRef.indexOf("/");
272
+ if (slash <= 0 || slash === modelRef.length - 1) {
273
+ throw new ConfigError(t("Model must be in provider/model format, e.g. bc-cloud/glm-5.3-flash (got: {ref})", { ref: modelRef }));
274
+ }
275
+ const providerId = modelRef.slice(0, slash);
276
+ const provider = config.providers[providerId];
277
+ if (!provider) {
278
+ throw new ConfigError(t('Provider "{id}" is not in the config. Available: {list}', { id: providerId, list: Object.keys(config.providers).join(", ") }));
279
+ }
280
+ const apiKey = (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || readCredentials(env)[providerId] || void 0;
281
+ if (provider.apiKeyEnv && !apiKey) {
282
+ throw new ConfigError(t("No API key for {id} yet. Run `bccli login` or set the env var {env}.", { id: providerId, env: provider.apiKeyEnv }));
283
+ }
284
+ return { providerId, model: modelRef.slice(slash + 1), baseURL: provider.baseURL.replace(/\/+$/, ""), apiKey };
285
+ }
286
+ function removeCredential(providerId, env = process.env) {
287
+ const creds = readCredentials(env);
288
+ if (!(providerId in creds)) return false;
289
+ delete creds[providerId];
290
+ const path = join(bccliHome(env), "credentials");
291
+ writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 384 });
292
+ chmodSync(path, 384);
293
+ return true;
294
+ }
295
+
296
+ export {
297
+ PRESETS,
298
+ matchKeybind,
299
+ setTheme,
300
+ color,
301
+ ACCENT,
302
+ ConfigError,
303
+ bccliHome,
304
+ readJsonConfig,
305
+ loadConfig,
306
+ readCredentials,
307
+ saveCredential,
308
+ resolveModel,
309
+ removeCredential
310
+ };
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ t
4
+ } from "./chunk-M23QBX3V.js";
5
+
6
+ // src/reasoning.ts
7
+ var REASONING_LEVELS = ["auto", "off", "low", "medium", "high", "max"];
8
+ var OPENAI_LEVELS = ["auto", "low", "medium", "high"];
9
+ var BOTCONNECTOR_LEVELS = REASONING_LEVELS;
10
+ function isReasoningLevel(value) {
11
+ return REASONING_LEVELS.includes(value);
12
+ }
13
+ function parseReasoningLevel(value, label = "reasoning") {
14
+ if (!isReasoningLevel(value)) {
15
+ throw new Error(t("{label} must be one of: {levels}", { label, levels: REASONING_LEVELS.join(", ") }));
16
+ }
17
+ return value;
18
+ }
19
+ function reasoningCapability(providerId) {
20
+ switch (providerId) {
21
+ case "bc-cloud":
22
+ return { levels: BOTCONNECTOR_LEVELS, parameter: "reasoning_effort" };
23
+ case "openai":
24
+ return { levels: OPENAI_LEVELS, parameter: "reasoning_effort" };
25
+ case "openrouter":
26
+ return { levels: ["auto", "low", "medium", "high"], parameter: "reasoning" };
27
+ default:
28
+ return void 0;
29
+ }
30
+ }
31
+ function supportedReasoningLevels(providerId) {
32
+ return reasoningCapability(providerId)?.levels ?? ["auto"];
33
+ }
34
+ function assertReasoningSupported(providerId, level) {
35
+ if (level === "auto") return;
36
+ const capability = reasoningCapability(providerId);
37
+ if (!capability) {
38
+ throw new Error(t("This provider has no manual reasoning setting yet. Use Auto."));
39
+ }
40
+ if (!capability.levels.includes(level)) {
41
+ const supported = capability.levels.filter((x) => x !== "auto").map(capitalize).join("/");
42
+ throw new Error(t("This model/provider does not support reasoning level '{level}'. Use Auto{supported}.", { level, supported: supported ? `/${supported}` : "" }));
43
+ }
44
+ }
45
+ function reasoningPayload(providerId, level) {
46
+ if (level === "auto") return {};
47
+ assertReasoningSupported(providerId, level);
48
+ const capability = reasoningCapability(providerId);
49
+ if (capability.parameter === "reasoning") return { reasoning: { effort: level } };
50
+ return { reasoning_effort: level };
51
+ }
52
+ function capitalize(value) {
53
+ return value.charAt(0).toUpperCase() + value.slice(1);
54
+ }
55
+
56
+ export {
57
+ REASONING_LEVELS,
58
+ isReasoningLevel,
59
+ parseReasoningLevel,
60
+ supportedReasoningLevels,
61
+ assertReasoningSupported,
62
+ reasoningPayload
63
+ };
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/version.ts
4
+ var VERSION = true ? "0.4.0-beta.40" : "0.0.0-dev";
5
+
6
+ export {
7
+ VERSION
8
+ };
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/tools/types.ts
4
+ function defineTool(tool) {
5
+ return tool;
6
+ }
7
+
8
+ export {
9
+ defineTool
10
+ };
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runCommand,
4
+ runHooks
5
+ } from "./chunk-ZMU7MXCH.js";
6
+ import {
7
+ ConfigError
8
+ } from "./chunk-M7LLJQ7M.js";
9
+ import {
10
+ t
11
+ } from "./chunk-M23QBX3V.js";
12
+
13
+ // src/verify.ts
14
+ var MAX_VERIFY_ROUNDS = 2;
15
+ async function runVerify(commands, opts) {
16
+ const run = opts.run ?? runCommand;
17
+ const failures = [];
18
+ for (const cmd of commands) {
19
+ const r = await run(cmd, { cwd: opts.cwd, timeoutMs: opts.timeoutMs ?? 3e5, signal: opts.signal, env: opts.env });
20
+ const exitCode = r.exitCode ?? -1;
21
+ if (exitCode !== 0) failures.push({ cmd, exitCode, output: r.output });
22
+ }
23
+ return failures;
24
+ }
25
+ function verifyFollowup(failures, maxBytes = 8e3) {
26
+ const blocks = failures.map((f) => {
27
+ const output = f.output.length > maxBytes ? `${f.output.slice(0, maxBytes)}
28
+ [truncated]` : f.output;
29
+ return `$ ${f.cmd}
30
+ [exit code ${f.exitCode}]
31
+ ${output}`;
32
+ });
33
+ return t("Automatic verification after your edits failed. Fix the problems so these commands pass, then stop.\n\n{blocks}", {
34
+ blocks: blocks.join("\n\n")
35
+ });
36
+ }
37
+
38
+ // src/print.ts
39
+ function toolLabel(tool) {
40
+ if (tool === "web_search") return "Search";
41
+ return tool.charAt(0).toUpperCase() + tool.slice(1);
42
+ }
43
+ async function runPrint(rt, prompt, io = { out: process.stdout, err: process.stderr }, opts = {}) {
44
+ const format = opts.outputFormat ?? "text";
45
+ const emit = (line) => io.out.write(`${JSON.stringify(line)}
46
+ `);
47
+ let result = "";
48
+ const toolNames = /* @__PURE__ */ new Map();
49
+ rt.interaction.approvePlan = async () => {
50
+ if (opts.allowAll) return "allowAll";
51
+ io.err.write(`${t("Plan needs approval; run with --allow-all.")}
52
+ `);
53
+ return "no";
54
+ };
55
+ let failed = false;
56
+ let wroteText = false;
57
+ let edited = false;
58
+ rt.agent.onEvent = (event) => {
59
+ switch (event.type) {
60
+ case "text":
61
+ if (format === "text") {
62
+ io.out.write(event.delta);
63
+ wroteText = true;
64
+ } else {
65
+ result += event.delta;
66
+ }
67
+ break;
68
+ case "toolStart":
69
+ toolNames.set(event.id, event.tool);
70
+ io.err.write(`\u23BF ${toolLabel(event.tool)} ${event.target}
71
+ `);
72
+ if (format === "stream-json") emit({ type: "tool_use", tool: event.tool, target: event.target });
73
+ break;
74
+ case "toolEnd":
75
+ if (event.isError) io.err.write(` \u2717 ${event.output.split("\n")[0]}
76
+ `);
77
+ if ((event.tool === "edit" || event.tool === "write") && !event.isError) edited = true;
78
+ if (format === "stream-json") emit({ type: "tool_result", tool: toolNames.get(event.id), isError: !!event.isError, output: event.output });
79
+ break;
80
+ case "stepLimit":
81
+ io.err.write(`${t("Step limit reached.")}
82
+ `);
83
+ failed = true;
84
+ break;
85
+ case "budgetExceeded":
86
+ io.err.write(`${t("Budget exceeded ({kind}): {used} of {limit}. Further model calls are blocked; raise usageCap in ~/.bccli/config.json.", { kind: event.kind, used: Math.round(event.used * 100) / 100, limit: event.limit })}
87
+ `);
88
+ if (format === "stream-json") emit({ type: "error", message: "budgetExceeded" });
89
+ failed = true;
90
+ break;
91
+ case "aborted":
92
+ io.err.write(`${t("Cancelled.")}
93
+ `);
94
+ failed = true;
95
+ break;
96
+ case "error":
97
+ io.err.write(`Error: ${event.message}
98
+ `);
99
+ if (format === "stream-json") emit({ type: "error", message: event.message });
100
+ failed = true;
101
+ break;
102
+ }
103
+ };
104
+ rt.agent.askPermission = async (req) => {
105
+ io.err.write(` ${t("Needs permission for {tool}. Run with --allow-all or --allowed-tools {kind}.", { tool: req.tool, kind: req.kind })}
106
+ `);
107
+ return "no";
108
+ };
109
+ const controller = new AbortController();
110
+ const onSigint = () => controller.abort();
111
+ process.once("SIGINT", onSigint);
112
+ const start = await runHooks(rt.config.hooks, "SessionStart", {}, { env: rt.env, cwd: rt.cwd });
113
+ for (const warning of start.warnings) io.err.write(`${warning}
114
+ `);
115
+ await rt.emitPlugins("SessionStart");
116
+ try {
117
+ await rt.agent.run(prompt, controller.signal);
118
+ const commands = rt.config.verifyCommands;
119
+ if (edited && commands.length && !controller.signal.aborted) {
120
+ for (let round = 0; round < MAX_VERIFY_ROUNDS; round++) {
121
+ io.err.write(`${t("Verifying edits: {cmds}", { cmds: commands.join(", ") })}
122
+ `);
123
+ const failures = await runVerify(commands, { cwd: rt.cwd, env: rt.env, signal: controller.signal });
124
+ if (!failures.length) {
125
+ io.err.write(`${t("Verification passed.")}
126
+ `);
127
+ break;
128
+ }
129
+ if (round === MAX_VERIFY_ROUNDS - 1 || controller.signal.aborted) {
130
+ io.err.write(`${t("Verification still failing after {n} fix round(s). Run the commands manually to see why.", { n: MAX_VERIFY_ROUNDS })}
131
+ `);
132
+ failed = true;
133
+ break;
134
+ }
135
+ await rt.agent.run(verifyFollowup(failures), controller.signal);
136
+ }
137
+ }
138
+ } finally {
139
+ process.off("SIGINT", onSigint);
140
+ }
141
+ const stop = await runHooks(rt.config.hooks, "Stop", {}, { env: rt.env, cwd: rt.cwd });
142
+ for (const warning of stop.warnings) io.err.write(`${warning}
143
+ `);
144
+ await rt.emitPlugins("Stop");
145
+ if (format === "json") {
146
+ io.out.write(`${JSON.stringify({ status: failed ? "failed" : "ok", model: rt.modelRef, result })}
147
+ `);
148
+ } else if (format === "stream-json") {
149
+ if (result) emit({ type: "message", text: result });
150
+ emit({ type: "result", status: failed ? "failed" : "ok", model: rt.modelRef });
151
+ } else if (wroteText) {
152
+ io.out.write("\n");
153
+ }
154
+ return failed ? 1 : 0;
155
+ }
156
+
157
+ // src/worktree.ts
158
+ import { execFile } from "child_process";
159
+ import { existsSync } from "fs";
160
+ import { basename, dirname, join } from "path";
161
+ function git(cwd, args, bin = "git") {
162
+ return new Promise((resolve) => {
163
+ execFile(
164
+ bin,
165
+ args,
166
+ { cwd, maxBuffer: 20 * 1024 * 1024 },
167
+ (error, stdout, stderr) => resolve({ ok: !error, out: error ? stderr : stdout, missing: error?.code === "ENOENT" })
168
+ );
169
+ });
170
+ }
171
+ var NAME_RE = /^[A-Za-z0-9._-]+$/;
172
+ function samePath(a, b) {
173
+ const norm = (p) => process.platform === "win32" ? p.replace(/\//g, "\\").toLowerCase() : p.replace(/\\/g, "/");
174
+ return norm(a) === norm(b);
175
+ }
176
+ function worktreeEntries(porcelain) {
177
+ return porcelain.split(/\r?\n\r?\n/).map((block) => {
178
+ const path = /^worktree (.+)$/m.exec(block)?.[1]?.trim();
179
+ if (!path) return void 0;
180
+ const branch = /^branch refs\/heads\/(.+)$/m.exec(block)?.[1];
181
+ return branch ? { path, branch } : { path };
182
+ }).filter((entry) => !!entry);
183
+ }
184
+ async function resolveWorktree(cwd, name, bin = "git") {
185
+ if (!NAME_RE.test(name)) {
186
+ throw new ConfigError(t('Invalid worktree name "{name}": use letters, digits, dot, dash, or underscore (no slashes).', { name }));
187
+ }
188
+ const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], bin);
189
+ if (inside.missing) throw new ConfigError(t("git is not installed or not on PATH, so -w cannot be used."));
190
+ if (!inside.ok || inside.out.trim() !== "true") throw new ConfigError(t("This folder is not a git repository, so -w cannot create a worktree."));
191
+ const list = await git(cwd, ["worktree", "list", "--porcelain"], bin);
192
+ if (!list.ok) throw new ConfigError(t("This folder is not a git repository, so -w cannot create a worktree."));
193
+ const entries = worktreeEntries(list.out);
194
+ const main = entries[0]?.path;
195
+ if (!main) throw new ConfigError(t("This folder is not a git repository, so -w cannot create a worktree."));
196
+ const path = join(dirname(main), `${basename(main)}.worktrees`, name);
197
+ if (entries.some((entry) => samePath(entry.path, path))) return { path, created: false };
198
+ if (existsSync(path)) {
199
+ throw new ConfigError(t("Worktree path {path} already exists but is not a registered worktree; remove or rename it first.", { path }));
200
+ }
201
+ const branch = `bccli/${name}`;
202
+ const hasBranch = (await git(main, ["rev-parse", "--verify", "-q", `refs/heads/${branch}`], bin)).ok;
203
+ const add = hasBranch ? await git(main, ["worktree", "add", path, branch], bin) : await git(main, ["worktree", "add", "-b", branch, path], bin);
204
+ if (!add.ok) throw new ConfigError(add.out.trim() || t("This folder is not a git repository, so -w cannot create a worktree."));
205
+ return { path, created: true };
206
+ }
207
+ async function worktreeListText(cwd, bin = "git") {
208
+ const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], bin);
209
+ if (inside.missing) return t("git is not installed or not on PATH, so /worktree cannot be used.");
210
+ if (!inside.ok || inside.out.trim() !== "true") return t("This folder is not a git repository, so there is no diff.");
211
+ const list = await git(cwd, ["worktree", "list", "--porcelain"], bin);
212
+ if (!list.ok) return t("This folder is not a git repository, so there is no diff.");
213
+ const rows = worktreeEntries(list.out).map((entry) => ` ${entry.path}${entry.branch ? ` (${entry.branch})` : ""}`);
214
+ return [
215
+ t("Git worktrees:"),
216
+ ...rows,
217
+ "",
218
+ t("Create one with: bccli -w <name>"),
219
+ t("Remove with: git worktree remove <path>")
220
+ ].join("\n");
221
+ }
222
+
223
+ export {
224
+ MAX_VERIFY_ROUNDS,
225
+ runVerify,
226
+ verifyFollowup,
227
+ toolLabel,
228
+ runPrint,
229
+ resolveWorktree,
230
+ worktreeListText
231
+ };
@@ -5,7 +5,7 @@ import {
5
5
  readCredentials,
6
6
  readJsonConfig,
7
7
  removeCredential
8
- } from "./chunk-RZUJYAQV.js";
8
+ } from "./chunk-M7LLJQ7M.js";
9
9
 
10
10
  // src/providers.ts
11
11
  import { mkdirSync, writeFileSync } from "fs";
@@ -45,6 +45,7 @@ function removeGlobalProvider(id, env = process.env) {
45
45
  export {
46
46
  providerName,
47
47
  hasKey,
48
+ readGlobalConfigFile,
48
49
  writeGlobalConfig,
49
50
  removeGlobalProvider
50
51
  };
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ contentText
4
+ } from "./chunk-GGNXHJL6.js";
2
5
 
3
6
  // src/session.ts
4
7
  import { randomBytes } from "crypto";
@@ -36,7 +39,7 @@ var Session = class _Session {
36
39
  return readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
37
40
  const session = new _Session(join(dir, f));
38
41
  const first = session.load().find((m) => m.role === "user");
39
- return { session, mtime: statSync(session.file).mtime, preview: first ? String(first.content).slice(0, 80) : "" };
42
+ return { session, mtime: statSync(session.file).mtime, preview: first ? contentText(first.content).slice(0, 80) : "" };
40
43
  }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime() || b.session.file.localeCompare(a.session.file));
41
44
  }
42
45
  static latest(home, cwd) {