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

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.
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  helpText,
4
4
  parseCliArgs
5
- } from "./chunk-NM3C75BT.js";
5
+ } from "./chunk-BTCJNE7R.js";
6
6
  import "./chunk-ELML4KVQ.js";
7
- import "./chunk-UJH2JT6H.js";
8
- import "./chunk-IJGTX7RC.js";
7
+ import "./chunk-3NTTXK4Y.js";
8
+ import "./chunk-CZMTWXCE.js";
9
9
  export {
10
10
  helpText,
11
11
  parseCliArgs
@@ -5,7 +5,7 @@ import {
5
5
  readCredentials,
6
6
  readJsonConfig,
7
7
  removeCredential
8
- } from "./chunk-IJGTX7RC.js";
8
+ } from "./chunk-CZMTWXCE.js";
9
9
 
10
10
  // src/providers.ts
11
11
  import { mkdirSync, writeFileSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  t
4
- } from "./chunk-IJGTX7RC.js";
4
+ } from "./chunk-CZMTWXCE.js";
5
5
 
6
6
  // src/reasoning.ts
7
7
  var REASONING_LEVELS = ["auto", "off", "low", "medium", "high", "max"];
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ConfigError,
4
4
  t
5
- } from "./chunk-IJGTX7RC.js";
5
+ } from "./chunk-CZMTWXCE.js";
6
6
 
7
7
  // src/mcp/catalog.ts
8
8
  var CATALOG = [
@@ -5,12 +5,12 @@ import {
5
5
  import {
6
6
  REASONING_LEVELS,
7
7
  isReasoningLevel
8
- } from "./chunk-UJH2JT6H.js";
8
+ } from "./chunk-3NTTXK4Y.js";
9
9
  import {
10
10
  ConfigError,
11
11
  parseLang,
12
12
  t
13
- } from "./chunk-IJGTX7RC.js";
13
+ } from "./chunk-CZMTWXCE.js";
14
14
 
15
15
  // src/args.ts
16
16
  import { parseArgs } from "util";
@@ -24,7 +24,7 @@ Usage:
24
24
  bccli provider list|add <id>|remove <id> manage providers (custom: --url <url> [--name N] [--key-env ENV])
25
25
  bccli mcp list|add <name>|remove <name> manage MCP servers (catalog, or --url <url>)
26
26
  bccli integrations show the status of external agent integrations
27
- bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible
27
+ bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible, openclaw, hermes
28
28
  bccli disconnect <agent> remove an integration and restore the previous config
29
29
 
30
30
  Options:
@@ -49,7 +49,7 @@ var ID = {
49
49
  "Session number (enter = new): ": "Nomor sesi (enter = baru): ",
50
50
  'This repo wants to run the MCP server "{name}" ({what}). Allow? [y/N] ': 'Repo ini ingin menjalankan server MCP "{name}" ({what}). Izinkan? [y/N] ',
51
51
  "Conversation summarized to fit the model context.": "Percakapan diringkas agar muat di konteks model.",
52
- 'Step limit of 50 reached. Type "continue" to keep going.': 'Batas 50 langkah tercapai. Ketik "lanjut" untuk meneruskan.',
52
+ 'Step limit of {n} reached. Type "continue" to keep going.': 'Batas {n} langkah tercapai. Ketik "lanjut" untuk meneruskan.',
53
53
  "Cancelled.": "Dibatalkan.",
54
54
  "Permission mode: {mode}": "Mode izin: {mode}",
55
55
  "Folder: {cwd}": "Folder: {cwd}",
@@ -111,7 +111,7 @@ Usage:
111
111
  bccli provider list|add <id>|remove <id> manage providers (custom: --url <url> [--name N] [--key-env ENV])
112
112
  bccli mcp list|add <name>|remove <name> manage MCP servers (catalog, or --url <url>)
113
113
  bccli integrations show the status of external agent integrations
114
- bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible
114
+ bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible, openclaw, hermes
115
115
  bccli disconnect <agent> remove an integration and restore the previous config
116
116
 
117
117
  Options:
@@ -134,7 +134,7 @@ Pemakaian:
134
134
  bccli provider list|add <id>|remove <id> kelola provider (custom: --url <url> [--name N] [--key-env ENV])
135
135
  bccli mcp list|add <nama>|remove <nama> kelola server MCP (katalog, atau --url <url>)
136
136
  bccli integrations lihat status integrasi agent eksternal
137
- bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible
137
+ bccli connect <agent> opencode, aider, cline, dsh, codex, claude-code, cursor, openai-cli, openai-sdk, openai-compatible, openclaw, hermes
138
138
  bccli disconnect <agent> lepas integrasi dan pulihkan config sebelumnya
139
139
 
140
140
  Opsi:
@@ -155,6 +155,7 @@ Opsi:
155
155
  "Needs permission for {tool}. Run with --allow-all or --allowed-tools {kind}.": "Butuh izin untuk {tool}. Jalankan dengan --allow-all atau --allowed-tools {kind}.",
156
156
  "--lang must be one of: en, id": "--lang harus salah satu dari: en, id",
157
157
  "Invalid config: {path} ({error})": "Config tidak valid: {path} ({error})",
158
+ "maxSteps must be a positive number, or 0/null to disable the limit.": "maxSteps harus berupa angka positif, atau 0/null untuk menonaktifkan batas.",
158
159
  "Model must be in provider/model format, e.g. bc-cloud/glm-5.3-flash (got: {ref})": "Model harus berformat provider/model, contoh bc-cloud/glm-5.3-flash (dapat: {ref})",
159
160
  'Provider "{id}" is not in the config. Available: {list}': 'Provider "{id}" tidak ada di config. Tersedia: {list}',
160
161
  "No API key for {id} yet. Run `bccli login` or set the env var {env}.": "API key untuk {id} belum ada. Jalankan `bccli login` atau set env {env}.",
@@ -195,10 +196,11 @@ Opsi:
195
196
  '-p mode needs a task, e.g. bccli -p "explain this repo"': 'Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"',
196
197
  'Project MCP server "{name}" skipped (not approved yet; run interactive bccli once to approve it).': 'Server MCP project "{name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).',
197
198
  'Interactive mode needs a terminal. For scripts/CI use: bccli -p "task"': 'Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"',
198
- "Agent must be one of: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code": "Agent harus salah satu dari: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code",
199
+ "Agent must be one of: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code, openclaw, hermes": "Agent harus salah satu dari: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code, openclaw, hermes",
199
200
  "Cannot determine the home directory.": "Home directory tidak dapat ditentukan.",
200
201
  "No BotConnector API key yet. Run bccli login bc-cloud first.": "API key BotConnector belum tersedia. Jalankan bccli login bc-cloud terlebih dahulu.",
201
202
  "Could not load the BotConnector model catalog (HTTP {status}).": "Katalog model BotConnector gagal dimuat (HTTP {status}).",
203
+ "The BotConnector model catalog is empty; cannot configure external agents.": "Katalog model BotConnector kosong; agent eksternal tidak dapat dikonfigurasi.",
202
204
  "The OpenCode config is not valid JSON: {path}": "Config OpenCode tidak valid JSON: {path}",
203
205
  "OpenCode is connected to BotConnector ({n} models).": "OpenCode terhubung ke BotConnector ({n} model).",
204
206
  "The Aider config already has its own env-file/openai-api settings: {path}. BCCLI will not overwrite them.": "Config Aider sudah memiliki env-file/openai-api setting sendiri: {path}. BCCLI tidak akan menimpanya.",
@@ -219,6 +221,15 @@ Opsi:
219
221
  "Codex already has botconnector.config.toml. BCCLI will not overwrite it.": "Codex sudah memiliki botconnector.config.toml. BCCLI tidak akan menimpanya.",
220
222
  "Codex CLI is connected to the BotConnector Responses API.": "Codex CLI terhubung ke BotConnector Responses API.",
221
223
  "Claude Code is connected to the BotConnector Anthropic Messages compatibility API.": "Claude Code terhubung ke BotConnector Anthropic Messages compatibility API.",
224
+ 'OpenClaw already has a provider "{id}" with a different endpoint ({base}). BCCLI will not overwrite it; disconnect or change that provider first.': 'OpenClaw sudah memiliki provider "{id}" dengan endpoint lain ({base}). BCCLI tidak akan menimpanya; disconnect/ubah provider tersebut lebih dulu.',
225
+ "The OpenClaw config is not valid JSON: {path}": "Config OpenClaw tidak valid JSON: {path}",
226
+ "OpenClaw is connected to BotConnector ({n} models; default {model}).": "OpenClaw terhubung ke BotConnector ({n} model; default {model}).",
227
+ "API key: env {env} (SecretRef, not stored in the config).": "API key: env {env} (SecretRef, tidak disimpan di config).",
228
+ "Set the default model in OpenClaw with: openclaw models set {provider}/{model}": "Atur model default di OpenClaw dengan: openclaw models set {provider}/{model}",
229
+ "The Hermes config is not valid YAML: {path}": "Config Hermes tidak valid YAML: {path}",
230
+ 'Hermes already has a provider "{id}" with a different endpoint ({base}). BCCLI will not overwrite it; disconnect or change that provider first.': 'Hermes sudah memiliki provider "{id}" dengan endpoint lain ({base}). BCCLI tidak akan menimpanya; disconnect/ubah provider tersebut lebih dulu.',
231
+ "Hermes is connected to BotConnector ({n} models; default {model}).": "Hermes terhubung ke BotConnector ({n} model; default {model}).",
232
+ "API key: env {env} only (via key_env; a plaintext key in config.yaml is never written).": "API key: hanya env {env} (melalui key_env; key plaintext tidak pernah ditulis ke config.yaml).",
222
233
  "{target}: not managed by BCCLI yet.": "{target}: belum dikelola oleh BCCLI.",
223
234
  "{target}: the BotConnector integration was removed and the config restored.": "{target}: integrasi BotConnector dilepas dan config dipulihkan.",
224
235
  "Custom commands:": "Perintah custom:",
@@ -361,6 +372,15 @@ function readJsonConfig(path) {
361
372
  throw new ConfigError(t("Invalid config: {path} ({error})", { path, error: error.message }));
362
373
  }
363
374
  }
375
+ function resolvedMaxSteps(project, global) {
376
+ const hasProjectValue = Object.hasOwn(project, "maxSteps");
377
+ const value = hasProjectValue ? project.maxSteps : global.maxSteps;
378
+ if (value == null || value === 0) return void 0;
379
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
380
+ throw new ConfigError(t("maxSteps must be a positive number, or 0/null to disable the limit."));
381
+ }
382
+ return Math.max(1, Math.floor(value));
383
+ }
364
384
  function untrustedProviders(project, known) {
365
385
  const out = {};
366
386
  for (const [id, provider] of Object.entries(project.providers ?? {})) {
@@ -378,6 +398,7 @@ function loadConfig(cwd, env = process.env) {
378
398
  model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
379
399
  permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
380
400
  reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
401
+ maxSteps: resolvedMaxSteps(project, global),
381
402
  providers: { ...known, ...fromProject },
382
403
  projectProviders: Object.keys(fromProject),
383
404
  allow: [...global.allow ?? []]
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  t
4
- } from "./chunk-IJGTX7RC.js";
4
+ } from "./chunk-CZMTWXCE.js";
5
5
 
6
6
  // src/print.ts
7
7
  function toolLabel(tool) {
@@ -73,14 +73,12 @@ var BUILTIN_AGENTS = [
73
73
  name: "explore",
74
74
  description: "Read-only: search and read code to answer a question (fast, safe, can run in parallel).",
75
75
  tools: ["read", "grep", "glob"],
76
- prompt: EXPLORE_PROMPT,
77
- maxSteps: 30
76
+ prompt: EXPLORE_PROMPT
78
77
  },
79
78
  {
80
79
  name: "general",
81
80
  description: "Full tools: carry out a self-contained sub-task (edits and commands still need user permission).",
82
- prompt: GENERAL_PROMPT,
83
- maxSteps: 50
81
+ prompt: GENERAL_PROMPT
84
82
  }
85
83
  ];
86
84
 
@@ -223,6 +221,11 @@ function loadAgentDefs(r) {
223
221
  if (data.tools)
224
222
  def.tools = Array.isArray(data.tools) ? data.tools : String(data.tools).split(",").map((s) => s.trim()).filter(Boolean);
225
223
  if (data.model) def.model = str(data.model);
224
+ const rawMaxSteps = str(data.maxSteps ?? data["max-steps"]);
225
+ if (rawMaxSteps) {
226
+ const maxSteps = Number(rawMaxSteps);
227
+ if (Number.isFinite(maxSteps) && maxSteps > 0) def.maxSteps = Math.max(1, Math.floor(maxSteps));
228
+ }
226
229
  byName.set(name, def);
227
230
  }
228
231
  }
@@ -35,8 +35,8 @@ function readSecret(prompt) {
35
35
  });
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-4JTIW3YA.js");
39
- const { parseCliArgs } = await import("./args-EFFR44JW.js");
38
+ const { runProviderCommand } = await import("./providerCli-WJXJW3W2.js");
39
+ const { parseCliArgs } = await import("./args-GJUGK4F4.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
42
42
  cwd,
@@ -4,17 +4,17 @@ import {
4
4
  providerName,
5
5
  removeGlobalProvider,
6
6
  writeGlobalConfig
7
- } from "./chunk-AOVIQXXO.js";
7
+ } from "./chunk-3DHJO7NC.js";
8
8
  import {
9
9
  reasoningPayload
10
- } from "./chunk-UJH2JT6H.js";
10
+ } from "./chunk-3NTTXK4Y.js";
11
11
  import {
12
12
  PRESETS,
13
13
  loadConfig,
14
14
  readCredentials,
15
15
  saveCredential,
16
16
  t
17
- } from "./chunk-IJGTX7RC.js";
17
+ } from "./chunk-CZMTWXCE.js";
18
18
 
19
19
  // src/provider.ts
20
20
  import OpenAI from "openai";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ConfigError,
4
4
  t
5
- } from "./chunk-IJGTX7RC.js";
5
+ } from "./chunk-CZMTWXCE.js";
6
6
 
7
7
  // src/mcp/config.ts
8
8
  import { createHash } from "crypto";
package/dist/cli.js CHANGED
@@ -10,11 +10,11 @@ import {
10
10
  parseFrontmatter,
11
11
  runPrint,
12
12
  validateGlobPattern
13
- } from "./chunk-LRMC4AOV.js";
13
+ } from "./chunk-HKHKYFZI.js";
14
14
  import {
15
15
  helpText,
16
16
  parseCliArgs
17
- } from "./chunk-NM3C75BT.js";
17
+ } from "./chunk-BTCJNE7R.js";
18
18
  import {
19
19
  Permissions
20
20
  } from "./chunk-ELML4KVQ.js";
@@ -24,15 +24,15 @@ import {
24
24
  createProvider,
25
25
  hasPendingProgrammaticReplay,
26
26
  listAllModels
27
- } from "./chunk-GJAX4NCM.js";
27
+ } from "./chunk-QKWSDZJQ.js";
28
28
  import {
29
29
  providerName,
30
30
  readGlobalConfigFile
31
- } from "./chunk-AOVIQXXO.js";
32
- import "./chunk-UJH2JT6H.js";
31
+ } from "./chunk-3DHJO7NC.js";
32
+ import "./chunk-3NTTXK4Y.js";
33
33
  import {
34
34
  runLogin
35
- } from "./chunk-FNBTY56Z.js";
35
+ } from "./chunk-NSCC5VMS.js";
36
36
  import {
37
37
  Session,
38
38
  pruneSessions
@@ -45,7 +45,7 @@ import {
45
45
  resolveModel,
46
46
  setLanguage,
47
47
  t
48
- } from "./chunk-IJGTX7RC.js";
48
+ } from "./chunk-CZMTWXCE.js";
49
49
 
50
50
  // src/textToolCalls.ts
51
51
  var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
@@ -935,7 +935,7 @@ var Agent = class {
935
935
  this.turnTools = this.tools;
936
936
  this.turnDefinitions = this.definitions;
937
937
  this.messages = [...opts.history ?? []];
938
- this.maxSteps = opts.maxSteps ?? 50;
938
+ this.maxSteps = typeof opts.maxSteps === "number" && Number.isFinite(opts.maxSteps) && opts.maxSteps > 0 ? Math.max(1, Math.floor(opts.maxSteps)) : void 0;
939
939
  this.contextWindow = opts.contextWindow ?? 128e3;
940
940
  this.systemPrompt = opts.systemPrompt;
941
941
  this.reasoning = opts.reasoning ?? "auto";
@@ -1028,7 +1028,7 @@ ${completion.text}` });
1028
1028
  try {
1029
1029
  if (this.lastInputTokens > this.contextWindow * 0.8 && !hasPendingProgrammaticReplay(this.messages)) await this.compact(signal);
1030
1030
  this.push({ role: "user", content: text });
1031
- for (let step = 0; step < this.maxSteps; step++) {
1031
+ for (let step = 0; this.maxSteps === void 0 || step < this.maxSteps; step++) {
1032
1032
  if (step > 0 && this.lastInputTokens > this.contextWindow * 0.8 && !hasPendingProgrammaticReplay(this.messages)) {
1033
1033
  await this.compact(signal);
1034
1034
  this.push({ role: "user", content: `Continue this task using the summary above: ${text}` });
@@ -1115,7 +1115,7 @@ ${completion.text}` });
1115
1115
  return;
1116
1116
  }
1117
1117
  }
1118
- this.onEvent({ type: "stepLimit" });
1118
+ if (this.maxSteps !== void 0) this.onEvent({ type: "stepLimit", maxSteps: this.maxSteps });
1119
1119
  } catch (error) {
1120
1120
  if (signal.aborted) {
1121
1121
  this.onEvent({ type: "aborted" });
@@ -1536,7 +1536,7 @@ ${list}`,
1536
1536
  # Your role
1537
1537
  ${def.prompt}`,
1538
1538
  cwd: opts.cwd,
1539
- maxSteps: def.maxSteps ?? 50,
1539
+ maxSteps: def.maxSteps,
1540
1540
  label: def.name,
1541
1541
  reasoning: opts.reasoning?.() ?? "auto",
1542
1542
  // Subagent edits belong to the parent's turn so /undo reverts them too.
@@ -1560,7 +1560,7 @@ ${def.prompt}`,
1560
1560
  const report = last && typeof last.content === "string" ? last.content : "(no report)";
1561
1561
  const suffix = outcome?.type === "stepLimit" ? `
1562
1562
 
1563
- (The subagent stopped at its ${def.maxSteps ?? 50}-step limit.)` : "";
1563
+ (The subagent stopped at its ${outcome.maxSteps}-step limit.)` : "";
1564
1564
  return { output: report + suffix, display };
1565
1565
  }
1566
1566
  });
@@ -1674,6 +1674,7 @@ function createRuntime(opts) {
1674
1674
  cwd: opts.cwd,
1675
1675
  history,
1676
1676
  reasoning,
1677
+ maxSteps: config.maxSteps,
1677
1678
  onMessage: (m) => session.append(m),
1678
1679
  onReset: () => session.reset(),
1679
1680
  onTurnStart: () => checkpoints.beginTurn(),
@@ -1758,7 +1759,7 @@ function createRuntime(opts) {
1758
1759
  }
1759
1760
 
1760
1761
  // src/version.ts
1761
- var VERSION = true ? "0.4.0-beta.35" : "0.0.0-dev";
1762
+ var VERSION = true ? "0.4.0-beta.36" : "0.0.0-dev";
1762
1763
 
1763
1764
  // src/cli.ts
1764
1765
  function chooseLanguage(argv) {
@@ -1783,17 +1784,17 @@ async function main() {
1783
1784
  const cwd = process.cwd();
1784
1785
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1785
1786
  if (args.command === "mcp") {
1786
- const { runMcpCommand } = await import("./mcpCli-UGNEDBMU.js");
1787
- const { readSecret } = await import("./login-7IARC5WX.js");
1787
+ const { runMcpCommand } = await import("./mcpCli-THHBIX7I.js");
1788
+ const { readSecret } = await import("./login-LKAO5PVR.js");
1788
1789
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1789
1790
  }
1790
1791
  if (args.command === "provider") {
1791
- const { runProviderCommand } = await import("./providerCli-4JTIW3YA.js");
1792
- const { readSecret } = await import("./login-7IARC5WX.js");
1792
+ const { runProviderCommand } = await import("./providerCli-WJXJW3W2.js");
1793
+ const { readSecret } = await import("./login-LKAO5PVR.js");
1793
1794
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1794
1795
  }
1795
1796
  if (args.command === "connect" || args.command === "disconnect" || args.command === "integrations") {
1796
- const { runIntegrationCommand } = await import("./integrationCli-W5LJQJ4J.js");
1797
+ const { runIntegrationCommand } = await import("./integrationCli-JWLY6E7E.js");
1797
1798
  return runIntegrationCommand(args, {
1798
1799
  env: process.env,
1799
1800
  cwd,
@@ -1816,7 +1817,7 @@ async function main() {
1816
1817
  }
1817
1818
  if (args.print) {
1818
1819
  if (!args.prompt) throw new ConfigError(t('-p mode needs a task, e.g. bccli -p "explain this repo"'));
1819
- const { serversToStart } = await import("./config-DMF4AO7E.js");
1820
+ const { serversToStart } = await import("./config-53MC3AH2.js");
1820
1821
  const plan = serversToStart(rt.home, cwd);
1821
1822
  for (const s of plan.needTrust) {
1822
1823
  console.error(t('Project MCP server "{name}" skipped (not approved yet; run interactive bccli once to approve it).', { name: s.name }));
@@ -1829,7 +1830,7 @@ async function main() {
1829
1830
  }
1830
1831
  }
1831
1832
  if (!process.stdin.isTTY) throw new ConfigError(t('Interactive mode needs a terminal. For scripts/CI use: bccli -p "task"'));
1832
- const { startInteractive } = await import("./ui-KK6ARPCL.js");
1833
+ const { startInteractive } = await import("./ui-I5BTZATT.js");
1833
1834
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1834
1835
  }
1835
1836
  main().then(
@@ -8,8 +8,8 @@ import {
8
8
  removeGlobalServer,
9
9
  serversToStart,
10
10
  setProjectTrust
11
- } from "./chunk-CDUPYHNL.js";
12
- import "./chunk-IJGTX7RC.js";
11
+ } from "./chunk-SZRM2VOB.js";
12
+ import "./chunk-CZMTWXCE.js";
13
13
  export {
14
14
  addGlobalServer,
15
15
  globalMcpPath,
@@ -6,7 +6,7 @@ import {
6
6
  readCredentials,
7
7
  resolveModel,
8
8
  t
9
- } from "./chunk-IJGTX7RC.js";
9
+ } from "./chunk-CZMTWXCE.js";
10
10
 
11
11
  // src/integrationCli.ts
12
12
  import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -23,11 +23,15 @@ function statePath(target, env) {
23
23
  function keyPath(env) {
24
24
  return join(integrationHome(env), "bc-cloud.key");
25
25
  }
26
+ var OPENCLAW_ENV_ID = "BOTCONNECTOR_API_KEY";
27
+ var HERMES_ENV_ID = "BOTCONNECTOR_API_KEY";
28
+ var HERMES_PROVIDER_ID = "botconnector-cloud";
29
+ var FALLBACK_DEFAULT_MODEL = "glm-5.3-flash";
26
30
  function parseTarget(raw) {
27
- if (raw === "opencode" || raw === "aider" || raw === "cline" || raw === "deepseek-harness" || raw === "cursor" || raw === "openai-cli" || raw === "openai-sdk" || raw === "openai-compatible" || raw === "codex" || raw === "claude-code") return raw;
31
+ if (raw === "opencode" || raw === "aider" || raw === "cline" || raw === "deepseek-harness" || raw === "cursor" || raw === "openai-cli" || raw === "openai-sdk" || raw === "openai-compatible" || raw === "codex" || raw === "claude-code" || raw === "openclaw" || raw === "hermes") return raw;
28
32
  if (raw === "dsh") return "deepseek-harness";
29
33
  throw new ConfigError(
30
- t("Agent must be one of: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code")
34
+ t("Agent must be one of: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code, openclaw, hermes")
31
35
  );
32
36
  }
33
37
  function homeDir(env) {
@@ -83,7 +87,9 @@ async function botConnectorModels(deps) {
83
87
  });
84
88
  if (!response.ok) throw new ConfigError(t("Could not load the BotConnector model catalog (HTTP {status}).", { status: response.status }));
85
89
  const payload = await response.json();
86
- return (payload.data ?? []).map((item) => item.id).filter((id) => typeof id === "string" && !!id);
90
+ const models = (payload.data ?? []).map((item) => item.id).filter((id) => typeof id === "string" && !!id);
91
+ if (!models.length) throw new ConfigError(t("The BotConnector model catalog is empty; cannot configure external agents."));
92
+ return models;
87
93
  }
88
94
  async function connectOpenCode(deps) {
89
95
  const home = homeDir(deps.env);
@@ -135,6 +141,15 @@ function stripManagedBlock(input) {
135
141
  return right ? `${left}
136
142
  ${right}` : left;
137
143
  }
144
+ function externalAgentDefaultModel(models, deps) {
145
+ try {
146
+ const config = loadConfig(deps.cwd, deps.env);
147
+ const preferred = config.model.startsWith("bc-cloud/") ? config.model.slice("bc-cloud/".length) : "";
148
+ if (preferred && models.includes(preferred)) return preferred;
149
+ } catch {
150
+ }
151
+ return models.includes(FALLBACK_DEFAULT_MODEL) ? FALLBACK_DEFAULT_MODEL : models[0];
152
+ }
138
153
  function yamlQuote(value) {
139
154
  return JSON.stringify(value.replace(/\\/g, "/"));
140
155
  }
@@ -537,6 +552,86 @@ async function connectClaudeCode(deps) {
537
552
  deps.out(`Launcher: ${launcher}`);
538
553
  deps.out(`Default coding model: ${model}`);
539
554
  }
555
+ async function connectOpenClaw(deps) {
556
+ const home = homeDir(deps.env);
557
+ const clawHome = deps.env.OPENCLAW_CONFIG_DIR || join(home, ".openclaw");
558
+ const path = join(clawHome, "openclaw.json");
559
+ const backup = safeBackup(path, deps.env);
560
+ ensureSecret(deps.env);
561
+ const models = await botConnectorModels(deps);
562
+ const model = externalAgentDefaultModel(models, deps);
563
+ let doc = {};
564
+ if (existsSync(path)) {
565
+ try {
566
+ doc = JSON.parse(readFileSync(path, "utf8"));
567
+ } catch {
568
+ throw new ConfigError(t("The OpenClaw config is not valid JSON: {path}", { path }));
569
+ }
570
+ }
571
+ const modelsRoot = doc.models && typeof doc.models === "object" && !Array.isArray(doc.models) ? { ...doc.models } : {};
572
+ const providers = modelsRoot.providers && typeof modelsRoot.providers === "object" && !Array.isArray(modelsRoot.providers) ? { ...modelsRoot.providers } : {};
573
+ const existing = providers[PROVIDER_ID];
574
+ if (existing?.baseUrl && existing.baseUrl.replace(/\/+$/, "") !== BASE_URL) {
575
+ throw new ConfigError(t('OpenClaw already has a provider "{id}" with a different endpoint ({base}). BCCLI will not overwrite it; disconnect or change that provider first.', { id: PROVIDER_ID, base: existing.baseUrl }));
576
+ }
577
+ const secretRef = { source: "env", provider: PROVIDER_ID, id: OPENCLAW_ENV_ID };
578
+ providers[PROVIDER_ID] = {
579
+ baseUrl: BASE_URL,
580
+ api: "openai-completions",
581
+ apiKey: secretRef,
582
+ // Live catalog ids only; OpenClaw resolves provider capabilities itself.
583
+ models: models.map((id) => ({ id, name: id }))
584
+ };
585
+ modelsRoot.providers = providers;
586
+ doc.models = modelsRoot;
587
+ mkdirSync(dirname(path), { recursive: true });
588
+ writeFileSync(path, `${JSON.stringify(doc, null, 2)}
589
+ `);
590
+ writeState("openclaw", {
591
+ target: "openclaw",
592
+ files: [{ path, ...backup }],
593
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
594
+ }, deps.env);
595
+ deps.out(t("OpenClaw is connected to BotConnector ({n} models; default {model}).", { n: models.length, model }));
596
+ deps.out(`Config: ${path}`);
597
+ deps.out(t("API key: env {env} (SecretRef, not stored in the config).", { env: OPENCLAW_ENV_ID }));
598
+ deps.out(t("Set the default model in OpenClaw with: openclaw models set {provider}/{model}", { provider: PROVIDER_ID, model }));
599
+ }
600
+ async function connectHermes(deps) {
601
+ const home = homeDir(deps.env);
602
+ const hermesHome = deps.env.HERMES_HOME || join(home, ".hermes");
603
+ const path = join(hermesHome, "config.yaml");
604
+ const backup = safeBackup(path, deps.env);
605
+ ensureSecret(deps.env);
606
+ const models = await botConnectorModels(deps);
607
+ const model = externalAgentDefaultModel(models, deps);
608
+ const source = existsSync(path) ? readFileSync(path, "utf8") : "";
609
+ const doc = parseDocument(source);
610
+ if (doc.errors.length) throw new ConfigError(t("The Hermes config is not valid YAML: {path}", { path }));
611
+ const previousProvider = doc.getIn(["providers", HERMES_PROVIDER_ID]);
612
+ if (previousProvider?.api && previousProvider.api.replace(/\/+$/, "") !== BASE_URL) {
613
+ throw new ConfigError(t('Hermes already has a provider "{id}" with a different endpoint ({base}). BCCLI will not overwrite it; disconnect or change that provider first.', { id: HERMES_PROVIDER_ID, base: previousProvider.api }));
614
+ }
615
+ doc.setIn(["providers", HERMES_PROVIDER_ID], {
616
+ api: BASE_URL,
617
+ key_env: HERMES_ENV_ID,
618
+ transport: "chat_completions",
619
+ default_model: model,
620
+ discover_models: true,
621
+ models: Object.fromEntries(models.map((id) => [id, {}]))
622
+ });
623
+ doc.setIn(["model"], { default: model, provider: `custom:${HERMES_PROVIDER_ID}` });
624
+ mkdirSync(hermesHome, { recursive: true });
625
+ writeFileSync(path, doc.toString());
626
+ writeState("hermes", {
627
+ target: "hermes",
628
+ files: [{ path, ...backup }],
629
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
630
+ }, deps.env);
631
+ deps.out(t("Hermes is connected to BotConnector ({n} models; default {model}).", { n: models.length, model }));
632
+ deps.out(`Config: ${path}`);
633
+ deps.out(t("API key: env {env} only (via key_env; a plaintext key in config.yaml is never written).", { env: HERMES_ENV_ID }));
634
+ }
540
635
  function disconnect(target, deps) {
541
636
  const state = readState(target, deps.env);
542
637
  if (!state) {
@@ -555,7 +650,7 @@ function disconnect(target, deps) {
555
650
  deps.out(t("{target}: the BotConnector integration was removed and the config restored.", { target }));
556
651
  }
557
652
  function listIntegrations(deps) {
558
- for (const target of ["opencode", "aider", "cline", "deepseek-harness", "cursor", "openai-cli", "openai-sdk", "openai-compatible", "codex", "claude-code"]) {
653
+ for (const target of ["opencode", "aider", "cline", "deepseek-harness", "cursor", "openai-cli", "openai-sdk", "openai-compatible", "codex", "claude-code", "openclaw", "hermes"]) {
559
654
  deps.out(`${target} ${readState(target, deps.env) ? "connected" : "not connected"}`);
560
655
  }
561
656
  }
@@ -578,6 +673,8 @@ async function runIntegrationCommand(args, deps) {
578
673
  else if (target === "openai-sdk") await connectOpenAiSdk(deps);
579
674
  else if (target === "openai-compatible") await connectOpenAiCompatible(deps);
580
675
  else if (target === "codex") await connectCodex(deps);
676
+ else if (target === "openclaw") await connectOpenClaw(deps);
677
+ else if (target === "hermes") await connectHermes(deps);
581
678
  else await connectClaudeCode(deps);
582
679
  return 0;
583
680
  }
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-FNBTY56Z.js";
5
+ } from "./chunk-NSCC5VMS.js";
6
6
  export {
7
7
  readSecret,
8
8
  runLogin
@@ -2,17 +2,17 @@
2
2
  import {
3
3
  CATALOG,
4
4
  fillTemplate
5
- } from "./chunk-GEG6QT5H.js";
5
+ } from "./chunk-5LSTEXSG.js";
6
6
  import {
7
7
  addGlobalServer,
8
8
  globalMcpPath,
9
9
  readMcpFile,
10
10
  removeGlobalServer
11
- } from "./chunk-CDUPYHNL.js";
11
+ } from "./chunk-SZRM2VOB.js";
12
12
  import {
13
13
  bccliHome,
14
14
  t
15
- } from "./chunk-IJGTX7RC.js";
15
+ } from "./chunk-CZMTWXCE.js";
16
16
 
17
17
  // src/mcpCli.ts
18
18
  async function runMcpCommand(args, deps) {
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ addProviderKey,
4
+ runProviderCommand
5
+ } from "./chunk-QKWSDZJQ.js";
6
+ import "./chunk-3DHJO7NC.js";
7
+ import "./chunk-3NTTXK4Y.js";
8
+ import "./chunk-CZMTWXCE.js";
9
+ export {
10
+ addProviderKey,
11
+ runProviderCommand
12
+ };
@@ -7,7 +7,7 @@ import {
7
7
  parseFrontmatter,
8
8
  toolLabel,
9
9
  validateGlobPattern
10
- } from "./chunk-LRMC4AOV.js";
10
+ } from "./chunk-HKHKYFZI.js";
11
11
  import {
12
12
  nextMode
13
13
  } from "./chunk-ELML4KVQ.js";
@@ -15,26 +15,26 @@ import {
15
15
  hasKey,
16
16
  providerName,
17
17
  writeGlobalConfig
18
- } from "./chunk-AOVIQXXO.js";
18
+ } from "./chunk-3DHJO7NC.js";
19
19
  import {
20
20
  REASONING_LEVELS,
21
21
  assertReasoningSupported,
22
22
  parseReasoningLevel,
23
23
  supportedReasoningLevels
24
- } from "./chunk-UJH2JT6H.js";
24
+ } from "./chunk-3NTTXK4Y.js";
25
25
  import {
26
26
  Session
27
27
  } from "./chunk-SJH3YLW4.js";
28
28
  import {
29
29
  CATALOG,
30
30
  fillTemplate
31
- } from "./chunk-GEG6QT5H.js";
31
+ } from "./chunk-5LSTEXSG.js";
32
32
  import {
33
33
  addGlobalServer,
34
34
  globalMcpPath,
35
35
  readMcpFile,
36
36
  removeGlobalServer
37
- } from "./chunk-CDUPYHNL.js";
37
+ } from "./chunk-SZRM2VOB.js";
38
38
  import {
39
39
  PRESETS,
40
40
  getLanguage,
@@ -43,7 +43,7 @@ import {
43
43
  removeCredential,
44
44
  setLanguage,
45
45
  t
46
- } from "./chunk-IJGTX7RC.js";
46
+ } from "./chunk-CZMTWXCE.js";
47
47
 
48
48
  // src/ui/index.tsx
49
49
  import { render } from "ink";
@@ -1180,7 +1180,7 @@ var nextId = 0;
1180
1180
  var entry = (e) => ({ ...e, id: nextId++ });
1181
1181
  var NOTICES = {
1182
1182
  compacted: ["Conversation summarized to fit the model context.", "info"],
1183
- stepLimit: ['Step limit of 50 reached. Type "continue" to keep going.', "warn"],
1183
+ stepLimit: ['Step limit of {n} reached. Type "continue" to keep going.', "warn"],
1184
1184
  aborted: ["Cancelled.", "warn"]
1185
1185
  };
1186
1186
  var isEmptyCont = (e) => e.kind === "assistant" && e.cont && !e.text;
@@ -1255,6 +1255,11 @@ function applyEvent(t2, event) {
1255
1255
  }
1256
1256
  case "error":
1257
1257
  return { ...t2, live: [...t2.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
1258
+ case "stepLimit": {
1259
+ const limit = event.maxSteps;
1260
+ const notice = NOTICES.stepLimit;
1261
+ return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0], { n: limit }), tone: notice[1] })] } : t2;
1262
+ }
1258
1263
  default: {
1259
1264
  const notice = NOTICES[event.type];
1260
1265
  return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0]), tone: notice[1] })] } : t2;
@@ -1964,7 +1969,7 @@ async function startInteractive(rt, opts) {
1964
1969
  if (chosen) rt.resume(chosen.session);
1965
1970
  }
1966
1971
  }
1967
- const { serversToStart, setProjectTrust } = await import("./config-DMF4AO7E.js");
1972
+ const { serversToStart, setProjectTrust } = await import("./config-53MC3AH2.js");
1968
1973
  const plan = serversToStart(rt.home, rt.cwd);
1969
1974
  if (plan.needTrust.length) {
1970
1975
  const { createInterface } = await import("readline/promises");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.0-beta.35",
3
+ "version": "0.4.0-beta.36",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- addProviderKey,
4
- runProviderCommand
5
- } from "./chunk-GJAX4NCM.js";
6
- import "./chunk-AOVIQXXO.js";
7
- import "./chunk-UJH2JT6H.js";
8
- import "./chunk-IJGTX7RC.js";
9
- export {
10
- addProviderKey,
11
- runProviderCommand
12
- };