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

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.
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  HELP_TEXT,
4
4
  parseCliArgs
5
- } from "./chunk-M7INIL2C.js";
5
+ } from "./chunk-UN7QXPCB.js";
6
6
  import "./chunk-ELML4KVQ.js";
7
- import "./chunk-Z5JM76EL.js";
7
+ import "./chunk-RZUJYAQV.js";
8
8
  export {
9
9
  HELP_TEXT,
10
10
  parseCliArgs
@@ -4,13 +4,13 @@ import {
4
4
  providerName,
5
5
  removeGlobalProvider,
6
6
  writeGlobalConfig
7
- } from "./chunk-5C6NQFSI.js";
7
+ } from "./chunk-U7DLMVPZ.js";
8
8
  import {
9
9
  PRESETS,
10
10
  loadConfig,
11
11
  readCredentials,
12
12
  saveCredential
13
- } from "./chunk-Z5JM76EL.js";
13
+ } from "./chunk-RZUJYAQV.js";
14
14
 
15
15
  // src/provider.ts
16
16
  var ProviderError = class extends Error {
@@ -35,8 +35,8 @@ function readSecret(prompt) {
35
35
  });
36
36
  }
37
37
  async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-SJRCBSRE.js");
39
- const { parseCliArgs } = await import("./args-EAKPBBGX.js");
38
+ const { runProviderCommand } = await import("./providerCli-G3X7KENO.js");
39
+ const { parseCliArgs } = await import("./args-FCLPQ7TO.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
42
42
  cwd,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigError
4
- } from "./chunk-Z5JM76EL.js";
4
+ } from "./chunk-RZUJYAQV.js";
5
5
 
6
6
  // src/mcp/config.ts
7
7
  import { createHash } from "crypto";
@@ -91,11 +91,12 @@ function resolveModel(config, modelRef, env = process.env) {
91
91
  }
92
92
  function removeCredential(providerId, env = process.env) {
93
93
  const creds = readCredentials(env);
94
- if (!(providerId in creds)) return;
94
+ if (!(providerId in creds)) return false;
95
95
  delete creds[providerId];
96
96
  const path = join(bccliHome(env), "credentials");
97
97
  writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 384 });
98
98
  chmodSync(path, 384);
99
+ return true;
99
100
  }
100
101
 
101
102
  export {
@@ -5,7 +5,7 @@ import {
5
5
  readCredentials,
6
6
  readJsonConfig,
7
7
  removeCredential
8
- } from "./chunk-Z5JM76EL.js";
8
+ } from "./chunk-RZUJYAQV.js";
9
9
 
10
10
  // src/providers.ts
11
11
  import { mkdirSync, writeFileSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigError
4
- } from "./chunk-Z5JM76EL.js";
4
+ } from "./chunk-RZUJYAQV.js";
5
5
 
6
6
  // src/mcp/catalog.ts
7
7
  var CATALOG = [
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-ELML4KVQ.js";
5
5
  import {
6
6
  ConfigError
7
- } from "./chunk-Z5JM76EL.js";
7
+ } from "./chunk-RZUJYAQV.js";
8
8
 
9
9
  // src/args.ts
10
10
  import { parseArgs } from "util";
package/dist/cli.js CHANGED
@@ -2,15 +2,15 @@
2
2
  import {
3
3
  HELP_TEXT,
4
4
  parseCliArgs
5
- } from "./chunk-M7INIL2C.js";
5
+ } from "./chunk-UN7QXPCB.js";
6
6
  import {
7
7
  addProviderKey,
8
8
  createProvider,
9
9
  listAllModels
10
- } from "./chunk-5ISYVAXI.js";
10
+ } from "./chunk-2JBRLGKD.js";
11
11
  import {
12
12
  runLogin
13
- } from "./chunk-QH7XXNHU.js";
13
+ } from "./chunk-L7L4RGKG.js";
14
14
  import {
15
15
  BUILTIN_AGENTS,
16
16
  buildSystemPrompt,
@@ -26,7 +26,7 @@ import {
26
26
  } from "./chunk-ELML4KVQ.js";
27
27
  import {
28
28
  providerName
29
- } from "./chunk-5C6NQFSI.js";
29
+ } from "./chunk-U7DLMVPZ.js";
30
30
  import {
31
31
  Session,
32
32
  pruneSessions
@@ -36,7 +36,7 @@ import {
36
36
  bccliHome,
37
37
  loadConfig,
38
38
  resolveModel
39
- } from "./chunk-Z5JM76EL.js";
39
+ } from "./chunk-RZUJYAQV.js";
40
40
 
41
41
  // src/tools/index.ts
42
42
  import { z as z8 } from "zod";
@@ -521,6 +521,71 @@ function toolDefinitions(tools) {
521
521
  });
522
522
  }
523
523
 
524
+ // src/textToolCalls.ts
525
+ var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
526
+ var CALL_START = /<tool_call>|\n?[^\n]*\bTool call list\b[^\n]*|\n\s*\d+\.\s*Tool:/g;
527
+ function coerce(value, type) {
528
+ const types = Array.isArray(type) ? type : [type];
529
+ const v = value.trim();
530
+ if ((types.includes("integer") || types.includes("number")) && v !== "" && !Number.isNaN(Number(v))) return Number(v);
531
+ if (types.includes("boolean") && /^(true|false)$/i.test(v)) return v.toLowerCase() === "true";
532
+ if ((types.includes("object") || types.includes("array")) && /^[[{]/.test(v)) {
533
+ try {
534
+ return JSON.parse(v);
535
+ } catch {
536
+ }
537
+ }
538
+ return value.replace(/^\n+|\n+$/g, "");
539
+ }
540
+ function pickTool(name, keys, tools) {
541
+ const named = name && tools.find((t) => t.function.name === name);
542
+ if (named) return named;
543
+ let best;
544
+ let bestScore = 0;
545
+ let tie = false;
546
+ for (const t of tools) {
547
+ const params = t.function.parameters;
548
+ const props = Object.keys(params.properties ?? {});
549
+ if (!(params.required ?? []).every((r) => keys.includes(r))) continue;
550
+ const score = keys.filter((k) => props.includes(k)).length;
551
+ if (score > bestScore) [best, bestScore, tie] = [t, score, false];
552
+ else if (score === bestScore && score > 0) tie = true;
553
+ }
554
+ return tie ? void 0 : best;
555
+ }
556
+ function recoverTextToolCalls(text, tools) {
557
+ if (!/<\/arg_value>|<tool_call>/.test(text)) return null;
558
+ const starts = [...text.matchAll(CALL_START)].map((m) => m.index ?? 0);
559
+ const firstPair = text.search(PAIR);
560
+ const cut = Math.min(...[...starts, firstPair].filter((i) => i >= 0));
561
+ const bounds = starts.filter((i) => i >= cut);
562
+ if (!bounds.length || bounds[0] > cut) bounds.unshift(cut);
563
+ const toolCalls = [];
564
+ bounds.forEach((start, n) => {
565
+ const segment = text.slice(start, bounds[n + 1] ?? text.length);
566
+ const name = /<tool_call>\s*([A-Za-z_][\w.-]*)/.exec(segment)?.[1] ?? /Tool:\s*`?([A-Za-z_][\w.-]*)`?/.exec(segment)?.[1];
567
+ let args = [];
568
+ const flush = () => {
569
+ if (!args.length) return;
570
+ const keys = args.map(([k]) => k);
571
+ const tool = pickTool(name, keys, tools);
572
+ if (tool) {
573
+ const props = tool.function.parameters.properties ?? {};
574
+ const input = Object.fromEntries(args.filter(([k]) => k in props).map(([k, v]) => [k, coerce(v, props[k].type)]));
575
+ toolCalls.push({ id: `text_call_${toolCalls.length}`, name: tool.function.name, arguments: JSON.stringify(input) });
576
+ }
577
+ args = [];
578
+ };
579
+ for (const m of segment.matchAll(PAIR)) {
580
+ if (args.some(([k]) => k === m[1])) flush();
581
+ args.push([m[1], m[2]]);
582
+ }
583
+ flush();
584
+ });
585
+ if (!toolCalls.length) return null;
586
+ return { text: text.slice(0, cut).trim(), toolCalls };
587
+ }
588
+
524
589
  // src/agent.ts
525
590
  var estimateTokens = (text) => Math.ceil(text.length / 4);
526
591
  var COMPACT_PROMPT = "Summarize the conversation so far for yourself so you can continue the work with no other context. Include: the user goals, decisions made, files touched and their current state, commands run and results, the task in progress and the next step. Be concise but complete.";
@@ -615,12 +680,19 @@ ${completion.text}` });
615
680
  await this.compact(signal);
616
681
  this.push({ role: "user", content: `Lanjutkan tugas ini sesuai ringkasan di atas: ${text}` });
617
682
  }
618
- const completion = await this.provider.chat({
683
+ let completion = await this.provider.chat({
619
684
  messages: [{ role: "system", content: this.systemPrompt }, ...this.messages],
620
685
  tools: this.turnDefinitions,
621
686
  signal,
622
687
  onText: (delta) => this.onEvent({ type: "text", delta })
623
688
  });
689
+ if (!completion.toolCalls.length && completion.finishReason !== "repetition") {
690
+ const recovered = recoverTextToolCalls(completion.text, this.turnDefinitions);
691
+ if (recovered) {
692
+ completion = { ...completion, ...recovered };
693
+ this.onEvent({ type: "textReplace", text: recovered.text });
694
+ }
695
+ }
624
696
  const usage = completion.usage ?? {
625
697
  inputTokens: estimateTokens(this.systemPrompt) + estimateTokens(JSON.stringify(this.messages)),
626
698
  outputTokens: estimateTokens(completion.text) + (completion.toolCalls.length ? estimateTokens(JSON.stringify(completion.toolCalls)) : 0)
@@ -1173,6 +1245,8 @@ function createRuntime(opts) {
1173
1245
  resume(previous) {
1174
1246
  session = previous;
1175
1247
  agent.load(previous.load());
1248
+ agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
1249
+ todos.set([]);
1176
1250
  checkpoints.clear();
1177
1251
  },
1178
1252
  newSession() {
@@ -1198,7 +1272,7 @@ function createRuntime(opts) {
1198
1272
  }
1199
1273
 
1200
1274
  // src/version.ts
1201
- var VERSION = true ? "0.4.0-beta.1" : "0.0.0-dev";
1275
+ var VERSION = true ? "0.4.0-beta.3" : "0.0.0-dev";
1202
1276
 
1203
1277
  // src/cli.ts
1204
1278
  async function main() {
@@ -1214,13 +1288,13 @@ async function main() {
1214
1288
  const cwd = process.cwd();
1215
1289
  if (args.command === "login") return runLogin(args.loginProvider ?? "bc-cloud", cwd);
1216
1290
  if (args.command === "mcp") {
1217
- const { runMcpCommand } = await import("./mcpCli-BV3IGR47.js");
1218
- const { readSecret } = await import("./login-FTMR64IA.js");
1291
+ const { runMcpCommand } = await import("./mcpCli-Q2H63ISP.js");
1292
+ const { readSecret } = await import("./login-KHU7ONSR.js");
1219
1293
  return runMcpCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1220
1294
  }
1221
1295
  if (args.command === "provider") {
1222
- const { runProviderCommand } = await import("./providerCli-SJRCBSRE.js");
1223
- const { readSecret } = await import("./login-FTMR64IA.js");
1296
+ const { runProviderCommand } = await import("./providerCli-G3X7KENO.js");
1297
+ const { readSecret } = await import("./login-KHU7ONSR.js");
1224
1298
  return runProviderCommand(args, { env: process.env, cwd, out: (s) => console.log(s), err: (s) => console.error(s), readSecret });
1225
1299
  }
1226
1300
  if (args.command === "models") {
@@ -1237,7 +1311,7 @@ async function main() {
1237
1311
  }
1238
1312
  if (args.print) {
1239
1313
  if (!args.prompt) throw new ConfigError('Mode -p butuh tugas, contoh: bccli -p "jelaskan repo ini"');
1240
- const { serversToStart } = await import("./config-XPWPGNZJ.js");
1314
+ const { serversToStart } = await import("./config-NPSYFAFD.js");
1241
1315
  const plan = serversToStart(rt.home, cwd);
1242
1316
  for (const s of plan.needTrust) {
1243
1317
  console.error(`Server MCP project "${s.name}" dilewati (belum diizinkan; jalankan bccli interaktif sekali untuk menyetujuinya).`);
@@ -1250,7 +1324,7 @@ async function main() {
1250
1324
  }
1251
1325
  }
1252
1326
  if (!process.stdin.isTTY) throw new ConfigError('Mode interaktif butuh terminal. Untuk skrip/CI pakai: bccli -p "tugas"');
1253
- const { startInteractive } = await import("./ui-LWZ75D7H.js");
1327
+ const { startInteractive } = await import("./ui-A32PTF4F.js");
1254
1328
  return startInteractive(rt, { initialPrompt: args.prompt, resume: args.resume, version: VERSION });
1255
1329
  }
1256
1330
  main().then(
@@ -8,8 +8,8 @@ import {
8
8
  removeGlobalServer,
9
9
  serversToStart,
10
10
  setProjectTrust
11
- } from "./chunk-XQLXE666.js";
12
- import "./chunk-Z5JM76EL.js";
11
+ } from "./chunk-NGLKANMD.js";
12
+ import "./chunk-RZUJYAQV.js";
13
13
  export {
14
14
  addGlobalServer,
15
15
  globalMcpPath,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  readSecret,
4
4
  runLogin
5
- } from "./chunk-QH7XXNHU.js";
5
+ } from "./chunk-L7L4RGKG.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-UESQNU4A.js";
6
6
  import {
7
7
  addGlobalServer,
8
8
  globalMcpPath,
9
9
  readMcpFile,
10
10
  removeGlobalServer
11
- } from "./chunk-XQLXE666.js";
11
+ } from "./chunk-NGLKANMD.js";
12
12
  import {
13
13
  bccliHome
14
- } from "./chunk-Z5JM76EL.js";
14
+ } from "./chunk-RZUJYAQV.js";
15
15
 
16
16
  // src/mcpCli.ts
17
17
  async function runMcpCommand(args, deps) {
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  addProviderKey,
4
4
  runProviderCommand
5
- } from "./chunk-5ISYVAXI.js";
6
- import "./chunk-5C6NQFSI.js";
7
- import "./chunk-Z5JM76EL.js";
5
+ } from "./chunk-2JBRLGKD.js";
6
+ import "./chunk-U7DLMVPZ.js";
7
+ import "./chunk-RZUJYAQV.js";
8
8
  export {
9
9
  addProviderKey,
10
10
  runProviderCommand
@@ -13,31 +13,31 @@ import {
13
13
  hasKey,
14
14
  providerName,
15
15
  writeGlobalConfig
16
- } from "./chunk-5C6NQFSI.js";
16
+ } from "./chunk-U7DLMVPZ.js";
17
17
  import {
18
18
  Session
19
19
  } from "./chunk-SJH3YLW4.js";
20
20
  import {
21
21
  CATALOG,
22
22
  fillTemplate
23
- } from "./chunk-TV6LEHOH.js";
23
+ } from "./chunk-UESQNU4A.js";
24
24
  import {
25
25
  addGlobalServer,
26
26
  globalMcpPath,
27
27
  readMcpFile,
28
28
  removeGlobalServer
29
- } from "./chunk-XQLXE666.js";
29
+ } from "./chunk-NGLKANMD.js";
30
30
  import {
31
31
  PRESETS,
32
32
  removeCredential
33
- } from "./chunk-Z5JM76EL.js";
33
+ } from "./chunk-RZUJYAQV.js";
34
34
 
35
35
  // src/ui/index.tsx
36
36
  import { render } from "ink";
37
37
 
38
38
  // src/ui/App.tsx
39
- import { spawnSync } from "child_process";
40
- import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
39
+ import { execFile as execFile2 } from "child_process";
40
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
41
41
  import { homedir } from "os";
42
42
  import { dirname, join, relative, resolve } from "path";
43
43
  import { Box as Box13, Static, Text as Text15, useApp, useInput as useInput9 } from "ink";
@@ -90,13 +90,19 @@ function osc52(text, inTmux) {
90
90
 
91
91
  // src/slash/diff.ts
92
92
  import { execFile } from "child_process";
93
- function git(cwd, args) {
93
+ function git(cwd, args, bin = "git") {
94
94
  return new Promise((resolve2) => {
95
- execFile("git", args, { cwd, maxBuffer: 20 * 1024 * 1024 }, (error, stdout, stderr) => resolve2({ ok: !error, out: error ? stderr : stdout }));
95
+ execFile(
96
+ bin,
97
+ args,
98
+ { cwd, maxBuffer: 20 * 1024 * 1024 },
99
+ (error, stdout, stderr) => resolve2({ ok: !error, out: error ? stderr : stdout, missing: error?.code === "ENOENT" })
100
+ );
96
101
  });
97
102
  }
98
- async function gitDiff(cwd, maxLines = 400) {
99
- const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
103
+ async function gitDiff(cwd, maxLines = 400, bin = "git") {
104
+ const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], bin);
105
+ if (inside.missing) return "git tidak terpasang atau tidak ada di PATH, jadi /diff tidak bisa dipakai.";
100
106
  if (!inside.ok || inside.out.trim() !== "true") return "Folder ini bukan repository git, jadi tidak ada diff.";
101
107
  const base = (await git(cwd, ["rev-parse", "--verify", "-q", "HEAD"])).ok ? "HEAD" : "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
102
108
  const [stat, diff, untracked] = await Promise.all([
@@ -117,13 +123,17 @@ async function doctorText(input, deps) {
117
123
  const major = Number(deps.nodeVersion.replace(/^v/, "").split(".")[0]);
118
124
  const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` : `\u2717 Node ${deps.nodeVersion} (butuh \u2265 22)`];
119
125
  for (const p of input.providers) lines.push(p.ready ? `\u2713 ${p.name}` : `\u25CB ${p.name} (belum ada key)`);
120
- try {
121
- lines.push(`\u2713 Koneksi ${input.activeProvider} (${(await deps.listModels()).length} model)`);
122
- } catch (error) {
123
- lines.push(`\u2717 Koneksi ${input.activeProvider}: ${error.message}`);
124
- }
126
+ const cmds = ["rg", "git", "npx", "uvx"];
127
+ const [connection, found] = await Promise.all([
128
+ deps.listModels().then(
129
+ (models) => `\u2713 Koneksi ${input.activeProvider} (${models.length} model)`,
130
+ (error) => `\u2717 Koneksi ${input.activeProvider}: ${error.message}`
131
+ ),
132
+ Promise.all(cmds.map(async (cmd) => deps.which(cmd)))
133
+ ]);
134
+ lines.push(connection);
125
135
  for (const m of input.mcp) lines.push(m.status === "error" ? `\u2717 MCP ${m.name}: ${m.error ?? ""}` : `\u2713 MCP ${m.name} (${m.status})`);
126
- for (const cmd of ["rg", "git", "npx", "uvx"]) lines.push(`${deps.which(cmd) ? "\u2713" : "\u2717"} ${cmd}`);
136
+ for (const [i, cmd] of cmds.entries()) lines.push(`${found[i] ? "\u2713" : "\u2717"} ${cmd}`);
127
137
  return lines.join("\n");
128
138
  }
129
139
 
@@ -186,6 +196,10 @@ function appendMemory(file, text) {
186
196
  appendFileSync(file, `${prefix}- ${text.trim()}
187
197
  `);
188
198
  }
199
+ function parseMemoryArgs(args) {
200
+ const match = /^global(?:\s+|$)/i.exec(args.trim());
201
+ return match ? { global: true, text: args.trim().slice(match[0].length).trim() } : { global: false, text: args.trim() };
202
+ }
189
203
 
190
204
  // src/ui/Markdown.tsx
191
205
  import { Box, Text } from "ink";
@@ -833,6 +847,7 @@ function App({ runtime, initialPrompt, version }) {
833
847
  const chosen = others.find((s) => s.session.file === id);
834
848
  if (!chosen) return;
835
849
  runtime.resume(chosen.session);
850
+ setTokens(0);
836
851
  const tail = runtime.agent.messages.filter((m) => (m.role === "user" || m.role === "assistant") && m.content).slice(-3);
837
852
  notice(
838
853
  [`Melanjutkan sesi (${runtime.agent.messages.length} pesan):`, ...tail.map((m) => `${m.role === "user" ? ">" : "\u25CF"} ${String(m.content).slice(0, 200)}`)].join(
@@ -874,7 +889,7 @@ function App({ runtime, initialPrompt, version }) {
874
889
  activeProvider: runtime.providerLabel()
875
890
  },
876
891
  {
877
- which: (cmd) => spawnSync(process.platform === "win32" ? "where" : "which", [cmd]).status === 0,
892
+ which: (cmd) => new Promise((done) => execFile2(process.platform === "win32" ? "where" : "which", [cmd], (error) => done(!error))),
878
893
  nodeVersion: process.version,
879
894
  listModels: () => runtime.agent.provider.listModels()
880
895
  }
@@ -910,8 +925,11 @@ function App({ runtime, initialPrompt, version }) {
910
925
  notice(files.length ? files.map((f) => `${f.path} (${f.lines} baris)`).join("\n") : "Belum ada AGENTS.md / BCCLI.md. Tambah dengan /memory <teks>.");
911
926
  return;
912
927
  }
913
- const global = /^global\s/i.test(args);
914
- const text = global ? args.replace(/^global\s+/i, "") : args;
928
+ const { global, text } = parseMemoryArgs(args);
929
+ if (!text) {
930
+ notice("Tulis teksnya: /memory global <teks>.", "warn");
931
+ return;
932
+ }
915
933
  const file = global ? join(runtime.home, "BCCLI.md") : join(runtime.cwd, "AGENTS.md");
916
934
  try {
917
935
  mkdirSync(dirname(file), { recursive: true });
@@ -937,15 +955,27 @@ function App({ runtime, initialPrompt, version }) {
937
955
  return;
938
956
  }
939
957
  runtime.setModel(runtime.modelRef);
958
+ const envName = runtime.config.providers[id]?.apiKeyEnv;
959
+ if (envName && runtime.env[envName]) {
960
+ notice(`Key ${runtime.providerLabel()} tersimpan, tapi ${envName} di environment lebih diutamakan dan tetap dipakai.`, "warn");
961
+ return;
962
+ }
940
963
  notice(`Key ${runtime.providerLabel()} tersimpan \xB7 ${result.models} model.`);
941
964
  return;
942
965
  }
943
966
  case "logout": {
944
967
  const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
945
- removeCredential(id, runtime.env);
968
+ const removed = removeCredential(id, runtime.env);
946
969
  const envName = runtime.config.providers[id]?.apiKeyEnv;
947
970
  if (envName && runtime.env[envName]) {
948
- notice(`Key tersimpan dihapus, tapi ${envName} masih ada di environment dan tetap dipakai.`, "warn");
971
+ notice(
972
+ `${removed ? "Key tersimpan dihapus, tapi" : "Tidak ada key tersimpan;"} ${envName} masih ada di environment dan tetap dipakai.`,
973
+ "warn"
974
+ );
975
+ return;
976
+ }
977
+ if (!removed) {
978
+ notice(`Tidak ada key tersimpan untuk ${runtime.providerLabel()}.`);
949
979
  return;
950
980
  }
951
981
  try {
@@ -996,6 +1026,13 @@ function App({ runtime, initialPrompt, version }) {
996
1026
  case "export": {
997
1027
  const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
998
1028
  const file = resolve(runtime.cwd, name2);
1029
+ if (existsSync2(file)) {
1030
+ const answer2 = await ask(`${file} sudah ada. Timpa? (y/N)`);
1031
+ if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
1032
+ notice("Ekspor dibatalkan.");
1033
+ return;
1034
+ }
1035
+ }
999
1036
  try {
1000
1037
  writeFileSync(file, exportMarkdown(runtime.agent.messages, `Sesi BCCLI ${runtime.startedAt.toLocaleString("id-ID")}`));
1001
1038
  } catch (error) {
@@ -1219,7 +1256,7 @@ async function startInteractive(rt, opts) {
1219
1256
  if (chosen) rt.resume(chosen.session);
1220
1257
  }
1221
1258
  }
1222
- const { serversToStart, setProjectTrust } = await import("./config-XPWPGNZJ.js");
1259
+ const { serversToStart, setProjectTrust } = await import("./config-NPSYFAFD.js");
1223
1260
  const plan = serversToStart(rt.home, rt.cwd);
1224
1261
  if (plan.needTrust.length) {
1225
1262
  const { createInterface } = await import("readline/promises");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.0-beta.1",
3
+ "version": "0.4.0-beta.3",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {