@botconnector/bccli 0.4.1 → 0.4.3
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/dist/{acp-3PHBAI64.js → acp-NU4HZHQO.js} +30 -6
- package/dist/acp.d.ts +1 -0
- package/dist/agent.d.ts +5 -1
- package/dist/args-NRTZWWEV.js +13 -0
- package/dist/args.d.ts +2 -0
- package/dist/checkpoints.d.ts +1 -0
- package/dist/{chunk-MFHCQQEN.js → chunk-3I5BW3ZT.js} +3 -2
- package/dist/{chunk-UZWJQF67.js → chunk-3WHI4LN3.js} +2 -1
- package/dist/{chunk-BGXNR74O.js → chunk-4W5SKQDC.js} +152 -31
- package/dist/{chunk-BC7UXFE4.js → chunk-6AUROP2E.js} +64 -3
- package/dist/{chunk-24JN75FZ.js → chunk-6M55UPES.js} +2 -2
- package/dist/{chunk-XV7ZVL4J.js → chunk-6UO5GX55.js} +26 -12
- package/dist/{chunk-75BECX4F.js → chunk-7I2ZGPUS.js} +4 -4
- package/dist/{chunk-BKGFA2PT.js → chunk-FUJGHCWN.js} +1 -1
- package/dist/{chunk-GN4W3T6E.js → chunk-GWVLPN4B.js} +4 -4
- package/dist/{chunk-JHPGOAXU.js → chunk-HQW4BROS.js} +4 -5
- package/dist/{chunk-45SDLMTV.js → chunk-JXM6REER.js} +1 -1
- package/dist/{chunk-BLOYFIAD.js → chunk-KPTML72Q.js} +1 -1
- package/dist/{chunk-WI22M2ID.js → chunk-NQOGVP3I.js} +2 -2
- package/dist/{chunk-QZKLZELZ.js → chunk-OK2USAXX.js} +28 -4
- package/dist/{chunk-PZRKU4UM.js → chunk-QNUJHKEL.js} +659 -162
- package/dist/{chunk-FLQPE4S5.js → chunk-RX4UQX43.js} +4 -1
- package/dist/{chunk-R5RE46CW.js → chunk-WDX62DFH.js} +40 -6
- package/dist/{chunk-MHIYLXJC.js → chunk-WFZKJY3R.js} +8 -4
- package/dist/{chunk-WNACAUDK.js → chunk-XTOATEI6.js} +66 -9
- package/dist/{chunk-BXQQCUAC.js → chunk-Z5NI5FIK.js} +7 -7
- package/dist/cli.js +60 -43
- package/dist/{completion-HPPFDAPB.js → completion-D2BSBTQO.js} +1 -1
- package/dist/{config-DIDLDONU.js → config-ENDRMOTP.js} +3 -3
- package/dist/{config-IU76CPK2.js → config-LAINORU5.js} +2 -2
- package/dist/config.d.ts +2 -0
- package/dist/{ghBotCli-E3VPHNZY.js → ghBotCli-GCIUUMKQ.js} +21 -20
- package/dist/{integrationCli-Q2YPDTPG.js → integrationCli-HXCSEWH2.js} +2 -2
- package/dist/{login-FP3BB44A.js → login-YXSSB2HQ.js} +4 -4
- package/dist/lsp.d.ts +26 -0
- package/dist/mcp/manager.d.ts +18 -0
- package/dist/{mcpCli-SNBDUWJN.js → mcpCli-SETTCL3K.js} +6 -6
- package/dist/{modelsCli-CZ2HNGKR.js → modelsCli-656AKYZD.js} +6 -6
- package/dist/{oauthLogin-6FWQABJ7.js → oauthLogin-XWY7OHCG.js} +1 -1
- package/dist/{pluginsCli-LPIUUDWB.js → pluginsCli-ZQNKWZHJ.js} +2 -2
- package/dist/{prCli-5IMV3VNQ.js → prCli-6PUWTMTP.js} +20 -4
- package/dist/prCli.d.ts +13 -0
- package/dist/print.d.ts +2 -0
- package/dist/provider.d.ts +5 -0
- package/dist/providerCli-V2576JXE.js +15 -0
- package/dist/sdk.d.ts +5 -1
- package/dist/sdk.js +1003 -230
- package/dist/{serve-KWOECK5W.js → serve-K5AH4MGO.js} +3 -3
- package/dist/{serve-X2ZV4S5U.js → serve-XPAGXIGS.js} +17 -17
- package/dist/session-V2VCAF7E.js +12 -0
- package/dist/session.d.ts +3 -0
- package/dist/setup-HVVHOIL4.js +24 -0
- package/dist/slash/info.d.ts +1 -0
- package/dist/{statsCli-CF3R7ATL.js → statsCli-XR6BEZQQ.js} +2 -2
- package/dist/tokenBudget.d.ts +1 -1
- package/dist/tools/diagnostics.d.ts +8 -0
- package/dist/tools/edit.d.ts +7 -2
- package/dist/tools/grep.d.ts +14 -2
- package/dist/tools/ignore.d.ts +2 -2
- package/dist/tools/paths.d.ts +2 -0
- package/dist/tools/sandbox.d.ts +3 -0
- package/dist/{transferCli-YUFPI6AH.js → transferCli-C6XS6QYH.js} +8 -6
- package/dist/ui/complete.d.ts +5 -0
- package/dist/ui/vim.d.ts +1 -1
- package/dist/{ui-IVIFNUCC.js → ui-43C6Y57K.js} +199 -54
- package/dist/{update-TZ2G6KQ3.js → update-AVZO4XID.js} +2 -2
- package/package.json +1 -1
- package/dist/args-W6BNKY7K.js +0 -13
- package/dist/providerCli-NMUFXAAS.js +0 -15
- package/dist/session-GKEXDJKZ.js +0 -12
- package/dist/setup-OV3NWQN5.js +0 -24
|
@@ -36,6 +36,9 @@ function runAcp(input, output, opts) {
|
|
|
36
36
|
const replyError = (id, code, message) => write({ jsonrpc: "2.0", id, error: { code, message } });
|
|
37
37
|
const update = (sessionId, body) => write({ jsonrpc: "2.0", method: "session/update", params: { sessionId, update: body } });
|
|
38
38
|
const ioFor = (sid, session) => ({
|
|
39
|
+
onThinking(delta) {
|
|
40
|
+
update(sid, { sessionUpdate: "agent_thought_chunk", content: { type: "text", text: delta } });
|
|
41
|
+
},
|
|
39
42
|
toolStart(id, tool, target) {
|
|
40
43
|
session.open.push({ id, tool, target });
|
|
41
44
|
update(sid, { sessionUpdate: "tool_call", toolCallId: id, title: titleOf(tool, target), kind: kindOf(tool), status: "in_progress", rawInput: { target } });
|
|
@@ -89,7 +92,7 @@ function runAcp(input, output, opts) {
|
|
|
89
92
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
90
93
|
agentCapabilities: {
|
|
91
94
|
loadSession: false,
|
|
92
|
-
promptCapabilities: { image: false, audio: false, embeddedContext:
|
|
95
|
+
promptCapabilities: { image: false, audio: false, embeddedContext: true }
|
|
93
96
|
}
|
|
94
97
|
});
|
|
95
98
|
}
|
|
@@ -108,7 +111,17 @@ function runAcp(input, output, opts) {
|
|
|
108
111
|
const sid = params?.sessionId ?? "";
|
|
109
112
|
const session = sessions.get(sid);
|
|
110
113
|
if (!session) return replyError(id, -32602, "Unknown session");
|
|
111
|
-
|
|
114
|
+
if (session.controller) return replyError(id, -32e3, "Session is busy");
|
|
115
|
+
const text = (params?.prompt ?? []).map((p) => {
|
|
116
|
+
if (p?.type === "text") return String(p.text ?? "");
|
|
117
|
+
if ((p?.type === "resource" || p?.type === "embeddedContext") && p.resource?.text) {
|
|
118
|
+
const label = p.resource.uri ? `
|
|
119
|
+
[${p.resource.uri}]
|
|
120
|
+
` : "\n";
|
|
121
|
+
return `${label}${p.resource.text}`;
|
|
122
|
+
}
|
|
123
|
+
return "";
|
|
124
|
+
}).filter(Boolean).join("\n");
|
|
112
125
|
const controller = new AbortController();
|
|
113
126
|
session.controller = controller;
|
|
114
127
|
let cancelled = false;
|
|
@@ -122,8 +135,10 @@ function runAcp(input, output, opts) {
|
|
|
122
135
|
if (cancelled) return reply(id, { stopReason: "cancelled" });
|
|
123
136
|
return replyError(id, -32603, e.message);
|
|
124
137
|
} finally {
|
|
125
|
-
session.controller
|
|
126
|
-
|
|
138
|
+
if (session.controller === controller) {
|
|
139
|
+
session.controller = null;
|
|
140
|
+
session.open = [];
|
|
141
|
+
}
|
|
127
142
|
}
|
|
128
143
|
return reply(id, { stopReason: cancelled ? "cancelled" : "end_turn" });
|
|
129
144
|
}
|
|
@@ -150,6 +165,10 @@ function runAcp(input, output, opts) {
|
|
|
150
165
|
};
|
|
151
166
|
const onEnd = () => {
|
|
152
167
|
input.removeListener("data", onData);
|
|
168
|
+
for (const session of sessions.values()) {
|
|
169
|
+
session.controller?.abort();
|
|
170
|
+
for (const settle of [...session.pending]) settle("no");
|
|
171
|
+
}
|
|
153
172
|
resolve();
|
|
154
173
|
};
|
|
155
174
|
input.on("data", onData);
|
|
@@ -161,17 +180,22 @@ function runtimeAgent(cwd, allowAll = false) {
|
|
|
161
180
|
return {
|
|
162
181
|
async run(text, onChunk, signal, io) {
|
|
163
182
|
if (!rt) {
|
|
164
|
-
const { parseCliArgs } = await import("./args-
|
|
165
|
-
const { createRuntime } = await import("./setup-
|
|
183
|
+
const { parseCliArgs } = await import("./args-NRTZWWEV.js");
|
|
184
|
+
const { createRuntime } = await import("./setup-HVVHOIL4.js");
|
|
166
185
|
rt = createRuntime({ cwd, args: { ...parseCliArgs([]), allowAll } });
|
|
167
186
|
}
|
|
168
187
|
if (io && !allowAll) rt.agent.askPermission = (req) => io.askPermission(req);
|
|
188
|
+
let turnError;
|
|
169
189
|
rt.agent.onEvent = (event) => {
|
|
170
190
|
if (event.type === "text") onChunk(event.delta);
|
|
191
|
+
else if (event.type === "thinking") io?.onThinking?.(event.delta);
|
|
171
192
|
else if (event.type === "toolStart") io?.toolStart(event.id, event.tool, event.target);
|
|
172
193
|
else if (event.type === "toolEnd") io?.toolEnd(event.id, event.output, event.isError);
|
|
194
|
+
else if (event.type === "error") turnError = event.message;
|
|
195
|
+
else if (event.type === "budgetExceeded") turnError = `Budget exceeded (${event.kind})`;
|
|
173
196
|
};
|
|
174
197
|
await rt.agent.run(text, signal);
|
|
198
|
+
if (turnError) throw new Error(turnError);
|
|
175
199
|
}
|
|
176
200
|
};
|
|
177
201
|
}
|
package/dist/acp.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface AcpIo {
|
|
|
5
5
|
toolStart(id: string, tool: string, target: string): void;
|
|
6
6
|
toolEnd(id: string, output: string, isError: boolean): void;
|
|
7
7
|
askPermission(req: PermissionAsk): Promise<PermissionAnswer>;
|
|
8
|
+
onThinking?(delta: string): void;
|
|
8
9
|
}
|
|
9
10
|
/** The minimum an ACP client needs: run one prompt, stream text chunks, allow cancellation. */
|
|
10
11
|
export interface AcpAgent {
|
package/dist/agent.d.ts
CHANGED
|
@@ -109,14 +109,18 @@ export declare class Agent {
|
|
|
109
109
|
private definitions;
|
|
110
110
|
private turnTools;
|
|
111
111
|
private turnDefinitions;
|
|
112
|
-
|
|
112
|
+
readonly readFiles: Set<string>;
|
|
113
113
|
private readonly webBudget;
|
|
114
114
|
private readonly maxSteps?;
|
|
115
115
|
private contextWindow;
|
|
116
|
+
private budgetModel;
|
|
116
117
|
private systemPrompt;
|
|
117
118
|
constructor(opts: AgentOptions);
|
|
118
119
|
setSystemPrompt(text: string): void;
|
|
119
120
|
setContextWindow(window: number): void;
|
|
121
|
+
getContextWindow(): number;
|
|
122
|
+
setBudgetModel(model: string): void;
|
|
123
|
+
getBudgetModel(): string;
|
|
120
124
|
setTools(tools: Tool[]): void;
|
|
121
125
|
private isParallelSafe;
|
|
122
126
|
private push;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
helpText,
|
|
4
|
+
parseCliArgs
|
|
5
|
+
} from "./chunk-6UO5GX55.js";
|
|
6
|
+
import "./chunk-WFZKJY3R.js";
|
|
7
|
+
import "./chunk-KPTML72Q.js";
|
|
8
|
+
import "./chunk-RX4UQX43.js";
|
|
9
|
+
import "./chunk-3WHI4LN3.js";
|
|
10
|
+
export {
|
|
11
|
+
helpText,
|
|
12
|
+
parseCliArgs
|
|
13
|
+
};
|
package/dist/args.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export interface CliArgs {
|
|
|
45
45
|
project?: string;
|
|
46
46
|
/** `bccli pr` — explicit owner/name repo when cwd is not a git checkout of the PR. */
|
|
47
47
|
repo?: string;
|
|
48
|
+
/** Automatic fallback model ref when the primary model fails with 429 or 5xx. */
|
|
49
|
+
fallbackModel?: string;
|
|
48
50
|
}
|
|
49
51
|
/** Help text in the current language (call it after the language is set). */
|
|
50
52
|
export declare const helpText: () => string;
|
package/dist/checkpoints.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare class CheckpointStore {
|
|
|
8
8
|
private readonly history;
|
|
9
9
|
private current;
|
|
10
10
|
private pendingRedo;
|
|
11
|
+
private offset;
|
|
11
12
|
beginTurn(): void;
|
|
12
13
|
/** Call before writing a file; only the first snapshot per file per turn is kept. */
|
|
13
14
|
snapshot(path: string): Promise<void>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
quarantineCorruptFile,
|
|
4
4
|
writeFileAtomic
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-RX4UQX43.js";
|
|
6
6
|
|
|
7
7
|
// src/mcp/oauth.ts
|
|
8
8
|
import { spawn } from "child_process";
|
|
@@ -156,8 +156,9 @@ async function authorizeMcpServer(spec, deps) {
|
|
|
156
156
|
res.end("<html><body><p>BCCLI is connected. You can close this tab.</p></body></html>");
|
|
157
157
|
settle.resolve(code);
|
|
158
158
|
} catch (error) {
|
|
159
|
+
const escaped = error.message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
159
160
|
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
160
|
-
res.end(`<html><body><p>${
|
|
161
|
+
res.end(`<html><body><p>${escaped}</p></body></html>`);
|
|
161
162
|
settle.reject(error);
|
|
162
163
|
}
|
|
163
164
|
});
|
|
@@ -458,7 +458,8 @@ Opsi:
|
|
|
458
458
|
"gh is not available ({error}); install GitHub CLI first": "gh tidak tersedia ({error}); pasang GitHub CLI dulu",
|
|
459
459
|
"gh pr checkout {n} failed (exit {code})": "gh pr checkout {n} gagal (exit {code})",
|
|
460
460
|
"checked out PR #{n}": "checkout PR #{n} selesai",
|
|
461
|
-
"--allow-all: the PR task runs every tool without asking": "--allow-all: tugas PR menjalankan semua alat tanpa bertanya"
|
|
461
|
+
"--allow-all: the PR task runs every tool without asking": "--allow-all: tugas PR menjalankan semua alat tanpa bertanya",
|
|
462
|
+
"view or change configuration settings": "lihat atau ubah pengaturan konfigurasi"
|
|
462
463
|
};
|
|
463
464
|
|
|
464
465
|
// src/i18n.ts
|
|
@@ -4,14 +4,21 @@ import {
|
|
|
4
4
|
} from "./chunk-PSO7R5AD.js";
|
|
5
5
|
import {
|
|
6
6
|
t
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-3WHI4LN3.js";
|
|
8
8
|
|
|
9
9
|
// src/tools/bash.ts
|
|
10
10
|
import { spawn } from "child_process";
|
|
11
|
-
import { existsSync as existsSync2 } from "fs";
|
|
11
|
+
import { existsSync as existsSync2, writeFileSync } from "fs";
|
|
12
|
+
import { tmpdir } from "os";
|
|
13
|
+
import { join } from "path";
|
|
12
14
|
import { z } from "zod";
|
|
13
15
|
|
|
14
16
|
// src/tools/sandbox.ts
|
|
17
|
+
function winPathToWsl(winPath) {
|
|
18
|
+
const normalized = winPath.replace(/\\/g, "/");
|
|
19
|
+
const m = /^([A-Za-z]):\/(.*)$/.exec(normalized);
|
|
20
|
+
return m ? `/mnt/${m[1].toLowerCase()}/${m[2]}` : normalized;
|
|
21
|
+
}
|
|
15
22
|
function darwinProfile(cwd, network) {
|
|
16
23
|
const writable = [cwd, "/tmp", "/private/tmp", "/dev", "/var/folders"].map((p) => `(subpath "${p}")`).join(" ");
|
|
17
24
|
return [
|
|
@@ -55,6 +62,33 @@ function sandboxArgv(command, opts) {
|
|
|
55
62
|
command
|
|
56
63
|
];
|
|
57
64
|
}
|
|
65
|
+
if (opts.platform === "win32" && (opts.wslBwrap || process.env.BCCLI_WIN32_WSL_SANDBOX === "1")) {
|
|
66
|
+
const wslCwd = winPathToWsl(opts.cwd);
|
|
67
|
+
return [
|
|
68
|
+
"wsl.exe",
|
|
69
|
+
"--",
|
|
70
|
+
"bwrap",
|
|
71
|
+
"--ro-bind",
|
|
72
|
+
"/",
|
|
73
|
+
"/",
|
|
74
|
+
"--dev-bind",
|
|
75
|
+
"/dev",
|
|
76
|
+
"/dev",
|
|
77
|
+
"--proc",
|
|
78
|
+
"/proc",
|
|
79
|
+
"--bind",
|
|
80
|
+
wslCwd,
|
|
81
|
+
wslCwd,
|
|
82
|
+
"--tmpfs",
|
|
83
|
+
"/tmp",
|
|
84
|
+
"--die-with-parent",
|
|
85
|
+
...opts.network ? [] : ["--unshare-net"],
|
|
86
|
+
"--",
|
|
87
|
+
"/bin/bash",
|
|
88
|
+
"-c",
|
|
89
|
+
command
|
|
90
|
+
];
|
|
91
|
+
}
|
|
58
92
|
return void 0;
|
|
59
93
|
}
|
|
60
94
|
|
|
@@ -236,7 +270,7 @@ function offlineEnv(env = process.env) {
|
|
|
236
270
|
};
|
|
237
271
|
}
|
|
238
272
|
function runCommand(command, opts) {
|
|
239
|
-
return new Promise((
|
|
273
|
+
return new Promise((resolve3) => {
|
|
240
274
|
const child = spawnChild(command, opts.cwd, opts.env, opts.shell ?? defaultShell());
|
|
241
275
|
if (!exitHookInstalled) {
|
|
242
276
|
process.on("exit", killAllCommands);
|
|
@@ -249,7 +283,14 @@ function runCommand(command, opts) {
|
|
|
249
283
|
let dropped = 0;
|
|
250
284
|
let timedOut = false;
|
|
251
285
|
let aborted = false;
|
|
286
|
+
const fullChunks = [];
|
|
287
|
+
let fullBytes = 0;
|
|
288
|
+
const MAX_SPILL_BYTES = 10 * 1024 * 1024;
|
|
252
289
|
const onData = (data) => {
|
|
290
|
+
if (fullBytes < MAX_SPILL_BYTES) {
|
|
291
|
+
fullChunks.push(data);
|
|
292
|
+
fullBytes += data.length;
|
|
293
|
+
}
|
|
253
294
|
let s = data;
|
|
254
295
|
if (head.length < HALF) {
|
|
255
296
|
const take = s.slice(0, HALF - head.length);
|
|
@@ -288,10 +329,19 @@ function runCommand(command, opts) {
|
|
|
288
329
|
active.delete(killAsAborted);
|
|
289
330
|
clearTimeout(timer);
|
|
290
331
|
opts.signal.removeEventListener("abort", onAbort);
|
|
332
|
+
let spillNote = "";
|
|
333
|
+
if (dropped && fullChunks.length) {
|
|
334
|
+
try {
|
|
335
|
+
const spillPath = join(tmpdir(), `bccli-bash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.log`);
|
|
336
|
+
writeFileSync(spillPath, fullChunks.join(""), "utf8");
|
|
337
|
+
spillNote = `; full output saved to ${spillPath}`;
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
}
|
|
291
341
|
const output = dropped ? `${head}
|
|
292
|
-
\u2026 [${dropped} characters truncated] \u2026
|
|
342
|
+
\u2026 [${dropped} characters truncated${spillNote}] \u2026
|
|
293
343
|
${tail}` : head + tail;
|
|
294
|
-
|
|
344
|
+
resolve3({ output: output + extra, exitCode, timedOut, aborted });
|
|
295
345
|
};
|
|
296
346
|
child.on("error", (error) => finish(null, error.message));
|
|
297
347
|
child.on("close", (code) => finish(code));
|
|
@@ -403,7 +453,7 @@ var BUILTIN_AGENTS = [
|
|
|
403
453
|
|
|
404
454
|
// src/context.ts
|
|
405
455
|
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
406
|
-
import { dirname, join } from "path";
|
|
456
|
+
import { dirname, join as join2 } from "path";
|
|
407
457
|
var FILES = ["AGENTS.md", "CLAUDE.md", "BCCLI.md"];
|
|
408
458
|
function readIf(path) {
|
|
409
459
|
try {
|
|
@@ -418,7 +468,7 @@ function instructionPaths(cwd, home) {
|
|
|
418
468
|
dirs2.unshift(dir);
|
|
419
469
|
if (dirname(dir) === dir) break;
|
|
420
470
|
}
|
|
421
|
-
return [
|
|
471
|
+
return [join2(home, "BCCLI.md"), ...dirs2.flatMap((dir) => FILES.map((name) => join2(dir, name)))];
|
|
422
472
|
}
|
|
423
473
|
function loadInstructions(cwd, home) {
|
|
424
474
|
const parts = [];
|
|
@@ -431,7 +481,7 @@ ${text}`);
|
|
|
431
481
|
}
|
|
432
482
|
function isGitRepo(cwd) {
|
|
433
483
|
for (let dir = cwd; ; dir = dirname(dir)) {
|
|
434
|
-
if (existsSync3(
|
|
484
|
+
if (existsSync3(join2(dir, ".git"))) return true;
|
|
435
485
|
if (dirname(dir) === dir) return false;
|
|
436
486
|
}
|
|
437
487
|
}
|
|
@@ -476,15 +526,33 @@ ${instructions}` : ""}`;
|
|
|
476
526
|
|
|
477
527
|
// src/extensions.ts
|
|
478
528
|
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
479
|
-
import { basename, join as
|
|
529
|
+
import { basename, join as join3 } from "path";
|
|
480
530
|
function parseFrontmatter(text) {
|
|
481
531
|
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
482
532
|
if (!match) return { data: {}, body: text };
|
|
483
533
|
const data = {};
|
|
534
|
+
let currentListKey;
|
|
484
535
|
for (const line of match[1].split(/\r?\n/)) {
|
|
536
|
+
const listItem = /^\s*-\s+(.+)$/.exec(line);
|
|
537
|
+
if (listItem && currentListKey) {
|
|
538
|
+
const item = listItem[1].trim().replace(/^["']|["']$/g, "");
|
|
539
|
+
const prev = data[currentListKey];
|
|
540
|
+
if (Array.isArray(prev)) prev.push(item);
|
|
541
|
+
else data[currentListKey] = [item];
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
485
544
|
const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
486
|
-
if (!kv)
|
|
545
|
+
if (!kv) {
|
|
546
|
+
if (line.trim()) currentListKey = void 0;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
487
549
|
let value = kv[2].trim();
|
|
550
|
+
if (!value) {
|
|
551
|
+
currentListKey = kv[1];
|
|
552
|
+
data[kv[1]] = "";
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
currentListKey = void 0;
|
|
488
556
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
489
557
|
data[kv[1]] = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
490
558
|
continue;
|
|
@@ -496,7 +564,7 @@ function parseFrontmatter(text) {
|
|
|
496
564
|
}
|
|
497
565
|
var str = (v) => Array.isArray(v) ? v.join(", ") : v;
|
|
498
566
|
function dirs(r, kind) {
|
|
499
|
-
return [
|
|
567
|
+
return [join3(r.userHome, ".claude", kind), join3(r.home, kind), join3(r.cwd, ".claude", kind), join3(r.cwd, ".bccli", kind)];
|
|
500
568
|
}
|
|
501
569
|
function list(dir) {
|
|
502
570
|
try {
|
|
@@ -509,11 +577,11 @@ function loadSkills(r) {
|
|
|
509
577
|
const byName = /* @__PURE__ */ new Map();
|
|
510
578
|
for (const dir of dirs(r, "skills")) {
|
|
511
579
|
for (const entry of list(dir)) {
|
|
512
|
-
const file =
|
|
580
|
+
const file = join3(dir, entry, "SKILL.md");
|
|
513
581
|
if (!existsSync4(file)) continue;
|
|
514
582
|
const { data } = parseFrontmatter(readFileSync2(file, "utf8"));
|
|
515
583
|
const name = str(data.name) || entry;
|
|
516
|
-
byName.set(name, { name, description: str(data.description) ?? "", dir:
|
|
584
|
+
byName.set(name, { name, description: str(data.description) ?? "", dir: join3(dir, entry), file });
|
|
517
585
|
}
|
|
518
586
|
}
|
|
519
587
|
return [...byName.values()];
|
|
@@ -522,7 +590,7 @@ function loadCommands(r) {
|
|
|
522
590
|
const byName = /* @__PURE__ */ new Map();
|
|
523
591
|
for (const dir of dirs(r, "commands")) {
|
|
524
592
|
for (const entry of list(dir)) {
|
|
525
|
-
const file =
|
|
593
|
+
const file = join3(dir, entry);
|
|
526
594
|
if (!entry.endsWith(".md") || !statSync(file).isFile()) continue;
|
|
527
595
|
const { data, body } = parseFrontmatter(readFileSync2(file, "utf8"));
|
|
528
596
|
const def = { name: basename(entry, ".md"), body: body.trim() };
|
|
@@ -538,7 +606,7 @@ function loadAgentDefs(r) {
|
|
|
538
606
|
for (const dir of dirs(r, "agents")) {
|
|
539
607
|
for (const entry of list(dir)) {
|
|
540
608
|
if (!entry.endsWith(".md")) continue;
|
|
541
|
-
const { data, body } = parseFrontmatter(readFileSync2(
|
|
609
|
+
const { data, body } = parseFrontmatter(readFileSync2(join3(dir, entry), "utf8"));
|
|
542
610
|
const name = str(data.name) || basename(entry, ".md");
|
|
543
611
|
const def = { name, description: str(data.description) ?? "", prompt: body.trim() };
|
|
544
612
|
if (data.tools)
|
|
@@ -573,7 +641,7 @@ function runOne(command, event, payload, opts) {
|
|
|
573
641
|
...payload.input !== void 0 ? { BCCLI_HOOK_INPUT: JSON.stringify(payload.input) } : {},
|
|
574
642
|
...payload.output !== void 0 ? { BCCLI_HOOK_OUTPUT: payload.output } : {}
|
|
575
643
|
};
|
|
576
|
-
return new Promise((
|
|
644
|
+
return new Promise((resolve3) => {
|
|
577
645
|
let stdout = "";
|
|
578
646
|
let stderr = "";
|
|
579
647
|
let timedOut = false;
|
|
@@ -582,7 +650,7 @@ function runOne(command, event, payload, opts) {
|
|
|
582
650
|
if (settled) return;
|
|
583
651
|
settled = true;
|
|
584
652
|
clearTimeout(timer);
|
|
585
|
-
|
|
653
|
+
resolve3(result);
|
|
586
654
|
};
|
|
587
655
|
let child;
|
|
588
656
|
const shell = defaultShell();
|
|
@@ -598,7 +666,7 @@ function runOne(command, event, payload, opts) {
|
|
|
598
666
|
child = spawn2(command, { shell: true, env, cwd: opts.cwd ?? process.cwd(), stdio: ["ignore", "pipe", "pipe"] });
|
|
599
667
|
}
|
|
600
668
|
} catch (error) {
|
|
601
|
-
|
|
669
|
+
resolve3({ failure: error.message });
|
|
602
670
|
return;
|
|
603
671
|
}
|
|
604
672
|
const timer = setTimeout(() => {
|
|
@@ -648,6 +716,38 @@ async function runHooks(hooks, event, payload = {}, opts = {}) {
|
|
|
648
716
|
return { blocked, warnings };
|
|
649
717
|
}
|
|
650
718
|
|
|
719
|
+
// src/tools/paths.ts
|
|
720
|
+
import { statSync as statSync2 } from "fs";
|
|
721
|
+
import { isAbsolute, resolve } from "path";
|
|
722
|
+
var readMtimes = /* @__PURE__ */ new WeakMap();
|
|
723
|
+
function resolvePath(cwd, path) {
|
|
724
|
+
return isAbsolute(path) ? path : resolve(cwd, path);
|
|
725
|
+
}
|
|
726
|
+
function errorCode(error) {
|
|
727
|
+
return error.code ?? error.message;
|
|
728
|
+
}
|
|
729
|
+
function recordFileMtime(readFiles, abs) {
|
|
730
|
+
readFiles.add(abs);
|
|
731
|
+
try {
|
|
732
|
+
let map = readMtimes.get(readFiles);
|
|
733
|
+
if (!map) {
|
|
734
|
+
map = /* @__PURE__ */ new Map();
|
|
735
|
+
readMtimes.set(readFiles, map);
|
|
736
|
+
}
|
|
737
|
+
map.set(abs, statSync2(abs).mtimeMs);
|
|
738
|
+
} catch {
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function isFileModifiedSinceRead(readFiles, abs) {
|
|
742
|
+
const recorded = readMtimes.get(readFiles)?.get(abs);
|
|
743
|
+
if (recorded === void 0) return false;
|
|
744
|
+
try {
|
|
745
|
+
return statSync2(abs).mtimeMs > recorded + 50;
|
|
746
|
+
} catch {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
651
751
|
// src/slash/memory.ts
|
|
652
752
|
import { appendFileSync, existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
653
753
|
function instructionFiles(cwd, home) {
|
|
@@ -665,22 +765,38 @@ function parseMemoryArgs(args) {
|
|
|
665
765
|
|
|
666
766
|
// src/tools/ignore.ts
|
|
667
767
|
import { readFileSync as readFileSync4 } from "fs";
|
|
668
|
-
import { join as
|
|
669
|
-
function ignorePatterns(root) {
|
|
768
|
+
import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
|
|
769
|
+
function ignorePatterns(root, projectRoot) {
|
|
670
770
|
const patterns = ["**/node_modules/**", "**/.git/**"];
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
771
|
+
const seen = /* @__PURE__ */ new Set();
|
|
772
|
+
const dirs2 = [];
|
|
773
|
+
let cur = resolve2(root);
|
|
774
|
+
const stop = projectRoot ? resolve2(projectRoot) : cur;
|
|
775
|
+
while (true) {
|
|
776
|
+
if (!seen.has(cur)) {
|
|
777
|
+
seen.add(cur);
|
|
778
|
+
dirs2.push(cur);
|
|
779
|
+
}
|
|
780
|
+
if (cur === stop || !cur.startsWith(stop)) break;
|
|
781
|
+
const parent = dirname2(cur);
|
|
782
|
+
if (parent === cur) break;
|
|
783
|
+
cur = parent;
|
|
676
784
|
}
|
|
677
|
-
for (const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
785
|
+
for (const dir of dirs2) {
|
|
786
|
+
let lines = [];
|
|
787
|
+
try {
|
|
788
|
+
lines = readFileSync4(join4(dir, ".gitignore"), "utf8").split(/\r?\n/);
|
|
789
|
+
} catch {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
for (const raw of lines) {
|
|
793
|
+
const line = raw.trim();
|
|
794
|
+
if (!line || line.startsWith("#") || line.startsWith("!")) continue;
|
|
795
|
+
const p = line.replace(/^\//, "").replace(/\/$/, "");
|
|
796
|
+
patterns.push(`**/${p}`, `**/${p}/**`);
|
|
797
|
+
}
|
|
682
798
|
}
|
|
683
|
-
return patterns;
|
|
799
|
+
return [...new Set(patterns)];
|
|
684
800
|
}
|
|
685
801
|
|
|
686
802
|
// src/tools/globSafety.ts
|
|
@@ -713,11 +829,16 @@ function normalizeGlobPath(path) {
|
|
|
713
829
|
}
|
|
714
830
|
|
|
715
831
|
export {
|
|
832
|
+
resolveShell,
|
|
716
833
|
runHooks,
|
|
717
834
|
renderBackgroundTasks,
|
|
718
835
|
runCommand,
|
|
719
836
|
createBashTool,
|
|
720
837
|
bashTool,
|
|
838
|
+
resolvePath,
|
|
839
|
+
errorCode,
|
|
840
|
+
recordFileMtime,
|
|
841
|
+
isFileModifiedSinceRead,
|
|
721
842
|
ignorePatterns,
|
|
722
843
|
validateGlobPattern,
|
|
723
844
|
normalizeGlobPath,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
contentText
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XTOATEI6.js";
|
|
5
5
|
|
|
6
6
|
// src/session.ts
|
|
7
7
|
import { randomBytes } from "crypto";
|
|
8
8
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "fs";
|
|
9
|
-
import { join } from "path";
|
|
9
|
+
import { basename, join } from "path";
|
|
10
10
|
function projectDir(home, cwd) {
|
|
11
11
|
const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(-100) || "root";
|
|
12
12
|
return join(home, "sessions", slug);
|
|
@@ -27,6 +27,9 @@ var Session = class _Session {
|
|
|
27
27
|
this.file = file;
|
|
28
28
|
}
|
|
29
29
|
file;
|
|
30
|
+
get id() {
|
|
31
|
+
return basename(this.file, ".jsonl");
|
|
32
|
+
}
|
|
30
33
|
static create(home, cwd, now = /* @__PURE__ */ new Date()) {
|
|
31
34
|
const dir = projectDir(home, cwd);
|
|
32
35
|
mkdirSync(dir, { recursive: true });
|
|
@@ -38,7 +41,7 @@ var Session = class _Session {
|
|
|
38
41
|
if (!existsSync(dir)) return [];
|
|
39
42
|
return readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
40
43
|
const session = new _Session(join(dir, f));
|
|
41
|
-
const first = session.
|
|
44
|
+
const first = session.loadHistory().find((m) => m.role === "user");
|
|
42
45
|
return { session, mtime: statSync(session.file).mtime, preview: first ? contentText(first.content).slice(0, 80) : "" };
|
|
43
46
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime() || b.session.file.localeCompare(a.session.file));
|
|
44
47
|
}
|
|
@@ -88,6 +91,35 @@ var Session = class _Session {
|
|
|
88
91
|
}
|
|
89
92
|
return void 0;
|
|
90
93
|
}
|
|
94
|
+
/** Accumulated token usage since the last non-compaction reset. */
|
|
95
|
+
totalUsage() {
|
|
96
|
+
const segments = [{ msgs: [], usage: { inputTokens: 0, outputTokens: 0 } }];
|
|
97
|
+
for (const record of this.records()) {
|
|
98
|
+
const cur = segments[segments.length - 1];
|
|
99
|
+
if (record.t === "reset") {
|
|
100
|
+
segments.push({ msgs: [], usage: { inputTokens: 0, outputTokens: 0 } });
|
|
101
|
+
} else if (record.t === "msg") {
|
|
102
|
+
cur.msgs.push(record.m);
|
|
103
|
+
} else if (record.t === "usage") {
|
|
104
|
+
cur.usage.inputTokens += record.inputTokens;
|
|
105
|
+
cur.usage.outputTokens += record.outputTokens;
|
|
106
|
+
if (record.cachedInputTokens) cur.usage.cachedInputTokens = (cur.usage.cachedInputTokens ?? 0) + record.cachedInputTokens;
|
|
107
|
+
if (record.cacheWriteTokens) cur.usage.cacheWriteTokens = (cur.usage.cacheWriteTokens ?? 0) + record.cacheWriteTokens;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
let total = { ...segments[0].usage };
|
|
111
|
+
for (const seg of segments.slice(1)) {
|
|
112
|
+
if (!isCompactionSummary(seg.msgs[0])) {
|
|
113
|
+
total = { ...seg.usage };
|
|
114
|
+
} else {
|
|
115
|
+
total.inputTokens += seg.usage.inputTokens;
|
|
116
|
+
total.outputTokens += seg.usage.outputTokens;
|
|
117
|
+
if (seg.usage.cachedInputTokens) total.cachedInputTokens = (total.cachedInputTokens ?? 0) + seg.usage.cachedInputTokens;
|
|
118
|
+
if (seg.usage.cacheWriteTokens) total.cacheWriteTokens = (total.cacheWriteTokens ?? 0) + seg.usage.cacheWriteTokens;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return total;
|
|
122
|
+
}
|
|
91
123
|
/**
|
|
92
124
|
* What the user sees on resume: the whole conversation. Compaction only shrinks what the model is sent, so
|
|
93
125
|
* its reset does not hide the older turns here. A real reset (/clear, rewind) still starts over, the
|
|
@@ -100,12 +132,22 @@ var Session = class _Session {
|
|
|
100
132
|
else if (record.t === "msg") segments[segments.length - 1].push(record.m);
|
|
101
133
|
}
|
|
102
134
|
let shown = segments[0];
|
|
135
|
+
let lastSummary;
|
|
103
136
|
for (const segment of segments.slice(1)) {
|
|
104
137
|
if (!isCompactionSummary(segment[0])) {
|
|
105
138
|
shown = segment;
|
|
139
|
+
lastSummary = void 0;
|
|
106
140
|
continue;
|
|
107
141
|
}
|
|
108
142
|
const kept = segment.slice(2);
|
|
143
|
+
if (lastSummary && sameMessage(segment[0], lastSummary)) {
|
|
144
|
+
const subIdx = findContiguousSlice(shown, kept);
|
|
145
|
+
if (subIdx !== -1) {
|
|
146
|
+
shown = shown.slice(0, subIdx + kept.length);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
lastSummary = segment[0];
|
|
109
151
|
let at = 0;
|
|
110
152
|
while (at <= shown.length && !endsWithPrefixOf(shown, at, kept)) at++;
|
|
111
153
|
shown = [...shown, ...kept.slice(shown.length - at)];
|
|
@@ -132,12 +174,31 @@ function endsWithPrefixOf(shown, at, kept) {
|
|
|
132
174
|
for (let j = 0; j < n; j++) if (!sameMessage(shown[at + j], kept[j])) return false;
|
|
133
175
|
return true;
|
|
134
176
|
}
|
|
177
|
+
function findContiguousSlice(shown, slice) {
|
|
178
|
+
if (slice.length === 0) return shown.length;
|
|
179
|
+
for (let i = 0; i + slice.length <= shown.length; i++) {
|
|
180
|
+
let ok = true;
|
|
181
|
+
for (let j = 0; j < slice.length; j++) {
|
|
182
|
+
if (!sameMessage(shown[i + j], slice[j])) {
|
|
183
|
+
ok = false;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (ok) return i;
|
|
188
|
+
}
|
|
189
|
+
return -1;
|
|
190
|
+
}
|
|
135
191
|
function pruneSessions(home, now = Date.now(), maxAgeDays = 30) {
|
|
136
192
|
const root = join(home, "sessions");
|
|
137
193
|
if (!existsSync(root)) return 0;
|
|
138
194
|
let removed = 0;
|
|
139
195
|
for (const project of readdirSync(root)) {
|
|
140
196
|
const dir = join(root, project);
|
|
197
|
+
try {
|
|
198
|
+
if (!statSync(dir).isDirectory()) continue;
|
|
199
|
+
} catch {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
141
202
|
for (const file of readdirSync(dir)) {
|
|
142
203
|
const path = join(dir, file);
|
|
143
204
|
if (now - statSync(path).mtimeMs > maxAgeDays * 864e5) {
|