@botconnector/bccli 0.4.0-beta.36 → 0.4.0-beta.37

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.
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  addGlobalServer,
4
+ expandEnvVars,
4
5
  globalMcpPath,
5
6
  projectMcpPath,
6
7
  projectTrust,
@@ -8,10 +9,12 @@ import {
8
9
  removeGlobalServer,
9
10
  serversToStart,
10
11
  setProjectTrust
11
- } from "./chunk-SZRM2VOB.js";
12
- import "./chunk-CZMTWXCE.js";
12
+ } from "./chunk-TASYQW7B.js";
13
+ import "./chunk-H5N4ST6B.js";
14
+ import "./chunk-4RMP4CNT.js";
13
15
  export {
14
16
  addGlobalServer,
17
+ expandEnvVars,
15
18
  globalMcpPath,
16
19
  projectMcpPath,
17
20
  projectTrust,
@@ -4,9 +4,11 @@ import {
4
4
  bccliHome,
5
5
  loadConfig,
6
6
  readCredentials,
7
- resolveModel,
7
+ resolveModel
8
+ } from "./chunk-H5N4ST6B.js";
9
+ import {
8
10
  t
9
- } from "./chunk-CZMTWXCE.js";
11
+ } from "./chunk-4RMP4CNT.js";
10
12
 
11
13
  // src/integrationCli.ts
12
14
  import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-NSCC5VMS.js";
5
+ } from "./chunk-FCQCZCKO.js";
6
6
  export {
7
7
  readSecret,
8
8
  runLogin
@@ -2,17 +2,23 @@
2
2
  import {
3
3
  CATALOG,
4
4
  fillTemplate
5
- } from "./chunk-5LSTEXSG.js";
5
+ } from "./chunk-HPZSXX4W.js";
6
+ import {
7
+ authorizeMcpServer,
8
+ clearTokens
9
+ } from "./chunk-3HCOAJEQ.js";
6
10
  import {
7
11
  addGlobalServer,
8
12
  globalMcpPath,
9
13
  readMcpFile,
10
14
  removeGlobalServer
11
- } from "./chunk-SZRM2VOB.js";
15
+ } from "./chunk-TASYQW7B.js";
16
+ import {
17
+ bccliHome
18
+ } from "./chunk-H5N4ST6B.js";
12
19
  import {
13
- bccliHome,
14
20
  t
15
- } from "./chunk-CZMTWXCE.js";
21
+ } from "./chunk-4RMP4CNT.js";
16
22
 
17
23
  // src/mcpCli.ts
18
24
  async function runMcpCommand(args, deps) {
@@ -33,8 +39,30 @@ async function runMcpCommand(args, deps) {
33
39
  deps.out(t("{name} removed.", { name }));
34
40
  return 0;
35
41
  }
42
+ if (action === "auth" || action === "logout") {
43
+ const config = installed[name];
44
+ if (config?.type !== "http") {
45
+ deps.err(
46
+ t("{name} is not an installed HTTP MCP server. Add it first with: bccli mcp add {name} --url <url>", { name })
47
+ );
48
+ return 1;
49
+ }
50
+ if (action === "logout") {
51
+ const removed = clearTokens(home, config.url);
52
+ deps.out(removed ? t("{name} logged out.", { name }) : t("{name} has no stored OAuth tokens.", { name }));
53
+ return 0;
54
+ }
55
+ try {
56
+ await authorizeMcpServer({ name, config }, { home });
57
+ deps.out(t("{name} authorized. Active in the next bccli session.", { name }));
58
+ return 0;
59
+ } catch (error) {
60
+ deps.err(t("OAuth login failed for {name}: {reason}", { name, reason: error.message }));
61
+ return 1;
62
+ }
63
+ }
36
64
  if (action !== "add") {
37
- deps.err(t("Unknown action: {action}. Use list, add, or remove.", { action }));
65
+ deps.err(t("Unknown action: {action}. Use list, add, remove, auth, or logout.", { action }));
38
66
  return 1;
39
67
  }
40
68
  if (args.url) {
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ addProviderKey,
4
+ runProviderCommand
5
+ } from "./chunk-BB4CVWHS.js";
6
+ import "./chunk-U66JJ6RN.js";
7
+ import "./chunk-7TABTC32.js";
8
+ import "./chunk-YURAIW7A.js";
9
+ import "./chunk-H5N4ST6B.js";
10
+ import "./chunk-4RMP4CNT.js";
11
+ export {
12
+ addProviderKey,
13
+ runProviderCommand
14
+ };
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ Session,
4
+ pruneSessions
5
+ } from "./chunk-3JRBXJVB.js";
6
+ import "./chunk-7TABTC32.js";
7
+ import "./chunk-YURAIW7A.js";
8
+ import "./chunk-4RMP4CNT.js";
9
+ export {
10
+ Session,
11
+ pruneSessions
12
+ };
@@ -1,56 +1,66 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  BUILTIN_AGENTS,
4
+ appendMemory,
4
5
  ignorePatterns,
5
- instructionPaths,
6
+ instructionFiles,
6
7
  normalizeGlobPath,
7
8
  parseFrontmatter,
9
+ parseMemoryArgs,
10
+ renderBackgroundTasks,
11
+ runHooks,
8
12
  toolLabel,
9
- validateGlobPattern
10
- } from "./chunk-HKHKYFZI.js";
13
+ validateGlobPattern,
14
+ worktreeListText
15
+ } from "./chunk-HXSREA7S.js";
11
16
  import {
12
17
  nextMode
13
- } from "./chunk-ELML4KVQ.js";
18
+ } from "./chunk-R3ND76LW.js";
14
19
  import {
15
20
  hasKey,
16
21
  providerName,
17
22
  writeGlobalConfig
18
- } from "./chunk-3DHJO7NC.js";
23
+ } from "./chunk-U66JJ6RN.js";
24
+ import {
25
+ Session
26
+ } from "./chunk-3JRBXJVB.js";
27
+ import {
28
+ contentText
29
+ } from "./chunk-7TABTC32.js";
19
30
  import {
20
31
  REASONING_LEVELS,
21
32
  assertReasoningSupported,
22
33
  parseReasoningLevel,
23
34
  supportedReasoningLevels
24
- } from "./chunk-3NTTXK4Y.js";
25
- import {
26
- Session
27
- } from "./chunk-SJH3YLW4.js";
35
+ } from "./chunk-YURAIW7A.js";
28
36
  import {
29
37
  CATALOG,
30
38
  fillTemplate
31
- } from "./chunk-5LSTEXSG.js";
39
+ } from "./chunk-HPZSXX4W.js";
32
40
  import {
33
41
  addGlobalServer,
34
42
  globalMcpPath,
35
43
  readMcpFile,
36
44
  removeGlobalServer
37
- } from "./chunk-SZRM2VOB.js";
45
+ } from "./chunk-TASYQW7B.js";
38
46
  import {
39
47
  PRESETS,
48
+ removeCredential
49
+ } from "./chunk-H5N4ST6B.js";
50
+ import {
40
51
  getLanguage,
41
52
  locale,
42
53
  parseLang,
43
- removeCredential,
44
54
  setLanguage,
45
55
  t
46
- } from "./chunk-CZMTWXCE.js";
56
+ } from "./chunk-4RMP4CNT.js";
47
57
 
48
58
  // src/ui/index.tsx
49
59
  import { render } from "ink";
50
60
 
51
61
  // src/ui/App.tsx
52
62
  import { execFile as execFile2 } from "child_process";
53
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
63
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
54
64
  import { homedir } from "os";
55
65
  import { dirname, join as join2, relative, resolve } from "path";
56
66
  import { Box as Box13, Static, Text as Text16, useApp, useInput as useInput9 } from "ink";
@@ -69,7 +79,10 @@ var SLASH_COMMANDS = [
69
79
  { name: "status", description: "version, model, permission mode, MCP, context" },
70
80
  { name: "permissions", description: "view/revoke permissions" },
71
81
  { name: "undo", description: "undo the file edits of the last turn" },
82
+ { name: "rewind", description: "step back turns: conversation and file edits (/rewind 2)" },
72
83
  { name: "diff", description: "git diff of the project" },
84
+ { name: "worktree", description: "list git worktrees; create one with bccli -w <name>" },
85
+ { name: "pr", description: "review a pull request (/pr <n|url|branch>)" },
73
86
  { name: "copy", description: "copy the last answer" },
74
87
  { name: "export", description: "save the conversation as markdown" },
75
88
  { name: "memory", description: "project instructions (/memory <text>, /memory global <text>)" },
@@ -83,6 +96,7 @@ var SLASH_COMMANDS = [
83
96
  { name: "clear", description: "start a new conversation" },
84
97
  { name: "compact", description: "summarize the conversation" },
85
98
  { name: "cost", description: "token usage of this session" },
99
+ { name: "tasks", description: "list background tasks" },
86
100
  { name: "exit", description: "quit" }
87
101
  ];
88
102
  function parseSlash(text) {
@@ -96,6 +110,14 @@ function expandCommand(def, args) {
96
110
 
97
111
  ${args}` : def.body;
98
112
  }
113
+ function prReviewPrompt(selector) {
114
+ const target = selector.trim();
115
+ const refs = target ? `Use "${target}" as the PR selector: run gh pr view ${target} --json url,title,body,baseRefName,headRefName,files,additions,deletions and gh pr diff ${target}.` : "Start with `gh pr view --json url,title,baseRefName,headRefName` for the PR of the current branch, then fetch the change with `gh pr diff`. If the branch has no open PR, review its changes against the default branch instead: find the default branch (`gh repo view --json defaultBranchRef` or `git remote show origin`), then `git diff origin/<default-branch>...HEAD`.";
116
+ return `Review this pull request like a senior reviewer.
117
+ ${refs}
118
+ Read the changed files in the repository when the diff alone is not enough to judge the change.
119
+ Write the review as: a 2-3 sentence summary; concrete problems (bugs, security, race conditions, error handling), each with file:line and why it matters; missing or weak tests; smaller style/nit issues; then a final verdict \u2014 approve, request changes, or comment \u2014 with the single most important reason.`;
120
+ }
99
121
 
100
122
  // src/slash/copy.ts
101
123
  function osc52(text, inTmux) {
@@ -156,7 +178,7 @@ async function doctorText(input, deps) {
156
178
  function exportMarkdown(messages, title) {
157
179
  const parts = [`# ${title}`, ""];
158
180
  for (const m of messages) {
159
- if (m.role === "user") parts.push("## User", "", String(m.content), "");
181
+ if (m.role === "user") parts.push("## User", "", contentText(m.content), "");
160
182
  else if (m.role === "assistant") {
161
183
  for (const c of m.tool_calls ?? []) parts.push(`\u23BF ${c.function.name} ${c.function.arguments.slice(0, 200)}`, "");
162
184
  if (m.content) parts.push("## BCCLI", "", m.content, "");
@@ -202,21 +224,6 @@ function sessionText(s) {
202
224
  ].join("\n");
203
225
  }
204
226
 
205
- // src/slash/memory.ts
206
- import { appendFileSync, existsSync, readFileSync } from "fs";
207
- function instructionFiles(cwd, home) {
208
- return instructionPaths(cwd, home).filter((path) => existsSync(path)).map((path) => ({ path, lines: readFileSync(path, "utf8").split("\n").filter(Boolean).length }));
209
- }
210
- function appendMemory(file, text) {
211
- const prefix = existsSync(file) && !readFileSync(file, "utf8").endsWith("\n") ? "\n" : "";
212
- appendFileSync(file, `${prefix}- ${text.trim()}
213
- `);
214
- }
215
- function parseMemoryArgs(args) {
216
- const match = /^global(?:\s+|$)/i.exec(args.trim());
217
- return match ? { global: true, text: args.trim().slice(match[0].length).trim() } : { global: false, text: args.trim() };
218
- }
219
-
220
227
  // src/ui/Markdown.tsx
221
228
  import { Box, Text, useStdout } from "ink";
222
229
 
@@ -1331,7 +1338,13 @@ function App({ runtime, initialPrompt, version }) {
1331
1338
  runtime.agent.onEvent = onEvent;
1332
1339
  runtime.agent.askPermission = (request) => new Promise((resolve2) => setAsks((q) => [...q, { id: askId.current++, request, resolve: resolve2 }]));
1333
1340
  runtime.interaction.approvePlan = (plan) => new Promise((resolve2) => setPlanAsk({ plan, resolve: resolve2 }));
1334
- }, [runtime, onEvent]);
1341
+ runHooks(runtime.config.hooks, "SessionStart", {}, { env: runtime.env, cwd: runtime.cwd }).then((outcome) => {
1342
+ for (const w of outcome.warnings) notice(w, "warn");
1343
+ }).catch(() => {
1344
+ });
1345
+ runtime.emitPlugins("SessionStart").catch(() => {
1346
+ });
1347
+ }, [runtime, onEvent, notice]);
1335
1348
  const runTurn = useCallback(
1336
1349
  async (text) => {
1337
1350
  setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "user", text })] }));
@@ -1340,11 +1353,17 @@ function App({ runtime, initialPrompt, version }) {
1340
1353
  controller.current = new AbortController();
1341
1354
  await runtime.agent.run(text, controller.current.signal);
1342
1355
  controller.current = null;
1356
+ runHooks(runtime.config.hooks, "Stop", {}, { env: runtime.env, cwd: runtime.cwd }).then((outcome) => {
1357
+ for (const w of outcome.warnings) notice(w, "warn");
1358
+ }).catch(() => {
1359
+ });
1360
+ runtime.emitPlugins("Stop").catch(() => {
1361
+ });
1343
1362
  setTranscript(endTurn);
1344
1363
  setBusy(false);
1345
1364
  setCancelling(false);
1346
1365
  },
1347
- [runtime]
1366
+ [runtime, notice]
1348
1367
  );
1349
1368
  const runSlashCommand = useCallback(
1350
1369
  async (name, args) => {
@@ -1365,6 +1384,9 @@ function App({ runtime, initialPrompt, version }) {
1365
1384
  notice(t("{input} tokens in \xB7 {output} tokens out", { input: u.inputTokens.toLocaleString(locale()), output: u.outputTokens.toLocaleString(locale()) }));
1366
1385
  return;
1367
1386
  }
1387
+ case "tasks":
1388
+ notice(renderBackgroundTasks());
1389
+ return;
1368
1390
  case "compact": {
1369
1391
  setBusy(true);
1370
1392
  setStartedAt(Date.now());
@@ -1481,7 +1503,7 @@ function App({ runtime, initialPrompt, version }) {
1481
1503
  if (!chosen) return;
1482
1504
  runtime.resume(chosen.session);
1483
1505
  setTokens(0);
1484
- const chat = runtime.agent.messages.filter(
1506
+ const chat = runtime.agent.messages.map((m) => m.role === "user" && Array.isArray(m.content) ? { ...m, content: contentText(m.content) } : m).filter(
1485
1507
  (m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && !!m.content.trim()
1486
1508
  );
1487
1509
  setTranscript({
@@ -1655,6 +1677,12 @@ function App({ runtime, initialPrompt, version }) {
1655
1677
  case "diff":
1656
1678
  notice(await gitDiff(runtime.cwd));
1657
1679
  return;
1680
+ case "worktree":
1681
+ notice(await worktreeListText(runtime.cwd));
1682
+ return;
1683
+ case "pr":
1684
+ void runTurn(prReviewPrompt(args));
1685
+ return;
1658
1686
  case "undo": {
1659
1687
  const result = await runtime.checkpoints.undo();
1660
1688
  if (!result) {
@@ -1673,6 +1701,25 @@ function App({ runtime, initialPrompt, version }) {
1673
1701
  );
1674
1702
  return;
1675
1703
  }
1704
+ case "rewind": {
1705
+ const parsed = Number.parseInt(args.trim(), 10);
1706
+ const result = await runtime.rewindTurns(Number.isFinite(parsed) && parsed > 0 ? parsed : 1);
1707
+ if (!result) {
1708
+ notice(t("Nothing to rewind."));
1709
+ return;
1710
+ }
1711
+ const rel = (p) => relative(runtime.cwd, p) || p;
1712
+ notice(
1713
+ [
1714
+ result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
1715
+ result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
1716
+ result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
1717
+ result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
1718
+ t("Rewound {n} turn(s): the conversation continues from before them.", { n: result.turns })
1719
+ ].filter(Boolean).join("\n")
1720
+ );
1721
+ return;
1722
+ }
1676
1723
  case "copy": {
1677
1724
  const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
1678
1725
  if (!last || typeof last.content !== "string") {
@@ -1689,7 +1736,7 @@ function App({ runtime, initialPrompt, version }) {
1689
1736
  case "export": {
1690
1737
  const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
1691
1738
  const file = resolve(runtime.cwd, name2);
1692
- if (existsSync2(file)) {
1739
+ if (existsSync(file)) {
1693
1740
  const answer2 = await ask(t("{file} already exists. Overwrite? (y/N)", { file }));
1694
1741
  if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
1695
1742
  notice(t("Export cancelled."));
@@ -1737,7 +1784,7 @@ function App({ runtime, initialPrompt, version }) {
1737
1784
  else if (skill) {
1738
1785
  let body;
1739
1786
  try {
1740
- body = parseFrontmatter(readFileSync2(skill.file, "utf8")).body;
1787
+ body = parseFrontmatter(readFileSync(skill.file, "utf8")).body;
1741
1788
  } catch (error) {
1742
1789
  notice(t("Skill {name} cannot be read: {error}", { name: skill.name, error: error.code ?? error.message }), "error");
1743
1790
  return;
@@ -1954,7 +2001,7 @@ ${t2.output}`);
1954
2001
  import { jsx as jsx17 } from "react/jsx-runtime";
1955
2002
  async function startInteractive(rt, opts) {
1956
2003
  if (opts.resume) {
1957
- const { Session: Session2 } = await import("./session-7UB77CEG.js");
2004
+ const { Session: Session2 } = await import("./session-IYVTCUBC.js");
1958
2005
  const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
1959
2006
  if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
1960
2007
  else {
@@ -1969,7 +2016,7 @@ async function startInteractive(rt, opts) {
1969
2016
  if (chosen) rt.resume(chosen.session);
1970
2017
  }
1971
2018
  }
1972
- const { serversToStart, setProjectTrust } = await import("./config-53MC3AH2.js");
2019
+ const { serversToStart, setProjectTrust } = await import("./config-JRO5EOKZ.js");
1973
2020
  const plan = serversToStart(rt.home, rt.cwd);
1974
2021
  if (plan.needTrust.length) {
1975
2022
  const { createInterface } = await import("readline/promises");
@@ -1982,7 +2029,9 @@ async function startInteractive(rt, opts) {
1982
2029
  }
1983
2030
  rl.close();
1984
2031
  }
1985
- void rt.startMcp(plan.start);
2032
+ void rt.startMcp(plan.start).catch((error) => {
2033
+ console.error(t("Could not start MCP servers: {error}", { error: error instanceof Error ? error.message : String(error) }));
2034
+ });
1986
2035
  const instance = render(/* @__PURE__ */ jsx17(App, { runtime: rt, initialPrompt: opts.initialPrompt, version: opts.version }), { exitOnCtrlC: true });
1987
2036
  await instance.waitUntilExit();
1988
2037
  await rt.mcp.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.0-beta.36",
3
+ "version": "0.4.0-beta.37",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,6 +29,7 @@
29
29
  "ink": "^7.1.1",
30
30
  "openai": "^7.27.0",
31
31
  "react": "^19.3.0",
32
+ "undici": "^7.28.0",
32
33
  "ws": "^8.22.0",
33
34
  "yaml": "^2.9.1",
34
35
  "zod": "^4.6.5"
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- helpText,
4
- parseCliArgs
5
- } from "./chunk-BTCJNE7R.js";
6
- import "./chunk-ELML4KVQ.js";
7
- import "./chunk-3NTTXK4Y.js";
8
- import "./chunk-CZMTWXCE.js";
9
- export {
10
- helpText,
11
- parseCliArgs
12
- };