@botconnector/bccli 0.4.0-beta.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.en.md +242 -0
  2. package/README.md +80 -4
  3. package/dist/acp-ATIL4JOZ.js +182 -0
  4. package/dist/args-J5ONVGIF.js +13 -0
  5. package/dist/chunk-45SDLMTV.js +341 -0
  6. package/dist/chunk-6M5X2KZE.js +8 -0
  7. package/dist/chunk-7F3IWBPQ.js +191 -0
  8. package/dist/{chunk-CLN4Z5E3.js → chunk-BLOYFIAD.js} +6 -3
  9. package/dist/chunk-BNA74HPN.js +145 -0
  10. package/dist/{chunk-DX7CZTBB.js → chunk-BQ2IGPBR.js} +14 -9
  11. package/dist/chunk-JL5ZCSP2.js +76 -0
  12. package/dist/chunk-KEMYIGCP.js +235 -0
  13. package/dist/{chunk-BFZXRKW5.js → chunk-L7QI6JN2.js} +24 -2
  14. package/dist/chunk-LACUWBWS.js +361 -0
  15. package/dist/chunk-M7CALSP3.js +733 -0
  16. package/dist/{chunk-ELML4KVQ.js → chunk-O7M3TGIW.js} +34 -2
  17. package/dist/chunk-PSO7R5AD.js +10 -0
  18. package/dist/chunk-PUN4HMAG.js +940 -0
  19. package/dist/{chunk-SJH3YLW4.js → chunk-QDQU6VY2.js} +60 -6
  20. package/dist/chunk-QZKLZELZ.js +64 -0
  21. package/dist/{chunk-7S6JXQCH.js → chunk-SPRNLS7K.js} +2 -1
  22. package/dist/chunk-TLPFQIS2.js +231 -0
  23. package/dist/chunk-TVALKHFB.js +2143 -0
  24. package/dist/chunk-UZWJQF67.js +500 -0
  25. package/dist/chunk-XBO5GACQ.js +56 -0
  26. package/dist/cli.js +151 -1382
  27. package/dist/completion-HPPFDAPB.js +125 -0
  28. package/dist/{config-YUNKPCNB.js → config-COPNMMV6.js} +5 -2
  29. package/dist/config-RZ5JONMX.js +30 -0
  30. package/dist/ghBotCli-IBCZIWT5.js +677 -0
  31. package/dist/integrationCli-P75R2VSC.js +685 -0
  32. package/dist/login-SYIQP46J.js +16 -0
  33. package/dist/{mcpCli-DZWQPTOS.js → mcpCli-QUV5SD64.js} +42 -9
  34. package/dist/modelsCli-TRRB5LX4.js +36 -0
  35. package/dist/npmRunner-LU43GM3W.js +46 -0
  36. package/dist/oauthLogin-6FWQABJ7.js +210 -0
  37. package/dist/pluginsCli-GNW4TPVV.js +284 -0
  38. package/dist/prCli-5IMV3VNQ.js +78 -0
  39. package/dist/providerCli-FAOZHOKW.js +15 -0
  40. package/dist/sdk.js +5490 -0
  41. package/dist/serve-6QLBJUYW.js +178 -0
  42. package/dist/serve-JG7RLIUD.js +515 -0
  43. package/dist/session-KBAQ4BZ5.js +12 -0
  44. package/dist/setup-FJAWDWV4.js +24 -0
  45. package/dist/statsCli-CF3R7ATL.js +353 -0
  46. package/dist/transferCli-TK2GSVRK.js +71 -0
  47. package/dist/ui-B3CJ6TGE.js +2365 -0
  48. package/dist/update-K5IQ2MYR.js +145 -0
  49. package/package.json +25 -4
  50. package/dist/args-JPVSGCGY.js +0 -12
  51. package/dist/chunk-2MGINW4O.js +0 -114
  52. package/dist/chunk-ISGBGFSU.js +0 -52
  53. package/dist/chunk-KV27KTOX.js +0 -254
  54. package/dist/chunk-U7GK73CK.js +0 -341
  55. package/dist/chunk-ZYCYTEKD.js +0 -96
  56. package/dist/integrationCli-BGT5UZU4.js +0 -223
  57. package/dist/login-6K32PIXV.js +0 -9
  58. package/dist/providerCli-Q7LRM2NP.js +0 -12
  59. package/dist/session-7UB77CEG.js +0 -9
  60. package/dist/ui-6GTQUJOE.js +0 -1415
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ defaultOpen
4
+ } from "./chunk-7F3IWBPQ.js";
5
+ import {
6
+ removeCredential,
7
+ saveCredential
8
+ } from "./chunk-LACUWBWS.js";
9
+ import {
10
+ t
11
+ } from "./chunk-UZWJQF67.js";
12
+
13
+ // src/login.ts
14
+ function readSecret(prompt) {
15
+ return new Promise((resolve) => {
16
+ process.stdout.write(prompt);
17
+ const stdin = process.stdin;
18
+ if (!stdin.isTTY) {
19
+ let data = "";
20
+ stdin.on("data", (d) => {
21
+ data += d;
22
+ });
23
+ stdin.on("end", () => resolve(data.trim()));
24
+ return;
25
+ }
26
+ let value = "";
27
+ stdin.setRawMode(true);
28
+ stdin.resume();
29
+ const onData = (buffer) => {
30
+ for (const ch of buffer.toString()) {
31
+ if (ch === "\r" || ch === "\n") {
32
+ stdin.setRawMode(false);
33
+ stdin.pause();
34
+ stdin.off("data", onData);
35
+ process.stdout.write("\n");
36
+ resolve(value.trim());
37
+ return;
38
+ }
39
+ if (ch === "") process.exit(130);
40
+ if (ch === "\x7F" || ch === "\b") value = value.slice(0, -1);
41
+ else value += ch;
42
+ }
43
+ };
44
+ stdin.on("data", onData);
45
+ });
46
+ }
47
+ function readLine(prompt) {
48
+ return new Promise((resolve) => {
49
+ process.stdout.write(prompt);
50
+ const stdin = process.stdin;
51
+ if (!stdin.isTTY) {
52
+ let data = "";
53
+ stdin.on("data", (d) => {
54
+ data += d;
55
+ });
56
+ stdin.on("end", () => resolve(data.trim()));
57
+ return;
58
+ }
59
+ let value = "";
60
+ stdin.setRawMode(true);
61
+ stdin.resume();
62
+ const onData = (buffer) => {
63
+ for (const ch of buffer.toString()) {
64
+ const code = ch.charCodeAt(0);
65
+ if (code === 13 || code === 10) {
66
+ stdin.setRawMode(false);
67
+ stdin.pause();
68
+ stdin.off("data", onData);
69
+ process.stdout.write("\n");
70
+ resolve(value.trim());
71
+ return;
72
+ }
73
+ if (code === 3) process.exit(130);
74
+ if (code === 127 || code === 8) value = value.slice(0, -1);
75
+ else value += ch;
76
+ }
77
+ };
78
+ stdin.on("data", onData);
79
+ });
80
+ }
81
+ function defaultLoginDeps() {
82
+ return {
83
+ out: (s) => console.log(s),
84
+ err: (s) => console.error(s),
85
+ env: process.env,
86
+ isTTY: process.stdin.isTTY ?? false,
87
+ open: (url) => defaultOpen(new URL(url)),
88
+ readLine,
89
+ saveCredential: (id, key) => saveCredential(id, key, process.env),
90
+ removeCredential: (id) => removeCredential(id, process.env),
91
+ fetch: globalThis.fetch,
92
+ // Wiring NYATA jalur lama: persis runLogin lama (provider add + prompt key).
93
+ legacyAdd: async (providerId, targetCwd) => {
94
+ const { runProviderCommand } = await import("./providerCli-FAOZHOKW.js");
95
+ const { parseCliArgs: parse } = await import("./args-J5ONVGIF.js");
96
+ return runProviderCommand(parse(["provider", "add", providerId]), {
97
+ env: process.env,
98
+ cwd: targetCwd,
99
+ out: (s) => console.log(s),
100
+ err: (s) => console.error(s),
101
+ readSecret
102
+ });
103
+ }
104
+ };
105
+ }
106
+ async function runLogin(providerId, cwd, args, deps) {
107
+ if (args.key && args.logout) {
108
+ deps.err(t("Use either --key or --logout, not both."));
109
+ return 1;
110
+ }
111
+ if (args.logout) {
112
+ if (providerId !== "openrouter") {
113
+ deps.err(
114
+ t("Logout is not supported for {id}; use bccli provider remove {id} to delete its stored key and config.", {
115
+ id: providerId
116
+ })
117
+ );
118
+ return 1;
119
+ }
120
+ const { runOauthLogout } = await import("./oauthLogin-6FWQABJ7.js");
121
+ return runOauthLogout({ out: deps.out, err: deps.err, env: deps.env, removeCredential: deps.removeCredential });
122
+ }
123
+ if (args.key) return deps.legacyAdd(providerId, cwd);
124
+ if (providerId === "openrouter") {
125
+ const { runOauthLogin } = await import("./oauthLogin-6FWQABJ7.js");
126
+ return runOauthLogin({
127
+ out: deps.out,
128
+ err: deps.err,
129
+ env: deps.env,
130
+ isTTY: deps.isTTY,
131
+ open: deps.open,
132
+ readLine: deps.readLine,
133
+ saveCredential: deps.saveCredential,
134
+ fetch: deps.fetch
135
+ });
136
+ }
137
+ return deps.legacyAdd(providerId, cwd);
138
+ }
139
+
140
+ export {
141
+ readSecret,
142
+ readLine,
143
+ defaultLoginDeps,
144
+ runLogin
145
+ };
@@ -1,23 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigError
4
- } from "./chunk-2MGINW4O.js";
4
+ } from "./chunk-LACUWBWS.js";
5
+ import {
6
+ t
7
+ } from "./chunk-UZWJQF67.js";
5
8
 
6
9
  // src/mcp/catalog.ts
7
10
  var CATALOG = [
8
- { name: "playwright", description: "kontrol browser (buka halaman, klik, isi form, screenshot)", config: { command: "npx", args: ["-y", "@playwright/mcp@latest"] } },
9
- { name: "context7", description: "dokumentasi library terbaru", config: { type: "http", url: "https://mcp.context7.com/mcp" } },
10
- { name: "fetch", description: "ambil halaman web jadi markdown (butuh uv)", config: { command: "uvx", args: ["mcp-server-fetch"] } },
11
+ { name: "playwright", description: "control a browser (open pages, click, fill forms, screenshot)", config: { command: "npx", args: ["-y", "@playwright/mcp@latest"] } },
12
+ { name: "context7", description: "up-to-date library documentation", config: { type: "http", url: "https://mcp.context7.com/mcp" } },
13
+ { name: "fetch", description: "fetch web pages as markdown (needs uv)", config: { command: "uvx", args: ["mcp-server-fetch"] } },
11
14
  {
12
15
  name: "filesystem",
13
- description: "akses folder di luar project",
16
+ description: "access folders outside the project",
17
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: ${dir} is filled by fillTemplate, not a template literal
14
18
  config: { command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "${dir}"] },
15
- inputs: [{ key: "dir", label: "Folder yang boleh diakses" }]
19
+ inputs: [{ key: "dir", label: "Folder the server may access" }]
16
20
  },
17
- { name: "git", description: "operasi git terstruktur (butuh uv)", config: { command: "uvx", args: ["mcp-server-git"] } },
21
+ { name: "git", description: "structured git operations (needs uv)", config: { command: "uvx", args: ["mcp-server-git"] } },
18
22
  {
19
23
  name: "github",
20
- description: "issue, PR, dan repo GitHub",
24
+ description: "GitHub issues, PRs, and repos",
25
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: ${token} is filled by fillTemplate, not a template literal
21
26
  config: { type: "http", url: "https://api.githubcopilot.com/mcp/", headers: { Authorization: "Bearer ${token}" } },
22
27
  inputs: [{ key: "token", label: "GitHub Personal Access Token", secret: true }]
23
28
  }
@@ -35,7 +40,7 @@ function fillTemplate(config, values) {
35
40
  ...config.args ? { args: config.args.map(fill) } : {},
36
41
  ...config.env ? { env: mapValues(config.env) } : {}
37
42
  };
38
- if (missing.size) throw new ConfigError(`Isian MCP belum lengkap: ${[...missing].join(", ")}`);
43
+ if (missing.size) throw new ConfigError(t("Missing MCP fields: {fields}", { fields: [...missing].join(", ") }));
39
44
  return out;
40
45
  }
41
46
 
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ listAllModels
4
+ } from "./chunk-XBO5GACQ.js";
5
+ import {
6
+ hasKey,
7
+ providerName,
8
+ removeGlobalProvider,
9
+ writeGlobalConfig
10
+ } from "./chunk-SPRNLS7K.js";
11
+ import {
12
+ PRESETS,
13
+ loadConfig,
14
+ saveCredential
15
+ } from "./chunk-LACUWBWS.js";
16
+ import {
17
+ t
18
+ } from "./chunk-UZWJQF67.js";
19
+
20
+ // src/providerCli.ts
21
+ async function addProviderKey(providerId, key, deps) {
22
+ const config = loadConfig(deps.cwd, deps.env);
23
+ const [group] = await listAllModels(config, deps.env, { only: providerId, fetch: deps.fetch, keyOverride: key || void 0 });
24
+ if (!group || group.error) return { ok: false, error: t("The API key was rejected or {provider} could not be reached.", { provider: providerName(config, providerId) }) };
25
+ if (key) saveCredential(providerId, key, deps.env);
26
+ return { ok: true, models: group.models.length };
27
+ }
28
+ async function runProviderCommand(args, deps) {
29
+ const [action = "list", id] = args.subArgs;
30
+ const config = loadConfig(deps.cwd, deps.env);
31
+ if (action === "list") {
32
+ for (const pid of Object.keys(config.providers)) {
33
+ const mark = hasKey(config, pid, deps.env) ? "\u2713" : "\u25CB";
34
+ deps.out(`${mark} ${pid.padEnd(14)} ${providerName(config, pid).padEnd(20)} ${config.providers[pid].baseURL}`);
35
+ }
36
+ return 0;
37
+ }
38
+ if (!id) {
39
+ deps.err(t("Usage: bccli provider {action} <id>", { action }));
40
+ return 1;
41
+ }
42
+ if (action === "remove") {
43
+ removeGlobalProvider(id, deps.env);
44
+ deps.out(t("{id} removed.", { id }));
45
+ return 0;
46
+ }
47
+ if (action !== "add") {
48
+ deps.err(t("Unknown action: {action}. Use list, add, or remove.", { action }));
49
+ return 1;
50
+ }
51
+ const preset = PRESETS.find((p) => p.id === id);
52
+ if (!preset && !config.providers[id]) {
53
+ if (!args.url) {
54
+ deps.err(t("{id} is not a preset. For a custom provider add --url <OpenAI-compatible base URL>.", { id }));
55
+ return 1;
56
+ }
57
+ writeGlobalConfig(
58
+ { providers: { [id]: { baseURL: args.url, ...args.name ? { name: args.name } : {}, ...args.keyEnv ? { apiKeyEnv: args.keyEnv } : {} } } },
59
+ deps.env
60
+ );
61
+ }
62
+ const needsKey = !!loadConfig(deps.cwd, deps.env).providers[id].apiKeyEnv || !preset;
63
+ const key = needsKey ? await deps.readSecret(t("API key for {id} (leave empty if not needed): ", { id })) : "";
64
+ const result = await addProviderKey(id, key, deps);
65
+ if (!result.ok) {
66
+ deps.err(result.error);
67
+ return 1;
68
+ }
69
+ deps.out(t("{id} is ready. {n} models available.", { id, n: result.models }));
70
+ return 0;
71
+ }
72
+
73
+ export {
74
+ addProviderKey,
75
+ runProviderCommand
76
+ };
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ MODE_ORDER
4
+ } from "./chunk-O7M3TGIW.js";
5
+ import {
6
+ REASONING_LEVELS,
7
+ isReasoningLevel
8
+ } from "./chunk-BLOYFIAD.js";
9
+ import {
10
+ ConfigError
11
+ } from "./chunk-LACUWBWS.js";
12
+ import {
13
+ parseLang,
14
+ t
15
+ } from "./chunk-UZWJQF67.js";
16
+
17
+ // src/args.ts
18
+ import { parseArgs } from "util";
19
+
20
+ // src/serveProtocol.ts
21
+ import { randomBytes, timingSafeEqual } from "crypto";
22
+ var MAX_FRAME_BYTES = 262144;
23
+ function parseClientFrame(raw) {
24
+ let value;
25
+ try {
26
+ value = JSON.parse(raw);
27
+ } catch {
28
+ return { error: "invalid JSON frame" };
29
+ }
30
+ const f = value;
31
+ if (typeof f?.streamId !== "string" || f.streamId === "") return { error: "frame requires a streamId" };
32
+ if (f.type === "open") {
33
+ if (typeof f.target !== "string" || f.target === "") return { error: "open requires a target" };
34
+ return { type: "open", streamId: f.streamId, target: f.target };
35
+ }
36
+ if (f.type === "cancel") return { type: "cancel", streamId: f.streamId };
37
+ return { error: `unknown frame type: ${String(f?.type)}` };
38
+ }
39
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]", "::1"]);
40
+ var LOOPBACK_BIND = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
41
+ function trustFence(headers, port) {
42
+ const host = headers.host;
43
+ if (!host) return "missing Host header";
44
+ const sep = host.lastIndexOf(":");
45
+ const hostname = sep > -1 ? host.slice(0, sep) : host;
46
+ const hostPort = sep > -1 ? Number(host.slice(sep + 1)) : 80;
47
+ if (!LOOPBACK_HOSTS.has(hostname)) return `non-loopback Host: ${hostname}`;
48
+ if (hostPort !== port) return `Host port ${hostPort} does not match listener ${port}`;
49
+ if (headers.origin) {
50
+ let origin;
51
+ try {
52
+ origin = new URL(headers.origin);
53
+ } catch {
54
+ return "malformed Origin header";
55
+ }
56
+ const originHost = origin.hostname === "::1" ? "[::1]" : origin.hostname;
57
+ const originPort = origin.port ? Number(origin.port) : origin.protocol === "https:" ? 443 : 80;
58
+ if (!LOOPBACK_HOSTS.has(originHost)) return `cross-origin Origin: ${headers.origin}`;
59
+ if (originPort !== port) return "Origin port mismatch";
60
+ }
61
+ if (headers["sec-fetch-site"] === "cross-site") return "cross-site request";
62
+ return null;
63
+ }
64
+ function bearerOk(header, token) {
65
+ if (!header?.startsWith("Bearer ")) return false;
66
+ const got = Buffer.from(header.slice(7));
67
+ const want = Buffer.from(token);
68
+ return got.length === want.length && timingSafeEqual(got, want);
69
+ }
70
+ function randomToken() {
71
+ return randomBytes(32).toString("hex");
72
+ }
73
+
74
+ // src/args.ts
75
+ var HELP = `BCCLI \u2014 BotConnector's AI coding agent for the terminal
76
+
77
+ Usage:
78
+ bccli [task] interactive mode (optionally start with a task)
79
+ bccli -p "task" run one task without interaction (scripts/CI)
80
+ bccli login [provider] OAuth login (or --key paste API key, --logout remove)
81
+ bccli models [provider] list models (default: the active provider)
82
+ bccli update install the newest bccli from npm
83
+ bccli acp run as an ACP agent for editors (Zed, ...)
84
+ bccli serve [--port N] serve sessions over HTTP+WS for IDEs/bots
85
+ bccli plugins list, install, remove, or update plugins
86
+ bccli gh-bot [serve] GitHub comment bot (off-by-default; needs config)
87
+ bccli provider list|add <id>|remove <id> manage providers (custom: --url <url> [--name N] [--key-env ENV])
88
+ bccli mcp list|add <name>|remove <name>|auth <name>|logout <name>|serve [--base-url <url>] manage MCP servers, or serve this account's tools over stdio
89
+ bccli completion <shell> print a shell completion script (bash, zsh, fish, powershell)
90
+ bccli stats [--days N] [--tools N] [--models [N|all]] [--project slug]
91
+ show token usage from local sessions (default: last 7 days)
92
+ bccli export [session] [file] copy a session JSONL to a file (default: last session here)
93
+ bccli import <file> import a session JSONL into the current project
94
+ bccli pr <number> [--repo owner/name] [task] check out a GitHub PR and run a task on it
95
+ bccli integrations show the status of external agent integrations
96
+ bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible, openclaw, hermes
97
+ bccli disconnect <agent> remove an integration and restore the previous config
98
+
99
+ Options:
100
+ -m, --model <provider/model> choose a model, e.g. bc-cloud/glm-5.3-flash
101
+ --reasoning <level> auto | off | low | medium | high | max
102
+ -c, --continue continue the last session in this folder
103
+ -r, --resume pick a session to continue
104
+ --allow-all run all tools without asking for permission
105
+ --allowed-tools <a,b> tools allowed without asking: bash, edit, fetch
106
+ -w, --worktree <name> run in git worktree ../<repo>.worktrees/<name> (created, or reused if it exists)
107
+ --output-format <fmt> text | json | stream-json for -p (machine-readable output)
108
+ --permission-mode <mode> default | acceptEdits | plan | allowAll
109
+ --lang <en|id> interface language (default: en; or BCCLI_LANG, or "language" in config)
110
+ -v, --version version
111
+ -h, --help this help`;
112
+ var helpText = () => t(HELP);
113
+ function parseCliArgs(argv) {
114
+ const expanded = argv.map(
115
+ (a, i) => a === "--models" && (i + 1 >= argv.length || argv[i + 1].startsWith("-")) ? "--models=all" : a
116
+ );
117
+ const { values, positionals } = parseArgs({
118
+ args: expanded,
119
+ allowPositionals: true,
120
+ options: {
121
+ print: { type: "boolean", short: "p" },
122
+ model: { type: "string", short: "m" },
123
+ reasoning: { type: "string" },
124
+ lang: { type: "string" },
125
+ continue: { type: "boolean", short: "c" },
126
+ resume: { type: "boolean", short: "r" },
127
+ "allow-all": { type: "boolean" },
128
+ "allowed-tools": { type: "string" },
129
+ worktree: { type: "string", short: "w" },
130
+ "output-format": { type: "string" },
131
+ "permission-mode": { type: "string" },
132
+ help: { type: "boolean", short: "h" },
133
+ version: { type: "boolean", short: "v" },
134
+ url: { type: "string" },
135
+ name: { type: "string" },
136
+ "key-env": { type: "string" },
137
+ value: { type: "string", multiple: true },
138
+ key: { type: "boolean" },
139
+ logout: { type: "boolean" },
140
+ port: { type: "string" },
141
+ host: { type: "string" },
142
+ token: { type: "string" },
143
+ "base-url": { type: "string" },
144
+ days: { type: "string" },
145
+ tools: { type: "string" },
146
+ models: { type: "string" },
147
+ project: { type: "string" },
148
+ repo: { type: "string" }
149
+ }
150
+ });
151
+ const reasoning = values.reasoning;
152
+ if (reasoning && !isReasoningLevel(reasoning)) {
153
+ throw new ConfigError(t("--reasoning must be one of: {levels}", { levels: REASONING_LEVELS.join(", ") }));
154
+ }
155
+ const mode = values["permission-mode"];
156
+ if (mode && !MODE_ORDER.includes(mode)) {
157
+ throw new ConfigError(t("--permission-mode must be one of: {modes}", { modes: MODE_ORDER.join(", ") }));
158
+ }
159
+ if (values.lang !== void 0 && !parseLang(values.lang)) throw new ConfigError(t("--lang must be one of: en, id"));
160
+ const outputFormat = values["output-format"];
161
+ if (outputFormat !== void 0 && !["text", "json", "stream-json"].includes(outputFormat)) {
162
+ throw new ConfigError(t("--output-format must be one of: {formats}", { formats: "text, json, stream-json" }));
163
+ }
164
+ const [first, ...rest] = positionals;
165
+ const SUBCOMMANDS = ["login", "models", "update", "acp", "serve", "plugins", "gh-bot", "provider", "mcp", "connect", "disconnect", "integrations", "completion", "stats", "export", "import", "pr"];
166
+ const command = SUBCOMMANDS.includes(first) ? first : "run";
167
+ const words = command === "run" ? positionals : rest;
168
+ const port = values.port !== void 0 ? Number(values.port) : void 0;
169
+ if (port !== void 0 && (!Number.isInteger(port) || port < 0 || port > 65535)) {
170
+ throw new ConfigError(t("--port must be a number between 0 and 65535"));
171
+ }
172
+ const days = values.days !== void 0 ? Number(values.days) : void 0;
173
+ if (days !== void 0 && (!Number.isInteger(days) || days < 1)) {
174
+ throw new ConfigError(t("--days must be a positive integer"));
175
+ }
176
+ const tools = values.tools !== void 0 ? Number(values.tools) : void 0;
177
+ if (tools !== void 0 && (!Number.isInteger(tools) || tools < 1)) {
178
+ throw new ConfigError(t("--tools must be a positive integer"));
179
+ }
180
+ let models;
181
+ if (values.models !== void 0) {
182
+ models = values.models === "" || values.models === "all" ? "all" : values.models;
183
+ if (models !== "all" && (!Number.isInteger(Number(models)) || Number(models) < 1)) {
184
+ throw new ConfigError(t('--models must be a positive integer or "all"'));
185
+ }
186
+ }
187
+ if (values.host !== void 0 && !LOOPBACK_BIND.has(values.host)) {
188
+ throw new ConfigError(t("serve only accepts loopback --host (127.0.0.1, localhost, ::1)"));
189
+ }
190
+ return {
191
+ command,
192
+ prompt: command === "run" && words.length ? words.join(" ") : void 0,
193
+ print: !!values.print,
194
+ model: values.model,
195
+ reasoning,
196
+ lang: parseLang(values.lang),
197
+ continue: !!values.continue,
198
+ resume: !!values.resume,
199
+ allowAll: !!values["allow-all"],
200
+ allowedTools: (values["allowed-tools"] ?? "").split(",").map((s) => s.trim()).filter(Boolean),
201
+ worktree: values.worktree,
202
+ outputFormat,
203
+ permissionMode: mode,
204
+ help: !!values.help,
205
+ version: !!values.version,
206
+ loginProvider: command === "login" ? rest[0] ?? "bc-cloud" : void 0,
207
+ key: !!values.key,
208
+ logout: !!values.logout,
209
+ subArgs: ["models", "plugins", "gh-bot", "provider", "mcp", "connect", "disconnect", "integrations", "completion", "stats", "export", "import", "pr"].includes(command) ? rest : [],
210
+ url: values.url,
211
+ name: values.name,
212
+ keyEnv: values["key-env"],
213
+ values: values.value ?? [],
214
+ port,
215
+ host: values.host,
216
+ token: values.token,
217
+ baseUrl: values["base-url"],
218
+ days,
219
+ tools,
220
+ models,
221
+ project: values.project,
222
+ repo: values.repo
223
+ };
224
+ }
225
+
226
+ export {
227
+ MAX_FRAME_BYTES,
228
+ parseClientFrame,
229
+ LOOPBACK_BIND,
230
+ trustFence,
231
+ bearerOk,
232
+ randomToken,
233
+ helpText,
234
+ parseCliArgs
235
+ };
@@ -1,12 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigError
4
- } from "./chunk-2MGINW4O.js";
4
+ } from "./chunk-LACUWBWS.js";
5
+ import {
6
+ t
7
+ } from "./chunk-UZWJQF67.js";
5
8
 
6
9
  // src/mcp/config.ts
7
10
  import { createHash } from "crypto";
8
11
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
9
12
  import { dirname, join, resolve } from "path";
13
+ var ENV_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
14
+ function expandEnvVars(config, env) {
15
+ const expand = (value) => value.replace(ENV_REF, (match, name, fallback) => {
16
+ const resolved = env[name];
17
+ if (resolved !== void 0) return resolved;
18
+ return fallback !== void 0 ? fallback : match;
19
+ });
20
+ const expandMap = (map) => Object.fromEntries(Object.entries(map).map(([k, v]) => [k, expand(v)]));
21
+ if (config.type === "http") {
22
+ return { ...config, url: expand(config.url), ...config.headers ? { headers: expandMap(config.headers) } : {} };
23
+ }
24
+ return {
25
+ ...config,
26
+ command: expand(config.command),
27
+ ...config.args ? { args: config.args.map(expand) } : {},
28
+ ...config.env ? { env: expandMap(config.env) } : {}
29
+ };
30
+ }
10
31
  var globalMcpPath = (home) => join(home, "mcp.json");
11
32
  var projectMcpPath = (cwd) => join(cwd, ".bccli", "mcp.json");
12
33
  var trustPath = (home) => join(home, "trusted-mcp.json");
@@ -15,7 +36,7 @@ function readJson(path, fallback) {
15
36
  try {
16
37
  return JSON.parse(readFileSync(path, "utf8"));
17
38
  } catch (error) {
18
- throw new ConfigError(`File MCP tidak valid: ${path} (${error.message})`);
39
+ throw new ConfigError(t("Invalid MCP file: {path} ({error})", { path, error: error.message }));
19
40
  }
20
41
  }
21
42
  function writePrivate(path, data) {
@@ -58,6 +79,7 @@ function serversToStart(home, cwd) {
58
79
  }
59
80
 
60
81
  export {
82
+ expandEnvVars,
61
83
  globalMcpPath,
62
84
  projectMcpPath,
63
85
  readMcpFile,