@botconnector/bccli 0.4.0-beta.14 → 0.4.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,11 +8,11 @@ import {
8
8
  loadSkills,
9
9
  parseFrontmatter,
10
10
  runPrint
11
- } from "./chunk-3MWAQJE7.js";
11
+ } from "./chunk-K5J2GGXT.js";
12
12
  import {
13
- HELP_TEXT,
13
+ helpText,
14
14
  parseCliArgs
15
- } from "./chunk-TRMRDQ43.js";
15
+ } from "./chunk-6AVRFRFU.js";
16
16
  import {
17
17
  Permissions
18
18
  } from "./chunk-ELML4KVQ.js";
@@ -20,14 +20,15 @@ import {
20
20
  addProviderKey,
21
21
  createProvider,
22
22
  listAllModels
23
- } from "./chunk-U7GK73CK.js";
23
+ } from "./chunk-RY7NCJXH.js";
24
24
  import {
25
- providerName
26
- } from "./chunk-7S6JXQCH.js";
27
- import "./chunk-CLN4Z5E3.js";
25
+ providerName,
26
+ readGlobalConfigFile
27
+ } from "./chunk-D7CF42EW.js";
28
+ import "./chunk-VEZFR7M7.js";
28
29
  import {
29
30
  runLogin
30
- } from "./chunk-6BLZELE6.js";
31
+ } from "./chunk-4KMOCORM.js";
31
32
  import {
32
33
  Session,
33
34
  pruneSessions
@@ -36,8 +37,11 @@ import {
36
37
  ConfigError,
37
38
  bccliHome,
38
39
  loadConfig,
39
- resolveModel
40
- } from "./chunk-2MGINW4O.js";
40
+ resolveLanguage,
41
+ resolveModel,
42
+ setLanguage,
43
+ t
44
+ } from "./chunk-DDA5TJ7T.js";
41
45
 
42
46
  // src/textToolCalls.ts
43
47
  var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
@@ -56,17 +60,17 @@ function coerce(value, type) {
56
60
  return value.replace(/^\n+|\n+$/g, "");
57
61
  }
58
62
  function pickTool(name, keys, tools) {
59
- const named = name && tools.find((t) => t.function.name === name);
63
+ const named = name && tools.find((t2) => t2.function.name === name);
60
64
  if (named) return named;
61
65
  let best;
62
66
  let bestScore = 0;
63
67
  let tie = false;
64
- for (const t of tools) {
65
- const params = t.function.parameters;
68
+ for (const t2 of tools) {
69
+ const params = t2.function.parameters;
66
70
  const props = Object.keys(params.properties ?? {});
67
71
  if (!(params.required ?? []).every((r) => keys.includes(r))) continue;
68
72
  const score = keys.filter((k) => props.includes(k)).length;
69
- if (score > bestScore) [best, bestScore, tie] = [t, score, false];
73
+ if (score > bestScore) [best, bestScore, tie] = [t2, score, false];
70
74
  else if (score === bestScore && score > 0) tie = true;
71
75
  }
72
76
  return tie ? void 0 : best;
@@ -202,7 +206,7 @@ function runCommand(command, opts) {
202
206
  clearTimeout(timer);
203
207
  opts.signal.removeEventListener("abort", onAbort);
204
208
  const output = dropped ? `${head}
205
- \u2026 [${dropped} karakter dipotong] \u2026
209
+ \u2026 [${dropped} characters truncated] \u2026
206
210
  ${tail}` : head + tail;
207
211
  resolve2({ output: output + extra, exitCode, timedOut, aborted });
208
212
  };
@@ -221,7 +225,7 @@ var bashTool = defineTool({
221
225
  target: (input) => input.command,
222
226
  async run(input, ctx) {
223
227
  const r = await runCommand(input.command, { cwd: ctx.cwd, timeoutMs: input.timeout_ms ?? 12e4, signal: ctx.signal });
224
- const status = r.timedOut ? `[dihentikan: timeout ${Math.round((input.timeout_ms ?? 12e4) / 1e3)} detik]` : r.aborted ? "[dibatalkan user]" : `[exit code ${r.exitCode}]`;
228
+ const status = r.timedOut ? `[stopped: timeout after ${Math.round((input.timeout_ms ?? 12e4) / 1e3)} seconds]` : r.aborted ? "[cancelled by the user]" : `[exit code ${r.exitCode}]`;
225
229
  return { output: `${r.output.trimEnd()}
226
230
  ${status}`.trimStart(), isError: r.exitCode !== 0 };
227
231
  }
@@ -280,7 +284,7 @@ function countOccurrences(haystack, needle) {
280
284
  }
281
285
  function applyEdit(content, input) {
282
286
  if (input.old_string === input.new_string) {
283
- return { output: "old_string dan new_string sama; tidak ada perubahan.", isError: true };
287
+ return { output: "old_string and new_string are identical; nothing to change.", isError: true };
284
288
  }
285
289
  const eol = content.includes("\r\n") ? "\r\n" : "\n";
286
290
  const toEol = (s) => s.replace(/\r?\n/g, eol);
@@ -291,13 +295,13 @@ function applyEdit(content, input) {
291
295
  if (count === 0) continue;
292
296
  if (count > 1 && !input.replace_all) {
293
297
  return {
294
- output: `old_string muncul ${count} kali di ${input.path}. Tambah konteks agar unik, atau pakai replace_all.`,
298
+ output: `old_string appears ${count} times in ${input.path}. Add more context to make it unique, or use replace_all.`,
295
299
  isError: true
296
300
  };
297
301
  }
298
302
  return input.replace_all ? content.split(oldText).join(newText) : content.replace(oldText, () => newText);
299
303
  }
300
- return { output: `old_string tidak ditemukan di ${input.path}. Baca ulang file-nya.`, isError: true };
304
+ return { output: `old_string not found in ${input.path}. Re-read the file.`, isError: true };
301
305
  }
302
306
  var editTool = defineTool({
303
307
  name: "edit",
@@ -307,12 +311,12 @@ var editTool = defineTool({
307
311
  target: (input) => input.path,
308
312
  async validate(input, ctx) {
309
313
  const abs = resolvePath(ctx.cwd, input.path);
310
- if (!ctx.readFiles.has(abs)) return `Baca ${input.path} dulu dengan read sebelum mengedit.`;
314
+ if (!ctx.readFiles.has(abs)) return `Read ${input.path} with read before editing it.`;
311
315
  let content;
312
316
  try {
313
317
  content = await readFile(abs, "utf8");
314
318
  } catch (error) {
315
- return `Tidak bisa membaca ${input.path}: ${errorCode(error)}`;
319
+ return `Cannot read ${input.path}: ${errorCode(error)}`;
316
320
  }
317
321
  const next = applyEdit(content, input);
318
322
  return typeof next === "string" ? void 0 : next.output;
@@ -326,19 +330,19 @@ var editTool = defineTool({
326
330
  async run(input, ctx) {
327
331
  const abs = resolvePath(ctx.cwd, input.path);
328
332
  if (!ctx.readFiles.has(abs)) {
329
- return { output: `Baca ${input.path} dulu dengan read sebelum mengedit.`, isError: true };
333
+ return { output: `Read ${input.path} with read before editing it.`, isError: true };
330
334
  }
331
335
  let content;
332
336
  try {
333
337
  content = await readFile(abs, "utf8");
334
338
  } catch (error) {
335
- return { output: `Tidak bisa membaca ${input.path}: ${errorCode(error)}`, isError: true };
339
+ return { output: `Cannot read ${input.path}: ${errorCode(error)}`, isError: true };
336
340
  }
337
341
  const next = applyEdit(content, input);
338
342
  if (typeof next !== "string") return next;
339
343
  await ctx.checkpoint?.(abs);
340
344
  await writeFile(abs, next);
341
- return { output: `Mengedit ${input.path}.`, display: formatDiff(diffLines(content, next)) };
345
+ return { output: `Edited ${input.path}.`, display: formatDiff(diffLines(content, next)) };
342
346
  }
343
347
  });
344
348
 
@@ -375,13 +379,13 @@ function describeFetchError(error) {
375
379
  const cause = error.cause;
376
380
  const code = cause?.code ?? "";
377
381
  const detail = cause?.message ?? error.message;
378
- if (error.name === "TimeoutError") return "tidak ada jawaban dalam 30 detik";
379
- if (code === "ENOTFOUND" || code === "EAI_AGAIN") return `${detail} \u2014 DNS tidak menemukan host (salah ketik, offline, atau diblokir DNS/ISP)`;
382
+ if (error.name === "TimeoutError") return "no response within 30 seconds";
383
+ if (code === "ENOTFOUND" || code === "EAI_AGAIN") return `${detail} \u2014 DNS could not find the host (typo, offline, or blocked by DNS/ISP)`;
380
384
  if (/CERT|SELF_SIGNED|UNABLE_TO/.test(code)) {
381
- return `${detail} \u2014 sertifikat TLS ditolak; sering karena antivirus/proxy yang memeriksa HTTPS. Coba jalankan dengan NODE_OPTIONS=--use-system-ca`;
385
+ return `${detail} \u2014 TLS certificate rejected; often caused by an antivirus or proxy that inspects HTTPS. Try running with NODE_OPTIONS=--use-system-ca`;
382
386
  }
383
387
  if (/ECONNRESET|ECONNREFUSED|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|UND_ERR_CONNECT_TIMEOUT|UND_ERR_SOCKET/.test(code)) {
384
- return `${detail} \u2014 koneksi gagal (firewall, proxy, atau situs diblokir jaringan)`;
388
+ return `${detail} \u2014 connection failed (firewall, proxy, or the site is blocked by the network)`;
385
389
  }
386
390
  return code ? `${detail} (${code})` : detail;
387
391
  }
@@ -415,14 +419,14 @@ function pickRelevant(text, query, count = FOCUS_CHUNKS, size = FOCUS_SIZE) {
415
419
  const q = [...new Set(words(query))];
416
420
  const docs = chunks.map(words);
417
421
  const avg = docs.reduce((n, d) => n + d.length, 0) / (docs.length || 1) || 1;
418
- const idf = q.map((t) => {
419
- const df = docs.filter((d) => d.includes(t)).length;
422
+ const idf = q.map((t2) => {
423
+ const df = docs.filter((d) => d.includes(t2)).length;
420
424
  return Math.log(1 + (docs.length - df + 0.5) / (df + 0.5));
421
425
  });
422
426
  const scored = docs.map((d, i) => ({
423
427
  i,
424
- score: q.reduce((sum, t, k) => {
425
- const tf = d.filter((w) => w === t).length;
428
+ score: q.reduce((sum, t2, k) => {
429
+ const tf = d.filter((w) => w === t2).length;
426
430
  return sum + idf[k] * tf * 2.2 / (tf + 1.2 * (0.25 + 0.75 * d.length / avg));
427
431
  }, 0)
428
432
  })).filter((c) => c.score > 0).sort((a, b) => b.score - a.score).slice(0, count).sort((a, b) => a.i - b.i);
@@ -444,7 +448,7 @@ function recall(key) {
444
448
  return hit;
445
449
  }
446
450
  var ACCEPT = "text/markdown, text/html;q=0.9, text/plain;q=0.8, */*;q=0.5";
447
- var withCost = (text, what) => `${what} \xB7 ~${tokensOf(text)} token`;
451
+ var withCost = (text, what) => `${what} \xB7 ${t("~{n} tokens", { n: tokensOf(text) })}`;
448
452
  function createFetchTool(opts = {}) {
449
453
  const keenableURL = opts.keenableURL === void 0 ? "https://api.keenable.ai/v1/fetch/public" : opts.keenableURL;
450
454
  const isPrivate = opts.isPrivate ?? isPrivateHost;
@@ -462,7 +466,7 @@ function createFetchTool(opts = {}) {
462
466
  kind: "fetch",
463
467
  target: (input) => input.url,
464
468
  async run(input, ctx) {
465
- if (!/^https?:\/\//i.test(input.url)) return { output: "URL harus diawali http:// atau https://", isError: true };
469
+ if (!/^https?:\/\//i.test(input.url)) return { output: "URL must start with http:// or https://", isError: true };
466
470
  const target = new URL(input.url);
467
471
  const prompt = input.prompt?.trim();
468
472
  const askKeenable = !!prompt && !input.offset && !!keenableURL && !target.username && !target.password && !isPrivate(target.hostname);
@@ -473,14 +477,14 @@ ${prompt}`;
473
477
  const hit = recall(key);
474
478
  if (hit) {
475
479
  ctx.refundFetch?.();
476
- return { output: hit, display: withCost(hit, "kutipan fokus \xB7 cache") };
480
+ return { output: hit, display: withCost(hit, t("focused excerpt \xB7 cache")) };
477
481
  }
478
482
  const out = await extractWithKeenable(keenableURL, input.url, prompt, ctx.signal);
479
483
  if (out) {
480
- const output2 = `[Kutipan dari ${input.url} untuk "${prompt}" (diekstrak Keenable). Kalau jawabannya "tidak ada", coba fetch tanpa prompt.]
484
+ const output2 = `[Excerpt from ${input.url} for "${prompt}" (extracted by Keenable). If it says the answer is not there, try fetch without a prompt.]
481
485
  ${out}`;
482
486
  remember(key, output2, output2.length);
483
- return { output: output2, display: withCost(output2, "kutipan fokus (Keenable)") };
487
+ return { output: output2, display: withCost(output2, t("focused excerpt (Keenable)")) };
484
488
  }
485
489
  }
486
490
  let page = recall(`p
@@ -499,23 +503,23 @@ ${input.url}`, page, page.text.length);
499
503
  if (prompt && !input.offset) {
500
504
  const picked = pickRelevant(text, prompt);
501
505
  if (picked) {
502
- const output2 = `[Kutipan paling relevan dari ${input.url} untuk "${prompt}" (${text.length} karakter total). Halaman penuh: fetch tanpa prompt.]
506
+ const output2 = `[Most relevant excerpt from ${input.url} for "${prompt}" (${text.length} characters total). Full page: fetch without a prompt.]
503
507
  ${picked}`;
504
- return { output: output2, display: withCost(output2, `kutipan fokus (lokal)${via}`) };
508
+ return { output: output2, display: withCost(output2, `${t("focused excerpt (local)")}${via}`) };
505
509
  }
506
510
  }
507
511
  const offset = input.offset ?? 0;
508
512
  if (offset >= text.length && text.length > 0) {
509
- return { output: `offset ${offset} melewati akhir halaman (${text.length} karakter).`, isError: true };
513
+ return { output: `offset ${offset} is past the end of the page (${text.length} characters).`, isError: true };
510
514
  }
511
515
  const end = Math.min(offset + MAX_CHARS, text.length);
512
516
  let output = text.slice(offset, end);
513
517
  if (end < text.length || truncated) {
514
518
  const total = truncated ? `${text.length}+` : text.length;
515
519
  output += `
516
- \u2026 [dipotong: karakter ${offset}-${end} dari ${total}. Lanjut dengan offset=${end}, atau pakai prompt untuk kutipan terfokus.]`;
520
+ \u2026 [truncated: characters ${offset}-${end} of ${total}. Continue with offset=${end}, or use prompt for a focused excerpt.]`;
517
521
  }
518
- const size = truncated ? "lebih dari 5 MB, dipotong" : `${text.length} karakter`;
522
+ const size = truncated ? t("over 5 MB, truncated") : t("{n} characters", { n: text.length });
519
523
  return { output, display: withCost(output, `${size}${via}`) };
520
524
  }
521
525
  });
@@ -546,19 +550,19 @@ async function loadPage(inputUrl, signal) {
546
550
  const next = new URL(location, url);
547
551
  if (next.host !== url.host) {
548
552
  await res.body?.cancel();
549
- return { error: `Redirect ke host lain: ${next.href}. Panggil fetch lagi dengan URL itu kalau memang perlu (akan diminta izin).` };
553
+ return { error: `Redirect to another host: ${next.href}. Call fetch again with that URL if needed (permission will be requested).` };
550
554
  }
551
555
  await res.body?.cancel();
552
556
  url = next;
553
557
  }
554
558
  } catch (error) {
555
- return { error: `Gagal mengambil ${inputUrl}: ${describeFetchError(error)}` };
559
+ return { error: `Failed to fetch ${inputUrl}: ${describeFetchError(error)}` };
556
560
  }
557
- if (!res) return { error: `Gagal mengambil ${inputUrl}` };
561
+ if (!res) return { error: `Failed to fetch ${inputUrl}` };
558
562
  const { text: body, truncated } = await readCapped(res);
559
563
  const type = res.headers.get("content-type") ?? "";
560
564
  const text = type.includes("html") ? htmlToText(body) : body;
561
- if (!res.ok) return { error: `HTTP ${res.status} dari ${inputUrl}: ${text.slice(0, 500)}` };
565
+ if (!res.ok) return { error: `HTTP ${res.status} from ${inputUrl}: ${text.slice(0, 500)}` };
562
566
  return { text, truncated, at: Date.now() };
563
567
  }
564
568
  var fetchTool = createFetchTool();
@@ -578,10 +582,10 @@ var globTool = defineTool({
578
582
  async run(input, ctx) {
579
583
  const root = resolvePath(ctx.cwd, input.path ?? ".");
580
584
  const files = (await fg(input.pattern, { cwd: root, ignore: ignorePatterns(root), onlyFiles: true, dot: true })).sort();
581
- if (!files.length) return { output: "Tidak ada file yang cocok." };
585
+ if (!files.length) return { output: "No matching files." };
582
586
  const shown = files.slice(0, 500);
583
587
  const more = files.length > shown.length ? `
584
- \u2026 ${files.length - shown.length} file lagi` : "";
588
+ \u2026 ${files.length - shown.length} more files` : "";
585
589
  return { output: shown.join("\n") + more, display: `${files.length} file` };
586
590
  }
587
591
  });
@@ -643,11 +647,11 @@ var grepTool = defineTool({
643
647
  try {
644
648
  new RegExp(input.pattern);
645
649
  } catch (error) {
646
- return { output: `Regex tidak valid: ${error.message}`, isError: true };
650
+ return { output: `Invalid regex: ${error.message}`, isError: true };
647
651
  }
648
652
  const matches = await grepRg(input.pattern, opts) ?? await grepJs(input.pattern, opts);
649
- if (!matches.length) return { output: "Tidak ada yang cocok." };
650
- return { output: matches.join("\n"), display: `${matches.length} hasil` };
653
+ if (!matches.length) return { output: "No matches." };
654
+ return { output: matches.join("\n"), display: t("{n} results", { n: matches.length }) };
651
655
  }
652
656
  });
653
657
 
@@ -671,25 +675,25 @@ var readTool = defineTool({
671
675
  try {
672
676
  buffer = await readFile3(abs);
673
677
  } catch (error) {
674
- return { output: `Tidak bisa membaca ${input.path}: ${errorCode(error)}`, isError: true };
678
+ return { output: `Cannot read ${input.path}: ${errorCode(error)}`, isError: true };
675
679
  }
676
680
  if (buffer.subarray(0, 8e3).includes(0)) {
677
- return { output: `${input.path} adalah file biner, tidak dibaca.`, isError: true };
681
+ return { output: `${input.path} is a binary file; not read.`, isError: true };
678
682
  }
679
683
  const lines = buffer.toString("utf8").split(/\r?\n/);
680
684
  const start = (input.offset ?? 1) - 1;
681
685
  const limit = input.limit ?? 2e3;
682
686
  const slice = lines.slice(start, start + limit);
683
687
  const body = slice.map((line, i) => {
684
- const text = line.length > MAX_LINE ? `${line.slice(0, MAX_LINE)}\u2026 [baris dipotong]` : line;
688
+ const text = line.length > MAX_LINE ? `${line.slice(0, MAX_LINE)}\u2026 [line truncated]` : line;
685
689
  return `${String(start + i + 1).padStart(6)} ${text}`;
686
690
  }).join("\n");
687
691
  const remaining = lines.length - start - slice.length;
688
692
  ctx.readFiles.add(abs);
689
693
  return {
690
694
  output: remaining > 0 ? `${body}
691
- \u2026 ${remaining} baris lagi (pakai offset)` : body,
692
- display: `${slice.length} baris`
695
+ \u2026 ${remaining} more lines (use offset)` : body,
696
+ display: t("{n} lines", { n: slice.length })
693
697
  };
694
698
  }
695
699
  });
@@ -710,7 +714,7 @@ var writeTool = defineTool({
710
714
  target: (input) => input.path,
711
715
  async validate(input, ctx) {
712
716
  const abs = resolvePath(ctx.cwd, input.path);
713
- return existsSync(abs) && !ctx.readFiles.has(abs) ? `${input.path} sudah ada. Baca dulu dengan read sebelum menimpanya.` : void 0;
717
+ return existsSync(abs) && !ctx.readFiles.has(abs) ? `${input.path} already exists. Read it with read before overwriting it.` : void 0;
714
718
  },
715
719
  async preview(input, ctx) {
716
720
  const abs = resolvePath(ctx.cwd, input.path);
@@ -720,18 +724,18 @@ var writeTool = defineTool({
720
724
  async run(input, ctx) {
721
725
  const abs = resolvePath(ctx.cwd, input.path);
722
726
  if (existsSync(abs) && !ctx.readFiles.has(abs)) {
723
- return { output: `${input.path} sudah ada. Baca dulu dengan read sebelum menimpanya.`, isError: true };
727
+ return { output: `${input.path} already exists. Read it with read before overwriting it.`, isError: true };
724
728
  }
725
729
  try {
726
730
  await ctx.checkpoint?.(abs);
727
731
  await mkdir(dirname(abs), { recursive: true });
728
732
  await writeFile2(abs, input.content);
729
733
  } catch (error) {
730
- return { output: `Gagal menulis ${input.path}: ${errorCode(error)}`, isError: true };
734
+ return { output: `Failed to write ${input.path}: ${errorCode(error)}`, isError: true };
731
735
  }
732
736
  ctx.readFiles.add(abs);
733
737
  const lines = input.content.split("\n").length;
734
- return { output: `Menulis ${input.path} (${lines} baris).`, display: `${lines} baris ditulis` };
738
+ return { output: `Wrote ${input.path} (${lines} lines).`, display: t("{lines} lines written", { lines }) };
735
739
  }
736
740
  });
737
741
 
@@ -747,7 +751,7 @@ function toolDefinitions(tools) {
747
751
  // src/webBudget.ts
748
752
  var KEEP_TOKENS = 4e4;
749
753
  var PRUNE_MIN_TOKENS = 2e4;
750
- var STUB = "[hasil tool lama dihapus:";
754
+ var STUB = "[old tool result removed:";
751
755
  var tokens = (text) => Math.ceil(text.length / 4);
752
756
  var isStub = (content) => content.startsWith(STUB);
753
757
  function pruneOldFetches(messages) {
@@ -778,7 +782,7 @@ function pruneOldFetches(messages) {
778
782
  if (oldTokens <= PRUNE_MIN_TOKENS) return false;
779
783
  for (const i of old) {
780
784
  const m = messages[i];
781
- messages[i] = { ...m, content: `${STUB} fetch ${urls.get(m.tool_call_id)}. Ambil lagi dengan fetch (pakai prompt untuk kutipan terfokus) kalau masih perlu.]` };
785
+ messages[i] = { ...m, content: `${STUB} fetch ${urls.get(m.tool_call_id)}. Fetch it again (use prompt for a focused excerpt) if still needed.]` };
782
786
  }
783
787
  return true;
784
788
  }
@@ -805,10 +809,10 @@ var WebBudget = class {
805
809
  /** Reserves one fetch; returns the refusal to show the model when over budget. */
806
810
  take() {
807
811
  if (this.fetches >= this.perTurn) {
808
- return `Batas ${this.perTurn} fetch per pertanyaan tercapai (tiap fetch memakai token user). Rangkum jawaban dari hasil yang sudah ada; kalau memang kurang, jelaskan ke user apa yang belum ketemu.`;
812
+ return `Limit of ${this.perTurn} fetches per question reached (each fetch uses the user's tokens). Answer from the results you already have; if that is not enough, tell the user what is still missing.`;
809
813
  }
810
814
  if (this.chars >= this.sessionChars) {
811
- return `Batas total halaman web sesi ini tercapai (~${Math.round(this.sessionChars / 4e3)}k token). Rangkum dari hasil yang sudah ada dan tanyakan ke user apakah perlu mencari lebih banyak.`;
815
+ return `Total web page limit for this session reached (~${Math.round(this.sessionChars / 4e3)}k tokens). Answer from the results you already have and ask the user whether to search more.`;
812
816
  }
813
817
  this.fetches++;
814
818
  return void 0;
@@ -865,7 +869,7 @@ var Agent = class {
865
869
  this.definitions = toolDefinitions(tools);
866
870
  }
867
871
  isParallelSafe(call) {
868
- const tool = this.turnTools.find((t) => t.name === call.name);
872
+ const tool = this.turnTools.find((t2) => t2.name === call.name);
869
873
  if (!tool?.parallelSafe) return false;
870
874
  try {
871
875
  const parsed = tool.schema.safeParse(JSON.parse(call.arguments || "{}"));
@@ -901,9 +905,9 @@ var Agent = class {
901
905
  reasoning: this.reasoning
902
906
  });
903
907
  this.clear();
904
- this.push({ role: "user", content: `Ringkasan percakapan sebelumnya:
908
+ this.push({ role: "user", content: `Summary of the previous conversation:
905
909
  ${completion.text}` });
906
- this.push({ role: "assistant", content: "Oke, saya lanjutkan dari ringkasan ini." });
910
+ this.push({ role: "assistant", content: "OK, I will continue from this summary." });
907
911
  this.onEvent({ type: "compacted" });
908
912
  }
909
913
  async run(text, signal) {
@@ -917,7 +921,7 @@ ${completion.text}` });
917
921
  for (let step = 0; step < this.maxSteps; step++) {
918
922
  if (step > 0 && this.lastInputTokens > this.contextWindow * 0.8) {
919
923
  await this.compact(signal);
920
- this.push({ role: "user", content: `Lanjutkan tugas ini sesuai ringkasan di atas: ${text}` });
924
+ this.push({ role: "user", content: `Continue this task using the summary above: ${text}` });
921
925
  }
922
926
  pruneOldFetches(this.messages);
923
927
  let completion = await this.provider.chat({
@@ -958,7 +962,7 @@ ${completion.text}` });
958
962
  this.onEvent({ type: "textReplace", text: completion.text });
959
963
  this.onEvent({
960
964
  type: "error",
961
- message: "Model terjebak mengulang teks, jawaban dihentikan. Coba ulangi atau ganti model dengan /model."
965
+ message: t("The model got stuck repeating text, so the answer was stopped. Try again or switch models with /model.")
962
966
  });
963
967
  return;
964
968
  }
@@ -970,7 +974,7 @@ ${completion.text}` });
970
974
  let i = 0;
971
975
  while (i < calls.length) {
972
976
  if (signal.aborted) {
973
- for (const pending of calls.slice(i)) this.push({ role: "tool", tool_call_id: pending.id, content: "Dibatalkan oleh user." });
977
+ for (const pending of calls.slice(i)) this.push({ role: "tool", tool_call_id: pending.id, content: "Cancelled by the user." });
974
978
  this.onEvent({ type: "aborted" });
975
979
  return;
976
980
  }
@@ -999,7 +1003,7 @@ ${completion.text}` });
999
1003
  this.onEvent({
1000
1004
  type: "error",
1001
1005
  message: noTools ? `${message}
1002
- Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model.` : message
1006
+ ${t("This model may not support tool calling. Try another model with /model.")}` : message
1003
1007
  });
1004
1008
  }
1005
1009
  }
@@ -1009,17 +1013,17 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
1009
1013
  this.onEvent({ type: "toolEnd", id: call.id, tool: call.name, output, isError: true });
1010
1014
  return output;
1011
1015
  };
1012
- const tool = this.turnTools.find((t) => t.name === call.name);
1013
- if (!tool) return fail(`Alat "${call.name}" tidak ada. Alat yang tersedia: ${this.turnTools.map((t) => t.name).join(", ")}`);
1016
+ const tool = this.turnTools.find((t2) => t2.name === call.name);
1017
+ if (!tool) return fail(`Tool "${call.name}" does not exist. Available tools: ${this.turnTools.map((t2) => t2.name).join(", ")}`);
1014
1018
  let args;
1015
1019
  try {
1016
1020
  args = JSON.parse(call.arguments || "{}");
1017
1021
  } catch {
1018
- return fail(`Argumen bukan JSON valid: ${call.arguments.slice(0, 200)}`);
1022
+ return fail(`Arguments are not valid JSON: ${call.arguments.slice(0, 200)}`);
1019
1023
  }
1020
1024
  const parsed = tool.schema.safeParse(args);
1021
1025
  if (!parsed.success) {
1022
- return fail(`Argumen tidak valid: ${parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
1026
+ return fail(`Invalid arguments: ${parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
1023
1027
  }
1024
1028
  const input = parsed.data;
1025
1029
  const target = tool.target(input);
@@ -1061,7 +1065,7 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
1061
1065
  decision = answer === "no" ? "deny" : "allow";
1062
1066
  }
1063
1067
  if (decision === "deny") {
1064
- 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.";
1068
+ const output = this.permissions.mode === "plan" ? "Denied: plan mode only allows reading and searching. Write the plan for the user without changing anything." : "The user declined to run this tool. Ask the user what they want, or try another approach.";
1065
1069
  this.onEvent({ type: "toolEnd", id: call.id, tool: tool.name, output, isError: true });
1066
1070
  return output;
1067
1071
  }
@@ -1101,7 +1105,7 @@ var CheckpointStore = class {
1101
1105
  while (this.turns() > MAX_TURNS) this.history.shift();
1102
1106
  }
1103
1107
  turns() {
1104
- return this.history.filter((t) => t.size).length;
1108
+ return this.history.filter((t2) => t2.size).length;
1105
1109
  }
1106
1110
  async undo() {
1107
1111
  while (this.history.length && !this.history.at(-1)?.size) this.history.pop();
@@ -1147,7 +1151,7 @@ function mcpToolName(server, tool, taken) {
1147
1151
  function withTimeout(promise, ms) {
1148
1152
  let timer;
1149
1153
  const timeout = new Promise((_, reject) => {
1150
- timer = setTimeout(() => reject(new Error(`timeout ${Math.round(ms / 1e3)} detik`)), ms);
1154
+ timer = setTimeout(() => reject(new Error(t("timeout after {n} seconds", { n: Math.round(ms / 1e3) }))), ms);
1151
1155
  });
1152
1156
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
1153
1157
  }
@@ -1184,7 +1188,7 @@ var McpManager = class {
1184
1188
  });
1185
1189
  return;
1186
1190
  }
1187
- entry.tools = tools.map((t) => this.wrap(spec.name, client, t));
1191
+ entry.tools = tools.map((t2) => this.wrap(spec.name, client, t2));
1188
1192
  entry.state = { ...entry.state, status: "ready", tools: entry.tools.length };
1189
1193
  } catch (error) {
1190
1194
  entry.state = { ...entry.state, status: "error", error: error.message };
@@ -1208,9 +1212,9 @@ var McpManager = class {
1208
1212
  async run(input, ctx) {
1209
1213
  try {
1210
1214
  const result = await client.callTool({ name: tool.name, arguments: input }, void 0, { signal: ctx.signal });
1211
- const output = (result.content ?? []).map((c) => c.type === "text" ? c.text ?? "" : `[${c.type}]`).join("\n") || "(kosong)";
1215
+ const output = (result.content ?? []).map((c) => c.type === "text" ? c.text ?? "" : `[${c.type}]`).join("\n") || "(empty)";
1212
1216
  return { output: output.length > 5e4 ? `${output.slice(0, 5e4)}
1213
- \u2026 [dipotong]` : output, isError: !!result.isError };
1217
+ \u2026 [truncated]` : output, isError: !!result.isError };
1214
1218
  } catch (error) {
1215
1219
  return { output: `Server MCP ${server} error: ${error.message}`, isError: true };
1216
1220
  }
@@ -1221,8 +1225,8 @@ var McpManager = class {
1221
1225
  const entry = this.servers.get(name);
1222
1226
  if (!entry) return;
1223
1227
  this.servers.delete(name);
1224
- for (const t of entry.tools) this.taken.delete(t.name);
1225
- if (entry.tools.length) this.opts.onToolsRemoved?.(entry.tools.map((t) => t.name));
1228
+ for (const t2 of entry.tools) this.taken.delete(t2.name);
1229
+ if (entry.tools.length) this.opts.onToolsRemoved?.(entry.tools.map((t2) => t2.name));
1226
1230
  await entry.client?.close().catch(() => {
1227
1231
  });
1228
1232
  this.opts.onChange?.();
@@ -1249,15 +1253,15 @@ function createExitPlanTool(opts) {
1249
1253
  description: "Plan mode only: present the finished plan to the user for approval. If approved you may start implementing it.",
1250
1254
  schema: z10.object({ plan: z10.string().describe("The plan, in markdown") }),
1251
1255
  kind: "read",
1252
- target: () => "rencana",
1256
+ target: () => t("plan"),
1253
1257
  async run(input) {
1254
- if (opts.permissions.mode !== "plan") return { output: "exit_plan hanya dipakai di mode plan.", isError: true };
1258
+ if (opts.permissions.mode !== "plan") return { output: "exit_plan is only used in plan mode.", isError: true };
1255
1259
  const decision = await opts.interaction.approvePlan(input.plan);
1256
1260
  if (decision === "no") {
1257
- return { output: "User belum menyetujui. Tanyakan apa yang perlu diperbaiki, perbaiki rencananya, lalu panggil exit_plan lagi." };
1261
+ return { output: "The user has not approved yet. Ask what needs to change, revise the plan, then call exit_plan again." };
1258
1262
  }
1259
1263
  opts.permissions.mode = decision;
1260
- return { output: `Rencana disetujui. Mode sekarang ${decision}. Mulai kerjakan rencananya sekarang.` };
1264
+ return { output: `Plan approved. The mode is now ${decision}. Start carrying out the plan now.` };
1261
1265
  }
1262
1266
  });
1263
1267
  }
@@ -1274,7 +1278,7 @@ function createSkillTool(skills) {
1274
1278
  target: (input) => input.name,
1275
1279
  async run(input) {
1276
1280
  const skill = skills.find((s) => s.name === input.name);
1277
- if (!skill) return { output: `Skill "${input.name}" tidak ada. Tersedia: ${skills.map((s) => s.name).join(", ") || "(tidak ada)"}`, isError: true };
1281
+ if (!skill) return { output: `Skill "${input.name}" does not exist. Available: ${skills.map((s) => s.name).join(", ") || "(none)"}`, isError: true };
1278
1282
  const { body } = parseFrontmatter(readFileSync(skill.file, "utf8"));
1279
1283
  return { output: `Base directory for this skill: ${skill.dir}
1280
1284
 
@@ -1334,12 +1338,12 @@ function createWebSearchTool(opts) {
1334
1338
  }
1335
1339
  }
1336
1340
  if (!results.length) {
1337
- return { output: errors.length ? `Web search gagal (${errors.join("; ")}).` : "Tidak ada hasil.", isError: errors.length > 0 };
1341
+ return { output: errors.length ? `Web search failed (${errors.join("; ")}).` : "No results.", isError: errors.length > 0 };
1338
1342
  }
1339
1343
  const output = results.map((r, i) => `${i + 1}. ${r.title}
1340
1344
  ${r.url}${r.snippet ? `
1341
1345
  ${r.snippet}` : ""}`).join("\n");
1342
- return { output, display: `${results.length} hasil` };
1346
+ return { output, display: t("{n} results", { n: results.length }) };
1343
1347
  }
1344
1348
  });
1345
1349
  }
@@ -1365,16 +1369,16 @@ ${list}`,
1365
1369
  const allowed = byName.get(input.agent)?.tools;
1366
1370
  if (!allowed) return false;
1367
1371
  const base = opts.baseTools();
1368
- return allowed.every((name) => base.find((t) => t.name === name)?.kind === "read");
1372
+ return allowed.every((name) => base.find((t2) => t2.name === name)?.kind === "read");
1369
1373
  },
1370
1374
  async run(input, ctx) {
1371
1375
  const def = byName.get(input.agent);
1372
- if (!def) return { output: `Agent "${input.agent}" tidak ada. Tersedia: ${[...byName.keys()].join(", ")}`, isError: true };
1373
- const base = opts.baseTools().filter((t) => t.name !== "task" && t.name !== "exit_plan");
1374
- const unknown = (def.tools ?? []).filter((n) => !base.some((t) => t.name === n));
1375
- if (unknown.length) return { output: `Agent "${def.name}": alat tidak dikenal: ${unknown.join(", ")}`, isError: true };
1376
+ if (!def) return { output: `Agent "${input.agent}" does not exist. Available: ${[...byName.keys()].join(", ")}`, isError: true };
1377
+ const base = opts.baseTools().filter((t2) => t2.name !== "task" && t2.name !== "exit_plan");
1378
+ const unknown = (def.tools ?? []).filter((n) => !base.some((t2) => t2.name === n));
1379
+ if (unknown.length) return { output: `Agent "${def.name}": unknown tools: ${unknown.join(", ")}`, isError: true };
1376
1380
  const allowed = def.tools;
1377
- const tools = allowed ? base.filter((t) => allowed.includes(t.name)) : base;
1381
+ const tools = allowed ? base.filter((t2) => allowed.includes(t2.name)) : base;
1378
1382
  let provider;
1379
1383
  try {
1380
1384
  provider = def.model ? opts.providerFor(def.model) : opts.provider();
@@ -1407,14 +1411,14 @@ ${def.prompt}`,
1407
1411
  await child.run(input.prompt, ctx.signal);
1408
1412
  ctx.addUsage?.(child.totalUsage);
1409
1413
  const tokens2 = child.totalUsage.inputTokens + child.totalUsage.outputTokens;
1410
- const display = `${steps} langkah \xB7 ${tokens2 >= 1e3 ? `${Math.round(tokens2 / 1e3)}k` : tokens2} token`;
1411
- if (outcome?.type === "error") return { output: `Subagent ${def.name} gagal: ${outcome.message}`, isError: true, display };
1412
- if (outcome?.type === "aborted") return { output: `Subagent ${def.name} dibatalkan.`, isError: true, display };
1414
+ const display = t("{steps} steps \xB7 {tokens} tokens", { steps, tokens: tokens2 >= 1e3 ? `${Math.round(tokens2 / 1e3)}k` : tokens2 });
1415
+ if (outcome?.type === "error") return { output: `Subagent ${def.name} failed: ${outcome.message}`, isError: true, display };
1416
+ if (outcome?.type === "aborted") return { output: `Subagent ${def.name} was cancelled.`, isError: true, display };
1413
1417
  const last = [...child.messages].reverse().find((m) => m.role === "assistant" && m.content);
1414
- const report = last && typeof last.content === "string" ? last.content : "(tidak ada laporan)";
1418
+ const report = last && typeof last.content === "string" ? last.content : "(no report)";
1415
1419
  const suffix = outcome?.type === "stepLimit" ? `
1416
1420
 
1417
- (Subagent berhenti di batas ${def.maxSteps ?? 50} langkah.)` : "";
1421
+ (The subagent stopped at its ${def.maxSteps ?? 50}-step limit.)` : "";
1418
1422
  return { output: report + suffix, display };
1419
1423
  }
1420
1424
  });
@@ -1444,12 +1448,12 @@ function createTodoTool(store) {
1444
1448
  kind: "read",
1445
1449
  target: (input) => `${input.todos.length} item`,
1446
1450
  async run(input) {
1447
- if (input.todos.filter((t) => t.status === "in_progress").length > 1) {
1448
- return { output: "Hanya boleh satu item in_progress.", isError: true };
1451
+ if (input.todos.filter((t2) => t2.status === "in_progress").length > 1) {
1452
+ return { output: "Only one item may be in_progress.", isError: true };
1449
1453
  }
1450
1454
  store.set(input.todos);
1451
- const done = input.todos.filter((t) => t.status === "completed").length;
1452
- return { output: `Todo diperbarui: ${done}/${input.todos.length} selesai.` };
1455
+ const done = input.todos.filter((t2) => t2.status === "completed").length;
1456
+ return { output: `Todo updated: ${done}/${input.todos.length} completed.` };
1453
1457
  }
1454
1458
  });
1455
1459
  }
@@ -1468,7 +1472,7 @@ function createRuntime(opts) {
1468
1472
  };
1469
1473
  const provider = makeProvider(modelRef);
1470
1474
  const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
1471
- const rules = [...config.allow, ...opts.args.allowedTools.map((t) => TOOL_RULES[t] ?? t)];
1475
+ const rules = [...config.allow, ...opts.args.allowedTools.map((t2) => TOOL_RULES[t2] ?? t2)];
1472
1476
  const permissions = new Permissions(mode, rules, opts.cwd);
1473
1477
  const roots = { cwd: opts.cwd, home, userHome: opts.userHome ?? homedir() };
1474
1478
  const skills = loadSkills(roots);
@@ -1502,7 +1506,7 @@ function createRuntime(opts) {
1502
1506
  const task = createTaskTool({
1503
1507
  agents: [...BUILTIN_AGENTS, ...agentDefs],
1504
1508
  // Subagents get whatever the main agent has right now (incl. MCP tools), minus task itself.
1505
- baseTools: () => agent.tools.filter((t) => t.name !== "task"),
1509
+ baseTools: () => agent.tools.filter((t2) => t2.name !== "task"),
1506
1510
  permissions,
1507
1511
  provider: () => agent.provider,
1508
1512
  providerFor: makeProvider,
@@ -1600,33 +1604,42 @@ function createRuntime(opts) {
1600
1604
  }
1601
1605
 
1602
1606
  // src/version.ts
1603
- var VERSION = true ? "0.4.0-beta.14" : "0.0.0-dev";
1607
+ var VERSION = true ? "0.4.0-beta.15" : "0.0.0-dev";
1604
1608
 
1605
1609
  // src/cli.ts
1610
+ function chooseLanguage(argv) {
1611
+ let fromConfig;
1612
+ try {
1613
+ fromConfig = readGlobalConfigFile(process.env).language;
1614
+ } catch {
1615
+ }
1616
+ setLanguage(resolveLanguage(argv, process.env, fromConfig));
1617
+ }
1606
1618
  async function main() {
1619
+ chooseLanguage(process.argv.slice(2));
1607
1620
  const args = parseCliArgs(process.argv.slice(2));
1608
1621
  if (args.version) {
1609
1622
  console.log(VERSION);
1610
1623
  return 0;
1611
1624
  }
1612
1625
  if (args.help) {
1613
- console.log(HELP_TEXT);
1626
+ console.log(helpText());
1614
1627
  return 0;
1615
1628
  }
1616
1629
  const cwd = process.cwd();
1617
1630
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1618
1631
  if (args.command === "mcp") {
1619
- const { runMcpCommand } = await import("./mcpCli-DZWQPTOS.js");
1620
- const { readSecret } = await import("./login-N2CJC6RU.js");
1632
+ const { runMcpCommand } = await import("./mcpCli-QJE7P76I.js");
1633
+ const { readSecret } = await import("./login-O2BQX6LZ.js");
1621
1634
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1622
1635
  }
1623
1636
  if (args.command === "provider") {
1624
- const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
1625
- const { readSecret } = await import("./login-N2CJC6RU.js");
1637
+ const { runProviderCommand } = await import("./providerCli-3FJAUH7C.js");
1638
+ const { readSecret } = await import("./login-O2BQX6LZ.js");
1626
1639
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1627
1640
  }
1628
1641
  if (args.command === "connect" || args.command === "disconnect" || args.command === "integrations") {
1629
- const { runIntegrationCommand } = await import("./integrationCli-3CGHHTM2.js");
1642
+ const { runIntegrationCommand } = await import("./integrationCli-RQO3HHQE.js");
1630
1643
  return runIntegrationCommand(args, {
1631
1644
  env: process.env,
1632
1645
  cwd,
@@ -1648,11 +1661,11 @@ async function main() {
1648
1661
  } catch {
1649
1662
  }
1650
1663
  if (args.print) {
1651
- if (!args.prompt) throw new ConfigError('Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"');
1652
- const { serversToStart } = await import("./config-YUNKPCNB.js");
1664
+ if (!args.prompt) throw new ConfigError(t('-p mode needs a task, e.g. bccli -p "explain this repo"'));
1665
+ const { serversToStart } = await import("./config-CGYYFECG.js");
1653
1666
  const plan = serversToStart(rt.home, cwd);
1654
1667
  for (const s of plan.needTrust) {
1655
- console.error(`Server MCP project "${s.name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).`);
1668
+ console.error(t('Project MCP server "{name}" skipped (not approved yet; run interactive bccli once to approve it).', { name: s.name }));
1656
1669
  }
1657
1670
  await rt.startMcp(plan.start);
1658
1671
  try {
@@ -1661,8 +1674,8 @@ async function main() {
1661
1674
  await rt.mcp.stop();
1662
1675
  }
1663
1676
  }
1664
- if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
1665
- const { startInteractive } = await import("./ui-5BJA3UXH.js");
1677
+ if (!process.stdin.isTTY) throw new ConfigError(t('Interactive mode needs a terminal. For scripts/CI use: bccli -p "task"'));
1678
+ const { startInteractive } = await import("./ui-HVH3TR3I.js");
1666
1679
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1667
1680
  }
1668
1681
  main().then(