clixad 0.0.1-beta.6 → 0.0.1-beta.7

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.
Files changed (2) hide show
  1. package/dist/clixad.mjs +192 -62
  2. package/package.json +1 -1
package/dist/clixad.mjs CHANGED
@@ -1504,6 +1504,10 @@ function centered(rendered, visibleW, colW) {
1504
1504
  const lead = Math.max(0, Math.floor((colW - visibleW) / 2));
1505
1505
  return cell(" ".repeat(lead) + rendered, lead + visibleW);
1506
1506
  }
1507
+ function bannerTip(index) {
1508
+ const i = index ?? Math.floor(Math.random() * BANNER_TIPS.length);
1509
+ return BANNER_TIPS[(i % BANNER_TIPS.length + BANNER_TIPS.length) % BANNER_TIPS.length];
1510
+ }
1507
1511
  function earnedLabel(opts) {
1508
1512
  return `$${opts.earnedUsdToday.toFixed(2)}/$${opts.maxRewardUsd.toFixed(2)} today`;
1509
1513
  }
@@ -1515,14 +1519,19 @@ function welcomeBanner(opts) {
1515
1519
  const cwd = opts.cwd.replace(homedir5(), "~").replace(/\\/g, "/");
1516
1520
  const welcome = opts.name ? `Welcome back, ${truncate(opts.name, 18)}!` : "Welcome to Clixad!";
1517
1521
  const duck = duckLines();
1518
- const model = truncate(opts.model, leftW - 4);
1519
1522
  const home = truncate(cwd, leftW);
1523
+ const hint = bannerTip(opts.tipIndex);
1524
+ const hintDesc = truncate(hint.desc, Math.max(1, leftW - 3 - hint.key.length));
1520
1525
  const left = [
1521
1526
  centered(BOLD + TEXT + welcome + R2, welcome.length, leftW),
1522
1527
  cell("", 0),
1523
1528
  ...duck.map((d) => centered(d, DUCK_W, leftW)),
1524
1529
  cell("", 0),
1525
- centered(MANGO + "\u25C6 " + R2 + TEXT + model + R2, 2 + model.length, leftW),
1530
+ centered(
1531
+ MANGO + "\u25C6 " + R2 + BOLD + TEXT + hint.key + R2 + FAINT + " " + hintDesc + R2,
1532
+ 2 + hint.key.length + 1 + hintDesc.length,
1533
+ leftW
1534
+ ),
1526
1535
  centered(FAINT + home + R2, home.length, leftW)
1527
1536
  ];
1528
1537
  const tip = (k, d) => cell(MANGO + k + R2 + FAINT + " " + d + R2, k.length + 1 + d.length);
@@ -1553,10 +1562,11 @@ function welcomeBanner(opts) {
1553
1562
  }
1554
1563
  function compactBanner(opts) {
1555
1564
  const welcome = opts.name ? `Welcome back, ${opts.name}!` : "Welcome to Clixad!";
1565
+ const hint = bannerTip(opts.tipIndex);
1556
1566
  return duckLines().map((d) => " " + d).join("\n") + `
1557
1567
 
1558
1568
  ${BOLD}${TEXT}${welcome}${R2}
1559
- ${MANGO}\u25C6${R2} ${opts.model} ${FAINT}\xB7${R2} ${opts.balance.toLocaleString("en-US")} credits
1569
+ ${MANGO}\u25C6${R2} ${BOLD}${TEXT}${hint.key}${R2} ${FAINT}${hint.desc}${R2} ${FAINT}\xB7${R2} ${opts.balance.toLocaleString("en-US")} credits
1560
1570
  `;
1561
1571
  }
1562
1572
  function clearScreen() {
@@ -1568,7 +1578,7 @@ function clearScreen() {
1568
1578
  function visibleLength(s) {
1569
1579
  return s.replace(/\x1b\[[0-9;]*m/g, "").length;
1570
1580
  }
1571
- var R2, BOLD, fg, bg, MANGO, TEXT, MUTED, FAINT, PAL, DUCK, DUCK_W, cell, padTo, truncate, MIN_BANNER_WIDTH, RIGHT_MAX;
1581
+ var R2, BOLD, fg, bg, MANGO, TEXT, MUTED, FAINT, PAL, DUCK, DUCK_W, cell, padTo, truncate, BANNER_TIPS, MIN_BANNER_WIDTH, RIGHT_MAX;
1572
1582
  var init_banner = __esm({
1573
1583
  "src/banner.ts"() {
1574
1584
  "use strict";
@@ -1610,6 +1620,15 @@ var init_banner = __esm({
1610
1620
  cell = (text, w) => ({ text, w });
1611
1621
  padTo = (c2, width) => (c2?.text ?? "") + " ".repeat(Math.max(0, width - (c2?.w ?? 0)));
1612
1622
  truncate = (s, max) => s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1623
+ BANNER_TIPS = [
1624
+ { key: "/init", desc: "make a CLIXAD.md" },
1625
+ { key: "shift+tab", desc: "switch mode" },
1626
+ { key: "@file", desc: "add a file to context" },
1627
+ { key: "ctrl+o", desc: "expand tool output" },
1628
+ { key: "esc", desc: "stop the current turn" },
1629
+ { key: "/compact", desc: "free up context" },
1630
+ { key: "/earn", desc: "top up credits" }
1631
+ ];
1613
1632
  MIN_BANNER_WIDTH = 66;
1614
1633
  RIGHT_MAX = 34;
1615
1634
  }
@@ -2158,6 +2177,45 @@ import { jsx, jsxs } from "react/jsx-runtime";
2158
2177
  function lineCount(text, cols) {
2159
2178
  return text.split("\n").reduce((n, line2) => n + Math.max(1, Math.ceil(visibleLength(line2) / Math.max(1, cols))), 0);
2160
2179
  }
2180
+ function tailRows(text, cols, max) {
2181
+ if (max <= 0) return "";
2182
+ const width = Math.max(1, cols);
2183
+ const lines = text.split("\n");
2184
+ const kept = [];
2185
+ let used = 0;
2186
+ for (let i = lines.length - 1; i >= 0; i--) {
2187
+ const line2 = lines[i];
2188
+ const h = Math.max(1, Math.ceil(visibleLength(line2) / width));
2189
+ if (used + h <= max) {
2190
+ kept.unshift(line2);
2191
+ used += h;
2192
+ continue;
2193
+ }
2194
+ const room = max - used;
2195
+ if (room > 0) kept.unshift(line2.slice(-(room * width)));
2196
+ break;
2197
+ }
2198
+ return kept.join("\n");
2199
+ }
2200
+ function headRows(text, cols, max) {
2201
+ if (max <= 0) return "";
2202
+ const width = Math.max(1, cols);
2203
+ const lines = text.split("\n");
2204
+ const kept = [];
2205
+ let used = 0;
2206
+ for (const line2 of lines) {
2207
+ const h = Math.max(1, Math.ceil(visibleLength(line2) / width));
2208
+ if (used + h <= max) {
2209
+ kept.push(line2);
2210
+ used += h;
2211
+ continue;
2212
+ }
2213
+ const room = max - used;
2214
+ if (room > 0) kept.push(line2.slice(0, room * width));
2215
+ break;
2216
+ }
2217
+ return kept.join("\n");
2218
+ }
2161
2219
  function entryHeight(entry, cols) {
2162
2220
  const margin = entry.kind === "banner" ? 0 : 1;
2163
2221
  switch (entry.kind) {
@@ -2204,7 +2262,7 @@ function EntryView({ entry }) {
2204
2262
  ) });
2205
2263
  }
2206
2264
  }
2207
- var MANGO3, MANGO_BRIGHT;
2265
+ var MANGO3, MANGO_BRIGHT, SLATE, MODE_STYLE;
2208
2266
  var init_views = __esm({
2209
2267
  "src/tui/views.tsx"() {
2210
2268
  "use strict";
@@ -2212,6 +2270,12 @@ var init_views = __esm({
2212
2270
  init_markdown();
2213
2271
  MANGO3 = "#f5b841";
2214
2272
  MANGO_BRIGHT = "#ffcf6b";
2273
+ SLATE = "#8b98ae";
2274
+ MODE_STYLE = {
2275
+ normal: { color: SLATE, glyph: "\u23F5" },
2276
+ acceptEdits: { color: "#7ee787", glyph: "\u23F5\u23F5" },
2277
+ plan: { color: "#79c0ff", glyph: "\u23F8" }
2278
+ };
2215
2279
  }
2216
2280
  });
2217
2281
 
@@ -2289,13 +2353,34 @@ function App({ client, config, wallet, session, initialTask }) {
2289
2353
  const lastOutputRef = useRef("");
2290
2354
  const runningToolRef = useRef(null);
2291
2355
  const pendingTaskRef = useRef(null);
2356
+ const deltaBufRef = useRef("");
2357
+ const deltaTimerRef = useRef(null);
2292
2358
  const sponsorRef = useRef(sponsorSource());
2293
2359
  const sponsorIdxRef = useRef(0);
2294
2360
  const sponsorPrevRef = useRef(void 0);
2295
2361
  const tallyRef = useRef(createTally());
2362
+ const heightCacheRef = useRef(/* @__PURE__ */ new Map());
2296
2363
  const push = useCallback((e) => {
2297
2364
  setEntries((prev) => [...prev, { ...e, id: idRef.current++ }]);
2298
2365
  }, []);
2366
+ const flushDelta = useCallback(() => {
2367
+ if (deltaTimerRef.current) {
2368
+ clearTimeout(deltaTimerRef.current);
2369
+ deltaTimerRef.current = null;
2370
+ }
2371
+ const buffered = deltaBufRef.current;
2372
+ if (!buffered) return;
2373
+ deltaBufRef.current = "";
2374
+ setLive((l) => ({ ...l ?? { text: "" }, text: (l?.text ?? "") + buffered }));
2375
+ }, []);
2376
+ const dropDelta = useCallback(() => {
2377
+ if (deltaTimerRef.current) {
2378
+ clearTimeout(deltaTimerRef.current);
2379
+ deltaTimerRef.current = null;
2380
+ }
2381
+ deltaBufRef.current = "";
2382
+ }, []);
2383
+ useEffect(() => dropDelta, [dropDelta]);
2299
2384
  useEffect(() => {
2300
2385
  const files = contextRef.current.files;
2301
2386
  if (files.length) push({ kind: "notice", text: ` context: ${files.join(", ")}` });
@@ -2351,10 +2436,13 @@ function App({ client, config, wallet, session, initialTask }) {
2351
2436
  }, []);
2352
2437
  const handleEvent = useCallback(
2353
2438
  (event) => {
2439
+ if (event.type === "delta") {
2440
+ deltaBufRef.current += event.text;
2441
+ if (!deltaTimerRef.current) deltaTimerRef.current = setTimeout(flushDelta, DELTA_FLUSH_MS);
2442
+ return;
2443
+ }
2444
+ flushDelta();
2354
2445
  switch (event.type) {
2355
- case "delta":
2356
- setLive((l) => ({ ...l ?? { text: "" }, text: (l?.text ?? "") + event.text }));
2357
- return;
2358
2446
  case "message":
2359
2447
  push({ kind: "assistant", text: event.content });
2360
2448
  setLive((l) => ({ ...l ?? { text: "" }, text: "" }));
@@ -2394,7 +2482,7 @@ function App({ client, config, wallet, session, initialTask }) {
2394
2482
  return;
2395
2483
  }
2396
2484
  },
2397
- [cols, push]
2485
+ [cols, flushDelta, push]
2398
2486
  );
2399
2487
  const runTurn2 = useCallback(
2400
2488
  async (task) => {
@@ -2474,12 +2562,13 @@ function App({ client, config, wallet, session, initialTask }) {
2474
2562
  }
2475
2563
  } finally {
2476
2564
  abortRef.current = null;
2565
+ dropDelta();
2477
2566
  setLive(null);
2478
2567
  setBusy(false);
2479
2568
  setSponsor(null);
2480
2569
  }
2481
2570
  },
2482
- [client, contextWindow, handleEvent, model, nextSponsor, permit, push, root, session?.title]
2571
+ [client, contextWindow, dropDelta, handleEvent, model, nextSponsor, permit, push, root, session?.title]
2483
2572
  );
2484
2573
  const stopCurrent = useCallback(() => {
2485
2574
  abortRef.current?.abort();
@@ -2860,62 +2949,95 @@ function App({ client, config, wallet, session, initialTask }) {
2860
2949
  const elapsed = busy && startedAt ? Math.floor((Date.now() - startedAt) / 1e3) : 0;
2861
2950
  const spinner = SPINNER[tick % SPINNER.length];
2862
2951
  const sponsorLine = busy && sponsor ? sponsorText(sponsor, cols) : null;
2863
- const liveText = live?.text ? tailLines(live.text, Math.max(4, rows - 12)) : "";
2864
- const liveBlock = [
2865
- liveText,
2952
+ const viewport = Math.max(6, rows - 1);
2953
+ const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, editor.lines.length));
2954
+ const inputFrom = windowStart(editor.row, editor.lines.length, inputRows);
2955
+ const inputBoxHeight = 2 + inputRows;
2956
+ const chromeHeight = inputBoxHeight + 2 + (sponsorLine ? 1 : 0);
2957
+ let budget = Math.max(0, viewport - chromeHeight - menu.length);
2958
+ const PICKER_CHROME = 8;
2959
+ const pickerRows = picker ? Math.max(1, Math.min(MAX_PICKER_ROWS, picker.items.length, budget - PICKER_CHROME)) : 0;
2960
+ const pickerFrom = picker ? windowStart(pickerSel, picker.items.length, pickerRows) : 0;
2961
+ const pickerItems = picker ? picker.items.slice(pickerFrom, pickerFrom + pickerRows) : [];
2962
+ const pickerHeight = picker ? pickerItems.length + PICKER_CHROME : 0;
2963
+ const pickerLabelW = picker ? picker.items.reduce((w, i) => Math.max(w, i.label.length), 0) : 0;
2964
+ budget -= pickerHeight;
2965
+ const ASK_CHROME = 4;
2966
+ const askSummaryRows = ask2 ? lineCount(ask2.req.summary, cols) : 0;
2967
+ const askPreview = ask2?.req.preview ? headRows(ask2.req.preview, cols, Math.max(1, budget - ASK_CHROME - askSummaryRows)) : "";
2968
+ const askHeight = ask2 ? askSummaryRows + lineCount(askPreview, cols) * (askPreview ? 1 : 0) + ASK_CHROME : 0;
2969
+ budget -= askHeight;
2970
+ const busyHeight = busy ? 2 : 0;
2971
+ budget -= busyHeight;
2972
+ const liveRaw = [
2973
+ // Trailing blank lines are rows the clamp would spend on nothing, and a
2974
+ // streamed answer ends on one more often than not.
2975
+ (live?.text ?? "").replace(/\n+$/, ""),
2866
2976
  live?.tool ? `\u23FA ${live.tool.name} ${live.tool.summary}` : "",
2867
2977
  live?.tool?.output ?? ""
2868
2978
  ].filter(Boolean).join("\n");
2869
- const askBlock = ask2 ? [ask2.req.summary, ask2.req.preview ?? ""].filter(Boolean).join("\n") : "";
2870
- const pickerHeight = picker ? picker.items.length + 8 : 0;
2871
- const pickerLabelW = picker ? picker.items.reduce((w, i) => Math.max(w, i.label.length), 0) : 0;
2872
- const inputBoxHeight = 2 + editor.lines.length;
2873
- const chromeHeight = inputBoxHeight + 2 + (sponsorLine ? 1 : 0);
2979
+ const liveBlock = tailRows(liveRaw, cols, Math.max(0, budget - 1));
2874
2980
  const liveHeight = liveBlock ? lineCount(liveBlock, cols) + 1 : 0;
2875
- const askHeight = ask2 ? lineCount(askBlock, cols) + 2 : 0;
2876
- const printed = useMemo(() => entries.reduce((n, e) => n + entryHeight(e, cols), 0), [entries, cols]);
2877
- const spacer = Math.max(
2878
- 0,
2879
- rows - 1 - printed - chromeHeight - menu.length - pickerHeight - liveHeight - askHeight
2880
- );
2981
+ const printed = useMemo(() => {
2982
+ const cache = heightCacheRef.current;
2983
+ return entries.reduce((n, e) => {
2984
+ const key = `${e.id}:${cols}`;
2985
+ let h = cache.get(key);
2986
+ if (h === void 0) {
2987
+ h = entryHeight(e, cols);
2988
+ cache.set(key, h);
2989
+ }
2990
+ return n + h;
2991
+ }, 0);
2992
+ }, [entries, cols]);
2993
+ const used = chromeHeight + menu.length + pickerHeight + askHeight + busyHeight + liveHeight;
2994
+ const spacer = Math.max(0, viewport - printed - used);
2881
2995
  const labelW = menu.reduce((w, c2) => Math.max(w, c2.label.length), 0);
2882
2996
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
2883
2997
  /* @__PURE__ */ jsx2(Static, { items: entries, children: (entry) => /* @__PURE__ */ jsx2(EntryView, { entry }, entry.id) }),
2884
2998
  spacer > 0 ? /* @__PURE__ */ jsx2(Box2, { height: spacer }) : null,
2885
2999
  liveBlock ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { children: liveBlock }) }) : null,
3000
+ busy ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
3001
+ /* @__PURE__ */ jsxs2(Text2, { color: MANGO3, children: [
3002
+ spinner,
3003
+ " "
3004
+ ] }),
3005
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
3006
+ busyLabel,
3007
+ " ",
3008
+ elapsed,
3009
+ "s \xB7 esc to stop"
3010
+ ] })
3011
+ ] }) : null,
2886
3012
  ask2 ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2887
3013
  /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: ask2.req.summary }),
2888
- ask2.req.preview ? /* @__PURE__ */ jsx2(Text2, { children: ask2.req.preview }) : null,
3014
+ askPreview ? /* @__PURE__ */ jsx2(Text2, { children: askPreview }) : null,
2889
3015
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "[y/\u23CE] once \xB7 [a] always \xB7 [n] no \xB7 esc cancels" })
2890
3016
  ] }) : null,
2891
3017
  picker ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: MANGO3, paddingX: 1, children: [
2892
3018
  /* @__PURE__ */ jsx2(Text2, { bold: true, color: MANGO_BRIGHT, children: picker.title }),
2893
3019
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: picker.subtitle }),
2894
3020
  /* @__PURE__ */ jsx2(Box2, { height: 1 }),
2895
- picker.items.map((item, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
2896
- /* @__PURE__ */ jsxs2(Text2, { color: i === pickerSel ? MANGO_BRIGHT : void 0, bold: i === pickerSel, children: [
2897
- i === pickerSel ? "\u276F " : " ",
2898
- `${i + 1}. `,
2899
- item.label.padEnd(pickerLabelW + 2)
2900
- ] }),
2901
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: item.hint }),
2902
- item.current ? /* @__PURE__ */ jsx2(Text2, { color: "green", children: " \u2190 current" }) : null
2903
- ] }, item.value)),
3021
+ pickerItems.map((item, i) => {
3022
+ const index = pickerFrom + i;
3023
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
3024
+ /* @__PURE__ */ jsxs2(Text2, { color: index === pickerSel ? MANGO_BRIGHT : void 0, bold: index === pickerSel, children: [
3025
+ index === pickerSel ? "\u276F " : " ",
3026
+ `${index + 1}. `,
3027
+ item.label.padEnd(pickerLabelW + 2)
3028
+ ] }),
3029
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: item.hint }),
3030
+ item.current ? /* @__PURE__ */ jsx2(Text2, { color: "green", children: " \u2190 current" }) : null
3031
+ ] }, item.value);
3032
+ }),
2904
3033
  /* @__PURE__ */ jsx2(Box2, { height: 1 }),
2905
3034
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2191\u2193 choose \xB7 1-9 jump straight to a row \xB7 \u23CE confirm \xB7 esc cancel" })
2906
3035
  ] }) : null,
2907
3036
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
2908
3037
  sponsorLine ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: sponsorLine }) : null,
2909
- /* @__PURE__ */ jsxs2(Box2, { borderStyle: "round", borderColor: busy ? MANGO_BRIGHT : MANGO3, paddingX: 1, children: [
2910
- /* @__PURE__ */ jsx2(Text2, { color: MANGO_BRIGHT, children: "\u276F " }),
2911
- busy ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
2912
- spinner,
2913
- " ",
2914
- busyLabel,
2915
- " ",
2916
- elapsed,
2917
- "s \xB7 esc to stop"
2918
- ] }) : /* @__PURE__ */ jsx2(Text2, { children: renderInput(editor) })
3038
+ /* @__PURE__ */ jsxs2(Box2, { borderStyle: "round", borderColor: busy ? SLATE : MANGO3, paddingX: 1, children: [
3039
+ /* @__PURE__ */ jsx2(Text2, { color: busy ? SLATE : MANGO_BRIGHT, children: "\u276F " }),
3040
+ busy ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: toText(editor) }) : /* @__PURE__ */ jsx2(Text2, { children: renderInput(editor, inputFrom, inputRows) })
2919
3041
  ] }),
2920
3042
  menu.map((item, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
2921
3043
  /* @__PURE__ */ jsxs2(Text2, { color: i === sel ? MANGO_BRIGHT : MANGO3, bold: i === sel, children: [
@@ -2924,17 +3046,20 @@ function App({ client, config, wallet, session, initialTask }) {
2924
3046
  ] }),
2925
3047
  /* @__PURE__ */ jsx2(Text2, { dimColor: i !== sel, children: item.hint })
2926
3048
  ] }, item.value)),
2927
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
3049
+ quitHint || menu.length > 0 ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
2928
3050
  " ",
2929
- statusLine({
2930
- menu: menu.length > 0,
2931
- quitHint,
2932
- model,
2933
- balance,
2934
- spent,
2935
- mode,
2936
- minutes: (Date.now() - runStartedAtRef.current) / 6e4
2937
- })
3051
+ quitHint ? "press ctrl+c again to quit" : "\u2191\u2193 choose \xB7 \u23CE run \xB7 tab complete \xB7 esc close"
3052
+ ] }) : /* @__PURE__ */ jsxs2(Box2, { children: [
3053
+ /* @__PURE__ */ jsx2(Text2, { color: MODE_STYLE[mode].color, bold: true, children: ` ${MODE_STYLE[mode].glyph} ${MODE_LABEL[mode]}` }),
3054
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
3055
+ " ",
3056
+ statusLine({
3057
+ model,
3058
+ balance,
3059
+ spent,
3060
+ minutes: (Date.now() - runStartedAtRef.current) / 6e4
3061
+ })
3062
+ ] })
2938
3063
  ] })
2939
3064
  ] })
2940
3065
  ] });
@@ -2951,9 +3076,10 @@ function sleep2(ms, signal) {
2951
3076
  signal.addEventListener("abort", done, { once: true });
2952
3077
  });
2953
3078
  }
2954
- function renderInput(state) {
2955
- return state.lines.map((line2, row) => {
2956
- const prefix = row === 0 ? "" : "\n";
3079
+ function renderInput(state, from, rows) {
3080
+ return state.lines.slice(from, from + rows).map((line2, i) => {
3081
+ const row = from + i;
3082
+ const prefix = i === 0 ? "" : "\n";
2957
3083
  if (row !== state.row) return /* @__PURE__ */ jsx2(Text2, { children: prefix + line2 }, row);
2958
3084
  const before = line2.slice(0, state.col);
2959
3085
  const at = line2.slice(state.col, state.col + 1) || " ";
@@ -2966,19 +3092,20 @@ function renderInput(state) {
2966
3092
  });
2967
3093
  }
2968
3094
  function statusLine(o) {
2969
- if (o.quitHint) return "press ctrl+c again to quit";
2970
- if (o.menu) return "\u2191\u2193 choose \xB7 \u23CE run \xB7 tab complete \xB7 esc close";
2971
3095
  const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
2972
3096
  const parts = [
2973
3097
  o.model,
2974
3098
  `${o.balance.toLocaleString("en-US")} cr`,
2975
- o.spent > 0 ? `\u2212${o.spent.toLocaleString("en-US")} this session` : void 0,
3099
+ o.spent > 0 ? `\u2212${o.spent.toLocaleString("en-US")}` : void 0,
2976
3100
  burn,
2977
- MODE_LABEL[o.mode],
2978
3101
  "/help"
2979
3102
  ].filter(Boolean);
2980
3103
  return parts.join(" \xB7 ");
2981
3104
  }
3105
+ function windowStart(sel, total, size) {
3106
+ if (total <= size) return 0;
3107
+ return Math.max(0, Math.min(total - size, sel - Math.floor(size / 2)));
3108
+ }
2982
3109
  function toEditorKey(ch, key) {
2983
3110
  const name = key.leftArrow ? "left" : key.rightArrow ? "right" : key.upArrow ? "up" : key.downArrow ? "down" : key.backspace ? "backspace" : key.delete ? "delete" : void 0;
2984
3111
  return {
@@ -3011,7 +3138,7 @@ function tailLines(text, max) {
3011
3138
  const lines = text.split("\n");
3012
3139
  return lines.length <= max ? text : lines.slice(-max).join("\n");
3013
3140
  }
3014
- var SPINNER, WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES;
3141
+ var SPINNER, WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES, DELTA_FLUSH_MS, MAX_INPUT_ROWS, MAX_PICKER_ROWS;
3015
3142
  var init_app = __esm({
3016
3143
  "src/tui/app.tsx"() {
3017
3144
  "use strict";
@@ -3044,6 +3171,9 @@ var init_app = __esm({
3044
3171
  NOT_SIGNED_IN = " Not signed in, or the stored token isn't valid for this gateway.\n Run /login to sign in.";
3045
3172
  LIVE_OUTPUT_LINES = 5;
3046
3173
  COMMITTED_OUTPUT_LINES = 4;
3174
+ DELTA_FLUSH_MS = 50;
3175
+ MAX_INPUT_ROWS = 10;
3176
+ MAX_PICKER_ROWS = 12;
3047
3177
  }
3048
3178
  });
3049
3179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clixad",
3
- "version": "0.0.1-beta.6",
3
+ "version": "0.0.1-beta.7",
4
4
  "description": "Free AI coding agent in your terminal, funded by rewarded ads.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",