@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,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
- }