@botconnector/bccli 0.4.17 → 0.5.0-rc.4

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 (186) hide show
  1. package/LICENSE +203 -21
  2. package/THIRD_PARTY_NOTICES.md +9 -0
  3. package/branding.mjs +347 -0
  4. package/cli.mjs +120 -0
  5. package/core.mjs +260 -0
  6. package/credentials.mjs +52 -0
  7. package/gateway.mjs +2 -0
  8. package/model-command.mjs +61 -0
  9. package/package.json +35 -59
  10. package/provider-cli.mjs +77 -0
  11. package/providers.mjs +166 -0
  12. package/proxy.mjs +242 -0
  13. package/startup-menu.mjs +120 -0
  14. package/README.en.md +0 -253
  15. package/README.md +0 -250
  16. package/dist/abort.d.ts +0 -5
  17. package/dist/acp-HWHF25KU.js +0 -221
  18. package/dist/acp.d.ts +0 -44
  19. package/dist/agent.d.ts +0 -158
  20. package/dist/agents.d.ts +0 -4
  21. package/dist/args-XA77XUMD.js +0 -13
  22. package/dist/args.d.ts +0 -59
  23. package/dist/budget.d.ts +0 -26
  24. package/dist/checkpoints.d.ts +0 -35
  25. package/dist/chunk-2IP5F3EK.js +0 -56
  26. package/dist/chunk-5CVHTYBQ.js +0 -270
  27. package/dist/chunk-6LO6YRDY.js +0 -216
  28. package/dist/chunk-6PJRWKVI.js +0 -50
  29. package/dist/chunk-7OTFZK4B.js +0 -159
  30. package/dist/chunk-7U7CMKKI.js +0 -861
  31. package/dist/chunk-A3NBWIEQ.js +0 -2706
  32. package/dist/chunk-FY543EQW.js +0 -50
  33. package/dist/chunk-IDZLHEVL.js +0 -524
  34. package/dist/chunk-IF5RNXBI.js +0 -341
  35. package/dist/chunk-M5T4AM4I.js +0 -256
  36. package/dist/chunk-NGFWBVHD.js +0 -71
  37. package/dist/chunk-OCEQ2TRJ.js +0 -1075
  38. package/dist/chunk-OK2USAXX.js +0 -88
  39. package/dist/chunk-PSO7R5AD.js +0 -10
  40. package/dist/chunk-RLSQAZBT.js +0 -78
  41. package/dist/chunk-VPFCIUC6.js +0 -91
  42. package/dist/chunk-XP5MTJN2.js +0 -8
  43. package/dist/chunk-YGBTNDPK.js +0 -202
  44. package/dist/chunk-YYTFJUO2.js +0 -148
  45. package/dist/chunk-ZXWRLN7E.js +0 -378
  46. package/dist/cli.d.ts +0 -1
  47. package/dist/cli.js +0 -233
  48. package/dist/clipboardImage.d.ts +0 -17
  49. package/dist/commands.d.ts +0 -16
  50. package/dist/completion-IRVEVW6H.js +0 -125
  51. package/dist/completion.d.ts +0 -14
  52. package/dist/config-OVWCUVY7.js +0 -25
  53. package/dist/config-S3CZUIBB.js +0 -34
  54. package/dist/config.d.ts +0 -99
  55. package/dist/context.d.ts +0 -13
  56. package/dist/contextWindow.d.ts +0 -3
  57. package/dist/diff.d.ts +0 -7
  58. package/dist/exit.d.ts +0 -9
  59. package/dist/extensions.d.ts +0 -34
  60. package/dist/ghBotCli-FYKJDJOD.js +0 -680
  61. package/dist/ghBotCli.d.ts +0 -9
  62. package/dist/ghBotDispatch.d.ts +0 -68
  63. package/dist/ghBotRunner.d.ts +0 -82
  64. package/dist/ghBotServe.d.ts +0 -21
  65. package/dist/ghBotWebhook.d.ts +0 -45
  66. package/dist/hooks.d.ts +0 -27
  67. package/dist/i18n/id.d.ts +0 -1
  68. package/dist/i18n.d.ts +0 -16
  69. package/dist/integrationCli-GY73S2K7.js +0 -876
  70. package/dist/integrationCli.d.ts +0 -9
  71. package/dist/keybinds.d.ts +0 -22
  72. package/dist/login-KSN63EM4.js +0 -18
  73. package/dist/login.d.ts +0 -21
  74. package/dist/lsp.d.ts +0 -73
  75. package/dist/mcp/catalog.d.ts +0 -14
  76. package/dist/mcp/config.d.ts +0 -36
  77. package/dist/mcp/manager.d.ts +0 -52
  78. package/dist/mcp/oauth.d.ts +0 -82
  79. package/dist/mcpCli-VWSCCDWT.js +0 -113
  80. package/dist/mcpCli.d.ts +0 -3
  81. package/dist/mcpServer/serve.d.ts +0 -8
  82. package/dist/mcpServer/server.d.ts +0 -19
  83. package/dist/models.d.ts +0 -13
  84. package/dist/modelsCli-4Q6KFWV2.js +0 -36
  85. package/dist/modelsCli.d.ts +0 -15
  86. package/dist/npmRunner-LU43GM3W.js +0 -46
  87. package/dist/npmRunner.d.ts +0 -21
  88. package/dist/oauthLogin-JWXQZTMZ.js +0 -210
  89. package/dist/oauthLogin.d.ts +0 -47
  90. package/dist/permissions.d.ts +0 -30
  91. package/dist/pluginSpec.d.ts +0 -25
  92. package/dist/plugins.d.ts +0 -45
  93. package/dist/pluginsCli-7XWDLJEL.js +0 -288
  94. package/dist/pluginsCli.d.ts +0 -16
  95. package/dist/prCli-FH7N26D4.js +0 -94
  96. package/dist/prCli.d.ts +0 -46
  97. package/dist/presets.d.ts +0 -8
  98. package/dist/print.d.ts +0 -17
  99. package/dist/provider.d.ts +0 -145
  100. package/dist/providerCli-YMG4G56R.js +0 -15
  101. package/dist/providerCli.d.ts +0 -17
  102. package/dist/providers.d.ts +0 -6
  103. package/dist/reasoning.d.ts +0 -12
  104. package/dist/repomap.d.ts +0 -10
  105. package/dist/sdk.d.ts +0 -61
  106. package/dist/sdk.js +0 -6532
  107. package/dist/serve-HSFF6MGB.js +0 -178
  108. package/dist/serve-HUQNVOSP.js +0 -532
  109. package/dist/serve.d.ts +0 -37
  110. package/dist/serveHeartbeat.d.ts +0 -14
  111. package/dist/serveProtocol.d.ts +0 -47
  112. package/dist/session-YM36TGU7.js +0 -12
  113. package/dist/session.d.ts +0 -33
  114. package/dist/setup-3YB6EKYX.js +0 -24
  115. package/dist/setup.d.ts +0 -79
  116. package/dist/slash/copy.d.ts +0 -1
  117. package/dist/slash/diff.d.ts +0 -1
  118. package/dist/slash/doctor.d.ts +0 -18
  119. package/dist/slash/export.d.ts +0 -2
  120. package/dist/slash/info.d.ts +0 -46
  121. package/dist/slash/memory.d.ts +0 -10
  122. package/dist/statsCli-I4TSDU3X.js +0 -353
  123. package/dist/statsCli.d.ts +0 -62
  124. package/dist/textToolCalls.d.ts +0 -6
  125. package/dist/thinking.d.ts +0 -17
  126. package/dist/tokenBudget.d.ts +0 -22
  127. package/dist/tools/bash.d.ts +0 -54
  128. package/dist/tools/diagnostics.d.ts +0 -13
  129. package/dist/tools/edit.d.ts +0 -12
  130. package/dist/tools/fetch.d.ts +0 -41
  131. package/dist/tools/glob.d.ts +0 -5
  132. package/dist/tools/globSafety.d.ts +0 -7
  133. package/dist/tools/grep.d.ts +0 -25
  134. package/dist/tools/ignore.d.ts +0 -2
  135. package/dist/tools/index.d.ts +0 -12
  136. package/dist/tools/memory.d.ts +0 -13
  137. package/dist/tools/paths.d.ts +0 -4
  138. package/dist/tools/plan.d.ts +0 -10
  139. package/dist/tools/read.d.ts +0 -16
  140. package/dist/tools/repomap.d.ts +0 -2
  141. package/dist/tools/sandbox.d.ts +0 -15
  142. package/dist/tools/shell.d.ts +0 -18
  143. package/dist/tools/skill.d.ts +0 -3
  144. package/dist/tools/task.d.ts +0 -28
  145. package/dist/tools/todo.d.ts +0 -13
  146. package/dist/tools/types.d.ts +0 -52
  147. package/dist/tools/websearch.d.ts +0 -9
  148. package/dist/tools/write.d.ts +0 -5
  149. package/dist/transferCli-3B4LMOYI.js +0 -73
  150. package/dist/transferCli.d.ts +0 -23
  151. package/dist/ui/App.d.ts +0 -7
  152. package/dist/ui/DiffView.d.ts +0 -3
  153. package/dist/ui/EditableText.d.ts +0 -7
  154. package/dist/ui/LinePrompt.d.ts +0 -6
  155. package/dist/ui/ListPicker.d.ts +0 -11
  156. package/dist/ui/Markdown.d.ts +0 -8
  157. package/dist/ui/McpMenu.d.ts +0 -11
  158. package/dist/ui/ModelPicker.d.ts +0 -6
  159. package/dist/ui/PermissionPrompt.d.ts +0 -5
  160. package/dist/ui/PlanApproval.d.ts +0 -5
  161. package/dist/ui/PromptInput.d.ts +0 -20
  162. package/dist/ui/ProviderMenu.d.ts +0 -10
  163. package/dist/ui/Spinner.d.ts +0 -4
  164. package/dist/ui/StatusBar.d.ts +0 -14
  165. package/dist/ui/TodoList.d.ts +0 -4
  166. package/dist/ui/ToolBlock.d.ts +0 -14
  167. package/dist/ui/complete.d.ts +0 -8
  168. package/dist/ui/index.d.ts +0 -7
  169. package/dist/ui/lineEdit.d.ts +0 -42
  170. package/dist/ui/liveCap.d.ts +0 -29
  171. package/dist/ui/markdownLayout.d.ts +0 -23
  172. package/dist/ui/replay.d.ts +0 -4
  173. package/dist/ui/stdinFilter.d.ts +0 -18
  174. package/dist/ui/table.d.ts +0 -20
  175. package/dist/ui/theme.d.ts +0 -5
  176. package/dist/ui/thinkingMouse.d.ts +0 -38
  177. package/dist/ui/transcript.d.ts +0 -55
  178. package/dist/ui/tty.d.ts +0 -11
  179. package/dist/ui/vim.d.ts +0 -26
  180. package/dist/ui-TAZMRXRI.js +0 -3044
  181. package/dist/update-5V425FIK.js +0 -145
  182. package/dist/update.d.ts +0 -30
  183. package/dist/verify.d.ts +0 -16
  184. package/dist/version.d.ts +0 -1
  185. package/dist/webBudget.d.ts +0 -25
  186. package/dist/worktree.d.ts +0 -12
@@ -1,532 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- createRuntime
4
- } from "./chunk-A3NBWIEQ.js";
5
- import "./chunk-OK2USAXX.js";
6
- import "./chunk-7U7CMKKI.js";
7
- import "./chunk-IF5RNXBI.js";
8
- import "./chunk-PSO7R5AD.js";
9
- import {
10
- VERSION
11
- } from "./chunk-XP5MTJN2.js";
12
- import {
13
- LOOPBACK_BIND,
14
- MAX_FRAME_BYTES,
15
- bearerOk,
16
- parseCliArgs,
17
- parseClientFrame,
18
- randomToken,
19
- trustFence
20
- } from "./chunk-M5T4AM4I.js";
21
- import "./chunk-YYTFJUO2.js";
22
- import "./chunk-RLSQAZBT.js";
23
- import {
24
- listAllModels
25
- } from "./chunk-2IP5F3EK.js";
26
- import "./chunk-FY543EQW.js";
27
- import "./chunk-YGBTNDPK.js";
28
- import {
29
- Session
30
- } from "./chunk-6LO6YRDY.js";
31
- import "./chunk-OCEQ2TRJ.js";
32
- import "./chunk-NGFWBVHD.js";
33
- import "./chunk-VPFCIUC6.js";
34
- import {
35
- bccliHome,
36
- loadConfig
37
- } from "./chunk-ZXWRLN7E.js";
38
- import "./chunk-IDZLHEVL.js";
39
-
40
- // src/serve.ts
41
- import { randomBytes } from "crypto";
42
- import { createServer } from "http";
43
- import { basename } from "path";
44
- import { WebSocket, WebSocketServer } from "ws";
45
-
46
- // src/serveHeartbeat.ts
47
- function createHeartbeat(intervalMs, ping, terminate) {
48
- let awaitingPong = false;
49
- let stopped = false;
50
- let timer = null;
51
- const tick = () => {
52
- if (stopped) return;
53
- if (awaitingPong) {
54
- terminate();
55
- timer = null;
56
- return;
57
- }
58
- awaitingPong = true;
59
- ping();
60
- timer = setTimeout(tick, intervalMs);
61
- };
62
- return {
63
- start() {
64
- if (stopped || timer) return;
65
- awaitingPong = true;
66
- ping();
67
- timer = setTimeout(tick, intervalMs);
68
- },
69
- pong() {
70
- awaitingPong = false;
71
- },
72
- stop() {
73
- stopped = true;
74
- if (timer) clearTimeout(timer);
75
- timer = null;
76
- }
77
- };
78
- }
79
-
80
- // src/serve.ts
81
- var MAX_BODY_BYTES = 1024 * 1024;
82
- var DISPLAY_MAX = 1e5;
83
- function json(res, status, value) {
84
- const body = JSON.stringify(value);
85
- res.writeHead(status, { "content-type": "application/json", "content-length": Buffer.byteLength(body) });
86
- res.end(body);
87
- }
88
- function readBody(req) {
89
- return new Promise((resolve, reject) => {
90
- let body = "";
91
- let size = 0;
92
- let tooLarge = false;
93
- req.on("data", (c) => {
94
- size += c.length;
95
- if (size > MAX_BODY_BYTES) tooLarge = true;
96
- else body += c;
97
- });
98
- req.on("end", () => resolve(tooLarge ? null : body));
99
- req.on("error", reject);
100
- });
101
- }
102
- function sendFrame(ws, frame) {
103
- if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(frame));
104
- }
105
- function serveBanner(handle, opts = {}) {
106
- const supplied = opts.token !== void 0 || (opts.env ?? process.env).BCCLI_SERVE_TOKEN !== void 0;
107
- return `bccli serve ${handle.url}${supplied ? "" : ` \u2014 Authorization: Bearer ${handle.token}`}
108
- `;
109
- }
110
- async function startServe(opts = {}) {
111
- const env = opts.env ?? process.env;
112
- const cwd = opts.cwd ?? process.cwd();
113
- const host = opts.host ?? "127.0.0.1";
114
- if (!LOOPBACK_BIND.has(host)) throw new Error(`serve refuses non-loopback --host ${host} (loopback only)`);
115
- const token = opts.token ?? env.BCCLI_SERVE_TOKEN ?? randomToken();
116
- const home = bccliHome(env);
117
- const sessions = /* @__PURE__ */ new Map();
118
- const allSockets = /* @__PURE__ */ new Set();
119
- const server = createServer((req, res) => {
120
- void handleHttp(req, res);
121
- });
122
- const wss = new WebSocketServer({ noServer: true });
123
- server.on("upgrade", (req, socket, head) => {
124
- const address2 = server.address();
125
- const port2 = typeof address2 === "object" && address2 ? address2.port : 0;
126
- const reject = (status, reason) => {
127
- socket.write(`HTTP/1.1 ${status} ${reason}\r
128
- Connection: close\r
129
- \r
130
- `);
131
- socket.destroy();
132
- };
133
- const fence = trustFence(req.headers, port2);
134
- if (fence) return reject(403, "Forbidden");
135
- const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
136
- if (url.pathname !== "/v1/mux") return reject(404, "Not Found");
137
- if (!bearerOk(req.headers.authorization, token)) return reject(401, "Unauthorized");
138
- wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
139
- });
140
- wss.on("connection", (ws) => {
141
- allSockets.add(ws);
142
- const heartbeat = createHeartbeat(opts.heartbeatMs ?? 2e3, () => ws.ping(), () => ws.terminate());
143
- ws.on("pong", () => heartbeat.pong());
144
- ws.on("close", () => heartbeat.stop());
145
- heartbeat.start();
146
- const connStreams = /* @__PURE__ */ new Map();
147
- ws.on("close", () => {
148
- allSockets.delete(ws);
149
- for (const stream of connStreams.values()) stream.session.streams.delete(stream);
150
- connStreams.clear();
151
- });
152
- ws.on("message", (data) => {
153
- const rawFrame = String(data);
154
- if (rawFrame.length > MAX_FRAME_BYTES) {
155
- sendFrame(ws, { type: "error", streamId: "", message: "frame exceeds 256 KB" });
156
- return;
157
- }
158
- const frame = parseClientFrame(rawFrame);
159
- if ("error" in frame) {
160
- sendFrame(ws, { type: "error", streamId: "", message: frame.error });
161
- return;
162
- }
163
- if (frame.type === "cancel") {
164
- const stream2 = connStreams.get(frame.streamId);
165
- if (!stream2) return;
166
- connStreams.delete(frame.streamId);
167
- stream2.session.streams.delete(stream2);
168
- sendFrame(ws, { type: "end", streamId: frame.streamId });
169
- return;
170
- }
171
- if (connStreams.has(frame.streamId)) {
172
- sendFrame(ws, { type: "error", streamId: frame.streamId, message: "duplicate open" });
173
- return;
174
- }
175
- const match = frame.target.match(/^session:(.+)$/);
176
- const sessionId = match ? decodeURIComponent(match[1]) : "";
177
- const session = sessionId ? sessions.get(sessionId) ?? attachSession(sessionId, cwd) : void 0;
178
- if (!session) {
179
- sendFrame(ws, { type: "error", streamId: frame.streamId, message: `unknown target ${frame.target}` });
180
- return;
181
- }
182
- const stream = { id: frame.streamId, ws, session };
183
- connStreams.set(frame.streamId, stream);
184
- session.streams.add(stream);
185
- sendFrame(ws, { type: "item", streamId: frame.streamId, value: { type: "ready", sessionId: session.id, host: { home, version: VERSION } } });
186
- for (const [requestId, p] of session.pending) {
187
- sendFrame(ws, {
188
- type: "item",
189
- streamId: frame.streamId,
190
- value: { type: "approval_request", requestId, tool: p.req.tool, kind: p.req.kind, target: p.req.target, preview: p.req.preview }
191
- });
192
- }
193
- });
194
- });
195
- function broadcast(s, value) {
196
- for (const stream of s.streams) sendFrame(stream.ws, { type: "item", streamId: stream.id, value });
197
- }
198
- async function handleHttp(req, res) {
199
- const address2 = server.address();
200
- const port2 = typeof address2 === "object" && address2 ? address2.port : 0;
201
- const fence = trustFence(req.headers, port2);
202
- if (fence) {
203
- jsonError(res, 403, fence);
204
- return;
205
- }
206
- const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
207
- if (url.pathname !== "/health" && !bearerOk(req.headers.authorization, token)) {
208
- jsonError(res, 401, "missing or invalid bearer token");
209
- return;
210
- }
211
- await route(req, res, url);
212
- }
213
- function jsonError(res, status, message) {
214
- json(res, status, { error: { message } });
215
- }
216
- async function route(req, res, url) {
217
- const method = req.method ?? "GET";
218
- if (method === "GET" && url.pathname === "/health") {
219
- json(res, 200, { ok: true, version: VERSION });
220
- return;
221
- }
222
- if (method === "GET" && url.pathname === "/v1/models") {
223
- try {
224
- const groups = opts.provider ? [{ providerId: "injected", providerName: "injected", models: await opts.provider.listModels() }] : await listAllModels(loadConfig(cwd, env), env, { fetch: opts.fetch });
225
- json(res, 200, groups);
226
- } catch (error) {
227
- jsonError(res, 500, error.message);
228
- }
229
- return;
230
- }
231
- if (url.pathname === "/v1/sessions" && method === "POST") {
232
- const raw = await readBody(req);
233
- if (raw === null) {
234
- jsonError(res, 413, "request body exceeds 1 MB");
235
- return;
236
- }
237
- let body = {};
238
- if (raw) {
239
- try {
240
- body = JSON.parse(raw);
241
- } catch {
242
- jsonError(res, 400, "invalid JSON body");
243
- return;
244
- }
245
- }
246
- if (body.permissionMode !== void 0 && body.permissionMode !== "ask" && body.permissionMode !== "bypassPermissions") {
247
- jsonError(res, 400, "permissionMode must be 'ask' or 'bypassPermissions'");
248
- return;
249
- }
250
- try {
251
- const s = openSession(body.cwd ?? cwd, body.permissionMode ?? "ask", body.model);
252
- json(res, 201, { id: s.id, cwd: s.cwd, permissionMode: s.permissionMode });
253
- } catch (error) {
254
- jsonError(res, 500, error.message);
255
- }
256
- return;
257
- }
258
- if (url.pathname === "/v1/sessions" && method === "GET") {
259
- const listCwd = url.searchParams.get("cwd") ?? cwd;
260
- const byId = /* @__PURE__ */ new Map();
261
- for (const { session, mtime, preview } of Session.list(home, listCwd)) {
262
- const id = basename(session.file, ".jsonl");
263
- byId.set(id, { id, mtime: mtime.toISOString(), preview });
264
- }
265
- for (const s of sessions.values()) {
266
- if (s.cwd === listCwd && !byId.has(s.id)) {
267
- byId.set(s.id, { id: s.id, mtime: (/* @__PURE__ */ new Date()).toISOString(), preview: "" });
268
- }
269
- }
270
- json(res, 200, [...byId.values()]);
271
- return;
272
- }
273
- const one = url.pathname.match(/^\/v1\/sessions\/([^/]+)$/);
274
- if (one && method === "GET") {
275
- const id = decodeURIComponent(one[1]);
276
- const live = sessions.get(id);
277
- if (live) {
278
- json(res, 200, { id, cwd: live.cwd, permissionMode: live.permissionMode, messages: live.rt.agent.messages });
279
- return;
280
- }
281
- const found = findSessionFile(id, url.searchParams.get("cwd") ?? cwd);
282
- if (!found) {
283
- jsonError(res, 404, `unknown session ${id}`);
284
- return;
285
- }
286
- json(res, 200, { id, cwd: found.cwd, permissionMode: "ask", messages: found.session.load() });
287
- return;
288
- }
289
- const messages = url.pathname.match(/^\/v1\/sessions\/([^/]+)\/messages$/);
290
- if (messages && method === "POST") {
291
- const raw = await readBody(req);
292
- if (raw === null) {
293
- jsonError(res, 413, "request body exceeds 1 MB");
294
- return;
295
- }
296
- let body = {};
297
- if (raw) {
298
- try {
299
- body = JSON.parse(raw);
300
- } catch {
301
- jsonError(res, 400, "invalid JSON body");
302
- return;
303
- }
304
- }
305
- const id = decodeURIComponent(messages[1]);
306
- let s = sessions.get(id);
307
- if (!s) s = attachSession(id, url.searchParams.get("cwd") ?? cwd);
308
- if (!s) {
309
- jsonError(res, 404, `unknown session ${id}`);
310
- return;
311
- }
312
- if (typeof body.prompt !== "string" || body.prompt === "") {
313
- jsonError(res, 400, "prompt must be a non-empty string");
314
- return;
315
- }
316
- if (s.busy) {
317
- jsonError(res, 409, `session ${id} is busy with turn ${s.busy.turnId}`);
318
- return;
319
- }
320
- const turnId = randomBytes(6).toString("hex");
321
- const controller = new AbortController();
322
- s.busy = { turnId, controller };
323
- void runTurn(s, body.prompt, controller);
324
- json(res, 202, { turnId });
325
- return;
326
- }
327
- const approvals = url.pathname.match(/^\/v1\/sessions\/([^/]+)\/approvals$/);
328
- if (approvals && method === "POST") {
329
- const raw = await readBody(req);
330
- if (raw === null) {
331
- jsonError(res, 413, "request body exceeds 1 MB");
332
- return;
333
- }
334
- let body = {};
335
- if (raw) {
336
- try {
337
- body = JSON.parse(raw);
338
- } catch {
339
- jsonError(res, 400, "invalid JSON body");
340
- return;
341
- }
342
- }
343
- const answers = ["yes", "no", "session", "all"];
344
- if (typeof body.requestId !== "string" || !answers.includes(body.answer)) {
345
- jsonError(res, 400, "requestId must be a string and answer one of 'yes'|'no'|'session'|'all'");
346
- return;
347
- }
348
- const s = sessions.get(decodeURIComponent(approvals[1]));
349
- if (!s) {
350
- jsonError(res, 404, `unknown session ${decodeURIComponent(approvals[1])}`);
351
- return;
352
- }
353
- const pending = s.pending.get(body.requestId);
354
- if (!pending) {
355
- jsonError(res, 404, `unknown or already answered requestId ${body.requestId}`);
356
- return;
357
- }
358
- s.pending.delete(body.requestId);
359
- pending.resolve(body.answer);
360
- json(res, 200, { accepted: true });
361
- return;
362
- }
363
- const cancel = url.pathname.match(/^\/v1\/sessions\/([^/]+)\/cancel$/);
364
- if (cancel && method === "POST") {
365
- const s = sessions.get(decodeURIComponent(cancel[1]));
366
- if (!s) {
367
- jsonError(res, 404, `unknown session ${decodeURIComponent(cancel[1])}`);
368
- return;
369
- }
370
- if (s.busy) s.busy.controller.abort();
371
- failPending(s);
372
- json(res, 200, { cancelled: true });
373
- return;
374
- }
375
- jsonError(res, 404, `no route ${method} ${url.pathname}`);
376
- }
377
- async function runTurn(s, prompt, controller) {
378
- let text = "";
379
- let stopReason = "done";
380
- const toolCalls = [];
381
- const byId = /* @__PURE__ */ new Map();
382
- let usage = { inputTokens: 0, outputTokens: 0 };
383
- const map = (event) => {
384
- switch (event.type) {
385
- case "text":
386
- text += event.delta;
387
- broadcast(s, { type: "text", delta: event.delta });
388
- break;
389
- case "textReplace":
390
- text = event.text;
391
- break;
392
- case "thinking":
393
- broadcast(s, { type: "thinking", delta: event.delta });
394
- break;
395
- case "toolStart": {
396
- const call = { name: event.tool, target: event.target };
397
- byId.set(event.id, call);
398
- toolCalls.push(call);
399
- broadcast(s, { type: "tool_use", tool: event.tool, target: event.target });
400
- break;
401
- }
402
- case "toolEnd": {
403
- const call = byId.get(event.id);
404
- if (call) {
405
- call.output = event.output;
406
- call.isError = event.isError;
407
- }
408
- const item = {
409
- type: "tool_result",
410
- tool: event.tool,
411
- output: event.output,
412
- isError: event.isError
413
- };
414
- if (typeof event.display === "string") {
415
- item.display = event.display.length > DISPLAY_MAX ? `${event.display.slice(0, DISPLAY_MAX)}
416
- [truncated]` : event.display;
417
- }
418
- broadcast(s, item);
419
- break;
420
- }
421
- case "usage":
422
- usage = { inputTokens: event.inputTokens, outputTokens: event.outputTokens };
423
- broadcast(s, { type: "usage", ...usage });
424
- break;
425
- case "stepLimit":
426
- stopReason = "stepLimit";
427
- break;
428
- case "budgetExceeded":
429
- stopReason = "budgetExceeded";
430
- break;
431
- case "aborted":
432
- stopReason = "aborted";
433
- break;
434
- case "error":
435
- broadcast(s, { type: "error", message: event.message });
436
- break;
437
- default:
438
- break;
439
- }
440
- };
441
- s.rt.agent.onEvent = map;
442
- try {
443
- await s.rt.agent.run(prompt, controller.signal);
444
- } catch {
445
- stopReason = controller.signal.aborted ? "aborted" : stopReason;
446
- } finally {
447
- s.rt.agent.onEvent = () => {
448
- };
449
- s.busy = null;
450
- failPending(s);
451
- broadcast(s, { type: "result", text, toolCalls, usage, stopReason });
452
- }
453
- }
454
- function findSessionFile(id, forCwd) {
455
- const hit = Session.list(home, forCwd).find(({ session }) => basename(session.file, ".jsonl") === id);
456
- return hit ? { session: hit.session, cwd: forCwd } : void 0;
457
- }
458
- function buildRuntime(forCwd, permissionMode, model) {
459
- return createRuntime({
460
- cwd: forCwd,
461
- args: { ...parseCliArgs([]), allowAll: permissionMode === "bypassPermissions", model },
462
- env,
463
- provider: opts.provider,
464
- fetch: opts.fetch
465
- });
466
- }
467
- function openSession(forCwd, permissionMode, model) {
468
- return registerSession(buildRuntime(forCwd, permissionMode, model), forCwd, permissionMode);
469
- }
470
- function attachSession(id, forCwd) {
471
- const found = findSessionFile(id, forCwd);
472
- if (!found) return void 0;
473
- const rt = buildRuntime(found.cwd, "ask", found.session.lastModel());
474
- rt.resume(found.session);
475
- return registerSession(rt, found.cwd, "ask", id);
476
- }
477
- function registerSession(rt, forCwd, permissionMode, id) {
478
- const sessionId = id ?? basename(rt.session.file, ".jsonl");
479
- const s = {
480
- id: sessionId,
481
- cwd: forCwd,
482
- rt,
483
- permissionMode,
484
- busy: null,
485
- streams: /* @__PURE__ */ new Set(),
486
- pending: /* @__PURE__ */ new Map()
487
- };
488
- if (permissionMode === "bypassPermissions") {
489
- rt.agent.askPermission = async () => "yes";
490
- } else {
491
- rt.agent.askPermission = (req) => new Promise((resolve) => {
492
- const requestId = randomBytes(6).toString("hex");
493
- s.pending.set(requestId, { resolve, req });
494
- broadcast(s, {
495
- type: "approval_request",
496
- requestId,
497
- tool: req.tool,
498
- kind: req.kind,
499
- target: req.target,
500
- preview: req.preview
501
- });
502
- });
503
- }
504
- sessions.set(sessionId, s);
505
- return s;
506
- }
507
- function failPending(s) {
508
- for (const [, p] of s.pending) p.resolve("no");
509
- s.pending.clear();
510
- }
511
- await new Promise(
512
- (resolve) => server.listen(opts.port ?? 8787, host === "localhost" ? "127.0.0.1" : host, resolve)
513
- );
514
- const address = server.address();
515
- const port = typeof address === "object" && address ? address.port : 0;
516
- return {
517
- port,
518
- token,
519
- url: `http://127.0.0.1:${port}`,
520
- close: () => new Promise((resolve) => {
521
- for (const s of sessions.values()) failPending(s);
522
- for (const ws of allSockets) ws.terminate();
523
- allSockets.clear();
524
- server.closeAllConnections();
525
- server.close(() => resolve());
526
- })
527
- };
528
- }
529
- export {
530
- serveBanner,
531
- startServe
532
- };
package/dist/serve.d.ts DELETED
@@ -1,37 +0,0 @@
1
- import type { Provider } from './provider';
2
- export interface ServeOptions {
3
- port?: number;
4
- host?: string;
5
- token?: string;
6
- cwd?: string;
7
- env?: NodeJS.ProcessEnv;
8
- provider?: Provider;
9
- fetch?: typeof fetch;
10
- heartbeatMs?: number;
11
- err?: (s: string) => void;
12
- }
13
- export interface ServeHandle {
14
- port: number;
15
- token: string;
16
- url: string;
17
- close(): Promise<void>;
18
- }
19
- export type PermissionMode = 'ask' | 'bypassPermissions';
20
- /**
21
- * One-line terminal banner for `bccli serve`. A token the user supplied (`--token` or
22
- * `BCCLI_SERVE_TOKEN`) is left out so it cannot leak into logs; a token BCCLI generated
23
- * itself must be printed or nobody could connect.
24
- */
25
- export declare function serveBanner(handle: {
26
- url: string;
27
- token: string;
28
- }, opts?: {
29
- token?: string;
30
- env?: NodeJS.ProcessEnv;
31
- }): string;
32
- /**
33
- * Starts the loopback session server (HTTP unary + WS mux at /v1/mux). Refuses
34
- * non-loopback bind addresses; every request passes the DSH-style trust fence
35
- * (403) and, except /health, the bearer-token check (401).
36
- */
37
- export declare function startServe(opts?: ServeOptions): Promise<ServeHandle>;
@@ -1,14 +0,0 @@
1
- export interface Heartbeat {
2
- /** Sends the first ping and schedules the check loop. Idempotent until stop(). */
3
- start(): void;
4
- /** Marks the last ping as answered (ws 'pong' event). */
5
- pong(): void;
6
- /** Clears the timer; no further pings or terminations. */
7
- stop(): void;
8
- }
9
- /**
10
- * ws heartbeat with deadline: every interval a ping goes out; a ping left
11
- * unanswered by the next tick terminates the socket (connection, not stream).
12
- * Modeled on the DSH gateway's `websocketHeartbeatIntervalMs` behavior.
13
- */
14
- export declare function createHeartbeat(intervalMs: number, ping: () => void, terminate: () => void): Heartbeat;
@@ -1,47 +0,0 @@
1
- /** Wire frames of the /v1/mux WebSocket (subset of the DSH stream protocol). */
2
- export type ClientFrame = {
3
- type: 'open';
4
- streamId: string;
5
- target: string;
6
- } | {
7
- type: 'cancel';
8
- streamId: string;
9
- };
10
- export type ServerFrame = {
11
- type: 'item';
12
- streamId: string;
13
- value: unknown;
14
- } | {
15
- type: 'end';
16
- streamId: string;
17
- } | {
18
- type: 'error';
19
- streamId: string;
20
- message: string;
21
- };
22
- /** Uplink frame size limit; oversized frames error their stream without closing the socket. */
23
- export declare const MAX_FRAME_BYTES = 262144;
24
- /**
25
- * Parses one uplink mux frame. Malformed JSON, unknown types, an empty streamId,
26
- * or an `open` without a target produce `{ error }` instead of throwing.
27
- */
28
- export declare function parseClientFrame(raw: string): ClientFrame | {
29
- error: string;
30
- };
31
- /** Addresses `bccli serve` is allowed to bind (loopback only; 0.0.0.0 is refused). */
32
- export declare const LOOPBACK_BIND: Set<string>;
33
- /**
34
- * DSH-style trust fence: loopback Host with a port matching the listener, a
35
- * same-machine Origin when attached, and no cross-site sec-fetch-site. Returns
36
- * null when trusted, else the 403 reason. Never establishes identity — the
37
- * bearer token does.
38
- */
39
- export declare function trustFence(headers: {
40
- host?: string;
41
- origin?: string;
42
- 'sec-fetch-site'?: string;
43
- }, port: number): string | null;
44
- /** Timing-safe `Authorization: Bearer <token>` check. */
45
- export declare function bearerOk(header: string | undefined, token: string): boolean;
46
- /** Fresh 32-byte hex serve token. */
47
- export declare function randomToken(): string;
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- Session,
4
- pruneSessions
5
- } from "./chunk-6LO6YRDY.js";
6
- import "./chunk-OCEQ2TRJ.js";
7
- import "./chunk-NGFWBVHD.js";
8
- import "./chunk-IDZLHEVL.js";
9
- export {
10
- Session,
11
- pruneSessions
12
- };
package/dist/session.d.ts DELETED
@@ -1,33 +0,0 @@
1
- import { type ChatMessage, type Usage } from './provider';
2
- export declare class Session {
3
- readonly file: string;
4
- constructor(file: string);
5
- get id(): string;
6
- static create(home: string, cwd: string, now?: Date): Session;
7
- static list(home: string, cwd: string): {
8
- session: Session;
9
- mtime: Date;
10
- preview: string;
11
- }[];
12
- static latest(home: string, cwd: string): Session | undefined;
13
- private write;
14
- append(message: ChatMessage): void;
15
- appendUsage(usage: Usage & {
16
- model?: string;
17
- }): void;
18
- reset(): void;
19
- private records;
20
- /** What the model continues from: everything after the last reset (after a compaction that is the summary and the recent turns). */
21
- load(): ChatMessage[];
22
- /** Model of the most recent turn that recorded usage — serve attach continues a session with its own model. */
23
- lastModel(): string | undefined;
24
- /** Accumulated token usage since the last non-compaction reset. */
25
- totalUsage(): Usage;
26
- /**
27
- * What the user sees on resume: the whole conversation. Compaction only shrinks what the model is sent, so
28
- * its reset does not hide the older turns here. A real reset (/clear, rewind) still starts over, the
29
- * synthetic summary pair is never shown, and the recent turns a compaction re-writes are not shown twice.
30
- */
31
- loadHistory(): ChatMessage[];
32
- }
33
- export declare function pruneSessions(home: string, now?: number, maxAgeDays?: number): number;