@botconnector/bccli 0.3.0-beta.2 → 0.4.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 +30 -1
- package/dist/{args-EVIFNGPQ.js → args-EAKPBBGX.js} +2 -2
- package/dist/{chunk-MANM7HY6.js → chunk-5ISYVAXI.js} +19 -1
- package/dist/{chunk-5XN44Q55.js → chunk-ELML4KVQ.js} +7 -1
- package/dist/{chunk-RDQ7HPWS.js → chunk-M7INIL2C.js} +1 -1
- package/dist/chunk-OSWQPSA4.js +252 -0
- package/dist/{chunk-E4BWUMQ2.js → chunk-QH7XXNHU.js} +2 -2
- package/dist/cli.js +121 -106
- package/dist/{login-IXJD5O7Z.js → login-FTMR64IA.js} +1 -1
- package/dist/{providerCli-7QNAYIFT.js → providerCli-SJRCBSRE.js} +1 -1
- package/dist/{ui-HNIC4DQ6.js → ui-LWZ75D7H.js} +538 -157
- package/package.json +1 -1
- package/dist/chunk-TBPPXOOZ.js +0 -165
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
BUILTIN_AGENTS,
|
|
3
4
|
ignorePatterns,
|
|
5
|
+
instructionPaths,
|
|
4
6
|
parseFrontmatter,
|
|
5
7
|
toolLabel
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OSWQPSA4.js";
|
|
7
9
|
import {
|
|
8
10
|
nextMode
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ELML4KVQ.js";
|
|
10
12
|
import {
|
|
11
13
|
hasKey,
|
|
12
14
|
providerName,
|
|
13
15
|
writeGlobalConfig
|
|
14
16
|
} from "./chunk-5C6NQFSI.js";
|
|
17
|
+
import {
|
|
18
|
+
Session
|
|
19
|
+
} from "./chunk-SJH3YLW4.js";
|
|
15
20
|
import {
|
|
16
21
|
CATALOG,
|
|
17
22
|
fillTemplate
|
|
@@ -23,17 +28,20 @@ import {
|
|
|
23
28
|
removeGlobalServer
|
|
24
29
|
} from "./chunk-XQLXE666.js";
|
|
25
30
|
import {
|
|
26
|
-
PRESETS
|
|
31
|
+
PRESETS,
|
|
32
|
+
removeCredential
|
|
27
33
|
} from "./chunk-Z5JM76EL.js";
|
|
28
34
|
|
|
29
35
|
// src/ui/index.tsx
|
|
30
36
|
import { render } from "ink";
|
|
31
37
|
|
|
32
38
|
// src/ui/App.tsx
|
|
39
|
+
import { spawnSync } from "child_process";
|
|
40
|
+
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
33
41
|
import { homedir } from "os";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
42
|
+
import { dirname, join, relative, resolve } from "path";
|
|
43
|
+
import { Box as Box13, Static, Text as Text15, useApp, useInput as useInput9 } from "ink";
|
|
44
|
+
import { useCallback, useEffect as useEffect2, useRef, useState as useState8 } from "react";
|
|
37
45
|
|
|
38
46
|
// src/commands.ts
|
|
39
47
|
var SLASH_COMMANDS = [
|
|
@@ -41,6 +49,22 @@ var SLASH_COMMANDS = [
|
|
|
41
49
|
{ name: "model", description: "ganti model" },
|
|
42
50
|
{ name: "provider", description: "tambah/pilih provider AI" },
|
|
43
51
|
{ name: "mcp", description: "pasang/kelola server MCP" },
|
|
52
|
+
{ name: "new", description: "sesi baru (sesi lama tetap tersimpan)" },
|
|
53
|
+
{ name: "resume", description: "lanjutkan sesi lain di folder ini" },
|
|
54
|
+
{ name: "session", description: "info sesi ini" },
|
|
55
|
+
{ name: "status", description: "versi, model, mode izin, MCP, konteks" },
|
|
56
|
+
{ name: "permissions", description: "lihat/cabut izin" },
|
|
57
|
+
{ name: "undo", description: "batalkan edit file giliran terakhir" },
|
|
58
|
+
{ name: "diff", description: "git diff project" },
|
|
59
|
+
{ name: "copy", description: "salin jawaban terakhir" },
|
|
60
|
+
{ name: "export", description: "simpan percakapan ke markdown" },
|
|
61
|
+
{ name: "memory", description: "instruksi project (/memory <teks>, /memory global <teks>)" },
|
|
62
|
+
{ name: "init", description: "buat/perbarui AGENTS.md untuk project ini" },
|
|
63
|
+
{ name: "agents", description: "daftar subagent" },
|
|
64
|
+
{ name: "skills", description: "daftar skill dan perintah custom" },
|
|
65
|
+
{ name: "doctor", description: "cek kesehatan instalasi" },
|
|
66
|
+
{ name: "login", description: "simpan API key provider aktif" },
|
|
67
|
+
{ name: "logout", description: "hapus API key provider aktif" },
|
|
44
68
|
{ name: "clear", description: "mulai percakapan baru" },
|
|
45
69
|
{ name: "compact", description: "ringkas percakapan" },
|
|
46
70
|
{ name: "cost", description: "pemakaian token sesi ini" },
|
|
@@ -58,6 +82,111 @@ function expandCommand(def, args) {
|
|
|
58
82
|
${args}` : def.body;
|
|
59
83
|
}
|
|
60
84
|
|
|
85
|
+
// src/slash/copy.ts
|
|
86
|
+
function osc52(text, inTmux) {
|
|
87
|
+
const seq = `\x1B]52;c;${Buffer.from(text).toString("base64")}\x07`;
|
|
88
|
+
return inTmux ? `\x1BPtmux;${seq.replaceAll("\x1B", "\x1B\x1B")}\x1B\\` : seq;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/slash/diff.ts
|
|
92
|
+
import { execFile } from "child_process";
|
|
93
|
+
function git(cwd, args) {
|
|
94
|
+
return new Promise((resolve2) => {
|
|
95
|
+
execFile("git", args, { cwd, maxBuffer: 20 * 1024 * 1024 }, (error, stdout, stderr) => resolve2({ ok: !error, out: error ? stderr : stdout }));
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function gitDiff(cwd, maxLines = 400) {
|
|
99
|
+
const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
100
|
+
if (!inside.ok || inside.out.trim() !== "true") return "Folder ini bukan repository git, jadi tidak ada diff.";
|
|
101
|
+
const base = (await git(cwd, ["rev-parse", "--verify", "-q", "HEAD"])).ok ? "HEAD" : "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
102
|
+
const [stat, diff, untracked] = await Promise.all([
|
|
103
|
+
git(cwd, ["diff", "--stat", base]),
|
|
104
|
+
git(cwd, ["diff", base]),
|
|
105
|
+
git(cwd, ["ls-files", "--others", "--exclude-standard"])
|
|
106
|
+
]);
|
|
107
|
+
const fresh = untracked.out.split("\n").filter(Boolean);
|
|
108
|
+
if (!diff.out.trim() && !fresh.length) return "Tidak ada perubahan yang belum di-commit.";
|
|
109
|
+
const lines = diff.out.trimEnd().split("\n");
|
|
110
|
+
const more = lines.length > maxLines ? `
|
|
111
|
+
\u2026 ${lines.length - maxLines} baris lagi` : "";
|
|
112
|
+
return [stat.out.trim(), lines.slice(0, maxLines).join("\n") + more, fresh.length ? `File baru (belum di-track): ${fresh.join(", ")}` : ""].filter(Boolean).join("\n\n");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/slash/doctor.ts
|
|
116
|
+
async function doctorText(input, deps) {
|
|
117
|
+
const major = Number(deps.nodeVersion.replace(/^v/, "").split(".")[0]);
|
|
118
|
+
const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` : `\u2717 Node ${deps.nodeVersion} (butuh \u2265 22)`];
|
|
119
|
+
for (const p of input.providers) lines.push(p.ready ? `\u2713 ${p.name}` : `\u25CB ${p.name} (belum ada key)`);
|
|
120
|
+
try {
|
|
121
|
+
lines.push(`\u2713 Koneksi ${input.activeProvider} (${(await deps.listModels()).length} model)`);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
lines.push(`\u2717 Koneksi ${input.activeProvider}: ${error.message}`);
|
|
124
|
+
}
|
|
125
|
+
for (const m of input.mcp) lines.push(m.status === "error" ? `\u2717 MCP ${m.name}: ${m.error ?? ""}` : `\u2713 MCP ${m.name} (${m.status})`);
|
|
126
|
+
for (const cmd of ["rg", "git", "npx", "uvx"]) lines.push(`${deps.which(cmd) ? "\u2713" : "\u2717"} ${cmd}`);
|
|
127
|
+
return lines.join("\n");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/slash/export.ts
|
|
131
|
+
function exportMarkdown(messages, title) {
|
|
132
|
+
const parts = [`# ${title}`, ""];
|
|
133
|
+
for (const m of messages) {
|
|
134
|
+
if (m.role === "user") parts.push("## User", "", String(m.content), "");
|
|
135
|
+
else if (m.role === "assistant") {
|
|
136
|
+
for (const c of m.tool_calls ?? []) parts.push(`\u23BF ${c.function.name} ${c.function.arguments.slice(0, 200)}`, "");
|
|
137
|
+
if (m.content) parts.push("## BCCLI", "", m.content, "");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return parts.join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/slash/info.ts
|
|
144
|
+
var k = (n) => n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
145
|
+
function statusText(s) {
|
|
146
|
+
const ready = s.mcp.filter((m) => m.status === "ready").length;
|
|
147
|
+
const errors = s.mcp.filter((m) => m.status === "error").length;
|
|
148
|
+
return [
|
|
149
|
+
`BCCLI ${s.version}`,
|
|
150
|
+
`Model: ${s.modelRef} (${s.providerLabel})`,
|
|
151
|
+
`Mode izin: ${s.mode}`,
|
|
152
|
+
`Folder: ${s.cwd}`,
|
|
153
|
+
`MCP: ${ready} aktif, ${errors} error`,
|
|
154
|
+
`Token sesi: ${k(s.usage.inputTokens)} masuk \xB7 ${k(s.usage.outputTokens)} keluar`,
|
|
155
|
+
`Konteks: \xB1${Math.round(s.lastInputTokens / 128e3 * 100)}% dari 128k`
|
|
156
|
+
].join("\n");
|
|
157
|
+
}
|
|
158
|
+
function agentsText(defs) {
|
|
159
|
+
return defs.map((d) => `${d.name} \u2014 ${d.description} \xB7 alat: ${d.tools ? d.tools.join(", ") : "semua"} \xB7 model: ${d.model ?? "ikut utama"}`).join("\n");
|
|
160
|
+
}
|
|
161
|
+
function skillsText(skills, commands) {
|
|
162
|
+
const s = skills.length ? skills.map((x) => `${x.name} \u2014 ${x.description.slice(0, 80)} (${x.dir})`).join("\n") : "(tidak ada skill)";
|
|
163
|
+
const c = commands.length ? commands.map((x) => `/${x.name} \u2014 ${x.description ?? ""}`).join("\n") : "(tidak ada perintah custom)";
|
|
164
|
+
return `Skill:
|
|
165
|
+
${s}
|
|
166
|
+
|
|
167
|
+
Perintah custom:
|
|
168
|
+
${c}`;
|
|
169
|
+
}
|
|
170
|
+
function sessionText(s) {
|
|
171
|
+
return [
|
|
172
|
+
`Mulai: ${s.started.toLocaleString("id-ID")}`,
|
|
173
|
+
`${s.messages} pesan \xB7 ${k(s.usage.inputTokens)} token masuk \xB7 ${k(s.usage.outputTokens)} keluar`,
|
|
174
|
+
`Model: ${s.modelRef}`,
|
|
175
|
+
`File: ${s.file}`
|
|
176
|
+
].join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/slash/memory.ts
|
|
180
|
+
import { appendFileSync, existsSync, readFileSync } from "fs";
|
|
181
|
+
function instructionFiles(cwd, home) {
|
|
182
|
+
return instructionPaths(cwd, home).filter((path) => existsSync(path)).map((path) => ({ path, lines: readFileSync(path, "utf8").split("\n").filter(Boolean).length }));
|
|
183
|
+
}
|
|
184
|
+
function appendMemory(file, text) {
|
|
185
|
+
const prefix = existsSync(file) && !readFileSync(file, "utf8").endsWith("\n") ? "\n" : "";
|
|
186
|
+
appendFileSync(file, `${prefix}- ${text.trim()}
|
|
187
|
+
`);
|
|
188
|
+
}
|
|
189
|
+
|
|
61
190
|
// src/ui/Markdown.tsx
|
|
62
191
|
import { Box, Text } from "ink";
|
|
63
192
|
|
|
@@ -116,14 +245,48 @@ function LinePrompt({ label, mask, onSubmit, onCancel }) {
|
|
|
116
245
|
] }) });
|
|
117
246
|
}
|
|
118
247
|
|
|
119
|
-
// src/ui/
|
|
248
|
+
// src/ui/ListPicker.tsx
|
|
120
249
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
121
250
|
import { useState as useState2 } from "react";
|
|
122
251
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
123
|
-
var VISIBLE =
|
|
124
|
-
function
|
|
252
|
+
var VISIBLE = 12;
|
|
253
|
+
function ListPicker({ title, items, onPick }) {
|
|
125
254
|
const [filter, setFilter] = useState2("");
|
|
126
255
|
const [index, setIndex] = useState2(0);
|
|
256
|
+
const shown = items.filter((i) => i.label.toLowerCase().includes(filter.toLowerCase()));
|
|
257
|
+
const cursor = Math.min(index, Math.max(0, shown.length - 1));
|
|
258
|
+
useInput2((input, key) => {
|
|
259
|
+
if (key.upArrow) setIndex(Math.max(0, cursor - 1));
|
|
260
|
+
else if (key.downArrow) setIndex(Math.min(shown.length - 1, cursor + 1));
|
|
261
|
+
else if (key.return) onPick(shown[cursor]?.id);
|
|
262
|
+
else if (key.escape) onPick(void 0);
|
|
263
|
+
else if (key.backspace || key.delete) {
|
|
264
|
+
setFilter((f) => f.slice(0, -1));
|
|
265
|
+
setIndex(0);
|
|
266
|
+
} else if (!key.ctrl && !key.meta && !key.tab && input) {
|
|
267
|
+
setFilter((f) => f + input);
|
|
268
|
+
setIndex(0);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
const start = Math.max(0, Math.min(cursor - 5, shown.length - VISIBLE));
|
|
272
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
273
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: `${title} (\u2191\u2193 enter \xB7 ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 esc batal)` }),
|
|
274
|
+
shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */ jsxs2(Text3, { color: start + i === cursor ? color("green") : void 0, children: [
|
|
275
|
+
`${start + i === cursor ? "\u203A" : " "} ${item.label}`,
|
|
276
|
+
item.hint ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ` ${item.hint}` }) : null
|
|
277
|
+
] }, item.id)),
|
|
278
|
+
shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tidak ada yang cocok." })
|
|
279
|
+
] });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/ui/ModelPicker.tsx
|
|
283
|
+
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
284
|
+
import { useState as useState3 } from "react";
|
|
285
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
286
|
+
var VISIBLE2 = 14;
|
|
287
|
+
function ModelPicker({ groups, current, onPick }) {
|
|
288
|
+
const [filter, setFilter] = useState3("");
|
|
289
|
+
const [index, setIndex] = useState3(0);
|
|
127
290
|
const needle = filter.toLowerCase();
|
|
128
291
|
const rows = [];
|
|
129
292
|
for (const g of groups) {
|
|
@@ -135,7 +298,7 @@ function ModelPicker({ groups, current, onPick }) {
|
|
|
135
298
|
}
|
|
136
299
|
const selectable = rows.flatMap((r, i) => r.kind === "model" ? [i] : []);
|
|
137
300
|
const cursor = selectable[Math.min(index, selectable.length - 1)] ?? -1;
|
|
138
|
-
|
|
301
|
+
useInput3((input, key) => {
|
|
139
302
|
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
140
303
|
else if (key.downArrow) setIndex((i) => Math.min(selectable.length - 1, i + 1));
|
|
141
304
|
else if (key.return) {
|
|
@@ -150,86 +313,86 @@ function ModelPicker({ groups, current, onPick }) {
|
|
|
150
313
|
setIndex(0);
|
|
151
314
|
}
|
|
152
315
|
});
|
|
153
|
-
const start = Math.max(0, Math.min(cursor - 5, rows.length -
|
|
154
|
-
return /* @__PURE__ */
|
|
155
|
-
/* @__PURE__ */
|
|
156
|
-
rows.slice(start, start +
|
|
157
|
-
(row, i) => row.kind === "header" ? /* @__PURE__ */
|
|
316
|
+
const start = Math.max(0, Math.min(cursor - 5, rows.length - VISIBLE2));
|
|
317
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
318
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: `Pilih model (ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 \u2191\u2193 enter \xB7 esc batal)` }),
|
|
319
|
+
rows.slice(start, start + VISIBLE2).map(
|
|
320
|
+
(row, i) => row.kind === "header" ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: `\u2500\u2500 ${row.text} ` }, `h${start + i}`) : /* @__PURE__ */ jsx4(Text4, { color: start + i === cursor ? color("green") : void 0, children: `${start + i === cursor ? "\u203A" : " "} ${row.label}${row.ref === current ? " (aktif)" : ""}` }, row.ref)
|
|
158
321
|
),
|
|
159
|
-
!selectable.length ? /* @__PURE__ */
|
|
160
|
-
groups.filter((g) => g.error).map((g) => /* @__PURE__ */
|
|
322
|
+
!selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Tidak ada model yang cocok." }) : null,
|
|
323
|
+
groups.filter((g) => g.error).map((g) => /* @__PURE__ */ jsx4(Text4, { color: color("yellow"), children: `${g.providerName} \u2014 ${g.error}` }, `e-${g.providerId}`))
|
|
161
324
|
] });
|
|
162
325
|
}
|
|
163
326
|
|
|
164
327
|
// src/ui/ProviderMenu.tsx
|
|
165
|
-
import { Box as
|
|
166
|
-
import { useState as
|
|
167
|
-
import { jsx as
|
|
328
|
+
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
329
|
+
import { useState as useState4 } from "react";
|
|
330
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
168
331
|
function ProviderMenu({ entries, onPick }) {
|
|
169
332
|
const rows = [...entries.map((e) => e.id), "custom"];
|
|
170
|
-
const [index, setIndex] =
|
|
171
|
-
|
|
333
|
+
const [index, setIndex] = useState4(0);
|
|
334
|
+
useInput4((_input, key) => {
|
|
172
335
|
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
173
336
|
else if (key.downArrow) setIndex((i) => Math.min(rows.length - 1, i + 1));
|
|
174
337
|
else if (key.return) onPick(rows[index]);
|
|
175
338
|
else if (key.escape) onPick(void 0);
|
|
176
339
|
});
|
|
177
|
-
return /* @__PURE__ */
|
|
178
|
-
/* @__PURE__ */
|
|
179
|
-
entries.map((e, i) => /* @__PURE__ */
|
|
340
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
341
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Provider (\u2191\u2193 enter, esc batal) \xB7 \u2713 siap \xB7 \u25CB butuh API key" }),
|
|
342
|
+
entries.map((e, i) => /* @__PURE__ */ jsxs4(Text5, { color: i === index ? color("green") : void 0, children: [
|
|
180
343
|
`${i === index ? "\u203A" : " "} ${e.ready ? "\u2713" : "\u25CB"} ${e.name}`,
|
|
181
|
-
/* @__PURE__ */
|
|
344
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${e.baseURL}` })
|
|
182
345
|
] }, e.id)),
|
|
183
|
-
/* @__PURE__ */
|
|
346
|
+
/* @__PURE__ */ jsx5(Text5, { color: index === rows.length - 1 ? color("green") : void 0, children: `${index === rows.length - 1 ? "\u203A" : " "} Custom\u2026` })
|
|
184
347
|
] });
|
|
185
348
|
}
|
|
186
349
|
|
|
187
350
|
// src/ui/PermissionPrompt.tsx
|
|
188
|
-
import { Box as
|
|
351
|
+
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
189
352
|
|
|
190
353
|
// src/ui/DiffView.tsx
|
|
191
|
-
import { Box as
|
|
192
|
-
import { jsx as
|
|
354
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
355
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
193
356
|
function DiffView({ diff }) {
|
|
194
|
-
return /* @__PURE__ */
|
|
357
|
+
return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: diff.split("\n").map((line, i) => {
|
|
195
358
|
const mark = line[6];
|
|
196
359
|
const c = mark === "+" ? color("green") : mark === "-" ? color("red") : void 0;
|
|
197
|
-
return /* @__PURE__ */
|
|
360
|
+
return /* @__PURE__ */ jsx6(Text6, { color: c, dimColor: mark !== "+" && mark !== "-", children: line }, i);
|
|
198
361
|
}) });
|
|
199
362
|
}
|
|
200
363
|
|
|
201
364
|
// src/ui/PermissionPrompt.tsx
|
|
202
|
-
import { jsx as
|
|
365
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
203
366
|
var VERB = { edit: "edit", write: "tulis", bash: "jalankan", fetch: "ambil" };
|
|
204
367
|
function PermissionPrompt({
|
|
205
368
|
request,
|
|
206
369
|
onAnswer
|
|
207
370
|
}) {
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
if (
|
|
211
|
-
else if (
|
|
212
|
-
else if (
|
|
213
|
-
else if (
|
|
371
|
+
useInput5((input, key) => {
|
|
372
|
+
const k2 = input.toLowerCase();
|
|
373
|
+
if (k2 === "y") onAnswer("yes");
|
|
374
|
+
else if (k2 === "a" && request.sessionRules?.length) onAnswer("session");
|
|
375
|
+
else if (k2 === "s") onAnswer("all");
|
|
376
|
+
else if (k2 === "n" || key.escape) onAnswer("no");
|
|
214
377
|
});
|
|
215
378
|
const rules = request.sessionRules ?? [];
|
|
216
379
|
const sessionLabel = !rules.length ? null : request.kind === "edit" ? "ya untuk semua edit di project ini, sesi ini" : `ya sesi ini untuk ${rules.join(", ")}`;
|
|
217
|
-
return /* @__PURE__ */
|
|
218
|
-
/* @__PURE__ */
|
|
219
|
-
request.preview ? /* @__PURE__ */
|
|
220
|
-
/* @__PURE__ */
|
|
221
|
-
/* @__PURE__ */
|
|
380
|
+
return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
|
|
381
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}Izinkan ${VERB[request.tool] ?? request.tool} ${request.target}?` }),
|
|
382
|
+
request.preview ? /* @__PURE__ */ jsx7(DiffView, { diff: request.preview }) : null,
|
|
383
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
384
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("green"), children: "[y] ya" }),
|
|
222
385
|
" ",
|
|
223
|
-
sessionLabel ? /* @__PURE__ */
|
|
224
|
-
/* @__PURE__ */
|
|
225
|
-
/* @__PURE__ */
|
|
386
|
+
sessionLabel ? /* @__PURE__ */ jsx7(Text7, { color: color("cyan"), children: `[a] ${sessionLabel} ` }) : null,
|
|
387
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("yellow"), children: "[s] ya semua " }),
|
|
388
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("red"), children: "[n] tidak" })
|
|
226
389
|
] })
|
|
227
390
|
] });
|
|
228
391
|
}
|
|
229
392
|
|
|
230
393
|
// src/ui/PromptInput.tsx
|
|
231
|
-
import { Box as
|
|
232
|
-
import { useState as
|
|
394
|
+
import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
395
|
+
import { useState as useState5 } from "react";
|
|
233
396
|
|
|
234
397
|
// src/ui/complete.ts
|
|
235
398
|
import fg from "fast-glob";
|
|
@@ -245,11 +408,11 @@ function completeFile(value, cwd) {
|
|
|
245
408
|
}
|
|
246
409
|
|
|
247
410
|
// src/ui/PromptInput.tsx
|
|
248
|
-
import { jsx as
|
|
411
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
249
412
|
function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
|
|
250
|
-
const [value, setValue] =
|
|
251
|
-
const [historyIndex, setHistoryIndex] =
|
|
252
|
-
const [selected, setSelected] =
|
|
413
|
+
const [value, setValue] = useState5("");
|
|
414
|
+
const [historyIndex, setHistoryIndex] = useState5(null);
|
|
415
|
+
const [selected, setSelected] = useState5(0);
|
|
253
416
|
const all = [...SLASH_COMMANDS, ...(extraCommands ?? []).filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name))];
|
|
254
417
|
const suggestions = value.startsWith("/") && !/\s/.test(value) ? all.filter((c) => c.name.startsWith(value.slice(1))).slice(0, 8) : [];
|
|
255
418
|
const highlighted = Math.min(selected, Math.max(0, suggestions.length - 1));
|
|
@@ -257,7 +420,7 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
|
|
|
257
420
|
setValue(next);
|
|
258
421
|
setSelected(0);
|
|
259
422
|
};
|
|
260
|
-
|
|
423
|
+
useInput6(
|
|
261
424
|
(input, key) => {
|
|
262
425
|
if (suggestions.length) {
|
|
263
426
|
if (key.upArrow) {
|
|
@@ -319,34 +482,34 @@ function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
|
|
|
319
482
|
},
|
|
320
483
|
{ isActive: !disabled }
|
|
321
484
|
);
|
|
322
|
-
return /* @__PURE__ */
|
|
323
|
-
/* @__PURE__ */
|
|
324
|
-
/* @__PURE__ */
|
|
485
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", children: [
|
|
486
|
+
/* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs6(Text8, { children: [
|
|
487
|
+
/* @__PURE__ */ jsx8(Text8, { color: color("green"), children: "> " }),
|
|
325
488
|
value,
|
|
326
|
-
disabled ? "" : /* @__PURE__ */
|
|
489
|
+
disabled ? "" : /* @__PURE__ */ jsx8(Text8, { inverse: true, children: " " })
|
|
327
490
|
] }) }),
|
|
328
|
-
suggestions.map((s, i) => /* @__PURE__ */
|
|
491
|
+
suggestions.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === highlighted ? color("green") : void 0, dimColor: i !== highlighted, children: `${i === highlighted ? "\u203A" : " "} /${s.name} ${s.description}` }, s.name))
|
|
329
492
|
] });
|
|
330
493
|
}
|
|
331
494
|
|
|
332
495
|
// src/ui/Spinner.tsx
|
|
333
|
-
import { Text as
|
|
334
|
-
import { useEffect, useState as
|
|
335
|
-
import { jsx as
|
|
496
|
+
import { Text as Text9 } from "ink";
|
|
497
|
+
import { useEffect, useState as useState6 } from "react";
|
|
498
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
336
499
|
var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
|
|
337
500
|
function Spinner({ label, startedAt }) {
|
|
338
|
-
const [tick, setTick] =
|
|
501
|
+
const [tick, setTick] = useState6(0);
|
|
339
502
|
useEffect(() => {
|
|
340
503
|
const id = setInterval(() => setTick((t) => t + 1), 120);
|
|
341
504
|
return () => clearInterval(id);
|
|
342
505
|
}, []);
|
|
343
506
|
const seconds = Math.floor((Date.now() - startedAt) / 1e3);
|
|
344
|
-
return /* @__PURE__ */
|
|
507
|
+
return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${label}\u2026 (${seconds} dtk \xB7 esc batal)` });
|
|
345
508
|
}
|
|
346
509
|
|
|
347
510
|
// src/ui/StatusBar.tsx
|
|
348
|
-
import { Text as
|
|
349
|
-
import { jsx as
|
|
511
|
+
import { Text as Text10 } from "ink";
|
|
512
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
350
513
|
var LABEL = {
|
|
351
514
|
default: "\u23F5 default",
|
|
352
515
|
acceptEdits: "\u23F5 accept edits",
|
|
@@ -358,87 +521,87 @@ function formatTokens(n) {
|
|
|
358
521
|
}
|
|
359
522
|
function StatusBar({ mode, tokens, busy, model }) {
|
|
360
523
|
const danger = mode === "allowAll";
|
|
361
|
-
return /* @__PURE__ */
|
|
524
|
+
return /* @__PURE__ */ jsx10(Text10, { 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"}` });
|
|
362
525
|
}
|
|
363
526
|
|
|
364
527
|
// src/ui/McpMenu.tsx
|
|
365
|
-
import { Box as
|
|
366
|
-
import { useState as
|
|
367
|
-
import { jsx as
|
|
528
|
+
import { Box as Box9, Text as Text11, useInput as useInput7 } from "ink";
|
|
529
|
+
import { useState as useState7 } from "react";
|
|
530
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
368
531
|
var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
|
|
369
532
|
function McpMenu({ items, onPick }) {
|
|
370
|
-
const [index, setIndex] =
|
|
371
|
-
|
|
533
|
+
const [index, setIndex] = useState7(0);
|
|
534
|
+
useInput7((_input, key) => {
|
|
372
535
|
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
373
536
|
else if (key.downArrow) setIndex((i) => Math.min(items.length - 1, i + 1));
|
|
374
537
|
else if (key.return) onPick(items[index]?.name);
|
|
375
538
|
else if (key.escape) onPick(void 0);
|
|
376
539
|
});
|
|
377
|
-
return /* @__PURE__ */
|
|
378
|
-
/* @__PURE__ */
|
|
379
|
-
items.map((item, i) => /* @__PURE__ */
|
|
540
|
+
return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
541
|
+
/* @__PURE__ */ jsx11(Text11, { bold: true, children: "Server MCP (enter: pasang / hapus \xB7 esc tutup) \xB7 \u2713 aktif \u25CB belum \u2717 error" }),
|
|
542
|
+
items.map((item, i) => /* @__PURE__ */ jsxs7(Text11, { color: i === index ? color("green") : void 0, children: [
|
|
380
543
|
`${i === index ? "\u203A" : " "} ${MARK[item.status]} ${item.name.padEnd(12)}`,
|
|
381
|
-
/* @__PURE__ */
|
|
544
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: item.status === "error" ? ` ${item.error ?? ""}` : ` ${item.description}` })
|
|
382
545
|
] }, item.name))
|
|
383
546
|
] });
|
|
384
547
|
}
|
|
385
548
|
|
|
386
549
|
// src/ui/PlanApproval.tsx
|
|
387
|
-
import { Box as
|
|
388
|
-
import { jsx as
|
|
550
|
+
import { Box as Box10, Text as Text12, useInput as useInput8 } from "ink";
|
|
551
|
+
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
389
552
|
function PlanApproval({ plan, onAnswer }) {
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
if (
|
|
393
|
-
else if (
|
|
394
|
-
else if (
|
|
553
|
+
useInput8((input, key) => {
|
|
554
|
+
const k2 = input.toLowerCase();
|
|
555
|
+
if (k2 === "a") onAnswer("acceptEdits");
|
|
556
|
+
else if (k2 === "y") onAnswer("default");
|
|
557
|
+
else if (k2 === "n" || key.escape) onAnswer("no");
|
|
395
558
|
});
|
|
396
|
-
return /* @__PURE__ */
|
|
397
|
-
/* @__PURE__ */
|
|
398
|
-
/* @__PURE__ */
|
|
399
|
-
/* @__PURE__ */
|
|
400
|
-
/* @__PURE__ */
|
|
559
|
+
return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
560
|
+
/* @__PURE__ */ jsx12(Text12, { bold: true, children: "Rencana dari agent" }),
|
|
561
|
+
/* @__PURE__ */ jsx12(Markdown, { text: plan }),
|
|
562
|
+
/* @__PURE__ */ jsxs8(Text12, { children: [
|
|
563
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("green"), children: "[a] ya, edit otomatis" }),
|
|
401
564
|
" ",
|
|
402
|
-
/* @__PURE__ */
|
|
565
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: "[y] ya, tanya tiap langkah" }),
|
|
403
566
|
" ",
|
|
404
|
-
/* @__PURE__ */
|
|
567
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("red"), children: "[n] belum, perbaiki dulu" })
|
|
405
568
|
] })
|
|
406
569
|
] });
|
|
407
570
|
}
|
|
408
571
|
|
|
409
572
|
// src/ui/TodoList.tsx
|
|
410
|
-
import { Box as
|
|
411
|
-
import { jsx as
|
|
573
|
+
import { Box as Box11, Text as Text13 } from "ink";
|
|
574
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
412
575
|
var MARK2 = { completed: "\u2611", in_progress: "\u25FC", pending: "\u2610" };
|
|
413
576
|
function TodoList({ items }) {
|
|
414
577
|
if (!items.length || items.every((t) => t.status === "completed")) return null;
|
|
415
|
-
return /* @__PURE__ */
|
|
578
|
+
return /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: items.map((t, i) => /* @__PURE__ */ jsx13(Text13, { dimColor: t.status === "completed", color: t.status === "in_progress" ? color("yellow") : void 0, children: `${MARK2[t.status]} ${t.content}` }, i)) });
|
|
416
579
|
}
|
|
417
580
|
|
|
418
581
|
// src/ui/ToolBlock.tsx
|
|
419
|
-
import { Box as
|
|
420
|
-
import { jsx as
|
|
582
|
+
import { Box as Box12, Text as Text14 } from "ink";
|
|
583
|
+
import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
421
584
|
var PREVIEW_LINES = 5;
|
|
422
585
|
function ToolBlock({ tool, target, output, display, isError, done, expanded, sub }) {
|
|
423
586
|
const isTask = tool === "task";
|
|
424
587
|
const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
|
|
425
588
|
const lines = (output ?? "").split("\n");
|
|
426
589
|
const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
|
|
427
|
-
return /* @__PURE__ */
|
|
428
|
-
/* @__PURE__ */
|
|
429
|
-
/* @__PURE__ */
|
|
430
|
-
/* @__PURE__ */
|
|
431
|
-
/* @__PURE__ */
|
|
432
|
-
display && !isDiff && !isTask ? /* @__PURE__ */
|
|
590
|
+
return /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 2, children: [
|
|
591
|
+
/* @__PURE__ */ jsxs9(Text14, { children: [
|
|
592
|
+
/* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
|
|
593
|
+
/* @__PURE__ */ jsx14(Text14, { bold: true, children: toolLabel(tool) }),
|
|
594
|
+
/* @__PURE__ */ jsx14(Text14, { children: ` ${target}` }),
|
|
595
|
+
display && !isDiff && !isTask ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` (${display})` }) : null
|
|
433
596
|
] }),
|
|
434
|
-
sub?.length ? /* @__PURE__ */
|
|
435
|
-
sub.slice(-6).map((s) => /* @__PURE__ */
|
|
436
|
-
sub.length > 6 ? /* @__PURE__ */
|
|
597
|
+
sub?.length ? /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 3, children: [
|
|
598
|
+
sub.slice(-6).map((s) => /* @__PURE__ */ jsx14(Text14, { dimColor: s.done && !s.isError, color: s.isError ? color("red") : void 0, children: `\u23BF ${toolLabel(s.tool)} ${s.target}` }, s.id)),
|
|
599
|
+
sub.length > 6 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: `\u2026 ${sub.length - 6} langkah sebelumnya` }) : null
|
|
437
600
|
] }) : null,
|
|
438
|
-
isTask && done ? /* @__PURE__ */
|
|
439
|
-
isDiff ? /* @__PURE__ */
|
|
440
|
-
shown.map((line, i) => /* @__PURE__ */
|
|
441
|
-
lines.length > shown.length ? /* @__PURE__ */
|
|
601
|
+
isTask && done ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : color("green"), children: `${isError ? "\u2717 gagal" : "\u2713 selesai"}${display ? ` \xB7 ${display}` : ""}` }) }) : null,
|
|
602
|
+
isDiff ? /* @__PURE__ */ jsx14(Box12, { marginLeft: 3, children: /* @__PURE__ */ jsx14(DiffView, { diff: display }) }) : output && (isError || tool === "bash") && !(isTask && !isError) ? /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 3, children: [
|
|
603
|
+
shown.map((line, i) => /* @__PURE__ */ jsx14(Text14, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
|
|
604
|
+
lines.length > shown.length ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: `\u2026 ${lines.length - shown.length} baris lagi (ctrl+o)` }) : null
|
|
442
605
|
] }) : null
|
|
443
606
|
] });
|
|
444
607
|
}
|
|
@@ -461,6 +624,11 @@ function applyEvent(t, event) {
|
|
|
461
624
|
if (last?.kind === "assistant") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
|
|
462
625
|
return { ...t, live: [...t.live, entry({ kind: "assistant", text: event.delta })] };
|
|
463
626
|
}
|
|
627
|
+
case "textReplace": {
|
|
628
|
+
const last = t.live.at(-1);
|
|
629
|
+
if (last?.kind !== "assistant") return t;
|
|
630
|
+
return { ...t, live: [...t.live.slice(0, -1), { ...last, text: event.text }] };
|
|
631
|
+
}
|
|
464
632
|
case "toolStart": {
|
|
465
633
|
const running = t.live.some((e) => e.kind === "tool" && !e.done);
|
|
466
634
|
const tool = entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false });
|
|
@@ -495,11 +663,11 @@ function applyEvent(t, event) {
|
|
|
495
663
|
}
|
|
496
664
|
|
|
497
665
|
// src/ui/App.tsx
|
|
498
|
-
import { jsx as
|
|
666
|
+
import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
499
667
|
function helpText(runtime) {
|
|
500
668
|
const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
|
|
501
669
|
return [
|
|
502
|
-
...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(
|
|
670
|
+
...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${c.description}`),
|
|
503
671
|
...custom.length ? ["", "Perintah custom:", ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
|
|
504
672
|
...runtime.skills.length ? ["", "Skill:", ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
|
|
505
673
|
"",
|
|
@@ -508,27 +676,29 @@ function helpText(runtime) {
|
|
|
508
676
|
"@file + tab lengkapi nama file \u2191\u2193 riwayat input"
|
|
509
677
|
].join("\n");
|
|
510
678
|
}
|
|
679
|
+
var INIT_PROMPT = "Pelajari project ini (struktur folder, file package/build, perintah test dan lint, konvensi kode yang terlihat). Lalu buat AGENTS.md di root project, atau perbarui bila sudah ada, berisi: cara build, test, dan lint; struktur singkat; konvensi penting. Ringkas dan faktual, hanya yang benar-benar ada di project.";
|
|
511
680
|
var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
|
|
512
681
|
function App({ runtime, initialPrompt, version }) {
|
|
513
682
|
const { exit } = useApp();
|
|
514
|
-
const [transcript, setTranscript] =
|
|
515
|
-
const [busy, setBusy] =
|
|
516
|
-
const [startedAt, setStartedAt] =
|
|
517
|
-
const [mode, setMode] =
|
|
518
|
-
const [tokens, setTokens] =
|
|
519
|
-
const [asks, setAsks] =
|
|
683
|
+
const [transcript, setTranscript] = useState8(() => ({ done: [entry({ kind: "header" })], live: [] }));
|
|
684
|
+
const [busy, setBusy] = useState8(false);
|
|
685
|
+
const [startedAt, setStartedAt] = useState8(0);
|
|
686
|
+
const [mode, setMode] = useState8(runtime.agent.permissions.mode);
|
|
687
|
+
const [tokens, setTokens] = useState8(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
|
|
688
|
+
const [asks, setAsks] = useState8([]);
|
|
520
689
|
const pending = asks[0] ?? null;
|
|
521
690
|
const askId = useRef(0);
|
|
522
|
-
const [picker, setPicker] =
|
|
523
|
-
const [providerMenu, setProviderMenu] =
|
|
524
|
-
const [prompt, setPrompt] =
|
|
525
|
-
const ask = (label, mask = false) => new Promise((
|
|
526
|
-
const [modelLabel, setModelLabel] =
|
|
527
|
-
const [history, setHistory] =
|
|
691
|
+
const [picker, setPicker] = useState8(null);
|
|
692
|
+
const [providerMenu, setProviderMenu] = useState8(null);
|
|
693
|
+
const [prompt, setPrompt] = useState8(null);
|
|
694
|
+
const ask = useCallback((label, mask = false) => new Promise((resolve2) => setPrompt({ label, mask, resolve: resolve2 })), []);
|
|
695
|
+
const [modelLabel, setModelLabel] = useState8(() => labelFor(runtime, runtime.modelRef));
|
|
696
|
+
const [history, setHistory] = useState8([]);
|
|
528
697
|
const controller = useRef(null);
|
|
529
|
-
const [todos, setTodos] =
|
|
530
|
-
const [mcpMenu, setMcpMenu] =
|
|
531
|
-
const [
|
|
698
|
+
const [todos, setTodos] = useState8(runtime.todos.items);
|
|
699
|
+
const [mcpMenu, setMcpMenu] = useState8(null);
|
|
700
|
+
const [listPicker, setListPicker] = useState8(null);
|
|
701
|
+
const [planAsk, setPlanAsk] = useState8(null);
|
|
532
702
|
useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
|
|
533
703
|
const extraCommands = [
|
|
534
704
|
...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? "perintah custom" })),
|
|
@@ -549,8 +719,8 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
549
719
|
}, []);
|
|
550
720
|
useEffect2(() => {
|
|
551
721
|
runtime.agent.onEvent = onEvent;
|
|
552
|
-
runtime.agent.askPermission = (request) => new Promise((
|
|
553
|
-
runtime.interaction.approvePlan = (plan) => new Promise((
|
|
722
|
+
runtime.agent.askPermission = (request) => new Promise((resolve2) => setAsks((q) => [...q, { id: askId.current++, request, resolve: resolve2 }]));
|
|
723
|
+
runtime.interaction.approvePlan = (plan) => new Promise((resolve2) => setPlanAsk({ plan, resolve: resolve2 }));
|
|
554
724
|
}, [runtime, onEvent]);
|
|
555
725
|
const runTurn = useCallback(
|
|
556
726
|
async (text) => {
|
|
@@ -565,7 +735,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
565
735
|
},
|
|
566
736
|
[runtime]
|
|
567
737
|
);
|
|
568
|
-
const
|
|
738
|
+
const runSlashCommand = useCallback(
|
|
569
739
|
async (name, args) => {
|
|
570
740
|
switch (name) {
|
|
571
741
|
case "help":
|
|
@@ -629,6 +799,212 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
629
799
|
setMcpMenu(items);
|
|
630
800
|
return;
|
|
631
801
|
}
|
|
802
|
+
case "new":
|
|
803
|
+
runtime.newSession();
|
|
804
|
+
setTokens(0);
|
|
805
|
+
notice("Sesi baru dimulai. Sesi sebelumnya tetap tersimpan (/resume).");
|
|
806
|
+
return;
|
|
807
|
+
case "session":
|
|
808
|
+
notice(
|
|
809
|
+
sessionText({
|
|
810
|
+
file: runtime.session.file,
|
|
811
|
+
started: runtime.startedAt,
|
|
812
|
+
messages: runtime.agent.messages.length,
|
|
813
|
+
usage: runtime.agent.totalUsage,
|
|
814
|
+
modelRef: runtime.modelRef
|
|
815
|
+
})
|
|
816
|
+
);
|
|
817
|
+
return;
|
|
818
|
+
case "resume": {
|
|
819
|
+
const others = Session.list(runtime.home, runtime.cwd).filter((s) => s.session.file !== runtime.session.file);
|
|
820
|
+
if (!others.length) {
|
|
821
|
+
notice("Belum ada sesi lain di folder ini.");
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
setListPicker({
|
|
825
|
+
title: "Lanjutkan sesi",
|
|
826
|
+
items: others.map((s) => ({
|
|
827
|
+
id: s.session.file,
|
|
828
|
+
label: `${s.mtime.toLocaleString("id-ID")} ${s.preview || "(kosong)"}`,
|
|
829
|
+
hint: `${s.session.load().length} pesan`
|
|
830
|
+
})),
|
|
831
|
+
onPick: (id) => {
|
|
832
|
+
setListPicker(null);
|
|
833
|
+
const chosen = others.find((s) => s.session.file === id);
|
|
834
|
+
if (!chosen) return;
|
|
835
|
+
runtime.resume(chosen.session);
|
|
836
|
+
const tail = runtime.agent.messages.filter((m) => (m.role === "user" || m.role === "assistant") && m.content).slice(-3);
|
|
837
|
+
notice(
|
|
838
|
+
[`Melanjutkan sesi (${runtime.agent.messages.length} pesan):`, ...tail.map((m) => `${m.role === "user" ? ">" : "\u25CF"} ${String(m.content).slice(0, 200)}`)].join(
|
|
839
|
+
"\n"
|
|
840
|
+
)
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
case "status":
|
|
847
|
+
notice(
|
|
848
|
+
statusText({
|
|
849
|
+
version,
|
|
850
|
+
modelRef: runtime.modelRef,
|
|
851
|
+
providerLabel: runtime.providerLabel(),
|
|
852
|
+
mode: runtime.agent.permissions.mode,
|
|
853
|
+
cwd: runtime.cwd,
|
|
854
|
+
mcp: runtime.mcp.states(),
|
|
855
|
+
usage: runtime.agent.totalUsage,
|
|
856
|
+
lastInputTokens: runtime.agent.lastInputTokens
|
|
857
|
+
})
|
|
858
|
+
);
|
|
859
|
+
return;
|
|
860
|
+
case "agents":
|
|
861
|
+
notice(agentsText([...BUILTIN_AGENTS, ...runtime.agentDefs]));
|
|
862
|
+
return;
|
|
863
|
+
case "skills":
|
|
864
|
+
notice(skillsText(runtime.skills, runtime.commands));
|
|
865
|
+
return;
|
|
866
|
+
case "doctor": {
|
|
867
|
+
notice("Memeriksa\u2026");
|
|
868
|
+
const c = runtime.config;
|
|
869
|
+
notice(
|
|
870
|
+
await doctorText(
|
|
871
|
+
{
|
|
872
|
+
providers: Object.keys(c.providers).map((id) => ({ id, name: providerName(c, id), ready: hasKey(c, id, runtime.env) })),
|
|
873
|
+
mcp: runtime.mcp.states(),
|
|
874
|
+
activeProvider: runtime.providerLabel()
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
which: (cmd) => spawnSync(process.platform === "win32" ? "where" : "which", [cmd]).status === 0,
|
|
878
|
+
nodeVersion: process.version,
|
|
879
|
+
listModels: () => runtime.agent.provider.listModels()
|
|
880
|
+
}
|
|
881
|
+
)
|
|
882
|
+
);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
case "permissions": {
|
|
886
|
+
const rules = runtime.agent.permissions.list();
|
|
887
|
+
if (!rules.length) {
|
|
888
|
+
notice("Belum ada izin tersimpan. Izin dari jawaban [a] dan dari config akan muncul di sini.");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
setListPicker({
|
|
892
|
+
title: "Izin aktif (enter: cabut izin sesi)",
|
|
893
|
+
items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : "sesi ini" })),
|
|
894
|
+
onPick: (id) => {
|
|
895
|
+
setListPicker(null);
|
|
896
|
+
const rule = rules.find((r) => r.rule === id);
|
|
897
|
+
if (!rule) return;
|
|
898
|
+
if (rule.source === "config") notice(`${rule.rule} berasal dari config/flag; hapus dari ~/.bccli/config.json untuk mencabutnya.`, "warn");
|
|
899
|
+
else {
|
|
900
|
+
runtime.agent.permissions.revoke(rule.rule);
|
|
901
|
+
notice(`Izin ${rule.rule} dicabut.`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
case "memory": {
|
|
908
|
+
if (!args) {
|
|
909
|
+
const files = instructionFiles(runtime.cwd, runtime.home);
|
|
910
|
+
notice(files.length ? files.map((f) => `${f.path} (${f.lines} baris)`).join("\n") : "Belum ada AGENTS.md / BCCLI.md. Tambah dengan /memory <teks>.");
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const global = /^global\s/i.test(args);
|
|
914
|
+
const text = global ? args.replace(/^global\s+/i, "") : args;
|
|
915
|
+
const file = global ? join(runtime.home, "BCCLI.md") : join(runtime.cwd, "AGENTS.md");
|
|
916
|
+
try {
|
|
917
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
918
|
+
appendMemory(file, text);
|
|
919
|
+
} catch (error) {
|
|
920
|
+
notice(`Gagal menulis ${file}: ${error.message}`, "error");
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
runtime.rebuildSystemPrompt();
|
|
924
|
+
notice(`Ditambahkan ke ${file}.`);
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
case "init":
|
|
928
|
+
void runTurn(INIT_PROMPT);
|
|
929
|
+
return;
|
|
930
|
+
case "login": {
|
|
931
|
+
const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
932
|
+
const key = await ask(`API key untuk ${runtime.providerLabel()}`, true);
|
|
933
|
+
if (!key) return;
|
|
934
|
+
const result = await runtime.addProviderKey(id, key);
|
|
935
|
+
if (!result.ok) {
|
|
936
|
+
notice(result.error, "error");
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
runtime.setModel(runtime.modelRef);
|
|
940
|
+
notice(`Key ${runtime.providerLabel()} tersimpan \xB7 ${result.models} model.`);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
case "logout": {
|
|
944
|
+
const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
945
|
+
removeCredential(id, runtime.env);
|
|
946
|
+
const envName = runtime.config.providers[id]?.apiKeyEnv;
|
|
947
|
+
if (envName && runtime.env[envName]) {
|
|
948
|
+
notice(`Key tersimpan dihapus, tapi ${envName} masih ada di environment dan tetap dipakai.`, "warn");
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
try {
|
|
952
|
+
runtime.setModel(runtime.modelRef);
|
|
953
|
+
} catch (error) {
|
|
954
|
+
const fail = async () => {
|
|
955
|
+
throw error;
|
|
956
|
+
};
|
|
957
|
+
runtime.agent.provider = { chat: fail, listModels: fail };
|
|
958
|
+
}
|
|
959
|
+
notice(`Key ${runtime.providerLabel()} dihapus. Pakai /login atau /provider untuk menambah lagi.`);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
case "diff":
|
|
963
|
+
notice(await gitDiff(runtime.cwd));
|
|
964
|
+
return;
|
|
965
|
+
case "undo": {
|
|
966
|
+
const result = await runtime.checkpoints.undo();
|
|
967
|
+
if (!result) {
|
|
968
|
+
notice("Tidak ada edit file yang bisa dibatalkan.");
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
const rel = (p) => relative(runtime.cwd, p) || p;
|
|
972
|
+
notice(
|
|
973
|
+
[
|
|
974
|
+
result.failed.length ? `Gagal dikembalikan: ${result.failed.map(rel).join(", ")}` : "",
|
|
975
|
+
result.restored.length ? `Dikembalikan: ${result.restored.map(rel).join(", ")}` : "",
|
|
976
|
+
result.deleted.length ? `Dihapus (file baru): ${result.deleted.map(rel).join(", ")}` : "",
|
|
977
|
+
result.skipped.length ? `Terlalu besar untuk disimpan, tidak diubah: ${result.skipped.map(rel).join(", ")}` : "",
|
|
978
|
+
"Catatan: perubahan lewat perintah bash tidak bisa di-undo."
|
|
979
|
+
].filter(Boolean).join("\n")
|
|
980
|
+
);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
case "copy": {
|
|
984
|
+
const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
|
|
985
|
+
if (!last || typeof last.content !== "string") {
|
|
986
|
+
notice("Belum ada jawaban untuk disalin.");
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
process.stdout.write(osc52(last.content, !!process.env.TMUX));
|
|
990
|
+
const file = join(runtime.home, "last-answer.md");
|
|
991
|
+
mkdirSync(runtime.home, { recursive: true });
|
|
992
|
+
writeFileSync(file, last.content);
|
|
993
|
+
notice(`Disalin ke clipboard (bila terminal mendukung OSC 52) dan disimpan di ${file}.`);
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
case "export": {
|
|
997
|
+
const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
|
|
998
|
+
const file = resolve(runtime.cwd, name2);
|
|
999
|
+
try {
|
|
1000
|
+
writeFileSync(file, exportMarkdown(runtime.agent.messages, `Sesi BCCLI ${runtime.startedAt.toLocaleString("id-ID")}`));
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
notice(`Gagal menulis ${file}: ${error.message}`, "error");
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
notice(`Percakapan disimpan di ${file}.`);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
632
1008
|
case "provider": {
|
|
633
1009
|
const c = runtime.config;
|
|
634
1010
|
setProviderMenu(
|
|
@@ -640,7 +1016,11 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
640
1016
|
notice(`Perintah tidak dikenal: /${name}. Ketik /help.`, "warn");
|
|
641
1017
|
}
|
|
642
1018
|
},
|
|
643
|
-
[runtime, notice, exit]
|
|
1019
|
+
[runtime, notice, exit, version, runTurn, ask]
|
|
1020
|
+
);
|
|
1021
|
+
const runSlash = useCallback(
|
|
1022
|
+
(name, args) => runSlashCommand(name, args).catch((error) => notice(`/${name}: ${error.message}`, "error")),
|
|
1023
|
+
[runSlashCommand, notice]
|
|
644
1024
|
);
|
|
645
1025
|
const submit = useCallback(
|
|
646
1026
|
(text) => {
|
|
@@ -657,7 +1037,7 @@ function App({ runtime, initialPrompt, version }) {
|
|
|
657
1037
|
else if (skill) {
|
|
658
1038
|
let body;
|
|
659
1039
|
try {
|
|
660
|
-
body = parseFrontmatter(
|
|
1040
|
+
body = parseFrontmatter(readFileSync2(skill.file, "utf8")).body;
|
|
661
1041
|
} catch (error) {
|
|
662
1042
|
notice(`Skill ${skill.name} tidak bisa dibaca: ${error.code ?? error.message}`, "error");
|
|
663
1043
|
return;
|
|
@@ -676,7 +1056,7 @@ ARGUMENTS: ${slash.args}`);
|
|
|
676
1056
|
submit(initialPrompt);
|
|
677
1057
|
}
|
|
678
1058
|
}, [initialPrompt, submit]);
|
|
679
|
-
|
|
1059
|
+
useInput9((input, key) => {
|
|
680
1060
|
if (key.tab && key.shift) {
|
|
681
1061
|
const next = nextMode(runtime.agent.permissions.mode);
|
|
682
1062
|
runtime.agent.permissions.mode = next;
|
|
@@ -750,31 +1130,31 @@ ${t.output}`);
|
|
|
750
1130
|
const renderEntry = (e) => {
|
|
751
1131
|
switch (e.kind) {
|
|
752
1132
|
case "header":
|
|
753
|
-
return /* @__PURE__ */
|
|
754
|
-
/* @__PURE__ */
|
|
755
|
-
/* @__PURE__ */
|
|
756
|
-
/* @__PURE__ */
|
|
1133
|
+
return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs10(Text15, { children: [
|
|
1134
|
+
/* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), bold: true, children: " \u273B " }),
|
|
1135
|
+
/* @__PURE__ */ jsx15(Text15, { bold: true, children: `BCCLI ${version}` }),
|
|
1136
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \xB7 ${runtime.modelRef} (${runtime.providerLabel()}) \xB7 ${cwd}` })
|
|
757
1137
|
] }) }, e.id);
|
|
758
1138
|
case "user":
|
|
759
|
-
return /* @__PURE__ */
|
|
1139
|
+
return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: `> ${e.text}` }) }, e.id);
|
|
760
1140
|
case "assistant":
|
|
761
1141
|
if (!e.text.trim()) return null;
|
|
762
|
-
return /* @__PURE__ */
|
|
763
|
-
/* @__PURE__ */
|
|
764
|
-
/* @__PURE__ */
|
|
1142
|
+
return /* @__PURE__ */ jsxs10(Box13, { marginTop: 1, children: [
|
|
1143
|
+
/* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), children: "\u25CF " }),
|
|
1144
|
+
/* @__PURE__ */ jsx15(Markdown, { text: e.text.replace(/^\s+/, "") })
|
|
765
1145
|
] }, e.id);
|
|
766
1146
|
case "tool":
|
|
767
|
-
return /* @__PURE__ */
|
|
1147
|
+
return /* @__PURE__ */ jsx15(ToolBlock, { tool: e.tool, target: e.target, output: e.output, display: e.display, isError: e.isError, done: e.done, sub: e.sub }, e.id);
|
|
768
1148
|
case "notice":
|
|
769
|
-
return /* @__PURE__ */
|
|
1149
|
+
return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { color: e.tone === "error" ? color("red") : e.tone === "warn" ? color("yellow") : void 0, dimColor: e.tone === "info", children: e.text }) }, e.id);
|
|
770
1150
|
}
|
|
771
1151
|
};
|
|
772
|
-
return /* @__PURE__ */
|
|
773
|
-
/* @__PURE__ */
|
|
1152
|
+
return /* @__PURE__ */ jsxs10(Box13, { flexDirection: "column", children: [
|
|
1153
|
+
/* @__PURE__ */ jsx15(Static, { items: transcript.done, children: renderEntry }),
|
|
774
1154
|
transcript.live.map(renderEntry),
|
|
775
|
-
busy && !pending && !planAsk ? /* @__PURE__ */
|
|
776
|
-
pending ? /* @__PURE__ */
|
|
777
|
-
picker ? /* @__PURE__ */
|
|
1155
|
+
busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: "Berpikir", startedAt }) }) : null,
|
|
1156
|
+
pending ? /* @__PURE__ */ jsx15(PermissionPrompt, { request: pending.request, onAnswer: answer }, pending.id) : null,
|
|
1157
|
+
picker ? /* @__PURE__ */ jsx15(
|
|
778
1158
|
ModelPicker,
|
|
779
1159
|
{
|
|
780
1160
|
groups: picker,
|
|
@@ -785,9 +1165,10 @@ ${t.output}`);
|
|
|
785
1165
|
}
|
|
786
1166
|
}
|
|
787
1167
|
) : null,
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1168
|
+
listPicker ? /* @__PURE__ */ jsx15(ListPicker, { title: listPicker.title, items: listPicker.items, onPick: listPicker.onPick }) : null,
|
|
1169
|
+
mcpMenu ? /* @__PURE__ */ jsx15(McpMenu, { items: mcpMenu, onPick: (n) => void pickMcp(n) }) : null,
|
|
1170
|
+
providerMenu ? /* @__PURE__ */ jsx15(ProviderMenu, { entries: providerMenu, onPick: (id) => void pickProvider(id) }) : null,
|
|
1171
|
+
prompt ? /* @__PURE__ */ jsx15(
|
|
791
1172
|
LinePrompt,
|
|
792
1173
|
{
|
|
793
1174
|
label: prompt.label,
|
|
@@ -802,7 +1183,7 @@ ${t.output}`);
|
|
|
802
1183
|
}
|
|
803
1184
|
}
|
|
804
1185
|
) : null,
|
|
805
|
-
planAsk ? /* @__PURE__ */
|
|
1186
|
+
planAsk ? /* @__PURE__ */ jsx15(
|
|
806
1187
|
PlanApproval,
|
|
807
1188
|
{
|
|
808
1189
|
plan: planAsk.plan,
|
|
@@ -813,18 +1194,18 @@ ${t.output}`);
|
|
|
813
1194
|
}
|
|
814
1195
|
}
|
|
815
1196
|
) : null,
|
|
816
|
-
/* @__PURE__ */
|
|
817
|
-
/* @__PURE__ */
|
|
818
|
-
/* @__PURE__ */
|
|
1197
|
+
/* @__PURE__ */ jsx15(TodoList, { items: todos }),
|
|
1198
|
+
/* @__PURE__ */ jsx15(PromptInput, { disabled: busy || !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: submit, extraCommands }),
|
|
1199
|
+
/* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel })
|
|
819
1200
|
] });
|
|
820
1201
|
}
|
|
821
1202
|
|
|
822
1203
|
// src/ui/index.tsx
|
|
823
|
-
import { jsx as
|
|
1204
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
824
1205
|
async function startInteractive(rt, opts) {
|
|
825
1206
|
if (opts.resume) {
|
|
826
|
-
const { Session } = await import("./session-7UB77CEG.js");
|
|
827
|
-
const list =
|
|
1207
|
+
const { Session: Session2 } = await import("./session-7UB77CEG.js");
|
|
1208
|
+
const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
|
|
828
1209
|
if (!list.length) console.log("Belum ada sesi di folder ini; memulai sesi baru.");
|
|
829
1210
|
else {
|
|
830
1211
|
list.forEach((s, i) => {
|
|
@@ -852,7 +1233,7 @@ async function startInteractive(rt, opts) {
|
|
|
852
1233
|
rl.close();
|
|
853
1234
|
}
|
|
854
1235
|
void rt.startMcp(plan.start);
|
|
855
|
-
const instance = render(/* @__PURE__ */
|
|
1236
|
+
const instance = render(/* @__PURE__ */ jsx16(App, { runtime: rt, initialPrompt: opts.initialPrompt, version: opts.version }), { exitOnCtrlC: true });
|
|
856
1237
|
await instance.waitUntilExit();
|
|
857
1238
|
await rt.mcp.stop();
|
|
858
1239
|
return 0;
|