wave-code 0.0.16 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/cli.d.ts +1 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +2 -2
  4. package/dist/commands/plugin/disable.d.ts +5 -0
  5. package/dist/commands/plugin/disable.d.ts.map +1 -0
  6. package/dist/commands/plugin/disable.js +21 -0
  7. package/dist/commands/plugin/enable.d.ts +5 -0
  8. package/dist/commands/plugin/enable.d.ts.map +1 -0
  9. package/dist/commands/plugin/enable.js +21 -0
  10. package/dist/commands/plugin/install.d.ts +5 -0
  11. package/dist/commands/plugin/install.d.ts.map +1 -0
  12. package/dist/commands/plugin/install.js +28 -0
  13. package/dist/commands/plugin/list.d.ts +2 -0
  14. package/dist/commands/plugin/list.d.ts.map +1 -0
  15. package/dist/commands/plugin/list.js +53 -0
  16. package/dist/commands/plugin/marketplace.d.ts +8 -0
  17. package/dist/commands/plugin/marketplace.d.ts.map +1 -0
  18. package/dist/commands/plugin/marketplace.js +73 -0
  19. package/dist/components/App.d.ts +1 -0
  20. package/dist/components/App.d.ts.map +1 -1
  21. package/dist/components/App.js +4 -4
  22. package/dist/components/BashHistorySelector.d.ts +1 -0
  23. package/dist/components/BashHistorySelector.d.ts.map +1 -1
  24. package/dist/components/BashHistorySelector.js +15 -5
  25. package/dist/components/BashShellManager.d.ts.map +1 -1
  26. package/dist/components/BashShellManager.js +4 -4
  27. package/dist/components/ChatInterface.d.ts.map +1 -1
  28. package/dist/components/ChatInterface.js +1 -2
  29. package/dist/components/CommandSelector.d.ts.map +1 -1
  30. package/dist/components/CommandSelector.js +2 -2
  31. package/dist/components/Confirmation.d.ts +1 -0
  32. package/dist/components/Confirmation.d.ts.map +1 -1
  33. package/dist/components/Confirmation.js +151 -48
  34. package/dist/components/DiffDisplay.d.ts +3 -2
  35. package/dist/components/DiffDisplay.d.ts.map +1 -1
  36. package/dist/components/DiffDisplay.js +87 -82
  37. package/dist/components/FileSelector.d.ts.map +1 -1
  38. package/dist/components/FileSelector.js +2 -2
  39. package/dist/components/InputBox.d.ts.map +1 -1
  40. package/dist/components/InputBox.js +2 -2
  41. package/dist/components/McpManager.d.ts.map +1 -1
  42. package/dist/components/McpManager.js +3 -3
  43. package/dist/components/MemoryTypeSelector.d.ts.map +1 -1
  44. package/dist/components/MemoryTypeSelector.js +1 -1
  45. package/dist/components/MessageList.js +1 -1
  46. package/dist/components/PlanDisplay.d.ts +8 -0
  47. package/dist/components/PlanDisplay.d.ts.map +1 -0
  48. package/dist/components/PlanDisplay.js +14 -0
  49. package/dist/components/ToolResultDisplay.d.ts.map +1 -1
  50. package/dist/components/ToolResultDisplay.js +1 -1
  51. package/dist/contexts/useChat.d.ts +1 -0
  52. package/dist/contexts/useChat.d.ts.map +1 -1
  53. package/dist/contexts/useChat.js +3 -1
  54. package/dist/hooks/useInputManager.d.ts +1 -0
  55. package/dist/hooks/useInputManager.d.ts.map +1 -1
  56. package/dist/hooks/useInputManager.js +4 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +103 -0
  59. package/dist/managers/InputManager.d.ts +1 -0
  60. package/dist/managers/InputManager.d.ts.map +1 -1
  61. package/dist/managers/InputManager.js +7 -2
  62. package/dist/print-cli.d.ts +1 -0
  63. package/dist/print-cli.d.ts.map +1 -1
  64. package/dist/print-cli.js +2 -1
  65. package/package.json +2 -2
  66. package/src/cli.tsx +8 -1
  67. package/src/commands/plugin/disable.ts +31 -0
  68. package/src/commands/plugin/enable.ts +31 -0
  69. package/src/commands/plugin/install.ts +42 -0
  70. package/src/commands/plugin/list.ts +64 -0
  71. package/src/commands/plugin/marketplace.ts +72 -0
  72. package/src/components/App.tsx +11 -5
  73. package/src/components/BashHistorySelector.tsx +25 -7
  74. package/src/components/BashShellManager.tsx +16 -8
  75. package/src/components/ChatInterface.tsx +29 -27
  76. package/src/components/CommandSelector.tsx +8 -4
  77. package/src/components/Confirmation.tsx +312 -106
  78. package/src/components/DiffDisplay.tsx +167 -149
  79. package/src/components/FileSelector.tsx +8 -4
  80. package/src/components/InputBox.tsx +14 -4
  81. package/src/components/McpManager.tsx +12 -6
  82. package/src/components/MemoryTypeSelector.tsx +4 -2
  83. package/src/components/MessageList.tsx +1 -1
  84. package/src/components/PlanDisplay.tsx +46 -0
  85. package/src/components/ToolResultDisplay.tsx +4 -2
  86. package/src/contexts/useChat.tsx +4 -0
  87. package/src/hooks/useInputManager.ts +8 -0
  88. package/src/index.ts +178 -0
  89. package/src/managers/InputManager.ts +12 -1
  90. package/src/print-cli.ts +3 -0
@@ -1,6 +1,17 @@
1
1
  import React, { useState } from "react";
2
2
  import { Box, Text, useInput } from "ink";
3
- import type { PermissionDecision } from "wave-agent-sdk";
3
+ import type { PermissionDecision, AskUserQuestionInput } from "wave-agent-sdk";
4
+ import {
5
+ BASH_TOOL_NAME,
6
+ EDIT_TOOL_NAME,
7
+ MULTI_EDIT_TOOL_NAME,
8
+ DELETE_FILE_TOOL_NAME,
9
+ WRITE_TOOL_NAME,
10
+ EXIT_PLAN_MODE_TOOL_NAME,
11
+ ASK_USER_QUESTION_TOOL_NAME,
12
+ } from "wave-agent-sdk";
13
+ import { DiffDisplay } from "./DiffDisplay.js";
14
+ import { PlanDisplay } from "./PlanDisplay.js";
4
15
 
5
16
  // Helper function to generate descriptive action text
6
17
  const getActionDescription = (
@@ -12,26 +23,40 @@ const getActionDescription = (
12
23
  }
13
24
 
14
25
  switch (toolName) {
15
- case "Bash":
26
+ case BASH_TOOL_NAME:
16
27
  return `Execute command: ${toolInput.command || "unknown command"}`;
17
- case "Edit":
28
+ case EDIT_TOOL_NAME:
18
29
  return `Edit file: ${toolInput.file_path || "unknown file"}`;
19
- case "MultiEdit":
30
+ case MULTI_EDIT_TOOL_NAME:
20
31
  return `Edit multiple sections in: ${toolInput.file_path || "unknown file"}`;
21
- case "Delete":
32
+ case DELETE_FILE_TOOL_NAME:
22
33
  return `Delete file: ${toolInput.target_file || "unknown file"}`;
23
- case "Write":
34
+ case WRITE_TOOL_NAME:
24
35
  return `Write to file: ${toolInput.file_path || "unknown file"}`;
36
+ case EXIT_PLAN_MODE_TOOL_NAME:
37
+ return "Review and approve the plan";
38
+ case ASK_USER_QUESTION_TOOL_NAME:
39
+ return "Answer questions to clarify intent";
25
40
  default:
26
41
  return "Execute operation";
27
42
  }
28
43
  };
29
44
 
45
+ const getHeaderColor = (header: string) => {
46
+ const colors = ["red", "green", "blue", "magenta", "cyan"] as const;
47
+ let hash = 0;
48
+ for (let i = 0; i < header.length; i++) {
49
+ hash = header.charCodeAt(i) + ((hash << 5) - hash);
50
+ }
51
+ return colors[Math.abs(hash) % colors.length];
52
+ };
53
+
30
54
  export interface ConfirmationProps {
31
55
  toolName: string;
32
56
  toolInput?: Record<string, unknown>;
33
57
  suggestedPrefix?: string;
34
58
  hidePersistentOption?: boolean;
59
+ isExpanded?: boolean;
35
60
  onDecision: (decision: PermissionDecision) => void;
36
61
  onCancel: () => void;
37
62
  onAbort: () => void;
@@ -48,6 +73,7 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
48
73
  toolInput,
49
74
  suggestedPrefix,
50
75
  hidePersistentOption,
76
+ isExpanded = false,
51
77
  onDecision,
52
78
  onCancel,
53
79
  onAbort,
@@ -58,8 +84,24 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
58
84
  hasUserInput: false,
59
85
  });
60
86
 
87
+ // Specialized state for AskUserQuestion
88
+ const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
89
+ const [selectedOptionIndex, setSelectedOptionIndex] = useState(0);
90
+ const [selectedOptionIndices, setSelectedOptionIndices] = useState<
91
+ Set<number>
92
+ >(new Set());
93
+ const [userAnswers, setUserAnswers] = useState<Record<string, string>>({});
94
+ const [otherText, setOtherText] = useState("");
95
+
96
+ const questions =
97
+ (toolInput as unknown as AskUserQuestionInput)?.questions || [];
98
+ const currentQuestion = questions[currentQuestionIndex];
99
+
61
100
  const getAutoOptionText = () => {
62
- if (toolName === "Bash") {
101
+ if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
102
+ return "Yes, and auto-accept edits";
103
+ }
104
+ if (toolName === BASH_TOOL_NAME) {
63
105
  if (suggestedPrefix) {
64
106
  return `Yes, and don't ask again for: ${suggestedPrefix}`;
65
107
  }
@@ -76,12 +118,116 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
76
118
  return;
77
119
  }
78
120
 
121
+ if (toolName === ASK_USER_QUESTION_TOOL_NAME) {
122
+ if (!currentQuestion) return;
123
+
124
+ const options = [...currentQuestion.options, { label: "Other" }];
125
+ const isMultiSelect = !!currentQuestion.multiSelect;
126
+
127
+ const isOtherFocused = selectedOptionIndex === options.length - 1;
128
+
129
+ if (key.return) {
130
+ let answer = "";
131
+ if (isMultiSelect) {
132
+ const selectedLabels = Array.from(selectedOptionIndices)
133
+ .filter((i) => i < currentQuestion.options.length)
134
+ .map((i) => currentQuestion.options[i].label);
135
+
136
+ const isOtherChecked = selectedOptionIndices.has(options.length - 1);
137
+ if (isOtherChecked && otherText.trim()) {
138
+ selectedLabels.push(otherText.trim());
139
+ }
140
+ answer = selectedLabels.join(", ");
141
+ } else {
142
+ if (isOtherFocused) {
143
+ answer = otherText.trim();
144
+ } else {
145
+ answer = options[selectedOptionIndex].label;
146
+ }
147
+ }
148
+
149
+ if (!answer) return;
150
+
151
+ const newAnswers = {
152
+ ...userAnswers,
153
+ [currentQuestion.question]: answer,
154
+ };
155
+ setUserAnswers(newAnswers);
156
+
157
+ if (currentQuestionIndex < questions.length - 1) {
158
+ setCurrentQuestionIndex(currentQuestionIndex + 1);
159
+ setSelectedOptionIndex(0);
160
+ setSelectedOptionIndices(new Set());
161
+ setOtherText("");
162
+ } else {
163
+ // All questions answered
164
+ onDecision({
165
+ behavior: "allow",
166
+ message: JSON.stringify(newAnswers),
167
+ });
168
+ }
169
+ return;
170
+ }
171
+
172
+ if (input === " ") {
173
+ if (
174
+ isMultiSelect &&
175
+ (!isOtherFocused || !selectedOptionIndices.has(selectedOptionIndex))
176
+ ) {
177
+ setSelectedOptionIndices((prev) => {
178
+ const next = new Set(prev);
179
+ if (next.has(selectedOptionIndex)) {
180
+ next.delete(selectedOptionIndex);
181
+ } else {
182
+ next.add(selectedOptionIndex);
183
+ }
184
+ return next;
185
+ });
186
+ return;
187
+ }
188
+
189
+ if (!isOtherFocused) {
190
+ return;
191
+ }
192
+ // If isOtherFocused is true, fall through to handle space as text input
193
+ }
194
+
195
+ if (key.upArrow) {
196
+ if (selectedOptionIndex > 0) {
197
+ setSelectedOptionIndex(selectedOptionIndex - 1);
198
+ }
199
+ return;
200
+ }
201
+
202
+ if (key.downArrow) {
203
+ if (selectedOptionIndex < options.length - 1) {
204
+ setSelectedOptionIndex(selectedOptionIndex + 1);
205
+ }
206
+ return;
207
+ }
208
+
209
+ if (isOtherFocused) {
210
+ if (key.backspace || key.delete) {
211
+ setOtherText((prev) => prev.slice(0, -1));
212
+ } else if (input && !key.ctrl && !key.meta) {
213
+ setOtherText((prev) => prev + input);
214
+ }
215
+ return;
216
+ }
217
+
218
+ return;
219
+ }
220
+
79
221
  // Handle Enter to confirm selection
80
222
  if (key.return) {
81
223
  if (state.selectedOption === "allow") {
82
- onDecision({ behavior: "allow" });
224
+ if (toolName === EXIT_PLAN_MODE_TOOL_NAME) {
225
+ onDecision({ behavior: "allow", newPermissionMode: "default" });
226
+ } else {
227
+ onDecision({ behavior: "allow" });
228
+ }
83
229
  } else if (state.selectedOption === "auto") {
84
- if (toolName === "Bash") {
230
+ if (toolName === BASH_TOOL_NAME) {
85
231
  const rule = suggestedPrefix
86
232
  ? `Bash(${suggestedPrefix}:*)`
87
233
  : `Bash(${toolInput?.command})`;
@@ -107,41 +253,6 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
107
253
  return;
108
254
  }
109
255
 
110
- // Handle numeric keys for quick selection (only if not typing in alternative)
111
- if (state.selectedOption !== "alternative" || !state.hasUserInput) {
112
- if (input === "1") {
113
- onDecision({ behavior: "allow" });
114
- return;
115
- }
116
- if (input === "2") {
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;
133
- } else {
134
- // If auto option is hidden, '2' selects alternative
135
- setState((prev) => ({ ...prev, selectedOption: "alternative" }));
136
- return;
137
- }
138
- }
139
- if (input === "3" && !hidePersistentOption) {
140
- setState((prev) => ({ ...prev, selectedOption: "alternative" }));
141
- return;
142
- }
143
- }
144
-
145
256
  // Handle arrow keys for navigation
146
257
  if (key.upArrow) {
147
258
  setState((prev) => {
@@ -208,79 +319,174 @@ export const Confirmation: React.FC<ConfirmationProps> = ({
208
319
  flexDirection="column"
209
320
  borderStyle="single"
210
321
  borderColor="yellow"
211
- padding={1}
212
- marginBottom={1}
322
+ borderBottom={false}
323
+ borderLeft={false}
324
+ borderRight={false}
325
+ paddingTop={1}
213
326
  >
214
327
  <Text color="yellow" bold>
215
328
  Tool: {toolName}
216
329
  </Text>
217
330
  <Text color="yellow">{getActionDescription(toolName, toolInput)}</Text>
218
331
 
219
- <Box marginTop={1}>
220
- <Text>Do you want to proceed?</Text>
221
- </Box>
222
-
223
- <Box marginTop={1} flexDirection="column">
224
- {/* Option 1: Yes */}
225
- <Box key="allow-option">
226
- <Text
227
- color={state.selectedOption === "allow" ? "black" : "white"}
228
- backgroundColor={
229
- state.selectedOption === "allow" ? "yellow" : undefined
230
- }
231
- bold={state.selectedOption === "allow"}
232
- >
233
- {state.selectedOption === "allow" ? "> " : " "}1. Yes
234
- </Text>
235
- </Box>
236
-
237
- {/* Option 2: Auto-accept/Persistent */}
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>
332
+ <DiffDisplay
333
+ toolName={toolName}
334
+ parameters={JSON.stringify(toolInput)}
335
+ isExpanded={isExpanded}
336
+ />
337
+
338
+ {toolName === ASK_USER_QUESTION_TOOL_NAME &&
339
+ currentQuestion &&
340
+ !isExpanded && (
341
+ <Box flexDirection="column" marginTop={1}>
342
+ <Box marginBottom={1}>
343
+ <Box
344
+ backgroundColor={getHeaderColor(currentQuestion.header)}
345
+ paddingX={1}
346
+ marginRight={1}
347
+ >
348
+ <Text color="black" bold>
349
+ {currentQuestion.header.slice(0, 12).toUpperCase()}
350
+ </Text>
351
+ </Box>
352
+ <Text bold>{currentQuestion.question}</Text>
353
+ </Box>
354
+
355
+ <Box flexDirection="column">
356
+ {(() => {
357
+ const isMultiSelect = !!currentQuestion.multiSelect;
358
+ return [...currentQuestion.options, { label: "Other" }].map(
359
+ (option, index) => {
360
+ const isSelected = selectedOptionIndex === index;
361
+ const isChecked = isMultiSelect
362
+ ? selectedOptionIndices.has(index)
363
+ : isSelected;
364
+ const isOther = index === currentQuestion.options.length;
365
+ const isRecommended = !isOther && option.isRecommended;
366
+
367
+ return (
368
+ <Box key={index}>
369
+ <Text
370
+ color={isSelected ? "black" : "white"}
371
+ backgroundColor={isSelected ? "yellow" : undefined}
372
+ >
373
+ {isSelected ? "> " : " "}
374
+ {isMultiSelect ? (isChecked ? "[x] " : "[ ] ") : ""}
375
+ {option.label}
376
+ {isRecommended && (
377
+ <Text color="green" bold>
378
+ {" "}
379
+ (Recommended)
380
+ </Text>
381
+ )}
382
+ {option.description ? ` - ${option.description}` : ""}
383
+ {isOther && isSelected && (
384
+ <Text>
385
+ :{" "}
386
+ {otherText || (
387
+ <Text color="gray" dimColor>
388
+ [Type your answer...]
389
+ </Text>
390
+ )}
391
+ </Text>
392
+ )}
393
+ </Text>
394
+ </Box>
395
+ );
396
+ },
397
+ );
398
+ })()}
399
+ </Box>
400
+
401
+ <Box marginTop={1}>
402
+ <Text dimColor>
403
+ Question {currentQuestionIndex + 1} of {questions.length} •
404
+ {currentQuestion.multiSelect ? " Space to toggle •" : ""} Use ↑↓
405
+ to navigate • Enter to confirm
406
+ </Text>
407
+ </Box>
250
408
  </Box>
251
409
  )}
252
410
 
253
- {/* Option 3: Alternative */}
254
- <Box key="alternative-option">
255
- <Text
256
- color={state.selectedOption === "alternative" ? "black" : "white"}
257
- backgroundColor={
258
- state.selectedOption === "alternative" ? "yellow" : undefined
259
- }
260
- bold={state.selectedOption === "alternative"}
261
- >
262
- {state.selectedOption === "alternative" ? "> " : " "}
263
- {hidePersistentOption ? "2. " : "3. "}
264
- {showPlaceholder ? (
265
- <Text color="gray" dimColor>
266
- {placeholderText}
267
- </Text>
268
- ) : (
269
- <Text>
270
- {state.alternativeText ||
271
- "Type here to tell Wave what to do differently"}
411
+ {toolName !== ASK_USER_QUESTION_TOOL_NAME &&
412
+ toolName === EXIT_PLAN_MODE_TOOL_NAME &&
413
+ !!toolInput?.plan_content && (
414
+ <PlanDisplay
415
+ plan={toolInput.plan_content as string}
416
+ isExpanded={isExpanded}
417
+ />
418
+ )}
419
+
420
+ {toolName !== ASK_USER_QUESTION_TOOL_NAME && !isExpanded && (
421
+ <>
422
+ <Box marginTop={1}>
423
+ <Text>Do you want to proceed?</Text>
424
+ </Box>
425
+
426
+ <Box marginTop={1} flexDirection="column">
427
+ {/* Option 1: Yes */}
428
+ <Box key="allow-option">
429
+ <Text
430
+ color={state.selectedOption === "allow" ? "black" : "white"}
431
+ backgroundColor={
432
+ state.selectedOption === "allow" ? "yellow" : undefined
433
+ }
434
+ bold={state.selectedOption === "allow"}
435
+ >
436
+ {state.selectedOption === "allow" ? "> " : " "}
437
+ {toolName === EXIT_PLAN_MODE_TOOL_NAME
438
+ ? "Yes, proceed with default mode"
439
+ : "Yes"}
272
440
  </Text>
441
+ </Box>
442
+
443
+ {/* Option 2: Auto-accept/Persistent */}
444
+ {!hidePersistentOption && (
445
+ <Box key="auto-option">
446
+ <Text
447
+ color={state.selectedOption === "auto" ? "black" : "white"}
448
+ backgroundColor={
449
+ state.selectedOption === "auto" ? "yellow" : undefined
450
+ }
451
+ bold={state.selectedOption === "auto"}
452
+ >
453
+ {state.selectedOption === "auto" ? "> " : " "}
454
+ {getAutoOptionText()}
455
+ </Text>
456
+ </Box>
273
457
  )}
274
- </Text>
275
- </Box>
276
- </Box>
277
-
278
- <Box marginTop={1}>
279
- <Text dimColor>
280
- Use ↑↓ or 1-{hidePersistentOption ? "2" : "3"} to navigate • ESC to
281
- cancel
282
- </Text>
283
- </Box>
458
+
459
+ {/* Option 3: Alternative */}
460
+ <Box key="alternative-option">
461
+ <Text
462
+ color={
463
+ state.selectedOption === "alternative" ? "black" : "white"
464
+ }
465
+ backgroundColor={
466
+ state.selectedOption === "alternative" ? "yellow" : undefined
467
+ }
468
+ bold={state.selectedOption === "alternative"}
469
+ >
470
+ {state.selectedOption === "alternative" ? "> " : " "}
471
+ {showPlaceholder ? (
472
+ <Text color="gray" dimColor>
473
+ {placeholderText}
474
+ </Text>
475
+ ) : (
476
+ <Text>
477
+ {state.alternativeText ||
478
+ "Type here to tell Wave what to do differently"}
479
+ </Text>
480
+ )}
481
+ </Text>
482
+ </Box>
483
+ </Box>
484
+
485
+ <Box marginTop={1}>
486
+ <Text dimColor>Use ↑↓ to navigate • ESC to cancel</Text>
487
+ </Box>
488
+ </>
489
+ )}
284
490
  </Box>
285
491
  );
286
492
  };