@myrialabs/clopen 0.0.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 (456) hide show
  1. package/.env.example +6 -0
  2. package/.github/workflows/release.yml +60 -0
  3. package/.github/workflows/test.yml +40 -0
  4. package/CONTRIBUTING.md +499 -0
  5. package/LICENSE +21 -0
  6. package/README.md +209 -0
  7. package/backend/index.ts +156 -0
  8. package/backend/lib/chat/helpers.ts +42 -0
  9. package/backend/lib/chat/index.ts +2 -0
  10. package/backend/lib/chat/stream-manager.ts +1126 -0
  11. package/backend/lib/database/README.md +77 -0
  12. package/backend/lib/database/index.ts +119 -0
  13. package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
  14. package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
  15. package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
  16. package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
  17. package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
  18. package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
  19. package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
  20. package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
  21. package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
  22. package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
  23. package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
  24. package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
  25. package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
  26. package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
  27. package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
  28. package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
  29. package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
  30. package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
  31. package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
  32. package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
  33. package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
  34. package/backend/lib/database/migrations/index.ts +154 -0
  35. package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
  36. package/backend/lib/database/queries/engine-queries.ts +75 -0
  37. package/backend/lib/database/queries/index.ts +9 -0
  38. package/backend/lib/database/queries/message-queries.ts +472 -0
  39. package/backend/lib/database/queries/project-queries.ts +117 -0
  40. package/backend/lib/database/queries/session-queries.ts +271 -0
  41. package/backend/lib/database/queries/settings-queries.ts +34 -0
  42. package/backend/lib/database/queries/snapshot-queries.ts +326 -0
  43. package/backend/lib/database/queries/utils-queries.ts +59 -0
  44. package/backend/lib/database/seeders/index.ts +13 -0
  45. package/backend/lib/database/seeders/settings_seeder.ts +84 -0
  46. package/backend/lib/database/utils/connection.ts +174 -0
  47. package/backend/lib/database/utils/index.ts +4 -0
  48. package/backend/lib/database/utils/migration-runner.ts +118 -0
  49. package/backend/lib/database/utils/seeder-runner.ts +121 -0
  50. package/backend/lib/engine/adapters/claude/environment.ts +164 -0
  51. package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
  52. package/backend/lib/engine/adapters/claude/index.ts +1 -0
  53. package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
  54. package/backend/lib/engine/adapters/claude/stream.ts +177 -0
  55. package/backend/lib/engine/adapters/opencode/index.ts +2 -0
  56. package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
  57. package/backend/lib/engine/adapters/opencode/server.ts +104 -0
  58. package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
  59. package/backend/lib/engine/index.ts +196 -0
  60. package/backend/lib/engine/types.ts +58 -0
  61. package/backend/lib/files/file-operations.ts +478 -0
  62. package/backend/lib/files/file-reading.ts +308 -0
  63. package/backend/lib/files/file-watcher.ts +383 -0
  64. package/backend/lib/files/path-browsing.ts +382 -0
  65. package/backend/lib/git/git-executor.ts +88 -0
  66. package/backend/lib/git/git-parser.ts +411 -0
  67. package/backend/lib/git/git-service.ts +505 -0
  68. package/backend/lib/mcp/README.md +1144 -0
  69. package/backend/lib/mcp/config.ts +316 -0
  70. package/backend/lib/mcp/index.ts +35 -0
  71. package/backend/lib/mcp/project-context.ts +236 -0
  72. package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
  73. package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
  74. package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
  75. package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
  76. package/backend/lib/mcp/servers/helper.ts +143 -0
  77. package/backend/lib/mcp/servers/index.ts +45 -0
  78. package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
  79. package/backend/lib/mcp/servers/weather/index.ts +31 -0
  80. package/backend/lib/mcp/stdio-server.ts +103 -0
  81. package/backend/lib/mcp/types.ts +65 -0
  82. package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
  83. package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
  84. package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
  85. package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
  86. package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
  87. package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
  88. package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
  89. package/backend/lib/preview/browser/browser-pool.ts +357 -0
  90. package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
  91. package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
  92. package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
  93. package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
  94. package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
  95. package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
  96. package/backend/lib/preview/browser/types.ts +359 -0
  97. package/backend/lib/preview/index.ts +24 -0
  98. package/backend/lib/project/index.ts +2 -0
  99. package/backend/lib/project/status-manager.ts +182 -0
  100. package/backend/lib/shared/index.ts +2 -0
  101. package/backend/lib/shared/port-utils.ts +25 -0
  102. package/backend/lib/shared/process-manager.ts +281 -0
  103. package/backend/lib/snapshot/blob-store.ts +227 -0
  104. package/backend/lib/snapshot/gitignore.ts +307 -0
  105. package/backend/lib/snapshot/helpers.ts +397 -0
  106. package/backend/lib/snapshot/snapshot-service.ts +483 -0
  107. package/backend/lib/terminal/helpers.ts +14 -0
  108. package/backend/lib/terminal/index.ts +8 -0
  109. package/backend/lib/terminal/pty-manager.ts +4 -0
  110. package/backend/lib/terminal/pty-session-manager.ts +387 -0
  111. package/backend/lib/terminal/shell-utils.ts +313 -0
  112. package/backend/lib/terminal/stream-manager.ts +293 -0
  113. package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
  114. package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
  115. package/backend/lib/user/helpers.ts +87 -0
  116. package/backend/lib/utils/ws.ts +944 -0
  117. package/backend/lib/vite-dev.ts +353 -0
  118. package/backend/middleware/cors.ts +15 -0
  119. package/backend/middleware/error-handler.ts +49 -0
  120. package/backend/middleware/logger.ts +9 -0
  121. package/backend/types/api.ts +24 -0
  122. package/backend/ws/README.md +1505 -0
  123. package/backend/ws/chat/background.ts +198 -0
  124. package/backend/ws/chat/index.ts +21 -0
  125. package/backend/ws/chat/stream.ts +707 -0
  126. package/backend/ws/engine/claude/accounts.ts +401 -0
  127. package/backend/ws/engine/claude/index.ts +13 -0
  128. package/backend/ws/engine/claude/status.ts +43 -0
  129. package/backend/ws/engine/index.ts +14 -0
  130. package/backend/ws/engine/opencode/index.ts +11 -0
  131. package/backend/ws/engine/opencode/status.ts +30 -0
  132. package/backend/ws/engine/utils.ts +36 -0
  133. package/backend/ws/files/index.ts +30 -0
  134. package/backend/ws/files/read.ts +189 -0
  135. package/backend/ws/files/search.ts +453 -0
  136. package/backend/ws/files/watch.ts +124 -0
  137. package/backend/ws/files/write.ts +143 -0
  138. package/backend/ws/git/branch.ts +106 -0
  139. package/backend/ws/git/commit.ts +39 -0
  140. package/backend/ws/git/conflict.ts +68 -0
  141. package/backend/ws/git/diff.ts +69 -0
  142. package/backend/ws/git/index.ts +24 -0
  143. package/backend/ws/git/log.ts +41 -0
  144. package/backend/ws/git/remote.ts +214 -0
  145. package/backend/ws/git/staging.ts +84 -0
  146. package/backend/ws/git/status.ts +90 -0
  147. package/backend/ws/index.ts +69 -0
  148. package/backend/ws/mcp/index.ts +61 -0
  149. package/backend/ws/messages/crud.ts +74 -0
  150. package/backend/ws/messages/index.ts +14 -0
  151. package/backend/ws/preview/browser/cleanup.ts +129 -0
  152. package/backend/ws/preview/browser/console.ts +114 -0
  153. package/backend/ws/preview/browser/interact.ts +513 -0
  154. package/backend/ws/preview/browser/mcp.ts +129 -0
  155. package/backend/ws/preview/browser/native-ui.ts +235 -0
  156. package/backend/ws/preview/browser/stats.ts +55 -0
  157. package/backend/ws/preview/browser/tab-info.ts +126 -0
  158. package/backend/ws/preview/browser/tab.ts +166 -0
  159. package/backend/ws/preview/browser/webcodecs.ts +293 -0
  160. package/backend/ws/preview/index.ts +146 -0
  161. package/backend/ws/projects/crud.ts +113 -0
  162. package/backend/ws/projects/index.ts +25 -0
  163. package/backend/ws/projects/presence.ts +46 -0
  164. package/backend/ws/projects/status.ts +116 -0
  165. package/backend/ws/sessions/crud.ts +327 -0
  166. package/backend/ws/sessions/index.ts +33 -0
  167. package/backend/ws/settings/crud.ts +112 -0
  168. package/backend/ws/settings/index.ts +14 -0
  169. package/backend/ws/snapshot/index.ts +17 -0
  170. package/backend/ws/snapshot/restore.ts +173 -0
  171. package/backend/ws/snapshot/timeline.ts +141 -0
  172. package/backend/ws/system/index.ts +14 -0
  173. package/backend/ws/system/operations.ts +49 -0
  174. package/backend/ws/terminal/index.ts +40 -0
  175. package/backend/ws/terminal/persistence.ts +153 -0
  176. package/backend/ws/terminal/session.ts +382 -0
  177. package/backend/ws/terminal/stream.ts +79 -0
  178. package/backend/ws/tunnel/index.ts +14 -0
  179. package/backend/ws/tunnel/operations.ts +91 -0
  180. package/backend/ws/types.ts +20 -0
  181. package/backend/ws/user/crud.ts +156 -0
  182. package/backend/ws/user/index.ts +14 -0
  183. package/bin/clopen.ts +307 -0
  184. package/bun.lock +1352 -0
  185. package/frontend/App.svelte +34 -0
  186. package/frontend/app.css +313 -0
  187. package/frontend/lib/app-environment.ts +10 -0
  188. package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
  189. package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
  190. package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
  191. package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
  192. package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
  193. package/frontend/lib/components/chat/formatters/index.ts +3 -0
  194. package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
  195. package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
  196. package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
  197. package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
  198. package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
  199. package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
  200. package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
  201. package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
  202. package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
  203. package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
  204. package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
  205. package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
  206. package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
  207. package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
  208. package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
  209. package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
  210. package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
  211. package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
  212. package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
  213. package/frontend/lib/components/chat/shared/index.ts +2 -0
  214. package/frontend/lib/components/chat/shared/utils.ts +116 -0
  215. package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
  216. package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
  217. package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
  218. package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
  219. package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
  220. package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
  221. package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
  222. package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
  223. package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
  224. package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
  225. package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
  226. package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
  227. package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
  228. package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
  229. package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
  230. package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
  231. package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
  232. package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
  233. package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
  234. package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
  235. package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
  236. package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
  237. package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
  238. package/frontend/lib/components/chat/tools/components/index.ts +7 -0
  239. package/frontend/lib/components/chat/tools/index.ts +26 -0
  240. package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
  241. package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
  242. package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
  243. package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
  244. package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
  245. package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
  246. package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
  247. package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
  248. package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
  249. package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
  250. package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
  251. package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
  252. package/frontend/lib/components/common/Alert.svelte +139 -0
  253. package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
  254. package/frontend/lib/components/common/Button.svelte +71 -0
  255. package/frontend/lib/components/common/Card.svelte +102 -0
  256. package/frontend/lib/components/common/Checkbox.svelte +48 -0
  257. package/frontend/lib/components/common/Dialog.svelte +249 -0
  258. package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
  259. package/frontend/lib/components/common/Icon.svelte +58 -0
  260. package/frontend/lib/components/common/Input.svelte +72 -0
  261. package/frontend/lib/components/common/Lightbox.svelte +233 -0
  262. package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
  263. package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
  264. package/frontend/lib/components/common/Modal.svelte +177 -0
  265. package/frontend/lib/components/common/ModalProvider.svelte +28 -0
  266. package/frontend/lib/components/common/ModelSelector.svelte +110 -0
  267. package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
  268. package/frontend/lib/components/common/NotificationToast.svelte +113 -0
  269. package/frontend/lib/components/common/PageTemplate.svelte +76 -0
  270. package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
  271. package/frontend/lib/components/common/Select.svelte +98 -0
  272. package/frontend/lib/components/common/Textarea.svelte +80 -0
  273. package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
  274. package/frontend/lib/components/common/lucide-icons.ts +1642 -0
  275. package/frontend/lib/components/common/material-icons.ts +1082 -0
  276. package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
  277. package/frontend/lib/components/common/xterm/index.ts +16 -0
  278. package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
  279. package/frontend/lib/components/common/xterm/types.ts +31 -0
  280. package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
  281. package/frontend/lib/components/files/FileNode.svelte +384 -0
  282. package/frontend/lib/components/files/FileTree.svelte +681 -0
  283. package/frontend/lib/components/files/FileViewer.svelte +728 -0
  284. package/frontend/lib/components/files/SearchResults.svelte +303 -0
  285. package/frontend/lib/components/git/BranchManager.svelte +458 -0
  286. package/frontend/lib/components/git/ChangesSection.svelte +107 -0
  287. package/frontend/lib/components/git/CommitForm.svelte +76 -0
  288. package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
  289. package/frontend/lib/components/git/DiffViewer.svelte +364 -0
  290. package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
  291. package/frontend/lib/components/git/GitButton.svelte +33 -0
  292. package/frontend/lib/components/git/GitLog.svelte +361 -0
  293. package/frontend/lib/components/git/GitModal.svelte +80 -0
  294. package/frontend/lib/components/history/HistoryModal.svelte +563 -0
  295. package/frontend/lib/components/history/HistoryView.svelte +615 -0
  296. package/frontend/lib/components/index.ts +34 -0
  297. package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
  298. package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
  299. package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
  300. package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
  301. package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
  302. package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
  303. package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
  304. package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
  305. package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
  306. package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
  307. package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
  308. package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
  309. package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
  310. package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
  311. package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
  312. package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
  313. package/frontend/lib/components/preview/index.ts +2 -0
  314. package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
  315. package/frontend/lib/components/settings/SettingsView.svelte +36 -0
  316. package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
  317. package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
  318. package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
  319. package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
  320. package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
  321. package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
  322. package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
  323. package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
  324. package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
  325. package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
  326. package/frontend/lib/components/terminal/Terminal.svelte +368 -0
  327. package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
  328. package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
  329. package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
  330. package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
  331. package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
  332. package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
  333. package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
  334. package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
  335. package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
  336. package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
  337. package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
  338. package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
  339. package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
  340. package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
  341. package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
  342. package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
  343. package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
  344. package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
  345. package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
  346. package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
  347. package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
  348. package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
  349. package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
  350. package/frontend/lib/constants/preview.ts +45 -0
  351. package/frontend/lib/services/chat/chat.service.ts +704 -0
  352. package/frontend/lib/services/chat/index.ts +7 -0
  353. package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
  354. package/frontend/lib/services/notification/index.ts +8 -0
  355. package/frontend/lib/services/notification/push.service.ts +144 -0
  356. package/frontend/lib/services/notification/sound.service.ts +127 -0
  357. package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
  358. package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
  359. package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
  360. package/frontend/lib/services/preview/index.ts +23 -0
  361. package/frontend/lib/services/project/index.ts +8 -0
  362. package/frontend/lib/services/project/status.service.ts +159 -0
  363. package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
  364. package/frontend/lib/services/terminal/background/index.ts +130 -0
  365. package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
  366. package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
  367. package/frontend/lib/services/terminal/index.ts +14 -0
  368. package/frontend/lib/services/terminal/persistence.service.ts +260 -0
  369. package/frontend/lib/services/terminal/project.service.ts +953 -0
  370. package/frontend/lib/services/terminal/session.service.ts +364 -0
  371. package/frontend/lib/services/terminal/terminal.service.ts +369 -0
  372. package/frontend/lib/stores/core/app.svelte.ts +117 -0
  373. package/frontend/lib/stores/core/files.svelte.ts +73 -0
  374. package/frontend/lib/stores/core/presence.svelte.ts +48 -0
  375. package/frontend/lib/stores/core/projects.svelte.ts +317 -0
  376. package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
  377. package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
  378. package/frontend/lib/stores/features/models.svelte.ts +89 -0
  379. package/frontend/lib/stores/features/settings.svelte.ts +87 -0
  380. package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
  381. package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
  382. package/frontend/lib/stores/features/user.svelte.ts +96 -0
  383. package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
  384. package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
  385. package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
  386. package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
  387. package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
  388. package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
  389. package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
  390. package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
  391. package/frontend/lib/types/native-ui.ts +73 -0
  392. package/frontend/lib/utils/chat/date-separator.ts +39 -0
  393. package/frontend/lib/utils/chat/message-grouper.ts +219 -0
  394. package/frontend/lib/utils/chat/message-processor.ts +135 -0
  395. package/frontend/lib/utils/chat/tool-handler.ts +161 -0
  396. package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
  397. package/frontend/lib/utils/click-outside.ts +20 -0
  398. package/frontend/lib/utils/context-manager.ts +257 -0
  399. package/frontend/lib/utils/file-icon-mappings.ts +769 -0
  400. package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
  401. package/frontend/lib/utils/git-status.ts +68 -0
  402. package/frontend/lib/utils/platform.ts +113 -0
  403. package/frontend/lib/utils/port-check.ts +65 -0
  404. package/frontend/lib/utils/terminalFormatter.ts +207 -0
  405. package/frontend/lib/utils/theme.ts +6 -0
  406. package/frontend/lib/utils/tree-visualizer.ts +320 -0
  407. package/frontend/lib/utils/ws.ts +44 -0
  408. package/frontend/main.ts +13 -0
  409. package/index.html +70 -0
  410. package/package.json +111 -0
  411. package/scripts/generate-icons.ts +87 -0
  412. package/scripts/pre-publish-check.sh +142 -0
  413. package/scripts/setup-hooks.sh +134 -0
  414. package/scripts/validate-branch-name.sh +47 -0
  415. package/scripts/validate-commit-msg.sh +42 -0
  416. package/shared/constants/engines.ts +134 -0
  417. package/shared/types/database/connection.ts +16 -0
  418. package/shared/types/database/index.ts +6 -0
  419. package/shared/types/database/schema.ts +141 -0
  420. package/shared/types/engine/index.ts +45 -0
  421. package/shared/types/filesystem/index.ts +22 -0
  422. package/shared/types/git.ts +171 -0
  423. package/shared/types/messaging/index.ts +239 -0
  424. package/shared/types/messaging/tool.ts +526 -0
  425. package/shared/types/network/api.ts +18 -0
  426. package/shared/types/network/index.ts +5 -0
  427. package/shared/types/stores/app.ts +23 -0
  428. package/shared/types/stores/dialog.ts +21 -0
  429. package/shared/types/stores/index.ts +3 -0
  430. package/shared/types/stores/settings.ts +15 -0
  431. package/shared/types/terminal/index.ts +44 -0
  432. package/shared/types/ui/components.ts +61 -0
  433. package/shared/types/ui/icons.ts +23 -0
  434. package/shared/types/ui/index.ts +22 -0
  435. package/shared/types/ui/notifications.ts +14 -0
  436. package/shared/types/ui/theme.ts +12 -0
  437. package/shared/types/websocket/index.ts +43 -0
  438. package/shared/types/window.d.ts +13 -0
  439. package/shared/utils/anonymous-user.ts +168 -0
  440. package/shared/utils/async.ts +10 -0
  441. package/shared/utils/diff-calculator.ts +184 -0
  442. package/shared/utils/file-type-detection.ts +166 -0
  443. package/shared/utils/logger.ts +158 -0
  444. package/shared/utils/message-formatter.ts +79 -0
  445. package/shared/utils/path.ts +47 -0
  446. package/shared/utils/ws-client.ts +768 -0
  447. package/shared/utils/ws-server.ts +660 -0
  448. package/static/audio/notification.ogg +0 -0
  449. package/static/favicon.svg +8 -0
  450. package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
  451. package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
  452. package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
  453. package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
  454. package/static/fonts/dm-sans.css +96 -0
  455. package/svelte.config.js +20 -0
  456. package/vite.config.ts +33 -0
@@ -0,0 +1,512 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { Page } from 'puppeteer';
3
+ import type {
4
+ BrowserTab,
5
+ BrowserSelectInfo,
6
+ BrowserSelectResponse,
7
+ BrowserContextMenuInfo,
8
+ BrowserContextMenuResponse,
9
+ BrowserContextMenuItem
10
+ } from './types';
11
+ import { debug } from '$shared/utils/logger';
12
+ import { nanoid } from 'nanoid';
13
+
14
+ /**
15
+ * Browser Native UI Handler
16
+ *
17
+ * Handles OS-native UI elements that cannot be rendered in headless browser:
18
+ * - <select> dropdown menus
19
+ * - Context menus (right-click)
20
+ *
21
+ * Detects these elements, extracts their data, and emits events to frontend
22
+ * for rendering as overlay components positioned over the canvas.
23
+ */
24
+ export class BrowserNativeUIHandler extends EventEmitter {
25
+ constructor() {
26
+ super();
27
+ }
28
+
29
+ /**
30
+ * Check if clicked element is a <select> and extract options
31
+ */
32
+ async checkForSelect(sessionId: string, page: Page, x: number, y: number): Promise<BrowserSelectInfo | null> {
33
+ try {
34
+ // Generate unique select ID
35
+ const selectId = nanoid(10);
36
+
37
+ const selectData = await page.evaluate((params) => {
38
+ const { x, y, selectId } = params;
39
+ const element = document.elementFromPoint(x, y);
40
+
41
+ if (!element) return null;
42
+
43
+ // Check if element is a select or inside a select
44
+ let selectElement: HTMLSelectElement | null = null;
45
+ if (element.tagName === 'SELECT') {
46
+ selectElement = element as HTMLSelectElement;
47
+ } else {
48
+ selectElement = element.closest('select') as HTMLSelectElement;
49
+ }
50
+
51
+ if (!selectElement) return null;
52
+
53
+ // IMPORTANT: Mark this select element with unique ID for later reference
54
+ selectElement.setAttribute('data-puppeteer-select-id', selectId);
55
+
56
+ // Extract select options
57
+ const options = Array.from(selectElement.options).map((opt, index) => ({
58
+ index,
59
+ value: opt.value || '',
60
+ text: opt.textContent || '',
61
+ selected: opt.selected,
62
+ disabled: opt.disabled
63
+ }));
64
+
65
+ // Get bounding box
66
+ const rect = selectElement.getBoundingClientRect();
67
+ const boundingBox = {
68
+ x: rect.left,
69
+ y: rect.top,
70
+ width: rect.width,
71
+ height: rect.height
72
+ };
73
+
74
+ return {
75
+ options,
76
+ selectedIndex: selectElement.selectedIndex,
77
+ boundingBox
78
+ };
79
+ }, { x, y, selectId });
80
+
81
+ if (!selectData) return null;
82
+ const selectInfo: any = {
83
+ sessionId, // Internal use only, converted to tabId at previewService layer
84
+ selectId,
85
+ x,
86
+ y,
87
+ boundingBox: selectData.boundingBox,
88
+ options: selectData.options,
89
+ selectedIndex: selectData.selectedIndex,
90
+ timestamp: Date.now()
91
+ };
92
+
93
+ debug.log('preview', `📋 Select element detected at (${x}, ${y}) with ${selectData.options.length} options`);
94
+ return selectInfo;
95
+ } catch (error) {
96
+ debug.error('preview', 'Error checking for select element:', error);
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Handle select option selection from frontend
103
+ */
104
+ async handleSelectResponse(page: Page, response: BrowserSelectResponse): Promise<boolean> {
105
+ const { selectId, selectedIndex } = response;
106
+
107
+ try {
108
+ // Update the select value in the page
109
+ const result = await page.evaluate((params) => {
110
+ const { selectId, index } = params;
111
+
112
+ // Find the select element by the unique ID we set earlier
113
+ const selectElement = document.querySelector(`select[data-puppeteer-select-id="${selectId}"]`) as HTMLSelectElement;
114
+
115
+ if (!selectElement) {
116
+ console.error(`Select element with ID ${selectId} not found`);
117
+ return false;
118
+ }
119
+
120
+ if (index < 0 || index >= selectElement.options.length) {
121
+ console.error(`Invalid option index: ${index}`);
122
+ return false;
123
+ }
124
+
125
+ // Update selected index
126
+ selectElement.selectedIndex = index;
127
+
128
+ // Trigger change event
129
+ const changeEvent = new Event('change', { bubbles: true });
130
+ selectElement.dispatchEvent(changeEvent);
131
+
132
+ // Trigger input event for React/Vue compatibility
133
+ const inputEvent = new Event('input', { bubbles: true });
134
+ selectElement.dispatchEvent(inputEvent);
135
+
136
+ // Clean up the tracking attribute
137
+ selectElement.removeAttribute('data-puppeteer-select-id');
138
+
139
+ return true;
140
+ }, { selectId, index: selectedIndex });
141
+
142
+ if (result) {
143
+ debug.log('preview', `✅ Select option updated to index: ${selectedIndex}`);
144
+ } else {
145
+ debug.warn('preview', `⚠️ Failed to update select option to index: ${selectedIndex}`);
146
+ }
147
+
148
+ return result;
149
+ } catch (error) {
150
+ debug.error('preview', 'Error handling select response:', error);
151
+ return false;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Check element at coordinates and build context menu
157
+ */
158
+ async checkForContextMenu(sessionId: string, page: Page, x: number, y: number): Promise<BrowserContextMenuInfo | null> {
159
+ try {
160
+ const contextData = await page.evaluate((coordinates) => {
161
+ const { x, y } = coordinates;
162
+ const element = document.elementFromPoint(x, y);
163
+
164
+ if (!element) return null;
165
+
166
+ // Get element information
167
+ const tagName = element.tagName;
168
+ const isLink = element.tagName === 'A' || element.closest('a') !== null;
169
+ const isImage = element.tagName === 'IMG';
170
+ const isInput = element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';
171
+
172
+ // Check for text selection
173
+ const selection = window.getSelection();
174
+ const isTextSelected = selection ? selection.toString().length > 0 : false;
175
+
176
+ // Get link URL if it's a link
177
+ let linkUrl: string | undefined;
178
+ if (isLink) {
179
+ const linkElement = element.tagName === 'A' ? element as HTMLAnchorElement : element.closest('a') as HTMLAnchorElement;
180
+ linkUrl = linkElement?.href;
181
+ }
182
+
183
+ // Get image URL if it's an image
184
+ let imageUrl: string | undefined;
185
+ if (isImage) {
186
+ imageUrl = (element as HTMLImageElement).src;
187
+ }
188
+
189
+ // Get input type
190
+ let inputType: string | undefined;
191
+ if (isInput && element.tagName === 'INPUT') {
192
+ inputType = (element as HTMLInputElement).type;
193
+ }
194
+
195
+ return {
196
+ tagName,
197
+ isLink,
198
+ isImage,
199
+ isInput,
200
+ isTextSelected,
201
+ linkUrl,
202
+ imageUrl,
203
+ inputType
204
+ };
205
+ }, { x, y });
206
+
207
+ if (!contextData) return null;
208
+
209
+ // Build context menu items based on element type
210
+ const items = this.buildContextMenuItems(contextData);
211
+
212
+ const menuId = nanoid(10);
213
+ const menuInfo: any = {
214
+ sessionId, // Internal use only, converted to tabId at previewService layer
215
+ menuId,
216
+ x,
217
+ y,
218
+ items,
219
+ elementInfo: contextData,
220
+ timestamp: Date.now()
221
+ };
222
+
223
+ debug.log('preview', `📜 Context menu requested at (${x}, ${y}) for element: ${contextData.tagName}`);
224
+ return menuInfo;
225
+ } catch (error) {
226
+ debug.error('preview', 'Error checking for context menu:', error);
227
+ return null;
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Build context menu items based on element type
233
+ */
234
+ private buildContextMenuItems(elementInfo: any): BrowserContextMenuItem[] {
235
+ const items: BrowserContextMenuItem[] = [];
236
+
237
+ // Back / Forward
238
+ items.push(
239
+ { id: 'back', label: 'Back', enabled: true },
240
+ { id: 'forward', label: 'Forward', enabled: true },
241
+ { id: 'reload', label: 'Reload', enabled: true },
242
+ { id: 'separator-1', label: '', enabled: false, type: 'separator' }
243
+ );
244
+
245
+ // Link-specific actions
246
+ if (elementInfo.isLink && elementInfo.linkUrl) {
247
+ items.push(
248
+ { id: 'open-link-new-tab', label: 'Open Link in New Tab', enabled: true },
249
+ { id: 'copy-link', label: 'Copy Link Address', enabled: true },
250
+ { id: 'separator-2', label: '', enabled: false, type: 'separator' }
251
+ );
252
+ }
253
+
254
+ // Image-specific actions
255
+ if (elementInfo.isImage && elementInfo.imageUrl) {
256
+ items.push(
257
+ { id: 'open-image-new-tab', label: 'Open Image in New Tab', enabled: true },
258
+ { id: 'save-image', label: 'Save Image As...', enabled: true },
259
+ { id: 'copy-image', label: 'Copy Image', enabled: true },
260
+ { id: 'copy-image-address', label: 'Copy Image Address', enabled: true },
261
+ { id: 'separator-3', label: '', enabled: false, type: 'separator' }
262
+ );
263
+ }
264
+
265
+ // Text selection actions
266
+ if (elementInfo.isTextSelected) {
267
+ items.push(
268
+ { id: 'copy', label: 'Copy', enabled: true },
269
+ { id: 'separator-4', label: '', enabled: false, type: 'separator' }
270
+ );
271
+ }
272
+
273
+ // Input-specific actions
274
+ if (elementInfo.isInput) {
275
+ items.push(
276
+ { id: 'cut', label: 'Cut', enabled: true },
277
+ { id: 'copy', label: 'Copy', enabled: elementInfo.isTextSelected },
278
+ { id: 'paste', label: 'Paste', enabled: true },
279
+ { id: 'separator-5', label: '', enabled: false, type: 'separator' }
280
+ );
281
+ }
282
+
283
+ return items;
284
+ }
285
+
286
+ /**
287
+ * Fetch image from page and emit download event with base64 data
288
+ */
289
+ private async downloadImageFromPage(page: Page, imageUrl: string): Promise<void> {
290
+ try {
291
+ debug.log('preview', `💾 Fetching image for download: ${imageUrl}`);
292
+
293
+ // Fetch image as base64 using page.evaluate
294
+ const imageData = await page.evaluate(async (url) => {
295
+ try {
296
+ const response = await fetch(url);
297
+ const blob = await response.blob();
298
+
299
+ return new Promise<{ base64: string, type: string, filename: string }>((resolve, reject) => {
300
+ const reader = new FileReader();
301
+ reader.onloadend = () => {
302
+ const base64 = reader.result as string;
303
+ // Extract filename from URL
304
+ const urlParts = url.split('/');
305
+ let filename = urlParts[urlParts.length - 1].split('?')[0] || 'image';
306
+
307
+ // If no extension, add one based on blob type
308
+ if (!filename.includes('.')) {
309
+ const ext = blob.type.split('/')[1] || 'png';
310
+ filename = `image.${ext}`;
311
+ }
312
+
313
+ resolve({
314
+ base64: base64.split(',')[1], // Remove data:image/png;base64, prefix
315
+ type: blob.type,
316
+ filename
317
+ });
318
+ };
319
+ reader.onerror = reject;
320
+ reader.readAsDataURL(blob);
321
+ });
322
+ } catch (error) {
323
+ throw new Error(`Failed to fetch image: ${error}`);
324
+ }
325
+ }, imageUrl);
326
+
327
+ // Emit event with image data to frontend
328
+ this.emit('download-image', imageData);
329
+ debug.log('preview', `✅ Image data sent for download: ${imageData.filename}`);
330
+ } catch (error) {
331
+ debug.error('preview', '❌ Failed to fetch image for download:', error);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Paste text to element at coordinates
337
+ */
338
+ private async pasteTextToPage(page: Page, x: number, y: number, text: string): Promise<void> {
339
+ try {
340
+ debug.log('preview', `📋 Pasting text to element at (${x}, ${y})`);
341
+
342
+ await page.evaluate((params) => {
343
+ const { x, y, text } = params;
344
+ const element = document.elementFromPoint(x, y) as HTMLElement;
345
+
346
+ if (element) {
347
+ // If it's an input element, insert text at cursor position
348
+ if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
349
+ const input = element as HTMLInputElement | HTMLTextAreaElement;
350
+ const start = input.selectionStart || 0;
351
+ const end = input.selectionEnd || 0;
352
+ const currentValue = input.value;
353
+
354
+ // Insert text at cursor position
355
+ input.value = currentValue.substring(0, start) + text + currentValue.substring(end);
356
+
357
+ // Move cursor to end of inserted text
358
+ const newCursorPos = start + text.length;
359
+ input.setSelectionRange(newCursorPos, newCursorPos);
360
+
361
+ // Trigger input event for React/Vue compatibility
362
+ const inputEvent = new Event('input', { bubbles: true });
363
+ input.dispatchEvent(inputEvent);
364
+
365
+ // Trigger change event
366
+ const changeEvent = new Event('change', { bubbles: true });
367
+ input.dispatchEvent(changeEvent);
368
+ } else if (element.isContentEditable) {
369
+ // For contenteditable elements
370
+ document.execCommand('insertText', false, text);
371
+ }
372
+ }
373
+ }, { x, y, text });
374
+
375
+ debug.log('preview', `✅ Pasted ${text.length} characters successfully`);
376
+ } catch (error) {
377
+ debug.error('preview', '❌ Failed to paste text:', error);
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Fetch image from page and emit copy event with base64 data
383
+ */
384
+ private async copyImageFromPage(page: Page, imageUrl: string): Promise<void> {
385
+ try {
386
+ debug.log('preview', `📋 Fetching image for clipboard: ${imageUrl}`);
387
+
388
+ // Fetch image as base64 using page.evaluate
389
+ const imageData = await page.evaluate(async (url) => {
390
+ try {
391
+ const response = await fetch(url);
392
+ const blob = await response.blob();
393
+
394
+ return new Promise<{ base64: string, type: string }>((resolve, reject) => {
395
+ const reader = new FileReader();
396
+ reader.onloadend = () => {
397
+ const base64 = reader.result as string;
398
+ resolve({
399
+ base64: base64.split(',')[1], // Remove data:image/png;base64, prefix
400
+ type: blob.type
401
+ });
402
+ };
403
+ reader.onerror = reject;
404
+ reader.readAsDataURL(blob);
405
+ });
406
+ } catch (error) {
407
+ throw new Error(`Failed to fetch image: ${error}`);
408
+ }
409
+ }, imageUrl);
410
+
411
+ // Emit event with image data to frontend
412
+ this.emit('copy-image-to-clipboard', imageData);
413
+ debug.log('preview', `✅ Image data sent for clipboard`);
414
+ } catch (error) {
415
+ debug.error('preview', '❌ Failed to fetch image for clipboard:', error);
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Handle context menu action from frontend
421
+ */
422
+ async handleContextMenuResponse(page: Page, response: BrowserContextMenuResponse, menuInfo: BrowserContextMenuInfo, clipboardText?: string): Promise<boolean> {
423
+ const { itemId } = response;
424
+
425
+ try {
426
+ debug.log('preview', `🎯 Executing context menu action: ${itemId}`);
427
+
428
+ switch (itemId) {
429
+ case 'back':
430
+ await page.goBack();
431
+ break;
432
+
433
+ case 'forward':
434
+ await page.goForward();
435
+ break;
436
+
437
+ case 'reload':
438
+ await page.reload();
439
+ break;
440
+
441
+ case 'copy':
442
+ await page.evaluate(() => {
443
+ document.execCommand('copy');
444
+ });
445
+ break;
446
+
447
+ case 'cut':
448
+ await page.evaluate(() => {
449
+ document.execCommand('cut');
450
+ });
451
+ break;
452
+
453
+ case 'paste':
454
+ // Paste clipboard content to the page
455
+ if (clipboardText !== undefined) {
456
+ await this.pasteTextToPage(page, menuInfo.x, menuInfo.y, clipboardText);
457
+ } else {
458
+ debug.warn('preview', '⚠️ No clipboard text provided for paste action');
459
+ }
460
+ break;
461
+
462
+ case 'copy-link':
463
+ if (menuInfo.elementInfo.linkUrl) {
464
+ // Emit event to frontend to copy to clipboard (can't access clipboard from backend)
465
+ this.emit('copy-to-clipboard', { text: menuInfo.elementInfo.linkUrl });
466
+ }
467
+ break;
468
+
469
+ case 'copy-image-address':
470
+ if (menuInfo.elementInfo.imageUrl) {
471
+ this.emit('copy-to-clipboard', { text: menuInfo.elementInfo.imageUrl });
472
+ }
473
+ break;
474
+
475
+ case 'open-link-new-tab':
476
+ if (menuInfo.elementInfo.linkUrl) {
477
+ this.emit('open-url-new-tab', { url: menuInfo.elementInfo.linkUrl });
478
+ }
479
+ break;
480
+
481
+ case 'open-image-new-tab':
482
+ if (menuInfo.elementInfo.imageUrl) {
483
+ this.emit('open-url-new-tab', { url: menuInfo.elementInfo.imageUrl });
484
+ }
485
+ break;
486
+
487
+ case 'save-image':
488
+ if (menuInfo.elementInfo.imageUrl) {
489
+ // Fetch image from page and send to frontend
490
+ await this.downloadImageFromPage(page, menuInfo.elementInfo.imageUrl);
491
+ }
492
+ break;
493
+
494
+ case 'copy-image':
495
+ if (menuInfo.elementInfo.imageUrl) {
496
+ // Fetch image from page and send to frontend
497
+ await this.copyImageFromPage(page, menuInfo.elementInfo.imageUrl);
498
+ }
499
+ break;
500
+
501
+ default:
502
+ debug.warn('preview', `⚠️ Unknown context menu action: ${itemId}`);
503
+ return false;
504
+ }
505
+
506
+ return true;
507
+ } catch (error) {
508
+ debug.error('preview', `Error handling context menu action ${itemId}:`, error);
509
+ return false;
510
+ }
511
+ }
512
+ }
@@ -0,0 +1,104 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { Page, HTTPRequest, Frame } from 'puppeteer';
3
+ import type { BrowserTab } from './types';
4
+
5
+ export class BrowserNavigationTracker extends EventEmitter {
6
+ constructor() {
7
+ super();
8
+ }
9
+
10
+ async setupNavigationTracking(sessionId: string, page: Page, session: BrowserTab) {
11
+
12
+ // Track navigation start (loading begins)
13
+ page.on('request', (request: HTTPRequest) => {
14
+ // Only track main frame document requests (not resources like images, CSS, etc.)
15
+ // Puppeteer uses resourceType() instead of isNavigationRequest()
16
+ if (request.resourceType() === 'document' && request.frame() === page.mainFrame()) {
17
+ const targetUrl = request.url();
18
+
19
+ // Emit navigation loading event to frontend
20
+ this.emit('navigation-loading', {
21
+ sessionId,
22
+ type: 'navigation-loading',
23
+ url: targetUrl,
24
+ timestamp: Date.now()
25
+ });
26
+ }
27
+ });
28
+
29
+ // Track all navigation events - including redirects, link clicks, and hash changes
30
+ page.on('framenavigated', (frame: Frame) => {
31
+ // Only track main frame navigation (not iframes)
32
+ if (frame === page.mainFrame()) {
33
+ const newUrl = frame.url();
34
+
35
+ // Update session URL
36
+ session.url = newUrl;
37
+
38
+ // Emit navigation completed event to frontend
39
+ this.emit('navigation', {
40
+ sessionId,
41
+ type: 'navigation',
42
+ url: newUrl,
43
+ timestamp: Date.now()
44
+ });
45
+ }
46
+ });
47
+
48
+ // Also track URL changes via JavaScript (for single page applications)
49
+ page.on('load', async () => {
50
+ const currentUrl = page.url();
51
+ if (currentUrl !== session.url) {
52
+
53
+ session.url = currentUrl;
54
+
55
+ this.emit('navigation', {
56
+ sessionId,
57
+ type: 'navigation',
58
+ url: currentUrl,
59
+ timestamp: Date.now()
60
+ });
61
+ }
62
+ });
63
+
64
+ // Track hash changes (fragment identifier changes like #contact-us)
65
+ await page.evaluateOnNewDocument(() => {
66
+ let lastUrl = window.location.href;
67
+
68
+ // Monitor for hash changes and other URL changes
69
+ const checkUrlChange = () => {
70
+ const currentUrl = window.location.href;
71
+ if (currentUrl !== lastUrl) {
72
+ lastUrl = currentUrl;
73
+
74
+ // Store the new URL for the backend to detect
75
+ (window as any).__urlChanged = {
76
+ url: currentUrl,
77
+ timestamp: Date.now()
78
+ };
79
+ }
80
+ };
81
+
82
+ // Listen to various events that might change URL
83
+ window.addEventListener('hashchange', checkUrlChange);
84
+ window.addEventListener('popstate', checkUrlChange);
85
+
86
+ // Periodically check for URL changes (for SPA navigation)
87
+ setInterval(checkUrlChange, 500);
88
+ });
89
+ }
90
+
91
+ async navigateSession(sessionId: string, session: BrowserTab, url: string): Promise<string> {
92
+
93
+ await session.page.goto(url);
94
+
95
+ // Get the final URL after any redirects
96
+ const finalUrl = session.page.url();
97
+ session.url = finalUrl;
98
+
99
+ // Update current URL tracking
100
+ session.currentUrl = finalUrl;
101
+
102
+ return finalUrl;
103
+ }
104
+ }