@botconnector/bccli 0.4.9 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/{acp-VEXFXT5P.js → acp-IPHWC6CO.js} +20 -5
  2. package/dist/acp.d.ts +17 -1
  3. package/dist/args-K5AJFDCZ.js +13 -0
  4. package/dist/args.d.ts +6 -0
  5. package/dist/budget.d.ts +4 -1
  6. package/dist/{chunk-2YG6L3YO.js → chunk-2DFY43RP.js} +2 -2
  7. package/dist/{chunk-KREGVQ63.js → chunk-6BXW7DGQ.js} +47 -18
  8. package/dist/{chunk-WSSJH765.js → chunk-6KZIOICR.js} +1 -1
  9. package/dist/{chunk-OUJRD7GG.js → chunk-6M5T7N7A.js} +8 -3
  10. package/dist/{chunk-DRJSANKH.js → chunk-C2FUGQJX.js} +9 -7
  11. package/dist/{chunk-OETLWNGS.js → chunk-F54FDJBD.js} +3 -3
  12. package/dist/{chunk-XCVYR5BK.js → chunk-G5TG43R4.js} +15 -1
  13. package/dist/{chunk-RRECJTOG.js → chunk-IHQGXDSG.js} +1 -1
  14. package/dist/{chunk-AWBEZY4D.js → chunk-JFYQCPXL.js} +7 -5
  15. package/dist/{chunk-6H6MPG7L.js → chunk-MBAMDCCV.js} +23 -9
  16. package/dist/{chunk-HCW67S5M.js → chunk-N6APAYSF.js} +1 -1
  17. package/dist/{chunk-TLCFBQ6E.js → chunk-NT2G5FSX.js} +2 -2
  18. package/dist/{chunk-CSIQDFEF.js → chunk-OAXD3X4R.js} +34 -50
  19. package/dist/{chunk-EOJNFGOP.js → chunk-OOOS5RCT.js} +98 -28
  20. package/dist/{chunk-Z2ELYJ5O.js → chunk-WSZF2CTU.js} +11 -4
  21. package/dist/{chunk-62UTSCWF.js → chunk-X24APPXO.js} +1 -1
  22. package/dist/{chunk-EAR2C6TS.js → chunk-XZ2TI5HV.js} +1 -1
  23. package/dist/{chunk-HPYI7L4W.js → chunk-Y6AZGGH7.js} +4 -4
  24. package/dist/{chunk-WFZKJY3R.js → chunk-YYTFJUO2.js} +3 -2
  25. package/dist/cli.js +40 -40
  26. package/dist/{completion-FETH4OGC.js → completion-QVDGAYUY.js} +1 -1
  27. package/dist/{config-KR5JI5RA.js → config-7TQ2KVVX.js} +3 -3
  28. package/dist/{config-RVMC23W3.js → config-RZY3XQZJ.js} +2 -2
  29. package/dist/{ghBotCli-Z6GWXCJ6.js → ghBotCli-HHJ4ANTH.js} +18 -16
  30. package/dist/{integrationCli-GPRR6J6W.js → integrationCli-LNNZDGZX.js} +40 -5
  31. package/dist/{login-DFSN77L3.js → login-MQEZGF2C.js} +6 -4
  32. package/dist/login.d.ts +2 -0
  33. package/dist/{mcpCli-64P3SVUE.js → mcpCli-CUIOYJ2A.js} +22 -7
  34. package/dist/{modelsCli-PFQY3G2Q.js → modelsCli-AAFHFI4H.js} +6 -6
  35. package/dist/{oauthLogin-NR22NWEV.js → oauthLogin-YFL2N4QA.js} +1 -1
  36. package/dist/{pluginsCli-UKRJ3AHN.js → pluginsCli-2QXXFLQA.js} +6 -2
  37. package/dist/{prCli-6FCI36AJ.js → prCli-KSA73RCS.js} +1 -1
  38. package/dist/print.d.ts +1 -0
  39. package/dist/providerCli-TXMVPSW5.js +15 -0
  40. package/dist/sdk.js +196 -87
  41. package/dist/{serve-PKUTKQQT.js → serve-EIQHEUYD.js} +3 -3
  42. package/dist/{serve-JYXPQAVY.js → serve-QYDBFTHC.js} +16 -16
  43. package/dist/session-CQCQ7KAP.js +12 -0
  44. package/dist/setup-DBSGMWHM.js +24 -0
  45. package/dist/setup.d.ts +5 -0
  46. package/dist/{statsCli-FR7GOKWO.js → statsCli-EHNJIQ2Z.js} +2 -2
  47. package/dist/tools/bash.d.ts +2 -0
  48. package/dist/{transferCli-GIE4MEJ4.js → transferCli-IXJ2O74Q.js} +4 -4
  49. package/dist/{ui-NZVOCQSU.js → ui-A2LASWDK.js} +14 -14
  50. package/dist/{update-DIYDD7LT.js → update-TR62JV3A.js} +2 -2
  51. package/package.json +1 -1
  52. package/dist/args-6XMUZDAK.js +0 -13
  53. package/dist/providerCli-PBSZECBM.js +0 -15
  54. package/dist/session-XABMBIIF.js +0 -12
  55. package/dist/setup-CLPQLTM7.js +0 -24
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ t
4
+ } from "./chunk-G5TG43R4.js";
2
5
 
3
6
  // src/acp.ts
4
7
  var ACP_PROTOCOL_VERSION = 1;
@@ -175,16 +178,27 @@ function runAcp(input, output, opts) {
175
178
  input.on("end", onEnd);
176
179
  });
177
180
  }
178
- function runtimeAgent(cwd, allowAll = false) {
181
+ function planDecisionFrom(answer) {
182
+ if (answer === "no") return "no";
183
+ if (answer === "session") return "acceptEdits";
184
+ return "default";
185
+ }
186
+ function runtimeAgent(cwd, allowAll = false, deps = {}) {
179
187
  let rt;
180
188
  return {
181
189
  async run(text, onChunk, signal, io) {
182
190
  if (!rt) {
183
- const { parseCliArgs } = await import("./args-6XMUZDAK.js");
184
- const { createRuntime } = await import("./setup-CLPQLTM7.js");
185
- rt = createRuntime({ cwd, args: { ...parseCliArgs([]), allowAll } });
191
+ if (deps.createRuntime) rt = deps.createRuntime({ cwd, allowAll });
192
+ else {
193
+ const { parseCliArgs } = await import("./args-K5AJFDCZ.js");
194
+ const { createRuntime } = await import("./setup-DBSGMWHM.js");
195
+ rt = createRuntime({ cwd, args: { ...parseCliArgs([]), allowAll } });
196
+ }
197
+ }
198
+ if (io && !allowAll) {
199
+ rt.agent.askPermission = (req) => io.askPermission(req);
200
+ rt.interaction.approvePlan = async (plan) => planDecisionFrom(await io.askPermission({ tool: "exit_plan", kind: "edit", target: t("plan"), preview: plan }));
186
201
  }
187
- if (io && !allowAll) rt.agent.askPermission = (req) => io.askPermission(req);
188
202
  let turnError;
189
203
  rt.agent.onEvent = (event) => {
190
204
  if (event.type === "text") onChunk(event.delta);
@@ -201,6 +215,7 @@ function runtimeAgent(cwd, allowAll = false) {
201
215
  }
202
216
  export {
203
217
  ACP_PROTOCOL_VERSION,
218
+ planDecisionFrom,
204
219
  runAcp,
205
220
  runtimeAgent
206
221
  };
package/dist/acp.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { PermissionAnswer, PermissionAsk } from './agent';
2
+ import type { Runtime } from './setup';
3
+ import type { PlanDecision } from './tools/plan';
2
4
  export declare const ACP_PROTOCOL_VERSION = 1;
3
5
  /** What the agent reports to the editor besides text: tool calls, and permission questions the editor answers. */
4
6
  export interface AcpIo {
@@ -21,8 +23,22 @@ export declare function runAcp(input: NodeJS.ReadableStream, output: NodeJS.Writ
21
23
  cwd: string;
22
24
  newAgent: (cwd: string) => AcpAgent;
23
25
  }): Promise<void>;
26
+ /**
27
+ * A plan has no ACP primitive of its own, so it is asked through session/request_permission.
28
+ * 'yes' approves and keeps the normal per-tool prompts; 'session' also stops asking for edits
29
+ * for the rest of the session (acceptEdits). A plan answer never turns on allowAll, which stays
30
+ * an explicit user choice.
31
+ */
32
+ export declare function planDecisionFrom(answer: PermissionAnswer): PlanDecision;
33
+ export interface AcpRuntimeDeps {
34
+ /** Test seam: build the runtime without reading config/plugins. */
35
+ createRuntime?: (opts: {
36
+ cwd: string;
37
+ allowAll: boolean;
38
+ }) => Runtime;
39
+ }
24
40
  /**
25
41
  * Production agent: one bccli runtime per ACP session. Permission questions go to the editor
26
42
  * (session/request_permission); `allowAll` skips them and runs everything unasked.
27
43
  */
28
- export declare function runtimeAgent(cwd: string, allowAll?: boolean): AcpAgent;
44
+ export declare function runtimeAgent(cwd: string, allowAll?: boolean, deps?: AcpRuntimeDeps): AcpAgent;
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ helpText,
4
+ parseCliArgs
5
+ } from "./chunk-WSZF2CTU.js";
6
+ import "./chunk-YYTFJUO2.js";
7
+ import "./chunk-XZ2TI5HV.js";
8
+ import "./chunk-C2FUGQJX.js";
9
+ import "./chunk-G5TG43R4.js";
10
+ export {
11
+ helpText,
12
+ parseCliArgs
13
+ };
package/dist/args.d.ts CHANGED
@@ -5,6 +5,10 @@ export interface CliArgs {
5
5
  command: 'run' | 'login' | 'models' | 'update' | 'acp' | 'serve' | 'plugins' | 'gh-bot' | 'provider' | 'mcp' | 'connect' | 'disconnect' | 'integrations' | 'completion' | 'stats' | 'export' | 'import' | 'pr';
6
6
  subArgs: string[];
7
7
  url?: string;
8
+ /** `bccli mcp add <name> --command <cmd>`: server stdio, bukan HTTP. */
9
+ stdioCommand?: string;
10
+ /** Argumen untuk server stdio (dipisah spasi). */
11
+ stdioArgs?: string;
8
12
  name?: string;
9
13
  keyEnv?: string;
10
14
  values: string[];
@@ -16,6 +20,8 @@ export interface CliArgs {
16
20
  continue: boolean;
17
21
  resume: boolean;
18
22
  allowAll: boolean;
23
+ /** `-p` untuk CI: kegagalan tool apa pun membuat exit code 1. */
24
+ failOnToolError: boolean;
19
25
  allowedTools: string[];
20
26
  worktree?: string;
21
27
  outputFormat?: 'text' | 'json' | 'stream-json';
package/dist/budget.d.ts CHANGED
@@ -3,19 +3,22 @@ export interface UsageCap {
3
3
  tokens?: number;
4
4
  /** Total USD per session; requires `prices`. */
5
5
  usd?: number;
6
- /** USD per 1M tokens, keyed by a substring of the model ref (first match wins). */
6
+ /** USD per 1M tokens. `cachedInput` is optional: without it cache reads cost the input price. */
7
7
  prices?: Record<string, {
8
8
  input: number;
9
9
  output: number;
10
+ cachedInput?: number;
10
11
  }>;
11
12
  }
12
13
  export declare function priceFor(cap: UsageCap, model: string): {
13
14
  input: number;
14
15
  output: number;
16
+ cachedInput?: number;
15
17
  } | undefined;
16
18
  export declare function budgetStatus(usage: {
17
19
  inputTokens: number;
18
20
  outputTokens: number;
21
+ cachedInputTokens?: number;
19
22
  }, cap: UsageCap | undefined, model: string): {
20
23
  kind: 'tokens' | 'usd';
21
24
  used: number;
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  ConfigError,
4
4
  writeFileAtomic
5
- } from "./chunk-DRJSANKH.js";
5
+ } from "./chunk-C2FUGQJX.js";
6
6
  import {
7
7
  t
8
- } from "./chunk-XCVYR5BK.js";
8
+ } from "./chunk-G5TG43R4.js";
9
9
 
10
10
  // src/mcp/config.ts
11
11
  import { createHash } from "crypto";
@@ -24,52 +24,52 @@ import {
24
24
  resolvePath,
25
25
  runHooks,
26
26
  validateGlobPattern
27
- } from "./chunk-CSIQDFEF.js";
27
+ } from "./chunk-OAXD3X4R.js";
28
28
  import {
29
29
  createWebSearchTool,
30
30
  fetchTool
31
- } from "./chunk-WSSJH765.js";
31
+ } from "./chunk-6KZIOICR.js";
32
32
  import {
33
33
  defineTool
34
34
  } from "./chunk-PSO7R5AD.js";
35
35
  import {
36
36
  Permissions
37
- } from "./chunk-WFZKJY3R.js";
37
+ } from "./chunk-YYTFJUO2.js";
38
38
  import {
39
39
  addProviderKey
40
- } from "./chunk-AWBEZY4D.js";
40
+ } from "./chunk-JFYQCPXL.js";
41
41
  import {
42
42
  listAllModels
43
- } from "./chunk-HPYI7L4W.js";
43
+ } from "./chunk-Y6AZGGH7.js";
44
44
  import {
45
45
  providerName
46
- } from "./chunk-62UTSCWF.js";
46
+ } from "./chunk-X24APPXO.js";
47
47
  import {
48
48
  FileOAuthProvider,
49
49
  hasStoredTokens
50
- } from "./chunk-RRECJTOG.js";
50
+ } from "./chunk-IHQGXDSG.js";
51
51
  import {
52
52
  Session
53
- } from "./chunk-OETLWNGS.js";
53
+ } from "./chunk-F54FDJBD.js";
54
54
  import {
55
55
  completionAssistantMessage,
56
56
  createProvider,
57
57
  hasPendingProgrammaticReplay,
58
58
  imageUserMessage,
59
59
  withFallbackProvider
60
- } from "./chunk-EOJNFGOP.js";
60
+ } from "./chunk-OOOS5RCT.js";
61
61
  import {
62
62
  expandEnvVars
63
- } from "./chunk-2YG6L3YO.js";
63
+ } from "./chunk-2DFY43RP.js";
64
64
  import {
65
65
  ConfigError,
66
66
  bccliHome,
67
67
  loadConfig,
68
68
  resolveModel
69
- } from "./chunk-DRJSANKH.js";
69
+ } from "./chunk-C2FUGQJX.js";
70
70
  import {
71
71
  t
72
- } from "./chunk-XCVYR5BK.js";
72
+ } from "./chunk-G5TG43R4.js";
73
73
 
74
74
  // src/textToolCalls.ts
75
75
  var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
@@ -622,6 +622,12 @@ async function readTextResult(input, buffer) {
622
622
  const lines = buffer.toString("utf8").split(/\r?\n/);
623
623
  const start = (input.offset ?? 1) - 1;
624
624
  const limit = input.limit ?? 2e3;
625
+ if (start >= lines.length) {
626
+ return {
627
+ output: `offset ${input.offset} is past the end of ${input.path} (${lines.length} line(s)); start at 1.`,
628
+ isError: true
629
+ };
630
+ }
625
631
  const requested = lines.slice(start, start + limit);
626
632
  const shown = [];
627
633
  let size = 0;
@@ -703,7 +709,9 @@ var writeTool = defineTool({
703
709
  target: (input) => input.path,
704
710
  async validate(input, ctx) {
705
711
  const abs = resolvePath(ctx.cwd, input.path);
706
- return existsSync(abs) && !ctx.readFiles.has(abs) ? `${input.path} already exists. Read it with read before overwriting it.` : void 0;
712
+ if (existsSync(abs) && !ctx.readFiles.has(abs)) return `${input.path} already exists. Read it with read before overwriting it.`;
713
+ if (isFileModifiedSinceRead(ctx.readFiles, abs)) return `${input.path} has been modified since it was last read. Read it again before overwriting it.`;
714
+ return void 0;
707
715
  },
708
716
  async preview(input, ctx) {
709
717
  const abs = resolvePath(ctx.cwd, input.path);
@@ -715,6 +723,9 @@ var writeTool = defineTool({
715
723
  if (existsSync(abs) && !ctx.readFiles.has(abs)) {
716
724
  return { output: `${input.path} already exists. Read it with read before overwriting it.`, isError: true };
717
725
  }
726
+ if (isFileModifiedSinceRead(ctx.readFiles, abs)) {
727
+ return { output: `${input.path} has been modified since it was last read. Read it again before overwriting it.`, isError: true };
728
+ }
718
729
  try {
719
730
  await ctx.checkpoint?.(abs);
720
731
  await mkdir(dirname2(abs), { recursive: true });
@@ -824,7 +835,9 @@ function budgetStatus(usage, cap, model) {
824
835
  if (cap.usd !== void 0) {
825
836
  const price = priceFor(cap, model);
826
837
  if (price) {
827
- const usd = (usage.inputTokens * price.input + usage.outputTokens * price.output) / 1e6;
838
+ const cached = Math.max(0, Math.min(usage.cachedInputTokens ?? 0, usage.inputTokens));
839
+ const cachedRate = price.cachedInput ?? price.input;
840
+ const usd = ((usage.inputTokens - cached) * price.input + cached * cachedRate + usage.outputTokens * price.output) / 1e6;
828
841
  if (usd >= cap.usd) return { kind: "usd", used: usd, limit: cap.usd };
829
842
  }
830
843
  }
@@ -2366,6 +2379,15 @@ function loadPlugins(specs, opts) {
2366
2379
 
2367
2380
  // src/setup.ts
2368
2381
  var TOOL_RULES = { bash: "bash", edit: "edit", write: "edit", fetch: "fetch" };
2382
+ var pluginCache = /* @__PURE__ */ new Map();
2383
+ function loadPluginsOnce(specs, opts) {
2384
+ const key = JSON.stringify({ specs, home: opts.home, cwd: opts.cwd });
2385
+ const cached = pluginCache.get(key);
2386
+ if (cached) return cached;
2387
+ const loaded = loadPlugins(specs, opts);
2388
+ pluginCache.set(key, loaded);
2389
+ return loaded;
2390
+ }
2369
2391
  function lunaPtcAutoEnabled(env) {
2370
2392
  const value = String(env.BCCLI_LUNA_PTC ?? "").trim().toLowerCase();
2371
2393
  return !["0", "false", "off", "no"].includes(value);
@@ -2373,6 +2395,7 @@ function lunaPtcAutoEnabled(env) {
2373
2395
  function createRuntime(opts) {
2374
2396
  const env = opts.env ?? process.env;
2375
2397
  const home = bccliHome(env);
2398
+ const persistSession = opts.persistSession !== false;
2376
2399
  let config = loadConfig(opts.cwd, env);
2377
2400
  let modelRef = opts.args.model ?? config.model;
2378
2401
  let reasoning = opts.args.reasoning ?? config.reasoning;
@@ -2461,7 +2484,7 @@ function createRuntime(opts) {
2461
2484
  createRepoMapTool()
2462
2485
  ];
2463
2486
  if (config.lsp?.servers.length) baseTools.push(createDiagnosticsTool(config.lsp.servers));
2464
- const plugins = loadPlugins(config.plugins ?? [], {
2487
+ const plugins = loadPluginsOnce(config.plugins ?? [], {
2465
2488
  home,
2466
2489
  cwd: opts.cwd,
2467
2490
  env,
@@ -2499,9 +2522,15 @@ function createRuntime(opts) {
2499
2522
  usageCap: config.usageCap,
2500
2523
  budgetModel: modelRef,
2501
2524
  contextWindow: contextWindowFor(modelRef, env),
2502
- onMessage: (m) => session.append(m),
2503
- onUsage: (u) => session.appendUsage({ ...u, model: modelRef }),
2504
- onReset: () => session.reset(),
2525
+ onMessage: (m) => {
2526
+ if (persistSession) session.append(m);
2527
+ },
2528
+ onUsage: (u) => {
2529
+ if (persistSession) session.appendUsage({ ...u, model: modelRef });
2530
+ },
2531
+ onReset: () => {
2532
+ if (persistSession) session.reset();
2533
+ },
2505
2534
  onTurnStart: () => {
2506
2535
  for (let i = turnMarks.length - 1; i >= 0 && turnMarks[i] > agent.messages.length; i--) {
2507
2536
  turnMarks.splice(i, 1);
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-PSO7R5AD.js";
5
5
  import {
6
6
  t
7
- } from "./chunk-XCVYR5BK.js";
7
+ } from "./chunk-G5TG43R4.js";
8
8
 
9
9
  // src/tools/websearch.ts
10
10
  import { z } from "zod";
@@ -2,13 +2,13 @@
2
2
  import {
3
3
  runCommand,
4
4
  runHooks
5
- } from "./chunk-CSIQDFEF.js";
5
+ } from "./chunk-OAXD3X4R.js";
6
6
  import {
7
7
  ConfigError
8
- } from "./chunk-DRJSANKH.js";
8
+ } from "./chunk-C2FUGQJX.js";
9
9
  import {
10
10
  t
11
- } from "./chunk-XCVYR5BK.js";
11
+ } from "./chunk-G5TG43R4.js";
12
12
 
13
13
  // src/verify.ts
14
14
  var MAX_VERIFY_ROUNDS = 2;
@@ -90,6 +90,7 @@ async function runPrint(rt, prompt, io = { out: process.stdout, err: process.std
90
90
  case "toolEnd":
91
91
  if (event.isError) io.err.write(` \u2717 ${event.output.split("\n")[0]}
92
92
  `);
93
+ if (event.isError && opts.failOnToolError) failed = true;
93
94
  if ((event.tool === "edit" || event.tool === "write") && !event.isError) edited = true;
94
95
  if (format === "stream-json") emit({ type: "tool_result", tool: toolNames.get(event.id), isError: !!event.isError, output: event.output });
95
96
  break;
@@ -125,6 +126,9 @@ async function runPrint(rt, prompt, io = { out: process.stdout, err: process.std
125
126
  const controller = new AbortController();
126
127
  const onSigint = () => controller.abort();
127
128
  process.once("SIGINT", onSigint);
129
+ const onSigbreak = () => controller.abort();
130
+ const useSigbreak = process.platform === "win32";
131
+ if (useSigbreak) process.once("SIGBREAK", onSigbreak);
128
132
  const start = await runHooks(rt.config.hooks, "SessionStart", {}, { env: rt.env, cwd: rt.cwd });
129
133
  for (const warning of start.warnings) io.err.write(`${warning}
130
134
  `);
@@ -153,6 +157,7 @@ async function runPrint(rt, prompt, io = { out: process.stdout, err: process.std
153
157
  }
154
158
  } finally {
155
159
  process.off("SIGINT", onSigint);
160
+ if (useSigbreak) process.off("SIGBREAK", onSigbreak);
156
161
  }
157
162
  const stop = await runHooks(rt.config.hooks, "Stop", {}, { env: rt.env, cwd: rt.cwd });
158
163
  for (const warning of stop.warnings) io.err.write(`${warning}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  t
4
- } from "./chunk-XCVYR5BK.js";
4
+ } from "./chunk-G5TG43R4.js";
5
5
 
6
6
  // src/config.ts
7
7
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
@@ -149,6 +149,9 @@ function resolvedUsageCap(global) {
149
149
  if (!price || !nonNegative(price.input) || !nonNegative(price.output)) {
150
150
  throw new ConfigError(t('usageCap.prices["{key}"] must have numeric input and output (USD per 1M tokens).', { key }));
151
151
  }
152
+ if (price.cachedInput !== void 0 && !nonNegative(price.cachedInput)) {
153
+ throw new ConfigError(t('usageCap.prices["{key}"].cachedInput must be a number >= 0 (USD per 1M tokens).', { key }));
154
+ }
152
155
  }
153
156
  }
154
157
  if (tokens === void 0 && usd === void 0) throw new ConfigError(t("usageCap needs at least one of: tokens, usd."));
@@ -236,7 +239,7 @@ function loadConfig(cwd, env = process.env) {
236
239
  const project = readJsonConfig(join(cwd, ".bccli", "config.json"));
237
240
  const known = { ...DEFAULT_CONFIG.providers, ...global.providers };
238
241
  const fromProject = untrustedProviders(project, known);
239
- const rawFallbacks = project.fallbackModels ?? global.fallbackModels;
242
+ const rawFallbacks = global.fallbackModels;
240
243
  const fallbackModels = Array.isArray(rawFallbacks) ? rawFallbacks.filter((m) => typeof m === "string" && m.includes("/")) : void 0;
241
244
  return {
242
245
  model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
@@ -305,10 +308,7 @@ function quarantineCorruptFile(path, error) {
305
308
  }
306
309
  function updateGlobalConfig(env, mutate) {
307
310
  const path = globalConfigPath(env);
308
- let cfg = {};
309
- if (existsSync(path)) {
310
- cfg = JSON.parse(readFileSync(path, "utf8"));
311
- }
311
+ const cfg = existsSync(path) ? readJsonConfig(path) : {};
312
312
  mutate(cfg);
313
313
  mkdirSync(dirname(path), { recursive: true });
314
314
  writeFileAtomic(path, `${JSON.stringify(cfg, null, 2)}
@@ -339,7 +339,9 @@ function resolveModel(config, modelRef, env = process.env) {
339
339
  if (!provider) {
340
340
  throw new ConfigError(t('Provider "{id}" is not in the config. Available: {list}', { id: providerId, list: Object.keys(config.providers).join(", ") }));
341
341
  }
342
- const apiKey = readCredentials(env)[providerId] || (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || void 0;
342
+ const untrusted = config.projectProviders?.includes(providerId) ?? false;
343
+ const stored = untrusted ? void 0 : readCredentials(env)[providerId];
344
+ const apiKey = stored || (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || void 0;
343
345
  if (provider.apiKeyEnv && !apiKey) {
344
346
  throw new ConfigError(t("No API key for {id} yet. Run `bccli login` or set the env var {env}.", { id: providerId, env: provider.apiKeyEnv }));
345
347
  }
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  contentText
4
- } from "./chunk-EOJNFGOP.js";
4
+ } from "./chunk-OOOS5RCT.js";
5
5
 
6
6
  // src/session.ts
7
7
  import { randomBytes } from "crypto";
8
8
  import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "fs";
9
- import { basename, join } from "path";
9
+ import { basename, dirname, join } from "path";
10
10
  function projectDir(home, cwd) {
11
11
  const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(-100) || "root";
12
12
  return join(home, "sessions", slug);
@@ -32,7 +32,6 @@ var Session = class _Session {
32
32
  }
33
33
  static create(home, cwd, now = /* @__PURE__ */ new Date()) {
34
34
  const dir = projectDir(home, cwd);
35
- mkdirSync(dir, { recursive: true });
36
35
  const stamp = now.toISOString().replace(/[:.]/g, "-");
37
36
  return new _Session(join(dir, `${stamp}-${randomBytes(3).toString("hex")}.jsonl`));
38
37
  }
@@ -49,6 +48,7 @@ var Session = class _Session {
49
48
  return _Session.list(home, cwd)[0]?.session;
50
49
  }
51
50
  write(record) {
51
+ mkdirSync(dirname(this.file), { recursive: true });
52
52
  appendFileSync(this.file, `${JSON.stringify(record)}
53
53
  `);
54
54
  }
@@ -136,6 +136,7 @@ Options:
136
136
  -r, --resume pick a session to continue
137
137
  --allow-all run all tools without asking for permission
138
138
  --allowed-tools <a,b> tools allowed without asking: bash, edit, fetch
139
+ --fail-on-tool-error with -p: exit 1 when any tool call fails (for CI)
139
140
  -w, --worktree <name> run in git worktree ../<repo>.worktrees/<name> (created, or reused if it exists)
140
141
  --output-format <fmt> text | json | stream-json for -p (machine-readable output)
141
142
  --permission-mode <mode> default | acceptEdits | plan | allowAll
@@ -166,6 +167,7 @@ Opsi:
166
167
  -r, --resume pilih sesi untuk dilanjutkan
167
168
  --allow-all jalankan semua alat tanpa minta izin
168
169
  --allowed-tools <a,b> alat yang boleh tanpa izin: bash, edit, fetch
170
+ --fail-on-tool-error dengan -p: keluar 1 bila ada tool gagal (untuk CI)
169
171
  -w, --worktree <name> jalankan di git worktree ../<repo>.worktrees/<name> (dibuat, atau dipakai ulang jika ada)
170
172
  --output-format <fmt> text | json | stream-json untuk -p (output mesin)
171
173
  --permission-mode <mode> default | acceptEdits | plan | allowAll
@@ -183,6 +185,7 @@ Opsi:
183
185
  "usageCap.usd must be a number greater than 0.": "usageCap.usd harus angka lebih dari 0.",
184
186
  "usageCap.usd requires usageCap.prices (USD per 1M tokens).": "usageCap.usd memerlukan usageCap.prices (USD per 1 juta token).",
185
187
  'usageCap.prices["{key}"] must have numeric input and output (USD per 1M tokens).': 'usageCap.prices["{key}"] harus punya input dan output numerik (USD per 1 juta token).',
188
+ 'usageCap.prices["{key}"].cachedInput must be a number >= 0 (USD per 1M tokens).': 'usageCap.prices["{key}"].cachedInput harus angka >= 0 (USD per 1 juta token).',
186
189
  "usageCap needs at least one of: tokens, usd.": "usageCap memerlukan setidaknya salah satu dari: tokens, usd.",
187
190
  'updateCheck must be "on" or "off".': 'updateCheck harus "on" atau "off".',
188
191
  "repoMap must be true or false.": "repoMap harus true atau false.",
@@ -224,6 +227,7 @@ Opsi:
224
227
  'npm removed the files of {name} but the config entry {entry} remains: {reason}. bccli will refuse to start while that entry is listed \u2014 fix the config error at {path}, then run "bccli plugins remove {name}" again (safe to repeat), or delete the entry manually.': 'npm sudah menghapus file {name} tetapi entry config {entry} tertinggal: {reason}. bccli menolak start selama entry itu terdaftar \u2014 perbaiki error config di {path}, lalu jalankan lagi "bccli plugins remove {name}" (aman diulang), atau hapus entry secara manual.',
225
228
  "Skipping malformed entry: {entry} \u2014 fix it manually in {path}.": "Melewatkan entry rusak: {entry} \u2014 perbaiki manual di {path}.",
226
229
  "No managed plugins to update.": "Tidak ada plugin terelola untuk diperbarui.",
230
+ "No plugins are installed. Install one with: bccli plugins install <spec>": "Belum ada plugin terpasang. Pasang dengan: bccli plugins install <spec>",
227
231
  "update requires a plugin name or no argument: bccli plugins update [name]": "update butuh nama plugin atau tanpa argumen: bccli plugins update [nama]",
228
232
  "{name} updated to latest.": "{name} diperbarui ke versi terbaru.",
229
233
  "Use either --key or --logout, not both.": "Pakai --key atau --logout, jangan keduanya.",
@@ -281,6 +285,13 @@ Opsi:
281
285
  "No API key for {id} yet. Run `bccli login` or set the env var {env}.": "API key untuk {id} belum ada. Jalankan `bccli login` atau set env {env}.",
282
286
  "Error from the provider: {message}": "Error dari provider: {message}",
283
287
  "Cannot connect to {url}: {error}": "Tidak bisa terhubung ke {url}: {error}",
288
+ "The API key was rejected. Check the key for this provider (bccli login) or its API key environment variable.": "Kunci API ditolak. Periksa kunci provider ini (bccli login) atau env var kuncinya.",
289
+ "Model or endpoint not found. Check the model name and the provider base URL.": "Model atau endpoint tidak ditemukan. Periksa nama model dan base URL provider.",
290
+ "Rate limit reached. Wait a moment, or switch model with /model.": "Batas laju tercapai. Tunggu sebentar, atau ganti model dengan /model.",
291
+ "The provider is having trouble. Try again, or switch model with /model.": "Penyedia sedang bermasalah. Coba lagi, atau ganti model dengan /model.",
292
+ "The provider did not respond within {seconds}s. Check the connection, then try again.": "Penyedia tidak merespons dalam {seconds} detik. Periksa koneksi, lalu coba lagi.",
293
+ "The provider stopped sending data for {seconds}s. The answer above may be incomplete; try again.": "Penyedia berhenti mengirim data selama {seconds} detik. Jawaban di atas mungkin tidak lengkap; coba lagi.",
294
+ "The response ended before it finished (no finish_reason or [DONE]). The answer above is incomplete; try again.": "Respons berakhir sebelum selesai (tanpa finish_reason atau [DONE]). Jawaban di atas tidak lengkap; coba lagi.",
284
295
  "{status} from {url}: {message}": "{status} dari {url}: {message}",
285
296
  "Request failed": "Permintaan gagal",
286
297
  "Connection closed before the response finished.": "Koneksi ditutup sebelum respons selesai.",
@@ -305,7 +316,8 @@ Opsi:
305
316
  "Usage: bccli mcp {action} <name>": "Pemakaian: bccli mcp {action} <nama>",
306
317
  "{name} removed.": "{name} dihapus.",
307
318
  "{name} installed ({url}).": "{name} terpasang ({url}).",
308
- "{name} is not in the catalog. For another remote server use: bccli mcp add {name} --url <url>": "{name} tidak ada di katalog. Untuk server remote lain pakai: bccli mcp add {name} --url <url>",
319
+ "{name} installed (stdio: {command}).": "{name} terpasang (stdio: {command}).",
320
+ "{name} is not in the catalog. For another server use: bccli mcp add {name} --url <url> (HTTP) or bccli mcp add {name} --command <cmd> (stdio).": "{name} tidak ada di katalog. Untuk server lain pakai: bccli mcp add {name} --url <url> (HTTP) atau bccli mcp add {name} --command <cmd> (stdio).",
309
321
  "{name} installed. Active in the next bccli session.": "{name} terpasang. Aktif di sesi bccli berikutnya.",
310
322
  "control a browser (open pages, click, fill forms, screenshot)": "kontrol browser (buka halaman, klik, isi form, screenshot)",
311
323
  "up-to-date library documentation": "dokumentasi library terbaru",
@@ -359,6 +371,8 @@ Opsi:
359
371
  "API key: env {env} only (via key_env; a plaintext key in config.yaml is never written).": "API key: hanya env {env} (melalui key_env; key plaintext tidak pernah ditulis ke config.yaml).",
360
372
  "{target}: not managed by BCCLI yet.": "{target}: belum dikelola oleh BCCLI.",
361
373
  "{target}: the BotConnector integration was removed and the config restored.": "{target}: integrasi BotConnector dilepas dan config dipulihkan.",
374
+ '{target}: {path} changed after BCCLI connected it, so the old backup was not copied over it. Remove the BCCLI part (provider "botconnector" or the marked block) from that file by hand.': '{target}: {path} berubah setelah BCCLI menyambungkannya, jadi backup lama tidak ditimpa ke file itu. Hapus bagian BCCLI (provider "botconnector" atau blok bertanda) dari file tersebut secara manual.',
375
+ "{target}: the BotConnector integration was removed. {n} file(s) were left untouched (see above); delete the BCCLI entries in them by hand for a clean removal.": "{target}: integrasi BotConnector dilepas. {n} file tidak disentuh (lihat di atas); hapus entri BCCLI di dalamnya secara manual agar bersih.",
362
376
  "Custom commands:": "Perintah custom:",
363
377
  "Skills:": "Skill:",
364
378
  "shift+tab switch permission mode esc cancel the turn": "shift+tab ganti mode izin esc batalkan giliran",
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  quarantineCorruptFile,
4
4
  writeFileAtomic
5
- } from "./chunk-DRJSANKH.js";
5
+ } from "./chunk-C2FUGQJX.js";
6
6
 
7
7
  // src/mcp/oauth.ts
8
8
  import { spawn } from "child_process";
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  listAllModels
4
- } from "./chunk-HPYI7L4W.js";
4
+ } from "./chunk-Y6AZGGH7.js";
5
5
  import {
6
6
  hasKey,
7
7
  providerName,
8
8
  removeGlobalProvider,
9
9
  writeGlobalConfig
10
- } from "./chunk-62UTSCWF.js";
10
+ } from "./chunk-X24APPXO.js";
11
11
  import {
12
12
  PRESETS,
13
13
  loadConfig,
14
14
  saveCredential
15
- } from "./chunk-DRJSANKH.js";
15
+ } from "./chunk-C2FUGQJX.js";
16
16
  import {
17
17
  t
18
- } from "./chunk-XCVYR5BK.js";
18
+ } from "./chunk-G5TG43R4.js";
19
19
 
20
20
  // src/providerCli.ts
21
21
  async function addProviderKey(providerId, key, deps) {
@@ -49,7 +49,8 @@ async function runProviderCommand(args, deps) {
49
49
  return 1;
50
50
  }
51
51
  const preset = PRESETS.find((p) => p.id === id);
52
- if (!preset && !config.providers[id]) {
52
+ const createdEntry = !preset && !config.providers[id];
53
+ if (createdEntry) {
53
54
  if (!args.url) {
54
55
  deps.err(t("{id} is not a preset. For a custom provider add --url <OpenAI-compatible base URL>.", { id }));
55
56
  return 1;
@@ -63,6 +64,7 @@ async function runProviderCommand(args, deps) {
63
64
  const key = needsKey ? await deps.readSecret(t("API key for {id} (leave empty if not needed): ", { id })) : "";
64
65
  const result = await addProviderKey(id, key, deps);
65
66
  if (!result.ok) {
67
+ if (createdEntry) removeGlobalProvider(id, deps.env);
66
68
  deps.err(result.error);
67
69
  return 1;
68
70
  }
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  defaultOpen
4
- } from "./chunk-RRECJTOG.js";
4
+ } from "./chunk-IHQGXDSG.js";
5
5
  import {
6
6
  removeCredential,
7
7
  saveCredential
8
- } from "./chunk-DRJSANKH.js";
8
+ } from "./chunk-C2FUGQJX.js";
9
9
  import {
10
10
  t
11
- } from "./chunk-XCVYR5BK.js";
11
+ } from "./chunk-G5TG43R4.js";
12
12
 
13
13
  // src/login.ts
14
14
  function readSecret(prompt) {
@@ -44,6 +44,11 @@ function readSecret(prompt) {
44
44
  stdin.on("data", onData);
45
45
  });
46
46
  }
47
+ function echoFor(ch) {
48
+ const code = ch.codePointAt(0) ?? 0;
49
+ if (code < 32 || code === 127) return void 0;
50
+ return ch;
51
+ }
47
52
  function readLine(prompt) {
48
53
  return new Promise((resolve) => {
49
54
  process.stdout.write(prompt);
@@ -71,8 +76,16 @@ function readLine(prompt) {
71
76
  return;
72
77
  }
73
78
  if (code === 3) process.exit(130);
74
- if (code === 127 || code === 8) value = value.slice(0, -1);
75
- else value += ch;
79
+ if (code === 127 || code === 8) {
80
+ if (value) {
81
+ value = value.slice(0, -1);
82
+ process.stdout.write("\b \b");
83
+ }
84
+ continue;
85
+ }
86
+ const echo = echoFor(ch);
87
+ if (echo) process.stdout.write(echo);
88
+ value += ch;
76
89
  }
77
90
  };
78
91
  stdin.on("data", onData);
@@ -91,8 +104,8 @@ function defaultLoginDeps() {
91
104
  fetch: globalThis.fetch,
92
105
  // Wiring NYATA jalur lama: persis runLogin lama (provider add + prompt key).
93
106
  legacyAdd: async (providerId, targetCwd) => {
94
- const { runProviderCommand } = await import("./providerCli-PBSZECBM.js");
95
- const { parseCliArgs: parse } = await import("./args-6XMUZDAK.js");
107
+ const { runProviderCommand } = await import("./providerCli-TXMVPSW5.js");
108
+ const { parseCliArgs: parse } = await import("./args-K5AJFDCZ.js");
96
109
  return runProviderCommand(parse(["provider", "add", providerId]), {
97
110
  env: process.env,
98
111
  cwd: targetCwd,
@@ -117,12 +130,12 @@ async function runLogin(providerId, cwd, args, deps) {
117
130
  );
118
131
  return 1;
119
132
  }
120
- const { runOauthLogout } = await import("./oauthLogin-NR22NWEV.js");
133
+ const { runOauthLogout } = await import("./oauthLogin-YFL2N4QA.js");
121
134
  return runOauthLogout({ out: deps.out, err: deps.err, env: deps.env, removeCredential: deps.removeCredential });
122
135
  }
123
136
  if (args.key) return deps.legacyAdd(providerId, cwd);
124
137
  if (providerId === "openrouter") {
125
- const { runOauthLogin } = await import("./oauthLogin-NR22NWEV.js");
138
+ const { runOauthLogin } = await import("./oauthLogin-YFL2N4QA.js");
126
139
  return runOauthLogin({
127
140
  out: deps.out,
128
141
  err: deps.err,
@@ -139,6 +152,7 @@ async function runLogin(providerId, cwd, args, deps) {
139
152
 
140
153
  export {
141
154
  readSecret,
155
+ echoFor,
142
156
  readLine,
143
157
  defaultLoginDeps,
144
158
  runLogin