@botconnector/bccli 0.4.0 → 0.4.1

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 (162) 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-3PHBAI64.js} +2 -2
  5. package/dist/acp.d.ts +27 -0
  6. package/dist/agent.d.ts +143 -0
  7. package/dist/agents.d.ts +4 -0
  8. package/dist/{args-J5ONVGIF.js → args-W6BNKY7K.js} +3 -3
  9. package/dist/args.d.ts +51 -0
  10. package/dist/budget.d.ts +23 -0
  11. package/dist/checkpoints.d.ts +34 -0
  12. package/dist/{chunk-L7QI6JN2.js → chunk-24JN75FZ.js} +6 -6
  13. package/dist/{chunk-XBO5GACQ.js → chunk-75BECX4F.js} +3 -3
  14. package/dist/{chunk-QDQU6VY2.js → chunk-BC7UXFE4.js} +10 -1
  15. package/dist/{chunk-M7CALSP3.js → chunk-BGXNR74O.js} +1 -1
  16. package/dist/{chunk-6M5X2KZE.js → chunk-BKGFA2PT.js} +1 -1
  17. package/dist/{chunk-BNA74HPN.js → chunk-BXQQCUAC.js} +4 -4
  18. package/dist/{chunk-LACUWBWS.js → chunk-FLQPE4S5.js} +21 -9
  19. package/dist/{chunk-JL5ZCSP2.js → chunk-GN4W3T6E.js} +3 -3
  20. package/dist/{chunk-SPRNLS7K.js → chunk-JHPGOAXU.js} +1 -1
  21. package/dist/{chunk-7F3IWBPQ.js → chunk-MFHCQQEN.js} +18 -8
  22. package/dist/{chunk-O7M3TGIW.js → chunk-MHIYLXJC.js} +35 -0
  23. package/dist/{chunk-TVALKHFB.js → chunk-PZRKU4UM.js} +10 -10
  24. package/dist/{chunk-TLPFQIS2.js → chunk-R5RE46CW.js} +2 -2
  25. package/dist/{chunk-BQ2IGPBR.js → chunk-WI22M2ID.js} +1 -1
  26. package/dist/{chunk-PUN4HMAG.js → chunk-WNACAUDK.js} +9 -3
  27. package/dist/{chunk-KEMYIGCP.js → chunk-XV7ZVL4J.js} +2 -2
  28. package/dist/cli.d.ts +1 -0
  29. package/dist/cli.js +34 -35
  30. package/dist/clipboardImage.d.ts +17 -0
  31. package/dist/commands.d.ts +16 -0
  32. package/dist/completion.d.ts +14 -0
  33. package/dist/{config-COPNMMV6.js → config-DIDLDONU.js} +2 -2
  34. package/dist/{config-RZ5JONMX.js → config-IU76CPK2.js} +7 -3
  35. package/dist/config.d.ts +97 -0
  36. package/dist/context.d.ts +13 -0
  37. package/dist/contextWindow.d.ts +3 -0
  38. package/dist/diff.d.ts +7 -0
  39. package/dist/exit.d.ts +9 -0
  40. package/dist/extensions.d.ts +34 -0
  41. package/dist/{ghBotCli-IBCZIWT5.js → ghBotCli-E3VPHNZY.js} +12 -12
  42. package/dist/ghBotCli.d.ts +9 -0
  43. package/dist/ghBotDispatch.d.ts +68 -0
  44. package/dist/ghBotRunner.d.ts +82 -0
  45. package/dist/ghBotServe.d.ts +21 -0
  46. package/dist/ghBotWebhook.d.ts +45 -0
  47. package/dist/hooks.d.ts +27 -0
  48. package/dist/i18n/id.d.ts +1 -0
  49. package/dist/i18n.d.ts +16 -0
  50. package/dist/{integrationCli-P75R2VSC.js → integrationCli-Q2YPDTPG.js} +1 -1
  51. package/dist/integrationCli.d.ts +9 -0
  52. package/dist/keybinds.d.ts +22 -0
  53. package/dist/{login-SYIQP46J.js → login-FP3BB44A.js} +3 -3
  54. package/dist/login.d.ts +19 -0
  55. package/dist/lsp.d.ts +47 -0
  56. package/dist/mcp/catalog.d.ts +14 -0
  57. package/dist/mcp/config.d.ts +36 -0
  58. package/dist/mcp/manager.d.ts +34 -0
  59. package/dist/mcp/oauth.d.ts +82 -0
  60. package/dist/{mcpCli-QUV5SD64.js → mcpCli-SNBDUWJN.js} +5 -5
  61. package/dist/mcpCli.d.ts +3 -0
  62. package/dist/mcpServer/serve.d.ts +8 -0
  63. package/dist/mcpServer/server.d.ts +19 -0
  64. package/dist/models.d.ts +13 -0
  65. package/dist/{modelsCli-TRRB5LX4.js → modelsCli-CZ2HNGKR.js} +4 -4
  66. package/dist/modelsCli.d.ts +15 -0
  67. package/dist/npmRunner.d.ts +21 -0
  68. package/dist/oauthLogin.d.ts +47 -0
  69. package/dist/permissions.d.ts +30 -0
  70. package/dist/pluginSpec.d.ts +25 -0
  71. package/dist/plugins.d.ts +45 -0
  72. package/dist/{pluginsCli-GNW4TPVV.js → pluginsCli-LPIUUDWB.js} +1 -1
  73. package/dist/pluginsCli.d.ts +16 -0
  74. package/dist/prCli.d.ts +33 -0
  75. package/dist/presets.d.ts +8 -0
  76. package/dist/print.d.ts +14 -0
  77. package/dist/provider.d.ts +140 -0
  78. package/dist/{providerCli-FAOZHOKW.js → providerCli-NMUFXAAS.js} +5 -5
  79. package/dist/providerCli.d.ts +17 -0
  80. package/dist/providers.d.ts +6 -0
  81. package/dist/reasoning.d.ts +12 -0
  82. package/dist/repomap.d.ts +10 -0
  83. package/dist/sdk.d.ts +57 -0
  84. package/dist/sdk.js +99 -18
  85. package/dist/{serve-6QLBJUYW.js → serve-KWOECK5W.js} +1 -1
  86. package/dist/{serve-JG7RLIUD.js → serve-X2ZV4S5U.js} +20 -14
  87. package/dist/serve.d.ts +37 -0
  88. package/dist/serveHeartbeat.d.ts +14 -0
  89. package/dist/serveProtocol.d.ts +47 -0
  90. package/dist/{session-KBAQ4BZ5.js → session-GKEXDJKZ.js} +2 -2
  91. package/dist/session.d.ts +30 -0
  92. package/dist/setup-OV3NWQN5.js +24 -0
  93. package/dist/setup.d.ts +74 -0
  94. package/dist/slash/copy.d.ts +1 -0
  95. package/dist/slash/diff.d.ts +1 -0
  96. package/dist/slash/doctor.d.ts +18 -0
  97. package/dist/slash/export.d.ts +2 -0
  98. package/dist/slash/info.d.ts +45 -0
  99. package/dist/slash/memory.d.ts +10 -0
  100. package/dist/statsCli.d.ts +62 -0
  101. package/dist/textToolCalls.d.ts +6 -0
  102. package/dist/thinking.d.ts +17 -0
  103. package/dist/tokenBudget.d.ts +22 -0
  104. package/dist/tools/bash.d.ts +52 -0
  105. package/dist/tools/diagnostics.d.ts +5 -0
  106. package/dist/tools/edit.d.ts +7 -0
  107. package/dist/tools/fetch.d.ts +41 -0
  108. package/dist/tools/glob.d.ts +5 -0
  109. package/dist/tools/globSafety.d.ts +7 -0
  110. package/dist/tools/grep.d.ts +13 -0
  111. package/dist/tools/ignore.d.ts +2 -0
  112. package/dist/tools/index.d.ts +12 -0
  113. package/dist/tools/memory.d.ts +13 -0
  114. package/dist/tools/paths.d.ts +2 -0
  115. package/dist/tools/plan.d.ts +10 -0
  116. package/dist/tools/read.d.ts +16 -0
  117. package/dist/tools/repomap.d.ts +2 -0
  118. package/dist/tools/sandbox.d.ts +12 -0
  119. package/dist/tools/shell.d.ts +18 -0
  120. package/dist/tools/skill.d.ts +3 -0
  121. package/dist/tools/task.d.ts +28 -0
  122. package/dist/tools/todo.d.ts +13 -0
  123. package/dist/tools/types.d.ts +52 -0
  124. package/dist/tools/websearch.d.ts +9 -0
  125. package/dist/tools/write.d.ts +5 -0
  126. package/dist/{transferCli-TK2GSVRK.js → transferCli-YUFPI6AH.js} +3 -3
  127. package/dist/transferCli.d.ts +23 -0
  128. package/dist/ui/App.d.ts +7 -0
  129. package/dist/ui/DiffView.d.ts +3 -0
  130. package/dist/ui/EditableText.d.ts +7 -0
  131. package/dist/ui/LinePrompt.d.ts +6 -0
  132. package/dist/ui/ListPicker.d.ts +11 -0
  133. package/dist/ui/Markdown.d.ts +8 -0
  134. package/dist/ui/McpMenu.d.ts +11 -0
  135. package/dist/ui/ModelPicker.d.ts +6 -0
  136. package/dist/ui/PermissionPrompt.d.ts +5 -0
  137. package/dist/ui/PlanApproval.d.ts +5 -0
  138. package/dist/ui/PromptInput.d.ts +18 -0
  139. package/dist/ui/ProviderMenu.d.ts +10 -0
  140. package/dist/ui/Spinner.d.ts +4 -0
  141. package/dist/ui/StatusBar.d.ts +9 -0
  142. package/dist/ui/TodoList.d.ts +4 -0
  143. package/dist/ui/ToolBlock.d.ts +14 -0
  144. package/dist/ui/complete.d.ts +1 -0
  145. package/dist/ui/focusReport.d.ts +1 -0
  146. package/dist/ui/index.d.ts +7 -0
  147. package/dist/ui/lineEdit.d.ts +42 -0
  148. package/dist/ui/replay.d.ts +4 -0
  149. package/dist/ui/table.d.ts +20 -0
  150. package/dist/ui/theme.d.ts +5 -0
  151. package/dist/ui/thinkingMouse.d.ts +33 -0
  152. package/dist/ui/transcript.d.ts +55 -0
  153. package/dist/ui/vim.d.ts +26 -0
  154. package/dist/{ui-B3CJ6TGE.js → ui-IVIFNUCC.js} +13 -11
  155. package/dist/{update-K5IQ2MYR.js → update-TZ2G6KQ3.js} +1 -1
  156. package/dist/update.d.ts +30 -0
  157. package/dist/verify.d.ts +16 -0
  158. package/dist/version.d.ts +1 -0
  159. package/dist/webBudget.d.ts +25 -0
  160. package/dist/worktree.d.ts +12 -0
  161. package/package.json +26 -13
  162. package/dist/setup-FJAWDWV4.js +0 -24
package/dist/sdk.js CHANGED
@@ -715,21 +715,46 @@ function loadConfig(cwd, env = process.env) {
715
715
  allow: [...global.allow ?? []]
716
716
  };
717
717
  }
718
+ function writeFileAtomic(path, data, mode) {
719
+ const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
720
+ try {
721
+ writeFileSync(tmp, data, mode === void 0 ? void 0 : { mode });
722
+ renameSync(tmp, path);
723
+ if (mode !== void 0) chmodSync(path, mode);
724
+ } catch (error) {
725
+ try {
726
+ rmSync(tmp, { force: true });
727
+ } catch {
728
+ }
729
+ throw error;
730
+ }
731
+ }
732
+ function quarantineCorruptFile(path, error) {
733
+ const stamp = Date.now();
734
+ let dest = `${path}.corrupt-${stamp}`;
735
+ for (let n = 1; existsSync(dest); n++) dest = `${path}.corrupt-${stamp}-${n}`;
736
+ try {
737
+ renameSync(path, dest);
738
+ } catch {
739
+ return void 0;
740
+ }
741
+ console.error(`${path} is not valid JSON (${error.message}); moved it to ${dest} instead of overwriting it.`);
742
+ return dest;
743
+ }
718
744
  function readCredentials(env = process.env) {
719
745
  const path = join(bccliHome(env), "credentials");
720
746
  if (!existsSync(path)) return {};
721
747
  try {
722
748
  return JSON.parse(readFileSync(path, "utf8"));
723
- } catch {
749
+ } catch (error) {
750
+ quarantineCorruptFile(path, error);
724
751
  return {};
725
752
  }
726
753
  }
727
754
  function saveCredential(providerId, key, env = process.env) {
728
755
  const home = bccliHome(env);
729
756
  mkdirSync(home, { recursive: true });
730
- const path = join(home, "credentials");
731
- writeFileSync(path, JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), { mode: 384 });
732
- chmodSync(path, 384);
757
+ writeFileAtomic(join(home, "credentials"), JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), 384);
733
758
  }
734
759
  function resolveModel(config, modelRef, env = process.env) {
735
760
  const slash = modelRef.indexOf("/");
@@ -765,8 +790,43 @@ function isAlwaysAsk(segment) {
765
790
  }
766
791
  return false;
767
792
  }
793
+ var RUNNER_COMMANDS = /* @__PURE__ */ new Set([
794
+ "npm",
795
+ "npx",
796
+ "pnpm",
797
+ "pnpx",
798
+ "yarn",
799
+ "bun",
800
+ "bunx",
801
+ "deno",
802
+ "node",
803
+ "tsx",
804
+ "ts-node",
805
+ "python",
806
+ "python3",
807
+ "py",
808
+ "uv",
809
+ "uvx",
810
+ "pip",
811
+ "pip3",
812
+ "pipx",
813
+ "pytest",
814
+ "make",
815
+ "cargo",
816
+ "go",
817
+ "dotnet",
818
+ "java",
819
+ "ruby",
820
+ "bundle",
821
+ "rake",
822
+ "gradle",
823
+ "mvn",
824
+ "php",
825
+ "composer"
826
+ ]);
768
827
  function commandKey(segment) {
769
828
  const words2 = segment.trim().split(/\s+/);
829
+ if (RUNNER_COMMANDS.has(words2[0] ?? "") && words2[2] && !words2[2].startsWith("-")) return words2.slice(0, 3).join(" ");
770
830
  return words2[1] ? `${words2[0]} ${words2[1]}` : words2[0];
771
831
  }
772
832
  function requiresSensitiveToolApproval(req) {
@@ -1748,7 +1808,7 @@ function createProvider(options) {
1748
1808
  const isBotConnectorCloud = /^https:\/\/api\.botconnector\.id\/v1\/?$/.test(baseURL) && apiKey?.startsWith("bc_live_");
1749
1809
  if (isBotConnectorCloud) {
1750
1810
  headers["x-botconnector-client"] = "bccli";
1751
- headers["x-botconnector-client-version"] = "0.4.0";
1811
+ headers["x-botconnector-client-version"] = "0.4.1";
1752
1812
  }
1753
1813
  const useNativeResponses = isBotConnectorCloud && providerId === "bc-cloud" && model === "gpt-6-luna";
1754
1814
  const useResponsesWebSocket = useNativeResponses && Boolean(apiKey) && (fetchOverride === void 0 || responsesWebSocketFactory !== void 0);
@@ -1779,7 +1839,7 @@ function createProvider(options) {
1779
1839
  baseURL: baseURL.replace(/\/+$/, ""),
1780
1840
  defaultHeaders: {
1781
1841
  "x-botconnector-client": "bccli",
1782
- "x-botconnector-client-version": "0.4.0"
1842
+ "x-botconnector-client-version": "0.4.1"
1783
1843
  }
1784
1844
  });
1785
1845
  lunaWs = responsesWebSocketFactory ? responsesWebSocketFactory(client) : new ResponsesWS(client, { reconnect: null });
@@ -1842,8 +1902,13 @@ function createProvider(options) {
1842
1902
  } catch (error) {
1843
1903
  const err = error instanceof ProviderError ? error : new ProviderError(error.message);
1844
1904
  const lostPrevious = err.code === "previous_response_not_found";
1905
+ const handshakeStatus = /unexpected server response:\s*(\d{3})\b/i.exec(err.message);
1906
+ const handshakeRejected = Boolean(handshakeStatus && Number(handshakeStatus[1]) >= 400);
1845
1907
  const connectionLost = /websocket|connection closed|socket|network/i.test(err.message) || err.code === "websocket_connection_limit_reached";
1846
1908
  resetLunaWebSocket();
1909
+ if (!emittedText && !signal?.aborted && handshakeRejected) {
1910
+ throw new ProviderError(err.message, Number(handshakeStatus?.[1]), "bc_ws_http_fallback");
1911
+ }
1847
1912
  if (attempt === 0 && !emittedText && !signal?.aborted && (plan.incremental || lostPrevious || connectionLost)) {
1848
1913
  continue;
1849
1914
  }
@@ -1870,7 +1935,8 @@ function createProvider(options) {
1870
1935
  if (res.ok) return res;
1871
1936
  const text = await res.text().catch(() => "");
1872
1937
  lastError = new ProviderError(t("{status} from {url}: {message}", { status: res.status, url: baseURL, message: errorMessage(text) }), res.status);
1873
- if (!retryable(res.status)) throw lastError;
1938
+ const deterministicQuota429 = res.status === 429 && /"code"\s*:\s*"(?:luna_monthly_quota_exhausted|plan_allowance_exhausted)"/i.test(text);
1939
+ if (!retryable(res.status) || deterministicQuota429) throw lastError;
1874
1940
  }
1875
1941
  throw lastError ?? new ProviderError(t("Request failed"));
1876
1942
  }
@@ -3897,7 +3963,7 @@ import { z as z9 } from "zod";
3897
3963
 
3898
3964
  // src/mcp/config.ts
3899
3965
  import { createHash } from "crypto";
3900
- import { chmodSync as chmodSync2, existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
3966
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
3901
3967
  import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
3902
3968
  var ENV_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
3903
3969
  function expandEnvVars(config, env) {
@@ -3920,7 +3986,7 @@ function expandEnvVars(config, env) {
3920
3986
 
3921
3987
  // src/mcp/oauth.ts
3922
3988
  import { spawn as spawn3 } from "child_process";
3923
- import { chmodSync as chmodSync3, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
3989
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync4 } from "fs";
3924
3990
  import { createServer } from "http";
3925
3991
  import { dirname as dirname4, join as join4 } from "path";
3926
3992
  import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
@@ -3930,23 +3996,29 @@ function readStore(home) {
3930
3996
  if (!existsSync7(path)) return {};
3931
3997
  try {
3932
3998
  return JSON.parse(readFileSync4(path, "utf8"));
3933
- } catch {
3999
+ } catch (error) {
4000
+ quarantineCorruptFile(path, error);
3934
4001
  return {};
3935
4002
  }
3936
4003
  }
3937
4004
  function writeStore(home, store) {
3938
4005
  const path = oauthStorePath(home);
3939
4006
  mkdirSync3(dirname4(path), { recursive: true });
3940
- writeFileSync3(path, `${JSON.stringify(store, null, 2)}
3941
- `, { mode: 384 });
3942
- chmodSync3(path, 384);
4007
+ writeFileAtomic(path, `${JSON.stringify(store, null, 2)}
4008
+ `, 384);
3943
4009
  }
3944
4010
  function hasStoredTokens(home, serverUrl) {
3945
4011
  return Boolean(readStore(home)[serverUrl]?.tokens);
3946
4012
  }
3947
- function defaultOpen(url) {
3948
- const target = url.href;
3949
- const child = process.platform === "win32" ? spawn3("cmd", ["/c", "start", "", target], { detached: true, stdio: "ignore" }) : spawn3(process.platform === "darwin" ? "open" : "xdg-open", [target], { detached: true, stdio: "ignore" });
4013
+ function openCommand(url, platform) {
4014
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
4015
+ if (platform === "win32") return { command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url.href] };
4016
+ return { command: platform === "darwin" ? "open" : "xdg-open", args: [url.href] };
4017
+ }
4018
+ function defaultOpen(url, deps = {}) {
4019
+ const plan = openCommand(url, deps.platform ?? process.platform);
4020
+ if (!plan) return;
4021
+ const child = (deps.spawn ?? spawn3)(plan.command, plan.args, { detached: true, stdio: "ignore" });
3950
4022
  child.unref();
3951
4023
  child.on("error", () => {
3952
4024
  });
@@ -4193,7 +4265,7 @@ import { homedir as homedir2 } from "os";
4193
4265
  // src/context.ts
4194
4266
  import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
4195
4267
  import { dirname as dirname5, join as join5 } from "path";
4196
- var FILES = ["AGENTS.md", "BCCLI.md"];
4268
+ var FILES = ["AGENTS.md", "CLAUDE.md", "BCCLI.md"];
4197
4269
  function readIf(path) {
4198
4270
  try {
4199
4271
  return existsSync8(path) ? readFileSync5(path, "utf8").trim() : void 0;
@@ -4346,7 +4418,7 @@ function loadAgentDefs(r) {
4346
4418
  }
4347
4419
 
4348
4420
  // src/providers.ts
4349
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
4421
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
4350
4422
  import { join as join7 } from "path";
4351
4423
  function providerName(config, id) {
4352
4424
  return PRESETS.find((p) => p.id === id)?.name ?? config.providers[id]?.name ?? id;
@@ -4480,6 +4552,15 @@ var Session = class _Session {
4480
4552
  }
4481
4553
  return repairTail(messages);
4482
4554
  }
4555
+ /** Model of the most recent turn that recorded usage — serve attach continues a session with its own model. */
4556
+ lastModel() {
4557
+ const records = this.records();
4558
+ for (let i = records.length - 1; i >= 0; i--) {
4559
+ const record = records[i];
4560
+ if (record.t === "usage" && record.model) return record.model;
4561
+ }
4562
+ return void 0;
4563
+ }
4483
4564
  /**
4484
4565
  * What the user sees on resume: the whole conversation. Compaction only shrinks what the model is sent, so
4485
4566
  * its reset does not hide the older turns here. A real reset (/clear, rewind) still starts over, the
@@ -8,7 +8,7 @@ import "./chunk-PSO7R5AD.js";
8
8
  import {
9
9
  loadConfig,
10
10
  readCredentials
11
- } from "./chunk-LACUWBWS.js";
11
+ } from "./chunk-FLQPE4S5.js";
12
12
  import {
13
13
  t
14
14
  } from "./chunk-UZWJQF67.js";
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createRuntime
4
- } from "./chunk-TVALKHFB.js";
4
+ } from "./chunk-PZRKU4UM.js";
5
5
  import "./chunk-QZKLZELZ.js";
6
- import "./chunk-M7CALSP3.js";
6
+ import "./chunk-BGXNR74O.js";
7
7
  import "./chunk-45SDLMTV.js";
8
8
  import "./chunk-PSO7R5AD.js";
9
9
  import {
10
10
  VERSION
11
- } from "./chunk-6M5X2KZE.js";
11
+ } from "./chunk-BKGFA2PT.js";
12
12
  import {
13
13
  LOOPBACK_BIND,
14
14
  MAX_FRAME_BYTES,
@@ -17,24 +17,24 @@ import {
17
17
  parseClientFrame,
18
18
  randomToken,
19
19
  trustFence
20
- } from "./chunk-KEMYIGCP.js";
21
- import "./chunk-O7M3TGIW.js";
22
- import "./chunk-JL5ZCSP2.js";
20
+ } from "./chunk-XV7ZVL4J.js";
21
+ import "./chunk-MHIYLXJC.js";
22
+ import "./chunk-GN4W3T6E.js";
23
23
  import {
24
24
  listAllModels
25
- } from "./chunk-XBO5GACQ.js";
26
- import "./chunk-SPRNLS7K.js";
27
- import "./chunk-7F3IWBPQ.js";
25
+ } from "./chunk-75BECX4F.js";
26
+ import "./chunk-JHPGOAXU.js";
27
+ import "./chunk-MFHCQQEN.js";
28
28
  import {
29
29
  Session
30
- } from "./chunk-QDQU6VY2.js";
31
- import "./chunk-PUN4HMAG.js";
30
+ } from "./chunk-BC7UXFE4.js";
31
+ import "./chunk-WNACAUDK.js";
32
32
  import "./chunk-BLOYFIAD.js";
33
- import "./chunk-L7QI6JN2.js";
33
+ import "./chunk-24JN75FZ.js";
34
34
  import {
35
35
  bccliHome,
36
36
  loadConfig
37
- } from "./chunk-LACUWBWS.js";
37
+ } from "./chunk-FLQPE4S5.js";
38
38
  import "./chunk-UZWJQF67.js";
39
39
 
40
40
  // src/serve.ts
@@ -101,6 +101,11 @@ function readBody(req) {
101
101
  function sendFrame(ws, frame) {
102
102
  if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(frame));
103
103
  }
104
+ function serveBanner(handle, opts = {}) {
105
+ const supplied = opts.token !== void 0 || (opts.env ?? process.env).BCCLI_SERVE_TOKEN !== void 0;
106
+ return `bccli serve ${handle.url}${supplied ? "" : ` \u2014 Authorization: Bearer ${handle.token}`}
107
+ `;
108
+ }
104
109
  async function startServe(opts = {}) {
105
110
  const env = opts.env ?? process.env;
106
111
  const cwd = opts.cwd ?? process.cwd();
@@ -454,7 +459,7 @@ Connection: close\r
454
459
  function attachSession(id, forCwd) {
455
460
  const found = findSessionFile(id, forCwd);
456
461
  if (!found) return void 0;
457
- const rt = buildRuntime(found.cwd, "ask");
462
+ const rt = buildRuntime(found.cwd, "ask", found.session.lastModel());
458
463
  rt.resume(found.session);
459
464
  return registerSession(rt, found.cwd, "ask", id);
460
465
  }
@@ -511,5 +516,6 @@ Connection: close\r
511
516
  };
512
517
  }
513
518
  export {
519
+ serveBanner,
514
520
  startServe
515
521
  };
@@ -0,0 +1,37 @@
1
+ import type { Provider } from './provider';
2
+ export interface ServeOptions {
3
+ port?: number;
4
+ host?: string;
5
+ token?: string;
6
+ cwd?: string;
7
+ env?: NodeJS.ProcessEnv;
8
+ provider?: Provider;
9
+ fetch?: typeof fetch;
10
+ heartbeatMs?: number;
11
+ err?: (s: string) => void;
12
+ }
13
+ export interface ServeHandle {
14
+ port: number;
15
+ token: string;
16
+ url: string;
17
+ close(): Promise<void>;
18
+ }
19
+ export type PermissionMode = 'ask' | 'bypassPermissions';
20
+ /**
21
+ * One-line terminal banner for `bccli serve`. A token the user supplied (`--token` or
22
+ * `BCCLI_SERVE_TOKEN`) is left out so it cannot leak into logs; a token BCCLI generated
23
+ * itself must be printed or nobody could connect.
24
+ */
25
+ export declare function serveBanner(handle: {
26
+ url: string;
27
+ token: string;
28
+ }, opts?: {
29
+ token?: string;
30
+ env?: NodeJS.ProcessEnv;
31
+ }): string;
32
+ /**
33
+ * Starts the loopback session server (HTTP unary + WS mux at /v1/mux). Refuses
34
+ * non-loopback bind addresses; every request passes the DSH-style trust fence
35
+ * (403) and, except /health, the bearer-token check (401).
36
+ */
37
+ export declare function startServe(opts?: ServeOptions): Promise<ServeHandle>;
@@ -0,0 +1,14 @@
1
+ export interface Heartbeat {
2
+ /** Sends the first ping and schedules the check loop. Idempotent until stop(). */
3
+ start(): void;
4
+ /** Marks the last ping as answered (ws 'pong' event). */
5
+ pong(): void;
6
+ /** Clears the timer; no further pings or terminations. */
7
+ stop(): void;
8
+ }
9
+ /**
10
+ * ws heartbeat with deadline: every interval a ping goes out; a ping left
11
+ * unanswered by the next tick terminates the socket (connection, not stream).
12
+ * Modeled on the DSH gateway's `websocketHeartbeatIntervalMs` behavior.
13
+ */
14
+ export declare function createHeartbeat(intervalMs: number, ping: () => void, terminate: () => void): Heartbeat;
@@ -0,0 +1,47 @@
1
+ /** Wire frames of the /v1/mux WebSocket (subset of the DSH stream protocol). */
2
+ export type ClientFrame = {
3
+ type: 'open';
4
+ streamId: string;
5
+ target: string;
6
+ } | {
7
+ type: 'cancel';
8
+ streamId: string;
9
+ };
10
+ export type ServerFrame = {
11
+ type: 'item';
12
+ streamId: string;
13
+ value: unknown;
14
+ } | {
15
+ type: 'end';
16
+ streamId: string;
17
+ } | {
18
+ type: 'error';
19
+ streamId: string;
20
+ message: string;
21
+ };
22
+ /** Uplink frame size limit; oversized frames error their stream without closing the socket. */
23
+ export declare const MAX_FRAME_BYTES = 262144;
24
+ /**
25
+ * Parses one uplink mux frame. Malformed JSON, unknown types, an empty streamId,
26
+ * or an `open` without a target produce `{ error }` instead of throwing.
27
+ */
28
+ export declare function parseClientFrame(raw: string): ClientFrame | {
29
+ error: string;
30
+ };
31
+ /** Addresses `bccli serve` is allowed to bind (loopback only; 0.0.0.0 is refused). */
32
+ export declare const LOOPBACK_BIND: Set<string>;
33
+ /**
34
+ * DSH-style trust fence: loopback Host with a port matching the listener, a
35
+ * same-machine Origin when attached, and no cross-site sec-fetch-site. Returns
36
+ * null when trusted, else the 403 reason. Never establishes identity — the
37
+ * bearer token does.
38
+ */
39
+ export declare function trustFence(headers: {
40
+ host?: string;
41
+ origin?: string;
42
+ 'sec-fetch-site'?: string;
43
+ }, port: number): string | null;
44
+ /** Timing-safe `Authorization: Bearer <token>` check. */
45
+ export declare function bearerOk(header: string | undefined, token: string): boolean;
46
+ /** Fresh 32-byte hex serve token. */
47
+ export declare function randomToken(): string;
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  Session,
4
4
  pruneSessions
5
- } from "./chunk-QDQU6VY2.js";
6
- import "./chunk-PUN4HMAG.js";
5
+ } from "./chunk-BC7UXFE4.js";
6
+ import "./chunk-WNACAUDK.js";
7
7
  import "./chunk-BLOYFIAD.js";
8
8
  import "./chunk-UZWJQF67.js";
9
9
  export {
@@ -0,0 +1,30 @@
1
+ import { type ChatMessage, type Usage } from './provider';
2
+ export declare class Session {
3
+ readonly file: string;
4
+ constructor(file: string);
5
+ static create(home: string, cwd: string, now?: Date): Session;
6
+ static list(home: string, cwd: string): {
7
+ session: Session;
8
+ mtime: Date;
9
+ preview: string;
10
+ }[];
11
+ static latest(home: string, cwd: string): Session | undefined;
12
+ private write;
13
+ append(message: ChatMessage): void;
14
+ appendUsage(usage: Usage & {
15
+ model?: string;
16
+ }): void;
17
+ reset(): void;
18
+ private records;
19
+ /** What the model continues from: everything after the last reset (after a compaction that is the summary and the recent turns). */
20
+ load(): ChatMessage[];
21
+ /** Model of the most recent turn that recorded usage — serve attach continues a session with its own model. */
22
+ lastModel(): string | undefined;
23
+ /**
24
+ * What the user sees on resume: the whole conversation. Compaction only shrinks what the model is sent, so
25
+ * its reset does not hide the older turns here. A real reset (/clear, rewind) still starts over, the
26
+ * synthetic summary pair is never shown, and the recent turns a compaction re-writes are not shown twice.
27
+ */
28
+ loadHistory(): ChatMessage[];
29
+ }
30
+ export declare function pruneSessions(home: string, now?: number, maxAgeDays?: number): number;
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createRuntime,
4
+ lunaPtcAutoEnabled
5
+ } from "./chunk-PZRKU4UM.js";
6
+ import "./chunk-QZKLZELZ.js";
7
+ import "./chunk-BGXNR74O.js";
8
+ import "./chunk-45SDLMTV.js";
9
+ import "./chunk-PSO7R5AD.js";
10
+ import "./chunk-MHIYLXJC.js";
11
+ import "./chunk-GN4W3T6E.js";
12
+ import "./chunk-75BECX4F.js";
13
+ import "./chunk-JHPGOAXU.js";
14
+ import "./chunk-MFHCQQEN.js";
15
+ import "./chunk-BC7UXFE4.js";
16
+ import "./chunk-WNACAUDK.js";
17
+ import "./chunk-BLOYFIAD.js";
18
+ import "./chunk-24JN75FZ.js";
19
+ import "./chunk-FLQPE4S5.js";
20
+ import "./chunk-UZWJQF67.js";
21
+ export {
22
+ createRuntime,
23
+ lunaPtcAutoEnabled
24
+ };
@@ -0,0 +1,74 @@
1
+ import { Agent } from './agent';
2
+ import { CheckpointStore } from './checkpoints';
3
+ import type { McpServerSpec } from './mcp/config';
4
+ import { McpManager } from './mcp/manager';
5
+ import type { CliArgs } from './args';
6
+ import { type Config } from './config';
7
+ import { type AgentDef, type CommandDef, type SkillDef } from './extensions';
8
+ import { type ModelGroup } from './models';
9
+ import { type ChatMessage, type Provider } from './provider';
10
+ import type { ReasoningLevel } from './reasoning';
11
+ import { addProviderKey } from './providerCli';
12
+ import { Session } from './session';
13
+ import { type Interaction } from './tools/plan';
14
+ import { TodoStore } from './tools/todo';
15
+ import { type PluginPayload } from './plugins';
16
+ import type { HookEvent } from './hooks';
17
+ export interface Runtime {
18
+ cwd: string;
19
+ home: string;
20
+ config: Config;
21
+ modelRef: string;
22
+ reasoning: ReasoningLevel;
23
+ agent: Agent;
24
+ session: Session;
25
+ setModel(ref: string): void;
26
+ setReasoning(level: ReasoningLevel): void;
27
+ env: NodeJS.ProcessEnv;
28
+ skills: SkillDef[];
29
+ commands: CommandDef[];
30
+ agentDefs: AgentDef[];
31
+ todos: TodoStore;
32
+ interaction: Interaction;
33
+ mcp: McpManager;
34
+ startMcp(specs: McpServerSpec[]): Promise<void>;
35
+ reloadConfig(): void;
36
+ providerLabel(): string;
37
+ listModels: (only?: string) => Promise<ModelGroup[]>;
38
+ addProviderKey(id: string, key: string): ReturnType<typeof addProviderKey>;
39
+ /** Continue an earlier session: load its history and append new messages to its file. */
40
+ resume(session: Session): void;
41
+ /** Starts a fresh session file; the previous one stays on disk for /resume. */
42
+ newSession(): void;
43
+ /** Re-reads AGENTS.md/BCCLI.md into the system prompt (after /memory). */
44
+ rebuildSystemPrompt(): void;
45
+ /** Fires a plugin observer event (SessionStart/Stop are emitted here; tool events come from the agent). */
46
+ emitPlugins(event: HookEvent, payload?: PluginPayload): Promise<void>;
47
+ checkpoints: CheckpointStore;
48
+ /**
49
+ * Steps back n conversation turns: truncates the history (and the session log) to before
50
+ * them and restores the file snapshots they took. Returns undefined when nothing is left.
51
+ */
52
+ rewindTurns(n: number): Promise<RewindResult | undefined>;
53
+ startedAt: Date;
54
+ }
55
+ export interface RewindResult {
56
+ turns: number;
57
+ restored: string[];
58
+ deleted: string[];
59
+ skipped: string[];
60
+ failed: string[];
61
+ }
62
+ /** PTC is automatic for Luna. This env var is only an emergency/internal kill switch. */
63
+ export declare function lunaPtcAutoEnabled(env: NodeJS.ProcessEnv): boolean;
64
+ export declare function createRuntime(opts: {
65
+ cwd: string;
66
+ args: CliArgs;
67
+ env?: NodeJS.ProcessEnv;
68
+ provider?: Provider;
69
+ history?: ChatMessage[];
70
+ fetch?: typeof fetch;
71
+ userHome?: string;
72
+ /** Override langkah agent (mis. githubBot.stepLimit) — menang atas config.maxSteps. */
73
+ maxSteps?: number | null;
74
+ }): Runtime;
@@ -0,0 +1 @@
1
+ export declare function osc52(text: string, inTmux: boolean): string;
@@ -0,0 +1 @@
1
+ export declare function gitDiff(cwd: string, maxLines?: number, bin?: string): Promise<string>;
@@ -0,0 +1,18 @@
1
+ export interface DoctorDeps {
2
+ which: (cmd: string) => boolean | Promise<boolean>;
3
+ nodeVersion: string;
4
+ listModels: () => Promise<unknown[]>;
5
+ }
6
+ export declare function doctorText(input: {
7
+ providers: {
8
+ id: string;
9
+ name: string;
10
+ ready: boolean;
11
+ }[];
12
+ mcp: {
13
+ name: string;
14
+ status: string;
15
+ error?: string;
16
+ }[];
17
+ activeProvider: string;
18
+ }, deps: DoctorDeps): Promise<string>;
@@ -0,0 +1,2 @@
1
+ import { type ChatMessage } from '../provider';
2
+ export declare function exportMarkdown(messages: ChatMessage[], title: string): string;
@@ -0,0 +1,45 @@
1
+ export interface StatusInput {
2
+ version: string;
3
+ modelRef: string;
4
+ providerLabel: string;
5
+ mode: string;
6
+ cwd: string;
7
+ mcp: {
8
+ name: string;
9
+ status: string;
10
+ error?: string;
11
+ tools: number;
12
+ }[];
13
+ usage: {
14
+ inputTokens: number;
15
+ outputTokens: number;
16
+ cachedInputTokens?: number;
17
+ cacheWriteTokens?: number;
18
+ };
19
+ lastInputTokens: number;
20
+ }
21
+ export declare function statusText(s: StatusInput): string;
22
+ export declare function agentsText(defs: {
23
+ name: string;
24
+ description: string;
25
+ tools?: string[];
26
+ model?: string;
27
+ }[]): string;
28
+ export declare function skillsText(skills: {
29
+ name: string;
30
+ description: string;
31
+ dir: string;
32
+ }[], commands: {
33
+ name: string;
34
+ description?: string;
35
+ }[]): string;
36
+ export declare function sessionText(s: {
37
+ file: string;
38
+ started: Date;
39
+ messages: number;
40
+ usage: {
41
+ inputTokens: number;
42
+ outputTokens: number;
43
+ };
44
+ modelRef: string;
45
+ }): string;
@@ -0,0 +1,10 @@
1
+ export declare function instructionFiles(cwd: string, home: string): {
2
+ path: string;
3
+ lines: number;
4
+ }[];
5
+ export declare function appendMemory(file: string, text: string): void;
6
+ /** `/memory [global] <teks>` → target file and text; text is empty when nothing follows. */
7
+ export declare function parseMemoryArgs(args: string): {
8
+ global: boolean;
9
+ text: string;
10
+ };