@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.
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,1561 +0,0 @@
1
- #!/usr/bin/env -S node --no-warnings=ExperimentalWarning --enable-source-maps
2
- import { fileURLToPath } from 'node:url'
3
- import { dirname, join } from 'node:path'
4
- import { existsSync } from 'node:fs'
5
- import { initSentry } from '../services/sentry'
6
- import { PRODUCT_COMMAND, PRODUCT_NAME } from '../constants/product'
7
- initSentry() // Initialize Sentry as early as possible
8
-
9
- // Ensure YOGA_WASM_PATH is set for Ink across run modes (wrapper/dev)
10
- // Resolve yoga.wasm relative to this file when missing using ESM-friendly APIs
11
- try {
12
- if (!process.env.YOGA_WASM_PATH) {
13
- const __filename = fileURLToPath(import.meta.url)
14
- const __dirname = dirname(__filename)
15
- const devCandidate = join(__dirname, '../../yoga.wasm')
16
- const distCandidate = join(__dirname, './yoga.wasm')
17
- const resolved = existsSync(distCandidate)
18
- ? distCandidate
19
- : existsSync(devCandidate)
20
- ? devCandidate
21
- : undefined
22
- if (resolved) {
23
- process.env.YOGA_WASM_PATH = resolved
24
- }
25
- }
26
- } catch {}
27
-
28
- // XXX: Without this line (and the Object.keys, even though it seems like it does nothing!),
29
- // there is a bug in Bun only on Win32 that causes this import to be removed, even though
30
- // its use is solely because of its side-effects.
31
- import * as dontcare from '@anthropic-ai/sdk/shims/node'
32
- Object.keys(dontcare)
33
-
34
- import React from 'react'
35
- import { ReadStream } from 'tty'
36
- import { openSync, existsSync } from 'fs'
37
- // ink and REPL are imported lazily to avoid top-level awaits during module init
38
- import type { RenderOptions } from 'ink'
39
- import { addToHistory } from '../history'
40
- import { getContext, setContext, removeContext } from '../context'
41
- import { Command } from '@commander-js/extra-typings'
42
- import { ask } from '../utils/ask'
43
- import { hasPermissionsToUseTool } from '../permissions'
44
- import { getTools } from '../tools'
45
- import {
46
- getGlobalConfig,
47
- getCurrentProjectConfig,
48
- saveGlobalConfig,
49
- saveCurrentProjectConfig,
50
- getCustomApiKeyStatus,
51
- normalizeApiKeyForConfig,
52
- setConfigForCLI,
53
- deleteConfigForCLI,
54
- getConfigForCLI,
55
- listConfigForCLI,
56
- enableConfigs,
57
- validateAndRepairAllGPT5Profiles,
58
- } from '../utils/config'
59
- import { cwd } from 'process'
60
- import { dateToFilename, logError, parseLogFilename } from '../utils/log'
61
- import { initDebugLogger } from '../utils/debugLogger'
62
- import { Onboarding } from '../components/Onboarding'
63
- import { Doctor } from '../screens/Doctor'
64
- import { ApproveApiKey } from '../components/ApproveApiKey'
65
- import { TrustDialog } from '../components/TrustDialog'
66
- import { checkHasTrustDialogAccepted, McpServerConfig } from '../utils/config'
67
- import { isDefaultSlowAndCapableModel } from '../utils/model'
68
- import { LogList } from '../screens/LogList'
69
- import { ResumeConversation } from '../screens/ResumeConversation'
70
- import { startMCPServer } from './mcp'
71
- import { env } from '../utils/env'
72
- import { getCwd, setCwd, setOriginalCwd } from '../utils/state'
73
- import { omit } from 'lodash-es'
74
- import { getCommands } from '../commands'
75
- import { getNextAvailableLogForkNumber, loadLogList } from '../utils/log'
76
- import { loadMessagesFromLog } from '../utils/conversationRecovery'
77
- import { cleanupOldMessageFilesInBackground } from '../utils/cleanup'
78
- import {
79
- handleListApprovedTools,
80
- handleRemoveApprovedTool,
81
- } from '../commands/approvedTools'
82
- import {
83
- addMcpServer,
84
- getMcpServer,
85
- listMCPServers,
86
- parseEnvVars,
87
- removeMcpServer,
88
- getClients,
89
- ensureConfigScope,
90
- } from '../services/mcpClient'
91
- import { handleMcprcServerApprovals } from '../services/mcpServerApproval'
92
- import { checkGate, initializeStatsig, logEvent } from '../services/statsig'
93
- import { getExampleCommands } from '../utils/exampleCommands'
94
- import { cursorShow } from 'ansi-escapes'
95
- import {
96
- getLatestVersion,
97
- installGlobalPackage,
98
- assertMinVersion,
99
- getUpdateCommandSuggestions,
100
- } from '../utils/autoUpdater'
101
- import { gt } from 'semver'
102
- import { CACHE_PATHS } from '../utils/log'
103
- // import { checkAndNotifyUpdate } from '../utils/autoUpdater'
104
- import { PersistentShell } from '../utils/PersistentShell'
105
- import { GATE_USE_EXTERNAL_UPDATER } from '../constants/betas'
106
- import { clearTerminal } from '../utils/terminal'
107
- import { showInvalidConfigDialog } from '../components/InvalidConfigDialog'
108
- import { ConfigParseError } from '../utils/errors'
109
- import { grantReadPermissionForOriginalDir } from '../utils/permissions/filesystem'
110
- import { MACRO } from '../constants/macros'
111
- export function completeOnboarding(): void {
112
- const config = getGlobalConfig()
113
- saveGlobalConfig({
114
- ...config,
115
- hasCompletedOnboarding: true,
116
- lastOnboardingVersion: MACRO.VERSION,
117
- })
118
- }
119
-
120
- async function showSetupScreens(
121
- safeMode?: boolean,
122
- print?: boolean,
123
- ): Promise<void> {
124
- if (process.env.NODE_ENV === 'test') {
125
- return
126
- }
127
-
128
- const config = getGlobalConfig()
129
- if (
130
- !config.theme ||
131
- !config.hasCompletedOnboarding // always show onboarding at least once
132
- ) {
133
- await clearTerminal()
134
- const { render } = await import('ink')
135
- await new Promise<void>(resolve => {
136
- render(
137
- <Onboarding
138
- onDone={async () => {
139
- completeOnboarding()
140
- await clearTerminal()
141
- resolve()
142
- }}
143
- />,
144
- {
145
- exitOnCtrlC: false,
146
- },
147
- )
148
- })
149
- }
150
-
151
- // // Check for custom API key (only allowed for ants)
152
- // if (process.env.ANTHROPIC_API_KEY && process.env.USER_TYPE === 'ant') {
153
- // const customApiKeyTruncated = normalizeApiKeyForConfig(
154
- // process.env.ANTHROPIC_API_KEY!,
155
- // )
156
- // const keyStatus = getCustomApiKeyStatus(customApiKeyTruncated)
157
- // if (keyStatus === 'new') {
158
- // await new Promise<void>(resolve => {
159
- // render(
160
- // <ApproveApiKey
161
- // customApiKeyTruncated={customApiKeyTruncated}
162
- // onDone={async () => {
163
- // await clearTerminal()
164
- // resolve()
165
- // }}
166
- // />,
167
- // {
168
- // exitOnCtrlC: false,
169
- // },
170
- // )
171
- // })
172
- // }
173
- // }
174
-
175
- // In non-interactive mode, only show trust dialog in safe mode
176
- if (!print && safeMode) {
177
- if (!checkHasTrustDialogAccepted()) {
178
- await new Promise<void>(resolve => {
179
- const onDone = () => {
180
- // Grant read permission to the current working directory
181
- grantReadPermissionForOriginalDir()
182
- resolve()
183
- }
184
- render(<TrustDialog onDone={onDone} />, {
185
- exitOnCtrlC: false,
186
- })
187
- })
188
- }
189
-
190
- // After trust dialog, check for any mcprc servers that need approval
191
- if (process.env.USER_TYPE === 'ant') {
192
- await handleMcprcServerApprovals()
193
- }
194
- }
195
- }
196
-
197
- function logStartup(): void {
198
- const config = getGlobalConfig()
199
- saveGlobalConfig({
200
- ...config,
201
- numStartups: (config.numStartups ?? 0) + 1,
202
- })
203
- }
204
-
205
- async function setup(cwd: string, safeMode?: boolean): Promise<void> {
206
- // Set both current and original working directory if --cwd was provided
207
- if (cwd !== process.cwd()) {
208
- setOriginalCwd(cwd)
209
- }
210
- await setCwd(cwd)
211
-
212
- // Always grant read permissions for original working dir
213
- grantReadPermissionForOriginalDir()
214
-
215
- // Start watching agent configuration files for changes
216
- // Try ESM-friendly path first (compiled dist), then fall back to extensionless (dev/tsx)
217
- let agentLoader: any
218
- try {
219
- agentLoader = await import('../utils/agentLoader.js')
220
- } catch {
221
- agentLoader = await import('../utils/agentLoader')
222
- }
223
- const { startAgentWatcher, clearAgentCache } = agentLoader
224
- await startAgentWatcher(() => {
225
- // Cache is already cleared in the watcher, just log
226
- console.log('✅ Agent configurations hot-reloaded')
227
- })
228
-
229
- // If --safe mode is enabled, prevent root/sudo usage for security
230
- if (safeMode) {
231
- // Check if running as root/sudo on Unix-like systems
232
- if (
233
- process.platform !== 'win32' &&
234
- typeof process.getuid === 'function' &&
235
- process.getuid() === 0
236
- ) {
237
- console.error(
238
- `--safe mode cannot be used with root/sudo privileges for security reasons`,
239
- )
240
- process.exit(1)
241
- }
242
- }
243
-
244
- if (process.env.NODE_ENV === 'test') {
245
- return
246
- }
247
-
248
- cleanupOldMessageFilesInBackground()
249
- // getExampleCommands() // Pre-fetch example commands
250
- getContext() // Pre-fetch all context data at once
251
- // initializeStatsig() // Kick off statsig initialization
252
-
253
- // Migrate old iterm2KeyBindingInstalled config to new shiftEnterKeyBindingInstalled
254
- const globalConfig = getGlobalConfig()
255
- if (
256
- globalConfig.iterm2KeyBindingInstalled === true &&
257
- globalConfig.shiftEnterKeyBindingInstalled !== true
258
- ) {
259
- const updatedConfig = {
260
- ...globalConfig,
261
- shiftEnterKeyBindingInstalled: true,
262
- }
263
- // Remove the old config property
264
- delete updatedConfig.iterm2KeyBindingInstalled
265
- saveGlobalConfig(updatedConfig)
266
- }
267
-
268
- // Check for last session's cost and duration
269
- const projectConfig = getCurrentProjectConfig()
270
- if (
271
- projectConfig.lastCost !== undefined &&
272
- projectConfig.lastDuration !== undefined
273
- ) {
274
- logEvent('tengu_exit', {
275
- last_session_cost: String(projectConfig.lastCost),
276
- last_session_api_duration: String(projectConfig.lastAPIDuration),
277
- last_session_duration: String(projectConfig.lastDuration),
278
- last_session_id: projectConfig.lastSessionId,
279
- })
280
- // Clear the values after logging
281
- // saveCurrentProjectConfig({
282
- // ...projectConfig,
283
- // lastCost: undefined,
284
- // lastAPIDuration: undefined,
285
- // lastDuration: undefined,
286
- // lastSessionId: undefined,
287
- // })
288
- }
289
-
290
- // Check auto-updater permissions
291
- const autoUpdaterStatus = globalConfig.autoUpdaterStatus ?? 'not_configured'
292
- if (autoUpdaterStatus === 'not_configured') {
293
- logEvent('tengu_setup_auto_updater_not_configured', {})
294
- await new Promise<void>(resolve => {
295
- render(<Doctor onDone={() => resolve()} />)
296
- })
297
- }
298
- }
299
-
300
- async function main() {
301
- // 初始化调试日志系统
302
- initDebugLogger()
303
-
304
- // Validate configs are valid and enable configuration system
305
- try {
306
- enableConfigs()
307
-
308
- // 🔧 Validate and auto-repair GPT-5 model profiles
309
- try {
310
- const repairResult = validateAndRepairAllGPT5Profiles()
311
- if (repairResult.repaired > 0) {
312
- console.log(`🔧 Auto-repaired ${repairResult.repaired} GPT-5 model configurations`)
313
- }
314
- } catch (repairError) {
315
- // Don't block startup if GPT-5 validation fails
316
- console.warn('⚠️ GPT-5 configuration validation failed:', repairError)
317
- }
318
- } catch (error: unknown) {
319
- if (error instanceof ConfigParseError) {
320
- // Show the invalid config dialog with the error object
321
- await showInvalidConfigDialog({ error })
322
- return // Exit after handling the config error
323
- }
324
- }
325
-
326
- // Disabled background notifier to avoid mid-screen logs during REPL
327
-
328
- let inputPrompt = ''
329
- let renderContext: RenderOptions | undefined = {
330
- exitOnCtrlC: false,
331
-
332
- onFlicker() {
333
- logEvent('tengu_flicker', {})
334
- },
335
- } as any
336
-
337
- if (
338
- !process.stdin.isTTY &&
339
- !process.env.CI &&
340
- // Input hijacking breaks MCP.
341
- !process.argv.includes('mcp')
342
- ) {
343
- inputPrompt = await stdin()
344
- if (process.platform !== 'win32') {
345
- try {
346
- const ttyFd = openSync('/dev/tty', 'r')
347
- renderContext = { ...renderContext, stdin: new ReadStream(ttyFd) }
348
- } catch (err) {
349
- logError(`Could not open /dev/tty: ${err}`)
350
- }
351
- }
352
- }
353
- await parseArgs(inputPrompt, renderContext)
354
- }
355
-
356
- async function parseArgs(
357
- stdinContent: string,
358
- renderContext: RenderOptions | undefined,
359
- ): Promise<Command> {
360
- const program = new Command()
361
-
362
- const renderContextWithExitOnCtrlC = {
363
- ...renderContext,
364
- exitOnCtrlC: true,
365
- }
366
-
367
- // Get the initial list of commands filtering based on user type
368
- const commands = await getCommands()
369
-
370
- // Format command list for help text (using same filter as in help.ts)
371
- const commandList = commands
372
- .filter(cmd => !cmd.isHidden)
373
- .map(cmd => `/${cmd.name} - ${cmd.description}`)
374
- .join('\n')
375
-
376
- program
377
- .name(PRODUCT_COMMAND)
378
- .description(
379
- `${PRODUCT_NAME} - starts an interactive session by default, use -p/--print for non-interactive output
380
-
381
- Slash commands available during an interactive session:
382
- ${commandList}`,
383
- )
384
- .argument('[prompt]', 'Your prompt', String)
385
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
386
- .option('-d, --debug', 'Enable debug mode', () => true)
387
- .option(
388
- '--debug-verbose',
389
- 'Enable verbose debug terminal output',
390
- () => true,
391
- )
392
- .option(
393
- '--verbose',
394
- 'Override verbose mode setting from config',
395
- () => true,
396
- )
397
- .option('-e, --enable-architect', 'Enable the Architect tool', () => true)
398
- .option(
399
- '-p, --print',
400
- 'Print response and exit (useful for pipes)',
401
- () => true,
402
- )
403
- .option(
404
- '--safe',
405
- 'Enable strict permission checking mode (default is permissive)',
406
- () => true,
407
- )
408
- .action(
409
- async (prompt, { cwd, debug, verbose, enableArchitect, print, safe }) => {
410
- await showSetupScreens(safe, print)
411
- logEvent('tengu_init', {
412
- entrypoint: PRODUCT_COMMAND,
413
- hasInitialPrompt: Boolean(prompt).toString(),
414
- hasStdin: Boolean(stdinContent).toString(),
415
- enableArchitect: enableArchitect?.toString() ?? 'false',
416
- verbose: verbose?.toString() ?? 'false',
417
- debug: debug?.toString() ?? 'false',
418
- print: print?.toString() ?? 'false',
419
- })
420
- await setup(cwd, safe)
421
-
422
- assertMinVersion()
423
-
424
- const [tools, mcpClients] = await Promise.all([
425
- getTools(
426
- enableArchitect ?? getCurrentProjectConfig().enableArchitectTool,
427
- ),
428
- getClients(),
429
- ])
430
- // logStartup()
431
- const inputPrompt = [prompt, stdinContent].filter(Boolean).join('\n')
432
- if (print) {
433
- if (!inputPrompt) {
434
- console.error(
435
- 'Error: Input must be provided either through stdin or as a prompt argument when using --print',
436
- )
437
- process.exit(1)
438
- }
439
-
440
- addToHistory(inputPrompt)
441
- const { resultText: response } = await ask({
442
- commands,
443
- hasPermissionsToUseTool,
444
- messageLogName: dateToFilename(new Date()),
445
- prompt: inputPrompt,
446
- cwd,
447
- tools,
448
- safeMode: safe,
449
- })
450
- console.log(response)
451
- process.exit(0)
452
- } else {
453
- const isDefaultModel = await isDefaultSlowAndCapableModel()
454
-
455
- // Prefetch update info before first render to place banner at top
456
- const updateInfo = await (async () => {
457
- try {
458
- const latest = await getLatestVersion()
459
- if (latest && gt(latest, MACRO.VERSION)) {
460
- const cmds = await getUpdateCommandSuggestions()
461
- return { version: latest as string, commands: cmds as string[] }
462
- }
463
- } catch {}
464
- return { version: null as string | null, commands: null as string[] | null }
465
- })()
466
-
467
- {
468
- const { render } = await import('ink')
469
- const { REPL } = await import('../screens/REPL')
470
- render(
471
- <REPL
472
- commands={commands}
473
- debug={debug}
474
- initialPrompt={inputPrompt}
475
- messageLogName={dateToFilename(new Date())}
476
- shouldShowPromptInput={true}
477
- verbose={verbose}
478
- tools={tools}
479
- safeMode={safe}
480
- mcpClients={mcpClients}
481
- isDefaultModel={isDefaultModel}
482
- initialUpdateVersion={updateInfo.version}
483
- initialUpdateCommands={updateInfo.commands}
484
- />,
485
- renderContext,
486
- )
487
- }
488
- }
489
- },
490
- )
491
- .version(MACRO.VERSION, '-v, --version')
492
-
493
- // Enable melon mode for ants if --melon is passed
494
- // For bun tree shaking to work, this has to be a top level --define, not inside MACRO
495
- // if (process.env.USER_TYPE === 'ant') {
496
- // program
497
- // .option('--melon', 'Enable melon mode')
498
- // .hook('preAction', async () => {
499
- // if ((program.opts() as { melon?: boolean }).melon) {
500
- // const { runMelonWrapper } = await import('../utils/melonWrapper')
501
- // const melonArgs = process.argv.slice(
502
- // process.argv.indexOf('--melon') + 1,
503
- // )
504
- // const exitCode = runMelonWrapper(melonArgs)
505
- // process.exit(exitCode)
506
- // }
507
- // })
508
- // }
509
-
510
- // claude config
511
- const config = program
512
- .command('config')
513
- .description(
514
- `Manage configuration (eg. ${PRODUCT_COMMAND} config set -g theme dark)`,
515
- )
516
-
517
- config
518
- .command('get <key>')
519
- .description('Get a config value')
520
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
521
- .option('-g, --global', 'Use global config')
522
- .action(async (key, { cwd, global }) => {
523
- await setup(cwd, false)
524
- console.log(getConfigForCLI(key, global ?? false))
525
- process.exit(0)
526
- })
527
-
528
- config
529
- .command('set <key> <value>')
530
- .description('Set a config value')
531
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
532
- .option('-g, --global', 'Use global config')
533
- .action(async (key, value, { cwd, global }) => {
534
- await setup(cwd, false)
535
- setConfigForCLI(key, value, global ?? false)
536
- console.log(`Set ${key} to ${value}`)
537
- process.exit(0)
538
- })
539
-
540
- config
541
- .command('remove <key>')
542
- .description('Remove a config value')
543
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
544
- .option('-g, --global', 'Use global config')
545
- .action(async (key, { cwd, global }) => {
546
- await setup(cwd, false)
547
- deleteConfigForCLI(key, global ?? false)
548
- console.log(`Removed ${key}`)
549
- process.exit(0)
550
- })
551
-
552
- config
553
- .command('list')
554
- .description('List all config values')
555
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
556
- .option('-g, --global', 'Use global config', false)
557
- .action(async ({ cwd, global }) => {
558
- await setup(cwd, false)
559
- console.log(
560
- JSON.stringify(global ? listConfigForCLI(true) : listConfigForCLI(false), null, 2),
561
- )
562
- process.exit(0)
563
- })
564
-
565
- // claude approved-tools
566
-
567
- const allowedTools = program
568
- .command('approved-tools')
569
- .description('Manage approved tools')
570
-
571
- allowedTools
572
- .command('list')
573
- .description('List all approved tools')
574
- .action(async () => {
575
- const result = handleListApprovedTools(getCwd())
576
- console.log(result)
577
- process.exit(0)
578
- })
579
-
580
- allowedTools
581
- .command('remove <tool>')
582
- .description('Remove a tool from the list of approved tools')
583
- .action(async (tool: string) => {
584
- const result = handleRemoveApprovedTool(tool)
585
- logEvent('tengu_approved_tool_remove', {
586
- tool,
587
- success: String(result.success),
588
- })
589
- console.log(result.message)
590
- process.exit(result.success ? 0 : 1)
591
- })
592
-
593
- // claude mcp
594
-
595
- const mcp = program
596
- .command('mcp')
597
- .description('Configure and manage MCP servers')
598
-
599
- mcp
600
- .command('serve')
601
- .description(`Start the ${PRODUCT_NAME} MCP server`)
602
- .action(async () => {
603
- const providedCwd = (program.opts() as { cwd?: string }).cwd ?? cwd()
604
- logEvent('tengu_mcp_start', { providedCwd })
605
-
606
- // Verify the directory exists
607
- if (!existsSync(providedCwd)) {
608
- console.error(`Error: Directory ${providedCwd} does not exist`)
609
- process.exit(1)
610
- }
611
-
612
- try {
613
- await setup(providedCwd, false)
614
- await startMCPServer(providedCwd)
615
- } catch (error) {
616
- console.error('Error: Failed to start MCP server:', error)
617
- process.exit(1)
618
- }
619
- })
620
-
621
- mcp
622
- .command('add-sse <name> <url>')
623
- .description('Add an SSE server')
624
- .option(
625
- '-s, --scope <scope>',
626
- 'Configuration scope (project or global)',
627
- 'project',
628
- )
629
- .action(async (name, url, options) => {
630
- try {
631
- const scope = ensureConfigScope(options.scope)
632
- logEvent('tengu_mcp_add', { name, type: 'sse', scope })
633
-
634
- addMcpServer(name, { type: 'sse', url }, scope)
635
- console.log(
636
- `Added SSE MCP server ${name} with URL ${url} to ${scope} config`,
637
- )
638
- process.exit(0)
639
- } catch (error) {
640
- console.error((error as Error).message)
641
- process.exit(1)
642
- }
643
- })
644
-
645
- mcp
646
- .command('add [name] [commandOrUrl] [args...]')
647
- .description('Add a server (run without arguments for interactive wizard)')
648
- .option(
649
- '-s, --scope <scope>',
650
- 'Configuration scope (project or global)',
651
- 'project',
652
- )
653
- .option(
654
- '-e, --env <env...>',
655
- 'Set environment variables (e.g. -e KEY=value)',
656
- )
657
- .action(async (name, commandOrUrl, args, options) => {
658
- try {
659
- // If name is not provided, start interactive wizard
660
- if (!name) {
661
- console.log('Interactive wizard mode: Enter the server details')
662
- const { createInterface } = await import('readline')
663
- const rl = createInterface({
664
- input: process.stdin,
665
- output: process.stdout,
666
- })
667
-
668
- const question = (query: string) =>
669
- new Promise<string>(resolve => rl.question(query, resolve))
670
-
671
- // Get server name
672
- const serverName = await question('Server name: ')
673
- if (!serverName) {
674
- console.error('Error: Server name is required')
675
- rl.close()
676
- process.exit(1)
677
- }
678
-
679
- // Get server type
680
- const serverType = await question(
681
- 'Server type (stdio or sse) [stdio]: ',
682
- )
683
- const type =
684
- serverType && ['stdio', 'sse'].includes(serverType)
685
- ? serverType
686
- : 'stdio'
687
-
688
- // Get command or URL
689
- const prompt = type === 'stdio' ? 'Command: ' : 'URL: '
690
- const commandOrUrlValue = await question(prompt)
691
- if (!commandOrUrlValue) {
692
- console.error(
693
- `Error: ${type === 'stdio' ? 'Command' : 'URL'} is required`,
694
- )
695
- rl.close()
696
- process.exit(1)
697
- }
698
-
699
- // Get args and env if stdio
700
- let serverArgs: string[] = []
701
- let serverEnv: Record<string, string> = {}
702
-
703
- if (type === 'stdio') {
704
- const argsStr = await question(
705
- 'Command arguments (space-separated): ',
706
- )
707
- serverArgs = argsStr ? argsStr.split(' ').filter(Boolean) : []
708
-
709
- const envStr = await question(
710
- 'Environment variables (format: KEY1=value1,KEY2=value2): ',
711
- )
712
- if (envStr) {
713
- const envPairs = envStr.split(',').map(pair => pair.trim())
714
- serverEnv = parseEnvVars(envPairs.map(pair => pair))
715
- }
716
- }
717
-
718
- // Get scope
719
- const scopeStr = await question(
720
- 'Configuration scope (project or global) [project]: ',
721
- )
722
- const serverScope = ensureConfigScope(scopeStr || 'project')
723
-
724
- rl.close()
725
-
726
- // Add the server
727
- if (type === 'sse') {
728
- logEvent('tengu_mcp_add', {
729
- name: serverName,
730
- type: 'sse',
731
- scope: serverScope,
732
- })
733
- addMcpServer(
734
- serverName,
735
- { type: 'sse', url: commandOrUrlValue },
736
- serverScope,
737
- )
738
- console.log(
739
- `Added SSE MCP server ${serverName} with URL ${commandOrUrlValue} to ${serverScope} config`,
740
- )
741
- } else {
742
- logEvent('tengu_mcp_add', {
743
- name: serverName,
744
- type: 'stdio',
745
- scope: serverScope,
746
- })
747
- addMcpServer(
748
- serverName,
749
- {
750
- type: 'stdio',
751
- command: commandOrUrlValue,
752
- args: serverArgs,
753
- env: serverEnv,
754
- },
755
- serverScope,
756
- )
757
-
758
- console.log(
759
- `Added stdio MCP server ${serverName} with command: ${commandOrUrlValue} ${serverArgs.join(' ')} to ${serverScope} config`,
760
- )
761
- }
762
- } else if (name && commandOrUrl) {
763
- // Regular non-interactive flow
764
- const scope = ensureConfigScope(options.scope)
765
-
766
- // Check if it's an SSE URL (starts with http:// or https://)
767
- if (commandOrUrl.match(/^https?:\/\//)) {
768
- logEvent('tengu_mcp_add', { name, type: 'sse', scope })
769
- addMcpServer(name, { type: 'sse', url: commandOrUrl }, scope)
770
- console.log(
771
- `Added SSE MCP server ${name} with URL ${commandOrUrl} to ${scope} config`,
772
- )
773
- } else {
774
- logEvent('tengu_mcp_add', { name, type: 'stdio', scope })
775
- const env = parseEnvVars(options.env)
776
- addMcpServer(
777
- name,
778
- { type: 'stdio', command: commandOrUrl, args: args || [], env },
779
- scope,
780
- )
781
-
782
- console.log(
783
- `Added stdio MCP server ${name} with command: ${commandOrUrl} ${(args || []).join(' ')} to ${scope} config`,
784
- )
785
- }
786
- } else {
787
- console.error(
788
- 'Error: Missing required arguments. Either provide no arguments for interactive mode or specify name and command/URL.',
789
- )
790
- process.exit(1)
791
- }
792
-
793
- process.exit(0)
794
- } catch (error) {
795
- console.error((error as Error).message)
796
- process.exit(1)
797
- }
798
- })
799
- mcp
800
- .command('remove <name>')
801
- .description('Remove an MCP server')
802
- .option(
803
- '-s, --scope <scope>',
804
- 'Configuration scope (project, global, or mcprc)',
805
- 'project',
806
- )
807
- .action(async (name: string, options: { scope?: string }) => {
808
- try {
809
- const scope = ensureConfigScope(options.scope)
810
- logEvent('tengu_mcp_delete', { name, scope })
811
-
812
- removeMcpServer(name, scope)
813
- console.log(`Removed MCP server ${name} from ${scope} config`)
814
- process.exit(0)
815
- } catch (error) {
816
- console.error((error as Error).message)
817
- process.exit(1)
818
- }
819
- })
820
-
821
- mcp
822
- .command('list')
823
- .description('List configured MCP servers')
824
- .action(() => {
825
- logEvent('tengu_mcp_list', {})
826
- const servers = listMCPServers()
827
- if (Object.keys(servers).length === 0) {
828
- console.log(
829
- `No MCP servers configured. Use \`${PRODUCT_COMMAND} mcp add\` to add a server.`,
830
- )
831
- } else {
832
- for (const [name, server] of Object.entries(servers)) {
833
- if (server.type === 'sse') {
834
- console.log(`${name}: ${server.url} (SSE)`)
835
- } else {
836
- console.log(`${name}: ${server.command} ${server.args.join(' ')}`)
837
- }
838
- }
839
- }
840
- process.exit(0)
841
- })
842
-
843
- mcp
844
- .command('add-json <name> <json>')
845
- .description('Add an MCP server (stdio or SSE) with a JSON string')
846
- .option(
847
- '-s, --scope <scope>',
848
- 'Configuration scope (project or global)',
849
- 'project',
850
- )
851
- .action(async (name, jsonStr, options) => {
852
- try {
853
- const scope = ensureConfigScope(options.scope)
854
-
855
- // Parse JSON string
856
- let serverConfig
857
- try {
858
- serverConfig = JSON.parse(jsonStr)
859
- } catch (e) {
860
- console.error('Error: Invalid JSON string')
861
- process.exit(1)
862
- }
863
-
864
- // Validate the server config
865
- if (
866
- !serverConfig.type ||
867
- !['stdio', 'sse'].includes(serverConfig.type)
868
- ) {
869
- console.error('Error: Server type must be "stdio" or "sse"')
870
- process.exit(1)
871
- }
872
-
873
- if (serverConfig.type === 'sse' && !serverConfig.url) {
874
- console.error('Error: SSE server must have a URL')
875
- process.exit(1)
876
- }
877
-
878
- if (serverConfig.type === 'stdio' && !serverConfig.command) {
879
- console.error('Error: stdio server must have a command')
880
- process.exit(1)
881
- }
882
-
883
- // Add server with the provided config
884
- logEvent('tengu_mcp_add_json', { name, type: serverConfig.type, scope })
885
- addMcpServer(name, serverConfig, scope)
886
-
887
- if (serverConfig.type === 'sse') {
888
- console.log(
889
- `Added SSE MCP server ${name} with URL ${serverConfig.url} to ${scope} config`,
890
- )
891
- } else {
892
- console.log(
893
- `Added stdio MCP server ${name} with command: ${serverConfig.command} ${(
894
- serverConfig.args || []
895
- ).join(' ')} to ${scope} config`,
896
- )
897
- }
898
-
899
- process.exit(0)
900
- } catch (error) {
901
- console.error((error as Error).message)
902
- process.exit(1)
903
- }
904
- })
905
-
906
- mcp
907
- .command('get <name>')
908
- .description('Get details about an MCP server')
909
- .action((name: string) => {
910
- logEvent('tengu_mcp_get', { name })
911
- const server = getMcpServer(name)
912
- if (!server) {
913
- console.error(`No MCP server found with name: ${name}`)
914
- process.exit(1)
915
- }
916
- console.log(`${name}:`)
917
- console.log(` Scope: ${server.scope}`)
918
- if (server.type === 'sse') {
919
- console.log(` Type: sse`)
920
- console.log(` URL: ${server.url}`)
921
- } else {
922
- console.log(` Type: stdio`)
923
- console.log(` Command: ${server.command}`)
924
- console.log(` Args: ${server.args.join(' ')}`)
925
- if (server.env) {
926
- console.log(' Environment:')
927
- for (const [key, value] of Object.entries(server.env)) {
928
- console.log(` ${key}=${value}`)
929
- }
930
- }
931
- }
932
- process.exit(0)
933
- })
934
-
935
- // Import servers from Claude Desktop
936
- mcp
937
- .command('add-from-claude-desktop')
938
- .description(
939
- 'Import MCP servers from Claude Desktop (Mac, Windows and WSL)',
940
- )
941
- .option(
942
- '-s, --scope <scope>',
943
- 'Configuration scope (project or global)',
944
- 'project',
945
- )
946
- .action(async options => {
947
- try {
948
- const scope = ensureConfigScope(options.scope)
949
- const platform = process.platform
950
-
951
- // Import fs and path modules
952
- const { existsSync, readFileSync } = await import('fs')
953
- const { join } = await import('path')
954
- const { exec } = await import('child_process')
955
-
956
- // Determine if running in WSL
957
- const isWSL =
958
- platform === 'linux' &&
959
- existsSync('/proc/version') &&
960
- readFileSync('/proc/version', 'utf-8')
961
- .toLowerCase()
962
- .includes('microsoft')
963
-
964
- if (platform !== 'darwin' && platform !== 'win32' && !isWSL) {
965
- console.error(
966
- 'Error: This command is only supported on macOS, Windows, and WSL',
967
- )
968
- process.exit(1)
969
- }
970
-
971
- // Get Claude Desktop config path
972
- let configPath
973
- if (platform === 'darwin') {
974
- configPath = join(
975
- process.env.HOME || '~',
976
- 'Library/Application Support/Claude/claude_desktop_config.json',
977
- )
978
- } else if (platform === 'win32') {
979
- configPath = join(
980
- process.env.APPDATA || '',
981
- 'Claude/claude_desktop_config.json',
982
- )
983
- } else if (isWSL) {
984
- // Get Windows username
985
- const whoamiCommand = await new Promise<string>((resolve, reject) => {
986
- exec(
987
- 'powershell.exe -Command "whoami"',
988
- (err: Error, stdout: string) => {
989
- if (err) reject(err)
990
- else resolve(stdout.trim().split('\\').pop() || '')
991
- },
992
- )
993
- })
994
-
995
- configPath = `/mnt/c/Users/${whoamiCommand}/AppData/Roaming/Claude/claude_desktop_config.json`
996
- }
997
-
998
- // Check if config file exists
999
- if (!existsSync(configPath)) {
1000
- console.error(
1001
- `Error: Claude Desktop config file not found at ${configPath}`,
1002
- )
1003
- process.exit(1)
1004
- }
1005
-
1006
- // Read config file
1007
- let config
1008
- try {
1009
- const configContent = readFileSync(configPath, 'utf-8')
1010
- config = JSON.parse(configContent)
1011
- } catch (err) {
1012
- console.error(`Error reading config file: ${err}`)
1013
- process.exit(1)
1014
- }
1015
-
1016
- // Extract MCP servers
1017
- const mcpServers = config.mcpServers || {}
1018
- const serverNames = Object.keys(mcpServers)
1019
- const numServers = serverNames.length
1020
-
1021
- if (numServers === 0) {
1022
- console.log('No MCP servers found in Claude Desktop config')
1023
- process.exit(0)
1024
- }
1025
-
1026
- // Create server information for display
1027
- const serversInfo = serverNames.map(name => {
1028
- const server = mcpServers[name]
1029
- let description = ''
1030
-
1031
- if (server.type === 'sse') {
1032
- description = `SSE: ${server.url}`
1033
- } else {
1034
- description = `stdio: ${server.command} ${(server.args || []).join(' ')}`
1035
- }
1036
-
1037
- return { name, description, server }
1038
- })
1039
-
1040
- // First import all required modules outside the component
1041
- // Import modules separately to avoid any issues
1042
- const ink = await import('ink')
1043
- const reactModule = await import('react')
1044
- const inkjsui = await import('@inkjs/ui')
1045
- const utilsTheme = await import('../utils/theme')
1046
-
1047
- const { render } = ink
1048
- const React = reactModule // React is already the default export when imported this way
1049
- const { MultiSelect } = inkjsui
1050
- const { Box, Text } = ink
1051
- const { getTheme } = utilsTheme
1052
-
1053
- // Use Ink to render a nice UI for selection
1054
- await new Promise<void>(resolve => {
1055
- // Create a component for the server selection
1056
- function ClaudeDesktopImport() {
1057
- const { useState } = reactModule
1058
- const [isFinished, setIsFinished] = useState(false)
1059
- const [importResults, setImportResults] = useState([] as { name: string; success: boolean }[])
1060
- const [isImporting, setIsImporting] = useState(false)
1061
- const theme = getTheme()
1062
-
1063
- // Function to import selected servers
1064
- const importServers = async (selectedServers: string[]) => {
1065
- setIsImporting(true)
1066
- const results = []
1067
-
1068
- for (const name of selectedServers) {
1069
- try {
1070
- const server = mcpServers[name]
1071
-
1072
- // Check if server already exists
1073
- const existingServer = getMcpServer(name)
1074
- if (existingServer) {
1075
- // Skip duplicates - we'll handle them in the confirmation step
1076
- continue
1077
- }
1078
-
1079
- addMcpServer(name, server as McpServerConfig, scope)
1080
- results.push({ name, success: true })
1081
- } catch (err) {
1082
- results.push({ name, success: false })
1083
- }
1084
- }
1085
-
1086
- setImportResults(results)
1087
- setIsImporting(false)
1088
- setIsFinished(true)
1089
-
1090
- // Give time to show results
1091
- setTimeout(() => {
1092
- resolve()
1093
- }, 1000)
1094
- }
1095
-
1096
- // Handle confirmation of selections
1097
- const handleConfirm = async (selectedServers: string[]) => {
1098
- // Check for existing servers and confirm overwrite
1099
- const existingServers = selectedServers.filter(name =>
1100
- getMcpServer(name),
1101
- )
1102
-
1103
- if (existingServers.length > 0) {
1104
- // We'll just handle it directly since we have a simple UI
1105
- const results = []
1106
-
1107
- // Process non-existing servers first
1108
- const newServers = selectedServers.filter(
1109
- name => !getMcpServer(name),
1110
- )
1111
- for (const name of newServers) {
1112
- try {
1113
- const server = mcpServers[name]
1114
- addMcpServer(name, server as McpServerConfig, scope)
1115
- results.push({ name, success: true })
1116
- } catch (err) {
1117
- results.push({ name, success: false })
1118
- }
1119
- }
1120
-
1121
- // Now handle existing servers by prompting for each one
1122
- for (const name of existingServers) {
1123
- try {
1124
- const server = mcpServers[name]
1125
- // Overwrite existing server - in a real interactive UI you'd prompt here
1126
- addMcpServer(name, server as McpServerConfig, scope)
1127
- results.push({ name, success: true })
1128
- } catch (err) {
1129
- results.push({ name, success: false })
1130
- }
1131
- }
1132
-
1133
- setImportResults(results)
1134
- setIsImporting(false)
1135
- setIsFinished(true)
1136
-
1137
- // Give time to show results before resolving
1138
- setTimeout(() => {
1139
- resolve()
1140
- }, 1000)
1141
- } else {
1142
- // No existing servers, proceed with import
1143
- await importServers(selectedServers)
1144
- }
1145
- }
1146
-
1147
- return (
1148
- <Box flexDirection="column" padding={1}>
1149
- <Box
1150
- flexDirection="column"
1151
- borderStyle="round"
1152
- borderColor={theme.kode}
1153
- padding={1}
1154
- width={'100%'}
1155
- >
1156
- <Text bold color={theme.kode}>
1157
- Import MCP Servers from Claude Desktop
1158
- </Text>
1159
-
1160
- <Box marginY={1}>
1161
- <Text>
1162
- Found {numServers} MCP servers in Claude Desktop.
1163
- </Text>
1164
- </Box>
1165
-
1166
- <Text>Please select the servers you want to import:</Text>
1167
-
1168
- <Box marginTop={1}>
1169
- <MultiSelect
1170
- options={serverNames.map(name => ({
1171
- label: name,
1172
- value: name,
1173
- }))}
1174
- defaultValue={serverNames}
1175
- onSubmit={handleConfirm}
1176
- />
1177
- </Box>
1178
- </Box>
1179
-
1180
- <Box marginTop={0} marginLeft={3}>
1181
- <Text dimColor>
1182
- Space to select · Enter to confirm · Esc to cancel
1183
- </Text>
1184
- </Box>
1185
-
1186
- {isFinished && (
1187
- <Box marginTop={1}>
1188
- <Text color={theme.success}>
1189
- Successfully imported{' '}
1190
- {importResults.filter(r => r.success).length} MCP server
1191
- to local config.
1192
- </Text>
1193
- </Box>
1194
- )}
1195
- </Box>
1196
- )
1197
- }
1198
-
1199
- // Render the component
1200
- const { unmount } = render(<ClaudeDesktopImport />)
1201
-
1202
- // Clean up when done
1203
- setTimeout(() => {
1204
- unmount()
1205
- resolve()
1206
- }, 30000) // Timeout after 30 seconds as a fallback
1207
- })
1208
-
1209
- process.exit(0)
1210
- } catch (error) {
1211
- console.error(`Error: ${(error as Error).message}`)
1212
- process.exit(1)
1213
- }
1214
- })
1215
-
1216
- // Function to reset MCP server choices
1217
- const resetMcpChoices = () => {
1218
- const config = getCurrentProjectConfig()
1219
- saveCurrentProjectConfig({
1220
- ...config,
1221
- approvedMcprcServers: [],
1222
- rejectedMcprcServers: [],
1223
- })
1224
- console.log('All .mcprc server approvals and rejections have been reset.')
1225
- console.log(
1226
- `You will be prompted for approval next time you start ${PRODUCT_NAME}.`,
1227
- )
1228
- process.exit(0)
1229
- }
1230
-
1231
- // New command name to match Kode
1232
- mcp
1233
- .command('reset-project-choices')
1234
- .description(
1235
- 'Reset all approved and rejected project-scoped (.mcp.json) servers within this project',
1236
- )
1237
- .action(() => {
1238
- logEvent('tengu_mcp_reset_project_choices', {})
1239
- resetMcpChoices()
1240
- })
1241
-
1242
- // Keep old command for backward compatibility (visible only to ants)
1243
- if (process.env.USER_TYPE === 'ant') {
1244
- mcp
1245
- .command('reset-mcprc-choices')
1246
- .description(
1247
- 'Reset all approved and rejected .mcprc servers for this project',
1248
- )
1249
- .action(() => {
1250
- logEvent('tengu_mcp_reset_mcprc_choices', {})
1251
- resetMcpChoices()
1252
- })
1253
- }
1254
-
1255
- // Doctor command - check installation health
1256
- program
1257
- .command('doctor')
1258
- .description(`Check the health of your ${PRODUCT_NAME} auto-updater`)
1259
- .action(async () => {
1260
- logEvent('tengu_doctor_command', {})
1261
-
1262
- await new Promise<void>(resolve => {
1263
- render(<Doctor onDone={() => resolve()} doctorMode={true} />)
1264
- })
1265
- process.exit(0)
1266
- })
1267
-
1268
- // ant-only commands
1269
-
1270
- // claude update
1271
- program
1272
- .command('update')
1273
- .description('Show manual upgrade commands (no auto-install)')
1274
- .action(async () => {
1275
- logEvent('tengu_update_check', {})
1276
- console.log(`Current version: ${MACRO.VERSION}`)
1277
- console.log('Checking for updates...')
1278
-
1279
- const latestVersion = await getLatestVersion()
1280
-
1281
- if (!latestVersion) {
1282
- console.error('Failed to check for updates')
1283
- process.exit(1)
1284
- }
1285
-
1286
- if (latestVersion === MACRO.VERSION) {
1287
- console.log(`${PRODUCT_NAME} is up to date`)
1288
- process.exit(0)
1289
- }
1290
-
1291
- console.log(`New version available: ${latestVersion}`)
1292
- const { getUpdateCommandSuggestions } = await import('../utils/autoUpdater')
1293
- const cmds = await getUpdateCommandSuggestions()
1294
- console.log('\nRun one of the following commands to update:')
1295
- for (const c of cmds) console.log(` ${c}`)
1296
- if (process.platform !== 'win32') {
1297
- console.log('\nNote: you may need to prefix with "sudo" on macOS/Linux.')
1298
- }
1299
- process.exit(0)
1300
- })
1301
-
1302
- // claude log
1303
- program
1304
- .command('log')
1305
- .description('Manage conversation logs.')
1306
- .argument(
1307
- '[number]',
1308
- 'A number (0, 1, 2, etc.) to display a specific log',
1309
- parseInt,
1310
- )
1311
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
1312
- .action(async (number, { cwd }) => {
1313
- await setup(cwd, false)
1314
- logEvent('tengu_view_logs', { number: number?.toString() ?? '' })
1315
- const context: { unmount?: () => void } = {}
1316
- const { unmount } = render(
1317
- <LogList context={context} type="messages" logNumber={number} />,
1318
- renderContextWithExitOnCtrlC,
1319
- )
1320
- context.unmount = unmount
1321
- })
1322
-
1323
- // claude resume
1324
- program
1325
- .command('resume')
1326
- .description(
1327
- 'Resume a previous conversation. Optionally provide a number (0, 1, 2, etc.) or file path to resume a specific conversation.',
1328
- )
1329
- .argument(
1330
- '[identifier]',
1331
- 'A number (0, 1, 2, etc.) or file path to resume a specific conversation',
1332
- )
1333
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
1334
- .option('-e, --enable-architect', 'Enable the Architect tool', () => true)
1335
- .option('-v, --verbose', 'Do not truncate message output', () => true)
1336
- .option(
1337
- '--safe',
1338
- 'Enable strict permission checking mode (default is permissive)',
1339
- () => true,
1340
- )
1341
- .action(async (identifier, { cwd, enableArchitect, safe, verbose }) => {
1342
- await setup(cwd, safe)
1343
- assertMinVersion()
1344
-
1345
- const [tools, commands, logs, mcpClients] = await Promise.all([
1346
- getTools(
1347
- enableArchitect ?? getCurrentProjectConfig().enableArchitectTool,
1348
- ),
1349
- getCommands(),
1350
- loadLogList(CACHE_PATHS.messages()),
1351
- getClients(),
1352
- ])
1353
- // logStartup()
1354
-
1355
- // If a specific conversation is requested, load and resume it directly
1356
- if (identifier !== undefined) {
1357
- // Check if identifier is a number or a file path
1358
- const number = Math.abs(parseInt(identifier))
1359
- const isNumber = !isNaN(number)
1360
- let messages, date, forkNumber
1361
- try {
1362
- if (isNumber) {
1363
- logEvent('tengu_resume', { number: number.toString() })
1364
- const log = logs[number]
1365
- if (!log) {
1366
- console.error('No conversation found at index', number)
1367
- process.exit(1)
1368
- }
1369
- messages = await loadMessagesFromLog(log.fullPath, tools)
1370
- ;({ date, forkNumber } = log)
1371
- } else {
1372
- // Handle file path case
1373
- logEvent('tengu_resume', { filePath: identifier })
1374
- if (!existsSync(identifier)) {
1375
- console.error('File does not exist:', identifier)
1376
- process.exit(1)
1377
- }
1378
- messages = await loadMessagesFromLog(identifier, tools)
1379
- const pathSegments = identifier.split('/')
1380
- const filename = pathSegments[pathSegments.length - 1] ?? 'unknown'
1381
- ;({ date, forkNumber } = parseLogFilename(filename))
1382
- }
1383
- const fork = getNextAvailableLogForkNumber(date, forkNumber ?? 1, 0)
1384
- const isDefaultModel = await isDefaultSlowAndCapableModel()
1385
- {
1386
- const { render } = await import('ink')
1387
- const { REPL } = await import('../screens/REPL')
1388
- render(
1389
- <REPL
1390
- initialPrompt=""
1391
- messageLogName={date}
1392
- initialForkNumber={fork}
1393
- shouldShowPromptInput={true}
1394
- verbose={verbose}
1395
- commands={commands}
1396
- tools={tools}
1397
- safeMode={safe}
1398
- initialMessages={messages}
1399
- mcpClients={mcpClients}
1400
- isDefaultModel={isDefaultModel}
1401
- />,
1402
- { exitOnCtrlC: false },
1403
- )
1404
- }
1405
- } catch (error) {
1406
- logError(`Failed to load conversation: ${error}`)
1407
- process.exit(1)
1408
- }
1409
- } else {
1410
- // Show the conversation selector UI
1411
- const context: { unmount?: () => void } = {}
1412
- const { unmount } = render(
1413
- <ResumeConversation
1414
- context={context}
1415
- commands={commands}
1416
- logs={logs}
1417
- tools={tools}
1418
- verbose={verbose}
1419
- />,
1420
- renderContextWithExitOnCtrlC,
1421
- )
1422
- context.unmount = unmount
1423
- }
1424
- })
1425
-
1426
- // claude error
1427
- program
1428
- .command('error')
1429
- .description(
1430
- 'View error logs. Optionally provide a number (0, -1, -2, etc.) to display a specific log.',
1431
- )
1432
- .argument(
1433
- '[number]',
1434
- 'A number (0, 1, 2, etc.) to display a specific log',
1435
- parseInt,
1436
- )
1437
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
1438
- .action(async (number, { cwd }) => {
1439
- await setup(cwd, false)
1440
- logEvent('tengu_view_errors', { number: number?.toString() ?? '' })
1441
- const context: { unmount?: () => void } = {}
1442
- const { unmount } = render(
1443
- <LogList context={context} type="errors" logNumber={number} />,
1444
- renderContextWithExitOnCtrlC,
1445
- )
1446
- context.unmount = unmount
1447
- })
1448
-
1449
- // claude context (TODO: deprecate)
1450
- const context = program
1451
- .command('context')
1452
- .description(
1453
- `Set static context (eg. ${PRODUCT_COMMAND} context add-file ./src/*.py)`,
1454
- )
1455
-
1456
- context
1457
- .command('get <key>')
1458
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
1459
- .description('Get a value from context')
1460
- .action(async (key, { cwd }) => {
1461
- await setup(cwd, false)
1462
- logEvent('tengu_context_get', { key })
1463
- const context = omit(
1464
- await getContext(),
1465
- 'codeStyle',
1466
- 'directoryStructure',
1467
- )
1468
- console.log(context[key])
1469
- process.exit(0)
1470
- })
1471
-
1472
- context
1473
- .command('set <key> <value>')
1474
- .description('Set a value in context')
1475
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
1476
- .action(async (key, value, { cwd }) => {
1477
- await setup(cwd, false)
1478
- logEvent('tengu_context_set', { key })
1479
- setContext(key, value)
1480
- console.log(`Set context.${key} to "${value}"`)
1481
- process.exit(0)
1482
- })
1483
-
1484
- context
1485
- .command('list')
1486
- .description('List all context values')
1487
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
1488
- .action(async ({ cwd }) => {
1489
- await setup(cwd, false)
1490
- logEvent('tengu_context_list', {})
1491
- const context = omit(
1492
- await getContext(),
1493
- 'codeStyle',
1494
- 'directoryStructure',
1495
- 'gitStatus',
1496
- )
1497
- console.log(JSON.stringify(context, null, 2))
1498
- process.exit(0)
1499
- })
1500
-
1501
- context
1502
- .command('remove <key>')
1503
- .description('Remove a value from context')
1504
- .option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
1505
- .action(async (key, { cwd }) => {
1506
- await setup(cwd, false)
1507
- logEvent('tengu_context_delete', { key })
1508
- removeContext(key)
1509
- console.log(`Removed context.${key}`)
1510
- process.exit(0)
1511
- })
1512
-
1513
- await program.parseAsync(process.argv)
1514
- return program
1515
- }
1516
-
1517
- // TODO: stream?
1518
- async function stdin() {
1519
- if (process.stdin.isTTY) {
1520
- return ''
1521
- }
1522
-
1523
- let data = ''
1524
- for await (const chunk of process.stdin) data += chunk
1525
- return data
1526
- }
1527
-
1528
- process.on('exit', () => {
1529
- resetCursor()
1530
- PersistentShell.getInstance().close()
1531
- })
1532
-
1533
- function gracefulExit(code = 0) {
1534
- try { resetCursor() } catch {}
1535
- try { PersistentShell.getInstance().close() } catch {}
1536
- process.exit(code)
1537
- }
1538
-
1539
- process.on('SIGINT', () => gracefulExit(0))
1540
- process.on('SIGTERM', () => gracefulExit(0))
1541
- // Windows CTRL+BREAK
1542
- process.on('SIGBREAK', () => gracefulExit(0))
1543
- process.on('unhandledRejection', err => {
1544
- console.error('Unhandled rejection:', err)
1545
- gracefulExit(1)
1546
- })
1547
- process.on('uncaughtException', err => {
1548
- console.error('Uncaught exception:', err)
1549
- gracefulExit(1)
1550
- })
1551
-
1552
- function resetCursor() {
1553
- const terminal = process.stderr.isTTY
1554
- ? process.stderr
1555
- : process.stdout.isTTY
1556
- ? process.stdout
1557
- : undefined
1558
- terminal?.write(`\u001B[?25h${cursorShow}`)
1559
- }
1560
-
1561
- main()