kimiflare 0.1.0

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/index.js ADDED
@@ -0,0 +1,2511 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/config.ts
12
+ import { readFile, mkdir, writeFile, chmod } from "fs/promises";
13
+ import { homedir } from "os";
14
+ import { join } from "path";
15
+ function configPath() {
16
+ const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
17
+ return join(xdg, "kimiflare", "config.json");
18
+ }
19
+ async function loadConfig() {
20
+ const envAccount = process.env.CLOUDFLARE_ACCOUNT_ID ?? process.env.CF_ACCOUNT_ID;
21
+ const envToken = process.env.CLOUDFLARE_API_TOKEN ?? process.env.CF_API_TOKEN;
22
+ const envModel = process.env.KIMI_MODEL ?? DEFAULT_MODEL;
23
+ if (envAccount && envToken) {
24
+ return { accountId: envAccount, apiToken: envToken, model: envModel };
25
+ }
26
+ try {
27
+ const raw = await readFile(configPath(), "utf8");
28
+ const parsed = JSON.parse(raw);
29
+ if (parsed.accountId && parsed.apiToken) {
30
+ return {
31
+ accountId: envAccount ?? parsed.accountId,
32
+ apiToken: envToken ?? parsed.apiToken,
33
+ model: envModel ?? parsed.model ?? DEFAULT_MODEL
34
+ };
35
+ }
36
+ } catch {
37
+ }
38
+ return null;
39
+ }
40
+ async function saveConfig(cfg) {
41
+ const p = configPath();
42
+ await mkdir(join(p, ".."), { recursive: true });
43
+ await writeFile(p, JSON.stringify(cfg, null, 2), "utf8");
44
+ await chmod(p, 384);
45
+ return p;
46
+ }
47
+ var DEFAULT_MODEL;
48
+ var init_config = __esm({
49
+ "src/config.ts"() {
50
+ "use strict";
51
+ DEFAULT_MODEL = "@cf/moonshotai/kimi-k2.6";
52
+ }
53
+ });
54
+
55
+ // src/util/sse.ts
56
+ async function* readSSE(stream, signal) {
57
+ const reader = stream.getReader();
58
+ const decoder = new TextDecoder("utf-8");
59
+ let buffer = "";
60
+ try {
61
+ while (true) {
62
+ if (signal?.aborted) throw new DOMException("aborted", "AbortError");
63
+ const { done, value } = await reader.read();
64
+ if (done) break;
65
+ buffer += decoder.decode(value, { stream: true });
66
+ buffer = buffer.replace(/\r\n/g, "\n");
67
+ let sep;
68
+ while ((sep = buffer.indexOf("\n\n")) !== -1) {
69
+ const event = buffer.slice(0, sep);
70
+ buffer = buffer.slice(sep + 2);
71
+ const data = extractData(event);
72
+ if (data !== null) yield data;
73
+ }
74
+ }
75
+ buffer += decoder.decode();
76
+ const tail = extractData(buffer.trim());
77
+ if (tail !== null) yield tail;
78
+ } finally {
79
+ reader.releaseLock();
80
+ }
81
+ }
82
+ function extractData(event) {
83
+ if (!event) return null;
84
+ const parts = [];
85
+ for (const raw of event.split("\n")) {
86
+ if (!raw.startsWith("data:")) continue;
87
+ parts.push(raw.slice(5).replace(/^ /, ""));
88
+ }
89
+ return parts.length ? parts.join("\n") : null;
90
+ }
91
+ var init_sse = __esm({
92
+ "src/util/sse.ts"() {
93
+ "use strict";
94
+ }
95
+ });
96
+
97
+ // src/util/errors.ts
98
+ var KimiApiError;
99
+ var init_errors = __esm({
100
+ "src/util/errors.ts"() {
101
+ "use strict";
102
+ KimiApiError = class extends Error {
103
+ constructor(message, code, httpStatus) {
104
+ super(message);
105
+ this.code = code;
106
+ this.httpStatus = httpStatus;
107
+ this.name = "KimiApiError";
108
+ }
109
+ code;
110
+ httpStatus;
111
+ };
112
+ }
113
+ });
114
+
115
+ // src/agent/client.ts
116
+ async function* runKimi(opts2) {
117
+ const url = `https://api.cloudflare.com/client/v4/accounts/${opts2.accountId}/ai/run/${opts2.model}`;
118
+ const body = {
119
+ messages: opts2.messages,
120
+ ...opts2.tools && opts2.tools.length ? { tools: opts2.tools, tool_choice: "auto", parallel_tool_calls: true } : {},
121
+ stream: true,
122
+ temperature: opts2.temperature ?? 0.2,
123
+ max_completion_tokens: opts2.maxCompletionTokens ?? 16384
124
+ };
125
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
126
+ const res = await fetch(url, {
127
+ method: "POST",
128
+ headers: {
129
+ Authorization: `Bearer ${opts2.apiToken}`,
130
+ "Content-Type": "application/json"
131
+ },
132
+ body: JSON.stringify(body),
133
+ signal: opts2.signal
134
+ });
135
+ const contentType = res.headers.get("content-type") ?? "";
136
+ if (!contentType.includes("text/event-stream")) {
137
+ const text = await res.text();
138
+ let parsed = null;
139
+ try {
140
+ parsed = JSON.parse(text);
141
+ } catch {
142
+ }
143
+ const err = extractCloudflareError(parsed);
144
+ if (err?.code === RETRYABLE_CODE && attempt < MAX_ATTEMPTS - 1) {
145
+ const delay = 500 * 2 ** attempt + Math.random() * 250;
146
+ await sleep(delay, opts2.signal);
147
+ continue;
148
+ }
149
+ const msg = err?.message ?? `HTTP ${res.status}: ${text.slice(0, 300)}`;
150
+ throw new KimiApiError(`kimiflare: ${msg}`, err?.code, res.status);
151
+ }
152
+ if (!res.body) throw new KimiApiError("kimiflare: empty response body", void 0, res.status);
153
+ yield* parseStream(res.body, opts2.signal);
154
+ return;
155
+ }
156
+ }
157
+ async function* parseStream(body, signal) {
158
+ const toolCalls = /* @__PURE__ */ new Map();
159
+ let lastUsage = null;
160
+ let finishReason = null;
161
+ for await (const dataStr of readSSE(body, signal)) {
162
+ if (dataStr === "[DONE]") break;
163
+ let chunk = null;
164
+ try {
165
+ chunk = JSON.parse(dataStr);
166
+ } catch {
167
+ continue;
168
+ }
169
+ if (!chunk) continue;
170
+ if (chunk.usage) {
171
+ lastUsage = chunk.usage;
172
+ yield { type: "usage", usage: chunk.usage };
173
+ }
174
+ const choice = chunk.choices?.[0];
175
+ if (!choice) continue;
176
+ const d = choice.delta;
177
+ if (d) {
178
+ if (typeof d.reasoning_content === "string" && d.reasoning_content.length) {
179
+ yield { type: "reasoning", delta: d.reasoning_content };
180
+ }
181
+ if (typeof d.content === "string" && d.content.length) {
182
+ yield { type: "text", delta: d.content };
183
+ }
184
+ if (Array.isArray(d.tool_calls)) {
185
+ for (const tc of d.tool_calls) {
186
+ const idx = typeof tc.index === "number" ? tc.index : 0;
187
+ let buf = toolCalls.get(idx);
188
+ const incomingName = tc.function?.name ?? null;
189
+ const incomingId = tc.id ?? null;
190
+ if (!buf) {
191
+ buf = { id: incomingId ?? `tc_${idx}`, name: incomingName ?? "", args: "" };
192
+ toolCalls.set(idx, buf);
193
+ if (buf.name) {
194
+ yield { type: "tool_call_start", index: idx, id: buf.id, name: buf.name };
195
+ }
196
+ } else {
197
+ if (!buf.name && incomingName) {
198
+ buf.name = incomingName;
199
+ yield { type: "tool_call_start", index: idx, id: buf.id, name: buf.name };
200
+ }
201
+ if (buf.id.startsWith("tc_") && incomingId) buf.id = incomingId;
202
+ }
203
+ const argDelta = tc.function?.arguments;
204
+ if (typeof argDelta === "string" && argDelta.length) {
205
+ buf.args += argDelta;
206
+ yield { type: "tool_call_args", index: idx, argsDelta: argDelta };
207
+ }
208
+ }
209
+ }
210
+ }
211
+ if (choice.finish_reason) finishReason = choice.finish_reason;
212
+ }
213
+ for (const [idx, buf] of [...toolCalls.entries()].sort((a, b) => a[0] - b[0])) {
214
+ if (!buf.name) continue;
215
+ yield {
216
+ type: "tool_call_complete",
217
+ index: idx,
218
+ id: buf.id,
219
+ name: buf.name,
220
+ arguments: buf.args
221
+ };
222
+ }
223
+ yield { type: "done", finishReason, usage: lastUsage };
224
+ }
225
+ function extractCloudflareError(parsed) {
226
+ if (!parsed || typeof parsed !== "object") return null;
227
+ const p = parsed;
228
+ if (p.success === false && Array.isArray(p.errors) && p.errors.length > 0) {
229
+ return { code: p.errors[0]?.code, message: p.errors[0]?.message };
230
+ }
231
+ return null;
232
+ }
233
+ function sleep(ms, signal) {
234
+ return new Promise((resolve2, reject) => {
235
+ if (signal?.aborted) return reject(new DOMException("aborted", "AbortError"));
236
+ const t = setTimeout(() => {
237
+ signal?.removeEventListener("abort", onAbort);
238
+ resolve2();
239
+ }, ms);
240
+ const onAbort = () => {
241
+ clearTimeout(t);
242
+ reject(new DOMException("aborted", "AbortError"));
243
+ };
244
+ signal?.addEventListener("abort", onAbort, { once: true });
245
+ });
246
+ }
247
+ var RETRYABLE_CODE, MAX_ATTEMPTS;
248
+ var init_client = __esm({
249
+ "src/agent/client.ts"() {
250
+ "use strict";
251
+ init_sse();
252
+ init_errors();
253
+ RETRYABLE_CODE = 3040;
254
+ MAX_ATTEMPTS = 5;
255
+ }
256
+ });
257
+
258
+ // src/tools/registry.ts
259
+ function toOpenAIToolDefs(tools) {
260
+ return tools.map((t) => ({
261
+ type: "function",
262
+ function: {
263
+ name: t.name,
264
+ description: t.description,
265
+ parameters: t.parameters
266
+ }
267
+ }));
268
+ }
269
+ var init_registry = __esm({
270
+ "src/tools/registry.ts"() {
271
+ "use strict";
272
+ }
273
+ });
274
+
275
+ // src/agent/loop.ts
276
+ async function runAgentTurn(opts2) {
277
+ const max = opts2.maxToolIterations ?? 50;
278
+ const toolDefs = toOpenAIToolDefs(opts2.tools);
279
+ for (let iter = 0; iter < max; iter++) {
280
+ const toolCalls = [];
281
+ let content = "";
282
+ let reasoning = "";
283
+ opts2.callbacks.onAssistantStart?.();
284
+ const events = runKimi({
285
+ accountId: opts2.accountId,
286
+ apiToken: opts2.apiToken,
287
+ model: opts2.model,
288
+ messages: opts2.messages,
289
+ tools: toolDefs,
290
+ signal: opts2.signal,
291
+ temperature: opts2.temperature,
292
+ maxCompletionTokens: opts2.maxCompletionTokens
293
+ });
294
+ for await (const ev of events) {
295
+ switch (ev.type) {
296
+ case "reasoning":
297
+ reasoning += ev.delta;
298
+ opts2.callbacks.onReasoningDelta?.(ev.delta);
299
+ break;
300
+ case "text":
301
+ content += ev.delta;
302
+ opts2.callbacks.onTextDelta?.(ev.delta);
303
+ break;
304
+ case "tool_call_start":
305
+ opts2.callbacks.onToolCallStart?.(ev.index, ev.id, ev.name);
306
+ break;
307
+ case "tool_call_args":
308
+ opts2.callbacks.onToolCallArgs?.(ev.index, ev.argsDelta);
309
+ break;
310
+ case "tool_call_complete": {
311
+ const call = {
312
+ id: ev.id,
313
+ type: "function",
314
+ function: { name: ev.name, arguments: ev.arguments }
315
+ };
316
+ toolCalls.push(call);
317
+ opts2.callbacks.onToolCallFinalized?.(call);
318
+ break;
319
+ }
320
+ case "usage":
321
+ opts2.callbacks.onUsage?.(ev.usage);
322
+ break;
323
+ case "done":
324
+ break;
325
+ }
326
+ }
327
+ const assistantMsg = {
328
+ role: "assistant",
329
+ content: content || null,
330
+ ...reasoning ? { reasoning_content: reasoning } : {},
331
+ ...toolCalls.length ? { tool_calls: toolCalls } : {}
332
+ };
333
+ opts2.messages.push(assistantMsg);
334
+ opts2.callbacks.onAssistantFinal?.(assistantMsg);
335
+ if (toolCalls.length === 0) return;
336
+ for (const tc of toolCalls) {
337
+ if (opts2.signal.aborted) throw new DOMException("aborted", "AbortError");
338
+ const result = await opts2.executor.run(
339
+ { id: tc.id, name: tc.function.name, arguments: tc.function.arguments },
340
+ opts2.callbacks.askPermission,
341
+ { cwd: opts2.cwd, signal: opts2.signal }
342
+ );
343
+ opts2.messages.push({
344
+ role: "tool",
345
+ tool_call_id: result.tool_call_id,
346
+ content: result.content,
347
+ name: result.name
348
+ });
349
+ opts2.callbacks.onToolResult?.(result);
350
+ }
351
+ }
352
+ throw new Error(`kimiflare: tool iteration limit reached (${opts2.maxToolIterations ?? 50})`);
353
+ }
354
+ var init_loop = __esm({
355
+ "src/agent/loop.ts"() {
356
+ "use strict";
357
+ init_client();
358
+ init_registry();
359
+ }
360
+ });
361
+
362
+ // src/agent/system-prompt.ts
363
+ import { platform, release, homedir as homedir2 } from "os";
364
+ import { basename } from "path";
365
+ function buildSystemPrompt(opts2) {
366
+ const now = opts2.now ?? /* @__PURE__ */ new Date();
367
+ const date = now.toISOString().slice(0, 10);
368
+ const shell = process.env.SHELL ? basename(process.env.SHELL) : "sh";
369
+ const toolsBlock = opts2.tools.map((t) => {
370
+ const perm = t.needsPermission ? " [needs user permission]" : "";
371
+ return `- \`${t.name}\`${perm}: ${t.description.split("\n")[0]}`;
372
+ }).join("\n");
373
+ return `You are kimiflare, an interactive coding assistant running in the user's terminal. You act on the user's local filesystem through the tools listed below. You are powered by the ${opts2.model} model on Cloudflare Workers AI.
374
+
375
+ Environment:
376
+ - Working directory: ${opts2.cwd}
377
+ - Platform: ${platform()} ${release()}
378
+ - Shell: ${shell}
379
+ - Home: ${homedir2()}
380
+ - Today: ${date}
381
+
382
+ Tools available:
383
+ ${toolsBlock}
384
+
385
+ How to work:
386
+ - Prefer calling tools over guessing. Read files before editing them. Use \`glob\` and \`grep\` to explore code before assuming structure.
387
+ - Before any mutating tool call (write, edit, bash), state in one short sentence what you're about to do, then call the tool. The user will be asked to approve each mutating call.
388
+ - When the user asks for a change, make the change. Do not paste code in chat that you could apply with \`edit\` or \`write\`.
389
+ - Keep responses terse. The user sees tool calls and their results inline \u2014 do not re-summarize them unless asked.
390
+ - If a tool returns an error, read it carefully and adjust; do not retry the same call blindly.
391
+ - You have a 262k-token context window. Read as much of a file as needed rather than guessing.
392
+ - If a request is ambiguous, ask one focused question instead of making large assumptions.
393
+ - When you finish a task, stop. Do not add a closing summary.`;
394
+ }
395
+ var init_system_prompt = __esm({
396
+ "src/agent/system-prompt.ts"() {
397
+ "use strict";
398
+ }
399
+ });
400
+
401
+ // src/util/paths.ts
402
+ import { resolve, isAbsolute } from "path";
403
+ import { homedir as homedir3 } from "os";
404
+ function resolvePath(cwd, input) {
405
+ if (input.startsWith("~/") || input === "~") {
406
+ return resolve(homedir3(), input === "~" ? "." : input.slice(2));
407
+ }
408
+ return isAbsolute(input) ? input : resolve(cwd, input);
409
+ }
410
+ function truncate(s, n) {
411
+ if (s.length <= n) return s;
412
+ return s.slice(0, n) + `
413
+ ... [truncated, ${s.length - n} chars omitted]`;
414
+ }
415
+ var init_paths = __esm({
416
+ "src/util/paths.ts"() {
417
+ "use strict";
418
+ }
419
+ });
420
+
421
+ // src/tools/read.ts
422
+ import { readFile as readFile2, stat } from "fs/promises";
423
+ var MAX_BYTES, readTool;
424
+ var init_read = __esm({
425
+ "src/tools/read.ts"() {
426
+ "use strict";
427
+ init_paths();
428
+ MAX_BYTES = 2 * 1024 * 1024;
429
+ readTool = {
430
+ name: "read",
431
+ description: "Read a text file from the local filesystem. Supports optional line offset/limit. Refuses files larger than 2MB. Returns contents with 1-indexed line numbers prefixed, cat -n style.",
432
+ parameters: {
433
+ type: "object",
434
+ properties: {
435
+ path: { type: "string", description: "Path to the file. Absolute or relative to cwd." },
436
+ offset: { type: "integer", description: "1-indexed line number to start reading from.", minimum: 1 },
437
+ limit: { type: "integer", description: "Maximum number of lines to return.", minimum: 1 }
438
+ },
439
+ required: ["path"],
440
+ additionalProperties: false
441
+ },
442
+ needsPermission: false,
443
+ render: ({ path }) => ({ title: `read ${path}` }),
444
+ async run(args, ctx) {
445
+ const abs = resolvePath(ctx.cwd, args.path);
446
+ const st = await stat(abs);
447
+ if (st.size > MAX_BYTES) throw new Error(`file too large: ${st.size} bytes (max ${MAX_BYTES})`);
448
+ const text = await readFile2(abs, "utf8");
449
+ const lines = text.split("\n");
450
+ const start = Math.max(0, (args.offset ?? 1) - 1);
451
+ const end = args.limit ? Math.min(lines.length, start + args.limit) : lines.length;
452
+ const width = String(end).length;
453
+ return lines.slice(start, end).map((l, i) => `${String(start + i + 1).padStart(width, " ")} ${l}`).join("\n");
454
+ }
455
+ };
456
+ }
457
+ });
458
+
459
+ // src/tools/write.ts
460
+ import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
461
+ import { dirname } from "path";
462
+ var writeTool;
463
+ var init_write = __esm({
464
+ "src/tools/write.ts"() {
465
+ "use strict";
466
+ init_paths();
467
+ writeTool = {
468
+ name: "write",
469
+ description: "Create a file or overwrite an existing one with the given contents. Prompts the user for permission first and shows a diff preview.",
470
+ parameters: {
471
+ type: "object",
472
+ properties: {
473
+ path: { type: "string" },
474
+ content: { type: "string" }
475
+ },
476
+ required: ["path", "content"],
477
+ additionalProperties: false
478
+ },
479
+ needsPermission: true,
480
+ render: (args) => ({
481
+ title: `write ${args.path} (${args.content.length} chars)`,
482
+ diff: { path: args.path, before: "", after: args.content }
483
+ }),
484
+ async run(args, ctx) {
485
+ const abs = resolvePath(ctx.cwd, args.path);
486
+ let before = "";
487
+ try {
488
+ before = await readFile3(abs, "utf8");
489
+ } catch {
490
+ }
491
+ await mkdir2(dirname(abs), { recursive: true });
492
+ await writeFile2(abs, args.content, "utf8");
493
+ const verb = before ? "Overwrote" : "Created";
494
+ return `${verb} ${args.path} (${args.content.length} chars).`;
495
+ }
496
+ };
497
+ }
498
+ });
499
+
500
+ // src/tools/edit.ts
501
+ import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
502
+ function countOccurrences(haystack, needle) {
503
+ if (!needle) return 0;
504
+ let count = 0;
505
+ let from = 0;
506
+ while (true) {
507
+ const i = haystack.indexOf(needle, from);
508
+ if (i === -1) return count;
509
+ count++;
510
+ from = i + needle.length;
511
+ }
512
+ }
513
+ var editTool;
514
+ var init_edit = __esm({
515
+ "src/tools/edit.ts"() {
516
+ "use strict";
517
+ init_paths();
518
+ editTool = {
519
+ name: "edit",
520
+ description: "Replace an exact string in a file. If replace_all is false (default), the old_string must appear exactly once or the call fails. Prompts the user for permission first and shows a diff preview.",
521
+ parameters: {
522
+ type: "object",
523
+ properties: {
524
+ path: { type: "string" },
525
+ old_string: { type: "string", description: "Exact text to replace." },
526
+ new_string: { type: "string", description: "Replacement text." },
527
+ replace_all: { type: "boolean", default: false }
528
+ },
529
+ required: ["path", "old_string", "new_string"],
530
+ additionalProperties: false
531
+ },
532
+ needsPermission: true,
533
+ render: (args) => ({
534
+ title: `edit ${args.path}${args.replace_all ? " (replace_all)" : ""}`,
535
+ diff: { path: args.path, before: args.old_string, after: args.new_string }
536
+ }),
537
+ async run(args, ctx) {
538
+ const abs = resolvePath(ctx.cwd, args.path);
539
+ const orig = await readFile4(abs, "utf8");
540
+ const occurrences = countOccurrences(orig, args.old_string);
541
+ if (occurrences === 0) throw new Error(`old_string not found in ${args.path}`);
542
+ if (occurrences > 1 && !args.replace_all) {
543
+ throw new Error(
544
+ `old_string appears ${occurrences} times in ${args.path}; pass replace_all=true or include more surrounding context`
545
+ );
546
+ }
547
+ const next = args.replace_all ? orig.split(args.old_string).join(args.new_string) : orig.replace(args.old_string, args.new_string);
548
+ await writeFile3(abs, next, "utf8");
549
+ return `Replaced ${occurrences} occurrence(s) in ${args.path}.`;
550
+ }
551
+ };
552
+ }
553
+ });
554
+
555
+ // src/tools/bash.ts
556
+ import { spawn } from "child_process";
557
+ var DEFAULT_TIMEOUT, MAX_TIMEOUT, OUTPUT_CAP, bashTool;
558
+ var init_bash = __esm({
559
+ "src/tools/bash.ts"() {
560
+ "use strict";
561
+ init_paths();
562
+ DEFAULT_TIMEOUT = 12e4;
563
+ MAX_TIMEOUT = 6e5;
564
+ OUTPUT_CAP = 3e4;
565
+ bashTool = {
566
+ name: "bash",
567
+ description: "Run a shell command via `bash -lc`. Prompts the user for permission before executing. stdout and stderr are captured, combined, and capped at 30KB.",
568
+ parameters: {
569
+ type: "object",
570
+ properties: {
571
+ command: { type: "string" },
572
+ timeout_ms: {
573
+ type: "integer",
574
+ description: "Milliseconds. Default 120000, max 600000.",
575
+ minimum: 1e3,
576
+ maximum: MAX_TIMEOUT
577
+ }
578
+ },
579
+ required: ["command"],
580
+ additionalProperties: false
581
+ },
582
+ needsPermission: true,
583
+ render: (args) => ({ title: `bash: ${args.command}`.slice(0, 120) }),
584
+ run(args, ctx) {
585
+ const timeout = Math.min(Math.max(1e3, args.timeout_ms ?? DEFAULT_TIMEOUT), MAX_TIMEOUT);
586
+ return new Promise((resolve2, reject) => {
587
+ const child = spawn("bash", ["-lc", args.command], {
588
+ cwd: ctx.cwd,
589
+ signal: ctx.signal
590
+ });
591
+ let stdout = "";
592
+ let stderr = "";
593
+ let killedByTimeout = false;
594
+ const timer = setTimeout(() => {
595
+ killedByTimeout = true;
596
+ child.kill("SIGKILL");
597
+ }, timeout);
598
+ child.stdout.on("data", (d) => {
599
+ stdout += d.toString("utf8");
600
+ });
601
+ child.stderr.on("data", (d) => {
602
+ stderr += d.toString("utf8");
603
+ });
604
+ child.on("error", (e) => {
605
+ clearTimeout(timer);
606
+ reject(e);
607
+ });
608
+ child.on("close", (code, signal) => {
609
+ clearTimeout(timer);
610
+ const header = killedByTimeout ? `(timed out after ${timeout}ms)` : `exit=${code ?? "?"}${signal ? ` signal=${signal}` : ""}`;
611
+ const parts = [header];
612
+ if (stdout) parts.push(`--- stdout ---
613
+ ${stdout.trimEnd()}`);
614
+ if (stderr) parts.push(`--- stderr ---
615
+ ${stderr.trimEnd()}`);
616
+ if (!stdout && !stderr) parts.push("(no output)");
617
+ resolve2(truncate(parts.join("\n"), OUTPUT_CAP));
618
+ });
619
+ });
620
+ }
621
+ };
622
+ }
623
+ });
624
+
625
+ // src/tools/glob.ts
626
+ import fg from "fast-glob";
627
+ var globTool;
628
+ var init_glob = __esm({
629
+ "src/tools/glob.ts"() {
630
+ "use strict";
631
+ init_paths();
632
+ globTool = {
633
+ name: "glob",
634
+ description: "Find files matching a glob pattern (e.g. `**/*.ts`). Returns up to 200 absolute paths, sorted by mtime descending.",
635
+ parameters: {
636
+ type: "object",
637
+ properties: {
638
+ pattern: { type: "string", description: "Glob pattern, e.g. `src/**/*.ts`." },
639
+ path: { type: "string", description: "Root directory. Defaults to cwd." }
640
+ },
641
+ required: ["pattern"],
642
+ additionalProperties: false
643
+ },
644
+ needsPermission: false,
645
+ render: (args) => ({ title: `glob ${args.pattern}${args.path ? ` in ${args.path}` : ""}` }),
646
+ async run(args, ctx) {
647
+ const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
648
+ const entries = await fg(args.pattern, {
649
+ cwd: root,
650
+ absolute: true,
651
+ dot: false,
652
+ onlyFiles: false,
653
+ stats: true
654
+ });
655
+ entries.sort((a, b) => (b.stats?.mtimeMs ?? 0) - (a.stats?.mtimeMs ?? 0));
656
+ const paths = entries.slice(0, 200).map((e) => e.path);
657
+ return paths.length ? paths.join("\n") : "(no matches)";
658
+ }
659
+ };
660
+ }
661
+ });
662
+
663
+ // src/tools/grep.ts
664
+ import { execFile } from "child_process";
665
+ import { promisify } from "util";
666
+ import { readFile as readFile5 } from "fs/promises";
667
+ import fg2 from "fast-glob";
668
+ async function hasRipgrep() {
669
+ if (cachedHasRg !== null) return cachedHasRg;
670
+ try {
671
+ await pExecFile("rg", ["--version"]);
672
+ cachedHasRg = true;
673
+ } catch {
674
+ cachedHasRg = false;
675
+ }
676
+ return cachedHasRg;
677
+ }
678
+ async function runRipgrep(args, root, mode) {
679
+ const rgArgs = ["--no-heading", "--color=never", "--line-number"];
680
+ if (args.case_insensitive) rgArgs.push("-i");
681
+ if (args.glob) rgArgs.push("--glob", args.glob);
682
+ if (mode === "files") rgArgs.push("-l");
683
+ rgArgs.push("--", args.pattern, root);
684
+ try {
685
+ const { stdout } = await pExecFile("rg", rgArgs, { maxBuffer: 10 * 1024 * 1024 });
686
+ const trimmed = stdout.trim();
687
+ return trimmed ? truncate(trimmed, 3e4) : "(no matches)";
688
+ } catch (e) {
689
+ const err = e;
690
+ if (err.code === 1) return "(no matches)";
691
+ throw new Error(err.stderr || String(e));
692
+ }
693
+ }
694
+ async function runJsFallback(args, root, mode) {
695
+ const re = new RegExp(args.pattern, args.case_insensitive ? "i" : "");
696
+ const globPattern = args.glob ? `**/${args.glob}` : "**/*";
697
+ const files = await fg2(globPattern, {
698
+ cwd: root,
699
+ absolute: true,
700
+ dot: false,
701
+ onlyFiles: true,
702
+ ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"]
703
+ });
704
+ const out = [];
705
+ for (const file of files.slice(0, 5e3)) {
706
+ try {
707
+ const content = await readFile5(file, "utf8");
708
+ if (mode === "files") {
709
+ if (re.test(content)) out.push(file);
710
+ } else {
711
+ const lines = content.split("\n");
712
+ for (let i = 0; i < lines.length; i++) {
713
+ if (re.test(lines[i])) {
714
+ out.push(`${file}:${i + 1}:${lines[i]}`);
715
+ if (out.length > 500) break;
716
+ }
717
+ }
718
+ }
719
+ } catch {
720
+ }
721
+ if (out.length > 500) break;
722
+ }
723
+ return out.length ? truncate(out.join("\n"), 3e4) : "(no matches)";
724
+ }
725
+ var pExecFile, cachedHasRg, grepTool;
726
+ var init_grep = __esm({
727
+ "src/tools/grep.ts"() {
728
+ "use strict";
729
+ init_paths();
730
+ pExecFile = promisify(execFile);
731
+ cachedHasRg = null;
732
+ grepTool = {
733
+ name: "grep",
734
+ description: "Search file contents for a regular expression. Shells out to ripgrep if available, otherwise uses a JavaScript fallback. Output is capped at 30KB.",
735
+ parameters: {
736
+ type: "object",
737
+ properties: {
738
+ pattern: { type: "string", description: "Regex pattern." },
739
+ path: { type: "string", description: "Root directory. Defaults to cwd." },
740
+ glob: { type: "string", description: "Filter files by glob, e.g. `*.ts`." },
741
+ case_insensitive: { type: "boolean" },
742
+ output_mode: {
743
+ type: "string",
744
+ enum: ["content", "files"],
745
+ description: "`content` returns matching lines; `files` returns matching file paths only."
746
+ }
747
+ },
748
+ required: ["pattern"],
749
+ additionalProperties: false
750
+ },
751
+ needsPermission: false,
752
+ render: (args) => ({ title: `grep ${args.pattern}${args.glob ? ` (${args.glob})` : ""}` }),
753
+ async run(args, ctx) {
754
+ const root = args.path ? resolvePath(ctx.cwd, args.path) : ctx.cwd;
755
+ const mode = args.output_mode ?? "content";
756
+ if (await hasRipgrep()) return runRipgrep(args, root, mode);
757
+ return runJsFallback(args, root, mode);
758
+ }
759
+ };
760
+ }
761
+ });
762
+
763
+ // src/tools/web-fetch.ts
764
+ import TurndownService from "turndown";
765
+ var MAX_BYTES2, MAX_OUTPUT, TIMEOUT_MS, webFetchTool;
766
+ var init_web_fetch = __esm({
767
+ "src/tools/web-fetch.ts"() {
768
+ "use strict";
769
+ init_paths();
770
+ MAX_BYTES2 = 1 * 1024 * 1024;
771
+ MAX_OUTPUT = 1e5;
772
+ TIMEOUT_MS = 2e4;
773
+ webFetchTool = {
774
+ name: "web_fetch",
775
+ description: "Fetch a URL over HTTPS and return its content. HTML pages are converted to markdown. Output is capped at ~100KB.",
776
+ parameters: {
777
+ type: "object",
778
+ properties: {
779
+ url: { type: "string", description: "Full URL, http(s)." }
780
+ },
781
+ required: ["url"],
782
+ additionalProperties: false
783
+ },
784
+ needsPermission: false,
785
+ render: (args) => ({ title: `GET ${args.url}` }),
786
+ async run(args) {
787
+ const controller = new AbortController();
788
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
789
+ try {
790
+ const res = await fetch(args.url, {
791
+ redirect: "follow",
792
+ signal: controller.signal,
793
+ headers: { "user-agent": "kimiflare/0.1 (+https://github.com/sinameraji/kimiflare)" }
794
+ });
795
+ const ct = res.headers.get("content-type") ?? "";
796
+ const body = await res.text();
797
+ const bounded = body.length > MAX_BYTES2 ? body.slice(0, MAX_BYTES2) : body;
798
+ if (ct.toLowerCase().includes("html")) {
799
+ const td = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced" });
800
+ return truncate(`# ${args.url}
801
+
802
+ ${td.turndown(bounded)}`, MAX_OUTPUT);
803
+ }
804
+ return truncate(`# ${args.url}
805
+
806
+ ${bounded}`, MAX_OUTPUT);
807
+ } finally {
808
+ clearTimeout(timer);
809
+ }
810
+ }
811
+ };
812
+ }
813
+ });
814
+
815
+ // src/tools/executor.ts
816
+ function truncateForError(s) {
817
+ return s.length <= 200 ? s : `${s.slice(0, 200)}... [${s.length - 200} more chars]`;
818
+ }
819
+ var ALL_TOOLS, ToolExecutor;
820
+ var init_executor = __esm({
821
+ "src/tools/executor.ts"() {
822
+ "use strict";
823
+ init_read();
824
+ init_write();
825
+ init_edit();
826
+ init_bash();
827
+ init_glob();
828
+ init_grep();
829
+ init_web_fetch();
830
+ ALL_TOOLS = [
831
+ readTool,
832
+ writeTool,
833
+ editTool,
834
+ bashTool,
835
+ globTool,
836
+ grepTool,
837
+ webFetchTool
838
+ ];
839
+ ToolExecutor = class {
840
+ sessionAllowed = /* @__PURE__ */ new Set();
841
+ tools;
842
+ constructor(tools = ALL_TOOLS) {
843
+ this.tools = new Map(tools.map((t) => [t.name, t]));
844
+ }
845
+ list() {
846
+ return [...this.tools.values()];
847
+ }
848
+ async run(call, askPermission, ctx) {
849
+ const tool = this.tools.get(call.name);
850
+ if (!tool) {
851
+ return {
852
+ tool_call_id: call.id,
853
+ name: call.name,
854
+ content: `Error: unknown tool "${call.name}". Valid tools: ${[...this.tools.keys()].join(", ")}.`,
855
+ ok: false
856
+ };
857
+ }
858
+ let args;
859
+ try {
860
+ args = call.arguments.trim() ? JSON.parse(call.arguments) : {};
861
+ } catch (e) {
862
+ return {
863
+ tool_call_id: call.id,
864
+ name: call.name,
865
+ content: `Error: invalid JSON arguments for ${call.name}: ${e.message}. Arguments received: ${truncateForError(call.arguments)}`,
866
+ ok: false
867
+ };
868
+ }
869
+ if (tool.needsPermission) {
870
+ const sessionKey = this.permissionKey(tool, args);
871
+ if (!this.sessionAllowed.has(sessionKey)) {
872
+ const decision = await askPermission({ tool, args, sessionKey });
873
+ if (decision === "deny") {
874
+ return {
875
+ tool_call_id: call.id,
876
+ name: call.name,
877
+ content: `Permission denied by user. Do not retry this exact call; ask the user what they want to do differently.`,
878
+ ok: false
879
+ };
880
+ }
881
+ if (decision === "allow_session") this.sessionAllowed.add(sessionKey);
882
+ }
883
+ }
884
+ try {
885
+ const content = await tool.run(args, ctx);
886
+ return { tool_call_id: call.id, name: call.name, content, ok: true };
887
+ } catch (e) {
888
+ return {
889
+ tool_call_id: call.id,
890
+ name: call.name,
891
+ content: `Error running ${call.name}: ${e.message ?? String(e)}`,
892
+ ok: false
893
+ };
894
+ }
895
+ }
896
+ permissionKey(tool, args) {
897
+ if (tool.name === "bash" && typeof args.command === "string") {
898
+ const firstToken = args.command.trim().split(/\s+/)[0] ?? "";
899
+ return `bash:${firstToken}`;
900
+ }
901
+ return tool.name;
902
+ }
903
+ };
904
+ }
905
+ });
906
+
907
+ // src/ui/diff-view.tsx
908
+ import { Box, Text } from "ink";
909
+ import { createTwoFilesPatch } from "diff";
910
+ import { jsx, jsxs } from "react/jsx-runtime";
911
+ function DiffView({ path, before, after, maxLines = 40 }) {
912
+ const patch = createTwoFilesPatch(path, path, before, after, "", "", { context: 2 });
913
+ const lines = patch.split("\n").slice(4);
914
+ const truncated = lines.length > maxLines ? lines.slice(0, maxLines) : lines;
915
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
916
+ truncated.map((line, i) => /* @__PURE__ */ jsx(DiffLine, { line }, i)),
917
+ lines.length > maxLines && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
918
+ "... (",
919
+ lines.length - maxLines,
920
+ " more lines)"
921
+ ] })
922
+ ] });
923
+ }
924
+ function DiffLine({ line }) {
925
+ if (line.startsWith("+") && !line.startsWith("+++")) {
926
+ return /* @__PURE__ */ jsx(Text, { color: "green", children: line });
927
+ }
928
+ if (line.startsWith("-") && !line.startsWith("---")) {
929
+ return /* @__PURE__ */ jsx(Text, { color: "red", children: line });
930
+ }
931
+ if (line.startsWith("@@")) {
932
+ return /* @__PURE__ */ jsx(Text, { color: "cyan", children: line });
933
+ }
934
+ return /* @__PURE__ */ jsx(Text, { color: "gray", children: line });
935
+ }
936
+ var init_diff_view = __esm({
937
+ "src/ui/diff-view.tsx"() {
938
+ "use strict";
939
+ }
940
+ });
941
+
942
+ // src/ui/tool-view.tsx
943
+ import { Box as Box2, Text as Text2 } from "ink";
944
+ import Spinner from "ink-spinner";
945
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
946
+ function ToolView({ evt }) {
947
+ const statusIcon = evt.status === "running" ? /* @__PURE__ */ jsx2(Spinner, { type: "dots" }) : evt.status === "error" ? /* @__PURE__ */ jsx2(Text2, { color: "red", children: "\u2717" }) : /* @__PURE__ */ jsx2(Text2, { color: "green", children: "\u2713" });
948
+ const title = evt.render?.title ?? `${evt.name}(${compactArgs(evt.args)})`;
949
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginLeft: 2, children: [
950
+ /* @__PURE__ */ jsxs2(Text2, { children: [
951
+ statusIcon,
952
+ " ",
953
+ /* @__PURE__ */ jsx2(Text2, { color: "magenta", children: title })
954
+ ] }),
955
+ evt.render?.diff ? /* @__PURE__ */ jsx2(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx2(DiffView, { ...evt.render.diff }) }) : null,
956
+ evt.result && evt.expanded ? /* @__PURE__ */ jsxs2(Box2, { marginLeft: 2, flexDirection: "column", children: [
957
+ evt.result.split("\n").slice(0, 20).map((l, i) => /* @__PURE__ */ jsx2(Text2, { color: "gray", children: l }, i)),
958
+ evt.result.split("\n").length > 20 && /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
959
+ "... (",
960
+ evt.result.split("\n").length - 20,
961
+ " more lines)"
962
+ ] })
963
+ ] }) : null,
964
+ evt.result && !evt.expanded && evt.status !== "running" ? /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
965
+ " ",
966
+ firstLine(evt.result)
967
+ ] }) : null
968
+ ] });
969
+ }
970
+ function compactArgs(raw) {
971
+ const s = raw.replace(/\s+/g, " ");
972
+ return s.length <= 80 ? s : s.slice(0, 80) + "\u2026";
973
+ }
974
+ function firstLine(s) {
975
+ const line = s.split("\n").find((l) => l.trim().length > 0) ?? "";
976
+ return line.length <= 120 ? line : line.slice(0, 120) + "\u2026";
977
+ }
978
+ var init_tool_view = __esm({
979
+ "src/ui/tool-view.tsx"() {
980
+ "use strict";
981
+ init_diff_view();
982
+ }
983
+ });
984
+
985
+ // src/ui/chat.tsx
986
+ import { Box as Box3, Text as Text3 } from "ink";
987
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
988
+ function ChatView({ events, showReasoning }) {
989
+ return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: events.map((e) => /* @__PURE__ */ jsx3(EventView, { evt: e, showReasoning }, e.key)) });
990
+ }
991
+ function EventView({ evt, showReasoning }) {
992
+ if (evt.kind === "user") {
993
+ return /* @__PURE__ */ jsxs3(Box3, { marginY: 0, children: [
994
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "\u203A " }),
995
+ /* @__PURE__ */ jsx3(Text3, { children: evt.text })
996
+ ] });
997
+ }
998
+ if (evt.kind === "assistant") {
999
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginY: 0, children: [
1000
+ showReasoning && evt.reasoning ? /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color: "gray", dimColor: true, children: [
1001
+ "\u2727 thinking: ",
1002
+ evt.reasoning.length > 400 ? evt.reasoning.slice(0, 400) + "\u2026" : evt.reasoning
1003
+ ] }) }) : null,
1004
+ evt.text ? /* @__PURE__ */ jsx3(Text3, { children: evt.text }) : null
1005
+ ] });
1006
+ }
1007
+ if (evt.kind === "tool") {
1008
+ return /* @__PURE__ */ jsx3(ToolView, { evt });
1009
+ }
1010
+ if (evt.kind === "info") {
1011
+ return /* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: evt.text });
1012
+ }
1013
+ return /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
1014
+ "! ",
1015
+ evt.text
1016
+ ] });
1017
+ }
1018
+ var init_chat = __esm({
1019
+ "src/ui/chat.tsx"() {
1020
+ "use strict";
1021
+ init_tool_view();
1022
+ }
1023
+ });
1024
+
1025
+ // src/ui/status.tsx
1026
+ import { Box as Box4, Text as Text4 } from "ink";
1027
+ import { jsx as jsx4 } from "react/jsx-runtime";
1028
+ function StatusBar({ model, usage, thinking, hint }) {
1029
+ const parts = [`model: ${shortModel(model)}`];
1030
+ if (usage) {
1031
+ const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
1032
+ const uncachedIn = usage.prompt_tokens - cached;
1033
+ const cost = uncachedIn * PRICE_IN_PER_M / 1e6 + cached * PRICE_IN_CACHED_PER_M / 1e6 + usage.completion_tokens * PRICE_OUT_PER_M / 1e6;
1034
+ parts.push(
1035
+ `in: ${usage.prompt_tokens}${cached ? ` (${cached} cached)` : ""}`,
1036
+ `out: ${usage.completion_tokens}`,
1037
+ `$${cost.toFixed(5)}`
1038
+ );
1039
+ }
1040
+ if (thinking) parts.push("thinking\u2026");
1041
+ if (hint) parts.push(hint);
1042
+ return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: parts.join(" \xB7 ") }) });
1043
+ }
1044
+ function shortModel(m) {
1045
+ const last = m.split("/").at(-1) ?? m;
1046
+ return last;
1047
+ }
1048
+ var PRICE_IN_PER_M, PRICE_IN_CACHED_PER_M, PRICE_OUT_PER_M;
1049
+ var init_status = __esm({
1050
+ "src/ui/status.tsx"() {
1051
+ "use strict";
1052
+ PRICE_IN_PER_M = 0.95;
1053
+ PRICE_IN_CACHED_PER_M = 0.16;
1054
+ PRICE_OUT_PER_M = 4;
1055
+ }
1056
+ });
1057
+
1058
+ // src/ui/permission.tsx
1059
+ import { Box as Box5, Text as Text5 } from "ink";
1060
+ import SelectInput from "ink-select-input";
1061
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1062
+ function PermissionModal({ tool, args, onDecide }) {
1063
+ const render2 = tool.render?.(args);
1064
+ const items = [
1065
+ { label: "Allow once", value: "allow" },
1066
+ { label: "Allow for this session", value: "allow_session" },
1067
+ { label: "Deny", value: "deny" }
1068
+ ];
1069
+ return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1070
+ /* @__PURE__ */ jsx5(Text5, { color: "yellow", bold: true, children: "Permission requested" }),
1071
+ /* @__PURE__ */ jsxs4(Text5, { children: [
1072
+ "tool: ",
1073
+ /* @__PURE__ */ jsx5(Text5, { color: "magenta", children: tool.name })
1074
+ ] }),
1075
+ render2?.title ? /* @__PURE__ */ jsxs4(Text5, { children: [
1076
+ "action: ",
1077
+ render2.title
1078
+ ] }) : null,
1079
+ render2?.diff ? /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx5(DiffView, { ...render2.diff }) }) : /* @__PURE__ */ jsxs4(Text5, { color: "gray", children: [
1080
+ "args: ",
1081
+ JSON.stringify(args)
1082
+ ] }),
1083
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(SelectInput, { items, onSelect: (item) => onDecide(item.value) }) })
1084
+ ] });
1085
+ }
1086
+ var init_permission = __esm({
1087
+ "src/ui/permission.tsx"() {
1088
+ "use strict";
1089
+ init_diff_view();
1090
+ }
1091
+ });
1092
+
1093
+ // node_modules/chalk/source/vendor/ansi-styles/index.js
1094
+ function assembleStyles() {
1095
+ const codes = /* @__PURE__ */ new Map();
1096
+ for (const [groupName, group] of Object.entries(styles)) {
1097
+ for (const [styleName, style] of Object.entries(group)) {
1098
+ styles[styleName] = {
1099
+ open: `\x1B[${style[0]}m`,
1100
+ close: `\x1B[${style[1]}m`
1101
+ };
1102
+ group[styleName] = styles[styleName];
1103
+ codes.set(style[0], style[1]);
1104
+ }
1105
+ Object.defineProperty(styles, groupName, {
1106
+ value: group,
1107
+ enumerable: false
1108
+ });
1109
+ }
1110
+ Object.defineProperty(styles, "codes", {
1111
+ value: codes,
1112
+ enumerable: false
1113
+ });
1114
+ styles.color.close = "\x1B[39m";
1115
+ styles.bgColor.close = "\x1B[49m";
1116
+ styles.color.ansi = wrapAnsi16();
1117
+ styles.color.ansi256 = wrapAnsi256();
1118
+ styles.color.ansi16m = wrapAnsi16m();
1119
+ styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
1120
+ styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
1121
+ styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
1122
+ Object.defineProperties(styles, {
1123
+ rgbToAnsi256: {
1124
+ value(red, green, blue) {
1125
+ if (red === green && green === blue) {
1126
+ if (red < 8) {
1127
+ return 16;
1128
+ }
1129
+ if (red > 248) {
1130
+ return 231;
1131
+ }
1132
+ return Math.round((red - 8) / 247 * 24) + 232;
1133
+ }
1134
+ return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
1135
+ },
1136
+ enumerable: false
1137
+ },
1138
+ hexToRgb: {
1139
+ value(hex) {
1140
+ const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
1141
+ if (!matches) {
1142
+ return [0, 0, 0];
1143
+ }
1144
+ let [colorString] = matches;
1145
+ if (colorString.length === 3) {
1146
+ colorString = [...colorString].map((character) => character + character).join("");
1147
+ }
1148
+ const integer = Number.parseInt(colorString, 16);
1149
+ return [
1150
+ /* eslint-disable no-bitwise */
1151
+ integer >> 16 & 255,
1152
+ integer >> 8 & 255,
1153
+ integer & 255
1154
+ /* eslint-enable no-bitwise */
1155
+ ];
1156
+ },
1157
+ enumerable: false
1158
+ },
1159
+ hexToAnsi256: {
1160
+ value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
1161
+ enumerable: false
1162
+ },
1163
+ ansi256ToAnsi: {
1164
+ value(code) {
1165
+ if (code < 8) {
1166
+ return 30 + code;
1167
+ }
1168
+ if (code < 16) {
1169
+ return 90 + (code - 8);
1170
+ }
1171
+ let red;
1172
+ let green;
1173
+ let blue;
1174
+ if (code >= 232) {
1175
+ red = ((code - 232) * 10 + 8) / 255;
1176
+ green = red;
1177
+ blue = red;
1178
+ } else {
1179
+ code -= 16;
1180
+ const remainder = code % 36;
1181
+ red = Math.floor(code / 36) / 5;
1182
+ green = Math.floor(remainder / 6) / 5;
1183
+ blue = remainder % 6 / 5;
1184
+ }
1185
+ const value = Math.max(red, green, blue) * 2;
1186
+ if (value === 0) {
1187
+ return 30;
1188
+ }
1189
+ let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
1190
+ if (value === 2) {
1191
+ result += 60;
1192
+ }
1193
+ return result;
1194
+ },
1195
+ enumerable: false
1196
+ },
1197
+ rgbToAnsi: {
1198
+ value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
1199
+ enumerable: false
1200
+ },
1201
+ hexToAnsi: {
1202
+ value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
1203
+ enumerable: false
1204
+ }
1205
+ });
1206
+ return styles;
1207
+ }
1208
+ var ANSI_BACKGROUND_OFFSET, wrapAnsi16, wrapAnsi256, wrapAnsi16m, styles, modifierNames, foregroundColorNames, backgroundColorNames, colorNames, ansiStyles, ansi_styles_default;
1209
+ var init_ansi_styles = __esm({
1210
+ "node_modules/chalk/source/vendor/ansi-styles/index.js"() {
1211
+ "use strict";
1212
+ ANSI_BACKGROUND_OFFSET = 10;
1213
+ wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
1214
+ wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
1215
+ wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
1216
+ styles = {
1217
+ modifier: {
1218
+ reset: [0, 0],
1219
+ // 21 isn't widely supported and 22 does the same thing
1220
+ bold: [1, 22],
1221
+ dim: [2, 22],
1222
+ italic: [3, 23],
1223
+ underline: [4, 24],
1224
+ overline: [53, 55],
1225
+ inverse: [7, 27],
1226
+ hidden: [8, 28],
1227
+ strikethrough: [9, 29]
1228
+ },
1229
+ color: {
1230
+ black: [30, 39],
1231
+ red: [31, 39],
1232
+ green: [32, 39],
1233
+ yellow: [33, 39],
1234
+ blue: [34, 39],
1235
+ magenta: [35, 39],
1236
+ cyan: [36, 39],
1237
+ white: [37, 39],
1238
+ // Bright color
1239
+ blackBright: [90, 39],
1240
+ gray: [90, 39],
1241
+ // Alias of `blackBright`
1242
+ grey: [90, 39],
1243
+ // Alias of `blackBright`
1244
+ redBright: [91, 39],
1245
+ greenBright: [92, 39],
1246
+ yellowBright: [93, 39],
1247
+ blueBright: [94, 39],
1248
+ magentaBright: [95, 39],
1249
+ cyanBright: [96, 39],
1250
+ whiteBright: [97, 39]
1251
+ },
1252
+ bgColor: {
1253
+ bgBlack: [40, 49],
1254
+ bgRed: [41, 49],
1255
+ bgGreen: [42, 49],
1256
+ bgYellow: [43, 49],
1257
+ bgBlue: [44, 49],
1258
+ bgMagenta: [45, 49],
1259
+ bgCyan: [46, 49],
1260
+ bgWhite: [47, 49],
1261
+ // Bright color
1262
+ bgBlackBright: [100, 49],
1263
+ bgGray: [100, 49],
1264
+ // Alias of `bgBlackBright`
1265
+ bgGrey: [100, 49],
1266
+ // Alias of `bgBlackBright`
1267
+ bgRedBright: [101, 49],
1268
+ bgGreenBright: [102, 49],
1269
+ bgYellowBright: [103, 49],
1270
+ bgBlueBright: [104, 49],
1271
+ bgMagentaBright: [105, 49],
1272
+ bgCyanBright: [106, 49],
1273
+ bgWhiteBright: [107, 49]
1274
+ }
1275
+ };
1276
+ modifierNames = Object.keys(styles.modifier);
1277
+ foregroundColorNames = Object.keys(styles.color);
1278
+ backgroundColorNames = Object.keys(styles.bgColor);
1279
+ colorNames = [...foregroundColorNames, ...backgroundColorNames];
1280
+ ansiStyles = assembleStyles();
1281
+ ansi_styles_default = ansiStyles;
1282
+ }
1283
+ });
1284
+
1285
+ // node_modules/chalk/source/vendor/supports-color/index.js
1286
+ import process2 from "process";
1287
+ import os from "os";
1288
+ import tty from "tty";
1289
+ function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process2.argv) {
1290
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
1291
+ const position = argv.indexOf(prefix + flag);
1292
+ const terminatorPosition = argv.indexOf("--");
1293
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
1294
+ }
1295
+ function envForceColor() {
1296
+ if ("FORCE_COLOR" in env) {
1297
+ if (env.FORCE_COLOR === "true") {
1298
+ return 1;
1299
+ }
1300
+ if (env.FORCE_COLOR === "false") {
1301
+ return 0;
1302
+ }
1303
+ return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
1304
+ }
1305
+ }
1306
+ function translateLevel(level) {
1307
+ if (level === 0) {
1308
+ return false;
1309
+ }
1310
+ return {
1311
+ level,
1312
+ hasBasic: true,
1313
+ has256: level >= 2,
1314
+ has16m: level >= 3
1315
+ };
1316
+ }
1317
+ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
1318
+ const noFlagForceColor = envForceColor();
1319
+ if (noFlagForceColor !== void 0) {
1320
+ flagForceColor = noFlagForceColor;
1321
+ }
1322
+ const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
1323
+ if (forceColor === 0) {
1324
+ return 0;
1325
+ }
1326
+ if (sniffFlags) {
1327
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
1328
+ return 3;
1329
+ }
1330
+ if (hasFlag("color=256")) {
1331
+ return 2;
1332
+ }
1333
+ }
1334
+ if ("TF_BUILD" in env && "AGENT_NAME" in env) {
1335
+ return 1;
1336
+ }
1337
+ if (haveStream && !streamIsTTY && forceColor === void 0) {
1338
+ return 0;
1339
+ }
1340
+ const min = forceColor || 0;
1341
+ if (env.TERM === "dumb") {
1342
+ return min;
1343
+ }
1344
+ if (process2.platform === "win32") {
1345
+ const osRelease = os.release().split(".");
1346
+ if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
1347
+ return Number(osRelease[2]) >= 14931 ? 3 : 2;
1348
+ }
1349
+ return 1;
1350
+ }
1351
+ if ("CI" in env) {
1352
+ if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
1353
+ return 3;
1354
+ }
1355
+ if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
1356
+ return 1;
1357
+ }
1358
+ return min;
1359
+ }
1360
+ if ("TEAMCITY_VERSION" in env) {
1361
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
1362
+ }
1363
+ if (env.COLORTERM === "truecolor") {
1364
+ return 3;
1365
+ }
1366
+ if (env.TERM === "xterm-kitty") {
1367
+ return 3;
1368
+ }
1369
+ if (env.TERM === "xterm-ghostty") {
1370
+ return 3;
1371
+ }
1372
+ if (env.TERM === "wezterm") {
1373
+ return 3;
1374
+ }
1375
+ if ("TERM_PROGRAM" in env) {
1376
+ const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
1377
+ switch (env.TERM_PROGRAM) {
1378
+ case "iTerm.app": {
1379
+ return version >= 3 ? 3 : 2;
1380
+ }
1381
+ case "Apple_Terminal": {
1382
+ return 2;
1383
+ }
1384
+ }
1385
+ }
1386
+ if (/-256(color)?$/i.test(env.TERM)) {
1387
+ return 2;
1388
+ }
1389
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
1390
+ return 1;
1391
+ }
1392
+ if ("COLORTERM" in env) {
1393
+ return 1;
1394
+ }
1395
+ return min;
1396
+ }
1397
+ function createSupportsColor(stream, options = {}) {
1398
+ const level = _supportsColor(stream, {
1399
+ streamIsTTY: stream && stream.isTTY,
1400
+ ...options
1401
+ });
1402
+ return translateLevel(level);
1403
+ }
1404
+ var env, flagForceColor, supportsColor, supports_color_default;
1405
+ var init_supports_color = __esm({
1406
+ "node_modules/chalk/source/vendor/supports-color/index.js"() {
1407
+ "use strict";
1408
+ ({ env } = process2);
1409
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
1410
+ flagForceColor = 0;
1411
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
1412
+ flagForceColor = 1;
1413
+ }
1414
+ supportsColor = {
1415
+ stdout: createSupportsColor({ isTTY: tty.isatty(1) }),
1416
+ stderr: createSupportsColor({ isTTY: tty.isatty(2) })
1417
+ };
1418
+ supports_color_default = supportsColor;
1419
+ }
1420
+ });
1421
+
1422
+ // node_modules/chalk/source/utilities.js
1423
+ function stringReplaceAll(string, substring, replacer) {
1424
+ let index = string.indexOf(substring);
1425
+ if (index === -1) {
1426
+ return string;
1427
+ }
1428
+ const substringLength = substring.length;
1429
+ let endIndex = 0;
1430
+ let returnValue = "";
1431
+ do {
1432
+ returnValue += string.slice(endIndex, index) + substring + replacer;
1433
+ endIndex = index + substringLength;
1434
+ index = string.indexOf(substring, endIndex);
1435
+ } while (index !== -1);
1436
+ returnValue += string.slice(endIndex);
1437
+ return returnValue;
1438
+ }
1439
+ function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
1440
+ let endIndex = 0;
1441
+ let returnValue = "";
1442
+ do {
1443
+ const gotCR = string[index - 1] === "\r";
1444
+ returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
1445
+ endIndex = index + 1;
1446
+ index = string.indexOf("\n", endIndex);
1447
+ } while (index !== -1);
1448
+ returnValue += string.slice(endIndex);
1449
+ return returnValue;
1450
+ }
1451
+ var init_utilities = __esm({
1452
+ "node_modules/chalk/source/utilities.js"() {
1453
+ "use strict";
1454
+ }
1455
+ });
1456
+
1457
+ // node_modules/chalk/source/index.js
1458
+ function createChalk(options) {
1459
+ return chalkFactory(options);
1460
+ }
1461
+ var stdoutColor, stderrColor, GENERATOR, STYLER, IS_EMPTY, levelMapping, styles2, applyOptions, chalkFactory, getModelAnsi, usedModels, proto, createStyler, createBuilder, applyStyle, chalk, chalkStderr, source_default;
1462
+ var init_source = __esm({
1463
+ "node_modules/chalk/source/index.js"() {
1464
+ "use strict";
1465
+ init_ansi_styles();
1466
+ init_supports_color();
1467
+ init_utilities();
1468
+ ({ stdout: stdoutColor, stderr: stderrColor } = supports_color_default);
1469
+ GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
1470
+ STYLER = /* @__PURE__ */ Symbol("STYLER");
1471
+ IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
1472
+ levelMapping = [
1473
+ "ansi",
1474
+ "ansi",
1475
+ "ansi256",
1476
+ "ansi16m"
1477
+ ];
1478
+ styles2 = /* @__PURE__ */ Object.create(null);
1479
+ applyOptions = (object, options = {}) => {
1480
+ if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
1481
+ throw new Error("The `level` option should be an integer from 0 to 3");
1482
+ }
1483
+ const colorLevel = stdoutColor ? stdoutColor.level : 0;
1484
+ object.level = options.level === void 0 ? colorLevel : options.level;
1485
+ };
1486
+ chalkFactory = (options) => {
1487
+ const chalk2 = (...strings) => strings.join(" ");
1488
+ applyOptions(chalk2, options);
1489
+ Object.setPrototypeOf(chalk2, createChalk.prototype);
1490
+ return chalk2;
1491
+ };
1492
+ Object.setPrototypeOf(createChalk.prototype, Function.prototype);
1493
+ for (const [styleName, style] of Object.entries(ansi_styles_default)) {
1494
+ styles2[styleName] = {
1495
+ get() {
1496
+ const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
1497
+ Object.defineProperty(this, styleName, { value: builder });
1498
+ return builder;
1499
+ }
1500
+ };
1501
+ }
1502
+ styles2.visible = {
1503
+ get() {
1504
+ const builder = createBuilder(this, this[STYLER], true);
1505
+ Object.defineProperty(this, "visible", { value: builder });
1506
+ return builder;
1507
+ }
1508
+ };
1509
+ getModelAnsi = (model, level, type, ...arguments_) => {
1510
+ if (model === "rgb") {
1511
+ if (level === "ansi16m") {
1512
+ return ansi_styles_default[type].ansi16m(...arguments_);
1513
+ }
1514
+ if (level === "ansi256") {
1515
+ return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
1516
+ }
1517
+ return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
1518
+ }
1519
+ if (model === "hex") {
1520
+ return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
1521
+ }
1522
+ return ansi_styles_default[type][model](...arguments_);
1523
+ };
1524
+ usedModels = ["rgb", "hex", "ansi256"];
1525
+ for (const model of usedModels) {
1526
+ styles2[model] = {
1527
+ get() {
1528
+ const { level } = this;
1529
+ return function(...arguments_) {
1530
+ const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
1531
+ return createBuilder(this, styler, this[IS_EMPTY]);
1532
+ };
1533
+ }
1534
+ };
1535
+ const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
1536
+ styles2[bgModel] = {
1537
+ get() {
1538
+ const { level } = this;
1539
+ return function(...arguments_) {
1540
+ const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
1541
+ return createBuilder(this, styler, this[IS_EMPTY]);
1542
+ };
1543
+ }
1544
+ };
1545
+ }
1546
+ proto = Object.defineProperties(() => {
1547
+ }, {
1548
+ ...styles2,
1549
+ level: {
1550
+ enumerable: true,
1551
+ get() {
1552
+ return this[GENERATOR].level;
1553
+ },
1554
+ set(level) {
1555
+ this[GENERATOR].level = level;
1556
+ }
1557
+ }
1558
+ });
1559
+ createStyler = (open, close, parent) => {
1560
+ let openAll;
1561
+ let closeAll;
1562
+ if (parent === void 0) {
1563
+ openAll = open;
1564
+ closeAll = close;
1565
+ } else {
1566
+ openAll = parent.openAll + open;
1567
+ closeAll = close + parent.closeAll;
1568
+ }
1569
+ return {
1570
+ open,
1571
+ close,
1572
+ openAll,
1573
+ closeAll,
1574
+ parent
1575
+ };
1576
+ };
1577
+ createBuilder = (self, _styler, _isEmpty) => {
1578
+ const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
1579
+ Object.setPrototypeOf(builder, proto);
1580
+ builder[GENERATOR] = self;
1581
+ builder[STYLER] = _styler;
1582
+ builder[IS_EMPTY] = _isEmpty;
1583
+ return builder;
1584
+ };
1585
+ applyStyle = (self, string) => {
1586
+ if (self.level <= 0 || !string) {
1587
+ return self[IS_EMPTY] ? "" : string;
1588
+ }
1589
+ let styler = self[STYLER];
1590
+ if (styler === void 0) {
1591
+ return string;
1592
+ }
1593
+ const { openAll, closeAll } = styler;
1594
+ if (string.includes("\x1B")) {
1595
+ while (styler !== void 0) {
1596
+ string = stringReplaceAll(string, styler.close, styler.open);
1597
+ styler = styler.parent;
1598
+ }
1599
+ }
1600
+ const lfIndex = string.indexOf("\n");
1601
+ if (lfIndex !== -1) {
1602
+ string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
1603
+ }
1604
+ return openAll + string + closeAll;
1605
+ };
1606
+ Object.defineProperties(createChalk.prototype, styles2);
1607
+ chalk = createChalk();
1608
+ chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
1609
+ source_default = chalk;
1610
+ }
1611
+ });
1612
+
1613
+ // src/ui/text-input.tsx
1614
+ import { useState, useEffect } from "react";
1615
+ import { Text as Text6, useInput } from "ink";
1616
+ import { jsx as jsx6 } from "react/jsx-runtime";
1617
+ function findWordBoundaryForward(text, pos) {
1618
+ while (pos < text.length && /\w/.test(text[pos])) pos++;
1619
+ while (pos < text.length && !/\w/.test(text[pos])) pos++;
1620
+ return pos;
1621
+ }
1622
+ function findWordBoundaryBackward(text, pos) {
1623
+ while (pos > 0 && !/\w/.test(text[pos - 1])) pos--;
1624
+ while (pos > 0 && /\w/.test(text[pos - 1])) pos--;
1625
+ return pos;
1626
+ }
1627
+ function CustomTextInput({
1628
+ value,
1629
+ onChange,
1630
+ onSubmit,
1631
+ onHistoryUp,
1632
+ onHistoryDown,
1633
+ onClearQueueItem,
1634
+ focus = true,
1635
+ mask
1636
+ }) {
1637
+ const [cursorOffset, setCursorOffset] = useState(value.length);
1638
+ useEffect(() => {
1639
+ if (!focus) return;
1640
+ setCursorOffset((prev) => prev > value.length ? value.length : prev);
1641
+ }, [value, focus]);
1642
+ useInput(
1643
+ (input, key) => {
1644
+ if (!focus) return;
1645
+ if (key.ctrl && input === "c") return;
1646
+ if (key.ctrl && input === "r") return;
1647
+ if (key.tab) return;
1648
+ if (key.return) {
1649
+ onSubmit(value);
1650
+ setCursorOffset(0);
1651
+ return;
1652
+ }
1653
+ if (key.upArrow) {
1654
+ onHistoryUp?.();
1655
+ return;
1656
+ }
1657
+ if (key.downArrow) {
1658
+ onHistoryDown?.();
1659
+ return;
1660
+ }
1661
+ let nextCursor = cursorOffset;
1662
+ let nextValue = value;
1663
+ let didDelete = false;
1664
+ if (key.leftArrow) {
1665
+ if (key.meta) {
1666
+ nextCursor = findWordBoundaryBackward(value, cursorOffset);
1667
+ } else {
1668
+ nextCursor = cursorOffset - 1;
1669
+ }
1670
+ } else if (key.rightArrow) {
1671
+ if (key.meta) {
1672
+ nextCursor = findWordBoundaryForward(value, cursorOffset);
1673
+ } else {
1674
+ nextCursor = cursorOffset + 1;
1675
+ }
1676
+ } else if (key.home || key.ctrl && input === "a") {
1677
+ nextCursor = 0;
1678
+ } else if (key.end || key.ctrl && input === "e") {
1679
+ nextCursor = value.length;
1680
+ } else if (key.backspace) {
1681
+ didDelete = true;
1682
+ if (key.meta || key.ctrl && input === "w") {
1683
+ const boundary = findWordBoundaryBackward(value, cursorOffset);
1684
+ nextValue = value.slice(0, boundary) + value.slice(cursorOffset);
1685
+ nextCursor = boundary;
1686
+ } else if (key.ctrl) {
1687
+ const boundary = findWordBoundaryBackward(value, cursorOffset);
1688
+ nextValue = value.slice(0, boundary) + value.slice(cursorOffset);
1689
+ nextCursor = boundary;
1690
+ } else {
1691
+ if (cursorOffset > 0) {
1692
+ nextValue = value.slice(0, cursorOffset - 1) + value.slice(cursorOffset);
1693
+ nextCursor = cursorOffset - 1;
1694
+ }
1695
+ }
1696
+ } else if (key.delete) {
1697
+ didDelete = true;
1698
+ if (key.meta || key.ctrl) {
1699
+ const boundary = findWordBoundaryForward(value, cursorOffset);
1700
+ nextValue = value.slice(0, cursorOffset) + value.slice(boundary);
1701
+ } else {
1702
+ nextValue = value.slice(0, cursorOffset) + value.slice(cursorOffset + 1);
1703
+ }
1704
+ } else if (key.ctrl && input === "w") {
1705
+ didDelete = true;
1706
+ const boundary = findWordBoundaryBackward(value, cursorOffset);
1707
+ nextValue = value.slice(0, boundary) + value.slice(cursorOffset);
1708
+ nextCursor = boundary;
1709
+ } else if (key.ctrl && input === "u") {
1710
+ didDelete = true;
1711
+ nextValue = value.slice(cursorOffset);
1712
+ nextCursor = 0;
1713
+ } else if (key.ctrl && input === "k") {
1714
+ didDelete = true;
1715
+ nextValue = value.slice(0, cursorOffset);
1716
+ } else if (input.length > 0 && !key.ctrl && !key.meta) {
1717
+ nextValue = value.slice(0, cursorOffset) + input + value.slice(cursorOffset);
1718
+ nextCursor = cursorOffset + input.length;
1719
+ }
1720
+ if (nextCursor < 0) nextCursor = 0;
1721
+ if (nextCursor > nextValue.length) nextCursor = nextValue.length;
1722
+ if (didDelete && nextValue === "" && value !== "") {
1723
+ onClearQueueItem?.(value);
1724
+ }
1725
+ if (nextCursor !== cursorOffset) {
1726
+ setCursorOffset(nextCursor);
1727
+ }
1728
+ if (nextValue !== value) {
1729
+ onChange(nextValue);
1730
+ }
1731
+ },
1732
+ { isActive: focus }
1733
+ );
1734
+ const displayValue = mask ? mask.repeat(value.length) : value;
1735
+ let renderedValue = "";
1736
+ let i = 0;
1737
+ for (const char of displayValue) {
1738
+ renderedValue += i === cursorOffset ? source_default.inverse(char) : char;
1739
+ i++;
1740
+ }
1741
+ if (displayValue.length === 0) {
1742
+ renderedValue = source_default.inverse(" ");
1743
+ } else if (cursorOffset === displayValue.length) {
1744
+ renderedValue += source_default.inverse(" ");
1745
+ }
1746
+ return /* @__PURE__ */ jsx6(Text6, { children: renderedValue });
1747
+ }
1748
+ var init_text_input = __esm({
1749
+ "src/ui/text-input.tsx"() {
1750
+ "use strict";
1751
+ init_source();
1752
+ }
1753
+ });
1754
+
1755
+ // src/util/update-check.ts
1756
+ import { readFile as readFile6, writeFile as writeFile4, mkdir as mkdir3, access } from "fs/promises";
1757
+ import { homedir as homedir4 } from "os";
1758
+ import { join as join2, dirname as dirname2 } from "path";
1759
+ import { fileURLToPath } from "url";
1760
+ function cachePath() {
1761
+ const xdg = process.env.XDG_CONFIG_HOME || join2(homedir4(), ".config");
1762
+ return join2(xdg, "kimiflare", "update-check.json");
1763
+ }
1764
+ function localPackageJsonPath() {
1765
+ const here = dirname2(fileURLToPath(import.meta.url));
1766
+ return join2(here, "..", "..", "package.json");
1767
+ }
1768
+ async function readLocalVersion() {
1769
+ try {
1770
+ const raw = await readFile6(localPackageJsonPath(), "utf8");
1771
+ const parsed = JSON.parse(raw);
1772
+ return parsed.version ?? null;
1773
+ } catch {
1774
+ return null;
1775
+ }
1776
+ }
1777
+ async function readCache() {
1778
+ try {
1779
+ const raw = await readFile6(cachePath(), "utf8");
1780
+ const parsed = JSON.parse(raw);
1781
+ if (Date.now() - parsed.checkedAt < CACHE_TTL_MS) {
1782
+ return parsed;
1783
+ }
1784
+ } catch {
1785
+ }
1786
+ return null;
1787
+ }
1788
+ async function writeCache(entry) {
1789
+ const p = cachePath();
1790
+ await mkdir3(dirname2(p), { recursive: true });
1791
+ await writeFile4(p, JSON.stringify(entry), "utf8");
1792
+ }
1793
+ async function fetchLatestVersion() {
1794
+ try {
1795
+ const res = await fetch(GITHUB_API, {
1796
+ headers: { "User-Agent": "kimiflare-update-checker" }
1797
+ });
1798
+ if (!res.ok) return null;
1799
+ const data = await res.json();
1800
+ return data.tag_name ?? null;
1801
+ } catch {
1802
+ return null;
1803
+ }
1804
+ }
1805
+ function stripV(v) {
1806
+ return v.startsWith("v") ? v.slice(1) : v;
1807
+ }
1808
+ function isNewer(local, remote) {
1809
+ const a = stripV(local).split(".").map(Number);
1810
+ const b = stripV(remote).split(".").map(Number);
1811
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
1812
+ const av = a[i] ?? 0;
1813
+ const bv = b[i] ?? 0;
1814
+ if (av < bv) return true;
1815
+ if (av > bv) return false;
1816
+ }
1817
+ return false;
1818
+ }
1819
+ async function checkForUpdate() {
1820
+ const localVersion = await readLocalVersion();
1821
+ if (!localVersion) return { hasUpdate: false, localVersion: null, latestVersion: null };
1822
+ const cached = await readCache();
1823
+ if (cached) {
1824
+ return { hasUpdate: cached.hasUpdate, localVersion, latestVersion: cached.latestVersion };
1825
+ }
1826
+ const latestVersion = await fetchLatestVersion();
1827
+ if (!latestVersion) {
1828
+ return { hasUpdate: false, localVersion, latestVersion: null };
1829
+ }
1830
+ const hasUpdate = isNewer(localVersion, latestVersion);
1831
+ await writeCache({ checkedAt: Date.now(), latestVersion, hasUpdate });
1832
+ return { hasUpdate, localVersion, latestVersion };
1833
+ }
1834
+ async function isGitRepo() {
1835
+ try {
1836
+ const here = dirname2(fileURLToPath(import.meta.url));
1837
+ await access(join2(here, "..", "..", ".git"));
1838
+ return true;
1839
+ } catch {
1840
+ return false;
1841
+ }
1842
+ }
1843
+ var CACHE_TTL_MS, GITHUB_API;
1844
+ var init_update_check = __esm({
1845
+ "src/util/update-check.ts"() {
1846
+ "use strict";
1847
+ CACHE_TTL_MS = 60 * 60 * 1e3;
1848
+ GITHUB_API = "https://api.github.com/repos/sinameraji/kimiflare/releases/latest";
1849
+ }
1850
+ });
1851
+
1852
+ // src/ui/onboarding.tsx
1853
+ import { useState as useState2 } from "react";
1854
+ import { Box as Box6, Text as Text7 } from "ink";
1855
+ import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1856
+ function Onboarding({ onDone }) {
1857
+ const [step, setStep] = useState2("accountId");
1858
+ const [accountId, setAccountId] = useState2("");
1859
+ const [apiToken, setApiToken] = useState2("");
1860
+ const [model, setModel] = useState2(DEFAULT_MODEL);
1861
+ const [savedPath, setSavedPath] = useState2(null);
1862
+ const handleAccountIdSubmit = (value) => {
1863
+ const trimmed = value.trim();
1864
+ if (!trimmed) return;
1865
+ setAccountId(trimmed);
1866
+ setStep("apiToken");
1867
+ };
1868
+ const handleApiTokenSubmit = (value) => {
1869
+ const trimmed = value.trim();
1870
+ if (!trimmed) return;
1871
+ setApiToken(trimmed);
1872
+ setStep("model");
1873
+ };
1874
+ const handleModelSubmit = (value) => {
1875
+ const trimmed = value.trim() || DEFAULT_MODEL;
1876
+ setModel(trimmed);
1877
+ setStep("confirm");
1878
+ };
1879
+ const handleConfirm = async () => {
1880
+ const cfg = { accountId, apiToken, model };
1881
+ try {
1882
+ const path = await saveConfig(cfg);
1883
+ setSavedPath(path);
1884
+ onDone(cfg);
1885
+ } catch (e) {
1886
+ setSavedPath(`error: ${e.message}`);
1887
+ }
1888
+ };
1889
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1890
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "Welcome to kimiflare!" }),
1891
+ /* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: "Terminal coding agent powered by Kimi-K2.6 on Cloudflare Workers AI." }),
1892
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
1893
+ step === "accountId" && /* @__PURE__ */ jsxs5(Fragment, { children: [
1894
+ /* @__PURE__ */ jsx7(Text7, { children: "Enter your Cloudflare Account ID:" }),
1895
+ /* @__PURE__ */ jsxs5(Box6, { children: [
1896
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "\u203A " }),
1897
+ /* @__PURE__ */ jsx7(
1898
+ CustomTextInput,
1899
+ {
1900
+ value: accountId,
1901
+ onChange: setAccountId,
1902
+ onSubmit: handleAccountIdSubmit
1903
+ }
1904
+ )
1905
+ ] })
1906
+ ] }),
1907
+ step === "apiToken" && /* @__PURE__ */ jsxs5(Fragment, { children: [
1908
+ /* @__PURE__ */ jsx7(Text7, { children: "Enter your Cloudflare API Token:" }),
1909
+ /* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: "Create one at https://dash.cloudflare.com/profile/api-tokens" }),
1910
+ /* @__PURE__ */ jsxs5(Box6, { children: [
1911
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "\u203A " }),
1912
+ /* @__PURE__ */ jsx7(
1913
+ CustomTextInput,
1914
+ {
1915
+ value: apiToken,
1916
+ onChange: setApiToken,
1917
+ onSubmit: handleApiTokenSubmit,
1918
+ mask: "\u2022"
1919
+ }
1920
+ )
1921
+ ] })
1922
+ ] }),
1923
+ step === "model" && /* @__PURE__ */ jsxs5(Fragment, { children: [
1924
+ /* @__PURE__ */ jsx7(Text7, { children: "Model ID (press Enter for default):" }),
1925
+ /* @__PURE__ */ jsxs5(Text7, { color: "gray", dimColor: true, children: [
1926
+ "default: ",
1927
+ DEFAULT_MODEL
1928
+ ] }),
1929
+ /* @__PURE__ */ jsxs5(Box6, { children: [
1930
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "\u203A " }),
1931
+ /* @__PURE__ */ jsx7(
1932
+ CustomTextInput,
1933
+ {
1934
+ value: model,
1935
+ onChange: setModel,
1936
+ onSubmit: handleModelSubmit
1937
+ }
1938
+ )
1939
+ ] })
1940
+ ] }),
1941
+ step === "confirm" && /* @__PURE__ */ jsxs5(Fragment, { children: [
1942
+ /* @__PURE__ */ jsx7(Text7, { children: "Ready to save configuration:" }),
1943
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginLeft: 2, children: [
1944
+ /* @__PURE__ */ jsxs5(Text7, { color: "gray", children: [
1945
+ "Account ID: ",
1946
+ accountId
1947
+ ] }),
1948
+ /* @__PURE__ */ jsxs5(Text7, { color: "gray", children: [
1949
+ "API Token: ",
1950
+ "\u2022".repeat(apiToken.length)
1951
+ ] }),
1952
+ /* @__PURE__ */ jsxs5(Text7, { color: "gray", children: [
1953
+ "Model: ",
1954
+ model
1955
+ ] })
1956
+ ] }),
1957
+ /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { children: "Press Enter to confirm, or Ctrl+C to cancel" }) }),
1958
+ /* @__PURE__ */ jsxs5(Box6, { children: [
1959
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "\u203A " }),
1960
+ /* @__PURE__ */ jsx7(
1961
+ CustomTextInput,
1962
+ {
1963
+ value: "",
1964
+ onChange: () => {
1965
+ },
1966
+ onSubmit: handleConfirm
1967
+ }
1968
+ )
1969
+ ] })
1970
+ ] }),
1971
+ savedPath && /* @__PURE__ */ jsxs5(Text7, { color: "green", children: [
1972
+ "Config saved to ",
1973
+ savedPath
1974
+ ] })
1975
+ ] })
1976
+ ] });
1977
+ }
1978
+ var init_onboarding = __esm({
1979
+ "src/ui/onboarding.tsx"() {
1980
+ "use strict";
1981
+ init_text_input();
1982
+ init_config();
1983
+ }
1984
+ });
1985
+
1986
+ // src/app.tsx
1987
+ var app_exports = {};
1988
+ __export(app_exports, {
1989
+ renderApp: () => renderApp
1990
+ });
1991
+ import { useState as useState3, useRef, useEffect as useEffect2, useCallback } from "react";
1992
+ import { Box as Box7, Text as Text8, useApp, useInput as useInput2, render } from "ink";
1993
+ import Spinner2 from "ink-spinner";
1994
+ import { unlink } from "fs/promises";
1995
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1996
+ function App({ initialCfg }) {
1997
+ const { exit } = useApp();
1998
+ const [cfg, setCfg] = useState3(initialCfg);
1999
+ const [events, setEvents] = useState3([
2000
+ { kind: "info", key: mkKey(), text: "kimiflare \xB7 /help for commands \xB7 ctrl-c to exit" }
2001
+ ]);
2002
+ const [input, setInput] = useState3("");
2003
+ const [busy, setBusy] = useState3(false);
2004
+ const [usage, setUsage] = useState3(null);
2005
+ const [showReasoning, setShowReasoning] = useState3(false);
2006
+ const [perm, setPerm] = useState3(null);
2007
+ const [queue, setQueue] = useState3([]);
2008
+ const [history, setHistory] = useState3([]);
2009
+ const [historyIndex, setHistoryIndex] = useState3(-1);
2010
+ const [draftInput, setDraftInput] = useState3("");
2011
+ const [updateInfo, setUpdateInfo] = useState3(null);
2012
+ const messagesRef = useRef([
2013
+ {
2014
+ role: "system",
2015
+ content: buildSystemPrompt({ cwd: process.cwd(), tools: ALL_TOOLS, model: cfg?.model ?? DEFAULT_MODEL })
2016
+ }
2017
+ ]);
2018
+ const executorRef = useRef(new ToolExecutor(ALL_TOOLS));
2019
+ const activeAsstIdRef = useRef(null);
2020
+ const activeControllerRef = useRef(null);
2021
+ useInput2((inputChar, key) => {
2022
+ if (key.ctrl && inputChar === "c") {
2023
+ if (busy && activeControllerRef.current) {
2024
+ activeControllerRef.current.abort();
2025
+ setEvents((e) => [...e, { kind: "info", key: mkKey(), text: "(interrupted)" }]);
2026
+ } else {
2027
+ exit();
2028
+ }
2029
+ }
2030
+ if (key.ctrl && inputChar === "r") setShowReasoning((s) => !s);
2031
+ });
2032
+ const updateAssistant = useCallback(
2033
+ (id, patch) => {
2034
+ setEvents(
2035
+ (evts) => evts.map(
2036
+ (e) => e.kind === "assistant" && e.id === id ? { ...e, ...patch(e) } : e
2037
+ )
2038
+ );
2039
+ },
2040
+ []
2041
+ );
2042
+ const updateTool = useCallback(
2043
+ (id, patch) => {
2044
+ setEvents(
2045
+ (evts) => evts.map(
2046
+ (e) => e.kind === "tool" && e.id === id ? { ...e, ...patch } : e
2047
+ )
2048
+ );
2049
+ },
2050
+ []
2051
+ );
2052
+ const handleSlash = useCallback(
2053
+ (cmd) => {
2054
+ const c = cmd.trim().toLowerCase();
2055
+ if (c === "/exit" || c === "/quit") {
2056
+ exit();
2057
+ return true;
2058
+ }
2059
+ if (c === "/clear") {
2060
+ messagesRef.current = [messagesRef.current[0]];
2061
+ setEvents([{ kind: "info", key: mkKey(), text: "conversation cleared" }]);
2062
+ setUsage(null);
2063
+ return true;
2064
+ }
2065
+ if (c === "/reasoning") {
2066
+ setShowReasoning((s) => {
2067
+ const next = !s;
2068
+ setEvents((e) => [
2069
+ ...e,
2070
+ { kind: "info", key: mkKey(), text: `reasoning: ${next ? "shown" : "hidden"}` }
2071
+ ]);
2072
+ return next;
2073
+ });
2074
+ return true;
2075
+ }
2076
+ if (c === "/cost") {
2077
+ setEvents((e) => [
2078
+ ...e,
2079
+ {
2080
+ kind: "info",
2081
+ key: mkKey(),
2082
+ text: usage ? `prompt ${usage.prompt_tokens} / completion ${usage.completion_tokens}` : "no usage yet"
2083
+ }
2084
+ ]);
2085
+ return true;
2086
+ }
2087
+ if (c === "/model") {
2088
+ setEvents((e) => [
2089
+ ...e,
2090
+ { kind: "info", key: mkKey(), text: `current model: ${cfg?.model ?? "unknown"}` }
2091
+ ]);
2092
+ return true;
2093
+ }
2094
+ if (c === "/update") {
2095
+ if (updateInfo?.hasUpdate) {
2096
+ setEvents((e) => [
2097
+ ...e,
2098
+ {
2099
+ kind: "info",
2100
+ key: mkKey(),
2101
+ text: `updating from ${updateInfo.localVersion} \u2192 ${updateInfo.latestVersion}\u2026`
2102
+ }
2103
+ ]);
2104
+ isGitRepo().then((git) => {
2105
+ if (git) {
2106
+ setEvents((e) => [
2107
+ ...e,
2108
+ {
2109
+ kind: "info",
2110
+ key: mkKey(),
2111
+ text: "run: git pull && npm install && npm run build then restart kimiflare"
2112
+ }
2113
+ ]);
2114
+ } else {
2115
+ setEvents((e) => [
2116
+ ...e,
2117
+ {
2118
+ kind: "info",
2119
+ key: mkKey(),
2120
+ text: "run: npm update -g kimiflare then restart"
2121
+ }
2122
+ ]);
2123
+ }
2124
+ });
2125
+ } else {
2126
+ setEvents((e) => [
2127
+ ...e,
2128
+ { kind: "info", key: mkKey(), text: "no update available" }
2129
+ ]);
2130
+ }
2131
+ return true;
2132
+ }
2133
+ if (c === "/logout") {
2134
+ unlink(configPath()).catch(() => {
2135
+ });
2136
+ setEvents((e) => [
2137
+ ...e,
2138
+ { kind: "info", key: mkKey(), text: `credentials cleared from ${configPath()}` }
2139
+ ]);
2140
+ setCfg(null);
2141
+ return true;
2142
+ }
2143
+ if (c === "/help") {
2144
+ setEvents((e) => [
2145
+ ...e,
2146
+ {
2147
+ kind: "info",
2148
+ key: mkKey(),
2149
+ text: "commands: /clear /reasoning /cost /model /update /logout /help /exit \xB7 keys: ctrl-r toggle reasoning, ctrl-c interrupt/exit"
2150
+ }
2151
+ ]);
2152
+ return true;
2153
+ }
2154
+ return false;
2155
+ },
2156
+ [cfg, exit, usage, updateInfo]
2157
+ );
2158
+ const processMessage = useCallback(
2159
+ async (text) => {
2160
+ if (!cfg) return;
2161
+ const trimmed = text.trim();
2162
+ if (!trimmed) return;
2163
+ if (trimmed.startsWith("/") && handleSlash(trimmed)) return;
2164
+ setEvents((e) => [...e, { kind: "user", key: mkKey(), text: trimmed }]);
2165
+ messagesRef.current.push({ role: "user", content: trimmed });
2166
+ setBusy(true);
2167
+ const controller = new AbortController();
2168
+ activeControllerRef.current = controller;
2169
+ try {
2170
+ await runAgentTurn({
2171
+ accountId: cfg.accountId,
2172
+ apiToken: cfg.apiToken,
2173
+ model: cfg.model,
2174
+ messages: messagesRef.current,
2175
+ tools: ALL_TOOLS,
2176
+ executor: executorRef.current,
2177
+ cwd: process.cwd(),
2178
+ signal: controller.signal,
2179
+ callbacks: {
2180
+ onAssistantStart: () => {
2181
+ const id = nextAssistantId++;
2182
+ activeAsstIdRef.current = id;
2183
+ setEvents((e) => [
2184
+ ...e,
2185
+ { kind: "assistant", key: `asst_${id}`, id, text: "", reasoning: "", streaming: true }
2186
+ ]);
2187
+ },
2188
+ onReasoningDelta: (d) => {
2189
+ const id = activeAsstIdRef.current;
2190
+ if (id !== null) updateAssistant(id, (e) => ({ reasoning: e.reasoning + d }));
2191
+ },
2192
+ onTextDelta: (d) => {
2193
+ const id = activeAsstIdRef.current;
2194
+ if (id !== null) updateAssistant(id, (e) => ({ text: e.text + d }));
2195
+ },
2196
+ onAssistantFinal: () => {
2197
+ const id = activeAsstIdRef.current;
2198
+ if (id !== null) updateAssistant(id, () => ({ streaming: false }));
2199
+ },
2200
+ onToolCallFinalized: (call) => {
2201
+ const spec = executorRef.current.list().find((t) => t.name === call.function.name);
2202
+ let renderMeta;
2203
+ try {
2204
+ const args = call.function.arguments ? JSON.parse(call.function.arguments) : {};
2205
+ renderMeta = spec?.render?.(args);
2206
+ } catch {
2207
+ }
2208
+ setEvents((e) => [
2209
+ ...e,
2210
+ {
2211
+ kind: "tool",
2212
+ key: `tool_${call.id}`,
2213
+ id: call.id,
2214
+ name: call.function.name,
2215
+ args: call.function.arguments,
2216
+ status: "running",
2217
+ render: renderMeta,
2218
+ expanded: false
2219
+ }
2220
+ ]);
2221
+ },
2222
+ onToolResult: (r) => {
2223
+ updateTool(r.tool_call_id, {
2224
+ status: r.ok ? "done" : "error",
2225
+ result: r.content
2226
+ });
2227
+ },
2228
+ onUsage: (u) => setUsage(u),
2229
+ askPermission: (req) => new Promise((resolve2) => {
2230
+ setPerm({ tool: req.tool, args: req.args, resolve: resolve2 });
2231
+ })
2232
+ }
2233
+ });
2234
+ } catch (e) {
2235
+ if (e.name === "AbortError") {
2236
+ setEvents((es) => [...es, { kind: "info", key: mkKey(), text: "(aborted)" }]);
2237
+ } else {
2238
+ setEvents((es) => [
2239
+ ...es,
2240
+ { kind: "error", key: mkKey(), text: e.message ?? String(e) }
2241
+ ]);
2242
+ }
2243
+ } finally {
2244
+ setBusy(false);
2245
+ activeAsstIdRef.current = null;
2246
+ activeControllerRef.current = null;
2247
+ }
2248
+ },
2249
+ [cfg, handleSlash, updateAssistant, updateTool]
2250
+ );
2251
+ useEffect2(() => {
2252
+ if (!busy && queue.length > 0) {
2253
+ const next = queue[0];
2254
+ setQueue((q) => q.slice(1));
2255
+ processMessage(next);
2256
+ }
2257
+ }, [busy, queue, processMessage]);
2258
+ const submit = useCallback(
2259
+ (text) => {
2260
+ const trimmed = text.trim();
2261
+ if (!trimmed) return;
2262
+ if (busy) {
2263
+ setQueue((q) => [...q, trimmed]);
2264
+ setHistory((h) => h.length > 0 && h[h.length - 1] === trimmed ? h : [...h, trimmed]);
2265
+ setInput("");
2266
+ setHistoryIndex(-1);
2267
+ return;
2268
+ }
2269
+ setHistory((h) => h.length > 0 && h[h.length - 1] === trimmed ? h : [...h, trimmed]);
2270
+ setInput("");
2271
+ setHistoryIndex(-1);
2272
+ processMessage(trimmed);
2273
+ },
2274
+ [busy, processMessage]
2275
+ );
2276
+ useEffect2(() => {
2277
+ }, [events]);
2278
+ useEffect2(() => {
2279
+ checkForUpdate().then((result) => {
2280
+ if (result.hasUpdate) {
2281
+ setUpdateInfo(result);
2282
+ setEvents((e) => [
2283
+ ...e,
2284
+ {
2285
+ kind: "info",
2286
+ key: mkKey(),
2287
+ text: `update available: ${result.localVersion} \u2192 ${result.latestVersion} \xB7 run /update to upgrade`
2288
+ }
2289
+ ]);
2290
+ }
2291
+ });
2292
+ }, []);
2293
+ if (!cfg) {
2294
+ return /* @__PURE__ */ jsx8(
2295
+ Onboarding,
2296
+ {
2297
+ onDone: (newCfg) => {
2298
+ setCfg(newCfg);
2299
+ setEvents((e) => [
2300
+ ...e,
2301
+ { kind: "info", key: mkKey(), text: "configuration saved \u2014 welcome to kimiflare!" }
2302
+ ]);
2303
+ }
2304
+ }
2305
+ );
2306
+ }
2307
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
2308
+ /* @__PURE__ */ jsx8(ChatView, { events, showReasoning }),
2309
+ perm ? /* @__PURE__ */ jsx8(
2310
+ PermissionModal,
2311
+ {
2312
+ tool: perm.tool,
2313
+ args: perm.args,
2314
+ onDecide: (d) => {
2315
+ perm.resolve(d);
2316
+ setPerm(null);
2317
+ }
2318
+ }
2319
+ ) : /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
2320
+ queue.length > 0 && /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", marginBottom: 1, children: queue.map((q, i) => /* @__PURE__ */ jsxs6(Text8, { color: "gray", dimColor: true, children: [
2321
+ "\u23F3 ",
2322
+ q
2323
+ ] }, `queue_${i}`)) }),
2324
+ /* @__PURE__ */ jsx8(
2325
+ StatusBar,
2326
+ {
2327
+ model: cfg.model,
2328
+ usage,
2329
+ thinking: busy,
2330
+ hint: busy ? "ctrl-c to interrupt" : "enter to send \xB7 /help"
2331
+ }
2332
+ ),
2333
+ /* @__PURE__ */ jsx8(Box7, { children: busy ? /* @__PURE__ */ jsxs6(Box7, { children: [
2334
+ /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: /* @__PURE__ */ jsx8(Spinner2, { type: "dots" }) }),
2335
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " working\u2026" })
2336
+ ] }) : /* @__PURE__ */ jsxs6(Box7, { children: [
2337
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: "\u203A " }),
2338
+ /* @__PURE__ */ jsx8(
2339
+ CustomTextInput,
2340
+ {
2341
+ value: input,
2342
+ onChange: setInput,
2343
+ onSubmit: submit,
2344
+ onHistoryUp: () => {
2345
+ if (history.length === 0) return;
2346
+ if (historyIndex === -1) {
2347
+ setDraftInput(input);
2348
+ const nextIndex = history.length - 1;
2349
+ setHistoryIndex(nextIndex);
2350
+ setInput(history[nextIndex]);
2351
+ } else {
2352
+ const nextIndex = Math.max(0, historyIndex - 1);
2353
+ setHistoryIndex(nextIndex);
2354
+ setInput(history[nextIndex]);
2355
+ }
2356
+ },
2357
+ onHistoryDown: () => {
2358
+ if (historyIndex === -1) return;
2359
+ const nextIndex = historyIndex + 1;
2360
+ if (nextIndex >= history.length) {
2361
+ setHistoryIndex(-1);
2362
+ setInput(draftInput);
2363
+ } else {
2364
+ setHistoryIndex(nextIndex);
2365
+ setInput(history[nextIndex]);
2366
+ }
2367
+ },
2368
+ onClearQueueItem: (text) => {
2369
+ setQueue((q) => {
2370
+ const idx = q.indexOf(text);
2371
+ if (idx >= 0) {
2372
+ const next = [...q];
2373
+ next.splice(idx, 1);
2374
+ return next;
2375
+ }
2376
+ return q;
2377
+ });
2378
+ }
2379
+ }
2380
+ )
2381
+ ] }) })
2382
+ ] })
2383
+ ] });
2384
+ }
2385
+ async function renderApp(cfg) {
2386
+ const instance = render(/* @__PURE__ */ jsx8(App, { initialCfg: cfg }));
2387
+ await instance.waitUntilExit();
2388
+ }
2389
+ var nextAssistantId, nextKey, mkKey;
2390
+ var init_app = __esm({
2391
+ "src/app.tsx"() {
2392
+ "use strict";
2393
+ init_loop();
2394
+ init_system_prompt();
2395
+ init_executor();
2396
+ init_chat();
2397
+ init_status();
2398
+ init_permission();
2399
+ init_text_input();
2400
+ init_update_check();
2401
+ init_onboarding();
2402
+ init_config();
2403
+ nextAssistantId = 1;
2404
+ nextKey = 1;
2405
+ mkKey = () => `evt_${nextKey++}`;
2406
+ }
2407
+ });
2408
+
2409
+ // src/index.tsx
2410
+ init_config();
2411
+ init_loop();
2412
+ init_system_prompt();
2413
+ init_executor();
2414
+ import { Command } from "commander";
2415
+ var program = new Command();
2416
+ program.name("kimiflare").description("Terminal coding agent powered by Kimi-K2.6 on Cloudflare Workers AI.").version("0.1.0").option("-p, --print <prompt>", "one-shot mode: send prompt, stream reply to stdout, exit").option("-m, --model <id>", "model id (defaults to @cf/moonshotai/kimi-k2.6)").option("--dangerously-allow-all", "auto-approve every permission prompt (print mode only)").option("--reasoning", "include reasoning in stdout (print mode only)").parse();
2417
+ var opts = program.opts();
2418
+ async function main() {
2419
+ const cfg = await loadConfig();
2420
+ if (opts.print !== void 0) {
2421
+ if (!cfg) {
2422
+ console.error(
2423
+ 'kimiflare: missing credentials.\nSet CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN, or write them to\n ~/.config/kimiflare/config.json (chmod 600)\n { "accountId": "...", "apiToken": "...", "model": "@cf/moonshotai/kimi-k2.6" }'
2424
+ );
2425
+ process.exit(2);
2426
+ }
2427
+ const model = opts.model ?? cfg.model ?? DEFAULT_MODEL;
2428
+ await runPrintMode({
2429
+ ...cfg,
2430
+ model,
2431
+ prompt: opts.print,
2432
+ allowAll: !!opts.dangerouslyAllowAll,
2433
+ showReasoning: !!opts.reasoning
2434
+ });
2435
+ return;
2436
+ }
2437
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
2438
+ console.error(
2439
+ 'kimiflare: interactive mode requires a TTY. Use `kimiflare -p "..."` for non-TTY / piped usage.'
2440
+ );
2441
+ process.exit(2);
2442
+ }
2443
+ const { renderApp: renderApp2 } = await Promise.resolve().then(() => (init_app(), app_exports));
2444
+ if (cfg) {
2445
+ const model = opts.model ?? cfg.model ?? DEFAULT_MODEL;
2446
+ await renderApp2({ ...cfg, model });
2447
+ } else {
2448
+ await renderApp2(null);
2449
+ }
2450
+ }
2451
+ async function runPrintMode(opts2) {
2452
+ const cwd = process.cwd();
2453
+ const executor = new ToolExecutor(ALL_TOOLS);
2454
+ const messages = [
2455
+ { role: "system", content: buildSystemPrompt({ cwd, tools: ALL_TOOLS, model: opts2.model }) },
2456
+ { role: "user", content: opts2.prompt }
2457
+ ];
2458
+ const controller = new AbortController();
2459
+ process.on("SIGINT", () => controller.abort());
2460
+ let printedReasoningHeader = false;
2461
+ let printedAnswerHeader = false;
2462
+ await runAgentTurn({
2463
+ accountId: opts2.accountId,
2464
+ apiToken: opts2.apiToken,
2465
+ model: opts2.model,
2466
+ messages,
2467
+ tools: ALL_TOOLS,
2468
+ executor,
2469
+ cwd,
2470
+ signal: controller.signal,
2471
+ callbacks: {
2472
+ onReasoningDelta: opts2.showReasoning ? (delta) => {
2473
+ if (!printedReasoningHeader) {
2474
+ process.stderr.write("\x1B[2m--- reasoning ---\n");
2475
+ printedReasoningHeader = true;
2476
+ }
2477
+ process.stderr.write(delta);
2478
+ } : void 0,
2479
+ onTextDelta: (delta) => {
2480
+ if (opts2.showReasoning && printedReasoningHeader && !printedAnswerHeader) {
2481
+ process.stderr.write("\n--- answer ---\x1B[0m\n");
2482
+ printedAnswerHeader = true;
2483
+ }
2484
+ process.stdout.write(delta);
2485
+ },
2486
+ onToolCallFinalized: (call) => {
2487
+ process.stderr.write(`\x1B[2m[tool ${call.function.name}(${call.function.arguments})]\x1B[0m
2488
+ `);
2489
+ },
2490
+ onToolResult: (result) => {
2491
+ const snippet = result.content.length > 400 ? result.content.slice(0, 400) + "..." : result.content;
2492
+ process.stderr.write(`\x1B[2m[result: ${snippet.replace(/\n/g, " \u23CE ")}]\x1B[0m
2493
+ `);
2494
+ },
2495
+ askPermission: async ({ tool, args }) => {
2496
+ if (opts2.allowAll) return "allow";
2497
+ process.stderr.write(
2498
+ `\x1B[31m[permission denied: ${tool.name}(${JSON.stringify(args)}) \u2014 pass --dangerously-allow-all to approve in print mode]\x1B[0m
2499
+ `
2500
+ );
2501
+ return "deny";
2502
+ }
2503
+ }
2504
+ });
2505
+ process.stdout.write("\n");
2506
+ }
2507
+ main().catch((e) => {
2508
+ console.error(`kimiflare: ${e instanceof Error ? e.message : String(e)}`);
2509
+ process.exit(1);
2510
+ });
2511
+ //# sourceMappingURL=index.js.map