@botconnector/bccli 0.3.0-beta.3 → 0.4.0-beta.10
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 +75 -2
- package/dist/args-AWLROI65.js +12 -0
- package/dist/{chunk-Z5JM76EL.js → chunk-2MGINW4O.js} +4 -1
- package/dist/{chunk-5C6NQFSI.js → chunk-7S6JXQCH.js} +1 -1
- package/dist/{chunk-XQLXE666.js → chunk-BFZXRKW5.js} +1 -1
- package/dist/{chunk-RDQ7HPWS.js → chunk-BJ337ABK.js} +18 -4
- package/dist/chunk-CLN4Z5E3.js +60 -0
- package/dist/{chunk-TV6LEHOH.js → chunk-DX7CZTBB.js} +1 -1
- package/dist/{chunk-5XN44Q55.js → chunk-ELML4KVQ.js} +7 -1
- package/dist/{chunk-R37QWDTL.js → chunk-IOWDWOXS.js} +2 -2
- package/dist/chunk-KV27KTOX.js +254 -0
- package/dist/{chunk-5ISYVAXI.js → chunk-U7GK73CK.js} +90 -15
- package/dist/cli.js +328 -135
- package/dist/{config-XPWPGNZJ.js → config-YUNKPCNB.js} +2 -2
- package/dist/integrationCli-GEIMUL3M.js +347 -0
- package/dist/{login-WJMGVDRM.js → login-4C262MEI.js} +1 -1
- package/dist/{mcpCli-BV3IGR47.js → mcpCli-DZWQPTOS.js} +3 -3
- package/dist/providerCli-Q7LRM2NP.js +12 -0
- package/dist/ui-6GTQUJOE.js +1415 -0
- package/package.json +2 -1
- package/dist/args-EVIFNGPQ.js +0 -11
- package/dist/chunk-TBPPXOOZ.js +0 -165
- package/dist/providerCli-SJRCBSRE.js +0 -11
- package/dist/ui-HAW5NMFA.js +0 -867
|
@@ -0,0 +1,1415 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
BUILTIN_AGENTS,
|
|
4
|
+
ignorePatterns,
|
|
5
|
+
instructionPaths,
|
|
6
|
+
parseFrontmatter,
|
|
7
|
+
toolLabel
|
|
8
|
+
} from "./chunk-KV27KTOX.js";
|
|
9
|
+
import {
|
|
10
|
+
nextMode
|
|
11
|
+
} from "./chunk-ELML4KVQ.js";
|
|
12
|
+
import {
|
|
13
|
+
hasKey,
|
|
14
|
+
providerName,
|
|
15
|
+
writeGlobalConfig
|
|
16
|
+
} from "./chunk-7S6JXQCH.js";
|
|
17
|
+
import {
|
|
18
|
+
REASONING_LEVELS,
|
|
19
|
+
assertReasoningSupported,
|
|
20
|
+
parseReasoningLevel,
|
|
21
|
+
supportedReasoningLevels
|
|
22
|
+
} from "./chunk-CLN4Z5E3.js";
|
|
23
|
+
import {
|
|
24
|
+
Session
|
|
25
|
+
} from "./chunk-SJH3YLW4.js";
|
|
26
|
+
import {
|
|
27
|
+
CATALOG,
|
|
28
|
+
fillTemplate
|
|
29
|
+
} from "./chunk-DX7CZTBB.js";
|
|
30
|
+
import {
|
|
31
|
+
addGlobalServer,
|
|
32
|
+
globalMcpPath,
|
|
33
|
+
readMcpFile,
|
|
34
|
+
removeGlobalServer
|
|
35
|
+
} from "./chunk-BFZXRKW5.js";
|
|
36
|
+
import {
|
|
37
|
+
PRESETS,
|
|
38
|
+
removeCredential
|
|
39
|
+
} from "./chunk-2MGINW4O.js";
|
|
40
|
+
|
|
41
|
+
// src/ui/index.tsx
|
|
42
|
+
import { render } from "ink";
|
|
43
|
+
|
|
44
|
+
// src/ui/App.tsx
|
|
45
|
+
import { execFile as execFile2 } from "child_process";
|
|
46
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
47
|
+
import { homedir } from "os";
|
|
48
|
+
import { dirname, join, relative, resolve } from "path";
|
|
49
|
+
import { Box as Box13, Static, Text as Text15, useApp, useInput as useInput9 } from "ink";
|
|
50
|
+
import { useCallback, useEffect as useEffect2, useRef, useState as useState8 } from "react";
|
|
51
|
+
|
|
52
|
+
// src/commands.ts
|
|
53
|
+
var SLASH_COMMANDS = [
|
|
54
|
+
{ name: "help", description: "daftar perintah dan pintasan" },
|
|
55
|
+
{ name: "model", description: "ganti model" },
|
|
56
|
+
{ name: "reasoning", description: "atur reasoning: auto/off/low/medium/high/max" },
|
|
57
|
+
{ name: "provider", description: "tambah/pilih provider AI" },
|
|
58
|
+
{ name: "mcp", description: "pasang/kelola server MCP" },
|
|
59
|
+
{ name: "new", description: "sesi baru (sesi lama tetap tersimpan)" },
|
|
60
|
+
{ name: "resume", description: "lanjutkan sesi lain di folder ini" },
|
|
61
|
+
{ name: "session", description: "info sesi ini" },
|
|
62
|
+
{ name: "status", description: "versi, model, mode izin, MCP, konteks" },
|
|
63
|
+
{ name: "permissions", description: "lihat/cabut izin" },
|
|
64
|
+
{ name: "undo", description: "batalkan edit file giliran terakhir" },
|
|
65
|
+
{ name: "diff", description: "git diff project" },
|
|
66
|
+
{ name: "copy", description: "salin jawaban terakhir" },
|
|
67
|
+
{ name: "export", description: "simpan percakapan ke markdown" },
|
|
68
|
+
{ name: "memory", description: "instruksi project (/memory <teks>, /memory global <teks>)" },
|
|
69
|
+
{ name: "init", description: "buat/perbarui AGENTS.md untuk project ini" },
|
|
70
|
+
{ name: "agents", description: "daftar subagent" },
|
|
71
|
+
{ name: "skills", description: "daftar skill dan perintah custom" },
|
|
72
|
+
{ name: "doctor", description: "cek kesehatan instalasi" },
|
|
73
|
+
{ name: "login", description: "simpan API key provider aktif" },
|
|
74
|
+
{ name: "logout", description: "hapus API key provider aktif" },
|
|
75
|
+
{ name: "clear", description: "mulai percakapan baru" },
|
|
76
|
+
{ name: "compact", description: "ringkas percakapan" },
|
|
77
|
+
{ name: "cost", description: "pemakaian token sesi ini" },
|
|
78
|
+
{ name: "exit", description: "keluar" }
|
|
79
|
+
];
|
|
80
|
+
function parseSlash(text) {
|
|
81
|
+
if (!text.startsWith("/")) return void 0;
|
|
82
|
+
const [name, ...rest] = text.slice(1).trim().split(/\s+/);
|
|
83
|
+
return { name: name.toLowerCase(), args: rest.join(" ") };
|
|
84
|
+
}
|
|
85
|
+
function expandCommand(def, args) {
|
|
86
|
+
if (def.body.includes("$ARGUMENTS")) return def.body.replaceAll("$ARGUMENTS", args);
|
|
87
|
+
return args ? `${def.body}
|
|
88
|
+
|
|
89
|
+
${args}` : def.body;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/slash/copy.ts
|
|
93
|
+
function osc52(text, inTmux) {
|
|
94
|
+
const seq = `\x1B]52;c;${Buffer.from(text).toString("base64")}\x07`;
|
|
95
|
+
return inTmux ? `\x1BPtmux;${seq.replaceAll("\x1B", "\x1B\x1B")}\x1B\\` : seq;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/slash/diff.ts
|
|
99
|
+
import { execFile } from "child_process";
|
|
100
|
+
function git(cwd, args, bin = "git") {
|
|
101
|
+
return new Promise((resolve2) => {
|
|
102
|
+
execFile(
|
|
103
|
+
bin,
|
|
104
|
+
args,
|
|
105
|
+
{ cwd, maxBuffer: 20 * 1024 * 1024 },
|
|
106
|
+
(error, stdout, stderr) => resolve2({ ok: !error, out: error ? stderr : stdout, missing: error?.code === "ENOENT" })
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async function gitDiff(cwd, maxLines = 400, bin = "git") {
|
|
111
|
+
const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], bin);
|
|
112
|
+
if (inside.missing) return "git tidak terpasang atau tidak ada di PATH, jadi /diff tidak bisa dipakai.";
|
|
113
|
+
if (!inside.ok || inside.out.trim() !== "true") return "Folder ini bukan repository git, jadi tidak ada diff.";
|
|
114
|
+
const base = (await git(cwd, ["rev-parse", "--verify", "-q", "HEAD"])).ok ? "HEAD" : "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
115
|
+
const [stat, diff, untracked] = await Promise.all([
|
|
116
|
+
git(cwd, ["diff", "--stat", base]),
|
|
117
|
+
git(cwd, ["diff", base]),
|
|
118
|
+
git(cwd, ["ls-files", "--others", "--exclude-standard"])
|
|
119
|
+
]);
|
|
120
|
+
const fresh = untracked.out.split("\n").filter(Boolean);
|
|
121
|
+
if (!diff.out.trim() && !fresh.length) return "Tidak ada perubahan yang belum di-commit.";
|
|
122
|
+
const lines = diff.out.trimEnd().split("\n");
|
|
123
|
+
const more = lines.length > maxLines ? `
|
|
124
|
+
\u2026 ${lines.length - maxLines} baris lagi` : "";
|
|
125
|
+
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");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/slash/doctor.ts
|
|
129
|
+
async function doctorText(input, deps) {
|
|
130
|
+
const major = Number(deps.nodeVersion.replace(/^v/, "").split(".")[0]);
|
|
131
|
+
const lines = [major >= 22 ? `\u2713 Node ${deps.nodeVersion}` : `\u2717 Node ${deps.nodeVersion} (butuh \u2265 22)`];
|
|
132
|
+
for (const p of input.providers) lines.push(p.ready ? `\u2713 ${p.name}` : `\u25CB ${p.name} (belum ada key)`);
|
|
133
|
+
const cmds = ["rg", "git", "npx", "uvx"];
|
|
134
|
+
const [connection, found] = await Promise.all([
|
|
135
|
+
deps.listModels().then(
|
|
136
|
+
(models) => `\u2713 Koneksi ${input.activeProvider} (${models.length} model)`,
|
|
137
|
+
(error) => `\u2717 Koneksi ${input.activeProvider}: ${error.message}`
|
|
138
|
+
),
|
|
139
|
+
Promise.all(cmds.map(async (cmd) => deps.which(cmd)))
|
|
140
|
+
]);
|
|
141
|
+
lines.push(connection);
|
|
142
|
+
for (const m of input.mcp) lines.push(m.status === "error" ? `\u2717 MCP ${m.name}: ${m.error ?? ""}` : `\u2713 MCP ${m.name} (${m.status})`);
|
|
143
|
+
for (const [i, cmd] of cmds.entries()) lines.push(`${found[i] ? "\u2713" : "\u2717"} ${cmd}`);
|
|
144
|
+
return lines.join("\n");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/slash/export.ts
|
|
148
|
+
function exportMarkdown(messages, title) {
|
|
149
|
+
const parts = [`# ${title}`, ""];
|
|
150
|
+
for (const m of messages) {
|
|
151
|
+
if (m.role === "user") parts.push("## User", "", String(m.content), "");
|
|
152
|
+
else if (m.role === "assistant") {
|
|
153
|
+
for (const c of m.tool_calls ?? []) parts.push(`\u23BF ${c.function.name} ${c.function.arguments.slice(0, 200)}`, "");
|
|
154
|
+
if (m.content) parts.push("## BCCLI", "", m.content, "");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return parts.join("\n");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/slash/info.ts
|
|
161
|
+
var k = (n) => n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
162
|
+
function statusText(s) {
|
|
163
|
+
const ready = s.mcp.filter((m) => m.status === "ready").length;
|
|
164
|
+
const errors = s.mcp.filter((m) => m.status === "error").length;
|
|
165
|
+
return [
|
|
166
|
+
`BCCLI ${s.version}`,
|
|
167
|
+
`Model: ${s.modelRef} (${s.providerLabel})`,
|
|
168
|
+
`Mode izin: ${s.mode}`,
|
|
169
|
+
`Folder: ${s.cwd}`,
|
|
170
|
+
`MCP: ${ready} aktif, ${errors} error`,
|
|
171
|
+
`Token sesi: ${k(s.usage.inputTokens)} masuk \xB7 ${k(s.usage.outputTokens)} keluar`,
|
|
172
|
+
`Konteks: \xB1${Math.round(s.lastInputTokens / 128e3 * 100)}% dari 128k`
|
|
173
|
+
].join("\n");
|
|
174
|
+
}
|
|
175
|
+
function agentsText(defs) {
|
|
176
|
+
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");
|
|
177
|
+
}
|
|
178
|
+
function skillsText(skills, commands) {
|
|
179
|
+
const s = skills.length ? skills.map((x) => `${x.name} \u2014 ${x.description.slice(0, 80)} (${x.dir})`).join("\n") : "(tidak ada skill)";
|
|
180
|
+
const c = commands.length ? commands.map((x) => `/${x.name} \u2014 ${x.description ?? ""}`).join("\n") : "(tidak ada perintah custom)";
|
|
181
|
+
return `Skill:
|
|
182
|
+
${s}
|
|
183
|
+
|
|
184
|
+
Perintah custom:
|
|
185
|
+
${c}`;
|
|
186
|
+
}
|
|
187
|
+
function sessionText(s) {
|
|
188
|
+
return [
|
|
189
|
+
`Mulai: ${s.started.toLocaleString("id-ID")}`,
|
|
190
|
+
`${s.messages} pesan \xB7 ${k(s.usage.inputTokens)} token masuk \xB7 ${k(s.usage.outputTokens)} keluar`,
|
|
191
|
+
`Model: ${s.modelRef}`,
|
|
192
|
+
`File: ${s.file}`
|
|
193
|
+
].join("\n");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/slash/memory.ts
|
|
197
|
+
import { appendFileSync, existsSync, readFileSync } from "fs";
|
|
198
|
+
function instructionFiles(cwd, home) {
|
|
199
|
+
return instructionPaths(cwd, home).filter((path) => existsSync(path)).map((path) => ({ path, lines: readFileSync(path, "utf8").split("\n").filter(Boolean).length }));
|
|
200
|
+
}
|
|
201
|
+
function appendMemory(file, text) {
|
|
202
|
+
const prefix = existsSync(file) && !readFileSync(file, "utf8").endsWith("\n") ? "\n" : "";
|
|
203
|
+
appendFileSync(file, `${prefix}- ${text.trim()}
|
|
204
|
+
`);
|
|
205
|
+
}
|
|
206
|
+
function parseMemoryArgs(args) {
|
|
207
|
+
const match = /^global(?:\s+|$)/i.exec(args.trim());
|
|
208
|
+
return match ? { global: true, text: args.trim().slice(match[0].length).trim() } : { global: false, text: args.trim() };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/ui/Markdown.tsx
|
|
212
|
+
import { Box, Text } from "ink";
|
|
213
|
+
|
|
214
|
+
// src/ui/theme.ts
|
|
215
|
+
function color(name) {
|
|
216
|
+
return process.env.NO_COLOR ? void 0 : name;
|
|
217
|
+
}
|
|
218
|
+
var ACCENT = "green";
|
|
219
|
+
|
|
220
|
+
// src/ui/Markdown.tsx
|
|
221
|
+
import { jsx } from "react/jsx-runtime";
|
|
222
|
+
function inline(line) {
|
|
223
|
+
return line.split(/(\*\*[^*]+\*\*|`[^`]+`)/g).map((part, i) => {
|
|
224
|
+
if (part.length > 4 && part.startsWith("**") && part.endsWith("**")) {
|
|
225
|
+
return /* @__PURE__ */ jsx(Text, { bold: true, children: part.slice(2, -2) }, i);
|
|
226
|
+
}
|
|
227
|
+
if (part.length > 2 && part.startsWith("`") && part.endsWith("`")) {
|
|
228
|
+
return /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: part.slice(1, -1) }, i);
|
|
229
|
+
}
|
|
230
|
+
return part;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function Markdown({ text }) {
|
|
234
|
+
let inCode = false;
|
|
235
|
+
const rows = [];
|
|
236
|
+
text.split("\n").forEach((line, i) => {
|
|
237
|
+
if (line.trimStart().startsWith("```")) {
|
|
238
|
+
inCode = !inCode;
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
rows.push(
|
|
242
|
+
inCode ? /* @__PURE__ */ jsx(Text, { color: color("cyan"), children: ` ${line}` }, i) : /* @__PURE__ */ jsx(Text, { children: line ? inline(line) : " " }, i)
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/ui/LinePrompt.tsx
|
|
249
|
+
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
250
|
+
import { useState } from "react";
|
|
251
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
252
|
+
function LinePrompt({ label, mask, onSubmit, onCancel }) {
|
|
253
|
+
const [value, setValue] = useState("");
|
|
254
|
+
useInput((input, key) => {
|
|
255
|
+
if (key.return) onSubmit(value.trim());
|
|
256
|
+
else if (key.escape) onCancel();
|
|
257
|
+
else if (key.backspace || key.delete) setValue((v) => v.slice(0, -1));
|
|
258
|
+
else if (!key.ctrl && !key.meta && !key.tab && !key.upArrow && !key.downArrow) setValue((v) => v + input.replace(/[\r\n]/g, ""));
|
|
259
|
+
});
|
|
260
|
+
const shown = mask ? "\u2022".repeat(value.length) : value;
|
|
261
|
+
return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: /* @__PURE__ */ jsxs(Text2, { children: [
|
|
262
|
+
`${label}: `,
|
|
263
|
+
shown,
|
|
264
|
+
/* @__PURE__ */ jsx2(Text2, { inverse: true, children: " " }),
|
|
265
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " enter simpan \xB7 esc batal" })
|
|
266
|
+
] }) });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/ui/ListPicker.tsx
|
|
270
|
+
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
271
|
+
import { useState as useState2 } from "react";
|
|
272
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
273
|
+
var VISIBLE = 12;
|
|
274
|
+
function ListPicker({ title, items, onPick }) {
|
|
275
|
+
const [filter, setFilter] = useState2("");
|
|
276
|
+
const [index, setIndex] = useState2(0);
|
|
277
|
+
const shown = items.filter((i) => i.label.toLowerCase().includes(filter.toLowerCase()));
|
|
278
|
+
const cursor = Math.min(index, Math.max(0, shown.length - 1));
|
|
279
|
+
useInput2((input, key) => {
|
|
280
|
+
if (key.upArrow) setIndex(Math.max(0, cursor - 1));
|
|
281
|
+
else if (key.downArrow) setIndex(Math.min(shown.length - 1, cursor + 1));
|
|
282
|
+
else if (key.return && !shown[cursor]?.disabled) onPick(shown[cursor]?.id);
|
|
283
|
+
else if (key.escape) onPick(void 0);
|
|
284
|
+
else if (key.backspace || key.delete) {
|
|
285
|
+
setFilter((f) => f.slice(0, -1));
|
|
286
|
+
setIndex(0);
|
|
287
|
+
} else if (!key.ctrl && !key.meta && !key.tab && input) {
|
|
288
|
+
setFilter((f) => f + input);
|
|
289
|
+
setIndex(0);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
const start = Math.max(0, Math.min(cursor - 5, shown.length - VISIBLE));
|
|
293
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
294
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: `${title} (\u2191\u2193 enter \xB7 ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 esc batal)` }),
|
|
295
|
+
shown.slice(start, start + VISIBLE).map((item, i) => /* @__PURE__ */ jsxs2(Text3, { color: start + i === cursor && !item.disabled ? color("green") : void 0, dimColor: item.disabled, children: [
|
|
296
|
+
`${start + i === cursor ? "\u203A" : " "} ${item.label}`,
|
|
297
|
+
item.hint ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ` ${item.hint}` }) : null
|
|
298
|
+
] }, item.id)),
|
|
299
|
+
shown.length ? null : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Tidak ada yang cocok." })
|
|
300
|
+
] });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/ui/ModelPicker.tsx
|
|
304
|
+
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
305
|
+
import { useState as useState3 } from "react";
|
|
306
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
307
|
+
var VISIBLE2 = 14;
|
|
308
|
+
function ModelPicker({ groups, current, onPick }) {
|
|
309
|
+
const [filter, setFilter] = useState3("");
|
|
310
|
+
const [index, setIndex] = useState3(0);
|
|
311
|
+
const needle = filter.toLowerCase();
|
|
312
|
+
const rows = [];
|
|
313
|
+
for (const g of groups) {
|
|
314
|
+
if (g.error) continue;
|
|
315
|
+
const models = g.models.filter((m) => m.toLowerCase().includes(needle));
|
|
316
|
+
if (!models.length) continue;
|
|
317
|
+
rows.push({ kind: "header", text: g.providerName });
|
|
318
|
+
for (const m of models) rows.push({ kind: "model", ref: `${g.providerId}/${m}`, label: m });
|
|
319
|
+
}
|
|
320
|
+
const selectable = rows.flatMap((r, i) => r.kind === "model" ? [i] : []);
|
|
321
|
+
const cursor = selectable[Math.min(index, selectable.length - 1)] ?? -1;
|
|
322
|
+
useInput3((input, key) => {
|
|
323
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
324
|
+
else if (key.downArrow) setIndex((i) => Math.min(selectable.length - 1, i + 1));
|
|
325
|
+
else if (key.return) {
|
|
326
|
+
const row = rows[cursor];
|
|
327
|
+
if (row?.kind === "model") onPick(row.ref);
|
|
328
|
+
} else if (key.escape) onPick(void 0);
|
|
329
|
+
else if (key.backspace || key.delete) {
|
|
330
|
+
setFilter((f) => f.slice(0, -1));
|
|
331
|
+
setIndex(0);
|
|
332
|
+
} else if (!key.ctrl && !key.meta && !key.tab && input) {
|
|
333
|
+
setFilter((f) => f + input);
|
|
334
|
+
setIndex(0);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
const start = Math.max(0, Math.min(cursor - 5, rows.length - VISIBLE2));
|
|
338
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
339
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: `Pilih model (ketik untuk menyaring${filter ? `: ${filter}` : ""} \xB7 \u2191\u2193 enter \xB7 esc batal)` }),
|
|
340
|
+
rows.slice(start, start + VISIBLE2).map(
|
|
341
|
+
(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)
|
|
342
|
+
),
|
|
343
|
+
!selectable.length ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Tidak ada model yang cocok." }) : null,
|
|
344
|
+
groups.filter((g) => g.error).map((g) => /* @__PURE__ */ jsx4(Text4, { color: color("yellow"), children: `${g.providerName} \u2014 ${g.error}` }, `e-${g.providerId}`))
|
|
345
|
+
] });
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/ui/ProviderMenu.tsx
|
|
349
|
+
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
350
|
+
import { useState as useState4 } from "react";
|
|
351
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
352
|
+
function ProviderMenu({ entries, onPick }) {
|
|
353
|
+
const rows = [...entries.map((e) => e.id), "custom"];
|
|
354
|
+
const [index, setIndex] = useState4(0);
|
|
355
|
+
useInput4((_input, key) => {
|
|
356
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
357
|
+
else if (key.downArrow) setIndex((i) => Math.min(rows.length - 1, i + 1));
|
|
358
|
+
else if (key.return) onPick(rows[index]);
|
|
359
|
+
else if (key.escape) onPick(void 0);
|
|
360
|
+
});
|
|
361
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
362
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Provider (\u2191\u2193 enter, esc batal) \xB7 \u2713 siap \xB7 \u25CB butuh API key" }),
|
|
363
|
+
entries.map((e, i) => /* @__PURE__ */ jsxs4(Text5, { color: i === index ? color("green") : void 0, children: [
|
|
364
|
+
`${i === index ? "\u203A" : " "} ${e.ready ? "\u2713" : "\u25CB"} ${e.name}`,
|
|
365
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${e.baseURL}` })
|
|
366
|
+
] }, e.id)),
|
|
367
|
+
/* @__PURE__ */ jsx5(Text5, { color: index === rows.length - 1 ? color("green") : void 0, children: `${index === rows.length - 1 ? "\u203A" : " "} Custom\u2026` })
|
|
368
|
+
] });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/ui/PermissionPrompt.tsx
|
|
372
|
+
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
373
|
+
|
|
374
|
+
// src/ui/DiffView.tsx
|
|
375
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
376
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
377
|
+
function DiffView({ diff }) {
|
|
378
|
+
return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: diff.split("\n").map((line, i) => {
|
|
379
|
+
const mark = line[6];
|
|
380
|
+
const c = mark === "+" ? color("green") : mark === "-" ? color("red") : void 0;
|
|
381
|
+
return /* @__PURE__ */ jsx6(Text6, { color: c, dimColor: mark !== "+" && mark !== "-", children: line }, i);
|
|
382
|
+
}) });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/ui/PermissionPrompt.tsx
|
|
386
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
387
|
+
var VERB = { edit: "edit", write: "tulis", bash: "jalankan", fetch: "ambil" };
|
|
388
|
+
function PermissionPrompt({
|
|
389
|
+
request,
|
|
390
|
+
onAnswer
|
|
391
|
+
}) {
|
|
392
|
+
useInput5((input, key) => {
|
|
393
|
+
const k2 = input.toLowerCase();
|
|
394
|
+
if (k2 === "y") onAnswer("yes");
|
|
395
|
+
else if (k2 === "a" && request.sessionRules?.length) onAnswer("session");
|
|
396
|
+
else if (k2 === "s") onAnswer("all");
|
|
397
|
+
else if (k2 === "n" || key.escape) onAnswer("no");
|
|
398
|
+
});
|
|
399
|
+
const rules = request.sessionRules ?? [];
|
|
400
|
+
const sessionLabel = !rules.length ? null : request.kind === "edit" ? "ya untuk semua edit di project ini, sesi ini" : `ya sesi ini untuk ${rules.join(", ")}`;
|
|
401
|
+
return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", borderStyle: "round", borderColor: color("yellow"), paddingX: 1, children: [
|
|
402
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: `${request.agent ? `[${request.agent}] ` : ""}Izinkan ${VERB[request.tool] ?? request.tool} ${request.target}?` }),
|
|
403
|
+
request.preview ? /* @__PURE__ */ jsx7(DiffView, { diff: request.preview }) : null,
|
|
404
|
+
/* @__PURE__ */ jsxs5(Text7, { children: [
|
|
405
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("green"), children: "[y] ya" }),
|
|
406
|
+
" ",
|
|
407
|
+
sessionLabel ? /* @__PURE__ */ jsx7(Text7, { color: color("cyan"), children: `[a] ${sessionLabel} ` }) : null,
|
|
408
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("yellow"), children: "[s] ya semua " }),
|
|
409
|
+
/* @__PURE__ */ jsx7(Text7, { color: color("red"), children: "[n] tidak" })
|
|
410
|
+
] })
|
|
411
|
+
] });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/ui/PromptInput.tsx
|
|
415
|
+
import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
416
|
+
import { useState as useState5 } from "react";
|
|
417
|
+
|
|
418
|
+
// src/ui/complete.ts
|
|
419
|
+
import fg from "fast-glob";
|
|
420
|
+
function completeFile(value, cwd) {
|
|
421
|
+
const match = /(^|\s)@([^\s]*)$/.exec(value);
|
|
422
|
+
if (!match) return value;
|
|
423
|
+
const prefix = match[2];
|
|
424
|
+
const escaped = prefix.replace(/([*?[\]{}()!])/g, "\\$1");
|
|
425
|
+
const found = fg.sync(`${escaped}*`, { cwd, ignore: ignorePatterns(cwd), onlyFiles: false, markDirectories: true, dot: false }).sort();
|
|
426
|
+
if (!found.length) return value;
|
|
427
|
+
const pick = found[0];
|
|
428
|
+
return `${value.slice(0, value.length - prefix.length)}${pick}${pick.endsWith("/") ? "" : " "}`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/ui/PromptInput.tsx
|
|
432
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
433
|
+
function PromptInput({ disabled, history, cwd, onSubmit, extraCommands }) {
|
|
434
|
+
const [value, setValue] = useState5("");
|
|
435
|
+
const [historyIndex, setHistoryIndex] = useState5(null);
|
|
436
|
+
const [selected, setSelected] = useState5(0);
|
|
437
|
+
const all = [...SLASH_COMMANDS, ...(extraCommands ?? []).filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name))];
|
|
438
|
+
const suggestions = value.startsWith("/") && !/\s/.test(value) ? all.filter((c) => c.name.startsWith(value.slice(1))).slice(0, 8) : [];
|
|
439
|
+
const highlighted = Math.min(selected, Math.max(0, suggestions.length - 1));
|
|
440
|
+
const edit = (next) => {
|
|
441
|
+
setValue(next);
|
|
442
|
+
setSelected(0);
|
|
443
|
+
};
|
|
444
|
+
useInput6(
|
|
445
|
+
(input, key) => {
|
|
446
|
+
if (suggestions.length) {
|
|
447
|
+
if (key.upArrow) {
|
|
448
|
+
setSelected(Math.max(0, highlighted - 1));
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (key.downArrow) {
|
|
452
|
+
setSelected(Math.min(suggestions.length - 1, highlighted + 1));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (key.tab && !key.shift) {
|
|
456
|
+
edit(`/${suggestions[highlighted].name} `);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (key.return) {
|
|
460
|
+
edit("");
|
|
461
|
+
setHistoryIndex(null);
|
|
462
|
+
onSubmit(`/${suggestions[highlighted].name}`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (key.return) {
|
|
467
|
+
if (value.endsWith("\\")) {
|
|
468
|
+
edit(`${value.slice(0, -1)}
|
|
469
|
+
`);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const text = value.trim();
|
|
473
|
+
if (!text) return;
|
|
474
|
+
edit("");
|
|
475
|
+
setHistoryIndex(null);
|
|
476
|
+
onSubmit(text);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (key.backspace || key.delete) {
|
|
480
|
+
edit((v) => v.slice(0, -1));
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (key.upArrow) {
|
|
484
|
+
if (!history.length) return;
|
|
485
|
+
const i = historyIndex === null ? history.length - 1 : Math.max(0, historyIndex - 1);
|
|
486
|
+
setHistoryIndex(i);
|
|
487
|
+
setValue(history[i]);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (key.downArrow) {
|
|
491
|
+
if (historyIndex === null) return;
|
|
492
|
+
const i = historyIndex + 1;
|
|
493
|
+
setHistoryIndex(i >= history.length ? null : i);
|
|
494
|
+
setValue(i >= history.length ? "" : history[i]);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (key.tab && !key.shift) {
|
|
498
|
+
edit((v) => completeFile(v, cwd));
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (key.ctrl || key.meta || key.escape || key.tab || key.leftArrow || key.rightArrow) return;
|
|
502
|
+
edit((v) => v + input.replace(/\r/g, "\n"));
|
|
503
|
+
},
|
|
504
|
+
{ isActive: !disabled }
|
|
505
|
+
);
|
|
506
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", children: [
|
|
507
|
+
/* @__PURE__ */ jsx8(Box8, { borderStyle: "round", borderColor: color("gray"), paddingX: 1, children: /* @__PURE__ */ jsxs6(Text8, { children: [
|
|
508
|
+
/* @__PURE__ */ jsx8(Text8, { color: color("green"), children: "> " }),
|
|
509
|
+
value,
|
|
510
|
+
disabled ? "" : /* @__PURE__ */ jsx8(Text8, { inverse: true, children: " " })
|
|
511
|
+
] }) }),
|
|
512
|
+
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))
|
|
513
|
+
] });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// src/ui/Spinner.tsx
|
|
517
|
+
import { Text as Text9 } from "ink";
|
|
518
|
+
import { useEffect, useState as useState6 } from "react";
|
|
519
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
520
|
+
var FRAMES = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
|
|
521
|
+
function Spinner({ label, startedAt }) {
|
|
522
|
+
const [tick, setTick] = useState6(0);
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
const id = setInterval(() => setTick((t) => t + 1), 120);
|
|
525
|
+
return () => clearInterval(id);
|
|
526
|
+
}, []);
|
|
527
|
+
const seconds = Math.floor((Date.now() - startedAt) / 1e3);
|
|
528
|
+
return /* @__PURE__ */ jsx9(Text9, { color: color(ACCENT), children: `${FRAMES[tick % FRAMES.length]} ${label}\u2026 (${seconds} dtk \xB7 esc batal)` });
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/ui/StatusBar.tsx
|
|
532
|
+
import { Text as Text10 } from "ink";
|
|
533
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
534
|
+
var LABEL = {
|
|
535
|
+
default: "\u23F5 default",
|
|
536
|
+
acceptEdits: "\u23F5 accept edits",
|
|
537
|
+
plan: "\u23F8 plan mode",
|
|
538
|
+
allowAll: "\u23F5\u23F5 allow all"
|
|
539
|
+
};
|
|
540
|
+
function formatTokens(n) {
|
|
541
|
+
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
542
|
+
}
|
|
543
|
+
function StatusBar({ mode, tokens, busy, model, reasoning = "auto" }) {
|
|
544
|
+
const danger = mode === "allowAll";
|
|
545
|
+
return /* @__PURE__ */ jsx10(Text10, { color: danger ? color("red") : void 0, dimColor: !danger, children: ` ${LABEL[mode]}${model ? ` \xB7 ${model}` : ""} \xB7 reasoning: ${reasoning} \xB7 shift+tab ganti mode \xB7 ${formatTokens(tokens)} token \xB7 ${busy ? "esc batal" : "/ perintah"}` });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/ui/McpMenu.tsx
|
|
549
|
+
import { Box as Box9, Text as Text11, useInput as useInput7 } from "ink";
|
|
550
|
+
import { useState as useState7 } from "react";
|
|
551
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
552
|
+
var MARK = { ready: "\u2713", error: "\u2717", starting: "\u2026", off: "\u25CB" };
|
|
553
|
+
function McpMenu({ items, onPick }) {
|
|
554
|
+
const [index, setIndex] = useState7(0);
|
|
555
|
+
useInput7((_input, key) => {
|
|
556
|
+
if (key.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
557
|
+
else if (key.downArrow) setIndex((i) => Math.min(items.length - 1, i + 1));
|
|
558
|
+
else if (key.return) onPick(items[index]?.name);
|
|
559
|
+
else if (key.escape) onPick(void 0);
|
|
560
|
+
});
|
|
561
|
+
return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
562
|
+
/* @__PURE__ */ jsx11(Text11, { bold: true, children: "Server MCP (enter: pasang / hapus \xB7 esc tutup) \xB7 \u2713 aktif \u25CB belum \u2717 error" }),
|
|
563
|
+
items.map((item, i) => /* @__PURE__ */ jsxs7(Text11, { color: i === index ? color("green") : void 0, children: [
|
|
564
|
+
`${i === index ? "\u203A" : " "} ${MARK[item.status]} ${item.name.padEnd(12)}`,
|
|
565
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: item.status === "error" ? ` ${item.error ?? ""}` : ` ${item.description}` })
|
|
566
|
+
] }, item.name))
|
|
567
|
+
] });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/ui/PlanApproval.tsx
|
|
571
|
+
import { Box as Box10, Text as Text12, useInput as useInput8 } from "ink";
|
|
572
|
+
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
573
|
+
function PlanApproval({ plan, onAnswer }) {
|
|
574
|
+
useInput8((input, key) => {
|
|
575
|
+
const k2 = input.toLowerCase();
|
|
576
|
+
if (k2 === "a") onAnswer("acceptEdits");
|
|
577
|
+
else if (k2 === "y") onAnswer("default");
|
|
578
|
+
else if (k2 === "n" || key.escape) onAnswer("no");
|
|
579
|
+
});
|
|
580
|
+
return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", borderStyle: "round", borderColor: color("cyan"), paddingX: 1, children: [
|
|
581
|
+
/* @__PURE__ */ jsx12(Text12, { bold: true, children: "Rencana dari agent" }),
|
|
582
|
+
/* @__PURE__ */ jsx12(Markdown, { text: plan }),
|
|
583
|
+
/* @__PURE__ */ jsxs8(Text12, { children: [
|
|
584
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("green"), children: "[a] ya, edit otomatis" }),
|
|
585
|
+
" ",
|
|
586
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("cyan"), children: "[y] ya, tanya tiap langkah" }),
|
|
587
|
+
" ",
|
|
588
|
+
/* @__PURE__ */ jsx12(Text12, { color: color("red"), children: "[n] belum, perbaiki dulu" })
|
|
589
|
+
] })
|
|
590
|
+
] });
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/ui/TodoList.tsx
|
|
594
|
+
import { Box as Box11, Text as Text13 } from "ink";
|
|
595
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
596
|
+
var MARK2 = { completed: "\u2611", in_progress: "\u25FC", pending: "\u2610" };
|
|
597
|
+
function TodoList({ items }) {
|
|
598
|
+
if (!items.length || items.every((t) => t.status === "completed")) return null;
|
|
599
|
+
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)) });
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/ui/ToolBlock.tsx
|
|
603
|
+
import { Box as Box12, Text as Text14 } from "ink";
|
|
604
|
+
import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
605
|
+
var PREVIEW_LINES = 5;
|
|
606
|
+
function ToolBlock({ tool, target, output, display, isError, done, expanded, sub }) {
|
|
607
|
+
const isTask = tool === "task";
|
|
608
|
+
const isDiff = !!display && /^\s*\d+ [+\- ] /.test(display);
|
|
609
|
+
const lines = (output ?? "").split("\n");
|
|
610
|
+
const shown = expanded ? lines : lines.slice(0, PREVIEW_LINES);
|
|
611
|
+
return /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 2, children: [
|
|
612
|
+
/* @__PURE__ */ jsxs9(Text14, { children: [
|
|
613
|
+
/* @__PURE__ */ jsx14(Text14, { color: isError ? color("red") : done ? color("green") : color("yellow"), children: "\u23BF " }),
|
|
614
|
+
/* @__PURE__ */ jsx14(Text14, { bold: true, children: toolLabel(tool) }),
|
|
615
|
+
/* @__PURE__ */ jsx14(Text14, { children: ` ${target}` }),
|
|
616
|
+
display && !isDiff && !isTask ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: ` (${display})` }) : null
|
|
617
|
+
] }),
|
|
618
|
+
sub?.length ? /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", marginLeft: 3, children: [
|
|
619
|
+
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)),
|
|
620
|
+
sub.length > 6 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: `\u2026 ${sub.length - 6} langkah sebelumnya` }) : null
|
|
621
|
+
] }) : null,
|
|
622
|
+
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,
|
|
623
|
+
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: [
|
|
624
|
+
shown.map((line, i) => /* @__PURE__ */ jsx14(Text14, { dimColor: !isError, color: isError ? color("red") : void 0, children: line }, i)),
|
|
625
|
+
lines.length > shown.length ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: `\u2026 ${lines.length - shown.length} baris lagi (ctrl+o)` }) : null
|
|
626
|
+
] }) : null
|
|
627
|
+
] });
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/ui/transcript.ts
|
|
631
|
+
var nextId = 0;
|
|
632
|
+
var entry = (e) => ({ ...e, id: nextId++ });
|
|
633
|
+
var NOTICES = {
|
|
634
|
+
compacted: ["Percakapan diringkas agar muat di konteks model.", "info"],
|
|
635
|
+
stepLimit: ['Batas 50 langkah tercapai. Ketik "lanjut" untuk meneruskan.', "warn"],
|
|
636
|
+
aborted: ["Dibatalkan.", "warn"]
|
|
637
|
+
};
|
|
638
|
+
var isEmptyCont = (e) => e.kind === "assistant" && e.cont && !e.text;
|
|
639
|
+
function endTurn(t) {
|
|
640
|
+
return { done: [...t.done, ...t.live.filter((e) => !isEmptyCont(e))], live: [] };
|
|
641
|
+
}
|
|
642
|
+
var TOOL_MARKUP = /<\/?tool_call>|<\/?arg_(?:key|value)>|Tool call list|^\s*\d+\.\s*Tool:|`\s*=\s*`/;
|
|
643
|
+
function finishedCut(text) {
|
|
644
|
+
let inFence = false;
|
|
645
|
+
let pos = 0;
|
|
646
|
+
let cut = -1;
|
|
647
|
+
const lines = text.split("\n");
|
|
648
|
+
for (const line of lines.slice(0, -1)) {
|
|
649
|
+
if (TOOL_MARKUP.test(line)) break;
|
|
650
|
+
if (line.trimStart().startsWith("```")) inFence = !inFence;
|
|
651
|
+
pos += line.length + 1;
|
|
652
|
+
if (!inFence) cut = pos - 1;
|
|
653
|
+
}
|
|
654
|
+
return cut;
|
|
655
|
+
}
|
|
656
|
+
function flushLines(t) {
|
|
657
|
+
const last = t.live.at(-1);
|
|
658
|
+
if (last?.kind !== "assistant") return t;
|
|
659
|
+
const cut = finishedCut(last.text);
|
|
660
|
+
if (cut < 0) return t;
|
|
661
|
+
const chunk = last.text.slice(0, cut);
|
|
662
|
+
if (!last.cont && !chunk.trim()) return t;
|
|
663
|
+
const rest = entry({ kind: "assistant", text: last.text.slice(cut + 1), cont: true, flushed: (last.flushed ?? 0) + cut + 1 });
|
|
664
|
+
return { done: [...t.done, ...t.live.slice(0, -1).filter((e) => !isEmptyCont(e)), { ...last, text: chunk || " " }], live: [rest] };
|
|
665
|
+
}
|
|
666
|
+
function applyEvent(t, event) {
|
|
667
|
+
switch (event.type) {
|
|
668
|
+
case "text": {
|
|
669
|
+
const last = t.live.at(-1);
|
|
670
|
+
if (last?.kind === "assistant") return flushLines({ ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] });
|
|
671
|
+
return flushLines({ ...t, live: [...t.live, entry({ kind: "assistant", text: event.delta })] });
|
|
672
|
+
}
|
|
673
|
+
case "thinking": {
|
|
674
|
+
const last = t.live.at(-1);
|
|
675
|
+
if (last?.kind === "thinking") return { ...t, live: [...t.live.slice(0, -1), { ...last, text: last.text + event.delta }] };
|
|
676
|
+
return { ...t, live: [...t.live, entry({ kind: "thinking", text: event.delta })] };
|
|
677
|
+
}
|
|
678
|
+
case "textReplace": {
|
|
679
|
+
const last = t.live.at(-1);
|
|
680
|
+
if (last?.kind !== "assistant") return t;
|
|
681
|
+
return { ...t, live: [...t.live.slice(0, -1), { ...last, text: event.text.slice(last.flushed ?? 0) }] };
|
|
682
|
+
}
|
|
683
|
+
case "toolStart": {
|
|
684
|
+
const running = t.live.some((e) => e.kind === "tool" && !e.done);
|
|
685
|
+
const tool = entry({ kind: "tool", callId: event.id, tool: event.tool, target: event.target, done: false });
|
|
686
|
+
return running ? { ...t, live: [...t.live, tool] } : { done: [...t.done, ...t.live.filter((e) => !isEmptyCont(e))], live: [tool] };
|
|
687
|
+
}
|
|
688
|
+
case "toolEnd": {
|
|
689
|
+
const live = t.live.map(
|
|
690
|
+
(e) => e.kind === "tool" && e.callId === event.id ? { ...e, output: event.output, display: event.display, isError: event.isError, done: true } : e
|
|
691
|
+
);
|
|
692
|
+
const firstRunning = live.findIndex((e) => e.kind === "tool" && !e.done);
|
|
693
|
+
const cut = firstRunning === -1 ? live.length : firstRunning;
|
|
694
|
+
return { done: [...t.done, ...live.slice(0, cut)], live: live.slice(cut) };
|
|
695
|
+
}
|
|
696
|
+
case "subagent": {
|
|
697
|
+
const inner = event.event;
|
|
698
|
+
if (inner.type !== "toolStart" && inner.type !== "toolEnd") return t;
|
|
699
|
+
const update = (e) => {
|
|
700
|
+
if (e.kind !== "tool" || e.callId !== event.parentId) return e;
|
|
701
|
+
const sub = e.sub ?? [];
|
|
702
|
+
if (inner.type === "toolStart") return { ...e, sub: [...sub, { id: inner.id, tool: inner.tool, target: inner.target, done: false }] };
|
|
703
|
+
return { ...e, sub: sub.map((s) => s.id === inner.id ? { ...s, done: true, isError: inner.isError } : s) };
|
|
704
|
+
};
|
|
705
|
+
return { ...t, live: t.live.map(update) };
|
|
706
|
+
}
|
|
707
|
+
case "error":
|
|
708
|
+
return { ...t, live: [...t.live, entry({ kind: "notice", text: `Error: ${event.message}`, tone: "error" })] };
|
|
709
|
+
default: {
|
|
710
|
+
const notice = NOTICES[event.type];
|
|
711
|
+
return notice ? { ...t, live: [...t.live, entry({ kind: "notice", text: notice[0], tone: notice[1] })] } : t;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/ui/App.tsx
|
|
717
|
+
import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
718
|
+
function helpText(runtime) {
|
|
719
|
+
const custom = runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name));
|
|
720
|
+
return [
|
|
721
|
+
...SLASH_COMMANDS.map((c) => `/${c.name.padEnd(12)} ${c.description}`),
|
|
722
|
+
...custom.length ? ["", "Perintah custom:", ...custom.map((c) => `/${c.name.padEnd(8)} ${c.description ?? ""}`)] : [],
|
|
723
|
+
...runtime.skills.length ? ["", "Skill:", ...runtime.skills.map((s) => `/${s.name.padEnd(8)} ${s.description.slice(0, 60)}`)] : [],
|
|
724
|
+
"",
|
|
725
|
+
"shift+tab ganti mode izin esc batalkan giliran",
|
|
726
|
+
"ctrl+o output alat terakhir lengkap \\ + enter baris baru",
|
|
727
|
+
"ctrl+t buka/tutup thinking model",
|
|
728
|
+
"@file + tab lengkapi nama file \u2191\u2193 riwayat input"
|
|
729
|
+
].join("\n");
|
|
730
|
+
}
|
|
731
|
+
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.";
|
|
732
|
+
var labelFor = (runtime, ref) => `${ref.slice(ref.indexOf("/") + 1)} \xB7 ${runtime.providerLabel()}`;
|
|
733
|
+
function App({ runtime, initialPrompt, version }) {
|
|
734
|
+
const { exit } = useApp();
|
|
735
|
+
const [transcript, setTranscript] = useState8(() => ({ done: [entry({ kind: "header" })], live: [] }));
|
|
736
|
+
const [busy, setBusy] = useState8(false);
|
|
737
|
+
const [startedAt, setStartedAt] = useState8(0);
|
|
738
|
+
const [mode, setMode] = useState8(runtime.agent.permissions.mode);
|
|
739
|
+
const [reasoning, setReasoning] = useState8(runtime.reasoning);
|
|
740
|
+
const [tokens, setTokens] = useState8(runtime.agent.totalUsage.inputTokens + runtime.agent.totalUsage.outputTokens);
|
|
741
|
+
const [asks, setAsks] = useState8([]);
|
|
742
|
+
const pending = asks[0] ?? null;
|
|
743
|
+
const askId = useRef(0);
|
|
744
|
+
const [picker, setPicker] = useState8(null);
|
|
745
|
+
const [providerMenu, setProviderMenu] = useState8(null);
|
|
746
|
+
const [prompt, setPrompt] = useState8(null);
|
|
747
|
+
const ask = useCallback((label, mask = false) => new Promise((resolve2) => setPrompt({ label, mask, resolve: resolve2 })), []);
|
|
748
|
+
const [modelLabel, setModelLabel] = useState8(() => labelFor(runtime, runtime.modelRef));
|
|
749
|
+
const [history, setHistory] = useState8([]);
|
|
750
|
+
const controller = useRef(null);
|
|
751
|
+
const [todos, setTodos] = useState8(runtime.todos.items);
|
|
752
|
+
const [mcpMenu, setMcpMenu] = useState8(null);
|
|
753
|
+
const [listPicker, setListPicker] = useState8(null);
|
|
754
|
+
const [planAsk, setPlanAsk] = useState8(null);
|
|
755
|
+
const [queued, setQueued] = useState8([]);
|
|
756
|
+
const [showThinking, setShowThinking] = useState8(false);
|
|
757
|
+
useEffect2(() => runtime.todos.subscribe(setTodos), [runtime]);
|
|
758
|
+
const extraCommands = [
|
|
759
|
+
...runtime.commands.filter((c) => !SLASH_COMMANDS.some((b) => b.name === c.name.toLowerCase())).map((c) => ({ name: c.name.toLowerCase(), description: c.description ?? "perintah custom" })),
|
|
760
|
+
...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)}` }))
|
|
761
|
+
];
|
|
762
|
+
const lastTool = useRef(null);
|
|
763
|
+
const toolTargets = useRef(/* @__PURE__ */ new Map());
|
|
764
|
+
const notice = useCallback((text, tone = "info") => {
|
|
765
|
+
setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "notice", text, tone })] }));
|
|
766
|
+
}, []);
|
|
767
|
+
const onEvent = useCallback((event) => {
|
|
768
|
+
if (event.type === "toolStart") toolTargets.current.set(event.id, event.target);
|
|
769
|
+
if (event.type === "toolEnd") {
|
|
770
|
+
lastTool.current = { tool: event.tool, target: toolTargets.current.get(event.id) ?? "", output: event.output };
|
|
771
|
+
}
|
|
772
|
+
if (event.type === "usage") setTokens(event.inputTokens + event.outputTokens);
|
|
773
|
+
setTranscript((t) => applyEvent(t, event));
|
|
774
|
+
}, []);
|
|
775
|
+
useEffect2(() => {
|
|
776
|
+
runtime.agent.onEvent = onEvent;
|
|
777
|
+
runtime.agent.askPermission = (request) => new Promise((resolve2) => setAsks((q) => [...q, { id: askId.current++, request, resolve: resolve2 }]));
|
|
778
|
+
runtime.interaction.approvePlan = (plan) => new Promise((resolve2) => setPlanAsk({ plan, resolve: resolve2 }));
|
|
779
|
+
}, [runtime, onEvent]);
|
|
780
|
+
const runTurn = useCallback(
|
|
781
|
+
async (text) => {
|
|
782
|
+
setTranscript((t) => ({ ...t, done: [...t.done, entry({ kind: "user", text })] }));
|
|
783
|
+
setBusy(true);
|
|
784
|
+
setStartedAt(Date.now());
|
|
785
|
+
controller.current = new AbortController();
|
|
786
|
+
await runtime.agent.run(text, controller.current.signal);
|
|
787
|
+
controller.current = null;
|
|
788
|
+
setTranscript(endTurn);
|
|
789
|
+
setBusy(false);
|
|
790
|
+
},
|
|
791
|
+
[runtime]
|
|
792
|
+
);
|
|
793
|
+
const runSlashCommand = useCallback(
|
|
794
|
+
async (name, args) => {
|
|
795
|
+
switch (name) {
|
|
796
|
+
case "help":
|
|
797
|
+
notice(helpText(runtime));
|
|
798
|
+
return;
|
|
799
|
+
case "exit":
|
|
800
|
+
exit();
|
|
801
|
+
return;
|
|
802
|
+
case "clear":
|
|
803
|
+
runtime.agent.clear();
|
|
804
|
+
setTokens(0);
|
|
805
|
+
notice("Percakapan dikosongkan.");
|
|
806
|
+
return;
|
|
807
|
+
case "cost": {
|
|
808
|
+
const u = runtime.agent.totalUsage;
|
|
809
|
+
notice(`${u.inputTokens.toLocaleString("id-ID")} token masuk \xB7 ${u.outputTokens.toLocaleString("id-ID")} token keluar`);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
case "compact": {
|
|
813
|
+
setBusy(true);
|
|
814
|
+
setStartedAt(Date.now());
|
|
815
|
+
controller.current = new AbortController();
|
|
816
|
+
try {
|
|
817
|
+
await runtime.agent.compact(controller.current.signal);
|
|
818
|
+
} catch (error) {
|
|
819
|
+
notice(`Gagal meringkas: ${error.message}`, "error");
|
|
820
|
+
}
|
|
821
|
+
controller.current = null;
|
|
822
|
+
setBusy(false);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
case "reasoning": {
|
|
826
|
+
const apply = (value) => {
|
|
827
|
+
try {
|
|
828
|
+
const level = parseReasoningLevel(value, "/reasoning");
|
|
829
|
+
const providerId2 = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
830
|
+
assertReasoningSupported(providerId2, level);
|
|
831
|
+
runtime.setReasoning(level);
|
|
832
|
+
writeGlobalConfig({ reasoning: level }, runtime.env);
|
|
833
|
+
setReasoning(level);
|
|
834
|
+
notice(`Reasoning: ${level} (tersimpan sebagai default)`);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
notice(error.message, "error");
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
if (args) {
|
|
840
|
+
apply(args);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const providerId = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
844
|
+
const supported = supportedReasoningLevels(providerId);
|
|
845
|
+
setListPicker({
|
|
846
|
+
title: "Reasoning",
|
|
847
|
+
items: REASONING_LEVELS.map((level) => ({
|
|
848
|
+
id: level,
|
|
849
|
+
label: level === "auto" ? "Auto (model default)" : level.charAt(0).toUpperCase() + level.slice(1),
|
|
850
|
+
disabled: !supported.includes(level),
|
|
851
|
+
hint: supported.includes(level) ? void 0 : "tidak didukung provider ini"
|
|
852
|
+
})),
|
|
853
|
+
onPick: (id) => {
|
|
854
|
+
setListPicker(null);
|
|
855
|
+
if (id) apply(id);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
case "model": {
|
|
861
|
+
if (args) {
|
|
862
|
+
try {
|
|
863
|
+
runtime.setModel(args);
|
|
864
|
+
writeGlobalConfig({ model: args }, runtime.env);
|
|
865
|
+
setModelLabel(labelFor(runtime, args));
|
|
866
|
+
notice(`Model: ${args} (tersimpan sebagai default)`);
|
|
867
|
+
} catch (error) {
|
|
868
|
+
notice(error.message, "error");
|
|
869
|
+
}
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
try {
|
|
873
|
+
setPicker(await runtime.listModels());
|
|
874
|
+
} catch (error) {
|
|
875
|
+
notice(`Tidak bisa mengambil daftar model: ${error.message}`, "error");
|
|
876
|
+
}
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
case "mcp": {
|
|
880
|
+
const installed = readMcpFile(globalMcpPath(runtime.home));
|
|
881
|
+
const states = runtime.mcp.states();
|
|
882
|
+
const items = [
|
|
883
|
+
...CATALOG.map((c) => ({ name: c.name, description: c.description, installed: !!installed[c.name] })),
|
|
884
|
+
...Object.keys(installed).filter((n) => !CATALOG.some((c) => c.name === n)).map((n) => ({ name: n, description: "(custom)", installed: true }))
|
|
885
|
+
].map((i) => {
|
|
886
|
+
const s = states.find((st) => st.name === i.name);
|
|
887
|
+
return { ...i, status: s?.status ?? "off", error: s?.error };
|
|
888
|
+
});
|
|
889
|
+
setMcpMenu(items);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
case "new":
|
|
893
|
+
runtime.newSession();
|
|
894
|
+
setTokens(0);
|
|
895
|
+
notice("Sesi baru dimulai. Sesi sebelumnya tetap tersimpan (/resume).");
|
|
896
|
+
return;
|
|
897
|
+
case "session":
|
|
898
|
+
notice(
|
|
899
|
+
sessionText({
|
|
900
|
+
file: runtime.session.file,
|
|
901
|
+
started: runtime.startedAt,
|
|
902
|
+
messages: runtime.agent.messages.length,
|
|
903
|
+
usage: runtime.agent.totalUsage,
|
|
904
|
+
modelRef: runtime.modelRef
|
|
905
|
+
})
|
|
906
|
+
);
|
|
907
|
+
return;
|
|
908
|
+
case "resume": {
|
|
909
|
+
const others = Session.list(runtime.home, runtime.cwd).filter((s) => s.session.file !== runtime.session.file);
|
|
910
|
+
if (!others.length) {
|
|
911
|
+
notice("Belum ada sesi lain di folder ini.");
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
setListPicker({
|
|
915
|
+
title: "Lanjutkan sesi",
|
|
916
|
+
items: others.map((s) => ({
|
|
917
|
+
id: s.session.file,
|
|
918
|
+
label: `${s.mtime.toLocaleString("id-ID")} ${s.preview || "(kosong)"}`,
|
|
919
|
+
hint: `${s.session.load().length} pesan`
|
|
920
|
+
})),
|
|
921
|
+
onPick: (id) => {
|
|
922
|
+
setListPicker(null);
|
|
923
|
+
const chosen = others.find((s) => s.session.file === id);
|
|
924
|
+
if (!chosen) return;
|
|
925
|
+
runtime.resume(chosen.session);
|
|
926
|
+
setTokens(0);
|
|
927
|
+
const chat = runtime.agent.messages.filter(
|
|
928
|
+
(m) => (m.role === "user" || m.role === "assistant") && typeof m.content === "string" && !!m.content.trim()
|
|
929
|
+
);
|
|
930
|
+
setTranscript({
|
|
931
|
+
done: [
|
|
932
|
+
entry({ kind: "header" }),
|
|
933
|
+
entry({
|
|
934
|
+
kind: "notice",
|
|
935
|
+
text: `Melanjutkan sesi (${runtime.agent.messages.length} pesan tersimpan \xB7 ${chat.length} chat user/assistant ditampilkan).`,
|
|
936
|
+
tone: "info"
|
|
937
|
+
}),
|
|
938
|
+
...chat.map(
|
|
939
|
+
(m) => entry(m.role === "user" ? { kind: "user", text: m.content } : { kind: "assistant", text: m.content })
|
|
940
|
+
)
|
|
941
|
+
],
|
|
942
|
+
live: []
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
case "status":
|
|
949
|
+
notice(
|
|
950
|
+
statusText({
|
|
951
|
+
version,
|
|
952
|
+
modelRef: runtime.modelRef,
|
|
953
|
+
providerLabel: runtime.providerLabel(),
|
|
954
|
+
mode: runtime.agent.permissions.mode,
|
|
955
|
+
cwd: runtime.cwd,
|
|
956
|
+
mcp: runtime.mcp.states(),
|
|
957
|
+
usage: runtime.agent.totalUsage,
|
|
958
|
+
lastInputTokens: runtime.agent.lastInputTokens
|
|
959
|
+
})
|
|
960
|
+
);
|
|
961
|
+
return;
|
|
962
|
+
case "agents":
|
|
963
|
+
notice(agentsText([...BUILTIN_AGENTS, ...runtime.agentDefs]));
|
|
964
|
+
return;
|
|
965
|
+
case "skills":
|
|
966
|
+
notice(skillsText(runtime.skills, runtime.commands));
|
|
967
|
+
return;
|
|
968
|
+
case "doctor": {
|
|
969
|
+
notice("Memeriksa\u2026");
|
|
970
|
+
const c = runtime.config;
|
|
971
|
+
notice(
|
|
972
|
+
await doctorText(
|
|
973
|
+
{
|
|
974
|
+
providers: Object.keys(c.providers).map((id) => ({ id, name: providerName(c, id), ready: hasKey(c, id, runtime.env) })),
|
|
975
|
+
mcp: runtime.mcp.states(),
|
|
976
|
+
activeProvider: runtime.providerLabel()
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
which: (cmd) => new Promise((done) => execFile2(process.platform === "win32" ? "where" : "which", [cmd], (error) => done(!error))),
|
|
980
|
+
nodeVersion: process.version,
|
|
981
|
+
listModels: () => runtime.agent.provider.listModels()
|
|
982
|
+
}
|
|
983
|
+
)
|
|
984
|
+
);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
case "permissions": {
|
|
988
|
+
const rules = runtime.agent.permissions.list();
|
|
989
|
+
if (!rules.length) {
|
|
990
|
+
notice("Belum ada izin tersimpan. Izin dari jawaban [a] dan dari config akan muncul di sini.");
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
setListPicker({
|
|
994
|
+
title: "Izin aktif (enter: cabut izin sesi)",
|
|
995
|
+
items: rules.map((r) => ({ id: r.rule, label: r.rule, hint: r.source === "config" ? "config" : "sesi ini" })),
|
|
996
|
+
onPick: (id) => {
|
|
997
|
+
setListPicker(null);
|
|
998
|
+
const rule = rules.find((r) => r.rule === id);
|
|
999
|
+
if (!rule) return;
|
|
1000
|
+
if (rule.source === "config") notice(`${rule.rule} berasal dari config/flag; hapus dari ~/.bccli/config.json untuk mencabutnya.`, "warn");
|
|
1001
|
+
else {
|
|
1002
|
+
runtime.agent.permissions.revoke(rule.rule);
|
|
1003
|
+
notice(`Izin ${rule.rule} dicabut.`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
case "memory": {
|
|
1010
|
+
if (!args) {
|
|
1011
|
+
const files = instructionFiles(runtime.cwd, runtime.home);
|
|
1012
|
+
notice(files.length ? files.map((f) => `${f.path} (${f.lines} baris)`).join("\n") : "Belum ada AGENTS.md / BCCLI.md. Tambah dengan /memory <teks>.");
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const { global, text } = parseMemoryArgs(args);
|
|
1016
|
+
if (!text) {
|
|
1017
|
+
notice("Tulis teksnya: /memory global <teks>.", "warn");
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
const file = global ? join(runtime.home, "BCCLI.md") : join(runtime.cwd, "AGENTS.md");
|
|
1021
|
+
try {
|
|
1022
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
1023
|
+
appendMemory(file, text);
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
notice(`Gagal menulis ${file}: ${error.message}`, "error");
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
runtime.rebuildSystemPrompt();
|
|
1029
|
+
notice(`Ditambahkan ke ${file}.`);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
case "init":
|
|
1033
|
+
void runTurn(INIT_PROMPT);
|
|
1034
|
+
return;
|
|
1035
|
+
case "login": {
|
|
1036
|
+
const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
1037
|
+
const key = await ask(`API key untuk ${runtime.providerLabel()}`, true);
|
|
1038
|
+
if (!key) return;
|
|
1039
|
+
const result = await runtime.addProviderKey(id, key);
|
|
1040
|
+
if (!result.ok) {
|
|
1041
|
+
notice(result.error, "error");
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
runtime.setModel(runtime.modelRef);
|
|
1045
|
+
const envName = runtime.config.providers[id]?.apiKeyEnv;
|
|
1046
|
+
if (envName && runtime.env[envName]) {
|
|
1047
|
+
notice(`Key ${runtime.providerLabel()} tersimpan, tapi ${envName} di environment lebih diutamakan dan tetap dipakai.`, "warn");
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
notice(`Key ${runtime.providerLabel()} tersimpan \xB7 ${result.models} model.`);
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
case "logout": {
|
|
1054
|
+
const id = runtime.modelRef.slice(0, runtime.modelRef.indexOf("/"));
|
|
1055
|
+
const removed = removeCredential(id, runtime.env);
|
|
1056
|
+
const envName = runtime.config.providers[id]?.apiKeyEnv;
|
|
1057
|
+
if (envName && runtime.env[envName]) {
|
|
1058
|
+
notice(
|
|
1059
|
+
`${removed ? "Key tersimpan dihapus, tapi" : "Tidak ada key tersimpan;"} ${envName} masih ada di environment dan tetap dipakai.`,
|
|
1060
|
+
"warn"
|
|
1061
|
+
);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
if (!removed) {
|
|
1065
|
+
notice(`Tidak ada key tersimpan untuk ${runtime.providerLabel()}.`);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
try {
|
|
1069
|
+
runtime.setModel(runtime.modelRef);
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
const fail = async () => {
|
|
1072
|
+
throw error;
|
|
1073
|
+
};
|
|
1074
|
+
runtime.agent.provider = { chat: fail, listModels: fail };
|
|
1075
|
+
}
|
|
1076
|
+
notice(`Key ${runtime.providerLabel()} dihapus. Pakai /login atau /provider untuk menambah lagi.`);
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
case "diff":
|
|
1080
|
+
notice(await gitDiff(runtime.cwd));
|
|
1081
|
+
return;
|
|
1082
|
+
case "undo": {
|
|
1083
|
+
const result = await runtime.checkpoints.undo();
|
|
1084
|
+
if (!result) {
|
|
1085
|
+
notice("Tidak ada edit file yang bisa dibatalkan.");
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const rel = (p) => relative(runtime.cwd, p) || p;
|
|
1089
|
+
notice(
|
|
1090
|
+
[
|
|
1091
|
+
result.failed.length ? `Gagal dikembalikan: ${result.failed.map(rel).join(", ")}` : "",
|
|
1092
|
+
result.restored.length ? `Dikembalikan: ${result.restored.map(rel).join(", ")}` : "",
|
|
1093
|
+
result.deleted.length ? `Dihapus (file baru): ${result.deleted.map(rel).join(", ")}` : "",
|
|
1094
|
+
result.skipped.length ? `Terlalu besar untuk disimpan, tidak diubah: ${result.skipped.map(rel).join(", ")}` : "",
|
|
1095
|
+
"Catatan: perubahan lewat perintah bash tidak bisa di-undo."
|
|
1096
|
+
].filter(Boolean).join("\n")
|
|
1097
|
+
);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
case "copy": {
|
|
1101
|
+
const last = runtime.agent.messages.findLast((m) => m.role === "assistant" && typeof m.content === "string" && m.content.trim());
|
|
1102
|
+
if (!last || typeof last.content !== "string") {
|
|
1103
|
+
notice("Belum ada jawaban untuk disalin.");
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
process.stdout.write(osc52(last.content, !!process.env.TMUX));
|
|
1107
|
+
const file = join(runtime.home, "last-answer.md");
|
|
1108
|
+
mkdirSync(runtime.home, { recursive: true });
|
|
1109
|
+
writeFileSync(file, last.content);
|
|
1110
|
+
notice(`Disalin ke clipboard (bila terminal mendukung OSC 52) dan disimpan di ${file}.`);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
case "export": {
|
|
1114
|
+
const name2 = args || `bccli-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace(/[:T]/g, "-")}.md`;
|
|
1115
|
+
const file = resolve(runtime.cwd, name2);
|
|
1116
|
+
if (existsSync2(file)) {
|
|
1117
|
+
const answer2 = await ask(`${file} sudah ada. Timpa? (y/N)`);
|
|
1118
|
+
if (!answer2 || !/^y(a|es)?$/i.test(answer2.trim())) {
|
|
1119
|
+
notice("Ekspor dibatalkan.");
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
writeFileSync(file, exportMarkdown(runtime.agent.messages, `Sesi BCCLI ${runtime.startedAt.toLocaleString("id-ID")}`));
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
notice(`Gagal menulis ${file}: ${error.message}`, "error");
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
notice(`Percakapan disimpan di ${file}.`);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
case "provider": {
|
|
1133
|
+
const c = runtime.config;
|
|
1134
|
+
setProviderMenu(
|
|
1135
|
+
Object.keys(c.providers).map((id) => ({ id, name: providerName(c, id), ready: hasKey(c, id, runtime.env), baseURL: c.providers[id].baseURL }))
|
|
1136
|
+
);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
default:
|
|
1140
|
+
notice(`Perintah tidak dikenal: /${name}. Ketik /help.`, "warn");
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
[runtime, notice, exit, version, runTurn, ask]
|
|
1144
|
+
);
|
|
1145
|
+
const runSlash = useCallback(
|
|
1146
|
+
(name, args) => runSlashCommand(name, args).catch((error) => notice(`/${name}: ${error.message}`, "error")),
|
|
1147
|
+
[runSlashCommand, notice]
|
|
1148
|
+
);
|
|
1149
|
+
const submit = useCallback(
|
|
1150
|
+
(text) => {
|
|
1151
|
+
setHistory((h) => [...h, text]);
|
|
1152
|
+
const slash = parseSlash(text);
|
|
1153
|
+
if (!slash) {
|
|
1154
|
+
void runTurn(text);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
const command = runtime.commands.find((c) => c.name.toLowerCase() === slash.name);
|
|
1158
|
+
const skill = runtime.skills.find((s) => s.name.toLowerCase() === slash.name);
|
|
1159
|
+
if (SLASH_COMMANDS.some((b) => b.name === slash.name) || !command && !skill) void runSlash(slash.name, slash.args);
|
|
1160
|
+
else if (command) void runTurn(expandCommand(command, slash.args));
|
|
1161
|
+
else if (skill) {
|
|
1162
|
+
let body;
|
|
1163
|
+
try {
|
|
1164
|
+
body = parseFrontmatter(readFileSync2(skill.file, "utf8")).body;
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
notice(`Skill ${skill.name} tidak bisa dibaca: ${error.code ?? error.message}`, "error");
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
void runTurn(`${body}
|
|
1170
|
+
|
|
1171
|
+
ARGUMENTS: ${slash.args}`);
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
[runSlash, runTurn, runtime, notice]
|
|
1175
|
+
);
|
|
1176
|
+
const onPrompt = useCallback(
|
|
1177
|
+
(text) => {
|
|
1178
|
+
if (busy) setQueued((q) => [...q, text]);
|
|
1179
|
+
else submit(text);
|
|
1180
|
+
},
|
|
1181
|
+
[busy, submit]
|
|
1182
|
+
);
|
|
1183
|
+
useEffect2(() => {
|
|
1184
|
+
if (busy || pending || planAsk || !queued.length) return;
|
|
1185
|
+
const [next, ...rest] = queued;
|
|
1186
|
+
setQueued(rest);
|
|
1187
|
+
submit(next);
|
|
1188
|
+
}, [busy, pending, planAsk, queued, submit]);
|
|
1189
|
+
const initialSent = useRef(false);
|
|
1190
|
+
useEffect2(() => {
|
|
1191
|
+
if (initialPrompt && !initialSent.current) {
|
|
1192
|
+
initialSent.current = true;
|
|
1193
|
+
submit(initialPrompt);
|
|
1194
|
+
}
|
|
1195
|
+
}, [initialPrompt, submit]);
|
|
1196
|
+
useInput9((input, key) => {
|
|
1197
|
+
if (key.tab && key.shift) {
|
|
1198
|
+
const next = nextMode(runtime.agent.permissions.mode);
|
|
1199
|
+
runtime.agent.permissions.mode = next;
|
|
1200
|
+
setMode(next);
|
|
1201
|
+
} else if (key.escape && busy && !pending) {
|
|
1202
|
+
controller.current?.abort();
|
|
1203
|
+
if (queued.length) {
|
|
1204
|
+
setQueued([]);
|
|
1205
|
+
notice(`${queued.length} pesan antrian dibatalkan.`, "warn");
|
|
1206
|
+
}
|
|
1207
|
+
} else if (key.ctrl && input === "t") {
|
|
1208
|
+
const next = !showThinking;
|
|
1209
|
+
setShowThinking(next);
|
|
1210
|
+
setTranscript((t) => {
|
|
1211
|
+
const last = [...t.done, ...t.live].findLast((e) => e.kind === "thinking");
|
|
1212
|
+
const reprint = next && last?.kind === "thinking" && t.done.includes(last);
|
|
1213
|
+
return {
|
|
1214
|
+
...t,
|
|
1215
|
+
done: [
|
|
1216
|
+
...t.done,
|
|
1217
|
+
...reprint ? [entry({ kind: "thinking", text: last.text, open: true })] : [],
|
|
1218
|
+
entry({ kind: "notice", text: next ? "Thinking ditampilkan (ctrl+t untuk menutup)." : "Thinking disembunyikan (ctrl+t untuk membuka).", tone: "info" })
|
|
1219
|
+
]
|
|
1220
|
+
};
|
|
1221
|
+
});
|
|
1222
|
+
} else if (key.ctrl && input === "o" && lastTool.current) {
|
|
1223
|
+
const t = lastTool.current;
|
|
1224
|
+
notice(`\u23BF ${t.tool} ${t.target}
|
|
1225
|
+
${t.output}`);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
const pickMcp = async (name) => {
|
|
1229
|
+
const item = mcpMenu?.find((i) => i.name === name);
|
|
1230
|
+
setMcpMenu(null);
|
|
1231
|
+
if (!item) return;
|
|
1232
|
+
if (item.installed) {
|
|
1233
|
+
removeGlobalServer(runtime.home, item.name);
|
|
1234
|
+
await runtime.mcp.remove(item.name);
|
|
1235
|
+
notice(`MCP ${item.name} dihapus.`);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
const entry2 = CATALOG.find((c) => c.name === item.name);
|
|
1239
|
+
if (!entry2) return;
|
|
1240
|
+
const values = {};
|
|
1241
|
+
for (const input of entry2.inputs ?? []) {
|
|
1242
|
+
const v = await ask(input.label, !!input.secret);
|
|
1243
|
+
if (!v) return;
|
|
1244
|
+
values[input.key] = v;
|
|
1245
|
+
}
|
|
1246
|
+
const config = fillTemplate(entry2.config, values);
|
|
1247
|
+
addGlobalServer(runtime.home, entry2.name, config);
|
|
1248
|
+
notice(`MCP ${entry2.name} dipasang, menghubungkan\u2026`);
|
|
1249
|
+
await runtime.mcp.add({ name: entry2.name, config, source: "global" });
|
|
1250
|
+
const state = runtime.mcp.states().find((s) => s.name === entry2.name);
|
|
1251
|
+
if (state?.status === "ready") notice(`MCP ${entry2.name} aktif \xB7 ${state.tools} alat.`);
|
|
1252
|
+
else notice(`MCP ${entry2.name} gagal: ${state?.error ?? "tidak diketahui"}`, "error");
|
|
1253
|
+
};
|
|
1254
|
+
const pickProvider = async (id) => {
|
|
1255
|
+
setProviderMenu(null);
|
|
1256
|
+
if (!id) return;
|
|
1257
|
+
let target = id;
|
|
1258
|
+
if (id === "custom") {
|
|
1259
|
+
const name = await ask("Id provider (huruf kecil, mis. corp)");
|
|
1260
|
+
const url = name ? await ask("Base URL OpenAI-compatible") : void 0;
|
|
1261
|
+
if (!name || !url) return;
|
|
1262
|
+
writeGlobalConfig({ providers: { [name]: { baseURL: url } } }, runtime.env);
|
|
1263
|
+
runtime.reloadConfig();
|
|
1264
|
+
target = name;
|
|
1265
|
+
}
|
|
1266
|
+
const c = runtime.config;
|
|
1267
|
+
const keyless = !c.providers[target].apiKeyEnv && PRESETS.some((p) => p.id === target);
|
|
1268
|
+
if (!hasKey(c, target, runtime.env) || id === "custom") {
|
|
1269
|
+
const key = keyless ? "" : await ask(`API key untuk ${providerName(c, target)}`, true);
|
|
1270
|
+
if (key === void 0) return;
|
|
1271
|
+
const result = await runtime.addProviderKey(target, key);
|
|
1272
|
+
if (!result.ok) {
|
|
1273
|
+
notice(result.error, "error");
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
notice(`${providerName(c, target)} siap \xB7 ${result.models} model.`);
|
|
1277
|
+
}
|
|
1278
|
+
setPicker(await runtime.listModels(target));
|
|
1279
|
+
};
|
|
1280
|
+
const answer = (a) => {
|
|
1281
|
+
pending?.resolve(a);
|
|
1282
|
+
setAsks((q) => q.slice(1));
|
|
1283
|
+
if (a === "all") setMode("allowAll");
|
|
1284
|
+
};
|
|
1285
|
+
const cwd = runtime.cwd.startsWith(homedir()) ? `~${runtime.cwd.slice(homedir().length)}` : runtime.cwd;
|
|
1286
|
+
const renderEntry = (e) => {
|
|
1287
|
+
switch (e.kind) {
|
|
1288
|
+
case "header":
|
|
1289
|
+
return /* @__PURE__ */ jsx15(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsxs10(Text15, { children: [
|
|
1290
|
+
/* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), bold: true, children: " \u273B " }),
|
|
1291
|
+
/* @__PURE__ */ jsx15(Text15, { bold: true, children: `BCCLI ${version}` }),
|
|
1292
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \xB7 ${runtime.modelRef} (${runtime.providerLabel()}) \xB7 ${cwd}` })
|
|
1293
|
+
] }) }, e.id);
|
|
1294
|
+
case "user":
|
|
1295
|
+
return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: `> ${e.text}` }) }, e.id);
|
|
1296
|
+
case "assistant":
|
|
1297
|
+
if (e.cont) {
|
|
1298
|
+
return e.text ? /* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Markdown, { text: e.text }) }, e.id) : null;
|
|
1299
|
+
}
|
|
1300
|
+
if (!e.text.trim()) return null;
|
|
1301
|
+
return /* @__PURE__ */ jsxs10(Box13, { marginTop: 1, children: [
|
|
1302
|
+
/* @__PURE__ */ jsx15(Text15, { color: color(ACCENT), children: "\u25CF " }),
|
|
1303
|
+
/* @__PURE__ */ jsx15(Markdown, { text: e.text.replace(/^\s+/, "") })
|
|
1304
|
+
] }, e.id);
|
|
1305
|
+
case "thinking": {
|
|
1306
|
+
const text = e.text.trim();
|
|
1307
|
+
if (!text) return null;
|
|
1308
|
+
if (!(e.open ?? showThinking)) {
|
|
1309
|
+
const lines = text.split("\n").length;
|
|
1310
|
+
return /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: `\u273B Berpikir \xB7 ${lines} baris \xB7 ctrl+t buka` }) }, e.id);
|
|
1311
|
+
}
|
|
1312
|
+
return /* @__PURE__ */ jsxs10(Box13, { marginTop: 1, flexDirection: "column", children: [
|
|
1313
|
+
/* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "\u273B Berpikir (ctrl+t tutup)" }),
|
|
1314
|
+
/* @__PURE__ */ jsx15(Box13, { paddingLeft: 2, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, italic: true, children: text }) })
|
|
1315
|
+
] }, e.id);
|
|
1316
|
+
}
|
|
1317
|
+
case "tool":
|
|
1318
|
+
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);
|
|
1319
|
+
case "notice":
|
|
1320
|
+
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);
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
return /* @__PURE__ */ jsxs10(Box13, { flexDirection: "column", children: [
|
|
1324
|
+
/* @__PURE__ */ jsx15(Static, { items: transcript.done, children: renderEntry }),
|
|
1325
|
+
transcript.live.map(renderEntry),
|
|
1326
|
+
busy && !pending && !planAsk ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Spinner, { label: "Berpikir", startedAt }) }) : null,
|
|
1327
|
+
pending ? /* @__PURE__ */ jsx15(PermissionPrompt, { request: pending.request, onAnswer: answer }, pending.id) : null,
|
|
1328
|
+
picker ? /* @__PURE__ */ jsx15(
|
|
1329
|
+
ModelPicker,
|
|
1330
|
+
{
|
|
1331
|
+
groups: picker,
|
|
1332
|
+
current: runtime.modelRef,
|
|
1333
|
+
onPick: (m) => {
|
|
1334
|
+
setPicker(null);
|
|
1335
|
+
if (m) void runSlash("model", m);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
) : null,
|
|
1339
|
+
listPicker ? /* @__PURE__ */ jsx15(ListPicker, { title: listPicker.title, items: listPicker.items, onPick: listPicker.onPick }) : null,
|
|
1340
|
+
mcpMenu ? /* @__PURE__ */ jsx15(McpMenu, { items: mcpMenu, onPick: (n) => void pickMcp(n) }) : null,
|
|
1341
|
+
providerMenu ? /* @__PURE__ */ jsx15(ProviderMenu, { entries: providerMenu, onPick: (id) => void pickProvider(id) }) : null,
|
|
1342
|
+
prompt ? /* @__PURE__ */ jsx15(
|
|
1343
|
+
LinePrompt,
|
|
1344
|
+
{
|
|
1345
|
+
label: prompt.label,
|
|
1346
|
+
mask: prompt.mask,
|
|
1347
|
+
onSubmit: (v) => {
|
|
1348
|
+
setPrompt(null);
|
|
1349
|
+
prompt.resolve(v);
|
|
1350
|
+
},
|
|
1351
|
+
onCancel: () => {
|
|
1352
|
+
setPrompt(null);
|
|
1353
|
+
prompt.resolve(void 0);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
) : null,
|
|
1357
|
+
planAsk ? /* @__PURE__ */ jsx15(
|
|
1358
|
+
PlanApproval,
|
|
1359
|
+
{
|
|
1360
|
+
plan: planAsk.plan,
|
|
1361
|
+
onAnswer: (d) => {
|
|
1362
|
+
planAsk.resolve(d);
|
|
1363
|
+
setPlanAsk(null);
|
|
1364
|
+
if (d !== "no") setMode(d);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
) : null,
|
|
1368
|
+
/* @__PURE__ */ jsx15(TodoList, { items: todos }),
|
|
1369
|
+
queued.map((q, i) => /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: ` \u23F3 antri: ${q}` }, `${i}-${q}`)),
|
|
1370
|
+
/* @__PURE__ */ jsx15(PromptInput, { disabled: !!pending || !!picker || !!providerMenu || !!prompt || !!planAsk || !!mcpMenu || !!listPicker, history, cwd: runtime.cwd, onSubmit: onPrompt, extraCommands }),
|
|
1371
|
+
/* @__PURE__ */ jsx15(StatusBar, { mode, tokens, busy, model: modelLabel, reasoning })
|
|
1372
|
+
] });
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// src/ui/index.tsx
|
|
1376
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
1377
|
+
async function startInteractive(rt, opts) {
|
|
1378
|
+
if (opts.resume) {
|
|
1379
|
+
const { Session: Session2 } = await import("./session-7UB77CEG.js");
|
|
1380
|
+
const list = Session2.list(rt.home, rt.cwd).slice(0, 20);
|
|
1381
|
+
if (!list.length) console.log("Belum ada sesi di folder ini; memulai sesi baru.");
|
|
1382
|
+
else {
|
|
1383
|
+
list.forEach((s, i) => {
|
|
1384
|
+
console.log(`${String(i + 1).padStart(2)}. ${s.mtime.toLocaleString("id-ID")} ${s.preview}`);
|
|
1385
|
+
});
|
|
1386
|
+
const { createInterface } = await import("readline/promises");
|
|
1387
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1388
|
+
const answer = Number(await rl.question("Nomor sesi (enter = baru): "));
|
|
1389
|
+
rl.close();
|
|
1390
|
+
const chosen = list[answer - 1];
|
|
1391
|
+
if (chosen) rt.resume(chosen.session);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
const { serversToStart, setProjectTrust } = await import("./config-YUNKPCNB.js");
|
|
1395
|
+
const plan = serversToStart(rt.home, rt.cwd);
|
|
1396
|
+
if (plan.needTrust.length) {
|
|
1397
|
+
const { createInterface } = await import("readline/promises");
|
|
1398
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1399
|
+
for (const s of plan.needTrust) {
|
|
1400
|
+
const what = s.config.type === "http" ? s.config.url : [s.config.command, ...s.config.args ?? []].join(" ");
|
|
1401
|
+
const answer = (await rl.question(`Repo ini ingin menjalankan server MCP "${s.name}" (${what}). Izinkan? [y/N] `)).trim().toLowerCase();
|
|
1402
|
+
setProjectTrust(rt.home, rt.cwd, s.name, answer === "y", s.config);
|
|
1403
|
+
if (answer === "y") plan.start.push(s);
|
|
1404
|
+
}
|
|
1405
|
+
rl.close();
|
|
1406
|
+
}
|
|
1407
|
+
void rt.startMcp(plan.start);
|
|
1408
|
+
const instance = render(/* @__PURE__ */ jsx16(App, { runtime: rt, initialPrompt: opts.initialPrompt, version: opts.version }), { exitOnCtrlC: true });
|
|
1409
|
+
await instance.waitUntilExit();
|
|
1410
|
+
await rt.mcp.stop();
|
|
1411
|
+
return 0;
|
|
1412
|
+
}
|
|
1413
|
+
export {
|
|
1414
|
+
startInteractive
|
|
1415
|
+
};
|