aiexecode 1.0.157

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 (188) hide show
  1. package/LICENSE +68 -0
  2. package/README.md +347 -0
  3. package/config_template/mcp_config.json +3 -0
  4. package/config_template/package_name_store.json +5 -0
  5. package/config_template/settings.json +5 -0
  6. package/index.js +879 -0
  7. package/mcp-agent-lib/example/01-basic-usage.js +82 -0
  8. package/mcp-agent-lib/example/02-quick-start.js +52 -0
  9. package/mcp-agent-lib/example/03-http-server.js +76 -0
  10. package/mcp-agent-lib/example/04-multiple-servers.js +117 -0
  11. package/mcp-agent-lib/example/05-error-handling.js +116 -0
  12. package/mcp-agent-lib/example/06-resources-and-prompts.js +174 -0
  13. package/mcp-agent-lib/example/07-advanced-configuration.js +191 -0
  14. package/mcp-agent-lib/example/08-real-world-chatbot.js +331 -0
  15. package/mcp-agent-lib/example/README.md +346 -0
  16. package/mcp-agent-lib/index.js +19 -0
  17. package/mcp-agent-lib/init.sh +3 -0
  18. package/mcp-agent-lib/package-lock.json +1216 -0
  19. package/mcp-agent-lib/package.json +53 -0
  20. package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
  21. package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
  22. package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
  23. package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
  24. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
  25. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
  26. package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
  27. package/mcp-agent-lib/sampleMCPHost/index.js +386 -0
  28. package/mcp-agent-lib/sampleMCPHost/mcp_config.json +24 -0
  29. package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
  30. package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
  31. package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
  32. package/mcp-agent-lib/src/mcp_client.js +1860 -0
  33. package/mcp-agent-lib/src/mcp_message_logger.js +517 -0
  34. package/package.json +72 -0
  35. package/payload_viewer/out/404/index.html +1 -0
  36. package/payload_viewer/out/404.html +1 -0
  37. package/payload_viewer/out/_next/static/chunks/060f9a97930f3d04.js +1 -0
  38. package/payload_viewer/out/_next/static/chunks/103c802c8f4a5ea1.js +1 -0
  39. package/payload_viewer/out/_next/static/chunks/16474fd6c6910c45.js +1 -0
  40. package/payload_viewer/out/_next/static/chunks/17722e3ac4e00587.js +1 -0
  41. package/payload_viewer/out/_next/static/chunks/305b077a9873cf54.js +1 -0
  42. package/payload_viewer/out/_next/static/chunks/4c1d05c6741c2bdd.js +5 -0
  43. package/payload_viewer/out/_next/static/chunks/538cc02e54714b23.js +1 -0
  44. package/payload_viewer/out/_next/static/chunks/6251fa5907d2b226.js +5 -0
  45. package/payload_viewer/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  46. package/payload_viewer/out/_next/static/chunks/b6c0459f3789d25c.js +1 -0
  47. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  48. package/payload_viewer/out/_next/static/chunks/bd2dcf98c9b362f6.js +1 -0
  49. package/payload_viewer/out/_next/static/chunks/c8a542ae21335479.js +1 -0
  50. package/payload_viewer/out/_next/static/chunks/cdd12d5c1a5a6064.js +1 -0
  51. package/payload_viewer/out/_next/static/chunks/e411019f55d87c42.js +1 -0
  52. package/payload_viewer/out/_next/static/chunks/e60ef129113f6e24.js +1 -0
  53. package/payload_viewer/out/_next/static/chunks/f1ac9047ac4a3fde.js +1 -0
  54. package/payload_viewer/out/_next/static/chunks/turbopack-0ac29803ce3c3c7a.js +3 -0
  55. package/payload_viewer/out/_next/static/chunks/turbopack-89db4c64206a73e4.js +3 -0
  56. package/payload_viewer/out/_next/static/chunks/turbopack-a5b8235fa59d7119.js +3 -0
  57. package/payload_viewer/out/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  58. package/payload_viewer/out/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  59. package/payload_viewer/out/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  60. package/payload_viewer/out/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  61. package/payload_viewer/out/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  62. package/payload_viewer/out/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  63. package/payload_viewer/out/_next/static/media/favicon.0b3bf435.ico +0 -0
  64. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_buildManifest.js +14 -0
  65. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_clientMiddlewareManifest.json +1 -0
  66. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_ssgManifest.js +1 -0
  67. package/payload_viewer/out/favicon.ico +0 -0
  68. package/payload_viewer/out/file.svg +1 -0
  69. package/payload_viewer/out/globe.svg +1 -0
  70. package/payload_viewer/out/index.html +1 -0
  71. package/payload_viewer/out/index.txt +23 -0
  72. package/payload_viewer/out/next.svg +1 -0
  73. package/payload_viewer/out/vercel.svg +1 -0
  74. package/payload_viewer/out/window.svg +1 -0
  75. package/payload_viewer/web_server.js +861 -0
  76. package/prompts/completion_judge.txt +128 -0
  77. package/prompts/orchestrator.txt +1213 -0
  78. package/src/LLMClient/client.js +1375 -0
  79. package/src/LLMClient/converters/input-normalizer.js +238 -0
  80. package/src/LLMClient/converters/responses-to-claude.js +503 -0
  81. package/src/LLMClient/converters/responses-to-gemini.js +648 -0
  82. package/src/LLMClient/converters/responses-to-ollama.js +348 -0
  83. package/src/LLMClient/converters/responses-to-zai.js +667 -0
  84. package/src/LLMClient/errors.js +398 -0
  85. package/src/LLMClient/index.js +36 -0
  86. package/src/ai_based/completion_judge.js +421 -0
  87. package/src/ai_based/orchestrator.js +527 -0
  88. package/src/ai_based/pip_package_installer.js +173 -0
  89. package/src/ai_based/pip_package_lookup.js +197 -0
  90. package/src/cli/mcp_cli.js +70 -0
  91. package/src/cli/mcp_commands.js +255 -0
  92. package/src/commands/agents.js +18 -0
  93. package/src/commands/apikey.js +55 -0
  94. package/src/commands/bg.js +140 -0
  95. package/src/commands/commands.js +56 -0
  96. package/src/commands/debug.js +54 -0
  97. package/src/commands/exit.js +19 -0
  98. package/src/commands/help.js +35 -0
  99. package/src/commands/mcp.js +128 -0
  100. package/src/commands/model.js +176 -0
  101. package/src/commands/setup.js +13 -0
  102. package/src/commands/skills.js +51 -0
  103. package/src/commands/tools.js +165 -0
  104. package/src/commands/viewer.js +147 -0
  105. package/src/config/ai_models.js +312 -0
  106. package/src/config/config.js +10 -0
  107. package/src/config/constants.js +71 -0
  108. package/src/config/feature_flags.js +15 -0
  109. package/src/frontend/App.js +1263 -0
  110. package/src/frontend/README.md +81 -0
  111. package/src/frontend/components/AutocompleteMenu.js +47 -0
  112. package/src/frontend/components/BackgroundProcessList.js +175 -0
  113. package/src/frontend/components/BlankLine.js +62 -0
  114. package/src/frontend/components/ConversationItem.js +893 -0
  115. package/src/frontend/components/CurrentModelView.js +43 -0
  116. package/src/frontend/components/FileDiffViewer.js +616 -0
  117. package/src/frontend/components/Footer.js +25 -0
  118. package/src/frontend/components/Header.js +42 -0
  119. package/src/frontend/components/HelpView.js +154 -0
  120. package/src/frontend/components/Input.js +344 -0
  121. package/src/frontend/components/LoadingIndicator.js +31 -0
  122. package/src/frontend/components/ModelListView.js +49 -0
  123. package/src/frontend/components/ModelUpdatedView.js +22 -0
  124. package/src/frontend/components/SessionSpinner.js +66 -0
  125. package/src/frontend/components/SetupWizard.js +242 -0
  126. package/src/frontend/components/StreamOutput.js +34 -0
  127. package/src/frontend/components/TodoList.js +56 -0
  128. package/src/frontend/components/ToolApprovalPrompt.js +452 -0
  129. package/src/frontend/design/themeColors.js +42 -0
  130. package/src/frontend/hooks/useCompletion.js +84 -0
  131. package/src/frontend/hooks/useFileCompletion.js +467 -0
  132. package/src/frontend/hooks/useKeypress.js +145 -0
  133. package/src/frontend/index.js +65 -0
  134. package/src/frontend/utils/GridRenderer.js +140 -0
  135. package/src/frontend/utils/InlineFormatter.js +156 -0
  136. package/src/frontend/utils/diffUtils.js +235 -0
  137. package/src/frontend/utils/inputBuffer.js +441 -0
  138. package/src/frontend/utils/markdownParser.js +377 -0
  139. package/src/frontend/utils/outputRedirector.js +47 -0
  140. package/src/frontend/utils/renderInkComponent.js +42 -0
  141. package/src/frontend/utils/syntaxHighlighter.js +149 -0
  142. package/src/frontend/utils/toolUIFormatter.js +261 -0
  143. package/src/system/agents_loader.js +170 -0
  144. package/src/system/ai_request.js +737 -0
  145. package/src/system/background_process.js +317 -0
  146. package/src/system/code_executer.js +1233 -0
  147. package/src/system/command_loader.js +40 -0
  148. package/src/system/command_parser.js +133 -0
  149. package/src/system/conversation_state.js +265 -0
  150. package/src/system/conversation_trimmer.js +265 -0
  151. package/src/system/custom_command_loader.js +395 -0
  152. package/src/system/file_integrity.js +466 -0
  153. package/src/system/import_analyzer.py +174 -0
  154. package/src/system/log.js +82 -0
  155. package/src/system/mcp_integration.js +304 -0
  156. package/src/system/output_helper.js +89 -0
  157. package/src/system/session.js +1393 -0
  158. package/src/system/session_memory.js +481 -0
  159. package/src/system/skill_loader.js +324 -0
  160. package/src/system/system_info.js +483 -0
  161. package/src/system/tool_approval.js +160 -0
  162. package/src/system/tool_registry.js +184 -0
  163. package/src/system/ui_events.js +279 -0
  164. package/src/tools/code_editor.js +792 -0
  165. package/src/tools/file_reader.js +385 -0
  166. package/src/tools/glob.js +263 -0
  167. package/src/tools/response_message.js +30 -0
  168. package/src/tools/ripgrep.js +554 -0
  169. package/src/tools/skill_tool.js +122 -0
  170. package/src/tools/todo_write.js +182 -0
  171. package/src/tools/web_download.py +74 -0
  172. package/src/tools/web_downloader.js +83 -0
  173. package/src/util/clone.js +174 -0
  174. package/src/util/config.js +203 -0
  175. package/src/util/config_migration.js +174 -0
  176. package/src/util/debug_log.js +49 -0
  177. package/src/util/exit_handler.js +53 -0
  178. package/src/util/file_reference_parser.js +132 -0
  179. package/src/util/mcp_config_manager.js +159 -0
  180. package/src/util/output_formatter.js +50 -0
  181. package/src/util/path_helper.js +27 -0
  182. package/src/util/path_validator.js +178 -0
  183. package/src/util/prompt_loader.js +184 -0
  184. package/src/util/rag_helper.js +101 -0
  185. package/src/util/safe_fs.js +645 -0
  186. package/src/util/setup_wizard.js +62 -0
  187. package/src/util/text_formatter.js +33 -0
  188. package/src/util/version_check.js +116 -0
@@ -0,0 +1,452 @@
1
+ /**
2
+ * ToolApprovalPrompt - 도구 실행 승인 UI
3
+ */
4
+
5
+ import React, { useState, useEffect } from 'react';
6
+ import { Box, Text, useInput } from 'ink';
7
+ import { theme } from '../design/themeColors.js';
8
+ import { FileDiffViewer } from './FileDiffViewer.js';
9
+ import { common, createLowlight } from 'lowlight';
10
+ import { getToolDisplayName } from '../../system/tool_registry.js';
11
+ import { getFileSnapshot } from '../../system/file_integrity.js';
12
+ import { resolve } from 'path';
13
+ import { toDisplayPath } from '../../util/path_helper.js';
14
+ import { prepareEditReplaceDiff } from '../utils/diffUtils.js';
15
+
16
+ const lowlight = createLowlight(common);
17
+
18
+ export function ToolApprovalPrompt({ toolName, args, onDecision, mcpToolInfo = null }) {
19
+ const [selectedIndex, setSelectedIndex] = useState(0);
20
+
21
+ const choices = [
22
+ { label: '✓ Allow (this time only)', value: 'allow' },
23
+ { label: '⊚ Always Allow (for this session)', value: 'always' },
24
+ { label: '✗ Deny', value: 'deny' }
25
+ ];
26
+
27
+ useInput((input, key) => {
28
+ if (key.upArrow) {
29
+ setSelectedIndex(prev => (prev - 1 + choices.length) % choices.length);
30
+ } else if (key.downArrow) {
31
+ setSelectedIndex(prev => (prev + 1) % choices.length);
32
+ } else if (key.return) {
33
+ onDecision(choices[selectedIndex].value);
34
+ } else if (input === 'q' || key.escape) {
35
+ // q 또는 ESC 누르면 도구만 거부 (세션은 계속)
36
+ onDecision('deny');
37
+ }
38
+ });
39
+
40
+ const displayName = getToolDisplayName(toolName);
41
+
42
+ return React.createElement(Box, {
43
+ flexDirection: 'column',
44
+ borderStyle: 'round',
45
+ borderColor: theme.brand.dark,
46
+ paddingX: 1,
47
+ flexGrow: 1
48
+ },
49
+ // Tool info
50
+ React.createElement(Box, { flexDirection: 'column', marginBottom: 0, marginTop: 0 },
51
+ React.createElement(Box, { marginBottom: 0 },
52
+ React.createElement(Text, { bold: true, color: theme.brand.light }, 'Tool '),
53
+ React.createElement(Text, { color: 'white' }, displayName)
54
+ ),
55
+ renderToolArgs(toolName, args, mcpToolInfo)
56
+ ),
57
+
58
+ // Choices
59
+ React.createElement(Box, { flexDirection: 'column' },
60
+ // React.createElement(Text, { dimColor: true }, 'Select an option:'),
61
+ ...choices.map((choice, index) =>
62
+ React.createElement(Box, { key: index, marginLeft: 1 },
63
+ React.createElement(Text, {
64
+ color: index === selectedIndex ? theme.brand.light : 'white',
65
+ bold: index === selectedIndex
66
+ }, index === selectedIndex ? '› ' : ' '),
67
+ React.createElement(Text, {
68
+ color: index === selectedIndex ? theme.brand.light : 'white'
69
+ }, choice.label)
70
+ )
71
+ )
72
+ ),
73
+
74
+ // Footer
75
+ React.createElement(Box, { marginTop: 1 },
76
+ React.createElement(Text, { dimColor: true }, '↑↓: Navigate Enter: Confirm q/Esc: Deny')
77
+ )
78
+ );
79
+ }
80
+
81
+ // Helper functions for syntax highlighting (copied from FileDiffViewer)
82
+ const syntaxTheme = {
83
+ keyword: 'magenta',
84
+ string: 'green',
85
+ number: 'cyan',
86
+ comment: 'gray',
87
+ function: 'blue',
88
+ variable: 'white',
89
+ operator: 'yellow',
90
+ default: 'white'
91
+ };
92
+
93
+ function getColorForClass(className) {
94
+ if (className.includes('keyword')) return syntaxTheme.keyword;
95
+ if (className.includes('string')) return syntaxTheme.string;
96
+ if (className.includes('number')) return syntaxTheme.number;
97
+ if (className.includes('comment')) return syntaxTheme.comment;
98
+ if (className.includes('function')) return syntaxTheme.function;
99
+ if (className.includes('variable')) return syntaxTheme.variable;
100
+ if (className.includes('operator')) return syntaxTheme.operator;
101
+ return syntaxTheme.default;
102
+ }
103
+
104
+ function renderHastNode(node, inheritedColor) {
105
+ if (node.type === 'text') {
106
+ const color = inheritedColor || syntaxTheme.default;
107
+ return React.createElement(Text, { color }, node.value);
108
+ }
109
+
110
+ if (node.type === 'element') {
111
+ const nodeClasses = node.properties?.className || [];
112
+ let elementColor = inheritedColor;
113
+
114
+ for (const className of nodeClasses) {
115
+ const color = getColorForClass(className);
116
+ if (color) {
117
+ elementColor = color;
118
+ break;
119
+ }
120
+ }
121
+
122
+ const children = node.children?.map((child, index) =>
123
+ React.createElement(React.Fragment, { key: index },
124
+ renderHastNode(child, elementColor)
125
+ )
126
+ );
127
+
128
+ return React.createElement(React.Fragment, null, children);
129
+ }
130
+
131
+ if (node.type === 'root') {
132
+ if (!node.children || node.children.length === 0) {
133
+ return null;
134
+ }
135
+
136
+ return node.children?.map((child, index) =>
137
+ React.createElement(React.Fragment, { key: index },
138
+ renderHastNode(child, inheritedColor)
139
+ )
140
+ );
141
+ }
142
+
143
+ return null;
144
+ }
145
+
146
+ function highlightCode(code, language) {
147
+ try {
148
+ const highlighted = !language || !lowlight.registered(language)
149
+ ? lowlight.highlightAuto(code)
150
+ : lowlight.highlight(language, code);
151
+
152
+ const rendered = renderHastNode(highlighted, undefined);
153
+ return rendered !== null ? rendered : code;
154
+ } catch (error) {
155
+ return code;
156
+ }
157
+ }
158
+
159
+ function detectLanguage(filePath) {
160
+ const ext = filePath.split('.').pop()?.toLowerCase();
161
+ const langMap = {
162
+ 'js': 'javascript',
163
+ 'jsx': 'javascript',
164
+ 'ts': 'typescript',
165
+ 'tsx': 'typescript',
166
+ 'py': 'python',
167
+ 'java': 'java',
168
+ 'c': 'c',
169
+ 'cpp': 'cpp',
170
+ 'h': 'c',
171
+ 'hpp': 'cpp',
172
+ 'css': 'css',
173
+ 'html': 'html',
174
+ 'json': 'json',
175
+ 'xml': 'xml',
176
+ 'yaml': 'yaml',
177
+ 'yml': 'yaml',
178
+ 'sh': 'bash',
179
+ 'bash': 'bash',
180
+ 'md': 'markdown',
181
+ 'go': 'go',
182
+ 'rs': 'rust',
183
+ 'rb': 'ruby',
184
+ 'php': 'php',
185
+ 'sql': 'sql'
186
+ };
187
+ return langMap[ext] || null;
188
+ }
189
+
190
+ function renderToolArgs(toolName, args, mcpToolInfo = null) {
191
+ // MCP Tool인 경우
192
+ if (mcpToolInfo) {
193
+ const jsonString = JSON.stringify(args, null, 2);
194
+ const jsonLines = jsonString.split('\n');
195
+
196
+ return React.createElement(Box, { flexDirection: 'column', marginTop: 0 },
197
+ // Server info
198
+ React.createElement(Box, { marginBottom: 0 },
199
+ React.createElement(Text, { bold: true, color: theme.brand.light }, 'Server '),
200
+ React.createElement(Text, { color: 'white' }, mcpToolInfo.server)
201
+ ),
202
+ // Description (박스 안에 담기)
203
+ mcpToolInfo.description && React.createElement(Box, { flexDirection: 'column', marginTop: 0 },
204
+ React.createElement(Text, { bold: true, color: theme.brand.light }, 'Description'),
205
+ React.createElement(Box, {
206
+ borderStyle: 'single',
207
+ borderColor: '#444444',
208
+ paddingX: 1,
209
+ marginTop: 0
210
+ },
211
+ React.createElement(Text, { color: 'white' }, mcpToolInfo.description)
212
+ )
213
+ ),
214
+ // Arguments (JSON 하이라이팅)
215
+ React.createElement(Box, { flexDirection: 'column', marginTop: 0 },
216
+ React.createElement(Text, { bold: true, color: theme.brand.light }, 'Arguments'),
217
+ React.createElement(Box, {
218
+ borderStyle: 'single',
219
+ borderColor: '#444444',
220
+ paddingX: 1,
221
+ marginTop: 0,
222
+ flexDirection: 'column'
223
+ },
224
+ ...jsonLines.map((line, index) =>
225
+ React.createElement(Box, { key: index },
226
+ React.createElement(Text, {},
227
+ highlightCode(line, 'json')
228
+ )
229
+ )
230
+ )
231
+ )
232
+ )
233
+ );
234
+ }
235
+
236
+ if (toolName === 'bash') {
237
+ const script = args.script || '';
238
+ const lines = script.split('\n');
239
+
240
+ return React.createElement(Box, { flexDirection: 'column', marginTop: 0 },
241
+ React.createElement(Box, {
242
+ borderStyle: 'single',
243
+ borderColor: '#444444',
244
+ paddingX: 1,
245
+ marginTop: 0,
246
+ flexDirection: 'column'
247
+ },
248
+ ...lines.map((line, index) =>
249
+ React.createElement(Box, { key: index },
250
+ React.createElement(Text, { color: 'gray', dimColor: true },
251
+ String(index + 1).padStart(3, ' ') + ' '
252
+ ),
253
+ React.createElement(Text, {},
254
+ highlightCode(line, 'bash')
255
+ )
256
+ )
257
+ )
258
+ )
259
+ );
260
+ }
261
+
262
+ if (toolName === 'run_python_code') {
263
+ const code = args.code || '';
264
+ const lines = code.split('\n');
265
+
266
+ return React.createElement(Box, { flexDirection: 'column', marginTop: 0 },
267
+ React.createElement(Box, {
268
+ borderStyle: 'single',
269
+ borderColor: '#444444',
270
+ paddingX: 1,
271
+ marginTop: 0,
272
+ flexDirection: 'column'
273
+ },
274
+ ...lines.map((line, index) =>
275
+ React.createElement(Box, { key: index },
276
+ React.createElement(Text, { color: 'gray', dimColor: true },
277
+ String(index + 1).padStart(3, ' ') + ' '
278
+ ),
279
+ React.createElement(Text, {},
280
+ highlightCode(line, 'python')
281
+ )
282
+ )
283
+ )
284
+ )
285
+ );
286
+ }
287
+
288
+ if (toolName === 'write_file') {
289
+ const content = args.content || '';
290
+ const language = detectLanguage(args.file_path || '');
291
+ const lines = content.split('\n');
292
+
293
+ return React.createElement(Box, { flexDirection: 'column', marginTop: 0 },
294
+ React.createElement(Box, {},
295
+ React.createElement(Text, { bold: true, color: theme.brand.light }, 'File '),
296
+ React.createElement(Text, { color: 'white' }, toDisplayPath(args.file_path))
297
+ ),
298
+ React.createElement(Box, {
299
+ borderStyle: 'single',
300
+ borderColor: '#444444',
301
+ paddingX: 1,
302
+ marginTop: 0,
303
+ flexDirection: 'column'
304
+ },
305
+ ...lines.map((line, index) =>
306
+ React.createElement(Box, { key: index },
307
+ React.createElement(Text, { color: 'gray', dimColor: true },
308
+ String(index + 1).padStart(3, ' ') + ' '
309
+ ),
310
+ React.createElement(Text, {},
311
+ highlightCode(line, language)
312
+ )
313
+ )
314
+ )
315
+ )
316
+ );
317
+ }
318
+
319
+ if (toolName === 'edit_file_range') {
320
+ const [oldContent, setOldContent] = useState(null);
321
+ const [contextBefore, setContextBefore] = useState([]);
322
+ const [contextAfter, setContextAfter] = useState([]);
323
+ const [contextStartLine, setContextStartLine] = useState(null);
324
+
325
+ useEffect(() => {
326
+ function loadFileContent() {
327
+ try {
328
+ // 스냅샷에서 파일 내용 조회 (절대 경로로 변환)
329
+ const absolutePath = resolve(args.file_path);
330
+ const snapshot = getFileSnapshot(absolutePath);
331
+ const content = snapshot?.content || '';
332
+
333
+ if (!content) {
334
+ setOldContent('');
335
+ setContextBefore([]);
336
+ setContextAfter([]);
337
+ setContextStartLine(args.start_line);
338
+ return;
339
+ }
340
+
341
+ const lines = content.split('\n');
342
+
343
+ // Handle files without trailing newline
344
+ const actualLines = content === '' ? [] :
345
+ (content.endsWith('\n') ? lines.slice(0, -1) : lines);
346
+
347
+ // Extract old content (the lines being edited)
348
+ const old = actualLines.slice(args.start_line - 1, args.end_line).join('\n');
349
+ setOldContent(old);
350
+
351
+ // Extract context (2 lines before and after)
352
+ const contextLines = 2;
353
+ const contextStartIdx = Math.max(0, args.start_line - 1 - contextLines);
354
+ const contextEndIdx = Math.min(actualLines.length, args.end_line + contextLines);
355
+
356
+ const ctxBefore = actualLines.slice(contextStartIdx, args.start_line - 1);
357
+ const ctxAfter = actualLines.slice(args.end_line, contextEndIdx);
358
+
359
+ setContextBefore(ctxBefore);
360
+ setContextAfter(ctxAfter);
361
+ setContextStartLine(contextStartIdx + 1);
362
+ } catch (error) {
363
+ // File might not exist yet or other error
364
+ setOldContent('');
365
+ setContextBefore([]);
366
+ setContextAfter([]);
367
+ }
368
+ }
369
+
370
+ loadFileContent();
371
+ }, [args.file_path, args.start_line, args.end_line]);
372
+
373
+ return React.createElement(Box, { flexDirection: 'column', marginTop: 0 },
374
+ React.createElement(Box, { marginBottom: 0 },
375
+ React.createElement(Text, { bold: true, color: theme.brand.light }, 'File '),
376
+ React.createElement(Text, { color: 'white' }, toDisplayPath(args.file_path))
377
+ ),
378
+ oldContent !== null ? React.createElement(FileDiffViewer, {
379
+ filePath: args.file_path,
380
+ startLine: args.start_line,
381
+ endLine: args.end_line,
382
+ oldContent: oldContent,
383
+ newContent: args.new_content || '',
384
+ contextBefore: contextBefore,
385
+ contextAfter: contextAfter,
386
+ contextStartLine: contextStartLine,
387
+ showHeader: false
388
+ }) : React.createElement(Text, { color: 'gray' }, 'Loading...')
389
+ );
390
+ }
391
+
392
+ if (toolName === 'edit_file_replace') {
393
+ const [diffData, setDiffData] = useState(null);
394
+
395
+ useEffect(() => {
396
+ function loadFileContent() {
397
+ // 스냅샷에서 파일 내용 조회 (절대 경로로 변환)
398
+ const absolutePath = resolve(args.file_path);
399
+ const snapshot = getFileSnapshot(absolutePath);
400
+ const content = snapshot?.content || '';
401
+
402
+ // Use shared diff preparation logic
403
+ const result = prepareEditReplaceDiff(
404
+ args.old_string || '',
405
+ args.new_string || '',
406
+ content,
407
+ false
408
+ );
409
+
410
+ setDiffData(result);
411
+ }
412
+
413
+ loadFileContent();
414
+ }, [args.file_path, args.old_string, args.new_string]);
415
+
416
+ return React.createElement(Box, { flexDirection: 'column', marginTop: 0 },
417
+ React.createElement(Box, { marginBottom: 0 },
418
+ React.createElement(Text, { bold: true, color: theme.brand.light }, 'File '),
419
+ React.createElement(Text, { color: 'white' }, toDisplayPath(args.file_path))
420
+ ),
421
+ diffData ? (
422
+ diffData.error ?
423
+ React.createElement(Text, { color: 'red' }, `Error: ${diffData.error}`) :
424
+ React.createElement(FileDiffViewer, {
425
+ filePath: args.file_path,
426
+ startLine: diffData.startLine,
427
+ endLine: diffData.endLine,
428
+ oldContent: diffData.oldContent,
429
+ newContent: diffData.newContent,
430
+ contextBefore: diffData.contextBefore,
431
+ contextAfter: diffData.contextAfter,
432
+ contextStartLine: diffData.contextStartLine,
433
+ isReplaceMode: true,
434
+ showHeader: false
435
+ })
436
+ ) : React.createElement(Text, { color: 'gray' }, 'Loading...')
437
+ );
438
+ }
439
+
440
+ // 기타 도구
441
+ return React.createElement(Box, { flexDirection: 'column', marginTop: 0 },
442
+ React.createElement(Text, { bold: true, color: theme.brand.light }, 'Arguments'),
443
+ React.createElement(Box, {
444
+ borderStyle: 'single',
445
+ borderColor: 'gray',
446
+ paddingX: 1,
447
+ marginTop: 0
448
+ },
449
+ React.createElement(Text, { color: 'white' }, JSON.stringify(args, null, 2))
450
+ )
451
+ );
452
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Theme Colors - Color definitions for theming
3
+ */
4
+
5
+ const defaultTheme = {
6
+ text: {
7
+ primary: '#E0E0E0',
8
+ secondary: '#888888',
9
+ accent: '#47c9a0',
10
+ link: '#47c9a0',
11
+ },
12
+ background: {
13
+ default: '#1E1E1E',
14
+ highlight: '#2D2D2D',
15
+ },
16
+ border: {
17
+ default: '#333333',
18
+ focused: '#333333',
19
+ },
20
+ ui: {
21
+ symbol: '#666666',
22
+ gradient: ['#00D9FF', '#7B68EE'],
23
+ },
24
+ status: {
25
+ error: '#FF0055',
26
+ warning: '#FFAA00',
27
+ success: '#00FF88',
28
+ info: '#00A8FF',
29
+ },
30
+ brand: {
31
+ dark: '#113429',
32
+ light: '#47c9a0',
33
+ },
34
+ };
35
+
36
+ const currentTheme = defaultTheme;
37
+
38
+ export const theme = new Proxy({}, {
39
+ get(target, prop) {
40
+ return currentTheme[prop];
41
+ }
42
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Autocompletion hook for slash commands
3
+ */
4
+
5
+ import { useState, useEffect, useCallback, useRef } from 'react';
6
+
7
+ export function useCompletion(buffer, commands = []) {
8
+ const [suggestions, setSuggestions] = useState([]);
9
+ const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(-1);
10
+ const [showSuggestions, setShowSuggestions] = useState(false);
11
+ const lastTextRef = useRef('');
12
+
13
+ useEffect(() => {
14
+ const text = buffer.text;
15
+
16
+ // Skip if text hasn't changed
17
+ if (text === lastTextRef.current) {
18
+ return;
19
+ }
20
+ lastTextRef.current = text;
21
+
22
+ // Check if we should show slash command completions
23
+ if (text.startsWith('/') && !text.includes('\n')) {
24
+ const query = text.slice(1).toLowerCase();
25
+ const matches = commands
26
+ .filter(cmd => cmd.name.toLowerCase().startsWith(query))
27
+ .map(cmd => ({
28
+ value: `/${cmd.name}`,
29
+ description: cmd.description || ''
30
+ }));
31
+
32
+ // Only update if suggestions actually changed
33
+ if (JSON.stringify(matches) !== JSON.stringify(suggestions)) {
34
+ setSuggestions(matches);
35
+ setShowSuggestions(matches.length > 0);
36
+ setActiveSuggestionIndex(matches.length > 0 ? 0 : -1);
37
+ }
38
+ } else if (showSuggestions) {
39
+ // Only clear if we're currently showing suggestions
40
+ setSuggestions([]);
41
+ setShowSuggestions(false);
42
+ setActiveSuggestionIndex(-1);
43
+ }
44
+ }, [buffer.text, commands, suggestions, showSuggestions]);
45
+
46
+ const navigateUp = useCallback(() => {
47
+ setActiveSuggestionIndex(prev =>
48
+ prev > 0 ? prev - 1 : suggestions.length - 1
49
+ );
50
+ }, [suggestions.length]);
51
+
52
+ const navigateDown = useCallback(() => {
53
+ setActiveSuggestionIndex(prev =>
54
+ prev < suggestions.length - 1 ? prev + 1 : 0
55
+ );
56
+ }, [suggestions.length]);
57
+
58
+ const handleAutocomplete = useCallback((index) => {
59
+ if (index >= 0 && index < suggestions.length) {
60
+ buffer.setText(suggestions[index].value + ' ');
61
+ // 커서를 텍스트의 가장 오른쪽으로 이동
62
+ buffer.move('end');
63
+ setSuggestions([]);
64
+ setShowSuggestions(false);
65
+ setActiveSuggestionIndex(-1);
66
+ }
67
+ }, [buffer, suggestions]);
68
+
69
+ const resetCompletionState = useCallback(() => {
70
+ setSuggestions([]);
71
+ setShowSuggestions(false);
72
+ setActiveSuggestionIndex(-1);
73
+ }, []);
74
+
75
+ return {
76
+ suggestions,
77
+ activeSuggestionIndex,
78
+ showSuggestions,
79
+ navigateUp,
80
+ navigateDown,
81
+ handleAutocomplete,
82
+ resetCompletionState
83
+ };
84
+ }