wave-code 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ChatInterface.js +1 -1
- package/dist/components/CommandSelector.d.ts.map +1 -1
- package/dist/components/CommandSelector.js +1 -38
- package/dist/components/ConfirmationSelector.d.ts.map +1 -1
- package/dist/components/ConfirmationSelector.js +11 -3
- package/dist/components/HelpView.d.ts +2 -0
- package/dist/components/HelpView.d.ts.map +1 -1
- package/dist/components/HelpView.js +49 -5
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +1 -1
- package/dist/components/MessageList.d.ts +2 -2
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +5 -6
- package/dist/constants/commands.d.ts +3 -0
- package/dist/constants/commands.d.ts.map +1 -0
- package/dist/constants/commands.js +38 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +2 -17
- package/dist/hooks/useInputManager.d.ts +7 -8
- package/dist/hooks/useInputManager.d.ts.map +1 -1
- package/dist/hooks/useInputManager.js +224 -232
- package/dist/managers/inputHandlers.d.ts +28 -0
- package/dist/managers/inputHandlers.d.ts.map +1 -0
- package/dist/managers/inputHandlers.js +378 -0
- package/dist/managers/inputReducer.d.ts +157 -0
- package/dist/managers/inputReducer.d.ts.map +1 -0
- package/dist/managers/inputReducer.js +242 -0
- package/dist/utils/highlightUtils.d.ts.map +1 -1
- package/dist/utils/highlightUtils.js +66 -42
- package/package.json +2 -2
- package/src/components/ChatInterface.tsx +1 -1
- package/src/components/CommandSelector.tsx +1 -40
- package/src/components/ConfirmationSelector.tsx +13 -3
- package/src/components/HelpView.tsx +129 -16
- package/src/components/InputBox.tsx +3 -1
- package/src/components/MessageList.tsx +5 -6
- package/src/constants/commands.ts +41 -0
- package/src/contexts/useChat.tsx +2 -17
- package/src/hooks/useInputManager.ts +352 -299
- package/src/managers/inputHandlers.ts +560 -0
- package/src/managers/inputReducer.ts +367 -0
- package/src/utils/highlightUtils.ts +66 -42
- package/dist/managers/InputManager.d.ts +0 -156
- package/dist/managers/InputManager.d.ts.map +0 -1
- package/dist/managers/InputManager.js +0 -749
- package/src/managers/InputManager.ts +0 -1024
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
export const initialState = {
|
|
2
|
+
inputText: "",
|
|
3
|
+
cursorPosition: 0,
|
|
4
|
+
showFileSelector: false,
|
|
5
|
+
atPosition: -1,
|
|
6
|
+
fileSearchQuery: "",
|
|
7
|
+
filteredFiles: [],
|
|
8
|
+
showCommandSelector: false,
|
|
9
|
+
slashPosition: -1,
|
|
10
|
+
commandSearchQuery: "",
|
|
11
|
+
showHistorySearch: false,
|
|
12
|
+
historySearchQuery: "",
|
|
13
|
+
longTextCounter: 0,
|
|
14
|
+
longTextMap: {},
|
|
15
|
+
attachedImages: [],
|
|
16
|
+
imageIdCounter: 1,
|
|
17
|
+
showBackgroundTaskManager: false,
|
|
18
|
+
showMcpManager: false,
|
|
19
|
+
showRewindManager: false,
|
|
20
|
+
showHelp: false,
|
|
21
|
+
showStatusCommand: false,
|
|
22
|
+
permissionMode: "default",
|
|
23
|
+
selectorJustUsed: false,
|
|
24
|
+
isPasting: false,
|
|
25
|
+
pasteBuffer: "",
|
|
26
|
+
initialPasteCursorPosition: 0,
|
|
27
|
+
};
|
|
28
|
+
export function inputReducer(state, action) {
|
|
29
|
+
switch (action.type) {
|
|
30
|
+
case "SET_INPUT_TEXT":
|
|
31
|
+
return { ...state, inputText: action.payload };
|
|
32
|
+
case "SET_CURSOR_POSITION":
|
|
33
|
+
return {
|
|
34
|
+
...state,
|
|
35
|
+
cursorPosition: Math.max(0, Math.min(state.inputText.length, action.payload)),
|
|
36
|
+
};
|
|
37
|
+
case "INSERT_TEXT": {
|
|
38
|
+
const beforeCursor = state.inputText.substring(0, state.cursorPosition);
|
|
39
|
+
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
40
|
+
const newText = beforeCursor + action.payload + afterCursor;
|
|
41
|
+
const newCursorPosition = state.cursorPosition + action.payload.length;
|
|
42
|
+
return {
|
|
43
|
+
...state,
|
|
44
|
+
inputText: newText,
|
|
45
|
+
cursorPosition: newCursorPosition,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
case "DELETE_CHAR": {
|
|
49
|
+
if (state.cursorPosition > 0) {
|
|
50
|
+
const beforeCursor = state.inputText.substring(0, state.cursorPosition - 1);
|
|
51
|
+
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
52
|
+
const newText = beforeCursor + afterCursor;
|
|
53
|
+
const newCursorPosition = state.cursorPosition - 1;
|
|
54
|
+
return {
|
|
55
|
+
...state,
|
|
56
|
+
inputText: newText,
|
|
57
|
+
cursorPosition: newCursorPosition,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return state;
|
|
61
|
+
}
|
|
62
|
+
case "MOVE_CURSOR": {
|
|
63
|
+
const newCursorPosition = Math.max(0, Math.min(state.inputText.length, state.cursorPosition + action.payload));
|
|
64
|
+
return { ...state, cursorPosition: newCursorPosition };
|
|
65
|
+
}
|
|
66
|
+
case "ACTIVATE_FILE_SELECTOR":
|
|
67
|
+
return {
|
|
68
|
+
...state,
|
|
69
|
+
showFileSelector: true,
|
|
70
|
+
atPosition: action.payload,
|
|
71
|
+
fileSearchQuery: "",
|
|
72
|
+
filteredFiles: [],
|
|
73
|
+
};
|
|
74
|
+
case "SET_FILE_SEARCH_QUERY":
|
|
75
|
+
return { ...state, fileSearchQuery: action.payload };
|
|
76
|
+
case "SET_FILTERED_FILES":
|
|
77
|
+
return { ...state, filteredFiles: action.payload };
|
|
78
|
+
case "CANCEL_FILE_SELECTOR":
|
|
79
|
+
return {
|
|
80
|
+
...state,
|
|
81
|
+
showFileSelector: false,
|
|
82
|
+
atPosition: -1,
|
|
83
|
+
fileSearchQuery: "",
|
|
84
|
+
filteredFiles: [],
|
|
85
|
+
selectorJustUsed: true,
|
|
86
|
+
};
|
|
87
|
+
case "ACTIVATE_COMMAND_SELECTOR":
|
|
88
|
+
return {
|
|
89
|
+
...state,
|
|
90
|
+
showCommandSelector: true,
|
|
91
|
+
slashPosition: action.payload,
|
|
92
|
+
commandSearchQuery: "",
|
|
93
|
+
};
|
|
94
|
+
case "SET_COMMAND_SEARCH_QUERY":
|
|
95
|
+
return { ...state, commandSearchQuery: action.payload };
|
|
96
|
+
case "CANCEL_COMMAND_SELECTOR":
|
|
97
|
+
return {
|
|
98
|
+
...state,
|
|
99
|
+
showCommandSelector: false,
|
|
100
|
+
slashPosition: -1,
|
|
101
|
+
commandSearchQuery: "",
|
|
102
|
+
selectorJustUsed: true,
|
|
103
|
+
};
|
|
104
|
+
case "ACTIVATE_HISTORY_SEARCH":
|
|
105
|
+
return {
|
|
106
|
+
...state,
|
|
107
|
+
showHistorySearch: true,
|
|
108
|
+
historySearchQuery: "",
|
|
109
|
+
};
|
|
110
|
+
case "SET_HISTORY_SEARCH_QUERY":
|
|
111
|
+
return { ...state, historySearchQuery: action.payload };
|
|
112
|
+
case "CANCEL_HISTORY_SEARCH":
|
|
113
|
+
return {
|
|
114
|
+
...state,
|
|
115
|
+
showHistorySearch: false,
|
|
116
|
+
historySearchQuery: "",
|
|
117
|
+
selectorJustUsed: true,
|
|
118
|
+
};
|
|
119
|
+
case "ADD_IMAGE": {
|
|
120
|
+
const newImage = {
|
|
121
|
+
id: state.imageIdCounter,
|
|
122
|
+
path: action.payload.path,
|
|
123
|
+
mimeType: action.payload.mimeType,
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
...state,
|
|
127
|
+
attachedImages: [...state.attachedImages, newImage],
|
|
128
|
+
imageIdCounter: state.imageIdCounter + 1,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
case "REMOVE_IMAGE":
|
|
132
|
+
return {
|
|
133
|
+
...state,
|
|
134
|
+
attachedImages: state.attachedImages.filter((img) => img.id !== action.payload),
|
|
135
|
+
};
|
|
136
|
+
case "CLEAR_IMAGES":
|
|
137
|
+
return { ...state, attachedImages: [] };
|
|
138
|
+
case "SET_SHOW_BACKGROUND_TASK_MANAGER":
|
|
139
|
+
return {
|
|
140
|
+
...state,
|
|
141
|
+
showBackgroundTaskManager: action.payload,
|
|
142
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
143
|
+
};
|
|
144
|
+
case "SET_SHOW_MCP_MANAGER":
|
|
145
|
+
return {
|
|
146
|
+
...state,
|
|
147
|
+
showMcpManager: action.payload,
|
|
148
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
149
|
+
};
|
|
150
|
+
case "SET_SHOW_REWIND_MANAGER":
|
|
151
|
+
return {
|
|
152
|
+
...state,
|
|
153
|
+
showRewindManager: action.payload,
|
|
154
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
155
|
+
};
|
|
156
|
+
case "SET_SHOW_HELP":
|
|
157
|
+
return {
|
|
158
|
+
...state,
|
|
159
|
+
showHelp: action.payload,
|
|
160
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
161
|
+
};
|
|
162
|
+
case "SET_SHOW_STATUS_COMMAND":
|
|
163
|
+
return {
|
|
164
|
+
...state,
|
|
165
|
+
showStatusCommand: action.payload,
|
|
166
|
+
selectorJustUsed: !action.payload ? true : state.selectorJustUsed,
|
|
167
|
+
};
|
|
168
|
+
case "SET_PERMISSION_MODE":
|
|
169
|
+
return { ...state, permissionMode: action.payload };
|
|
170
|
+
case "SET_SELECTOR_JUST_USED":
|
|
171
|
+
return { ...state, selectorJustUsed: action.payload };
|
|
172
|
+
case "COMPRESS_AND_INSERT_TEXT": {
|
|
173
|
+
let textToInsert = action.payload;
|
|
174
|
+
let newLongTextCounter = state.longTextCounter;
|
|
175
|
+
const newLongTextMap = { ...state.longTextMap };
|
|
176
|
+
if (textToInsert.length > 200) {
|
|
177
|
+
newLongTextCounter += 1;
|
|
178
|
+
const compressedLabel = `[LongText#${newLongTextCounter}]`;
|
|
179
|
+
newLongTextMap[compressedLabel] = textToInsert;
|
|
180
|
+
textToInsert = compressedLabel;
|
|
181
|
+
}
|
|
182
|
+
const beforeCursor = state.inputText.substring(0, state.cursorPosition);
|
|
183
|
+
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
184
|
+
const newText = beforeCursor + textToInsert + afterCursor;
|
|
185
|
+
const newCursorPosition = state.cursorPosition + textToInsert.length;
|
|
186
|
+
return {
|
|
187
|
+
...state,
|
|
188
|
+
inputText: newText,
|
|
189
|
+
cursorPosition: newCursorPosition,
|
|
190
|
+
longTextCounter: newLongTextCounter,
|
|
191
|
+
longTextMap: newLongTextMap,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
case "CLEAR_LONG_TEXT_MAP":
|
|
195
|
+
return { ...state, longTextMap: {} };
|
|
196
|
+
case "CLEAR_INPUT":
|
|
197
|
+
return {
|
|
198
|
+
...state,
|
|
199
|
+
inputText: "",
|
|
200
|
+
cursorPosition: 0,
|
|
201
|
+
};
|
|
202
|
+
case "START_PASTE":
|
|
203
|
+
return {
|
|
204
|
+
...state,
|
|
205
|
+
isPasting: true,
|
|
206
|
+
pasteBuffer: action.payload.buffer,
|
|
207
|
+
initialPasteCursorPosition: action.payload.cursorPosition,
|
|
208
|
+
};
|
|
209
|
+
case "APPEND_PASTE_BUFFER":
|
|
210
|
+
return {
|
|
211
|
+
...state,
|
|
212
|
+
pasteBuffer: state.pasteBuffer + action.payload,
|
|
213
|
+
};
|
|
214
|
+
case "END_PASTE":
|
|
215
|
+
return {
|
|
216
|
+
...state,
|
|
217
|
+
isPasting: false,
|
|
218
|
+
pasteBuffer: "",
|
|
219
|
+
};
|
|
220
|
+
case "ADD_IMAGE_AND_INSERT_PLACEHOLDER": {
|
|
221
|
+
const newImage = {
|
|
222
|
+
id: state.imageIdCounter,
|
|
223
|
+
path: action.payload.path,
|
|
224
|
+
mimeType: action.payload.mimeType,
|
|
225
|
+
};
|
|
226
|
+
const placeholder = `[Image #${newImage.id}]`;
|
|
227
|
+
const beforeCursor = state.inputText.substring(0, state.cursorPosition);
|
|
228
|
+
const afterCursor = state.inputText.substring(state.cursorPosition);
|
|
229
|
+
const newText = beforeCursor + placeholder + afterCursor;
|
|
230
|
+
const newCursorPosition = state.cursorPosition + placeholder.length;
|
|
231
|
+
return {
|
|
232
|
+
...state,
|
|
233
|
+
attachedImages: [...state.attachedImages, newImage],
|
|
234
|
+
imageIdCounter: state.imageIdCounter + 1,
|
|
235
|
+
inputText: newText,
|
|
236
|
+
cursorPosition: newCursorPosition,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
default:
|
|
240
|
+
return state;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"highlightUtils.d.ts","sourceRoot":"","sources":["../../src/utils/highlightUtils.ts"],"names":[],"mappings":"AA6DA,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"highlightUtils.d.ts","sourceRoot":"","sources":["../../src/utils/highlightUtils.ts"],"names":[],"mappings":"AA6DA,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAsCvE"}
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
import hljs from
|
|
2
|
-
import { parse, HTMLElement, TextNode } from
|
|
3
|
-
import chalk from
|
|
1
|
+
import hljs from "highlight.js";
|
|
2
|
+
import { parse, HTMLElement, TextNode } from "node-html-parser";
|
|
3
|
+
import chalk from "chalk";
|
|
4
4
|
const theme = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
5
|
+
"hljs-keyword": chalk.blue,
|
|
6
|
+
"hljs-built_in": chalk.cyan,
|
|
7
|
+
"hljs-type": chalk.cyan,
|
|
8
|
+
"hljs-literal": chalk.magenta,
|
|
9
|
+
"hljs-number": chalk.magenta,
|
|
10
|
+
"hljs-operator": chalk.white,
|
|
11
|
+
"hljs-punctuation": chalk.white,
|
|
12
|
+
"hljs-property": chalk.yellow,
|
|
13
|
+
"hljs-attr": chalk.yellow,
|
|
14
|
+
"hljs-variable": chalk.white,
|
|
15
|
+
"hljs-template-variable": chalk.white,
|
|
16
|
+
"hljs-string": chalk.green,
|
|
17
|
+
"hljs-char": chalk.green,
|
|
18
|
+
"hljs-comment": chalk.gray,
|
|
19
|
+
"hljs-doctag": chalk.gray,
|
|
20
|
+
"hljs-function": chalk.yellow,
|
|
21
|
+
"hljs-title": chalk.yellow,
|
|
22
|
+
"hljs-params": chalk.white,
|
|
23
|
+
"hljs-tag": chalk.blue,
|
|
24
|
+
"hljs-name": chalk.blue,
|
|
25
|
+
"hljs-selector-tag": chalk.blue,
|
|
26
|
+
"hljs-selector-id": chalk.blue,
|
|
27
|
+
"hljs-selector-class": chalk.blue,
|
|
28
|
+
"hljs-selector-attr": chalk.blue,
|
|
29
|
+
"hljs-selector-pseudo": chalk.blue,
|
|
30
|
+
"hljs-subst": chalk.white,
|
|
31
|
+
"hljs-section": chalk.blue.bold,
|
|
32
|
+
"hljs-bullet": chalk.magenta,
|
|
33
|
+
"hljs-emphasis": chalk.italic,
|
|
34
|
+
"hljs-strong": chalk.bold,
|
|
35
|
+
"hljs-addition": chalk.green,
|
|
36
|
+
"hljs-deletion": chalk.red,
|
|
37
|
+
"hljs-link": chalk.blue.underline,
|
|
38
38
|
};
|
|
39
39
|
function nodeToAnsi(node) {
|
|
40
40
|
if (node instanceof TextNode) {
|
|
41
41
|
return node.text;
|
|
42
42
|
}
|
|
43
43
|
if (node instanceof HTMLElement) {
|
|
44
|
-
const content = node.childNodes.map(nodeToAnsi).join(
|
|
45
|
-
const classes = node.getAttribute(
|
|
44
|
+
const content = node.childNodes.map(nodeToAnsi).join("");
|
|
45
|
+
const classes = node.getAttribute("class")?.split(/\s+/) || [];
|
|
46
46
|
for (const className of classes) {
|
|
47
47
|
if (theme[className]) {
|
|
48
48
|
return theme[className](content);
|
|
@@ -50,18 +50,42 @@ function nodeToAnsi(node) {
|
|
|
50
50
|
}
|
|
51
51
|
return content;
|
|
52
52
|
}
|
|
53
|
-
return
|
|
53
|
+
return "";
|
|
54
54
|
}
|
|
55
55
|
export function highlightToAnsi(code, language) {
|
|
56
56
|
if (!code) {
|
|
57
|
-
return
|
|
57
|
+
return "";
|
|
58
58
|
}
|
|
59
59
|
try {
|
|
60
60
|
const highlighted = language
|
|
61
61
|
? hljs.highlight(code, { language }).value
|
|
62
|
-
: hljs.highlightAuto(code
|
|
62
|
+
: hljs.highlightAuto(code, [
|
|
63
|
+
"javascript",
|
|
64
|
+
"typescript",
|
|
65
|
+
"bash",
|
|
66
|
+
"json",
|
|
67
|
+
"markdown",
|
|
68
|
+
"python",
|
|
69
|
+
"yaml",
|
|
70
|
+
"html",
|
|
71
|
+
"css",
|
|
72
|
+
"sql",
|
|
73
|
+
"xml",
|
|
74
|
+
"rust",
|
|
75
|
+
"go",
|
|
76
|
+
"java",
|
|
77
|
+
"cpp",
|
|
78
|
+
"c",
|
|
79
|
+
"csharp",
|
|
80
|
+
"php",
|
|
81
|
+
"ruby",
|
|
82
|
+
"swift",
|
|
83
|
+
"kotlin",
|
|
84
|
+
"toml",
|
|
85
|
+
"ini",
|
|
86
|
+
]).value;
|
|
63
87
|
const root = parse(highlighted);
|
|
64
|
-
return root.childNodes.map(nodeToAnsi).join(
|
|
88
|
+
return root.childNodes.map(nodeToAnsi).join("");
|
|
65
89
|
}
|
|
66
90
|
catch {
|
|
67
91
|
return code;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"react": "^19.2.4",
|
|
40
40
|
"react-dom": "19.2.4",
|
|
41
41
|
"yargs": "^17.7.2",
|
|
42
|
-
"wave-agent-sdk": "0.8.
|
|
42
|
+
"wave-agent-sdk": "0.8.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/react": "^19.1.8",
|
|
@@ -95,7 +95,7 @@ export const ChatInterface: React.FC = () => {
|
|
|
95
95
|
<MessageList
|
|
96
96
|
messages={messages}
|
|
97
97
|
isExpanded={isExpanded}
|
|
98
|
-
|
|
98
|
+
forceStatic={isConfirmationVisible && isConfirmationTooTall}
|
|
99
99
|
version={version}
|
|
100
100
|
workdir={workdir}
|
|
101
101
|
model={model}
|
|
@@ -1,46 +1,7 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import type { SlashCommand } from "wave-agent-sdk";
|
|
4
|
-
|
|
5
|
-
const AVAILABLE_COMMANDS: SlashCommand[] = [
|
|
6
|
-
{
|
|
7
|
-
id: "clear",
|
|
8
|
-
name: "clear",
|
|
9
|
-
description: "Clear the chat session and terminal",
|
|
10
|
-
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
id: "tasks",
|
|
14
|
-
name: "tasks",
|
|
15
|
-
description: "View and manage background tasks (shells and subagents)",
|
|
16
|
-
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: "mcp",
|
|
20
|
-
name: "mcp",
|
|
21
|
-
description: "View and manage MCP servers",
|
|
22
|
-
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: "rewind",
|
|
26
|
-
name: "rewind",
|
|
27
|
-
description:
|
|
28
|
-
"Revert conversation and file changes to a previous checkpoint",
|
|
29
|
-
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
id: "help",
|
|
33
|
-
name: "help",
|
|
34
|
-
description: "Show help and key bindings",
|
|
35
|
-
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: "status",
|
|
39
|
-
name: "status",
|
|
40
|
-
description: "Show agent status and configuration",
|
|
41
|
-
handler: () => {}, // Handler here won't be used, actual processing is in the hook
|
|
42
|
-
},
|
|
43
|
-
];
|
|
4
|
+
import { AVAILABLE_COMMANDS } from "../constants/commands.js";
|
|
44
5
|
|
|
45
6
|
export interface CommandSelectorProps {
|
|
46
7
|
searchQuery: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useLayoutEffect, useRef, useState } from "react";
|
|
1
|
+
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useInput, useStdout, measureElement } from "ink";
|
|
3
3
|
import type { PermissionDecision, AskUserQuestionInput } from "wave-agent-sdk";
|
|
4
4
|
import {
|
|
@@ -81,6 +81,16 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
|
|
|
81
81
|
>,
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
const pendingDecisionRef = useRef<PermissionDecision | null>(null);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (pendingDecisionRef.current) {
|
|
88
|
+
const decision = pendingDecisionRef.current;
|
|
89
|
+
pendingDecisionRef.current = null;
|
|
90
|
+
onDecision(decision);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
84
94
|
const questions =
|
|
85
95
|
(toolInput as unknown as AskUserQuestionInput)?.questions || [];
|
|
86
96
|
const currentQuestion = questions[questionState.currentQuestionIndex];
|
|
@@ -204,10 +214,10 @@ export const ConfirmationSelector: React.FC<ConfirmationSelectorProps> = ({
|
|
|
204
214
|
);
|
|
205
215
|
if (!allAnswered) return prev;
|
|
206
216
|
|
|
207
|
-
|
|
217
|
+
pendingDecisionRef.current = {
|
|
208
218
|
behavior: "allow",
|
|
209
219
|
message: JSON.stringify(finalAnswers),
|
|
210
|
-
}
|
|
220
|
+
};
|
|
211
221
|
return {
|
|
212
222
|
...prev,
|
|
213
223
|
userAnswers: finalAnswers,
|
|
@@ -1,14 +1,57 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useState } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import type { SlashCommand } from "wave-agent-sdk";
|
|
4
|
+
import { AVAILABLE_COMMANDS } from "../constants/commands.js";
|
|
3
5
|
|
|
4
6
|
export interface HelpViewProps {
|
|
5
7
|
onCancel: () => void;
|
|
8
|
+
commands?: SlashCommand[];
|
|
6
9
|
}
|
|
7
10
|
|
|
8
|
-
export const HelpView: React.FC<HelpViewProps> = ({
|
|
11
|
+
export const HelpView: React.FC<HelpViewProps> = ({
|
|
12
|
+
onCancel,
|
|
13
|
+
commands = [],
|
|
14
|
+
}) => {
|
|
15
|
+
const [activeTab, setActiveTab] = useState<
|
|
16
|
+
"general" | "commands" | "custom-commands"
|
|
17
|
+
>("general");
|
|
18
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
19
|
+
const MAX_VISIBLE_ITEMS = 10;
|
|
20
|
+
|
|
21
|
+
const tabs: ("general" | "commands" | "custom-commands")[] = [
|
|
22
|
+
"general",
|
|
23
|
+
"commands",
|
|
24
|
+
];
|
|
25
|
+
if (commands.length > 0) {
|
|
26
|
+
tabs.push("custom-commands");
|
|
27
|
+
}
|
|
28
|
+
|
|
9
29
|
useInput((_, key) => {
|
|
10
|
-
if (key.escape
|
|
30
|
+
if (key.escape) {
|
|
11
31
|
onCancel();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (key.tab) {
|
|
36
|
+
setActiveTab((prev) => {
|
|
37
|
+
const currentIndex = tabs.indexOf(prev);
|
|
38
|
+
const nextIndex = (currentIndex + 1) % tabs.length;
|
|
39
|
+
return tabs[nextIndex];
|
|
40
|
+
});
|
|
41
|
+
setSelectedIndex(0);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (activeTab === "commands" || activeTab === "custom-commands") {
|
|
46
|
+
const currentCommands =
|
|
47
|
+
activeTab === "commands" ? AVAILABLE_COMMANDS : commands;
|
|
48
|
+
if (key.upArrow) {
|
|
49
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
50
|
+
} else if (key.downArrow) {
|
|
51
|
+
setSelectedIndex((prev) =>
|
|
52
|
+
Math.min(currentCommands.length - 1, prev + 1),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
12
55
|
}
|
|
13
56
|
});
|
|
14
57
|
|
|
@@ -21,14 +64,35 @@ export const HelpView: React.FC<HelpViewProps> = ({ onCancel }) => {
|
|
|
21
64
|
{ key: "Ctrl+B", description: "Background current task" },
|
|
22
65
|
{ key: "Ctrl+V", description: "Paste image" },
|
|
23
66
|
{ key: "Shift+Tab", description: "Cycle permission mode" },
|
|
24
|
-
{ key: "/status", description: "Show agent status and configuration" },
|
|
25
|
-
{ key: "/clear", description: "Clear the chat session and terminal" },
|
|
26
67
|
{
|
|
27
68
|
key: "Esc",
|
|
28
69
|
description: "Interrupt AI or command / Cancel selector / Close help",
|
|
29
70
|
},
|
|
30
71
|
];
|
|
31
72
|
|
|
73
|
+
// Calculate visible window for commands
|
|
74
|
+
const currentCommands =
|
|
75
|
+
activeTab === "commands" ? AVAILABLE_COMMANDS : commands;
|
|
76
|
+
const startIndex = Math.max(
|
|
77
|
+
0,
|
|
78
|
+
Math.min(
|
|
79
|
+
selectedIndex - Math.floor(MAX_VISIBLE_ITEMS / 2),
|
|
80
|
+
Math.max(0, currentCommands.length - MAX_VISIBLE_ITEMS),
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
const visibleCommands = currentCommands.slice(
|
|
84
|
+
startIndex,
|
|
85
|
+
startIndex + MAX_VISIBLE_ITEMS,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const footerText = [
|
|
89
|
+
"Tab switch",
|
|
90
|
+
activeTab !== "general" && "↑↓ navigate",
|
|
91
|
+
"Esc close",
|
|
92
|
+
]
|
|
93
|
+
.filter(Boolean)
|
|
94
|
+
.join(" • ");
|
|
95
|
+
|
|
32
96
|
return (
|
|
33
97
|
<Box
|
|
34
98
|
flexDirection="column"
|
|
@@ -37,24 +101,73 @@ export const HelpView: React.FC<HelpViewProps> = ({ onCancel }) => {
|
|
|
37
101
|
borderLeft={false}
|
|
38
102
|
borderRight={false}
|
|
39
103
|
paddingX={1}
|
|
104
|
+
width="100%"
|
|
40
105
|
>
|
|
41
|
-
<Box marginBottom={1}>
|
|
42
|
-
<Text
|
|
43
|
-
|
|
106
|
+
<Box marginBottom={1} gap={2}>
|
|
107
|
+
<Text
|
|
108
|
+
color={activeTab === "general" ? "cyan" : "gray"}
|
|
109
|
+
bold
|
|
110
|
+
underline={activeTab === "general"}
|
|
111
|
+
>
|
|
112
|
+
General
|
|
113
|
+
</Text>
|
|
114
|
+
<Text
|
|
115
|
+
color={activeTab === "commands" ? "cyan" : "gray"}
|
|
116
|
+
bold
|
|
117
|
+
underline={activeTab === "commands"}
|
|
118
|
+
>
|
|
119
|
+
Commands
|
|
44
120
|
</Text>
|
|
121
|
+
{commands.length > 0 && (
|
|
122
|
+
<Text
|
|
123
|
+
color={activeTab === "custom-commands" ? "cyan" : "gray"}
|
|
124
|
+
bold
|
|
125
|
+
underline={activeTab === "custom-commands"}
|
|
126
|
+
>
|
|
127
|
+
Custom Commands
|
|
128
|
+
</Text>
|
|
129
|
+
)}
|
|
45
130
|
</Box>
|
|
46
131
|
|
|
47
|
-
{
|
|
48
|
-
<Box
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
132
|
+
{activeTab === "general" ? (
|
|
133
|
+
<Box flexDirection="column">
|
|
134
|
+
{helpItems.map((item, index) => (
|
|
135
|
+
<Box key={index}>
|
|
136
|
+
<Box width={20}>
|
|
137
|
+
<Text color="yellow">{item.key}</Text>
|
|
138
|
+
</Box>
|
|
139
|
+
<Text color="white">{item.description}</Text>
|
|
140
|
+
</Box>
|
|
141
|
+
))}
|
|
142
|
+
</Box>
|
|
143
|
+
) : (
|
|
144
|
+
<Box flexDirection="column">
|
|
145
|
+
{visibleCommands.map((command, index) => {
|
|
146
|
+
const actualIndex = startIndex + index;
|
|
147
|
+
const isSelected = actualIndex === selectedIndex;
|
|
148
|
+
return (
|
|
149
|
+
<Box key={command.id} flexDirection="column">
|
|
150
|
+
<Text
|
|
151
|
+
color={isSelected ? "black" : "white"}
|
|
152
|
+
backgroundColor={isSelected ? "cyan" : undefined}
|
|
153
|
+
>
|
|
154
|
+
{isSelected ? "▶ " : " "}/{command.id}
|
|
155
|
+
</Text>
|
|
156
|
+
{isSelected && (
|
|
157
|
+
<Box marginLeft={4}>
|
|
158
|
+
<Text color="gray" dimColor>
|
|
159
|
+
{command.description}
|
|
160
|
+
</Text>
|
|
161
|
+
</Box>
|
|
162
|
+
)}
|
|
163
|
+
</Box>
|
|
164
|
+
);
|
|
165
|
+
})}
|
|
53
166
|
</Box>
|
|
54
|
-
)
|
|
167
|
+
)}
|
|
55
168
|
|
|
56
169
|
<Box marginTop={1}>
|
|
57
|
-
<Text dimColor>
|
|
170
|
+
<Text dimColor>{footerText}</Text>
|
|
58
171
|
</Box>
|
|
59
172
|
</Box>
|
|
60
173
|
);
|