@mseep/claudian 2.0.25

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 (687) hide show
  1. package/.env.local.example +2 -0
  2. package/.github/workflows/ci.yml +59 -0
  3. package/.github/workflows/claude-code-review.yml +57 -0
  4. package/.github/workflows/claude.yml +50 -0
  5. package/.github/workflows/duplicate-issues.yml +22 -0
  6. package/.github/workflows/release.yml +73 -0
  7. package/.github/workflows/stale.yml +21 -0
  8. package/.node-version +1 -0
  9. package/AGENTS.md +3 -0
  10. package/CLAUDE.md +80 -0
  11. package/LICENSE +21 -0
  12. package/README.md +190 -0
  13. package/assets/Preview.png +0 -0
  14. package/assets/sponsors/MOMA.png +0 -0
  15. package/bun.lock +1618 -0
  16. package/esbuild.config.mjs +195 -0
  17. package/eslint.config.mjs +143 -0
  18. package/jest.config.js +41 -0
  19. package/main.js +104915 -0
  20. package/manifest.json +10 -0
  21. package/package.json +65 -0
  22. package/scripts/build-css.mjs +119 -0
  23. package/scripts/build.mjs +19 -0
  24. package/scripts/postinstall.mjs +25 -0
  25. package/scripts/rendererSafeUnref.js +205 -0
  26. package/scripts/run-jest.js +19 -0
  27. package/scripts/sync-version.js +16 -0
  28. package/src/app/settings/ClaudianSettingsStorage.ts +435 -0
  29. package/src/app/settings/defaultSettings.ts +54 -0
  30. package/src/app/storage/SharedStorageService.ts +106 -0
  31. package/src/core/CLAUDE.md +84 -0
  32. package/src/core/auxiliary/AuxQueryRunner.ts +11 -0
  33. package/src/core/auxiliary/QueryBackedInlineEditService.ts +73 -0
  34. package/src/core/auxiliary/QueryBackedInstructionRefineService.ts +78 -0
  35. package/src/core/auxiliary/QueryBackedTitleGenerationService.ts +90 -0
  36. package/src/core/bootstrap/SessionStorage.ts +179 -0
  37. package/src/core/bootstrap/StoragePaths.ts +7 -0
  38. package/src/core/bootstrap/storage.ts +20 -0
  39. package/src/core/commands/builtInCommands.ts +141 -0
  40. package/src/core/mcp/McpConfigParser.ts +102 -0
  41. package/src/core/mcp/McpServerManager.ts +119 -0
  42. package/src/core/mcp/McpTester.ts +310 -0
  43. package/src/core/prompt/inlineEdit.ts +252 -0
  44. package/src/core/prompt/instructionRefine.ts +72 -0
  45. package/src/core/prompt/mainAgent.ts +213 -0
  46. package/src/core/prompt/titleGeneration.ts +44 -0
  47. package/src/core/providers/ProviderRegistry.ts +253 -0
  48. package/src/core/providers/ProviderSettingsCoordinator.ts +434 -0
  49. package/src/core/providers/ProviderWorkspaceRegistry.ts +119 -0
  50. package/src/core/providers/commands/ProviderCommandCatalog.ts +21 -0
  51. package/src/core/providers/commands/ProviderCommandEntry.ts +33 -0
  52. package/src/core/providers/commands/hiddenCommands.ts +74 -0
  53. package/src/core/providers/modelRouting.ts +17 -0
  54. package/src/core/providers/modelSelection.ts +72 -0
  55. package/src/core/providers/providerConfig.ts +34 -0
  56. package/src/core/providers/providerEnvironment.ts +364 -0
  57. package/src/core/providers/types.ts +544 -0
  58. package/src/core/runtime/ChatRuntime.ts +66 -0
  59. package/src/core/runtime/QueuedTurn.ts +97 -0
  60. package/src/core/runtime/types.ts +118 -0
  61. package/src/core/security/ApprovalManager.ts +142 -0
  62. package/src/core/storage/HomeFileAdapter.ts +75 -0
  63. package/src/core/storage/VaultFileAdapter.ts +132 -0
  64. package/src/core/tools/todo.ts +65 -0
  65. package/src/core/tools/toolIcons.ts +80 -0
  66. package/src/core/tools/toolInput.ts +119 -0
  67. package/src/core/tools/toolNames.ts +149 -0
  68. package/src/core/tools/toolResultContent.ts +26 -0
  69. package/src/core/types/agent.ts +28 -0
  70. package/src/core/types/chat.ts +177 -0
  71. package/src/core/types/diff.ts +31 -0
  72. package/src/core/types/index.ts +78 -0
  73. package/src/core/types/mcp.ts +97 -0
  74. package/src/core/types/plugins.ts +9 -0
  75. package/src/core/types/provider.ts +1 -0
  76. package/src/core/types/settings.ts +152 -0
  77. package/src/core/types/tools.ts +80 -0
  78. package/src/features/chat/CLAUDE.md +136 -0
  79. package/src/features/chat/ClaudianView.ts +762 -0
  80. package/src/features/chat/constants.ts +114 -0
  81. package/src/features/chat/controllers/BrowserSelectionController.ts +295 -0
  82. package/src/features/chat/controllers/CanvasSelectionController.ts +142 -0
  83. package/src/features/chat/controllers/ConversationController.ts +1103 -0
  84. package/src/features/chat/controllers/InputController.ts +1707 -0
  85. package/src/features/chat/controllers/NavigationController.ts +209 -0
  86. package/src/features/chat/controllers/SelectionController.ts +430 -0
  87. package/src/features/chat/controllers/StreamController.ts +1560 -0
  88. package/src/features/chat/controllers/contextRowVisibility.ts +18 -0
  89. package/src/features/chat/rendering/DiffRenderer.ts +134 -0
  90. package/src/features/chat/rendering/InlineAskUserQuestion.ts +702 -0
  91. package/src/features/chat/rendering/InlineExitPlanMode.ts +263 -0
  92. package/src/features/chat/rendering/InlinePlanApproval.ts +183 -0
  93. package/src/features/chat/rendering/MessageRenderer.ts +907 -0
  94. package/src/features/chat/rendering/SubagentRenderer.ts +678 -0
  95. package/src/features/chat/rendering/ThinkingBlockRenderer.ts +126 -0
  96. package/src/features/chat/rendering/TodoListRenderer.ts +5 -0
  97. package/src/features/chat/rendering/ToolCallRenderer.ts +1161 -0
  98. package/src/features/chat/rendering/WriteEditRenderer.ts +232 -0
  99. package/src/features/chat/rendering/collapsible.ts +101 -0
  100. package/src/features/chat/rendering/subagentLifecycleResolution.ts +25 -0
  101. package/src/features/chat/rendering/todoUtils.ts +29 -0
  102. package/src/features/chat/rewind.ts +31 -0
  103. package/src/features/chat/services/BangBashService.ts +56 -0
  104. package/src/features/chat/services/SubagentManager.ts +1107 -0
  105. package/src/features/chat/state/ChatState.ts +436 -0
  106. package/src/features/chat/state/types.ts +138 -0
  107. package/src/features/chat/tabs/Tab.ts +1886 -0
  108. package/src/features/chat/tabs/TabBar.ts +179 -0
  109. package/src/features/chat/tabs/TabManager.ts +1021 -0
  110. package/src/features/chat/tabs/providerResolution.ts +34 -0
  111. package/src/features/chat/tabs/types.ts +287 -0
  112. package/src/features/chat/ui/BangBashModeManager.ts +121 -0
  113. package/src/features/chat/ui/FileContext.ts +385 -0
  114. package/src/features/chat/ui/ImageContext.ts +366 -0
  115. package/src/features/chat/ui/InputToolbar.ts +1244 -0
  116. package/src/features/chat/ui/InstructionModeManager.ts +158 -0
  117. package/src/features/chat/ui/NavigationSidebar.ts +126 -0
  118. package/src/features/chat/ui/StatusPanel.ts +589 -0
  119. package/src/features/chat/ui/file-context/state/FileContextState.ts +83 -0
  120. package/src/features/chat/ui/file-context/view/FileChipsView.ts +70 -0
  121. package/src/features/chat/ui/textareaResize.ts +47 -0
  122. package/src/features/chat/utils/usageInfo.ts +26 -0
  123. package/src/features/inline-edit/ui/InlineEditModal.ts +895 -0
  124. package/src/features/inline-edit/ui/inlineEditMarkdownPreview.ts +55 -0
  125. package/src/features/settings/ClaudianSettings.ts +672 -0
  126. package/src/features/settings/keyboardNavigation.ts +60 -0
  127. package/src/features/settings/ui/EnvSnippetManager.ts +430 -0
  128. package/src/features/settings/ui/EnvironmentSettingsSection.ts +85 -0
  129. package/src/features/settings/ui/McpServerModal.ts +335 -0
  130. package/src/features/settings/ui/McpSettingsManager.ts +400 -0
  131. package/src/features/settings/ui/McpTestModal.ts +346 -0
  132. package/src/i18n/constants.ts +58 -0
  133. package/src/i18n/i18n.ts +140 -0
  134. package/src/i18n/locales/de.json +322 -0
  135. package/src/i18n/locales/en.json +322 -0
  136. package/src/i18n/locales/es.json +322 -0
  137. package/src/i18n/locales/fr.json +322 -0
  138. package/src/i18n/locales/ja.json +322 -0
  139. package/src/i18n/locales/ko.json +322 -0
  140. package/src/i18n/locales/pt.json +322 -0
  141. package/src/i18n/locales/ru.json +322 -0
  142. package/src/i18n/locales/zh-CN.json +322 -0
  143. package/src/i18n/locales/zh-TW.json +322 -0
  144. package/src/i18n/types.ts +248 -0
  145. package/src/main.ts +772 -0
  146. package/src/providers/acp/AcpClientConnection.ts +361 -0
  147. package/src/providers/acp/AcpJsonRpcTransport.ts +427 -0
  148. package/src/providers/acp/AcpSessionConfig.ts +139 -0
  149. package/src/providers/acp/AcpSessionUpdateNormalizer.ts +371 -0
  150. package/src/providers/acp/AcpSubprocess.ts +155 -0
  151. package/src/providers/acp/AcpToolStreamAdapter.ts +132 -0
  152. package/src/providers/acp/buildAcpUsageInfo.ts +41 -0
  153. package/src/providers/acp/index.ts +9 -0
  154. package/src/providers/acp/methodNames.ts +50 -0
  155. package/src/providers/acp/types.ts +566 -0
  156. package/src/providers/claude/CLAUDE.md +78 -0
  157. package/src/providers/claude/agents/AgentManager.ts +225 -0
  158. package/src/providers/claude/agents/AgentStorage.ts +101 -0
  159. package/src/providers/claude/app/ClaudeWorkspaceServices.ts +90 -0
  160. package/src/providers/claude/auxiliary/ClaudeInlineEditService.ts +122 -0
  161. package/src/providers/claude/auxiliary/ClaudeInstructionRefineService.ts +90 -0
  162. package/src/providers/claude/auxiliary/ClaudeTitleGenerationService.ts +129 -0
  163. package/src/providers/claude/auxiliary/extractAssistantText.ts +28 -0
  164. package/src/providers/claude/capabilities.ts +17 -0
  165. package/src/providers/claude/cli/findClaudeCLIPath.ts +261 -0
  166. package/src/providers/claude/commands/ClaudeCommandCatalog.ts +149 -0
  167. package/src/providers/claude/commands/probeRuntimeCommands.ts +86 -0
  168. package/src/providers/claude/env/ClaudeSettingsReconciler.ts +90 -0
  169. package/src/providers/claude/env/claudeModelEnv.ts +86 -0
  170. package/src/providers/claude/history/ClaudeConversationHistoryService.ts +446 -0
  171. package/src/providers/claude/history/ClaudeHistoryStore.ts +170 -0
  172. package/src/providers/claude/history/sdkAsyncSubagent.ts +92 -0
  173. package/src/providers/claude/history/sdkBranchFilter.ts +271 -0
  174. package/src/providers/claude/history/sdkHistoryTypes.ts +61 -0
  175. package/src/providers/claude/history/sdkMessageParsing.ts +413 -0
  176. package/src/providers/claude/history/sdkSessionPaths.ts +98 -0
  177. package/src/providers/claude/history/sdkSubagentSidecar.ts +261 -0
  178. package/src/providers/claude/hooks/SubagentHooks.ts +31 -0
  179. package/src/providers/claude/modelLabels.ts +77 -0
  180. package/src/providers/claude/modelOptions.ts +113 -0
  181. package/src/providers/claude/modelSelection.ts +17 -0
  182. package/src/providers/claude/plugins/PluginManager.ts +194 -0
  183. package/src/providers/claude/prompt/ClaudeTurnEncoder.ts +46 -0
  184. package/src/providers/claude/registration.ts +39 -0
  185. package/src/providers/claude/runtime/ClaudeApprovalHandler.ts +153 -0
  186. package/src/providers/claude/runtime/ClaudeChatRuntime.ts +1812 -0
  187. package/src/providers/claude/runtime/ClaudeCliResolver.ts +94 -0
  188. package/src/providers/claude/runtime/ClaudeDynamicUpdates.ts +164 -0
  189. package/src/providers/claude/runtime/ClaudeMessageChannel.ts +209 -0
  190. package/src/providers/claude/runtime/ClaudeQueryOptionsBuilder.ts +315 -0
  191. package/src/providers/claude/runtime/ClaudeRewindService.ts +220 -0
  192. package/src/providers/claude/runtime/ClaudeSessionManager.ts +92 -0
  193. package/src/providers/claude/runtime/ClaudeTaskResultInterpreter.ts +172 -0
  194. package/src/providers/claude/runtime/ClaudeUserMessageFactory.ts +83 -0
  195. package/src/providers/claude/runtime/claudeColdStartQuery.ts +152 -0
  196. package/src/providers/claude/runtime/customSpawn.ts +87 -0
  197. package/src/providers/claude/runtime/types.ts +134 -0
  198. package/src/providers/claude/sdk/messages.ts +17 -0
  199. package/src/providers/claude/sdk/toolResultContent.ts +4 -0
  200. package/src/providers/claude/sdk/typeGuards.ts +14 -0
  201. package/src/providers/claude/sdk/types.ts +15 -0
  202. package/src/providers/claude/security/ClaudePermissionUpdates.ts +44 -0
  203. package/src/providers/claude/settings.ts +138 -0
  204. package/src/providers/claude/storage/AgentVaultStorage.ts +101 -0
  205. package/src/providers/claude/storage/CCSettingsStorage.ts +153 -0
  206. package/src/providers/claude/storage/ClaudianSettingsStorage.ts +6 -0
  207. package/src/providers/claude/storage/McpStorage.ts +139 -0
  208. package/src/providers/claude/storage/SessionStorage.ts +5 -0
  209. package/src/providers/claude/storage/SkillStorage.ts +61 -0
  210. package/src/providers/claude/storage/SlashCommandStorage.ts +96 -0
  211. package/src/providers/claude/storage/StorageService.ts +185 -0
  212. package/src/providers/claude/stream/toolInputStreamState.ts +318 -0
  213. package/src/providers/claude/stream/transformClaudeMessage.ts +586 -0
  214. package/src/providers/claude/types/agent.ts +2 -0
  215. package/src/providers/claude/types/models.ts +168 -0
  216. package/src/providers/claude/types/plugins.ts +14 -0
  217. package/src/providers/claude/types/providerState.ts +16 -0
  218. package/src/providers/claude/types/settings.ts +98 -0
  219. package/src/providers/claude/ui/AgentSettings.ts +389 -0
  220. package/src/providers/claude/ui/ClaudeChatUIConfig.ts +113 -0
  221. package/src/providers/claude/ui/ClaudeSettingsTab.ts +407 -0
  222. package/src/providers/claude/ui/PluginSettingsManager.ts +149 -0
  223. package/src/providers/claude/ui/SlashCommandSettings.ts +527 -0
  224. package/src/providers/codex/CLAUDE.md +64 -0
  225. package/src/providers/codex/agents/CodexAgentMentionProvider.ts +33 -0
  226. package/src/providers/codex/app/CodexWorkspaceServices.ts +76 -0
  227. package/src/providers/codex/auxiliary/CodexInlineEditService.ts +9 -0
  228. package/src/providers/codex/auxiliary/CodexInstructionRefineService.ts +9 -0
  229. package/src/providers/codex/auxiliary/CodexTaskResultInterpreter.ts +29 -0
  230. package/src/providers/codex/auxiliary/CodexTitleGenerationService.ts +22 -0
  231. package/src/providers/codex/capabilities.ts +16 -0
  232. package/src/providers/codex/commands/CodexSkillCatalog.ts +180 -0
  233. package/src/providers/codex/env/CodexSettingsReconciler.ts +68 -0
  234. package/src/providers/codex/history/CodexConversationHistoryService.ts +212 -0
  235. package/src/providers/codex/history/CodexHistoryStore.ts +1672 -0
  236. package/src/providers/codex/modelOptions.ts +99 -0
  237. package/src/providers/codex/modelSelection.ts +17 -0
  238. package/src/providers/codex/normalization/codexSubagentNormalization.ts +227 -0
  239. package/src/providers/codex/normalization/codexToolNormalization.ts +390 -0
  240. package/src/providers/codex/prompt/encodeCodexTurn.ts +57 -0
  241. package/src/providers/codex/registration.ts +29 -0
  242. package/src/providers/codex/runtime/CodexAppServerProcess.ts +105 -0
  243. package/src/providers/codex/runtime/CodexAuxQueryRunner.ts +180 -0
  244. package/src/providers/codex/runtime/CodexBinaryLocator.ts +49 -0
  245. package/src/providers/codex/runtime/CodexChatRuntime.ts +1296 -0
  246. package/src/providers/codex/runtime/CodexCliResolver.ts +65 -0
  247. package/src/providers/codex/runtime/CodexExecutionTargetResolver.ts +104 -0
  248. package/src/providers/codex/runtime/CodexLaunchSpecBuilder.ts +85 -0
  249. package/src/providers/codex/runtime/CodexNotificationRouter.ts +1033 -0
  250. package/src/providers/codex/runtime/CodexPathMapper.ts +155 -0
  251. package/src/providers/codex/runtime/CodexRpcTransport.ts +171 -0
  252. package/src/providers/codex/runtime/CodexRuntimeContext.ts +109 -0
  253. package/src/providers/codex/runtime/CodexServerRequestRouter.ts +331 -0
  254. package/src/providers/codex/runtime/CodexSessionFileTail.ts +792 -0
  255. package/src/providers/codex/runtime/CodexSessionManager.ts +39 -0
  256. package/src/providers/codex/runtime/codexAppServerSupport.ts +58 -0
  257. package/src/providers/codex/runtime/codexAppServerTypes.ts +705 -0
  258. package/src/providers/codex/runtime/codexLaunchTypes.ts +30 -0
  259. package/src/providers/codex/settings.ts +236 -0
  260. package/src/providers/codex/skills/CodexSkillListingService.ts +173 -0
  261. package/src/providers/codex/storage/CodexSkillStorage.ts +250 -0
  262. package/src/providers/codex/storage/CodexSubagentStorage.ts +212 -0
  263. package/src/providers/codex/types/index.ts +16 -0
  264. package/src/providers/codex/types/models.ts +46 -0
  265. package/src/providers/codex/types/subagent.ts +23 -0
  266. package/src/providers/codex/ui/CodexChatUIConfig.ts +128 -0
  267. package/src/providers/codex/ui/CodexSettingsTab.ts +432 -0
  268. package/src/providers/codex/ui/CodexSkillSettings.ts +275 -0
  269. package/src/providers/codex/ui/CodexSubagentSettings.ts +400 -0
  270. package/src/providers/defaultProviderConfigs.ts +14 -0
  271. package/src/providers/index.ts +30 -0
  272. package/src/providers/opencode/agents/OpencodeAgentMentionProvider.ts +42 -0
  273. package/src/providers/opencode/app/OpencodeRuntimeCommandLoader.ts +67 -0
  274. package/src/providers/opencode/app/OpencodeWorkspaceServices.ts +55 -0
  275. package/src/providers/opencode/auxiliary/OpencodeInlineEditService.ts +13 -0
  276. package/src/providers/opencode/auxiliary/OpencodeInstructionRefineService.ts +12 -0
  277. package/src/providers/opencode/auxiliary/OpencodeTaskResultInterpreter.ts +29 -0
  278. package/src/providers/opencode/auxiliary/OpencodeTitleGenerationService.ts +27 -0
  279. package/src/providers/opencode/capabilities.ts +16 -0
  280. package/src/providers/opencode/commands/OpencodeCommandCatalog.ts +92 -0
  281. package/src/providers/opencode/discoveryState.ts +135 -0
  282. package/src/providers/opencode/env/OpencodeSettingsReconciler.ts +178 -0
  283. package/src/providers/opencode/history/OpencodeConversationHistoryService.ts +84 -0
  284. package/src/providers/opencode/history/OpencodeHistoryStore.ts +472 -0
  285. package/src/providers/opencode/history/OpencodeSqliteReader.ts +284 -0
  286. package/src/providers/opencode/internal/compareCollections.ts +72 -0
  287. package/src/providers/opencode/internal/providerProjection.ts +15 -0
  288. package/src/providers/opencode/models.ts +378 -0
  289. package/src/providers/opencode/modes.ts +150 -0
  290. package/src/providers/opencode/normalization/opencodeToolNormalization.ts +406 -0
  291. package/src/providers/opencode/registration.ts +27 -0
  292. package/src/providers/opencode/runtime/OpencodeAuxQueryRunner.ts +436 -0
  293. package/src/providers/opencode/runtime/OpencodeChatRuntime.ts +1603 -0
  294. package/src/providers/opencode/runtime/OpencodeCliResolver.ts +57 -0
  295. package/src/providers/opencode/runtime/OpencodeLaunchArtifacts.ts +231 -0
  296. package/src/providers/opencode/runtime/OpencodePaths.ts +113 -0
  297. package/src/providers/opencode/runtime/OpencodeRuntimeEnvironment.ts +18 -0
  298. package/src/providers/opencode/runtime/buildOpencodePrompt.ts +66 -0
  299. package/src/providers/opencode/settings.ts +427 -0
  300. package/src/providers/opencode/storage/OpencodeAgentStorage.ts +346 -0
  301. package/src/providers/opencode/types/agent.ts +37 -0
  302. package/src/providers/opencode/types/index.ts +9 -0
  303. package/src/providers/opencode/ui/OpencodeAgentSettings.ts +579 -0
  304. package/src/providers/opencode/ui/OpencodeChatUIConfig.ts +316 -0
  305. package/src/providers/opencode/ui/OpencodeSettingsTab.ts +674 -0
  306. package/src/providers/pi/app/PiRuntimeCommandLoader.ts +57 -0
  307. package/src/providers/pi/app/PiWorkspaceServices.ts +39 -0
  308. package/src/providers/pi/auxiliary/PiInlineEditService.ts +9 -0
  309. package/src/providers/pi/auxiliary/PiInstructionRefineService.ts +9 -0
  310. package/src/providers/pi/auxiliary/PiTaskResultInterpreter.ts +29 -0
  311. package/src/providers/pi/auxiliary/PiTitleGenerationService.ts +19 -0
  312. package/src/providers/pi/capabilities.ts +16 -0
  313. package/src/providers/pi/commands/PiCommandCatalog.ts +92 -0
  314. package/src/providers/pi/env/PiSettingsReconciler.ts +180 -0
  315. package/src/providers/pi/history/PiConversationHistoryService.ts +123 -0
  316. package/src/providers/pi/history/PiHistoryStore.ts +664 -0
  317. package/src/providers/pi/internal/compareCollections.ts +4 -0
  318. package/src/providers/pi/internal/providerProjection.ts +18 -0
  319. package/src/providers/pi/models.ts +302 -0
  320. package/src/providers/pi/normalizations/piEventNormalization.ts +211 -0
  321. package/src/providers/pi/normalizations/piToolNormalization.ts +97 -0
  322. package/src/providers/pi/registration.ts +30 -0
  323. package/src/providers/pi/runtime/PiAuxQueryRunner.ts +216 -0
  324. package/src/providers/pi/runtime/PiChatRuntime.ts +1064 -0
  325. package/src/providers/pi/runtime/PiCliResolver.ts +53 -0
  326. package/src/providers/pi/runtime/PiExtensionUiBridge.ts +161 -0
  327. package/src/providers/pi/runtime/PiJsonl.ts +71 -0
  328. package/src/providers/pi/runtime/PiLaunchSpec.ts +70 -0
  329. package/src/providers/pi/runtime/PiModelDiscoveryService.ts +92 -0
  330. package/src/providers/pi/runtime/PiRpcPayloads.ts +18 -0
  331. package/src/providers/pi/runtime/PiRpcTransport.ts +243 -0
  332. package/src/providers/pi/runtime/PiSubprocess.ts +159 -0
  333. package/src/providers/pi/runtime/buildPiPrompt.ts +62 -0
  334. package/src/providers/pi/runtime/buildPiUsageInfo.ts +69 -0
  335. package/src/providers/pi/settings.ts +468 -0
  336. package/src/providers/pi/types.ts +64 -0
  337. package/src/providers/pi/ui/ObsidianPiExtensionUiRenderer.ts +251 -0
  338. package/src/providers/pi/ui/PiChatUIConfig.ts +265 -0
  339. package/src/providers/pi/ui/PiExtensionUiRenderer.ts +12 -0
  340. package/src/providers/pi/ui/PiSettingsTab.ts +642 -0
  341. package/src/shared/components/ResumeSessionDropdown.ts +185 -0
  342. package/src/shared/components/SelectableDropdown.ts +140 -0
  343. package/src/shared/components/SelectionHighlight.ts +77 -0
  344. package/src/shared/components/SlashCommandDropdown.ts +421 -0
  345. package/src/shared/icons.ts +180 -0
  346. package/src/shared/mention/MentionDropdownController.ts +627 -0
  347. package/src/shared/mention/VaultMentionCache.ts +106 -0
  348. package/src/shared/mention/VaultMentionDataProvider.ts +51 -0
  349. package/src/shared/mention/types.ts +67 -0
  350. package/src/shared/modals/ConfirmModal.ts +60 -0
  351. package/src/shared/modals/ForkTargetModal.ts +47 -0
  352. package/src/shared/modals/InstructionConfirmModal.ts +281 -0
  353. package/src/style/CLAUDE.md +49 -0
  354. package/src/style/accessibility.css +40 -0
  355. package/src/style/base/animations.css +44 -0
  356. package/src/style/base/container.css +20 -0
  357. package/src/style/base/variables.css +46 -0
  358. package/src/style/base/visibility.css +15 -0
  359. package/src/style/components/code.css +97 -0
  360. package/src/style/components/context-footer.css +76 -0
  361. package/src/style/components/header.css +27 -0
  362. package/src/style/components/history.css +221 -0
  363. package/src/style/components/input.css +312 -0
  364. package/src/style/components/messages.css +262 -0
  365. package/src/style/components/nav-sidebar.css +58 -0
  366. package/src/style/components/status-panel.css +202 -0
  367. package/src/style/components/subagent.css +248 -0
  368. package/src/style/components/tabs.css +112 -0
  369. package/src/style/components/thinking.css +88 -0
  370. package/src/style/components/toolcalls.css +278 -0
  371. package/src/style/features/ask-user-question.css +315 -0
  372. package/src/style/features/diff.css +197 -0
  373. package/src/style/features/file-context.css +188 -0
  374. package/src/style/features/file-link.css +22 -0
  375. package/src/style/features/image-context.css +179 -0
  376. package/src/style/features/image-embed.css +40 -0
  377. package/src/style/features/image-modal.css +52 -0
  378. package/src/style/features/inline-edit.css +278 -0
  379. package/src/style/features/plan-mode.css +103 -0
  380. package/src/style/features/resume-session.css +119 -0
  381. package/src/style/features/slash-commands.css +91 -0
  382. package/src/style/index.css +63 -0
  383. package/src/style/modals/fork-target.css +21 -0
  384. package/src/style/modals/instruction.css +161 -0
  385. package/src/style/modals/mcp-modal.css +241 -0
  386. package/src/style/settings/agent-settings.css +2 -0
  387. package/src/style/settings/base.css +300 -0
  388. package/src/style/settings/env-snippets.css +366 -0
  389. package/src/style/settings/mcp-settings.css +211 -0
  390. package/src/style/settings/plugin-settings.css +164 -0
  391. package/src/style/settings/provider-model-picker.css +367 -0
  392. package/src/style/settings/slash-settings.css +16 -0
  393. package/src/style/toolbar/external-context.css +177 -0
  394. package/src/style/toolbar/mcp-selector.css +176 -0
  395. package/src/style/toolbar/mode-selector.css +19 -0
  396. package/src/style/toolbar/model-selector.css +99 -0
  397. package/src/style/toolbar/permission-toggle.css +56 -0
  398. package/src/style/toolbar/service-tier-toggle.css +39 -0
  399. package/src/style/toolbar/thinking-selector.css +83 -0
  400. package/src/types/smol-toml.d.ts +4 -0
  401. package/src/utils/agent.ts +50 -0
  402. package/src/utils/animationFrame.ts +46 -0
  403. package/src/utils/browser.ts +46 -0
  404. package/src/utils/canvas.ts +14 -0
  405. package/src/utils/cliBinaryLocator.ts +97 -0
  406. package/src/utils/context.ts +117 -0
  407. package/src/utils/contextMentionResolver.ts +154 -0
  408. package/src/utils/date.ts +31 -0
  409. package/src/utils/diff.ts +384 -0
  410. package/src/utils/editor.ts +104 -0
  411. package/src/utils/electronCompat.ts +53 -0
  412. package/src/utils/env.ts +465 -0
  413. package/src/utils/externalContext.ts +143 -0
  414. package/src/utils/externalContextScanner.ts +135 -0
  415. package/src/utils/fileLink.ts +263 -0
  416. package/src/utils/frontmatter.ts +194 -0
  417. package/src/utils/imageEmbed.ts +139 -0
  418. package/src/utils/inlineEdit.ts +22 -0
  419. package/src/utils/interrupt.ts +23 -0
  420. package/src/utils/markdown.ts +25 -0
  421. package/src/utils/markdownMath.ts +130 -0
  422. package/src/utils/mcp.ts +96 -0
  423. package/src/utils/obsidianCompat.ts +23 -0
  424. package/src/utils/path.ts +342 -0
  425. package/src/utils/session.ts +240 -0
  426. package/src/utils/slashCommand.ts +152 -0
  427. package/src/utils/subagentJsonl.ts +52 -0
  428. package/src/utils/windowsCmdShim.ts +98 -0
  429. package/tests/__mocks__/claude-agent-sdk.ts +317 -0
  430. package/tests/__mocks__/codex-sdk.ts +88 -0
  431. package/tests/__mocks__/obsidian.ts +434 -0
  432. package/tests/helpers/mockElement.ts +403 -0
  433. package/tests/helpers/sdkMessages.ts +291 -0
  434. package/tests/integration/core/agent/ClaudianService.test.ts +1845 -0
  435. package/tests/integration/core/mcp/mcp.test.ts +905 -0
  436. package/tests/integration/features/chat/imagePersistence.test.ts +38 -0
  437. package/tests/integration/main.test.ts +1701 -0
  438. package/tests/setupWindow.ts +26 -0
  439. package/tests/tsconfig.json +7 -0
  440. package/tests/unit/core/commands/builtInCommands.test.ts +239 -0
  441. package/tests/unit/core/mcp/McpServerManager.test.ts +405 -0
  442. package/tests/unit/core/mcp/McpTester.test.ts +282 -0
  443. package/tests/unit/core/mcp/createNodeFetch.test.ts +188 -0
  444. package/tests/unit/core/providers/ProviderRegistry.test.ts +275 -0
  445. package/tests/unit/core/providers/ProviderSettingsCoordinator.test.ts +490 -0
  446. package/tests/unit/core/providers/ProviderWorkspaceRegistry.test.ts +84 -0
  447. package/tests/unit/core/providers/modelRouting.test.ts +91 -0
  448. package/tests/unit/core/providers/modelSelection.test.ts +155 -0
  449. package/tests/unit/core/providers/providerEnvironment.test.ts +162 -0
  450. package/tests/unit/core/providers/tabLifecycle.test.ts +217 -0
  451. package/tests/unit/core/security/ApprovalManager.test.ts +152 -0
  452. package/tests/unit/core/storage/VaultFileAdapter.test.ts +535 -0
  453. package/tests/unit/core/tools/todo.test.ts +227 -0
  454. package/tests/unit/core/tools/toolIcons.test.ts +75 -0
  455. package/tests/unit/core/tools/toolInput.test.ts +350 -0
  456. package/tests/unit/core/tools/toolNames.test.ts +464 -0
  457. package/tests/unit/core/types/mcp.test.ts +115 -0
  458. package/tests/unit/features/chat/ClaudianView.test.ts +404 -0
  459. package/tests/unit/features/chat/controllers/BrowserSelectionController.test.ts +179 -0
  460. package/tests/unit/features/chat/controllers/CanvasSelectionController.test.ts +216 -0
  461. package/tests/unit/features/chat/controllers/ConversationController.test.ts +2764 -0
  462. package/tests/unit/features/chat/controllers/InputController.test.ts +3188 -0
  463. package/tests/unit/features/chat/controllers/NavigationController.test.ts +640 -0
  464. package/tests/unit/features/chat/controllers/SelectionController.test.ts +695 -0
  465. package/tests/unit/features/chat/controllers/StreamController.test.ts +2534 -0
  466. package/tests/unit/features/chat/controllers/contextRowVisibility.test.ts +46 -0
  467. package/tests/unit/features/chat/controllers/index.test.ts +16 -0
  468. package/tests/unit/features/chat/rendering/DiffRenderer.test.ts +355 -0
  469. package/tests/unit/features/chat/rendering/InlineAskUserQuestion.test.ts +1035 -0
  470. package/tests/unit/features/chat/rendering/InlineExitPlanMode.test.ts +191 -0
  471. package/tests/unit/features/chat/rendering/InlinePlanApproval.test.ts +126 -0
  472. package/tests/unit/features/chat/rendering/MessageRenderer.test.ts +2004 -0
  473. package/tests/unit/features/chat/rendering/SubagentRenderer.test.ts +917 -0
  474. package/tests/unit/features/chat/rendering/ThinkingBlockRenderer.test.ts +124 -0
  475. package/tests/unit/features/chat/rendering/TodoListRenderer.test.ts +173 -0
  476. package/tests/unit/features/chat/rendering/ToolCallRenderer.test.ts +909 -0
  477. package/tests/unit/features/chat/rendering/WriteEditRenderer.test.ts +474 -0
  478. package/tests/unit/features/chat/rendering/collapsible.test.ts +158 -0
  479. package/tests/unit/features/chat/rendering/todoUtils.test.ts +105 -0
  480. package/tests/unit/features/chat/rewind.test.ts +56 -0
  481. package/tests/unit/features/chat/services/BangBashService.test.ts +142 -0
  482. package/tests/unit/features/chat/services/InstructionRefineService.test.ts +371 -0
  483. package/tests/unit/features/chat/services/SubagentManager.test.ts +1759 -0
  484. package/tests/unit/features/chat/services/TitleGenerationService.test.ts +480 -0
  485. package/tests/unit/features/chat/state/ChatState.test.ts +581 -0
  486. package/tests/unit/features/chat/tabs/Tab.test.ts +4287 -0
  487. package/tests/unit/features/chat/tabs/TabBar.test.ts +357 -0
  488. package/tests/unit/features/chat/tabs/TabManager.test.ts +2962 -0
  489. package/tests/unit/features/chat/tabs/index.test.ts +11 -0
  490. package/tests/unit/features/chat/ui/BangBashModeManager.test.ts +321 -0
  491. package/tests/unit/features/chat/ui/ExternalContextSelector.test.ts +555 -0
  492. package/tests/unit/features/chat/ui/FileContextManager.test.ts +876 -0
  493. package/tests/unit/features/chat/ui/ImageContext.test.ts +777 -0
  494. package/tests/unit/features/chat/ui/InputToolbar.test.ts +1139 -0
  495. package/tests/unit/features/chat/ui/InstructionModeManager.test.ts +243 -0
  496. package/tests/unit/features/chat/ui/NavigationSidebar.test.ts +570 -0
  497. package/tests/unit/features/chat/ui/StatusPanel.test.ts +953 -0
  498. package/tests/unit/features/chat/ui/file-context/state/FileContextState.test.ts +155 -0
  499. package/tests/unit/features/chat/ui/textareaResize.test.ts +102 -0
  500. package/tests/unit/features/chat/utils/usageInfo.test.ts +56 -0
  501. package/tests/unit/features/inline-edit/InlineEditService.test.ts +1199 -0
  502. package/tests/unit/features/inline-edit/ui/InlineEditModal.openAndWait.test.ts +1482 -0
  503. package/tests/unit/features/inline-edit/ui/InlineEditModal.test.ts +495 -0
  504. package/tests/unit/features/inline-edit/ui/inlineEditMarkdownPreview.test.ts +92 -0
  505. package/tests/unit/features/settings/AgentSettings.test.ts +82 -0
  506. package/tests/unit/features/settings/keyboardNavigation.test.ts +73 -0
  507. package/tests/unit/features/settings/ui/CodexSkillSettings.test.ts +294 -0
  508. package/tests/unit/features/settings/ui/CodexSubagentSettings.test.ts +207 -0
  509. package/tests/unit/i18n/constants.test.ts +43 -0
  510. package/tests/unit/i18n/i18n.test.ts +244 -0
  511. package/tests/unit/i18n/locales.test.ts +134 -0
  512. package/tests/unit/providers/acp/AcpClientConnection.test.ts +248 -0
  513. package/tests/unit/providers/acp/AcpJsonRpcTransport.test.ts +186 -0
  514. package/tests/unit/providers/acp/AcpSessionConfig.test.ts +247 -0
  515. package/tests/unit/providers/acp/AcpSessionUpdateNormalizer.test.ts +145 -0
  516. package/tests/unit/providers/acp/AcpSubprocess.test.ts +105 -0
  517. package/tests/unit/providers/acp/buildAcpUsageInfo.test.ts +51 -0
  518. package/tests/unit/providers/claude/agents/AgentManager.test.ts +590 -0
  519. package/tests/unit/providers/claude/agents/AgentStorage.test.ts +434 -0
  520. package/tests/unit/providers/claude/agents/index.test.ts +10 -0
  521. package/tests/unit/providers/claude/commands/ClaudeCommandCatalog.test.ts +396 -0
  522. package/tests/unit/providers/claude/commands/probeRuntimeCommands.test.ts +92 -0
  523. package/tests/unit/providers/claude/env/ClaudeSettingsReconciler.test.ts +57 -0
  524. package/tests/unit/providers/claude/env/claudeModelEnv.test.ts +228 -0
  525. package/tests/unit/providers/claude/hooks/SubagentHooks.test.ts +83 -0
  526. package/tests/unit/providers/claude/plugins/PluginManager.test.ts +832 -0
  527. package/tests/unit/providers/claude/plugins/index.test.ts +7 -0
  528. package/tests/unit/providers/claude/prompt/ClaudeTurnEncoder.test.ts +145 -0
  529. package/tests/unit/providers/claude/prompt/instructionRefine.test.ts +185 -0
  530. package/tests/unit/providers/claude/prompt/systemPrompt.test.ts +163 -0
  531. package/tests/unit/providers/claude/prompt/titleGeneration.test.ts +20 -0
  532. package/tests/unit/providers/claude/runtime/ClaudeTaskResultInterpreter.test.ts +28 -0
  533. package/tests/unit/providers/claude/runtime/ClaudianService.test.ts +3796 -0
  534. package/tests/unit/providers/claude/runtime/MessageChannel.test.ts +421 -0
  535. package/tests/unit/providers/claude/runtime/QueryOptionsBuilder.test.ts +775 -0
  536. package/tests/unit/providers/claude/runtime/SessionManager.test.ts +182 -0
  537. package/tests/unit/providers/claude/runtime/claudeColdStartQuery.test.ts +331 -0
  538. package/tests/unit/providers/claude/runtime/customSpawn.test.ts +374 -0
  539. package/tests/unit/providers/claude/runtime/index.test.ts +13 -0
  540. package/tests/unit/providers/claude/runtime/types.test.ts +190 -0
  541. package/tests/unit/providers/claude/sdk/typeGuards.test.ts +50 -0
  542. package/tests/unit/providers/claude/security/ClaudePermissionUpdates.test.ts +198 -0
  543. package/tests/unit/providers/claude/storage/AgentVaultStorage.test.ts +413 -0
  544. package/tests/unit/providers/claude/storage/CCSettingsStorage.test.ts +408 -0
  545. package/tests/unit/providers/claude/storage/ClaudianSettingsStorage.test.ts +653 -0
  546. package/tests/unit/providers/claude/storage/McpStorage.test.ts +619 -0
  547. package/tests/unit/providers/claude/storage/SessionStorage.test.ts +680 -0
  548. package/tests/unit/providers/claude/storage/SkillStorage.test.ts +275 -0
  549. package/tests/unit/providers/claude/storage/SlashCommandStorage.test.ts +612 -0
  550. package/tests/unit/providers/claude/storage/storage.test.ts +360 -0
  551. package/tests/unit/providers/claude/storage/storageService.convenience.test.ts +447 -0
  552. package/tests/unit/providers/claude/stream/transformSDKMessage.test.ts +1729 -0
  553. package/tests/unit/providers/claude/types/types.test.ts +726 -0
  554. package/tests/unit/providers/claude/ui/ClaudeChatUIConfig.test.ts +173 -0
  555. package/tests/unit/providers/claude/ui/ClaudeSettingsTab.test.ts +466 -0
  556. package/tests/unit/providers/codex/agents/CodexAgentMentionProvider.test.ts +89 -0
  557. package/tests/unit/providers/codex/auxiliary/CodexInstructionRefineService.test.ts +81 -0
  558. package/tests/unit/providers/codex/capabilities.test.ts +39 -0
  559. package/tests/unit/providers/codex/commands/CodexSkillCatalog.test.ts +413 -0
  560. package/tests/unit/providers/codex/env/CodexSettingsReconciler.test.ts +106 -0
  561. package/tests/unit/providers/codex/fixtures/codex-session-abort.jsonl +9 -0
  562. package/tests/unit/providers/codex/fixtures/codex-session-agent-lifecycle.jsonl +12 -0
  563. package/tests/unit/providers/codex/fixtures/codex-session-persisted-tools.jsonl +15 -0
  564. package/tests/unit/providers/codex/fixtures/codex-session-simple.jsonl +10 -0
  565. package/tests/unit/providers/codex/fixtures/codex-session-tools.jsonl +12 -0
  566. package/tests/unit/providers/codex/fixtures/codex-session-websearch-persisted.jsonl +3 -0
  567. package/tests/unit/providers/codex/fixtures/codex-session-websearch.jsonl +7 -0
  568. package/tests/unit/providers/codex/history/CodexConversationHistoryService.test.ts +690 -0
  569. package/tests/unit/providers/codex/history/CodexHistoryStore.test.ts +2202 -0
  570. package/tests/unit/providers/codex/normalization/codexSubagentNormalization.test.ts +81 -0
  571. package/tests/unit/providers/codex/normalization/codexToolNormalization.test.ts +322 -0
  572. package/tests/unit/providers/codex/prompt/encodeCodexTurn.test.ts +168 -0
  573. package/tests/unit/providers/codex/runtime/CodexAppServerProcess.test.ts +255 -0
  574. package/tests/unit/providers/codex/runtime/CodexAuxQueryRunner.test.ts +130 -0
  575. package/tests/unit/providers/codex/runtime/CodexBinaryLocator.test.ts +90 -0
  576. package/tests/unit/providers/codex/runtime/CodexChatRuntime.test.ts +2445 -0
  577. package/tests/unit/providers/codex/runtime/CodexCliResolver.test.ts +103 -0
  578. package/tests/unit/providers/codex/runtime/CodexExecutionTargetResolver.test.ts +105 -0
  579. package/tests/unit/providers/codex/runtime/CodexLaunchSpecBuilder.test.ts +150 -0
  580. package/tests/unit/providers/codex/runtime/CodexNotificationRouter.test.ts +1248 -0
  581. package/tests/unit/providers/codex/runtime/CodexPathMapper.test.ts +51 -0
  582. package/tests/unit/providers/codex/runtime/CodexRpcTransport.test.ts +220 -0
  583. package/tests/unit/providers/codex/runtime/CodexRuntimeContext.test.ts +107 -0
  584. package/tests/unit/providers/codex/runtime/CodexServerRequestRouter.test.ts +537 -0
  585. package/tests/unit/providers/codex/runtime/CodexSessionFileTail.test.ts +1305 -0
  586. package/tests/unit/providers/codex/runtime/CodexSessionManager.test.ts +82 -0
  587. package/tests/unit/providers/codex/runtime/codexAppServerTypes.test.ts +336 -0
  588. package/tests/unit/providers/codex/settings.test.ts +189 -0
  589. package/tests/unit/providers/codex/skills/CodexSkillListingService.test.ts +178 -0
  590. package/tests/unit/providers/codex/storage/CodexSkillStorage.test.ts +342 -0
  591. package/tests/unit/providers/codex/storage/CodexSubagentStorage.test.ts +376 -0
  592. package/tests/unit/providers/codex/ui/CodexChatUIConfig.test.ts +211 -0
  593. package/tests/unit/providers/codex/ui/CodexSettingsTab.test.ts +578 -0
  594. package/tests/unit/providers/defaultProviderConfigs.test.ts +18 -0
  595. package/tests/unit/providers/opencode/OpencodeAuxQueryRunner.test.ts +355 -0
  596. package/tests/unit/providers/opencode/OpencodeChatRuntime.test.ts +808 -0
  597. package/tests/unit/providers/opencode/OpencodeCliResolver.test.ts +93 -0
  598. package/tests/unit/providers/opencode/OpencodeCommandCatalog.test.ts +75 -0
  599. package/tests/unit/providers/opencode/OpencodeConversationHistoryService.test.ts +103 -0
  600. package/tests/unit/providers/opencode/OpencodeHistoryStore.test.ts +418 -0
  601. package/tests/unit/providers/opencode/OpencodeLaunchArtifacts.test.ts +268 -0
  602. package/tests/unit/providers/opencode/OpencodePaths.test.ts +35 -0
  603. package/tests/unit/providers/opencode/OpencodeRuntimeCommandLoader.test.ts +129 -0
  604. package/tests/unit/providers/opencode/OpencodeSettingsReconciler.test.ts +96 -0
  605. package/tests/unit/providers/opencode/OpencodeSettingsTab.test.ts +649 -0
  606. package/tests/unit/providers/opencode/OpencodeSqliteReader.test.ts +185 -0
  607. package/tests/unit/providers/opencode/agents/OpencodeAgentMentionProvider.test.ts +56 -0
  608. package/tests/unit/providers/opencode/buildOpencodePrompt.test.ts +87 -0
  609. package/tests/unit/providers/opencode/capabilities.test.ts +39 -0
  610. package/tests/unit/providers/opencode/models.test.ts +273 -0
  611. package/tests/unit/providers/opencode/modes.test.ts +151 -0
  612. package/tests/unit/providers/opencode/opencodeToolNormalization.test.ts +197 -0
  613. package/tests/unit/providers/opencode/settings.test.ts +469 -0
  614. package/tests/unit/providers/opencode/storage/OpencodeAgentStorage.test.ts +377 -0
  615. package/tests/unit/providers/opencode/ui/OpencodeAgentSettings.test.ts +91 -0
  616. package/tests/unit/providers/pi/PiRuntimeCommandLoader.test.ts +149 -0
  617. package/tests/unit/providers/pi/capabilities.test.ts +20 -0
  618. package/tests/unit/providers/pi/commands/PiCommandCatalog.test.ts +69 -0
  619. package/tests/unit/providers/pi/env/PiSettingsReconciler.test.ts +61 -0
  620. package/tests/unit/providers/pi/history/PiConversationHistoryService.test.ts +147 -0
  621. package/tests/unit/providers/pi/history/PiHistoryStore.test.ts +523 -0
  622. package/tests/unit/providers/pi/models.test.ts +96 -0
  623. package/tests/unit/providers/pi/registration.test.ts +54 -0
  624. package/tests/unit/providers/pi/runtime/PiAuxQueryRunner.test.ts +172 -0
  625. package/tests/unit/providers/pi/runtime/PiChatRuntime.test.ts +830 -0
  626. package/tests/unit/providers/pi/runtime/PiCliResolver.test.ts +105 -0
  627. package/tests/unit/providers/pi/runtime/PiEventNormalization.test.ts +147 -0
  628. package/tests/unit/providers/pi/runtime/PiExtensionUiBridge.test.ts +98 -0
  629. package/tests/unit/providers/pi/runtime/PiJsonl.test.ts +42 -0
  630. package/tests/unit/providers/pi/runtime/PiLaunchSpec.test.ts +92 -0
  631. package/tests/unit/providers/pi/runtime/PiModelDiscoveryService.test.ts +135 -0
  632. package/tests/unit/providers/pi/runtime/PiRpcPayloads.test.ts +15 -0
  633. package/tests/unit/providers/pi/runtime/PiRpcTransport.test.ts +116 -0
  634. package/tests/unit/providers/pi/runtime/PiSubprocess.test.ts +151 -0
  635. package/tests/unit/providers/pi/runtime/buildPiPrompt.test.ts +84 -0
  636. package/tests/unit/providers/pi/runtime/buildPiUsageInfo.test.ts +69 -0
  637. package/tests/unit/providers/pi/settings.test.ts +253 -0
  638. package/tests/unit/providers/pi/ui/PiChatUIConfig.test.ts +161 -0
  639. package/tests/unit/providers/pi/ui/PiSettingsTab.test.ts +492 -0
  640. package/tests/unit/scripts/rendererSafeUnref.test.ts +101 -0
  641. package/tests/unit/shared/components/ResumeSessionDropdown.test.ts +356 -0
  642. package/tests/unit/shared/components/SelectableDropdown.test.ts +406 -0
  643. package/tests/unit/shared/components/SlashCommandDropdown.provider.test.ts +354 -0
  644. package/tests/unit/shared/components/SlashCommandDropdown.test.ts +508 -0
  645. package/tests/unit/shared/icons.test.ts +56 -0
  646. package/tests/unit/shared/index.test.ts +46 -0
  647. package/tests/unit/shared/mention/MentionDropdownController.test.ts +823 -0
  648. package/tests/unit/shared/mention/VaultFileCache.test.ts +195 -0
  649. package/tests/unit/shared/mention/VaultFolderCache.test.ts +143 -0
  650. package/tests/unit/shared/mention/VaultMentionDataProvider.test.ts +87 -0
  651. package/tests/unit/shared/modals/ConfirmModal.test.ts +111 -0
  652. package/tests/unit/shared/modals/ForkTargetModal.test.ts +101 -0
  653. package/tests/unit/shared/modals/InstructionConfirmModal.test.ts +305 -0
  654. package/tests/unit/utils/agent.test.ts +395 -0
  655. package/tests/unit/utils/animationFrame.test.ts +59 -0
  656. package/tests/unit/utils/browser.test.ts +73 -0
  657. package/tests/unit/utils/canvas.test.ts +54 -0
  658. package/tests/unit/utils/claudeCli.test.ts +294 -0
  659. package/tests/unit/utils/cliBinaryLocator.test.ts +33 -0
  660. package/tests/unit/utils/context.test.ts +288 -0
  661. package/tests/unit/utils/contextMentionResolver.test.ts +270 -0
  662. package/tests/unit/utils/date.test.ts +80 -0
  663. package/tests/unit/utils/diff.test.ts +291 -0
  664. package/tests/unit/utils/editor.test.ts +249 -0
  665. package/tests/unit/utils/electronCompat.test.ts +87 -0
  666. package/tests/unit/utils/env.test.ts +1240 -0
  667. package/tests/unit/utils/externalContext.test.ts +336 -0
  668. package/tests/unit/utils/externalContextScanner.test.ts +186 -0
  669. package/tests/unit/utils/fileLink.dom.test.ts +273 -0
  670. package/tests/unit/utils/fileLink.handler.test.ts +64 -0
  671. package/tests/unit/utils/fileLink.test.ts +233 -0
  672. package/tests/unit/utils/frontmatter.test.ts +434 -0
  673. package/tests/unit/utils/imageEmbed.test.ts +407 -0
  674. package/tests/unit/utils/inlineEdit.test.ts +63 -0
  675. package/tests/unit/utils/interrupt.test.ts +73 -0
  676. package/tests/unit/utils/markdown.test.ts +35 -0
  677. package/tests/unit/utils/markdownMath.test.ts +54 -0
  678. package/tests/unit/utils/mcp.test.ts +256 -0
  679. package/tests/unit/utils/obsidianCompat.test.ts +18 -0
  680. package/tests/unit/utils/path.test.ts +677 -0
  681. package/tests/unit/utils/sdkSession.test.ts +2359 -0
  682. package/tests/unit/utils/session.test.ts +971 -0
  683. package/tests/unit/utils/slashCommand.test.ts +778 -0
  684. package/tests/unit/utils/utils.test.ts +809 -0
  685. package/tsconfig.jest.json +8 -0
  686. package/tsconfig.json +26 -0
  687. package/versions.json +4 -0
@@ -0,0 +1,778 @@
1
+ import { extractFirstParagraph, parseSlashCommandContent, serializeCommand, serializeSlashCommandMarkdown, validateCommandName, yamlString } from '@/utils/slashCommand';
2
+
3
+ describe('parseSlashCommandContent', () => {
4
+ describe('basic parsing', () => {
5
+ it('should parse command with full frontmatter', () => {
6
+ const content = `---
7
+ description: Review code for issues
8
+ argument-hint: "[file] [focus]"
9
+ allowed-tools:
10
+ - Read
11
+ - Grep
12
+ model: claude-sonnet-4-5
13
+ ---
14
+ Review this code: $ARGUMENTS`;
15
+
16
+ const parsed = parseSlashCommandContent(content);
17
+
18
+ expect(parsed.description).toBe('Review code for issues');
19
+ expect(parsed.argumentHint).toBe('[file] [focus]');
20
+ expect(parsed.allowedTools).toEqual(['Read', 'Grep']);
21
+ expect(parsed.model).toBe('claude-sonnet-4-5');
22
+ expect(parsed.promptContent).toBe('Review this code: $ARGUMENTS');
23
+ });
24
+
25
+ it('should parse command with minimal frontmatter', () => {
26
+ const content = `---
27
+ description: Simple command
28
+ ---
29
+ Do something`;
30
+
31
+ const parsed = parseSlashCommandContent(content);
32
+
33
+ expect(parsed.description).toBe('Simple command');
34
+ expect(parsed.argumentHint).toBeUndefined();
35
+ expect(parsed.allowedTools).toBeUndefined();
36
+ expect(parsed.model).toBeUndefined();
37
+ expect(parsed.promptContent).toBe('Do something');
38
+ });
39
+
40
+ it('should handle content without frontmatter', () => {
41
+ const content = 'Just a prompt without frontmatter';
42
+
43
+ const parsed = parseSlashCommandContent(content);
44
+
45
+ expect(parsed.description).toBeUndefined();
46
+ expect(parsed.promptContent).toBe('Just a prompt without frontmatter');
47
+ });
48
+
49
+ it('should handle inline array syntax for allowed-tools', () => {
50
+ const content = `---
51
+ allowed-tools: [Read, Write, Bash]
52
+ ---
53
+ Prompt`;
54
+
55
+ const parsed = parseSlashCommandContent(content);
56
+ expect(parsed.allowedTools).toEqual(['Read', 'Write', 'Bash']);
57
+ });
58
+
59
+ it('should handle quoted values', () => {
60
+ const content = `---
61
+ description: "Value with: colon"
62
+ argument-hint: 'Single quoted'
63
+ ---
64
+ Prompt`;
65
+
66
+ const parsed = parseSlashCommandContent(content);
67
+
68
+ expect(parsed.description).toBe('Value with: colon');
69
+ expect(parsed.argumentHint).toBe('Single quoted');
70
+ });
71
+ });
72
+
73
+ describe('block scalar support', () => {
74
+ it('should parse literal block scalar (|) for description', () => {
75
+ const content = `---
76
+ description: |
77
+ Records a checkpoint of progress in the daily note.
78
+ Includes timestamp and current task status.
79
+ ---
80
+ Prompt`;
81
+
82
+ const parsed = parseSlashCommandContent(content);
83
+
84
+ expect(parsed.description).toBe('Records a checkpoint of progress in the daily note.\nIncludes timestamp and current task status.');
85
+ expect(parsed.promptContent).toBe('Prompt');
86
+ });
87
+
88
+ it('should parse folded block scalar (>) for description', () => {
89
+ const content = `---
90
+ description: >
91
+ Records a checkpoint of progress
92
+ in the daily note.
93
+ ---
94
+ Prompt`;
95
+
96
+ const parsed = parseSlashCommandContent(content);
97
+
98
+ expect(parsed.description).toBe('Records a checkpoint of progress in the daily note.');
99
+ expect(parsed.promptContent).toBe('Prompt');
100
+ });
101
+
102
+ it('should produce different output for | vs > block scalars', () => {
103
+ const literalContent = `---
104
+ description: |
105
+ Line one
106
+ Line two
107
+ ---
108
+ Prompt`;
109
+ const foldedContent = `---
110
+ description: >
111
+ Line one
112
+ Line two
113
+ ---
114
+ Prompt`;
115
+
116
+ const literal = parseSlashCommandContent(literalContent);
117
+ const folded = parseSlashCommandContent(foldedContent);
118
+
119
+ expect(literal.description).toBe('Line one\nLine two');
120
+ expect(folded.description).toBe('Line one Line two');
121
+ expect(literal.description).not.toBe(folded.description);
122
+ });
123
+
124
+ it('should preserve paragraph breaks in folded block scalar (>)', () => {
125
+ const content = `---
126
+ description: >
127
+ First paragraph here.
128
+
129
+ Second paragraph after empty line.
130
+ ---
131
+ Prompt`;
132
+
133
+ const parsed = parseSlashCommandContent(content);
134
+
135
+ expect(parsed.description).toBe('First paragraph here.\n\nSecond paragraph after empty line.');
136
+ expect(parsed.promptContent).toBe('Prompt');
137
+ });
138
+
139
+ it('should parse literal block scalar for argument-hint', () => {
140
+ const content = `---
141
+ argument-hint: |
142
+ [task-name]
143
+ [optional-notes]
144
+ ---
145
+ Prompt`;
146
+
147
+ const parsed = parseSlashCommandContent(content);
148
+
149
+ expect(parsed.argumentHint).toBe('[task-name]\n[optional-notes]');
150
+ });
151
+
152
+ it('should handle empty lines in literal block scalar', () => {
153
+ const content = `---
154
+ description: |
155
+ First paragraph here.
156
+
157
+ Second paragraph after empty line.
158
+ ---
159
+ Prompt`;
160
+
161
+ const parsed = parseSlashCommandContent(content);
162
+
163
+ expect(parsed.description).toBe('First paragraph here.\n\nSecond paragraph after empty line.');
164
+ });
165
+
166
+ it('should parse block scalar with other fields', () => {
167
+ const content = `---
168
+ description: |
169
+ Multi-line description
170
+ with multiple lines
171
+ model: claude-sonnet-4-5
172
+ allowed-tools:
173
+ - Read
174
+ - Write
175
+ ---
176
+ Prompt content`;
177
+
178
+ const parsed = parseSlashCommandContent(content);
179
+
180
+ expect(parsed.description).toBe('Multi-line description\nwith multiple lines');
181
+ expect(parsed.model).toBe('claude-sonnet-4-5');
182
+ expect(parsed.allowedTools).toEqual(['Read', 'Write']);
183
+ expect(parsed.promptContent).toBe('Prompt content');
184
+ });
185
+
186
+ it('should handle block scalar at end of frontmatter', () => {
187
+ const content = `---
188
+ model: claude-haiku-4-5
189
+ description: |
190
+ Last field in frontmatter
191
+ with multiple lines
192
+ ---
193
+ Prompt`;
194
+
195
+ const parsed = parseSlashCommandContent(content);
196
+
197
+ expect(parsed.description).toBe('Last field in frontmatter\nwith multiple lines');
198
+ expect(parsed.model).toBe('claude-haiku-4-5');
199
+ });
200
+
201
+ it('should preserve indentation within block scalar content', () => {
202
+ const content = `---
203
+ description: |
204
+ Code example:
205
+ - Step 1
206
+ - Step 2
207
+ ---
208
+ Prompt`;
209
+
210
+ const parsed = parseSlashCommandContent(content);
211
+
212
+ expect(parsed.description).toBe('Code example:\n - Step 1\n - Step 2');
213
+ });
214
+
215
+ it('should handle single-line block scalar', () => {
216
+ const content = `---
217
+ description: |
218
+ Just one line
219
+ ---
220
+ Prompt`;
221
+
222
+ const parsed = parseSlashCommandContent(content);
223
+
224
+ expect(parsed.description).toBe('Just one line');
225
+ });
226
+
227
+ it('should not confuse pipe in quoted string with block scalar', () => {
228
+ const content = `---
229
+ description: "Contains | pipe character"
230
+ ---
231
+ Prompt`;
232
+
233
+ const parsed = parseSlashCommandContent(content);
234
+
235
+ expect(parsed.description).toBe('Contains | pipe character');
236
+ });
237
+
238
+ it('should handle multiple block scalars in same frontmatter', () => {
239
+ const content = `---
240
+ description: |
241
+ First block scalar
242
+ with multiple lines
243
+ argument-hint: |
244
+ Second block scalar
245
+ also multi-line
246
+ ---
247
+ Prompt`;
248
+
249
+ const parsed = parseSlashCommandContent(content);
250
+
251
+ expect(parsed.description).toBe('First block scalar\nwith multiple lines');
252
+ expect(parsed.argumentHint).toBe('Second block scalar\nalso multi-line');
253
+ });
254
+
255
+ it('should handle CRLF line endings in block scalar', () => {
256
+ const content = '---\r\ndescription: |\r\n Line one\r\n Line two\r\n---\r\nPrompt';
257
+
258
+ const parsed = parseSlashCommandContent(content);
259
+
260
+ expect(parsed.description).toBe('Line one\nLine two');
261
+ expect(parsed.promptContent).toBe('Prompt');
262
+ });
263
+ });
264
+
265
+ describe('block scalar edge cases', () => {
266
+ it('should handle empty block scalar followed by another field', () => {
267
+ const content = `---
268
+ description: |
269
+ model: claude-sonnet-4-5
270
+ ---
271
+ Prompt`;
272
+
273
+ const parsed = parseSlashCommandContent(content);
274
+
275
+ // Empty block scalar yields no description (semantically same as absent)
276
+ expect(parsed.description).toBeUndefined();
277
+ expect(parsed.model).toBe('claude-sonnet-4-5');
278
+ });
279
+
280
+ it('should handle block scalar with only empty lines before next field', () => {
281
+ const content = `---
282
+ description: |
283
+
284
+ model: claude-sonnet-4-5
285
+ ---
286
+ Prompt`;
287
+
288
+ const parsed = parseSlashCommandContent(content);
289
+
290
+ // Empty lines followed by unindented field should end the block scalar
291
+ expect(parsed.model).toBe('claude-sonnet-4-5');
292
+ });
293
+
294
+ it('should handle strip chomping indicator (|-)', () => {
295
+ const content = `---
296
+ description: |-
297
+ No trailing newline here
298
+ ---
299
+ Prompt`;
300
+
301
+ const parsed = parseSlashCommandContent(content);
302
+
303
+ // Chomping indicator is recognized and parsed as block scalar
304
+ expect(parsed.description).toBe('No trailing newline here');
305
+ });
306
+
307
+ it('should handle keep chomping indicator (|+)', () => {
308
+ const content = `---
309
+ description: |+
310
+ Keep indicator recognized
311
+ ---
312
+ Prompt`;
313
+
314
+ const parsed = parseSlashCommandContent(content);
315
+
316
+ // Keep chomping indicator is recognized
317
+ expect(parsed.description).toBe('Keep indicator recognized');
318
+ });
319
+
320
+ it('should handle folded with strip chomping (>-)', () => {
321
+ const content = `---
322
+ description: >-
323
+ Folded with strip
324
+ chomping indicator
325
+ ---
326
+ Prompt`;
327
+
328
+ const parsed = parseSlashCommandContent(content);
329
+
330
+ expect(parsed.description).toBe('Folded with strip chomping indicator');
331
+ });
332
+
333
+ it('should not enable block scalar for unsupported keys', () => {
334
+ const content = `---
335
+ notes: |
336
+ This should not be parsed as block scalar
337
+ description: Regular description
338
+ ---
339
+ Prompt`;
340
+
341
+ const parsed = parseSlashCommandContent(content);
342
+
343
+ // notes is not a supported key, so | is treated as the value
344
+ // description should be parsed normally
345
+ expect(parsed.description).toBe('Regular description');
346
+ });
347
+
348
+ it('should handle allowed-tools with block scalar indicator gracefully', () => {
349
+ const content = `---
350
+ allowed-tools: |
351
+ Read
352
+ Write
353
+ description: Test
354
+ ---
355
+ Prompt`;
356
+
357
+ const parsed = parseSlashCommandContent(content);
358
+
359
+ // allowed-tools doesn't support block scalar, so | becomes the value
360
+ // The Read/Write lines are ignored as they're not valid YAML keys
361
+ expect(parsed.description).toBe('Test');
362
+ });
363
+
364
+ it('should preserve unicode content in block scalar', () => {
365
+ const content = `---
366
+ description: |
367
+ Hello 世界
368
+ Émoji: 🎉
369
+ ---
370
+ Prompt`;
371
+
372
+ const parsed = parseSlashCommandContent(content);
373
+
374
+ expect(parsed.description).toBe('Hello 世界\nÉmoji: 🎉');
375
+ });
376
+
377
+ it('should preserve relative indentation in deeply nested content', () => {
378
+ const content = `---
379
+ description: |
380
+ Level 1
381
+ Level 2
382
+ Level 3
383
+ Level 4
384
+ ---
385
+ Prompt`;
386
+
387
+ const parsed = parseSlashCommandContent(content);
388
+
389
+ expect(parsed.description).toBe('Level 1\n Level 2\n Level 3\n Level 4');
390
+ });
391
+
392
+ it('should preserve colons in block scalar content', () => {
393
+ const content = `---
394
+ description: |
395
+ key: value
396
+ another: pair
397
+ ---
398
+ Prompt`;
399
+
400
+ const parsed = parseSlashCommandContent(content);
401
+
402
+ expect(parsed.description).toBe('key: value\nanother: pair');
403
+ });
404
+
405
+ it('should preserve comment-like content (# lines)', () => {
406
+ const content = `---
407
+ description: |
408
+ # This looks like a YAML comment
409
+ But it is preserved as content
410
+ ---
411
+ Prompt`;
412
+
413
+ const parsed = parseSlashCommandContent(content);
414
+
415
+ expect(parsed.description).toBe('# This looks like a YAML comment\nBut it is preserved as content');
416
+ });
417
+
418
+ it('should preserve trailing whitespace in block scalar lines', () => {
419
+ // Use explicit string to ensure trailing spaces are preserved
420
+ const content = '---\ndescription: |\n Line with trailing spaces \n Normal line\n---\nPrompt';
421
+
422
+ const parsed = parseSlashCommandContent(content);
423
+
424
+ expect(parsed.description).toBe('Line with trailing spaces \nNormal line');
425
+ });
426
+
427
+ it('should preserve leading empty lines in block scalar', () => {
428
+ const content = `---
429
+ description: |
430
+
431
+ Content after empty line
432
+ ---
433
+ Prompt`;
434
+
435
+ const parsed = parseSlashCommandContent(content);
436
+
437
+ // Leading empty lines are preserved per YAML spec
438
+ expect(parsed.description).toBe('\nContent after empty line');
439
+ });
440
+ });
441
+
442
+ describe('skill fields', () => {
443
+ it('should parse kebab-case disable-model-invocation', () => {
444
+ const content = `---
445
+ description: A skill
446
+ disable-model-invocation: true
447
+ ---
448
+ Prompt`;
449
+
450
+ const parsed = parseSlashCommandContent(content);
451
+ expect(parsed.disableModelInvocation).toBe(true);
452
+ });
453
+
454
+ it('should parse kebab-case user-invocable', () => {
455
+ const content = `---
456
+ description: A skill
457
+ user-invocable: false
458
+ ---
459
+ Prompt`;
460
+
461
+ const parsed = parseSlashCommandContent(content);
462
+ expect(parsed.userInvocable).toBe(false);
463
+ });
464
+
465
+ it('should parse camelCase disableModelInvocation (backwards compat)', () => {
466
+ const content = `---
467
+ description: A skill
468
+ disableModelInvocation: true
469
+ ---
470
+ Prompt`;
471
+
472
+ const parsed = parseSlashCommandContent(content);
473
+ expect(parsed.disableModelInvocation).toBe(true);
474
+ });
475
+
476
+ it('should parse camelCase userInvocable (backwards compat)', () => {
477
+ const content = `---
478
+ description: A skill
479
+ userInvocable: false
480
+ ---
481
+ Prompt`;
482
+
483
+ const parsed = parseSlashCommandContent(content);
484
+ expect(parsed.userInvocable).toBe(false);
485
+ });
486
+
487
+ it('should prefer kebab-case over camelCase when both present', () => {
488
+ const content = `---
489
+ disable-model-invocation: true
490
+ disableModelInvocation: false
491
+ user-invocable: false
492
+ userInvocable: true
493
+ ---
494
+ Prompt`;
495
+
496
+ const parsed = parseSlashCommandContent(content);
497
+ expect(parsed.disableModelInvocation).toBe(true);
498
+ expect(parsed.userInvocable).toBe(false);
499
+ });
500
+
501
+ it('should parse context string', () => {
502
+ const content = `---
503
+ description: A skill
504
+ context: fork
505
+ ---
506
+ Prompt`;
507
+
508
+ const parsed = parseSlashCommandContent(content);
509
+ expect(parsed.context).toBe('fork');
510
+ });
511
+
512
+ it('should parse agent string', () => {
513
+ const content = `---
514
+ description: A skill
515
+ agent: code-reviewer
516
+ ---
517
+ Prompt`;
518
+
519
+ const parsed = parseSlashCommandContent(content);
520
+ expect(parsed.agent).toBe('code-reviewer');
521
+ });
522
+
523
+ it('should parse all skill fields together', () => {
524
+ const content = `---
525
+ description: Full skill
526
+ disableModelInvocation: true
527
+ userInvocable: true
528
+ context: fork
529
+ agent: code-reviewer
530
+ model: sonnet
531
+ ---
532
+ Do the thing`;
533
+
534
+ const parsed = parseSlashCommandContent(content);
535
+ expect(parsed.description).toBe('Full skill');
536
+ expect(parsed.disableModelInvocation).toBe(true);
537
+ expect(parsed.userInvocable).toBe(true);
538
+ expect(parsed.context).toBe('fork');
539
+ expect(parsed.agent).toBe('code-reviewer');
540
+ expect(parsed.model).toBe('sonnet');
541
+ expect(parsed.promptContent).toBe('Do the thing');
542
+ });
543
+
544
+ it('should return undefined for missing skill fields', () => {
545
+ const content = `---
546
+ description: Simple command
547
+ ---
548
+ Prompt`;
549
+
550
+ const parsed = parseSlashCommandContent(content);
551
+ expect(parsed.disableModelInvocation).toBeUndefined();
552
+ expect(parsed.userInvocable).toBeUndefined();
553
+ expect(parsed.context).toBeUndefined();
554
+ expect(parsed.agent).toBeUndefined();
555
+ expect(parsed.hooks).toBeUndefined();
556
+ });
557
+ });
558
+ });
559
+
560
+ describe('yamlString', () => {
561
+ it('returns plain value for simple strings', () => {
562
+ expect(yamlString('hello world')).toBe('hello world');
563
+ });
564
+
565
+ it('quotes strings with colons', () => {
566
+ expect(yamlString('key: value')).toBe('"key: value"');
567
+ });
568
+
569
+ it('quotes strings with hash', () => {
570
+ expect(yamlString('has # comment')).toBe('"has # comment"');
571
+ });
572
+
573
+ it('quotes strings with newlines', () => {
574
+ expect(yamlString('line1\nline2')).toBe('"line1\nline2"');
575
+ });
576
+
577
+ it('quotes strings starting with space', () => {
578
+ expect(yamlString(' leading')).toBe('" leading"');
579
+ });
580
+
581
+ it('quotes strings ending with space', () => {
582
+ expect(yamlString('trailing ')).toBe('"trailing "');
583
+ });
584
+
585
+ it('escapes double quotes inside quoted strings', () => {
586
+ expect(yamlString('has "quotes" inside: yes')).toBe('"has \\"quotes\\" inside: yes"');
587
+ });
588
+ });
589
+
590
+ describe('serializeCommand', () => {
591
+ it('strips frontmatter from content before serializing', () => {
592
+ const result = serializeCommand({
593
+ id: 'cmd-test',
594
+ name: 'test',
595
+ description: 'Test',
596
+ content: '---\ndescription: old\n---\nBody text',
597
+ });
598
+
599
+ // Should use the SlashCommand's description, not the one in content frontmatter
600
+ expect(result).toContain('description: Test');
601
+ expect(result).toContain('Body text');
602
+ expect(result).not.toContain('description: old');
603
+ });
604
+
605
+ it('handles content without frontmatter', () => {
606
+ const result = serializeCommand({
607
+ id: 'cmd-test',
608
+ name: 'test',
609
+ description: 'Simple',
610
+ content: 'Just a prompt',
611
+ });
612
+
613
+ expect(result).toContain('description: Simple');
614
+ expect(result).toContain('Just a prompt');
615
+ });
616
+ });
617
+
618
+ describe('serializeSlashCommandMarkdown', () => {
619
+ it('serializes all fields in kebab-case', () => {
620
+ const result = serializeSlashCommandMarkdown({
621
+ name: 'my-skill',
622
+ description: 'Test command',
623
+ argumentHint: '[file]',
624
+ allowedTools: ['Read', 'Grep'],
625
+ model: 'claude-sonnet-4-5',
626
+ disableModelInvocation: true,
627
+ userInvocable: false,
628
+ context: 'fork',
629
+ agent: 'code-reviewer',
630
+ }, 'Do the thing');
631
+
632
+ expect(result).toContain('name: my-skill');
633
+ expect(result).toContain('description: Test command');
634
+ expect(result).toContain('argument-hint: "[file]"');
635
+ expect(result).toContain('allowed-tools:');
636
+ expect(result).toContain(' - Read');
637
+ expect(result).toContain(' - Grep');
638
+ expect(result).toContain('model: claude-sonnet-4-5');
639
+ expect(result).toContain('disable-model-invocation: true');
640
+ expect(result).toContain('user-invocable: false');
641
+ expect(result).toContain('context: fork');
642
+ expect(result).toContain('agent: code-reviewer');
643
+ expect(result).toContain('Do the thing');
644
+ });
645
+
646
+ it('omits undefined fields', () => {
647
+ const result = serializeSlashCommandMarkdown({
648
+ description: 'Minimal',
649
+ }, 'Prompt');
650
+
651
+ expect(result).toContain('description: Minimal');
652
+ expect(result).not.toContain('name');
653
+ expect(result).not.toContain('argument-hint');
654
+ expect(result).not.toContain('allowed-tools');
655
+ expect(result).not.toContain('model');
656
+ expect(result).not.toContain('disable-model-invocation');
657
+ expect(result).not.toContain('user-invocable');
658
+ expect(result).not.toContain('context');
659
+ expect(result).not.toContain('agent');
660
+ expect(result).not.toContain('hooks');
661
+ });
662
+
663
+ it('serializes hooks as JSON', () => {
664
+ const hooks = { PreToolUse: [{ matcher: 'Bash' }] };
665
+ const result = serializeSlashCommandMarkdown({ hooks }, 'Prompt');
666
+ expect(result).toContain(`hooks: ${JSON.stringify(hooks)}`);
667
+ });
668
+
669
+ it('produces valid frontmatter when no metadata exists', () => {
670
+ const result = serializeSlashCommandMarkdown({}, 'Just a prompt');
671
+ expect(result).toBe('---\n\n---\nJust a prompt');
672
+ });
673
+
674
+ it('round-trips through parse', () => {
675
+ const serialized = serializeSlashCommandMarkdown({
676
+ description: 'Round trip',
677
+ disableModelInvocation: true,
678
+ userInvocable: false,
679
+ context: 'fork',
680
+ agent: 'reviewer',
681
+ }, 'Body text');
682
+
683
+ const parsed = parseSlashCommandContent(serialized);
684
+ expect(parsed.description).toBe('Round trip');
685
+ expect(parsed.disableModelInvocation).toBe(true);
686
+ expect(parsed.userInvocable).toBe(false);
687
+ expect(parsed.context).toBe('fork');
688
+ expect(parsed.agent).toBe('reviewer');
689
+ expect(parsed.promptContent).toBe('Body text');
690
+ });
691
+ });
692
+
693
+ describe('validateCommandName', () => {
694
+ it('accepts valid lowercase names', () => {
695
+ expect(validateCommandName('my-command')).toBeNull();
696
+ expect(validateCommandName('test')).toBeNull();
697
+ expect(validateCommandName('a')).toBeNull();
698
+ expect(validateCommandName('abc123')).toBeNull();
699
+ expect(validateCommandName('my-cmd-2')).toBeNull();
700
+ expect(validateCommandName('a1b2c3')).toBeNull();
701
+ });
702
+
703
+ it('accepts name at exactly 64 characters', () => {
704
+ const name = 'a'.repeat(64);
705
+ expect(validateCommandName(name)).toBeNull();
706
+ });
707
+
708
+ it('rejects empty name', () => {
709
+ expect(validateCommandName('')).not.toBeNull();
710
+ });
711
+
712
+ it('rejects uppercase letters', () => {
713
+ expect(validateCommandName('MyCommand')).not.toBeNull();
714
+ expect(validateCommandName('TEST')).not.toBeNull();
715
+ });
716
+
717
+ it('rejects underscores', () => {
718
+ expect(validateCommandName('my_command')).not.toBeNull();
719
+ });
720
+
721
+ it('rejects slashes', () => {
722
+ expect(validateCommandName('my/command')).not.toBeNull();
723
+ });
724
+
725
+ it('rejects spaces', () => {
726
+ expect(validateCommandName('my command')).not.toBeNull();
727
+ });
728
+
729
+ it('rejects colons', () => {
730
+ expect(validateCommandName('my:command')).not.toBeNull();
731
+ });
732
+
733
+ it('rejects names exceeding 64 characters', () => {
734
+ const name = 'a'.repeat(65);
735
+ expect(validateCommandName(name)).not.toBeNull();
736
+ });
737
+
738
+ it('rejects special characters', () => {
739
+ expect(validateCommandName('cmd!@#')).not.toBeNull();
740
+ expect(validateCommandName('cmd.test')).not.toBeNull();
741
+ });
742
+
743
+ it.each(['true', 'false', 'null', 'yes', 'no', 'on', 'off'])(
744
+ 'rejects YAML reserved word "%s"',
745
+ (word) => {
746
+ expect(validateCommandName(word)).not.toBeNull();
747
+ }
748
+ );
749
+ });
750
+
751
+ describe('extractFirstParagraph', () => {
752
+ it('returns the first paragraph from multi-paragraph content', () => {
753
+ expect(extractFirstParagraph('First paragraph.\n\nSecond paragraph.'))
754
+ .toBe('First paragraph.');
755
+ });
756
+
757
+ it('returns single-line content as-is', () => {
758
+ expect(extractFirstParagraph('Only one line')).toBe('Only one line');
759
+ });
760
+
761
+ it('collapses multi-line first paragraph into single line', () => {
762
+ expect(extractFirstParagraph('Line one\nline two\n\nSecond paragraph'))
763
+ .toBe('Line one line two');
764
+ });
765
+
766
+ it('returns undefined for empty content', () => {
767
+ expect(extractFirstParagraph('')).toBeUndefined();
768
+ });
769
+
770
+ it('returns undefined for whitespace-only content', () => {
771
+ expect(extractFirstParagraph(' \n \n ')).toBeUndefined();
772
+ });
773
+
774
+ it('skips leading blank lines', () => {
775
+ expect(extractFirstParagraph('\n\nActual first paragraph.\n\nSecond.'))
776
+ .toBe('Actual first paragraph.');
777
+ });
778
+ });