@shareai-lab/kode 1.0.70 → 1.0.71

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 (253) hide show
  1. package/README.md +202 -76
  2. package/README.zh-CN.md +246 -0
  3. package/cli.js +62 -0
  4. package/package.json +45 -25
  5. package/scripts/postinstall.js +56 -0
  6. package/src/ProjectOnboarding.tsx +180 -0
  7. package/src/Tool.ts +53 -0
  8. package/src/commands/approvedTools.ts +53 -0
  9. package/src/commands/bug.tsx +20 -0
  10. package/src/commands/clear.ts +43 -0
  11. package/src/commands/compact.ts +120 -0
  12. package/src/commands/config.tsx +19 -0
  13. package/src/commands/cost.ts +18 -0
  14. package/src/commands/ctx_viz.ts +209 -0
  15. package/src/commands/doctor.ts +24 -0
  16. package/src/commands/help.tsx +19 -0
  17. package/src/commands/init.ts +37 -0
  18. package/src/commands/listen.ts +42 -0
  19. package/src/commands/login.tsx +51 -0
  20. package/src/commands/logout.tsx +40 -0
  21. package/src/commands/mcp.ts +41 -0
  22. package/src/commands/model.tsx +40 -0
  23. package/src/commands/modelstatus.tsx +20 -0
  24. package/src/commands/onboarding.tsx +34 -0
  25. package/src/commands/pr_comments.ts +59 -0
  26. package/src/commands/refreshCommands.ts +54 -0
  27. package/src/commands/release-notes.ts +34 -0
  28. package/src/commands/resume.tsx +30 -0
  29. package/src/commands/review.ts +49 -0
  30. package/src/commands/terminalSetup.ts +221 -0
  31. package/src/commands.ts +136 -0
  32. package/src/components/ApproveApiKey.tsx +93 -0
  33. package/src/components/AsciiLogo.tsx +13 -0
  34. package/src/components/AutoUpdater.tsx +148 -0
  35. package/src/components/Bug.tsx +367 -0
  36. package/src/components/Config.tsx +289 -0
  37. package/src/components/ConsoleOAuthFlow.tsx +326 -0
  38. package/src/components/Cost.tsx +23 -0
  39. package/src/components/CostThresholdDialog.tsx +46 -0
  40. package/src/components/CustomSelect/option-map.ts +42 -0
  41. package/src/components/CustomSelect/select-option.tsx +52 -0
  42. package/src/components/CustomSelect/select.tsx +143 -0
  43. package/src/components/CustomSelect/use-select-state.ts +414 -0
  44. package/src/components/CustomSelect/use-select.ts +35 -0
  45. package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
  46. package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
  47. package/src/components/Help.tsx +215 -0
  48. package/src/components/HighlightedCode.tsx +33 -0
  49. package/src/components/InvalidConfigDialog.tsx +113 -0
  50. package/src/components/Link.tsx +32 -0
  51. package/src/components/LogSelector.tsx +86 -0
  52. package/src/components/Logo.tsx +145 -0
  53. package/src/components/MCPServerApprovalDialog.tsx +100 -0
  54. package/src/components/MCPServerDialogCopy.tsx +25 -0
  55. package/src/components/MCPServerMultiselectDialog.tsx +109 -0
  56. package/src/components/Message.tsx +219 -0
  57. package/src/components/MessageResponse.tsx +15 -0
  58. package/src/components/MessageSelector.tsx +211 -0
  59. package/src/components/ModeIndicator.tsx +88 -0
  60. package/src/components/ModelConfig.tsx +301 -0
  61. package/src/components/ModelListManager.tsx +223 -0
  62. package/src/components/ModelSelector.tsx +3208 -0
  63. package/src/components/ModelStatusDisplay.tsx +228 -0
  64. package/src/components/Onboarding.tsx +274 -0
  65. package/src/components/PressEnterToContinue.tsx +11 -0
  66. package/src/components/PromptInput.tsx +710 -0
  67. package/src/components/SentryErrorBoundary.ts +33 -0
  68. package/src/components/Spinner.tsx +129 -0
  69. package/src/components/StructuredDiff.tsx +184 -0
  70. package/src/components/TextInput.tsx +246 -0
  71. package/src/components/TokenWarning.tsx +31 -0
  72. package/src/components/ToolUseLoader.tsx +40 -0
  73. package/src/components/TrustDialog.tsx +106 -0
  74. package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
  75. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
  76. package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
  77. package/src/components/binary-feedback/utils.ts +220 -0
  78. package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
  79. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +45 -0
  80. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
  81. package/src/components/messages/AssistantTextMessage.tsx +144 -0
  82. package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
  83. package/src/components/messages/AssistantToolUseMessage.tsx +123 -0
  84. package/src/components/messages/UserBashInputMessage.tsx +28 -0
  85. package/src/components/messages/UserCommandMessage.tsx +30 -0
  86. package/src/components/messages/UserKodingInputMessage.tsx +28 -0
  87. package/src/components/messages/UserPromptMessage.tsx +35 -0
  88. package/src/components/messages/UserTextMessage.tsx +39 -0
  89. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
  90. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
  91. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
  92. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
  93. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
  94. package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
  95. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
  96. package/src/components/permissions/FallbackPermissionRequest.tsx +155 -0
  97. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
  98. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +75 -0
  99. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
  100. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +81 -0
  101. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +242 -0
  102. package/src/components/permissions/PermissionRequest.tsx +103 -0
  103. package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
  104. package/src/components/permissions/hooks.ts +44 -0
  105. package/src/components/permissions/toolUseOptions.ts +59 -0
  106. package/src/components/permissions/utils.ts +23 -0
  107. package/src/constants/betas.ts +5 -0
  108. package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
  109. package/src/constants/figures.ts +4 -0
  110. package/src/constants/keys.ts +3 -0
  111. package/src/constants/macros.ts +6 -0
  112. package/src/constants/models.ts +935 -0
  113. package/src/constants/oauth.ts +18 -0
  114. package/src/constants/product.ts +17 -0
  115. package/src/constants/prompts.ts +177 -0
  116. package/src/constants/releaseNotes.ts +7 -0
  117. package/src/context/PermissionContext.tsx +149 -0
  118. package/src/context.ts +278 -0
  119. package/src/cost-tracker.ts +84 -0
  120. package/src/entrypoints/cli.tsx +1498 -0
  121. package/src/entrypoints/mcp.ts +176 -0
  122. package/src/history.ts +25 -0
  123. package/src/hooks/useApiKeyVerification.ts +59 -0
  124. package/src/hooks/useArrowKeyHistory.ts +55 -0
  125. package/src/hooks/useCanUseTool.ts +138 -0
  126. package/src/hooks/useCancelRequest.ts +39 -0
  127. package/src/hooks/useDoublePress.ts +42 -0
  128. package/src/hooks/useExitOnCtrlCD.ts +31 -0
  129. package/src/hooks/useInterval.ts +25 -0
  130. package/src/hooks/useLogMessages.ts +16 -0
  131. package/src/hooks/useLogStartupTime.ts +12 -0
  132. package/src/hooks/useNotifyAfterTimeout.ts +65 -0
  133. package/src/hooks/usePermissionRequestLogging.ts +44 -0
  134. package/src/hooks/useSlashCommandTypeahead.ts +137 -0
  135. package/src/hooks/useTerminalSize.ts +49 -0
  136. package/src/hooks/useTextInput.ts +315 -0
  137. package/src/messages.ts +37 -0
  138. package/src/permissions.ts +268 -0
  139. package/src/query.ts +704 -0
  140. package/src/screens/ConfigureNpmPrefix.tsx +197 -0
  141. package/src/screens/Doctor.tsx +219 -0
  142. package/src/screens/LogList.tsx +68 -0
  143. package/src/screens/REPL.tsx +792 -0
  144. package/src/screens/ResumeConversation.tsx +68 -0
  145. package/src/services/browserMocks.ts +66 -0
  146. package/src/services/claude.ts +1947 -0
  147. package/src/services/customCommands.ts +683 -0
  148. package/src/services/fileFreshness.ts +377 -0
  149. package/src/services/mcpClient.ts +564 -0
  150. package/src/services/mcpServerApproval.tsx +50 -0
  151. package/src/services/notifier.ts +40 -0
  152. package/src/services/oauth.ts +357 -0
  153. package/src/services/openai.ts +796 -0
  154. package/src/services/sentry.ts +3 -0
  155. package/src/services/statsig.ts +171 -0
  156. package/src/services/statsigStorage.ts +86 -0
  157. package/src/services/systemReminder.ts +406 -0
  158. package/src/services/vcr.ts +161 -0
  159. package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
  160. package/src/tools/ArchitectTool/prompt.ts +15 -0
  161. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +505 -0
  162. package/src/tools/BashTool/BashTool.tsx +270 -0
  163. package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
  164. package/src/tools/BashTool/OutputLine.tsx +48 -0
  165. package/src/tools/BashTool/prompt.ts +174 -0
  166. package/src/tools/BashTool/utils.ts +56 -0
  167. package/src/tools/FileEditTool/FileEditTool.tsx +316 -0
  168. package/src/tools/FileEditTool/prompt.ts +51 -0
  169. package/src/tools/FileEditTool/utils.ts +58 -0
  170. package/src/tools/FileReadTool/FileReadTool.tsx +371 -0
  171. package/src/tools/FileReadTool/prompt.ts +7 -0
  172. package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
  173. package/src/tools/FileWriteTool/prompt.ts +10 -0
  174. package/src/tools/GlobTool/GlobTool.tsx +119 -0
  175. package/src/tools/GlobTool/prompt.ts +8 -0
  176. package/src/tools/GrepTool/GrepTool.tsx +147 -0
  177. package/src/tools/GrepTool/prompt.ts +11 -0
  178. package/src/tools/MCPTool/MCPTool.tsx +106 -0
  179. package/src/tools/MCPTool/prompt.ts +3 -0
  180. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
  181. package/src/tools/MemoryReadTool/prompt.ts +3 -0
  182. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
  183. package/src/tools/MemoryWriteTool/prompt.ts +3 -0
  184. package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
  185. package/src/tools/MultiEditTool/prompt.ts +45 -0
  186. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
  187. package/src/tools/NotebookEditTool/prompt.ts +3 -0
  188. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +266 -0
  189. package/src/tools/NotebookReadTool/prompt.ts +3 -0
  190. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
  191. package/src/tools/StickerRequestTool/prompt.ts +19 -0
  192. package/src/tools/TaskTool/TaskTool.tsx +382 -0
  193. package/src/tools/TaskTool/constants.ts +1 -0
  194. package/src/tools/TaskTool/prompt.ts +56 -0
  195. package/src/tools/ThinkTool/ThinkTool.tsx +56 -0
  196. package/src/tools/ThinkTool/prompt.ts +12 -0
  197. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +289 -0
  198. package/src/tools/TodoWriteTool/prompt.ts +63 -0
  199. package/src/tools/lsTool/lsTool.tsx +269 -0
  200. package/src/tools/lsTool/prompt.ts +2 -0
  201. package/src/tools.ts +63 -0
  202. package/src/types/PermissionMode.ts +120 -0
  203. package/src/types/RequestContext.ts +72 -0
  204. package/src/utils/Cursor.ts +436 -0
  205. package/src/utils/PersistentShell.ts +373 -0
  206. package/src/utils/agentStorage.ts +97 -0
  207. package/src/utils/array.ts +3 -0
  208. package/src/utils/ask.tsx +98 -0
  209. package/src/utils/auth.ts +13 -0
  210. package/src/utils/autoCompactCore.ts +223 -0
  211. package/src/utils/autoUpdater.ts +318 -0
  212. package/src/utils/betas.ts +20 -0
  213. package/src/utils/browser.ts +14 -0
  214. package/src/utils/cleanup.ts +72 -0
  215. package/src/utils/commands.ts +261 -0
  216. package/src/utils/config.ts +771 -0
  217. package/src/utils/conversationRecovery.ts +54 -0
  218. package/src/utils/debugLogger.ts +1123 -0
  219. package/src/utils/diff.ts +42 -0
  220. package/src/utils/env.ts +57 -0
  221. package/src/utils/errors.ts +21 -0
  222. package/src/utils/exampleCommands.ts +108 -0
  223. package/src/utils/execFileNoThrow.ts +51 -0
  224. package/src/utils/expertChatStorage.ts +136 -0
  225. package/src/utils/file.ts +402 -0
  226. package/src/utils/fileRecoveryCore.ts +71 -0
  227. package/src/utils/format.tsx +44 -0
  228. package/src/utils/generators.ts +62 -0
  229. package/src/utils/git.ts +92 -0
  230. package/src/utils/globalLogger.ts +77 -0
  231. package/src/utils/http.ts +10 -0
  232. package/src/utils/imagePaste.ts +38 -0
  233. package/src/utils/json.ts +13 -0
  234. package/src/utils/log.ts +382 -0
  235. package/src/utils/markdown.ts +213 -0
  236. package/src/utils/messageContextManager.ts +289 -0
  237. package/src/utils/messages.tsx +938 -0
  238. package/src/utils/model.ts +836 -0
  239. package/src/utils/permissions/filesystem.ts +118 -0
  240. package/src/utils/ripgrep.ts +167 -0
  241. package/src/utils/sessionState.ts +49 -0
  242. package/src/utils/state.ts +25 -0
  243. package/src/utils/style.ts +29 -0
  244. package/src/utils/terminal.ts +49 -0
  245. package/src/utils/theme.ts +122 -0
  246. package/src/utils/thinking.ts +144 -0
  247. package/src/utils/todoStorage.ts +431 -0
  248. package/src/utils/tokens.ts +43 -0
  249. package/src/utils/toolExecutionController.ts +163 -0
  250. package/src/utils/unaryLogging.ts +26 -0
  251. package/src/utils/user.ts +37 -0
  252. package/src/utils/validate.ts +165 -0
  253. package/cli.mjs +0 -1803
@@ -0,0 +1,710 @@
1
+ import { Box, Text, useInput } from 'ink'
2
+ import { sample } from 'lodash-es'
3
+ import { getExampleCommands } from '../utils/exampleCommands'
4
+ import * as React from 'react'
5
+ import { type Message } from '../query'
6
+ import { processUserInput } from '../utils/messages'
7
+ import { useArrowKeyHistory } from '../hooks/useArrowKeyHistory'
8
+ import { useSlashCommandTypeahead } from '../hooks/useSlashCommandTypeahead'
9
+ import { addToHistory } from '../history'
10
+ import TextInput from './TextInput'
11
+ import { memo, useCallback, useEffect, useMemo, useState } from 'react'
12
+ import { countTokens } from '../utils/tokens'
13
+ import { SentryErrorBoundary } from './SentryErrorBoundary'
14
+ import { AutoUpdater } from './AutoUpdater'
15
+ import type { AutoUpdaterResult } from '../utils/autoUpdater'
16
+ import type { Command } from '../commands'
17
+ import type { SetToolJSXFn, Tool } from '../Tool'
18
+ import { TokenWarning, WARNING_THRESHOLD } from './TokenWarning'
19
+ import { useTerminalSize } from '../hooks/useTerminalSize'
20
+ import { getTheme } from '../utils/theme'
21
+ import { getModelManager, reloadModelManager } from '../utils/model'
22
+ import { saveGlobalConfig } from '../utils/config'
23
+ import { setTerminalTitle } from '../utils/terminal'
24
+ import terminalSetup, {
25
+ isShiftEnterKeyBindingInstalled,
26
+ handleHashCommand,
27
+ } from '../commands/terminalSetup'
28
+ import { usePermissionContext } from '../context/PermissionContext'
29
+
30
+ // Async function to interpret the '#' command input using AI
31
+ async function interpretHashCommand(input: string): Promise<string> {
32
+ // Use the AI to interpret the input
33
+ try {
34
+ const { queryQuick } = await import('../services/claude')
35
+
36
+ // Create a prompt for the model to interpret the hash command
37
+ const systemPrompt = [
38
+ "You're helping the user structure notes that will be added to their KODING.md file.",
39
+ "Format the user's input into a well-structured note that will be useful for later reference.",
40
+ 'Add appropriate markdown formatting, headings, bullet points, or other structural elements as needed.',
41
+ 'The goal is to transform the raw note into something that will be more useful when reviewed later.',
42
+ 'You should keep the original meaning but make the structure clear.',
43
+ ]
44
+
45
+ // Send the request to the AI
46
+ const result = await queryQuick({
47
+ systemPrompt,
48
+ userPrompt: `Transform this note for KODING.md: ${input}`,
49
+ })
50
+
51
+ // Extract the content from the response
52
+ if (typeof result.message.content === 'string') {
53
+ return result.message.content
54
+ } else if (Array.isArray(result.message.content)) {
55
+ return result.message.content
56
+ .filter(block => block.type === 'text')
57
+ .map(block => (block.type === 'text' ? block.text : ''))
58
+ .join('\n')
59
+ }
60
+
61
+ return `# ${input}\n\n_Added on ${new Date().toLocaleString()}_`
62
+ } catch (e) {
63
+ // If interpretation fails, return the input with minimal formatting
64
+ return `# ${input}\n\n_Added on ${new Date().toLocaleString()}_`
65
+ }
66
+ }
67
+
68
+ type Props = {
69
+ commands: Command[]
70
+ forkNumber: number
71
+ messageLogName: string
72
+ isDisabled: boolean
73
+ isLoading: boolean
74
+ onQuery: (
75
+ newMessages: Message[],
76
+ abortController?: AbortController,
77
+ ) => Promise<void>
78
+ debug: boolean
79
+ verbose: boolean
80
+ messages: Message[]
81
+ setToolJSX: SetToolJSXFn
82
+ onAutoUpdaterResult: (result: AutoUpdaterResult) => void
83
+ autoUpdaterResult: AutoUpdaterResult | null
84
+ tools: Tool[]
85
+ input: string
86
+ onInputChange: (value: string) => void
87
+ mode: 'bash' | 'prompt' | 'koding'
88
+ onModeChange: (mode: 'bash' | 'prompt' | 'koding') => void
89
+ submitCount: number
90
+ onSubmitCountChange: (updater: (prev: number) => number) => void
91
+ setIsLoading: (isLoading: boolean) => void
92
+ setAbortController: (abortController: AbortController | null) => void
93
+ onShowMessageSelector: () => void
94
+ setForkConvoWithMessagesOnTheNextRender: (
95
+ forkConvoWithMessages: Message[],
96
+ ) => void
97
+ readFileTimestamps: { [filename: string]: number }
98
+ abortController: AbortController | null
99
+ setAbortController: (abortController: AbortController | null) => void
100
+ onModelChange?: () => void
101
+ }
102
+
103
+ function getPastedTextPrompt(text: string): string {
104
+ const newlineCount = (text.match(/\r\n|\r|\n/g) || []).length
105
+ return `[Pasted text +${newlineCount} lines] `
106
+ }
107
+ function PromptInput({
108
+ commands,
109
+ forkNumber,
110
+ messageLogName,
111
+ isDisabled,
112
+ isLoading,
113
+ onQuery,
114
+ debug,
115
+ verbose,
116
+ messages,
117
+ setToolJSX,
118
+ onAutoUpdaterResult,
119
+ autoUpdaterResult,
120
+ tools,
121
+ input,
122
+ onInputChange,
123
+ mode,
124
+ onModeChange,
125
+ submitCount,
126
+ onSubmitCountChange,
127
+ setIsLoading,
128
+ abortController,
129
+ setAbortController,
130
+ onShowMessageSelector,
131
+ setForkConvoWithMessagesOnTheNextRender,
132
+ readFileTimestamps,
133
+ onModelChange,
134
+ }: Props): React.ReactNode {
135
+ const [isAutoUpdating, setIsAutoUpdating] = useState(false)
136
+ const [exitMessage, setExitMessage] = useState<{
137
+ show: boolean
138
+ key?: string
139
+ }>({ show: false })
140
+ const [message, setMessage] = useState<{ show: boolean; text?: string }>({
141
+ show: false,
142
+ })
143
+ const [modelSwitchMessage, setModelSwitchMessage] = useState<{
144
+ show: boolean
145
+ text?: string
146
+ }>({
147
+ show: false,
148
+ })
149
+ const [pastedImage, setPastedImage] = useState<string | null>(null)
150
+ const [placeholder, setPlaceholder] = useState('')
151
+ const [cursorOffset, setCursorOffset] = useState<number>(input.length)
152
+ const [pastedText, setPastedText] = useState<string | null>(null)
153
+
154
+ // Permission context for mode management
155
+ const { cycleMode, currentMode } = usePermissionContext()
156
+
157
+ // useEffect(() => {
158
+ // getExampleCommands().then(commands => {
159
+ // setPlaceholder(`Try "${sample(commands)}"`)
160
+ // })
161
+ // }, [])
162
+ const { columns } = useTerminalSize()
163
+
164
+ const commandWidth = useMemo(
165
+ () => Math.max(...commands.map(cmd => cmd.userFacingName().length)) + 5,
166
+ [commands],
167
+ )
168
+
169
+ const {
170
+ suggestions,
171
+ selectedSuggestion,
172
+ updateSuggestions,
173
+ clearSuggestions,
174
+ } = useSlashCommandTypeahead({
175
+ commands,
176
+ onInputChange,
177
+ onSubmit,
178
+ setCursorOffset,
179
+ currentInput: input,
180
+ })
181
+
182
+ const onChange = useCallback(
183
+ (value: string) => {
184
+ if (value.startsWith('!')) {
185
+ onModeChange('bash')
186
+ return
187
+ }
188
+ if (value.startsWith('#')) {
189
+ onModeChange('koding')
190
+ return
191
+ }
192
+ updateSuggestions(value)
193
+ onInputChange(value)
194
+ },
195
+ [onModeChange, onInputChange, updateSuggestions],
196
+ )
197
+
198
+ // Handle Tab key model switching with simple context check
199
+ const handleQuickModelSwitch = useCallback(async () => {
200
+ const modelManager = getModelManager()
201
+ const currentTokens = countTokens(messages)
202
+
203
+ const switchResult = modelManager.switchToNextModel(currentTokens)
204
+
205
+ if (switchResult.success && switchResult.modelName) {
206
+ // Successful switch
207
+ onSubmitCountChange(prev => prev + 1)
208
+ const newModel = modelManager.getModel('main')
209
+ setModelSwitchMessage({
210
+ show: true,
211
+ text: `✅ Switched to ${switchResult.modelName} (${newModel?.provider || 'Unknown'} | Model: ${newModel?.modelName || 'N/A'})`,
212
+ })
213
+ setTimeout(() => setModelSwitchMessage({ show: false }), 3000)
214
+ } else if (switchResult.blocked && switchResult.message) {
215
+ // Context overflow - show detailed message
216
+ setModelSwitchMessage({
217
+ show: true,
218
+ text: switchResult.message,
219
+ })
220
+ setTimeout(() => setModelSwitchMessage({ show: false }), 5000)
221
+ } else {
222
+ // No other models available or other error
223
+ setModelSwitchMessage({
224
+ show: true,
225
+ text:
226
+ switchResult.message ||
227
+ '⚠️ No other models configured. Use /model to add more models',
228
+ })
229
+ setTimeout(() => setModelSwitchMessage({ show: false }), 3000)
230
+ }
231
+ }, [onSubmitCountChange, messages])
232
+
233
+ const { resetHistory, onHistoryUp, onHistoryDown } = useArrowKeyHistory(
234
+ (value: string, mode: 'bash' | 'prompt' | 'koding') => {
235
+ onChange(value)
236
+ onModeChange(mode)
237
+ },
238
+ input,
239
+ )
240
+
241
+ // Only use history navigation when there are 0 or 1 slash command suggestions
242
+ const handleHistoryUp = () => {
243
+ if (suggestions.length <= 1) {
244
+ onHistoryUp()
245
+ }
246
+ }
247
+
248
+ const handleHistoryDown = () => {
249
+ if (suggestions.length <= 1) {
250
+ onHistoryDown()
251
+ }
252
+ }
253
+
254
+ async function onSubmit(input: string, isSubmittingSlashCommand = false) {
255
+ // Special handling for "put a verbose summary" and similar action prompts in koding mode
256
+ if (
257
+ (mode === 'koding' || input.startsWith('#')) &&
258
+ input.match(/^(#\s*)?(put|create|generate|write|give|provide)/i)
259
+ ) {
260
+ try {
261
+ // Store the original input for history
262
+ const originalInput = input
263
+
264
+ // Strip the # prefix if present
265
+ const cleanInput = mode === 'koding' ? input : input.substring(1).trim()
266
+
267
+ // Add to history and clear input field
268
+ addToHistory(mode === 'koding' ? `#${input}` : input)
269
+ onInputChange('')
270
+
271
+ // Create additional context to inform Claude this is for KODING.md
272
+ const kodingContext =
273
+ 'The user is using Koding mode. Format your response as a comprehensive, well-structured document suitable for adding to KODE.md. Use proper markdown formatting with headings, lists, code blocks, etc. The response should be complete and ready to add to KODE.md documentation.'
274
+
275
+ // Switch to prompt mode but tag the submission for later capture
276
+ onModeChange('prompt')
277
+
278
+ // 🔧 Fix Koding mode: clean up previous state
279
+ if (abortController) {
280
+ abortController.abort()
281
+ }
282
+ setIsLoading(false)
283
+ await new Promise(resolve => setTimeout(resolve, 0))
284
+
285
+ // Set loading state - AbortController now created in onQuery
286
+ setIsLoading(true)
287
+
288
+ // Process as a normal user input but with special handling
289
+ const messages = await processUserInput(
290
+ cleanInput,
291
+ 'prompt', // Use prompt mode for processing
292
+ setToolJSX,
293
+ {
294
+ options: {
295
+ commands,
296
+ forkNumber,
297
+ messageLogName,
298
+ tools,
299
+ verbose,
300
+ maxThinkingTokens: 0,
301
+ // Add context flag for koding mode
302
+ isKodingRequest: true,
303
+ kodingContext,
304
+ },
305
+ messageId: undefined,
306
+ abortController: abortController || new AbortController(), // Temporary controller, actual one created in onQuery
307
+ readFileTimestamps,
308
+ setForkConvoWithMessagesOnTheNextRender,
309
+ },
310
+ pastedImage ?? null,
311
+ )
312
+
313
+ // Send query and capture response
314
+ if (messages.length) {
315
+ await onQuery(messages)
316
+
317
+ // After query completes, the last message should be Claude's response
318
+ // We'll set up a one-time listener to capture and save Claude's response
319
+ // This will be handled by the REPL component or message handler
320
+ }
321
+
322
+ return
323
+ } catch (e) {
324
+ // If something fails, log the error
325
+ console.error('Error processing Koding request:', e)
326
+ }
327
+ }
328
+
329
+ // If in koding mode or input starts with '#', interpret it using AI before appending to KODE.md
330
+ else if (mode === 'koding' || input.startsWith('#')) {
331
+ try {
332
+ // Strip the # if we're in koding mode and the user didn't type it (since it's implied)
333
+ const contentToInterpret =
334
+ mode === 'koding' && !input.startsWith('#')
335
+ ? input.trim()
336
+ : input.substring(1).trim()
337
+
338
+ const interpreted = await interpretHashCommand(contentToInterpret)
339
+ handleHashCommand(interpreted)
340
+ } catch (e) {
341
+ // If interpretation fails, log the error
342
+ }
343
+ onInputChange('')
344
+ addToHistory(mode === 'koding' ? `#${input}` : input)
345
+ onModeChange('prompt')
346
+ return
347
+ }
348
+ if (input === '') {
349
+ return
350
+ }
351
+ if (isDisabled) {
352
+ return
353
+ }
354
+ if (isLoading) {
355
+ return
356
+ }
357
+ if (suggestions.length > 0 && !isSubmittingSlashCommand) {
358
+ return
359
+ }
360
+
361
+ // Handle exit commands
362
+ if (['exit', 'quit', ':q', ':q!', ':wq', ':wq!'].includes(input.trim())) {
363
+ exit()
364
+ }
365
+
366
+ let finalInput = input
367
+ if (pastedText) {
368
+ // Create the prompt pattern that would have been used for this pasted text
369
+ const pastedPrompt = getPastedTextPrompt(pastedText)
370
+ if (finalInput.includes(pastedPrompt)) {
371
+ finalInput = finalInput.replace(pastedPrompt, pastedText)
372
+ } // otherwise, ignore the pastedText if the user has modified the prompt
373
+ }
374
+ onInputChange('')
375
+ onModeChange('prompt')
376
+ clearSuggestions()
377
+ setPastedImage(null)
378
+ setPastedText(null)
379
+ onSubmitCountChange(_ => _ + 1)
380
+
381
+ setIsLoading(true)
382
+
383
+ const newAbortController = new AbortController()
384
+ setAbortController(newAbortController)
385
+
386
+ const messages = await processUserInput(
387
+ finalInput,
388
+ mode,
389
+ setToolJSX,
390
+ {
391
+ options: {
392
+ commands,
393
+ forkNumber,
394
+ messageLogName,
395
+ tools,
396
+ verbose,
397
+ maxThinkingTokens: 0,
398
+ },
399
+ messageId: undefined,
400
+ abortController: newAbortController,
401
+ readFileTimestamps,
402
+ setForkConvoWithMessagesOnTheNextRender,
403
+ },
404
+ pastedImage ?? null,
405
+ )
406
+
407
+ if (messages.length) {
408
+ onQuery(messages, newAbortController)
409
+ } else {
410
+ // Local JSX commands
411
+ addToHistory(input)
412
+ resetHistory()
413
+ return
414
+ }
415
+
416
+ for (const message of messages) {
417
+ if (message.type === 'user') {
418
+ const inputToAdd = mode === 'bash' ? `!${input}` : input
419
+ addToHistory(inputToAdd)
420
+ resetHistory()
421
+ }
422
+ }
423
+ }
424
+
425
+ function onImagePaste(image: string) {
426
+ onModeChange('prompt')
427
+ setPastedImage(image)
428
+ }
429
+
430
+ function onTextPaste(rawText: string) {
431
+ // Replace any \r with \n first to match useTextInput's conversion behavior
432
+ const text = rawText.replace(/\r/g, '\n')
433
+
434
+ // Get prompt with newline count
435
+ const pastedPrompt = getPastedTextPrompt(text)
436
+
437
+ // Update the input with a visual indicator that text has been pasted
438
+ const newInput =
439
+ input.slice(0, cursorOffset) + pastedPrompt + input.slice(cursorOffset)
440
+ onInputChange(newInput)
441
+
442
+ // Update cursor position to be after the inserted indicator
443
+ setCursorOffset(cursorOffset + pastedPrompt.length)
444
+
445
+ // Still set the pastedText state for actual submission
446
+ setPastedText(text)
447
+ }
448
+
449
+ useInput((input, key) => {
450
+ if (input === '' && (key.escape || key.backspace || key.delete)) {
451
+ onModeChange('prompt')
452
+ }
453
+ // esc is a little overloaded:
454
+ // - when we're loading a response, it's used to cancel the request
455
+ // - otherwise, it's used to show the message selector
456
+ // - when double pressed, it's used to clear the input
457
+ if (key.escape && messages.length > 0 && !input && !isLoading) {
458
+ onShowMessageSelector()
459
+ }
460
+
461
+ // Shift+Tab for mode cycling (matching original Claude Code implementation)
462
+ if (key.shift && key.tab) {
463
+ cycleMode()
464
+ return true // Explicitly handled
465
+ }
466
+
467
+ // Tab key for model switching (simple and non-conflicting)
468
+ if (key.tab && !key.shift) {
469
+ handleQuickModelSwitch()
470
+ return true // Explicitly handled
471
+ }
472
+
473
+ return false // Not handled, allow other hooks
474
+ })
475
+
476
+ const textInputColumns = useTerminalSize().columns - 6
477
+ const tokenUsage = useMemo(() => countTokens(messages), [messages])
478
+ const theme = getTheme()
479
+
480
+ // 🔧 Fix: Track model ID changes to detect external config updates
481
+ const modelManager = getModelManager()
482
+ const currentModelId = modelManager.getModel('main')?.id || null
483
+
484
+ const modelInfo = useMemo(() => {
485
+ // Force fresh ModelManager instance to detect config changes
486
+ const freshModelManager = getModelManager()
487
+ const currentModel = freshModelManager.getModel('main')
488
+ if (!currentModel) {
489
+ return null
490
+ }
491
+
492
+ return {
493
+ name: currentModel.modelName, // 🔧 Fix: Use actual model name, not display name
494
+ id: currentModel.id, // 添加模型ID用于调试
495
+ provider: currentModel.provider, // 添加提供商信息
496
+ contextLength: currentModel.contextLength,
497
+ currentTokens: tokenUsage,
498
+ }
499
+ }, [tokenUsage, modelSwitchMessage.show, submitCount, currentModelId]) // Track model ID to detect config changes
500
+
501
+ return (
502
+ <Box flexDirection="column">
503
+ {/* Model info in top-right corner */}
504
+ {modelInfo && (
505
+ <Box justifyContent="flex-end" marginBottom={1}>
506
+ <Text dimColor>
507
+ [{modelInfo.provider}] {modelInfo.name}:{' '}
508
+ {Math.round(modelInfo.currentTokens / 1000)}k /{' '}
509
+ {Math.round(modelInfo.contextLength / 1000)}k
510
+ </Text>
511
+ </Box>
512
+ )}
513
+
514
+ <Box
515
+ alignItems="flex-start"
516
+ justifyContent="flex-start"
517
+ borderColor={
518
+ mode === 'bash'
519
+ ? theme.bashBorder
520
+ : mode === 'koding'
521
+ ? theme.koding
522
+ : theme.secondaryBorder
523
+ }
524
+ borderDimColor
525
+ borderStyle="round"
526
+ marginTop={1}
527
+ width="100%"
528
+ >
529
+ <Box
530
+ alignItems="flex-start"
531
+ alignSelf="flex-start"
532
+ flexWrap="nowrap"
533
+ justifyContent="flex-start"
534
+ width={3}
535
+ >
536
+ {mode === 'bash' ? (
537
+ <Text color={theme.bashBorder}>&nbsp;!&nbsp;</Text>
538
+ ) : mode === 'koding' ? (
539
+ <Text color={theme.koding}>&nbsp;#&nbsp;</Text>
540
+ ) : (
541
+ <Text color={isLoading ? theme.secondaryText : undefined}>
542
+ &nbsp;&gt;&nbsp;
543
+ </Text>
544
+ )}
545
+ </Box>
546
+ <Box paddingRight={1}>
547
+ <TextInput
548
+ multiline
549
+ onSubmit={onSubmit}
550
+ onChange={onChange}
551
+ value={input}
552
+ onHistoryUp={handleHistoryUp}
553
+ onHistoryDown={handleHistoryDown}
554
+ onHistoryReset={() => resetHistory()}
555
+ placeholder={submitCount > 0 ? undefined : placeholder}
556
+ onExit={() => process.exit(0)}
557
+ onExitMessage={(show, key) => setExitMessage({ show, key })}
558
+ onMessage={(show, text) => setMessage({ show, text })}
559
+ onImagePaste={onImagePaste}
560
+ columns={textInputColumns}
561
+ isDimmed={isDisabled || isLoading}
562
+ disableCursorMovementForUpDownKeys={suggestions.length > 0}
563
+ cursorOffset={cursorOffset}
564
+ onChangeCursorOffset={setCursorOffset}
565
+ onPaste={onTextPaste}
566
+ />
567
+ </Box>
568
+ </Box>
569
+ {suggestions.length === 0 && (
570
+ <Box
571
+ flexDirection="row"
572
+ justifyContent="space-between"
573
+ paddingX={2}
574
+ paddingY={0}
575
+ >
576
+ <Box justifyContent="flex-start" gap={1}>
577
+ {exitMessage.show ? (
578
+ <Text dimColor>Press {exitMessage.key} again to exit</Text>
579
+ ) : message.show ? (
580
+ <Text dimColor>{message.text}</Text>
581
+ ) : modelSwitchMessage.show ? (
582
+ <Text color={theme.success}>{modelSwitchMessage.text}</Text>
583
+ ) : (
584
+ <>
585
+ <Text
586
+ color={mode === 'bash' ? theme.bashBorder : undefined}
587
+ dimColor={mode !== 'bash'}
588
+ >
589
+ ! for bash mode
590
+ </Text>
591
+ <Text
592
+ color={mode === 'koding' ? theme.koding : undefined}
593
+ dimColor={mode !== 'koding'}
594
+ >
595
+ · # for KODE.md
596
+ </Text>
597
+ <Text dimColor>
598
+ · / for commands · tab to switch model · esc to undo
599
+ </Text>
600
+ </>
601
+ )}
602
+ </Box>
603
+ <SentryErrorBoundary>
604
+ <Box justifyContent="flex-end" gap={1}>
605
+ {!autoUpdaterResult &&
606
+ !isAutoUpdating &&
607
+ !debug &&
608
+ tokenUsage < WARNING_THRESHOLD && (
609
+ <Text dimColor>
610
+ {terminalSetup.isEnabled &&
611
+ isShiftEnterKeyBindingInstalled()
612
+ ? 'shift + ⏎ for newline'
613
+ : '\\⏎ for newline'}
614
+ </Text>
615
+ )}
616
+ <TokenWarning tokenUsage={tokenUsage} />
617
+ {/* <AutoUpdater
618
+ debug={debug}
619
+ onAutoUpdaterResult={onAutoUpdaterResult}
620
+ autoUpdaterResult={autoUpdaterResult}
621
+ isUpdating={isAutoUpdating}
622
+ onChangeIsUpdating={setIsAutoUpdating}
623
+ /> */}
624
+ </Box>
625
+ </SentryErrorBoundary>
626
+ </Box>
627
+ )}
628
+ {suggestions.length > 0 && (
629
+ <Box
630
+ flexDirection="row"
631
+ justifyContent="space-between"
632
+ paddingX={2}
633
+ paddingY={0}
634
+ >
635
+ <Box flexDirection="column">
636
+ {suggestions.map((suggestion, index) => {
637
+ const command = commands.find(
638
+ cmd => cmd.userFacingName() === suggestion.replace('/', ''),
639
+ )
640
+ return (
641
+ <Box
642
+ key={suggestion}
643
+ flexDirection={columns < 80 ? 'column' : 'row'}
644
+ >
645
+ <Box width={columns < 80 ? undefined : commandWidth}>
646
+ <Text
647
+ color={
648
+ index === selectedSuggestion
649
+ ? theme.suggestion
650
+ : undefined
651
+ }
652
+ dimColor={index !== selectedSuggestion}
653
+ >
654
+ /{suggestion}
655
+ {command?.aliases && command.aliases.length > 0 && (
656
+ <Text dimColor> ({command.aliases.join(', ')})</Text>
657
+ )}
658
+ </Text>
659
+ </Box>
660
+ {command && (
661
+ <Box
662
+ width={columns - (columns < 80 ? 4 : commandWidth + 4)}
663
+ paddingLeft={columns < 80 ? 4 : 0}
664
+ >
665
+ <Text
666
+ color={
667
+ index === selectedSuggestion
668
+ ? theme.suggestion
669
+ : undefined
670
+ }
671
+ dimColor={index !== selectedSuggestion}
672
+ wrap="wrap"
673
+ >
674
+ <Text dimColor={index !== selectedSuggestion}>
675
+ {command.description}
676
+ {command.type === 'prompt' && command.argNames?.length
677
+ ? ` (arguments: ${command.argNames.join(', ')})`
678
+ : null}
679
+ </Text>
680
+ </Text>
681
+ </Box>
682
+ )}
683
+ </Box>
684
+ )
685
+ })}
686
+ </Box>
687
+ <SentryErrorBoundary>
688
+ <Box justifyContent="flex-end" gap={1}>
689
+ <TokenWarning tokenUsage={countTokens(messages)} />
690
+ <AutoUpdater
691
+ debug={debug}
692
+ onAutoUpdaterResult={onAutoUpdaterResult}
693
+ autoUpdaterResult={autoUpdaterResult}
694
+ isUpdating={isAutoUpdating}
695
+ onChangeIsUpdating={setIsAutoUpdating}
696
+ />
697
+ </Box>
698
+ </SentryErrorBoundary>
699
+ </Box>
700
+ )}
701
+ </Box>
702
+ )
703
+ }
704
+
705
+ export default memo(PromptInput)
706
+
707
+ function exit(): never {
708
+ setTerminalTitle('')
709
+ process.exit(0)
710
+ }