@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,197 @@
1
+ import React, { useState } from 'react'
2
+ import { Box, Text } from 'ink'
3
+ import { Select } from '../components/CustomSelect/select'
4
+ import TextInput from '../components/TextInput'
5
+ import { SimpleSpinner } from '../components/Spinner'
6
+ import { getTheme } from '../utils/theme'
7
+ import { useTerminalSize } from '../hooks/useTerminalSize'
8
+ import { PRODUCT_NAME } from '../constants/product'
9
+ import { setupNewPrefix, installGlobalPackage } from '../utils/autoUpdater'
10
+ import { logError } from '../utils/log'
11
+ import { logEvent } from '../services/statsig'
12
+ import { MACRO } from '../constants/macros'
13
+ type Props = {
14
+ customPrefix: string
15
+ onCustomPrefixChange: (value: string) => void
16
+ onSuccess: () => void
17
+ onCancel: () => void
18
+ }
19
+
20
+ export function ConfigureNpmPrefix({
21
+ customPrefix,
22
+ onCustomPrefixChange,
23
+ onSuccess,
24
+ onCancel,
25
+ }: Props): React.ReactNode {
26
+ const [cursorOffset, setCursorOffset] = useState(customPrefix.length)
27
+ const [showConfirmation, setShowConfirmation] = useState(false)
28
+ const [isSettingUpPrefix, setIsSettingUpPrefix] = useState(false)
29
+ const [error, setError] = useState<string | null>(null)
30
+ const [stepsStatus, setStepsStatus] = useState<{
31
+ completeSteps: boolean[]
32
+ inProgressStep: number | null
33
+ }>({
34
+ completeSteps: [false, false, false, false],
35
+ inProgressStep: null,
36
+ })
37
+ const textInputColumns = useTerminalSize().columns - 6
38
+ const theme = getTheme()
39
+
40
+ async function handleSetupNewPrefix(prefix: string) {
41
+ setIsSettingUpPrefix(true)
42
+ setError(null)
43
+
44
+ try {
45
+ // Reset status
46
+ setStepsStatus({
47
+ completeSteps: [false, false, false, false],
48
+ inProgressStep: 0,
49
+ })
50
+
51
+ // Start first three steps
52
+ await setupNewPrefix(prefix)
53
+ setStepsStatus({
54
+ completeSteps: [true, true, true, false],
55
+ inProgressStep: 3,
56
+ })
57
+
58
+ // Start install step
59
+ await installGlobalPackage()
60
+ setStepsStatus({
61
+ completeSteps: [true, true, true, true],
62
+ inProgressStep: null,
63
+ })
64
+
65
+ logEvent('tengu_auto_updater_config_complete', {
66
+ finalStatus: 'enabled',
67
+ method: 'prefix',
68
+ success: 'true',
69
+ })
70
+
71
+ onSuccess()
72
+ } catch (err) {
73
+ logError(err)
74
+ const errorMessage =
75
+ err instanceof Error ? err.message : 'Failed to setup npm prefix'
76
+ setError(errorMessage)
77
+ setIsSettingUpPrefix(false)
78
+
79
+ logEvent('tengu_auto_updater_config_complete', {
80
+ finalStatus: 'not_configured',
81
+ method: 'prefix',
82
+ success: 'false',
83
+ error: errorMessage,
84
+ })
85
+ }
86
+ }
87
+
88
+ const installSteps = [
89
+ {
90
+ label: 'Create new directory for npm global packages',
91
+ command: `mkdir -p ${customPrefix}`,
92
+ },
93
+ {
94
+ label: 'Configure npm to use new location',
95
+ command: `npm -g config set prefix ${customPrefix}`,
96
+ },
97
+ {
98
+ label: 'Update shell PATH configuration',
99
+ command: `export PATH=${customPrefix}/bin:$PATH`,
100
+ },
101
+ {
102
+ label: `Reinstall ${PRODUCT_NAME} globally`,
103
+ command: `npm install -g ${MACRO.PACKAGE_URL}`,
104
+ },
105
+ ]
106
+
107
+ return (
108
+ <Box marginLeft={2} flexDirection="column">
109
+ <Box flexDirection="column" gap={1}>
110
+ <Text>
111
+ ⚠️ Warning: This will modify your global npm configuration and can be
112
+ dangerous. The following changes will be made:
113
+ </Text>
114
+ {installSteps.map((step, index) => (
115
+ <Box key={index} flexDirection="column">
116
+ <Box flexDirection="row">
117
+ <Text
118
+ color={
119
+ stepsStatus.completeSteps[index] ? theme.success : undefined
120
+ }
121
+ >
122
+ {isSettingUpPrefix
123
+ ? stepsStatus.completeSteps[index]
124
+ ? '✓'
125
+ : ' '
126
+ : `${index + 1}.`}
127
+ </Text>
128
+ <Box width={2}>
129
+ {stepsStatus.inProgressStep === index && <SimpleSpinner />}
130
+ </Box>
131
+ <Text
132
+ color={
133
+ stepsStatus.completeSteps[index] ? theme.success : undefined
134
+ }
135
+ >
136
+ {step.label}
137
+ </Text>
138
+ </Box>
139
+ {step.command && (
140
+ <Box marginLeft={2}>
141
+ <Text color={theme.suggestion} dimColor>
142
+ $ {step.command}
143
+ </Text>
144
+ </Box>
145
+ )}
146
+ </Box>
147
+ ))}
148
+
149
+ <Text color={theme.suggestion}>
150
+ Note: You&apos;ll need to restart your terminal after this change
151
+ </Text>
152
+ <Text color={theme.warning}>
153
+ Important: Any existing global npm packages may need to be reinstalled
154
+ </Text>
155
+ </Box>
156
+ {!isSettingUpPrefix && (
157
+ <Box marginTop={1} flexDirection="column">
158
+ <Text>Enter prefix path:</Text>
159
+ <Box flexDirection="row" gap={1}>
160
+ <Text>&gt;</Text>
161
+ <TextInput
162
+ placeholder={customPrefix}
163
+ value={customPrefix}
164
+ onChange={onCustomPrefixChange}
165
+ onSubmit={() => setShowConfirmation(true)}
166
+ columns={textInputColumns}
167
+ cursorOffset={cursorOffset}
168
+ onChangeCursorOffset={setCursorOffset}
169
+ />
170
+ </Box>
171
+ {showConfirmation && (
172
+ <Box marginTop={1} flexDirection="column">
173
+ <Text>
174
+ Are you sure you want to continue with prefix: {customPrefix}?
175
+ </Text>
176
+ <Select
177
+ options={[
178
+ { label: 'Yes', value: 'yes' },
179
+ { label: 'No', value: 'no' },
180
+ ]}
181
+ onChange={(value: string) => {
182
+ setShowConfirmation(false)
183
+ if (value === 'yes') {
184
+ handleSetupNewPrefix(customPrefix)
185
+ } else {
186
+ onCancel()
187
+ }
188
+ }}
189
+ />
190
+ </Box>
191
+ )}
192
+ </Box>
193
+ )}
194
+ {error && <Text color={theme.error}>Error: {error}</Text>}
195
+ </Box>
196
+ )
197
+ }
@@ -0,0 +1,219 @@
1
+ import React, { useCallback, useEffect, useState } from 'react'
2
+ import { Box, Text, useInput } from 'ink'
3
+ import { Select } from '../components/CustomSelect/select'
4
+ import { getTheme } from '../utils/theme'
5
+ import { ConfigureNpmPrefix } from './ConfigureNpmPrefix.tsx'
6
+ import { platform } from 'process'
7
+ import {
8
+ checkNpmPermissions,
9
+ getDefaultNpmPrefix,
10
+ getPermissionsCommand,
11
+ } from '../utils/autoUpdater.js'
12
+ import { saveGlobalConfig, getGlobalConfig } from '../utils/config'
13
+ import { logEvent } from '../services/statsig'
14
+ import { PRODUCT_NAME } from '../constants/product'
15
+ import { PressEnterToContinue } from '../components/PressEnterToContinue'
16
+
17
+ type Props = {
18
+ onDone: () => void
19
+ doctorMode?: boolean
20
+ }
21
+
22
+ type Option = {
23
+ label: string
24
+ value: 'auto' | 'manual' | 'ignore'
25
+ description: string
26
+ }
27
+
28
+ export function Doctor({ onDone, doctorMode = false }: Props): React.ReactNode {
29
+ const [hasPermissions, setHasPermissions] = useState<boolean | null>(null)
30
+ const [npmPrefix, setNpmPrefix] = useState<string | null>(null)
31
+ const [selectedOption, setSelectedOption] = useState<Option['value'] | null>(
32
+ null,
33
+ )
34
+ const [customPrefix, setCustomPrefix] = useState<string>(
35
+ getDefaultNpmPrefix(),
36
+ )
37
+ const theme = getTheme()
38
+ const [showingPermissionsMessage, setShowingPermissionsMessage] =
39
+ useState(false)
40
+
41
+ const options: Option[] = [
42
+ {
43
+ label: `Manually fix permissions on current npm prefix (Recommended)`,
44
+ value: 'manual',
45
+ description:
46
+ platform === 'win32'
47
+ ? 'Uses icacls to grant write permissions'
48
+ : 'Uses sudo to change ownership',
49
+ },
50
+ {
51
+ label: 'Create new npm prefix directory',
52
+ value: 'auto',
53
+ description:
54
+ 'Creates a new directory for global npm packages in your home directory',
55
+ },
56
+ {
57
+ label: 'Skip configuration until next session',
58
+ value: 'ignore',
59
+ description: 'Skip this warning (you will be reminded again later)',
60
+ },
61
+ ]
62
+
63
+ const checkPermissions = useCallback(async () => {
64
+ const result = await checkNpmPermissions()
65
+ logEvent('tengu_auto_updater_permissions_check', {
66
+ hasPermissions: result.hasPermissions.toString(),
67
+ npmPrefix: result.npmPrefix ?? 'null',
68
+ })
69
+ setHasPermissions(result.hasPermissions)
70
+ if (result.npmPrefix) {
71
+ setNpmPrefix(result.npmPrefix)
72
+ }
73
+ if (result.hasPermissions) {
74
+ const config = getGlobalConfig()
75
+ saveGlobalConfig({
76
+ ...config,
77
+ autoUpdaterStatus: 'enabled',
78
+ })
79
+ if (!doctorMode) {
80
+ onDone()
81
+ }
82
+ }
83
+ }, [onDone, doctorMode])
84
+
85
+ useEffect(() => {
86
+ logEvent('tengu_auto_updater_config_start', {})
87
+ checkPermissions()
88
+ }, [checkPermissions])
89
+
90
+ useInput(
91
+ (_input, key) => {
92
+ if (
93
+ (showingPermissionsMessage ||
94
+ (doctorMode && hasPermissions === true)) &&
95
+ key.return
96
+ ) {
97
+ onDone()
98
+ }
99
+ },
100
+ {
101
+ isActive:
102
+ showingPermissionsMessage || (doctorMode && hasPermissions === true),
103
+ },
104
+ )
105
+
106
+ if (hasPermissions === null) {
107
+ return (
108
+ <Box paddingX={1} paddingTop={1}>
109
+ <Text color={theme.secondaryText}>Checking npm permissions…</Text>
110
+ </Box>
111
+ )
112
+ }
113
+
114
+ if (hasPermissions === true) {
115
+ if (doctorMode) {
116
+ return (
117
+ <Box flexDirection="column" gap={1} paddingX={1} paddingTop={1}>
118
+ <Text color={theme.success}>✓ npm permissions: OK</Text>
119
+ <Text>Your installation is healthy and ready for auto-updates.</Text>
120
+ <PressEnterToContinue />
121
+ </Box>
122
+ )
123
+ }
124
+ return (
125
+ <Box paddingX={1} paddingTop={1}>
126
+ <Text color={theme.success}>✓ Auto-updates enabled</Text>
127
+ </Box>
128
+ )
129
+ }
130
+ return (
131
+ <Box
132
+ borderColor={theme.permission}
133
+ borderStyle="round"
134
+ flexDirection="column"
135
+ gap={1}
136
+ paddingX={1}
137
+ paddingTop={1}
138
+ >
139
+ <Text bold color={theme.permission}>
140
+ Enable automatic updates?
141
+ </Text>
142
+ <Text>
143
+ {PRODUCT_NAME} can&apos;t update itself because it doesn&apos;t have
144
+ permissions. Do you want to fix this to get automatic updates?
145
+ </Text>
146
+ <Box flexDirection="column">
147
+ {!selectedOption && (
148
+ <Box marginLeft={2}>
149
+ <Text>Select an option below to fix the permissions issue:</Text>
150
+ <Select
151
+ options={options}
152
+ onChange={(value: string) => {
153
+ if (
154
+ value !== 'auto' &&
155
+ value !== 'manual' &&
156
+ value !== 'ignore'
157
+ )
158
+ return
159
+ setSelectedOption(value)
160
+
161
+ // Log option selection
162
+ logEvent('tengu_auto_updater_config_option_selected', {
163
+ option: value as 'auto' | 'manual' | 'ignore',
164
+ npmPrefix: npmPrefix ?? 'null',
165
+ })
166
+
167
+ if (value === 'manual') {
168
+ const config = getGlobalConfig()
169
+ saveGlobalConfig({
170
+ ...config,
171
+ autoUpdaterStatus: 'not_configured',
172
+ })
173
+ setShowingPermissionsMessage(true)
174
+ } else if (value === 'ignore') {
175
+ const config = getGlobalConfig()
176
+ saveGlobalConfig({
177
+ ...config,
178
+ autoUpdaterStatus: 'not_configured',
179
+ })
180
+ onDone()
181
+ }
182
+ }}
183
+ />
184
+ </Box>
185
+ )}
186
+
187
+ {selectedOption === 'auto' && (
188
+ <Box marginLeft={2}>
189
+ <ConfigureNpmPrefix
190
+ customPrefix={customPrefix}
191
+ onCustomPrefixChange={setCustomPrefix}
192
+ onSuccess={checkPermissions}
193
+ onCancel={onDone}
194
+ />
195
+ </Box>
196
+ )}
197
+
198
+ {selectedOption === 'manual' && (
199
+ <>
200
+ <Box marginLeft={4} flexDirection="column">
201
+ <Text>Run this command in your terminal:</Text>
202
+ <Box flexDirection="row" gap={1}>
203
+ <Text color={theme.warning}>
204
+ {getPermissionsCommand(npmPrefix ?? '')}
205
+ </Text>
206
+ </Box>
207
+ <Box flexDirection="row" gap={1}>
208
+ <Text color={theme.suggestion}>
209
+ After running the command, restart {PRODUCT_NAME}
210
+ </Text>
211
+ </Box>
212
+ </Box>
213
+ <PressEnterToContinue />
214
+ </>
215
+ )}
216
+ </Box>
217
+ </Box>
218
+ )
219
+ }
@@ -0,0 +1,68 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { CACHE_PATHS } from '../utils/log'
3
+ import { LogSelector } from '../components/LogSelector'
4
+ import type { LogOption, LogListProps } from '../types/logs'
5
+ import { loadLogList } from '../utils/log'
6
+ import { logError } from '../utils/log'
7
+
8
+ type Props = LogListProps & {
9
+ type: 'messages' | 'errors'
10
+ logNumber?: number
11
+ }
12
+
13
+ export function LogList({ context, type, logNumber }: Props): React.ReactNode {
14
+ const [logs, setLogs] = useState<LogOption[]>([])
15
+ const [didSelectLog, setDidSelectLog] = useState(false)
16
+
17
+ useEffect(() => {
18
+ loadLogList(
19
+ type === 'messages' ? CACHE_PATHS.messages() : CACHE_PATHS.errors(),
20
+ )
21
+ .then(logs => {
22
+ // If logNumber is provided, immediately display that log
23
+ if (logNumber !== undefined) {
24
+ const log = logs[logNumber >= 0 ? logNumber : 0] // Handle out of bounds
25
+ if (log) {
26
+ console.log(JSON.stringify(log.messages, null, 2))
27
+ process.exit(0)
28
+ } else {
29
+ console.error('No log found at index', logNumber)
30
+ process.exit(1)
31
+ }
32
+ }
33
+
34
+ setLogs(logs)
35
+ })
36
+ .catch(error => {
37
+ logError(error)
38
+ if (logNumber !== undefined) {
39
+ process.exit(1)
40
+ } else {
41
+ context.unmount?.()
42
+ }
43
+ })
44
+ }, [context, type, logNumber])
45
+
46
+ function onSelect(index: number): void {
47
+ const log = logs[index]
48
+ if (!log) {
49
+ return
50
+ }
51
+ setDidSelectLog(true)
52
+ setTimeout(() => {
53
+ console.log(JSON.stringify(log.messages, null, 2))
54
+ process.exit(0)
55
+ }, 100)
56
+ }
57
+
58
+ // If logNumber is provided, don't render the selector
59
+ if (logNumber !== undefined) {
60
+ return null
61
+ }
62
+
63
+ if (didSelectLog) {
64
+ return null
65
+ }
66
+
67
+ return <LogSelector logs={logs} onSelect={onSelect} />
68
+ }