@botconnector/bccli 0.5.0-rc.4 → 0.5.1

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 (187) hide show
  1. package/LICENSE +21 -203
  2. package/README.en.md +253 -0
  3. package/README.md +250 -0
  4. package/dist/abort.d.ts +5 -0
  5. package/dist/acp-HWHF25KU.js +221 -0
  6. package/dist/acp.d.ts +44 -0
  7. package/dist/agent.d.ts +158 -0
  8. package/dist/agents.d.ts +4 -0
  9. package/dist/args-XA77XUMD.js +13 -0
  10. package/dist/args.d.ts +59 -0
  11. package/dist/budget.d.ts +26 -0
  12. package/dist/checkpoints.d.ts +35 -0
  13. package/dist/chunk-2IP5F3EK.js +56 -0
  14. package/dist/chunk-5CVHTYBQ.js +270 -0
  15. package/dist/chunk-6LO6YRDY.js +216 -0
  16. package/dist/chunk-6PJRWKVI.js +50 -0
  17. package/dist/chunk-6YRTPXGY.js +8 -0
  18. package/dist/chunk-7OTFZK4B.js +159 -0
  19. package/dist/chunk-7U7CMKKI.js +861 -0
  20. package/dist/chunk-A3NBWIEQ.js +2706 -0
  21. package/dist/chunk-FY543EQW.js +50 -0
  22. package/dist/chunk-IDZLHEVL.js +524 -0
  23. package/dist/chunk-IF5RNXBI.js +341 -0
  24. package/dist/chunk-M5T4AM4I.js +256 -0
  25. package/dist/chunk-NGFWBVHD.js +71 -0
  26. package/dist/chunk-OCEQ2TRJ.js +1075 -0
  27. package/dist/chunk-OK2USAXX.js +88 -0
  28. package/dist/chunk-PSO7R5AD.js +10 -0
  29. package/dist/chunk-RLSQAZBT.js +78 -0
  30. package/dist/chunk-VPFCIUC6.js +91 -0
  31. package/dist/chunk-YGBTNDPK.js +202 -0
  32. package/dist/chunk-YYTFJUO2.js +148 -0
  33. package/dist/chunk-ZXWRLN7E.js +378 -0
  34. package/dist/cli.d.ts +1 -0
  35. package/dist/cli.js +251 -0
  36. package/dist/clipboardImage.d.ts +17 -0
  37. package/dist/commands.d.ts +16 -0
  38. package/dist/completion-IRVEVW6H.js +125 -0
  39. package/dist/completion.d.ts +14 -0
  40. package/dist/config-OVWCUVY7.js +25 -0
  41. package/dist/config-S3CZUIBB.js +34 -0
  42. package/dist/config.d.ts +99 -0
  43. package/dist/context.d.ts +13 -0
  44. package/dist/contextWindow.d.ts +3 -0
  45. package/dist/diff.d.ts +7 -0
  46. package/dist/exit.d.ts +9 -0
  47. package/dist/extensions.d.ts +34 -0
  48. package/dist/ghBotCli-FYKJDJOD.js +680 -0
  49. package/dist/ghBotCli.d.ts +9 -0
  50. package/dist/ghBotDispatch.d.ts +68 -0
  51. package/dist/ghBotRunner.d.ts +82 -0
  52. package/dist/ghBotServe.d.ts +21 -0
  53. package/dist/ghBotWebhook.d.ts +45 -0
  54. package/dist/hooks.d.ts +27 -0
  55. package/dist/i18n/id.d.ts +1 -0
  56. package/dist/i18n.d.ts +16 -0
  57. package/dist/integrationCli-GY73S2K7.js +876 -0
  58. package/dist/integrationCli.d.ts +9 -0
  59. package/dist/keybinds.d.ts +22 -0
  60. package/dist/login-KSN63EM4.js +18 -0
  61. package/dist/login.d.ts +21 -0
  62. package/dist/lsp.d.ts +73 -0
  63. package/dist/mcp/catalog.d.ts +14 -0
  64. package/dist/mcp/config.d.ts +36 -0
  65. package/dist/mcp/manager.d.ts +52 -0
  66. package/dist/mcp/oauth.d.ts +82 -0
  67. package/dist/mcpCli-VWSCCDWT.js +113 -0
  68. package/dist/mcpCli.d.ts +3 -0
  69. package/dist/mcpServer/serve.d.ts +8 -0
  70. package/dist/mcpServer/server.d.ts +19 -0
  71. package/dist/models.d.ts +13 -0
  72. package/dist/modelsCli-4Q6KFWV2.js +36 -0
  73. package/dist/modelsCli.d.ts +15 -0
  74. package/dist/npmRunner-LU43GM3W.js +46 -0
  75. package/dist/npmRunner.d.ts +21 -0
  76. package/dist/oauthLogin-JWXQZTMZ.js +210 -0
  77. package/dist/oauthLogin.d.ts +47 -0
  78. package/dist/onboarding.d.ts +6 -0
  79. package/dist/permissions.d.ts +30 -0
  80. package/dist/pluginSpec.d.ts +25 -0
  81. package/dist/plugins.d.ts +45 -0
  82. package/dist/pluginsCli-7XWDLJEL.js +288 -0
  83. package/dist/pluginsCli.d.ts +16 -0
  84. package/dist/prCli-FH7N26D4.js +94 -0
  85. package/dist/prCli.d.ts +46 -0
  86. package/dist/presets.d.ts +8 -0
  87. package/dist/print.d.ts +17 -0
  88. package/dist/provider.d.ts +145 -0
  89. package/dist/providerCli-YMG4G56R.js +15 -0
  90. package/dist/providerCli.d.ts +17 -0
  91. package/dist/providers.d.ts +6 -0
  92. package/dist/reasoning.d.ts +12 -0
  93. package/dist/repomap.d.ts +10 -0
  94. package/dist/sdk.d.ts +61 -0
  95. package/dist/sdk.js +6532 -0
  96. package/dist/serve-HSFF6MGB.js +178 -0
  97. package/dist/serve-R2IHP26A.js +532 -0
  98. package/dist/serve.d.ts +37 -0
  99. package/dist/serveHeartbeat.d.ts +14 -0
  100. package/dist/serveProtocol.d.ts +47 -0
  101. package/dist/session-YM36TGU7.js +12 -0
  102. package/dist/session.d.ts +33 -0
  103. package/dist/setup-3YB6EKYX.js +24 -0
  104. package/dist/setup.d.ts +79 -0
  105. package/dist/slash/copy.d.ts +1 -0
  106. package/dist/slash/diff.d.ts +1 -0
  107. package/dist/slash/doctor.d.ts +18 -0
  108. package/dist/slash/export.d.ts +2 -0
  109. package/dist/slash/info.d.ts +46 -0
  110. package/dist/slash/memory.d.ts +10 -0
  111. package/dist/statsCli-I4TSDU3X.js +353 -0
  112. package/dist/statsCli.d.ts +62 -0
  113. package/dist/textToolCalls.d.ts +6 -0
  114. package/dist/thinking.d.ts +17 -0
  115. package/dist/tokenBudget.d.ts +22 -0
  116. package/dist/tools/bash.d.ts +54 -0
  117. package/dist/tools/diagnostics.d.ts +13 -0
  118. package/dist/tools/edit.d.ts +12 -0
  119. package/dist/tools/fetch.d.ts +41 -0
  120. package/dist/tools/glob.d.ts +5 -0
  121. package/dist/tools/globSafety.d.ts +7 -0
  122. package/dist/tools/grep.d.ts +25 -0
  123. package/dist/tools/ignore.d.ts +2 -0
  124. package/dist/tools/index.d.ts +12 -0
  125. package/dist/tools/memory.d.ts +13 -0
  126. package/dist/tools/paths.d.ts +4 -0
  127. package/dist/tools/plan.d.ts +10 -0
  128. package/dist/tools/read.d.ts +16 -0
  129. package/dist/tools/repomap.d.ts +2 -0
  130. package/dist/tools/sandbox.d.ts +15 -0
  131. package/dist/tools/shell.d.ts +18 -0
  132. package/dist/tools/skill.d.ts +3 -0
  133. package/dist/tools/task.d.ts +28 -0
  134. package/dist/tools/todo.d.ts +13 -0
  135. package/dist/tools/types.d.ts +52 -0
  136. package/dist/tools/websearch.d.ts +9 -0
  137. package/dist/tools/write.d.ts +5 -0
  138. package/dist/transferCli-3B4LMOYI.js +73 -0
  139. package/dist/transferCli.d.ts +23 -0
  140. package/dist/ui/App.d.ts +7 -0
  141. package/dist/ui/DiffView.d.ts +3 -0
  142. package/dist/ui/EditableText.d.ts +7 -0
  143. package/dist/ui/LinePrompt.d.ts +6 -0
  144. package/dist/ui/ListPicker.d.ts +11 -0
  145. package/dist/ui/Markdown.d.ts +8 -0
  146. package/dist/ui/McpMenu.d.ts +11 -0
  147. package/dist/ui/ModelPicker.d.ts +6 -0
  148. package/dist/ui/PermissionPrompt.d.ts +5 -0
  149. package/dist/ui/PlanApproval.d.ts +5 -0
  150. package/dist/ui/PromptInput.d.ts +20 -0
  151. package/dist/ui/ProviderMenu.d.ts +10 -0
  152. package/dist/ui/Spinner.d.ts +4 -0
  153. package/dist/ui/StatusBar.d.ts +14 -0
  154. package/dist/ui/TodoList.d.ts +4 -0
  155. package/dist/ui/ToolBlock.d.ts +14 -0
  156. package/dist/ui/complete.d.ts +8 -0
  157. package/dist/ui/index.d.ts +7 -0
  158. package/dist/ui/lineEdit.d.ts +42 -0
  159. package/dist/ui/liveCap.d.ts +29 -0
  160. package/dist/ui/markdownLayout.d.ts +23 -0
  161. package/dist/ui/replay.d.ts +4 -0
  162. package/dist/ui/stdinFilter.d.ts +18 -0
  163. package/dist/ui/table.d.ts +20 -0
  164. package/dist/ui/theme.d.ts +5 -0
  165. package/dist/ui/thinkingMouse.d.ts +38 -0
  166. package/dist/ui/transcript.d.ts +55 -0
  167. package/dist/ui/tty.d.ts +11 -0
  168. package/dist/ui/vim.d.ts +26 -0
  169. package/dist/ui-TAZMRXRI.js +3044 -0
  170. package/dist/update-XC3B6SJC.js +145 -0
  171. package/dist/update.d.ts +30 -0
  172. package/dist/verify.d.ts +16 -0
  173. package/dist/version.d.ts +1 -0
  174. package/dist/webBudget.d.ts +25 -0
  175. package/dist/worktree.d.ts +12 -0
  176. package/package.json +59 -35
  177. package/THIRD_PARTY_NOTICES.md +0 -9
  178. package/branding.mjs +0 -347
  179. package/cli.mjs +0 -120
  180. package/core.mjs +0 -260
  181. package/credentials.mjs +0 -52
  182. package/gateway.mjs +0 -2
  183. package/model-command.mjs +0 -61
  184. package/provider-cli.mjs +0 -77
  185. package/providers.mjs +0 -166
  186. package/proxy.mjs +0 -242
  187. package/startup-menu.mjs +0 -120
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/tokenBudget.ts
4
+ var IMAGE_TOKEN_COST = 1e3;
5
+ var estimateTokens = (text) => Math.ceil(text.length / 4);
6
+ function isImageAttachmentMessage(m) {
7
+ if (m.role !== "user" || !Array.isArray(m.content)) return false;
8
+ const hasImage = m.content.some((p) => p.type === "image_url");
9
+ const firstText = m.content.find((p) => p.type === "text");
10
+ return hasImage && !!firstText && firstText.type === "text" && firstText.text.startsWith("Image read from ");
11
+ }
12
+ function isUserTurnBoundary(m) {
13
+ return m.role === "user" && !isImageAttachmentMessage(m);
14
+ }
15
+ function estimateMessages(messages) {
16
+ let total = 0;
17
+ for (const m of messages) {
18
+ if (m.role === "user" && Array.isArray(m.content)) {
19
+ for (const part of m.content) {
20
+ if (part.type === "image_url") total += IMAGE_TOKEN_COST;
21
+ else total += estimateTokens(part.text);
22
+ }
23
+ total += 8;
24
+ } else {
25
+ total += estimateTokens(JSON.stringify(m));
26
+ }
27
+ }
28
+ return total;
29
+ }
30
+ function trimToFit(messages, maxTokens) {
31
+ let out = messages;
32
+ while (estimateMessages(out) > maxTokens) {
33
+ const boundary = out.findIndex((m, i) => i > 0 && isUserTurnBoundary(m));
34
+ if (boundary === -1) return out;
35
+ out = out.slice(boundary);
36
+ }
37
+ return out;
38
+ }
39
+ function splitKeepRecent(messages, keepTokens) {
40
+ if (messages.length === 0) return [[], []];
41
+ let start = messages.length;
42
+ for (let i = messages.length - 1; i >= 0; i--) {
43
+ if (i > 0 && !isUserTurnBoundary(messages[i])) continue;
44
+ if (estimateMessages(messages.slice(i)) <= keepTokens) start = i;
45
+ else break;
46
+ }
47
+ if (start === messages.length) {
48
+ for (let i = messages.length - 1; i >= 0; i--) {
49
+ if (isUserTurnBoundary(messages[i])) {
50
+ start = i;
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ return [messages.slice(0, start), messages.slice(start)];
56
+ }
57
+ var TOOL_STUB = "[Old tool result content cleared]";
58
+ var TOOL_PRUNE_KEEP_TOKENS = 4e4;
59
+ var TOOL_PRUNE_MIN_TOKENS = 2e4;
60
+ var isToolStub = (content) => content.startsWith(TOOL_STUB);
61
+ function pruneOldToolOutputs(messages) {
62
+ let kept = 0;
63
+ let oldTokens = 0;
64
+ const old = [];
65
+ for (let i = messages.length - 1; i >= 0; i--) {
66
+ const m = messages[i];
67
+ if (m.role !== "tool" || isToolStub(m.content)) continue;
68
+ kept += estimateTokens(m.content);
69
+ if (kept > TOOL_PRUNE_KEEP_TOKENS) {
70
+ old.push(i);
71
+ oldTokens += estimateTokens(m.content);
72
+ }
73
+ }
74
+ if (oldTokens <= TOOL_PRUNE_MIN_TOKENS) return false;
75
+ for (const i of old) {
76
+ const m = messages[i];
77
+ messages[i] = { ...m, content: TOOL_STUB };
78
+ }
79
+ return true;
80
+ }
81
+
82
+ export {
83
+ estimateTokens,
84
+ estimateMessages,
85
+ trimToFit,
86
+ splitKeepRecent,
87
+ pruneOldToolOutputs
88
+ };
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/tools/types.ts
4
+ function defineTool(tool) {
5
+ return tool;
6
+ }
7
+
8
+ export {
9
+ defineTool
10
+ };
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ listAllModels
4
+ } from "./chunk-2IP5F3EK.js";
5
+ import {
6
+ hasKey,
7
+ providerName,
8
+ removeGlobalProvider,
9
+ writeGlobalConfig
10
+ } from "./chunk-FY543EQW.js";
11
+ import {
12
+ PRESETS,
13
+ loadConfig,
14
+ saveCredential
15
+ } from "./chunk-ZXWRLN7E.js";
16
+ import {
17
+ t
18
+ } from "./chunk-IDZLHEVL.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
+ const createdEntry = !preset && !config.providers[id];
53
+ if (createdEntry) {
54
+ if (!args.url) {
55
+ deps.err(t("{id} is not a preset. For a custom provider add --url <OpenAI-compatible base URL>.", { id }));
56
+ return 1;
57
+ }
58
+ writeGlobalConfig(
59
+ { providers: { [id]: { baseURL: args.url, ...args.name ? { name: args.name } : {}, ...args.keyEnv ? { apiKeyEnv: args.keyEnv } : {} } } },
60
+ deps.env
61
+ );
62
+ }
63
+ const needsKey = !!loadConfig(deps.cwd, deps.env).providers[id].apiKeyEnv || !preset;
64
+ const key = needsKey ? await deps.readSecret(t("API key for {id} (leave empty if not needed): ", { id })) : "";
65
+ const result = await addProviderKey(id, key, deps);
66
+ if (!result.ok) {
67
+ if (createdEntry) removeGlobalProvider(id, deps.env);
68
+ deps.err(result.error);
69
+ return 1;
70
+ }
71
+ deps.out(t("{id} is ready. {n} models available.", { id, n: result.models }));
72
+ return 0;
73
+ }
74
+
75
+ export {
76
+ addProviderKey,
77
+ runProviderCommand
78
+ };
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ConfigError,
4
+ writeFileAtomic
5
+ } from "./chunk-ZXWRLN7E.js";
6
+ import {
7
+ t
8
+ } from "./chunk-IDZLHEVL.js";
9
+
10
+ // src/mcp/config.ts
11
+ import { createHash } from "crypto";
12
+ import { existsSync, mkdirSync, readFileSync } from "fs";
13
+ import { dirname, join, resolve } from "path";
14
+ var ENV_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
15
+ function expandEnvVars(config, env) {
16
+ const expand = (value) => value.replace(ENV_REF, (match, name, fallback) => {
17
+ const resolved = env[name];
18
+ if (resolved !== void 0) return resolved;
19
+ return fallback !== void 0 ? fallback : match;
20
+ });
21
+ const expandMap = (map) => Object.fromEntries(Object.entries(map).map(([k, v]) => [k, expand(v)]));
22
+ if (config.type === "http") {
23
+ return { ...config, url: expand(config.url), ...config.headers ? { headers: expandMap(config.headers) } : {} };
24
+ }
25
+ return {
26
+ ...config,
27
+ command: expand(config.command),
28
+ ...config.args ? { args: config.args.map(expand) } : {},
29
+ ...config.env ? { env: expandMap(config.env) } : {}
30
+ };
31
+ }
32
+ var globalMcpPath = (home) => join(home, "mcp.json");
33
+ var projectMcpPath = (cwd) => join(cwd, ".bccli", "mcp.json");
34
+ var trustPath = (home) => join(home, "trusted-mcp.json");
35
+ function readJson(path, fallback) {
36
+ if (!existsSync(path)) return fallback;
37
+ try {
38
+ return JSON.parse(readFileSync(path, "utf8"));
39
+ } catch (error) {
40
+ throw new ConfigError(t("Invalid MCP file: {path} ({error})", { path, error: error.message }));
41
+ }
42
+ }
43
+ function writePrivate(path, data) {
44
+ mkdirSync(dirname(path), { recursive: true });
45
+ writeFileAtomic(path, `${JSON.stringify(data, null, 2)}
46
+ `, 384);
47
+ }
48
+ function readMcpFile(path) {
49
+ return readJson(path, {}).mcpServers ?? {};
50
+ }
51
+ function addGlobalServer(home, name, config) {
52
+ writePrivate(globalMcpPath(home), { mcpServers: { ...readMcpFile(globalMcpPath(home)), [name]: config } });
53
+ }
54
+ function removeGlobalServer(home, name) {
55
+ const { [name]: _removed, ...rest } = readMcpFile(globalMcpPath(home));
56
+ writePrivate(globalMcpPath(home), { mcpServers: rest });
57
+ }
58
+ var configHash = (config) => createHash("sha256").update(JSON.stringify(config)).digest("hex");
59
+ function projectTrust(home, cwd, name, config) {
60
+ const decision = readJson(trustPath(home), {})[resolve(cwd)]?.[name];
61
+ return decision?.hash === configHash(config) ? decision.allowed : void 0;
62
+ }
63
+ function setProjectTrust(home, cwd, name, allowed, config) {
64
+ const all = readJson(trustPath(home), {});
65
+ const key = resolve(cwd);
66
+ writePrivate(trustPath(home), { ...all, [key]: { ...all[key], [name]: { allowed, hash: configHash(config) } } });
67
+ }
68
+ function serversToStart(home, cwd) {
69
+ const global = readMcpFile(globalMcpPath(home));
70
+ const start = Object.entries(global).map(([name, config]) => ({ name, config, source: "global" }));
71
+ const needTrust = [];
72
+ for (const [name, config] of Object.entries(readMcpFile(projectMcpPath(cwd)))) {
73
+ if (global[name]) continue;
74
+ const trust = projectTrust(home, cwd, name, config);
75
+ if (trust === true) start.push({ name, config, source: "project" });
76
+ else if (trust === void 0) needTrust.push({ name, config, source: "project" });
77
+ }
78
+ return { start, needTrust };
79
+ }
80
+
81
+ export {
82
+ expandEnvVars,
83
+ globalMcpPath,
84
+ projectMcpPath,
85
+ readMcpFile,
86
+ addGlobalServer,
87
+ removeGlobalServer,
88
+ projectTrust,
89
+ setProjectTrust,
90
+ serversToStart
91
+ };
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ quarantineCorruptFile,
4
+ writeFileAtomic
5
+ } from "./chunk-ZXWRLN7E.js";
6
+
7
+ // src/mcp/oauth.ts
8
+ import { spawn } from "child_process";
9
+ import { existsSync, mkdirSync, readFileSync } from "fs";
10
+ import { createServer } from "http";
11
+ import { dirname, join } from "path";
12
+ import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
13
+ var oauthStorePath = (home) => join(home, "mcp-oauth.json");
14
+ function readStore(home) {
15
+ const path = oauthStorePath(home);
16
+ if (!existsSync(path)) return {};
17
+ try {
18
+ return JSON.parse(readFileSync(path, "utf8"));
19
+ } catch (error) {
20
+ quarantineCorruptFile(path, error);
21
+ return {};
22
+ }
23
+ }
24
+ function writeStore(home, store) {
25
+ const path = oauthStorePath(home);
26
+ mkdirSync(dirname(path), { recursive: true });
27
+ writeFileAtomic(path, `${JSON.stringify(store, null, 2)}
28
+ `, 384);
29
+ }
30
+ function hasStoredTokens(home, serverUrl) {
31
+ return Boolean(readStore(home)[serverUrl]?.tokens);
32
+ }
33
+ function clearTokens(home, serverUrl) {
34
+ const store = readStore(home);
35
+ const entry = store[serverUrl];
36
+ if (!entry?.tokens && !entry?.client && !entry?.codeVerifier && !entry?.discovery) return false;
37
+ delete store[serverUrl];
38
+ writeStore(home, store);
39
+ return true;
40
+ }
41
+ function openCommand(url, platform) {
42
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
43
+ if (platform === "win32") return { command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url.href] };
44
+ return { command: platform === "darwin" ? "open" : "xdg-open", args: [url.href] };
45
+ }
46
+ function defaultOpen(url, deps = {}) {
47
+ const plan = openCommand(url, deps.platform ?? process.platform);
48
+ if (!plan) return;
49
+ const child = (deps.spawn ?? spawn)(plan.command, plan.args, { detached: true, stdio: "ignore" });
50
+ child.unref();
51
+ child.on("error", () => {
52
+ });
53
+ }
54
+ var FileOAuthProvider = class {
55
+ opts;
56
+ constructor(opts) {
57
+ this.opts = opts;
58
+ }
59
+ /**
60
+ * Always defined: MCP SDK treats a missing redirectUrl as a non-interactive
61
+ * (client_credentials) flow and would try a token exchange without a code.
62
+ */
63
+ get redirectUrl() {
64
+ return this.opts.redirectUrl ?? "http://127.0.0.1:1/callback";
65
+ }
66
+ get clientMetadata() {
67
+ return {
68
+ client_name: "BCCLI",
69
+ redirect_uris: [String(this.redirectUrl)],
70
+ grant_types: ["authorization_code", "refresh_token"],
71
+ response_types: ["code"],
72
+ token_endpoint_auth_method: "none"
73
+ };
74
+ }
75
+ entry() {
76
+ return readStore(this.opts.home)[this.opts.serverUrl] ?? {};
77
+ }
78
+ patch(patch) {
79
+ const store = readStore(this.opts.home);
80
+ const next = { ...store[this.opts.serverUrl], ...patch };
81
+ for (const key of Object.keys(next)) {
82
+ if (next[key] === void 0) delete next[key];
83
+ }
84
+ store[this.opts.serverUrl] = next;
85
+ if (Object.keys(next).length === 0) delete store[this.opts.serverUrl];
86
+ writeStore(this.opts.home, store);
87
+ }
88
+ clientInformation() {
89
+ return this.entry().client;
90
+ }
91
+ saveClientInformation(clientInformation) {
92
+ this.patch({ client: clientInformation });
93
+ }
94
+ tokens() {
95
+ return this.entry().tokens;
96
+ }
97
+ saveTokens(tokens) {
98
+ this.patch({ tokens });
99
+ }
100
+ saveCodeVerifier(codeVerifier) {
101
+ this.patch({ codeVerifier });
102
+ }
103
+ codeVerifier() {
104
+ return this.entry().codeVerifier ?? "";
105
+ }
106
+ redirectToAuthorization(authorizationUrl) {
107
+ return (this.opts.open ?? defaultOpen)(authorizationUrl);
108
+ }
109
+ saveDiscoveryState(state) {
110
+ this.patch({ discovery: state });
111
+ }
112
+ discoveryState() {
113
+ return this.entry().discovery;
114
+ }
115
+ invalidateCredentials(scope) {
116
+ if (scope === "all") {
117
+ this.patch({ client: void 0, tokens: void 0, codeVerifier: void 0, discovery: void 0 });
118
+ return;
119
+ }
120
+ if (scope === "client") this.patch({ client: void 0 });
121
+ if (scope === "tokens") this.patch({ tokens: void 0 });
122
+ if (scope === "verifier") this.patch({ codeVerifier: void 0 });
123
+ if (scope === "discovery") this.patch({ discovery: void 0 });
124
+ }
125
+ };
126
+ function parseCallback(input) {
127
+ const url = typeof input === "string" ? new URL(input) : input;
128
+ const error = url.searchParams.get("error");
129
+ if (error) {
130
+ const description = url.searchParams.get("error_description");
131
+ throw new Error(description ? `OAuth ${error}: ${description}` : `OAuth ${error}`);
132
+ }
133
+ const code = url.searchParams.get("code");
134
+ if (!code) throw new Error("OAuth callback is missing the code parameter.");
135
+ const state = url.searchParams.get("state") ?? void 0;
136
+ return { code, state };
137
+ }
138
+ var CALLBACK_TIMEOUT_MS = 18e4;
139
+ async function authorizeMcpServer(spec, deps) {
140
+ let settle;
141
+ const codeReceived = new Promise((resolve, reject) => {
142
+ settle = { resolve, reject };
143
+ });
144
+ codeReceived.catch(() => {
145
+ });
146
+ const server = createServer((req, res) => {
147
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
148
+ if (url.pathname !== "/callback") {
149
+ res.writeHead(404);
150
+ res.end();
151
+ return;
152
+ }
153
+ try {
154
+ const { code } = parseCallback(url);
155
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
156
+ res.end("<html><body><p>BCCLI is connected. You can close this tab.</p></body></html>");
157
+ settle.resolve(code);
158
+ } catch (error) {
159
+ const escaped = error.message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
160
+ res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
161
+ res.end(`<html><body><p>${escaped}</p></body></html>`);
162
+ settle.reject(error);
163
+ }
164
+ });
165
+ const port = await new Promise((resolve, reject) => {
166
+ server.once("error", reject);
167
+ server.listen(0, "127.0.0.1", () => {
168
+ const address = server.address();
169
+ resolve(typeof address === "object" && address ? address.port : 0);
170
+ });
171
+ });
172
+ try {
173
+ const provider = new FileOAuthProvider({
174
+ home: deps.home,
175
+ serverUrl: spec.config.url,
176
+ redirectUrl: `http://127.0.0.1:${port}/callback`,
177
+ open: deps.open
178
+ });
179
+ const serverUrl = new URL(spec.config.url);
180
+ const result = await auth(provider, { serverUrl, fetchFn: deps.fetchFn });
181
+ if (result === "AUTHORIZED") return;
182
+ const timer = setTimeout(() => settle.reject(new Error("Timed out waiting for the OAuth callback.")), CALLBACK_TIMEOUT_MS);
183
+ let code;
184
+ try {
185
+ code = await codeReceived;
186
+ } finally {
187
+ clearTimeout(timer);
188
+ }
189
+ const second = await auth(provider, { serverUrl, authorizationCode: code, fetchFn: deps.fetchFn });
190
+ if (second !== "AUTHORIZED") throw new Error("OAuth authorization did not complete.");
191
+ } finally {
192
+ server.close();
193
+ }
194
+ }
195
+
196
+ export {
197
+ hasStoredTokens,
198
+ clearTokens,
199
+ defaultOpen,
200
+ FileOAuthProvider,
201
+ authorizeMcpServer
202
+ };
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/permissions.ts
4
+ import { isAbsolute, relative, resolve } from "path";
5
+ var MODE_ORDER = ["default", "acceptEdits", "plan", "allowAll"];
6
+ function nextMode(mode) {
7
+ return MODE_ORDER[(MODE_ORDER.indexOf(mode) + 1) % MODE_ORDER.length];
8
+ }
9
+ var ALWAYS_ASK_COMMANDS = /* @__PURE__ */ new Set(["sudo", "sh", "bash", "zsh", "su"]);
10
+ function isAlwaysAsk(segment) {
11
+ const words = segment.trim().split(/\s+/);
12
+ const first = words[0] ?? "";
13
+ if (ALWAYS_ASK_COMMANDS.has(first)) return true;
14
+ const flags = words.slice(1).filter((w) => w.startsWith("-"));
15
+ if (first === "rm" && flags.some((w) => w.includes("r"))) return true;
16
+ if (first === "chmod" && flags.some((w) => w.includes("R"))) return true;
17
+ if (first === "kill" && words.slice(1).includes("-9")) return true;
18
+ if (first === "git" && words[1] === "push") {
19
+ return words.slice(2).some((w) => w === "-f" || w === "--force" || w.startsWith("--force="));
20
+ }
21
+ return false;
22
+ }
23
+ var RUNNER_COMMANDS = /* @__PURE__ */ new Set([
24
+ "npm",
25
+ "npx",
26
+ "pnpm",
27
+ "pnpx",
28
+ "yarn",
29
+ "bun",
30
+ "bunx",
31
+ "deno",
32
+ "node",
33
+ "tsx",
34
+ "ts-node",
35
+ "python",
36
+ "python3",
37
+ "py",
38
+ "uv",
39
+ "uvx",
40
+ "pip",
41
+ "pip3",
42
+ "pipx",
43
+ "pytest",
44
+ "make",
45
+ "cargo",
46
+ "go",
47
+ "dotnet",
48
+ "java",
49
+ "ruby",
50
+ "bundle",
51
+ "rake",
52
+ "gradle",
53
+ "mvn",
54
+ "php",
55
+ "composer"
56
+ ]);
57
+ function commandKey(segment) {
58
+ const words = segment.trim().split(/\s+/);
59
+ if (RUNNER_COMMANDS.has(words[0] ?? "") && words[2] && !words[2].startsWith("-")) return words.slice(0, 3).join(" ");
60
+ return words[1] ? `${words[0]} ${words[1]}` : words[0];
61
+ }
62
+ function requiresSensitiveToolApproval(req) {
63
+ const target = req.target.trim();
64
+ if (req.kind === "read" || req.kind === "edit") {
65
+ return /(?:^|[\s/\\])(?:\.env(?:\.[^\s/\\]+)?|\.ssh|\.aws|\.config[/\\]gcloud|\.npmrc|\.netrc|\.git-credentials|(?:secrets?|credentials?)(?:\.[a-z0-9_-]+)?)(?:[\s/\\]|$)/i.test(
66
+ target
67
+ ) || /(?:id_rsa|id_ed25519|\.pem|\.p12|\.key)(?:\s|$)/i.test(target);
68
+ }
69
+ if (req.kind === "bash") {
70
+ return /\b(?:rm\s+-[a-z]*r[a-z]*f[a-z]*\s|rm\s+-[a-z]*f[a-z]*r[a-z]*\s|mkfs(?:\.|\s)|wipefs\b|shred\s|git\s+push\s+(?:--force|-f)\b|docker\s+(?:system\s+prune|volume\s+rm)|kubectl\s+delete\b|drop\s+(?:database|table)|npm\s+publish\b)/i.test(target) || /\b(?:deploy-service\.sh|aws\s+secretsmanager|cat\s+[^;&|]*\.env\b)/i.test(target);
71
+ }
72
+ if (req.kind === "mcp") {
73
+ return /(?:delete|remove|revoke|transfer|send_email|publish|deploy|execute_command|run_command|read_secret)/i.test(req.tool);
74
+ }
75
+ return false;
76
+ }
77
+ var Permissions = class {
78
+ constructor(mode, rules = [], cwd = process.cwd()) {
79
+ this.mode = mode;
80
+ this.cwd = cwd;
81
+ this.rules = new Set(rules);
82
+ this.configRules = new Set(rules);
83
+ }
84
+ mode;
85
+ cwd;
86
+ rules;
87
+ configRules;
88
+ /** Edits may only be auto-allowed inside the project and outside .git. */
89
+ editInProject(target) {
90
+ const rel = relative(this.cwd, resolve(this.cwd, target));
91
+ if (!rel || rel.startsWith("..") || isAbsolute(rel)) return false;
92
+ const parts = rel.split(/[/\\]/).map((p) => p.toLowerCase());
93
+ return !parts.includes(".git");
94
+ }
95
+ /** Rules that "[a] allow for this session" would add; undefined when it must always be asked. */
96
+ rulesFor(req) {
97
+ if (requiresSensitiveToolApproval(req) && (req.kind === "read" || req.kind === "edit" || process.env.BC_TOOL_POLICY_ENFORCE === "true"))
98
+ return void 0;
99
+ if (req.kind === "edit") return this.editInProject(req.target) ? ["edit(project)"] : void 0;
100
+ if (req.kind === "fetch") {
101
+ try {
102
+ return [`fetch(${new URL(req.target).hostname})`];
103
+ } catch {
104
+ return void 0;
105
+ }
106
+ }
107
+ if (req.kind === "bash") {
108
+ if (/`|\$\(|[<>]|^\s*[({]/.test(req.target)) return void 0;
109
+ const segments = req.target.split(/&&|\|\||[;&|\n]/).map((s) => s.trim()).filter(Boolean);
110
+ if (!segments.length) return void 0;
111
+ if (segments.some((s) => /^[({]/.test(s) || isAlwaysAsk(s))) return void 0;
112
+ return [...new Set(segments.map((s) => `bash(${commandKey(s)})`))];
113
+ }
114
+ if (req.kind === "mcp") return [`mcp(${req.tool})`];
115
+ return [];
116
+ }
117
+ check(req) {
118
+ if (this.mode === "plan" && req.kind !== "read") return "deny";
119
+ if ((req.kind === "read" || req.kind === "edit") && this.mode !== "allowAll" && requiresSensitiveToolApproval(req))
120
+ return "ask";
121
+ if (process.env.BC_TOOL_POLICY_ENFORCE === "true" && requiresSensitiveToolApproval(req)) return "ask";
122
+ if (req.kind === "read") return "allow";
123
+ if (this.mode === "allowAll") return "allow";
124
+ if (this.mode === "plan") return "deny";
125
+ if (this.rules.has(req.kind)) return "allow";
126
+ const needed = this.rulesFor(req);
127
+ if (!needed) return "ask";
128
+ if (req.kind === "edit" && this.mode === "acceptEdits") return "allow";
129
+ return needed.every((rule) => this.rules.has(rule)) ? "allow" : "ask";
130
+ }
131
+ list() {
132
+ return [...this.rules].map((rule) => ({ rule, source: this.configRules.has(rule) ? "config" : "session" }));
133
+ }
134
+ /** Removes a session grant; rules from config/flags stay until the user edits them. */
135
+ revoke(rule) {
136
+ if (!this.configRules.has(rule)) this.rules.delete(rule);
137
+ }
138
+ allowForSession(req) {
139
+ for (const rule of this.rulesFor(req) ?? []) this.rules.add(rule);
140
+ }
141
+ };
142
+
143
+ export {
144
+ MODE_ORDER,
145
+ nextMode,
146
+ requiresSensitiveToolApproval,
147
+ Permissions
148
+ };