@cryer/star-cli 0.3.5 → 0.3.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/main.js CHANGED
@@ -44,7 +44,7 @@ import {
44
44
  resolveSnapshotContent,
45
45
  rewindToSnapshot,
46
46
  summarizeMessages
47
- } from "./chunk-OIDB2E5J.js";
47
+ } from "./chunk-I6FDXAWX.js";
48
48
  import {
49
49
  envFilePath,
50
50
  globalConfigPath,
@@ -57,6 +57,198 @@ import {
57
57
  // src/main.tsx
58
58
  import { Command } from "commander";
59
59
 
60
+ // src/cli/markdown.ts
61
+ var ESC = "\x1B[";
62
+ var RESET = `${ESC}0m`;
63
+ function styled(open, text, base) {
64
+ const reopen = base ? `${ESC}${base}m` : "";
65
+ return `${ESC}${open}m${text}${RESET}${reopen}`;
66
+ }
67
+ var bold = (t, b) => styled("1", t, b);
68
+ var dim = (t, b) => styled("2", t, b);
69
+ var italic = (t, b) => styled("3", t, b);
70
+ var underline = (t, b) => styled("4", t, b);
71
+ var strike = (t, b) => styled("9", t, b);
72
+ var yellow = (t, b) => styled("33", t, b);
73
+ var cyanBold = (t, b) => styled("1;36", t, b);
74
+ function cellWidth(text) {
75
+ let width = 0;
76
+ for (const ch of text) {
77
+ const cp = ch.codePointAt(0) ?? 0;
78
+ if (cp >= 768 && cp <= 879 || cp === 8205 || cp >= 65024 && cp <= 65039) {
79
+ continue;
80
+ }
81
+ if (cp >= 4352 && cp <= 4447 || cp >= 11904 && cp <= 40959 || cp >= 40960 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 126976 && cp <= 129791 || cp >= 131072 && cp <= 262141) {
82
+ width += 2;
83
+ } else {
84
+ width += 1;
85
+ }
86
+ }
87
+ return width;
88
+ }
89
+ var IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
90
+ var LINK_RE = /\[([^\]]+)\]\(([^)\s]+)\)/g;
91
+ var BOLD_RE = /\*\*([^*\n]+)\*\*|__([^_\n]+)__/g;
92
+ var STRIKE_RE = /~~([^~\n]+)~~/g;
93
+ var STAR_ITALIC_RE = /\*([^*\n]+)\*/g;
94
+ var UNDERSCORE_ITALIC_RE = /(^|[^\w])_([^_\n]+)_(?=[^\w]|$)/g;
95
+ function renderInline(text, base = "") {
96
+ return text.split(/`([^`\n]+)`/).map((segment, index) => {
97
+ if (index % 2 === 1) return yellow(segment, base);
98
+ return segment.replace(IMAGE_RE, (_m, alt) => dim(`[image: ${alt}]`, base)).replace(
99
+ LINK_RE,
100
+ (_m, label, url) => `${underline(label, base)}${dim(` (${url})`, base)}`
101
+ ).replace(BOLD_RE, (_m, star, under) => bold(star ?? under, base)).replace(STRIKE_RE, (_m, t) => strike(t, base)).replace(STAR_ITALIC_RE, (_m, t) => italic(t, base)).replace(UNDERSCORE_ITALIC_RE, (_m, pre, t) => pre + italic(t, base));
102
+ }).join("");
103
+ }
104
+ var HEADING_RE = /^#{1,6}\s+(.*)$/;
105
+ var HR_RE = /^\s{0,3}(?:-{3,}|\*{3,}|_{3,})\s*$/;
106
+ var QUOTE_RE = /^\s{0,3}>\s?(.*)$/;
107
+ var FENCE_RE = /^\s{0,3}(```+|~~~+)/;
108
+ var TABLE_SEPARATOR_CELL_RE = /^:?-{3,}:?$/;
109
+ function isTableSeparator(line) {
110
+ const cells = splitTableRow(line);
111
+ return cells !== null && cells.length > 0 && cells.every((cell) => TABLE_SEPARATOR_CELL_RE.test(cell.trim()));
112
+ }
113
+ function splitTableRow(line) {
114
+ const trimmed = line.trim();
115
+ if (!trimmed.includes("|")) return null;
116
+ const stripped = trimmed.replace(/^\|/, "").replace(/\|$/, "");
117
+ return stripped.split("|").map((cell) => cell.trim());
118
+ }
119
+ var MIN_COLUMN_WIDTH = 4;
120
+ function wrapCell(text, width) {
121
+ const lines = [];
122
+ let current = "";
123
+ let currentWidth = 0;
124
+ for (const ch of text) {
125
+ const w = cellWidth(ch);
126
+ if (currentWidth > 0 && currentWidth + w > width) {
127
+ const lastSpace = current.lastIndexOf(" ");
128
+ if (lastSpace > 0 && ch !== " ") {
129
+ lines.push(current.slice(0, lastSpace));
130
+ current = current.slice(lastSpace + 1) + ch;
131
+ } else {
132
+ lines.push(current);
133
+ current = ch === " " ? "" : ch;
134
+ }
135
+ currentWidth = cellWidth(current);
136
+ } else {
137
+ current += ch;
138
+ currentWidth += w;
139
+ }
140
+ }
141
+ if (current.length > 0 || lines.length === 0) lines.push(current);
142
+ return lines;
143
+ }
144
+ function renderTable(lines, base, maxWidth) {
145
+ const rows = [];
146
+ for (const line of lines) {
147
+ const cells = splitTableRow(line);
148
+ if (cells) rows.push(cells);
149
+ }
150
+ if (rows.length < 2) return lines;
151
+ const columnCount = Math.max(...rows.map((row) => row.length));
152
+ const widths = Array.from(
153
+ { length: columnCount },
154
+ (_, column) => Math.max(1, ...rows.map((row) => cellWidth(row[column] ?? "")))
155
+ );
156
+ const totalWidth = () => 2 + widths.reduce((sum, width) => sum + width, 0) + 3 * (columnCount - 1);
157
+ while (totalWidth() > maxWidth) {
158
+ const widest = widths.indexOf(Math.max(...widths));
159
+ if (widths[widest] === void 0 || widths[widest] <= MIN_COLUMN_WIDTH) break;
160
+ widths[widest] -= 1;
161
+ }
162
+ const joiner = ` ${dim("\u2502", base)} `;
163
+ const formatRow = (row, style) => {
164
+ const wrapped = widths.map((width, column) => wrapCell(row[column] ?? "", width));
165
+ const height = Math.max(...wrapped.map((cell) => cell.length));
166
+ const out = [];
167
+ for (let line = 0; line < height; line++) {
168
+ out.push(
169
+ ` ${widths.map((width, column) => {
170
+ const raw = wrapped[column]?.[line] ?? "";
171
+ return style(raw) + " ".repeat(Math.max(0, width - cellWidth(raw)));
172
+ }).join(joiner).trimEnd()}`
173
+ );
174
+ }
175
+ return out;
176
+ };
177
+ const separator = dim(` ${widths.map((width) => "\u2500".repeat(width)).join("\u2500\u253C\u2500")}`, base);
178
+ const [header, , ...body] = rows;
179
+ return [
180
+ ...formatRow(header, (cell) => bold(cell, base)),
181
+ separator,
182
+ ...body.flatMap((row, index) => {
183
+ const lines2 = formatRow(row, (cell) => renderInline(cell, base));
184
+ return index < body.length - 1 ? [...lines2, separator] : lines2;
185
+ })
186
+ ];
187
+ }
188
+ function committableLineCount(lines) {
189
+ let inFence = false;
190
+ let safe = 0;
191
+ for (let i = 0; i < lines.length; i++) {
192
+ const line = lines[i] ?? "";
193
+ if (FENCE_RE.test(line)) inFence = !inFence;
194
+ const isPipe = line.includes("|");
195
+ const nextIsPipe = i + 1 >= lines.length || (lines[i + 1] ?? "").includes("|");
196
+ if (!inFence && !(isPipe && nextIsPipe)) safe = i + 1;
197
+ }
198
+ return safe;
199
+ }
200
+ function renderMarkdown(text, base = "", maxWidth = 80) {
201
+ const lines = text.split("\n");
202
+ const out = [];
203
+ let inFence = false;
204
+ for (let i = 0; i < lines.length; i++) {
205
+ const line = lines[i] ?? "";
206
+ if (inFence) {
207
+ if (FENCE_RE.test(line)) {
208
+ inFence = false;
209
+ } else {
210
+ out.push(`${dim(" \u258E", base)}${line}`);
211
+ }
212
+ continue;
213
+ }
214
+ const fence = FENCE_RE.exec(line);
215
+ if (fence) {
216
+ inFence = true;
217
+ const lang = line.trim().slice(fence[1]?.length ?? 0).trim();
218
+ if (lang) out.push(dim(` \u258E ${lang}`, base));
219
+ continue;
220
+ }
221
+ const heading = HEADING_RE.exec(line);
222
+ if (heading) {
223
+ out.push(cyanBold(heading[1] ?? "", base));
224
+ continue;
225
+ }
226
+ if (HR_RE.test(line)) {
227
+ out.push(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", base));
228
+ continue;
229
+ }
230
+ const quote = QUOTE_RE.exec(line);
231
+ if (quote) {
232
+ out.push(`${dim(" \u258E", base)}${italic(renderInline(quote[1] ?? "", base), base)}`);
233
+ continue;
234
+ }
235
+ const headerCells = splitTableRow(line);
236
+ if (headerCells && i + 1 < lines.length && isTableSeparator(lines[i + 1] ?? "")) {
237
+ const block = [line];
238
+ let j = i + 1;
239
+ while (j < lines.length && splitTableRow(lines[j] ?? "") !== null) {
240
+ block.push(lines[j] ?? "");
241
+ j += 1;
242
+ }
243
+ out.push(...renderTable(block, base, maxWidth));
244
+ i = j - 1;
245
+ continue;
246
+ }
247
+ out.push(renderInline(line, base));
248
+ }
249
+ return out.join("\n");
250
+ }
251
+
60
252
  // src/cli/terminal-text.ts
61
253
  var UNSAFE_CHARS = /[\x00-\x09\x0b-\x1f\x7f]/;
62
254
  var CONTROL_CHARS = /[\x00-\x08\x0b-\x1f\x7f]/g;
@@ -85,9 +277,11 @@ function splitCommittableLines(text, minCompleteLines) {
85
277
  const lastNewline = text.lastIndexOf("\n");
86
278
  if (lastNewline < 0) return null;
87
279
  const complete = text.slice(0, lastNewline);
88
- const completeLines = complete.split("\n").length;
89
- if (completeLines < minCompleteLines) return null;
90
- return { committed: complete, rest: text.slice(lastNewline + 1) };
280
+ const lines = complete.split("\n");
281
+ const count = committableLineCount(lines);
282
+ if (count < minCompleteLines) return null;
283
+ const committed = lines.slice(0, count).join("\n");
284
+ return { committed, rest: text.slice(committed.length + 1) };
91
285
  }
92
286
  function formatStreamError(error) {
93
287
  if (error.message.includes("Unexpected end of JSON input")) {
@@ -491,8 +685,8 @@ var UsageTracker = class {
491
685
  };
492
686
 
493
687
  // src/cli/repl.tsx
494
- import { Box as Box13, Text as Text14, render, useApp, useInput as useInput8 } from "ink";
495
- import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef5, useState as useState5 } from "react";
688
+ import { Box as Box13, Text as Text14, render, useApp } from "ink";
689
+ import { useCallback, useEffect as useEffect3, useMemo, useRef as useRef6, useState as useState5 } from "react";
496
690
 
497
691
  // src/config/save.ts
498
692
  import fs from "fs";
@@ -600,6 +794,75 @@ async function savePermissionMode(mode) {
600
794
  await fs.promises.mkdir(path3.dirname(filePath), { recursive: true });
601
795
  await writeFileAtomic(filePath, upsertTopLevelKeyText(content, "permissionMode", mode));
602
796
  }
797
+ function unquoteTomlString(raw) {
798
+ if (raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
799
+ try {
800
+ return JSON.parse(raw);
801
+ } catch {
802
+ return null;
803
+ }
804
+ }
805
+ function setReasoningEffortInToml(content, modelName, effort) {
806
+ const lines = content.split("\n");
807
+ const header = /^\s*\[\[\s*models\s*\]\]\s*(?:#.*)?$/;
808
+ const anyHeader = /^\s*\[/;
809
+ const nameLine = /^\s*name\s*=\s*("(?:[^"\\]|\\.)*"|'[^']*')/;
810
+ for (let start = 0; start < lines.length; start++) {
811
+ if (!header.test(lines[start] ?? "")) continue;
812
+ let end = lines.length;
813
+ for (let i = start + 1; i < lines.length; i++) {
814
+ if (anyHeader.test(lines[i] ?? "")) {
815
+ end = i;
816
+ break;
817
+ }
818
+ }
819
+ let nameIndex = -1;
820
+ for (let i = start + 1; i < end; i++) {
821
+ const match = nameLine.exec(lines[i] ?? "");
822
+ if (match?.[1] && unquoteTomlString(match[1]) === modelName) {
823
+ nameIndex = i;
824
+ break;
825
+ }
826
+ }
827
+ if (nameIndex === -1) continue;
828
+ const keyLine = /^\s*reasoningEffort\s*=/;
829
+ for (let i = start + 1; i < end; i++) {
830
+ if (!keyLine.test(lines[i] ?? "")) continue;
831
+ if (effort === void 0) {
832
+ lines.splice(i, 1);
833
+ } else {
834
+ const comment = /\s+#.*$/.exec(lines[i] ?? "");
835
+ lines[i] = `reasoningEffort = ${tomlString(effort)}${comment?.[0] ?? ""}`;
836
+ }
837
+ return lines.join("\n");
838
+ }
839
+ if (effort === void 0) return content;
840
+ let insertAt = nameIndex + 1;
841
+ for (let i = start + 1; i < end; i++) {
842
+ if (/^\s*model\s*=/.test(lines[i] ?? "")) {
843
+ insertAt = i + 1;
844
+ break;
845
+ }
846
+ }
847
+ lines.splice(insertAt, 0, `reasoningEffort = ${tomlString(effort)}`);
848
+ return lines.join("\n");
849
+ }
850
+ return null;
851
+ }
852
+ async function saveReasoningEffort(modelName, effort) {
853
+ const filePath = globalConfigPath();
854
+ let content = "";
855
+ try {
856
+ content = await fs.promises.readFile(filePath, "utf8");
857
+ } catch (error) {
858
+ if (error.code !== "ENOENT") throw error;
859
+ }
860
+ const updated = setReasoningEffortInToml(content, modelName, effort);
861
+ if (updated === null) return false;
862
+ await fs.promises.mkdir(path3.dirname(filePath), { recursive: true });
863
+ await writeFileAtomic(filePath, updated);
864
+ return true;
865
+ }
603
866
 
604
867
  // src/llm/provider.ts
605
868
  import { createAnthropic } from "@ai-sdk/anthropic";
@@ -734,6 +997,14 @@ function createModel(config, modelName) {
734
997
  }).responses(modelConfig.model);
735
998
  }
736
999
  }
1000
+ function reasoningEffortMetadata(config, modelName) {
1001
+ const modelConfig = resolveModelConfig(config, modelName);
1002
+ if (!modelConfig.reasoningEffort) return void 0;
1003
+ const provider = config.providers.find((p) => p.name === modelConfig.provider);
1004
+ if (!provider || provider.protocol === "anthropic") return void 0;
1005
+ const key = provider.protocol === "openai-responses" ? "openai" : provider.name;
1006
+ return { [key]: { reasoningEffort: modelConfig.reasoningEffort } };
1007
+ }
737
1008
 
738
1009
  // src/session/checkpoints.ts
739
1010
  import fs2 from "fs/promises";
@@ -3184,17 +3455,295 @@ async function buildUndoTreeDiffs(cwd, tree) {
3184
3455
  }
3185
3456
 
3186
3457
  // src/cli/components/ConnectWizard.tsx
3187
- import { Box as Box4, Text as Text4, useInput as useInput4 } from "ink";
3188
- import { useRef as useRef4, useState as useState4 } from "react";
3458
+ import { Box as Box4, Text as Text4 } from "ink";
3459
+ import { useRef as useRef5, useState as useState4 } from "react";
3460
+
3461
+ // src/cli/use-input.ts
3462
+ import { useStdin } from "ink";
3463
+ import { useEffect, useRef } from "react";
3464
+
3465
+ // src/cli/keypress.ts
3466
+ var metaKeyCodeRe = /^(?:\u001B)([a-zA-Z0-9])$/;
3467
+ var fnKeyRe = /^(?:\u001B+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/;
3468
+ var keyName = {
3469
+ OP: "f1",
3470
+ OQ: "f2",
3471
+ OR: "f3",
3472
+ OS: "f4",
3473
+ "[11~": "f1",
3474
+ "[12~": "f2",
3475
+ "[13~": "f3",
3476
+ "[14~": "f4",
3477
+ "[[A": "f1",
3478
+ "[[B": "f2",
3479
+ "[[C": "f3",
3480
+ "[[D": "f4",
3481
+ "[[E": "f5",
3482
+ "[15~": "f5",
3483
+ "[17~": "f6",
3484
+ "[18~": "f7",
3485
+ "[19~": "f8",
3486
+ "[20~": "f9",
3487
+ "[21~": "f10",
3488
+ "[23~": "f11",
3489
+ "[24~": "f12",
3490
+ "[A": "up",
3491
+ "[B": "down",
3492
+ "[C": "right",
3493
+ "[D": "left",
3494
+ "[E": "clear",
3495
+ "[F": "end",
3496
+ "[H": "home",
3497
+ OA: "up",
3498
+ OB: "down",
3499
+ OC: "right",
3500
+ OD: "left",
3501
+ OE: "clear",
3502
+ OF: "end",
3503
+ OH: "home",
3504
+ "[1~": "home",
3505
+ "[2~": "insert",
3506
+ "[3~": "delete",
3507
+ "[4~": "end",
3508
+ "[5~": "pageup",
3509
+ "[6~": "pagedown",
3510
+ "[[5~": "pageup",
3511
+ "[[6~": "pagedown",
3512
+ "[7~": "home",
3513
+ "[8~": "end",
3514
+ "[a": "up",
3515
+ "[b": "down",
3516
+ "[c": "right",
3517
+ "[d": "left",
3518
+ "[e": "clear",
3519
+ "[2$": "insert",
3520
+ "[3$": "delete",
3521
+ "[5$": "pageup",
3522
+ "[6$": "pagedown",
3523
+ "[7$": "home",
3524
+ "[8$": "end",
3525
+ Oa: "up",
3526
+ Ob: "down",
3527
+ Oc: "right",
3528
+ Od: "left",
3529
+ Oe: "clear",
3530
+ "[2^": "insert",
3531
+ "[3^": "delete",
3532
+ "[5^": "pageup",
3533
+ "[6^": "pagedown",
3534
+ "[7^": "home",
3535
+ "[8^": "end",
3536
+ "[Z": "tab"
3537
+ };
3538
+ var nonAlphanumericKeys = [...Object.values(keyName), "backspace"];
3539
+ var shiftKeyCodes = /* @__PURE__ */ new Set([
3540
+ "[a",
3541
+ "[b",
3542
+ "[c",
3543
+ "[d",
3544
+ "[e",
3545
+ "[2$",
3546
+ "[3$",
3547
+ "[5$",
3548
+ "[6$",
3549
+ "[7$",
3550
+ "[8$",
3551
+ "[Z"
3552
+ ]);
3553
+ var ctrlKeyCodes = /* @__PURE__ */ new Set([
3554
+ "Oa",
3555
+ "Ob",
3556
+ "Oc",
3557
+ "Od",
3558
+ "Oe",
3559
+ "[2^",
3560
+ "[3^",
3561
+ "[5^",
3562
+ "[6^",
3563
+ "[7^",
3564
+ "[8^"
3565
+ ]);
3566
+ function parseKeypress(s) {
3567
+ const key = {
3568
+ name: void 0,
3569
+ ctrl: false,
3570
+ meta: false,
3571
+ shift: false,
3572
+ option: false,
3573
+ sequence: s
3574
+ };
3575
+ if (s === "\r") {
3576
+ key.name = "return";
3577
+ } else if (s === "\n") {
3578
+ key.name = "enter";
3579
+ } else if (s === " ") {
3580
+ key.name = "tab";
3581
+ } else if (s === "\b" || s === "\x1B\b") {
3582
+ key.name = "backspace";
3583
+ key.meta = s.charAt(0) === "\x1B";
3584
+ } else if (s === "\x7F" || s === "\x1B\x7F") {
3585
+ key.name = "delete";
3586
+ key.meta = s.charAt(0) === "\x1B";
3587
+ } else if (s === "\x1B" || s === "\x1B\x1B") {
3588
+ key.name = "escape";
3589
+ key.meta = s.length === 2;
3590
+ } else if (s === " " || s === "\x1B ") {
3591
+ key.name = "space";
3592
+ key.meta = s.length === 2;
3593
+ } else if (s.length === 1 && s <= "") {
3594
+ key.name = String.fromCharCode(s.charCodeAt(0) + "a".charCodeAt(0) - 1);
3595
+ key.ctrl = true;
3596
+ } else if (s.length === 1 && s >= "0" && s <= "9") {
3597
+ key.name = "number";
3598
+ } else if (s.length === 1 && s >= "a" && s <= "z") {
3599
+ key.name = s;
3600
+ } else if (s.length === 1 && s >= "A" && s <= "Z") {
3601
+ key.name = s.toLowerCase();
3602
+ key.shift = true;
3603
+ }
3604
+ const metaMatch = key.name === void 0 ? metaKeyCodeRe.exec(s) : null;
3605
+ const fnMatch = key.name === void 0 && metaMatch === null ? fnKeyRe.exec(s) : null;
3606
+ if (metaMatch) {
3607
+ key.meta = true;
3608
+ key.shift = /^[A-Z]$/.test(metaMatch[1] ?? "");
3609
+ } else if (fnMatch) {
3610
+ const segs = [...s];
3611
+ if (segs[0] === "\x1B" && segs[1] === "\x1B") {
3612
+ key.option = true;
3613
+ }
3614
+ const code = [fnMatch[1], fnMatch[2], fnMatch[4], fnMatch[6]].filter(Boolean).join("");
3615
+ const modifier = Number(fnMatch[3] || fnMatch[5] || 1) - 1;
3616
+ key.ctrl = !!(modifier & 4);
3617
+ key.meta = !!(modifier & 10);
3618
+ key.shift = !!(modifier & 1);
3619
+ key.code = code;
3620
+ key.name = keyName[code];
3621
+ key.shift = shiftKeyCodes.has(code) || key.shift;
3622
+ key.ctrl = ctrlKeyCodes.has(code) || key.ctrl;
3623
+ }
3624
+ return key;
3625
+ }
3626
+ function splitKeypresses(chunk) {
3627
+ const units = [];
3628
+ let i = 0;
3629
+ const csiLength = (start) => {
3630
+ let j = start;
3631
+ while (j < chunk.length) {
3632
+ const code = chunk.charCodeAt(j);
3633
+ j += 1;
3634
+ if (code >= 64 && code <= 126) break;
3635
+ }
3636
+ return j;
3637
+ };
3638
+ while (i < chunk.length) {
3639
+ if (chunk[i] !== "\x1B") {
3640
+ const next2 = chunk.indexOf("\x1B", i);
3641
+ const end = next2 === -1 ? chunk.length : next2;
3642
+ units.push(chunk.slice(i, end));
3643
+ i = end;
3644
+ continue;
3645
+ }
3646
+ const next = chunk[i + 1];
3647
+ if (next === void 0) {
3648
+ units.push("\x1B");
3649
+ i += 1;
3650
+ } else if (next === "[") {
3651
+ const end = csiLength(i + 2);
3652
+ units.push(chunk.slice(i, end));
3653
+ i = end;
3654
+ } else if (next === "O") {
3655
+ const end = Math.min(i + 3, chunk.length);
3656
+ units.push(chunk.slice(i, end));
3657
+ i = end;
3658
+ } else if (next === "\x1B" && (chunk[i + 2] === "[" || chunk[i + 2] === "O")) {
3659
+ const end = chunk[i + 2] === "[" ? csiLength(i + 3) : Math.min(i + 4, chunk.length);
3660
+ units.push(chunk.slice(i, end));
3661
+ i = end;
3662
+ } else if (next === "\x1B") {
3663
+ units.push("\x1B");
3664
+ i += 1;
3665
+ } else {
3666
+ units.push(chunk.slice(i, i + 2));
3667
+ i += 2;
3668
+ }
3669
+ }
3670
+ return units;
3671
+ }
3672
+ var INCOMPLETE_TAIL_RE = /\u001B(?:\[(?:[0-9;?]*)?|O|\u001B(?:\[(?:[0-9;?]*)?|O))$/;
3673
+ function splitTrailingIncomplete(chunk) {
3674
+ const match = INCOMPLETE_TAIL_RE.exec(chunk);
3675
+ if (!match) return { head: chunk, tail: "" };
3676
+ return { head: chunk.slice(0, chunk.length - match[0].length), tail: match[0] };
3677
+ }
3678
+ function toInputKey(unit) {
3679
+ const keypress = parseKeypress(unit);
3680
+ const key = {
3681
+ upArrow: keypress.name === "up",
3682
+ downArrow: keypress.name === "down",
3683
+ leftArrow: keypress.name === "left",
3684
+ rightArrow: keypress.name === "right",
3685
+ pageDown: keypress.name === "pagedown",
3686
+ pageUp: keypress.name === "pageup",
3687
+ return: keypress.name === "return",
3688
+ escape: keypress.name === "escape",
3689
+ ctrl: keypress.ctrl,
3690
+ shift: keypress.shift,
3691
+ tab: keypress.name === "tab",
3692
+ backspace: keypress.name === "backspace",
3693
+ delete: keypress.name === "delete",
3694
+ meta: keypress.meta || keypress.name === "escape" || keypress.option
3695
+ };
3696
+ let input = keypress.ctrl ? keypress.name ?? "" : keypress.sequence;
3697
+ if (nonAlphanumericKeys.includes(keypress.name ?? "")) {
3698
+ input = "";
3699
+ }
3700
+ if (input.startsWith("\x1B")) {
3701
+ input = input.slice(1);
3702
+ }
3703
+ if (input.length === 1 && /[A-Z]/.test(input)) {
3704
+ key.shift = true;
3705
+ }
3706
+ return { input, key };
3707
+ }
3708
+
3709
+ // src/cli/use-input.ts
3710
+ function useInput(inputHandler, options = {}) {
3711
+ const { setRawMode, internal_exitOnCtrlC, internal_eventEmitter } = useStdin();
3712
+ const pendingRef = useRef("");
3713
+ useEffect(() => {
3714
+ if (options.isActive === false) return;
3715
+ setRawMode(true);
3716
+ return () => {
3717
+ setRawMode(false);
3718
+ };
3719
+ }, [options.isActive, setRawMode]);
3720
+ useEffect(() => {
3721
+ if (options.isActive === false) return;
3722
+ const handleData = (data) => {
3723
+ const { head, tail } = splitTrailingIncomplete(pendingRef.current + data);
3724
+ pendingRef.current = tail;
3725
+ for (const unit of splitKeypresses(head)) {
3726
+ const { input, key } = toInputKey(unit);
3727
+ if (!(input === "c" && key.ctrl) || !internal_exitOnCtrlC) {
3728
+ inputHandler(input, key);
3729
+ }
3730
+ }
3731
+ };
3732
+ internal_eventEmitter?.on("input", handleData);
3733
+ return () => {
3734
+ internal_eventEmitter?.removeListener("input", handleData);
3735
+ };
3736
+ }, [options.isActive, internal_exitOnCtrlC, internal_eventEmitter, inputHandler]);
3737
+ }
3189
3738
 
3190
3739
  // src/cli/components/SelectPrompt.tsx
3191
- import { Box, Text, useInput } from "ink";
3192
- import { useRef, useState } from "react";
3740
+ import { Box, Text } from "ink";
3741
+ import { useRef as useRef2, useState } from "react";
3193
3742
  import { jsx, jsxs } from "react/jsx-runtime";
3194
3743
  var SELECT_PROMPT_MAX_VISIBLE = 9;
3195
3744
  function SelectPrompt({ title, options, onSelect, onCancel }) {
3196
3745
  const [index, setIndex] = useState(0);
3197
- const indexRef = useRef(0);
3746
+ const indexRef = useRef2(0);
3198
3747
  const move = (delta) => {
3199
3748
  indexRef.current = Math.max(0, Math.min(indexRef.current + delta, options.length - 1));
3200
3749
  setIndex(indexRef.current);
@@ -3255,12 +3804,12 @@ function SelectPrompt({ title, options, onSelect, onCancel }) {
3255
3804
  }
3256
3805
 
3257
3806
  // src/cli/components/TextField.tsx
3258
- import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
3259
- import { useRef as useRef3, useState as useState3 } from "react";
3807
+ import { Box as Box3, Text as Text3 } from "ink";
3808
+ import { useRef as useRef4, useState as useState3 } from "react";
3260
3809
 
3261
3810
  // src/cli/components/InputBox.tsx
3262
- import { Box as Box2, Text as Text2, useInput as useInput2, useStdout } from "ink";
3263
- import { useEffect, useRef as useRef2, useState as useState2 } from "react";
3811
+ import { Box as Box2, Text as Text2, useStdout } from "ink";
3812
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
3264
3813
 
3265
3814
  // src/cli/path-suggest.ts
3266
3815
  import { readdir as readdir2 } from "fs/promises";
@@ -3410,25 +3959,25 @@ function InputBox({
3410
3959
  const [highlight, setHighlight] = useState2(0);
3411
3960
  const [suggestionsDismissed, setSuggestionsDismissed] = useState2(false);
3412
3961
  const [pathSuggestions, setPathSuggestions] = useState2([]);
3413
- const historyIndexRef = useRef2(null);
3414
- const draftRef = useRef2("");
3415
- const refillSeqRef = useRef2(0);
3962
+ const historyIndexRef = useRef3(null);
3963
+ const draftRef = useRef3("");
3964
+ const refillSeqRef = useRef3(0);
3416
3965
  const [search, setSearch] = useState2(null);
3417
- const searchSavedRef = useRef2(null);
3418
- const pastesRef = useRef2(/* @__PURE__ */ new Map());
3419
- const pasteSeqRef = useRef2(0);
3420
- const bracketRef = useRef2({ active: false, buffer: "", pending: "" });
3421
- const lastPasteRef = useRef2(
3966
+ const searchSavedRef = useRef3(null);
3967
+ const pastesRef = useRef3(/* @__PURE__ */ new Map());
3968
+ const pasteSeqRef = useRef3(0);
3969
+ const bracketRef = useRef3({ active: false, buffer: "", pending: "" });
3970
+ const lastPasteRef = useRef3(
3422
3971
  null
3423
3972
  );
3424
- const valueRef = useRef2(value);
3425
- const cursorRef = useRef2(cursor);
3426
- useEffect(() => {
3973
+ const valueRef = useRef3(value);
3974
+ const cursorRef = useRef3(cursor);
3975
+ useEffect2(() => {
3427
3976
  valueRef.current = value;
3428
3977
  cursorRef.current = cursor;
3429
3978
  });
3430
3979
  const { stdout } = useStdout();
3431
- useEffect(() => {
3980
+ useEffect2(() => {
3432
3981
  if (!stdout?.isTTY) return;
3433
3982
  stdout.write("\x1B[?2004h");
3434
3983
  return () => {
@@ -3517,7 +4066,7 @@ function InputBox({
3517
4066
  }
3518
4067
  }
3519
4068
  };
3520
- useEffect(() => {
4069
+ useEffect2(() => {
3521
4070
  if (refill && refill.seq !== refillSeqRef.current) {
3522
4071
  refillSeqRef.current = refill.seq;
3523
4072
  edit(refill.text, refill.text.length);
@@ -3525,7 +4074,7 @@ function InputBox({
3525
4074
  });
3526
4075
  const suggestionsEnabled = !isStreaming && !disabled && !suggestionsDismissed && !search;
3527
4076
  const suggestions = suggestionsEnabled && commands ? filterCommands(value, commands) : [];
3528
- useEffect(() => {
4077
+ useEffect2(() => {
3529
4078
  if (!suggestionsEnabled || !cwd) {
3530
4079
  setPathSuggestions([]);
3531
4080
  return;
@@ -3563,7 +4112,7 @@ function InputBox({
3563
4112
  token.start + replacement.length
3564
4113
  );
3565
4114
  };
3566
- useInput2((input, key) => {
4115
+ useInput((input, key) => {
3567
4116
  if (!disabled) {
3568
4117
  const bracket = bracketRef.current;
3569
4118
  const feedable = input.length > 0 && !key.ctrl && !key.meta;
@@ -3796,9 +4345,9 @@ ${value.slice(cursor)}`, cursor + 1);
3796
4345
  const after = value.slice(cursor + 1);
3797
4346
  const cursorChar = at === "\n" || at === void 0 || at === PASTE_DELIM ? " " : at;
3798
4347
  const tail = (at === "\n" ? "\n" : "") + stylePasteTokens(after);
3799
- const styled = search ? `\x1B[36m(reverse-search) \x1B[39m'${search.query}': ${search.match === null ? "\x1B[2mno match\x1B[22m\x1B[7m \x1B[27m" : `${history[search.match]}\x1B[7m \x1B[27m`}` : `\x1B[36m> \x1B[39m${stylePasteTokens(before)}\x1B[7m${cursorChar}\x1B[27m${tail}`;
4348
+ const styled2 = search ? `\x1B[36m(reverse-search) \x1B[39m'${search.query}': ${search.match === null ? "\x1B[2mno match\x1B[22m\x1B[7m \x1B[27m" : `${history[search.match]}\x1B[7m \x1B[27m`}` : `\x1B[36m\u276F \x1B[39m${stylePasteTokens(before)}\x1B[7m${cursorChar}\x1B[27m${tail}`;
3800
4349
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
3801
- /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { children: styled }) }),
4350
+ /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { children: styled2 }) }),
3802
4351
  suggestions.length > 0 && /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", paddingLeft: 2, children: suggestions.map((cmd, index) => {
3803
4352
  const label = cmd.usage ?? `/${cmd.name}`;
3804
4353
  return index === activeIndex ? /* @__PURE__ */ jsx2(Text2, { bold: true, inverse: true, children: `${label} - ${cmd.description}` }, cmd.name) : /* @__PURE__ */ jsxs2(Text2, { children: [
@@ -3827,8 +4376,8 @@ function TextField({
3827
4376
  onCancel
3828
4377
  }) {
3829
4378
  const [value, setValue] = useState3(initialValue);
3830
- const valueRef = useRef3(initialValue);
3831
- const bracketRef = useRef3({ active: false, buffer: "", pending: "" });
4379
+ const valueRef = useRef4(initialValue);
4380
+ const bracketRef = useRef4({ active: false, buffer: "", pending: "" });
3832
4381
  const flushBracket = () => {
3833
4382
  const bracket = bracketRef.current;
3834
4383
  const content = bracket.buffer.replace(/[\r\n]+/g, "");
@@ -3840,7 +4389,7 @@ function TextField({
3840
4389
  setValue(valueRef.current);
3841
4390
  }
3842
4391
  };
3843
- useInput3((input, key) => {
4392
+ useInput((input, key) => {
3844
4393
  if (key.escape) {
3845
4394
  onCancel();
3846
4395
  return;
@@ -3879,7 +4428,7 @@ function TextField({
3879
4428
  // src/cli/components/ConnectWizard.tsx
3880
4429
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3881
4430
  function YN({ question, onAnswer }) {
3882
- useInput4((input, key) => {
4431
+ useInput((input, key) => {
3883
4432
  const ch = input.toLowerCase();
3884
4433
  if (ch === "y") onAnswer(true);
3885
4434
  else if (ch === "n" || key.escape) onAnswer(false);
@@ -3909,8 +4458,8 @@ function ConnectWizard({
3909
4458
  const [error, setError] = useState4(null);
3910
4459
  const [busy, setBusy] = useState4(false);
3911
4460
  const [notes, setNotes] = useState4([]);
3912
- const busyRef = useRef4(false);
3913
- const notesRef = useRef4([]);
4461
+ const busyRef = useRef5(false);
4462
+ const notesRef = useRef5([]);
3914
4463
  const pushNote = (note) => {
3915
4464
  notesRef.current = [...notesRef.current, note];
3916
4465
  setNotes(notesRef.current);
@@ -4093,10 +4642,10 @@ function ConnectWizard({
4093
4642
  }
4094
4643
 
4095
4644
  // src/cli/components/ExitPlanPrompt.tsx
4096
- import { Box as Box5, Text as Text5, useInput as useInput5 } from "ink";
4645
+ import { Box as Box5, Text as Text5 } from "ink";
4097
4646
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
4098
4647
  function ExitPlanPrompt({ onDecision }) {
4099
- useInput5((input, key) => {
4648
+ useInput((input, key) => {
4100
4649
  const ch = input.toLowerCase();
4101
4650
  if (ch === "y") onDecision("yes");
4102
4651
  else if (ch === "n" || key.escape) onDecision("no");
@@ -4109,7 +4658,7 @@ function ExitPlanPrompt({ onDecision }) {
4109
4658
  }
4110
4659
 
4111
4660
  // src/cli/components/MessageList.tsx
4112
- import { Box as Box6, Static, Text as Text7 } from "ink";
4661
+ import { Box as Box6, Static, Text as Text7, useStdout as useStdout2 } from "ink";
4113
4662
  import { memo } from "react";
4114
4663
 
4115
4664
  // src/cli/components/DiffLines.tsx
@@ -4141,26 +4690,44 @@ function DiffLines({ lines }) {
4141
4690
  // src/cli/components/MessageList.tsx
4142
4691
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
4143
4692
  var roleStyles = {
4144
- user: { label: "you", color: "cyan" },
4145
- assistant: { label: "star", color: "green" },
4146
- system: { label: "system", color: "yellow" },
4147
- tool: { label: "tool", color: "magenta" }
4693
+ assistant: { label: "\u2726 star", color: "green" },
4694
+ system: { label: "\u25CF system", color: "yellow" }
4148
4695
  };
4149
4696
  var interruptedMarker = (text) => /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: text.length > 0 ? " [interrupted]" : "[interrupted]" });
4150
4697
  var MessageList = memo(function MessageList2({ messages }) {
4698
+ const { stdout } = useStdout2();
4699
+ const termWidth = stdout.columns ?? 80;
4151
4700
  return /* @__PURE__ */ jsx7(Static, { items: messages, children: (message) => {
4152
4701
  const marginBottom = message.tight ? 0 : 1;
4153
4702
  if (message.role === "assistant-cont") {
4154
4703
  return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginBottom, children: /* @__PURE__ */ jsxs7(Text7, { color: "green", children: [
4155
- message.text,
4704
+ renderMarkdown(message.text, "32", termWidth),
4156
4705
  message.interrupted && interruptedMarker(message.text)
4157
4706
  ] }) }, message.id);
4158
4707
  }
4708
+ if (message.role === "user") {
4709
+ return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginBottom, children: [
4710
+ /* @__PURE__ */ jsxs7(Box6, { children: [
4711
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "\u276F " }),
4712
+ /* @__PURE__ */ jsxs7(Text7, { color: "cyan", children: [
4713
+ message.text,
4714
+ message.interrupted && interruptedMarker(message.text)
4715
+ ] })
4716
+ ] }),
4717
+ message.note && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
4718
+ " ",
4719
+ message.note
4720
+ ] })
4721
+ ] }, message.id);
4722
+ }
4723
+ if (message.role === "tool") {
4724
+ return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", marginBottom, children: /* @__PURE__ */ jsx7(Text7, { color: "magenta", children: message.text }) }, message.id);
4725
+ }
4159
4726
  const style = roleStyles[message.role];
4160
4727
  return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", marginBottom, children: [
4161
4728
  /* @__PURE__ */ jsx7(Text7, { bold: true, color: style.color, children: style.label }),
4162
4729
  /* @__PURE__ */ jsxs7(Text7, { color: style.color, children: [
4163
- message.text,
4730
+ message.role === "assistant" ? renderMarkdown(message.text, "32", termWidth) : message.text,
4164
4731
  message.interrupted && interruptedMarker(message.text)
4165
4732
  ] }),
4166
4733
  message.diff && /* @__PURE__ */ jsx7(DiffLines, { lines: message.diff }),
@@ -4170,7 +4737,7 @@ var MessageList = memo(function MessageList2({ messages }) {
4170
4737
  });
4171
4738
 
4172
4739
  // src/cli/components/PermissionPrompt.tsx
4173
- import { Box as Box7, Text as Text8, useInput as useInput6 } from "ink";
4740
+ import { Box as Box7, Text as Text8 } from "ink";
4174
4741
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
4175
4742
  var BASH_COMMAND_MAX = 1e3;
4176
4743
  function middleTruncate(text, max) {
@@ -4184,7 +4751,7 @@ function bashCommand(args) {
4184
4751
  return typeof command === "string" ? command : null;
4185
4752
  }
4186
4753
  function PermissionPrompt({ request, preview, onDecision }) {
4187
- useInput6((input) => {
4754
+ useInput((input) => {
4188
4755
  const ch = input.toLowerCase();
4189
4756
  if (ch === "y") onDecision("yes");
4190
4757
  else if (ch === "n") onDecision("no");
@@ -4211,7 +4778,7 @@ function PermissionPrompt({ request, preview, onDecision }) {
4211
4778
  }
4212
4779
 
4213
4780
  // src/cli/components/RewindConfirmPrompt.tsx
4214
- import { Box as Box8, Text as Text9, useInput as useInput7 } from "ink";
4781
+ import { Box as Box8, Text as Text9 } from "ink";
4215
4782
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
4216
4783
  function RewindConfirmPrompt({
4217
4784
  summary,
@@ -4220,7 +4787,7 @@ function RewindConfirmPrompt({
4220
4787
  diffs,
4221
4788
  onDecision
4222
4789
  }) {
4223
- useInput7((input, key) => {
4790
+ useInput((input, key) => {
4224
4791
  const ch = input.toLowerCase();
4225
4792
  if (ch === "y") onDecision("yes");
4226
4793
  else if (ch === "n" || key.escape) onDecision("no");
@@ -4241,8 +4808,74 @@ function RewindConfirmPrompt({
4241
4808
  }
4242
4809
 
4243
4810
  // src/cli/components/StatusBar.tsx
4244
- import { Box as Box9, Text as Text10, useStdout as useStdout2 } from "ink";
4811
+ import { Box as Box9, Text as Text10, useStdout as useStdout3 } from "ink";
4245
4812
  import { memo as memo2 } from "react";
4813
+
4814
+ // src/cli/icons.ts
4815
+ var EMOJI_TERM_PROGRAMS = /* @__PURE__ */ new Set([
4816
+ "vscode",
4817
+ "WezTerm",
4818
+ "iTerm.app",
4819
+ "Apple_Terminal",
4820
+ "ghostty",
4821
+ "kitty",
4822
+ "Hyper",
4823
+ "WarpTerminal"
4824
+ ]);
4825
+ function resolveIconMode(env = process.env) {
4826
+ const override = env.STAR_ICONS;
4827
+ if (override === "emoji" || override === "plain") return override;
4828
+ if (env.WT_SESSION) return "emoji";
4829
+ if (EMOJI_TERM_PROGRAMS.has(env.TERM_PROGRAM ?? "")) return "emoji";
4830
+ const term = env.TERM ?? "";
4831
+ if (term.includes("kitty") || term.includes("alacritty")) return "emoji";
4832
+ return "plain";
4833
+ }
4834
+ var EMOJI_TOOL_ICONS = {
4835
+ bash: "\u{1F4BB}",
4836
+ read_file: "\u{1F4C4}",
4837
+ write_file: "\u{1F4DD}",
4838
+ edit_file: "\u270F\uFE0F",
4839
+ glob: "\u{1F4C2}",
4840
+ grep: "\u{1F50D}",
4841
+ web_search: "\u{1F50E}",
4842
+ web_fetch: "\u{1F310}",
4843
+ todo_write: "\u{1F4CB}",
4844
+ todo_read: "\u{1F4CB}",
4845
+ task_list: "\u{1F9F5}",
4846
+ task_output: "\u{1F9F5}",
4847
+ task_kill: "\u{1F6D1}",
4848
+ subagent: "\u{1F916}",
4849
+ skill: "\u2728",
4850
+ remember: "\u{1F9E0}"
4851
+ };
4852
+ var PLAIN_TOOL_ICONS = {
4853
+ bash: "$",
4854
+ read_file: "\u2261",
4855
+ write_file: "+",
4856
+ edit_file: "\u270E",
4857
+ glob: "*",
4858
+ grep: "/",
4859
+ web_search: "?",
4860
+ web_fetch: "\u21D3",
4861
+ todo_write: "\u2023",
4862
+ todo_read: "\u2023",
4863
+ task_list: "\xB6",
4864
+ task_output: "\xB6",
4865
+ task_kill: "\xD7",
4866
+ subagent: "\u25C9",
4867
+ skill: "\u2727",
4868
+ remember: "\u203B"
4869
+ };
4870
+ var iconMode = resolveIconMode();
4871
+ function toolIcon(name) {
4872
+ const map = iconMode === "emoji" ? EMOJI_TOOL_ICONS : PLAIN_TOOL_ICONS;
4873
+ return map[name] ?? (iconMode === "emoji" ? "\u{1F527}" : "\u25B8");
4874
+ }
4875
+ var thinkingIcon = iconMode === "emoji" ? "\u{1F4AD}" : "\u2727";
4876
+ var folderIcon = iconMode === "emoji" ? "\u{1F4C1} " : "";
4877
+
4878
+ // src/cli/components/StatusBar.tsx
4246
4879
  import { jsxs as jsxs10 } from "react/jsx-runtime";
4247
4880
  var BG_LABEL_MAX = 40;
4248
4881
  var WIDE_COLUMNS = 120;
@@ -4262,12 +4895,13 @@ var StatusBar = memo2(function StatusBar2({
4262
4895
  sessionCostUsd,
4263
4896
  backgroundTasks = []
4264
4897
  }) {
4265
- const { stdout } = useStdout2();
4898
+ const { stdout } = useStdout3();
4266
4899
  const wide = (stdout?.columns ?? 80) >= WIDE_COLUMNS;
4267
4900
  const joined = backgroundTasks.map((label) => toTerminalSafe(label)).join(", ");
4268
4901
  const labels = joined.length > BG_LABEL_MAX ? `${joined.slice(0, BG_LABEL_MAX)}\u2026` : joined;
4269
4902
  return /* @__PURE__ */ jsxs10(Box9, { justifyContent: "space-between", children: [
4270
4903
  /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
4904
+ folderIcon,
4271
4905
  cwdLabel(cwd, wide),
4272
4906
  gitBranch ? ` [${gitBranch}]` : ""
4273
4907
  ] }),
@@ -4279,7 +4913,7 @@ var StatusBar = memo2(function StatusBar2({
4279
4913
  ")"
4280
4914
  ] }),
4281
4915
  /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
4282
- "model: ",
4916
+ "\u2726 ",
4283
4917
  model
4284
4918
  ] }),
4285
4919
  contextPercent != null && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
@@ -4307,23 +4941,24 @@ var StatusBar = memo2(function StatusBar2({
4307
4941
  });
4308
4942
 
4309
4943
  // src/cli/components/StreamingMessage.tsx
4310
- import { Box as Box10, Text as Text11 } from "ink";
4944
+ import { Box as Box10, Text as Text11, useStdout as useStdout4 } from "ink";
4311
4945
  import { memo as memo3 } from "react";
4312
4946
  import { jsx as jsx10, jsxs as jsxs11 } from "react/jsx-runtime";
4313
4947
  var StreamingMessage = memo3(function StreamingMessage2({
4314
4948
  text,
4315
4949
  continuation
4316
4950
  }) {
4951
+ const { stdout } = useStdout4();
4317
4952
  return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "column", marginBottom: 1, children: [
4318
- !continuation && /* @__PURE__ */ jsx10(Text11, { bold: true, color: "green", children: "star" }),
4319
- /* @__PURE__ */ jsx10(Text11, { color: "green", children: text })
4953
+ !continuation && /* @__PURE__ */ jsx10(Text11, { bold: true, color: "green", children: "\u2726 star" }),
4954
+ /* @__PURE__ */ jsx10(Text11, { color: "green", children: renderMarkdown(text, "32", stdout.columns ?? 80) })
4320
4955
  ] });
4321
4956
  });
4322
4957
 
4323
4958
  // src/cli/components/ThinkingIndicator.tsx
4324
4959
  import { Box as Box11, Text as Text12 } from "ink";
4325
4960
  import { jsxs as jsxs12 } from "react/jsx-runtime";
4326
- var SPINNER_FRAMES = ["-", "\\", "|", "/"];
4961
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4327
4962
  var REASONING_TAIL_LENGTH = 200;
4328
4963
  var THOUGHT_SUMMARY_LENGTH = 100;
4329
4964
  function truncateTail(text, max) {
@@ -4338,14 +4973,15 @@ function ThinkingIndicator({
4338
4973
  }) {
4339
4974
  const tail = reasoning ? truncateTail(reasoning, REASONING_TAIL_LENGTH) : "";
4340
4975
  const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
4976
+ const label = activity ?? `${thinkingIcon} star is thinking\u2026`;
4341
4977
  return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", children: [
4342
4978
  /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
4343
4979
  spinner,
4344
4980
  " ",
4345
- activity ?? "star is thinking\u2026"
4981
+ label
4346
4982
  ] }),
4347
4983
  tail !== "" && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
4348
- " ",
4984
+ " ",
4349
4985
  tail
4350
4986
  ] })
4351
4987
  ] });
@@ -4355,9 +4991,9 @@ function ThinkingIndicator({
4355
4991
  import { Box as Box12, Text as Text13 } from "ink";
4356
4992
  import { jsxs as jsxs13 } from "react/jsx-runtime";
4357
4993
  var SYMBOLS = {
4358
- pending: "[ ]",
4359
- in_progress: "[~]",
4360
- done: "[x]"
4994
+ pending: "\u25CB",
4995
+ in_progress: "\u25D0",
4996
+ done: "\u25CF"
4361
4997
  };
4362
4998
  var MAX_VISIBLE_TODOS = 10;
4363
4999
  function visibleTodoWindow(todos, max = MAX_VISIBLE_TODOS) {
@@ -4384,6 +5020,7 @@ function TodoPanel({ todos }) {
4384
5020
  Text13,
4385
5021
  {
4386
5022
  dimColor: todo.status === "done",
5023
+ strikethrough: todo.status === "done",
4387
5024
  color: todo.status === "in_progress" ? "cyan" : void 0,
4388
5025
  children: [
4389
5026
  SYMBOLS[todo.status],
@@ -4403,7 +5040,7 @@ function TodoPanel({ todos }) {
4403
5040
 
4404
5041
  // src/cli/components/ToolCallCard.tsx
4405
5042
  function formatToolCard(card) {
4406
- const header = `${card.name} ${card.argsSummary}${card.isError ? " [error]" : ""}`;
5043
+ const header = `${toolIcon(card.name)} ${card.name} ${card.argsSummary}${card.isError ? " [error]" : ""}`;
4407
5044
  if (card.result === void 0) return header;
4408
5045
  const preview = previewLines(toTerminalSafe(card.result), 10);
4409
5046
  return `${header}
@@ -4631,6 +5268,7 @@ function formatUsage(usage) {
4631
5268
  var SESSION_PICKER_CAP = 20;
4632
5269
  var SESSION_PREVIEW_MAX = 60;
4633
5270
  var MAX_VISIBLE_QUEUED = 5;
5271
+ var REASONING_EFFORT_LEVELS = ["minimal", "low", "medium", "high", "max"];
4634
5272
  function Repl({
4635
5273
  backend,
4636
5274
  model,
@@ -4662,47 +5300,47 @@ function Repl({
4662
5300
  const [pendingRewind, setPendingRewindState] = useState5(null);
4663
5301
  const [picker, setPickerState] = useState5(null);
4664
5302
  const [pendingConnect, setPendingConnectState] = useState5(null);
4665
- const backendRef = useRef5(backend);
4666
- const nextIdRef = useRef5(initialDisplay.length);
4667
- const abortRef = useRef5(null);
4668
- const streamedRef = useRef5("");
4669
- const lastCommittedLenRef = useRef5(0);
4670
- const thinkingRef = useRef5(false);
4671
- const reasoningRef = useRef5("");
4672
- const tickerStopRef = useRef5(null);
4673
- const flushedRef = useRef5({ streamed: "", reasoning: "" });
4674
- const toolCardsRef = useRef5(/* @__PURE__ */ new Map());
4675
- const pendingRef = useRef5(null);
4676
- const permissionChainRef = useRef5(Promise.resolve());
4677
- const alwaysAllowedRef = useRef5(/* @__PURE__ */ new Set());
4678
- const prevModeRef = useRef5(null);
4679
- const planApprovalRef = useRef5(false);
4680
- const pendingRewindRef = useRef5(null);
4681
- const pickerRef = useRef5(null);
4682
- const pendingConnectRef = useRef5(null);
4683
- const modelNameRef = useRef5(model);
4684
- const sessionStoreRef = useRef5(sessionStore);
4685
- const configRef = useRef5(config);
4686
- useEffect2(() => {
5303
+ const backendRef = useRef6(backend);
5304
+ const nextIdRef = useRef6(initialDisplay.length);
5305
+ const abortRef = useRef6(null);
5306
+ const streamedRef = useRef6("");
5307
+ const lastCommittedLenRef = useRef6(0);
5308
+ const thinkingRef = useRef6(false);
5309
+ const reasoningRef = useRef6("");
5310
+ const tickerStopRef = useRef6(null);
5311
+ const flushedRef = useRef6({ streamed: "", reasoning: "" });
5312
+ const toolCardsRef = useRef6(/* @__PURE__ */ new Map());
5313
+ const pendingRef = useRef6(null);
5314
+ const permissionChainRef = useRef6(Promise.resolve());
5315
+ const alwaysAllowedRef = useRef6(/* @__PURE__ */ new Set());
5316
+ const prevModeRef = useRef6(null);
5317
+ const planApprovalRef = useRef6(false);
5318
+ const pendingRewindRef = useRef6(null);
5319
+ const pickerRef = useRef6(null);
5320
+ const pendingConnectRef = useRef6(null);
5321
+ const modelNameRef = useRef6(model);
5322
+ const sessionStoreRef = useRef6(sessionStore);
5323
+ const configRef = useRef6(config);
5324
+ useEffect3(() => {
4687
5325
  configRef.current = config;
4688
5326
  }, [config]);
4689
- const usageRef = useRef5(initialUsage ? { ...initialUsage } : emptyUsage());
4690
- const turnChunksRef = useRef5(0);
4691
- const messagesRef = useRef5(initialDisplay);
5327
+ const usageRef = useRef6(initialUsage ? { ...initialUsage } : emptyUsage());
5328
+ const turnChunksRef = useRef6(0);
5329
+ const messagesRef = useRef6(initialDisplay);
4692
5330
  const [queueItems, setQueueItems] = useState5([]);
4693
- const queueRef = useRef5(new PromptQueue());
5331
+ const queueRef = useRef6(new PromptQueue());
4694
5332
  const [pendingImages, setPendingImages] = useState5([]);
4695
- const pendingImagesRef = useRef5([]);
4696
- const imageSeqRef = useRef5(1);
5333
+ const pendingImagesRef = useRef6([]);
5334
+ const imageSeqRef = useRef6(1);
4697
5335
  const [inputRefill, setInputRefill] = useState5();
4698
- const refillSeqRef = useRef5(0);
4699
- const lastEscRef = useRef5(null);
5336
+ const refillSeqRef = useRef6(0);
5337
+ const lastEscRef = useRef6(null);
4700
5338
  const [gitBranch, setGitBranch] = useState5(null);
4701
5339
  const [contextPercent, setContextPercent] = useState5(null);
4702
5340
  const [initialHistory] = useState5(() => loadHistory());
4703
- const budgetWarnedRef = useRef5(false);
4704
- const budgetExceededRef = useRef5(false);
4705
- const runStreamRef = useRef5(
5341
+ const budgetWarnedRef = useRef6(false);
5342
+ const budgetExceededRef = useRef6(false);
5343
+ const runStreamRef = useRef6(
4706
5344
  null
4707
5345
  );
4708
5346
  const pushMessage = useCallback(
@@ -4882,7 +5520,7 @@ function Repl({
4882
5520
  },
4883
5521
  [setPendingPermission, cwd, pushMessage]
4884
5522
  );
4885
- useEffect2(() => {
5523
+ useEffect3(() => {
4886
5524
  attachConfirmHandler(backendRef.current);
4887
5525
  return () => {
4888
5526
  tickerStopRef.current?.();
@@ -4890,19 +5528,19 @@ function Repl({
4890
5528
  pendingRef.current?.resolve(false);
4891
5529
  };
4892
5530
  }, [attachConfirmHandler]);
4893
- useEffect2(() => {
5531
+ useEffect3(() => {
4894
5532
  if (process.env.STAR_NO_UPDATE_CHECK === "1") return;
4895
5533
  void checkForUpdate(VERSION).then((message) => {
4896
5534
  if (message) pushMessage("system", message);
4897
5535
  });
4898
5536
  }, [pushMessage]);
4899
5537
  const resumedAtStart = initialMessages !== void 0;
4900
- useEffect2(() => {
5538
+ useEffect3(() => {
4901
5539
  if (!resumedAtStart) return;
4902
5540
  loadTodos(cwd).then(setTodos).catch(() => {
4903
5541
  });
4904
5542
  }, [cwd, resumedAtStart]);
4905
- useEffect2(() => {
5543
+ useEffect3(() => {
4906
5544
  const onUpdate = (task) => {
4907
5545
  setBgLabels(runningTaskLabels());
4908
5546
  pushMessage(
@@ -4985,7 +5623,7 @@ function Repl({
4985
5623
  }
4986
5624
  exit();
4987
5625
  }, [exit, pushMessage]);
4988
- useInput8((_input, key) => {
5626
+ useInput((_input, key) => {
4989
5627
  if (key.escape) {
4990
5628
  const busy = abortRef.current !== null || pendingRef.current !== null || planApprovalRef.current || pendingRewindRef.current !== null || pendingConnectRef.current !== null || pickerRef.current !== null;
4991
5629
  if (busy) {
@@ -5044,7 +5682,8 @@ function Repl({
5044
5682
  cwd,
5045
5683
  system: SYSTEM_PROMPT,
5046
5684
  sessionStore: sessionStoreRef.current,
5047
- contextMaxTokens: contextWindowTokens(config, name)
5685
+ contextMaxTokens: contextWindowTokens(config, name),
5686
+ providerMetadata: reasoningEffortMetadata(config, name)
5048
5687
  });
5049
5688
  const prev = backendRef.current;
5050
5689
  if (prev instanceof AgentLoop) {
@@ -5061,6 +5700,42 @@ function Repl({
5061
5700
  },
5062
5701
  [config, cwd, attachConfirmHandler]
5063
5702
  );
5703
+ const pickReasoningEffort = useCallback(
5704
+ async (name) => {
5705
+ const modelConfig = config.models.find((m) => m.name === name);
5706
+ const current = modelConfig?.reasoningEffort;
5707
+ const levels = current && !REASONING_EFFORT_LEVELS.includes(current) ? [...REASONING_EFFORT_LEVELS, current] : REASONING_EFFORT_LEVELS;
5708
+ const options = [
5709
+ {
5710
+ value: "",
5711
+ label: "default",
5712
+ description: "send no effort field \u2014 the provider's server-side default applies",
5713
+ hint: current === void 0 ? "current" : void 0
5714
+ },
5715
+ ...levels.map((level) => ({
5716
+ value: level,
5717
+ label: level,
5718
+ description: "only works if the model/provider supports this level",
5719
+ hint: level === current ? "current" : void 0
5720
+ }))
5721
+ ];
5722
+ const picked = await showPicker(`Reasoning effort for "${name}"`, options);
5723
+ if (picked === null || picked === "" && current === void 0 || picked === current) {
5724
+ return `Reasoning effort unchanged${current ? ` \u2014 still "${current}"` : " \u2014 server default"}.`;
5725
+ }
5726
+ const effort = picked === "" ? void 0 : picked;
5727
+ if (modelConfig) modelConfig.reasoningEffort = effort;
5728
+ const backend2 = backendRef.current;
5729
+ if (backend2 instanceof AgentLoop) {
5730
+ backend2.setProviderMetadata(reasoningEffortMetadata(config, name));
5731
+ }
5732
+ const saved = await saveReasoningEffort(name, effort);
5733
+ const applied = effort === void 0 ? "server default" : `"${effort}"`;
5734
+ const suffix = saved ? "Saved to config." : `This session only \u2014 "${name}" has no [[models]] block in the global config file; add reasoningEffort there to persist it.`;
5735
+ return `Reasoning effort for "${name}" set to ${applied}. ${suffix}`;
5736
+ },
5737
+ [config, showPicker]
5738
+ );
5064
5739
  const handleConnectSetDefault = useCallback(
5065
5740
  async (modelName2) => {
5066
5741
  saveDefaultModel(modelName2);
@@ -5170,7 +5845,7 @@ function Repl({
5170
5845
  setThinking(false);
5171
5846
  if (reasoningRef.current.length > 0) {
5172
5847
  setThoughtSummary(
5173
- `thought: ${truncateTail(reasoningRef.current, THOUGHT_SUMMARY_LENGTH)}`
5848
+ `${thinkingIcon} thought: ${truncateTail(reasoningRef.current, THOUGHT_SUMMARY_LENGTH)}`
5174
5849
  );
5175
5850
  }
5176
5851
  }
@@ -5188,7 +5863,9 @@ function Repl({
5188
5863
  const list = parseTodoArgs(event.args);
5189
5864
  if (list) setTodos(list);
5190
5865
  }
5191
- setActivity(`running ${event.name}: ${summarizeArgs(event.args, 60)}`);
5866
+ setActivity(
5867
+ `${toolIcon(event.name)} running ${event.name}: ${summarizeArgs(event.args, 60)}`
5868
+ );
5192
5869
  } else if (event.type === "tool-result") {
5193
5870
  const card = toolCardsRef.current.get(event.id) ?? {
5194
5871
  id: event.id,
@@ -5337,7 +6014,7 @@ function Repl({
5337
6014
  sessionCostUsd
5338
6015
  ]
5339
6016
  );
5340
- useEffect2(() => {
6017
+ useEffect3(() => {
5341
6018
  runStreamRef.current = runStream;
5342
6019
  }, [runStream]);
5343
6020
  const handlePlanDecision = useCallback(
@@ -5391,7 +6068,7 @@ function Repl({
5391
6068
  const models = listModels(config);
5392
6069
  if (models.length === 0) return "No models configured.";
5393
6070
  const lines = models.map(
5394
- (m) => `${m.name === modelNameRef.current ? "*" : " "} ${m.name} (${m.provider}/${m.model})`
6071
+ (m) => `${m.name === modelNameRef.current ? "*" : " "} ${m.name} (${m.provider}/${m.model})${m.reasoningEffort ? ` [effort: ${m.reasoningEffort}]` : ""}`
5395
6072
  );
5396
6073
  return `Models (* = current):
5397
6074
  ${lines.join("\n")}`;
@@ -5410,6 +6087,7 @@ ${lines.join("\n")}`;
5410
6087
  if (models.length === 0) return "No models configured.";
5411
6088
  const options = models.map((m) => {
5412
6089
  const parts = [`${m.provider}/${m.model}`, `${contextWindowTokens(config, m.name)} ctx`];
6090
+ if (m.reasoningEffort) parts.push(`effort: ${m.reasoningEffort}`);
5413
6091
  if (m.promptPrice !== void 0 && m.completionPrice !== void 0) {
5414
6092
  parts.push(`$${m.promptPrice}/$${m.completionPrice} per 1M tokens`);
5415
6093
  }
@@ -5421,10 +6099,17 @@ ${lines.join("\n")}`;
5421
6099
  };
5422
6100
  });
5423
6101
  const picked = await showPicker("Select a model", options);
5424
- if (!picked || picked === modelNameRef.current) {
5425
- return `Model unchanged \u2014 still "${modelNameRef.current}".`;
6102
+ if (!picked) return `Model unchanged \u2014 still "${modelNameRef.current}".`;
6103
+ const results = [];
6104
+ if (picked === modelNameRef.current) {
6105
+ results.push(`Model unchanged \u2014 still "${picked}".`);
6106
+ } else {
6107
+ const switched = await switchModel(picked);
6108
+ if (switched.startsWith("Failed to switch model:")) return switched;
6109
+ results.push(switched);
5426
6110
  }
5427
- return switchModel(picked);
6111
+ results.push(await pickReasoningEffort(picked));
6112
+ return results.join("\n");
5428
6113
  },
5429
6114
  pickPermissionMode: async () => {
5430
6115
  const current = config.permissionMode;
@@ -5762,6 +6447,7 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5762
6447
  config,
5763
6448
  cwd,
5764
6449
  switchModel,
6450
+ pickReasoningEffort,
5765
6451
  resume,
5766
6452
  runStream,
5767
6453
  applyMessages,
@@ -5865,7 +6551,7 @@ ${plan.affected.length} file change(s) will be reverted, ${messageCount} message
5865
6551
  })),
5866
6552
  [registry]
5867
6553
  );
5868
- useEffect2(() => {
6554
+ useEffect3(() => {
5869
6555
  setGitBranch(getGitSummary(cwd)?.branch ?? null);
5870
6556
  const current = backendRef.current;
5871
6557
  if (current instanceof AgentLoop) {
@@ -6081,7 +6767,8 @@ async function createLoop(config, modelName, cwd, sessionStore) {
6081
6767
  cwd,
6082
6768
  system: SYSTEM_PROMPT,
6083
6769
  sessionStore,
6084
- contextMaxTokens: contextWindowTokens(config, modelName)
6770
+ contextMaxTokens: contextWindowTokens(config, modelName),
6771
+ providerMetadata: reasoningEffortMetadata(config, modelName)
6085
6772
  });
6086
6773
  }
6087
6774
  async function printMode(loop, prompt, cwd, json, imagePaths, sessionStore) {