@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,100 @@
1
+ import React from 'react'
2
+ import { Box, Text, useInput } from 'ink'
3
+ import { getTheme } from '../utils/theme'
4
+ import { Select } from '@inkjs/ui'
5
+ import {
6
+ saveCurrentProjectConfig,
7
+ getCurrentProjectConfig,
8
+ } from '../utils/config.js'
9
+ import { MCPServerDialogCopy } from './MCPServerDialogCopy'
10
+ import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
11
+
12
+ type Props = {
13
+ serverName: string
14
+ onDone(): void
15
+ }
16
+
17
+ export function MCPServerApprovalDialog({
18
+ serverName,
19
+ onDone,
20
+ }: Props): React.ReactNode {
21
+ const theme = getTheme()
22
+ function onChange(value: 'yes' | 'no') {
23
+ const config = getCurrentProjectConfig()
24
+ switch (value) {
25
+ case 'yes': {
26
+ if (!config.approvedMcprcServers) {
27
+ config.approvedMcprcServers = []
28
+ }
29
+ if (!config.approvedMcprcServers.includes(serverName)) {
30
+ config.approvedMcprcServers.push(serverName)
31
+ }
32
+ saveCurrentProjectConfig(config)
33
+ onDone()
34
+ break
35
+ }
36
+ case 'no': {
37
+ if (!config.rejectedMcprcServers) {
38
+ config.rejectedMcprcServers = []
39
+ }
40
+ if (!config.rejectedMcprcServers.includes(serverName)) {
41
+ config.rejectedMcprcServers.push(serverName)
42
+ }
43
+ saveCurrentProjectConfig(config)
44
+ onDone()
45
+ break
46
+ }
47
+ }
48
+ }
49
+
50
+ const exitState = useExitOnCtrlCD(() => process.exit(0))
51
+
52
+ useInput((_input, key) => {
53
+ if (key.escape) {
54
+ onDone()
55
+ return
56
+ }
57
+ })
58
+
59
+ return (
60
+ <>
61
+ <Box
62
+ flexDirection="column"
63
+ gap={1}
64
+ padding={1}
65
+ borderStyle="round"
66
+ borderColor={theme.warning}
67
+ >
68
+ <Text bold color={theme.warning}>
69
+ New MCP Server Detected
70
+ </Text>
71
+ <Text>
72
+ This project contains a .mcprc file with an MCP server that requires
73
+ your approval:
74
+ </Text>
75
+ <Text bold>{serverName}</Text>
76
+
77
+ <MCPServerDialogCopy />
78
+
79
+ <Text>Do you want to approve this MCP server?</Text>
80
+
81
+ <Select
82
+ options={[
83
+ { label: 'Yes, approve this server', value: 'yes' },
84
+ { label: 'No, reject this server', value: 'no' },
85
+ ]}
86
+ onChange={value => onChange(value as 'yes' | 'no')}
87
+ />
88
+ </Box>
89
+ <Box marginLeft={3}>
90
+ <Text dimColor>
91
+ {exitState.pending ? (
92
+ <>Press {exitState.keyName} again to exit</>
93
+ ) : (
94
+ <>Enter to confirm · Esc to reject</>
95
+ )}
96
+ </Text>
97
+ </Box>
98
+ </>
99
+ )
100
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react'
2
+ import { Text } from 'ink'
3
+ import Link from 'ink-link'
4
+ import { PRODUCT_NAME, PRODUCT_COMMAND } from '../constants/product'
5
+
6
+ export function MCPServerDialogCopy(): React.ReactNode {
7
+ return (
8
+ <>
9
+ <Text>
10
+ MCP servers provide additional functionality to {PRODUCT_NAME}. They may
11
+ execute code, make network requests, or access system resources via tool
12
+ calls. All tool calls will require your explicit approval before
13
+ execution. For more information, see{' '}
14
+ <Link url="https://docs.anthropic.com/s/claude-code-mcp">
15
+ MCP documentation
16
+ </Link>
17
+ </Text>
18
+
19
+ <Text dimColor>
20
+ Remember: You can always change these choices later by running `
21
+ {PRODUCT_COMMAND} mcp reset-mcprc-choices`
22
+ </Text>
23
+ </>
24
+ )
25
+ }
@@ -0,0 +1,109 @@
1
+ import React from 'react'
2
+ import { Box, Text, useInput } from 'ink'
3
+ import { getTheme } from '../utils/theme'
4
+ import { MultiSelect } from '@inkjs/ui'
5
+ import {
6
+ saveCurrentProjectConfig,
7
+ getCurrentProjectConfig,
8
+ } from '../utils/config.js'
9
+ import { partition } from 'lodash-es'
10
+ import { MCPServerDialogCopy } from './MCPServerDialogCopy'
11
+ import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
12
+
13
+ type Props = {
14
+ serverNames: string[]
15
+ onDone(): void
16
+ }
17
+
18
+ export function MCPServerMultiselectDialog({
19
+ serverNames,
20
+ onDone,
21
+ }: Props): React.ReactNode {
22
+ const theme = getTheme()
23
+ function onSubmit(selectedServers: string[]) {
24
+ const config = getCurrentProjectConfig()
25
+
26
+ // Initialize arrays if they don't exist
27
+ if (!config.approvedMcprcServers) {
28
+ config.approvedMcprcServers = []
29
+ }
30
+ if (!config.rejectedMcprcServers) {
31
+ config.rejectedMcprcServers = []
32
+ }
33
+
34
+ // Use partition to separate approved and rejected servers
35
+ const [approvedServers, rejectedServers] = partition(serverNames, server =>
36
+ selectedServers.includes(server),
37
+ )
38
+
39
+ // Add new servers directly to the respective lists
40
+ config.approvedMcprcServers.push(...approvedServers)
41
+ config.rejectedMcprcServers.push(...rejectedServers)
42
+
43
+ saveCurrentProjectConfig(config)
44
+ onDone()
45
+ }
46
+
47
+ const exitState = useExitOnCtrlCD(() => process.exit())
48
+
49
+ useInput((_input, key) => {
50
+ if (key.escape) {
51
+ // On escape, treat all servers as rejected
52
+ const config = getCurrentProjectConfig()
53
+ if (!config.rejectedMcprcServers) {
54
+ config.rejectedMcprcServers = []
55
+ }
56
+
57
+ for (const server of serverNames) {
58
+ if (!config.rejectedMcprcServers.includes(server)) {
59
+ config.rejectedMcprcServers.push(server)
60
+ }
61
+ }
62
+
63
+ saveCurrentProjectConfig(config)
64
+ onDone()
65
+ return
66
+ }
67
+ })
68
+
69
+ return (
70
+ <>
71
+ <Box
72
+ flexDirection="column"
73
+ gap={1}
74
+ padding={1}
75
+ borderStyle="round"
76
+ borderColor={theme.warning}
77
+ >
78
+ <Text bold color={theme.warning}>
79
+ New MCP Servers Detected
80
+ </Text>
81
+ <Text>
82
+ This project contains a .mcprc file with {serverNames.length} MCP
83
+ servers that require your approval.
84
+ </Text>
85
+ <MCPServerDialogCopy />
86
+
87
+ <Text>Please select the servers you want to enable:</Text>
88
+
89
+ <MultiSelect
90
+ options={serverNames.map(server => ({
91
+ label: server,
92
+ value: server,
93
+ }))}
94
+ defaultValue={serverNames}
95
+ onSubmit={onSubmit}
96
+ />
97
+ </Box>
98
+ <Box marginLeft={3}>
99
+ <Text dimColor>
100
+ {exitState.pending ? (
101
+ <>Press {exitState.keyName} again to exit</>
102
+ ) : (
103
+ <>Space to select · Enter to confirm · Esc to reject all</>
104
+ )}
105
+ </Text>
106
+ </Box>
107
+ </>
108
+ )
109
+ }
@@ -0,0 +1,219 @@
1
+ import { Box } from 'ink'
2
+ import * as React from 'react'
3
+ import type { AssistantMessage, Message, UserMessage } from '../query'
4
+ import type {
5
+ ContentBlock,
6
+ DocumentBlockParam,
7
+ ImageBlockParam,
8
+ TextBlockParam,
9
+ ThinkingBlockParam,
10
+ ToolResultBlockParam,
11
+ ToolUseBlockParam,
12
+ } from '@anthropic-ai/sdk/resources/index.mjs'
13
+ import { Tool } from '../Tool'
14
+ import { logError } from '../utils/log'
15
+ import { UserToolResultMessage } from './messages/UserToolResultMessage/UserToolResultMessage'
16
+ import { AssistantToolUseMessage } from './messages/AssistantToolUseMessage'
17
+ import { AssistantTextMessage } from './messages/AssistantTextMessage'
18
+ import { UserTextMessage } from './messages/UserTextMessage'
19
+ import { NormalizedMessage } from '../utils/messages'
20
+ import { AssistantThinkingMessage } from './messages/AssistantThinkingMessage'
21
+ import { AssistantRedactedThinkingMessage } from './messages/AssistantRedactedThinkingMessage'
22
+ import { useTerminalSize } from '../hooks/useTerminalSize'
23
+
24
+ type Props = {
25
+ message: UserMessage | AssistantMessage
26
+ messages: NormalizedMessage[]
27
+ // TODO: Find a way to remove this, and leave spacing to the consumer
28
+ addMargin: boolean
29
+ tools: Tool[]
30
+ verbose: boolean
31
+ debug: boolean
32
+ erroredToolUseIDs: Set<string>
33
+ inProgressToolUseIDs: Set<string>
34
+ unresolvedToolUseIDs: Set<string>
35
+ shouldAnimate: boolean
36
+ shouldShowDot: boolean
37
+ width?: number | string
38
+ }
39
+
40
+ export function Message({
41
+ message,
42
+ messages,
43
+ addMargin,
44
+ tools,
45
+ verbose,
46
+ debug,
47
+ erroredToolUseIDs,
48
+ inProgressToolUseIDs,
49
+ unresolvedToolUseIDs,
50
+ shouldAnimate,
51
+ shouldShowDot,
52
+ width,
53
+ }: Props): React.ReactNode {
54
+ // Assistant message
55
+ if (message.type === 'assistant') {
56
+ return (
57
+ <Box flexDirection="column" width="100%">
58
+ {message.message.content.map((_, index) => (
59
+ <AssistantMessage
60
+ key={index}
61
+ param={_}
62
+ costUSD={message.costUSD}
63
+ durationMs={message.durationMs}
64
+ addMargin={addMargin}
65
+ tools={tools}
66
+ debug={debug}
67
+ options={{ verbose }}
68
+ erroredToolUseIDs={erroredToolUseIDs}
69
+ inProgressToolUseIDs={inProgressToolUseIDs}
70
+ unresolvedToolUseIDs={unresolvedToolUseIDs}
71
+ shouldAnimate={shouldAnimate}
72
+ shouldShowDot={shouldShowDot}
73
+ width={width}
74
+ />
75
+ ))}
76
+ </Box>
77
+ )
78
+ }
79
+
80
+ // User message
81
+ // TODO: normalize upstream
82
+ const content =
83
+ typeof message.message.content === 'string'
84
+ ? [{ type: 'text', text: message.message.content } as TextBlockParam]
85
+ : message.message.content
86
+ return (
87
+ <Box flexDirection="column" width="100%">
88
+ {content.map((_, index) => (
89
+ <UserMessage
90
+ key={index}
91
+ message={message}
92
+ messages={messages}
93
+ addMargin={addMargin}
94
+ tools={tools}
95
+ param={_ as TextBlockParam}
96
+ options={{ verbose }}
97
+ />
98
+ ))}
99
+ </Box>
100
+ )
101
+ }
102
+
103
+ function UserMessage({
104
+ message,
105
+ messages,
106
+ addMargin,
107
+ tools,
108
+ param,
109
+ options: { verbose },
110
+ }: {
111
+ message: UserMessage
112
+ messages: Message[]
113
+ addMargin: boolean
114
+ tools: Tool[]
115
+ param:
116
+ | TextBlockParam
117
+ | DocumentBlockParam
118
+ | ImageBlockParam
119
+ | ToolUseBlockParam
120
+ | ToolResultBlockParam
121
+ options: {
122
+ verbose: boolean
123
+ }
124
+ }): React.ReactNode {
125
+ const { columns } = useTerminalSize()
126
+ switch (param.type) {
127
+ case 'text':
128
+ return <UserTextMessage addMargin={addMargin} param={param} />
129
+ case 'tool_result':
130
+ return (
131
+ <UserToolResultMessage
132
+ param={param}
133
+ message={message}
134
+ messages={messages}
135
+ tools={tools}
136
+ verbose={verbose}
137
+ width={columns - 5}
138
+ />
139
+ )
140
+ }
141
+ }
142
+
143
+ function AssistantMessage({
144
+ param,
145
+ costUSD,
146
+ durationMs,
147
+ addMargin,
148
+ tools,
149
+ debug,
150
+ options: { verbose },
151
+ erroredToolUseIDs,
152
+ inProgressToolUseIDs,
153
+ unresolvedToolUseIDs,
154
+ shouldAnimate,
155
+ shouldShowDot,
156
+ width,
157
+ }: {
158
+ param:
159
+ | ContentBlock
160
+ | TextBlockParam
161
+ | ImageBlockParam
162
+ | ThinkingBlockParam
163
+ | ToolUseBlockParam
164
+ | ToolResultBlockParam
165
+ costUSD: number
166
+ durationMs: number
167
+ addMargin: boolean
168
+ tools: Tool[]
169
+ debug: boolean
170
+ options: {
171
+ verbose: boolean
172
+ }
173
+ erroredToolUseIDs: Set<string>
174
+ inProgressToolUseIDs: Set<string>
175
+ unresolvedToolUseIDs: Set<string>
176
+ shouldAnimate: boolean
177
+ shouldShowDot: boolean
178
+ width?: number | string
179
+ }): React.ReactNode {
180
+ switch (param.type) {
181
+ case 'tool_use':
182
+ return (
183
+ <AssistantToolUseMessage
184
+ param={param}
185
+ costUSD={costUSD}
186
+ durationMs={durationMs}
187
+ addMargin={addMargin}
188
+ tools={tools}
189
+ debug={debug}
190
+ verbose={verbose}
191
+ erroredToolUseIDs={erroredToolUseIDs}
192
+ inProgressToolUseIDs={inProgressToolUseIDs}
193
+ unresolvedToolUseIDs={unresolvedToolUseIDs}
194
+ shouldAnimate={shouldAnimate}
195
+ shouldShowDot={shouldShowDot}
196
+ />
197
+ )
198
+ case 'text':
199
+ return (
200
+ <AssistantTextMessage
201
+ param={param}
202
+ costUSD={costUSD}
203
+ durationMs={durationMs}
204
+ debug={debug}
205
+ addMargin={addMargin}
206
+ shouldShowDot={shouldShowDot}
207
+ verbose={verbose}
208
+ width={width}
209
+ />
210
+ )
211
+ case 'redacted_thinking':
212
+ return <AssistantRedactedThinkingMessage addMargin={addMargin} />
213
+ case 'thinking':
214
+ return <AssistantThinkingMessage addMargin={addMargin} param={param} />
215
+ default:
216
+ logError(`Unable to render message type: ${param.type}`)
217
+ return null
218
+ }
219
+ }
@@ -0,0 +1,15 @@
1
+ import { Box, Text } from 'ink'
2
+ import * as React from 'react'
3
+
4
+ type Props = {
5
+ children: React.ReactNode
6
+ }
7
+
8
+ export function MessageResponse({ children }: Props): React.ReactNode {
9
+ return (
10
+ <Box flexDirection="row" height={1} overflow="hidden">
11
+ <Text>{' '}⎿ &nbsp;</Text>
12
+ {children}
13
+ </Box>
14
+ )
15
+ }
@@ -0,0 +1,211 @@
1
+ import { Box, Text, useInput } from 'ink'
2
+ import * as React from 'react'
3
+ import { useMemo, useState, useEffect } from 'react'
4
+ import figures from 'figures'
5
+ import { getTheme } from '../utils/theme'
6
+ import { Message as MessageComponent } from './Message'
7
+ import { randomUUID } from 'crypto'
8
+ import { type Tool } from '../Tool'
9
+ import {
10
+ createUserMessage,
11
+ isEmptyMessageText,
12
+ isNotEmptyMessage,
13
+ normalizeMessages,
14
+ } from '../utils/messages.js'
15
+ import { logEvent } from '../services/statsig'
16
+ import type { AssistantMessage, UserMessage } from '../query'
17
+ import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
18
+
19
+ type Props = {
20
+ erroredToolUseIDs: Set<string>
21
+ messages: (UserMessage | AssistantMessage)[]
22
+ onSelect: (message: UserMessage) => void
23
+ onEscape: () => void
24
+ tools: Tool[]
25
+ unresolvedToolUseIDs: Set<string>
26
+ }
27
+
28
+ const MAX_VISIBLE_MESSAGES = 7
29
+
30
+ export function MessageSelector({
31
+ erroredToolUseIDs,
32
+ messages,
33
+ onSelect,
34
+ onEscape,
35
+ tools,
36
+ unresolvedToolUseIDs,
37
+ }: Props): React.ReactNode {
38
+ const currentUUID = useMemo(randomUUID, [])
39
+
40
+ // Log when selector is opened
41
+ useEffect(() => {
42
+ logEvent('tengu_message_selector_opened', {})
43
+ }, [])
44
+
45
+ function handleSelect(message: UserMessage) {
46
+ const indexFromEnd = messages.length - 1 - messages.indexOf(message)
47
+ logEvent('tengu_message_selector_selected', {
48
+ index_from_end: indexFromEnd.toString(),
49
+ message_type: message.type,
50
+ is_current_prompt: (message.uuid === currentUUID).toString(),
51
+ })
52
+ onSelect(message)
53
+ }
54
+
55
+ function handleEscape() {
56
+ logEvent('tengu_message_selector_cancelled', {})
57
+ onEscape()
58
+ }
59
+
60
+ // Add current prompt as a virtual message
61
+ const allItems = useMemo(
62
+ () => [
63
+ // Filter out tool results
64
+ ...messages
65
+ .filter(
66
+ _ =>
67
+ !(
68
+ _.type === 'user' &&
69
+ Array.isArray(_.message.content) &&
70
+ _.message.content[0]?.type === 'tool_result'
71
+ ),
72
+ )
73
+ // Filter out assistant messages, until we have a way to kick off the tool use loop from REPL
74
+ .filter(_ => _.type !== 'assistant'),
75
+ { ...createUserMessage(''), uuid: currentUUID } as UserMessage,
76
+ ],
77
+ [messages, currentUUID],
78
+ )
79
+ const [selectedIndex, setSelectedIndex] = useState(allItems.length - 1)
80
+
81
+ const exitState = useExitOnCtrlCD(() => process.exit(0))
82
+
83
+ useInput((input, key) => {
84
+ if (key.tab || key.escape) {
85
+ handleEscape()
86
+ return
87
+ }
88
+ if (key.return) {
89
+ handleSelect(allItems[selectedIndex]!)
90
+ return
91
+ }
92
+ if (key.upArrow) {
93
+ if (key.ctrl || key.shift || key.meta) {
94
+ // Jump to top with any modifier key
95
+ setSelectedIndex(0)
96
+ } else {
97
+ setSelectedIndex(prev => Math.max(0, prev - 1))
98
+ }
99
+ }
100
+ if (key.downArrow) {
101
+ if (key.ctrl || key.shift || key.meta) {
102
+ // Jump to bottom with any modifier key
103
+ setSelectedIndex(allItems.length - 1)
104
+ } else {
105
+ setSelectedIndex(prev => Math.min(allItems.length - 1, prev + 1))
106
+ }
107
+ }
108
+
109
+ // Handle number keys (1-9)
110
+ const num = Number(input)
111
+ if (!isNaN(num) && num >= 1 && num <= Math.min(9, allItems.length)) {
112
+ if (!allItems[num - 1]) {
113
+ return
114
+ }
115
+ handleSelect(allItems[num - 1]!)
116
+ }
117
+ })
118
+
119
+ const firstVisibleIndex = Math.max(
120
+ 0,
121
+ Math.min(
122
+ selectedIndex - Math.floor(MAX_VISIBLE_MESSAGES / 2),
123
+ allItems.length - MAX_VISIBLE_MESSAGES,
124
+ ),
125
+ )
126
+
127
+ const normalizedMessages = useMemo(
128
+ () => normalizeMessages(messages).filter(isNotEmptyMessage),
129
+ [messages],
130
+ )
131
+
132
+ return (
133
+ <>
134
+ <Box
135
+ flexDirection="column"
136
+ borderStyle="round"
137
+ borderColor={getTheme().secondaryBorder}
138
+ height={4 + Math.min(MAX_VISIBLE_MESSAGES, allItems.length) * 2}
139
+ paddingX={1}
140
+ marginTop={1}
141
+ >
142
+ <Box flexDirection="column" minHeight={2} marginBottom={1}>
143
+ <Text bold>Jump to a previous message</Text>
144
+ <Text dimColor>This will fork the conversation</Text>
145
+ </Box>
146
+ {allItems
147
+ .slice(firstVisibleIndex, firstVisibleIndex + MAX_VISIBLE_MESSAGES)
148
+ .map((msg, index) => {
149
+ const actualIndex = firstVisibleIndex + index
150
+ const isSelected = actualIndex === selectedIndex
151
+ const isCurrent = msg.uuid === currentUUID
152
+
153
+ return (
154
+ <Box key={msg.uuid} flexDirection="row" height={2} minHeight={2}>
155
+ <Box width={7}>
156
+ {isSelected ? (
157
+ <Text color="blue" bold>
158
+ {figures.pointer} {firstVisibleIndex + index + 1}{' '}
159
+ </Text>
160
+ ) : (
161
+ <Text>
162
+ {' '}
163
+ {firstVisibleIndex + index + 1}{' '}
164
+ </Text>
165
+ )}
166
+ </Box>
167
+ <Box height={1} overflow="hidden" width={100}>
168
+ {isCurrent ? (
169
+ <Box width="100%">
170
+ <Text dimColor italic>
171
+ {'(current)'}
172
+ </Text>
173
+ </Box>
174
+ ) : Array.isArray(msg.message.content) &&
175
+ msg.message.content[0]?.type === 'text' &&
176
+ isEmptyMessageText(msg.message.content[0].text) ? (
177
+ <Text dimColor italic>
178
+ (empty message)
179
+ </Text>
180
+ ) : (
181
+ <MessageComponent
182
+ message={msg}
183
+ messages={normalizedMessages}
184
+ addMargin={false}
185
+ tools={tools}
186
+ verbose={false}
187
+ debug={false}
188
+ erroredToolUseIDs={erroredToolUseIDs}
189
+ inProgressToolUseIDs={new Set()}
190
+ unresolvedToolUseIDs={unresolvedToolUseIDs}
191
+ shouldAnimate={false}
192
+ shouldShowDot={false}
193
+ />
194
+ )}
195
+ </Box>
196
+ </Box>
197
+ )
198
+ })}
199
+ </Box>
200
+ <Box marginLeft={3}>
201
+ <Text dimColor>
202
+ {exitState.pending ? (
203
+ <>Press {exitState.keyName} again to exit</>
204
+ ) : (
205
+ <>↑/↓ to select · Enter to confirm · Tab/Esc to cancel</>
206
+ )}
207
+ </Text>
208
+ </Box>
209
+ </>
210
+ )
211
+ }