@botconnector/bccli 0.4.0-beta.8 → 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
  ```
@@ -88,6 +88,22 @@ preference `reasoning` hanya dibaca dari config user `~/.bccli/config.json`, buk
88
88
  izin (`permissionMode`, `allow`) dan provider yang sudah ada hanya diatur dari `~/.bccli/config.json`,
89
89
  supaya repo yang kamu clone tidak bisa memberi dirinya izin penuh atau mencuri API key.
90
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
+
91
107
  ## Internet
92
108
 
93
109
  - `web_search`: mencari di web tanpa perlu izin. Memakai akun BotConnector Cloud
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  HELP_TEXT,
4
4
  parseCliArgs
5
- } from "./chunk-Z7PM3LTC.js";
5
+ } from "./chunk-ZYCYTEKD.js";
6
6
  import "./chunk-ELML4KVQ.js";
7
7
  import "./chunk-CLN4Z5E3.js";
8
8
  import "./chunk-2MGINW4O.js";
@@ -36,7 +36,7 @@ function readSecret(prompt) {
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
38
  const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
39
- const { parseCliArgs } = await import("./args-FLTRM57K.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,
@@ -21,6 +21,9 @@ Pemakaian:
21
21
  bccli models daftar model dari provider aktif
22
22
  bccli provider list|add <id>|remove <id> kelola provider (custom: --url <url> [--name N] [--key-env ENV])
23
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
24
27
 
25
28
  Opsi:
26
29
  -m, --model <provider/model> pilih model, contoh bc-cloud/glm-5.3-flash
@@ -62,7 +65,7 @@ function parseCliArgs(argv) {
62
65
  throw new ConfigError(`--permission-mode harus salah satu dari: ${MODE_ORDER.join(", ")}`);
63
66
  }
64
67
  const [first, ...rest] = positionals;
65
- const SUBCOMMANDS = ["login", "models", "provider", "mcp"];
68
+ const SUBCOMMANDS = ["login", "models", "provider", "mcp", "connect", "disconnect", "integrations"];
66
69
  const command = SUBCOMMANDS.includes(first) ? first : "run";
67
70
  const words = command === "run" ? positionals : rest;
68
71
  return {
@@ -79,7 +82,7 @@ function parseCliArgs(argv) {
79
82
  help: !!values.help,
80
83
  version: !!values.version,
81
84
  loginProvider: command === "login" ? rest[0] ?? "bc-cloud" : void 0,
82
- subArgs: command === "provider" || command === "mcp" ? rest : [],
85
+ subArgs: ["provider", "mcp", "connect", "disconnect", "integrations"].includes(command) ? rest : [],
83
86
  url: values.url,
84
87
  name: values.name,
85
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-Z7PM3LTC.js";
6
- import {
7
- addProviderKey,
8
- createProvider,
9
- listAllModels
10
- } from "./chunk-U7GK73CK.js";
11
- import {
12
- runLogin
13
- } from "./chunk-IM3KMPVZ.js";
14
2
  import {
15
3
  BUILTIN_AGENTS,
16
4
  buildSystemPrompt,
@@ -21,13 +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
26
  } from "./chunk-7S6JXQCH.js";
30
27
  import "./chunk-CLN4Z5E3.js";
28
+ import {
29
+ runLogin
30
+ } from "./chunk-ISGBGFSU.js";
31
31
  import {
32
32
  Session,
33
33
  pruneSessions
@@ -1374,7 +1374,7 @@ function createRuntime(opts) {
1374
1374
  }
1375
1375
 
1376
1376
  // src/version.ts
1377
- var VERSION = true ? "0.4.0-beta.8" : "0.0.0-dev";
1377
+ var VERSION = true ? "0.4.0-beta.9" : "0.0.0-dev";
1378
1378
 
1379
1379
  // src/cli.ts
1380
1380
  async function main() {
@@ -1391,14 +1391,24 @@ async function main() {
1391
1391
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1392
1392
  if (args.command === "mcp") {
1393
1393
  const { runMcpCommand } = await import("./mcpCli-DZWQPTOS.js");
1394
- const { readSecret } = await import("./login-JXFORO3J.js");
1394
+ const { readSecret } = await import("./login-6K32PIXV.js");
1395
1395
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1396
1396
  }
1397
1397
  if (args.command === "provider") {
1398
1398
  const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
1399
- const { readSecret } = await import("./login-JXFORO3J.js");
1399
+ const { readSecret } = await import("./login-6K32PIXV.js");
1400
1400
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1401
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
+ }
1402
1412
  if (args.command === "models") {
1403
1413
  const config = loadConfig(cwd);
1404
1414
  const resolved = resolveModel(config, args.model ?? config.model);
@@ -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-IM3KMPVZ.js";
5
+ } from "./chunk-ISGBGFSU.js";
6
6
  export {
7
7
  readSecret,
8
8
  runLogin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.0-beta.8",
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": {