@shareai-lab/kode 1.0.70 → 1.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +202 -76
- package/README.zh-CN.md +246 -0
- package/cli.js +62 -0
- package/package.json +45 -25
- package/scripts/postinstall.js +56 -0
- package/src/ProjectOnboarding.tsx +180 -0
- package/src/Tool.ts +53 -0
- package/src/commands/approvedTools.ts +53 -0
- package/src/commands/bug.tsx +20 -0
- package/src/commands/clear.ts +43 -0
- package/src/commands/compact.ts +120 -0
- package/src/commands/config.tsx +19 -0
- package/src/commands/cost.ts +18 -0
- package/src/commands/ctx_viz.ts +209 -0
- package/src/commands/doctor.ts +24 -0
- package/src/commands/help.tsx +19 -0
- package/src/commands/init.ts +37 -0
- package/src/commands/listen.ts +42 -0
- package/src/commands/login.tsx +51 -0
- package/src/commands/logout.tsx +40 -0
- package/src/commands/mcp.ts +41 -0
- package/src/commands/model.tsx +40 -0
- package/src/commands/modelstatus.tsx +20 -0
- package/src/commands/onboarding.tsx +34 -0
- package/src/commands/pr_comments.ts +59 -0
- package/src/commands/refreshCommands.ts +54 -0
- package/src/commands/release-notes.ts +34 -0
- package/src/commands/resume.tsx +30 -0
- package/src/commands/review.ts +49 -0
- package/src/commands/terminalSetup.ts +221 -0
- package/src/commands.ts +136 -0
- package/src/components/ApproveApiKey.tsx +93 -0
- package/src/components/AsciiLogo.tsx +13 -0
- package/src/components/AutoUpdater.tsx +148 -0
- package/src/components/Bug.tsx +367 -0
- package/src/components/Config.tsx +289 -0
- package/src/components/ConsoleOAuthFlow.tsx +326 -0
- package/src/components/Cost.tsx +23 -0
- package/src/components/CostThresholdDialog.tsx +46 -0
- package/src/components/CustomSelect/option-map.ts +42 -0
- package/src/components/CustomSelect/select-option.tsx +52 -0
- package/src/components/CustomSelect/select.tsx +143 -0
- package/src/components/CustomSelect/use-select-state.ts +414 -0
- package/src/components/CustomSelect/use-select.ts +35 -0
- package/src/components/FallbackToolUseRejectedMessage.tsx +15 -0
- package/src/components/FileEditToolUpdatedMessage.tsx +66 -0
- package/src/components/Help.tsx +215 -0
- package/src/components/HighlightedCode.tsx +33 -0
- package/src/components/InvalidConfigDialog.tsx +113 -0
- package/src/components/Link.tsx +32 -0
- package/src/components/LogSelector.tsx +86 -0
- package/src/components/Logo.tsx +145 -0
- package/src/components/MCPServerApprovalDialog.tsx +100 -0
- package/src/components/MCPServerDialogCopy.tsx +25 -0
- package/src/components/MCPServerMultiselectDialog.tsx +109 -0
- package/src/components/Message.tsx +219 -0
- package/src/components/MessageResponse.tsx +15 -0
- package/src/components/MessageSelector.tsx +211 -0
- package/src/components/ModeIndicator.tsx +88 -0
- package/src/components/ModelConfig.tsx +301 -0
- package/src/components/ModelListManager.tsx +223 -0
- package/src/components/ModelSelector.tsx +3208 -0
- package/src/components/ModelStatusDisplay.tsx +228 -0
- package/src/components/Onboarding.tsx +274 -0
- package/src/components/PressEnterToContinue.tsx +11 -0
- package/src/components/PromptInput.tsx +710 -0
- package/src/components/SentryErrorBoundary.ts +33 -0
- package/src/components/Spinner.tsx +129 -0
- package/src/components/StructuredDiff.tsx +184 -0
- package/src/components/TextInput.tsx +246 -0
- package/src/components/TokenWarning.tsx +31 -0
- package/src/components/ToolUseLoader.tsx +40 -0
- package/src/components/TrustDialog.tsx +106 -0
- package/src/components/binary-feedback/BinaryFeedback.tsx +63 -0
- package/src/components/binary-feedback/BinaryFeedbackOption.tsx +111 -0
- package/src/components/binary-feedback/BinaryFeedbackView.tsx +172 -0
- package/src/components/binary-feedback/utils.ts +220 -0
- package/src/components/messages/AssistantBashOutputMessage.tsx +22 -0
- package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +45 -0
- package/src/components/messages/AssistantRedactedThinkingMessage.tsx +19 -0
- package/src/components/messages/AssistantTextMessage.tsx +144 -0
- package/src/components/messages/AssistantThinkingMessage.tsx +40 -0
- package/src/components/messages/AssistantToolUseMessage.tsx +123 -0
- package/src/components/messages/UserBashInputMessage.tsx +28 -0
- package/src/components/messages/UserCommandMessage.tsx +30 -0
- package/src/components/messages/UserKodingInputMessage.tsx +28 -0
- package/src/components/messages/UserPromptMessage.tsx +35 -0
- package/src/components/messages/UserTextMessage.tsx +39 -0
- package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +12 -0
- package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +36 -0
- package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +31 -0
- package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +57 -0
- package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +35 -0
- package/src/components/messages/UserToolResultMessage/utils.tsx +56 -0
- package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +121 -0
- package/src/components/permissions/FallbackPermissionRequest.tsx +155 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +182 -0
- package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +75 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +164 -0
- package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +81 -0
- package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +242 -0
- package/src/components/permissions/PermissionRequest.tsx +103 -0
- package/src/components/permissions/PermissionRequestTitle.tsx +69 -0
- package/src/components/permissions/hooks.ts +44 -0
- package/src/components/permissions/toolUseOptions.ts +59 -0
- package/src/components/permissions/utils.ts +23 -0
- package/src/constants/betas.ts +5 -0
- package/src/constants/claude-asterisk-ascii-art.tsx +238 -0
- package/src/constants/figures.ts +4 -0
- package/src/constants/keys.ts +3 -0
- package/src/constants/macros.ts +6 -0
- package/src/constants/models.ts +935 -0
- package/src/constants/oauth.ts +18 -0
- package/src/constants/product.ts +17 -0
- package/src/constants/prompts.ts +177 -0
- package/src/constants/releaseNotes.ts +7 -0
- package/src/context/PermissionContext.tsx +149 -0
- package/src/context.ts +278 -0
- package/src/cost-tracker.ts +84 -0
- package/src/entrypoints/cli.tsx +1498 -0
- package/src/entrypoints/mcp.ts +176 -0
- package/src/history.ts +25 -0
- package/src/hooks/useApiKeyVerification.ts +59 -0
- package/src/hooks/useArrowKeyHistory.ts +55 -0
- package/src/hooks/useCanUseTool.ts +138 -0
- package/src/hooks/useCancelRequest.ts +39 -0
- package/src/hooks/useDoublePress.ts +42 -0
- package/src/hooks/useExitOnCtrlCD.ts +31 -0
- package/src/hooks/useInterval.ts +25 -0
- package/src/hooks/useLogMessages.ts +16 -0
- package/src/hooks/useLogStartupTime.ts +12 -0
- package/src/hooks/useNotifyAfterTimeout.ts +65 -0
- package/src/hooks/usePermissionRequestLogging.ts +44 -0
- package/src/hooks/useSlashCommandTypeahead.ts +137 -0
- package/src/hooks/useTerminalSize.ts +49 -0
- package/src/hooks/useTextInput.ts +315 -0
- package/src/messages.ts +37 -0
- package/src/permissions.ts +268 -0
- package/src/query.ts +704 -0
- package/src/screens/ConfigureNpmPrefix.tsx +197 -0
- package/src/screens/Doctor.tsx +219 -0
- package/src/screens/LogList.tsx +68 -0
- package/src/screens/REPL.tsx +792 -0
- package/src/screens/ResumeConversation.tsx +68 -0
- package/src/services/browserMocks.ts +66 -0
- package/src/services/claude.ts +1947 -0
- package/src/services/customCommands.ts +683 -0
- package/src/services/fileFreshness.ts +377 -0
- package/src/services/mcpClient.ts +564 -0
- package/src/services/mcpServerApproval.tsx +50 -0
- package/src/services/notifier.ts +40 -0
- package/src/services/oauth.ts +357 -0
- package/src/services/openai.ts +796 -0
- package/src/services/sentry.ts +3 -0
- package/src/services/statsig.ts +171 -0
- package/src/services/statsigStorage.ts +86 -0
- package/src/services/systemReminder.ts +406 -0
- package/src/services/vcr.ts +161 -0
- package/src/tools/ArchitectTool/ArchitectTool.tsx +122 -0
- package/src/tools/ArchitectTool/prompt.ts +15 -0
- package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +505 -0
- package/src/tools/BashTool/BashTool.tsx +270 -0
- package/src/tools/BashTool/BashToolResultMessage.tsx +38 -0
- package/src/tools/BashTool/OutputLine.tsx +48 -0
- package/src/tools/BashTool/prompt.ts +174 -0
- package/src/tools/BashTool/utils.ts +56 -0
- package/src/tools/FileEditTool/FileEditTool.tsx +316 -0
- package/src/tools/FileEditTool/prompt.ts +51 -0
- package/src/tools/FileEditTool/utils.ts +58 -0
- package/src/tools/FileReadTool/FileReadTool.tsx +371 -0
- package/src/tools/FileReadTool/prompt.ts +7 -0
- package/src/tools/FileWriteTool/FileWriteTool.tsx +297 -0
- package/src/tools/FileWriteTool/prompt.ts +10 -0
- package/src/tools/GlobTool/GlobTool.tsx +119 -0
- package/src/tools/GlobTool/prompt.ts +8 -0
- package/src/tools/GrepTool/GrepTool.tsx +147 -0
- package/src/tools/GrepTool/prompt.ts +11 -0
- package/src/tools/MCPTool/MCPTool.tsx +106 -0
- package/src/tools/MCPTool/prompt.ts +3 -0
- package/src/tools/MemoryReadTool/MemoryReadTool.tsx +127 -0
- package/src/tools/MemoryReadTool/prompt.ts +3 -0
- package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +89 -0
- package/src/tools/MemoryWriteTool/prompt.ts +3 -0
- package/src/tools/MultiEditTool/MultiEditTool.tsx +366 -0
- package/src/tools/MultiEditTool/prompt.ts +45 -0
- package/src/tools/NotebookEditTool/NotebookEditTool.tsx +298 -0
- package/src/tools/NotebookEditTool/prompt.ts +3 -0
- package/src/tools/NotebookReadTool/NotebookReadTool.tsx +266 -0
- package/src/tools/NotebookReadTool/prompt.ts +3 -0
- package/src/tools/StickerRequestTool/StickerRequestTool.tsx +93 -0
- package/src/tools/StickerRequestTool/prompt.ts +19 -0
- package/src/tools/TaskTool/TaskTool.tsx +382 -0
- package/src/tools/TaskTool/constants.ts +1 -0
- package/src/tools/TaskTool/prompt.ts +56 -0
- package/src/tools/ThinkTool/ThinkTool.tsx +56 -0
- package/src/tools/ThinkTool/prompt.ts +12 -0
- package/src/tools/TodoWriteTool/TodoWriteTool.tsx +289 -0
- package/src/tools/TodoWriteTool/prompt.ts +63 -0
- package/src/tools/lsTool/lsTool.tsx +269 -0
- package/src/tools/lsTool/prompt.ts +2 -0
- package/src/tools.ts +63 -0
- package/src/types/PermissionMode.ts +120 -0
- package/src/types/RequestContext.ts +72 -0
- package/src/utils/Cursor.ts +436 -0
- package/src/utils/PersistentShell.ts +373 -0
- package/src/utils/agentStorage.ts +97 -0
- package/src/utils/array.ts +3 -0
- package/src/utils/ask.tsx +98 -0
- package/src/utils/auth.ts +13 -0
- package/src/utils/autoCompactCore.ts +223 -0
- package/src/utils/autoUpdater.ts +318 -0
- package/src/utils/betas.ts +20 -0
- package/src/utils/browser.ts +14 -0
- package/src/utils/cleanup.ts +72 -0
- package/src/utils/commands.ts +261 -0
- package/src/utils/config.ts +771 -0
- package/src/utils/conversationRecovery.ts +54 -0
- package/src/utils/debugLogger.ts +1123 -0
- package/src/utils/diff.ts +42 -0
- package/src/utils/env.ts +57 -0
- package/src/utils/errors.ts +21 -0
- package/src/utils/exampleCommands.ts +108 -0
- package/src/utils/execFileNoThrow.ts +51 -0
- package/src/utils/expertChatStorage.ts +136 -0
- package/src/utils/file.ts +402 -0
- package/src/utils/fileRecoveryCore.ts +71 -0
- package/src/utils/format.tsx +44 -0
- package/src/utils/generators.ts +62 -0
- package/src/utils/git.ts +92 -0
- package/src/utils/globalLogger.ts +77 -0
- package/src/utils/http.ts +10 -0
- package/src/utils/imagePaste.ts +38 -0
- package/src/utils/json.ts +13 -0
- package/src/utils/log.ts +382 -0
- package/src/utils/markdown.ts +213 -0
- package/src/utils/messageContextManager.ts +289 -0
- package/src/utils/messages.tsx +938 -0
- package/src/utils/model.ts +836 -0
- package/src/utils/permissions/filesystem.ts +118 -0
- package/src/utils/ripgrep.ts +167 -0
- package/src/utils/sessionState.ts +49 -0
- package/src/utils/state.ts +25 -0
- package/src/utils/style.ts +29 -0
- package/src/utils/terminal.ts +49 -0
- package/src/utils/theme.ts +122 -0
- package/src/utils/thinking.ts +144 -0
- package/src/utils/todoStorage.ts +431 -0
- package/src/utils/tokens.ts +43 -0
- package/src/utils/toolExecutionController.ts +163 -0
- package/src/utils/unaryLogging.ts +26 -0
- package/src/utils/user.ts +37 -0
- package/src/utils/validate.ts +165 -0
- package/cli.mjs +0 -1803
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统一的全局日志系统
|
|
3
|
+
* 普通模式:完全静默,零日志输出
|
|
4
|
+
* 调试模式:详细日志输出
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// 环境检测 - 只在明确的调试标志下才启用日志
|
|
8
|
+
const isDebugMode = () =>
|
|
9
|
+
process.argv.includes('--debug') ||
|
|
10
|
+
process.argv.includes('--verbose') ||
|
|
11
|
+
process.env.NODE_ENV === 'development'
|
|
12
|
+
|
|
13
|
+
// 全局日志开关 - 普通模式下完全关闭
|
|
14
|
+
const LOGGING_ENABLED = isDebugMode()
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 统一的日志接口
|
|
18
|
+
* 普通模式下所有调用都是空操作
|
|
19
|
+
*/
|
|
20
|
+
export const globalLogger = {
|
|
21
|
+
// 标准日志级别
|
|
22
|
+
debug: (...args: any[]) => {
|
|
23
|
+
if (LOGGING_ENABLED) console.debug(...args)
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
info: (...args: any[]) => {
|
|
27
|
+
if (LOGGING_ENABLED) console.info(...args)
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
warn: (...args: any[]) => {
|
|
31
|
+
if (LOGGING_ENABLED) console.warn(...args)
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
error: (...args: any[]) => {
|
|
35
|
+
if (LOGGING_ENABLED) console.error(...args)
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
log: (...args: any[]) => {
|
|
39
|
+
if (LOGGING_ENABLED) console.log(...args)
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// 兼容现有的console.log调用
|
|
43
|
+
console: (...args: any[]) => {
|
|
44
|
+
if (LOGGING_ENABLED) console.log(...args)
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// 模型切换相关日志
|
|
48
|
+
modelSwitch: (message: string, data?: any) => {
|
|
49
|
+
if (LOGGING_ENABLED) {
|
|
50
|
+
console.log(`🔄 Model Switch: ${message}`, data ? data : '')
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// API 相关日志
|
|
55
|
+
api: (message: string, data?: any) => {
|
|
56
|
+
if (LOGGING_ENABLED) {
|
|
57
|
+
console.log(`🌐 API: ${message}`, data ? data : '')
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// 用户友好的状态日志 - 只在调试模式下显示
|
|
62
|
+
status: (message: string) => {
|
|
63
|
+
if (LOGGING_ENABLED) {
|
|
64
|
+
console.log(`ℹ️ ${message}`)
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// 检查日志是否启用
|
|
69
|
+
isEnabled: () => LOGGING_ENABLED
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 兼容性:导出为默认console替代
|
|
73
|
+
export const logger = globalLogger
|
|
74
|
+
|
|
75
|
+
// 用于替换现有的console.log调用
|
|
76
|
+
export const debugLog = globalLogger.console
|
|
77
|
+
export const statusLog = globalLogger.status
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP utility constants and helpers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { MACRO } from '../constants/macros'
|
|
6
|
+
import { PRODUCT_COMMAND } from '../constants/product'
|
|
7
|
+
|
|
8
|
+
// WARNING: We rely on `claude-cli` in the user agent for log filtering.
|
|
9
|
+
// Please do NOT change this without making sure that logging also gets updated!
|
|
10
|
+
export const USER_AGENT = `${PRODUCT_COMMAND}/${MACRO.VERSION} (${process.env.USER_TYPE})`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { execSync } from 'child_process'
|
|
2
|
+
import { readFileSync } from 'fs'
|
|
3
|
+
|
|
4
|
+
const SCREENSHOT_PATH = '/tmp/claude_cli_latest_screenshot.png'
|
|
5
|
+
|
|
6
|
+
export const CLIPBOARD_ERROR_MESSAGE =
|
|
7
|
+
'No image found in clipboard. Use Cmd + Ctrl + Shift + 4 to copy a screenshot to clipboard.'
|
|
8
|
+
|
|
9
|
+
export function getImageFromClipboard(): string | null {
|
|
10
|
+
if (process.platform !== 'darwin') {
|
|
11
|
+
// only support image paste on macOS for now
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Check if clipboard has image
|
|
17
|
+
execSync(`osascript -e 'the clipboard as «class PNGf»'`, {
|
|
18
|
+
stdio: 'ignore',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Save the image
|
|
22
|
+
execSync(
|
|
23
|
+
`osascript -e 'set png_data to (the clipboard as «class PNGf»)' -e 'set fp to open for access POSIX file "${SCREENSHOT_PATH}" with write permission' -e 'write png_data to fp' -e 'close access fp'`,
|
|
24
|
+
{ stdio: 'ignore' },
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// Read the image and convert to base64
|
|
28
|
+
const imageBuffer = readFileSync(SCREENSHOT_PATH)
|
|
29
|
+
const base64Image = imageBuffer.toString('base64')
|
|
30
|
+
|
|
31
|
+
// Cleanup
|
|
32
|
+
execSync(`rm -f "${SCREENSHOT_PATH}"`, { stdio: 'ignore' })
|
|
33
|
+
|
|
34
|
+
return base64Image
|
|
35
|
+
} catch {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/utils/log.ts
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from 'fs'
|
|
2
|
+
import { dirname, join } from 'path'
|
|
3
|
+
import { writeFileSync, readFileSync } from 'fs'
|
|
4
|
+
import { captureException } from '../services/sentry'
|
|
5
|
+
import { randomUUID } from 'crypto'
|
|
6
|
+
import envPaths from 'env-paths'
|
|
7
|
+
import { promises as fsPromises } from 'fs'
|
|
8
|
+
import type { LogOption, SerializedMessage } from '../types/logs'
|
|
9
|
+
import { MACRO } from '../constants/macros'
|
|
10
|
+
import { PRODUCT_COMMAND } from '../constants/product'
|
|
11
|
+
const IN_MEMORY_ERROR_LOG: Array<{
|
|
12
|
+
error: string
|
|
13
|
+
timestamp: string
|
|
14
|
+
}> = []
|
|
15
|
+
const MAX_IN_MEMORY_ERRORS = 100 // Limit to prevent memory issues
|
|
16
|
+
|
|
17
|
+
export const SESSION_ID = randomUUID()
|
|
18
|
+
|
|
19
|
+
const paths = envPaths(PRODUCT_COMMAND)
|
|
20
|
+
|
|
21
|
+
function getProjectDir(cwd: string): string {
|
|
22
|
+
return cwd.replace(/[^a-zA-Z0-9]/g, '-')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const CACHE_PATHS = {
|
|
26
|
+
errors: () => join(paths.cache, getProjectDir(process.cwd()), 'errors'),
|
|
27
|
+
messages: () => join(paths.cache, getProjectDir(process.cwd()), 'messages'),
|
|
28
|
+
mcpLogs: (serverName: string) =>
|
|
29
|
+
join(paths.cache, getProjectDir(process.cwd()), `mcp-logs-${serverName}`),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function dateToFilename(date: Date): string {
|
|
33
|
+
return date.toISOString().replace(/[:.]/g, '-')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const DATE = dateToFilename(new Date())
|
|
37
|
+
|
|
38
|
+
function getErrorsPath(): string {
|
|
39
|
+
return join(CACHE_PATHS.errors(), DATE + '.txt')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getMessagesPath(
|
|
43
|
+
messageLogName: string,
|
|
44
|
+
forkNumber: number,
|
|
45
|
+
sidechainNumber: number,
|
|
46
|
+
): string {
|
|
47
|
+
return join(
|
|
48
|
+
CACHE_PATHS.messages(),
|
|
49
|
+
`${messageLogName}${forkNumber > 0 ? `-${forkNumber}` : ''}${
|
|
50
|
+
sidechainNumber > 0 ? `-sidechain-${sidechainNumber}` : ''
|
|
51
|
+
}.json`,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function logError(error: unknown): void {
|
|
56
|
+
try {
|
|
57
|
+
if (process.env.NODE_ENV === 'test') {
|
|
58
|
+
console.error(error)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const errorStr =
|
|
62
|
+
error instanceof Error ? error.stack || error.message : String(error)
|
|
63
|
+
|
|
64
|
+
const errorInfo = {
|
|
65
|
+
error: errorStr,
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (IN_MEMORY_ERROR_LOG.length >= MAX_IN_MEMORY_ERRORS) {
|
|
70
|
+
IN_MEMORY_ERROR_LOG.shift() // Remove oldest error
|
|
71
|
+
}
|
|
72
|
+
IN_MEMORY_ERROR_LOG.push(errorInfo)
|
|
73
|
+
|
|
74
|
+
appendToLog(getErrorsPath(), {
|
|
75
|
+
error: errorStr,
|
|
76
|
+
})
|
|
77
|
+
} catch {
|
|
78
|
+
// pass
|
|
79
|
+
}
|
|
80
|
+
// Also send to Sentry with session ID, but don't await
|
|
81
|
+
captureException(error)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getErrorsLog(): object[] {
|
|
85
|
+
return readLog(getErrorsPath())
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getInMemoryErrors(): object[] {
|
|
89
|
+
return [...IN_MEMORY_ERROR_LOG]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readLog(path: string): object[] {
|
|
93
|
+
if (!existsSync(path)) {
|
|
94
|
+
return []
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
98
|
+
} catch {
|
|
99
|
+
return []
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function appendToLog(path: string, message: object): void {
|
|
104
|
+
if (process.env.USER_TYPE === 'external') {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const dir = dirname(path)
|
|
109
|
+
if (!existsSync(dir)) {
|
|
110
|
+
mkdirSync(dir, { recursive: true })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Create messages file with empty array if it doesn't exist
|
|
114
|
+
if (!existsSync(path)) {
|
|
115
|
+
writeFileSync(path, '[]', 'utf8')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const messages = readLog(path)
|
|
119
|
+
const messageWithTimestamp = {
|
|
120
|
+
...message,
|
|
121
|
+
cwd: process.cwd(),
|
|
122
|
+
userType: process.env.USER_TYPE,
|
|
123
|
+
sessionId: SESSION_ID,
|
|
124
|
+
timestamp: new Date().toISOString(),
|
|
125
|
+
version: MACRO.VERSION,
|
|
126
|
+
}
|
|
127
|
+
messages.push(messageWithTimestamp)
|
|
128
|
+
|
|
129
|
+
writeFileSync(path, JSON.stringify(messages, null, 2), 'utf8')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function overwriteLog(path: string, messages: object[]): void {
|
|
133
|
+
if (process.env.USER_TYPE === 'external') {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!messages.length) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const dir = dirname(path)
|
|
142
|
+
if (!existsSync(dir)) {
|
|
143
|
+
mkdirSync(dir, { recursive: true })
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const messagesWithMetadata = messages.map(message => ({
|
|
147
|
+
...message,
|
|
148
|
+
cwd: process.cwd(),
|
|
149
|
+
userType: process.env.USER_TYPE,
|
|
150
|
+
sessionId: SESSION_ID,
|
|
151
|
+
timestamp: new Date().toISOString(),
|
|
152
|
+
version: MACRO.VERSION,
|
|
153
|
+
}))
|
|
154
|
+
|
|
155
|
+
writeFileSync(path, JSON.stringify(messagesWithMetadata, null, 2), 'utf8')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function loadLogList(
|
|
159
|
+
path = CACHE_PATHS.messages(),
|
|
160
|
+
): Promise<LogOption[]> {
|
|
161
|
+
if (!existsSync(path)) {
|
|
162
|
+
logError(`No logs found at ${path}`)
|
|
163
|
+
return []
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const files = await fsPromises.readdir(path)
|
|
167
|
+
const logData = await Promise.all(
|
|
168
|
+
files.map(async (file, i) => {
|
|
169
|
+
const fullPath = join(path, file)
|
|
170
|
+
const content = await fsPromises.readFile(fullPath, 'utf8')
|
|
171
|
+
const messages = JSON.parse(content) as SerializedMessage[]
|
|
172
|
+
const firstMessage = messages[0]
|
|
173
|
+
const lastMessage = messages[messages.length - 1]
|
|
174
|
+
const firstPrompt =
|
|
175
|
+
firstMessage?.type === 'user' &&
|
|
176
|
+
typeof firstMessage?.message?.content === 'string'
|
|
177
|
+
? firstMessage?.message?.content
|
|
178
|
+
: 'No prompt'
|
|
179
|
+
|
|
180
|
+
const { date, forkNumber, sidechainNumber } = parseLogFilename(file)
|
|
181
|
+
return {
|
|
182
|
+
date,
|
|
183
|
+
forkNumber,
|
|
184
|
+
fullPath,
|
|
185
|
+
messages,
|
|
186
|
+
value: i, // hack: overwritten after sorting, right below this
|
|
187
|
+
created: parseISOString(firstMessage?.timestamp || date),
|
|
188
|
+
modified: lastMessage?.timestamp
|
|
189
|
+
? parseISOString(lastMessage.timestamp)
|
|
190
|
+
: parseISOString(date),
|
|
191
|
+
firstPrompt:
|
|
192
|
+
firstPrompt.split('\n')[0]?.slice(0, 50) +
|
|
193
|
+
(firstPrompt.length > 50 ? '…' : '') || 'No prompt',
|
|
194
|
+
messageCount: messages.length,
|
|
195
|
+
sidechainNumber,
|
|
196
|
+
}
|
|
197
|
+
}),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return sortLogs(logData.filter(_ => _.messages.length)).map((_, i) => ({
|
|
201
|
+
..._,
|
|
202
|
+
value: i,
|
|
203
|
+
}))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function parseLogFilename(filename: string): {
|
|
207
|
+
date: string
|
|
208
|
+
forkNumber: number | undefined
|
|
209
|
+
sidechainNumber: number | undefined
|
|
210
|
+
} {
|
|
211
|
+
const base = filename.split('.')[0]!
|
|
212
|
+
// Default timestamp format has 6 segments: 2025-01-27T01-31-35-104Z
|
|
213
|
+
const segments = base.split('-')
|
|
214
|
+
const hasSidechain = base.includes('-sidechain-')
|
|
215
|
+
|
|
216
|
+
let date = base
|
|
217
|
+
let forkNumber: number | undefined = undefined
|
|
218
|
+
let sidechainNumber: number | undefined = undefined
|
|
219
|
+
|
|
220
|
+
if (hasSidechain) {
|
|
221
|
+
const sidechainIndex = segments.indexOf('sidechain')
|
|
222
|
+
sidechainNumber = Number(segments[sidechainIndex + 1])
|
|
223
|
+
// Fork number is before sidechain if exists
|
|
224
|
+
if (sidechainIndex > 6) {
|
|
225
|
+
forkNumber = Number(segments[sidechainIndex - 1])
|
|
226
|
+
date = segments.slice(0, 6).join('-')
|
|
227
|
+
} else {
|
|
228
|
+
date = segments.slice(0, 6).join('-')
|
|
229
|
+
}
|
|
230
|
+
} else if (segments.length > 6) {
|
|
231
|
+
// Has fork number
|
|
232
|
+
const lastSegment = Number(segments[segments.length - 1])
|
|
233
|
+
forkNumber = lastSegment >= 0 ? lastSegment : undefined
|
|
234
|
+
date = segments.slice(0, 6).join('-')
|
|
235
|
+
} else {
|
|
236
|
+
// Basic timestamp only
|
|
237
|
+
date = base
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { date, forkNumber, sidechainNumber }
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function getNextAvailableLogForkNumber(
|
|
244
|
+
date: string,
|
|
245
|
+
forkNumber: number,
|
|
246
|
+
// Main chain has sidechainNumber 0
|
|
247
|
+
sidechainNumber: number,
|
|
248
|
+
): number {
|
|
249
|
+
while (existsSync(getMessagesPath(date, forkNumber, sidechainNumber))) {
|
|
250
|
+
forkNumber++
|
|
251
|
+
}
|
|
252
|
+
return forkNumber
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function getNextAvailableLogSidechainNumber(
|
|
256
|
+
date: string,
|
|
257
|
+
forkNumber: number,
|
|
258
|
+
): number {
|
|
259
|
+
let sidechainNumber = 1
|
|
260
|
+
while (existsSync(getMessagesPath(date, forkNumber, sidechainNumber))) {
|
|
261
|
+
sidechainNumber++
|
|
262
|
+
}
|
|
263
|
+
return sidechainNumber
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function getForkNumberFromFilename(
|
|
267
|
+
filename: string,
|
|
268
|
+
): number | undefined {
|
|
269
|
+
const base = filename.split('.')[0]!
|
|
270
|
+
const segments = base.split('-')
|
|
271
|
+
const hasSidechain = base.includes('-sidechain-')
|
|
272
|
+
|
|
273
|
+
if (hasSidechain) {
|
|
274
|
+
const sidechainIndex = segments.indexOf('sidechain')
|
|
275
|
+
if (sidechainIndex > 6) {
|
|
276
|
+
return Number(segments[sidechainIndex - 1])
|
|
277
|
+
}
|
|
278
|
+
return undefined
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (segments.length > 6) {
|
|
282
|
+
const lastNumber = Number(segments[segments.length - 1])
|
|
283
|
+
return lastNumber >= 0 ? lastNumber : undefined
|
|
284
|
+
}
|
|
285
|
+
return undefined
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function sortLogs(logs: LogOption[]): LogOption[] {
|
|
289
|
+
return logs.sort((a, b) => {
|
|
290
|
+
// Sort by modified date (newest first)
|
|
291
|
+
const modifiedDiff = b.modified.getTime() - a.modified.getTime()
|
|
292
|
+
if (modifiedDiff !== 0) {
|
|
293
|
+
return modifiedDiff
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// If modified dates are equal, sort by created date
|
|
297
|
+
const createdDiff = b.created.getTime() - a.created.getTime()
|
|
298
|
+
if (createdDiff !== 0) {
|
|
299
|
+
return createdDiff
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// If both dates are equal, sort by fork number
|
|
303
|
+
return (b.forkNumber ?? 0) - (a.forkNumber ?? 0)
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function formatDate(date: Date): string {
|
|
308
|
+
const now = new Date()
|
|
309
|
+
const yesterday = new Date(now)
|
|
310
|
+
yesterday.setDate(yesterday.getDate() - 1)
|
|
311
|
+
|
|
312
|
+
const isToday = date.toDateString() === now.toDateString()
|
|
313
|
+
const isYesterday = date.toDateString() === yesterday.toDateString()
|
|
314
|
+
|
|
315
|
+
const timeStr = date
|
|
316
|
+
.toLocaleTimeString('en-US', {
|
|
317
|
+
hour: 'numeric',
|
|
318
|
+
minute: '2-digit',
|
|
319
|
+
hour12: true,
|
|
320
|
+
})
|
|
321
|
+
.toLowerCase()
|
|
322
|
+
|
|
323
|
+
if (isToday) {
|
|
324
|
+
return `Today at ${timeStr}`
|
|
325
|
+
} else if (isYesterday) {
|
|
326
|
+
return `Yesterday at ${timeStr}`
|
|
327
|
+
} else {
|
|
328
|
+
return (
|
|
329
|
+
date.toLocaleDateString('en-US', {
|
|
330
|
+
month: 'short',
|
|
331
|
+
day: 'numeric',
|
|
332
|
+
}) + ` at ${timeStr}`
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function parseISOString(s: string): Date {
|
|
338
|
+
const b = s.split(/\D+/)
|
|
339
|
+
return new Date(
|
|
340
|
+
Date.UTC(
|
|
341
|
+
parseInt(b[0]!, 10),
|
|
342
|
+
parseInt(b[1]!, 10) - 1,
|
|
343
|
+
parseInt(b[2]!, 10),
|
|
344
|
+
parseInt(b[3]!, 10),
|
|
345
|
+
parseInt(b[4]!, 10),
|
|
346
|
+
parseInt(b[5]!, 10),
|
|
347
|
+
parseInt(b[6]!, 10),
|
|
348
|
+
),
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function logMCPError(serverName: string, error: unknown): void {
|
|
353
|
+
try {
|
|
354
|
+
const logDir = CACHE_PATHS.mcpLogs(serverName)
|
|
355
|
+
const errorStr =
|
|
356
|
+
error instanceof Error ? error.stack || error.message : String(error)
|
|
357
|
+
const timestamp = new Date().toISOString()
|
|
358
|
+
|
|
359
|
+
const logFile = join(logDir, DATE + '.txt')
|
|
360
|
+
|
|
361
|
+
if (!existsSync(logDir)) {
|
|
362
|
+
mkdirSync(logDir, { recursive: true })
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!existsSync(logFile)) {
|
|
366
|
+
writeFileSync(logFile, '[]', 'utf8')
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const errorInfo = {
|
|
370
|
+
error: errorStr,
|
|
371
|
+
timestamp,
|
|
372
|
+
sessionId: SESSION_ID,
|
|
373
|
+
cwd: process.cwd(),
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const messages = readLog(logFile)
|
|
377
|
+
messages.push(errorInfo)
|
|
378
|
+
writeFileSync(logFile, JSON.stringify(messages, null, 2), 'utf8')
|
|
379
|
+
} catch {
|
|
380
|
+
// Silently fail
|
|
381
|
+
}
|
|
382
|
+
}
|