@shareai-lab/kode 1.0.70 → 1.0.73

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