wave-code 0.7.2 → 0.8.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 (110) hide show
  1. package/dist/cli.d.ts +2 -4
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +24 -52
  4. package/dist/components/App.d.ts +3 -4
  5. package/dist/components/App.d.ts.map +1 -1
  6. package/dist/components/App.js +49 -6
  7. package/dist/components/BangDisplay.d.ts +9 -0
  8. package/dist/components/BangDisplay.d.ts.map +1 -0
  9. package/dist/components/{CommandOutputDisplay.js → BangDisplay.js} +1 -1
  10. package/dist/components/ChatInterface.d.ts.map +1 -1
  11. package/dist/components/ChatInterface.js +3 -2
  12. package/dist/components/CommandSelector.d.ts.map +1 -1
  13. package/dist/components/CommandSelector.js +7 -28
  14. package/dist/components/ConfirmationSelector.d.ts.map +1 -1
  15. package/dist/components/ConfirmationSelector.js +116 -11
  16. package/dist/components/HelpView.d.ts +2 -0
  17. package/dist/components/HelpView.d.ts.map +1 -1
  18. package/dist/components/HelpView.js +49 -3
  19. package/dist/components/InputBox.d.ts.map +1 -1
  20. package/dist/components/InputBox.js +10 -4
  21. package/dist/components/MarketplaceAddForm.d.ts.map +1 -1
  22. package/dist/components/MarketplaceAddForm.js +13 -6
  23. package/dist/components/MarketplaceDetail.d.ts.map +1 -1
  24. package/dist/components/MarketplaceDetail.js +8 -3
  25. package/dist/components/MessageBlockItem.js +2 -2
  26. package/dist/components/MessageList.d.ts +4 -1
  27. package/dist/components/MessageList.d.ts.map +1 -1
  28. package/dist/components/MessageList.js +15 -8
  29. package/dist/components/PluginDetail.d.ts.map +1 -1
  30. package/dist/components/PluginDetail.js +14 -3
  31. package/dist/components/PluginManagerShell.d.ts.map +1 -1
  32. package/dist/components/PluginManagerShell.js +3 -3
  33. package/dist/components/PluginManagerTypes.d.ts +2 -0
  34. package/dist/components/PluginManagerTypes.d.ts.map +1 -1
  35. package/dist/components/SessionSelector.d.ts.map +1 -1
  36. package/dist/components/SessionSelector.js +5 -5
  37. package/dist/components/StatusCommand.d.ts +6 -0
  38. package/dist/components/StatusCommand.d.ts.map +1 -0
  39. package/dist/components/StatusCommand.js +28 -0
  40. package/dist/components/WorktreeExitPrompt.d.ts +13 -0
  41. package/dist/components/WorktreeExitPrompt.d.ts.map +1 -0
  42. package/dist/components/WorktreeExitPrompt.js +26 -0
  43. package/dist/constants/commands.d.ts +3 -0
  44. package/dist/constants/commands.d.ts.map +1 -0
  45. package/dist/constants/commands.js +38 -0
  46. package/dist/contexts/useChat.d.ts +9 -5
  47. package/dist/contexts/useChat.d.ts.map +1 -1
  48. package/dist/contexts/useChat.js +38 -8
  49. package/dist/contracts/status.d.ts +8 -0
  50. package/dist/contracts/status.d.ts.map +1 -0
  51. package/dist/contracts/status.js +1 -0
  52. package/dist/hooks/useInputManager.d.ts +2 -0
  53. package/dist/hooks/useInputManager.d.ts.map +1 -1
  54. package/dist/hooks/useInputManager.js +12 -0
  55. package/dist/hooks/usePluginManager.d.ts.map +1 -1
  56. package/dist/hooks/usePluginManager.js +41 -13
  57. package/dist/hooks/useTasks.js +2 -2
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +53 -4
  60. package/dist/managers/InputManager.d.ts +6 -0
  61. package/dist/managers/InputManager.d.ts.map +1 -1
  62. package/dist/managers/InputManager.js +32 -13
  63. package/dist/print-cli.d.ts +2 -4
  64. package/dist/print-cli.d.ts.map +1 -1
  65. package/dist/print-cli.js +31 -2
  66. package/dist/session-selector-cli.d.ts +3 -1
  67. package/dist/session-selector-cli.d.ts.map +1 -1
  68. package/dist/session-selector-cli.js +2 -2
  69. package/dist/types.d.ts +11 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +1 -0
  72. package/dist/utils/highlightUtils.d.ts.map +1 -1
  73. package/dist/utils/highlightUtils.js +66 -42
  74. package/dist/utils/worktree.d.ts +23 -0
  75. package/dist/utils/worktree.d.ts.map +1 -0
  76. package/dist/utils/worktree.js +135 -0
  77. package/package.json +2 -2
  78. package/src/cli.tsx +36 -59
  79. package/src/components/App.tsx +99 -11
  80. package/src/components/{CommandOutputDisplay.tsx → BangDisplay.tsx} +4 -4
  81. package/src/components/ChatInterface.tsx +8 -0
  82. package/src/components/CommandSelector.tsx +7 -29
  83. package/src/components/ConfirmationSelector.tsx +131 -12
  84. package/src/components/HelpView.tsx +129 -14
  85. package/src/components/InputBox.tsx +14 -2
  86. package/src/components/MarketplaceAddForm.tsx +21 -8
  87. package/src/components/MarketplaceDetail.tsx +19 -4
  88. package/src/components/MessageBlockItem.tsx +3 -3
  89. package/src/components/MessageList.tsx +47 -23
  90. package/src/components/PluginDetail.tsx +30 -6
  91. package/src/components/PluginManagerShell.tsx +24 -6
  92. package/src/components/PluginManagerTypes.ts +2 -0
  93. package/src/components/SessionSelector.tsx +33 -16
  94. package/src/components/StatusCommand.tsx +94 -0
  95. package/src/components/WorktreeExitPrompt.tsx +86 -0
  96. package/src/constants/commands.ts +41 -0
  97. package/src/contexts/useChat.tsx +57 -13
  98. package/src/contracts/status.ts +7 -0
  99. package/src/hooks/useInputManager.ts +12 -0
  100. package/src/hooks/usePluginManager.ts +47 -13
  101. package/src/hooks/useTasks.ts +2 -2
  102. package/src/index.ts +71 -12
  103. package/src/managers/InputManager.ts +37 -15
  104. package/src/print-cli.ts +48 -5
  105. package/src/session-selector-cli.tsx +6 -2
  106. package/src/types.ts +11 -0
  107. package/src/utils/highlightUtils.ts +66 -42
  108. package/src/utils/worktree.ts +164 -0
  109. package/dist/components/CommandOutputDisplay.d.ts +0 -9
  110. package/dist/components/CommandOutputDisplay.d.ts.map +0 -1
package/dist/cli.d.ts CHANGED
@@ -1,9 +1,7 @@
1
- export interface CliOptions {
1
+ import { BaseAppProps } from "./types.js";
2
+ export interface CliOptions extends BaseAppProps {
2
3
  restoreSessionId?: string;
3
4
  continueLastSession?: boolean;
4
- bypassPermissions?: boolean;
5
- pluginDirs?: string[];
6
- tools?: string[];
7
5
  }
8
6
  export declare function startCli(options: CliOptions): Promise<void>;
9
7
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFjE"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA0DjE"}
package/dist/cli.js CHANGED
@@ -2,61 +2,33 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { render } from "ink";
3
3
  import { App } from "./components/App.js";
4
4
  import { cleanupLogs } from "./utils/logger.js";
5
+ import { removeWorktree } from "./utils/worktree.js";
5
6
  export async function startCli(options) {
6
- const { restoreSessionId, continueLastSession, bypassPermissions, pluginDirs, tools, } = options;
7
+ const { restoreSessionId, continueLastSession, bypassPermissions, pluginDirs, tools, worktreeSession, workdir, version, model, } = options;
7
8
  // Continue with ink-based UI for normal mode
8
- // Global cleanup tracker
9
- let isCleaningUp = false;
10
- let appUnmounted = false;
11
- const cleanup = async () => {
12
- if (isCleaningUp)
13
- return;
14
- isCleaningUp = true;
15
- console.log("\nShutting down gracefully...");
16
- try {
17
- // Clean up old log files
18
- await cleanupLogs().catch((error) => {
19
- console.warn("Failed to cleanup old logs:", error);
20
- });
21
- // Unmount the React app to trigger cleanup
22
- if (!appUnmounted) {
23
- unmount();
24
- appUnmounted = true;
25
- // Give React time to cleanup
26
- await new Promise((resolve) => setTimeout(resolve, 100));
27
- }
28
- process.exit(0);
29
- }
30
- catch (error) {
31
- console.error("Error during cleanup:", error);
32
- process.exit(1);
33
- }
9
+ let shouldRemoveWorktree = false;
10
+ const handleExit = (shouldRemove) => {
11
+ shouldRemoveWorktree = shouldRemove;
12
+ unmount();
34
13
  };
35
- // Handle process signals
36
- process.on("SIGINT", cleanup);
37
- process.on("SIGTERM", cleanup);
38
- // Handle uncaught exceptions
39
- process.on("uncaughtException", (error) => {
40
- console.error("Uncaught exception:", error);
41
- cleanup();
42
- });
43
- process.on("unhandledRejection", (reason, promise) => {
44
- console.error("Unhandled rejection at:", promise, "reason:", reason);
45
- cleanup();
46
- });
47
14
  // Render the application
48
- const { unmount } = render(_jsx(App, { restoreSessionId: restoreSessionId, continueLastSession: continueLastSession, bypassPermissions: bypassPermissions, pluginDirs: pluginDirs, tools: tools }));
49
- // Store unmount function for cleanup when process exits normally
50
- process.on("exit", () => {
51
- if (!appUnmounted) {
52
- try {
53
- unmount();
54
- }
55
- catch {
56
- // Ignore errors during unmount
57
- }
15
+ const { unmount, waitUntilExit } = render(_jsx(App, { restoreSessionId: restoreSessionId, continueLastSession: continueLastSession, bypassPermissions: bypassPermissions, pluginDirs: pluginDirs, tools: tools, worktreeSession: worktreeSession, workdir: workdir, version: version, model: model, onExit: handleExit }), { exitOnCtrlC: false });
16
+ // Wait for the app to finish unmounting
17
+ await waitUntilExit();
18
+ try {
19
+ // Clean up old log files
20
+ await cleanupLogs().catch((error) => {
21
+ console.warn("Failed to cleanup old logs:", error);
22
+ });
23
+ // Cleanup worktree if requested
24
+ if (shouldRemoveWorktree && worktreeSession) {
25
+ process.chdir(worktreeSession.repoRoot);
26
+ removeWorktree(worktreeSession);
58
27
  }
59
- });
60
- // Return a promise that never resolves to keep the CLI running
61
- return new Promise(() => { });
28
+ process.exit(0);
29
+ }
30
+ catch (error) {
31
+ console.error("Error during cleanup:", error);
32
+ process.exit(1);
33
+ }
62
34
  }
@@ -1,10 +1,9 @@
1
1
  import React from "react";
2
- interface AppProps {
2
+ import { BaseAppProps } from "../types.js";
3
+ interface AppProps extends BaseAppProps {
3
4
  restoreSessionId?: string;
4
5
  continueLastSession?: boolean;
5
- bypassPermissions?: boolean;
6
- pluginDirs?: string[];
7
- tools?: string[];
6
+ onExit: (shouldRemove: boolean) => void;
8
7
  }
9
8
  export declare const App: React.FC<AppProps>;
10
9
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/components/App.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAM3D,UAAU,QAAQ;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAiED,eAAO,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAmBlC,CAAC"}
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../src/components/App.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAWxE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,UAAU,QAAS,SAAQ,YAAY;IACrC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC;AA0ID,eAAO,MAAM,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CA6BlC,CAAC"}
@@ -1,11 +1,54 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef } from "react";
3
- import { useStdout } from "ink";
2
+ import { useState, useEffect, useRef, useCallback } from "react";
3
+ import { useStdout, useInput } from "ink";
4
4
  import { ChatInterface } from "./ChatInterface.js";
5
5
  import { ChatProvider, useChat } from "../contexts/useChat.js";
6
6
  import { AppProvider } from "../contexts/useAppConfig.js";
7
- const AppWithProviders = ({ bypassPermissions, pluginDirs, tools }) => {
8
- return (_jsx(ChatProvider, { bypassPermissions: bypassPermissions, pluginDirs: pluginDirs, tools: tools, children: _jsx(ChatInterfaceWithRemount, {}) }));
7
+ import { WorktreeExitPrompt } from "./WorktreeExitPrompt.js";
8
+ import { hasUncommittedChanges, hasNewCommits, getDefaultRemoteBranch, } from "wave-agent-sdk";
9
+ const AppWithProviders = ({ bypassPermissions, pluginDirs, tools, worktreeSession, workdir, version, model, onExit, }) => {
10
+ const [isExiting, setIsExiting] = useState(false);
11
+ const [worktreeStatus, setWorktreeStatus] = useState(null);
12
+ const handleSignal = useCallback(async () => {
13
+ if (worktreeSession) {
14
+ const cwd = workdir || worktreeSession.path;
15
+ const baseBranch = getDefaultRemoteBranch(cwd);
16
+ const hasChanges = hasUncommittedChanges(cwd);
17
+ const hasCommits = hasNewCommits(cwd, baseBranch);
18
+ if (hasChanges || hasCommits) {
19
+ setWorktreeStatus({
20
+ hasUncommittedChanges: hasChanges,
21
+ hasNewCommits: hasCommits,
22
+ });
23
+ setIsExiting(true);
24
+ }
25
+ else {
26
+ onExit(true);
27
+ }
28
+ }
29
+ else {
30
+ onExit(false);
31
+ }
32
+ }, [worktreeSession, workdir, onExit]);
33
+ useInput((input, key) => {
34
+ if (input === "c" && key.ctrl) {
35
+ handleSignal();
36
+ }
37
+ });
38
+ useEffect(() => {
39
+ const onSigInt = () => handleSignal();
40
+ const onSigTerm = () => handleSignal();
41
+ process.on("SIGINT", onSigInt);
42
+ process.on("SIGTERM", onSigTerm);
43
+ return () => {
44
+ process.off("SIGINT", onSigInt);
45
+ process.off("SIGTERM", onSigTerm);
46
+ };
47
+ }, [handleSignal]);
48
+ if (isExiting && worktreeSession && worktreeStatus) {
49
+ return (_jsx(WorktreeExitPrompt, { name: worktreeSession.name, path: worktreeSession.path, hasUncommittedChanges: worktreeStatus.hasUncommittedChanges, hasNewCommits: worktreeStatus.hasNewCommits, onKeep: () => onExit(false), onRemove: () => onExit(true), onCancel: () => setIsExiting(false) }));
50
+ }
51
+ return (_jsx(ChatProvider, { bypassPermissions: bypassPermissions, pluginDirs: pluginDirs, tools: tools, workdir: workdir, worktreeSession: worktreeSession, version: version, model: model, children: _jsx(ChatInterfaceWithRemount, {}) }));
9
52
  };
10
53
  const ChatInterfaceWithRemount = () => {
11
54
  const { stdout } = useStdout();
@@ -43,6 +86,6 @@ const ChatInterfaceWithRemount = () => {
43
86
  ]);
44
87
  return _jsx(ChatInterface, {}, remountKey);
45
88
  };
46
- export const App = ({ restoreSessionId, continueLastSession, bypassPermissions, pluginDirs, tools, }) => {
47
- return (_jsx(AppProvider, { restoreSessionId: restoreSessionId, continueLastSession: continueLastSession, children: _jsx(AppWithProviders, { bypassPermissions: bypassPermissions, pluginDirs: pluginDirs, tools: tools }) }));
89
+ export const App = ({ restoreSessionId, continueLastSession, bypassPermissions, pluginDirs, tools, worktreeSession, workdir, version, model, onExit, }) => {
90
+ return (_jsx(AppProvider, { restoreSessionId: restoreSessionId, continueLastSession: continueLastSession, children: _jsx(AppWithProviders, { bypassPermissions: bypassPermissions, pluginDirs: pluginDirs, tools: tools, worktreeSession: worktreeSession, workdir: workdir, version: version, model: model, onExit: onExit }) }));
48
91
  };
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import type { BangBlock } from "wave-agent-sdk";
3
+ interface BangDisplayProps {
4
+ block: BangBlock;
5
+ isExpanded?: boolean;
6
+ }
7
+ export declare const BangDisplay: React.FC<BangDisplayProps>;
8
+ export {};
9
+ //# sourceMappingURL=BangDisplay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BangDisplay.d.ts","sourceRoot":"","sources":["../../src/components/BangDisplay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,UAAU,gBAAgB;IACxB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAiDlD,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
3
  import { Box, Text } from "ink";
4
- export const CommandOutputDisplay = ({ block, isExpanded = false, }) => {
4
+ export const BangDisplay = ({ block, isExpanded = false, }) => {
5
5
  const { command, output, isRunning, exitCode } = block;
6
6
  const [isOverflowing, setIsOverflowing] = useState(false);
7
7
  const MAX_LINES = 3; // Set maximum display lines
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInterface.d.ts","sourceRoot":"","sources":["../../src/components/ChatInterface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAYtE,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAqIjC,CAAC"}
1
+ {"version":3,"file":"ChatInterface.d.ts","sourceRoot":"","sources":["../../src/components/ChatInterface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiD,MAAM,OAAO,CAAC;AAYtE,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EA6IjC,CAAC"}
@@ -13,7 +13,8 @@ export const ChatInterface = () => {
13
13
  const [detailsHeight, setDetailsHeight] = useState(0);
14
14
  const [selectorHeight, setSelectorHeight] = useState(0);
15
15
  const [isConfirmationTooTall, setIsConfirmationTooTall] = useState(false);
16
- const { messages, isLoading, isCommandRunning, isCompressing, sendMessage, abortMessage, mcpServers, connectMcpServer, disconnectMcpServer, isExpanded, sessionId, latestTotalTokens, slashCommands, hasSlashCommand, isConfirmationVisible, confirmingTool, handleConfirmationDecision, handleConfirmationCancel: originalHandleConfirmationCancel, setWasLastDetailsTooTall, } = useChat();
16
+ const { messages, isLoading, isCommandRunning, isCompressing, sendMessage, abortMessage, mcpServers, connectMcpServer, disconnectMcpServer, isExpanded, sessionId, latestTotalTokens, slashCommands, hasSlashCommand, isConfirmationVisible, confirmingTool, handleConfirmationDecision, handleConfirmationCancel: originalHandleConfirmationCancel, setWasLastDetailsTooTall, version, workdir, getModelConfig, } = useChat();
17
+ const model = getModelConfig().model;
17
18
  const handleDetailsHeightMeasured = useCallback((height) => {
18
19
  setDetailsHeight(height);
19
20
  }, []);
@@ -54,7 +55,7 @@ export const ChatInterface = () => {
54
55
  ]);
55
56
  if (!sessionId)
56
57
  return null;
57
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(MessageList, { messages: messages, isExpanded: isExpanded, hideDynamicBlocks: isConfirmationVisible }), (isLoading || isCommandRunning || isCompressing) &&
58
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(MessageList, { messages: messages, isExpanded: isExpanded, hideDynamicBlocks: isConfirmationVisible, version: version, workdir: workdir, model: model }), (isLoading || isCommandRunning || isCompressing) &&
58
59
  !isConfirmationVisible &&
59
60
  !isExpanded && (_jsx(LoadingIndicator, { isLoading: isLoading, isCommandRunning: isCommandRunning, isCompressing: isCompressing, latestTotalTokens: latestTotalTokens })), !isConfirmationVisible && !isExpanded && _jsx(TaskList, {}), isConfirmationVisible && (_jsxs(_Fragment, { children: [_jsx(ConfirmationDetails, { toolName: confirmingTool.name, toolInput: confirmingTool.input, isExpanded: isExpanded, onHeightMeasured: handleDetailsHeightMeasured, isStatic: isConfirmationTooTall }), _jsx(ConfirmationSelector, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, isExpanded: isExpanded, onDecision: wrappedHandleConfirmationDecision, onCancel: handleConfirmationCancel, onAbort: abortMessage, onHeightMeasured: handleSelectorHeightMeasured })] })), !isConfirmationVisible && !isExpanded && (_jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, sendMessage: sendMessage, abortMessage: abortMessage, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand }))] }));
60
61
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CommandSelector.d.ts","sourceRoot":"","sources":["../../src/components/CommandSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA8BnD,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA0I1D,CAAC"}
1
+ {"version":3,"file":"CommandSelector.d.ts","sourceRoot":"","sources":["../../src/components/CommandSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA+I1D,CAAC"}
@@ -1,38 +1,17 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import React, { useState } from "react";
3
3
  import { Box, Text, useInput } from "ink";
4
- const AVAILABLE_COMMANDS = [
5
- {
6
- id: "tasks",
7
- name: "tasks",
8
- description: "View and manage background tasks (shells and subagents)",
9
- handler: () => { }, // Handler here won't be used, actual processing is in the hook
10
- },
11
- {
12
- id: "mcp",
13
- name: "mcp",
14
- description: "View and manage MCP servers",
15
- handler: () => { }, // Handler here won't be used, actual processing is in the hook
16
- },
17
- {
18
- id: "rewind",
19
- name: "rewind",
20
- description: "Revert conversation and file changes to a previous checkpoint",
21
- handler: () => { }, // Handler here won't be used, actual processing is in the hook
22
- },
23
- {
24
- id: "help",
25
- name: "help",
26
- description: "Show help and key bindings",
27
- handler: () => { }, // Handler here won't be used, actual processing is in the hook
28
- },
29
- ];
4
+ import { AVAILABLE_COMMANDS } from "../constants/commands.js";
30
5
  export const CommandSelector = ({ searchQuery, onSelect, onInsert, onCancel, commands = [], // Default to empty array
31
6
  }) => {
32
7
  const MAX_VISIBLE_ITEMS = 3;
33
8
  const [selectedIndex, setSelectedIndex] = useState(0);
9
+ // Reset selected index when search query changes
10
+ React.useEffect(() => {
11
+ setSelectedIndex(0);
12
+ }, [searchQuery]);
34
13
  // Merge agent commands and local commands
35
- const allCommands = [...commands, ...AVAILABLE_COMMANDS];
14
+ const allCommands = [...AVAILABLE_COMMANDS, ...commands];
36
15
  // Filter command list
37
16
  const filteredCommands = allCommands.filter((command) => !searchQuery ||
38
17
  command.id.toLowerCase().includes(searchQuery.toLowerCase()));
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmationSelector.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4C,MAAM,OAAO,CAAC;AAEjE,OAAO,KAAK,EAAE,kBAAkB,EAAwB,MAAM,gBAAgB,CAAC;AAgB/E,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AASD,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAsfpE,CAAC"}
1
+ {"version":3,"file":"ConfirmationSelector.d.ts","sourceRoot":"","sources":["../../src/components/ConfirmationSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuD,MAAM,OAAO,CAAC;AAE5E,OAAO,KAAK,EAAE,kBAAkB,EAAwB,MAAM,gBAAgB,CAAC;AAgB/E,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AASD,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CA6mBpE,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useLayoutEffect, useRef, useState } from "react";
2
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
3
3
  import { Box, Text, useInput, useStdout, measureElement } from "ink";
4
4
  import { BASH_TOOL_NAME, EXIT_PLAN_MODE_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "wave-agent-sdk";
5
5
  const getHeaderColor = (header) => {
@@ -32,6 +32,15 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
32
32
  userAnswers: {},
33
33
  otherText: "",
34
34
  otherCursorPosition: 0,
35
+ savedStates: {},
36
+ });
37
+ const pendingDecisionRef = useRef(null);
38
+ useEffect(() => {
39
+ if (pendingDecisionRef.current) {
40
+ const decision = pendingDecisionRef.current;
41
+ pendingDecisionRef.current = null;
42
+ onDecision(decision);
43
+ }
35
44
  });
36
45
  const questions = toolInput?.questions || [];
37
46
  const currentQuestion = questions[questionState.currentQuestionIndex];
@@ -87,24 +96,72 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
87
96
  [currentQuestion.question]: answer,
88
97
  };
89
98
  if (prev.currentQuestionIndex < questions.length - 1) {
90
- return {
91
- ...prev,
92
- currentQuestionIndex: prev.currentQuestionIndex + 1,
99
+ const nextIndex = prev.currentQuestionIndex + 1;
100
+ const savedStates = {
101
+ ...prev.savedStates,
102
+ [prev.currentQuestionIndex]: {
103
+ selectedOptionIndex: prev.selectedOptionIndex,
104
+ selectedOptionIndices: prev.selectedOptionIndices,
105
+ otherText: prev.otherText,
106
+ otherCursorPosition: prev.otherCursorPosition,
107
+ },
108
+ };
109
+ const nextState = savedStates[nextIndex] || {
93
110
  selectedOptionIndex: 0,
94
111
  selectedOptionIndices: new Set(),
95
- userAnswers: newAnswers,
96
112
  otherText: "",
97
113
  otherCursorPosition: 0,
98
114
  };
115
+ return {
116
+ ...prev,
117
+ currentQuestionIndex: nextIndex,
118
+ ...nextState,
119
+ userAnswers: newAnswers,
120
+ savedStates,
121
+ };
99
122
  }
100
123
  else {
101
- onDecision({
124
+ const finalAnswers = { ...newAnswers };
125
+ // Also collect from savedStates for any questions that were skipped via Tab
126
+ for (const [idxStr, s] of Object.entries(prev.savedStates)) {
127
+ const idx = parseInt(idxStr);
128
+ const q = questions[idx];
129
+ if (q && !finalAnswers[q.question]) {
130
+ const opts = [...q.options, { label: "Other" }];
131
+ let a = "";
132
+ if (q.multiSelect) {
133
+ const selectedLabels = Array.from(s.selectedOptionIndices)
134
+ .filter((i) => i < q.options.length)
135
+ .map((i) => q.options[i].label);
136
+ const isOtherChecked = s.selectedOptionIndices.has(opts.length - 1);
137
+ if (isOtherChecked && s.otherText.trim()) {
138
+ selectedLabels.push(s.otherText.trim());
139
+ }
140
+ a = selectedLabels.join(", ");
141
+ }
142
+ else {
143
+ if (s.selectedOptionIndex === opts.length - 1) {
144
+ a = s.otherText.trim();
145
+ }
146
+ else {
147
+ a = opts[s.selectedOptionIndex].label;
148
+ }
149
+ }
150
+ if (a)
151
+ finalAnswers[q.question] = a;
152
+ }
153
+ }
154
+ // Only submit if all questions have been answered
155
+ const allAnswered = questions.every((q) => finalAnswers[q.question]);
156
+ if (!allAnswered)
157
+ return prev;
158
+ pendingDecisionRef.current = {
102
159
  behavior: "allow",
103
- message: JSON.stringify(newAnswers),
104
- });
160
+ message: JSON.stringify(finalAnswers),
161
+ };
105
162
  return {
106
163
  ...prev,
107
- userAnswers: newAnswers,
164
+ userAnswers: finalAnswers,
108
165
  };
109
166
  }
110
167
  });
@@ -144,6 +201,40 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
144
201
  }));
145
202
  return;
146
203
  }
204
+ if (key.tab) {
205
+ setQuestionState((prev) => {
206
+ const direction = key.shift ? -1 : 1;
207
+ let nextIndex = prev.currentQuestionIndex + direction;
208
+ if (nextIndex < 0)
209
+ nextIndex = questions.length - 1;
210
+ if (nextIndex >= questions.length)
211
+ nextIndex = 0;
212
+ if (nextIndex === prev.currentQuestionIndex)
213
+ return prev;
214
+ const savedStates = {
215
+ ...prev.savedStates,
216
+ [prev.currentQuestionIndex]: {
217
+ selectedOptionIndex: prev.selectedOptionIndex,
218
+ selectedOptionIndices: prev.selectedOptionIndices,
219
+ otherText: prev.otherText,
220
+ otherCursorPosition: prev.otherCursorPosition,
221
+ },
222
+ };
223
+ const nextState = savedStates[nextIndex] || {
224
+ selectedOptionIndex: 0,
225
+ selectedOptionIndices: new Set(),
226
+ otherText: "",
227
+ otherCursorPosition: 0,
228
+ };
229
+ return {
230
+ ...prev,
231
+ currentQuestionIndex: nextIndex,
232
+ ...nextState,
233
+ savedStates,
234
+ };
235
+ });
236
+ return;
237
+ }
147
238
  setQuestionState((prev) => {
148
239
  const isOtherFocused = prev.selectedOptionIndex === options.length - 1;
149
240
  if (isOtherFocused) {
@@ -258,6 +349,20 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
258
349
  }
259
350
  return;
260
351
  }
352
+ if (key.tab) {
353
+ const currentIndex = availableOptions.indexOf(state.selectedOption);
354
+ const direction = key.shift ? -1 : 1;
355
+ let nextIndex = currentIndex + direction;
356
+ if (nextIndex < 0)
357
+ nextIndex = availableOptions.length - 1;
358
+ if (nextIndex >= availableOptions.length)
359
+ nextIndex = 0;
360
+ setState((prev) => ({
361
+ ...prev,
362
+ selectedOption: availableOptions[nextIndex],
363
+ }));
364
+ return;
365
+ }
261
366
  if (input && !key.ctrl && !key.meta && !("alt" in key && key.alt)) {
262
367
  setState((prev) => {
263
368
  const nextText = prev.alternativeText.slice(0, prev.alternativeCursorPosition) +
@@ -306,8 +411,8 @@ export const ConfirmationSelector = ({ toolName, toolInput, suggestedPrefix, hid
306
411
  ? "[x] "
307
412
  : "[ ] "
308
413
  : "", option.label, option.description ? ` - ${option.description}` : "", isOther && isSelected && (_jsxs(Text, { children: [":", " ", questionState.otherText ? (_jsxs(_Fragment, { children: [questionState.otherText.slice(0, questionState.otherCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: questionState.otherText[questionState.otherCursorPosition] || " " }), questionState.otherText.slice(questionState.otherCursorPosition + 1)] })) : (_jsx(Text, { color: "gray", dimColor: true, children: "[Type your answer...]" }))] }))] }) }, index));
309
- }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Question ", questionState.currentQuestionIndex + 1, " of", " ", questions.length, " \u2022", currentQuestion.multiSelect ? " Space to toggle •" : "", " Use \u2191\u2193 to navigate \u2022 Enter to confirm"] }) })] })), toolName !== ASK_USER_QUESTION_TOOL_NAME && !isExpanded && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Do you want to proceed?" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [toolName === EXIT_PLAN_MODE_TOOL_NAME && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "clear" ? "black" : "white", backgroundColor: state.selectedOption === "clear" ? "yellow" : undefined, bold: state.selectedOption === "clear", children: [state.selectedOption === "clear" ? "> " : " ", "Yes, clear context and auto-accept edits"] }) }, "clear-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "allow" ? "black" : "white", backgroundColor: state.selectedOption === "allow" ? "yellow" : undefined, bold: state.selectedOption === "allow", children: [state.selectedOption === "allow" ? "> " : " ", toolName === EXIT_PLAN_MODE_TOOL_NAME
414
+ }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Question ", questionState.currentQuestionIndex + 1, " of", " ", questions.length, " \u2022", currentQuestion.multiSelect ? " Space to toggle •" : "", " Use \u2191\u2193 or Tab to navigate \u2022 Enter to confirm"] }) })] })), toolName !== ASK_USER_QUESTION_TOOL_NAME && !isExpanded && (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Do you want to proceed?" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [toolName === EXIT_PLAN_MODE_TOOL_NAME && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "clear" ? "black" : "white", backgroundColor: state.selectedOption === "clear" ? "yellow" : undefined, bold: state.selectedOption === "clear", children: [state.selectedOption === "clear" ? "> " : " ", "Yes, clear context and auto-accept edits"] }) }, "clear-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "allow" ? "black" : "white", backgroundColor: state.selectedOption === "allow" ? "yellow" : undefined, bold: state.selectedOption === "allow", children: [state.selectedOption === "allow" ? "> " : " ", toolName === EXIT_PLAN_MODE_TOOL_NAME
310
415
  ? "Yes, manually approve edits"
311
- : "Yes, proceed"] }) }, "allow-option"), !hidePersistentOption && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "auto" ? "black" : "white", backgroundColor: state.selectedOption === "auto" ? "yellow" : undefined, bold: state.selectedOption === "auto", children: [state.selectedOption === "auto" ? "> " : " ", getAutoOptionText()] }) }, "auto-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "alternative" ? "black" : "white", backgroundColor: state.selectedOption === "alternative" ? "yellow" : undefined, bold: state.selectedOption === "alternative", children: [state.selectedOption === "alternative" ? "> " : " ", showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: placeholderText })) : (_jsx(Text, { children: state.alternativeText ? (_jsxs(_Fragment, { children: [state.alternativeText.slice(0, state.alternativeCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: state.alternativeText[state.alternativeCursorPosition] || " " }), state.alternativeText.slice(state.alternativeCursorPosition + 1)] })) : ("Type here to tell Wave what to change") }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate \u2022 ESC to cancel" }) })] }))] }));
416
+ : "Yes, proceed"] }) }, "allow-option"), !hidePersistentOption && (_jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "auto" ? "black" : "white", backgroundColor: state.selectedOption === "auto" ? "yellow" : undefined, bold: state.selectedOption === "auto", children: [state.selectedOption === "auto" ? "> " : " ", getAutoOptionText()] }) }, "auto-option")), _jsx(Box, { children: _jsxs(Text, { color: state.selectedOption === "alternative" ? "black" : "white", backgroundColor: state.selectedOption === "alternative" ? "yellow" : undefined, bold: state.selectedOption === "alternative", children: [state.selectedOption === "alternative" ? "> " : " ", showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: placeholderText })) : (_jsx(Text, { children: state.alternativeText ? (_jsxs(_Fragment, { children: [state.alternativeText.slice(0, state.alternativeCursorPosition), _jsx(Text, { backgroundColor: "white", color: "black", children: state.alternativeText[state.alternativeCursorPosition] || " " }), state.alternativeText.slice(state.alternativeCursorPosition + 1)] })) : ("Type here to tell Wave what to change") }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 or Tab to navigate \u2022 ESC to cancel" }) })] }))] }));
312
417
  };
313
418
  ConfirmationSelector.displayName = "ConfirmationSelector";
@@ -1,6 +1,8 @@
1
1
  import React from "react";
2
+ import type { SlashCommand } from "wave-agent-sdk";
2
3
  export interface HelpViewProps {
3
4
  onCancel: () => void;
5
+ commands?: SlashCommand[];
4
6
  }
5
7
  export declare const HelpView: React.FC<HelpViewProps>;
6
8
  //# sourceMappingURL=HelpView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HelpView.d.ts","sourceRoot":"","sources":["../../src/components/HelpView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAmD5C,CAAC"}
1
+ {"version":3,"file":"HelpView.d.ts","sourceRoot":"","sources":["../../src/components/HelpView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAmK5C,CAAC"}
@@ -1,9 +1,40 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
2
3
  import { Box, Text, useInput } from "ink";
3
- export const HelpView = ({ onCancel }) => {
4
+ import { AVAILABLE_COMMANDS } from "../constants/commands.js";
5
+ export const HelpView = ({ onCancel, commands = [], }) => {
6
+ const [activeTab, setActiveTab] = useState("general");
7
+ const [selectedIndex, setSelectedIndex] = useState(0);
8
+ const MAX_VISIBLE_ITEMS = 10;
9
+ const tabs = [
10
+ "general",
11
+ "commands",
12
+ ];
13
+ if (commands.length > 0) {
14
+ tabs.push("custom-commands");
15
+ }
4
16
  useInput((_, key) => {
5
- if (key.escape || key.return) {
17
+ if (key.escape) {
6
18
  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
+ }
7
38
  }
8
39
  });
9
40
  const helpItems = [
@@ -20,5 +51,20 @@ export const HelpView = ({ onCancel }) => {
20
51
  description: "Interrupt AI or command / Cancel selector / Close help",
21
52
  },
22
53
  ];
23
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", borderLeft: false, borderRight: false, paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "cyan", bold: true, underline: true, children: "Help & Key Bindings" }) }), helpItems.map((item, index) => (_jsxs(Box, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: item.key }) }), _jsx(Text, { color: "white", children: item.description })] }, index))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Esc or Enter to close" }) })] }));
54
+ // Calculate visible window for commands
55
+ const currentCommands = activeTab === "commands" ? AVAILABLE_COMMANDS : commands;
56
+ const startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2), Math.max(0, currentCommands.length - MAX_VISIBLE_ITEMS)));
57
+ const visibleCommands = currentCommands.slice(startIndex, startIndex + MAX_VISIBLE_ITEMS);
58
+ const footerText = [
59
+ "Tab switch",
60
+ activeTab !== "general" && "↑↓ navigate",
61
+ "Esc close",
62
+ ]
63
+ .filter(Boolean)
64
+ .join(" • ");
65
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", borderLeft: false, borderRight: false, paddingX: 1, width: "100%", children: [_jsxs(Box, { marginBottom: 1, gap: 2, children: [_jsx(Text, { color: activeTab === "general" ? "cyan" : "gray", bold: true, underline: activeTab === "general", children: "General" }), _jsx(Text, { color: activeTab === "commands" ? "cyan" : "gray", bold: true, underline: activeTab === "commands", children: "Commands" }), commands.length > 0 && (_jsx(Text, { color: activeTab === "custom-commands" ? "cyan" : "gray", bold: true, underline: activeTab === "custom-commands", children: "Custom Commands" }))] }), activeTab === "general" ? (_jsx(Box, { flexDirection: "column", children: helpItems.map((item, index) => (_jsxs(Box, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: "yellow", children: item.key }) }), _jsx(Text, { color: "white", children: item.description })] }, index))) })) : (_jsx(Box, { flexDirection: "column", children: visibleCommands.map((command, index) => {
66
+ const actualIndex = startIndex + index;
67
+ const isSelected = actualIndex === selectedIndex;
68
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: isSelected ? "black" : "white", backgroundColor: isSelected ? "cyan" : undefined, children: [isSelected ? "▶ " : " ", "/", command.id] }), isSelected && (_jsx(Box, { marginLeft: 4, children: _jsx(Text, { color: "gray", dimColor: true, children: command.description }) }))] }, command.id));
69
+ }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: footerText }) })] }));
24
70
  };
@@ -1 +1 @@
1
- {"version":3,"file":"InputBox.d.ts","sourceRoot":"","sources":["../../src/components/InputBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAazC,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,KAC/C,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,CAuN5C,CAAC"}
1
+ {"version":3,"file":"InputBox.d.ts","sourceRoot":"","sources":["../../src/components/InputBox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAczC,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,KAC/C,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,CAkO5C,CAAC"}