Full Code of cartesiancs/vessel for AI

main eb91955c98e1 cached
575 files
1.8 MB
459.9k tokens
1756 symbols
1 requests
Download .txt
Showing preview only (1,962K chars total). Download the full file or copy to clipboard to get everything.
Repository: cartesiancs/vessel
Branch: main
Commit: eb91955c98e1
Files: 575
Total size: 1.8 MB

Directory structure:
gitextract_1sirbdjr/

├── .cargo/
│   └── config.toml
├── .github/
│   ├── GIT.md
│   └── workflows/
│       ├── build.yml
│       └── release-macos.yml
├── .gitignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CODE_RULE.md
├── CONTRIBUTING.md
├── Cargo.toml
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── apps/
│   ├── capsule/
│   │   ├── Cargo.toml
│   │   ├── Dockerfile
│   │   ├── docker-compose.yml
│   │   ├── src/
│   │   │   ├── api/
│   │   │   │   ├── auth.rs
│   │   │   │   ├── chat.rs
│   │   │   │   ├── key.rs
│   │   │   │   ├── mod.rs
│   │   │   │   └── routes.rs
│   │   │   ├── config.rs
│   │   │   ├── crypto/
│   │   │   │   ├── encryption.rs
│   │   │   │   ├── keypair.rs
│   │   │   │   └── mod.rs
│   │   │   ├── error.rs
│   │   │   ├── main.rs
│   │   │   ├── services/
│   │   │   │   ├── jwt.rs
│   │   │   │   ├── mod.rs
│   │   │   │   ├── openai.rs
│   │   │   │   └── usage.rs
│   │   │   └── types/
│   │   │       ├── decrypted.rs
│   │   │       ├── encrypted.rs
│   │   │       └── mod.rs
│   │   └── tests/
│   │       ├── api.test.mjs
│   │       ├── crypto.mjs
│   │       └── package.json
│   ├── client/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── components.json
│   │   ├── eslint.config.js
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── App.tsx
│   │   │   ├── app/
│   │   │   │   ├── pageWrapper/
│   │   │   │   │   └── page-wrapper.tsx
│   │   │   │   └── providers/
│   │   │   │       └── theme-provider.tsx
│   │   │   ├── components/
│   │   │   │   ├── icon/
│   │   │   │   │   └── Logo.tsx
│   │   │   │   └── ui/
│   │   │   │       ├── alert-dialog.tsx
│   │   │   │       ├── alert.tsx
│   │   │   │       ├── avatar.tsx
│   │   │   │       ├── badge.tsx
│   │   │   │       ├── breadcrumb.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── command.tsx
│   │   │   │       ├── context-menu.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── dropdown-menu.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── label.tsx
│   │   │   │       ├── navigation-menu.tsx
│   │   │   │       ├── popover.tsx
│   │   │   │       ├── resizable.tsx
│   │   │   │       ├── scroll-area.tsx
│   │   │   │       ├── select.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       ├── sheet.tsx
│   │   │   │       ├── sidebar.tsx
│   │   │   │       ├── skeleton.tsx
│   │   │   │       ├── sonner.tsx
│   │   │   │       ├── switch.tsx
│   │   │   │       ├── table.tsx
│   │   │   │       ├── tabs.tsx
│   │   │   │       ├── textarea.tsx
│   │   │   │       └── tooltip.tsx
│   │   │   ├── contexts/
│   │   │   │   └── SupabaseAuthContext.tsx
│   │   │   ├── entities/
│   │   │   │   ├── configurations/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── codeService.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── custom-nodes/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── presets.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── device/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── device-token/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── dynamic-dashboard/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── interaction.ts
│   │   │   │   │   ├── layoutResolve.ts
│   │   │   │   │   └── store.ts
│   │   │   │   ├── entity/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── file/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── flow/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── ha/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── integrations/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── log/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── map/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── permission/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── recording/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── role/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── stat/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── tunnel/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   └── user/
│   │   │   │       ├── api.ts
│   │   │   │       ├── store.ts
│   │   │   │       └── types.ts
│   │   │   ├── features/
│   │   │   │   ├── account-switcher/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── auth/
│   │   │   │   │   ├── AuthInterceptor.tsx
│   │   │   │   │   ├── DefaultAdminPasswordDialog.tsx
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── hook.ts
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── code/
│   │   │   │   │   ├── CreateItemDialog.tsx
│   │   │   │   │   ├── FileEditor.tsx
│   │   │   │   │   └── FileTree.tsx
│   │   │   │   ├── configurations/
│   │   │   │   │   ├── ConfigurationActionButton.tsx
│   │   │   │   │   ├── ConfigurationCreate.tsx
│   │   │   │   │   └── ConfigurationCreateButton.tsx
│   │   │   │   ├── darkmode/
│   │   │   │   │   └── mode-toggle.tsx
│   │   │   │   ├── dashboard-swipe/
│   │   │   │   │   ├── DashboardSwipeHeader.tsx
│   │   │   │   │   └── DashboardSwipeLayout.tsx
│   │   │   │   ├── device/
│   │   │   │   │   ├── DeviceCreateButton.tsx
│   │   │   │   │   ├── DeviceDeleteButton.tsx
│   │   │   │   │   ├── DeviceKeyButton.tsx
│   │   │   │   │   └── DeviceUpdateButton.tsx
│   │   │   │   ├── device-token/
│   │   │   │   │   └── DeviceTokenManager.tsx
│   │   │   │   ├── dynamic-dashboard/
│   │   │   │   │   ├── GroupCanvas.tsx
│   │   │   │   │   ├── events/
│   │   │   │   │   │   ├── dispatcher.test.ts
│   │   │   │   │   │   └── dispatcher.ts
│   │   │   │   │   └── panels/
│   │   │   │   │       ├── ButtonPanel.tsx
│   │   │   │   │       ├── FlowPanel.tsx
│   │   │   │   │       └── MapPanel.tsx
│   │   │   │   ├── entity/
│   │   │   │   │   ├── AllEntities.tsx
│   │   │   │   │   ├── AnalyzeMenuItem.tsx
│   │   │   │   │   ├── Card.tsx
│   │   │   │   │   ├── EntityCreateButton.tsx
│   │   │   │   │   ├── EntityDeleteButton.tsx
│   │   │   │   │   ├── EntityUpdateButton.tsx
│   │   │   │   │   ├── SelectPlatforms.tsx
│   │   │   │   │   ├── SelectTypes.tsx
│   │   │   │   │   ├── StateHistorySheet.tsx
│   │   │   │   │   └── useEntitiesData.ts
│   │   │   │   ├── error/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── flow/
│   │   │   │   │   ├── AddCustomNode.tsx
│   │   │   │   │   ├── Flow.tsx
│   │   │   │   │   ├── Graph.tsx
│   │   │   │   │   ├── Options.tsx
│   │   │   │   │   ├── OptionsVariation.tsx
│   │   │   │   │   ├── RunFlow.tsx
│   │   │   │   │   ├── SelectedItemActions.tsx
│   │   │   │   │   ├── flow-chat/
│   │   │   │   │   │   ├── buildSystemPrompt.ts
│   │   │   │   │   │   ├── executeToolCalls.ts
│   │   │   │   │   │   ├── flowTools.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── flowNode.ts
│   │   │   │   │   ├── flowTypes.ts
│   │   │   │   │   ├── flowUtils.ts
│   │   │   │   │   └── nodes/
│   │   │   │   │       ├── ButtonNode.tsx
│   │   │   │   │       ├── CalcNode.tsx
│   │   │   │   │       ├── HttpNode.tsx
│   │   │   │   │       ├── IntervalNode.tsx
│   │   │   │   │       ├── LogicNode.tsx
│   │   │   │   │       ├── LoopNode.tsx
│   │   │   │   │       ├── MQTTNode.tsx
│   │   │   │   │       ├── NumberNode.tsx
│   │   │   │   │       ├── ProcessingNode.tsx
│   │   │   │   │       ├── TitleNode.tsx
│   │   │   │   │       └── VarNode.tsx
│   │   │   │   ├── flow-log/
│   │   │   │   │   └── FlowLog.tsx
│   │   │   │   ├── footer/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── gps/
│   │   │   │   │   └── parseGps.ts
│   │   │   │   ├── ha/
│   │   │   │   │   ├── HaEntitiesTable.tsx
│   │   │   │   │   ├── HaStatBlock.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── integration/
│   │   │   │   │   ├── HA.tsx
│   │   │   │   │   ├── Integration.tsx
│   │   │   │   │   ├── ROS.tsx
│   │   │   │   │   ├── SDR.tsx
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── json/
│   │   │   │   │   └── JsonEditor.tsx
│   │   │   │   ├── llm-chat/
│   │   │   │   │   ├── ChatInput.tsx
│   │   │   │   │   ├── ChatMessage.tsx
│   │   │   │   │   ├── ChatMessages.tsx
│   │   │   │   │   ├── ChatPanel.tsx
│   │   │   │   │   ├── ChatPanelContainer.tsx
│   │   │   │   │   ├── ChatPanelMobile.tsx
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   ├── store.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   └── useChatKeyboard.ts
│   │   │   │   ├── log/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── map/
│   │   │   │   │   ├── CurrentLocationMarker.tsx
│   │   │   │   │   ├── MapViewPersistence.tsx
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── style.css
│   │   │   │   ├── map-draw/
│   │   │   │   │   ├── FeatureDetailsPanel.tsx
│   │   │   │   │   ├── FeatureDrawingPreview.tsx
│   │   │   │   │   ├── FeatureEditor.tsx
│   │   │   │   │   ├── FeatureRenderer.tsx
│   │   │   │   │   ├── LayerDialog.tsx
│   │   │   │   │   ├── LayerSidebar.tsx
│   │   │   │   │   ├── MapEvents.tsx
│   │   │   │   │   └── MapToolbar.tsx
│   │   │   │   ├── map-entity/
│   │   │   │   │   ├── EntityDetailsPanel.tsx
│   │   │   │   │   ├── render.tsx
│   │   │   │   │   └── store.ts
│   │   │   │   ├── recording/
│   │   │   │   │   ├── RecordingButton.tsx
│   │   │   │   │   ├── RecordingsList.tsx
│   │   │   │   │   ├── VideoPlaybackDialog.tsx
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── AudioWaveformPlayer.tsx
│   │   │   │   │   │   ├── FrameTimeline.tsx
│   │   │   │   │   │   ├── PlaybackControls.tsx
│   │   │   │   │   │   ├── TimeRuler.tsx
│   │   │   │   │   │   ├── VideoControlBar.tsx
│   │   │   │   │   │   └── WaveformCanvas.tsx
│   │   │   │   │   ├── hooks/
│   │   │   │   │   │   ├── useAudioWaveform.ts
│   │   │   │   │   │   ├── useMediaPlayback.ts
│   │   │   │   │   │   └── useVideoFrames.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── role/
│   │   │   │   │   ├── RoleDialogs.tsx
│   │   │   │   │   └── RoleForm.tsx
│   │   │   │   ├── ros2/
│   │   │   │   │   └── Ros2Dashboard.tsx
│   │   │   │   ├── rtc/
│   │   │   │   │   ├── AudioLevelBar.tsx
│   │   │   │   │   ├── StreamReceiver.tsx
│   │   │   │   │   ├── WebRTCProvider.tsx
│   │   │   │   │   ├── captureFrame.ts
│   │   │   │   │   ├── rtc.ts
│   │   │   │   │   └── turnService.ts
│   │   │   │   ├── sdr/
│   │   │   │   │   ├── SdrAudioPlayer.tsx
│   │   │   │   │   ├── SdrDashboard.tsx
│   │   │   │   │   └── api.ts
│   │   │   │   ├── search/
│   │   │   │   │   └── search-form.tsx
│   │   │   │   ├── server-resource/
│   │   │   │   │   └── resourceUsage.tsx
│   │   │   │   ├── setup/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── sidebar/
│   │   │   │   │   ├── footer.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── stat/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── topbar/
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── style.css
│   │   │   │   ├── user/
│   │   │   │   │   ├── UserRoleAssigner.tsx
│   │   │   │   │   ├── userAdd.tsx
│   │   │   │   │   ├── userDelete.tsx
│   │   │   │   │   ├── userEdit.tsx
│   │   │   │   │   └── userForm.tsx
│   │   │   │   └── ws/
│   │   │   │       ├── FlowUiEventBridge.tsx
│   │   │   │       ├── IsConnected.tsx
│   │   │   │       ├── WebSocketProvider.tsx
│   │   │   │       ├── flowUiAdapters/
│   │   │   │       │   └── toastFlowUiAdapter.ts
│   │   │   │       ├── flowUiEventRouter.test.ts
│   │   │   │       ├── flowUiEventRouter.ts
│   │   │   │       ├── ws.ts
│   │   │   │       └── wsMock.ts
│   │   │   ├── font.css
│   │   │   ├── hooks/
│   │   │   │   ├── use-mobile.ts
│   │   │   │   ├── useDesktopSidecar.ts
│   │   │   │   └── usePreventBackNavigation.ts
│   │   │   ├── index.css
│   │   │   ├── lib/
│   │   │   │   ├── electron.ts
│   │   │   │   ├── geometry-precision.ts
│   │   │   │   ├── geometry.ts
│   │   │   │   ├── jwt.ts
│   │   │   │   ├── storage.ts
│   │   │   │   ├── string.ts
│   │   │   │   ├── supabase.ts
│   │   │   │   ├── time.ts
│   │   │   │   └── utils.ts
│   │   │   ├── main.tsx
│   │   │   ├── pages/
│   │   │   │   ├── auth/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── code/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── dashboard/
│   │   │   │   │   ├── DashboardMainPanel.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── desktop-settings/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── devices/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── dynamic-dashboard/
│   │   │   │   │   ├── DynamicDashboardMainPanel.tsx
│   │   │   │   │   ├── NewDynamicDashboardPanel.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── flow/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── landing/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── map/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── notfound/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── recordings/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── settings/
│   │   │   │   │   ├── account.tsx
│   │   │   │   │   ├── config.tsx
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   ├── integration.tsx
│   │   │   │   │   ├── log.tsx
│   │   │   │   │   ├── networks.tsx
│   │   │   │   │   ├── services.tsx
│   │   │   │   │   └── users.tsx
│   │   │   │   └── setup/
│   │   │   │       └── index.tsx
│   │   │   ├── shared/
│   │   │   │   ├── api/
│   │   │   │   │   └── index.ts
│   │   │   │   ├── demo.ts
│   │   │   │   ├── desktop.ts
│   │   │   │   └── mock/
│   │   │   │       ├── mockAdapter.ts
│   │   │   │       └── mockData.ts
│   │   │   ├── vite-env.d.ts
│   │   │   └── widgets/
│   │   │       ├── auth/
│   │   │       │   ├── AuthenticatedLayout.tsx
│   │   │       │   └── TopBarWrapper.tsx
│   │   │       ├── device-list/
│   │   │       │   └── DeviceList.tsx
│   │   │       ├── entity-list/
│   │   │       │   └── EntityList.tsx
│   │   │       ├── role-table/
│   │   │       │   └── RoleList.tsx
│   │   │       └── user-table/
│   │   │           └── UserList.tsx
│   │   ├── tailwind.config.js
│   │   ├── tsconfig.app.json
│   │   ├── tsconfig.app.tsbuildinfo
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   ├── tsconfig.node.tsbuildinfo
│   │   └── vite.config.ts
│   ├── desktop/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── package.json
│   │   └── src-tauri/
│   │       ├── Cargo.toml
│   │       ├── build.rs
│   │       ├── capabilities/
│   │       │   └── main.json
│   │       ├── entitlements.plist
│   │       ├── icons/
│   │       │   └── icon.icns
│   │       ├── src/
│   │       │   └── main.rs
│   │       └── tauri.conf.json
│   ├── landing/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── components.json
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── public/
│   │   │   └── glb/
│   │   │       ├── Computers.glb
│   │   │       ├── Drone.glb
│   │   │       ├── Engineer.glb
│   │   │       ├── Robot.glb
│   │   │       ├── SecurityCamera.glb
│   │   │       └── Trailer.glb
│   │   ├── src/
│   │   │   ├── App.tsx
│   │   │   ├── components/
│   │   │   │   ├── Footer.tsx
│   │   │   │   ├── Navbar.tsx
│   │   │   │   ├── UsageCharts.tsx
│   │   │   │   ├── sections/
│   │   │   │   │   ├── CapsulePromo.tsx
│   │   │   │   │   ├── Faqs.tsx
│   │   │   │   │   ├── Features.tsx
│   │   │   │   │   ├── FooterCta.tsx
│   │   │   │   │   ├── HeroScene/
│   │   │   │   │   │   ├── CameraRig.tsx
│   │   │   │   │   │   ├── Computers.tsx
│   │   │   │   │   │   ├── HeroScene.tsx
│   │   │   │   │   │   └── SpinningBox.tsx
│   │   │   │   │   ├── IntegrationImage.tsx
│   │   │   │   │   ├── ListCards.tsx
│   │   │   │   │   ├── MidCta.tsx
│   │   │   │   │   ├── ScrollBox.tsx
│   │   │   │   │   ├── ScrollTextReveal.tsx
│   │   │   │   │   ├── SecurityCta.tsx
│   │   │   │   │   ├── SubheadingSection.tsx
│   │   │   │   │   ├── ThreeCards.tsx
│   │   │   │   │   ├── Usecase.tsx
│   │   │   │   │   ├── UsecaseAI.tsx
│   │   │   │   │   └── capsule/
│   │   │   │   │       ├── CapsuleArchitecture.tsx
│   │   │   │   │       ├── CapsuleFaq.tsx
│   │   │   │   │       ├── CapsuleFeatures.tsx
│   │   │   │   │       ├── CapsuleFooterCta.tsx
│   │   │   │   │       ├── CapsuleHero.tsx
│   │   │   │   │       ├── CapsuleHowItWorks.tsx
│   │   │   │   │       ├── CapsuleSecurity.tsx
│   │   │   │   │       ├── CapsuleSubheading.tsx
│   │   │   │   │       ├── CapsuleUsecases.tsx
│   │   │   │   │       └── EncryptionFlowIllustration.tsx
│   │   │   │   └── ui/
│   │   │   │       ├── alert-dialog.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── chart.tsx
│   │   │   │       ├── navigation-menu.tsx
│   │   │   │       └── sonner.tsx
│   │   │   ├── contexts/
│   │   │   │   └── AuthContext.tsx
│   │   │   ├── hooks/
│   │   │   │   └── useUsageData.ts
│   │   │   ├── index.css
│   │   │   ├── lib/
│   │   │   │   ├── billing.ts
│   │   │   │   ├── supabase.ts
│   │   │   │   ├── theatre.ts
│   │   │   │   ├── useFadeInOnScroll.ts
│   │   │   │   └── utils.ts
│   │   │   ├── main.tsx
│   │   │   ├── pages/
│   │   │   │   ├── Capsule.tsx
│   │   │   │   ├── CheckoutSuccess.tsx
│   │   │   │   ├── Contact.tsx
│   │   │   │   ├── Dashboard.tsx
│   │   │   │   ├── Disclaimer.tsx
│   │   │   │   ├── Login.tsx
│   │   │   │   ├── Main.tsx
│   │   │   │   ├── Pricing.tsx
│   │   │   │   ├── Privacy.tsx
│   │   │   │   ├── PrivacyPolicy.tsx
│   │   │   │   ├── Roadmap.tsx
│   │   │   │   ├── Terms.tsx
│   │   │   │   └── UseCase.tsx
│   │   │   └── vite-env.d.ts
│   │   ├── tsconfig.app.json
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   └── vite.config.ts
│   └── server/
│       ├── Cargo.toml
│       ├── build.rs
│       ├── diesel.toml
│       ├── migrations/
│       │   ├── .keep
│       │   ├── 2025-08-07-152325_users/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2025-08-22-092047_map/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2025-09-08-073323_create_rbac_tables/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2025-09-11-151252_custom_node/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2025-09-19-060609_create_streams/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2026-01-12-023507_dyndashboard/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   └── 2026-01-31-000001_create_recordings/
│       │       ├── down.sql
│       │       └── up.sql
│       ├── src/
│       │   ├── broker_mqtt.rs
│       │   ├── config.rs
│       │   ├── db/
│       │   │   ├── conn.rs
│       │   │   ├── mod.rs
│       │   │   ├── models.rs
│       │   │   ├── repository/
│       │   │   │   ├── dashboards.rs
│       │   │   │   ├── mod.rs
│       │   │   │   ├── rbac.rs
│       │   │   │   ├── recordings.rs
│       │   │   │   └── streams.rs
│       │   │   └── schema.rs
│       │   ├── error.rs
│       │   ├── flow/
│       │   │   ├── binary_store.rs
│       │   │   ├── engine.rs
│       │   │   ├── manager_state.rs
│       │   │   ├── mod.rs
│       │   │   ├── nodes/
│       │   │   │   ├── branch.rs
│       │   │   │   ├── calc.rs
│       │   │   │   ├── custom_node.rs
│       │   │   │   ├── dashboard_event_listener.rs
│       │   │   │   ├── decode_h264.rs
│       │   │   │   ├── decode_opus.rs
│       │   │   │   ├── gst_decoder.rs
│       │   │   │   ├── http.rs
│       │   │   │   ├── interval.rs
│       │   │   │   ├── json_modify.rs
│       │   │   │   ├── json_selector.rs
│       │   │   │   ├── log_message.rs
│       │   │   │   ├── logic_operator.rs
│       │   │   │   ├── mod.rs
│       │   │   │   ├── mqtt_publish.rs
│       │   │   │   ├── mqtt_subscribe.rs
│       │   │   │   ├── rtp_stream_in.rs
│       │   │   │   ├── set_variable.rs
│       │   │   │   ├── set_variable_with_exec.rs
│       │   │   │   ├── show_toast.rs
│       │   │   │   ├── start.rs
│       │   │   │   ├── type_converter.rs
│       │   │   │   ├── websocket_on.rs
│       │   │   │   ├── websocket_send.rs
│       │   │   │   └── yolo_detect.rs
│       │   │   └── types.rs
│       │   ├── handler/
│       │   │   ├── auth.rs
│       │   │   ├── configurations.rs
│       │   │   ├── custom_nodes.rs
│       │   │   ├── dashboards.rs
│       │   │   ├── device_tokens.rs
│       │   │   ├── devices.rs
│       │   │   ├── entities.rs
│       │   │   ├── flows.rs
│       │   │   ├── ha.rs
│       │   │   ├── integration.rs
│       │   │   ├── log.rs
│       │   │   ├── map.rs
│       │   │   ├── mod.rs
│       │   │   ├── permissions.rs
│       │   │   ├── recordings.rs
│       │   │   ├── roles.rs
│       │   │   ├── sdr.rs
│       │   │   ├── stat.rs
│       │   │   ├── state.rs
│       │   │   ├── storage.rs
│       │   │   ├── streams.rs
│       │   │   ├── tunnel.rs
│       │   │   ├── users.rs
│       │   │   └── ws/
│       │   │       ├── dashboard_component_event.rs
│       │   │       ├── handlers.rs
│       │   │       ├── mod.rs
│       │   │       └── webrtc.rs
│       │   ├── init/
│       │   │   ├── db_record.rs
│       │   │   ├── mod.rs
│       │   │   └── streams.rs
│       │   ├── lib.rs
│       │   ├── logo.rs
│       │   ├── main.rs
│       │   ├── media/
│       │   │   ├── adapter.rs
│       │   │   ├── mod.rs
│       │   │   ├── rtp_push.rs
│       │   │   └── rtsp_pull.rs
│       │   ├── recording/
│       │   │   ├── manager.rs
│       │   │   ├── mod.rs
│       │   │   ├── muxer.rs
│       │   │   └── service.rs
│       │   ├── routes.rs
│       │   ├── state.rs
│       │   ├── tunnel_control.rs
│       │   └── utils/
│       │       ├── entity_map.rs
│       │       ├── hash.rs
│       │       ├── mod.rs
│       │       ├── stream_checker.rs
│       │       └── system_configs.rs
│       └── tests/
│           ├── common/
│           │   └── mod.rs
│           ├── recording_manager_tests.rs
│           ├── recordings_repository_tests.rs
│           └── state_tests.rs
├── configs/
│   └── projects.json
├── docs/
│   ├── .vitepress/
│   │   └── config.mts
│   ├── concepts.md
│   ├── dashboard.md
│   ├── features.md
│   ├── flow.md
│   ├── index.md
│   ├── installation.md
│   ├── introduction.md
│   ├── map.md
│   ├── setup.md
│   └── troubleshooting.md
├── example/
│   ├── golang/
│   │   ├── .gitignore
│   │   ├── audio-file/
│   │   │   ├── audio.go
│   │   │   └── audio.sdp
│   │   ├── audio-mic/
│   │   │   └── audio.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── video-sample/
│   │   │   └── video.go
│   │   ├── video2-sample/
│   │   │   └── video.go
│   │   └── video3-sample/
│   │       └── video.go
│   └── js/
│       └── index.js
├── jest.config.js
├── package.json
├── packages/
│   ├── PKG.md
│   ├── capsule-client/
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── client.ts
│   │   │   ├── conversation.ts
│   │   │   ├── crypto.ts
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   └── tsconfig.json
│   └── custom-node-utils/
│       ├── add_number.py
│       ├── random.py
│       └── vessel.config.json
├── scripts/
│   └── bundle-macos-deps.sh
└── tests/
    ├── TEST.md
    └── api.test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .cargo/config.toml
================================================
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-args=-Wl,-headerpad_max_install_names"]

[target.x86_64-apple-darwin]
rustflags = ["-C", "link-args=-Wl,-headerpad_max_install_names"]


================================================
FILE: .github/GIT.md
================================================


================================================
FILE: .github/workflows/build.yml
================================================
name: Rust

# on:
#   push:
#     branches: ["main"]
#   pull_request:
#     branches: ["main"]

env:
  CARGO_TERM_COLOR: always
  BINARY_NAME: server

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20.x"

      - name: Install system dependencies
        run: sudo apt-get update && sudo apt-get install -y libglib2.0-dev pkg-config libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev

      - name: Install npm dependencies
        run: npm install

      - name: Build
        run: cargo build --release --verbose
        working-directory: ./apps/server

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ env.BINARY_NAME }}
          path: target/release/${{ env.BINARY_NAME }}


================================================
FILE: .github/workflows/release-macos.yml
================================================
name: Release macOS

on:
  push:
    tags: ['v*']
  workflow_dispatch:

jobs:
  build:
    runs-on: macos-14
    timeout-minutes: 60

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: aarch64-apple-darwin

      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: |
            .
            apps/desktop/src-tauri

      - name: Install GStreamer & GLib
        run: brew install gstreamer glib pkg-config

      - name: Import code signing certificate
        env:
          APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
        run: |
          KEYCHAIN_PATH="$RUNNER_TEMP/build.keychain-db"
          CERT_PATH="$RUNNER_TEMP/cert.p12"

          security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
          security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

          echo -n "$APPLE_CERTIFICATE" | base64 --decode -o "$CERT_PATH"
          security import "$CERT_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" \
            -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
          security set-key-partition-list -S apple-tool:,apple:,codesign: \
            -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

          security list-keychain -d user -s "$KEYCHAIN_PATH" login.keychain
          security default-keychain -s "$KEYCHAIN_PATH"

          security find-identity -v -p codesigning "$KEYCHAIN_PATH"

      - name: Install npm dependencies
        run: npm ci

      - name: Build, sign and notarize
        env:
          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          APPLE_ENTITLEMENTS_PATH: ${{ github.workspace }}/apps/desktop/src-tauri/entitlements.plist
          VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }}
          VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
          VITE_CAPSULE_URL: ${{ secrets.VITE_CAPSULE_URL }}
        working-directory: apps/desktop
        run: npm run tauri -- build --target aarch64-apple-darwin

      - name: Notarize and staple DMG
        env:
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
        run: |
          set -e
          DMG=$(ls target/aarch64-apple-darwin/release/bundle/dmg/*.dmg | head -1)
          echo "==> Submitting DMG for notarization: $DMG"
          xcrun notarytool submit "$DMG" \
            --apple-id "$APPLE_ID" \
            --password "$APPLE_PASSWORD" \
            --team-id "$APPLE_TEAM_ID" \
            --wait
          echo "==> Stapling DMG"
          xcrun stapler staple "$DMG"

      - name: Verify signature and notarization
        run: |
          set -e
          BUNDLE_DIR="target/aarch64-apple-darwin/release/bundle"
          APP=$(ls -d "$BUNDLE_DIR"/macos/*.app | head -1)
          DMG=$(ls "$BUNDLE_DIR"/dmg/*.dmg | head -1)

          echo "==> App: $APP"
          echo "==> DMG: $DMG"

          codesign -dv --verbose=4 "$APP" 2>&1 | grep -E 'Authority|TeamIdentifier|Runtime|Identifier'
          codesign --verify --deep --strict --verbose=2 "$APP"
          spctl -a -vv -t exec "$APP"
          xcrun stapler validate "$DMG"
          spctl -a -vv -t install "$DMG"

      - name: Upload DMG artifact
        uses: actions/upload-artifact@v4
        with:
          name: vessel-macos-arm64
          path: target/aarch64-apple-darwin/release/bundle/dmg/*.dmg
          if-no-files-found: error

      - name: Publish to GitHub Release
        if: startsWith(github.ref, 'refs/tags/')
        uses: softprops/action-gh-release@v2
        with:
          files: target/aarch64-apple-darwin/release/bundle/dmg/*.dmg
          draft: true
          generate_release_notes: true

      - name: Cleanup keychain
        if: always()
        run: security delete-keychain "$RUNNER_TEMP/build.keychain-db" || true


================================================
FILE: .gitignore
================================================
/node_modules
docs/.vitepress/dist
docs/.vitepress/cache
/target
.env
database.db
database.db-*
.DS_Store
*.log.*
*.log
/storage
/config.toml
/.venv
/recordings
/packages/*/node_modules
/packages/*/dist
/packages/*/**/node_modules
/apps/*/**/node_modules
/supabase

================================================
FILE: .prettierrc
================================================
{
  "semi": true,
  "singleQuote": false,
  "jsxSingleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "arrowParens": "always"
}


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

- The use of sexualized language or imagery, and sexual attention or
  advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
  address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
3457xc@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.


================================================
FILE: CODE_RULE.md
================================================
# Coding Rules

This document establishes the official coding standards for all mission-critical software. Adherence to these rules is mandatory to ensure maximum safety, reliability, and security. The guiding principle is to produce code that is robust, predictable, and resilient under all operating conditions by managing the entire development lifecycle, not just the code itself.

## ## Core Principles

### 1. Fail-Safe Principle

All systems must be designed to default to a safe state in the event of any failure. The software must anticipate failures and handle them gracefully without compromising the integrity of the mission or system.

- **R1.1: No Silent Failures.** All fallible operations **must** return an explicit result type (e.g., `Result<T, E>` or a `Promise<T>`). The consuming code **must** exhaustively handle both success and failure states. Linter rules must be configured to fail the build on any unhandled error or result.
- **R1.2: Graceful Degradation.** In the event of a non-critical component failure, the system must continue to operate, albeit in a degraded mode. The software must be able to isolate faults and prevent them from cascading.
- **R1.3: Explicit Resource Management.** Leverage language features for automatic resource cleanup where available (e.g., RAII). For resources not managed automatically, cleanup **must** be guaranteed using deterministic patterns like `try...finally` blocks.

### 2. Deterministic Behavior

The software's output must depend solely on its inputs, and its execution time must be predictable and bounded. Non-deterministic behavior introduces unacceptable risk.

- **R2.1: Minimize and Isolate Dynamic Allocation.** After an initial setup phase, dynamic heap allocation should be avoided in performance-critical loops. Where unavoidable, use patterns like object pooling and pre-allocated buffers with fixed capacity to control memory usage.
- **R2.2: Forbid Recursion.** Recursive function calls are prohibited. All recursive algorithms **must** be implemented iteratively to prevent stack overflow errors.
- **R2.3: Avoid Floating-Point Ambiguity.** Never compare floating-point numbers for exact equality. Comparisons **must** be made within a defined tolerance (`epsilon`).
- **R2.4: Use Fixed-Size Data Structures.** All data structures, including arrays and buffers, **must** have a fixed, statically defined maximum size to prevent buffer overflows.
- **R2.5: Use Named Constants over Magic Numbers.** Do not use unexplained numeric literals. Use the language's constant features (`const`, `static`, `enum`) to define all such values.

### 3. Security by Design

Security is not an afterthought; it is an integral part of the design process. The system must be architected to be secure from the ground up.

- **R3.1: Default to Secure.** The default state of any system parameter **must** be the most secure state. Permissions should be denied by default and only explicitly granted.
- **R3.2: Validate All External Inputs.** All data originating from outside a trusted boundary is considered hostile. It **must** be validated at runtime before use, as compile-time type safety is insufficient for external data.
- **R3.3: Principle of Least Privilege.** Each software module **must** only be granted the permissions and access rights essential for its designated task.
- **R3.4: Keep it Simple.** Code complexity is the enemy of security. Avoid complex control flows and deep nesting. Prefer simple, linear logic that is easy to inspect and verify.
- **R3.5: Ensure Data Integrity.** Use mechanisms like CRCs or checksums to verify the integrity of critical data, especially during transmission or storage.

### 4. Concurrency and Real-Time Behavior

Systems with multiple threads or processes must behave predictably and meet their operational deadlines without fail.

- **R4.1: Avoid Shared Mutable State.** The modification of shared data by multiple threads should be forbidden or strictly controlled. Use language-enforced safety mechanisms where available, and prefer message-passing architectures over shared memory.
- **R4.2: Bounded Execution Time.** All tasks and functions **must** have a provable worst-case execution time (WCET) to ensure the system can meet its real-time deadlines.
- **R4.3: Handle Interrupts with Extreme Care.** Interrupt Service Routines (ISRs) **must** be as short and simple as possible. Forbid any non-deterministic operations within an ISR.

### 5. Verification and Validation (V&V)

It is not enough for code to be well-written; it must be proven to meet its requirements correctly.

- **R5.1: Traceability to Requirements.** Every line of code **must** trace back to a specific, documented requirement. No code should exist that does not fulfill a requirement.
- **R5.2: Mandatory Peer Reviews.** All code **must** be reviewed by at least one other qualified engineer before being integrated. This is a critical step for quality assurance.
- **R5.3: Strive for 100% Test Coverage.** All code **must** be unit-tested, with the goal of achieving 100% statement and branch coverage to ensure every execution path is verified.

### 6. Toolchain and Dependency Integrity

The reliability of the final product depends on the reliability of the tools and libraries used to create it.

- **R6.1: Vet All Third-Party Code.** Any external library or open-source component **must** undergo a rigorous vetting process for security and reliability before it can be used.
- **R6.2: Lock Compiler and Dependency Versions.** The exact versions of the compiler, libraries, and all other build tools **must** be specified and locked using the language's standard lock file mechanism (e.g., `Cargo.lock`, `package-lock.json`).

---

## Minor Rule

### m1. Everything is Strict

A strict development environment minimizes ambiguity and catches errors at the earliest possible stage.

- **R_m1.1: Zero Tolerance for Warnings.** All available compiler and linter checks **must** be enabled at their strictest levels. Any build that produces a warning is considered a failed build and **must** be corrected.
- **R_m1.2: Enforce a Single Coding Standard.** All code **must** be formatted using the standard automated tool for the language (e.g., `rustfmt`, `prettier`). The formatter **must** be run as a pre-commit hook.
- **R_m1.3: Use Comprehensive Static Analysis.** All code **must** pass a comprehensive suite of static analysis tools (e.g., `Clippy`, `ESLint`) without errors before being committed. The linter's ruleset **must** be configured for maximum strictness.


================================================
FILE: CONTRIBUTING.md
================================================
# CONTRIBUTING

...

## Projects Structure

`/apps/client` Frontend code with Vite.  
`/apps/server` Rust server that connects the physical device to the front end.  
`/apps/sentinel-go` Audio transfer test code written in Golang.  
`/apps/landing` Landing page code https://vessel.cartesiancs.com

`/docs` Document code https://vessel.cartesiancs.com/docs  
`/tests` Test code.


================================================
FILE: Cargo.toml
================================================
[workspace]
members = [
    "apps/server",
    "apps/desktop/src-tauri",
    "apps/capsule",
]
resolver = "2"


================================================
FILE: Dockerfile
================================================
FROM rust:latest AS builder

RUN apt-get update && apt-get install -y cmake build-essential libglib2.0-dev pkg-config musl-tools libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev python3-dev

RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
    apt-get install -y nodejs

WORKDIR /app

COPY Cargo.toml Cargo.lock ./
COPY package*.json ./

RUN npm install

COPY ./apps ./apps

WORKDIR /app/apps/client

RUN npm install

WORKDIR /app/apps/server

RUN cargo build --release

WORKDIR /app/target/release

CMD ["./server"]

================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
<p align='center'>
<img src='.github/icon.png' width='140' />
<h1 align='center'>Vessel</h1>

<p align='center'>
<a href="https://github.com/cartesiancs/vessel/blob/main/LICENSE"><img src="https://img.shields.io/github/license/cartesiancs/vessel?style=for-the-badge" /></a>
<a href="https://github.com/cartesiancs/vessel/stargazers"><img src="https://img.shields.io/github/stars/cartesiancs/vessel?style=for-the-badge" /></a>
<a href="https://github.com/cartesiancs/vessel/issues"><img src="https://img.shields.io/github/issues/cartesiancs/vessel?style=for-the-badge" /></a>
<a href="https://vessel.cartesiancs.com/"><img src="https://img.shields.io/badge/Website-Live-2563eb?style=for-the-badge" /></a>
<a href="https://vessel.cartesiancs.com/docs/introduction"><img src="https://img.shields.io/badge/Docs-Ready-7c3aed?style=for-the-badge" /></a>
</p>

<p align='center'>
<a href="https://vessel.cartesiancs.com/">Visit Website</a> · <a href="https://github.com/cartesiancs/vessel/issues">Report Bugs</a> · <a href="https://vessel.cartesiancs.com/docs/introduction">Docs</a> · <a href="https://vsl.cartesiancs.com/">App</a>
</p>

## About The Project

![banner](./.github/banner.png)

Vessel is the **C2 (Command & Control) software** for connecting, monitoring, and orchestrating arrays of physical sensors via an intuitive, visual flow-based interface.

This project is to build a "proactive security system". To achieve this, the following three functions are necessary:

1. **Connect** to Physical Device
2. **Detect** Threats
3. **Control** and Respond

This project solves the problems with existing **home security systems**. Current systems fail to protect against burglaries, trespassing, theft and even war.

So we plan to open-source the technology used in existing defense systems.

This system allows you to analyze video and audio sources with AI/ML technology.

And automate actions through Flow-based operations. The Flow provides the flexibility to select multiple AI models and connect them directly to stream sources.

When everything is implemented, individuals will be able to protect themselves from any threats.

> [!NOTE]
> 🚧 <strong>This project is under active development.</strong> Some features may be unstable or subject to change without notice.

## Features

- Connect all sensers (MQTT, RTP, RTSP, HTTP, ...)
- RTP Audio & Video Streaming
- RTSP Video Streaming
- Real-time streaming support
- Flow Visual Logic
- Custom Script Language Support
- Pub/Sub MQTT with Flow
- Map based UI
- Home Assistant Integration
- ROS2 Integration
- External access support
- Capsule (Zero-Knowledge LLM Call) security.
- Local-first design, Offline-first design.

## Develop

Get your local copy up and running.

#### Prerequisites

- [Rust](https://www.rust-lang.org/) & Cargo
- [Node.js](https://nodejs.org/en/) (v18+) and npm
- [gstreamer](https://gstreamer.freedesktop.org/documentation/rust/git/docs/gstreamer/index.html)
- [mosquitto (MQTT)](https://mosquitto.org/) (additional)

### Option1. Run normally

##### 1. Server Setup

```bash
# 1. Clone the repository
git clone https://github.com/cartesiancs/vessel.git
cd vessel/apps/server

# 2. Copy and configure environment variables
cp .env.example .env
# nano .env (Modify if needed)

# 3. Run database migrations
diesel setup
diesel migration run

# 4. Run the server
cargo run
```

##### 2. Client Setup

```bash
# 1. Install dependencies
npm install

# 2. Run the development server
npm run client
```

### Option2. Run Docker

```bash
docker build -t server .

docker run -p 0.0.0.0:6174:6174 server:latest
```

### Option3. Desktop (Tauri)

```bash
npm run desktop
```

Builds the Rust server sidecar in release mode, starts the Vite dev server for the client, and launches the Tauri shell. For a packaged build use `npm run desktop:build`.

## Compile

This command compiles the entire project, including both the server and the client, into a single executable file.

```bash
npm run build
```

The compiled binary, named 'server', will be located in the target/release directory.

> To run the server executable, you must have a .env file in the same directory (target/release).

## Principles

1. Local-first
2. Offline-first
3. Ultimate control rests with the user

## Troubleshooting

> A more detailed troubleshooting guide will be available soon.

## Roadmap

Please visit our Roadmap page below:

[Roadmap Page >](https://vessel.cartesiancs.com/roadmap)

## Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

Please refer to our [CONTRIBUTING.md](CONTRIBUTING.md) for details.

## Contributors

 <a href = "https://github.com/cartesiancs/vessel/graphs/contributors">
   <img src = "https://contrib.rocks/image?repo=cartesiancs/vessel"/>
 </a>

## License

Distributed under the Apache-2.0 License. See [LICENSE](LICENSE) for more information.

## Disclaimer

This project is intended for academic and research purposes only. It is designed to facilitate the connection and control of physical devices. All responsibility for its use lies with the user.


================================================
FILE: SECURITY.md
================================================
# Security

Please report security issues to `3457xc@gmail.com`.

This email is the maintainer's personal and public email, allowing for immediate responses to security issues. Alternatively, you may also send a message via LinkedIn DM. My LinkedIn profile is: [in/huhhyeongjun](https://www.linkedin.com/in/huhhyeongjun/)


================================================
FILE: apps/capsule/Cargo.toml
================================================
[package]
name = "capsule"
version = "0.1.0"
edition = "2021"

[dependencies]
# Web Framework
tokio = { version = "1", features = ["full"] }
axum = { version = "0.7", features = ["json"] }
tower-http = { version = "0.5", features = ["cors", "trace", "limit"] }
tower = "0.5"

# HTTP types
http = "1"

# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Cryptography
chacha20poly1305 = "0.10"
x25519-dalek = { version = "2", features = ["static_secrets"] }
rand = "0.8"
zeroize = { version = "1", features = ["derive"] }
base64 = "0.22"
hkdf = "0.12"
sha2 = "0.10"

# OpenAI
reqwest = { version = "0.12", features = ["json", "stream"] }
async-stream = "0.3"
futures-util = "0.3"
tokio-stream = "0.1"

# JWT & Auth
jsonwebtoken = "9"
async-trait = "0.1"
chrono = { version = "0.4", features = ["serde"] }
axum-extra = { version = "0.9", features = ["typed-header"] }

# Utilities
anyhow = "1"
thiserror = "1"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
dotenvy = "0.15"

[[bin]]
name = "capsule"
path = "src/main.rs"


================================================
FILE: apps/capsule/Dockerfile
================================================
# Build stage
FROM rust:1.75-alpine AS builder

# 빌드 의존성 설치
RUN apk add --no-cache musl-dev pkgconfig openssl-dev

WORKDIR /app

# 의존성 캐싱을 위해 Cargo 파일 먼저 복사
COPY Cargo.toml Cargo.lock* ./

# 더미 소스로 의존성 빌드 (캐싱)
RUN mkdir src && \
    echo "fn main() {}" > src/main.rs && \
    cargo build --release && \
    rm -rf src

# 실제 소스 복사 및 빌드
COPY src ./src
RUN touch src/main.rs && cargo build --release

# Runtime stage
FROM alpine:3.19

# 보안: non-root 사용자 생성
RUN addgroup -g 1001 capsule && \
    adduser -u 1001 -G capsule -s /bin/sh -D capsule

# 런타임 의존성
RUN apk add --no-cache ca-certificates

WORKDIR /app

# 빌드된 바이너리 복사
COPY --from=builder /app/target/release/capsule .

# 소유권 변경
RUN chown -R capsule:capsule /app

# non-root로 실행
USER capsule

# 포트 노출
EXPOSE 3000

# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

# 환경 변수
ENV RUST_LOG=info
ENV PORT=3000

# 실행
CMD ["./capsule"]


================================================
FILE: apps/capsule/docker-compose.yml
================================================
version: '3.8'

services:
  capsule:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - RUST_LOG=info
      - PORT=3000
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - OPENAI_MODEL=${OPENAI_MODEL:-gpt-4o}
    deploy:
      resources:
        limits:
          memory: 256M
          cpus: '0.5'
    # 보안 설정
    security_opt:
      - no-new-privileges:true
    # 읽기 전용 파일시스템 (민감 데이터 디스크 저장 방지)
    read_only: true
    # 임시 파일용 tmpfs
    tmpfs:
      - /tmp
    # 재시작 정책
    restart: unless-stopped
    # 헬스체크
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s


================================================
FILE: apps/capsule/src/api/auth.rs
================================================
//! Authentication extractor for protected endpoints
//!
//! Extracts and validates JWT tokens from Authorization header.
//! Returns AuthUser with user_id for downstream handlers.

use axum::{
    extract::FromRequestParts,
    http::{request::Parts, StatusCode},
    response::{IntoResponse, Response},
    Json, RequestPartsExt,
};
use axum_extra::{
    headers::{authorization::Bearer, Authorization},
    TypedHeader,
};
use std::sync::Arc;

use crate::services::{JwtError, JwtValidator, SupabaseClaims};

/// Authenticated user information
///
/// Extracted from validated JWT token.
/// Available in handlers as an extractor parameter.
#[derive(Debug, Clone)]
pub struct AuthUser {
    /// User ID (UUID) from JWT sub claim
    pub user_id: String,

    /// User email (optional)
    pub email: Option<String>,

    /// Full JWT claims for additional data
    pub claims: SupabaseClaims,
}

/// Authentication errors
#[derive(Debug)]
pub enum AuthError {
    /// No Authorization header present
    MissingToken,

    /// Token format is invalid
    InvalidToken(String),

    /// Token has expired
    Expired,
}

impl IntoResponse for AuthError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            AuthError::MissingToken => (StatusCode::UNAUTHORIZED, "Authorization token required"),
            AuthError::InvalidToken(msg) => {
                tracing::debug!("Invalid token: {}", msg);
                (StatusCode::UNAUTHORIZED, "Invalid authorization token")
            }
            AuthError::Expired => (StatusCode::UNAUTHORIZED, "Token expired"),
        };

        (status, Json(serde_json::json!({ "error": message }))).into_response()
    }
}

#[async_trait::async_trait]
impl<S> FromRequestParts<S> for AuthUser
where
    S: Send + Sync,
{
    type Rejection = AuthError;

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        // Extract Bearer token from Authorization header
        let TypedHeader(Authorization(bearer)) = parts
            .extract::<TypedHeader<Authorization<Bearer>>>()
            .await
            .map_err(|_| AuthError::MissingToken)?;

        // Get JWT validator from extensions (set by middleware layer)
        let jwt_validator = parts
            .extensions
            .get::<Arc<JwtValidator>>()
            .ok_or_else(|| AuthError::InvalidToken("JWT validator not configured".to_string()))?;

        // Validate token (async - fetches JWKS if needed)
        let claims = jwt_validator
            .validate(bearer.token())
            .await
            .map_err(|e| match e {
                JwtError::Expired => AuthError::Expired,
                _ => AuthError::InvalidToken(e.to_string()),
            })?;

        tracing::debug!(user_id = %claims.sub, "User authenticated");

        Ok(AuthUser {
            user_id: claims.sub.clone(),
            email: claims.email.clone(),
            claims,
        })
    }
}


================================================
FILE: apps/capsule/src/api/chat.rs
================================================
//! Chat API handlers with authentication and usage tracking
//!
//! # Security Data Flow
//! 1. Extract and validate JWT token (AuthUser extractor)
//! 2. Check subscription status (billing_subscriptions table)
//! 3. Check rate limits (enclave_usage table)
//! 4. Process request (decrypt image if present)
//! 5. Track usage (fire-and-forget)
//! 6. Return response

use axum::{
    extract::State,
    response::sse::{Event, Sse},
    Json,
};
use futures_util::{stream::Stream, StreamExt};
use std::sync::Arc;

use crate::api::AuthUser;
use crate::error::CapsuleError;
use crate::types::{ChatRequest, ChatResponse};
use crate::AppState;

/// POST /api/chat
///
/// Receives encrypted image and message, returns AI response.
///
/// # Authentication
/// Requires valid Supabase JWT token in Authorization header.
///
/// # Subscription
/// Only users with active Pro subscription can access this endpoint.
///
/// # Security Data Flow
/// 1. `EncryptedImage` received (safe to log)
/// 2. Decrypted to `DecryptedImage` (memory only)
/// 3. OpenAI API call (using decrypted image)
/// 4. `DecryptedImage` auto-dropped → memory zeroized
/// 5. Response returned
pub async fn chat_handler(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
    Json(req): Json<ChatRequest>,
) -> Result<Json<ChatResponse>, CapsuleError> {
    // 1. Check subscription and rate limits
    let rate_status = state
        .usage_tracker
        .check_rate_limit(&auth.user_id)
        .await
        .map_err(|e| CapsuleError::Internal(e.to_string()))?;

    // 2. Block non-subscribers
    if !rate_status.subscribed {
        tracing::info!(user_id = %auth.user_id, "Subscription required");
        return Err(CapsuleError::SubscriptionRequired);
    }

    // 3. Check rate limit
    if !rate_status.allowed {
        tracing::info!(
            user_id = %auth.user_id,
            reason = ?rate_status.reason,
            "Rate limited"
        );
        return Err(CapsuleError::RateLimited(
            rate_status.reason.unwrap_or_else(|| "Rate limit exceeded".to_string()),
        ));
    }

    // 3.5. Validate history
    req.validate_history()?;

    tracing::info!(
        user_id = %auth.user_id,
        message_len = req.message.len(),
        has_image = req.encrypted_image.is_some(),
        history_len = req.history.as_ref().map(|h| h.len()).unwrap_or(0),
        has_system_prompt = req.system_prompt.is_some(),
        "Chat request"
    );

    let is_image_request = req.encrypted_image.is_some();
    let history_slice = req.history.as_deref();
    let system_prompt = req.system_prompt.as_deref();
    let tools = req.tools.as_ref();
    let tool_choice = req.tool_choice.as_ref();

    // 4. Process request
    let result = if let Some(encrypted_image) = req.encrypted_image {
        let decrypted = state.key_manager.decrypt(&encrypted_image).await?;

        tracing::debug!("Image decrypted: {} bytes", decrypted.len());

        state
            .openai
            .analyze_image(&req.message, decrypted, system_prompt, history_slice, tools, tool_choice)
            .await
            .map_err(|e| CapsuleError::OpenAIError(e.to_string()))?
    } else {
        state
            .openai
            .chat(&req.message, system_prompt, history_slice, tools, tool_choice)
            .await
            .map_err(|e| CapsuleError::OpenAIError(e.to_string()))?
    };

    tracing::info!(
        user_id = %auth.user_id,
        response_len = result.content.len(),
        has_tool_calls = result.tool_calls.is_some(),
        input_tokens = result.usage.prompt_tokens,
        output_tokens = result.usage.completion_tokens,
        "Chat response generated"
    );

    // 5. Track usage (fire-and-forget)
    let tracker = state.usage_tracker.clone();
    let user_id = auth.user_id.clone();
    let input_tokens = result.usage.prompt_tokens;
    let output_tokens = result.usage.completion_tokens;
    tokio::spawn(async move {
        if let Err(e) = tracker.track_request(&user_id, input_tokens, output_tokens, is_image_request).await {
            tracing::warn!(error = %e, "Failed to track usage");
        }
    });

    Ok(Json(ChatResponse {
        response: result.content,
        tool_calls: result.tool_calls,
    }))
}

/// POST /api/chat/stream
///
/// Returns streaming response via Server-Sent Events.
///
/// # Authentication
/// Requires valid Supabase JWT token in Authorization header.
///
/// # Subscription
/// Only users with active Pro subscription can access this endpoint.
pub async fn chat_stream_handler(
    State(state): State<Arc<AppState>>,
    auth: AuthUser,
    Json(req): Json<ChatRequest>,
) -> Result<Sse<impl Stream<Item = Result<Event, CapsuleError>>>, CapsuleError> {
    // 1. Check subscription and rate limits
    let rate_status = state
        .usage_tracker
        .check_rate_limit(&auth.user_id)
        .await
        .map_err(|e| CapsuleError::Internal(e.to_string()))?;

    // 2. Block non-subscribers
    if !rate_status.subscribed {
        tracing::info!(user_id = %auth.user_id, "Subscription required");
        return Err(CapsuleError::SubscriptionRequired);
    }

    // 3. Check rate limit
    if !rate_status.allowed {
        tracing::info!(
            user_id = %auth.user_id,
            reason = ?rate_status.reason,
            "Rate limited"
        );
        return Err(CapsuleError::RateLimited(
            rate_status.reason.unwrap_or_else(|| "Rate limit exceeded".to_string()),
        ));
    }

    // 3.5. Validate history
    req.validate_history()?;

    tracing::info!(
        user_id = %auth.user_id,
        message_len = req.message.len(),
        has_image = req.encrypted_image.is_some(),
        history_len = req.history.as_ref().map(|h| h.len()).unwrap_or(0),
        has_system_prompt = req.system_prompt.is_some(),
        "Stream chat request"
    );

    let is_image_request = req.encrypted_image.is_some();
    let history_slice = req.history.as_deref();
    let system_prompt = req.system_prompt.as_deref();
    let tools = req.tools.as_ref();
    let tool_choice = req.tool_choice.as_ref();

    // 4. Process request
    let (stream, usage) = if let Some(encrypted_image) = req.encrypted_image {
        let decrypted = state.key_manager.decrypt(&encrypted_image).await?;
        tracing::debug!("Image decrypted for streaming: {} bytes", decrypted.len());

        state
            .openai
            .analyze_image_stream(&req.message, decrypted, system_prompt, history_slice, tools, tool_choice)
            .await
            .map_err(|e| CapsuleError::OpenAIError(e.to_string()))?
    } else {
        state
            .openai
            .chat_stream(&req.message, system_prompt, history_slice, tools, tool_choice)
            .await
            .map_err(|e| CapsuleError::OpenAIError(e.to_string()))?
    };

    // 5. Track usage after stream completes (fire-and-forget)
    let tracker = state.usage_tracker.clone();
    let user_id = auth.user_id.clone();

    // Wrap the stream to track usage when it completes
    let wrapped_stream = async_stream::stream! {
        let mut inner = std::pin::pin!(stream);
        while let Some(item) = inner.next().await {
            yield item;
        }

        // Stream completed, now track usage
        let (input_tokens, output_tokens) = {
            let guard = usage.lock().unwrap();
            (guard.prompt_tokens, guard.completion_tokens)
        };

        tracing::info!(
            user_id = %user_id,
            input_tokens = input_tokens,
            output_tokens = output_tokens,
            "Stream completed, tracking usage"
        );

        let tracker_clone = tracker.clone();
        let user_id_clone = user_id.clone();
        tokio::spawn(async move {
            if let Err(e) = tracker_clone.track_request(&user_id_clone, input_tokens, output_tokens, is_image_request).await {
                tracing::warn!(error = %e, "Failed to track usage");
            }
        });
    };

    Ok(Sse::new(wrapped_stream))
}


================================================
FILE: apps/capsule/src/api/key.rs
================================================
use axum::{extract::State, Json};
use std::sync::Arc;

use crate::types::PublicKeyResponse;
use crate::AppState;

/// GET /api/public-key
///
/// 서버의 공개키를 반환합니다.
/// 클라이언트는 이 키로 이미지를 암호화합니다.
///
/// # Response
/// ```json
/// {
///   "public_key": "base64_encoded_public_key",
///   "key_id": "key_identifier",
///   "expires_at": "2024-01-01T00:00:00Z"
/// }
/// ```
pub async fn public_key_handler(State(state): State<Arc<AppState>>) -> Json<PublicKeyResponse> {
    let (public_key, key_id, expires_at) = state.key_manager.current_public_key().await;

    tracing::info!(key_id = %key_id, "Public key requested");

    Json(PublicKeyResponse {
        public_key,
        key_id,
        expires_at,
    })
}


================================================
FILE: apps/capsule/src/api/mod.rs
================================================
mod auth;
mod chat;
mod key;
mod routes;

pub use auth::AuthUser;
pub use chat::{chat_handler, chat_stream_handler};
pub use key::public_key_handler;
pub use routes::{create_router, RouterConfig};


================================================
FILE: apps/capsule/src/api/routes.rs
================================================
use axum::{
    routing::{get, post},
    Extension, Router,
};
use std::sync::Arc;
use tower_http::{
    cors::{AllowHeaders, AllowMethods, AllowOrigin, CorsLayer},
    limit::RequestBodyLimitLayer,
    trace::TraceLayer,
};

use crate::api::{chat_handler, chat_stream_handler, public_key_handler};
use crate::services::JwtValidator;
use crate::AppState;

/// Max request body size: 100MB (for encrypted images)
const MAX_BODY_SIZE: usize = 100 * 1024 * 1024;

/// Router configuration
pub struct RouterConfig {
    /// Allowed CORS origins
    pub allowed_origins: Vec<String>,
}

/// Create API router
pub fn create_router(
    state: Arc<AppState>,
    config: &RouterConfig,
    jwt_validator: Arc<JwtValidator>,
) -> Router {
    // CORS configuration
    let cors = build_cors_layer(config);

    Router::new()
        // Health check (public)
        .route("/health", get(health_handler))
        // Public Key endpoint (public)
        .route("/api/public-key", get(public_key_handler))
        // Chat endpoints (authenticated)
        .route("/api/chat", post(chat_handler))
        .route("/api/chat/stream", post(chat_stream_handler))
        // Middleware
        .layer(TraceLayer::new_for_http())
        .layer(cors)
        .layer(RequestBodyLimitLayer::new(MAX_BODY_SIZE))
        // JWT validator extension for auth extractor
        .layer(Extension(jwt_validator))
        // State
        .with_state(state)
}

/// Build CORS layer
fn build_cors_layer(config: &RouterConfig) -> CorsLayer {
    let allowed_origins = &config.allowed_origins;

    if allowed_origins.is_empty() || allowed_origins.iter().any(|o| o == "*") {
        // Development: Allow all origins (with warning)
        tracing::warn!("CORS: Allowing all origins (development mode)");
        CorsLayer::new()
            .allow_origin(AllowOrigin::any())
            .allow_methods(AllowMethods::list([
                http::Method::GET,
                http::Method::POST,
                http::Method::OPTIONS,
            ]))
            .allow_headers(AllowHeaders::list([
                http::header::CONTENT_TYPE,
                http::header::AUTHORIZATION,
            ]))
    } else {
        // Production: Allow only specified origins
        tracing::info!("CORS: Allowing origins: {:?}", allowed_origins);
        let origins: Vec<_> = allowed_origins
            .iter()
            .filter_map(|o| o.parse().ok())
            .collect();

        CorsLayer::new()
            .allow_origin(origins)
            .allow_methods(AllowMethods::list([
                http::Method::GET,
                http::Method::POST,
                http::Method::OPTIONS,
            ]))
            .allow_headers(AllowHeaders::list([
                http::header::CONTENT_TYPE,
                http::header::AUTHORIZATION,
            ]))
    }
}

/// Health check handler
async fn health_handler() -> &'static str {
    "OK"
}


================================================
FILE: apps/capsule/src/config.rs
================================================
use zeroize::Zeroizing;

use crate::error::CapsuleError;

/// Application configuration
pub struct Config {
    /// Server port
    pub port: u16,
    /// OpenAI API key (protected with Zeroizing)
    pub openai_api_key: Zeroizing<String>,
    /// OpenAI model (default: gpt-4o)
    pub openai_model: String,
    /// Allowed CORS origins
    pub allowed_origins: Vec<String>,
    /// Supabase project URL
    pub supabase_url: String,
    /// Supabase service role key (protected with Zeroizing)
    pub supabase_service_key: Zeroizing<String>,
    /// Key rotation interval in seconds (default: 86400 = 24 hours)
    pub key_rotation_interval_secs: u64,
    /// Grace period for previous key in seconds (default: 300 = 5 minutes)
    pub key_grace_period_secs: u64,
}

impl Config {
    /// Load configuration from environment variables
    ///
    /// # Security
    /// - API keys are protected with `Zeroizing<String>`
    /// - Sensitive environment variables are removed after loading
    pub fn from_env() -> Result<Self, CapsuleError> {
        dotenvy::dotenv().ok();

        // Load OpenAI API key
        let openai_api_key = std::env::var("OPENAI_API_KEY").map_err(|_| {
            CapsuleError::ConfigError("OPENAI_API_KEY environment variable is required".to_string())
        })?;

        // Load Supabase configuration
        let supabase_url = std::env::var("SUPABASE_URL").map_err(|_| {
            CapsuleError::ConfigError("SUPABASE_URL environment variable is required".to_string())
        })?;

        let supabase_service_key = std::env::var("SUPABASE_SERVICE_KEY").map_err(|_| {
            CapsuleError::ConfigError(
                "SUPABASE_SERVICE_KEY environment variable is required".to_string(),
            )
        })?;

        // Security: Remove sensitive environment variables
        std::env::remove_var("OPENAI_API_KEY");
        std::env::remove_var("SUPABASE_SERVICE_KEY");
        tracing::debug!("Removed sensitive environment variables");

        let port = std::env::var("PORT")
            .unwrap_or_else(|_| "3000".to_string())
            .parse()
            .unwrap_or(3000);

        let openai_model = std::env::var("OPENAI_MODEL").unwrap_or_else(|_| "gpt-4o".to_string());

        // CORS allowed origins (comma-separated)
        let allowed_origins = std::env::var("ALLOWED_ORIGINS")
            .unwrap_or_else(|_| "*".to_string())
            .split(',')
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .collect();

        let key_rotation_interval_secs = std::env::var("KEY_ROTATION_INTERVAL_SECS")
            .unwrap_or_else(|_| "86400".to_string())
            .parse()
            .unwrap_or(86400);

        let key_grace_period_secs = std::env::var("KEY_GRACE_PERIOD_SECS")
            .unwrap_or_else(|_| "300".to_string())
            .parse()
            .unwrap_or(300);

        Ok(Self {
            port,
            openai_api_key: Zeroizing::new(openai_api_key),
            openai_model,
            allowed_origins,
            supabase_url,
            supabase_service_key: Zeroizing::new(supabase_service_key),
            key_rotation_interval_secs,
            key_grace_period_secs,
        })
    }
}


================================================
FILE: apps/capsule/src/crypto/encryption.rs
================================================
use base64::{engine::general_purpose::STANDARD, Engine};
use chacha20poly1305::{
    aead::{Aead, KeyInit},
    XChaCha20Poly1305, XNonce,
};
use hkdf::Hkdf;
use sha2::Sha256;
use x25519_dalek::{PublicKey, StaticSecret};
use zeroize::Zeroize;

use crate::error::CapsuleError;
use crate::types::{DecryptedImage, EncryptedImage};

/// HKDF에 사용할 salt와 info
const HKDF_SALT: &[u8] = b"vessel-capsule-v1-salt";
const HKDF_INFO: &[u8] = b"vessel-capsule-v1-key";

/// 특정 비밀키로 암호화된 이미지 복호화
///
/// # 보안
/// - Shared secret은 함수 종료 시 zeroize됨
/// - 반환되는 `DecryptedImage`는 Drop 시 자동 zeroize
///
/// # 암호화 스킴
/// 1. X25519 ECDH로 shared secret 계산
/// 2. HKDF-SHA256으로 대칭키 유도
/// 3. XChaCha20-Poly1305로 복호화 (AEAD)
pub(crate) fn decrypt_with_secret(
    server_secret: &StaticSecret,
    encrypted: &EncryptedImage,
) -> Result<DecryptedImage, CapsuleError> {
    // 1. Base64 디코딩
    let ephemeral_pk_bytes = STANDARD
        .decode(&encrypted.ephemeral_public_key)
        .map_err(|_| CapsuleError::InvalidPublicKey)?;

    let nonce_bytes = STANDARD
        .decode(&encrypted.nonce)
        .map_err(|_| CapsuleError::InvalidNonce)?;

    let ciphertext = STANDARD
        .decode(&encrypted.ciphertext)
        .map_err(|_| CapsuleError::InvalidCiphertext)?;

    // 2. 공개키 파싱 (32 bytes)
    let ephemeral_pk: [u8; 32] = ephemeral_pk_bytes
        .try_into()
        .map_err(|_| CapsuleError::InvalidPublicKey)?;
    let client_public = PublicKey::from(ephemeral_pk);

    // 3. Shared Secret 계산 (X25519 ECDH)
    let mut shared_secret = server_secret.diffie_hellman(&client_public);

    // 4. HKDF로 대칭키 유도 (32 bytes for XChaCha20)
    let mut symmetric_key = [0u8; 32];
    let hkdf = Hkdf::<Sha256>::new(Some(HKDF_SALT), shared_secret.as_bytes());
    hkdf.expand(HKDF_INFO, &mut symmetric_key)
        .map_err(|_| CapsuleError::CipherInitFailed)?;

    // Shared secret 즉시 클리어
    shared_secret.zeroize();

    // 5. Nonce 파싱 (24 bytes for XChaCha20)
    let nonce: [u8; 24] = nonce_bytes
        .try_into()
        .map_err(|_| CapsuleError::InvalidNonce)?;

    // 6. XChaCha20-Poly1305로 복호화
    let cipher =
        XChaCha20Poly1305::new_from_slice(&symmetric_key).map_err(|_| CapsuleError::CipherInitFailed)?;

    // 대칭키 즉시 클리어
    symmetric_key.zeroize();

    let plaintext = cipher
        .decrypt(XNonce::from_slice(&nonce), ciphertext.as_ref())
        .map_err(|_| CapsuleError::DecryptionFailed)?;

    tracing::debug!("Successfully decrypted {} bytes", plaintext.len());

    Ok(DecryptedImage::new(plaintext))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_decrypt_invalid_public_key() {
        let secret = StaticSecret::random_from_rng(rand::thread_rng());

        let encrypted = EncryptedImage {
            ephemeral_public_key: "invalid".to_string(),
            nonce: STANDARD.encode([0u8; 24]),
            ciphertext: STANDARD.encode(vec![0u8; 32]),
            key_id: None,
        };

        let result = decrypt_with_secret(&secret, &encrypted);
        assert!(matches!(result, Err(CapsuleError::InvalidPublicKey)));
    }

    #[test]
    fn test_decrypt_invalid_nonce() {
        let secret = StaticSecret::random_from_rng(rand::thread_rng());

        let encrypted = EncryptedImage {
            ephemeral_public_key: STANDARD.encode([0u8; 32]),
            nonce: "invalid".to_string(),
            ciphertext: STANDARD.encode(vec![0u8; 32]),
            key_id: None,
        };

        let result = decrypt_with_secret(&secret, &encrypted);
        assert!(matches!(result, Err(CapsuleError::InvalidNonce)));
    }
}


================================================
FILE: apps/capsule/src/crypto/keypair.rs
================================================
use base64::{engine::general_purpose::STANDARD, Engine};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use x25519_dalek::{PublicKey, StaticSecret};

use crate::crypto::decrypt_with_secret;
use crate::error::CapsuleError;
use crate::types::{DecryptedImage, EncryptedImage};

/// 버전이 지정된 키 쌍
struct VersionedKey {
    /// 키 고유 식별자 (public key base64의 앞 16자)
    key_id: String,
    /// Private Key
    secret: StaticSecret,
    /// Public Key
    public: PublicKey,
    /// 생성 시각
    created_at: std::time::Instant,
}

impl VersionedKey {
    fn new() -> Self {
        let secret = StaticSecret::random_from_rng(rand::thread_rng());
        let public = PublicKey::from(&secret);
        let key_id = STANDARD.encode(public.as_bytes())[..16].to_string();

        tracing::info!(key_id = %key_id, "New versioned key pair generated");

        Self {
            key_id,
            secret,
            public,
            created_at: std::time::Instant::now(),
        }
    }

    fn public_key_base64(&self) -> String {
        STANDARD.encode(self.public.as_bytes())
    }
}

/// 현재/이전 키 슬롯
struct KeySlots {
    /// 현재 활성 키 (새 클라이언트에게 배포)
    current: VersionedKey,
    /// 이전 키 (grace period 동안 유지)
    previous: Option<VersionedKey>,
}

/// 서버 키 쌍 관리자 (로테이션 지원)
///
/// # 보안 특성
/// - Private Key는 절대 외부로 노출되지 않음
/// - 복호화는 `decrypt()` 메서드를 통해서만 가능
/// - 키 로테이션으로 침해 범위를 로테이션 주기로 한정
/// - Drop 시 모든 Private Key 자동 zeroize (StaticSecret 내장)
///
/// # Two-Slot Key Model
/// - `current`: 새 클라이언트에게 배포, 새 요청 복호화
/// - `previous`: grace period 동안 유지, 이전 키를 캐싱한 클라이언트 지원
pub struct KeyManager {
    keys: Arc<RwLock<KeySlots>>,
    rotation_interval: Duration,
    grace_period: Duration,
}

impl KeyManager {
    /// 새 KeyManager 생성 (로테이션 설정 포함)
    ///
    /// # 보안
    /// - 키는 메모리에만 존재
    /// - 디스크에 저장되지 않음
    /// - 컨테이너 재시작 시 새 키 생성
    pub fn new(rotation_interval: Duration, grace_period: Duration) -> Self {
        let current = VersionedKey::new();
        tracing::info!(
            key_id = %current.key_id,
            rotation_interval_secs = rotation_interval.as_secs(),
            grace_period_secs = grace_period.as_secs(),
            "KeyManager initialized"
        );

        Self {
            keys: Arc::new(RwLock::new(KeySlots {
                current,
                previous: None,
            })),
            rotation_interval,
            grace_period,
        }
    }

    /// 현재 공개키 정보 반환 (key_id, expires_at 포함)
    pub async fn current_public_key(&self) -> (String, String, String) {
        let keys = self.keys.read().await;
        let elapsed = keys.current.created_at.elapsed();
        let remaining = self.rotation_interval.saturating_sub(elapsed);
        let expiry = chrono::Utc::now()
            + chrono::Duration::from_std(remaining).unwrap_or(chrono::Duration::zero());
        (
            keys.current.public_key_base64(),
            keys.current.key_id.clone(),
            expiry.to_rfc3339(),
        )
    }

    /// 복호화 수행 (key_id로 적절한 키 선택)
    ///
    /// # 키 선택 로직
    /// - `key_id` 있으면: 매칭되는 키로 복호화
    /// - `key_id` 없으면 (하위 호환): current → previous 순서로 시도
    pub async fn decrypt(&self, encrypted: &EncryptedImage) -> Result<DecryptedImage, CapsuleError> {
        let keys = self.keys.read().await;

        match &encrypted.key_id {
            Some(kid) => {
                // key_id가 명시된 경우: 정확히 매칭되는 키 사용
                if kid == &keys.current.key_id {
                    decrypt_with_secret(&keys.current.secret, encrypted)
                } else if let Some(prev) = &keys.previous {
                    if kid == &prev.key_id {
                        tracing::debug!(key_id = %kid, "Decrypting with previous key");
                        decrypt_with_secret(&prev.secret, encrypted)
                    } else {
                        tracing::warn!(
                            requested_key_id = %kid,
                            current_key_id = %keys.current.key_id,
                            "Unknown key_id requested"
                        );
                        Err(CapsuleError::DecryptionFailed)
                    }
                } else {
                    tracing::warn!(
                        requested_key_id = %kid,
                        current_key_id = %keys.current.key_id,
                        "Unknown key_id requested (no previous key)"
                    );
                    Err(CapsuleError::DecryptionFailed)
                }
            }
            None => {
                // key_id가 없는 경우 (하위 호환): current → previous 순서로 시도
                match decrypt_with_secret(&keys.current.secret, encrypted) {
                    Ok(result) => Ok(result),
                    Err(_) => {
                        if let Some(prev) = &keys.previous {
                            tracing::debug!("Retrying decryption with previous key (no key_id)");
                            decrypt_with_secret(&prev.secret, encrypted)
                        } else {
                            Err(CapsuleError::DecryptionFailed)
                        }
                    }
                }
            }
        }
    }

    /// 키 로테이션: current → previous, 새 키 → current
    async fn rotate(&self) {
        let mut keys = self.keys.write().await;
        let new_key = VersionedKey::new();

        tracing::info!(
            new_key_id = %new_key.key_id,
            old_key_id = %keys.current.key_id,
            "Key rotation: new key generated"
        );

        let old_current = std::mem::replace(&mut keys.current, new_key);
        // 이전 previous가 있으면 여기서 drop → StaticSecret zeroize
        keys.previous = Some(old_current);
    }

    /// 이전 키 제거 (grace period 만료 후 호출)
    async fn clear_previous(&self) {
        let mut keys = self.keys.write().await;
        if let Some(prev) = keys.previous.take() {
            tracing::info!(
                key_id = %prev.key_id,
                "Previous key cleared (grace period expired)"
            );
            // prev drop → StaticSecret zeroize
        }
    }

    /// 백그라운드 로테이션 태스크 시작
    pub fn start_rotation_task(self: &Arc<Self>) -> tokio::task::JoinHandle<()> {
        let manager = Arc::clone(self);
        let rotation_interval = self.rotation_interval;
        let grace_period = self.grace_period;

        tokio::spawn(async move {
            loop {
                tokio::time::sleep(rotation_interval).await;

                tracing::info!("Key rotation triggered");
                manager.rotate().await;

                // Grace period 후 이전 키 제거
                let manager_clone = Arc::clone(&manager);
                tokio::spawn(async move {
                    tokio::time::sleep(grace_period).await;
                    manager_clone.clear_previous().await;
                });
            }
        })
    }
}

impl Drop for KeyManager {
    fn drop(&mut self) {
        tracing::info!("KeyManager dropped, all secret keys will be zeroized");
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::EncryptedImage;
    use base64::{engine::general_purpose::STANDARD, Engine};
    use chacha20poly1305::{
        aead::{Aead, KeyInit},
        XChaCha20Poly1305, XNonce,
    };
    use hkdf::Hkdf;
    use sha2::Sha256;

    /// 테스트용: 특정 공개키로 이미지를 암호화
    fn encrypt_for_key(public_key: &PublicKey, data: &[u8]) -> (EncryptedImage, String) {
        let ephemeral_secret = StaticSecret::random_from_rng(rand::thread_rng());
        let ephemeral_public = PublicKey::from(&ephemeral_secret);

        let shared_secret = ephemeral_secret.diffie_hellman(public_key);

        let mut symmetric_key = [0u8; 32];
        let hkdf = Hkdf::<Sha256>::new(
            Some(b"vessel-capsule-v1-salt"),
            shared_secret.as_bytes(),
        );
        hkdf.expand(b"vessel-capsule-v1-key", &mut symmetric_key)
            .unwrap();

        let cipher = XChaCha20Poly1305::new_from_slice(&symmetric_key).unwrap();
        let nonce_bytes: [u8; 24] = rand::random();
        let ciphertext = cipher
            .encrypt(XNonce::from_slice(&nonce_bytes), data)
            .unwrap();

        let key_id = STANDARD.encode(public_key.as_bytes())[..16].to_string();

        let encrypted = EncryptedImage {
            ephemeral_public_key: STANDARD.encode(ephemeral_public.as_bytes()),
            nonce: STANDARD.encode(nonce_bytes),
            ciphertext: STANDARD.encode(&ciphertext),
            key_id: Some(key_id.clone()),
        };

        (encrypted, key_id)
    }

    #[tokio::test]
    async fn test_rotation_creates_new_key() {
        let km = Arc::new(KeyManager::new(
            Duration::from_secs(3600),
            Duration::from_secs(60),
        ));
        let (_, id1, _) = km.current_public_key().await;
        km.rotate().await;
        let (_, id2, _) = km.current_public_key().await;
        assert_ne!(id1, id2);
    }

    #[tokio::test]
    async fn test_decrypt_with_current_key() {
        let km = Arc::new(KeyManager::new(
            Duration::from_secs(3600),
            Duration::from_secs(60),
        ));

        let keys = km.keys.read().await;
        let public = keys.current.public;
        drop(keys);

        let (encrypted, _) = encrypt_for_key(&public, b"test image data");
        let decrypted = km.decrypt(&encrypted).await.unwrap();
        assert_eq!(decrypted.len(), b"test image data".len());
    }

    #[tokio::test]
    async fn test_previous_key_available_after_rotation() {
        let km = Arc::new(KeyManager::new(
            Duration::from_secs(3600),
            Duration::from_secs(60),
        ));

        // 현재 키로 암호화
        let keys = km.keys.read().await;
        let old_public = keys.current.public;
        drop(keys);

        let (encrypted, _) = encrypt_for_key(&old_public, b"old key data");

        // 로테이션
        km.rotate().await;

        // 이전 키로 복호화 성공해야 함
        let decrypted = km.decrypt(&encrypted).await.unwrap();
        assert_eq!(decrypted.len(), b"old key data".len());
    }

    #[tokio::test]
    async fn test_previous_key_cleared_after_grace_period() {
        let km = Arc::new(KeyManager::new(
            Duration::from_secs(3600),
            Duration::from_secs(60),
        ));

        // 현재 키로 암호화
        let keys = km.keys.read().await;
        let old_public = keys.current.public;
        drop(keys);

        let (encrypted, _) = encrypt_for_key(&old_public, b"expired data");

        // 로테이션 + 이전 키 제거
        km.rotate().await;
        km.clear_previous().await;

        // 이전 키가 없으므로 복호화 실패해야 함
        let result = km.decrypt(&encrypted).await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_no_key_id_fallback() {
        let km = Arc::new(KeyManager::new(
            Duration::from_secs(3600),
            Duration::from_secs(60),
        ));

        // 현재 키로 암호화 (key_id 없이)
        let keys = km.keys.read().await;
        let old_public = keys.current.public;
        drop(keys);

        let (mut encrypted, _) = encrypt_for_key(&old_public, b"no key id data");
        encrypted.key_id = None; // key_id 제거 (하위 호환 시뮬레이션)

        // 로테이션 후에도 fallback으로 복호화 성공
        km.rotate().await;
        let decrypted = km.decrypt(&encrypted).await.unwrap();
        assert_eq!(decrypted.len(), b"no key id data".len());
    }
}


================================================
FILE: apps/capsule/src/crypto/mod.rs
================================================
mod keypair;
mod encryption;

pub use keypair::KeyManager;
pub(crate) use encryption::decrypt_with_secret;


================================================
FILE: apps/capsule/src/error.rs
================================================
use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde_json::json;
use thiserror::Error;

/// Capsule error types
#[derive(Error, Debug)]
pub enum CapsuleError {
    #[error("Invalid public key format")]
    InvalidPublicKey,

    #[error("Invalid nonce format")]
    InvalidNonce,

    #[error("Invalid ciphertext format")]
    InvalidCiphertext,

    #[error("Cipher initialization failed")]
    CipherInitFailed,

    #[error("Decryption failed - invalid ciphertext or key")]
    DecryptionFailed,

    #[error("OpenAI API error: {0}")]
    OpenAIError(String),

    #[error("Configuration error: {0}")]
    ConfigError(String),

    #[error("Internal error: {0}")]
    Internal(String),

    #[error("Rate limit exceeded: {0}")]
    RateLimited(String),

    #[error("Subscription required")]
    SubscriptionRequired,

    #[error("History validation failed: {0}")]
    HistoryTooLarge(String),
}

impl IntoResponse for CapsuleError {
    fn into_response(self) -> Response {
        let (status, error_message) = match &self {
            CapsuleError::InvalidPublicKey
            | CapsuleError::InvalidNonce
            | CapsuleError::InvalidCiphertext => (StatusCode::BAD_REQUEST, self.to_string()),

            CapsuleError::DecryptionFailed => {
                (StatusCode::BAD_REQUEST, "Decryption failed".to_string())
            }

            CapsuleError::CipherInitFailed | CapsuleError::Internal(_) => {
                // Internal errors don't expose details
                tracing::error!("Internal error: {}", self);
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    "Internal server error".to_string(),
                )
            }

            CapsuleError::OpenAIError(msg) => {
                tracing::error!("OpenAI error: {}", msg);
                (StatusCode::BAD_GATEWAY, "AI service error".to_string())
            }

            CapsuleError::ConfigError(msg) => {
                tracing::error!("Config error: {}", msg);
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    "Configuration error".to_string(),
                )
            }

            CapsuleError::RateLimited(reason) => {
                (StatusCode::TOO_MANY_REQUESTS, reason.clone())
            }

            CapsuleError::SubscriptionRequired => {
                (
                    StatusCode::FORBIDDEN,
                    "Pro subscription required".to_string(),
                )
            }

            CapsuleError::HistoryTooLarge(msg) => {
                (StatusCode::BAD_REQUEST, msg.clone())
            }
        };

        let body = Json(json!({
            "error": error_message
        }));

        (status, body).into_response()
    }
}

impl From<anyhow::Error> for CapsuleError {
    fn from(err: anyhow::Error) -> Self {
        CapsuleError::Internal(err.to_string())
    }
}


================================================
FILE: apps/capsule/src/main.rs
================================================
mod api;
mod config;
mod crypto;
mod error;
mod services;
mod types;

use std::sync::Arc;
use std::time::Duration;

use config::Config;
use crypto::KeyManager;
use services::{JwtValidator, OpenAIService, UsageTracker};

/// Application state
///
/// # Security
/// - `KeyManager` holds private keys internally with rotation support
/// - Private keys are never exposed externally
/// - Keys are rotated periodically to limit blast radius
/// - JWT validator uses Zeroizing for secret storage
/// - Usage tracker uses service key for Supabase API calls
pub struct AppState {
    /// Key manager (encryption/decryption with rotation)
    pub key_manager: Arc<KeyManager>,
    /// OpenAI service
    pub openai: OpenAIService,
    /// JWT validator for Supabase tokens
    pub jwt_validator: Arc<JwtValidator>,
    /// Usage tracker for rate limiting and billing
    pub usage_tracker: UsageTracker,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Initialize logging
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::from_default_env()
                .add_directive("capsule=debug".parse().unwrap()),
        )
        .init();

    tracing::info!("Starting Capsule server...");

    // Load configuration (API keys removed from env after loading)
    let config = Config::from_env()?;
    tracing::info!("Configuration loaded (port: {})", config.port);
    tracing::info!("CORS allowed origins: {:?}", config.allowed_origins);
    tracing::info!("Supabase URL: {}", config.supabase_url);

    // Router configuration
    let app_config = api::RouterConfig {
        allowed_origins: config.allowed_origins.clone(),
    };

    // Create JWT validator (uses JWKS from Supabase for ES256 validation)
    let jwt_validator = Arc::new(JwtValidator::new(config.supabase_url.clone()));

    // Create usage tracker
    let usage_tracker = UsageTracker::new(
        config.supabase_url.clone(),
        config.supabase_service_key,
    );

    // Create KeyManager with rotation configuration
    let key_manager = Arc::new(KeyManager::new(
        Duration::from_secs(config.key_rotation_interval_secs),
        Duration::from_secs(config.key_grace_period_secs),
    ));

    // Start background key rotation task
    let _rotation_handle = key_manager.start_rotation_task();

    // Create application state
    let openai_model = config.openai_model.clone();
    let state = Arc::new(AppState {
        key_manager: Arc::clone(&key_manager),
        openai: OpenAIService::new(config.openai_api_key).with_model(openai_model),
        jwt_validator: jwt_validator.clone(),
        usage_tracker,
    });

    // Create router
    let app = api::create_router(state, &app_config, jwt_validator);

    // Start server
    let addr = format!("0.0.0.0:{}", config.port);
    let listener = tokio::net::TcpListener::bind(&addr).await?;

    tracing::info!("Capsule server listening on {}", addr);
    tracing::info!("Security: Private keys exist only in memory");
    tracing::info!("Security: Key rotation enabled (interval: {}s, grace: {}s)",
        config.key_rotation_interval_secs, config.key_grace_period_secs);
    tracing::info!("Security: Decrypted images are zeroized after use");
    tracing::info!("Security: API keys protected with Zeroizing<String>");
    tracing::info!("Security: JWT authentication required for chat endpoints");
    tracing::info!("Security: Subscription verification via Supabase");

    axum::serve(listener, app).await?;

    Ok(())
}


================================================
FILE: apps/capsule/src/services/jwt.rs
================================================
//! JWT validation service for Supabase tokens
//!
//! Validates Supabase JWT tokens using ES256 algorithm with JWKS.
//! Extracts user_id (sub claim) for authentication.

use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use thiserror::Error;
use tokio::sync::RwLock;

/// JWT validation errors
#[derive(Error, Debug)]
pub enum JwtError {
    #[error("Invalid token format")]
    InvalidFormat,

    #[error("Token expired")]
    Expired,

    #[error("Invalid signature")]
    InvalidSignature,

    #[error("Invalid audience")]
    InvalidAudience,

    #[error("JWKS fetch failed: {0}")]
    JwksFetchFailed(String),

    #[error("No matching key found")]
    NoMatchingKey,

    #[error("Validation failed: {0}")]
    ValidationFailed(String),
}

/// JWKS response from Supabase
#[derive(Debug, Deserialize)]
struct JwksResponse {
    keys: Vec<Jwk>,
}

/// JSON Web Key
#[derive(Debug, Clone, Deserialize)]
struct Jwk {
    kty: String,
    #[serde(default)]
    kid: Option<String>,
    #[serde(default)]
    alg: Option<String>,
    /// EC curve (for ES256)
    #[serde(default)]
    crv: Option<String>,
    /// EC x coordinate (base64url)
    #[serde(default)]
    x: Option<String>,
    /// EC y coordinate (base64url)
    #[serde(default)]
    y: Option<String>,
}

/// Supabase JWT claims
///
/// Contains the standard claims from Supabase auth tokens.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupabaseClaims {
    /// Subject - User ID (UUID)
    pub sub: String,

    /// Audience - Should be "authenticated"
    pub aud: String,

    /// User role
    pub role: String,

    /// Expiration timestamp
    pub exp: usize,

    /// Issued at timestamp
    pub iat: usize,

    /// User email (optional)
    pub email: Option<String>,
}

/// Cached JWKS data
struct CachedJwks {
    keys: Vec<Jwk>,
    fetched_at: std::time::Instant,
}

/// JWT validator for Supabase tokens
///
/// # Security
/// - Uses ES256 algorithm with JWKS public keys
/// - Validates audience claim to ensure token is for authenticated users
/// - Validates expiration to prevent replay attacks
/// - JWKS is cached for 1 hour to reduce network calls
pub struct JwtValidator {
    /// Supabase URL for JWKS endpoint
    supabase_url: String,
    /// HTTP client
    client: reqwest::Client,
    /// Cached JWKS
    jwks_cache: Arc<RwLock<Option<CachedJwks>>>,
}

impl JwtValidator {
    /// Create a new JWT validator
    ///
    /// # Arguments
    /// * `supabase_url` - Supabase project URL
    pub fn new(supabase_url: String) -> Self {
        Self {
            supabase_url,
            client: reqwest::Client::new(),
            jwks_cache: Arc::new(RwLock::new(None)),
        }
    }

    /// Fetch JWKS from Supabase (with caching)
    async fn get_jwks(&self) -> Result<Vec<Jwk>, JwtError> {
        // Check cache
        {
            let cache = self.jwks_cache.read().await;
            if let Some(cached) = cache.as_ref() {
                // Cache valid for 1 hour
                if cached.fetched_at.elapsed() < std::time::Duration::from_secs(3600) {
                    return Ok(cached.keys.clone());
                }
            }
        }

        // Fetch from Supabase
        let jwks_url = format!("{}/auth/v1/.well-known/jwks.json", self.supabase_url);
        tracing::debug!("Fetching JWKS from: {}", jwks_url);

        let response = self
            .client
            .get(&jwks_url)
            .send()
            .await
            .map_err(|e| JwtError::JwksFetchFailed(e.to_string()))?;

        if !response.status().is_success() {
            return Err(JwtError::JwksFetchFailed(format!(
                "HTTP {}",
                response.status()
            )));
        }

        let jwks: JwksResponse = response
            .json()
            .await
            .map_err(|e| JwtError::JwksFetchFailed(e.to_string()))?;

        tracing::debug!("Fetched {} keys from JWKS", jwks.keys.len());

        // Update cache
        {
            let mut cache = self.jwks_cache.write().await;
            *cache = Some(CachedJwks {
                keys: jwks.keys.clone(),
                fetched_at: std::time::Instant::now(),
            });
        }

        Ok(jwks.keys)
    }

    /// Validate a JWT token and extract claims
    ///
    /// # Arguments
    /// * `token` - Bearer token string (without "Bearer " prefix)
    ///
    /// # Returns
    /// * `Ok(SupabaseClaims)` - Validated claims including user_id
    /// * `Err(JwtError)` - Validation failure reason
    ///
    /// # Security
    /// - Validates ES256 signature against JWKS public key
    /// - Checks "authenticated" audience claim
    /// - Verifies token has not expired
    pub async fn validate(&self, token: &str) -> Result<SupabaseClaims, JwtError> {
        // Decode header to get kid
        let header = decode_header(token).map_err(|e| JwtError::InvalidFormat)?;
        tracing::debug!("JWT header: alg={:?}, kid={:?}", header.alg, header.kid);

        // Fetch JWKS
        let keys = self.get_jwks().await?;

        // Find matching key
        let jwk = if let Some(kid) = &header.kid {
            keys.iter().find(|k| k.kid.as_ref() == Some(kid))
        } else {
            // Use first ES256 key if no kid
            keys.iter()
                .find(|k| k.alg.as_deref() == Some("ES256") || k.crv.as_deref() == Some("P-256"))
        }
        .ok_or(JwtError::NoMatchingKey)?;

        // Build decoding key from JWK
        let decoding_key = self.jwk_to_decoding_key(jwk)?;

        // Validate token
        let mut validation = Validation::new(Algorithm::ES256);
        validation.set_audience(&["authenticated"]);

        let token_data =
            decode::<SupabaseClaims>(token, &decoding_key, &validation).map_err(|e| {
                match e.kind() {
                    jsonwebtoken::errors::ErrorKind::ExpiredSignature => JwtError::Expired,
                    jsonwebtoken::errors::ErrorKind::InvalidSignature => JwtError::InvalidSignature,
                    jsonwebtoken::errors::ErrorKind::InvalidAudience => JwtError::InvalidAudience,
                    _ => JwtError::ValidationFailed(e.to_string()),
                }
            })?;

        Ok(token_data.claims)
    }

    /// Convert JWK to DecodingKey
    fn jwk_to_decoding_key(&self, jwk: &Jwk) -> Result<DecodingKey, JwtError> {
        if jwk.kty != "EC" {
            return Err(JwtError::ValidationFailed(format!(
                "Unsupported key type: {}",
                jwk.kty
            )));
        }

        let x = jwk
            .x
            .as_ref()
            .ok_or_else(|| JwtError::ValidationFailed("Missing x coordinate".to_string()))?;
        let y = jwk
            .y
            .as_ref()
            .ok_or_else(|| JwtError::ValidationFailed("Missing y coordinate".to_string()))?;

        // Build JWK JSON for jsonwebtoken
        let jwk_json = serde_json::json!({
            "kty": "EC",
            "crv": "P-256",
            "x": x,
            "y": y
        });

        DecodingKey::from_jwk(
            &serde_json::from_value(jwk_json)
                .map_err(|e| JwtError::ValidationFailed(format!("Invalid JWK format: {}", e)))?,
        )
        .map_err(|e| JwtError::ValidationFailed(format!("Failed to create decoding key: {}", e)))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_invalid_token() {
        let validator = JwtValidator::new("https://example.supabase.co".to_string());
        let result = validator.validate("invalid-token").await;
        assert!(result.is_err());
    }
}


================================================
FILE: apps/capsule/src/services/mod.rs
================================================
mod jwt;
mod openai;
mod usage;

pub use jwt::{JwtError, JwtValidator, SupabaseClaims};
pub use openai::{ChatResult, OpenAIService, TokenUsage};
pub use usage::{RateLimitStatus, UsageTracker};


================================================
FILE: apps/capsule/src/services/openai.rs
================================================
use axum::response::sse::Event;
use futures_util::{Stream, StreamExt};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::pin::Pin;
use zeroize::{Zeroize, Zeroizing};

use crate::error::CapsuleError;
use crate::types::{DecryptedImage, HistoryMessage};

const OPENAI_API_URL: &str = "https://api.openai.com/v1/chat/completions";

/// OpenAI API 서비스
///
/// # 보안
/// - `api_key`는 `Zeroizing<String>`으로 보호됨
/// - Drop 시 자동으로 메모리 클리어
pub struct OpenAIService {
    client: Client,
    api_key: Zeroizing<String>,
    model: String,
}

impl OpenAIService {
    /// 새 OpenAI 서비스 생성
    pub fn new(api_key: Zeroizing<String>) -> Self {
        Self {
            client: Client::new(),
            api_key,
            model: "gpt-4o".to_string(),
        }
    }

    /// 모델 설정
    pub fn with_model(mut self, model: String) -> Self {
        self.model = model;
        self
    }

    /// 메시지 배열 빌드: system_prompt → history → current_message
    fn build_messages(
        system_prompt: Option<&str>,
        history: Option<&[HistoryMessage]>,
        current_message: Message,
    ) -> Vec<Message> {
        let mut messages = Vec::new();

        if let Some(prompt) = system_prompt {
            messages.push(Message {
                role: "system".to_string(),
                content: Some(MessageContent::Text(prompt.to_string())),
                tool_call_id: None,
                tool_calls: None,
            });
        }

        if let Some(history) = history {
            for msg in history {
                let mut m = Message {
                    role: msg.role.clone(),
                    content: Some(MessageContent::Text(msg.content.clone())),
                    tool_call_id: None,
                    tool_calls: None,
                };
                if let Some(ref tc_id) = msg.tool_call_id {
                    m.tool_call_id = Some(tc_id.clone());
                }
                if let Some(ref tc) = msg.tool_calls {
                    m.tool_calls = Some(tc.clone());
                    if msg.content.is_empty() {
                        m.content = None;
                    }
                }
                messages.push(m);
            }
        }

        messages.push(current_message);
        messages
    }

    /// 텍스트 전용 채팅
    pub async fn chat(
        &self,
        message: &str,
        system_prompt: Option<&str>,
        history: Option<&[HistoryMessage]>,
        tools: Option<&serde_json::Value>,
        tool_choice: Option<&serde_json::Value>,
    ) -> Result<ChatResult, anyhow::Error> {
        let current = Message {
            role: "user".to_string(),
            content: Some(MessageContent::Text(message.to_string())),
            tool_call_id: None,
            tool_calls: None,
        };

        let request_body = ChatRequest {
            model: self.model.clone(),
            messages: Self::build_messages(system_prompt, history, current),
            max_tokens: Some(4096),
            stream: false,
            stream_options: None,
            tools: tools.cloned(),
            tool_choice: tool_choice.cloned(),
        };

        let response = self
            .client
            .post(OPENAI_API_URL)
            .header("Authorization", format!("Bearer {}", &*self.api_key))
            .header("Content-Type", "application/json")
            .json(&request_body)
            .send()
            .await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            return Err(anyhow::anyhow!("OpenAI API error: {}", error_text));
        }

        let response_body: ChatResponse = response.json().await?;

        let choice = response_body.choices.first();
        Ok(ChatResult {
            content: choice
                .and_then(|c| c.message.content.clone())
                .unwrap_or_default(),
            tool_calls: choice.and_then(|c| c.message.tool_calls.clone()),
            usage: response_body.usage.into(),
        })
    }

    /// 이미지 분석 요청
    ///
    /// # 보안
    /// - `decrypted_image`는 소유권이 이전됨
    /// - 함수 종료 시 자동으로 drop → zeroize
    /// - base64 문자열도 사용 후 명시적 zeroize
    pub async fn analyze_image(
        &self,
        message: &str,
        decrypted_image: DecryptedImage,
        system_prompt: Option<&str>,
        history: Option<&[HistoryMessage]>,
        tools: Option<&serde_json::Value>,
        tool_choice: Option<&serde_json::Value>,
    ) -> Result<ChatResult, anyhow::Error> {
        let mut image_base64 = decrypted_image.to_base64();
        drop(decrypted_image);

        let current = Message {
            role: "user".to_string(),
            content: Some(MessageContent::MultiPart(vec![
                ContentPart::Text {
                    text: message.to_string(),
                },
                ContentPart::ImageUrl {
                    image_url: ImageUrl {
                        url: format!("data:image/jpeg;base64,{}", image_base64),
                    },
                },
            ])),
            tool_call_id: None,
            tool_calls: None,
        };

        let request_body = ChatRequest {
            model: self.model.clone(),
            messages: Self::build_messages(system_prompt, history, current),
            max_tokens: Some(4096),
            stream: false,
            stream_options: None,
            tools: tools.cloned(),
            tool_choice: tool_choice.cloned(),
        };

        let response = self
            .client
            .post(OPENAI_API_URL)
            .header("Authorization", format!("Bearer {}", &*self.api_key))
            .header("Content-Type", "application/json")
            .json(&request_body)
            .send()
            .await;

        image_base64.zeroize();

        let response = response?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            return Err(anyhow::anyhow!("OpenAI API error: {}", error_text));
        }

        let response_body: ChatResponse = response.json().await?;

        let choice = response_body.choices.first();
        Ok(ChatResult {
            content: choice
                .and_then(|c| c.message.content.clone())
                .unwrap_or_default(),
            tool_calls: choice.and_then(|c| c.message.tool_calls.clone()),
            usage: response_body.usage.into(),
        })
    }

    /// 텍스트 전용 스트리밍 채팅
    ///
    /// Returns a stream of SSE events and a shared usage tracker.
    /// The usage will be populated when the stream completes.
    pub async fn chat_stream(
        &self,
        message: &str,
        system_prompt: Option<&str>,
        history: Option<&[HistoryMessage]>,
        tools: Option<&serde_json::Value>,
        tool_choice: Option<&serde_json::Value>,
    ) -> Result<(Pin<Box<dyn Stream<Item = Result<Event, CapsuleError>> + Send>>, std::sync::Arc<std::sync::Mutex<TokenUsage>>), anyhow::Error>
    {
        let current = Message {
            role: "user".to_string(),
            content: Some(MessageContent::Text(message.to_string())),
            tool_call_id: None,
            tool_calls: None,
        };

        let request_body = ChatRequest {
            model: self.model.clone(),
            messages: Self::build_messages(system_prompt, history, current),
            max_tokens: Some(4096),
            stream: true,
            stream_options: Some(StreamOptions { include_usage: true }),
            tools: tools.cloned(),
            tool_choice: tool_choice.cloned(),
        };

        let response = self
            .client
            .post(OPENAI_API_URL)
            .header("Authorization", format!("Bearer {}", &*self.api_key))
            .header("Content-Type", "application/json")
            .json(&request_body)
            .send()
            .await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            return Err(anyhow::anyhow!("OpenAI API error: {}", error_text));
        }

        let usage = std::sync::Arc::new(std::sync::Mutex::new(TokenUsage::default()));
        let stream = Box::pin(Self::process_stream_with_usage(response, usage.clone()));
        Ok((stream, usage))
    }

    /// 이미지 분석 스트리밍 요청
    ///
    /// Returns a stream of SSE events and a shared usage tracker.
    /// The usage will be populated when the stream completes.
    pub async fn analyze_image_stream(
        &self,
        message: &str,
        decrypted_image: DecryptedImage,
        system_prompt: Option<&str>,
        history: Option<&[HistoryMessage]>,
        tools: Option<&serde_json::Value>,
        tool_choice: Option<&serde_json::Value>,
    ) -> Result<(Pin<Box<dyn Stream<Item = Result<Event, CapsuleError>> + Send>>, std::sync::Arc<std::sync::Mutex<TokenUsage>>), anyhow::Error>
    {
        let mut image_base64 = decrypted_image.to_base64();
        drop(decrypted_image);

        let current = Message {
            role: "user".to_string(),
            content: Some(MessageContent::MultiPart(vec![
                ContentPart::Text {
                    text: message.to_string(),
                },
                ContentPart::ImageUrl {
                    image_url: ImageUrl {
                        url: format!("data:image/jpeg;base64,{}", image_base64),
                    },
                },
            ])),
            tool_call_id: None,
            tool_calls: None,
        };

        let request_body = ChatRequest {
            model: self.model.clone(),
            messages: Self::build_messages(system_prompt, history, current),
            max_tokens: Some(4096),
            stream: true,
            stream_options: Some(StreamOptions { include_usage: true }),
            tools: tools.cloned(),
            tool_choice: tool_choice.cloned(),
        };

        let response = self
            .client
            .post(OPENAI_API_URL)
            .header("Authorization", format!("Bearer {}", &*self.api_key))
            .header("Content-Type", "application/json")
            .json(&request_body)
            .send()
            .await;

        image_base64.zeroize();

        let response = response?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            return Err(anyhow::anyhow!("OpenAI API error: {}", error_text));
        }

        let usage = std::sync::Arc::new(std::sync::Mutex::new(TokenUsage::default()));
        let stream = Box::pin(Self::process_stream_with_usage(response, usage.clone()));
        Ok((stream, usage))
    }

    /// SSE 스트림 처리 (with usage tracking and tool call accumulation)
    fn process_stream_with_usage(
        response: reqwest::Response,
        usage: std::sync::Arc<std::sync::Mutex<TokenUsage>>,
    ) -> impl Stream<Item = Result<Event, CapsuleError>> {
        async_stream::stream! {
            let mut stream = response.bytes_stream();
            let mut tool_call_buffer: HashMap<u32, AccumulatedToolCall> = HashMap::new();

            while let Some(chunk) = stream.next().await {
                match chunk {
                    Ok(bytes) => {
                        let text = String::from_utf8_lossy(&bytes);

                        for line in text.lines() {
                            if line.starts_with("data: ") {
                                let data = &line[6..];

                                if data == "[DONE]" {
                                    // Emit accumulated tool calls before [DONE] if any
                                    if !tool_call_buffer.is_empty() {
                                        let mut entries: Vec<_> = tool_call_buffer.drain().collect();
                                        entries.sort_by_key(|(idx, _)| *idx);
                                        let tool_calls: Vec<serde_json::Value> = entries
                                            .into_iter()
                                            .map(|(_, tc)| tc.to_json())
                                            .collect();
                                        if let Ok(json) = serde_json::to_string(&tool_calls) {
                                            yield Ok(Event::default().data(format!("[TOOL_CALLS]{}", json)));
                                        }
                                    }
                                    yield Ok(Event::default().data("[DONE]"));
                                    return;
                                }

                                if let Ok(chunk) = serde_json::from_str::<StreamChunk>(data) {
                                    if let Some(u) = chunk.usage {
                                        if let Ok(mut usage_guard) = usage.lock() {
                                            usage_guard.prompt_tokens = u.prompt_tokens;
                                            usage_guard.completion_tokens = u.completion_tokens;
                                            usage_guard.total_tokens = u.total_tokens;
                                        }
                                    }

                                    if let Some(choice) = chunk.choices.first() {
                                        // Stream text content
                                        if let Some(content) = &choice.delta.content {
                                            yield Ok(Event::default().data(content.clone()));
                                        }

                                        // Accumulate tool calls by index
                                        if let Some(ref tcs) = choice.delta.tool_calls {
                                            for tc in tcs {
                                                let entry = tool_call_buffer
                                                    .entry(tc.index)
                                                    .or_insert_with(AccumulatedToolCall::default);

                                                if let Some(ref id) = tc.id {
                                                    entry.id = id.clone();
                                                }
                                                if let Some(ref func) = tc.function {
                                                    if let Some(ref name) = func.name {
                                                        entry.function_name = name.clone();
                                                    }
                                                    if let Some(ref args) = func.arguments {
                                                        entry.arguments.push_str(args);
                                                    }
                                                }
                                            }
                                        }

                                        // Emit tool calls when finish_reason is "tool_calls"
                                        if choice.finish_reason.as_deref() == Some("tool_calls") && !tool_call_buffer.is_empty() {
                                            let mut entries: Vec<_> = tool_call_buffer.drain().collect();
                                            entries.sort_by_key(|(idx, _)| *idx);
                                            let tool_calls: Vec<serde_json::Value> = entries
                                                .into_iter()
                                                .map(|(_, tc)| tc.to_json())
                                                .collect();
                                            if let Ok(json) = serde_json::to_string(&tool_calls) {
                                                yield Ok(Event::default().data(format!("[TOOL_CALLS]{}", json)));
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    Err(e) => {
                        yield Err(CapsuleError::OpenAIError(e.to_string()));
                        return;
                    }
                }
            }
        }
    }
}

// -- Accumulated tool call buffer --

#[derive(Default)]
struct AccumulatedToolCall {
    id: String,
    function_name: String,
    arguments: String,
}

impl AccumulatedToolCall {
    fn to_json(&self) -> serde_json::Value {
        serde_json::json!({
            "id": self.id,
            "type": "function",
            "function": {
                "name": self.function_name,
                "arguments": self.arguments,
            }
        })
    }
}

// -- OpenAI API request/response types --

#[derive(Serialize)]
struct ChatRequest {
    model: String,
    messages: Vec<Message>,
    #[serde(skip_serializing_if = "Option::is_none")]
    max_tokens: Option<u32>,
    stream: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    stream_options: Option<StreamOptions>,
    #[serde(skip_serializing_if = "Option::is_none")]
    tools: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    tool_choice: Option<serde_json::Value>,
}

#[derive(Serialize)]
struct StreamOptions {
    include_usage: bool,
}

#[derive(Serialize)]
struct Message {
    role: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    content: Option<MessageContent>,
    #[serde(skip_serializing_if = "Option::is_none")]
    tool_call_id: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    tool_calls: Option<serde_json::Value>,
}

#[derive(Serialize)]
#[serde(untagged)]
enum MessageContent {
    Text(String),
    MultiPart(Vec<ContentPart>),
}

#[derive(Serialize)]
#[serde(tag = "type")]
enum ContentPart {
    #[serde(rename = "text")]
    Text { text: String },
    #[serde(rename = "image_url")]
    ImageUrl { image_url: ImageUrl },
}

#[derive(Serialize)]
struct ImageUrl {
    url: String,
}

/// Token usage information from OpenAI API
#[derive(Debug, Clone, Default, Deserialize)]
pub struct TokenUsage {
    pub prompt_tokens: i32,
    pub completion_tokens: i32,
    pub total_tokens: i32,
}

/// Result of a chat request including content and token usage
#[derive(Debug, Clone)]
pub struct ChatResult {
    pub content: String,
    pub tool_calls: Option<serde_json::Value>,
    pub usage: TokenUsage,
}

#[derive(Deserialize)]
struct ChatResponse {
    choices: Vec<Choice>,
    usage: Option<Usage>,
}

#[derive(Deserialize)]
struct Usage {
    prompt_tokens: i32,
    completion_tokens: i32,
    total_tokens: i32,
}

impl From<Option<Usage>> for TokenUsage {
    fn from(usage: Option<Usage>) -> Self {
        match usage {
            Some(u) => TokenUsage {
                prompt_tokens: u.prompt_tokens,
                completion_tokens: u.completion_tokens,
                total_tokens: u.total_tokens,
            },
            None => TokenUsage::default(),
        }
    }
}

#[derive(Deserialize)]
struct Choice {
    message: ResponseMessage,
}

#[derive(Deserialize)]
struct ResponseMessage {
    content: Option<String>,
    tool_calls: Option<serde_json::Value>,
}

#[derive(Deserialize)]
struct StreamChunk {
    choices: Vec<StreamChoice>,
    usage: Option<Usage>,
}

#[derive(Deserialize)]
struct StreamChoice {
    delta: Delta,
    #[serde(default)]
    finish_reason: Option<String>,
}

#[derive(Deserialize)]
struct Delta {
    content: Option<String>,
    #[serde(default)]
    tool_calls: Option<Vec<StreamToolCall>>,
}

#[derive(Deserialize)]
struct StreamToolCall {
    index: u32,
    id: Option<String>,
    #[serde(default)]
    function: Option<StreamToolCallFunction>,
}

#[derive(Deserialize, Default)]
struct StreamToolCallFunction {
    name: Option<String>,
    arguments: Option<String>,
}


================================================
FILE: apps/capsule/src/services/usage.rs
================================================
//! Usage tracking service for rate limiting and billing
//!
//! Communicates with Supabase to:
//! 1. Check subscription status (billing_subscriptions table)
//! 2. Check rate limits (enclave_rate_limits table)
//! 3. Track usage (enclave_usage table)

use reqwest::Client;
use serde::Deserialize;
use zeroize::Zeroizing;

/// Rate limit check result from Supabase RPC
#[derive(Debug, Deserialize)]
pub struct RateLimitStatus {
    /// Whether the request is allowed
    pub allowed: bool,

    /// Reason for denial (if not allowed)
    pub reason: Option<String>,

    /// Whether user has active subscription
    pub subscribed: bool,

    /// Remaining requests for today
    pub requests_remaining: i32,

    /// Remaining tokens for today
    pub tokens_remaining: i32,
}

/// Usage tracking service
///
/// # Security
/// - Service key is stored in `Zeroizing<String>`
/// - All requests use HTTPS to Supabase
/// - Fire-and-forget usage tracking doesn't block responses
#[derive(Clone)]
pub struct UsageTracker {
    client: Client,
    supabase_url: String,
    service_key: Zeroizing<String>,
}

impl UsageTracker {
    /// Create a new usage tracker
    ///
    /// # Arguments
    /// * `supabase_url` - Supabase project URL
    /// * `service_key` - Supabase service role key (NOT anon key)
    pub fn new(supabase_url: String, service_key: Zeroizing<String>) -> Self {
        Self {
            client: Client::new(),
            supabase_url,
            service_key,
        }
    }

    /// Check if user can make a request
    ///
    /// Calls the `check_rate_limit` RPC function which:
    /// 1. Verifies active subscription in billing_subscriptions
    /// 2. Checks daily request/token limits
    ///
    /// # Arguments
    /// * `user_id` - User UUID from JWT claims
    ///
    /// # Returns
    /// * `RateLimitStatus` with allowed flag and remaining quotas
    pub async fn check_rate_limit(&self, user_id: &str) -> Result<RateLimitStatus, anyhow::Error> {
        let url = format!("{}/rest/v1/rpc/check_rate_limit", self.supabase_url);

        tracing::debug!(user_id = %user_id, "Checking rate limit");

        let response = self
            .client
            .post(&url)
            .header("apikey", self.service_key.as_str())
            .header(
                "Authorization",
                format!("Bearer {}", self.service_key.as_str()),
            )
            .header("Content-Type", "application/json")
            .json(&serde_json::json!({ "p_user_id": user_id }))
            .send()
            .await?;

        if !response.status().is_success() {
            let status_code = response.status();
            let error_text = response.text().await.unwrap_or_default();
            tracing::error!(
                status = %status_code,
                error = %error_text,
                "check_rate_limit RPC failed - check if function exists in Supabase"
            );
            return Err(anyhow::anyhow!("Supabase RPC error: {}", error_text));
        }

        let status = response.json::<RateLimitStatus>().await?;
        tracing::debug!(
            allowed = status.allowed,
            subscribed = status.subscribed,
            requests_remaining = status.requests_remaining,
            "Rate limit check result"
        );
        Ok(status)
    }

    /// Track a completed request
    ///
    /// Calls the `increment_usage` RPC function to record:
    /// - Request count
    /// - Input/output tokens
    /// - Image request flag
    ///
    /// # Note
    /// This is designed to be called in a fire-and-forget manner
    /// using `tokio::spawn` to not block the response.
    ///
    /// # Arguments
    /// * `user_id` - User UUID from JWT claims
    /// * `input_tokens` - Number of input tokens used
    /// * `output_tokens` - Number of output tokens generated
    /// * `is_image_request` - Whether this was an image analysis request
    pub async fn track_request(
        &self,
        user_id: &str,
        input_tokens: i32,
        output_tokens: i32,
        is_image_request: bool,
    ) -> Result<(), anyhow::Error> {
        let today = chrono::Utc::now().format("%Y-%m-%d").to_string();
        let url = format!("{}/rest/v1/rpc/increment_usage", self.supabase_url);

        tracing::debug!(
            user_id = %user_id,
            date = %today,
            is_image = is_image_request,
            "Tracking usage request"
        );

        let response = self
            .client
            .post(&url)
            .header("apikey", self.service_key.as_str())
            .header(
                "Authorization",
                format!("Bearer {}", self.service_key.as_str()),
            )
            .header("Content-Type", "application/json")
            .json(&serde_json::json!({
                "p_user_id": user_id,
                "p_date": today,
                "p_request_count": 1,
                "p_input_tokens": input_tokens,
                "p_output_tokens": output_tokens,
                "p_image_requests": if is_image_request { 1 } else { 0 }
            }))
            .send()
            .await?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response.text().await.unwrap_or_default();
            tracing::error!(
                status = %status,
                error = %error_text,
                "Failed to track usage - check if increment_usage RPC function exists"
            );
            return Err(anyhow::anyhow!("Supabase RPC error: {}", error_text));
        }

        tracing::info!(user_id = %user_id, "Usage tracked successfully");
        Ok(())
    }
}


================================================
FILE: apps/capsule/src/types/decrypted.rs
================================================
use base64::{engine::general_purpose::STANDARD, Engine};
use zeroize::{Zeroize, ZeroizeOnDrop};

/// 복호화된 이미지 - Capsule 내부에서만 존재
///
/// # 보안 특성
/// - `Debug` 미구현: 로그에 출력 불가
/// - `Display` 미구현: `println!` 등으로 출력 불가
/// - `Clone` 미구현: 복사 불가
/// - `Zeroize` + `ZeroizeOnDrop`: Drop 시 자동 메모리 클리어
///
/// # 컴파일 타임 보안
/// ```compile_fail
/// let img = DecryptedImage::new(vec![1, 2, 3]);
/// println!("{:?}", img);  // 컴파일 에러: Debug 미구현
/// ```
///
/// ```compile_fail
/// let img = DecryptedImage::new(vec![1, 2, 3]);
/// let copy = img.clone();  // 컴파일 에러: Clone 미구현
/// ```
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct DecryptedImage {
    /// 복호화된 이미지 데이터 (private 필드)
    data: Vec<u8>,
}

impl DecryptedImage {
    /// 내부에서만 생성 가능 (crate 내부 가시성)
    pub(crate) fn new(data: Vec<u8>) -> Self {
        Self { data }
    }

    /// OpenAI API 호출용 base64 인코딩
    ///
    /// # 주의
    /// 반환된 `String`도 사용 후 명시적으로 zeroize 권장
    ///
    /// # Example
    /// ```
    /// let mut base64_str = decrypted.to_base64();
    /// // ... OpenAI API 호출 ...
    /// base64_str.zeroize();  // 명시적 클리어
    /// ```
    pub fn to_base64(&self) -> String {
        STANDARD.encode(&self.data)
    }

    /// 이미지 크기 (바이트)
    pub fn len(&self) -> usize {
        self.data.len()
    }

    /// 이미지가 비어있는지 확인
    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }
}

// Debug, Display, Clone을 의도적으로 구현하지 않음
// 이는 컴파일 타임에 민감 데이터 노출을 방지함


================================================
FILE: apps/capsule/src/types/encrypted.rs
================================================
use serde::{Deserialize, Serialize};

use crate::error::CapsuleError;

/// 최대 히스토리 메시지 수
const MAX_HISTORY_MESSAGES: usize = 50;

/// 최대 히스토리 총 문자 수 (~25k 토큰)
const MAX_HISTORY_CHARS: usize = 100_000;

/// 허용되는 메시지 역할
const ALLOWED_ROLES: &[&str] = &["user", "assistant", "system", "tool"];

/// 클라이언트에서 전송된 암호화된 이미지
/// 이 타입은 안전하게 로깅 가능 (민감 데이터 없음 - 복호화 불가)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptedImage {
    /// 클라이언트의 임시 공개키 (32 bytes, base64)
    pub ephemeral_public_key: String,
    /// Nonce (24 bytes, base64)
    pub nonce: String,
    /// 암호화된 이미지 데이터 (base64)
    pub ciphertext: String,
    /// 암호화에 사용된 서버 키 ID (하위 호환을 위해 선택사항)
    #[serde(default)]
    pub key_id: Option<String>,
}

/// 대화 히스토리의 단일 메시지
///
/// 클라이언트가 관리하며, 서버는 저장하지 않음 (zero-knowledge)
/// 이미지는 텍스트 요약으로만 포함 가능
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryMessage {
    /// 메시지 역할: "user", "assistant", "system", 또는 "tool"
    pub role: String,
    /// 텍스트 내용
    pub content: String,
    /// Tool call ID (role이 "tool"일 때 필수)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tool_call_id: Option<String>,
    /// Tool calls (role이 "assistant"이고 tool call 응답일 때)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<serde_json::Value>,
}

/// 채팅 요청
#[derive(Debug, Deserialize)]
pub struct ChatRequest {
    /// 사용자 메시지
    pub message: String,
    /// 암호화된 이미지 (선택사항)
    #[serde(default)]
    pub encrypted_image: Option<EncryptedImage>,
    /// 대화 히스토리 (선택사항, 오래된 순서)
    #[serde(default)]
    pub history: Option<Vec<HistoryMessage>>,
    /// 시스템 프롬프트 (선택사항)
    #[serde(default)]
    pub system_prompt: Option<String>,
    /// OpenAI tools 정의 (프론트엔드에서 주입, 그대로 passthrough)
    #[serde(default)]
    pub tools: Option<serde_json::Value>,
    /// OpenAI tool_choice (프론트엔드에서 주입, 그대로 passthrough)
    /// "auto" | "required" | "none" | {"type":"function","function":{"name":"..."}}
    #[serde(default)]
    pub tool_choice: Option<serde_json::Value>,
}

impl ChatRequest {
    /// 히스토리 유효성 검사
    pub fn validate_history(&self) -> Result<(), CapsuleError> {
        if let Some(history) = &self.history {
            if history.len() > MAX_HISTORY_MESSAGES {
                return Err(CapsuleError::HistoryTooLarge(format!(
                    "History contains {} messages, maximum is {}",
                    history.len(),
                    MAX_HISTORY_MESSAGES
                )));
            }

            let total_chars: usize = history.iter().map(|m| m.content.len()).sum();
            if total_chars > MAX_HISTORY_CHARS {
                return Err(CapsuleError::HistoryTooLarge(format!(
                    "History total size is {} characters, maximum is {}",
                    total_chars, MAX_HISTORY_CHARS
                )));
            }

            for msg in history {
                if !ALLOWED_ROLES.contains(&msg.role.as_str()) {
                    return Err(CapsuleError::HistoryTooLarge(format!(
                        "Invalid role '{}', allowed: {:?}",
                        msg.role, ALLOWED_ROLES
                    )));
                }
            }
        }
        Ok(())
    }
}

/// 채팅 응답
#[derive(Debug, Serialize)]
pub struct ChatResponse {
    /// AI 응답 텍스트
    pub response: String,
    /// Tool calls (함수 호출 요청, 있을 경우)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_calls: Option<serde_json::Value>,
}

/// Public Key 응답
#[derive(Debug, Serialize)]
pub struct PublicKeyResponse {
    /// 서버 공개키 (base64)
    pub public_key: String,
    /// 키 고유 식별자
    pub key_id: String,
    /// 키 만료 예상 시각 (RFC 3339) - 클라이언트 캐시 갱신 힌트
    pub expires_at: String,
}

/// 스트리밍 청크
#[derive(Debug, Serialize)]
pub struct StreamChunk {
    /// 응답 텍스트 조각
    pub content: String,
    /// 완료 여부
    pub done: bool,
}


================================================
FILE: apps/capsule/src/types/mod.rs
================================================
mod encrypted;
mod decrypted;

pub use encrypted::*;
pub use decrypted::*;


================================================
FILE: apps/capsule/tests/api.test.mjs
================================================
/**
 * Capsule API 테스트
 *
 * 사용법:
 *   cd apps/capsule/tests
 *   npm install
 *   npm test                         # 텍스트 전용 테스트
 *   npm test ./path/to/image.jpg     # 이미지 분석 테스트
 */

import { readFileSync } from 'fs';
import { encryptImage } from './crypto.mjs';

const BASE_URL = process.env.CAPSULE_URL || 'http://localhost:3000';

// 테스트 결과 추적
const results = {
  passed: 0,
  failed: 0,
  tests: [],
};

/** 테스트 실행 헬퍼 */
async function test(name, fn) {
  process.stdout.write(`  ${name}... `);
  try {
    await fn();
    console.log('✓');
    results.passed++;
    results.tests.push({ name, status: 'passed' });
  } catch (error) {
    console.log('✗');
    console.log(`    Error: ${error.message}`);
    results.failed++;
    results.tests.push({ name, status: 'failed', error: error.message });
  }
}

/** 단언 헬퍼 */
function assert(condition, message) {
  if (!condition) {
    throw new Error(message || 'Assertion failed');
  }
}

function assertEqual(actual, expected, message) {
  if (actual !== expected) {
    throw new Error(message || `Expected ${expected}, got ${actual}`);
  }
}

/** API 호출 헬퍼 */
async function api(path, options = {}) {
  const response = await fetch(`${BASE_URL}${path}`, {
    headers: { 'Content-Type': 'application/json' },
    ...options,
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`API Error ${response.status}: ${error}`);
  }

  const contentType = response.headers.get('content-type');
  if (contentType && contentType.includes('application/json')) {
    return response.json();
  }
  return response.text();
}

// ============================================
// 테스트 케이스
// ============================================

async function testHealthCheck() {
  const response = await fetch(`${BASE_URL}/health`);
  assertEqual(response.status, 200, 'Health check should return 200');
  const text = await response.text();
  assertEqual(text, 'OK', 'Health check should return OK');
}

async function testPublicKey() {
  const data = await api('/api/public-key');
  assert(data.public_key, 'Response should have public_key');
  assert(data.public_key.length > 0, 'Public key should not be empty');
  // Base64 encoded 32 bytes = 44 characters
  assertEqual(data.public_key.length, 44, 'Public key should be 44 chars (base64 of 32 bytes)');
  return data.public_key;
}

async function testTextChat() {
  const data = await api('/api/chat', {
    method: 'POST',
    body: JSON.stringify({ message: '1+1은?' }),
  });
  assert(data.response, 'Response should have response field');
  assert(data.response.length > 0, 'Response should not be empty');
}

async function testTextChatEmptyMessage() {
  const data = await api('/api/chat', {
    method: 'POST',
    body: JSON.stringify({ message: '' }),
  });
  // 빈 메시지도 처리되어야 함
  assert(data.response !== undefined, 'Response should exist');
}

async function testImageEncryption(publicKey) {
  // 테스트용 더미 이미지 데이터 (1x1 JPEG)
  const dummyJpeg = new Uint8Array([
    0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01,
    0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43,
    0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, 0x07, 0x07, 0x09,
    0x09, 0x08, 0x0a, 0x0c, 0x14, 0x0d, 0x0c, 0x0b, 0x0b, 0x0c, 0x19, 0x12,
    0x13, 0x0f, 0x14, 0x1d, 0x1a, 0x1f, 0x1e, 0x1d, 0x1a, 0x1c, 0x1c, 0x20,
    0x24, 0x2e, 0x27, 0x20, 0x22, 0x2c, 0x23, 0x1c, 0x1c, 0x28, 0x37, 0x29,
    0x2c, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1f, 0x27, 0x39, 0x3d, 0x38, 0x32,
    0x3c, 0x2e, 0x33, 0x34, 0x32, 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01,
    0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00,
    0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
    0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, 0x02, 0x01, 0x03,
    0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d,
    0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
    0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
    0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72,
    0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
    0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45,
    0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
    0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75,
    0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
    0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
    0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
    0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
    0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
    0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4,
    0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
    0x00, 0x00, 0x3f, 0x00, 0xfb, 0xd5, 0xdb, 0x20, 0xa8, 0xf1, 0x45, 0x10,
    0xff, 0xd9,
  ]);

  const encrypted = await encryptImage(dummyJpeg, publicKey);

  assert(encrypted.ephemeral_public_key, 'Should have ephemeral_public_key');
  assert(encrypted.nonce, 'Should have nonce');
  assert(encrypted.ciphertext, 'Should have ciphertext');

  assertEqual(encrypted.ephemeral_public_key.length, 44, 'Ephemeral public key should be 44 chars');
  assertEqual(encrypted.nonce.length, 32, 'Nonce should be 32 chars (24 bytes base64)');
  assert(encrypted.ciphertext.length > 0, 'Ciphertext should not be empty');
}

async function testImageAnalysis(imagePath, publicKey) {
  // 이미지 로드
  const imageData = new Uint8Array(readFileSync(imagePath));
  console.log(`\n    Image: ${imagePath} (${imageData.length} bytes)`);

  // 이미지 암호화
  const encrypted = await encryptImage(imageData, publicKey);
  console.log(`    Encrypted: ${encrypted.ciphertext.length} chars (base64)`);

  // API 호출
  const data = await api('/api/chat', {
    method: 'POST',
    body: JSON.stringify({
      message: '이 이미지에 무엇이 있는지 간단히 설명해주세요.',
      encrypted_image: encrypted,
    }),
  });

  assert(data.response, 'Response should have response field');
  assert(data.response.length > 0, 'Response should not be empty');
  console.log(`    Response: ${data.response.substring(0, 100)}...`);
}

async function testInvalidEncryptedImage() {
  try {
    await api('/api/chat', {
      method: 'POST',
      body: JSON.stringify({
        message: 'test',
        encrypted_image: {
          ephemeral_public_key: 'invalid',
          nonce: 'invalid',
          ciphertext: 'invalid',
        },
      }),
    });
    throw new Error('Should have thrown error for invalid encrypted image');
  } catch (error) {
    assert(error.message.includes('API Error'), 'Should return API error');
  }
}

// ============================================
// 메인
// ============================================

async function main() {
  console.log(`\nCapsule API Tests`);
  console.log(`Server: ${BASE_URL}`);
  console.log('─'.repeat(50));

  let publicKey = null;

  // 기본 테스트
  console.log('\n[Basic Tests]');
  await test('Health check', testHealthCheck);
  await test('Get public key', async () => {
    publicKey = await testPublicKey();
  });

  // 텍스트 채팅 테스트
  console.log('\n[Text Chat Tests]');
  await test('Text chat', testTextChat);
  await test('Empty message', testTextChatEmptyMessage);

  // 암호화 테스트
  console.log('\n[Encryption Tests]');
  await test('Image encryption', () => testImageEncryption(publicKey));
  await test('Invalid encrypted image', testInvalidEncryptedImage);

  // 이미지 분석 테스트 (이미지 경로가 제공된 경우)
  const imagePath = process.argv[2];
  if (imagePath) {
    console.log('\n[Image Analysis Tests]');
    await test('Image analysis', () => testImageAnalysis(imagePath, publicKey));
  } else {
    console.log('\n[Image Analysis Tests]');
    console.log('  Skipped (no image path provided)');
    console.log('  Usage: npm test ./path/to/image.jpg');
  }

  // 결과 출력
  console.log('\n' + '─'.repeat(50));
  console.log(`Results: ${results.passed} passed, ${results.failed} failed`);

  if (results.failed > 0) {
    console.log('\nFailed tests:');
    results.tests
      .filter((t) => t.status === 'failed')
      .forEach((t) => console.log(`  - ${t.name}: ${t.error}`));
    process.exit(1);
  }

  console.log('\nAll tests passed! ✓\n');
}

main().catch((error) => {
  console.error('\nTest suite failed:', error.message);
  process.exit(1);
});


================================================
FILE: apps/capsule/tests/crypto.mjs
================================================
/**
 * 클라이언트 측 암호화 유틸리티
 * 서버의 암호화 스킴과 동일하게 구현
 */

import { x25519 } from '@noble/curves/ed25519';
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { hkdf } from '@noble/hashes/hkdf';
import { sha256 } from '@noble/hashes/sha256';
import { randomBytes } from '@noble/ciphers/webcrypto';

/** HKDF 상수 (서버와 동일해야 함) */
const HKDF_SALT = new TextEncoder().encode('vessel-capsule-v1-salt');
const HKDF_INFO = new TextEncoder().encode('vessel-capsule-v1-key');

/** Base64 인코딩 */
export function bytesToBase64(bytes) {
  return Buffer.from(bytes).toString('base64');
}

/** Base64 디코딩 */
export function base64ToBytes(base64) {
  return new Uint8Array(Buffer.from(base64, 'base64'));
}

/**
 * 이미지를 서버 공개키로 암호화
 *
 * @param {Uint8Array} imageData - 암호화할 이미지 데이터
 * @param {string} serverPublicKeyBase64 - 서버 공개키 (base64)
 * @returns {Promise<Object>} 암호화된 이미지 데이터
 */
export async function encryptImage(imageData, serverPublicKeyBase64) {
  // 1. 서버 공개키 디코딩
  const serverPublicKey = base64ToBytes(serverPublicKeyBase64);

  // 2. 클라이언트 임시 키 쌍 생성
  const ephemeralPrivateKey = randomBytes(32);
  const ephemeralPublicKey = x25519.getPublicKey(ephemeralPrivateKey);

  // 3. Shared secret 계산 (X25519 ECDH)
  const sharedSecret = x25519.getSharedSecret(ephemeralPrivateKey, serverPublicKey);

  // 4. HKDF로 대칭키 유도
  const symmetricKey = hkdf(sha256, sharedSecret, HKDF_SALT, HKDF_INFO, 32);

  // 5. Nonce 생성 (24 bytes)
  const nonce = randomBytes(24);

  // 6. XChaCha20-Poly1305로 암호화
  const cipher = xchacha20poly1305(symmetricKey, nonce);
  const ciphertext = cipher.encrypt(imageData);

  // 민감 데이터 클리어
  ephemeralPrivateKey.fill(0);
  sharedSecret.fill(0);
  symmetricKey.fill(0);

  return {
    ephemeral_public_key: bytesToBase64(ephemeralPublicKey),
    nonce: bytesToBase64(nonce),
    ciphertext: bytesToBase64(ciphertext),
  };
}


================================================
FILE: apps/capsule/tests/package.json
================================================
{
  "name": "capsule-tests",
  "type": "module",
  "private": true,
  "scripts": {
    "test": "node api.test.mjs",
    "test:image": "node api.test.mjs"
  },
  "dependencies": {
    "@noble/ciphers": "^0.5.0",
    "@noble/curves": "^1.3.0",
    "@noble/hashes": "^1.3.3"
  }
}


================================================
FILE: apps/client/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

maindist

================================================
FILE: apps/client/README.md
================================================
# client


================================================
FILE: apps/client/components.json
================================================
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "src/index.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide"
}


================================================
FILE: apps/client/eslint.config.js
================================================
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";

export default tseslint.config(
  { ignores: ["dist"] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ["**/*.{ts,tsx}"],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      "react-hooks": reactHooks,
      "react-refresh": reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      "react-refresh/only-export-components": [
        "warn",
        { allowConstantExport: true },
      ],
    },
  },
);


================================================
FILE: apps/client/index.html
================================================
<!DOCTYPE html>
<html lang="en" class="dark">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vessel</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: apps/client/package.json
================================================
{
  "name": "client",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "main": "maindist/main.js",
  "scripts": {
    "dev": "concurrently \"npm run dev:vite\"",
    "dev:vite": "vite --host",
    "build:main": "tsc -p ./configs/electron",
    "build:app": "tsc -b && vite build",
    "build:vite": "tsc -b && vite build",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview",
    "test": "vitest run",
    "test:watch": "vitest"
  },
  "dependencies": {
    "@vessel/capsule-client": "file:../../packages/capsule-client",
    "@codemirror/lang-json": "^6.0.2",
    "@emotion/react": "^11.14.0",
    "@monaco-editor/react": "^4.7.0",
    "@radix-ui/react-alert-dialog": "^1.1.14",
    "@radix-ui/react-avatar": "^1.1.10",
    "@radix-ui/react-context-menu": "^2.2.16",
    "@radix-ui/react-dialog": "^1.1.15",
    "@radix-ui/react-dropdown-menu": "^2.1.15",
    "@radix-ui/react-label": "^2.1.7",
    "@radix-ui/react-navigation-menu": "^1.2.14",
    "@radix-ui/react-popover": "^1.1.15",
    "@radix-ui/react-scroll-area": "^1.2.10",
    "@radix-ui/react-select": "^2.2.5",
    "@radix-ui/react-separator": "^1.1.7",
    "@radix-ui/react-slot": "^1.2.3",
    "@radix-ui/react-switch": "^1.2.5",
    "@radix-ui/react-tabs": "^1.1.13",
    "@radix-ui/react-tooltip": "^1.2.7",
    "@shadcn/ui": "^0.0.4",
    "@supabase/supabase-js": "^2.49.4",
    "@tailwindcss/postcss": "^4.1.8",
    "@tauri-apps/api": "^2.2.0",
    "@tauri-apps/plugin-shell": "^2.3.4",
    "@uiw/react-codemirror": "^4.24.2",
    "autoprefixer": "^10.4.21",
    "axios": "^1.11.0",
    "clsx": "^2.1.1",
    "cmdk": "^1.1.1",
    "d3": "^7.9.0",
    "electron": "^35.0.0",
    "js-cookie": "^3.0.5",
    "leaflet": "^1.9.4",
    "maplibre-gl": "^5.6.2",
    "next-themes": "^0.4.6",
    "postcss": "^8.5.4",
    "react-hook-form": "^7.62.0",
    "react-icons": "^5.5.0",
    "react-leaflet": "^5.0.0",
    "react-map-gl": "^8.0.4",
    "react-resizable-panels": "^3.0.5",
    "sonner": "^2.0.5",
    "vite-plugin-dts": "^4.5.3",
    "zustand": "^5.0.5"
  },
  "devDependencies": {
    "vitest": "^3.0.0",
    "@eslint/js": "^9.21.0",
    "@types/d3": "^7.4.3",
    "@types/js-cookie": "^3.0.6",
    "@types/leaflet": "^1.9.20",
    "wait-on": "^8.0.3"
  }
}


================================================
FILE: apps/client/src/App.tsx
================================================
import { AuthPage } from "./pages/auth";

import { createBrowserRouter, RouterProvider } from "react-router";
import {
  DashboardSwipeLayout,
  DashboardSwipeRoutePlaceholder,
} from "./features/dashboard-swipe/DashboardSwipeLayout";
import { DevicePage } from "./pages/devices";
import { FlowPage } from "./pages/flow";
import { AuthInterceptor } from "./features/auth/AuthInterceptor";
import { NotFound } from "./pages/notfound";
import LandingPage from "./pages/landing";
import { MapPage } from "./pages/map";
import { SetupPage } from "./pages/setup";
import { CodePage } from "./pages/code";
import { AuthenticatedLayout } from "./widgets/auth/AuthenticatedLayout";
import { TopBarWrapper } from "./widgets/auth/TopBarWrapper";
import { useDesktopSidecar } from "./hooks/useDesktopSidecar";
import { usePreventBackNavigation } from "./hooks/usePreventBackNavigation";
import { SettingsPage } from "./pages/settings";
import { AccountSettingsPage } from "./pages/settings/account";
import { ServicesSettingsPage } from "./pages/settings/services";
import { UsersSettingsPage } from "./pages/settings/users";
import { NetworksSettingsPage } from "./pages/settings/networks";
import { IntegrationSettingsPage } from "./pages/settings/integration";
import { LogSettingsPage } from "./pages/settings/log";
import { ConfigSettingsPage } from "./pages/settings/config";
import { RecordingsPage } from "./pages/recordings";
import { DesktopSettingsPage } from "./pages/desktop-settings";

const router = createBrowserRouter([
  {
    path: "/",
    element: <LandingPage />,
  },
  {
    path: "/auth",
    element: (
      <TopBarWrapper hide={true}>
        <AuthPage />
      </TopBarWrapper>
    ),
  },
  {
    element: <AuthenticatedLayout />,
    children: [
      {
        element: (
          <AuthInterceptor>
            <DashboardSwipeLayout />
          </AuthInterceptor>
        ),
        children: [
          {
            path: "/dashboard",
            element: <DashboardSwipeRoutePlaceholder />,
          },
          {
            path: "/dynamic-dashboard/new",
            element: <DashboardSwipeRoutePlaceholder />,
          },
          {
            path: "/dynamic-dashboard/:dashboardId",
            element: <DashboardSwipeRoutePlaceholder />,
          },
          {
            path: "/dynamic-dashboard",
            element: <DashboardSwipeRoutePlaceholder />,
          },
        ],
      },
      {
        path: "/devices",
        element: (
          <AuthInterceptor>
            <DevicePage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/flow",
        element: (
          <AuthInterceptor>
            <FlowPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/map",
        element: (
          <AuthInterceptor>
            <MapPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/setup",
        element: (
          <AuthInterceptor>
            <SetupPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/settings",
        element: (
          <AuthInterceptor>
            <SettingsPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/settings/account",
        element: (
          <AuthInterceptor>
            <AccountSettingsPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/settings/services",
        element: (
          <AuthInterceptor>
            <ServicesSettingsPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/settings/users",
        element: (
          <AuthInterceptor>
            <UsersSettingsPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/settings/networks",
        element: (
          <AuthInterceptor>
            <NetworksSettingsPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/settings/integration",
        element: (
          <AuthInterceptor>
            <IntegrationSettingsPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/settings/log",
        element: (
          <AuthInterceptor>
            <LogSettingsPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/settings/config",
        element: (
          <AuthInterceptor>
            <ConfigSettingsPage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/code",
        element: (
          <AuthInterceptor>
            <CodePage />
          </AuthInterceptor>
        ),
      },
      {
        path: "/recordings",
        element: (
          <AuthInterceptor>
            <RecordingsPage />
          </AuthInterceptor>
        ),
      },
    ],
  },
  {
    path: "*",
    element: (
      <TopBarWrapper>
        <NotFound />
      </TopBarWrapper>
    ),
  },
]);

const isDesktopSettingsWindow = (): boolean => {
  if (typeof window === "undefined") return false;
  try {
    const params = new URLSearchParams(window.location.search);
    if (params.get("view") === "desktop_settings") return true;
    if (params.get("desktop_settings") === "1") return true;
    if (window.location.hash.includes("desktop_settings")) return true;
    return false;
  } catch {
    return false;
  }
};

function App() {
  useDesktopSidecar();
  usePreventBackNavigation();

  if (isDesktopSettingsWindow()) {
    return <DesktopSettingsPage />;
  }

  return (
    <>
      <RouterProvider router={router} />
    </>
  );
}

export default App;


================================================
FILE: apps/client/src/app/pageWrapper/page-wrapper.tsx
================================================
import { isElectron } from "@/lib/electron";
import type { PropsWithChildren } from "react";

export function PageWrapper(props: PropsWithChildren) {
  if (isElectron()) {
    return (
      <div className='relative overflow-scroll h-[calc(100%_-_34px)] top-[34px]'>
        {props.children}
      </div>
    );
  } else {
    return (
      <div className='relative overflow-scroll h-full'>{props.children}</div>
    );
  }
}


================================================
FILE: apps/client/src/app/providers/theme-provider.tsx
================================================
import { createContext, useContext, useEffect, useState } from "react";

type Theme = "dark" | "light" | "system";

type ThemeProviderProps = {
  children: React.ReactNode;
  defaultTheme?: Theme;
  storageKey?: string;
};

type ThemeProviderState = {
  theme: Theme;
  setTheme: (theme: Theme) => void;
};

const initialState: ThemeProviderState = {
  theme: "system",
  setTheme: () => null,
};

const ThemeProviderContext = createContext<ThemeProviderState>(initialState);

export function ThemeProvider({
  children,
  defaultTheme = "system",
  storageKey = "vite-ui-theme",
  ...props
}: ThemeProviderProps) {
  const [theme, setTheme] = useState<Theme>(
    () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
  );

  useEffect(() => {
    const root = window.document.documentElement;

    root.classList.remove("light", "dark");

    if (theme === "system") {
      const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
        .matches
        ? "dark"
        : "light";

      root.classList.add(systemTheme);
      return;
    }

    root.classList.add(theme);
  }, [theme]);

  const value = {
    theme,
    setTheme: (theme: Theme) => {
      localStorage.setItem(storageKey, theme);
      setTheme(theme);
    },
  };

  return (
    <ThemeProviderContext.Provider {...props} value={value}>
      {children}
    </ThemeProviderContext.Provider>
  );
}

export const useTheme = () => {
  const context = useContext(ThemeProviderContext);

  if (context === undefined)
    throw new Error("useTheme must be used within a ThemeProvider");

  return context;
};


================================================
FILE: apps/client/src/components/icon/Logo.tsx
================================================
export function VesselLogo() {
  return (
    <svg
      width='285'
      height='133'
      viewBox='0 0 285 133'
      fill='none'
      xmlns='http://www.w3.org/2000/svg'
    >
      <path
        d='M168.023 87.8333L161.544 87.2898L155.965 65.3658L138.509 58.843L137.429 54.4945L139.589 54.8568L139.949 52.139L135.99 50.3271L134.37 52.5014L135.27 58.2994H131.671L129.151 36.5567L139.229 36.1943V34.5636L128.251 34.2012L127.891 28.5844L133.47 28.7656L133.29 27.316L127.351 26.5913L126.992 22.6051L137.429 23.5111L137.069 21.6992L126.812 20.9744L126.272 18.4378L128.971 17.8942V15.5387L123.032 14.6328V8.47234H125.552V7.20401H123.032V0.5H121.953L120.693 15.5387L118.893 16.0823V17.8942L120.693 18.9813V20.9744H111.695V22.4239L121.953 23.1487V34.2012H107.736V35.6508L123.032 36.7379L121.953 46.8845L119.793 59.2054L115.114 65.3658L107.196 66.8154L96.398 65.3658L94.5984 72.251L92.6189 65.3658L83.0809 66.8154L67.6042 65.9094L65.6246 70.6203H59.8658L57.8862 76.2372L45.109 74.9689L18.6546 77.6867L17.7548 82.76H0.838379L6.41719 93.0878L136.889 118.998L247.386 131.5L261.423 114.831L282.838 99.7918L216.972 94.8997H173.242L171.622 91.0947L168.563 90.1888L168.023 87.8333Z'
        fill='#EEEEF5'
        stroke='#EEEEF5'
      />
    </svg>
  );
}


================================================
FILE: apps/client/src/components/ui/alert-dialog.tsx
================================================
import * as React from "react";
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";

import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";

function AlertDialog({
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
  return <AlertDialogPrimitive.Root data-slot='alert-dialog' {...props} />;
}

function AlertDialogTrigger({
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
  return (
    <AlertDialogPrimitive.Trigger data-slot='alert-dialog-trigger' {...props} />
  );
}

function AlertDialogPortal({
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
  return (
    <AlertDialogPrimitive.Portal data-slot='alert-dialog-portal' {...props} />
  );
}

function AlertDialogOverlay({
  className,
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
  return (
    <AlertDialogPrimitive.Overlay
      data-slot='alert-dialog-overlay'
      className={cn(
        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
        className,
      )}
      {...props}
    />
  );
}

function AlertDialogContent({
  className,
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
  return (
    <AlertDialogPortal>
      <AlertDialogOverlay />
      <AlertDialogPrimitive.Content
        data-slot='alert-dialog-content'
        className={cn(
          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:max-w-lg",
          className,
        )}
        {...props}
      />
    </AlertDialogPortal>
  );
}

function AlertDialogHeader({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='alert-dialog-header'
      className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
      {...props}
    />
  );
}

function AlertDialogFooter({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='alert-dialog-footer'
      className={cn(
        "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
        className,
      )}
      {...props}
    />
  );
}

function AlertDialogTitle({
  className,
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
  return (
    <AlertDialogPrimitive.Title
      data-slot='alert-dialog-title'
      className={cn("text-lg font-semibold", className)}
      {...props}
    />
  );
}

function AlertDialogDescription({
  className,
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
  return (
    <AlertDialogPrimitive.Description
      data-slot='alert-dialog-description'
      className={cn("text-muted-foreground text-sm", className)}
      {...props}
    />
  );
}

function AlertDialogAction({
  className,
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {
  return (
    <AlertDialogPrimitive.Action
      className={cn(buttonVariants(), className)}
      {...props}
    />
  );
}

function AlertDialogCancel({
  className,
  ...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
  return (
    <AlertDialogPrimitive.Cancel
      className={cn(buttonVariants({ variant: "outline" }), className)}
      {...props}
    />
  );
}

export {
  AlertDialog,
  AlertDialogPortal,
  AlertDialogOverlay,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogFooter,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogAction,
  AlertDialogCancel,
};


================================================
FILE: apps/client/src/components/ui/alert.tsx
================================================
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const alertVariants = cva(
  "relative w-full border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
  {
    variants: {
      variant: {
        default: "bg-background text-foreground",
        destructive:
          "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  },
);

const Alert = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
  <div
    ref={ref}
    role='alert'
    className={cn(alertVariants({ variant }), className)}
    {...props}
  />
));
Alert.displayName = "Alert";

const AlertTitle = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
  <h5
    ref={ref}
    className={cn("mb-1 font-medium leading-none tracking-tight", className)}
    {...props}
  />
));
AlertTitle.displayName = "AlertTitle";

const AlertDescription = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("text-sm [&_p]:leading-relaxed", className)}
    {...props}
  />
));
AlertDescription.displayName = "AlertDescription";

export { Alert, AlertTitle, AlertDescription };


================================================
FILE: apps/client/src/components/ui/avatar.tsx
================================================
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"

import { cn } from "@/lib/utils"

function Avatar({
  className,
  ...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
  return (
    <AvatarPrimitive.Root
      data-slot="avatar"
      className={cn(
        "relative flex size-8 shrink-0 overflow-hidden rounded-full",
        className
      )}
      {...props}
    />
  )
}

function AvatarImage({
  className,
  ...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
  return (
    <AvatarPrimitive.Image
      data-slot="avatar-image"
      className={cn("aspect-square size-full", className)}
      {...props}
    />
  )
}

function AvatarFallback({
  className,
  ...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
  return (
    <AvatarPrimitive.Fallback
      data-slot="avatar-fallback"
      className={cn(
        "bg-muted flex size-full items-center justify-center rounded-full",
        className
      )}
      {...props}
    />
  )
}

export { Avatar, AvatarImage, AvatarFallback }


================================================
FILE: apps/client/src/components/ui/badge.tsx
================================================
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const badgeVariants = cva(
  "inline-flex items-center justify-center border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
  {
    variants: {
      variant: {
        default:
          "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
        secondary:
          "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
        destructive:
          "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
        outline:
          "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  },
);

function Badge({
  className,
  variant,
  asChild = false,
  ...props
}: React.ComponentProps<"span"> &
  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
  const Comp = asChild ? Slot : "span";

  return (
    <Comp
      data-slot='badge'
      className={cn(badgeVariants({ variant }), className)}
      {...props}
    />
  );
}

export { Badge, badgeVariants };


================================================
FILE: apps/client/src/components/ui/breadcrumb.tsx
================================================
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"

import { cn } from "@/lib/utils"

function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
  return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
}

function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
  return (
    <ol
      data-slot="breadcrumb-list"
      className={cn(
        "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
        className
      )}
      {...props}
    />
  )
}

function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
  return (
    <li
      data-slot="breadcrumb-item"
      className={cn("inline-flex items-center gap-1.5", className)}
      {...props}
    />
  )
}

function BreadcrumbLink({
  asChild,
  className,
  ...props
}: React.ComponentProps<"a"> & {
  asChild?: boolean
}) {
  const Comp = asChild ? Slot : "a"

  return (
    <Comp
      data-slot="breadcrumb-link"
      className={cn("hover:text-foreground transition-colors", className)}
      {...props}
    />
  )
}

function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="breadcrumb-page"
      role="link"
      aria-disabled="true"
      aria-current="page"
      className={cn("text-foreground font-normal", className)}
      {...props}
    />
  )
}

function BreadcrumbSeparator({
  children,
  className,
  ...props
}: React.ComponentProps<"li">) {
  return (
    <li
      data-slot="breadcrumb-separator"
      role="presentation"
      aria-hidden="true"
      className={cn("[&>svg]:size-3.5", className)}
      {...props}
    >
      {children ?? <ChevronRight />}
    </li>
  )
}

function BreadcrumbEllipsis({
  className,
  ...props
}: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="breadcrumb-ellipsis"
      role="presentation"
      aria-hidden="true"
      className={cn("flex size-9 items-center justify-center", className)}
      {...props}
    >
      <MoreHorizontal className="size-4" />
      <span className="sr-only">More</span>
    </span>
  )
}

export {
  Breadcrumb,
  BreadcrumbList,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbPage,
  BreadcrumbSeparator,
  BreadcrumbEllipsis,
}


================================================
FILE: apps/client/src/components/ui/button.tsx
================================================
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer",
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
        destructive:
          "!bg-red-600 !text-white hover:!bg-red-700 shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20",
        outline:
          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
        ghost:
          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2 has-[>svg]:px-3",
        sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
        lg: "h-10 px-6 has-[>svg]:px-4",
        icon: "size-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

function Button({
  className,
  variant,
  size,
  asChild = false,
  ...props
}: React.ComponentProps<"button"> &
  VariantProps<typeof buttonVariants> & {
    asChild?: boolean;
  }) {
  const Comp = asChild ? Slot : "button";

  return (
    <Comp
      data-slot='button'
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  );
}

export { Button, buttonVariants };


================================================
FILE: apps/client/src/components/ui/card.tsx
================================================
import * as React from "react";

import { cn } from "@/lib/utils";

function Card({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='card'
      className={cn(
        "bg-card text-card-foreground flex flex-col gap-6 border py-6 shadow-sm",
        className,
      )}
      {...props}
    />
  );
}

function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='card-header'
      className={cn(
        "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
        className,
      )}
      {...props}
    />
  );
}

function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='card-title'
      className={cn("leading-none font-semibold", className)}
      {...props}
    />
  );
}

function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='card-description'
      className={cn("text-muted-foreground text-sm", className)}
      {...props}
    />
  );
}

function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='card-action'
      className={cn(
        "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
        className,
      )}
      {...props}
    />
  );
}

function CardContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='card-content'
      className={cn("px-6", className)}
      {...props}
    />
  );
}

function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='card-footer'
      className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
      {...props}
    />
  );
}

export {
  Card,
  CardHeader,
  CardFooter,
  CardTitle,
  CardAction,
  CardDescription,
  CardContent,
};


================================================
FILE: apps/client/src/components/ui/command.tsx
================================================
"use client";

import * as React from "react";
import { type DialogProps } from "@radix-ui/react-dialog";
import { Command as CommandPrimitive } from "cmdk";
import { Search } from "lucide-react";

import { cn } from "@/lib/utils";
import { Dialog, DialogContent } from "@/components/ui/dialog";

const Command = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
  <CommandPrimitive
    ref={ref}
    className={cn(
      "flex h-full w-full flex-col overflow-hidden bg-popover text-popover-foreground",
      className,
    )}
    {...props}
  />
));
Command.displayName = CommandPrimitive.displayName;

const CommandDialog = ({ children, ...props }: DialogProps) => {
  return (
    <Dialog {...props}>
      <DialogContent className='overflow-hidden p-0'>
        <Command className='[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5'>
          {children}
        </Command>
      </DialogContent>
    </Dialog>
  );
};

const CommandInput = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Input>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
  <div className='flex items-center border-b px-3' cmdk-input-wrapper=''>
    <Search className='mr-2 h-4 w-4 shrink-0 opacity-50' />
    <CommandPrimitive.Input
      ref={ref}
      className={cn(
        "flex h-10 w-full bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
        className,
      )}
      {...props}
    />
  </div>
));

CommandInput.displayName = CommandPrimitive.Input.displayName;

const CommandList = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.List>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.List
    ref={ref}
    className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
    {...props}
  />
));

CommandList.displayName = CommandPrimitive.List.displayName;

const CommandEmpty = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Empty>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
  <CommandPrimitive.Empty
    ref={ref}
    className='py-6 text-center text-sm'
    {...props}
  />
));

CommandEmpty.displayName = CommandPrimitive.Empty.displayName;

const CommandGroup = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Group>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.Group
    ref={ref}
    className={cn(
      "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
      className,
    )}
    {...props}
  />
));

CommandGroup.displayName = CommandPrimitive.Group.displayName;

const CommandSeparator = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Separator>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.Separator
    ref={ref}
    className={cn("-mx-1 h-px bg-border", className)}
    {...props}
  />
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;

const CommandItem = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.Item
    ref={ref}
    className={cn(
      "relative flex cursor-default gap-2 select-none items-center px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
      className,
    )}
    {...props}
  />
));

CommandItem.displayName = CommandPrimitive.Item.displayName;

const CommandShortcut = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
  return (
    <span
      className={cn(
        "ml-auto text-xs tracking-widest text-muted-foreground",
        className,
      )}
      {...props}
    />
  );
};
CommandShortcut.displayName = "CommandShortcut";

export {
  Command,
  CommandDialog,
  CommandInput,
  CommandList,
  CommandEmpty,
  CommandGroup,
  CommandItem,
  CommandShortcut,
  CommandSeparator,
};


================================================
FILE: apps/client/src/components/ui/context-menu.tsx
================================================
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { Check, ChevronRight, Circle } from "lucide-react"

import { cn } from "@/lib/utils"

const ContextMenu = ContextMenuPrimitive.Root

const ContextMenuTrigger = ContextMenuPrimitive.Trigger

const ContextMenuGroup = ContextMenuPrimitive.Group

const ContextMenuPortal = ContextMenuPrimitive.Portal

const ContextMenuSub = ContextMenuPrimitive.Sub

const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup

const ContextMenuSubTrigger = React.forwardRef<
  React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
    inset?: boolean
  }
>(({ className, inset, children, ...props }, ref) => (
  <ContextMenuPrimitive.SubTrigger
    ref={ref}
    className={cn(
      "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
      inset && "pl-8",
      className
    )}
    {...props}
  >
    {children}
    <ChevronRight className="ml-auto h-4 w-4" />
  </ContextMenuPrimitive.SubTrigger>
))
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName

const ContextMenuSubContent = React.forwardRef<
  React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
  <ContextMenuPrimitive.SubContent
    ref={ref}
    className={cn(
      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
      className
    )}
    {...props}
  />
))
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName

const ContextMenuContent = React.forwardRef<
  React.ElementRef<typeof ContextMenuPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
  <ContextMenuPrimitive.Portal>
    <ContextMenuPrimitive.Content
      ref={ref}
      className={cn(
        "z-50 max-h-[--radix-context-menu-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
        className
      )}
      {...props}
    />
  </ContextMenuPrimitive.Portal>
))
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName

const ContextMenuItem = React.forwardRef<
  React.ElementRef<typeof ContextMenuPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
    inset?: boolean
  }
>(({ className, inset, ...props }, ref) => (
  <ContextMenuPrimitive.Item
    ref={ref}
    className={cn(
      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
      inset && "pl-8",
      className
    )}
    {...props}
  />
))
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName

const ContextMenuCheckboxItem = React.forwardRef<
  React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
  <ContextMenuPrimitive.CheckboxItem
    ref={ref}
    className={cn(
      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
      className
    )}
    checked={checked}
    {...props}
  >
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
      <ContextMenuPrimitive.ItemIndicator>
        <Check className="h-4 w-4" />
      </ContextMenuPrimitive.ItemIndicator>
    </span>
    {children}
  </ContextMenuPrimitive.CheckboxItem>
))
ContextMenuCheckboxItem.displayName =
  ContextMenuPrimitive.CheckboxItem.displayName

const ContextMenuRadioItem = React.forwardRef<
  React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
  <ContextMenuPrimitive.RadioItem
    ref={ref}
    className={cn(
      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
      className
    )}
    {...props}
  >
    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
      <ContextMenuPrimitive.ItemIndicator>
        <Circle className="h-4 w-4 fill-current" />
      </ContextMenuPrimitive.ItemIndicator>
    </span>
    {children}
  </ContextMenuPrimitive.RadioItem>
))
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName

const ContextMenuLabel = React.forwardRef<
  React.ElementRef<typeof ContextMenuPrimitive.Label>,
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
    inset?: boolean
  }
>(({ className, inset, ...props }, ref) => (
  <ContextMenuPrimitive.Label
    ref={ref}
    className={cn(
      "px-2 py-1.5 text-sm font-semibold text-foreground",
      inset && "pl-8",
      className
    )}
    {...props}
  />
))
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName

const ContextMenuSeparator = React.forwardRef<
  React.ElementRef<typeof ContextMenuPrimitive.Separator>,
  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
  <ContextMenuPrimitive.Separator
    ref={ref}
    className={cn("-mx-1 my-1 h-px bg-border", className)}
    {...props}
  />
))
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName

const ContextMenuShortcut = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
  return (
    <span
      className={cn(
        "ml-auto text-xs tracking-widest text-muted-foreground",
        className
      )}
      {...props}
    />
  )
}
ContextMenuShortcut.displayName = "ContextMenuShortcut"

export {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuCheckboxItem,
  ContextMenuRadioItem,
  ContextMenuLabel,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuGroup,
  ContextMenuPortal,
  ContextMenuSub,
  ContextMenuSubContent,
  ContextMenuSubTrigger,
  ContextMenuRadioGroup,
}


================================================
FILE: apps/client/src/components/ui/dialog.tsx
================================================
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";

import { cn } from "@/lib/utils";

function Dialog({
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
  return <DialogPrimitive.Root data-slot='dialog' {...props} />;
}

function DialogTrigger({
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
  return <DialogPrimitive.Trigger data-slot='dialog-trigger' {...props} />;
}

function DialogPortal({
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
  return <DialogPrimitive.Portal data-slot='dialog-portal' {...props} />;
}

function DialogClose({
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
  return <DialogPrimitive.Close data-slot='dialog-close' {...props} />;
}

function DialogOverlay({
  className,
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
  return (
    <DialogPrimitive.Overlay
      data-slot='dialog-overlay'
      className={cn(
        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
        className,
      )}
      {...props}
    />
  );
}

function DialogContent({
  className,
  children,
  showCloseButton = true,
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
  showCloseButton?: boolean;
}) {
  return (
    <DialogPortal data-slot='dialog-portal'>
      <DialogOverlay />
      <DialogPrimitive.Content
        data-slot='dialog-content'
        className={cn(
          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4  border p-6 shadow-lg duration-200 sm:max-w-lg",
          className,
        )}
        {...props}
      >
        {children}
        {showCloseButton && (
          <DialogPrimitive.Close
            data-slot='dialog-close'
            className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
          >
            <XIcon />
            <span className='sr-only'>Close</span>
          </DialogPrimitive.Close>
        )}
      </DialogPrimitive.Content>
    </DialogPortal>
  );
}

function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='dialog-header'
      className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
      {...props}
    />
  );
}

function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot='dialog-footer'
      className={cn(
        "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
        className,
      )}
      {...props}
    />
  );
}

function DialogTitle({
  className,
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
  return (
    <DialogPrimitive.Title
      data-slot='dialog-title'
      className={cn("text-lg leading-none font-semibold", className)}
      {...props}
    />
  );
}

function DialogDescription({
  className,
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
  return (
    <DialogPrimitive.Description
      data-slot='dialog-description'
      className={cn("text-muted-foreground text-sm", className)}
      {...props}
    />
  );
}

export {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogOverlay,
  DialogPortal,
  DialogTitle,
  DialogTrigger,
};


================================================
FILE: apps/client/src/components/ui/dropdown-menu.tsx
================================================
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";

import { cn } from "@/lib/utils";

function DropdownMenu({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
  return <DropdownMenuPrimitive.Root data-slot='dropdown-menu' {...props} />;
}

function DropdownMenuPortal({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
  return (
    <DropdownMenuPrimitive.Portal data-slot='dropdown-menu-portal' {...props} />
  );
}

function DropdownMenuTrigger({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
  return (
    <DropdownMenuPrimitive.Trigger
      data-slot='dropdown-menu-trigger'
      {...props}
    />
  );
}

function DropdownMenuContent({
  className,
  sideOffset = 4,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
  return (
    <DropdownMenuPrimitive.Portal>
      <DropdownMenuPrimitive.Content
        data-slot='dropdown-menu-content'
        sideOffset={sideOffset}
        className={cn(
          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto border p-1 shadow-md",
          className,
        )}
        {...props}
      />
    </DropdownMenuPrimitive.Portal>
  );
}

function DropdownMenuGroup({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
  return (
    <DropdownMenuPrimitive.Group data-slot='dropdown-menu-group' {...props} />
  );
}

function DropdownMenuItem({
  className,
  inset,
  variant = "default",
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
  inset?: boolean;
  variant?: "default" | "destructive";
}) {
  return (
    <DropdownMenuPrimitive.Item
      data-slot='dropdown-menu-item'
      data-inset={inset}
      data-variant={variant}
      className={cn(
        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
      )}
      {...props}
    />
  );
}

function DropdownMenuCheckboxItem({
  className,
  children,
  checked,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
  return (
    <DropdownMenuPrimitive.CheckboxItem
      data-slot='dropdown-menu-checkbox-item'
      className={cn(
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
      )}
      checked={checked}
      {...props}
    >
      <span className='pointer-events-none absolute left-2 flex size-3.5 items-center justify-center'>
        <DropdownMenuPrimitive.ItemIndicator>
          <CheckIcon className='size-4' />
        </DropdownMenuPrimitive.ItemIndicator>
      </span>
      {children}
    </DropdownMenuPrimitive.CheckboxItem>
  );
}

function DropdownMenuRadioGroup({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
  return (
    <DropdownMenuPrimitive.RadioGroup
      data-slot='dropdown-menu-radio-group'
      {...props}
    />
  );
}

function DropdownMenuRadioItem({
  className,
  children,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
  return (
    <DropdownMenuPrimitive.RadioItem
      data-slot='dropdown-menu-radio-item'
      className={cn(
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
      )}
      {...props}
    >
      <span className='pointer-events-none absolute left-2 flex size-3.5 items-center justify-center'>
        <DropdownMenuPrimitive.ItemIndicator>
          <CircleIcon className='size-2 fill-current' />
        </DropdownMenuPrimitive.ItemIndicator>
      </span>
      {children}
    </DropdownMenuPrimitive.RadioItem>
  );
}

function DropdownMenuLabel({
  className,
  inset,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
  inset?: boolean;
}) {
  return (
    <DropdownMenuPrimitive.Label
      data-slot='dropdown-menu-label'
      data-inset={inset}
      className={cn(
        "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
        className,
      )}
      {...props}
    />
  );
}

function DropdownMenuSeparator({
  className,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
  return (
    <DropdownMenuPrimitive.Separator
      data-slot='dropdown-menu-separator'
      className={cn("bg-border -mx-1 my-1 h-px", className)}
      {...props}
    />
  );
}

function DropdownMenuShortcut({
  className,
  ...props
}: React.ComponentProps<"span">) {
  return (
    <span
      data-slot='dropdown-menu-shortcut'
      className={cn(
        "text-muted-foreground ml-auto text-xs tracking-widest",
        className,
      )}
      {...props}
    />
  );
}

function DropdownMenuSub({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
  return <DropdownMenuPrimitive.Sub data-slot='dropdown-menu-sub' {...props} />;
}

function DropdownMenuSubTrigger({
  className,
  inset,
  children,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
  inset?: boolean;
}) {
  return (
    <DropdownMenuPrimitive.SubTrigger
      data-slot='dropdown-menu-sub-trigger'
      data-inset={inset}
      className={cn(
        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
        className,
      )}
      {...props}
    >
      {children}
      <ChevronRightIcon className='ml-auto size-4' />
    </DropdownMenuPrimitive.SubTrigger>
  );
}

function DropdownMenuSubContent({
  className,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
  return (
    <DropdownMenuPrimitive.SubContent
      data-slot='dropdown-menu-sub-content'
      className={cn(
        "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
        className,
      )}
      {...props}
    />
  );
}

export {
  DropdownMenu,
  DropdownMenuPortal,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
};


================================================
FILE: apps/client/src/components/ui/input.tsx
================================================
import * as React from "react";

import { cn } from "@/lib/utils";

function Input({ className, type, ...props }: React.ComponentProps<"input">) {
  return (
    <input
      type={type}
      data-slot='input'
      className={cn(
        "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0  border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
        "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
        className,
      )}
      {...props}
    />
  );
}

export { Input };


================================================
FILE: apps/client/src/components/ui/label.tsx
================================================
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"

import { cn } from "@/lib/utils"

function Label({
  className,
  ...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
  return (
    <LabelPrimitive.Root
      data-slot="label"
      className={cn(
        "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
        className
      )}
      {...props}
    />
  )
}

export { Label }


================================================
FILE: apps/client/src/components/ui/navigation-menu.tsx
================================================
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDownIcon } from "lucide-react"

import { cn } from "@/lib/utils"

function NavigationMenu({
  className,
  children,
  viewport = true,
  ...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
  viewport?: boolean
}) {
  return (
    <NavigationMenuPrimitive.Root
      data-slot="navigation-menu"
      data-viewport={viewport}
      className={cn(
        "group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
        className
      )}
      {...props}
    >
      {children}
      {viewport && <NavigationMenuViewport />}
    </NavigationMenuPrimitive.Root>
  )
}

function NavigationMenuList({
  className,
  ...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
  return (
    <NavigationMenuPrimitive.List
      data-slot="navigation-menu-list"
      className={cn(
        "group flex flex-1 list-none items-center justify-center gap-1",
        className
      )}
      {...props}
    />
  )
}

function NavigationMenuItem({
  className,
  ...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
  return (
    <NavigationMenuPrimitive.Item
      data-slot="navigation-menu-item"
      className={cn("relative", className)}
      {...props}
    />
  )
}

const navigationMenuTriggerStyle = cva(
  "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
)

function NavigationMenuTrigger({
  className,
  children,
  ...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
  return (
    <NavigationMenuPrimitive.Trigger
      data-slot="navigation-menu-trigger"
      className={cn(navigationMenuTriggerStyle(), "group", className)}
      {...props}
    >
      {children}{" "}
      <ChevronDownIcon
        className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
        aria-hidden="true"
      />
    </NavigationMenuPrimitive.Trigger>
  )
}

function NavigationMenuContent({
  className,
  ...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
  return (
    <NavigationMenuPrimitive.Content
      data-slot="navigation-menu-content"
      className={cn(
        "data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
        "group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
        className
      )}
      {...props}
    />
  )
}

function NavigationMenuViewport({
  className,
  ...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
  return (
    <div
      className={cn(
        "absolute top-full left-0 isolate z-50 flex justify-center"
      )}
    >
      <NavigationMenuPrimitive.Viewport
        data-slot="navigation-menu-viewport"
        className={cn(
          "origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
          className
        )}
        {...props}
      />
    </div>
  )
}

function NavigationMenuLink({
  className,
  ...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
  return (
    <NavigationMenuPrimitive.Link
      data-slot="navigation-menu-link"
      className={cn(
        "data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    />
  )
}

function NavigationMenuIndicator({
  className,
  ...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
  return (
    <NavigationMenuPrimitive.Indicator
      data-slot="navigation-menu-indicator"
      className={cn(
        "data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
        className
      )}
      {...props}
    >
      <div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
    </NavigationMenuPrimitive.Indicator>
  )
}

export {
  NavigationMenu,
  NavigationMenuList,
  NavigationMenuItem,
  NavigationMenuContent,
  NavigationMenuTrigger,
  NavigationMenuLink,
  NavigationMenuIndicator,
  NavigationMenuViewport,
  navigationMenuTriggerStyle,
}


================================================
FILE: apps/client/src/components/ui/popover.tsx
================================================
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"

import { cn } from "@/lib/utils"

const Popover = PopoverPrimitive.Root

const PopoverTrigger = PopoverPrimitive.Trigger

const PopoverAnchor = PopoverPrimitive.Anchor

type PopoverContentProps = React.ComponentPropsWithoutRef<
  typeof PopoverPrimitive.Content
> & { container?: HTMLElement | null }

const PopoverContent = React.forwardRef<
  React.ElementRef<typeof PopoverPrimitive.Content>,
  PopoverContentProps
>(({ className, align = "center", sideOffset = 4, container, ...props }, ref) => (
  <PopoverPrimitive.Portal container={container || undefined}>
    <PopoverPrimitive.Content
      ref={ref}
      align={align}
      sideOffset={sideOffset}
      className={cn(
        "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]",
        className
      )}
      {...props}
    />
  </PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName

export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }


================================================
FILE: apps/client/src/components/ui/resizable.tsx
================================================
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"

import { cn } from "@/lib/utils"

const ResizablePanelGroup = ({
  className,
  ...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
  <ResizablePrimitive.PanelGroup
    className={cn(
      "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
      className
    )}
    {...props}
  />
)

const ResizablePanel = ResizablePrimitive.Panel

const ResizableHandle = ({
  withHandle,
  className,
  ...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
  withHandle?: boolean
}) => (
  <ResizablePrimitive.PanelResizeHandle
    className={cn(
      "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
      className
    )}
    {...props}
  >
    {withHandle && (
      <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
        <GripVertical className="h-2.5 w-2.5" />
      </div>
    )}
  </ResizablePrimitive.PanelResizeHandle>
)

export { ResizablePanelGroup, ResizablePanel, ResizableHandle }


================================================
FILE: apps/client/src/components/ui/scroll-area.tsx
================================================
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"

import { cn } from "@/lib/utils"

const ScrollArea = React.forwardRef<
  React.ElementRef<typeof ScrollAreaP
Download .txt
gitextract_1sirbdjr/

├── .cargo/
│   └── config.toml
├── .github/
│   ├── GIT.md
│   └── workflows/
│       ├── build.yml
│       └── release-macos.yml
├── .gitignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CODE_RULE.md
├── CONTRIBUTING.md
├── Cargo.toml
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── apps/
│   ├── capsule/
│   │   ├── Cargo.toml
│   │   ├── Dockerfile
│   │   ├── docker-compose.yml
│   │   ├── src/
│   │   │   ├── api/
│   │   │   │   ├── auth.rs
│   │   │   │   ├── chat.rs
│   │   │   │   ├── key.rs
│   │   │   │   ├── mod.rs
│   │   │   │   └── routes.rs
│   │   │   ├── config.rs
│   │   │   ├── crypto/
│   │   │   │   ├── encryption.rs
│   │   │   │   ├── keypair.rs
│   │   │   │   └── mod.rs
│   │   │   ├── error.rs
│   │   │   ├── main.rs
│   │   │   ├── services/
│   │   │   │   ├── jwt.rs
│   │   │   │   ├── mod.rs
│   │   │   │   ├── openai.rs
│   │   │   │   └── usage.rs
│   │   │   └── types/
│   │   │       ├── decrypted.rs
│   │   │       ├── encrypted.rs
│   │   │       └── mod.rs
│   │   └── tests/
│   │       ├── api.test.mjs
│   │       ├── crypto.mjs
│   │       └── package.json
│   ├── client/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── components.json
│   │   ├── eslint.config.js
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── App.tsx
│   │   │   ├── app/
│   │   │   │   ├── pageWrapper/
│   │   │   │   │   └── page-wrapper.tsx
│   │   │   │   └── providers/
│   │   │   │       └── theme-provider.tsx
│   │   │   ├── components/
│   │   │   │   ├── icon/
│   │   │   │   │   └── Logo.tsx
│   │   │   │   └── ui/
│   │   │   │       ├── alert-dialog.tsx
│   │   │   │       ├── alert.tsx
│   │   │   │       ├── avatar.tsx
│   │   │   │       ├── badge.tsx
│   │   │   │       ├── breadcrumb.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── command.tsx
│   │   │   │       ├── context-menu.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── dropdown-menu.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── label.tsx
│   │   │   │       ├── navigation-menu.tsx
│   │   │   │       ├── popover.tsx
│   │   │   │       ├── resizable.tsx
│   │   │   │       ├── scroll-area.tsx
│   │   │   │       ├── select.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       ├── sheet.tsx
│   │   │   │       ├── sidebar.tsx
│   │   │   │       ├── skeleton.tsx
│   │   │   │       ├── sonner.tsx
│   │   │   │       ├── switch.tsx
│   │   │   │       ├── table.tsx
│   │   │   │       ├── tabs.tsx
│   │   │   │       ├── textarea.tsx
│   │   │   │       └── tooltip.tsx
│   │   │   ├── contexts/
│   │   │   │   └── SupabaseAuthContext.tsx
│   │   │   ├── entities/
│   │   │   │   ├── configurations/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── codeService.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── custom-nodes/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── presets.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── device/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── device-token/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── dynamic-dashboard/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── interaction.ts
│   │   │   │   │   ├── layoutResolve.ts
│   │   │   │   │   └── store.ts
│   │   │   │   ├── entity/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── file/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── flow/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── ha/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── integrations/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── log/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── map/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── permission/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── recording/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── role/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── stat/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── tunnel/
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── store.ts
│   │   │   │   │   └── types.ts
│   │   │   │   └── user/
│   │   │   │       ├── api.ts
│   │   │   │       ├── store.ts
│   │   │   │       └── types.ts
│   │   │   ├── features/
│   │   │   │   ├── account-switcher/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── auth/
│   │   │   │   │   ├── AuthInterceptor.tsx
│   │   │   │   │   ├── DefaultAdminPasswordDialog.tsx
│   │   │   │   │   ├── api.ts
│   │   │   │   │   ├── hook.ts
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── code/
│   │   │   │   │   ├── CreateItemDialog.tsx
│   │   │   │   │   ├── FileEditor.tsx
│   │   │   │   │   └── FileTree.tsx
│   │   │   │   ├── configurations/
│   │   │   │   │   ├── ConfigurationActionButton.tsx
│   │   │   │   │   ├── ConfigurationCreate.tsx
│   │   │   │   │   └── ConfigurationCreateButton.tsx
│   │   │   │   ├── darkmode/
│   │   │   │   │   └── mode-toggle.tsx
│   │   │   │   ├── dashboard-swipe/
│   │   │   │   │   ├── DashboardSwipeHeader.tsx
│   │   │   │   │   └── DashboardSwipeLayout.tsx
│   │   │   │   ├── device/
│   │   │   │   │   ├── DeviceCreateButton.tsx
│   │   │   │   │   ├── DeviceDeleteButton.tsx
│   │   │   │   │   ├── DeviceKeyButton.tsx
│   │   │   │   │   └── DeviceUpdateButton.tsx
│   │   │   │   ├── device-token/
│   │   │   │   │   └── DeviceTokenManager.tsx
│   │   │   │   ├── dynamic-dashboard/
│   │   │   │   │   ├── GroupCanvas.tsx
│   │   │   │   │   ├── events/
│   │   │   │   │   │   ├── dispatcher.test.ts
│   │   │   │   │   │   └── dispatcher.ts
│   │   │   │   │   └── panels/
│   │   │   │   │       ├── ButtonPanel.tsx
│   │   │   │   │       ├── FlowPanel.tsx
│   │   │   │   │       └── MapPanel.tsx
│   │   │   │   ├── entity/
│   │   │   │   │   ├── AllEntities.tsx
│   │   │   │   │   ├── AnalyzeMenuItem.tsx
│   │   │   │   │   ├── Card.tsx
│   │   │   │   │   ├── EntityCreateButton.tsx
│   │   │   │   │   ├── EntityDeleteButton.tsx
│   │   │   │   │   ├── EntityUpdateButton.tsx
│   │   │   │   │   ├── SelectPlatforms.tsx
│   │   │   │   │   ├── SelectTypes.tsx
│   │   │   │   │   ├── StateHistorySheet.tsx
│   │   │   │   │   └── useEntitiesData.ts
│   │   │   │   ├── error/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── flow/
│   │   │   │   │   ├── AddCustomNode.tsx
│   │   │   │   │   ├── Flow.tsx
│   │   │   │   │   ├── Graph.tsx
│   │   │   │   │   ├── Options.tsx
│   │   │   │   │   ├── OptionsVariation.tsx
│   │   │   │   │   ├── RunFlow.tsx
│   │   │   │   │   ├── SelectedItemActions.tsx
│   │   │   │   │   ├── flow-chat/
│   │   │   │   │   │   ├── buildSystemPrompt.ts
│   │   │   │   │   │   ├── executeToolCalls.ts
│   │   │   │   │   │   ├── flowTools.ts
│   │   │   │   │   │   └── index.ts
│   │   │   │   │   ├── flowNode.ts
│   │   │   │   │   ├── flowTypes.ts
│   │   │   │   │   ├── flowUtils.ts
│   │   │   │   │   └── nodes/
│   │   │   │   │       ├── ButtonNode.tsx
│   │   │   │   │       ├── CalcNode.tsx
│   │   │   │   │       ├── HttpNode.tsx
│   │   │   │   │       ├── IntervalNode.tsx
│   │   │   │   │       ├── LogicNode.tsx
│   │   │   │   │       ├── LoopNode.tsx
│   │   │   │   │       ├── MQTTNode.tsx
│   │   │   │   │       ├── NumberNode.tsx
│   │   │   │   │       ├── ProcessingNode.tsx
│   │   │   │   │       ├── TitleNode.tsx
│   │   │   │   │       └── VarNode.tsx
│   │   │   │   ├── flow-log/
│   │   │   │   │   └── FlowLog.tsx
│   │   │   │   ├── footer/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── gps/
│   │   │   │   │   └── parseGps.ts
│   │   │   │   ├── ha/
│   │   │   │   │   ├── HaEntitiesTable.tsx
│   │   │   │   │   ├── HaStatBlock.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── integration/
│   │   │   │   │   ├── HA.tsx
│   │   │   │   │   ├── Integration.tsx
│   │   │   │   │   ├── ROS.tsx
│   │   │   │   │   ├── SDR.tsx
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   └── types.ts
│   │   │   │   ├── json/
│   │   │   │   │   └── JsonEditor.tsx
│   │   │   │   ├── llm-chat/
│   │   │   │   │   ├── ChatInput.tsx
│   │   │   │   │   ├── ChatMessage.tsx
│   │   │   │   │   ├── ChatMessages.tsx
│   │   │   │   │   ├── ChatPanel.tsx
│   │   │   │   │   ├── ChatPanelContainer.tsx
│   │   │   │   │   ├── ChatPanelMobile.tsx
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   ├── store.ts
│   │   │   │   │   ├── types.ts
│   │   │   │   │   └── useChatKeyboard.ts
│   │   │   │   ├── log/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── map/
│   │   │   │   │   ├── CurrentLocationMarker.tsx
│   │   │   │   │   ├── MapViewPersistence.tsx
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── style.css
│   │   │   │   ├── map-draw/
│   │   │   │   │   ├── FeatureDetailsPanel.tsx
│   │   │   │   │   ├── FeatureDrawingPreview.tsx
│   │   │   │   │   ├── FeatureEditor.tsx
│   │   │   │   │   ├── FeatureRenderer.tsx
│   │   │   │   │   ├── LayerDialog.tsx
│   │   │   │   │   ├── LayerSidebar.tsx
│   │   │   │   │   ├── MapEvents.tsx
│   │   │   │   │   └── MapToolbar.tsx
│   │   │   │   ├── map-entity/
│   │   │   │   │   ├── EntityDetailsPanel.tsx
│   │   │   │   │   ├── render.tsx
│   │   │   │   │   └── store.ts
│   │   │   │   ├── recording/
│   │   │   │   │   ├── RecordingButton.tsx
│   │   │   │   │   ├── RecordingsList.tsx
│   │   │   │   │   ├── VideoPlaybackDialog.tsx
│   │   │   │   │   ├── components/
│   │   │   │   │   │   ├── AudioWaveformPlayer.tsx
│   │   │   │   │   │   ├── FrameTimeline.tsx
│   │   │   │   │   │   ├── PlaybackControls.tsx
│   │   │   │   │   │   ├── TimeRuler.tsx
│   │   │   │   │   │   ├── VideoControlBar.tsx
│   │   │   │   │   │   └── WaveformCanvas.tsx
│   │   │   │   │   ├── hooks/
│   │   │   │   │   │   ├── useAudioWaveform.ts
│   │   │   │   │   │   ├── useMediaPlayback.ts
│   │   │   │   │   │   └── useVideoFrames.ts
│   │   │   │   │   └── index.ts
│   │   │   │   ├── role/
│   │   │   │   │   ├── RoleDialogs.tsx
│   │   │   │   │   └── RoleForm.tsx
│   │   │   │   ├── ros2/
│   │   │   │   │   └── Ros2Dashboard.tsx
│   │   │   │   ├── rtc/
│   │   │   │   │   ├── AudioLevelBar.tsx
│   │   │   │   │   ├── StreamReceiver.tsx
│   │   │   │   │   ├── WebRTCProvider.tsx
│   │   │   │   │   ├── captureFrame.ts
│   │   │   │   │   ├── rtc.ts
│   │   │   │   │   └── turnService.ts
│   │   │   │   ├── sdr/
│   │   │   │   │   ├── SdrAudioPlayer.tsx
│   │   │   │   │   ├── SdrDashboard.tsx
│   │   │   │   │   └── api.ts
│   │   │   │   ├── search/
│   │   │   │   │   └── search-form.tsx
│   │   │   │   ├── server-resource/
│   │   │   │   │   └── resourceUsage.tsx
│   │   │   │   ├── setup/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── sidebar/
│   │   │   │   │   ├── footer.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── stat/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── topbar/
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── style.css
│   │   │   │   ├── user/
│   │   │   │   │   ├── UserRoleAssigner.tsx
│   │   │   │   │   ├── userAdd.tsx
│   │   │   │   │   ├── userDelete.tsx
│   │   │   │   │   ├── userEdit.tsx
│   │   │   │   │   └── userForm.tsx
│   │   │   │   └── ws/
│   │   │   │       ├── FlowUiEventBridge.tsx
│   │   │   │       ├── IsConnected.tsx
│   │   │   │       ├── WebSocketProvider.tsx
│   │   │   │       ├── flowUiAdapters/
│   │   │   │       │   └── toastFlowUiAdapter.ts
│   │   │   │       ├── flowUiEventRouter.test.ts
│   │   │   │       ├── flowUiEventRouter.ts
│   │   │   │       ├── ws.ts
│   │   │   │       └── wsMock.ts
│   │   │   ├── font.css
│   │   │   ├── hooks/
│   │   │   │   ├── use-mobile.ts
│   │   │   │   ├── useDesktopSidecar.ts
│   │   │   │   └── usePreventBackNavigation.ts
│   │   │   ├── index.css
│   │   │   ├── lib/
│   │   │   │   ├── electron.ts
│   │   │   │   ├── geometry-precision.ts
│   │   │   │   ├── geometry.ts
│   │   │   │   ├── jwt.ts
│   │   │   │   ├── storage.ts
│   │   │   │   ├── string.ts
│   │   │   │   ├── supabase.ts
│   │   │   │   ├── time.ts
│   │   │   │   └── utils.ts
│   │   │   ├── main.tsx
│   │   │   ├── pages/
│   │   │   │   ├── auth/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── code/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── dashboard/
│   │   │   │   │   ├── DashboardMainPanel.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── desktop-settings/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── devices/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── dynamic-dashboard/
│   │   │   │   │   ├── DynamicDashboardMainPanel.tsx
│   │   │   │   │   ├── NewDynamicDashboardPanel.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── flow/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── landing/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── map/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── notfound/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── recordings/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── settings/
│   │   │   │   │   ├── account.tsx
│   │   │   │   │   ├── config.tsx
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   ├── integration.tsx
│   │   │   │   │   ├── log.tsx
│   │   │   │   │   ├── networks.tsx
│   │   │   │   │   ├── services.tsx
│   │   │   │   │   └── users.tsx
│   │   │   │   └── setup/
│   │   │   │       └── index.tsx
│   │   │   ├── shared/
│   │   │   │   ├── api/
│   │   │   │   │   └── index.ts
│   │   │   │   ├── demo.ts
│   │   │   │   ├── desktop.ts
│   │   │   │   └── mock/
│   │   │   │       ├── mockAdapter.ts
│   │   │   │       └── mockData.ts
│   │   │   ├── vite-env.d.ts
│   │   │   └── widgets/
│   │   │       ├── auth/
│   │   │       │   ├── AuthenticatedLayout.tsx
│   │   │       │   └── TopBarWrapper.tsx
│   │   │       ├── device-list/
│   │   │       │   └── DeviceList.tsx
│   │   │       ├── entity-list/
│   │   │       │   └── EntityList.tsx
│   │   │       ├── role-table/
│   │   │       │   └── RoleList.tsx
│   │   │       └── user-table/
│   │   │           └── UserList.tsx
│   │   ├── tailwind.config.js
│   │   ├── tsconfig.app.json
│   │   ├── tsconfig.app.tsbuildinfo
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   ├── tsconfig.node.tsbuildinfo
│   │   └── vite.config.ts
│   ├── desktop/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── package.json
│   │   └── src-tauri/
│   │       ├── Cargo.toml
│   │       ├── build.rs
│   │       ├── capabilities/
│   │       │   └── main.json
│   │       ├── entitlements.plist
│   │       ├── icons/
│   │       │   └── icon.icns
│   │       ├── src/
│   │       │   └── main.rs
│   │       └── tauri.conf.json
│   ├── landing/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── components.json
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── public/
│   │   │   └── glb/
│   │   │       ├── Computers.glb
│   │   │       ├── Drone.glb
│   │   │       ├── Engineer.glb
│   │   │       ├── Robot.glb
│   │   │       ├── SecurityCamera.glb
│   │   │       └── Trailer.glb
│   │   ├── src/
│   │   │   ├── App.tsx
│   │   │   ├── components/
│   │   │   │   ├── Footer.tsx
│   │   │   │   ├── Navbar.tsx
│   │   │   │   ├── UsageCharts.tsx
│   │   │   │   ├── sections/
│   │   │   │   │   ├── CapsulePromo.tsx
│   │   │   │   │   ├── Faqs.tsx
│   │   │   │   │   ├── Features.tsx
│   │   │   │   │   ├── FooterCta.tsx
│   │   │   │   │   ├── HeroScene/
│   │   │   │   │   │   ├── CameraRig.tsx
│   │   │   │   │   │   ├── Computers.tsx
│   │   │   │   │   │   ├── HeroScene.tsx
│   │   │   │   │   │   └── SpinningBox.tsx
│   │   │   │   │   ├── IntegrationImage.tsx
│   │   │   │   │   ├── ListCards.tsx
│   │   │   │   │   ├── MidCta.tsx
│   │   │   │   │   ├── ScrollBox.tsx
│   │   │   │   │   ├── ScrollTextReveal.tsx
│   │   │   │   │   ├── SecurityCta.tsx
│   │   │   │   │   ├── SubheadingSection.tsx
│   │   │   │   │   ├── ThreeCards.tsx
│   │   │   │   │   ├── Usecase.tsx
│   │   │   │   │   ├── UsecaseAI.tsx
│   │   │   │   │   └── capsule/
│   │   │   │   │       ├── CapsuleArchitecture.tsx
│   │   │   │   │       ├── CapsuleFaq.tsx
│   │   │   │   │       ├── CapsuleFeatures.tsx
│   │   │   │   │       ├── CapsuleFooterCta.tsx
│   │   │   │   │       ├── CapsuleHero.tsx
│   │   │   │   │       ├── CapsuleHowItWorks.tsx
│   │   │   │   │       ├── CapsuleSecurity.tsx
│   │   │   │   │       ├── CapsuleSubheading.tsx
│   │   │   │   │       ├── CapsuleUsecases.tsx
│   │   │   │   │       └── EncryptionFlowIllustration.tsx
│   │   │   │   └── ui/
│   │   │   │       ├── alert-dialog.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── chart.tsx
│   │   │   │       ├── navigation-menu.tsx
│   │   │   │       └── sonner.tsx
│   │   │   ├── contexts/
│   │   │   │   └── AuthContext.tsx
│   │   │   ├── hooks/
│   │   │   │   └── useUsageData.ts
│   │   │   ├── index.css
│   │   │   ├── lib/
│   │   │   │   ├── billing.ts
│   │   │   │   ├── supabase.ts
│   │   │   │   ├── theatre.ts
│   │   │   │   ├── useFadeInOnScroll.ts
│   │   │   │   └── utils.ts
│   │   │   ├── main.tsx
│   │   │   ├── pages/
│   │   │   │   ├── Capsule.tsx
│   │   │   │   ├── CheckoutSuccess.tsx
│   │   │   │   ├── Contact.tsx
│   │   │   │   ├── Dashboard.tsx
│   │   │   │   ├── Disclaimer.tsx
│   │   │   │   ├── Login.tsx
│   │   │   │   ├── Main.tsx
│   │   │   │   ├── Pricing.tsx
│   │   │   │   ├── Privacy.tsx
│   │   │   │   ├── PrivacyPolicy.tsx
│   │   │   │   ├── Roadmap.tsx
│   │   │   │   ├── Terms.tsx
│   │   │   │   └── UseCase.tsx
│   │   │   └── vite-env.d.ts
│   │   ├── tsconfig.app.json
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   └── vite.config.ts
│   └── server/
│       ├── Cargo.toml
│       ├── build.rs
│       ├── diesel.toml
│       ├── migrations/
│       │   ├── .keep
│       │   ├── 2025-08-07-152325_users/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2025-08-22-092047_map/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2025-09-08-073323_create_rbac_tables/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2025-09-11-151252_custom_node/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2025-09-19-060609_create_streams/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   ├── 2026-01-12-023507_dyndashboard/
│       │   │   ├── down.sql
│       │   │   └── up.sql
│       │   └── 2026-01-31-000001_create_recordings/
│       │       ├── down.sql
│       │       └── up.sql
│       ├── src/
│       │   ├── broker_mqtt.rs
│       │   ├── config.rs
│       │   ├── db/
│       │   │   ├── conn.rs
│       │   │   ├── mod.rs
│       │   │   ├── models.rs
│       │   │   ├── repository/
│       │   │   │   ├── dashboards.rs
│       │   │   │   ├── mod.rs
│       │   │   │   ├── rbac.rs
│       │   │   │   ├── recordings.rs
│       │   │   │   └── streams.rs
│       │   │   └── schema.rs
│       │   ├── error.rs
│       │   ├── flow/
│       │   │   ├── binary_store.rs
│       │   │   ├── engine.rs
│       │   │   ├── manager_state.rs
│       │   │   ├── mod.rs
│       │   │   ├── nodes/
│       │   │   │   ├── branch.rs
│       │   │   │   ├── calc.rs
│       │   │   │   ├── custom_node.rs
│       │   │   │   ├── dashboard_event_listener.rs
│       │   │   │   ├── decode_h264.rs
│       │   │   │   ├── decode_opus.rs
│       │   │   │   ├── gst_decoder.rs
│       │   │   │   ├── http.rs
│       │   │   │   ├── interval.rs
│       │   │   │   ├── json_modify.rs
│       │   │   │   ├── json_selector.rs
│       │   │   │   ├── log_message.rs
│       │   │   │   ├── logic_operator.rs
│       │   │   │   ├── mod.rs
│       │   │   │   ├── mqtt_publish.rs
│       │   │   │   ├── mqtt_subscribe.rs
│       │   │   │   ├── rtp_stream_in.rs
│       │   │   │   ├── set_variable.rs
│       │   │   │   ├── set_variable_with_exec.rs
│       │   │   │   ├── show_toast.rs
│       │   │   │   ├── start.rs
│       │   │   │   ├── type_converter.rs
│       │   │   │   ├── websocket_on.rs
│       │   │   │   ├── websocket_send.rs
│       │   │   │   └── yolo_detect.rs
│       │   │   └── types.rs
│       │   ├── handler/
│       │   │   ├── auth.rs
│       │   │   ├── configurations.rs
│       │   │   ├── custom_nodes.rs
│       │   │   ├── dashboards.rs
│       │   │   ├── device_tokens.rs
│       │   │   ├── devices.rs
│       │   │   ├── entities.rs
│       │   │   ├── flows.rs
│       │   │   ├── ha.rs
│       │   │   ├── integration.rs
│       │   │   ├── log.rs
│       │   │   ├── map.rs
│       │   │   ├── mod.rs
│       │   │   ├── permissions.rs
│       │   │   ├── recordings.rs
│       │   │   ├── roles.rs
│       │   │   ├── sdr.rs
│       │   │   ├── stat.rs
│       │   │   ├── state.rs
│       │   │   ├── storage.rs
│       │   │   ├── streams.rs
│       │   │   ├── tunnel.rs
│       │   │   ├── users.rs
│       │   │   └── ws/
│       │   │       ├── dashboard_component_event.rs
│       │   │       ├── handlers.rs
│       │   │       ├── mod.rs
│       │   │       └── webrtc.rs
│       │   ├── init/
│       │   │   ├── db_record.rs
│       │   │   ├── mod.rs
│       │   │   └── streams.rs
│       │   ├── lib.rs
│       │   ├── logo.rs
│       │   ├── main.rs
│       │   ├── media/
│       │   │   ├── adapter.rs
│       │   │   ├── mod.rs
│       │   │   ├── rtp_push.rs
│       │   │   └── rtsp_pull.rs
│       │   ├── recording/
│       │   │   ├── manager.rs
│       │   │   ├── mod.rs
│       │   │   ├── muxer.rs
│       │   │   └── service.rs
│       │   ├── routes.rs
│       │   ├── state.rs
│       │   ├── tunnel_control.rs
│       │   └── utils/
│       │       ├── entity_map.rs
│       │       ├── hash.rs
│       │       ├── mod.rs
│       │       ├── stream_checker.rs
│       │       └── system_configs.rs
│       └── tests/
│           ├── common/
│           │   └── mod.rs
│           ├── recording_manager_tests.rs
│           ├── recordings_repository_tests.rs
│           └── state_tests.rs
├── configs/
│   └── projects.json
├── docs/
│   ├── .vitepress/
│   │   └── config.mts
│   ├── concepts.md
│   ├── dashboard.md
│   ├── features.md
│   ├── flow.md
│   ├── index.md
│   ├── installation.md
│   ├── introduction.md
│   ├── map.md
│   ├── setup.md
│   └── troubleshooting.md
├── example/
│   ├── golang/
│   │   ├── .gitignore
│   │   ├── audio-file/
│   │   │   ├── audio.go
│   │   │   └── audio.sdp
│   │   ├── audio-mic/
│   │   │   └── audio.go
│   │   ├── go.mod
│   │   ├── go.sum
│   │   ├── video-sample/
│   │   │   └── video.go
│   │   ├── video2-sample/
│   │   │   └── video.go
│   │   └── video3-sample/
│   │       └── video.go
│   └── js/
│       └── index.js
├── jest.config.js
├── package.json
├── packages/
│   ├── PKG.md
│   ├── capsule-client/
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── client.ts
│   │   │   ├── conversation.ts
│   │   │   ├── crypto.ts
│   │   │   ├── index.ts
│   │   │   └── types.ts
│   │   └── tsconfig.json
│   └── custom-node-utils/
│       ├── add_number.py
│       ├── random.py
│       └── vessel.config.json
├── scripts/
│   └── bundle-macos-deps.sh
└── tests/
    ├── TEST.md
    └── api.test.js
Download .txt
SYMBOL INDEX (1756 symbols across 409 files)

FILE: apps/capsule/src/api/auth.rs
  type AuthUser (line 25) | pub struct AuthUser {
    type Rejection (line 69) | type Rejection = AuthError;
    method from_request_parts (line 71) | async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<S...
  type AuthError (line 38) | pub enum AuthError {
  method into_response (line 50) | fn into_response(self) -> Response {

FILE: apps/capsule/src/api/chat.rs
  function chat_handler (line 40) | pub async fn chat_handler(
  function chat_stream_handler (line 142) | pub async fn chat_stream_handler(

FILE: apps/capsule/src/api/key.rs
  function public_key_handler (line 20) | pub async fn public_key_handler(State(state): State<Arc<AppState>>) -> J...

FILE: apps/capsule/src/api/routes.rs
  constant MAX_BODY_SIZE (line 17) | const MAX_BODY_SIZE: usize = 100 * 1024 * 1024;
  type RouterConfig (line 20) | pub struct RouterConfig {
  function create_router (line 26) | pub fn create_router(
  function build_cors_layer (line 53) | fn build_cors_layer(config: &RouterConfig) -> CorsLayer {
  function health_handler (line 93) | async fn health_handler() -> &'static str {

FILE: apps/capsule/src/config.rs
  type Config (line 6) | pub struct Config {
    method from_env (line 31) | pub fn from_env() -> Result<Self, CapsuleError> {

FILE: apps/capsule/src/crypto/encryption.rs
  constant HKDF_SALT (line 15) | const HKDF_SALT: &[u8] = b"vessel-capsule-v1-salt";
  constant HKDF_INFO (line 16) | const HKDF_INFO: &[u8] = b"vessel-capsule-v1-key";
  function decrypt_with_secret (line 28) | pub(crate) fn decrypt_with_secret(
  function test_decrypt_invalid_public_key (line 89) | fn test_decrypt_invalid_public_key() {
  function test_decrypt_invalid_nonce (line 104) | fn test_decrypt_invalid_nonce() {

FILE: apps/capsule/src/crypto/keypair.rs
  type VersionedKey (line 12) | struct VersionedKey {
    method new (line 24) | fn new() -> Self {
    method public_key_base64 (line 39) | fn public_key_base64(&self) -> String {
  type KeySlots (line 45) | struct KeySlots {
  type KeyManager (line 63) | pub struct KeyManager {
    method new (line 76) | pub fn new(rotation_interval: Duration, grace_period: Duration) -> Self {
    method current_public_key (line 96) | pub async fn current_public_key(&self) -> (String, String, String) {
    method decrypt (line 114) | pub async fn decrypt(&self, encrypted: &EncryptedImage) -> Result<Decr...
    method rotate (line 161) | async fn rotate(&self) {
    method clear_previous (line 177) | async fn clear_previous(&self) {
    method start_rotation_task (line 189) | pub fn start_rotation_task(self: &Arc<Self>) -> tokio::task::JoinHandl...
  method drop (line 213) | fn drop(&mut self) {
  function encrypt_for_key (line 231) | fn encrypt_for_key(public_key: &PublicKey, data: &[u8]) -> (EncryptedIma...
  function test_rotation_creates_new_key (line 264) | async fn test_rotation_creates_new_key() {
  function test_decrypt_with_current_key (line 276) | async fn test_decrypt_with_current_key() {
  function test_previous_key_available_after_rotation (line 292) | async fn test_previous_key_available_after_rotation() {
  function test_previous_key_cleared_after_grace_period (line 314) | async fn test_previous_key_cleared_after_grace_period() {
  function test_no_key_id_fallback (line 337) | async fn test_no_key_id_fallback() {

FILE: apps/capsule/src/error.rs
  type CapsuleError (line 11) | pub enum CapsuleError {
    method from (line 104) | fn from(err: anyhow::Error) -> Self {
  method into_response (line 47) | fn into_response(self) -> Response {

FILE: apps/capsule/src/main.rs
  type AppState (line 23) | pub struct AppState {
  function main (line 35) | async fn main() -> anyhow::Result<()> {

FILE: apps/capsule/src/services/jwt.rs
  type JwtError (line 14) | pub enum JwtError {
  type JwksResponse (line 39) | struct JwksResponse {
  type Jwk (line 45) | struct Jwk {
  type SupabaseClaims (line 66) | pub struct SupabaseClaims {
  type CachedJwks (line 87) | struct CachedJwks {
  type JwtValidator (line 99) | pub struct JwtValidator {
    method new (line 113) | pub fn new(supabase_url: String) -> Self {
    method get_jwks (line 122) | async fn get_jwks(&self) -> Result<Vec<Jwk>, JwtError> {
    method validate (line 184) | pub async fn validate(&self, token: &str) -> Result<SupabaseClaims, Jw...
    method jwk_to_decoding_key (line 223) | fn jwk_to_decoding_key(&self, jwk: &Jwk) -> Result<DecodingKey, JwtErr...
  function test_invalid_token (line 261) | async fn test_invalid_token() {

FILE: apps/capsule/src/services/openai.rs
  constant OPENAI_API_URL (line 12) | const OPENAI_API_URL: &str = "https://api.openai.com/v1/chat/completions";
  type OpenAIService (line 19) | pub struct OpenAIService {
    method new (line 27) | pub fn new(api_key: Zeroizing<String>) -> Self {
    method with_model (line 36) | pub fn with_model(mut self, model: String) -> Self {
    method build_messages (line 42) | fn build_messages(
    method chat (line 84) | pub async fn chat(
    method analyze_image (line 141) | pub async fn analyze_image(
    method chat_stream (line 213) | pub async fn chat_stream(
    method analyze_image_stream (line 262) | pub async fn analyze_image_stream(
    method process_stream_with_usage (line 325) | fn process_stream_with_usage(
  type AccumulatedToolCall (line 425) | struct AccumulatedToolCall {
    method to_json (line 432) | fn to_json(&self) -> serde_json::Value {
  type ChatRequest (line 447) | struct ChatRequest {
  type StreamOptions (line 462) | struct StreamOptions {
  type Message (line 467) | struct Message {
  type MessageContent (line 479) | enum MessageContent {
  type ContentPart (line 486) | enum ContentPart {
  type ImageUrl (line 494) | struct ImageUrl {
  type TokenUsage (line 500) | pub struct TokenUsage {
    method from (line 528) | fn from(usage: Option<Usage>) -> Self {
  type ChatResult (line 508) | pub struct ChatResult {
  type ChatResponse (line 515) | struct ChatResponse {
  type Usage (line 521) | struct Usage {
  type Choice (line 541) | struct Choice {
  type ResponseMessage (line 546) | struct ResponseMessage {
  type StreamChunk (line 552) | struct StreamChunk {
  type StreamChoice (line 558) | struct StreamChoice {
  type Delta (line 565) | struct Delta {
  type StreamToolCall (line 572) | struct StreamToolCall {
  type StreamToolCallFunction (line 580) | struct StreamToolCallFunction {

FILE: apps/capsule/src/services/usage.rs
  type RateLimitStatus (line 14) | pub struct RateLimitStatus {
  type UsageTracker (line 38) | pub struct UsageTracker {
    method new (line 50) | pub fn new(supabase_url: String, service_key: Zeroizing<String>) -> Se...
    method check_rate_limit (line 69) | pub async fn check_rate_limit(&self, user_id: &str) -> Result<RateLimi...
    method track_request (line 124) | pub async fn track_request(

FILE: apps/capsule/src/types/decrypted.rs
  type DecryptedImage (line 23) | pub struct DecryptedImage {
    method new (line 30) | pub(crate) fn new(data: Vec<u8>) -> Self {
    method to_base64 (line 45) | pub fn to_base64(&self) -> String {
    method len (line 50) | pub fn len(&self) -> usize {
    method is_empty (line 55) | pub fn is_empty(&self) -> bool {

FILE: apps/capsule/src/types/encrypted.rs
  constant MAX_HISTORY_MESSAGES (line 6) | const MAX_HISTORY_MESSAGES: usize = 50;
  constant MAX_HISTORY_CHARS (line 9) | const MAX_HISTORY_CHARS: usize = 100_000;
  constant ALLOWED_ROLES (line 12) | const ALLOWED_ROLES: &[&str] = &["user", "assistant", "system", "tool"];
  type EncryptedImage (line 17) | pub struct EncryptedImage {
  type HistoryMessage (line 34) | pub struct HistoryMessage {
  type ChatRequest (line 49) | pub struct ChatRequest {
    method validate_history (line 72) | pub fn validate_history(&self) -> Result<(), CapsuleError> {
  type ChatResponse (line 105) | pub struct ChatResponse {
  type PublicKeyResponse (line 115) | pub struct PublicKeyResponse {
  type StreamChunk (line 126) | pub struct StreamChunk {

FILE: apps/capsule/tests/api.test.mjs
  constant BASE_URL (line 14) | const BASE_URL = process.env.CAPSULE_URL || 'http://localhost:3000';
  function test (line 24) | async function test(name, fn) {
  function assert (line 40) | function assert(condition, message) {
  function assertEqual (line 46) | function assertEqual(actual, expected, message) {
  function api (line 53) | async function api(path, options = {}) {
  function testHealthCheck (line 75) | async function testHealthCheck() {
  function testPublicKey (line 82) | async function testPublicKey() {
  function testTextChat (line 91) | async function testTextChat() {
  function testTextChatEmptyMessage (line 100) | async function testTextChatEmptyMessage() {
  function testImageEncryption (line 109) | async function testImageEncryption(publicKey) {
  function testImageAnalysis (line 154) | async function testImageAnalysis(imagePath, publicKey) {
  function testInvalidEncryptedImage (line 177) | async function testInvalidEncryptedImage() {
  function main (line 200) | async function main() {

FILE: apps/capsule/tests/crypto.mjs
  constant HKDF_SALT (line 13) | const HKDF_SALT = new TextEncoder().encode('vessel-capsule-v1-salt');
  constant HKDF_INFO (line 14) | const HKDF_INFO = new TextEncoder().encode('vessel-capsule-v1-key');
  function bytesToBase64 (line 17) | function bytesToBase64(bytes) {
  function base64ToBytes (line 22) | function base64ToBytes(base64) {
  function encryptImage (line 33) | async function encryptImage(imageData, serverPublicKeyBase64) {

FILE: apps/client/src/App.tsx
  function App (line 209) | function App() {

FILE: apps/client/src/app/pageWrapper/page-wrapper.tsx
  function PageWrapper (line 4) | function PageWrapper(props: PropsWithChildren) {

FILE: apps/client/src/app/providers/theme-provider.tsx
  type Theme (line 3) | type Theme = "dark" | "light" | "system";
  type ThemeProviderProps (line 5) | type ThemeProviderProps = {
  type ThemeProviderState (line 11) | type ThemeProviderState = {
  function ThemeProvider (line 23) | function ThemeProvider({

FILE: apps/client/src/components/icon/Logo.tsx
  function VesselLogo (line 1) | function VesselLogo() {

FILE: apps/client/src/components/ui/alert-dialog.tsx
  function AlertDialog (line 7) | function AlertDialog({
  function AlertDialogTrigger (line 13) | function AlertDialogTrigger({
  function AlertDialogPortal (line 21) | function AlertDialogPortal({
  function AlertDialogOverlay (line 29) | function AlertDialogOverlay({
  function AlertDialogContent (line 45) | function AlertDialogContent({
  function AlertDialogHeader (line 64) | function AlertDialogHeader({
  function AlertDialogFooter (line 77) | function AlertDialogFooter({
  function AlertDialogTitle (line 93) | function AlertDialogTitle({
  function AlertDialogDescription (line 106) | function AlertDialogDescription({
  function AlertDialogAction (line 119) | function AlertDialogAction({
  function AlertDialogCancel (line 131) | function AlertDialogCancel({

FILE: apps/client/src/components/ui/avatar.tsx
  function Avatar (line 6) | function Avatar({
  function AvatarImage (line 22) | function AvatarImage({
  function AvatarFallback (line 35) | function AvatarFallback({

FILE: apps/client/src/components/ui/badge.tsx
  function Badge (line 28) | function Badge({

FILE: apps/client/src/components/ui/breadcrumb.tsx
  function Breadcrumb (line 7) | function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
  function BreadcrumbList (line 11) | function BreadcrumbList({ className, ...props }: React.ComponentProps<"o...
  function BreadcrumbItem (line 24) | function BreadcrumbItem({ className, ...props }: React.ComponentProps<"l...
  function BreadcrumbLink (line 34) | function BreadcrumbLink({
  function BreadcrumbPage (line 52) | function BreadcrumbPage({ className, ...props }: React.ComponentProps<"s...
  function BreadcrumbSeparator (line 65) | function BreadcrumbSeparator({
  function BreadcrumbEllipsis (line 83) | function BreadcrumbEllipsis({

FILE: apps/client/src/components/ui/button.tsx
  function Button (line 38) | function Button({

FILE: apps/client/src/components/ui/card.tsx
  function Card (line 5) | function Card({ className, ...props }: React.ComponentProps<"div">) {
  function CardHeader (line 18) | function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  function CardTitle (line 31) | function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  function CardDescription (line 41) | function CardDescription({ className, ...props }: React.ComponentProps<"...
  function CardAction (line 51) | function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  function CardContent (line 64) | function CardContent({ className, ...props }: React.ComponentProps<"div"...
  function CardFooter (line 74) | function CardFooter({ className, ...props }: React.ComponentProps<"div">) {

FILE: apps/client/src/components/ui/dialog.tsx
  function Dialog (line 7) | function Dialog({
  function DialogTrigger (line 13) | function DialogTrigger({
  function DialogPortal (line 19) | function DialogPortal({
  function DialogClose (line 25) | function DialogClose({
  function DialogOverlay (line 31) | function DialogOverlay({
  function DialogContent (line 47) | function DialogContent({
  function DialogHeader (line 81) | function DialogHeader({ className, ...props }: React.ComponentProps<"div...
  function DialogFooter (line 91) | function DialogFooter({ className, ...props }: React.ComponentProps<"div...
  function DialogTitle (line 104) | function DialogTitle({
  function DialogDescription (line 117) | function DialogDescription({

FILE: apps/client/src/components/ui/dropdown-menu.tsx
  function DropdownMenu (line 7) | function DropdownMenu({
  function DropdownMenuPortal (line 13) | function DropdownMenuPortal({
  function DropdownMenuTrigger (line 21) | function DropdownMenuTrigger({
  function DropdownMenuContent (line 32) | function DropdownMenuContent({
  function DropdownMenuGroup (line 52) | function DropdownMenuGroup({
  function DropdownMenuItem (line 60) | function DropdownMenuItem({
  function DropdownMenuCheckboxItem (line 83) | function DropdownMenuCheckboxItem({
  function DropdownMenuRadioGroup (line 109) | function DropdownMenuRadioGroup({
  function DropdownMenuRadioItem (line 120) | function DropdownMenuRadioItem({
  function DropdownMenuLabel (line 144) | function DropdownMenuLabel({
  function DropdownMenuSeparator (line 164) | function DropdownMenuSeparator({
  function DropdownMenuShortcut (line 177) | function DropdownMenuShortcut({
  function DropdownMenuSub (line 193) | function DropdownMenuSub({
  function DropdownMenuSubTrigger (line 199) | function DropdownMenuSubTrigger({
  function DropdownMenuSubContent (line 223) | function DropdownMenuSubContent({

FILE: apps/client/src/components/ui/input.tsx
  function Input (line 5) | function Input({ className, type, ...props }: React.ComponentProps<"inpu...

FILE: apps/client/src/components/ui/label.tsx
  function Label (line 6) | function Label({

FILE: apps/client/src/components/ui/navigation-menu.tsx
  function NavigationMenu (line 8) | function NavigationMenu({
  function NavigationMenuList (line 32) | function NavigationMenuList({
  function NavigationMenuItem (line 48) | function NavigationMenuItem({
  function NavigationMenuTrigger (line 65) | function NavigationMenuTrigger({
  function NavigationMenuContent (line 85) | function NavigationMenuContent({
  function NavigationMenuViewport (line 102) | function NavigationMenuViewport({
  function NavigationMenuLink (line 124) | function NavigationMenuLink({
  function NavigationMenuIndicator (line 140) | function NavigationMenuIndicator({

FILE: apps/client/src/components/ui/popover.tsx
  type PopoverContentProps (line 12) | type PopoverContentProps = React.ComponentPropsWithoutRef<

FILE: apps/client/src/components/ui/select.tsx
  function Select (line 7) | function Select({
  function SelectGroup (line 13) | function SelectGroup({
  function SelectValue (line 19) | function SelectValue({
  function SelectTrigger (line 25) | function SelectTrigger({
  function SelectContent (line 51) | function SelectContent({
  function SelectLabel (line 86) | function SelectLabel({
  function SelectItem (line 99) | function SelectItem({
  function SelectSeparator (line 123) | function SelectSeparator({
  function SelectScrollUpButton (line 136) | function SelectScrollUpButton({
  function SelectScrollDownButton (line 154) | function SelectScrollDownButton({

FILE: apps/client/src/components/ui/separator.tsx
  function Separator (line 8) | function Separator({

FILE: apps/client/src/components/ui/sheet.tsx
  function Sheet (line 6) | function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive....
  function SheetTrigger (line 10) | function SheetTrigger({
  function SheetClose (line 16) | function SheetClose({
  function SheetPortal (line 22) | function SheetPortal({
  function SheetOverlay (line 28) | function SheetOverlay({
  function SheetContent (line 44) | function SheetContent({
  function SheetHeader (line 80) | function SheetHeader({ className, ...props }: React.ComponentProps<"div"...
  function SheetFooter (line 90) | function SheetFooter({ className, ...props }: React.ComponentProps<"div"...
  function SheetTitle (line 100) | function SheetTitle({
  function SheetDescription (line 113) | function SheetDescription({

FILE: apps/client/src/components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_NAME (line 26) | const SIDEBAR_COOKIE_NAME = "sidebar_state";
  constant SIDEBAR_COOKIE_MAX_AGE (line 27) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
  constant SIDEBAR_WIDTH (line 28) | const SIDEBAR_WIDTH = "16rem";
  constant SIDEBAR_WIDTH_MOBILE (line 29) | const SIDEBAR_WIDTH_MOBILE = "18rem";
  constant SIDEBAR_WIDTH_ICON (line 30) | const SIDEBAR_WIDTH_ICON = "3rem";
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 31) | const SIDEBAR_KEYBOARD_SHORTCUT = "b";
  type SidebarContextProps (line 33) | type SidebarContextProps = {
  function useSidebar (line 45) | function useSidebar() {
  function SidebarProvider (line 54) | function SidebarProvider({
  function Sidebar (line 152) | function Sidebar({
  function SidebarTrigger (line 254) | function SidebarTrigger({
  function SidebarRail (line 280) | function SidebarRail({ className, ...props }: React.ComponentProps<"butt...
  function SidebarInset (line 305) | function SidebarInset({ className, ...props }: React.ComponentProps<"mai...
  function SidebarInput (line 319) | function SidebarInput({
  function SidebarHeader (line 333) | function SidebarHeader({ className, ...props }: React.ComponentProps<"di...
  function SidebarFooter (line 344) | function SidebarFooter({ className, ...props }: React.ComponentProps<"di...
  function SidebarSeparator (line 355) | function SidebarSeparator({
  function SidebarContent (line 369) | function SidebarContent({ className, ...props }: React.ComponentProps<"d...
  function SidebarGroup (line 383) | function SidebarGroup({ className, ...props }: React.ComponentProps<"div...
  function SidebarGroupLabel (line 394) | function SidebarGroupLabel({
  function SidebarGroupAction (line 415) | function SidebarGroupAction({
  function SidebarGroupContent (line 438) | function SidebarGroupContent({
  function SidebarMenu (line 452) | function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
  function SidebarMenuItem (line 466) | function SidebarMenuItem({ className, ...props }: React.ComponentProps<"...
  function SidebarMenuButton (line 499) | function SidebarMenuButton({
  function SidebarMenuAction (line 549) | function SidebarMenuAction({
  function SidebarMenuBadge (line 581) | function SidebarMenuBadge({
  function SidebarMenuSkeleton (line 603) | function SidebarMenuSkeleton({
  function SidebarMenuSub (line 638) | function SidebarMenuSub({ className, ...props }: React.ComponentProps<"u...
  function SidebarMenuSubItem (line 653) | function SidebarMenuSubItem({
  function SidebarMenuSubButton (line 667) | function SidebarMenuSubButton({

FILE: apps/client/src/components/ui/skeleton.tsx
  function Skeleton (line 3) | function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

FILE: apps/client/src/components/ui/switch.tsx
  function Switch (line 6) | function Switch({

FILE: apps/client/src/components/ui/table.tsx
  function Table (line 5) | function Table({ className, ...props }: React.ComponentProps<"table">) {
  function TableHeader (line 20) | function TableHeader({ className, ...props }: React.ComponentProps<"thea...
  function TableBody (line 30) | function TableBody({ className, ...props }: React.ComponentProps<"tbody"...
  function TableFooter (line 40) | function TableFooter({ className, ...props }: React.ComponentProps<"tfoo...
  function TableRow (line 53) | function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
  function TableHead (line 66) | function TableHead({ className, ...props }: React.ComponentProps<"th">) {
  function TableCell (line 79) | function TableCell({ className, ...props }: React.ComponentProps<"td">) {
  function TableCaption (line 92) | function TableCaption({

FILE: apps/client/src/components/ui/textarea.tsx
  function Textarea (line 5) | function Textarea({ className, ...props }: React.ComponentProps<"textare...

FILE: apps/client/src/components/ui/tooltip.tsx
  function TooltipProvider (line 8) | function TooltipProvider({
  function Tooltip (line 21) | function Tooltip({
  function TooltipTrigger (line 31) | function TooltipTrigger({
  function TooltipContent (line 37) | function TooltipContent({

FILE: apps/client/src/contexts/SupabaseAuthContext.tsx
  type SupabaseAuthContextType (line 16) | type SupabaseAuthContextType = {
  function SupabaseAuthProvider (line 26) | function SupabaseAuthProvider({ children }: { children: ReactNode }) {
  function useSupabaseAuth (line 170) | function useSupabaseAuth() {

FILE: apps/client/src/entities/configurations/codeService.ts
  constant CODE_SERVICE_CONFIG_KEY (line 3) | const CODE_SERVICE_CONFIG_KEY = "code_service_enabled";
  function getCodeServiceEnabled (line 5) | function getCodeServiceEnabled(

FILE: apps/client/src/entities/configurations/store.ts
  type ConfigState (line 5) | interface ConfigState {

FILE: apps/client/src/entities/configurations/types.ts
  type SystemConfiguration (line 1) | interface SystemConfiguration {
  type SystemConfigurationPayload (line 11) | type SystemConfigurationPayload = Omit<

FILE: apps/client/src/entities/custom-nodes/presets.ts
  type PresetConnector (line 1) | interface PresetConnector {
  type PresetCategory (line 7) | type PresetCategory =
  type RhaiPreset (line 15) | interface RhaiPreset {
  function presetToApiPayload (line 24) | function presetToApiPayload(preset: RhaiPreset) {
  constant RHAI_PRESETS (line 36) | const RHAI_PRESETS: RhaiPreset[] = [
  function getPresetCategories (line 459) | function getPresetCategories(): PresetCategory[] {
  function getPresetsByCategory (line 467) | function getPresetsByCategory(category: PresetCategory): RhaiPreset[] {

FILE: apps/client/src/entities/custom-nodes/types.ts
  type CustomNodeFromApi (line 1) | interface CustomNodeFromApi {
  type CustomNodeDynamicData (line 6) | type CustomNodeDynamicData = Record<string, unknown>;
  type CustomNodeData (line 8) | interface CustomNodeData {
  type CustomNode (line 17) | type CustomNode = CustomNodeFromApi;
  type Connector (line 19) | interface Connector {
  type CustomNodeState (line 25) | interface CustomNodeState {

FILE: apps/client/src/entities/device-token/store.ts
  type DeviceTokenState (line 5) | interface DeviceTokenState {

FILE: apps/client/src/entities/device-token/types.ts
  type DeviceToken (line 1) | interface DeviceToken {
  type IssuedTokenResponse (line 9) | interface IssuedTokenResponse {

FILE: apps/client/src/entities/device/store.ts
  type DeviceState (line 5) | interface DeviceState {

FILE: apps/client/src/entities/device/types.ts
  type Device (line 3) | interface Device {
  type DeviceWithEntity (line 11) | interface DeviceWithEntity {
  type DevicePayload (line 20) | type DevicePayload = Omit<Device, "id">;

FILE: apps/client/src/entities/dynamic-dashboard/api.ts
  type DynamicDashboardDto (line 3) | type DynamicDashboardDto = {

FILE: apps/client/src/entities/dynamic-dashboard/interaction.ts
  constant DASHBOARD_COMPONENT_EVENT_VERSION (line 2) | const DASHBOARD_COMPONENT_EVENT_VERSION = 2 as const;
  constant MAX_LISTENER_ID_LENGTH (line 4) | const MAX_LISTENER_ID_LENGTH = 128;
  function isValidListenerId (line 7) | function isValidListenerId(id: string): boolean {
  type DashboardComponentType (line 13) | type DashboardComponentType = "button" | string;
  type DashboardComponentAction (line 15) | type DashboardComponentAction = "click" | "change" | "submit" | string;
  type DashboardComponentEventPayload (line 21) | type DashboardComponentEventPayload = {

FILE: apps/client/src/entities/dynamic-dashboard/layoutResolve.ts
  function clampItemPosition (line 4) | function clampItemPosition(
  function itemsCollide (line 15) | function itemsCollide(
  function resolveItemPositionOrNull (line 31) | function resolveItemPositionOrNull(

FILE: apps/client/src/entities/dynamic-dashboard/store.ts
  type DashboardItemType (line 5) | type DashboardItemType =
  type DashboardItemDataMap (line 13) | type DashboardItemDataMap = {
  type DashboardItem (line 36) | type DashboardItem<T extends DashboardItemType = DashboardItemType> = {
  type DashboardGroup (line 47) | type DashboardGroup = {
  type DynamicDashboard (line 55) | type DynamicDashboard = {
  type LayoutPayload (line 61) | type LayoutPayload = {
  type CreateItemPayload (line 66) | type CreateItemPayload = {
  type DynamicDashboardState (line 75) | interface DynamicDashboardState {
  constant DEFAULT_ITEM_SIZES (line 114) | const DEFAULT_ITEM_SIZES: Record<DashboardItemType, { w: number; h: numb...
  constant DEFAULT_ITEM_DATA (line 124) | const DEFAULT_ITEM_DATA = {

FILE: apps/client/src/entities/entity/store.ts
  type EntityState (line 5) | interface EntityState {

FILE: apps/client/src/entities/entity/types.ts
  type Entity (line 1) | interface Entity {
  type DynamicConfig (line 11) | interface DynamicConfig {
  type State (line 15) | interface State {
  type EntityAll (line 25) | interface EntityAll {
  type EntityPayload (line 36) | type EntityPayload = Omit<Entity, "id">;

FILE: apps/client/src/entities/file/store.ts
  type FileTreeState (line 6) | interface FileTreeState {

FILE: apps/client/src/entities/file/types.ts
  type DirEntry (line 1) | interface DirEntry {
  type IdeState (line 7) | interface IdeState {

FILE: apps/client/src/entities/flow/store.ts
  type FlowState (line 11) | interface FlowState {

FILE: apps/client/src/entities/flow/types.ts
  type Flow (line 1) | interface Flow {
  type FlowPayload (line 10) | interface FlowPayload {
  type FlowVersion (line 16) | interface FlowVersion {
  type FlowVersionPayload (line 25) | interface FlowVersionPayload {

FILE: apps/client/src/entities/ha/types.ts
  type JsonPrimitive (line 1) | type JsonPrimitive = string | number | boolean | null;
  type JsonObject (line 2) | type JsonObject = { [key: string]: JsonValue };
  type JsonArray (line 3) | type JsonArray = JsonValue[];
  type JsonValue (line 4) | type JsonValue = JsonPrimitive | JsonObject | JsonArray;
  type HaState (line 6) | interface HaState {
  type HaStateStore (line 15) | interface HaStateStore {

FILE: apps/client/src/entities/integrations/store.ts
  type IntegrationState (line 4) | interface IntegrationState {

FILE: apps/client/src/entities/integrations/types.ts
  type IntegrationStatus (line 1) | interface IntegrationStatus {
  type IntegrationRegisterPayload (line 7) | interface IntegrationRegisterPayload {

FILE: apps/client/src/entities/log/types.ts
  type LogContentResponse (line 1) | type LogContentResponse = {
  type LogFileListResponse (line 6) | type LogFileListResponse = {

FILE: apps/client/src/entities/map/types.ts
  type MapVertex (line 3) | interface MapVertex {
  type MapFeature (line 12) | interface MapFeature {
  type FeatureWithVertices (line 23) | interface FeatureWithVertices extends MapFeature {
  type MapLayer (line 27) | interface MapLayer {
  type LayerWithFeatures (line 37) | interface LayerWithFeatures extends MapLayer {
  type LayerPayload (line 41) | interface LayerPayload {
  type VertexPayload (line 47) | interface VertexPayload {
  type FeaturePayload (line 53) | interface FeaturePayload {
  type UpdateFeaturePayload (line 61) | interface UpdateFeaturePayload {
  type DrawingMode (line 67) | type DrawingMode = "POINT" | "LINE" | "POLYGON" | null;
  type TileMapType (line 70) | type TileMapType = "dark" | "satellite";
  type TileMapConfig (line 72) | interface TileMapConfig {
  constant TILE_MAPS (line 79) | const TILE_MAPS: Record<TileMapType, TileMapConfig> = {
  type MapInteractionState (line 95) | interface MapInteractionState {
  type MapDataState (line 107) | interface MapDataState {

FILE: apps/client/src/entities/permission/store.ts
  type PermissionState (line 5) | type PermissionState = {

FILE: apps/client/src/entities/permission/types.ts
  type Permission (line 1) | type Permission = {

FILE: apps/client/src/entities/recording/store.ts
  type RecordingState (line 5) | interface RecordingState {

FILE: apps/client/src/entities/recording/types.ts
  type Recording (line 1) | interface Recording {
  type RecordingStatus (line 17) | type RecordingStatus = "recording" | "completed" | "failed";
  type StartRecordingRequest (line 19) | interface StartRecordingRequest {
  type StartRecordingResponse (line 23) | interface StartRecordingResponse {
  type ActiveRecordingInfo (line 29) | interface ActiveRecordingInfo {
  type TopicRecordingStatus (line 34) | interface TopicRecordingStatus {

FILE: apps/client/src/entities/role/store.ts
  type RoleState (line 5) | type RoleState = {

FILE: apps/client/src/entities/role/types.ts
  type Role (line 3) | type Role = {
  type CreateRolePayload (line 10) | type CreateRolePayload = {
  type UpdateRolePayload (line 16) | type UpdateRolePayload = Partial<CreateRolePayload>;

FILE: apps/client/src/entities/stat/store.ts
  type StatState (line 5) | interface StatState {

FILE: apps/client/src/entities/stat/types.ts
  type Stat (line 1) | interface Stat {

FILE: apps/client/src/entities/tunnel/store.ts
  type TunnelState (line 5) | type TunnelState = {

FILE: apps/client/src/entities/tunnel/types.ts
  type TunnelStatus (line 1) | type TunnelStatus = {
  type StartTunnelRequest (line 8) | type StartTunnelRequest = {
  type StartTunnelResponse (line 14) | type StartTunnelResponse = {

FILE: apps/client/src/entities/user/store.ts
  type UserState (line 5) | type UserState = {

FILE: apps/client/src/entities/user/types.ts
  type User (line 3) | type User = {
  type CreateUserPayload (line 12) | type CreateUserPayload = {
  type UpdateUserPayload (line 18) | type UpdateUserPayload = {

FILE: apps/client/src/features/account-switcher/index.tsx
  function AccountSwitcher (line 17) | function AccountSwitcher({

FILE: apps/client/src/features/auth/AuthInterceptor.tsx
  function AuthInterceptor (line 7) | function AuthInterceptor({ children }: { children: React.ReactNode }) {

FILE: apps/client/src/features/auth/DefaultAdminPasswordDialog.tsx
  type DefaultAdminPasswordDialogProps (line 21) | type DefaultAdminPasswordDialogProps = {
  function DefaultAdminPasswordDialog (line 27) | function DefaultAdminPasswordDialog({

FILE: apps/client/src/features/auth/api.ts
  type AuthCredentials (line 4) | type AuthCredentials = {

FILE: apps/client/src/features/auth/index.tsx
  constant MAX_RECENT_URLS (line 22) | const MAX_RECENT_URLS = 5;
  function sanitizeServerUrl (line 24) | function sanitizeServerUrl(rawUrl: string): string | null {
  function LoginForm (line 38) | function LoginForm({

FILE: apps/client/src/features/code/CreateItemDialog.tsx
  type CreateItemDialogProps (line 12) | interface CreateItemDialogProps {

FILE: apps/client/src/features/code/FileTree.tsx
  type TreeNodeProps (line 42) | interface TreeNodeProps {
  type ConfirmDeleteDialogProps (line 197) | interface ConfirmDeleteDialogProps {

FILE: apps/client/src/features/configurations/ConfigurationActionButton.tsx
  type Props (line 39) | interface Props {
  function ConfigurationActionButton (line 43) | function ConfigurationActionButton({ config }: Props) {

FILE: apps/client/src/features/configurations/ConfigurationCreate.tsx
  function ConfigurationCreate (line 17) | function ConfigurationCreate({

FILE: apps/client/src/features/configurations/ConfigurationCreateButton.tsx
  function ConfigurationCreateButton (line 6) | function ConfigurationCreateButton() {

FILE: apps/client/src/features/darkmode/mode-toggle.tsx
  function ModeToggle (line 12) | function ModeToggle() {

FILE: apps/client/src/features/dashboard-swipe/DashboardSwipeHeader.tsx
  function DashboardSwipeHeader (line 53) | function DashboardSwipeHeader() {

FILE: apps/client/src/features/dashboard-swipe/DashboardSwipeLayout.tsx
  function DashboardSwipeRoutePlaceholder (line 24) | function DashboardSwipeRoutePlaceholder() {
  function maxPanelIndex (line 28) | function maxPanelIndex(
  function panelIndexFromPath (line 35) | function panelIndexFromPath(
  constant INTEGRATION_VIEW_IDS (line 76) | const INTEGRATION_VIEW_IDS: DashboardMainPanelContentView[] = [
  function DashboardSwipeLayout (line 82) | function DashboardSwipeLayout() {

FILE: apps/client/src/features/device-token/DeviceTokenManager.tsx
  type Props (line 26) | interface Props {
  function DeviceTokenManager (line 30) | function DeviceTokenManager({ deviceId }: Props) {

FILE: apps/client/src/features/device/DeviceCreateButton.tsx
  function DeviceCreateButton (line 18) | function DeviceCreateButton() {

FILE: apps/client/src/features/device/DeviceDeleteButton.tsx
  type Props (line 18) | interface Props {
  function DeviceDeleteButton (line 22) | function DeviceDeleteButton({ deviceId }: Props) {

FILE: apps/client/src/features/device/DeviceKeyButton.tsx
  type Props (line 14) | interface Props {
  function DeviceKeyButton (line 18) | function DeviceKeyButton({ deviceId }: Props) {

FILE: apps/client/src/features/device/DeviceUpdateButton.tsx
  type Props (line 19) | interface Props {
  function DeviceUpdateButton (line 23) | function DeviceUpdateButton({ device }: Props) {

FILE: apps/client/src/features/dynamic-dashboard/GroupCanvas.tsx
  constant MOBILE_ROW_UNIT_PX (line 56) | const MOBILE_ROW_UNIT_PX = 28;
  constant DRAG_DELETE_SHOW_MAX_CLIENT_Y (line 59) | const DRAG_DELETE_SHOW_MAX_CLIENT_Y = 100;
  constant DRAG_DELETE_TARGET_TOP (line 62) | const DRAG_DELETE_TARGET_TOP = "3.5rem";
  constant DRAG_SPRING_STIFFNESS (line 65) | const DRAG_SPRING_STIFFNESS = 400;
  constant DRAG_SPRING_DAMPING (line 66) | const DRAG_SPRING_DAMPING = 40;
  type Vec2 (line 68) | type Vec2 = { x: number; y: number };
  type DragSpringSim (line 70) | type DragSpringSim = {
  function clampGridSpan (line 78) | function clampGridSpan(position: number, span: number, limit: number): n...
  type DragSessionState (line 84) | type DragSessionState = {
  type ResizeEdge (line 92) | type ResizeEdge = "n" | "s" | "e" | "w" | "ne" | "nw" | "se" | "sw";
  type ResizeState (line 94) | type ResizeState = {
  constant FRAME_STRIP_CLASS (line 104) | const FRAME_STRIP_CLASS =
  function resizeCursorForEdge (line 107) | function resizeCursorForEdge(edge: ResizeEdge): string {
  function startResize (line 126) | function startResize(
  type GroupCanvasProps (line 147) | type GroupCanvasProps = {
  function GroupCanvas (line 154) | function GroupCanvas({
  type ButtonConfigEditorProps (line 996) | type ButtonConfigEditorProps = {
  function ButtonConfigEditor (line 1001) | function ButtonConfigEditor({ item, onSave }: ButtonConfigEditorProps) {
  function Placeholder (line 1045) | function Placeholder({ text }: { text: string }) {

FILE: apps/client/src/features/dynamic-dashboard/events/dispatcher.ts
  constant DEFAULT_DASHBOARD_COOLDOWN_MS (line 9) | const DEFAULT_DASHBOARD_COOLDOWN_MS = 320;
  constant MIN_DASHBOARD_COOLDOWN_MS (line 10) | const MIN_DASHBOARD_COOLDOWN_MS = 100;
  type DashboardEventDispatchContext (line 17) | type DashboardEventDispatchContext = {
  type DashboardEventDispatcherDeps (line 30) | type DashboardEventDispatcherDeps = {
  function createDashboardEventDispatcher (line 38) | function createDashboardEventDispatcher(deps: DashboardEventDispatcherDe...
  type DashboardEventDispatcher (line 119) | type DashboardEventDispatcher = ReturnType<

FILE: apps/client/src/features/dynamic-dashboard/panels/ButtonPanel.tsx
  type ButtonPanelProps (line 7) | type ButtonPanelProps = {
  function ButtonPanel (line 13) | function ButtonPanel({

FILE: apps/client/src/features/dynamic-dashboard/panels/FlowPanel.tsx
  type FlowPanelProps (line 20) | type FlowPanelProps = {
  type FlowStatusPayload (line 25) | type FlowStatusPayload = {
  function FlowPanel (line 31) | function FlowPanel({ data, onFlowChange }: FlowPanelProps) {

FILE: apps/client/src/features/dynamic-dashboard/panels/MapPanel.tsx
  type MapPanelProps (line 16) | type MapPanelProps = {
  constant FALLBACK_CENTER (line 21) | const FALLBACK_CENTER: [number, number] = [37.5665, 126.978];
  constant FALLBACK_ZOOM (line 22) | const FALLBACK_ZOOM = 5;
  function MapPanel (line 24) | function MapPanel({ data, onLayerChange }: MapPanelProps) {
  function ResizeInvalidator (line 142) | function ResizeInvalidator() {

FILE: apps/client/src/features/entity/AllEntities.tsx
  function AllEntities (line 9) | function AllEntities() {

FILE: apps/client/src/features/entity/AnalyzeMenuItem.tsx
  type AnalyzeMenuItemProps (line 9) | interface AnalyzeMenuItemProps {
  function AnalyzeMenuItem (line 14) | function AnalyzeMenuItem({ topic, className }: AnalyzeMenuItemProps) {

FILE: apps/client/src/features/entity/Card.tsx
  type StreamState (line 24) | type StreamState = {
  function EntityCard (line 42) | function EntityCard({
  function DefaultEntityCard (line 227) | function DefaultEntityCard({ item }: { item: EntityAll }) {
  function Dot (line 262) | function Dot({ color }: { color: string }) {
  function Offline (line 266) | function Offline() {
  function Online (line 274) | function Online() {

FILE: apps/client/src/features/entity/EntityCreateButton.tsx
  function EntityCreateButton (line 31) | function EntityCreateButton() {

FILE: apps/client/src/features/entity/EntityDeleteButton.tsx
  type Props (line 17) | interface Props {
  function EntityDeleteButton (line 21) | function EntityDeleteButton({ entityId }: Props) {

FILE: apps/client/src/features/entity/EntityUpdateButton.tsx
  type Props (line 30) | interface Props {
  type EntityFormValues (line 35) | type EntityFormValues = Omit<EntityPayload, "configuration"> & {
  function EntityUpdateButton (line 39) | function EntityUpdateButton({ entity, defaultOpen = false }: Props) {

FILE: apps/client/src/features/entity/SelectPlatforms.tsx
  function EntitySelectPlatforms (line 3) | function EntitySelectPlatforms() {

FILE: apps/client/src/features/entity/SelectTypes.tsx
  function EntitySelectTypes (line 4) | function EntitySelectTypes() {

FILE: apps/client/src/features/entity/StateHistorySheet.tsx
  type Props (line 22) | type Props = {
  constant STRIP_WIDTH (line 29) | const STRIP_WIDTH = 360;
  constant STRIP_HEIGHT (line 30) | const STRIP_HEIGHT = 80;
  constant STRIP_PADDING_X (line 31) | const STRIP_PADDING_X = 16;
  constant AXIS_Y (line 32) | const AXIS_Y = STRIP_HEIGHT - 24;
  function StateHistorySheet (line 34) | function StateHistorySheet({
  function SummaryCell (line 239) | function SummaryCell({ label, value }: { label: string; value: string }) {

FILE: apps/client/src/features/entity/useEntitiesData.ts
  type StreamState (line 7) | type StreamState = {
  type ChangeStatePayload (line 12) | type ChangeStatePayload = {
  function useEntitiesData (line 17) | function useEntitiesData() {

FILE: apps/client/src/features/error/index.tsx
  function ErrorRender (line 3) | function ErrorRender() {

FILE: apps/client/src/features/flow-log/FlowLog.tsx
  function FlowLog (line 6) | function FlowLog() {

FILE: apps/client/src/features/flow/AddCustomNode.tsx
  function CustomNodeForm (line 50) | function CustomNodeForm({
  function AddCustomNode (line 123) | function AddCustomNode() {

FILE: apps/client/src/features/flow/Flow.tsx
  function FlowPage (line 41) | function FlowPage() {
  function FlowHeader (line 106) | function FlowHeader() {
  function FlowSidebar (line 173) | function FlowSidebar() {

FILE: apps/client/src/features/flow/Graph.tsx
  type NodeGroup (line 43) | type NodeGroup = {
  function Graph (line 56) | function Graph({

FILE: apps/client/src/features/flow/Options.tsx
  type OptionsProps (line 23) | interface OptionsProps {
  function Options (line 43) | function Options({ open, selectedNode, setOpen }: OptionsProps) {

FILE: apps/client/src/features/flow/RunFlow.tsx
  function RunFlowButton (line 9) | function RunFlowButton() {

FILE: apps/client/src/features/flow/SelectedItemActions.tsx
  type SelectedElement (line 4) | type SelectedElement = {
  type Props (line 9) | type Props = {
  function SelectedItemActions (line 15) | function SelectedItemActions({ selectedElement, onDeleteNode }: Props) {

FILE: apps/client/src/features/flow/flow-chat/buildSystemPrompt.ts
  function buildFlowSystemPrompt (line 5) | function buildFlowSystemPrompt(

FILE: apps/client/src/features/flow/flow-chat/executeToolCalls.ts
  type ToolExecutionResult (line 4) | interface ToolExecutionResult {
  type FlowStoreActions (line 11) | interface FlowStoreActions {
  function validateEdges (line 19) | function validateEdges(
  function executeGenerateFlow (line 68) | function executeGenerateFlow(
  function executeAddNodes (line 84) | function executeAddNodes(
  function executeAddEdges (line 99) | function executeAddEdges(
  function executeRemoveNodes (line 128) | function executeRemoveNodes(
  function executeUpdateNode (line 153) | function executeUpdateNode(
  function executeFlowToolCalls (line 177) | function executeFlowToolCalls(

FILE: apps/client/src/features/flow/flow-chat/flowTools.ts
  constant FLOW_TOOLS (line 3) | const FLOW_TOOLS: Tool[] = [

FILE: apps/client/src/features/flow/flowNode.ts
  type DefaultValueType (line 3) | type DefaultValueType = {
  constant DEFINITION_NODE (line 10) | const DEFINITION_NODE = {

FILE: apps/client/src/features/flow/flowTypes.ts
  type Connector (line 3) | type Connector = {
  type NodeRenderer (line 9) | type NodeRenderer = (
  type NodeTypes (line 14) | type NodeTypes = keyof typeof DEFINITION_NODE;
  type NumberNodeType (line 16) | type NumberNodeType = {
  type TextNodeType (line 20) | type TextNodeType = {
  type AddNodeType (line 24) | type AddNodeType = {
  type SetVariableNodeType (line 29) | type SetVariableNodeType = {
  type ConditionNodeType (line 34) | type ConditionNodeType = {
  type CalculationNodeType (line 39) | type CalculationNodeType = {
  type LogicOpetatorNodeType (line 43) | type LogicOpetatorNodeType = {
  type HTTPRequestNodeType (line 59) | type HTTPRequestNodeType = {
  type LoopNodeType (line 64) | type LoopNodeType = {
  type IntervalNodeType (line 68) | type IntervalNodeType = {
  type MqttPublishNodeType (line 73) | type MqttPublishNodeType = {
  type MqttSubscribeNodeType (line 79) | type MqttSubscribeNodeType = {
  type TypeConverterNodeType (line 83) | type TypeConverterNodeType = {
  type RtpStreamInNodeType (line 87) | type RtpStreamInNodeType = {
  type JsonSelectorNodeType (line 91) | type JsonSelectorNodeType = {
  type GstDecoderNodeType (line 95) | type GstDecoderNodeType = {
  type YoloDetectNodeType (line 99) | type YoloDetectNodeType = {
  type WebSocketOnNodeType (line 107) | type WebSocketOnNodeType = {
  type WebSocketSendNodeType (line 111) | type WebSocketSendNodeType = {
  type Generalize (line 115) | type Generalize<T> = {
  type AllSpecificDataNodeTypes (line 125) | type AllSpecificDataNodeTypes = {
  type SpecificDataNodeType (line 128) | type SpecificDataNodeType =
  type DataNodeType (line 131) | type DataNodeType = Generalize<Exclude<SpecificDataNodeType, undefined>>;
  type DataNodeTypeType (line 133) | type DataNodeTypeType = Record<string, string>;
  type Node (line 135) | type Node = {
  type Edge (line 148) | type Edge = {
  type GraphProps (line 154) | interface GraphProps {

FILE: apps/client/src/features/flow/flowUtils.ts
  function getDefalutValue (line 5) | function getDefalutValue(type: NodeTypes, id: string) {
  function getDefalutNode (line 17) | function getDefalutNode(
  function getCustomValue (line 36) | function getCustomValue(
  function getCustomNode (line 86) | function getCustomNode(
  function getNodeValue (line 110) | function getNodeValue(
  function getNode (line 126) | function getNode(

FILE: apps/client/src/features/flow/nodes/ButtonNode.tsx
  function renderButtonNode (line 3) | function renderButtonNode(

FILE: apps/client/src/features/flow/nodes/CalcNode.tsx
  function renderCalcNode (line 3) | function renderCalcNode(

FILE: apps/client/src/features/flow/nodes/HttpNode.tsx
  function renderHttpNode (line 3) | function renderHttpNode(

FILE: apps/client/src/features/flow/nodes/IntervalNode.tsx
  function renderIntervalNode (line 3) | function renderIntervalNode(

FILE: apps/client/src/features/flow/nodes/LogicNode.tsx
  function renderLogicNode (line 3) | function renderLogicNode(

FILE: apps/client/src/features/flow/nodes/LoopNode.tsx
  function renderLoopNode (line 3) | function renderLoopNode(

FILE: apps/client/src/features/flow/nodes/MQTTNode.tsx
  function mqttLikeCenterLabel (line 3) | function mqttLikeCenterLabel(d: Node): string {
  function renderMQTTNode (line 11) | function renderMQTTNode(

FILE: apps/client/src/features/flow/nodes/NumberNode.tsx
  function renderNumberNode (line 3) | function renderNumberNode(

FILE: apps/client/src/features/flow/nodes/ProcessingNode.tsx
  function renderProcessingNode (line 3) | function renderProcessingNode(

FILE: apps/client/src/features/flow/nodes/TitleNode.tsx
  function renderTitleNode (line 3) | function renderTitleNode(

FILE: apps/client/src/features/flow/nodes/VarNode.tsx
  function renderVarNode (line 3) | function renderVarNode(

FILE: apps/client/src/features/footer/index.tsx
  function Footer (line 5) | function Footer() {

FILE: apps/client/src/features/ha/HaEntitiesTable.tsx
  type HaEntitiesTableProps (line 19) | interface HaEntitiesTableProps {

FILE: apps/client/src/features/ha/HaStatBlock.tsx
  type HaStatBlockProps (line 6) | interface HaStatBlockProps {

FILE: apps/client/src/features/integration/Integration.tsx
  function Intergration (line 243) | function Intergration() {

FILE: apps/client/src/features/integration/constants.ts
  constant INTEGRATION_DEVICE_ID (line 1) | const INTEGRATION_DEVICE_ID: Record<string, string> = {
  constant INTEGRATION_ENTITY_ID (line 7) | const INTEGRATION_ENTITY_ID: Record<string, string> = {

FILE: apps/client/src/features/integration/types.ts
  type IntegrationId (line 1) | type IntegrationId = "home-assistant" | "ros2" | "sdr";
  type StepComponentProps (line 3) | interface StepComponentProps {
  type FinalStepProps (line 8) | interface FinalStepProps {
  type IntegrationWizardModalProps (line 12) | interface IntegrationWizardModalProps {

FILE: apps/client/src/features/json/JsonEditor.tsx
  type JsonCodeEditorProps (line 7) | interface JsonCodeEditorProps extends ReactCodeMirrorProps {

FILE: apps/client/src/features/llm-chat/ChatInput.tsx
  type ChatInputProps (line 5) | interface ChatInputProps {
  function ChatInput (line 13) | function ChatInput({

FILE: apps/client/src/features/llm-chat/ChatMessage.tsx
  type ChatMessageProps (line 5) | interface ChatMessageProps {
  function ChatMessage (line 9) | function ChatMessage({ message }: ChatMessageProps) {

FILE: apps/client/src/features/llm-chat/ChatMessages.tsx
  type ChatMessagesProps (line 6) | interface ChatMessagesProps {
  function ChatMessages (line 11) | function ChatMessages({ messages, isLoading }: ChatMessagesProps) {

FILE: apps/client/src/features/llm-chat/ChatPanel.tsx
  constant PANEL_WIDTH (line 9) | const PANEL_WIDTH = 400;
  constant ELECTRON_TOP_OFFSET (line 10) | const ELECTRON_TOP_OFFSET = 34;
  function ChatPanel (line 12) | function ChatPanel() {

FILE: apps/client/src/features/llm-chat/ChatPanelContainer.tsx
  function ChatPanelContainer (line 7) | function ChatPanelContainer() {

FILE: apps/client/src/features/llm-chat/ChatPanelMobile.tsx
  function ChatPanelMobile (line 13) | function ChatPanelMobile() {

FILE: apps/client/src/features/llm-chat/store.ts
  function generateId (line 13) | function generateId(): string {
  function buildHistory (line 17) | function buildHistory(messages: ChatMessage[]): HistoryMessage[] {
  constant DEFAULT_CAPSULE_URL (line 28) | const DEFAULT_CAPSULE_URL = import.meta.env.VITE_CAPSULE_URL || "http://...
  function handleChatError (line 41) | function handleChatError(

FILE: apps/client/src/features/llm-chat/types.ts
  type ChatMessageImage (line 4) | interface ChatMessageImage {
  type ChatMessage (line 13) | interface ChatMessage {
  type FlowChatContext (line 31) | interface FlowChatContext {
  type ChatPanelState (line 37) | interface ChatPanelState {

FILE: apps/client/src/features/llm-chat/useChatKeyboard.ts
  constant CHAT_KEYBOARD_SHORTCUT (line 4) | const CHAT_KEYBOARD_SHORTCUT = "k";
  function useChatKeyboard (line 6) | function useChatKeyboard() {

FILE: apps/client/src/features/log/index.tsx
  function Logs (line 19) | function Logs() {

FILE: apps/client/src/features/map-draw/FeatureDetailsPanel.tsx
  type FeatureDetailsPanelProps (line 51) | interface FeatureDetailsPanelProps {
  function FeatureDetailsPanel (line 56) | function FeatureDetailsPanel({

FILE: apps/client/src/features/map-draw/FeatureDrawingPreview.tsx
  function DrawingPreview (line 4) | function DrawingPreview() {

FILE: apps/client/src/features/map-draw/FeatureEditor.tsx
  function FeatureEditor (line 14) | function FeatureEditor() {

FILE: apps/client/src/features/map-draw/FeatureRenderer.tsx
  type FeatureRendererProps (line 7) | interface FeatureRendererProps {
  function FeatureRenderer (line 11) | function FeatureRenderer({ feature }: FeatureRendererProps) {

FILE: apps/client/src/features/map-draw/LayerDialog.tsx
  type LayerDialogProps (line 16) | interface LayerDialogProps {
  function LayerDialog (line 23) | function LayerDialog({

FILE: apps/client/src/features/map-draw/LayerSidebar.tsx
  type LayerDialogState (line 27) | type LayerDialogState =
  function LayerSidebar (line 32) | function LayerSidebar() {

FILE: apps/client/src/features/map-draw/MapEvents.tsx
  function MapEvents (line 4) | function MapEvents() {

FILE: apps/client/src/features/map-draw/MapToolbar.tsx
  function MapToolbar (line 5) | function MapToolbar() {

FILE: apps/client/src/features/map-entity/EntityDetailsPanel.tsx
  type EntityDetailsPanelProps (line 21) | interface EntityDetailsPanelProps {
  function EntityDetailsPanel (line 26) | function EntityDetailsPanel({

FILE: apps/client/src/features/map-entity/render.tsx
  constant MARKER_W (line 11) | const MARKER_W = 32;
  constant MARKER_H (line 12) | const MARKER_H = 36;
  constant MARKER_SEL_W (line 13) | const MARKER_SEL_W = 40;
  constant MARKER_SEL_H (line 14) | const MARKER_SEL_H = 44;
  constant PIN_SHADOW (line 16) | const PIN_SHADOW =
  function lucideMapPinMarkup (line 19) | function lucideMapPinMarkup(size: number): string {
  function entityMarkerHtml (line 33) | function entityMarkerHtml(selected: boolean): string {
  function MapEntityRender (line 54) | function MapEntityRender() {

FILE: apps/client/src/features/map-entity/store.ts
  type MapEntityState (line 4) | interface MapEntityState {

FILE: apps/client/src/features/map/CurrentLocationMarker.tsx
  type Props (line 4) | type Props = {
  function CurrentLocationMarker (line 8) | function CurrentLocationMarker({ onLocationUpdate }: Props) {

FILE: apps/client/src/features/map/MapViewPersistence.tsx
  constant MAP_VIEW_STORAGE_KEY (line 4) | const MAP_VIEW_STORAGE_KEY = "map:last-view";
  type StoredMapView (line 6) | type StoredMapView = {
  function getStoredMapView (line 12) | function getStoredMapView(): StoredMapView | null {
  function persistMapView (line 30) | function persistMapView(center: { lat: number; lng: number }, zoom: numb...
  function MapLastViewTracker (line 41) | function MapLastViewTracker() {

FILE: apps/client/src/features/map/index.tsx
  function MapResizer (line 37) | function MapResizer({ isSidebarCollapsed }: { isSidebarCollapsed: boolea...
  function CustomZoomControl (line 53) | function CustomZoomControl() {
  function CustomScaleControl (line 78) | function CustomScaleControl() {
  function MapView (line 128) | function MapView({

FILE: apps/client/src/features/recording/RecordingButton.tsx
  type RecordingButtonProps (line 14) | interface RecordingButtonProps {
  function RecordingButton (line 19) | function RecordingButton({ topic, className }: RecordingButtonProps) {
  function RecordingMenuItem (line 82) | function RecordingMenuItem({ topic, className }: RecordingButtonProps) {

FILE: apps/client/src/features/recording/RecordingsList.tsx
  function RecordingsList (line 36) | function RecordingsList() {
  type RecordingRowProps (line 152) | interface RecordingRowProps {
  function RecordingRow (line 161) | function RecordingRow({
  function formatDuration (line 231) | function formatDuration(ms: number): string {
  function formatFileSize (line 243) | function formatFileSize(bytes: number): string {

FILE: apps/client/src/features/recording/VideoPlaybackDialog.tsx
  type VideoPlaybackDialogProps (line 14) | interface VideoPlaybackDialogProps {
  function VideoPlaybackDialog (line 20) | function VideoPlaybackDialog({
  function formatDuration (line 96) | function formatDuration(ms: number): string {
  function formatFileSize (line 107) | function formatFileSize(bytes: number): string {

FILE: apps/client/src/features/recording/components/AudioWaveformPlayer.tsx
  type AudioWaveformPlayerProps (line 10) | interface AudioWaveformPlayerProps {
  function AudioWaveformPlayer (line 17) | function AudioWaveformPlayer({

FILE: apps/client/src/features/recording/components/FrameTimeline.tsx
  type Frame (line 5) | interface Frame {
  type FrameTimelineProps (line 10) | interface FrameTimelineProps {
  function FrameTimeline (line 19) | function FrameTimeline({
  function formatTime (line 158) | function formatTime(ms: number): string {

FILE: apps/client/src/features/recording/components/PlaybackControls.tsx
  type PlaybackControlsProps (line 5) | interface PlaybackControlsProps {
  function PlaybackControls (line 15) | function PlaybackControls({
  function formatTime (line 53) | function formatTime(ms: number): string {

FILE: apps/client/src/features/recording/components/TimeRuler.tsx
  type TimeRulerProps (line 4) | interface TimeRulerProps {
  function TimeRuler (line 13) | function TimeRuler({
  function formatRulerTime (line 135) | function formatRulerTime(ms: number): string {

FILE: apps/client/src/features/recording/components/VideoControlBar.tsx
  type VideoControlBarProps (line 9) | interface VideoControlBarProps {
  function VideoControlBar (line 17) | function VideoControlBar({

FILE: apps/client/src/features/recording/components/WaveformCanvas.tsx
  type WaveformCanvasProps (line 5) | interface WaveformCanvasProps {
  function WaveformCanvas (line 17) | function WaveformCanvas({

FILE: apps/client/src/features/recording/hooks/useAudioWaveform.ts
  type UseAudioWaveformOptions (line 3) | interface UseAudioWaveformOptions {
  type UseAudioWaveformReturn (line 8) | interface UseAudioWaveformReturn {
  function useAudioWaveform (line 14) | function useAudioWaveform({

FILE: apps/client/src/features/recording/hooks/useMediaPlayback.ts
  type UseMediaPlaybackReturn (line 3) | interface UseMediaPlaybackReturn {
  function useMediaPlayback (line 14) | function useMediaPlayback(

FILE: apps/client/src/features/recording/hooks/useVideoFrames.ts
  type Frame (line 3) | interface Frame {
  type UseVideoFramesOptions (line 8) | interface UseVideoFramesOptions {
  type UseVideoFramesReturn (line 15) | interface UseVideoFramesReturn {
  function useVideoFrames (line 21) | function useVideoFrames({

FILE: apps/client/src/features/role/RoleForm.tsx
  type RoleFormProps (line 29) | interface RoleFormProps {

FILE: apps/client/src/features/ros2/Ros2Dashboard.tsx
  function Ros2Dashboard (line 5) | function Ros2Dashboard() {

FILE: apps/client/src/features/rtc/AudioLevelBar.tsx
  type AudioLevelBarProps (line 4) | type AudioLevelBarProps = {
  function AudioLevelBar (line 9) | function AudioLevelBar({ stream, className }: AudioLevelBarProps) {

FILE: apps/client/src/features/rtc/StreamReceiver.tsx
  type StreamReceiverProps (line 7) | type StreamReceiverProps = {
  function StreamReceiver (line 12) | function StreamReceiver({ topic, streamType }: StreamReceiverProps) {

FILE: apps/client/src/features/rtc/WebRTCProvider.tsx
  type StreamInfo (line 23) | type StreamInfo = {
  type WebRTCContextType (line 28) | type WebRTCContextType = {
  function useWebRTC (line 37) | function useWebRTC() {
  type WebRTCProviderProps (line 45) | type WebRTCProviderProps = {
  function WebRTCProvider (line 49) | function WebRTCProvider({ children }: WebRTCProviderProps) {

FILE: apps/client/src/features/rtc/captureFrame.ts
  function captureFrameFromStream (line 8) | async function captureFrameFromStream(

FILE: apps/client/src/features/rtc/rtc.ts
  type WebRTCConfig (line 3) | interface WebRTCConfig {
  class WebRTCManager (line 7) | class WebRTCManager {
    method constructor (line 20) | constructor(
    method setupEvents (line 38) | private setupEvents(): void {
    method connect (line 138) | public async connect(): Promise<void> {
    method subscribe (line 159) | public subscribe(topic: string, streamType: "audio" | "video"): void {
    method close (line 189) | public close(): void {

FILE: apps/client/src/features/rtc/turnService.ts
  type TurnUsage (line 4) | interface TurnUsage {
  type TurnCredentialsResponse (line 15) | interface TurnCredentialsResponse {
  constant DEFAULT_ICE_SERVERS (line 21) | const DEFAULT_ICE_SERVERS: RTCIceServer[] = [
  constant TURN_CACHE_KEY (line 25) | const TURN_CACHE_KEY = "vessel_turn_credentials";
  constant RENEWAL_BUFFER_MS (line 26) | const RENEWAL_BUFFER_MS = 5 * 60 * 1000;
  type CachedCredentials (line 28) | interface CachedCredentials {
  type CredentialChangeCallback (line 35) | type CredentialChangeCallback = (iceServers: RTCIceServer[]) => void;
  type TurnCredentialErrorCode (line 38) | type TurnCredentialErrorCode =
  class TurnCredentialError (line 43) | class TurnCredentialError extends Error {
    method constructor (line 48) | constructor(
  function isTurnCredentialError (line 61) | function isTurnCredentialError(error: unknown): error is TurnCredentialE...
  type TurnErrorCallback (line 65) | type TurnErrorCallback = (error: TurnCredentialError) => void;
  function onTurnCredentialError (line 68) | function onTurnCredentialError(cb: TurnErrorCallback): () => void {
  function notifyTurnCredentialError (line 75) | function notifyTurnCredentialError(error: TurnCredentialError): void {
  function getDefaultUsage (line 79) | function getDefaultUsage(raw: Partial<TurnUsage> = {}): TurnUsage {
  function getTurnErrorCode (line 102) | function getTurnErrorCode(errorCode?: string): TurnCredentialErrorCode {
  function parseInvokeError (line 113) | async function parseInvokeError(error: unknown): Promise<TurnCredentialE...
  function toTurnCredentialError (line 161) | function toTurnCredentialError(error: unknown): TurnCredentialError {
  function getTurnErrorSummary (line 174) | function getTurnErrorSummary(error: TurnCredentialError): string | null {
  function onCredentialChange (line 188) | function onCredentialChange(cb: CredentialChangeCallback): () => void {
  function notifyListeners (line 195) | function notifyListeners(iceServers: RTCIceServer[]): void {
  function scheduleRenewal (line 203) | function scheduleRenewal(expiresAt: string): void {
  function stopAutoRenewal (line 248) | function stopAutoRenewal(): void {
  function getCachedCredentials (line 257) | function getCachedCredentials(): CachedCredentials | null {
  function cacheCredentials (line 277) | function cacheCredentials(data: TurnCredentialsResponse): void {
  constant TURN_CONFIG_KEY (line 291) | const TURN_CONFIG_KEY = "turn_server_config";
  function loadTurnConfigFromServer (line 293) | async function loadTurnConfigFromServer(): Promise<CachedCredentials | n...
  function saveTurnConfigToServer (line 311) | async function saveTurnConfigToServer(
  function getIceServers (line 336) | function getIceServers(): RTCIceServer[] {
  function getCredentialInfo (line 342) | function getCredentialInfo(): {
  function ensureIceServers (line 367) | async function ensureIceServers(): Promise<RTCIceServer[]> {
  function fetchTurnCredentials (line 405) | async function fetchTurnCredentials(): Promise<TurnCredentialsResponse> {
  function clearTurnCache (line 439) | function clearTurnCache(): void {
  function getTurnCredentialErrorSummary (line 443) | function getTurnCredentialErrorSummary(error: TurnCredentialError): stri...

FILE: apps/client/src/features/sdr/SdrAudioPlayer.tsx
  constant AUDIO_SAMPLE_RATE (line 4) | const AUDIO_SAMPLE_RATE = 48000;
  constant BUFFER_SIZE (line 5) | const BUFFER_SIZE = 4096;
  constant WATERFALL_HEIGHT (line 6) | const WATERFALL_HEIGHT = 200;
  constant FFT_SIZE (line 7) | const FFT_SIZE = 2048;
  function amplitudeToColor (line 10) | function amplitudeToColor(value: number): [number, number, number] {
  type SdrAudioPlayerProps (line 50) | interface SdrAudioPlayerProps {
  function SdrAudioPlayer (line 57) | function SdrAudioPlayer({

FILE: apps/client/src/features/sdr/SdrDashboard.tsx
  function SdrDashboard (line 17) | function SdrDashboard() {

FILE: apps/client/src/features/search/search-form.tsx
  function SearchForm (line 10) | function SearchForm({ ...props }: React.ComponentProps<"form">) {

FILE: apps/client/src/features/server-resource/resourceUsage.tsx
  function ResourceUsage (line 12) | function ResourceUsage() {

FILE: apps/client/src/features/setup/index.tsx
  type SetupStep (line 6) | type SetupStep = {

FILE: apps/client/src/features/sidebar/footer.tsx
  function NavFooter (line 37) | function NavFooter({

FILE: apps/client/src/features/sidebar/index.tsx
  constant CONTROLS_OPEN_KEY (line 46) | const CONTROLS_OPEN_KEY = "controls-menu-open";
  constant SIDEBAR_SCROLL_KEY (line 47) | const SIDEBAR_SCROLL_KEY = "sidebar-scroll-top";
  function AppSidebar (line 119) | function AppSidebar({ ...props }: ComponentProps<typeof Sidebar>) {

FILE: apps/client/src/features/stat/index.tsx
  function StatBlock (line 11) | function StatBlock() {

FILE: apps/client/src/features/topbar/index.tsx
  function TopBar (line 5) | function TopBar({ hide = false }: { hide?: boolean }) {

FILE: apps/client/src/features/user/UserRoleAssigner.tsx
  type UserRoleAssignerProps (line 23) | interface UserRoleAssignerProps {

FILE: apps/client/src/features/user/userDelete.tsx
  type DeleteUserProps (line 16) | interface DeleteUserProps {

FILE: apps/client/src/features/user/userEdit.tsx
  type EditUserProps (line 19) | interface EditUserProps {

FILE: apps/client/src/features/user/userForm.tsx
  type UserFormProps (line 13) | interface UserFormProps {

FILE: apps/client/src/features/ws/FlowUiEventBridge.tsx
  function FlowUiEventBridge (line 12) | function FlowUiEventBridge() {

FILE: apps/client/src/features/ws/IsConnected.tsx
  function Offline (line 4) | function Offline() {
  function Online (line 12) | function Online() {
  function WebSocketStatusIndicator (line 20) | function WebSocketStatusIndicator() {

FILE: apps/client/src/features/ws/WebSocketProvider.tsx
  type WebSocketManager (line 14) | type WebSocketManager = WebSocketChannel | MockWebSocketChannel;
  type WebSocketContextState (line 16) | interface WebSocketContextState {

FILE: apps/client/src/features/ws/flowUiAdapters/toastFlowUiAdapter.ts
  function isRecord (line 4) | function isRecord(value: unknown): value is Record<string, unknown> {
  function asToastData (line 8) | function asToastData(raw: unknown): FlowUiEventToastData | null {
  function toastFlowUiAdapter (line 29) | function toastFlowUiAdapter(payload: FlowUiEventPayload): void {

FILE: apps/client/src/features/ws/flowUiEventRouter.ts
  type FlowUiEventAdapter (line 3) | type FlowUiEventAdapter = (payload: FlowUiEventPayload) => void;
  function createFlowUiEventRouter (line 10) | function createFlowUiEventRouter(
  function mergeFlowUiAdapters (line 24) | function mergeFlowUiAdapters(

FILE: apps/client/src/features/ws/ws.ts
  type FlowUiEventPayload (line 4) | type FlowUiEventPayload = {
  type FlowUiEventToastData (line 16) | type FlowUiEventToastData = {
  type WebSocketMessage (line 23) | type WebSocketMessage = {
  constant FLOW_RUN_SESSION_STORAGE_KEY (line 61) | const FLOW_RUN_SESSION_STORAGE_KEY = "vessel_flow_run_session_id";
  function getFlowRunSessionId (line 64) | function getFlowRunSessionId(): string {
  class WebSocketChannel (line 80) | class WebSocketChannel {
    method isConnected (line 85) | public isConnected(): boolean {
    method connect (line 89) | connect(url: string): void {
    method send (line 105) | send(message: WebSocketMessage): void {
    method close (line 119) | close(): void {
    method addMessageListener (line 125) | addMessageListener(listener: (msg: WebSocketMessage) => void): void {
    method removeMessageListener (line 129) | removeMessageListener(listener: (msg: WebSocketMessage) => void): void {

FILE: apps/client/src/features/ws/wsMock.ts
  class MockWebSocketChannel (line 3) | class MockWebSocketChannel {
    method isConnected (line 10) | isConnected(): boolean {
    method emit (line 14) | private emit(message: WebSocketMessage) {
    method connect (line 18) | connect(): void {
    method send (line 53) | send(message: WebSocketMessage): void {
    method close (line 86) | close(): void {
    method addMessageListener (line 94) | addMessageListener(listener: (msg: WebSocketMessage) => void): void {
    method removeMessageListener (line 98) | removeMessageListener(listener: (msg: WebSocketMessage) => void): void {

FILE: apps/client/src/hooks/use-mobile.ts
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768
  function useIsMobile (line 5) | function useIsMobile() {

FILE: apps/client/src/lib/geometry-precision.ts
  type GeometryProperties (line 3) | interface GeometryProperties {
  constant WGS84 (line 9) | const WGS84 = {
  function vincentyDistance (line 26) | function vincentyDistance(
  function sphericalTriangleArea (line 93) | function sphericalTriangleArea(
  function calculateFeatureGeometry (line 118) | function calculateFeatureGeometry(

FILE: apps/client/src/lib/geometry.ts
  type GeometryProperties (line 3) | interface GeometryProperties {
  constant EARTH_RADIUS_KM (line 9) | const EARTH_RADIUS_KM = 6371;
  function calculateFeatureGeometry (line 40) | function calculateFeatureGeometry(

FILE: apps/client/src/lib/jwt.ts
  type JwtPayload (line 1) | type JwtPayload = {
  function parseJwt (line 7) | function parseJwt(token?: string): JwtPayload | null {

FILE: apps/client/src/lib/storage.ts
  constant TOKEN_KEY (line 1) | const TOKEN_KEY = "vessel_token";
  constant SERVER_URL_KEY (line 2) | const SERVER_URL_KEY = "vessel_server_url";
  constant RECENT_URLS_KEY (line 3) | const RECENT_URLS_KEY = "vessel_recent_server_urls";
  constant CAPSULE_URL_KEY (line 4) | const CAPSULE_URL_KEY = "vessel_capsule_url";

FILE: apps/client/src/lib/string.ts
  function formatConstantCase (line 1) | function formatConstantCase(str: string) {

FILE: apps/client/src/lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {

FILE: apps/client/src/pages/auth/index.tsx
  function AuthPage (line 3) | function AuthPage() {

FILE: apps/client/src/pages/code/index.tsx
  function CodePage (line 28) | function CodePage() {

FILE: apps/client/src/pages/dashboard/DashboardMainPanel.tsx
  type DashboardMainPanelContentView (line 8) | type DashboardMainPanelContentView = "main" | "ha" | "ros2" | "sdr";
  type DashboardMainPanelProps (line 10) | type DashboardMainPanelProps = {
  function DashboardMainPanel (line 14) | function DashboardMainPanel({

FILE: apps/client/src/pages/desktop-settings/index.tsx
  constant HOST_PRESETS (line 20) | const HOST_PRESETS = [
  function DesktopSettingsPage (line 54) | function DesktopSettingsPage(): React.ReactElement {

FILE: apps/client/src/pages/devices/index.tsx
  function DevicePage (line 27) | function DevicePage() {

FILE: apps/client/src/pages/dynamic-dashboard/DynamicDashboardMainPanel.tsx
  type DynamicDashboardMainPanelProps (line 6) | type DynamicDashboardMainPanelProps = {
  function DynamicDashboardMainPanel (line 11) | function DynamicDashboardMainPanel({

FILE: apps/client/src/pages/dynamic-dashboard/NewDynamicDashboardPanel.tsx
  function NewDynamicDashboardPanel (line 6) | function NewDynamicDashboardPanel() {

FILE: apps/client/src/pages/flow/index.tsx
  constant UNSAVED_FLOW_MESSAGE (line 37) | const UNSAVED_FLOW_MESSAGE =
  function FlowPage (line 40) | function FlowPage() {

FILE: apps/client/src/pages/landing/index.tsx
  function LandingPage (line 11) | function LandingPage() {
  function Navbar (line 56) | function Navbar() {

FILE: apps/client/src/pages/map/index.tsx
  function MapLayout (line 22) | function MapLayout() {
  function MapPage (line 54) | function MapPage() {

FILE: apps/client/src/pages/notfound/index.tsx
  function NotFound (line 3) | function NotFound() {

FILE: apps/client/src/pages/recordings/index.tsx
  function RecordingsPage (line 18) | function RecordingsPage() {

FILE: apps/client/src/pages/settings/account.tsx
  function formatTurnQuotaUsage (line 44) | function formatTurnQuotaUsage(usage: TurnUsage | null): string | null {
  function AccountSettingsPage (line 58) | function AccountSettingsPage() {

FILE: apps/client/src/pages/settings/config.tsx
  function ConfigSettingsPage (line 31) | function ConfigSettingsPage() {

FILE: apps/client/src/pages/settings/index.tsx
  type Category (line 28) | type Category = {
  function SettingsPage (line 88) | function SettingsPage() {

FILE: apps/client/src/pages/settings/integration.tsx
  function IntegrationSettingsPage (line 21) | function IntegrationSettingsPage(): React.ReactElement {

FILE: apps/client/src/pages/settings/log.tsx
  function LogSettingsPage (line 19) | function LogSettingsPage() {

FILE: apps/client/src/pages/settings/networks.tsx
  function useOnlineStatus (line 21) | function useOnlineStatus() {
  type NetworkCardProps (line 40) | interface NetworkCardProps {
  function NetworkCard (line 46) | function NetworkCard({ icon, name, status }: NetworkCardProps) {
  function NetworksSettingsPage (line 64) | function NetworksSettingsPage() {

FILE: apps/client/src/pages/settings/services.tsx
  function ServicesSettingsPage (line 39) | function ServicesSettingsPage() {

FILE: apps/client/src/pages/settings/users.tsx
  function UsersSettingsPage (line 21) | function UsersSettingsPage() {

FILE: apps/client/src/pages/setup/index.tsx
  function SetupPage (line 21) | function SetupPage(): React.ReactElement {

FILE: apps/client/src/shared/demo.ts
  constant DEMO_HOSTNAME (line 1) | const DEMO_HOSTNAME = "demo.vsl.cartesiancs.com";
  constant DEMO_SERVER_URL (line 14) | const DEMO_SERVER_URL = "https://demo.vessel.local";
  constant DEMO_TOKEN (line 15) | const DEMO_TOKEN = "demo-token";

FILE: apps/client/src/shared/desktop.ts
  type InvokeFn (line 1) | type InvokeFn = (cmd: string, args?: Record<string, unknown>) => Promise...
  type TauriCore (line 2) | type TauriCore = { invoke?: InvokeFn };
  type TauriNamespace (line 3) | type TauriNamespace = { invoke?: InvokeFn };
  type TauriGlobal (line 4) | type TauriGlobal = {
  type SidecarStatus (line 91) | type SidecarStatus = {
  type ServerAddress (line 98) | type ServerAddress = {

FILE: apps/client/src/shared/mock/mockAdapter.ts
  type DeviceWithEntities (line 121) | type DeviceWithEntities = MockDatabase["devices"][number] & {
  type RequestBody (line 125) | type RequestBody = Record<string, unknown>;
  type VertexInput (line 126) | type VertexInput = {

FILE: apps/client/src/shared/mock/mockData.ts
  type MockDatabase (line 14) | type MockDatabase = {

FILE: apps/client/src/widgets/auth/AuthenticatedLayout.tsx
  function AuthenticatedLayout (line 13) | function AuthenticatedLayout() {

FILE: apps/client/src/widgets/auth/TopBarWrapper.tsx
  type TopBarWrapType (line 8) | interface TopBarWrapType extends PropsWithChildren {
  function TopBarWrapper (line 12) | function TopBarWrapper(props: TopBarWrapType) {

FILE: apps/client/src/widgets/device-list/DeviceList.tsx
  function DeviceList (line 30) | function DeviceList() {

FILE: apps/client/src/widgets/entity-list/EntityList.tsx
  function EntityList (line 31) | function EntityList() {

FILE: apps/desktop/src-tauri/build.rs
  function main (line 5) | fn main() {

FILE: apps/desktop/src-tauri/src/main.rs
  constant DEFAULT_LISTEN (line 23) | const DEFAULT_LISTEN: &str = "0.0.0.0:6174";
  constant CONFIG_FILE (line 24) | const CONFIG_FILE: &str = "config.toml";
  constant SETTINGS_QUERY (line 25) | const SETTINGS_QUERY: &str = "index.html?view=desktop_settings";
  type SidecarManager (line 28) | struct SidecarManager {
  type SidecarStatus (line 39) | struct SidecarStatus {
  type ServerAddress (line 47) | struct ServerAddress {
  function ensure_workdir (line 52) | fn ensure_workdir(
  function read_listen_address (line 71) | fn read_listen_address(workdir: &Path) -> Option<String> {
  function write_listen_address (line 85) | fn write_listen_address(workdir: &Path, listen: &str) -> Result<(), Stri...
  function base_url_from_listen (line 110) | fn base_url_from_listen(listen: &str) -> String {
  function parse_listen (line 130) | fn parse_listen(listen: &str) -> ServerAddress {
  function build_status (line 145) | fn build_status(state: &SidecarManager, workdir: &Path) -> SidecarStatus {
  function write_log (line 160) | fn write_log(path: &Path, msg: &str) {
  function bytes_to_line (line 169) | fn bytes_to_line(bytes: Vec<u8>) -> String {
  function start_server_sidecar (line 173) | fn start_server_sidecar(
  function stop_sidecar_internal (line 291) | fn stop_sidecar_internal(state: &SidecarManager) -> Result<(), String> {
  function update_tray_status (line 298) | fn update_tray_status(app: &AppHandle, status: &SidecarStatus) {
  function update_tray_toggle (line 311) | fn update_tray_toggle(app: &AppHandle) {
  function get_sidecar_status (line 323) | fn get_sidecar_status(
  function start_sidecar (line 334) | fn start_sidecar(
  function stop_sidecar (line 346) | fn stop_sidecar(app: AppHandle, state: State<'_, SidecarManager>) -> Res...
  function get_server_address (line 355) | fn get_server_address(
  function update_server_address (line 365) | fn update_server_address(
  function open_settings_window (line 443) | fn open_settings_window(app: AppHandle) -> Result<(), String> {
  function build_tray (line 466) | fn build_tray(app: &AppHandle) -> Result<(), Box<dyn std::error::Error>> {
  function main (line 558) | fn main() {

FILE: apps/landing/src/App.tsx
  function App (line 72) | function App() {

FILE: apps/landing/src/components/Footer.tsx
  function Footer (line 36) | function Footer() {

FILE: apps/landing/src/components/Navbar.tsx
  function Navbar (line 13) | function Navbar() {

FILE: apps/landing/src/components/UsageCharts.tsx
  function formatBytes (line 26) | function formatBytes(bytes: number): string {
  function formatDate (line 34) | function formatDate(dateStr: string): string {
  function EmptyState (line 62) | function EmptyState() {
  function hasNetworkData (line 70) | function hasNetworkData(data: DailyUsage[]): boolean {
  function hasCapsuleRequestData (line 74) | function hasCapsuleRequestData(data: DailyUsage[]): boolean {
  function UsageCharts (line 79) | function UsageCharts({

FILE: apps/landing/src/components/sections/CapsulePromo.tsx
  function cx (line 4) | function cx(...classes: Array<string | false | null | undefined>) {
  function CapsulePromoSection (line 8) | function CapsulePromoSection() {

FILE: apps/landing/src/components/sections/Faqs.tsx
  type FAQItem (line 5) | type FAQItem = {
  function cx (line 48) | function cx(...classes: Array<string | false | null | undefined>) {
  function FAQSection (line 52) | function FAQSection() {

FILE: apps/landing/src/components/sections/Features.tsx
  function HighlightedText (line 33) | function HighlightedText({
  function FeaturesSection (line 77) | function FeaturesSection() {

FILE: apps/landing/src/components/sections/FooterCta.tsx
  function cx (line 3) | function cx(...classes: Array<string | false | null | undefined>) {
  function FooterCtaSection (line 7) | function FooterCtaSection() {

FILE: apps/landing/src/components/sections/HeroScene/CameraRig.tsx
  function smoothstep (line 5) | function smoothstep(t: number): number {
  constant START_POS (line 9) | const START_POS: [number, number, number] = [-1.5, 1, 5.5];
  constant END_POS (line 10) | const END_POS: [number, number, number] = [-0.4, 0.7, 3.8];
  function CameraRig (line 12) | function CameraRig() {

FILE: apps/landing/src/components/sections/HeroScene/Computers.tsx
  function Instances (line 8) | function Instances({ children, ...props }: any) {
  function Computers (line 26) | function Computers(props: any) {
  function ScreenVideo (line 55) | function ScreenVideo({ frame, panel, ...props }: any) {
  function Leds (line 94) | function Leds({ instances }: any) {

FILE: apps/landing/src/components/sections/HeroScene/HeroScene.tsx
  function HeroSceneSection (line 11) | function HeroSceneSection() {

FILE: apps/landing/src/components/sections/HeroScene/SpinningBox.tsx
  type SpinningBoxProps (line 7) | interface SpinningBoxProps {
  function SpinningBox (line 13) | function SpinningBox({ scale = 1, ...props }: SpinningBoxProps) {

FILE: apps/landing/src/components/sections/IntegrationImage.tsx
  function cx (line 4) | function cx(...classes: Array<string | false | null | undefined>) {
  type Logo (line 16) | type Logo = { src: string; alt: string };
  function LogoMarquee (line 18) | function LogoMarquee({
  function IntegrationSection (line 136) | function IntegrationSection() {

FILE: apps/landing/src/components/sections/ListCards.tsx
  type Slide (line 5) | type Slide = {
  function cx (line 41) | function cx(...classes: Array<string | false | null | undefined>) {
  function ListCardsSection (line 45) | function ListCardsSection() {

FILE: apps/landing/src/components/sections/MidCta.tsx
  function cx (line 3) | function cx(...classes: Array<string | false | null | undefined>) {
  function MapPlaneIllustration (line 7) | function MapPlaneIllustration() {
  function MidCTASection (line 84) | function MidCTASection() {

FILE: apps/landing/src/components/sections/ScrollBox.tsx
  constant EXTRA_SCROLL_PX (line 5) | const EXTRA_SCROLL_PX = 1000;
  function RotatingBox (line 7) | function RotatingBox({ progress }: { progress: number }) {
  function ScrollBoxSection (line 33) | function ScrollBoxSection() {

FILE: apps/landing/src/components/sections/ScrollTextReveal.tsx
  constant QUOTE (line 3) | const QUOTE = {
  function Word (line 9) | function Word({ word, progress }: { word: string; progress: number }) {
  function QuoteSection (line 28) | function QuoteSection() {
  function ScrollTextRevealSection (line 99) | function ScrollTextRevealSection() {

FILE: apps/landing/src/components/sections/SecurityCta.tsx
  function cx (line 3) | function cx(...classes: Array<string | false | null | undefined>) {
  function SecurityCTASection (line 7) | function SecurityCTASection() {

FILE: apps/landing/src/components/sections/SubheadingSection.tsx
  constant TITLE (line 3) | const TITLE = "Build your own infrastructure";
  function SubheadingSection (line 5) | function SubheadingSection() {

FILE: apps/landing/src/components/sections/ThreeCards.tsx
  type Card (line 3) | type Card = {
  function cx (line 33) | function cx(...classes: Array<string | false | null | undefined>) {
  function ThreeCardsSection (line 37) | function ThreeCardsSection() {

FILE: apps/landing/src/components/sections/Usecase.tsx
  type Usecase (line 4) | type Usecase = {
  function cx (line 35) | function cx(...classes: Array<string | false | null | undefined>) {
  function UsecaseSection (line 39) | function UsecaseSection() {

FILE: apps/landing/src/components/sections/UsecaseAI.tsx
  type Usecase (line 4) | type Usecase = {
  function cx (line 42) | function cx(...classes: Array<string | false | null | undefined>) {
  function UsecaseAIAssistantSection (line 46) | function UsecaseAIAssistantSection() {

FILE: apps/landing/src/components/sections/capsule/CapsuleArchitecture.tsx
  function cx (line 5) | function cx(...classes: Array<string | false | null | undefined>) {
  type Card (line 9) | type Card = {
  function CapsuleArchitecture (line 36) | function CapsuleArchitecture() {

FILE: apps/landing/src/components/sections/capsule/CapsuleFaq.tsx
  function cx (line 5) | function cx(...classes: Array<string | false | null | undefined>) {
  type FAQItem (line 9) | type FAQItem = {
  function CapsuleFaq (line 47) | function CapsuleFaq() {

FILE: apps/landing/src/components/sections/capsule/CapsuleFeatures.tsx
  function cx (line 12) | function cx(...classes: Array<string | false | null | undefined>) {
  type Feature (line 16) | type Feature = {
  function CapsuleFeatures (line 61) | function CapsuleFeatures() {

FILE: apps/landing/src/components/sections/capsule/CapsuleFooterCta.tsx
  function cx (line 3) | function cx(...classes: Array<string | false | null | undefined>) {
  function CapsuleFooterCta (line 7) | function CapsuleFooterCta() {

FILE: apps/landing/src/components/sections/capsule/CapsuleHero.tsx
  function CapsuleHero (line 3) | function CapsuleHero() {

FILE: apps/landing/src/components/sections/capsule/CapsuleHowItWorks.tsx
  function cx (line 4) | function cx(...classes: Array<string | false | null | undefined>) {
  function CapsuleHowItWorks (line 29) | function CapsuleHowItWorks() {

FILE: apps/landing/src/components/sections/capsule/CapsuleSecurity.tsx
  function cx (line 4) | function cx(...classes: Array<string | false | null | undefined>) {
  function CapsuleSecurity (line 8) | function CapsuleSecurity() {

FILE: apps/landing/src/components/sections/capsule/CapsuleSubheading.tsx
  constant TITLE (line 3) | const TITLE = "Zero knowledge LLM call";
  function CapsuleSubheading (line 5) | function CapsuleSubheading() {

FILE: apps/landing/src/components/sections/capsule/CapsuleUsecases.tsx
  function cx (line 6) | function cx(...classes: Array<string | false | null | undefined>) {
  type Usecase (line 10) | type Usecase = {
  function CapsuleUsecases (line 37) | function CapsuleUsecases() {

FILE: apps/landing/src/components/sections/capsule/EncryptionFlowIllustration.tsx
  function EncryptionFlowIllustration (line 1) | function EncryptionFlowIllustration() {

FILE: apps/landing/src/components/ui/alert-dialog.tsx
  function AlertDialog (line 7) | function AlertDialog({
  function AlertDialogTrigger (line 13) | function AlertDialogTrigger({
  function AlertDialogPortal (line 21) | function AlertDialogPortal({
  function AlertDialogOverlay (line 29) | function AlertDialogOverlay({
  function AlertDialogContent (line 45) | function AlertDialogContent({
  function AlertDialogHeader (line 64) | function AlertDialogHeader({
  function AlertDialogFooter (line 77) | function AlertDialogFooter({
  function AlertDialogTitle (line 93) | function AlertDialogTitle({
  function AlertDialogDescription (line 106) | function AlertDialogDescription({
  function AlertDialogAction (line 119) | function AlertDialogAction({
  function AlertDialogCancel (line 131) | function AlertDialogCancel({

FILE: apps/landing/src/components/ui/button.tsx
  function Button (line 38) | function Button({

FILE: apps/landing/src/components/ui/card.tsx
  function Card (line 5) | function Card({ className, ...props }: React.ComponentProps<"div">) {
  function CardHeader (line 18) | function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  function CardTitle (line 31) | function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  function CardDescription (line 41) | function CardDescription({ className, ...props }: React.ComponentProps<"...
  function CardAction (line 51) | function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  function CardContent (line 64) | function CardContent({ className, ...props }: React.ComponentProps<"div"...
  function CardFooter (line 74) | function CardFooter({ className, ...props }: React.ComponentProps<"div">) {

FILE: apps/landing/src/components/ui/chart.tsx
  constant THEMES (line 7) | const THEMES = { light: "", dark: ".dark" } as const
  type ChartConfig (line 9) | type ChartConfig = {
  type ChartContextProps (line 19) | type ChartContextProps = {
  function useChart (line 25) | function useChart() {
  function ChartContainer (line 35) | function ChartContainer({
  function ChartTooltipContent (line 109) | function ChartTooltipContent({
  function ChartLegendContent (line 257) | function ChartLegendContent({
  function getPayloadConfigFromPayload (line 314) | function getPayloadConfigFromPayload(

FILE: apps/landing/src/components/ui/navigation-menu.tsx
  function NavigationMenu (line 8) | function NavigationMenu({
  function NavigationMenuList (line 32) | function NavigationMenuList({
  function NavigationMenuItem (line 48) | function NavigationMenuItem({
  function NavigationMenuTrigger (line 65) | function NavigationMenuTrigger({
  function NavigationMenuContent (line 85) | function NavigationMenuContent({
  function NavigationMenuViewport (line 102) | function NavigationMenuViewport({
  function NavigationMenuLink (line 124) | function NavigationMenuLink({
  function NavigationMenuIndicator (line 140) | function NavigationMenuIndicator({

FILE: apps/landing/src/contexts/AuthContext.tsx
  type AuthContextType (line 11) | interface AuthContextType {
  function AuthProvider (line 21) | function AuthProvider({ children }: { children: ReactNode }) {
  function useAuth (line 72) | function useAuth() {

FILE: apps/landing/src/hooks/useUsageData.ts
  type DailyUsage (line 5) | interface DailyUsage {
  function getThirtyDaysAgo (line 15) | function getThirtyDaysAgo(): string {
  function toDateKey (line 21) | function toDateKey(value: string): string {
  function useUsageData (line 25) | function useUsageData(user: User | null) {

FILE: apps/landing/src/lib/billing.ts
  function cancelSubscription (line 1) | async function cancelSubscription(accessToken: string) {

FILE: apps/landing/src/lib/useFadeInOnScroll.ts
  function useFadeInOnScroll (line 6) | function useFadeInOnScroll<T extends HTMLElement>() {

FILE: apps/landing/src/lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {

FILE: apps/landing/src/main.tsx
  function init (line 7) | async function init() {

FILE: apps/landing/src/pages/Capsule.tsx
  function CapsulePage (line 11) | function CapsulePage() {

FILE: apps/landing/src/pages/CheckoutSuccess.tsx
  type SubscriptionStatus (line 17) | type SubscriptionStatus = "loading" | "active" | "pending" | "error";
  constant MAX_POLLS (line 19) | const MAX_POLLS = 10;
  constant POLL_INTERVAL (line 20) | const POLL_INTERVAL = 2000;
  function CheckoutSuccessPage (line 22) | function CheckoutSuccessPage() {

FILE: apps/landing/src/pages/Contact.tsx
  function ContactPage (line 18) | function ContactPage() {

FILE: apps/landing/src/pages/Dashboard.tsx
  function DashboardPage (line 32) | function DashboardPage() {

FILE: apps/landing/src/pages/Disclaimer.tsx
  function DisclaimerPage (line 4) | function DisclaimerPage() {

FILE: apps/landing/src/pages/Login.tsx
  function LoginPage (line 14) | function LoginPage() {

FILE: apps/landing/src/pages/Main.tsx
  function maxHeroInsetForWidth (line 25) | function maxHeroInsetForWidth(width: number): number {
  constant HERO_INSET_SCROLL_RANGE (line 33) | const HERO_INSET_SCROLL_RANGE = 0.72;
  constant MOBILE_BG_OFF_MEDIA (line 36) | const MOBILE_BG_OFF_MEDIA = "(max-width: 767px)";
  function LandingPage (line 38) | function LandingPage() {

FILE: apps/landing/src/pages/Pricing.tsx
  function PricingPage (line 42) | function PricingPage() {

FILE: apps/landing/src/pages/Privacy.tsx
  function cx (line 18) | function cx(...classes: Array<string | false | null | undefined>) {
  function PrivacyPage (line 93) | function PrivacyPage() {

FILE: apps/landing/src/pages/PrivacyPolicy.tsx
  function PrivacyPolicyPage (line 4) | function PrivacyPolicyPage() {

FILE: apps/landing/src/pages/Roadmap.tsx
  function RoadmapPage (line 56) | function RoadmapPage() {

FILE: apps/landing/src/pages/Terms.tsx
  function TermsPage (line 4) | function TermsPage() {

FILE: apps/landing/src/pages/UseCase.tsx
  function UsecasePage (line 35) | function UsecasePage() {

FILE: apps/landing/src/vite-env.d.ts
  type ImportMetaEnv (line 3) | interface ImportMetaEnv {
  type ImportMeta (line 9) | interface ImportMeta {

FILE: apps/server/build.rs
  function main (line 4) | fn main() -> Result<(), Box<dyn std::error::Error>> {

FILE: apps/server/migrations/2025-08-07-152325_users/up.sql
  type users (line 1) | CREATE TABLE users (
  type devices (line 10) | CREATE TABLE devices (
  type device_tokens (line 18) | CREATE TABLE device_tokens (
  type entities (line 28) | CREATE TABLE entities (
  type states_meta (line 38) | CREATE TABLE states_meta (
  type states (line 43) | CREATE TABLE states (
  type events (line 54) | CREATE TABLE events (
  type entities_configurations (line 62) | CREATE TABLE entities_configurations (
  type system_configurations (line 71) | CREATE TABLE system_configurations (
  type flows (line 81) | CREATE TABLE flows (
  type flow_versions (line 90) | CREATE TABLE flow_versions (

FILE: apps/server/migrations/2025-08-22-092047_map/up.sql
  type map_layers (line 2) | CREATE TABLE map_layers (
  type map_features (line 14) | CREATE TABLE map_features (
  type map_vertices (line 28) | CREATE TABLE map_vertices (

FILE: apps/server/migrations/2025-09-08-073323_create_rbac_tables/up.sql
  type roles (line 1) | CREATE TABLE roles (
  type permissions (line 7) | CREATE TABLE permissions (
  type role_permissions (line 13) | CREATE TABLE role_permissions (
  type user_roles (line 21) | CREATE TABLE user_roles (

FILE: apps/server/migrations/2025-09-11-151252_custom_node/up.sql
  type custom_nodes (line 1) | CREATE TABLE custom_nodes (

FILE: apps/server/migrations/2025-09-19-060609_create_streams/up.sql
  type streams (line 2) | CREATE TABLE streams (

FILE: apps/server/migrations/2026-01-12-023507_dyndashboard/up.sql
  type dynamic_dashboards (line 2) | CREATE TABLE dynamic_dashboards (

FILE: apps/server/migrations/2026-01-31-000001_create_recordings/up.sql
  type recordings (line 1) | CREATE TABLE recordings (
  type idx_recordings_topic (line 17) | CREATE INDEX idx_recordings_topic ON recordings(topic)
  type idx_recordings_status (line 18) | CREATE INDEX idx_recordings_status ON recordings(status)
  type idx_recordings_started_at (line 19) | CREATE INDEX idx_recordings_started_at ON recordings(started_at)

FILE: apps/server/src/broker_mqtt.rs
  function start_event_loop (line 13) | pub async fn start_event_loop(

FILE: apps/server/src/config.rs
  constant CONFIG_FILE (line 6) | const CONFIG_FILE: &str = "config.toml";
  type Settings (line 9) | pub struct Settings {
    method new (line 16) | pub fn new() -> Result<Self, ConfigError> {

FILE: apps/server/src/db/conn.rs
  type SqliteConnectionCustomizer (line 8) | struct SqliteConnectionCustomizer;
    method on_acquire (line 11) | fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<(), diesel...
  function establish_connection (line 18) | pub fn establish_connection(database_url: &str) -> DbPool {

FILE: apps/server/src/db/models.rs
  type User (line 14) | pub struct User {
  type UserWithRoles (line 25) | pub struct UserWithRoles {
  type NewUser (line 36) | pub struct NewUser<'a> {
  type UpdateUser (line 44) | pub struct UpdateUser {
  type Device (line 52) | pub struct Device {
  type NewDevice (line 62) | pub struct NewDevice<'a> {
  type Entity (line 72) | pub struct Entity {
  type NewEntity (line 83) | pub struct NewEntity<'a> {
  type EntityConfiguration (line 95) | pub struct EntityConfiguration {
  type NewEntityConfiguration (line 105) | pub struct NewEntityConfiguration<'a> {
  type EntityWithConfig (line 111) | pub struct EntityWithConfig {
  type EntityWithStateAndConfig (line 118) | pub struct EntityWithStateAndConfig {
  type StatesMeta (line 128) | pub struct StatesMeta {
  type NewStatesMeta (line 135) | pub struct NewStatesMeta<'a> {
  type State (line 143) | pub struct State {
  type NewState (line 155) | pub struct NewState<'a> {
  type Event (line 167) | pub struct Event {
  type NewEvent (line 177) | pub struct NewEvent<'a> {
  type SystemConfiguration (line 186) | pub struct SystemConfiguration {
  type NewSystemConfiguration (line 198) | pub struct NewSystemConfiguration<'a> {
  type DynamicDashboard (line 208) | pub struct DynamicDashboard {
  type NewDynamicDashboard (line 218) | pub struct NewDynamicDashboard<'a> {
  type UpdateDynamicDashboard (line 226) | pub struct UpdateDynamicDashboard<'a> {
  type DeviceToken (line 235) | pub struct DeviceToken {
  type NewDeviceToken (line 247) | pub struct NewDeviceToken<'a> {
  type Flow (line 254) | pub struct Flow {
  type NewFlow (line 265) | pub struct NewFlow<'a> {
  type FlowVersion (line 274) | pub struct FlowVersion {
  type NewFlowVersion (line 285) | pub struct NewFlowVersion<'a> {
  type MapLayer (line 296) | pub struct MapLayer {
  type NewMapLayer (line 308) | pub struct NewMapLayer<'a> {
  type UpdateMapLayer (line 317) | pub struct UpdateMapLayer {
  type MapFeature (line 328) | pub struct MapFeature {
  type NewMapFeature (line 341) | pub struct NewMapFeature<'a> {
  type UpdateMapFeature (line 351) | pub struct UpdateMapFeature {
    method has_changes (line 357) | pub fn has_changes(&self) -> bool {
  type MapVertex (line 366) | pub struct MapVertex {
  type NewMapVertex (line 377) | pub struct NewMapVertex {
  type FeatureWithVertices (line 386) | pub struct FeatureWithVertices {
  type LayerWithFeatures (line 393) | pub struct LayerWithFeatures {
  type Role (line 402) | pub struct Role {
  type NewRole (line 410) | pub struct NewRole<'a> {
  type Permission (line 418) | pub struct Permission {
  type NewPermission (line 426) | pub struct NewPermission<'a> {
  type UserRole (line 437) | pub struct UserRole {
  type NewUserRole (line 444) | pub struct NewUserRole {
  type RolePermission (line 455) | pub struct RolePermission {
  type NewRolePermission (line 462) | pub struct NewRolePermission {
  type RoleWithPermissions (line 468) | pub struct RoleWithPermissions {
  type CustomNode (line 479) | pub struct CustomNode {
  type CustomNodeResult (line 485) | pub struct CustomNodeResult {
  type NewCustomNode (line 492) | pub struct NewCustomNode<'a> {
  type UpdateCustomNode (line 499) | pub struct UpdateCustomNode {
  type Stream (line 506) | pub struct Stream {
  type NewStream (line 516) | pub struct NewStream<'a> {
  type Recording (line 525) | pub struct Recording {
  type NewRecording (line 543) | pub struct NewRecording<'a> {
  type UpdateRecording (line 556) | pub struct UpdateRecording {

FILE: apps/server/src/db/repository/dashboards.rs
  function list_dynamic_dashboards (line 11) | pub fn list_dynamic_dashboards(pool: &DbPool) -> Result<Vec<DynamicDashb...
  function get_dynamic_dashboard (line 20) | pub fn get_dynamic_dashboard(
  function create_dynamic_dashboard (line 33) | pub fn create_dynamic_dashboard(
  function update_dynamic_dashboard (line 53) | pub fn update_dynamic_dashboard(
  function delete_dynamic_dashboard (line 76) | pub fn delete_dynamic_dashboard(pool: &DbPool, target_id: &str) -> Resul...

FILE: apps/server/src/db/repository/mod.rs
  function get_user_by_name (line 32) | pub fn get_user_by_name(pool: &DbPool, target_username: &str) -> Result<...
  function get_all_users (line 45) | pub fn get_all_users(pool: &DbPool) -> Result<Vec<UserWithRoles>, anyhow...
  function create_user (line 78) | pub fn create_user(pool: &DbPool, new_user: NewUser) -> Result<User, any...
  function get_user_by_id (line 89) | pub fn get_user_by_id(pool: &DbPool, user_id: i32) -> Result<User, anyho...
  function update_user (line 101) | pub fn update_user(
  function delete_user (line 116) | pub fn delete_user(pool: &DbPool, user_id: i32) -> Result<usize, anyhow:...
  function create_device (line 125) | pub fn create_device(pool: &DbPool, new_device: NewDevice) -> Result<Dev...
  function get_all_devices (line 135) | pub fn get_all_devices(pool: &DbPool) -> Result<Vec<Device>, anyhow::Err...
  function update_device (line 144) | pub fn update_device(
  function delete_device (line 162) | pub fn delete_device(pool: &DbPool, target_id: i32) -> Result<usize, any...
  function create_entity (line 169) | pub fn create_entity(pool: &DbPool, new_entity: NewEntity) -> Result<Ent...
  function get_all_entities (line 178) | pub fn get_all_entities(pool: &DbPool) -> Result<Vec<Entity>, anyhow::Er...
  function update_entity (line 187) | pub fn update_entity(
  function create_entity_with_config (line 205) | pub fn create_entity_with_config(
  function get_all_entities_with_configs (line 237) | pub fn get_all_entities_with_configs(
  function get_all_entities_with_configs_filter (line 265) | pub fn get_all_entities_with_configs_filter(
  function get_entities_by_device_id (line 300) | pub fn get_entities_by_device_id(
  function update_entity_with_config (line 374) | pub fn update_entity_with_config(
  function delete_entity (line 419) | pub fn delete_entity(pool: &DbPool, target_id: i32) -> Result<usize, any...
  function create_system_config (line 426) | pub fn create_system_config(
  function get_all_system_configs (line 441) | pub fn get_all_system_configs(pool: &DbPool) -> Result<Vec<SystemConfigu...
  constant CODE_SERVICE_CONFIG_KEY (line 451) | pub const CODE_SERVICE_CONFIG_KEY: &str = "code_service_enabled";
  function is_code_service_enabled (line 453) | pub fn is_code_service_enabled(pool: &DbPool) -> Result<bool, anyhow::Er...
  function update_system_config (line 464) | pub fn update_system_config(
  function delete_system_config (line 477) | pub fn delete_system_config(pool: &DbPool, target_id: i32) -> Result<usi...
  function create_or_replace_device_token (line 484) | pub fn create_or_replace_device_token(
  function get_token_info_for_device (line 507) | pub fn get_token_info_for_device(
  function delete_token_for_device (line 521) | pub fn delete_token_for_device(
  function get_device_by_device_id (line 532) | pub fn get_device_by_device_id(
  function get_device_by_id (line 545) | pub fn get_device_by_id(pool: &DbPool, target_id: i32) -> Result<Device,...
  function create_flow (line 555) | pub fn create_flow(pool: &DbPool, new_flow: NewFlow) -> Result<Flow, any...
  function get_all_flows (line 567) | pub fn get_all_flows(pool: &DbPool) -> Result<Vec<Flow>, anyhow::Error> {
  function update_flow (line 574) | pub fn update_flow(
  function delete_flow (line 587) | pub fn delete_flow(pool: &DbPool, target_id: i32) -> Result<usize, anyho...
  function create_flow_version (line 594) | pub fn create_flow_version(
  function overwrite_flow_version (line 606) | pub fn overwrite_flow_version(
  function get_latest_version_number (line 623) | pub fn get_latest_version_number(
  function get_versions_for_flow (line 636) | pub fn get_versions_for_flow(
  function get_entity_by_entity_id (line 650) | pub fn get_entity_by_entity_id(
  function set_entity_state (line 666) | pub fn set_entity_state(
  function get_entity_state_history (line 742) | pub fn get_entity_state_history(
  function get_all_entities_with_states_and_configs (line 769) | pub fn get_all_entities_with_states_and_configs(
  function get_all_entities_with_states_and_configs_filter (line 825) | pub fn get_all_entities_with_states_and_configs_filter(
  function get_entity_with_config_by_entity_id (line 889) | pub fn get_entity_with_config_by_entity_id(
  function upsert_device (line 915) | pub fn upsert_device(
  function upsert_entity_with_config (line 955) | pub fn upsert_entity_with_config(
  function delete_device_by_device_id (line 1019) | pub fn delete_device_by_device_id(
  function create_map_layer (line 1030) | pub fn create_map_layer(pool: &DbPool, new_layer: NewMapLayer) -> Result...
  function get_all_map_layers (line 1040) | pub fn get_all_map_layers(pool: &DbPool) -> Result<Vec<MapLayer>, anyhow...
  function get_map_layer_with_features (line 1048) | pub fn get_map_layer_with_features(
  function update_map_layer (line 1087) | pub fn update_map_layer(
  function delete_map_layer (line 1101) | pub fn delete_map_layer(pool: &DbPool, target_layer_id: i32) -> Result<u...
  function create_map_feature (line 1127) | pub fn create_map_feature(
  function get_map_feature_with_vertices (line 1159) | pub fn get_map_feature_with_vertices(
  function update_map_feature (line 1174) | pub fn update_map_feature(
  function delete_map_feature (line 1216) | pub fn delete_map_feature(pool: &DbPool, target_feature_id: i32) -> Resu...
  function create_custom_node (line 1230) | pub fn create_custom_node(
  function get_all_custom_nodes (line 1242) | pub fn get_all_custom_nodes(pool: &DbPool) -> Result<Vec<CustomNodeResul...
  function get_custom_node (line 1265) | pub fn get_custom_node(pool: &DbPool, target_node_type: &str) -> Result<...
  function update_custom_node (line 1274) | pub fn update_custom_node(
  function delete_custom_node (line 1301) | pub fn delete_custom_node(pool: &DbPool, target_node_type: &str) -> Resu...

FILE: apps/server/src/db/repository/rbac.rs
  function create_role_with_permissions (line 12) | pub fn create_role_with_permissions(
  function update_role_with_permissions (line 47) | pub fn update_role_with_permissions(
  function get_all_roles (line 87) | pub fn get_all_roles(pool: &DbPool) -> Result<Vec<Role>, anyhow::Error> {
  function delete_role (line 94) | pub fn delete_role(pool: &DbPool, role_id: i32) -> Result<usize, anyhow:...
  function create_permission (line 101) | pub fn create_permission(
  function get_all_permissions (line 113) | pub fn get_all_permissions(pool: &DbPool) -> Result<Vec<Permission>, any...
  function assign_role_to_user (line 122) | pub fn assign_role_to_user(
  function remove_role_from_user (line 139) | pub fn remove_role_from_user(
  function get_user_roles (line 153) | pub fn get_user_roles(pool: &DbPool, target_user_id: i32) -> Result<Vec<...
  function grant_permission_to_role (line 164) | pub fn grant_permission_to_role(
  function revoke_permission_from_role (line 182) | pub fn revoke_permission_from_role(
  function get_role_permissions (line 200) | pub fn get_role_permissions(
  function get_all_roles_with_permissions (line 214) | pub fn get_all_roles_with_permissions(

FILE: apps/server/src/db/repository/recordings.rs
  function create_recording (line 5) | pub fn create_recording(
  function get_all_recordings (line 19) | pub fn get_all_recordings(pool: &DbPool) -> Result<Vec<Recording>, anyho...
  function get_recordings_by_status (line 31) | pub fn get_recordings_by_status(
  function get_recordings_by_topic (line 47) | pub fn get_recordings_by_topic(
  function get_active_recording_by_topic (line 63) | pub fn get_active_recording_by_topic(
  function get_recording_by_id (line 79) | pub fn get_recording_by_id(pool: &DbPool, recording_id: i32) -> Result<R...
  function update_recording (line 91) | pub fn update_recording(
  function delete_recording (line 106) | pub fn delete_recording(pool: &DbPool, recording_id: i32) -> Result<usiz...
  function mark_orphaned_as_abandoned (line 117) | pub fn mark_orphaned_as_abandoned(pool: &DbPool) -> Result<usize, anyhow...

FILE: apps/server/src/db/repository/streams.rs
  function get_all_streams (line 15) | pub fn get_all_streams(pool: &DbPool) -> Result<Vec<Stream>, anyhow::Err...
  function upsert_stream (line 23) | pub fn upsert_stream(pool: &DbPool, new_stream: &NewStream) -> Result<St...
  function delete_stream (line 38) | pub fn delete_stream(pool: &DbPool, target_ssrc: i32) -> Result<usize, a...

FILE: apps/server/src/error.rs
  type AppError (line 6) | pub struct AppError(anyhow::Error);
    method from (line 24) | fn from(err: E) -> Self {
  method into_response (line 9) | fn into_response(self) -> Response {

FILE: apps/server/src/flow/binary_store.rs
  type BinaryStore (line 7) | pub struct BinaryStore {
    method new (line 12) | pub fn new() -> Self {
    method insert (line 28) | pub fn insert(&self, data: Vec<u8>) -> Uuid {
    method remove (line 34) | pub fn remove(&self, id: &Uuid) -> Option<Vec<u8>> {

FILE: apps/server/src/flow/engine.rs
  type ExecutionContext (line 40) | pub struct ExecutionContext {
    method new (line 49) | pub fn new(
    method set_variable (line 64) | pub fn set_variable(&mut self, name: &str, value: Value) {
    method get_variable (line 68) | pub fn get_variable(&self, name: &str) -> Option<&Value> {
    method get_broadcast (line 72) | pub fn get_broadcast(&self) -> broadcast::Sender<String> {
    method emit_flow_ui_event (line 77) | pub fn emit_flow_ui_event(
    method mqtt_client (line 106) | pub fn mqtt_client(&self) -> &Option<AsyncClient> {
  type FlowController (line 111) | pub struct FlowController {
    method stop (line 116) | pub fn stop(&self) {
  type TriggerCommand (line 122) | pub struct TriggerCommand {
  type FlowEngine (line 127) | pub struct FlowEngine {
    method new (line 139) | pub fn new(
    method get_node_by_id (line 203) | pub fn get_node_by_id(&self, node_id: &str) -> Option<&Node> {
    method get_start_nodes (line 207) | pub fn get_start_nodes(&self) -> Vec<String> {
    method get_source_node_ids (line 215) | pub fn get_source_node_ids(&self) -> Vec<String> {
    method get_node_instance (line 223) | pub fn get_node_instance(
    method start (line 307) | pub async fn start(

FILE: apps/server/src/flow/manager_state.rs
  type FlowManagerCommand (line 20) | pub enum FlowManagerCommand {
  type ActiveFlow (line 36) | struct ActiveFlow {
  type FlowManagerActor (line 42) | pub struct FlowManagerActor {
    method new (line 55) | pub fn new(
    method run (line 77) | pub async fn run(&mut self) {
  function enrich_configs_from_entities (line 173) | fn enrich_configs_from_entities(pool: &DbPool, configs: &mut Vec<SystemC...

FILE: apps/server/src/flow/nodes/branch.rs
  type BranchNode (line 10) | pub struct BranchNode;
    method new (line 13) | pub fn new() -> Result<Self> {
  method execute (line 20) | async fn execute(

FILE: apps/server/src/flow/nodes/calc.rs
  type CalcNodeData (line 13) | struct CalcNodeData {
  type CalcNode (line 17) | pub struct CalcNode {
    method new (line 22) | pub fn new(node_data: &Value) -> Result<Self> {
  method execute (line 30) | async fn execute(

FILE: apps/server/src/flow/nodes/custom_node.rs
  type CustomNode (line 16) | pub struct CustomNode {
    method new (line 21) | pub fn new(node: &Node) -> Result<Self> {
  method execute (line 29) | async fn execute(

FILE: apps/server/src/flow/nodes/dashboard_event_listener.rs
  type DashboardEventListenerNodeData (line 18) | pub struct DashboardEventListenerNodeData {
  type DashboardEventListenerNode (line 22) | pub struct DashboardEventListenerNode {
    method new (line 28) | pub fn new(
  method execute (line 39) | async fn execute(
  method is_trigger (line 54) | fn is_trigger(&self) -> bool {
  method start_trigger (line 58) | fn start_trigger(

FILE: apps/server/src/flow/nodes/decode_h264.rs
  type DecodeH264Node (line 15) | pub struct DecodeH264Node {
    method new (line 22) | pub fn new() -> Result<Self> {
  method execute (line 110) | async fn execute(
  method drop (line 147) | fn drop(&mut self) {

FILE: apps/server/src/flow/nodes/decode_opus.rs
  type DecodeOpusNode (line 10) | pub struct DecodeOpusNode;
    method new (line 13) | pub fn new() -> Result<Self> {
  method execute (line 20) | async fn execute(

FILE: apps/server/src/flow/nodes/gst_decoder.rs
  type GstDecoderData (line 22) | pub struct GstDecoderData {
  type GstDecoderNode (line 26) | pub struct GstDecoderNode {
    method new (line 33) | pub fn new(
  method execute (line 49) | async fn execute(
  method is_trigger (line 60) | fn is_trigger(&self) -> bool {
  method start_trigger (line 64) | fn start_trigger(

FILE: apps/server/src/flow/nodes/http.rs
  type HttpNodeData (line 14) | struct HttpNodeData {
  type HttpNode (line 19) | pub struct HttpNode {
    method new (line 24) | pub fn new(node_data: &Value) -> Result<Self> {
  method execute (line 32) | async fn execute(

FILE: apps/server/src/flow/nodes/interval.rs
  type IntervalNodeData (line 12) | struct IntervalNodeData {
  type IntervalNode (line 17) | pub struct IntervalNode {
    method new (line 22) | pub fn new(node_data: &Value) -> Result<Self> {
    method get_duration (line 27) | pub fn get_duration(&self) -> Result<Duration> {
  method execute (line 39) | async fn execute(
  method is_trigger (line 52) | fn is_trigger(&self) -> bool {
  method start_trigger (line 56) | fn start_trigger(

FILE: apps/server/src/flow/nodes/json_modify.rs
  type JsonModifyData (line 11) | struct JsonModifyData {
  type JsonModifyNode (line 15) | pub struct JsonModifyNode {
    method new (line 20) | pub fn new(node_data: &Value) -> Result<Self> {
  method execute (line 28) | async fn execute(

FILE: apps/server/src/flow/nodes/json_selector.rs
  type JsonSelectorData (line 11) | struct JsonSelectorData {
  type JsonSelectorNode (line 15) | pub struct JsonSelectorNode {
    method new (line 20) | pub fn new(node_data: &Value) -> Result<Self> {
  method execute (line 28) | async fn execute(

FILE: apps/server/src/flow/nodes/log_message.rs
  type LogMessageNode (line 11) | pub struct LogMessageNode;
  method execute (line 15) | async fn execute(

FILE: apps/server/src/flow/nodes/logic_operator.rs
  type LogicOpetatorNodeData (line 13) | struct LogicOpetatorNodeData {
  type LogicOpetatorNode (line 17) | pub struct LogicOpetatorNode {
    method new (line 22) | pub fn new(node_data: &Value) -> Result<Self> {
    method get_input_as_bool (line 27) | async fn get_input_as_bool(
    method get_input_as_f64 (line 53) | async fn get_input_as_f64(
  method execute (line 75) | async fn execute(

FILE: apps/server/src/flow/nodes/mod.rs
  type ExecutableNode (line 16) | pub trait ExecutableNode: Send + Sync {
    method execute (line 17) | async fn execute(
    method is_trigger (line 23) | fn is_trigger(&self) -> bool {
    method start_trigger (line 27) | fn start_trigger(

FILE: apps/server/src/flow/nodes/mqtt_publish.rs
  type MqttPublishNodeData (line 15) | struct MqttPublishNodeData {
  type MqttPublishNode (line 21) | pub struct MqttPublishNode {
    method new (line 26) | pub fn new(node_data: &Value) -> Result<Self> {
  method execute (line 34) | async fn execute(

FILE: apps/server/src/flow/nodes/mqtt_subscribe.rs
  type MqttSubscribeNodeData (line 18) | pub struct MqttSubscribeNodeData {
  type MqttSubscribeNode (line 22) | pub struct MqttSubscribeNode {
    method new (line 29) | pub fn new(
  method execute (line 45) | async fn execute(
  method is_trigger (line 60) | fn is_trigger(&self) -> bool {
  method start_trigger (line 64) | fn start_trigger(

FILE: apps/server/src/flow/nodes/rtp_stream_in.rs
  type RtpStreamInData (line 17) | pub struct RtpStreamInData {
  type RtpStreamInNode (line 21) | pub struct RtpStreamInNode {
    method new (line 28) | pub fn new(
  method execute (line 44) | async fn execute(
  method is_trigger (line 62) | fn is_trigger(&self) -> bool {
  method start_trigger (line 66) | fn start_trigger(

FILE: apps/server/src/flow/nodes/set_variable.rs
  type SetVariableNodeData (line 13) | struct SetVariableNodeData {
  type SetVariableNode (line 18) | pub struct SetVariableNode {
    method new (line 23) | pub fn new(node_data: &Value) -> Result<Self> {
  method execute (line 31) | async fn execute(

FILE: apps/server/src/flow/nodes/set_variable_with_exec.rs
  type SetVariableNodeData (line 13) | struct SetVariableNodeData {
  type SetVariableWithExecNode (line 18) | pub struct SetVariableWithExecNode {
    method new (line 23) | pub fn new(node_data: &Value) -> Result<Self> {
  method execute (line 31) | async fn execute(

FILE: apps/server/src/flow/nodes/show_toast.rs
  type ShowToastData (line 12) | struct ShowToastData {
  function default_level (line 21) | fn default_level() -> String {
  function default_duration_ms (line 25) | fn default_duration_ms() -> u64 {
  type ShowToastNode (line 29) | pub struct ShowToastNode {
    method new (line 35) | pub fn new(node_data: &Value, node_id: String) -> Result<Self> {
  method execute (line 43) | async fn execute(

FILE: apps/server/src/flow/nodes/start.rs
  type StartNode (line 8) | pub struct StartNode;
  method execute (line 12) | async fn execute(

FILE: apps/server/src/flow/nodes/type_converter.rs
  type TypeConverterNodeData (line 12) | struct TypeConverterNodeData {
  type TypeConverterNode (line 16) | pub struct TypeConverterNode {
    method new (line 21) | pub fn new(node_data: &Value) -> Result<Self> {
  method execute (line 29) | async fn execute(

FILE: apps/server/src/flow/nodes/websocket_on.rs
  type WebSocketOnNodeData (line 31) | pub struct WebSocketOnNodeData {
  type WebSocketOnNode (line 35) | pub struct WebSocketOnNode {
    method new (line 41) | pub fn new(node_data: &Value, system_configs: Vec<SystemConfiguration>...
  function handle_connection (line 50) | async fn handle_connection(
  method execute (line 97) | async fn execute(
  method is_trigger (line 112) | fn is_trigger(&self) -> bool {
  method start_trigger (line 116) | fn start_trigger(

FILE: apps/server/src/flow/nodes/websocket_send.rs
  type WebSocketSendNodeData (line 19) | struct WebSocketSendNodeData {
  type WebSocketSendNode (line 23) | pub struct WebSocketSendNode {
    method new (line 29) | pub fn new(node_data: &Value, system_configs: Vec<SystemConfiguration>...
  method execute (line 40) | async fn execute(

FILE: apps/server/src/flow/nodes/yolo_detect.rs
  type Model (line 16) | type Model = TypedRunnableModel<Graph<TypedFact, Box<dyn TypedOp>>>;
  type YoloDetectData (line 19) | struct YoloDetectData {
  type BBox (line 28) | struct BBox {
  type YoloDetectNode (line 37) | pub struct YoloDetectNode {
    method new (line 75) | pub fn new(node_data: &Value, binary_store: BinaryStore) -> Result<Sel...
  function nms (line 44) | fn nms(boxes: &mut Vec<BBox>, threshold: f32) {
  method execute (line 104) | async fn execute(

FILE: apps/server/src/flow/types.rs
  type FlowRunContext (line 7) | pub struct FlowRunContext {
  type Trigger (line 12) | pub struct Trigger {
  type ExecutionResult (line 18) | pub struct ExecutionResult {
  type Graph (line 25) | pub struct Graph {
  type Node (line 32) | pub struct Node {
  type Edge (line 42) | pub struct Edge {
  type ConnectorKind (line 48) | pub enum ConnectorKind {
  type Connector (line 55) | pub struct Connector {

FILE: apps/server/src/handler/auth.rs
  type ErrorResponse (line 31) | pub struct ErrorResponse {
  type AuthPayload (line 37) | pub struct AuthPayload {
  type AuthError (line 43) | pub enum AuthError {
  method into_response (line 52) | fn into_response(self) -> Response {
  type Claims (line 69) | pub struct Claims {
  type JwtAuth (line 74) | pub struct JwtAuth(pub Claims);
    type Rejection (line 82) | type Rejection = AuthError;
    method from_request_parts (line 84) | async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Se...
  type AuthUser (line 105) | pub struct AuthUser(pub User);
    type Rejection (line 113) | type Rejection = AuthError;
    method from_request_parts (line 115) | async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Se...
  function get_token_from_header (line 127) | async fn get_token_from_header(parts: &mut Parts) -> Option<String> {
  function get_token_from_query (line 135) | async fn get_token_from_query(parts: &mut Parts) -> Option<String> {
  function auth_with_password (line 148) | pub async fn auth_with_password(
  type DeviceTokenAuth (line 188) | pub struct DeviceTokenAuth {
    type Rejection (line 194) | type Rejection = AppError;
    method from_request_parts (line 196) | async fn from_request_parts(

FILE: apps/server/src/handler/configurations.rs
  type SystemConfigPayload (line 19) | pub struct SystemConfigPayload {
  function create_config (line 26) | pub async fn create_config(
  function get_configs (line 41) | pub async fn get_configs(
  function update_config (line 49) | pub async fn update_config(
  function delete_config (line 65) | pub async fn delete_config(

FILE: apps/server/src/handler/custom_nodes.rs
  type CreateCustomNodePayload (line 19) | pub struct CreateCustomNodePayload {
  function create_custom_node_handler (line 24) | pub async fn create_custom_node_handler(
  function get_all_custom_nodes_handler (line 48) | pub async fn get_all_custom_nodes_handler(
  function get_custom_node_handler (line 55) | pub async fn get_custom_node_handler(
  type UpdateCustomNodePayload (line 64) | pub struct UpdateCustomNodePayload {
  function update_custom_node_handler (line 68) | pub async fn update_custom_node_handler(
  function delete_custom_node_handler (line 92) | pub async fn delete_custom_node_handler(

FILE: apps/server/src/handler/dashboards.rs
  type DynamicDashboardResponse (line 15) | pub struct DynamicDashboardResponse {
    method from (line 24) | fn from(model: db::models::DynamicDashboard) -> Self {
  type CreateDashboardPayload (line 39) | pub struct CreateDashboardPayload {
  type UpdateDashboardPayload (line 45) | pub struct UpdateDashboardPayload {
  function list_dashboards (line 50) | pub async fn list_dashboards(
  function create_dashboard (line 61) | pub async fn create_dashboard(
  function get_dashboard (line 74) | pub async fn get_dashboard(
  function update_dashboard (line 86) | pub async fn update_dashboard(
  function delete_dashboard (line 104) | pub async fn delete_dashboard(

FILE: apps/server/src/handler/device_tokens.rs
  function generate_api_key (line 11) | fn generate_api_key() -> String {
  function issue_token (line 17) | pub async fn issue_token(
  function get_token_info (line 34) | pub async fn get_token_info(
  function revoke_token (line 49) | pub async fn revoke_token(

FILE: apps/server/src/handler/devices.rs
  type DevicePayload (line 19) | pub struct DevicePayload {
  type DeviceWithEntities (line 27) | pub struct DeviceWithEntities {
  function create_device (line 33) | pub async fn create_device(
  function get_devices (line 48) | pub async fn get_devices(
  function get_device (line 56) | pub async fn get_device(
  function update_device (line 69) | pub async fn update_device(
  function delete_device (line 85) | pub async fn delete_device(

FILE: apps/server/src/handler/entities.rs
  type EntityPayload (line 20) | pub struct EntityPayload {
  type GetEntitiesParams (line 30) | pub struct GetEntitiesParams {
  function create_entity (line 34) | pub async fn create_entity(
  function get_entities (line 72) | pub async fn get_entities(
  function get_entities_with_states (line 82) | pub async fn get_entities_with_states(
  function update_entity (line 94) | pub async fn update_entity(
  function delete_entity (line 133) | pub async fn delete_entity(
  function get_entity_history (line 146) | pub async fn get_entity_history(

FILE: apps/server/src/handler/flows.rs
  type FlowPayload (line 19) | pub struct FlowPayload {
  type FlowVersionPayload (line 26) | pub struct FlowVersionPayload {
  function get_all_flows (line 31) | pub async fn get_all_flows(
  function create_flow (line 39) | pub async fn create_flow(
  function update_flow (line 53) | pub async fn update_flow(
  function delete_flow (line 70) | pub async fn delete_flow(
  function create_flow_version (line 81) | pub async fn create_flow_version(
  function get_flow_versions (line 97) | pub async fn get_flow_versions(

FILE: apps/server/src/handler/ha.rs
  type HaState (line 14) | pub struct HaState {
  function get_ha_credentials (line 23) | fn get_ha_credentials(pool: &DbPool) -> Result<(String, String), anyhow:...
  function get_all_states (line 44) | pub async fn get_all_states(
  type HaStateUpdatePayload (line 82) | pub struct HaStateUpdatePayload {
  function post_state (line 87) | pub async fn post_state(

FILE: apps/server/src/handler/integration.rs
  type IntegrationRegisterPayload (line 18) | pub struct IntegrationRegisterPayload {
  function register_integration (line 23) | pub async fn register_integration(
  function get_integration_status (line 109) | pub async fn get_integration_status(
  function delete_integration (line 138) | pub async fn delete_integration(

FILE: apps/server/src/handler/log.rs
  constant LOG_DIR (line 12) | const LOG_DIR: &str = "log";
  type LogContentResponse (line 15) | struct LogContentResponse {
  type LogFileListResponse (line 21) | struct LogFileListResponse {
  type ErrorResponse (line 26) | struct ErrorResponse {
  function get_log_files (line 30) | fn get_log_files() -> io::Result<Vec<String>> {
  function read_log_file_content (line 47) | fn read_log_file_content(filename: &str) -> Result<String, io::Error> {
  function get_latest_log_handler (line 67) | pub async fn get_latest_log_handler(AuthUser(_user): AuthUser) -> Respon...
  function list_log_files_handler (line 88) | pub async fn list_log_files_handler(AuthUser(_user): AuthUser) -> Respon...
  function get_log_by_filename_handler (line 103) | pub async fn get_log_by_filename_handler(

FILE: apps/server/src/handler/map.rs
  type LayerPayload (line 15) | pub struct LayerPayload {
  type VertexPayload (line 22) | pub struct VertexPayload {
  type FeaturePayload (line 29) | pub struct FeaturePayload {
  type UpdateFeaturePayload (line 38) | pub struct UpdateFeaturePayload {
  function create_layer (line 44) | pub async fn create_layer(
  function get_layers (line 59) | pub async fn get_layers(
  function get_layer (line 67) | pub async fn get_layer(
  function update_layer (line 76) | pub async fn update_layer(
  function delete_layer (line 91) | pub async fn delete_layer(
  function create_feature (line 102) | pub async fn create_feature(
  function get_feature (line 132) | pub async fn get_feature(
  function update_feature (line 141) | pub async fn update_feature(
  function delete_feature (line 170) | pub async fn delete_feature(

FILE: apps/server/src/handler/permissions.rs
  type PermissionPayload (line 16) | pub struct PermissionPayload {
  function create_permission (line 21) | pub async fn create_permission(
  function get_permissions (line 34) | pub async fn get_permissions(

FILE: apps/server/src/handler/recordings.rs
  type StartRecordingRequest (line 21) | pub struct StartRecordingRequest {
  type StartRecordingResponse (line 26) | pub struct StartRecordingResponse {
  type ListRecordingsQuery (line 33) | pub struct ListRecordingsQuery {
  type RecordingResponse (line 41) | pub struct RecordingResponse {
    method from (line 58) | fn from(r: Recording) -> Self {
  type ErrorResponse (line 78) | struct ErrorResponse {
  type ActiveRecordingInfo (line 83) | struct ActiveRecordingInfo {
  function start_recording (line 88) | pub async fn start_recording(
  function stop_recording (line 127) | pub async fn stop_recording(
  function list_recordings (line 156) | pub async fn list_recordings(
  function get_recording (line 187) | pub async fn get_recording(
  function delete_recording (line 210) | pub async fn delete_recording(
  function stream_recording (line 270) | pub async fn stream_recording(
  function parse_range_header (line 403) | fn parse_range_header(range: &str, file_size: u64) -> Option<(u64, u64)> {
  function get_active_recordings (line 429) | pub async fn get_active_recordings(
  function is_topic_recording (line 445) | pub async fn is_topic_recording(

FILE: apps/server/src/handler/roles.rs
  type RolePayload (line 20) | pub struct RolePayload {
  type PermissionAssignmentPayload (line 27) | pub struct PermissionAssignmentPayload {
  function create_role (line 31) | pub async fn create_role(
  function get_roles (line 45) | pub async fn get_roles(
  function update_role (line 53) | pub async fn update_role(
  function delete_role (line 69) | pub async fn delete_role(
  function grant_permission_to_role (line 78) | pub async fn grant_permission_to_role(
  function revoke_permission_from_role (line 88) | pub async fn revoke_permission_from_role(
  function get_role_permissions (line 97) | pub async fn get_role_permissions(

FILE: apps/server/src/handler/sdr.rs
  constant RTL_TCP_SET_FREQ (line 18) | const RTL_TCP_SET_FREQ: u8 = 0x01;
  constant RTL_TCP_SET_SAMPLERATE (line 19) | const RTL_TCP_SET_SAMPLERATE: u8 = 0x02;
  constant RTL_TCP_SET_GAIN_MODE (line 20) | const RTL_TCP_SET_GAIN_MODE: u8 = 0x03;
  constant RTL_TCP_SET_AGC (line 21) | const RTL_TCP_SET_AGC: u8 = 0x08;
  constant DEFAULT_SAMPLERATE (line 23) | const DEFAULT_SAMPLERATE: u32 = 2_048_000;
  constant AUDIO_SAMPLE_RATE (line 24) | const AUDIO_SAMPLE_RATE: u32 = 48000;
  constant CONNECT_TIMEOUT (line 25) | const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
  function get_sdr_address (line 27) | fn get_sdr_address(pool: &DbPool) -> Result<(String, u16), anyhow::Error> {
  function rtl_tcp_command (line 54) | async fn rtl_tcp_command(stream: &mut TcpStream, cmd: u8, value: u32) ->...
  function read_rtl_tcp_header (line 64) | async fn read_rtl_tcp_header(stream: &mut TcpStream) -> Result<(u32, u32...
  type FmDemodState (line 84) | struct FmDemodState {
    method new (line 91) | fn new() -> Self {
  function u8_iq_to_float (line 97) | fn u8_iq_to_float(data: &[u8]) -> Vec<f32> {
  function fm_demodulate (line 102) | fn fm_demodulate(iq_samples: &[f32], state: &mut FmDemodState, lpf_alpha...
  function downsample_to_i16 (line 130) | fn downsample_to_i16(samples: &[f32], src_rate: u32, dst_rate: u32) -> V...
  type SetFrequencyPayload (line 156) | pub struct SetFrequencyPayload {
  function set_frequency (line 160) | pub async fn set_frequency(
  function get_samplerate (line 182) | pub async fn get_samplerate(
  function start_stream (line 189) | pub async fn start_stream(
  function stop_stream (line 196) | pub async fn stop_stream(
  function sdr_audio_ws (line 205) | pub async fn sdr_audio_ws(
  function handle_sdr_audio (line 212) | async fn handle_sdr_audio(mut ws: WebSocket, state: Arc<AppState>) {

FILE: apps/server/src/handler/stat.rs
  function get_stats (line 12) | pub async fn get_stats(

FILE: apps/server/src/handler/state.rs
  type StateRequest (line 19) | pub struct StateRequest {
  type StateResponse (line 24) | pub struct StateResponse {
  function set_state (line 28) | pub async fn set_state(

FILE: apps/server/src/handler/storage.rs
  type ErrorResponse (line 21) | struct ErrorResponse {
  type SuccessResponse (line 26) | struct SuccessResponse {
  type UpdateContentRequest (line 31) | pub struct UpdateContentRequest {
  type RenameRequest (line 36) | pub struct RenameRequest {
  type DirEntry (line 42) | pub struct DirEntry {
  type ListResponse (line 48) | pub struct ListResponse {
  function get_storage_root (line 53) | fn get_storage_root() -> io::Result<PathBuf> {
  function get_safe_path (line 59) | fn get_safe_path(root: &StdPath, user_path: &str) -> Result<PathBuf, io:...
  function map_io_error (line 72) | fn map_io_error(e: io::Error, path_str: &str) -> Response {
  function guard_code_service (line 91) | fn guard_code_service(pool: &DbPool) -> Result<(), Response> {
  function read_handler (line 114) | pub async fn read_handler(
  function list_dir_contents (line 140) | async fn list_dir_contents(path: &PathBuf, user_path: &str) -> Response {
  function read_file_content (line 165) | async fn read_file_content(path: &PathBuf, user_path: &str) -> Response {
  function create_or_update_file_handler (line 172) | pub async fn create_or_update_file_handler(
  function create_dir_handler (line 215) | pub async fn create_dir_handler(
  function delete_handler (line 244) | pub async fn delete_handler(
  function rename_handler (line 279) | pub async fn rename_handler(

FILE: apps/server/src/handler/streams.rs
  type RegisterStreamRequest (line 14) | pub struct RegisterStreamRequest {
  type RegisterStreamResponse (line 20) | pub struct RegisterStreamResponse {
  function register_stream (line 25) | pub async fn register_stream(

FILE: apps/server/src/handler/tunnel.rs
  type StartRequest (line 9) | pub struct StartRequest {
  type StartResponse (line 16) | pub struct StartResponse {
  type StatusResponse (line 21) | pub struct StatusResponse {
  function start_tunnel (line 28) | pub async fn start_tunnel(
  function stop_tunnel (line 40) | pub async fn stop_tunnel(State(state): State<Arc<AppState>>) -> Result<S...
  function status_tunnel (line 49) | pub async fn status_tunnel(State(state): State<Arc<AppState>>) -> Json<S...

FILE: apps/server/src/handler/users.rs
  type CreateUserPayload (line 22) | pub struct CreateUserPayload {
  function get_users_list (line 28) | pub async fn get_users_list(
  function create_user (line 36) | pub async fn create_user(
  function get_user (line 53) | pub async fn get_user(
  type UpdateUserPayload (line 63) | pub struct UpdateUserPayload {
  function update_user (line 69) | pub async fn update_user(
  function delete_user (line 91) | pub async fn delete_user(
  type RoleAssignmentPayload (line 101) | pub struct RoleAssignmentPayload {
  function assign_role_to_user (line 105) | pub async fn assign_role_to_user(
  function remove_role_from_user (line 115) | pub async fn remove_role_from_user(
  function get_user_roles (line 124) | pub async fn get_user_roles(

FILE: apps/server/src/handler/ws/dashboard_component_event.rs
  constant SERVER_MIN_COOLDOWN_MS (line 10) | const SERVER_MIN_COOLDOWN_MS: u64 = 100;
  constant MAX_LISTENER_ID_LEN (line 11) | const MAX_LISTENER_ID_LEN: usize = 128;
  type DashboardComponentEventV2 (line 14) | struct DashboardComponentEventV2 {
  function rate_limit_key (line 35) | fn rate_limit_key(ev: &DashboardComponentEventV2) -> String {
  function cooldown_ms (line 45) | fn cooldown_ms(ev: &DashboardComponentEventV2) -> u64 {
  function validate_listener_id (line 49) | fn validate_listener_id(id: &str) -> bool {
  function apply_dashboard_component_event (line 59) | pub(crate) fn apply_dashboard_component_event(
  function deserializes_v2 (line 138) | fn deserializes_v2() {
  function listener_id_validation (line 156) | fn listener_id_validation() {

FILE: apps/server/src/handler/ws/handlers.rs
  type WsMessageIn (line 41) | struct WsMessageIn {
  type ServerStats (line 48) | struct ServerStats {
  type StreamState (line 54) | struct StreamState {
  type SubscribeStreamPayload (line 60) | struct SubscribeStreamPayload {
  type FlowPayload (line 65) | struct FlowPayload {
  type FlowStatus (line 70) | struct FlowStatus {
  type ActorCommand (line 76) | enum ActorCommand {
  type WSActor (line 118) | struct WSActor {
    method run (line 129) | async fn run(mut self) {
    method handle_get_all_flows (line 210) | async fn handle_get_all_flows(&self, responder: oneshot::Sender<Result...
    method handle_get_server (line 241) | async fn handle_get_server(&self) -> Result<ServerStats> {
    method handle_get_all_stream_state (line 257) | async fn handle_get_all_stream_state(&self, payload: serde_json::Value) {
    method handle_offer (line 287) | async fn handle_offer(
    method handle_health_check (line 303) | async fn handle_health_check(&self, payload: serde_json::Value) {
    method handle_ping (line 331) | async fn handle_ping(&self, payload: serde_json::Value) {
    method handle_compute_flow (line 350) | async fn handle_compute_flow(&self, payload: serde_json::Value) {
    method handle_stop_flow (line 401) | async fn handle_stop_flow(&self, flow_id: i32) {
    method extract_ice_credentials (line 408) | fn extract_ice_credentials(sdp: &str) -> Option<(String, String)> {
    method patch_ice_credentials (line 424) | fn patch_ice_credentials(sdp: &str, ufrag: &str, pwd: &str) -> String {
    method is_h264_keyframe (line 441) | fn is_h264_keyframe(pkt: &webrtc::rtp::packet::Packet) -> bool {
    method handle_subscribe (line 480) | async fn handle_subscribe(&mut self, topic: String) {
  function handle_socket (line 791) | pub async fn handle_socket(socket: WebSocket, state: Arc<AppState>) {

FILE: apps/server/src/handler/ws/mod.rs
  function ws_handler (line 20) | pub async fn ws_handler(
  type WsMessageOut (line 32) | pub struct WsMessageOut<'a, T: Serialize> {

FILE: apps/server/src/handler/ws/webrtc.rs
  function filter_ice_url (line 36) | fn filter_ice_url(url: &str) -> Option<String> {
  function build_ice_servers (line 48) | fn build_ice_servers(pool: &DbPool) -> Vec<RTCIceServer> {
  function create_peer_connection (line 145) | pub async fn create_peer_connection(

FILE: apps/server/src/init/db_record.rs
  function create_initial_admin (line 11) | pub fn create_initial_admin(conn: &mut SqliteConnection) {
  function create_initial_configurations (line 50) | pub fn create_initial_configurations(conn: &mut SqliteConnection) {
  function seed_initial_permissions (line 101) | pub fn seed_initial_permissions(conn: &mut SqliteConnection) {

FILE: apps/server/src/init/streams.rs
  function create_hydrate_streams (line 8) | pub fn create_hydrate_streams(pool: &DbPool, streams: &StreamManager) {

FILE: apps/server/src/logo.rs
  constant LOGO (line 6) | const LOGO: &str = r#"
  function print_header (line 16) | pub fn print_header() {
  function print_footer (line 21) | pub fn print_footer() {
  function print_local_ip_info (line 25) | pub fn print_local_ip_info(ip: &str, port: &str) {
  function print_error (line 34) | pub fn print_error(message: &str) {
  function get_local_ip_address (line 38) | pub fn get_local_ip_address() -> Result<String, Box<dyn Error>> {
  function print_logo (line 43) | pub fn print_logo() {

FILE: apps/server/src/main.rs
  constant LOG_FILE_PATH (line 12) | const LOG_FILE_PATH: &str = "log/app.log";
  constant MIGRATIONS (line 45) | pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migratio...
  function run_migrations (line 47) | fn run_migrations(connection: &mut impl MigrationHarness<diesel::sqlite:...
  function main (line 55) | async fn main() -> Result<()> {

FILE: apps/server/src/media/adapter.rs
  type MediaAdapter (line 8) | pub trait MediaAdapter: Send + Sync {
    method protocol (line 9) | fn protocol(&self) -> Protocol;
    method start (line 10) | async fn start(&self, shutdown: watch::Receiver<()>) -> Result<()>;

FILE: apps/server/src/media/rtp_push.rs
  type RtpPushAdapter (line 10) | pub struct RtpPushAdapter {
    method new (line 16) | pub fn new(addr: String, streams: StreamManager) -> Self {
    method run (line 20) | async fn run(&self, mut shutdown_rx: watch::Receiver<()>) -> Result<()> {
  method protocol (line 77) | fn protocol(&self) -> Protocol {
  method start (line 81) | async fn start(&self, shutdown: watch::Receiver<()>) -> Result<()> {

FILE: apps/server/src/media/rtsp_pull.rs
  type RtspPullAdapter (line 20) | pub struct RtspPullAdapter {
    method new (line 27) | pub fn new(
    method start_pipelines (line 39) | async fn start_pipelines(&self, mut shutdown_rx: watch::Receiver<()>) ...
    method spawn_new_pipelines (line 77) | async fn spawn_new_pipelines(
  method protocol (line 160) | fn protocol(&self) -> Protocol {
  method start (line 164) | async fn start(&self, shutdown: watch::Receiver<()>) -> Result<()> {
  function run_pipeline (line 169) | async fn run_pipeline(

FILE: apps/server/src/recording/manager.rs
  type ActiveRecording (line 15) | pub struct ActiveRecording {
  type RecordingManager (line 21) | pub struct RecordingManager {
    method new (line 29) | pub fn new(streams: StreamManager, pool: DbPool) -> Self {
    method cleanup_orphaned_recordings (line 50) | fn cleanup_orphaned_recordings(pool: &DbPool) {
    method start_recording (line 67) | pub fn start_recording(&self, topic: &str, user_id: Option<i32>) -> Re...
    method stop_recording (line 182) | pub fn stop_recording(&self, recording_id: i32) -> Result<()> {
    method stop_recording_by_topic (line 197) | pub fn stop_recording_by_topic(&self, topic: &str) -> Result<()> {
    method is_recording (line 211) | pub fn is_recording(&self, topic: &str) -> bool {
    method get_active_recording_id (line 215) | pub fn get_active_recording_id(&self, topic: &str) -> Option<i32> {
    method get_all_active_recordings (line 221) | pub fn get_all_active_recordings(&self) -> Vec<(String, i32)> {

FILE: apps/server/src/recording/muxer.rs
  type GstMuxer (line 12) | pub struct GstMuxer {
    method new (line 21) | pub fn new(output_path: &str, media_type: MediaType, payload_type: u8)...
    method start (line 64) | pub fn start(&self) -> Result<()> {
    method write_packet (line 70) | pub fn write_packet(&self, packet: &Packet) -> Result<()> {
    method get_bytes_written (line 98) | pub async fn get_bytes_written(&self) -> u64 {
    method finalize (line 102) | pub fn finalize(&self) -> Result<()> {
    method output_path (line 136) | pub fn output_path(&self) -> &str {
    method media_type (line 140) | pub fn media_type(&self) -> MediaType {
  method drop (line 146) | fn drop(&mut self) {

FILE: apps/server/src/recording/service.rs
  type RecordingService (line 14) | pub struct RecordingService {
    method new (line 23) | pub fn new(
    method run (line 39) | pub async fn run(&self, mut shutdown_rx: watch::Receiver<()>) -> Resul...
    method update_recording_completed (line 175) | fn update_recording_completed(&self, duration_ms: i32, file_size: i32)...
    method update_recording_failed (line 187) | fn update_recording_failed(&self, error_msg: &str) -> Result<()> {

FILE: apps/server/src/routes.rs
  function get_server_info (line 25) | async fn get_server_info() -> Json<serde_json::Value> {
  type ClientAsset (line 35) | struct ClientAsset;
  function static_handler (line 37) | async fn static_handler(uri: Uri) -> impl IntoResponse {
  function web_server (line 59) | pub async fn web_server(

FILE: apps/server/src/state.rs
  type DbPool (line 12) | pub type DbPool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
  type MediaType (line 22) | pub enum MediaType {
  type Protocol (line 28) | pub enum Protocol {
  type StreamDescriptor (line 40) | pub struct StreamDescriptor {
  type StreamHandle (line 49) | pub struct StreamHandle {
  type StreamRegistry (line 57) | pub struct StreamRegistry {
    method new (line 63) | pub fn new() -> Self {
    method register (line 67) | pub fn register(&self, descriptor: StreamDescriptor) -> StreamHandle {
    method insert_with_sender (line 72) | pub fn insert_with_sender(
    method get_by_ssrc (line 90) | pub fn get_by_ssrc(&self, ssrc: u32) -> Option<StreamHandle> {
    method get_by_topic (line 94) | pub fn get_by_topic(&self, topic: &str) -> Option<StreamHandle> {
    method mark_online (line 100) | pub fn mark_online(&self, ssrc: u32) {
    method update_last_seen (line 111) | pub fn update_last_seen(&self, ssrc: u32) {
    method iter (line 119) | pub fn iter(&self) -> dashmap::iter::Iter<'_, u32, StreamHandle> {
    method remove (line 123) | pub fn remove(&self, ssrc: &u32) -> Option<(u32, StreamHandle)> {
    method len (line 132) | pub fn len(&self) -> usize {
  type StreamManager (line 137) | pub type StreamManager = Arc<StreamRegistry>;
  type MqttMessage (line 140) | pub struct MqttMessage {
  type TopicMapping (line 146) | pub struct TopicMapping {
  type DashboardUiEvent (line 154) | pub struct DashboardUiEvent {
  type AppState (line 172) | pub struct AppState {

FILE: apps/server/src/tunnel_control.rs
  constant BINARY_PREFIX (line 18) | const BINARY_PREFIX: usize = 8;
  constant MAX_HEADER_BYTES (line 19) | const MAX_HEADER_BYTES: usize = 64 * 1024;
  type TunnelStatus (line 22) | pub struct TunnelStatus {
  type TunnelManager (line 29) | pub struct TunnelManager {
    method new (line 42) | pub fn new() -> Self {
    method start (line 54) | pub async fn start(&self, server: String, target: String, access_token...
    method stop (line 84) | pub async fn stop(&self) -> Result<()> {
    method status (line 98) | pub async fn status(&self) -> TunnelStatus {
  type Inner (line 33) | struct Inner {
  type AgentToServer (line 111) | enum AgentToServer {
  type ServerToAgent (line 127) | enum ServerToAgent {
  type AgentState (line 143) | struct AgentState {
  type PendingRequest (line 149) | struct PendingRequest {
  type AgentOutbound (line 160) | enum AgentOutbound {
  type TargetInfo (line 166) | struct TargetInfo {
    method parse (line 173) | fn parse(raw: &str) -> Result<Self> {
    method host_header (line 190) | fn host_header(&self) -> String {
  function run_agent (line 195) | async fn run_agent(
  function wait_for_registration (line 302) | async fn wait_for_registration(
  function handle_server_control (line 317) | async fn handle_server_control(agent: Arc<AgentState>, msg: ServerToAgen...
  function handle_server_body (line 387) | async fn handle_server_body(agent: Arc<AgentState>, data: Bytes) -> Resu...
  function forward_request (line 404) | async fn forward_request(
  function find_header_split (line 574) | fn find_header_split(buf: &[u8]) -> Option<usize> {
  function is_upgrade (line 580) | fn is_upgrade(headers: &[(String, String)]) -> bool {

FILE: apps/server/src/utils/entity_map.rs
  function remap_topics (line 10) | pub async fn remap_topics(State(state): State<Arc<AppState>>) -> Result<...

FILE: apps/server/src/utils/hash.rs
  function hash_password (line 1) | pub fn hash_password(password: &str) -> Result<String, bcrypt::BcryptErr...
  function verify_password (line 6) | pub fn verify_password(password: &str, hashed_password: &str) -> Result<...

FILE: apps/server/src/utils/stream_checker.rs
  constant STREAM_TIMEOUT (line 9) | const STREAM_TIMEOUT: Duration = Duration::from_secs(10);
  function stream_status_checker (line 11) | pub async fn stream_status_checker(

FILE: apps/server/src/utils/system_configs.rs
  function replace_config_placeholders (line 3) | pub fn replace_config_placeholders(template: &str, configs: &[SystemConf...

FILE: apps/server/tests/common/mod.rs
  constant MIGRATIONS (line 6) | pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
  function setup_test_pool (line 9) | pub fn setup_test_pool() -> DbPool {

FILE: apps/server/tests/recording_manager_tests.rs
  function setup_test_streams (line 9) | fn setup_test_streams() -> Arc<StreamRegistry> {
  function test_is_recording_false_initially (line 26) | fn test_is_recording_false_initially() {
  function test_get_active_recording_id_none (line 35) | fn test_get_active_recording_id_none() {
  function test_get_all_active_recordings_empty (line 44) | fn test_get_all_active_recordings_empty() {
  function test_start_recording_stream_not_found (line 53) | fn test_start_recording_stream_not_found() {
  function test_stop_recording_not_found (line 64) | fn test_stop_recording_not_found() {
  function test_stop_recording_by_topic_not_found (line 75) | fn test_stop_recording_by_topic_not_found() {

FILE: apps/server/tests/recordings_repository_tests.rs
  function create_test_recording (line 6) | fn create_test_recording<'a>() -> NewRecording<'a> {
  function test_create_recording (line 20) | fn test_create_recording() {
  function test_get_recording_by_id (line 29) | fn test_get_recording_by_id() {
  function test_get_recording_by_id_not_found (line 38) | fn test_get_recording_by_id_not_found() {
  function test_get_all_recordings (line 44) | fn test_get_all_recordings() {
  function test_get_recordings_by_status (line 54) | fn test_get_recordings_by_status() {
  function test_update_recording (line 73) | fn test_update_recording() {
  function test_delete_recording (line 89) | fn test_delete_recording() {
  function test_mark_orphaned_as_abandoned (line 98) | fn test_mark_orphaned_as_abandoned() {

FILE: apps/server/tests/state_tests.rs
  function create_test_descriptor (line 5) | fn create_test_descriptor(id: u32, topic: &str) -> StreamDescriptor {
  function test_register_stream (line 16) | fn test_register_stream() {
  function test_get_by_ssrc (line 27) | fn test_get_by_ssrc() {
  function test_get_by_topic (line 36) | fn test_get_by_topic() {
  function test_mark_online (line 45) | fn test_mark_online() {
  function test_remove (line 55) | fn test_remove() {
  function test_insert_with_sender (line 67) | fn test_insert_with_sender() {
  function test_iter (line 77) | fn test_iter() {

FILE: example/golang/audio-file/audio.go
  type RegisterRequest (line 11) | type RegisterRequest struct
  type RegisterResponse (line 16) | type RegisterResponse struct
  function main (line 21) | func main() {

FILE: example/golang/audio-mic/audio.go
  type RegisterRequest (line 14) | type RegisterRequest struct
  type RegisterResponse (line 19) | type RegisterResponse struct
  function main (line 24) | func main() {

FILE: example/golang/video-sample/video.go
  type RegisterRequest (line 11) | type RegisterRequest struct
  type RegisterResponse (line 16) | type RegisterResponse struct
  function main (line 21) | func main() {

FILE: example/golang/video2-sample/video.go
  type RegisterRequest (line 11) | type RegisterRequest struct
  type RegisterResponse (line 16) | type RegisterResponse struct
  function main (line 21) | func main() {

FILE: example/golang/video3-sample/video.go
  type RegisterRequest (line 11) | type RegisterRequest struct
  type RegisterResponse (line 16) | type RegisterResponse struct
  function main (line 21) | func main() {

FILE: example/js/index.js
  function postState (line 29) | async function postState() {

FILE: packages/capsule-client/src/client.ts
  class CapsuleAuthError (line 19) | class CapsuleAuthError extends Error {
    method constructor (line 20) | constructor(message: string = 'Authentication required') {
  class CapsuleRateLimitError (line 31) | class CapsuleRateLimitError extends Error {
    method constructor (line 32) | constructor(message: string = 'Rate limit exceeded') {
  class CapsuleSubscriptionError (line 43) | class CapsuleSubscriptionError extends Error {
    method constructor (line 44) | constructor(message: string = 'Pro subscription required') {
  class CapsuleClient (line 70) | class CapsuleClient {
    method constructor (line 76) | constructor(options: CapsuleClientOptions) {
    method getPublicKey (line 88) | async getPublicKey(): Promise<string> {
    method clearPublicKeyCache (line 106) | clearPublicKeyCache(): void {
    method setBaseUrl (line 115) | setBaseUrl(url: string): void {
    method chat (line 126) | async chat(message: string, options?: ChatOptions): Promise<ChatRespon...
    method analyzeImage (line 154) | async analyzeImage(options: AnalyzeImageOptions, chatOptions?: ChatOpt...
    method chatStream (line 188) | async chatStream(message: string, onChunk: StreamCallback, options?: C...
    method analyzeImageStream (line 207) | async analyzeImageStream(
    method fetch (line 238) | private async fetch<T>(path: string, init: RequestInit): Promise<T> {
    method fetchStream (line 288) | private async fetchStream(

FILE: packages/capsule-client/src/conversation.ts
  constant DEFAULT_MAX_HISTORY (line 7) | const DEFAULT_MAX_HISTORY = 50;
  type ConversationOptions (line 12) | interface ConversationOptions {
  class Conversation (line 40) | class Conversation {
    method constructor (line 46) | constructor(client: CapsuleClient, options?: ConversationOptions) {
    method chat (line 57) | async chat(message: string): Promise<ChatResponse> {
    method chatStream (line 76) | async chatStream(message: string, onChunk: StreamCallback): Promise<vo...
    method addImageAnalysisToHistory (line 107) | addImageAnalysisToHistory(userMessage: string, assistantResponse: stri...
    method getHistory (line 121) | getHistory(): ReadonlyArray<HistoryMessage> {
    method clearHistory (line 128) | clearHistory(): void {
    method setSystemPrompt (line 135) | setSystemPrompt(prompt: string | undefined): void {
    method appendToHistory (line 139) | private appendToHistory(message: HistoryMessage): void {

FILE: packages/capsule-client/src/crypto.ts
  constant HKDF_SALT (line 9) | const HKDF_SALT = new TextEncoder().encode('vessel-capsule-v1-salt');
  constant HKDF_INFO (line 11) | const HKDF_INFO = new TextEncoder().encode('vessel-capsule-v1-key');
  function bytesToBase64 (line 16) | function bytesToBase64(bytes: Uint8Array): string {
  function base64ToBytes (line 32) | function base64ToBytes(base64: string): Uint8Array {
  function zeroize (line 44) | function zeroize(arr: Uint8Array): void {
  function encryptImage (line 73) | async function encryptImage(
  function fileToBytes (line 114) | async function fileToBytes(file: File | Blob): Promise<Uint8Array> {

FILE: packages/capsule-client/src/types.ts
  type HistoryMessage (line 7) | interface HistoryMessage {
  type ToolFunction (line 21) | interface ToolFunction {
  type Tool (line 30) | interface Tool {
  type ToolCallResult (line 38) | interface ToolCallResult {
  type ToolCallCallback (line 47) | type ToolCallCallback = (toolCalls: ToolCallResult[]) => void;
  type ChatOptions (line 52) | interface ChatOptions {
  type EncryptedImage (line 68) | interface EncryptedImage {
  type ChatRequest (line 80) | interface ChatRequest {
  type ChatResponse (line 98) | interface ChatResponse {
  type PublicKeyResponse (line 106) | interface PublicKeyResponse {
  type CapsuleClientOptions (line 114) | interface CapsuleClientOptions {
  type AnalyzeImageOptions (line 142) | interface AnalyzeImageOptions {
  type StreamCallback (line 152) | type StreamCallback = (chunk: string, done: boolean) => void;

FILE: packages/custom-node-utils/add_number.py
  function main (line 1) | def main(inputs):

FILE: packages/custom-node-utils/random.py
  function main (line 3) | def main(inputs):

FILE: tests/api.test.js
  constant API_BASE_URL (line 3) | const API_BASE_URL = "http://localhost:6174";
Condensed preview — 575 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,964K chars).
[
  {
    "path": ".cargo/config.toml",
    "chars": 190,
    "preview": "[target.aarch64-apple-darwin]\nrustflags = [\"-C\", \"link-args=-Wl,-headerpad_max_install_names\"]\n\n[target.x86_64-apple-dar"
  },
  {
    "path": ".github/GIT.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 884,
    "preview": "name: Rust\n\n# on:\n#   push:\n#     branches: [\"main\"]\n#   pull_request:\n#     branches: [\"main\"]\n\nenv:\n  CARGO_TERM_COLOR"
  },
  {
    "path": ".github/workflows/release-macos.yml",
    "chars": 4360,
    "preview": "name: Release macOS\n\non:\n  push:\n    tags: ['v*']\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: macos-14\n    timeout"
  },
  {
    "path": ".gitignore",
    "chars": 264,
    "preview": "/node_modules\ndocs/.vitepress/dist\ndocs/.vitepress/cache\n/target\n.env\ndatabase.db\ndatabase.db-*\n.DS_Store\n*.log.*\n*.log\n"
  },
  {
    "path": ".prettierrc",
    "chars": 139,
    "preview": "{\n  \"semi\": true,\n  \"singleQuote\": false,\n  \"jsxSingleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"all\",\n  \"arrowP"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5217,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "CODE_RULE.md",
    "chars": 6576,
    "preview": "# Coding Rules\n\nThis document establishes the official coding standards for all mission-critical software. Adherence to "
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 379,
    "preview": "# CONTRIBUTING\n\n...\n\n## Projects Structure\n\n`/apps/client` Frontend code with Vite.  \n`/apps/server` Rust server that co"
  },
  {
    "path": "Cargo.toml",
    "chars": 110,
    "preview": "[workspace]\nmembers = [\n    \"apps/server\",\n    \"apps/desktop/src-tauri\",\n    \"apps/capsule\",\n]\nresolver = \"2\"\n"
  },
  {
    "path": "Dockerfile",
    "chars": 538,
    "preview": "FROM rust:latest AS builder\n\nRUN apt-get update && apt-get install -y cmake build-essential libglib2.0-dev pkg-config mu"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 5168,
    "preview": "<p align='center'>\n<img src='.github/icon.png' width='140' />\n<h1 align='center'>Vessel</h1>\n\n<p align='center'>\n<a href"
  },
  {
    "path": "SECURITY.md",
    "chars": 322,
    "preview": "# Security\n\nPlease report security issues to `3457xc@gmail.com`.\n\nThis email is the maintainer's personal and public ema"
  },
  {
    "path": "apps/capsule/Cargo.toml",
    "chars": 1088,
    "preview": "[package]\nname = \"capsule\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\n# Web Framework\ntokio = { version = \"1\", f"
  },
  {
    "path": "apps/capsule/Dockerfile",
    "chars": 993,
    "preview": "# Build stage\nFROM rust:1.75-alpine AS builder\n\n# 빌드 의존성 설치\nRUN apk add --no-cache musl-dev pkgconfig openssl-dev\n\nWORKD"
  },
  {
    "path": "apps/capsule/docker-compose.yml",
    "chars": 767,
    "preview": "version: '3.8'\n\nservices:\n  capsule:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    ports:\n      - \"3000:3"
  },
  {
    "path": "apps/capsule/src/api/auth.rs",
    "chars": 2993,
    "preview": "//! Authentication extractor for protected endpoints\n//!\n//! Extracts and validates JWT tokens from Authorization header"
  },
  {
    "path": "apps/capsule/src/api/chat.rs",
    "chars": 8047,
    "preview": "//! Chat API handlers with authentication and usage tracking\n//!\n//! # Security Data Flow\n//! 1. Extract and validate JW"
  },
  {
    "path": "apps/capsule/src/api/key.rs",
    "chars": 712,
    "preview": "use axum::{extract::State, Json};\nuse std::sync::Arc;\n\nuse crate::types::PublicKeyResponse;\nuse crate::AppState;\n\n/// GE"
  },
  {
    "path": "apps/capsule/src/api/mod.rs",
    "chars": 197,
    "preview": "mod auth;\nmod chat;\nmod key;\nmod routes;\n\npub use auth::AuthUser;\npub use chat::{chat_handler, chat_stream_handler};\npub"
  },
  {
    "path": "apps/capsule/src/api/routes.rs",
    "chars": 2923,
    "preview": "use axum::{\n    routing::{get, post},\n    Extension, Router,\n};\nuse std::sync::Arc;\nuse tower_http::{\n    cors::{AllowHe"
  },
  {
    "path": "apps/capsule/src/config.rs",
    "chars": 3241,
    "preview": "use zeroize::Zeroizing;\n\nuse crate::error::CapsuleError;\n\n/// Application configuration\npub struct Config {\n    /// Serv"
  },
  {
    "path": "apps/capsule/src/crypto/encryption.rs",
    "chars": 3584,
    "preview": "use base64::{engine::general_purpose::STANDARD, Engine};\nuse chacha20poly1305::{\n    aead::{Aead, KeyInit},\n    XChaCha2"
  },
  {
    "path": "apps/capsule/src/crypto/keypair.rs",
    "chars": 11349,
    "preview": "use base64::{engine::general_purpose::STANDARD, Engine};\nuse std::sync::Arc;\nuse std::time::Duration;\nuse tokio::sync::R"
  },
  {
    "path": "apps/capsule/src/crypto/mod.rs",
    "chars": 107,
    "preview": "mod keypair;\nmod encryption;\n\npub use keypair::KeyManager;\npub(crate) use encryption::decrypt_with_secret;\n"
  },
  {
    "path": "apps/capsule/src/error.rs",
    "chars": 2951,
    "preview": "use axum::{\n    http::StatusCode,\n    response::{IntoResponse, Response},\n    Json,\n};\nuse serde_json::json;\nuse thiserr"
  },
  {
    "path": "apps/capsule/src/main.rs",
    "chars": 3529,
    "preview": "mod api;\nmod config;\nmod crypto;\nmod error;\nmod services;\nmod types;\n\nuse std::sync::Arc;\nuse std::time::Duration;\n\nuse "
  },
  {
    "path": "apps/capsule/src/services/jwt.rs",
    "chars": 7714,
    "preview": "//! JWT validation service for Supabase tokens\n//!\n//! Validates Supabase JWT tokens using ES256 algorithm with JWKS.\n//"
  },
  {
    "path": "apps/capsule/src/services/mod.rs",
    "chars": 193,
    "preview": "mod jwt;\nmod openai;\nmod usage;\n\npub use jwt::{JwtError, JwtValidator, SupabaseClaims};\npub use openai::{ChatResult, Ope"
  },
  {
    "path": "apps/capsule/src/services/openai.rs",
    "chars": 19861,
    "preview": "use axum::response::sse::Event;\nuse futures_util::{Stream, StreamExt};\nuse reqwest::Client;\nuse serde::{Deserialize, Ser"
  },
  {
    "path": "apps/capsule/src/services/usage.rs",
    "chars": 5691,
    "preview": "//! Usage tracking service for rate limiting and billing\n//!\n//! Communicates with Supabase to:\n//! 1. Check subscriptio"
  },
  {
    "path": "apps/capsule/src/types/decrypted.rs",
    "chars": 1434,
    "preview": "use base64::{engine::general_purpose::STANDARD, Engine};\nuse zeroize::{Zeroize, ZeroizeOnDrop};\n\n/// 복호화된 이미지 - Capsule "
  },
  {
    "path": "apps/capsule/src/types/encrypted.rs",
    "chars": 3866,
    "preview": "use serde::{Deserialize, Serialize};\n\nuse crate::error::CapsuleError;\n\n/// 최대 히스토리 메시지 수\nconst MAX_HISTORY_MESSAGES: usi"
  },
  {
    "path": "apps/capsule/src/types/mod.rs",
    "chars": 75,
    "preview": "mod encrypted;\nmod decrypted;\n\npub use encrypted::*;\npub use decrypted::*;\n"
  },
  {
    "path": "apps/capsule/tests/api.test.mjs",
    "chars": 8593,
    "preview": "/**\n * Capsule API 테스트\n *\n * 사용법:\n *   cd apps/capsule/tests\n *   npm install\n *   npm test                         # 텍스"
  },
  {
    "path": "apps/capsule/tests/crypto.mjs",
    "chars": 1853,
    "preview": "/**\n * 클라이언트 측 암호화 유틸리티\n * 서버의 암호화 스킴과 동일하게 구현\n */\n\nimport { x25519 } from '@noble/curves/ed25519';\nimport { xchacha20po"
  },
  {
    "path": "apps/capsule/tests/package.json",
    "chars": 278,
    "preview": "{\n  \"name\": \"capsule-tests\",\n  \"type\": \"module\",\n  \"private\": true,\n  \"scripts\": {\n    \"test\": \"node api.test.mjs\",\n    "
  },
  {
    "path": "apps/client/.gitignore",
    "chars": 262,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "apps/client/README.md",
    "chars": 9,
    "preview": "# client\n"
  },
  {
    "path": "apps/client/components.json",
    "chars": 444,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": "
  },
  {
    "path": "apps/client/eslint.config.js",
    "chars": 740,
    "preview": "import js from \"@eslint/js\";\nimport globals from \"globals\";\nimport reactHooks from \"eslint-plugin-react-hooks\";\nimport r"
  },
  {
    "path": "apps/client/index.html",
    "chars": 306,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\" class=\"dark\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta name=\"viewport\" content=\"w"
  },
  {
    "path": "apps/client/package.json",
    "chars": 2271,
    "preview": "{\n  \"name\": \"client\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"main\": \"maindist/main.js\",\n  \"scri"
  },
  {
    "path": "apps/client/src/App.tsx",
    "chars": 5543,
    "preview": "import { AuthPage } from \"./pages/auth\";\n\nimport { createBrowserRouter, RouterProvider } from \"react-router\";\nimport {\n "
  },
  {
    "path": "apps/client/src/app/pageWrapper/page-wrapper.tsx",
    "chars": 427,
    "preview": "import { isElectron } from \"@/lib/electron\";\nimport type { PropsWithChildren } from \"react\";\n\nexport function PageWrappe"
  },
  {
    "path": "apps/client/src/app/providers/theme-provider.tsx",
    "chars": 1605,
    "preview": "import { createContext, useContext, useEffect, useState } from \"react\";\n\ntype Theme = \"dark\" | \"light\" | \"system\";\n\ntype"
  },
  {
    "path": "apps/client/src/components/icon/Logo.tsx",
    "chars": 1248,
    "preview": "export function VesselLogo() {\n  return (\n    <svg\n      width='285'\n      height='133'\n      viewBox='0 0 285 133'\n    "
  },
  {
    "path": "apps/client/src/components/ui/alert-dialog.tsx",
    "chars": 3858,
    "preview": "import * as React from \"react\";\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\";\n\nimport { cn } fro"
  },
  {
    "path": "apps/client/src/components/ui/alert.tsx",
    "chars": 1599,
    "preview": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \""
  },
  {
    "path": "apps/client/src/components/ui/avatar.tsx",
    "chars": 1083,
    "preview": "import * as React from \"react\"\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\"\n\nimport { cn } from \"@/lib/util"
  },
  {
    "path": "apps/client/src/components/ui/badge.tsx",
    "chars": 1629,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"cla"
  },
  {
    "path": "apps/client/src/components/ui/breadcrumb.tsx",
    "chars": 2357,
    "preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { ChevronRight, MoreHorizontal } from "
  },
  {
    "path": "apps/client/src/components/ui/button.tsx",
    "chars": 2069,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"cla"
  },
  {
    "path": "apps/client/src/components/ui/card.tsx",
    "chars": 1991,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Card({ className, ...props }: React.Compone"
  },
  {
    "path": "apps/client/src/components/ui/command.tsx",
    "chars": 4886,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { type DialogProps } from \"@radix-ui/react-dialog\";\nimport { Comma"
  },
  {
    "path": "apps/client/src/components/ui/context-menu.tsx",
    "chars": 7406,
    "preview": "import * as React from \"react\"\nimport * as ContextMenuPrimitive from \"@radix-ui/react-context-menu\"\nimport { Check, Chev"
  },
  {
    "path": "apps/client/src/components/ui/dialog.tsx",
    "chars": 3966,
    "preview": "import * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon } from \"lucide"
  },
  {
    "path": "apps/client/src/components/ui/dropdown-menu.tsx",
    "chars": 8247,
    "preview": "import * as React from \"react\";\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { CheckIc"
  },
  {
    "path": "apps/client/src/components/ui/input.tsx",
    "chars": 962,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Input({ className, type, ...props }: React."
  },
  {
    "path": "apps/client/src/components/ui/label.tsx",
    "chars": 597,
    "preview": "import * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\n\nimport { cn } from \"@/lib/utils\""
  },
  {
    "path": "apps/client/src/components/ui/navigation-menu.tsx",
    "chars": 6664,
    "preview": "import * as React from \"react\"\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\"\nimport { cva }"
  },
  {
    "path": "apps/client/src/components/ui/popover.tsx",
    "chars": 1477,
    "preview": "import * as React from \"react\"\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\"\n\nimport { cn } from \"@/lib/ut"
  },
  {
    "path": "apps/client/src/components/ui/resizable.tsx",
    "chars": 1709,
    "preview": "import { GripVertical } from \"lucide-react\"\nimport * as ResizablePrimitive from \"react-resizable-panels\"\n\nimport { cn } "
  },
  {
    "path": "apps/client/src/components/ui/scroll-area.tsx",
    "chars": 1642,
    "preview": "import * as React from \"react\"\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\"\n\nimport { cn } from \"@"
  },
  {
    "path": "apps/client/src/components/ui/select.tsx",
    "chars": 6228,
    "preview": "import * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { CheckIcon, ChevronDo"
  },
  {
    "path": "apps/client/src/components/ui/separator.tsx",
    "chars": 699,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { c"
  },
  {
    "path": "apps/client/src/components/ui/sheet.tsx",
    "chars": 4015,
    "preview": "import * as React from \"react\";\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\n\nimport { cn } from \"@/lib/uti"
  },
  {
    "path": "apps/client/src/components/ui/sidebar.tsx",
    "chars": 21734,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, VariantProps } from \"class-va"
  },
  {
    "path": "apps/client/src/components/ui/skeleton.tsx",
    "chars": 276,
    "preview": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n "
  },
  {
    "path": "apps/client/src/components/ui/sonner.tsx",
    "chars": 584,
    "preview": "import { useTheme } from \"next-themes\"\nimport { Toaster as Sonner, ToasterProps } from \"sonner\"\n\nconst Toaster = ({ ...p"
  },
  {
    "path": "apps/client/src/components/ui/switch.tsx",
    "chars": 1163,
    "preview": "import * as React from \"react\"\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\"\n\nimport { cn } from \"@/lib/util"
  },
  {
    "path": "apps/client/src/components/ui/table.tsx",
    "chars": 2434,
    "preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nfunction Table({ className, ...props }: React.Componen"
  },
  {
    "path": "apps/client/src/components/ui/tabs.tsx",
    "chars": 1870,
    "preview": "import * as React from \"react\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\n\nimport { cn } from \"@/lib/utils\""
  },
  {
    "path": "apps/client/src/components/ui/textarea.tsx",
    "chars": 753,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Textarea({ className, ...props }: React.Com"
  },
  {
    "path": "apps/client/src/components/ui/tooltip.tsx",
    "chars": 1891,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\"\n\nimport { cn } "
  },
  {
    "path": "apps/client/src/contexts/SupabaseAuthContext.tsx",
    "chars": 4620,
    "preview": "import {\n  createContext,\n  useContext,\n  useEffect,\n  useState,\n  useCallback,\n  type ReactNode,\n} from \"react\";\nimport"
  },
  {
    "path": "apps/client/src/entities/configurations/api.ts",
    "chars": 583,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport type { SystemConfiguration, SystemConfigurationPayload } from \"./types\""
  },
  {
    "path": "apps/client/src/entities/configurations/codeService.ts",
    "chars": 338,
    "preview": "import type { SystemConfiguration } from \"./types\";\n\nexport const CODE_SERVICE_CONFIG_KEY = \"code_service_enabled\";\n\nexp"
  },
  {
    "path": "apps/client/src/entities/configurations/store.ts",
    "chars": 1349,
    "preview": "import { create } from \"zustand\";\nimport * as api from \"./api\";\nimport type { SystemConfiguration, SystemConfigurationPa"
  },
  {
    "path": "apps/client/src/entities/configurations/types.ts",
    "chars": 291,
    "preview": "export interface SystemConfiguration {\n  id: number;\n  key: string;\n  value: string;\n  enabled: number;\n  description: s"
  },
  {
    "path": "apps/client/src/entities/custom-nodes/api.ts",
    "chars": 872,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport { CustomNodeDynamicData, CustomNodeFromApi } from \"./types\";\n\nexport co"
  },
  {
    "path": "apps/client/src/entities/custom-nodes/presets.ts",
    "chars": 13432,
    "preview": "export interface PresetConnector {\n  id: string;\n  name: string;\n  type: \"in\" | \"out\";\n}\n\nexport type PresetCategory =\n "
  },
  {
    "path": "apps/client/src/entities/custom-nodes/store.ts",
    "chars": 2133,
    "preview": "import { create } from \"zustand\";\nimport { CustomNodeState } from \"./types\";\nimport * as api from \"./api\";\n\n// const par"
  },
  {
    "path": "apps/client/src/entities/custom-nodes/types.ts",
    "chars": 848,
    "preview": "export interface CustomNodeFromApi {\n  node_type: string;\n  data: CustomNodeDynamicData;\n}\n\nexport type CustomNodeDynami"
  },
  {
    "path": "apps/client/src/entities/device/api.ts",
    "chars": 583,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport type { Device, DevicePayload, DeviceWithEntity } from \"./types\";\n\nexpor"
  },
  {
    "path": "apps/client/src/entities/device/store.ts",
    "chars": 1426,
    "preview": "import { create } from \"zustand\";\nimport * as api from \"./api\";\nimport type { Device, DevicePayload } from \"./types\";\n\ni"
  },
  {
    "path": "apps/client/src/entities/device/types.ts",
    "chars": 413,
    "preview": "import { EntityAll } from \"../entity/types\";\n\nexport interface Device {\n  id: number;\n  device_id: string;\n  name: strin"
  },
  {
    "path": "apps/client/src/entities/device-token/api.ts",
    "chars": 485,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport type { DeviceToken, IssuedTokenResponse } from \"./types\";\n\nexport const"
  },
  {
    "path": "apps/client/src/entities/device-token/store.ts",
    "chars": 1938,
    "preview": "import { create } from \"zustand\";\nimport * as api from \"./api\";\nimport type { DeviceToken } from \"./types\";\n\ninterface D"
  },
  {
    "path": "apps/client/src/entities/device-token/types.ts",
    "chars": 228,
    "preview": "export interface DeviceToken {\n  id: number;\n  device_id: number;\n  expires_at: string | null;\n  last_used_at: string | "
  },
  {
    "path": "apps/client/src/entities/dynamic-dashboard/api.ts",
    "chars": 813,
    "preview": "import { apiClient } from \"@/shared/api\";\n\nexport type DynamicDashboardDto = {\n  id: string;\n  name: string;\n  layout: u"
  },
  {
    "path": "apps/client/src/entities/dynamic-dashboard/interaction.ts",
    "chars": 1110,
    "preview": "/** Contract version for dashboard → flow WebSocket payloads (listener id + broadcast bus). */\nexport const DASHBOARD_CO"
  },
  {
    "path": "apps/client/src/entities/dynamic-dashboard/layoutResolve.ts",
    "chars": 1457,
    "preview": "import type { DashboardGroup, DashboardItem } from \"./store\";\n\n/** Clamp top-left grid position to group bounds (matches"
  },
  {
    "path": "apps/client/src/entities/dynamic-dashboard/store.ts",
    "chars": 15631,
    "preview": "import { create } from \"zustand\";\nimport * as api from \"./api\";\nimport { clampItemPosition, itemsCollide } from \"./layou"
  },
  {
    "path": "apps/client/src/entities/entity/api.ts",
    "chars": 1054,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport type { Entity, EntityAll, EntityPayload, State } from \"./types\";\n\nexpor"
  },
  {
    "path": "apps/client/src/entities/entity/store.ts",
    "chars": 1338,
    "preview": "import { create } from \"zustand\";\nimport * as api from \"./api\";\nimport type { Entity, EntityPayload } from \"./types\";\n\ni"
  },
  {
    "path": "apps/client/src/entities/entity/types.ts",
    "chars": 725,
    "preview": "export interface Entity {\n  id: number;\n  entity_id: string;\n  device_id: number | null;\n  friendly_name: string | null;"
  },
  {
    "path": "apps/client/src/entities/file/api.ts",
    "chars": 1316,
    "preview": "import { DirEntry } from \"./types\";\nimport { apiClient } from \"@/shared/api\";\n\nexport const getDirectoryListing = async "
  },
  {
    "path": "apps/client/src/entities/file/store.ts",
    "chars": 2110,
    "preview": "import { create } from \"zustand\";\nimport { IdeState } from \"./types\";\nimport { getFileContent, updateFileContent } from "
  },
  {
    "path": "apps/client/src/entities/file/types.ts",
    "chars": 402,
    "preview": "export interface DirEntry {\n  name: string;\n  isDir: boolean;\n  path: string;\n}\n\nexport interface IdeState {\n  activeFil"
  },
  {
    "path": "apps/client/src/entities/flow/api.ts",
    "chars": 1002,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport { Flow, FlowPayload, FlowVersion, FlowVersionPayload } from \"./types\";\n"
  },
  {
    "path": "apps/client/src/entities/flow/store.ts",
    "chars": 4523,
    "preview": "import { create } from \"zustand\";\nimport { DataNodeType, Edge, Node } from \"@/features/flow/flowTypes\";\nimport {\n  getFl"
  },
  {
    "path": "apps/client/src/entities/flow/types.ts",
    "chars": 482,
    "preview": "export interface Flow {\n  id: number;\n  name: string;\n  description: string | null;\n  enabled: number;\n  created_at: str"
  },
  {
    "path": "apps/client/src/entities/ha/api.ts",
    "chars": 392,
    "preview": "import { apiClient } from \"@/shared/api\";\n\nimport { HaState } from \"./types\";\n\nexport const fetchAllHaStates = async ():"
  },
  {
    "path": "apps/client/src/entities/ha/store.ts",
    "chars": 550,
    "preview": "import { create } from \"zustand\";\nimport { HaStateStore } from \"./types\";\nimport { fetchAllHaStates } from \"./api\";\n\nexp"
  },
  {
    "path": "apps/client/src/entities/ha/types.ts",
    "chars": 540,
    "preview": "type JsonPrimitive = string | number | boolean | null;\nexport type JsonObject = { [key: string]: JsonValue };\ntype JsonA"
  },
  {
    "path": "apps/client/src/entities/integrations/api.ts",
    "chars": 462,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport type { IntegrationStatus, IntegrationRegisterPayload } from \"./types\";\n"
  },
  {
    "path": "apps/client/src/entities/integrations/store.ts",
    "chars": 1439,
    "preview": "import { create } from \"zustand\";\nimport * as api from \"./api\";\n\ninterface IntegrationState {\n  isHaConnected: boolean;\n"
  },
  {
    "path": "apps/client/src/entities/integrations/types.ts",
    "chars": 253,
    "preview": "export interface IntegrationStatus {\n  home_assistant: { connected: boolean };\n  ros2: { connected: boolean };\n  sdr: { "
  },
  {
    "path": "apps/client/src/entities/log/api.ts",
    "chars": 634,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport { LogContentResponse, LogFileListResponse } from \"./types\";\n\nexport con"
  },
  {
    "path": "apps/client/src/entities/log/types.ts",
    "chars": 133,
    "preview": "export type LogContentResponse = {\n  filename: string;\n  logs: string;\n};\n\nexport type LogFileListResponse = {\n  files: "
  },
  {
    "path": "apps/client/src/entities/map/api.ts",
    "chars": 2056,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport {\n  MapLayer,\n  LayerWithFeatures,\n  LayerPayload,\n  FeaturePayload,\n  "
  },
  {
    "path": "apps/client/src/entities/map/store.ts",
    "chars": 5000,
    "preview": "import { create } from \"zustand\";\nimport * as mapApi from \"./api\";\nimport {\n  MapDataState,\n  MapInteractionState,\n  Fea"
  },
  {
    "path": "apps/client/src/entities/map/types.ts",
    "chars": 3121,
    "preview": "import { LatLng } from \"leaflet\";\n\nexport interface MapVertex {\n  id: number;\n  feature_id: number;\n  latitude: number;\n"
  },
  {
    "path": "apps/client/src/entities/permission/api.ts",
    "chars": 162,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport { Permission } from \"./types\";\n\nexport const getPermissions = () => api"
  },
  {
    "path": "apps/client/src/entities/permission/store.ts",
    "chars": 867,
    "preview": "import { create } from \"zustand\";\nimport { getPermissions } from \"./api\";\nimport { Permission } from \"./types\";\n\ntype Pe"
  },
  {
    "path": "apps/client/src/entities/permission/types.ts",
    "chars": 90,
    "preview": "export type Permission = {\n  id: number;\n  name: string;\n  description: string | null;\n};\n"
  },
  {
    "path": "apps/client/src/entities/recording/api.ts",
    "chars": 2293,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport {\n  Recording,\n  StartRecordingRequest,\n  StartRecordingResponse,\n  Act"
  },
  {
    "path": "apps/client/src/entities/recording/index.ts",
    "chars": 93,
    "preview": "export * from \"./types\";\nexport * from \"./api\";\nexport { useRecordingStore } from \"./store\";\n"
  },
  {
    "path": "apps/client/src/entities/recording/store.ts",
    "chars": 2492,
    "preview": "import { create } from \"zustand\";\nimport * as api from \"./api\";\nimport type { Recording, ActiveRecordingInfo } from \"./t"
  },
  {
    "path": "apps/client/src/entities/recording/types.ts",
    "chars": 743,
    "preview": "export interface Recording {\n  id: number;\n  stream_ssrc: number;\n  topic: string;\n  device_id: string;\n  media_type: \"a"
  },
  {
    "path": "apps/client/src/entities/role/api.ts",
    "chars": 459,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport { Role, CreateRolePayload, UpdateRolePayload } from \"./types\";\n\nexport "
  },
  {
    "path": "apps/client/src/entities/role/store.ts",
    "chars": 1848,
    "preview": "import { create } from \"zustand\";\nimport { getRoles, createRole, updateRole, deleteRole } from \"./api\";\nimport { Role, C"
  },
  {
    "path": "apps/client/src/entities/role/types.ts",
    "chars": 340,
    "preview": "import { Permission } from \"@/entities/permission/types\";\n\nexport type Role = {\n  id: number;\n  name: string;\n  descript"
  },
  {
    "path": "apps/client/src/entities/stat/api.ts",
    "chars": 201,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport { Stat } from \"./types\";\n\nexport const getStats = async (): Promise<Sta"
  },
  {
    "path": "apps/client/src/entities/stat/store.ts",
    "chars": 670,
    "preview": "import { create } from \"zustand\";\nimport * as api from \"./api\";\nimport type { Stat } from \"./types\";\n\ninterface StatStat"
  },
  {
    "path": "apps/client/src/entities/stat/types.ts",
    "chars": 85,
    "preview": "export interface Stat {\n  count: {\n    devices: number;\n    entities: number;\n  };\n}\n"
  },
  {
    "path": "apps/client/src/entities/tunnel/api.ts",
    "chars": 405,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport type {\n  StartTunnelRequest,\n  StartTunnelResponse,\n  TunnelStatus,\n} f"
  },
  {
    "path": "apps/client/src/entities/tunnel/store.ts",
    "chars": 1798,
    "preview": "import { create } from \"zustand\";\nimport * as api from \"./api\";\nimport type { TunnelStatus } from \"./types\";\n\ntype Tunne"
  },
  {
    "path": "apps/client/src/entities/tunnel/types.ts",
    "chars": 295,
    "preview": "export type TunnelStatus = {\n  active: boolean;\n  session_id?: string | null;\n  server?: string | null;\n  target?: strin"
  },
  {
    "path": "apps/client/src/entities/user/api.ts",
    "chars": 720,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport { User, CreateUserPayload, UpdateUserPayload } from \"./types\";\n\nexport "
  },
  {
    "path": "apps/client/src/entities/user/store.ts",
    "chars": 1753,
    "preview": "import { create } from \"zustand\";\nimport { getUsers, createUser, updateUser, deleteUser } from \"./api\";\nimport { User, C"
  },
  {
    "path": "apps/client/src/entities/user/types.ts",
    "chars": 368,
    "preview": "import { Role } from \"../role/types\";\n\nexport type User = {\n  id: number;\n  username: string;\n  email: string;\n  created"
  },
  {
    "path": "apps/client/src/features/account-switcher/index.tsx",
    "chars": 1963,
    "preview": "import * as React from \"react\";\nimport { Check, ChevronsUpDown } from \"lucide-react\";\n\nimport {\n  DropdownMenu,\n  Dropdo"
  },
  {
    "path": "apps/client/src/features/auth/AuthInterceptor.tsx",
    "chars": 969,
    "preview": "import { useEffect } from \"react\";\nimport { Navigate, useLocation } from \"react-router\";\nimport { parseJwt } from \"@/lib"
  },
  {
    "path": "apps/client/src/features/auth/DefaultAdminPasswordDialog.tsx",
    "chars": 3848,
    "preview": "import { useState } from \"react\";\nimport { toast } from \"sonner\";\n\nimport { Button } from \"@/components/ui/button\";\nimpo"
  },
  {
    "path": "apps/client/src/features/auth/api.ts",
    "chars": 1310,
    "preview": "import { apiClient } from \"@/shared/api\";\nimport { User } from \"@/entities/user/types\";\n\nexport type AuthCredentials = {"
  },
  {
    "path": "apps/client/src/features/auth/hook.ts",
    "chars": 371,
    "preview": "import { useCallback } from \"react\";\nimport { useNavigate } from \"react-router\";\nimport { storage } from \"@/lib/storage\""
  },
  {
    "path": "apps/client/src/features/auth/index.tsx",
    "chars": 13191,
    "preview": "import { useEffect, useState } from \"react\";\nimport { toast } from \"sonner\";\n\nimport { cn } from \"@/lib/utils\";\nimport {"
  },
  {
    "path": "apps/client/src/features/code/CreateItemDialog.tsx",
    "chars": 1757,
    "preview": "import React, { useState } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogHeader,\n  DialogTitle,\n  DialogFoot"
  },
  {
    "path": "apps/client/src/features/code/FileEditor.tsx",
    "chars": 2410,
    "preview": "import { Button } from \"@/components/ui/button\";\nimport { useIdeStore } from \"@/entities/file/store\";\nimport Editor, { l"
  },
  {
    "path": "apps/client/src/features/code/FileTree.tsx",
    "chars": 9069,
    "preview": "import React, { useState, useEffect, useRef } from \"react\";\nimport {\n  ChevronRight,\n  Folder,\n  File as FileIcon,\n  Loa"
  },
  {
    "path": "apps/client/src/features/configurations/ConfigurationActionButton.tsx",
    "chars": 5402,
    "preview": "import { useState } from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\nimport { MoreHorizontal, Pencil"
  },
  {
    "path": "apps/client/src/features/configurations/ConfigurationCreate.tsx",
    "chars": 2924,
    "preview": "import { Controller, useForm } from \"react-hook-form\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Dialo"
  },
  {
    "path": "apps/client/src/features/configurations/ConfigurationCreateButton.tsx",
    "chars": 553,
    "preview": "import { useState } from \"react\";\nimport { ConfigurationCreate } from \"./ConfigurationCreate\";\nimport { Button } from \"@"
  },
  {
    "path": "apps/client/src/features/darkmode/mode-toggle.tsx",
    "chars": 1216,
    "preview": "import { Moon, Sun } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n  DropdownMenu,\n  D"
  },
  {
    "path": "apps/client/src/features/dashboard-swipe/DashboardSwipeHeader.tsx",
    "chars": 11142,
    "preview": "import {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  Aler"
  },
  {
    "path": "apps/client/src/features/dashboard-swipe/DashboardSwipeLayout.tsx",
    "chars": 10203,
    "preview": "import { Footer } from \"@/features/footer\";\nimport { AppSidebar } from \"@/features/sidebar\";\nimport { WebRTCProvider } f"
  },
  {
    "path": "apps/client/src/features/device/DeviceCreateButton.tsx",
    "chars": 2399,
    "preview": "import { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { Button } from \"@/components/ui/but"
  },
  {
    "path": "apps/client/src/features/device/DeviceDeleteButton.tsx",
    "chars": 1841,
    "preview": "import { useState } from \"react\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent"
  },
  {
    "path": "apps/client/src/features/device/DeviceKeyButton.tsx",
    "chars": 1116,
    "preview": "import { useState } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader,\n  DialogTitle"
  },
  {
    "path": "apps/client/src/features/device/DeviceUpdateButton.tsx",
    "chars": 2765,
    "preview": "import { useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { Button } from \"@/components/ui/but"
  },
  {
    "path": "apps/client/src/features/device-token/DeviceTokenManager.tsx",
    "chars": 5027,
    "preview": "import { useEffect, useState } from \"react\";\nimport { Copy, Check, Key, AlertTriangle, RefreshCw } from \"lucide-react\";\n"
  },
  {
    "path": "apps/client/src/features/dynamic-dashboard/GroupCanvas.tsx",
    "chars": 33745,
    "preview": "import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { PointerEvent as ReactPointerEve"
  },
  {
    "path": "apps/client/src/features/dynamic-dashboard/events/dispatcher.test.ts",
    "chars": 2037,
    "preview": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { createDashboardEventDispatcher } from"
  },
  {
    "path": "apps/client/src/features/dynamic-dashboard/events/dispatcher.ts",
    "chars": 3371,
    "preview": "import {\n  DASHBOARD_COMPONENT_EVENT_VERSION,\n  type DashboardComponentEventPayload,\n  type DashboardComponentAction,\n  "
  },
  {
    "path": "apps/client/src/features/dynamic-dashboard/panels/ButtonPanel.tsx",
    "chars": 1753,
    "preview": "import { useEffect, useState } from \"react\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/com"
  },
  {
    "path": "apps/client/src/features/dynamic-dashboard/panels/FlowPanel.tsx",
    "chars": 4774,
    "preview": "import { useEffect, useMemo, useState } from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } f"
  },
  {
    "path": "apps/client/src/features/dynamic-dashboard/panels/MapPanel.tsx",
    "chars": 4743,
    "preview": "import { useEffect, useMemo, useState } from \"react\";\nimport { MapContainer, TileLayer, useMap } from \"react-leaflet\";\ni"
  },
  {
    "path": "apps/client/src/features/entity/AllEntities.tsx",
    "chars": 613,
    "preview": "import { Fragment } from \"react\";\n// import { EntityAll } from \"@/entities/entity/types\";\nimport { EntityCard } from \"./"
  },
  {
    "path": "apps/client/src/features/entity/AnalyzeMenuItem.tsx",
    "chars": 1554,
    "preview": "import { useState } from \"react\";\nimport { Eye, Loader2 } from \"lucide-react\";\nimport { DropdownMenuItem } from \"@/compo"
  },
  {
    "path": "apps/client/src/features/entity/Card.tsx",
    "chars": 8582,
    "preview": "import { useState } from \"react\";\nimport { EntityAll } from \"@/entities/entity/types\";\nimport {\n  Card,\n  CardHeader,\n  "
  },
  {
    "path": "apps/client/src/features/entity/EntityCreateButton.tsx",
    "chars": 6378,
    "preview": "import { useEffect, useState } from \"react\";\nimport { Controller, useForm } from \"react-hook-form\";\n\nimport { Button } f"
  },
  {
    "path": "apps/client/src/features/entity/EntityDeleteButton.tsx",
    "chars": 1822,
    "preview": "import { useState } from \"react\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent"
  },
  {
    "path": "apps/client/src/features/entity/EntityUpdateButton.tsx",
    "chars": 6595,
    "preview": "import { useEffect, useState } from \"react\";\nimport { useForm, Controller } from \"react-hook-form\";\nimport { Button } fr"
  },
  {
    "path": "apps/client/src/features/entity/SelectPlatforms.tsx",
    "chars": 332,
    "preview": "import { SelectItem } from \"@/components/ui/select\";\n\nexport function EntitySelectPlatforms() {\n  return (\n    <>\n      "
  },
  {
    "path": "apps/client/src/features/entity/SelectTypes.tsx",
    "chars": 572,
    "preview": "import { SelectItem } from \"@/components/ui/select\";\nimport { Baseline, Database, Locate, Play } from \"lucide-react\";\n\ne"
  },
  {
    "path": "apps/client/src/features/entity/StateHistorySheet.tsx",
    "chars": 8332,
    "preview": "import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { scaleTime } from \"d3-scale\";\nimport {\n  Sheet,\n  "
  },
  {
    "path": "apps/client/src/features/entity/useEntitiesData.ts",
    "chars": 2287,
    "preview": "import { useCallback, useEffect, useState } from \"react\";\nimport * as api from \"@/entities/entity/api\";\nimport { EntityA"
  },
  {
    "path": "apps/client/src/features/error/index.tsx",
    "chars": 734,
    "preview": "import { Button } from \"@/components/ui/button\";\n\nexport function ErrorRender() {\n  return (\n    <div className='bg-back"
  },
  {
    "path": "apps/client/src/features/flow/AddCustomNode.tsx",
    "chars": 13428,
    "preview": "import { useEffect, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Dialog,\n  Dialo"
  },
  {
    "path": "apps/client/src/features/flow/Flow.tsx",
    "chars": 11233,
    "preview": "import { useEffect, useState } from \"react\";\nimport { Graph } from \"./Graph\";\nimport { Button } from \"@/components/ui/bu"
  },
  {
    "path": "apps/client/src/features/flow/Graph.tsx",
    "chars": 26088,
    "preview": "import { useState, useRef, useEffect, useCallback, useMemo } from \"react\";\nimport * as d3 from \"d3\";\nimport { Plus, Minu"
  },
  {
    "path": "apps/client/src/features/flow/Options.tsx",
    "chars": 8793,
    "preview": "import { useState, useEffect, useCallback } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Inpu"
  },
  {
    "path": "apps/client/src/features/flow/OptionsVariation.tsx",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "apps/client/src/features/flow/RunFlow.tsx",
    "chars": 2853,
    "preview": "import { Button } from \"@/components/ui/button\";\nimport { useFlowStore } from \"@/entities/flow/store\";\nimport { Play, Sq"
  },
  {
    "path": "apps/client/src/features/flow/SelectedItemActions.tsx",
    "chars": 891,
    "preview": "import { Button } from \"@/components/ui/button\";\nimport { Trash2 } from \"lucide-react\";\n\ntype SelectedElement = {\n  type"
  },
  {
    "path": "apps/client/src/features/flow/flow-chat/buildSystemPrompt.ts",
    "chars": 2874,
    "preview": "import type { Node, Edge } from \"@/features/flow/flowTypes\";\nimport type { Flow } from \"@/entities/flow/types\";\nimport {"
  },
  {
    "path": "apps/client/src/features/flow/flow-chat/executeToolCalls.ts",
    "chars": 6505,
    "preview": "import type { ToolCallResult } from \"@vessel/capsule-client\";\nimport type { Node, Edge, DataNodeType } from \"@/features/"
  },
  {
    "path": "apps/client/src/features/flow/flow-chat/flowTools.ts",
    "chars": 5754,
    "preview": "import type { Tool } from \"@vessel/capsule-client\";\n\nexport const FLOW_TOOLS: Tool[] = [\n  {\n    type: \"function\",\n    f"
  },
  {
    "path": "apps/client/src/features/flow/flow-chat/index.ts",
    "chars": 225,
    "preview": "export { buildFlowSystemPrompt } from \"./buildSystemPrompt\";\nexport { FLOW_TOOLS } from \"./flowTools\";\nexport { executeF"
  },
  {
    "path": "apps/client/src/features/flow/flowNode.ts",
    "chars": 7942,
    "preview": "import { Node } from \"./flowTypes\";\n\nexport type DefaultValueType = {\n  connectors: Node[\"connectors\"];\n  nodeType: stri"
  },
  {
    "path": "apps/client/src/features/flow/flowTypes.ts",
    "chars": 3040,
    "preview": "import { DEFINITION_NODE } from \"./flowNode\";\n\nexport type Connector = {\n  id: string;\n  name: string;\n  type: \"in\" | \"o"
  },
  {
    "path": "apps/client/src/features/flow/flowUtils.ts",
    "chars": 2930,
    "preview": "import { CustomNode, CustomNodeDynamicData } from \"@/entities/custom-nodes/types\";\nimport { DEFINITION_NODE } from \"./fl"
  },
  {
    "path": "apps/client/src/features/flow/nodes/ButtonNode.tsx",
    "chars": 794,
    "preview": "import { Node } from \"../flowTypes\";\n\nexport function renderButtonNode(\n  g: d3.Selection<SVGGElement, Node, null, undef"
  },
  {
    "path": "apps/client/src/features/flow/nodes/CalcNode.tsx",
    "chars": 856,
    "preview": "import { CalculationNodeType, Node } from \"../flowTypes\";\n\nexport function renderCalcNode(\n  g: d3.Selection<SVGGElement"
  },
  {
    "path": "apps/client/src/features/flow/nodes/HttpNode.tsx",
    "chars": 854,
    "preview": "import { HTTPRequestNodeType, Node } from \"../flowTypes\";\n\nexport function renderHttpNode(\n  g: d3.Selection<SVGGElement"
  },
  {
    "path": "apps/client/src/features/flow/nodes/IntervalNode.tsx",
    "chars": 859,
    "preview": "import { IntervalNodeType, Node } from \"../flowTypes\";\n\nexport function renderIntervalNode(\n  g: d3.Selection<SVGGElemen"
  },
  {
    "path": "apps/client/src/features/flow/nodes/LogicNode.tsx",
    "chars": 857,
    "preview": "import { LogicOpetatorNodeType, Node } from \"../flowTypes\";\n\nexport function renderLogicNode(\n  g: d3.Selection<SVGGElem"
  },
  {
    "path": "apps/client/src/features/flow/nodes/LoopNode.tsx",
    "chars": 850,
    "preview": "import { LoopNodeType, Node } from \"../flowTypes\";\n\nexport function renderLoopNode(\n  g: d3.Selection<SVGGElement, Node,"
  },
  {
    "path": "apps/client/src/features/flow/nodes/MQTTNode.tsx",
    "chars": 1088,
    "preview": "import { MqttPublishNodeType, Node } from \"../flowTypes\";\n\nfunction mqttLikeCenterLabel(d: Node): string {\n  if (d.nodeT"
  },
  {
    "path": "apps/client/src/features/flow/nodes/NumberNode.tsx",
    "chars": 864,
    "preview": "import { Node, NumberNodeType } from \"../flowTypes\";\n\nexport function renderNumberNode(\n  g: d3.Selection<SVGGElement, N"
  },
  {
    "path": "apps/client/src/features/flow/nodes/ProcessingNode.tsx",
    "chars": 460,
    "preview": "import { Node } from \"../flowTypes\";\n\nexport function renderProcessingNode(\n  g: d3.Selection<SVGGElement, Node, null, u"
  },
  {
    "path": "apps/client/src/features/flow/nodes/TitleNode.tsx",
    "chars": 436,
    "preview": "import { Node } from \"../flowTypes\";\n\nexport function renderTitleNode(\n  g: d3.Selection<SVGGElement, Node, null, undefi"
  },
  {
    "path": "apps/client/src/features/flow/nodes/VarNode.tsx",
    "chars": 793,
    "preview": "import { Node } from \"../flowTypes\";\n\nexport function renderVarNode(\n  g: d3.Selection<SVGGElement, Node, null, undefine"
  },
  {
    "path": "apps/client/src/features/flow-log/FlowLog.tsx",
    "chars": 3117,
    "preview": "import { useEffect, useState, useRef, useCallback } from \"react\";\nimport { X, ChevronDown, ChevronUp, Terminal } from \"l"
  },
  {
    "path": "apps/client/src/features/footer/index.tsx",
    "chars": 2787,
    "preview": "import { useState, useEffect, useCallback } from \"react\";\nimport { useWebSocket, useWebSocketMessage } from \"../ws/WebSo"
  },
  {
    "path": "apps/client/src/features/gps/parseGps.ts",
    "chars": 459,
    "preview": "export const parseGpsState = (\n  state: string | null | undefined,\n): [number, number] | null => {\n  if (!state) return "
  },
  {
    "path": "apps/client/src/features/ha/HaEntitiesTable.tsx",
    "chars": 2087,
    "preview": "import {\n  Card,\n  CardContent,\n  CardHeader,\n  CardTitle,\n  CardDescription,\n} from \"@/components/ui/card\";\nimport {\n  "
  }
]

// ... and 375 more files (download for full content)

About this extraction

This page contains the full source code of the cartesiancs/vessel GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 575 files (1.8 MB), approximately 459.9k tokens, and a symbol index with 1756 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!