@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
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ignorePatterns,
|
|
4
|
+
parseFrontmatter,
|
|
5
|
+
toolLabel
|
|
6
|
+
} from "./chunk-TBPPXOOZ.js";
|
|
7
|
+
import {
|
|
8
|
+
nextMode
|
|
9
|
+
} from "./chunk-5XN44Q55.js";
|
|
10
|
+
import {
|
|
11
|
+
hasKey,
|
|
12
|
+
providerName,
|
|
13
|
+
writeGlobalConfig
|
|
14
|
+
} from "./chunk-5C6NQFSI.js";
|
|
15
|
+
import {
|
|
16
|
+
CATALOG,
|
|
17
|
+
fillTemplate
|
|
18
|
+
} from "./chunk-TV6LEHOH.js";
|
|
19
|
+
import {
|
|
20
|
+
addGlobalServer,
|
|
21
|
+
globalMcpPath,
|
|
22
|
+
readMcpFile,
|
|
23
|
+
removeGlobalServer
|
|
24
|
+
} from "./chunk-XQLXE666.js";
|
|
25
|
+
import {
|
|
26
|
+
PRESETS
|
|
27
|
+
} from "./chunk-Z5JM76EL.js";
|
|
28
|
+
|
|
29
|
+
// src/ui/index.tsx
|
|
30
|
+
import { render } from "ink";
|
|
31
|
+
|
|
32
|
+
// src/ui/App.tsx
|
|
33
|
+
import { homedir } from "os";
|
|
34
|
+
import { Box as Box12, Static, Text as Text14, useApp, useInput as useInput8 } from "ink";
|
|
35
|
+
import { useCallback, useEffect as useEffect2, useRef, useState as useState7 } from "react";
|
|
36
|
+
import { readFileSync } from "fs";
|
|
37
|
+
|
|
38
|
+
// src/commands.ts
|
|
39
|
+
var SLASH_COMMANDS = [
|
|
40
|
+
{ name: "help", description: "daftar perintah dan pintasan" },
|
|
41
|
+
{ name: "model", description: "ganti model" },
|
|
42
|
+
{ name: "provider", description: "tambah/pilih provider AI" },
|
|
43
|
+
{ name: "mcp", description: "pasang/kelola server MCP" },
|
|
44
|
+
{ name: "clear", description: "mulai percakapan baru" },
|
|
45
|
+
{ name: "compact", description: "ringkas percakapan" },
|
|
46
|
+
{ name: "cost", description: "pemakaian token sesi ini" },
|
|
47
|
+
{ name: "exit", description: "keluar" }
|
|
48
|
+
];
|
|
49
|
+
function parseSlash(text) {
|
|
50
|
+
if (!text.startsWith("/")) return void 0;
|
|
51
|
+
const [name, ...rest] = text.slice(1).trim().split(/\s+/);
|
|
52
|
+
return { name: name.toLowerCase(), args: rest.join(" ") };
|
|
53
|
+
}
|
|
54
|
+
function expandCommand(def, args) {
|
|
55
|
+
if (def.body.includes("$ARGUMENTS")) return def.body.replaceAll("$ARGUMENTS", args);
|
|
56
|
+
return args ? `${def.body}
|
|
57
|
+
|
|
58
|
+
${args}` : def.body;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/ui/Markdown.tsx
|
|
62
|
+
import { Box, Text } from "ink";
|
|
63
|
+
|
|
64
|
+
// src/ui/theme.ts
|
|
65
|
+
function color(name) {
|
|
66
|
+
return process.env.NO_COLOR ? void 0 : name;
|
|
67
|
+
}
|
|
68
|
+
var ACCENT = "green";
|
|
69
|
+
|
|
70
|
+
// src/ui/Markdown.tsx
|
|
71
|
+
import { jsx } from "react/jsx-runtime";
|
|
72
|
+
function inline(line) {
|
|
73
|
+
return line.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((part, i) => {
|
|
74
|
+
if (part.length > 4 && part.startsWith("**") && part.endsWith("**")) {
|
|
75
|
+
return /* @__PURE__ */ jsx(Text, { bold: true, children: part.slice(2, -2) }, i);
|
|
76
|
+
}
|
|
77
|
+
if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) {
|
|
78
|
+
return /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: part.slice(1, -1) }, i);
|
|
79
|
+
}
|
|
80
|
+
return part;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function Markdown({ text }) {
|
|
84
|
+
let inCode = false;
|
|
85
|
+
const rows = [];
|
|
86
|
+
text.split("\n").forEach((line, i) => {
|
|
87
|
+
if (line.trimStart().startsWith("```")) {
|
|
88
|
+
inCode = !inCode;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
rows.push(
|
|
92
|
+
inCode ? /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i) : /* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i)
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/ui/LinePrompt.tsx
|
|
99
|
+
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
100
|
+
import { useState } from "react";
|
|
101
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
102
|
+
function LinePrompt({ label, mask, onSubmit, onCancel }) {
|
|
103
|
+
const [value, setValue] = useState("");
|
|
104
|
+
useInput((input, key) => {
|
|
105
|
+
if (key.return) onSubmit(value.trim());
|
|
106
|
+
else if (key.escape) onCancel();
|
|
107
|
+
else if (key.backspace || key.delete) setValue((v) => v.slice(0, -1));
|
|
108
|
+
else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow) setValue((v) => v + input.replace(/[\r\n]/g, ""));
|
|
109
|
+
});
|
|
110
|
+
const shown = mask ? "\u2022".repeat(value.length) : value;
|
|
111
|
+
return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs(Text2, { children: [
|
|
112
|
+
`${label}: `,
|
|
113
|
+
shown,
|
|
114
|
+
/* @__PURE__ */ jsx2(Text2, { inverse: true, children: " " }),
|
|
115
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " enter simpan \xB7 esc batal" })
|
|
116
|
+
] }) });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/ui/ModelPicker.tsx
|
|
120
|
+
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
121
|
+
import { useState as useState2 } from "react";
|
|
122
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
123
|
+
var VISIBLE = 14;
|
|
124
|
+
function ModelPicker({ groups, current, onPick }) {
|
|
125
|
+
const [filter, setFilter] = useState2("");
|
|
126
|
+
const [index, setIndex] = useState2(0);
|
|
127
|
+
const needle = filter.toLowerCase();
|
|
128
|
+
const rows = [];
|
|
129
|
+
for (const g of groups) {
|
|
130
|
+
if (g.error) continue;
|
|
131
|
+
const models = g.models.filter((m) => m.toLowerCase().includes(needle));
|
|
132
|
+
if (!models.length) continue;
|
|
133
|
+
rows.push({ kind: "header", text: g.providerName });
|
|
134
|
+
for (const m of models) rows.push({ kind: "model", ref: `${g.providerId}/${m}`, label: m });
|
|
135
|
+
}
|
|
136
|
+
const selectable = rows.flatMap((r, i) => r.kind === "model" ? [i] : []);
|
|
137
|
+
const cursor = selectable[Math.min(index, selectable.length - 1)] ?? -1;
|
|
138
|
+
useInput2((input, key) => {
|
|
139
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
140
|
+
else if (key.downArrow) setIndex((i) => Math.min(selectable.length - 1, i + 1));
|
|
141
|
+
else if (key.return) {
|
|
142
|
+
const row = rows[cursor];
|
|
143
|
+
if (row?.kind === "model") onPick(row.ref);
|
|
144
|
+
} else if (key.escape) onPick(void 0);
|
|
145
|
+
else if (key.backspace || key.delete) {
|
|
146
|
+
setFilter((f) => f.slice(0, -1));
|
|
147
|
+
setIndex(0);
|
|
148
|
+
} else if (!key.ctrl && !key.meta && !key.tab && input) {
|
|
149
|
+
setFilter((f) => f + input);
|
|
150
|
+
setIndex(0);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
const start = Math.max(0, Math.min(cursor - 5, rows.length - VISIBLE));
|
|
154
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
155
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: `Pilih model (ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 \u2191\u2193 enter \xB7 esc batal)` }),
|
|
156
|
+
rows.slice(start, start + VISIBLE).map(
|
|
157
|
+
(row, i) => row.kind === "header" ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: `\u2500\u2500 ${row.text} ` }, `h${start + i}`) : /* @__PURE__ */ jsx3(Text3, { color: start + i === cursor ? color("green") : void 0, children: `${start + i === cursor ? "\u203A" : " "} ${row.label}${row.ref === current ? " (aktif)" : ""}` }, row.ref)
|
|
158
|
+
),
|
|
159
|
+
!selectable.length ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tidak ada model yang cocok." }) : null,
|
|
160
|
+
groups.filter((g) => g.error).map((g) => /* @__PURE__ */ jsx3(Text3, { color: color("yellow"), children: `${g.providerName} \u2014 ${g.error}` }, `e-${g.providerId}`))
|
|
161
|
+
] });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/ui/ProviderMenu.tsx
|
|
165
|
+
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
166
|
+
import { useState as useState3 } from "react";
|
|
167
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
168
|
+
function ProviderMenu({ entries, onPick }) {
|
|
169
|
+
const rows = [...entries.map((e) => e.id), "custom"];
|
|
170
|
+
const [index, setIndex] = useState3(0);
|
|
171
|
+
useInput3((_input, key) => {
|
|
172
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
173
|
+
else if (key.downArrow) setIndex((i) => Math.min(rows.length - 1, i + 1));
|
|
174
|
+
else if (key.return) onPick(rows[index]);
|
|
175
|
+
else if (key.escape) onPick(void 0);
|
|
176
|
+
});
|
|
177
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
178
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Provider (\u2191\u2193 enter, esc batal) \xB7 \u2713 siap \xB7 \u25CB butuh API key" }),
|
|
179
|
+
entries.map((e, i) => /* @__PURE__ */ jsxs3(Text4, { color: i === index ? color("green") : void 0, children: [
|
|
180
|
+
`${i === index ? "\u203A" : " "} ${e.ready ? "\u2713" : "\u25CB"} ${e.name}`,
|
|
181
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: ` ${e.baseURL}` })
|
|
182
|
+
] }, e.id)),
|
|
183
|
+
/* @__PURE__ */ jsx4(Text4, { color: index === rows.length - 1 ? color("green") : void 0, children: `${index === rows.length - 1 ? "\u203A" : " "} Custom\u2026` })
|
|
184
|
+
] });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/ui/PermissionPrompt.tsx
|
|
188
|
+
import { Box as Box6, Text as Text6, useInput as useInput4 } from "ink";
|
|
189
|
+
|
|
190
|
+
// src/ui/DiffView.tsx
|
|
191
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
192
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
193
|
+
function DiffView({ diff }) {
|
|
194
|
+
return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: diff.split("\n").map((line, i) => {
|
|
195
|
+
const mark = line[6];
|
|
196
|
+
const c = mark === "+" ? color("green") : mark === "-" ? color("red") : void 0;
|
|
197
|
+
return /* @__PURE__ */ jsx5(Text5, { color: c, dimColor: mark !== "+" && mark !== "-", children: line }, i);
|
|
198
|
+
}) });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/ui/PermissionPrompt.tsx
|
|
202
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
203
|
+
var VERB = { edit: "edit", write: "tulis", bash: "jalankan", fetch: "ambil" };
|
|
204
|
+
function PermissionPrompt({
|
|
205
|
+
request,
|
|
206
|
+
onAnswer
|
|
207
|
+
}) {
|
|
208
|
+
useInput4((input, key) => {
|
|
209
|
+
const k = input.toLowerCase();
|
|
210
|
+
if (k === "y") onAnswer("yes");
|
|
211
|
+
else if (k === "a" && request.sessionRules?.length) onAnswer("session");
|
|
212
|
+
else if (k === "n" || key.escape) onAnswer("no");
|
|
213
|
+
});
|
|
214
|
+
const rules = request.sessionRules ?? [];
|
|
215
|
+
const sessionLabel = !rules.length ? null : request.kind === "edit" ? "ya untuk semua edit di project ini, sesi ini" : `ya sesi ini untuk ${rules.join(", ")}`;
|
|
216
|
+
return /* @__PURE__ */ jsxs4(Box6, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
|
|
217
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}Izinkan ${VERB[request.tool] ?? request.tool} ${request.target}?` }),
|
|
218
|
+
request.preview ? /* @__PURE__ */ jsx6(DiffView, { diff: request.preview }) : null,
|
|
219
|
+
/* @__PURE__ */ jsxs4(Text6, { children: [
|
|
220
|
+
/* @__PURE__ */ jsx6(Text6, { color: color("green"), children: "[y] ya" }),
|
|
221
|
+
" ",
|
|
222
|
+
sessionLabel ? /* @__PURE__ */ jsx6(Text6, { color: color("cyan"), children: `[a] ${sessionLabel} ` }) : null,
|
|
223
|
+
/* @__PURE__ */ jsx6(Text6, { color: color("red"), children: "[n] tidak" })
|
|
224
|
+
] })
|
|
225
|
+
] });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/ui/PromptInput.tsx
|
|
229
|
+
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
230
|
+
import { useState as useState4 } from "react";
|
|
231
|
+
|
|
232
|
+
// src/ui/complete.ts
|
|
233
|
+
import fg from "fast-glob";
|
|
234
|
+
function completeFile(value, cwd) {
|
|
235
|
+
const match = /(^|\s)@([^\s]*)$/.exec(value);
|
|
236
|
+
if (!match) return value;
|
|
237
|
+
const prefix = match[2];
|
|
238
|
+
const escaped = prefix.replace(/([*?[\]{}()!])/g, "\\$1");
|
|
239
|
+
const found = fg.sync(`${escaped}*`, { cwd, ignore: ignorePatterns(cwd), onlyFiles: false, markDirectories: true, dot: false }).sort();
|
|
240
|
+
if (!found.length) return value;
|
|
241
|
+
const pick = found[0];
|
|
242
|
+
return `${value.slice(0, value.length - prefix.length)}${pick}${pick.endsWith("/") ? "" : " "}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/ui/PromptInput.tsx
|
|
246
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
247
|
+
function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
|
|
248
|
+
const [value, setValue] = useState4("");
|
|
249
|
+
const [historyIndex, setHistoryIndex] = useState4(null);
|
|
250
|
+
useInput5(
|
|
251
|
+
(input, key) => {
|
|
252
|
+
if (key.return) {
|
|
253
|
+
if (value.endsWith("\\")) {
|
|
254
|
+
setValue(`${value.slice(0, -1)}
|
|
255
|
+
`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const text = value.trim();
|
|
259
|
+
if (!text) return;
|
|
260
|
+
setValue("");
|
|
261
|
+
setHistoryIndex(null);
|
|
262
|
+
onSubmit(text);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (key.backspace || key.delete) {
|
|
266
|
+
setValue((v) => v.slice(0, -1));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (key.upArrow) {
|
|
270
|
+
if (!history.length) return;
|
|
271
|
+
const i = historyIndex === null ? history.length - 1 : Math.max(0, historyIndex - 1);
|
|
272
|
+
setHistoryIndex(i);
|
|
273
|
+
setValue(history[i]);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (key.downArrow) {
|
|
277
|
+
if (historyIndex === null) return;
|
|
278
|
+
const i = historyIndex + 1;
|
|
279
|
+
setHistoryIndex(i >= history.length ? null : i);
|
|
280
|
+
setValue(i >= history.length ? "" : history[i]);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (key.tab && !key.shift) {
|
|
284
|
+
setValue((v) => completeFile(v, cwd));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (key.ctrl || key.meta || key.escape || key.tab || key.leftArrow || key.rightArrow) return;
|
|
288
|
+
setValue((v) => v + input.replace(/\r/g, "\n"));
|
|
289
|
+
},
|
|
290
|
+
{ isActive: !disabled }
|
|
291
|
+
);
|
|
292
|
+
const all = [...SLASH_COMMANDS, ...(extraCommands ?? []).filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name))];
|
|
293
|
+
const suggestions = value.startsWith("/") && !/\s/.test(value) ? all.filter((c) => c.name.startsWith(value.slice(1))).slice(0, 8) : [];
|
|
294
|
+
return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", children: [
|
|
295
|
+
/* @__PURE__ */ jsx7(Box7, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs5(Text7, { children: [
|
|
296
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("green"), children: "> " }),
|
|
297
|
+
value,
|
|
298
|
+
disabled ? "" : /* @__PURE__ */ jsx7(Text7, { inverse: true, children: " " })
|
|
299
|
+
] }) }),
|
|
300
|
+
suggestions.map((s) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: ` /${s.name} ${s.description}` }, s.name))
|
|
301
|
+
] });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/ui/Spinner.tsx
|
|
305
|
+
import { Text as Text8 } from "ink";
|
|
306
|
+
import { useEffect, useState as useState5 } from "react";
|
|
307
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
308
|
+
var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
|
|
309
|
+
function Spinner({ label, startedAt }) {
|
|
310
|
+
const [tick, setTick] = useState5(0);
|
|
311
|
+
useEffect(() => {
|
|
312
|
+
const id = setInterval(() => setTick((t) => t + 1), 120);
|
|
313
|
+
return () => clearInterval(id);
|
|
314
|
+
}, []);
|
|
315
|
+
const seconds = Math.floor((Date.now() - startedAt) / 1e3);
|
|
316
|
+
return /* @__PURE__ */ jsx8(Text8, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${label}\u2026 (${seconds} dtk \xB7 esc batal)` });
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/ui/StatusBar.tsx
|
|
320
|
+
import { Text as Text9 } from "ink";
|
|
321
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
322
|
+
var LABEL = {
|
|
323
|
+
default: "\u23F5 default",
|
|
324
|
+
acceptEdits: "\u23F5 accept edits",
|
|
325
|
+
plan: "\u23F8 plan mode",
|
|
326
|
+
allowAll: "\u23F5\u23F5 allow all"
|
|
327
|
+
};
|
|
328
|
+
function formatTokens(n) {
|
|
329
|
+
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
330
|
+
}
|
|
331
|
+
function StatusBar({ mode, tokens, busy, model }) {
|
|
332
|
+
const danger = mode === "allowAll";
|
|
333
|
+
return /* @__PURE__ */ jsx9(Text9, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 shift+tab ganti mode \xB7 ${formatTokens(tokens)} token \xB7 ${busy ? "esc batal" : "/ perintah"}` });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/ui/McpMenu.tsx
|
|
337
|
+
import { Box as Box8, Text as Text10, useInput as useInput6 } from "ink";
|
|
338
|
+
import { useState as useState6 } from "react";
|
|
339
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
340
|
+
var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
|
|
341
|
+
function McpMenu({ items, onPick }) {
|
|
342
|
+
const [index, setIndex] = useState6(0);
|
|
343
|
+
useInput6((_input, key) => {
|
|
344
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
345
|
+
else if (key.downArrow) setIndex((i) => Math.min(items.length - 1, i + 1));
|
|
346
|
+
else if (key.return) onPick(items[index]?.name);
|
|
347
|
+
else if (key.escape) onPick(void 0);
|
|
348
|
+
});
|
|
349
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
350
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, children: "Server MCP (enter: pasang / hapus \xB7 esc tutup) \xB7 \u2713 aktif \u25CB belum \u2717 error" }),
|
|
351
|
+
items.map((item, i) => /* @__PURE__ */ jsxs6(Text10, { color: i === index ? color("green") : void 0, children: [
|
|
352
|
+
`${i === index ? "\u203A" : " "} ${MARK[item.status]} ${item.name.padEnd(12)}`,
|
|
353
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: item.status === "error" ? ` ${item.error ?? ""}` : ` ${item.description}` })
|
|
354
|
+
] }, item.name))
|
|
355
|
+
] });
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/ui/PlanApproval.tsx
|
|
359
|
+
import { Box as Box9, Text as Text11, useInput as useInput7 } from "ink";
|
|
360
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
361
|
+
function PlanApproval({ plan, onAnswer }) {
|
|
362
|
+
useInput7((input, key) => {
|
|
363
|
+
const k = input.toLowerCase();
|
|
364
|
+
if (k === "a") onAnswer("acceptEdits");
|
|
365
|
+
else if (k === "y") onAnswer("default");
|
|
366
|
+
else if (k === "n" || key.escape) onAnswer("no");
|
|
367
|
+
});
|
|
368
|
+
return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
369
|
+
/* @__PURE__ */ jsx11(Text11, { bold: true, children: "Rencana dari agent" }),
|
|
370
|
+
/* @__PURE__ */ jsx11(Markdown, { text: plan }),
|
|
371
|
+
/* @__PURE__ */ jsxs7(Text11, { children: [
|
|
372
|
+
/* @__PURE__ */ jsx11(Text11, { color: color("green"), children: "[a] ya, edit otomatis" }),
|
|
373
|
+
" ",
|
|
374
|
+
/* @__PURE__ */ jsx11(Text11, { color: color("cyan"), children: "[y] ya, tanya tiap langkah" }),
|
|
375
|
+
" ",
|
|
376
|
+
/* @__PURE__ */ jsx11(Text11, { color: color("red"), children: "[n] belum, perbaiki dulu" })
|
|
377
|
+
] })
|
|
378
|
+
] });
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/ui/TodoList.tsx
|
|
382
|
+
import { Box as Box10, Text as Text12 } from "ink";
|
|
383
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
384
|
+
var MARK2 = { completed: "\u2611", in_progress: "\u25FC", pending: "\u2610" };
|
|
385
|
+
function TodoList({ items }) {
|
|
386
|
+
if (!items.length || items.every((t) => t.status === "completed")) return null;
|
|
387
|
+
return /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: items.map((t, i) => /* @__PURE__ */ jsx12(Text12, { dimColor: t.status === "completed", color: t.status === "in_progress" ? color("yellow") : void 0, children: `${MARK2[t.status]} ${t.content}` }, i)) });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/ui/ToolBlock.tsx
|
|
391
|
+
import { Box as Box11, Text as Text13 } from "ink";
|
|
392
|
+
import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
393
|
+
var PREVIEW_LINES = 5;
|
|
394
|
+
function ToolBlock({ tool, target, output, display, isError, done, expanded, sub }) {
|
|
395
|
+
const isTask = tool === "task";
|
|
396
|
+
const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
|
|
397
|
+
const lines = (output ?? "").split("\n");
|
|
398
|
+
const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
|
|
399
|
+
return /* @__PURE__ */ jsxs8(Box11, { flexDirection: "column", marginLeft: 2, children: [
|
|
400
|
+
/* @__PURE__ */ jsxs8(Text13, { children: [
|
|
401
|
+
/* @__PURE__ */ jsx13(Text13, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
|
|
402
|
+
/* @__PURE__ */ jsx13(Text13, { bold: true, children: toolLabel(tool) }),
|
|
403
|
+
/* @__PURE__ */ jsx13(Text13, { children: ` ${target}` }),
|
|
404
|
+
display && !isDiff && !isTask ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: ` (${display})` }) : null
|
|
405
|
+
] }),
|
|
406
|
+
sub?.length ? /* @__PURE__ */ jsxs8(Box11, { flexDirection: "column", marginLeft: 3, children: [
|
|
407
|
+
sub.slice(-6).map((s) => /* @__PURE__ */ jsx13(Text13, { dimColor: s.done && !s.isError, color: s.isError ? color("red") : void 0, children: `\u23BF ${toolLabel(s.tool)} ${s.target}` }, s.id)),
|
|
408
|
+
sub.length > 6 ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: `\u2026 ${sub.length - 6} langkah sebelumnya` }) : null
|
|
409
|
+
] }) : null,
|
|
410
|
+
isTask && done ? /* @__PURE__ */ jsx13(Box11, { marginLeft: 3, children: /* @__PURE__ */ jsx13(Text13, { color: isError ? color("red") : color("green"), children: `${isError ? "\u2717 gagal" : "\u2713 selesai"}${display ? ` \xB7 ${display}` : ""}` }) }) : null,
|
|
411
|
+
isDiff ? /* @__PURE__ */ jsx13(Box11, { marginLeft: 3, children: /* @__PURE__ */ jsx13(DiffView, { diff: display }) }) : output && (isError || tool === "bash") && !(isTask && !isError) ? /* @__PURE__ */ jsxs8(Box11, { flexDirection: "column", marginLeft: 3, children: [
|
|
412
|
+
shown.map((line, i) => /* @__PURE__ */ jsx13(Text13, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
|
|
413
|
+
lines.length > shown.length ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: `\u2026 ${lines.length - shown.length} baris lagi (ctrl+o)` }) : null
|
|
414
|
+
] }) : null
|
|
415
|
+
] });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/ui/transcript.ts
|
|
419
|
+
var nextId = 0;
|
|
420
|
+
var entry = (e) => ({ ...e, id: nextId++ });
|
|
421
|
+
var NOTICES = {
|
|
422
|
+
compacted: ["Percakapan diringkas agar muat di konteks model.", "info"],
|
|
423
|
+
stepLimit: ['Batas 50 langkah tercapai. Ketik "lanjut" untuk meneruskan.', "warn"],
|
|
424
|
+
aborted: ["Dibatalkan.", "warn"]
|
|
425
|
+
};
|
|
426
|
+
function endTurn(t) {
|
|
427
|
+
return { done: [...t.done, ...t.live], live: [] };
|
|
428
|
+
}
|
|
429
|
+
function applyEvent(t, event) {
|
|
430
|
+
switch (event.type) {
|
|
431
|
+
case "text": {
|
|
432
|
+
const last = t.live.at(-1);
|
|
433
|
+
if (last?.kind === "assistant") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
|
|
434
|
+
return { ...t, live: [...t.live, entry({ kind: "assistant", text: event.delta })] };
|
|
435
|
+
}
|
|
436
|
+
case "toolStart": {
|
|
437
|
+
const running = t.live.some((e) => e.kind === "tool" && !e.done);
|
|
438
|
+
const tool = entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false });
|
|
439
|
+
return running ? { ...t, live: [...t.live, tool] } : { done: [...t.done, ...t.live], live: [tool] };
|
|
440
|
+
}
|
|
441
|
+
case "toolEnd": {
|
|
442
|
+
const live = t.live.map(
|
|
443
|
+
(e) => e.kind === "tool" && e.callId === event.id ? { ...e, output: event.output, display: event.display, isError: event.isError, done: true } : e
|
|
444
|
+
);
|
|
445
|
+
const firstRunning = live.findIndex((e) => e.kind === "tool" && !e.done);
|
|
446
|
+
const cut = firstRunning === -1 ? live.length : firstRunning;
|
|
447
|
+
return { done: [...t.done, ...live.slice(0, cut)], live: live.slice(cut) };
|
|
448
|
+
}
|
|
449
|
+
case "subagent": {
|
|
450
|
+
const inner = event.event;
|
|
451
|
+
if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t;
|
|
452
|
+
const update = (e) => {
|
|
453
|
+
if (e.kind !== "tool" || e.callId !== event.parentId) return e;
|
|
454
|
+
const sub = e.sub ?? [];
|
|
455
|
+
if (inner.type === "toolStart") return { ...e, sub: [...sub, { id: inner.id, tool: inner.tool, target: inner.target, done: false }] };
|
|
456
|
+
return { ...e, sub: sub.map((s) => s.id === inner.id ? { ...s, done: true, isError: inner.isError } : s) };
|
|
457
|
+
};
|
|
458
|
+
return { ...t, live: t.live.map(update) };
|
|
459
|
+
}
|
|
460
|
+
case "error":
|
|
461
|
+
return { ...t, live: [...t.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
|
|
462
|
+
default: {
|
|
463
|
+
const notice = NOTICES[event.type];
|
|
464
|
+
return notice ? { ...t, live: [...t.live, entry({ kind: "notice", text: notice[0], tone: notice[1] })] } : t;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/ui/App.tsx
|
|
470
|
+
import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
471
|
+
function helpText(runtime) {
|
|
472
|
+
const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
|
|
473
|
+
return [
|
|
474
|
+
...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(8)} ${c.description}`),
|
|
475
|
+
...custom.length ? ["", "Perintah custom:", ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
|
|
476
|
+
...runtime.skills.length ? ["", "Skill:", ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
|
|
477
|
+
"",
|
|
478
|
+
"shift+tab ganti mode izin esc batalkan giliran",
|
|
479
|
+
"ctrl+o output alat terakhir lengkap \\ + enter baris baru",
|
|
480
|
+
"@file + tab lengkapi nama file \u2191\u2193 riwayat input"
|
|
481
|
+
].join("\n");
|
|
482
|
+
}
|
|
483
|
+
var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
|
|
484
|
+
function App({ runtime, initialPrompt, version }) {
|
|
485
|
+
const { exit } = useApp();
|
|
486
|
+
const [transcript, setTranscript] = useState7(() => ({ done: [entry({ kind: "header" })], live: [] }));
|
|
487
|
+
const [busy, setBusy] = useState7(false);
|
|
488
|
+
const [startedAt, setStartedAt] = useState7(0);
|
|
489
|
+
const [mode, setMode] = useState7(runtime.agent.permissions.mode);
|
|
490
|
+
const [tokens, setTokens] = useState7(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
|
|
491
|
+
const [asks, setAsks] = useState7([]);
|
|
492
|
+
const pending = asks[0] ?? null;
|
|
493
|
+
const askId = useRef(0);
|
|
494
|
+
const [picker, setPicker] = useState7(null);
|
|
495
|
+
const [providerMenu, setProviderMenu] = useState7(null);
|
|
496
|
+
const [prompt, setPrompt] = useState7(null);
|
|
497
|
+
const ask = (label, mask = false) => new Promise((resolve) => setPrompt({ label, mask, resolve }));
|
|
498
|
+
const [modelLabel, setModelLabel] = useState7(() => labelFor(runtime, runtime.modelRef));
|
|
499
|
+
const [history, setHistory] = useState7([]);
|
|
500
|
+
const controller = useRef(null);
|
|
501
|
+
const [todos, setTodos] = useState7(runtime.todos.items);
|
|
502
|
+
const [mcpMenu, setMcpMenu] = useState7(null);
|
|
503
|
+
const [planAsk, setPlanAsk] = useState7(null);
|
|
504
|
+
useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
|
|
505
|
+
const extraCommands = [
|
|
506
|
+
...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? "perintah custom" })),
|
|
507
|
+
...runtime.skills.filter((s) => !runtime.commands.some((c) => c.name.toLowerCase() === s.name.toLowerCase())).filter((s) => !SLASH_COMMANDS.some((b) => b.name === s.name.toLowerCase())).map((s) => ({ name: s.name, description: `skill \xB7 ${s.description.slice(0, 50)}` }))
|
|
508
|
+
];
|
|
509
|
+
const lastTool = useRef(null);
|
|
510
|
+
const toolTargets = useRef(/* @__PURE__ */ new Map());
|
|
511
|
+
const notice = useCallback((text, tone = "info") => {
|
|
512
|
+
setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "notice", text, tone })] }));
|
|
513
|
+
}, []);
|
|
514
|
+
const onEvent = useCallback((event) => {
|
|
515
|
+
if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
|
|
516
|
+
if (event.type === "toolEnd") {
|
|
517
|
+
lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
|
|
518
|
+
}
|
|
519
|
+
if (event.type === "usage") setTokens(event.inputTokens + event.outputTokens);
|
|
520
|
+
setTranscript((t) => applyEvent(t, event));
|
|
521
|
+
}, []);
|
|
522
|
+
useEffect2(() => {
|
|
523
|
+
runtime.agent.onEvent = onEvent;
|
|
524
|
+
runtime.agent.askPermission = (request) => new Promise((resolve) => setAsks((q) => [...q, { id: askId.current++, request, resolve }]));
|
|
525
|
+
runtime.interaction.approvePlan = (plan) => new Promise((resolve) => setPlanAsk({ plan, resolve }));
|
|
526
|
+
}, [runtime, onEvent]);
|
|
527
|
+
const runTurn = useCallback(
|
|
528
|
+
async (text) => {
|
|
529
|
+
setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "user", text })] }));
|
|
530
|
+
setBusy(true);
|
|
531
|
+
setStartedAt(Date.now());
|
|
532
|
+
controller.current = new AbortController();
|
|
533
|
+
await runtime.agent.run(text, controller.current.signal);
|
|
534
|
+
controller.current = null;
|
|
535
|
+
setTranscript(endTurn);
|
|
536
|
+
setBusy(false);
|
|
537
|
+
},
|
|
538
|
+
[runtime]
|
|
539
|
+
);
|
|
540
|
+
const runSlash = useCallback(
|
|
541
|
+
async (name, args) => {
|
|
542
|
+
switch (name) {
|
|
543
|
+
case "help":
|
|
544
|
+
notice(helpText(runtime));
|
|
545
|
+
return;
|
|
546
|
+
case "exit":
|
|
547
|
+
exit();
|
|
548
|
+
return;
|
|
549
|
+
case "clear":
|
|
550
|
+
runtime.agent.clear();
|
|
551
|
+
setTokens(0);
|
|
552
|
+
notice("Percakapan dikosongkan.");
|
|
553
|
+
return;
|
|
554
|
+
case "cost": {
|
|
555
|
+
const u = runtime.agent.totalUsage;
|
|
556
|
+
notice(`${u.inputTokens.toLocaleString("id-ID")} token masuk \xB7 ${u.outputTokens.toLocaleString("id-ID")} token keluar`);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
case "compact": {
|
|
560
|
+
setBusy(true);
|
|
561
|
+
setStartedAt(Date.now());
|
|
562
|
+
controller.current = new AbortController();
|
|
563
|
+
try {
|
|
564
|
+
await runtime.agent.compact(controller.current.signal);
|
|
565
|
+
} catch (error) {
|
|
566
|
+
notice(`Gagal meringkas: ${error.message}`, "error");
|
|
567
|
+
}
|
|
568
|
+
controller.current = null;
|
|
569
|
+
setBusy(false);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
case "model": {
|
|
573
|
+
if (args) {
|
|
574
|
+
try {
|
|
575
|
+
runtime.setModel(args);
|
|
576
|
+
writeGlobalConfig({ model: args }, runtime.env);
|
|
577
|
+
setModelLabel(labelFor(runtime, args));
|
|
578
|
+
notice(`Model: ${args} (tersimpan sebagai default)`);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
notice(error.message, "error");
|
|
581
|
+
}
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
setPicker(await runtime.listModels());
|
|
586
|
+
} catch (error) {
|
|
587
|
+
notice(`Tidak bisa mengambil daftar model: ${error.message}`, "error");
|
|
588
|
+
}
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
case "mcp": {
|
|
592
|
+
const installed = readMcpFile(globalMcpPath(runtime.home));
|
|
593
|
+
const states = runtime.mcp.states();
|
|
594
|
+
const items = [
|
|
595
|
+
...CATALOG.map((c) => ({ name: c.name, description: c.description, installed: !!installed[c.name] })),
|
|
596
|
+
...Object.keys(installed).filter((n) => !CATALOG.some((c) => c.name === n)).map((n) => ({ name: n, description: "(custom)", installed: true }))
|
|
597
|
+
].map((i) => {
|
|
598
|
+
const s = states.find((st) => st.name === i.name);
|
|
599
|
+
return { ...i, status: s?.status ?? "off", error: s?.error };
|
|
600
|
+
});
|
|
601
|
+
setMcpMenu(items);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
case "provider": {
|
|
605
|
+
const c = runtime.config;
|
|
606
|
+
setProviderMenu(
|
|
607
|
+
Object.keys(c.providers).map((id) => ({ id, name: providerName(c, id), ready: hasKey(c, id, runtime.env), baseURL: c.providers[id].baseURL }))
|
|
608
|
+
);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
default:
|
|
612
|
+
notice(`Perintah tidak dikenal: /${name}. Ketik /help.`, "warn");
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
[runtime, notice, exit]
|
|
616
|
+
);
|
|
617
|
+
const submit = useCallback(
|
|
618
|
+
(text) => {
|
|
619
|
+
setHistory((h) => [...h, text]);
|
|
620
|
+
const slash = parseSlash(text);
|
|
621
|
+
if (!slash) {
|
|
622
|
+
void runTurn(text);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const command = runtime.commands.find((c) => c.name.toLowerCase() === slash.name);
|
|
626
|
+
const skill = runtime.skills.find((s) => s.name.toLowerCase() === slash.name);
|
|
627
|
+
if (SLASH_COMMANDS.some((b) => b.name === slash.name) || !command && !skill) void runSlash(slash.name, slash.args);
|
|
628
|
+
else if (command) void runTurn(expandCommand(command, slash.args));
|
|
629
|
+
else if (skill) {
|
|
630
|
+
let body;
|
|
631
|
+
try {
|
|
632
|
+
body = parseFrontmatter(readFileSync(skill.file, "utf8")).body;
|
|
633
|
+
} catch (error) {
|
|
634
|
+
notice(`Skill ${skill.name} tidak bisa dibaca: ${error.code ?? error.message}`, "error");
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
void runTurn(`${body}
|
|
638
|
+
|
|
639
|
+
ARGUMENTS: ${slash.args}`);
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
[runSlash, runTurn, runtime, notice]
|
|
643
|
+
);
|
|
644
|
+
const initialSent = useRef(false);
|
|
645
|
+
useEffect2(() => {
|
|
646
|
+
if (initialPrompt && !initialSent.current) {
|
|
647
|
+
initialSent.current = true;
|
|
648
|
+
submit(initialPrompt);
|
|
649
|
+
}
|
|
650
|
+
}, [initialPrompt, submit]);
|
|
651
|
+
useInput8((input, key) => {
|
|
652
|
+
if (key.tab && key.shift) {
|
|
653
|
+
const next = nextMode(runtime.agent.permissions.mode);
|
|
654
|
+
runtime.agent.permissions.mode = next;
|
|
655
|
+
setMode(next);
|
|
656
|
+
} else if (key.escape && busy && !pending) {
|
|
657
|
+
controller.current?.abort();
|
|
658
|
+
} else if (key.ctrl && input === "o" && lastTool.current) {
|
|
659
|
+
const t = lastTool.current;
|
|
660
|
+
notice(`\u23BF ${t.tool} ${t.target}
|
|
661
|
+
${t.output}`);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
const pickMcp = async (name) => {
|
|
665
|
+
const item = mcpMenu?.find((i) => i.name === name);
|
|
666
|
+
setMcpMenu(null);
|
|
667
|
+
if (!item) return;
|
|
668
|
+
if (item.installed) {
|
|
669
|
+
removeGlobalServer(runtime.home, item.name);
|
|
670
|
+
await runtime.mcp.remove(item.name);
|
|
671
|
+
notice(`MCP ${item.name} dihapus.`);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const entry2 = CATALOG.find((c) => c.name === item.name);
|
|
675
|
+
if (!entry2) return;
|
|
676
|
+
const values = {};
|
|
677
|
+
for (const input of entry2.inputs ?? []) {
|
|
678
|
+
const v = await ask(input.label, !!input.secret);
|
|
679
|
+
if (!v) return;
|
|
680
|
+
values[input.key] = v;
|
|
681
|
+
}
|
|
682
|
+
const config = fillTemplate(entry2.config, values);
|
|
683
|
+
addGlobalServer(runtime.home, entry2.name, config);
|
|
684
|
+
notice(`MCP ${entry2.name} dipasang, menghubungkan\u2026`);
|
|
685
|
+
await runtime.mcp.add({ name: entry2.name, config, source: "global" });
|
|
686
|
+
const state = runtime.mcp.states().find((s) => s.name === entry2.name);
|
|
687
|
+
if (state?.status === "ready") notice(`MCP ${entry2.name} aktif \xB7 ${state.tools} alat.`);
|
|
688
|
+
else notice(`MCP ${entry2.name} gagal: ${state?.error ?? "tidak diketahui"}`, "error");
|
|
689
|
+
};
|
|
690
|
+
const pickProvider = async (id) => {
|
|
691
|
+
setProviderMenu(null);
|
|
692
|
+
if (!id) return;
|
|
693
|
+
let target = id;
|
|
694
|
+
if (id === "custom") {
|
|
695
|
+
const name = await ask("Id provider (huruf kecil, mis. corp)");
|
|
696
|
+
const url = name ? await ask("Base URL OpenAI-compatible") : void 0;
|
|
697
|
+
if (!name || !url) return;
|
|
698
|
+
writeGlobalConfig({ providers: { [name]: { baseURL: url } } }, runtime.env);
|
|
699
|
+
runtime.reloadConfig();
|
|
700
|
+
target = name;
|
|
701
|
+
}
|
|
702
|
+
const c = runtime.config;
|
|
703
|
+
const keyless = !c.providers[target].apiKeyEnv && PRESETS.some((p) => p.id === target);
|
|
704
|
+
if (!hasKey(c, target, runtime.env) || id === "custom") {
|
|
705
|
+
const key = keyless ? "" : await ask(`API key untuk ${providerName(c, target)}`, true);
|
|
706
|
+
if (key === void 0) return;
|
|
707
|
+
const result = await runtime.addProviderKey(target, key);
|
|
708
|
+
if (!result.ok) {
|
|
709
|
+
notice(result.error, "error");
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
notice(`${providerName(c, target)} siap \xB7 ${result.models} model.`);
|
|
713
|
+
}
|
|
714
|
+
setPicker(await runtime.listModels(target));
|
|
715
|
+
};
|
|
716
|
+
const answer = (a) => {
|
|
717
|
+
pending?.resolve(a);
|
|
718
|
+
setAsks((q) => q.slice(1));
|
|
719
|
+
};
|
|
720
|
+
const cwd = runtime.cwd.startsWith(homedir()) ? `~${runtime.cwd.slice(homedir().length)}` : runtime.cwd;
|
|
721
|
+
const renderEntry = (e) => {
|
|
722
|
+
switch (e.kind) {
|
|
723
|
+
case "header":
|
|
724
|
+
return /* @__PURE__ */ jsx14(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsxs9(Text14, { children: [
|
|
725
|
+
/* @__PURE__ */ jsx14(Text14, { color: color(ACCENT), bold: true, children: " \u273B " }),
|
|
726
|
+
/* @__PURE__ */ jsx14(Text14, { bold: true, children: `BCCLI ${version}` }),
|
|
727
|
+
/* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` \xB7 ${runtime.modelRef} (${runtime.providerLabel()}) \xB7 ${cwd}` })
|
|
728
|
+
] }) }, e.id);
|
|
729
|
+
case "user":
|
|
730
|
+
return /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: `> ${e.text}` }) }, e.id);
|
|
731
|
+
case "assistant":
|
|
732
|
+
if (!e.text.trim()) return null;
|
|
733
|
+
return /* @__PURE__ */ jsxs9(Box12, { marginTop: 1, children: [
|
|
734
|
+
/* @__PURE__ */ jsx14(Text14, { color: color(ACCENT), children: "\u25CF " }),
|
|
735
|
+
/* @__PURE__ */ jsx14(Markdown, { text: e.text.replace(/^\s+/, "") })
|
|
736
|
+
] }, e.id);
|
|
737
|
+
case "tool":
|
|
738
|
+
return /* @__PURE__ */ jsx14(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, sub: e.sub }, e.id);
|
|
739
|
+
case "notice":
|
|
740
|
+
return /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: e.tone === "error" ? color("red") : e.tone === "warn" ? color("yellow") : void 0, dimColor: e.tone === "info", children: e.text }) }, e.id);
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
return /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", children: [
|
|
744
|
+
/* @__PURE__ */ jsx14(Static, { items: transcript.done, children: renderEntry }),
|
|
745
|
+
transcript.live.map(renderEntry),
|
|
746
|
+
busy && !pending && !planAsk ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Spinner, { label: "Berpikir", startedAt }) }) : null,
|
|
747
|
+
pending ? /* @__PURE__ */ jsx14(PermissionPrompt, { request: pending.request, onAnswer: answer }, pending.id) : null,
|
|
748
|
+
picker ? /* @__PURE__ */ jsx14(
|
|
749
|
+
ModelPicker,
|
|
750
|
+
{
|
|
751
|
+
groups: picker,
|
|
752
|
+
current: runtime.modelRef,
|
|
753
|
+
onPick: (m) => {
|
|
754
|
+
setPicker(null);
|
|
755
|
+
if (m) void runSlash("model", m);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
) : null,
|
|
759
|
+
mcpMenu ? /* @__PURE__ */ jsx14(McpMenu, { items: mcpMenu, onPick: (n) => void pickMcp(n) }) : null,
|
|
760
|
+
providerMenu ? /* @__PURE__ */ jsx14(ProviderMenu, { entries: providerMenu, onPick: (id) => void pickProvider(id) }) : null,
|
|
761
|
+
prompt ? /* @__PURE__ */ jsx14(
|
|
762
|
+
LinePrompt,
|
|
763
|
+
{
|
|
764
|
+
label: prompt.label,
|
|
765
|
+
mask: prompt.mask,
|
|
766
|
+
onSubmit: (v) => {
|
|
767
|
+
setPrompt(null);
|
|
768
|
+
prompt.resolve(v);
|
|
769
|
+
},
|
|
770
|
+
onCancel: () => {
|
|
771
|
+
setPrompt(null);
|
|
772
|
+
prompt.resolve(void 0);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
) : null,
|
|
776
|
+
planAsk ? /* @__PURE__ */ jsx14(
|
|
777
|
+
PlanApproval,
|
|
778
|
+
{
|
|
779
|
+
plan: planAsk.plan,
|
|
780
|
+
onAnswer: (d) => {
|
|
781
|
+
planAsk.resolve(d);
|
|
782
|
+
setPlanAsk(null);
|
|
783
|
+
if (d !== "no") setMode(d);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
) : null,
|
|
787
|
+
/* @__PURE__ */ jsx14(TodoList, { items: todos }),
|
|
788
|
+
/* @__PURE__ */ jsx14(PromptInput, { disabled: busy || !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu, history, cwd: runtime.cwd, onSubmit: submit, extraCommands }),
|
|
789
|
+
/* @__PURE__ */ jsx14(StatusBar, { mode, tokens, busy, model: modelLabel })
|
|
790
|
+
] });
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/ui/index.tsx
|
|
794
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
795
|
+
async function startInteractive(rt, opts) {
|
|
796
|
+
if (opts.resume) {
|
|
797
|
+
const { Session } = await import("./session-7UB77CEG.js");
|
|
798
|
+
const list = Session.list(rt.home, rt.cwd).slice(0, 20);
|
|
799
|
+
if (!list.length) console.log("Belum ada sesi di folder ini; memulai sesi baru.");
|
|
800
|
+
else {
|
|
801
|
+
list.forEach((s, i) => {
|
|
802
|
+
console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString("id-ID")} ${s.preview}`);
|
|
803
|
+
});
|
|
804
|
+
const { createInterface } = await import("readline/promises");
|
|
805
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
806
|
+
const answer = Number(await rl.question("Nomor sesi (enter = baru): "));
|
|
807
|
+
rl.close();
|
|
808
|
+
const chosen = list[answer - 1];
|
|
809
|
+
if (chosen) rt.resume(chosen.session);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
const { serversToStart, setProjectTrust } = await import("./config-XPWPGNZJ.js");
|
|
813
|
+
const plan = serversToStart(rt.home, rt.cwd);
|
|
814
|
+
if (plan.needTrust.length) {
|
|
815
|
+
const { createInterface } = await import("readline/promises");
|
|
816
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
817
|
+
for (const s of plan.needTrust) {
|
|
818
|
+
const what = s.config.type === "http" ? s.config.url : [s.config.command, ...s.config.args ?? []].join(" ");
|
|
819
|
+
const answer = (await rl.question(`Repo ini ingin menjalankan server MCP "${s.name}" (${what}). Izinkan? [y/N] `)).trim().toLowerCase();
|
|
820
|
+
setProjectTrust(rt.home, rt.cwd, s.name, answer === "y", s.config);
|
|
821
|
+
if (answer === "y") plan.start.push(s);
|
|
822
|
+
}
|
|
823
|
+
rl.close();
|
|
824
|
+
}
|
|
825
|
+
void rt.startMcp(plan.start);
|
|
826
|
+
const instance = render(/* @__PURE__ */ jsx15(App, { runtime: rt, initialPrompt: opts.initialPrompt, version: opts.version }), { exitOnCtrlC: true });
|
|
827
|
+
await instance.waitUntilExit();
|
|
828
|
+
await rt.mcp.stop();
|
|
829
|
+
return 0;
|
|
830
|
+
}
|
|
831
|
+
export {
|
|
832
|
+
startInteractive
|
|
833
|
+
};
|