@botconnector/bccli 0.2.0-beta.1 → 0.3.0-beta.1

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/dist/cli.js CHANGED
@@ -1,346 +1,40 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ HELP_TEXT,
4
+ parseCliArgs
5
+ } from "./chunk-RDQ7HPWS.js";
6
+ import {
7
+ addProviderKey,
8
+ createProvider,
9
+ listAllModels
10
+ } from "./chunk-MANM7HY6.js";
11
+ import {
12
+ runLogin
13
+ } from "./chunk-E4BWUMQ2.js";
2
14
  import {
3
15
  Session,
4
16
  pruneSessions
5
17
  } from "./chunk-SJH3YLW4.js";
6
18
  import {
7
- MODE_ORDER,
8
- Permissions,
9
19
  ignorePatterns,
20
+ loadAgentDefs,
21
+ loadCommands,
22
+ loadSkills,
23
+ parseFrontmatter,
10
24
  runPrint
11
- } from "./chunk-WZYWKWPD.js";
12
-
13
- // src/args.ts
14
- import { parseArgs } from "util";
15
-
16
- // src/config.ts
17
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
18
- import { homedir } from "os";
19
- import { join } from "path";
20
- var ConfigError = class extends Error {
21
- };
22
- var DEFAULT_CONFIG = {
23
- model: "bc-cloud/glm-5.3-flash",
24
- permissionMode: "default",
25
- providers: {
26
- "bc-cloud": { baseURL: "https://api.botconnector.id/v1", apiKeyEnv: "BOTCONNECTOR_API_KEY" }
27
- },
28
- allow: []
29
- };
30
- function bccliHome(env = process.env) {
31
- return env.BCCLI_HOME ?? join(homedir(), ".bccli");
32
- }
33
- function readJson(path) {
34
- if (!existsSync(path)) return {};
35
- try {
36
- return JSON.parse(readFileSync(path, "utf8"));
37
- } catch (error) {
38
- throw new ConfigError(`Config tidak valid: ${path} (${error.message})`);
39
- }
40
- }
41
- function untrustedProviders(project, known) {
42
- const out = {};
43
- for (const [id, provider] of Object.entries(project.providers ?? {})) {
44
- if (known[id] || typeof provider?.baseURL !== "string") continue;
45
- out[id] = { baseURL: provider.baseURL };
46
- }
47
- return out;
48
- }
49
- function loadConfig(cwd, env = process.env) {
50
- const global = readJson(join(bccliHome(env), "config.json"));
51
- const project = readJson(join(cwd, ".bccli", "config.json"));
52
- const known = { ...DEFAULT_CONFIG.providers, ...global.providers };
53
- return {
54
- model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
55
- permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
56
- providers: { ...known, ...untrustedProviders(project, known) },
57
- allow: [...global.allow ?? []]
58
- };
59
- }
60
- function readCredentials(env = process.env) {
61
- const path = join(bccliHome(env), "credentials");
62
- if (!existsSync(path)) return {};
63
- try {
64
- return JSON.parse(readFileSync(path, "utf8"));
65
- } catch {
66
- return {};
67
- }
68
- }
69
- function saveCredential(providerId, key, env = process.env) {
70
- const home = bccliHome(env);
71
- mkdirSync(home, { recursive: true });
72
- const path = join(home, "credentials");
73
- writeFileSync(path, JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), { mode: 384 });
74
- chmodSync(path, 384);
75
- }
76
- function resolveModel(config, modelRef, env = process.env) {
77
- const slash = modelRef.indexOf("/");
78
- if (slash <= 0 || slash === modelRef.length - 1) {
79
- throw new ConfigError(`Model harus berformat provider/model, contoh bc-cloud/glm-5.3-flash (dapat: ${modelRef})`);
80
- }
81
- const providerId = modelRef.slice(0, slash);
82
- const provider = config.providers[providerId];
83
- if (!provider) {
84
- throw new ConfigError(`Provider "${providerId}" tidak ada di config. Tersedia: ${Object.keys(config.providers).join(", ")}`);
85
- }
86
- const apiKey = (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || readCredentials(env)[providerId] || void 0;
87
- if (provider.apiKeyEnv && !apiKey) {
88
- throw new ConfigError(`API key untuk ${providerId} belum ada. Jalankan \`bccli login\` atau set env ${provider.apiKeyEnv}.`);
89
- }
90
- return { providerId, model: modelRef.slice(slash + 1), baseURL: provider.baseURL.replace(/\/+$/, ""), apiKey };
91
- }
92
-
93
- // src/args.ts
94
- var HELP_TEXT = `BCCLI \u2014 agent AI BotConnector di terminal
95
-
96
- Pemakaian:
97
- bccli [tugas] mode interaktif (opsional langsung dengan tugas)
98
- bccli -p "tugas" jalankan satu tugas tanpa interaksi (skrip/CI)
99
- bccli login [provider] simpan API key (default: bc-cloud)
100
- bccli models daftar model dari provider aktif
101
-
102
- Opsi:
103
- -m, --model <provider/model> pilih model, contoh bc-cloud/glm-5.3-flash
104
- -c, --continue lanjutkan sesi terakhir di folder ini
105
- -r, --resume pilih sesi untuk dilanjutkan
106
- --allow-all jalankan semua alat tanpa minta izin
107
- --allowed-tools <a,b> alat yang boleh tanpa izin: bash, edit, fetch
108
- --permission-mode <mode> default | acceptEdits | plan | allowAll
109
- -v, --version versi
110
- -h, --help bantuan ini`;
111
- function parseCliArgs(argv) {
112
- const { values, positionals } = parseArgs({
113
- args: argv,
114
- allowPositionals: true,
115
- options: {
116
- print: { type: "boolean", short: "p" },
117
- model: { type: "string", short: "m" },
118
- continue: { type: "boolean", short: "c" },
119
- resume: { type: "boolean", short: "r" },
120
- "allow-all": { type: "boolean" },
121
- "allowed-tools": { type: "string" },
122
- "permission-mode": { type: "string" },
123
- help: { type: "boolean", short: "h" },
124
- version: { type: "boolean", short: "v" }
125
- }
126
- });
127
- const mode = values["permission-mode"];
128
- if (mode && !MODE_ORDER.includes(mode)) {
129
- throw new ConfigError(`--permission-mode harus salah satu dari: ${MODE_ORDER.join(", ")}`);
130
- }
131
- const [first, ...rest] = positionals;
132
- const command = first === "login" || first === "models" ? first : "run";
133
- const words = command === "run" ? positionals : rest;
134
- return {
135
- command,
136
- prompt: command === "run" && words.length ? words.join(" ") : void 0,
137
- print: !!values.print,
138
- model: values.model,
139
- continue: !!values.continue,
140
- resume: !!values.resume,
141
- allowAll: !!values["allow-all"],
142
- allowedTools: (values["allowed-tools"] ?? "").split(",").map((s) => s.trim()).filter(Boolean),
143
- permissionMode: mode,
144
- help: !!values.help,
145
- version: !!values.version,
146
- loginProvider: command === "login" ? rest[0] ?? "bc-cloud" : void 0
147
- };
148
- }
149
-
150
- // src/provider.ts
151
- var ProviderError = class extends Error {
152
- constructor(message, status) {
153
- super(message);
154
- this.status = status;
155
- }
156
- status;
157
- };
158
- var retryable = (status) => status === 429 || status >= 500;
159
- function sleep(ms, signal) {
160
- return new Promise((resolve2, reject) => {
161
- const timer = setTimeout(resolve2, ms);
162
- signal?.addEventListener(
163
- "abort",
164
- () => {
165
- clearTimeout(timer);
166
- reject(signal.reason);
167
- },
168
- { once: true }
169
- );
170
- });
171
- }
172
- function errorMessage(body) {
173
- try {
174
- const parsed = JSON.parse(body);
175
- return parsed?.error?.message ?? parsed?.message ?? body.slice(0, 300);
176
- } catch {
177
- return body.slice(0, 300);
178
- }
179
- }
180
- var toUsage = (u) => u ? { inputTokens: u.prompt_tokens ?? 0, outputTokens: u.completion_tokens ?? 0 } : void 0;
181
- async function readStream(res, onText) {
182
- let text = "";
183
- let usage;
184
- let finishReason;
185
- const calls = [];
186
- const byIndex = /* @__PURE__ */ new Map();
187
- let current;
188
- const decoder = new TextDecoder();
189
- let buffer = "";
190
- const handle = (line) => {
191
- if (!line.startsWith("data:")) return;
192
- const data = line.slice(5).trim();
193
- if (!data || data === "[DONE]") return;
194
- let event;
195
- try {
196
- event = JSON.parse(data);
197
- } catch {
198
- return;
199
- }
200
- if (event.error) throw new ProviderError(`Error dari provider: ${event.error.message ?? JSON.stringify(event.error)}`);
201
- if (event.usage) usage = toUsage(event.usage);
202
- const choice = event.choices?.[0];
203
- if (!choice) return;
204
- if (choice.finish_reason) finishReason = choice.finish_reason;
205
- const delta = choice.delta ?? {};
206
- if (delta.content) {
207
- text += delta.content;
208
- onText?.(delta.content);
209
- }
210
- for (const tc of delta.tool_calls ?? []) {
211
- let target = tc.index !== void 0 ? byIndex.get(tc.index) : current;
212
- if (!target || tc.index === void 0 && tc.id && target.id && target.id !== tc.id) {
213
- target = { id: "", name: "", arguments: "" };
214
- calls.push(target);
215
- if (tc.index !== void 0) byIndex.set(tc.index, target);
216
- }
217
- current = target;
218
- if (tc.id) target.id = tc.id;
219
- if (tc.function?.name && !target.name) target.name = tc.function.name;
220
- if (tc.function?.arguments) target.arguments += tc.function.arguments;
221
- }
222
- };
223
- for await (const chunk of res.body) {
224
- buffer += decoder.decode(chunk, { stream: true });
225
- let newline = buffer.indexOf("\n");
226
- while (newline >= 0) {
227
- handle(buffer.slice(0, newline).trim());
228
- buffer = buffer.slice(newline + 1);
229
- newline = buffer.indexOf("\n");
230
- }
231
- }
232
- handle(buffer.trim());
233
- const toolCalls = calls.map((call, index) => ({ ...call, id: call.id || `call_${index}` }));
234
- return { text, toolCalls, usage, finishReason };
235
- }
236
- async function readJson2(res) {
237
- const body = await res.json();
238
- const choice = body.choices?.[0];
239
- const toolCalls = (choice?.message?.tool_calls ?? []).map((tc, i) => ({
240
- id: tc.id || `call_${i}`,
241
- name: tc.function?.name ?? "",
242
- arguments: tc.function?.arguments ?? ""
243
- }));
244
- return { text: choice?.message?.content ?? "", toolCalls, usage: toUsage(body.usage), finishReason: choice?.finish_reason };
245
- }
246
- function createProvider({ baseURL, apiKey, model, fetch: doFetch = fetch, retryDelayMs = 1e3 }) {
247
- const headers = { "content-type": "application/json" };
248
- if (apiKey) headers.authorization = `Bearer ${apiKey}`;
249
- async function post(path, body, signal) {
250
- let lastError;
251
- for (let attempt = 0; attempt < 4; attempt++) {
252
- if (attempt > 0) await sleep(retryDelayMs * 2 ** (attempt - 1), signal);
253
- let res;
254
- try {
255
- res = await doFetch(`${baseURL}${path}`, { method: "POST", headers, body: JSON.stringify(body), signal });
256
- } catch (error) {
257
- if (signal?.aborted) throw error;
258
- lastError = new ProviderError(`Tidak bisa terhubung ke ${baseURL}: ${error.message}`);
259
- continue;
260
- }
261
- if (res.ok) return res;
262
- const text = await res.text().catch(() => "");
263
- lastError = new ProviderError(`${res.status} dari ${baseURL}: ${errorMessage(text)}`, res.status);
264
- if (!retryable(res.status)) throw lastError;
265
- }
266
- throw lastError ?? new ProviderError("Permintaan gagal");
267
- }
268
- return {
269
- async chat({ messages, tools, signal, onText }) {
270
- const res = await post(
271
- "/chat/completions",
272
- { model, messages, stream: true, stream_options: { include_usage: true }, ...tools?.length ? { tools } : {} },
273
- signal
274
- );
275
- if ((res.headers.get("content-type") ?? "").includes("application/json")) return readJson2(res);
276
- if (!res.body) throw new ProviderError("Respons provider kosong");
277
- return readStream(res, onText);
278
- },
279
- async listModels() {
280
- const res = await doFetch(`${baseURL}/models`, { headers });
281
- if (!res.ok) throw new ProviderError(`${res.status} saat mengambil daftar model`, res.status);
282
- const body = await res.json();
283
- return (body.data ?? []).map((m) => m.id).sort();
284
- }
285
- };
286
- }
287
-
288
- // src/login.ts
289
- function readSecret(prompt) {
290
- return new Promise((resolve2) => {
291
- process.stdout.write(prompt);
292
- const stdin = process.stdin;
293
- if (!stdin.isTTY) {
294
- let data = "";
295
- stdin.on("data", (d) => {
296
- data += d;
297
- });
298
- stdin.on("end", () => resolve2(data.trim()));
299
- return;
300
- }
301
- let value = "";
302
- stdin.setRawMode(true);
303
- stdin.resume();
304
- const onData = (buffer) => {
305
- for (const ch of buffer.toString()) {
306
- if (ch === "\r" || ch === "\n") {
307
- stdin.setRawMode(false);
308
- stdin.pause();
309
- stdin.off("data", onData);
310
- process.stdout.write("\n");
311
- resolve2(value.trim());
312
- return;
313
- }
314
- if (ch === "") process.exit(130);
315
- if (ch === "\x7F" || ch === "\b") value = value.slice(0, -1);
316
- else value += ch;
317
- }
318
- };
319
- stdin.on("data", onData);
320
- });
321
- }
322
- async function runLogin(providerId, cwd) {
323
- const config = loadConfig(cwd);
324
- const provider = config.providers[providerId];
325
- if (!provider) {
326
- console.error(`Provider "${providerId}" tidak ada. Tersedia: ${Object.keys(config.providers).join(", ")}`);
327
- return 1;
328
- }
329
- const key = await readSecret(`API key untuk ${providerId} (${provider.baseURL}): `);
330
- if (!key) {
331
- console.error("API key kosong, tidak disimpan.");
332
- return 1;
333
- }
334
- try {
335
- const models = await createProvider({ baseURL: provider.baseURL.replace(/\/+$/, ""), apiKey: key, model: "" }).listModels();
336
- saveCredential(providerId, key);
337
- console.log(`Tersimpan. ${models.length} model tersedia di ${providerId}.`);
338
- return 0;
339
- } catch (error) {
340
- console.error(`API key ditolak atau provider tidak bisa dihubungi: ${error.message}`);
341
- return 1;
342
- }
343
- }
25
+ } from "./chunk-TBPPXOOZ.js";
26
+ import {
27
+ Permissions
28
+ } from "./chunk-5XN44Q55.js";
29
+ import {
30
+ providerName
31
+ } from "./chunk-5C6NQFSI.js";
32
+ import {
33
+ ConfigError,
34
+ bccliHome,
35
+ loadConfig,
36
+ resolveModel
37
+ } from "./chunk-Z5JM76EL.js";
344
38
 
345
39
  // src/tools/index.ts
346
40
  import { z as z8 } from "zod";
@@ -775,7 +469,7 @@ var readTool = defineTool({
775
469
  });
776
470
 
777
471
  // src/tools/write.ts
778
- import { existsSync as existsSync2 } from "fs";
472
+ import { existsSync } from "fs";
779
473
  import { mkdir, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
780
474
  import { dirname } from "path";
781
475
  import { z as z7 } from "zod";
@@ -790,16 +484,16 @@ var writeTool = defineTool({
790
484
  target: (input) => input.path,
791
485
  async validate(input, ctx) {
792
486
  const abs = resolvePath(ctx.cwd, input.path);
793
- return existsSync2(abs) && !ctx.readFiles.has(abs) ? `${input.path} sudah ada. Baca dulu dengan read sebelum menimpanya.` : void 0;
487
+ return existsSync(abs) && !ctx.readFiles.has(abs) ? `${input.path} sudah ada. Baca dulu dengan read sebelum menimpanya.` : void 0;
794
488
  },
795
489
  async preview(input, ctx) {
796
490
  const abs = resolvePath(ctx.cwd, input.path);
797
- const old = existsSync2(abs) ? await readFile4(abs, "utf8") : "";
491
+ const old = existsSync(abs) ? await readFile4(abs, "utf8") : "";
798
492
  return formatDiff(diffLines(old, input.content));
799
493
  },
800
494
  async run(input, ctx) {
801
495
  const abs = resolvePath(ctx.cwd, input.path);
802
- if (existsSync2(abs) && !ctx.readFiles.has(abs)) {
496
+ if (existsSync(abs) && !ctx.readFiles.has(abs)) {
803
497
  return { output: `${input.path} sudah ada. Baca dulu dengan read sebelum menimpanya.`, isError: true };
804
498
  }
805
499
  try {
@@ -818,7 +512,7 @@ var writeTool = defineTool({
818
512
  var ALL_TOOLS = [readTool, writeTool, editTool, bashTool, grepTool, globTool, fetchTool];
819
513
  function toolDefinitions(tools) {
820
514
  return tools.map((tool) => {
821
- const { $schema: _ignored, ...parameters } = z8.toJSONSchema(tool.schema);
515
+ const { $schema: _ignored, ...parameters } = tool.jsonSchema ?? z8.toJSONSchema(tool.schema);
822
516
  return { type: "function", function: { name: tool.name, description: tool.description, parameters } };
823
517
  });
824
518
  }
@@ -833,6 +527,8 @@ var Agent = class {
833
527
  this.permissions = opts.permissions;
834
528
  this.tools = opts.tools;
835
529
  this.definitions = toolDefinitions(opts.tools);
530
+ this.turnTools = this.tools;
531
+ this.turnDefinitions = this.definitions;
836
532
  this.messages = [...opts.history ?? []];
837
533
  this.maxSteps = opts.maxSteps ?? 50;
838
534
  this.contextWindow = opts.contextWindow ?? 128e3;
@@ -848,10 +544,27 @@ var Agent = class {
848
544
  askPermission = async () => "no";
849
545
  tools;
850
546
  definitions;
547
+ // Snapshot for the running turn: tools added/removed mid-turn (e.g. an MCP server connecting) apply from the next turn.
548
+ turnTools;
549
+ turnDefinitions;
851
550
  readFiles = /* @__PURE__ */ new Set();
852
551
  maxSteps;
853
552
  // ponytail: one window for every model; read it from /v1/models metadata when providers expose it.
854
553
  contextWindow;
554
+ setTools(tools) {
555
+ this.tools = tools;
556
+ this.definitions = toolDefinitions(tools);
557
+ }
558
+ isParallelSafe(call) {
559
+ const tool = this.turnTools.find((t) => t.name === call.name);
560
+ if (!tool?.parallelSafe) return false;
561
+ try {
562
+ const parsed = tool.schema.safeParse(JSON.parse(call.arguments || "{}"));
563
+ return parsed.success && tool.parallelSafe(parsed.data);
564
+ } catch {
565
+ return false;
566
+ }
567
+ }
855
568
  push(message) {
856
569
  this.messages.push(message);
857
570
  this.opts.onMessage?.(message);
@@ -876,6 +589,8 @@ ${completion.text}` });
876
589
  this.onEvent({ type: "compacted" });
877
590
  }
878
591
  async run(text, signal) {
592
+ this.turnTools = this.tools;
593
+ this.turnDefinitions = this.definitions;
879
594
  try {
880
595
  if (this.lastInputTokens > this.contextWindow * 0.8) await this.compact(signal);
881
596
  this.push({ role: "user", content: text });
@@ -886,7 +601,7 @@ ${completion.text}` });
886
601
  }
887
602
  const completion = await this.provider.chat({
888
603
  messages: [{ role: "system", content: this.opts.systemPrompt }, ...this.messages],
889
- tools: this.definitions,
604
+ tools: this.turnDefinitions,
890
605
  signal,
891
606
  onText: (delta) => this.onEvent({ type: "text", delta })
892
607
  });
@@ -913,16 +628,22 @@ ${completion.text}` });
913
628
  this.onEvent({ type: "done" });
914
629
  return;
915
630
  }
916
- for (let i = 0; i < completion.toolCalls.length; i++) {
917
- const call = completion.toolCalls[i];
631
+ const calls = completion.toolCalls;
632
+ let i = 0;
633
+ while (i < calls.length) {
918
634
  if (signal.aborted) {
919
- for (const pending of completion.toolCalls.slice(i)) {
920
- this.push({ role: "tool", tool_call_id: pending.id, content: "Dibatalkan oleh user." });
921
- }
635
+ for (const pending of calls.slice(i)) this.push({ role: "tool", tool_call_id: pending.id, content: "Dibatalkan oleh user." });
922
636
  this.onEvent({ type: "aborted" });
923
637
  return;
924
638
  }
925
- this.push({ role: "tool", tool_call_id: call.id, content: await this.runTool(call, signal) });
639
+ let j = i + 1;
640
+ if (this.isParallelSafe(calls[i])) while (j < calls.length && this.isParallelSafe(calls[j])) j++;
641
+ const batch = calls.slice(i, j);
642
+ const results = await Promise.all(batch.map((c) => this.runTool(c, signal)));
643
+ batch.forEach((c, k) => {
644
+ this.push({ role: "tool", tool_call_id: c.id, content: results[k] });
645
+ });
646
+ i = j;
926
647
  }
927
648
  if (signal.aborted) {
928
649
  this.onEvent({ type: "aborted" });
@@ -950,8 +671,8 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
950
671
  this.onEvent({ type: "toolEnd", id: call.id, tool: call.name, output, isError: true });
951
672
  return output;
952
673
  };
953
- const tool = this.tools.find((t) => t.name === call.name);
954
- if (!tool) return fail(`Alat "${call.name}" tidak ada. Alat yang tersedia: ${this.tools.map((t) => t.name).join(", ")}`);
674
+ const tool = this.turnTools.find((t) => t.name === call.name);
675
+ if (!tool) return fail(`Alat "${call.name}" tidak ada. Alat yang tersedia: ${this.turnTools.map((t) => t.name).join(", ")}`);
955
676
  let args;
956
677
  try {
957
678
  args = JSON.parse(call.arguments || "{}");
@@ -964,7 +685,19 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
964
685
  }
965
686
  const input = parsed.data;
966
687
  const target = tool.target(input);
967
- const ctx = { cwd: this.opts.cwd, signal, readFiles: this.readFiles };
688
+ const ctx = {
689
+ cwd: this.opts.cwd,
690
+ signal,
691
+ readFiles: this.readFiles,
692
+ callId: call.id,
693
+ emit: (event) => this.onEvent(event),
694
+ ask: (req) => this.askPermission(req),
695
+ addUsage: (u) => {
696
+ this.totalUsage.inputTokens += u.inputTokens;
697
+ this.totalUsage.outputTokens += u.outputTokens;
698
+ this.onEvent({ type: "usage", ...this.totalUsage });
699
+ }
700
+ };
968
701
  this.onEvent({ type: "toolStart", id: call.id, tool: tool.name, target });
969
702
  const invalid = await tool.validate?.(input, ctx).catch((error) => error.message);
970
703
  if (invalid) {
@@ -975,7 +708,12 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
975
708
  let decision = this.permissions.check(request);
976
709
  if (decision === "ask") {
977
710
  const preview = await tool.preview?.(input, ctx).catch(() => void 0);
978
- const answer = await this.askPermission({ ...request, preview, sessionRules: this.permissions.rulesFor(request) });
711
+ const answer = await this.askPermission({
712
+ ...request,
713
+ preview,
714
+ sessionRules: this.permissions.rulesFor(request),
715
+ ...this.opts.label ? { agent: this.opts.label } : {}
716
+ });
979
717
  if (answer === "session") this.permissions.allowForSession(request);
980
718
  decision = answer === "no" ? "deny" : "allow";
981
719
  }
@@ -995,13 +733,142 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
995
733
  }
996
734
  };
997
735
 
736
+ // src/agents.ts
737
+ var EXPLORE_PROMPT = "You are a read-only research agent working for another agent. Search and read the codebase to answer the request. You cannot edit files or run commands. Reply with a concise report: the answer, the relevant file paths with line numbers, and nothing else.";
738
+ var GENERAL_PROMPT = "You are an agent handling a delegated task for another agent. Complete the task end to end with the tools you have, verify the result, and reply with a short report of what you did and what you found.";
739
+ var BUILTIN_AGENTS = [
740
+ {
741
+ name: "explore",
742
+ description: "Read-only: search and read code to answer a question (fast, safe, can run in parallel).",
743
+ tools: ["read", "grep", "glob"],
744
+ prompt: EXPLORE_PROMPT,
745
+ maxSteps: 30
746
+ },
747
+ {
748
+ name: "general",
749
+ description: "Full tools: carry out a self-contained sub-task (edits and commands still need user permission).",
750
+ prompt: GENERAL_PROMPT,
751
+ maxSteps: 50
752
+ }
753
+ ];
754
+
755
+ // src/mcp/manager.ts
756
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
757
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
758
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
759
+ import { z as z9 } from "zod";
760
+ function mcpToolName(server, tool, taken) {
761
+ const clean = (s) => s.replace(/[^A-Za-z0-9_-]+/g, "_");
762
+ const base = `mcp__${clean(server)}__${clean(tool)}`.slice(0, 64);
763
+ let name = base;
764
+ for (let n = 2; taken.has(name); n++) name = `${base.slice(0, 64 - String(n).length - 1)}_${n}`;
765
+ taken.add(name);
766
+ return name;
767
+ }
768
+ function withTimeout(promise, ms) {
769
+ let timer;
770
+ const timeout = new Promise((_, reject) => {
771
+ timer = setTimeout(() => reject(new Error(`timeout ${Math.round(ms / 1e3)} detik`)), ms);
772
+ });
773
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
774
+ }
775
+ var McpManager = class {
776
+ constructor(opts = {}) {
777
+ this.opts = opts;
778
+ }
779
+ opts;
780
+ servers = /* @__PURE__ */ new Map();
781
+ taken = /* @__PURE__ */ new Set();
782
+ async start(specs) {
783
+ await Promise.all(specs.map((spec) => this.add(spec)));
784
+ }
785
+ async add(spec) {
786
+ await this.remove(spec.name);
787
+ const entry = { spec, state: { name: spec.name, source: spec.source, status: "starting", tools: 0 }, tools: [] };
788
+ this.servers.set(spec.name, entry);
789
+ this.opts.onChange?.();
790
+ const client = new Client({ name: "bccli", version: "0.3.0" });
791
+ entry.client = client;
792
+ const timeoutMs = this.opts.connectTimeoutMs ?? 3e4;
793
+ try {
794
+ const config = spec.config;
795
+ const transport = config.type === "http" ? new StreamableHTTPClientTransport(new URL(config.url), { requestInit: { headers: config.headers ?? {} } }) : new StdioClientTransport({
796
+ command: config.command,
797
+ args: config.args ?? [],
798
+ env: { ...process.env, ...config.env },
799
+ stderr: "ignore"
800
+ });
801
+ await withTimeout(client.connect(transport), timeoutMs);
802
+ const { tools } = await withTimeout(client.listTools(), timeoutMs);
803
+ if (this.servers.get(spec.name) !== entry) {
804
+ await client.close().catch(() => {
805
+ });
806
+ return;
807
+ }
808
+ entry.tools = tools.map((t) => this.wrap(spec.name, client, t));
809
+ entry.state = { ...entry.state, status: "ready", tools: entry.tools.length };
810
+ } catch (error) {
811
+ entry.state = { ...entry.state, status: "error", error: error.message };
812
+ await client.close().catch(() => {
813
+ });
814
+ if (this.servers.get(spec.name) !== entry) return;
815
+ }
816
+ this.opts.onChange?.();
817
+ }
818
+ wrap(server, client, tool) {
819
+ return {
820
+ name: mcpToolName(server, tool.name, this.taken),
821
+ description: `[MCP ${server}] ${tool.description ?? tool.name}`.slice(0, 1024),
822
+ schema: z9.record(z9.string(), z9.unknown()),
823
+ jsonSchema: tool.inputSchema,
824
+ kind: "mcp",
825
+ target: (input) => {
826
+ const text = JSON.stringify(input);
827
+ return text.length > 80 ? `${text.slice(0, 77)}\u2026` : text;
828
+ },
829
+ async run(input, ctx) {
830
+ try {
831
+ const result = await client.callTool({ name: tool.name, arguments: input }, void 0, { signal: ctx.signal });
832
+ const output = (result.content ?? []).map((c) => c.type === "text" ? c.text ?? "" : `[${c.type}]`).join("\n") || "(kosong)";
833
+ return { output: output.length > 5e4 ? `${output.slice(0, 5e4)}
834
+ \u2026 [dipotong]` : output, isError: !!result.isError };
835
+ } catch (error) {
836
+ return { output: `Server MCP ${server} error: ${error.message}`, isError: true };
837
+ }
838
+ }
839
+ };
840
+ }
841
+ async remove(name) {
842
+ const entry = this.servers.get(name);
843
+ if (!entry) return;
844
+ this.servers.delete(name);
845
+ for (const t of entry.tools) this.taken.delete(t.name);
846
+ if (entry.tools.length) this.opts.onToolsRemoved?.(entry.tools.map((t) => t.name));
847
+ await entry.client?.close().catch(() => {
848
+ });
849
+ this.opts.onChange?.();
850
+ }
851
+ tools() {
852
+ return [...this.servers.values()].flatMap((s) => s.tools);
853
+ }
854
+ states() {
855
+ return [...this.servers.values()].map((s) => s.state);
856
+ }
857
+ async stop() {
858
+ await Promise.all([...this.servers.keys()].map((name) => this.remove(name)));
859
+ }
860
+ };
861
+
862
+ // src/setup.ts
863
+ import { homedir } from "os";
864
+
998
865
  // src/context.ts
999
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1000
- import { dirname as dirname2, join as join2 } from "path";
866
+ import { existsSync as existsSync2, readFileSync } from "fs";
867
+ import { dirname as dirname2, join } from "path";
1001
868
  var FILES = ["AGENTS.md", "BCCLI.md"];
1002
869
  function readIf(path) {
1003
870
  try {
1004
- return existsSync3(path) ? readFileSync2(path, "utf8").trim() : void 0;
871
+ return existsSync2(path) ? readFileSync(path, "utf8").trim() : void 0;
1005
872
  } catch {
1006
873
  return void 0;
1007
874
  }
@@ -1013,13 +880,13 @@ function loadInstructions(cwd, home) {
1013
880
  if (dirname2(dir) === dir) break;
1014
881
  }
1015
882
  const parts = [];
1016
- const global = readIf(join2(home, "BCCLI.md"));
1017
- if (global) parts.push(`# ${join2(home, "BCCLI.md")}
883
+ const global = readIf(join(home, "BCCLI.md"));
884
+ if (global) parts.push(`# ${join(home, "BCCLI.md")}
1018
885
  ${global}`);
1019
886
  for (const dir of dirs) {
1020
887
  for (const name of FILES) {
1021
- const text = readIf(join2(dir, name));
1022
- if (text) parts.push(`# ${join2(dir, name)}
888
+ const text = readIf(join(dir, name));
889
+ if (text) parts.push(`# ${join(dir, name)}
1023
890
  ${text}`);
1024
891
  }
1025
892
  }
@@ -1027,17 +894,24 @@ ${text}`);
1027
894
  }
1028
895
  function isGitRepo(cwd) {
1029
896
  for (let dir = cwd; ; dir = dirname2(dir)) {
1030
- if (existsSync3(join2(dir, ".git"))) return true;
897
+ if (existsSync2(join(dir, ".git"))) return true;
1031
898
  if (dirname2(dir) === dir) return false;
1032
899
  }
1033
900
  }
1034
901
  function buildSystemPrompt(opts) {
1035
902
  const instructions = loadInstructions(opts.cwd, opts.home);
903
+ const skills = (opts.skills ?? []).slice(0, 50);
904
+ const skillText = skills.length ? `
905
+
906
+ Skills (load one with the skill tool when it matches the task):
907
+ ${skills.map((s) => `- ${s.name}: ${s.description.slice(0, 200)}`).join("\n")}` : "";
1036
908
  return `You are BCCLI, a coding agent by BotConnector running in the user's terminal. You help with software engineering tasks by reading code, editing files, and running commands with the tools provided.
1037
909
 
1038
910
  How to work:
1039
911
  - Understand before changing: read the relevant files and search the codebase first. Never guess file contents.
1040
912
  - Always read a file before editing it. Make the smallest correct change; match the existing style.
913
+ - For tasks with 3 or more steps, keep a todo list with todo_write and update it as you go.
914
+ - In plan mode you can only read and search. When the plan is ready, call exit_plan with it and wait for approval.
1041
915
  - Verify your work: run the tests, build or the command that proves the change works, and report the real result.
1042
916
  - The user must approve edits and commands. If they decline, ask what they want instead of retrying.
1043
917
  - Be concise. Reply in the user's language. No preamble; lead with the answer or the action.
@@ -1048,19 +922,168 @@ Environment:
1048
922
  - Git repository: ${isGitRepo(opts.cwd) ? "yes" : "no"}
1049
923
  - Platform: ${opts.platform ?? process.platform}
1050
924
  - Date: ${opts.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
1051
- - Model: ${opts.model}${instructions ? `
925
+ - Model: ${opts.model}${skillText}${instructions ? `
1052
926
 
1053
927
  Project and user instructions (follow them):
1054
928
 
1055
929
  ${instructions}` : ""}`;
1056
930
  }
1057
931
 
932
+ // src/tools/plan.ts
933
+ import { z as z10 } from "zod";
934
+ function createExitPlanTool(opts) {
935
+ return defineTool({
936
+ name: "exit_plan",
937
+ description: "Plan mode only: present the finished plan to the user for approval. If approved you may start implementing it.",
938
+ schema: z10.object({ plan: z10.string().describe("The plan, in markdown") }),
939
+ kind: "read",
940
+ target: () => "rencana",
941
+ async run(input) {
942
+ if (opts.permissions.mode !== "plan") return { output: "exit_plan hanya dipakai di mode plan.", isError: true };
943
+ const decision = await opts.interaction.approvePlan(input.plan);
944
+ if (decision === "no") {
945
+ return { output: "User belum menyetujui. Tanyakan apa yang perlu diperbaiki, perbaiki rencananya, lalu panggil exit_plan lagi." };
946
+ }
947
+ opts.permissions.mode = decision;
948
+ return { output: `Rencana disetujui. Mode sekarang ${decision}. Mulai kerjakan rencananya sekarang.` };
949
+ }
950
+ });
951
+ }
952
+
953
+ // src/tools/skill.ts
954
+ import { readFileSync as readFileSync2 } from "fs";
955
+ import { z as z11 } from "zod";
956
+ function createSkillTool(skills) {
957
+ return defineTool({
958
+ name: "skill",
959
+ description: "Load a skill (instructions for a kind of task) by name. Use it when a listed skill matches the task, then follow it.",
960
+ schema: z11.object({ name: z11.string().describe("Skill name from the list in the system prompt") }),
961
+ kind: "read",
962
+ target: (input) => input.name,
963
+ async run(input) {
964
+ const skill = skills.find((s) => s.name === input.name);
965
+ if (!skill) return { output: `Skill "${input.name}" tidak ada. Tersedia: ${skills.map((s) => s.name).join(", ") || "(tidak ada)"}`, isError: true };
966
+ const { body } = parseFrontmatter(readFileSync2(skill.file, "utf8"));
967
+ return { output: `Base directory for this skill: ${skill.dir}
968
+
969
+ ${body}`, display: skill.description.slice(0, 60) };
970
+ }
971
+ });
972
+ }
973
+
974
+ // src/tools/task.ts
975
+ import { z as z12 } from "zod";
976
+ function createTaskTool(opts) {
977
+ const byName = new Map(opts.agents.map((a) => [a.name, a]));
978
+ const list = opts.agents.map((a) => `- ${a.name}: ${a.description}`).join("\n");
979
+ return defineTool({
980
+ name: "task",
981
+ description: `Delegate a self-contained sub-task to a subagent. It starts with an empty context and returns only its final report. Available agents:
982
+ ${list}`,
983
+ schema: z12.object({
984
+ agent: z12.string().describe("Agent name"),
985
+ description: z12.string().describe("3-6 word summary shown to the user"),
986
+ prompt: z12.string().describe("Full instructions; the subagent sees nothing else")
987
+ }),
988
+ kind: "read",
989
+ target: (input) => `[${input.agent}] ${input.description}`,
990
+ // Parallel only when the resolved agent can do nothing but read (a project file may redefine "explore").
991
+ parallelSafe: (input) => {
992
+ const allowed = byName.get(input.agent)?.tools;
993
+ if (!allowed) return false;
994
+ const base = opts.baseTools();
995
+ return allowed.every((name) => base.find((t) => t.name === name)?.kind === "read");
996
+ },
997
+ async run(input, ctx) {
998
+ const def = byName.get(input.agent);
999
+ if (!def) return { output: `Agent "${input.agent}" tidak ada. Tersedia: ${[...byName.keys()].join(", ")}`, isError: true };
1000
+ const base = opts.baseTools().filter((t) => t.name !== "task" && t.name !== "exit_plan");
1001
+ const unknown = (def.tools ?? []).filter((n) => !base.some((t) => t.name === n));
1002
+ if (unknown.length) return { output: `Agent "${def.name}": alat tidak dikenal: ${unknown.join(", ")}`, isError: true };
1003
+ const allowed = def.tools;
1004
+ const tools = allowed ? base.filter((t) => allowed.includes(t.name)) : base;
1005
+ let provider;
1006
+ try {
1007
+ provider = def.model ? opts.providerFor(def.model) : opts.provider();
1008
+ } catch (error) {
1009
+ return { output: `Agent "${def.name}": ${error.message}`, isError: true };
1010
+ }
1011
+ const child = new Agent({
1012
+ provider,
1013
+ tools,
1014
+ permissions: opts.permissions,
1015
+ systemPrompt: `${opts.systemPrompt}
1016
+
1017
+ # Your role
1018
+ ${def.prompt}`,
1019
+ cwd: opts.cwd,
1020
+ maxSteps: def.maxSteps ?? 50,
1021
+ label: def.name
1022
+ });
1023
+ let outcome;
1024
+ let steps = 0;
1025
+ child.onEvent = (event) => {
1026
+ if (event.type === "toolStart") steps++;
1027
+ if (event.type === "error" || event.type === "stepLimit" || event.type === "aborted" || event.type === "done") outcome = event;
1028
+ if (event.type !== "usage" && ctx.callId) ctx.emit?.({ type: "subagent", parentId: ctx.callId, agent: def.name, event });
1029
+ };
1030
+ if (ctx.ask) child.askPermission = ctx.ask;
1031
+ await child.run(input.prompt, ctx.signal);
1032
+ ctx.addUsage?.(child.totalUsage);
1033
+ const tokens = child.totalUsage.inputTokens + child.totalUsage.outputTokens;
1034
+ const display = `${steps} langkah \xB7 ${tokens >= 1e3 ? `${Math.round(tokens / 1e3)}k` : tokens} token`;
1035
+ if (outcome?.type === "error") return { output: `Subagent ${def.name} gagal: ${outcome.message}`, isError: true, display };
1036
+ if (outcome?.type === "aborted") return { output: `Subagent ${def.name} dibatalkan.`, isError: true, display };
1037
+ const last = [...child.messages].reverse().find((m) => m.role === "assistant" && m.content);
1038
+ const report = last && typeof last.content === "string" ? last.content : "(tidak ada laporan)";
1039
+ const suffix = outcome?.type === "stepLimit" ? `
1040
+
1041
+ (Subagent berhenti di batas ${def.maxSteps ?? 50} langkah.)` : "";
1042
+ return { output: report + suffix, display };
1043
+ }
1044
+ });
1045
+ }
1046
+
1047
+ // src/tools/todo.ts
1048
+ import { z as z13 } from "zod";
1049
+ var TodoStore = class {
1050
+ items = [];
1051
+ listeners = /* @__PURE__ */ new Set();
1052
+ set(items) {
1053
+ this.items = items;
1054
+ for (const fn of this.listeners) fn(items);
1055
+ }
1056
+ subscribe(fn) {
1057
+ this.listeners.add(fn);
1058
+ return () => this.listeners.delete(fn);
1059
+ }
1060
+ };
1061
+ function createTodoTool(store) {
1062
+ return defineTool({
1063
+ name: "todo_write",
1064
+ description: "Write the task list for multi-step work (3+ steps). Send the whole list every time; exactly one item in_progress while working. Mark items completed as soon as they are done.",
1065
+ schema: z13.object({
1066
+ todos: z13.array(z13.object({ content: z13.string(), status: z13.enum(["pending", "in_progress", "completed"]) }))
1067
+ }),
1068
+ kind: "read",
1069
+ target: (input) => `${input.todos.length} item`,
1070
+ async run(input) {
1071
+ if (input.todos.filter((t) => t.status === "in_progress").length > 1) {
1072
+ return { output: "Hanya boleh satu item in_progress.", isError: true };
1073
+ }
1074
+ store.set(input.todos);
1075
+ const done = input.todos.filter((t) => t.status === "completed").length;
1076
+ return { output: `Todo diperbarui: ${done}/${input.todos.length} selesai.` };
1077
+ }
1078
+ });
1079
+ }
1080
+
1058
1081
  // src/setup.ts
1059
1082
  var TOOL_RULES = { bash: "bash", edit: "edit", write: "edit", fetch: "fetch" };
1060
1083
  function createRuntime(opts) {
1061
1084
  const env = opts.env ?? process.env;
1062
1085
  const home = bccliHome(env);
1063
- const config = loadConfig(opts.cwd, env);
1086
+ let config = loadConfig(opts.cwd, env);
1064
1087
  let modelRef = opts.args.model ?? config.model;
1065
1088
  const makeProvider = (ref) => {
1066
1089
  const resolved = resolveModel(config, ref, env);
@@ -1070,6 +1093,12 @@ function createRuntime(opts) {
1070
1093
  const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
1071
1094
  const rules = [...config.allow, ...opts.args.allowedTools.map((t) => TOOL_RULES[t] ?? t)];
1072
1095
  const permissions = new Permissions(mode, rules, opts.cwd);
1096
+ const roots = { cwd: opts.cwd, home, userHome: opts.userHome ?? homedir() };
1097
+ const skills = loadSkills(roots);
1098
+ const commands = loadCommands(roots);
1099
+ const agentDefs = loadAgentDefs(roots);
1100
+ const todos = new TodoStore();
1101
+ const interaction = { approvePlan: async () => "no" };
1073
1102
  let session = Session.create(home, opts.cwd);
1074
1103
  let history = opts.history;
1075
1104
  if (!history && opts.args.continue) {
@@ -1079,20 +1108,61 @@ function createRuntime(opts) {
1079
1108
  history = latest.load();
1080
1109
  }
1081
1110
  }
1111
+ const systemPrompt = buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills });
1112
+ const baseTools = [...ALL_TOOLS, createSkillTool(skills), createTodoTool(todos), createExitPlanTool({ permissions, interaction })];
1113
+ const task = createTaskTool({
1114
+ agents: [...BUILTIN_AGENTS, ...agentDefs],
1115
+ // Subagents get whatever the main agent has right now (incl. MCP tools), minus task itself.
1116
+ baseTools: () => agent.tools.filter((t) => t.name !== "task"),
1117
+ permissions,
1118
+ provider: () => agent.provider,
1119
+ providerFor: makeProvider,
1120
+ systemPrompt,
1121
+ cwd: opts.cwd
1122
+ });
1082
1123
  const agent = new Agent({
1083
1124
  provider,
1084
- tools: ALL_TOOLS,
1125
+ tools: [...baseTools, task],
1085
1126
  permissions,
1086
- systemPrompt: buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef }),
1127
+ systemPrompt,
1087
1128
  cwd: opts.cwd,
1088
1129
  history,
1089
1130
  onMessage: (m) => session.append(m),
1090
1131
  onReset: () => session.reset()
1091
1132
  });
1133
+ const mcp = new McpManager({
1134
+ onChange: () => agent.setTools([...baseTools, task, ...mcp.tools()]),
1135
+ // A later server may reuse a sanitized name; it must not inherit this one's "[a]" grants.
1136
+ onToolsRemoved: (names) => {
1137
+ for (const name of names) permissions.revoke(`mcp(${name})`);
1138
+ }
1139
+ });
1092
1140
  return {
1093
1141
  cwd: opts.cwd,
1094
1142
  home,
1095
- config,
1143
+ env,
1144
+ skills,
1145
+ commands,
1146
+ agentDefs,
1147
+ todos,
1148
+ interaction,
1149
+ mcp,
1150
+ startMcp: (specs) => mcp.start(specs),
1151
+ get config() {
1152
+ return config;
1153
+ },
1154
+ reloadConfig() {
1155
+ config = loadConfig(opts.cwd, env);
1156
+ },
1157
+ providerLabel() {
1158
+ return providerName(config, modelRef.slice(0, modelRef.indexOf("/")));
1159
+ },
1160
+ listModels: (only) => listAllModels(config, env, { fetch: opts.fetch, only }),
1161
+ addProviderKey(id, key) {
1162
+ return addProviderKey(id, key, { env, cwd: opts.cwd, out: () => {
1163
+ }, err: () => {
1164
+ }, readSecret: async () => "", fetch: opts.fetch });
1165
+ },
1096
1166
  get modelRef() {
1097
1167
  return modelRef;
1098
1168
  },
@@ -1112,7 +1182,7 @@ function createRuntime(opts) {
1112
1182
  }
1113
1183
 
1114
1184
  // src/version.ts
1115
- var VERSION = true ? "0.2.0-beta.1" : "0.0.0-dev";
1185
+ var VERSION = true ? "0.3.0-beta.1" : "0.0.0-dev";
1116
1186
 
1117
1187
  // src/cli.ts
1118
1188
  async function main() {
@@ -1127,6 +1197,16 @@ async function main() {
1127
1197
  }
1128
1198
  const cwd = process.cwd();
1129
1199
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1200
+ if (args.command === "mcp") {
1201
+ const { runMcpCommand } = await import("./mcpCli-BV3IGR47.js");
1202
+ const { readSecret } = await import("./login-IXJD5O7Z.js");
1203
+ return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1204
+ }
1205
+ if (args.command === "provider") {
1206
+ const { runProviderCommand } = await import("./providerCli-7QNAYIFT.js");
1207
+ const { readSecret } = await import("./login-IXJD5O7Z.js");
1208
+ return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1209
+ }
1130
1210
  if (args.command === "models") {
1131
1211
  const config = loadConfig(cwd);
1132
1212
  const resolved = resolveModel(config, args.model ?? config.model);
@@ -1141,10 +1221,20 @@ async function main() {
1141
1221
  }
1142
1222
  if (args.print) {
1143
1223
  if (!args.prompt) throw new ConfigError('Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"');
1144
- return runPrint(rt, args.prompt);
1224
+ const { serversToStart } = await import("./config-XPWPGNZJ.js");
1225
+ const plan = serversToStart(rt.home, cwd);
1226
+ for (const s of plan.needTrust) {
1227
+ console.error(`Server MCP project "${s.name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).`);
1228
+ }
1229
+ await rt.startMcp(plan.start);
1230
+ try {
1231
+ return await runPrint(rt, args.prompt, void 0, { allowAll: args.allowAll });
1232
+ } finally {
1233
+ await rt.mcp.stop();
1234
+ }
1145
1235
  }
1146
1236
  if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
1147
- const { startInteractive } = await import("./ui-U6OMOINK.js");
1237
+ const { startInteractive } = await import("./ui-6PGC4T6N.js");
1148
1238
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1149
1239
  }
1150
1240
  main().then(