@shareai-lab/kode 1.1.13 → 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 (288) hide show
  1. package/dist/entrypoints/cli.js +59 -38
  2. package/dist/entrypoints/cli.js.map +3 -3
  3. package/dist/index.js +5 -26
  4. package/dist/package.json +4 -1
  5. package/package.json +11 -104
  6. package/dist/test/testAdapters.js +0 -88
  7. package/dist/test/testAdapters.js.map +0 -1
  8. package/src/ProjectOnboarding.tsx +0 -198
  9. package/src/Tool.ts +0 -83
  10. package/src/commands/agents.tsx +0 -3416
  11. package/src/commands/approvedTools.ts +0 -53
  12. package/src/commands/bug.tsx +0 -20
  13. package/src/commands/clear.ts +0 -43
  14. package/src/commands/compact.ts +0 -120
  15. package/src/commands/config.tsx +0 -19
  16. package/src/commands/cost.ts +0 -18
  17. package/src/commands/ctx_viz.ts +0 -209
  18. package/src/commands/doctor.ts +0 -24
  19. package/src/commands/help.tsx +0 -19
  20. package/src/commands/init.ts +0 -37
  21. package/src/commands/listen.ts +0 -42
  22. package/src/commands/login.tsx +0 -51
  23. package/src/commands/logout.tsx +0 -40
  24. package/src/commands/mcp.ts +0 -41
  25. package/src/commands/model.tsx +0 -40
  26. package/src/commands/modelstatus.tsx +0 -20
  27. package/src/commands/onboarding.tsx +0 -34
  28. package/src/commands/pr_comments.ts +0 -59
  29. package/src/commands/refreshCommands.ts +0 -54
  30. package/src/commands/release-notes.ts +0 -34
  31. package/src/commands/resume.tsx +0 -31
  32. package/src/commands/review.ts +0 -49
  33. package/src/commands/terminalSetup.ts +0 -221
  34. package/src/commands.ts +0 -139
  35. package/src/components/ApproveApiKey.tsx +0 -93
  36. package/src/components/AsciiLogo.tsx +0 -13
  37. package/src/components/AutoUpdater.tsx +0 -148
  38. package/src/components/Bug.tsx +0 -367
  39. package/src/components/Config.tsx +0 -293
  40. package/src/components/ConsoleOAuthFlow.tsx +0 -327
  41. package/src/components/Cost.tsx +0 -23
  42. package/src/components/CostThresholdDialog.tsx +0 -46
  43. package/src/components/CustomSelect/option-map.ts +0 -42
  44. package/src/components/CustomSelect/select-option.tsx +0 -78
  45. package/src/components/CustomSelect/select.tsx +0 -152
  46. package/src/components/CustomSelect/theme.ts +0 -45
  47. package/src/components/CustomSelect/use-select-state.ts +0 -414
  48. package/src/components/CustomSelect/use-select.ts +0 -35
  49. package/src/components/FallbackToolUseRejectedMessage.tsx +0 -15
  50. package/src/components/FileEditToolUpdatedMessage.tsx +0 -66
  51. package/src/components/Help.tsx +0 -215
  52. package/src/components/HighlightedCode.tsx +0 -33
  53. package/src/components/InvalidConfigDialog.tsx +0 -113
  54. package/src/components/Link.tsx +0 -32
  55. package/src/components/LogSelector.tsx +0 -86
  56. package/src/components/Logo.tsx +0 -170
  57. package/src/components/MCPServerApprovalDialog.tsx +0 -100
  58. package/src/components/MCPServerDialogCopy.tsx +0 -25
  59. package/src/components/MCPServerMultiselectDialog.tsx +0 -109
  60. package/src/components/Message.tsx +0 -221
  61. package/src/components/MessageResponse.tsx +0 -15
  62. package/src/components/MessageSelector.tsx +0 -211
  63. package/src/components/ModeIndicator.tsx +0 -88
  64. package/src/components/ModelConfig.tsx +0 -301
  65. package/src/components/ModelListManager.tsx +0 -227
  66. package/src/components/ModelSelector.tsx +0 -3387
  67. package/src/components/ModelStatusDisplay.tsx +0 -230
  68. package/src/components/Onboarding.tsx +0 -274
  69. package/src/components/PressEnterToContinue.tsx +0 -11
  70. package/src/components/PromptInput.tsx +0 -760
  71. package/src/components/SentryErrorBoundary.ts +0 -39
  72. package/src/components/Spinner.tsx +0 -129
  73. package/src/components/StickerRequestForm.tsx +0 -16
  74. package/src/components/StructuredDiff.tsx +0 -191
  75. package/src/components/TextInput.tsx +0 -259
  76. package/src/components/TodoItem.tsx +0 -47
  77. package/src/components/TokenWarning.tsx +0 -31
  78. package/src/components/ToolUseLoader.tsx +0 -40
  79. package/src/components/TrustDialog.tsx +0 -106
  80. package/src/components/binary-feedback/BinaryFeedback.tsx +0 -63
  81. package/src/components/binary-feedback/BinaryFeedbackOption.tsx +0 -111
  82. package/src/components/binary-feedback/BinaryFeedbackView.tsx +0 -172
  83. package/src/components/binary-feedback/utils.ts +0 -220
  84. package/src/components/messages/AssistantBashOutputMessage.tsx +0 -22
  85. package/src/components/messages/AssistantLocalCommandOutputMessage.tsx +0 -49
  86. package/src/components/messages/AssistantRedactedThinkingMessage.tsx +0 -19
  87. package/src/components/messages/AssistantTextMessage.tsx +0 -144
  88. package/src/components/messages/AssistantThinkingMessage.tsx +0 -40
  89. package/src/components/messages/AssistantToolUseMessage.tsx +0 -132
  90. package/src/components/messages/TaskProgressMessage.tsx +0 -32
  91. package/src/components/messages/TaskToolMessage.tsx +0 -58
  92. package/src/components/messages/UserBashInputMessage.tsx +0 -28
  93. package/src/components/messages/UserCommandMessage.tsx +0 -30
  94. package/src/components/messages/UserKodingInputMessage.tsx +0 -28
  95. package/src/components/messages/UserPromptMessage.tsx +0 -35
  96. package/src/components/messages/UserTextMessage.tsx +0 -39
  97. package/src/components/messages/UserToolResultMessage/UserToolCanceledMessage.tsx +0 -12
  98. package/src/components/messages/UserToolResultMessage/UserToolErrorMessage.tsx +0 -36
  99. package/src/components/messages/UserToolResultMessage/UserToolRejectMessage.tsx +0 -31
  100. package/src/components/messages/UserToolResultMessage/UserToolResultMessage.tsx +0 -57
  101. package/src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx +0 -35
  102. package/src/components/messages/UserToolResultMessage/utils.tsx +0 -56
  103. package/src/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx +0 -121
  104. package/src/components/permissions/FallbackPermissionRequest.tsx +0 -153
  105. package/src/components/permissions/FileEditPermissionRequest/FileEditPermissionRequest.tsx +0 -182
  106. package/src/components/permissions/FileEditPermissionRequest/FileEditToolDiff.tsx +0 -77
  107. package/src/components/permissions/FileWritePermissionRequest/FileWritePermissionRequest.tsx +0 -164
  108. package/src/components/permissions/FileWritePermissionRequest/FileWriteToolDiff.tsx +0 -83
  109. package/src/components/permissions/FilesystemPermissionRequest/FilesystemPermissionRequest.tsx +0 -240
  110. package/src/components/permissions/PermissionRequest.tsx +0 -101
  111. package/src/components/permissions/PermissionRequestTitle.tsx +0 -69
  112. package/src/components/permissions/hooks.ts +0 -44
  113. package/src/components/permissions/toolUseOptions.ts +0 -59
  114. package/src/components/permissions/utils.ts +0 -23
  115. package/src/constants/betas.ts +0 -5
  116. package/src/constants/claude-asterisk-ascii-art.tsx +0 -238
  117. package/src/constants/figures.ts +0 -4
  118. package/src/constants/keys.ts +0 -3
  119. package/src/constants/macros.ts +0 -11
  120. package/src/constants/modelCapabilities.ts +0 -179
  121. package/src/constants/models.ts +0 -1025
  122. package/src/constants/oauth.ts +0 -18
  123. package/src/constants/product.ts +0 -17
  124. package/src/constants/prompts.ts +0 -168
  125. package/src/constants/releaseNotes.ts +0 -7
  126. package/src/context/PermissionContext.tsx +0 -149
  127. package/src/context.ts +0 -278
  128. package/src/cost-tracker.ts +0 -84
  129. package/src/entrypoints/cli.tsx +0 -1561
  130. package/src/entrypoints/mcp.ts +0 -175
  131. package/src/history.ts +0 -25
  132. package/src/hooks/useApiKeyVerification.ts +0 -59
  133. package/src/hooks/useArrowKeyHistory.ts +0 -55
  134. package/src/hooks/useCanUseTool.ts +0 -138
  135. package/src/hooks/useCancelRequest.ts +0 -39
  136. package/src/hooks/useDoublePress.ts +0 -41
  137. package/src/hooks/useExitOnCtrlCD.ts +0 -31
  138. package/src/hooks/useInterval.ts +0 -25
  139. package/src/hooks/useLogMessages.ts +0 -16
  140. package/src/hooks/useLogStartupTime.ts +0 -12
  141. package/src/hooks/useNotifyAfterTimeout.ts +0 -65
  142. package/src/hooks/usePermissionRequestLogging.ts +0 -44
  143. package/src/hooks/useTerminalSize.ts +0 -49
  144. package/src/hooks/useTextInput.ts +0 -317
  145. package/src/hooks/useUnifiedCompletion.ts +0 -1405
  146. package/src/index.ts +0 -34
  147. package/src/messages.ts +0 -38
  148. package/src/permissions.ts +0 -268
  149. package/src/query.ts +0 -720
  150. package/src/screens/ConfigureNpmPrefix.tsx +0 -197
  151. package/src/screens/Doctor.tsx +0 -219
  152. package/src/screens/LogList.tsx +0 -68
  153. package/src/screens/REPL.tsx +0 -813
  154. package/src/screens/ResumeConversation.tsx +0 -68
  155. package/src/services/adapters/base.ts +0 -38
  156. package/src/services/adapters/chatCompletions.ts +0 -90
  157. package/src/services/adapters/responsesAPI.ts +0 -170
  158. package/src/services/browserMocks.ts +0 -66
  159. package/src/services/claude.ts +0 -2197
  160. package/src/services/customCommands.ts +0 -704
  161. package/src/services/fileFreshness.ts +0 -377
  162. package/src/services/gpt5ConnectionTest.ts +0 -340
  163. package/src/services/mcpClient.ts +0 -564
  164. package/src/services/mcpServerApproval.tsx +0 -50
  165. package/src/services/mentionProcessor.ts +0 -273
  166. package/src/services/modelAdapterFactory.ts +0 -69
  167. package/src/services/notifier.ts +0 -40
  168. package/src/services/oauth.ts +0 -357
  169. package/src/services/openai.ts +0 -1359
  170. package/src/services/responseStateManager.ts +0 -90
  171. package/src/services/sentry.ts +0 -3
  172. package/src/services/statsig.ts +0 -172
  173. package/src/services/statsigStorage.ts +0 -86
  174. package/src/services/systemReminder.ts +0 -507
  175. package/src/services/vcr.ts +0 -161
  176. package/src/test/testAdapters.ts +0 -96
  177. package/src/tools/ArchitectTool/ArchitectTool.tsx +0 -135
  178. package/src/tools/ArchitectTool/prompt.ts +0 -15
  179. package/src/tools/AskExpertModelTool/AskExpertModelTool.tsx +0 -576
  180. package/src/tools/BashTool/BashTool.tsx +0 -243
  181. package/src/tools/BashTool/BashToolResultMessage.tsx +0 -38
  182. package/src/tools/BashTool/OutputLine.tsx +0 -49
  183. package/src/tools/BashTool/prompt.ts +0 -174
  184. package/src/tools/BashTool/utils.ts +0 -56
  185. package/src/tools/FileEditTool/FileEditTool.tsx +0 -319
  186. package/src/tools/FileEditTool/prompt.ts +0 -51
  187. package/src/tools/FileEditTool/utils.ts +0 -58
  188. package/src/tools/FileReadTool/FileReadTool.tsx +0 -404
  189. package/src/tools/FileReadTool/prompt.ts +0 -7
  190. package/src/tools/FileWriteTool/FileWriteTool.tsx +0 -301
  191. package/src/tools/FileWriteTool/prompt.ts +0 -10
  192. package/src/tools/GlobTool/GlobTool.tsx +0 -119
  193. package/src/tools/GlobTool/prompt.ts +0 -8
  194. package/src/tools/GrepTool/GrepTool.tsx +0 -147
  195. package/src/tools/GrepTool/prompt.ts +0 -11
  196. package/src/tools/MCPTool/MCPTool.tsx +0 -107
  197. package/src/tools/MCPTool/prompt.ts +0 -3
  198. package/src/tools/MemoryReadTool/MemoryReadTool.tsx +0 -127
  199. package/src/tools/MemoryReadTool/prompt.ts +0 -3
  200. package/src/tools/MemoryWriteTool/MemoryWriteTool.tsx +0 -89
  201. package/src/tools/MemoryWriteTool/prompt.ts +0 -3
  202. package/src/tools/MultiEditTool/MultiEditTool.tsx +0 -388
  203. package/src/tools/MultiEditTool/prompt.ts +0 -45
  204. package/src/tools/NotebookEditTool/NotebookEditTool.tsx +0 -298
  205. package/src/tools/NotebookEditTool/prompt.ts +0 -3
  206. package/src/tools/NotebookReadTool/NotebookReadTool.tsx +0 -258
  207. package/src/tools/NotebookReadTool/prompt.ts +0 -3
  208. package/src/tools/StickerRequestTool/StickerRequestTool.tsx +0 -107
  209. package/src/tools/StickerRequestTool/prompt.ts +0 -19
  210. package/src/tools/TaskTool/TaskTool.tsx +0 -438
  211. package/src/tools/TaskTool/constants.ts +0 -1
  212. package/src/tools/TaskTool/prompt.ts +0 -92
  213. package/src/tools/ThinkTool/ThinkTool.tsx +0 -54
  214. package/src/tools/ThinkTool/prompt.ts +0 -12
  215. package/src/tools/TodoWriteTool/TodoWriteTool.tsx +0 -313
  216. package/src/tools/TodoWriteTool/prompt.ts +0 -63
  217. package/src/tools/URLFetcherTool/URLFetcherTool.tsx +0 -178
  218. package/src/tools/URLFetcherTool/cache.ts +0 -55
  219. package/src/tools/URLFetcherTool/htmlToMarkdown.ts +0 -55
  220. package/src/tools/URLFetcherTool/prompt.ts +0 -17
  221. package/src/tools/WebSearchTool/WebSearchTool.tsx +0 -103
  222. package/src/tools/WebSearchTool/prompt.ts +0 -13
  223. package/src/tools/WebSearchTool/searchProviders.ts +0 -66
  224. package/src/tools/lsTool/lsTool.tsx +0 -272
  225. package/src/tools/lsTool/prompt.ts +0 -2
  226. package/src/tools.ts +0 -67
  227. package/src/types/PermissionMode.ts +0 -120
  228. package/src/types/RequestContext.ts +0 -72
  229. package/src/types/common.d.ts +0 -2
  230. package/src/types/conversation.ts +0 -51
  231. package/src/types/logs.ts +0 -58
  232. package/src/types/modelCapabilities.ts +0 -64
  233. package/src/types/notebook.ts +0 -87
  234. package/src/utils/Cursor.ts +0 -436
  235. package/src/utils/PersistentShell.ts +0 -552
  236. package/src/utils/advancedFuzzyMatcher.ts +0 -290
  237. package/src/utils/agentLoader.ts +0 -278
  238. package/src/utils/agentStorage.ts +0 -97
  239. package/src/utils/array.ts +0 -3
  240. package/src/utils/ask.tsx +0 -99
  241. package/src/utils/auth.ts +0 -13
  242. package/src/utils/autoCompactCore.ts +0 -223
  243. package/src/utils/autoUpdater.ts +0 -458
  244. package/src/utils/betas.ts +0 -20
  245. package/src/utils/browser.ts +0 -14
  246. package/src/utils/cleanup.ts +0 -72
  247. package/src/utils/commands.ts +0 -261
  248. package/src/utils/commonUnixCommands.ts +0 -161
  249. package/src/utils/config.ts +0 -945
  250. package/src/utils/conversationRecovery.ts +0 -55
  251. package/src/utils/debugLogger.ts +0 -1235
  252. package/src/utils/diff.ts +0 -42
  253. package/src/utils/env.ts +0 -57
  254. package/src/utils/errors.ts +0 -21
  255. package/src/utils/exampleCommands.ts +0 -109
  256. package/src/utils/execFileNoThrow.ts +0 -51
  257. package/src/utils/expertChatStorage.ts +0 -136
  258. package/src/utils/file.ts +0 -405
  259. package/src/utils/fileRecoveryCore.ts +0 -71
  260. package/src/utils/format.tsx +0 -44
  261. package/src/utils/fuzzyMatcher.ts +0 -328
  262. package/src/utils/generators.ts +0 -62
  263. package/src/utils/git.ts +0 -92
  264. package/src/utils/globalLogger.ts +0 -77
  265. package/src/utils/http.ts +0 -10
  266. package/src/utils/imagePaste.ts +0 -38
  267. package/src/utils/json.ts +0 -13
  268. package/src/utils/log.ts +0 -382
  269. package/src/utils/markdown.ts +0 -213
  270. package/src/utils/messageContextManager.ts +0 -294
  271. package/src/utils/messages.tsx +0 -945
  272. package/src/utils/model.ts +0 -914
  273. package/src/utils/permissions/filesystem.ts +0 -127
  274. package/src/utils/responseState.ts +0 -23
  275. package/src/utils/ripgrep.ts +0 -167
  276. package/src/utils/secureFile.ts +0 -564
  277. package/src/utils/sessionState.ts +0 -49
  278. package/src/utils/state.ts +0 -25
  279. package/src/utils/style.ts +0 -29
  280. package/src/utils/terminal.ts +0 -50
  281. package/src/utils/theme.ts +0 -127
  282. package/src/utils/thinking.ts +0 -144
  283. package/src/utils/todoStorage.ts +0 -431
  284. package/src/utils/tokens.ts +0 -43
  285. package/src/utils/toolExecutionController.ts +0 -163
  286. package/src/utils/unaryLogging.ts +0 -26
  287. package/src/utils/user.ts +0 -37
  288. package/src/utils/validate.ts +0 -165
@@ -1,704 +0,0 @@
1
- import { existsSync, readFileSync } from 'fs'
2
- import { join } from 'path'
3
- import { homedir } from 'os'
4
- import { memoize } from 'lodash-es'
5
- import type { MessageParam } from '@anthropic-ai/sdk/resources/index.mjs'
6
- import type { Command } from '../commands'
7
- import { getCwd } from '../utils/state'
8
- import { logEvent } from './statsig'
9
- import { execFile } from 'child_process'
10
- import { promisify } from 'util'
11
-
12
- const execFileAsync = promisify(execFile)
13
-
14
- /**
15
- * Execute bash commands found in custom command content using !`command` syntax
16
- *
17
- * This function processes dynamic command execution within custom commands,
18
- * following the same security model as the main BashTool but with restricted scope.
19
- * Commands are executed in the current working directory with a timeout.
20
- *
21
- * @param content - The custom command content to process
22
- * @returns Promise<string> - Content with bash commands replaced by their output
23
- */
24
- export async function executeBashCommands(content: string): Promise<string> {
25
- // Match patterns like !`git status` or !`command here`
26
- const bashCommandRegex = /!\`([^`]+)\`/g
27
- const matches = [...content.matchAll(bashCommandRegex)]
28
-
29
- if (matches.length === 0) {
30
- return content
31
- }
32
-
33
- let result = content
34
-
35
- for (const match of matches) {
36
- const fullMatch = match[0]
37
- const command = match[1].trim()
38
-
39
- try {
40
- // Parse command and args using simple shell parsing
41
- // This mirrors the approach used in the main BashTool but with stricter limits
42
- const parts = command.split(/\s+/)
43
- const cmd = parts[0]
44
- const args = parts.slice(1)
45
-
46
- // Execute with conservative timeout (5s vs BashTool's 2min default)
47
- const { stdout, stderr } = await execFileAsync(cmd, args, {
48
- timeout: 5000,
49
- encoding: 'utf8',
50
- cwd: getCwd(), // Use current working directory for consistency
51
- })
52
-
53
- // Replace the bash command with its output, preferring stdout
54
- const output = stdout.trim() || stderr.trim() || '(no output)'
55
- result = result.replace(fullMatch, output)
56
- } catch (error) {
57
- console.warn(`Failed to execute bash command "${command}":`, error)
58
- result = result.replace(fullMatch, `(error executing: ${command})`)
59
- }
60
- }
61
-
62
- return result
63
- }
64
-
65
- /**
66
- * Resolve file references using @filepath syntax within custom commands
67
- *
68
- * This function implements file inclusion for custom commands, similar to how
69
- * the FileReadTool works but with inline processing. Files are read from the
70
- * current working directory and formatted as markdown code blocks.
71
- *
72
- * Security note: Files are read with the same permissions as the main process,
73
- * following the same security model as other file operations in the system.
74
- *
75
- * @param content - The custom command content to process
76
- * @returns Promise<string> - Content with file references replaced by file contents
77
- */
78
- export async function resolveFileReferences(content: string): Promise<string> {
79
- // Match patterns like @src/file.js or @path/to/file.txt
80
- // Use consistent file mention pattern from mentionProcessor
81
- // Exclude agent and ask-model patterns to avoid conflicts
82
- const fileRefRegex = /@([a-zA-Z0-9/._-]+(?:\.[a-zA-Z0-9]+)?)/g
83
- const matches = [...content.matchAll(fileRefRegex)]
84
-
85
- if (matches.length === 0) {
86
- return content
87
- }
88
-
89
- let result = content
90
-
91
- for (const match of matches) {
92
- const fullMatch = match[0]
93
- const filePath = match[1]
94
-
95
- // Skip agent mentions - these are handled by the mention processor
96
- if (filePath.startsWith('agent-')) {
97
- continue
98
- }
99
-
100
- try {
101
- // Resolve relative to current working directory
102
- // This maintains consistency with how other file operations work
103
- const fullPath = join(getCwd(), filePath)
104
-
105
- if (existsSync(fullPath)) {
106
- const fileContent = readFileSync(fullPath, { encoding: 'utf-8' })
107
-
108
- // Format file content with filename header for clarity
109
- // This matches the format used by FileReadTool for consistency
110
- const formattedContent = `\n\n## File: ${filePath}\n\`\`\`\n${fileContent}\n\`\`\`\n`
111
- result = result.replace(fullMatch, formattedContent)
112
- } else {
113
- result = result.replace(fullMatch, `(file not found: ${filePath})`)
114
- }
115
- } catch (error) {
116
- console.warn(`Failed to read file "${filePath}":`, error)
117
- result = result.replace(fullMatch, `(error reading: ${filePath})`)
118
- }
119
- }
120
-
121
- return result
122
- }
123
-
124
- /**
125
- * Validate and process allowed-tools specification from frontmatter
126
- *
127
- * This function handles tool restriction specifications in custom commands.
128
- * Currently it provides logging and validation structure - full enforcement
129
- * would require deep integration with the tool permission system.
130
- *
131
- * Future implementation should connect to src/permissions.ts and the
132
- * tool execution pipeline to enforce these restrictions.
133
- *
134
- * @param allowedTools - Array of tool names from frontmatter
135
- * @returns boolean - Currently always true, future will return actual validation result
136
- */
137
- function validateAllowedTools(allowedTools: string[] | undefined): boolean {
138
- // Log allowed tools for debugging and future integration
139
- if (allowedTools && allowedTools.length > 0) {
140
- console.log('Command allowed tools:', allowedTools)
141
- // TODO: Integrate with src/permissions.ts tool permission system
142
- // TODO: Connect to Tool.tsx needsPermissions() mechanism
143
- }
144
- return true // Allow execution for now - future versions will enforce restrictions
145
- }
146
-
147
- /**
148
- * Frontmatter configuration for custom commands
149
- *
150
- * This interface defines the YAML frontmatter structure that can be used
151
- * to configure custom commands. It follows the same pattern as Claude Desktop's
152
- * custom command system but with additional fields for enhanced functionality.
153
- */
154
- export interface CustomCommandFrontmatter {
155
- /** Display name for the command (overrides filename-based naming) */
156
- name?: string
157
- /** Brief description of what the command does */
158
- description?: string
159
- /** Alternative names that can be used to invoke this command */
160
- aliases?: string[]
161
- /** Whether this command is active and can be executed */
162
- enabled?: boolean
163
- /** Whether this command should be hidden from help output */
164
- hidden?: boolean
165
- /** Message to display while the command is running */
166
- progressMessage?: string
167
- /** Named arguments for legacy {arg} placeholder support */
168
- argNames?: string[]
169
- /** Tools that this command is restricted to use */
170
- 'allowed-tools'?: string[]
171
- }
172
-
173
- /**
174
- * Extended Command interface with scope information
175
- *
176
- * This extends the base Command interface to include scope metadata
177
- * for distinguishing between user-level and project-level commands.
178
- */
179
- export interface CustomCommandWithScope {
180
- /** Command type - matches PromptCommand */
181
- type: 'prompt'
182
- /** Command name */
183
- name: string
184
- /** Command description */
185
- description: string
186
- /** Whether command is enabled */
187
- isEnabled: boolean
188
- /** Whether command is hidden */
189
- isHidden: boolean
190
- /** Command aliases */
191
- aliases?: string[]
192
- /** Progress message */
193
- progressMessage: string
194
- /** Argument names for legacy support */
195
- argNames?: string[]
196
- /** User-facing name function */
197
- userFacingName(): string
198
- /** Prompt generation function */
199
- getPromptForCommand(args: string): Promise<MessageParam[]>
200
- /** Scope indicates whether this is a user or project command */
201
- scope?: 'user' | 'project'
202
- }
203
-
204
- /**
205
- * Parsed custom command file representation
206
- *
207
- * This interface represents a fully parsed custom command file with
208
- * separated frontmatter and content sections.
209
- */
210
- export interface CustomCommandFile {
211
- /** Parsed frontmatter configuration */
212
- frontmatter: CustomCommandFrontmatter
213
- /** Markdown content (without frontmatter) */
214
- content: string
215
- /** Absolute path to the source file */
216
- filePath: string
217
- }
218
-
219
- /**
220
- * Parse YAML frontmatter from markdown content
221
- *
222
- * This function extracts and parses YAML frontmatter from markdown files,
223
- * supporting the same syntax as Jekyll and other static site generators.
224
- * It handles basic YAML constructs including strings, booleans, and arrays.
225
- *
226
- * The parser is intentionally simple and focused on the specific needs of
227
- * custom commands rather than being a full YAML parser. Complex YAML features
228
- * like nested objects, multi-line strings, and advanced syntax are not supported.
229
- *
230
- * @param content - Raw markdown content with optional frontmatter
231
- * @returns Object containing parsed frontmatter and remaining content
232
- */
233
- export function parseFrontmatter(content: string): {
234
- frontmatter: CustomCommandFrontmatter
235
- content: string
236
- } {
237
- const frontmatterRegex = /^---\s*\n([\s\S]*?)---\s*\n?/
238
- const match = content.match(frontmatterRegex)
239
-
240
- if (!match) {
241
- return { frontmatter: {}, content }
242
- }
243
-
244
- const yamlContent = match[1] || ''
245
- const markdownContent = content.slice(match[0].length)
246
- const frontmatter: CustomCommandFrontmatter = {}
247
-
248
- // Simple YAML parser for basic key-value pairs and arrays
249
- // This handles the subset of YAML needed for custom command configuration
250
- const lines = yamlContent.split('\n')
251
- let currentKey: string | null = null
252
- let arrayItems: string[] = []
253
- let inArray = false
254
-
255
- for (const line of lines) {
256
- const trimmed = line.trim()
257
- if (!trimmed || trimmed.startsWith('#')) continue
258
-
259
- // Handle array item continuation (- item)
260
- if (inArray && trimmed.startsWith('-')) {
261
- const item = trimmed.slice(1).trim().replace(/['"]/g, '')
262
- arrayItems.push(item)
263
- continue
264
- }
265
-
266
- // End array processing when we hit a new key
267
- if (inArray && trimmed.includes(':')) {
268
- if (currentKey) {
269
- ;(frontmatter as any)[currentKey] = arrayItems
270
- }
271
- inArray = false
272
- arrayItems = []
273
- currentKey = null
274
- }
275
-
276
- const colonIndex = trimmed.indexOf(':')
277
- if (colonIndex === -1) continue
278
-
279
- const key = trimmed.slice(0, colonIndex).trim()
280
- const value = trimmed.slice(colonIndex + 1).trim()
281
-
282
- // Handle inline arrays [item1, item2]
283
- if (value.startsWith('[') && value.endsWith(']')) {
284
- const items = value
285
- .slice(1, -1)
286
- .split(',')
287
- .map(s => s.trim().replace(/['"]/g, ''))
288
- .filter(s => s.length > 0)
289
- ;(frontmatter as any)[key] = items
290
- }
291
- // Handle multi-line arrays (value is empty or [])
292
- else if (value === '' || value === '[]') {
293
- currentKey = key
294
- inArray = true
295
- arrayItems = []
296
- }
297
- // Handle boolean values
298
- else if (value === 'true' || value === 'false') {
299
- ;(frontmatter as any)[key] = value === 'true'
300
- }
301
- // Handle string values (remove quotes)
302
- else {
303
- ;(frontmatter as any)[key] = value.replace(/['"]/g, '')
304
- }
305
- }
306
-
307
- // Handle final array if we ended in array mode
308
- if (inArray && currentKey) {
309
- ;(frontmatter as any)[currentKey] = arrayItems
310
- }
311
-
312
- return { frontmatter, content: markdownContent }
313
- }
314
-
315
- /**
316
- * Scan directory for markdown files using find command
317
- *
318
- * This function discovers .md files in the specified directory using the
319
- * system's find command. It's designed as a fallback when ripgrep is not
320
- * available, providing the same functionality with broader compatibility.
321
- *
322
- * The function includes timeout and signal handling for robustness,
323
- * especially important when scanning large directory trees.
324
- *
325
- * @param args - Legacy parameter for ripgrep compatibility (ignored)
326
- * @param directory - Directory to scan for markdown files
327
- * @param signal - AbortSignal for cancellation support
328
- * @returns Promise<string[]> - Array of absolute paths to .md files
329
- */
330
- async function scanMarkdownFiles(
331
- args: string[], // Legacy parameter for ripgrep compatibility
332
- directory: string,
333
- signal: AbortSignal,
334
- ): Promise<string[]> {
335
- try {
336
- // Use find command as fallback since ripgrep may not be available
337
- // This provides broader compatibility across different systems
338
- const { stdout } = await execFileAsync(
339
- 'find',
340
- [directory, '-name', '*.md', '-type', 'f'],
341
- { signal, timeout: 3000 },
342
- )
343
- return stdout
344
- .trim()
345
- .split('\n')
346
- .filter(line => line.length > 0)
347
- } catch (error) {
348
- // If find fails or directory doesn't exist, return empty array
349
- // This ensures graceful degradation when directories are missing
350
- return []
351
- }
352
- }
353
-
354
- /**
355
- * Create a Command object from custom command file data
356
- *
357
- * This function transforms parsed custom command data into a Command object
358
- * that integrates with the main command system. It handles naming, scoping,
359
- * and prompt generation according to the project's command patterns.
360
- *
361
- * Command naming follows a hierarchical structure:
362
- * - Project commands: "project:namespace:command"
363
- * - User commands: "user:namespace:command"
364
- * - Namespace is derived from directory structure
365
- *
366
- * @param frontmatter - Parsed frontmatter configuration
367
- * @param content - Markdown content of the command
368
- * @param filePath - Absolute path to the command file
369
- * @param baseDir - Base directory for scope determination
370
- * @returns CustomCommandWithScope | null - Processed command or null if invalid
371
- */
372
- function createCustomCommand(
373
- frontmatter: CustomCommandFrontmatter,
374
- content: string,
375
- filePath: string,
376
- baseDir: string,
377
- ): CustomCommandWithScope | null {
378
- // Extract command name with namespace support
379
- const relativePath = filePath.replace(baseDir + '/', '')
380
- const pathParts = relativePath.split('/')
381
- const fileName = pathParts[pathParts.length - 1].replace('.md', '')
382
-
383
- // Determine scope based on directory location
384
- // This follows the same pattern as Claude Desktop's command system
385
- const userClaudeDir = join(homedir(), '.claude', 'commands')
386
- const userKodeDir = join(homedir(), '.kode', 'commands')
387
- const scope: 'user' | 'project' =
388
- (baseDir === userClaudeDir || baseDir === userKodeDir) ? 'user' : 'project'
389
- const prefix = scope === 'user' ? 'user' : 'project'
390
-
391
- // Create proper command name with prefix and namespace
392
- let finalName: string
393
- if (frontmatter.name) {
394
- // If frontmatter specifies name, use it but ensure proper prefix
395
- finalName = frontmatter.name.startsWith(`${prefix}:`)
396
- ? frontmatter.name
397
- : `${prefix}:${frontmatter.name}`
398
- } else {
399
- // Generate name from file path, supporting directory-based namespacing
400
- if (pathParts.length > 1) {
401
- const namespace = pathParts.slice(0, -1).join(':')
402
- finalName = `${prefix}:${namespace}:${fileName}`
403
- } else {
404
- finalName = `${prefix}:${fileName}`
405
- }
406
- }
407
-
408
- // Extract configuration with sensible defaults
409
- const description = frontmatter.description || `Custom command: ${finalName}`
410
- const enabled = frontmatter.enabled !== false // Default to true
411
- const hidden = frontmatter.hidden === true // Default to false
412
- const aliases = frontmatter.aliases || []
413
- const progressMessage =
414
- frontmatter.progressMessage || `Running ${finalName}...`
415
- const argNames = frontmatter.argNames
416
-
417
- // Validate required fields
418
- if (!finalName) {
419
- console.warn(`Custom command file ${filePath} has no name, skipping`)
420
- return null
421
- }
422
-
423
- // Create the command object following the project's Command interface
424
- const command: CustomCommandWithScope = {
425
- type: 'prompt',
426
- name: finalName,
427
- description,
428
- isEnabled: enabled,
429
- isHidden: hidden,
430
- aliases,
431
- progressMessage,
432
- argNames,
433
- scope,
434
- userFacingName(): string {
435
- return finalName
436
- },
437
- async getPromptForCommand(args: string): Promise<MessageParam[]> {
438
- let prompt = content.trim()
439
-
440
- // Process argument substitution following Claude Code conventions
441
- // This supports both the official $ARGUMENTS format and legacy {arg} format
442
-
443
- // Step 1: Handle $ARGUMENTS placeholder (official Claude Code format)
444
- if (prompt.includes('$ARGUMENTS')) {
445
- prompt = prompt.replace(/\$ARGUMENTS/g, args || '')
446
- }
447
-
448
- // Step 2: Legacy support for named argument placeholders
449
- if (argNames && argNames.length > 0) {
450
- const argValues = args.trim().split(/\s+/)
451
- argNames.forEach((argName, index) => {
452
- const value = argValues[index] || ''
453
- prompt = prompt.replace(new RegExp(`\\{${argName}\\}`, 'g'), value)
454
- })
455
- }
456
-
457
- // Step 3: If args are provided but no placeholders used, append to prompt
458
- if (
459
- args.trim() &&
460
- !prompt.includes('$ARGUMENTS') &&
461
- (!argNames || argNames.length === 0)
462
- ) {
463
- prompt += `\n\nAdditional context: ${args}`
464
- }
465
-
466
- // Step 4: Add tool restrictions if specified
467
- const allowedTools = frontmatter['allowed-tools']
468
- if (
469
- allowedTools &&
470
- Array.isArray(allowedTools) &&
471
- allowedTools.length > 0
472
- ) {
473
- const allowedToolsStr = allowedTools.join(', ')
474
- prompt += `\n\nIMPORTANT: You are restricted to using only these tools: ${allowedToolsStr}. Do not use any other tools even if they might be helpful for the task.`
475
- }
476
-
477
- return [
478
- {
479
- role: 'user',
480
- content: prompt,
481
- },
482
- ]
483
- },
484
- }
485
-
486
- return command
487
- }
488
-
489
- /**
490
- * Load custom commands from .claude/commands/ directories
491
- *
492
- * This function scans both user-level and project-level command directories
493
- * for markdown files and processes them into Command objects. It follows the
494
- * same discovery pattern as Claude Desktop but with additional performance
495
- * optimizations and error handling.
496
- *
497
- * Directory structure:
498
- * - User commands: ~/.claude/commands/
499
- * - Project commands: {project}/.claude/commands/
500
- *
501
- * The function is memoized for performance but includes cache invalidation
502
- * based on directory contents and timestamps.
503
- *
504
- * @returns Promise<CustomCommandWithScope[]> - Array of loaded and enabled commands
505
- */
506
- export const loadCustomCommands = memoize(
507
- async (): Promise<CustomCommandWithScope[]> => {
508
- // Support both .claude and .kode directories
509
- const userClaudeDir = join(homedir(), '.claude', 'commands')
510
- const projectClaudeDir = join(getCwd(), '.claude', 'commands')
511
- const userKodeDir = join(homedir(), '.kode', 'commands')
512
- const projectKodeDir = join(getCwd(), '.kode', 'commands')
513
-
514
- // Set up abort controller for timeout handling
515
- const abortController = new AbortController()
516
- const timeout = setTimeout(() => abortController.abort(), 3000)
517
-
518
- try {
519
- const startTime = Date.now()
520
-
521
- // Scan all four directories for .md files concurrently
522
- // This pattern matches the async loading used elsewhere in the project
523
- const [projectClaudeFiles, userClaudeFiles, projectKodeFiles, userKodeFiles] = await Promise.all([
524
- existsSync(projectClaudeDir)
525
- ? scanMarkdownFiles(
526
- ['--files', '--hidden', '--glob', '*.md'], // Legacy args for ripgrep compatibility
527
- projectClaudeDir,
528
- abortController.signal,
529
- )
530
- : Promise.resolve([]),
531
- existsSync(userClaudeDir)
532
- ? scanMarkdownFiles(
533
- ['--files', '--glob', '*.md'], // Legacy args for ripgrep compatibility
534
- userClaudeDir,
535
- abortController.signal,
536
- )
537
- : Promise.resolve([]),
538
- existsSync(projectKodeDir)
539
- ? scanMarkdownFiles(
540
- ['--files', '--hidden', '--glob', '*.md'], // Legacy args for ripgrep compatibility
541
- projectKodeDir,
542
- abortController.signal,
543
- )
544
- : Promise.resolve([]),
545
- existsSync(userKodeDir)
546
- ? scanMarkdownFiles(
547
- ['--files', '--glob', '*.md'], // Legacy args for ripgrep compatibility
548
- userKodeDir,
549
- abortController.signal,
550
- )
551
- : Promise.resolve([]),
552
- ])
553
-
554
- // Combine files with priority: project > user, kode > claude
555
- const projectFiles = [...projectKodeFiles, ...projectClaudeFiles]
556
- const userFiles = [...userKodeFiles, ...userClaudeFiles]
557
- const allFiles = [...projectFiles, ...userFiles]
558
- const duration = Date.now() - startTime
559
-
560
- // Log performance metrics for monitoring
561
- // This follows the same pattern as other performance-sensitive operations
562
- logEvent('tengu_custom_command_scan', {
563
- durationMs: duration.toString(),
564
- projectFilesFound: projectFiles.length.toString(),
565
- userFilesFound: userFiles.length.toString(),
566
- totalFiles: allFiles.length.toString(),
567
- })
568
-
569
- // Parse files and create command objects
570
- const commands: CustomCommandWithScope[] = []
571
-
572
- // Process project files first (higher priority)
573
- for (const filePath of projectFiles) {
574
- try {
575
- const content = readFileSync(filePath, { encoding: 'utf-8' })
576
- const { frontmatter, content: commandContent } =
577
- parseFrontmatter(content)
578
- // Determine which base directory this file is from
579
- const baseDir = filePath.includes('.kode/commands') ? projectKodeDir : projectClaudeDir
580
- const command = createCustomCommand(
581
- frontmatter,
582
- commandContent,
583
- filePath,
584
- baseDir,
585
- )
586
-
587
- if (command) {
588
- commands.push(command)
589
- }
590
- } catch (error) {
591
- console.warn(`Failed to load custom command from ${filePath}:`, error)
592
- }
593
- }
594
-
595
- // Process user files second (lower priority)
596
- for (const filePath of userFiles) {
597
- try {
598
- const content = readFileSync(filePath, { encoding: 'utf-8' })
599
- const { frontmatter, content: commandContent } =
600
- parseFrontmatter(content)
601
- // Determine which base directory this file is from
602
- const baseDir = filePath.includes('.kode/commands') ? userKodeDir : userClaudeDir
603
- const command = createCustomCommand(
604
- frontmatter,
605
- commandContent,
606
- filePath,
607
- baseDir,
608
- )
609
-
610
- if (command) {
611
- commands.push(command)
612
- }
613
- } catch (error) {
614
- console.warn(`Failed to load custom command from ${filePath}:`, error)
615
- }
616
- }
617
-
618
- // Filter enabled commands and log results
619
- const enabledCommands = commands.filter(cmd => cmd.isEnabled)
620
-
621
- // Log loading results for debugging and monitoring
622
- logEvent('tengu_custom_commands_loaded', {
623
- totalCommands: commands.length.toString(),
624
- enabledCommands: enabledCommands.length.toString(),
625
- userCommands: commands.filter(cmd => cmd.scope === 'user').length.toString(),
626
- projectCommands: commands.filter(cmd => cmd.scope === 'project').length.toString(),
627
- })
628
-
629
- return enabledCommands
630
- } catch (error) {
631
- console.warn('Failed to load custom commands:', error)
632
- return []
633
- } finally {
634
- clearTimeout(timeout)
635
- }
636
- },
637
- // Memoization resolver based on current working directory and directory state
638
- // This ensures cache invalidation when directories change
639
- () => {
640
- const cwd = getCwd()
641
- const userClaudeDir = join(homedir(), '.claude', 'commands')
642
- const projectClaudeDir = join(cwd, '.claude', 'commands')
643
- const userKodeDir = join(homedir(), '.kode', 'commands')
644
- const projectKodeDir = join(cwd, '.kode', 'commands')
645
-
646
- // Create cache key that includes directory existence and timestamp
647
- // This provides reasonable cache invalidation without excessive file system checks
648
- return `${cwd}:${existsSync(userClaudeDir)}:${existsSync(projectClaudeDir)}:${existsSync(userKodeDir)}:${existsSync(projectKodeDir)}:${Math.floor(Date.now() / 60000)}`
649
- },
650
- )
651
-
652
- /**
653
- * Clear the custom commands cache to force reload
654
- *
655
- * This function invalidates the memoized cache for custom commands,
656
- * forcing the next invocation to re-scan the filesystem. It's useful
657
- * when commands are added, removed, or modified during runtime.
658
- *
659
- * This follows the same pattern as other cache invalidation functions
660
- * in the project, such as getCommands.cache.clear().
661
- */
662
- export const reloadCustomCommands = (): void => {
663
- loadCustomCommands.cache.clear()
664
- console.log(
665
- 'Custom commands cache cleared. Commands will be reloaded on next use.',
666
- )
667
- }
668
-
669
- /**
670
- * Get custom command directories for help and diagnostic purposes
671
- *
672
- * This function returns the standard directory paths where custom commands
673
- * are expected to be found. It's used by help systems and diagnostic tools
674
- * to inform users about the proper directory structure.
675
- *
676
- * @returns Object containing user and project command directory paths
677
- */
678
- export function getCustomCommandDirectories(): {
679
- userClaude: string
680
- projectClaude: string
681
- userKode: string
682
- projectKode: string
683
- } {
684
- return {
685
- userClaude: join(homedir(), '.claude', 'commands'),
686
- projectClaude: join(getCwd(), '.claude', 'commands'),
687
- userKode: join(homedir(), '.kode', 'commands'),
688
- projectKode: join(getCwd(), '.kode', 'commands'),
689
- }
690
- }
691
-
692
- /**
693
- * Check if custom commands are available in either directory
694
- *
695
- * This function provides a quick way to determine if custom commands
696
- * are configured without actually loading them. It's useful for conditional
697
- * UI elements and feature detection.
698
- *
699
- * @returns boolean - True if at least one command directory exists
700
- */
701
- export function hasCustomCommands(): boolean {
702
- const { userClaude, projectClaude, userKode, projectKode } = getCustomCommandDirectories()
703
- return existsSync(userClaude) || existsSync(projectClaude) || existsSync(userKode) || existsSync(projectKode)
704
- }