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,189 @@
1
+ import { connect } from '@nats-io/transport-node';
2
+ import type {
3
+ PtyCreateRequest,
4
+ PtyDestroyRequest,
5
+ PtyInputMessage,
6
+ PtyListRequest,
7
+ PtyReplayRequest,
8
+ PtyResizeRequest,
9
+ } from '../schema';
10
+ import { createSessionManager } from './session';
11
+
12
+ async function main() {
13
+ const nc = await connect({
14
+ servers: process.env.NATS_URL || 'nats://localhost:4222',
15
+ });
16
+
17
+ console.log('[pty-service] connected to NATS, subscribing to khal.*.pty.*');
18
+
19
+ const sessionManager = createSessionManager(nc);
20
+
21
+ // --- khal.*.pty.create (request-reply, wildcard for any orgId) ---
22
+ const createSub = nc.subscribe('khal.*.pty.create');
23
+ (async () => {
24
+ for await (const msg of createSub) {
25
+ try {
26
+ const orgId = msg.subject.split('.')[1];
27
+ let request: PtyCreateRequest & { _authUserId?: string } = {};
28
+ if (msg.data.length > 0) {
29
+ request = msg.json<PtyCreateRequest & { _authUserId?: string }>();
30
+ }
31
+ const userId = request._authUserId || '';
32
+
33
+ // If sessionId provided and session exists, reattach (no buffer replay yet -- client requests it after subscribing)
34
+ if (request.sessionId && sessionManager.hasSession(request.sessionId)) {
35
+ if (!userId || !sessionManager.isOwner(request.sessionId, userId)) {
36
+ console.warn(
37
+ `[pty-service] reattach denied: user ${userId || '<empty>'} does not own session ${request.sessionId}`
38
+ );
39
+ msg.respond(JSON.stringify({ error: 'ownership check failed' }));
40
+ continue;
41
+ }
42
+ console.log(`[pty-service] reattaching to existing session ${request.sessionId}`);
43
+ msg.respond(
44
+ JSON.stringify({
45
+ sessionId: request.sessionId,
46
+ created: false,
47
+ })
48
+ );
49
+ continue;
50
+ }
51
+
52
+ const response = sessionManager.createSession(request, orgId, userId);
53
+ msg.respond(JSON.stringify(response));
54
+ } catch (err) {
55
+ console.error('[pty-service] create error:', err);
56
+ msg.respond(JSON.stringify({ error: (err as Error).message }));
57
+ }
58
+ }
59
+ })();
60
+
61
+ // --- khal.*.pty.destroy (request-reply) ---
62
+ const destroySub = nc.subscribe('khal.*.pty.destroy');
63
+ (async () => {
64
+ for await (const msg of destroySub) {
65
+ try {
66
+ const request = msg.json<PtyDestroyRequest & { _authUserId?: string }>();
67
+ const userId = request._authUserId;
68
+ const destroyed = sessionManager.destroySession(request.sessionId, userId);
69
+ if (!destroyed) {
70
+ msg.respond(JSON.stringify({ ok: false, error: 'ownership check failed or session not found' }));
71
+ } else {
72
+ msg.respond(JSON.stringify({ ok: true }));
73
+ }
74
+ } catch (err) {
75
+ console.error('[pty-service] destroy error:', err);
76
+ msg.respond(JSON.stringify({ error: (err as Error).message }));
77
+ }
78
+ }
79
+ })();
80
+
81
+ // --- khal.*.pty.list (request-reply) ---
82
+ const listSub = nc.subscribe('khal.*.pty.list');
83
+ (async () => {
84
+ for await (const msg of listSub) {
85
+ try {
86
+ const orgId = msg.subject.split('.')[1];
87
+ let listRequest: PtyListRequest & { _authUserId?: string } = {};
88
+ if (msg.data.length > 0) {
89
+ listRequest = msg.json<PtyListRequest & { _authUserId?: string }>();
90
+ }
91
+ const response = sessionManager.listSessions(orgId, listRequest._authUserId);
92
+ msg.respond(JSON.stringify(response));
93
+ } catch (err) {
94
+ console.error('[pty-service] list error:', err);
95
+ msg.respond(JSON.stringify({ error: (err as Error).message }));
96
+ }
97
+ }
98
+ })();
99
+
100
+ // --- khal.*.pty.*.input (wildcard for orgId AND sessionId, fire-and-forget) ---
101
+ const inputSub = nc.subscribe('khal.*.pty.*.input');
102
+ (async () => {
103
+ for await (const msg of inputSub) {
104
+ try {
105
+ const sessionId = msg.subject.split('.')[3];
106
+ let data = '';
107
+ let userId: string | undefined;
108
+
109
+ if (msg.data.length > 0) {
110
+ const parsed = msg.json<PtyInputMessage & { _authUserId?: string }>();
111
+ data = parsed.data;
112
+ userId = parsed._authUserId;
113
+ }
114
+
115
+ if (sessionId && data) {
116
+ sessionManager.writeToSession(sessionId, data, userId);
117
+ }
118
+ } catch (err) {
119
+ console.error('[pty-service] input error:', err);
120
+ }
121
+ }
122
+ })();
123
+
124
+ // --- khal.*.pty.*.resize (wildcard, fire-and-forget) ---
125
+ const resizeSub = nc.subscribe('khal.*.pty.*.resize');
126
+ (async () => {
127
+ for await (const msg of resizeSub) {
128
+ try {
129
+ const sessionId = msg.subject.split('.')[3];
130
+ const request = msg.json<PtyResizeRequest & { _authUserId?: string }>();
131
+
132
+ if (sessionId) {
133
+ sessionManager.resizeSession(sessionId, request.cols, request.rows, request._authUserId);
134
+ }
135
+ } catch (err) {
136
+ console.error('[pty-service] resize error:', err);
137
+ }
138
+ }
139
+ })();
140
+
141
+ // --- khal.*.pty.*.replay (wildcard, fire-and-forget) ---
142
+ // Client sends this AFTER subscribing to buffer/buffer-end subjects
143
+ const replaySub = nc.subscribe('khal.*.pty.*.replay');
144
+ (async () => {
145
+ for await (const msg of replaySub) {
146
+ try {
147
+ const sessionId = msg.subject.split('.')[3];
148
+ let userId: string | undefined;
149
+ if (msg.data.length > 0) {
150
+ const request = msg.json<PtyReplayRequest & { _authUserId?: string }>();
151
+ userId = request._authUserId;
152
+ }
153
+ if (sessionId) {
154
+ console.log(`[pty-service] replaying buffer for session ${sessionId}`);
155
+ sessionManager.replayBuffer(sessionId, userId);
156
+ }
157
+ } catch (err) {
158
+ console.error('[pty-service] replay error:', err);
159
+ }
160
+ }
161
+ })();
162
+
163
+ // --- Graceful shutdown ---
164
+ const shutdown = async () => {
165
+ console.log('[pty-service] shutting down...');
166
+ sessionManager.shutdown();
167
+
168
+ createSub.unsubscribe();
169
+ destroySub.unsubscribe();
170
+ listSub.unsubscribe();
171
+ inputSub.unsubscribe();
172
+ resizeSub.unsubscribe();
173
+ replaySub.unsubscribe();
174
+
175
+ await nc.close();
176
+ process.exit(0);
177
+ };
178
+
179
+ process.on('SIGINT', shutdown);
180
+ process.on('SIGTERM', shutdown);
181
+
182
+ // Keep alive
183
+ await nc.closed();
184
+ }
185
+
186
+ main().catch((err) => {
187
+ console.error('[pty-service] fatal:', err);
188
+ process.exit(1);
189
+ });
@@ -0,0 +1,296 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import type { NatsConnection } from '@nats-io/transport-node';
5
+ import * as pty from 'node-pty';
6
+ import { SUBJECTS } from '@/lib/subjects';
7
+ import type {
8
+ PtyBufferEndMessage,
9
+ PtyBufferMessage,
10
+ PtyCreateRequest,
11
+ PtyCreateResponse,
12
+ PtyDataMessage,
13
+ PtyExitMessage,
14
+ PtyListResponse,
15
+ } from '../schema';
16
+
17
+ const SHELL = process.env.SHELL || '/bin/bash';
18
+ const MAX_BUFFER_BYTES = parseInt(process.env.PTY_MAX_BUFFER_BYTES || '1048576', 10); // 1MB
19
+ const SESSION_TTL_MS = parseInt(process.env.PTY_SESSION_TTL_MS || '259200000', 10); // 72h
20
+ const CLEANUP_INTERVAL_MS = 3600000; // 1 hour
21
+
22
+ const __dir = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));
23
+ const BASHRC_HOOK_PATH = resolve(__dir, 'shell-hooks', 'bashrc-hook.sh');
24
+
25
+ interface PTYSession {
26
+ sessionId: string;
27
+ orgId: string;
28
+ ownerId: string;
29
+ process: pty.IPty;
30
+ buffer: Buffer[];
31
+ bufferBytes: number;
32
+ createdAt: number;
33
+ lastActivity: number;
34
+ subscribers: number;
35
+ }
36
+
37
+ function addToBuffer(session: PTYSession, data: string): void {
38
+ const chunk = Buffer.from(data, 'utf8');
39
+ session.buffer.push(chunk);
40
+ session.bufferBytes += chunk.length;
41
+
42
+ // Enforce byte cap by removing oldest chunks
43
+ while (session.bufferBytes > MAX_BUFFER_BYTES && session.buffer.length > 0) {
44
+ const removed = session.buffer.shift();
45
+ if (removed) {
46
+ session.bufferBytes -= removed.length;
47
+ }
48
+ }
49
+
50
+ session.lastActivity = Date.now();
51
+ }
52
+
53
+ export function createSessionManager(nc: NatsConnection) {
54
+ const sessions = new Map<string, PTYSession>();
55
+ let cleanupTimer: ReturnType<typeof setInterval> | null = null;
56
+
57
+ function createSession(request: PtyCreateRequest, orgId: string, userId: string): PtyCreateResponse {
58
+ const sessionId = request.sessionId || randomUUID();
59
+ const cols = request.cols || 80;
60
+ const rows = request.rows || 24;
61
+
62
+ console.log(`[pty] Creating session ${sessionId} (org=${orgId}, user=${userId}) -- spawning ${SHELL}`);
63
+
64
+ // Determine shell args -- for bash, use --rcfile for OSC integration
65
+ let shellArgs: string[] = [];
66
+ const shellName = SHELL.split('/').pop();
67
+ if (shellName === 'bash') {
68
+ shellArgs = ['--rcfile', BASHRC_HOOK_PATH];
69
+ }
70
+
71
+ const ptyProcess = pty.spawn(SHELL, shellArgs, {
72
+ name: 'xterm-256color',
73
+ cols,
74
+ rows,
75
+ cwd: process.env.HOME || '/home/genie',
76
+ env: {
77
+ ...(process.env as Record<string, string>),
78
+ TERM: 'xterm-256color',
79
+ COLORTERM: 'truecolor',
80
+ },
81
+ });
82
+
83
+ const session: PTYSession = {
84
+ sessionId,
85
+ orgId,
86
+ ownerId: userId,
87
+ process: ptyProcess,
88
+ buffer: [],
89
+ bufferBytes: 0,
90
+ createdAt: Date.now(),
91
+ lastActivity: Date.now(),
92
+ subscribers: 0,
93
+ };
94
+
95
+ // PTY output -> buffer + publish to NATS
96
+ ptyProcess.onData((data: string) => {
97
+ addToBuffer(session, data);
98
+
99
+ const msg: PtyDataMessage = {
100
+ sessionId,
101
+ data: Buffer.from(data, 'utf8').toString('base64'),
102
+ };
103
+ nc.publish(SUBJECTS.pty.data(session.orgId, sessionId), JSON.stringify(msg));
104
+ });
105
+
106
+ // PTY exit -> publish exit message + cleanup
107
+ ptyProcess.onExit(({ exitCode, signal }) => {
108
+ console.log(`[pty] Session ${sessionId} exited (code=${exitCode}, signal=${signal})`);
109
+
110
+ const msg: PtyExitMessage = {
111
+ sessionId,
112
+ code: exitCode,
113
+ ...(signal !== undefined ? { signal } : {}),
114
+ };
115
+ nc.publish(SUBJECTS.pty.exit(session.orgId, sessionId), JSON.stringify(msg));
116
+
117
+ sessions.delete(sessionId);
118
+ });
119
+
120
+ sessions.set(sessionId, session);
121
+
122
+ return { sessionId, created: true };
123
+ }
124
+
125
+ function destroySession(sessionId: string, userId?: string): boolean {
126
+ const session = sessions.get(sessionId);
127
+ if (!session) return false;
128
+
129
+ // Strict ownership: if session has an owner, userId must match
130
+ if (session.ownerId && (!userId || session.ownerId !== userId)) {
131
+ console.warn(
132
+ `[pty] Ownership check failed: user ${userId || '<empty>'} tried to destroy session ${sessionId} owned by ${session.ownerId}`
133
+ );
134
+ return false;
135
+ }
136
+
137
+ console.log(`[pty] Destroying session ${sessionId}`);
138
+
139
+ try {
140
+ session.process.kill();
141
+ } catch (err) {
142
+ console.error(`[pty] Error killing PTY ${sessionId}:`, (err as Error).message);
143
+ }
144
+
145
+ sessions.delete(sessionId);
146
+ return true;
147
+ }
148
+
149
+ function replayBuffer(sessionId: string, userId?: string): boolean {
150
+ const session = sessions.get(sessionId);
151
+ if (!session) return false;
152
+
153
+ if (session.ownerId && (!userId || session.ownerId !== userId)) {
154
+ console.warn(
155
+ `[pty] Ownership check failed: user ${userId || '<empty>'} tried to replay session ${sessionId} owned by ${session.ownerId}`
156
+ );
157
+ // Send buffer-end with error so the client doesn't hang
158
+ const endMsg: PtyBufferEndMessage = { sessionId, error: 'access_denied' };
159
+ nc.publish(SUBJECTS.pty.bufferEnd(session.orgId, sessionId), JSON.stringify(endMsg));
160
+ return false;
161
+ }
162
+
163
+ // Publish each buffered chunk as base64
164
+ for (const chunk of session.buffer) {
165
+ const msg: PtyBufferMessage = {
166
+ sessionId,
167
+ data: chunk.toString('base64'),
168
+ };
169
+ nc.publish(SUBJECTS.pty.buffer(session.orgId, sessionId), JSON.stringify(msg));
170
+ }
171
+
172
+ // Signal end of buffer replay
173
+ const endMsg: PtyBufferEndMessage = { sessionId };
174
+ nc.publish(SUBJECTS.pty.bufferEnd(session.orgId, sessionId), JSON.stringify(endMsg));
175
+
176
+ return true;
177
+ }
178
+
179
+ function resizeSession(sessionId: string, cols: number, rows: number, userId?: string): boolean {
180
+ const session = sessions.get(sessionId);
181
+ if (!session) return false;
182
+
183
+ if (session.ownerId && (!userId || session.ownerId !== userId)) {
184
+ console.warn(
185
+ `[pty] Ownership check failed: user ${userId || '<empty>'} tried to resize session ${sessionId} owned by ${session.ownerId}`
186
+ );
187
+ return false;
188
+ }
189
+
190
+ const newCols = Math.max(1, Math.floor(cols));
191
+ const newRows = Math.max(1, Math.floor(rows));
192
+
193
+ // Skip if dimensions haven't changed — avoids redundant SIGWINCH
194
+ if (session.process.cols === newCols && session.process.rows === newRows) {
195
+ return true;
196
+ }
197
+
198
+ session.process.resize(newCols, newRows);
199
+ session.lastActivity = Date.now();
200
+ return true;
201
+ }
202
+
203
+ function writeToSession(sessionId: string, data: string, userId?: string): boolean {
204
+ const session = sessions.get(sessionId);
205
+ if (!session) return false;
206
+
207
+ if (session.ownerId && (!userId || session.ownerId !== userId)) {
208
+ console.warn(
209
+ `[pty] Ownership check failed: user ${userId || '<empty>'} tried to write to session ${sessionId} owned by ${session.ownerId}`
210
+ );
211
+ return false;
212
+ }
213
+
214
+ session.process.write(data);
215
+ session.lastActivity = Date.now();
216
+ return true;
217
+ }
218
+
219
+ function hasSession(sessionId: string): boolean {
220
+ return sessions.has(sessionId);
221
+ }
222
+
223
+ function isOwner(sessionId: string, userId: string): boolean {
224
+ const session = sessions.get(sessionId);
225
+ if (!session) return false;
226
+ return session.ownerId === userId;
227
+ }
228
+
229
+ function listSessions(orgId?: string, userId?: string): PtyListResponse {
230
+ let entries = Array.from(sessions.values());
231
+
232
+ if (orgId) {
233
+ entries = entries.filter((s) => s.orgId === orgId);
234
+ }
235
+
236
+ if (userId) {
237
+ entries = entries.filter((s) => s.ownerId === userId);
238
+ }
239
+
240
+ const list = entries.map((s) => ({
241
+ sessionId: s.sessionId,
242
+ createdAt: s.createdAt,
243
+ lastActivity: s.lastActivity,
244
+ bufferBytes: s.bufferBytes,
245
+ connected: s.subscribers > 0,
246
+ }));
247
+
248
+ return { sessions: list };
249
+ }
250
+
251
+ function setSubscribers(sessionId: string, count: number): void {
252
+ const session = sessions.get(sessionId);
253
+ if (session) {
254
+ session.subscribers = count;
255
+ }
256
+ }
257
+
258
+ function cleanupExpiredSessions(): void {
259
+ const now = Date.now();
260
+ for (const [sessionId, session] of sessions.entries()) {
261
+ if (now - session.lastActivity > SESSION_TTL_MS) {
262
+ console.log(`[pty] Session ${sessionId} expired (TTL exceeded)`);
263
+ destroySession(sessionId);
264
+ }
265
+ }
266
+ }
267
+
268
+ // Start TTL cleanup interval
269
+ cleanupTimer = setInterval(cleanupExpiredSessions, CLEANUP_INTERVAL_MS);
270
+
271
+ function shutdown(): void {
272
+ console.log('[pty] Shutting down session manager...');
273
+
274
+ if (cleanupTimer) {
275
+ clearInterval(cleanupTimer);
276
+ cleanupTimer = null;
277
+ }
278
+
279
+ for (const sessionId of sessions.keys()) {
280
+ destroySession(sessionId);
281
+ }
282
+ }
283
+
284
+ return {
285
+ createSession,
286
+ destroySession,
287
+ replayBuffer,
288
+ resizeSession,
289
+ writeToSession,
290
+ hasSession,
291
+ isOwner,
292
+ listSessions,
293
+ setSubscribers,
294
+ shutdown,
295
+ };
296
+ }
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ # Khal Terminal Integration Hook
3
+ # This file is sourced by bash when spawned by the PTY server.
4
+ # It provides shell integration for CWD tracking via OSC 7.
5
+
6
+ # Source user's existing bashrc first to preserve customizations
7
+ if [ -f ~/.bashrc ]; then
8
+ source ~/.bashrc
9
+ fi
10
+
11
+ # OSC 7 CWD reporting — sends current directory to terminal after each command
12
+ __khal_precmd() {
13
+ printf '\e]7;file://%s%s\e\\' "$HOSTNAME" "$PWD"
14
+ }
15
+
16
+ # Add to PROMPT_COMMAND (append, don't overwrite)
17
+ if [ -z "$PROMPT_COMMAND" ]; then
18
+ PROMPT_COMMAND="__khal_precmd"
19
+ else
20
+ PROMPT_COMMAND="__khal_precmd;${PROMPT_COMMAND}"
21
+ fi
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Terminal tab data model for multi-tab terminal.
3
+ */
4
+ export interface TerminalTab {
5
+ id: string;
6
+ ptySessionId: string | null; // assigned on connect (legacy, kept for compatibility)
7
+ title: string; // from OSC or fallback
8
+ cwd: string | null; // from OSC 7
9
+ lastCommand: string | null; // from input tracking
10
+ splitTree: SplitNode; // recursive split tree (root starts as single leaf)
11
+ focusedPaneId: string; // which pane in the split tree is focused
12
+ }
13
+
14
+ /**
15
+ * Recursive split pane tree structure.
16
+ * A tab's content is either a single pane (leaf) or a split container (branch).
17
+ */
18
+ export type SplitNode =
19
+ | { type: 'leaf'; id: string; ptySessionId: string | null; cwd: string | null; lastCommand: string | null }
20
+ | {
21
+ type: 'branch';
22
+ id: string;
23
+ direction: 'horizontal' | 'vertical';
24
+ children: [SplitNode, SplitNode];
25
+ ratio: number;
26
+ };