@botconnector/bccli 0.4.1 → 0.4.2

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 (69) hide show
  1. package/dist/{acp-3PHBAI64.js → acp-UYC6APR5.js} +30 -6
  2. package/dist/acp.d.ts +1 -0
  3. package/dist/agent.d.ts +5 -1
  4. package/dist/args-NRTZWWEV.js +13 -0
  5. package/dist/args.d.ts +2 -0
  6. package/dist/checkpoints.d.ts +1 -0
  7. package/dist/{chunk-BKGFA2PT.js → chunk-266SY3MC.js} +1 -1
  8. package/dist/{chunk-MFHCQQEN.js → chunk-3I5BW3ZT.js} +3 -2
  9. package/dist/{chunk-UZWJQF67.js → chunk-3WHI4LN3.js} +2 -1
  10. package/dist/{chunk-WNACAUDK.js → chunk-5YJ3ZQVR.js} +66 -9
  11. package/dist/{chunk-24JN75FZ.js → chunk-6M55UPES.js} +2 -2
  12. package/dist/{chunk-XV7ZVL4J.js → chunk-6UO5GX55.js} +26 -12
  13. package/dist/{chunk-R5RE46CW.js → chunk-C64KM37J.js} +36 -6
  14. package/dist/{chunk-GN4W3T6E.js → chunk-H7XJDKJY.js} +4 -4
  15. package/dist/{chunk-JHPGOAXU.js → chunk-HQW4BROS.js} +4 -5
  16. package/dist/{chunk-75BECX4F.js → chunk-IYR5PYA3.js} +4 -4
  17. package/dist/{chunk-45SDLMTV.js → chunk-JXM6REER.js} +1 -1
  18. package/dist/{chunk-BLOYFIAD.js → chunk-KPTML72Q.js} +1 -1
  19. package/dist/{chunk-WI22M2ID.js → chunk-NQOGVP3I.js} +2 -2
  20. package/dist/{chunk-QZKLZELZ.js → chunk-OK2USAXX.js} +28 -4
  21. package/dist/{chunk-PZRKU4UM.js → chunk-R72OV7RF.js} +421 -108
  22. package/dist/{chunk-FLQPE4S5.js → chunk-RX4UQX43.js} +4 -1
  23. package/dist/{chunk-BC7UXFE4.js → chunk-SVD4LHJC.js} +64 -3
  24. package/dist/{chunk-BXQQCUAC.js → chunk-TIMSYZIR.js} +7 -7
  25. package/dist/{chunk-MHIYLXJC.js → chunk-WFZKJY3R.js} +8 -4
  26. package/dist/{chunk-BGXNR74O.js → chunk-ZEPTUKNW.js} +119 -31
  27. package/dist/cli.js +60 -43
  28. package/dist/{completion-HPPFDAPB.js → completion-D2BSBTQO.js} +1 -1
  29. package/dist/{config-DIDLDONU.js → config-ENDRMOTP.js} +3 -3
  30. package/dist/{config-IU76CPK2.js → config-LAINORU5.js} +2 -2
  31. package/dist/config.d.ts +2 -0
  32. package/dist/{ghBotCli-E3VPHNZY.js → ghBotCli-TSLRORJJ.js} +21 -20
  33. package/dist/{integrationCli-Q2YPDTPG.js → integrationCli-HXCSEWH2.js} +2 -2
  34. package/dist/{login-FP3BB44A.js → login-RYESFZG6.js} +4 -4
  35. package/dist/lsp.d.ts +3 -0
  36. package/dist/mcp/manager.d.ts +18 -0
  37. package/dist/{mcpCli-SNBDUWJN.js → mcpCli-SETTCL3K.js} +6 -6
  38. package/dist/{modelsCli-CZ2HNGKR.js → modelsCli-OP7CDZC7.js} +6 -6
  39. package/dist/{oauthLogin-6FWQABJ7.js → oauthLogin-XWY7OHCG.js} +1 -1
  40. package/dist/{pluginsCli-LPIUUDWB.js → pluginsCli-ZQNKWZHJ.js} +2 -2
  41. package/dist/{prCli-5IMV3VNQ.js → prCli-6PUWTMTP.js} +20 -4
  42. package/dist/prCli.d.ts +13 -0
  43. package/dist/print.d.ts +2 -0
  44. package/dist/provider.d.ts +5 -0
  45. package/dist/providerCli-5OZI2GQQ.js +15 -0
  46. package/dist/sdk.d.ts +5 -1
  47. package/dist/sdk.js +733 -176
  48. package/dist/{serve-X2ZV4S5U.js → serve-4J7HA4JO.js} +17 -17
  49. package/dist/{serve-KWOECK5W.js → serve-K5AH4MGO.js} +3 -3
  50. package/dist/session-YQXCCKXI.js +12 -0
  51. package/dist/session.d.ts +3 -0
  52. package/dist/setup-S4AJT6XE.js +24 -0
  53. package/dist/slash/info.d.ts +1 -0
  54. package/dist/{statsCli-CF3R7ATL.js → statsCli-XR6BEZQQ.js} +2 -2
  55. package/dist/tokenBudget.d.ts +1 -1
  56. package/dist/tools/edit.d.ts +7 -2
  57. package/dist/tools/grep.d.ts +14 -2
  58. package/dist/tools/ignore.d.ts +2 -2
  59. package/dist/tools/paths.d.ts +2 -0
  60. package/dist/{transferCli-YUFPI6AH.js → transferCli-R2KEIWQR.js} +8 -6
  61. package/dist/ui/complete.d.ts +5 -0
  62. package/dist/ui/vim.d.ts +1 -1
  63. package/dist/{ui-IVIFNUCC.js → ui-36ZEK65V.js} +177 -54
  64. package/dist/{update-TZ2G6KQ3.js → update-FSS7X5WV.js} +2 -2
  65. package/package.json +1 -1
  66. package/dist/args-W6BNKY7K.js +0 -13
  67. package/dist/providerCli-NMUFXAAS.js +0 -15
  68. package/dist/session-GKEXDJKZ.js +0 -12
  69. package/dist/setup-OV3NWQN5.js +0 -24
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  t
4
- } from "./chunk-UZWJQF67.js";
4
+ } from "./chunk-3WHI4LN3.js";
5
5
 
6
6
  // src/config.ts
7
7
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
@@ -236,8 +236,11 @@ function loadConfig(cwd, env = process.env) {
236
236
  const project = readJsonConfig(join(cwd, ".bccli", "config.json"));
237
237
  const known = { ...DEFAULT_CONFIG.providers, ...global.providers };
238
238
  const fromProject = untrustedProviders(project, known);
239
+ const rawFallbacks = project.fallbackModels ?? global.fallbackModels;
240
+ const fallbackModels = Array.isArray(rawFallbacks) ? rawFallbacks.filter((m) => typeof m === "string" && m.includes("/")) : void 0;
239
241
  return {
240
242
  model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
243
+ ...fallbackModels?.length ? { fallbackModels } : {},
241
244
  permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
242
245
  reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
243
246
  thinkingVisible: global.thinkingVisible === true,
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  contentText
4
- } from "./chunk-WNACAUDK.js";
4
+ } from "./chunk-5YJ3ZQVR.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.load().find((m) => m.role === "user");
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) {
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  defaultOpen
4
- } from "./chunk-MFHCQQEN.js";
4
+ } from "./chunk-3I5BW3ZT.js";
5
5
  import {
6
6
  removeCredential,
7
7
  saveCredential
8
- } from "./chunk-FLQPE4S5.js";
8
+ } from "./chunk-RX4UQX43.js";
9
9
  import {
10
10
  t
11
- } from "./chunk-UZWJQF67.js";
11
+ } from "./chunk-3WHI4LN3.js";
12
12
 
13
13
  // src/login.ts
14
14
  function readSecret(prompt) {
@@ -91,8 +91,8 @@ function defaultLoginDeps() {
91
91
  fetch: globalThis.fetch,
92
92
  // Wiring NYATA jalur lama: persis runLogin lama (provider add + prompt key).
93
93
  legacyAdd: async (providerId, targetCwd) => {
94
- const { runProviderCommand } = await import("./providerCli-NMUFXAAS.js");
95
- const { parseCliArgs: parse } = await import("./args-W6BNKY7K.js");
94
+ const { runProviderCommand } = await import("./providerCli-5OZI2GQQ.js");
95
+ const { parseCliArgs: parse } = await import("./args-NRTZWWEV.js");
96
96
  return runProviderCommand(parse(["provider", "add", providerId]), {
97
97
  env: process.env,
98
98
  cwd: targetCwd,
@@ -117,12 +117,12 @@ async function runLogin(providerId, cwd, args, deps) {
117
117
  );
118
118
  return 1;
119
119
  }
120
- const { runOauthLogout } = await import("./oauthLogin-6FWQABJ7.js");
120
+ const { runOauthLogout } = await import("./oauthLogin-XWY7OHCG.js");
121
121
  return runOauthLogout({ out: deps.out, err: deps.err, env: deps.env, removeCredential: deps.removeCredential });
122
122
  }
123
123
  if (args.key) return deps.legacyAdd(providerId, cwd);
124
124
  if (providerId === "openrouter") {
125
- const { runOauthLogin } = await import("./oauthLogin-6FWQABJ7.js");
125
+ const { runOauthLogin } = await import("./oauthLogin-XWY7OHCG.js");
126
126
  return runOauthLogin({
127
127
  out: deps.out,
128
128
  err: deps.err,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/permissions.ts
4
- import { isAbsolute, relative, resolve, sep } from "path";
4
+ import { isAbsolute, relative, resolve } from "path";
5
5
  var MODE_ORDER = ["default", "acceptEdits", "plan", "allowAll"];
6
6
  function nextMode(mode) {
7
7
  return MODE_ORDER[(MODE_ORDER.indexOf(mode) + 1) % MODE_ORDER.length];
@@ -62,7 +62,9 @@ function commandKey(segment) {
62
62
  function requiresSensitiveToolApproval(req) {
63
63
  const target = req.target.trim();
64
64
  if (req.kind === "read" || req.kind === "edit") {
65
- return /(?:^|[/\\])(?:\.env(?:\..*)?|\.ssh|\.aws|\.config\/gcloud|secrets?|credentials?)(?:[/\\]|$)/i.test(target) || /(?:id_rsa|id_ed25519|\.pem|\.p12|\.key)$/i.test(target);
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);
66
68
  }
67
69
  if (req.kind === "bash") {
68
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);
@@ -87,7 +89,8 @@ var Permissions = class {
87
89
  editInProject(target) {
88
90
  const rel = relative(this.cwd, resolve(this.cwd, target));
89
91
  if (!rel || rel.startsWith("..") || isAbsolute(rel)) return false;
90
- return !rel.split(sep).includes(".git") && !rel.split("/").includes(".git");
92
+ const parts = rel.split(/[/\\]/).map((p) => p.toLowerCase());
93
+ return !parts.includes(".git");
91
94
  }
92
95
  /** Rules that "[a] allow for this session" would add; undefined when it must always be asked. */
93
96
  rulesFor(req) {
@@ -105,7 +108,7 @@ var Permissions = class {
105
108
  if (/`|\$\(|[<>]|^\s*[({]/.test(req.target)) return void 0;
106
109
  const segments = req.target.split(/&&|\|\||[;&|\n]/).map((s) => s.trim()).filter(Boolean);
107
110
  if (!segments.length) return void 0;
108
- if (segments.some(isAlwaysAsk)) return void 0;
111
+ if (segments.some((s) => /^[({]/.test(s) || isAlwaysAsk(s))) return void 0;
109
112
  return [...new Set(segments.map((s) => `bash(${commandKey(s)})`))];
110
113
  }
111
114
  if (req.kind === "mcp") return [`mcp(${req.tool})`];
@@ -139,5 +142,6 @@ var Permissions = class {
139
142
  export {
140
143
  MODE_ORDER,
141
144
  nextMode,
145
+ requiresSensitiveToolApproval,
142
146
  Permissions
143
147
  };
@@ -4,11 +4,13 @@ import {
4
4
  } from "./chunk-PSO7R5AD.js";
5
5
  import {
6
6
  t
7
- } from "./chunk-UZWJQF67.js";
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
@@ -236,7 +238,7 @@ function offlineEnv(env = process.env) {
236
238
  };
237
239
  }
238
240
  function runCommand(command, opts) {
239
- return new Promise((resolve) => {
241
+ return new Promise((resolve3) => {
240
242
  const child = spawnChild(command, opts.cwd, opts.env, opts.shell ?? defaultShell());
241
243
  if (!exitHookInstalled) {
242
244
  process.on("exit", killAllCommands);
@@ -249,7 +251,14 @@ function runCommand(command, opts) {
249
251
  let dropped = 0;
250
252
  let timedOut = false;
251
253
  let aborted = false;
254
+ const fullChunks = [];
255
+ let fullBytes = 0;
256
+ const MAX_SPILL_BYTES = 10 * 1024 * 1024;
252
257
  const onData = (data) => {
258
+ if (fullBytes < MAX_SPILL_BYTES) {
259
+ fullChunks.push(data);
260
+ fullBytes += data.length;
261
+ }
253
262
  let s = data;
254
263
  if (head.length < HALF) {
255
264
  const take = s.slice(0, HALF - head.length);
@@ -288,10 +297,19 @@ function runCommand(command, opts) {
288
297
  active.delete(killAsAborted);
289
298
  clearTimeout(timer);
290
299
  opts.signal.removeEventListener("abort", onAbort);
300
+ let spillNote = "";
301
+ if (dropped && fullChunks.length) {
302
+ try {
303
+ const spillPath = join(tmpdir(), `bccli-bash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.log`);
304
+ writeFileSync(spillPath, fullChunks.join(""), "utf8");
305
+ spillNote = `; full output saved to ${spillPath}`;
306
+ } catch {
307
+ }
308
+ }
291
309
  const output = dropped ? `${head}
292
- \u2026 [${dropped} characters truncated] \u2026
310
+ \u2026 [${dropped} characters truncated${spillNote}] \u2026
293
311
  ${tail}` : head + tail;
294
- resolve({ output: output + extra, exitCode, timedOut, aborted });
312
+ resolve3({ output: output + extra, exitCode, timedOut, aborted });
295
313
  };
296
314
  child.on("error", (error) => finish(null, error.message));
297
315
  child.on("close", (code) => finish(code));
@@ -403,7 +421,7 @@ var BUILTIN_AGENTS = [
403
421
 
404
422
  // src/context.ts
405
423
  import { existsSync as existsSync3, readFileSync } from "fs";
406
- import { dirname, join } from "path";
424
+ import { dirname, join as join2 } from "path";
407
425
  var FILES = ["AGENTS.md", "CLAUDE.md", "BCCLI.md"];
408
426
  function readIf(path) {
409
427
  try {
@@ -418,7 +436,7 @@ function instructionPaths(cwd, home) {
418
436
  dirs2.unshift(dir);
419
437
  if (dirname(dir) === dir) break;
420
438
  }
421
- return [join(home, "BCCLI.md"), ...dirs2.flatMap((dir) => FILES.map((name) => join(dir, name)))];
439
+ return [join2(home, "BCCLI.md"), ...dirs2.flatMap((dir) => FILES.map((name) => join2(dir, name)))];
422
440
  }
423
441
  function loadInstructions(cwd, home) {
424
442
  const parts = [];
@@ -431,7 +449,7 @@ ${text}`);
431
449
  }
432
450
  function isGitRepo(cwd) {
433
451
  for (let dir = cwd; ; dir = dirname(dir)) {
434
- if (existsSync3(join(dir, ".git"))) return true;
452
+ if (existsSync3(join2(dir, ".git"))) return true;
435
453
  if (dirname(dir) === dir) return false;
436
454
  }
437
455
  }
@@ -476,15 +494,33 @@ ${instructions}` : ""}`;
476
494
 
477
495
  // src/extensions.ts
478
496
  import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
479
- import { basename, join as join2 } from "path";
497
+ import { basename, join as join3 } from "path";
480
498
  function parseFrontmatter(text) {
481
499
  const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
482
500
  if (!match) return { data: {}, body: text };
483
501
  const data = {};
502
+ let currentListKey;
484
503
  for (const line of match[1].split(/\r?\n/)) {
504
+ const listItem = /^\s*-\s+(.+)$/.exec(line);
505
+ if (listItem && currentListKey) {
506
+ const item = listItem[1].trim().replace(/^["']|["']$/g, "");
507
+ const prev = data[currentListKey];
508
+ if (Array.isArray(prev)) prev.push(item);
509
+ else data[currentListKey] = [item];
510
+ continue;
511
+ }
485
512
  const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
486
- if (!kv) continue;
513
+ if (!kv) {
514
+ if (line.trim()) currentListKey = void 0;
515
+ continue;
516
+ }
487
517
  let value = kv[2].trim();
518
+ if (!value) {
519
+ currentListKey = kv[1];
520
+ data[kv[1]] = "";
521
+ continue;
522
+ }
523
+ currentListKey = void 0;
488
524
  if (value.startsWith("[") && value.endsWith("]")) {
489
525
  data[kv[1]] = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
490
526
  continue;
@@ -496,7 +532,7 @@ function parseFrontmatter(text) {
496
532
  }
497
533
  var str = (v) => Array.isArray(v) ? v.join(", ") : v;
498
534
  function dirs(r, kind) {
499
- return [join2(r.userHome, ".claude", kind), join2(r.home, kind), join2(r.cwd, ".claude", kind), join2(r.cwd, ".bccli", kind)];
535
+ return [join3(r.userHome, ".claude", kind), join3(r.home, kind), join3(r.cwd, ".claude", kind), join3(r.cwd, ".bccli", kind)];
500
536
  }
501
537
  function list(dir) {
502
538
  try {
@@ -509,11 +545,11 @@ function loadSkills(r) {
509
545
  const byName = /* @__PURE__ */ new Map();
510
546
  for (const dir of dirs(r, "skills")) {
511
547
  for (const entry of list(dir)) {
512
- const file = join2(dir, entry, "SKILL.md");
548
+ const file = join3(dir, entry, "SKILL.md");
513
549
  if (!existsSync4(file)) continue;
514
550
  const { data } = parseFrontmatter(readFileSync2(file, "utf8"));
515
551
  const name = str(data.name) || entry;
516
- byName.set(name, { name, description: str(data.description) ?? "", dir: join2(dir, entry), file });
552
+ byName.set(name, { name, description: str(data.description) ?? "", dir: join3(dir, entry), file });
517
553
  }
518
554
  }
519
555
  return [...byName.values()];
@@ -522,7 +558,7 @@ function loadCommands(r) {
522
558
  const byName = /* @__PURE__ */ new Map();
523
559
  for (const dir of dirs(r, "commands")) {
524
560
  for (const entry of list(dir)) {
525
- const file = join2(dir, entry);
561
+ const file = join3(dir, entry);
526
562
  if (!entry.endsWith(".md") || !statSync(file).isFile()) continue;
527
563
  const { data, body } = parseFrontmatter(readFileSync2(file, "utf8"));
528
564
  const def = { name: basename(entry, ".md"), body: body.trim() };
@@ -538,7 +574,7 @@ function loadAgentDefs(r) {
538
574
  for (const dir of dirs(r, "agents")) {
539
575
  for (const entry of list(dir)) {
540
576
  if (!entry.endsWith(".md")) continue;
541
- const { data, body } = parseFrontmatter(readFileSync2(join2(dir, entry), "utf8"));
577
+ const { data, body } = parseFrontmatter(readFileSync2(join3(dir, entry), "utf8"));
542
578
  const name = str(data.name) || basename(entry, ".md");
543
579
  const def = { name, description: str(data.description) ?? "", prompt: body.trim() };
544
580
  if (data.tools)
@@ -573,7 +609,7 @@ function runOne(command, event, payload, opts) {
573
609
  ...payload.input !== void 0 ? { BCCLI_HOOK_INPUT: JSON.stringify(payload.input) } : {},
574
610
  ...payload.output !== void 0 ? { BCCLI_HOOK_OUTPUT: payload.output } : {}
575
611
  };
576
- return new Promise((resolve) => {
612
+ return new Promise((resolve3) => {
577
613
  let stdout = "";
578
614
  let stderr = "";
579
615
  let timedOut = false;
@@ -582,7 +618,7 @@ function runOne(command, event, payload, opts) {
582
618
  if (settled) return;
583
619
  settled = true;
584
620
  clearTimeout(timer);
585
- resolve(result);
621
+ resolve3(result);
586
622
  };
587
623
  let child;
588
624
  const shell = defaultShell();
@@ -598,7 +634,7 @@ function runOne(command, event, payload, opts) {
598
634
  child = spawn2(command, { shell: true, env, cwd: opts.cwd ?? process.cwd(), stdio: ["ignore", "pipe", "pipe"] });
599
635
  }
600
636
  } catch (error) {
601
- resolve({ failure: error.message });
637
+ resolve3({ failure: error.message });
602
638
  return;
603
639
  }
604
640
  const timer = setTimeout(() => {
@@ -648,6 +684,38 @@ async function runHooks(hooks, event, payload = {}, opts = {}) {
648
684
  return { blocked, warnings };
649
685
  }
650
686
 
687
+ // src/tools/paths.ts
688
+ import { statSync as statSync2 } from "fs";
689
+ import { isAbsolute, resolve } from "path";
690
+ var readMtimes = /* @__PURE__ */ new WeakMap();
691
+ function resolvePath(cwd, path) {
692
+ return isAbsolute(path) ? path : resolve(cwd, path);
693
+ }
694
+ function errorCode(error) {
695
+ return error.code ?? error.message;
696
+ }
697
+ function recordFileMtime(readFiles, abs) {
698
+ readFiles.add(abs);
699
+ try {
700
+ let map = readMtimes.get(readFiles);
701
+ if (!map) {
702
+ map = /* @__PURE__ */ new Map();
703
+ readMtimes.set(readFiles, map);
704
+ }
705
+ map.set(abs, statSync2(abs).mtimeMs);
706
+ } catch {
707
+ }
708
+ }
709
+ function isFileModifiedSinceRead(readFiles, abs) {
710
+ const recorded = readMtimes.get(readFiles)?.get(abs);
711
+ if (recorded === void 0) return false;
712
+ try {
713
+ return statSync2(abs).mtimeMs > recorded + 50;
714
+ } catch {
715
+ return false;
716
+ }
717
+ }
718
+
651
719
  // src/slash/memory.ts
652
720
  import { appendFileSync, existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
653
721
  function instructionFiles(cwd, home) {
@@ -665,22 +733,38 @@ function parseMemoryArgs(args) {
665
733
 
666
734
  // src/tools/ignore.ts
667
735
  import { readFileSync as readFileSync4 } from "fs";
668
- import { join as join3 } from "path";
669
- function ignorePatterns(root) {
736
+ import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
737
+ function ignorePatterns(root, projectRoot) {
670
738
  const patterns = ["**/node_modules/**", "**/.git/**"];
671
- let lines = [];
672
- try {
673
- lines = readFileSync4(join3(root, ".gitignore"), "utf8").split(/\r?\n/);
674
- } catch {
675
- return patterns;
739
+ const seen = /* @__PURE__ */ new Set();
740
+ const dirs2 = [];
741
+ let cur = resolve2(root);
742
+ const stop = projectRoot ? resolve2(projectRoot) : cur;
743
+ while (true) {
744
+ if (!seen.has(cur)) {
745
+ seen.add(cur);
746
+ dirs2.push(cur);
747
+ }
748
+ if (cur === stop || !cur.startsWith(stop)) break;
749
+ const parent = dirname2(cur);
750
+ if (parent === cur) break;
751
+ cur = parent;
676
752
  }
677
- for (const raw of lines) {
678
- const line = raw.trim();
679
- if (!line || line.startsWith("#") || line.startsWith("!")) continue;
680
- const p = line.replace(/^\//, "").replace(/\/$/, "");
681
- patterns.push(`**/${p}`, `**/${p}/**`);
753
+ for (const dir of dirs2) {
754
+ let lines = [];
755
+ try {
756
+ lines = readFileSync4(join4(dir, ".gitignore"), "utf8").split(/\r?\n/);
757
+ } catch {
758
+ continue;
759
+ }
760
+ for (const raw of lines) {
761
+ const line = raw.trim();
762
+ if (!line || line.startsWith("#") || line.startsWith("!")) continue;
763
+ const p = line.replace(/^\//, "").replace(/\/$/, "");
764
+ patterns.push(`**/${p}`, `**/${p}/**`);
765
+ }
682
766
  }
683
- return patterns;
767
+ return [...new Set(patterns)];
684
768
  }
685
769
 
686
770
  // src/tools/globSafety.ts
@@ -718,6 +802,10 @@ export {
718
802
  runCommand,
719
803
  createBashTool,
720
804
  bashTool,
805
+ resolvePath,
806
+ errorCode,
807
+ recordFileMtime,
808
+ isFileModifiedSinceRead,
721
809
  ignorePatterns,
722
810
  validateGlobPattern,
723
811
  normalizeGlobPath,