@botconnector/bccli 0.3.0-beta.3 → 0.4.0-beta.10

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/README.md CHANGED
@@ -4,7 +4,7 @@ Agent AI coding dari BotConnector yang bekerja di terminal: membaca kode, menged
4
4
  dan menjalankan perintah — dengan izin kamu.
5
5
 
6
6
  ```bash
7
- npm i -g @botconnector/bccli@next # v0.2 beta, butuh Node.js 22+
7
+ npm i -g @botconnector/bccli@next # beta terbaru, butuh Node.js 22+
8
8
  bccli login # simpan API key BotConnector Cloud
9
9
  cd project-kamu && bccli
10
10
  ```
@@ -17,9 +17,40 @@ cd project-kamu && bccli
17
17
  | `bccli -p "tugas"` | satu tugas tanpa interaksi (skrip/CI) |
18
18
  | `bccli -c` / `bccli -r` | lanjutkan sesi terakhir / pilih sesi |
19
19
  | `bccli -m provider/model` | pilih model |
20
+ | `bccli --reasoning <auto|off|low|medium|high|max>` | pilih level reasoning untuk invocation ini |
20
21
  | `bccli models` | daftar model |
21
22
 
22
- Di dalam sesi: `/help`, `/model`, `/clear`, `/compact`, `/cost`, `/exit`.
23
+ Perintah di dalam sesi (ketik `/` untuk saran, ↑↓ pilih, Enter jalankan):
24
+
25
+ | Perintah | Fungsi |
26
+ |---|---|
27
+ | `/help` | daftar perintah dan pintasan |
28
+ | `/model` | ganti model |
29
+ | `/reasoning` | pilih Auto / Off / Low / Medium / High / Max |
30
+ | `/provider` | tambah/pilih provider AI |
31
+ | `/mcp` | pasang/kelola server MCP |
32
+ | `/new` | sesi baru (sesi lama tetap tersimpan) |
33
+ | `/resume` | lanjutkan sesi lain di folder ini |
34
+ | `/session` | info sesi ini |
35
+ | `/status` | versi, model, mode izin, MCP, konteks |
36
+ | `/permissions` | lihat/cabut izin |
37
+ | `/undo` | batalkan edit file giliran terakhir |
38
+ | `/diff` | git diff project |
39
+ | `/copy` | salin jawaban terakhir |
40
+ | `/export` | simpan percakapan ke markdown |
41
+ | `/memory` | instruksi project (/memory <teks>, /memory global <teks>) |
42
+ | `/init` | buat/perbarui AGENTS.md untuk project ini |
43
+ | `/agents` | daftar subagent |
44
+ | `/skills` | daftar skill dan perintah custom |
45
+ | `/doctor` | cek kesehatan instalasi |
46
+ | `/login` | simpan API key provider aktif |
47
+ | `/logout` | hapus API key provider aktif |
48
+ | `/clear` | mulai percakapan baru |
49
+ | `/compact` | ringkas percakapan |
50
+ | `/cost` | pemakaian token sesi ini |
51
+ | `/exit` | keluar |
52
+
53
+ `/undo` hanya membatalkan perubahan lewat alat edit/tulis (bukan perintah bash), per giliran, sampai 20 giliran ke belakang.
23
54
  Shift+Tab ganti mode izin: `default` → `acceptEdits` → `plan` → `allowAll`.
24
55
 
25
56
  Mode `-p` tidak pernah bertanya: alat yang butuh izin ditolak, kecuali memakai
@@ -47,10 +78,52 @@ Provider lain yang OpenAI-compatible: pilih **Custom…** di `/provider`, atau
47
78
  `/model` menampilkan model dari **semua** provider yang sudah punya key, dikelompokkan
48
79
  per provider; ketik untuk menyaring. Pilihan tersimpan sebagai default.
49
80
 
81
+ Reasoning dapat dipilih lewat `bccli --reasoning <auto|off|low|medium|high|max>` atau `/reasoning`.
82
+ **Auto** memakai default model/provider dan BCCLI tidak mengirim override reasoning apa pun. BCCLI tidak
83
+ otomatis menurunkan atau menaikkan reasoning effort. Level manual diteruskan bila kontrak provider/model
84
+ mendukungnya; level yang tidak didukung menghasilkan error yang jelas dan tidak di-downgrade diam-diam.
85
+
50
86
  `.bccli/config.json` di dalam project hanya boleh memilih `model` dan menambah provider baru tanpa API key —
87
+ preference `reasoning` hanya dibaca dari config user `~/.bccli/config.json`, bukan dari repo yang di-clone —
51
88
  izin (`permissionMode`, `allow`) dan provider yang sudah ada hanya diatur dari `~/.bccli/config.json`,
52
89
  supaya repo yang kamu clone tidak bisa memberi dirinya izin penuh atau mencuri API key.
53
90
 
91
+ ## Integrasi agent eksternal
92
+
93
+ BCCLI dapat memasang BotConnector sebagai provider untuk agent lain:
94
+
95
+ | Perintah | Fungsi |
96
+ |---|---|
97
+ | `bccli integrations` | lihat status integrasi |
98
+ | `bccli connect opencode` | hubungkan OpenCode ke BotConnector |
99
+ | `bccli connect aider` | hubungkan Aider ke BotConnector |
100
+ | `bccli connect cline` | hubungkan Cline CLI ke BotConnector |
101
+ | `bccli connect deepseek-harness` / `bccli connect dsh` | hubungkan DeepSeek Harness ke BotConnector |
102
+ | `bccli disconnect <agent>` | pulihkan config sebelum integrasi |
103
+
104
+ OpenCode memakai config global `~/.config/opencode/opencode.json`. BCCLI mengambil katalog model BotConnector saat connect,
105
+ menjaga config provider lain, dan menyimpan secret di file terpisah di `~/.bccli/integrations/`.
106
+ Aider memakai `~/.aider.conf.yml` yang menunjuk ke env file BCCLI terpisah; BCCLI menolak menimpa setting
107
+ OpenAI/env-file Aider yang sudah ada.
108
+
109
+ Cline memakai provider `openai-compatible` di `~/.cline/data/settings/providers.json`. Jika slot itu sudah dipakai
110
+ endpoint lain, BCCLI menolak menimpanya. Cline sendiri menyimpan API key provider di file tersebut.
111
+
112
+ DeepSeek Harness memakai custom provider `botconnector` di `$DSH_HOME/settings.yaml` dengan
113
+ `api: openai-completions` dan `apiKeyEnv: BOTCONNECTOR_API_KEY`; key diletakkan pada blok BCCLI di
114
+ `$DSH_HOME/.env`. Provider DSH lain tetap dipertahankan.
115
+
116
+ External agent memanggil BotConnector langsung, jadi discovery model tidak memakai identitas khusus BCCLI.
117
+ Akses yang memang dibatasi ke BCCLI/Web App (misalnya Luna launch access) tidak otomatis diwariskan.
118
+ Sebelum mengubah config eksternal, BCCLI membuat backup agar disconnect dapat memulihkannya.
119
+
120
+ ## Internet
121
+
122
+ - `web_search`: mencari di web tanpa perlu izin. Memakai akun BotConnector Cloud
123
+ bila ada key, kalau tidak langsung ke Keenable (gratis, tanpa key). Hasilnya
124
+ judul, URL, dan cuplikan singkat, jadi hemat token.
125
+ - `fetch`: membuka satu URL dari komputer ini (butuh izin per host).
126
+
54
127
  ## MCP
55
128
 
56
129
  `/mcp` (atau `bccli mcp add <nama>`) memasang server dari katalog:
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ HELP_TEXT,
4
+ parseCliArgs
5
+ } from "./chunk-BJ337ABK.js";
6
+ import "./chunk-ELML4KVQ.js";
7
+ import "./chunk-CLN4Z5E3.js";
8
+ import "./chunk-2MGINW4O.js";
9
+ export {
10
+ HELP_TEXT,
11
+ parseCliArgs
12
+ };
@@ -22,6 +22,7 @@ var ConfigError = class extends Error {
22
22
  var DEFAULT_CONFIG = {
23
23
  model: "bc-cloud/glm-5.3-flash",
24
24
  permissionMode: "default",
25
+ reasoning: "auto",
25
26
  providers: Object.fromEntries(PRESETS.map((p) => [p.id, { baseURL: p.baseURL, apiKeyEnv: p.apiKeyEnv }])),
26
27
  allow: []
27
28
  };
@@ -52,6 +53,7 @@ function loadConfig(cwd, env = process.env) {
52
53
  return {
53
54
  model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
54
55
  permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
56
+ reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
55
57
  providers: { ...known, ...fromProject },
56
58
  projectProviders: Object.keys(fromProject),
57
59
  allow: [...global.allow ?? []]
@@ -91,11 +93,12 @@ function resolveModel(config, modelRef, env = process.env) {
91
93
  }
92
94
  function removeCredential(providerId, env = process.env) {
93
95
  const creds = readCredentials(env);
94
- if (!(providerId in creds)) return;
96
+ if (!(providerId in creds)) return false;
95
97
  delete creds[providerId];
96
98
  const path = join(bccliHome(env), "credentials");
97
99
  writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 384 });
98
100
  chmodSync(path, 384);
101
+ return true;
99
102
  }
100
103
 
101
104
  export {
@@ -5,7 +5,7 @@ import {
5
5
  readCredentials,
6
6
  readJsonConfig,
7
7
  removeCredential
8
- } from "./chunk-Z5JM76EL.js";
8
+ } from "./chunk-2MGINW4O.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-2MGINW4O.js";
5
5
 
6
6
  // src/mcp/config.ts
7
7
  import { createHash } from "crypto";
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  MODE_ORDER
4
- } from "./chunk-5XN44Q55.js";
4
+ } from "./chunk-ELML4KVQ.js";
5
+ import {
6
+ REASONING_LEVELS,
7
+ isReasoningLevel
8
+ } from "./chunk-CLN4Z5E3.js";
5
9
  import {
6
10
  ConfigError
7
- } from "./chunk-Z5JM76EL.js";
11
+ } from "./chunk-2MGINW4O.js";
8
12
 
9
13
  // src/args.ts
10
14
  import { parseArgs } from "util";
@@ -17,9 +21,13 @@ Pemakaian:
17
21
  bccli models daftar model dari provider aktif
18
22
  bccli provider list|add <id>|remove <id> kelola provider (custom: --url <url> [--name N] [--key-env ENV])
19
23
  bccli mcp list|add <nama>|remove <nama> kelola server MCP (katalog, atau --url <url>)
24
+ bccli integrations lihat status integrasi agent eksternal
25
+ bccli connect <agent> hubungkan BotConnector ke agent (opencode, aider, cline, deepseek-harness/dsh)
26
+ bccli disconnect <agent> lepas integrasi dan pulihkan config sebelumnya
20
27
 
21
28
  Opsi:
22
29
  -m, --model <provider/model> pilih model, contoh bc-cloud/glm-5.3-flash
30
+ --reasoning <level> auto | off | low | medium | high | max
23
31
  -c, --continue lanjutkan sesi terakhir di folder ini
24
32
  -r, --resume pilih sesi untuk dilanjutkan
25
33
  --allow-all jalankan semua alat tanpa minta izin
@@ -34,6 +42,7 @@ function parseCliArgs(argv) {
34
42
  options: {
35
43
  print: { type: "boolean", short: "p" },
36
44
  model: { type: "string", short: "m" },
45
+ reasoning: { type: "string" },
37
46
  continue: { type: "boolean", short: "c" },
38
47
  resume: { type: "boolean", short: "r" },
39
48
  "allow-all": { type: "boolean" },
@@ -47,12 +56,16 @@ function parseCliArgs(argv) {
47
56
  value: { type: "string", multiple: true }
48
57
  }
49
58
  });
59
+ const reasoning = values.reasoning;
60
+ if (reasoning && !isReasoningLevel(reasoning)) {
61
+ throw new ConfigError(`--reasoning harus salah satu dari: ${REASONING_LEVELS.join(", ")}`);
62
+ }
50
63
  const mode = values["permission-mode"];
51
64
  if (mode && !MODE_ORDER.includes(mode)) {
52
65
  throw new ConfigError(`--permission-mode harus salah satu dari: ${MODE_ORDER.join(", ")}`);
53
66
  }
54
67
  const [first, ...rest] = positionals;
55
- const SUBCOMMANDS = ["login", "models", "provider", "mcp"];
68
+ const SUBCOMMANDS = ["login", "models", "provider", "mcp", "connect", "disconnect", "integrations"];
56
69
  const command = SUBCOMMANDS.includes(first) ? first : "run";
57
70
  const words = command === "run" ? positionals : rest;
58
71
  return {
@@ -60,6 +73,7 @@ function parseCliArgs(argv) {
60
73
  prompt: command === "run" && words.length ? words.join(" ") : void 0,
61
74
  print: !!values.print,
62
75
  model: values.model,
76
+ reasoning,
63
77
  continue: !!values.continue,
64
78
  resume: !!values.resume,
65
79
  allowAll: !!values["allow-all"],
@@ -68,7 +82,7 @@ function parseCliArgs(argv) {
68
82
  help: !!values.help,
69
83
  version: !!values.version,
70
84
  loginProvider: command === "login" ? rest[0] ?? "bc-cloud" : void 0,
71
- subArgs: command === "provider" || command === "mcp" ? rest : [],
85
+ subArgs: ["provider", "mcp", "connect", "disconnect", "integrations"].includes(command) ? rest : [],
72
86
  url: values.url,
73
87
  name: values.name,
74
88
  keyEnv: values["key-env"],
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/reasoning.ts
4
+ var REASONING_LEVELS = ["auto", "off", "low", "medium", "high", "max"];
5
+ var OPENAI_LEVELS = ["auto", "low", "medium", "high"];
6
+ var BOTCONNECTOR_LEVELS = REASONING_LEVELS;
7
+ function isReasoningLevel(value) {
8
+ return REASONING_LEVELS.includes(value);
9
+ }
10
+ function parseReasoningLevel(value, label = "reasoning") {
11
+ if (!isReasoningLevel(value)) {
12
+ throw new Error(`${label} harus salah satu dari: ${REASONING_LEVELS.join(", ")}`);
13
+ }
14
+ return value;
15
+ }
16
+ function reasoningCapability(providerId) {
17
+ switch (providerId) {
18
+ case "bc-cloud":
19
+ return { levels: BOTCONNECTOR_LEVELS, parameter: "reasoning_effort" };
20
+ case "openai":
21
+ return { levels: OPENAI_LEVELS, parameter: "reasoning_effort" };
22
+ case "openrouter":
23
+ return { levels: ["auto", "low", "medium", "high"], parameter: "reasoning" };
24
+ default:
25
+ return void 0;
26
+ }
27
+ }
28
+ function supportedReasoningLevels(providerId) {
29
+ return reasoningCapability(providerId)?.levels ?? ["auto"];
30
+ }
31
+ function assertReasoningSupported(providerId, level) {
32
+ if (level === "auto") return;
33
+ const capability = reasoningCapability(providerId);
34
+ if (!capability) {
35
+ throw new Error("Provider ini belum memiliki kontrak reasoning manual. Gunakan Auto.");
36
+ }
37
+ if (!capability.levels.includes(level)) {
38
+ const supported = capability.levels.filter((x) => x !== "auto").map(capitalize).join("/");
39
+ throw new Error(`Model/provider ini tidak mendukung reasoning level '${level}'. Gunakan Auto${supported ? `/${supported}` : ""}.`);
40
+ }
41
+ }
42
+ function reasoningPayload(providerId, level) {
43
+ if (level === "auto") return {};
44
+ assertReasoningSupported(providerId, level);
45
+ const capability = reasoningCapability(providerId);
46
+ if (capability.parameter === "reasoning") return { reasoning: { effort: level } };
47
+ return { reasoning_effort: level };
48
+ }
49
+ function capitalize(value) {
50
+ return value.charAt(0).toUpperCase() + value.slice(1);
51
+ }
52
+
53
+ export {
54
+ REASONING_LEVELS,
55
+ isReasoningLevel,
56
+ parseReasoningLevel,
57
+ supportedReasoningLevels,
58
+ assertReasoningSupported,
59
+ reasoningPayload
60
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigError
4
- } from "./chunk-Z5JM76EL.js";
4
+ } from "./chunk-2MGINW4O.js";
5
5
 
6
6
  // src/mcp/catalog.ts
7
7
  var CATALOG = [
@@ -16,10 +16,12 @@ var Permissions = class {
16
16
  this.mode = mode;
17
17
  this.cwd = cwd;
18
18
  this.rules = new Set(rules);
19
+ this.configRules = new Set(rules);
19
20
  }
20
21
  mode;
21
22
  cwd;
22
23
  rules;
24
+ configRules;
23
25
  /** Edits may only be auto-allowed inside the project and outside .git. */
24
26
  editInProject(target) {
25
27
  const rel = relative(this.cwd, resolve(this.cwd, target));
@@ -55,8 +57,12 @@ var Permissions = class {
55
57
  if (req.kind === "edit" && this.mode === "acceptEdits") return "allow";
56
58
  return needed.every((rule) => this.rules.has(rule)) ? "allow" : "ask";
57
59
  }
60
+ list() {
61
+ return [...this.rules].map((rule) => ({ rule, source: this.configRules.has(rule) ? "config" : "session" }));
62
+ }
63
+ /** Removes a session grant; rules from config/flags stay until the user edits them. */
58
64
  revoke(rule) {
59
- this.rules.delete(rule);
65
+ if (!this.configRules.has(rule)) this.rules.delete(rule);
60
66
  }
61
67
  allowForSession(req) {
62
68
  for (const rule of this.rulesFor(req) ?? []) this.rules.add(rule);
@@ -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-EVIFNGPQ.js");
38
+ const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
39
+ const { parseCliArgs } = await import("./args-AWLROI65.js");
40
40
  return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
41
  env: process.env,
42
42
  cwd,
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/print.ts
4
+ function toolLabel(tool) {
5
+ if (tool === "web_search") return "Search";
6
+ return tool.charAt(0).toUpperCase() + tool.slice(1);
7
+ }
8
+ async function runPrint(rt, prompt, io = { out: process.stdout, err: process.stderr }, opts = {}) {
9
+ rt.interaction.approvePlan = async () => {
10
+ if (opts.allowAll) return "allowAll";
11
+ io.err.write("Rencana butuh persetujuan; jalankan dengan --allow-all.\n");
12
+ return "no";
13
+ };
14
+ let failed = false;
15
+ let wroteText = false;
16
+ rt.agent.onEvent = (event) => {
17
+ switch (event.type) {
18
+ case "text":
19
+ io.out.write(event.delta);
20
+ wroteText = true;
21
+ break;
22
+ case "toolStart":
23
+ io.err.write(`\u23BF ${toolLabel(event.tool)} ${event.target}
24
+ `);
25
+ break;
26
+ case "toolEnd":
27
+ if (event.isError) io.err.write(` \u2717 ${event.output.split("\n")[0]}
28
+ `);
29
+ break;
30
+ case "stepLimit":
31
+ io.err.write("Batas langkah tercapai.\n");
32
+ failed = true;
33
+ break;
34
+ case "aborted":
35
+ io.err.write("Dibatalkan.\n");
36
+ failed = true;
37
+ break;
38
+ case "error":
39
+ io.err.write(`Error: ${event.message}
40
+ `);
41
+ failed = true;
42
+ break;
43
+ }
44
+ };
45
+ rt.agent.askPermission = async (req) => {
46
+ io.err.write(` Butuh izin untuk ${req.tool}. Jalankan dengan --allow-all atau --allowed-tools ${req.kind}.
47
+ `);
48
+ return "no";
49
+ };
50
+ const controller = new AbortController();
51
+ const onSigint = () => controller.abort();
52
+ process.once("SIGINT", onSigint);
53
+ try {
54
+ await rt.agent.run(prompt, controller.signal);
55
+ } finally {
56
+ process.off("SIGINT", onSigint);
57
+ }
58
+ if (wroteText) io.out.write("\n");
59
+ return failed ? 1 : 0;
60
+ }
61
+
62
+ // src/agents.ts
63
+ 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.";
64
+ 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.";
65
+ var BUILTIN_AGENTS = [
66
+ {
67
+ name: "explore",
68
+ description: "Read-only: search and read code to answer a question (fast, safe, can run in parallel).",
69
+ tools: ["read", "grep", "glob"],
70
+ prompt: EXPLORE_PROMPT,
71
+ maxSteps: 30
72
+ },
73
+ {
74
+ name: "general",
75
+ description: "Full tools: carry out a self-contained sub-task (edits and commands still need user permission).",
76
+ prompt: GENERAL_PROMPT,
77
+ maxSteps: 50
78
+ }
79
+ ];
80
+
81
+ // src/context.ts
82
+ import { existsSync, readFileSync } from "fs";
83
+ import { dirname, join } from "path";
84
+ var FILES = ["AGENTS.md", "BCCLI.md"];
85
+ function readIf(path) {
86
+ try {
87
+ return existsSync(path) ? readFileSync(path, "utf8").trim() : void 0;
88
+ } catch {
89
+ return void 0;
90
+ }
91
+ }
92
+ function instructionPaths(cwd, home) {
93
+ const dirs2 = [];
94
+ for (let dir = cwd; ; dir = dirname(dir)) {
95
+ dirs2.unshift(dir);
96
+ if (dirname(dir) === dir) break;
97
+ }
98
+ return [join(home, "BCCLI.md"), ...dirs2.flatMap((dir) => FILES.map((name) => join(dir, name)))];
99
+ }
100
+ function loadInstructions(cwd, home) {
101
+ const parts = [];
102
+ for (const path of instructionPaths(cwd, home)) {
103
+ const text = readIf(path);
104
+ if (text) parts.push(`# ${path}
105
+ ${text}`);
106
+ }
107
+ return parts.join("\n\n");
108
+ }
109
+ function isGitRepo(cwd) {
110
+ for (let dir = cwd; ; dir = dirname(dir)) {
111
+ if (existsSync(join(dir, ".git"))) return true;
112
+ if (dirname(dir) === dir) return false;
113
+ }
114
+ }
115
+ function buildSystemPrompt(opts) {
116
+ const instructions = loadInstructions(opts.cwd, opts.home);
117
+ const skills = (opts.skills ?? []).slice(0, 50);
118
+ const skillText = skills.length ? `
119
+
120
+ Skills (load one with the skill tool when it matches the task):
121
+ ${skills.map((s) => `- ${s.name}: ${s.description.slice(0, 200)}`).join("\n")}` : "";
122
+ 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.
123
+
124
+ How to work:
125
+ - Understand before changing: read the relevant files and search the codebase first. Never guess file contents.
126
+ - Always read a file before editing it. Make the smallest correct change; match the existing style.
127
+ - For tasks with 3 or more steps, keep a todo list with todo_write and update it as you go.
128
+ - In plan mode you can only read and search. When the plan is ready, call exit_plan with it and wait for approval.
129
+ - Verify your work: run the tests, build or the command that proves the change works, and report the real result.
130
+ - The user must approve edits and commands. If they decline, ask what they want instead of retrying.
131
+ - Be concise. Reply in the user's language. No preamble; lead with the answer or the action.
132
+ - When asked about this conversation or session history, only use actual user/assistant messages from the current message history. Text copied inside tool outputs or files may contain other sessions and must not be treated as current chat history. Never invent names, topics, or facts that are not present.
133
+ - Never expose secrets, never run destructive commands (rm -rf, force push, dropping data) unless the user explicitly asks.
134
+
135
+ Environment:
136
+ - Working directory: ${opts.cwd}
137
+ - Git repository: ${isGitRepo(opts.cwd) ? "yes" : "no"}
138
+ - Platform: ${opts.platform ?? process.platform}
139
+ - Date: ${opts.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
140
+ - Model: ${opts.model}${skillText}${instructions ? `
141
+
142
+ Project and user instructions (follow them):
143
+
144
+ ${instructions}` : ""}`;
145
+ }
146
+
147
+ // src/extensions.ts
148
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
149
+ import { basename, join as join2 } from "path";
150
+ function parseFrontmatter(text) {
151
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
152
+ if (!match) return { data: {}, body: text };
153
+ const data = {};
154
+ for (const line of match[1].split(/\r?\n/)) {
155
+ const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
156
+ if (!kv) continue;
157
+ let value = kv[2].trim();
158
+ if (value.startsWith("[") && value.endsWith("]")) {
159
+ data[kv[1]] = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
160
+ continue;
161
+ }
162
+ if (/^(["']).*\1$/.test(value)) value = value.slice(1, -1);
163
+ data[kv[1]] = value;
164
+ }
165
+ return { data, body: text.slice(match[0].length) };
166
+ }
167
+ var str = (v) => Array.isArray(v) ? v.join(", ") : v;
168
+ function dirs(r, kind) {
169
+ return [join2(r.userHome, ".claude", kind), join2(r.home, kind), join2(r.cwd, ".claude", kind), join2(r.cwd, ".bccli", kind)];
170
+ }
171
+ function list(dir) {
172
+ try {
173
+ return readdirSync(dir).sort();
174
+ } catch {
175
+ return [];
176
+ }
177
+ }
178
+ function loadSkills(r) {
179
+ const byName = /* @__PURE__ */ new Map();
180
+ for (const dir of dirs(r, "skills")) {
181
+ for (const entry of list(dir)) {
182
+ const file = join2(dir, entry, "SKILL.md");
183
+ if (!existsSync2(file)) continue;
184
+ const { data } = parseFrontmatter(readFileSync2(file, "utf8"));
185
+ const name = str(data.name) || entry;
186
+ byName.set(name, { name, description: str(data.description) ?? "", dir: join2(dir, entry), file });
187
+ }
188
+ }
189
+ return [...byName.values()];
190
+ }
191
+ function loadCommands(r) {
192
+ const byName = /* @__PURE__ */ new Map();
193
+ for (const dir of dirs(r, "commands")) {
194
+ for (const entry of list(dir)) {
195
+ const file = join2(dir, entry);
196
+ if (!entry.endsWith(".md") || !statSync(file).isFile()) continue;
197
+ const { data, body } = parseFrontmatter(readFileSync2(file, "utf8"));
198
+ const def = { name: basename(entry, ".md"), body: body.trim() };
199
+ if (data.description) def.description = str(data.description);
200
+ if (data["argument-hint"]) def.argumentHint = str(data["argument-hint"]);
201
+ byName.set(def.name, def);
202
+ }
203
+ }
204
+ return [...byName.values()];
205
+ }
206
+ function loadAgentDefs(r) {
207
+ const byName = /* @__PURE__ */ new Map();
208
+ for (const dir of dirs(r, "agents")) {
209
+ for (const entry of list(dir)) {
210
+ if (!entry.endsWith(".md")) continue;
211
+ const { data, body } = parseFrontmatter(readFileSync2(join2(dir, entry), "utf8"));
212
+ const name = str(data.name) || basename(entry, ".md");
213
+ const def = { name, description: str(data.description) ?? "", prompt: body.trim() };
214
+ if (data.tools)
215
+ def.tools = Array.isArray(data.tools) ? data.tools : String(data.tools).split(",").map((s) => s.trim()).filter(Boolean);
216
+ if (data.model) def.model = str(data.model);
217
+ byName.set(name, def);
218
+ }
219
+ }
220
+ return [...byName.values()];
221
+ }
222
+
223
+ // src/tools/ignore.ts
224
+ import { readFileSync as readFileSync3 } from "fs";
225
+ import { join as join3 } from "path";
226
+ function ignorePatterns(root) {
227
+ const patterns = ["**/node_modules/**", "**/.git/**"];
228
+ let lines = [];
229
+ try {
230
+ lines = readFileSync3(join3(root, ".gitignore"), "utf8").split(/\r?\n/);
231
+ } catch {
232
+ return patterns;
233
+ }
234
+ for (const raw of lines) {
235
+ const line = raw.trim();
236
+ if (!line || line.startsWith("#") || line.startsWith("!")) continue;
237
+ const p = line.replace(/^\//, "").replace(/\/$/, "");
238
+ patterns.push(`**/${p}`, `**/${p}/**`);
239
+ }
240
+ return patterns;
241
+ }
242
+
243
+ export {
244
+ toolLabel,
245
+ runPrint,
246
+ ignorePatterns,
247
+ BUILTIN_AGENTS,
248
+ instructionPaths,
249
+ buildSystemPrompt,
250
+ parseFrontmatter,
251
+ loadSkills,
252
+ loadCommands,
253
+ loadAgentDefs
254
+ };