wave-code 0.14.4 → 0.15.1

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 (116) hide show
  1. package/dist/acp/agent.d.ts.map +1 -1
  2. package/dist/acp/agent.js +10 -0
  3. package/dist/components/BackgroundTaskManager.d.ts.map +1 -1
  4. package/dist/components/BackgroundTaskManager.js +17 -53
  5. package/dist/components/CommandSelector.d.ts.map +1 -1
  6. package/dist/components/CommandSelector.js +38 -28
  7. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  8. package/dist/components/ConfirmationSelector.js +18 -196
  9. package/dist/components/DiscoverView.d.ts.map +1 -1
  10. package/dist/components/DiscoverView.js +19 -11
  11. package/dist/components/FileSelector.d.ts.map +1 -1
  12. package/dist/components/FileSelector.js +29 -25
  13. package/dist/components/HelpView.d.ts.map +1 -1
  14. package/dist/components/HelpView.js +24 -28
  15. package/dist/components/HistorySearch.d.ts.map +1 -1
  16. package/dist/components/HistorySearch.js +26 -28
  17. package/dist/components/InputBox.d.ts.map +1 -1
  18. package/dist/components/InputBox.js +14 -6
  19. package/dist/components/InstalledView.d.ts.map +1 -1
  20. package/dist/components/InstalledView.js +19 -11
  21. package/dist/components/MarketplaceDetail.d.ts.map +1 -1
  22. package/dist/components/MarketplaceDetail.js +35 -16
  23. package/dist/components/MarketplaceView.d.ts.map +1 -1
  24. package/dist/components/MarketplaceView.js +21 -12
  25. package/dist/components/McpManager.d.ts.map +1 -1
  26. package/dist/components/McpManager.js +27 -46
  27. package/dist/components/ModelSelector.d.ts.map +1 -1
  28. package/dist/components/ModelSelector.js +27 -17
  29. package/dist/components/PluginDetail.d.ts.map +1 -1
  30. package/dist/components/PluginDetail.js +39 -26
  31. package/dist/components/RewindCommand.d.ts.map +1 -1
  32. package/dist/components/RewindCommand.js +27 -21
  33. package/dist/components/SessionSelector.d.ts.map +1 -1
  34. package/dist/components/SessionSelector.js +21 -16
  35. package/dist/components/StatusLine.d.ts.map +1 -1
  36. package/dist/components/StatusLine.js +5 -1
  37. package/dist/components/WorktreeExitPrompt.d.ts.map +1 -1
  38. package/dist/components/WorktreeExitPrompt.js +21 -12
  39. package/dist/contexts/useChat.d.ts +0 -1
  40. package/dist/contexts/useChat.d.ts.map +1 -1
  41. package/dist/contexts/useChat.js +13 -20
  42. package/dist/hooks/useInputManager.d.ts +5 -19
  43. package/dist/hooks/useInputManager.d.ts.map +1 -1
  44. package/dist/hooks/useInputManager.js +222 -79
  45. package/dist/managers/inputHandlers.d.ts +1 -1
  46. package/dist/managers/inputHandlers.d.ts.map +1 -1
  47. package/dist/managers/inputHandlers.js +3 -6
  48. package/dist/managers/inputReducer.d.ts +46 -4
  49. package/dist/managers/inputReducer.d.ts.map +1 -1
  50. package/dist/managers/inputReducer.js +519 -4
  51. package/dist/print-cli.d.ts.map +1 -1
  52. package/dist/print-cli.js +21 -9
  53. package/dist/reducers/backgroundTaskManagerReducer.d.ts +20 -0
  54. package/dist/reducers/backgroundTaskManagerReducer.d.ts.map +1 -1
  55. package/dist/reducers/backgroundTaskManagerReducer.js +83 -0
  56. package/dist/reducers/confirmationReducer.d.ts +11 -0
  57. package/dist/reducers/confirmationReducer.d.ts.map +1 -1
  58. package/dist/reducers/confirmationReducer.js +146 -0
  59. package/dist/reducers/helpSelectorReducer.d.ts +26 -0
  60. package/dist/reducers/helpSelectorReducer.d.ts.map +1 -0
  61. package/dist/reducers/helpSelectorReducer.js +52 -0
  62. package/dist/reducers/mcpManagerReducer.d.ts +22 -0
  63. package/dist/reducers/mcpManagerReducer.d.ts.map +1 -1
  64. package/dist/reducers/mcpManagerReducer.js +57 -0
  65. package/dist/reducers/pluginDetailReducer.d.ts +8 -0
  66. package/dist/reducers/pluginDetailReducer.d.ts.map +1 -1
  67. package/dist/reducers/pluginDetailReducer.js +32 -0
  68. package/dist/reducers/questionReducer.d.ts +21 -0
  69. package/dist/reducers/questionReducer.d.ts.map +1 -1
  70. package/dist/reducers/questionReducer.js +177 -0
  71. package/dist/reducers/rewindSelectorReducer.d.ts +22 -0
  72. package/dist/reducers/rewindSelectorReducer.d.ts.map +1 -0
  73. package/dist/reducers/rewindSelectorReducer.js +37 -0
  74. package/dist/reducers/selectorReducer.d.ts +22 -0
  75. package/dist/reducers/selectorReducer.d.ts.map +1 -0
  76. package/dist/reducers/selectorReducer.js +40 -0
  77. package/dist/utils/inputUtils.d.ts +18 -0
  78. package/dist/utils/inputUtils.d.ts.map +1 -0
  79. package/dist/utils/inputUtils.js +67 -0
  80. package/dist/utils/worktree.d.ts.map +1 -1
  81. package/dist/utils/worktree.js +1 -2
  82. package/package.json +2 -2
  83. package/src/acp/agent.ts +10 -0
  84. package/src/components/BackgroundTaskManager.tsx +19 -58
  85. package/src/components/CommandSelector.tsx +53 -44
  86. package/src/components/ConfirmationSelector.tsx +18 -202
  87. package/src/components/DiscoverView.tsx +21 -11
  88. package/src/components/FileSelector.tsx +40 -35
  89. package/src/components/HelpView.tsx +37 -43
  90. package/src/components/HistorySearch.tsx +29 -36
  91. package/src/components/InputBox.tsx +14 -7
  92. package/src/components/InstalledView.tsx +21 -11
  93. package/src/components/MarketplaceDetail.tsx +48 -24
  94. package/src/components/MarketplaceView.tsx +25 -10
  95. package/src/components/McpManager.tsx +29 -54
  96. package/src/components/ModelSelector.tsx +30 -23
  97. package/src/components/PluginDetail.tsx +50 -27
  98. package/src/components/RewindCommand.tsx +38 -32
  99. package/src/components/SessionSelector.tsx +23 -20
  100. package/src/components/StatusLine.tsx +10 -1
  101. package/src/components/WorktreeExitPrompt.tsx +23 -13
  102. package/src/contexts/useChat.tsx +13 -31
  103. package/src/hooks/useInputManager.ts +273 -141
  104. package/src/managers/inputHandlers.ts +1 -6
  105. package/src/managers/inputReducer.ts +675 -7
  106. package/src/print-cli.ts +20 -11
  107. package/src/reducers/backgroundTaskManagerReducer.ts +110 -1
  108. package/src/reducers/confirmationReducer.ts +173 -1
  109. package/src/reducers/helpSelectorReducer.ts +77 -0
  110. package/src/reducers/mcpManagerReducer.ts +82 -1
  111. package/src/reducers/pluginDetailReducer.ts +42 -1
  112. package/src/reducers/questionReducer.ts +204 -0
  113. package/src/reducers/rewindSelectorReducer.ts +54 -0
  114. package/src/reducers/selectorReducer.ts +57 -0
  115. package/src/utils/inputUtils.ts +94 -0
  116. package/src/utils/worktree.ts +1 -7
@@ -1,10 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useReducer, useEffect } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { AVAILABLE_COMMANDS } from "../constants/commands.js";
5
+ import { helpSelectorReducer } from "../reducers/helpSelectorReducer.js";
5
6
  export const HelpView = ({ onCancel, commands = [], }) => {
6
- const [activeTab, setActiveTab] = useState("general");
7
- const [selectedIndex, setSelectedIndex] = useState(0);
8
7
  const MAX_VISIBLE_ITEMS = 10;
9
8
  const tabs = [
10
9
  "general",
@@ -13,29 +12,30 @@ export const HelpView = ({ onCancel, commands = [], }) => {
13
12
  if (commands.length > 0) {
14
13
  tabs.push("custom-commands");
15
14
  }
16
- useInput((_, key) => {
17
- if (key.escape) {
15
+ const [state, dispatch] = useReducer(helpSelectorReducer, {
16
+ selectedIndex: 0,
17
+ activeTab: "general",
18
+ pendingDecision: null,
19
+ });
20
+ const { selectedIndex, activeTab, pendingDecision } = state;
21
+ // Handle decisions from reducer
22
+ useEffect(() => {
23
+ if (pendingDecision === "cancel") {
18
24
  onCancel();
19
- return;
20
- }
21
- if (key.tab) {
22
- setActiveTab((prev) => {
23
- const currentIndex = tabs.indexOf(prev);
24
- const nextIndex = (currentIndex + 1) % tabs.length;
25
- return tabs[nextIndex];
26
- });
27
- setSelectedIndex(0);
28
- return;
29
- }
30
- if (activeTab === "commands" || activeTab === "custom-commands") {
31
- const currentCommands = activeTab === "commands" ? AVAILABLE_COMMANDS : commands;
32
- if (key.upArrow) {
33
- setSelectedIndex((prev) => Math.max(0, prev - 1));
34
- }
35
- else if (key.downArrow) {
36
- setSelectedIndex((prev) => Math.min(currentCommands.length - 1, prev + 1));
37
- }
25
+ dispatch({ type: "CLEAR_DECISION" });
38
26
  }
27
+ }, [pendingDecision, onCancel]);
28
+ const currentCommands = activeTab === "commands" ? AVAILABLE_COMMANDS : commands;
29
+ // Calculate visible window for commands
30
+ const startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2), Math.max(0, currentCommands.length - MAX_VISIBLE_ITEMS)));
31
+ const visibleCommands = currentCommands.slice(startIndex, startIndex + MAX_VISIBLE_ITEMS);
32
+ useInput((_, key) => {
33
+ dispatch({
34
+ type: "HANDLE_KEY",
35
+ key,
36
+ maxIndex: activeTab === "general" ? 0 : currentCommands.length - 1,
37
+ tabs,
38
+ });
39
39
  });
40
40
  const helpItems = [
41
41
  { key: "@", description: "Reference files" },
@@ -53,10 +53,6 @@ export const HelpView = ({ onCancel, commands = [], }) => {
53
53
  description: "Interrupt AI or command / Cancel selector / Close help",
54
54
  },
55
55
  ];
56
- // Calculate visible window for commands
57
- const currentCommands = activeTab === "commands" ? AVAILABLE_COMMANDS : commands;
58
- const startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2), Math.max(0, currentCommands.length - MAX_VISIBLE_ITEMS)));
59
- const visibleCommands = currentCommands.slice(startIndex, startIndex + MAX_VISIBLE_ITEMS);
60
56
  const footerText = [
61
57
  "Tab switch",
62
58
  activeTab !== "general" && "↑↓ navigate",
@@ -1 +1 @@
1
- {"version":3,"file":"HistorySearch.d.ts","sourceRoot":"","sources":["../../src/components/HistorySearch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAkKtD,CAAC"}
1
+ {"version":3,"file":"HistorySearch.d.ts","sourceRoot":"","sources":["../../src/components/HistorySearch.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAE/D,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAMxE,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAuJtD,CAAC"}
@@ -1,48 +1,46 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import React, { useState, useEffect } from "react";
2
+ import { useReducer, useEffect, useState } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { PromptHistoryManager } from "wave-agent-sdk";
5
+ import { selectorReducer, } from "../reducers/selectorReducer.js";
5
6
  export const HistorySearch = ({ searchQuery, onSelect, onCancel, }) => {
6
7
  const MAX_VISIBLE_ITEMS = 5;
7
- const [selectedIndex, setSelectedIndex] = useState(0);
8
+ const [state, dispatch] = useReducer(selectorReducer, {
9
+ selectedIndex: 0,
10
+ pendingDecision: null,
11
+ });
8
12
  const [entries, setEntries] = useState([]);
9
- const entriesRef = React.useRef([]);
10
- const selectedIndexRef = React.useRef(0);
11
- useEffect(() => {
12
- entriesRef.current = entries;
13
- }, [entries]);
14
- useEffect(() => {
15
- selectedIndexRef.current = selectedIndex;
16
- }, [selectedIndex]);
13
+ const { selectedIndex, pendingDecision } = state;
17
14
  useEffect(() => {
18
15
  const fetchHistory = async () => {
19
16
  const results = await PromptHistoryManager.searchHistory(searchQuery);
20
17
  const limitedResults = results.slice(0, 20);
21
- setEntries(limitedResults); // Limit to 20 results
22
- setSelectedIndex(0);
18
+ setEntries(limitedResults);
19
+ dispatch({ type: "RESET_INDEX" });
23
20
  };
24
21
  fetchHistory();
25
22
  }, [searchQuery]);
26
- useInput((input, key) => {
27
- if (key.return) {
28
- if (entriesRef.current.length > 0 &&
29
- selectedIndexRef.current < entriesRef.current.length) {
30
- onSelect(entriesRef.current[selectedIndexRef.current]);
31
- }
23
+ // Handle decisions from reducer
24
+ useEffect(() => {
25
+ if (!pendingDecision)
32
26
  return;
27
+ if (pendingDecision === "select") {
28
+ if (entries.length > 0 && selectedIndex < entries.length) {
29
+ onSelect(entries[selectedIndex]);
30
+ }
33
31
  }
34
- if (key.escape) {
32
+ else if (pendingDecision === "cancel") {
35
33
  onCancel();
36
- return;
37
- }
38
- if (key.upArrow) {
39
- setSelectedIndex((prev) => Math.max(0, prev - 1));
40
- return;
41
- }
42
- if (key.downArrow) {
43
- setSelectedIndex((prev) => Math.min(entriesRef.current.length - 1, prev + 1));
44
- return;
45
34
  }
35
+ dispatch({ type: "CLEAR_DECISION" });
36
+ }, [pendingDecision, selectedIndex, entries, onSelect, onCancel]);
37
+ useInput((input, key) => {
38
+ dispatch({
39
+ type: "HANDLE_KEY",
40
+ key,
41
+ maxIndex: entries.length - 1,
42
+ hasInsert: false,
43
+ });
46
44
  });
47
45
  if (entries.length === 0) {
48
46
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", borderBottom: false, borderLeft: false, borderRight: false, children: [_jsxs(Text, { color: "yellow", children: ["No history found ", searchQuery && `for "${searchQuery}"`] }), _jsx(Text, { dimColor: true, children: "Press Escape to cancel" })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"InputBox.d.ts","sourceRoot":"","sources":["../../src/components/InputBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAkBzC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEpE,eAAO,MAAM,sBAAsB,mDACe,CAAC;AAEnD,eAAO,MAAM,6BAA6B,QAGzC,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,CACZ,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,EAClD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACjC,IAAI,CAAC;IACV,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAE1B,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,mBAAmB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/D,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAC/B,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAClD;AAED,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CA6Q5C,CAAC"}
1
+ {"version":3,"file":"InputBox.d.ts","sourceRoot":"","sources":["../../src/components/InputBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAkBzC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEpE,eAAO,MAAM,sBAAsB,mDACe,CAAC;AAEnD,eAAO,MAAM,6BAA6B,QAGzC,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,CACZ,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,EAClD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACjC,IAAI,CAAC;IACV,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAE1B,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,mBAAmB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE/D,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAC/B,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CAClD;AAED,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAoR5C,CAAC"}
@@ -19,7 +19,7 @@ import { useChat } from "../contexts/useChat.js";
19
19
  export const INPUT_PLACEHOLDER_TEXT = "Type your message (use /help for more info)...";
20
20
  export const INPUT_PLACEHOLDER_TEXT_PREFIX = INPUT_PLACEHOLDER_TEXT.substring(0, 10);
21
21
  export const InputBox = ({ sendMessage = () => { }, abortMessage = () => { }, mcpServers = [], connectMcpServer = async () => false, disconnectMcpServer = async () => false, slashCommands = [], hasSlashCommand = () => false, }) => {
22
- const { permissionMode: chatPermissionMode, setPermissionMode: setChatPermissionMode, allowBypassInCycle: chatAllowBypassInCycle, handleRewindSelect, backgroundCurrentTask, messages, getFullMessageThread, sessionId, workingDirectory, askBtw, currentModel, configuredModels, setModel, recreateAgent, } = useChat();
22
+ const { permissionMode: chatPermissionMode, setPermissionMode: setChatPermissionMode, handleRewindSelect, backgroundCurrentTask, messages, getFullMessageThread, sessionId, workingDirectory, askBtw, currentModel, configuredModels, setModel, recreateAgent, } = useChat();
23
23
  // Input manager with all input state and functionality (including images)
24
24
  const { inputText, cursorPosition,
25
25
  // Image management
@@ -33,7 +33,7 @@ export const InputBox = ({ sendMessage = () => { }, abortMessage = () => { }, mc
33
33
  // Task/MCP Manager
34
34
  showBackgroundTaskManager, showMcpManager, showRewindManager, showHelp, showStatusCommand, showPluginManager, showModelSelector, setShowBackgroundTaskManager, setShowMcpManager, setShowRewindManager, setShowHelp, setShowStatusCommand, setShowPluginManager, setShowModelSelector,
35
35
  // Permission mode
36
- permissionMode, setPermissionMode, setAllowBypassInCycle,
36
+ permissionMode, setPermissionMode,
37
37
  // BTW state
38
38
  btwState,
39
39
  // Main handler
@@ -54,12 +54,20 @@ export const InputBox = ({ sendMessage = () => { }, abortMessage = () => { }, mc
54
54
  useEffect(() => {
55
55
  setPermissionMode(chatPermissionMode);
56
56
  }, [chatPermissionMode, setPermissionMode]);
57
- // Sync allowBypassInCycle from useChat to InputManager
58
- useEffect(() => {
59
- setAllowBypassInCycle(chatAllowBypassInCycle);
60
- }, [chatAllowBypassInCycle, setAllowBypassInCycle]);
61
57
  // Use the InputManager's unified input handler
62
58
  useInput(async (input, key) => {
59
+ // These views have their own useInput handlers that handle escape and navigation.
60
+ // If they are active, we should skip InputBox's global input handling to avoid
61
+ // duplicate dispatches or state update conflicts.
62
+ if (showRewindManager ||
63
+ showHelp ||
64
+ showStatusCommand ||
65
+ showPluginManager ||
66
+ showModelSelector ||
67
+ showBackgroundTaskManager ||
68
+ showMcpManager) {
69
+ return;
70
+ }
63
71
  await handleInput(input, key, attachedImages, clearImages);
64
72
  });
65
73
  const handleRewindCancel = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"InstalledView.d.ts","sourceRoot":"","sources":["../../src/components/InstalledView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAIxC,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAqEjC,CAAC"}
1
+ {"version":3,"file":"InstalledView.d.ts","sourceRoot":"","sources":["../../src/components/InstalledView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAKrD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EA8EjC,CAAC"}
@@ -1,25 +1,33 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useReducer, useEffect } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
5
+ import { selectorReducer } from "../reducers/selectorReducer.js";
5
6
  export const InstalledView = () => {
6
7
  const { installedPlugins, actions } = usePluginManagerContext();
7
- const [selectedIndex, setSelectedIndex] = useState(0);
8
- useInput((input, key) => {
9
- if (key.upArrow) {
10
- setSelectedIndex(Math.max(0, selectedIndex - 1));
11
- }
12
- else if (key.downArrow) {
13
- setSelectedIndex(Math.min(installedPlugins.length - 1, selectedIndex + 1));
14
- }
15
- else if (key.return) {
8
+ const [state, dispatch] = useReducer(selectorReducer, {
9
+ selectedIndex: 0,
10
+ pendingDecision: null,
11
+ });
12
+ const { selectedIndex, pendingDecision } = state;
13
+ useInput((_input, key) => {
14
+ dispatch({
15
+ type: "HANDLE_KEY",
16
+ key,
17
+ maxIndex: installedPlugins.length - 1,
18
+ hasInsert: false,
19
+ });
20
+ });
21
+ useEffect(() => {
22
+ if (pendingDecision === "select") {
16
23
  const plugin = installedPlugins[selectedIndex];
17
24
  if (plugin) {
18
25
  actions.setSelectedId(`${plugin.name}@${plugin.marketplace}`);
19
26
  actions.setView("PLUGIN_DETAIL");
20
27
  }
28
+ dispatch({ type: "CLEAR_DECISION" });
21
29
  }
22
- });
30
+ }, [pendingDecision, selectedIndex, installedPlugins, actions]);
23
31
  if (installedPlugins.length === 0) {
24
32
  return (_jsx(Box, { padding: 1, children: _jsx(Text, { dimColor: true, children: "No plugins installed." }) }));
25
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MarketplaceDetail.d.ts","sourceRoot":"","sources":["../../src/components/MarketplaceDetail.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAIxC,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAuHrC,CAAC"}
1
+ {"version":3,"file":"MarketplaceDetail.d.ts","sourceRoot":"","sources":["../../src/components/MarketplaceDetail.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAK9D,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EA8IrC,CAAC"}
@@ -1,30 +1,38 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useReducer, useEffect, useMemo } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
5
+ import { selectorReducer } from "../reducers/selectorReducer.js";
5
6
  export const MarketplaceDetail = () => {
6
7
  const { state, marketplaces, actions } = usePluginManagerContext();
7
- const [selectedActionIndex, setSelectedActionIndex] = useState(0);
8
+ const [selectorState, dispatch] = useReducer(selectorReducer, {
9
+ selectedIndex: 0,
10
+ pendingDecision: null,
11
+ });
12
+ const { selectedIndex: selectedActionIndex, pendingDecision } = selectorState;
8
13
  const marketplace = marketplaces.find((m) => m.name === state.selectedId);
9
- const ACTIONS = [
14
+ const ACTIONS = useMemo(() => [
10
15
  {
11
16
  id: "toggle-auto-update",
12
17
  label: `${marketplace?.autoUpdate ? "Disable" : "Enable"} auto-update`,
13
18
  },
14
19
  { id: "update", label: "Update marketplace" },
15
20
  { id: "remove", label: "Remove marketplace" },
16
- ];
17
- useInput((input, key) => {
18
- if (key.escape) {
19
- actions.setView("MARKETPLACES");
20
- }
21
- else if (key.upArrow) {
22
- setSelectedActionIndex((prev) => prev > 0 ? prev - 1 : ACTIONS.length - 1);
23
- }
24
- else if (key.downArrow) {
25
- setSelectedActionIndex((prev) => prev < ACTIONS.length - 1 ? prev + 1 : 0);
26
- }
27
- else if (key.return && marketplace && !state.isLoading) {
21
+ ], [marketplace?.autoUpdate]);
22
+ useInput((_input, key) => {
23
+ if (state.isLoading && !key.escape)
24
+ return;
25
+ dispatch({
26
+ type: "HANDLE_KEY",
27
+ key,
28
+ maxIndex: ACTIONS.length - 1,
29
+ hasInsert: false,
30
+ });
31
+ });
32
+ useEffect(() => {
33
+ if (!pendingDecision)
34
+ return;
35
+ if (pendingDecision === "select" && marketplace && !state.isLoading) {
28
36
  const action = ACTIONS[selectedActionIndex].id;
29
37
  if (action === "toggle-auto-update") {
30
38
  actions.toggleAutoUpdate(marketplace.name, !marketplace.autoUpdate);
@@ -36,7 +44,18 @@ export const MarketplaceDetail = () => {
36
44
  actions.removeMarketplace(marketplace.name);
37
45
  }
38
46
  }
39
- });
47
+ else if (pendingDecision === "cancel") {
48
+ actions.setView("MARKETPLACES");
49
+ }
50
+ dispatch({ type: "CLEAR_DECISION" });
51
+ }, [
52
+ pendingDecision,
53
+ selectedActionIndex,
54
+ marketplace,
55
+ state.isLoading,
56
+ actions,
57
+ ACTIONS,
58
+ ]);
40
59
  if (!marketplace) {
41
60
  return (_jsx(Box, { children: _jsx(Text, { color: "red", children: "Marketplace not found." }) }));
42
61
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MarketplaceView.d.ts","sourceRoot":"","sources":["../../src/components/MarketplaceView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAMxC,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAoCnC,CAAC"}
1
+ {"version":3,"file":"MarketplaceView.d.ts","sourceRoot":"","sources":["../../src/components/MarketplaceView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAOrD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAkDnC,CAAC"}
@@ -1,28 +1,37 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useReducer, useEffect } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { usePluginManagerContext } from "../contexts/PluginManagerContext.js";
5
+ import { selectorReducer } from "../reducers/selectorReducer.js";
5
6
  import { MarketplaceList } from "./MarketplaceList.js";
6
7
  export const MarketplaceView = () => {
7
8
  const { marketplaces, actions } = usePluginManagerContext();
8
- const [selectedIndex, setSelectedIndex] = useState(0);
9
+ const [state, dispatch] = useReducer(selectorReducer, {
10
+ selectedIndex: 0,
11
+ pendingDecision: null,
12
+ });
13
+ const { selectedIndex, pendingDecision } = state;
9
14
  useInput((input, key) => {
10
- if (key.upArrow) {
11
- setSelectedIndex(Math.max(0, selectedIndex - 1));
12
- }
13
- else if (key.downArrow) {
14
- setSelectedIndex(Math.min(marketplaces.length - 1, selectedIndex + 1));
15
+ if (input === "a") {
16
+ actions.setView("ADD_MARKETPLACE");
17
+ return;
15
18
  }
16
- else if (key.return) {
19
+ dispatch({
20
+ type: "HANDLE_KEY",
21
+ key,
22
+ maxIndex: marketplaces.length - 1,
23
+ hasInsert: false,
24
+ });
25
+ });
26
+ useEffect(() => {
27
+ if (pendingDecision === "select") {
17
28
  const mk = marketplaces[selectedIndex];
18
29
  if (mk) {
19
30
  actions.setSelectedId(mk.name);
20
31
  actions.setView("MARKETPLACE_DETAIL");
21
32
  }
33
+ dispatch({ type: "CLEAR_DECISION" });
22
34
  }
23
- else if (input === "a") {
24
- actions.setView("ADD_MARKETPLACE");
25
- }
26
- });
35
+ }, [pendingDecision, selectedIndex, marketplaces, actions]);
27
36
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "green", children: "Press 'a' to add a new marketplace" }) }), _jsx(MarketplaceList, { marketplaces: marketplaces, selectedIndex: selectedIndex }), marketplaces.length > 0 && (_jsx(Box, { marginLeft: 4, marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter for actions" }) }))] }));
28
37
  };
@@ -1 +1 @@
1
- {"version":3,"file":"McpManager.d.ts","sourceRoot":"","sources":["../../src/components/McpManager.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqB,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMjD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAOD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAuShD,CAAC"}
1
+ {"version":3,"file":"McpManager.d.ts","sourceRoot":"","sources":["../../src/components/McpManager.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMjD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,eAAe,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9D;AAQD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA6QhD,CAAC"}
@@ -1,13 +1,32 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useReducer } from "react";
2
+ import { useReducer, useEffect } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
4
  import { mcpManagerReducer, } from "../reducers/mcpManagerReducer.js";
5
5
  const initialState = {
6
6
  selectedIndex: 0,
7
7
  viewMode: "list",
8
+ pendingEffect: null,
8
9
  };
9
10
  export const McpManager = ({ onCancel, servers, onConnectServer, onDisconnectServer, }) => {
10
11
  const [state, dispatch] = useReducer(mcpManagerReducer, initialState);
12
+ // Handle pending effects
13
+ useEffect(() => {
14
+ if (!state.pendingEffect)
15
+ return;
16
+ const effect = state.pendingEffect;
17
+ dispatch({ type: "CLEAR_PENDING_EFFECT" });
18
+ switch (effect.type) {
19
+ case "CANCEL":
20
+ onCancel();
21
+ break;
22
+ case "CONNECT_SERVER":
23
+ onConnectServer(effect.serverName);
24
+ break;
25
+ case "DISCONNECT_SERVER":
26
+ onDisconnectServer(effect.serverName);
27
+ break;
28
+ }
29
+ }, [state.pendingEffect, onCancel, onConnectServer, onDisconnectServer]);
11
30
  // Dynamically calculate selectedServer based on selectedIndex and servers
12
31
  const selectedServer = state.viewMode === "detail" &&
13
32
  servers.length > 0 &&
@@ -41,52 +60,14 @@ export const McpManager = ({ onCancel, servers, onConnectServer, onDisconnectSer
41
60
  return "○";
42
61
  }
43
62
  };
44
- const handleConnect = async (serverName) => {
45
- await onConnectServer(serverName);
46
- };
47
- const handleDisconnect = async (serverName) => {
48
- await onDisconnectServer(serverName);
49
- };
50
63
  useInput((input, key) => {
51
- if (key.return) {
52
- if (state.viewMode === "list") {
53
- dispatch({ type: "SET_VIEW_MODE", viewMode: "detail" });
54
- }
55
- return;
56
- }
57
- if (key.escape) {
58
- if (state.viewMode === "detail") {
59
- dispatch({ type: "SET_VIEW_MODE", viewMode: "list" });
60
- }
61
- else {
62
- onCancel();
63
- }
64
- return;
65
- }
66
- if (key.upArrow) {
67
- dispatch({ type: "MOVE_UP", serverCount: servers.length });
68
- return;
69
- }
70
- if (key.downArrow) {
71
- dispatch({ type: "MOVE_DOWN", serverCount: servers.length });
72
- return;
73
- }
74
- // Hotkeys for server actions
75
- if (input === "c") {
76
- const server = servers[state.selectedIndex];
77
- if (server &&
78
- (server.status === "disconnected" || server.status === "error")) {
79
- handleConnect(server.name);
80
- }
81
- return;
82
- }
83
- if (input === "d") {
84
- const server = servers[state.selectedIndex];
85
- if (server && server.status === "connected") {
86
- handleDisconnect(server.name);
87
- }
88
- return;
89
- }
64
+ dispatch({
65
+ type: "HANDLE_KEY",
66
+ input,
67
+ key,
68
+ serverCount: servers.length,
69
+ servers: servers.map((s) => ({ name: s.name, status: s.status })),
70
+ });
90
71
  });
91
72
  if (state.viewMode === "detail" && selectedServer) {
92
73
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", borderBottom: false, borderLeft: false, borderRight: false, paddingTop: 1, gap: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "cyan", bold: true, children: ["MCP Server Details: ", selectedServer.name] }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Status:" }), " ", _jsxs(Text, { color: getStatusColor(selectedServer.status), children: [getStatusIcon(selectedServer.status), " ", selectedServer.status] })] }) }), _jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Command:" }), " ", selectedServer.config.command] }) }), selectedServer.config.args && (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Args:" }), " ", selectedServer.config.args.join(" ")] }) })), selectedServer.toolCount !== undefined && (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Tools:" }), " ", selectedServer.toolCount, " ", "tools"] }) })), selectedServer.capabilities && (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Capabilities:" }), " ", selectedServer.capabilities.join(", ")] }) })), selectedServer.lastConnected && (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "blue", children: "Last Connected:" }), " ", formatTime(selectedServer.lastConnected)] }) })), selectedServer.error && (_jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: "red", children: "Error:" }), " ", selectedServer.error] }) }))] }), selectedServer.config.env &&
@@ -1 +1 @@
1
- {"version":3,"file":"ModelSelector.d.ts","sourceRoot":"","sources":["../../src/components/ModelSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAGxC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAID,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAoHtD,CAAC"}
1
+ {"version":3,"file":"ModelSelector.d.ts","sourceRoot":"","sources":["../../src/components/ModelSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAIrD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAID,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA0HtD,CAAC"}
@@ -1,33 +1,43 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useReducer, useEffect } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
+ import { selectorReducer } from "../reducers/selectorReducer.js";
4
5
  const MAX_VISIBLE_ITEMS = 5;
5
6
  export const ModelSelector = ({ onCancel, currentModel, configuredModels, onSelectModel, }) => {
6
- const [selectedIndex, setSelectedIndex] = useState(() => {
7
- const index = configuredModels.indexOf(currentModel);
8
- return index !== -1 ? index : 0;
7
+ const [state, dispatch] = useReducer(selectorReducer, {
8
+ selectedIndex: configuredModels.indexOf(currentModel) !== -1
9
+ ? configuredModels.indexOf(currentModel)
10
+ : 0,
11
+ pendingDecision: null,
9
12
  });
13
+ const { selectedIndex, pendingDecision } = state;
10
14
  useInput((_input, key) => {
11
- if (key.return) {
15
+ dispatch({
16
+ type: "HANDLE_KEY",
17
+ key,
18
+ maxIndex: configuredModels.length - 1,
19
+ hasInsert: false,
20
+ });
21
+ });
22
+ useEffect(() => {
23
+ if (pendingDecision === "select") {
12
24
  if (configuredModels.length > 0) {
13
25
  onSelectModel(configuredModels[selectedIndex]);
14
26
  }
15
27
  onCancel();
16
- return;
28
+ dispatch({ type: "CLEAR_DECISION" });
17
29
  }
18
- if (key.escape) {
30
+ else if (pendingDecision === "cancel") {
19
31
  onCancel();
20
- return;
21
- }
22
- if (key.upArrow) {
23
- setSelectedIndex((prev) => Math.max(0, prev - 1));
24
- return;
32
+ dispatch({ type: "CLEAR_DECISION" });
25
33
  }
26
- if (key.downArrow) {
27
- setSelectedIndex((prev) => Math.min(configuredModels.length - 1, prev + 1));
28
- return;
29
- }
30
- });
34
+ }, [
35
+ pendingDecision,
36
+ selectedIndex,
37
+ configuredModels,
38
+ onSelectModel,
39
+ onCancel,
40
+ ]);
31
41
  // Calculate visible window
32
42
  const startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2), Math.max(0, configuredModels.length - MAX_VISIBLE_ITEMS)));
33
43
  const visibleModels = configuredModels.slice(startIndex, startIndex + MAX_VISIBLE_ITEMS);
@@ -1 +1 @@
1
- {"version":3,"file":"PluginDetail.d.ts","sourceRoot":"","sources":["../../src/components/PluginDetail.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqB,MAAM,OAAO,CAAC;AAc1C,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAyJhC,CAAC"}
1
+ {"version":3,"file":"PluginDetail.d.ts","sourceRoot":"","sources":["../../src/components/PluginDetail.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAc9D,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAgLhC,CAAC"}