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

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.
@@ -5,7 +5,7 @@ import {
5
5
  instructionPaths,
6
6
  parseFrontmatter,
7
7
  toolLabel
8
- } from "./chunk-3MWAQJE7.js";
8
+ } from "./chunk-K5J2GGXT.js";
9
9
  import {
10
10
  nextMode
11
11
  } from "./chunk-ELML4KVQ.js";
@@ -13,30 +13,35 @@ import {
13
13
  hasKey,
14
14
  providerName,
15
15
  writeGlobalConfig
16
- } from "./chunk-7S6JXQCH.js";
16
+ } from "./chunk-D7CF42EW.js";
17
17
  import {
18
18
  REASONING_LEVELS,
19
19
  assertReasoningSupported,
20
20
  parseReasoningLevel,
21
21
  supportedReasoningLevels
22
- } from "./chunk-CLN4Z5E3.js";
22
+ } from "./chunk-VEZFR7M7.js";
23
23
  import {
24
24
  Session
25
25
  } from "./chunk-SJH3YLW4.js";
26
26
  import {
27
27
  CATALOG,
28
28
  fillTemplate
29
- } from "./chunk-DX7CZTBB.js";
29
+ } from "./chunk-RQGMYLVH.js";
30
30
  import {
31
31
  addGlobalServer,
32
32
  globalMcpPath,
33
33
  readMcpFile,
34
34
  removeGlobalServer
35
- } from "./chunk-BFZXRKW5.js";
35
+ } from "./chunk-ERVDVPKJ.js";
36
36
  import {
37
37
  PRESETS,
38
- removeCredential
39
- } from "./chunk-2MGINW4O.js";
38
+ getLanguage,
39
+ locale,
40
+ parseLang,
41
+ removeCredential,
42
+ setLanguage,
43
+ t
44
+ } from "./chunk-DDA5TJ7T.js";
40
45
 
41
46
  // src/ui/index.tsx
42
47
  import { render } from "ink";
@@ -51,31 +56,32 @@ import { useCallback, useEffect as useEffect2, useRef, useState as useState8 } f
51
56
 
52
57
  // src/commands.ts
53
58
  var SLASH_COMMANDS = [
54
- { name: "help", description: "daftar perintah dan pintasan" },
55
- { name: "model", description: "ganti model" },
56
- { name: "reasoning", description: "atur reasoning: auto/off/low/medium/high/max" },
57
- { name: "provider", description: "tambah/pilih provider AI" },
58
- { name: "mcp", description: "pasang/kelola server MCP" },
59
- { name: "new", description: "sesi baru (sesi lama tetap tersimpan)" },
60
- { name: "resume", description: "lanjutkan sesi lain di folder ini" },
61
- { name: "session", description: "info sesi ini" },
62
- { name: "status", description: "versi, model, mode izin, MCP, konteks" },
63
- { name: "permissions", description: "lihat/cabut izin" },
64
- { name: "undo", description: "batalkan edit file giliran terakhir" },
65
- { name: "diff", description: "git diff project" },
66
- { name: "copy", description: "salin jawaban terakhir" },
67
- { name: "export", description: "simpan percakapan ke markdown" },
68
- { name: "memory", description: "instruksi project (/memory <teks>, /memory global <teks>)" },
69
- { name: "init", description: "buat/perbarui AGENTS.md untuk project ini" },
70
- { name: "agents", description: "daftar subagent" },
71
- { name: "skills", description: "daftar skill dan perintah custom" },
72
- { name: "doctor", description: "cek kesehatan instalasi" },
73
- { name: "login", description: "simpan API key provider aktif" },
74
- { name: "logout", description: "hapus API key provider aktif" },
75
- { name: "clear", description: "mulai percakapan baru" },
76
- { name: "compact", description: "ringkas percakapan" },
77
- { name: "cost", description: "pemakaian token sesi ini" },
78
- { name: "exit", description: "keluar" }
59
+ { name: "help", description: "list of commands and shortcuts" },
60
+ { name: "model", description: "change model" },
61
+ { name: "reasoning", description: "set reasoning: auto/off/low/medium/high/max" },
62
+ { name: "provider", description: "add/choose an AI provider" },
63
+ { name: "mcp", description: "install/manage MCP servers" },
64
+ { name: "new", description: "new session (the old one stays saved)" },
65
+ { name: "resume", description: "continue another session in this folder" },
66
+ { name: "session", description: "info about this session" },
67
+ { name: "status", description: "version, model, permission mode, MCP, context" },
68
+ { name: "permissions", description: "view/revoke permissions" },
69
+ { name: "undo", description: "undo the file edits of the last turn" },
70
+ { name: "diff", description: "git diff of the project" },
71
+ { name: "copy", description: "copy the last answer" },
72
+ { name: "export", description: "save the conversation as markdown" },
73
+ { name: "memory", description: "project instructions (/memory <text>, /memory global <text>)" },
74
+ { name: "init", description: "create/update AGENTS.md for this project" },
75
+ { name: "agents", description: "list subagents" },
76
+ { name: "skills", description: "list skills and custom commands" },
77
+ { name: "doctor", description: "check installation health" },
78
+ { name: "login", description: "save the API key of the current provider" },
79
+ { name: "logout", description: "delete the API key of the current provider" },
80
+ { name: "language", description: "set the interface language: en/id" },
81
+ { name: "clear", description: "start a new conversation" },
82
+ { name: "compact", description: "summarize the conversation" },
83
+ { name: "cost", description: "token usage of this session" },
84
+ { name: "exit", description: "quit" }
79
85
  ];
80
86
  function parseSlash(text) {
81
87
  if (!text.startsWith("/")) return void 0;
@@ -109,8 +115,8 @@ function git(cwd, args, bin = "git") {
109
115
  }
110
116
  async function gitDiff(cwd, maxLines = 400, bin = "git") {
111
117
  const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], bin);
112
- if (inside.missing) return "git tidak terpasang atau tidak ada di PATH, jadi /diff tidak bisa dipakai.";
113
- if (!inside.ok || inside.out.trim() !== "true") return "Folder ini bukan repository git, jadi tidak ada diff.";
118
+ if (inside.missing) return t("git is not installed or not on PATH, so /diff cannot be used.");
119
+ if (!inside.ok || inside.out.trim() !== "true") return t("This folder is not a git repository, so there is no diff.");
114
120
  const base = (await git(cwd, ["rev-parse", "--verify", "-q", "HEAD"])).ok ? "HEAD" : "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
115
121
  const [stat, diff, untracked] = await Promise.all([
116
122
  git(cwd, ["diff", "--stat", base]),
@@ -118,23 +124,23 @@ async function gitDiff(cwd, maxLines = 400, bin = "git") {
118
124
  git(cwd, ["ls-files", "--others", "--exclude-standard"])
119
125
  ]);
120
126
  const fresh = untracked.out.split("\n").filter(Boolean);
121
- if (!diff.out.trim() && !fresh.length) return "Tidak ada perubahan yang belum di-commit.";
127
+ if (!diff.out.trim() && !fresh.length) return t("No uncommitted changes.");
122
128
  const lines = diff.out.trimEnd().split("\n");
123
129
  const more = lines.length > maxLines ? `
124
- \u2026 ${lines.length - maxLines} baris lagi` : "";
125
- return [stat.out.trim(), lines.slice(0, maxLines).join("\n") + more, fresh.length ? `File baru (belum di-track): ${fresh.join(", ")}` : ""].filter(Boolean).join("\n\n");
130
+ ${t("\u2026 {n} more lines", { n: lines.length - maxLines })}` : "";
131
+ return [stat.out.trim(), lines.slice(0, maxLines).join("\n") + more, fresh.length ? t("New files (untracked): {files}", { files: fresh.join(", ") }) : ""].filter(Boolean).join("\n\n");
126
132
  }
127
133
 
128
134
  // src/slash/doctor.ts
129
135
  async function doctorText(input, deps) {
130
136
  const major = Number(deps.nodeVersion.replace(/^v/, "").split(".")[0]);
131
- const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` : `\u2717 Node ${deps.nodeVersion} (butuh \u2265 22)`];
132
- for (const p of input.providers) lines.push(p.ready ? `\u2713 ${p.name}` : `\u25CB ${p.name} (belum ada key)`);
137
+ const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` : t("\u2717 Node {version} (needs \u2265 22)", { version: deps.nodeVersion })];
138
+ for (const p of input.providers) lines.push(p.ready ? `\u2713 ${p.name}` : t("\u25CB {name} (no key yet)", { name: p.name }));
133
139
  const cmds = ["rg", "git", "npx", "uvx"];
134
140
  const [connection, found] = await Promise.all([
135
141
  deps.listModels().then(
136
- (models) => `\u2713 Koneksi ${input.activeProvider} (${models.length} model)`,
137
- (error) => `\u2717 Koneksi ${input.activeProvider}: ${error.message}`
142
+ (models) => t("\u2713 Connection {provider} ({n} models)", { provider: input.activeProvider, n: models.length }),
143
+ (error) => t("\u2717 Connection {provider}: {error}", { provider: input.activeProvider, error: error.message })
138
144
  ),
139
145
  Promise.all(cmds.map(async (cmd) => deps.which(cmd)))
140
146
  ]);
@@ -165,31 +171,31 @@ function statusText(s) {
165
171
  return [
166
172
  `BCCLI ${s.version}`,
167
173
  `Model: ${s.modelRef} (${s.providerLabel})`,
168
- `Mode izin: ${s.mode}`,
169
- `Folder: ${s.cwd}`,
170
- `MCP: ${ready} aktif, ${errors} error`,
171
- `Token sesi: ${k(s.usage.inputTokens)} masuk \xB7 ${k(s.usage.outputTokens)} keluar`,
172
- `Konteks: \xB1${Math.round(s.lastInputTokens / 128e3 * 100)}% dari 128k`
174
+ t("Permission mode: {mode}", { mode: s.mode }),
175
+ t("Folder: {cwd}", { cwd: s.cwd }),
176
+ t("MCP: {ready} active, {errors} errors", { ready, errors }),
177
+ t("Session tokens: {input} in \xB7 {output} out", { input: k(s.usage.inputTokens), output: k(s.usage.outputTokens) }),
178
+ t("Context: ~{pct}% of 128k", { pct: Math.round(s.lastInputTokens / 128e3 * 100) })
173
179
  ].join("\n");
174
180
  }
175
181
  function agentsText(defs) {
176
- return defs.map((d) => `${d.name} \u2014 ${d.description} \xB7 alat: ${d.tools ? d.tools.join(", ") : "semua"} \xB7 model: ${d.model ?? "ikut utama"}`).join("\n");
182
+ return defs.map((d) => t("{name} \u2014 {description} \xB7 tools: {tools} \xB7 model: {model}", { name: d.name, description: d.description, tools: d.tools ? d.tools.join(", ") : t("all"), model: d.model ?? t("same as main") })).join("\n");
177
183
  }
178
184
  function skillsText(skills, commands) {
179
- const s = skills.length ? skills.map((x) => `${x.name} \u2014 ${x.description.slice(0, 80)} (${x.dir})`).join("\n") : "(tidak ada skill)";
180
- const c = commands.length ? commands.map((x) => `/${x.name} \u2014 ${x.description ?? ""}`).join("\n") : "(tidak ada perintah custom)";
181
- return `Skill:
185
+ const s = skills.length ? skills.map((x) => `${x.name} \u2014 ${x.description.slice(0, 80)} (${x.dir})`).join("\n") : t("(no skills)");
186
+ const c = commands.length ? commands.map((x) => `/${x.name} \u2014 ${x.description ?? ""}`).join("\n") : t("(no custom commands)");
187
+ return `${t("Skills")}:
182
188
  ${s}
183
189
 
184
- Perintah custom:
190
+ ${t("Custom commands")}:
185
191
  ${c}`;
186
192
  }
187
193
  function sessionText(s) {
188
194
  return [
189
- `Mulai: ${s.started.toLocaleString("id-ID")}`,
190
- `${s.messages} pesan \xB7 ${k(s.usage.inputTokens)} token masuk \xB7 ${k(s.usage.outputTokens)} keluar`,
195
+ t("Started: {when}", { when: s.started.toLocaleString(locale()) }),
196
+ t("{messages} messages \xB7 {input} tokens in \xB7 {output} out", { messages: s.messages, input: k(s.usage.inputTokens), output: k(s.usage.outputTokens) }),
191
197
  `Model: ${s.modelRef}`,
192
- `File: ${s.file}`
198
+ t("File: {file}", { file: s.file })
193
199
  ].join("\n");
194
200
  }
195
201
 
@@ -209,7 +215,308 @@ function parseMemoryArgs(args) {
209
215
  }
210
216
 
211
217
  // src/ui/Markdown.tsx
212
- import { Box, Text } from "ink";
218
+ import { Box, Text, useStdout } from "ink";
219
+
220
+ // src/ui/table.ts
221
+ var WIDE = [
222
+ [4352, 4447],
223
+ [8986, 8987],
224
+ [9193, 9196],
225
+ [9200, 9200],
226
+ [9203, 9203],
227
+ [9725, 9726],
228
+ [9748, 9749],
229
+ [9800, 9811],
230
+ [9855, 9855],
231
+ [9875, 9875],
232
+ [9889, 9889],
233
+ [9898, 9899],
234
+ [9917, 9918],
235
+ [9924, 9925],
236
+ [9934, 9934],
237
+ [9940, 9940],
238
+ [9962, 9962],
239
+ [9970, 9971],
240
+ [9973, 9973],
241
+ [9978, 9978],
242
+ [9981, 9981],
243
+ [9989, 9989],
244
+ [9994, 9995],
245
+ [10024, 10024],
246
+ [10060, 10060],
247
+ [10062, 10062],
248
+ [10067, 10069],
249
+ [10071, 10071],
250
+ [10133, 10135],
251
+ [10160, 10160],
252
+ [10175, 10175],
253
+ [11035, 11036],
254
+ [11088, 11088],
255
+ [11093, 11093],
256
+ [11904, 42191],
257
+ [44032, 55203],
258
+ [63744, 64255],
259
+ [65072, 65135],
260
+ [65280, 65376],
261
+ [65504, 65510],
262
+ [127744, 128591],
263
+ [128640, 128767],
264
+ [129280, 129791],
265
+ [131072, 262141]
266
+ ];
267
+ var ZERO = [
268
+ [768, 879],
269
+ [6832, 6911],
270
+ [7616, 7679],
271
+ [8203, 8207],
272
+ [8400, 8447],
273
+ [65024, 65039],
274
+ [65279, 65279],
275
+ [917760, 917999]
276
+ ];
277
+ var inRanges = (cp, ranges) => ranges.some(([a, b]) => cp >= a && cp <= b);
278
+ function charWidth(cp) {
279
+ if (cp < 32 || cp >= 127 && cp < 160) return 0;
280
+ if (cp < 768) return 1;
281
+ if (inRanges(cp, ZERO)) return 0;
282
+ return inRanges(cp, WIDE) ? 2 : 1;
283
+ }
284
+ function displayWidth(text) {
285
+ let width = 0;
286
+ for (const ch of text) width += charWidth(ch.codePointAt(0));
287
+ return width;
288
+ }
289
+ var segsWidth = (segs) => segs.reduce((sum, s) => sum + displayWidth(s.text), 0);
290
+ var INLINE = /(`[^`]+`|\*\*[^*]+\*\*|~~[^~]+~~|!?\[[^\]\n]+\]\([^)\s]+\)|(?<![\w*])\*(?![\s*])[^*\n]+?(?<![\s*])\*(?![\w*])|(?<!\w)_(?![\s_])[^_\n]+?(?<![\s_])_(?!\w))/g;
291
+ function parseInline(text) {
292
+ const segs = [];
293
+ for (const part of text.split(INLINE)) {
294
+ if (part === "") continue;
295
+ if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) segs.push({ text: part.slice(1, -1), style: "code" });
296
+ else if (part.length > 4 && part.startsWith("**") && part.endsWith("**")) segs.push({ text: part.slice(2, -2), style: "bold" });
297
+ else if (part.length > 4 && part.startsWith("~~") && part.endsWith("~~")) segs.push({ text: part.slice(2, -2), style: "strike" });
298
+ else if (/^!?\[[^\]\n]+\]\([^)\s]+\)$/.test(part)) {
299
+ const m = /^(!?)\[([^\]]+)\]\(([^)\s]+)\)$/.exec(part);
300
+ const [, image, label, url] = m;
301
+ segs.push({ text: label, style: image ? "plain" : "link" });
302
+ if (label !== url && !url.startsWith("#")) segs.push({ text: ` (${url})`, style: "dim" });
303
+ } else if (part.length > 2 && (part.startsWith("*") && part.endsWith("*") || part.startsWith("_") && part.endsWith("_"))) {
304
+ segs.push({ text: part.slice(1, -1), style: "italic" });
305
+ } else segs.push({ text: part, style: "plain" });
306
+ }
307
+ return segs;
308
+ }
309
+ function splitRow(line) {
310
+ let s = line.trim();
311
+ if (s.startsWith("|")) s = s.slice(1);
312
+ if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
313
+ const cells = [];
314
+ let current = "";
315
+ for (let i = 0; i < s.length; i++) {
316
+ if (s[i] === "\\" && s[i + 1] === "|") {
317
+ current += "|";
318
+ i++;
319
+ } else if (s[i] === "|") {
320
+ cells.push(current);
321
+ current = "";
322
+ } else current += s[i];
323
+ }
324
+ cells.push(current);
325
+ return cells.map((c) => c.trim());
326
+ }
327
+ var SEPARATOR = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/;
328
+ var isSeparator = (line) => line.includes("|") && SEPARATOR.test(line);
329
+ var cell = (raw) => parseInline(raw.replace(/<br\s*\/?>/gi, "\n"));
330
+ function parseTable(lines, start) {
331
+ if (start + 1 >= lines.length || !lines[start].includes("|") || !isSeparator(lines[start + 1])) return void 0;
332
+ const header = splitRow(lines[start]);
333
+ const sep = splitRow(lines[start + 1]);
334
+ if (sep.length !== header.length) return void 0;
335
+ const body = [];
336
+ let end = start + 2;
337
+ while (end < lines.length && lines[end].trim() && lines[end].includes("|")) body.push(splitRow(lines[end++]));
338
+ const columns = Math.max(header.length, ...body.map((r) => r.length));
339
+ const pad2 = (row, fill) => [...row, ...Array.from({ length: columns - row.length }, () => fill)];
340
+ const align = pad2(
341
+ sep.map((s) => s.startsWith(":") && s.endsWith(":") ? "center" : s.endsWith(":") ? "right" : "left"),
342
+ "left"
343
+ );
344
+ return { table: { header: pad2(header, "").map(cell), align, rows: body.map((r) => pad2(r, "").map(cell)) }, end };
345
+ }
346
+ function tokenize(segs) {
347
+ const tokens = [];
348
+ let word = [];
349
+ const endWord = () => {
350
+ if (word.length) tokens.push(word);
351
+ word = [];
352
+ };
353
+ for (const seg of segs) {
354
+ for (const piece of seg.text.match(/\n|[^\S\n]+|[^\s]+/g) ?? []) {
355
+ if (piece === "\n") {
356
+ endWord();
357
+ tokens.push("br");
358
+ } else if (/^\s+$/.test(piece)) endWord();
359
+ else word.push({ text: piece, style: seg.style });
360
+ }
361
+ }
362
+ endWord();
363
+ return tokens;
364
+ }
365
+ function splitWord(word, width) {
366
+ const pieces = [];
367
+ let current = [];
368
+ let used = 0;
369
+ for (const seg of word) {
370
+ let text = "";
371
+ for (const ch of seg.text) {
372
+ const w = charWidth(ch.codePointAt(0));
373
+ if (used + w > width && used > 0) {
374
+ if (text) current.push({ text, style: seg.style });
375
+ pieces.push(current);
376
+ current = [];
377
+ text = "";
378
+ used = 0;
379
+ }
380
+ text += ch;
381
+ used += w;
382
+ }
383
+ if (text) current.push({ text, style: seg.style });
384
+ }
385
+ if (current.length) pieces.push(current);
386
+ return pieces;
387
+ }
388
+ function wrap(tokens, width) {
389
+ const lines = [];
390
+ let line = [];
391
+ let used = 0;
392
+ const flush = () => {
393
+ lines.push(line);
394
+ line = [];
395
+ used = 0;
396
+ };
397
+ for (const token of tokens) {
398
+ if (token === "br") {
399
+ flush();
400
+ continue;
401
+ }
402
+ const w = segsWidth(token);
403
+ if (w > width) {
404
+ if (used > 0) flush();
405
+ const pieces = splitWord(token, width);
406
+ for (const piece of pieces.slice(0, -1)) {
407
+ line = piece;
408
+ flush();
409
+ }
410
+ line = pieces.at(-1) ?? [];
411
+ used = segsWidth(line);
412
+ } else if (used === 0) {
413
+ line = [...token];
414
+ used = w;
415
+ } else if (used + 1 + w <= width) {
416
+ line.push({ text: " ", style: "plain" }, ...token);
417
+ used += 1 + w;
418
+ } else {
419
+ flush();
420
+ line = [...token];
421
+ used = w;
422
+ }
423
+ }
424
+ flush();
425
+ return lines;
426
+ }
427
+ var SEP = " \u2502 ";
428
+ var RULE_JOIN = "\u2500\u253C\u2500";
429
+ var MAX_MIN_WIDTH = 12;
430
+ var dim = (text) => ({ text, style: "dim" });
431
+ function rtrim(line) {
432
+ const out = [...line];
433
+ while (out.length) {
434
+ const last = out[out.length - 1];
435
+ const text = last.text.trimEnd();
436
+ if (text) {
437
+ out[out.length - 1] = { ...last, text };
438
+ break;
439
+ }
440
+ out.pop();
441
+ }
442
+ return out;
443
+ }
444
+ var bold = (segs) => segs.map((s) => s.style === "plain" ? { ...s, style: "bold" } : s);
445
+ function pad(segs, width, align, last) {
446
+ const space = Math.max(0, width - segsWidth(segs));
447
+ const left = align === "right" ? space : align === "center" ? Math.floor(space / 2) : 0;
448
+ const right = last ? 0 : space - left;
449
+ return [...left ? [{ text: " ".repeat(left), style: "plain" }] : [], ...segs, ...right ? [{ text: " ".repeat(right), style: "plain" }] : []];
450
+ }
451
+ function columnWidths(table, available) {
452
+ const columns = table.align.length;
453
+ const all = [table.header, ...table.rows];
454
+ const natural = [];
455
+ const minimum = [];
456
+ for (let c = 0; c < columns; c++) {
457
+ let widest = 1;
458
+ let longestWord = 1;
459
+ for (const row of all) {
460
+ const tokens = tokenize(row[c]);
461
+ for (const line of wrap(tokens, Number.POSITIVE_INFINITY)) widest = Math.max(widest, segsWidth(line));
462
+ for (const token of tokens) if (token !== "br") longestWord = Math.max(longestWord, segsWidth(token));
463
+ }
464
+ natural.push(widest);
465
+ minimum.push(Math.min(longestWord, MAX_MIN_WIDTH, widest));
466
+ }
467
+ if (minimum.reduce((a, b) => a + b, 0) > available) return void 0;
468
+ const widths = [...natural];
469
+ let total = widths.reduce((a, b) => a + b, 0);
470
+ while (total > available) {
471
+ let pick = -1;
472
+ for (let c = 0; c < columns; c++) if (widths[c] > minimum[c] && (pick < 0 || widths[c] > widths[pick])) pick = c;
473
+ if (pick < 0) return void 0;
474
+ widths[pick]--;
475
+ total--;
476
+ }
477
+ return widths;
478
+ }
479
+ function stacked(table, width) {
480
+ const lines = [];
481
+ table.rows.forEach((row, r) => {
482
+ if (r > 0) lines.push([dim("\u2500".repeat(Math.min(width, 40)))]);
483
+ row.forEach((value, c) => {
484
+ const label = [...bold(table.header[c]), { text: ": ", style: "plain" }, ...value];
485
+ lines.push(...wrap(tokenize(label), width));
486
+ });
487
+ });
488
+ return lines.length ? lines : [bold(table.header.flat())];
489
+ }
490
+ function layoutTable(table, width) {
491
+ const columns = table.align.length;
492
+ const available = Math.max(columns, width - (columns - 1) * SEP.length);
493
+ const widths = columnWidths(table, available);
494
+ if (!widths) return stacked(table, Math.max(10, width));
495
+ const renderRow = (cells, header) => {
496
+ const wrapped = cells.map((segs, c) => wrap(tokenize(header ? bold(segs) : segs), widths[c]));
497
+ const height = Math.max(...wrapped.map((w) => w.length));
498
+ const lines = [];
499
+ for (let l = 0; l < height; l++) {
500
+ const line = [];
501
+ wrapped.forEach((w, c) => {
502
+ if (c > 0) line.push(dim(SEP));
503
+ line.push(...pad(w[l] ?? [], widths[c], table.align[c], c === columns - 1));
504
+ });
505
+ lines.push(rtrim(line));
506
+ }
507
+ return { lines, height };
508
+ };
509
+ const rule = [dim(widths.map((w) => "\u2500".repeat(w)).join(RULE_JOIN))];
510
+ const head = renderRow(table.header, true);
511
+ const body = table.rows.map((r) => renderRow(r, false));
512
+ const wrappedRows = body.some((b) => b.height > 1);
513
+ const out = [...head.lines, rule];
514
+ body.forEach((b, i) => {
515
+ if (wrappedRows && i > 0) out.push(rule);
516
+ out.push(...b.lines);
517
+ });
518
+ return out;
519
+ }
213
520
 
214
521
  // src/ui/theme.ts
215
522
  function color(name) {
@@ -218,30 +525,111 @@ function color(name) {
218
525
  var ACCENT = "green";
219
526
 
220
527
  // src/ui/Markdown.tsx
221
- import { jsx } from "react/jsx-runtime";
222
- function inline(line) {
223
- return line.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((part, i) => {
224
- if (part.length > 4 && part.startsWith("**") && part.endsWith("**")) {
225
- return /* @__PURE__ */ jsx(Text, { bold: true, children: part.slice(2, -2) }, i);
226
- }
227
- if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) {
228
- return /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: part.slice(1, -1) }, i);
229
- }
230
- return part;
231
- });
528
+ import { jsx, jsxs } from "react/jsx-runtime";
529
+ function segment(seg, key) {
530
+ switch (seg.style) {
531
+ case "bold":
532
+ return /* @__PURE__ */ jsx(Text, { bold: true, children: seg.text }, key);
533
+ case "italic":
534
+ return /* @__PURE__ */ jsx(Text, { italic: true, children: seg.text }, key);
535
+ case "strike":
536
+ return /* @__PURE__ */ jsx(Text, { strikethrough: true, children: seg.text }, key);
537
+ case "link":
538
+ return /* @__PURE__ */ jsx(Text, { underline: true, color: color("cyan"), children: seg.text }, key);
539
+ case "code":
540
+ return /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: seg.text }, key);
541
+ case "dim":
542
+ return /* @__PURE__ */ jsx(Text, { dimColor: true, children: seg.text }, key);
543
+ default:
544
+ return seg.text;
545
+ }
232
546
  }
233
- function Markdown({ text }) {
234
- let inCode = false;
547
+ var inline = (line) => parseInline(line).map(segment);
548
+ var HEADING = /^\s{0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
549
+ var RULE = /^\s{0,3}([-*_])(?:\s*\1){2,}\s*$/;
550
+ var QUOTE = /^\s{0,3}((?:>\s?)+)(.*)$/;
551
+ var BULLET = /^(\s*)[-*+]\s+(.*)$/;
552
+ var NUMBERED = /^(\s*)(\d{1,9})[.)]\s+(.*)$/;
553
+ var BULLETS = ["\u2022", "\u25E6", "\u25AA"];
554
+ function hanging(key, marker, body, width, padding = 0) {
555
+ const textWidth = Math.max(10, width - padding - displayWidth(marker) - 1);
556
+ return /* @__PURE__ */ jsxs(Box, { paddingLeft: padding, children: [
557
+ /* @__PURE__ */ jsx(Box, { flexShrink: 0, marginRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: marker }) }),
558
+ /* @__PURE__ */ jsx(Box, { width: textWidth, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { children: body }) })
559
+ ] }, key);
560
+ }
561
+ function Markdown({ text, indent = 2 }) {
562
+ const { stdout } = useStdout();
563
+ const width = Math.max(20, (stdout?.columns ?? 80) - indent - 1);
564
+ const lines = text.split("\n");
235
565
  const rows = [];
236
- text.split("\n").forEach((line, i) => {
566
+ let inCode = false;
567
+ let indents = [];
568
+ const depth = (spaces) => {
569
+ while (indents.length && spaces < indents.at(-1)) indents.pop();
570
+ if (!indents.length || spaces > indents.at(-1)) indents.push(spaces);
571
+ return indents.length - 1;
572
+ };
573
+ for (let i = 0; i < lines.length; i++) {
574
+ const line = lines[i];
237
575
  if (line.trimStart().startsWith("```")) {
238
576
  inCode = !inCode;
239
- return;
577
+ continue;
240
578
  }
241
- rows.push(
242
- inCode ? /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i) : /* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i)
243
- );
244
- });
579
+ if (inCode) {
580
+ rows.push(
581
+ /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i)
582
+ );
583
+ continue;
584
+ }
585
+ const parsed = parseTable(lines, i);
586
+ if (parsed) {
587
+ layoutTable(parsed.table, width).forEach((tableLine, n) => {
588
+ rows.push(/* @__PURE__ */ jsx(Text, { children: tableLine.length ? tableLine.map(segment) : " " }, `${i}-${n}`));
589
+ });
590
+ i = parsed.end - 1;
591
+ continue;
592
+ }
593
+ const heading = HEADING.exec(line);
594
+ if (heading) {
595
+ const level = heading[1].length;
596
+ rows.push(
597
+ /* @__PURE__ */ jsx(Text, { bold: true, underline: level === 1, color: level <= 2 ? color(ACCENT) : void 0, children: parseInline(heading[2]).map((s) => s.text).join("") }, i)
598
+ );
599
+ continue;
600
+ }
601
+ if (RULE.test(line)) {
602
+ rows.push(
603
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.min(width, 80)) }, i)
604
+ );
605
+ continue;
606
+ }
607
+ const quote = QUOTE.exec(line);
608
+ if (quote) {
609
+ const depth2 = quote[1].replace(/\s/g, "").length;
610
+ rows.push(hanging(i, "\u258E".repeat(depth2), /* @__PURE__ */ jsx(Text, { italic: true, children: inline(quote[2]) }), width));
611
+ continue;
612
+ }
613
+ const bullet = BULLET.exec(line);
614
+ if (bullet) {
615
+ const padding = Math.min(bullet[1].replace(/\t/g, " ").length, 16);
616
+ const level = depth(padding);
617
+ const task = /^\[([ xX])\]\s+(.*)$/.exec(bullet[2]);
618
+ rows.push(
619
+ task ? hanging(i, task[1] === " " ? "\u2610" : "\u2611", inline(task[2]), width, padding) : hanging(i, BULLETS[Math.min(level, BULLETS.length - 1)], inline(bullet[2]), width, padding)
620
+ );
621
+ continue;
622
+ }
623
+ const numbered = NUMBERED.exec(line);
624
+ if (numbered) {
625
+ const padding = Math.min(numbered[1].replace(/\t/g, " ").length, 16);
626
+ depth(padding);
627
+ rows.push(hanging(i, `${numbered[2]}.`, inline(numbered[3]), width, padding));
628
+ continue;
629
+ }
630
+ if (line.trim()) indents = [];
631
+ rows.push(/* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i));
632
+ }
245
633
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
246
634
  }
247
635
 
@@ -253,7 +641,7 @@ import { useState } from "react";
253
641
  var isFocusReport = (input) => input === "[I" || input === "[O";
254
642
 
255
643
  // src/ui/LinePrompt.tsx
256
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
644
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
257
645
  function LinePrompt({ label, mask, onSubmit, onCancel }) {
258
646
  const [value, setValue] = useState("");
259
647
  useInput((input, key) => {
@@ -263,18 +651,18 @@ function LinePrompt({ label, mask, onSubmit, onCancel }) {
263
651
  else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow && !isFocusReport(input)) setValue((v) => v + input.replace(/[\r\n]/g, ""));
264
652
  });
265
653
  const shown = mask ? "\u2022".repeat(value.length) : value;
266
- return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs(Text2, { children: [
654
+ return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
267
655
  `${label}: `,
268
656
  shown,
269
657
  /* @__PURE__ */ jsx2(Text2, { inverse: true, children: " " }),
270
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " enter simpan \xB7 esc batal" })
658
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t(" enter save \xB7 esc cancel") })
271
659
  ] }) });
272
660
  }
273
661
 
274
662
  // src/ui/ListPicker.tsx
275
663
  import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
276
664
  import { useState as useState2 } from "react";
277
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
665
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
278
666
  var VISIBLE = 12;
279
667
  function ListPicker({ title, items, onPick }) {
280
668
  const [filter, setFilter] = useState2("");
@@ -295,20 +683,20 @@ function ListPicker({ title, items, onPick }) {
295
683
  }
296
684
  });
297
685
  const start = Math.max(0, Math.min(cursor - 5, shown.length - VISIBLE));
298
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
299
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: `${title} (\u2191\u2193 enter \xB7 ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 esc batal)` }),
300
- shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */ jsxs2(Text3, { color: start + i === cursor && !item.disabled ? color("green") : void 0, dimColor: item.disabled, children: [
686
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
687
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: t("{title} (\u2191\u2193 enter \xB7 type to filter{filter} \xB7 esc cancel)", { title, filter: filter ? `: ${filter}` : "" }) }),
688
+ shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */ jsxs3(Text3, { color: start + i === cursor && !item.disabled ? color("green") : void 0, dimColor: item.disabled, children: [
301
689
  `${start + i === cursor ? "\u203A" : " "} ${item.label}`,
302
690
  item.hint ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ` ${item.hint}` }) : null
303
691
  ] }, item.id)),
304
- shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tidak ada yang cocok." })
692
+ shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: t("No matches.") })
305
693
  ] });
306
694
  }
307
695
 
308
696
  // src/ui/ModelPicker.tsx
309
697
  import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
310
698
  import { useState as useState3 } from "react";
311
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
699
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
312
700
  var VISIBLE2 = 14;
313
701
  function ModelPicker({ groups, current, onPick }) {
314
702
  const [filter, setFilter] = useState3("");
@@ -340,12 +728,12 @@ function ModelPicker({ groups, current, onPick }) {
340
728
  }
341
729
  });
342
730
  const start = Math.max(0, Math.min(cursor - 5, rows.length - VISIBLE2));
343
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
344
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: `Pilih model (ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 \u2191\u2193 enter \xB7 esc batal)` }),
731
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
732
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: t("Choose a model (type to filter{filter} \xB7 \u2191\u2193 enter \xB7 esc cancel)", { filter: filter ? `: ${filter}` : "" }) }),
345
733
  rows.slice(start, start + VISIBLE2).map(
346
734
  (row, i) => row.kind === "header" ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: `\u2500\u2500 ${row.text} ` }, `h${start + i}`) : /* @__PURE__ */ jsx4(Text4, { color: start + i === cursor ? color("green") : void 0, children: `${start + i === cursor ? "\u203A" : " "} ${row.label}${row.ref === current ? " (aktif)" : ""}` }, row.ref)
347
735
  ),
348
- !selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Tidak ada model yang cocok." }) : null,
736
+ !selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("No matching models.") }) : null,
349
737
  groups.filter((g) => g.error).map((g) => /* @__PURE__ */ jsx4(Text4, { color: color("yellow"), children: `${g.providerName} \u2014 ${g.error}` }, `e-${g.providerId}`))
350
738
  ] });
351
739
  }
@@ -353,7 +741,7 @@ function ModelPicker({ groups, current, onPick }) {
353
741
  // src/ui/ProviderMenu.tsx
354
742
  import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
355
743
  import { useState as useState4 } from "react";
356
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
744
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
357
745
  function ProviderMenu({ entries, onPick }) {
358
746
  const rows = [...entries.map((e) => e.id), "custom"];
359
747
  const [index, setIndex] = useState4(0);
@@ -363,9 +751,9 @@ function ProviderMenu({ entries, onPick }) {
363
751
  else if (key.return) onPick(rows[index]);
364
752
  else if (key.escape) onPick(void 0);
365
753
  });
366
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
367
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Provider (\u2191\u2193 enter, esc batal) \xB7 \u2713 siap \xB7 \u25CB butuh API key" }),
368
- entries.map((e, i) => /* @__PURE__ */ jsxs4(Text5, { color: i === index ? color("green") : void 0, children: [
754
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
755
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: t("Provider (\u2191\u2193 enter, esc cancel) \xB7 \u2713 ready \xB7 \u25CB needs API key") }),
756
+ entries.map((e, i) => /* @__PURE__ */ jsxs5(Text5, { color: i === index ? color("green") : void 0, children: [
369
757
  `${i === index ? "\u203A" : " "} ${e.ready ? "\u2713" : "\u25CB"} ${e.name}`,
370
758
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${e.baseURL}` })
371
759
  ] }, e.id)),
@@ -388,8 +776,8 @@ function DiffView({ diff }) {
388
776
  }
389
777
 
390
778
  // src/ui/PermissionPrompt.tsx
391
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
392
- var VERB = { edit: "edit", write: "tulis", bash: "jalankan", fetch: "ambil" };
779
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
780
+ var VERB = { edit: "edit", write: "write", bash: "run", fetch: "fetch" };
393
781
  function PermissionPrompt({
394
782
  request,
395
783
  onAnswer
@@ -402,16 +790,16 @@ function PermissionPrompt({
402
790
  else if (k2 === "n" || key.escape) onAnswer("no");
403
791
  });
404
792
  const rules = request.sessionRules ?? [];
405
- const sessionLabel = !rules.length ? null : request.kind === "edit" ? "ya untuk semua edit di project ini, sesi ini" : `ya sesi ini untuk ${rules.join(", ")}`;
406
- return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
407
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}Izinkan ${VERB[request.tool] ?? request.tool} ${request.target}?` }),
793
+ const sessionLabel = !rules.length ? null : request.kind === "edit" ? t("yes for all edits in this project, this session") : t("yes this session for {rules}", { rules: rules.join(", ") });
794
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
795
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}${t("Allow {verb} {target}?", { verb: t(VERB[request.tool] ?? request.tool), target: request.target })}` }),
408
796
  request.preview ? /* @__PURE__ */ jsx7(DiffView, { diff: request.preview }) : null,
409
- /* @__PURE__ */ jsxs5(Text7, { children: [
410
- /* @__PURE__ */ jsx7(Text7, { color: color("green"), children: "[y] ya" }),
797
+ /* @__PURE__ */ jsxs6(Text7, { children: [
798
+ /* @__PURE__ */ jsx7(Text7, { color: color("green"), children: t("[y] yes") }),
411
799
  " ",
412
800
  sessionLabel ? /* @__PURE__ */ jsx7(Text7, { color: color("cyan"), children: `[a] ${sessionLabel} ` }) : null,
413
- /* @__PURE__ */ jsx7(Text7, { color: color("yellow"), children: "[s] ya semua " }),
414
- /* @__PURE__ */ jsx7(Text7, { color: color("red"), children: "[n] tidak" })
801
+ /* @__PURE__ */ jsx7(Text7, { color: color("yellow"), children: t("[s] yes to all ") }),
802
+ /* @__PURE__ */ jsx7(Text7, { color: color("red"), children: t("[n] no") })
415
803
  ] })
416
804
  ] });
417
805
  }
@@ -434,7 +822,7 @@ function completeFile(value, cwd) {
434
822
  }
435
823
 
436
824
  // src/ui/PromptInput.tsx
437
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
825
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
438
826
  function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
439
827
  const [value, setValue] = useState5("");
440
828
  const [historyIndex, setHistoryIndex] = useState5(null);
@@ -508,13 +896,13 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
508
896
  },
509
897
  { isActive: !disabled }
510
898
  );
511
- return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", children: [
512
- /* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs6(Text8, { children: [
899
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
900
+ /* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs7(Text8, { children: [
513
901
  /* @__PURE__ */ jsx8(Text8, { color: color("green"), children: "> " }),
514
902
  value,
515
903
  disabled ? "" : /* @__PURE__ */ jsx8(Text8, { inverse: true, children: " " })
516
904
  ] }) }),
517
- suggestions.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} /${s.name} ${s.description}` }, s.name))
905
+ suggestions.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} /${s.name} ${t(s.description)}` }, s.name))
518
906
  ] });
519
907
  }
520
908
 
@@ -526,11 +914,11 @@ var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
526
914
  function Spinner({ label, startedAt }) {
527
915
  const [tick, setTick] = useState6(0);
528
916
  useEffect(() => {
529
- const id = setInterval(() => setTick((t) => t + 1), 120);
917
+ const id = setInterval(() => setTick((t2) => t2 + 1), 120);
530
918
  return () => clearInterval(id);
531
919
  }, []);
532
920
  const seconds = Math.floor((Date.now() - startedAt) / 1e3);
533
- return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${label}\u2026 (${seconds} dtk \xB7 esc batal)` });
921
+ return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${t("{label}\u2026 ({seconds}s \xB7 esc to cancel)", { label, seconds })}` });
534
922
  }
535
923
 
536
924
  // src/ui/StatusBar.tsx
@@ -547,13 +935,13 @@ function formatTokens(n) {
547
935
  }
548
936
  function StatusBar({ mode, tokens, busy, model, reasoning = "auto" }) {
549
937
  const danger = mode === "allowAll";
550
- return /* @__PURE__ */ jsx10(Text10, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 reasoning: ${reasoning} \xB7 shift+tab ganti mode \xB7 ${formatTokens(tokens)} token \xB7 ${busy ? "esc batal" : "/ perintah"}` });
938
+ return /* @__PURE__ */ jsx10(Text10, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 reasoning: ${reasoning} \xB7 ${t("shift+tab switch mode")} \xB7 ${t("{n} tokens", { n: formatTokens(tokens) })} \xB7 ${busy ? t("esc to cancel") : t("/ for commands")}` });
551
939
  }
552
940
 
553
941
  // src/ui/McpMenu.tsx
554
942
  import { Box as Box9, Text as Text11, useInput as useInput7 } from "ink";
555
943
  import { useState as useState7 } from "react";
556
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
944
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
557
945
  var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
558
946
  function McpMenu({ items, onPick }) {
559
947
  const [index, setIndex] = useState7(0);
@@ -563,9 +951,9 @@ function McpMenu({ items, onPick }) {
563
951
  else if (key.return) onPick(items[index]?.name);
564
952
  else if (key.escape) onPick(void 0);
565
953
  });
566
- return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
567
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "Server MCP (enter: pasang / hapus \xB7 esc tutup) \xB7 \u2713 aktif \u25CB belum \u2717 error" }),
568
- items.map((item, i) => /* @__PURE__ */ jsxs7(Text11, { color: i === index ? color("green") : void 0, children: [
954
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
955
+ /* @__PURE__ */ jsx11(Text11, { bold: true, children: t("MCP servers (enter: install / remove \xB7 esc close) \xB7 \u2713 active \u25CB not installed \u2717 error") }),
956
+ items.map((item, i) => /* @__PURE__ */ jsxs8(Text11, { color: i === index ? color("green") : void 0, children: [
569
957
  `${i === index ? "\u203A" : " "} ${MARK[item.status]} ${item.name.padEnd(12)}`,
570
958
  /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: item.status === "error" ? ` ${item.error ?? ""}` : ` ${item.description}` })
571
959
  ] }, item.name))
@@ -574,7 +962,7 @@ function McpMenu({ items, onPick }) {
574
962
 
575
963
  // src/ui/PlanApproval.tsx
576
964
  import { Box as Box10, Text as Text12, useInput as useInput8 } from "ink";
577
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
965
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
578
966
  function PlanApproval({ plan, onAnswer }) {
579
967
  useInput8((input, key) => {
580
968
  const k2 = input.toLowerCase();
@@ -582,15 +970,15 @@ function PlanApproval({ plan, onAnswer }) {
582
970
  else if (k2 === "y") onAnswer("default");
583
971
  else if (k2 === "n" || key.escape) onAnswer("no");
584
972
  });
585
- return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
586
- /* @__PURE__ */ jsx12(Text12, { bold: true, children: "Rencana dari agent" }),
587
- /* @__PURE__ */ jsx12(Markdown, { text: plan }),
588
- /* @__PURE__ */ jsxs8(Text12, { children: [
589
- /* @__PURE__ */ jsx12(Text12, { color: color("green"), children: "[a] ya, edit otomatis" }),
973
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
974
+ /* @__PURE__ */ jsx12(Text12, { bold: true, children: t("Plan from the agent") }),
975
+ /* @__PURE__ */ jsx12(Markdown, { text: plan, indent: 5 }),
976
+ /* @__PURE__ */ jsxs9(Text12, { children: [
977
+ /* @__PURE__ */ jsx12(Text12, { color: color("green"), children: t("[a] yes, auto-edit") }),
590
978
  " ",
591
- /* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: "[y] ya, tanya tiap langkah" }),
979
+ /* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: t("[y] yes, ask at each step") }),
592
980
  " ",
593
- /* @__PURE__ */ jsx12(Text12, { color: color("red"), children: "[n] belum, perbaiki dulu" })
981
+ /* @__PURE__ */ jsx12(Text12, { color: color("red"), children: t("[n] not yet, revise first") })
594
982
  ] })
595
983
  ] });
596
984
  }
@@ -600,34 +988,34 @@ import { Box as Box11, Text as Text13 } from "ink";
600
988
  import { jsx as jsx13 } from "react/jsx-runtime";
601
989
  var MARK2 = { completed: "\u2611", in_progress: "\u25FC", pending: "\u2610" };
602
990
  function TodoList({ items }) {
603
- if (!items.length || items.every((t) => t.status === "completed")) return null;
604
- return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: items.map((t, i) => /* @__PURE__ */ jsx13(Text13, { dimColor: t.status === "completed", color: t.status === "in_progress" ? color("yellow") : void 0, children: `${MARK2[t.status]} ${t.content}` }, i)) });
991
+ if (!items.length || items.every((t2) => t2.status === "completed")) return null;
992
+ return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: items.map((t2, i) => /* @__PURE__ */ jsx13(Text13, { dimColor: t2.status === "completed", color: t2.status === "in_progress" ? color("yellow") : void 0, children: `${MARK2[t2.status]} ${t2.content}` }, i)) });
605
993
  }
606
994
 
607
995
  // src/ui/ToolBlock.tsx
608
996
  import { Box as Box12, Text as Text14 } from "ink";
609
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
997
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
610
998
  var PREVIEW_LINES = 5;
611
999
  function ToolBlock({ tool, target, output, display, isError, done, expanded, sub }) {
612
1000
  const isTask = tool === "task";
613
1001
  const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
614
1002
  const lines = (output ?? "").split("\n");
615
1003
  const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
616
- return /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 2, children: [
617
- /* @__PURE__ */ jsxs9(Text14, { children: [
1004
+ return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 2, children: [
1005
+ /* @__PURE__ */ jsxs10(Text14, { children: [
618
1006
  /* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
619
1007
  /* @__PURE__ */ jsx14(Text14, { bold: true, children: toolLabel(tool) }),
620
1008
  /* @__PURE__ */ jsx14(Text14, { children: ` ${target}` }),
621
1009
  display && !isDiff && !isTask ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` (${display})` }) : null
622
1010
  ] }),
623
- sub?.length ? /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 3, children: [
1011
+ sub?.length ? /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 3, children: [
624
1012
  sub.slice(-6).map((s) => /* @__PURE__ */ jsx14(Text14, { dimColor: s.done && !s.isError, color: s.isError ? color("red") : void 0, children: `\u23BF ${toolLabel(s.tool)} ${s.target}` }, s.id)),
625
- sub.length > 6 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: `\u2026 ${sub.length - 6} langkah sebelumnya` }) : null
1013
+ sub.length > 6 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: t("\u2026 {n} earlier steps", { n: sub.length - 6 }) }) : null
626
1014
  ] }) : null,
627
- isTask && done ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : color("green"), children: `${isError ? "\u2717 gagal" : "\u2713 selesai"}${display ? ` \xB7 ${display}` : ""}` }) }) : null,
628
- isDiff ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(DiffView, { diff: display }) }) : output && (isError || tool === "bash") && !(isTask && !isError) ? /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 3, children: [
1015
+ isTask && done ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : color("green"), children: `${isError ? t("\u2717 failed") : t("\u2713 done")}${display ? ` \xB7 ${display}` : ""}` }) }) : null,
1016
+ isDiff ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(DiffView, { diff: display }) }) : output && (isError || tool === "bash") && !(isTask && !isError) ? /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 3, children: [
629
1017
  shown.map((line, i) => /* @__PURE__ */ jsx14(Text14, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
630
- lines.length > shown.length ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: `\u2026 ${lines.length - shown.length} baris lagi (ctrl+o)` }) : null
1018
+ lines.length > shown.length ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: t("\u2026 {n} more lines (ctrl+o)", { n: lines.length - shown.length }) }) : null
631
1019
  ] }) : null
632
1020
  ] });
633
1021
  }
@@ -636,13 +1024,13 @@ function ToolBlock({ tool, target, output, display, isError, done, expanded, sub
636
1024
  var nextId = 0;
637
1025
  var entry = (e) => ({ ...e, id: nextId++ });
638
1026
  var NOTICES = {
639
- compacted: ["Percakapan diringkas agar muat di konteks model.", "info"],
640
- stepLimit: ['Batas 50 langkah tercapai. Ketik "lanjut" untuk meneruskan.', "warn"],
641
- aborted: ["Dibatalkan.", "warn"]
1027
+ compacted: ["Conversation summarized to fit the model context.", "info"],
1028
+ stepLimit: ['Step limit of 50 reached. Type "continue" to keep going.', "warn"],
1029
+ aborted: ["Cancelled.", "warn"]
642
1030
  };
643
1031
  var isEmptyCont = (e) => e.kind === "assistant" && e.cont && !e.text;
644
- function endTurn(t) {
645
- return { done: [...t.done, ...t.live.filter((e) => !isEmptyCont(e))], live: [] };
1032
+ function endTurn(t2) {
1033
+ return { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [] };
646
1034
  }
647
1035
  var TOOL_MARKUP = /<\/?tool_call>|<\/?arg_(?:key|value)>|Tool call list|^\s*\d+\.\s*Tool:|`\s*=\s*`/;
648
1036
  function finishedCut(text) {
@@ -653,87 +1041,88 @@ function finishedCut(text) {
653
1041
  for (const line of lines.slice(0, -1)) {
654
1042
  if (TOOL_MARKUP.test(line)) break;
655
1043
  if (line.trimStart().startsWith("```")) inFence = !inFence;
1044
+ const tableRow = !inFence && !line.trimStart().startsWith("```") && line.includes("|") && line.trim() !== "";
656
1045
  pos += line.length + 1;
657
- if (!inFence) cut = pos - 1;
1046
+ if (!inFence && !tableRow) cut = pos - 1;
658
1047
  }
659
1048
  return cut;
660
1049
  }
661
- function flushLines(t) {
662
- const last = t.live.at(-1);
663
- if (last?.kind !== "assistant") return t;
1050
+ function flushLines(t2) {
1051
+ const last = t2.live.at(-1);
1052
+ if (last?.kind !== "assistant") return t2;
664
1053
  const cut = finishedCut(last.text);
665
- if (cut < 0) return t;
1054
+ if (cut < 0) return t2;
666
1055
  const chunk = last.text.slice(0, cut);
667
- if (!last.cont && !chunk.trim()) return t;
1056
+ if (!last.cont && !chunk.trim()) return t2;
668
1057
  const rest = entry({ kind: "assistant", text: last.text.slice(cut + 1), cont: true, flushed: (last.flushed ?? 0) + cut + 1 });
669
- return { done: [...t.done, ...t.live.slice(0, -1).filter((e) => !isEmptyCont(e)), { ...last, text: chunk || " " }], live: [rest] };
1058
+ return { done: [...t2.done, ...t2.live.slice(0, -1).filter((e) => !isEmptyCont(e)), { ...last, text: chunk || " " }], live: [rest] };
670
1059
  }
671
- function applyEvent(t, event) {
1060
+ function applyEvent(t2, event) {
672
1061
  switch (event.type) {
673
1062
  case "text": {
674
- const last = t.live.at(-1);
675
- if (last?.kind === "assistant") return flushLines({ ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] });
676
- return flushLines({ ...t, live: [...t.live, entry({ kind: "assistant", text: event.delta })] });
1063
+ const last = t2.live.at(-1);
1064
+ if (last?.kind === "assistant") return flushLines({ ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] });
1065
+ return flushLines({ ...t2, live: [...t2.live, entry({ kind: "assistant", text: event.delta })] });
677
1066
  }
678
1067
  case "thinking": {
679
- const last = t.live.at(-1);
680
- if (last?.kind === "thinking") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
681
- return { ...t, live: [...t.live, entry({ kind: "thinking", text: event.delta })] };
1068
+ const last = t2.live.at(-1);
1069
+ if (last?.kind === "thinking") return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
1070
+ return { ...t2, live: [...t2.live, entry({ kind: "thinking", text: event.delta })] };
682
1071
  }
683
1072
  case "textReplace": {
684
- const last = t.live.at(-1);
685
- if (last?.kind !== "assistant") return t;
686
- return { ...t, live: [...t.live.slice(0, -1), { ...last, text: event.text.slice(last.flushed ?? 0) }] };
1073
+ const last = t2.live.at(-1);
1074
+ if (last?.kind !== "assistant") return t2;
1075
+ return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: event.text.slice(last.flushed ?? 0) }] };
687
1076
  }
688
1077
  case "toolStart": {
689
- const running = t.live.some((e) => e.kind === "tool" && !e.done);
1078
+ const running = t2.live.some((e) => e.kind === "tool" && !e.done);
690
1079
  const tool = entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false });
691
- return running ? { ...t, live: [...t.live, tool] } : { done: [...t.done, ...t.live.filter((e) => !isEmptyCont(e))], live: [tool] };
1080
+ return running ? { ...t2, live: [...t2.live, tool] } : { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [tool] };
692
1081
  }
693
1082
  case "toolEnd": {
694
- const live = t.live.map(
1083
+ const live = t2.live.map(
695
1084
  (e) => e.kind === "tool" && e.callId === event.id ? { ...e, output: event.output, display: event.display, isError: event.isError, done: true } : e
696
1085
  );
697
1086
  const firstRunning = live.findIndex((e) => e.kind === "tool" && !e.done);
698
1087
  const cut = firstRunning === -1 ? live.length : firstRunning;
699
- return { done: [...t.done, ...live.slice(0, cut)], live: live.slice(cut) };
1088
+ return { done: [...t2.done, ...live.slice(0, cut)], live: live.slice(cut) };
700
1089
  }
701
1090
  case "subagent": {
702
1091
  const inner = event.event;
703
- if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t;
1092
+ if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t2;
704
1093
  const update = (e) => {
705
1094
  if (e.kind !== "tool" || e.callId !== event.parentId) return e;
706
1095
  const sub = e.sub ?? [];
707
1096
  if (inner.type === "toolStart") return { ...e, sub: [...sub, { id: inner.id, tool: inner.tool, target: inner.target, done: false }] };
708
1097
  return { ...e, sub: sub.map((s) => s.id === inner.id ? { ...s, done: true, isError: inner.isError } : s) };
709
1098
  };
710
- return { ...t, live: t.live.map(update) };
1099
+ return { ...t2, live: t2.live.map(update) };
711
1100
  }
712
1101
  case "error":
713
- return { ...t, live: [...t.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
1102
+ return { ...t2, live: [...t2.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
714
1103
  default: {
715
1104
  const notice = NOTICES[event.type];
716
- return notice ? { ...t, live: [...t.live, entry({ kind: "notice", text: notice[0], tone: notice[1] })] } : t;
1105
+ return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0]), tone: notice[1] })] } : t2;
717
1106
  }
718
1107
  }
719
1108
  }
720
1109
 
721
1110
  // src/ui/App.tsx
722
- import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1111
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
723
1112
  function helpText(runtime) {
724
1113
  const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
725
1114
  return [
726
- ...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${c.description}`),
727
- ...custom.length ? ["", "Perintah custom:", ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
728
- ...runtime.skills.length ? ["", "Skill:", ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
1115
+ ...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${t(c.description)}`),
1116
+ ...custom.length ? ["", t("Custom commands:"), ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
1117
+ ...runtime.skills.length ? ["", t("Skills:"), ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
729
1118
  "",
730
- "shift+tab ganti mode izin esc batalkan giliran",
731
- "ctrl+o output alat terakhir lengkap \\ + enter baris baru",
732
- "ctrl+t buka/tutup thinking model",
733
- "@file + tab lengkapi nama file \u2191\u2193 riwayat input"
1119
+ t("shift+tab switch permission mode esc cancel the turn"),
1120
+ t("ctrl+o full output of the last tool \\ + enter new line"),
1121
+ t("ctrl+t show/hide the model\u2019s thinking"),
1122
+ t("@file + tab complete file names \u2191\u2193 input history")
734
1123
  ].join("\n");
735
1124
  }
736
- var INIT_PROMPT = "Pelajari project ini (struktur folder, file package/build, perintah test dan lint, konvensi kode yang terlihat). Lalu buat AGENTS.md di root project, atau perbarui bila sudah ada, berisi: cara build, test, dan lint; struktur singkat; konvensi penting. Ringkas dan faktual, hanya yang benar-benar ada di project.";
1125
+ var INIT_PROMPT = "Study this project (folder structure, package/build files, test and lint commands, visible code conventions). Then create AGENTS.md in the project root, or update it if it already exists, covering: how to build, test, and lint; a short structure overview; important conventions. Keep it concise and factual, only what actually exists in the project.";
737
1126
  var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
738
1127
  function App({ runtime, initialPrompt, version }) {
739
1128
  const { exit } = useApp();
@@ -761,13 +1150,13 @@ function App({ runtime, initialPrompt, version }) {
761
1150
  const [showThinking, setShowThinking] = useState8(false);
762
1151
  useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
763
1152
  const extraCommands = [
764
- ...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? "perintah custom" })),
765
- ...runtime.skills.filter((s) => !runtime.commands.some((c) => c.name.toLowerCase() === s.name.toLowerCase())).filter((s) => !SLASH_COMMANDS.some((b) => b.name === s.name.toLowerCase())).map((s) => ({ name: s.name, description: `skill \xB7 ${s.description.slice(0, 50)}` }))
1153
+ ...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? t("custom command") })),
1154
+ ...runtime.skills.filter((s) => !runtime.commands.some((c) => c.name.toLowerCase() === s.name.toLowerCase())).filter((s) => !SLASH_COMMANDS.some((b) => b.name === s.name.toLowerCase())).map((s) => ({ name: s.name, description: t("skill \xB7 {description}", { description: s.description.slice(0, 50) }) }))
766
1155
  ];
767
1156
  const lastTool = useRef(null);
768
1157
  const toolTargets = useRef(/* @__PURE__ */ new Map());
769
1158
  const notice = useCallback((text, tone = "info") => {
770
- setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "notice", text, tone })] }));
1159
+ setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "notice", text, tone })] }));
771
1160
  }, []);
772
1161
  const onEvent = useCallback((event) => {
773
1162
  if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
@@ -775,7 +1164,7 @@ function App({ runtime, initialPrompt, version }) {
775
1164
  lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
776
1165
  }
777
1166
  if (event.type === "usage") setTokens(event.inputTokens + event.outputTokens);
778
- setTranscript((t) => applyEvent(t, event));
1167
+ setTranscript((t2) => applyEvent(t2, event));
779
1168
  }, []);
780
1169
  useEffect2(() => {
781
1170
  runtime.agent.onEvent = onEvent;
@@ -784,7 +1173,7 @@ function App({ runtime, initialPrompt, version }) {
784
1173
  }, [runtime, onEvent]);
785
1174
  const runTurn = useCallback(
786
1175
  async (text) => {
787
- setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "user", text })] }));
1176
+ setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "user", text })] }));
788
1177
  setBusy(true);
789
1178
  setStartedAt(Date.now());
790
1179
  controller.current = new AbortController();
@@ -807,11 +1196,11 @@ function App({ runtime, initialPrompt, version }) {
807
1196
  case "clear":
808
1197
  runtime.agent.clear();
809
1198
  setTokens(0);
810
- notice("Percakapan dikosongkan.");
1199
+ notice(t("Conversation cleared."));
811
1200
  return;
812
1201
  case "cost": {
813
1202
  const u = runtime.agent.totalUsage;
814
- notice(`${u.inputTokens.toLocaleString("id-ID")} token masuk \xB7 ${u.outputTokens.toLocaleString("id-ID")} token keluar`);
1203
+ notice(t("{input} tokens in \xB7 {output} tokens out", { input: u.inputTokens.toLocaleString(locale()), output: u.outputTokens.toLocaleString(locale()) }));
815
1204
  return;
816
1205
  }
817
1206
  case "compact": {
@@ -821,7 +1210,7 @@ function App({ runtime, initialPrompt, version }) {
821
1210
  try {
822
1211
  await runtime.agent.compact(controller.current.signal);
823
1212
  } catch (error) {
824
- notice(`Gagal meringkas: ${error.message}`, "error");
1213
+ notice(t("Could not summarize: {error}", { error: error.message }), "error");
825
1214
  }
826
1215
  controller.current = null;
827
1216
  setBusy(false);
@@ -836,7 +1225,7 @@ function App({ runtime, initialPrompt, version }) {
836
1225
  runtime.setReasoning(level);
837
1226
  writeGlobalConfig({ reasoning: level }, runtime.env);
838
1227
  setReasoning(level);
839
- notice(`Reasoning: ${level} (tersimpan sebagai default)`);
1228
+ notice(t("Reasoning: {level} (saved as default)", { level }));
840
1229
  } catch (error) {
841
1230
  notice(error.message, "error");
842
1231
  }
@@ -853,7 +1242,7 @@ function App({ runtime, initialPrompt, version }) {
853
1242
  id: level,
854
1243
  label: level === "auto" ? "Auto (model default)" : level.charAt(0).toUpperCase() + level.slice(1),
855
1244
  disabled: !supported.includes(level),
856
- hint: supported.includes(level) ? void 0 : "tidak didukung provider ini"
1245
+ hint: supported.includes(level) ? void 0 : t("not supported by this provider")
857
1246
  })),
858
1247
  onPick: (id) => {
859
1248
  setListPicker(null);
@@ -868,7 +1257,7 @@ function App({ runtime, initialPrompt, version }) {
868
1257
  runtime.setModel(args);
869
1258
  writeGlobalConfig({ model: args }, runtime.env);
870
1259
  setModelLabel(labelFor(runtime, args));
871
- notice(`Model: ${args} (tersimpan sebagai default)`);
1260
+ notice(t("Model: {model} (saved as default)", { model: args }));
872
1261
  } catch (error) {
873
1262
  notice(error.message, "error");
874
1263
  }
@@ -877,7 +1266,7 @@ function App({ runtime, initialPrompt, version }) {
877
1266
  try {
878
1267
  setPicker(await runtime.listModels());
879
1268
  } catch (error) {
880
- notice(`Tidak bisa mengambil daftar model: ${error.message}`, "error");
1269
+ notice(t("Cannot fetch the model list: {error}", { error: error.message }), "error");
881
1270
  }
882
1271
  return;
883
1272
  }
@@ -885,7 +1274,7 @@ function App({ runtime, initialPrompt, version }) {
885
1274
  const installed = readMcpFile(globalMcpPath(runtime.home));
886
1275
  const states = runtime.mcp.states();
887
1276
  const items = [
888
- ...CATALOG.map((c) => ({ name: c.name, description: c.description, installed: !!installed[c.name] })),
1277
+ ...CATALOG.map((c) => ({ name: c.name, description: t(c.description), installed: !!installed[c.name] })),
889
1278
  ...Object.keys(installed).filter((n) => !CATALOG.some((c) => c.name === n)).map((n) => ({ name: n, description: "(custom)", installed: true }))
890
1279
  ].map((i) => {
891
1280
  const s = states.find((st) => st.name === i.name);
@@ -897,7 +1286,7 @@ function App({ runtime, initialPrompt, version }) {
897
1286
  case "new":
898
1287
  runtime.newSession();
899
1288
  setTokens(0);
900
- notice("Sesi baru dimulai. Sesi sebelumnya tetap tersimpan (/resume).");
1289
+ notice(t("New session started. The previous session stays saved (/resume)."));
901
1290
  return;
902
1291
  case "session":
903
1292
  notice(
@@ -913,15 +1302,15 @@ function App({ runtime, initialPrompt, version }) {
913
1302
  case "resume": {
914
1303
  const others = Session.list(runtime.home, runtime.cwd).filter((s) => s.session.file !== runtime.session.file);
915
1304
  if (!others.length) {
916
- notice("Belum ada sesi lain di folder ini.");
1305
+ notice(t("No other sessions in this folder yet."));
917
1306
  return;
918
1307
  }
919
1308
  setListPicker({
920
- title: "Lanjutkan sesi",
1309
+ title: t("Continue a session"),
921
1310
  items: others.map((s) => ({
922
1311
  id: s.session.file,
923
- label: `${s.mtime.toLocaleString("id-ID")} ${s.preview || "(kosong)"}`,
924
- hint: `${s.session.load().length} pesan`
1312
+ label: `${s.mtime.toLocaleString(locale())} ${s.preview || t("(empty)")}`,
1313
+ hint: t("{n} messages", { n: s.session.load().length })
925
1314
  })),
926
1315
  onPick: (id) => {
927
1316
  setListPicker(null);
@@ -937,7 +1326,7 @@ function App({ runtime, initialPrompt, version }) {
937
1326
  entry({ kind: "header" }),
938
1327
  entry({
939
1328
  kind: "notice",
940
- text: `Melanjutkan sesi (${runtime.agent.messages.length} pesan tersimpan \xB7 ${chat.length} chat user/assistant ditampilkan).`,
1329
+ text: t("Resuming the session ({messages} messages saved \xB7 {chat} user/assistant chats shown).", { messages: runtime.agent.messages.length, chat: chat.length }),
941
1330
  tone: "info"
942
1331
  }),
943
1332
  ...chat.map(
@@ -971,7 +1360,7 @@ function App({ runtime, initialPrompt, version }) {
971
1360
  notice(skillsText(runtime.skills, runtime.commands));
972
1361
  return;
973
1362
  case "doctor": {
974
- notice("Memeriksa\u2026");
1363
+ notice(t("Checking\u2026"));
975
1364
  const c = runtime.config;
976
1365
  notice(
977
1366
  await doctorText(
@@ -992,20 +1381,20 @@ function App({ runtime, initialPrompt, version }) {
992
1381
  case "permissions": {
993
1382
  const rules = runtime.agent.permissions.list();
994
1383
  if (!rules.length) {
995
- notice("Belum ada izin tersimpan. Izin dari jawaban [a] dan dari config akan muncul di sini.");
1384
+ notice(t("No saved permissions yet. Permissions from [a] answers and from the config will appear here."));
996
1385
  return;
997
1386
  }
998
1387
  setListPicker({
999
- title: "Izin aktif (enter: cabut izin sesi)",
1000
- items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : "sesi ini" })),
1388
+ title: t("Active permissions (enter: revoke a session permission)"),
1389
+ items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : t("this session") })),
1001
1390
  onPick: (id) => {
1002
1391
  setListPicker(null);
1003
1392
  const rule = rules.find((r) => r.rule === id);
1004
1393
  if (!rule) return;
1005
- if (rule.source === "config") notice(`${rule.rule} berasal dari config/flag; hapus dari ~/.bccli/config.json untuk mencabutnya.`, "warn");
1394
+ if (rule.source === "config") notice(t("{rule} comes from the config/flag; remove it from ~/.bccli/config.json to revoke it.", { rule: rule.rule }), "warn");
1006
1395
  else {
1007
1396
  runtime.agent.permissions.revoke(rule.rule);
1008
- notice(`Izin ${rule.rule} dicabut.`);
1397
+ notice(t("Permission {rule} revoked.", { rule: rule.rule }));
1009
1398
  }
1010
1399
  }
1011
1400
  });
@@ -1014,12 +1403,12 @@ function App({ runtime, initialPrompt, version }) {
1014
1403
  case "memory": {
1015
1404
  if (!args) {
1016
1405
  const files = instructionFiles(runtime.cwd, runtime.home);
1017
- notice(files.length ? files.map((f) => `${f.path} (${f.lines} baris)`).join("\n") : "Belum ada AGENTS.md / BCCLI.md. Tambah dengan /memory <teks>.");
1406
+ notice(files.length ? files.map((f) => t("{path} ({lines} lines)", { path: f.path, lines: f.lines })).join("\n") : t("No AGENTS.md / BCCLI.md yet. Add one with /memory <text>."));
1018
1407
  return;
1019
1408
  }
1020
1409
  const { global, text } = parseMemoryArgs(args);
1021
1410
  if (!text) {
1022
- notice("Tulis teksnya: /memory global <teks>.", "warn");
1411
+ notice(t("Write the text: /memory global <text>."), "warn");
1023
1412
  return;
1024
1413
  }
1025
1414
  const file = global ? join(runtime.home, "BCCLI.md") : join(runtime.cwd, "AGENTS.md");
@@ -1027,19 +1416,38 @@ function App({ runtime, initialPrompt, version }) {
1027
1416
  mkdirSync(dirname(file), { recursive: true });
1028
1417
  appendMemory(file, text);
1029
1418
  } catch (error) {
1030
- notice(`Gagal menulis ${file}: ${error.message}`, "error");
1419
+ notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
1031
1420
  return;
1032
1421
  }
1033
1422
  runtime.rebuildSystemPrompt();
1034
- notice(`Ditambahkan ke ${file}.`);
1423
+ notice(t("Added to {file}.", { file }));
1035
1424
  return;
1036
1425
  }
1037
1426
  case "init":
1038
1427
  void runTurn(INIT_PROMPT);
1039
1428
  return;
1429
+ case "language": {
1430
+ if (!args) {
1431
+ notice(t("Language: {lang}. Change it with /language en or /language id.", { lang: getLanguage() }));
1432
+ return;
1433
+ }
1434
+ const wanted = parseLang(args);
1435
+ if (!wanted) {
1436
+ notice(t('Unknown language "{lang}". Use en or id.', { lang: args }), "warn");
1437
+ return;
1438
+ }
1439
+ setLanguage(wanted);
1440
+ try {
1441
+ writeGlobalConfig({ language: wanted }, runtime.env);
1442
+ } catch (error) {
1443
+ notice(t("Could not save the language: {error}", { error: error.message }), "error");
1444
+ }
1445
+ notice(t("Language set to {lang} (saved as default).", { lang: wanted }));
1446
+ return;
1447
+ }
1040
1448
  case "login": {
1041
1449
  const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
1042
- const key = await ask(`API key untuk ${runtime.providerLabel()}`, true);
1450
+ const key = await ask(t("API key for {provider}", { provider: runtime.providerLabel() }), true);
1043
1451
  if (!key) return;
1044
1452
  const result = await runtime.addProviderKey(id, key);
1045
1453
  if (!result.ok) {
@@ -1049,10 +1457,10 @@ function App({ runtime, initialPrompt, version }) {
1049
1457
  runtime.setModel(runtime.modelRef);
1050
1458
  const envName = runtime.config.providers[id]?.apiKeyEnv;
1051
1459
  if (envName && runtime.env[envName]) {
1052
- notice(`Key ${runtime.providerLabel()} tersimpan, tapi ${envName} di environment lebih diutamakan dan tetap dipakai.`, "warn");
1460
+ notice(t("Key for {provider} saved, but {env} in the environment takes precedence and is still used.", { provider: runtime.providerLabel(), env: envName }), "warn");
1053
1461
  return;
1054
1462
  }
1055
- notice(`Key ${runtime.providerLabel()} tersimpan \xB7 ${result.models} model.`);
1463
+ notice(t("Key for {provider} saved \xB7 {n} models.", { provider: runtime.providerLabel(), n: result.models }));
1056
1464
  return;
1057
1465
  }
1058
1466
  case "logout": {
@@ -1061,13 +1469,13 @@ function App({ runtime, initialPrompt, version }) {
1061
1469
  const envName = runtime.config.providers[id]?.apiKeyEnv;
1062
1470
  if (envName && runtime.env[envName]) {
1063
1471
  notice(
1064
- `${removed ? "Key tersimpan dihapus, tapi" : "Tidak ada key tersimpan;"} ${envName} masih ada di environment dan tetap dipakai.`,
1472
+ removed ? t("Stored key deleted, but {env} is still set in the environment and is still used.", { env: envName }) : t("No stored key; {env} is still set in the environment and is still used.", { env: envName }),
1065
1473
  "warn"
1066
1474
  );
1067
1475
  return;
1068
1476
  }
1069
1477
  if (!removed) {
1070
- notice(`Tidak ada key tersimpan untuk ${runtime.providerLabel()}.`);
1478
+ notice(t("No stored key for {provider}.", { provider: runtime.providerLabel() }));
1071
1479
  return;
1072
1480
  }
1073
1481
  try {
@@ -1078,7 +1486,7 @@ function App({ runtime, initialPrompt, version }) {
1078
1486
  };
1079
1487
  runtime.agent.provider = { chat: fail, listModels: fail };
1080
1488
  }
1081
- notice(`Key ${runtime.providerLabel()} dihapus. Pakai /login atau /provider untuk menambah lagi.`);
1489
+ notice(t("Key for {provider} deleted. Use /login or /provider to add one again.", { provider: runtime.providerLabel() }));
1082
1490
  return;
1083
1491
  }
1084
1492
  case "diff":
@@ -1087,17 +1495,17 @@ function App({ runtime, initialPrompt, version }) {
1087
1495
  case "undo": {
1088
1496
  const result = await runtime.checkpoints.undo();
1089
1497
  if (!result) {
1090
- notice("Tidak ada edit file yang bisa dibatalkan.");
1498
+ notice(t("No file edits to undo."));
1091
1499
  return;
1092
1500
  }
1093
1501
  const rel = (p) => relative(runtime.cwd, p) || p;
1094
1502
  notice(
1095
1503
  [
1096
- result.failed.length ? `Gagal dikembalikan: ${result.failed.map(rel).join(", ")}` : "",
1097
- result.restored.length ? `Dikembalikan: ${result.restored.map(rel).join(", ")}` : "",
1098
- result.deleted.length ? `Dihapus (file baru): ${result.deleted.map(rel).join(", ")}` : "",
1099
- result.skipped.length ? `Terlalu besar untuk disimpan, tidak diubah: ${result.skipped.map(rel).join(", ")}` : "",
1100
- "Catatan: perubahan lewat perintah bash tidak bisa di-undo."
1504
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
1505
+ result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
1506
+ result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
1507
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
1508
+ t("Note: changes made through bash commands cannot be undone.")
1101
1509
  ].filter(Boolean).join("\n")
1102
1510
  );
1103
1511
  return;
@@ -1105,33 +1513,33 @@ function App({ runtime, initialPrompt, version }) {
1105
1513
  case "copy": {
1106
1514
  const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
1107
1515
  if (!last || typeof last.content !== "string") {
1108
- notice("Belum ada jawaban untuk disalin.");
1516
+ notice(t("No answer to copy yet."));
1109
1517
  return;
1110
1518
  }
1111
1519
  process.stdout.write(osc52(last.content, !!process.env.TMUX));
1112
1520
  const file = join(runtime.home, "last-answer.md");
1113
1521
  mkdirSync(runtime.home, { recursive: true });
1114
1522
  writeFileSync(file, last.content);
1115
- notice(`Disalin ke clipboard (bila terminal mendukung OSC 52) dan disimpan di ${file}.`);
1523
+ notice(t("Copied to the clipboard (if the terminal supports OSC 52) and saved to {file}.", { file }));
1116
1524
  return;
1117
1525
  }
1118
1526
  case "export": {
1119
1527
  const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
1120
1528
  const file = resolve(runtime.cwd, name2);
1121
1529
  if (existsSync2(file)) {
1122
- const answer2 = await ask(`${file} sudah ada. Timpa? (y/N)`);
1530
+ const answer2 = await ask(t("{file} already exists. Overwrite? (y/N)", { file }));
1123
1531
  if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
1124
- notice("Ekspor dibatalkan.");
1532
+ notice(t("Export cancelled."));
1125
1533
  return;
1126
1534
  }
1127
1535
  }
1128
1536
  try {
1129
- writeFileSync(file, exportMarkdown(runtime.agent.messages, `Sesi BCCLI ${runtime.startedAt.toLocaleString("id-ID")}`));
1537
+ writeFileSync(file, exportMarkdown(runtime.agent.messages, t("BCCLI session {when}", { when: runtime.startedAt.toLocaleString(locale()) })));
1130
1538
  } catch (error) {
1131
- notice(`Gagal menulis ${file}: ${error.message}`, "error");
1539
+ notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
1132
1540
  return;
1133
1541
  }
1134
- notice(`Percakapan disimpan di ${file}.`);
1542
+ notice(t("Conversation saved to {file}.", { file }));
1135
1543
  return;
1136
1544
  }
1137
1545
  case "provider": {
@@ -1142,7 +1550,7 @@ function App({ runtime, initialPrompt, version }) {
1142
1550
  return;
1143
1551
  }
1144
1552
  default:
1145
- notice(`Perintah tidak dikenal: /${name}. Ketik /help.`, "warn");
1553
+ notice(t("Unknown command: /{name}. Type /help.", { name }), "warn");
1146
1554
  }
1147
1555
  },
1148
1556
  [runtime, notice, exit, version, runTurn, ask]
@@ -1168,7 +1576,7 @@ function App({ runtime, initialPrompt, version }) {
1168
1576
  try {
1169
1577
  body = parseFrontmatter(readFileSync2(skill.file, "utf8")).body;
1170
1578
  } catch (error) {
1171
- notice(`Skill ${skill.name} tidak bisa dibaca: ${error.code ?? error.message}`, "error");
1579
+ notice(t("Skill {name} cannot be read: {error}", { name: skill.name, error: error.code ?? error.message }), "error");
1172
1580
  return;
1173
1581
  }
1174
1582
  void runTurn(`${body}
@@ -1207,27 +1615,28 @@ ARGUMENTS: ${slash.args}`);
1207
1615
  controller.current?.abort();
1208
1616
  if (queued.length) {
1209
1617
  setQueued([]);
1210
- notice(`${queued.length} pesan antrian dibatalkan.`, "warn");
1618
+ notice(t("{n} queued messages cancelled.", { n: queued.length }), "warn");
1211
1619
  }
1212
1620
  } else if (key.ctrl && input === "t") {
1213
1621
  const next = !showThinking;
1622
+ const toggled = next ? t("Thinking shown (ctrl+t to hide).") : t("Thinking hidden (ctrl+t to show).");
1214
1623
  setShowThinking(next);
1215
- setTranscript((t) => {
1216
- const last = [...t.done, ...t.live].findLast((e) => e.kind === "thinking");
1217
- const reprint = next && last?.kind === "thinking" && t.done.includes(last);
1624
+ setTranscript((t2) => {
1625
+ const last = [...t2.done, ...t2.live].findLast((e) => e.kind === "thinking");
1626
+ const reprint = next && last?.kind === "thinking" && t2.done.includes(last);
1218
1627
  return {
1219
- ...t,
1628
+ ...t2,
1220
1629
  done: [
1221
- ...t.done,
1630
+ ...t2.done,
1222
1631
  ...reprint ? [entry({ kind: "thinking", text: last.text, open: true })] : [],
1223
- entry({ kind: "notice", text: next ? "Thinking ditampilkan (ctrl+t untuk menutup)." : "Thinking disembunyikan (ctrl+t untuk membuka).", tone: "info" })
1632
+ entry({ kind: "notice", text: toggled, tone: "info" })
1224
1633
  ]
1225
1634
  };
1226
1635
  });
1227
1636
  } else if (key.ctrl && input === "o" && lastTool.current) {
1228
- const t = lastTool.current;
1229
- notice(`\u23BF ${t.tool} ${t.target}
1230
- ${t.output}`);
1637
+ const t2 = lastTool.current;
1638
+ notice(`\u23BF ${t2.tool} ${t2.target}
1639
+ ${t2.output}`);
1231
1640
  }
1232
1641
  });
1233
1642
  const pickMcp = async (name) => {
@@ -1237,24 +1646,24 @@ ${t.output}`);
1237
1646
  if (item.installed) {
1238
1647
  removeGlobalServer(runtime.home, item.name);
1239
1648
  await runtime.mcp.remove(item.name);
1240
- notice(`MCP ${item.name} dihapus.`);
1649
+ notice(t("MCP {name} removed.", { name: item.name }));
1241
1650
  return;
1242
1651
  }
1243
1652
  const entry2 = CATALOG.find((c) => c.name === item.name);
1244
1653
  if (!entry2) return;
1245
1654
  const values = {};
1246
1655
  for (const input of entry2.inputs ?? []) {
1247
- const v = await ask(input.label, !!input.secret);
1656
+ const v = await ask(t(input.label), !!input.secret);
1248
1657
  if (!v) return;
1249
1658
  values[input.key] = v;
1250
1659
  }
1251
1660
  const config = fillTemplate(entry2.config, values);
1252
1661
  addGlobalServer(runtime.home, entry2.name, config);
1253
- notice(`MCP ${entry2.name} dipasang, menghubungkan\u2026`);
1662
+ notice(t("MCP {name} installed, connecting\u2026", { name: entry2.name }));
1254
1663
  await runtime.mcp.add({ name: entry2.name, config, source: "global" });
1255
1664
  const state = runtime.mcp.states().find((s) => s.name === entry2.name);
1256
- if (state?.status === "ready") notice(`MCP ${entry2.name} aktif \xB7 ${state.tools} alat.`);
1257
- else notice(`MCP ${entry2.name} gagal: ${state?.error ?? "tidak diketahui"}`, "error");
1665
+ if (state?.status === "ready") notice(t("MCP {name} active \xB7 {n} tools.", { name: entry2.name, n: state.tools }));
1666
+ else notice(t("MCP {name} failed: {error}", { name: entry2.name, error: state?.error ?? t("unknown") }), "error");
1258
1667
  };
1259
1668
  const pickProvider = async (id) => {
1260
1669
  setProviderMenu(null);
@@ -1271,7 +1680,7 @@ ${t.output}`);
1271
1680
  const c = runtime.config;
1272
1681
  const keyless = !c.providers[target].apiKeyEnv && PRESETS.some((p) => p.id === target);
1273
1682
  if (!hasKey(c, target, runtime.env) || id === "custom") {
1274
- const key = keyless ? "" : await ask(`API key untuk ${providerName(c, target)}`, true);
1683
+ const key = keyless ? "" : await ask(t("API key for {provider}", { provider: providerName(c, target) }), true);
1275
1684
  if (key === void 0) return;
1276
1685
  const result = await runtime.addProviderKey(target, key);
1277
1686
  if (!result.ok) {
@@ -1291,7 +1700,7 @@ ${t.output}`);
1291
1700
  const renderEntry = (e) => {
1292
1701
  switch (e.kind) {
1293
1702
  case "header":
1294
- return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs10(Text15, { children: [
1703
+ return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Text15, { children: [
1295
1704
  /* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), bold: true, children: " \u273B " }),
1296
1705
  /* @__PURE__ */ jsx15(Text15, { bold: true, children: `BCCLI ${version}` }),
1297
1706
  /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \xB7 ${runtime.modelRef} (${runtime.providerLabel()}) \xB7 ${cwd}` })
@@ -1303,7 +1712,7 @@ ${t.output}`);
1303
1712
  return e.text ? /* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Markdown, { text: e.text }) }, e.id) : null;
1304
1713
  }
1305
1714
  if (!e.text.trim()) return null;
1306
- return /* @__PURE__ */ jsxs10(Box13, { marginTop: 1, children: [
1715
+ return /* @__PURE__ */ jsxs11(Box13, { marginTop: 1, children: [
1307
1716
  /* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), children: "\u25CF " }),
1308
1717
  /* @__PURE__ */ jsx15(Markdown, { text: e.text.replace(/^\s+/, "") })
1309
1718
  ] }, e.id);
@@ -1312,10 +1721,10 @@ ${t.output}`);
1312
1721
  if (!text) return null;
1313
1722
  if (!(e.open ?? showThinking)) {
1314
1723
  const lines = text.split("\n").length;
1315
- return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: `\u273B Berpikir \xB7 ${lines} baris \xB7 ctrl+t buka` }) }, e.id);
1724
+ return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u273B Thinking \xB7 {n} lines \xB7 ctrl+t to show", { n: lines }) }) }, e.id);
1316
1725
  }
1317
- return /* @__PURE__ */ jsxs10(Box13, { marginTop: 1, flexDirection: "column", children: [
1318
- /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "\u273B Berpikir (ctrl+t tutup)" }),
1726
+ return /* @__PURE__ */ jsxs11(Box13, { marginTop: 1, flexDirection: "column", children: [
1727
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u273B Thinking (ctrl+t to hide)") }),
1319
1728
  /* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, italic: true, children: text }) })
1320
1729
  ] }, e.id);
1321
1730
  }
@@ -1325,10 +1734,10 @@ ${t.output}`);
1325
1734
  return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { color: e.tone === "error" ? color("red") : e.tone === "warn" ? color("yellow") : void 0, dimColor: e.tone === "info", children: e.text }) }, e.id);
1326
1735
  }
1327
1736
  };
1328
- return /* @__PURE__ */ jsxs10(Box13, { flexDirection: "column", children: [
1737
+ return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", children: [
1329
1738
  /* @__PURE__ */ jsx15(Static, { items: transcript.done, children: renderEntry }),
1330
1739
  transcript.live.map(renderEntry),
1331
- busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: "Berpikir", startedAt }) }) : null,
1740
+ busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: t("Thinking"), startedAt }) }) : null,
1332
1741
  pending ? /* @__PURE__ */ jsx15(PermissionPrompt, { request: pending.request, onAnswer: answer }, pending.id) : null,
1333
1742
  picker ? /* @__PURE__ */ jsx15(
1334
1743
  ModelPicker,
@@ -1371,7 +1780,7 @@ ${t.output}`);
1371
1780
  }
1372
1781
  ) : null,
1373
1782
  /* @__PURE__ */ jsx15(TodoList, { items: todos }),
1374
- queued.map((q, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \u23F3 antri: ${q}` }, `${i}-${q}`)),
1783
+ queued.map((q, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t(" \u23F3 queued: {q}", { q }) }, `${i}-${q}`)),
1375
1784
  /* @__PURE__ */ jsx15(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands }),
1376
1785
  /* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel, reasoning })
1377
1786
  ] });
@@ -1383,27 +1792,27 @@ async function startInteractive(rt, opts) {
1383
1792
  if (opts.resume) {
1384
1793
  const { Session: Session2 } = await import("./session-7UB77CEG.js");
1385
1794
  const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
1386
- if (!list.length) console.log("Belum ada sesi di folder ini; memulai sesi baru.");
1795
+ if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
1387
1796
  else {
1388
1797
  list.forEach((s, i) => {
1389
- console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString("id-ID")} ${s.preview}`);
1798
+ console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString(locale())} ${s.preview}`);
1390
1799
  });
1391
1800
  const { createInterface } = await import("readline/promises");
1392
1801
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1393
- const answer = Number(await rl.question("Nomor sesi (enter = baru): "));
1802
+ const answer = Number(await rl.question(t("Session number (enter = new): ")));
1394
1803
  rl.close();
1395
1804
  const chosen = list[answer - 1];
1396
1805
  if (chosen) rt.resume(chosen.session);
1397
1806
  }
1398
1807
  }
1399
- const { serversToStart, setProjectTrust } = await import("./config-YUNKPCNB.js");
1808
+ const { serversToStart, setProjectTrust } = await import("./config-CGYYFECG.js");
1400
1809
  const plan = serversToStart(rt.home, rt.cwd);
1401
1810
  if (plan.needTrust.length) {
1402
1811
  const { createInterface } = await import("readline/promises");
1403
1812
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1404
1813
  for (const s of plan.needTrust) {
1405
1814
  const what = s.config.type === "http" ? s.config.url : [s.config.command, ...s.config.args ?? []].join(" ");
1406
- const answer = (await rl.question(`Repo ini ingin menjalankan server MCP "${s.name}" (${what}). Izinkan? [y/N] `)).trim().toLowerCase();
1815
+ const answer = (await rl.question(t('This repo wants to run the MCP server "{name}" ({what}). Allow? [y/N] ', { name: s.name, what }))).trim().toLowerCase();
1407
1816
  setProjectTrust(rt.home, rt.cwd, s.name, answer === "y", s.config);
1408
1817
  if (answer === "y") plan.start.push(s);
1409
1818
  }