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,110 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { Desktop } from '@/components/desktop/Desktop';
5
+ import { DesktopBackground } from '@/components/desktop/DesktopBackground';
6
+ import { ShortcutViewer } from '@/components/desktop/ShortcutViewer';
7
+ import { WindowRenderer } from '@/components/desktop/WindowRenderer';
8
+ import { WindowSwitcher } from '@/components/desktop/WindowSwitcher';
9
+ import { NotificationCenter } from '@/components/notifications/NotificationCenter';
10
+ import { NotificationToasts } from '@/components/notifications/NotificationToasts';
11
+ import { OrphanSessionToast } from '@/components/notifications/OrphanSessionToast';
12
+ import { Taskbar } from '@/components/taskbar/Taskbar';
13
+ import { useKhalAuth } from '@/lib/auth/use-auth';
14
+ import { useDesktopNats } from '@/lib/hooks/use-desktop-nats';
15
+ import { useNatsNotifications } from '@/lib/hooks/use-nats-notifications';
16
+ import { useGlobalKeybinds } from '@/lib/keyboard/use-global-keybinds';
17
+ import { useWindowStore } from '@/stores/window-store';
18
+
19
+ /** Fallback workspace ID for unauthenticated / loading state. */
20
+ const LOCAL_WORKSPACE_ID = 'local';
21
+
22
+ function useDocumentTitle() {
23
+ useEffect(() => {
24
+ document.title = 'Khal';
25
+ }, []);
26
+ }
27
+
28
+ /**
29
+ * Derives the workspace key from auth state.
30
+ * Returns `orgId:userId` when authenticated, or `'local'` as fallback.
31
+ */
32
+ function getWorkspaceId(auth: { orgId: string; userId: string } | null): string {
33
+ if (!auth || !auth.userId) return LOCAL_WORKSPACE_ID;
34
+ return `${auth.orgId}:${auth.userId}`;
35
+ }
36
+
37
+ interface DesktopShellProps {
38
+ /** Role resolved server-side via withAuth(), normalized to canonical slug. */
39
+ serverRole: string;
40
+ /** Permissions resolved server-side via withAuth(). */
41
+ serverPermissions: string[];
42
+ }
43
+
44
+ export function DesktopShell(_props: DesktopShellProps) {
45
+ useDocumentTitle();
46
+ useNatsNotifications();
47
+ useDesktopNats();
48
+ const { switcher, launcherToggle, shortcutViewerVisible, setShortcutViewerVisible } = useGlobalKeybinds();
49
+
50
+ const auth = useKhalAuth();
51
+
52
+ // SSR hydration guard: only render after client-side mount
53
+ const [mounted, setMounted] = useState(false);
54
+
55
+ // Bootstrap: set the workspace from auth
56
+ const bootstrappedRef = useRef(false);
57
+ useEffect(() => {
58
+ setMounted(true);
59
+ }, []);
60
+
61
+ // Set workspace when auth resolves (or changes)
62
+ useEffect(() => {
63
+ if (!mounted) return;
64
+ if (auth?.loading) return;
65
+
66
+ const workspaceId = getWorkspaceId(auth);
67
+ const store = useWindowStore.getState();
68
+
69
+ // Migrate: if 'local' workspace has data and the user's workspace doesn't, copy it
70
+ if (workspaceId !== LOCAL_WORKSPACE_ID && !bootstrappedRef.current) {
71
+ const localWindows = store.windowsByWorkspace[LOCAL_WORKSPACE_ID];
72
+ const userWindows = store.windowsByWorkspace[workspaceId];
73
+
74
+ if (localWindows && localWindows.length > 0 && (!userWindows || userWindows.length === 0)) {
75
+ store.loadWindowState(workspaceId, localWindows);
76
+ }
77
+ // Clear local workspace after migration to prevent re-copying on every refresh
78
+ if (localWindows && localWindows.length > 0) {
79
+ store.loadWindowState(LOCAL_WORKSPACE_ID, []);
80
+ }
81
+ }
82
+
83
+ bootstrappedRef.current = true;
84
+ store.setActiveWorkspace(workspaceId);
85
+ }, [mounted, auth?.loading, auth?.userId, auth?.orgId]);
86
+
87
+ // Prevent hydration mismatch by not rendering until mounted
88
+ // Also show loading state while auth is resolving
89
+ if (!mounted || auth?.loading) {
90
+ return (
91
+ <div className="h-screen w-screen overflow-hidden">
92
+ <DesktopBackground />
93
+ </div>
94
+ );
95
+ }
96
+
97
+ return (
98
+ <div className="h-screen w-screen overflow-hidden">
99
+ <DesktopBackground />
100
+ <Desktop />
101
+ <WindowRenderer />
102
+ <Taskbar launcherToggle={launcherToggle} />
103
+ <NotificationToasts />
104
+ <NotificationCenter />
105
+ <OrphanSessionToast />
106
+ <WindowSwitcher visible={switcher.visible} windows={switcher.windows} selectedIndex={switcher.selectedIndex} />
107
+ <ShortcutViewer visible={shortcutViewerVisible} onClose={() => setShortcutViewerVisible(false)} />
108
+ </div>
109
+ );
110
+ }
@@ -0,0 +1,8 @@
1
+ export default function DesktopLayout({ children }: { children: React.ReactNode }) {
2
+ return (
3
+ <>
4
+ <style>{':root { --os-html-bg: var(--os-bg-primary, var(--ds-background-100, #0a0a0a)); }'}</style>
5
+ {children}
6
+ </>
7
+ );
8
+ }
@@ -0,0 +1,24 @@
1
+ import { withAuth } from '@workos-inc/authkit-nextjs';
2
+ import { normalizeRole } from '@/lib/auth/roles';
3
+ import { DesktopShell } from './desktop-shell';
4
+
5
+ /**
6
+ * Server component that bridges WorkOS session data to the client.
7
+ *
8
+ * `withAuth()` runs server-side and returns { user, role, permissions, organizationId }.
9
+ * The `useAuth()` client hook doesn't expose role/permissions, so this server component
10
+ * is the bridge that passes them as props to the client-side DesktopShell.
11
+ *
12
+ * HeadlessChrome bypass: when middleware skips auth (d3k monitoring), withAuth()
13
+ * returns { user: null } — we fall back to platform-owner for machine access.
14
+ */
15
+ export default async function DesktopPage() {
16
+ const session = await withAuth();
17
+
18
+ // HeadlessChrome bypass: no user means d3k monitoring mode
19
+ const isMachine = !session.user;
20
+ const serverRole = isMachine ? 'platform-owner' : normalizeRole(session.role);
21
+ const serverPermissions = isMachine ? [] : (session.permissions ?? []);
22
+
23
+ return <DesktopShell serverRole={serverRole} serverPermissions={serverPermissions} />;
24
+ }
Binary file
@@ -0,0 +1,7 @@
1
+ @layer theme, base, components, utilities;
2
+
3
+ @import "tailwindcss/theme.css" layer(theme);
4
+ @import "tailwindcss/preflight.css" layer(base);
5
+ @import "tailwindcss/utilities.css" layer(utilities);
6
+
7
+ @import "../theme/index.css";
@@ -0,0 +1,64 @@
1
+ import { AuthKitProvider } from '@workos-inc/authkit-nextjs/components';
2
+ import type { Metadata, Viewport } from 'next';
3
+ import { Geist, Geist_Mono, JetBrains_Mono } from 'next/font/google';
4
+ import { ThemeProvider } from '@/components/ui/theme-provider';
5
+ import './globals.css';
6
+
7
+ const geistSans = Geist({
8
+ variable: '--font-geist-sans',
9
+ subsets: ['latin'],
10
+ });
11
+
12
+ const geistMono = Geist_Mono({
13
+ variable: '--font-geist-mono',
14
+ subsets: ['latin'],
15
+ });
16
+
17
+ const jetbrainsMono = JetBrains_Mono({
18
+ variable: '--font-jetbrains-mono',
19
+ subsets: ['latin'],
20
+ weight: ['400', '500', '600', '700'],
21
+ });
22
+
23
+ export const metadata: Metadata = {
24
+ title: 'Khal',
25
+ description: 'Desktop-in-browser OS shell',
26
+ };
27
+
28
+ export const viewport: Viewport = {
29
+ width: 'device-width',
30
+ initialScale: 1,
31
+ maximumScale: 1,
32
+ userScalable: false,
33
+ viewportFit: 'cover',
34
+ interactiveWidget: 'resizes-content',
35
+ };
36
+
37
+ export default function RootLayout({
38
+ children,
39
+ }: Readonly<{
40
+ children: React.ReactNode;
41
+ }>) {
42
+ return (
43
+ <html
44
+ lang="en"
45
+ suppressHydrationWarning
46
+ className="tailwind !min-h-screen"
47
+ style={{ background: 'var(--os-html-bg, transparent)' }}
48
+ >
49
+ <body className={`${geistSans.variable} ${geistMono.variable} ${jetbrainsMono.variable} antialiased`}>
50
+ <AuthKitProvider>
51
+ <ThemeProvider
52
+ attribute="class"
53
+ storageKey="khal-theme-mode"
54
+ defaultTheme="light"
55
+ enableSystem
56
+ disableTransitionOnChange
57
+ >
58
+ {children}
59
+ </ThemeProvider>
60
+ </AuthKitProvider>
61
+ </body>
62
+ </html>
63
+ );
64
+ }
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+
3
+ export default function OfflinePage() {
4
+ return (
5
+ <div
6
+ style={{
7
+ height: '100svh',
8
+ width: '100vw',
9
+ display: 'flex',
10
+ flexDirection: 'column',
11
+ alignItems: 'center',
12
+ justifyContent: 'center',
13
+ background: '#0a0f0a',
14
+ color: '#e0e0e0',
15
+ fontFamily: "'JetBrains Mono', ui-monospace, monospace",
16
+ gap: '24px',
17
+ }}
18
+ >
19
+ <svg
20
+ xmlns="http://www.w3.org/2000/svg"
21
+ width="80"
22
+ height="80"
23
+ viewBox="0 0 24 24"
24
+ fill="#39ff14"
25
+ stroke="none"
26
+ aria-hidden="true"
27
+ >
28
+ <path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" />
29
+ </svg>
30
+
31
+ <h1
32
+ style={{
33
+ margin: 0,
34
+ fontSize: '2rem',
35
+ fontWeight: 700,
36
+ color: '#39ff14',
37
+ letterSpacing: '0.05em',
38
+ }}
39
+ >
40
+ Khal
41
+ </h1>
42
+
43
+ <p
44
+ style={{
45
+ margin: 0,
46
+ fontSize: '1rem',
47
+ color: '#a0a0a0',
48
+ textAlign: 'center',
49
+ maxWidth: '360px',
50
+ lineHeight: 1.6,
51
+ }}
52
+ >
53
+ Server is unreachable. Start the services and reload.
54
+ </p>
55
+
56
+ <button
57
+ onClick={() => window.location.reload()}
58
+ style={{
59
+ marginTop: '8px',
60
+ padding: '10px 28px',
61
+ background: 'transparent',
62
+ border: '2px solid #39ff14',
63
+ borderRadius: '6px',
64
+ color: '#39ff14',
65
+ fontFamily: "'JetBrains Mono', ui-monospace, monospace",
66
+ fontSize: '0.9rem',
67
+ fontWeight: 600,
68
+ letterSpacing: '0.08em',
69
+ cursor: 'pointer',
70
+ transition: 'background 0.15s',
71
+ }}
72
+ onMouseEnter={(e) => {
73
+ (e.currentTarget as HTMLButtonElement).style.background = 'rgba(57, 255, 20, 0.12)';
74
+ }}
75
+ onMouseLeave={(e) => {
76
+ (e.currentTarget as HTMLButtonElement).style.background = 'transparent';
77
+ }}
78
+ >
79
+ Reload
80
+ </button>
81
+ </div>
82
+ );
83
+ }
@@ -0,0 +1,5 @@
1
+ import { redirect } from 'next/navigation';
2
+
3
+ export default function Home() {
4
+ redirect('/desktop');
5
+ }
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+ import { useParams } from 'next/navigation';
3
+ import { APP_COMPONENTS, APP_REGISTRY, type AppComponentProps } from '@/components/apps/app-registry';
4
+ import { StandaloneFrame } from '@/components/window/StandaloneFrame';
5
+
6
+ export default function StandalonePage() {
7
+ const params = useParams<{ appId: string }>();
8
+ const appId = params.appId;
9
+ const AppComponent = APP_COMPONENTS[appId as keyof typeof APP_COMPONENTS] as
10
+ | React.ComponentType<AppComponentProps>
11
+ | undefined;
12
+ const registryEntry = APP_REGISTRY[appId as keyof typeof APP_REGISTRY];
13
+ const title = registryEntry?.label ?? appId;
14
+
15
+ if (!AppComponent) {
16
+ return (
17
+ <div style={{ padding: 40, color: '#ef4444' }}>
18
+ App not found: <code>{appId}</code>
19
+ </div>
20
+ );
21
+ }
22
+
23
+ return (
24
+ <StandaloneFrame appId={appId} title={title}>
25
+ <AppComponent windowId={`standalone-${appId}`} />
26
+ </StandaloneFrame>
27
+ );
28
+ }
@@ -0,0 +1,10 @@
1
+ export default function StandaloneLayout({ children }: { children: React.ReactNode }) {
2
+ return (
3
+ <>
4
+ <style>
5
+ {':root { --os-html-bg: transparent !important; } html, body { background: transparent !important; }'}
6
+ </style>
7
+ {children}
8
+ </>
9
+ );
10
+ }
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+
3
+ import { useResolvedIcon } from '@/lib/icons/resolve-window-icon';
4
+
5
+ interface AppIconProps {
6
+ appId: string;
7
+ size?: number;
8
+ className?: string;
9
+ }
10
+
11
+ export function AppIcon({ appId, size = 16, className }: AppIconProps) {
12
+ const iconUrl = useResolvedIcon(appId);
13
+
14
+ if (!iconUrl) return null;
15
+
16
+ return (
17
+ <img
18
+ src={iconUrl}
19
+ alt=""
20
+ width={size}
21
+ height={size}
22
+ className={className ?? `h-[${size}px] w-[${size}px] shrink-0`}
23
+ style={{ width: size, height: size }}
24
+ />
25
+ );
26
+ }
27
+
28
+ /**
29
+ * Variant that renders a letter-initial fallback when no icon is available.
30
+ */
31
+ export function AppIconWithFallback({ appId, title, size = 16, className }: AppIconProps & { title: string }) {
32
+ const iconUrl = useResolvedIcon(appId);
33
+
34
+ if (iconUrl) {
35
+ return (
36
+ <img
37
+ src={iconUrl}
38
+ alt=""
39
+ width={size}
40
+ height={size}
41
+ className={className ?? 'shrink-0 object-contain'}
42
+ style={{ width: size, height: size }}
43
+ />
44
+ );
45
+ }
46
+
47
+ return (
48
+ <div
49
+ className="flex items-center justify-center rounded-lg bg-gray-alpha-200 font-medium text-gray-900 shrink-0"
50
+ style={{ width: size, height: size, fontSize: size * 0.4 }}
51
+ >
52
+ {title.charAt(0).toUpperCase()}
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,14 @@
1
+ import { Static, Type } from '@sinclair/typebox';
2
+
3
+ export const EchoRequest = Type.Object({
4
+ message: Type.Optional(Type.String()),
5
+ });
6
+ export type EchoRequest = Static<typeof EchoRequest>;
7
+
8
+ export const EchoResponse = Type.Object({
9
+ ts: Type.String(),
10
+ service: Type.Literal('echo'),
11
+ uptime: Type.Number(),
12
+ echo: Type.Optional(Type.String()),
13
+ });
14
+ export type EchoResponse = Static<typeof EchoResponse>;
@@ -0,0 +1,42 @@
1
+ import { connect } from '@nats-io/transport-node';
2
+ import type { EchoRequest, EchoResponse } from '../schema';
3
+
4
+ const startTime = Date.now();
5
+
6
+ async function main() {
7
+ const nc = await connect({
8
+ servers: process.env.NATS_URL || 'nats://localhost:4222',
9
+ });
10
+
11
+ console.log('[echo] connected to NATS, subscribing to khal.*.echo');
12
+
13
+ const sub = nc.subscribe('khal.*.echo');
14
+
15
+ for await (const msg of sub) {
16
+ let request: EchoRequest = {};
17
+ try {
18
+ if (msg.data.length > 0) {
19
+ request = msg.json<EchoRequest>();
20
+ }
21
+ } catch {
22
+ // empty or invalid payload is fine, echo without message
23
+ }
24
+
25
+ const response: EchoResponse = {
26
+ ts: new Date().toISOString(),
27
+ service: 'echo',
28
+ uptime: (Date.now() - startTime) / 1000,
29
+ ...(request.message != null ? { echo: request.message } : {}),
30
+ };
31
+
32
+ console.log('[echo] request:', request, '-> response:', response);
33
+ msg.respond(JSON.stringify(response));
34
+ }
35
+
36
+ await nc.closed();
37
+ }
38
+
39
+ main().catch((err) => {
40
+ console.error('[echo] fatal:', err);
41
+ process.exit(1);
42
+ });
@@ -0,0 +1,97 @@
1
+ /**
2
+ * App manifest — pure data, no React imports.
3
+ *
4
+ * Each app declares its metadata here. This is the single source of truth
5
+ * for app identity, permissions, NATS subject prefixes, and access levels.
6
+ *
7
+ * Importable by both the UI (app-registry.ts) and the WS bridge (Bun process).
8
+ */
9
+
10
+ /** Role hierarchy from least to most privileged (forward-compatible with Phase 2 multi-org). */
11
+ export const ROLE_HIERARCHY = ['member', 'platform-dev', 'platform-admin', 'platform-owner'] as const;
12
+ export type Role = (typeof ROLE_HIERARCHY)[number];
13
+
14
+ export interface AppManifestEntry {
15
+ label: string;
16
+ /** Permission string required to use this app. */
17
+ permission: string;
18
+ /** Minimum role required to access this app. */
19
+ minRole: Role;
20
+ /** NATS subject segment after `khal.<orgId>.` — if the app uses NATS subjects. */
21
+ natsPrefix?: string;
22
+ defaultSize: { width: number; height: number };
23
+ /**
24
+ * When true, the window skips the normal WindowFrame/WindowContent wrapper.
25
+ * The app renders its entire surface including the title bar area.
26
+ * Floating window controls (min/max/close) overlay on top.
27
+ */
28
+ fullSizeContent?: boolean;
29
+ }
30
+
31
+ export type AppId = 'terminal' | 'settings' | 'files' | 'nats-viewer' | 'dev3000';
32
+
33
+ export const APP_MANIFEST: Record<AppId, AppManifestEntry> = {
34
+ terminal: {
35
+ label: 'Terminal',
36
+ permission: 'terminal',
37
+ minRole: 'platform-dev',
38
+ natsPrefix: 'pty',
39
+ defaultSize: { width: 720, height: 480 },
40
+ fullSizeContent: true,
41
+ },
42
+ settings: {
43
+ label: 'Settings',
44
+ permission: 'settings',
45
+ minRole: 'platform-dev',
46
+ defaultSize: { width: 800, height: 600 },
47
+ },
48
+ files: {
49
+ label: 'Files',
50
+ permission: 'files',
51
+ minRole: 'member',
52
+ natsPrefix: 'fs',
53
+ defaultSize: { width: 800, height: 600 },
54
+ },
55
+ 'nats-viewer': {
56
+ label: 'NATS Viewer',
57
+ permission: 'nats-viewer',
58
+ minRole: 'platform-dev',
59
+ defaultSize: { width: 900, height: 600 },
60
+ },
61
+ dev3000: {
62
+ label: 'dev3000',
63
+ permission: 'dev3000',
64
+ minRole: 'platform-dev',
65
+ natsPrefix: 'd3k',
66
+ defaultSize: { width: 960, height: 640 },
67
+ },
68
+ };
69
+
70
+ /**
71
+ * Derived: NATS subject segment → required permission.
72
+ * Used by the WS bridge for server-side subject-level access control.
73
+ */
74
+ export const SUBJECT_PERMISSIONS: Record<string, string> = {
75
+ ...Object.fromEntries(
76
+ Object.values(APP_MANIFEST)
77
+ .filter((e) => e.natsPrefix)
78
+ .map((e) => [e.natsPrefix, e.permission])
79
+ ),
80
+ desktop: 'desktop',
81
+ };
82
+
83
+ /**
84
+ * Derived: role → list of permissions granted.
85
+ * A role gets access to all apps whose minRole is at or below it in the hierarchy.
86
+ * Used by both client (use-auth.ts) and server (ws-server.ts) for role-based fallback.
87
+ */
88
+ export const DEFAULT_ROLE_PERMISSIONS: Record<string, string[]> = Object.fromEntries(
89
+ ROLE_HIERARCHY.map((role) => {
90
+ const roleLevel = ROLE_HIERARCHY.indexOf(role);
91
+ const permissions = Object.values(APP_MANIFEST)
92
+ .filter((app) => ROLE_HIERARCHY.indexOf(app.minRole) <= roleLevel)
93
+ .map((app) => app.permission);
94
+ // Desktop control is available to all roles
95
+ return [role, [...permissions, 'desktop']];
96
+ })
97
+ );
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+
3
+ import { APP_MANIFEST, type AppId } from './app-manifest';
4
+ import { Dev3000App } from './dev3000';
5
+ import { FilesApp } from './files/FilesApp';
6
+ import { NatsViewer } from './nats-viewer';
7
+ import { Settings } from './settings/Settings';
8
+ import { MultiTerminalApp } from './terminal/ui/MultiTerminalApp';
9
+
10
+ export type { AppId } from './app-manifest';
11
+
12
+ export interface AppComponentProps {
13
+ windowId: string;
14
+ meta?: Record<string, unknown>;
15
+ }
16
+
17
+ export interface AppRegistryEntry {
18
+ component: React.ComponentType<AppComponentProps>;
19
+ label: string;
20
+ defaultSize: { width: number; height: number };
21
+ requiredPermission?: string;
22
+ }
23
+
24
+ /** Map appId → React component. Add new apps here. */
25
+ const APP_COMPONENTS_MAP: Record<AppId, React.ComponentType<AppComponentProps>> = {
26
+ terminal: MultiTerminalApp,
27
+ settings: Settings,
28
+ files: FilesApp,
29
+ 'nats-viewer': NatsViewer,
30
+ dev3000: Dev3000App,
31
+ };
32
+
33
+ /**
34
+ * Central registry derived from APP_MANIFEST + component map.
35
+ * Metadata (label, permission, size) comes from the manifest.
36
+ * Components are added here (can't live in manifest — React dependency).
37
+ */
38
+ export const APP_REGISTRY: Record<AppId, AppRegistryEntry> = Object.fromEntries(
39
+ (Object.keys(APP_MANIFEST) as AppId[]).map((id) => [
40
+ id,
41
+ {
42
+ component: APP_COMPONENTS_MAP[id],
43
+ label: APP_MANIFEST[id].label,
44
+ defaultSize: APP_MANIFEST[id].defaultSize,
45
+ requiredPermission: APP_MANIFEST[id].permission,
46
+ },
47
+ ])
48
+ ) as Record<AppId, AppRegistryEntry>;
49
+
50
+ /**
51
+ * Flat component lookup for backwards compatibility.
52
+ */
53
+ export const APP_COMPONENTS: Record<string, React.ComponentType<AppComponentProps>> = Object.fromEntries(
54
+ Object.entries(APP_REGISTRY).map(([id, entry]) => [id, entry.component])
55
+ );