@botconnector/bccli 0.4.0-beta.7 → 0.4.0-beta.9

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.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Agent AI coding dari BotConnector yang bekerja di terminal: membaca kode, menged
4
4
  dan menjalankan perintah — dengan izin kamu.
5
5
 
6
6
  ```bash
7
- npm i -g @botconnector/bccli@next # v0.2 beta, butuh Node.js 22+
7
+ npm i -g @botconnector/bccli@next # beta terbaru, butuh Node.js 22+
8
8
  bccli login # simpan API key BotConnector Cloud
9
9
  cd project-kamu && bccli
10
10
  ```
@@ -17,6 +17,7 @@ cd project-kamu && bccli
17
17
  | `bccli -p "tugas"` | satu tugas tanpa interaksi (skrip/CI) |
18
18
  | `bccli -c` / `bccli -r` | lanjutkan sesi terakhir / pilih sesi |
19
19
  | `bccli -m provider/model` | pilih model |
20
+ | `bccli --reasoning <auto|off|low|medium|high|max>` | pilih level reasoning untuk invocation ini |
20
21
  | `bccli models` | daftar model |
21
22
 
22
23
  Perintah di dalam sesi (ketik `/` untuk saran, ↑↓ pilih, Enter jalankan):
@@ -25,6 +26,7 @@ Perintah di dalam sesi (ketik `/` untuk saran, ↑↓ pilih, Enter jalankan):
25
26
  |---|---|
26
27
  | `/help` | daftar perintah dan pintasan |
27
28
  | `/model` | ganti model |
29
+ | `/reasoning` | pilih Auto / Off / Low / Medium / High / Max |
28
30
  | `/provider` | tambah/pilih provider AI |
29
31
  | `/mcp` | pasang/kelola server MCP |
30
32
  | `/new` | sesi baru (sesi lama tetap tersimpan) |
@@ -76,10 +78,32 @@ Provider lain yang OpenAI-compatible: pilih **Custom…** di `/provider`, atau
76
78
  `/model` menampilkan model dari **semua** provider yang sudah punya key, dikelompokkan
77
79
  per provider; ketik untuk menyaring. Pilihan tersimpan sebagai default.
78
80
 
81
+ Reasoning dapat dipilih lewat `bccli --reasoning <auto|off|low|medium|high|max>` atau `/reasoning`.
82
+ **Auto** memakai default model/provider dan BCCLI tidak mengirim override reasoning apa pun. BCCLI tidak
83
+ otomatis menurunkan atau menaikkan reasoning effort. Level manual diteruskan bila kontrak provider/model
84
+ mendukungnya; level yang tidak didukung menghasilkan error yang jelas dan tidak di-downgrade diam-diam.
85
+
79
86
  `.bccli/config.json` di dalam project hanya boleh memilih `model` dan menambah provider baru tanpa API key —
87
+ preference `reasoning` hanya dibaca dari config user `~/.bccli/config.json`, bukan dari repo yang di-clone —
80
88
  izin (`permissionMode`, `allow`) dan provider yang sudah ada hanya diatur dari `~/.bccli/config.json`,
81
89
  supaya repo yang kamu clone tidak bisa memberi dirinya izin penuh atau mencuri API key.
82
90
 
91
+ ## Integrasi agent eksternal
92
+
93
+ BCCLI dapat memasang BotConnector sebagai provider untuk agent lain:
94
+
95
+ | Perintah | Fungsi |
96
+ |---|---|
97
+ | `bccli integrations` | lihat status integrasi |
98
+ | `bccli connect opencode` | hubungkan OpenCode ke BotConnector |
99
+ | `bccli connect aider` | hubungkan Aider ke BotConnector |
100
+ | `bccli disconnect <agent>` | pulihkan config sebelum integrasi |
101
+
102
+ OpenCode memakai config global `~/.config/opencode/opencode.json`. BCCLI mengambil katalog model BotConnector saat connect,
103
+ menjaga config provider lain, dan menyimpan secret di file terpisah di `~/.bccli/integrations/`.
104
+ Aider memakai `~/.aider.conf.yml` yang menunjuk ke env file BCCLI terpisah; BCCLI menolak menimpa setting
105
+ OpenAI/env-file Aider yang sudah ada. Sebelum mengubah config eksternal, BCCLI membuat backup agar disconnect dapat memulihkannya.
106
+
83
107
  ## Internet
84
108
 
85
109
  - `web_search`: mencari di web tanpa perlu izin. Memakai akun BotConnector Cloud
@@ -2,9 +2,10 @@
2
2
  import {
3
3
  HELP_TEXT,
4
4
  parseCliArgs
5
- } from "./chunk-UN7QXPCB.js";
5
+ } from "./chunk-ZYCYTEKD.js";
6
6
  import "./chunk-ELML4KVQ.js";
7
- import "./chunk-RZUJYAQV.js";
7
+ import "./chunk-CLN4Z5E3.js";
8
+ import "./chunk-2MGINW4O.js";
8
9
  export {
9
10
  HELP_TEXT,
10
11
  parseCliArgs
@@ -22,6 +22,7 @@ var ConfigError = class extends Error {
22
22
  var DEFAULT_CONFIG = {
23
23
  model: "bc-cloud/glm-5.3-flash",
24
24
  permissionMode: "default",
25
+ reasoning: "auto",
25
26
  providers: Object.fromEntries(PRESETS.map((p) => [p.id, { baseURL: p.baseURL, apiKeyEnv: p.apiKeyEnv }])),
26
27
  allow: []
27
28
  };
@@ -52,6 +53,7 @@ function loadConfig(cwd, env = process.env) {
52
53
  return {
53
54
  model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
54
55
  permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
56
+ reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
55
57
  providers: { ...known, ...fromProject },
56
58
  projectProviders: Object.keys(fromProject),
57
59
  allow: [...global.allow ?? []]
@@ -5,7 +5,7 @@ import {
5
5
  readCredentials,
6
6
  readJsonConfig,
7
7
  removeCredential
8
- } from "./chunk-RZUJYAQV.js";
8
+ } from "./chunk-2MGINW4O.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
  ConfigError
4
- } from "./chunk-RZUJYAQV.js";
4
+ } from "./chunk-2MGINW4O.js";
5
5
 
6
6
  // src/mcp/config.ts
7
7
  import { createHash } from "crypto";
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/reasoning.ts
4
+ var REASONING_LEVELS = ["auto", "off", "low", "medium", "high", "max"];
5
+ var OPENAI_LEVELS = ["auto", "low", "medium", "high"];
6
+ var BOTCONNECTOR_LEVELS = REASONING_LEVELS;
7
+ function isReasoningLevel(value) {
8
+ return REASONING_LEVELS.includes(value);
9
+ }
10
+ function parseReasoningLevel(value, label = "reasoning") {
11
+ if (!isReasoningLevel(value)) {
12
+ throw new Error(`${label} harus salah satu dari: ${REASONING_LEVELS.join(", ")}`);
13
+ }
14
+ return value;
15
+ }
16
+ function reasoningCapability(providerId) {
17
+ switch (providerId) {
18
+ case "bc-cloud":
19
+ return { levels: BOTCONNECTOR_LEVELS, parameter: "reasoning_effort" };
20
+ case "openai":
21
+ return { levels: OPENAI_LEVELS, parameter: "reasoning_effort" };
22
+ case "openrouter":
23
+ return { levels: ["auto", "low", "medium", "high"], parameter: "reasoning" };
24
+ default:
25
+ return void 0;
26
+ }
27
+ }
28
+ function supportedReasoningLevels(providerId) {
29
+ return reasoningCapability(providerId)?.levels ?? ["auto"];
30
+ }
31
+ function assertReasoningSupported(providerId, level) {
32
+ if (level === "auto") return;
33
+ const capability = reasoningCapability(providerId);
34
+ if (!capability) {
35
+ throw new Error("Provider ini belum memiliki kontrak reasoning manual. Gunakan Auto.");
36
+ }
37
+ if (!capability.levels.includes(level)) {
38
+ const supported = capability.levels.filter((x) => x !== "auto").map(capitalize).join("/");
39
+ throw new Error(`Model/provider ini tidak mendukung reasoning level '${level}'. Gunakan Auto${supported ? `/${supported}` : ""}.`);
40
+ }
41
+ }
42
+ function reasoningPayload(providerId, level) {
43
+ if (level === "auto") return {};
44
+ assertReasoningSupported(providerId, level);
45
+ const capability = reasoningCapability(providerId);
46
+ if (capability.parameter === "reasoning") return { reasoning: { effort: level } };
47
+ return { reasoning_effort: level };
48
+ }
49
+ function capitalize(value) {
50
+ return value.charAt(0).toUpperCase() + value.slice(1);
51
+ }
52
+
53
+ export {
54
+ REASONING_LEVELS,
55
+ isReasoningLevel,
56
+ parseReasoningLevel,
57
+ supportedReasoningLevels,
58
+ assertReasoningSupported,
59
+ reasoningPayload
60
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigError
4
- } from "./chunk-RZUJYAQV.js";
4
+ } from "./chunk-2MGINW4O.js";
5
5
 
6
6
  // src/mcp/catalog.ts
7
7
  var CATALOG = [
@@ -35,8 +35,8 @@ function readSecret(prompt) {
35
35
  });
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-6ON7QEVN.js");
39
- const { parseCliArgs } = await import("./args-FCLPQ7TO.js");
38
+ const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
39
+ const { parseCliArgs } = await import("./args-JPVSGCGY.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
42
42
  cwd,
@@ -4,13 +4,16 @@ import {
4
4
  providerName,
5
5
  removeGlobalProvider,
6
6
  writeGlobalConfig
7
- } from "./chunk-U7DLMVPZ.js";
7
+ } from "./chunk-7S6JXQCH.js";
8
+ import {
9
+ reasoningPayload
10
+ } from "./chunk-CLN4Z5E3.js";
8
11
  import {
9
12
  PRESETS,
10
13
  loadConfig,
11
14
  readCredentials,
12
15
  saveCredential
13
- } from "./chunk-RZUJYAQV.js";
16
+ } from "./chunk-2MGINW4O.js";
14
17
 
15
18
  // src/thinking.ts
16
19
  var OPEN = "<think>";
@@ -188,7 +191,7 @@ async function readJson(res) {
188
191
  const thinking = (choice?.message?.reasoning_content || choice?.message?.reasoning || "") + split.thinking;
189
192
  return { text: split.text, toolCalls, thinking: thinking || void 0, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
190
193
  }
191
- function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryDelayMs = 1e3 }) {
194
+ function createProvider({ baseURL, apiKey, model, providerId, fetch: doFetch = fetch, retryDelayMs = 1e3 }) {
192
195
  const headers = { "content-type": "application/json" };
193
196
  if (apiKey) headers.authorization = `Bearer ${apiKey}`;
194
197
  if (/^https:\/\/api\.botconnector\.id\/v1\/?$/.test(baseURL) && apiKey?.startsWith("bc_live_")) {
@@ -215,10 +218,16 @@ function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryD
215
218
  throw lastError ?? new ProviderError("Permintaan gagal");
216
219
  }
217
220
  return {
218
- async chat({ messages, tools, signal, onText, onThinking }) {
221
+ async chat({ messages, tools, signal, onText, onThinking, reasoning = "auto" }) {
222
+ let reasoningOverride;
223
+ try {
224
+ reasoningOverride = reasoningPayload(providerId, reasoning);
225
+ } catch (error) {
226
+ throw new ProviderError(error.message);
227
+ }
219
228
  const res = await post(
220
229
  "/chat/completions",
221
- { model, messages, stream: true, stream_options: { include_usage: true }, ...tools?.length ? { tools } : {} },
230
+ { model, messages, stream: true, stream_options: { include_usage: true }, ...tools?.length ? { tools } : {}, ...reasoningOverride },
222
231
  signal
223
232
  );
224
233
  if ((res.headers.get("content-type") ?? "").includes("application/json")) return readJson(res);
@@ -2,9 +2,13 @@
2
2
  import {
3
3
  MODE_ORDER
4
4
  } from "./chunk-ELML4KVQ.js";
5
+ import {
6
+ REASONING_LEVELS,
7
+ isReasoningLevel
8
+ } from "./chunk-CLN4Z5E3.js";
5
9
  import {
6
10
  ConfigError
7
- } from "./chunk-RZUJYAQV.js";
11
+ } from "./chunk-2MGINW4O.js";
8
12
 
9
13
  // src/args.ts
10
14
  import { parseArgs } from "util";
@@ -17,9 +21,13 @@ Pemakaian:
17
21
  bccli models daftar model dari provider aktif
18
22
  bccli provider list|add <id>|remove <id> kelola provider (custom: --url <url> [--name N] [--key-env ENV])
19
23
  bccli mcp list|add <nama>|remove <nama> kelola server MCP (katalog, atau --url <url>)
24
+ bccli integrations lihat status integrasi agent eksternal
25
+ bccli connect <agent> hubungkan BotConnector ke agent (opencode, aider)
26
+ bccli disconnect <agent> lepas integrasi dan pulihkan config sebelumnya
20
27
 
21
28
  Opsi:
22
29
  -m, --model <provider/model> pilih model, contoh bc-cloud/glm-5.3-flash
30
+ --reasoning <level> auto | off | low | medium | high | max
23
31
  -c, --continue lanjutkan sesi terakhir di folder ini
24
32
  -r, --resume pilih sesi untuk dilanjutkan
25
33
  --allow-all jalankan semua alat tanpa minta izin
@@ -34,6 +42,7 @@ function parseCliArgs(argv) {
34
42
  options: {
35
43
  print: { type: "boolean", short: "p" },
36
44
  model: { type: "string", short: "m" },
45
+ reasoning: { type: "string" },
37
46
  continue: { type: "boolean", short: "c" },
38
47
  resume: { type: "boolean", short: "r" },
39
48
  "allow-all": { type: "boolean" },
@@ -47,12 +56,16 @@ function parseCliArgs(argv) {
47
56
  value: { type: "string", multiple: true }
48
57
  }
49
58
  });
59
+ const reasoning = values.reasoning;
60
+ if (reasoning && !isReasoningLevel(reasoning)) {
61
+ throw new ConfigError(`--reasoning harus salah satu dari: ${REASONING_LEVELS.join(", ")}`);
62
+ }
50
63
  const mode = values["permission-mode"];
51
64
  if (mode && !MODE_ORDER.includes(mode)) {
52
65
  throw new ConfigError(`--permission-mode harus salah satu dari: ${MODE_ORDER.join(", ")}`);
53
66
  }
54
67
  const [first, ...rest] = positionals;
55
- const SUBCOMMANDS = ["login", "models", "provider", "mcp"];
68
+ const SUBCOMMANDS = ["login", "models", "provider", "mcp", "connect", "disconnect", "integrations"];
56
69
  const command = SUBCOMMANDS.includes(first) ? first : "run";
57
70
  const words = command === "run" ? positionals : rest;
58
71
  return {
@@ -60,6 +73,7 @@ function parseCliArgs(argv) {
60
73
  prompt: command === "run" && words.length ? words.join(" ") : void 0,
61
74
  print: !!values.print,
62
75
  model: values.model,
76
+ reasoning,
63
77
  continue: !!values.continue,
64
78
  resume: !!values.resume,
65
79
  allowAll: !!values["allow-all"],
@@ -68,7 +82,7 @@ function parseCliArgs(argv) {
68
82
  help: !!values.help,
69
83
  version: !!values.version,
70
84
  loginProvider: command === "login" ? rest[0] ?? "bc-cloud" : void 0,
71
- subArgs: command === "provider" || command === "mcp" ? rest : [],
85
+ subArgs: ["provider", "mcp", "connect", "disconnect", "integrations"].includes(command) ? rest : [],
72
86
  url: values.url,
73
87
  name: values.name,
74
88
  keyEnv: values["key-env"],
package/dist/cli.js CHANGED
@@ -1,16 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- HELP_TEXT,
4
- parseCliArgs
5
- } from "./chunk-UN7QXPCB.js";
6
- import {
7
- addProviderKey,
8
- createProvider,
9
- listAllModels
10
- } from "./chunk-FMM53YSU.js";
11
- import {
12
- runLogin
13
- } from "./chunk-BRTSYM5O.js";
14
2
  import {
15
3
  BUILTIN_AGENTS,
16
4
  buildSystemPrompt,
@@ -21,12 +9,25 @@ import {
21
9
  parseFrontmatter,
22
10
  runPrint
23
11
  } from "./chunk-KV27KTOX.js";
12
+ import {
13
+ HELP_TEXT,
14
+ parseCliArgs
15
+ } from "./chunk-ZYCYTEKD.js";
24
16
  import {
25
17
  Permissions
26
18
  } from "./chunk-ELML4KVQ.js";
19
+ import {
20
+ addProviderKey,
21
+ createProvider,
22
+ listAllModels
23
+ } from "./chunk-U7GK73CK.js";
27
24
  import {
28
25
  providerName
29
- } from "./chunk-U7DLMVPZ.js";
26
+ } from "./chunk-7S6JXQCH.js";
27
+ import "./chunk-CLN4Z5E3.js";
28
+ import {
29
+ runLogin
30
+ } from "./chunk-ISGBGFSU.js";
30
31
  import {
31
32
  Session,
32
33
  pruneSessions
@@ -36,7 +37,7 @@ import {
36
37
  bccliHome,
37
38
  loadConfig,
38
39
  resolveModel
39
- } from "./chunk-RZUJYAQV.js";
40
+ } from "./chunk-2MGINW4O.js";
40
41
 
41
42
  // src/tools/index.ts
42
43
  import { z as z8 } from "zod";
@@ -616,6 +617,7 @@ var Agent = class {
616
617
  this.maxSteps = opts.maxSteps ?? 50;
617
618
  this.contextWindow = opts.contextWindow ?? 128e3;
618
619
  this.systemPrompt = opts.systemPrompt;
620
+ this.reasoning = opts.reasoning ?? "auto";
619
621
  }
620
622
  opts;
621
623
  provider;
@@ -625,6 +627,7 @@ var Agent = class {
625
627
  lastInputTokens = 0;
626
628
  onEvent = () => {
627
629
  };
630
+ reasoning;
628
631
  askPermission = async () => "no";
629
632
  tools;
630
633
  definitions;
@@ -674,7 +677,8 @@ var Agent = class {
674
677
  messages: [{ role: "system", content: this.systemPrompt }, ...this.messages, { role: "user", content: COMPACT_PROMPT }],
675
678
  // Some gateways reject tool_calls in history when no tools are declared.
676
679
  tools: this.definitions,
677
- signal
680
+ signal,
681
+ reasoning: this.reasoning
678
682
  });
679
683
  this.clear();
680
684
  this.push({ role: "user", content: `Ringkasan percakapan sebelumnya:
@@ -699,7 +703,8 @@ ${completion.text}` });
699
703
  tools: this.turnDefinitions,
700
704
  signal,
701
705
  onText: (delta) => this.onEvent({ type: "text", delta }),
702
- onThinking: (delta) => this.onEvent({ type: "thinking", delta })
706
+ onThinking: (delta) => this.onEvent({ type: "thinking", delta }),
707
+ reasoning: this.reasoning
703
708
  });
704
709
  if (!completion.toolCalls.length && completion.finishReason !== "repetition") {
705
710
  const recovered = recoverTextToolCalls(completion.text, this.turnDefinitions);
@@ -1161,6 +1166,7 @@ ${def.prompt}`,
1161
1166
  cwd: opts.cwd,
1162
1167
  maxSteps: def.maxSteps ?? 50,
1163
1168
  label: def.name,
1169
+ reasoning: opts.reasoning?.() ?? "auto",
1164
1170
  // Subagent edits belong to the parent's turn so /undo reverts them too.
1165
1171
  checkpoint: ctx.checkpoint
1166
1172
  });
@@ -1229,9 +1235,10 @@ function createRuntime(opts) {
1229
1235
  const home = bccliHome(env);
1230
1236
  let config = loadConfig(opts.cwd, env);
1231
1237
  let modelRef = opts.args.model ?? config.model;
1238
+ let reasoning = opts.args.reasoning ?? config.reasoning;
1232
1239
  const makeProvider = (ref) => {
1233
1240
  const resolved = resolveModel(config, ref, env);
1234
- return opts.provider ?? createProvider({ baseURL: resolved.baseURL, apiKey: resolved.apiKey, model: resolved.model });
1241
+ return opts.provider ?? createProvider({ baseURL: resolved.baseURL, apiKey: resolved.apiKey, model: resolved.model, providerId: resolved.providerId });
1235
1242
  };
1236
1243
  const provider = makeProvider(modelRef);
1237
1244
  const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
@@ -1274,7 +1281,8 @@ function createRuntime(opts) {
1274
1281
  provider: () => agent.provider,
1275
1282
  providerFor: makeProvider,
1276
1283
  systemPrompt,
1277
- cwd: opts.cwd
1284
+ cwd: opts.cwd,
1285
+ reasoning: () => reasoning
1278
1286
  });
1279
1287
  const agent = new Agent({
1280
1288
  provider,
@@ -1283,6 +1291,7 @@ function createRuntime(opts) {
1283
1291
  systemPrompt,
1284
1292
  cwd: opts.cwd,
1285
1293
  history,
1294
+ reasoning,
1286
1295
  onMessage: (m) => session.append(m),
1287
1296
  onReset: () => session.reset(),
1288
1297
  onTurnStart: () => checkpoints.beginTurn(),
@@ -1324,6 +1333,9 @@ function createRuntime(opts) {
1324
1333
  get modelRef() {
1325
1334
  return modelRef;
1326
1335
  },
1336
+ get reasoning() {
1337
+ return reasoning;
1338
+ },
1327
1339
  agent,
1328
1340
  get session() {
1329
1341
  return session;
@@ -1353,12 +1365,16 @@ function createRuntime(opts) {
1353
1365
  setModel(ref) {
1354
1366
  agent.provider = makeProvider(ref);
1355
1367
  modelRef = ref;
1368
+ },
1369
+ setReasoning(level) {
1370
+ reasoning = level;
1371
+ agent.reasoning = level;
1356
1372
  }
1357
1373
  };
1358
1374
  }
1359
1375
 
1360
1376
  // src/version.ts
1361
- var VERSION = true ? "0.4.0-beta.7" : "0.0.0-dev";
1377
+ var VERSION = true ? "0.4.0-beta.9" : "0.0.0-dev";
1362
1378
 
1363
1379
  // src/cli.ts
1364
1380
  async function main() {
@@ -1374,15 +1390,25 @@ async function main() {
1374
1390
  const cwd = process.cwd();
1375
1391
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1376
1392
  if (args.command === "mcp") {
1377
- const { runMcpCommand } = await import("./mcpCli-Q2H63ISP.js");
1378
- const { readSecret } = await import("./login-4277DGKX.js");
1393
+ const { runMcpCommand } = await import("./mcpCli-DZWQPTOS.js");
1394
+ const { readSecret } = await import("./login-6K32PIXV.js");
1379
1395
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1380
1396
  }
1381
1397
  if (args.command === "provider") {
1382
- const { runProviderCommand } = await import("./providerCli-6ON7QEVN.js");
1383
- const { readSecret } = await import("./login-4277DGKX.js");
1398
+ const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
1399
+ const { readSecret } = await import("./login-6K32PIXV.js");
1384
1400
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1385
1401
  }
1402
+ if (args.command === "connect" || args.command === "disconnect" || args.command === "integrations") {
1403
+ const { runIntegrationCommand } = await import("./integrationCli-BGT5UZU4.js");
1404
+ return runIntegrationCommand(args, {
1405
+ env: process.env,
1406
+ cwd,
1407
+ out: (s) => console.log(s),
1408
+ err: (s) => console.error(s),
1409
+ fetch: globalThis.fetch
1410
+ });
1411
+ }
1386
1412
  if (args.command === "models") {
1387
1413
  const config = loadConfig(cwd);
1388
1414
  const resolved = resolveModel(config, args.model ?? config.model);
@@ -1397,7 +1423,7 @@ async function main() {
1397
1423
  }
1398
1424
  if (args.print) {
1399
1425
  if (!args.prompt) throw new ConfigError('Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"');
1400
- const { serversToStart } = await import("./config-NPSYFAFD.js");
1426
+ const { serversToStart } = await import("./config-YUNKPCNB.js");
1401
1427
  const plan = serversToStart(rt.home, cwd);
1402
1428
  for (const s of plan.needTrust) {
1403
1429
  console.error(`Server MCP project "${s.name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).`);
@@ -1410,7 +1436,7 @@ async function main() {
1410
1436
  }
1411
1437
  }
1412
1438
  if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
1413
- const { startInteractive } = await import("./ui-4EBFPASA.js");
1439
+ const { startInteractive } = await import("./ui-6GTQUJOE.js");
1414
1440
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1415
1441
  }
1416
1442
  main().then(
@@ -8,8 +8,8 @@ import {
8
8
  removeGlobalServer,
9
9
  serversToStart,
10
10
  setProjectTrust
11
- } from "./chunk-NGLKANMD.js";
12
- import "./chunk-RZUJYAQV.js";
11
+ } from "./chunk-BFZXRKW5.js";
12
+ import "./chunk-2MGINW4O.js";
13
13
  export {
14
14
  addGlobalServer,
15
15
  globalMcpPath,
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ConfigError,
4
+ bccliHome,
5
+ loadConfig,
6
+ readCredentials,
7
+ resolveModel
8
+ } from "./chunk-2MGINW4O.js";
9
+
10
+ // src/integrationCli.ts
11
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
12
+ import { dirname, join } from "path";
13
+ var BASE_URL = "https://api.botconnector.id/v1";
14
+ var PROVIDER_ID = "botconnector";
15
+ function integrationHome(env) {
16
+ return join(bccliHome(env), "integrations");
17
+ }
18
+ function statePath(target, env) {
19
+ return join(integrationHome(env), `${target}.state.json`);
20
+ }
21
+ function keyPath(env) {
22
+ return join(integrationHome(env), "bc-cloud.key");
23
+ }
24
+ function parseTarget(raw) {
25
+ if (raw === "opencode" || raw === "aider") return raw;
26
+ throw new ConfigError("Agent harus salah satu dari: opencode, aider");
27
+ }
28
+ function homeDir(env) {
29
+ const value = env.HOME || env.USERPROFILE;
30
+ if (!value) throw new ConfigError("Home directory tidak dapat ditentukan.");
31
+ return value;
32
+ }
33
+ function ensureSecret(env) {
34
+ const key = readCredentials(env)["bc-cloud"] || env.BOTCONNECTOR_API_KEY;
35
+ if (!key) throw new ConfigError("API key BotConnector belum tersedia. Jalankan bccli login bc-cloud terlebih dahulu.");
36
+ const dir = integrationHome(env);
37
+ mkdirSync(dir, { recursive: true });
38
+ const path = keyPath(env);
39
+ writeFileSync(path, `${key}
40
+ `, { mode: 384 });
41
+ try {
42
+ chmodSync(path, 384);
43
+ } catch {
44
+ }
45
+ return path;
46
+ }
47
+ function safeBackup(path, env) {
48
+ if (!existsSync(path)) return { created: true };
49
+ const dir = integrationHome(env);
50
+ mkdirSync(dir, { recursive: true });
51
+ const name = path.split(/[\\/]/).pop() || "config";
52
+ const backup = join(dir, `${name}.${Date.now()}.bak`);
53
+ copyFileSync(path, backup);
54
+ return { backup, created: false };
55
+ }
56
+ function writeState(target, state, env) {
57
+ const path = statePath(target, env);
58
+ mkdirSync(dirname(path), { recursive: true });
59
+ writeFileSync(path, `${JSON.stringify(state, null, 2)}
60
+ `, { mode: 384 });
61
+ }
62
+ function readState(target, env) {
63
+ const path = statePath(target, env);
64
+ if (!existsSync(path)) return null;
65
+ try {
66
+ return JSON.parse(readFileSync(path, "utf8"));
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ async function botConnectorModels(deps) {
72
+ const config = loadConfig(deps.cwd);
73
+ const resolved = resolveModel(config, config.model, deps.env);
74
+ const key = readCredentials(deps.env)["bc-cloud"] || resolved.apiKey;
75
+ if (!key) throw new ConfigError("API key BotConnector belum tersedia. Jalankan bccli login bc-cloud terlebih dahulu.");
76
+ const response = await deps.fetch(`${BASE_URL}/models`, {
77
+ headers: {
78
+ authorization: `Bearer ${key}`,
79
+ "x-botconnector-client": "bccli",
80
+ "x-botconnector-client-version": "0.4.0"
81
+ }
82
+ });
83
+ if (!response.ok) throw new ConfigError(`Katalog model BotConnector gagal dimuat (HTTP ${response.status}).`);
84
+ const payload = await response.json();
85
+ return (payload.data ?? []).map((item) => item.id).filter((id) => typeof id === "string" && !!id);
86
+ }
87
+ async function connectOpenCode(deps) {
88
+ const home = homeDir(deps.env);
89
+ const path = join(home, ".config", "opencode", "opencode.json");
90
+ const backup = safeBackup(path, deps.env);
91
+ const secret = ensureSecret(deps.env);
92
+ const models = await botConnectorModels(deps);
93
+ let doc = {};
94
+ if (existsSync(path)) {
95
+ try {
96
+ doc = JSON.parse(readFileSync(path, "utf8"));
97
+ } catch {
98
+ throw new ConfigError(`Config OpenCode tidak valid JSON: ${path}`);
99
+ }
100
+ }
101
+ const providers = doc.provider && typeof doc.provider === "object" && !Array.isArray(doc.provider) ? { ...doc.provider } : {};
102
+ providers[PROVIDER_ID] = {
103
+ npm: "@ai-sdk/openai-compatible",
104
+ name: "BotConnector",
105
+ options: {
106
+ baseURL: BASE_URL,
107
+ apiKey: `{file:${secret.replace(/\\/g, "/")}}`
108
+ },
109
+ models: Object.fromEntries(models.map((id) => [id, { name: id }]))
110
+ };
111
+ if (!doc.$schema) doc.$schema = "https://opencode.ai/config.json";
112
+ doc.provider = providers;
113
+ mkdirSync(dirname(path), { recursive: true });
114
+ writeFileSync(path, `${JSON.stringify(doc, null, 2)}
115
+ `);
116
+ writeState("opencode", {
117
+ target: "opencode",
118
+ files: [{ path, ...backup }],
119
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
120
+ }, deps.env);
121
+ deps.out(`OpenCode terhubung ke BotConnector (${models.length} model).`);
122
+ deps.out(`Config: ${path}`);
123
+ }
124
+ var AIDER_BEGIN = "# BEGIN BCCLI BOTCONNECTOR";
125
+ var AIDER_END = "# END BCCLI BOTCONNECTOR";
126
+ function stripManagedBlock(input) {
127
+ const start = input.indexOf(AIDER_BEGIN);
128
+ if (start < 0) return input;
129
+ const end = input.indexOf(AIDER_END, start);
130
+ if (end < 0) return input;
131
+ const after = end + AIDER_END.length;
132
+ const left = input.slice(0, start).trimEnd();
133
+ const right = input.slice(after).trimStart();
134
+ return right ? `${left}
135
+ ${right}` : left;
136
+ }
137
+ function yamlQuote(value) {
138
+ return JSON.stringify(value.replace(/\\/g, "/"));
139
+ }
140
+ async function connectAider(deps) {
141
+ const home = homeDir(deps.env);
142
+ const configPath = join(home, ".aider.conf.yml");
143
+ const envPath = join(integrationHome(deps.env), "aider.env");
144
+ const configBackup = safeBackup(configPath, deps.env);
145
+ const envBackup = safeBackup(envPath, deps.env);
146
+ const secret = ensureSecret(deps.env);
147
+ const key = readFileSync(secret, "utf8").trim();
148
+ const config = loadConfig(deps.cwd);
149
+ const defaultModel = config.model.startsWith("bc-cloud/") ? config.model.slice("bc-cloud/".length) : "glm-5.3-flash";
150
+ let existing = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
151
+ existing = stripManagedBlock(existing);
152
+ if (/^\s*(env-file|openai-api-base|openai-api-key)\s*:/m.test(existing)) {
153
+ throw new ConfigError(`Config Aider sudah memiliki env-file/openai-api setting sendiri: ${configPath}. BCCLI tidak akan menimpanya.`);
154
+ }
155
+ mkdirSync(dirname(envPath), { recursive: true });
156
+ writeFileSync(envPath, [
157
+ `OPENAI_API_BASE=${BASE_URL}`,
158
+ `OPENAI_API_KEY=${key}`,
159
+ `AIDER_MODEL=openai/${defaultModel}`,
160
+ ""
161
+ ].join("\n"), { mode: 384 });
162
+ try {
163
+ chmodSync(envPath, 384);
164
+ } catch {
165
+ }
166
+ const managed = [
167
+ AIDER_BEGIN,
168
+ `env-file: ${yamlQuote(envPath)}`,
169
+ AIDER_END
170
+ ].join("\n");
171
+ const merged = existing.trim() ? `${existing.trimEnd()}
172
+
173
+ ${managed}
174
+ ` : `${managed}
175
+ `;
176
+ writeFileSync(configPath, merged);
177
+ writeState("aider", {
178
+ target: "aider",
179
+ files: [{ path: configPath, ...configBackup }, { path: envPath, ...envBackup }],
180
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
181
+ }, deps.env);
182
+ deps.out("Aider terhubung ke BotConnector.");
183
+ deps.out(`Config: ${configPath}`);
184
+ }
185
+ function disconnect(target, deps) {
186
+ const state = readState(target, deps.env);
187
+ if (!state) {
188
+ deps.out(`${target}: belum dikelola oleh BCCLI.`);
189
+ return;
190
+ }
191
+ for (const file of [...state.files].reverse()) {
192
+ if (file.backup && existsSync(file.backup)) {
193
+ mkdirSync(dirname(file.path), { recursive: true });
194
+ copyFileSync(file.backup, file.path);
195
+ } else if (file.created) {
196
+ rmSync(file.path, { force: true });
197
+ }
198
+ }
199
+ rmSync(statePath(target, deps.env), { force: true });
200
+ deps.out(`${target}: integrasi BotConnector dilepas dan config dipulihkan.`);
201
+ }
202
+ function listIntegrations(deps) {
203
+ for (const target of ["opencode", "aider"]) {
204
+ deps.out(`${target} ${readState(target, deps.env) ? "connected" : "not connected"}`);
205
+ }
206
+ }
207
+ async function runIntegrationCommand(args, deps) {
208
+ if (args.command === "integrations") {
209
+ listIntegrations(deps);
210
+ return 0;
211
+ }
212
+ const target = parseTarget(args.subArgs[0]);
213
+ if (args.command === "disconnect") {
214
+ disconnect(target, deps);
215
+ return 0;
216
+ }
217
+ if (target === "opencode") await connectOpenCode(deps);
218
+ else await connectAider(deps);
219
+ return 0;
220
+ }
221
+ export {
222
+ runIntegrationCommand
223
+ };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-BRTSYM5O.js";
5
+ } from "./chunk-ISGBGFSU.js";
6
6
  export {
7
7
  readSecret,
8
8
  runLogin
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  CATALOG,
4
4
  fillTemplate
5
- } from "./chunk-UESQNU4A.js";
5
+ } from "./chunk-DX7CZTBB.js";
6
6
  import {
7
7
  addGlobalServer,
8
8
  globalMcpPath,
9
9
  readMcpFile,
10
10
  removeGlobalServer
11
- } from "./chunk-NGLKANMD.js";
11
+ } from "./chunk-BFZXRKW5.js";
12
12
  import {
13
13
  bccliHome
14
- } from "./chunk-RZUJYAQV.js";
14
+ } from "./chunk-2MGINW4O.js";
15
15
 
16
16
  // src/mcpCli.ts
17
17
  async function runMcpCommand(args, deps) {
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ addProviderKey,
4
+ runProviderCommand
5
+ } from "./chunk-U7GK73CK.js";
6
+ import "./chunk-7S6JXQCH.js";
7
+ import "./chunk-CLN4Z5E3.js";
8
+ import "./chunk-2MGINW4O.js";
9
+ export {
10
+ addProviderKey,
11
+ runProviderCommand
12
+ };
@@ -13,24 +13,30 @@ import {
13
13
  hasKey,
14
14
  providerName,
15
15
  writeGlobalConfig
16
- } from "./chunk-U7DLMVPZ.js";
16
+ } from "./chunk-7S6JXQCH.js";
17
+ import {
18
+ REASONING_LEVELS,
19
+ assertReasoningSupported,
20
+ parseReasoningLevel,
21
+ supportedReasoningLevels
22
+ } from "./chunk-CLN4Z5E3.js";
17
23
  import {
18
24
  Session
19
25
  } from "./chunk-SJH3YLW4.js";
20
26
  import {
21
27
  CATALOG,
22
28
  fillTemplate
23
- } from "./chunk-UESQNU4A.js";
29
+ } from "./chunk-DX7CZTBB.js";
24
30
  import {
25
31
  addGlobalServer,
26
32
  globalMcpPath,
27
33
  readMcpFile,
28
34
  removeGlobalServer
29
- } from "./chunk-NGLKANMD.js";
35
+ } from "./chunk-BFZXRKW5.js";
30
36
  import {
31
37
  PRESETS,
32
38
  removeCredential
33
- } from "./chunk-RZUJYAQV.js";
39
+ } from "./chunk-2MGINW4O.js";
34
40
 
35
41
  // src/ui/index.tsx
36
42
  import { render } from "ink";
@@ -47,6 +53,7 @@ import { useCallback, useEffect as useEffect2, useRef, useState as useState8 } f
47
53
  var SLASH_COMMANDS = [
48
54
  { name: "help", description: "daftar perintah dan pintasan" },
49
55
  { name: "model", description: "ganti model" },
56
+ { name: "reasoning", description: "atur reasoning: auto/off/low/medium/high/max" },
50
57
  { name: "provider", description: "tambah/pilih provider AI" },
51
58
  { name: "mcp", description: "pasang/kelola server MCP" },
52
59
  { name: "new", description: "sesi baru (sesi lama tetap tersimpan)" },
@@ -272,7 +279,7 @@ function ListPicker({ title, items, onPick }) {
272
279
  useInput2((input, key) => {
273
280
  if (key.upArrow) setIndex(Math.max(0, cursor - 1));
274
281
  else if (key.downArrow) setIndex(Math.min(shown.length - 1, cursor + 1));
275
- else if (key.return) onPick(shown[cursor]?.id);
282
+ else if (key.return && !shown[cursor]?.disabled) onPick(shown[cursor]?.id);
276
283
  else if (key.escape) onPick(void 0);
277
284
  else if (key.backspace || key.delete) {
278
285
  setFilter((f) => f.slice(0, -1));
@@ -285,7 +292,7 @@ function ListPicker({ title, items, onPick }) {
285
292
  const start = Math.max(0, Math.min(cursor - 5, shown.length - VISIBLE));
286
293
  return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
287
294
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: `${title} (\u2191\u2193 enter \xB7 ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 esc batal)` }),
288
- shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */ jsxs2(Text3, { color: start + i === cursor ? color("green") : void 0, children: [
295
+ shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */ jsxs2(Text3, { color: start + i === cursor && !item.disabled ? color("green") : void 0, dimColor: item.disabled, children: [
289
296
  `${start + i === cursor ? "\u203A" : " "} ${item.label}`,
290
297
  item.hint ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ` ${item.hint}` }) : null
291
298
  ] }, item.id)),
@@ -533,9 +540,9 @@ var LABEL = {
533
540
  function formatTokens(n) {
534
541
  return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
535
542
  }
536
- function StatusBar({ mode, tokens, busy, model }) {
543
+ function StatusBar({ mode, tokens, busy, model, reasoning = "auto" }) {
537
544
  const danger = mode === "allowAll";
538
- return /* @__PURE__ */ jsx10(Text10, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 shift+tab ganti mode \xB7 ${formatTokens(tokens)} token \xB7 ${busy ? "esc batal" : "/ perintah"}` });
545
+ return /* @__PURE__ */ jsx10(Text10, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 reasoning: ${reasoning} \xB7 shift+tab ganti mode \xB7 ${formatTokens(tokens)} token \xB7 ${busy ? "esc batal" : "/ perintah"}` });
539
546
  }
540
547
 
541
548
  // src/ui/McpMenu.tsx
@@ -729,6 +736,7 @@ function App({ runtime, initialPrompt, version }) {
729
736
  const [busy, setBusy] = useState8(false);
730
737
  const [startedAt, setStartedAt] = useState8(0);
731
738
  const [mode, setMode] = useState8(runtime.agent.permissions.mode);
739
+ const [reasoning, setReasoning] = useState8(runtime.reasoning);
732
740
  const [tokens, setTokens] = useState8(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
733
741
  const [asks, setAsks] = useState8([]);
734
742
  const pending = asks[0] ?? null;
@@ -814,6 +822,41 @@ function App({ runtime, initialPrompt, version }) {
814
822
  setBusy(false);
815
823
  return;
816
824
  }
825
+ case "reasoning": {
826
+ const apply = (value) => {
827
+ try {
828
+ const level = parseReasoningLevel(value, "/reasoning");
829
+ const providerId2 = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
830
+ assertReasoningSupported(providerId2, level);
831
+ runtime.setReasoning(level);
832
+ writeGlobalConfig({ reasoning: level }, runtime.env);
833
+ setReasoning(level);
834
+ notice(`Reasoning: ${level} (tersimpan sebagai default)`);
835
+ } catch (error) {
836
+ notice(error.message, "error");
837
+ }
838
+ };
839
+ if (args) {
840
+ apply(args);
841
+ return;
842
+ }
843
+ const providerId = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
844
+ const supported = supportedReasoningLevels(providerId);
845
+ setListPicker({
846
+ title: "Reasoning",
847
+ items: REASONING_LEVELS.map((level) => ({
848
+ id: level,
849
+ label: level === "auto" ? "Auto (model default)" : level.charAt(0).toUpperCase() + level.slice(1),
850
+ disabled: !supported.includes(level),
851
+ hint: supported.includes(level) ? void 0 : "tidak didukung provider ini"
852
+ })),
853
+ onPick: (id) => {
854
+ setListPicker(null);
855
+ if (id) apply(id);
856
+ }
857
+ });
858
+ return;
859
+ }
817
860
  case "model": {
818
861
  if (args) {
819
862
  try {
@@ -1325,7 +1368,7 @@ ${t.output}`);
1325
1368
  /* @__PURE__ */ jsx15(TodoList, { items: todos }),
1326
1369
  queued.map((q, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \u23F3 antri: ${q}` }, `${i}-${q}`)),
1327
1370
  /* @__PURE__ */ jsx15(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands }),
1328
- /* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel })
1371
+ /* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel, reasoning })
1329
1372
  ] });
1330
1373
  }
1331
1374
 
@@ -1348,7 +1391,7 @@ async function startInteractive(rt, opts) {
1348
1391
  if (chosen) rt.resume(chosen.session);
1349
1392
  }
1350
1393
  }
1351
- const { serversToStart, setProjectTrust } = await import("./config-NPSYFAFD.js");
1394
+ const { serversToStart, setProjectTrust } = await import("./config-YUNKPCNB.js");
1352
1395
  const plan = serversToStart(rt.home, rt.cwd);
1353
1396
  if (plan.needTrust.length) {
1354
1397
  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.7",
3
+ "version": "0.4.0-beta.9",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- addProviderKey,
4
- runProviderCommand
5
- } from "./chunk-FMM53YSU.js";
6
- import "./chunk-U7DLMVPZ.js";
7
- import "./chunk-RZUJYAQV.js";
8
- export {
9
- addProviderKey,
10
- runProviderCommand
11
- };