@supatest/cli 0.0.38 → 0.0.40

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/index.js +188 -138
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5891,7 +5891,7 @@ var CLI_VERSION;
5891
5891
  var init_version = __esm({
5892
5892
  "src/version.ts"() {
5893
5893
  "use strict";
5894
- CLI_VERSION = "0.0.38";
5894
+ CLI_VERSION = "0.0.40";
5895
5895
  }
5896
5896
  });
5897
5897
 
@@ -5963,7 +5963,7 @@ var init_error_logger = __esm({
5963
5963
  "src/utils/error-logger.ts"() {
5964
5964
  "use strict";
5965
5965
  init_version();
5966
- SUPATEST_DIR = path2.join(os2.homedir(), ".supatest");
5966
+ SUPATEST_DIR = process.platform === "win32" ? path2.join(os2.tmpdir(), ".supatest") : path2.join(os2.homedir(), ".supatest");
5967
5967
  LOGS_DIR = path2.join(SUPATEST_DIR, "logs");
5968
5968
  ERROR_LOG_FILE = path2.join(LOGS_DIR, "error.log");
5969
5969
  MAX_LOG_SIZE = 5 * 1024 * 1024;
@@ -7527,11 +7527,12 @@ var init_react = __esm({
7527
7527
  });
7528
7528
 
7529
7529
  // src/ui/contexts/SessionContext.tsx
7530
- import React, { createContext, useCallback, useContext, useMemo, useState } from "react";
7531
- var SessionContext, SessionProvider, useSession;
7530
+ import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
7531
+ var UsageStatsContext, SessionContext, SessionProvider, useSession, useUsageStats;
7532
7532
  var init_SessionContext = __esm({
7533
7533
  "src/ui/contexts/SessionContext.tsx"() {
7534
7534
  "use strict";
7535
+ UsageStatsContext = createContext(null);
7535
7536
  SessionContext = createContext(null);
7536
7537
  SessionProvider = ({
7537
7538
  children,
@@ -7573,12 +7574,25 @@ var init_SessionContext = __esm({
7573
7574
  },
7574
7575
  [allToolsExpanded]
7575
7576
  );
7577
+ const pendingUpdateRef = useRef(null);
7578
+ const updateScheduledRef = useRef(false);
7576
7579
  const updateLastMessage = useCallback((updates) => {
7577
- setMessages((prev) => {
7578
- if (prev.length === 0) return prev;
7579
- const last = prev[prev.length - 1];
7580
- return [...prev.slice(0, -1), { ...last, ...updates }];
7581
- });
7580
+ pendingUpdateRef.current = pendingUpdateRef.current ? { ...pendingUpdateRef.current, ...updates } : updates;
7581
+ if (!updateScheduledRef.current) {
7582
+ updateScheduledRef.current = true;
7583
+ setTimeout(() => {
7584
+ updateScheduledRef.current = false;
7585
+ const pending = pendingUpdateRef.current;
7586
+ pendingUpdateRef.current = null;
7587
+ if (pending) {
7588
+ setMessages((prev) => {
7589
+ if (prev.length === 0) return prev;
7590
+ const last = prev[prev.length - 1];
7591
+ return [...prev.slice(0, -1), { ...last, ...pending }];
7592
+ });
7593
+ }
7594
+ }, 0);
7595
+ }
7582
7596
  }, []);
7583
7597
  const updateMessageById = useCallback((id, updates) => {
7584
7598
  setMessages((prev) => {
@@ -7658,8 +7672,6 @@ var init_SessionContext = __esm({
7658
7672
  setTodos,
7659
7673
  stats,
7660
7674
  updateStats,
7661
- usageStats,
7662
- setUsageStats,
7663
7675
  isAgentRunning,
7664
7676
  setIsAgentRunning,
7665
7677
  shouldInterruptAgent,
@@ -7693,7 +7705,6 @@ var init_SessionContext = __esm({
7693
7705
  todos,
7694
7706
  stats,
7695
7707
  updateStats,
7696
- usageStats,
7697
7708
  isAgentRunning,
7698
7709
  shouldInterruptAgent,
7699
7710
  sessionId,
@@ -7705,7 +7716,11 @@ var init_SessionContext = __esm({
7705
7716
  staticRemountKey,
7706
7717
  refreshStatic
7707
7718
  ]);
7708
- return /* @__PURE__ */ React.createElement(SessionContext.Provider, { value }, children);
7719
+ const usageStatsValue = useMemo(() => ({
7720
+ usageStats,
7721
+ setUsageStats
7722
+ }), [usageStats]);
7723
+ return /* @__PURE__ */ React.createElement(SessionContext.Provider, { value }, /* @__PURE__ */ React.createElement(UsageStatsContext.Provider, { value: usageStatsValue }, children));
7709
7724
  };
7710
7725
  useSession = () => {
7711
7726
  const context = useContext(SessionContext);
@@ -7714,6 +7729,13 @@ var init_SessionContext = __esm({
7714
7729
  }
7715
7730
  return context;
7716
7731
  };
7732
+ useUsageStats = () => {
7733
+ const context = useContext(UsageStatsContext);
7734
+ if (!context) {
7735
+ throw new Error("useUsageStats must be used within SessionProvider");
7736
+ }
7737
+ return context;
7738
+ };
7717
7739
  }
7718
7740
  });
7719
7741
 
@@ -8248,48 +8270,36 @@ var init_ErrorMessage = __esm({
8248
8270
  // src/ui/components/messages/LoadingMessage.tsx
8249
8271
  import { Box as Box5, Text as Text5 } from "ink";
8250
8272
  import Spinner2 from "ink-spinner";
8251
- import React6, { useEffect, useState as useState2 } from "react";
8252
- var LOADING_MESSAGES, SHIMMER_INTERVAL_MS, TEXT_ROTATION_INTERVAL_MS, LoadingMessage;
8273
+ import React6, { memo, useEffect, useRef as useRef2, useState as useState2 } from "react";
8274
+ var LOADING_MESSAGES, TEXT_ROTATION_INTERVAL_MS, LoadingMessage;
8253
8275
  var init_LoadingMessage = __esm({
8254
8276
  "src/ui/components/messages/LoadingMessage.tsx"() {
8255
8277
  "use strict";
8256
8278
  init_theme();
8257
8279
  LOADING_MESSAGES = [
8258
- "Brainstorming...",
8259
- "Brewing coffee...",
8260
- "Sipping espresso...",
8261
- "Testing theories...",
8262
- "Making magic...",
8263
- "Multiplying matrices..."
8280
+ "Thinking...",
8281
+ "Working...",
8282
+ "Processing..."
8264
8283
  ];
8265
- SHIMMER_INTERVAL_MS = 80;
8266
- TEXT_ROTATION_INTERVAL_MS = 2e3;
8267
- LoadingMessage = ({ headless = false }) => {
8284
+ TEXT_ROTATION_INTERVAL_MS = 3e3;
8285
+ LoadingMessage = memo(({ headless = false }) => {
8268
8286
  const [messageIndex, setMessageIndex] = useState2(0);
8269
- const [shimmerPosition, setShimmerPosition] = useState2(0);
8287
+ const intervalRef = useRef2(null);
8270
8288
  const message = LOADING_MESSAGES[messageIndex];
8271
8289
  useEffect(() => {
8272
- const rotationInterval = setInterval(() => {
8290
+ intervalRef.current = setInterval(() => {
8273
8291
  setMessageIndex((prev) => (prev + 1) % LOADING_MESSAGES.length);
8274
- setShimmerPosition(0);
8275
8292
  }, TEXT_ROTATION_INTERVAL_MS);
8276
8293
  return () => {
8277
- clearInterval(rotationInterval);
8294
+ if (intervalRef.current) {
8295
+ clearInterval(intervalRef.current);
8296
+ intervalRef.current = null;
8297
+ }
8278
8298
  };
8279
8299
  }, []);
8280
- useEffect(() => {
8281
- const shimmerInterval = setInterval(() => {
8282
- setShimmerPosition((prev) => (prev + 1) % (message.length + 1));
8283
- }, SHIMMER_INTERVAL_MS);
8284
- return () => {
8285
- clearInterval(shimmerInterval);
8286
- };
8287
- }, [message.length]);
8288
- const before = message.slice(0, shimmerPosition);
8289
- const current = message[shimmerPosition] || "";
8290
- const after = message.slice(shimmerPosition + 1);
8291
- return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React6.createElement(Box5, { width: 2 }, /* @__PURE__ */ React6.createElement(Text5, { color: theme.text.accent }, /* @__PURE__ */ React6.createElement(Spinner2, { type: "dots" }))), /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React6.createElement(Text5, null, /* @__PURE__ */ React6.createElement(Text5, { color: theme.text.primary }, before), /* @__PURE__ */ React6.createElement(Text5, { bold: true, color: theme.text.accent }, current), /* @__PURE__ */ React6.createElement(Text5, { color: theme.text.primary }, after), !headless && /* @__PURE__ */ React6.createElement(Text5, { color: theme.text.primary }, " (esc to interrupt)"))));
8292
- };
8300
+ return /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React6.createElement(Box5, { width: 2 }, /* @__PURE__ */ React6.createElement(Text5, { color: theme.text.accent }, /* @__PURE__ */ React6.createElement(Spinner2, { type: "dots" }))), /* @__PURE__ */ React6.createElement(Box5, { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React6.createElement(Text5, null, /* @__PURE__ */ React6.createElement(Text5, { color: theme.text.primary }, message), !headless && /* @__PURE__ */ React6.createElement(Text5, { color: theme.text.dim }, " (esc to interrupt)"))));
8301
+ });
8302
+ LoadingMessage.displayName = "LoadingMessage";
8293
8303
  }
8294
8304
  });
8295
8305
 
@@ -8620,8 +8630,8 @@ var init_QueuedMessageDisplay = __esm({
8620
8630
 
8621
8631
  // src/ui/components/MessageList.tsx
8622
8632
  import { Box as Box12, Static } from "ink";
8623
- import React13, { useMemo as useMemo4, useRef } from "react";
8624
- var MessageList;
8633
+ import React13, { memo as memo2, useCallback as useCallback2, useMemo as useMemo3 } from "react";
8634
+ var StaticHeader, MessageList;
8625
8635
  var init_MessageList = __esm({
8626
8636
  "src/ui/components/MessageList.tsx"() {
8627
8637
  "use strict";
@@ -8636,9 +8646,17 @@ var init_MessageList = __esm({
8636
8646
  init_ToolMessage();
8637
8647
  init_UserMessage();
8638
8648
  init_QueuedMessageDisplay();
8639
- MessageList = ({ terminalWidth, currentFolder, gitBranch, headless = false, queuedTasks = [] }) => {
8649
+ StaticHeader = memo2(({ currentFolder, gitBranch, headless, staticRemountKey }) => {
8650
+ const headerItems = useMemo3(() => [{ id: "header" }], []);
8651
+ return /* @__PURE__ */ React13.createElement(Static, { items: headerItems, key: `header-${staticRemountKey}` }, () => /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", key: "header" }, /* @__PURE__ */ React13.createElement(Header, { currentFolder, gitBranch, headless })));
8652
+ });
8653
+ StaticHeader.displayName = "StaticHeader";
8654
+ MessageList = memo2(({ terminalWidth, currentFolder, gitBranch, headless = false, queuedTasks = [] }) => {
8640
8655
  const { messages, updateMessageById, isAgentRunning, staticRemountKey, toolGroupsExpanded, toggleToolGroups } = useSession();
8641
- const renderMessage = (message, isInGroup = false) => {
8656
+ const handleToggle = useCallback2((id, currentExpanded) => {
8657
+ updateMessageById(id, { isExpanded: !currentExpanded });
8658
+ }, [updateMessageById]);
8659
+ const renderMessage = useCallback2((message, isInGroup = false) => {
8642
8660
  switch (message.type) {
8643
8661
  case "user":
8644
8662
  return /* @__PURE__ */ React13.createElement(UserMessage, { key: message.id, text: message.content });
@@ -8662,7 +8680,7 @@ var init_MessageList = __esm({
8662
8680
  input: message.toolInput,
8663
8681
  isExpanded: message.isExpanded,
8664
8682
  key: message.id,
8665
- onToggle: (id) => updateMessageById(id, { isExpanded: !message.isExpanded }),
8683
+ onToggle: (id) => handleToggle(id, message.isExpanded),
8666
8684
  result: message.toolResult,
8667
8685
  toolName: message.toolName || "Unknown"
8668
8686
  }
@@ -8676,7 +8694,7 @@ var init_MessageList = __esm({
8676
8694
  input: message.toolInput,
8677
8695
  isExpanded: message.isExpanded,
8678
8696
  key: message.id,
8679
- onToggle: (id) => updateMessageById(id, { isExpanded: !message.isExpanded }),
8697
+ onToggle: (id) => handleToggle(id, message.isExpanded),
8680
8698
  result: message.toolResult,
8681
8699
  toolName: message.toolName || "Unknown"
8682
8700
  }
@@ -8689,7 +8707,7 @@ var init_MessageList = __esm({
8689
8707
  id: message.id,
8690
8708
  isExpanded: message.isExpanded,
8691
8709
  key: message.id,
8692
- onToggle: (id) => updateMessageById(id, { isExpanded: !message.isExpanded })
8710
+ onToggle: (id) => handleToggle(id, message.isExpanded)
8693
8711
  }
8694
8712
  );
8695
8713
  case "error":
@@ -8706,8 +8724,8 @@ var init_MessageList = __esm({
8706
8724
  default:
8707
8725
  return null;
8708
8726
  }
8709
- };
8710
- const renderGroupedMessage = (group) => {
8727
+ }, [terminalWidth, handleToggle]);
8728
+ const renderGroupedMessage = useCallback2((group) => {
8711
8729
  if (group.type === "group") {
8712
8730
  return /* @__PURE__ */ React13.createElement(
8713
8731
  ToolGroup,
@@ -8718,7 +8736,7 @@ var init_MessageList = __esm({
8718
8736
  onToggleTool: (id) => {
8719
8737
  const msg = group.messages.find((m) => m.id === id);
8720
8738
  if (msg) {
8721
- updateMessageById(id, { isExpanded: !msg.isExpanded });
8739
+ handleToggle(id, msg.isExpanded);
8722
8740
  }
8723
8741
  },
8724
8742
  tools: group.messages.map((msg) => ({
@@ -8733,32 +8751,30 @@ var init_MessageList = __esm({
8733
8751
  );
8734
8752
  }
8735
8753
  return renderMessage(group.messages[0]);
8736
- };
8737
- const lastUserMessageIndex = useMemo4(() => {
8738
- for (let i = messages.length - 1; i >= 0; i--) {
8739
- if (messages[i].type === "user") {
8740
- return i;
8741
- }
8742
- }
8743
- return -1;
8744
- }, [messages]);
8745
- const hasPendingAssistant = useMemo4(
8754
+ }, [toolGroupsExpanded, toggleToolGroups, handleToggle, renderMessage]);
8755
+ const hasPendingAssistant = useMemo3(
8746
8756
  () => messages.some((m) => m.type === "assistant" && m.isPending),
8747
8757
  [messages]
8748
8758
  );
8749
- const completedBoundaryRef = useRef(-1);
8750
- const completedBoundaryKey = useMemo4(() => {
8751
- const currentBoundary = lastUserMessageIndex;
8752
- if (currentBoundary !== completedBoundaryRef.current) {
8753
- completedBoundaryRef.current = currentBoundary;
8754
- return `boundary-${currentBoundary}`;
8755
- }
8756
- return `boundary-${completedBoundaryRef.current}`;
8757
- }, [lastUserMessageIndex]);
8758
- const { completedGroups, currentTurnGroups } = useMemo4(() => {
8759
+ const { completedGroups, currentTurnGroups } = useMemo3(() => {
8759
8760
  const completed = [];
8760
8761
  const currentTurn = [];
8761
- const processTurn = (turnMessages2, targetArray) => {
8762
+ const isMessageComplete = (msg) => {
8763
+ switch (msg.type) {
8764
+ case "user":
8765
+ case "error":
8766
+ case "todo":
8767
+ case "thinking":
8768
+ return true;
8769
+ case "tool":
8770
+ return msg.toolResult !== void 0;
8771
+ case "assistant":
8772
+ return !msg.isPending;
8773
+ default:
8774
+ return true;
8775
+ }
8776
+ };
8777
+ const processTurn = (turnMessages, targetArray) => {
8762
8778
  let currentToolGroup = [];
8763
8779
  const flushToolGroup = () => {
8764
8780
  if (currentToolGroup.length === 0) return;
@@ -8769,7 +8785,7 @@ var init_MessageList = __esm({
8769
8785
  }
8770
8786
  currentToolGroup = [];
8771
8787
  };
8772
- for (const msg of turnMessages2) {
8788
+ for (const msg of turnMessages) {
8773
8789
  if (msg.type === "tool") {
8774
8790
  currentToolGroup.push(msg);
8775
8791
  } else {
@@ -8779,38 +8795,46 @@ var init_MessageList = __esm({
8779
8795
  }
8780
8796
  flushToolGroup();
8781
8797
  };
8782
- let turnMessages = [];
8783
- for (let i = 0; i < lastUserMessageIndex; i++) {
8784
- const msg = messages[i];
8785
- if (msg.type === "user") {
8786
- processTurn(turnMessages, completed);
8787
- turnMessages = [];
8788
- completed.push({ type: "single", messages: [msg] });
8798
+ const completeMessages = [];
8799
+ const inProgressMessages = [];
8800
+ for (const msg of messages) {
8801
+ if (isMessageComplete(msg)) {
8802
+ completeMessages.push(msg);
8789
8803
  } else {
8790
- turnMessages.push(msg);
8804
+ inProgressMessages.push(msg);
8791
8805
  }
8792
8806
  }
8793
- processTurn(turnMessages, completed);
8794
- if (lastUserMessageIndex >= 0) {
8795
- completed.push({ type: "single", messages: [messages[lastUserMessageIndex]] });
8807
+ let splitIndex = messages.length;
8808
+ for (let i = messages.length - 1; i >= 0; i--) {
8809
+ if (isMessageComplete(messages[i])) {
8810
+ splitIndex = i + 1;
8811
+ break;
8812
+ }
8796
8813
  }
8797
- const currentTurnMessages = lastUserMessageIndex >= 0 ? messages.slice(lastUserMessageIndex + 1) : messages;
8798
- processTurn(currentTurnMessages, currentTurn);
8814
+ const finalCompleteMessages = messages.slice(0, splitIndex);
8815
+ const finalInProgressMessages = messages.slice(splitIndex);
8816
+ processTurn(finalCompleteMessages, completed);
8817
+ processTurn(finalInProgressMessages, currentTurn);
8799
8818
  return { completedGroups: completed, currentTurnGroups: currentTurn };
8800
- }, [messages, lastUserMessageIndex, completedBoundaryKey]);
8801
- const staticItems = useMemo4(() => [
8802
- { id: "header", type: "header" },
8803
- ...completedGroups.map((group, idx) => {
8819
+ }, [messages]);
8820
+ const staticItems = useMemo3(
8821
+ () => completedGroups.map((group, idx) => {
8804
8822
  if (group.type === "group") {
8805
- return { ...group, _isGroup: true, id: `group-${idx}` };
8823
+ return { ...group, _isGroup: true, id: `group-${group.messages[0]?.id || idx}` };
8806
8824
  }
8807
8825
  return { ...group.messages[0], _isMessage: true };
8808
- })
8809
- ], [completedGroups]);
8810
- return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column" }, /* @__PURE__ */ React13.createElement(Static, { items: staticItems, key: `${staticRemountKey}-${toolGroupsExpanded ? "expanded" : "collapsed"}` }, (item) => {
8811
- if (item.type === "header") {
8812
- return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", key: "header" }, /* @__PURE__ */ React13.createElement(Header, { currentFolder, gitBranch, headless }));
8826
+ }),
8827
+ [completedGroups]
8828
+ );
8829
+ return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column" }, /* @__PURE__ */ React13.createElement(
8830
+ StaticHeader,
8831
+ {
8832
+ currentFolder,
8833
+ gitBranch,
8834
+ headless,
8835
+ staticRemountKey
8813
8836
  }
8837
+ ), staticItems.length > 0 && /* @__PURE__ */ React13.createElement(Static, { items: staticItems, key: `messages-${staticRemountKey}-${toolGroupsExpanded}` }, (item) => {
8814
8838
  if (item._isGroup) {
8815
8839
  const content2 = renderGroupedMessage(item);
8816
8840
  if (!content2) {
@@ -8830,7 +8854,8 @@ var init_MessageList = __esm({
8830
8854
  }
8831
8855
  return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", key: group.type === "group" ? `current-group-${idx}` : group.messages[0].id, width: "100%" }, content);
8832
8856
  }), /* @__PURE__ */ React13.createElement(QueuedMessageDisplay, { messageQueue: queuedTasks }), isAgentRunning && !hasPendingAssistant && /* @__PURE__ */ React13.createElement(LoadingMessage, { headless, key: "loading" }), /* @__PURE__ */ React13.createElement(Box12, { height: 1 }));
8833
- };
8857
+ });
8858
+ MessageList.displayName = "MessageList";
8834
8859
  }
8835
8860
  });
8836
8861
 
@@ -10121,10 +10146,10 @@ var init_mouse = __esm({
10121
10146
  import { useStdin as useStdin2 } from "ink";
10122
10147
  import React17, {
10123
10148
  createContext as createContext2,
10124
- useCallback as useCallback2,
10149
+ useCallback as useCallback3,
10125
10150
  useContext as useContext2,
10126
10151
  useEffect as useEffect4,
10127
- useRef as useRef3
10152
+ useRef as useRef5
10128
10153
  } from "react";
10129
10154
  function charLengthAt(str, i) {
10130
10155
  if (str.length <= i) {
@@ -10411,16 +10436,16 @@ function KeypressProvider({
10411
10436
  debugKeystrokeLogging
10412
10437
  }) {
10413
10438
  const { stdin, setRawMode } = useStdin2();
10414
- const subscribers = useRef3(/* @__PURE__ */ new Set()).current;
10415
- const subscribe = useCallback2(
10439
+ const subscribers = useRef5(/* @__PURE__ */ new Set()).current;
10440
+ const subscribe = useCallback3(
10416
10441
  (handler) => subscribers.add(handler),
10417
10442
  [subscribers]
10418
10443
  );
10419
- const unsubscribe = useCallback2(
10444
+ const unsubscribe = useCallback3(
10420
10445
  (handler) => subscribers.delete(handler),
10421
10446
  [subscribers]
10422
10447
  );
10423
- const broadcast = useCallback2(
10448
+ const broadcast = useCallback3(
10424
10449
  (key) => subscribers.forEach((handler) => handler(key)),
10425
10450
  [subscribers]
10426
10451
  );
@@ -11037,7 +11062,8 @@ var init_TestSelector = __esm({
11037
11062
  // Only fetch failed tests
11038
11063
  });
11039
11064
  setTotalTests(result.total);
11040
- setHasMore(allTests.length + result.tests.length < result.total);
11065
+ const loadedCount = allTests.length + result.tests.length;
11066
+ setHasMore(result.tests.length === PAGE_SIZE2 && loadedCount < result.total);
11041
11067
  setAllTests((prev) => [...prev, ...result.tests]);
11042
11068
  } catch (err) {
11043
11069
  setError(err instanceof Error ? err.message : String(err));
@@ -11149,7 +11175,7 @@ var init_TestSelector = __esm({
11149
11175
  const indicator = isSelected ? "\u25B6 " : " ";
11150
11176
  const bgColor = isSelected ? theme.text.accent : void 0;
11151
11177
  return /* @__PURE__ */ React21.createElement(Box18, { key: test.id, marginBottom: 0 }, /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, color: isChecked ? "green" : isSelected ? "black" : theme.text.dim }, checkbox), /* @__PURE__ */ React21.createElement(Text16, null, " "), /* @__PURE__ */ React21.createElement(Text16, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, file, line && /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, ":", line), /* @__PURE__ */ React21.createElement(Text16, { color: isSelected ? "black" : theme.text.dim }, " - "), title));
11152
- })), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", marginTop: 1 }, allTests.length > VISIBLE_ITEMS2 && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", adjustedStart + 1, "-", adjustedEnd, " of ", totalTests || allTests.length, " failed tests", hasMore && !isLoading && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " \u2022 Scroll for more"))), /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "a"), " all \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "n"), " none \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Enter"), " fix selected")), selectedTests.size > 0 && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, selectedTests.size, " test", selectedTests.size !== 1 ? "s" : "", " selected")), isLoading && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "cyan" }, "Loading more tests..."))));
11178
+ })), /* @__PURE__ */ React21.createElement(Box18, { flexDirection: "column", marginTop: 1 }, allTests.length > VISIBLE_ITEMS2 && /* @__PURE__ */ React21.createElement(Box18, { marginBottom: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "yellow" }, "Showing ", adjustedStart + 1, "-", adjustedEnd, " of ", allTests.length, " failed tests", hasMore && !isLoading && /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, " (scroll for more)"))), /* @__PURE__ */ React21.createElement(Box18, null, /* @__PURE__ */ React21.createElement(Text16, { color: theme.text.dim }, /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Space"), " toggle \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "a"), " all \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "n"), " none \u2022 ", /* @__PURE__ */ React21.createElement(Text16, { bold: true }, "Enter"), " fix selected")), selectedTests.size > 0 && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "green" }, selectedTests.size, " test", selectedTests.size !== 1 ? "s" : "", " selected")), isLoading && /* @__PURE__ */ React21.createElement(Box18, { marginTop: 1 }, /* @__PURE__ */ React21.createElement(Text16, { color: "cyan" }, "Loading more tests..."))));
11153
11179
  };
11154
11180
  }
11155
11181
  });
@@ -11426,8 +11452,8 @@ var init_ModelSelector = __esm({
11426
11452
  import path5 from "path";
11427
11453
  import chalk4 from "chalk";
11428
11454
  import { Box as Box21, Text as Text19 } from "ink";
11429
- import React24, { forwardRef, useEffect as useEffect9, useImperativeHandle, useState as useState10 } from "react";
11430
- var InputPrompt;
11455
+ import React24, { forwardRef, memo as memo3, useEffect as useEffect9, useImperativeHandle, useState as useState10 } from "react";
11456
+ var InputPromptInner, InputPrompt;
11431
11457
  var init_InputPrompt = __esm({
11432
11458
  "src/ui/components/InputPrompt.tsx"() {
11433
11459
  "use strict";
@@ -11438,7 +11464,7 @@ var init_InputPrompt = __esm({
11438
11464
  init_file_completion();
11439
11465
  init_theme();
11440
11466
  init_ModelSelector();
11441
- InputPrompt = forwardRef(({
11467
+ InputPromptInner = forwardRef(({
11442
11468
  onSubmit,
11443
11469
  placeholder = "Enter your task (press Enter to submit, Shift+Enter for new line)...",
11444
11470
  disabled = false,
@@ -11449,7 +11475,8 @@ var init_InputPrompt = __esm({
11449
11475
  onInputChange,
11450
11476
  isClaudeMax = false
11451
11477
  }, ref) => {
11452
- const { messages, agentMode, selectedModel, setSelectedModel, isAgentRunning, usageStats } = useSession();
11478
+ const { agentMode, selectedModel, setSelectedModel, isAgentRunning } = useSession();
11479
+ const { usageStats } = useUsageStats();
11453
11480
  const [value, setValue] = useState10("");
11454
11481
  const [cursorOffset, setCursorOffset] = useState10(0);
11455
11482
  const [allFiles, setAllFiles] = useState10([]);
@@ -11733,6 +11760,8 @@ var init_InputPrompt = __esm({
11733
11760
  })), !hasContent && disabled && /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, italic: true }, "Waiting for agent to complete...")))
11734
11761
  ), /* @__PURE__ */ React24.createElement(Box21, { justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React24.createElement(Box21, { gap: 2 }, /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "\u23F8 plan" : "\u25B6 build"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (shift+tab)")), /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "model:"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelDisplayName(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (Cost: "), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelCostLabel(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, ") (shift+m)"))), /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: usageStats && usageStats.contextPct >= 90 ? theme.text.error : usageStats && usageStats.contextPct >= 75 ? theme.text.warning : theme.text.dim }, usageStats?.contextPct ?? 0, "% context used"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " ", "(", usageStats ? usageStats.inputTokens >= 1e3 ? `${(usageStats.inputTokens / 1e3).toFixed(1)}K` : usageStats.inputTokens : 0, " / ", usageStats ? usageStats.contextWindow >= 1e3 ? `${(usageStats.contextWindow / 1e3).toFixed(0)}K` : usageStats.contextWindow : "200K", ")"))));
11735
11762
  });
11763
+ InputPromptInner.displayName = "InputPromptInner";
11764
+ InputPrompt = memo3(InputPromptInner);
11736
11765
  InputPrompt.displayName = "InputPrompt";
11737
11766
  }
11738
11767
  });
@@ -12228,18 +12257,18 @@ var init_useModeToggle = __esm({
12228
12257
  });
12229
12258
 
12230
12259
  // src/ui/hooks/useOverlayEscapeGuard.ts
12231
- import { useCallback as useCallback3, useMemo as useMemo5, useRef as useRef4 } from "react";
12260
+ import { useCallback as useCallback4, useMemo as useMemo4, useRef as useRef6 } from "react";
12232
12261
  var useOverlayEscapeGuard;
12233
12262
  var init_useOverlayEscapeGuard = __esm({
12234
12263
  "src/ui/hooks/useOverlayEscapeGuard.ts"() {
12235
12264
  "use strict";
12236
12265
  useOverlayEscapeGuard = ({ overlays, suppressionMs = 250 }) => {
12237
- const suppressUntilRef = useRef4(0);
12238
- const markOverlayClosed = useCallback3(() => {
12266
+ const suppressUntilRef = useRef6(0);
12267
+ const markOverlayClosed = useCallback4(() => {
12239
12268
  suppressUntilRef.current = Date.now() + suppressionMs;
12240
12269
  }, [suppressionMs]);
12241
- const isCancelSuppressed = useCallback3(() => Date.now() < suppressUntilRef.current, []);
12242
- const isOverlayOpen = useMemo5(() => overlays.some(Boolean), [overlays]);
12270
+ const isCancelSuppressed = useCallback4(() => Date.now() < suppressUntilRef.current, []);
12271
+ const isOverlayOpen = useMemo4(() => overlays.some(Boolean), [overlays]);
12243
12272
  return { isOverlayOpen, isCancelSuppressed, markOverlayClosed };
12244
12273
  };
12245
12274
  }
@@ -12250,7 +12279,7 @@ import { execSync as execSync6 } from "child_process";
12250
12279
  import { homedir as homedir8 } from "os";
12251
12280
  import { Box as Box27, Text as Text25, useApp as useApp2, useStdout as useStdout2 } from "ink";
12252
12281
  import Spinner4 from "ink-spinner";
12253
- import React30, { useEffect as useEffect13, useRef as useRef5, useState as useState16 } from "react";
12282
+ import React30, { useCallback as useCallback5, useEffect as useEffect13, useRef as useRef7, useState as useState16 } from "react";
12254
12283
  var getGitBranch2, getCurrentFolder2, AppContent, App;
12255
12284
  var init_App = __esm({
12256
12285
  "src/ui/App.tsx"() {
@@ -12302,15 +12331,15 @@ var init_App = __esm({
12302
12331
  AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession }) => {
12303
12332
  const { exit } = useApp2();
12304
12333
  const { stdout } = useStdout2();
12305
- const { addMessage, clearMessages, isAgentRunning, messages, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider } = useSession();
12334
+ const { addMessage, clearMessages, isAgentRunning, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider } = useSession();
12306
12335
  useModeToggle();
12307
12336
  const [terminalWidth, setTerminalWidth] = useState16(process.stdout.columns || 80);
12308
12337
  const [showInput, setShowInput] = useState16(true);
12309
12338
  const [gitBranch] = useState16(() => getGitBranch2());
12310
12339
  const [currentFolder] = useState16(() => getCurrentFolder2(config2.cwd));
12311
- const [hasInputContent, setHasInputContent] = useState16(false);
12340
+ const hasInputContentRef = useRef7(false);
12312
12341
  const [exitWarning, setExitWarning] = useState16(null);
12313
- const inputPromptRef = useRef5(null);
12342
+ const inputPromptRef = useRef7(null);
12314
12343
  const [showSessionSelector, setShowSessionSelector] = useState16(false);
12315
12344
  const [showModelSelector, setShowModelSelector] = useState16(false);
12316
12345
  const [showProviderSelector, setShowProviderSelector] = useState16(false);
@@ -12560,7 +12589,7 @@ var init_App = __esm({
12560
12589
  setShowAuthDialog(true);
12561
12590
  return;
12562
12591
  }
12563
- setHasInputContent(false);
12592
+ hasInputContentRef.current = false;
12564
12593
  setExitWarning(null);
12565
12594
  if (!isAgentRunning) {
12566
12595
  addMessage({
@@ -12573,6 +12602,9 @@ var init_App = __esm({
12573
12602
  onSubmitTask(task);
12574
12603
  }
12575
12604
  };
12605
+ const handleInputChange = useCallback5((val) => {
12606
+ hasInputContentRef.current = val.trim().length > 0;
12607
+ }, []);
12576
12608
  const handleSessionSelect = async (session) => {
12577
12609
  setShowSessionSelector(false);
12578
12610
  setIsLoadingSession(true);
@@ -12790,7 +12822,7 @@ var init_App = __esm({
12790
12822
  markOverlayClosed();
12791
12823
  setShowMcpServers(true);
12792
12824
  };
12793
- const isInitialMount = useRef5(true);
12825
+ const isInitialMount = useRef7(true);
12794
12826
  useEffect13(() => {
12795
12827
  const handleResize = () => {
12796
12828
  setTerminalWidth(process.stdout.columns || 80);
@@ -12839,8 +12871,9 @@ var init_App = __esm({
12839
12871
  } else if (exitWarning) {
12840
12872
  exit();
12841
12873
  onExit(true);
12842
- } else if (showInput && hasInputContent) {
12874
+ } else if (showInput && hasInputContentRef.current) {
12843
12875
  inputPromptRef.current?.clear();
12876
+ hasInputContentRef.current = false;
12844
12877
  setExitWarning("Press Ctrl+C again to exit");
12845
12878
  setTimeout(() => setExitWarning(null), 1500);
12846
12879
  } else {
@@ -12953,7 +12986,7 @@ var init_App = __esm({
12953
12986
  cwd: config2.cwd,
12954
12987
  gitBranch,
12955
12988
  isClaudeMax: !!config2.oauthToken,
12956
- onInputChange: (val) => setHasInputContent(val.trim().length > 0),
12989
+ onInputChange: handleInputChange,
12957
12990
  onSubmit: handleSubmitTask,
12958
12991
  placeholder: "Enter your task...",
12959
12992
  ref: inputPromptRef
@@ -12998,7 +13031,7 @@ __export(interactive_exports, {
12998
13031
  runInteractive: () => runInteractive
12999
13032
  });
13000
13033
  import { render as render2 } from "ink";
13001
- import React31, { useEffect as useEffect15, useRef as useRef6 } from "react";
13034
+ import React31, { useEffect as useEffect15, useRef as useRef8 } from "react";
13002
13035
  function getToolDescription2(toolName, input) {
13003
13036
  switch (toolName) {
13004
13037
  case "Read":
@@ -13147,9 +13180,14 @@ async function runInteractive(config2) {
13147
13180
  stdout: inkStdout,
13148
13181
  stderr: inkStderr,
13149
13182
  stdin: process.stdin,
13183
+ // Keep alternateBuffer: false for native terminal scrollback and text selection
13150
13184
  alternateBuffer: false,
13151
- // Like Gemini CLI - allows terminal to handle scroll & selection
13152
- exitOnCtrlC: false
13185
+ exitOnCtrlC: false,
13186
+ // Disable incrementalRendering - it doesn't work well without alternateBuffer
13187
+ // and can cause more flickering issues
13188
+ incrementalRendering: false,
13189
+ // Prevent Ink from patching console methods
13190
+ patchConsole: false
13153
13191
  }
13154
13192
  );
13155
13193
  unmountFn = unmount;
@@ -13199,7 +13237,6 @@ var init_interactive = __esm({
13199
13237
  setIsAgentRunning,
13200
13238
  updateStats,
13201
13239
  setTodos,
13202
- setUsageStats,
13203
13240
  shouldInterruptAgent,
13204
13241
  setShouldInterruptAgent,
13205
13242
  agentMode,
@@ -13208,7 +13245,8 @@ var init_interactive = __esm({
13208
13245
  selectedModel,
13209
13246
  llmProvider
13210
13247
  } = useSession();
13211
- const agentRef = useRef6(null);
13248
+ const { setUsageStats } = useUsageStats();
13249
+ const agentRef = useRef8(null);
13212
13250
  useEffect15(() => {
13213
13251
  if (shouldInterruptAgent && agentRef.current) {
13214
13252
  agentRef.current.abort();
@@ -13320,9 +13358,9 @@ var init_interactive = __esm({
13320
13358
  addMessage,
13321
13359
  loadMessages,
13322
13360
  setSessionId: setContextSessionId,
13323
- setIsAgentRunning,
13324
- setUsageStats
13361
+ setIsAgentRunning
13325
13362
  } = useSession();
13363
+ const { setUsageStats } = useUsageStats();
13326
13364
  const [sessionId, setSessionId] = React31.useState(initialSessionId);
13327
13365
  const [currentTask, setCurrentTask] = React31.useState(config2.task);
13328
13366
  const [taskId, setTaskId] = React31.useState(0);
@@ -13531,7 +13569,7 @@ init_SessionContext();
13531
13569
  import { execSync as execSync2 } from "child_process";
13532
13570
  import { homedir as homedir4 } from "os";
13533
13571
  import { Box as Box13, useApp } from "ink";
13534
- import React14, { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
13572
+ import React14, { useEffect as useEffect2, useRef as useRef4, useState as useState3 } from "react";
13535
13573
  var getGitBranch = () => {
13536
13574
  try {
13537
13575
  return execSync2("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
@@ -13554,10 +13592,10 @@ var HeadlessAgentRunner = ({ config: config2, sessionId, apiClient, onComplete }
13554
13592
  updateMessageByToolId,
13555
13593
  setIsAgentRunning,
13556
13594
  updateStats,
13557
- setTodos,
13558
- setUsageStats
13595
+ setTodos
13559
13596
  } = useSession();
13560
- const agentRef = useRef2(null);
13597
+ const { setUsageStats } = useUsageStats();
13598
+ const agentRef = useRef4(null);
13561
13599
  useEffect2(() => {
13562
13600
  let isMounted = true;
13563
13601
  const runAgent2 = async () => {
@@ -13645,7 +13683,7 @@ var HeadlessAppContent = ({
13645
13683
  const [gitBranch] = useState3(() => getGitBranch());
13646
13684
  const [currentFolder] = useState3(() => getCurrentFolder());
13647
13685
  const [terminalWidth, setTerminalWidth] = useState3(process.stdout.columns || 80);
13648
- const hasCompletedRef = useRef2(false);
13686
+ const hasCompletedRef = useRef4(false);
13649
13687
  useEffect2(() => {
13650
13688
  setSessionId(sessionId);
13651
13689
  if (webUrl) {
@@ -13813,10 +13851,22 @@ async function checkAndAutoUpdate() {
13813
13851
  console.log(`
13814
13852
  Updating Supatest CLI ${CLI_VERSION} \u2192 ${latest}...`);
13815
13853
  try {
13816
- execSync3("npm install -g @supatest/cli@latest", {
13817
- stdio: "inherit",
13818
- timeout: INSTALL_TIMEOUT
13819
- });
13854
+ try {
13855
+ const npmPrefix = execSync3("npm config get prefix", { encoding: "utf-8" }).trim();
13856
+ execSync3(`rm -rf ${npmPrefix}/lib/node_modules/@supatest/.cli-* 2>/dev/null || true`);
13857
+ } catch {
13858
+ }
13859
+ try {
13860
+ execSync3("npm update -g @supatest/cli", {
13861
+ stdio: "inherit",
13862
+ timeout: INSTALL_TIMEOUT
13863
+ });
13864
+ } catch {
13865
+ execSync3("npm install -g @supatest/cli@latest --force", {
13866
+ stdio: "inherit",
13867
+ timeout: INSTALL_TIMEOUT
13868
+ });
13869
+ }
13820
13870
  console.log("\u2713 Updated successfully\n");
13821
13871
  const child = spawn(process.argv[0], process.argv.slice(1), {
13822
13872
  stdio: "inherit",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supatest/cli",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "description": "Supatest CLI - AI-powered task automation for CI/CD",
5
5
  "type": "module",
6
6
  "bin": {