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,42 @@
1
+ /**
2
+ * Header component with ASCII art logo
3
+ */
4
+
5
+ import React from 'react';
6
+ import { Box, Text } from 'ink';
7
+ import Gradient from 'ink-gradient';
8
+ import { theme } from '../design/themeColors.js';
9
+
10
+ // const ASCII_LOGO = `
11
+ // d8888 8888888 8888888888 Y88b d88P 8888888888 888
12
+ // d88888 888 888 Y88b d88P 888 888
13
+ // d88P888 888 888 Y88o88P 888 888
14
+ // d88P 888 888 8888888 Y888P 8888888 .d8888b .d88b. .d88888 .d88b.
15
+ // d88P 888 888 888 d888b 888 d88P" d88""88b d88" 888 d8P Y8b
16
+ // d88P 888 888 888 d88888b 888 888 888 888 888 888 88888888
17
+ // d8888888888 888 888 d88P Y88b 888 Y88b. Y88..88P Y88b 888 Y8b.
18
+ // d88P 888 8888888 8888888888 d88P Y88b 8888888888 "Y8888P "Y88P" "Y88888 "Y8888
19
+ // `;
20
+
21
+ export function Header({ version = '1.0.0', updateInfo = null }) {
22
+ const ASCII_LOGO = `
23
+ ▞▀▖▜▘▛▀▘▌ ▌▛▀▘ ▌
24
+ ▙▄▌▐ ▙▄ ▝▞ ▙▄ ▞▀▖▞▀▖▞▀▌▞▀▖
25
+ ▌ ▌▐ ▌ ▞▝▖▌ ▌ ▖▌ ▌▌ ▌▛▀
26
+ ▘ ▘▀▘▀▀▘▘ ▘▀▀▘▝▀ ▝▀ ▝▀▘▝▀▘
27
+ https://cokac.com
28
+ `.split('\n').map(line => line.trim()).join("\n");
29
+ return React.createElement(Box, { flexDirection: "column", marginBottom: 1, marginLeft: 2 },
30
+ React.createElement(Text, { color: theme.brand.light }, ASCII_LOGO),
31
+ React.createElement(Box, { justifyContent: "flex-left" },
32
+ React.createElement(Text, { color: theme.text.secondary }, `AIEXEcode v${version}`),
33
+ updateInfo && updateInfo.updateAvailable && React.createElement(Text, null,
34
+ React.createElement(Text, { color: '#666666' }, ' → '),
35
+ React.createElement(Text, { color: theme.status.warning }, `v${updateInfo.remoteVersion}`),
36
+ React.createElement(Text, null, ' available ('),
37
+ React.createElement(Text, { color: '#FFD700' }, 'npm install aiexecode -g'),
38
+ React.createElement(Text, null, ')')
39
+ )
40
+ )
41
+ );
42
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Help View Component - Ink-based UI for displaying available commands
3
+ */
4
+
5
+ import React from 'react';
6
+ import { Box, Text } from 'ink';
7
+
8
+ export function HelpView({ commands, customCommands = [], skills = [] }) {
9
+ // 커맨드를 카테고리별로 분류
10
+ const aiCommands = commands.filter(cmd =>
11
+ ['model', 'apikey'].includes(cmd.name)
12
+ );
13
+
14
+ const sessionCommands = commands.filter(cmd =>
15
+ ['exit'].includes(cmd.name)
16
+ );
17
+
18
+ const otherCommands = commands.filter(cmd =>
19
+ !aiCommands.includes(cmd) && !sessionCommands.includes(cmd)
20
+ );
21
+
22
+ const renderCommandGroup = (title, commandList) => {
23
+ if (commandList.length === 0) return null;
24
+
25
+ return React.createElement(Box, {
26
+ flexDirection: 'column',
27
+ marginBottom: 1
28
+ },
29
+ React.createElement(Text, {
30
+ bold: true,
31
+ color: 'cyan'
32
+ }, title),
33
+
34
+ commandList.map(cmd =>
35
+ React.createElement(Box, {
36
+ key: cmd.name,
37
+ flexDirection: 'row',
38
+ marginLeft: 2
39
+ },
40
+ React.createElement(Text, { color: 'white', bold: true }, cmd.usage),
41
+ React.createElement(Text, { color: 'gray' }, ' ' + cmd.description)
42
+ )
43
+ )
44
+ );
45
+ };
46
+
47
+ // 커스텀 커맨드 렌더링
48
+ const renderCustomCommands = () => {
49
+ if (customCommands.length === 0) return null;
50
+
51
+ return React.createElement(Box, {
52
+ flexDirection: 'column',
53
+ marginBottom: 1
54
+ },
55
+ React.createElement(Text, {
56
+ bold: true,
57
+ color: 'cyan'
58
+ }, 'Custom Commands'),
59
+
60
+ customCommands.map(cmd =>
61
+ React.createElement(Box, {
62
+ key: cmd.name,
63
+ flexDirection: 'row',
64
+ marginLeft: 2
65
+ },
66
+ React.createElement(Text, { color: 'white', bold: true },
67
+ `/${cmd.name}${cmd.argumentHint ? ' ' + cmd.argumentHint : ''}`
68
+ ),
69
+ React.createElement(Text, { color: 'gray', dimColor: true },
70
+ ` (${cmd.source})`
71
+ ),
72
+ cmd.description && React.createElement(Text, { color: 'gray' },
73
+ ' ' + cmd.description.substring(0, 50) + (cmd.description.length > 50 ? '...' : '')
74
+ )
75
+ )
76
+ )
77
+ );
78
+ };
79
+
80
+ // 스킬 렌더링
81
+ const renderSkills = () => {
82
+ if (skills.length === 0) return null;
83
+
84
+ return React.createElement(Box, {
85
+ flexDirection: 'column',
86
+ marginBottom: 1
87
+ },
88
+ React.createElement(Text, {
89
+ bold: true,
90
+ color: 'cyan'
91
+ }, 'Skills'),
92
+
93
+ skills.map(skill =>
94
+ React.createElement(Box, {
95
+ key: skill.name,
96
+ flexDirection: 'row',
97
+ marginLeft: 2
98
+ },
99
+ React.createElement(Text, { color: 'white', bold: true }, `/${skill.name}`),
100
+ React.createElement(Text, { color: 'gray', dimColor: true },
101
+ ` (${skill.source})`
102
+ ),
103
+ skill.description && React.createElement(Text, { color: 'gray' },
104
+ ' ' + skill.description.substring(0, 50) + (skill.description.length > 50 ? '...' : '')
105
+ )
106
+ )
107
+ )
108
+ );
109
+ };
110
+
111
+ return React.createElement(Box, {
112
+ flexDirection: 'column',
113
+ paddingY: 1
114
+ },
115
+ // Header
116
+ React.createElement(Text, {
117
+ bold: true,
118
+ color: 'whiteBright'
119
+ }, 'Available Commands'),
120
+
121
+ React.createElement(Text, null),
122
+
123
+ // AI Configuration Commands
124
+ renderCommandGroup('AI Configuration', aiCommands),
125
+
126
+ // Session Commands
127
+ renderCommandGroup('Session', sessionCommands),
128
+
129
+ // Other Commands
130
+ renderCommandGroup('Other', otherCommands),
131
+
132
+ // Custom Commands
133
+ renderCustomCommands(),
134
+
135
+ // Skills
136
+ renderSkills(),
137
+
138
+ // Footer
139
+ React.createElement(Box, {
140
+ flexDirection: 'column',
141
+ marginTop: 1
142
+ },
143
+ React.createElement(Text, {
144
+ dimColor: true
145
+ }, 'Type any command followed by arguments to execute'),
146
+ React.createElement(Text, {
147
+ dimColor: true
148
+ }, 'Custom commands: ~/.aiexe/commands/ or .aiexe/commands/'),
149
+ React.createElement(Text, {
150
+ dimColor: true
151
+ }, 'Skills: ~/.aiexe/skills/ or .aiexe/skills/')
152
+ )
153
+ );
154
+ }
@@ -0,0 +1,344 @@
1
+ /**
2
+ * Advanced Input component with multi-line support
3
+ */
4
+
5
+ import React, { useCallback, useState, useRef, useEffect, useMemo } from 'react';
6
+ import { Box, Text } from 'ink';
7
+ import { theme } from '../design/themeColors.js';
8
+ import { useKeypress, keyMatchers, Command } from '../hooks/useKeypress.js';
9
+ import { useCompletion } from '../hooks/useCompletion.js';
10
+ import { useFileCompletion } from '../hooks/useFileCompletion.js';
11
+ import { AutocompleteMenu } from './AutocompleteMenu.js';
12
+ import { cpSlice, cpLen } from '../utils/inputBuffer.js';
13
+ import { uiEvents } from '../../system/ui_events.js';
14
+ import chalk from 'chalk';
15
+ import stringWidth from 'string-width';
16
+
17
+ // 20개의 placeholder 메시지
18
+ const PLACEHOLDER_MESSAGES = [
19
+ ' Fix all bugs in the codebase',
20
+ ' Add unit tests for the API',
21
+ ' Refactor the authentication module',
22
+ ' Optimize database queries',
23
+ ' Implement user profile page',
24
+ ' Add error handling to services',
25
+ ' Write documentation for utils',
26
+ ' Create REST API endpoints',
27
+ ' Set up CI/CD pipeline',
28
+ ' Improve code performance',
29
+ ' Add input validation',
30
+ ' Implement caching layer',
31
+ ' Fix linting errors',
32
+ ' Add logging functionality',
33
+ ' Create data migration scripts',
34
+ ' Implement search feature',
35
+ ' Add authentication middleware',
36
+ ' Optimize bundle size',
37
+ ' Update dependencies',
38
+ ' Add responsive design'
39
+ ];
40
+
41
+ // 무작위 placeholder 선택 함수
42
+ function getRandomPlaceholder() {
43
+ return PLACEHOLDER_MESSAGES[Math.floor(Math.random() * PLACEHOLDER_MESSAGES.length)];
44
+ }
45
+
46
+ function InputPromptComponent({ buffer, onSubmit, onClearScreen, onExit, commands = [], placeholder, focus = true, isSessionRunning = false }) {
47
+ const [isExiting, setIsExiting] = useState(false);
48
+
49
+ // placeholder가 제공되지 않으면 무작위로 선택 (컴포넌트 마운트 시 한 번만)
50
+ const defaultPlaceholder = useMemo(() => placeholder || getRandomPlaceholder(), []);
51
+
52
+ const commandCompletionRaw = useCompletion(buffer, commands);
53
+ const fileCompletionRaw = useFileCompletion(buffer);
54
+
55
+ // Stabilize completion objects to prevent handleInput from being recreated
56
+ const commandCompletion = useMemo(() => commandCompletionRaw, [
57
+ commandCompletionRaw.showSuggestions,
58
+ commandCompletionRaw.suggestions.length,
59
+ commandCompletionRaw.activeSuggestionIndex,
60
+ commandCompletionRaw.handleAutocomplete,
61
+ commandCompletionRaw.navigateUp,
62
+ commandCompletionRaw.navigateDown
63
+ ]);
64
+
65
+ const fileCompletion = useMemo(() => fileCompletionRaw, [
66
+ fileCompletionRaw.showSuggestions,
67
+ fileCompletionRaw.suggestions.length,
68
+ fileCompletionRaw.activeSuggestionIndex,
69
+ fileCompletionRaw.handleAutocomplete,
70
+ fileCompletionRaw.navigateUp,
71
+ fileCompletionRaw.navigateDown
72
+ ]);
73
+
74
+ // 파일 자동완성 우선, 그 다음 명령어 자동완성
75
+ const completion = fileCompletion.showSuggestions ? fileCompletion : commandCompletion;
76
+
77
+
78
+ const handleSubmitAndClear = useCallback((submittedValue) => {
79
+ buffer.setText('');
80
+ onSubmit(submittedValue);
81
+ }, [onSubmit, buffer]);
82
+
83
+ const handleInput = useCallback((key) => {
84
+ if (!focus) return;
85
+
86
+ // Block all input if exiting
87
+ if (isExiting) return;
88
+
89
+ // PRIORITY 1: Handle paste events
90
+ if (key.paste) {
91
+ buffer.handleInput(key);
92
+ return;
93
+ }
94
+
95
+ // PRIORITY 2: Exit commands
96
+ if (keyMatchers[Command.QUIT](key)) {
97
+ // Direct exit without confirmation
98
+ setIsExiting(true);
99
+ if (onExit) {
100
+ onExit();
101
+ }
102
+ return;
103
+ }
104
+
105
+ if (keyMatchers[Command.EXIT](key)) {
106
+ if (buffer.text.length > 0) return;
107
+ // Direct exit
108
+ setIsExiting(true);
109
+ if (onExit) {
110
+ onExit();
111
+ }
112
+ return;
113
+ }
114
+
115
+ // PRIORITY 4: Escape handling
116
+ if (keyMatchers[Command.ESCAPE](key)) {
117
+ // 1순위: 자동완성 목록이 열려있으면 목록만 닫기
118
+ if (completion.showSuggestions) {
119
+ completion.resetCompletionState();
120
+ return;
121
+ }
122
+
123
+ // 2순위: 입력 텍스트가 있으면 지우기
124
+ if (buffer.text !== '') {
125
+ buffer.setText('');
126
+ return;
127
+ }
128
+
129
+ // 3순위: 세션이 실행 중이면 중단 요청
130
+ if (isSessionRunning) {
131
+ // 중단 요청 전송
132
+ uiEvents.requestSessionInterrupt();
133
+ return;
134
+ }
135
+
136
+ // 4순위: 아무것도 없으면 무시
137
+ return;
138
+ }
139
+
140
+ // PRIORITY 5: Clear screen
141
+ if (keyMatchers[Command.CLEAR_SCREEN](key)) {
142
+ onClearScreen();
143
+ return;
144
+ }
145
+
146
+ // PRIORITY 6: Submit handling
147
+ if (keyMatchers[Command.SUBMIT](key)) {
148
+ // Block submission if session is running
149
+ if (isSessionRunning) {
150
+ return;
151
+ }
152
+
153
+ // If suggestions are shown, accept the active suggestion
154
+ if (completion.showSuggestions && completion.suggestions.length > 0) {
155
+ const targetIndex = completion.activeSuggestionIndex === -1 ? 0 : completion.activeSuggestionIndex;
156
+ const suggestion = completion.suggestions[targetIndex];
157
+
158
+ // 파일 자동완성인 경우 (@로 시작) 완성만 하고 submit하지 않음
159
+ if (suggestion.value.startsWith('@')) {
160
+ completion.handleAutocomplete(targetIndex);
161
+ return;
162
+ }
163
+
164
+ // 명령어 자동완성인 경우 완성 후 바로 실행
165
+ completion.handleAutocomplete(targetIndex);
166
+ const completedCommand = suggestion.value;
167
+ handleSubmitAndClear(completedCommand);
168
+ return;
169
+ }
170
+
171
+ if (buffer.text.trim()) {
172
+ const [row, col] = buffer.cursor;
173
+ const line = buffer.lines[row];
174
+ const charBefore = col > 0 ? cpSlice(line, col - 1, col) : '';
175
+
176
+ if (charBefore === '\\') {
177
+ buffer.backspace();
178
+ buffer.newline();
179
+ } else {
180
+ handleSubmitAndClear(buffer.text);
181
+ }
182
+ }
183
+ return;
184
+ }
185
+
186
+ // PRIORITY 7: Newline
187
+ if (keyMatchers[Command.NEWLINE](key)) {
188
+ buffer.newline();
189
+ return;
190
+ }
191
+
192
+ // PRIORITY 8: Navigation commands
193
+ if (keyMatchers[Command.HOME](key)) {
194
+ buffer.move('home');
195
+ return;
196
+ }
197
+
198
+ if (keyMatchers[Command.END](key)) {
199
+ buffer.move('end');
200
+ return;
201
+ }
202
+
203
+ if (keyMatchers[Command.KILL_LINE_LEFT](key)) {
204
+ buffer.killLineLeft();
205
+ return;
206
+ }
207
+
208
+ if (keyMatchers[Command.KILL_LINE_RIGHT](key)) {
209
+ buffer.killLineRight();
210
+ return;
211
+ }
212
+
213
+ if (keyMatchers[Command.DELETE_WORD_BACKWARD](key)) {
214
+ buffer.deleteWordLeft();
215
+ return;
216
+ }
217
+
218
+ // PRIORITY 9: Autocomplete
219
+ if (keyMatchers[Command.ACCEPT_SUGGESTION](key)) {
220
+ if (completion.showSuggestions && completion.suggestions.length > 0) {
221
+ const targetIndex = completion.activeSuggestionIndex === -1 ? 0 : completion.activeSuggestionIndex;
222
+ completion.handleAutocomplete(targetIndex);
223
+ return;
224
+ }
225
+ }
226
+
227
+ // PRIORITY 10: Arrow keys (completion or navigation)
228
+ if (key.upArrow) {
229
+ if (completion.showSuggestions && completion.suggestions.length > 1) {
230
+ completion.navigateUp();
231
+ return;
232
+ }
233
+ buffer.move('up');
234
+ return;
235
+ }
236
+ if (key.downArrow) {
237
+ if (completion.showSuggestions && completion.suggestions.length > 1) {
238
+ completion.navigateDown();
239
+ return;
240
+ }
241
+ buffer.move('down');
242
+ return;
243
+ }
244
+ if (key.leftArrow) {
245
+ buffer.move('left');
246
+ return;
247
+ }
248
+ if (key.rightArrow) {
249
+ buffer.move('right');
250
+ return;
251
+ }
252
+
253
+ // PRIORITY 11: Backspace and Delete
254
+ if (key.backspace) {
255
+ const count = key.repeatCount || 1;
256
+ for (let i = 0; i < count; i++) {
257
+ buffer.backspace();
258
+ }
259
+ return;
260
+ }
261
+
262
+ if (key.delete) {
263
+ const count = key.repeatCount || 1;
264
+ for (let i = 0; i < count; i++) {
265
+ buffer.delete();
266
+ }
267
+ return;
268
+ }
269
+
270
+ // PRIORITY 12: Default input handling
271
+ buffer.handleInput(key);
272
+ }, [
273
+ focus,
274
+ buffer,
275
+ onClearScreen,
276
+ handleSubmitAndClear,
277
+ completion,
278
+ isSessionRunning,
279
+ isExiting,
280
+ onExit
281
+ ]);
282
+
283
+ useKeypress(handleInput, { isActive: focus });
284
+
285
+ // Extract all needed values in one go to avoid multiple getter calls
286
+ const linesToRender = useMemo(() => buffer.viewportVisualLines, [buffer.viewportVisualLines]);
287
+ const [cursorVisualRowAbsolute, cursorVisualColAbsolute] = useMemo(() => buffer.visualCursor, [buffer.visualCursor]);
288
+ const scrollVisualRow = useMemo(() => buffer.visualScrollRow, [buffer.visualScrollRow]);
289
+ const cursorVisualRow = cursorVisualRowAbsolute - scrollVisualRow;
290
+ const bufferTextLength = useMemo(() => buffer.text.length, [buffer.text]);
291
+
292
+ return React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
293
+ React.createElement(Box, {
294
+ borderStyle: "round",
295
+ borderColor: theme.brand.light,
296
+ paddingX: 1,
297
+ flexDirection: "row",
298
+ alignItems: "flex-start",
299
+ minHeight: 3,
300
+ maxHeight: 12, // Limit input height to prevent pushing other elements
301
+ flexShrink: 0 // Prevent input from being squeezed by other elements
302
+ // Don't set explicit width - let Ink calculate it
303
+ },
304
+ React.createElement(Text, { color: theme.brand.light }, '> '),
305
+ React.createElement(Box, { flexGrow: 1, flexDirection: "column" },
306
+ bufferTextLength === 0 && defaultPlaceholder
307
+ ? (focus
308
+ ? React.createElement(Text, null,
309
+ chalk.inverse(defaultPlaceholder.slice(0, 1)),
310
+ React.createElement(Text, { color: theme.text.secondary }, defaultPlaceholder.slice(1)))
311
+ : React.createElement(Text, { color: theme.text.secondary }, defaultPlaceholder))
312
+ : linesToRender.map((lineText, visualIdxInRenderedSet) => {
313
+ const isOnCursorLine = focus && visualIdxInRenderedSet === cursorVisualRow;
314
+ let display = lineText;
315
+
316
+ // Render cursor inline
317
+ if (isOnCursorLine && cursorVisualColAbsolute < cpLen(lineText)) {
318
+ const charToHighlight = cpSlice(lineText, cursorVisualColAbsolute, cursorVisualColAbsolute + 1);
319
+ const highlighted = chalk.inverse(charToHighlight);
320
+ display =
321
+ cpSlice(lineText, 0, cursorVisualColAbsolute) +
322
+ highlighted +
323
+ cpSlice(lineText, cursorVisualColAbsolute + 1);
324
+ } else if (isOnCursorLine && cursorVisualColAbsolute === cpLen(lineText)) {
325
+ display = lineText + chalk.inverse(' ');
326
+ }
327
+
328
+ return React.createElement(Box, { key: `line-${visualIdxInRenderedSet}`, height: 1 },
329
+ React.createElement(Text, { color: theme.text.primary }, display)
330
+ );
331
+ })
332
+ )
333
+ ),
334
+
335
+ completion.showSuggestions && React.createElement(AutocompleteMenu, {
336
+ suggestions: completion.suggestions,
337
+ activeIndex: completion.activeSuggestionIndex
338
+ })
339
+ );
340
+ }
341
+
342
+ // Don't memoize - buffer state changes frequently and we need to react to it
343
+ // The buffer object reference is stable (from useRef), but its internal state changes
344
+ export const Input = InputPromptComponent;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Loading Indicator component
3
+ * Displays a loading spinner during initialization
4
+ */
5
+
6
+ import React from 'react';
7
+ import { Box, Text } from 'ink';
8
+ import Spinner from 'ink-spinner';
9
+
10
+ export function LoadingIndicator({ tasks = [] }) {
11
+ return React.createElement(Box, {
12
+ flexDirection: "column",
13
+ alignItems: "center",
14
+ justifyContent: "center",
15
+ height: 10
16
+ },
17
+ React.createElement(Box, { marginBottom: 1 },
18
+ React.createElement(Text, { color: "cyan" },
19
+ React.createElement(Spinner, { type: "dots" }),
20
+ " Initializing..."
21
+ )
22
+ ),
23
+ tasks.length > 0 && tasks.map((task) =>
24
+ React.createElement(Text, {
25
+ key: task.id,
26
+ color: "gray",
27
+ dimColor: true
28
+ }, task.text)
29
+ )
30
+ );
31
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Model List View Component - Ink-based UI for displaying available models
3
+ */
4
+
5
+ import React from 'react';
6
+ import { Box, Text } from 'ink';
7
+ import { theme } from '../design/themeColors.js';
8
+
9
+ export function ModelListView({ modelsByProvider }) {
10
+ const sections = [
11
+ React.createElement(Text, {
12
+ key: 'header',
13
+ bold: true,
14
+ color: 'whiteBright'
15
+ }, 'Available AI Models'),
16
+
17
+ React.createElement(Text, { key: 'spacer1' }, null)
18
+ ];
19
+
20
+ // 모든 모델을 하나의 리스트로 통합
21
+ const allModels = [];
22
+ Object.keys(modelsByProvider).forEach(provider => {
23
+ const models = modelsByProvider[provider];
24
+ models.forEach(model => {
25
+ allModels.push(model);
26
+ });
27
+ });
28
+
29
+ // 모델 리스트 추가
30
+ allModels.forEach((model, index) => {
31
+ sections.push(
32
+ React.createElement(Text, {
33
+ key: model.id,
34
+ color: 'white'
35
+ }, ` • ${model.id}`)
36
+ );
37
+ });
38
+
39
+ sections.push(
40
+ React.createElement(Text, { key: 'spacer2' }, null),
41
+ React.createElement(Text, { key: 'usage', bold: true }, 'Usage:'),
42
+ React.createElement(Text, { key: 'usage-cmd' }, ' /model <model-id>'),
43
+ React.createElement(Text, { key: 'usage-example', dimColor: true }, ' Example: /model glm-4.5')
44
+ );
45
+
46
+ return React.createElement(Box, {
47
+ flexDirection: 'column'
48
+ }, ...sections);
49
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Model Updated View Component - Ink-based UI for model update confirmation
3
+ */
4
+
5
+ import React from 'react';
6
+ import { Box, Text } from 'ink';
7
+ import { theme } from '../design/themeColors.js';
8
+
9
+ export function ModelUpdatedView({ provider, modelId, modelInfo, settingsFile, warning }) {
10
+ return React.createElement(Box, {
11
+ flexDirection: 'column'
12
+ },
13
+ React.createElement(Text, {
14
+ color: 'green'
15
+ }, `Model updated: ${modelId}`),
16
+
17
+ // Warning if API key not configured
18
+ warning && React.createElement(Text, {
19
+ color: 'yellow'
20
+ }, `Warning: ${warning.message} ${warning.hint}`)
21
+ );
22
+ }