khal-os 1.260324.2

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 (408) hide show
  1. package/.env.example +23 -0
  2. package/.genie/mailbox/cli-sent.jsonl +3 -0
  3. package/.genie/mailbox/ds1-wave2-engineer-1.json +15 -0
  4. package/.genie/mailbox/ds1-wave2-engineer-2.json +15 -0
  5. package/.genie/mailbox/ds1-wave2-engineer-3.json +15 -0
  6. package/.genie/state/os-observability.json +39 -0
  7. package/.genie/state/tmux-control-mode-terminal.json +28 -0
  8. package/.genie/wishes/genieos-one-theme/WISH.md +417 -0
  9. package/.genie/wishes/workos-prod-rbac/WISH.md +345 -0
  10. package/.github/workflows/ci.yml +39 -0
  11. package/.github/workflows/release.yml +78 -0
  12. package/.github/workflows/version.yml +122 -0
  13. package/.husky/pre-commit +1 -0
  14. package/.pnpm-approve-builds.json +1 -0
  15. package/CLAUDE.md +117 -0
  16. package/LICENSE +21 -0
  17. package/README.md +38 -0
  18. package/biome.json +124 -0
  19. package/bun.lock +1249 -0
  20. package/docs/workos-setup.md +116 -0
  21. package/ecosystem.config.cjs +26 -0
  22. package/instrumentation.ts +8 -0
  23. package/knip.json +35 -0
  24. package/nats.conf +7 -0
  25. package/next.config.ts +25 -0
  26. package/package.json +78 -0
  27. package/packages/dev3000-app/components.ts +12 -0
  28. package/packages/dev3000-app/manifest.ts +19 -0
  29. package/packages/dev3000-app/package.json +23 -0
  30. package/packages/dev3000-app/views/dev3000/Dev3000App.tsx +758 -0
  31. package/packages/dev3000-app/views/dev3000/ErrorsPanel.tsx +160 -0
  32. package/packages/dev3000-app/views/dev3000/dev3000-context.tsx +21 -0
  33. package/packages/dev3000-app/views/dev3000/index.ts +4 -0
  34. package/packages/dev3000-app/views/dev3000/schema.ts +55 -0
  35. package/packages/dev3000-app/views/dev3000/service/index.ts +358 -0
  36. package/packages/dev3000-app/views/dev3000/service/runtime +1 -0
  37. package/packages/dev3000-app/views/dev3000/subjects.ts +9 -0
  38. package/packages/dev3000-app/views/dev3000/types.ts +77 -0
  39. package/packages/files-app/components.ts +12 -0
  40. package/packages/files-app/manifest.ts +19 -0
  41. package/packages/files-app/package.json +23 -0
  42. package/packages/files-app/views/files/ContextMenu.tsx +151 -0
  43. package/packages/files-app/views/files/DeleteConfirmDialog.tsx +39 -0
  44. package/packages/files-app/views/files/FileItem.tsx +128 -0
  45. package/packages/files-app/views/files/FilesApp.tsx +509 -0
  46. package/packages/files-app/views/files/FilesListView.tsx +201 -0
  47. package/packages/files-app/views/files/FilesToolbar.tsx +117 -0
  48. package/packages/files-app/views/files/GridView.tsx +90 -0
  49. package/packages/files-app/views/files/InlineInput.tsx +131 -0
  50. package/packages/files-app/views/files/UploadOverlay.tsx +61 -0
  51. package/packages/files-app/views/files/schema.ts +49 -0
  52. package/packages/files-app/views/files/service/index.ts +184 -0
  53. package/packages/files-app/views/files/service/runtime +1 -0
  54. package/packages/files-app/views/files/use-files.ts +201 -0
  55. package/packages/files-app/views/files/use-upload.ts +105 -0
  56. package/packages/genie-app/components.ts +12 -0
  57. package/packages/genie-app/lib/subjects.ts +87 -0
  58. package/packages/genie-app/manifest.ts +19 -0
  59. package/packages/genie-app/package.json +29 -0
  60. package/packages/genie-app/views/genie/service/agent-lifecycle.ts +136 -0
  61. package/packages/genie-app/views/genie/service/cli.ts +114 -0
  62. package/packages/genie-app/views/genie/service/comms.ts +141 -0
  63. package/packages/genie-app/views/genie/service/directory.ts +167 -0
  64. package/packages/genie-app/views/genie/service/index.ts +219 -0
  65. package/packages/genie-app/views/genie/service/system.ts +123 -0
  66. package/packages/genie-app/views/genie/service/teams.ts +191 -0
  67. package/packages/genie-app/views/genie/service/terminal-proxy.ts +184 -0
  68. package/packages/genie-app/views/genie/service/tmux-control.ts +318 -0
  69. package/packages/genie-app/views/genie/service/wishes.ts +270 -0
  70. package/packages/genie-app/views/genie/ui/GenieApp.tsx +5 -0
  71. package/packages/genie-app/views/genie/ui/PaneCard.tsx +307 -0
  72. package/packages/genie-app/views/genie/ui/Sidebar.tsx +212 -0
  73. package/packages/genie-app/views/genie/ui/TabBar.tsx +70 -0
  74. package/packages/genie-app/views/genie/ui/WorkspaceCanvas.tsx +343 -0
  75. package/packages/genie-app/views/genie/ui/XTermPane.tsx +306 -0
  76. package/packages/genie-app/views/genie/ui/hooks/useNatsAction.ts +54 -0
  77. package/packages/genie-app/views/genie/ui/hooks/useNatsRequest.ts +68 -0
  78. package/packages/genie-app/views/genie/ui/panels/AgentsPanel.tsx +399 -0
  79. package/packages/genie-app/views/genie/ui/panels/ChatPanel.tsx +351 -0
  80. package/packages/genie-app/views/genie/ui/panels/SystemPanel.tsx +195 -0
  81. package/packages/genie-app/views/genie/ui/panels/TeamsPanel.tsx +560 -0
  82. package/packages/genie-app/views/genie/ui/panels/WishesPanel.tsx +424 -0
  83. package/packages/nats-viewer-app/components.ts +12 -0
  84. package/packages/nats-viewer-app/manifest.ts +18 -0
  85. package/packages/nats-viewer-app/package.json +14 -0
  86. package/packages/nats-viewer-app/views/nats-viewer/ActiveSubs.tsx +34 -0
  87. package/packages/nats-viewer-app/views/nats-viewer/MessageLog.tsx +247 -0
  88. package/packages/nats-viewer-app/views/nats-viewer/NatsViewer.tsx +209 -0
  89. package/packages/nats-viewer-app/views/nats-viewer/PublishPanel.tsx +111 -0
  90. package/packages/nats-viewer-app/views/nats-viewer/RequestPanel.tsx +165 -0
  91. package/packages/nats-viewer-app/views/nats-viewer/Sidebar.tsx +59 -0
  92. package/packages/nats-viewer-app/views/nats-viewer/SubjectCatalog.tsx +63 -0
  93. package/packages/nats-viewer-app/views/nats-viewer/SubscribeInput.tsx +59 -0
  94. package/packages/nats-viewer-app/views/nats-viewer/index.ts +5 -0
  95. package/packages/nats-viewer-app/views/nats-viewer/nats-viewer-context.tsx +31 -0
  96. package/packages/nats-viewer-app/views/nats-viewer/types.ts +7 -0
  97. package/packages/nats-viewer-app/views/nats-viewer/use-message-buffer.ts +55 -0
  98. package/packages/os-cli/package.json +18 -0
  99. package/packages/os-cli/src/commands/events.ts +176 -0
  100. package/packages/os-cli/src/commands/logs.ts +96 -0
  101. package/packages/os-cli/src/commands/status.ts +53 -0
  102. package/packages/os-cli/src/commands/traces.ts +115 -0
  103. package/packages/os-cli/src/index.ts +15 -0
  104. package/packages/os-cli/src/lib/formatter.ts +123 -0
  105. package/packages/os-cli/src/lib/nats.ts +16 -0
  106. package/packages/os-cli/src/lib/trace-tree.ts +144 -0
  107. package/packages/os-cli/tsconfig.json +12 -0
  108. package/packages/os-sdk/package.json +27 -0
  109. package/packages/os-sdk/src/api/handler.ts +67 -0
  110. package/packages/os-sdk/src/config.ts +68 -0
  111. package/packages/os-sdk/src/db/factory.test.ts +42 -0
  112. package/packages/os-sdk/src/db/factory.ts +72 -0
  113. package/packages/os-sdk/src/db/migrate.ts +140 -0
  114. package/packages/os-sdk/src/db/provision.ts +44 -0
  115. package/packages/os-sdk/src/index.ts +36 -0
  116. package/packages/os-sdk/src/service/console-intercept.ts +60 -0
  117. package/packages/os-sdk/src/service/logger.ts +88 -0
  118. package/packages/os-sdk/src/service/o11y-streams.ts +88 -0
  119. package/packages/os-sdk/src/service/runtime.ts +259 -0
  120. package/packages/os-sdk/src/service/trace.ts +71 -0
  121. package/packages/os-sdk/tsconfig.json +16 -0
  122. package/packages/os-ui/package.json +13 -0
  123. package/packages/os-ui/src/index.ts +29 -0
  124. package/packages/os-ui/src/server.ts +4 -0
  125. package/packages/os-ui/tsconfig.json +19 -0
  126. package/packages/settings-app/components.ts +12 -0
  127. package/packages/settings-app/manifest.ts +18 -0
  128. package/packages/settings-app/package.json +14 -0
  129. package/packages/settings-app/views/settings/Settings.tsx +492 -0
  130. package/packages/terminal-app/components.ts +12 -0
  131. package/packages/terminal-app/manifest.ts +20 -0
  132. package/packages/terminal-app/package.json +23 -0
  133. package/packages/terminal-app/views/terminal/schema.ts +82 -0
  134. package/packages/terminal-app/views/terminal/service/index.ts +133 -0
  135. package/packages/terminal-app/views/terminal/service/runtime +1 -0
  136. package/packages/terminal-app/views/terminal/service/session.ts +290 -0
  137. package/packages/terminal-app/views/terminal/service/shell-hooks/bashrc-hook.sh +21 -0
  138. package/packages/terminal-app/views/terminal/types.ts +26 -0
  139. package/packages/terminal-app/views/terminal/ui/MultiTerminalApp.tsx +615 -0
  140. package/packages/terminal-app/views/terminal/ui/SplitDragHandle.tsx +91 -0
  141. package/packages/terminal-app/views/terminal/ui/SplitPaneRenderer.tsx +112 -0
  142. package/packages/terminal-app/views/terminal/ui/TerminalPane.tsx +478 -0
  143. package/packages/terminal-app/views/terminal/ui/TerminalTabBar.tsx +131 -0
  144. package/pnpm-workspace.yaml +9 -0
  145. package/postcss.config.mjs +7 -0
  146. package/public/file.svg +1 -0
  147. package/public/globe.svg +1 -0
  148. package/public/icons/code-server.svg +6 -0
  149. package/public/icons/default.svg +5 -0
  150. package/public/icons/dusk/1password.svg +1 -0
  151. package/public/icons/dusk/activity_monitor.svg +1 -0
  152. package/public/icons/dusk/app_store.svg +1 -0
  153. package/public/icons/dusk/atom.svg +1 -0
  154. package/public/icons/dusk/brave.svg +1 -0
  155. package/public/icons/dusk/calculator.svg +1 -0
  156. package/public/icons/dusk/calendar.svg +1 -0
  157. package/public/icons/dusk/chrome.svg +1 -0
  158. package/public/icons/dusk/chrome2.svg +1 -0
  159. package/public/icons/dusk/dashboard.svg +13 -0
  160. package/public/icons/dusk/discord.svg +1 -0
  161. package/public/icons/dusk/dropbox.svg +1 -0
  162. package/public/icons/dusk/electron.svg +1 -0
  163. package/public/icons/dusk/figma.svg +1 -0
  164. package/public/icons/dusk/finder.svg +1 -0
  165. package/public/icons/dusk/finder2.svg +1 -0
  166. package/public/icons/dusk/finder3.svg +1 -0
  167. package/public/icons/dusk/firefox.svg +1 -0
  168. package/public/icons/dusk/framer.svg +1 -0
  169. package/public/icons/dusk/gimp.svg +1 -0
  170. package/public/icons/dusk/github_desktop.svg +1 -0
  171. package/public/icons/dusk/hyper.svg +1 -0
  172. package/public/icons/dusk/hyper3.svg +1 -0
  173. package/public/icons/dusk/intellij.svg +1 -0
  174. package/public/icons/dusk/iterm2.svg +1 -0
  175. package/public/icons/dusk/itunes.svg +1 -0
  176. package/public/icons/dusk/mail.svg +1 -0
  177. package/public/icons/dusk/messenger.svg +1 -0
  178. package/public/icons/dusk/mongodb.svg +1 -0
  179. package/public/icons/dusk/notes.svg +1 -0
  180. package/public/icons/dusk/notion.svg +1 -0
  181. package/public/icons/dusk/obs.svg +1 -0
  182. package/public/icons/dusk/pages.svg +1 -0
  183. package/public/icons/dusk/photos.svg +1 -0
  184. package/public/icons/dusk/postman.svg +1 -0
  185. package/public/icons/dusk/preview.svg +1 -0
  186. package/public/icons/dusk/reminders.svg +1 -0
  187. package/public/icons/dusk/safari.svg +1 -0
  188. package/public/icons/dusk/sequel_pro.svg +1 -0
  189. package/public/icons/dusk/sketch.svg +1 -0
  190. package/public/icons/dusk/skype.svg +1 -0
  191. package/public/icons/dusk/slack.svg +1 -0
  192. package/public/icons/dusk/slack2.svg +1 -0
  193. package/public/icons/dusk/spotify.svg +1 -0
  194. package/public/icons/dusk/steam.svg +1 -0
  195. package/public/icons/dusk/system_preferences.svg +1 -0
  196. package/public/icons/dusk/tableplus.svg +1 -0
  197. package/public/icons/dusk/teams.svg +1 -0
  198. package/public/icons/dusk/telegram.svg +1 -0
  199. package/public/icons/dusk/terminal.svg +1 -0
  200. package/public/icons/dusk/todoist.svg +1 -0
  201. package/public/icons/dusk/trash.svg +1 -0
  202. package/public/icons/dusk/trello.svg +1 -0
  203. package/public/icons/dusk/vivaldi.svg +1 -0
  204. package/public/icons/dusk/vlc.svg +1 -0
  205. package/public/icons/dusk/vscode.svg +1 -0
  206. package/public/icons/dusk/whatsapp.svg +1 -0
  207. package/public/icons/dusk/xeyes.svg +1 -0
  208. package/public/icons/dusk/zoom.svg +1 -0
  209. package/public/icons/files.svg +5 -0
  210. package/public/icons/pwa/icon-192.png +0 -0
  211. package/public/icons/pwa/icon-512.png +0 -0
  212. package/public/icons/settings.svg +14 -0
  213. package/public/icons/terminal.svg +5 -0
  214. package/public/icons/text-editor.svg +7 -0
  215. package/public/manifest.json +38 -0
  216. package/public/next.svg +1 -0
  217. package/public/sw.js +41 -0
  218. package/public/vercel.svg +1 -0
  219. package/public/wallpapers/default.svg +10 -0
  220. package/public/window.svg +1 -0
  221. package/scripts/generate-pwa-icons.mjs +33 -0
  222. package/scripts/install-nats.sh +37 -0
  223. package/sentry.client.config.ts +21 -0
  224. package/sentry.edge.config.ts +12 -0
  225. package/sentry.server.config.ts +12 -0
  226. package/src/app/api/files/download/route.ts +81 -0
  227. package/src/app/api/files/download-zip/route.ts +102 -0
  228. package/src/app/api/files/upload/route.ts +58 -0
  229. package/src/app/api/webhooks/workos/route.ts +98 -0
  230. package/src/app/auth/callback/route.ts +16 -0
  231. package/src/app/auth/logout/route.ts +15 -0
  232. package/src/app/desktop/desktop-shell.tsx +110 -0
  233. package/src/app/desktop/layout.tsx +8 -0
  234. package/src/app/desktop/page.tsx +24 -0
  235. package/src/app/favicon.ico +0 -0
  236. package/src/app/globals.css +7 -0
  237. package/src/app/layout.tsx +64 -0
  238. package/src/app/offline/page.tsx +83 -0
  239. package/src/app/page.tsx +5 -0
  240. package/src/app/standalone/[appId]/page.tsx +28 -0
  241. package/src/app/standalone/layout.tsx +10 -0
  242. package/src/components/app-icon.tsx +55 -0
  243. package/src/components/apps/_echo/schema.ts +14 -0
  244. package/src/components/apps/_echo/service/index.ts +42 -0
  245. package/src/components/apps/app-manifest.ts +97 -0
  246. package/src/components/apps/app-registry.ts +55 -0
  247. package/src/components/apps/dev3000/Dev3000App.tsx +224 -0
  248. package/src/components/apps/dev3000/ErrorsPanel.tsx +160 -0
  249. package/src/components/apps/dev3000/Sidebar.tsx +41 -0
  250. package/src/components/apps/dev3000/TimelineLog.tsx +173 -0
  251. package/src/components/apps/dev3000/dev3000-context.tsx +29 -0
  252. package/src/components/apps/dev3000/index.ts +4 -0
  253. package/src/components/apps/dev3000/schema.ts +48 -0
  254. package/src/components/apps/dev3000/service/index.ts +520 -0
  255. package/src/components/apps/dev3000/service/runtime +1 -0
  256. package/src/components/apps/dev3000/types.ts +15 -0
  257. package/src/components/apps/dev3000/use-message-buffer.ts +46 -0
  258. package/src/components/apps/files/ContextMenu.tsx +151 -0
  259. package/src/components/apps/files/DeleteConfirmDialog.tsx +78 -0
  260. package/src/components/apps/files/FileItem.tsx +128 -0
  261. package/src/components/apps/files/FilesApp.tsx +509 -0
  262. package/src/components/apps/files/FilesListView.tsx +201 -0
  263. package/src/components/apps/files/FilesToolbar.tsx +117 -0
  264. package/src/components/apps/files/GridView.tsx +90 -0
  265. package/src/components/apps/files/InlineInput.tsx +131 -0
  266. package/src/components/apps/files/UploadOverlay.tsx +61 -0
  267. package/src/components/apps/files/schema.ts +49 -0
  268. package/src/components/apps/files/service/index.ts +227 -0
  269. package/src/components/apps/files/service/runtime +1 -0
  270. package/src/components/apps/files/use-files.ts +201 -0
  271. package/src/components/apps/files/use-upload.ts +105 -0
  272. package/src/components/apps/nats-viewer/ActiveSubs.tsx +34 -0
  273. package/src/components/apps/nats-viewer/MessageLog.tsx +247 -0
  274. package/src/components/apps/nats-viewer/NatsViewer.tsx +209 -0
  275. package/src/components/apps/nats-viewer/PublishPanel.tsx +113 -0
  276. package/src/components/apps/nats-viewer/RequestPanel.tsx +167 -0
  277. package/src/components/apps/nats-viewer/Sidebar.tsx +62 -0
  278. package/src/components/apps/nats-viewer/SubjectCatalog.tsx +64 -0
  279. package/src/components/apps/nats-viewer/SubscribeInput.tsx +59 -0
  280. package/src/components/apps/nats-viewer/index.ts +5 -0
  281. package/src/components/apps/nats-viewer/nats-viewer-context.tsx +31 -0
  282. package/src/components/apps/nats-viewer/types.ts +7 -0
  283. package/src/components/apps/nats-viewer/use-message-buffer.ts +55 -0
  284. package/src/components/apps/settings/Settings.tsx +492 -0
  285. package/src/components/apps/terminal/schema.ts +82 -0
  286. package/src/components/apps/terminal/service/index.ts +189 -0
  287. package/src/components/apps/terminal/service/runtime +1 -0
  288. package/src/components/apps/terminal/service/session.ts +296 -0
  289. package/src/components/apps/terminal/service/shell-hooks/bashrc-hook.sh +21 -0
  290. package/src/components/apps/terminal/types.ts +26 -0
  291. package/src/components/apps/terminal/ui/MultiTerminalApp.tsx +617 -0
  292. package/src/components/apps/terminal/ui/SplitDragHandle.tsx +91 -0
  293. package/src/components/apps/terminal/ui/SplitPaneRenderer.tsx +112 -0
  294. package/src/components/apps/terminal/ui/TerminalPane.tsx +476 -0
  295. package/src/components/apps/terminal/ui/TerminalTabBar.tsx +131 -0
  296. package/src/components/desktop/AnimatedBackground.tsx +69 -0
  297. package/src/components/desktop/Desktop.tsx +79 -0
  298. package/src/components/desktop/DesktopBackground.tsx +16 -0
  299. package/src/components/desktop/DesktopIcon.tsx +49 -0
  300. package/src/components/desktop/ShortcutViewer.tsx +136 -0
  301. package/src/components/desktop/WindowRenderer.tsx +34 -0
  302. package/src/components/desktop/WindowSwitcher.tsx +42 -0
  303. package/src/components/notifications/NotificationCenter.tsx +153 -0
  304. package/src/components/notifications/NotificationToasts.tsx +66 -0
  305. package/src/components/notifications/OrphanSessionToast.tsx +293 -0
  306. package/src/components/os-primitives/collapsible-sidebar.tsx +226 -0
  307. package/src/components/os-primitives/dialog.tsx +76 -0
  308. package/src/components/os-primitives/empty-state.tsx +43 -0
  309. package/src/components/os-primitives/index.ts +21 -0
  310. package/src/components/os-primitives/list-view.tsx +155 -0
  311. package/src/components/os-primitives/property-panel.tsx +108 -0
  312. package/src/components/os-primitives/section-header.tsx +19 -0
  313. package/src/components/os-primitives/sidebar-nav.tsx +110 -0
  314. package/src/components/os-primitives/split-pane.tsx +146 -0
  315. package/src/components/os-primitives/status-badge.tsx +10 -0
  316. package/src/components/os-primitives/status-bar.tsx +100 -0
  317. package/src/components/os-primitives/toolbar.tsx +152 -0
  318. package/src/components/taskbar/AppLauncher.tsx +114 -0
  319. package/src/components/taskbar/RunningApps.tsx +71 -0
  320. package/src/components/taskbar/SystemTray.tsx +134 -0
  321. package/src/components/taskbar/Taskbar.tsx +45 -0
  322. package/src/components/taskbar/UserMenu.tsx +138 -0
  323. package/src/components/taskbar/WorkspaceSwitcher.tsx +9 -0
  324. package/src/components/ui/ContextMenu.tsx +130 -0
  325. package/src/components/ui/badge.tsx +39 -0
  326. package/src/components/ui/button.tsx +102 -0
  327. package/src/components/ui/command.tsx +165 -0
  328. package/src/components/ui/dropdown-menu.tsx +233 -0
  329. package/src/components/ui/input.tsx +48 -0
  330. package/src/components/ui/note.tsx +55 -0
  331. package/src/components/ui/separator.tsx +25 -0
  332. package/src/components/ui/spinner.tsx +42 -0
  333. package/src/components/ui/switch.tsx +36 -0
  334. package/src/components/ui/theme-provider.tsx +24 -0
  335. package/src/components/ui/theme-switcher.tsx +51 -0
  336. package/src/components/ui/tooltip.tsx +62 -0
  337. package/src/components/window/MobileWindowStack.tsx +218 -0
  338. package/src/components/window/SnapPreview.tsx +37 -0
  339. package/src/components/window/StandaloneFrame.tsx +170 -0
  340. package/src/components/window/Window.tsx +423 -0
  341. package/src/components/window/WindowContent.tsx +14 -0
  342. package/src/components/window/WindowControlsOverlay.tsx +89 -0
  343. package/src/components/window/WindowFrame.tsx +124 -0
  344. package/src/lib/auth/index.ts +27 -0
  345. package/src/lib/auth/roles.ts +50 -0
  346. package/src/lib/auth/types.ts +32 -0
  347. package/src/lib/auth/use-auth.ts +53 -0
  348. package/src/lib/auth/webhook-handler.ts +87 -0
  349. package/src/lib/auth/workos.ts +67 -0
  350. package/src/lib/constants.ts +1 -0
  351. package/src/lib/desktop/dedup.ts +57 -0
  352. package/src/lib/desktop/schema.ts +55 -0
  353. package/src/lib/files/filename-validation.ts +41 -0
  354. package/src/lib/files/safe-path.ts +49 -0
  355. package/src/lib/hooks/use-desktop-nats.ts +438 -0
  356. package/src/lib/hooks/use-is-mobile.ts +23 -0
  357. package/src/lib/hooks/use-launch-app.ts +79 -0
  358. package/src/lib/hooks/use-nats-notifications.ts +84 -0
  359. package/src/lib/hooks/use-nats.ts +60 -0
  360. package/src/lib/hooks/use-visual-viewport.ts +72 -0
  361. package/src/lib/icons/resolve-window-icon.ts +10 -0
  362. package/src/lib/keyboard/defaults.ts +146 -0
  363. package/src/lib/keyboard/types.ts +52 -0
  364. package/src/lib/keyboard/use-global-keybinds.ts +231 -0
  365. package/src/lib/nats-client.ts +255 -0
  366. package/src/lib/nats.ts +35 -0
  367. package/src/lib/notifications/schema.ts +12 -0
  368. package/src/lib/service-loader.ts +171 -0
  369. package/src/lib/subjects.ts +64 -0
  370. package/src/lib/utils.ts +6 -0
  371. package/src/lib/ws-bridge.ts +288 -0
  372. package/src/lib/ws-protocol.ts +53 -0
  373. package/src/lib/ws-server.ts +167 -0
  374. package/src/middleware.ts +57 -0
  375. package/src/stores/desktop-store.ts +112 -0
  376. package/src/stores/keybind-store.ts +66 -0
  377. package/src/stores/notification-store.ts +271 -0
  378. package/src/stores/theme-store.ts +25 -0
  379. package/src/stores/window-store.ts +294 -0
  380. package/src/theme/animations.css +68 -0
  381. package/src/theme/base.css +123 -0
  382. package/src/theme/controls.css +35 -0
  383. package/src/theme/design-tokens.css +276 -0
  384. package/src/theme/index.css +23 -0
  385. package/src/theme/menus.css +45 -0
  386. package/src/theme/status.css +41 -0
  387. package/src/theme/surfaces.css +94 -0
  388. package/src/theme/tailwind-map.css +138 -0
  389. package/src/theme/taskbar.css +25 -0
  390. package/src/theme/terminal.css +55 -0
  391. package/src/theme/typography.css +26 -0
  392. package/src/theme/utilities.css +156 -0
  393. package/src/theme/window.css +103 -0
  394. package/src/types/desktop-entry.ts +12 -0
  395. package/src/types/use-descendants.d.ts +13 -0
  396. package/src/types/window.ts +28 -0
  397. package/src/types.d.ts +9 -0
  398. package/tauri/Cargo.lock +5464 -0
  399. package/tauri/Cargo.toml +19 -0
  400. package/tauri/build.rs +3 -0
  401. package/tauri/capabilities/default.json +36 -0
  402. package/tauri/icons/128x128.png +0 -0
  403. package/tauri/icons/128x128@2x.png +0 -0
  404. package/tauri/icons/32x32.png +0 -0
  405. package/tauri/icons/icon.png +0 -0
  406. package/tauri/src/main.rs +396 -0
  407. package/tauri/tauri.conf.json +23 -0
  408. package/tsconfig.json +43 -0
@@ -0,0 +1,160 @@
1
+ 'use client';
2
+
3
+ import { AlertTriangle, CheckCircle, Loader2, RefreshCw } from 'lucide-react';
4
+ import { useCallback, useState } from 'react';
5
+ import { EmptyState } from '@/components/os-primitives/empty-state';
6
+ import { useDev3000 } from './dev3000-context';
7
+
8
+ type ErrorsState = 'idle' | 'loading' | 'success' | 'error';
9
+
10
+ interface D3kErrorsResponse {
11
+ total: number;
12
+ showing: number;
13
+ errors: string[];
14
+ }
15
+
16
+ interface ParsedError {
17
+ key: string;
18
+ time: string;
19
+ source: string;
20
+ message: string;
21
+ }
22
+
23
+ // Parse d3k log line: "[HH:mm:ss.SSS] [SOURCE] [TAG] message"
24
+ const LOG_LINE_RE = /^\[(\d{2}:\d{2}:\d{2}\.\d{3})]\s+\[(\w+)]\s+(.*)$/;
25
+
26
+ function parseErrors(raw: string): { errors: ParsedError[]; total: number } {
27
+ try {
28
+ const parsed: D3kErrorsResponse = JSON.parse(raw);
29
+ const errors = (parsed.errors || []).map((line, i) => {
30
+ const match = line.match(LOG_LINE_RE);
31
+ if (match) {
32
+ return { key: `${match[1]}-${i}`, time: match[1], source: match[2], message: match[3] };
33
+ }
34
+ return { key: `raw-${i}`, time: '', source: '', message: line };
35
+ });
36
+ return { errors, total: parsed.total || 0 };
37
+ } catch {
38
+ return { errors: [{ key: 'raw-0', time: '', source: '', message: raw }], total: 0 };
39
+ }
40
+ }
41
+
42
+ function ErrorRow({ error }: { error: ParsedError }) {
43
+ return (
44
+ <div className="px-3 py-2 text-[11px] transition-colors hover:bg-gray-alpha-50">
45
+ <div className="flex items-baseline gap-2">
46
+ {error.time && <span className="shrink-0 font-mono text-gray-600">{error.time}</span>}
47
+ {error.source && (
48
+ <span className="shrink-0 rounded bg-red-500/10 px-1 py-0.5 font-mono text-[9px] font-semibold text-red-600">
49
+ {error.source}
50
+ </span>
51
+ )}
52
+ </div>
53
+ <p className="mt-0.5 font-mono leading-4 text-gray-1000">{error.message}</p>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ export function ErrorsPanel() {
59
+ const { runCommand } = useDev3000();
60
+ const [state, setState] = useState<ErrorsState>('idle');
61
+ const [errors, setErrors] = useState<ParsedError[]>([]);
62
+ const [total, setTotal] = useState(0);
63
+ const [errorMsg, setErrorMsg] = useState('');
64
+
65
+ const fetchErrors = useCallback(async () => {
66
+ setState('loading');
67
+ setErrors([]);
68
+ setErrorMsg('');
69
+ const result = await runCommand('errors', []).catch((err) => ({
70
+ ok: false as const,
71
+ error: (err as Error).message,
72
+ output: undefined as string | undefined,
73
+ }));
74
+ if (result.ok && result.output) {
75
+ const parsed = parseErrors(result.output);
76
+ setErrors(parsed.errors);
77
+ setTotal(parsed.total);
78
+ setState('success');
79
+ } else if (result.ok) {
80
+ setErrors([]);
81
+ setState('success');
82
+ } else {
83
+ setState('error');
84
+ setErrorMsg(result.error || 'Failed to fetch errors.');
85
+ }
86
+ }, [runCommand]);
87
+
88
+ return (
89
+ <div className="flex h-full flex-col">
90
+ {/* Header with refresh */}
91
+ <div className="flex items-center gap-2 border-b border-gray-alpha-200 px-3 py-2">
92
+ <AlertTriangle className="h-4 w-4 text-red-500" />
93
+ <span className="text-label-13 font-medium text-gray-1000">Errors</span>
94
+ {state === 'success' && <span className="text-[11px] text-gray-600">{total} total</span>}
95
+ <button
96
+ type="button"
97
+ onClick={fetchErrors}
98
+ disabled={state === 'loading'}
99
+ className="ml-auto flex items-center gap-1 rounded px-2 py-1 text-[11px] font-medium text-gray-800 transition-colors hover:bg-gray-alpha-200 disabled:opacity-50"
100
+ >
101
+ {state === 'loading' ? <Loader2 className="h-3 w-3 animate-spin" /> : <RefreshCw className="h-3 w-3" />}
102
+ {state === 'loading' ? 'Fetching...' : 'Refresh'}
103
+ </button>
104
+ </div>
105
+
106
+ {/* Content */}
107
+ <div className="flex-1 overflow-auto">
108
+ {state === 'idle' && (
109
+ <div className="p-3">
110
+ <EmptyState
111
+ title="Click to fetch errors"
112
+ description="Run d3k errors to see recent browser and server errors."
113
+ action={
114
+ <button
115
+ type="button"
116
+ onClick={fetchErrors}
117
+ className="rounded bg-red-500 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-red-600"
118
+ >
119
+ Fetch Errors
120
+ </button>
121
+ }
122
+ />
123
+ </div>
124
+ )}
125
+
126
+ {state === 'loading' && (
127
+ <div className="flex h-32 items-center justify-center">
128
+ <Loader2 className="h-5 w-5 animate-spin text-gray-600" />
129
+ </div>
130
+ )}
131
+
132
+ {state === 'success' && errors.length === 0 && (
133
+ <div className="p-3">
134
+ <EmptyState
135
+ title="No errors"
136
+ description="No recent errors found in the d3k session."
137
+ icon={<CheckCircle className="h-8 w-8 text-green-500" />}
138
+ />
139
+ </div>
140
+ )}
141
+
142
+ {state === 'success' && errors.length > 0 && (
143
+ <div className="divide-y divide-gray-alpha-100">
144
+ {errors.map((error) => (
145
+ <ErrorRow key={error.key} error={error} />
146
+ ))}
147
+ </div>
148
+ )}
149
+
150
+ {state === 'error' && (
151
+ <div className="p-3">
152
+ <div className="rounded border border-red-200 bg-red-50 p-3">
153
+ <p className="font-mono text-[11px] text-red-600">{errorMsg}</p>
154
+ </div>
155
+ </div>
156
+ )}
157
+ </div>
158
+ </div>
159
+ );
160
+ }
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext } from 'react';
4
+ import type { D3kQaReport, D3kSessionState, D3kTab } from './types';
5
+
6
+ export interface Dev3000ContextValue {
7
+ sessionActive: boolean;
8
+ sessionInfo: D3kSessionState | null;
9
+ lastReport: D3kQaReport | null;
10
+ activeTab: D3kTab;
11
+ setActiveTab: (t: D3kTab) => void;
12
+ runCommand: (cmd: string, args: string[]) => Promise<{ ok: boolean; output?: string; error?: string }>;
13
+ }
14
+
15
+ export const Dev3000Context = createContext<Dev3000ContextValue | null>(null);
16
+
17
+ export function useDev3000(): Dev3000ContextValue {
18
+ const ctx = useContext(Dev3000Context);
19
+ if (!ctx) throw new Error('useDev3000 must be used within <Dev3000App>');
20
+ return ctx;
21
+ }
@@ -0,0 +1,4 @@
1
+ export { Dev3000App } from './Dev3000App';
2
+ export type { Dev3000ContextValue } from './dev3000-context';
3
+ export { useDev3000 } from './dev3000-context';
4
+ export type { D3kQaReport, D3kSessionEvent, D3kSessionState, D3kTab } from './types';
@@ -0,0 +1,55 @@
1
+ import { type Static, Type } from '@sinclair/typebox';
2
+
3
+ // --- Command request/response ---
4
+
5
+ export const D3kCmdRequest = Type.Object({
6
+ command: Type.String(), // e.g. 'session', 'screenshot', 'errors', 'network', 'dom', 'qa', 'health'
7
+ args: Type.Optional(Type.Array(Type.String())),
8
+ });
9
+ export type D3kCmdRequest = Static<typeof D3kCmdRequest>;
10
+
11
+ export const D3kCmdResponse = Type.Object({
12
+ ok: Type.Boolean(),
13
+ output: Type.Optional(Type.String()),
14
+ error: Type.Optional(Type.String()),
15
+ });
16
+ export type D3kCmdResponse = Static<typeof D3kCmdResponse>;
17
+
18
+ // --- Status request/response ---
19
+
20
+ export const D3kStatusRequest = Type.Object({});
21
+ export type D3kStatusRequest = Static<typeof D3kStatusRequest>;
22
+
23
+ export const D3kStatusResponse = Type.Object({
24
+ sessionActive: Type.Boolean(),
25
+ pid: Type.Optional(Type.Number()),
26
+ port: Type.Optional(Type.Number()),
27
+ wsEndpoint: Type.Optional(Type.String()),
28
+ startedAt: Type.Optional(Type.String()),
29
+ uptime: Type.Number(),
30
+ lastReportPath: Type.Optional(Type.String()),
31
+ });
32
+ export type D3kStatusResponse = Static<typeof D3kStatusResponse>;
33
+
34
+ // --- Session event published to NATS ---
35
+
36
+ export const D3kSessionEventSchema = Type.Object({
37
+ type: Type.Union([Type.Literal('started'), Type.Literal('stopped')]),
38
+ pid: Type.Optional(Type.Number()),
39
+ wsEndpoint: Type.Optional(Type.String()),
40
+ port: Type.Optional(Type.Number()),
41
+ timestamp: Type.String(),
42
+ });
43
+ export type D3kSessionEventSchema = Static<typeof D3kSessionEventSchema>;
44
+
45
+ // --- Recording event published to NATS os.dev3000.recording ---
46
+
47
+ export const D3kRecordingEventSchema = Type.Object({
48
+ type: Type.Union([Type.Literal('started'), Type.Literal('chunk'), Type.Literal('stopped'), Type.Literal('error')]),
49
+ command: Type.String(),
50
+ args: Type.Optional(Type.Array(Type.String())),
51
+ output: Type.Optional(Type.String()),
52
+ error: Type.Optional(Type.String()),
53
+ timestamp: Type.String(),
54
+ });
55
+ export type D3kRecordingEventSchema = Static<typeof D3kRecordingEventSchema>;
@@ -0,0 +1,358 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { createService } from '@genie-os/sdk/service';
4
+ import type { D3kCmdRequest, D3kCmdResponse, D3kRecordingEventSchema } from '../schema';
5
+
6
+ const startTime = Date.now();
7
+ const SESSION_FILE = '/tmp/d3k-session.json';
8
+ const REPORTS_DIR = join(process.cwd(), '.d3k', 'reports');
9
+ const CLI_ENTRY = join(process.cwd(), 'src', 'tools', 'd3k', 'cli.ts');
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Session state
13
+ // ---------------------------------------------------------------------------
14
+
15
+ interface SessionState {
16
+ pid: number;
17
+ port: number;
18
+ wsEndpoint: string;
19
+ startedAt: string;
20
+ chromeBin?: string;
21
+ }
22
+
23
+ function isPidAlive(pid: number): boolean {
24
+ try {
25
+ process.kill(pid, 0);
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ function readSessionFile(): SessionState | null {
33
+ try {
34
+ if (!existsSync(SESSION_FILE)) return null;
35
+ const raw = readFileSync(SESSION_FILE, 'utf8');
36
+ const parsed = JSON.parse(raw) as SessionState;
37
+ return isPidAlive(parsed.pid) ? parsed : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // CLI command runner — spawns bun run src/tools/d3k/cli.ts <command> <args>
45
+ // ---------------------------------------------------------------------------
46
+
47
+ async function runCliCommand(command: string, args: string[] = [], timeoutMs = 15_000): Promise<D3kCmdResponse> {
48
+ const { spawn } = await import('node:child_process');
49
+
50
+ return new Promise((resolve) => {
51
+ let resolved = false;
52
+
53
+ const done = (result: D3kCmdResponse) => {
54
+ if (resolved) return;
55
+ resolved = true;
56
+ clearTimeout(timer);
57
+ resolve(result);
58
+ };
59
+
60
+ const child = spawn('bun', ['run', CLI_ENTRY, command, ...args], {
61
+ cwd: process.cwd(),
62
+ env: { ...process.env, FORCE_COLOR: '0' },
63
+ stdio: ['ignore', 'pipe', 'pipe'],
64
+ });
65
+
66
+ const timer = setTimeout(() => {
67
+ child.kill('SIGTERM');
68
+ done({ ok: false, error: `command timed out after ${timeoutMs / 1000}s` });
69
+ }, timeoutMs);
70
+
71
+ let stdout = '';
72
+ let stderr = '';
73
+
74
+ child.stdout?.on('data', (chunk: Buffer) => {
75
+ stdout += chunk.toString();
76
+ });
77
+ child.stderr?.on('data', (chunk: Buffer) => {
78
+ stderr += chunk.toString();
79
+ });
80
+
81
+ child.on('close', (code) => {
82
+ if (code === 0) {
83
+ done({ ok: true, output: stdout.trim() || undefined });
84
+ } else {
85
+ done({ ok: false, error: stderr.trim() || `exit code ${code}`, output: stdout.trim() || undefined });
86
+ }
87
+ });
88
+
89
+ child.on('error', (err) => {
90
+ done({ ok: false, error: err.message });
91
+ });
92
+ });
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Session file watcher
97
+ // ---------------------------------------------------------------------------
98
+
99
+ async function watchSessionFile(
100
+ onStarted: (session: SessionState) => void,
101
+ onStopped: () => void,
102
+ signal: AbortSignal
103
+ ): Promise<void> {
104
+ const { watch } = await import('node:fs/promises');
105
+ const watchDir = SESSION_FILE.substring(0, SESSION_FILE.lastIndexOf('/'));
106
+ const fileName = SESSION_FILE.substring(SESSION_FILE.lastIndexOf('/') + 1);
107
+ let wasActive = readSessionFile() !== null;
108
+
109
+ try {
110
+ const watcher = watch(watchDir, { signal });
111
+ for await (const event of watcher) {
112
+ if (signal.aborted) break;
113
+ if (event.filename !== fileName) continue;
114
+ const session = readSessionFile();
115
+ const isActive = session !== null;
116
+ if (isActive && !wasActive) {
117
+ wasActive = true;
118
+ onStarted(session);
119
+ } else if (!isActive && wasActive) {
120
+ wasActive = false;
121
+ onStopped();
122
+ }
123
+ }
124
+ } catch (err: unknown) {
125
+ if (!signal.aborted) console.error('[d3k-service] session watcher error:', err);
126
+ }
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // Reports directory watcher — extracted helpers to reduce complexity
131
+ // ---------------------------------------------------------------------------
132
+
133
+ function ensureReportsDir(): void {
134
+ try {
135
+ mkdirSync(REPORTS_DIR, { recursive: true });
136
+ } catch {
137
+ // ignore
138
+ }
139
+ }
140
+
141
+ function loadSeenReports(): Set<string> {
142
+ const seen = new Set<string>();
143
+ if (!existsSync(REPORTS_DIR)) return seen;
144
+ try {
145
+ for (const f of readdirSync(REPORTS_DIR)) {
146
+ if (f.endsWith('-qa.json')) seen.add(f);
147
+ }
148
+ } catch {
149
+ // ignore
150
+ }
151
+ return seen;
152
+ }
153
+
154
+ async function watchReportsDir(onNewReport: (reportPath: string) => void, signal: AbortSignal): Promise<void> {
155
+ const { watch } = await import('node:fs/promises');
156
+ ensureReportsDir();
157
+ const seen = loadSeenReports();
158
+
159
+ try {
160
+ const watcher = watch(REPORTS_DIR, { signal });
161
+ for await (const event of watcher) {
162
+ if (signal.aborted) break;
163
+ if (!event.filename?.endsWith('-qa.json')) continue;
164
+ if (seen.has(event.filename)) continue;
165
+ seen.add(event.filename);
166
+ const fullPath = join(REPORTS_DIR, event.filename);
167
+ if (existsSync(fullPath)) onNewReport(fullPath);
168
+ }
169
+ } catch (err: unknown) {
170
+ if (!signal.aborted) console.error('[d3k-service] reports watcher error:', err);
171
+ }
172
+ }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // Status helpers — extracted to reduce complexity of the status handler
176
+ // ---------------------------------------------------------------------------
177
+
178
+ function findLastReportPath(): string | undefined {
179
+ if (!existsSync(REPORTS_DIR)) return undefined;
180
+ try {
181
+ const reports = readdirSync(REPORTS_DIR)
182
+ .filter((f) => f.endsWith('-qa.json'))
183
+ .sort()
184
+ .reverse();
185
+ return reports.length > 0 ? join(REPORTS_DIR, reports[0]) : undefined;
186
+ } catch {
187
+ return undefined;
188
+ }
189
+ }
190
+
191
+ function buildStatusResponse(session: SessionState | null): string {
192
+ return JSON.stringify({
193
+ sessionActive: session !== null,
194
+ pid: session?.pid,
195
+ port: session?.port,
196
+ wsEndpoint: session?.wsEndpoint,
197
+ startedAt: session?.startedAt,
198
+ uptime: (Date.now() - startTime) / 1000,
199
+ lastReportPath: findLastReportPath(),
200
+ });
201
+ }
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // NATS subscription handlers
205
+ // ---------------------------------------------------------------------------
206
+
207
+ function isRecordCommand(command: string): boolean {
208
+ return command === 'record';
209
+ }
210
+
211
+ const SLOW_COMMANDS = new Set(['record', 'qa', 'crawl', 'monitor', 'console', 'network']);
212
+
213
+ function getCommandTimeout(command: string): number {
214
+ return SLOW_COMMANDS.has(command) ? 120_000 : 30_000;
215
+ }
216
+
217
+ type RecordingPublisher = (event: D3kRecordingEventSchema) => void;
218
+
219
+ function buildRecordingStartEvent(request: D3kCmdRequest): D3kRecordingEventSchema {
220
+ return {
221
+ type: 'started',
222
+ command: request.command,
223
+ args: request.args,
224
+ timestamp: new Date().toISOString(),
225
+ };
226
+ }
227
+
228
+ function buildRecordingEndEvent(request: D3kCmdRequest, result: D3kCmdResponse): D3kRecordingEventSchema {
229
+ return {
230
+ type: result.ok ? 'stopped' : 'error',
231
+ command: request.command,
232
+ args: request.args,
233
+ output: result.output,
234
+ error: result.error,
235
+ timestamp: new Date().toISOString(),
236
+ };
237
+ }
238
+
239
+ async function handleCmdMessage(
240
+ msg: { data: Uint8Array; subject: string; json: <T>() => T; respond: (data: string) => void },
241
+ publishRecording: RecordingPublisher
242
+ ): Promise<void> {
243
+ const request: D3kCmdRequest = msg.data.length > 0 ? msg.json<D3kCmdRequest>() : { command: 'health' };
244
+ const timeout = getCommandTimeout(request.command);
245
+ console.log(`[d3k-service] cmd: ${request.command} ${(request.args || []).join(' ')}`);
246
+
247
+ if (isRecordCommand(request.command)) {
248
+ publishRecording(buildRecordingStartEvent(request));
249
+ }
250
+
251
+ const result = await runCliCommand(request.command, request.args ?? [], timeout);
252
+
253
+ if (isRecordCommand(request.command)) {
254
+ publishRecording(buildRecordingEndEvent(request, result));
255
+ }
256
+
257
+ msg.respond(JSON.stringify(result));
258
+ }
259
+
260
+ // ---------------------------------------------------------------------------
261
+ // State shared between onReady and onShutdown
262
+ // ---------------------------------------------------------------------------
263
+
264
+ let abortController: AbortController | null = null;
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // Main
268
+ // ---------------------------------------------------------------------------
269
+
270
+ createService({
271
+ name: 'd3k-service',
272
+ onReady: async (nc) => {
273
+ console.log('[d3k-service] connected to NATS (lightweight mode — no Chrome spawning)');
274
+
275
+ abortController = new AbortController();
276
+
277
+ const publishSessionStarted = (session: SessionState) => {
278
+ const event = JSON.stringify({
279
+ type: 'started',
280
+ pid: session.pid,
281
+ wsEndpoint: session.wsEndpoint,
282
+ port: session.port,
283
+ timestamp: new Date().toISOString(),
284
+ });
285
+ nc.publish('os.dev3000.session', event);
286
+ console.log(`[d3k-service] session started (PID ${session.pid})`);
287
+ };
288
+
289
+ const publishSessionStopped = () => {
290
+ const event = JSON.stringify({ type: 'stopped', timestamp: new Date().toISOString() });
291
+ nc.publish('os.dev3000.session', event);
292
+ console.log('[d3k-service] session stopped');
293
+ };
294
+
295
+ const publishQaReport = (reportPath: string) => {
296
+ try {
297
+ const payload = readFileSync(reportPath, 'utf8');
298
+ nc.publish('os.dev3000.qa.report', payload);
299
+ console.log(`[d3k-service] QA report published: ${reportPath}`);
300
+ } catch (err) {
301
+ console.error('[d3k-service] failed to publish QA report:', err);
302
+ }
303
+ };
304
+
305
+ const publishRecordingEvent = (event: D3kRecordingEventSchema) => {
306
+ nc.publish('os.dev3000.recording', JSON.stringify(event));
307
+ };
308
+
309
+ // --- Start file watchers ---
310
+ watchSessionFile(publishSessionStarted, publishSessionStopped, abortController.signal).catch((err) =>
311
+ console.error('[d3k-service] session watcher fatal:', err)
312
+ );
313
+
314
+ watchReportsDir(publishQaReport, abortController.signal).catch((err) =>
315
+ console.error('[d3k-service] reports watcher fatal:', err)
316
+ );
317
+
318
+ // --- os.dev3000.cmd (request-reply) ---
319
+ const cmdSub = nc.subscribe('os.dev3000.cmd');
320
+ (async () => {
321
+ for await (const msg of cmdSub) {
322
+ try {
323
+ await handleCmdMessage(msg, publishRecordingEvent);
324
+ } catch (err) {
325
+ console.error('[d3k-service] cmd error:', err);
326
+ msg.respond(JSON.stringify({ ok: false, error: (err as Error).message }));
327
+ }
328
+ }
329
+ })();
330
+
331
+ // --- os.dev3000.status (request-reply) ---
332
+ const statusSub = nc.subscribe('os.dev3000.status');
333
+ (async () => {
334
+ for await (const msg of statusSub) {
335
+ try {
336
+ msg.respond(buildStatusResponse(readSessionFile()));
337
+ } catch (err) {
338
+ console.error('[d3k-service] status error:', err);
339
+ msg.respond(JSON.stringify({ error: (err as Error).message }));
340
+ }
341
+ }
342
+ })();
343
+
344
+ const initialSession = readSessionFile();
345
+ if (initialSession) {
346
+ console.log(`[d3k-service] active session detected at startup (PID ${initialSession.pid})`);
347
+ } else {
348
+ console.log('[d3k-service] no active session at startup');
349
+ }
350
+ },
351
+ onShutdown: async () => {
352
+ console.log('[d3k-service] shutting down...');
353
+ abortController?.abort();
354
+ },
355
+ }).catch((err) => {
356
+ console.error('[d3k-service] fatal:', err);
357
+ process.exit(1);
358
+ });
@@ -0,0 +1,9 @@
1
+ const ROOT = 'os.dev3000';
2
+
3
+ export const DEV3000_SUBJECTS = {
4
+ cmd: () => `${ROOT}.cmd`,
5
+ status: () => `${ROOT}.status`,
6
+ session: () => `${ROOT}.session`,
7
+ qaReport: () => `${ROOT}.qa.report`,
8
+ recording: () => `${ROOT}.recording`,
9
+ } as const;
@@ -0,0 +1,77 @@
1
+ export type D3kTab = 'dashboard' | 'errors';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Session
5
+ // ---------------------------------------------------------------------------
6
+
7
+ export interface D3kSessionState {
8
+ active: boolean;
9
+ pid?: number;
10
+ wsEndpoint?: string;
11
+ port?: number;
12
+ startedAt?: string;
13
+ }
14
+
15
+ export interface D3kSessionEvent {
16
+ type: 'started' | 'stopped';
17
+ pid?: number;
18
+ wsEndpoint?: string;
19
+ port?: number;
20
+ timestamp: string;
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // QA Report
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export interface D3kQaRouteErrors {
28
+ exceptions: number;
29
+ console_errors: number;
30
+ }
31
+
32
+ export interface D3kQaRouteNetwork {
33
+ total: number;
34
+ failed: number;
35
+ }
36
+
37
+ export interface D3kQaScreenshot {
38
+ route: string;
39
+ file: string;
40
+ viewport: string;
41
+ }
42
+
43
+ export interface D3kQaDomCheck {
44
+ route: string;
45
+ selector: string;
46
+ check: string;
47
+ pass: boolean;
48
+ }
49
+
50
+ export interface D3kQaSummary {
51
+ pass: boolean;
52
+ routes_passed: number;
53
+ routes_failed: number;
54
+ total_errors: number;
55
+ total_failed_network: number;
56
+ screenshots_taken: number;
57
+ }
58
+
59
+ export interface D3kQaReport {
60
+ timestamp: string;
61
+ duration_ms: number;
62
+ routes_tested: string[];
63
+ screenshots: D3kQaScreenshot[];
64
+ errors: {
65
+ js_exceptions: unknown[];
66
+ console_errors: unknown[];
67
+ by_route: Record<string, D3kQaRouteErrors>;
68
+ };
69
+ network: {
70
+ total_requests: number;
71
+ failed_requests: number;
72
+ slow_requests: unknown[];
73
+ by_route: Record<string, D3kQaRouteNetwork>;
74
+ };
75
+ dom_checks: D3kQaDomCheck[];
76
+ summary: D3kQaSummary;
77
+ }