@botconnector/bccli 0.4.0-beta.8 → 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.
- package/README.en.md +242 -0
- package/README.md +95 -3
- package/dist/acp-ATIL4JOZ.js +182 -0
- package/dist/args-J5ONVGIF.js +13 -0
- package/dist/chunk-45SDLMTV.js +341 -0
- package/dist/chunk-6M5X2KZE.js +8 -0
- package/dist/chunk-7F3IWBPQ.js +191 -0
- package/dist/{chunk-CLN4Z5E3.js → chunk-BLOYFIAD.js} +6 -3
- package/dist/chunk-BNA74HPN.js +145 -0
- package/dist/{chunk-DX7CZTBB.js → chunk-BQ2IGPBR.js} +14 -9
- package/dist/chunk-JL5ZCSP2.js +76 -0
- package/dist/chunk-KEMYIGCP.js +235 -0
- package/dist/{chunk-BFZXRKW5.js → chunk-L7QI6JN2.js} +24 -2
- package/dist/chunk-LACUWBWS.js +361 -0
- package/dist/chunk-M7CALSP3.js +733 -0
- package/dist/{chunk-ELML4KVQ.js → chunk-O7M3TGIW.js} +34 -2
- package/dist/chunk-PSO7R5AD.js +10 -0
- package/dist/chunk-PUN4HMAG.js +940 -0
- package/dist/{chunk-SJH3YLW4.js → chunk-QDQU6VY2.js} +60 -6
- package/dist/chunk-QZKLZELZ.js +64 -0
- package/dist/{chunk-7S6JXQCH.js → chunk-SPRNLS7K.js} +2 -1
- package/dist/chunk-TLPFQIS2.js +231 -0
- package/dist/chunk-TVALKHFB.js +2143 -0
- package/dist/chunk-UZWJQF67.js +500 -0
- package/dist/chunk-XBO5GACQ.js +56 -0
- package/dist/cli.js +162 -1383
- package/dist/completion-HPPFDAPB.js +125 -0
- package/dist/{config-YUNKPCNB.js → config-COPNMMV6.js} +5 -2
- package/dist/config-RZ5JONMX.js +30 -0
- package/dist/ghBotCli-IBCZIWT5.js +677 -0
- package/dist/integrationCli-P75R2VSC.js +685 -0
- package/dist/login-SYIQP46J.js +16 -0
- package/dist/{mcpCli-DZWQPTOS.js → mcpCli-QUV5SD64.js} +42 -9
- package/dist/modelsCli-TRRB5LX4.js +36 -0
- package/dist/npmRunner-LU43GM3W.js +46 -0
- package/dist/oauthLogin-6FWQABJ7.js +210 -0
- package/dist/pluginsCli-GNW4TPVV.js +284 -0
- package/dist/prCli-5IMV3VNQ.js +78 -0
- package/dist/providerCli-FAOZHOKW.js +15 -0
- package/dist/sdk.js +5490 -0
- package/dist/serve-6QLBJUYW.js +178 -0
- package/dist/serve-JG7RLIUD.js +515 -0
- package/dist/session-KBAQ4BZ5.js +12 -0
- package/dist/setup-FJAWDWV4.js +24 -0
- package/dist/statsCli-CF3R7ATL.js +353 -0
- package/dist/transferCli-TK2GSVRK.js +71 -0
- package/dist/ui-B3CJ6TGE.js +2365 -0
- package/dist/update-K5IQ2MYR.js +145 -0
- package/package.json +25 -4
- package/dist/args-FLTRM57K.js +0 -12
- package/dist/chunk-2MGINW4O.js +0 -114
- package/dist/chunk-IM3KMPVZ.js +0 -52
- package/dist/chunk-KV27KTOX.js +0 -254
- package/dist/chunk-U7GK73CK.js +0 -341
- package/dist/chunk-Z7PM3LTC.js +0 -93
- package/dist/login-JXFORO3J.js +0 -9
- package/dist/providerCli-Q7LRM2NP.js +0 -12
- package/dist/session-7UB77CEG.js +0 -9
- 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-
|
|
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: "
|
|
9
|
-
{ name: "context7", description: "
|
|
10
|
-
{ name: "fetch", description: "
|
|
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: "
|
|
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
|
|
19
|
+
inputs: [{ key: "dir", label: "Folder the server may access" }]
|
|
16
20
|
},
|
|
17
|
-
{ name: "git", description: "
|
|
21
|
+
{ name: "git", description: "structured git operations (needs uv)", config: { command: "uvx", args: ["mcp-server-git"] } },
|
|
18
22
|
{
|
|
19
23
|
name: "github",
|
|
20
|
-
description: "
|
|
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(
|
|
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-
|
|
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(
|
|
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,
|