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

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,8 +708,14 @@ 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);
718
+ if (answer === "all") this.permissions.mode = "allowAll";
980
719
  decision = answer === "no" ? "deny" : "allow";
981
720
  }
982
721
  if (decision === "deny") {
@@ -995,13 +734,142 @@ Model ini sepertinya tidak mendukung tool calling. Coba model lain dengan /model
995
734
  }
996
735
  };
997
736
 
737
+ // src/agents.ts
738
+ 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.";
739
+ 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.";
740
+ var BUILTIN_AGENTS = [
741
+ {
742
+ name: "explore",
743
+ description: "Read-only: search and read code to answer a question (fast, safe, can run in parallel).",
744
+ tools: ["read", "grep", "glob"],
745
+ prompt: EXPLORE_PROMPT,
746
+ maxSteps: 30
747
+ },
748
+ {
749
+ name: "general",
750
+ description: "Full tools: carry out a self-contained sub-task (edits and commands still need user permission).",
751
+ prompt: GENERAL_PROMPT,
752
+ maxSteps: 50
753
+ }
754
+ ];
755
+
756
+ // src/mcp/manager.ts
757
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
758
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
759
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
760
+ import { z as z9 } from "zod";
761
+ function mcpToolName(server, tool, taken) {
762
+ const clean = (s) => s.replace(/[^A-Za-z0-9_-]+/g, "_");
763
+ const base = `mcp__${clean(server)}__${clean(tool)}`.slice(0, 64);
764
+ let name = base;
765
+ for (let n = 2; taken.has(name); n++) name = `${base.slice(0, 64 - String(n).length - 1)}_${n}`;
766
+ taken.add(name);
767
+ return name;
768
+ }
769
+ function withTimeout(promise, ms) {
770
+ let timer;
771
+ const timeout = new Promise((_, reject) => {
772
+ timer = setTimeout(() => reject(new Error(`timeout ${Math.round(ms / 1e3)} detik`)), ms);
773
+ });
774
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
775
+ }
776
+ var McpManager = class {
777
+ constructor(opts = {}) {
778
+ this.opts = opts;
779
+ }
780
+ opts;
781
+ servers = /* @__PURE__ */ new Map();
782
+ taken = /* @__PURE__ */ new Set();
783
+ async start(specs) {
784
+ await Promise.all(specs.map((spec) => this.add(spec)));
785
+ }
786
+ async add(spec) {
787
+ await this.remove(spec.name);
788
+ const entry = { spec, state: { name: spec.name, source: spec.source, status: "starting", tools: 0 }, tools: [] };
789
+ this.servers.set(spec.name, entry);
790
+ this.opts.onChange?.();
791
+ const client = new Client({ name: "bccli", version: "0.3.0" });
792
+ entry.client = client;
793
+ const timeoutMs = this.opts.connectTimeoutMs ?? 3e4;
794
+ try {
795
+ const config = spec.config;
796
+ const transport = config.type === "http" ? new StreamableHTTPClientTransport(new URL(config.url), { requestInit: { headers: config.headers ?? {} } }) : new StdioClientTransport({
797
+ command: config.command,
798
+ args: config.args ?? [],
799
+ env: { ...process.env, ...config.env },
800
+ stderr: "ignore"
801
+ });
802
+ await withTimeout(client.connect(transport), timeoutMs);
803
+ const { tools } = await withTimeout(client.listTools(), timeoutMs);
804
+ if (this.servers.get(spec.name) !== entry) {
805
+ await client.close().catch(() => {
806
+ });
807
+ return;
808
+ }
809
+ entry.tools = tools.map((t) => this.wrap(spec.name, client, t));
810
+ entry.state = { ...entry.state, status: "ready", tools: entry.tools.length };
811
+ } catch (error) {
812
+ entry.state = { ...entry.state, status: "error", error: error.message };
813
+ await client.close().catch(() => {
814
+ });
815
+ if (this.servers.get(spec.name) !== entry) return;
816
+ }
817
+ this.opts.onChange?.();
818
+ }
819
+ wrap(server, client, tool) {
820
+ return {
821
+ name: mcpToolName(server, tool.name, this.taken),
822
+ description: `[MCP ${server}] ${tool.description ?? tool.name}`.slice(0, 1024),
823
+ schema: z9.record(z9.string(), z9.unknown()),
824
+ jsonSchema: tool.inputSchema,
825
+ kind: "mcp",
826
+ target: (input) => {
827
+ const text = JSON.stringify(input);
828
+ return text.length > 80 ? `${text.slice(0, 77)}\u2026` : text;
829
+ },
830
+ async run(input, ctx) {
831
+ try {
832
+ const result = await client.callTool({ name: tool.name, arguments: input }, void 0, { signal: ctx.signal });
833
+ const output = (result.content ?? []).map((c) => c.type === "text" ? c.text ?? "" : `[${c.type}]`).join("\n") || "(kosong)";
834
+ return { output: output.length > 5e4 ? `${output.slice(0, 5e4)}
835
+ \u2026 [dipotong]` : output, isError: !!result.isError };
836
+ } catch (error) {
837
+ return { output: `Server MCP ${server} error: ${error.message}`, isError: true };
838
+ }
839
+ }
840
+ };
841
+ }
842
+ async remove(name) {
843
+ const entry = this.servers.get(name);
844
+ if (!entry) return;
845
+ this.servers.delete(name);
846
+ for (const t of entry.tools) this.taken.delete(t.name);
847
+ if (entry.tools.length) this.opts.onToolsRemoved?.(entry.tools.map((t) => t.name));
848
+ await entry.client?.close().catch(() => {
849
+ });
850
+ this.opts.onChange?.();
851
+ }
852
+ tools() {
853
+ return [...this.servers.values()].flatMap((s) => s.tools);
854
+ }
855
+ states() {
856
+ return [...this.servers.values()].map((s) => s.state);
857
+ }
858
+ async stop() {
859
+ await Promise.all([...this.servers.keys()].map((name) => this.remove(name)));
860
+ }
861
+ };
862
+
863
+ // src/setup.ts
864
+ import { homedir } from "os";
865
+
998
866
  // src/context.ts
999
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1000
- import { dirname as dirname2, join as join2 } from "path";
867
+ import { existsSync as existsSync2, readFileSync } from "fs";
868
+ import { dirname as dirname2, join } from "path";
1001
869
  var FILES = ["AGENTS.md", "BCCLI.md"];
1002
870
  function readIf(path) {
1003
871
  try {
1004
- return existsSync3(path) ? readFileSync2(path, "utf8").trim() : void 0;
872
+ return existsSync2(path) ? readFileSync(path, "utf8").trim() : void 0;
1005
873
  } catch {
1006
874
  return void 0;
1007
875
  }
@@ -1013,13 +881,13 @@ function loadInstructions(cwd, home) {
1013
881
  if (dirname2(dir) === dir) break;
1014
882
  }
1015
883
  const parts = [];
1016
- const global = readIf(join2(home, "BCCLI.md"));
1017
- if (global) parts.push(`# ${join2(home, "BCCLI.md")}
884
+ const global = readIf(join(home, "BCCLI.md"));
885
+ if (global) parts.push(`# ${join(home, "BCCLI.md")}
1018
886
  ${global}`);
1019
887
  for (const dir of dirs) {
1020
888
  for (const name of FILES) {
1021
- const text = readIf(join2(dir, name));
1022
- if (text) parts.push(`# ${join2(dir, name)}
889
+ const text = readIf(join(dir, name));
890
+ if (text) parts.push(`# ${join(dir, name)}
1023
891
  ${text}`);
1024
892
  }
1025
893
  }
@@ -1027,17 +895,24 @@ ${text}`);
1027
895
  }
1028
896
  function isGitRepo(cwd) {
1029
897
  for (let dir = cwd; ; dir = dirname2(dir)) {
1030
- if (existsSync3(join2(dir, ".git"))) return true;
898
+ if (existsSync2(join(dir, ".git"))) return true;
1031
899
  if (dirname2(dir) === dir) return false;
1032
900
  }
1033
901
  }
1034
902
  function buildSystemPrompt(opts) {
1035
903
  const instructions = loadInstructions(opts.cwd, opts.home);
904
+ const skills = (opts.skills ?? []).slice(0, 50);
905
+ const skillText = skills.length ? `
906
+
907
+ Skills (load one with the skill tool when it matches the task):
908
+ ${skills.map((s) => `- ${s.name}: ${s.description.slice(0, 200)}`).join("\n")}` : "";
1036
909
  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
910
 
1038
911
  How to work:
1039
912
  - Understand before changing: read the relevant files and search the codebase first. Never guess file contents.
1040
913
  - Always read a file before editing it. Make the smallest correct change; match the existing style.
914
+ - For tasks with 3 or more steps, keep a todo list with todo_write and update it as you go.
915
+ - In plan mode you can only read and search. When the plan is ready, call exit_plan with it and wait for approval.
1041
916
  - Verify your work: run the tests, build or the command that proves the change works, and report the real result.
1042
917
  - The user must approve edits and commands. If they decline, ask what they want instead of retrying.
1043
918
  - Be concise. Reply in the user's language. No preamble; lead with the answer or the action.
@@ -1048,19 +923,168 @@ Environment:
1048
923
  - Git repository: ${isGitRepo(opts.cwd) ? "yes" : "no"}
1049
924
  - Platform: ${opts.platform ?? process.platform}
1050
925
  - Date: ${opts.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
1051
- - Model: ${opts.model}${instructions ? `
926
+ - Model: ${opts.model}${skillText}${instructions ? `
1052
927
 
1053
928
  Project and user instructions (follow them):
1054
929
 
1055
930
  ${instructions}` : ""}`;
1056
931
  }
1057
932
 
933
+ // src/tools/plan.ts
934
+ import { z as z10 } from "zod";
935
+ function createExitPlanTool(opts) {
936
+ return defineTool({
937
+ name: "exit_plan",
938
+ description: "Plan mode only: present the finished plan to the user for approval. If approved you may start implementing it.",
939
+ schema: z10.object({ plan: z10.string().describe("The plan, in markdown") }),
940
+ kind: "read",
941
+ target: () => "rencana",
942
+ async run(input) {
943
+ if (opts.permissions.mode !== "plan") return { output: "exit_plan hanya dipakai di mode plan.", isError: true };
944
+ const decision = await opts.interaction.approvePlan(input.plan);
945
+ if (decision === "no") {
946
+ return { output: "User belum menyetujui. Tanyakan apa yang perlu diperbaiki, perbaiki rencananya, lalu panggil exit_plan lagi." };
947
+ }
948
+ opts.permissions.mode = decision;
949
+ return { output: `Rencana disetujui. Mode sekarang ${decision}. Mulai kerjakan rencananya sekarang.` };
950
+ }
951
+ });
952
+ }
953
+
954
+ // src/tools/skill.ts
955
+ import { readFileSync as readFileSync2 } from "fs";
956
+ import { z as z11 } from "zod";
957
+ function createSkillTool(skills) {
958
+ return defineTool({
959
+ name: "skill",
960
+ description: "Load a skill (instructions for a kind of task) by name. Use it when a listed skill matches the task, then follow it.",
961
+ schema: z11.object({ name: z11.string().describe("Skill name from the list in the system prompt") }),
962
+ kind: "read",
963
+ target: (input) => input.name,
964
+ async run(input) {
965
+ const skill = skills.find((s) => s.name === input.name);
966
+ if (!skill) return { output: `Skill "${input.name}" tidak ada. Tersedia: ${skills.map((s) => s.name).join(", ") || "(tidak ada)"}`, isError: true };
967
+ const { body } = parseFrontmatter(readFileSync2(skill.file, "utf8"));
968
+ return { output: `Base directory for this skill: ${skill.dir}
969
+
970
+ ${body}`, display: skill.description.slice(0, 60) };
971
+ }
972
+ });
973
+ }
974
+
975
+ // src/tools/task.ts
976
+ import { z as z12 } from "zod";
977
+ function createTaskTool(opts) {
978
+ const byName = new Map(opts.agents.map((a) => [a.name, a]));
979
+ const list = opts.agents.map((a) => `- ${a.name}: ${a.description}`).join("\n");
980
+ return defineTool({
981
+ name: "task",
982
+ description: `Delegate a self-contained sub-task to a subagent. It starts with an empty context and returns only its final report. Available agents:
983
+ ${list}`,
984
+ schema: z12.object({
985
+ agent: z12.string().describe("Agent name"),
986
+ description: z12.string().describe("3-6 word summary shown to the user"),
987
+ prompt: z12.string().describe("Full instructions; the subagent sees nothing else")
988
+ }),
989
+ kind: "read",
990
+ target: (input) => `[${input.agent}] ${input.description}`,
991
+ // Parallel only when the resolved agent can do nothing but read (a project file may redefine "explore").
992
+ parallelSafe: (input) => {
993
+ const allowed = byName.get(input.agent)?.tools;
994
+ if (!allowed) return false;
995
+ const base = opts.baseTools();
996
+ return allowed.every((name) => base.find((t) => t.name === name)?.kind === "read");
997
+ },
998
+ async run(input, ctx) {
999
+ const def = byName.get(input.agent);
1000
+ if (!def) return { output: `Agent "${input.agent}" tidak ada. Tersedia: ${[...byName.keys()].join(", ")}`, isError: true };
1001
+ const base = opts.baseTools().filter((t) => t.name !== "task" && t.name !== "exit_plan");
1002
+ const unknown = (def.tools ?? []).filter((n) => !base.some((t) => t.name === n));
1003
+ if (unknown.length) return { output: `Agent "${def.name}": alat tidak dikenal: ${unknown.join(", ")}`, isError: true };
1004
+ const allowed = def.tools;
1005
+ const tools = allowed ? base.filter((t) => allowed.includes(t.name)) : base;
1006
+ let provider;
1007
+ try {
1008
+ provider = def.model ? opts.providerFor(def.model) : opts.provider();
1009
+ } catch (error) {
1010
+ return { output: `Agent "${def.name}": ${error.message}`, isError: true };
1011
+ }
1012
+ const child = new Agent({
1013
+ provider,
1014
+ tools,
1015
+ permissions: opts.permissions,
1016
+ systemPrompt: `${opts.systemPrompt}
1017
+
1018
+ # Your role
1019
+ ${def.prompt}`,
1020
+ cwd: opts.cwd,
1021
+ maxSteps: def.maxSteps ?? 50,
1022
+ label: def.name
1023
+ });
1024
+ let outcome;
1025
+ let steps = 0;
1026
+ child.onEvent = (event) => {
1027
+ if (event.type === "toolStart") steps++;
1028
+ if (event.type === "error" || event.type === "stepLimit" || event.type === "aborted" || event.type === "done") outcome = event;
1029
+ if (event.type !== "usage" && ctx.callId) ctx.emit?.({ type: "subagent", parentId: ctx.callId, agent: def.name, event });
1030
+ };
1031
+ if (ctx.ask) child.askPermission = ctx.ask;
1032
+ await child.run(input.prompt, ctx.signal);
1033
+ ctx.addUsage?.(child.totalUsage);
1034
+ const tokens = child.totalUsage.inputTokens + child.totalUsage.outputTokens;
1035
+ const display = `${steps} langkah \xB7 ${tokens >= 1e3 ? `${Math.round(tokens / 1e3)}k` : tokens} token`;
1036
+ if (outcome?.type === "error") return { output: `Subagent ${def.name} gagal: ${outcome.message}`, isError: true, display };
1037
+ if (outcome?.type === "aborted") return { output: `Subagent ${def.name} dibatalkan.`, isError: true, display };
1038
+ const last = [...child.messages].reverse().find((m) => m.role === "assistant" && m.content);
1039
+ const report = last && typeof last.content === "string" ? last.content : "(tidak ada laporan)";
1040
+ const suffix = outcome?.type === "stepLimit" ? `
1041
+
1042
+ (Subagent berhenti di batas ${def.maxSteps ?? 50} langkah.)` : "";
1043
+ return { output: report + suffix, display };
1044
+ }
1045
+ });
1046
+ }
1047
+
1048
+ // src/tools/todo.ts
1049
+ import { z as z13 } from "zod";
1050
+ var TodoStore = class {
1051
+ items = [];
1052
+ listeners = /* @__PURE__ */ new Set();
1053
+ set(items) {
1054
+ this.items = items;
1055
+ for (const fn of this.listeners) fn(items);
1056
+ }
1057
+ subscribe(fn) {
1058
+ this.listeners.add(fn);
1059
+ return () => this.listeners.delete(fn);
1060
+ }
1061
+ };
1062
+ function createTodoTool(store) {
1063
+ return defineTool({
1064
+ name: "todo_write",
1065
+ 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.",
1066
+ schema: z13.object({
1067
+ todos: z13.array(z13.object({ content: z13.string(), status: z13.enum(["pending", "in_progress", "completed"]) }))
1068
+ }),
1069
+ kind: "read",
1070
+ target: (input) => `${input.todos.length} item`,
1071
+ async run(input) {
1072
+ if (input.todos.filter((t) => t.status === "in_progress").length > 1) {
1073
+ return { output: "Hanya boleh satu item in_progress.", isError: true };
1074
+ }
1075
+ store.set(input.todos);
1076
+ const done = input.todos.filter((t) => t.status === "completed").length;
1077
+ return { output: `Todo diperbarui: ${done}/${input.todos.length} selesai.` };
1078
+ }
1079
+ });
1080
+ }
1081
+
1058
1082
  // src/setup.ts
1059
1083
  var TOOL_RULES = { bash: "bash", edit: "edit", write: "edit", fetch: "fetch" };
1060
1084
  function createRuntime(opts) {
1061
1085
  const env = opts.env ?? process.env;
1062
1086
  const home = bccliHome(env);
1063
- const config = loadConfig(opts.cwd, env);
1087
+ let config = loadConfig(opts.cwd, env);
1064
1088
  let modelRef = opts.args.model ?? config.model;
1065
1089
  const makeProvider = (ref) => {
1066
1090
  const resolved = resolveModel(config, ref, env);
@@ -1070,6 +1094,12 @@ function createRuntime(opts) {
1070
1094
  const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
1071
1095
  const rules = [...config.allow, ...opts.args.allowedTools.map((t) => TOOL_RULES[t] ?? t)];
1072
1096
  const permissions = new Permissions(mode, rules, opts.cwd);
1097
+ const roots = { cwd: opts.cwd, home, userHome: opts.userHome ?? homedir() };
1098
+ const skills = loadSkills(roots);
1099
+ const commands = loadCommands(roots);
1100
+ const agentDefs = loadAgentDefs(roots);
1101
+ const todos = new TodoStore();
1102
+ const interaction = { approvePlan: async () => "no" };
1073
1103
  let session = Session.create(home, opts.cwd);
1074
1104
  let history = opts.history;
1075
1105
  if (!history && opts.args.continue) {
@@ -1079,20 +1109,61 @@ function createRuntime(opts) {
1079
1109
  history = latest.load();
1080
1110
  }
1081
1111
  }
1112
+ const systemPrompt = buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef, skills });
1113
+ const baseTools = [...ALL_TOOLS, createSkillTool(skills), createTodoTool(todos), createExitPlanTool({ permissions, interaction })];
1114
+ const task = createTaskTool({
1115
+ agents: [...BUILTIN_AGENTS, ...agentDefs],
1116
+ // Subagents get whatever the main agent has right now (incl. MCP tools), minus task itself.
1117
+ baseTools: () => agent.tools.filter((t) => t.name !== "task"),
1118
+ permissions,
1119
+ provider: () => agent.provider,
1120
+ providerFor: makeProvider,
1121
+ systemPrompt,
1122
+ cwd: opts.cwd
1123
+ });
1082
1124
  const agent = new Agent({
1083
1125
  provider,
1084
- tools: ALL_TOOLS,
1126
+ tools: [...baseTools, task],
1085
1127
  permissions,
1086
- systemPrompt: buildSystemPrompt({ cwd: opts.cwd, home, model: modelRef }),
1128
+ systemPrompt,
1087
1129
  cwd: opts.cwd,
1088
1130
  history,
1089
1131
  onMessage: (m) => session.append(m),
1090
1132
  onReset: () => session.reset()
1091
1133
  });
1134
+ const mcp = new McpManager({
1135
+ onChange: () => agent.setTools([...baseTools, task, ...mcp.tools()]),
1136
+ // A later server may reuse a sanitized name; it must not inherit this one's "[a]" grants.
1137
+ onToolsRemoved: (names) => {
1138
+ for (const name of names) permissions.revoke(`mcp(${name})`);
1139
+ }
1140
+ });
1092
1141
  return {
1093
1142
  cwd: opts.cwd,
1094
1143
  home,
1095
- config,
1144
+ env,
1145
+ skills,
1146
+ commands,
1147
+ agentDefs,
1148
+ todos,
1149
+ interaction,
1150
+ mcp,
1151
+ startMcp: (specs) => mcp.start(specs),
1152
+ get config() {
1153
+ return config;
1154
+ },
1155
+ reloadConfig() {
1156
+ config = loadConfig(opts.cwd, env);
1157
+ },
1158
+ providerLabel() {
1159
+ return providerName(config, modelRef.slice(0, modelRef.indexOf("/")));
1160
+ },
1161
+ listModels: (only) => listAllModels(config, env, { fetch: opts.fetch, only }),
1162
+ addProviderKey(id, key) {
1163
+ return addProviderKey(id, key, { env, cwd: opts.cwd, out: () => {
1164
+ }, err: () => {
1165
+ }, readSecret: async () => "", fetch: opts.fetch });
1166
+ },
1096
1167
  get modelRef() {
1097
1168
  return modelRef;
1098
1169
  },
@@ -1112,7 +1183,7 @@ function createRuntime(opts) {
1112
1183
  }
1113
1184
 
1114
1185
  // src/version.ts
1115
- var VERSION = true ? "0.2.0-beta.1" : "0.0.0-dev";
1186
+ var VERSION = true ? "0.3.0-beta.2" : "0.0.0-dev";
1116
1187
 
1117
1188
  // src/cli.ts
1118
1189
  async function main() {
@@ -1127,6 +1198,16 @@ async function main() {
1127
1198
  }
1128
1199
  const cwd = process.cwd();
1129
1200
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1201
+ if (args.command === "mcp") {
1202
+ const { runMcpCommand } = await import("./mcpCli-BV3IGR47.js");
1203
+ const { readSecret } = await import("./login-IXJD5O7Z.js");
1204
+ return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1205
+ }
1206
+ if (args.command === "provider") {
1207
+ const { runProviderCommand } = await import("./providerCli-7QNAYIFT.js");
1208
+ const { readSecret } = await import("./login-IXJD5O7Z.js");
1209
+ return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1210
+ }
1130
1211
  if (args.command === "models") {
1131
1212
  const config = loadConfig(cwd);
1132
1213
  const resolved = resolveModel(config, args.model ?? config.model);
@@ -1141,10 +1222,20 @@ async function main() {
1141
1222
  }
1142
1223
  if (args.print) {
1143
1224
  if (!args.prompt) throw new ConfigError('Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"');
1144
- return runPrint(rt, args.prompt);
1225
+ const { serversToStart } = await import("./config-XPWPGNZJ.js");
1226
+ const plan = serversToStart(rt.home, cwd);
1227
+ for (const s of plan.needTrust) {
1228
+ console.error(`Server MCP project "${s.name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).`);
1229
+ }
1230
+ await rt.startMcp(plan.start);
1231
+ try {
1232
+ return await runPrint(rt, args.prompt, void 0, { allowAll: args.allowAll });
1233
+ } finally {
1234
+ await rt.mcp.stop();
1235
+ }
1145
1236
  }
1146
1237
  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");
1238
+ const { startInteractive } = await import("./ui-HNIC4DQ6.js");
1148
1239
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1149
1240
  }
1150
1241
  main().then(