wave-code 0.0.5 → 0.0.8
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.
- package/README.md +3 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/components/App.d.ts +1 -0
- package/dist/components/App.d.ts.map +1 -1
- package/dist/components/App.js +4 -4
- package/dist/components/BashHistorySelector.d.ts.map +1 -1
- package/dist/components/BashHistorySelector.js +17 -3
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +6 -24
- package/dist/components/CommandSelector.js +4 -4
- package/dist/components/Confirmation.d.ts +11 -0
- package/dist/components/Confirmation.d.ts.map +1 -0
- package/dist/components/Confirmation.js +148 -0
- package/dist/components/DiffDisplay.d.ts +8 -0
- package/dist/components/DiffDisplay.d.ts.map +1 -0
- package/dist/components/DiffDisplay.js +168 -0
- package/dist/components/FileSelector.d.ts +2 -4
- package/dist/components/FileSelector.d.ts.map +1 -1
- package/dist/components/FileSelector.js +2 -2
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +30 -50
- package/dist/components/Markdown.d.ts +6 -0
- package/dist/components/Markdown.d.ts.map +1 -0
- package/dist/components/Markdown.js +22 -0
- package/dist/components/MemoryDisplay.js +1 -1
- package/dist/components/MessageItem.d.ts +8 -0
- package/dist/components/MessageItem.d.ts.map +1 -0
- package/dist/components/MessageItem.js +15 -0
- package/dist/components/MessageList.d.ts +1 -1
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +33 -33
- package/dist/components/ReasoningDisplay.d.ts +8 -0
- package/dist/components/ReasoningDisplay.d.ts.map +1 -0
- package/dist/components/ReasoningDisplay.js +10 -0
- package/dist/components/SubagentBlock.d.ts +0 -1
- package/dist/components/SubagentBlock.d.ts.map +1 -1
- package/dist/components/SubagentBlock.js +29 -30
- package/dist/components/ToolResultDisplay.d.ts.map +1 -1
- package/dist/components/ToolResultDisplay.js +6 -5
- package/dist/contexts/useChat.d.ts +14 -2
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +128 -17
- package/dist/hooks/useInputManager.d.ts +6 -1
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +32 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -5
- package/dist/managers/InputManager.d.ts +11 -1
- package/dist/managers/InputManager.d.ts.map +1 -1
- package/dist/managers/InputManager.js +77 -26
- package/dist/print-cli.d.ts +2 -0
- package/dist/print-cli.d.ts.map +1 -1
- package/dist/print-cli.js +121 -23
- package/dist/utils/toolParameterTransforms.d.ts +23 -0
- package/dist/utils/toolParameterTransforms.d.ts.map +1 -0
- package/dist/utils/toolParameterTransforms.js +77 -0
- package/dist/utils/usageSummary.d.ts +6 -0
- package/dist/utils/usageSummary.d.ts.map +1 -1
- package/dist/utils/usageSummary.js +72 -0
- package/package.json +13 -8
- package/src/cli.tsx +3 -1
- package/src/components/App.tsx +7 -3
- package/src/components/BashHistorySelector.tsx +26 -3
- package/src/components/ChatInterface.tsx +38 -54
- package/src/components/CommandSelector.tsx +5 -5
- package/src/components/Confirmation.tsx +253 -0
- package/src/components/DiffDisplay.tsx +300 -0
- package/src/components/FileSelector.tsx +4 -6
- package/src/components/InputBox.tsx +58 -87
- package/src/components/Markdown.tsx +29 -0
- package/src/components/MemoryDisplay.tsx +1 -1
- package/src/components/MessageItem.tsx +96 -0
- package/src/components/MessageList.tsx +140 -202
- package/src/components/ReasoningDisplay.tsx +33 -0
- package/src/components/SubagentBlock.tsx +56 -84
- package/src/components/ToolResultDisplay.tsx +9 -5
- package/src/contexts/useChat.tsx +194 -21
- package/src/hooks/useInputManager.ts +40 -3
- package/src/index.ts +45 -5
- package/src/managers/InputManager.ts +101 -27
- package/src/print-cli.ts +143 -21
- package/src/utils/toolParameterTransforms.ts +104 -0
- package/src/utils/usageSummary.ts +109 -0
- package/dist/components/DiffViewer.d.ts +0 -9
- package/dist/components/DiffViewer.d.ts.map +0 -1
- package/dist/components/DiffViewer.js +0 -221
- package/dist/utils/fileSearch.d.ts +0 -20
- package/dist/utils/fileSearch.d.ts.map +0 -1
- package/dist/utils/fileSearch.js +0 -102
- package/src/components/DiffViewer.tsx +0 -321
- package/src/utils/fileSearch.ts +0 -133
package/src/cli.tsx
CHANGED
|
@@ -6,10 +6,11 @@ import { cleanupLogs } from "./utils/logger.js";
|
|
|
6
6
|
export interface CliOptions {
|
|
7
7
|
restoreSessionId?: string;
|
|
8
8
|
continueLastSession?: boolean;
|
|
9
|
+
bypassPermissions?: boolean;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export async function startCli(options: CliOptions): Promise<void> {
|
|
12
|
-
const { restoreSessionId, continueLastSession } = options;
|
|
13
|
+
const { restoreSessionId, continueLastSession, bypassPermissions } = options;
|
|
13
14
|
|
|
14
15
|
// Continue with ink-based UI for normal mode
|
|
15
16
|
// Global cleanup tracker
|
|
@@ -63,6 +64,7 @@ export async function startCli(options: CliOptions): Promise<void> {
|
|
|
63
64
|
<App
|
|
64
65
|
restoreSessionId={restoreSessionId}
|
|
65
66
|
continueLastSession={continueLastSession}
|
|
67
|
+
bypassPermissions={bypassPermissions}
|
|
66
68
|
/>,
|
|
67
69
|
);
|
|
68
70
|
|
package/src/components/App.tsx
CHANGED
|
@@ -6,11 +6,14 @@ import { AppProvider } from "../contexts/useAppConfig.js";
|
|
|
6
6
|
interface AppProps {
|
|
7
7
|
restoreSessionId?: string;
|
|
8
8
|
continueLastSession?: boolean;
|
|
9
|
+
bypassPermissions?: boolean;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
const AppWithProviders: React.FC = (
|
|
12
|
+
const AppWithProviders: React.FC<{ bypassPermissions?: boolean }> = ({
|
|
13
|
+
bypassPermissions,
|
|
14
|
+
}) => {
|
|
12
15
|
return (
|
|
13
|
-
<ChatProvider>
|
|
16
|
+
<ChatProvider bypassPermissions={bypassPermissions}>
|
|
14
17
|
<ChatInterface />
|
|
15
18
|
</ChatProvider>
|
|
16
19
|
);
|
|
@@ -19,13 +22,14 @@ const AppWithProviders: React.FC = () => {
|
|
|
19
22
|
export const App: React.FC<AppProps> = ({
|
|
20
23
|
restoreSessionId,
|
|
21
24
|
continueLastSession,
|
|
25
|
+
bypassPermissions,
|
|
22
26
|
}) => {
|
|
23
27
|
return (
|
|
24
28
|
<AppProvider
|
|
25
29
|
restoreSessionId={restoreSessionId}
|
|
26
30
|
continueLastSession={continueLastSession}
|
|
27
31
|
>
|
|
28
|
-
<AppWithProviders />
|
|
32
|
+
<AppWithProviders bypassPermissions={bypassPermissions} />
|
|
29
33
|
</AppProvider>
|
|
30
34
|
);
|
|
31
35
|
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
searchBashHistory,
|
|
5
|
+
deleteBashCommandFromHistory,
|
|
6
|
+
type BashHistoryEntry,
|
|
7
|
+
} from "wave-agent-sdk";
|
|
4
8
|
import { logger } from "../utils/logger.js";
|
|
5
9
|
|
|
6
10
|
export interface BashHistorySelectorProps {
|
|
@@ -20,6 +24,7 @@ export const BashHistorySelector: React.FC<BashHistorySelectorProps> = ({
|
|
|
20
24
|
}) => {
|
|
21
25
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
22
26
|
const [commands, setCommands] = useState<BashHistoryEntry[]>([]);
|
|
27
|
+
const [refreshCounter, setRefreshCounter] = useState(0);
|
|
23
28
|
|
|
24
29
|
// Search bash history
|
|
25
30
|
useEffect(() => {
|
|
@@ -30,8 +35,9 @@ export const BashHistorySelector: React.FC<BashHistorySelectorProps> = ({
|
|
|
30
35
|
searchQuery,
|
|
31
36
|
workdir,
|
|
32
37
|
resultCount: results.length,
|
|
38
|
+
refreshCounter,
|
|
33
39
|
});
|
|
34
|
-
}, [searchQuery, workdir]);
|
|
40
|
+
}, [searchQuery, workdir, refreshCounter]);
|
|
35
41
|
|
|
36
42
|
useInput((input, key) => {
|
|
37
43
|
logger.debug("BashHistorySelector useInput:", {
|
|
@@ -77,6 +83,22 @@ export const BashHistorySelector: React.FC<BashHistorySelectorProps> = ({
|
|
|
77
83
|
setSelectedIndex(Math.min(commands.length - 1, selectedIndex + 1));
|
|
78
84
|
return;
|
|
79
85
|
}
|
|
86
|
+
|
|
87
|
+
if (key.delete) {
|
|
88
|
+
if (commands.length > 0 && selectedIndex < commands.length) {
|
|
89
|
+
const selectedCommand = commands[selectedIndex];
|
|
90
|
+
deleteBashCommandFromHistory(
|
|
91
|
+
selectedCommand.command,
|
|
92
|
+
selectedCommand.workdir,
|
|
93
|
+
);
|
|
94
|
+
setRefreshCounter((prev) => prev + 1);
|
|
95
|
+
// Adjust selectedIndex if we deleted the last item
|
|
96
|
+
if (selectedIndex >= commands.length - 1 && selectedIndex > 0) {
|
|
97
|
+
setSelectedIndex(selectedIndex - 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
80
102
|
});
|
|
81
103
|
|
|
82
104
|
if (commands.length === 0) {
|
|
@@ -155,7 +177,8 @@ export const BashHistorySelector: React.FC<BashHistorySelectorProps> = ({
|
|
|
155
177
|
|
|
156
178
|
<Box>
|
|
157
179
|
<Text dimColor>
|
|
158
|
-
Use ↑↓ to navigate, Enter to execute, Tab to insert,
|
|
180
|
+
Use ↑↓ to navigate, Enter to execute, Tab to insert, Delete to remove,
|
|
181
|
+
Escape to cancel
|
|
159
182
|
</Text>
|
|
160
183
|
</Box>
|
|
161
184
|
</Box>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { Box } from "ink";
|
|
3
3
|
import { MessageList } from "./MessageList.js";
|
|
4
4
|
import { InputBox } from "./InputBox.js";
|
|
5
|
+
import { Confirmation } from "./Confirmation.js";
|
|
5
6
|
import { useChat } from "../contexts/useChat.js";
|
|
6
|
-
import type { Message } from "wave-agent-sdk";
|
|
7
7
|
|
|
8
8
|
export const ChatInterface: React.FC = () => {
|
|
9
9
|
const {
|
|
@@ -19,70 +19,54 @@ export const ChatInterface: React.FC = () => {
|
|
|
19
19
|
connectMcpServer,
|
|
20
20
|
disconnectMcpServer,
|
|
21
21
|
isExpanded,
|
|
22
|
+
sessionId,
|
|
22
23
|
latestTotalTokens,
|
|
23
24
|
slashCommands,
|
|
24
25
|
hasSlashCommand,
|
|
26
|
+
isConfirmationVisible,
|
|
27
|
+
confirmingTool,
|
|
28
|
+
handleConfirmationDecision,
|
|
29
|
+
handleConfirmationCancel,
|
|
25
30
|
} = useChat();
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
const expandedMessagesRef = useRef<Message[]>([]);
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
// Only sync when collapsed
|
|
32
|
-
if (!isExpanded) {
|
|
33
|
-
expandedMessagesRef.current = messages.map((message, index) => {
|
|
34
|
-
// If it's the last message, deep copy its blocks
|
|
35
|
-
if (index === messages.length - 1) {
|
|
36
|
-
return {
|
|
37
|
-
...message,
|
|
38
|
-
blocks: message.blocks.map((block) => ({ ...block })),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return message;
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}, [isExpanded, messages]);
|
|
32
|
+
if (!sessionId) return null;
|
|
45
33
|
|
|
46
34
|
return (
|
|
47
|
-
<Box flexDirection="column" height="100%">
|
|
48
|
-
<
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
35
|
+
<Box flexDirection="column" height="100%" paddingY={1}>
|
|
36
|
+
<MessageList
|
|
37
|
+
messages={messages}
|
|
38
|
+
isLoading={isLoading}
|
|
39
|
+
isCommandRunning={isCommandRunning}
|
|
40
|
+
isCompressing={isCompressing}
|
|
41
|
+
latestTotalTokens={latestTotalTokens}
|
|
42
|
+
isExpanded={isExpanded}
|
|
43
|
+
key={String(isExpanded) + sessionId}
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
{!isExpanded &&
|
|
47
|
+
(isConfirmationVisible ? (
|
|
48
|
+
<Confirmation
|
|
49
|
+
toolName={confirmingTool!.name}
|
|
50
|
+
toolInput={confirmingTool!.input}
|
|
51
|
+
onDecision={handleConfirmationDecision}
|
|
52
|
+
onCancel={handleConfirmationCancel}
|
|
53
|
+
onAbort={abortMessage}
|
|
57
54
|
/>
|
|
58
55
|
) : (
|
|
59
|
-
|
|
60
|
-
<MessageList
|
|
61
|
-
messages={messages}
|
|
56
|
+
<InputBox
|
|
62
57
|
isLoading={isLoading}
|
|
63
58
|
isCommandRunning={isCommandRunning}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
userInputHistory={userInputHistory}
|
|
60
|
+
sendMessage={sendMessage}
|
|
61
|
+
abortMessage={abortMessage}
|
|
62
|
+
saveMemory={saveMemory}
|
|
63
|
+
mcpServers={mcpServers}
|
|
64
|
+
connectMcpServer={connectMcpServer}
|
|
65
|
+
disconnectMcpServer={disconnectMcpServer}
|
|
66
|
+
slashCommands={slashCommands}
|
|
67
|
+
hasSlashCommand={hasSlashCommand}
|
|
67
68
|
/>
|
|
68
|
-
)}
|
|
69
|
-
</Box>
|
|
70
|
-
|
|
71
|
-
{!isExpanded && (
|
|
72
|
-
<InputBox
|
|
73
|
-
isLoading={isLoading}
|
|
74
|
-
isCommandRunning={isCommandRunning}
|
|
75
|
-
userInputHistory={userInputHistory}
|
|
76
|
-
sendMessage={sendMessage}
|
|
77
|
-
abortMessage={abortMessage}
|
|
78
|
-
saveMemory={saveMemory}
|
|
79
|
-
mcpServers={mcpServers}
|
|
80
|
-
connectMcpServer={connectMcpServer}
|
|
81
|
-
disconnectMcpServer={disconnectMcpServer}
|
|
82
|
-
slashCommands={slashCommands}
|
|
83
|
-
hasSlashCommand={hasSlashCommand}
|
|
84
|
-
/>
|
|
85
|
-
)}
|
|
69
|
+
))}
|
|
86
70
|
</Box>
|
|
87
71
|
);
|
|
88
72
|
};
|
|
@@ -41,7 +41,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
41
41
|
const filteredCommands = allCommands.filter(
|
|
42
42
|
(command) =>
|
|
43
43
|
!searchQuery ||
|
|
44
|
-
command.
|
|
44
|
+
command.id.toLowerCase().includes(searchQuery.toLowerCase()),
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
useInput((input, key) => {
|
|
@@ -50,7 +50,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
50
50
|
filteredCommands.length > 0 &&
|
|
51
51
|
selectedIndex < filteredCommands.length
|
|
52
52
|
) {
|
|
53
|
-
const selectedCommand = filteredCommands[selectedIndex].
|
|
53
|
+
const selectedCommand = filteredCommands[selectedIndex].id;
|
|
54
54
|
onSelect(selectedCommand);
|
|
55
55
|
}
|
|
56
56
|
return;
|
|
@@ -61,7 +61,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
61
61
|
filteredCommands.length > 0 &&
|
|
62
62
|
selectedIndex < filteredCommands.length
|
|
63
63
|
) {
|
|
64
|
-
const selectedCommand = filteredCommands[selectedIndex].
|
|
64
|
+
const selectedCommand = filteredCommands[selectedIndex].id;
|
|
65
65
|
onInsert(selectedCommand);
|
|
66
66
|
}
|
|
67
67
|
return;
|
|
@@ -116,12 +116,12 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
116
116
|
</Box>
|
|
117
117
|
|
|
118
118
|
{filteredCommands.map((command, index) => (
|
|
119
|
-
<Box key={command.
|
|
119
|
+
<Box key={command.id} flexDirection="column">
|
|
120
120
|
<Text
|
|
121
121
|
color={index === selectedIndex ? "black" : "white"}
|
|
122
122
|
backgroundColor={index === selectedIndex ? "magenta" : undefined}
|
|
123
123
|
>
|
|
124
|
-
{index === selectedIndex ? "▶ " : " "}/{command.
|
|
124
|
+
{index === selectedIndex ? "▶ " : " "}/{command.id}
|
|
125
125
|
</Text>
|
|
126
126
|
{index === selectedIndex && (
|
|
127
127
|
<Box marginLeft={4}>
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import type { PermissionDecision } from "wave-agent-sdk";
|
|
4
|
+
|
|
5
|
+
// Helper function to generate descriptive action text
|
|
6
|
+
const getActionDescription = (
|
|
7
|
+
toolName: string,
|
|
8
|
+
toolInput?: Record<string, unknown>,
|
|
9
|
+
): string => {
|
|
10
|
+
if (!toolInput) {
|
|
11
|
+
return "Execute operation";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
switch (toolName) {
|
|
15
|
+
case "Bash":
|
|
16
|
+
return `Execute command: ${toolInput.command || "unknown command"}`;
|
|
17
|
+
case "Edit":
|
|
18
|
+
return `Edit file: ${toolInput.file_path || "unknown file"}`;
|
|
19
|
+
case "MultiEdit":
|
|
20
|
+
return `Edit multiple sections in: ${toolInput.file_path || "unknown file"}`;
|
|
21
|
+
case "Delete":
|
|
22
|
+
return `Delete file: ${toolInput.target_file || "unknown file"}`;
|
|
23
|
+
case "Write":
|
|
24
|
+
return `Write to file: ${toolInput.file_path || "unknown file"}`;
|
|
25
|
+
default:
|
|
26
|
+
return "Execute operation";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export interface ConfirmationProps {
|
|
31
|
+
toolName: string;
|
|
32
|
+
toolInput?: Record<string, unknown>;
|
|
33
|
+
onDecision: (decision: PermissionDecision) => void;
|
|
34
|
+
onCancel: () => void;
|
|
35
|
+
onAbort: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ConfirmationState {
|
|
39
|
+
selectedOption: "allow" | "auto" | "alternative";
|
|
40
|
+
alternativeText: string;
|
|
41
|
+
hasUserInput: boolean; // to hide placeholder
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
45
|
+
toolName,
|
|
46
|
+
toolInput,
|
|
47
|
+
onDecision,
|
|
48
|
+
onCancel,
|
|
49
|
+
onAbort,
|
|
50
|
+
}) => {
|
|
51
|
+
const [state, setState] = useState<ConfirmationState>({
|
|
52
|
+
selectedOption: "allow",
|
|
53
|
+
alternativeText: "",
|
|
54
|
+
hasUserInput: false,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const getAutoOptionText = () => {
|
|
58
|
+
if (toolName === "Bash") {
|
|
59
|
+
return `Yes, and don't ask again for ${toolInput?.command || "this"} commands in this workdir`;
|
|
60
|
+
}
|
|
61
|
+
return "Yes, and auto-accept edits";
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
useInput((input, key) => {
|
|
65
|
+
// Handle ESC to cancel and abort
|
|
66
|
+
if (key.escape) {
|
|
67
|
+
onCancel();
|
|
68
|
+
onAbort();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Handle Enter to confirm selection
|
|
73
|
+
if (key.return) {
|
|
74
|
+
if (state.selectedOption === "allow") {
|
|
75
|
+
onDecision({ behavior: "allow" });
|
|
76
|
+
} else if (state.selectedOption === "auto") {
|
|
77
|
+
if (toolName === "Bash") {
|
|
78
|
+
onDecision({
|
|
79
|
+
behavior: "allow",
|
|
80
|
+
newPermissionRule: `Bash(${toolInput?.command})`,
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
onDecision({
|
|
84
|
+
behavior: "allow",
|
|
85
|
+
newPermissionMode: "acceptEdits",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// For alternative option, require text input
|
|
90
|
+
if (state.alternativeText.trim()) {
|
|
91
|
+
onDecision({
|
|
92
|
+
behavior: "deny",
|
|
93
|
+
message: state.alternativeText.trim(),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle numeric keys for quick selection (only if not typing in alternative)
|
|
101
|
+
if (state.selectedOption !== "alternative" || !state.hasUserInput) {
|
|
102
|
+
if (input === "1") {
|
|
103
|
+
onDecision({ behavior: "allow" });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (input === "2") {
|
|
107
|
+
if (toolName === "Bash") {
|
|
108
|
+
onDecision({
|
|
109
|
+
behavior: "allow",
|
|
110
|
+
newPermissionRule: `Bash(${toolInput?.command})`,
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
onDecision({
|
|
114
|
+
behavior: "allow",
|
|
115
|
+
newPermissionMode: "acceptEdits",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (input === "3") {
|
|
121
|
+
setState((prev) => ({ ...prev, selectedOption: "alternative" }));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Handle arrow keys for navigation
|
|
127
|
+
if (key.upArrow) {
|
|
128
|
+
setState((prev) => {
|
|
129
|
+
if (prev.selectedOption === "alternative")
|
|
130
|
+
return { ...prev, selectedOption: "auto" };
|
|
131
|
+
if (prev.selectedOption === "auto")
|
|
132
|
+
return { ...prev, selectedOption: "allow" };
|
|
133
|
+
return prev;
|
|
134
|
+
});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (key.downArrow) {
|
|
139
|
+
setState((prev) => {
|
|
140
|
+
if (prev.selectedOption === "allow")
|
|
141
|
+
return { ...prev, selectedOption: "auto" };
|
|
142
|
+
if (prev.selectedOption === "auto")
|
|
143
|
+
return { ...prev, selectedOption: "alternative" };
|
|
144
|
+
return prev;
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Handle text input for alternative option
|
|
150
|
+
if (input && !key.ctrl && !key.meta && !("alt" in key && key.alt)) {
|
|
151
|
+
// Focus on alternative option when user starts typing
|
|
152
|
+
setState((prev) => ({
|
|
153
|
+
selectedOption: "alternative",
|
|
154
|
+
alternativeText: prev.alternativeText + input,
|
|
155
|
+
hasUserInput: true,
|
|
156
|
+
}));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle backspace and delete (same behavior - delete one character)
|
|
161
|
+
if (key.backspace || key.delete) {
|
|
162
|
+
setState((prev) => {
|
|
163
|
+
const newText = prev.alternativeText.slice(0, -1);
|
|
164
|
+
return {
|
|
165
|
+
...prev,
|
|
166
|
+
selectedOption: "alternative",
|
|
167
|
+
alternativeText: newText,
|
|
168
|
+
hasUserInput: newText.length > 0,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const placeholderText = "Type here to tell Wave what to do differently";
|
|
176
|
+
const showPlaceholder =
|
|
177
|
+
state.selectedOption === "alternative" && !state.hasUserInput;
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<Box
|
|
181
|
+
flexDirection="column"
|
|
182
|
+
borderStyle="single"
|
|
183
|
+
borderColor="yellow"
|
|
184
|
+
padding={1}
|
|
185
|
+
marginBottom={1}
|
|
186
|
+
>
|
|
187
|
+
<Text color="yellow" bold>
|
|
188
|
+
Tool: {toolName}
|
|
189
|
+
</Text>
|
|
190
|
+
<Text color="yellow">{getActionDescription(toolName, toolInput)}</Text>
|
|
191
|
+
|
|
192
|
+
<Box marginTop={1}>
|
|
193
|
+
<Text>Do you want to proceed?</Text>
|
|
194
|
+
</Box>
|
|
195
|
+
|
|
196
|
+
<Box marginTop={1} flexDirection="column">
|
|
197
|
+
{/* Option 1: Yes */}
|
|
198
|
+
<Box key="allow-option">
|
|
199
|
+
<Text
|
|
200
|
+
color={state.selectedOption === "allow" ? "black" : "white"}
|
|
201
|
+
backgroundColor={
|
|
202
|
+
state.selectedOption === "allow" ? "yellow" : undefined
|
|
203
|
+
}
|
|
204
|
+
bold={state.selectedOption === "allow"}
|
|
205
|
+
>
|
|
206
|
+
{state.selectedOption === "allow" ? "> " : " "}1. Yes
|
|
207
|
+
</Text>
|
|
208
|
+
</Box>
|
|
209
|
+
|
|
210
|
+
{/* Option 2: Auto-accept/Persistent */}
|
|
211
|
+
<Box key="auto-option">
|
|
212
|
+
<Text
|
|
213
|
+
color={state.selectedOption === "auto" ? "black" : "white"}
|
|
214
|
+
backgroundColor={
|
|
215
|
+
state.selectedOption === "auto" ? "yellow" : undefined
|
|
216
|
+
}
|
|
217
|
+
bold={state.selectedOption === "auto"}
|
|
218
|
+
>
|
|
219
|
+
{state.selectedOption === "auto" ? "> " : " "}2.{" "}
|
|
220
|
+
{getAutoOptionText()}
|
|
221
|
+
</Text>
|
|
222
|
+
</Box>
|
|
223
|
+
|
|
224
|
+
{/* Option 3: Alternative */}
|
|
225
|
+
<Box key="alternative-option">
|
|
226
|
+
<Text
|
|
227
|
+
color={state.selectedOption === "alternative" ? "black" : "white"}
|
|
228
|
+
backgroundColor={
|
|
229
|
+
state.selectedOption === "alternative" ? "yellow" : undefined
|
|
230
|
+
}
|
|
231
|
+
bold={state.selectedOption === "alternative"}
|
|
232
|
+
>
|
|
233
|
+
{state.selectedOption === "alternative" ? "> " : " "}3.{" "}
|
|
234
|
+
{showPlaceholder ? (
|
|
235
|
+
<Text color="gray" dimColor>
|
|
236
|
+
{placeholderText}
|
|
237
|
+
</Text>
|
|
238
|
+
) : (
|
|
239
|
+
<Text>
|
|
240
|
+
{state.alternativeText ||
|
|
241
|
+
"Type here to tell Wave what to do differently"}
|
|
242
|
+
</Text>
|
|
243
|
+
)}
|
|
244
|
+
</Text>
|
|
245
|
+
</Box>
|
|
246
|
+
</Box>
|
|
247
|
+
|
|
248
|
+
<Box marginTop={1}>
|
|
249
|
+
<Text dimColor>Use ↑↓ or 1-3 to navigate • ESC to cancel</Text>
|
|
250
|
+
</Box>
|
|
251
|
+
</Box>
|
|
252
|
+
);
|
|
253
|
+
};
|