wave-code 0.0.8 → 0.0.11
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 +5 -5
- package/dist/components/BashHistorySelector.d.ts.map +1 -1
- package/dist/components/BashHistorySelector.js +3 -17
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +1 -1
- package/dist/components/Confirmation.d.ts +2 -0
- package/dist/components/Confirmation.d.ts.map +1 -1
- package/dist/components/Confirmation.js +44 -20
- package/dist/components/Markdown.d.ts.map +1 -1
- package/dist/components/Markdown.js +115 -16
- package/dist/contexts/useChat.d.ts +3 -1
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +10 -3
- package/dist/managers/InputManager.js +1 -1
- package/package.json +6 -5
- package/src/components/BashHistorySelector.tsx +3 -26
- package/src/components/ChatInterface.tsx +2 -0
- package/src/components/Confirmation.tsx +64 -31
- package/src/components/Markdown.tsx +329 -16
- package/src/contexts/useChat.tsx +31 -3
- package/src/managers/InputManager.ts +1 -1
package/README.md
CHANGED
|
@@ -27,20 +27,20 @@ Before use, you need to configure the following environment variables for AI mod
|
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
# AI Gateway access token (required)
|
|
30
|
-
export
|
|
30
|
+
export WAVE_API_KEY="your_token_here"
|
|
31
31
|
|
|
32
32
|
# AI Gateway API URL (required)
|
|
33
|
-
export
|
|
33
|
+
export WAVE_BASE_URL="https://your-api-gateway-url.com"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
### Optional Environment Variables
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
# Specify AI model (optional, defaults to system configured model)
|
|
40
|
-
export
|
|
40
|
+
export WAVE_MODEL="gemini-2.5-flash"
|
|
41
41
|
|
|
42
42
|
# Specify fast AI model (optional, for quick response scenarios)
|
|
43
|
-
export
|
|
43
|
+
export WAVE_FAST_MODEL="gemini-1.5-flash"
|
|
44
44
|
|
|
45
45
|
# Log level (optional, defaults to info)
|
|
46
46
|
export LOG_LEVEL="debug"
|
|
@@ -52,7 +52,7 @@ export LOG_FILE="/path/to/your/logfile.log"
|
|
|
52
52
|
export LOG_MAX_FILE_SIZE="10485760"
|
|
53
53
|
|
|
54
54
|
# Token limit (optional, defaults to 96000)
|
|
55
|
-
export
|
|
55
|
+
export WAVE_MAX_INPUT_TOKENS="96000"
|
|
56
56
|
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BashHistorySelector.d.ts","sourceRoot":"","sources":["../../src/components/BashHistorySelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"BashHistorySelector.d.ts","sourceRoot":"","sources":["../../src/components/BashHistorySelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAKnD,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAqJlE,CAAC"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from "react";
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
|
-
import { searchBashHistory
|
|
4
|
+
import { searchBashHistory } from "wave-agent-sdk";
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
6
|
export const BashHistorySelector = ({ searchQuery, workdir, onSelect, onExecute, onCancel, }) => {
|
|
7
7
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
8
8
|
const [commands, setCommands] = useState([]);
|
|
9
|
-
const [refreshCounter, setRefreshCounter] = useState(0);
|
|
10
9
|
// Search bash history
|
|
11
10
|
useEffect(() => {
|
|
12
11
|
const results = searchBashHistory(searchQuery, 10, workdir);
|
|
@@ -16,9 +15,8 @@ export const BashHistorySelector = ({ searchQuery, workdir, onSelect, onExecute,
|
|
|
16
15
|
searchQuery,
|
|
17
16
|
workdir,
|
|
18
17
|
resultCount: results.length,
|
|
19
|
-
refreshCounter,
|
|
20
18
|
});
|
|
21
|
-
}, [searchQuery, workdir
|
|
19
|
+
}, [searchQuery, workdir]);
|
|
22
20
|
useInput((input, key) => {
|
|
23
21
|
logger.debug("BashHistorySelector useInput:", {
|
|
24
22
|
input,
|
|
@@ -60,18 +58,6 @@ export const BashHistorySelector = ({ searchQuery, workdir, onSelect, onExecute,
|
|
|
60
58
|
setSelectedIndex(Math.min(commands.length - 1, selectedIndex + 1));
|
|
61
59
|
return;
|
|
62
60
|
}
|
|
63
|
-
if (key.delete) {
|
|
64
|
-
if (commands.length > 0 && selectedIndex < commands.length) {
|
|
65
|
-
const selectedCommand = commands[selectedIndex];
|
|
66
|
-
deleteBashCommandFromHistory(selectedCommand.command, selectedCommand.workdir);
|
|
67
|
-
setRefreshCounter((prev) => prev + 1);
|
|
68
|
-
// Adjust selectedIndex if we deleted the last item
|
|
69
|
-
if (selectedIndex >= commands.length - 1 && selectedIndex > 0) {
|
|
70
|
-
setSelectedIndex(selectedIndex - 1);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
61
|
});
|
|
76
62
|
if (commands.length === 0) {
|
|
77
63
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", padding: 1, marginBottom: 1, children: [_jsxs(Text, { color: "yellow", children: ["No bash history found ", searchQuery && `for "${searchQuery}"`] }), searchQuery.trim() && (_jsxs(Text, { color: "green", children: ["Press Enter to execute: ", searchQuery] })), searchQuery.trim() && (_jsxs(Text, { color: "blue", children: ["Press Tab to insert: ", searchQuery] })), _jsx(Text, { dimColor: true, children: "Press Escape to cancel" })] }));
|
|
@@ -93,5 +79,5 @@ export const BashHistorySelector = ({ searchQuery, workdir, onSelect, onExecute,
|
|
|
93
79
|
return diffMinutes > 0 ? `${diffMinutes}m ago` : "just now";
|
|
94
80
|
}
|
|
95
81
|
};
|
|
96
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "blue", padding: 1, gap: 1, marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "blue", bold: true, children: ["Bash History ", searchQuery && `(filtering: "${searchQuery}")`] }) }), commands.map((cmd, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: index === selectedIndex ? "black" : "white", backgroundColor: index === selectedIndex ? "blue" : undefined, children: cmd.command }), index === selectedIndex && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: _jsxs(Text, { color: "gray", dimColor: true, children: [formatTimestamp(cmd.timestamp), cmd.workdir !== workdir && ` • in ${cmd.workdir}`] }) }))] }, index))), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate, Enter to execute, Tab to insert,
|
|
82
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "blue", padding: 1, gap: 1, marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "blue", bold: true, children: ["Bash History ", searchQuery && `(filtering: "${searchQuery}")`] }) }), commands.map((cmd, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: index === selectedIndex ? "black" : "white", backgroundColor: index === selectedIndex ? "blue" : undefined, children: cmd.command }), index === selectedIndex && (_jsx(Box, { marginLeft: 4, flexDirection: "column", children: _jsxs(Text, { color: "gray", dimColor: true, children: [formatTimestamp(cmd.timestamp), cmd.workdir !== workdir && ` • in ${cmd.workdir}`] }) }))] }, index))), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Use \u2191\u2193 to navigate, Enter to execute, Tab to insert, Escape to cancel" }) })] }));
|
|
97
83
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatInterface.d.ts","sourceRoot":"","sources":["../../src/components/ChatInterface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"ChatInterface.d.ts","sourceRoot":"","sources":["../../src/components/ChatInterface.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAkEjC,CAAC"}
|
|
@@ -9,5 +9,5 @@ export const ChatInterface = () => {
|
|
|
9
9
|
if (!sessionId)
|
|
10
10
|
return null;
|
|
11
11
|
return (_jsxs(Box, { flexDirection: "column", height: "100%", paddingY: 1, children: [_jsx(MessageList, { messages: messages, isLoading: isLoading, isCommandRunning: isCommandRunning, isCompressing: isCompressing, latestTotalTokens: latestTotalTokens, isExpanded: isExpanded }, String(isExpanded) + sessionId), !isExpanded &&
|
|
12
|
-
(isConfirmationVisible ? (_jsx(Confirmation, { toolName: confirmingTool.name, toolInput: confirmingTool.input, onDecision: handleConfirmationDecision, onCancel: handleConfirmationCancel, onAbort: abortMessage })) : (_jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, userInputHistory: userInputHistory, sendMessage: sendMessage, abortMessage: abortMessage, saveMemory: saveMemory, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand })))] }));
|
|
12
|
+
(isConfirmationVisible ? (_jsx(Confirmation, { toolName: confirmingTool.name, toolInput: confirmingTool.input, suggestedPrefix: confirmingTool.suggestedPrefix, hidePersistentOption: confirmingTool.hidePersistentOption, onDecision: handleConfirmationDecision, onCancel: handleConfirmationCancel, onAbort: abortMessage })) : (_jsx(InputBox, { isLoading: isLoading, isCommandRunning: isCommandRunning, userInputHistory: userInputHistory, sendMessage: sendMessage, abortMessage: abortMessage, saveMemory: saveMemory, mcpServers: mcpServers, connectMcpServer: connectMcpServer, disconnectMcpServer: disconnectMcpServer, slashCommands: slashCommands, hasSlashCommand: hasSlashCommand })))] }));
|
|
13
13
|
};
|
|
@@ -3,6 +3,8 @@ import type { PermissionDecision } from "wave-agent-sdk";
|
|
|
3
3
|
export interface ConfirmationProps {
|
|
4
4
|
toolName: string;
|
|
5
5
|
toolInput?: Record<string, unknown>;
|
|
6
|
+
suggestedPrefix?: string;
|
|
7
|
+
hidePersistentOption?: boolean;
|
|
6
8
|
onDecision: (decision: PermissionDecision) => void;
|
|
7
9
|
onCancel: () => void;
|
|
8
10
|
onAbort: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Confirmation.d.ts","sourceRoot":"","sources":["../../src/components/Confirmation.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AA2BzD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAQD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"Confirmation.d.ts","sourceRoot":"","sources":["../../src/components/Confirmation.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AA2BzD,MAAM,WAAW,iBAAiB;IAChC,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,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAQD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgPpD,CAAC"}
|
|
@@ -21,7 +21,7 @@ const getActionDescription = (toolName, toolInput) => {
|
|
|
21
21
|
return "Execute operation";
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
|
-
export const Confirmation = ({ toolName, toolInput, onDecision, onCancel, onAbort, }) => {
|
|
24
|
+
export const Confirmation = ({ toolName, toolInput, suggestedPrefix, hidePersistentOption, onDecision, onCancel, onAbort, }) => {
|
|
25
25
|
const [state, setState] = useState({
|
|
26
26
|
selectedOption: "allow",
|
|
27
27
|
alternativeText: "",
|
|
@@ -29,7 +29,10 @@ export const Confirmation = ({ toolName, toolInput, onDecision, onCancel, onAbor
|
|
|
29
29
|
});
|
|
30
30
|
const getAutoOptionText = () => {
|
|
31
31
|
if (toolName === "Bash") {
|
|
32
|
-
|
|
32
|
+
if (suggestedPrefix) {
|
|
33
|
+
return `Yes, and don't ask again for: ${suggestedPrefix}`;
|
|
34
|
+
}
|
|
35
|
+
return "Yes, and don't ask again for this command in this workdir";
|
|
33
36
|
}
|
|
34
37
|
return "Yes, and auto-accept edits";
|
|
35
38
|
};
|
|
@@ -47,9 +50,12 @@ export const Confirmation = ({ toolName, toolInput, onDecision, onCancel, onAbor
|
|
|
47
50
|
}
|
|
48
51
|
else if (state.selectedOption === "auto") {
|
|
49
52
|
if (toolName === "Bash") {
|
|
53
|
+
const rule = suggestedPrefix
|
|
54
|
+
? `Bash(${suggestedPrefix}:*)`
|
|
55
|
+
: `Bash(${toolInput?.command})`;
|
|
50
56
|
onDecision({
|
|
51
57
|
behavior: "allow",
|
|
52
|
-
newPermissionRule:
|
|
58
|
+
newPermissionRule: rule,
|
|
53
59
|
});
|
|
54
60
|
}
|
|
55
61
|
else {
|
|
@@ -77,21 +83,31 @@ export const Confirmation = ({ toolName, toolInput, onDecision, onCancel, onAbor
|
|
|
77
83
|
return;
|
|
78
84
|
}
|
|
79
85
|
if (input === "2") {
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
if (!hidePersistentOption) {
|
|
87
|
+
if (toolName === "Bash") {
|
|
88
|
+
const rule = suggestedPrefix
|
|
89
|
+
? `Bash(${suggestedPrefix}:*)`
|
|
90
|
+
: `Bash(${toolInput?.command})`;
|
|
91
|
+
onDecision({
|
|
92
|
+
behavior: "allow",
|
|
93
|
+
newPermissionRule: rule,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
onDecision({
|
|
98
|
+
behavior: "allow",
|
|
99
|
+
newPermissionMode: "acceptEdits",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
85
103
|
}
|
|
86
104
|
else {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
105
|
+
// If auto option is hidden, '2' selects alternative
|
|
106
|
+
setState((prev) => ({ ...prev, selectedOption: "alternative" }));
|
|
107
|
+
return;
|
|
91
108
|
}
|
|
92
|
-
return;
|
|
93
109
|
}
|
|
94
|
-
if (input === "3") {
|
|
110
|
+
if (input === "3" && !hidePersistentOption) {
|
|
95
111
|
setState((prev) => ({ ...prev, selectedOption: "alternative" }));
|
|
96
112
|
return;
|
|
97
113
|
}
|
|
@@ -99,8 +115,12 @@ export const Confirmation = ({ toolName, toolInput, onDecision, onCancel, onAbor
|
|
|
99
115
|
// Handle arrow keys for navigation
|
|
100
116
|
if (key.upArrow) {
|
|
101
117
|
setState((prev) => {
|
|
102
|
-
if (prev.selectedOption === "alternative")
|
|
103
|
-
return {
|
|
118
|
+
if (prev.selectedOption === "alternative") {
|
|
119
|
+
return {
|
|
120
|
+
...prev,
|
|
121
|
+
selectedOption: hidePersistentOption ? "allow" : "auto",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
104
124
|
if (prev.selectedOption === "auto")
|
|
105
125
|
return { ...prev, selectedOption: "allow" };
|
|
106
126
|
return prev;
|
|
@@ -109,8 +129,12 @@ export const Confirmation = ({ toolName, toolInput, onDecision, onCancel, onAbor
|
|
|
109
129
|
}
|
|
110
130
|
if (key.downArrow) {
|
|
111
131
|
setState((prev) => {
|
|
112
|
-
if (prev.selectedOption === "allow")
|
|
113
|
-
return {
|
|
132
|
+
if (prev.selectedOption === "allow") {
|
|
133
|
+
return {
|
|
134
|
+
...prev,
|
|
135
|
+
selectedOption: hidePersistentOption ? "alternative" : "auto",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
114
138
|
if (prev.selectedOption === "auto")
|
|
115
139
|
return { ...prev, selectedOption: "alternative" };
|
|
116
140
|
return prev;
|
|
@@ -143,6 +167,6 @@ export const Confirmation = ({ toolName, toolInput, onDecision, onCancel, onAbor
|
|
|
143
167
|
});
|
|
144
168
|
const placeholderText = "Type here to tell Wave what to do differently";
|
|
145
169
|
const showPlaceholder = state.selectedOption === "alternative" && !state.hasUserInput;
|
|
146
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", padding: 1, marginBottom: 1, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tool: ", toolName] }), _jsx(Text, { color: "yellow", children: getActionDescription(toolName, toolInput) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Do you want to proceed?" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_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" ? "> " : " ", "1. Yes"] }) }, "allow-option"), _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" ? "> " : " ", "2.", " ", 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" ? "> " : " ", "
|
|
147
|
-
"Type here to tell Wave what to do differently" }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children:
|
|
170
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", padding: 1, marginBottom: 1, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Tool: ", toolName] }), _jsx(Text, { color: "yellow", children: getActionDescription(toolName, toolInput) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Do you want to proceed?" }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_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" ? "> " : " ", "1. Yes"] }) }, "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" ? "> " : " ", "2.", " ", 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" ? "> " : " ", hidePersistentOption ? "2. " : "3. ", showPlaceholder ? (_jsx(Text, { color: "gray", dimColor: true, children: placeholderText })) : (_jsx(Text, { children: state.alternativeText ||
|
|
171
|
+
"Type here to tell Wave what to do differently" }))] }) }, "alternative-option")] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Use \u2191\u2193 or 1-", hidePersistentOption ? "2" : "3", " to navigate \u2022 ESC to cancel"] }) })] }));
|
|
148
172
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../src/components/Markdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../src/components/Markdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAIvC,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoUD,eAAO,MAAM,QAAQ,2CAA6B,aAAa,6CAQ7D,CAAC"}
|
|
@@ -1,22 +1,121 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useMemo } from "react";
|
|
3
|
-
import { Text } from "ink";
|
|
3
|
+
import { Box, Text, useStdout } from "ink";
|
|
4
4
|
import { marked } from "marked";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
const unescapeHtml = (html) => {
|
|
6
|
+
return html
|
|
7
|
+
.replace(/&/g, "&")
|
|
8
|
+
.replace(/</g, "<")
|
|
9
|
+
.replace(/>/g, ">")
|
|
10
|
+
.replace(/"/g, '"')
|
|
11
|
+
.replace(/'/g, "'");
|
|
12
|
+
};
|
|
13
|
+
const InlineRenderer = ({ tokens }) => {
|
|
14
|
+
return (_jsx(_Fragment, { children: tokens.map((token, index) => {
|
|
15
|
+
switch (token.type) {
|
|
16
|
+
case "text": {
|
|
17
|
+
const t = token;
|
|
18
|
+
if (t.tokens) {
|
|
19
|
+
return _jsx(InlineRenderer, { tokens: t.tokens }, index);
|
|
20
|
+
}
|
|
21
|
+
return _jsx(Text, { children: unescapeHtml(t.text) }, index);
|
|
22
|
+
}
|
|
23
|
+
case "strong":
|
|
24
|
+
return (_jsx(Text, { bold: true, children: token.tokens ? (_jsx(InlineRenderer, { tokens: token.tokens })) : (unescapeHtml(token.text)) }, index));
|
|
25
|
+
case "em":
|
|
26
|
+
return (_jsx(Text, { italic: true, children: token.tokens ? (_jsx(InlineRenderer, { tokens: token.tokens })) : (unescapeHtml(token.text)) }, index));
|
|
27
|
+
case "codespan":
|
|
28
|
+
return (_jsx(Text, { color: "yellow", children: unescapeHtml(token.text) }, index));
|
|
29
|
+
case "link":
|
|
30
|
+
return (_jsx(Text, { color: "blue", underline: true, children: token.tokens ? (_jsx(InlineRenderer, { tokens: token.tokens })) : (unescapeHtml(token.text)) }, index));
|
|
31
|
+
case "br":
|
|
32
|
+
return _jsx(Text, { children: "\n" }, index);
|
|
33
|
+
case "del":
|
|
34
|
+
return (_jsx(Text, { strikethrough: true, children: token.tokens ? (_jsx(InlineRenderer, { tokens: token.tokens })) : (unescapeHtml(token.text)) }, index));
|
|
35
|
+
default:
|
|
36
|
+
return _jsx(Text, { children: token.raw }, index);
|
|
37
|
+
}
|
|
38
|
+
}) }));
|
|
39
|
+
};
|
|
40
|
+
const TableRenderer = ({ token }) => {
|
|
41
|
+
const { stdout } = useStdout();
|
|
42
|
+
const terminalWidth = (stdout?.columns || 80) - 2;
|
|
43
|
+
const columnWidths = useMemo(() => {
|
|
44
|
+
const numCols = token.header.length;
|
|
45
|
+
const minWidth = 5;
|
|
46
|
+
const maxColWidth = 40;
|
|
47
|
+
const widths = token.header.map((h) => Math.min(maxColWidth, Math.max(minWidth, h.text.length)));
|
|
48
|
+
token.rows.forEach((row) => {
|
|
49
|
+
row.forEach((cell, i) => {
|
|
50
|
+
widths[i] = Math.min(maxColWidth, Math.max(widths[i] || minWidth, cell.text.length));
|
|
51
|
+
});
|
|
15
52
|
});
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
53
|
+
const paddedWidths = widths.map((w) => w + 2);
|
|
54
|
+
const totalWidth = paddedWidths.reduce((a, b) => a + b, 0) + numCols + 1;
|
|
55
|
+
if (totalWidth <= terminalWidth) {
|
|
56
|
+
return paddedWidths;
|
|
57
|
+
}
|
|
58
|
+
// If table is too wide, scale down columns proportionally
|
|
59
|
+
const availableWidth = terminalWidth - numCols - 1;
|
|
60
|
+
const scaleFactor = availableWidth / (totalWidth - numCols - 1);
|
|
61
|
+
return paddedWidths.map((w) => Math.max(minWidth, Math.floor(w * scaleFactor)));
|
|
62
|
+
}, [token, terminalWidth]);
|
|
63
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: "gray", width: columnWidths.reduce((a, b) => a + b, 0) + token.header.length + 1, children: [_jsx(Box, { flexDirection: "row", borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, borderColor: "gray", children: token.header.map((cell, i) => (_jsx(Box, { width: columnWidths[i], paddingX: 1, borderStyle: "single", borderLeft: i > 0, borderRight: false, borderTop: false, borderBottom: false, borderColor: "gray", children: _jsx(Text, { bold: true, wrap: "wrap", children: _jsx(InlineRenderer, { tokens: cell.tokens }) }) }, i))) }), token.rows.map((row, rowIndex) => (_jsx(Box, { flexDirection: "row", children: row.map((cell, i) => (_jsx(Box, { width: columnWidths[i], paddingX: 1, borderStyle: "single", borderLeft: i > 0, borderRight: false, borderTop: false, borderBottom: false, borderColor: "gray", children: _jsx(Text, { wrap: "wrap", children: _jsx(InlineRenderer, { tokens: cell.tokens }) }) }, i))) }, rowIndex)))] }));
|
|
64
|
+
};
|
|
65
|
+
const BlockRenderer = ({ tokens }) => {
|
|
66
|
+
return (_jsx(_Fragment, { children: tokens.map((token, index) => {
|
|
67
|
+
switch (token.type) {
|
|
68
|
+
case "heading": {
|
|
69
|
+
const t = token;
|
|
70
|
+
return (_jsx(Box, { marginBottom: 1, flexDirection: "column", children: _jsxs(Text, { bold: true, color: "cyan", children: ["#".repeat(t.depth), " ", _jsx(InlineRenderer, { tokens: t.tokens })] }) }, index));
|
|
71
|
+
}
|
|
72
|
+
case "paragraph": {
|
|
73
|
+
const t = token;
|
|
74
|
+
return (_jsx(Box, { marginBottom: 1, flexDirection: "row", flexWrap: "wrap", children: _jsx(InlineRenderer, { tokens: t.tokens }) }, index));
|
|
75
|
+
}
|
|
76
|
+
case "code": {
|
|
77
|
+
const t = token;
|
|
78
|
+
if (t.lang !== undefined) {
|
|
79
|
+
const lines = token.raw.replace(/\n$/, "").split("\n");
|
|
80
|
+
const opening = lines[0];
|
|
81
|
+
const closing = lines[lines.length - 1];
|
|
82
|
+
const content = lines.slice(1, -1).join("\n");
|
|
83
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [_jsx(Text, { color: "gray", children: opening }), content && _jsx(Text, { children: content }), _jsx(Text, { color: "gray", children: closing })] }, index));
|
|
84
|
+
}
|
|
85
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: _jsx(Text, { children: t.text }) }, index));
|
|
86
|
+
}
|
|
87
|
+
case "list": {
|
|
88
|
+
const t = token;
|
|
89
|
+
return (_jsx(Box, { flexDirection: "column", marginBottom: 1, paddingLeft: 2, children: t.items.map((item, i) => {
|
|
90
|
+
const start = t.start || 1;
|
|
91
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: "gray", children: t.ordered ? `${start + i}. ` : "• " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: item.tokens.map((itemToken, itemIndex) => {
|
|
92
|
+
if (itemToken.type === "text") {
|
|
93
|
+
const it = itemToken;
|
|
94
|
+
return (_jsx(Box, { flexDirection: "row", flexWrap: "wrap", children: _jsx(InlineRenderer, { tokens: it.tokens || [itemToken] }) }, itemIndex));
|
|
95
|
+
}
|
|
96
|
+
return (_jsx(BlockRenderer, { tokens: [itemToken] }, itemIndex));
|
|
97
|
+
}) })] }, i));
|
|
98
|
+
}) }, index));
|
|
99
|
+
}
|
|
100
|
+
case "blockquote": {
|
|
101
|
+
const t = token;
|
|
102
|
+
return (_jsx(Box, { flexDirection: "column", paddingLeft: 2, borderStyle: "single", borderLeft: true, borderRight: false, borderTop: false, borderBottom: false, borderColor: "gray", marginBottom: 1, children: _jsx(BlockRenderer, { tokens: t.tokens }) }, index));
|
|
103
|
+
}
|
|
104
|
+
case "hr":
|
|
105
|
+
return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "gray", children: "─".repeat(20) }) }, index));
|
|
106
|
+
case "table":
|
|
107
|
+
return _jsx(TableRenderer, { token: token }, index);
|
|
108
|
+
case "space":
|
|
109
|
+
return null;
|
|
110
|
+
default:
|
|
111
|
+
return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: token.raw }) }, index));
|
|
112
|
+
}
|
|
113
|
+
}) }));
|
|
114
|
+
};
|
|
115
|
+
// Markdown component using custom Ink-based renderer
|
|
116
|
+
export const Markdown = React.memo(({ children }) => {
|
|
117
|
+
const tokens = useMemo(() => marked.lexer(children), [children]);
|
|
118
|
+
return (_jsx(Box, { flexDirection: "column", children: _jsx(BlockRenderer, { tokens: tokens }) }));
|
|
20
119
|
});
|
|
21
120
|
// Add display name for debugging
|
|
22
121
|
Markdown.displayName = "Markdown";
|
|
@@ -34,8 +34,10 @@ export interface ChatContextType {
|
|
|
34
34
|
confirmingTool?: {
|
|
35
35
|
name: string;
|
|
36
36
|
input?: Record<string, unknown>;
|
|
37
|
+
suggestedPrefix?: string;
|
|
38
|
+
hidePersistentOption?: boolean;
|
|
37
39
|
};
|
|
38
|
-
showConfirmation: (toolName: string, toolInput?: Record<string, unknown
|
|
40
|
+
showConfirmation: (toolName: string, toolInput?: Record<string, unknown>, suggestedPrefix?: string, hidePersistentOption?: boolean) => Promise<PermissionDecision>;
|
|
39
41
|
hideConfirmation: () => void;
|
|
40
42
|
handleConfirmationDecision: (decision: PermissionDecision) => void;
|
|
41
43
|
handleConfirmationCancel: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/contexts/useChat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EACV,OAAO,EACP,eAAe,EACf,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACf,MAAM,gBAAgB,CAAC;AAUxB,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAE3B,UAAU,EAAE,OAAO,CAAC;IAEpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,CACX,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,KAC/C,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAE1B,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzE,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9D,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,wBAAwB,EAAE,CACxB,OAAO,EAAE,MAAM,KACZ;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/D,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAElD,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAEhD,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAE5C,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAElD,qBAAqB,EAAE,OAAO,CAAC;IAC/B,cAAc,CAAC,EAAE;
|
|
1
|
+
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/contexts/useChat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EACV,OAAO,EACP,eAAe,EACf,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACf,MAAM,gBAAgB,CAAC;AAUxB,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAE3B,UAAU,EAAE,OAAO,CAAC;IAEpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,CACX,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,KAC/C,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAE1B,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzE,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9D,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,wBAAwB,EAAE,CACxB,OAAO,EAAE,MAAM,KACZ;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/D,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAElD,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAEhD,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAE5C,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAElD,qBAAqB,EAAE,OAAO,CAAC;IAC/B,cAAc,CAAC,EAAE;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC,CAAC;IACF,gBAAgB,EAAE,CAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,eAAe,CAAC,EAAE,MAAM,EACxB,oBAAoB,CAAC,EAAE,OAAO,KAC3B,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjC,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,0BAA0B,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACnE,wBAAwB,EAAE,MAAM,IAAI,CAAC;CACtC;AAID,eAAO,MAAM,OAAO,uBAMnB,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA0apD,CAAC"}
|
package/dist/contexts/useChat.js
CHANGED
|
@@ -42,11 +42,13 @@ export const ChatProvider = ({ children, bypassPermissions, }) => {
|
|
|
42
42
|
const [currentConfirmation, setCurrentConfirmation] = useState(null);
|
|
43
43
|
const agentRef = useRef(null);
|
|
44
44
|
// Permission confirmation methods with queue support
|
|
45
|
-
const showConfirmation = useCallback(async (toolName, toolInput) => {
|
|
45
|
+
const showConfirmation = useCallback(async (toolName, toolInput, suggestedPrefix, hidePersistentOption) => {
|
|
46
46
|
return new Promise((resolve, reject) => {
|
|
47
47
|
const queueItem = {
|
|
48
48
|
toolName,
|
|
49
49
|
toolInput,
|
|
50
|
+
suggestedPrefix,
|
|
51
|
+
hidePersistentOption,
|
|
50
52
|
resolver: resolve,
|
|
51
53
|
reject,
|
|
52
54
|
};
|
|
@@ -98,7 +100,7 @@ export const ChatProvider = ({ children, bypassPermissions, }) => {
|
|
|
98
100
|
? undefined
|
|
99
101
|
: async (context) => {
|
|
100
102
|
try {
|
|
101
|
-
return await showConfirmation(context.toolName, context.toolInput);
|
|
103
|
+
return await showConfirmation(context.toolName, context.toolInput, context.suggestedPrefix, context.hidePersistentOption);
|
|
102
104
|
}
|
|
103
105
|
catch {
|
|
104
106
|
// If confirmation was cancelled or failed, deny the operation
|
|
@@ -261,7 +263,12 @@ export const ChatProvider = ({ children, bypassPermissions, }) => {
|
|
|
261
263
|
if (confirmationQueue.length > 0 && !isConfirmationVisible) {
|
|
262
264
|
const next = confirmationQueue[0];
|
|
263
265
|
setCurrentConfirmation(next);
|
|
264
|
-
setConfirmingTool({
|
|
266
|
+
setConfirmingTool({
|
|
267
|
+
name: next.toolName,
|
|
268
|
+
input: next.toolInput,
|
|
269
|
+
suggestedPrefix: next.suggestedPrefix,
|
|
270
|
+
hidePersistentOption: next.hidePersistentOption,
|
|
271
|
+
});
|
|
265
272
|
setIsConfirmationVisible(true);
|
|
266
273
|
setConfirmationQueue((prev) => prev.slice(1));
|
|
267
274
|
}
|
|
@@ -680,7 +680,7 @@ export class InputManager {
|
|
|
680
680
|
}
|
|
681
681
|
// Handle selector input (when any selector is active)
|
|
682
682
|
handleSelectorInput(input, key) {
|
|
683
|
-
if (key.backspace ||
|
|
683
|
+
if (key.backspace || key.delete) {
|
|
684
684
|
if (this.cursorPosition > 0) {
|
|
685
685
|
this.deleteCharAtCursor((newInput, newCursorPosition) => {
|
|
686
686
|
// Check for special character deletion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -29,13 +29,11 @@
|
|
|
29
29
|
"glob": "^13.0.0",
|
|
30
30
|
"ink": "^6.5.1",
|
|
31
31
|
"marked": "^11.2.0",
|
|
32
|
-
"marked-terminal": "^7.3.0",
|
|
33
32
|
"react": "^19.1.0",
|
|
34
33
|
"yargs": "^17.7.2",
|
|
35
|
-
"wave-agent-sdk": "0.0.
|
|
34
|
+
"wave-agent-sdk": "0.0.12"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
|
-
"@types/marked-terminal": "^6.1.1",
|
|
39
37
|
"@types/react": "^19.1.8",
|
|
40
38
|
"@types/yargs": "^17.0.0",
|
|
41
39
|
"eslint-plugin-react": "^7.37.5",
|
|
@@ -60,6 +58,9 @@
|
|
|
60
58
|
"watch": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
|
|
61
59
|
"test": "vitest run",
|
|
62
60
|
"lint": "eslint --cache",
|
|
63
|
-
"format": "prettier --write ."
|
|
61
|
+
"format": "prettier --write .",
|
|
62
|
+
"version:patch": "node ../../scripts/version.js patch",
|
|
63
|
+
"version:minor": "node ../../scripts/version.js minor",
|
|
64
|
+
"version:major": "node ../../scripts/version.js major"
|
|
64
65
|
}
|
|
65
66
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
-
import {
|
|
4
|
-
searchBashHistory,
|
|
5
|
-
deleteBashCommandFromHistory,
|
|
6
|
-
type BashHistoryEntry,
|
|
7
|
-
} from "wave-agent-sdk";
|
|
3
|
+
import { searchBashHistory, type BashHistoryEntry } from "wave-agent-sdk";
|
|
8
4
|
import { logger } from "../utils/logger.js";
|
|
9
5
|
|
|
10
6
|
export interface BashHistorySelectorProps {
|
|
@@ -24,7 +20,6 @@ export const BashHistorySelector: React.FC<BashHistorySelectorProps> = ({
|
|
|
24
20
|
}) => {
|
|
25
21
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
26
22
|
const [commands, setCommands] = useState<BashHistoryEntry[]>([]);
|
|
27
|
-
const [refreshCounter, setRefreshCounter] = useState(0);
|
|
28
23
|
|
|
29
24
|
// Search bash history
|
|
30
25
|
useEffect(() => {
|
|
@@ -35,9 +30,8 @@ export const BashHistorySelector: React.FC<BashHistorySelectorProps> = ({
|
|
|
35
30
|
searchQuery,
|
|
36
31
|
workdir,
|
|
37
32
|
resultCount: results.length,
|
|
38
|
-
refreshCounter,
|
|
39
33
|
});
|
|
40
|
-
}, [searchQuery, workdir
|
|
34
|
+
}, [searchQuery, workdir]);
|
|
41
35
|
|
|
42
36
|
useInput((input, key) => {
|
|
43
37
|
logger.debug("BashHistorySelector useInput:", {
|
|
@@ -83,22 +77,6 @@ export const BashHistorySelector: React.FC<BashHistorySelectorProps> = ({
|
|
|
83
77
|
setSelectedIndex(Math.min(commands.length - 1, selectedIndex + 1));
|
|
84
78
|
return;
|
|
85
79
|
}
|
|
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
|
-
}
|
|
102
80
|
});
|
|
103
81
|
|
|
104
82
|
if (commands.length === 0) {
|
|
@@ -177,8 +155,7 @@ export const BashHistorySelector: React.FC<BashHistorySelectorProps> = ({
|
|
|
177
155
|
|
|
178
156
|
<Box>
|
|
179
157
|
<Text dimColor>
|
|
180
|
-
Use ↑↓ to navigate, Enter to execute, Tab to insert,
|
|
181
|
-
Escape to cancel
|
|
158
|
+
Use ↑↓ to navigate, Enter to execute, Tab to insert, Escape to cancel
|
|
182
159
|
</Text>
|
|
183
160
|
</Box>
|
|
184
161
|
</Box>
|
|
@@ -48,6 +48,8 @@ export const ChatInterface: React.FC = () => {
|
|
|
48
48
|
<Confirmation
|
|
49
49
|
toolName={confirmingTool!.name}
|
|
50
50
|
toolInput={confirmingTool!.input}
|
|
51
|
+
suggestedPrefix={confirmingTool!.suggestedPrefix}
|
|
52
|
+
hidePersistentOption={confirmingTool!.hidePersistentOption}
|
|
51
53
|
onDecision={handleConfirmationDecision}
|
|
52
54
|
onCancel={handleConfirmationCancel}
|
|
53
55
|
onAbort={abortMessage}
|
|
@@ -30,6 +30,8 @@ const getActionDescription = (
|
|
|
30
30
|
export interface ConfirmationProps {
|
|
31
31
|
toolName: string;
|
|
32
32
|
toolInput?: Record<string, unknown>;
|
|
33
|
+
suggestedPrefix?: string;
|
|
34
|
+
hidePersistentOption?: boolean;
|
|
33
35
|
onDecision: (decision: PermissionDecision) => void;
|
|
34
36
|
onCancel: () => void;
|
|
35
37
|
onAbort: () => void;
|
|
@@ -44,6 +46,8 @@ interface ConfirmationState {
|
|
|
44
46
|
export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
45
47
|
toolName,
|
|
46
48
|
toolInput,
|
|
49
|
+
suggestedPrefix,
|
|
50
|
+
hidePersistentOption,
|
|
47
51
|
onDecision,
|
|
48
52
|
onCancel,
|
|
49
53
|
onAbort,
|
|
@@ -56,7 +60,10 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
|
56
60
|
|
|
57
61
|
const getAutoOptionText = () => {
|
|
58
62
|
if (toolName === "Bash") {
|
|
59
|
-
|
|
63
|
+
if (suggestedPrefix) {
|
|
64
|
+
return `Yes, and don't ask again for: ${suggestedPrefix}`;
|
|
65
|
+
}
|
|
66
|
+
return "Yes, and don't ask again for this command in this workdir";
|
|
60
67
|
}
|
|
61
68
|
return "Yes, and auto-accept edits";
|
|
62
69
|
};
|
|
@@ -75,9 +82,12 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
|
75
82
|
onDecision({ behavior: "allow" });
|
|
76
83
|
} else if (state.selectedOption === "auto") {
|
|
77
84
|
if (toolName === "Bash") {
|
|
85
|
+
const rule = suggestedPrefix
|
|
86
|
+
? `Bash(${suggestedPrefix}:*)`
|
|
87
|
+
: `Bash(${toolInput?.command})`;
|
|
78
88
|
onDecision({
|
|
79
89
|
behavior: "allow",
|
|
80
|
-
newPermissionRule:
|
|
90
|
+
newPermissionRule: rule,
|
|
81
91
|
});
|
|
82
92
|
} else {
|
|
83
93
|
onDecision({
|
|
@@ -104,20 +114,29 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
|
104
114
|
return;
|
|
105
115
|
}
|
|
106
116
|
if (input === "2") {
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
if (!hidePersistentOption) {
|
|
118
|
+
if (toolName === "Bash") {
|
|
119
|
+
const rule = suggestedPrefix
|
|
120
|
+
? `Bash(${suggestedPrefix}:*)`
|
|
121
|
+
: `Bash(${toolInput?.command})`;
|
|
122
|
+
onDecision({
|
|
123
|
+
behavior: "allow",
|
|
124
|
+
newPermissionRule: rule,
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
onDecision({
|
|
128
|
+
behavior: "allow",
|
|
129
|
+
newPermissionMode: "acceptEdits",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
112
133
|
} else {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
134
|
+
// If auto option is hidden, '2' selects alternative
|
|
135
|
+
setState((prev) => ({ ...prev, selectedOption: "alternative" }));
|
|
136
|
+
return;
|
|
117
137
|
}
|
|
118
|
-
return;
|
|
119
138
|
}
|
|
120
|
-
if (input === "3") {
|
|
139
|
+
if (input === "3" && !hidePersistentOption) {
|
|
121
140
|
setState((prev) => ({ ...prev, selectedOption: "alternative" }));
|
|
122
141
|
return;
|
|
123
142
|
}
|
|
@@ -126,8 +145,12 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
|
126
145
|
// Handle arrow keys for navigation
|
|
127
146
|
if (key.upArrow) {
|
|
128
147
|
setState((prev) => {
|
|
129
|
-
if (prev.selectedOption === "alternative")
|
|
130
|
-
return {
|
|
148
|
+
if (prev.selectedOption === "alternative") {
|
|
149
|
+
return {
|
|
150
|
+
...prev,
|
|
151
|
+
selectedOption: hidePersistentOption ? "allow" : "auto",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
131
154
|
if (prev.selectedOption === "auto")
|
|
132
155
|
return { ...prev, selectedOption: "allow" };
|
|
133
156
|
return prev;
|
|
@@ -137,8 +160,12 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
|
137
160
|
|
|
138
161
|
if (key.downArrow) {
|
|
139
162
|
setState((prev) => {
|
|
140
|
-
if (prev.selectedOption === "allow")
|
|
141
|
-
return {
|
|
163
|
+
if (prev.selectedOption === "allow") {
|
|
164
|
+
return {
|
|
165
|
+
...prev,
|
|
166
|
+
selectedOption: hidePersistentOption ? "alternative" : "auto",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
142
169
|
if (prev.selectedOption === "auto")
|
|
143
170
|
return { ...prev, selectedOption: "alternative" };
|
|
144
171
|
return prev;
|
|
@@ -208,18 +235,20 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
|
208
235
|
</Box>
|
|
209
236
|
|
|
210
237
|
{/* Option 2: Auto-accept/Persistent */}
|
|
211
|
-
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
238
|
+
{!hidePersistentOption && (
|
|
239
|
+
<Box key="auto-option">
|
|
240
|
+
<Text
|
|
241
|
+
color={state.selectedOption === "auto" ? "black" : "white"}
|
|
242
|
+
backgroundColor={
|
|
243
|
+
state.selectedOption === "auto" ? "yellow" : undefined
|
|
244
|
+
}
|
|
245
|
+
bold={state.selectedOption === "auto"}
|
|
246
|
+
>
|
|
247
|
+
{state.selectedOption === "auto" ? "> " : " "}2.{" "}
|
|
248
|
+
{getAutoOptionText()}
|
|
249
|
+
</Text>
|
|
250
|
+
</Box>
|
|
251
|
+
)}
|
|
223
252
|
|
|
224
253
|
{/* Option 3: Alternative */}
|
|
225
254
|
<Box key="alternative-option">
|
|
@@ -230,7 +259,8 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
|
230
259
|
}
|
|
231
260
|
bold={state.selectedOption === "alternative"}
|
|
232
261
|
>
|
|
233
|
-
{state.selectedOption === "alternative" ? "> " : " "}
|
|
262
|
+
{state.selectedOption === "alternative" ? "> " : " "}
|
|
263
|
+
{hidePersistentOption ? "2. " : "3. "}
|
|
234
264
|
{showPlaceholder ? (
|
|
235
265
|
<Text color="gray" dimColor>
|
|
236
266
|
{placeholderText}
|
|
@@ -246,7 +276,10 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
|
|
|
246
276
|
</Box>
|
|
247
277
|
|
|
248
278
|
<Box marginTop={1}>
|
|
249
|
-
<Text dimColor>
|
|
279
|
+
<Text dimColor>
|
|
280
|
+
Use ↑↓ or 1-{hidePersistentOption ? "2" : "3"} to navigate • ESC to
|
|
281
|
+
cancel
|
|
282
|
+
</Text>
|
|
250
283
|
</Box>
|
|
251
284
|
</Box>
|
|
252
285
|
);
|
|
@@ -1,28 +1,341 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
|
-
import { Text } from "ink";
|
|
3
|
-
import { marked } from "marked";
|
|
4
|
-
import TerminalRenderer from "marked-terminal";
|
|
2
|
+
import { Box, Text, useStdout } from "ink";
|
|
3
|
+
import { marked, type Token, type Tokens } from "marked";
|
|
5
4
|
|
|
6
5
|
export interface MarkdownProps {
|
|
7
6
|
children: string;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
const unescapeHtml = (html: string) => {
|
|
10
|
+
return html
|
|
11
|
+
.replace(/&/g, "&")
|
|
12
|
+
.replace(/</g, "<")
|
|
13
|
+
.replace(/>/g, ">")
|
|
14
|
+
.replace(/"/g, '"')
|
|
15
|
+
.replace(/'/g, "'");
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const InlineRenderer = ({ tokens }: { tokens: Token[] }) => {
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
{tokens.map((token, index) => {
|
|
22
|
+
switch (token.type) {
|
|
23
|
+
case "text": {
|
|
24
|
+
const t = token as Tokens.Text;
|
|
25
|
+
if (t.tokens) {
|
|
26
|
+
return <InlineRenderer key={index} tokens={t.tokens} />;
|
|
27
|
+
}
|
|
28
|
+
return <Text key={index}>{unescapeHtml(t.text)}</Text>;
|
|
29
|
+
}
|
|
30
|
+
case "strong":
|
|
31
|
+
return (
|
|
32
|
+
<Text key={index} bold>
|
|
33
|
+
{token.tokens ? (
|
|
34
|
+
<InlineRenderer tokens={token.tokens} />
|
|
35
|
+
) : (
|
|
36
|
+
unescapeHtml((token as Tokens.Strong).text)
|
|
37
|
+
)}
|
|
38
|
+
</Text>
|
|
39
|
+
);
|
|
40
|
+
case "em":
|
|
41
|
+
return (
|
|
42
|
+
<Text key={index} italic>
|
|
43
|
+
{token.tokens ? (
|
|
44
|
+
<InlineRenderer tokens={token.tokens} />
|
|
45
|
+
) : (
|
|
46
|
+
unescapeHtml((token as Tokens.Em).text)
|
|
47
|
+
)}
|
|
48
|
+
</Text>
|
|
49
|
+
);
|
|
50
|
+
case "codespan":
|
|
51
|
+
return (
|
|
52
|
+
<Text key={index} color="yellow">
|
|
53
|
+
{unescapeHtml((token as Tokens.Codespan).text)}
|
|
54
|
+
</Text>
|
|
55
|
+
);
|
|
56
|
+
case "link":
|
|
57
|
+
return (
|
|
58
|
+
<Text key={index} color="blue" underline>
|
|
59
|
+
{token.tokens ? (
|
|
60
|
+
<InlineRenderer tokens={token.tokens} />
|
|
61
|
+
) : (
|
|
62
|
+
unescapeHtml((token as Tokens.Link).text)
|
|
63
|
+
)}
|
|
64
|
+
</Text>
|
|
65
|
+
);
|
|
66
|
+
case "br":
|
|
67
|
+
return <Text key={index}>{"\n"}</Text>;
|
|
68
|
+
case "del":
|
|
69
|
+
return (
|
|
70
|
+
<Text key={index} strikethrough>
|
|
71
|
+
{token.tokens ? (
|
|
72
|
+
<InlineRenderer tokens={token.tokens} />
|
|
73
|
+
) : (
|
|
74
|
+
unescapeHtml((token as Tokens.Del).text)
|
|
75
|
+
)}
|
|
76
|
+
</Text>
|
|
77
|
+
);
|
|
78
|
+
default:
|
|
79
|
+
return <Text key={index}>{token.raw}</Text>;
|
|
80
|
+
}
|
|
81
|
+
})}
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const TableRenderer = ({ token }: { token: Tokens.Table }) => {
|
|
87
|
+
const { stdout } = useStdout();
|
|
88
|
+
const terminalWidth = (stdout?.columns || 80) - 2;
|
|
89
|
+
|
|
90
|
+
const columnWidths = useMemo(() => {
|
|
91
|
+
const numCols = token.header.length;
|
|
92
|
+
const minWidth = 5;
|
|
93
|
+
const maxColWidth = 40;
|
|
94
|
+
const widths = token.header.map((h) =>
|
|
95
|
+
Math.min(maxColWidth, Math.max(minWidth, h.text.length)),
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
token.rows.forEach((row) => {
|
|
99
|
+
row.forEach((cell, i) => {
|
|
100
|
+
widths[i] = Math.min(
|
|
101
|
+
maxColWidth,
|
|
102
|
+
Math.max(widths[i] || minWidth, cell.text.length),
|
|
103
|
+
);
|
|
104
|
+
});
|
|
19
105
|
});
|
|
20
106
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
107
|
+
const paddedWidths = widths.map((w) => w + 2);
|
|
108
|
+
const totalWidth = paddedWidths.reduce((a, b) => a + b, 0) + numCols + 1;
|
|
109
|
+
|
|
110
|
+
if (totalWidth <= terminalWidth) {
|
|
111
|
+
return paddedWidths;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// If table is too wide, scale down columns proportionally
|
|
115
|
+
const availableWidth = terminalWidth - numCols - 1;
|
|
116
|
+
const scaleFactor = availableWidth / (totalWidth - numCols - 1);
|
|
117
|
+
return paddedWidths.map((w) =>
|
|
118
|
+
Math.max(minWidth, Math.floor(w * scaleFactor)),
|
|
119
|
+
);
|
|
120
|
+
}, [token, terminalWidth]);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Box
|
|
124
|
+
flexDirection="column"
|
|
125
|
+
marginBottom={1}
|
|
126
|
+
borderStyle="single"
|
|
127
|
+
borderColor="gray"
|
|
128
|
+
width={columnWidths.reduce((a, b) => a + b, 0) + token.header.length + 1}
|
|
129
|
+
>
|
|
130
|
+
{/* Header */}
|
|
131
|
+
<Box
|
|
132
|
+
flexDirection="row"
|
|
133
|
+
borderStyle="single"
|
|
134
|
+
borderBottom
|
|
135
|
+
borderTop={false}
|
|
136
|
+
borderLeft={false}
|
|
137
|
+
borderRight={false}
|
|
138
|
+
borderColor="gray"
|
|
139
|
+
>
|
|
140
|
+
{token.header.map((cell, i) => (
|
|
141
|
+
<Box
|
|
142
|
+
key={i}
|
|
143
|
+
width={columnWidths[i]}
|
|
144
|
+
paddingX={1}
|
|
145
|
+
borderStyle="single"
|
|
146
|
+
borderLeft={i > 0}
|
|
147
|
+
borderRight={false}
|
|
148
|
+
borderTop={false}
|
|
149
|
+
borderBottom={false}
|
|
150
|
+
borderColor="gray"
|
|
151
|
+
>
|
|
152
|
+
<Text bold wrap="wrap">
|
|
153
|
+
<InlineRenderer tokens={cell.tokens} />
|
|
154
|
+
</Text>
|
|
155
|
+
</Box>
|
|
156
|
+
))}
|
|
157
|
+
</Box>
|
|
158
|
+
{/* Rows */}
|
|
159
|
+
{token.rows.map((row, rowIndex) => (
|
|
160
|
+
<Box key={rowIndex} flexDirection="row">
|
|
161
|
+
{row.map((cell, i) => (
|
|
162
|
+
<Box
|
|
163
|
+
key={i}
|
|
164
|
+
width={columnWidths[i]}
|
|
165
|
+
paddingX={1}
|
|
166
|
+
borderStyle="single"
|
|
167
|
+
borderLeft={i > 0}
|
|
168
|
+
borderRight={false}
|
|
169
|
+
borderTop={false}
|
|
170
|
+
borderBottom={false}
|
|
171
|
+
borderColor="gray"
|
|
172
|
+
>
|
|
173
|
+
<Text wrap="wrap">
|
|
174
|
+
<InlineRenderer tokens={cell.tokens} />
|
|
175
|
+
</Text>
|
|
176
|
+
</Box>
|
|
177
|
+
))}
|
|
178
|
+
</Box>
|
|
179
|
+
))}
|
|
180
|
+
</Box>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const BlockRenderer = ({ tokens }: { tokens: Token[] }) => {
|
|
185
|
+
return (
|
|
186
|
+
<>
|
|
187
|
+
{tokens.map((token, index) => {
|
|
188
|
+
switch (token.type) {
|
|
189
|
+
case "heading": {
|
|
190
|
+
const t = token as Tokens.Heading;
|
|
191
|
+
return (
|
|
192
|
+
<Box key={index} marginBottom={1} flexDirection="column">
|
|
193
|
+
<Text bold color="cyan">
|
|
194
|
+
{"#".repeat(t.depth)} <InlineRenderer tokens={t.tokens} />
|
|
195
|
+
</Text>
|
|
196
|
+
</Box>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
case "paragraph": {
|
|
200
|
+
const t = token as Tokens.Paragraph;
|
|
201
|
+
return (
|
|
202
|
+
<Box
|
|
203
|
+
key={index}
|
|
204
|
+
marginBottom={1}
|
|
205
|
+
flexDirection="row"
|
|
206
|
+
flexWrap="wrap"
|
|
207
|
+
>
|
|
208
|
+
<InlineRenderer tokens={t.tokens} />
|
|
209
|
+
</Box>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
case "code": {
|
|
213
|
+
const t = token as Tokens.Code;
|
|
214
|
+
if (t.lang !== undefined) {
|
|
215
|
+
const lines = token.raw.replace(/\n$/, "").split("\n");
|
|
216
|
+
const opening = lines[0];
|
|
217
|
+
const closing = lines[lines.length - 1];
|
|
218
|
+
const content = lines.slice(1, -1).join("\n");
|
|
219
|
+
return (
|
|
220
|
+
<Box
|
|
221
|
+
key={index}
|
|
222
|
+
flexDirection="column"
|
|
223
|
+
paddingX={1}
|
|
224
|
+
marginBottom={1}
|
|
225
|
+
>
|
|
226
|
+
<Text color="gray">{opening}</Text>
|
|
227
|
+
{content && <Text>{content}</Text>}
|
|
228
|
+
<Text color="gray">{closing}</Text>
|
|
229
|
+
</Box>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return (
|
|
233
|
+
<Box
|
|
234
|
+
key={index}
|
|
235
|
+
flexDirection="column"
|
|
236
|
+
paddingX={1}
|
|
237
|
+
marginBottom={1}
|
|
238
|
+
>
|
|
239
|
+
<Text>{t.text}</Text>
|
|
240
|
+
</Box>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
case "list": {
|
|
244
|
+
const t = token as Tokens.List;
|
|
245
|
+
return (
|
|
246
|
+
<Box
|
|
247
|
+
key={index}
|
|
248
|
+
flexDirection="column"
|
|
249
|
+
marginBottom={1}
|
|
250
|
+
paddingLeft={2}
|
|
251
|
+
>
|
|
252
|
+
{t.items.map((item, i) => {
|
|
253
|
+
const start = t.start || 1;
|
|
254
|
+
return (
|
|
255
|
+
<Box key={i} flexDirection="row">
|
|
256
|
+
<Text color="gray">
|
|
257
|
+
{t.ordered ? `${start + i}. ` : "• "}
|
|
258
|
+
</Text>
|
|
259
|
+
<Box flexDirection="column" flexGrow={1}>
|
|
260
|
+
{item.tokens.map((itemToken, itemIndex) => {
|
|
261
|
+
if (itemToken.type === "text") {
|
|
262
|
+
const it = itemToken as Tokens.Text;
|
|
263
|
+
return (
|
|
264
|
+
<Box
|
|
265
|
+
key={itemIndex}
|
|
266
|
+
flexDirection="row"
|
|
267
|
+
flexWrap="wrap"
|
|
268
|
+
>
|
|
269
|
+
<InlineRenderer
|
|
270
|
+
tokens={it.tokens || [itemToken]}
|
|
271
|
+
/>
|
|
272
|
+
</Box>
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return (
|
|
276
|
+
<BlockRenderer
|
|
277
|
+
key={itemIndex}
|
|
278
|
+
tokens={[itemToken]}
|
|
279
|
+
/>
|
|
280
|
+
);
|
|
281
|
+
})}
|
|
282
|
+
</Box>
|
|
283
|
+
</Box>
|
|
284
|
+
);
|
|
285
|
+
})}
|
|
286
|
+
</Box>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
case "blockquote": {
|
|
290
|
+
const t = token as Tokens.Blockquote;
|
|
291
|
+
return (
|
|
292
|
+
<Box
|
|
293
|
+
key={index}
|
|
294
|
+
flexDirection="column"
|
|
295
|
+
paddingLeft={2}
|
|
296
|
+
borderStyle="single"
|
|
297
|
+
borderLeft
|
|
298
|
+
borderRight={false}
|
|
299
|
+
borderTop={false}
|
|
300
|
+
borderBottom={false}
|
|
301
|
+
borderColor="gray"
|
|
302
|
+
marginBottom={1}
|
|
303
|
+
>
|
|
304
|
+
<BlockRenderer tokens={t.tokens} />
|
|
305
|
+
</Box>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
case "hr":
|
|
309
|
+
return (
|
|
310
|
+
<Box key={index} marginBottom={1}>
|
|
311
|
+
<Text color="gray">{"─".repeat(20)}</Text>
|
|
312
|
+
</Box>
|
|
313
|
+
);
|
|
314
|
+
case "table":
|
|
315
|
+
return <TableRenderer key={index} token={token as Tokens.Table} />;
|
|
316
|
+
case "space":
|
|
317
|
+
return null;
|
|
318
|
+
default:
|
|
319
|
+
return (
|
|
320
|
+
<Box key={index} marginBottom={1}>
|
|
321
|
+
<Text>{token.raw}</Text>
|
|
322
|
+
</Box>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
})}
|
|
326
|
+
</>
|
|
327
|
+
);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Markdown component using custom Ink-based renderer
|
|
331
|
+
export const Markdown = React.memo(({ children }: MarkdownProps) => {
|
|
332
|
+
const tokens = useMemo(() => marked.lexer(children), [children]);
|
|
24
333
|
|
|
25
|
-
return
|
|
334
|
+
return (
|
|
335
|
+
<Box flexDirection="column">
|
|
336
|
+
<BlockRenderer tokens={tokens} />
|
|
337
|
+
</Box>
|
|
338
|
+
);
|
|
26
339
|
});
|
|
27
340
|
|
|
28
341
|
// Add display name for debugging
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -63,10 +63,17 @@ export interface ChatContextType {
|
|
|
63
63
|
setPermissionMode: (mode: PermissionMode) => void;
|
|
64
64
|
// Permission confirmation state
|
|
65
65
|
isConfirmationVisible: boolean;
|
|
66
|
-
confirmingTool?: {
|
|
66
|
+
confirmingTool?: {
|
|
67
|
+
name: string;
|
|
68
|
+
input?: Record<string, unknown>;
|
|
69
|
+
suggestedPrefix?: string;
|
|
70
|
+
hidePersistentOption?: boolean;
|
|
71
|
+
};
|
|
67
72
|
showConfirmation: (
|
|
68
73
|
toolName: string,
|
|
69
74
|
toolInput?: Record<string, unknown>,
|
|
75
|
+
suggestedPrefix?: string,
|
|
76
|
+
hidePersistentOption?: boolean,
|
|
70
77
|
) => Promise<PermissionDecision>;
|
|
71
78
|
hideConfirmation: () => void;
|
|
72
79
|
handleConfirmationDecision: (decision: PermissionDecision) => void;
|
|
@@ -129,12 +136,20 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
129
136
|
// Confirmation state with queue-based architecture
|
|
130
137
|
const [isConfirmationVisible, setIsConfirmationVisible] = useState(false);
|
|
131
138
|
const [confirmingTool, setConfirmingTool] = useState<
|
|
132
|
-
|
|
139
|
+
| {
|
|
140
|
+
name: string;
|
|
141
|
+
input?: Record<string, unknown>;
|
|
142
|
+
suggestedPrefix?: string;
|
|
143
|
+
hidePersistentOption?: boolean;
|
|
144
|
+
}
|
|
145
|
+
| undefined
|
|
133
146
|
>();
|
|
134
147
|
const [confirmationQueue, setConfirmationQueue] = useState<
|
|
135
148
|
Array<{
|
|
136
149
|
toolName: string;
|
|
137
150
|
toolInput?: Record<string, unknown>;
|
|
151
|
+
suggestedPrefix?: string;
|
|
152
|
+
hidePersistentOption?: boolean;
|
|
138
153
|
resolver: (decision: PermissionDecision) => void;
|
|
139
154
|
reject: () => void;
|
|
140
155
|
}>
|
|
@@ -142,6 +157,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
142
157
|
const [currentConfirmation, setCurrentConfirmation] = useState<{
|
|
143
158
|
toolName: string;
|
|
144
159
|
toolInput?: Record<string, unknown>;
|
|
160
|
+
suggestedPrefix?: string;
|
|
161
|
+
hidePersistentOption?: boolean;
|
|
145
162
|
resolver: (decision: PermissionDecision) => void;
|
|
146
163
|
reject: () => void;
|
|
147
164
|
} | null>(null);
|
|
@@ -153,11 +170,15 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
153
170
|
async (
|
|
154
171
|
toolName: string,
|
|
155
172
|
toolInput?: Record<string, unknown>,
|
|
173
|
+
suggestedPrefix?: string,
|
|
174
|
+
hidePersistentOption?: boolean,
|
|
156
175
|
): Promise<PermissionDecision> => {
|
|
157
176
|
return new Promise<PermissionDecision>((resolve, reject) => {
|
|
158
177
|
const queueItem = {
|
|
159
178
|
toolName,
|
|
160
179
|
toolInput,
|
|
180
|
+
suggestedPrefix,
|
|
181
|
+
hidePersistentOption,
|
|
161
182
|
resolver: resolve,
|
|
162
183
|
reject,
|
|
163
184
|
};
|
|
@@ -219,6 +240,8 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
219
240
|
return await showConfirmation(
|
|
220
241
|
context.toolName,
|
|
221
242
|
context.toolInput,
|
|
243
|
+
context.suggestedPrefix,
|
|
244
|
+
context.hidePersistentOption,
|
|
222
245
|
);
|
|
223
246
|
} catch {
|
|
224
247
|
// If confirmation was cancelled or failed, deny the operation
|
|
@@ -407,7 +430,12 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
407
430
|
if (confirmationQueue.length > 0 && !isConfirmationVisible) {
|
|
408
431
|
const next = confirmationQueue[0];
|
|
409
432
|
setCurrentConfirmation(next);
|
|
410
|
-
setConfirmingTool({
|
|
433
|
+
setConfirmingTool({
|
|
434
|
+
name: next.toolName,
|
|
435
|
+
input: next.toolInput,
|
|
436
|
+
suggestedPrefix: next.suggestedPrefix,
|
|
437
|
+
hidePersistentOption: next.hidePersistentOption,
|
|
438
|
+
});
|
|
411
439
|
setIsConfirmationVisible(true);
|
|
412
440
|
setConfirmationQueue((prev) => prev.slice(1));
|
|
413
441
|
}
|
|
@@ -944,7 +944,7 @@ export class InputManager {
|
|
|
944
944
|
|
|
945
945
|
// Handle selector input (when any selector is active)
|
|
946
946
|
handleSelectorInput(input: string, key: Key): boolean {
|
|
947
|
-
if (key.backspace ||
|
|
947
|
+
if (key.backspace || key.delete) {
|
|
948
948
|
if (this.cursorPosition > 0) {
|
|
949
949
|
this.deleteCharAtCursor((newInput, newCursorPosition) => {
|
|
950
950
|
// Check for special character deletion
|