@botconnector/bccli 0.4.0-beta.2 → 0.4.0-beta.21

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-OSWQPSA4.js";
8
+ } from "./chunk-HP3J6JEY.js";
9
9
  import {
10
10
  nextMode
11
11
  } from "./chunk-ELML4KVQ.js";
@@ -13,24 +13,35 @@ import {
13
13
  hasKey,
14
14
  providerName,
15
15
  writeGlobalConfig
16
- } from "./chunk-U7DLMVPZ.js";
16
+ } from "./chunk-K5JNR3HL.js";
17
+ import {
18
+ REASONING_LEVELS,
19
+ assertReasoningSupported,
20
+ parseReasoningLevel,
21
+ supportedReasoningLevels
22
+ } from "./chunk-SN3ODJZN.js";
17
23
  import {
18
24
  Session
19
25
  } from "./chunk-SJH3YLW4.js";
20
26
  import {
21
27
  CATALOG,
22
28
  fillTemplate
23
- } from "./chunk-UESQNU4A.js";
29
+ } from "./chunk-BEXQSEBI.js";
24
30
  import {
25
31
  addGlobalServer,
26
32
  globalMcpPath,
27
33
  readMcpFile,
28
34
  removeGlobalServer
29
- } from "./chunk-NGLKANMD.js";
35
+ } from "./chunk-44MXWCFN.js";
30
36
  import {
31
37
  PRESETS,
32
- removeCredential
33
- } from "./chunk-RZUJYAQV.js";
38
+ getLanguage,
39
+ locale,
40
+ parseLang,
41
+ removeCredential,
42
+ setLanguage,
43
+ t
44
+ } from "./chunk-EO7IVVX4.js";
34
45
 
35
46
  // src/ui/index.tsx
36
47
  import { render } from "ink";
@@ -45,30 +56,32 @@ import { useCallback, useEffect as useEffect2, useRef, useState as useState8 } f
45
56
 
46
57
  // src/commands.ts
47
58
  var SLASH_COMMANDS = [
48
- { name: "help", description: "daftar perintah dan pintasan" },
49
- { name: "model", description: "ganti model" },
50
- { name: "provider", description: "tambah/pilih provider AI" },
51
- { name: "mcp", description: "pasang/kelola server MCP" },
52
- { name: "new", description: "sesi baru (sesi lama tetap tersimpan)" },
53
- { name: "resume", description: "lanjutkan sesi lain di folder ini" },
54
- { name: "session", description: "info sesi ini" },
55
- { name: "status", description: "versi, model, mode izin, MCP, konteks" },
56
- { name: "permissions", description: "lihat/cabut izin" },
57
- { name: "undo", description: "batalkan edit file giliran terakhir" },
58
- { name: "diff", description: "git diff project" },
59
- { name: "copy", description: "salin jawaban terakhir" },
60
- { name: "export", description: "simpan percakapan ke markdown" },
61
- { name: "memory", description: "instruksi project (/memory <teks>, /memory global <teks>)" },
62
- { name: "init", description: "buat/perbarui AGENTS.md untuk project ini" },
63
- { name: "agents", description: "daftar subagent" },
64
- { name: "skills", description: "daftar skill dan perintah custom" },
65
- { name: "doctor", description: "cek kesehatan instalasi" },
66
- { name: "login", description: "simpan API key provider aktif" },
67
- { name: "logout", description: "hapus API key provider aktif" },
68
- { name: "clear", description: "mulai percakapan baru" },
69
- { name: "compact", description: "ringkas percakapan" },
70
- { name: "cost", description: "pemakaian token sesi ini" },
71
- { 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" }
72
85
  ];
73
86
  function parseSlash(text) {
74
87
  if (!text.startsWith("/")) return void 0;
@@ -102,8 +115,8 @@ function git(cwd, args, bin = "git") {
102
115
  }
103
116
  async function gitDiff(cwd, maxLines = 400, bin = "git") {
104
117
  const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], bin);
105
- if (inside.missing) return "git tidak terpasang atau tidak ada di PATH, jadi /diff tidak bisa dipakai.";
106
- 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.");
107
120
  const base = (await git(cwd, ["rev-parse", "--verify", "-q", "HEAD"])).ok ? "HEAD" : "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
108
121
  const [stat, diff, untracked] = await Promise.all([
109
122
  git(cwd, ["diff", "--stat", base]),
@@ -111,23 +124,23 @@ async function gitDiff(cwd, maxLines = 400, bin = "git") {
111
124
  git(cwd, ["ls-files", "--others", "--exclude-standard"])
112
125
  ]);
113
126
  const fresh = untracked.out.split("\n").filter(Boolean);
114
- 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.");
115
128
  const lines = diff.out.trimEnd().split("\n");
116
129
  const more = lines.length > maxLines ? `
117
- \u2026 ${lines.length - maxLines} baris lagi` : "";
118
- 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");
119
132
  }
120
133
 
121
134
  // src/slash/doctor.ts
122
135
  async function doctorText(input, deps) {
123
136
  const major = Number(deps.nodeVersion.replace(/^v/, "").split(".")[0]);
124
- const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` : `\u2717 Node ${deps.nodeVersion} (butuh \u2265 22)`];
125
- 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 }));
126
139
  const cmds = ["rg", "git", "npx", "uvx"];
127
140
  const [connection, found] = await Promise.all([
128
141
  deps.listModels().then(
129
- (models) => `\u2713 Koneksi ${input.activeProvider} (${models.length} model)`,
130
- (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 })
131
144
  ),
132
145
  Promise.all(cmds.map(async (cmd) => deps.which(cmd)))
133
146
  ]);
@@ -158,31 +171,31 @@ function statusText(s) {
158
171
  return [
159
172
  `BCCLI ${s.version}`,
160
173
  `Model: ${s.modelRef} (${s.providerLabel})`,
161
- `Mode izin: ${s.mode}`,
162
- `Folder: ${s.cwd}`,
163
- `MCP: ${ready} aktif, ${errors} error`,
164
- `Token sesi: ${k(s.usage.inputTokens)} masuk \xB7 ${k(s.usage.outputTokens)} keluar`,
165
- `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) })
166
179
  ].join("\n");
167
180
  }
168
181
  function agentsText(defs) {
169
- 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");
170
183
  }
171
184
  function skillsText(skills, commands) {
172
- const s = skills.length ? skills.map((x) => `${x.name} \u2014 ${x.description.slice(0, 80)} (${x.dir})`).join("\n") : "(tidak ada skill)";
173
- const c = commands.length ? commands.map((x) => `/${x.name} \u2014 ${x.description ?? ""}`).join("\n") : "(tidak ada perintah custom)";
174
- 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")}:
175
188
  ${s}
176
189
 
177
- Perintah custom:
190
+ ${t("Custom commands")}:
178
191
  ${c}`;
179
192
  }
180
193
  function sessionText(s) {
181
194
  return [
182
- `Mulai: ${s.started.toLocaleString("id-ID")}`,
183
- `${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) }),
184
197
  `Model: ${s.modelRef}`,
185
- `File: ${s.file}`
198
+ t("File: {file}", { file: s.file })
186
199
  ].join("\n");
187
200
  }
188
201
 
@@ -202,7 +215,308 @@ function parseMemoryArgs(args) {
202
215
  }
203
216
 
204
217
  // src/ui/Markdown.tsx
205
- 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
+ }
206
520
 
207
521
  // src/ui/theme.ts
208
522
  function color(name) {
@@ -211,58 +525,136 @@ function color(name) {
211
525
  var ACCENT = "green";
212
526
 
213
527
  // src/ui/Markdown.tsx
214
- import { jsx } from "react/jsx-runtime";
215
- function inline(line) {
216
- return line.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((part, i) => {
217
- if (part.length > 4 && part.startsWith("**") && part.endsWith("**")) {
218
- return /* @__PURE__ */ jsx(Text, { bold: true, children: part.slice(2, -2) }, i);
219
- }
220
- if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) {
221
- return /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: part.slice(1, -1) }, i);
222
- }
223
- return part;
224
- });
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
+ }
225
546
  }
226
- function Markdown({ text }) {
227
- 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
+ var bulletFor = (spaces) => BULLETS[spaces === 0 ? 0 : spaces <= 4 ? 1 : 2];
555
+ function hanging(key, marker, body, width, padding = 0) {
556
+ const textWidth = Math.max(10, width - padding - displayWidth(marker) - 1);
557
+ return /* @__PURE__ */ jsxs(Box, { paddingLeft: padding, children: [
558
+ /* @__PURE__ */ jsx(Box, { flexShrink: 0, marginRight: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: marker }) }),
559
+ /* @__PURE__ */ jsx(Box, { width: textWidth, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { children: body }) })
560
+ ] }, key);
561
+ }
562
+ function Markdown({ text, indent = 2 }) {
563
+ const { stdout } = useStdout();
564
+ const width = Math.max(20, (stdout?.columns ?? 80) - indent - 1);
565
+ const lines = text.split("\n");
228
566
  const rows = [];
229
- text.split("\n").forEach((line, i) => {
567
+ let inCode = false;
568
+ for (let i = 0; i < lines.length; i++) {
569
+ const line = lines[i];
230
570
  if (line.trimStart().startsWith("```")) {
231
571
  inCode = !inCode;
232
- return;
572
+ continue;
233
573
  }
234
- rows.push(
235
- inCode ? /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i) : /* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i)
236
- );
237
- });
574
+ if (inCode) {
575
+ rows.push(
576
+ /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i)
577
+ );
578
+ continue;
579
+ }
580
+ const parsed = parseTable(lines, i);
581
+ if (parsed) {
582
+ layoutTable(parsed.table, width).forEach((tableLine, n) => {
583
+ rows.push(/* @__PURE__ */ jsx(Text, { children: tableLine.length ? tableLine.map(segment) : " " }, `${i}-${n}`));
584
+ });
585
+ i = parsed.end - 1;
586
+ continue;
587
+ }
588
+ const heading = HEADING.exec(line);
589
+ if (heading) {
590
+ const level = heading[1].length;
591
+ rows.push(
592
+ /* @__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)
593
+ );
594
+ continue;
595
+ }
596
+ if (RULE.test(line)) {
597
+ rows.push(
598
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.min(width, 80)) }, i)
599
+ );
600
+ continue;
601
+ }
602
+ const quote = QUOTE.exec(line);
603
+ if (quote) {
604
+ const depth = quote[1].replace(/\s/g, "").length;
605
+ rows.push(hanging(i, "\u258E".repeat(depth), /* @__PURE__ */ jsx(Text, { italic: true, children: inline(quote[2]) }), width));
606
+ continue;
607
+ }
608
+ const bullet = BULLET.exec(line);
609
+ if (bullet) {
610
+ const padding = Math.min(bullet[1].replace(/\t/g, " ").length, 16);
611
+ const task = /^\[([ xX])\]\s+(.*)$/.exec(bullet[2]);
612
+ rows.push(
613
+ task ? hanging(i, task[1] === " " ? "\u2610" : "\u2611", inline(task[2]), width, padding) : hanging(i, bulletFor(padding), inline(bullet[2]), width, padding)
614
+ );
615
+ continue;
616
+ }
617
+ const numbered = NUMBERED.exec(line);
618
+ if (numbered) {
619
+ const padding = Math.min(numbered[1].replace(/\t/g, " ").length, 16);
620
+ rows.push(hanging(i, `${numbered[2]}.`, inline(numbered[3]), width, padding));
621
+ continue;
622
+ }
623
+ rows.push(/* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i));
624
+ }
238
625
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
239
626
  }
240
627
 
241
628
  // src/ui/LinePrompt.tsx
242
629
  import { Box as Box2, Text as Text2, useInput } from "ink";
243
630
  import { useState } from "react";
244
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
631
+
632
+ // src/ui/focusReport.ts
633
+ var isFocusReport = (input) => input === "[I" || input === "[O";
634
+
635
+ // src/ui/LinePrompt.tsx
636
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
245
637
  function LinePrompt({ label, mask, onSubmit, onCancel }) {
246
638
  const [value, setValue] = useState("");
247
639
  useInput((input, key) => {
248
640
  if (key.return) onSubmit(value.trim());
249
641
  else if (key.escape) onCancel();
250
642
  else if (key.backspace || key.delete) setValue((v) => v.slice(0, -1));
251
- else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow) setValue((v) => v + input.replace(/[\r\n]/g, ""));
643
+ else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow && !isFocusReport(input)) setValue((v) => v + input.replace(/[\r\n]/g, ""));
252
644
  });
253
645
  const shown = mask ? "\u2022".repeat(value.length) : value;
254
- return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs(Text2, { children: [
646
+ return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
255
647
  `${label}: `,
256
648
  shown,
257
649
  /* @__PURE__ */ jsx2(Text2, { inverse: true, children: " " }),
258
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " enter simpan \xB7 esc batal" })
650
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: t(" enter save \xB7 esc cancel") })
259
651
  ] }) });
260
652
  }
261
653
 
262
654
  // src/ui/ListPicker.tsx
263
655
  import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
264
656
  import { useState as useState2 } from "react";
265
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
657
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
266
658
  var VISIBLE = 12;
267
659
  function ListPicker({ title, items, onPick }) {
268
660
  const [filter, setFilter] = useState2("");
@@ -272,31 +664,31 @@ function ListPicker({ title, items, onPick }) {
272
664
  useInput2((input, key) => {
273
665
  if (key.upArrow) setIndex(Math.max(0, cursor - 1));
274
666
  else if (key.downArrow) setIndex(Math.min(shown.length - 1, cursor + 1));
275
- else if (key.return) onPick(shown[cursor]?.id);
667
+ else if (key.return && !shown[cursor]?.disabled) onPick(shown[cursor]?.id);
276
668
  else if (key.escape) onPick(void 0);
277
669
  else if (key.backspace || key.delete) {
278
670
  setFilter((f) => f.slice(0, -1));
279
671
  setIndex(0);
280
- } else if (!key.ctrl && !key.meta && !key.tab && input) {
672
+ } else if (!key.ctrl && !key.meta && !key.tab && input && !isFocusReport(input)) {
281
673
  setFilter((f) => f + input);
282
674
  setIndex(0);
283
675
  }
284
676
  });
285
677
  const start = Math.max(0, Math.min(cursor - 5, shown.length - VISIBLE));
286
- return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
287
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: `${title} (\u2191\u2193 enter \xB7 ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 esc batal)` }),
288
- shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */ jsxs2(Text3, { color: start + i === cursor ? color("green") : void 0, children: [
678
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
679
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: t("{title} (\u2191\u2193 enter \xB7 type to filter{filter} \xB7 esc cancel)", { title, filter: filter ? `: ${filter}` : "" }) }),
680
+ 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: [
289
681
  `${start + i === cursor ? "\u203A" : " "} ${item.label}`,
290
682
  item.hint ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ` ${item.hint}` }) : null
291
683
  ] }, item.id)),
292
- shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tidak ada yang cocok." })
684
+ shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: t("No matches.") })
293
685
  ] });
294
686
  }
295
687
 
296
688
  // src/ui/ModelPicker.tsx
297
689
  import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
298
690
  import { useState as useState3 } from "react";
299
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
691
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
300
692
  var VISIBLE2 = 14;
301
693
  function ModelPicker({ groups, current, onPick }) {
302
694
  const [filter, setFilter] = useState3("");
@@ -322,18 +714,18 @@ function ModelPicker({ groups, current, onPick }) {
322
714
  else if (key.backspace || key.delete) {
323
715
  setFilter((f) => f.slice(0, -1));
324
716
  setIndex(0);
325
- } else if (!key.ctrl && !key.meta && !key.tab && input) {
717
+ } else if (!key.ctrl && !key.meta && !key.tab && input && !isFocusReport(input)) {
326
718
  setFilter((f) => f + input);
327
719
  setIndex(0);
328
720
  }
329
721
  });
330
722
  const start = Math.max(0, Math.min(cursor - 5, rows.length - VISIBLE2));
331
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
332
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: `Pilih model (ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 \u2191\u2193 enter \xB7 esc batal)` }),
723
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
724
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: t("Choose a model (type to filter{filter} \xB7 \u2191\u2193 enter \xB7 esc cancel)", { filter: filter ? `: ${filter}` : "" }) }),
333
725
  rows.slice(start, start + VISIBLE2).map(
334
726
  (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)
335
727
  ),
336
- !selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Tidak ada model yang cocok." }) : null,
728
+ !selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: t("No matching models.") }) : null,
337
729
  groups.filter((g) => g.error).map((g) => /* @__PURE__ */ jsx4(Text4, { color: color("yellow"), children: `${g.providerName} \u2014 ${g.error}` }, `e-${g.providerId}`))
338
730
  ] });
339
731
  }
@@ -341,7 +733,7 @@ function ModelPicker({ groups, current, onPick }) {
341
733
  // src/ui/ProviderMenu.tsx
342
734
  import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
343
735
  import { useState as useState4 } from "react";
344
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
736
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
345
737
  function ProviderMenu({ entries, onPick }) {
346
738
  const rows = [...entries.map((e) => e.id), "custom"];
347
739
  const [index, setIndex] = useState4(0);
@@ -351,9 +743,9 @@ function ProviderMenu({ entries, onPick }) {
351
743
  else if (key.return) onPick(rows[index]);
352
744
  else if (key.escape) onPick(void 0);
353
745
  });
354
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
355
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Provider (\u2191\u2193 enter, esc batal) \xB7 \u2713 siap \xB7 \u25CB butuh API key" }),
356
- entries.map((e, i) => /* @__PURE__ */ jsxs4(Text5, { color: i === index ? color("green") : void 0, children: [
746
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
747
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: t("Provider (\u2191\u2193 enter, esc cancel) \xB7 \u2713 ready \xB7 \u25CB needs API key") }),
748
+ entries.map((e, i) => /* @__PURE__ */ jsxs5(Text5, { color: i === index ? color("green") : void 0, children: [
357
749
  `${i === index ? "\u203A" : " "} ${e.ready ? "\u2713" : "\u25CB"} ${e.name}`,
358
750
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${e.baseURL}` })
359
751
  ] }, e.id)),
@@ -376,8 +768,8 @@ function DiffView({ diff }) {
376
768
  }
377
769
 
378
770
  // src/ui/PermissionPrompt.tsx
379
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
380
- var VERB = { edit: "edit", write: "tulis", bash: "jalankan", fetch: "ambil" };
771
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
772
+ var VERB = { edit: "edit", write: "write", bash: "run", fetch: "fetch" };
381
773
  function PermissionPrompt({
382
774
  request,
383
775
  onAnswer
@@ -390,16 +782,16 @@ function PermissionPrompt({
390
782
  else if (k2 === "n" || key.escape) onAnswer("no");
391
783
  });
392
784
  const rules = request.sessionRules ?? [];
393
- const sessionLabel = !rules.length ? null : request.kind === "edit" ? "ya untuk semua edit di project ini, sesi ini" : `ya sesi ini untuk ${rules.join(", ")}`;
394
- return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
395
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}Izinkan ${VERB[request.tool] ?? request.tool} ${request.target}?` }),
785
+ 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(", ") });
786
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
787
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}${t("Allow {verb} {target}?", { verb: t(VERB[request.tool] ?? request.tool), target: request.target })}` }),
396
788
  request.preview ? /* @__PURE__ */ jsx7(DiffView, { diff: request.preview }) : null,
397
- /* @__PURE__ */ jsxs5(Text7, { children: [
398
- /* @__PURE__ */ jsx7(Text7, { color: color("green"), children: "[y] ya" }),
789
+ /* @__PURE__ */ jsxs6(Text7, { children: [
790
+ /* @__PURE__ */ jsx7(Text7, { color: color("green"), children: t("[y] yes") }),
399
791
  " ",
400
792
  sessionLabel ? /* @__PURE__ */ jsx7(Text7, { color: color("cyan"), children: `[a] ${sessionLabel} ` }) : null,
401
- /* @__PURE__ */ jsx7(Text7, { color: color("yellow"), children: "[s] ya semua " }),
402
- /* @__PURE__ */ jsx7(Text7, { color: color("red"), children: "[n] tidak" })
793
+ /* @__PURE__ */ jsx7(Text7, { color: color("yellow"), children: t("[s] yes to all ") }),
794
+ /* @__PURE__ */ jsx7(Text7, { color: color("red"), children: t("[n] no") })
403
795
  ] })
404
796
  ] });
405
797
  }
@@ -422,7 +814,7 @@ function completeFile(value, cwd) {
422
814
  }
423
815
 
424
816
  // src/ui/PromptInput.tsx
425
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
817
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
426
818
  function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
427
819
  const [value, setValue] = useState5("");
428
820
  const [historyIndex, setHistoryIndex] = useState5(null);
@@ -491,18 +883,18 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
491
883
  edit((v) => completeFile(v, cwd));
492
884
  return;
493
885
  }
494
- if (key.ctrl || key.meta || key.escape || key.tab || key.leftArrow || key.rightArrow) return;
886
+ if (key.ctrl || key.meta || key.escape || key.tab || key.leftArrow || key.rightArrow || isFocusReport(input)) return;
495
887
  edit((v) => v + input.replace(/\r/g, "\n"));
496
888
  },
497
889
  { isActive: !disabled }
498
890
  );
499
- return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", children: [
500
- /* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs6(Text8, { children: [
891
+ return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
892
+ /* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs7(Text8, { children: [
501
893
  /* @__PURE__ */ jsx8(Text8, { color: color("green"), children: "> " }),
502
894
  value,
503
895
  disabled ? "" : /* @__PURE__ */ jsx8(Text8, { inverse: true, children: " " })
504
896
  ] }) }),
505
- 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))
897
+ 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))
506
898
  ] });
507
899
  }
508
900
 
@@ -514,11 +906,11 @@ var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
514
906
  function Spinner({ label, startedAt }) {
515
907
  const [tick, setTick] = useState6(0);
516
908
  useEffect(() => {
517
- const id = setInterval(() => setTick((t) => t + 1), 120);
909
+ const id = setInterval(() => setTick((t2) => t2 + 1), 120);
518
910
  return () => clearInterval(id);
519
911
  }, []);
520
912
  const seconds = Math.floor((Date.now() - startedAt) / 1e3);
521
- return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${label}\u2026 (${seconds} dtk \xB7 esc batal)` });
913
+ return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${t("{label}\u2026 ({seconds}s \xB7 esc to cancel)", { label, seconds })}` });
522
914
  }
523
915
 
524
916
  // src/ui/StatusBar.tsx
@@ -533,15 +925,15 @@ var LABEL = {
533
925
  function formatTokens(n) {
534
926
  return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
535
927
  }
536
- function StatusBar({ mode, tokens, busy, model }) {
928
+ function StatusBar({ mode, tokens, busy, model, reasoning = "auto" }) {
537
929
  const danger = mode === "allowAll";
538
- return /* @__PURE__ */ jsx10(Text10, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 shift+tab ganti mode \xB7 ${formatTokens(tokens)} token \xB7 ${busy ? "esc batal" : "/ perintah"}` });
930
+ 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")}` });
539
931
  }
540
932
 
541
933
  // src/ui/McpMenu.tsx
542
934
  import { Box as Box9, Text as Text11, useInput as useInput7 } from "ink";
543
935
  import { useState as useState7 } from "react";
544
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
936
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
545
937
  var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
546
938
  function McpMenu({ items, onPick }) {
547
939
  const [index, setIndex] = useState7(0);
@@ -551,9 +943,9 @@ function McpMenu({ items, onPick }) {
551
943
  else if (key.return) onPick(items[index]?.name);
552
944
  else if (key.escape) onPick(void 0);
553
945
  });
554
- return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
555
- /* @__PURE__ */ jsx11(Text11, { bold: true, children: "Server MCP (enter: pasang / hapus \xB7 esc tutup) \xB7 \u2713 aktif \u25CB belum \u2717 error" }),
556
- items.map((item, i) => /* @__PURE__ */ jsxs7(Text11, { color: i === index ? color("green") : void 0, children: [
946
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
947
+ /* @__PURE__ */ jsx11(Text11, { bold: true, children: t("MCP servers (enter: install / remove \xB7 esc close) \xB7 \u2713 active \u25CB not installed \u2717 error") }),
948
+ items.map((item, i) => /* @__PURE__ */ jsxs8(Text11, { color: i === index ? color("green") : void 0, children: [
557
949
  `${i === index ? "\u203A" : " "} ${MARK[item.status]} ${item.name.padEnd(12)}`,
558
950
  /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: item.status === "error" ? ` ${item.error ?? ""}` : ` ${item.description}` })
559
951
  ] }, item.name))
@@ -562,7 +954,7 @@ function McpMenu({ items, onPick }) {
562
954
 
563
955
  // src/ui/PlanApproval.tsx
564
956
  import { Box as Box10, Text as Text12, useInput as useInput8 } from "ink";
565
- import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
957
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
566
958
  function PlanApproval({ plan, onAnswer }) {
567
959
  useInput8((input, key) => {
568
960
  const k2 = input.toLowerCase();
@@ -570,15 +962,15 @@ function PlanApproval({ plan, onAnswer }) {
570
962
  else if (k2 === "y") onAnswer("default");
571
963
  else if (k2 === "n" || key.escape) onAnswer("no");
572
964
  });
573
- return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
574
- /* @__PURE__ */ jsx12(Text12, { bold: true, children: "Rencana dari agent" }),
575
- /* @__PURE__ */ jsx12(Markdown, { text: plan }),
576
- /* @__PURE__ */ jsxs8(Text12, { children: [
577
- /* @__PURE__ */ jsx12(Text12, { color: color("green"), children: "[a] ya, edit otomatis" }),
965
+ return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
966
+ /* @__PURE__ */ jsx12(Text12, { bold: true, children: t("Plan from the agent") }),
967
+ /* @__PURE__ */ jsx12(Markdown, { text: plan, indent: 5 }),
968
+ /* @__PURE__ */ jsxs9(Text12, { children: [
969
+ /* @__PURE__ */ jsx12(Text12, { color: color("green"), children: t("[a] yes, auto-edit") }),
578
970
  " ",
579
- /* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: "[y] ya, tanya tiap langkah" }),
971
+ /* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: t("[y] yes, ask at each step") }),
580
972
  " ",
581
- /* @__PURE__ */ jsx12(Text12, { color: color("red"), children: "[n] belum, perbaiki dulu" })
973
+ /* @__PURE__ */ jsx12(Text12, { color: color("red"), children: t("[n] not yet, revise first") })
582
974
  ] })
583
975
  ] });
584
976
  }
@@ -588,34 +980,34 @@ import { Box as Box11, Text as Text13 } from "ink";
588
980
  import { jsx as jsx13 } from "react/jsx-runtime";
589
981
  var MARK2 = { completed: "\u2611", in_progress: "\u25FC", pending: "\u2610" };
590
982
  function TodoList({ items }) {
591
- if (!items.length || items.every((t) => t.status === "completed")) return null;
592
- 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)) });
983
+ if (!items.length || items.every((t2) => t2.status === "completed")) return null;
984
+ 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)) });
593
985
  }
594
986
 
595
987
  // src/ui/ToolBlock.tsx
596
988
  import { Box as Box12, Text as Text14 } from "ink";
597
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
989
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
598
990
  var PREVIEW_LINES = 5;
599
991
  function ToolBlock({ tool, target, output, display, isError, done, expanded, sub }) {
600
992
  const isTask = tool === "task";
601
993
  const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
602
994
  const lines = (output ?? "").split("\n");
603
995
  const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
604
- return /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 2, children: [
605
- /* @__PURE__ */ jsxs9(Text14, { children: [
996
+ return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 2, children: [
997
+ /* @__PURE__ */ jsxs10(Text14, { children: [
606
998
  /* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
607
999
  /* @__PURE__ */ jsx14(Text14, { bold: true, children: toolLabel(tool) }),
608
1000
  /* @__PURE__ */ jsx14(Text14, { children: ` ${target}` }),
609
1001
  display && !isDiff && !isTask ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` (${display})` }) : null
610
1002
  ] }),
611
- sub?.length ? /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 3, children: [
1003
+ sub?.length ? /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginLeft: 3, children: [
612
1004
  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)),
613
- sub.length > 6 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: `\u2026 ${sub.length - 6} langkah sebelumnya` }) : null
1005
+ sub.length > 6 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: t("\u2026 {n} earlier steps", { n: sub.length - 6 }) }) : null
614
1006
  ] }) : null,
615
- 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,
616
- 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: [
1007
+ 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,
1008
+ 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: [
617
1009
  shown.map((line, i) => /* @__PURE__ */ jsx14(Text14, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
618
- lines.length > shown.length ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: `\u2026 ${lines.length - shown.length} baris lagi (ctrl+o)` }) : null
1010
+ lines.length > shown.length ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: t("\u2026 {n} more lines (ctrl+o)", { n: lines.length - shown.length }) }) : null
619
1011
  ] }) : null
620
1012
  ] });
621
1013
  }
@@ -624,73 +1016,105 @@ function ToolBlock({ tool, target, output, display, isError, done, expanded, sub
624
1016
  var nextId = 0;
625
1017
  var entry = (e) => ({ ...e, id: nextId++ });
626
1018
  var NOTICES = {
627
- compacted: ["Percakapan diringkas agar muat di konteks model.", "info"],
628
- stepLimit: ['Batas 50 langkah tercapai. Ketik "lanjut" untuk meneruskan.', "warn"],
629
- aborted: ["Dibatalkan.", "warn"]
1019
+ compacted: ["Conversation summarized to fit the model context.", "info"],
1020
+ stepLimit: ['Step limit of 50 reached. Type "continue" to keep going.', "warn"],
1021
+ aborted: ["Cancelled.", "warn"]
630
1022
  };
631
- function endTurn(t) {
632
- return { done: [...t.done, ...t.live], live: [] };
1023
+ var isEmptyCont = (e) => e.kind === "assistant" && e.cont && !e.text;
1024
+ function endTurn(t2) {
1025
+ return { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [] };
633
1026
  }
634
- function applyEvent(t, event) {
1027
+ var TOOL_MARKUP = /<\/?tool_call>|<\/?arg_(?:key|value)>|Tool call list|^\s*\d+\.\s*Tool:|`\s*=\s*`/;
1028
+ function finishedCut(text) {
1029
+ let inFence = false;
1030
+ let pos = 0;
1031
+ let cut = -1;
1032
+ const lines = text.split("\n");
1033
+ for (const line of lines.slice(0, -1)) {
1034
+ if (TOOL_MARKUP.test(line)) break;
1035
+ if (line.trimStart().startsWith("```")) inFence = !inFence;
1036
+ const tableRow = !inFence && !line.trimStart().startsWith("```") && line.includes("|") && line.trim() !== "";
1037
+ pos += line.length + 1;
1038
+ if (!inFence && !tableRow) cut = pos - 1;
1039
+ }
1040
+ return cut;
1041
+ }
1042
+ function flushLines(t2) {
1043
+ const last = t2.live.at(-1);
1044
+ if (last?.kind !== "assistant") return t2;
1045
+ const cut = finishedCut(last.text);
1046
+ if (cut < 0) return t2;
1047
+ const chunk = last.text.slice(0, cut);
1048
+ if (!last.cont && !chunk.trim()) return t2;
1049
+ const rest = entry({ kind: "assistant", text: last.text.slice(cut + 1), cont: true, flushed: (last.flushed ?? 0) + cut + 1 });
1050
+ return { done: [...t2.done, ...t2.live.slice(0, -1).filter((e) => !isEmptyCont(e)), { ...last, text: chunk || " " }], live: [rest] };
1051
+ }
1052
+ function applyEvent(t2, event) {
635
1053
  switch (event.type) {
636
1054
  case "text": {
637
- const last = t.live.at(-1);
638
- if (last?.kind === "assistant") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
639
- return { ...t, live: [...t.live, entry({ kind: "assistant", text: event.delta })] };
1055
+ const last = t2.live.at(-1);
1056
+ if (last?.kind === "assistant") return flushLines({ ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] });
1057
+ return flushLines({ ...t2, live: [...t2.live, entry({ kind: "assistant", text: event.delta })] });
1058
+ }
1059
+ case "thinking": {
1060
+ const last = t2.live.at(-1);
1061
+ if (last?.kind === "thinking") return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
1062
+ return { ...t2, live: [...t2.live, entry({ kind: "thinking", text: event.delta })] };
640
1063
  }
641
1064
  case "textReplace": {
642
- const last = t.live.at(-1);
643
- if (last?.kind !== "assistant") return t;
644
- return { ...t, live: [...t.live.slice(0, -1), { ...last, text: event.text }] };
1065
+ const last = t2.live.at(-1);
1066
+ if (last?.kind !== "assistant") return t2;
1067
+ return { ...t2, live: [...t2.live.slice(0, -1), { ...last, text: event.text.slice(last.flushed ?? 0) }] };
645
1068
  }
646
1069
  case "toolStart": {
647
- const running = t.live.some((e) => e.kind === "tool" && !e.done);
1070
+ const running = t2.live.some((e) => e.kind === "tool" && !e.done);
648
1071
  const tool = entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false });
649
- return running ? { ...t, live: [...t.live, tool] } : { done: [...t.done, ...t.live], live: [tool] };
1072
+ return running ? { ...t2, live: [...t2.live, tool] } : { done: [...t2.done, ...t2.live.filter((e) => !isEmptyCont(e))], live: [tool] };
650
1073
  }
651
1074
  case "toolEnd": {
652
- const live = t.live.map(
1075
+ const live = t2.live.map(
653
1076
  (e) => e.kind === "tool" && e.callId === event.id ? { ...e, output: event.output, display: event.display, isError: event.isError, done: true } : e
654
1077
  );
655
1078
  const firstRunning = live.findIndex((e) => e.kind === "tool" && !e.done);
656
1079
  const cut = firstRunning === -1 ? live.length : firstRunning;
657
- return { done: [...t.done, ...live.slice(0, cut)], live: live.slice(cut) };
1080
+ return { done: [...t2.done, ...live.slice(0, cut)], live: live.slice(cut) };
658
1081
  }
659
1082
  case "subagent": {
660
1083
  const inner = event.event;
661
- if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t;
1084
+ if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t2;
662
1085
  const update = (e) => {
663
1086
  if (e.kind !== "tool" || e.callId !== event.parentId) return e;
664
1087
  const sub = e.sub ?? [];
665
1088
  if (inner.type === "toolStart") return { ...e, sub: [...sub, { id: inner.id, tool: inner.tool, target: inner.target, done: false }] };
666
1089
  return { ...e, sub: sub.map((s) => s.id === inner.id ? { ...s, done: true, isError: inner.isError } : s) };
667
1090
  };
668
- return { ...t, live: t.live.map(update) };
1091
+ return { ...t2, live: t2.live.map(update) };
669
1092
  }
670
1093
  case "error":
671
- return { ...t, live: [...t.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
1094
+ return { ...t2, live: [...t2.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
672
1095
  default: {
673
1096
  const notice = NOTICES[event.type];
674
- return notice ? { ...t, live: [...t.live, entry({ kind: "notice", text: notice[0], tone: notice[1] })] } : t;
1097
+ return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0]), tone: notice[1] })] } : t2;
675
1098
  }
676
1099
  }
677
1100
  }
678
1101
 
679
1102
  // src/ui/App.tsx
680
- import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1103
+ import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
681
1104
  function helpText(runtime) {
682
1105
  const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
683
1106
  return [
684
- ...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${c.description}`),
685
- ...custom.length ? ["", "Perintah custom:", ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
686
- ...runtime.skills.length ? ["", "Skill:", ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
1107
+ ...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${t(c.description)}`),
1108
+ ...custom.length ? ["", t("Custom commands:"), ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
1109
+ ...runtime.skills.length ? ["", t("Skills:"), ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
687
1110
  "",
688
- "shift+tab ganti mode izin esc batalkan giliran",
689
- "ctrl+o output alat terakhir lengkap \\ + enter baris baru",
690
- "@file + tab lengkapi nama file \u2191\u2193 riwayat input"
1111
+ t("shift+tab switch permission mode esc cancel the turn"),
1112
+ t("ctrl+o full output of the last tool \\ + enter new line"),
1113
+ t("ctrl+t show/hide the model\u2019s thinking"),
1114
+ t("@file + tab complete file names \u2191\u2193 input history")
691
1115
  ].join("\n");
692
1116
  }
693
- 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.";
1117
+ 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.";
694
1118
  var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
695
1119
  function App({ runtime, initialPrompt, version }) {
696
1120
  const { exit } = useApp();
@@ -698,6 +1122,7 @@ function App({ runtime, initialPrompt, version }) {
698
1122
  const [busy, setBusy] = useState8(false);
699
1123
  const [startedAt, setStartedAt] = useState8(0);
700
1124
  const [mode, setMode] = useState8(runtime.agent.permissions.mode);
1125
+ const [reasoning, setReasoning] = useState8(runtime.reasoning);
701
1126
  const [tokens, setTokens] = useState8(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
702
1127
  const [asks, setAsks] = useState8([]);
703
1128
  const pending = asks[0] ?? null;
@@ -713,15 +1138,17 @@ function App({ runtime, initialPrompt, version }) {
713
1138
  const [mcpMenu, setMcpMenu] = useState8(null);
714
1139
  const [listPicker, setListPicker] = useState8(null);
715
1140
  const [planAsk, setPlanAsk] = useState8(null);
1141
+ const [queued, setQueued] = useState8([]);
1142
+ const [showThinking, setShowThinking] = useState8(false);
716
1143
  useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
717
1144
  const extraCommands = [
718
- ...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? "perintah custom" })),
719
- ...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)}` }))
1145
+ ...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") })),
1146
+ ...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) }) }))
720
1147
  ];
721
1148
  const lastTool = useRef(null);
722
1149
  const toolTargets = useRef(/* @__PURE__ */ new Map());
723
1150
  const notice = useCallback((text, tone = "info") => {
724
- setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "notice", text, tone })] }));
1151
+ setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "notice", text, tone })] }));
725
1152
  }, []);
726
1153
  const onEvent = useCallback((event) => {
727
1154
  if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
@@ -729,7 +1156,7 @@ function App({ runtime, initialPrompt, version }) {
729
1156
  lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
730
1157
  }
731
1158
  if (event.type === "usage") setTokens(event.inputTokens + event.outputTokens);
732
- setTranscript((t) => applyEvent(t, event));
1159
+ setTranscript((t2) => applyEvent(t2, event));
733
1160
  }, []);
734
1161
  useEffect2(() => {
735
1162
  runtime.agent.onEvent = onEvent;
@@ -738,7 +1165,7 @@ function App({ runtime, initialPrompt, version }) {
738
1165
  }, [runtime, onEvent]);
739
1166
  const runTurn = useCallback(
740
1167
  async (text) => {
741
- setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "user", text })] }));
1168
+ setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "user", text })] }));
742
1169
  setBusy(true);
743
1170
  setStartedAt(Date.now());
744
1171
  controller.current = new AbortController();
@@ -761,11 +1188,11 @@ function App({ runtime, initialPrompt, version }) {
761
1188
  case "clear":
762
1189
  runtime.agent.clear();
763
1190
  setTokens(0);
764
- notice("Percakapan dikosongkan.");
1191
+ notice(t("Conversation cleared."));
765
1192
  return;
766
1193
  case "cost": {
767
1194
  const u = runtime.agent.totalUsage;
768
- notice(`${u.inputTokens.toLocaleString("id-ID")} token masuk \xB7 ${u.outputTokens.toLocaleString("id-ID")} token keluar`);
1195
+ notice(t("{input} tokens in \xB7 {output} tokens out", { input: u.inputTokens.toLocaleString(locale()), output: u.outputTokens.toLocaleString(locale()) }));
769
1196
  return;
770
1197
  }
771
1198
  case "compact": {
@@ -775,19 +1202,54 @@ function App({ runtime, initialPrompt, version }) {
775
1202
  try {
776
1203
  await runtime.agent.compact(controller.current.signal);
777
1204
  } catch (error) {
778
- notice(`Gagal meringkas: ${error.message}`, "error");
1205
+ notice(t("Could not summarize: {error}", { error: error.message }), "error");
779
1206
  }
780
1207
  controller.current = null;
781
1208
  setBusy(false);
782
1209
  return;
783
1210
  }
1211
+ case "reasoning": {
1212
+ const apply = (value) => {
1213
+ try {
1214
+ const level = parseReasoningLevel(value, "/reasoning");
1215
+ const providerId2 = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
1216
+ assertReasoningSupported(providerId2, level);
1217
+ runtime.setReasoning(level);
1218
+ writeGlobalConfig({ reasoning: level }, runtime.env);
1219
+ setReasoning(level);
1220
+ notice(t("Reasoning: {level} (saved as default)", { level }));
1221
+ } catch (error) {
1222
+ notice(error.message, "error");
1223
+ }
1224
+ };
1225
+ if (args) {
1226
+ apply(args);
1227
+ return;
1228
+ }
1229
+ const providerId = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
1230
+ const supported = supportedReasoningLevels(providerId);
1231
+ setListPicker({
1232
+ title: "Reasoning",
1233
+ items: REASONING_LEVELS.map((level) => ({
1234
+ id: level,
1235
+ label: level === "auto" ? "Auto (model default)" : level.charAt(0).toUpperCase() + level.slice(1),
1236
+ disabled: !supported.includes(level),
1237
+ hint: supported.includes(level) ? void 0 : t("not supported by this provider")
1238
+ })),
1239
+ onPick: (id) => {
1240
+ setListPicker(null);
1241
+ if (id) apply(id);
1242
+ }
1243
+ });
1244
+ return;
1245
+ }
784
1246
  case "model": {
785
1247
  if (args) {
786
1248
  try {
787
1249
  runtime.setModel(args);
788
1250
  writeGlobalConfig({ model: args }, runtime.env);
789
1251
  setModelLabel(labelFor(runtime, args));
790
- notice(`Model: ${args} (tersimpan sebagai default)`);
1252
+ notice(t("Model: {model} (saved as default)", { model: args }));
791
1253
  } catch (error) {
792
1254
  notice(error.message, "error");
793
1255
  }
@@ -796,7 +1258,7 @@ function App({ runtime, initialPrompt, version }) {
796
1258
  try {
797
1259
  setPicker(await runtime.listModels());
798
1260
  } catch (error) {
799
- notice(`Tidak bisa mengambil daftar model: ${error.message}`, "error");
1261
+ notice(t("Cannot fetch the model list: {error}", { error: error.message }), "error");
800
1262
  }
801
1263
  return;
802
1264
  }
@@ -804,7 +1266,7 @@ function App({ runtime, initialPrompt, version }) {
804
1266
  const installed = readMcpFile(globalMcpPath(runtime.home));
805
1267
  const states = runtime.mcp.states();
806
1268
  const items = [
807
- ...CATALOG.map((c) => ({ name: c.name, description: c.description, installed: !!installed[c.name] })),
1269
+ ...CATALOG.map((c) => ({ name: c.name, description: t(c.description), installed: !!installed[c.name] })),
808
1270
  ...Object.keys(installed).filter((n) => !CATALOG.some((c) => c.name === n)).map((n) => ({ name: n, description: "(custom)", installed: true }))
809
1271
  ].map((i) => {
810
1272
  const s = states.find((st) => st.name === i.name);
@@ -816,7 +1278,7 @@ function App({ runtime, initialPrompt, version }) {
816
1278
  case "new":
817
1279
  runtime.newSession();
818
1280
  setTokens(0);
819
- notice("Sesi baru dimulai. Sesi sebelumnya tetap tersimpan (/resume).");
1281
+ notice(t("New session started. The previous session stays saved (/resume)."));
820
1282
  return;
821
1283
  case "session":
822
1284
  notice(
@@ -832,15 +1294,15 @@ function App({ runtime, initialPrompt, version }) {
832
1294
  case "resume": {
833
1295
  const others = Session.list(runtime.home, runtime.cwd).filter((s) => s.session.file !== runtime.session.file);
834
1296
  if (!others.length) {
835
- notice("Belum ada sesi lain di folder ini.");
1297
+ notice(t("No other sessions in this folder yet."));
836
1298
  return;
837
1299
  }
838
1300
  setListPicker({
839
- title: "Lanjutkan sesi",
1301
+ title: t("Continue a session"),
840
1302
  items: others.map((s) => ({
841
1303
  id: s.session.file,
842
- label: `${s.mtime.toLocaleString("id-ID")} ${s.preview || "(kosong)"}`,
843
- hint: `${s.session.load().length} pesan`
1304
+ label: `${s.mtime.toLocaleString(locale())} ${s.preview || t("(empty)")}`,
1305
+ hint: t("{n} messages", { n: s.session.load().length })
844
1306
  })),
845
1307
  onPick: (id) => {
846
1308
  setListPicker(null);
@@ -848,12 +1310,23 @@ function App({ runtime, initialPrompt, version }) {
848
1310
  if (!chosen) return;
849
1311
  runtime.resume(chosen.session);
850
1312
  setTokens(0);
851
- const tail = runtime.agent.messages.filter((m) => (m.role === "user" || m.role === "assistant") && m.content).slice(-3);
852
- notice(
853
- [`Melanjutkan sesi (${runtime.agent.messages.length} pesan):`, ...tail.map((m) => `${m.role === "user" ? ">" : "\u25CF"} ${String(m.content).slice(0, 200)}`)].join(
854
- "\n"
855
- )
1313
+ const chat = runtime.agent.messages.filter(
1314
+ (m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && !!m.content.trim()
856
1315
  );
1316
+ setTranscript({
1317
+ done: [
1318
+ entry({ kind: "header" }),
1319
+ entry({
1320
+ kind: "notice",
1321
+ text: t("Resuming the session ({messages} messages saved \xB7 {chat} user/assistant chats shown).", { messages: runtime.agent.messages.length, chat: chat.length }),
1322
+ tone: "info"
1323
+ }),
1324
+ ...chat.map(
1325
+ (m) => entry(m.role === "user" ? { kind: "user", text: m.content } : { kind: "assistant", text: m.content })
1326
+ )
1327
+ ],
1328
+ live: []
1329
+ });
857
1330
  }
858
1331
  });
859
1332
  return;
@@ -879,7 +1352,7 @@ function App({ runtime, initialPrompt, version }) {
879
1352
  notice(skillsText(runtime.skills, runtime.commands));
880
1353
  return;
881
1354
  case "doctor": {
882
- notice("Memeriksa\u2026");
1355
+ notice(t("Checking\u2026"));
883
1356
  const c = runtime.config;
884
1357
  notice(
885
1358
  await doctorText(
@@ -900,20 +1373,20 @@ function App({ runtime, initialPrompt, version }) {
900
1373
  case "permissions": {
901
1374
  const rules = runtime.agent.permissions.list();
902
1375
  if (!rules.length) {
903
- notice("Belum ada izin tersimpan. Izin dari jawaban [a] dan dari config akan muncul di sini.");
1376
+ notice(t("No saved permissions yet. Permissions from [a] answers and from the config will appear here."));
904
1377
  return;
905
1378
  }
906
1379
  setListPicker({
907
- title: "Izin aktif (enter: cabut izin sesi)",
908
- items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : "sesi ini" })),
1380
+ title: t("Active permissions (enter: revoke a session permission)"),
1381
+ items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : t("this session") })),
909
1382
  onPick: (id) => {
910
1383
  setListPicker(null);
911
1384
  const rule = rules.find((r) => r.rule === id);
912
1385
  if (!rule) return;
913
- if (rule.source === "config") notice(`${rule.rule} berasal dari config/flag; hapus dari ~/.bccli/config.json untuk mencabutnya.`, "warn");
1386
+ 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");
914
1387
  else {
915
1388
  runtime.agent.permissions.revoke(rule.rule);
916
- notice(`Izin ${rule.rule} dicabut.`);
1389
+ notice(t("Permission {rule} revoked.", { rule: rule.rule }));
917
1390
  }
918
1391
  }
919
1392
  });
@@ -922,12 +1395,12 @@ function App({ runtime, initialPrompt, version }) {
922
1395
  case "memory": {
923
1396
  if (!args) {
924
1397
  const files = instructionFiles(runtime.cwd, runtime.home);
925
- notice(files.length ? files.map((f) => `${f.path} (${f.lines} baris)`).join("\n") : "Belum ada AGENTS.md / BCCLI.md. Tambah dengan /memory <teks>.");
1398
+ 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>."));
926
1399
  return;
927
1400
  }
928
1401
  const { global, text } = parseMemoryArgs(args);
929
1402
  if (!text) {
930
- notice("Tulis teksnya: /memory global <teks>.", "warn");
1403
+ notice(t("Write the text: /memory global <text>."), "warn");
931
1404
  return;
932
1405
  }
933
1406
  const file = global ? join(runtime.home, "BCCLI.md") : join(runtime.cwd, "AGENTS.md");
@@ -935,19 +1408,38 @@ function App({ runtime, initialPrompt, version }) {
935
1408
  mkdirSync(dirname(file), { recursive: true });
936
1409
  appendMemory(file, text);
937
1410
  } catch (error) {
938
- notice(`Gagal menulis ${file}: ${error.message}`, "error");
1411
+ notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
939
1412
  return;
940
1413
  }
941
1414
  runtime.rebuildSystemPrompt();
942
- notice(`Ditambahkan ke ${file}.`);
1415
+ notice(t("Added to {file}.", { file }));
943
1416
  return;
944
1417
  }
945
1418
  case "init":
946
1419
  void runTurn(INIT_PROMPT);
947
1420
  return;
1421
+ case "language": {
1422
+ if (!args) {
1423
+ notice(t("Language: {lang}. Change it with /language en or /language id.", { lang: getLanguage() }));
1424
+ return;
1425
+ }
1426
+ const wanted = parseLang(args);
1427
+ if (!wanted) {
1428
+ notice(t('Unknown language "{lang}". Use en or id.', { lang: args }), "warn");
1429
+ return;
1430
+ }
1431
+ setLanguage(wanted);
1432
+ try {
1433
+ writeGlobalConfig({ language: wanted }, runtime.env);
1434
+ } catch (error) {
1435
+ notice(t("Could not save the language: {error}", { error: error.message }), "error");
1436
+ }
1437
+ notice(t("Language set to {lang} (saved as default).", { lang: wanted }));
1438
+ return;
1439
+ }
948
1440
  case "login": {
949
1441
  const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
950
- const key = await ask(`API key untuk ${runtime.providerLabel()}`, true);
1442
+ const key = await ask(t("API key for {provider}", { provider: runtime.providerLabel() }), true);
951
1443
  if (!key) return;
952
1444
  const result = await runtime.addProviderKey(id, key);
953
1445
  if (!result.ok) {
@@ -957,10 +1449,10 @@ function App({ runtime, initialPrompt, version }) {
957
1449
  runtime.setModel(runtime.modelRef);
958
1450
  const envName = runtime.config.providers[id]?.apiKeyEnv;
959
1451
  if (envName && runtime.env[envName]) {
960
- notice(`Key ${runtime.providerLabel()} tersimpan, tapi ${envName} di environment lebih diutamakan dan tetap dipakai.`, "warn");
1452
+ notice(t("Key for {provider} saved, but {env} in the environment takes precedence and is still used.", { provider: runtime.providerLabel(), env: envName }), "warn");
961
1453
  return;
962
1454
  }
963
- notice(`Key ${runtime.providerLabel()} tersimpan \xB7 ${result.models} model.`);
1455
+ notice(t("Key for {provider} saved \xB7 {n} models.", { provider: runtime.providerLabel(), n: result.models }));
964
1456
  return;
965
1457
  }
966
1458
  case "logout": {
@@ -969,13 +1461,13 @@ function App({ runtime, initialPrompt, version }) {
969
1461
  const envName = runtime.config.providers[id]?.apiKeyEnv;
970
1462
  if (envName && runtime.env[envName]) {
971
1463
  notice(
972
- `${removed ? "Key tersimpan dihapus, tapi" : "Tidak ada key tersimpan;"} ${envName} masih ada di environment dan tetap dipakai.`,
1464
+ 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 }),
973
1465
  "warn"
974
1466
  );
975
1467
  return;
976
1468
  }
977
1469
  if (!removed) {
978
- notice(`Tidak ada key tersimpan untuk ${runtime.providerLabel()}.`);
1470
+ notice(t("No stored key for {provider}.", { provider: runtime.providerLabel() }));
979
1471
  return;
980
1472
  }
981
1473
  try {
@@ -986,7 +1478,7 @@ function App({ runtime, initialPrompt, version }) {
986
1478
  };
987
1479
  runtime.agent.provider = { chat: fail, listModels: fail };
988
1480
  }
989
- notice(`Key ${runtime.providerLabel()} dihapus. Pakai /login atau /provider untuk menambah lagi.`);
1481
+ notice(t("Key for {provider} deleted. Use /login or /provider to add one again.", { provider: runtime.providerLabel() }));
990
1482
  return;
991
1483
  }
992
1484
  case "diff":
@@ -995,17 +1487,17 @@ function App({ runtime, initialPrompt, version }) {
995
1487
  case "undo": {
996
1488
  const result = await runtime.checkpoints.undo();
997
1489
  if (!result) {
998
- notice("Tidak ada edit file yang bisa dibatalkan.");
1490
+ notice(t("No file edits to undo."));
999
1491
  return;
1000
1492
  }
1001
1493
  const rel = (p) => relative(runtime.cwd, p) || p;
1002
1494
  notice(
1003
1495
  [
1004
- result.failed.length ? `Gagal dikembalikan: ${result.failed.map(rel).join(", ")}` : "",
1005
- result.restored.length ? `Dikembalikan: ${result.restored.map(rel).join(", ")}` : "",
1006
- result.deleted.length ? `Dihapus (file baru): ${result.deleted.map(rel).join(", ")}` : "",
1007
- result.skipped.length ? `Terlalu besar untuk disimpan, tidak diubah: ${result.skipped.map(rel).join(", ")}` : "",
1008
- "Catatan: perubahan lewat perintah bash tidak bisa di-undo."
1496
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
1497
+ result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
1498
+ result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
1499
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
1500
+ t("Note: changes made through bash commands cannot be undone.")
1009
1501
  ].filter(Boolean).join("\n")
1010
1502
  );
1011
1503
  return;
@@ -1013,33 +1505,33 @@ function App({ runtime, initialPrompt, version }) {
1013
1505
  case "copy": {
1014
1506
  const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
1015
1507
  if (!last || typeof last.content !== "string") {
1016
- notice("Belum ada jawaban untuk disalin.");
1508
+ notice(t("No answer to copy yet."));
1017
1509
  return;
1018
1510
  }
1019
1511
  process.stdout.write(osc52(last.content, !!process.env.TMUX));
1020
1512
  const file = join(runtime.home, "last-answer.md");
1021
1513
  mkdirSync(runtime.home, { recursive: true });
1022
1514
  writeFileSync(file, last.content);
1023
- notice(`Disalin ke clipboard (bila terminal mendukung OSC 52) dan disimpan di ${file}.`);
1515
+ notice(t("Copied to the clipboard (if the terminal supports OSC 52) and saved to {file}.", { file }));
1024
1516
  return;
1025
1517
  }
1026
1518
  case "export": {
1027
1519
  const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
1028
1520
  const file = resolve(runtime.cwd, name2);
1029
1521
  if (existsSync2(file)) {
1030
- const answer2 = await ask(`${file} sudah ada. Timpa? (y/N)`);
1522
+ const answer2 = await ask(t("{file} already exists. Overwrite? (y/N)", { file }));
1031
1523
  if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
1032
- notice("Ekspor dibatalkan.");
1524
+ notice(t("Export cancelled."));
1033
1525
  return;
1034
1526
  }
1035
1527
  }
1036
1528
  try {
1037
- writeFileSync(file, exportMarkdown(runtime.agent.messages, `Sesi BCCLI ${runtime.startedAt.toLocaleString("id-ID")}`));
1529
+ writeFileSync(file, exportMarkdown(runtime.agent.messages, t("BCCLI session {when}", { when: runtime.startedAt.toLocaleString(locale()) })));
1038
1530
  } catch (error) {
1039
- notice(`Gagal menulis ${file}: ${error.message}`, "error");
1531
+ notice(t("Could not write {file}: {error}", { file, error: error.message }), "error");
1040
1532
  return;
1041
1533
  }
1042
- notice(`Percakapan disimpan di ${file}.`);
1534
+ notice(t("Conversation saved to {file}.", { file }));
1043
1535
  return;
1044
1536
  }
1045
1537
  case "provider": {
@@ -1050,7 +1542,7 @@ function App({ runtime, initialPrompt, version }) {
1050
1542
  return;
1051
1543
  }
1052
1544
  default:
1053
- notice(`Perintah tidak dikenal: /${name}. Ketik /help.`, "warn");
1545
+ notice(t("Unknown command: /{name}. Type /help.", { name }), "warn");
1054
1546
  }
1055
1547
  },
1056
1548
  [runtime, notice, exit, version, runTurn, ask]
@@ -1076,7 +1568,7 @@ function App({ runtime, initialPrompt, version }) {
1076
1568
  try {
1077
1569
  body = parseFrontmatter(readFileSync2(skill.file, "utf8")).body;
1078
1570
  } catch (error) {
1079
- notice(`Skill ${skill.name} tidak bisa dibaca: ${error.code ?? error.message}`, "error");
1571
+ notice(t("Skill {name} cannot be read: {error}", { name: skill.name, error: error.code ?? error.message }), "error");
1080
1572
  return;
1081
1573
  }
1082
1574
  void runTurn(`${body}
@@ -1086,6 +1578,19 @@ ARGUMENTS: ${slash.args}`);
1086
1578
  },
1087
1579
  [runSlash, runTurn, runtime, notice]
1088
1580
  );
1581
+ const onPrompt = useCallback(
1582
+ (text) => {
1583
+ if (busy) setQueued((q) => [...q, text]);
1584
+ else submit(text);
1585
+ },
1586
+ [busy, submit]
1587
+ );
1588
+ useEffect2(() => {
1589
+ if (busy || pending || planAsk || !queued.length) return;
1590
+ const [next, ...rest] = queued;
1591
+ setQueued(rest);
1592
+ submit(next);
1593
+ }, [busy, pending, planAsk, queued, submit]);
1089
1594
  const initialSent = useRef(false);
1090
1595
  useEffect2(() => {
1091
1596
  if (initialPrompt && !initialSent.current) {
@@ -1100,10 +1605,30 @@ ARGUMENTS: ${slash.args}`);
1100
1605
  setMode(next);
1101
1606
  } else if (key.escape && busy && !pending) {
1102
1607
  controller.current?.abort();
1608
+ if (queued.length) {
1609
+ setQueued([]);
1610
+ notice(t("{n} queued messages cancelled.", { n: queued.length }), "warn");
1611
+ }
1612
+ } else if (key.ctrl && input === "t") {
1613
+ const next = !showThinking;
1614
+ const toggled = next ? t("Thinking shown (ctrl+t to hide).") : t("Thinking hidden (ctrl+t to show).");
1615
+ setShowThinking(next);
1616
+ setTranscript((t2) => {
1617
+ const last = [...t2.done, ...t2.live].findLast((e) => e.kind === "thinking");
1618
+ const reprint = next && last?.kind === "thinking" && t2.done.includes(last);
1619
+ return {
1620
+ ...t2,
1621
+ done: [
1622
+ ...t2.done,
1623
+ ...reprint ? [entry({ kind: "thinking", text: last.text, open: true })] : [],
1624
+ entry({ kind: "notice", text: toggled, tone: "info" })
1625
+ ]
1626
+ };
1627
+ });
1103
1628
  } else if (key.ctrl && input === "o" && lastTool.current) {
1104
- const t = lastTool.current;
1105
- notice(`\u23BF ${t.tool} ${t.target}
1106
- ${t.output}`);
1629
+ const t2 = lastTool.current;
1630
+ notice(`\u23BF ${t2.tool} ${t2.target}
1631
+ ${t2.output}`);
1107
1632
  }
1108
1633
  });
1109
1634
  const pickMcp = async (name) => {
@@ -1113,24 +1638,24 @@ ${t.output}`);
1113
1638
  if (item.installed) {
1114
1639
  removeGlobalServer(runtime.home, item.name);
1115
1640
  await runtime.mcp.remove(item.name);
1116
- notice(`MCP ${item.name} dihapus.`);
1641
+ notice(t("MCP {name} removed.", { name: item.name }));
1117
1642
  return;
1118
1643
  }
1119
1644
  const entry2 = CATALOG.find((c) => c.name === item.name);
1120
1645
  if (!entry2) return;
1121
1646
  const values = {};
1122
1647
  for (const input of entry2.inputs ?? []) {
1123
- const v = await ask(input.label, !!input.secret);
1648
+ const v = await ask(t(input.label), !!input.secret);
1124
1649
  if (!v) return;
1125
1650
  values[input.key] = v;
1126
1651
  }
1127
1652
  const config = fillTemplate(entry2.config, values);
1128
1653
  addGlobalServer(runtime.home, entry2.name, config);
1129
- notice(`MCP ${entry2.name} dipasang, menghubungkan\u2026`);
1654
+ notice(t("MCP {name} installed, connecting\u2026", { name: entry2.name }));
1130
1655
  await runtime.mcp.add({ name: entry2.name, config, source: "global" });
1131
1656
  const state = runtime.mcp.states().find((s) => s.name === entry2.name);
1132
- if (state?.status === "ready") notice(`MCP ${entry2.name} aktif \xB7 ${state.tools} alat.`);
1133
- else notice(`MCP ${entry2.name} gagal: ${state?.error ?? "tidak diketahui"}`, "error");
1657
+ if (state?.status === "ready") notice(t("MCP {name} active \xB7 {n} tools.", { name: entry2.name, n: state.tools }));
1658
+ else notice(t("MCP {name} failed: {error}", { name: entry2.name, error: state?.error ?? t("unknown") }), "error");
1134
1659
  };
1135
1660
  const pickProvider = async (id) => {
1136
1661
  setProviderMenu(null);
@@ -1147,7 +1672,7 @@ ${t.output}`);
1147
1672
  const c = runtime.config;
1148
1673
  const keyless = !c.providers[target].apiKeyEnv && PRESETS.some((p) => p.id === target);
1149
1674
  if (!hasKey(c, target, runtime.env) || id === "custom") {
1150
- const key = keyless ? "" : await ask(`API key untuk ${providerName(c, target)}`, true);
1675
+ const key = keyless ? "" : await ask(t("API key for {provider}", { provider: providerName(c, target) }), true);
1151
1676
  if (key === void 0) return;
1152
1677
  const result = await runtime.addProviderKey(target, key);
1153
1678
  if (!result.ok) {
@@ -1167,7 +1692,7 @@ ${t.output}`);
1167
1692
  const renderEntry = (e) => {
1168
1693
  switch (e.kind) {
1169
1694
  case "header":
1170
- return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs10(Text15, { children: [
1695
+ return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs11(Text15, { children: [
1171
1696
  /* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), bold: true, children: " \u273B " }),
1172
1697
  /* @__PURE__ */ jsx15(Text15, { bold: true, children: `BCCLI ${version}` }),
1173
1698
  /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \xB7 ${runtime.modelRef} (${runtime.providerLabel()}) \xB7 ${cwd}` })
@@ -1175,21 +1700,36 @@ ${t.output}`);
1175
1700
  case "user":
1176
1701
  return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: `> ${e.text}` }) }, e.id);
1177
1702
  case "assistant":
1703
+ if (e.cont) {
1704
+ return e.text ? /* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Markdown, { text: e.text }) }, e.id) : null;
1705
+ }
1178
1706
  if (!e.text.trim()) return null;
1179
- return /* @__PURE__ */ jsxs10(Box13, { marginTop: 1, children: [
1707
+ return /* @__PURE__ */ jsxs11(Box13, { marginTop: 1, children: [
1180
1708
  /* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), children: "\u25CF " }),
1181
1709
  /* @__PURE__ */ jsx15(Markdown, { text: e.text.replace(/^\s+/, "") })
1182
1710
  ] }, e.id);
1711
+ case "thinking": {
1712
+ const text = e.text.trim();
1713
+ if (!text) return null;
1714
+ if (!(e.open ?? showThinking)) {
1715
+ const lines = text.split("\n").length;
1716
+ 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);
1717
+ }
1718
+ return /* @__PURE__ */ jsxs11(Box13, { marginTop: 1, flexDirection: "column", children: [
1719
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t("\u273B Thinking (ctrl+t to hide)") }),
1720
+ /* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, italic: true, children: text }) })
1721
+ ] }, e.id);
1722
+ }
1183
1723
  case "tool":
1184
1724
  return /* @__PURE__ */ jsx15(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, sub: e.sub }, e.id);
1185
1725
  case "notice":
1186
1726
  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);
1187
1727
  }
1188
1728
  };
1189
- return /* @__PURE__ */ jsxs10(Box13, { flexDirection: "column", children: [
1729
+ return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", children: [
1190
1730
  /* @__PURE__ */ jsx15(Static, { items: transcript.done, children: renderEntry }),
1191
1731
  transcript.live.map(renderEntry),
1192
- busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: "Berpikir", startedAt }) }) : null,
1732
+ busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: t("Thinking"), startedAt }) }) : null,
1193
1733
  pending ? /* @__PURE__ */ jsx15(PermissionPrompt, { request: pending.request, onAnswer: answer }, pending.id) : null,
1194
1734
  picker ? /* @__PURE__ */ jsx15(
1195
1735
  ModelPicker,
@@ -1232,8 +1772,9 @@ ${t.output}`);
1232
1772
  }
1233
1773
  ) : null,
1234
1774
  /* @__PURE__ */ jsx15(TodoList, { items: todos }),
1235
- /* @__PURE__ */ jsx15(PromptInput, { disabled: busy || !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: submit, extraCommands }),
1236
- /* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel })
1775
+ queued.map((q, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: t(" \u23F3 queued: {q}", { q }) }, `${i}-${q}`)),
1776
+ /* @__PURE__ */ jsx15(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands }),
1777
+ /* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel, reasoning })
1237
1778
  ] });
1238
1779
  }
1239
1780
 
@@ -1243,27 +1784,27 @@ async function startInteractive(rt, opts) {
1243
1784
  if (opts.resume) {
1244
1785
  const { Session: Session2 } = await import("./session-7UB77CEG.js");
1245
1786
  const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
1246
- if (!list.length) console.log("Belum ada sesi di folder ini; memulai sesi baru.");
1787
+ if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
1247
1788
  else {
1248
1789
  list.forEach((s, i) => {
1249
- console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString("id-ID")} ${s.preview}`);
1790
+ console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString(locale())} ${s.preview}`);
1250
1791
  });
1251
1792
  const { createInterface } = await import("readline/promises");
1252
1793
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1253
- const answer = Number(await rl.question("Nomor sesi (enter = baru): "));
1794
+ const answer = Number(await rl.question(t("Session number (enter = new): ")));
1254
1795
  rl.close();
1255
1796
  const chosen = list[answer - 1];
1256
1797
  if (chosen) rt.resume(chosen.session);
1257
1798
  }
1258
1799
  }
1259
- const { serversToStart, setProjectTrust } = await import("./config-NPSYFAFD.js");
1800
+ const { serversToStart, setProjectTrust } = await import("./config-PBEZ3IGD.js");
1260
1801
  const plan = serversToStart(rt.home, rt.cwd);
1261
1802
  if (plan.needTrust.length) {
1262
1803
  const { createInterface } = await import("readline/promises");
1263
1804
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1264
1805
  for (const s of plan.needTrust) {
1265
1806
  const what = s.config.type === "http" ? s.config.url : [s.config.command, ...s.config.args ?? []].join(" ");
1266
- const answer = (await rl.question(`Repo ini ingin menjalankan server MCP "${s.name}" (${what}). Izinkan? [y/N] `)).trim().toLowerCase();
1807
+ 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();
1267
1808
  setProjectTrust(rt.home, rt.cwd, s.name, answer === "y", s.config);
1268
1809
  if (answer === "y") plan.start.push(s);
1269
1810
  }