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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,6 +28,7 @@ Perintah di dalam sesi (ketik `/` untuk saran, ↑↓ pilih, Enter jalankan):
28
28
  | `/model` | ganti model |
29
29
  | `/reasoning` | pilih Auto / Off / Low / Medium / High / Max |
30
30
  | `/provider` | tambah/pilih provider AI |
31
+ | `/language` | ganti bahasa tampilan (`en` / `id`) |
31
32
  | `/mcp` | pasang/kelola server MCP |
32
33
  | `/new` | sesi baru (sesi lama tetap tersimpan) |
33
34
  | `/resume` | lanjutkan sesi lain di folder ini |
@@ -56,6 +57,19 @@ Shift+Tab ganti mode izin: `default` → `acceptEdits` → `plan` → `allowAll`
56
57
  Mode `-p` tidak pernah bertanya: alat yang butuh izin ditolak, kecuali memakai
57
58
  `--allow-all` atau `--allowed-tools bash,edit,fetch`.
58
59
 
60
+ ## Bahasa
61
+
62
+ Tampilan BCCLI (bantuan, pesan, prompt izin) memakai **bahasa Inggris secara default**. Bahasa Indonesia tetap tersedia:
63
+
64
+ ```bash
65
+ bccli --lang id # hanya untuk satu kali jalan
66
+ BCCLI_LANG=id bccli # lewat environment
67
+ /language id # di dalam sesi; disimpan di ~/.bccli/config.json ("language")
68
+ ```
69
+
70
+ Urutan prioritas: `--lang`, lalu `BCCLI_LANG`, lalu `language` di config, lalu Inggris. Bahasa jawaban model mengikuti bahasa yang kamu tulis, dan
71
+ pesan alat yang dibaca model (hasil tool, galat) selalu berbahasa Inggris supaya tidak mengganggu bahasa jawabannya.
72
+
59
73
  ## Provider
60
74
 
61
75
  `/provider` di dalam sesi (atau `bccli provider add <id>`) memasang provider hanya dengan API key:
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ helpText,
4
+ parseCliArgs
5
+ } from "./chunk-6AVRFRFU.js";
6
+ import "./chunk-ELML4KVQ.js";
7
+ import "./chunk-VEZFR7M7.js";
8
+ import "./chunk-DDA5TJ7T.js";
9
+ export {
10
+ helpText,
11
+ parseCliArgs
12
+ };
@@ -35,8 +35,8 @@ function readSecret(prompt) {
35
35
  });
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
39
- const { parseCliArgs } = await import("./args-SKWUZOJD.js");
38
+ const { runProviderCommand } = await import("./providerCli-3FJAUH7C.js");
39
+ const { parseCliArgs } = await import("./args-L555OESS.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
42
42
  cwd,
@@ -5,36 +5,40 @@ import {
5
5
  import {
6
6
  REASONING_LEVELS,
7
7
  isReasoningLevel
8
- } from "./chunk-CLN4Z5E3.js";
8
+ } from "./chunk-VEZFR7M7.js";
9
9
  import {
10
- ConfigError
11
- } from "./chunk-2MGINW4O.js";
10
+ ConfigError,
11
+ parseLang,
12
+ t
13
+ } from "./chunk-DDA5TJ7T.js";
12
14
 
13
15
  // src/args.ts
14
16
  import { parseArgs } from "util";
15
- var HELP_TEXT = `BCCLI \u2014 agent AI BotConnector di terminal
17
+ var HELP = `BCCLI \u2014 BotConnector's AI coding agent for the terminal
16
18
 
17
- Pemakaian:
18
- bccli [tugas] mode interaktif (opsional langsung dengan tugas)
19
- bccli -p "tugas" jalankan satu tugas tanpa interaksi (skrip/CI)
20
- bccli login [provider] simpan API key (default: bc-cloud)
21
- bccli models daftar model dari provider aktif
22
- bccli provider list|add <id>|remove <id> kelola provider (custom: --url <url> [--name N] [--key-env ENV])
23
- bccli mcp list|add <nama>|remove <nama> kelola server MCP (katalog, atau --url <url>)
24
- bccli integrations lihat status integrasi agent eksternal
19
+ Usage:
20
+ bccli [task] interactive mode (optionally start with a task)
21
+ bccli -p "task" run one task without interaction (scripts/CI)
22
+ bccli login [provider] save an API key (default: bc-cloud)
23
+ bccli models list models from the active provider
24
+ bccli provider list|add <id>|remove <id> manage providers (custom: --url <url> [--name N] [--key-env ENV])
25
+ bccli mcp list|add <name>|remove <name> manage MCP servers (catalog, or --url <url>)
26
+ bccli integrations show the status of external agent integrations
25
27
  bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible
26
- bccli disconnect <agent> lepas integrasi dan pulihkan config sebelumnya
28
+ bccli disconnect <agent> remove an integration and restore the previous config
27
29
 
28
- Opsi:
29
- -m, --model <provider/model> pilih model, contoh bc-cloud/glm-5.3-flash
30
+ Options:
31
+ -m, --model <provider/model> choose a model, e.g. bc-cloud/glm-5.3-flash
30
32
  --reasoning <level> auto | off | low | medium | high | max
31
- -c, --continue lanjutkan sesi terakhir di folder ini
32
- -r, --resume pilih sesi untuk dilanjutkan
33
- --allow-all jalankan semua alat tanpa minta izin
34
- --allowed-tools <a,b> alat yang boleh tanpa izin: bash, edit, fetch
33
+ -c, --continue continue the last session in this folder
34
+ -r, --resume pick a session to continue
35
+ --allow-all run all tools without asking for permission
36
+ --allowed-tools <a,b> tools allowed without asking: bash, edit, fetch
35
37
  --permission-mode <mode> default | acceptEdits | plan | allowAll
36
- -v, --version versi
37
- -h, --help bantuan ini`;
38
+ --lang <en|id> interface language (default: en; or BCCLI_LANG, or "language" in config)
39
+ -v, --version version
40
+ -h, --help this help`;
41
+ var helpText = () => t(HELP);
38
42
  function parseCliArgs(argv) {
39
43
  const { values, positionals } = parseArgs({
40
44
  args: argv,
@@ -43,6 +47,7 @@ function parseCliArgs(argv) {
43
47
  print: { type: "boolean", short: "p" },
44
48
  model: { type: "string", short: "m" },
45
49
  reasoning: { type: "string" },
50
+ lang: { type: "string" },
46
51
  continue: { type: "boolean", short: "c" },
47
52
  resume: { type: "boolean", short: "r" },
48
53
  "allow-all": { type: "boolean" },
@@ -58,12 +63,13 @@ function parseCliArgs(argv) {
58
63
  });
59
64
  const reasoning = values.reasoning;
60
65
  if (reasoning && !isReasoningLevel(reasoning)) {
61
- throw new ConfigError(`--reasoning harus salah satu dari: ${REASONING_LEVELS.join(", ")}`);
66
+ throw new ConfigError(t("--reasoning must be one of: {levels}", { levels: REASONING_LEVELS.join(", ") }));
62
67
  }
63
68
  const mode = values["permission-mode"];
64
69
  if (mode && !MODE_ORDER.includes(mode)) {
65
- throw new ConfigError(`--permission-mode harus salah satu dari: ${MODE_ORDER.join(", ")}`);
70
+ throw new ConfigError(t("--permission-mode must be one of: {modes}", { modes: MODE_ORDER.join(", ") }));
66
71
  }
72
+ if (values.lang !== void 0 && !parseLang(values.lang)) throw new ConfigError(t("--lang must be one of: en, id"));
67
73
  const [first, ...rest] = positionals;
68
74
  const SUBCOMMANDS = ["login", "models", "provider", "mcp", "connect", "disconnect", "integrations"];
69
75
  const command = SUBCOMMANDS.includes(first) ? first : "run";
@@ -74,6 +80,7 @@ function parseCliArgs(argv) {
74
80
  print: !!values.print,
75
81
  model: values.model,
76
82
  reasoning,
83
+ lang: parseLang(values.lang),
77
84
  continue: !!values.continue,
78
85
  resume: !!values.resume,
79
86
  allowAll: !!values["allow-all"],
@@ -91,6 +98,6 @@ function parseCliArgs(argv) {
91
98
  }
92
99
 
93
100
  export {
94
- HELP_TEXT,
101
+ helpText,
95
102
  parseCliArgs
96
103
  };
@@ -5,7 +5,7 @@ import {
5
5
  readCredentials,
6
6
  readJsonConfig,
7
7
  removeCredential
8
- } from "./chunk-2MGINW4O.js";
8
+ } from "./chunk-DDA5TJ7T.js";
9
9
 
10
10
  // src/providers.ts
11
11
  import { mkdirSync, writeFileSync } from "fs";
@@ -45,6 +45,7 @@ function removeGlobalProvider(id, env = process.env) {
45
45
  export {
46
46
  providerName,
47
47
  hasKey,
48
+ readGlobalConfigFile,
48
49
  writeGlobalConfig,
49
50
  removeGlobalProvider
50
51
  };
@@ -0,0 +1,440 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/presets.ts
4
+ var PRESETS = [
5
+ { id: "bc-cloud", name: "BotConnector Cloud", baseURL: "https://api.botconnector.id/v1", apiKeyEnv: "BOTCONNECTOR_API_KEY" },
6
+ { id: "openrouter", name: "OpenRouter", baseURL: "https://openrouter.ai/api/v1", apiKeyEnv: "OPENROUTER_API_KEY" },
7
+ { id: "openai", name: "OpenAI", baseURL: "https://api.openai.com/v1", apiKeyEnv: "OPENAI_API_KEY" },
8
+ { id: "gemini", name: "Google Gemini", baseURL: "https://generativelanguage.googleapis.com/v1beta/openai", apiKeyEnv: "GEMINI_API_KEY" },
9
+ { id: "deepseek", name: "DeepSeek", baseURL: "https://api.deepseek.com/v1", apiKeyEnv: "DEEPSEEK_API_KEY" },
10
+ { id: "groq", name: "Groq", baseURL: "https://api.groq.com/openai/v1", apiKeyEnv: "GROQ_API_KEY" },
11
+ { id: "ollama-cloud", name: "Ollama Cloud", baseURL: "https://ollama.com/v1", apiKeyEnv: "OLLAMA_API_KEY" },
12
+ { id: "ollama", name: "Ollama lokal", baseURL: "http://127.0.0.1:11434/v1", local: true },
13
+ { id: "lmstudio", name: "LM Studio lokal", baseURL: "http://127.0.0.1:1234/v1", local: true }
14
+ ];
15
+
16
+ // src/i18n/id.ts
17
+ var ID = {
18
+ "MCP servers (enter: install / remove \xB7 esc close) \xB7 \u2713 active \u25CB not installed \u2717 error": "Server MCP (enter: pasang / hapus \xB7 esc tutup) \xB7 \u2713 aktif \u25CB belum \u2717 error",
19
+ "{label}\u2026 ({seconds}s \xB7 esc to cancel)": "{label}\u2026 ({seconds} dtk \xB7 esc batal)",
20
+ "Provider (\u2191\u2193 enter, esc cancel) \xB7 \u2713 ready \xB7 \u25CB needs API key": "Provider (\u2191\u2193 enter, esc batal) \xB7 \u2713 siap \xB7 \u25CB butuh API key",
21
+ "Choose a model (type to filter{filter} \xB7 \u2191\u2193 enter \xB7 esc cancel)": "Pilih model (ketik untuk menyaring{filter} \xB7 \u2191\u2193 enter \xB7 esc batal)",
22
+ "No matching models.": "Tidak ada model yang cocok.",
23
+ "{title} (\u2191\u2193 enter \xB7 type to filter{filter} \xB7 esc cancel)": "{title} (\u2191\u2193 enter \xB7 ketik untuk menyaring{filter} \xB7 esc batal)",
24
+ "No matches.": "Tidak ada yang cocok.",
25
+ "edit": "edit",
26
+ "write": "tulis",
27
+ "run": "jalankan",
28
+ "fetch": "ambil",
29
+ "yes for all edits in this project, this session": "ya untuk semua edit di project ini, sesi ini",
30
+ "yes this session for {rules}": "ya sesi ini untuk {rules}",
31
+ "Allow {verb} {target}?": "Izinkan {verb} {target}?",
32
+ "[y] yes": "[y] ya",
33
+ "[s] yes to all ": "[s] ya semua ",
34
+ "[n] no": "[n] tidak",
35
+ "Plan from the agent": "Rencana dari agent",
36
+ "[a] yes, auto-edit": "[a] ya, edit otomatis",
37
+ "[y] yes, ask at each step": "[y] ya, tanya tiap langkah",
38
+ "[n] not yet, revise first": "[n] belum, perbaiki dulu",
39
+ " enter save \xB7 esc cancel": " enter simpan \xB7 esc batal",
40
+ "shift+tab switch mode": "shift+tab ganti mode",
41
+ "{n} tokens": "{n} token",
42
+ "esc to cancel": "esc batal",
43
+ "/ for commands": "/ perintah",
44
+ "\u2026 {n} earlier steps": "\u2026 {n} langkah sebelumnya",
45
+ "\u2717 failed": "\u2717 gagal",
46
+ "\u2713 done": "\u2713 selesai",
47
+ "\u2026 {n} more lines (ctrl+o)": "\u2026 {n} baris lagi (ctrl+o)",
48
+ "No sessions in this folder yet; starting a new one.": "Belum ada sesi di folder ini; memulai sesi baru.",
49
+ "Session number (enter = new): ": "Nomor sesi (enter = baru): ",
50
+ 'This repo wants to run the MCP server "{name}" ({what}). Allow? [y/N] ': 'Repo ini ingin menjalankan server MCP "{name}" ({what}). Izinkan? [y/N] ',
51
+ "Conversation summarized to fit the model context.": "Percakapan diringkas agar muat di konteks model.",
52
+ 'Step limit of 50 reached. Type "continue" to keep going.': 'Batas 50 langkah tercapai. Ketik "lanjut" untuk meneruskan.',
53
+ "Cancelled.": "Dibatalkan.",
54
+ "Permission mode: {mode}": "Mode izin: {mode}",
55
+ "Folder: {cwd}": "Folder: {cwd}",
56
+ "MCP: {ready} active, {errors} errors": "MCP: {ready} aktif, {errors} error",
57
+ "Session tokens: {input} in \xB7 {output} out": "Token sesi: {input} masuk \xB7 {output} keluar",
58
+ "Context: ~{pct}% of 128k": "Konteks: \xB1{pct}% dari 128k",
59
+ "{name} \u2014 {description} \xB7 tools: {tools} \xB7 model: {model}": "{name} \u2014 {description} \xB7 alat: {tools} \xB7 model: {model}",
60
+ "all": "semua",
61
+ "same as main": "ikut utama",
62
+ "(no skills)": "(tidak ada skill)",
63
+ "(no custom commands)": "(tidak ada perintah custom)",
64
+ "Skills": "Skill",
65
+ "Custom commands": "Perintah custom",
66
+ "Started: {when}": "Mulai: {when}",
67
+ "{messages} messages \xB7 {input} tokens in \xB7 {output} out": "{messages} pesan \xB7 {input} token masuk \xB7 {output} keluar",
68
+ "File: {file}": "File: {file}",
69
+ "git is not installed or not on PATH, so /diff cannot be used.": "git tidak terpasang atau tidak ada di PATH, jadi /diff tidak bisa dipakai.",
70
+ "This folder is not a git repository, so there is no diff.": "Folder ini bukan repository git, jadi tidak ada diff.",
71
+ "No uncommitted changes.": "Tidak ada perubahan yang belum di-commit.",
72
+ "\u2026 {n} more lines": "\u2026 {n} baris lagi",
73
+ "New files (untracked): {files}": "File baru (belum di-track): {files}",
74
+ "\u2717 Node {version} (needs \u2265 22)": "\u2717 Node {version} (butuh \u2265 22)",
75
+ "\u25CB {name} (no key yet)": "\u25CB {name} (belum ada key)",
76
+ "\u2713 Connection {provider} ({n} models)": "\u2713 Koneksi {provider} ({n} model)",
77
+ "\u2717 Connection {provider}: {error}": "\u2717 Koneksi {provider}: {error}",
78
+ "list of commands and shortcuts": "daftar perintah dan pintasan",
79
+ "change model": "ganti model",
80
+ "set reasoning: auto/off/low/medium/high/max": "atur reasoning: auto/off/low/medium/high/max",
81
+ "add/choose an AI provider": "tambah/pilih provider AI",
82
+ "install/manage MCP servers": "pasang/kelola server MCP",
83
+ "new session (the old one stays saved)": "sesi baru (sesi lama tetap tersimpan)",
84
+ "continue another session in this folder": "lanjutkan sesi lain di folder ini",
85
+ "info about this session": "info sesi ini",
86
+ "version, model, permission mode, MCP, context": "versi, model, mode izin, MCP, konteks",
87
+ "view/revoke permissions": "lihat/cabut izin",
88
+ "undo the file edits of the last turn": "batalkan edit file giliran terakhir",
89
+ "git diff of the project": "git diff project",
90
+ "copy the last answer": "salin jawaban terakhir",
91
+ "save the conversation as markdown": "simpan percakapan ke markdown",
92
+ "project instructions (/memory <text>, /memory global <text>)": "instruksi project (/memory <teks>, /memory global <teks>)",
93
+ "create/update AGENTS.md for this project": "buat/perbarui AGENTS.md untuk project ini",
94
+ "list subagents": "daftar subagent",
95
+ "list skills and custom commands": "daftar skill dan perintah custom",
96
+ "check installation health": "cek kesehatan instalasi",
97
+ "save the API key of the current provider": "simpan API key provider aktif",
98
+ "delete the API key of the current provider": "hapus API key provider aktif",
99
+ "set the interface language: en/id": "atur bahasa tampilan: en/id",
100
+ "start a new conversation": "mulai percakapan baru",
101
+ "summarize the conversation": "ringkas percakapan",
102
+ "token usage of this session": "pemakaian token sesi ini",
103
+ "quit": "keluar",
104
+ [`BCCLI \u2014 BotConnector's AI coding agent for the terminal
105
+
106
+ Usage:
107
+ bccli [task] interactive mode (optionally start with a task)
108
+ bccli -p "task" run one task without interaction (scripts/CI)
109
+ bccli login [provider] save an API key (default: bc-cloud)
110
+ bccli models list models from the active provider
111
+ bccli provider list|add <id>|remove <id> manage providers (custom: --url <url> [--name N] [--key-env ENV])
112
+ bccli mcp list|add <name>|remove <name> manage MCP servers (catalog, or --url <url>)
113
+ bccli integrations show the status of external agent integrations
114
+ bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible
115
+ bccli disconnect <agent> remove an integration and restore the previous config
116
+
117
+ Options:
118
+ -m, --model <provider/model> choose a model, e.g. bc-cloud/glm-5.3-flash
119
+ --reasoning <level> auto | off | low | medium | high | max
120
+ -c, --continue continue the last session in this folder
121
+ -r, --resume pick a session to continue
122
+ --allow-all run all tools without asking for permission
123
+ --allowed-tools <a,b> tools allowed without asking: bash, edit, fetch
124
+ --permission-mode <mode> default | acceptEdits | plan | allowAll
125
+ --lang <en|id> interface language (default: en; or BCCLI_LANG, or "language" in config)
126
+ -v, --version version
127
+ -h, --help this help`]: `BCCLI \u2014 agent AI BotConnector di terminal
128
+
129
+ Pemakaian:
130
+ bccli [tugas] mode interaktif (opsional langsung dengan tugas)
131
+ bccli -p "tugas" jalankan satu tugas tanpa interaksi (skrip/CI)
132
+ bccli login [provider] simpan API key (default: bc-cloud)
133
+ bccli models daftar model dari provider aktif
134
+ bccli provider list|add <id>|remove <id> kelola provider (custom: --url <url> [--name N] [--key-env ENV])
135
+ bccli mcp list|add <nama>|remove <nama> kelola server MCP (katalog, atau --url <url>)
136
+ bccli integrations lihat status integrasi agent eksternal
137
+ bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible
138
+ bccli disconnect <agent> lepas integrasi dan pulihkan config sebelumnya
139
+
140
+ Opsi:
141
+ -m, --model <provider/model> pilih model, contoh bc-cloud/glm-5.3-flash
142
+ --reasoning <level> auto | off | low | medium | high | max
143
+ -c, --continue lanjutkan sesi terakhir di folder ini
144
+ -r, --resume pilih sesi untuk dilanjutkan
145
+ --allow-all jalankan semua alat tanpa minta izin
146
+ --allowed-tools <a,b> alat yang boleh tanpa izin: bash, edit, fetch
147
+ --permission-mode <mode> default | acceptEdits | plan | allowAll
148
+ --lang <en|id> bahasa tampilan (default: en; atau BCCLI_LANG, atau "language" di config)
149
+ -v, --version versi
150
+ -h, --help bantuan ini`,
151
+ "--reasoning must be one of: {levels}": "--reasoning harus salah satu dari: {levels}",
152
+ "--permission-mode must be one of: {modes}": "--permission-mode harus salah satu dari: {modes}",
153
+ "Plan needs approval; run with --allow-all.": "Rencana butuh persetujuan; jalankan dengan --allow-all.",
154
+ "Step limit reached.": "Batas langkah tercapai.",
155
+ "Needs permission for {tool}. Run with --allow-all or --allowed-tools {kind}.": "Butuh izin untuk {tool}. Jalankan dengan --allow-all atau --allowed-tools {kind}.",
156
+ "--lang must be one of: en, id": "--lang harus salah satu dari: en, id",
157
+ "Invalid config: {path} ({error})": "Config tidak valid: {path} ({error})",
158
+ "Model must be in provider/model format, e.g. bc-cloud/glm-5.3-flash (got: {ref})": "Model harus berformat provider/model, contoh bc-cloud/glm-5.3-flash (dapat: {ref})",
159
+ 'Provider "{id}" is not in the config. Available: {list}': 'Provider "{id}" tidak ada di config. Tersedia: {list}',
160
+ "No API key for {id} yet. Run `bccli login` or set the env var {env}.": "API key untuk {id} belum ada. Jalankan `bccli login` atau set env {env}.",
161
+ "Error from the provider: {message}": "Error dari provider: {message}",
162
+ "Cannot connect to {url}: {error}": "Tidak bisa terhubung ke {url}: {error}",
163
+ "{status} from {url}: {message}": "{status} dari {url}: {message}",
164
+ "Request failed": "Permintaan gagal",
165
+ "Empty response from the provider": "Respons provider kosong",
166
+ "{status} while fetching the model list": "{status} saat mengambil daftar model",
167
+ "The API key was rejected or {provider} could not be reached.": "API key ditolak atau {provider} tidak bisa dihubungi.",
168
+ "Usage: bccli provider {action} <id>": "Pemakaian: bccli provider {action} <id>",
169
+ "{id} removed.": "{id} dihapus.",
170
+ "Unknown action: {action}. Use list, add, or remove.": "Aksi tidak dikenal: {action}. Pakai list, add, atau remove.",
171
+ "{id} is not a preset. For a custom provider add --url <OpenAI-compatible base URL>.": "{id} bukan preset. Untuk provider custom tambahkan --url <base URL OpenAI-compatible>.",
172
+ "API key for {id} (leave empty if not needed): ": "API key untuk {id} (kosongkan bila tidak perlu): ",
173
+ "{id} is ready. {n} models available.": "{id} siap. {n} model tersedia.",
174
+ "Usage: bccli mcp {action} <name>": "Pemakaian: bccli mcp {action} <nama>",
175
+ "{name} removed.": "{name} dihapus.",
176
+ "{name} installed ({url}).": "{name} terpasang ({url}).",
177
+ "{name} is not in the catalog. For another remote server use: bccli mcp add {name} --url <url>": "{name} tidak ada di katalog. Untuk server remote lain pakai: bccli mcp add {name} --url <url>",
178
+ "{name} installed. Active in the next bccli session.": "{name} terpasang. Aktif di sesi bccli berikutnya.",
179
+ "control a browser (open pages, click, fill forms, screenshot)": "kontrol browser (buka halaman, klik, isi form, screenshot)",
180
+ "up-to-date library documentation": "dokumentasi library terbaru",
181
+ "fetch web pages as markdown (needs uv)": "ambil halaman web jadi markdown (butuh uv)",
182
+ "access folders outside the project": "akses folder di luar project",
183
+ "Folder the server may access": "Folder yang boleh diakses",
184
+ "structured git operations (needs uv)": "operasi git terstruktur (butuh uv)",
185
+ "GitHub issues, PRs, and repos": "issue, PR, dan repo GitHub",
186
+ "Missing MCP fields: {fields}": "Isian MCP belum lengkap: {fields}",
187
+ "Invalid MCP file: {path} ({error})": "File MCP tidak valid: {path} ({error})",
188
+ "could not be reached": "tidak bisa dihubungi",
189
+ "{label} must be one of: {levels}": "{label} harus salah satu dari: {levels}",
190
+ "This provider has no manual reasoning setting yet. Use Auto.": "Provider ini belum memiliki kontrak reasoning manual. Gunakan Auto.",
191
+ "This model/provider does not support reasoning level '{level}'. Use Auto{supported}.": "Model/provider ini tidak mendukung reasoning level '{level}'. Gunakan Auto{supported}.",
192
+ '-p mode needs a task, e.g. bccli -p "explain this repo"': 'Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"',
193
+ 'Project MCP server "{name}" skipped (not approved yet; run interactive bccli once to approve it).': 'Server MCP project "{name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).',
194
+ 'Interactive mode needs a terminal. For scripts/CI use: bccli -p "task"': 'Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"',
195
+ "Agent must be one of: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code": "Agent harus salah satu dari: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code",
196
+ "Cannot determine the home directory.": "Home directory tidak dapat ditentukan.",
197
+ "No BotConnector API key yet. Run bccli login bc-cloud first.": "API key BotConnector belum tersedia. Jalankan bccli login bc-cloud terlebih dahulu.",
198
+ "Could not load the BotConnector model catalog (HTTP {status}).": "Katalog model BotConnector gagal dimuat (HTTP {status}).",
199
+ "The OpenCode config is not valid JSON: {path}": "Config OpenCode tidak valid JSON: {path}",
200
+ "OpenCode is connected to BotConnector ({n} models).": "OpenCode terhubung ke BotConnector ({n} model).",
201
+ "The Aider config already has its own env-file/openai-api settings: {path}. BCCLI will not overwrite them.": "Config Aider sudah memiliki env-file/openai-api setting sendiri: {path}. BCCLI tidak akan menimpanya.",
202
+ "Aider is connected to BotConnector.": "Aider terhubung ke BotConnector.",
203
+ "The BotConnector catalog has no model that external agents can use.": "Katalog BotConnector tidak memiliki model yang dapat dipakai agent eksternal.",
204
+ "The Cline config is not valid JSON: {path}": "Config Cline tidak valid JSON: {path}",
205
+ "Cline already uses another openai-compatible provider ({base}). BCCLI will not overwrite it; disconnect or change that provider first.": "Cline sudah memakai provider openai-compatible lain ({base}). BCCLI tidak akan menimpanya; disconnect/ubah provider tersebut lebih dulu.",
206
+ "Cline is connected to BotConnector ({n} models available; default {model}).": "Cline terhubung ke BotConnector ({n} model tersedia; default {model}).",
207
+ "The DeepSeek Harness config is not valid YAML: {path}": "Config DeepSeek Harness tidak valid YAML: {path}",
208
+ 'DeepSeek Harness already has a provider "{id}" with a different endpoint.': 'DeepSeek Harness sudah memiliki provider "{id}" dengan endpoint lain.',
209
+ "$DSH_HOME/.env already has BOTCONNECTOR_API_KEY outside the BCCLI block: {path}": "$DSH_HOME/.env sudah memiliki BOTCONNECTOR_API_KEY di luar blok BCCLI: {path}",
210
+ "DeepSeek Harness is connected to BotConnector ({n} models).": "DeepSeek Harness terhubung ke BotConnector ({n} model).",
211
+ "Equivalent long form: dsh --profile web": "Bentuk panjang yang setara: dsh --profile web",
212
+ "The launcher does not store the API key in the script; the key is read from BCCLI storage when it runs.": "Launcher tidak menyimpan API key di dalam script; key dibaca dari storage BCCLI saat dijalankan.",
213
+ "The Python and Node OpenAI SDKs can read OPENAI_BASE_URL and OPENAI_API_KEY from this profile.": "Python dan Node OpenAI SDK dapat membaca OPENAI_BASE_URL dan OPENAI_API_KEY dari profile ini.",
214
+ "BCCLI does not edit Cursor\u2019s internal storage because that override format is not a stable external configuration.": "BCCLI tidak mengedit storage internal Cursor karena format override tersebut bukan konfigurasi eksternal yang stabil.",
215
+ "The BotConnector catalog has no model for external coding agents.": "Katalog BotConnector tidak memiliki model untuk coding agent eksternal.",
216
+ "Codex already has botconnector.config.toml. BCCLI will not overwrite it.": "Codex sudah memiliki botconnector.config.toml. BCCLI tidak akan menimpanya.",
217
+ "Codex CLI is connected to the BotConnector Responses API.": "Codex CLI terhubung ke BotConnector Responses API.",
218
+ "Claude Code is connected to the BotConnector Anthropic Messages compatibility API.": "Claude Code terhubung ke BotConnector Anthropic Messages compatibility API.",
219
+ "{target}: not managed by BCCLI yet.": "{target}: belum dikelola oleh BCCLI.",
220
+ "{target}: the BotConnector integration was removed and the config restored.": "{target}: integrasi BotConnector dilepas dan config dipulihkan.",
221
+ "Custom commands:": "Perintah custom:",
222
+ "Skills:": "Skill:",
223
+ "shift+tab switch permission mode esc cancel the turn": "shift+tab ganti mode izin esc batalkan giliran",
224
+ "ctrl+o full output of the last tool \\ + enter new line": "ctrl+o output alat terakhir lengkap \\ + enter baris baru",
225
+ "ctrl+t show/hide the model\u2019s thinking": "ctrl+t buka/tutup thinking model",
226
+ "@file + tab complete file names \u2191\u2193 input history": "@file + tab lengkapi nama file \u2191\u2193 riwayat input",
227
+ "custom command": "perintah custom",
228
+ "skill \xB7 {description}": "skill \xB7 {description}",
229
+ "Conversation cleared.": "Percakapan dikosongkan.",
230
+ "Could not summarize: {error}": "Gagal meringkas: {error}",
231
+ "Reasoning: {level} (saved as default)": "Reasoning: {level} (tersimpan sebagai default)",
232
+ "not supported by this provider": "tidak didukung provider ini",
233
+ "Model: {model} (saved as default)": "Model: {model} (tersimpan sebagai default)",
234
+ "Cannot fetch the model list: {error}": "Tidak bisa mengambil daftar model: {error}",
235
+ "New session started. The previous session stays saved (/resume).": "Sesi baru dimulai. Sesi sebelumnya tetap tersimpan (/resume).",
236
+ "No other sessions in this folder yet.": "Belum ada sesi lain di folder ini.",
237
+ "Continue a session": "Lanjutkan sesi",
238
+ "(empty)": "(kosong)",
239
+ "{n} messages": "{n} pesan",
240
+ "Resuming the session ({messages} messages saved \xB7 {chat} user/assistant chats shown).": "Melanjutkan sesi ({messages} pesan tersimpan \xB7 {chat} chat user/assistant ditampilkan).",
241
+ "Checking\u2026": "Memeriksa\u2026",
242
+ "No saved permissions yet. Permissions from [a] answers and from the config will appear here.": "Belum ada izin tersimpan. Izin dari jawaban [a] dan dari config akan muncul di sini.",
243
+ "Active permissions (enter: revoke a session permission)": "Izin aktif (enter: cabut izin sesi)",
244
+ "this session": "sesi ini",
245
+ "{rule} comes from the config/flag; remove it from ~/.bccli/config.json to revoke it.": "{rule} berasal dari config/flag; hapus dari ~/.bccli/config.json untuk mencabutnya.",
246
+ "Permission {rule} revoked.": "Izin {rule} dicabut.",
247
+ "{path} ({lines} lines)": "{path} ({lines} baris)",
248
+ "No AGENTS.md / BCCLI.md yet. Add one with /memory <text>.": "Belum ada AGENTS.md / BCCLI.md. Tambah dengan /memory <teks>.",
249
+ "Write the text: /memory global <text>.": "Tulis teksnya: /memory global <teks>.",
250
+ "Could not write {file}: {error}": "Gagal menulis {file}: {error}",
251
+ "Added to {file}.": "Ditambahkan ke {file}.",
252
+ "API key for {provider}": "API key untuk {provider}",
253
+ "Key for {provider} saved, but {env} in the environment takes precedence and is still used.": "Key {provider} tersimpan, tapi {env} di environment lebih diutamakan dan tetap dipakai.",
254
+ "Key for {provider} saved \xB7 {n} models.": "Key {provider} tersimpan \xB7 {n} model.",
255
+ "Stored key deleted, but {env} is still set in the environment and is still used.": "Key tersimpan dihapus, tapi {env} masih ada di environment dan tetap dipakai.",
256
+ "No stored key; {env} is still set in the environment and is still used.": "Tidak ada key tersimpan; {env} masih ada di environment dan tetap dipakai.",
257
+ "No stored key for {provider}.": "Tidak ada key tersimpan untuk {provider}.",
258
+ "Key for {provider} deleted. Use /login or /provider to add one again.": "Key {provider} dihapus. Pakai /login atau /provider untuk menambah lagi.",
259
+ "No file edits to undo.": "Tidak ada edit file yang bisa dibatalkan.",
260
+ "Could not restore: {files}": "Gagal dikembalikan: {files}",
261
+ "Restored: {files}": "Dikembalikan: {files}",
262
+ "Deleted (new files): {files}": "Dihapus (file baru): {files}",
263
+ "Too large to back up, left unchanged: {files}": "Terlalu besar untuk disimpan, tidak diubah: {files}",
264
+ "Note: changes made through bash commands cannot be undone.": "Catatan: perubahan lewat perintah bash tidak bisa di-undo.",
265
+ "No answer to copy yet.": "Belum ada jawaban untuk disalin.",
266
+ "Copied to the clipboard (if the terminal supports OSC 52) and saved to {file}.": "Disalin ke clipboard (bila terminal mendukung OSC 52) dan disimpan di {file}.",
267
+ "{file} already exists. Overwrite? (y/N)": "{file} sudah ada. Timpa? (y/N)",
268
+ "Export cancelled.": "Ekspor dibatalkan.",
269
+ "BCCLI session {when}": "Sesi BCCLI {when}",
270
+ "Conversation saved to {file}.": "Percakapan disimpan di {file}.",
271
+ "Unknown command: /{name}. Type /help.": "Perintah tidak dikenal: /{name}. Ketik /help.",
272
+ "Skill {name} cannot be read: {error}": "Skill {name} tidak bisa dibaca: {error}",
273
+ "{n} queued messages cancelled.": "{n} pesan antrian dibatalkan.",
274
+ "Thinking shown (ctrl+t to hide).": "Thinking ditampilkan (ctrl+t untuk menutup).",
275
+ "Thinking hidden (ctrl+t to show).": "Thinking disembunyikan (ctrl+t untuk membuka).",
276
+ "MCP {name} removed.": "MCP {name} dihapus.",
277
+ "MCP {name} installed, connecting\u2026": "MCP {name} dipasang, menghubungkan\u2026",
278
+ "MCP {name} active \xB7 {n} tools.": "MCP {name} aktif \xB7 {n} alat.",
279
+ "MCP {name} failed: {error}": "MCP {name} gagal: {error}",
280
+ "unknown": "tidak diketahui",
281
+ "\u273B Thinking \xB7 {n} lines \xB7 ctrl+t to show": "\u273B Berpikir \xB7 {n} baris \xB7 ctrl+t buka",
282
+ "\u273B Thinking (ctrl+t to hide)": "\u273B Berpikir (ctrl+t tutup)",
283
+ " \u23F3 queued: {q}": " \u23F3 antri: {q}",
284
+ "Thinking": "Berpikir",
285
+ "Language: {lang}. Change it with /language en or /language id.": "Bahasa: {lang}. Ganti dengan /language en atau /language id.",
286
+ 'Unknown language "{lang}". Use en or id.': 'Bahasa "{lang}" tidak dikenal. Pakai en atau id.',
287
+ "Could not save the language: {error}": "Gagal menyimpan bahasa: {error}",
288
+ "Language set to {lang} (saved as default).": "Bahasa diatur ke {lang} (tersimpan sebagai default).",
289
+ "The model got stuck repeating text, so the answer was stopped. Try again or switch models with /model.": "Model terjebak mengulang teks, jawaban dihentikan. Coba ulangi atau ganti model dengan /model.",
290
+ "This model may not support tool calling. Try another model with /model.": "Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model.",
291
+ "{lines} lines written": "{lines} baris ditulis",
292
+ "{n} lines": "{n} baris",
293
+ "{n} results": "{n} hasil",
294
+ "{steps} steps \xB7 {tokens} tokens": "{steps} langkah \xB7 {tokens} token",
295
+ "plan": "rencana",
296
+ "~{n} tokens": "~{n} token",
297
+ "focused excerpt \xB7 cache": "kutipan fokus \xB7 cache",
298
+ "focused excerpt (Keenable)": "kutipan fokus (Keenable)",
299
+ "focused excerpt (local)": "kutipan fokus (lokal)",
300
+ "over 5 MB, truncated": "lebih dari 5 MB, dipotong",
301
+ "{n} characters": "{n} karakter",
302
+ "{input} tokens in \xB7 {output} tokens out": "{input} token masuk \xB7 {output} token keluar",
303
+ "Run: dsh web": "Jalankan: dsh web",
304
+ "timeout after {n} seconds": "timeout {n} detik"
305
+ };
306
+
307
+ // src/i18n.ts
308
+ var LANGS = ["en", "id"];
309
+ var current = "en";
310
+ function parseLang(value) {
311
+ const v = value?.trim().toLowerCase().split(/[-_.]/)[0];
312
+ return LANGS.includes(v ?? "") ? v : void 0;
313
+ }
314
+ function setLanguage(lang) {
315
+ current = lang;
316
+ }
317
+ function getLanguage() {
318
+ return current;
319
+ }
320
+ var locale = () => current === "id" ? "id-ID" : "en-US";
321
+ function t(en, params) {
322
+ const template = current === "id" ? ID[en] ?? en : en;
323
+ return params ? template.replace(/\{(\w+)\}/g, (match, key) => key in params ? String(params[key]) : match) : template;
324
+ }
325
+ function langFromArgv(argv) {
326
+ for (let i = 0; i < argv.length; i++) {
327
+ if (argv[i] === "--lang") return parseLang(argv[i + 1]);
328
+ if (argv[i].startsWith("--lang=")) return parseLang(argv[i].slice(7));
329
+ }
330
+ return void 0;
331
+ }
332
+ function resolveLanguage(argv, env, fromConfig) {
333
+ return langFromArgv(argv) ?? parseLang(env.BCCLI_LANG) ?? parseLang(fromConfig) ?? "en";
334
+ }
335
+
336
+ // src/config.ts
337
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
338
+ import { homedir } from "os";
339
+ import { join } from "path";
340
+ var ConfigError = class extends Error {
341
+ };
342
+ var DEFAULT_CONFIG = {
343
+ model: "bc-cloud/glm-5.3-flash",
344
+ permissionMode: "default",
345
+ reasoning: "auto",
346
+ providers: Object.fromEntries(PRESETS.map((p) => [p.id, { baseURL: p.baseURL, apiKeyEnv: p.apiKeyEnv }])),
347
+ allow: []
348
+ };
349
+ function bccliHome(env = process.env) {
350
+ return env.BCCLI_HOME ?? join(homedir(), ".bccli");
351
+ }
352
+ function readJsonConfig(path) {
353
+ if (!existsSync(path)) return {};
354
+ try {
355
+ return JSON.parse(readFileSync(path, "utf8"));
356
+ } catch (error) {
357
+ throw new ConfigError(t("Invalid config: {path} ({error})", { path, error: error.message }));
358
+ }
359
+ }
360
+ function untrustedProviders(project, known) {
361
+ const out = {};
362
+ for (const [id, provider] of Object.entries(project.providers ?? {})) {
363
+ if (known[id] || typeof provider?.baseURL !== "string") continue;
364
+ out[id] = { baseURL: provider.baseURL };
365
+ }
366
+ return out;
367
+ }
368
+ function loadConfig(cwd, env = process.env) {
369
+ const global = readJsonConfig(join(bccliHome(env), "config.json"));
370
+ const project = readJsonConfig(join(cwd, ".bccli", "config.json"));
371
+ const known = { ...DEFAULT_CONFIG.providers, ...global.providers };
372
+ const fromProject = untrustedProviders(project, known);
373
+ return {
374
+ model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
375
+ permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
376
+ reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
377
+ providers: { ...known, ...fromProject },
378
+ projectProviders: Object.keys(fromProject),
379
+ allow: [...global.allow ?? []]
380
+ };
381
+ }
382
+ function readCredentials(env = process.env) {
383
+ const path = join(bccliHome(env), "credentials");
384
+ if (!existsSync(path)) return {};
385
+ try {
386
+ return JSON.parse(readFileSync(path, "utf8"));
387
+ } catch {
388
+ return {};
389
+ }
390
+ }
391
+ function saveCredential(providerId, key, env = process.env) {
392
+ const home = bccliHome(env);
393
+ mkdirSync(home, { recursive: true });
394
+ const path = join(home, "credentials");
395
+ writeFileSync(path, JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), { mode: 384 });
396
+ chmodSync(path, 384);
397
+ }
398
+ function resolveModel(config, modelRef, env = process.env) {
399
+ const slash = modelRef.indexOf("/");
400
+ if (slash <= 0 || slash === modelRef.length - 1) {
401
+ throw new ConfigError(t("Model must be in provider/model format, e.g. bc-cloud/glm-5.3-flash (got: {ref})", { ref: modelRef }));
402
+ }
403
+ const providerId = modelRef.slice(0, slash);
404
+ const provider = config.providers[providerId];
405
+ if (!provider) {
406
+ throw new ConfigError(t('Provider "{id}" is not in the config. Available: {list}', { id: providerId, list: Object.keys(config.providers).join(", ") }));
407
+ }
408
+ const apiKey = (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || readCredentials(env)[providerId] || void 0;
409
+ if (provider.apiKeyEnv && !apiKey) {
410
+ throw new ConfigError(t("No API key for {id} yet. Run `bccli login` or set the env var {env}.", { id: providerId, env: provider.apiKeyEnv }));
411
+ }
412
+ return { providerId, model: modelRef.slice(slash + 1), baseURL: provider.baseURL.replace(/\/+$/, ""), apiKey };
413
+ }
414
+ function removeCredential(providerId, env = process.env) {
415
+ const creds = readCredentials(env);
416
+ if (!(providerId in creds)) return false;
417
+ delete creds[providerId];
418
+ const path = join(bccliHome(env), "credentials");
419
+ writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 384 });
420
+ chmodSync(path, 384);
421
+ return true;
422
+ }
423
+
424
+ export {
425
+ PRESETS,
426
+ parseLang,
427
+ setLanguage,
428
+ getLanguage,
429
+ locale,
430
+ t,
431
+ resolveLanguage,
432
+ ConfigError,
433
+ bccliHome,
434
+ readJsonConfig,
435
+ loadConfig,
436
+ readCredentials,
437
+ saveCredential,
438
+ resolveModel,
439
+ removeCredential
440
+ };
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ConfigError
4
- } from "./chunk-2MGINW4O.js";
3
+ ConfigError,
4
+ t
5
+ } from "./chunk-DDA5TJ7T.js";
5
6
 
6
7
  // src/mcp/config.ts
7
8
  import { createHash } from "crypto";
@@ -15,7 +16,7 @@ function readJson(path, fallback) {
15
16
  try {
16
17
  return JSON.parse(readFileSync(path, "utf8"));
17
18
  } catch (error) {
18
- throw new ConfigError(`File MCP tidak valid: ${path} (${error.message})`);
19
+ throw new ConfigError(t("Invalid MCP file: {path} ({error})", { path, error: error.message }));
19
20
  }
20
21
  }
21
22
  function writePrivate(path, data) {