@botconnector/bccli 0.4.0-beta.8 → 0.4.0

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 (59) hide show
  1. package/README.en.md +242 -0
  2. package/README.md +95 -3
  3. package/dist/acp-ATIL4JOZ.js +182 -0
  4. package/dist/args-J5ONVGIF.js +13 -0
  5. package/dist/chunk-45SDLMTV.js +341 -0
  6. package/dist/chunk-6M5X2KZE.js +8 -0
  7. package/dist/chunk-7F3IWBPQ.js +191 -0
  8. package/dist/{chunk-CLN4Z5E3.js → chunk-BLOYFIAD.js} +6 -3
  9. package/dist/chunk-BNA74HPN.js +145 -0
  10. package/dist/{chunk-DX7CZTBB.js → chunk-BQ2IGPBR.js} +14 -9
  11. package/dist/chunk-JL5ZCSP2.js +76 -0
  12. package/dist/chunk-KEMYIGCP.js +235 -0
  13. package/dist/{chunk-BFZXRKW5.js → chunk-L7QI6JN2.js} +24 -2
  14. package/dist/chunk-LACUWBWS.js +361 -0
  15. package/dist/chunk-M7CALSP3.js +733 -0
  16. package/dist/{chunk-ELML4KVQ.js → chunk-O7M3TGIW.js} +34 -2
  17. package/dist/chunk-PSO7R5AD.js +10 -0
  18. package/dist/chunk-PUN4HMAG.js +940 -0
  19. package/dist/{chunk-SJH3YLW4.js → chunk-QDQU6VY2.js} +60 -6
  20. package/dist/chunk-QZKLZELZ.js +64 -0
  21. package/dist/{chunk-7S6JXQCH.js → chunk-SPRNLS7K.js} +2 -1
  22. package/dist/chunk-TLPFQIS2.js +231 -0
  23. package/dist/chunk-TVALKHFB.js +2143 -0
  24. package/dist/chunk-UZWJQF67.js +500 -0
  25. package/dist/chunk-XBO5GACQ.js +56 -0
  26. package/dist/cli.js +162 -1383
  27. package/dist/completion-HPPFDAPB.js +125 -0
  28. package/dist/{config-YUNKPCNB.js → config-COPNMMV6.js} +5 -2
  29. package/dist/config-RZ5JONMX.js +30 -0
  30. package/dist/ghBotCli-IBCZIWT5.js +677 -0
  31. package/dist/integrationCli-P75R2VSC.js +685 -0
  32. package/dist/login-SYIQP46J.js +16 -0
  33. package/dist/{mcpCli-DZWQPTOS.js → mcpCli-QUV5SD64.js} +42 -9
  34. package/dist/modelsCli-TRRB5LX4.js +36 -0
  35. package/dist/npmRunner-LU43GM3W.js +46 -0
  36. package/dist/oauthLogin-6FWQABJ7.js +210 -0
  37. package/dist/pluginsCli-GNW4TPVV.js +284 -0
  38. package/dist/prCli-5IMV3VNQ.js +78 -0
  39. package/dist/providerCli-FAOZHOKW.js +15 -0
  40. package/dist/sdk.js +5490 -0
  41. package/dist/serve-6QLBJUYW.js +178 -0
  42. package/dist/serve-JG7RLIUD.js +515 -0
  43. package/dist/session-KBAQ4BZ5.js +12 -0
  44. package/dist/setup-FJAWDWV4.js +24 -0
  45. package/dist/statsCli-CF3R7ATL.js +353 -0
  46. package/dist/transferCli-TK2GSVRK.js +71 -0
  47. package/dist/ui-B3CJ6TGE.js +2365 -0
  48. package/dist/update-K5IQ2MYR.js +145 -0
  49. package/package.json +25 -4
  50. package/dist/args-FLTRM57K.js +0 -12
  51. package/dist/chunk-2MGINW4O.js +0 -114
  52. package/dist/chunk-IM3KMPVZ.js +0 -52
  53. package/dist/chunk-KV27KTOX.js +0 -254
  54. package/dist/chunk-U7GK73CK.js +0 -341
  55. package/dist/chunk-Z7PM3LTC.js +0 -93
  56. package/dist/login-JXFORO3J.js +0 -9
  57. package/dist/providerCli-Q7LRM2NP.js +0 -12
  58. package/dist/session-7UB77CEG.js +0 -9
  59. package/dist/ui-6GTQUJOE.js +0 -1415
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ VERSION
4
+ } from "./chunk-6M5X2KZE.js";
5
+ import {
6
+ t
7
+ } from "./chunk-UZWJQF67.js";
8
+
9
+ // src/update.ts
10
+ import { spawnSync } from "child_process";
11
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
12
+ import { dirname, join } from "path";
13
+ var REGISTRY_URL = "https://registry.npmjs.org/-/package/@botconnector/bccli/dist-tags";
14
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
15
+ function splitVersion(v) {
16
+ const i = v.indexOf("-");
17
+ const coreStr = i < 0 ? v : v.slice(0, i);
18
+ const preStr = i < 0 ? "" : v.slice(i + 1);
19
+ return {
20
+ core: coreStr.split(".").map((p) => Number.parseInt(p, 10) || 0),
21
+ pre: preStr ? preStr.split(".") : []
22
+ };
23
+ }
24
+ function compareVersions(a, b) {
25
+ const A = splitVersion(a);
26
+ const B = splitVersion(b);
27
+ for (let i = 0; i < Math.max(A.core.length, B.core.length); i++) {
28
+ const x = A.core[i] ?? 0;
29
+ const y = B.core[i] ?? 0;
30
+ if (x !== y) return x < y ? -1 : 1;
31
+ }
32
+ if (!A.pre.length && !B.pre.length) return 0;
33
+ if (!A.pre.length) return 1;
34
+ if (!B.pre.length) return -1;
35
+ for (let i = 0; i < Math.max(A.pre.length, B.pre.length); i++) {
36
+ const x = A.pre[i];
37
+ const y = B.pre[i];
38
+ if (x === void 0) return -1;
39
+ if (y === void 0) return 1;
40
+ const nx = Number.parseInt(x, 10);
41
+ const ny = Number.parseInt(y, 10);
42
+ if (String(nx) === x && String(ny) === y) {
43
+ if (nx !== ny) return nx < ny ? -1 : 1;
44
+ } else if (x !== y) {
45
+ return x < y ? -1 : 1;
46
+ }
47
+ }
48
+ return 0;
49
+ }
50
+ function channelFor(version) {
51
+ return version.includes("-") ? "next" : "latest";
52
+ }
53
+ function cachePath(home) {
54
+ return join(home, "update-check.json");
55
+ }
56
+ function readCache(home) {
57
+ try {
58
+ const parsed = JSON.parse(readFileSync(cachePath(home), "utf8"));
59
+ return typeof parsed?.checkedAt === "string" ? parsed : void 0;
60
+ } catch {
61
+ return void 0;
62
+ }
63
+ }
64
+ function writeCache(home, cache) {
65
+ mkdirSync(home, { recursive: true });
66
+ const tmp = `${cachePath(home)}.tmp`;
67
+ writeFileSync(tmp, JSON.stringify(cache));
68
+ renameSync(tmp, cachePath(home));
69
+ }
70
+ function dueForCheck(cache, now, intervalMs = CHECK_INTERVAL_MS) {
71
+ if (!cache) return true;
72
+ const at = Date.parse(cache.checkedAt);
73
+ if (!Number.isFinite(at)) return true;
74
+ return now.getTime() - at >= intervalMs;
75
+ }
76
+ function updateNotice(current, latest) {
77
+ if (!latest || latest.length > 64 || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(latest) || compareVersions(current, latest) >= 0) return void 0;
78
+ return t("A new bccli is available: {latest} (you have {current}). Run: bccli update", { latest, current });
79
+ }
80
+ function notifyOnce(home, cache) {
81
+ const message = updateNotice(VERSION, cache?.latest);
82
+ if (!message || !cache?.latest || cache.notified === cache.latest) return void 0;
83
+ writeCache(home, { ...cache, notified: cache.latest });
84
+ return message;
85
+ }
86
+ async function checkForUpdate(deps) {
87
+ try {
88
+ const cached = readCache(deps.home);
89
+ if (!dueForCheck(cached, deps.now)) return notifyOnce(deps.home, cached);
90
+ const res = await deps.fetch(REGISTRY_URL, { signal: AbortSignal.timeout(deps.timeoutMs ?? 3e3) });
91
+ if (!res.ok) return notifyOnce(deps.home, cached);
92
+ const tags = await res.json();
93
+ const latest = tags[channelFor(VERSION)] ?? tags.latest;
94
+ const updated = { checkedAt: deps.now.toISOString(), latest, notified: cached?.notified };
95
+ writeCache(deps.home, updated);
96
+ return notifyOnce(deps.home, updated);
97
+ } catch {
98
+ return void 0;
99
+ }
100
+ }
101
+ function updateSpawnCommand(version, platform = process.platform, nodeExecPath = process.execPath, targetVersion) {
102
+ const spec = `@botconnector/bccli@${targetVersion ?? channelFor(version)}`;
103
+ if (platform !== "win32") return { command: "npm", args: ["i", "-g", spec] };
104
+ const npmCli = join(dirname(nodeExecPath), "node_modules", "npm", "bin", "npm-cli.js");
105
+ return { command: nodeExecPath, args: [npmCli, "i", "-g", spec], npmCli };
106
+ }
107
+ async function runUpdateCommand(deps) {
108
+ const current = deps.env.BCCLI_VERSION_OVERRIDE ?? VERSION;
109
+ let target;
110
+ try {
111
+ const response = await (deps.fetch ?? fetch)(REGISTRY_URL, {
112
+ signal: AbortSignal.timeout(8e3),
113
+ cache: "no-store",
114
+ headers: { "cache-control": "no-cache" }
115
+ });
116
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
117
+ const tags = await response.json();
118
+ target = tags[channelFor(current)];
119
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(target ?? "")) throw new Error("invalid registry version");
120
+ } catch (error) {
121
+ console.error(`Could not verify BCCLI update version: ${String(error)}. No changes made.`);
122
+ return 1;
123
+ }
124
+ if (compareVersions(target, current) <= 0) {
125
+ console.log(`BCCLI ${current} is already up to date (registry: ${target}); no changes made.`);
126
+ return 0;
127
+ }
128
+ const spec = updateSpawnCommand(current, deps.platform, deps.nodeExecPath, target);
129
+ if (spec.npmCli && !existsSync(spec.npmCli)) {
130
+ console.error(`npm CLI was not found at ${spec.npmCli}. Update manually with: npm install -g @botconnector/bccli@${channelFor(deps.env.BCCLI_VERSION_OVERRIDE ?? VERSION)}`);
131
+ return 1;
132
+ }
133
+ const r = (deps.spawn ?? spawnSync)(spec.command, spec.args, { stdio: "inherit", env: deps.env, shell: false });
134
+ if (r.error) console.error(`BCCLI update failed: ${r.error.message}. Try: npm install -g @botconnector/bccli@${channelFor(deps.env.BCCLI_VERSION_OVERRIDE ?? VERSION)}`);
135
+ return typeof r.status === "number" ? r.status : 1;
136
+ }
137
+ export {
138
+ channelFor,
139
+ checkForUpdate,
140
+ compareVersions,
141
+ dueForCheck,
142
+ runUpdateCommand,
143
+ updateNotice,
144
+ updateSpawnCommand
145
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botconnector/bccli",
3
- "version": "0.4.0-beta.8",
3
+ "version": "0.4.0",
4
4
  "description": "BCCLI — BotConnector AI coding agent for the terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,20 +24,41 @@
24
24
  "test": "vitest run"
25
25
  },
26
26
  "dependencies": {
27
- "@modelcontextprotocol/sdk": "^1.30.1",
27
+ "@modelcontextprotocol/sdk": "^1.32.1",
28
28
  "diff": "^9.0.0",
29
- "fast-glob": "^3.3.3",
30
29
  "ink": "^7.1.1",
30
+ "openai": "^7.27.0",
31
31
  "react": "^19.3.0",
32
+ "undici": "^7.30.1",
33
+ "ws": "^8.22.0",
34
+ "yaml": "^2.9.1",
32
35
  "zod": "^4.6.5"
33
36
  },
34
37
  "devDependencies": {
35
38
  "@biomejs/biome": "^2.5.14",
36
39
  "@types/node": "^26.6.3",
37
40
  "@types/react": "^19.3.0",
41
+ "@types/ws": "^8.18.2",
38
42
  "ink-testing-library": "^4.0.0",
39
43
  "tsup": "^8.5.1",
40
44
  "typescript": "^7.0.2",
41
45
  "vitest": "^5.0.2"
42
- }
46
+ },
47
+ "overrides": {
48
+ "esbuild": "0.28.2"
49
+ },
50
+ "main": "dist/sdk.js",
51
+ "exports": {
52
+ ".": "./dist/sdk.js"
53
+ },
54
+ "directories": {
55
+ "doc": "docs",
56
+ "test": "test"
57
+ },
58
+ "keywords": [],
59
+ "author": "",
60
+ "bugs": {
61
+ "url": "https://github.com/farijarifriyanto-debug/bccli/issues"
62
+ },
63
+ "homepage": "https://github.com/farijarifriyanto-debug/bccli#readme"
43
64
  }
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- HELP_TEXT,
4
- parseCliArgs
5
- } from "./chunk-Z7PM3LTC.js";
6
- import "./chunk-ELML4KVQ.js";
7
- import "./chunk-CLN4Z5E3.js";
8
- import "./chunk-2MGINW4O.js";
9
- export {
10
- HELP_TEXT,
11
- parseCliArgs
12
- };
@@ -1,114 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/presets.ts
4
- var PRESETS = [
5
- { id: "bc-cloud", name: "BotConnector Cloud", baseURL: "https://api.botconnector.id/v1", apiKeyEnv: "BOTCONNECTOR_API_KEY" },
6
- { id: "openrouter", name: "OpenRouter", baseURL: "https://openrouter.ai/api/v1", apiKeyEnv: "OPENROUTER_API_KEY" },
7
- { id: "openai", name: "OpenAI", baseURL: "https://api.openai.com/v1", apiKeyEnv: "OPENAI_API_KEY" },
8
- { id: "gemini", name: "Google Gemini", baseURL: "https://generativelanguage.googleapis.com/v1beta/openai", apiKeyEnv: "GEMINI_API_KEY" },
9
- { id: "deepseek", name: "DeepSeek", baseURL: "https://api.deepseek.com/v1", apiKeyEnv: "DEEPSEEK_API_KEY" },
10
- { id: "groq", name: "Groq", baseURL: "https://api.groq.com/openai/v1", apiKeyEnv: "GROQ_API_KEY" },
11
- { id: "ollama-cloud", name: "Ollama Cloud", baseURL: "https://ollama.com/v1", apiKeyEnv: "OLLAMA_API_KEY" },
12
- { id: "ollama", name: "Ollama lokal", baseURL: "http://127.0.0.1:11434/v1", local: true },
13
- { id: "lmstudio", name: "LM Studio lokal", baseURL: "http://127.0.0.1:1234/v1", local: true }
14
- ];
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
- reasoning: "auto",
26
- providers: Object.fromEntries(PRESETS.map((p) => [p.id, { baseURL: p.baseURL, apiKeyEnv: p.apiKeyEnv }])),
27
- allow: []
28
- };
29
- function bccliHome(env = process.env) {
30
- return env.BCCLI_HOME ?? join(homedir(), ".bccli");
31
- }
32
- function readJsonConfig(path) {
33
- if (!existsSync(path)) return {};
34
- try {
35
- return JSON.parse(readFileSync(path, "utf8"));
36
- } catch (error) {
37
- throw new ConfigError(`Config tidak valid: ${path} (${error.message})`);
38
- }
39
- }
40
- function untrustedProviders(project, known) {
41
- const out = {};
42
- for (const [id, provider] of Object.entries(project.providers ?? {})) {
43
- if (known[id] || typeof provider?.baseURL !== "string") continue;
44
- out[id] = { baseURL: provider.baseURL };
45
- }
46
- return out;
47
- }
48
- function loadConfig(cwd, env = process.env) {
49
- const global = readJsonConfig(join(bccliHome(env), "config.json"));
50
- const project = readJsonConfig(join(cwd, ".bccli", "config.json"));
51
- const known = { ...DEFAULT_CONFIG.providers, ...global.providers };
52
- const fromProject = untrustedProviders(project, known);
53
- return {
54
- model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
55
- permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
56
- reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
57
- providers: { ...known, ...fromProject },
58
- projectProviders: Object.keys(fromProject),
59
- allow: [...global.allow ?? []]
60
- };
61
- }
62
- function readCredentials(env = process.env) {
63
- const path = join(bccliHome(env), "credentials");
64
- if (!existsSync(path)) return {};
65
- try {
66
- return JSON.parse(readFileSync(path, "utf8"));
67
- } catch {
68
- return {};
69
- }
70
- }
71
- function saveCredential(providerId, key, env = process.env) {
72
- const home = bccliHome(env);
73
- mkdirSync(home, { recursive: true });
74
- const path = join(home, "credentials");
75
- writeFileSync(path, JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), { mode: 384 });
76
- chmodSync(path, 384);
77
- }
78
- function resolveModel(config, modelRef, env = process.env) {
79
- const slash = modelRef.indexOf("/");
80
- if (slash <= 0 || slash === modelRef.length - 1) {
81
- throw new ConfigError(`Model harus berformat provider/model, contoh bc-cloud/glm-5.3-flash (dapat: ${modelRef})`);
82
- }
83
- const providerId = modelRef.slice(0, slash);
84
- const provider = config.providers[providerId];
85
- if (!provider) {
86
- throw new ConfigError(`Provider "${providerId}" tidak ada di config. Tersedia: ${Object.keys(config.providers).join(", ")}`);
87
- }
88
- const apiKey = (provider.apiKeyEnv ? env[provider.apiKeyEnv] : void 0) || readCredentials(env)[providerId] || void 0;
89
- if (provider.apiKeyEnv && !apiKey) {
90
- throw new ConfigError(`API key untuk ${providerId} belum ada. Jalankan \`bccli login\` atau set env ${provider.apiKeyEnv}.`);
91
- }
92
- return { providerId, model: modelRef.slice(slash + 1), baseURL: provider.baseURL.replace(/\/+$/, ""), apiKey };
93
- }
94
- function removeCredential(providerId, env = process.env) {
95
- const creds = readCredentials(env);
96
- if (!(providerId in creds)) return false;
97
- delete creds[providerId];
98
- const path = join(bccliHome(env), "credentials");
99
- writeFileSync(path, JSON.stringify(creds, null, 2), { mode: 384 });
100
- chmodSync(path, 384);
101
- return true;
102
- }
103
-
104
- export {
105
- PRESETS,
106
- ConfigError,
107
- bccliHome,
108
- readJsonConfig,
109
- loadConfig,
110
- readCredentials,
111
- saveCredential,
112
- resolveModel,
113
- removeCredential
114
- };
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/login.ts
4
- function readSecret(prompt) {
5
- return new Promise((resolve) => {
6
- process.stdout.write(prompt);
7
- const stdin = process.stdin;
8
- if (!stdin.isTTY) {
9
- let data = "";
10
- stdin.on("data", (d) => {
11
- data += d;
12
- });
13
- stdin.on("end", () => resolve(data.trim()));
14
- return;
15
- }
16
- let value = "";
17
- stdin.setRawMode(true);
18
- stdin.resume();
19
- const onData = (buffer) => {
20
- for (const ch of buffer.toString()) {
21
- if (ch === "\r" || ch === "\n") {
22
- stdin.setRawMode(false);
23
- stdin.pause();
24
- stdin.off("data", onData);
25
- process.stdout.write("\n");
26
- resolve(value.trim());
27
- return;
28
- }
29
- if (ch === "") process.exit(130);
30
- if (ch === "\x7F" || ch === "\b") value = value.slice(0, -1);
31
- else value += ch;
32
- }
33
- };
34
- stdin.on("data", onData);
35
- });
36
- }
37
- async function runLogin(providerId, cwd) {
38
- const { runProviderCommand } = await import("./providerCli-Q7LRM2NP.js");
39
- const { parseCliArgs } = await import("./args-FLTRM57K.js");
40
- return runProviderCommand(parseCliArgs(["provider", "add", providerId]), {
41
- env: process.env,
42
- cwd,
43
- out: (s) => console.log(s),
44
- err: (s) => console.error(s),
45
- readSecret
46
- });
47
- }
48
-
49
- export {
50
- readSecret,
51
- runLogin
52
- };
@@ -1,254 +0,0 @@
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
- };