@botconnector/bccli 0.4.0-beta.1 → 0.4.0-beta.11

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.
@@ -0,0 +1,583 @@
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
+ import { parseDocument } from "yaml";
14
+ var BASE_URL = "https://api.botconnector.id/v1";
15
+ var PROVIDER_ID = "botconnector";
16
+ function integrationHome(env) {
17
+ return join(bccliHome(env), "integrations");
18
+ }
19
+ function statePath(target, env) {
20
+ return join(integrationHome(env), `${target}.state.json`);
21
+ }
22
+ function keyPath(env) {
23
+ return join(integrationHome(env), "bc-cloud.key");
24
+ }
25
+ function parseTarget(raw) {
26
+ if (raw === "opencode" || raw === "aider" || raw === "cline" || raw === "deepseek-harness" || raw === "cursor" || raw === "openai-cli" || raw === "openai-sdk" || raw === "openai-compatible" || raw === "codex" || raw === "claude-code") return raw;
27
+ if (raw === "dsh") return "deepseek-harness";
28
+ throw new ConfigError(
29
+ "Agent harus salah satu dari: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code"
30
+ );
31
+ }
32
+ function homeDir(env) {
33
+ const value = env.HOME || env.USERPROFILE;
34
+ if (!value) throw new ConfigError("Home directory tidak dapat ditentukan.");
35
+ return value;
36
+ }
37
+ function ensureSecret(env) {
38
+ const key = readCredentials(env)["bc-cloud"] || env.BOTCONNECTOR_API_KEY;
39
+ if (!key) throw new ConfigError("API key BotConnector belum tersedia. Jalankan bccli login bc-cloud terlebih dahulu.");
40
+ const dir = integrationHome(env);
41
+ mkdirSync(dir, { recursive: true });
42
+ const path = keyPath(env);
43
+ writeFileSync(path, `${key}
44
+ `, { mode: 384 });
45
+ try {
46
+ chmodSync(path, 384);
47
+ } catch {
48
+ }
49
+ return path;
50
+ }
51
+ function safeBackup(path, env) {
52
+ if (!existsSync(path)) return { created: true };
53
+ const dir = integrationHome(env);
54
+ mkdirSync(dir, { recursive: true });
55
+ const name = path.split(/[\\/]/).pop() || "config";
56
+ const backup = join(dir, `${name}.${Date.now()}.bak`);
57
+ copyFileSync(path, backup);
58
+ return { backup, created: false };
59
+ }
60
+ function writeState(target, state, env) {
61
+ const path = statePath(target, env);
62
+ mkdirSync(dirname(path), { recursive: true });
63
+ writeFileSync(path, `${JSON.stringify(state, null, 2)}
64
+ `, { mode: 384 });
65
+ }
66
+ function readState(target, env) {
67
+ const path = statePath(target, env);
68
+ if (!existsSync(path)) return null;
69
+ try {
70
+ return JSON.parse(readFileSync(path, "utf8"));
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ async function botConnectorModels(deps) {
76
+ const config = loadConfig(deps.cwd);
77
+ const resolved = resolveModel(config, config.model, deps.env);
78
+ const key = readCredentials(deps.env)["bc-cloud"] || resolved.apiKey;
79
+ if (!key) throw new ConfigError("API key BotConnector belum tersedia. Jalankan bccli login bc-cloud terlebih dahulu.");
80
+ const response = await deps.fetch(`${BASE_URL}/models`, {
81
+ headers: { authorization: `Bearer ${key}` }
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 externalDefaultModel(models, deps) {
186
+ const config = loadConfig(deps.cwd);
187
+ const preferred = config.model.startsWith("bc-cloud/") ? config.model.slice("bc-cloud/".length) : "";
188
+ if (preferred && models.includes(preferred)) return preferred;
189
+ const first = models[0];
190
+ if (!first) throw new ConfigError("Katalog BotConnector tidak memiliki model yang dapat dipakai agent eksternal.");
191
+ return first;
192
+ }
193
+ async function connectCline(deps) {
194
+ const home = homeDir(deps.env);
195
+ const dataDir = deps.env.CLINE_DATA_DIR || join(home, ".cline", "data");
196
+ const path = join(dataDir, "settings", "providers.json");
197
+ const backup = safeBackup(path, deps.env);
198
+ const secret = ensureSecret(deps.env);
199
+ const key = readFileSync(secret, "utf8").trim();
200
+ const models = await botConnectorModels(deps);
201
+ const model = externalDefaultModel(models, deps);
202
+ let doc = { version: 1, modes: {}, providers: {} };
203
+ if (existsSync(path)) {
204
+ try {
205
+ doc = JSON.parse(readFileSync(path, "utf8"));
206
+ } catch {
207
+ throw new ConfigError(`Config Cline tidak valid JSON: ${path}`);
208
+ }
209
+ }
210
+ const providers = doc.providers && typeof doc.providers === "object" && !Array.isArray(doc.providers) ? { ...doc.providers } : {};
211
+ const current = providers["openai-compatible"];
212
+ const currentBase = current?.settings?.baseUrl;
213
+ if (currentBase && currentBase.replace(/\/+$/, "") !== BASE_URL) {
214
+ throw new ConfigError(
215
+ "Cline sudah memakai provider openai-compatible lain (" + currentBase + "). BCCLI tidak akan menimpanya; disconnect/ubah provider tersebut lebih dulu."
216
+ );
217
+ }
218
+ providers["openai-compatible"] = {
219
+ settings: { provider: "openai-compatible", apiKey: key, model, baseUrl: BASE_URL },
220
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
221
+ tokenSource: "manual"
222
+ };
223
+ doc.version = typeof doc.version === "number" ? doc.version : 1;
224
+ doc.lastUsedProvider = "openai-compatible";
225
+ doc.modes = doc.modes && typeof doc.modes === "object" ? doc.modes : {};
226
+ doc.providers = providers;
227
+ mkdirSync(dirname(path), { recursive: true });
228
+ writeFileSync(path, `${JSON.stringify(doc, null, 2)}
229
+ `, { mode: 384 });
230
+ try {
231
+ chmodSync(path, 384);
232
+ } catch {
233
+ }
234
+ writeState("cline", {
235
+ target: "cline",
236
+ files: [{ path, ...backup }],
237
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
238
+ }, deps.env);
239
+ deps.out(`Cline terhubung ke BotConnector (${models.length} model tersedia; default ${model}).`);
240
+ deps.out(`Config: ${path}`);
241
+ deps.out("Catatan: Cline menyimpan API key provider di providers.json miliknya.");
242
+ }
243
+ var DSH_ENV_BEGIN = "# BEGIN BCCLI BOTCONNECTOR";
244
+ var DSH_ENV_END = "# END BCCLI BOTCONNECTOR";
245
+ function stripDshEnvBlock(input) {
246
+ const start = input.indexOf(DSH_ENV_BEGIN);
247
+ if (start < 0) return input;
248
+ const end = input.indexOf(DSH_ENV_END, start);
249
+ if (end < 0) return input;
250
+ const after = end + DSH_ENV_END.length;
251
+ const left = input.slice(0, start).trimEnd();
252
+ const right = input.slice(after).trimStart();
253
+ return right ? `${left}
254
+ ${right}` : left;
255
+ }
256
+ async function connectDeepSeekHarness(deps) {
257
+ const home = homeDir(deps.env);
258
+ const dshHome = deps.env.DSH_HOME || join(home, ".dsh");
259
+ const settingsPath = join(dshHome, "settings.yaml");
260
+ const envPath = join(dshHome, ".env");
261
+ const settingsBackup = safeBackup(settingsPath, deps.env);
262
+ const envBackup = safeBackup(envPath, deps.env);
263
+ const secret = ensureSecret(deps.env);
264
+ const key = readFileSync(secret, "utf8").trim();
265
+ const models = await botConnectorModels(deps);
266
+ const source = existsSync(settingsPath) ? readFileSync(settingsPath, "utf8") : "{}\n";
267
+ const doc = parseDocument(source);
268
+ if (doc.errors.length) throw new ConfigError(`Config DeepSeek Harness tidak valid YAML: ${settingsPath}`);
269
+ const existingProvider = doc.getIn(["llm-pi-ai", "providers", PROVIDER_ID]);
270
+ if (existingProvider?.baseURL && existingProvider.baseURL.replace(/\/+$/, "") !== BASE_URL) {
271
+ throw new ConfigError(`DeepSeek Harness sudah memiliki provider "${PROVIDER_ID}" dengan endpoint lain.`);
272
+ }
273
+ doc.setIn(["llm-pi-ai", "providers", PROVIDER_ID], {
274
+ displayName: "BotConnector",
275
+ apiKeyEnv: "BOTCONNECTOR_API_KEY",
276
+ api: "openai-completions",
277
+ baseURL: BASE_URL,
278
+ defaultInput: ["text"],
279
+ compat: { supportsDeveloperRole: false, maxTokensField: "max_tokens" },
280
+ models: models.map((id) => ({ id, name: id }))
281
+ });
282
+ let envText = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
283
+ envText = stripDshEnvBlock(envText);
284
+ if (/^\s*BOTCONNECTOR_API_KEY\s*=/m.test(envText)) {
285
+ throw new ConfigError(`$DSH_HOME/.env sudah memiliki BOTCONNECTOR_API_KEY di luar blok BCCLI: ${envPath}`);
286
+ }
287
+ const managed = [DSH_ENV_BEGIN, `BOTCONNECTOR_API_KEY=${key}`, DSH_ENV_END].join("\n");
288
+ const mergedEnv = envText.trim() ? `${envText.trimEnd()}
289
+
290
+ ${managed}
291
+ ` : `${managed}
292
+ `;
293
+ mkdirSync(dshHome, { recursive: true });
294
+ writeFileSync(settingsPath, doc.toString());
295
+ writeFileSync(envPath, mergedEnv, { mode: 384 });
296
+ try {
297
+ chmodSync(envPath, 384);
298
+ } catch {
299
+ }
300
+ writeState("deepseek-harness", {
301
+ target: "deepseek-harness",
302
+ files: [{ path: settingsPath, ...settingsBackup }, { path: envPath, ...envBackup }],
303
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
304
+ }, deps.env);
305
+ deps.out(`DeepSeek Harness terhubung ke BotConnector (${models.length} model).`);
306
+ deps.out(`Config: ${settingsPath}`);
307
+ deps.out("Provider: botconnector (OpenAI Chat Completions)");
308
+ }
309
+ function managedProfilePath(target, env, ext = "env") {
310
+ return join(integrationHome(env), `${target}.${ext}`);
311
+ }
312
+ function writeOpenAiEnvProfile(target, deps) {
313
+ const secret = ensureSecret(deps.env);
314
+ const key = readFileSync(secret, "utf8").trim();
315
+ const path = managedProfilePath(target, deps.env);
316
+ const backup = safeBackup(path, deps.env);
317
+ mkdirSync(dirname(path), { recursive: true });
318
+ writeFileSync(path, [
319
+ `OPENAI_BASE_URL=${BASE_URL}`,
320
+ `OPENAI_API_KEY=${key}`,
321
+ ""
322
+ ].join("\n"), { mode: 384 });
323
+ try {
324
+ chmodSync(path, 384);
325
+ } catch {
326
+ }
327
+ writeState(target, {
328
+ target,
329
+ files: [{ path, ...backup }],
330
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
331
+ }, deps.env);
332
+ return path;
333
+ }
334
+ async function connectOpenAiCli(deps) {
335
+ const secret = ensureSecret(deps.env);
336
+ const dir = integrationHome(deps.env);
337
+ const launcher = join(dir, process.platform === "win32" ? "openai-botconnector.cmd" : "openai-botconnector");
338
+ const backup = safeBackup(launcher, deps.env);
339
+ mkdirSync(dir, { recursive: true });
340
+ if (process.platform === "win32") {
341
+ const keyFile = secret.replace(/\//g, "\\");
342
+ writeFileSync(launcher, [
343
+ "@echo off",
344
+ `set "OPENAI_BASE_URL=${BASE_URL}"`,
345
+ `for /f "usebackq delims=" %%A in ("${keyFile}") do set "OPENAI_API_KEY=%%A"`,
346
+ "openai %*",
347
+ ""
348
+ ].join("\r\n"));
349
+ } else {
350
+ const keyFile = secret.replace(/'/g, "'\\''");
351
+ writeFileSync(launcher, [
352
+ "#!/bin/sh",
353
+ `export OPENAI_BASE_URL='${BASE_URL}'`,
354
+ `export OPENAI_API_KEY="$(cat '${keyFile}')"`,
355
+ 'exec openai "$@"',
356
+ ""
357
+ ].join("\n"), { mode: 448 });
358
+ try {
359
+ chmodSync(launcher, 448);
360
+ } catch {
361
+ }
362
+ }
363
+ writeState("openai-cli", {
364
+ target: "openai-cli",
365
+ files: [{ path: launcher, ...backup }],
366
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
367
+ }, deps.env);
368
+ deps.out("OpenAI CLI profile BotConnector siap.");
369
+ deps.out(`Launcher: ${launcher}`);
370
+ deps.out("Launcher tidak menyimpan API key di dalam script; key dibaca dari storage BCCLI saat dijalankan.");
371
+ }
372
+ async function connectOpenAiSdk(deps) {
373
+ const path = writeOpenAiEnvProfile("openai-sdk", deps);
374
+ deps.out("OpenAI SDK profile BotConnector siap.");
375
+ deps.out(`Env profile: ${path}`);
376
+ deps.out("Python dan Node OpenAI SDK dapat membaca OPENAI_BASE_URL dan OPENAI_API_KEY dari profile ini.");
377
+ }
378
+ async function connectOpenAiCompatible(deps) {
379
+ const path = writeOpenAiEnvProfile("openai-compatible", deps);
380
+ deps.out("Profile OpenAI-compatible BotConnector siap.");
381
+ deps.out(`Env profile: ${path}`);
382
+ deps.out(`Base URL: ${BASE_URL}`);
383
+ }
384
+ async function connectCursor(deps) {
385
+ const secret = ensureSecret(deps.env);
386
+ const path = managedProfilePath("cursor", deps.env, "txt");
387
+ const backup = safeBackup(path, deps.env);
388
+ const keyFile = secret.replace(/\\/g, "/");
389
+ mkdirSync(dirname(path), { recursive: true });
390
+ writeFileSync(path, [
391
+ "BotConnector -> Cursor guided setup",
392
+ "",
393
+ `Base URL: ${BASE_URL}`,
394
+ `API key file: ${keyFile}`,
395
+ "",
396
+ "Cursor Settings -> Models:",
397
+ "1. Enable/add your OpenAI API key.",
398
+ "2. Enable Override OpenAI Base URL.",
399
+ `3. Set the override URL to ${BASE_URL}.`,
400
+ "4. Use Chat/Agent only; Cursor Tab/autocomplete remains on Cursor infrastructure.",
401
+ "",
402
+ "Important: Cursor currently applies the OpenAI Base URL override globally to OpenAI-family model requests.",
403
+ "Disable the override before switching back to Cursor-managed OpenAI-family models.",
404
+ ""
405
+ ].join("\n"));
406
+ writeState("cursor", {
407
+ target: "cursor",
408
+ files: [{ path, ...backup }],
409
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
410
+ }, deps.env);
411
+ deps.out("Cursor guided setup profile dibuat.");
412
+ deps.out(`Panduan: ${path}`);
413
+ deps.out("BCCLI tidak mengedit storage internal Cursor karena format override tersebut bukan konfigurasi eksternal yang stabil.");
414
+ }
415
+ function agentToolDefaultModel(models) {
416
+ for (const id of ["ling-3.0-flash", "mimo-v2.5", "deepseek-v4-flash"]) {
417
+ if (models.includes(id)) return id;
418
+ }
419
+ const first = models[0];
420
+ if (!first) throw new ConfigError("Katalog BotConnector tidak memiliki model untuk coding agent eksternal.");
421
+ return first;
422
+ }
423
+ async function connectCodex(deps) {
424
+ if (readState("codex", deps.env)) disconnect("codex", deps);
425
+ const home = homeDir(deps.env);
426
+ const codexHome = deps.env.CODEX_HOME || join(home, ".codex");
427
+ const profilePath = join(codexHome, "botconnector.config.toml");
428
+ const launcher = join(integrationHome(deps.env), process.platform === "win32" ? "codex-botconnector.cmd" : "codex-botconnector");
429
+ if (existsSync(profilePath)) {
430
+ throw new ConfigError("Codex sudah memiliki botconnector.config.toml. BCCLI tidak akan menimpanya.");
431
+ }
432
+ const profileBackup = safeBackup(profilePath, deps.env);
433
+ const launcherBackup = safeBackup(launcher, deps.env);
434
+ const secret = ensureSecret(deps.env);
435
+ const models = await botConnectorModels(deps);
436
+ const model = agentToolDefaultModel(models);
437
+ const profile = [
438
+ `model = ${JSON.stringify(model)}`,
439
+ 'model_provider = "botconnector"',
440
+ "",
441
+ "[model_providers.botconnector]",
442
+ 'name = "BotConnector"',
443
+ `base_url = "${BASE_URL}"`,
444
+ 'env_key = "BOTCONNECTOR_API_KEY"',
445
+ 'wire_api = "responses"',
446
+ "requires_openai_auth = false",
447
+ "supports_websockets = false",
448
+ "supports_standalone_web_search = false",
449
+ ""
450
+ ].join("\n");
451
+ mkdirSync(codexHome, { recursive: true });
452
+ writeFileSync(profilePath, profile);
453
+ mkdirSync(dirname(launcher), { recursive: true });
454
+ if (process.platform === "win32") {
455
+ const keyFile = secret.replace(/\//g, "\\");
456
+ writeFileSync(launcher, [
457
+ "@echo off",
458
+ 'set "BOTCONNECTOR_API_KEY="',
459
+ `for /f "usebackq delims=" %%A in ("${keyFile}") do set "BOTCONNECTOR_API_KEY=%%A"`,
460
+ "codex -p botconnector %*",
461
+ ""
462
+ ].join("\r\n"));
463
+ } else {
464
+ const keyFile = secret.replace(/'/g, "'\\''");
465
+ writeFileSync(launcher, [
466
+ "#!/bin/sh",
467
+ `export BOTCONNECTOR_API_KEY="$(cat '${keyFile}')"`,
468
+ 'exec codex -p botconnector "$@"',
469
+ ""
470
+ ].join("\n"), { mode: 448 });
471
+ try {
472
+ chmodSync(launcher, 448);
473
+ } catch {
474
+ }
475
+ }
476
+ writeState("codex", {
477
+ target: "codex",
478
+ files: [{ path: profilePath, ...profileBackup }, { path: launcher, ...launcherBackup }],
479
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
480
+ }, deps.env);
481
+ deps.out("Codex CLI terhubung ke BotConnector Responses API.");
482
+ deps.out(`Profile: ${profilePath}`);
483
+ deps.out(`Launcher: ${launcher}`);
484
+ deps.out(`Default coding model: ${model}`);
485
+ }
486
+ async function connectClaudeCode(deps) {
487
+ const secret = ensureSecret(deps.env);
488
+ const models = await botConnectorModels(deps);
489
+ const model = agentToolDefaultModel(models);
490
+ const launcher = join(integrationHome(deps.env), process.platform === "win32" ? "claude-botconnector.cmd" : "claude-botconnector");
491
+ const backup = safeBackup(launcher, deps.env);
492
+ mkdirSync(dirname(launcher), { recursive: true });
493
+ if (process.platform === "win32") {
494
+ const keyFile = secret.replace(/\//g, "\\");
495
+ writeFileSync(launcher, [
496
+ "@echo off",
497
+ 'set "ANTHROPIC_AUTH_TOKEN="',
498
+ 'set "CLAUDE_CODE_OAUTH_TOKEN="',
499
+ 'set "ANTHROPIC_API_KEY="',
500
+ `for /f "usebackq delims=" %%A in ("${keyFile}") do set "ANTHROPIC_API_KEY=%%A"`,
501
+ 'set "ANTHROPIC_BASE_URL=https://api.botconnector.id"',
502
+ `set "ANTHROPIC_MODEL=${model}"`,
503
+ `set "ANTHROPIC_DEFAULT_OPUS_MODEL=${model}"`,
504
+ `set "ANTHROPIC_DEFAULT_SONNET_MODEL=${model}"`,
505
+ `set "ANTHROPIC_DEFAULT_HAIKU_MODEL=${model}"`,
506
+ "claude %*",
507
+ ""
508
+ ].join("\r\n"));
509
+ } else {
510
+ const keyFile = secret.replace(/'/g, "'\\''");
511
+ writeFileSync(launcher, [
512
+ "#!/bin/sh",
513
+ "unset ANTHROPIC_AUTH_TOKEN CLAUDE_CODE_OAUTH_TOKEN",
514
+ `export ANTHROPIC_API_KEY="$(cat '${keyFile}')"`,
515
+ "export ANTHROPIC_BASE_URL='https://api.botconnector.id'",
516
+ `export ANTHROPIC_MODEL='${model}'`,
517
+ `export ANTHROPIC_DEFAULT_OPUS_MODEL='${model}'`,
518
+ `export ANTHROPIC_DEFAULT_SONNET_MODEL='${model}'`,
519
+ `export ANTHROPIC_DEFAULT_HAIKU_MODEL='${model}'`,
520
+ 'exec claude "$@"',
521
+ ""
522
+ ].join("\n"), { mode: 448 });
523
+ try {
524
+ chmodSync(launcher, 448);
525
+ } catch {
526
+ }
527
+ }
528
+ writeState("claude-code", {
529
+ target: "claude-code",
530
+ files: [{ path: launcher, ...backup }],
531
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
532
+ }, deps.env);
533
+ deps.out("Claude Code terhubung ke BotConnector Anthropic Messages compatibility API.");
534
+ deps.out(`Launcher: ${launcher}`);
535
+ deps.out(`Default coding model: ${model}`);
536
+ }
537
+ function disconnect(target, deps) {
538
+ const state = readState(target, deps.env);
539
+ if (!state) {
540
+ deps.out(`${target}: belum dikelola oleh BCCLI.`);
541
+ return;
542
+ }
543
+ for (const file of [...state.files].reverse()) {
544
+ if (file.backup && existsSync(file.backup)) {
545
+ mkdirSync(dirname(file.path), { recursive: true });
546
+ copyFileSync(file.backup, file.path);
547
+ } else if (file.created) {
548
+ rmSync(file.path, { force: true });
549
+ }
550
+ }
551
+ rmSync(statePath(target, deps.env), { force: true });
552
+ deps.out(`${target}: integrasi BotConnector dilepas dan config dipulihkan.`);
553
+ }
554
+ function listIntegrations(deps) {
555
+ for (const target of ["opencode", "aider", "cline", "deepseek-harness", "cursor", "openai-cli", "openai-sdk", "openai-compatible", "codex", "claude-code"]) {
556
+ deps.out(`${target} ${readState(target, deps.env) ? "connected" : "not connected"}`);
557
+ }
558
+ }
559
+ async function runIntegrationCommand(args, deps) {
560
+ if (args.command === "integrations") {
561
+ listIntegrations(deps);
562
+ return 0;
563
+ }
564
+ const target = parseTarget(args.subArgs[0]);
565
+ if (args.command === "disconnect") {
566
+ disconnect(target, deps);
567
+ return 0;
568
+ }
569
+ if (target === "opencode") await connectOpenCode(deps);
570
+ else if (target === "aider") await connectAider(deps);
571
+ else if (target === "cline") await connectCline(deps);
572
+ else if (target === "deepseek-harness") await connectDeepSeekHarness(deps);
573
+ else if (target === "cursor") await connectCursor(deps);
574
+ else if (target === "openai-cli") await connectOpenAiCli(deps);
575
+ else if (target === "openai-sdk") await connectOpenAiSdk(deps);
576
+ else if (target === "openai-compatible") await connectOpenAiCompatible(deps);
577
+ else if (target === "codex") await connectCodex(deps);
578
+ else await connectClaudeCode(deps);
579
+ return 0;
580
+ }
581
+ export {
582
+ runIntegrationCommand
583
+ };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-QH7XXNHU.js";
5
+ } from "./chunk-6BLZELE6.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-TV6LEHOH.js";
5
+ } from "./chunk-DX7CZTBB.js";
6
6
  import {
7
7
  addGlobalServer,
8
8
  globalMcpPath,
9
9
  readMcpFile,
10
10
  removeGlobalServer
11
- } from "./chunk-XQLXE666.js";
11
+ } from "./chunk-BFZXRKW5.js";
12
12
  import {
13
13
  bccliHome
14
- } from "./chunk-Z5JM76EL.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
+ };