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,68 @@
1
+ /**
2
+ * OS Config — source of truth for all environment variables.
3
+ *
4
+ * All values are resolved eagerly at module import time.
5
+ * Required variables that are missing will throw immediately (fail-fast).
6
+ *
7
+ * Server-side exports: NATS_URL, getDatabaseUrl()
8
+ * Path config exports (server-only, no default applied): FILES_ROOT_ENV, AGENT_REPO_ROOT_ENV, AGENT_WORKTREE_ROOT_ENV
9
+ * getDatabaseUrl() is lazy (not resolved at import) so client components can safely
10
+ * import this module without triggering a browser crash.
11
+ */
12
+
13
+ /**
14
+ * URL of the NATS server.
15
+ *
16
+ * Resolution order: NATS_URL → 'nats://localhost:4222'
17
+ * Never throws — falls back to localhost.
18
+ */
19
+ export const NATS_URL: string = process.env.NATS_URL ?? 'nats://localhost:4222';
20
+
21
+ /**
22
+ * PostgreSQL connection string.
23
+ *
24
+ * Lazy — resolved on first access, not at import time.
25
+ * This allows client components to safely import this module without triggering
26
+ * a DATABASE_URL crash in the browser.
27
+ *
28
+ * Throws if accessed from the browser or if DATABASE_URL is not set.
29
+ */
30
+ let _databaseUrl: string | undefined;
31
+
32
+ export function getDatabaseUrl(): string {
33
+ if (typeof window !== 'undefined') {
34
+ throw new Error('[config] DATABASE_URL must not be accessed from the browser.');
35
+ }
36
+ if (_databaseUrl === undefined) {
37
+ const url = process.env.DATABASE_URL;
38
+ if (!url) {
39
+ throw new Error('[config] DATABASE_URL is required but not set. Set it in your .env file.');
40
+ }
41
+ _databaseUrl = url;
42
+ }
43
+ return _databaseUrl;
44
+ }
45
+
46
+ /**
47
+ * Raw env var for the files root directory.
48
+ * Use getFilesRoot() from @/lib/files/safe-path which applies the ~/genie-os-files default.
49
+ *
50
+ * Server-only path config — safe to read (just a string), but the default requires node:os.
51
+ */
52
+ export const FILES_ROOT_ENV: string | undefined = process.env.OS_FILES_ROOT;
53
+
54
+ /**
55
+ * Raw env var for the agent git repository root directory.
56
+ * Use getAgentRepoRoot() from @/lib/git/agent-repo which applies the default.
57
+ *
58
+ * Server-only path config.
59
+ */
60
+ export const AGENT_REPO_ROOT_ENV: string | undefined = process.env.OS_AGENT_REPO_ROOT;
61
+
62
+ /**
63
+ * Raw env var for the agent git worktree root directory.
64
+ * Use createWorktreeManager() from @/lib/git/worktree which applies the default.
65
+ *
66
+ * Server-only path config.
67
+ */
68
+ export const AGENT_WORKTREE_ROOT_ENV: string | undefined = process.env.OS_AGENT_WORKTREE_ROOT;
@@ -0,0 +1,42 @@
1
+ // @ts-nocheck
2
+ import { beforeEach, describe, expect, test } from 'bun:test';
3
+ import { closeDb, getDb, initDb, isDbInitialized } from './factory';
4
+
5
+ describe('db factory', () => {
6
+ beforeEach(async () => {
7
+ await closeDb();
8
+ });
9
+
10
+ test('throws if getDb() called before initDb()', () => {
11
+ expect(() => getDb()).toThrow('Call initDb() during host bootstrap');
12
+ });
13
+
14
+ test('singleton: getDb() returns same instance', () => {
15
+ initDb({
16
+ schema: {},
17
+ getDatabaseUrl: () => 'postgres://localhost:5432/test',
18
+ });
19
+ expect(isDbInitialized()).toBe(true);
20
+ const db1 = getDb();
21
+ const db2 = getDb();
22
+ expect(db1).toBe(db2);
23
+ });
24
+
25
+ test('closeDb resets db + config; getDb requires init again', async () => {
26
+ initDb({
27
+ schema: {},
28
+ getDatabaseUrl: () => 'postgres://localhost:5432/test',
29
+ });
30
+ const db1 = getDb();
31
+ await closeDb();
32
+ expect(isDbInitialized()).toBe(false);
33
+ expect(() => getDb()).toThrow('Call initDb() during host bootstrap');
34
+
35
+ initDb({
36
+ schema: {},
37
+ getDatabaseUrl: () => 'postgres://localhost:5432/test',
38
+ });
39
+ const db2 = getDb();
40
+ expect(db2).not.toBe(db1);
41
+ });
42
+ });
@@ -0,0 +1,72 @@
1
+ import { drizzle } from 'drizzle-orm/postgres-js';
2
+ import postgres from 'postgres';
3
+
4
+ export interface DbConfig {
5
+ schema: Record<string, unknown>;
6
+ getDatabaseUrl: () => string;
7
+ appId?: string;
8
+ searchPath?: string; // e.g. 'myapp,public'
9
+ }
10
+
11
+ let _config: DbConfig | null = null;
12
+ let _db: ReturnType<typeof drizzle> | null = null;
13
+ let _client: ReturnType<typeof postgres> | null = null;
14
+
15
+ /**
16
+ * Register database configuration during host bootstrap.
17
+ * Connection remains lazy and is opened on first getDb() call.
18
+ */
19
+ export function initDb(config: DbConfig): void {
20
+ if (_config) return;
21
+ _config = config;
22
+ }
23
+
24
+ export function isDbInitialized(): boolean {
25
+ return _config !== null;
26
+ }
27
+
28
+ export function assertDbInitialized(): void {
29
+ if (!_config) {
30
+ throw new Error('[os-sdk] Database not initialized. Call initDb() during host bootstrap.');
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Return the appId that was passed to initDb(), if any.
36
+ */
37
+ export function getAppId(): string | undefined {
38
+ return _config?.appId;
39
+ }
40
+
41
+ /**
42
+ * Return the Drizzle DB instance, creating the connection on first call.
43
+ * Throws if initDb() has not been called yet.
44
+ */
45
+ export function getDb() {
46
+ assertDbInitialized();
47
+ const config = _config;
48
+ if (!config) {
49
+ throw new Error('[os-sdk] Database not initialized. Call initDb() during host bootstrap.');
50
+ }
51
+ if (!_db) {
52
+ const url = config.getDatabaseUrl();
53
+ const opts: Record<string, unknown> = { max: 10, idle_timeout: 20, connect_timeout: 10 };
54
+ if (config.searchPath) {
55
+ opts.connection = { search_path: config.searchPath };
56
+ }
57
+ _client = postgres(url, opts);
58
+ _db = drizzle(_client, { schema: config.schema });
59
+ }
60
+ return _db;
61
+ }
62
+
63
+ export type Database = ReturnType<typeof getDb>;
64
+
65
+ export async function closeDb(): Promise<void> {
66
+ if (_client) {
67
+ await _client.end();
68
+ }
69
+ _client = null;
70
+ _db = null;
71
+ _config = null;
72
+ }
@@ -0,0 +1,140 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { drizzle } from 'drizzle-orm/postgres-js';
4
+ import { migrate } from 'drizzle-orm/postgres-js/migrator';
5
+ import postgres from 'postgres';
6
+ import { getDatabaseUrl } from '../config';
7
+
8
+ const HOST_MIGRATIONS_FOLDER = './drizzle';
9
+
10
+ /**
11
+ * Run host-only migrations from ./drizzle (backwards-compatible).
12
+ */
13
+ export async function runMigrations(): Promise<void> {
14
+ const url = getDatabaseUrl();
15
+
16
+ // Drizzle migrator requires meta/_journal.json — skip if no migrations exist yet
17
+ if (!existsSync(`${HOST_MIGRATIONS_FOLDER}/meta/_journal.json`)) {
18
+ return;
19
+ }
20
+
21
+ const searchPath = process.env.GENIE_OS_SEARCH_PATH;
22
+ const opts: Record<string, unknown> = { max: 1 };
23
+ if (searchPath) {
24
+ opts.connection = { search_path: searchPath };
25
+ }
26
+ const migrationClient = postgres(url, opts);
27
+ const db = drizzle(migrationClient);
28
+
29
+ try {
30
+ await migrate(db, { migrationsFolder: HOST_MIGRATIONS_FOLDER });
31
+ } finally {
32
+ await migrationClient.end();
33
+ }
34
+ }
35
+
36
+ interface AppMigrationTarget {
37
+ packageName: string;
38
+ schemaName: string;
39
+ migrationsFolder: string;
40
+ }
41
+
42
+ /**
43
+ * Scan packages/* /drizzle/ for app migration folders.
44
+ * Returns a list of migration targets with their schema names.
45
+ */
46
+ function discoverAppMigrations(): AppMigrationTarget[] {
47
+ const targets: AppMigrationTarget[] = [];
48
+ const packagesDir = resolve(process.cwd(), 'packages');
49
+
50
+ if (!existsSync(packagesDir)) return targets;
51
+
52
+ const entries = readdirSync(packagesDir, { withFileTypes: true });
53
+ for (const entry of entries) {
54
+ if (!entry.isDirectory()) continue;
55
+
56
+ const pkgDir = resolve(packagesDir, entry.name);
57
+ const journalPath = resolve(pkgDir, 'drizzle', 'meta', '_journal.json');
58
+
59
+ // Skip packages without drizzle/meta/_journal.json
60
+ if (!existsSync(journalPath)) continue;
61
+
62
+ const pkgJsonPath = resolve(pkgDir, 'package.json');
63
+ if (!existsSync(pkgJsonPath)) continue;
64
+
65
+ try {
66
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
67
+ const schemaName = pkg.genieOs?.schema?.schemaName;
68
+
69
+ if (!schemaName) {
70
+ // biome-ignore lint/suspicious/noConsole: migration logging
71
+ console.warn(
72
+ `[db:migrate] package ${entry.name} has drizzle/ but no genieOs.schema.schemaName in package.json — skipping`
73
+ );
74
+ continue;
75
+ }
76
+
77
+ targets.push({
78
+ packageName: entry.name,
79
+ schemaName,
80
+ migrationsFolder: resolve(pkgDir, 'drizzle'),
81
+ });
82
+ } catch {
83
+ // biome-ignore lint/suspicious/noConsole: migration logging
84
+ console.warn(`[db:migrate] failed to read package.json for ${entry.name} — skipping`);
85
+ }
86
+ }
87
+
88
+ return targets;
89
+ }
90
+
91
+ /**
92
+ * Run migrations for a single app package with search_path isolation.
93
+ */
94
+ async function runAppMigrations(target: AppMigrationTarget): Promise<void> {
95
+ const url = getDatabaseUrl();
96
+ const searchPath = `${target.schemaName},public`;
97
+
98
+ const migrationClient = postgres(url, {
99
+ max: 1,
100
+ connection: { search_path: searchPath },
101
+ });
102
+ const db = drizzle(migrationClient);
103
+
104
+ try {
105
+ // biome-ignore lint/suspicious/noConsole: migration logging
106
+ console.log(`[db:migrate] running migrations for ${target.packageName} (schema: ${target.schemaName})`);
107
+ await migrate(db, { migrationsFolder: target.migrationsFolder });
108
+ // biome-ignore lint/suspicious/noConsole: migration logging
109
+ console.log(`[db:migrate] migrations complete for ${target.packageName}`);
110
+ } finally {
111
+ await migrationClient.end();
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Run all migrations: host first, then per-app.
117
+ * Each app's migrations run with search_path = <appSchema>,public.
118
+ * Apps without drizzle/ are silently skipped.
119
+ * Migration errors are fatal (not swallowed).
120
+ */
121
+ export async function runAllMigrations(): Promise<void> {
122
+ // 1. Host migrations (OS core tables)
123
+ await runMigrations();
124
+
125
+ // 2. Per-app migrations
126
+ const appTargets = discoverAppMigrations();
127
+ for (const target of appTargets) {
128
+ await runAppMigrations(target);
129
+ }
130
+ }
131
+
132
+ if ((import.meta as { main?: boolean }).main) {
133
+ runAllMigrations()
134
+ .then(() => process.exit(0))
135
+ .catch((err) => {
136
+ // biome-ignore lint/suspicious/noConsole: CLI entrypoint must log errors
137
+ console.error('[db:migrate] failed:', err);
138
+ process.exit(1);
139
+ });
140
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * App schema provisioning — core OS capability.
3
+ *
4
+ * Each app calls provisionAppSchema() on boot to create its isolated
5
+ * PostgreSQL schema + role via pgserve. Idempotent, safe for restarts.
6
+ *
7
+ * Requires Bun runtime (uses Bun.SQL for the pgserve admin connection).
8
+ */
9
+
10
+ import { getDatabaseUrl } from '../config';
11
+
12
+ export interface AppSchemaConfig {
13
+ name: string;
14
+ schemaName: string;
15
+ roleName: string;
16
+ }
17
+
18
+ export async function provisionAppSchema(config: AppSchemaConfig): Promise<{ created: boolean }> {
19
+ // pgserve is an optional runtime dependency — indirect import to skip tsc module resolution
20
+ const pgserveModule = 'pgserve';
21
+ // biome-ignore lint/suspicious/noExplicitAny: optional runtime dependency, no types guaranteed
22
+ const { initCatalog, provisionSchema } = (await import(pgserveModule)) as any;
23
+ // Bun.SQL is only available at runtime under Bun — indirect import to skip tsc module resolution
24
+ const bunModule = 'bun';
25
+ // biome-ignore lint/suspicious/noExplicitAny: Bun-only runtime API, no type declarations available
26
+ const { SQL } = (await import(bunModule)) as any;
27
+
28
+ const url = new URL(getDatabaseUrl());
29
+ const adminSql = new SQL({
30
+ hostname: url.hostname,
31
+ port: Number(url.port),
32
+ database: url.pathname.slice(1),
33
+ username: url.username,
34
+ password: url.password,
35
+ });
36
+
37
+ try {
38
+ await initCatalog(adminSql);
39
+ const result = await provisionSchema(adminSql, config);
40
+ return { created: result.created };
41
+ } finally {
42
+ await adminSql.close();
43
+ }
44
+ }
@@ -0,0 +1,36 @@
1
+ export type { ApiContext, ApiContextWithDb } from './api/handler';
2
+ export { apiHandler, apiHandlerWithDb } from './api/handler';
3
+ export {
4
+ AGENT_REPO_ROOT_ENV,
5
+ AGENT_WORKTREE_ROOT_ENV,
6
+ FILES_ROOT_ENV,
7
+ getDatabaseUrl,
8
+ NATS_URL,
9
+ } from './config';
10
+ export type { Database, DbConfig } from './db/factory';
11
+ export {
12
+ assertDbInitialized,
13
+ closeDb,
14
+ getAppId,
15
+ getDb,
16
+ initDb,
17
+ initDb as createDb,
18
+ isDbInitialized,
19
+ } from './db/factory';
20
+ export { runMigrations } from './db/migrate';
21
+ export { interceptConsole, restoreConsole } from './service/console-intercept';
22
+ export type { LogEntry, Logger } from './service/logger';
23
+ export { createLogger } from './service/logger';
24
+ export {
25
+ ensureO11yStreams,
26
+ O11Y_STREAM_EVENTS,
27
+ O11Y_STREAM_LOGS,
28
+ O11Y_STREAM_TRACES,
29
+ O11Y_SUBJECT_EVENTS,
30
+ O11Y_SUBJECT_LOGS,
31
+ O11Y_SUBJECT_TRACES,
32
+ } from './service/o11y-streams';
33
+ export type { Msg, NatsConnection, ObserveConfig, ServiceConfig, ServiceHandler } from './service/runtime';
34
+ export { createService } from './service/runtime';
35
+ export type { TraceContext } from './service/trace';
36
+ export { extractTrace, injectTrace, newSpan, PARENT_SPAN_HEADER, SPAN_HEADER, TRACE_HEADER } from './service/trace';
@@ -0,0 +1,60 @@
1
+ import type { Logger } from './logger';
2
+
3
+ interface OriginalConsole {
4
+ log: typeof console.log;
5
+ info: typeof console.info;
6
+ warn: typeof console.warn;
7
+ error: typeof console.error;
8
+ debug: typeof console.debug;
9
+ }
10
+
11
+ let originals: OriginalConsole | null = null;
12
+
13
+ function formatArgs(args: unknown[]): string {
14
+ return args
15
+ .map((arg) => {
16
+ if (typeof arg === 'string') return arg;
17
+ if (arg instanceof Error) return `${arg.message}\n${arg.stack ?? ''}`;
18
+ try {
19
+ return JSON.stringify(arg);
20
+ } catch {
21
+ return String(arg);
22
+ }
23
+ })
24
+ .join(' ');
25
+ }
26
+
27
+ export function interceptConsole(log: Logger): () => void {
28
+ if (originals) {
29
+ // Already intercepted — avoid double-wrapping
30
+ return restoreConsole;
31
+ }
32
+
33
+ originals = {
34
+ log: console.log,
35
+ info: console.info,
36
+ warn: console.warn,
37
+ error: console.error,
38
+ debug: console.debug,
39
+ };
40
+
41
+ console.log = (...args: unknown[]) => log.info(formatArgs(args));
42
+ console.info = (...args: unknown[]) => log.info(formatArgs(args));
43
+ console.warn = (...args: unknown[]) => log.warn(formatArgs(args));
44
+ console.error = (...args: unknown[]) => log.error(formatArgs(args));
45
+ console.debug = (...args: unknown[]) => log.debug(formatArgs(args));
46
+
47
+ return restoreConsole;
48
+ }
49
+
50
+ export function restoreConsole(): void {
51
+ if (!originals) return;
52
+
53
+ console.log = originals.log;
54
+ console.info = originals.info;
55
+ console.warn = originals.warn;
56
+ console.error = originals.error;
57
+ console.debug = originals.debug;
58
+
59
+ originals = null;
60
+ }
@@ -0,0 +1,88 @@
1
+ import type { NatsConnection } from '@nats-io/transport-node';
2
+
3
+ export interface LogEntry {
4
+ ts: string;
5
+ level: 'debug' | 'info' | 'warn' | 'error';
6
+ service: string;
7
+ msg: string;
8
+ error?: { message: string; stack?: string };
9
+ trace_id?: string;
10
+ span_id?: string;
11
+ req_id?: string;
12
+ meta?: Record<string, unknown>;
13
+ }
14
+
15
+ export interface Logger {
16
+ debug(msg: string, meta?: Record<string, unknown>): void;
17
+ info(msg: string, meta?: Record<string, unknown>): void;
18
+ warn(msg: string, meta?: Record<string, unknown>): void;
19
+ error(msg: string, meta?: Record<string, unknown>): void;
20
+ child(defaultMeta: Record<string, unknown>): Logger;
21
+ }
22
+
23
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
24
+
25
+ function writeStdout(line: string): void {
26
+ originalStdoutWrite(`${line}\n`);
27
+ }
28
+
29
+ function applyMeta(entry: LogEntry, merged: Record<string, unknown>): void {
30
+ if (merged.error instanceof Error) {
31
+ entry.error = { message: merged.error.message, stack: merged.error.stack };
32
+ const { error: _, ...rest } = merged;
33
+ if (Object.keys(rest).length > 0) entry.meta = rest;
34
+ return;
35
+ }
36
+
37
+ const rest: Record<string, unknown> = {};
38
+ for (const [key, value] of Object.entries(merged)) {
39
+ if (key === 'trace_id' && value) entry.trace_id = value as string;
40
+ else if (key === 'span_id' && value) entry.span_id = value as string;
41
+ else if (key === 'req_id' && value) entry.req_id = value as string;
42
+ else rest[key] = value;
43
+ }
44
+ if (Object.keys(rest).length > 0) entry.meta = rest;
45
+ }
46
+
47
+ function publishToNats(nc: NatsConnection | null, subject: string, json: string): void {
48
+ if (!nc) return;
49
+ try {
50
+ nc.publish(subject, json);
51
+ } catch {
52
+ // Silently ignore publish failures
53
+ }
54
+ }
55
+
56
+ export function createLogger(
57
+ serviceName: string,
58
+ nc: NatsConnection | null,
59
+ defaultMeta?: Record<string, unknown>
60
+ ): Logger {
61
+ const subject = `os.o11y.logs.${serviceName}`;
62
+
63
+ function emit(level: LogEntry['level'], msg: string, meta?: Record<string, unknown>): void {
64
+ const entry: LogEntry = {
65
+ ts: new Date().toISOString(),
66
+ level,
67
+ service: serviceName,
68
+ msg,
69
+ };
70
+
71
+ const merged = defaultMeta ? { ...defaultMeta, ...meta } : meta;
72
+ if (merged) applyMeta(entry, merged);
73
+
74
+ const json = JSON.stringify(entry);
75
+ writeStdout(json);
76
+ publishToNats(nc, subject, json);
77
+ }
78
+
79
+ return {
80
+ debug: (msg, meta) => emit('debug', msg, meta),
81
+ info: (msg, meta) => emit('info', msg, meta),
82
+ warn: (msg, meta) => emit('warn', msg, meta),
83
+ error: (msg, meta) => emit('error', msg, meta),
84
+ child(childMeta) {
85
+ return createLogger(serviceName, nc, { ...defaultMeta, ...childMeta });
86
+ },
87
+ };
88
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * JetStream stream provisioning for observability data.
3
+ *
4
+ * Creates ring buffer streams for logs, events, and traces on startup.
5
+ * Idempotent — safe to call on every service-loader boot.
6
+ */
7
+
8
+ import { DiscardPolicy, jetstreamManager, RetentionPolicy, StorageType } from '@nats-io/jetstream';
9
+ import { type NatsConnection } from '@nats-io/transport-node';
10
+
11
+ // ── Stream names ────────────────────────────────────────────────────
12
+ export const O11Y_STREAM_LOGS = 'OS_O11Y_LOGS';
13
+ export const O11Y_STREAM_EVENTS = 'OS_O11Y_EVENTS';
14
+ export const O11Y_STREAM_TRACES = 'OS_O11Y_TRACES';
15
+
16
+ // ── Subject prefixes ────────────────────────────────────────────────
17
+ export const O11Y_SUBJECT_LOGS = 'os.o11y.logs.>';
18
+ export const O11Y_SUBJECT_EVENTS = 'os.o11y.events.>';
19
+ export const O11Y_SUBJECT_TRACES = 'os.o11y.traces.>';
20
+
21
+ const MB = 1024 * 1024;
22
+
23
+ interface StreamDef {
24
+ name: string;
25
+ subjects: string[];
26
+ max_bytes: number;
27
+ }
28
+
29
+ const STREAMS: StreamDef[] = [
30
+ { name: O11Y_STREAM_LOGS, subjects: [O11Y_SUBJECT_LOGS], max_bytes: 20 * MB },
31
+ { name: O11Y_STREAM_EVENTS, subjects: [O11Y_SUBJECT_EVENTS], max_bytes: 10 * MB },
32
+ { name: O11Y_STREAM_TRACES, subjects: [O11Y_SUBJECT_TRACES], max_bytes: 5 * MB },
33
+ ];
34
+
35
+ /**
36
+ * Ensure all observability JetStream streams exist with the correct config.
37
+ * Creates streams that don't exist; updates streams whose config has drifted.
38
+ */
39
+ export async function ensureO11yStreams(nc: NatsConnection): Promise<void> {
40
+ const jsm = await jetstreamManager(nc);
41
+
42
+ for (const def of STREAMS) {
43
+ try {
44
+ const info = await jsm.streams.info(def.name);
45
+
46
+ // Check if config needs updating
47
+ const cfg = info.config;
48
+ if (
49
+ cfg.max_bytes !== def.max_bytes ||
50
+ cfg.retention !== RetentionPolicy.Limits ||
51
+ JSON.stringify(cfg.subjects) !== JSON.stringify(def.subjects)
52
+ ) {
53
+ await jsm.streams.update(def.name, {
54
+ subjects: def.subjects,
55
+ max_bytes: def.max_bytes,
56
+ });
57
+ console.log(`[o11y] updated stream ${def.name}`);
58
+ }
59
+ } catch (err: unknown) {
60
+ // Stream doesn't exist — create it
61
+ if (isStreamNotFound(err)) {
62
+ await jsm.streams.add({
63
+ name: def.name,
64
+ subjects: def.subjects,
65
+ retention: RetentionPolicy.Limits,
66
+ storage: StorageType.File,
67
+ max_bytes: def.max_bytes,
68
+ discard: DiscardPolicy.Old,
69
+ });
70
+ console.log(`[o11y] created stream ${def.name} (max ${def.max_bytes / MB}MB)`);
71
+ } else {
72
+ throw err;
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ function isStreamNotFound(err: unknown): boolean {
79
+ if (err && typeof err === 'object') {
80
+ // JetStreamApiError has a code property; 404 = stream not found
81
+ if ('code' in err && (err as { code: number }).code === 404) return true;
82
+ // Fallback: check message
83
+ if ('message' in err && typeof (err as { message: string }).message === 'string') {
84
+ return (err as { message: string }).message.includes('stream not found');
85
+ }
86
+ }
87
+ return false;
88
+ }