wave-code 0.0.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.
Files changed (131) hide show
  1. package/README.md +120 -0
  2. package/bin/wave-code.js +16 -0
  3. package/dist/cli.d.ts +6 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +62 -0
  6. package/dist/components/App.d.ts +8 -0
  7. package/dist/components/App.d.ts.map +1 -0
  8. package/dist/components/App.js +10 -0
  9. package/dist/components/BashHistorySelector.d.ts +10 -0
  10. package/dist/components/BashHistorySelector.d.ts.map +1 -0
  11. package/dist/components/BashHistorySelector.js +83 -0
  12. package/dist/components/BashShellManager.d.ts +6 -0
  13. package/dist/components/BashShellManager.d.ts.map +1 -0
  14. package/dist/components/BashShellManager.js +116 -0
  15. package/dist/components/ChatInterface.d.ts +3 -0
  16. package/dist/components/ChatInterface.d.ts.map +1 -0
  17. package/dist/components/ChatInterface.js +31 -0
  18. package/dist/components/CommandOutputDisplay.d.ts +9 -0
  19. package/dist/components/CommandOutputDisplay.d.ts.map +1 -0
  20. package/dist/components/CommandOutputDisplay.js +40 -0
  21. package/dist/components/CommandSelector.d.ts +11 -0
  22. package/dist/components/CommandSelector.d.ts.map +1 -0
  23. package/dist/components/CommandSelector.js +60 -0
  24. package/dist/components/CompressDisplay.d.ts +9 -0
  25. package/dist/components/CompressDisplay.d.ts.map +1 -0
  26. package/dist/components/CompressDisplay.js +17 -0
  27. package/dist/components/DiffViewer.d.ts +9 -0
  28. package/dist/components/DiffViewer.d.ts.map +1 -0
  29. package/dist/components/DiffViewer.js +221 -0
  30. package/dist/components/FileSelector.d.ts +13 -0
  31. package/dist/components/FileSelector.d.ts.map +1 -0
  32. package/dist/components/FileSelector.js +48 -0
  33. package/dist/components/InputBox.d.ts +23 -0
  34. package/dist/components/InputBox.d.ts.map +1 -0
  35. package/dist/components/InputBox.js +124 -0
  36. package/dist/components/McpManager.d.ts +10 -0
  37. package/dist/components/McpManager.d.ts.map +1 -0
  38. package/dist/components/McpManager.js +123 -0
  39. package/dist/components/MemoryDisplay.d.ts +8 -0
  40. package/dist/components/MemoryDisplay.d.ts.map +1 -0
  41. package/dist/components/MemoryDisplay.js +25 -0
  42. package/dist/components/MemoryTypeSelector.d.ts +8 -0
  43. package/dist/components/MemoryTypeSelector.d.ts.map +1 -0
  44. package/dist/components/MemoryTypeSelector.js +38 -0
  45. package/dist/components/MessageList.d.ts +12 -0
  46. package/dist/components/MessageList.d.ts.map +1 -0
  47. package/dist/components/MessageList.js +36 -0
  48. package/dist/components/ToolResultDisplay.d.ts +9 -0
  49. package/dist/components/ToolResultDisplay.d.ts.map +1 -0
  50. package/dist/components/ToolResultDisplay.js +52 -0
  51. package/dist/contexts/useAppConfig.d.ts +11 -0
  52. package/dist/contexts/useAppConfig.d.ts.map +1 -0
  53. package/dist/contexts/useAppConfig.js +13 -0
  54. package/dist/contexts/useChat.d.ts +36 -0
  55. package/dist/contexts/useChat.d.ts.map +1 -0
  56. package/dist/contexts/useChat.js +208 -0
  57. package/dist/hooks/useBashHistorySelector.d.ts +15 -0
  58. package/dist/hooks/useBashHistorySelector.d.ts.map +1 -0
  59. package/dist/hooks/useBashHistorySelector.js +61 -0
  60. package/dist/hooks/useCommandSelector.d.ts +24 -0
  61. package/dist/hooks/useCommandSelector.d.ts.map +1 -0
  62. package/dist/hooks/useCommandSelector.js +98 -0
  63. package/dist/hooks/useFileSelector.d.ts +16 -0
  64. package/dist/hooks/useFileSelector.d.ts.map +1 -0
  65. package/dist/hooks/useFileSelector.js +174 -0
  66. package/dist/hooks/useImageManager.d.ts +13 -0
  67. package/dist/hooks/useImageManager.d.ts.map +1 -0
  68. package/dist/hooks/useImageManager.js +46 -0
  69. package/dist/hooks/useInputHistory.d.ts +11 -0
  70. package/dist/hooks/useInputHistory.d.ts.map +1 -0
  71. package/dist/hooks/useInputHistory.js +64 -0
  72. package/dist/hooks/useInputKeyboardHandler.d.ts +83 -0
  73. package/dist/hooks/useInputKeyboardHandler.d.ts.map +1 -0
  74. package/dist/hooks/useInputKeyboardHandler.js +507 -0
  75. package/dist/hooks/useInputState.d.ts +14 -0
  76. package/dist/hooks/useInputState.d.ts.map +1 -0
  77. package/dist/hooks/useInputState.js +57 -0
  78. package/dist/hooks/useMemoryTypeSelector.d.ts +9 -0
  79. package/dist/hooks/useMemoryTypeSelector.d.ts.map +1 -0
  80. package/dist/hooks/useMemoryTypeSelector.js +27 -0
  81. package/dist/hooks/usePagination.d.ts +20 -0
  82. package/dist/hooks/usePagination.d.ts.map +1 -0
  83. package/dist/hooks/usePagination.js +168 -0
  84. package/dist/index.d.ts +5 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +91 -0
  87. package/dist/plain-cli.d.ts +7 -0
  88. package/dist/plain-cli.d.ts.map +1 -0
  89. package/dist/plain-cli.js +49 -0
  90. package/dist/utils/clipboard.d.ts +22 -0
  91. package/dist/utils/clipboard.d.ts.map +1 -0
  92. package/dist/utils/clipboard.js +347 -0
  93. package/dist/utils/constants.d.ts +17 -0
  94. package/dist/utils/constants.d.ts.map +1 -0
  95. package/dist/utils/constants.js +18 -0
  96. package/dist/utils/logger.d.ts +72 -0
  97. package/dist/utils/logger.d.ts.map +1 -0
  98. package/dist/utils/logger.js +245 -0
  99. package/package.json +60 -0
  100. package/src/cli.tsx +82 -0
  101. package/src/components/App.tsx +31 -0
  102. package/src/components/BashHistorySelector.tsx +163 -0
  103. package/src/components/BashShellManager.tsx +306 -0
  104. package/src/components/ChatInterface.tsx +88 -0
  105. package/src/components/CommandOutputDisplay.tsx +81 -0
  106. package/src/components/CommandSelector.tsx +144 -0
  107. package/src/components/CompressDisplay.tsx +58 -0
  108. package/src/components/DiffViewer.tsx +321 -0
  109. package/src/components/FileSelector.tsx +137 -0
  110. package/src/components/InputBox.tsx +310 -0
  111. package/src/components/McpManager.tsx +328 -0
  112. package/src/components/MemoryDisplay.tsx +62 -0
  113. package/src/components/MemoryTypeSelector.tsx +96 -0
  114. package/src/components/MessageList.tsx +215 -0
  115. package/src/components/ToolResultDisplay.tsx +138 -0
  116. package/src/contexts/useAppConfig.tsx +32 -0
  117. package/src/contexts/useChat.tsx +300 -0
  118. package/src/hooks/useBashHistorySelector.ts +77 -0
  119. package/src/hooks/useCommandSelector.ts +131 -0
  120. package/src/hooks/useFileSelector.ts +227 -0
  121. package/src/hooks/useImageManager.ts +64 -0
  122. package/src/hooks/useInputHistory.ts +74 -0
  123. package/src/hooks/useInputKeyboardHandler.ts +778 -0
  124. package/src/hooks/useInputState.ts +66 -0
  125. package/src/hooks/useMemoryTypeSelector.ts +40 -0
  126. package/src/hooks/usePagination.ts +203 -0
  127. package/src/index.ts +108 -0
  128. package/src/plain-cli.ts +66 -0
  129. package/src/utils/clipboard.ts +384 -0
  130. package/src/utils/constants.ts +22 -0
  131. package/src/utils/logger.ts +301 -0
@@ -0,0 +1,137 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+
4
+ export interface FileItem {
5
+ path: string;
6
+ type: "file" | "directory";
7
+ }
8
+
9
+ export interface FileSelectorProps {
10
+ files: FileItem[];
11
+ searchQuery: string;
12
+ onSelect: (filePath: string) => void;
13
+ onCancel: () => void;
14
+ }
15
+
16
+ export const FileSelector: React.FC<FileSelectorProps> = ({
17
+ files,
18
+ searchQuery,
19
+ onSelect,
20
+ onCancel,
21
+ }) => {
22
+ const [selectedIndex, setSelectedIndex] = useState(0);
23
+
24
+ useInput((input, key) => {
25
+ if (key.return) {
26
+ if (files.length > 0 && selectedIndex < files.length) {
27
+ onSelect(files[selectedIndex].path);
28
+ }
29
+ return;
30
+ }
31
+
32
+ if (key.escape) {
33
+ onCancel();
34
+ return;
35
+ }
36
+
37
+ if (key.upArrow) {
38
+ setSelectedIndex(Math.max(0, selectedIndex - 1));
39
+ return;
40
+ }
41
+
42
+ if (key.downArrow) {
43
+ setSelectedIndex(Math.min(files.length - 1, selectedIndex + 1));
44
+ return;
45
+ }
46
+ });
47
+
48
+ if (files.length === 0) {
49
+ return (
50
+ <Box
51
+ flexDirection="column"
52
+ borderStyle="single"
53
+ borderColor="yellow"
54
+ padding={1}
55
+ marginBottom={1}
56
+ >
57
+ <Text color="yellow">📁 No files found for "{searchQuery}"</Text>
58
+ <Text dimColor>Press Escape to cancel</Text>
59
+ </Box>
60
+ );
61
+ }
62
+
63
+ const maxDisplay = 10;
64
+
65
+ // Calculate display window start and end positions
66
+ const getDisplayWindow = () => {
67
+ const startIndex = Math.max(
68
+ 0,
69
+ Math.min(
70
+ selectedIndex - Math.floor(maxDisplay / 2),
71
+ files.length - maxDisplay,
72
+ ),
73
+ );
74
+ const endIndex = Math.min(files.length, startIndex + maxDisplay);
75
+ const adjustedStartIndex = Math.max(0, endIndex - maxDisplay);
76
+
77
+ return {
78
+ startIndex: adjustedStartIndex,
79
+ endIndex: endIndex,
80
+ displayFiles: files.slice(adjustedStartIndex, endIndex),
81
+ };
82
+ };
83
+
84
+ const { startIndex, endIndex, displayFiles } = getDisplayWindow();
85
+
86
+ return (
87
+ <Box
88
+ flexDirection="column"
89
+ borderStyle="single"
90
+ borderColor="cyan"
91
+ padding={1}
92
+ marginBottom={1}
93
+ >
94
+ <Text color="cyan" bold>
95
+ 📁 Select File/Directory{" "}
96
+ {searchQuery && `(filtering: "${searchQuery}")`}
97
+ </Text>
98
+
99
+ {/* Show hint for more files above */}
100
+ {startIndex > 0 && (
101
+ <Text dimColor>... {startIndex} more files above</Text>
102
+ )}
103
+
104
+ {displayFiles.map((fileItem, displayIndex) => {
105
+ const actualIndex = startIndex + displayIndex;
106
+ const isSelected = actualIndex === selectedIndex;
107
+ const icon = fileItem.type === "directory" ? "📁" : "📄";
108
+
109
+ return (
110
+ <Box key={fileItem.path}>
111
+ <Text
112
+ color={isSelected ? "black" : "white"}
113
+ backgroundColor={isSelected ? "cyan" : undefined}
114
+ >
115
+ {isSelected ? "▶ " : " "}
116
+ {icon} {fileItem.path}
117
+ </Text>
118
+ </Box>
119
+ );
120
+ })}
121
+
122
+ {/* Show hint for more files below */}
123
+ {endIndex < files.length && (
124
+ <Text dimColor>... {files.length - endIndex} more files below</Text>
125
+ )}
126
+
127
+ <Box marginTop={1}>
128
+ <Text dimColor>
129
+ Use ↑↓ to navigate, Enter to select, Escape to cancel
130
+ </Text>
131
+ <Text dimColor>
132
+ File {selectedIndex + 1} of {files.length}
133
+ </Text>
134
+ </Box>
135
+ </Box>
136
+ );
137
+ };
@@ -0,0 +1,310 @@
1
+ import React, { useState, useCallback } from "react";
2
+ import { Box, Text } from "ink";
3
+ import { FileSelector } from "./FileSelector.js";
4
+ import { CommandSelector } from "./CommandSelector.js";
5
+ import { BashHistorySelector } from "./BashHistorySelector.js";
6
+ import { MemoryTypeSelector } from "./MemoryTypeSelector.js";
7
+ import { BashShellManager } from "./BashShellManager.js";
8
+ import { McpManager } from "./McpManager.js";
9
+ import { useInputState } from "../hooks/useInputState.js";
10
+ import { useFileSelector } from "../hooks/useFileSelector.js";
11
+ import { useCommandSelector } from "../hooks/useCommandSelector.js";
12
+ import { useBashHistorySelector } from "../hooks/useBashHistorySelector.js";
13
+ import { useMemoryTypeSelector } from "../hooks/useMemoryTypeSelector.js";
14
+ import { useInputHistory } from "../hooks/useInputHistory.js";
15
+ import { useInputKeyboardHandler } from "../hooks/useInputKeyboardHandler.js";
16
+ import { useImageManager } from "../hooks/useImageManager.js";
17
+ import type { McpServerStatus, SlashCommand } from "wave-agent-sdk";
18
+
19
+ export const INPUT_PLACEHOLDER_TEXT =
20
+ "Type your message (use @ to reference files, / for commands, ! for bash history, # to add memory)...";
21
+
22
+ export const INPUT_PLACEHOLDER_TEXT_PREFIX = INPUT_PLACEHOLDER_TEXT.substring(
23
+ 0,
24
+ 10,
25
+ );
26
+
27
+ export interface InputBoxProps {
28
+ isLoading?: boolean;
29
+ isCommandRunning?: boolean;
30
+ workdir?: string;
31
+ userInputHistory?: string[];
32
+ sendMessage?: (
33
+ message: string,
34
+ images?: Array<{ path: string; mimeType: string }>,
35
+ ) => void;
36
+ abortMessage?: () => void;
37
+ saveMemory?: (message: string, type: "project" | "user") => Promise<void>;
38
+ // MCP related properties
39
+ mcpServers?: McpServerStatus[];
40
+ connectMcpServer?: (serverName: string) => Promise<boolean>;
41
+ disconnectMcpServer?: (serverName: string) => Promise<boolean>;
42
+ // Slash Command related properties
43
+ slashCommands?: SlashCommand[];
44
+ hasSlashCommand?: (commandId: string) => boolean;
45
+ }
46
+
47
+ export const InputBox: React.FC<InputBoxProps> = ({
48
+ isLoading = false,
49
+ isCommandRunning = false,
50
+ workdir,
51
+ userInputHistory = [],
52
+ sendMessage = () => {},
53
+ abortMessage = () => {},
54
+ saveMemory = async () => {},
55
+ mcpServers = [],
56
+ connectMcpServer = async () => false,
57
+ disconnectMcpServer = async () => false,
58
+ slashCommands = [],
59
+ hasSlashCommand = () => false,
60
+ }) => {
61
+ // Get current working directory
62
+ const currentWorkdir = workdir || process.cwd();
63
+ // Bash shell manager state
64
+ const [showBashManager, setShowBashManager] = useState(false);
65
+ // MCP manager state
66
+ const [showMcpManager, setShowMcpManager] = useState(false);
67
+ // Basic input state
68
+ const {
69
+ inputText,
70
+ setInputText,
71
+ cursorPosition,
72
+ setCursorPosition,
73
+ insertTextAtCursor,
74
+ deleteCharAtCursor,
75
+ clearInput,
76
+ moveCursorLeft,
77
+ moveCursorRight,
78
+ moveCursorToStart,
79
+ moveCursorToEnd,
80
+ } = useInputState();
81
+
82
+ // File selector functionality
83
+ const {
84
+ showFileSelector,
85
+ filteredFiles,
86
+ searchQuery,
87
+ activateFileSelector,
88
+ handleFileSelect: handleFileSelectorSelect,
89
+ handleCancelFileSelect,
90
+ updateSearchQuery,
91
+ checkForAtDeletion,
92
+ atPosition,
93
+ } = useFileSelector();
94
+
95
+ // Command selector functionality
96
+ const {
97
+ showCommandSelector,
98
+ commandSearchQuery,
99
+ activateCommandSelector,
100
+ handleCommandSelect: handleCommandSelectorSelect,
101
+ handleCommandInsert: handleCommandSelectorInsert,
102
+ handleCancelCommandSelect,
103
+ updateCommandSearchQuery,
104
+ checkForSlashDeletion,
105
+ slashPosition,
106
+ } = useCommandSelector({
107
+ onShowBashManager: () => setShowBashManager(true),
108
+ onShowMcpManager: () => setShowMcpManager(true),
109
+ sendMessage: async (content: string) => {
110
+ await sendMessage(content);
111
+ },
112
+ hasSlashCommand,
113
+ });
114
+
115
+ // Bash history selector functionality
116
+ const {
117
+ showBashHistorySelector,
118
+ bashHistorySearchQuery,
119
+ activateBashHistorySelector,
120
+ handleBashHistorySelect: handleBashHistorySelectorSelect,
121
+ handleBashHistoryExecute,
122
+ handleCancelBashHistorySelect,
123
+ updateBashHistorySearchQuery,
124
+ checkForExclamationDeletion,
125
+ exclamationPosition,
126
+ } = useBashHistorySelector();
127
+
128
+ // Memory type selector functionality
129
+ const {
130
+ showMemoryTypeSelector,
131
+ memoryMessage,
132
+ activateMemoryTypeSelector,
133
+ handleMemoryTypeSelect: handleMemoryTypeSelectorSelect,
134
+ handleCancelMemoryTypeSelect,
135
+ } = useMemoryTypeSelector();
136
+
137
+ // Input history functionality
138
+ const { resetHistoryNavigation, navigateHistory } = useInputHistory({
139
+ userInputHistory,
140
+ });
141
+
142
+ // Image management functionality (includes clipboard paste)
143
+ const { attachedImages, clearImages, handlePasteImage } =
144
+ useImageManager(insertTextAtCursor);
145
+
146
+ // Keyboard handling
147
+ const {
148
+ handleFileSelect,
149
+ handleCommandSelect,
150
+ handleBashHistorySelect,
151
+ handleBashHistoryExecute: keyboardHandleBashHistoryExecute,
152
+ handleMemoryTypeSelect,
153
+ } = useInputKeyboardHandler({
154
+ inputText,
155
+ setInputText,
156
+ cursorPosition,
157
+ setCursorPosition,
158
+ moveCursorLeft,
159
+ moveCursorRight,
160
+ moveCursorToStart,
161
+ moveCursorToEnd,
162
+ deleteCharAtCursor,
163
+ insertTextAtCursor,
164
+ clearInput,
165
+ resetHistoryNavigation,
166
+ navigateHistory,
167
+ handlePasteImage,
168
+ attachedImages,
169
+ clearImages,
170
+ showFileSelector,
171
+ activateFileSelector,
172
+ handleFileSelect: handleFileSelectorSelect,
173
+ handleCancelFileSelect,
174
+ updateSearchQuery,
175
+ checkForAtDeletion,
176
+ atPosition,
177
+ showCommandSelector,
178
+ activateCommandSelector,
179
+ handleCommandSelect: handleCommandSelectorSelect,
180
+ handleCommandInsert: handleCommandSelectorInsert,
181
+ handleCancelCommandSelect,
182
+ updateCommandSearchQuery,
183
+ checkForSlashDeletion,
184
+ slashPosition,
185
+ showBashHistorySelector,
186
+ activateBashHistorySelector,
187
+ handleBashHistorySelect: handleBashHistorySelectorSelect,
188
+ handleBashHistoryExecute,
189
+ handleCancelBashHistorySelect,
190
+ updateBashHistorySearchQuery,
191
+ checkForExclamationDeletion,
192
+ exclamationPosition,
193
+ showMemoryTypeSelector,
194
+ activateMemoryTypeSelector,
195
+ handleMemoryTypeSelect: handleMemoryTypeSelectorSelect,
196
+ showBashManager,
197
+ showMcpManager,
198
+ isCommandRunning,
199
+ isLoading,
200
+ sendMessage,
201
+ abortMessage,
202
+ saveMemory,
203
+ });
204
+
205
+ const isPlaceholder = !inputText;
206
+ const placeholderText = INPUT_PLACEHOLDER_TEXT;
207
+
208
+ // Create adapter function for CommandSelector
209
+ const handleCommandInsert = useCallback(
210
+ (command: string) => {
211
+ const result = handleCommandSelectorInsert(
212
+ command,
213
+ inputText,
214
+ cursorPosition,
215
+ );
216
+ setInputText(result.newInput);
217
+ setCursorPosition(result.newCursorPosition);
218
+ },
219
+ [
220
+ handleCommandSelectorInsert,
221
+ inputText,
222
+ cursorPosition,
223
+ setInputText,
224
+ setCursorPosition,
225
+ ],
226
+ );
227
+
228
+ // Split text into three parts: before cursor, cursor position, after cursor
229
+ const displayText = isPlaceholder ? placeholderText : inputText;
230
+ const beforeCursor = displayText.substring(0, cursorPosition);
231
+ const atCursor =
232
+ cursorPosition < displayText.length ? displayText[cursorPosition] : " ";
233
+ const afterCursor = displayText.substring(cursorPosition + 1);
234
+
235
+ // Always show cursor, allow user to continue input during loading
236
+ const shouldShowCursor = true;
237
+
238
+ return (
239
+ <Box flexDirection="column" width={"100%"}>
240
+ {showFileSelector && (
241
+ <FileSelector
242
+ files={filteredFiles}
243
+ searchQuery={searchQuery}
244
+ onSelect={handleFileSelect}
245
+ onCancel={handleCancelFileSelect}
246
+ />
247
+ )}
248
+
249
+ {showCommandSelector && (
250
+ <CommandSelector
251
+ searchQuery={commandSearchQuery}
252
+ onSelect={handleCommandSelect}
253
+ onInsert={handleCommandInsert}
254
+ onCancel={handleCancelCommandSelect}
255
+ commands={slashCommands}
256
+ />
257
+ )}
258
+
259
+ {showBashHistorySelector && (
260
+ <BashHistorySelector
261
+ searchQuery={bashHistorySearchQuery}
262
+ workdir={currentWorkdir}
263
+ onSelect={handleBashHistorySelect}
264
+ onExecute={keyboardHandleBashHistoryExecute}
265
+ onCancel={handleCancelBashHistorySelect}
266
+ />
267
+ )}
268
+
269
+ {showMemoryTypeSelector && (
270
+ <MemoryTypeSelector
271
+ message={memoryMessage}
272
+ onSelect={handleMemoryTypeSelect}
273
+ onCancel={handleCancelMemoryTypeSelect}
274
+ />
275
+ )}
276
+
277
+ {showBashManager && (
278
+ <BashShellManager onCancel={() => setShowBashManager(false)} />
279
+ )}
280
+
281
+ {showMcpManager && (
282
+ <McpManager
283
+ onCancel={() => setShowMcpManager(false)}
284
+ servers={mcpServers}
285
+ onConnectServer={connectMcpServer}
286
+ onDisconnectServer={disconnectMcpServer}
287
+ />
288
+ )}
289
+ {showBashManager || showMcpManager || (
290
+ <Box borderStyle="single" borderColor="gray" paddingX={1}>
291
+ <Box width="100%" flexDirection="row" justifyContent="space-between">
292
+ <Text color={isPlaceholder ? "gray" : "white"}>
293
+ {shouldShowCursor ? (
294
+ <>
295
+ {beforeCursor}
296
+ <Text backgroundColor="white" color="black">
297
+ {atCursor}
298
+ </Text>
299
+ {afterCursor}
300
+ </>
301
+ ) : (
302
+ displayText
303
+ )}
304
+ </Text>
305
+ </Box>
306
+ </Box>
307
+ )}
308
+ </Box>
309
+ );
310
+ };