@botconnector/bccli 0.4.0-beta.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.en.md +242 -0
  2. package/README.md +80 -4
  3. package/dist/acp-ATIL4JOZ.js +182 -0
  4. package/dist/args-J5ONVGIF.js +13 -0
  5. package/dist/chunk-45SDLMTV.js +341 -0
  6. package/dist/chunk-6M5X2KZE.js +8 -0
  7. package/dist/chunk-7F3IWBPQ.js +191 -0
  8. package/dist/{chunk-CLN4Z5E3.js → chunk-BLOYFIAD.js} +6 -3
  9. package/dist/chunk-BNA74HPN.js +145 -0
  10. package/dist/{chunk-DX7CZTBB.js → chunk-BQ2IGPBR.js} +14 -9
  11. package/dist/chunk-JL5ZCSP2.js +76 -0
  12. package/dist/chunk-KEMYIGCP.js +235 -0
  13. package/dist/{chunk-BFZXRKW5.js → chunk-L7QI6JN2.js} +24 -2
  14. package/dist/chunk-LACUWBWS.js +361 -0
  15. package/dist/chunk-M7CALSP3.js +733 -0
  16. package/dist/{chunk-ELML4KVQ.js → chunk-O7M3TGIW.js} +34 -2
  17. package/dist/chunk-PSO7R5AD.js +10 -0
  18. package/dist/chunk-PUN4HMAG.js +940 -0
  19. package/dist/{chunk-SJH3YLW4.js → chunk-QDQU6VY2.js} +60 -6
  20. package/dist/chunk-QZKLZELZ.js +64 -0
  21. package/dist/{chunk-7S6JXQCH.js → chunk-SPRNLS7K.js} +2 -1
  22. package/dist/chunk-TLPFQIS2.js +231 -0
  23. package/dist/chunk-TVALKHFB.js +2143 -0
  24. package/dist/chunk-UZWJQF67.js +500 -0
  25. package/dist/chunk-XBO5GACQ.js +56 -0
  26. package/dist/cli.js +151 -1382
  27. package/dist/completion-HPPFDAPB.js +125 -0
  28. package/dist/{config-YUNKPCNB.js → config-COPNMMV6.js} +5 -2
  29. package/dist/config-RZ5JONMX.js +30 -0
  30. package/dist/ghBotCli-IBCZIWT5.js +677 -0
  31. package/dist/integrationCli-P75R2VSC.js +685 -0
  32. package/dist/login-SYIQP46J.js +16 -0
  33. package/dist/{mcpCli-DZWQPTOS.js → mcpCli-QUV5SD64.js} +42 -9
  34. package/dist/modelsCli-TRRB5LX4.js +36 -0
  35. package/dist/npmRunner-LU43GM3W.js +46 -0
  36. package/dist/oauthLogin-6FWQABJ7.js +210 -0
  37. package/dist/pluginsCli-GNW4TPVV.js +284 -0
  38. package/dist/prCli-5IMV3VNQ.js +78 -0
  39. package/dist/providerCli-FAOZHOKW.js +15 -0
  40. package/dist/sdk.js +5490 -0
  41. package/dist/serve-6QLBJUYW.js +178 -0
  42. package/dist/serve-JG7RLIUD.js +515 -0
  43. package/dist/session-KBAQ4BZ5.js +12 -0
  44. package/dist/setup-FJAWDWV4.js +24 -0
  45. package/dist/statsCli-CF3R7ATL.js +353 -0
  46. package/dist/transferCli-TK2GSVRK.js +71 -0
  47. package/dist/ui-B3CJ6TGE.js +2365 -0
  48. package/dist/update-K5IQ2MYR.js +145 -0
  49. package/package.json +25 -4
  50. package/dist/args-JPVSGCGY.js +0 -12
  51. package/dist/chunk-2MGINW4O.js +0 -114
  52. package/dist/chunk-ISGBGFSU.js +0 -52
  53. package/dist/chunk-KV27KTOX.js +0 -254
  54. package/dist/chunk-U7GK73CK.js +0 -341
  55. package/dist/chunk-ZYCYTEKD.js +0 -96
  56. package/dist/integrationCli-BGT5UZU4.js +0 -223
  57. package/dist/login-6K32PIXV.js +0 -9
  58. package/dist/providerCli-Q7LRM2NP.js +0 -12
  59. package/dist/session-7UB77CEG.js +0 -9
  60. package/dist/ui-6GTQUJOE.js +0 -1415
@@ -1,341 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- hasKey,
4
- providerName,
5
- removeGlobalProvider,
6
- writeGlobalConfig
7
- } from "./chunk-7S6JXQCH.js";
8
- import {
9
- reasoningPayload
10
- } from "./chunk-CLN4Z5E3.js";
11
- import {
12
- PRESETS,
13
- loadConfig,
14
- readCredentials,
15
- saveCredential
16
- } from "./chunk-2MGINW4O.js";
17
-
18
- // src/thinking.ts
19
- var OPEN = "<think>";
20
- var CLOSE = "</think>";
21
- function partialTag(text, tag) {
22
- for (let n = Math.min(tag.length - 1, text.length); n > 0; n--) if (tag.startsWith(text.slice(-n))) return n;
23
- return 0;
24
- }
25
- var ThinkSplitter = class {
26
- inside = false;
27
- held = "";
28
- push(delta) {
29
- let rest = this.held + delta;
30
- this.held = "";
31
- let text = "";
32
- let thinking = "";
33
- while (rest) {
34
- const tag = this.inside ? CLOSE : OPEN;
35
- const at = rest.indexOf(tag);
36
- if (at >= 0) {
37
- if (this.inside) thinking += rest.slice(0, at);
38
- else text += rest.slice(0, at);
39
- rest = rest.slice(at + tag.length);
40
- this.inside = !this.inside;
41
- continue;
42
- }
43
- const keep = partialTag(rest, tag);
44
- const out = rest.slice(0, rest.length - keep);
45
- if (this.inside) thinking += out;
46
- else text += out;
47
- this.held = rest.slice(rest.length - keep);
48
- break;
49
- }
50
- return { text, thinking };
51
- }
52
- /** Whatever was held back as a possible tag start is plain text after all. */
53
- flush() {
54
- const held = this.held;
55
- this.held = "";
56
- return this.inside ? { text: "", thinking: held } : { text: held, thinking: "" };
57
- }
58
- };
59
- function splitThinking(text) {
60
- const s = new ThinkSplitter();
61
- const a = s.push(text);
62
- const b = s.flush();
63
- return { text: a.text + b.text, thinking: a.thinking + b.thinking };
64
- }
65
-
66
- // src/provider.ts
67
- var ProviderError = class extends Error {
68
- constructor(message, status) {
69
- super(message);
70
- this.status = status;
71
- }
72
- status;
73
- };
74
- var retryable = (status) => status === 429 || status >= 500;
75
- function detectRepetition(text) {
76
- const tail = text.slice(-600);
77
- for (let len = 1; len <= 12; len++) {
78
- const unit = tail.slice(-len);
79
- if (unit.length < len || !unit.trim()) continue;
80
- let i = tail.length;
81
- let count = 0;
82
- while (i >= len && tail.slice(i - len, i) === unit) {
83
- count++;
84
- i -= len;
85
- }
86
- if (count >= 20 && count * len >= 300) return text.length - count * len;
87
- }
88
- return -1;
89
- }
90
- function sleep(ms, signal) {
91
- return new Promise((resolve, reject) => {
92
- const timer = setTimeout(resolve, ms);
93
- signal?.addEventListener(
94
- "abort",
95
- () => {
96
- clearTimeout(timer);
97
- reject(signal.reason);
98
- },
99
- { once: true }
100
- );
101
- });
102
- }
103
- function errorMessage(body) {
104
- try {
105
- const parsed = JSON.parse(body);
106
- return parsed?.error?.message ?? parsed?.message ?? body.slice(0, 300);
107
- } catch {
108
- return body.slice(0, 300);
109
- }
110
- }
111
- var toUsage = (u) => u ? { inputTokens: u.prompt_tokens ?? 0, outputTokens: u.completion_tokens ?? 0 } : void 0;
112
- async function readStream(res, onText, onThinking) {
113
- let text = "";
114
- let thinking = "";
115
- const splitter = new ThinkSplitter();
116
- const emit = (part) => {
117
- if (part.thinking) {
118
- thinking += part.thinking;
119
- onThinking?.(part.thinking);
120
- }
121
- if (part.text) {
122
- text += part.text;
123
- onText?.(part.text);
124
- if (loopAt < 0) loopAt = detectRepetition(text);
125
- }
126
- };
127
- let usage;
128
- let finishReason;
129
- const calls = [];
130
- const byIndex = /* @__PURE__ */ new Map();
131
- let current;
132
- const decoder = new TextDecoder();
133
- let buffer = "";
134
- let loopAt = -1;
135
- const handle = (line) => {
136
- if (!line.startsWith("data:")) return;
137
- const data = line.slice(5).trim();
138
- if (!data || data === "[DONE]") return;
139
- let event;
140
- try {
141
- event = JSON.parse(data);
142
- } catch {
143
- return;
144
- }
145
- if (event.error) throw new ProviderError(`Error dari provider: ${event.error.message ?? JSON.stringify(event.error)}`);
146
- if (event.usage) usage = toUsage(event.usage);
147
- const choice = event.choices?.[0];
148
- if (!choice) return;
149
- if (choice.finish_reason) finishReason = choice.finish_reason;
150
- const delta = choice.delta ?? {};
151
- const reasoning = delta.reasoning_content || delta.reasoning;
152
- if (reasoning) emit({ text: "", thinking: reasoning });
153
- if (delta.content) emit(splitter.push(delta.content));
154
- for (const tc of delta.tool_calls ?? []) {
155
- let target = tc.index !== void 0 ? byIndex.get(tc.index) : current;
156
- if (!target || tc.index === void 0 && tc.id && target.id && target.id !== tc.id) {
157
- target = { id: "", name: "", arguments: "" };
158
- calls.push(target);
159
- if (tc.index !== void 0) byIndex.set(tc.index, target);
160
- }
161
- current = target;
162
- if (tc.id) target.id = tc.id;
163
- if (tc.function?.name && !target.name) target.name = tc.function.name;
164
- if (tc.function?.arguments) target.arguments += tc.function.arguments;
165
- }
166
- };
167
- for await (const chunk of res.body) {
168
- buffer += decoder.decode(chunk, { stream: true });
169
- let newline = buffer.indexOf("\n");
170
- while (newline >= 0 && loopAt < 0) {
171
- handle(buffer.slice(0, newline).trim());
172
- buffer = buffer.slice(newline + 1);
173
- newline = buffer.indexOf("\n");
174
- }
175
- if (loopAt >= 0) return { text: text.slice(0, loopAt), toolCalls: [], thinking: thinking || void 0, usage, finishReason: "repetition" };
176
- }
177
- handle(buffer.trim());
178
- emit(splitter.flush());
179
- const toolCalls = calls.map((call, index) => ({ ...call, id: call.id || `call_${index}` }));
180
- return { text, toolCalls, thinking: thinking || void 0, usage, finishReason };
181
- }
182
- async function readJson(res) {
183
- const body = await res.json();
184
- const choice = body.choices?.[0];
185
- const toolCalls = (choice?.message?.tool_calls ?? []).map((tc, i) => ({
186
- id: tc.id || `call_${i}`,
187
- name: tc.function?.name ?? "",
188
- arguments: tc.function?.arguments ?? ""
189
- }));
190
- const split = splitThinking(choice?.message?.content ?? "");
191
- const thinking = (choice?.message?.reasoning_content || choice?.message?.reasoning || "") + split.thinking;
192
- return { text: split.text, toolCalls, thinking: thinking || void 0, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
193
- }
194
- function createProvider({ baseURL, apiKey, model, providerId, fetch: doFetch = fetch, retryDelayMs = 1e3 }) {
195
- const headers = { "content-type": "application/json" };
196
- if (apiKey) headers.authorization = `Bearer ${apiKey}`;
197
- if (/^https:\/\/api\.botconnector\.id\/v1\/?$/.test(baseURL) && apiKey?.startsWith("bc_live_")) {
198
- headers["x-botconnector-client"] = "bccli";
199
- headers["x-botconnector-client-version"] = "0.4.0";
200
- }
201
- async function post(path, body, signal) {
202
- let lastError;
203
- for (let attempt = 0; attempt < 4; attempt++) {
204
- if (attempt > 0) await sleep(retryDelayMs * 2 ** (attempt - 1), signal);
205
- let res;
206
- try {
207
- res = await doFetch(`${baseURL}${path}`, { method: "POST", headers, body: JSON.stringify(body), signal });
208
- } catch (error) {
209
- if (signal?.aborted) throw error;
210
- lastError = new ProviderError(`Tidak bisa terhubung ke ${baseURL}: ${error.message}`);
211
- continue;
212
- }
213
- if (res.ok) return res;
214
- const text = await res.text().catch(() => "");
215
- lastError = new ProviderError(`${res.status} dari ${baseURL}: ${errorMessage(text)}`, res.status);
216
- if (!retryable(res.status)) throw lastError;
217
- }
218
- throw lastError ?? new ProviderError("Permintaan gagal");
219
- }
220
- return {
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
- }
228
- const res = await post(
229
- "/chat/completions",
230
- { model, messages, stream: true, stream_options: { include_usage: true }, ...tools?.length ? { tools } : {}, ...reasoningOverride },
231
- signal
232
- );
233
- if ((res.headers.get("content-type") ?? "").includes("application/json")) return readJson(res);
234
- if (!res.body) throw new ProviderError("Respons provider kosong");
235
- return readStream(res, onText, onThinking);
236
- },
237
- async listModels() {
238
- const res = await doFetch(`${baseURL}/models`, { headers });
239
- if (!res.ok) throw new ProviderError(`${res.status} saat mengambil daftar model`, res.status);
240
- const body = await res.json();
241
- return (body.data ?? []).map((m) => m.id).sort();
242
- }
243
- };
244
- }
245
-
246
- // src/models.ts
247
- function order(ids) {
248
- const presetIds = PRESETS.map((p) => p.id);
249
- const presets = presetIds.filter((id) => ids.includes(id));
250
- const custom = ids.filter((id) => !presetIds.includes(id)).sort();
251
- return [...presets, ...custom];
252
- }
253
- async function listAllModels(config, env = process.env, opts = {}) {
254
- const timeoutMs = opts.timeoutMs ?? 5e3;
255
- const baseFetch = opts.fetch ?? fetch;
256
- const untrusted = new Set(config.projectProviders ?? []);
257
- const ids = order(Object.keys(config.providers)).filter((id) => opts.only ? id === opts.only : !untrusted.has(id) && hasKey(config, id, env));
258
- const results = await Promise.all(
259
- ids.map(async (id) => {
260
- const provider = config.providers[id];
261
- const apiKey = (opts.keyOverride && id === opts.only ? opts.keyOverride : void 0) || (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || readCredentials(env)[id] || void 0;
262
- const local = !provider.apiKeyEnv;
263
- const timedFetch = ((input, init) => baseFetch(input, { ...init, signal: AbortSignal.timeout(timeoutMs) }));
264
- const list = createProvider({ baseURL: provider.baseURL.replace(/\/+$/, ""), apiKey, model: "", fetch: timedFetch }).listModels();
265
- let timer;
266
- const timeout = new Promise((_, reject) => {
267
- timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
268
- });
269
- try {
270
- const models = await Promise.race([list, timeout]);
271
- return { providerId: id, providerName: providerName(config, id), models };
272
- } catch {
273
- if (local && !opts.only) return void 0;
274
- return { providerId: id, providerName: providerName(config, id), models: [], error: "tidak bisa dihubungi" };
275
- } finally {
276
- clearTimeout(timer);
277
- }
278
- })
279
- );
280
- return results.filter((g) => !!g);
281
- }
282
-
283
- // src/providerCli.ts
284
- async function addProviderKey(providerId, key, deps) {
285
- const config = loadConfig(deps.cwd, deps.env);
286
- const [group] = await listAllModels(config, deps.env, { only: providerId, fetch: deps.fetch, keyOverride: key || void 0 });
287
- if (!group || group.error) return { ok: false, error: `API key ditolak atau ${providerName(config, providerId)} tidak bisa dihubungi.` };
288
- if (key) saveCredential(providerId, key, deps.env);
289
- return { ok: true, models: group.models.length };
290
- }
291
- async function runProviderCommand(args, deps) {
292
- const [action = "list", id] = args.subArgs;
293
- const config = loadConfig(deps.cwd, deps.env);
294
- if (action === "list") {
295
- for (const pid of Object.keys(config.providers)) {
296
- const mark = hasKey(config, pid, deps.env) ? "\u2713" : "\u25CB";
297
- deps.out(`${mark} ${pid.padEnd(14)} ${providerName(config, pid).padEnd(20)} ${config.providers[pid].baseURL}`);
298
- }
299
- return 0;
300
- }
301
- if (!id) {
302
- deps.err(`Pemakaian: bccli provider ${action} <id>`);
303
- return 1;
304
- }
305
- if (action === "remove") {
306
- removeGlobalProvider(id, deps.env);
307
- deps.out(`${id} dihapus.`);
308
- return 0;
309
- }
310
- if (action !== "add") {
311
- deps.err(`Aksi tidak dikenal: ${action}. Pakai list, add, atau remove.`);
312
- return 1;
313
- }
314
- const preset = PRESETS.find((p) => p.id === id);
315
- if (!preset && !config.providers[id]) {
316
- if (!args.url) {
317
- deps.err(`${id} bukan preset. Untuk provider custom tambahkan --url <base URL OpenAI-compatible>.`);
318
- return 1;
319
- }
320
- writeGlobalConfig(
321
- { providers: { [id]: { baseURL: args.url, ...args.name ? { name: args.name } : {}, ...args.keyEnv ? { apiKeyEnv: args.keyEnv } : {} } } },
322
- deps.env
323
- );
324
- }
325
- const needsKey = !!loadConfig(deps.cwd, deps.env).providers[id].apiKeyEnv || !preset;
326
- const key = needsKey ? await deps.readSecret(`API key untuk ${id} (kosongkan bila tidak perlu): `) : "";
327
- const result = await addProviderKey(id, key, deps);
328
- if (!result.ok) {
329
- deps.err(result.error);
330
- return 1;
331
- }
332
- deps.out(`${id} siap. ${result.models} model tersedia.`);
333
- return 0;
334
- }
335
-
336
- export {
337
- createProvider,
338
- listAllModels,
339
- addProviderKey,
340
- runProviderCommand
341
- };
@@ -1,96 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- MODE_ORDER
4
- } from "./chunk-ELML4KVQ.js";
5
- import {
6
- REASONING_LEVELS,
7
- isReasoningLevel
8
- } from "./chunk-CLN4Z5E3.js";
9
- import {
10
- ConfigError
11
- } from "./chunk-2MGINW4O.js";
12
-
13
- // src/args.ts
14
- import { parseArgs } from "util";
15
- var HELP_TEXT = `BCCLI \u2014 agent AI BotConnector di terminal
16
-
17
- Pemakaian:
18
- bccli [tugas] mode interaktif (opsional langsung dengan tugas)
19
- bccli -p "tugas" jalankan satu tugas tanpa interaksi (skrip/CI)
20
- bccli login [provider] simpan API key (default: bc-cloud)
21
- bccli models daftar model dari provider aktif
22
- bccli provider list|add <id>|remove <id> kelola provider (custom: --url <url> [--name N] [--key-env ENV])
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
27
-
28
- Opsi:
29
- -m, --model <provider/model> pilih model, contoh bc-cloud/glm-5.3-flash
30
- --reasoning <level> auto | off | low | medium | high | max
31
- -c, --continue lanjutkan sesi terakhir di folder ini
32
- -r, --resume pilih sesi untuk dilanjutkan
33
- --allow-all jalankan semua alat tanpa minta izin
34
- --allowed-tools <a,b> alat yang boleh tanpa izin: bash, edit, fetch
35
- --permission-mode <mode> default | acceptEdits | plan | allowAll
36
- -v, --version versi
37
- -h, --help bantuan ini`;
38
- function parseCliArgs(argv) {
39
- const { values, positionals } = parseArgs({
40
- args: argv,
41
- allowPositionals: true,
42
- options: {
43
- print: { type: "boolean", short: "p" },
44
- model: { type: "string", short: "m" },
45
- reasoning: { type: "string" },
46
- continue: { type: "boolean", short: "c" },
47
- resume: { type: "boolean", short: "r" },
48
- "allow-all": { type: "boolean" },
49
- "allowed-tools": { type: "string" },
50
- "permission-mode": { type: "string" },
51
- help: { type: "boolean", short: "h" },
52
- version: { type: "boolean", short: "v" },
53
- url: { type: "string" },
54
- name: { type: "string" },
55
- "key-env": { type: "string" },
56
- value: { type: "string", multiple: true }
57
- }
58
- });
59
- const reasoning = values.reasoning;
60
- if (reasoning && !isReasoningLevel(reasoning)) {
61
- throw new ConfigError(`--reasoning harus salah satu dari: ${REASONING_LEVELS.join(", ")}`);
62
- }
63
- const mode = values["permission-mode"];
64
- if (mode && !MODE_ORDER.includes(mode)) {
65
- throw new ConfigError(`--permission-mode harus salah satu dari: ${MODE_ORDER.join(", ")}`);
66
- }
67
- const [first, ...rest] = positionals;
68
- const SUBCOMMANDS = ["login", "models", "provider", "mcp", "connect", "disconnect", "integrations"];
69
- const command = SUBCOMMANDS.includes(first) ? first : "run";
70
- const words = command === "run" ? positionals : rest;
71
- return {
72
- command,
73
- prompt: command === "run" && words.length ? words.join(" ") : void 0,
74
- print: !!values.print,
75
- model: values.model,
76
- reasoning,
77
- continue: !!values.continue,
78
- resume: !!values.resume,
79
- allowAll: !!values["allow-all"],
80
- allowedTools: (values["allowed-tools"] ?? "").split(",").map((s) => s.trim()).filter(Boolean),
81
- permissionMode: mode,
82
- help: !!values.help,
83
- version: !!values.version,
84
- loginProvider: command === "login" ? rest[0] ?? "bc-cloud" : void 0,
85
- subArgs: ["provider", "mcp", "connect", "disconnect", "integrations"].includes(command) ? rest : [],
86
- url: values.url,
87
- name: values.name,
88
- keyEnv: values["key-env"],
89
- values: values.value ?? []
90
- };
91
- }
92
-
93
- export {
94
- HELP_TEXT,
95
- parseCliArgs
96
- };
@@ -1,223 +0,0 @@
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
- };
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- readSecret,
4
- runLogin
5
- } from "./chunk-ISGBGFSU.js";
6
- export {
7
- readSecret,
8
- runLogin
9
- };
@@ -1,12 +0,0 @@
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
- };
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- Session,
4
- pruneSessions
5
- } from "./chunk-SJH3YLW4.js";
6
- export {
7
- Session,
8
- pruneSessions
9
- };