@shareai-lab/kode 1.1.14 → 1.1.16-dev.1
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.
- package/cli.js +77 -82
- package/dist/entrypoints/cli.js +59 -38
- package/dist/entrypoints/cli.js.map +3 -3
- package/dist/index.js +5 -26
- package/dist/package.json +4 -1
- package/package.json +11 -104
- package/dist/test/testAdapters.js +0 -88
- package/dist/test/testAdapters.js.map +0 -1
- package/src/ProjectOnboarding.tsx +0 -198
- package/src/Tool.ts +0 -83
- package/src/commands/agents.tsx +0 -3416
- package/src/commands/approvedTools.ts +0 -53
- package/src/commands/bug.tsx +0 -20
- package/src/commands/clear.ts +0 -43
- package/src/commands/compact.ts +0 -120
- package/src/commands/config.tsx +0 -19
- package/src/commands/cost.ts +0 -18
- package/src/commands/ctx_viz.ts +0 -209
- package/src/commands/doctor.ts +0 -24
- package/src/commands/help.tsx +0 -19
- package/src/commands/init.ts +0 -37
- package/src/commands/listen.ts +0 -42
- package/src/commands/login.tsx +0 -51
- package/src/commands/logout.tsx +0 -40
- package/src/commands/mcp.ts +0 -41
- package/src/commands/model.tsx +0 -40
- package/src/commands/modelstatus.tsx +0 -20
- package/src/commands/onboarding.tsx +0 -34
- package/src/commands/pr_comments.ts +0 -59
- package/src/commands/refreshCommands.ts +0 -54
- package/src/commands/release-notes.ts +0 -34
- package/src/commands/resume.tsx +0 -31
- package/src/commands/review.ts +0 -49
- package/src/commands/terminalSetup.ts +0 -221
- package/src/commands.ts +0 -139
- package/src/components/ApproveApiKey.tsx +0 -93
- package/src/components/AsciiLogo.tsx +0 -13
- package/src/components/AutoUpdater.tsx +0 -148
- package/src/components/Bug.tsx +0 -367
- package/src/components/Config.tsx +0 -293
- package/src/components/ConsoleOAuthFlow.tsx +0 -327
- package/src/components/Cost.tsx +0 -23
- package/src/components/CostThresholdDialog.tsx +0 -46
- package/src/components/CustomSelect/option-map.ts +0 -42
- package/src/components/CustomSelect/select-option.tsx +0 -78
- package/src/components/CustomSelect/select.tsx +0 -152
- package/src/components/CustomSelect/theme.ts +0 -45
- package/src/components/CustomSelect/use-select-state.ts +0 -414
- package/src/components/CustomSelect/use-select.ts +0 -35
- package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
- package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
- package/src/components/Help.tsx +0 -215
- package/src/components/HighlightedCode.tsx +0 -33
- package/src/components/InvalidConfigDialog.tsx +0 -113
- package/src/components/Link.tsx +0 -32
- package/src/components/LogSelector.tsx +0 -86
- package/src/components/Logo.tsx +0 -170
- package/src/components/MCPServerApprovalDialog.tsx +0 -100
- package/src/components/MCPServerDialogCopy.tsx +0 -25
- package/src/components/MCPServerMultiselectDialog.tsx +0 -109
- package/src/components/Message.tsx +0 -221
- package/src/components/MessageResponse.tsx +0 -15
- package/src/components/MessageSelector.tsx +0 -211
- package/src/components/ModeIndicator.tsx +0 -88
- package/src/components/ModelConfig.tsx +0 -301
- package/src/components/ModelListManager.tsx +0 -227
- package/src/components/ModelSelector.tsx +0 -3387
- package/src/components/ModelStatusDisplay.tsx +0 -230
- package/src/components/Onboarding.tsx +0 -274
- package/src/components/PressEnterToContinue.tsx +0 -11
- package/src/components/PromptInput.tsx +0 -760
- package/src/components/SentryErrorBoundary.ts +0 -39
- package/src/components/Spinner.tsx +0 -129
- package/src/components/StickerRequestForm.tsx +0 -16
- package/src/components/StructuredDiff.tsx +0 -191
- package/src/components/TextInput.tsx +0 -259
- package/src/components/TodoItem.tsx +0 -47
- package/src/components/TokenWarning.tsx +0 -31
- package/src/components/ToolUseLoader.tsx +0 -40
- package/src/components/TrustDialog.tsx +0 -106
- package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
- package/src/components/binary-feedback/utils.ts +0 -220
- package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
- package/src/components/messages/AssistantTextMessage.tsx +0 -144
- package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
- package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
- package/src/components/messages/TaskProgressMessage.tsx +0 -32
- package/src/components/messages/TaskToolMessage.tsx +0 -58
- package/src/components/messages/UserBashInputMessage.tsx +0 -28
- package/src/components/messages/UserCommandMessage.tsx +0 -30
- package/src/components/messages/UserKodingInputMessage.tsx +0 -28
- package/src/components/messages/UserPromptMessage.tsx +0 -35
- package/src/components/messages/UserTextMessage.tsx +0 -39
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
- package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
- package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
- package/src/components/permissions/PermissionRequest.tsx +0 -101
- package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
- package/src/components/permissions/hooks.ts +0 -44
- package/src/components/permissions/toolUseOptions.ts +0 -59
- package/src/components/permissions/utils.ts +0 -23
- package/src/constants/betas.ts +0 -5
- package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
- package/src/constants/figures.ts +0 -4
- package/src/constants/keys.ts +0 -3
- package/src/constants/macros.ts +0 -11
- package/src/constants/modelCapabilities.ts +0 -179
- package/src/constants/models.ts +0 -1025
- package/src/constants/oauth.ts +0 -18
- package/src/constants/product.ts +0 -17
- package/src/constants/prompts.ts +0 -168
- package/src/constants/releaseNotes.ts +0 -7
- package/src/context/PermissionContext.tsx +0 -149
- package/src/context.ts +0 -278
- package/src/cost-tracker.ts +0 -84
- package/src/entrypoints/cli.tsx +0 -1561
- package/src/entrypoints/mcp.ts +0 -175
- package/src/history.ts +0 -25
- package/src/hooks/useApiKeyVerification.ts +0 -59
- package/src/hooks/useArrowKeyHistory.ts +0 -55
- package/src/hooks/useCanUseTool.ts +0 -138
- package/src/hooks/useCancelRequest.ts +0 -39
- package/src/hooks/useDoublePress.ts +0 -41
- package/src/hooks/useExitOnCtrlCD.ts +0 -31
- package/src/hooks/useInterval.ts +0 -25
- package/src/hooks/useLogMessages.ts +0 -16
- package/src/hooks/useLogStartupTime.ts +0 -12
- package/src/hooks/useNotifyAfterTimeout.ts +0 -65
- package/src/hooks/usePermissionRequestLogging.ts +0 -44
- package/src/hooks/useTerminalSize.ts +0 -49
- package/src/hooks/useTextInput.ts +0 -317
- package/src/hooks/useUnifiedCompletion.ts +0 -1405
- package/src/index.ts +0 -34
- package/src/messages.ts +0 -38
- package/src/permissions.ts +0 -268
- package/src/query.ts +0 -720
- package/src/screens/ConfigureNpmPrefix.tsx +0 -197
- package/src/screens/Doctor.tsx +0 -219
- package/src/screens/LogList.tsx +0 -68
- package/src/screens/REPL.tsx +0 -813
- package/src/screens/ResumeConversation.tsx +0 -68
- package/src/services/adapters/base.ts +0 -38
- package/src/services/adapters/chatCompletions.ts +0 -90
- package/src/services/adapters/responsesAPI.ts +0 -170
- package/src/services/browserMocks.ts +0 -66
- package/src/services/claude.ts +0 -2197
- package/src/services/customCommands.ts +0 -704
- package/src/services/fileFreshness.ts +0 -377
- package/src/services/gpt5ConnectionTest.ts +0 -340
- package/src/services/mcpClient.ts +0 -564
- package/src/services/mcpServerApproval.tsx +0 -50
- package/src/services/mentionProcessor.ts +0 -273
- package/src/services/modelAdapterFactory.ts +0 -69
- package/src/services/notifier.ts +0 -40
- package/src/services/oauth.ts +0 -357
- package/src/services/openai.ts +0 -1359
- package/src/services/responseStateManager.ts +0 -90
- package/src/services/sentry.ts +0 -3
- package/src/services/statsig.ts +0 -172
- package/src/services/statsigStorage.ts +0 -86
- package/src/services/systemReminder.ts +0 -507
- package/src/services/vcr.ts +0 -161
- package/src/test/testAdapters.ts +0 -96
- package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
- package/src/tools/ArchitectTool/prompt.ts +0 -15
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
- package/src/tools/BashTool/BashTool.tsx +0 -243
- package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
- package/src/tools/BashTool/OutputLine.tsx +0 -49
- package/src/tools/BashTool/prompt.ts +0 -174
- package/src/tools/BashTool/utils.ts +0 -56
- package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
- package/src/tools/FileEditTool/prompt.ts +0 -51
- package/src/tools/FileEditTool/utils.ts +0 -58
- package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
- package/src/tools/FileReadTool/prompt.ts +0 -7
- package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
- package/src/tools/FileWriteTool/prompt.ts +0 -10
- package/src/tools/GlobTool/GlobTool.tsx +0 -119
- package/src/tools/GlobTool/prompt.ts +0 -8
- package/src/tools/GrepTool/GrepTool.tsx +0 -147
- package/src/tools/GrepTool/prompt.ts +0 -11
- package/src/tools/MCPTool/MCPTool.tsx +0 -107
- package/src/tools/MCPTool/prompt.ts +0 -3
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
- package/src/tools/MemoryReadTool/prompt.ts +0 -3
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
- package/src/tools/MemoryWriteTool/prompt.ts +0 -3
- package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
- package/src/tools/MultiEditTool/prompt.ts +0 -45
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
- package/src/tools/NotebookEditTool/prompt.ts +0 -3
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
- package/src/tools/NotebookReadTool/prompt.ts +0 -3
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
- package/src/tools/StickerRequestTool/prompt.ts +0 -19
- package/src/tools/TaskTool/TaskTool.tsx +0 -438
- package/src/tools/TaskTool/constants.ts +0 -1
- package/src/tools/TaskTool/prompt.ts +0 -92
- package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
- package/src/tools/ThinkTool/prompt.ts +0 -12
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
- package/src/tools/TodoWriteTool/prompt.ts +0 -63
- package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
- package/src/tools/URLFetcherTool/cache.ts +0 -55
- package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
- package/src/tools/URLFetcherTool/prompt.ts +0 -17
- package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
- package/src/tools/WebSearchTool/prompt.ts +0 -13
- package/src/tools/WebSearchTool/searchProviders.ts +0 -66
- package/src/tools/lsTool/lsTool.tsx +0 -272
- package/src/tools/lsTool/prompt.ts +0 -2
- package/src/tools.ts +0 -67
- package/src/types/PermissionMode.ts +0 -120
- package/src/types/RequestContext.ts +0 -72
- package/src/types/common.d.ts +0 -2
- package/src/types/conversation.ts +0 -51
- package/src/types/logs.ts +0 -58
- package/src/types/modelCapabilities.ts +0 -64
- package/src/types/notebook.ts +0 -87
- package/src/utils/Cursor.ts +0 -436
- package/src/utils/PersistentShell.ts +0 -552
- package/src/utils/advancedFuzzyMatcher.ts +0 -290
- package/src/utils/agentLoader.ts +0 -278
- package/src/utils/agentStorage.ts +0 -97
- package/src/utils/array.ts +0 -3
- package/src/utils/ask.tsx +0 -99
- package/src/utils/auth.ts +0 -13
- package/src/utils/autoCompactCore.ts +0 -223
- package/src/utils/autoUpdater.ts +0 -458
- package/src/utils/betas.ts +0 -20
- package/src/utils/browser.ts +0 -14
- package/src/utils/cleanup.ts +0 -72
- package/src/utils/commands.ts +0 -261
- package/src/utils/commonUnixCommands.ts +0 -161
- package/src/utils/config.ts +0 -945
- package/src/utils/conversationRecovery.ts +0 -55
- package/src/utils/debugLogger.ts +0 -1235
- package/src/utils/diff.ts +0 -42
- package/src/utils/env.ts +0 -57
- package/src/utils/errors.ts +0 -21
- package/src/utils/exampleCommands.ts +0 -109
- package/src/utils/execFileNoThrow.ts +0 -51
- package/src/utils/expertChatStorage.ts +0 -136
- package/src/utils/file.ts +0 -405
- package/src/utils/fileRecoveryCore.ts +0 -71
- package/src/utils/format.tsx +0 -44
- package/src/utils/fuzzyMatcher.ts +0 -328
- package/src/utils/generators.ts +0 -62
- package/src/utils/git.ts +0 -92
- package/src/utils/globalLogger.ts +0 -77
- package/src/utils/http.ts +0 -10
- package/src/utils/imagePaste.ts +0 -38
- package/src/utils/json.ts +0 -13
- package/src/utils/log.ts +0 -382
- package/src/utils/markdown.ts +0 -213
- package/src/utils/messageContextManager.ts +0 -294
- package/src/utils/messages.tsx +0 -945
- package/src/utils/model.ts +0 -914
- package/src/utils/permissions/filesystem.ts +0 -127
- package/src/utils/responseState.ts +0 -23
- package/src/utils/ripgrep.ts +0 -167
- package/src/utils/secureFile.ts +0 -564
- package/src/utils/sessionState.ts +0 -49
- package/src/utils/state.ts +0 -25
- package/src/utils/style.ts +0 -29
- package/src/utils/terminal.ts +0 -50
- package/src/utils/theme.ts +0 -127
- package/src/utils/thinking.ts +0 -144
- package/src/utils/todoStorage.ts +0 -431
- package/src/utils/tokens.ts +0 -43
- package/src/utils/toolExecutionController.ts +0 -163
- package/src/utils/unaryLogging.ts +0 -26
- package/src/utils/user.ts +0 -37
- 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
|
-
}
|