@botconnector/bccli 0.4.0-beta.35 → 0.4.0-beta.37
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 +1 -0
- package/dist/args-MNVI2OJI.js +13 -0
- package/dist/chunk-3HCOAJEQ.js +190 -0
- package/dist/{chunk-SJH3YLW4.js → chunk-3JRBXJVB.js} +4 -1
- package/dist/{chunk-IJGTX7RC.js → chunk-4RMP4CNT.js} +53 -118
- package/dist/{chunk-GJAX4NCM.js → chunk-7TABTC32.js} +44 -118
- package/dist/{chunk-NM3C75BT.js → chunk-ASTNW3X2.js} +18 -6
- package/dist/chunk-BB4CVWHS.js +115 -0
- package/dist/{chunk-FNBTY56Z.js → chunk-FCQCZCKO.js} +2 -2
- package/dist/chunk-H5N4ST6B.js +149 -0
- package/dist/{chunk-GEG6QT5H.js → chunk-HPZSXX4W.js} +6 -2
- package/dist/chunk-HXSREA7S.js +785 -0
- package/dist/{chunk-ELML4KVQ.js → chunk-R3ND76LW.js} +16 -2
- package/dist/{chunk-CDUPYHNL.js → chunk-TASYQW7B.js} +23 -2
- package/dist/{chunk-AOVIQXXO.js → chunk-U66JJ6RN.js} +1 -1
- package/dist/{chunk-UJH2JT6H.js → chunk-YURAIW7A.js} +1 -1
- package/dist/cli.js +637 -293
- package/dist/{config-DMF4AO7E.js → config-JRO5EOKZ.js} +5 -2
- package/dist/{integrationCli-W5LJQJ4J.js → integrationCli-ZTZTQD2I.js} +105 -6
- package/dist/{login-7IARC5WX.js → login-MHVCON5R.js} +1 -1
- package/dist/{mcpCli-UGNEDBMU.js → mcpCli-5ZSAMBJS.js} +33 -5
- package/dist/providerCli-Z3TZ5XSZ.js +14 -0
- package/dist/session-IYVTCUBC.js +12 -0
- package/dist/{ui-KK6ARPCL.js → ui-YLIKFLGN.js} +93 -39
- package/package.json +2 -1
- package/dist/args-EFFR44JW.js +0 -12
- package/dist/chunk-LRMC4AOV.js +0 -294
- package/dist/providerCli-4JTIW3YA.js +0 -12
- package/dist/session-7UB77CEG.js +0 -9
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
addGlobalServer,
|
|
4
|
+
expandEnvVars,
|
|
4
5
|
globalMcpPath,
|
|
5
6
|
projectMcpPath,
|
|
6
7
|
projectTrust,
|
|
@@ -8,10 +9,12 @@ import {
|
|
|
8
9
|
removeGlobalServer,
|
|
9
10
|
serversToStart,
|
|
10
11
|
setProjectTrust
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-TASYQW7B.js";
|
|
13
|
+
import "./chunk-H5N4ST6B.js";
|
|
14
|
+
import "./chunk-4RMP4CNT.js";
|
|
13
15
|
export {
|
|
14
16
|
addGlobalServer,
|
|
17
|
+
expandEnvVars,
|
|
15
18
|
globalMcpPath,
|
|
16
19
|
projectMcpPath,
|
|
17
20
|
projectTrust,
|
|
@@ -4,9 +4,11 @@ import {
|
|
|
4
4
|
bccliHome,
|
|
5
5
|
loadConfig,
|
|
6
6
|
readCredentials,
|
|
7
|
-
resolveModel
|
|
7
|
+
resolveModel
|
|
8
|
+
} from "./chunk-H5N4ST6B.js";
|
|
9
|
+
import {
|
|
8
10
|
t
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-4RMP4CNT.js";
|
|
10
12
|
|
|
11
13
|
// src/integrationCli.ts
|
|
12
14
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
@@ -23,11 +25,15 @@ function statePath(target, env) {
|
|
|
23
25
|
function keyPath(env) {
|
|
24
26
|
return join(integrationHome(env), "bc-cloud.key");
|
|
25
27
|
}
|
|
28
|
+
var OPENCLAW_ENV_ID = "BOTCONNECTOR_API_KEY";
|
|
29
|
+
var HERMES_ENV_ID = "BOTCONNECTOR_API_KEY";
|
|
30
|
+
var HERMES_PROVIDER_ID = "botconnector-cloud";
|
|
31
|
+
var FALLBACK_DEFAULT_MODEL = "glm-5.3-flash";
|
|
26
32
|
function parseTarget(raw) {
|
|
27
|
-
if (raw === "opencode" || raw === "aider" || raw === "cline" || raw === "deepseek-harness" || raw === "cursor" || raw === "openai-cli" || raw === "openai-sdk" || raw === "openai-compatible" || raw === "codex" || raw === "claude-code") return raw;
|
|
33
|
+
if (raw === "opencode" || raw === "aider" || raw === "cline" || raw === "deepseek-harness" || raw === "cursor" || raw === "openai-cli" || raw === "openai-sdk" || raw === "openai-compatible" || raw === "codex" || raw === "claude-code" || raw === "openclaw" || raw === "hermes") return raw;
|
|
28
34
|
if (raw === "dsh") return "deepseek-harness";
|
|
29
35
|
throw new ConfigError(
|
|
30
|
-
t("Agent must be one of: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code")
|
|
36
|
+
t("Agent must be one of: opencode, aider, cline, deepseek-harness (dsh), cursor, openai-cli, openai-sdk, openai-compatible, codex, claude-code, openclaw, hermes")
|
|
31
37
|
);
|
|
32
38
|
}
|
|
33
39
|
function homeDir(env) {
|
|
@@ -83,7 +89,9 @@ async function botConnectorModels(deps) {
|
|
|
83
89
|
});
|
|
84
90
|
if (!response.ok) throw new ConfigError(t("Could not load the BotConnector model catalog (HTTP {status}).", { status: response.status }));
|
|
85
91
|
const payload = await response.json();
|
|
86
|
-
|
|
92
|
+
const models = (payload.data ?? []).map((item) => item.id).filter((id) => typeof id === "string" && !!id);
|
|
93
|
+
if (!models.length) throw new ConfigError(t("The BotConnector model catalog is empty; cannot configure external agents."));
|
|
94
|
+
return models;
|
|
87
95
|
}
|
|
88
96
|
async function connectOpenCode(deps) {
|
|
89
97
|
const home = homeDir(deps.env);
|
|
@@ -135,6 +143,15 @@ function stripManagedBlock(input) {
|
|
|
135
143
|
return right ? `${left}
|
|
136
144
|
${right}` : left;
|
|
137
145
|
}
|
|
146
|
+
function externalAgentDefaultModel(models, deps) {
|
|
147
|
+
try {
|
|
148
|
+
const config = loadConfig(deps.cwd, deps.env);
|
|
149
|
+
const preferred = config.model.startsWith("bc-cloud/") ? config.model.slice("bc-cloud/".length) : "";
|
|
150
|
+
if (preferred && models.includes(preferred)) return preferred;
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
return models.includes(FALLBACK_DEFAULT_MODEL) ? FALLBACK_DEFAULT_MODEL : models[0];
|
|
154
|
+
}
|
|
138
155
|
function yamlQuote(value) {
|
|
139
156
|
return JSON.stringify(value.replace(/\\/g, "/"));
|
|
140
157
|
}
|
|
@@ -537,6 +554,86 @@ async function connectClaudeCode(deps) {
|
|
|
537
554
|
deps.out(`Launcher: ${launcher}`);
|
|
538
555
|
deps.out(`Default coding model: ${model}`);
|
|
539
556
|
}
|
|
557
|
+
async function connectOpenClaw(deps) {
|
|
558
|
+
const home = homeDir(deps.env);
|
|
559
|
+
const clawHome = deps.env.OPENCLAW_CONFIG_DIR || join(home, ".openclaw");
|
|
560
|
+
const path = join(clawHome, "openclaw.json");
|
|
561
|
+
const backup = safeBackup(path, deps.env);
|
|
562
|
+
ensureSecret(deps.env);
|
|
563
|
+
const models = await botConnectorModels(deps);
|
|
564
|
+
const model = externalAgentDefaultModel(models, deps);
|
|
565
|
+
let doc = {};
|
|
566
|
+
if (existsSync(path)) {
|
|
567
|
+
try {
|
|
568
|
+
doc = JSON.parse(readFileSync(path, "utf8"));
|
|
569
|
+
} catch {
|
|
570
|
+
throw new ConfigError(t("The OpenClaw config is not valid JSON: {path}", { path }));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
const modelsRoot = doc.models && typeof doc.models === "object" && !Array.isArray(doc.models) ? { ...doc.models } : {};
|
|
574
|
+
const providers = modelsRoot.providers && typeof modelsRoot.providers === "object" && !Array.isArray(modelsRoot.providers) ? { ...modelsRoot.providers } : {};
|
|
575
|
+
const existing = providers[PROVIDER_ID];
|
|
576
|
+
if (existing?.baseUrl && existing.baseUrl.replace(/\/+$/, "") !== BASE_URL) {
|
|
577
|
+
throw new ConfigError(t('OpenClaw already has a provider "{id}" with a different endpoint ({base}). BCCLI will not overwrite it; disconnect or change that provider first.', { id: PROVIDER_ID, base: existing.baseUrl }));
|
|
578
|
+
}
|
|
579
|
+
const secretRef = { source: "env", provider: PROVIDER_ID, id: OPENCLAW_ENV_ID };
|
|
580
|
+
providers[PROVIDER_ID] = {
|
|
581
|
+
baseUrl: BASE_URL,
|
|
582
|
+
api: "openai-completions",
|
|
583
|
+
apiKey: secretRef,
|
|
584
|
+
// Live catalog ids only; OpenClaw resolves provider capabilities itself.
|
|
585
|
+
models: models.map((id) => ({ id, name: id }))
|
|
586
|
+
};
|
|
587
|
+
modelsRoot.providers = providers;
|
|
588
|
+
doc.models = modelsRoot;
|
|
589
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
590
|
+
writeFileSync(path, `${JSON.stringify(doc, null, 2)}
|
|
591
|
+
`);
|
|
592
|
+
writeState("openclaw", {
|
|
593
|
+
target: "openclaw",
|
|
594
|
+
files: [{ path, ...backup }],
|
|
595
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
596
|
+
}, deps.env);
|
|
597
|
+
deps.out(t("OpenClaw is connected to BotConnector ({n} models; default {model}).", { n: models.length, model }));
|
|
598
|
+
deps.out(`Config: ${path}`);
|
|
599
|
+
deps.out(t("API key: env {env} (SecretRef, not stored in the config).", { env: OPENCLAW_ENV_ID }));
|
|
600
|
+
deps.out(t("Set the default model in OpenClaw with: openclaw models set {provider}/{model}", { provider: PROVIDER_ID, model }));
|
|
601
|
+
}
|
|
602
|
+
async function connectHermes(deps) {
|
|
603
|
+
const home = homeDir(deps.env);
|
|
604
|
+
const hermesHome = deps.env.HERMES_HOME || join(home, ".hermes");
|
|
605
|
+
const path = join(hermesHome, "config.yaml");
|
|
606
|
+
const backup = safeBackup(path, deps.env);
|
|
607
|
+
ensureSecret(deps.env);
|
|
608
|
+
const models = await botConnectorModels(deps);
|
|
609
|
+
const model = externalAgentDefaultModel(models, deps);
|
|
610
|
+
const source = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
611
|
+
const doc = parseDocument(source);
|
|
612
|
+
if (doc.errors.length) throw new ConfigError(t("The Hermes config is not valid YAML: {path}", { path }));
|
|
613
|
+
const previousProvider = doc.getIn(["providers", HERMES_PROVIDER_ID]);
|
|
614
|
+
if (previousProvider?.api && previousProvider.api.replace(/\/+$/, "") !== BASE_URL) {
|
|
615
|
+
throw new ConfigError(t('Hermes already has a provider "{id}" with a different endpoint ({base}). BCCLI will not overwrite it; disconnect or change that provider first.', { id: HERMES_PROVIDER_ID, base: previousProvider.api }));
|
|
616
|
+
}
|
|
617
|
+
doc.setIn(["providers", HERMES_PROVIDER_ID], {
|
|
618
|
+
api: BASE_URL,
|
|
619
|
+
key_env: HERMES_ENV_ID,
|
|
620
|
+
transport: "chat_completions",
|
|
621
|
+
default_model: model,
|
|
622
|
+
discover_models: true,
|
|
623
|
+
models: Object.fromEntries(models.map((id) => [id, {}]))
|
|
624
|
+
});
|
|
625
|
+
doc.setIn(["model"], { default: model, provider: `custom:${HERMES_PROVIDER_ID}` });
|
|
626
|
+
mkdirSync(hermesHome, { recursive: true });
|
|
627
|
+
writeFileSync(path, doc.toString());
|
|
628
|
+
writeState("hermes", {
|
|
629
|
+
target: "hermes",
|
|
630
|
+
files: [{ path, ...backup }],
|
|
631
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
632
|
+
}, deps.env);
|
|
633
|
+
deps.out(t("Hermes is connected to BotConnector ({n} models; default {model}).", { n: models.length, model }));
|
|
634
|
+
deps.out(`Config: ${path}`);
|
|
635
|
+
deps.out(t("API key: env {env} only (via key_env; a plaintext key in config.yaml is never written).", { env: HERMES_ENV_ID }));
|
|
636
|
+
}
|
|
540
637
|
function disconnect(target, deps) {
|
|
541
638
|
const state = readState(target, deps.env);
|
|
542
639
|
if (!state) {
|
|
@@ -555,7 +652,7 @@ function disconnect(target, deps) {
|
|
|
555
652
|
deps.out(t("{target}: the BotConnector integration was removed and the config restored.", { target }));
|
|
556
653
|
}
|
|
557
654
|
function listIntegrations(deps) {
|
|
558
|
-
for (const target of ["opencode", "aider", "cline", "deepseek-harness", "cursor", "openai-cli", "openai-sdk", "openai-compatible", "codex", "claude-code"]) {
|
|
655
|
+
for (const target of ["opencode", "aider", "cline", "deepseek-harness", "cursor", "openai-cli", "openai-sdk", "openai-compatible", "codex", "claude-code", "openclaw", "hermes"]) {
|
|
559
656
|
deps.out(`${target} ${readState(target, deps.env) ? "connected" : "not connected"}`);
|
|
560
657
|
}
|
|
561
658
|
}
|
|
@@ -578,6 +675,8 @@ async function runIntegrationCommand(args, deps) {
|
|
|
578
675
|
else if (target === "openai-sdk") await connectOpenAiSdk(deps);
|
|
579
676
|
else if (target === "openai-compatible") await connectOpenAiCompatible(deps);
|
|
580
677
|
else if (target === "codex") await connectCodex(deps);
|
|
678
|
+
else if (target === "openclaw") await connectOpenClaw(deps);
|
|
679
|
+
else if (target === "hermes") await connectHermes(deps);
|
|
581
680
|
else await connectClaudeCode(deps);
|
|
582
681
|
return 0;
|
|
583
682
|
}
|
|
@@ -2,17 +2,23 @@
|
|
|
2
2
|
import {
|
|
3
3
|
CATALOG,
|
|
4
4
|
fillTemplate
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HPZSXX4W.js";
|
|
6
|
+
import {
|
|
7
|
+
authorizeMcpServer,
|
|
8
|
+
clearTokens
|
|
9
|
+
} from "./chunk-3HCOAJEQ.js";
|
|
6
10
|
import {
|
|
7
11
|
addGlobalServer,
|
|
8
12
|
globalMcpPath,
|
|
9
13
|
readMcpFile,
|
|
10
14
|
removeGlobalServer
|
|
11
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-TASYQW7B.js";
|
|
16
|
+
import {
|
|
17
|
+
bccliHome
|
|
18
|
+
} from "./chunk-H5N4ST6B.js";
|
|
12
19
|
import {
|
|
13
|
-
bccliHome,
|
|
14
20
|
t
|
|
15
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-4RMP4CNT.js";
|
|
16
22
|
|
|
17
23
|
// src/mcpCli.ts
|
|
18
24
|
async function runMcpCommand(args, deps) {
|
|
@@ -33,8 +39,30 @@ async function runMcpCommand(args, deps) {
|
|
|
33
39
|
deps.out(t("{name} removed.", { name }));
|
|
34
40
|
return 0;
|
|
35
41
|
}
|
|
42
|
+
if (action === "auth" || action === "logout") {
|
|
43
|
+
const config = installed[name];
|
|
44
|
+
if (config?.type !== "http") {
|
|
45
|
+
deps.err(
|
|
46
|
+
t("{name} is not an installed HTTP MCP server. Add it first with: bccli mcp add {name} --url <url>", { name })
|
|
47
|
+
);
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
if (action === "logout") {
|
|
51
|
+
const removed = clearTokens(home, config.url);
|
|
52
|
+
deps.out(removed ? t("{name} logged out.", { name }) : t("{name} has no stored OAuth tokens.", { name }));
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await authorizeMcpServer({ name, config }, { home });
|
|
57
|
+
deps.out(t("{name} authorized. Active in the next bccli session.", { name }));
|
|
58
|
+
return 0;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
deps.err(t("OAuth login failed for {name}: {reason}", { name, reason: error.message }));
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
36
64
|
if (action !== "add") {
|
|
37
|
-
deps.err(t("Unknown action: {action}. Use list, add, or
|
|
65
|
+
deps.err(t("Unknown action: {action}. Use list, add, remove, auth, or logout.", { action }));
|
|
38
66
|
return 1;
|
|
39
67
|
}
|
|
40
68
|
if (args.url) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
addProviderKey,
|
|
4
|
+
runProviderCommand
|
|
5
|
+
} from "./chunk-BB4CVWHS.js";
|
|
6
|
+
import "./chunk-U66JJ6RN.js";
|
|
7
|
+
import "./chunk-7TABTC32.js";
|
|
8
|
+
import "./chunk-YURAIW7A.js";
|
|
9
|
+
import "./chunk-H5N4ST6B.js";
|
|
10
|
+
import "./chunk-4RMP4CNT.js";
|
|
11
|
+
export {
|
|
12
|
+
addProviderKey,
|
|
13
|
+
runProviderCommand
|
|
14
|
+
};
|
|
@@ -1,56 +1,66 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
BUILTIN_AGENTS,
|
|
4
|
+
appendMemory,
|
|
4
5
|
ignorePatterns,
|
|
5
|
-
|
|
6
|
+
instructionFiles,
|
|
6
7
|
normalizeGlobPath,
|
|
7
8
|
parseFrontmatter,
|
|
9
|
+
parseMemoryArgs,
|
|
10
|
+
renderBackgroundTasks,
|
|
11
|
+
runHooks,
|
|
8
12
|
toolLabel,
|
|
9
|
-
validateGlobPattern
|
|
10
|
-
|
|
13
|
+
validateGlobPattern,
|
|
14
|
+
worktreeListText
|
|
15
|
+
} from "./chunk-HXSREA7S.js";
|
|
11
16
|
import {
|
|
12
17
|
nextMode
|
|
13
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-R3ND76LW.js";
|
|
14
19
|
import {
|
|
15
20
|
hasKey,
|
|
16
21
|
providerName,
|
|
17
22
|
writeGlobalConfig
|
|
18
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-U66JJ6RN.js";
|
|
24
|
+
import {
|
|
25
|
+
Session
|
|
26
|
+
} from "./chunk-3JRBXJVB.js";
|
|
27
|
+
import {
|
|
28
|
+
contentText
|
|
29
|
+
} from "./chunk-7TABTC32.js";
|
|
19
30
|
import {
|
|
20
31
|
REASONING_LEVELS,
|
|
21
32
|
assertReasoningSupported,
|
|
22
33
|
parseReasoningLevel,
|
|
23
34
|
supportedReasoningLevels
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import {
|
|
26
|
-
Session
|
|
27
|
-
} from "./chunk-SJH3YLW4.js";
|
|
35
|
+
} from "./chunk-YURAIW7A.js";
|
|
28
36
|
import {
|
|
29
37
|
CATALOG,
|
|
30
38
|
fillTemplate
|
|
31
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-HPZSXX4W.js";
|
|
32
40
|
import {
|
|
33
41
|
addGlobalServer,
|
|
34
42
|
globalMcpPath,
|
|
35
43
|
readMcpFile,
|
|
36
44
|
removeGlobalServer
|
|
37
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-TASYQW7B.js";
|
|
38
46
|
import {
|
|
39
47
|
PRESETS,
|
|
48
|
+
removeCredential
|
|
49
|
+
} from "./chunk-H5N4ST6B.js";
|
|
50
|
+
import {
|
|
40
51
|
getLanguage,
|
|
41
52
|
locale,
|
|
42
53
|
parseLang,
|
|
43
|
-
removeCredential,
|
|
44
54
|
setLanguage,
|
|
45
55
|
t
|
|
46
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-4RMP4CNT.js";
|
|
47
57
|
|
|
48
58
|
// src/ui/index.tsx
|
|
49
59
|
import { render } from "ink";
|
|
50
60
|
|
|
51
61
|
// src/ui/App.tsx
|
|
52
62
|
import { execFile as execFile2 } from "child_process";
|
|
53
|
-
import { existsSync
|
|
63
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
54
64
|
import { homedir } from "os";
|
|
55
65
|
import { dirname, join as join2, relative, resolve } from "path";
|
|
56
66
|
import { Box as Box13, Static, Text as Text16, useApp, useInput as useInput9 } from "ink";
|
|
@@ -69,7 +79,10 @@ var SLASH_COMMANDS = [
|
|
|
69
79
|
{ name: "status", description: "version, model, permission mode, MCP, context" },
|
|
70
80
|
{ name: "permissions", description: "view/revoke permissions" },
|
|
71
81
|
{ name: "undo", description: "undo the file edits of the last turn" },
|
|
82
|
+
{ name: "rewind", description: "step back turns: conversation and file edits (/rewind 2)" },
|
|
72
83
|
{ name: "diff", description: "git diff of the project" },
|
|
84
|
+
{ name: "worktree", description: "list git worktrees; create one with bccli -w <name>" },
|
|
85
|
+
{ name: "pr", description: "review a pull request (/pr <n|url|branch>)" },
|
|
73
86
|
{ name: "copy", description: "copy the last answer" },
|
|
74
87
|
{ name: "export", description: "save the conversation as markdown" },
|
|
75
88
|
{ name: "memory", description: "project instructions (/memory <text>, /memory global <text>)" },
|
|
@@ -83,6 +96,7 @@ var SLASH_COMMANDS = [
|
|
|
83
96
|
{ name: "clear", description: "start a new conversation" },
|
|
84
97
|
{ name: "compact", description: "summarize the conversation" },
|
|
85
98
|
{ name: "cost", description: "token usage of this session" },
|
|
99
|
+
{ name: "tasks", description: "list background tasks" },
|
|
86
100
|
{ name: "exit", description: "quit" }
|
|
87
101
|
];
|
|
88
102
|
function parseSlash(text) {
|
|
@@ -96,6 +110,14 @@ function expandCommand(def, args) {
|
|
|
96
110
|
|
|
97
111
|
${args}` : def.body;
|
|
98
112
|
}
|
|
113
|
+
function prReviewPrompt(selector) {
|
|
114
|
+
const target = selector.trim();
|
|
115
|
+
const refs = target ? `Use "${target}" as the PR selector: run gh pr view ${target} --json url,title,body,baseRefName,headRefName,files,additions,deletions and gh pr diff ${target}.` : "Start with `gh pr view --json url,title,baseRefName,headRefName` for the PR of the current branch, then fetch the change with `gh pr diff`. If the branch has no open PR, review its changes against the default branch instead: find the default branch (`gh repo view --json defaultBranchRef` or `git remote show origin`), then `git diff origin/<default-branch>...HEAD`.";
|
|
116
|
+
return `Review this pull request like a senior reviewer.
|
|
117
|
+
${refs}
|
|
118
|
+
Read the changed files in the repository when the diff alone is not enough to judge the change.
|
|
119
|
+
Write the review as: a 2-3 sentence summary; concrete problems (bugs, security, race conditions, error handling), each with file:line and why it matters; missing or weak tests; smaller style/nit issues; then a final verdict \u2014 approve, request changes, or comment \u2014 with the single most important reason.`;
|
|
120
|
+
}
|
|
99
121
|
|
|
100
122
|
// src/slash/copy.ts
|
|
101
123
|
function osc52(text, inTmux) {
|
|
@@ -156,7 +178,7 @@ async function doctorText(input, deps) {
|
|
|
156
178
|
function exportMarkdown(messages, title) {
|
|
157
179
|
const parts = [`# ${title}`, ""];
|
|
158
180
|
for (const m of messages) {
|
|
159
|
-
if (m.role === "user") parts.push("## User", "",
|
|
181
|
+
if (m.role === "user") parts.push("## User", "", contentText(m.content), "");
|
|
160
182
|
else if (m.role === "assistant") {
|
|
161
183
|
for (const c of m.tool_calls ?? []) parts.push(`\u23BF ${c.function.name} ${c.function.arguments.slice(0, 200)}`, "");
|
|
162
184
|
if (m.content) parts.push("## BCCLI", "", m.content, "");
|
|
@@ -202,21 +224,6 @@ function sessionText(s) {
|
|
|
202
224
|
].join("\n");
|
|
203
225
|
}
|
|
204
226
|
|
|
205
|
-
// src/slash/memory.ts
|
|
206
|
-
import { appendFileSync, existsSync, readFileSync } from "fs";
|
|
207
|
-
function instructionFiles(cwd, home) {
|
|
208
|
-
return instructionPaths(cwd, home).filter((path) => existsSync(path)).map((path) => ({ path, lines: readFileSync(path, "utf8").split("\n").filter(Boolean).length }));
|
|
209
|
-
}
|
|
210
|
-
function appendMemory(file, text) {
|
|
211
|
-
const prefix = existsSync(file) && !readFileSync(file, "utf8").endsWith("\n") ? "\n" : "";
|
|
212
|
-
appendFileSync(file, `${prefix}- ${text.trim()}
|
|
213
|
-
`);
|
|
214
|
-
}
|
|
215
|
-
function parseMemoryArgs(args) {
|
|
216
|
-
const match = /^global(?:\s+|$)/i.exec(args.trim());
|
|
217
|
-
return match ? { global: true, text: args.trim().slice(match[0].length).trim() } : { global: false, text: args.trim() };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
227
|
// src/ui/Markdown.tsx
|
|
221
228
|
import { Box, Text, useStdout } from "ink";
|
|
222
229
|
|
|
@@ -1180,7 +1187,7 @@ var nextId = 0;
|
|
|
1180
1187
|
var entry = (e) => ({ ...e, id: nextId++ });
|
|
1181
1188
|
var NOTICES = {
|
|
1182
1189
|
compacted: ["Conversation summarized to fit the model context.", "info"],
|
|
1183
|
-
stepLimit: ['Step limit of
|
|
1190
|
+
stepLimit: ['Step limit of {n} reached. Type "continue" to keep going.', "warn"],
|
|
1184
1191
|
aborted: ["Cancelled.", "warn"]
|
|
1185
1192
|
};
|
|
1186
1193
|
var isEmptyCont = (e) => e.kind === "assistant" && e.cont && !e.text;
|
|
@@ -1255,6 +1262,11 @@ function applyEvent(t2, event) {
|
|
|
1255
1262
|
}
|
|
1256
1263
|
case "error":
|
|
1257
1264
|
return { ...t2, live: [...t2.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
|
|
1265
|
+
case "stepLimit": {
|
|
1266
|
+
const limit = event.maxSteps;
|
|
1267
|
+
const notice = NOTICES.stepLimit;
|
|
1268
|
+
return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0], { n: limit }), tone: notice[1] })] } : t2;
|
|
1269
|
+
}
|
|
1258
1270
|
default: {
|
|
1259
1271
|
const notice = NOTICES[event.type];
|
|
1260
1272
|
return notice ? { ...t2, live: [...t2.live, entry({ kind: "notice", text: t(notice[0]), tone: notice[1] })] } : t2;
|
|
@@ -1326,7 +1338,13 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1326
1338
|
runtime.agent.onEvent = onEvent;
|
|
1327
1339
|
runtime.agent.askPermission = (request) => new Promise((resolve2) => setAsks((q) => [...q, { id: askId.current++, request, resolve: resolve2 }]));
|
|
1328
1340
|
runtime.interaction.approvePlan = (plan) => new Promise((resolve2) => setPlanAsk({ plan, resolve: resolve2 }));
|
|
1329
|
-
|
|
1341
|
+
runHooks(runtime.config.hooks, "SessionStart", {}, { env: runtime.env, cwd: runtime.cwd }).then((outcome) => {
|
|
1342
|
+
for (const w of outcome.warnings) notice(w, "warn");
|
|
1343
|
+
}).catch(() => {
|
|
1344
|
+
});
|
|
1345
|
+
runtime.emitPlugins("SessionStart").catch(() => {
|
|
1346
|
+
});
|
|
1347
|
+
}, [runtime, onEvent, notice]);
|
|
1330
1348
|
const runTurn = useCallback(
|
|
1331
1349
|
async (text) => {
|
|
1332
1350
|
setTranscript((t2) => ({ ...t2, done: [...t2.done, entry({ kind: "user", text })] }));
|
|
@@ -1335,11 +1353,17 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1335
1353
|
controller.current = new AbortController();
|
|
1336
1354
|
await runtime.agent.run(text, controller.current.signal);
|
|
1337
1355
|
controller.current = null;
|
|
1356
|
+
runHooks(runtime.config.hooks, "Stop", {}, { env: runtime.env, cwd: runtime.cwd }).then((outcome) => {
|
|
1357
|
+
for (const w of outcome.warnings) notice(w, "warn");
|
|
1358
|
+
}).catch(() => {
|
|
1359
|
+
});
|
|
1360
|
+
runtime.emitPlugins("Stop").catch(() => {
|
|
1361
|
+
});
|
|
1338
1362
|
setTranscript(endTurn);
|
|
1339
1363
|
setBusy(false);
|
|
1340
1364
|
setCancelling(false);
|
|
1341
1365
|
},
|
|
1342
|
-
[runtime]
|
|
1366
|
+
[runtime, notice]
|
|
1343
1367
|
);
|
|
1344
1368
|
const runSlashCommand = useCallback(
|
|
1345
1369
|
async (name, args) => {
|
|
@@ -1360,6 +1384,9 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1360
1384
|
notice(t("{input} tokens in \xB7 {output} tokens out", { input: u.inputTokens.toLocaleString(locale()), output: u.outputTokens.toLocaleString(locale()) }));
|
|
1361
1385
|
return;
|
|
1362
1386
|
}
|
|
1387
|
+
case "tasks":
|
|
1388
|
+
notice(renderBackgroundTasks());
|
|
1389
|
+
return;
|
|
1363
1390
|
case "compact": {
|
|
1364
1391
|
setBusy(true);
|
|
1365
1392
|
setStartedAt(Date.now());
|
|
@@ -1476,7 +1503,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1476
1503
|
if (!chosen) return;
|
|
1477
1504
|
runtime.resume(chosen.session);
|
|
1478
1505
|
setTokens(0);
|
|
1479
|
-
const chat = runtime.agent.messages.filter(
|
|
1506
|
+
const chat = runtime.agent.messages.map((m) => m.role === "user" && Array.isArray(m.content) ? { ...m, content: contentText(m.content) } : m).filter(
|
|
1480
1507
|
(m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && !!m.content.trim()
|
|
1481
1508
|
);
|
|
1482
1509
|
setTranscript({
|
|
@@ -1650,6 +1677,12 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1650
1677
|
case "diff":
|
|
1651
1678
|
notice(await gitDiff(runtime.cwd));
|
|
1652
1679
|
return;
|
|
1680
|
+
case "worktree":
|
|
1681
|
+
notice(await worktreeListText(runtime.cwd));
|
|
1682
|
+
return;
|
|
1683
|
+
case "pr":
|
|
1684
|
+
void runTurn(prReviewPrompt(args));
|
|
1685
|
+
return;
|
|
1653
1686
|
case "undo": {
|
|
1654
1687
|
const result = await runtime.checkpoints.undo();
|
|
1655
1688
|
if (!result) {
|
|
@@ -1668,6 +1701,25 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1668
1701
|
);
|
|
1669
1702
|
return;
|
|
1670
1703
|
}
|
|
1704
|
+
case "rewind": {
|
|
1705
|
+
const parsed = Number.parseInt(args.trim(), 10);
|
|
1706
|
+
const result = await runtime.rewindTurns(Number.isFinite(parsed) && parsed > 0 ? parsed : 1);
|
|
1707
|
+
if (!result) {
|
|
1708
|
+
notice(t("Nothing to rewind."));
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
const rel = (p) => relative(runtime.cwd, p) || p;
|
|
1712
|
+
notice(
|
|
1713
|
+
[
|
|
1714
|
+
result.failed.length ? t("Could not restore: {files}", { files: result.failed.map(rel).join(", ") }) : "",
|
|
1715
|
+
result.restored.length ? t("Restored: {files}", { files: result.restored.map(rel).join(", ") }) : "",
|
|
1716
|
+
result.deleted.length ? t("Deleted (new files): {files}", { files: result.deleted.map(rel).join(", ") }) : "",
|
|
1717
|
+
result.skipped.length ? t("Too large to back up, left unchanged: {files}", { files: result.skipped.map(rel).join(", ") }) : "",
|
|
1718
|
+
t("Rewound {n} turn(s): the conversation continues from before them.", { n: result.turns })
|
|
1719
|
+
].filter(Boolean).join("\n")
|
|
1720
|
+
);
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1671
1723
|
case "copy": {
|
|
1672
1724
|
const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
|
|
1673
1725
|
if (!last || typeof last.content !== "string") {
|
|
@@ -1684,7 +1736,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1684
1736
|
case "export": {
|
|
1685
1737
|
const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
|
|
1686
1738
|
const file = resolve(runtime.cwd, name2);
|
|
1687
|
-
if (
|
|
1739
|
+
if (existsSync(file)) {
|
|
1688
1740
|
const answer2 = await ask(t("{file} already exists. Overwrite? (y/N)", { file }));
|
|
1689
1741
|
if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
|
|
1690
1742
|
notice(t("Export cancelled."));
|
|
@@ -1732,7 +1784,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
1732
1784
|
else if (skill) {
|
|
1733
1785
|
let body;
|
|
1734
1786
|
try {
|
|
1735
|
-
body = parseFrontmatter(
|
|
1787
|
+
body = parseFrontmatter(readFileSync(skill.file, "utf8")).body;
|
|
1736
1788
|
} catch (error) {
|
|
1737
1789
|
notice(t("Skill {name} cannot be read: {error}", { name: skill.name, error: error.code ?? error.message }), "error");
|
|
1738
1790
|
return;
|
|
@@ -1949,7 +2001,7 @@ ${t2.output}`);
|
|
|
1949
2001
|
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1950
2002
|
async function startInteractive(rt, opts) {
|
|
1951
2003
|
if (opts.resume) {
|
|
1952
|
-
const { Session: Session2 } = await import("./session-
|
|
2004
|
+
const { Session: Session2 } = await import("./session-IYVTCUBC.js");
|
|
1953
2005
|
const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
|
|
1954
2006
|
if (!list.length) console.log(t("No sessions in this folder yet; starting a new one."));
|
|
1955
2007
|
else {
|
|
@@ -1964,7 +2016,7 @@ async function startInteractive(rt, opts) {
|
|
|
1964
2016
|
if (chosen) rt.resume(chosen.session);
|
|
1965
2017
|
}
|
|
1966
2018
|
}
|
|
1967
|
-
const { serversToStart, setProjectTrust } = await import("./config-
|
|
2019
|
+
const { serversToStart, setProjectTrust } = await import("./config-JRO5EOKZ.js");
|
|
1968
2020
|
const plan = serversToStart(rt.home, rt.cwd);
|
|
1969
2021
|
if (plan.needTrust.length) {
|
|
1970
2022
|
const { createInterface } = await import("readline/promises");
|
|
@@ -1977,7 +2029,9 @@ async function startInteractive(rt, opts) {
|
|
|
1977
2029
|
}
|
|
1978
2030
|
rl.close();
|
|
1979
2031
|
}
|
|
1980
|
-
void rt.startMcp(plan.start)
|
|
2032
|
+
void rt.startMcp(plan.start).catch((error) => {
|
|
2033
|
+
console.error(t("Could not start MCP servers: {error}", { error: error instanceof Error ? error.message : String(error) }));
|
|
2034
|
+
});
|
|
1981
2035
|
const instance = render(/* @__PURE__ */ jsx17(App, { runtime: rt, initialPrompt: opts.initialPrompt, version: opts.version }), { exitOnCtrlC: true });
|
|
1982
2036
|
await instance.waitUntilExit();
|
|
1983
2037
|
await rt.mcp.stop();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botconnector/bccli",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.37",
|
|
4
4
|
"description": "BCCLI — BotConnector AI coding agent for the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"ink": "^7.1.1",
|
|
30
30
|
"openai": "^7.27.0",
|
|
31
31
|
"react": "^19.3.0",
|
|
32
|
+
"undici": "^7.28.0",
|
|
32
33
|
"ws": "^8.22.0",
|
|
33
34
|
"yaml": "^2.9.1",
|
|
34
35
|
"zod": "^4.6.5"
|
package/dist/args-EFFR44JW.js
DELETED