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.
- package/.env.example +23 -0
- package/.genie/mailbox/cli-sent.jsonl +3 -0
- package/.genie/mailbox/ds1-wave2-engineer-1.json +15 -0
- package/.genie/mailbox/ds1-wave2-engineer-2.json +15 -0
- package/.genie/mailbox/ds1-wave2-engineer-3.json +15 -0
- package/.genie/state/os-observability.json +39 -0
- package/.genie/state/tmux-control-mode-terminal.json +28 -0
- package/.genie/wishes/genieos-one-theme/WISH.md +417 -0
- package/.genie/wishes/workos-prod-rbac/WISH.md +345 -0
- package/.github/workflows/ci.yml +39 -0
- package/.github/workflows/release.yml +78 -0
- package/.github/workflows/version.yml +122 -0
- package/.husky/pre-commit +1 -0
- package/.pnpm-approve-builds.json +1 -0
- package/CLAUDE.md +117 -0
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/biome.json +124 -0
- package/bun.lock +1249 -0
- package/docs/workos-setup.md +116 -0
- package/ecosystem.config.cjs +26 -0
- package/instrumentation.ts +8 -0
- package/knip.json +35 -0
- package/nats.conf +7 -0
- package/next.config.ts +25 -0
- package/package.json +78 -0
- package/packages/dev3000-app/components.ts +12 -0
- package/packages/dev3000-app/manifest.ts +19 -0
- package/packages/dev3000-app/package.json +23 -0
- package/packages/dev3000-app/views/dev3000/Dev3000App.tsx +758 -0
- package/packages/dev3000-app/views/dev3000/ErrorsPanel.tsx +160 -0
- package/packages/dev3000-app/views/dev3000/dev3000-context.tsx +21 -0
- package/packages/dev3000-app/views/dev3000/index.ts +4 -0
- package/packages/dev3000-app/views/dev3000/schema.ts +55 -0
- package/packages/dev3000-app/views/dev3000/service/index.ts +358 -0
- package/packages/dev3000-app/views/dev3000/service/runtime +1 -0
- package/packages/dev3000-app/views/dev3000/subjects.ts +9 -0
- package/packages/dev3000-app/views/dev3000/types.ts +77 -0
- package/packages/files-app/components.ts +12 -0
- package/packages/files-app/manifest.ts +19 -0
- package/packages/files-app/package.json +23 -0
- package/packages/files-app/views/files/ContextMenu.tsx +151 -0
- package/packages/files-app/views/files/DeleteConfirmDialog.tsx +39 -0
- package/packages/files-app/views/files/FileItem.tsx +128 -0
- package/packages/files-app/views/files/FilesApp.tsx +509 -0
- package/packages/files-app/views/files/FilesListView.tsx +201 -0
- package/packages/files-app/views/files/FilesToolbar.tsx +117 -0
- package/packages/files-app/views/files/GridView.tsx +90 -0
- package/packages/files-app/views/files/InlineInput.tsx +131 -0
- package/packages/files-app/views/files/UploadOverlay.tsx +61 -0
- package/packages/files-app/views/files/schema.ts +49 -0
- package/packages/files-app/views/files/service/index.ts +184 -0
- package/packages/files-app/views/files/service/runtime +1 -0
- package/packages/files-app/views/files/use-files.ts +201 -0
- package/packages/files-app/views/files/use-upload.ts +105 -0
- package/packages/genie-app/components.ts +12 -0
- package/packages/genie-app/lib/subjects.ts +87 -0
- package/packages/genie-app/manifest.ts +19 -0
- package/packages/genie-app/package.json +29 -0
- package/packages/genie-app/views/genie/service/agent-lifecycle.ts +136 -0
- package/packages/genie-app/views/genie/service/cli.ts +114 -0
- package/packages/genie-app/views/genie/service/comms.ts +141 -0
- package/packages/genie-app/views/genie/service/directory.ts +167 -0
- package/packages/genie-app/views/genie/service/index.ts +219 -0
- package/packages/genie-app/views/genie/service/system.ts +123 -0
- package/packages/genie-app/views/genie/service/teams.ts +191 -0
- package/packages/genie-app/views/genie/service/terminal-proxy.ts +184 -0
- package/packages/genie-app/views/genie/service/tmux-control.ts +318 -0
- package/packages/genie-app/views/genie/service/wishes.ts +270 -0
- package/packages/genie-app/views/genie/ui/GenieApp.tsx +5 -0
- package/packages/genie-app/views/genie/ui/PaneCard.tsx +307 -0
- package/packages/genie-app/views/genie/ui/Sidebar.tsx +212 -0
- package/packages/genie-app/views/genie/ui/TabBar.tsx +70 -0
- package/packages/genie-app/views/genie/ui/WorkspaceCanvas.tsx +343 -0
- package/packages/genie-app/views/genie/ui/XTermPane.tsx +306 -0
- package/packages/genie-app/views/genie/ui/hooks/useNatsAction.ts +54 -0
- package/packages/genie-app/views/genie/ui/hooks/useNatsRequest.ts +68 -0
- package/packages/genie-app/views/genie/ui/panels/AgentsPanel.tsx +399 -0
- package/packages/genie-app/views/genie/ui/panels/ChatPanel.tsx +351 -0
- package/packages/genie-app/views/genie/ui/panels/SystemPanel.tsx +195 -0
- package/packages/genie-app/views/genie/ui/panels/TeamsPanel.tsx +560 -0
- package/packages/genie-app/views/genie/ui/panels/WishesPanel.tsx +424 -0
- package/packages/nats-viewer-app/components.ts +12 -0
- package/packages/nats-viewer-app/manifest.ts +18 -0
- package/packages/nats-viewer-app/package.json +14 -0
- package/packages/nats-viewer-app/views/nats-viewer/ActiveSubs.tsx +34 -0
- package/packages/nats-viewer-app/views/nats-viewer/MessageLog.tsx +247 -0
- package/packages/nats-viewer-app/views/nats-viewer/NatsViewer.tsx +209 -0
- package/packages/nats-viewer-app/views/nats-viewer/PublishPanel.tsx +111 -0
- package/packages/nats-viewer-app/views/nats-viewer/RequestPanel.tsx +165 -0
- package/packages/nats-viewer-app/views/nats-viewer/Sidebar.tsx +59 -0
- package/packages/nats-viewer-app/views/nats-viewer/SubjectCatalog.tsx +63 -0
- package/packages/nats-viewer-app/views/nats-viewer/SubscribeInput.tsx +59 -0
- package/packages/nats-viewer-app/views/nats-viewer/index.ts +5 -0
- package/packages/nats-viewer-app/views/nats-viewer/nats-viewer-context.tsx +31 -0
- package/packages/nats-viewer-app/views/nats-viewer/types.ts +7 -0
- package/packages/nats-viewer-app/views/nats-viewer/use-message-buffer.ts +55 -0
- package/packages/os-cli/package.json +18 -0
- package/packages/os-cli/src/commands/events.ts +176 -0
- package/packages/os-cli/src/commands/logs.ts +96 -0
- package/packages/os-cli/src/commands/status.ts +53 -0
- package/packages/os-cli/src/commands/traces.ts +115 -0
- package/packages/os-cli/src/index.ts +15 -0
- package/packages/os-cli/src/lib/formatter.ts +123 -0
- package/packages/os-cli/src/lib/nats.ts +16 -0
- package/packages/os-cli/src/lib/trace-tree.ts +144 -0
- package/packages/os-cli/tsconfig.json +12 -0
- package/packages/os-sdk/package.json +27 -0
- package/packages/os-sdk/src/api/handler.ts +67 -0
- package/packages/os-sdk/src/config.ts +68 -0
- package/packages/os-sdk/src/db/factory.test.ts +42 -0
- package/packages/os-sdk/src/db/factory.ts +72 -0
- package/packages/os-sdk/src/db/migrate.ts +140 -0
- package/packages/os-sdk/src/db/provision.ts +44 -0
- package/packages/os-sdk/src/index.ts +36 -0
- package/packages/os-sdk/src/service/console-intercept.ts +60 -0
- package/packages/os-sdk/src/service/logger.ts +88 -0
- package/packages/os-sdk/src/service/o11y-streams.ts +88 -0
- package/packages/os-sdk/src/service/runtime.ts +259 -0
- package/packages/os-sdk/src/service/trace.ts +71 -0
- package/packages/os-sdk/tsconfig.json +16 -0
- package/packages/os-ui/package.json +13 -0
- package/packages/os-ui/src/index.ts +29 -0
- package/packages/os-ui/src/server.ts +4 -0
- package/packages/os-ui/tsconfig.json +19 -0
- package/packages/settings-app/components.ts +12 -0
- package/packages/settings-app/manifest.ts +18 -0
- package/packages/settings-app/package.json +14 -0
- package/packages/settings-app/views/settings/Settings.tsx +492 -0
- package/packages/terminal-app/components.ts +12 -0
- package/packages/terminal-app/manifest.ts +20 -0
- package/packages/terminal-app/package.json +23 -0
- package/packages/terminal-app/views/terminal/schema.ts +82 -0
- package/packages/terminal-app/views/terminal/service/index.ts +133 -0
- package/packages/terminal-app/views/terminal/service/runtime +1 -0
- package/packages/terminal-app/views/terminal/service/session.ts +290 -0
- package/packages/terminal-app/views/terminal/service/shell-hooks/bashrc-hook.sh +21 -0
- package/packages/terminal-app/views/terminal/types.ts +26 -0
- package/packages/terminal-app/views/terminal/ui/MultiTerminalApp.tsx +615 -0
- package/packages/terminal-app/views/terminal/ui/SplitDragHandle.tsx +91 -0
- package/packages/terminal-app/views/terminal/ui/SplitPaneRenderer.tsx +112 -0
- package/packages/terminal-app/views/terminal/ui/TerminalPane.tsx +478 -0
- package/packages/terminal-app/views/terminal/ui/TerminalTabBar.tsx +131 -0
- package/pnpm-workspace.yaml +9 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/icons/code-server.svg +6 -0
- package/public/icons/default.svg +5 -0
- package/public/icons/dusk/1password.svg +1 -0
- package/public/icons/dusk/activity_monitor.svg +1 -0
- package/public/icons/dusk/app_store.svg +1 -0
- package/public/icons/dusk/atom.svg +1 -0
- package/public/icons/dusk/brave.svg +1 -0
- package/public/icons/dusk/calculator.svg +1 -0
- package/public/icons/dusk/calendar.svg +1 -0
- package/public/icons/dusk/chrome.svg +1 -0
- package/public/icons/dusk/chrome2.svg +1 -0
- package/public/icons/dusk/dashboard.svg +13 -0
- package/public/icons/dusk/discord.svg +1 -0
- package/public/icons/dusk/dropbox.svg +1 -0
- package/public/icons/dusk/electron.svg +1 -0
- package/public/icons/dusk/figma.svg +1 -0
- package/public/icons/dusk/finder.svg +1 -0
- package/public/icons/dusk/finder2.svg +1 -0
- package/public/icons/dusk/finder3.svg +1 -0
- package/public/icons/dusk/firefox.svg +1 -0
- package/public/icons/dusk/framer.svg +1 -0
- package/public/icons/dusk/gimp.svg +1 -0
- package/public/icons/dusk/github_desktop.svg +1 -0
- package/public/icons/dusk/hyper.svg +1 -0
- package/public/icons/dusk/hyper3.svg +1 -0
- package/public/icons/dusk/intellij.svg +1 -0
- package/public/icons/dusk/iterm2.svg +1 -0
- package/public/icons/dusk/itunes.svg +1 -0
- package/public/icons/dusk/mail.svg +1 -0
- package/public/icons/dusk/messenger.svg +1 -0
- package/public/icons/dusk/mongodb.svg +1 -0
- package/public/icons/dusk/notes.svg +1 -0
- package/public/icons/dusk/notion.svg +1 -0
- package/public/icons/dusk/obs.svg +1 -0
- package/public/icons/dusk/pages.svg +1 -0
- package/public/icons/dusk/photos.svg +1 -0
- package/public/icons/dusk/postman.svg +1 -0
- package/public/icons/dusk/preview.svg +1 -0
- package/public/icons/dusk/reminders.svg +1 -0
- package/public/icons/dusk/safari.svg +1 -0
- package/public/icons/dusk/sequel_pro.svg +1 -0
- package/public/icons/dusk/sketch.svg +1 -0
- package/public/icons/dusk/skype.svg +1 -0
- package/public/icons/dusk/slack.svg +1 -0
- package/public/icons/dusk/slack2.svg +1 -0
- package/public/icons/dusk/spotify.svg +1 -0
- package/public/icons/dusk/steam.svg +1 -0
- package/public/icons/dusk/system_preferences.svg +1 -0
- package/public/icons/dusk/tableplus.svg +1 -0
- package/public/icons/dusk/teams.svg +1 -0
- package/public/icons/dusk/telegram.svg +1 -0
- package/public/icons/dusk/terminal.svg +1 -0
- package/public/icons/dusk/todoist.svg +1 -0
- package/public/icons/dusk/trash.svg +1 -0
- package/public/icons/dusk/trello.svg +1 -0
- package/public/icons/dusk/vivaldi.svg +1 -0
- package/public/icons/dusk/vlc.svg +1 -0
- package/public/icons/dusk/vscode.svg +1 -0
- package/public/icons/dusk/whatsapp.svg +1 -0
- package/public/icons/dusk/xeyes.svg +1 -0
- package/public/icons/dusk/zoom.svg +1 -0
- package/public/icons/files.svg +5 -0
- package/public/icons/pwa/icon-192.png +0 -0
- package/public/icons/pwa/icon-512.png +0 -0
- package/public/icons/settings.svg +14 -0
- package/public/icons/terminal.svg +5 -0
- package/public/icons/text-editor.svg +7 -0
- package/public/manifest.json +38 -0
- package/public/next.svg +1 -0
- package/public/sw.js +41 -0
- package/public/vercel.svg +1 -0
- package/public/wallpapers/default.svg +10 -0
- package/public/window.svg +1 -0
- package/scripts/generate-pwa-icons.mjs +33 -0
- package/scripts/install-nats.sh +37 -0
- package/sentry.client.config.ts +21 -0
- package/sentry.edge.config.ts +12 -0
- package/sentry.server.config.ts +12 -0
- package/src/app/api/files/download/route.ts +81 -0
- package/src/app/api/files/download-zip/route.ts +102 -0
- package/src/app/api/files/upload/route.ts +58 -0
- package/src/app/api/webhooks/workos/route.ts +98 -0
- package/src/app/auth/callback/route.ts +16 -0
- package/src/app/auth/logout/route.ts +15 -0
- package/src/app/desktop/desktop-shell.tsx +110 -0
- package/src/app/desktop/layout.tsx +8 -0
- package/src/app/desktop/page.tsx +24 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +7 -0
- package/src/app/layout.tsx +64 -0
- package/src/app/offline/page.tsx +83 -0
- package/src/app/page.tsx +5 -0
- package/src/app/standalone/[appId]/page.tsx +28 -0
- package/src/app/standalone/layout.tsx +10 -0
- package/src/components/app-icon.tsx +55 -0
- package/src/components/apps/_echo/schema.ts +14 -0
- package/src/components/apps/_echo/service/index.ts +42 -0
- package/src/components/apps/app-manifest.ts +97 -0
- package/src/components/apps/app-registry.ts +55 -0
- package/src/components/apps/dev3000/Dev3000App.tsx +224 -0
- package/src/components/apps/dev3000/ErrorsPanel.tsx +160 -0
- package/src/components/apps/dev3000/Sidebar.tsx +41 -0
- package/src/components/apps/dev3000/TimelineLog.tsx +173 -0
- package/src/components/apps/dev3000/dev3000-context.tsx +29 -0
- package/src/components/apps/dev3000/index.ts +4 -0
- package/src/components/apps/dev3000/schema.ts +48 -0
- package/src/components/apps/dev3000/service/index.ts +520 -0
- package/src/components/apps/dev3000/service/runtime +1 -0
- package/src/components/apps/dev3000/types.ts +15 -0
- package/src/components/apps/dev3000/use-message-buffer.ts +46 -0
- package/src/components/apps/files/ContextMenu.tsx +151 -0
- package/src/components/apps/files/DeleteConfirmDialog.tsx +78 -0
- package/src/components/apps/files/FileItem.tsx +128 -0
- package/src/components/apps/files/FilesApp.tsx +509 -0
- package/src/components/apps/files/FilesListView.tsx +201 -0
- package/src/components/apps/files/FilesToolbar.tsx +117 -0
- package/src/components/apps/files/GridView.tsx +90 -0
- package/src/components/apps/files/InlineInput.tsx +131 -0
- package/src/components/apps/files/UploadOverlay.tsx +61 -0
- package/src/components/apps/files/schema.ts +49 -0
- package/src/components/apps/files/service/index.ts +227 -0
- package/src/components/apps/files/service/runtime +1 -0
- package/src/components/apps/files/use-files.ts +201 -0
- package/src/components/apps/files/use-upload.ts +105 -0
- package/src/components/apps/nats-viewer/ActiveSubs.tsx +34 -0
- package/src/components/apps/nats-viewer/MessageLog.tsx +247 -0
- package/src/components/apps/nats-viewer/NatsViewer.tsx +209 -0
- package/src/components/apps/nats-viewer/PublishPanel.tsx +113 -0
- package/src/components/apps/nats-viewer/RequestPanel.tsx +167 -0
- package/src/components/apps/nats-viewer/Sidebar.tsx +62 -0
- package/src/components/apps/nats-viewer/SubjectCatalog.tsx +64 -0
- package/src/components/apps/nats-viewer/SubscribeInput.tsx +59 -0
- package/src/components/apps/nats-viewer/index.ts +5 -0
- package/src/components/apps/nats-viewer/nats-viewer-context.tsx +31 -0
- package/src/components/apps/nats-viewer/types.ts +7 -0
- package/src/components/apps/nats-viewer/use-message-buffer.ts +55 -0
- package/src/components/apps/settings/Settings.tsx +492 -0
- package/src/components/apps/terminal/schema.ts +82 -0
- package/src/components/apps/terminal/service/index.ts +189 -0
- package/src/components/apps/terminal/service/runtime +1 -0
- package/src/components/apps/terminal/service/session.ts +296 -0
- package/src/components/apps/terminal/service/shell-hooks/bashrc-hook.sh +21 -0
- package/src/components/apps/terminal/types.ts +26 -0
- package/src/components/apps/terminal/ui/MultiTerminalApp.tsx +617 -0
- package/src/components/apps/terminal/ui/SplitDragHandle.tsx +91 -0
- package/src/components/apps/terminal/ui/SplitPaneRenderer.tsx +112 -0
- package/src/components/apps/terminal/ui/TerminalPane.tsx +476 -0
- package/src/components/apps/terminal/ui/TerminalTabBar.tsx +131 -0
- package/src/components/desktop/AnimatedBackground.tsx +69 -0
- package/src/components/desktop/Desktop.tsx +79 -0
- package/src/components/desktop/DesktopBackground.tsx +16 -0
- package/src/components/desktop/DesktopIcon.tsx +49 -0
- package/src/components/desktop/ShortcutViewer.tsx +136 -0
- package/src/components/desktop/WindowRenderer.tsx +34 -0
- package/src/components/desktop/WindowSwitcher.tsx +42 -0
- package/src/components/notifications/NotificationCenter.tsx +153 -0
- package/src/components/notifications/NotificationToasts.tsx +66 -0
- package/src/components/notifications/OrphanSessionToast.tsx +293 -0
- package/src/components/os-primitives/collapsible-sidebar.tsx +226 -0
- package/src/components/os-primitives/dialog.tsx +76 -0
- package/src/components/os-primitives/empty-state.tsx +43 -0
- package/src/components/os-primitives/index.ts +21 -0
- package/src/components/os-primitives/list-view.tsx +155 -0
- package/src/components/os-primitives/property-panel.tsx +108 -0
- package/src/components/os-primitives/section-header.tsx +19 -0
- package/src/components/os-primitives/sidebar-nav.tsx +110 -0
- package/src/components/os-primitives/split-pane.tsx +146 -0
- package/src/components/os-primitives/status-badge.tsx +10 -0
- package/src/components/os-primitives/status-bar.tsx +100 -0
- package/src/components/os-primitives/toolbar.tsx +152 -0
- package/src/components/taskbar/AppLauncher.tsx +114 -0
- package/src/components/taskbar/RunningApps.tsx +71 -0
- package/src/components/taskbar/SystemTray.tsx +134 -0
- package/src/components/taskbar/Taskbar.tsx +45 -0
- package/src/components/taskbar/UserMenu.tsx +138 -0
- package/src/components/taskbar/WorkspaceSwitcher.tsx +9 -0
- package/src/components/ui/ContextMenu.tsx +130 -0
- package/src/components/ui/badge.tsx +39 -0
- package/src/components/ui/button.tsx +102 -0
- package/src/components/ui/command.tsx +165 -0
- package/src/components/ui/dropdown-menu.tsx +233 -0
- package/src/components/ui/input.tsx +48 -0
- package/src/components/ui/note.tsx +55 -0
- package/src/components/ui/separator.tsx +25 -0
- package/src/components/ui/spinner.tsx +42 -0
- package/src/components/ui/switch.tsx +36 -0
- package/src/components/ui/theme-provider.tsx +24 -0
- package/src/components/ui/theme-switcher.tsx +51 -0
- package/src/components/ui/tooltip.tsx +62 -0
- package/src/components/window/MobileWindowStack.tsx +218 -0
- package/src/components/window/SnapPreview.tsx +37 -0
- package/src/components/window/StandaloneFrame.tsx +170 -0
- package/src/components/window/Window.tsx +423 -0
- package/src/components/window/WindowContent.tsx +14 -0
- package/src/components/window/WindowControlsOverlay.tsx +89 -0
- package/src/components/window/WindowFrame.tsx +124 -0
- package/src/lib/auth/index.ts +27 -0
- package/src/lib/auth/roles.ts +50 -0
- package/src/lib/auth/types.ts +32 -0
- package/src/lib/auth/use-auth.ts +53 -0
- package/src/lib/auth/webhook-handler.ts +87 -0
- package/src/lib/auth/workos.ts +67 -0
- package/src/lib/constants.ts +1 -0
- package/src/lib/desktop/dedup.ts +57 -0
- package/src/lib/desktop/schema.ts +55 -0
- package/src/lib/files/filename-validation.ts +41 -0
- package/src/lib/files/safe-path.ts +49 -0
- package/src/lib/hooks/use-desktop-nats.ts +438 -0
- package/src/lib/hooks/use-is-mobile.ts +23 -0
- package/src/lib/hooks/use-launch-app.ts +79 -0
- package/src/lib/hooks/use-nats-notifications.ts +84 -0
- package/src/lib/hooks/use-nats.ts +60 -0
- package/src/lib/hooks/use-visual-viewport.ts +72 -0
- package/src/lib/icons/resolve-window-icon.ts +10 -0
- package/src/lib/keyboard/defaults.ts +146 -0
- package/src/lib/keyboard/types.ts +52 -0
- package/src/lib/keyboard/use-global-keybinds.ts +231 -0
- package/src/lib/nats-client.ts +255 -0
- package/src/lib/nats.ts +35 -0
- package/src/lib/notifications/schema.ts +12 -0
- package/src/lib/service-loader.ts +171 -0
- package/src/lib/subjects.ts +64 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/ws-bridge.ts +288 -0
- package/src/lib/ws-protocol.ts +53 -0
- package/src/lib/ws-server.ts +167 -0
- package/src/middleware.ts +57 -0
- package/src/stores/desktop-store.ts +112 -0
- package/src/stores/keybind-store.ts +66 -0
- package/src/stores/notification-store.ts +271 -0
- package/src/stores/theme-store.ts +25 -0
- package/src/stores/window-store.ts +294 -0
- package/src/theme/animations.css +68 -0
- package/src/theme/base.css +123 -0
- package/src/theme/controls.css +35 -0
- package/src/theme/design-tokens.css +276 -0
- package/src/theme/index.css +23 -0
- package/src/theme/menus.css +45 -0
- package/src/theme/status.css +41 -0
- package/src/theme/surfaces.css +94 -0
- package/src/theme/tailwind-map.css +138 -0
- package/src/theme/taskbar.css +25 -0
- package/src/theme/terminal.css +55 -0
- package/src/theme/typography.css +26 -0
- package/src/theme/utilities.css +156 -0
- package/src/theme/window.css +103 -0
- package/src/types/desktop-entry.ts +12 -0
- package/src/types/use-descendants.d.ts +13 -0
- package/src/types/window.ts +28 -0
- package/src/types.d.ts +9 -0
- package/tauri/Cargo.lock +5464 -0
- package/tauri/Cargo.toml +19 -0
- package/tauri/build.rs +3 -0
- package/tauri/capabilities/default.json +36 -0
- package/tauri/icons/128x128.png +0 -0
- package/tauri/icons/128x128@2x.png +0 -0
- package/tauri/icons/32x32.png +0 -0
- package/tauri/icons/icon.png +0 -0
- package/tauri/src/main.rs +396 -0
- package/tauri/tauri.conf.json +23 -0
- 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,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,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
|
+
}
|
package/src/app/page.tsx
ADDED
|
@@ -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,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
|
+
);
|