@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 +17 -1
- package/dist/{args-FLTRM57K.js → args-JPVSGCGY.js} +1 -1
- package/dist/{chunk-IM3KMPVZ.js → chunk-ISGBGFSU.js} +1 -1
- package/dist/{chunk-Z7PM3LTC.js → chunk-ZYCYTEKD.js} +5 -2
- package/dist/cli.js +25 -15
- package/dist/integrationCli-BGT5UZU4.js +223 -0
- package/dist/{login-JXFORO3J.js → login-6K32PIXV.js} +1 -1
- package/package.json +1 -1
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 #
|
|
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
|
|
@@ -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-
|
|
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:
|
|
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.
|
|
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-
|
|
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-
|
|
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
|
+
};
|