@botconnector/bccli 0.4.0 → 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 (173) hide show
  1. package/README.en.md +13 -2
  2. package/README.md +13 -2
  3. package/dist/abort.d.ts +5 -0
  4. package/dist/{acp-ATIL4JOZ.js → acp-UYC6APR5.js} +30 -6
  5. package/dist/acp.d.ts +28 -0
  6. package/dist/agent.d.ts +147 -0
  7. package/dist/agents.d.ts +4 -0
  8. package/dist/args-NRTZWWEV.js +13 -0
  9. package/dist/args.d.ts +53 -0
  10. package/dist/budget.d.ts +23 -0
  11. package/dist/checkpoints.d.ts +35 -0
  12. package/dist/{chunk-6M5X2KZE.js → chunk-266SY3MC.js} +1 -1
  13. package/dist/{chunk-7F3IWBPQ.js → chunk-3I5BW3ZT.js} +20 -9
  14. package/dist/{chunk-UZWJQF67.js → chunk-3WHI4LN3.js} +2 -1
  15. package/dist/{chunk-PUN4HMAG.js → chunk-5YJ3ZQVR.js} +73 -10
  16. package/dist/{chunk-L7QI6JN2.js → chunk-6M55UPES.js} +7 -7
  17. package/dist/{chunk-KEMYIGCP.js → chunk-6UO5GX55.js} +26 -12
  18. package/dist/{chunk-TLPFQIS2.js → chunk-C64KM37J.js} +36 -6
  19. package/dist/{chunk-JL5ZCSP2.js → chunk-H7XJDKJY.js} +4 -4
  20. package/dist/{chunk-SPRNLS7K.js → chunk-HQW4BROS.js} +4 -5
  21. package/dist/{chunk-XBO5GACQ.js → chunk-IYR5PYA3.js} +4 -4
  22. package/dist/{chunk-45SDLMTV.js → chunk-JXM6REER.js} +1 -1
  23. package/dist/{chunk-BLOYFIAD.js → chunk-KPTML72Q.js} +1 -1
  24. package/dist/{chunk-BQ2IGPBR.js → chunk-NQOGVP3I.js} +2 -2
  25. package/dist/{chunk-QZKLZELZ.js → chunk-OK2USAXX.js} +28 -4
  26. package/dist/{chunk-TVALKHFB.js → chunk-R72OV7RF.js} +421 -108
  27. package/dist/{chunk-LACUWBWS.js → chunk-RX4UQX43.js} +25 -10
  28. package/dist/{chunk-QDQU6VY2.js → chunk-SVD4LHJC.js} +73 -3
  29. package/dist/{chunk-BNA74HPN.js → chunk-TIMSYZIR.js} +7 -7
  30. package/dist/{chunk-O7M3TGIW.js → chunk-WFZKJY3R.js} +43 -4
  31. package/dist/{chunk-M7CALSP3.js → chunk-ZEPTUKNW.js} +120 -32
  32. package/dist/cli.d.ts +1 -0
  33. package/dist/cli.js +61 -45
  34. package/dist/clipboardImage.d.ts +17 -0
  35. package/dist/commands.d.ts +16 -0
  36. package/dist/{completion-HPPFDAPB.js → completion-D2BSBTQO.js} +1 -1
  37. package/dist/completion.d.ts +14 -0
  38. package/dist/{config-COPNMMV6.js → config-ENDRMOTP.js} +3 -3
  39. package/dist/{config-RZ5JONMX.js → config-LAINORU5.js} +8 -4
  40. package/dist/config.d.ts +99 -0
  41. package/dist/context.d.ts +13 -0
  42. package/dist/contextWindow.d.ts +3 -0
  43. package/dist/diff.d.ts +7 -0
  44. package/dist/exit.d.ts +9 -0
  45. package/dist/extensions.d.ts +34 -0
  46. package/dist/{ghBotCli-IBCZIWT5.js → ghBotCli-TSLRORJJ.js} +21 -20
  47. package/dist/ghBotCli.d.ts +9 -0
  48. package/dist/ghBotDispatch.d.ts +68 -0
  49. package/dist/ghBotRunner.d.ts +82 -0
  50. package/dist/ghBotServe.d.ts +21 -0
  51. package/dist/ghBotWebhook.d.ts +45 -0
  52. package/dist/hooks.d.ts +27 -0
  53. package/dist/i18n/id.d.ts +1 -0
  54. package/dist/i18n.d.ts +16 -0
  55. package/dist/{integrationCli-P75R2VSC.js → integrationCli-HXCSEWH2.js} +2 -2
  56. package/dist/integrationCli.d.ts +9 -0
  57. package/dist/keybinds.d.ts +22 -0
  58. package/dist/{login-SYIQP46J.js → login-RYESFZG6.js} +4 -4
  59. package/dist/login.d.ts +19 -0
  60. package/dist/lsp.d.ts +50 -0
  61. package/dist/mcp/catalog.d.ts +14 -0
  62. package/dist/mcp/config.d.ts +36 -0
  63. package/dist/mcp/manager.d.ts +52 -0
  64. package/dist/mcp/oauth.d.ts +82 -0
  65. package/dist/{mcpCli-QUV5SD64.js → mcpCli-SETTCL3K.js} +6 -6
  66. package/dist/mcpCli.d.ts +3 -0
  67. package/dist/mcpServer/serve.d.ts +8 -0
  68. package/dist/mcpServer/server.d.ts +19 -0
  69. package/dist/models.d.ts +13 -0
  70. package/dist/{modelsCli-TRRB5LX4.js → modelsCli-OP7CDZC7.js} +6 -6
  71. package/dist/modelsCli.d.ts +15 -0
  72. package/dist/npmRunner.d.ts +21 -0
  73. package/dist/{oauthLogin-6FWQABJ7.js → oauthLogin-XWY7OHCG.js} +1 -1
  74. package/dist/oauthLogin.d.ts +47 -0
  75. package/dist/permissions.d.ts +30 -0
  76. package/dist/pluginSpec.d.ts +25 -0
  77. package/dist/plugins.d.ts +45 -0
  78. package/dist/{pluginsCli-GNW4TPVV.js → pluginsCli-ZQNKWZHJ.js} +2 -2
  79. package/dist/pluginsCli.d.ts +16 -0
  80. package/dist/{prCli-5IMV3VNQ.js → prCli-6PUWTMTP.js} +20 -4
  81. package/dist/prCli.d.ts +46 -0
  82. package/dist/presets.d.ts +8 -0
  83. package/dist/print.d.ts +16 -0
  84. package/dist/provider.d.ts +145 -0
  85. package/dist/providerCli-5OZI2GQQ.js +15 -0
  86. package/dist/providerCli.d.ts +17 -0
  87. package/dist/providers.d.ts +6 -0
  88. package/dist/reasoning.d.ts +12 -0
  89. package/dist/repomap.d.ts +10 -0
  90. package/dist/sdk.d.ts +61 -0
  91. package/dist/sdk.js +829 -191
  92. package/dist/{serve-JG7RLIUD.js → serve-4J7HA4JO.js} +24 -18
  93. package/dist/{serve-6QLBJUYW.js → serve-K5AH4MGO.js} +3 -3
  94. package/dist/serve.d.ts +37 -0
  95. package/dist/serveHeartbeat.d.ts +14 -0
  96. package/dist/serveProtocol.d.ts +47 -0
  97. package/dist/session-YQXCCKXI.js +12 -0
  98. package/dist/session.d.ts +33 -0
  99. package/dist/setup-S4AJT6XE.js +24 -0
  100. package/dist/setup.d.ts +74 -0
  101. package/dist/slash/copy.d.ts +1 -0
  102. package/dist/slash/diff.d.ts +1 -0
  103. package/dist/slash/doctor.d.ts +18 -0
  104. package/dist/slash/export.d.ts +2 -0
  105. package/dist/slash/info.d.ts +46 -0
  106. package/dist/slash/memory.d.ts +10 -0
  107. package/dist/{statsCli-CF3R7ATL.js → statsCli-XR6BEZQQ.js} +2 -2
  108. package/dist/statsCli.d.ts +62 -0
  109. package/dist/textToolCalls.d.ts +6 -0
  110. package/dist/thinking.d.ts +17 -0
  111. package/dist/tokenBudget.d.ts +22 -0
  112. package/dist/tools/bash.d.ts +52 -0
  113. package/dist/tools/diagnostics.d.ts +5 -0
  114. package/dist/tools/edit.d.ts +12 -0
  115. package/dist/tools/fetch.d.ts +41 -0
  116. package/dist/tools/glob.d.ts +5 -0
  117. package/dist/tools/globSafety.d.ts +7 -0
  118. package/dist/tools/grep.d.ts +25 -0
  119. package/dist/tools/ignore.d.ts +2 -0
  120. package/dist/tools/index.d.ts +12 -0
  121. package/dist/tools/memory.d.ts +13 -0
  122. package/dist/tools/paths.d.ts +4 -0
  123. package/dist/tools/plan.d.ts +10 -0
  124. package/dist/tools/read.d.ts +16 -0
  125. package/dist/tools/repomap.d.ts +2 -0
  126. package/dist/tools/sandbox.d.ts +12 -0
  127. package/dist/tools/shell.d.ts +18 -0
  128. package/dist/tools/skill.d.ts +3 -0
  129. package/dist/tools/task.d.ts +28 -0
  130. package/dist/tools/todo.d.ts +13 -0
  131. package/dist/tools/types.d.ts +52 -0
  132. package/dist/tools/websearch.d.ts +9 -0
  133. package/dist/tools/write.d.ts +5 -0
  134. package/dist/{transferCli-TK2GSVRK.js → transferCli-R2KEIWQR.js} +8 -6
  135. package/dist/transferCli.d.ts +23 -0
  136. package/dist/ui/App.d.ts +7 -0
  137. package/dist/ui/DiffView.d.ts +3 -0
  138. package/dist/ui/EditableText.d.ts +7 -0
  139. package/dist/ui/LinePrompt.d.ts +6 -0
  140. package/dist/ui/ListPicker.d.ts +11 -0
  141. package/dist/ui/Markdown.d.ts +8 -0
  142. package/dist/ui/McpMenu.d.ts +11 -0
  143. package/dist/ui/ModelPicker.d.ts +6 -0
  144. package/dist/ui/PermissionPrompt.d.ts +5 -0
  145. package/dist/ui/PlanApproval.d.ts +5 -0
  146. package/dist/ui/PromptInput.d.ts +18 -0
  147. package/dist/ui/ProviderMenu.d.ts +10 -0
  148. package/dist/ui/Spinner.d.ts +4 -0
  149. package/dist/ui/StatusBar.d.ts +9 -0
  150. package/dist/ui/TodoList.d.ts +4 -0
  151. package/dist/ui/ToolBlock.d.ts +14 -0
  152. package/dist/ui/complete.d.ts +6 -0
  153. package/dist/ui/focusReport.d.ts +1 -0
  154. package/dist/ui/index.d.ts +7 -0
  155. package/dist/ui/lineEdit.d.ts +42 -0
  156. package/dist/ui/replay.d.ts +4 -0
  157. package/dist/ui/table.d.ts +20 -0
  158. package/dist/ui/theme.d.ts +5 -0
  159. package/dist/ui/thinkingMouse.d.ts +33 -0
  160. package/dist/ui/transcript.d.ts +55 -0
  161. package/dist/ui/vim.d.ts +26 -0
  162. package/dist/{ui-B3CJ6TGE.js → ui-36ZEK65V.js} +179 -54
  163. package/dist/{update-K5IQ2MYR.js → update-FSS7X5WV.js} +2 -2
  164. package/dist/update.d.ts +30 -0
  165. package/dist/verify.d.ts +16 -0
  166. package/dist/version.d.ts +1 -0
  167. package/dist/webBudget.d.ts +25 -0
  168. package/dist/worktree.d.ts +12 -0
  169. package/package.json +26 -13
  170. package/dist/args-J5ONVGIF.js +0 -13
  171. package/dist/providerCli-FAOZHOKW.js +0 -15
  172. package/dist/session-KBAQ4BZ5.js +0 -12
  173. package/dist/setup-FJAWDWV4.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,
@@ -274,11 +277,12 @@ function readGlobalPlugins(env = process.env) {
274
277
  }
275
278
  return value;
276
279
  }
277
- function writeFileAtomic(path, data) {
280
+ function writeFileAtomic(path, data, mode) {
278
281
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
279
282
  try {
280
- writeFileSync(tmp, data);
283
+ writeFileSync(tmp, data, mode === void 0 ? void 0 : { mode });
281
284
  renameSync(tmp, path);
285
+ if (mode !== void 0) chmodSync(path, mode);
282
286
  } catch (error) {
283
287
  try {
284
288
  rmSync(tmp, { force: true });
@@ -287,6 +291,18 @@ function writeFileAtomic(path, data) {
287
291
  throw error;
288
292
  }
289
293
  }
294
+ function quarantineCorruptFile(path, error) {
295
+ const stamp = Date.now();
296
+ let dest = `${path}.corrupt-${stamp}`;
297
+ for (let n = 1; existsSync(dest); n++) dest = `${path}.corrupt-${stamp}-${n}`;
298
+ try {
299
+ renameSync(path, dest);
300
+ } catch {
301
+ return void 0;
302
+ }
303
+ console.error(`${path} is not valid JSON (${error.message}); moved it to ${dest} instead of overwriting it.`);
304
+ return dest;
305
+ }
290
306
  function updateGlobalConfig(env, mutate) {
291
307
  const path = globalConfigPath(env);
292
308
  let cfg = {};
@@ -303,16 +319,15 @@ function readCredentials(env = process.env) {
303
319
  if (!existsSync(path)) return {};
304
320
  try {
305
321
  return JSON.parse(readFileSync(path, "utf8"));
306
- } catch {
322
+ } catch (error) {
323
+ quarantineCorruptFile(path, error);
307
324
  return {};
308
325
  }
309
326
  }
310
327
  function saveCredential(providerId, key, env = process.env) {
311
328
  const home = bccliHome(env);
312
329
  mkdirSync(home, { recursive: true });
313
- const path = join(home, "credentials");
314
- writeFileSync(path, JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), { mode: 384 });
315
- chmodSync(path, 384);
330
+ writeFileAtomic(join(home, "credentials"), JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), 384);
316
331
  }
317
332
  function resolveModel(config, modelRef, env = process.env) {
318
333
  const slash = modelRef.indexOf("/");
@@ -334,9 +349,7 @@ function removeCredential(providerId, env = process.env) {
334
349
  const creds = readCredentials(env);
335
350
  if (!(providerId in creds)) return false;
336
351
  delete creds[providerId];
337
- const path = join(bccliHome(env), "credentials");
338
- writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 384 });
339
- chmodSync(path, 384);
352
+ writeFileAtomic(join(bccliHome(env), "credentials"), JSON.stringify(creds, null, 2), 384);
340
353
  return true;
341
354
  }
342
355
 
@@ -353,6 +366,8 @@ export {
353
366
  loadConfig,
354
367
  globalConfigPath,
355
368
  readGlobalPlugins,
369
+ writeFileAtomic,
370
+ quarantineCorruptFile,
356
371
  updateGlobalConfig,
357
372
  readCredentials,
358
373
  saveCredential,
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  contentText
4
- } from "./chunk-PUN4HMAG.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
  }
@@ -79,6 +82,44 @@ var Session = class _Session {
79
82
  }
80
83
  return repairTail(messages);
81
84
  }
85
+ /** Model of the most recent turn that recorded usage — serve attach continues a session with its own model. */
86
+ lastModel() {
87
+ const records = this.records();
88
+ for (let i = records.length - 1; i >= 0; i--) {
89
+ const record = records[i];
90
+ if (record.t === "usage" && record.model) return record.model;
91
+ }
92
+ return void 0;
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
+ }
82
123
  /**
83
124
  * What the user sees on resume: the whole conversation. Compaction only shrinks what the model is sent, so
84
125
  * its reset does not hide the older turns here. A real reset (/clear, rewind) still starts over, the
@@ -91,12 +132,22 @@ var Session = class _Session {
91
132
  else if (record.t === "msg") segments[segments.length - 1].push(record.m);
92
133
  }
93
134
  let shown = segments[0];
135
+ let lastSummary;
94
136
  for (const segment of segments.slice(1)) {
95
137
  if (!isCompactionSummary(segment[0])) {
96
138
  shown = segment;
139
+ lastSummary = void 0;
97
140
  continue;
98
141
  }
99
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];
100
151
  let at = 0;
101
152
  while (at <= shown.length && !endsWithPrefixOf(shown, at, kept)) at++;
102
153
  shown = [...shown, ...kept.slice(shown.length - at)];
@@ -123,12 +174,31 @@ function endsWithPrefixOf(shown, at, kept) {
123
174
  for (let j = 0; j < n; j++) if (!sameMessage(shown[at + j], kept[j])) return false;
124
175
  return true;
125
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
+ }
126
191
  function pruneSessions(home, now = Date.now(), maxAgeDays = 30) {
127
192
  const root = join(home, "sessions");
128
193
  if (!existsSync(root)) return 0;
129
194
  let removed = 0;
130
195
  for (const project of readdirSync(root)) {
131
196
  const dir = join(root, project);
197
+ try {
198
+ if (!statSync(dir).isDirectory()) continue;
199
+ } catch {
200
+ continue;
201
+ }
132
202
  for (const file of readdirSync(dir)) {
133
203
  const path = join(dir, file);
134
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-7F3IWBPQ.js";
4
+ } from "./chunk-3I5BW3ZT.js";
5
5
  import {
6
6
  removeCredential,
7
7
  saveCredential
8
- } from "./chunk-LACUWBWS.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-FAOZHOKW.js");
95
- const { parseCliArgs: parse } = await import("./args-J5ONVGIF.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];
@@ -20,14 +20,51 @@ function isAlwaysAsk(segment) {
20
20
  }
21
21
  return false;
22
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
+ ]);
23
57
  function commandKey(segment) {
24
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(" ");
25
60
  return words[1] ? `${words[0]} ${words[1]}` : words[0];
26
61
  }
27
62
  function requiresSensitiveToolApproval(req) {
28
63
  const target = req.target.trim();
29
64
  if (req.kind === "read" || req.kind === "edit") {
30
- 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);
31
68
  }
32
69
  if (req.kind === "bash") {
33
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);
@@ -52,7 +89,8 @@ var Permissions = class {
52
89
  editInProject(target) {
53
90
  const rel = relative(this.cwd, resolve(this.cwd, target));
54
91
  if (!rel || rel.startsWith("..") || isAbsolute(rel)) return false;
55
- return !rel.split(sep).includes(".git") && !rel.split("/").includes(".git");
92
+ const parts = rel.split(/[/\\]/).map((p) => p.toLowerCase());
93
+ return !parts.includes(".git");
56
94
  }
57
95
  /** Rules that "[a] allow for this session" would add; undefined when it must always be asked. */
58
96
  rulesFor(req) {
@@ -70,7 +108,7 @@ var Permissions = class {
70
108
  if (/`|\$\(|[<>]|^\s*[({]/.test(req.target)) return void 0;
71
109
  const segments = req.target.split(/&&|\|\||[;&|\n]/).map((s) => s.trim()).filter(Boolean);
72
110
  if (!segments.length) return void 0;
73
- if (segments.some(isAlwaysAsk)) return void 0;
111
+ if (segments.some((s) => /^[({]/.test(s) || isAlwaysAsk(s))) return void 0;
74
112
  return [...new Set(segments.map((s) => `bash(${commandKey(s)})`))];
75
113
  }
76
114
  if (req.kind === "mcp") return [`mcp(${req.tool})`];
@@ -104,5 +142,6 @@ var Permissions = class {
104
142
  export {
105
143
  MODE_ORDER,
106
144
  nextMode,
145
+ requiresSensitiveToolApproval,
107
146
  Permissions
108
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,8 +421,8 @@ 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";
407
- var FILES = ["AGENTS.md", "BCCLI.md"];
424
+ import { dirname, join as join2 } from "path";
425
+ var FILES = ["AGENTS.md", "CLAUDE.md", "BCCLI.md"];
408
426
  function readIf(path) {
409
427
  try {
410
428
  return existsSync3(path) ? readFileSync(path, "utf8").trim() : void 0;
@@ -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,
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};