@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,33 @@
1
+ import * as React from 'react'
2
+ import { captureException } from '../services/sentry'
3
+
4
+ interface Props {
5
+ children: React.ReactNode
6
+ }
7
+
8
+ interface State {
9
+ hasError: boolean
10
+ }
11
+
12
+ export class SentryErrorBoundary extends React.Component<Props, State> {
13
+ constructor(props: Props) {
14
+ super(props)
15
+ this.state = { hasError: false }
16
+ }
17
+
18
+ static getDerivedStateFromError(): State {
19
+ return { hasError: true }
20
+ }
21
+
22
+ componentDidCatch(error: Error): void {
23
+ captureException(error)
24
+ }
25
+
26
+ render(): React.ReactNode {
27
+ if (this.state.hasError) {
28
+ return null
29
+ }
30
+
31
+ return this.props.children
32
+ }
33
+ }
@@ -0,0 +1,129 @@
1
+ import { Box, Text } from 'ink'
2
+ import * as React from 'react'
3
+ import { useEffect, useRef, useState } from 'react'
4
+ import { getTheme } from '../utils/theme'
5
+ import { sample } from 'lodash-es'
6
+ import { getSessionState } from '../utils/sessionState'
7
+ // NB: The third character in this string is an emoji that
8
+ // renders on Windows consoles with a green background
9
+ const CHARACTERS =
10
+ process.platform === 'darwin'
11
+ ? ['·', '✢', '✳', '∗', '✻', '✽']
12
+ : ['·', '✢', '*', '∗', '✻', '✽']
13
+
14
+ const MESSAGES = [
15
+ 'Accomplishing',
16
+ 'Actioning',
17
+ 'Actualizing',
18
+ 'Baking',
19
+ 'Brewing',
20
+ 'Calculating',
21
+ 'Cerebrating',
22
+ 'Churning',
23
+ 'Coding',
24
+ 'Coalescing',
25
+ 'Cogitating',
26
+ 'Computing',
27
+ 'Conjuring',
28
+ 'Considering',
29
+ 'Cooking',
30
+ 'Crafting',
31
+ 'Creating',
32
+ 'Crunching',
33
+ 'Deliberating',
34
+ 'Determining',
35
+ 'Doing',
36
+ 'Effecting',
37
+ 'Finagling',
38
+ 'Forging',
39
+ 'Forming',
40
+ 'Generating',
41
+ 'Hatching',
42
+ 'Herding',
43
+ 'Honking',
44
+ 'Hustling',
45
+ 'Ideating',
46
+ 'Inferring',
47
+ 'Manifesting',
48
+ 'Marinating',
49
+ 'Moseying',
50
+ 'Mulling',
51
+ 'Mustering',
52
+ 'Musing',
53
+ 'Noodling',
54
+ 'Percolating',
55
+ 'Pondering',
56
+ 'Processing',
57
+ 'Puttering',
58
+ 'Reticulating',
59
+ 'Ruminating',
60
+ 'Schlepping',
61
+ 'Shucking',
62
+ 'Simmering',
63
+ 'Smooshing',
64
+ 'Spinning',
65
+ 'Stewing',
66
+ 'Synthesizing',
67
+ 'Thinking',
68
+ 'Transmuting',
69
+ 'Vibing',
70
+ 'Working',
71
+ ]
72
+
73
+ export function Spinner(): React.ReactNode {
74
+ const frames = [...CHARACTERS, ...[...CHARACTERS].reverse()]
75
+ const [frame, setFrame] = useState(0)
76
+ const [elapsedTime, setElapsedTime] = useState(0)
77
+ const message = useRef(sample(MESSAGES))
78
+ const startTime = useRef(Date.now())
79
+
80
+ useEffect(() => {
81
+ const timer = setInterval(() => {
82
+ setFrame(f => (f + 1) % frames.length)
83
+ }, 120)
84
+
85
+ return () => clearInterval(timer)
86
+ }, [frames.length])
87
+
88
+ useEffect(() => {
89
+ const timer = setInterval(() => {
90
+ setElapsedTime(Math.floor((Date.now() - startTime.current) / 1000))
91
+ }, 1000)
92
+
93
+ return () => clearInterval(timer)
94
+ }, [])
95
+
96
+ return (
97
+ <Box flexDirection="row" marginTop={1}>
98
+ <Box flexWrap="nowrap" height={1} width={2}>
99
+ <Text color={getTheme().claude}>{frames[frame]}</Text>
100
+ </Box>
101
+ <Text color={getTheme().claude}>{message.current}… </Text>
102
+ <Text color={getTheme().secondaryText}>
103
+ ({elapsedTime}s · <Text bold>esc</Text> to interrupt)
104
+ </Text>
105
+ <Text color={getTheme().secondaryText}>
106
+ · {getSessionState('currentError')}
107
+ </Text>
108
+ </Box>
109
+ )
110
+ }
111
+
112
+ export function SimpleSpinner(): React.ReactNode {
113
+ const frames = [...CHARACTERS, ...[...CHARACTERS].reverse()]
114
+ const [frame, setFrame] = useState(0)
115
+
116
+ useEffect(() => {
117
+ const timer = setInterval(() => {
118
+ setFrame(f => (f + 1) % frames.length)
119
+ }, 120)
120
+
121
+ return () => clearInterval(timer)
122
+ }, [frames.length])
123
+
124
+ return (
125
+ <Box flexWrap="nowrap" height={1} width={2}>
126
+ <Text color={getTheme().claude}>{frames[frame]}</Text>
127
+ </Box>
128
+ )
129
+ }
@@ -0,0 +1,184 @@
1
+ import { Box, Text } from 'ink'
2
+ import * as React from 'react'
3
+ import { Hunk } from 'diff'
4
+ import { getTheme, ThemeNames } from '../utils/theme'
5
+ import { useMemo } from 'react'
6
+ import { wrapText } from '../utils/format'
7
+
8
+ type Props = {
9
+ patch: Hunk
10
+ dim: boolean
11
+ width: number
12
+ overrideTheme?: ThemeNames // custom theme for previews
13
+ }
14
+
15
+ export function StructuredDiff({
16
+ patch,
17
+ dim,
18
+ width,
19
+ overrideTheme,
20
+ }: Props): React.ReactNode {
21
+ const diff = useMemo(
22
+ () => formatDiff(patch.lines, patch.oldStart, width, dim, overrideTheme),
23
+ [patch.lines, patch.oldStart, width, dim, overrideTheme],
24
+ )
25
+
26
+ return diff.map((_, i) => <Box key={i}>{_}</Box>)
27
+ }
28
+
29
+ function formatDiff(
30
+ lines: string[],
31
+ startingLineNumber: number,
32
+ width: number,
33
+ dim: boolean,
34
+ overrideTheme?: ThemeNames,
35
+ ): React.ReactNode[] {
36
+ const theme = getTheme(overrideTheme)
37
+
38
+ const ls = numberDiffLines(
39
+ lines.map(code => {
40
+ if (code.startsWith('+')) {
41
+ return {
42
+ code: ' ' + code.slice(1),
43
+ i: 0,
44
+ type: 'add',
45
+ }
46
+ }
47
+ if (code.startsWith('-')) {
48
+ return {
49
+ code: ' ' + code.slice(1),
50
+ i: 0,
51
+ type: 'remove',
52
+ }
53
+ }
54
+ return { code, i: 0, type: 'nochange' }
55
+ }),
56
+ startingLineNumber,
57
+ )
58
+
59
+ const maxLineNumber = Math.max(...ls.map(({ i }) => i))
60
+ const maxWidth = maxLineNumber.toString().length
61
+
62
+ return ls.flatMap(({ type, code, i }) => {
63
+ const wrappedLines = wrapText(code, width - maxWidth)
64
+ return wrappedLines.map((line, lineIndex) => {
65
+ const key = `${type}-${i}-${lineIndex}`
66
+ switch (type) {
67
+ case 'add':
68
+ return (
69
+ <Text key={key}>
70
+ <LineNumber
71
+ i={lineIndex === 0 ? i : undefined}
72
+ width={maxWidth}
73
+ />
74
+ <Text
75
+ color={overrideTheme ? theme.text : undefined}
76
+ backgroundColor={
77
+ dim ? theme.diff.addedDimmed : theme.diff.added
78
+ }
79
+ dimColor={dim}
80
+ >
81
+ {line}
82
+ </Text>
83
+ </Text>
84
+ )
85
+ case 'remove':
86
+ return (
87
+ <Text key={key}>
88
+ <LineNumber
89
+ i={lineIndex === 0 ? i : undefined}
90
+ width={maxWidth}
91
+ />
92
+ <Text
93
+ color={overrideTheme ? theme.text : undefined}
94
+ backgroundColor={
95
+ dim ? theme.diff.removedDimmed : theme.diff.removed
96
+ }
97
+ dimColor={dim}
98
+ >
99
+ {line}
100
+ </Text>
101
+ </Text>
102
+ )
103
+ case 'nochange':
104
+ return (
105
+ <Text key={key}>
106
+ <LineNumber
107
+ i={lineIndex === 0 ? i : undefined}
108
+ width={maxWidth}
109
+ />
110
+ <Text
111
+ color={overrideTheme ? theme.text : undefined}
112
+ dimColor={dim}
113
+ >
114
+ {line}
115
+ </Text>
116
+ </Text>
117
+ )
118
+ }
119
+ })
120
+ })
121
+ }
122
+
123
+ function LineNumber({
124
+ i,
125
+ width,
126
+ }: {
127
+ i: number | undefined
128
+ width: number
129
+ }): React.ReactNode {
130
+ return (
131
+ <Text color={getTheme().secondaryText}>
132
+ {i !== undefined ? i.toString().padStart(width) : ' '.repeat(width)}{' '}
133
+ </Text>
134
+ )
135
+ }
136
+
137
+ function numberDiffLines(
138
+ diff: { code: string; type: string }[],
139
+ startLine: number,
140
+ ): { code: string; type: string; i: number }[] {
141
+ let i = startLine
142
+ const result: { code: string; type: string; i: number }[] = []
143
+ const queue = [...diff]
144
+
145
+ while (queue.length > 0) {
146
+ const { code, type } = queue.shift()!
147
+ const line = {
148
+ code: code,
149
+ type,
150
+ i,
151
+ }
152
+
153
+ // Update counters based on change type
154
+ switch (type) {
155
+ case 'nochange':
156
+ i++
157
+ result.push(line)
158
+ break
159
+ case 'add':
160
+ i++
161
+ result.push(line)
162
+ break
163
+ case 'remove': {
164
+ result.push(line)
165
+ let numRemoved = 0
166
+ while (queue[0]?.type === 'remove') {
167
+ i++
168
+ const { code, type } = queue.shift()!
169
+ const line = {
170
+ code: code,
171
+ type,
172
+ i,
173
+ }
174
+ result.push(line)
175
+ numRemoved++
176
+ }
177
+ i -= numRemoved
178
+ break
179
+ }
180
+ }
181
+ }
182
+
183
+ return result
184
+ }
@@ -0,0 +1,246 @@
1
+ import React from 'react'
2
+ import { Text, useInput } from 'ink'
3
+ import chalk from 'chalk'
4
+ import { useTextInput } from '../hooks/useTextInput'
5
+ import { getTheme } from '../utils/theme'
6
+ import { type Key } from 'ink'
7
+
8
+ export type Props = {
9
+ /**
10
+ * Optional callback for handling history navigation on up arrow at start of input
11
+ */
12
+ readonly onHistoryUp?: () => void
13
+
14
+ /**
15
+ * Optional callback for handling history navigation on down arrow at end of input
16
+ */
17
+ readonly onHistoryDown?: () => void
18
+
19
+ /**
20
+ * Text to display when `value` is empty.
21
+ */
22
+ readonly placeholder?: string
23
+
24
+ /**
25
+ * Allow multi-line input via line ending with backslash (default: `true`)
26
+ */
27
+ readonly multiline?: boolean
28
+
29
+ /**
30
+ * Listen to user's input. Useful in case there are multiple input components
31
+ * at the same time and input must be "routed" to a specific component.
32
+ */
33
+ readonly focus?: boolean
34
+
35
+ /**
36
+ * Replace all chars and mask the value. Useful for password inputs.
37
+ */
38
+ readonly mask?: string
39
+
40
+ /**
41
+ * Whether to show cursor and allow navigation inside text input with arrow keys.
42
+ */
43
+ readonly showCursor?: boolean
44
+
45
+ /**
46
+ * Highlight pasted text
47
+ */
48
+ readonly highlightPastedText?: boolean
49
+
50
+ /**
51
+ * Value to display in a text input.
52
+ */
53
+ readonly value: string
54
+
55
+ /**
56
+ * Function to call when value updates.
57
+ */
58
+ readonly onChange: (value: string) => void
59
+
60
+ /**
61
+ * Function to call when `Enter` is pressed, where first argument is a value of the input.
62
+ */
63
+ readonly onSubmit?: (value: string) => void
64
+
65
+ /**
66
+ * Function to call when Ctrl+C is pressed to exit.
67
+ */
68
+ readonly onExit?: () => void
69
+
70
+ /**
71
+ * Optional callback to show exit message
72
+ */
73
+ readonly onExitMessage?: (show: boolean, key?: string) => void
74
+
75
+ /**
76
+ * Optional callback to show custom message
77
+ */
78
+ readonly onMessage?: (show: boolean, message?: string) => void
79
+
80
+ /**
81
+ * Optional callback to reset history position
82
+ */
83
+ readonly onHistoryReset?: () => void
84
+
85
+ /**
86
+ * Number of columns to wrap text at
87
+ */
88
+ readonly columns: number
89
+
90
+ /**
91
+ * Optional callback when an image is pasted
92
+ */
93
+ readonly onImagePaste?: (base64Image: string) => void
94
+
95
+ /**
96
+ * Optional callback when a large text (over 800 chars) is pasted
97
+ */
98
+ readonly onPaste?: (text: string) => void
99
+
100
+ /**
101
+ * Whether the input is dimmed and non-interactive
102
+ */
103
+ readonly isDimmed?: boolean
104
+
105
+ /**
106
+ * Whether to disable cursor movement for up/down arrow keys
107
+ */
108
+ readonly disableCursorMovementForUpDownKeys?: boolean
109
+
110
+ readonly cursorOffset: number
111
+
112
+ /**
113
+ * Callback to set the offset of the cursor
114
+ */
115
+ onChangeCursorOffset: (offset: number) => void
116
+ }
117
+
118
+ export default function TextInput({
119
+ value: originalValue,
120
+ placeholder = '',
121
+ focus = true,
122
+ mask,
123
+ multiline = false,
124
+ highlightPastedText = false,
125
+ showCursor = true,
126
+ onChange,
127
+ onSubmit,
128
+ onExit,
129
+ onHistoryUp,
130
+ onHistoryDown,
131
+ onExitMessage,
132
+ onMessage,
133
+ onHistoryReset,
134
+ columns,
135
+ onImagePaste,
136
+ onPaste,
137
+ isDimmed = false,
138
+ disableCursorMovementForUpDownKeys = false,
139
+ cursorOffset,
140
+ onChangeCursorOffset,
141
+ }: Props) {
142
+ const { onInput, renderedValue } = useTextInput({
143
+ value: originalValue,
144
+ onChange,
145
+ onSubmit,
146
+ onExit,
147
+ onExitMessage,
148
+ onMessage,
149
+ onHistoryReset,
150
+ onHistoryUp,
151
+ onHistoryDown,
152
+ focus,
153
+ mask,
154
+ multiline,
155
+ cursorChar: showCursor ? ' ' : '',
156
+ highlightPastedText,
157
+ invert: chalk.inverse,
158
+ themeText: (text: string) => chalk.hex(getTheme().text)(text),
159
+ columns,
160
+ onImagePaste,
161
+ disableCursorMovementForUpDownKeys,
162
+ externalOffset: cursorOffset,
163
+ onOffsetChange: onChangeCursorOffset,
164
+ })
165
+
166
+ // Paste detection state
167
+ const [pasteState, setPasteState] = React.useState<{
168
+ chunks: string[]
169
+ timeoutId: ReturnType<typeof setTimeout> | null
170
+ }>({ chunks: [], timeoutId: null })
171
+
172
+ const resetPasteTimeout = (
173
+ currentTimeoutId: ReturnType<typeof setTimeout> | null,
174
+ ) => {
175
+ if (currentTimeoutId) {
176
+ clearTimeout(currentTimeoutId)
177
+ }
178
+ return setTimeout(() => {
179
+ setPasteState(({ chunks }) => {
180
+ const pastedText = chunks.join('')
181
+ // Schedule callback after current render to avoid state updates during render
182
+ Promise.resolve().then(() => onPaste!(pastedText))
183
+ return { chunks: [], timeoutId: null }
184
+ })
185
+ }, 100)
186
+ }
187
+
188
+ const wrappedOnInput = (input: string, key: Key): void => {
189
+ // Special handling for backspace or delete
190
+ if (
191
+ key.backspace ||
192
+ key.delete ||
193
+ input === '\b' ||
194
+ input === '\x7f' ||
195
+ input === '\x08'
196
+ ) {
197
+ // Ensure backspace is handled directly
198
+ onInput(input, {
199
+ ...key,
200
+ backspace: true,
201
+ })
202
+ return
203
+ }
204
+
205
+ // Handle pastes (>800 chars)
206
+ // Usually we get one or two input characters at a time. If we
207
+ // get a bunch, the user has probably pasted.
208
+ // Unfortunately node batches long pastes, so it's possible
209
+ // that we would see e.g. 1024 characters and then just a few
210
+ // more in the next frame that belong with the original paste.
211
+ // This batching number is not consistent.
212
+ if (onPaste && (input.length > 800 || pasteState.timeoutId)) {
213
+ setPasteState(({ chunks, timeoutId }) => {
214
+ return {
215
+ chunks: [...chunks, input],
216
+ timeoutId: resetPasteTimeout(timeoutId),
217
+ }
218
+ })
219
+ return
220
+ }
221
+
222
+ onInput(input, key)
223
+ }
224
+
225
+ useInput(wrappedOnInput, { isActive: focus })
226
+
227
+ let renderedPlaceholder = placeholder
228
+ ? chalk.hex(getTheme().secondaryText)(placeholder)
229
+ : undefined
230
+
231
+ // Fake mouse cursor, because we like punishment
232
+ if (showCursor && focus) {
233
+ renderedPlaceholder =
234
+ placeholder.length > 0
235
+ ? chalk.inverse(placeholder[0]) +
236
+ chalk.hex(getTheme().secondaryText)(placeholder.slice(1))
237
+ : chalk.inverse(' ')
238
+ }
239
+
240
+ const showPlaceholder = originalValue.length == 0 && placeholder
241
+ return (
242
+ <Text wrap="truncate-end" dimColor={isDimmed}>
243
+ {showPlaceholder ? renderedPlaceholder : renderedValue}
244
+ </Text>
245
+ )
246
+ }
@@ -0,0 +1,31 @@
1
+ import { Box, Text } from 'ink'
2
+ import * as React from 'react'
3
+ import { getTheme } from '../utils/theme'
4
+
5
+ type Props = {
6
+ tokenUsage: number
7
+ }
8
+
9
+ const MAX_TOKENS = 190_000
10
+ export const WARNING_THRESHOLD = MAX_TOKENS * 0.6
11
+ const ERROR_THRESHOLD = MAX_TOKENS * 0.8
12
+
13
+ export function TokenWarning({ tokenUsage }: Props): React.ReactNode {
14
+ const theme = getTheme()
15
+
16
+ if (tokenUsage < WARNING_THRESHOLD) {
17
+ return null
18
+ }
19
+
20
+ const isError = tokenUsage >= ERROR_THRESHOLD
21
+
22
+ return (
23
+ <Box flexDirection="row">
24
+ <Text color={isError ? theme.error : theme.warning}>
25
+ Context low (
26
+ {Math.max(0, 100 - Math.round((tokenUsage / MAX_TOKENS) * 100))}%
27
+ remaining) &middot; Run /compact to compact & continue
28
+ </Text>
29
+ </Box>
30
+ )
31
+ }
@@ -0,0 +1,40 @@
1
+ import { Box, Text } from 'ink'
2
+ import React from 'react'
3
+ import { useInterval } from '../hooks/useInterval'
4
+ import { getTheme } from '../utils/theme'
5
+ import { BLACK_CIRCLE } from '../constants/figures'
6
+
7
+ type Props = {
8
+ isError: boolean
9
+ isUnresolved: boolean
10
+ shouldAnimate: boolean
11
+ }
12
+
13
+ export function ToolUseLoader({
14
+ isError,
15
+ isUnresolved,
16
+ shouldAnimate,
17
+ }: Props): React.ReactNode {
18
+ const [isVisible, setIsVisible] = React.useState(true)
19
+
20
+ useInterval(() => {
21
+ if (!shouldAnimate) {
22
+ return
23
+ }
24
+ // To avoid flickering when the tool use confirm is visible, we set the loader to be visible
25
+ // when the tool use confirm is visible.
26
+ setIsVisible(_ => !_)
27
+ }, 600)
28
+
29
+ const color = isUnresolved
30
+ ? getTheme().secondaryText
31
+ : isError
32
+ ? getTheme().error
33
+ : getTheme().success
34
+
35
+ return (
36
+ <Box minWidth={2}>
37
+ <Text color={color}>{isVisible ? BLACK_CIRCLE : ' '}</Text>
38
+ </Box>
39
+ )
40
+ }