@botconnector/bccli 0.2.0-beta.1 → 0.3.0-beta.1
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 +59 -14
- package/dist/args-EVIFNGPQ.js +11 -0
- package/dist/chunk-5C6NQFSI.js +50 -0
- package/dist/{chunk-WZYWKWPD.js → chunk-5XN44Q55.js} +5 -77
- package/dist/chunk-E4BWUMQ2.js +52 -0
- package/dist/chunk-MANM7HY6.js +248 -0
- package/dist/chunk-RDQ7HPWS.js +82 -0
- package/dist/chunk-TBPPXOOZ.js +165 -0
- package/dist/chunk-TV6LEHOH.js +45 -0
- package/dist/chunk-XQLXE666.js +69 -0
- package/dist/chunk-Z5JM76EL.js +111 -0
- package/dist/cli.js +457 -367
- package/dist/config-XPWPGNZJ.js +22 -0
- package/dist/login-IXJD5O7Z.js +9 -0
- package/dist/mcpCli-BV3IGR47.js +65 -0
- package/dist/providerCli-7QNAYIFT.js +11 -0
- package/dist/ui-6PGC4T6N.js +833 -0
- package/package.json +15 -5
- package/dist/ui-U6OMOINK.js +0 -506
package/package.json
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botconnector/bccli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta.1",
|
|
4
4
|
"description": "BCCLI — BotConnector AI coding agent for the terminal",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
6
|
+
"bin": {
|
|
7
|
+
"bccli": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=22"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/farijarifriyanto-debug/bccli.git"
|
|
18
|
+
},
|
|
10
19
|
"license": "MIT",
|
|
11
20
|
"scripts": {
|
|
12
21
|
"build": "tsup",
|
|
@@ -15,6 +24,7 @@
|
|
|
15
24
|
"test": "vitest run"
|
|
16
25
|
},
|
|
17
26
|
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
18
28
|
"diff": "^9.0.0",
|
|
19
29
|
"fast-glob": "^3.3.3",
|
|
20
30
|
"ink": "^7.1.1",
|
package/dist/ui-U6OMOINK.js
DELETED
|
@@ -1,506 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
ignorePatterns,
|
|
4
|
-
nextMode,
|
|
5
|
-
toolLabel
|
|
6
|
-
} from "./chunk-WZYWKWPD.js";
|
|
7
|
-
|
|
8
|
-
// src/ui/index.tsx
|
|
9
|
-
import { render } from "ink";
|
|
10
|
-
|
|
11
|
-
// src/ui/App.tsx
|
|
12
|
-
import { homedir } from "os";
|
|
13
|
-
import { Box as Box7, Static, Text as Text9, useApp, useInput as useInput4 } from "ink";
|
|
14
|
-
import { useCallback, useEffect as useEffect2, useRef, useState as useState4 } from "react";
|
|
15
|
-
|
|
16
|
-
// src/commands.ts
|
|
17
|
-
var SLASH_COMMANDS = [
|
|
18
|
-
{ name: "help", description: "daftar perintah dan pintasan" },
|
|
19
|
-
{ name: "model", description: "ganti model" },
|
|
20
|
-
{ name: "clear", description: "mulai percakapan baru" },
|
|
21
|
-
{ name: "compact", description: "ringkas percakapan" },
|
|
22
|
-
{ name: "cost", description: "pemakaian token sesi ini" },
|
|
23
|
-
{ name: "exit", description: "keluar" }
|
|
24
|
-
];
|
|
25
|
-
function parseSlash(text) {
|
|
26
|
-
if (!text.startsWith("/")) return void 0;
|
|
27
|
-
const [name, ...rest] = text.slice(1).trim().split(/\s+/);
|
|
28
|
-
return { name: name.toLowerCase(), args: rest.join(" ") };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// src/ui/Markdown.tsx
|
|
32
|
-
import { Box, Text } from "ink";
|
|
33
|
-
|
|
34
|
-
// src/ui/theme.ts
|
|
35
|
-
function color(name) {
|
|
36
|
-
return process.env.NO_COLOR ? void 0 : name;
|
|
37
|
-
}
|
|
38
|
-
var ACCENT = "green";
|
|
39
|
-
|
|
40
|
-
// src/ui/Markdown.tsx
|
|
41
|
-
import { jsx } from "react/jsx-runtime";
|
|
42
|
-
function inline(line) {
|
|
43
|
-
return line.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((part, i) => {
|
|
44
|
-
if (part.length > 4 && part.startsWith("**") && part.endsWith("**")) {
|
|
45
|
-
return /* @__PURE__ */ jsx(Text, { bold: true, children: part.slice(2, -2) }, i);
|
|
46
|
-
}
|
|
47
|
-
if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) {
|
|
48
|
-
return /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: part.slice(1, -1) }, i);
|
|
49
|
-
}
|
|
50
|
-
return part;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
function Markdown({ text }) {
|
|
54
|
-
let inCode = false;
|
|
55
|
-
const rows = [];
|
|
56
|
-
text.split("\n").forEach((line, i) => {
|
|
57
|
-
if (line.trimStart().startsWith("```")) {
|
|
58
|
-
inCode = !inCode;
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
rows.push(
|
|
62
|
-
inCode ? /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i) : /* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i)
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// src/ui/ModelPicker.tsx
|
|
69
|
-
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
70
|
-
import { useState } from "react";
|
|
71
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
72
|
-
function ModelPicker({ models, current, onPick }) {
|
|
73
|
-
const [index, setIndex] = useState(Math.max(0, models.indexOf(current)));
|
|
74
|
-
useInput((_input, key) => {
|
|
75
|
-
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
76
|
-
else if (key.downArrow) setIndex((i) => Math.min(models.length - 1, i + 1));
|
|
77
|
-
else if (key.return) onPick(models[index]);
|
|
78
|
-
else if (key.escape) onPick(void 0);
|
|
79
|
-
});
|
|
80
|
-
const start = Math.max(0, Math.min(index - 5, models.length - 10));
|
|
81
|
-
return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
82
|
-
/* @__PURE__ */ jsx2(Text2, { bold: true, children: "Pilih model (\u2191\u2193 enter, esc batal)" }),
|
|
83
|
-
models.slice(start, start + 10).map((m, i) => /* @__PURE__ */ jsx2(Text2, { color: start + i === index ? color("green") : void 0, children: `${start + i === index ? "\u203A" : " "} ${m}${m === current ? " (aktif)" : ""}` }, m))
|
|
84
|
-
] });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/ui/PermissionPrompt.tsx
|
|
88
|
-
import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
|
|
89
|
-
|
|
90
|
-
// src/ui/DiffView.tsx
|
|
91
|
-
import { Box as Box3, Text as Text3 } from "ink";
|
|
92
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
93
|
-
function DiffView({ diff }) {
|
|
94
|
-
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: diff.split("\n").map((line, i) => {
|
|
95
|
-
const mark = line[6];
|
|
96
|
-
const c = mark === "+" ? color("green") : mark === "-" ? color("red") : void 0;
|
|
97
|
-
return /* @__PURE__ */ jsx3(Text3, { color: c, dimColor: mark !== "+" && mark !== "-", children: line }, i);
|
|
98
|
-
}) });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// src/ui/PermissionPrompt.tsx
|
|
102
|
-
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
103
|
-
var VERB = { edit: "edit", write: "tulis", bash: "jalankan", fetch: "ambil" };
|
|
104
|
-
function PermissionPrompt({
|
|
105
|
-
request,
|
|
106
|
-
onAnswer
|
|
107
|
-
}) {
|
|
108
|
-
useInput2((input, key) => {
|
|
109
|
-
const k = input.toLowerCase();
|
|
110
|
-
if (k === "y") onAnswer("yes");
|
|
111
|
-
else if (k === "a" && request.sessionRules?.length) onAnswer("session");
|
|
112
|
-
else if (k === "n" || key.escape) onAnswer("no");
|
|
113
|
-
});
|
|
114
|
-
const rules = request.sessionRules ?? [];
|
|
115
|
-
const sessionLabel = !rules.length ? null : request.kind === "edit" ? "ya untuk semua edit di project ini, sesi ini" : `ya sesi ini untuk ${rules.join(", ")}`;
|
|
116
|
-
return /* @__PURE__ */ jsxs2(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
|
|
117
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, children: `Izinkan ${VERB[request.tool] ?? request.tool} ${request.target}?` }),
|
|
118
|
-
request.preview ? /* @__PURE__ */ jsx4(DiffView, { diff: request.preview }) : null,
|
|
119
|
-
/* @__PURE__ */ jsxs2(Text4, { children: [
|
|
120
|
-
/* @__PURE__ */ jsx4(Text4, { color: color("green"), children: "[y] ya" }),
|
|
121
|
-
" ",
|
|
122
|
-
sessionLabel ? /* @__PURE__ */ jsx4(Text4, { color: color("cyan"), children: `[a] ${sessionLabel} ` }) : null,
|
|
123
|
-
/* @__PURE__ */ jsx4(Text4, { color: color("red"), children: "[n] tidak" })
|
|
124
|
-
] })
|
|
125
|
-
] });
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// src/ui/PromptInput.tsx
|
|
129
|
-
import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
|
|
130
|
-
import { useState as useState2 } from "react";
|
|
131
|
-
|
|
132
|
-
// src/ui/complete.ts
|
|
133
|
-
import fg from "fast-glob";
|
|
134
|
-
function completeFile(value, cwd) {
|
|
135
|
-
const match = /(^|\s)@([^\s]*)$/.exec(value);
|
|
136
|
-
if (!match) return value;
|
|
137
|
-
const prefix = match[2];
|
|
138
|
-
const escaped = prefix.replace(/([*?[\]{}()!])/g, "\\$1");
|
|
139
|
-
const found = fg.sync(`${escaped}*`, { cwd, ignore: ignorePatterns(cwd), onlyFiles: false, markDirectories: true, dot: false }).sort();
|
|
140
|
-
if (!found.length) return value;
|
|
141
|
-
const pick = found[0];
|
|
142
|
-
return `${value.slice(0, value.length - prefix.length)}${pick}${pick.endsWith("/") ? "" : " "}`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// src/ui/PromptInput.tsx
|
|
146
|
-
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
147
|
-
function PromptInput({ disabled, history, cwd, onSubmit }) {
|
|
148
|
-
const [value, setValue] = useState2("");
|
|
149
|
-
const [historyIndex, setHistoryIndex] = useState2(null);
|
|
150
|
-
useInput3(
|
|
151
|
-
(input, key) => {
|
|
152
|
-
if (key.return) {
|
|
153
|
-
if (value.endsWith("\\")) {
|
|
154
|
-
setValue(`${value.slice(0, -1)}
|
|
155
|
-
`);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const text = value.trim();
|
|
159
|
-
if (!text) return;
|
|
160
|
-
setValue("");
|
|
161
|
-
setHistoryIndex(null);
|
|
162
|
-
onSubmit(text);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (key.backspace || key.delete) {
|
|
166
|
-
setValue((v) => v.slice(0, -1));
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (key.upArrow) {
|
|
170
|
-
if (!history.length) return;
|
|
171
|
-
const i = historyIndex === null ? history.length - 1 : Math.max(0, historyIndex - 1);
|
|
172
|
-
setHistoryIndex(i);
|
|
173
|
-
setValue(history[i]);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (key.downArrow) {
|
|
177
|
-
if (historyIndex === null) return;
|
|
178
|
-
const i = historyIndex + 1;
|
|
179
|
-
setHistoryIndex(i >= history.length ? null : i);
|
|
180
|
-
setValue(i >= history.length ? "" : history[i]);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (key.tab && !key.shift) {
|
|
184
|
-
setValue((v) => completeFile(v, cwd));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (key.ctrl || key.meta || key.escape || key.tab || key.leftArrow || key.rightArrow) return;
|
|
188
|
-
setValue((v) => v + input.replace(/\r/g, "\n"));
|
|
189
|
-
},
|
|
190
|
-
{ isActive: !disabled }
|
|
191
|
-
);
|
|
192
|
-
const suggestions = value.startsWith("/") && !/\s/.test(value) ? SLASH_COMMANDS.filter((c) => c.name.startsWith(value.slice(1))) : [];
|
|
193
|
-
return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", children: [
|
|
194
|
-
/* @__PURE__ */ jsx5(Box5, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs3(Text5, { children: [
|
|
195
|
-
/* @__PURE__ */ jsx5(Text5, { color: color("green"), children: "> " }),
|
|
196
|
-
value,
|
|
197
|
-
disabled ? "" : /* @__PURE__ */ jsx5(Text5, { inverse: true, children: " " })
|
|
198
|
-
] }) }),
|
|
199
|
-
suggestions.map((s) => /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` /${s.name} ${s.description}` }, s.name))
|
|
200
|
-
] });
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/ui/Spinner.tsx
|
|
204
|
-
import { Text as Text6 } from "ink";
|
|
205
|
-
import { useEffect, useState as useState3 } from "react";
|
|
206
|
-
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
207
|
-
var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
|
|
208
|
-
function Spinner({ label, startedAt }) {
|
|
209
|
-
const [tick, setTick] = useState3(0);
|
|
210
|
-
useEffect(() => {
|
|
211
|
-
const id = setInterval(() => setTick((t) => t + 1), 120);
|
|
212
|
-
return () => clearInterval(id);
|
|
213
|
-
}, []);
|
|
214
|
-
const seconds = Math.floor((Date.now() - startedAt) / 1e3);
|
|
215
|
-
return /* @__PURE__ */ jsx6(Text6, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${label}\u2026 (${seconds} dtk \xB7 esc batal)` });
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// src/ui/StatusBar.tsx
|
|
219
|
-
import { Text as Text7 } from "ink";
|
|
220
|
-
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
221
|
-
var LABEL = {
|
|
222
|
-
default: "\u23F5 default",
|
|
223
|
-
acceptEdits: "\u23F5 accept edits",
|
|
224
|
-
plan: "\u23F8 plan mode",
|
|
225
|
-
allowAll: "\u23F5\u23F5 allow all"
|
|
226
|
-
};
|
|
227
|
-
function formatTokens(n) {
|
|
228
|
-
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
229
|
-
}
|
|
230
|
-
function StatusBar({ mode, tokens, busy }) {
|
|
231
|
-
const danger = mode === "allowAll";
|
|
232
|
-
return /* @__PURE__ */ jsx7(Text7, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]} \xB7 shift+tab ganti mode \xB7 ${formatTokens(tokens)} token \xB7 ${busy ? "esc batal" : "/ perintah"}` });
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// src/ui/ToolBlock.tsx
|
|
236
|
-
import { Box as Box6, Text as Text8 } from "ink";
|
|
237
|
-
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
238
|
-
var PREVIEW_LINES = 5;
|
|
239
|
-
function ToolBlock({ tool, target, output, display, isError, done, expanded }) {
|
|
240
|
-
const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
|
|
241
|
-
const lines = (output ?? "").split("\n");
|
|
242
|
-
const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
|
|
243
|
-
return /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", marginLeft: 2, children: [
|
|
244
|
-
/* @__PURE__ */ jsxs4(Text8, { children: [
|
|
245
|
-
/* @__PURE__ */ jsx8(Text8, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
|
|
246
|
-
/* @__PURE__ */ jsx8(Text8, { bold: true, children: toolLabel(tool) }),
|
|
247
|
-
/* @__PURE__ */ jsx8(Text8, { children: ` ${target}` }),
|
|
248
|
-
display && !isDiff ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: ` (${display})` }) : null
|
|
249
|
-
] }),
|
|
250
|
-
isDiff ? /* @__PURE__ */ jsx8(Box6, { marginLeft: 3, children: /* @__PURE__ */ jsx8(DiffView, { diff: display }) }) : output && (isError || tool === "bash") ? /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", marginLeft: 3, children: [
|
|
251
|
-
shown.map((line, i) => /* @__PURE__ */ jsx8(Text8, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
|
|
252
|
-
lines.length > shown.length ? /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: `\u2026 ${lines.length - shown.length} baris lagi (ctrl+o)` }) : null
|
|
253
|
-
] }) : null
|
|
254
|
-
] });
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// src/ui/transcript.ts
|
|
258
|
-
var nextId = 0;
|
|
259
|
-
var entry = (e) => ({ ...e, id: nextId++ });
|
|
260
|
-
var NOTICES = {
|
|
261
|
-
compacted: ["Percakapan diringkas agar muat di konteks model.", "info"],
|
|
262
|
-
stepLimit: ['Batas 50 langkah tercapai. Ketik "lanjut" untuk meneruskan.', "warn"],
|
|
263
|
-
aborted: ["Dibatalkan.", "warn"]
|
|
264
|
-
};
|
|
265
|
-
function endTurn(t) {
|
|
266
|
-
return { done: [...t.done, ...t.live], live: [] };
|
|
267
|
-
}
|
|
268
|
-
function applyEvent(t, event) {
|
|
269
|
-
switch (event.type) {
|
|
270
|
-
case "text": {
|
|
271
|
-
const last = t.live.at(-1);
|
|
272
|
-
if (last?.kind === "assistant") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
|
|
273
|
-
return { ...t, live: [...t.live, entry({ kind: "assistant", text: event.delta })] };
|
|
274
|
-
}
|
|
275
|
-
case "toolStart":
|
|
276
|
-
return {
|
|
277
|
-
done: [...t.done, ...t.live],
|
|
278
|
-
live: [entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false })]
|
|
279
|
-
};
|
|
280
|
-
case "toolEnd": {
|
|
281
|
-
const live = t.live.map(
|
|
282
|
-
(e) => e.kind === "tool" && e.callId === event.id ? { ...e, output: event.output, display: event.display, isError: event.isError, done: true } : e
|
|
283
|
-
);
|
|
284
|
-
return { done: [...t.done, ...live], live: [] };
|
|
285
|
-
}
|
|
286
|
-
case "error":
|
|
287
|
-
return { ...t, live: [...t.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
|
|
288
|
-
default: {
|
|
289
|
-
const notice = NOTICES[event.type];
|
|
290
|
-
return notice ? { ...t, live: [...t.live, entry({ kind: "notice", text: notice[0], tone: notice[1] })] } : t;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// src/ui/App.tsx
|
|
296
|
-
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
297
|
-
var HELP = [
|
|
298
|
-
...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(8)} ${c.description}`),
|
|
299
|
-
"",
|
|
300
|
-
"shift+tab ganti mode izin esc batalkan giliran",
|
|
301
|
-
"ctrl+o output alat terakhir lengkap \\ + enter baris baru",
|
|
302
|
-
"@file + tab lengkapi nama file \u2191\u2193 riwayat input"
|
|
303
|
-
].join("\n");
|
|
304
|
-
function App({ runtime, initialPrompt, version }) {
|
|
305
|
-
const { exit } = useApp();
|
|
306
|
-
const [transcript, setTranscript] = useState4(() => ({ done: [entry({ kind: "header" })], live: [] }));
|
|
307
|
-
const [busy, setBusy] = useState4(false);
|
|
308
|
-
const [startedAt, setStartedAt] = useState4(0);
|
|
309
|
-
const [mode, setMode] = useState4(runtime.agent.permissions.mode);
|
|
310
|
-
const [tokens, setTokens] = useState4(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
|
|
311
|
-
const [pending, setPending] = useState4(
|
|
312
|
-
null
|
|
313
|
-
);
|
|
314
|
-
const [picker, setPicker] = useState4(null);
|
|
315
|
-
const [history, setHistory] = useState4([]);
|
|
316
|
-
const controller = useRef(null);
|
|
317
|
-
const lastTool = useRef(null);
|
|
318
|
-
const toolTargets = useRef(/* @__PURE__ */ new Map());
|
|
319
|
-
const notice = useCallback((text, tone = "info") => {
|
|
320
|
-
setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "notice", text, tone })] }));
|
|
321
|
-
}, []);
|
|
322
|
-
const onEvent = useCallback((event) => {
|
|
323
|
-
if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
|
|
324
|
-
if (event.type === "toolEnd") {
|
|
325
|
-
lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
|
|
326
|
-
}
|
|
327
|
-
if (event.type === "usage") setTokens(event.inputTokens + event.outputTokens);
|
|
328
|
-
setTranscript((t) => applyEvent(t, event));
|
|
329
|
-
}, []);
|
|
330
|
-
useEffect2(() => {
|
|
331
|
-
runtime.agent.onEvent = onEvent;
|
|
332
|
-
runtime.agent.askPermission = (request) => new Promise((resolve) => setPending({ request, resolve }));
|
|
333
|
-
}, [runtime, onEvent]);
|
|
334
|
-
const runTurn = useCallback(
|
|
335
|
-
async (text) => {
|
|
336
|
-
setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "user", text })] }));
|
|
337
|
-
setBusy(true);
|
|
338
|
-
setStartedAt(Date.now());
|
|
339
|
-
controller.current = new AbortController();
|
|
340
|
-
await runtime.agent.run(text, controller.current.signal);
|
|
341
|
-
controller.current = null;
|
|
342
|
-
setTranscript(endTurn);
|
|
343
|
-
setBusy(false);
|
|
344
|
-
},
|
|
345
|
-
[runtime]
|
|
346
|
-
);
|
|
347
|
-
const runSlash = useCallback(
|
|
348
|
-
async (name, args) => {
|
|
349
|
-
switch (name) {
|
|
350
|
-
case "help":
|
|
351
|
-
notice(HELP);
|
|
352
|
-
return;
|
|
353
|
-
case "exit":
|
|
354
|
-
exit();
|
|
355
|
-
return;
|
|
356
|
-
case "clear":
|
|
357
|
-
runtime.agent.clear();
|
|
358
|
-
setTokens(0);
|
|
359
|
-
notice("Percakapan dikosongkan.");
|
|
360
|
-
return;
|
|
361
|
-
case "cost": {
|
|
362
|
-
const u = runtime.agent.totalUsage;
|
|
363
|
-
notice(`${u.inputTokens.toLocaleString("id-ID")} token masuk \xB7 ${u.outputTokens.toLocaleString("id-ID")} token keluar`);
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
case "compact": {
|
|
367
|
-
setBusy(true);
|
|
368
|
-
setStartedAt(Date.now());
|
|
369
|
-
controller.current = new AbortController();
|
|
370
|
-
try {
|
|
371
|
-
await runtime.agent.compact(controller.current.signal);
|
|
372
|
-
} catch (error) {
|
|
373
|
-
notice(`Gagal meringkas: ${error.message}`, "error");
|
|
374
|
-
}
|
|
375
|
-
controller.current = null;
|
|
376
|
-
setBusy(false);
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
case "model": {
|
|
380
|
-
if (args) {
|
|
381
|
-
try {
|
|
382
|
-
runtime.setModel(args);
|
|
383
|
-
notice(`Model: ${args}`);
|
|
384
|
-
} catch (error) {
|
|
385
|
-
notice(error.message, "error");
|
|
386
|
-
}
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
try {
|
|
390
|
-
const provider = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
391
|
-
const models = await runtime.agent.provider.listModels();
|
|
392
|
-
setPicker(models.map((m) => `${provider}/${m}`));
|
|
393
|
-
} catch (error) {
|
|
394
|
-
notice(`Tidak bisa mengambil daftar model: ${error.message}`, "error");
|
|
395
|
-
}
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
default:
|
|
399
|
-
notice(`Perintah tidak dikenal: /${name}. Ketik /help.`, "warn");
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
[runtime, notice, exit]
|
|
403
|
-
);
|
|
404
|
-
const submit = useCallback(
|
|
405
|
-
(text) => {
|
|
406
|
-
setHistory((h) => [...h, text]);
|
|
407
|
-
const slash = parseSlash(text);
|
|
408
|
-
if (slash) void runSlash(slash.name, slash.args);
|
|
409
|
-
else void runTurn(text);
|
|
410
|
-
},
|
|
411
|
-
[runSlash, runTurn]
|
|
412
|
-
);
|
|
413
|
-
const initialSent = useRef(false);
|
|
414
|
-
useEffect2(() => {
|
|
415
|
-
if (initialPrompt && !initialSent.current) {
|
|
416
|
-
initialSent.current = true;
|
|
417
|
-
submit(initialPrompt);
|
|
418
|
-
}
|
|
419
|
-
}, [initialPrompt, submit]);
|
|
420
|
-
useInput4((input, key) => {
|
|
421
|
-
if (key.tab && key.shift) {
|
|
422
|
-
const next = nextMode(runtime.agent.permissions.mode);
|
|
423
|
-
runtime.agent.permissions.mode = next;
|
|
424
|
-
setMode(next);
|
|
425
|
-
} else if (key.escape && busy && !pending) {
|
|
426
|
-
controller.current?.abort();
|
|
427
|
-
} else if (key.ctrl && input === "o" && lastTool.current) {
|
|
428
|
-
const t = lastTool.current;
|
|
429
|
-
notice(`\u23BF ${t.tool} ${t.target}
|
|
430
|
-
${t.output}`);
|
|
431
|
-
}
|
|
432
|
-
});
|
|
433
|
-
const answer = (a) => {
|
|
434
|
-
pending?.resolve(a);
|
|
435
|
-
setPending(null);
|
|
436
|
-
};
|
|
437
|
-
const cwd = runtime.cwd.startsWith(homedir()) ? `~${runtime.cwd.slice(homedir().length)}` : runtime.cwd;
|
|
438
|
-
const renderEntry = (e) => {
|
|
439
|
-
switch (e.kind) {
|
|
440
|
-
case "header":
|
|
441
|
-
return /* @__PURE__ */ jsx9(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsxs5(Text9, { children: [
|
|
442
|
-
/* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), bold: true, children: " \u273B " }),
|
|
443
|
-
/* @__PURE__ */ jsx9(Text9, { bold: true, children: `BCCLI ${version}` }),
|
|
444
|
-
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: ` \xB7 ${runtime.modelRef} \xB7 ${cwd}` })
|
|
445
|
-
] }) }, e.id);
|
|
446
|
-
case "user":
|
|
447
|
-
return /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: `> ${e.text}` }) }, e.id);
|
|
448
|
-
case "assistant":
|
|
449
|
-
if (!e.text.trim()) return null;
|
|
450
|
-
return /* @__PURE__ */ jsxs5(Box7, { marginTop: 1, children: [
|
|
451
|
-
/* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: "\u25CF " }),
|
|
452
|
-
/* @__PURE__ */ jsx9(Markdown, { text: e.text.replace(/^\s+/, "") })
|
|
453
|
-
] }, e.id);
|
|
454
|
-
case "tool":
|
|
455
|
-
return /* @__PURE__ */ jsx9(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done }, e.id);
|
|
456
|
-
case "notice":
|
|
457
|
-
return /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: e.tone === "error" ? color("red") : e.tone === "warn" ? color("yellow") : void 0, dimColor: e.tone === "info", children: e.text }) }, e.id);
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", children: [
|
|
461
|
-
/* @__PURE__ */ jsx9(Static, { items: transcript.done, children: renderEntry }),
|
|
462
|
-
transcript.live.map(renderEntry),
|
|
463
|
-
busy && !pending ? /* @__PURE__ */ jsx9(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx9(Spinner, { label: "Berpikir", startedAt }) }) : null,
|
|
464
|
-
pending ? /* @__PURE__ */ jsx9(PermissionPrompt, { request: pending.request, onAnswer: answer }) : null,
|
|
465
|
-
picker ? /* @__PURE__ */ jsx9(
|
|
466
|
-
ModelPicker,
|
|
467
|
-
{
|
|
468
|
-
models: picker,
|
|
469
|
-
current: runtime.modelRef,
|
|
470
|
-
onPick: (m) => {
|
|
471
|
-
setPicker(null);
|
|
472
|
-
if (m) void runSlash("model", m);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
) : null,
|
|
476
|
-
/* @__PURE__ */ jsx9(PromptInput, { disabled: busy || !!pending || !!picker, history, cwd: runtime.cwd, onSubmit: submit }),
|
|
477
|
-
/* @__PURE__ */ jsx9(StatusBar, { mode, tokens, busy })
|
|
478
|
-
] });
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
// src/ui/index.tsx
|
|
482
|
-
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
483
|
-
async function startInteractive(rt, opts) {
|
|
484
|
-
if (opts.resume) {
|
|
485
|
-
const { Session } = await import("./session-7UB77CEG.js");
|
|
486
|
-
const list = Session.list(rt.home, rt.cwd).slice(0, 20);
|
|
487
|
-
if (!list.length) console.log("Belum ada sesi di folder ini; memulai sesi baru.");
|
|
488
|
-
else {
|
|
489
|
-
list.forEach((s, i) => {
|
|
490
|
-
console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString("id-ID")} ${s.preview}`);
|
|
491
|
-
});
|
|
492
|
-
const { createInterface } = await import("readline/promises");
|
|
493
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
494
|
-
const answer = Number(await rl.question("Nomor sesi (enter = baru): "));
|
|
495
|
-
rl.close();
|
|
496
|
-
const chosen = list[answer - 1];
|
|
497
|
-
if (chosen) rt.resume(chosen.session);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
const instance = render(/* @__PURE__ */ jsx10(App, { runtime: rt, initialPrompt: opts.initialPrompt, version: opts.version }), { exitOnCtrlC: true });
|
|
501
|
-
await instance.waitUntilExit();
|
|
502
|
-
return 0;
|
|
503
|
-
}
|
|
504
|
-
export {
|
|
505
|
-
startInteractive
|
|
506
|
-
};
|