@botconnector/bccli 0.4.0-beta.8 → 0.4.0

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 (59) hide show
  1. package/README.en.md +242 -0
  2. package/README.md +95 -3
  3. package/dist/acp-ATIL4JOZ.js +182 -0
  4. package/dist/args-J5ONVGIF.js +13 -0
  5. package/dist/chunk-45SDLMTV.js +341 -0
  6. package/dist/chunk-6M5X2KZE.js +8 -0
  7. package/dist/chunk-7F3IWBPQ.js +191 -0
  8. package/dist/{chunk-CLN4Z5E3.js → chunk-BLOYFIAD.js} +6 -3
  9. package/dist/chunk-BNA74HPN.js +145 -0
  10. package/dist/{chunk-DX7CZTBB.js → chunk-BQ2IGPBR.js} +14 -9
  11. package/dist/chunk-JL5ZCSP2.js +76 -0
  12. package/dist/chunk-KEMYIGCP.js +235 -0
  13. package/dist/{chunk-BFZXRKW5.js → chunk-L7QI6JN2.js} +24 -2
  14. package/dist/chunk-LACUWBWS.js +361 -0
  15. package/dist/chunk-M7CALSP3.js +733 -0
  16. package/dist/{chunk-ELML4KVQ.js → chunk-O7M3TGIW.js} +34 -2
  17. package/dist/chunk-PSO7R5AD.js +10 -0
  18. package/dist/chunk-PUN4HMAG.js +940 -0
  19. package/dist/{chunk-SJH3YLW4.js → chunk-QDQU6VY2.js} +60 -6
  20. package/dist/chunk-QZKLZELZ.js +64 -0
  21. package/dist/{chunk-7S6JXQCH.js → chunk-SPRNLS7K.js} +2 -1
  22. package/dist/chunk-TLPFQIS2.js +231 -0
  23. package/dist/chunk-TVALKHFB.js +2143 -0
  24. package/dist/chunk-UZWJQF67.js +500 -0
  25. package/dist/chunk-XBO5GACQ.js +56 -0
  26. package/dist/cli.js +162 -1383
  27. package/dist/completion-HPPFDAPB.js +125 -0
  28. package/dist/{config-YUNKPCNB.js → config-COPNMMV6.js} +5 -2
  29. package/dist/config-RZ5JONMX.js +30 -0
  30. package/dist/ghBotCli-IBCZIWT5.js +677 -0
  31. package/dist/integrationCli-P75R2VSC.js +685 -0
  32. package/dist/login-SYIQP46J.js +16 -0
  33. package/dist/{mcpCli-DZWQPTOS.js → mcpCli-QUV5SD64.js} +42 -9
  34. package/dist/modelsCli-TRRB5LX4.js +36 -0
  35. package/dist/npmRunner-LU43GM3W.js +46 -0
  36. package/dist/oauthLogin-6FWQABJ7.js +210 -0
  37. package/dist/pluginsCli-GNW4TPVV.js +284 -0
  38. package/dist/prCli-5IMV3VNQ.js +78 -0
  39. package/dist/providerCli-FAOZHOKW.js +15 -0
  40. package/dist/sdk.js +5490 -0
  41. package/dist/serve-6QLBJUYW.js +178 -0
  42. package/dist/serve-JG7RLIUD.js +515 -0
  43. package/dist/session-KBAQ4BZ5.js +12 -0
  44. package/dist/setup-FJAWDWV4.js +24 -0
  45. package/dist/statsCli-CF3R7ATL.js +353 -0
  46. package/dist/transferCli-TK2GSVRK.js +71 -0
  47. package/dist/ui-B3CJ6TGE.js +2365 -0
  48. package/dist/update-K5IQ2MYR.js +145 -0
  49. package/package.json +25 -4
  50. package/dist/args-FLTRM57K.js +0 -12
  51. package/dist/chunk-2MGINW4O.js +0 -114
  52. package/dist/chunk-IM3KMPVZ.js +0 -52
  53. package/dist/chunk-KV27KTOX.js +0 -254
  54. package/dist/chunk-U7GK73CK.js +0 -341
  55. package/dist/chunk-Z7PM3LTC.js +0 -93
  56. package/dist/login-JXFORO3J.js +0 -9
  57. package/dist/providerCli-Q7LRM2NP.js +0 -12
  58. package/dist/session-7UB77CEG.js +0 -9
  59. package/dist/ui-6GTQUJOE.js +0 -1415
package/dist/cli.js CHANGED
@@ -1,1438 +1,217 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- HELP_TEXT,
4
- parseCliArgs
5
- } from "./chunk-Z7PM3LTC.js";
3
+ resolveWorktree,
4
+ runPrint
5
+ } from "./chunk-TLPFQIS2.js";
6
6
  import {
7
- addProviderKey,
8
- createProvider,
9
- listAllModels
10
- } from "./chunk-U7GK73CK.js";
7
+ createRuntime
8
+ } from "./chunk-TVALKHFB.js";
9
+ import "./chunk-QZKLZELZ.js";
10
+ import "./chunk-M7CALSP3.js";
11
+ import "./chunk-45SDLMTV.js";
12
+ import "./chunk-PSO7R5AD.js";
11
13
  import {
12
- runLogin
13
- } from "./chunk-IM3KMPVZ.js";
14
+ VERSION
15
+ } from "./chunk-6M5X2KZE.js";
14
16
  import {
15
- BUILTIN_AGENTS,
16
- buildSystemPrompt,
17
- ignorePatterns,
18
- loadAgentDefs,
19
- loadCommands,
20
- loadSkills,
21
- parseFrontmatter,
22
- runPrint
23
- } from "./chunk-KV27KTOX.js";
17
+ helpText,
18
+ parseCliArgs
19
+ } from "./chunk-KEMYIGCP.js";
20
+ import "./chunk-O7M3TGIW.js";
21
+ import "./chunk-JL5ZCSP2.js";
22
+ import "./chunk-XBO5GACQ.js";
24
23
  import {
25
- Permissions
26
- } from "./chunk-ELML4KVQ.js";
24
+ readGlobalConfigFile
25
+ } from "./chunk-SPRNLS7K.js";
27
26
  import {
28
- providerName
29
- } from "./chunk-7S6JXQCH.js";
30
- import "./chunk-CLN4Z5E3.js";
27
+ defaultLoginDeps,
28
+ runLogin
29
+ } from "./chunk-BNA74HPN.js";
30
+ import "./chunk-7F3IWBPQ.js";
31
31
  import {
32
- Session,
33
32
  pruneSessions
34
- } from "./chunk-SJH3YLW4.js";
33
+ } from "./chunk-QDQU6VY2.js";
34
+ import "./chunk-PUN4HMAG.js";
35
+ import "./chunk-BLOYFIAD.js";
36
+ import "./chunk-L7QI6JN2.js";
35
37
  import {
36
38
  ConfigError,
37
- bccliHome,
38
- loadConfig,
39
- resolveModel
40
- } from "./chunk-2MGINW4O.js";
41
-
42
- // src/tools/index.ts
43
- import { z as z8 } from "zod";
44
-
45
- // src/tools/bash.ts
46
- import { spawn } from "child_process";
47
- import { z } from "zod";
48
-
49
- // src/tools/types.ts
50
- function defineTool(tool) {
51
- return tool;
39
+ setTheme
40
+ } from "./chunk-LACUWBWS.js";
41
+ import {
42
+ resolveLanguage,
43
+ setLanguage,
44
+ t
45
+ } from "./chunk-UZWJQF67.js";
46
+
47
+ // src/exit.ts
48
+ var EXIT_GRACE_MS = 1e3;
49
+ function settleExit(code, deps) {
50
+ const d = {
51
+ setExitCode: (c) => {
52
+ process.exitCode = c;
53
+ },
54
+ exit: (c) => process.exit(c),
55
+ setTimer: (fn, ms) => setTimeout(fn, ms),
56
+ ...deps
57
+ };
58
+ d.setExitCode(code);
59
+ const force = d.setTimer(() => d.exit(code), EXIT_GRACE_MS);
60
+ force.unref();
52
61
  }
53
62
 
54
- // src/tools/bash.ts
55
- var HALF = 15e3;
56
- function killProcessTree(pid, platform = process.platform, spawnFn = spawn) {
57
- if (platform === "win32") {
58
- const killer = spawnFn("taskkill", ["/pid", String(pid), "/T", "/F"]);
59
- killer.on("error", () => {
60
- try {
61
- process.kill(pid);
62
- } catch {
63
- }
64
- });
65
- return;
66
- }
63
+ // src/cli.ts
64
+ function chooseLanguage(argv) {
65
+ let fromConfig;
67
66
  try {
68
- process.kill(-pid, "SIGKILL");
67
+ fromConfig = readGlobalConfigFile(process.env).language;
69
68
  } catch {
70
- try {
71
- process.kill(pid, "SIGKILL");
72
- } catch {
73
- }
74
- }
75
- }
76
- var active = /* @__PURE__ */ new Set();
77
- var exitHookInstalled = false;
78
- function killAllCommands() {
79
- for (const kill of active) kill();
80
- }
81
- function runCommand(command, opts) {
82
- return new Promise((resolve2) => {
83
- const child = spawn(command, {
84
- cwd: opts.cwd,
85
- shell: true,
86
- detached: process.platform !== "win32",
87
- stdio: ["ignore", "pipe", "pipe"]
88
- });
89
- if (!exitHookInstalled) {
90
- process.on("exit", killAllCommands);
91
- exitHookInstalled = true;
92
- }
93
- child.stdout.setEncoding("utf8");
94
- child.stderr.setEncoding("utf8");
95
- let head = "";
96
- let tail = "";
97
- let dropped = 0;
98
- let timedOut = false;
99
- let aborted = false;
100
- const onData = (data) => {
101
- let s = data;
102
- if (head.length < HALF) {
103
- const take = s.slice(0, HALF - head.length);
104
- head += take;
105
- s = s.slice(take.length);
106
- }
107
- if (s) {
108
- tail += s;
109
- if (tail.length > HALF) {
110
- dropped += tail.length - HALF;
111
- tail = tail.slice(-HALF);
112
- }
113
- }
114
- };
115
- child.stdout.on("data", onData);
116
- child.stderr.on("data", onData);
117
- const kill = () => {
118
- if (child.pid !== void 0) killProcessTree(child.pid);
119
- };
120
- const killAsAborted = () => {
121
- aborted = true;
122
- kill();
123
- };
124
- active.add(killAsAborted);
125
- const timer = setTimeout(() => {
126
- timedOut = true;
127
- kill();
128
- }, opts.timeoutMs);
129
- const onAbort = () => {
130
- aborted = true;
131
- kill();
132
- };
133
- if (opts.signal.aborted) onAbort();
134
- opts.signal.addEventListener("abort", onAbort, { once: true });
135
- const finish = (exitCode, extra = "") => {
136
- active.delete(killAsAborted);
137
- clearTimeout(timer);
138
- opts.signal.removeEventListener("abort", onAbort);
139
- const output = dropped ? `${head}
140
- \u2026 [${dropped} karakter dipotong] \u2026
141
- ${tail}` : head + tail;
142
- resolve2({ output: output + extra, exitCode, timedOut, aborted });
143
- };
144
- child.on("error", (error) => finish(null, error.message));
145
- child.on("close", (code) => finish(code));
146
- });
147
- }
148
- var bashTool = defineTool({
149
- name: "bash",
150
- description: "Run a shell command in the project directory. No interactive input. Default timeout 2 minutes (max 10). Use for tests, builds, git, and other CLI tasks.",
151
- schema: z.object({
152
- command: z.string().describe("The shell command to run"),
153
- timeout_ms: z.number().int().min(1e3).max(6e5).optional().describe("Timeout in milliseconds")
154
- }),
155
- kind: "bash",
156
- target: (input) => input.command,
157
- async run(input, ctx) {
158
- const r = await runCommand(input.command, { cwd: ctx.cwd, timeoutMs: input.timeout_ms ?? 12e4, signal: ctx.signal });
159
- const status = r.timedOut ? `[dihentikan: timeout ${Math.round((input.timeout_ms ?? 12e4) / 1e3)} detik]` : r.aborted ? "[dibatalkan user]" : `[exit code ${r.exitCode}]`;
160
- return { output: `${r.output.trimEnd()}
161
- ${status}`.trimStart(), isError: r.exitCode !== 0 };
162
- }
163
- });
164
-
165
- // src/tools/edit.ts
166
- import { readFile, writeFile } from "fs/promises";
167
- import { z as z2 } from "zod";
168
-
169
- // src/diff.ts
170
- import { structuredPatch } from "diff";
171
- function diffLines(oldText, newText) {
172
- const patch = structuredPatch("a", "b", oldText, newText, "", "", { context: 2 });
173
- const out = [];
174
- for (const hunk of patch.hunks) {
175
- let oldLine = hunk.oldStart;
176
- let newLine = hunk.newStart;
177
- for (const raw of hunk.lines) {
178
- const text = raw.slice(1);
179
- if (raw[0] === "-") out.push({ kind: "del", line: oldLine++, text });
180
- else if (raw[0] === "+") out.push({ kind: "add", line: newLine++, text });
181
- else if (raw[0] === " ") {
182
- out.push({ kind: "ctx", line: newLine, text });
183
- oldLine++;
184
- newLine++;
185
- }
186
- }
187
- }
188
- return out;
189
- }
190
- function formatDiff(lines) {
191
- const mark = { add: "+", del: "-", ctx: " " };
192
- return lines.map((l) => `${String(l.line).padStart(5)} ${mark[l.kind]} ${l.text}`).join("\n");
193
- }
194
-
195
- // src/tools/paths.ts
196
- import { isAbsolute, resolve } from "path";
197
- function resolvePath(cwd, path) {
198
- return isAbsolute(path) ? path : resolve(cwd, path);
199
- }
200
- function errorCode(error) {
201
- return error.code ?? error.message;
202
- }
203
-
204
- // src/tools/edit.ts
205
- var schema = z2.object({
206
- path: z2.string().describe("File path, relative to the project or absolute"),
207
- old_string: z2.string().describe("Exact text to replace; must be unique unless replace_all"),
208
- new_string: z2.string().describe("Replacement text"),
209
- replace_all: z2.boolean().optional().describe("Replace every occurrence")
210
- });
211
- function countOccurrences(haystack, needle) {
212
- let count = 0;
213
- for (let i = haystack.indexOf(needle); i !== -1; i = haystack.indexOf(needle, i + needle.length)) count++;
214
- return count;
215
- }
216
- function applyEdit(content, input) {
217
- if (input.old_string === input.new_string) {
218
- return { output: "old_string dan new_string sama; tidak ada perubahan.", isError: true };
219
- }
220
- const eol = content.includes("\r\n") ? "\r\n" : "\n";
221
- const toEol = (s) => s.replace(/\r?\n/g, eol);
222
- const attempts = [[input.old_string, input.new_string]];
223
- if (toEol(input.old_string) !== input.old_string) attempts.push([toEol(input.old_string), toEol(input.new_string)]);
224
- for (const [oldText, newText] of attempts) {
225
- const count = oldText ? countOccurrences(content, oldText) : 0;
226
- if (count === 0) continue;
227
- if (count > 1 && !input.replace_all) {
228
- return {
229
- output: `old_string muncul ${count} kali di ${input.path}. Tambah konteks agar unik, atau pakai replace_all.`,
230
- isError: true
231
- };
232
- }
233
- return input.replace_all ? content.split(oldText).join(newText) : content.replace(oldText, () => newText);
234
- }
235
- return { output: `old_string tidak ditemukan di ${input.path}. Baca ulang file-nya.`, isError: true };
236
- }
237
- var editTool = defineTool({
238
- name: "edit",
239
- description: "Replace exact text in a file. The file must have been read first. old_string must match exactly (including indentation) and be unique unless replace_all is true.",
240
- schema,
241
- kind: "edit",
242
- target: (input) => input.path,
243
- async validate(input, ctx) {
244
- const abs = resolvePath(ctx.cwd, input.path);
245
- if (!ctx.readFiles.has(abs)) return `Baca ${input.path} dulu dengan read sebelum mengedit.`;
246
- let content;
247
- try {
248
- content = await readFile(abs, "utf8");
249
- } catch (error) {
250
- return `Tidak bisa membaca ${input.path}: ${errorCode(error)}`;
251
- }
252
- const next = applyEdit(content, input);
253
- return typeof next === "string" ? void 0 : next.output;
254
- },
255
- async preview(input, ctx) {
256
- const abs = resolvePath(ctx.cwd, input.path);
257
- const content = await readFile(abs, "utf8");
258
- const next = applyEdit(content, input);
259
- return typeof next === "string" ? formatDiff(diffLines(content, next)) : void 0;
260
- },
261
- async run(input, ctx) {
262
- const abs = resolvePath(ctx.cwd, input.path);
263
- if (!ctx.readFiles.has(abs)) {
264
- return { output: `Baca ${input.path} dulu dengan read sebelum mengedit.`, isError: true };
265
- }
266
- let content;
267
- try {
268
- content = await readFile(abs, "utf8");
269
- } catch (error) {
270
- return { output: `Tidak bisa membaca ${input.path}: ${errorCode(error)}`, isError: true };
271
- }
272
- const next = applyEdit(content, input);
273
- if (typeof next !== "string") return next;
274
- await ctx.checkpoint?.(abs);
275
- await writeFile(abs, next);
276
- return { output: `Mengedit ${input.path}.`, display: formatDiff(diffLines(content, next)) };
277
- }
278
- });
279
-
280
- // src/tools/fetch.ts
281
- import { z as z3 } from "zod";
282
- var MAX_CHARS = 5e4;
283
- var MAX_BYTES = 5 * 1024 * 1024;
284
- async function readCapped(res) {
285
- const reader = res.body?.getReader();
286
- if (!reader) return { text: "", truncated: false };
287
- const chunks = [];
288
- let size = 0;
289
- for (; ; ) {
290
- const { done, value } = await reader.read();
291
- if (done) return { text: Buffer.concat(chunks).toString("utf8"), truncated: false };
292
- size += value.length;
293
- if (size > MAX_BYTES) {
294
- chunks.push(value.subarray(0, value.length - (size - MAX_BYTES)));
295
- await reader.cancel();
296
- return { text: Buffer.concat(chunks).toString("utf8"), truncated: true };
297
- }
298
- chunks.push(value);
299
- }
300
- }
301
- function htmlToText(html) {
302
- return html.replace(/<(script|style|noscript)[\s\S]*?<\/\1>/gi, "").replace(/<(br|\/p|\/div|\/h[1-6]|\/li|\/tr)\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&").split("\n").map((l) => l.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
303
- }
304
- function describeFetchError(error) {
305
- const cause = error.cause;
306
- const code = cause?.code ?? "";
307
- const detail = cause?.message ?? error.message;
308
- if (error.name === "TimeoutError") return "tidak ada jawaban dalam 30 detik";
309
- if (code === "ENOTFOUND" || code === "EAI_AGAIN") return `${detail} \u2014 DNS tidak menemukan host (salah ketik, offline, atau diblokir DNS/ISP)`;
310
- if (/CERT|SELF_SIGNED|UNABLE_TO/.test(code)) {
311
- return `${detail} \u2014 sertifikat TLS ditolak; sering karena antivirus/proxy yang memeriksa HTTPS. Coba jalankan dengan NODE_OPTIONS=--use-system-ca`;
312
- }
313
- if (/ECONNRESET|ECONNREFUSED|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|UND_ERR_CONNECT_TIMEOUT|UND_ERR_SOCKET/.test(code)) {
314
- return `${detail} \u2014 koneksi gagal (firewall, proxy, atau situs diblokir jaringan)`;
315
- }
316
- return code ? `${detail} (${code})` : detail;
317
- }
318
- var fetchTool = defineTool({
319
- name: "fetch",
320
- description: "Fetch a URL (http/https). HTML is converted to plain text. Max 50k characters.",
321
- schema: z3.object({ url: z3.string().describe("Absolute http(s) URL") }),
322
- kind: "fetch",
323
- target: (input) => input.url,
324
- async run(input, ctx) {
325
- if (!/^https?:\/\//i.test(input.url)) return { output: "URL harus diawali http:// atau https://", isError: true };
326
- let url = new URL(input.url);
327
- let res;
328
- try {
329
- for (let hop = 0; hop < 5; hop++) {
330
- res = await fetch(url, { signal: AbortSignal.any([ctx.signal, AbortSignal.timeout(3e4)]), redirect: "manual" });
331
- const location = res.status >= 300 && res.status < 400 ? res.headers.get("location") : null;
332
- if (!location) break;
333
- const next = new URL(location, url);
334
- if (next.host !== url.host) {
335
- await res.body?.cancel();
336
- return {
337
- output: `Redirect ke host lain: ${next.href}. Panggil fetch lagi dengan URL itu kalau memang perlu (akan diminta izin).`,
338
- isError: true
339
- };
340
- }
341
- await res.body?.cancel();
342
- url = next;
343
- }
344
- } catch (error) {
345
- return { output: `Gagal mengambil ${input.url}: ${describeFetchError(error)}`, isError: true };
346
- }
347
- if (!res) return { output: `Gagal mengambil ${input.url}`, isError: true };
348
- const { text: body, truncated } = await readCapped(res);
349
- const text = (res.headers.get("content-type") ?? "").includes("html") ? htmlToText(body) : body;
350
- if (!res.ok) return { output: `HTTP ${res.status} dari ${input.url}: ${text.slice(0, 500)}`, isError: true };
351
- const clipped = text.length > MAX_CHARS || truncated ? `${text.slice(0, MAX_CHARS)}
352
- \u2026 [dipotong]` : text;
353
- const display = truncated ? "lebih dari 5 MB, dipotong" : `${text.length} karakter`;
354
- return { output: clipped, display };
355
- }
356
- });
357
-
358
- // src/tools/glob.ts
359
- import fg from "fast-glob";
360
- import { z as z4 } from "zod";
361
- var globTool = defineTool({
362
- name: "glob",
363
- description: 'Find files by glob pattern, e.g. "src/**/*.ts". Respects .gitignore. Max 500 results.',
364
- schema: z4.object({
365
- pattern: z4.string().describe("Glob pattern"),
366
- path: z4.string().optional().describe("Base directory, default project root")
367
- }),
368
- kind: "read",
369
- target: (input) => input.pattern,
370
- async run(input, ctx) {
371
- const root = resolvePath(ctx.cwd, input.path ?? ".");
372
- const files = (await fg(input.pattern, { cwd: root, ignore: ignorePatterns(root), onlyFiles: true, dot: true })).sort();
373
- if (!files.length) return { output: "Tidak ada file yang cocok." };
374
- const shown = files.slice(0, 500);
375
- const more = files.length > shown.length ? `
376
- \u2026 ${files.length - shown.length} file lagi` : "";
377
- return { output: shown.join("\n") + more, display: `${files.length} file` };
378
- }
379
- });
380
-
381
- // src/tools/grep.ts
382
- import { execFile } from "child_process";
383
- import { readFile as readFile2 } from "fs/promises";
384
- import fg2 from "fast-glob";
385
- import { z as z5 } from "zod";
386
- var MAX_MATCHES = 200;
387
- async function grepJs(pattern, opts) {
388
- const regex = new RegExp(pattern, opts.ignoreCase ? "i" : "");
389
- const files = await fg2(opts.glob ?? "**/*", { cwd: opts.root, ignore: ignorePatterns(opts.root), onlyFiles: true, dot: true });
390
- const out = [];
391
- for (const file of files.sort()) {
392
- let text;
393
- try {
394
- const buffer = await readFile2(resolvePath(opts.root, file));
395
- if (buffer.subarray(0, 8e3).includes(0)) continue;
396
- text = buffer.toString("utf8");
397
- } catch {
398
- continue;
399
- }
400
- const lines = text.split(/\r?\n/);
401
- for (let i = 0; i < lines.length && out.length < MAX_MATCHES; i++) {
402
- if (regex.test(lines[i])) out.push(`${file}:${i + 1}:${lines[i].slice(0, 300)}`);
403
- }
404
- if (out.length >= MAX_MATCHES) break;
405
69
  }
406
- return out;
407
- }
408
- function grepRg(pattern, opts) {
409
- const args = ["--line-number", "--no-heading", "--color", "never", "--max-columns", "300", "-e", pattern];
410
- if (opts.ignoreCase) args.push("-i");
411
- if (opts.glob) args.push("-g", opts.glob);
412
- args.push(".");
413
- return new Promise((resolve2) => {
414
- execFile("rg", args, { cwd: opts.root, maxBuffer: 10 * 1024 * 1024 }, (error, stdout) => {
415
- const code = error?.code;
416
- if (code === "ENOENT") return resolve2(void 0);
417
- if (error && error.code !== 1) return resolve2(void 0);
418
- resolve2(stdout.split("\n").filter(Boolean).map((l) => l.replace(/^\.\//, "")).slice(0, MAX_MATCHES));
419
- });
420
- });
70
+ setLanguage(resolveLanguage(argv, process.env, fromConfig));
421
71
  }
422
- var grepTool = defineTool({
423
- name: "grep",
424
- description: "Search file contents with a regular expression. Respects .gitignore. Returns path:line:text (max 200).",
425
- schema: z5.object({
426
- pattern: z5.string().describe("Regular expression"),
427
- path: z5.string().optional().describe("Directory to search, default project root"),
428
- glob: z5.string().optional().describe('Only files matching this glob, e.g. "**/*.ts"'),
429
- ignore_case: z5.boolean().optional()
430
- }),
431
- kind: "read",
432
- target: (input) => input.pattern,
433
- async run(input, ctx) {
434
- const opts = { root: resolvePath(ctx.cwd, input.path ?? "."), glob: input.glob, ignoreCase: input.ignore_case };
435
- try {
436
- new RegExp(input.pattern);
437
- } catch (error) {
438
- return { output: `Regex tidak valid: ${error.message}`, isError: true };
439
- }
440
- const matches = await grepRg(input.pattern, opts) ?? await grepJs(input.pattern, opts);
441
- if (!matches.length) return { output: "Tidak ada yang cocok." };
442
- return { output: matches.join("\n"), display: `${matches.length} hasil` };
443
- }
444
- });
445
-
446
- // src/tools/read.ts
447
- import { readFile as readFile3 } from "fs/promises";
448
- import { z as z6 } from "zod";
449
- var MAX_LINE = 2e3;
450
- var readTool = defineTool({
451
- name: "read",
452
- description: "Read a text file. Returns numbered lines (default first 2000). Use offset/limit for large files. Always read a file before editing it.",
453
- schema: z6.object({
454
- path: z6.string().describe("File path, relative to the project or absolute"),
455
- offset: z6.number().int().min(1).optional().describe("First line to read, 1-based"),
456
- limit: z6.number().int().min(1).optional().describe("Number of lines, default 2000")
457
- }),
458
- kind: "read",
459
- target: (input) => input.path,
460
- async run(input, ctx) {
461
- const abs = resolvePath(ctx.cwd, input.path);
462
- let buffer;
463
- try {
464
- buffer = await readFile3(abs);
465
- } catch (error) {
466
- return { output: `Tidak bisa membaca ${input.path}: ${errorCode(error)}`, isError: true };
467
- }
468
- if (buffer.subarray(0, 8e3).includes(0)) {
469
- return { output: `${input.path} adalah file biner, tidak dibaca.`, isError: true };
470
- }
471
- const lines = buffer.toString("utf8").split(/\r?\n/);
472
- const start = (input.offset ?? 1) - 1;
473
- const limit = input.limit ?? 2e3;
474
- const slice = lines.slice(start, start + limit);
475
- const body = slice.map((line, i) => {
476
- const text = line.length > MAX_LINE ? `${line.slice(0, MAX_LINE)}\u2026 [baris dipotong]` : line;
477
- return `${String(start + i + 1).padStart(6)} ${text}`;
478
- }).join("\n");
479
- const remaining = lines.length - start - slice.length;
480
- ctx.readFiles.add(abs);
481
- return {
482
- output: remaining > 0 ? `${body}
483
- \u2026 ${remaining} baris lagi (pakai offset)` : body,
484
- display: `${slice.length} baris`
485
- };
486
- }
487
- });
488
-
489
- // src/tools/write.ts
490
- import { existsSync } from "fs";
491
- import { mkdir, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
492
- import { dirname } from "path";
493
- import { z as z7 } from "zod";
494
- var writeTool = defineTool({
495
- name: "write",
496
- description: "Create or overwrite a file with the given content. Read an existing file first. Prefer edit for changes.",
497
- schema: z7.object({
498
- path: z7.string().describe("File path, relative to the project or absolute"),
499
- content: z7.string().describe("Full new file content")
500
- }),
501
- kind: "edit",
502
- target: (input) => input.path,
503
- async validate(input, ctx) {
504
- const abs = resolvePath(ctx.cwd, input.path);
505
- return existsSync(abs) && !ctx.readFiles.has(abs) ? `${input.path} sudah ada. Baca dulu dengan read sebelum menimpanya.` : void 0;
506
- },
507
- async preview(input, ctx) {
508
- const abs = resolvePath(ctx.cwd, input.path);
509
- const old = existsSync(abs) ? await readFile4(abs, "utf8") : "";
510
- return formatDiff(diffLines(old, input.content));
511
- },
512
- async run(input, ctx) {
513
- const abs = resolvePath(ctx.cwd, input.path);
514
- if (existsSync(abs) && !ctx.readFiles.has(abs)) {
515
- return { output: `${input.path} sudah ada. Baca dulu dengan read sebelum menimpanya.`, isError: true };
516
- }
517
- try {
518
- await ctx.checkpoint?.(abs);
519
- await mkdir(dirname(abs), { recursive: true });
520
- await writeFile2(abs, input.content);
521
- } catch (error) {
522
- return { output: `Gagal menulis ${input.path}: ${errorCode(error)}`, isError: true };
523
- }
524
- ctx.readFiles.add(abs);
525
- const lines = input.content.split("\n").length;
526
- return { output: `Menulis ${input.path} (${lines} baris).`, display: `${lines} baris ditulis` };
527
- }
528
- });
529
-
530
- // src/tools/index.ts
531
- var ALL_TOOLS = [readTool, writeTool, editTool, bashTool, grepTool, globTool, fetchTool];
532
- function toolDefinitions(tools) {
533
- return tools.map((tool) => {
534
- const { $schema: _ignored, ...parameters } = tool.jsonSchema ?? z8.toJSONSchema(tool.schema);
535
- return { type: "function", function: { name: tool.name, description: tool.description, parameters } };
536
- });
537
- }
538
-
539
- // src/textToolCalls.ts
540
- var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
541
- var CALL_START = /<tool_call>|\n?[^\n]*\bTool call list\b[^\n]*|\n\s*\d+\.\s*Tool:/g;
542
- function coerce(value, type) {
543
- const types = Array.isArray(type) ? type : [type];
544
- const v = value.trim();
545
- if ((types.includes("integer") || types.includes("number")) && v !== "" && !Number.isNaN(Number(v))) return Number(v);
546
- if (types.includes("boolean") && /^(true|false)$/i.test(v)) return v.toLowerCase() === "true";
547
- if ((types.includes("object") || types.includes("array")) && /^[[{]/.test(v)) {
548
- try {
549
- return JSON.parse(v);
550
- } catch {
551
- }
552
- }
553
- return value.replace(/^\n+|\n+$/g, "");
554
- }
555
- function pickTool(name, keys, tools) {
556
- const named = name && tools.find((t) => t.function.name === name);
557
- if (named) return named;
558
- let best;
559
- let bestScore = 0;
560
- let tie = false;
561
- for (const t of tools) {
562
- const params = t.function.parameters;
563
- const props = Object.keys(params.properties ?? {});
564
- if (!(params.required ?? []).every((r) => keys.includes(r))) continue;
565
- const score = keys.filter((k) => props.includes(k)).length;
566
- if (score > bestScore) [best, bestScore, tie] = [t, score, false];
567
- else if (score === bestScore && score > 0) tie = true;
568
- }
569
- return tie ? void 0 : best;
570
- }
571
- function recoverTextToolCalls(text, tools) {
572
- if (!/<\/arg_value>|<tool_call>/.test(text)) return null;
573
- const starts = [...text.matchAll(CALL_START)].map((m) => m.index ?? 0);
574
- const firstPair = text.search(PAIR);
575
- const cut = Math.min(...[...starts, firstPair].filter((i) => i >= 0));
576
- const bounds = starts.filter((i) => i >= cut);
577
- if (!bounds.length || bounds[0] > cut) bounds.unshift(cut);
578
- const toolCalls = [];
579
- bounds.forEach((start, n) => {
580
- const segment = text.slice(start, bounds[n + 1] ?? text.length);
581
- const name = /<tool_call>\s*([A-Za-z_][\w.-]*)/.exec(segment)?.[1] ?? /Tool:\s*`?([A-Za-z_][\w.-]*)`?/.exec(segment)?.[1];
582
- let args = [];
583
- const flush = () => {
584
- if (!args.length) return;
585
- const keys = args.map(([k]) => k);
586
- const tool = pickTool(name, keys, tools);
587
- if (tool) {
588
- const props = tool.function.parameters.properties ?? {};
589
- const input = Object.fromEntries(args.filter(([k]) => k in props).map(([k, v]) => [k, coerce(v, props[k].type)]));
590
- toolCalls.push({ id: `text_call_${toolCalls.length}`, name: tool.function.name, arguments: JSON.stringify(input) });
591
- }
592
- args = [];
593
- };
594
- for (const m of segment.matchAll(PAIR)) {
595
- if (args.some(([k]) => k === m[1])) flush();
596
- args.push([m[1], m[2]]);
597
- }
598
- flush();
599
- });
600
- if (!toolCalls.length) return null;
601
- return { text: text.slice(0, cut).trim(), toolCalls };
602
- }
603
-
604
- // src/agent.ts
605
- var estimateTokens = (text) => Math.ceil(text.length / 4);
606
- var COMPACT_PROMPT = "Summarize the conversation so far for yourself so you can continue the work with no other context. Include: the user goals, decisions made, files touched and their current state, commands run and results, the task in progress and the next step. Be concise but complete.";
607
- var Agent = class {
608
- constructor(opts) {
609
- this.opts = opts;
610
- this.provider = opts.provider;
611
- this.permissions = opts.permissions;
612
- this.tools = opts.tools;
613
- this.definitions = toolDefinitions(opts.tools);
614
- this.turnTools = this.tools;
615
- this.turnDefinitions = this.definitions;
616
- this.messages = [...opts.history ?? []];
617
- this.maxSteps = opts.maxSteps ?? 50;
618
- this.contextWindow = opts.contextWindow ?? 128e3;
619
- this.systemPrompt = opts.systemPrompt;
620
- this.reasoning = opts.reasoning ?? "auto";
621
- }
622
- opts;
623
- provider;
624
- permissions;
625
- messages;
626
- totalUsage = { inputTokens: 0, outputTokens: 0 };
627
- lastInputTokens = 0;
628
- onEvent = () => {
629
- };
630
- reasoning;
631
- askPermission = async () => "no";
632
- tools;
633
- definitions;
634
- // Snapshot for the running turn: tools added/removed mid-turn (e.g. an MCP server connecting) apply from the next turn.
635
- turnTools;
636
- turnDefinitions;
637
- readFiles = /* @__PURE__ */ new Set();
638
- maxSteps;
639
- // ponytail: one window for every model; read it from /v1/models metadata when providers expose it.
640
- contextWindow;
641
- systemPrompt;
642
- setSystemPrompt(text) {
643
- this.systemPrompt = text;
644
- }
645
- setTools(tools) {
646
- this.tools = tools;
647
- this.definitions = toolDefinitions(tools);
648
- }
649
- isParallelSafe(call) {
650
- const tool = this.turnTools.find((t) => t.name === call.name);
651
- if (!tool?.parallelSafe) return false;
652
- try {
653
- const parsed = tool.schema.safeParse(JSON.parse(call.arguments || "{}"));
654
- return parsed.success && tool.parallelSafe(parsed.data);
655
- } catch {
656
- return false;
657
- }
658
- }
659
- push(message) {
660
- this.messages.push(message);
661
- this.opts.onMessage?.(message);
662
- }
663
- /** Swaps in another conversation (resume, new session) without writing to the session log. */
664
- load(messages) {
665
- this.messages = messages;
666
- this.readFiles.clear();
667
- this.lastInputTokens = 0;
668
- }
669
- clear() {
670
- this.messages = [];
671
- this.readFiles.clear();
672
- this.lastInputTokens = 0;
673
- this.opts.onReset?.();
674
- }
675
- async compact(signal) {
676
- const completion = await this.provider.chat({
677
- messages: [{ role: "system", content: this.systemPrompt }, ...this.messages, { role: "user", content: COMPACT_PROMPT }],
678
- // Some gateways reject tool_calls in history when no tools are declared.
679
- tools: this.definitions,
680
- signal,
681
- reasoning: this.reasoning
682
- });
683
- this.clear();
684
- this.push({ role: "user", content: `Ringkasan percakapan sebelumnya:
685
- ${completion.text}` });
686
- this.push({ role: "assistant", content: "Oke, saya lanjutkan dari ringkasan ini." });
687
- this.onEvent({ type: "compacted" });
688
- }
689
- async run(text, signal) {
690
- this.turnTools = this.tools;
691
- this.turnDefinitions = this.definitions;
692
- this.opts.onTurnStart?.();
693
- try {
694
- if (this.lastInputTokens > this.contextWindow * 0.8) await this.compact(signal);
695
- this.push({ role: "user", content: text });
696
- for (let step = 0; step < this.maxSteps; step++) {
697
- if (step > 0 && this.lastInputTokens > this.contextWindow * 0.8) {
698
- await this.compact(signal);
699
- this.push({ role: "user", content: `Lanjutkan tugas ini sesuai ringkasan di atas: ${text}` });
700
- }
701
- let completion = await this.provider.chat({
702
- messages: [{ role: "system", content: this.systemPrompt }, ...this.messages],
703
- tools: this.turnDefinitions,
704
- signal,
705
- onText: (delta) => this.onEvent({ type: "text", delta }),
706
- onThinking: (delta) => this.onEvent({ type: "thinking", delta }),
707
- reasoning: this.reasoning
708
- });
709
- if (!completion.toolCalls.length && completion.finishReason !== "repetition") {
710
- const recovered = recoverTextToolCalls(completion.text, this.turnDefinitions);
711
- if (recovered) {
712
- completion = { ...completion, ...recovered };
713
- this.onEvent({ type: "textReplace", text: recovered.text });
714
- }
715
- }
716
- const usage = completion.usage ?? {
717
- inputTokens: estimateTokens(this.systemPrompt) + estimateTokens(JSON.stringify(this.messages)),
718
- outputTokens: estimateTokens(completion.text) + (completion.toolCalls.length ? estimateTokens(JSON.stringify(completion.toolCalls)) : 0)
719
- };
720
- this.totalUsage.inputTokens += usage.inputTokens;
721
- this.totalUsage.outputTokens += usage.outputTokens;
722
- this.lastInputTokens = usage.inputTokens;
723
- this.onEvent({ type: "usage", ...this.totalUsage });
724
- this.push({
725
- role: "assistant",
726
- content: completion.text || null,
727
- ...completion.toolCalls.length ? {
728
- tool_calls: completion.toolCalls.map((c) => ({
729
- id: c.id,
730
- type: "function",
731
- function: { name: c.name, arguments: c.arguments }
732
- }))
733
- } : {}
734
- });
735
- if (completion.finishReason === "repetition") {
736
- this.onEvent({ type: "textReplace", text: completion.text });
737
- this.onEvent({
738
- type: "error",
739
- message: "Model terjebak mengulang teks, jawaban dihentikan. Coba ulangi atau ganti model dengan /model."
740
- });
741
- return;
742
- }
743
- if (!completion.toolCalls.length) {
744
- this.onEvent({ type: "done" });
745
- return;
746
- }
747
- const calls = completion.toolCalls;
748
- let i = 0;
749
- while (i < calls.length) {
750
- if (signal.aborted) {
751
- for (const pending of calls.slice(i)) this.push({ role: "tool", tool_call_id: pending.id, content: "Dibatalkan oleh user." });
752
- this.onEvent({ type: "aborted" });
753
- return;
754
- }
755
- let j = i + 1;
756
- if (this.isParallelSafe(calls[i])) while (j < calls.length && this.isParallelSafe(calls[j])) j++;
757
- const batch = calls.slice(i, j);
758
- const results = await Promise.all(batch.map((c) => this.runTool(c, signal)));
759
- batch.forEach((c, k) => {
760
- this.push({ role: "tool", tool_call_id: c.id, content: results[k] });
761
- });
762
- i = j;
763
- }
764
- if (signal.aborted) {
765
- this.onEvent({ type: "aborted" });
766
- return;
767
- }
768
- }
769
- this.onEvent({ type: "stepLimit" });
770
- } catch (error) {
771
- if (signal.aborted) {
772
- this.onEvent({ type: "aborted" });
773
- return;
774
- }
775
- const message = error.message;
776
- const noTools = error.status === 400 && /tool/i.test(message);
777
- this.onEvent({
778
- type: "error",
779
- message: noTools ? `${message}
780
- Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model.` : message
781
- });
782
- }
783
- }
784
- async runTool(call, signal) {
785
- const fail = (output, target2 = "") => {
786
- this.onEvent({ type: "toolStart", id: call.id, tool: call.name, target: target2 });
787
- this.onEvent({ type: "toolEnd", id: call.id, tool: call.name, output, isError: true });
788
- return output;
789
- };
790
- const tool = this.turnTools.find((t) => t.name === call.name);
791
- if (!tool) return fail(`Alat "${call.name}" tidak ada. Alat yang tersedia: ${this.turnTools.map((t) => t.name).join(", ")}`);
792
- let args;
793
- try {
794
- args = JSON.parse(call.arguments || "{}");
795
- } catch {
796
- return fail(`Argumen bukan JSON valid: ${call.arguments.slice(0, 200)}`);
797
- }
798
- const parsed = tool.schema.safeParse(args);
799
- if (!parsed.success) {
800
- return fail(`Argumen tidak valid: ${parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
801
- }
802
- const input = parsed.data;
803
- const target = tool.target(input);
804
- const ctx = {
805
- cwd: this.opts.cwd,
806
- signal,
807
- readFiles: this.readFiles,
808
- callId: call.id,
809
- emit: (event) => this.onEvent(event),
810
- ask: (req) => this.askPermission(req),
811
- checkpoint: this.opts.checkpoint,
812
- addUsage: (u) => {
813
- this.totalUsage.inputTokens += u.inputTokens;
814
- this.totalUsage.outputTokens += u.outputTokens;
815
- this.onEvent({ type: "usage", ...this.totalUsage });
816
- }
817
- };
818
- this.onEvent({ type: "toolStart", id: call.id, tool: tool.name, target });
819
- const invalid = await tool.validate?.(input, ctx).catch((error) => error.message);
820
- if (invalid) {
821
- this.onEvent({ type: "toolEnd", id: call.id, tool: tool.name, output: invalid, isError: true });
822
- return invalid;
823
- }
824
- const request = { tool: tool.name, kind: tool.kind, target };
825
- let decision = this.permissions.check(request);
826
- if (decision === "ask") {
827
- const preview = await tool.preview?.(input, ctx).catch(() => void 0);
828
- const answer = await this.askPermission({
829
- ...request,
830
- preview,
831
- sessionRules: this.permissions.rulesFor(request),
832
- ...this.opts.label ? { agent: this.opts.label } : {}
833
- });
834
- if (answer === "session") this.permissions.allowForSession(request);
835
- if (answer === "all") this.permissions.mode = "allowAll";
836
- decision = answer === "no" ? "deny" : "allow";
837
- }
838
- if (decision === "deny") {
839
- const output = this.permissions.mode === "plan" ? "Ditolak: mode plan hanya boleh membaca dan mencari. Susun rencana untuk user tanpa mengubah apa pun." : "User menolak menjalankan alat ini. Tanyakan ke user apa yang diinginkan, atau coba cara lain.";
840
- this.onEvent({ type: "toolEnd", id: call.id, tool: tool.name, output, isError: true });
841
- return output;
842
- }
843
- let result;
844
- try {
845
- result = await tool.run(input, ctx);
846
- } catch (error) {
847
- result = { output: `Error: ${error.message}`, isError: true };
848
- }
849
- this.onEvent({ type: "toolEnd", id: call.id, tool: tool.name, output: result.output, display: result.display, isError: !!result.isError });
850
- return result.output;
851
- }
852
- };
853
-
854
- // src/checkpoints.ts
855
- import { existsSync as existsSync2, statSync } from "fs";
856
- import { readFile as readFile5, rm, writeFile as writeFile3 } from "fs/promises";
857
- var MAX_TURNS = 20;
858
- var MAX_BYTES2 = 2 * 1024 * 1024;
859
- var CheckpointStore = class {
860
- history = [];
861
- current;
862
- beginTurn() {
863
- if (this.current?.size === 0) return;
864
- this.current = /* @__PURE__ */ new Map();
865
- this.history.push(this.current);
866
- }
867
- /** Call before writing a file; only the first snapshot per file per turn is kept. */
868
- async snapshot(path) {
869
- if (!this.current) this.beginTurn();
870
- const turn = this.current;
871
- if (turn.has(path)) return;
872
- if (!existsSync2(path)) turn.set(path, null);
873
- else if (statSync(path).size > MAX_BYTES2) turn.set(path, "skipped");
874
- else turn.set(path, await readFile5(path));
875
- while (this.turns() > MAX_TURNS) this.history.shift();
876
- }
877
- turns() {
878
- return this.history.filter((t) => t.size).length;
879
- }
880
- async undo() {
881
- while (this.history.length && !this.history.at(-1)?.size) this.history.pop();
882
- const turn = this.history.pop();
883
- this.current = void 0;
884
- if (!turn) return void 0;
885
- const result = { restored: [], deleted: [], skipped: [], failed: [] };
886
- for (const [path, original] of turn) {
887
- try {
888
- if (original === "skipped") result.skipped.push(path);
889
- else if (original === null) {
890
- await rm(path, { force: true });
891
- result.deleted.push(path);
892
- } else {
893
- await writeFile3(path, original);
894
- result.restored.push(path);
895
- }
896
- } catch {
897
- result.failed.push(path);
898
- }
899
- }
900
- return result;
901
- }
902
- clear() {
903
- this.history.length = 0;
904
- this.current = void 0;
905
- }
906
- };
907
-
908
- // src/mcp/manager.ts
909
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
910
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
911
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
912
- import { z as z9 } from "zod";
913
- function mcpToolName(server, tool, taken) {
914
- const clean2 = (s) => s.replace(/[^A-Za-z0-9_-]+/g, "_");
915
- const base = `mcp__${clean2(server)}__${clean2(tool)}`.slice(0, 64);
916
- let name = base;
917
- for (let n = 2; taken.has(name); n++) name = `${base.slice(0, 64 - String(n).length - 1)}_${n}`;
918
- taken.add(name);
919
- return name;
920
- }
921
- function withTimeout(promise, ms) {
922
- let timer;
923
- const timeout = new Promise((_, reject) => {
924
- timer = setTimeout(() => reject(new Error(`timeout ${Math.round(ms / 1e3)} detik`)), ms);
925
- });
926
- return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
927
- }
928
- var McpManager = class {
929
- constructor(opts = {}) {
930
- this.opts = opts;
931
- }
932
- opts;
933
- servers = /* @__PURE__ */ new Map();
934
- taken = /* @__PURE__ */ new Set();
935
- async start(specs) {
936
- await Promise.all(specs.map((spec) => this.add(spec)));
937
- }
938
- async add(spec) {
939
- await this.remove(spec.name);
940
- const entry = { spec, state: { name: spec.name, source: spec.source, status: "starting", tools: 0 }, tools: [] };
941
- this.servers.set(spec.name, entry);
942
- this.opts.onChange?.();
943
- const client = new Client({ name: "bccli", version: "0.3.0" });
944
- entry.client = client;
945
- const timeoutMs = this.opts.connectTimeoutMs ?? 3e4;
946
- try {
947
- const config = spec.config;
948
- const transport = config.type === "http" ? new StreamableHTTPClientTransport(new URL(config.url), { requestInit: { headers: config.headers ?? {} } }) : new StdioClientTransport({
949
- command: config.command,
950
- args: config.args ?? [],
951
- env: { ...process.env, ...config.env },
952
- stderr: "ignore"
953
- });
954
- await withTimeout(client.connect(transport), timeoutMs);
955
- const { tools } = await withTimeout(client.listTools(), timeoutMs);
956
- if (this.servers.get(spec.name) !== entry) {
957
- await client.close().catch(() => {
958
- });
959
- return;
960
- }
961
- entry.tools = tools.map((t) => this.wrap(spec.name, client, t));
962
- entry.state = { ...entry.state, status: "ready", tools: entry.tools.length };
963
- } catch (error) {
964
- entry.state = { ...entry.state, status: "error", error: error.message };
965
- await client.close().catch(() => {
966
- });
967
- if (this.servers.get(spec.name) !== entry) return;
968
- }
969
- this.opts.onChange?.();
970
- }
971
- wrap(server, client, tool) {
972
- return {
973
- name: mcpToolName(server, tool.name, this.taken),
974
- description: `[MCP ${server}] ${tool.description ?? tool.name}`.slice(0, 1024),
975
- schema: z9.record(z9.string(), z9.unknown()),
976
- jsonSchema: tool.inputSchema,
977
- kind: "mcp",
978
- target: (input) => {
979
- const text = JSON.stringify(input);
980
- return text.length > 80 ? `${text.slice(0, 77)}\u2026` : text;
981
- },
982
- async run(input, ctx) {
983
- try {
984
- const result = await client.callTool({ name: tool.name, arguments: input }, void 0, { signal: ctx.signal });
985
- const output = (result.content ?? []).map((c) => c.type === "text" ? c.text ?? "" : `[${c.type}]`).join("\n") || "(kosong)";
986
- return { output: output.length > 5e4 ? `${output.slice(0, 5e4)}
987
- \u2026 [dipotong]` : output, isError: !!result.isError };
988
- } catch (error) {
989
- return { output: `Server MCP ${server} error: ${error.message}`, isError: true };
990
- }
991
- }
992
- };
993
- }
994
- async remove(name) {
995
- const entry = this.servers.get(name);
996
- if (!entry) return;
997
- this.servers.delete(name);
998
- for (const t of entry.tools) this.taken.delete(t.name);
999
- if (entry.tools.length) this.opts.onToolsRemoved?.(entry.tools.map((t) => t.name));
1000
- await entry.client?.close().catch(() => {
1001
- });
1002
- this.opts.onChange?.();
1003
- }
1004
- tools() {
1005
- return [...this.servers.values()].flatMap((s) => s.tools);
1006
- }
1007
- states() {
1008
- return [...this.servers.values()].map((s) => s.state);
1009
- }
1010
- async stop() {
1011
- await Promise.all([...this.servers.keys()].map((name) => this.remove(name)));
1012
- }
1013
- };
1014
-
1015
- // src/setup.ts
1016
- import { homedir } from "os";
1017
-
1018
- // src/tools/plan.ts
1019
- import { z as z10 } from "zod";
1020
- function createExitPlanTool(opts) {
1021
- return defineTool({
1022
- name: "exit_plan",
1023
- description: "Plan mode only: present the finished plan to the user for approval. If approved you may start implementing it.",
1024
- schema: z10.object({ plan: z10.string().describe("The plan, in markdown") }),
1025
- kind: "read",
1026
- target: () => "rencana",
1027
- async run(input) {
1028
- if (opts.permissions.mode !== "plan") return { output: "exit_plan hanya dipakai di mode plan.", isError: true };
1029
- const decision = await opts.interaction.approvePlan(input.plan);
1030
- if (decision === "no") {
1031
- return { output: "User belum menyetujui. Tanyakan apa yang perlu diperbaiki, perbaiki rencananya, lalu panggil exit_plan lagi." };
1032
- }
1033
- opts.permissions.mode = decision;
1034
- return { output: `Rencana disetujui. Mode sekarang ${decision}. Mulai kerjakan rencananya sekarang.` };
1035
- }
1036
- });
1037
- }
1038
-
1039
- // src/tools/skill.ts
1040
- import { readFileSync } from "fs";
1041
- import { z as z11 } from "zod";
1042
- function createSkillTool(skills) {
1043
- return defineTool({
1044
- name: "skill",
1045
- description: "Load a skill (instructions for a kind of task) by name. Use it when a listed skill matches the task, then follow it.",
1046
- schema: z11.object({ name: z11.string().describe("Skill name from the list in the system prompt") }),
1047
- kind: "read",
1048
- target: (input) => input.name,
1049
- async run(input) {
1050
- const skill = skills.find((s) => s.name === input.name);
1051
- if (!skill) return { output: `Skill "${input.name}" tidak ada. Tersedia: ${skills.map((s) => s.name).join(", ") || "(tidak ada)"}`, isError: true };
1052
- const { body } = parseFrontmatter(readFileSync(skill.file, "utf8"));
1053
- return { output: `Base directory for this skill: ${skill.dir}
1054
-
1055
- ${body}`, display: skill.description.slice(0, 60) };
1056
- }
1057
- });
1058
- }
1059
-
1060
- // src/tools/websearch.ts
1061
- import { z as z12 } from "zod";
1062
- var clean = (items, max) => (Array.isArray(items) ? items : []).filter((r) => typeof r?.url === "string" && /^https?:\/\//.test(r.url)).slice(0, max).map((r) => ({
1063
- title: String(r.title || r.url).slice(0, 200),
1064
- url: r.url,
1065
- snippet: String(r.snippet || r.description || "").replace(/\s+/g, " ").trim().slice(0, 600)
1066
- }));
1067
- async function postJson(url, body, headers, signal) {
1068
- const res = await fetch(url, {
1069
- method: "POST",
1070
- headers: { "content-type": "application/json", ...headers },
1071
- body: JSON.stringify(body),
1072
- signal: AbortSignal.any([signal, AbortSignal.timeout(3e4)])
1073
- });
1074
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1075
- return res.json();
1076
- }
1077
- function createWebSearchTool(opts) {
1078
- const keenableURL = opts.keenableURL ?? "https://api.keenable.ai/v1/search/public";
1079
- return defineTool({
1080
- name: "web_search",
1081
- description: "Search the web. Returns titles, URLs and short snippets. Use it to find the right pages instead of guessing URLs; then fetch a result URL when you need the full page.",
1082
- schema: z12.object({
1083
- query: z12.string().min(1).max(500).describe("Search query"),
1084
- max_results: z12.number().int().min(1).max(10).optional().describe("Number of results (default 6)")
1085
- }),
1086
- kind: "read",
1087
- target: (input) => input.query,
1088
- parallelSafe: () => true,
1089
- async run(input, ctx) {
1090
- const max = input.max_results ?? 6;
1091
- const errors = [];
1092
- let results = [];
1093
- const bc = opts.botconnector();
1094
- if (bc) {
1095
- try {
1096
- const data = await postJson(`${bc.baseURL.replace(/\/+$/, "")}/web/search`, { query: input.query, max_results: max }, { authorization: `Bearer ${bc.apiKey}` }, ctx.signal);
1097
- results = clean(data.results, max);
1098
- } catch (error) {
1099
- errors.push(`BotConnector: ${error.message}`);
1100
- }
1101
- }
1102
- if (!results.length) {
1103
- try {
1104
- const data = await postJson(keenableURL, { query: input.query }, { "x-keenable-title": "BotConnector" }, ctx.signal);
1105
- results = clean(data.results, max);
1106
- } catch (error) {
1107
- errors.push(`Keenable: ${error.message}`);
1108
- }
1109
- }
1110
- if (!results.length) {
1111
- return { output: errors.length ? `Web search gagal (${errors.join("; ")}).` : "Tidak ada hasil.", isError: errors.length > 0 };
1112
- }
1113
- const output = results.map((r, i) => `${i + 1}. ${r.title}
1114
- ${r.url}${r.snippet ? `
1115
- ${r.snippet}` : ""}`).join("\n");
1116
- return { output, display: `${results.length} hasil` };
1117
- }
1118
- });
1119
- }
1120
-
1121
- // src/tools/task.ts
1122
- import { z as z13 } from "zod";
1123
- function createTaskTool(opts) {
1124
- const byName = new Map(opts.agents.map((a) => [a.name, a]));
1125
- const list = opts.agents.map((a) => `- ${a.name}: ${a.description}`).join("\n");
1126
- return defineTool({
1127
- name: "task",
1128
- description: `Delegate a self-contained sub-task to a subagent. It starts with an empty context and returns only its final report. Available agents:
1129
- ${list}`,
1130
- schema: z13.object({
1131
- agent: z13.string().describe("Agent name"),
1132
- description: z13.string().describe("3-6 word summary shown to the user"),
1133
- prompt: z13.string().describe("Full instructions; the subagent sees nothing else")
1134
- }),
1135
- kind: "read",
1136
- target: (input) => `[${input.agent}] ${input.description}`,
1137
- // Parallel only when the resolved agent can do nothing but read (a project file may redefine "explore").
1138
- parallelSafe: (input) => {
1139
- const allowed = byName.get(input.agent)?.tools;
1140
- if (!allowed) return false;
1141
- const base = opts.baseTools();
1142
- return allowed.every((name) => base.find((t) => t.name === name)?.kind === "read");
1143
- },
1144
- async run(input, ctx) {
1145
- const def = byName.get(input.agent);
1146
- if (!def) return { output: `Agent "${input.agent}" tidak ada. Tersedia: ${[...byName.keys()].join(", ")}`, isError: true };
1147
- const base = opts.baseTools().filter((t) => t.name !== "task" && t.name !== "exit_plan");
1148
- const unknown = (def.tools ?? []).filter((n) => !base.some((t) => t.name === n));
1149
- if (unknown.length) return { output: `Agent "${def.name}": alat tidak dikenal: ${unknown.join(", ")}`, isError: true };
1150
- const allowed = def.tools;
1151
- const tools = allowed ? base.filter((t) => allowed.includes(t.name)) : base;
1152
- let provider;
1153
- try {
1154
- provider = def.model ? opts.providerFor(def.model) : opts.provider();
1155
- } catch (error) {
1156
- return { output: `Agent "${def.name}": ${error.message}`, isError: true };
1157
- }
1158
- const child = new Agent({
1159
- provider,
1160
- tools,
1161
- permissions: opts.permissions,
1162
- systemPrompt: `${opts.systemPrompt}
1163
-
1164
- # Your role
1165
- ${def.prompt}`,
1166
- cwd: opts.cwd,
1167
- maxSteps: def.maxSteps ?? 50,
1168
- label: def.name,
1169
- reasoning: opts.reasoning?.() ?? "auto",
1170
- // Subagent edits belong to the parent's turn so /undo reverts them too.
1171
- checkpoint: ctx.checkpoint
1172
- });
1173
- let outcome;
1174
- let steps = 0;
1175
- child.onEvent = (event) => {
1176
- if (event.type === "toolStart") steps++;
1177
- if (event.type === "error" || event.type === "stepLimit" || event.type === "aborted" || event.type === "done") outcome = event;
1178
- if (event.type !== "usage" && ctx.callId) ctx.emit?.({ type: "subagent", parentId: ctx.callId, agent: def.name, event });
1179
- };
1180
- if (ctx.ask) child.askPermission = ctx.ask;
1181
- await child.run(input.prompt, ctx.signal);
1182
- ctx.addUsage?.(child.totalUsage);
1183
- const tokens = child.totalUsage.inputTokens + child.totalUsage.outputTokens;
1184
- const display = `${steps} langkah \xB7 ${tokens >= 1e3 ? `${Math.round(tokens / 1e3)}k` : tokens} token`;
1185
- if (outcome?.type === "error") return { output: `Subagent ${def.name} gagal: ${outcome.message}`, isError: true, display };
1186
- if (outcome?.type === "aborted") return { output: `Subagent ${def.name} dibatalkan.`, isError: true, display };
1187
- const last = [...child.messages].reverse().find((m) => m.role === "assistant" && m.content);
1188
- const report = last && typeof last.content === "string" ? last.content : "(tidak ada laporan)";
1189
- const suffix = outcome?.type === "stepLimit" ? `
1190
-
1191
- (Subagent berhenti di batas ${def.maxSteps ?? 50} langkah.)` : "";
1192
- return { output: report + suffix, display };
1193
- }
1194
- });
1195
- }
1196
-
1197
- // src/tools/todo.ts
1198
- import { z as z14 } from "zod";
1199
- var TodoStore = class {
1200
- items = [];
1201
- listeners = /* @__PURE__ */ new Set();
1202
- set(items) {
1203
- this.items = items;
1204
- for (const fn of this.listeners) fn(items);
1205
- }
1206
- subscribe(fn) {
1207
- this.listeners.add(fn);
1208
- return () => this.listeners.delete(fn);
1209
- }
1210
- };
1211
- function createTodoTool(store) {
1212
- return defineTool({
1213
- name: "todo_write",
1214
- description: "Write the task list for multi-step work (3+ steps). Send the whole list every time; exactly one item in_progress while working. Mark items completed as soon as they are done.",
1215
- schema: z14.object({
1216
- todos: z14.array(z14.object({ content: z14.string(), status: z14.enum(["pending", "in_progress", "completed"]) }))
1217
- }),
1218
- kind: "read",
1219
- target: (input) => `${input.todos.length} item`,
1220
- async run(input) {
1221
- if (input.todos.filter((t) => t.status === "in_progress").length > 1) {
1222
- return { output: "Hanya boleh satu item in_progress.", isError: true };
1223
- }
1224
- store.set(input.todos);
1225
- const done = input.todos.filter((t) => t.status === "completed").length;
1226
- return { output: `Todo diperbarui: ${done}/${input.todos.length} selesai.` };
1227
- }
1228
- });
1229
- }
1230
-
1231
- // src/setup.ts
1232
- var TOOL_RULES = { bash: "bash", edit: "edit", write: "edit", fetch: "fetch" };
1233
- function createRuntime(opts) {
1234
- const env = opts.env ?? process.env;
1235
- const home = bccliHome(env);
1236
- let config = loadConfig(opts.cwd, env);
1237
- let modelRef = opts.args.model ?? config.model;
1238
- let reasoning = opts.args.reasoning ?? config.reasoning;
1239
- const makeProvider = (ref) => {
1240
- const resolved = resolveModel(config, ref, env);
1241
- return opts.provider ?? createProvider({ baseURL: resolved.baseURL, apiKey: resolved.apiKey, model: resolved.model, providerId: resolved.providerId });
1242
- };
1243
- const provider = makeProvider(modelRef);
1244
- const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
1245
- const rules = [...config.allow, ...opts.args.allowedTools.map((t) => TOOL_RULES[t] ?? t)];
1246
- const permissions = new Permissions(mode, rules, opts.cwd);
1247
- const roots = { cwd: opts.cwd, home, userHome: opts.userHome ?? homedir() };
1248
- const skills = loadSkills(roots);
1249
- const commands = loadCommands(roots);
1250
- const agentDefs = loadAgentDefs(roots);
1251
- const todos = new TodoStore();
1252
- const checkpoints = new CheckpointStore();
1253
- let startedAt = /* @__PURE__ */ new Date();
1254
- const interaction = { approvePlan: async () => "no" };
1255
- let session = Session.create(home, opts.cwd);
1256
- let history = opts.history;
1257
- if (!history && opts.args.continue) {
1258
- const latest = Session.latest(home, opts.cwd);
1259
- if (latest) {
1260
- session = latest;
1261
- history = latest.load();
1262
- }
1263
- }
1264
- const systemPrompt = buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills });
1265
- const webSearch = createWebSearchTool({
1266
- botconnector: () => {
1267
- try {
1268
- const bc = resolveModel(config, "bc-cloud/search", env);
1269
- return bc.apiKey ? { baseURL: bc.baseURL, apiKey: bc.apiKey } : void 0;
1270
- } catch {
1271
- return void 0;
1272
- }
1273
- }
1274
- });
1275
- const baseTools = [...ALL_TOOLS, webSearch, createSkillTool(skills), createTodoTool(todos), createExitPlanTool({ permissions, interaction })];
1276
- const task = createTaskTool({
1277
- agents: [...BUILTIN_AGENTS, ...agentDefs],
1278
- // Subagents get whatever the main agent has right now (incl. MCP tools), minus task itself.
1279
- baseTools: () => agent.tools.filter((t) => t.name !== "task"),
1280
- permissions,
1281
- provider: () => agent.provider,
1282
- providerFor: makeProvider,
1283
- systemPrompt,
1284
- cwd: opts.cwd,
1285
- reasoning: () => reasoning
1286
- });
1287
- const agent = new Agent({
1288
- provider,
1289
- tools: [...baseTools, task],
1290
- permissions,
1291
- systemPrompt,
1292
- cwd: opts.cwd,
1293
- history,
1294
- reasoning,
1295
- onMessage: (m) => session.append(m),
1296
- onReset: () => session.reset(),
1297
- onTurnStart: () => checkpoints.beginTurn(),
1298
- checkpoint: (path) => checkpoints.snapshot(path)
1299
- });
1300
- const mcp = new McpManager({
1301
- onChange: () => agent.setTools([...baseTools, task, ...mcp.tools()]),
1302
- // A later server may reuse a sanitized name; it must not inherit this one's "[a]" grants.
1303
- onToolsRemoved: (names) => {
1304
- for (const name of names) permissions.revoke(`mcp(${name})`);
1305
- }
1306
- });
1307
- return {
1308
- cwd: opts.cwd,
1309
- home,
1310
- env,
1311
- skills,
1312
- commands,
1313
- agentDefs,
1314
- todos,
1315
- interaction,
1316
- mcp,
1317
- startMcp: (specs) => mcp.start(specs),
1318
- get config() {
1319
- return config;
1320
- },
1321
- reloadConfig() {
1322
- config = loadConfig(opts.cwd, env);
1323
- },
1324
- providerLabel() {
1325
- return providerName(config, modelRef.slice(0, modelRef.indexOf("/")));
1326
- },
1327
- listModels: (only) => listAllModels(config, env, { fetch: opts.fetch, only }),
1328
- addProviderKey(id, key) {
1329
- return addProviderKey(id, key, { env, cwd: opts.cwd, out: () => {
1330
- }, err: () => {
1331
- }, readSecret: async () => "", fetch: opts.fetch });
1332
- },
1333
- get modelRef() {
1334
- return modelRef;
1335
- },
1336
- get reasoning() {
1337
- return reasoning;
1338
- },
1339
- agent,
1340
- get session() {
1341
- return session;
1342
- },
1343
- resume(previous) {
1344
- session = previous;
1345
- agent.load(previous.load());
1346
- agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
1347
- todos.set([]);
1348
- checkpoints.clear();
1349
- },
1350
- newSession() {
1351
- session = Session.create(home, opts.cwd);
1352
- agent.load([]);
1353
- agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
1354
- todos.set([]);
1355
- checkpoints.clear();
1356
- startedAt = /* @__PURE__ */ new Date();
1357
- },
1358
- rebuildSystemPrompt() {
1359
- agent.setSystemPrompt(buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills }));
1360
- },
1361
- checkpoints,
1362
- get startedAt() {
1363
- return startedAt;
1364
- },
1365
- setModel(ref) {
1366
- agent.provider = makeProvider(ref);
1367
- modelRef = ref;
1368
- },
1369
- setReasoning(level) {
1370
- reasoning = level;
1371
- agent.reasoning = level;
1372
- }
1373
- };
1374
- }
1375
-
1376
- // src/version.ts
1377
- var VERSION = true ? "0.4.0-beta.8" : "0.0.0-dev";
1378
-
1379
- // src/cli.ts
1380
72
  async function main() {
73
+ chooseLanguage(process.argv.slice(2));
1381
74
  const args = parseCliArgs(process.argv.slice(2));
1382
75
  if (args.version) {
1383
76
  console.log(VERSION);
1384
77
  return 0;
1385
78
  }
1386
79
  if (args.help) {
1387
- console.log(HELP_TEXT);
80
+ console.log(helpText());
1388
81
  return 0;
1389
82
  }
1390
- const cwd = process.cwd();
1391
- if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
83
+ let cwd = process.cwd();
84
+ if (args.worktree) cwd = (await resolveWorktree(cwd, args.worktree)).path;
85
+ if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd, args, defaultLoginDeps());
1392
86
  if (args.command === "mcp") {
1393
- const { runMcpCommand } = await import("./mcpCli-DZWQPTOS.js");
1394
- const { readSecret } = await import("./login-JXFORO3J.js");
87
+ const { runMcpCommand } = await import("./mcpCli-QUV5SD64.js");
88
+ const { readSecret } = await import("./login-SYIQP46J.js");
1395
89
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1396
90
  }
1397
91
  if (args.command === "provider") {
1398
- const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
1399
- const { readSecret } = await import("./login-JXFORO3J.js");
92
+ const { runProviderCommand } = await import("./providerCli-FAOZHOKW.js");
93
+ const { readSecret } = await import("./login-SYIQP46J.js");
1400
94
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1401
95
  }
96
+ if (args.command === "connect" || args.command === "disconnect" || args.command === "integrations") {
97
+ const { runIntegrationCommand } = await import("./integrationCli-P75R2VSC.js");
98
+ return runIntegrationCommand(args, {
99
+ env: process.env,
100
+ cwd,
101
+ out: (s) => console.log(s),
102
+ err: (s) => console.error(s),
103
+ fetch: globalThis.fetch
104
+ });
105
+ }
1402
106
  if (args.command === "models") {
1403
- const config = loadConfig(cwd);
1404
- const resolved = resolveModel(config, args.model ?? config.model);
1405
- const models = await createProvider(resolved).listModels();
1406
- console.log(models.map((m) => `${resolved.providerId}/${m}`).join("\n"));
107
+ const { runModelsCommand } = await import("./modelsCli-TRRB5LX4.js");
108
+ return runModelsCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), fetch: globalThis.fetch });
109
+ }
110
+ if (args.command === "update") {
111
+ const { runUpdateCommand } = await import("./update-K5IQ2MYR.js");
112
+ return runUpdateCommand({ env: process.env });
113
+ }
114
+ if (args.command === "acp") {
115
+ const { runtimeAgent, runAcp } = await import("./acp-ATIL4JOZ.js");
116
+ await runAcp(process.stdin, process.stdout, { cwd, newAgent: (c) => runtimeAgent(c, args.allowAll) });
117
+ return 0;
118
+ }
119
+ if (args.command === "serve") {
120
+ const { startServe } = await import("./serve-JG7RLIUD.js");
121
+ const handle = await startServe({ port: args.port, host: args.host, token: args.token, cwd, env: process.env });
122
+ process.stderr.write(`bccli serve ${handle.url} \u2014 Authorization: Bearer ${handle.token}
123
+ `);
124
+ await new Promise((resolve) => {
125
+ const stop = () => {
126
+ void handle.close().finally(resolve);
127
+ };
128
+ process.once("SIGINT", stop);
129
+ process.once("SIGTERM", stop);
130
+ });
1407
131
  return 0;
1408
132
  }
133
+ if (args.command === "plugins") {
134
+ const { runPluginsCommand } = await import("./pluginsCli-GNW4TPVV.js");
135
+ const { readGlobalPlugins, updateGlobalConfig } = await import("./config-RZ5JONMX.js");
136
+ const { runNpmSync } = await import("./npmRunner-LU43GM3W.js");
137
+ return runPluginsCommand(args, {
138
+ out: (s) => console.log(s),
139
+ err: (s) => console.error(s),
140
+ env: process.env,
141
+ runNpm: async (prefix, argv) => runNpmSync(prefix, argv),
142
+ readPlugins: () => readGlobalPlugins(process.env),
143
+ savePlugins: (entries) => updateGlobalConfig(process.env, (cfg) => {
144
+ cfg.plugins = entries;
145
+ })
146
+ });
147
+ }
148
+ if (args.command === "gh-bot") {
149
+ const { runGhBotCommand } = await import("./ghBotCli-IBCZIWT5.js");
150
+ return runGhBotCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s) });
151
+ }
152
+ if (args.command === "completion") {
153
+ const { runCompletionCommand } = await import("./completion-HPPFDAPB.js");
154
+ return runCompletionCommand(args, { out: (s) => console.log(s), err: (s) => console.error(s) });
155
+ }
156
+ if (args.command === "stats") {
157
+ const { runStatsCommand } = await import("./statsCli-CF3R7ATL.js");
158
+ const { bccliHome } = await import("./config-RZ5JONMX.js");
159
+ return runStatsCommand(
160
+ { subArgs: args.subArgs, home: bccliHome(process.env), cwd, days: args.days, tools: args.tools, models: args.models, project: args.project },
161
+ { out: (s) => console.log(s), err: (s) => console.error(s) }
162
+ );
163
+ }
164
+ if (args.command === "export" || args.command === "import") {
165
+ const { runExportCommand, runImportCommand } = await import("./transferCli-TK2GSVRK.js");
166
+ const { bccliHome } = await import("./config-RZ5JONMX.js");
167
+ const deps = { subArgs: args.subArgs, home: bccliHome(process.env), cwd };
168
+ const io = { out: (s) => console.log(s), err: (s) => console.error(s) };
169
+ return args.command === "export" ? runExportCommand(deps, io) : runImportCommand(deps, io);
170
+ }
171
+ if (args.command === "pr") {
172
+ const { runPrCommand } = await import("./prCli-5IMV3VNQ.js");
173
+ return runPrCommand(
174
+ { subArgs: args.subArgs, cwd, allowAll: args.allowAll },
175
+ { out: (s) => console.log(s), err: (s) => console.error(s) }
176
+ );
177
+ }
1409
178
  const rt = createRuntime({ cwd, args });
179
+ setTheme(rt.config.theme);
1410
180
  try {
1411
- pruneSessions(rt.home);
181
+ if (rt.config.sessionRetentionDays) pruneSessions(rt.home, Date.now(), rt.config.sessionRetentionDays);
1412
182
  } catch {
1413
183
  }
1414
184
  if (args.print) {
1415
- if (!args.prompt) throw new ConfigError('Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"');
1416
- const { serversToStart } = await import("./config-YUNKPCNB.js");
185
+ if (!args.prompt) throw new ConfigError(t('-p mode needs a task, e.g. bccli -p "explain this repo"'));
186
+ const { serversToStart } = await import("./config-COPNMMV6.js");
1417
187
  const plan = serversToStart(rt.home, cwd);
1418
188
  for (const s of plan.needTrust) {
1419
- console.error(`Server MCP project "${s.name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).`);
189
+ console.error(t('Project MCP server "{name}" skipped (not approved yet; run interactive bccli once to approve it).', { name: s.name }));
1420
190
  }
1421
191
  await rt.startMcp(plan.start);
1422
192
  try {
1423
- return await runPrint(rt, args.prompt, void 0, { allowAll: args.allowAll });
193
+ return await runPrint(rt, args.prompt, void 0, { allowAll: args.allowAll, outputFormat: args.outputFormat });
1424
194
  } finally {
1425
195
  await rt.mcp.stop();
1426
196
  }
1427
197
  }
1428
- if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
1429
- const { startInteractive } = await import("./ui-6GTQUJOE.js");
1430
- return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
198
+ if (!process.stdin.isTTY) throw new ConfigError(t('Interactive mode needs a terminal. For scripts/CI use: bccli -p "task"'));
199
+ let updateMessage;
200
+ if (rt.config.updateCheck !== "off" && !process.env.BCCLI_NO_UPDATE_CHECK) {
201
+ try {
202
+ const { checkForUpdate } = await import("./update-K5IQ2MYR.js");
203
+ const update = await checkForUpdate({ home: rt.home, fetch: globalThis.fetch, now: /* @__PURE__ */ new Date() });
204
+ updateMessage = update;
205
+ } catch {
206
+ }
207
+ }
208
+ const { startInteractive } = await import("./ui-B3CJ6TGE.js");
209
+ return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION, updateMessage });
1431
210
  }
1432
211
  main().then(
1433
- (code) => process.exit(code),
212
+ (code) => settleExit(code),
1434
213
  (error) => {
1435
214
  console.error(error instanceof ConfigError || error?.code === "ERR_PARSE_ARGS_UNKNOWN_OPTION" ? error.message : error);
1436
- process.exit(1);
215
+ settleExit(1);
1437
216
  }
1438
217
  );