@shareai-lab/kode 1.1.14 → 1.1.16-dev.2

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 (289) hide show
  1. package/cli.js +77 -82
  2. package/dist/entrypoints/cli.js +59 -38
  3. package/dist/entrypoints/cli.js.map +3 -3
  4. package/dist/index.js +5 -26
  5. package/dist/package.json +4 -1
  6. package/package.json +11 -104
  7. package/dist/test/testAdapters.js +0 -88
  8. package/dist/test/testAdapters.js.map +0 -1
  9. package/src/ProjectOnboarding.tsx +0 -198
  10. package/src/Tool.ts +0 -83
  11. package/src/commands/agents.tsx +0 -3416
  12. package/src/commands/approvedTools.ts +0 -53
  13. package/src/commands/bug.tsx +0 -20
  14. package/src/commands/clear.ts +0 -43
  15. package/src/commands/compact.ts +0 -120
  16. package/src/commands/config.tsx +0 -19
  17. package/src/commands/cost.ts +0 -18
  18. package/src/commands/ctx_viz.ts +0 -209
  19. package/src/commands/doctor.ts +0 -24
  20. package/src/commands/help.tsx +0 -19
  21. package/src/commands/init.ts +0 -37
  22. package/src/commands/listen.ts +0 -42
  23. package/src/commands/login.tsx +0 -51
  24. package/src/commands/logout.tsx +0 -40
  25. package/src/commands/mcp.ts +0 -41
  26. package/src/commands/model.tsx +0 -40
  27. package/src/commands/modelstatus.tsx +0 -20
  28. package/src/commands/onboarding.tsx +0 -34
  29. package/src/commands/pr_comments.ts +0 -59
  30. package/src/commands/refreshCommands.ts +0 -54
  31. package/src/commands/release-notes.ts +0 -34
  32. package/src/commands/resume.tsx +0 -31
  33. package/src/commands/review.ts +0 -49
  34. package/src/commands/terminalSetup.ts +0 -221
  35. package/src/commands.ts +0 -139
  36. package/src/components/ApproveApiKey.tsx +0 -93
  37. package/src/components/AsciiLogo.tsx +0 -13
  38. package/src/components/AutoUpdater.tsx +0 -148
  39. package/src/components/Bug.tsx +0 -367
  40. package/src/components/Config.tsx +0 -293
  41. package/src/components/ConsoleOAuthFlow.tsx +0 -327
  42. package/src/components/Cost.tsx +0 -23
  43. package/src/components/CostThresholdDialog.tsx +0 -46
  44. package/src/components/CustomSelect/option-map.ts +0 -42
  45. package/src/components/CustomSelect/select-option.tsx +0 -78
  46. package/src/components/CustomSelect/select.tsx +0 -152
  47. package/src/components/CustomSelect/theme.ts +0 -45
  48. package/src/components/CustomSelect/use-select-state.ts +0 -414
  49. package/src/components/CustomSelect/use-select.ts +0 -35
  50. package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
  51. package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
  52. package/src/components/Help.tsx +0 -215
  53. package/src/components/HighlightedCode.tsx +0 -33
  54. package/src/components/InvalidConfigDialog.tsx +0 -113
  55. package/src/components/Link.tsx +0 -32
  56. package/src/components/LogSelector.tsx +0 -86
  57. package/src/components/Logo.tsx +0 -170
  58. package/src/components/MCPServerApprovalDialog.tsx +0 -100
  59. package/src/components/MCPServerDialogCopy.tsx +0 -25
  60. package/src/components/MCPServerMultiselectDialog.tsx +0 -109
  61. package/src/components/Message.tsx +0 -221
  62. package/src/components/MessageResponse.tsx +0 -15
  63. package/src/components/MessageSelector.tsx +0 -211
  64. package/src/components/ModeIndicator.tsx +0 -88
  65. package/src/components/ModelConfig.tsx +0 -301
  66. package/src/components/ModelListManager.tsx +0 -227
  67. package/src/components/ModelSelector.tsx +0 -3387
  68. package/src/components/ModelStatusDisplay.tsx +0 -230
  69. package/src/components/Onboarding.tsx +0 -274
  70. package/src/components/PressEnterToContinue.tsx +0 -11
  71. package/src/components/PromptInput.tsx +0 -760
  72. package/src/components/SentryErrorBoundary.ts +0 -39
  73. package/src/components/Spinner.tsx +0 -129
  74. package/src/components/StickerRequestForm.tsx +0 -16
  75. package/src/components/StructuredDiff.tsx +0 -191
  76. package/src/components/TextInput.tsx +0 -259
  77. package/src/components/TodoItem.tsx +0 -47
  78. package/src/components/TokenWarning.tsx +0 -31
  79. package/src/components/ToolUseLoader.tsx +0 -40
  80. package/src/components/TrustDialog.tsx +0 -106
  81. package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
  82. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
  83. package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
  84. package/src/components/binary-feedback/utils.ts +0 -220
  85. package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
  86. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
  87. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
  88. package/src/components/messages/AssistantTextMessage.tsx +0 -144
  89. package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
  90. package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
  91. package/src/components/messages/TaskProgressMessage.tsx +0 -32
  92. package/src/components/messages/TaskToolMessage.tsx +0 -58
  93. package/src/components/messages/UserBashInputMessage.tsx +0 -28
  94. package/src/components/messages/UserCommandMessage.tsx +0 -30
  95. package/src/components/messages/UserKodingInputMessage.tsx +0 -28
  96. package/src/components/messages/UserPromptMessage.tsx +0 -35
  97. package/src/components/messages/UserTextMessage.tsx +0 -39
  98. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
  99. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
  100. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
  101. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
  102. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
  103. package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
  104. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
  105. package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
  106. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
  107. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
  108. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
  109. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
  110. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
  111. package/src/components/permissions/PermissionRequest.tsx +0 -101
  112. package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
  113. package/src/components/permissions/hooks.ts +0 -44
  114. package/src/components/permissions/toolUseOptions.ts +0 -59
  115. package/src/components/permissions/utils.ts +0 -23
  116. package/src/constants/betas.ts +0 -5
  117. package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
  118. package/src/constants/figures.ts +0 -4
  119. package/src/constants/keys.ts +0 -3
  120. package/src/constants/macros.ts +0 -11
  121. package/src/constants/modelCapabilities.ts +0 -179
  122. package/src/constants/models.ts +0 -1025
  123. package/src/constants/oauth.ts +0 -18
  124. package/src/constants/product.ts +0 -17
  125. package/src/constants/prompts.ts +0 -168
  126. package/src/constants/releaseNotes.ts +0 -7
  127. package/src/context/PermissionContext.tsx +0 -149
  128. package/src/context.ts +0 -278
  129. package/src/cost-tracker.ts +0 -84
  130. package/src/entrypoints/cli.tsx +0 -1561
  131. package/src/entrypoints/mcp.ts +0 -175
  132. package/src/history.ts +0 -25
  133. package/src/hooks/useApiKeyVerification.ts +0 -59
  134. package/src/hooks/useArrowKeyHistory.ts +0 -55
  135. package/src/hooks/useCanUseTool.ts +0 -138
  136. package/src/hooks/useCancelRequest.ts +0 -39
  137. package/src/hooks/useDoublePress.ts +0 -41
  138. package/src/hooks/useExitOnCtrlCD.ts +0 -31
  139. package/src/hooks/useInterval.ts +0 -25
  140. package/src/hooks/useLogMessages.ts +0 -16
  141. package/src/hooks/useLogStartupTime.ts +0 -12
  142. package/src/hooks/useNotifyAfterTimeout.ts +0 -65
  143. package/src/hooks/usePermissionRequestLogging.ts +0 -44
  144. package/src/hooks/useTerminalSize.ts +0 -49
  145. package/src/hooks/useTextInput.ts +0 -317
  146. package/src/hooks/useUnifiedCompletion.ts +0 -1405
  147. package/src/index.ts +0 -34
  148. package/src/messages.ts +0 -38
  149. package/src/permissions.ts +0 -268
  150. package/src/query.ts +0 -720
  151. package/src/screens/ConfigureNpmPrefix.tsx +0 -197
  152. package/src/screens/Doctor.tsx +0 -219
  153. package/src/screens/LogList.tsx +0 -68
  154. package/src/screens/REPL.tsx +0 -813
  155. package/src/screens/ResumeConversation.tsx +0 -68
  156. package/src/services/adapters/base.ts +0 -38
  157. package/src/services/adapters/chatCompletions.ts +0 -90
  158. package/src/services/adapters/responsesAPI.ts +0 -170
  159. package/src/services/browserMocks.ts +0 -66
  160. package/src/services/claude.ts +0 -2197
  161. package/src/services/customCommands.ts +0 -704
  162. package/src/services/fileFreshness.ts +0 -377
  163. package/src/services/gpt5ConnectionTest.ts +0 -340
  164. package/src/services/mcpClient.ts +0 -564
  165. package/src/services/mcpServerApproval.tsx +0 -50
  166. package/src/services/mentionProcessor.ts +0 -273
  167. package/src/services/modelAdapterFactory.ts +0 -69
  168. package/src/services/notifier.ts +0 -40
  169. package/src/services/oauth.ts +0 -357
  170. package/src/services/openai.ts +0 -1359
  171. package/src/services/responseStateManager.ts +0 -90
  172. package/src/services/sentry.ts +0 -3
  173. package/src/services/statsig.ts +0 -172
  174. package/src/services/statsigStorage.ts +0 -86
  175. package/src/services/systemReminder.ts +0 -507
  176. package/src/services/vcr.ts +0 -161
  177. package/src/test/testAdapters.ts +0 -96
  178. package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
  179. package/src/tools/ArchitectTool/prompt.ts +0 -15
  180. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
  181. package/src/tools/BashTool/BashTool.tsx +0 -243
  182. package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
  183. package/src/tools/BashTool/OutputLine.tsx +0 -49
  184. package/src/tools/BashTool/prompt.ts +0 -174
  185. package/src/tools/BashTool/utils.ts +0 -56
  186. package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
  187. package/src/tools/FileEditTool/prompt.ts +0 -51
  188. package/src/tools/FileEditTool/utils.ts +0 -58
  189. package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
  190. package/src/tools/FileReadTool/prompt.ts +0 -7
  191. package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
  192. package/src/tools/FileWriteTool/prompt.ts +0 -10
  193. package/src/tools/GlobTool/GlobTool.tsx +0 -119
  194. package/src/tools/GlobTool/prompt.ts +0 -8
  195. package/src/tools/GrepTool/GrepTool.tsx +0 -147
  196. package/src/tools/GrepTool/prompt.ts +0 -11
  197. package/src/tools/MCPTool/MCPTool.tsx +0 -107
  198. package/src/tools/MCPTool/prompt.ts +0 -3
  199. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
  200. package/src/tools/MemoryReadTool/prompt.ts +0 -3
  201. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
  202. package/src/tools/MemoryWriteTool/prompt.ts +0 -3
  203. package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
  204. package/src/tools/MultiEditTool/prompt.ts +0 -45
  205. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
  206. package/src/tools/NotebookEditTool/prompt.ts +0 -3
  207. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
  208. package/src/tools/NotebookReadTool/prompt.ts +0 -3
  209. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
  210. package/src/tools/StickerRequestTool/prompt.ts +0 -19
  211. package/src/tools/TaskTool/TaskTool.tsx +0 -438
  212. package/src/tools/TaskTool/constants.ts +0 -1
  213. package/src/tools/TaskTool/prompt.ts +0 -92
  214. package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
  215. package/src/tools/ThinkTool/prompt.ts +0 -12
  216. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
  217. package/src/tools/TodoWriteTool/prompt.ts +0 -63
  218. package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
  219. package/src/tools/URLFetcherTool/cache.ts +0 -55
  220. package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
  221. package/src/tools/URLFetcherTool/prompt.ts +0 -17
  222. package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
  223. package/src/tools/WebSearchTool/prompt.ts +0 -13
  224. package/src/tools/WebSearchTool/searchProviders.ts +0 -66
  225. package/src/tools/lsTool/lsTool.tsx +0 -272
  226. package/src/tools/lsTool/prompt.ts +0 -2
  227. package/src/tools.ts +0 -67
  228. package/src/types/PermissionMode.ts +0 -120
  229. package/src/types/RequestContext.ts +0 -72
  230. package/src/types/common.d.ts +0 -2
  231. package/src/types/conversation.ts +0 -51
  232. package/src/types/logs.ts +0 -58
  233. package/src/types/modelCapabilities.ts +0 -64
  234. package/src/types/notebook.ts +0 -87
  235. package/src/utils/Cursor.ts +0 -436
  236. package/src/utils/PersistentShell.ts +0 -552
  237. package/src/utils/advancedFuzzyMatcher.ts +0 -290
  238. package/src/utils/agentLoader.ts +0 -278
  239. package/src/utils/agentStorage.ts +0 -97
  240. package/src/utils/array.ts +0 -3
  241. package/src/utils/ask.tsx +0 -99
  242. package/src/utils/auth.ts +0 -13
  243. package/src/utils/autoCompactCore.ts +0 -223
  244. package/src/utils/autoUpdater.ts +0 -458
  245. package/src/utils/betas.ts +0 -20
  246. package/src/utils/browser.ts +0 -14
  247. package/src/utils/cleanup.ts +0 -72
  248. package/src/utils/commands.ts +0 -261
  249. package/src/utils/commonUnixCommands.ts +0 -161
  250. package/src/utils/config.ts +0 -945
  251. package/src/utils/conversationRecovery.ts +0 -55
  252. package/src/utils/debugLogger.ts +0 -1235
  253. package/src/utils/diff.ts +0 -42
  254. package/src/utils/env.ts +0 -57
  255. package/src/utils/errors.ts +0 -21
  256. package/src/utils/exampleCommands.ts +0 -109
  257. package/src/utils/execFileNoThrow.ts +0 -51
  258. package/src/utils/expertChatStorage.ts +0 -136
  259. package/src/utils/file.ts +0 -405
  260. package/src/utils/fileRecoveryCore.ts +0 -71
  261. package/src/utils/format.tsx +0 -44
  262. package/src/utils/fuzzyMatcher.ts +0 -328
  263. package/src/utils/generators.ts +0 -62
  264. package/src/utils/git.ts +0 -92
  265. package/src/utils/globalLogger.ts +0 -77
  266. package/src/utils/http.ts +0 -10
  267. package/src/utils/imagePaste.ts +0 -38
  268. package/src/utils/json.ts +0 -13
  269. package/src/utils/log.ts +0 -382
  270. package/src/utils/markdown.ts +0 -213
  271. package/src/utils/messageContextManager.ts +0 -294
  272. package/src/utils/messages.tsx +0 -945
  273. package/src/utils/model.ts +0 -914
  274. package/src/utils/permissions/filesystem.ts +0 -127
  275. package/src/utils/responseState.ts +0 -23
  276. package/src/utils/ripgrep.ts +0 -167
  277. package/src/utils/secureFile.ts +0 -564
  278. package/src/utils/sessionState.ts +0 -49
  279. package/src/utils/state.ts +0 -25
  280. package/src/utils/style.ts +0 -29
  281. package/src/utils/terminal.ts +0 -50
  282. package/src/utils/theme.ts +0 -127
  283. package/src/utils/thinking.ts +0 -144
  284. package/src/utils/todoStorage.ts +0 -431
  285. package/src/utils/tokens.ts +0 -43
  286. package/src/utils/toolExecutionController.ts +0 -163
  287. package/src/utils/unaryLogging.ts +0 -26
  288. package/src/utils/user.ts +0 -37
  289. package/src/utils/validate.ts +0 -165
@@ -1,552 +0,0 @@
1
- import * as fs from 'fs'
2
- import { homedir } from 'os'
3
- import { existsSync } from 'fs'
4
- import shellquote from 'shell-quote'
5
- import { spawn, execSync, type ChildProcess } from 'child_process'
6
- import { isAbsolute, resolve, join } from 'path'
7
- import { logError } from './log'
8
- import * as os from 'os'
9
- import { logEvent } from '../services/statsig'
10
- import { PRODUCT_COMMAND } from '../constants/product'
11
-
12
- type ExecResult = {
13
- stdout: string
14
- stderr: string
15
- code: number
16
- interrupted: boolean
17
- }
18
- type QueuedCommand = {
19
- command: string
20
- abortSignal?: AbortSignal
21
- timeout?: number
22
- resolve: (result: ExecResult) => void
23
- reject: (error: Error) => void
24
- }
25
-
26
- const TEMPFILE_PREFIX = os.tmpdir() + `/${PRODUCT_COMMAND}-`
27
- const DEFAULT_TIMEOUT = 30 * 60 * 1000
28
- const SIGTERM_CODE = 143 // Standard exit code for SIGTERM
29
- const FILE_SUFFIXES = {
30
- STATUS: '-status',
31
- STDOUT: '-stdout',
32
- STDERR: '-stderr',
33
- CWD: '-cwd',
34
- }
35
- const SHELL_CONFIGS: Record<string, string> = {
36
- '/bin/bash': '.bashrc',
37
- '/bin/zsh': '.zshrc',
38
- }
39
-
40
- type DetectedShell = {
41
- bin: string
42
- args: string[]
43
- type: 'posix' | 'msys' | 'wsl'
44
- }
45
-
46
- function quoteForBash(str: string): string {
47
- return `'${str.replace(/'/g, "'\\''")}'`
48
- }
49
-
50
- function toBashPath(pathStr: string, type: 'posix' | 'msys' | 'wsl'): string {
51
- // Already POSIX absolute path
52
- if (pathStr.startsWith('/')) return pathStr
53
- if (type === 'posix') return pathStr
54
-
55
- // Normalize backslashes
56
- const normalized = pathStr.replace(/\\/g, '/').replace(/\\\\/g, '/')
57
- const driveMatch = /^[A-Za-z]:/.exec(normalized)
58
- if (driveMatch) {
59
- const drive = normalized[0].toLowerCase()
60
- const rest = normalized.slice(2)
61
- if (type === 'msys') {
62
- return `/` + drive + (rest.startsWith('/') ? rest : `/${rest}`)
63
- }
64
- // wsl
65
- return `/mnt/` + drive + (rest.startsWith('/') ? rest : `/${rest}`)
66
- }
67
- // Relative path: just convert slashes
68
- return normalized
69
- }
70
-
71
- function fileExists(p: string | undefined): p is string {
72
- return !!p && existsSync(p)
73
- }
74
-
75
- // Robust PATH splitter for Windows and POSIX
76
- function splitPathEntries(pathEnv: string, platform: NodeJS.Platform): string[] {
77
- if (!pathEnv) return []
78
-
79
- // POSIX: ':' is the separator
80
- if (platform !== 'win32') {
81
- return pathEnv
82
- .split(':')
83
- .map(s => s.trim().replace(/^"|"$/g, ''))
84
- .filter(Boolean)
85
- }
86
-
87
- // Windows: primarily ';', but some environments may use ':'
88
- // We must not split drive letters like 'C:\\' or 'D:foo\\bar'
89
- const entries: string[] = []
90
- let current = ''
91
- const pushCurrent = () => {
92
- const cleaned = current.trim().replace(/^"|"$/g, '')
93
- if (cleaned) entries.push(cleaned)
94
- current = ''
95
- }
96
-
97
- for (let i = 0; i < pathEnv.length; i++) {
98
- const ch = pathEnv[i]
99
-
100
- if (ch === ';') {
101
- pushCurrent()
102
- continue
103
- }
104
-
105
- if (ch === ':') {
106
- const segmentLength = current.length
107
- const firstChar = current[0]
108
- const isDriveLetterPrefix = segmentLength === 1 && /[A-Za-z]/.test(firstChar || '')
109
- // Treat ':' as separator only if it's NOT the drive letter colon
110
- if (!isDriveLetterPrefix) {
111
- pushCurrent()
112
- continue
113
- }
114
- }
115
-
116
- current += ch
117
- }
118
-
119
- // Flush the final segment
120
- pushCurrent()
121
-
122
- return entries
123
- }
124
-
125
- function detectShell(): DetectedShell {
126
- const isWin = process.platform === 'win32'
127
- if (!isWin) {
128
- const bin = process.env.SHELL || '/bin/bash'
129
- return { bin, args: ['-l'], type: 'posix' }
130
- }
131
-
132
- // 1) Respect SHELL if it points to a bash.exe that exists
133
- if (process.env.SHELL && /bash\.exe$/i.test(process.env.SHELL) && existsSync(process.env.SHELL)) {
134
- return { bin: process.env.SHELL, args: ['-l'], type: 'msys' }
135
- }
136
-
137
- // 1.1) Explicit override
138
- if (process.env.KODE_BASH && existsSync(process.env.KODE_BASH)) {
139
- return { bin: process.env.KODE_BASH, args: ['-l'], type: 'msys' }
140
- }
141
-
142
- // 2) Common Git Bash/MSYS2 locations
143
- const programFiles = [
144
- process.env['ProgramFiles'],
145
- process.env['ProgramFiles(x86)'],
146
- process.env['ProgramW6432'],
147
- ].filter(Boolean) as string[]
148
-
149
- const localAppData = process.env['LocalAppData']
150
-
151
- const candidates: string[] = []
152
- for (const base of programFiles) {
153
- candidates.push(
154
- join(base, 'Git', 'bin', 'bash.exe'),
155
- join(base, 'Git', 'usr', 'bin', 'bash.exe'),
156
- )
157
- }
158
- if (localAppData) {
159
- candidates.push(
160
- join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'),
161
- join(localAppData, 'Programs', 'Git', 'usr', 'bin', 'bash.exe'),
162
- )
163
- }
164
- // MSYS2 default
165
- candidates.push('C:/msys64/usr/bin/bash.exe')
166
-
167
- for (const c of candidates) {
168
- if (existsSync(c)) {
169
- return { bin: c, args: ['-l'], type: 'msys' }
170
- }
171
- }
172
-
173
- // 2.1) Search in PATH for bash.exe
174
- const pathEnv = process.env.PATH || process.env.Path || process.env.path || ''
175
- const pathEntries = splitPathEntries(pathEnv, process.platform)
176
- for (const p of pathEntries) {
177
- const candidate = join(p, 'bash.exe')
178
- if (existsSync(candidate)) {
179
- return { bin: candidate, args: ['-l'], type: 'msys' }
180
- }
181
- }
182
-
183
- // 3) WSL
184
- try {
185
- // Quick probe to ensure WSL+bash exists
186
- execSync('wsl.exe -e bash -lc "echo KODE_OK"', { stdio: 'ignore', timeout: 1500 })
187
- return { bin: 'wsl.exe', args: ['-e', 'bash', '-l'], type: 'wsl' }
188
- } catch {}
189
-
190
- // 4) Last resort: meaningful error
191
- const hint = [
192
- '无法找到可用的 bash。请安装 Git for Windows 或启用 WSL。',
193
- '推荐安装 Git: https://git-scm.com/download/win',
194
- '或启用 WSL 并安装 Ubuntu: https://learn.microsoft.com/windows/wsl/install',
195
- ].join('\n')
196
- throw new Error(hint)
197
- }
198
-
199
- export class PersistentShell {
200
- private commandQueue: QueuedCommand[] = []
201
- private isExecuting: boolean = false
202
- private shell: ChildProcess
203
- private isAlive: boolean = true
204
- private commandInterrupted: boolean = false
205
- private statusFile: string
206
- private stdoutFile: string
207
- private stderrFile: string
208
- private cwdFile: string
209
- private cwd: string
210
- private binShell: string
211
- private shellArgs: string[]
212
- private shellType: 'posix' | 'msys' | 'wsl'
213
- private statusFileBashPath: string
214
- private stdoutFileBashPath: string
215
- private stderrFileBashPath: string
216
- private cwdFileBashPath: string
217
-
218
- constructor(cwd: string) {
219
- const { bin, args, type } = detectShell()
220
- this.binShell = bin
221
- this.shellArgs = args
222
- this.shellType = type
223
-
224
- this.shell = spawn(this.binShell, this.shellArgs, {
225
- stdio: ['pipe', 'pipe', 'pipe'],
226
- cwd,
227
- env: {
228
- ...process.env,
229
- GIT_EDITOR: 'true',
230
- },
231
- })
232
-
233
- this.cwd = cwd
234
-
235
- this.shell.on('exit', (code, signal) => {
236
- if (code) {
237
- // TODO: It would be nice to alert the user that shell crashed
238
- logError(`Shell exited with code ${code} and signal ${signal}`)
239
- logEvent('persistent_shell_exit', {
240
- code: code?.toString() || 'null',
241
- signal: signal || 'null',
242
- })
243
- }
244
- for (const file of [
245
- this.statusFile,
246
- this.stdoutFile,
247
- this.stderrFile,
248
- this.cwdFile,
249
- ]) {
250
- if (fs.existsSync(file)) {
251
- fs.unlinkSync(file)
252
- }
253
- }
254
- this.isAlive = false
255
- })
256
-
257
- const id = Math.floor(Math.random() * 0x10000)
258
- .toString(16)
259
- .padStart(4, '0')
260
-
261
- this.statusFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STATUS
262
- this.stdoutFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STDOUT
263
- this.stderrFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.STDERR
264
- this.cwdFile = TEMPFILE_PREFIX + id + FILE_SUFFIXES.CWD
265
- for (const file of [this.statusFile, this.stdoutFile, this.stderrFile]) {
266
- fs.writeFileSync(file, '')
267
- }
268
- // Initialize CWD file with initial directory
269
- fs.writeFileSync(this.cwdFile, cwd)
270
-
271
- // Compute bash-visible paths for redirections
272
- this.statusFileBashPath = toBashPath(this.statusFile, this.shellType)
273
- this.stdoutFileBashPath = toBashPath(this.stdoutFile, this.shellType)
274
- this.stderrFileBashPath = toBashPath(this.stderrFile, this.shellType)
275
- this.cwdFileBashPath = toBashPath(this.cwdFile, this.shellType)
276
-
277
- // Source ~/.bashrc when available (works for bash on POSIX/MSYS/WSL)
278
- this.sendToShell('[ -f ~/.bashrc ] && source ~/.bashrc || true')
279
- }
280
-
281
- private static instance: PersistentShell | null = null
282
-
283
- static restart() {
284
- if (PersistentShell.instance) {
285
- PersistentShell.instance.close()
286
- PersistentShell.instance = null
287
- }
288
- }
289
-
290
- static getInstance(): PersistentShell {
291
- if (!PersistentShell.instance || !PersistentShell.instance.isAlive) {
292
- PersistentShell.instance = new PersistentShell(process.cwd())
293
- }
294
- return PersistentShell.instance
295
- }
296
-
297
- killChildren() {
298
- const parentPid = this.shell.pid
299
- try {
300
- const childPids = execSync(`pgrep -P ${parentPid}`)
301
- .toString()
302
- .trim()
303
- .split('\n')
304
- .filter(Boolean) // Filter out empty strings
305
-
306
- if (childPids.length > 0) {
307
- logEvent('persistent_shell_command_interrupted', {
308
- numChildProcesses: childPids.length.toString(),
309
- })
310
- }
311
-
312
- childPids.forEach(pid => {
313
- try {
314
- process.kill(Number(pid), 'SIGTERM')
315
- } catch (error) {
316
- logError(`Failed to kill process ${pid}: ${error}`)
317
- logEvent('persistent_shell_kill_process_error', {
318
- error: (error as Error).message.substring(0, 10),
319
- })
320
- }
321
- })
322
- } catch {
323
- // pgrep returns non-zero when no processes are found - this is expected
324
- } finally {
325
- this.commandInterrupted = true
326
- }
327
- }
328
-
329
- private async processQueue() {
330
- /**
331
- * Processes commands from the queue one at a time.
332
- * Concurrency invariants:
333
- * - Only one instance runs at a time (controlled by isExecuting)
334
- * - Is the only caller of updateCwd() in the system
335
- * - Calls updateCwd() after each command completes
336
- * - Ensures commands execute serially via the queue
337
- * - Handles interruption via abortSignal by calling killChildren()
338
- * - Cleans up abortSignal listeners after command completion or interruption
339
- */
340
- if (this.isExecuting || this.commandQueue.length === 0) return
341
-
342
- this.isExecuting = true
343
- const { command, abortSignal, timeout, resolve, reject } =
344
- this.commandQueue.shift()!
345
-
346
- const killChildren = () => this.killChildren()
347
- if (abortSignal) {
348
- abortSignal.addEventListener('abort', killChildren)
349
- }
350
-
351
- try {
352
- const result = await this.exec_(command, timeout)
353
-
354
- // No need to update cwd - it's handled in exec_ via the CWD file
355
-
356
- resolve(result)
357
- } catch (error) {
358
- logEvent('persistent_shell_command_error', {
359
- error: (error as Error).message.substring(0, 10),
360
- })
361
- reject(error as Error)
362
- } finally {
363
- this.isExecuting = false
364
- if (abortSignal) {
365
- abortSignal.removeEventListener('abort', killChildren)
366
- }
367
- // Process next command in queue
368
- this.processQueue()
369
- }
370
- }
371
-
372
- async exec(
373
- command: string,
374
- abortSignal?: AbortSignal,
375
- timeout?: number,
376
- ): Promise<ExecResult> {
377
- return new Promise((resolve, reject) => {
378
- this.commandQueue.push({ command, abortSignal, timeout, resolve, reject })
379
- this.processQueue()
380
- })
381
- }
382
-
383
- private async exec_(command: string, timeout?: number): Promise<ExecResult> {
384
- /**
385
- * Direct command execution without going through the queue.
386
- * Concurrency invariants:
387
- * - Not safe for concurrent calls (uses shared files)
388
- * - Called only when queue is idle
389
- * - Relies on file-based IPC to handle shell interaction
390
- * - Does not modify the command queue state
391
- * - Tracks interruption state via commandInterrupted flag
392
- * - Resets interruption state at start of new command
393
- * - Reports interruption status in result object
394
- *
395
- * Exit Code & CWD Handling:
396
- * - Executes command and immediately captures its exit code into a shell variable
397
- * - Updates the CWD file with the working directory after capturing exit code
398
- * - Writes the preserved exit code to the status file as the final step
399
- * - This sequence eliminates race conditions between exit code capture and CWD updates
400
- * - The pwd() method reads the CWD file directly for current directory info
401
- */
402
- const quotedCommand = shellquote.quote([command])
403
-
404
- // Check the syntax of the command
405
- try {
406
- if (this.shellType === 'wsl') {
407
- execSync(`wsl.exe -e bash -n -c ${quotedCommand}`, {
408
- stdio: 'ignore',
409
- timeout: 1000,
410
- })
411
- } else {
412
- execSync(`${this.binShell} -n -c ${quotedCommand}`, {
413
- stdio: 'ignore',
414
- timeout: 1000,
415
- })
416
- }
417
- } catch (stderr) {
418
- // If there's a syntax error, return an error and log it
419
- const errorStr =
420
- typeof stderr === 'string' ? stderr : String(stderr || '')
421
- logEvent('persistent_shell_syntax_error', {
422
- error: errorStr.substring(0, 10),
423
- })
424
- return Promise.resolve({
425
- stdout: '',
426
- stderr: errorStr,
427
- code: 128,
428
- interrupted: false,
429
- })
430
- }
431
-
432
- const commandTimeout = timeout || DEFAULT_TIMEOUT
433
- // Reset interrupted state for new command
434
- this.commandInterrupted = false
435
- return new Promise<ExecResult>(resolve => {
436
- // Truncate output files
437
- fs.writeFileSync(this.stdoutFile, '')
438
- fs.writeFileSync(this.stderrFile, '')
439
- fs.writeFileSync(this.statusFile, '')
440
- // Break up the command sequence for clarity using an array of commands
441
- const commandParts = []
442
-
443
- // 1. Execute the main command with redirections
444
- commandParts.push(
445
- `eval ${quotedCommand} < /dev/null > ${quoteForBash(this.stdoutFileBashPath)} 2> ${quoteForBash(this.stderrFileBashPath)}`,
446
- )
447
-
448
- // 2. Capture exit code immediately after command execution to avoid losing it
449
- commandParts.push(`EXEC_EXIT_CODE=$?`)
450
-
451
- // 3. Update CWD file
452
- commandParts.push(`pwd > ${quoteForBash(this.cwdFileBashPath)}`)
453
-
454
- // 4. Write the preserved exit code to status file to avoid race with pwd
455
- commandParts.push(`echo $EXEC_EXIT_CODE > ${quoteForBash(this.statusFileBashPath)}`)
456
-
457
- // Send the combined commands as a single operation to maintain atomicity
458
- this.sendToShell(commandParts.join('\n'))
459
-
460
- // Check for command completion or timeout
461
- const start = Date.now()
462
- const checkCompletion = setInterval(() => {
463
- try {
464
- let statusFileSize = 0
465
- if (fs.existsSync(this.statusFile)) {
466
- statusFileSize = fs.statSync(this.statusFile).size
467
- }
468
-
469
- if (
470
- statusFileSize > 0 ||
471
- Date.now() - start > commandTimeout ||
472
- this.commandInterrupted
473
- ) {
474
- clearInterval(checkCompletion)
475
- const stdout = fs.existsSync(this.stdoutFile)
476
- ? fs.readFileSync(this.stdoutFile, 'utf8')
477
- : ''
478
- let stderr = fs.existsSync(this.stderrFile)
479
- ? fs.readFileSync(this.stderrFile, 'utf8')
480
- : ''
481
- let code: number
482
- if (statusFileSize) {
483
- code = Number(fs.readFileSync(this.statusFile, 'utf8'))
484
- } else {
485
- // Timeout occurred - kill any running processes
486
- this.killChildren()
487
- code = SIGTERM_CODE
488
- stderr += (stderr ? '\n' : '') + 'Command execution timed out'
489
- logEvent('persistent_shell_command_timeout', {
490
- command: command.substring(0, 10),
491
- timeout: commandTimeout.toString(),
492
- })
493
- }
494
- resolve({
495
- stdout,
496
- stderr,
497
- code,
498
- interrupted: this.commandInterrupted,
499
- })
500
- }
501
- } catch {
502
- // Ignore file system errors during polling - they are expected
503
- // as we check for completion before files exist
504
- }
505
- }, 10) // increasing this will introduce latency
506
- })
507
- }
508
-
509
- private sendToShell(command: string) {
510
- try {
511
- this.shell!.stdin!.write(command + '\n')
512
- } catch (error) {
513
- const errorString =
514
- error instanceof Error
515
- ? error.message
516
- : String(error || 'Unknown error')
517
- logError(`Error in sendToShell: ${errorString}`)
518
- logEvent('persistent_shell_write_error', {
519
- error: errorString.substring(0, 100),
520
- command: command.substring(0, 30),
521
- })
522
- throw error
523
- }
524
- }
525
-
526
- pwd(): string {
527
- try {
528
- const newCwd = fs.readFileSync(this.cwdFile, 'utf8').trim()
529
- if (newCwd) {
530
- this.cwd = newCwd
531
- }
532
- } catch (error) {
533
- logError(`Shell pwd error ${error}`)
534
- }
535
- // Always return the cached value
536
- return this.cwd
537
- }
538
-
539
- async setCwd(cwd: string) {
540
- const resolved = isAbsolute(cwd) ? cwd : resolve(process.cwd(), cwd)
541
- if (!existsSync(resolved)) {
542
- throw new Error(`Path "${resolved}" does not exist`)
543
- }
544
- const bashPath = toBashPath(resolved, this.shellType)
545
- await this.exec(`cd ${quoteForBash(bashPath)}`)
546
- }
547
-
548
- close(): void {
549
- this.shell!.stdin!.end()
550
- this.shell.kill()
551
- }
552
- }