cueclaw 0.2.0 → 0.2.2

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.
@@ -268,8 +268,12 @@ var KeyPriority = {
268
268
  var KeypressContext = createContext(null);
269
269
  function KeypressProvider({ children }) {
270
270
  const handlersRef = useRef([]);
271
+ const sortHandlers = () => {
272
+ handlersRef.current.sort((a, b) => b.priority - a.priority);
273
+ };
271
274
  const register = useCallback((entry) => {
272
275
  handlersRef.current = [...handlersRef.current.filter((h) => h.id !== entry.id), entry];
276
+ sortHandlers();
273
277
  }, []);
274
278
  const unregister = useCallback((id) => {
275
279
  handlersRef.current = handlersRef.current.filter((h) => h.id !== id);
@@ -280,8 +284,8 @@ function KeypressProvider({ children }) {
280
284
  );
281
285
  }, []);
282
286
  useInput((input, key) => {
283
- const sorted = [...handlersRef.current].filter((h) => h.isActive).sort((a, b) => b.priority - a.priority);
284
- for (const entry of sorted) {
287
+ for (const entry of handlersRef.current) {
288
+ if (!entry.isActive) continue;
285
289
  const consumed = entry.handler(input, key);
286
290
  if (consumed === true) break;
287
291
  }
@@ -369,7 +373,7 @@ function DialogManager({ children }) {
369
373
  }
370
374
 
371
375
  // src/tui/app-provider.tsx
372
- import { useReducer, useCallback as useCallback7, useMemo as useMemo2, useState as useState4, useEffect as useEffect3 } from "react";
376
+ import { useReducer, useCallback as useCallback7, useMemo, useState as useState4, useEffect as useEffect3, useRef as useRef6 } from "react";
373
377
  import { useApp } from "ink";
374
378
 
375
379
  // src/tui/ui-state-context.ts
@@ -510,9 +514,13 @@ async function stopDaemonBridge(bridge) {
510
514
  function stopExternalDaemon() {
511
515
  const pid = readPidFile();
512
516
  if (pid && isProcessAlive(pid)) {
513
- process.kill(pid, "SIGTERM");
517
+ try {
518
+ process.kill(pid, "SIGTERM");
519
+ logger.info({ pid }, "Stopped external daemon");
520
+ } catch (err) {
521
+ logger.warn({ err, pid }, "Failed to stop external daemon");
522
+ }
514
523
  removePidFile();
515
- logger.info({ pid }, "Stopped external daemon");
516
524
  }
517
525
  }
518
526
 
@@ -528,7 +536,9 @@ function useDaemonBridge(config, db, cwd, dispatch) {
528
536
  dispatch({ type: "ADD_MESSAGE", message: { type: "system", text: "Starting daemon..." } });
529
537
  initDaemonBridge(db, config, cwd, { skipBots: !hasConfiguredBots }).then((bridge) => {
530
538
  if (cancelled) {
531
- stopDaemonBridge(bridge);
539
+ stopDaemonBridge(bridge).catch((err) => {
540
+ logger.error({ err }, "Failed to stop daemon bridge after cancellation");
541
+ });
532
542
  return;
533
543
  }
534
544
  bridgeRef.current = bridge;
@@ -563,7 +573,9 @@ function useDaemonBridge(config, db, cwd, dispatch) {
563
573
  return () => {
564
574
  cancelled = true;
565
575
  if (bridgeRef.current) {
566
- stopDaemonBridge(bridgeRef.current);
576
+ stopDaemonBridge(bridgeRef.current).catch((err) => {
577
+ logger.error({ err }, "Failed to stop daemon bridge during cleanup");
578
+ });
567
579
  bridgeRef.current = null;
568
580
  }
569
581
  };
@@ -708,7 +720,7 @@ Guidelines:
708
720
  }
709
721
 
710
722
  // src/tui/hooks/use-planner-session.ts
711
- function usePlannerSession(config, dispatch, streamingText) {
723
+ function usePlannerSession(config, dispatch) {
712
724
  const plannerSessionRef = useRef3(null);
713
725
  const handleUserMessage = useCallback3(async (text) => {
714
726
  if (!config) return;
@@ -717,28 +729,25 @@ function usePlannerSession(config, dispatch, streamingText) {
717
729
  dispatch({ type: "SET_STREAMING_TEXT", text: "" });
718
730
  try {
719
731
  let result;
732
+ let accumulated = "";
733
+ const onToken = (token) => {
734
+ accumulated += token;
735
+ dispatch({ type: "SET_STREAMING_TEXT", text: accumulated });
736
+ };
720
737
  const tuiContext = { channel: "tui" };
721
738
  if (plannerSessionRef.current && plannerSessionRef.current.status === "conversing") {
722
739
  result = await continuePlannerSession(
723
740
  plannerSessionRef.current,
724
741
  text,
725
742
  config,
726
- {
727
- onToken: (token) => {
728
- dispatch({ type: "SET_STREAMING_TEXT", text: (streamingText || "") + token });
729
- }
730
- },
743
+ { onToken },
731
744
  tuiContext
732
745
  );
733
746
  } else {
734
747
  result = await startPlannerSession(
735
748
  text,
736
749
  config,
737
- {
738
- onToken: (token) => {
739
- dispatch({ type: "SET_STREAMING_TEXT", text: (streamingText || "") + token });
740
- }
741
- },
750
+ { onToken },
742
751
  tuiContext
743
752
  );
744
753
  }
@@ -774,7 +783,7 @@ function usePlannerSession(config, dispatch, streamingText) {
774
783
  plannerSessionRef.current = null;
775
784
  logger.error({ err }, "Planner session failed");
776
785
  }
777
- }, [config, streamingText]);
786
+ }, [config]);
778
787
  const handleCancelGeneration = useCallback3(() => {
779
788
  if (plannerSessionRef.current) {
780
789
  cancelPlannerSession(plannerSessionRef.current);
@@ -801,6 +810,8 @@ function useWorkflowExecution(workflow, db, cwd, bridgeRef, plannerSessionRef, d
801
810
  upsertWorkflow(db, confirmed);
802
811
  } catch (err) {
803
812
  logger.error({ err }, "Failed to persist workflow");
813
+ dispatch({ type: "ADD_MESSAGE", message: { type: "error", text: `Failed to save workflow: ${err instanceof Error ? err.message : String(err)}` } });
814
+ return;
804
815
  }
805
816
  const controller = new AbortController();
806
817
  abortRef.current = controller;
@@ -873,7 +884,11 @@ ${failedSteps.join("\n")}` : "";
873
884
  const handleCancel = useCallback4(() => {
874
885
  if (workflow) {
875
886
  const rejected = rejectPlan(workflow);
876
- updateWorkflowPhase(db, workflow.id, rejected.phase);
887
+ try {
888
+ updateWorkflowPhase(db, workflow.id, rejected.phase);
889
+ } catch (err) {
890
+ logger.error({ err }, "Failed to update workflow phase");
891
+ }
877
892
  }
878
893
  if (plannerSessionRef.current) {
879
894
  plannerSessionRef.current = null;
@@ -1122,7 +1137,7 @@ function useGlobalKeypress({
1122
1137
  }
1123
1138
 
1124
1139
  // src/tui/hooks/use-command-dispatch.ts
1125
- import React4, { useCallback as useCallback6, useMemo } from "react";
1140
+ import React4, { useCallback as useCallback6, useRef as useRef5 } from "react";
1126
1141
 
1127
1142
  // src/tui/commands/registry.ts
1128
1143
  var commands = [];
@@ -1485,7 +1500,8 @@ function useCommandDispatch({
1485
1500
  showDialog,
1486
1501
  dismissDialog
1487
1502
  }) {
1488
- const commandCtx = useMemo(() => ({
1503
+ const commandCtxRef = useRef5(null);
1504
+ commandCtxRef.current = {
1489
1505
  db,
1490
1506
  config,
1491
1507
  cwd,
@@ -1494,13 +1510,12 @@ function useCommandDispatch({
1494
1510
  clearMessages: () => dispatch({ type: "SET_MESSAGES", messages: [] }),
1495
1511
  setConfig,
1496
1512
  setThemeVersion
1497
- }), [db, config, cwd]);
1513
+ };
1498
1514
  const handleSlashCommand = useCallback6(async (text) => {
1499
1515
  if (!config) return false;
1500
1516
  const parsed = parseSlashCommand(text);
1501
1517
  if (!parsed) return false;
1502
- commandCtx.bridge = bridgeRef.current;
1503
- commandCtx.config = config;
1518
+ const commandCtx = commandCtxRef.current;
1504
1519
  dispatch({ type: "ADD_MESSAGE", message: { type: "user", text } });
1505
1520
  if (parsed.name === "cancel") {
1506
1521
  if (plannerSessionRef.current) {
@@ -1589,8 +1604,8 @@ function useCommandDispatch({
1589
1604
  dispatch({ type: "ADD_MESSAGE", message: { type: "assistant", text: `Unknown command: /${parsed.name}. Type /help for available commands.` } });
1590
1605
  }
1591
1606
  return true;
1592
- }, [config, db, commandCtx]);
1593
- return { handleSlashCommand, commandCtx };
1607
+ }, [config, db, exit, showDialog, dismissDialog]);
1608
+ return { handleSlashCommand };
1594
1609
  }
1595
1610
 
1596
1611
  // src/tui/app-provider.tsx
@@ -1619,8 +1634,10 @@ function appReducer(state, action) {
1619
1634
  return { ...state, streamingText: action.text };
1620
1635
  case "UPDATE_STEP":
1621
1636
  return { ...state, stepProgress: new Map(state.stepProgress).set(action.stepId, action.progress) };
1622
- case "ADD_OUTPUT":
1623
- return { ...state, executionOutput: [...state.executionOutput, action.line] };
1637
+ case "ADD_OUTPUT": {
1638
+ const next = [...state.executionOutput, action.line];
1639
+ return { ...state, executionOutput: next.length > 200 ? next.slice(-200) : next };
1640
+ }
1624
1641
  default:
1625
1642
  return state;
1626
1643
  }
@@ -1628,7 +1645,7 @@ function appReducer(state, action) {
1628
1645
  function AppProvider({ cwd, skipOnboarding, children }) {
1629
1646
  const { exit } = useApp();
1630
1647
  const { showDialog, dismissDialog } = useDialog();
1631
- const validation = useMemo2(() => validateConfig(), []);
1648
+ const validation = useMemo(() => validateConfig(), []);
1632
1649
  const needsSetup = !skipOnboarding && !validation.valid;
1633
1650
  const [config, setConfig] = useState4(() => {
1634
1651
  if (needsSetup) return null;
@@ -1638,7 +1655,7 @@ function AppProvider({ cwd, skipOnboarding, children }) {
1638
1655
  return null;
1639
1656
  }
1640
1657
  });
1641
- const db = useMemo2(() => initDb(), []);
1658
+ const db = useMemo(() => initDb(), []);
1642
1659
  const [themeVersion, setThemeVersion] = useState4(0);
1643
1660
  const initialState = {
1644
1661
  view: needsSetup ? "onboarding" : "chat",
@@ -1653,7 +1670,16 @@ function AppProvider({ cwd, skipOnboarding, children }) {
1653
1670
  detailRuns: [],
1654
1671
  detailStepRuns: []
1655
1672
  };
1656
- const [state, dispatch] = useReducer(appReducer, initialState);
1673
+ const msgIdRef = useRef6(0);
1674
+ const [state, rawDispatch] = useReducer(appReducer, initialState);
1675
+ const dispatch = useCallback7((action) => {
1676
+ if (action.type === "ADD_MESSAGE") {
1677
+ msgIdRef.current += 1;
1678
+ rawDispatch({ type: "ADD_MESSAGE", message: { ...action.message, id: msgIdRef.current } });
1679
+ } else {
1680
+ rawDispatch(action);
1681
+ }
1682
+ }, []);
1657
1683
  useEffect3(() => {
1658
1684
  markSessionStart();
1659
1685
  }, []);
@@ -1663,7 +1689,7 @@ function AppProvider({ cwd, skipOnboarding, children }) {
1663
1689
  });
1664
1690
  }, []);
1665
1691
  const { bridgeRef, daemonStatus } = useDaemonBridge(config, db, cwd, dispatch);
1666
- const planner = usePlannerSession(config, dispatch, state.streamingText);
1692
+ const planner = usePlannerSession(config, dispatch);
1667
1693
  const execution = useWorkflowExecution(
1668
1694
  state.workflow,
1669
1695
  db,
@@ -1774,7 +1800,7 @@ function AppProvider({ cwd, skipOnboarding, children }) {
1774
1800
  execution.handleExecutionBack();
1775
1801
  }
1776
1802
  }, [state.previousView, state.workflow, db, execution.handleExecutionBack]);
1777
- const uiState = useMemo2(() => ({
1803
+ const uiState = useMemo(() => ({
1778
1804
  view: state.view,
1779
1805
  messages: state.messages,
1780
1806
  workflow: state.workflow,
@@ -1794,7 +1820,7 @@ function AppProvider({ cwd, skipOnboarding, children }) {
1794
1820
  detailRuns: state.detailRuns,
1795
1821
  detailStepRuns: state.detailStepRuns
1796
1822
  }), [state, daemonStatus, execution.isExecuting, config, cwd, footerExtra, footerHints, planner.isConversing, themeVersion]);
1797
- const uiActions = useMemo2(() => ({
1823
+ const uiActions = useMemo(() => ({
1798
1824
  handleChatSubmit,
1799
1825
  handleCancelGeneration: planner.handleCancelGeneration,
1800
1826
  handleConfirm: execution.handleConfirm,
@@ -1815,7 +1841,7 @@ function AppProvider({ cwd, skipOnboarding, children }) {
1815
1841
  }
1816
1842
 
1817
1843
  // src/tui/app-layout.tsx
1818
- import { useMemo as useMemo6 } from "react";
1844
+ import { useMemo as useMemo5 } from "react";
1819
1845
  import { Box as Box21, Static, useStdout as useStdout5 } from "ink";
1820
1846
 
1821
1847
  // src/tui/banner.tsx
@@ -1881,18 +1907,18 @@ function lerpGradient(stops, t) {
1881
1907
  import { Box as Box15 } from "ink";
1882
1908
 
1883
1909
  // src/tui/main-content.tsx
1884
- import { useState as useState6, useMemo as useMemo3, useEffect as useEffect5, useRef as useRef6, useCallback as useCallback9 } from "react";
1910
+ import { useState as useState6, useMemo as useMemo2, useCallback as useCallback9 } from "react";
1885
1911
  import { Box as Box13, Text as Text13, useStdout as useStdout2 } from "ink";
1886
1912
 
1887
1913
  // src/tui/thinking-indicator.tsx
1888
- import { useState as useState5, useEffect as useEffect4, useCallback as useCallback8, useRef as useRef5, memo as memo2 } from "react";
1914
+ import { useState as useState5, useEffect as useEffect4, useCallback as useCallback8, useRef as useRef7, memo as memo2 } from "react";
1889
1915
  import { Box as Box4, Text as Text4 } from "ink";
1890
1916
  import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1891
1917
  var DOTS = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1892
1918
  var ThinkingIndicator = memo2(function ThinkingIndicator2({ onCancel }) {
1893
1919
  const [elapsed, setElapsed] = useState5(0);
1894
1920
  const [frame, setFrame] = useState5(0);
1895
- const startRef = useRef5(Date.now());
1921
+ const startRef = useRef7(Date.now());
1896
1922
  useEffect4(() => {
1897
1923
  const timer = setInterval(() => {
1898
1924
  setElapsed(Math.floor((Date.now() - startRef.current) / 1e3));
@@ -1908,8 +1934,7 @@ var ThinkingIndicator = memo2(function ThinkingIndicator2({ onCancel }) {
1908
1934
  return false;
1909
1935
  }, [onCancel]));
1910
1936
  const gradient = theme.ui.gradient;
1911
- const cyclePos = Date.now() / 4e3 % 1;
1912
- const gradientIdx = Math.floor(cyclePos * gradient.length) % gradient.length;
1937
+ const gradientIdx = frame % gradient.length;
1913
1938
  const spinnerColor = gradient[gradientIdx] ?? theme.text.accent;
1914
1939
  const cancelHint = onCancel ? " (esc to cancel)" : "";
1915
1940
  return /* @__PURE__ */ jsxs4(Box4, { paddingX: 1, children: [
@@ -2048,12 +2073,6 @@ function MainContent() {
2048
2073
  const { stdout } = useStdout2();
2049
2074
  const rows = stdout?.rows ?? 24;
2050
2075
  const [scrollOffset, setScrollOffset] = useState6(0);
2051
- const prevMessageCountRef = useRef6(messages.length);
2052
- useEffect5(() => {
2053
- if (messages.length > prevMessageCountRef.current && scrollOffset === 0) {
2054
- }
2055
- prevMessageCountRef.current = messages.length;
2056
- }, [messages.length, scrollOffset]);
2057
2076
  const pageSize = Math.max(1, Math.floor(rows / 2));
2058
2077
  useKeypress("chat-scroll", KeyPriority.Normal, useCallback9((input, key) => {
2059
2078
  if (keyBindings.scrollUp(input, key)) {
@@ -2066,7 +2085,7 @@ function MainContent() {
2066
2085
  }
2067
2086
  return false;
2068
2087
  }, [pageSize, messages.length]));
2069
- const visibleMessages = useMemo3(() => {
2088
+ const visibleMessages = useMemo2(() => {
2070
2089
  if (scrollOffset === 0) return messages;
2071
2090
  const end = messages.length - scrollOffset;
2072
2091
  return messages.slice(0, Math.max(0, end));
@@ -2081,7 +2100,7 @@ function MainContent() {
2081
2100
  " (Ctrl+P/Ctrl+N)"
2082
2101
  ] }) }),
2083
2102
  /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", flexGrow: 1, marginTop: hiddenAbove > 0 ? 0 : 1, children: [
2084
- visibleMessages.map((msg, i) => /* @__PURE__ */ jsx16(Box13, { marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsx16(MessageDisplay, { message: msg }) }, i)),
2103
+ visibleMessages.map((msg) => /* @__PURE__ */ jsx16(Box13, { marginBottom: 1, flexDirection: "column", children: /* @__PURE__ */ jsx16(MessageDisplay, { message: msg }) }, msg.id)),
2085
2104
  streamingText && /* @__PURE__ */ jsxs12(Box13, { marginBottom: 1, paddingX: 1, children: [
2086
2105
  /* @__PURE__ */ jsx16(Box13, { width: 2, children: /* @__PURE__ */ jsx16(Text13, { color: theme.text.accent, children: "\u2726 " }) }),
2087
2106
  /* @__PURE__ */ jsx16(Box13, { flexShrink: 1, children: /* @__PURE__ */ jsx16(Text13, { color: theme.text.primary, children: streamingText }) })
@@ -2092,11 +2111,11 @@ function MainContent() {
2092
2111
  }
2093
2112
 
2094
2113
  // src/tui/composer.tsx
2095
- import { useState as useState7, useMemo as useMemo4 } from "react";
2114
+ import { useState as useState7, useMemo as useMemo3, useCallback as useCallback12 } from "react";
2096
2115
  import { Box as Box14, Text as Text15, useStdout as useStdout3 } from "ink";
2097
2116
 
2098
2117
  // src/tui/resettable-input.tsx
2099
- import { useReducer as useReducer2, useEffect as useEffect6, useRef as useRef7, useCallback as useCallback10 } from "react";
2118
+ import { useReducer as useReducer2, useEffect as useEffect5, useRef as useRef8, useCallback as useCallback10 } from "react";
2100
2119
  import { Text as Text14 } from "ink";
2101
2120
  import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
2102
2121
  function inputReducer(state, action) {
@@ -2122,13 +2141,13 @@ function inputReducer(state, action) {
2122
2141
  }
2123
2142
  function ResettableInput({ placeholder, onSubmit, onChange, isDisabled, onUpArrow, onDownArrow }) {
2124
2143
  const [state, dispatch] = useReducer2(inputReducer, { value: "", prevValue: "", cursor: 0 });
2125
- const submitRef = useRef7(null);
2126
- useEffect6(() => {
2144
+ const submitRef = useRef8(null);
2145
+ useEffect5(() => {
2127
2146
  if (state.value !== state.prevValue) {
2128
2147
  onChange?.(state.value);
2129
2148
  }
2130
2149
  }, [state.value, state.prevValue, onChange]);
2131
- useEffect6(() => {
2150
+ useEffect5(() => {
2132
2151
  if (submitRef.current !== null) {
2133
2152
  const value = submitRef.current;
2134
2153
  submitRef.current = null;
@@ -2136,7 +2155,7 @@ function ResettableInput({ placeholder, onSubmit, onChange, isDisabled, onUpArro
2136
2155
  }
2137
2156
  });
2138
2157
  useKeypress("resettable-input", KeyPriority.Normal, useCallback10((input, key) => {
2139
- if (key.ctrl && input === "c" || key.tab || key.shift && key.tab) return false;
2158
+ if (key.ctrl && input === "c" || key.tab || key.shift && key.tab || key.escape) return false;
2140
2159
  if (key.upArrow) {
2141
2160
  if (onUpArrow) {
2142
2161
  const entry = onUpArrow(state.value);
@@ -2188,11 +2207,11 @@ function ResettableInput({ placeholder, onSubmit, onChange, isDisabled, onUpArro
2188
2207
  }
2189
2208
 
2190
2209
  // src/tui/use-input-history.ts
2191
- import { useRef as useRef8, useCallback as useCallback11 } from "react";
2210
+ import { useRef as useRef9, useCallback as useCallback11 } from "react";
2192
2211
  function useInputHistory() {
2193
- const historyRef = useRef8([]);
2194
- const indexRef = useRef8(-1);
2195
- const draftRef = useRef8("");
2212
+ const historyRef = useRef9([]);
2213
+ const indexRef = useRef9(-1);
2214
+ const draftRef = useRef9("");
2196
2215
  const up = useCallback11((currentValue) => {
2197
2216
  const history = historyRef.current;
2198
2217
  if (history.length === 0) return void 0;
@@ -2259,8 +2278,8 @@ function Composer() {
2259
2278
  const cols = stdout?.columns ?? 80;
2260
2279
  const history = useInputHistory();
2261
2280
  const [currentInput, setCurrentInput] = useState7("");
2262
- const allCommands = useMemo4(() => getCommands(), []);
2263
- const matchingCommands = useMemo4(() => {
2281
+ const allCommands = useMemo3(() => getCommands(), []);
2282
+ const matchingCommands = useMemo3(() => {
2264
2283
  if (!currentInput.startsWith("/")) return [];
2265
2284
  const prefix = currentInput.toLowerCase();
2266
2285
  return allCommands.filter((c) => {
@@ -2269,6 +2288,18 @@ function Composer() {
2269
2288
  });
2270
2289
  }, [currentInput, allCommands]);
2271
2290
  const showCommandHints = currentInput.startsWith("/") && matchingCommands.length > 0 && currentInput !== "/" + matchingCommands[0]?.name;
2291
+ const handleInputChange = useCallback12((value) => {
2292
+ setCurrentInput(value);
2293
+ history.resetBrowsing();
2294
+ }, [history]);
2295
+ const handleInputSubmit = useCallback12((value) => {
2296
+ const trimmed = value.trim();
2297
+ if (trimmed) {
2298
+ history.push(trimmed);
2299
+ setCurrentInput("");
2300
+ handleChatSubmit(trimmed);
2301
+ }
2302
+ }, [history, handleChatSubmit]);
2272
2303
  const mode = modeLabel({ isExecuting, isGenerating, isConversing });
2273
2304
  const daemon = daemonLabel(daemonStatus);
2274
2305
  return /* @__PURE__ */ jsxs14(Fragment3, { children: [
@@ -2311,18 +2342,8 @@ function Composer() {
2311
2342
  ResettableInput,
2312
2343
  {
2313
2344
  placeholder: "Describe a workflow or type /help",
2314
- onChange: (value) => {
2315
- setCurrentInput(value);
2316
- history.resetBrowsing();
2317
- },
2318
- onSubmit: (value) => {
2319
- const trimmed = value.trim();
2320
- if (trimmed) {
2321
- history.push(trimmed);
2322
- setCurrentInput("");
2323
- handleChatSubmit(trimmed);
2324
- }
2325
- },
2345
+ onChange: handleInputChange,
2346
+ onSubmit: handleInputSubmit,
2326
2347
  onUpArrow: history.up,
2327
2348
  onDownArrow: history.down,
2328
2349
  isDisabled: isGenerating
@@ -2344,11 +2365,11 @@ function Chat() {
2344
2365
  }
2345
2366
 
2346
2367
  // src/tui/plan-view.tsx
2347
- import { useCallback as useCallback12, memo as memo10 } from "react";
2368
+ import { useCallback as useCallback13, memo as memo10 } from "react";
2348
2369
  import { Box as Box16, Text as Text16 } from "ink";
2349
2370
  import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
2350
2371
  function PlanView({ workflow, onConfirm, onModify, onCancel }) {
2351
- useKeypress("plan-view-actions", KeyPriority.Normal, useCallback12((input, key) => {
2372
+ useKeypress("plan-view-actions", KeyPriority.Normal, useCallback13((input, key) => {
2352
2373
  if (keyBindings.confirmPlan(input, key)) {
2353
2374
  onConfirm();
2354
2375
  return true;
@@ -2411,13 +2432,90 @@ var StepLine = memo10(function StepLine2({ step, index }) {
2411
2432
  });
2412
2433
 
2413
2434
  // src/tui/execution-view.tsx
2414
- import { useCallback as useCallback13 } from "react";
2435
+ import { useCallback as useCallback14 } from "react";
2415
2436
  import { Box as Box17, Text as Text17 } from "ink";
2437
+
2438
+ // src/tui/format-utils.ts
2439
+ function stepStatusIcon(status) {
2440
+ switch (status) {
2441
+ case "succeeded":
2442
+ return "\u2713";
2443
+ case "running":
2444
+ return "\u22B7";
2445
+ case "failed":
2446
+ return "\u2717";
2447
+ case "skipped":
2448
+ return "\u25CB";
2449
+ default:
2450
+ return "\u25CB";
2451
+ }
2452
+ }
2453
+ function stepStatusColor(status) {
2454
+ switch (status) {
2455
+ case "succeeded":
2456
+ return theme.status.success;
2457
+ case "running":
2458
+ return theme.status.warning;
2459
+ case "failed":
2460
+ return theme.status.error;
2461
+ case "skipped":
2462
+ return theme.status.muted;
2463
+ default:
2464
+ return theme.status.muted;
2465
+ }
2466
+ }
2467
+ function phaseColor2(phase) {
2468
+ switch (phase) {
2469
+ case "executing":
2470
+ return theme.status.warning;
2471
+ case "active":
2472
+ return theme.status.success;
2473
+ case "completed":
2474
+ return theme.status.success;
2475
+ case "failed":
2476
+ return theme.status.error;
2477
+ case "paused":
2478
+ return theme.status.muted;
2479
+ default:
2480
+ return theme.text.primary;
2481
+ }
2482
+ }
2483
+ function runStatusColor(status) {
2484
+ switch (status) {
2485
+ case "completed":
2486
+ return theme.status.success;
2487
+ case "running":
2488
+ return theme.status.warning;
2489
+ case "failed":
2490
+ return theme.status.error;
2491
+ default:
2492
+ return theme.text.primary;
2493
+ }
2494
+ }
2495
+ function formatDuration2(ms) {
2496
+ if (ms < 1e3) return `${ms}ms`;
2497
+ if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
2498
+ return `${Math.round(ms / 6e4)}m`;
2499
+ }
2500
+ function formatTrigger(trigger) {
2501
+ switch (trigger.type) {
2502
+ case "poll":
2503
+ return `poll every ${trigger.interval_seconds}s (${trigger.diff_mode})`;
2504
+ case "cron":
2505
+ return `cron: ${trigger.expression}${trigger.timezone ? ` (${trigger.timezone})` : ""}`;
2506
+ case "manual":
2507
+ return "manual";
2508
+ default:
2509
+ return trigger.type;
2510
+ }
2511
+ }
2512
+
2513
+ // src/tui/execution-view.tsx
2416
2514
  import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
2417
2515
  function ExecutionView({ workflow, stepProgress, output, onBack, onAbort }) {
2418
2516
  const { isExecuting } = useUIState();
2419
2517
  const isRunning = Array.from(stepProgress.values()).some((s) => s.status === "running");
2420
- useKeypress("execution-view-actions", KeyPriority.Normal, useCallback13((input, key) => {
2518
+ useKeypress("execution-view-actions", KeyPriority.Normal, useCallback14((input, key) => {
2421
2519
  if (isExecuting && onAbort && keyBindings.abortExec(input, key)) {
2422
2520
  onAbort();
2423
2521
  return true;
@@ -2445,9 +2543,9 @@ function ExecutionView({ workflow, stepProgress, output, onBack, onAbort }) {
2445
2543
  workflow.steps.map((step, i) => {
2446
2544
  const progress = stepProgress.get(step.id);
2447
2545
  const status = progress?.status ?? "pending";
2448
- const icon = statusIcon(status);
2546
+ const icon = stepStatusIcon(status);
2449
2547
  const durationText = progress?.duration ? ` (${formatDuration2(progress.duration)})` : "";
2450
- return /* @__PURE__ */ jsx21(Box17, { children: /* @__PURE__ */ jsxs17(Text17, { color: statusColor(status), children: [
2548
+ return /* @__PURE__ */ jsx21(Box17, { children: /* @__PURE__ */ jsxs17(Text17, { color: stepStatusColor(status), children: [
2451
2549
  icon,
2452
2550
  " ",
2453
2551
  i + 1,
@@ -2464,47 +2562,20 @@ function ExecutionView({ workflow, stepProgress, output, onBack, onAbort }) {
2464
2562
  /* @__PURE__ */ jsx21(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text17, { color: theme.ui.comment, children: isExecuting ? "Press [X] to cancel" : "Press Enter, Q, or Esc to return to chat" }) })
2465
2563
  ] });
2466
2564
  }
2467
- function statusIcon(status) {
2468
- switch (status) {
2469
- case "succeeded":
2470
- return "\u2713";
2471
- case "running":
2472
- return "\u22B7";
2473
- case "failed":
2474
- return "\u2717";
2475
- case "skipped":
2476
- return "\u25CB";
2477
- default:
2478
- return "\u25CB";
2479
- }
2480
- }
2481
- function statusColor(status) {
2482
- switch (status) {
2483
- case "succeeded":
2484
- return theme.status.success;
2485
- case "running":
2486
- return theme.status.warning;
2487
- case "failed":
2488
- return theme.status.error;
2489
- case "skipped":
2490
- return theme.status.muted;
2491
- default:
2492
- return theme.status.muted;
2493
- }
2494
- }
2495
- function formatDuration2(ms) {
2496
- if (ms < 1e3) return `${ms}ms`;
2497
- return `${Math.round(ms / 1e3)}s`;
2498
- }
2499
2565
 
2500
2566
  // src/tui/workflow-detail-view.tsx
2501
- import { useState as useState8, useCallback as useCallback14 } from "react";
2567
+ import { useState as useState8, useCallback as useCallback15, useEffect as useEffect6 } from "react";
2502
2568
  import { Box as Box18, Text as Text18 } from "ink";
2503
2569
  import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
2504
2570
  function WorkflowDetailView({ workflow, runs, latestStepRuns, onBack, onSelectRun, onStop }) {
2505
2571
  const [selectedRunIndex, setSelectedRunIndex] = useState8(0);
2506
2572
  const displayRuns = runs.slice(0, 5);
2507
- useKeypress("detail-view-actions", KeyPriority.Normal, useCallback14((input, key) => {
2573
+ useEffect6(() => {
2574
+ if (selectedRunIndex >= displayRuns.length && displayRuns.length > 0) {
2575
+ setSelectedRunIndex(displayRuns.length - 1);
2576
+ }
2577
+ }, [displayRuns.length, selectedRunIndex]);
2578
+ useKeypress("detail-view-actions", KeyPriority.Normal, useCallback15((input, key) => {
2508
2579
  if (keyBindings.escape(input, key) || keyBindings.quit(input, key)) {
2509
2580
  onBack();
2510
2581
  return true;
@@ -2566,8 +2637,8 @@ function WorkflowDetailView({ workflow, runs, latestStepRuns, onBack, onSelectRu
2566
2637
  workflow.steps.map((step, i) => {
2567
2638
  const deps = step.depends_on.length > 0 ? ` \u2192 depends on: ${step.depends_on.join(", ")}` : "";
2568
2639
  const stepRun = latestStepRuns.find((sr) => sr.step_id === step.id);
2569
- const icon = stepRun ? statusIcon2(stepRun.status) : "\u25CB";
2570
- const iconColor = stepRun ? statusColor2(stepRun.status) : theme.status.muted;
2640
+ const icon = stepRun ? stepStatusIcon(stepRun.status) : "\u25CB";
2641
+ const iconColor = stepRun ? stepStatusColor(stepRun.status) : theme.status.muted;
2571
2642
  return /* @__PURE__ */ jsxs18(Box18, { children: [
2572
2643
  /* @__PURE__ */ jsxs18(Text18, { color: iconColor, children: [
2573
2644
  icon,
@@ -2593,7 +2664,7 @@ function WorkflowDetailView({ workflow, runs, latestStepRuns, onBack, onSelectRu
2593
2664
  displayRuns.map((run, i) => /* @__PURE__ */ jsx22(Box18, { children: /* @__PURE__ */ jsxs18(Text18, { inverse: i === selectedRunIndex, children: [
2594
2665
  /* @__PURE__ */ jsx22(Text18, { color: runStatusColor(run.status), children: run.status.padEnd(14) }),
2595
2666
  /* @__PURE__ */ jsx22(Text18, { children: run.started_at.slice(0, 22).padEnd(24) }),
2596
- /* @__PURE__ */ jsx22(Text18, { children: (run.duration_ms != null ? formatDuration3(run.duration_ms) : "\u2014").padEnd(12) }),
2667
+ /* @__PURE__ */ jsx22(Text18, { children: (run.duration_ms != null ? formatDuration2(run.duration_ms) : "\u2014").padEnd(12) }),
2597
2668
  /* @__PURE__ */ jsx22(Text18, { color: theme.status.error, children: run.error ? run.error.slice(0, 40) : "" })
2598
2669
  ] }) }, run.id))
2599
2670
  ] })
@@ -2605,80 +2676,9 @@ function WorkflowDetailView({ workflow, runs, latestStepRuns, onBack, onSelectRu
2605
2676
  ] }) })
2606
2677
  ] });
2607
2678
  }
2608
- function phaseColor2(phase) {
2609
- switch (phase) {
2610
- case "executing":
2611
- return theme.status.warning;
2612
- case "active":
2613
- return theme.status.success;
2614
- case "completed":
2615
- return theme.status.success;
2616
- case "failed":
2617
- return theme.status.error;
2618
- case "paused":
2619
- return theme.status.muted;
2620
- default:
2621
- return theme.text.primary;
2622
- }
2623
- }
2624
- function runStatusColor(status) {
2625
- switch (status) {
2626
- case "completed":
2627
- return theme.status.success;
2628
- case "running":
2629
- return theme.status.warning;
2630
- case "failed":
2631
- return theme.status.error;
2632
- default:
2633
- return theme.text.primary;
2634
- }
2635
- }
2636
- function statusIcon2(status) {
2637
- switch (status) {
2638
- case "succeeded":
2639
- return "\u2713";
2640
- case "running":
2641
- return "\u22B7";
2642
- case "failed":
2643
- return "\u2717";
2644
- case "skipped":
2645
- return "\u25CB";
2646
- default:
2647
- return "\u25CB";
2648
- }
2649
- }
2650
- function statusColor2(status) {
2651
- switch (status) {
2652
- case "succeeded":
2653
- return theme.status.success;
2654
- case "running":
2655
- return theme.status.warning;
2656
- case "failed":
2657
- return theme.status.error;
2658
- case "skipped":
2659
- return theme.status.muted;
2660
- default:
2661
- return theme.status.muted;
2662
- }
2663
- }
2664
- function formatTrigger(trigger) {
2665
- switch (trigger.type) {
2666
- case "poll":
2667
- return `poll every ${trigger.interval_seconds}s (${trigger.diff_mode})`;
2668
- case "cron":
2669
- return `cron: ${trigger.expression}${trigger.timezone ? ` (${trigger.timezone})` : ""}`;
2670
- case "manual":
2671
- return "manual";
2672
- }
2673
- }
2674
- function formatDuration3(ms) {
2675
- if (ms < 1e3) return `${ms}ms`;
2676
- if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
2677
- return `${Math.round(ms / 6e4)}m`;
2678
- }
2679
2679
 
2680
2680
  // src/tui/onboarding.tsx
2681
- import { useState as useState9, useCallback as useCallback15, useMemo as useMemo5 } from "react";
2681
+ import { useState as useState9, useCallback as useCallback16, useMemo as useMemo4, useRef as useRef10, useEffect as useEffect7 } from "react";
2682
2682
  import { Box as Box19, Text as Text19, useStdout as useStdout4 } from "ink";
2683
2683
  import { TextInput, PasswordInput, ConfirmInput, Spinner, StatusMessage } from "@inkjs/ui";
2684
2684
  import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
@@ -2686,11 +2686,23 @@ function maskKey(key) {
2686
2686
  if (key.length <= 8) return "****";
2687
2687
  return key.slice(0, 4) + "****" + key.slice(-4);
2688
2688
  }
2689
+ function StepLayout({ children, input }) {
2690
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", flexGrow: 1, children: [
2691
+ /* @__PURE__ */ jsx23(Box19, { flexDirection: "column", flexGrow: 1, children }),
2692
+ /* @__PURE__ */ jsx23(Box19, { marginTop: 1, children: input })
2693
+ ] });
2694
+ }
2689
2695
  function Onboarding({ onComplete, onCancel, issues }) {
2690
2696
  const { stdout } = useStdout4();
2691
2697
  const cols = stdout?.columns ?? 80;
2692
- const existing = useMemo5(() => loadExistingConfig(), []);
2693
- const initialStep = useMemo5(() => {
2698
+ const existing = useMemo4(() => loadExistingConfig(), []);
2699
+ const completeTimerRef = useRef10(null);
2700
+ useEffect7(() => {
2701
+ return () => {
2702
+ if (completeTimerRef.current) clearTimeout(completeTimerRef.current);
2703
+ };
2704
+ }, []);
2705
+ const initialStep = useMemo4(() => {
2694
2706
  if (!issues || issues.length === 0) return "welcome";
2695
2707
  const errorFields = new Set(issues.filter((i) => i.severity === "error").map((i) => i.field));
2696
2708
  if (errorFields.size === 1 && errorFields.has("claude.api_key")) {
@@ -2707,62 +2719,46 @@ function Onboarding({ onComplete, onCancel, issues }) {
2707
2719
  telegramToken: existing.telegramToken ?? "",
2708
2720
  whatsappEnabled: existing.whatsappEnabled ?? false
2709
2721
  });
2710
- const env = checkEnvironment();
2711
- useKeypress("onboarding-cancel", KeyPriority.Normal, useCallback15((input, key) => {
2722
+ const env = useMemo4(() => checkEnvironment(), []);
2723
+ useKeypress("onboarding-cancel", KeyPriority.Normal, useCallback16((input, key) => {
2712
2724
  if (onCancel && keyBindings.escape(input, key)) {
2725
+ if (completeTimerRef.current) clearTimeout(completeTimerRef.current);
2713
2726
  onCancel();
2714
2727
  return true;
2715
2728
  }
2716
2729
  return false;
2717
2730
  }, [onCancel]));
2718
- const gotoApiKey = useCallback15(() => {
2731
+ const gotoApiKey = useCallback16(() => {
2719
2732
  setStep(existing.apiKey ? "api_key_existing" : "api_key");
2720
2733
  }, [existing]);
2721
- const gotoBaseUrl = useCallback15(() => {
2734
+ const gotoBaseUrl = useCallback16(() => {
2722
2735
  if (!isDev) return;
2723
2736
  setStep(existing.baseUrl ? "base_url_existing" : "base_url");
2724
2737
  }, [existing]);
2725
- const gotoContainer = useCallback15(() => {
2738
+ const gotoTelegram = useCallback16(() => {
2739
+ setStep(existing.telegramEnabled !== void 0 ? "telegram_existing" : "telegram");
2740
+ }, [existing]);
2741
+ const gotoContainer = useCallback16(() => {
2726
2742
  if (!(env.docker && env.dockerRunning)) {
2727
2743
  gotoTelegram();
2728
2744
  return;
2729
2745
  }
2730
2746
  setStep(existing.containerEnabled !== void 0 ? "container_existing" : "container");
2731
- }, [env, existing]);
2732
- const gotoTelegram = useCallback15(() => {
2733
- setStep(existing.telegramEnabled !== void 0 ? "telegram_existing" : "telegram");
2734
- }, [existing]);
2735
- const gotoTelegramToken = useCallback15(() => {
2747
+ }, [env, existing, gotoTelegram]);
2748
+ const gotoTelegramToken = useCallback16(() => {
2736
2749
  setStep(existing.telegramToken ? "telegram_token_existing" : "telegram_token");
2737
2750
  }, [existing]);
2738
- const gotoWhatsApp = useCallback15(() => {
2751
+ const gotoWhatsApp = useCallback16(() => {
2739
2752
  setStep(existing.whatsappEnabled !== void 0 ? "whatsapp_existing" : "whatsapp");
2740
2753
  }, [existing]);
2741
- const afterValidation = useCallback15(() => {
2754
+ const afterValidation = useCallback16(() => {
2742
2755
  if (env.docker && env.dockerRunning) {
2743
2756
  gotoContainer();
2744
2757
  } else {
2745
2758
  gotoTelegram();
2746
2759
  }
2747
2760
  }, [env, gotoContainer, gotoTelegram]);
2748
- const handleApiKeySubmit = useCallback15((value) => {
2749
- const key = value.trim();
2750
- if (!key) return;
2751
- setState((s) => ({ ...s, apiKey: key }));
2752
- if (isDev) {
2753
- gotoBaseUrl();
2754
- } else {
2755
- setStep("validating");
2756
- doValidation(key, "");
2757
- }
2758
- }, [gotoBaseUrl]);
2759
- const handleBaseUrlSubmit = useCallback15((value) => {
2760
- const url = value.trim();
2761
- setState((s) => ({ ...s, baseUrl: url }));
2762
- setStep("validating");
2763
- doValidation(state.apiKey, url);
2764
- }, [state.apiKey]);
2765
- const doValidation = useCallback15(async (apiKey, baseUrl) => {
2761
+ const doValidation = useCallback16(async (apiKey, baseUrl) => {
2766
2762
  const prevKey = process.env["ANTHROPIC_API_KEY"];
2767
2763
  const prevUrl = process.env["ANTHROPIC_BASE_URL"];
2768
2764
  process.env["ANTHROPIC_API_KEY"] = apiKey;
@@ -2791,35 +2787,44 @@ function Onboarding({ onComplete, onCancel, issues }) {
2791
2787
  else delete process.env["ANTHROPIC_BASE_URL"];
2792
2788
  }
2793
2789
  }, [afterValidation]);
2794
- const handleContainerYes = useCallback15(() => {
2790
+ const handleApiKeySubmit = useCallback16((value) => {
2791
+ const key = value.trim();
2792
+ if (!key) return;
2793
+ setState((s) => ({ ...s, apiKey: key }));
2794
+ if (isDev) {
2795
+ gotoBaseUrl();
2796
+ } else {
2797
+ setStep("validating");
2798
+ doValidation(key, "");
2799
+ }
2800
+ }, [gotoBaseUrl, doValidation]);
2801
+ const handleBaseUrlSubmit = useCallback16((value) => {
2802
+ const url = value.trim();
2803
+ setState((s) => ({ ...s, baseUrl: url }));
2804
+ setStep("validating");
2805
+ doValidation(state.apiKey, url);
2806
+ }, [state.apiKey, doValidation]);
2807
+ const handleContainerYes = useCallback16(() => {
2795
2808
  setState((s) => ({ ...s, containerEnabled: true }));
2796
2809
  gotoTelegram();
2797
2810
  }, [gotoTelegram]);
2798
- const handleContainerNo = useCallback15(() => {
2811
+ const handleContainerNo = useCallback16(() => {
2799
2812
  setState((s) => ({ ...s, containerEnabled: false }));
2800
2813
  gotoTelegram();
2801
2814
  }, [gotoTelegram]);
2802
- const handleTelegramYes = useCallback15(() => {
2815
+ const handleTelegramYes = useCallback16(() => {
2803
2816
  setState((s) => ({ ...s, telegramEnabled: true }));
2804
2817
  gotoTelegramToken();
2805
2818
  }, [gotoTelegramToken]);
2806
- const handleTelegramNo = useCallback15(() => {
2819
+ const handleTelegramNo = useCallback16(() => {
2807
2820
  setState((s) => ({ ...s, telegramEnabled: false }));
2808
2821
  gotoWhatsApp();
2809
2822
  }, [gotoWhatsApp]);
2810
- const handleTelegramTokenSubmit = useCallback15((value) => {
2823
+ const handleTelegramTokenSubmit = useCallback16((value) => {
2811
2824
  setState((s) => ({ ...s, telegramToken: value.trim() }));
2812
2825
  gotoWhatsApp();
2813
2826
  }, [gotoWhatsApp]);
2814
- const finishWithWhatsApp = useCallback15((enabled) => {
2815
- const next = { ...state, whatsappEnabled: enabled };
2816
- setState((s) => ({ ...s, whatsappEnabled: enabled }));
2817
- setStep("saving");
2818
- doSaveConfig(next);
2819
- }, [state]);
2820
- const handleWhatsAppYes = useCallback15(() => finishWithWhatsApp(true), [finishWithWhatsApp]);
2821
- const handleWhatsAppNo = useCallback15(() => finishWithWhatsApp(false), [finishWithWhatsApp]);
2822
- const doSaveConfig = useCallback15((finalState) => {
2827
+ const doSaveConfig = useCallback16((finalState) => {
2823
2828
  if (isDev) {
2824
2829
  writeEnvVar("ANTHROPIC_API_KEY", finalState.apiKey);
2825
2830
  if (finalState.baseUrl) {
@@ -2848,12 +2853,16 @@ function Onboarding({ onComplete, onCancel, issues }) {
2848
2853
  }
2849
2854
  const config = loadConfig();
2850
2855
  setStep("done");
2851
- setTimeout(() => onComplete(config), 1500);
2856
+ completeTimerRef.current = setTimeout(() => onComplete(config), 1500);
2852
2857
  }, [onComplete]);
2853
- const StepLayout = ({ children, input }) => /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", flexGrow: 1, children: [
2854
- /* @__PURE__ */ jsx23(Box19, { flexDirection: "column", flexGrow: 1, children }),
2855
- /* @__PURE__ */ jsx23(Box19, { marginTop: 1, children: input })
2856
- ] });
2858
+ const finishWithWhatsApp = useCallback16((enabled) => {
2859
+ const next = { ...state, whatsappEnabled: enabled };
2860
+ setState((s) => ({ ...s, whatsappEnabled: enabled }));
2861
+ setStep("saving");
2862
+ doSaveConfig(next);
2863
+ }, [state, doSaveConfig]);
2864
+ const handleWhatsAppYes = useCallback16(() => finishWithWhatsApp(true), [finishWithWhatsApp]);
2865
+ const handleWhatsAppNo = useCallback16(() => finishWithWhatsApp(false), [finishWithWhatsApp]);
2857
2866
  return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2858
2867
  onCancel && /* @__PURE__ */ jsx23(Box19, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx23(Text19, { dimColor: true, children: "Press Esc to cancel" }) }),
2859
2868
  step === "welcome" && /* @__PURE__ */ jsxs19(
@@ -3106,19 +3115,24 @@ function Onboarding({ onComplete, onCancel, issues }) {
3106
3115
  }
3107
3116
 
3108
3117
  // src/tui/status.tsx
3109
- import { useState as useState10, useCallback as useCallback16, useEffect as useEffect7 } from "react";
3118
+ import { useState as useState10, useCallback as useCallback17, useEffect as useEffect8 } from "react";
3110
3119
  import { Box as Box20, Text as Text20 } from "ink";
3111
3120
  import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
3112
3121
  function Status({ workflows, onSelect, onBack, onStop, onDelete }) {
3113
3122
  const [selectedIndex, setSelectedIndex] = useState10(0);
3114
3123
  const [confirm, setConfirm] = useState10(null);
3115
3124
  const [message, setMessage] = useState10(null);
3116
- useEffect7(() => {
3125
+ useEffect8(() => {
3126
+ if (selectedIndex >= workflows.length && workflows.length > 0) {
3127
+ setSelectedIndex(workflows.length - 1);
3128
+ }
3129
+ }, [workflows.length, selectedIndex]);
3130
+ useEffect8(() => {
3117
3131
  if (!message) return;
3118
3132
  const timer = setTimeout(() => setMessage(null), 3e3);
3119
3133
  return () => clearTimeout(timer);
3120
3134
  }, [message]);
3121
- useKeypress("status-view", KeyPriority.Normal, useCallback16((input, key) => {
3135
+ useKeypress("status-view", KeyPriority.Normal, useCallback17((input, key) => {
3122
3136
  if (confirm) {
3123
3137
  if (keyBindings.confirmYes(input, key)) {
3124
3138
  const wf = workflows.find((w) => w.id === confirm.workflowId);
@@ -3167,22 +3181,6 @@ function Status({ workflows, onSelect, onBack, onStop, onDelete }) {
3167
3181
  }
3168
3182
  return false;
3169
3183
  }, [confirm, message, selectedIndex, workflows, onSelect, onBack, onStop, onDelete]));
3170
- const phaseColor3 = (phase) => {
3171
- switch (phase) {
3172
- case "executing":
3173
- return theme.status.warning;
3174
- case "active":
3175
- return theme.status.success;
3176
- case "completed":
3177
- return theme.status.success;
3178
- case "failed":
3179
- return theme.status.error;
3180
- case "paused":
3181
- return theme.status.muted;
3182
- default:
3183
- return theme.text.primary;
3184
- }
3185
- };
3186
3184
  return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
3187
3185
  /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", flexGrow: 1, children: [
3188
3186
  /* @__PURE__ */ jsx24(Text20, { bold: true, color: theme.border.accent, children: "Workflows" }),
@@ -3196,7 +3194,7 @@ function Status({ workflows, onSelect, onBack, onStop, onDelete }) {
3196
3194
  workflows.map((wf, i) => /* @__PURE__ */ jsx24(Box20, { children: /* @__PURE__ */ jsxs20(Text20, { inverse: i === selectedIndex, children: [
3197
3195
  /* @__PURE__ */ jsx24(Text20, { children: wf.id.slice(0, 12).padEnd(14) }),
3198
3196
  /* @__PURE__ */ jsx24(Text20, { children: wf.name.slice(0, 26).padEnd(28) }),
3199
- /* @__PURE__ */ jsx24(Text20, { color: phaseColor3(wf.phase), children: wf.phase })
3197
+ /* @__PURE__ */ jsx24(Text20, { color: phaseColor2(wf.phase), children: wf.phase })
3200
3198
  ] }) }, wf.id))
3201
3199
  ] })
3202
3200
  ] }),
@@ -3221,10 +3219,10 @@ function AppLayout({ cwd }) {
3221
3219
  const rows = stdout?.rows ?? 24;
3222
3220
  const displayPath = cwd ? cwd.replace(process.env["HOME"] ?? "", "~") : "";
3223
3221
  const versionLabel = appVersion === "dev" ? "dev" : `v${appVersion}`;
3224
- const configIssues = useMemo6(() => {
3222
+ const configIssues = useMemo5(() => {
3225
3223
  const validation = validateConfig();
3226
3224
  return validation.issues.filter((i) => i.severity === "error");
3227
- }, []);
3225
+ }, [config]);
3228
3226
  return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", height: rows, children: [
3229
3227
  /* @__PURE__ */ jsx25(Static, { items: view !== "onboarding" ? ["banner"] : [], children: (item) => /* @__PURE__ */ jsx25(
3230
3228
  Banner,
package/dist/cli.js CHANGED
@@ -508,7 +508,7 @@ program.command("tui").description("Start interactive TUI").option("--skip-onboa
508
508
  enableTuiLogging();
509
509
  const React = await import("react");
510
510
  const { render } = await import("ink");
511
- const { App } = await import("./app-XQRFUTEX.js");
511
+ const { App } = await import("./app-PUHQN5BJ.js");
512
512
  render(React.createElement(App, { cwd: process.cwd(), skipOnboarding: opts.skipOnboarding }), { exitOnCtrlC: false });
513
513
  } catch (err) {
514
514
  logger.error({ err }, "Failed to start TUI");
@@ -522,7 +522,7 @@ program.option("--skip-onboarding", "Skip first-run onboarding wizard").action(a
522
522
  enableTuiLogging();
523
523
  const React = await import("react");
524
524
  const { render } = await import("ink");
525
- const { App } = await import("./app-XQRFUTEX.js");
525
+ const { App } = await import("./app-PUHQN5BJ.js");
526
526
  render(React.createElement(App, { cwd: process.cwd(), skipOnboarding }), { exitOnCtrlC: false });
527
527
  } catch (err) {
528
528
  logger.error({ err }, "Failed to start TUI");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cueclaw",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Orchestrate agent workflows with natural language. Natural language in, executable DAG out.",
5
5
  "type": "module",
6
6
  "bin": {