vole-agent 0.1.4 → 0.1.6

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/dist/app.js CHANGED
@@ -1,712 +1,34 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- CliChatSession,
4
- loadConfig,
5
- renderToolResult
6
- } from "./chunk-ZGGSF3JK.js";
2
+ import{a as ie,d as $,h as le}from"./chunk-QGVGG7HC.js";import{useState as T,useEffect as ve,useCallback as I,useMemo as pe,useRef as Se}from"react";import{render as Ie,Box as a,Text as o,useInput as N,useApp as Ae,useAnimation as ke,useStdout as Re,Static as Ee}from"ink";import Me from"ink-text-input";import{readFile as _e}from"fs/promises";import{join as De}from"path";import{marked as Ve}from"marked";import{useRef as ze}from"react";import{Text as we}from"ink";import p from"chalk";import{marked as ce}from"marked";var ae=!1;function de(){ae||(ae=!0,ce.use({tokenizer:{del(){}}}))}function y(n,u=0,m=!1){switch(n.type){case"heading":{let s=(n.tokens??[]).map(i=>y(i)).join("");return n.depth===1?p.bold.underline(s)+`
7
3
 
8
- // src/app.tsx
9
- import { useState, useEffect, useCallback, useMemo, useRef as useRef2 } from "react";
10
- import { render, Box, Text as Text2, useInput, useApp, useAnimation, useStdout, Static } from "ink";
11
- import TextInput from "ink-text-input";
12
- import { readFile } from "fs/promises";
13
- import { join } from "path";
4
+ `:n.depth===2?p.bold(s)+`
14
5
 
15
- // src/Markdown.tsx
16
- import { marked as marked2 } from "marked";
17
- import { useRef } from "react";
18
- import { Text } from "ink";
6
+ `:p.bold(p.dim(s))+`
19
7
 
20
- // src/markdownFormat.ts
21
- import chalk from "chalk";
22
- import { marked } from "marked";
23
- var configured = false;
24
- function configureMarked() {
25
- if (configured) return;
26
- configured = true;
27
- marked.use({
28
- tokenizer: {
29
- del() {
30
- return void 0;
31
- }
32
- }
33
- });
34
- }
35
- function formatToken(token, listDepth = 0, ordered = false) {
36
- switch (token.type) {
37
- case "heading": {
38
- const inner = (token.tokens ?? []).map((t) => formatToken(t)).join("");
39
- if (token.depth === 1) return chalk.bold.underline(inner) + "\n\n";
40
- if (token.depth === 2) return chalk.bold(inner) + "\n\n";
41
- return chalk.bold(chalk.dim(inner)) + "\n\n";
42
- }
43
- case "paragraph": {
44
- const inner = (token.tokens ?? []).map((t) => formatToken(t)).join("");
45
- return inner + "\n\n";
46
- }
47
- case "strong": {
48
- const inner = (token.tokens ?? []).map((t) => formatToken(t)).join("");
49
- return chalk.bold(inner);
50
- }
51
- case "em": {
52
- const inner = (token.tokens ?? []).map((t) => formatToken(t)).join("");
53
- return chalk.italic(inner);
54
- }
55
- case "del": {
56
- const inner = (token.tokens ?? []).map((t) => formatToken(t)).join("");
57
- return chalk.strikethrough(inner);
58
- }
59
- case "codespan": {
60
- return chalk.yellow(token.text);
61
- }
62
- case "code": {
63
- const lines = token.text.split("\n");
64
- const formatted = lines.map((line) => " " + chalk.yellow(line)).join("\n");
65
- return chalk.dim("```" + (token.lang ?? "")) + "\n" + formatted + "\n" + chalk.dim("```") + "\n\n";
66
- }
67
- case "blockquote": {
68
- const inner = (token.tokens ?? []).map((t) => formatToken(t)).join("").trim();
69
- return inner.split("\n").map((line) => chalk.dim("\u2502 ") + chalk.italic(line)).join("\n") + "\n\n";
70
- }
71
- case "list": {
72
- const items = (token.items ?? []).map((item, i) => {
73
- const prefix = token.ordered ? chalk.bold(`${i + 1}.`) + " " : chalk.bold("\u2022") + " ";
74
- const indent = " ".repeat(listDepth);
75
- const body = (item.tokens ?? []).map((t) => {
76
- if (t.type === "list") return "\n" + formatToken(t, listDepth + 1);
77
- return formatToken(t, listDepth);
78
- }).join("").trim();
79
- return indent + prefix + body;
80
- });
81
- return items.join("\n") + "\n\n";
82
- }
83
- case "hr": {
84
- return chalk.dim("\u2500".repeat(40)) + "\n\n";
85
- }
86
- case "link": {
87
- const label = (token.tokens ?? []).map((t) => formatToken(t)).join("") || token.text;
88
- if (token.href && token.href !== label) {
89
- return label + chalk.dim(` (${token.href})`);
90
- }
91
- return label;
92
- }
93
- case "image": {
94
- return chalk.dim(`[image: ${token.text}]`);
95
- }
96
- case "html": {
97
- return token.text.replace(/<[^>]+>/g, "");
98
- }
99
- case "text": {
100
- const inner = (token.tokens ?? []).map((t) => formatToken(t)).join("");
101
- return inner || token.text;
102
- }
103
- case "space": {
104
- return "\n";
105
- }
106
- case "escape": {
107
- return token.text;
108
- }
109
- case "table": {
110
- const sep = chalk.dim(" | ");
111
- const header = (token.header ?? []).map(
112
- (cell) => chalk.bold((cell.tokens ?? []).map((t) => formatToken(t)).join(""))
113
- ).join(sep);
114
- const divider = chalk.dim("\u2500".repeat(Math.max(header.length - sep.length * ((token.header?.length ?? 1) - 1), 20)));
115
- const rows = (token.rows ?? []).map(
116
- (row) => row.map((cell) => (cell.tokens ?? []).map((t) => formatToken(t)).join("")).join(sep)
117
- );
118
- return [header, divider, ...rows].join("\n") + "\n\n";
119
- }
120
- default: {
121
- const t = token;
122
- return t.text ?? t.raw ?? "";
123
- }
124
- }
125
- }
126
- function markdownToAnsi(content) {
127
- configureMarked();
128
- const tokens = marked.lexer(content);
129
- return tokens.map((t) => formatToken(t)).join("").trimEnd();
130
- }
8
+ `}case"paragraph":return(n.tokens??[]).map(i=>y(i)).join("")+`
131
9
 
132
- // src/Markdown.tsx
133
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
134
- function Markdown({ children }) {
135
- return /* @__PURE__ */ jsx(Text, { children: markdownToAnsi(children) });
136
- }
10
+ `;case"strong":{let s=(n.tokens??[]).map(i=>y(i)).join("");return p.bold(s)}case"em":{let s=(n.tokens??[]).map(i=>y(i)).join("");return p.italic(s)}case"del":{let s=(n.tokens??[]).map(i=>y(i)).join("");return p.strikethrough(s)}case"codespan":return p.yellow(n.text);case"code":{let i=n.text.split(`
11
+ `).map(c=>" "+p.yellow(c)).join(`
12
+ `);return p.dim("```"+(n.lang??""))+`
13
+ `+i+`
14
+ `+p.dim("```")+`
137
15
 
138
- // src/app.tsx
139
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
140
- var SLASH_COMMANDS = [
141
- { command: "/help", description: "Show available commands" },
142
- { command: "/resume", description: "Resume a previous session" },
143
- { command: "/trace", description: "Show recent trace events" },
144
- { command: "/config", description: "Show redacted configuration" },
145
- { command: "/skills", description: "List loaded skills" },
146
- { command: "/clear", description: "Clear screen and reset context" },
147
- { command: "/exit", description: "Leave chat" }
148
- ];
149
- var VOLE_COLOR = "#d9ff33";
150
- function Spinner({ label }) {
151
- const { frame } = useAnimation({ interval: 80 });
152
- const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
153
- return /* @__PURE__ */ jsxs2(Text2, { color: "yellow", children: [
154
- frames[frame % frames.length],
155
- " ",
156
- label
157
- ] });
158
- }
159
- function WelcomeScreen({ model, sessionId }) {
160
- return /* @__PURE__ */ jsx2(Box, { flexDirection: "column", marginBottom: 1, gap: 1, children: /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", gap: 3, alignItems: "flex-start", children: [
161
- /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", children: [
162
- /* @__PURE__ */ jsx2(Text2, { color: VOLE_COLOR, children: " (\\_/)" }),
163
- /* @__PURE__ */ jsx2(Text2, { color: VOLE_COLOR, children: " (\u2022\u1D65\u2022)" }),
164
- /* @__PURE__ */ jsx2(Text2, { color: VOLE_COLOR, children: " /> \\" })
165
- ] }),
166
- /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", gap: 1, children: [
167
- /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", gap: 1, children: [
168
- /* @__PURE__ */ jsx2(Text2, { color: VOLE_COLOR, bold: true, children: "vole" }),
169
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2014 a capable coding and general-purpose agent" })
170
- ] }),
171
- /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", gap: 2, children: [
172
- /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", gap: 1, children: [
173
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "model" }),
174
- /* @__PURE__ */ jsx2(Text2, { children: model })
175
- ] }),
176
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7" }),
177
- /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", gap: 1, children: [
178
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "session" }),
179
- /* @__PURE__ */ jsx2(Text2, { color: "blueBright", children: sessionId.slice(-8) })
180
- ] })
181
- ] }),
182
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Type /help for commands \xB7 /exit to leave" })
183
- ] })
184
- ] }) });
185
- }
186
- function CompactHeader({ model, sessionId }) {
187
- return /* @__PURE__ */ jsxs2(Box, { marginBottom: 1, flexDirection: "row", gap: 2, alignItems: "center", children: [
188
- /* @__PURE__ */ jsx2(Text2, { color: VOLE_COLOR, bold: true, children: "vole" }),
189
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7" }),
190
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: model }),
191
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7" }),
192
- /* @__PURE__ */ jsx2(Text2, { color: "blueBright", children: sessionId.slice(-8) }),
193
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7 /help" })
194
- ] });
195
- }
196
- function SessionPicker({
197
- sessions,
198
- selectedIndex,
199
- onSelect,
200
- onCancel
201
- }) {
202
- useInput((_, key) => {
203
- if (key.escape) {
204
- onCancel();
205
- return;
206
- }
207
- if (key.return && sessions[selectedIndex] !== void 0) {
208
- onSelect(sessions[selectedIndex]);
209
- return;
210
- }
211
- });
212
- if (sessions.length === 0) {
213
- return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", marginBottom: 1, children: [
214
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No previous sessions found." }),
215
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Esc cancel" })
216
- ] });
217
- }
218
- return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", marginBottom: 1, children: [
219
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, bold: true, children: "Resume session \u2191\u2193 navigate \xB7 Enter select \xB7 Esc cancel" }),
220
- sessions.map((s, i) => {
221
- const label = s.title ?? s.id.slice(-12);
222
- const date = s.updatedAt.slice(0, 16).replace("T", " ");
223
- const active = i === selectedIndex;
224
- return /* @__PURE__ */ jsxs2(Box, { gap: 2, children: [
225
- active ? /* @__PURE__ */ jsx2(Text2, { color: VOLE_COLOR, bold: true, children: " \u25B6" }) : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " " }),
226
- /* @__PURE__ */ jsx2(Text2, { ...active ? {} : { dimColor: true }, children: date }),
227
- active ? /* @__PURE__ */ jsx2(Text2, { color: VOLE_COLOR, children: label }) : /* @__PURE__ */ jsx2(Text2, { children: label })
228
- ] }, s.id);
229
- })
230
- ] });
231
- }
232
- function StreamingMessage({ text }) {
233
- return /* @__PURE__ */ jsxs2(Box, { marginBottom: 1, flexDirection: "column", children: [
234
- /* @__PURE__ */ jsx2(Text2, { color: "green", bold: true, children: "Assistant" }),
235
- /* @__PURE__ */ jsxs2(Box, { paddingLeft: 2, children: [
236
- /* @__PURE__ */ jsx2(Text2, { children: text }),
237
- /* @__PURE__ */ jsx2(Text2, { color: "blueBright", children: "\u258A" })
238
- ] })
239
- ] });
240
- }
241
- function ToolProgress({ toolName, input }) {
242
- const preview = input !== void 0 ? (() => {
243
- try {
244
- const s = JSON.stringify(input);
245
- return s.length > 60 ? s.slice(0, 57) + "\u2026" : s;
246
- } catch {
247
- return "";
248
- }
249
- })() : "";
250
- return /* @__PURE__ */ jsxs2(Box, { marginBottom: 1, flexDirection: "column", children: [
251
- /* @__PURE__ */ jsx2(Spinner, { label: `${toolName}` }),
252
- preview !== "" && /* @__PURE__ */ jsx2(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: preview }) })
253
- ] });
254
- }
255
- function TodosPanel({ todos }) {
256
- if (todos.length === 0) return null;
257
- const done = todos.filter((t) => t.status === "completed").length;
258
- return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
259
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, bold: true, children: `Todo ${done}/${todos.length}` }),
260
- todos.map((todo, i) => {
261
- const icon = todo.status === "completed" ? "\u2713" : todo.status === "in_progress" ? "\u203A" : "\xB7";
262
- const color = todo.status === "completed" ? "green" : todo.status === "in_progress" ? "yellow" : void 0;
263
- return /* @__PURE__ */ jsxs2(Box, { children: [
264
- color !== void 0 ? /* @__PURE__ */ jsxs2(Text2, { color, children: [
265
- icon,
266
- " "
267
- ] }) : /* @__PURE__ */ jsxs2(Text2, { children: [
268
- icon,
269
- " "
270
- ] }),
271
- /* @__PURE__ */ jsx2(Text2, { dimColor: todo.status === "completed", children: todo.content })
272
- ] }, i);
273
- })
274
- ] });
275
- }
276
- function ApprovalPrompt({
277
- request,
278
- onApprove,
279
- onDeny
280
- }) {
281
- useInput((inputChar) => {
282
- if (inputChar === "y" || inputChar === "Y") {
283
- onApprove();
284
- } else {
285
- onDeny();
286
- }
287
- });
288
- const inputPreview = (() => {
289
- try {
290
- const s = JSON.stringify(request.call.input, null, 2);
291
- const lines = s.split("\n");
292
- return lines.length > 6 ? lines.slice(0, 6).join("\n") + "\n \u2026" : s;
293
- } catch {
294
- return "";
295
- }
296
- })();
297
- const riskColor = request.decision.risk === "high" ? "red" : request.decision.risk === "medium" ? "yellow" : "green";
298
- return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginBottom: 1, children: [
299
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: "\u26A0 Approval Required" }),
300
- /* @__PURE__ */ jsxs2(Box, { gap: 1, children: [
301
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Tool:" }),
302
- /* @__PURE__ */ jsx2(Text2, { bold: true, children: request.call.name }),
303
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\xB7" }),
304
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Risk:" }),
305
- /* @__PURE__ */ jsx2(Text2, { color: riskColor, bold: true, children: request.decision.risk })
306
- ] }),
307
- inputPreview !== "" && /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
308
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Input:" }),
309
- /* @__PURE__ */ jsx2(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: inputPreview }) })
310
- ] }),
311
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: request.decision.reason }),
312
- /* @__PURE__ */ jsx2(Box, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: " y approve any other key deny" }) })
313
- ] });
314
- }
315
- function SuggestionsBox({
316
- suggestions,
317
- selectedIndex
318
- }) {
319
- if (suggestions.length === 0) return null;
320
- return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", marginBottom: 1, paddingLeft: 2, children: [
321
- suggestions.map((s, i) => /* @__PURE__ */ jsxs2(Box, { gap: 2, children: [
322
- i === selectedIndex ? /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: s.command }) : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: s.command }),
323
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: s.description })
324
- ] }, s.command)),
325
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Tab \xB7 complete \u2191\u2193 \xB7 select" })
326
- ] });
327
- }
328
- function ChatApp({ config, cliOptions, sessionId }) {
329
- const { exit } = useApp();
330
- const { write: writeToStdout } = useStdout();
331
- const [session, setSession] = useState(null);
332
- const [loadError, setLoadError] = useState(null);
333
- const [activeSessionId, setActiveSessionId] = useState(sessionId);
334
- const [messages, setMessages] = useState([]);
335
- const [input, setInput] = useState("");
336
- const [isSending, setIsSending] = useState(false);
337
- const [inputHistory, setInputHistory] = useState([]);
338
- const [historyIndex, setHistoryIndex] = useState(-1);
339
- const [draftInput, setDraftInput] = useState("");
340
- const [suggestionIndex, setSuggestionIndex] = useState(0);
341
- const [streamingText, setStreamingText] = useState("");
342
- const [currentTool, setCurrentTool] = useState(null);
343
- const [todos, setTodos] = useState([]);
344
- const [sessionPicker, setSessionPicker] = useState(null);
345
- const [pendingApproval, setPendingApproval] = useState(null);
346
- const suggestions = useMemo(() => {
347
- if (!input.startsWith("/")) return [];
348
- if (input === "/") return SLASH_COMMANDS;
349
- return SLASH_COMMANDS.filter(
350
- (c) => c.command.startsWith(input) && c.command !== input
351
- );
352
- }, [input]);
353
- const showSuggestions = suggestions.length > 0;
354
- const inkApprovalResolver = useMemo(
355
- () => ({
356
- resolve: (request) => new Promise((resolve) => {
357
- setPendingApproval({
358
- request,
359
- resolve: (decision) => {
360
- setPendingApproval(null);
361
- resolve(decision);
362
- }
363
- });
364
- })
365
- }),
366
- []
367
- );
368
- useEffect(() => {
369
- CliChatSession.createConfigured(config, cliOptions, {
370
- approvalResolver: inkApprovalResolver,
371
- preferStreaming: true,
372
- ...sessionId !== void 0 ? { sessionId } : {}
373
- }).then((s) => {
374
- setSession(s);
375
- setActiveSessionId(s.sessionId);
376
- }).catch((err) => {
377
- setLoadError(err instanceof Error ? err.message : "Failed to create session.");
378
- });
379
- }, [config, cliOptions, inkApprovalResolver, sessionId]);
380
- const resetSession = useCallback(async () => {
381
- session?.close();
382
- setSession(null);
383
- setMessages([]);
384
- setStreamingText("");
385
- setCurrentTool(null);
386
- setTodos([]);
387
- setPendingApproval(null);
388
- try {
389
- const s = await CliChatSession.createConfigured(config, cliOptions, {
390
- approvalResolver: inkApprovalResolver,
391
- preferStreaming: true
392
- });
393
- setSession(s);
394
- setActiveSessionId(s.sessionId);
395
- } catch (err) {
396
- setLoadError(err instanceof Error ? err.message : "Failed to reset session.");
397
- }
398
- }, [session, config, cliOptions, inkApprovalResolver]);
399
- const handleResumeSession = useCallback(async (target) => {
400
- setSessionPicker(null);
401
- session?.close();
402
- setSession(null);
403
- setMessages([]);
404
- setStreamingText("");
405
- setCurrentTool(null);
406
- setTodos([]);
407
- setPendingApproval(null);
408
- try {
409
- const s = await CliChatSession.createConfigured(config, cliOptions, {
410
- approvalResolver: inkApprovalResolver,
411
- preferStreaming: true,
412
- sessionId: target.id
413
- });
414
- const stored = await s.loadMessages();
415
- const history = stored.flatMap((m) => {
416
- if (m.role === "user" && m.content) {
417
- return [{ role: "user", content: m.content }];
418
- }
419
- if (m.role === "assistant" && m.content) {
420
- return [{ role: "assistant", content: m.content }];
421
- }
422
- return [];
423
- });
424
- setMessages(history);
425
- setSession(s);
426
- setActiveSessionId(s.sessionId);
427
- } catch (err) {
428
- setLoadError(err instanceof Error ? err.message : "Failed to resume session.");
429
- }
430
- }, [session, config, cliOptions, inkApprovalResolver]);
431
- const handleEvent = useCallback((event) => {
432
- if (event.type === "token_delta") {
433
- setStreamingText((prev) => prev + event.delta);
434
- } else if (event.type === "tool_started") {
435
- setCurrentTool({ name: event.toolName });
436
- } else if (event.type === "tool_call_requested") {
437
- setCurrentTool({ name: event.call.name, input: event.call.input });
438
- } else if (event.type === "tool_completed") {
439
- setCurrentTool(null);
440
- if (event.toolName === "update_todos") return;
441
- const resultText = renderToolResult(event.result);
442
- setMessages((prev) => [...prev, { role: "tool_result", toolName: event.toolName, content: resultText, ok: true }]);
443
- } else if (event.type === "tool_failed") {
444
- setCurrentTool(null);
445
- setMessages((prev) => [...prev, { role: "tool_result", toolName: event.toolName, content: event.error.message, ok: false }]);
446
- } else if (event.type === "todos_updated") {
447
- setTodos([...event.todos]);
448
- } else if (event.type === "run_failed") {
449
- setMessages((prev) => [...prev, { role: "error", content: event.error.message }]);
450
- }
451
- }, []);
452
- const abortControllerRef = useRef2(null);
453
- const sendMessage = useCallback(
454
- async (message) => {
455
- if (session === null || isSending || message.trim() === "") return;
456
- const trimmed = message.trim();
457
- if (config.secrets.apiKey === void 0) {
458
- setMessages((prev) => [...prev, { role: "user", content: trimmed }, { role: "error", content: 'No API key configured. Add one to ~/.vole/config.json (e.g. {"apiKey": "sk-..."}) or set VOLE_API_KEY / ANTHROPIC_API_KEY / OPENROUTER_API_KEY in your shell.' }]);
459
- return;
460
- }
461
- const controller = new AbortController();
462
- abortControllerRef.current = controller;
463
- setMessages((prev) => [...prev, { role: "user", content: trimmed }]);
464
- setIsSending(true);
465
- setStreamingText("");
466
- setCurrentTool(null);
467
- try {
468
- const turn = await session.sendMessage(trimmed, { onEvent: handleEvent, signal: controller.signal });
469
- setStreamingText("");
470
- setCurrentTool(null);
471
- if (turn.assistantText !== "" && !controller.signal.aborted) {
472
- setMessages((prev) => [...prev, { role: "assistant", content: turn.assistantText }]);
473
- }
474
- } finally {
475
- abortControllerRef.current = null;
476
- setIsSending(false);
477
- }
478
- },
479
- [session, isSending, handleEvent]
480
- );
481
- const handleSlashCommand = useCallback(
482
- async (command) => {
483
- if (session === null) return;
484
- if (command === "/resume") {
485
- const sessions = await session.listSessions({ limit: 20 });
486
- const resumable = sessions.filter((s) => s.id !== session.sessionId);
487
- setSessionPicker({ sessions: resumable, selectedIndex: 0 });
488
- return;
489
- }
490
- if (command === "/clear") {
491
- writeToStdout("\x1B[2J\x1B[H");
492
- void resetSession();
493
- return;
494
- }
495
- const lines = await session.runSlashCommand(command);
496
- setMessages((prev) => [...prev, { role: "slash_result", command, lines }]);
497
- },
498
- [session, writeToStdout, resetSession, handleResumeSession]
499
- );
500
- const handleChange = useCallback(
501
- (value) => {
502
- if (value.includes(" ")) {
503
- const base = value.replace(/\t/g, "");
504
- const filtered = base.startsWith("/") ? SLASH_COMMANDS.filter((c) => c.command.startsWith(base) && c.command !== base) : [];
505
- const target = filtered[suggestionIndex] ?? filtered[0];
506
- setInput(target !== void 0 ? target.command : base);
507
- setSuggestionIndex(0);
508
- setHistoryIndex(-1);
509
- } else {
510
- setInput(value);
511
- setSuggestionIndex(0);
512
- setHistoryIndex(-1);
513
- setDraftInput(value);
514
- }
515
- },
516
- [suggestionIndex]
517
- );
518
- useInput(
519
- (_, key) => {
520
- if (sessionPicker !== null) {
521
- if (key.upArrow) {
522
- setSessionPicker((p) => p && { ...p, selectedIndex: Math.max(0, p.selectedIndex - 1) });
523
- } else if (key.downArrow) {
524
- setSessionPicker((p) => p && { ...p, selectedIndex: Math.min(p.sessions.length - 1, p.selectedIndex + 1) });
525
- }
526
- return;
527
- }
528
- if (key.upArrow) {
529
- if (showSuggestions) {
530
- setSuggestionIndex((i) => Math.max(0, i - 1));
531
- } else if (historyIndex < inputHistory.length - 1) {
532
- const newIndex = historyIndex + 1;
533
- setHistoryIndex(newIndex);
534
- setInput(inputHistory[newIndex] ?? "");
535
- }
536
- } else if (key.downArrow) {
537
- if (showSuggestions) {
538
- setSuggestionIndex((i) => Math.min(suggestions.length - 1, i + 1));
539
- } else if (historyIndex > 0) {
540
- const newIndex = historyIndex - 1;
541
- setHistoryIndex(newIndex);
542
- setInput(inputHistory[newIndex] ?? "");
543
- } else if (historyIndex === 0) {
544
- setHistoryIndex(-1);
545
- setInput(draftInput);
546
- }
547
- }
548
- },
549
- { isActive: !isSending && pendingApproval === null }
550
- );
551
- useInput(
552
- (_, key) => {
553
- if (key.escape) {
554
- abortControllerRef.current?.abort();
555
- setStreamingText("");
556
- setCurrentTool(null);
557
- }
558
- },
559
- { isActive: isSending }
560
- );
561
- useInput(
562
- (inputChar, key) => {
563
- if (key.ctrl && inputChar === "c") exit();
564
- },
565
- { isActive: true }
566
- );
567
- const handleSubmit = useCallback(
568
- (value) => {
569
- if (isSending || pendingApproval !== null) return;
570
- const trimmed = value.trim();
571
- if (trimmed === "") return;
572
- setInput("");
573
- setHistoryIndex(-1);
574
- setSuggestionIndex(0);
575
- if (trimmed === "/exit") {
576
- exit();
577
- return;
578
- }
579
- if (trimmed.startsWith("/")) {
580
- void handleSlashCommand(trimmed);
581
- return;
582
- }
583
- setInputHistory((prev) => prev[0] === trimmed ? prev : [trimmed, ...prev.slice(0, 49)]);
584
- void sendMessage(trimmed);
585
- },
586
- [isSending, pendingApproval, sendMessage, exit, handleSlashCommand]
587
- );
588
- if (loadError !== null) {
589
- return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", padding: 1, children: [
590
- /* @__PURE__ */ jsx2(Text2, { color: "red", bold: true, children: "Error" }),
591
- /* @__PURE__ */ jsx2(Text2, { children: loadError })
592
- ] });
593
- }
594
- if (session === null) {
595
- return /* @__PURE__ */ jsx2(Box, { padding: 1, children: /* @__PURE__ */ jsx2(Spinner, { label: "Starting Vole\u2026" }) });
596
- }
597
- const sidLabel = activeSessionId ?? "\u2026";
598
- const modelLabel = `${config.model.provider}/${config.model.model}`;
599
- const hasMessages = messages.length > 0 || streamingText !== "" || currentTool !== null || isSending;
600
- return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", children: [
601
- hasMessages ? /* @__PURE__ */ jsx2(CompactHeader, { model: modelLabel, sessionId: sidLabel }) : /* @__PURE__ */ jsx2(WelcomeScreen, { model: modelLabel, sessionId: sidLabel }),
602
- /* @__PURE__ */ jsx2(Static, { items: messages, children: (msg, i) => {
603
- if (msg.role === "user") return /* @__PURE__ */ jsxs2(Box, { marginBottom: 1, children: [
604
- /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "You " }),
605
- /* @__PURE__ */ jsx2(Text2, { children: msg.content })
606
- ] }, i);
607
- if (msg.role === "tool_result") return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", marginBottom: 1, paddingLeft: 2, children: [
608
- /* @__PURE__ */ jsxs2(Box, { gap: 1, children: [
609
- /* @__PURE__ */ jsx2(Text2, { color: msg.ok ? "green" : "red", children: msg.ok ? "\u2713" : "\u2717" }),
610
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, bold: true, children: msg.toolName })
611
- ] }),
612
- /* @__PURE__ */ jsx2(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
613
- msg.content.slice(0, 200),
614
- msg.content.length > 200 ? " \u2026" : ""
615
- ] }) })
616
- ] }, i);
617
- if (msg.role === "error") return /* @__PURE__ */ jsxs2(Box, { marginBottom: 1, borderStyle: "single", borderColor: "red", paddingX: 1, children: [
618
- /* @__PURE__ */ jsx2(Text2, { color: "red", bold: true, children: "\u2717 " }),
619
- /* @__PURE__ */ jsx2(Text2, { color: "red", children: msg.content })
620
- ] }, i);
621
- if (msg.role === "slash_result") return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", marginBottom: 1, paddingLeft: 2, children: [
622
- /* @__PURE__ */ jsx2(Text2, { color: "blue", dimColor: true, children: msg.command }),
623
- msg.lines.map((line, j) => /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: line }, j))
624
- ] }, i);
625
- return /* @__PURE__ */ jsxs2(Box, { flexDirection: "column", marginBottom: 1, children: [
626
- /* @__PURE__ */ jsx2(Text2, { color: "green", bold: true, children: "Assistant" }),
627
- /* @__PURE__ */ jsx2(Box, { paddingLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx2(Markdown, { children: msg.content }) })
628
- ] }, i);
629
- } }),
630
- streamingText !== "" && /* @__PURE__ */ jsx2(StreamingMessage, { text: streamingText }),
631
- currentTool !== null && /* @__PURE__ */ jsx2(ToolProgress, { toolName: currentTool.name, input: currentTool.input }),
632
- /* @__PURE__ */ jsx2(TodosPanel, { todos }),
633
- pendingApproval !== null && /* @__PURE__ */ jsx2(
634
- ApprovalPrompt,
635
- {
636
- request: pendingApproval.request,
637
- onApprove: () => pendingApproval.resolve({ approved: true, reason: "Approved from CLI." }),
638
- onDeny: () => pendingApproval.resolve({ approved: false, reason: "Denied from CLI." })
639
- }
640
- ),
641
- sessionPicker !== null && /* @__PURE__ */ jsx2(
642
- SessionPicker,
643
- {
644
- sessions: sessionPicker.sessions,
645
- selectedIndex: sessionPicker.selectedIndex,
646
- onSelect: (s) => void handleResumeSession(s),
647
- onCancel: () => setSessionPicker(null)
648
- }
649
- ),
650
- !isSending && pendingApproval === null && sessionPicker === null && /* @__PURE__ */ jsx2(SuggestionsBox, { suggestions, selectedIndex: suggestionIndex }),
651
- !isSending && pendingApproval === null && sessionPicker === null && /* @__PURE__ */ jsxs2(Box, { gap: 1, children: [
652
- /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "\u203A" }),
653
- /* @__PURE__ */ jsx2(
654
- TextInput,
655
- {
656
- value: input,
657
- onChange: handleChange,
658
- onSubmit: handleSubmit,
659
- focus: pendingApproval === null && !isSending
660
- }
661
- )
662
- ] }),
663
- isSending && pendingApproval === null && /* @__PURE__ */ jsxs2(Box, { gap: 2, children: [
664
- /* @__PURE__ */ jsx2(Spinner, { label: "Thinking\u2026" }),
665
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Esc to stop" })
666
- ] })
667
- ] });
668
- }
669
- async function readJsonFile(path) {
670
- try {
671
- return JSON.parse(await readFile(path, "utf8"));
672
- } catch {
673
- return void 0;
674
- }
675
- }
676
- async function runInkChat({ args, env, sessionsDirectory }) {
677
- let config;
678
- try {
679
- const home = env.HOME ?? process.env.HOME;
680
- const input = { env };
681
- if (home !== void 0) input.userConfig = await readJsonFile(join(home, ".vole", "config.json"));
682
- input.projectConfig = await readJsonFile("vole.config.json");
683
- config = loadConfig(input);
684
- } catch (err) {
685
- process.stderr.write(
686
- `Configuration error: ${err instanceof Error ? err.message : String(err)}
687
- `
688
- );
689
- process.exitCode = 1;
690
- return;
691
- }
692
- const sessionIndex = args.indexOf("--session");
693
- const sessionId = sessionIndex !== -1 && args[sessionIndex + 1] !== void 0 ? args[sessionIndex + 1] : void 0;
694
- const cliOptions = {
695
- env,
696
- ...sessionsDirectory !== void 0 ? { sessionsDirectory } : {}
697
- };
698
- const { waitUntilExit } = render(
699
- /* @__PURE__ */ jsx2(
700
- ChatApp,
701
- {
702
- config,
703
- cliOptions,
704
- ...sessionId !== void 0 ? { sessionId } : {}
705
- }
706
- )
707
- );
708
- await waitUntilExit();
709
- }
710
- export {
711
- runInkChat
712
- };
16
+ `}case"blockquote":return(n.tokens??[]).map(i=>y(i)).join("").trim().split(`
17
+ `).map(i=>p.dim("\u2502 ")+p.italic(i)).join(`
18
+ `)+`
19
+
20
+ `;case"list":return(n.items??[]).map((i,c)=>{let g=n.ordered?p.bold(`${c+1}.`)+" ":p.bold("\u2022")+" ",C=" ".repeat(u),f=(i.tokens??[]).map(b=>b.type==="list"?`
21
+ `+y(b,u+1):y(b,u)).join("").trim();return C+g+f}).join(`
22
+ `)+`
23
+
24
+ `;case"hr":return p.dim("\u2500".repeat(40))+`
25
+
26
+ `;case"link":{let s=(n.tokens??[]).map(i=>y(i)).join("")||n.text;return n.href&&n.href!==s?s+p.dim(` (${n.href})`):s}case"image":return p.dim(`[image: ${n.text}]`);case"html":return n.text.replace(/<[^>]+>/g,"");case"text":return(n.tokens??[]).map(i=>y(i)).join("")||n.text;case"space":return`
27
+ `;case"escape":return n.text;case"table":{let s=p.dim(" | "),i=(n.header??[]).map(C=>p.bold((C.tokens??[]).map(f=>y(f)).join(""))).join(s),c=p.dim("\u2500".repeat(Math.max(i.length-s.length*((n.header?.length??1)-1),20))),g=(n.rows??[]).map(C=>C.map(f=>(f.tokens??[]).map(b=>y(b)).join("")).join(s));return[i,c,...g].join(`
28
+ `)+`
29
+
30
+ `}default:{let s=n;return s.text??s.raw??""}}}function ue(n){return de(),ce.lexer(n).map(m=>y(m)).join("").trimEnd()}import{Fragment as en,jsx as Be,jsxs as nn}from"react/jsx-runtime";function me({children:n}){return Be(we,{children:ue(n)})}import{jsx as e,jsxs as d}from"react/jsx-runtime";var X=[{command:"/help",description:"Show available commands"},{command:"/resume",description:"Resume a previous session"},{command:"/trace",description:"Show recent trace events"},{command:"/config",description:"Show redacted configuration"},{command:"/skills",description:"List loaded skills"},{command:"/clear",description:"Clear screen and reset context"},{command:"/exit",description:"Leave chat"}],A="#d9ff33";function U({label:n}){let{frame:u}=ke({interval:80}),m=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];return d(o,{color:"yellow",children:[m[u%m.length]," ",n]})}function je({model:n,sessionId:u}){return e(a,{flexDirection:"column",marginBottom:1,gap:1,children:d(a,{flexDirection:"row",gap:3,alignItems:"flex-start",children:[d(a,{flexDirection:"column",children:[e(o,{color:A,children:" (\\_/)"}),e(o,{color:A,children:" (\u2022\u1D65\u2022)"}),e(o,{color:A,children:" /> \\"})]}),d(a,{flexDirection:"column",gap:1,children:[d(a,{flexDirection:"row",gap:1,children:[e(o,{color:A,bold:!0,children:"vole"}),e(o,{dimColor:!0,children:"\u2014 a capable coding and general-purpose agent"})]}),d(a,{flexDirection:"row",gap:2,children:[d(a,{flexDirection:"row",gap:1,children:[e(o,{dimColor:!0,children:"model"}),e(o,{children:n})]}),e(o,{dimColor:!0,children:"\xB7"}),d(a,{flexDirection:"row",gap:1,children:[e(o,{dimColor:!0,children:"session"}),e(o,{color:"blueBright",children:u.slice(-8)})]})]}),e(o,{dimColor:!0,children:"Type /help for commands \xB7 /exit to leave"})]})]})})}function Le({model:n,sessionId:u}){return d(a,{marginBottom:1,flexDirection:"row",gap:2,alignItems:"center",children:[e(o,{color:A,bold:!0,children:"vole"}),e(o,{dimColor:!0,children:"\xB7"}),e(o,{dimColor:!0,children:n}),e(o,{dimColor:!0,children:"\xB7"}),e(o,{color:"blueBright",children:u.slice(-8)}),e(o,{dimColor:!0,children:"\xB7 /help"})]})}function Pe({sessions:n,selectedIndex:u,onSelect:m,onCancel:s}){return N((i,c)=>{if(c.escape){s();return}if(c.return&&n[u]!==void 0){m(n[u]);return}}),n.length===0?d(a,{flexDirection:"column",marginBottom:1,children:[e(o,{dimColor:!0,children:"No previous sessions found."}),e(o,{dimColor:!0,children:"Esc cancel"})]}):d(a,{flexDirection:"column",marginBottom:1,children:[e(o,{dimColor:!0,bold:!0,children:"Resume session \u2191\u2193 navigate \xB7 Enter select \xB7 Esc cancel"}),n.map((i,c)=>{let g=i.title??i.id.slice(-12),C=i.updatedAt.slice(0,16).replace("T"," "),f=c===u;return d(a,{gap:2,children:[f?e(o,{color:A,bold:!0,children:" \u25B6"}):e(o,{dimColor:!0,children:" "}),e(o,{...f?{}:{dimColor:!0},children:C}),f?e(o,{color:A,children:g}):e(o,{children:g})]},i.id)})]})}function Ne({text:n}){return d(a,{marginBottom:1,flexDirection:"column",children:[e(o,{color:"green",bold:!0,children:"Assistant"}),d(a,{paddingLeft:2,children:[e(o,{children:n}),e(o,{color:"blueBright",children:"\u258A"})]})]})}function Oe({toolName:n,input:u}){let m=u!==void 0?(()=>{try{let s=JSON.stringify(u);return s.length>60?s.slice(0,57)+"\u2026":s}catch{return""}})():"";return d(a,{marginBottom:1,flexDirection:"column",children:[e(U,{label:`${n}`}),m!==""&&e(a,{paddingLeft:2,children:e(o,{dimColor:!0,children:m})})]})}function He({todos:n}){if(n.length===0)return null;let u=n.filter(m=>m.status==="completed").length;return d(a,{flexDirection:"column",marginBottom:1,borderStyle:"single",borderColor:"gray",paddingX:1,children:[e(o,{dimColor:!0,bold:!0,children:`Todo ${u}/${n.length}`}),n.map((m,s)=>{let i=m.status==="completed"?"\u2713":m.status==="in_progress"?"\u203A":"\xB7",c=m.status==="completed"?"green":m.status==="in_progress"?"yellow":void 0;return d(a,{children:[c!==void 0?d(o,{color:c,children:[i," "]}):d(o,{children:[i," "]}),e(o,{dimColor:m.status==="completed",children:m.content})]},s)})]})}function $e({request:n,onApprove:u,onDeny:m}){N(c=>{c==="y"||c==="Y"?u():m()});let s=(()=>{try{let c=JSON.stringify(n.call.input,null,2),g=c.split(`
31
+ `);return g.length>6?g.slice(0,6).join(`
32
+ `)+`
33
+ \u2026`:c}catch{return""}})(),i=n.decision.risk==="high"?"red":n.decision.risk==="medium"?"yellow":"green";return d(a,{flexDirection:"column",borderStyle:"round",borderColor:"yellow",paddingX:1,marginBottom:1,children:[e(o,{bold:!0,color:"yellow",children:"\u26A0 Approval Required"}),d(a,{gap:1,children:[e(o,{dimColor:!0,children:"Tool:"}),e(o,{bold:!0,children:n.call.name}),e(o,{dimColor:!0,children:"\xB7"}),e(o,{dimColor:!0,children:"Risk:"}),e(o,{color:i,bold:!0,children:n.decision.risk})]}),s!==""&&d(a,{flexDirection:"column",marginTop:1,marginBottom:1,children:[e(o,{dimColor:!0,children:"Input:"}),e(a,{paddingLeft:2,children:e(o,{dimColor:!0,children:s})})]}),e(o,{dimColor:!0,children:n.decision.reason}),e(a,{marginTop:1,children:e(o,{color:"yellow",children:" y approve any other key deny"})})]})}function We({suggestions:n,selectedIndex:u}){return n.length===0?null:d(a,{flexDirection:"column",marginBottom:1,paddingLeft:2,children:[n.map((m,s)=>d(a,{gap:2,children:[s===u?e(o,{color:"cyan",bold:!0,children:m.command}):e(o,{dimColor:!0,children:m.command}),e(o,{dimColor:!0,children:m.description})]},m.command)),e(o,{dimColor:!0,children:"Tab \xB7 complete \u2191\u2193 \xB7 select"})]})}function qe({config:n,cliOptions:u,sessionId:m}){let{exit:s}=Ae(),{write:i}=Re(),[c,g]=T(null),[C,f]=T(null),[b,W]=T(m),[z,v]=T([]),[k,R]=T(""),[w,G]=T(!1),[q,ge]=T([]),[j,E]=T(-1),[xe,Te]=T(""),[F,L]=T(0),[J,M]=T(""),[O,S]=T(null),[he,K]=T([]),[_,P]=T(null),[B,H]=T(null),Y=pe(()=>k.startsWith("/")?k==="/"?X:X.filter(t=>t.command.startsWith(k)&&t.command!==k):[],[k]),Q=Y.length>0,D=pe(()=>({resolve:t=>new Promise(r=>{H({request:t,resolve:l=>{H(null),r(l)}})})}),[]);ve(()=>{$.createConfigured(n,u,{approvalResolver:D,preferStreaming:!0,...m!==void 0?{sessionId:m}:{}}).then(t=>{g(t),W(t.sessionId)}).catch(t=>{f(t instanceof Error?t.message:"Failed to create session.")})},[n,u,D,m]);let Z=I(async()=>{c?.close(),g(null),v([]),M(""),S(null),K([]),H(null);try{let t=await $.createConfigured(n,u,{approvalResolver:D,preferStreaming:!0});g(t),W(t.sessionId)}catch(t){f(t instanceof Error?t.message:"Failed to reset session.")}},[c,n,u,D]),ee=I(async t=>{P(null),c?.close(),g(null),v([]),M(""),S(null),K([]),H(null);try{let r=await $.createConfigured(n,u,{approvalResolver:D,preferStreaming:!0,sessionId:t.id}),x=(await r.loadMessages()).flatMap(h=>h.role==="user"&&h.content?[{role:"user",content:h.content}]:h.role==="assistant"&&h.content?[{role:"assistant",content:h.content}]:[]);v(x),g(r),W(r.sessionId)}catch(r){f(r instanceof Error?r.message:"Failed to resume session.")}},[c,n,u,D]),ne=I(t=>{if(t.type==="token_delta")M(r=>r+t.delta);else if(t.type==="tool_started")S({name:t.toolName});else if(t.type==="tool_call_requested")S({name:t.call.name,input:t.call.input});else if(t.type==="tool_completed"){if(S(null),t.toolName==="update_todos")return;let r=le(t.result);v(l=>[...l,{role:"tool_result",toolName:t.toolName,content:r,ok:!0}])}else t.type==="tool_failed"?(S(null),v(r=>[...r,{role:"tool_result",toolName:t.toolName,content:t.error.message,ok:!1}])):t.type==="todos_updated"?K([...t.todos]):t.type==="run_failed"&&v(r=>[...r,{role:"error",content:t.error.message}])},[]),V=Se(null),te=I(async t=>{if(c===null||w||t.trim()==="")return;let r=t.trim();if(n.secrets.apiKey===void 0){v(x=>[...x,{role:"user",content:r},{role:"error",content:'No API key configured. Add one to ~/.vole/config.json (e.g. {"apiKey": "sk-..."}) or set VOLE_API_KEY / ANTHROPIC_API_KEY / OPENROUTER_API_KEY in your shell.'}]);return}let l=new AbortController;V.current=l,v(x=>[...x,{role:"user",content:r}]),G(!0),M(""),S(null);try{let x=await c.sendMessage(r,{onEvent:ne,signal:l.signal});M(""),S(null),x.assistantText!==""&&!l.signal.aborted&&v(h=>[...h,{role:"assistant",content:x.assistantText}])}finally{V.current=null,G(!1)}},[c,w,ne]),oe=I(async t=>{if(c===null)return;if(t==="/resume"){let x=(await c.listSessions({limit:20})).filter(h=>h.id!==c.sessionId);P({sessions:x,selectedIndex:0});return}if(t==="/clear"){i("\x1B[2J\x1B[H"),Z();return}let r=await c.runSlashCommand(t);v(l=>[...l,{role:"slash_result",command:t,lines:r}])},[c,i,Z,ee]),ye=I(t=>{if(t.includes(" ")){let r=t.replace(/\t/g,""),l=r.startsWith("/")?X.filter(h=>h.command.startsWith(r)&&h.command!==r):[],x=l[F]??l[0];R(x!==void 0?x.command:r),L(0),E(-1)}else R(t),L(0),E(-1),Te(t)},[F]);N((t,r)=>{if(_!==null){r.upArrow?P(l=>l&&{...l,selectedIndex:Math.max(0,l.selectedIndex-1)}):r.downArrow&&P(l=>l&&{...l,selectedIndex:Math.min(l.sessions.length-1,l.selectedIndex+1)});return}if(r.upArrow){if(Q)L(l=>Math.max(0,l-1));else if(j<q.length-1){let l=j+1;E(l),R(q[l]??"")}}else if(r.downArrow)if(Q)L(l=>Math.min(Y.length-1,l+1));else if(j>0){let l=j-1;E(l),R(q[l]??"")}else j===0&&(E(-1),R(xe))},{isActive:!w&&B===null}),N((t,r)=>{r.escape&&(V.current?.abort(),M(""),S(null))},{isActive:w}),N((t,r)=>{r.ctrl&&t==="c"&&s()},{isActive:!0});let Ce=I(t=>{if(w||B!==null)return;let r=t.trim();if(r!==""){if(R(""),E(-1),L(0),r==="/exit"){s();return}if(r.startsWith("/")){oe(r);return}ge(l=>l[0]===r?l:[r,...l.slice(0,49)]),te(r)}},[w,B,te,s,oe]);if(C!==null)return d(a,{flexDirection:"column",padding:1,children:[e(o,{color:"red",bold:!0,children:"Error"}),e(o,{children:C})]});if(c===null)return e(a,{padding:1,children:e(U,{label:"Starting Vole\u2026"})});let re=b??"\u2026",se=`${n.model.provider}/${n.model.model}`,be=z.length>0||J!==""||O!==null||w;return d(a,{flexDirection:"column",children:[be?e(Le,{model:se,sessionId:re}):e(je,{model:se,sessionId:re}),e(Ee,{items:z,children:(t,r)=>t.role==="user"?d(a,{marginBottom:1,children:[e(o,{color:"cyan",bold:!0,children:"You "}),e(o,{children:t.content})]},r):t.role==="tool_result"?d(a,{flexDirection:"column",marginBottom:1,paddingLeft:2,children:[d(a,{gap:1,children:[e(o,{color:t.ok?"green":"red",children:t.ok?"\u2713":"\u2717"}),e(o,{dimColor:!0,bold:!0,children:t.toolName})]}),e(a,{paddingLeft:2,children:d(o,{dimColor:!0,children:[t.content.slice(0,200),t.content.length>200?" \u2026":""]})})]},r):t.role==="error"?d(a,{marginBottom:1,borderStyle:"single",borderColor:"red",paddingX:1,children:[e(o,{color:"red",bold:!0,children:"\u2717 "}),e(o,{color:"red",children:t.content})]},r):t.role==="slash_result"?d(a,{flexDirection:"column",marginBottom:1,paddingLeft:2,children:[e(o,{color:"blue",dimColor:!0,children:t.command}),t.lines.map((l,x)=>e(o,{dimColor:!0,children:l},x))]},r):d(a,{flexDirection:"column",marginBottom:1,children:[e(o,{color:"green",bold:!0,children:"Assistant"}),e(a,{paddingLeft:2,flexDirection:"column",children:e(me,{children:t.content})})]},r)}),J!==""&&e(Ne,{text:J}),O!==null&&e(Oe,{toolName:O.name,input:O.input}),e(He,{todos:he}),B!==null&&e($e,{request:B.request,onApprove:()=>B.resolve({approved:!0,reason:"Approved from CLI."}),onDeny:()=>B.resolve({approved:!1,reason:"Denied from CLI."})}),_!==null&&e(Pe,{sessions:_.sessions,selectedIndex:_.selectedIndex,onSelect:t=>{ee(t)},onCancel:()=>P(null)}),!w&&B===null&&_===null&&e(We,{suggestions:Y,selectedIndex:F}),!w&&B===null&&_===null&&d(a,{gap:1,children:[e(o,{color:"cyan",bold:!0,children:"\u203A"}),e(Me,{value:k,onChange:ye,onSubmit:Ce,focus:B===null&&!w})]}),w&&B===null&&d(a,{gap:2,children:[e(U,{label:"Thinking\u2026"}),e(o,{dimColor:!0,children:"Esc to stop"})]})]})}async function fe(n){try{return JSON.parse(await _e(n,"utf8"))}catch{return}}async function fn({args:n,env:u,sessionsDirectory:m}){let s;try{let f=u.HOME??process.env.HOME,b={env:u};f!==void 0&&(b.userConfig=await fe(De(f,".vole","config.json"))),b.projectConfig=await fe("vole.config.json"),s=ie(b)}catch(f){process.stderr.write(`Configuration error: ${f instanceof Error?f.message:String(f)}
34
+ `),process.exitCode=1;return}let i=n.indexOf("--session"),c=i!==-1&&n[i+1]!==void 0?n[i+1]:void 0,g={env:u,...m!==void 0?{sessionsDirectory:m}:{}},{waitUntilExit:C}=Ie(e(qe,{config:s,cliOptions:g,...c!==void 0?{sessionId:c}:{}}));await C()}export{fn as runInkChat};