Full Code of hackerai-tech/hackerai for AI

main 3509f2f406d3 cached
772 files
4.6 MB
1.3M tokens
2099 symbols
1 requests
Download .txt
Showing preview only (5,029K chars total). Download the full file or copy to clipboard to get everything.
Repository: hackerai-tech/hackerai
Branch: main
Commit: 3509f2f406d3
Files: 772
Total size: 4.6 MB

Directory structure:
gitextract_6kt6d70a/

├── .agents/
│   └── skills/
│       ├── logging-best-practices/
│       │   └── SKILL.md
│       ├── trigger-agents/
│       │   ├── SKILL.md
│       │   └── references/
│       │       ├── ai-tool.md
│       │       ├── orchestration.md
│       │       ├── streaming.md
│       │       └── waitpoints.md
│       ├── trigger-config/
│       │   ├── SKILL.md
│       │   └── references/
│       │       └── config.md
│       ├── trigger-cost-savings/
│       │   ├── SKILL.md
│       │   └── references/
│       │       └── cost-reduction.md
│       ├── trigger-realtime/
│       │   ├── SKILL.md
│       │   └── references/
│       │       └── realtime.md
│       ├── trigger-setup/
│       │   ├── SKILL.md
│       │   └── references/
│       │       ├── environment-setup.md
│       │       └── project-structure.md
│       ├── trigger-tasks/
│       │   ├── SKILL.md
│       │   └── references/
│       │       ├── advanced-tasks.md
│       │       ├── basic-tasks.md
│       │       └── scheduled-tasks.md
│       ├── vercel-composition-patterns/
│       │   ├── AGENTS.md
│       │   ├── SKILL.md
│       │   └── rules/
│       │       ├── architecture-avoid-boolean-props.md
│       │       ├── architecture-compound-components.md
│       │       ├── patterns-children-over-render-props.md
│       │       ├── patterns-explicit-variants.md
│       │       ├── react19-no-forwardref.md
│       │       ├── state-context-interface.md
│       │       ├── state-decouple-implementation.md
│       │       └── state-lift-state.md
│       ├── vercel-react-best-practices/
│       │   ├── AGENTS.md
│       │   ├── SKILL.md
│       │   └── rules/
│       │       ├── advanced-event-handler-refs.md
│       │       ├── advanced-init-once.md
│       │       ├── advanced-use-latest.md
│       │       ├── async-api-routes.md
│       │       ├── async-defer-await.md
│       │       ├── async-dependencies.md
│       │       ├── async-parallel.md
│       │       ├── async-suspense-boundaries.md
│       │       ├── bundle-barrel-imports.md
│       │       ├── bundle-conditional.md
│       │       ├── bundle-defer-third-party.md
│       │       ├── bundle-dynamic-imports.md
│       │       ├── bundle-preload.md
│       │       ├── client-event-listeners.md
│       │       ├── client-localstorage-schema.md
│       │       ├── client-passive-event-listeners.md
│       │       ├── client-swr-dedup.md
│       │       ├── js-batch-dom-css.md
│       │       ├── js-cache-function-results.md
│       │       ├── js-cache-property-access.md
│       │       ├── js-cache-storage.md
│       │       ├── js-combine-iterations.md
│       │       ├── js-early-exit.md
│       │       ├── js-hoist-regexp.md
│       │       ├── js-index-maps.md
│       │       ├── js-length-check-first.md
│       │       ├── js-min-max-loop.md
│       │       ├── js-set-map-lookups.md
│       │       ├── js-tosorted-immutable.md
│       │       ├── rendering-activity.md
│       │       ├── rendering-animate-svg-wrapper.md
│       │       ├── rendering-conditional-render.md
│       │       ├── rendering-content-visibility.md
│       │       ├── rendering-hoist-jsx.md
│       │       ├── rendering-hydration-no-flicker.md
│       │       ├── rendering-hydration-suppress-warning.md
│       │       ├── rendering-svg-precision.md
│       │       ├── rendering-usetransition-loading.md
│       │       ├── rerender-defer-reads.md
│       │       ├── rerender-dependencies.md
│       │       ├── rerender-derived-state-no-effect.md
│       │       ├── rerender-derived-state.md
│       │       ├── rerender-functional-setstate.md
│       │       ├── rerender-lazy-state-init.md
│       │       ├── rerender-memo-with-default-value.md
│       │       ├── rerender-memo.md
│       │       ├── rerender-move-effect-to-event.md
│       │       ├── rerender-simple-expression-in-memo.md
│       │       ├── rerender-transitions.md
│       │       ├── rerender-use-ref-transient-values.md
│       │       ├── server-after-nonblocking.md
│       │       ├── server-auth-actions.md
│       │       ├── server-cache-lru.md
│       │       ├── server-cache-react.md
│       │       ├── server-dedup-props.md
│       │       ├── server-parallel-fetching.md
│       │       └── server-serialization.md
│       └── web-design-guidelines/
│           └── SKILL.md
├── .claude/
│   └── agents/
│       └── trigger-dev-task-writer.md
├── .cursor/
│   └── rules/
│       ├── convex_rules.mdc
│       ├── trigger.advanced-tasks.mdc
│       ├── trigger.basic.mdc
│       ├── trigger.config.mdc
│       ├── trigger.realtime.mdc
│       └── trigger.scheduled-tasks.mdc
├── .env.e2e.example
├── .env.local.example
├── .github/
│   ├── skills/
│   │   ├── vercel-react-best-practices/
│   │   │   ├── AGENTS.md
│   │   │   ├── README.md
│   │   │   ├── SKILL.md
│   │   │   ├── metadata.json
│   │   │   └── rules/
│   │   │       ├── _sections.md
│   │   │       ├── _template.md
│   │   │       ├── advanced-event-handler-refs.md
│   │   │       ├── advanced-use-latest.md
│   │   │       ├── async-api-routes.md
│   │   │       ├── async-defer-await.md
│   │   │       ├── async-dependencies.md
│   │   │       ├── async-parallel.md
│   │   │       ├── async-suspense-boundaries.md
│   │   │       ├── bundle-barrel-imports.md
│   │   │       ├── bundle-conditional.md
│   │   │       ├── bundle-defer-third-party.md
│   │   │       ├── bundle-dynamic-imports.md
│   │   │       ├── bundle-preload.md
│   │   │       ├── client-event-listeners.md
│   │   │       ├── client-swr-dedup.md
│   │   │       ├── js-batch-dom-css.md
│   │   │       ├── js-cache-function-results.md
│   │   │       ├── js-cache-property-access.md
│   │   │       ├── js-cache-storage.md
│   │   │       ├── js-combine-iterations.md
│   │   │       ├── js-early-exit.md
│   │   │       ├── js-hoist-regexp.md
│   │   │       ├── js-index-maps.md
│   │   │       ├── js-length-check-first.md
│   │   │       ├── js-min-max-loop.md
│   │   │       ├── js-set-map-lookups.md
│   │   │       ├── js-tosorted-immutable.md
│   │   │       ├── rendering-activity.md
│   │   │       ├── rendering-animate-svg-wrapper.md
│   │   │       ├── rendering-conditional-render.md
│   │   │       ├── rendering-content-visibility.md
│   │   │       ├── rendering-hoist-jsx.md
│   │   │       ├── rendering-hydration-no-flicker.md
│   │   │       ├── rendering-svg-precision.md
│   │   │       ├── rerender-defer-reads.md
│   │   │       ├── rerender-dependencies.md
│   │   │       ├── rerender-derived-state.md
│   │   │       ├── rerender-functional-setstate.md
│   │   │       ├── rerender-lazy-state-init.md
│   │   │       ├── rerender-memo.md
│   │   │       ├── rerender-transitions.md
│   │   │       ├── server-after-nonblocking.md
│   │   │       ├── server-cache-lru.md
│   │   │       ├── server-cache-react.md
│   │   │       ├── server-parallel-fetching.md
│   │   │       └── server-serialization.md
│   │   └── web-design-guidelines/
│   │       └── SKILL.md
│   └── workflows/
│       ├── desktop-build.yml
│       ├── docker-sandbox.yml
│       └── test.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── LICENSE
├── README.md
├── __mocks__/
│   ├── @aws-sdk/
│   │   ├── client-s3.ts
│   │   └── s3-request-presigner.ts
│   ├── @upstash/
│   │   ├── ratelimit.ts
│   │   └── redis.ts
│   ├── convex/
│   │   └── browser.ts
│   ├── convex-react.ts
│   ├── franc-min.ts
│   ├── jose.ts
│   ├── next/
│   │   └── navigation.ts
│   ├── react-hotkeys-hook.ts
│   ├── react-markdown.tsx
│   ├── react-shiki.tsx
│   ├── shiki.ts
│   ├── streamdown.tsx
│   ├── stripe.ts
│   ├── use-stick-to-bottom.ts
│   ├── uuid.ts
│   ├── workos-authkit.ts
│   ├── workos-node.ts
│   └── workos.ts
├── app/
│   ├── (chat)/
│   │   ├── c/
│   │   │   └── [id]/
│   │   │       └── page.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── api/
│   │   ├── agent/
│   │   │   └── route.ts
│   │   ├── agent-long/
│   │   │   ├── cancel/
│   │   │   │   └── route.ts
│   │   │   ├── resume/
│   │   │   │   └── route.ts
│   │   │   └── route.ts
│   │   ├── auth/
│   │   │   └── desktop-callback/
│   │   │       └── route.ts
│   │   ├── chat/
│   │   │   ├── [id]/
│   │   │   │   └── stream/
│   │   │   │       └── route.ts
│   │   │   └── route.ts
│   │   ├── clear-auth-cookies/
│   │   │   └── route.ts
│   │   ├── delete-account/
│   │   │   └── route.ts
│   │   ├── delete-sandboxes/
│   │   │   └── route.ts
│   │   ├── entitlements/
│   │   │   └── route.ts
│   │   ├── extra-usage/
│   │   │   ├── confirm/
│   │   │   │   └── route.ts
│   │   │   └── webhook/
│   │   │       └── route.ts
│   │   ├── fraud/
│   │   │   └── webhook/
│   │   │       └── route.ts
│   │   ├── logout-all/
│   │   │   └── route.ts
│   │   ├── mfa/
│   │   │   ├── delete/
│   │   │   │   └── route.ts
│   │   │   ├── enroll/
│   │   │   │   └── route.ts
│   │   │   ├── factors/
│   │   │   │   └── route.ts
│   │   │   └── verify/
│   │   │       └── route.ts
│   │   ├── migrate-pentestgpt/
│   │   │   └── route.ts
│   │   ├── sandbox/
│   │   │   └── presence/
│   │   │       └── route.ts
│   │   ├── stripe.ts
│   │   ├── subscribe/
│   │   │   └── route.ts
│   │   ├── subscription/
│   │   │   └── webhook/
│   │   │       └── route.ts
│   │   ├── subscription-details/
│   │   │   └── route.ts
│   │   ├── team/
│   │   │   ├── extra-usage/
│   │   │   │   ├── confirm/
│   │   │   │   │   └── route.ts
│   │   │   │   ├── members/
│   │   │   │   │   └── [userId]/
│   │   │   │   │       └── route.ts
│   │   │   │   ├── purchase/
│   │   │   │   │   └── route.ts
│   │   │   │   ├── route.ts
│   │   │   │   └── webhook/
│   │   │   │       └── route.ts
│   │   │   ├── invite/
│   │   │   │   └── route.ts
│   │   │   ├── members/
│   │   │   │   └── route.ts
│   │   │   ├── seats/
│   │   │   │   └── route.ts
│   │   │   └── team-auth.ts
│   │   └── workos.ts
│   ├── auth-error/
│   │   ├── auto-retry-button.tsx
│   │   └── page.tsx
│   ├── callback/
│   │   └── route.ts
│   ├── components/
│   │   ├── AccountTab.tsx
│   │   ├── AgentsTab.tsx
│   │   ├── AllFilesDialog.tsx
│   │   ├── AttachmentButton.tsx
│   │   ├── BillingFrequencySelector.tsx
│   │   ├── BranchIndicator.tsx
│   │   ├── CancelSubscriptionDialog.tsx
│   │   ├── ChatHeader.tsx
│   │   ├── ChatInput/
│   │   │   ├── AgentUpgradeDialog.tsx
│   │   │   ├── ChatInput.tsx
│   │   │   ├── ChatInputTextarea.tsx
│   │   │   ├── ChatInputToolbar.tsx
│   │   │   ├── ChatModeSelector.tsx
│   │   │   ├── ModeSelectorMenu/
│   │   │   │   ├── ModeOptionItem.tsx
│   │   │   │   ├── ModeSelectorContent.tsx
│   │   │   │   ├── ModeSelectorTrigger.tsx
│   │   │   │   └── index.ts
│   │   │   ├── SubmitStopButton.tsx
│   │   │   └── index.ts
│   │   ├── ChatItem.tsx
│   │   ├── ChatLayout.tsx
│   │   ├── CodeHighlight.tsx
│   │   ├── ComputerCodeBlock.tsx
│   │   ├── ComputerSidebar.tsx
│   │   ├── ContextUsageIndicator.tsx
│   │   ├── ConvexErrorBoundary.tsx
│   │   ├── CustomizeHackerAIDialog.tsx
│   │   ├── DataControlsTab.tsx
│   │   ├── DataStreamProvider.tsx
│   │   ├── DeleteAccountDialog.tsx
│   │   ├── DeleteMfaFactorDialog.tsx
│   │   ├── DiffView.tsx
│   │   ├── DragDropOverlay.tsx
│   │   ├── ExtraUsageSection.tsx
│   │   ├── FeedbackInput.tsx
│   │   ├── FilePartRenderer.tsx
│   │   ├── FileUploadPreview.tsx
│   │   ├── FinishReasonNotice.tsx
│   │   ├── Footer.tsx
│   │   ├── HackingSuggestions.tsx
│   │   ├── Header.tsx
│   │   ├── ImageViewer.tsx
│   │   ├── ManageNotesDialog.tsx
│   │   ├── ManageSharedChatsDialog.tsx
│   │   ├── MarkdownTable.tsx
│   │   ├── MemoizedMarkdown.tsx
│   │   ├── MessageActions.tsx
│   │   ├── MessageEditor.tsx
│   │   ├── MessageErrorState.tsx
│   │   ├── MessageItem.tsx
│   │   ├── MessagePartHandler.tsx
│   │   ├── MessageSearchDialog.tsx
│   │   ├── Messages.tsx
│   │   ├── MfaVerificationDialog.tsx
│   │   ├── MigratePentestgptDialog.tsx
│   │   ├── ModelSelector/
│   │   │   ├── CostIndicator.tsx
│   │   │   ├── __tests__/
│   │   │   │   └── options-drift.test.ts
│   │   │   ├── constants.ts
│   │   │   └── icons.tsx
│   │   ├── ModelSelector.tsx
│   │   ├── PersonalizationTab.tsx
│   │   ├── PricingDialog.tsx
│   │   ├── QueuedMessagesPanel.tsx
│   │   ├── RateLimitWarning.tsx
│   │   ├── ReasoningHandler.tsx
│   │   ├── RemoteControlTab.tsx
│   │   ├── SandboxSelector.tsx
│   │   ├── ScrollToBottomButton.tsx
│   │   ├── SecurityTab.tsx
│   │   ├── SettingsDialog.tsx
│   │   ├── ShareDialog.tsx
│   │   ├── SharedLinksTab.tsx
│   │   ├── Sidebar.tsx
│   │   ├── SidebarHeader.tsx
│   │   ├── SidebarHistory.tsx
│   │   ├── SidebarUserNav.tsx
│   │   ├── SourcesDialog.tsx
│   │   ├── TeamDialogs.tsx
│   │   ├── TeamExtraUsageSection.tsx
│   │   ├── TeamMembersList.tsx
│   │   ├── TeamPricingDialog.tsx
│   │   ├── TeamTab.tsx
│   │   ├── TerminalCodeBlock.tsx
│   │   ├── TodoPanel.tsx
│   │   ├── UpgradeConfirmationDialog.tsx
│   │   ├── UsageTab.tsx
│   │   ├── XtermRenderer.tsx
│   │   ├── __mocks__/
│   │   │   └── DataStreamProvider.tsx
│   │   ├── __tests__/
│   │   │   ├── ChatInput.integration.test.tsx
│   │   │   ├── CodeHighlight.test.tsx
│   │   │   ├── ContextUsageIndicator.test.tsx
│   │   │   ├── FinishReasonNotice.test.tsx
│   │   │   ├── MessageItem.worked-for.test.tsx
│   │   │   ├── RemoteControlTab.test.tsx
│   │   │   ├── ShareDialog.test.tsx
│   │   │   ├── UpgradeConfirmationDialog.test.tsx
│   │   │   ├── chat.integration.test.tsx
│   │   │   └── worked-for-parts.test.ts
│   │   ├── chat.tsx
│   │   ├── computer-sidebar-utils.tsx
│   │   ├── extra-usage/
│   │   │   ├── AdjustSpendingLimitDialog.tsx
│   │   │   ├── AutoReloadDialog.tsx
│   │   │   ├── BuyExtraUsageDialog.tsx
│   │   │   ├── ExtraUsagePurchaseToast.tsx
│   │   │   ├── TurnOffExtraUsageDialog.tsx
│   │   │   └── index.ts
│   │   ├── testUtils.tsx
│   │   ├── tools/
│   │   │   ├── FileHandler.tsx
│   │   │   ├── FileToolsHandler.tsx
│   │   │   ├── GetTerminalFilesHandler.tsx
│   │   │   ├── HttpRequestToolHandler.tsx
│   │   │   ├── NotesToolHandler.tsx
│   │   │   ├── ProxyToolHandler.tsx
│   │   │   ├── SummarizationHandler.tsx
│   │   │   ├── TerminalToolHandler.tsx
│   │   │   ├── TodoToolHandler.tsx
│   │   │   ├── WebToolHandler.tsx
│   │   │   ├── __tests__/
│   │   │   │   └── proxy-formatters.test.ts
│   │   │   ├── notes-tool-utils.tsx
│   │   │   └── shell-tool-utils.ts
│   │   ├── usage/
│   │   │   ├── IncludedUsageCard.tsx
│   │   │   ├── OnDemandUsageCard.tsx
│   │   │   ├── TokenBreakdownTooltip.tsx
│   │   │   └── UsageLogsTable.tsx
│   │   └── worked-for-parts.ts
│   ├── contexts/
│   │   ├── FileUrlCacheContext.tsx
│   │   ├── GlobalState.tsx
│   │   └── TodoBlockContext.tsx
│   ├── desktop-callback/
│   │   └── route.ts
│   ├── desktop-login/
│   │   └── route.ts
│   ├── download/
│   │   ├── DownloadPageContent.tsx
│   │   ├── DownloadSection.tsx
│   │   ├── constants.ts
│   │   ├── icons/
│   │   │   ├── AndroidIcon.tsx
│   │   │   ├── AppleIcon.tsx
│   │   │   ├── DeviceIcon.tsx
│   │   │   ├── DownloadIcon.tsx
│   │   │   ├── LinuxIcon.tsx
│   │   │   ├── WindowsIcon.tsx
│   │   │   └── index.ts
│   │   └── page.tsx
│   ├── globals.css
│   ├── hooks/
│   │   ├── __tests__/
│   │   │   ├── useAutoContinue.test.ts
│   │   │   ├── useFileUpload.local-desktop.test.tsx
│   │   │   ├── useImageUrlCache.test.ts
│   │   │   └── useToolSidebar.test.tsx
│   │   ├── useAutoContinue.ts
│   │   ├── useAutoResume.ts
│   │   ├── useChatHandlers.ts
│   │   ├── useChats.ts
│   │   ├── useDocumentDragAndDrop.ts
│   │   ├── useFeedback.ts
│   │   ├── useFileUpload.ts
│   │   ├── useFileUrlCache.ts
│   │   ├── useLatestRef.ts
│   │   ├── useMessageScroll.ts
│   │   ├── usePentestgptMigration.ts
│   │   ├── usePricingDialog.ts
│   │   ├── useSandboxPreference.ts
│   │   ├── useSidebarNavigation.ts
│   │   ├── useTauri.ts
│   │   ├── useToolSidebar.ts
│   │   ├── useTypingAnimation.ts
│   │   └── useUpgrade.ts
│   ├── layout.tsx
│   ├── login/
│   │   └── route.ts
│   ├── logout/
│   │   └── route.ts
│   ├── posthog.js
│   ├── privacy-policy/
│   │   └── page.tsx
│   ├── providers.tsx
│   ├── services/
│   │   ├── __tests__/
│   │   │   └── desktop-sandbox-bridge.test.ts
│   │   └── desktop-sandbox-bridge.ts
│   ├── share/
│   │   └── [shareId]/
│   │       ├── SharedChatContext.tsx
│   │       ├── SharedChatView.tsx
│   │       ├── SharedMessages.tsx
│   │       ├── __tests__/
│   │       │   └── SharedMessages.test.tsx
│   │       ├── components/
│   │       │   ├── SharedMessagePartHandler.tsx
│   │       │   └── SharedTodoBlock.tsx
│   │       └── page.tsx
│   ├── signup/
│   │   └── route.ts
│   └── terms-of-service/
│       └── page.tsx
├── components/
│   ├── ConvexClientProvider.tsx
│   ├── ai-elements/
│   │   ├── __tests__/
│   │   │   ├── reasoning.test.tsx
│   │   │   └── worked-for.test.tsx
│   │   ├── reasoning.tsx
│   │   ├── shimmer.tsx
│   │   └── worked-for.tsx
│   ├── icons/
│   │   └── hackerai-svg.tsx
│   └── ui/
│       ├── alert-dialog.tsx
│       ├── avatar.tsx
│       ├── badge.tsx
│       ├── button.tsx
│       ├── calendar.tsx
│       ├── card.tsx
│       ├── code-action-buttons.tsx
│       ├── collapsible.tsx
│       ├── dialog.tsx
│       ├── dots-spinner.tsx
│       ├── dropdown-menu.tsx
│       ├── input.tsx
│       ├── label.tsx
│       ├── loading.tsx
│       ├── popover.tsx
│       ├── radio-group.tsx
│       ├── select.tsx
│       ├── separator.tsx
│       ├── shared-todo-item.tsx
│       ├── sheet.tsx
│       ├── sidebar.tsx
│       ├── skeleton.tsx
│       ├── sonner.tsx
│       ├── switch.tsx
│       ├── textarea.tsx
│       ├── todo-block.tsx
│       ├── tool-block.tsx
│       ├── tooltip.tsx
│       └── with-tooltip.tsx
├── components.json
├── convex/
│   ├── __tests__/
│   │   ├── chatSummaryFallback.test.ts
│   │   ├── fileStorage.aggregate.test.ts
│   │   ├── fileStorage.delete.test.ts
│   │   ├── messages.hidden.test.ts
│   │   ├── s3Actions.test.ts
│   │   ├── s3Cleanup.test.ts
│   │   ├── s3Utils.test.ts
│   │   ├── teamExtraUsage.test.ts
│   │   ├── userDeletion.test.ts
│   │   ├── userSuspensions.test.ts
│   │   └── webhookClaim.test.ts
│   ├── _generated/
│   │   ├── api.d.ts
│   │   ├── api.js
│   │   ├── dataModel.d.ts
│   │   ├── server.d.ts
│   │   └── server.js
│   ├── auth.config.ts
│   ├── chatStreams.ts
│   ├── chats.ts
│   ├── constants.ts
│   ├── convex.config.ts
│   ├── crons.ts
│   ├── extraUsage.ts
│   ├── extraUsageActions.ts
│   ├── feedback.ts
│   ├── fileActions.ts
│   ├── fileAggregate.ts
│   ├── fileStorage.ts
│   ├── lib/
│   │   ├── logger.ts
│   │   └── utils.ts
│   ├── localSandbox.ts
│   ├── messages.ts
│   ├── notes.ts
│   ├── rateLimitStatus.ts
│   ├── redisPubsub.ts
│   ├── s3Actions.ts
│   ├── s3Cleanup.ts
│   ├── s3Utils.ts
│   ├── schema.ts
│   ├── sharedChats.ts
│   ├── teamExtraUsage.ts
│   ├── teamExtraUsageActions.ts
│   ├── tempStreams.ts
│   ├── usageLogs.ts
│   ├── userCustomization.ts
│   ├── userDeletion.ts
│   └── userSuspensions.ts
├── docker/
│   ├── Dockerfile
│   ├── build.sh
│   ├── centrifugo/
│   │   ├── README.md
│   │   ├── config.json
│   │   └── docker-compose.yml
│   └── run.sh
├── e2b/
│   ├── README.md
│   ├── build.dev.ts
│   ├── build.prod.ts
│   └── template.ts
├── e2e/
│   ├── README.md
│   ├── chat-agent.spec.ts
│   ├── chat-files-pro.spec.ts
│   ├── chat-free.spec.ts
│   ├── chat-pinned.spec.ts
│   ├── chat-switching.spec.ts
│   ├── constants.ts
│   ├── fixtures/
│   │   └── auth.ts
│   ├── helpers/
│   │   ├── convex-helpers.ts
│   │   ├── mock-handlers.ts
│   │   └── test-helpers.ts
│   ├── page-objects/
│   │   ├── BasePage.ts
│   │   ├── ChatComponent.ts
│   │   ├── ChatModeSelector.ts
│   │   ├── ChatPage.ts
│   │   ├── FileAttachment.ts
│   │   ├── HomePage.ts
│   │   ├── SettingsDialog.ts
│   │   ├── SidebarComponent.ts
│   │   ├── UpgradeDialog.ts
│   │   ├── UserMenuComponent.ts
│   │   └── index.ts
│   ├── resource/
│   │   └── secret.txt
│   └── setup/
│       └── auth.setup.ts
├── eslint.config.mjs
├── global.d.ts
├── hooks/
│   ├── use-is-standalone.ts
│   └── use-mobile.ts
├── instrumentation.ts
├── jest.config.js
├── jest.setup.js
├── lib/
│   ├── __tests__/
│   │   ├── desktop-auth.test.ts
│   │   ├── extra-usage.test.ts
│   │   ├── resolve-customer-users.test.ts
│   │   ├── suspensionMessage.test.ts
│   │   ├── suspensions.test.ts
│   │   ├── usage-tracker.test.ts
│   │   └── utils.test.ts
│   ├── actions/
│   │   ├── billing-portal.ts
│   │   └── index.ts
│   ├── ai/
│   │   ├── providers.ts
│   │   └── tools/
│   │       ├── __tests__/
│   │       │   ├── interact-terminal-session.test.ts
│   │       │   ├── run-terminal-cmd.test.ts
│   │       │   └── sandbox-capabilities.test.ts
│   │       ├── file.ts
│   │       ├── get-terminal-files.ts
│   │       ├── index.ts
│   │       ├── interact-terminal-session.ts
│   │       ├── notes.ts
│   │       ├── open-url.ts
│   │       ├── proxy-tool.ts
│   │       ├── run-terminal-cmd.ts
│   │       ├── todo-write.ts
│   │       ├── utils/
│   │       │   ├── __tests__/
│   │       │   │   ├── centrifugo-sandbox.test.ts
│   │       │   │   ├── e2b-pty-adapter.test.ts
│   │       │   │   ├── platform-utils.test.ts
│   │       │   │   ├── proxy-manager.test.ts
│   │       │   │   ├── pty-keys.test.ts
│   │       │   │   ├── pty-session-manager.test.ts
│   │       │   │   ├── sandbox-file-uploader.test.ts
│   │       │   │   └── tauri-sandbox.test.ts
│   │       │   ├── background-process-tracker.ts
│   │       │   ├── caido-proxy.ts
│   │       │   ├── centrifugo-pty-adapter.ts
│   │       │   ├── centrifugo-sandbox.ts
│   │       │   ├── e2b-errors.ts
│   │       │   ├── e2b-pty-adapter.ts
│   │       │   ├── file-accumulator.ts
│   │       │   ├── guardrails.ts
│   │       │   ├── hybrid-sandbox-manager.ts
│   │       │   ├── path-validation.ts
│   │       │   ├── perplexity.ts
│   │       │   ├── pid-discovery.ts
│   │       │   ├── platform-utils.ts
│   │       │   ├── process-termination.ts
│   │       │   ├── proxy-manager.ts
│   │       │   ├── pty-exited-promise.ts
│   │       │   ├── pty-keys.ts
│   │       │   ├── pty-output-formatter.ts
│   │       │   ├── pty-output.ts
│   │       │   ├── pty-session-manager.ts
│   │       │   ├── pty-wait-utils.ts
│   │       │   ├── retry-with-backoff.ts
│   │       │   ├── sandbox-command-options.ts
│   │       │   ├── sandbox-file-uploader.ts
│   │       │   ├── sandbox-health.ts
│   │       │   ├── sandbox-manager.ts
│   │       │   ├── sandbox-tools.ts
│   │       │   ├── sandbox-types.ts
│   │       │   ├── sandbox.ts
│   │       │   ├── terminal-output-saver.ts
│   │       │   └── todo-manager.ts
│   │       └── web-search.ts
│   ├── api/
│   │   ├── __tests__/
│   │   │   ├── agent-long-contracts.test.ts
│   │   │   ├── build-extra-usage-config.test.ts
│   │   │   ├── chat-handler-pty-cleanup.test.ts
│   │   │   ├── chat-logger.test.ts
│   │   │   ├── chat-stream-helpers-fallback.test.ts
│   │   │   └── chat-stream-helpers-notes.test.ts
│   │   ├── agent-stream-runner.ts
│   │   ├── chat-handler.ts
│   │   ├── chat-logger.ts
│   │   ├── chat-stream-helpers.ts
│   │   └── response.ts
│   ├── auth/
│   │   ├── __tests__/
│   │   │   ├── cross-tab-mutex.test.ts
│   │   │   ├── feature-flags.test.ts
│   │   │   ├── shared-token.test.ts
│   │   │   ├── use-auth-from-authkit.test.ts
│   │   │   └── workos-organization-name.test.ts
│   │   ├── auth-redirect-intents.ts
│   │   ├── cross-tab-mutex.ts
│   │   ├── entitlements.ts
│   │   ├── feature-flags.ts
│   │   ├── get-user-id.ts
│   │   ├── shared-token.ts
│   │   ├── use-auth-from-authkit.ts
│   │   └── workos-organization-name.ts
│   ├── billing/
│   │   └── resolve-customer-users.ts
│   ├── centrifugo/
│   │   ├── __tests__/
│   │   │   └── jwt.test.ts
│   │   ├── jwt.ts
│   │   └── types.ts
│   ├── chat/
│   │   ├── __tests__/
│   │   │   ├── agent-long-heartbeat.test.ts
│   │   │   ├── agent-long-tool-input-dedup.test.ts
│   │   │   ├── agent-routing.test.ts
│   │   │   ├── chat-processor.test.ts
│   │   │   ├── doom-loop-detection.test.ts
│   │   │   └── stop-conditions.test.ts
│   │   ├── agent-long-heartbeat.ts
│   │   ├── agent-long-tool-input-dedup.ts
│   │   ├── agent-long-transport.ts
│   │   ├── agent-routing.ts
│   │   ├── auth-disclaimer.ts
│   │   ├── budget-monitor.ts
│   │   ├── chat-processor.ts
│   │   ├── compaction/
│   │   │   ├── __tests__/
│   │   │   │   └── prune-tool-outputs.test.ts
│   │   │   └── prune-tool-outputs.ts
│   │   ├── doom-loop-detection.ts
│   │   ├── stop-conditions.ts
│   │   ├── summarization/
│   │   │   ├── __tests__/
│   │   │   │   └── index.test.ts
│   │   │   ├── constants.ts
│   │   │   ├── helpers.ts
│   │   │   ├── index.ts
│   │   │   └── prompts.ts
│   │   └── tool-abort-utils.ts
│   ├── constants/
│   │   └── s3.ts
│   ├── db/
│   │   ├── __tests__/
│   │   │   ├── convex-value-sanitizer.test.ts
│   │   │   └── message-persistence-diagnostics.test.ts
│   │   ├── actions.ts
│   │   ├── convex-client.ts
│   │   ├── convex-value-sanitizer.ts
│   │   └── message-persistence-diagnostics.ts
│   ├── desktop-auth.ts
│   ├── errors.ts
│   ├── extra-usage.ts
│   ├── logger.ts
│   ├── moderation.ts
│   ├── posthog/
│   │   ├── server.ts
│   │   └── worker.ts
│   ├── pricing/
│   │   └── features.ts
│   ├── rate-limit/
│   │   ├── __tests__/
│   │   │   ├── index.test.ts
│   │   │   ├── refund.test.ts
│   │   │   ├── sliding-window.test.ts
│   │   │   ├── token-bucket.integration.test.ts
│   │   │   └── token-bucket.test.ts
│   │   ├── index.ts
│   │   ├── redis.ts
│   │   ├── refund.ts
│   │   ├── sliding-window.ts
│   │   └── token-bucket.ts
│   ├── suspensionMessage.ts
│   ├── suspensions.ts
│   ├── system-prompt/
│   │   ├── bio.ts
│   │   ├── notes.ts
│   │   ├── personality.ts
│   │   └── resume.ts
│   ├── system-prompt.ts
│   ├── token-utils.ts
│   ├── usage-projection.ts
│   ├── usage-tracker.ts
│   ├── utils/
│   │   ├── __tests__/
│   │   │   ├── client-storage.test.ts
│   │   │   ├── error-utils.test.ts
│   │   │   ├── file-transform-utils.test.ts
│   │   │   ├── message-utils.test.ts
│   │   │   ├── pro-max-notice-cookie.test.ts
│   │   │   ├── sandbox-file-utils.test.ts
│   │   │   ├── stream-writer-utils.test.ts
│   │   │   └── todo-utils.test.ts
│   │   ├── accumulate-ui-chunks.ts
│   │   ├── client-storage.ts
│   │   ├── error-utils.ts
│   │   ├── file-download.ts
│   │   ├── file-token-utils.ts
│   │   ├── file-transform-utils.ts
│   │   ├── file-utils.ts
│   │   ├── logout.ts
│   │   ├── message-processor.ts
│   │   ├── message-utils.ts
│   │   ├── mode-helpers.ts
│   │   ├── parse-rate-limit-warning.ts
│   │   ├── pro-max-notice-cookie.ts
│   │   ├── redis-pubsub.ts
│   │   ├── safe-wait-until.ts
│   │   ├── sandbox-command.ts
│   │   ├── sandbox-file-utils.ts
│   │   ├── scroll-events.ts
│   │   ├── settings-dialog.ts
│   │   ├── shiki.tsx
│   │   ├── sidebar-storage.ts
│   │   ├── sidebar-utils.ts
│   │   ├── stream-cancellation.ts
│   │   ├── stream-writer-utils.ts
│   │   ├── terminal-executor.ts
│   │   ├── todo-block-manager.ts
│   │   └── todo-utils.ts
│   └── utils.ts
├── middleware.ts
├── next.config.ts
├── package.json
├── packages/
│   ├── desktop/
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── scripts/
│   │   │   ├── build.js
│   │   │   └── generate-icons.mjs
│   │   ├── src/
│   │   │   └── index.html
│   │   ├── src-tauri/
│   │   │   ├── Cargo.toml
│   │   │   ├── build.rs
│   │   │   ├── capabilities/
│   │   │   │   └── default.json
│   │   │   ├── entitlements.plist
│   │   │   ├── gen/
│   │   │   │   └── schemas/
│   │   │   │       ├── acl-manifests.json
│   │   │   │       ├── capabilities.json
│   │   │   │       ├── desktop-schema.json
│   │   │   │       └── macOS-schema.json
│   │   │   ├── hackerai.desktop
│   │   │   ├── icons/
│   │   │   │   └── icon.icns
│   │   │   ├── permissions/
│   │   │   │   └── desktop-command-bridge.toml
│   │   │   ├── scripts/
│   │   │   │   └── deb-postinstall.sh
│   │   │   ├── src/
│   │   │   │   ├── lib.rs
│   │   │   │   ├── main.rs
│   │   │   │   ├── platform.rs
│   │   │   │   └── pty.rs
│   │   │   ├── tauri.conf.json
│   │   │   └── tauri.dev.conf.json
│   │   └── tsconfig.json
│   └── local/
│       ├── .gitignore
│       ├── README.md
│       ├── package.json
│       ├── src/
│       │   ├── __tests__/
│       │   │   └── utils.test.ts
│       │   ├── index.ts
│       │   ├── process-runner.ts
│       │   └── utils.ts
│       └── tsconfig.json
├── patches/
│   └── ai@6.0.184.patch
├── playwright.config.ts
├── pnpm-workspace.yaml
├── postcss.config.mjs
├── public/
│   └── manifest.json
├── scripts/
│   ├── README.md
│   ├── accept-invitation.ts
│   ├── attach-failing-card.ts
│   ├── check-openrouter-gen-id.ts
│   ├── create-test-users.ts
│   ├── reset-rate-limit.ts
│   ├── setup.ts
│   ├── test-users-config.ts
│   ├── validate-s3-security.ts
│   ├── verify-email.ts
│   └── verify-test-users.ts
├── skills-lock.json
├── trigger/
│   ├── agent-long.ts
│   ├── stream-ids.ts
│   └── streams.ts
├── trigger.config.ts
├── tsconfig.json
├── types/
│   ├── agent.ts
│   ├── chat.ts
│   ├── file.ts
│   ├── index.ts
│   └── user.ts
└── vercel.json

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

================================================
FILE: .agents/skills/logging-best-practices/SKILL.md
================================================
---
name: logging-best-practices
description: Use before implementing logs in a medium to large scale production system.
---

> This skill is adpated from ["Logging sucks. And here's how to make it better."](https://loggingsucks.com/) by Boris Tane.

When helping with logging, observability, or debugging strategies, follow these principles:

## Core Philosophy

- Logs are optimized for querying, not writing — always design with debugging in mind
- Context is everything — a log without correlation IDs is useless in distributed systems
- Logs are for humans during incidents, not just for compliance or "just in case"
- If you can't filter and search your logs effectively, they provide zero value

## Structured Logging Requirements

- Always use key-value pairs (JSON) instead of string interpolation
- Bad: "Payment failed for user 123"
- Good: {"event": "payment_failed", "user_id": "123", "reason": "insufficient_funds", "amount": 99.99}
- Structured logs are machine-parseable, enabling aggregation, alerting, and dashboards

## Required Fields for Every Log Event

- timestamp — ISO 8601 with timezone (e.g., 2025-01-24T20:00:00Z)
- level — debug, info, warn, error (be consistent, don't invent new levels)
- event — machine-readable event name, snake_case (e.g., user_login_success)
- request_id or trace_id — for correlating logs across a single request
- service — which service/application emitted this log
- environment — prod, staging, dev

## Examples of High-Cardinality Fields (always include when available):

- user_id, org_id, account_id — who is affected
- request_id, trace_id, span_id — for distributed tracing
- order_id, transaction_id, job_id — domain-specific identifiers

These fields are what make logs actually queryable during incidents. Without them, you're grepping through millions of lines blindly.

Look for opportunities for high-cardinality fields that can help you identify the root cause of an issue quickly.

## Context Propagation

- Pass trace/request IDs through all service boundaries (HTTP headers, message queues, etc.)
- Downstream services must inherit correlation IDs from upstream
- Use middleware or interceptors to automatically inject context into every log
- For async jobs, store and restore the original request context

Log Levels — Use Them Correctly:

- debug — Verbose details for local development, usually disabled in production
- info — Normal operations worth recording (user actions, job completions, deploys)
- warn — Something unexpected happened but the system handled it (retries, fallbacks)
- error — Something failed and likely needs human attention (exceptions, failed requests)

Don't log errors for expected conditions (e.g., user enters wrong password)

What to Log:

- Request entry and exit points (with duration)
- State transitions (order created → paid → shipped)
- External service calls (with latency and response codes)
- Authentication and authorization events
- Background job starts, completions, and failures
- Retry attempts and circuit breaker state changes

What NOT to Log:

- Sensitive data (passwords, tokens, PII, credit card numbers)
- Logs inside tight loops (will generate millions of useless entries)
- Success cases that provide no debugging value
- Redundant information already captured by infrastructure (load balancer logs, etc.)

Naming Conventions:

- Be consistent across all services — agree on field names as a team
- Use snake_case for field names: user_id, not userId or user-id
- Use past-tense verbs for events: payment_completed, not complete_payment
- Prefix events by domain when helpful: auth.login_failed, billing.invoice_created

Performance Considerations:

- Use sampling for high-volume debug logs in production
- Avoid logging inside hot paths unless absolutely necessary
- Buffer and batch log writes to reduce I/O overhead
- Consider log levels that can be changed at runtime without redeploying

During Incidents:

- Your logs should answer: Who was affected? What failed? When? Why?
- If you can't answer these within 5 minutes of querying, your logging strategy needs work
- Post-incident: add the logs you wished you had


================================================
FILE: .agents/skills/trigger-agents/SKILL.md
================================================
---
name: trigger-agents
description: AI agent patterns with Trigger.dev - orchestration, parallelization, routing, evaluator-optimizer, and human-in-the-loop. Use when building LLM-powered tasks that need parallel workers, approval gates, tool calling, or multi-step agent workflows.
---

# AI Agent Patterns with Trigger.dev

Build production-ready AI agents using Trigger.dev's durable execution.

## Pattern Selection

```
Need to...                              → Use
─────────────────────────────────────────────────────
Process items in parallel               → Parallelization
Route to different models/handlers      → Routing
Chain steps with validation gates       → Prompt Chaining
Coordinate multiple specialized tasks   → Orchestrator-Workers
Self-improve until quality threshold    → Evaluator-Optimizer
Pause for human approval                → Human-in-the-Loop (waitpoints.md)
Stream progress to frontend             → Realtime Streams (streaming.md)
Let LLM call your tasks as tools        → ai.tool (ai-tool.md)
```

---

## Core Patterns

### 1. Prompt Chaining (Sequential with Gates)

Chain LLM calls with validation between steps. Fail early if intermediate output is bad.

```typescript
import { task } from "@trigger.dev/sdk";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

export const translateCopy = task({
  id: "translate-copy",
  run: async ({ text, targetLanguage, maxWords }) => {
    // Step 1: Generate
    const draft = await generateText({
      model: openai("gpt-4o"),
      prompt: `Write marketing copy about: ${text}`,
    });

    // Gate: Validate before continuing
    const wordCount = draft.text.split(/\s+/).length;
    if (wordCount > maxWords) {
      throw new Error(`Draft too long: ${wordCount} > ${maxWords}`);
    }

    // Step 2: Translate (only if gate passed)
    const translated = await generateText({
      model: openai("gpt-4o"),
      prompt: `Translate to ${targetLanguage}: ${draft.text}`,
    });

    return { draft: draft.text, translated: translated.text };
  },
});
```

---

### 2. Routing (Classify → Dispatch)

Use a cheap model to classify, then route to appropriate handler.

```typescript
import { task } from "@trigger.dev/sdk";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

const routingSchema = z.object({
  model: z.enum(["gpt-4o", "o1-mini"]),
  reason: z.string(),
});

export const routeQuestion = task({
  id: "route-question",
  run: async ({ question }) => {
    // Cheap classification call
    const routing = await generateText({
      model: openai("gpt-4o-mini"),
      messages: [
        {
          role: "system",
          content: `Classify question complexity. Return JSON: {"model": "gpt-4o" | "o1-mini", "reason": "..."}
          - gpt-4o: simple factual questions
          - o1-mini: complex reasoning, math, code`,
        },
        { role: "user", content: question },
      ],
    });

    const { model } = routingSchema.parse(JSON.parse(routing.text));

    // Route to selected model
    const answer = await generateText({
      model: openai(model),
      prompt: question,
    });

    return { answer: answer.text, routedTo: model };
  },
});
```

---

### 3. Parallelization

Run independent LLM calls simultaneously with `batch.triggerByTaskAndWait`.

```typescript
import { batch, task } from "@trigger.dev/sdk";

export const analyzeContent = task({
  id: "analyze-content",
  run: async ({ text }) => {
    // All three run in parallel
    const { runs: [sentiment, summary, moderation] } = await batch.triggerByTaskAndWait([
      { task: analyzeSentiment, payload: { text } },
      { task: summarizeText, payload: { text } },
      { task: moderateContent, payload: { text } },
    ]);

    // Check moderation first
    if (moderation.ok && moderation.output.flagged) {
      return { error: "Content flagged", reason: moderation.output.reason };
    }

    return {
      sentiment: sentiment.ok ? sentiment.output : null,
      summary: summary.ok ? summary.output : null,
    };
  },
});
```

**See:** `references/orchestration.md` for advanced patterns

---

### 4. Orchestrator-Workers (Fan-out/Fan-in)

Orchestrator extracts work items, fans out to workers, aggregates results.

```typescript
import { batch, task } from "@trigger.dev/sdk";

export const factChecker = task({
  id: "fact-checker",
  run: async ({ article }) => {
    // Step 1: Extract claims (sequential - need output first)
    const { runs: [extractResult] } = await batch.triggerByTaskAndWait([
      { task: extractClaims, payload: { article } },
    ]);

    if (!extractResult.ok) throw new Error("Failed to extract claims");
    const claims = extractResult.output;

    // Step 2: Fan-out - verify all claims in parallel
    const { runs } = await batch.triggerByTaskAndWait(
      claims.map(claim => ({ task: verifyClaim, payload: claim }))
    );

    // Step 3: Fan-in - aggregate results
    const verified = runs
      .filter((r): r is typeof r & { ok: true } => r.ok)
      .map(r => r.output);

    return { claims, verifications: verified };
  },
});
```

---

### 5. Evaluator-Optimizer (Self-Refining Loop)

Generate → Evaluate → Retry with feedback until approved.

```typescript
import { task } from "@trigger.dev/sdk";

export const refineTranslation = task({
  id: "refine-translation",
  run: async ({ text, targetLanguage, feedback, attempt = 0 }) => {
    // Bail condition
    if (attempt >= 5) {
      return { text, status: "MAX_ATTEMPTS", attempts: attempt };
    }

    // Generate (with feedback if retrying)
    const prompt = feedback
      ? `Improve this translation based on feedback:\n${feedback}\n\nOriginal: ${text}`
      : `Translate to ${targetLanguage}: ${text}`;

    const translation = await generateText({
      model: openai("gpt-4o"),
      prompt,
    });

    // Evaluate
    const evaluation = await generateText({
      model: openai("gpt-4o"),
      prompt: `Evaluate translation quality. Reply APPROVED or provide specific feedback:\n${translation.text}`,
    });

    if (evaluation.text.includes("APPROVED")) {
      return { text: translation.text, status: "APPROVED", attempts: attempt + 1 };
    }

    // Recursive self-call with feedback
    return refineTranslation.triggerAndWait({
      text,
      targetLanguage,
      feedback: evaluation.text,
      attempt: attempt + 1,
    }).unwrap();
  },
});
```

---

## Trigger-Specific Features

| Feature | What it enables | Reference |
|---------|-----------------|-----------|
| **Waitpoints** | Human approval gates, external callbacks | `references/waitpoints.md` |
| **Streams** | Real-time progress to frontend | `references/streaming.md` |
| **ai.tool** | Let LLMs call your tasks as tools | `references/ai-tool.md` |
| **batch.triggerByTaskAndWait** | Typed parallel execution | `references/orchestration.md` |

---

## Error Handling

```typescript
const { runs } = await batch.triggerByTaskAndWait([...]);

// Check individual results
for (const run of runs) {
  if (run.ok) {
    console.log(run.output);  // Typed output
  } else {
    console.error(run.error);  // Error details
    console.log(run.taskIdentifier);  // Which task failed
  }
}

// Or filter by task type
const verifications = runs
  .filter((r): r is typeof r & { ok: true } =>
    r.ok && r.taskIdentifier === "verify-claim"
  )
  .map(r => r.output);
```

---

## Quick Reference

```typescript
// Trigger and wait for result
const result = await myTask.triggerAndWait(payload);
if (result.ok) console.log(result.output);

// Batch trigger same task
const results = await myTask.batchTriggerAndWait([
  { payload: item1 },
  { payload: item2 },
]);

// Batch trigger different tasks (typed)
const { runs } = await batch.triggerByTaskAndWait([
  { task: taskA, payload: { foo: 1 } },
  { task: taskB, payload: { bar: "x" } },
]);

// Self-recursion with unwrap
return myTask.triggerAndWait(newPayload).unwrap();
```


================================================
FILE: .agents/skills/trigger-agents/references/ai-tool.md
================================================
# ai.tool Integration

Convert Trigger.dev tasks to Vercel AI SDK tools. Let LLMs call your tasks autonomously.

## Basic Usage

```typescript
import { schemaTask, ai } from "@trigger.dev/sdk";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

// 1. Define task with schema
const lookupWeather = schemaTask({
  id: "lookup-weather",
  schema: z.object({
    location: z.string().describe("City name"),
    units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
  }),
  run: async ({ location, units }) => {
    const weather = await fetchWeather(location, units);
    return { temperature: weather.temp, conditions: weather.conditions };
  },
});

// 2. Convert to AI tool
const weatherTool = ai.tool(lookupWeather);

// 3. Use with AI SDK
export const weatherAgent = schemaTask({
  id: "weather-agent",
  schema: z.object({ question: z.string() }),
  run: async ({ question }) => {
    const result = await generateText({
      model: openai("gpt-4o"),
      prompt: question,
      tools: {
        lookupWeather: weatherTool,
      },
    });

    return { answer: result.text };
  },
});
```

---

## Schema Requirements

The task **must** use `schemaTask` with a Zod schema:

```typescript
// ✅ Works - has schema
const myTask = schemaTask({
  id: "my-task",
  schema: z.object({
    query: z.string(),
  }),
  run: async (payload) => { ... },
});

// ❌ Won't work - no schema
const myTask = task({
  id: "my-task",
  run: async (payload: { query: string }) => { ... },
});
```

**Supported schema libraries:**
- Zod
- ArkType
- Any schema with `.toJsonSchema()` method

---

## Tool Result Customization

Customize how results are sent back to the LLM:

```typescript
const searchTool = ai.tool(searchDatabase, {
  experimental_toToolResultContent: (result) => {
    // Return structured content for the LLM
    return [
      {
        type: "text",
        text: `Found ${result.count} results:\n${result.items.map(i => i.title).join("\n")}`,
      },
    ];
  },
});
```

---

## Accessing Tool Options

Get execution context inside the task:

```typescript
const myToolTask = schemaTask({
  id: "my-tool-task",
  schema: z.object({ input: z.string() }),
  run: async (payload) => {
    // Access AI SDK tool execution options
    const toolOptions = ai.currentToolOptions();

    console.log(toolOptions);
    // { toolCallId: "...", messages: [...], ... }

    return processInput(payload.input);
  },
});
```

---

## Multiple Tools

```typescript
const searchTool = ai.tool(searchDatabase);
const calculateTool = ai.tool(calculate);
const summarizeTool = ai.tool(summarize);

export const agentTask = schemaTask({
  id: "agent",
  schema: z.object({ task: z.string() }),
  run: async ({ task }) => {
    const result = await generateText({
      model: openai("gpt-4o"),
      prompt: task,
      tools: {
        search: searchTool,
        calculate: calculateTool,
        summarize: summarizeTool,
      },
      maxSteps: 10,  // Allow multiple tool calls
    });

    return { result: result.text };
  },
});
```

---

## With Tool Choice

```typescript
const result = await generateText({
  model: openai("gpt-4o"),
  prompt: "What's the weather in Tokyo?",
  tools: {
    weather: weatherTool,
    news: newsTool,
  },
  toolChoice: "required",  // Force tool use
  // or: toolChoice: { type: "tool", toolName: "weather" }
});
```

---

## Description from Schema

Add descriptions for better LLM understanding:

```typescript
const searchTask = schemaTask({
  id: "search-database",
  description: "Search the product database for items matching a query",
  schema: z.object({
    query: z.string().describe("Search terms"),
    limit: z.number().min(1).max(100).describe("Max results to return"),
    category: z.enum(["electronics", "clothing", "books"]).optional()
      .describe("Filter by product category"),
  }),
  run: async (payload) => { ... },
});
```

---

## Common Pattern: Research Agent

```typescript
const webSearch = schemaTask({
  id: "web-search",
  schema: z.object({
    query: z.string(),
    maxResults: z.number().default(5),
  }),
  run: async ({ query, maxResults }) => {
    return await searchWeb(query, maxResults);
  },
});

const readUrl = schemaTask({
  id: "read-url",
  schema: z.object({
    url: z.string().url(),
  }),
  run: async ({ url }) => {
    return await fetchAndParse(url);
  },
});

export const researchAgent = schemaTask({
  id: "research-agent",
  schema: z.object({ topic: z.string() }),
  run: async ({ topic }) => {
    const result = await generateText({
      model: openai("gpt-4o"),
      system: "Research the topic thoroughly using available tools.",
      prompt: topic,
      tools: {
        search: ai.tool(webSearch),
        read: ai.tool(readUrl),
      },
      maxSteps: 20,
    });

    return { research: result.text };
  },
});
```

---

## Tips

1. **Always use schemaTask** - regular `task` won't work
2. **Add descriptions** - helps LLM understand when to use the tool
3. **Use `.describe()`** - on schema fields for parameter hints
4. **Set maxSteps** - allow multiple tool calls for complex tasks
5. **Customize results** - use `experimental_toToolResultContent` for better LLM context


================================================
FILE: .agents/skills/trigger-agents/references/orchestration.md
================================================
# Orchestration Patterns

Advanced patterns for `batch.triggerByTaskAndWait` and task coordination.

## Basic Usage

```typescript
import { batch, task } from "@trigger.dev/sdk";

// Trigger different tasks, get typed results
const { runs } = await batch.triggerByTaskAndWait([
  { task: taskA, payload: { foo: "bar" } },  // payload typed to taskA
  { task: taskB, payload: { num: 42 } },     // payload typed to taskB
]);

// Results are typed based on position
if (runs[0].ok) {
  console.log(runs[0].output);  // typed as taskA output
}
```

## Destructured Results

```typescript
const {
  runs: [userRun, postsRun, settingsRun],
} = await batch.triggerByTaskAndWait([
  { task: fetchUser, payload: { id } },
  { task: fetchPosts, payload: { userId: id } },
  { task: fetchSettings, payload: { userId: id } },
]);

// Each run is individually typed
const user = userRun.ok ? userRun.output : null;
const posts = postsRun.ok ? postsRun.output : [];
```

---

## Error Handling Per-Task

```typescript
const { runs } = await batch.triggerByTaskAndWait([
  { task: riskyTask, payload: item1 },
  { task: riskyTask, payload: item2 },
  { task: riskyTask, payload: item3 },
]);

// Individual error handling
const results = runs.map(run => {
  if (run.ok) {
    return { success: true, data: run.output };
  }
  return {
    success: false,
    error: run.error,
    taskId: run.taskIdentifier,
    runId: run.id,
  };
});

// Or throw if any failed
const failed = runs.filter(r => !r.ok);
if (failed.length > 0) {
  throw new Error(`${failed.length} tasks failed`);
}
```

---

## Filtering by Task Identifier

When running mixed task types, filter results by `taskIdentifier`:

```typescript
const { runs } = await batch.triggerByTaskAndWait([
  ...claims.map(c => ({ task: verifySource, payload: c })),
  ...claims.map(c => ({ task: analyzeHistory, payload: c })),
]);

// Filter to specific task results
const verifications = runs
  .filter((r): r is typeof r & { ok: true } =>
    r.ok && r.taskIdentifier === "verify-source"
  )
  .map(r => r.output as SourceVerification);

const analyses = runs
  .filter((r): r is typeof r & { ok: true } =>
    r.ok && r.taskIdentifier === "analyze-history"
  )
  .map(r => r.output as HistoricalAnalysis);
```

---

## Fan-out/Fan-in Pattern

```typescript
export const processItems = task({
  id: "process-items",
  run: async ({ items }) => {
    // Fan-out: process all items in parallel
    const { runs } = await batch.triggerByTaskAndWait(
      items.map(item => ({ task: processItem, payload: item }))
    );

    // Fan-in: aggregate results
    const successful = runs.filter(r => r.ok).map(r => r.output);
    const failed = runs.filter(r => !r.ok);

    return {
      processed: successful.length,
      failed: failed.length,
      results: successful,
      errors: failed.map(f => ({ id: f.id, error: f.error })),
    };
  },
});
```

---

## Sequential Then Parallel

```typescript
export const orchestrator = task({
  id: "orchestrator",
  run: async ({ input }) => {
    // Step 1: Sequential preprocessing
    const { runs: [prepResult] } = await batch.triggerByTaskAndWait([
      { task: preprocess, payload: { input } },
    ]);

    if (!prepResult.ok) {
      throw new Error(`Preprocessing failed: ${prepResult.error}`);
    }

    const items = prepResult.output;

    // Step 2: Parallel processing
    const { runs } = await batch.triggerByTaskAndWait(
      items.map(item => ({ task: processItem, payload: item }))
    );

    // Step 3: Sequential aggregation
    const { runs: [aggResult] } = await batch.triggerByTaskAndWait([
      { task: aggregate, payload: { results: runs.filter(r => r.ok).map(r => r.output) } },
    ]);

    return aggResult.ok ? aggResult.output : null;
  },
});
```

---

## Same Task, Multiple Items

For batch processing the same task:

```typescript
// Using batchTriggerAndWait (single task type)
const results = await processItem.batchTriggerAndWait([
  { payload: item1 },
  { payload: item2 },
  { payload: item3 },
]);

// Equivalent using batch.triggerByTaskAndWait
const { runs } = await batch.triggerByTaskAndWait([
  { task: processItem, payload: item1 },
  { task: processItem, payload: item2 },
  { task: processItem, payload: item3 },
]);
```

---

## Concurrency Control

Control parallelism via queue settings on child tasks:

```typescript
import { queue, task } from "@trigger.dev/sdk";

const rateLimitedQueue = queue({
  name: "api-calls",
  concurrencyLimit: 5,  // Max 5 concurrent
});

export const callExternalApi = task({
  id: "call-external-api",
  queue: rateLimitedQueue,
  run: async (payload) => {
    // Rate limited to 5 concurrent executions
    return fetch(payload.url);
  },
});

// Parent can batch trigger many - queue handles concurrency
export const batchProcess = task({
  id: "batch-process",
  run: async ({ urls }) => {
    // Will queue up, respecting concurrencyLimit: 5
    return callExternalApi.batchTriggerAndWait(
      urls.map(url => ({ payload: { url } }))
    );
  },
});
```

---

## Streaming Batch Items

For large batches, stream items instead of loading all at once:

```typescript
import { batch } from "@trigger.dev/sdk";

// Generator function for items
async function* generateItems() {
  for await (const record of database.cursor()) {
    yield { task: processRecord, payload: record };
  }
}

// Stream to batch trigger
const { runs } = await batch.triggerByTaskAndWait(generateItems());
```

---

## Tips

1. **Use destructuring** for known task counts - cleaner code
2. **Filter by taskIdentifier** when mixing task types
3. **Check `.ok`** before accessing `.output`
4. **Control concurrency** on child task queues, not in orchestrator
5. **Avoid parallel waits** - use batch methods, not Promise.all with triggerAndWait


================================================
FILE: .agents/skills/trigger-agents/references/streaming.md
================================================
# Realtime Streams

Stream data from tasks to your frontend in real-time. Perfect for AI completions, progress updates, and live status.

## Define Streams

Create typed stream definitions in a shared file:

```typescript
// trigger/streams.ts
import { streams } from "@trigger.dev/sdk";

// Define with type and unique ID
export const progressStream = streams.define<string>({
  id: "progress",
});

export const aiOutputStream = streams.define<string>({
  id: "ai-output",
});

// Export type for frontend
export type STREAMS = typeof progressStream | typeof aiOutputStream;
```

---

## Emit from Tasks

### Basic emit

```typescript
import { task } from "@trigger.dev/sdk";
import { progressStream } from "./streams";

export const processItems = task({
  id: "process-items",
  run: async ({ items }) => {
    for (const [i, item] of items.entries()) {
      await processItem(item);

      // Emit progress
      progressStream.append(
        JSON.stringify({
          current: i + 1,
          total: items.length,
          status: `Processing ${item.name}`,
        })
      );
    }

    return { processed: items.length };
  },
});
```

### Stream AI completion

```typescript
import { task } from "@trigger.dev/sdk";
import { streamText } from "ai";
import { aiOutputStream } from "./streams";

export const generateText = task({
  id: "generate-text",
  run: async ({ prompt }) => {
    const result = streamText({
      model: openai("gpt-4o"),
      prompt,
    });

    // Pipe AI stream to Trigger stream
    for await (const chunk of result.textStream) {
      aiOutputStream.append(chunk);
    }

    return { text: await result.text };
  },
});
```

---

## Child → Parent Streaming

When child tasks need to emit to the parent's stream:

```typescript
// Child task
export const workerTask = task({
  id: "worker",
  run: async ({ item }) => {
    const result = await processItem(item);

    // Emit to PARENT's stream, not this task's
    progressStream.append(
      JSON.stringify({ item: item.id, status: "done" }),
      { target: "parent" }
    );

    return result;
  },
});

// Parent task - frontend subscribes to this run
export const orchestrator = task({
  id: "orchestrator",
  run: async ({ items }) => {
    // Child emits bubble up to this task's stream
    return workerTask.batchTriggerAndWait(
      items.map(item => ({ payload: { item } }))
    );
  },
});
```

---

## Frontend Subscription

### Using useRealtimeStream (Recommended)

```tsx
import { useRealtimeStream } from "@trigger.dev/react-hooks";
import type { progressStream } from "@/trigger/streams";

function Progress({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { data } = useRealtimeStream<typeof progressStream>(runId, {
    accessToken,
    stream: "progress",
  });

  if (!data) return <div>Waiting...</div>;

  // data is array of emitted values
  const latest = data[data.length - 1];
  const progress = JSON.parse(latest);

  return (
    <div>
      {progress.current} / {progress.total}: {progress.status}
    </div>
  );
}
```

### Using useRealtimeRunWithStreams

```tsx
import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";
import type { processItems, STREAMS } from "@/trigger/tasks";

function TaskProgress({ runId, accessToken }: Props) {
  const { run, streams } = useRealtimeRunWithStreams<typeof processItems, STREAMS>(
    runId,
    { accessToken }
  );

  const progressUpdates = streams.progress ?? [];
  const latest = progressUpdates[progressUpdates.length - 1];

  return (
    <div>
      <p>Status: {run?.status}</p>
      {latest && <p>Progress: {latest}</p>}
    </div>
  );
}
```

---

## Backend Consumption

Read streams from your backend:

```typescript
import { aiOutputStream } from "./trigger/streams";

async function consumeStream(runId: string) {
  const stream = await aiOutputStream.read(runId, {
    timeoutInSeconds: 120,
  });

  let fullText = "";
  for await (const chunk of stream) {
    fullText += chunk;
    console.log("Received:", chunk);
  }

  return fullText;
}
```

---

## JSON Serialization Pattern

Streams serialize as strings. For objects, use JSON:

```typescript
// Define helper functions
export function emitProgress(update: ProgressUpdate, options?: { target: "parent" }) {
  progressStream.append(JSON.stringify(update), options);
}

// Parse on frontend
const updates = streams.progress?.map(s => JSON.parse(s) as ProgressUpdate) ?? [];
```

---

## Throttling Frontend Updates

Prevent excessive re-renders:

```tsx
const { data } = useRealtimeStream<typeof progressStream>(runId, {
  accessToken,
  stream: "progress",
  throttleInMs: 100,  // Max 10 updates/second
});
```

---

## AI SDK Tool Calls

Stream tool calls and results:

```tsx
const { streams } = useRealtimeRunWithStreams<typeof aiTask, STREAMS>(runId, {
  accessToken,
});

// streams.openai is TextStreamPart[]
const toolCalls = streams.openai?.filter(s => s.type === "tool-call") ?? [];
const toolResults = streams.openai?.filter(s => s.type === "tool-result") ?? [];
const textDeltas = streams.openai?.filter(s => s.type === "text-delta") ?? [];

const fullText = textDeltas.map(d => d.textDelta).join("");
```

---

## Tips

1. **Use streams.define()** - always define in shared file for type safety
2. **JSON stringify objects** - streams are strings internally
3. **Use `{ target: "parent" }`** - for child-to-parent bubbling
4. **Throttle on frontend** - prevent excessive re-renders
5. **Set appropriate timeouts** - AI completions may need longer waits


================================================
FILE: .agents/skills/trigger-agents/references/waitpoints.md
================================================
# Human-in-the-Loop with Waitpoints

Pause task execution for human approval, external callbacks, or async events.

## Core API

```typescript
import { wait } from "@trigger.dev/sdk";

// Create a token (pauses execution point)
const token = await wait.createToken({
  timeout: "10m",  // "1h", "1d", etc.
});

// Wait for completion (blocks until resolved)
const result = await wait.forToken<ApprovalPayload>(token.id);

if (result.ok) {
  console.log(result.output);  // Typed as ApprovalPayload
} else {
  console.log("Timed out:", result.error);
}
```

---

## Complete Pattern: Slack Approval

```typescript
import { task, wait } from "@trigger.dev/sdk";

type ApprovalToken = {
  approved: boolean;
  selectedOption: "optionA" | "optionB";
  approvedBy: string;
};

export const generateWithApproval = task({
  id: "generate-with-approval",
  maxDuration: 600,  // 10 min to account for human delay
  run: async ({ prompt }) => {
    // 1. Generate options
    const options = await generateOptions(prompt);

    // 2. Create approval token
    const token = await wait.createToken({
      timeout: "1h",
    });

    // 3. Send to Slack/email/webhook
    await sendSlackMessage({
      text: "Please approve one option:",
      options,
      approvalUrl: `${process.env.APP_URL}/approve?token=${token.id}`,
      // Or use: token.url for direct callback
    });

    // 4. Wait for human (task suspends here)
    const result = await wait.forToken<ApprovalToken>(token.id);

    if (!result.ok) {
      throw new Error("Approval timed out");
    }

    // 5. Continue with approved option
    return {
      selected: result.output.selectedOption,
      approvedBy: result.output.approvedBy,
      options,
    };
  },
});
```

---

## Completing Tokens

### From your backend

```typescript
import { wait } from "@trigger.dev/sdk";

// In your approval endpoint
export async function POST(request: Request) {
  const { tokenId, approved, option, userId } = await request.json();

  await wait.completeToken<ApprovalToken>(tokenId, {
    approved,
    selectedOption: option,
    approvedBy: userId,
  });

  return Response.json({ success: true });
}
```

### Via HTTP callback (webhooks)

```typescript
const token = await wait.createToken({ timeout: "10m" });

// token.url is a webhook URL that completes the token
// POST to token.url with JSON body → becomes the output
await externalService.startJob({
  callbackUrl: token.url,  // Service POSTs result here
});

const result = await wait.forToken<ExternalResult>(token.id);
```

### From React (useWaitToken)

```typescript
import { useWaitToken } from "@trigger.dev/react-hooks";

function ApprovalButton({ tokenId, publicToken }) {
  const { complete, isCompleting } = useWaitToken(tokenId, {
    accessToken: publicToken,
  });

  return (
    <button
      onClick={() => complete({ approved: true })}
      disabled={isCompleting}
    >
      Approve
    </button>
  );
}
```

---

## Timeout Handling

```typescript
const result = await wait.forToken<ApprovalToken>(token.id);

if (result.ok) {
  // Human responded in time
  return processApproval(result.output);
} else {
  // Timed out - handle gracefully
  await notifyTimeout();
  return { status: "timeout", defaultAction: "rejected" };
}
```

### Using .unwrap() for cleaner code

```typescript
try {
  const approval = await wait.forToken<ApprovalToken>(token.id).unwrap();
  // approval is directly typed, throws on timeout
  return processApproval(approval);
} catch (error) {
  // Timeout throws here
  return handleTimeout();
}
```

---

## Idempotency

Prevent duplicate tokens for the same workflow:

```typescript
const token = await wait.createToken({
  timeout: "1h",
  idempotencyKey: `review-${workflowId}`,
});
```

---

## Tags for Tracking

```typescript
const token = await wait.createToken({
  timeout: "1h",
  tags: [`workflow:${workflowId}`, `user:${userId}`],
});
```

---

## Public Access Token

For frontend completion without server round-trip:

```typescript
const token = await wait.createToken({ timeout: "10m" });

// Pass to frontend
return {
  tokenId: token.id,
  publicToken: token.publicAccessToken,  // Auto-generated, expires in 1h
};
```

---

## Example: Multi-step Review

```typescript
export const contentPipeline = task({
  id: "content-pipeline",
  run: async ({ content }) => {
    // Step 1: AI generation
    const draft = await generateDraft(content);

    // Step 2: Human review
    const reviewToken = await wait.createToken({ timeout: "24h" });
    await sendForReview(draft, reviewToken.id);
    const review = await wait.forToken<ReviewResult>(reviewToken.id);

    if (!review.ok || !review.output.approved) {
      return { status: "rejected", feedback: review.output?.feedback };
    }

    // Step 3: Final approval
    const publishToken = await wait.createToken({ timeout: "1h" });
    await sendForPublishApproval(draft, publishToken.id);
    const publish = await wait.forToken<PublishResult>(publishToken.id);

    if (!publish.ok || !publish.output.approved) {
      return { status: "not_published" };
    }

    // Step 4: Publish
    await publishContent(draft);
    return { status: "published" };
  },
});
```

---

## Tips

1. **Set realistic timeouts** - account for human response time
2. **Handle timeouts gracefully** - don't throw, provide default behavior
3. **Use idempotencyKey** - prevent duplicate tokens on retries
4. **Increase maxDuration** - task needs enough time for human + processing
5. **Use publicAccessToken** - for direct frontend completion


================================================
FILE: .agents/skills/trigger-config/SKILL.md
================================================
---
name: trigger-config
description: Configure Trigger.dev projects with trigger.config.ts. Use when setting up build extensions for Prisma, Playwright, FFmpeg, Python, or customizing deployment settings.
---

# Trigger.dev Configuration

Configure your Trigger.dev project with `trigger.config.ts` and build extensions.

## When to Use

- Setting up a new Trigger.dev project
- Adding database support (Prisma, TypeORM)
- Configuring browser automation (Playwright, Puppeteer)
- Adding media processing (FFmpeg)
- Running Python scripts from tasks
- Syncing environment variables
- Installing system packages

## Basic Configuration

```ts
// trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "<project-ref>",
  dirs: ["./trigger"],
  runtime: "node", // "node", "node-22", or "bun"
  logLevel: "info",

  retries: {
    enabledInDev: false,
    default: {
      maxAttempts: 3,
      minTimeoutInMs: 1000,
      maxTimeoutInMs: 10000,
      factor: 2,
    },
  },

  build: {
    extensions: [], // Add extensions here
  },
});
```

## Common Build Extensions

### Prisma

```ts
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";

export default defineConfig({
  // ...
  build: {
    extensions: [
      prismaExtension({
        schema: "prisma/schema.prisma",
        migrate: true,
        directUrlEnvVarName: "DIRECT_DATABASE_URL",
      }),
    ],
  },
});
```

### Playwright (Browser Automation)

```ts
import { playwright } from "@trigger.dev/build/extensions/playwright";

extensions: [
  playwright({
    browsers: ["chromium"], // or ["chromium", "firefox", "webkit"]
  }),
]
```

### Puppeteer

```ts
import { puppeteer } from "@trigger.dev/build/extensions/puppeteer";

extensions: [puppeteer()]

// Set env var: PUPPETEER_EXECUTABLE_PATH="/usr/bin/google-chrome-stable"
```

### FFmpeg (Media Processing)

```ts
import { ffmpeg } from "@trigger.dev/build/extensions/core";

extensions: [
  ffmpeg({ version: "7" }),
]
// Automatically sets FFMPEG_PATH and FFPROBE_PATH
```

### Python

```ts
import { pythonExtension } from "@trigger.dev/build/extensions/python";

extensions: [
  pythonExtension({
    scripts: ["./python/**/*.py"],
    requirementsFile: "./requirements.txt",
    devPythonBinaryPath: ".venv/bin/python",
  }),
]

// Usage in tasks:
const result = await python.runScript("./python/process.py", ["arg1"]);
```

### System Packages (apt-get)

```ts
import { aptGet } from "@trigger.dev/build/extensions/core";

extensions: [
  aptGet({
    packages: ["imagemagick", "curl"],
  }),
]
```

### Additional Files

```ts
import { additionalFiles } from "@trigger.dev/build/extensions/core";

extensions: [
  additionalFiles({
    files: ["./assets/**", "./templates/**"],
  }),
]
```

### Environment Variable Sync

```ts
import { syncEnvVars } from "@trigger.dev/build/extensions/core";

extensions: [
  syncEnvVars(async (ctx) => {
    return [
      { name: "API_KEY", value: await getSecret(ctx.environment) },
      { name: "ENV", value: ctx.environment },
    ];
  }),
]
```

## Common Extension Combinations

### Full-Stack Web App

```ts
extensions: [
  prismaExtension({ schema: "prisma/schema.prisma", migrate: true }),
  additionalFiles({ files: ["./assets/**"] }),
  syncEnvVars(async (ctx) => [...envVars]),
]
```

### AI/ML Processing

```ts
extensions: [
  pythonExtension({
    scripts: ["./ai/**/*.py"],
    requirementsFile: "./requirements.txt",
  }),
  ffmpeg({ version: "7" }),
]
```

### Web Scraping

```ts
extensions: [
  playwright({ browsers: ["chromium"] }),
  additionalFiles({ files: ["./selectors.json"] }),
]
```

## Global Lifecycle Hooks

```ts
export default defineConfig({
  // ...
  onStartAttempt: async ({ payload, ctx }) => {
    console.log("Task starting:", ctx.task.id);
  },
  onSuccess: async ({ payload, output, ctx }) => {
    console.log("Task succeeded");
  },
  onFailure: async ({ payload, error, ctx }) => {
    console.error("Task failed:", error);
  },
});
```

## Machine Defaults

```ts
export default defineConfig({
  // ...
  defaultMachine: "medium-1x",
  maxDuration: 300, // seconds
});
```

## Telemetry Integration

```ts
import { PrismaInstrumentation } from "@prisma/instrumentation";

export default defineConfig({
  // ...
  telemetry: {
    instrumentations: [new PrismaInstrumentation()],
  },
});
```

## Best Practices

1. **Pin versions** for reproducible builds
2. **Use `syncEnvVars`** for dynamic secrets
3. **Add native modules** to `build.external` array
4. **Debug with** `--log-level debug --dry-run`

Extensions only affect deployment, not local development.

See `references/config.md` for complete documentation.


================================================
FILE: .agents/skills/trigger-config/references/config.md
================================================
# Trigger.dev Configuration

**Complete guide to configuring `trigger.config.ts` with build extensions**

## Basic Configuration

```ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "<project-ref>", // Required: Your project reference
  dirs: ["./trigger"], // Task directories
  runtime: "node", // "node", "node-22", or "bun"
  logLevel: "info", // "debug", "info", "warn", "error"

  // Default retry settings
  retries: {
    enabledInDev: false,
    default: {
      maxAttempts: 3,
      minTimeoutInMs: 1000,
      maxTimeoutInMs: 10000,
      factor: 2,
      randomize: true,
    },
  },

  // Build configuration
  build: {
    autoDetectExternal: true,
    keepNames: true,
    minify: false,
    extensions: [], // Build extensions go here
  },

  // Global lifecycle hooks
  onStartAttempt: async ({ payload, ctx }) => {
    console.log("Global task start");
  },
  onSuccess: async ({ payload, output, ctx }) => {
    console.log("Global task success");
  },
  onFailure: async ({ payload, error, ctx }) => {
    console.log("Global task failure");
  },
});
```

## Build Extensions

### Database & ORM

#### Prisma

```ts
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";

extensions: [
  prismaExtension({
    schema: "prisma/schema.prisma",
    version: "5.19.0", // Optional: specify version
    migrate: true, // Run migrations during build
    directUrlEnvVarName: "DIRECT_DATABASE_URL",
    typedSql: true, // Enable TypedSQL support
  }),
];
```

#### TypeScript Decorators (for TypeORM)

```ts
import { emitDecoratorMetadata } from "@trigger.dev/build/extensions/typescript";

extensions: [
  emitDecoratorMetadata(), // Enables decorator metadata
];
```

### Scripting Languages

#### Python

```ts
import { pythonExtension } from "@trigger.dev/build/extensions/python";

extensions: [
  pythonExtension({
    scripts: ["./python/**/*.py"], // Copy Python files
    requirementsFile: "./requirements.txt", // Install packages
    devPythonBinaryPath: ".venv/bin/python", // Dev mode binary
  }),
];

// Usage in tasks
const result = await python.runInline(`print("Hello, world!")`);
const output = await python.runScript("./python/script.py", ["arg1"]);
```

### Browser Automation

#### Playwright

```ts
import { playwright } from "@trigger.dev/build/extensions/playwright";

extensions: [
  playwright({
    browsers: ["chromium", "firefox", "webkit"], // Default: ["chromium"]
    headless: true, // Default: true
  }),
];
```

#### Puppeteer

```ts
import { puppeteer } from "@trigger.dev/build/extensions/puppeteer";

extensions: [puppeteer()];

// Environment variable needed:
// PUPPETEER_EXECUTABLE_PATH: "/usr/bin/google-chrome-stable"
```

#### Lightpanda

```ts
import { lightpanda } from "@trigger.dev/build/extensions/lightpanda";

extensions: [
  lightpanda({
    version: "latest", // or "nightly"
    disableTelemetry: false,
  }),
];
```

### Media Processing

#### FFmpeg

```ts
import { ffmpeg } from "@trigger.dev/build/extensions/core";

extensions: [
  ffmpeg({ version: "7" }), // Static build, or omit for Debian version
];

// Automatically sets FFMPEG_PATH and FFPROBE_PATH
// Add fluent-ffmpeg to external packages if using
```

#### Audio Waveform

```ts
import { audioWaveform } from "@trigger.dev/build/extensions/audioWaveform";

extensions: [
  audioWaveform(), // Installs Audio Waveform 1.1.0
];
```

### System & Package Management

#### System Packages (apt-get)

```ts
import { aptGet } from "@trigger.dev/build/extensions/core";

extensions: [
  aptGet({
    packages: ["ffmpeg", "imagemagick", "curl=7.68.0-1"], // Can specify versions
  }),
];
```

#### Additional NPM Packages

Only use this for installing CLI tools, NOT packages you import in your code.

```ts
import { additionalPackages } from "@trigger.dev/build/extensions/core";

extensions: [
  additionalPackages({
    packages: ["wrangler"], // CLI tools and specific versions
  }),
];
```

#### Additional Files

```ts
import { additionalFiles } from "@trigger.dev/build/extensions/core";

extensions: [
  additionalFiles({
    files: ["wrangler.toml", "./assets/**", "./fonts/**"], // Glob patterns supported
  }),
];
```

### Environment & Build Tools

#### Environment Variable Sync

```ts
import { syncEnvVars } from "@trigger.dev/build/extensions/core";

extensions: [
  syncEnvVars(async (ctx) => {
    // ctx contains: environment, projectRef, env
    return [
      { name: "SECRET_KEY", value: await getSecret(ctx.environment) },
      { name: "API_URL", value: ctx.environment === "prod" ? "api.prod.com" : "api.dev.com" },
    ];
  }),
];
```

#### ESBuild Plugins

```ts
import { esbuildPlugin } from "@trigger.dev/build/extensions";
import { sentryEsbuildPlugin } from "@sentry/esbuild-plugin";

extensions: [
  esbuildPlugin(
    sentryEsbuildPlugin({
      org: process.env.SENTRY_ORG,
      project: process.env.SENTRY_PROJECT,
      authToken: process.env.SENTRY_AUTH_TOKEN,
    }),
    { placement: "last", target: "deploy" } // Optional config
  ),
];
```

## Custom Build Extensions

```ts
import { defineConfig } from "@trigger.dev/sdk";

const customExtension = {
  name: "my-custom-extension",

  externalsForTarget: (target) => {
    return ["some-native-module"]; // Add external dependencies
  },

  onBuildStart: async (context) => {
    console.log(`Build starting for ${context.target}`);
    // Register esbuild plugins, modify build context
  },

  onBuildComplete: async (context, manifest) => {
    console.log("Build complete, adding layers");
    // Add build layers, modify deployment
    context.addLayer({
      id: "my-layer",
      files: [{ source: "./custom-file", destination: "/app/custom" }],
      commands: ["chmod +x /app/custom"],
    });
  },
};

export default defineConfig({
  project: "my-project",
  build: {
    extensions: [customExtension],
  },
});
```

## Advanced Configuration

### Telemetry

```ts
import { PrismaInstrumentation } from "@prisma/instrumentation";
import { OpenAIInstrumentation } from "@langfuse/openai";

export default defineConfig({
  // ... other config
  telemetry: {
    instrumentations: [new PrismaInstrumentation(), new OpenAIInstrumentation()],
    exporters: [customExporter], // Optional custom exporters
  },
});
```

### Machine & Performance

```ts
export default defineConfig({
  // ... other config
  defaultMachine: "large-1x", // Default machine for all tasks
  maxDuration: 300, // Default max duration (seconds)
  enableConsoleLogging: true, // Console logging in development
});
```

## Common Extension Combinations

### Full-Stack Web App

```ts
extensions: [
  prismaExtension({ schema: "prisma/schema.prisma", migrate: true }),
  additionalFiles({ files: ["./public/**", "./assets/**"] }),
  syncEnvVars(async (ctx) => [...envVars]),
];
```

### AI/ML Processing

```ts
extensions: [
  pythonExtension({
    scripts: ["./ai/**/*.py"],
    requirementsFile: "./requirements.txt",
  }),
  ffmpeg({ version: "7" }),
  additionalPackages({ packages: ["wrangler"] }),
];
```

### Web Scraping

```ts
extensions: [
  playwright({ browsers: ["chromium"] }),
  puppeteer(),
  additionalFiles({ files: ["./selectors.json", "./proxies.txt"] }),
];
```

## Best Practices

- **Use specific versions**: Pin extension versions for reproducible builds
- **External packages**: Add modules with native addons to the `build.external` array
- **Environment sync**: Use `syncEnvVars` for dynamic secrets
- **File paths**: Use glob patterns for flexible file inclusion
- **Debug builds**: Use `--log-level debug --dry-run` for troubleshooting

Extensions only affect deployment, not local development. Use `external` array for packages that shouldn't be bundled.


================================================
FILE: .agents/skills/trigger-cost-savings/SKILL.md
================================================
---
name: trigger-cost-savings
description: Analyze Trigger.dev tasks, schedules, and runs for cost optimization opportunities. Use when asked to reduce spend, optimize costs, audit usage, right-size machines, or review task efficiency. Requires Trigger.dev MCP tools for run analysis.
---

# Trigger.dev Cost Savings Analysis

Analyze task runs and configurations to find cost reduction opportunities.

## Prerequisites: MCP Tools

This skill requires the **Trigger.dev MCP server** to analyze live run data.

### Check MCP availability

Before analysis, verify these MCP tools are available:
- `list_runs` — list runs with filters (status, task, time period, machine size)
- `get_run_details` — get run logs, duration, and status
- `get_current_worker` — get registered tasks and their configurations

If these tools are **not available**, instruct the user:

```
To analyze your runs, you need the Trigger.dev MCP server installed.

Run this command to install it:

  npx trigger.dev@latest install-mcp

This launches an interactive wizard that configures the MCP server for your AI client.
```

Do NOT proceed with run analysis without MCP tools. You can still review source code for static issues (see Static Analysis below).

### Load latest cost reduction documentation

Before giving recommendations, fetch the latest guidance:

```
WebFetch: https://trigger.dev/docs/how-to-reduce-your-spend
```

Use the fetched content to ensure recommendations are current. If the fetch fails, fall back to the reference documentation in `references/cost-reduction.md`.

## Analysis Workflow

### Step 1: Static Analysis (source code)

Scan task files in the project for these issues:

1. **Oversized machines** — tasks using `large-1x` or `large-2x` without clear need
2. **Missing `maxDuration`** — tasks without execution time limits (runaway cost risk)
3. **Excessive retries** — `maxAttempts` > 5 without `AbortTaskRunError` for known failures
4. **Missing debounce** — high-frequency triggers without debounce configuration
5. **Missing idempotency** — payment/critical tasks without idempotency keys
6. **Polling instead of waits** — `setTimeout`/`setInterval`/sleep loops instead of `wait.for()`
7. **Short waits** — `wait.for()` with < 5 seconds (not checkpointed, wastes compute)
8. **Sequential instead of batch** — multiple `triggerAndWait()` calls that could use `batchTriggerAndWait()`
9. **Over-scheduled crons** — schedules running more frequently than necessary

### Step 2: Run Analysis (requires MCP tools)

Use MCP tools to analyze actual usage patterns:

#### 2a. Identify expensive tasks

```
list_runs with filters:
- period: "30d" or "7d"
- Sort by duration or cost
- Check across different task IDs
```

Look for:
- Tasks with high total compute time (duration x run count)
- Tasks with high failure rates (wasted retries)
- Tasks running on large machines with short durations (over-provisioned)

#### 2b. Analyze failure patterns

```
list_runs with status: "FAILED" or "CRASHED"
```

For high-failure tasks:
- Check if failures are retryable (transient) vs permanent
- Suggest `AbortTaskRunError` for known non-retryable errors
- Calculate wasted compute from failed retries

#### 2c. Check machine utilization

```
get_run_details for sample runs of each task
```

Compare actual resource usage against machine preset:
- If a task on `large-2x` consistently runs in < 1 second, it's over-provisioned
- If tasks are I/O-bound (API calls, DB queries), they likely don't need large machines

#### 2d. Review schedule frequency

```
get_current_worker to list scheduled tasks and their cron patterns
```

Flag schedules that may be too frequent for their purpose.

### Step 3: Generate Recommendations

Present findings as a prioritized list with estimated impact:

```markdown
## Cost Optimization Report

### High Impact
1. **Right-size `process-images` machine** — Currently `large-2x`, average run 2s.
   Switching to `small-2x` could reduce this task's cost by ~16x.
   ```ts
   machine: { preset: "small-2x" }  // was "large-2x"
   ```

### Medium Impact
2. **Add debounce to `sync-user-data`** — 847 runs/day, often triggered in bursts.
   ```ts
   debounce: { key: `user-${userId}`, delay: "5s" }
   ```

### Low Impact / Best Practices
3. **Add `maxDuration` to `generate-report`** — No timeout configured.
   ```ts
   maxDuration: 300  // 5 minutes
   ```
```

## Machine Preset Costs (relative)

Larger machines cost proportionally more per second of compute:

| Preset | vCPU | RAM | Relative Cost |
|--------|------|-----|---------------|
| micro | 0.25 | 0.25 GB | 0.25x |
| small-1x | 0.5 | 0.5 GB | 1x (baseline) |
| small-2x | 1 | 1 GB | 2x |
| medium-1x | 1 | 2 GB | 2x |
| medium-2x | 2 | 4 GB | 4x |
| large-1x | 4 | 8 GB | 8x |
| large-2x | 8 | 16 GB | 16x |

## Key Principles

- **Waits > 5 seconds are free** — checkpointed, no compute charge
- **Start small, scale up** — default `small-1x` is right for most tasks
- **I/O-bound tasks don't need big machines** — API calls, DB queries wait on network
- **Debounce saves the most on high-frequency tasks** — consolidates bursts into single runs
- **Idempotency prevents duplicate work** — especially important for expensive operations
- **`AbortTaskRunError` stops wasteful retries** — don't retry permanent failures

See `references/cost-reduction.md` for detailed strategies with code examples.


================================================
FILE: .agents/skills/trigger-cost-savings/references/cost-reduction.md
================================================
# Cost Reduction Strategies

Detailed strategies for reducing Trigger.dev spend. For the latest version, fetch:
https://trigger.dev/docs/how-to-reduce-your-spend

## 1. Monitor Usage

Review your usage dashboard regularly to identify:
- Most expensive tasks (by total compute time)
- Run counts and daily spikes
- Failure rates and wasted retries

## 2. Configure Billing Alerts

Set up alerts in the Trigger.dev dashboard:
- **Standard alerts**: Notifications at 75%, 90%, 100%, 200%, 500% of budget
- **Spike alerts**: Protection at 10x, 20x, 50x, 100x of monthly budget

Keep spike alerts enabled as a safety net against runaway costs.

## 3. Right-Size Machines

Start with the smallest machine and scale only when necessary:

```ts
// Default (small-1x) is right for most tasks
export const apiTask = task({
  id: "call-api",
  // No machine preset needed — defaults to small-1x
  run: async (payload) => {
    const response = await fetch("https://api.example.com/data");
    return response.json();
  },
});

// Only use larger machines for CPU/memory-intensive work
export const imageProcessor = task({
  id: "process-image",
  machine: { preset: "medium-1x" }, // Only if actually needed
  run: async (payload) => {
    // Heavy image processing that needs more RAM
  },
});

// Override machine at trigger time for variable workloads
await imageProcessor.trigger(largePayload, {
  machine: { preset: "large-1x" }, // Larger only for this specific run
});
```

## 4. Use Idempotency Keys

Prevent duplicate execution of expensive operations:

```ts
import { task, idempotencyKeys } from "@trigger.dev/sdk";

export const expensiveTask = task({
  id: "expensive-operation",
  run: async (payload: { orderId: string }) => {
    const key = await idempotencyKeys.create(`order-${payload.orderId}`);

    // This won't re-execute if triggered again with same key
    await costlyChildTask.trigger(payload, {
      idempotencyKey: key,
      idempotencyKeyTTL: "24h",
    });
  },
});
```

## 5. Parallelize Within Tasks

Consolidate multiple async operations into single tasks instead of spawning many:

```ts
// Expensive: 3 separate task runs
await taskA.triggerAndWait(data);
await taskB.triggerAndWait(data);
await taskC.triggerAndWait(data);

// Cheaper: single task with parallel I/O (when work is I/O-bound)
export const combinedTask = task({
  id: "combined-api-calls",
  run: async (payload) => {
    const [a, b, c] = await Promise.all([
      fetch("https://api-a.com"),
      fetch("https://api-b.com"),
      fetch("https://api-c.com"),
    ]);
    return { a: await a.json(), b: await b.json(), c: await c.json() };
  },
});
```

Note: Only use `Promise.all` for regular async operations (fetch, DB queries), NOT for `triggerAndWait()` or `wait.*` calls.

## 6. Optimize Retries

Reduce wasted compute from retries:

```ts
import { task, AbortTaskRunError } from "@trigger.dev/sdk";

export const smartRetryTask = task({
  id: "smart-retry",
  retry: {
    maxAttempts: 3, // Not 10 — be realistic
  },
  catchError: async ({ error }) => {
    // Don't retry known permanent failures
    if (error.message?.includes("NOT_FOUND")) {
      throw new AbortTaskRunError("Resource not found — won't retry");
    }
    if (error.message?.includes("UNAUTHORIZED")) {
      throw new AbortTaskRunError("Auth failed — won't retry");
    }
    // Only retry transient errors
  },
  run: async (payload) => {
    // task logic
  },
});
```

## 7. Set maxDuration

Prevent runaway tasks from consuming unlimited compute:

```ts
export const boundedTask = task({
  id: "bounded-task",
  maxDuration: 300, // 5 minutes max
  run: async (payload) => {
    // If this takes longer than 5 minutes, it's killed
  },
});
```

## 8. Use Waitpoints Instead of Polling

Waits > 5 seconds are checkpointed and free:

```ts
// Expensive: polling loop burns compute
export const pollingTask = task({
  id: "polling-bad",
  run: async (payload) => {
    while (true) {
      const status = await checkStatus(payload.id);
      if (status === "ready") break;
      await new Promise((r) => setTimeout(r, 5000)); // WASTES compute
    }
  },
});

// Free: checkpointed wait
import { wait } from "@trigger.dev/sdk";

export const waitTask = task({
  id: "wait-good",
  run: async (payload) => {
    await wait.for({ minutes: 5 }); // FREE — checkpointed
    const status = await checkStatus(payload.id);
    if (status !== "ready") {
      await wait.for({ minutes: 5 }); // Still free
    }
  },
});
```

## 9. Debounce High-Frequency Triggers

Consolidate bursts into single executions:

```ts
// Without debounce: 100 webhook events = 100 task runs
await syncTask.trigger({ userId: "123" });

// With debounce: 100 events in 5s = 1 task run
await syncTask.trigger(
  { userId: "123" },
  {
    debounce: {
      key: "sync-user-123",
      delay: "5s",
      mode: "trailing", // Use latest payload
    },
  }
);
```

## Cost Checklist

Use this checklist when reviewing tasks:

- [ ] Machine preset matches actual resource needs (start with `small-1x`)
- [ ] `maxDuration` is set to a reasonable limit
- [ ] Retry `maxAttempts` is appropriate (not excessive)
- [ ] `AbortTaskRunError` used for known permanent failures
- [ ] Idempotency keys used for expensive/critical operations
- [ ] `wait.for()` used instead of polling loops (with delays > 5s)
- [ ] Debounce configured for high-frequency trigger sources
- [ ] Batch triggering used instead of sequential `triggerAndWait()` loops
- [ ] Scheduled task frequency matches actual business needs
- [ ] Billing alerts configured in dashboard


================================================
FILE: .agents/skills/trigger-realtime/SKILL.md
================================================
---
name: trigger-realtime
description: Subscribe to Trigger.dev task runs in real-time from frontend and backend. Use when building progress indicators, live dashboards, streaming AI/LLM responses, or React components that display task status.
---

# Trigger.dev Realtime

Subscribe to task runs and stream data in real-time from frontend and backend.

## When to Use

- Building progress indicators for long-running tasks
- Creating live dashboards showing task status
- Streaming AI/LLM responses to the UI
- React components that trigger and monitor tasks
- Waiting for user approval in tasks

## Authentication

### Create Public Access Token (Backend)

```ts
import { auth } from "@trigger.dev/sdk";

// Read-only token for specific runs
const publicToken = await auth.createPublicToken({
  scopes: {
    read: {
      runs: ["run_123"],
      tasks: ["my-task"],
    },
  },
  expirationTime: "1h",
});

// Pass this token to your frontend
```

### Create Trigger Token (for frontend triggering)

```ts
const triggerToken = await auth.createTriggerPublicToken("my-task", {
  expirationTime: "30m",
});
```

## Backend Subscriptions

```ts
import { runs, tasks } from "@trigger.dev/sdk";

// Trigger and subscribe
const handle = await tasks.trigger("my-task", { data: "value" });

for await (const run of runs.subscribeToRun(handle.id)) {
  console.log(`Status: ${run.status}`);
  console.log(`Progress: ${run.metadata?.progress}`);
  
  if (run.status === "COMPLETED") {
    console.log("Output:", run.output);
    break;
  }
}

// Subscribe to tagged runs
for await (const run of runs.subscribeToRunsWithTag("user-123")) {
  console.log(`Run ${run.id}: ${run.status}`);
}

// Subscribe to batch
for await (const run of runs.subscribeToBatch(batchId)) {
  console.log(`Batch run ${run.id}: ${run.status}`);
}
```

## React Hooks

### Installation

```bash
npm add @trigger.dev/react-hooks
```

### Trigger Task from React

```tsx
"use client";
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/tasks";

function TaskTrigger({ accessToken }: { accessToken: string }) {
  const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof myTask>(
    "my-task",
    { accessToken }
  );

  return (
    <div>
      <button 
        onClick={() => submit({ data: "value" })} 
        disabled={isLoading}
      >
        Start Task
      </button>
      
      {run && (
        <div>
          <p>Status: {run.status}</p>
          <p>Progress: {run.metadata?.progress}%</p>
          {run.output && <p>Result: {JSON.stringify(run.output)}</p>}
        </div>
      )}
    </div>
  );
}
```

### Subscribe to Existing Run

```tsx
"use client";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/tasks";

function RunStatus({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { run, error } = useRealtimeRun<typeof myTask>(runId, {
    accessToken,
    onComplete: (run) => {
      console.log("Completed:", run.output);
    },
  });

  if (error) return <div>Error: {error.message}</div>;
  if (!run) return <div>Loading...</div>;

  return (
    <div>
      <p>Status: {run.status}</p>
      <p>Progress: {run.metadata?.progress || 0}%</p>
    </div>
  );
}
```

### Subscribe to Tagged Runs

```tsx
"use client";
import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";

function UserTasks({ userId, accessToken }: { userId: string; accessToken: string }) {
  const { runs } = useRealtimeRunsWithTag(`user-${userId}`, { accessToken });

  return (
    <ul>
      {runs.map((run) => (
        <li key={run.id}>{run.id}: {run.status}</li>
      ))}
    </ul>
  );
}
```

## Realtime Streams (AI/LLM)

### Define Stream (shared location)

```ts
// trigger/streams.ts
import { streams } from "@trigger.dev/sdk";

export const aiStream = streams.define<string>({
  id: "ai-output",
});
```

### Pipe Stream in Task

```ts
import { task } from "@trigger.dev/sdk";
import { aiStream } from "./streams";

export const streamingTask = task({
  id: "streaming-task",
  run: async (payload: { prompt: string }) => {
    const completion = await openai.chat.completions.create({
      model: "gpt-4",
      messages: [{ role: "user", content: payload.prompt }],
      stream: true,
    });

    const { waitUntilComplete } = aiStream.pipe(completion);
    await waitUntilComplete();
  },
});
```

### Read Stream in React

```tsx
"use client";
import { useRealtimeStream } from "@trigger.dev/react-hooks";
import { aiStream } from "../trigger/streams";

function AIResponse({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { parts, error } = useRealtimeStream(aiStream, runId, {
    accessToken,
    throttleInMs: 50,
  });

  if (error) return <div>Error: {error.message}</div>;
  if (!parts) return <div>Waiting for response...</div>;

  return <div>{parts.join("")}</div>;
}
```

## Wait Tokens (Human-in-the-loop)

### In Task

```ts
import { task, wait } from "@trigger.dev/sdk";

export const approvalTask = task({
  id: "approval-task",
  run: async (payload) => {
    // Process initial data
    const processed = await processData(payload);

    // Wait for human approval
    const approval = await wait.forToken<{ approved: boolean }>({
      token: `approval-${payload.id}`,
      timeoutInSeconds: 86400, // 24 hours
    });

    if (approval.approved) {
      return await finalizeData(processed);
    }
    
    throw new Error("Not approved");
  },
});
```

### Complete Token from React

```tsx
"use client";
import { useWaitToken } from "@trigger.dev/react-hooks";

function ApprovalButton({ tokenId, accessToken }: { tokenId: string; accessToken: string }) {
  const { complete } = useWaitToken(tokenId, { accessToken });

  return (
    <div>
      <button onClick={() => complete({ approved: true })}>
        Approve
      </button>
      <button onClick={() => complete({ approved: false })}>
        Reject
      </button>
    </div>
  );
}
```

## Run Object Properties

| Property | Description |
|----------|-------------|
| `id` | Unique run identifier |
| `status` | `QUEUED`, `EXECUTING`, `COMPLETED`, `FAILED`, `CANCELED` |
| `payload` | Task input (typed) |
| `output` | Task result (typed, when completed) |
| `metadata` | Real-time updatable data |
| `createdAt` | Start timestamp |
| `costInCents` | Execution cost |

## Best Practices

1. **Scope tokens narrowly** — only grant necessary permissions
2. **Set expiration times** — don't use long-lived tokens
3. **Use typed hooks** — pass task types for proper inference
4. **Handle errors** — always check for errors in hooks
5. **Throttle streams** — use `throttleInMs` to control re-renders

See `references/realtime.md` for complete documentation.


================================================
FILE: .agents/skills/trigger-realtime/references/realtime.md
================================================
# Trigger.dev Realtime

**Real-time monitoring and updates for runs**

## Core Concepts

Realtime allows you to:

- Subscribe to run status changes, metadata updates, and streams
- Build real-time dashboards and UI updates
- Monitor task progress from frontend and backend

## Authentication

### Public Access Tokens

```ts
import { auth } from "@trigger.dev/sdk";

// Read-only token for specific runs
const publicToken = await auth.createPublicToken({
  scopes: {
    read: {
      runs: ["run_123", "run_456"],
      tasks: ["my-task-1", "my-task-2"],
    },
  },
  expirationTime: "1h", // Default: 15 minutes
});
```

### Trigger Tokens (Frontend only)

```ts
// Single-use token for triggering tasks
const triggerToken = await auth.createTriggerPublicToken("my-task", {
  expirationTime: "30m",
});
```

## Backend Usage

### Subscribe to Runs

```ts
import { runs, tasks } from "@trigger.dev/sdk";

// Trigger and subscribe
const handle = await tasks.trigger("my-task", { data: "value" });

// Subscribe to specific run
for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {
  console.log(`Status: ${run.status}, Progress: ${run.metadata?.progress}`);
  if (run.status === "COMPLETED") break;
}

// Subscribe to runs with tag
for await (const run of runs.subscribeToRunsWithTag("user-123")) {
  console.log(`Tagged run ${run.id}: ${run.status}`);
}

// Subscribe to batch
for await (const run of runs.subscribeToBatch(batchId)) {
  console.log(`Batch run ${run.id}: ${run.status}`);
}
```

### Realtime Streams v2

```ts
import { streams, InferStreamType } from "@trigger.dev/sdk";

// 1. Define streams (shared location)
export const aiStream = streams.define<string>({
  id: "ai-output",
});

export type AIStreamPart = InferStreamType<typeof aiStream>;

// 2. Pipe from task
export const streamingTask = task({
  id: "streaming-task",
  run: async (payload) => {
    const completion = await openai.chat.completions.create({
      model: "gpt-4",
      messages: [{ role: "user", content: payload.prompt }],
      stream: true,
    });

    const { waitUntilComplete } = aiStream.pipe(completion);
    await waitUntilComplete();
  },
});

// 3. Read from backend
const stream = await aiStream.read(runId, {
  timeoutInSeconds: 300,
  startIndex: 0, // Resume from specific chunk
});

for await (const chunk of stream) {
  console.log("Chunk:", chunk); // Fully typed
}
```

## React Frontend Usage

### Installation

```bash
npm add @trigger.dev/react-hooks
```

### Triggering Tasks

```tsx
"use client";
import { useTaskTrigger, useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/tasks";

function TriggerComponent({ accessToken }: { accessToken: string }) {
  // Basic trigger
  const { submit, handle, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
    accessToken,
  });

  // Trigger with realtime updates
  const {
    submit: realtimeSubmit,
    run,
    isLoading: isRealtimeLoading,
  } = useRealtimeTaskTrigger<typeof myTask>("my-task", { accessToken });

  return (
    <div>
      <button onClick={() => submit({ data: "value" })} disabled={isLoading}>
        Trigger Task
      </button>

      <button onClick={() => realtimeSubmit({ data: "realtime" })} disabled={isRealtimeLoading}>
        Trigger with Realtime
      </button>

      {run && <div>Status: {run.status}</div>}
    </div>
  );
}
```

### Subscribing to Runs

```tsx
"use client";
import { useRealtimeRun, useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";
import type { myTask } from "../trigger/tasks";

function SubscribeComponent({ runId, accessToken }: { runId: string; accessToken: string }) {
  // Subscribe to specific run
  const { run, error } = useRealtimeRun<typeof myTask>(runId, {
    accessToken,
    onComplete: (run) => {
      console.log("Task completed:", run.output);
    },
  });

  // Subscribe to tagged runs
  const { runs } = useRealtimeRunsWithTag("user-123", { accessToken });

  if (error) return <div>Error: {error.message}</div>;
  if (!run) return <div>Loading...</div>;

  return (
    <div>
      <div>Status: {run.status}</div>
      <div>Progress: {run.metadata?.progress || 0}%</div>
      {run.output && <div>Result: {JSON.stringify(run.output)}</div>}

      <h3>Tagged Runs:</h3>
      {runs.map((r) => (
        <div key={r.id}>
          {r.id}: {r.status}
        </div>
      ))}
    </div>
  );
}
```

### Realtime Streams with React

```tsx
"use client";
import { useRealtimeStream } from "@trigger.dev/react-hooks";
import { aiStream } from "../trigger/streams";

function StreamComponent({ runId, accessToken }: { runId: string; accessToken: string }) {
  // Pass defined stream directly for type safety
  const { parts, error } = useRealtimeStream(aiStream, runId, {
    accessToken,
    timeoutInSeconds: 300,
    throttleInMs: 50, // Control re-render frequency
  });

  if (error) return <div>Error: {error.message}</div>;
  if (!parts) return <div>Loading...</div>;

  const text = parts.join(""); // parts is typed as AIStreamPart[]

  return <div>Streamed Text: {text}</div>;
}
```

### Wait Tokens

```tsx
"use client";
import { useWaitToken } from "@trigger.dev/react-hooks";

function WaitTokenComponent({ tokenId, accessToken }: { tokenId: string; accessToken: string }) {
  const { complete } = useWaitToken(tokenId, { accessToken });

  return <button onClick={() => complete({ approved: true })}>Approve Task</button>;
}
```

## Run Object Properties

Key properties available in run subscriptions:

- `id`: Unique run identifier
- `status`: `QUEUED`, `EXECUTING`, `COMPLETED`, `FAILED`, `CANCELED`, etc.
- `payload`: Task input data (typed)
- `output`: Task result (typed, when completed)
- `metadata`: Real-time updatable data
- `createdAt`, `updatedAt`: Timestamps
- `costInCents`: Execution cost

## Best Practices

- **Use Realtime over SWR**: Recommended for most use cases due to rate limits
- **Scope tokens properly**: Only grant necessary read/trigger permissions
- **Handle errors**: Always check for errors in hooks and subscriptions
- **Type safety**: Use task types for proper payload/output typing
- **Cleanup subscriptions**: Backend subscriptions auto-complete, frontend hooks auto-cleanup


================================================
FILE: .agents/skills/trigger-setup/SKILL.md
================================================
---
name: trigger-setup
description: Set up Trigger.dev in your project. Use when adding Trigger.dev for the first time, creating trigger.config.ts, or initializing the trigger directory.
---

# Trigger.dev Setup

Get Trigger.dev running in your project in minutes.

## When to Use

- Adding Trigger.dev to an existing project
- Creating your first task
- Setting up trigger.config.ts
- Connecting to Trigger.dev cloud

## Prerequisites

- Node.js 18+ or Bun
- A Trigger.dev account (https://cloud.trigger.dev)

## Quick Start

### 1. Install the SDK

```bash
npm install @trigger.dev/sdk
```

### 2. Initialize Your Project

```bash
npx trigger init
```

This creates:
- `trigger.config.ts` - project configuration
- `trigger/` directory - where your tasks live
- `trigger/example.ts` - a sample task

### 3. Configure trigger.config.ts

```ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "proj_xxxxx", // From dashboard
  dirs: ["./trigger"],
});
```

### 4. Create Your First Task

```ts
// trigger/my-task.ts
import { task } from "@trigger.dev/sdk";

export const myFirstTask = task({
  id: "my-first-task",
  run: async (payload: { name: string }) => {
    console.log(`Hello, ${payload.name}!`);
    return { message: `Processed ${payload.name}` };
  },
});
```

### 5. Start Development Server

```bash
npx trigger dev
```

### 6. Trigger Your Task

From your app code:

```ts
import { tasks } from "@trigger.dev/sdk";
import type { myFirstTask } from "./trigger/my-task";

await tasks.trigger<typeof myFirstTask>("my-first-task", {
  name: "World",
});
```

Or from the Trigger.dev dashboard "Test" tab.

## Project Structure

```
your-project/
├── trigger.config.ts    # Required - project config
├── trigger/             # Required - task files
│   ├── my-task.ts
│   └── another-task.ts
├── package.json
└── ...
```

## Environment Variables

Create `.env` or set in your environment:

```bash
TRIGGER_SECRET_KEY=tr_dev_xxxxx  # From dashboard > API Keys
```

## Common Issues

### "No tasks found"
- Ensure tasks are **exported** from files in `dirs` folders
- Check `trigger.config.ts` points to correct directories

### "Project not found"
- Verify `project` in config matches dashboard
- Check `TRIGGER_SECRET_KEY` is set

### "Task not registered"
- Restart `npx trigger dev` after adding new tasks
- Tasks must use `task()` or `schemaTask()` from `@trigger.dev/sdk`

## Next Steps

- Add retry logic → see **trigger-tasks** skill
- Configure build extensions → see **trigger-config** skill
- Build AI workflows → see **trigger-agents** skill
- Add real-time UI → see **trigger-realtime** skill


================================================
FILE: .agents/skills/trigger-setup/references/environment-setup.md
================================================
# Environment Setup

## Required Variables

| Variable | Description | Where to find |
|----------|-------------|---------------|
| `TRIGGER_SECRET_KEY` | API key for authentication | Dashboard > API Keys |

## Development vs Production Keys

```bash
# Development (starts with tr_dev_)
TRIGGER_SECRET_KEY=tr_dev_xxxxx

# Production (starts with tr_prod_)
TRIGGER_SECRET_KEY=tr_prod_xxxxx
```

## Local Development

### Option 1: .env File

```bash
# .env
TRIGGER_SECRET_KEY=tr_dev_xxxxx
```

Add to `.gitignore`:

```
.env
.env.local
```

### Option 2: Shell Export

```bash
export TRIGGER_SECRET_KEY=tr_dev_xxxxx
npx trigger dev
```

## CI/CD Deployment

### GitHub Actions

```yaml
- name: Deploy Trigger.dev
  env:
    TRIGGER_SECRET_KEY: ${{ secrets.TRIGGER_SECRET_KEY }}
  run: npx trigger deploy
```

### Vercel

Add `TRIGGER_SECRET_KEY` in Project Settings > Environment Variables.

### Other Platforms

Set `TRIGGER_SECRET_KEY` in your platform's secret management.

## Multi-Environment Setup

Use different keys per environment:

| Environment | Key Prefix | Dashboard Section |
|-------------|------------|-------------------|
| Development | `tr_dev_` | Dev environment |
| Staging | `tr_stg_` | Staging environment |
| Production | `tr_prod_` | Prod environment |

## Task Environment Variables

Tasks run in Trigger.dev's infrastructure. To use env vars in tasks:

1. **Sync from local** (using `syncEnvVars` extension):

```ts
// trigger.config.ts
import { defineConfig } from "@trigger.dev/sdk";
import { syncEnvVars } from "@trigger.dev/build/extensions/core";

export default defineConfig({
  project: "proj_xxxxx",
  build: {
    extensions: [
      syncEnvVars(),
    ],
  },
});
```

2. **Set in dashboard**: Project Settings > Environment Variables

3. **Access in tasks**:

```ts
export const myTask = task({
  id: "my-task",
  run: async () => {
    const apiKey = process.env.EXTERNAL_API_KEY;
    // ...
  },
});
```


================================================
FILE: .agents/skills/trigger-setup/references/project-structure.md
================================================
# Project Structure

## Default Layout

```
your-project/
├── trigger.config.ts    # Required - project configuration
├── trigger/             # Default task directory
│   ├── example.ts       # Created by `npx trigger init`
│   └── ...
├── package.json
└── src/                 # Your app code
```

## Monorepo Layout

For monorepos, place `trigger.config.ts` in the package that contains your tasks:

```
monorepo/
├── packages/
│   ├── api/
│   │   ├── trigger.config.ts  # Config here
│   │   ├── trigger/           # Tasks here
│   │   └── src/
│   └── web/
└── package.json
```

## Multiple Task Directories

Configure multiple directories in `trigger.config.ts`:

```ts
import { defineConfig } from "@trigger.dev/sdk";

export default defineConfig({
  project: "proj_xxxxx",
  dirs: [
    "./trigger",           // Default
    "./src/jobs",          // Additional
    "./src/scheduled",     // Another
  ],
});
```

## Collocated Tasks

Keep tasks next to related code:

```
src/
├── users/
│   ├── routes.ts
│   └── tasks/
│       └── send-welcome-email.ts
├── orders/
│   ├── routes.ts
│   └── tasks/
│       └── process-order.ts
```

```ts
// trigger.config.ts
export default defineConfig({
  project: "proj_xxxxx",
  dirs: ["./src/**/tasks"],  // Glob pattern
});
```

## Task File Requirements

Each task file must:

1. **Export** tasks (named exports)
2. Use `task()` or `schemaTask()` from `@trigger.dev/sdk`
3. Have unique task IDs across all files

```ts
// ✅ Correct - exported task
export const myTask = task({
  id: "my-task",  // Unique ID
  run: async (payload) => { ... },
});

// ❌ Wrong - not exported
const privateTask = task({ ... });

// ❌ Wrong - duplicate ID will error
export const anotherTask = task({
  id: "my-task",  // Conflicts!
  run: async (payload) => { ... },
});
```


================================================
FILE: .agents/skills/trigger-tasks/SKILL.md
================================================
---
name: trigger-tasks
description: Build AI agents, workflows and durable background tasks with Trigger.dev. Use when creating tasks, triggering jobs, handling retries, scheduling cron jobs, or implementing queues and concurrency control.
---

# Trigger.dev Tasks

Build durable background tasks that run reliably with automatic retries, queuing, and observability.

## When to Use

- Creating background jobs or async workflows
- Building AI agents that need long-running execution
- Processing webhooks, emails, or file uploads
- Scheduling recurring tasks (cron)
- Any work that shouldn't block your main application

## Critical Rules

1. **Always use `@trigger.dev/sdk`** — never use deprecated `client.defineJob`
2. **Check `result.ok`** before accessing `result.output` from `triggerAndWait()`
3. **Never use `Promise.all`** with `triggerAndWait()` or `wait.*` calls
4. **Export tasks** from files in your `trigger/` directory

## Basic Task

```ts
import { task } from "@trigger.dev/sdk";

export const processData = task({
  id: "process-data",
  retry: {
    maxAttempts: 10,
    factor: 1.8,
    minTimeoutInMs: 500,
    maxTimeoutInMs: 30_000,
  },
  run: async (payload: { userId: string; data: any[] }) => {
    console.log(`Processing ${payload.data.length} items`);
    return { processed: payload.data.length };
  },
});
```

## Schema Task (Validated Input)

```ts
import { schemaTask } from "@trigger.dev/sdk";
import { z } from "zod";

export const validatedTask = schemaTask({
  id: "validated-task",
  schema: z.object({
    name: z.string(),
    email: z.string().email(),
  }),
  run: async (payload) => {
    // payload is typed and validated
    return { message: `Hello ${payload.name}` };
  },
});
```

## Triggering Tasks

### From Backend Code

```ts
import { tasks } from "@trigger.dev/sdk";
import type { processData } from "./trigger/tasks";

// Single trigger (fire and forget)
const handle = await tasks.trigger<typeof processData>("process-data", {
  userId: "123",
  data: [{ id: 1 }],
});

// Batch trigger (up to 1,000 items, 3MB per payload)
const batchHandle = await tasks.batchTrigger<typeof processData>("process-data", [
  { payload: { userId: "123", data: [] } },
  { payload: { userId: "456", data: [] } },
]);
```

### From Inside Tasks

```ts
export const parentTask = task({
  id: "parent-task",
  run: async (payload) => {
    // Fire and forget
    const handle = await childTask.trigger({ data: "value" });

    // Wait for result - returns Result object, NOT direct output
    const result = await childTask.triggerAndWait({ data: "value" });
    if (result.ok) {
      console.log("Output:", result.output);
    } else {
      console.error("Failed:", result.error);
    }

    // Quick unwrap (throws on error)
    const output = await childTask.triggerAndWait({ data: "value" }).unwrap();

    // Batch with wait
    const results = await childTask.batchTriggerAndWait([
      { payload: { data: "item1" } },
      { payload: { data: "item2" } },
    ]);
  },
});
```

## Waits

```ts
import { task, wait } from "@trigger.dev/sdk";

export const taskWithWaits = task({
  id: "task-with-waits",
  run: async (payload) => {
    await wait.for({ seconds: 30 });
    await wait.for({ minutes: 5 });
    await wait.until({ date: new Date("2024-12-25") });

    // Wait for external approval
    await wait.forToken({
      token: "user-approval-token",
      timeoutInSeconds: 3600,
    });
  },
});
```

> Waits > 5 seconds are checkpointed and don't count toward compute.

## Concurrency & Queues

```ts
import { task, queue } from "@trigger.dev/sdk";

// Shared queue
const emailQueue = queue({
  name: "email-processing",
  concurrencyLimit: 5,
});

// Task-level concurrency
export const oneAtATime = task({
  id: "sequential-task",
  queue: { concurrencyLimit: 1 },
  run: async (payload) => {
    // Only one instance runs at a time
  },
});

// Use shared queue
export const emailTask = task({
  id: "send-email",
  queue: emailQueue,
  run: async (payload) => {},
});

// Per-tenant concurrency (at trigger time)
await childTask.trigger(payload, {
  queue: {
    name: `user-${userId}`,
    concurrencyLimit: 2,
  },
});
```

## Debouncing

Consolidate rapid triggers into a single execution:

```ts
await myTask.trigger(
  { userId: "123" },
  {
    debounce: {
      key: "user-123-update",
      delay: "5s",
      mode: "trailing", // Use latest payload (default: "leading")
    },
  }
);
```

## Idempotency

```ts
import { task, idempotencyKeys } from "@trigger.dev/sdk";

export const paymentTask = task({
  id: "process-payment",
  run: async (payload: { orderId: string }) => {
    const key = await idempotencyKeys.create(`payment-${payload.orderId}`);

    await chargeCustomer.trigger(payload, {
      idempotencyKey: key,
      idempotencyKeyTTL: "24h",
    });
  },
});
```

## Error Handling & Retries

```ts
import { task, retry, AbortTaskRunError } from "@trigger.dev/sdk";

export const resilientTask = task({
  id: "resilient-task",
  retry: {
    maxAttempts: 10,
    factor: 1.8,
    minTimeoutInMs: 500,
    maxTimeoutInMs: 30_000,
  },
  catchError: async ({ error, ctx }) => {
    if (error.code === "FATAL_ERROR") {
      throw new AbortTaskRunError("Cannot retry");
    }
    return { retryAt: new Date(Date.now() + 60000) };
  },
  run: async (payload) => {
    // Retry specific operations
    const result = await retry.onThrow(
      async () => unstableApiCall(payload),
      { maxAttempts: 3 }
    );

    // HTTP retries with conditions
    const response = await retry.fetch("https://api.example.com", {
      retry: {
        maxAttempts: 5,
        condition: (res, err) => res?.status === 429 || res?.status >= 500,
      },
    });
  },
});
```

## Scheduled Tasks (Cron)

```ts
import { schedules } from "@trigger.dev/sdk";

// Declarative schedule
export const dailyTask = schedules.task({
  id: "daily-cleanup",
  cron: "0 0 * * *", // Midnight UTC
  run: async (payload) => {
    // payload.timestamp - scheduled time
    // payload.timezone - IANA timezone
    // payload.scheduleId - schedule identifier
  },
});

// With timezone
export const tokyoTask = schedules.task({
  id: "tokyo-morning",
  cron: { pattern: "0 9 * * *", timezone: "Asia/Tokyo" },
  run: async () => {},
});

// Dynamic/multi-tenant schedules
await schedules.create({
  task: "reminder-task",
  cron: "0 8 * * *",
  timezone: "America/New_York",
  externalId: userId,
  deduplicationKey: `${userId}-daily`,
});
```

## Metadata & Progress

```ts
import { task, metadata } from "@trigger.dev/sdk";

export const batchProcessor = task({
  id: "batch-processor",
  run: async (payload: { items: any[] }) => {
    metadata.set("progress", 0).set("total", payload.items.length);

    for (let i = 0; i < payload.items.length; i++) {
      await processItem(payload.items[i]);
      metadata.set("progress", ((i + 1) / payload.items.length) * 100);
    }

    metadata.set("status", "completed");
  },
});
```

## Tags

```ts
import { task, tags } from "@trigger.dev/sdk";

export const processUser = task({
  id: "process-user",
  run: async (payload: { userId: string }) => {
    await tags.add(`user_${payload.userId}`);
  },
});

// Trigger with tags
await processUser.trigger(
  { userId: "123" },
  { tags: ["priority", "user_123"] }
);
```

## Machine Presets

```ts
export const heavyTask = task({
  id: "heavy-computation",
  machine: { preset: "large-2x" }, // 8 vCPU, 16 GB RAM
  maxDuration: 1800, // 30 minutes
  run: async (payload) => {},
});
```

| Preset | vCPU | RAM |
|--------|------|-----|
| micro | 0.25 | 0.25 GB |
| small-1x | 0.5 | 0.5 GB (default) |
| small-2x | 1 | 1 GB |
| medium-1x | 1 | 2 GB |
| medium-2x | 2 | 4 GB |
| large-1x | 4 | 8 GB |
| large-2x | 8 | 16 GB |

## Best Practices

1. **Make tasks idempotent** — safe to retry without side effects
2. **Use queues** to prevent overwhelming external services
3. **Configure appropriate retries** with exponential backoff
4. **Track progress with metadata** for long-running tasks
5. **Use debouncing** for user activity and webhook bursts
6. **Match machine size** to computational requirements

See `references/` for detailed documentation on each feature.


================================================
FILE: .agents/skills/trigger-tasks/references/advanced-tasks.md
================================================
# Trigger.dev Advanced Tasks (v4)

**Advanced patterns and features for writing tasks**

## Tags & Organization

```ts
import { task, tags } from "@trigger.dev/sdk";

export const processUser = task({
  id: "process-user",
  run: async (payload: { userId: string; orgId: string }, { ctx }) => {
    // Add tags during execution
    await tags.add(`user_${payload.userId}`);
    await tags.add(`org_${payload.orgId}`);

    return { processed: true };
  },
});

// Trigger with tags
await processUser.trigger(
  { userId: "123", orgId: "abc" },
  { tags: ["priority", "user_123", "org_abc"] } // Max 10 tags per run
);

// Subscribe to tagged runs
for await (const run of runs.subscribeToRunsWithTag("user_123")) {
  console.log(`User task ${run.id}: ${run.status}`);
}
```

**Tag Best Practices:**

- Use prefixes: `user_123`, `org_abc`, `video:456`
- Max 10 tags per run, 1-64 characters each
- Tags don't propagate to child tasks automatically

## Batch Triggering v2

Enhanced batch triggering with larger payloads and streaming ingestion.

### Limits

- **Maximum batch size**: 1,000 items (increased from 500)
- **Payload per item**: 3MB each (increased from 1MB combined)
- Payloads > 512KB automatically offload to object storage

### Rate Limiting (per environment)

| Tier | Bucket Size | Refill Rate |
|------|-------------|-------------|
| Free | 1,200 runs | 100 runs/10 sec |
| Hobby | 5,000 runs | 500 runs/5 sec |
| Pro | 5,000 runs | 500 runs/5 sec |

### Concurrent Batch Processing

| Tier | Concurrent Batches |
|------|-------------------|
| Free | 1 |
| Hobby | 10 |
| Pro | 10 |

### Usage

```ts
import { myTask } from "./trigger/myTask";

// Basic batch trigger (up to 1,000 items)
const runs = await myTask.batchTrigger([
  { payload: { userId: "user-1" } },
  { payload: { userId: "user-2" } },
  { payload: { userId: "user-3" } },
]);

// Batch trigger with wait
const results = await myTask.batchTriggerAndWait([
  { payload: { userId: "user-1" } },
  { payload: { userId: "user-2" } },
]);

for (const result of results) {
  if (result.ok) {
    console.log("Result:", result.output);
  }
}

// With per-item options
const batchHandle = await myTask.batchTrigger([
  {
    payload: { userId: "123" },
    options: {
      idempotencyKey: "user-123-batch",
      tags: ["priority"],
    },
  },
  {
    payload: { userId: "456" },
    options: {
      idempotencyKey: "user-456-batch",
    },
  },
]);
```

## Debouncing

Consolidate multiple triggers into a single execution by debouncing task runs with a unique key and delay window.

### Use Cases

- **User activity updates**: Batch rapid user actions into a single run
- **Webhook deduplication**: Handle webhook bursts without redundant processing
- **Search indexing**: Combine document updates instead of processing individually
- **Notification batching**: Group notifications to prevent user spam

### Basic Usage

```ts
await myTask.trigger(
  { userId: "123" },
  {
    debounce: {
      key: "user-123-update",  // Unique identifier for debounce group
      delay: "5s",              // Wait duration ("5s", "1m", or milliseconds)
    },
  }
);
```

### Execution Modes

**Leading Mode** (default): Uses payload/options from the first trigger; subsequent triggers only reschedule execution time.

```ts
// First trigger sets the payload
await myTask.trigger({ action: "first" }, {
  debounce: { key: "my-key", delay: "10s" }
});

// Second trigger only reschedules - payload remains "first"
await myTask.trigger({ action: "second" }, {
  debounce: { key: "my-key", delay: "10s" }
});
// Task executes with { action: "first" }
```

**Trailing Mode**: Uses payload/options from the most recent trigger.

```ts
await myTask.trigger(
  { data: "latest-value" },
  {
    debounce: {
      key: "trailing-example",
      delay: "10s",
      mode: "trailing",
    },
  }
);
```

In trailing mode, these options update with each trigger:
- `payload` — task input data
- `metadata` — run metadata
- `tags` — run tags (replaces existing)
- `maxAttempts` — retry attempts
- `maxDuration` — maximum compute time
- `machine` — machine preset

### Important Notes

- Idempotency keys take precedence over debounce settings
- Compatible with `triggerAndWait()` — parent runs block correctly on debounced execution
- Debounce key is scoped to the task

## Concurrency & Queues

```ts
import { task, queue } from "@trigger.dev/sdk";

// Shared queue for related tasks
const emailQueue = queue({
  name: "email-processing",
  concurrencyLimit: 5, // Max 5 emails processing simultaneously
});

// Task-level concurrency
export const oneAtATime = task({
  id: "sequential-task",
  queue: { concurrencyLimit: 1 }, // Process one at a time
  run: async (payload) => {
    // Critical section - only one instance runs
  },
});

// Per-user concurrency
export const processUserData = task({
  id: "process-user-data",
  run: async (payload: { userId: string }) => {
    // Override queue with user-specific concurrency
    await childTask.trigger(payload, {
      queue: {
        name: `user-${payload.userId}`,
        concurrencyLimit: 2,
      },
    });
  },
});

export const emailTask = task({
  id: "send-email",
  queue: emailQueue, // Use shared queue
  run: async (payload: { to: string }) => {
    // Send email logic
  },
});
```

## Error Handling & Retries

```ts
import { task, retry, AbortTaskRunError } from "@trigger.dev/sdk";

export const resilientTask = task({
  id: "resilient-task",
  retry: {
    maxAttempts: 10,
    factor: 1.8, // Exponential backoff multiplier
    minTimeoutInMs: 500,
    maxTimeoutInMs: 30_000,
    randomize: false,
  },
  catchError: async ({ error, ctx }) => {
    // Custom error handling
    if (error.code === "FATAL_ERROR") {
      throw new AbortTaskRunError("Cannot retry this error");
    }

    // Log error details
    console.error(`Task ${ctx.task.id} failed:`, error);

    // Allow retry by returning nothing
    return { retryAt: new Date(Date.now() + 60000) }; // Retry in 1 minute
  },
  run: async (payload) => {
    // Retry specific operations
    const result = await retry.onThrow(
      async () => {
        return await unstableApiCall(payload);
      },
      { maxAttempts: 3 }
    );

    // Conditional HTTP retries
    const response = await retry.fetch("https://api.example.com", {
      retry: {
        maxAttempts: 5,
        condition: (response, error) => {
          return response?.status === 429 || response?.status >= 500;
        },
      },
    });

    return result;
  },
});
```

## Machines & Performance

```ts
export const heavyTask = task({
  id: "heavy-computation",
  machine: { preset: "large-2x" }, // 8 vCPU, 16 GB RAM
  maxDuration: 1800, // 30 minutes timeout
  run: async (payload, { ctx }) => {
    // Resource-intensive computation
    if (ctx.machine.preset === "large-2x") {
      // Use all available cores
      return await parallelProcessing(payload);
    }

    return await standardProcessing(payload);
  },
});

// Override machine when triggering
await heavyTask.trigger(payload, {
  machine: { preset: "medium-1x" }, // Override for this run
});
```

**Machine Presets:**

- `micro`: 0.25 vCPU, 0.25 GB RAM
- `small-1x`: 0.5 vCPU, 0.5 GB RAM (default)
- `small-2x`: 1 vCPU, 1 GB RAM
- `medium-1x`: 1 vCPU, 2 GB RAM
- `medium-2x`: 2 vCPU, 4 GB RAM
- `large-1x`: 4 vCPU, 8 GB RAM
- `large-2x`: 8 vCPU, 16 GB RAM

## Idempotency

```ts
import { task, idempotencyKeys } from "@trigger.dev/sdk";

export const paymentTask = task({
  id: "process-payment",
  retry: {
    maxAttempts: 3,
  },
  run: async (payload: { orderId: string; amount: number }) => {
    // Automatically scoped to this task run, so if the task is retried, the idempotency key will be the same
    const idempotencyKey = await idempotencyKeys.create(`payment-${payload.orderId}`);

    // Ensure payment is processed only once
    await chargeCustomer.trigger(payload, {
      idempotencyKey,
      idempotencyKeyTTL: "24h", // Key expires in 24 hours
    });
  },
});

// Payload-based idempotency
import { createHash } from "node:crypto";

function createPayloadHash(payload: any): string {
  const hash = createHash("sha256");
  hash.update(JSON.stringify(payload));
  return hash.digest("hex");
}

export const deduplicatedTask = task({
  id: "deduplicated-task",
  run: async (payload) => {
    const payloadHash = createPayloadHash(payload);
    const idempotencyKey = await idempotencyKeys.create(payloadHash);

    await processData.trigger(payload, { idempotencyKey });
  },
});
```

## Metadata & Progress Tracking

```ts
import { task, metadata } from "@trigger.dev/sdk";

export const batchProcessor = task({
  id: "batch-processor",
  run: async (payload: { items: any[] }, { ctx }) => {
    const totalItems = payload.items.length;

    // Initialize progress metadata
    metadata
      .set("progress", 0)
      .set("totalItems", totalItems)
      .set("processedItems", 0)
      .set("status", "starting");

    const results = [];

    for (let i = 0; i < payload.items.length; i++) {
      const item = payload.items[i];

      // Process item
      const result = await processItem(item);
      results.push(result);

      // Update progress
      const progress = ((i + 1) / totalItems) * 100;
      metadata
        .set("progress", progress)
        .increment("processedItems", 1)
        .append("logs", `Processed item ${i + 1}/${totalItems}`)
        .set("currentItem", item.id);
    }

    // Final status
    metadata.set("status", "completed");

    return { results, totalProcessed: results.length };
  },
});

// Update parent metadata from child task
export const childTask = task({
  id: "child-task",
  run: async (payload, { ctx }) => {
    // Update parent task metadata
    metadata.parent.set("childStatus", "processing");
    metadata.root.increment("childrenCompleted", 1);

    return { processed: true };
  },
});
```

## Logging & Tracing

```ts
import { task, logger } from "@trigger.dev/sdk";

export const tracedTask = task({
  id: "traced-task",
  run: async (payload, { ctx }) => {
    logger.info("Task started", { userId: payload.userId });

    // Custom trace with attributes
    const user = await logger.trace(
      "fetch-user",
      async (span) => {
        span.setAttribute("user.id", payload.userId);
        span.setAttribute("operation", "database-fetch");

        const userData = await database.findUser(payload.userId);
        span.setAttribute("user.found", !!userData);

        return userData;
      },
      { userId: payload.userId }
    );

    logger.debug("User fetched", { user: user.id });

    try {
      const result = await processUser(user);
      logger.info("Processing completed", { result });
      return result;
    } catch (error) {
      logger.error("Processing failed", {
        error: error.message,
        userId: payload.userId,
      });
      throw error;
    }
  },
});
```

## Hidden Tasks

```ts
// Hidden task - not exported, only used internally
const internalProcessor = task({
  id: "internal-processor",
  run: async (payload: { data: string }) => {
    return { processed: payload.data.toUpperCase() };
  },
});

// Public task that uses hidden task
export const publicWorkflow = task({
  id: "public-workflow",
  run: async (payload: { input: string }) => {
    // Use hidden task internally
    const result = await internalProcessor.triggerAndWait({
      data: payload.input,
    });

    if (result.ok) {
      return { output: result.output.processed };
    }

    throw new Error("Internal processing failed");
  },
});
```

## Best Practices

- **Concurrency**: Use queues to prevent overwhelming external services
- **Retries**: Configure exponential backoff for transient failures
- **Idempotency**: Always use for payment/critical operations
- **Metadata**: Track progress for long-running tasks
- **Machines**: Match machine size to computational requirements
- **Tags**: Use consistent naming patterns for filtering
- **Debouncing**: Use for user activity, webhooks, and notification batching
- **Batch triggering**: Use for bulk operations up to 1,000 items
- **Error Handling**: Distinguish between retryable and fatal errors

Design tasks to be stateless, idempotent, and resilient to failures. Use metadata for state tracking and queues for resource management.


================================================
FILE: .agents/skills/trigger-tasks/references/basic-tasks.md
================================================
# Trigger.dev Basic Tasks (v4)

**MUST use `@trigger.dev/sdk`, NEVER `client.defineJob`**

## Basic Task

```ts
import { task } from "@trigger.dev/sdk";

export const processData = task({
  id: "process-data",
  retry: {
    maxAttempts: 10,
    factor: 1.8,
    minTimeoutInMs: 500,
    maxTimeoutInMs: 30_000,
    randomize: false,
  },
  run: async (payload: { userId: string; data: any[] }) => {
    // Task logic - runs for long time, no timeouts
    console.log(`Processing ${payload.data.length} items for user ${payload.userId}`);
    return { processed: payload.data.length };
  },
});
```

## Schema Task (with validation)

```ts
import { schemaTask } from "@trigger.dev/sdk";
import { z } from "zod";

export const validatedTask = schemaTask({
  id: "validated-task",
  schema: z.object({
    name: z.string(),
    age: z.number(),
    email: z.string().email(),
  }),
  run: async (payload) => {
    // Payload is automatically validated and typed
    return { message: `Hello ${payload.name}, age ${payload.age}` };
  },
});
```

## Triggering Tasks

### From Backend Code

```ts
import { tasks } from "@trigger.dev/sdk";
import type { processData } from "./trigger/tasks";

// Single trigger
const handle = await tasks.trigger<typeof processData>("process-data", {
  userId: "123",
  data: [{ id: 1 }, { id: 2 }],
});

// Batch trigger (up to 1,000 items, 3MB per payload)
const batchHandle = await tasks.batchTrigger<typeof processData>("process-data", [
  { payload: { userId: "123", data: [{ id: 1 }] } },
  { payload: { userId: "456", data: [{ id: 2 }] } },
]);
```

### Debounced Triggering

Consolidate multiple triggers into a single execution:

```ts
// Multiple rapid triggers with same key = single execution
await myTask.trigger(
  { userId: "123" },
  {
    debounce: {
      key: "user-123-update",  // Unique key for debounce group
      delay: "5s",              // Wait before executing
    },
  }
);

// Trailing mode: use payload from LAST trigger
await myTask.trigger(
  { data: "latest-value" },
  {
    debounce: {
      key: "trailing-example",
      delay: "10s",
      mode: "trailing",  // Default is "leading" (first payload)
    },
  }
);
```

**Debounce modes:**
- `leading` (default): Uses payload from first trigger, subsequent triggers only reschedule
- `trailing`: Uses payload from most recent trigger

### From Inside Tasks (with Result handling)

```ts
export const parentTask = task({
  id: "parent-task",
  run: async (payload) => {
    // Trigger and continue
    const handle = await childTask.trigger({ data: "value" });

    // Trigger and wait - returns Result object, NOT task output
    const result = await childTask.triggerAndWait({ data: "value" });
    if (result.ok) {
      console.log("Task output:", result.output); // Actual task return value
    } else {
      console.error("Task failed:", result.error);
    }

    // Quick unwrap (throws on error)
    const output = await childTask.triggerAndWait({ data: "value" }).unwrap();

    // Batch trigger and wait
    const results = await childTask.batchTriggerAndWait([
      { payload: { data: "item1" } },
      { payload: { data: "item2" } },
    ]);

    for (const run of results) {
      if (run.ok) {
        console.log("Success:", run.output);
      } else {
        console.log("Failed:", run.error);
      }
    }
  },
});

export const childTask = task({
  id: "child-task",
  run: async (payload: { data: string }) => {
    return { processed: payload.data };
  },
});
```

> Never wrap triggerAndWait or batchTriggerAndWait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks.

## Waits

```ts
import { task, wait } from "@trigger.dev/sdk";

export const taskWithWaits = task({
  id: "task-with-waits",
  run: async (payload) => {
    console.log("Starting task");

    // Wait for specific duration
    await wait.for({ seconds: 30 });
    await wait.for({ minutes: 5 });
    await wait.for({ hours: 1 });
    await wait.for({ days: 1 });

    // Wait until specific date
    await wait.until({ date: new Date("2024-12-25") });

    // Wait for token (from external system)
    await wait.forToken({
      token: "user-approval-token",
      timeoutInSeconds: 3600, // 1 hour timeout
    });

    console.log("All waits completed");
    return { status: "completed" };
  },
});
```

> Never wrap wait calls in a Promise.all or Promise.allSettled as this is not supported in Trigger.dev tasks.

## Key Points

- **Result vs Output**: `triggerAndWait()` returns a `Result` object with `ok`, `output`, `error` properties - NOT the direct task output
- **Type safety**: Use `import type` for task references when triggering from backend
- **Waits > 5 seconds**: Automatically checkpointed, don't count toward compute usage
- **Debounce + idempotency**: Idempotency keys take precedence over debounce settings

## NEVER Use (v2 deprecated)

```ts
// BREAKS APPLICATION
client.defineJob({
  id: "job-id",
  run: async (payload, io) => {
    /* ... */
  },
});
```

Use SDK (`@trigger.dev/sdk`), check `result.ok` before accessing `result.output`


================================================
FILE: .agents/skills/trigger-tasks/references/scheduled-tasks.md
================================================
# Scheduled Tasks (Cron)

Recurring tasks using cron. For one-off future runs, use the **delay** option.

## Define a Scheduled Task

```ts
import { schedules } from "@trigger.dev/sdk";

export const task = schedules.task({
  id: "first-scheduled-task",
  run: async (payload) => {
    payload.timestamp; // Date (scheduled time, UTC)
    payload.lastTimestamp; // Date | undefined
    payload.timezone; // IANA, e.g. "America/New_York" (default "UTC")
    payload.scheduleId; // string
    payload.externalId; // string | undefined
    payload.upcoming; // Date[]

    payload.timestamp.toLocaleString("en-US", { timeZone: payload.timezone });
  },
});
```

> Scheduled tasks need at least one schedule attached to run.

## Attach Schedules

**Declarative (sync on dev/deploy):**

```ts
schedules.task({
  id: "every-2h",
  cron: "0 */2 * * *", // UTC
  run: async () => {},
});

schedules.task({
  id: "tokyo-5am",
  cron: { pattern: "0 5 * * *", timezone: "Asia/Tokyo", environments: ["PRODUCTION", "STAGING"] },
  run: async () => {},
});
```

**Imperative (SDK or dashboard):**

```ts
await schedules.create({
  task: task.id,
  cron: "0 0 * * *",
  timezone: "America/New_York", // DST-aware
  externalId: "user_123",
  deduplicationKey: "user_123-daily", // updates if reused
});
```

### Dynamic / Multi-tenant Example

```ts
// /trigger/reminder.ts
export const reminderTask = schedules.task({
  id: "todo-reminder",
  run: async (p) => {
    if (!p.externalId) throw new Error("externalId is required");
    const user = await db.getUser(p.externalId);
    await sendReminderEmail(user);
  },
});
```

```ts
// app/reminders/route.ts
export async function POST(req: Request) {
  const data = await req.json();
  return Response.json(
    await schedules.create({
      task: reminderTask.id,
      cron: "0 8 * * *",
      timezone: data.timezone,
      externalId: data.userId,
      deduplicationKey: `${data.userId}-reminder`,
    })
  );
}
```

## Cron Syntax (no seconds)

```
* * * * *
| | | | └ day of week (0–7 or 1L–7L; 0/7=Sun; L=last)
| | | └── month (1–12)
| | └──── day of month (1–31 or L)
| └────── hour (0–23)
└──────── minute (0–59)
```

## When Schedules Won't Trigger

- **Dev:** only when the dev CLI is running.
- **Staging/Production:** only for tasks in the **latest deployment**.

## SDK Management

```ts
await schedules.retrieve(id);
await schedules.list();
await schedules.update(id, { cron: "0 0 1 * *", externalId: "ext", deduplicationKey: "key" });
await schedules.deactivate(id);
await schedules.activate(id);
await schedules.del(id);
await schedules.timezones(); // list of IANA timezones
```


================================================
FILE: .agents/skills/vercel-composition-patterns/AGENTS.md
================================================
# React Composition Patterns

**Version 1.0.0**  
Engineering  
January 2026

> **Note:**  
> This document is mainly for agents and LLMs to follow when maintaining,  
> generating, or refactoring React codebases using composition. Humans  
> may also find it useful, but guidance here is optimized for automation  
> and consistency by AI-assisted workflows.

---

## Abstract

Composition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier for both humans and AI agents to work with as they scale.

---

## Table of Contents

1. [Component Architecture](#1-component-architecture) — **HIGH**
   - 1.1 [Avoid Boolean Prop Proliferation](#11-avoid-boolean-prop-proliferation)
   - 1.2 [Use Compound Components](#12-use-compound-components)
2. [State Management](#2-state-management) — **MEDIUM**
   - 2.1 [Decouple State Management from UI](#21-decouple-state-management-from-ui)
   - 2.2 [Define Generic Context Interfaces for Dependency Injection](#22-define-generic-context-interfaces-for-dependency-injection)
   - 2.3 [Lift State into Provider Components](#23-lift-state-into-provider-components)
3. [Implementation Patterns](#3-implementation-patterns) — **MEDIUM**
   - 3.1 [Create Explicit Component Variants](#31-create-explicit-component-variants)
   - 3.2 [Prefer Composing Children Over Render Props](#32-prefer-composing-children-over-render-props)
4. [React 19 APIs](#4-react-19-apis) — **MEDIUM**
   - 4.1 [React 19 API Changes](#41-react-19-api-changes)

---

## 1. Component Architecture

**Impact: HIGH**

Fundamental patterns for structuring components to avoid prop
proliferation and enable flexible composition.

### 1.1 Avoid Boolean Prop Proliferation

**Impact: CRITICAL (prevents unmaintainable component variants)**

Don't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize

component behavior. Each boolean doubles possible states and creates

unmaintainable conditional logic. Use composition instead.

**Incorrect: boolean props create exponential complexity**

```tsx
function Composer({
  onSubmit,
  isThread,
  channelId,
  isDMThread,
  dmId,
  isEditing,
  isForwarding,
}: Props) {
  return (
    <form>
      <Header />
      <Input />
      {isDMThread ? (
        <AlsoSendToDMField id={dmId} />
      ) : isThread ? (
        <AlsoSendToChannelField id={channelId} />
      ) : null}
      {isEditing ? (
        <EditActions />
      ) : isForwarding ? (
        <ForwardActions />
      ) : (
        <DefaultActions />
      )}
      <Footer onSubmit={onSubmit} />
    </form>
  )
}
```

**Correct: composition eliminates conditionals**

```tsx
// Channel composer
function ChannelComposer() {
  return (
    <Composer.Frame>
      <Composer.Header />
      <Composer.Input />
      <Composer.Footer>
        <Composer.Attachments />
        <Composer.Formatting />
        <Composer.Emojis />
        <Composer.Submit />
      </Composer.Footer>
    </Composer.Frame>
  )
}

// Thread composer - adds "also send to channel" field
function ThreadComposer({ channelId }: { channelId: string }) {
  return (
    <Composer.Frame>
      <Composer.Header />
      <Composer.Input />
      <AlsoSendToChannelField id={channelId} />
      <Composer.Footer>
        <Composer.Formatting />
        <Composer.Emojis />
        <Composer.Submit />
      </Composer.Footer>
    </Composer.Frame>
  )
}

// Edit composer - different footer actions
function EditComposer() {
  return (
    <Composer.Frame>
      <Composer.Input />
      <Composer.Footer>
        <Composer.Formatting />
        <Composer.Emojis />
        <Composer.CancelEdit />
        <Composer.SaveEdit />
      </Composer.Footer>
    </Composer.Frame>
  )
}
```

Each variant is explicit about what it renders. We can share internals without

sharing a single monolithic parent.

### 1.2 Use Compound Components

**Impact: HIGH (enables flexible composition without prop drilling)**

Structure complex components as compound components with a shared context. Each

subcomponent accesses shared state via context, not props. Consumers compose the

pieces they need.

**Incorrect: monolithic component with render props**

```tsx
function Composer({
  renderHeader,
  renderFooter,
  renderActions,
  showAttachments,
  showFormatting,
  showEmojis,
}: Props) {
  return (
    <form>
      {renderHeader?.()}
      <Input />
      {showAttachments && <Attachments />}
      {renderFooter ? (
        renderFooter()
      ) : (
        <Footer>
          {showFormatting && <Formatting />}
          {showEmojis && <Emojis />}
          {renderActions?.()}
        </Footer>
      )}
    </form>
  )
}
```

**Correct: compound components with shared context**

```tsx
const ComposerContext = createContext<ComposerContextValue | null>(null)

function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
  return (
    <ComposerContext value={{ state, actions, meta }}>
      {children}
    </ComposerContext>
  )
}

function ComposerFrame({ children }: { children: React.ReactNode }) {
  return <form>{children}</form>
}

function ComposerInput() {
  const {
    state,
    actions: { update },
    meta: { inputRef },
  } = use(ComposerContext)
  return (
    <TextInput
      ref={inputRef}
      value={state.input}
      onChangeText={(text) => update((s) => ({ ...s, input: text }))}
    />
  )
}

function ComposerSubmit() {
  const {
    actions: { submit },
  } = use(ComposerContext)
  return <Button onPress={submit}>Send</Button>
}

// Export as compound component
const Composer = {
  Provider: ComposerProvider,
  Frame: ComposerFrame,
  Input: ComposerInput,
  Submit: ComposerSubmit,
  Header: ComposerHeader,
  Footer: ComposerFooter,
  Attachments: ComposerAttachments,
  Formatting: ComposerFormatting,
  Emojis: ComposerEmojis,
}
```

**Usage:**

```tsx
<Composer.Provider state={state} actions={actions} meta={meta}>
  <Composer.Frame>
    <Composer.Header />
    <Composer.Input />
    <Composer.Footer>
      <Composer.Formatting />
      <Composer.Submit />
    </Composer.Footer>
  </Composer.Frame>
</Composer.Provider>
```

Consumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.

---

## 2. State Management

**Impact: MEDIUM**

Patterns for lifting state and managing shared context across
composed components.

### 2.1 Decouple State Management from UI

**Impact: MEDIUM (enables swapping state implementations without changing UI)**

The provider component should be the only place that knows how state is managed.

UI components consume the context interface—they don't know if state comes from

useState, Zustand, or a server sync.

**Incorrect: UI coupled to state implementation**

```tsx
function ChannelComposer({ channelId }: { channelId: string }) {
  // UI component knows about global state implementation
  const state = useGlobalChannelState(channelId)
  const { submit, updateInput } = useChannelSync(channelId)

  return (
    <Composer.Frame>
      <Composer.Input
        value={state.input}
        onChange={(text) => sync.updateInput(text)}
      />
      <Composer.Submit onPress={() => sync.submit()} />
    </Composer.Frame>
  )
}
```

**Correct: state management isolated in provider**

```tsx
// Provider handles all state management details
function ChannelProvider({
  channelId,
  children,
}: {
  channelId: string
  children: React.ReactNode
}) {
  const { state, update, submit } = useGlobalChannel(channelId)
  const inputRef = useRef(null)

  return (
    <Composer.Provider
      state={state}
      actions={{ update, submit }}
      meta={{ inputRef }}
    >
      {children}
    </Composer.Provider>
  )
}

// UI component only knows about the context interface
function ChannelComposer() {
  return (
    <Composer.Frame>
      <Composer.Header />
      <Composer.Input />
      <Composer.Footer>
        <Composer.Submit />
      </Composer.Footer>
    </Composer.Frame>
  )
}

// Usage
function Channel({ channelId }: { channelId: string }) {
  return (
    <ChannelProvider channelId={channelId}>
      <ChannelComposer />
    </ChannelProvider>
  )
}
```

**Different providers, same UI:**

```tsx
// Local state for ephemeral forms
function ForwardMessageProvider({ children }) {
  const [state, setState] = useState(initialState)
  const forwardMessage = useForwardMessage()

  return (
    <Composer.Provider
      state={state}
      actions={{ update: setState, submit: forwardMessage }}
    >
      {children}
    </Composer.Provider>
  )
}

// Global synced state for channels
function ChannelProvider({ channelId, children }) {
  const { state, update, submit } = useGlobalChannel(channelId)

  return (
    <Composer.Provider state={state} actions={{ update, submit }}>
      {children}
    </Composer.Provider>
  )
}
```

The same `Composer.Input` component works with both providers because it only

depends on the context interface, not the implementation.

### 2.2 Define Generic Context Interfaces for Dependency Injection

**Impact: HIGH (enables dependency-injectable state across use-cases)**

Define a **generic interface** for your component context with three parts:

`state`, `actions`, and `meta`. This interface is a contract that any provider

can implement—enabling the same UI components to work with completely different

state implementations.

**Core principle:** Lift state, compose internals, make state

dependency-injectable.

**Incorrect: UI coupled to specific state implementation**

```tsx
function ComposerInput() {
  // Tightly coupled to a specific hook
  const { input, setInput } = useChannelComposerState()
  return <TextInput value={input} onChangeText={setInput} />
}
```

**Correct: generic interface enables dependency injection**

```tsx
// Define a GENERIC interface that any provider can implement
interface ComposerState {
  input: string
  attachments: Attachment[]
  isSubmitting: boolean
}

interface ComposerActions {
  update: (updater: (state: ComposerState) => ComposerState) => void
  submit: () => void
}

interface ComposerMeta {
  inputRef: React.RefObject<TextInput>
}

interface ComposerContextValue {
  state: ComposerState
  actions: ComposerActions
  meta: ComposerMeta
}

const ComposerContext = createContext<ComposerContextValue | null>(null)
```

**UI components consume the interface, not the implementation:**

```tsx
function ComposerInput() {
  const {
    state,
    actions: { update },
    meta,
  } = use(ComposerContext)

  // This component works with ANY provider that implements the interface
  return (
    <TextInput
      ref={meta.inputRef}
      value={state.input}
      onChangeText={(text) => update((s) => ({ ...s, input: text }))}
    />
  )
}
```

**Different providers implement the same interface:**

```tsx
// Provider A: Local state for ephemeral forms
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = useState(initialState)
  const inputRef = useRef(null)
  const submit = useForwardMessage()

  return (
    <ComposerContext
      value={{
        state,
        actions: { update: setState, submit },
        meta: { inputRef },
      }}
    >
      {children}
    </ComposerContext>
  )
}

// Provider B: Global synced state for channels
function ChannelProvider({ channelId, children }: Props) {
  const { state, update, submit } = useGlobalChannel(channelId)
  const inputRef = useRef(null)

  return (
    <ComposerContext
      value={{
        state,
        actions: { update, submit },
        meta: { inputRef },
      }}
    >
      {children}
    </ComposerContext>
  )
}
```

**The same composed UI works with both:**

```tsx
// Works with ForwardMessageProvider (local state)
<ForwardMessageProvider>
  <Composer.Frame>
    <Composer.Input />
    <Composer.Submit />
  </Composer.Frame>
</ForwardMessageProvider>

// Works with ChannelProvider (global synced state)
<ChannelProvider channelId="abc">
  <Composer.Frame>
    <Composer.Input />
    <Composer.Submit />
  </Composer.Frame>
</ChannelProvider>
```

**Custom UI outside the component can access state and actions:**

```tsx
function ForwardMessageDialog() {
  return (
    <ForwardMessageProvider>
      <Dialog>
        {/* The composer UI */}
        <Composer.Frame>
          <Composer.Input placeholder="Add a message, if you'd like." />
          <Composer.Footer>
            <Composer.Formatting />
            <Composer.Emojis />
          </Composer.Footer>
        </Composer.Frame>

        {/* Custom UI OUTSIDE the composer, but INSIDE the provider */}
        <MessagePreview />

        {/* Actions at the bottom of the dialog */}
        <DialogActions>
          <CancelButton />
          <ForwardButton />
        </DialogActions>
      </Dialog>
    </ForwardMessageProvider>
  )
}

// This button lives OUTSIDE Composer.Frame but can still submit based on its context!
function ForwardButton() {
  const {
    actions: { submit },
  } = use(ComposerContext)
  return <Button onPress={submit}>Forward</Button>
}

// This preview lives OUTSIDE Composer.Frame but can read composer's state!
function MessagePreview() {
  const { state } = use(ComposerContext)
  return <Preview message={state.input} attachments={state.attachments} />
}
```

The provider boundary is what matters—not the visual nesting. Components that

need shared state don't have to be inside the `Composer.Frame`. They just need

to be within the provider.

The `ForwardButton` and `MessagePreview` are not visually inside the composer

box, but they can still access its state and actions. This is the power of

lifting state into providers.

The UI is reusable bits you compose together. The state is dependency-injected

by the provider. Swap the provider, keep the UI.

### 2.3 Lift State into Provider Components

**Impact: HIGH (enables state sharing outside component boundaries)**

Move state management into dedicated provider components. This allows sibling

components outside the main UI to access and modify state without prop drilling

or awkward refs.

**Incorrect: state trapped inside component**

```tsx
function ForwardMessageComposer() {
  const [state, setState] = useState(initialState)
  const forwardMessage = useForwardMessage()

  return (
    <Composer.Frame>
      <Composer.Input />
      <Composer.Footer />
    </Composer.Frame>
  )
}

// Problem: How does this button access composer state?
function ForwardMessageDialog() {
  return (
    <Dialog>
      <ForwardMessageComposer />
      <MessagePreview /> {/* Needs composer state */}
      <DialogActions>
        <CancelButton />
        <ForwardButton /> {/* Needs to call submit */}
      </DialogActions>
    </Dialog>
  )
}
```

**Incorrect: useEffect to sync state up**

```tsx
function ForwardMessageDialog() {
  const [input, setInput] = useState('')
  return (
    <Dialog>
      <ForwardMessageComposer onInputChange={setInput} />
      <MessagePreview input={input} />
    </Dialog>
  )
}

function ForwardMessageComposer({ onInputChange }) {
  const [state, setState] = useState(initialState)
  useEffect(() => {
    onInputChange(state.input) // Sync on every change 😬
  }, [state.input])
}
```

**Incorrect: reading state from ref on submit**

```tsx
function ForwardMessageDialog() {
  const stateRef = useRef(null)
  return (
    <Dialog>
      <ForwardMessageComposer stateRef={stateRef} />
      <ForwardButton onPress={() => submit(stateRef.current)} />
    </Dialog>
  )
}
```

**Correct: state lifted to provider**

```tsx
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = useState(initialState)
  const forwardMessage = useForwardMessage()
  const inputRef = useRef(null)

  return (
    <Composer.Provider
      state={state}
      actions={{ update: setState, submit: forwardMessage }}
      meta={{ inputRef }}
    >
      {children}
    </Composer.Provider>
  )
}

function ForwardMessageDialog() {
  return (
    <ForwardMessageProvider>
      <Dialog>
        <ForwardMessageComposer />
        <MessagePreview /> {/* Custom components can access state and actions */}
        <DialogActions>
          <CancelButton />
          <ForwardButton /> {/* Custom components can access state and actions */}
        </DialogActions>
      </Dialog>
    </ForwardMessageProvider>
  )
}

function ForwardButton() {
  const { actions } = use(Composer.Context)
  return <Button onPress={actions.submit}>Forward</Button>
}
```

The ForwardButton lives outside the Composer.Frame but still has access to the

submit action because it's within the provider. Even though it's a one-off

component, it can still access the composer's state and actions from outside the

UI itself.

**Key insight:** Components that need shared state don't have to be visually

nested inside each other—they just need to be within the same provider.

---

## 3. Implementation Patterns

**Impact: MEDIUM**

Specific techniques for implementing compound components and
context providers.

### 3.1 Create Explicit Component Variants

**Impact: MEDIUM (self-documenting code, no hidden conditionals)**

Instead of one component with many boolean props, create explicit variant

components. Each variant composes the pieces it needs. The code documents

itself.

**Incorrect: one component, many modes**

```tsx
// What does this component actually render?
<Composer
  isThread
  isEditing={false}
  channelId='abc'
  showAttachments
  showFormatting={false}
/>
```

**Correct: explicit variants**

```tsx
// Immediately clear what this renders
<ThreadComposer channelId="abc" />

// Or
<EditMessageComposer messageId="xyz" />

// Or
<ForwardMessageComposer messageId="123" />
```

Each implementation is unique, explicit and self-contained. Yet they can each

use shared parts.

**Implementation:**

```tsx
function ThreadComposer({ channelId }: { channelId: string }) {
  return (
    <ThreadProvider channelId={channelId}>
      <Composer.Frame>
        <Composer.Input />
        <AlsoSendToChannelField channelId={channelId} />
        <Composer.Footer>
          <Composer.Formatting />
          <Composer.Emojis />
          <Composer.Submit />
        </Composer.Footer>
      </Composer.Frame>
    </ThreadProvider>
  )
}

function EditMessageComposer({ messageId }: { messageId: string }) {
  return (
    <EditMessageProvider messageId={messageId}>
      <Composer.Frame>
        <Composer.Input />
        <Composer.Footer>
          <Composer.Formatting />
          <Composer.Emojis />
          <Composer.CancelEdit />
          <Composer.SaveEdit />
        </Composer.Footer>
      </Composer.Frame>
    </EditMessageProvider>
  )
}

function ForwardMessageComposer({ messageId }: { messageId: string }) {
  return (
    <ForwardMessageProvider messageId={messageId}>
      <Composer.Frame>
        <Composer.Input placeholder="Add a message, if you'd like." />
        <Composer.Footer>
          <Composer.Formatting />
          <Composer.Emojis />
          <Composer.Mentions />
        </Composer.Footer>
      </Composer.Frame>
    </ForwardMessageProvider>
  )
}
```

Each variant is explicit about:

- What provider/state it uses

- What UI elements it includes

- What actions are available

No boolean prop combinations to reason about. No impossible states.

### 3.2 Prefer Composing Children Over Render Props

**Impact: MEDIUM (cleaner composition, better readability)**

Use `children` for composition instead of `renderX` props. Children are more

readable, compose naturally, and don't require understanding callback

signatures.

**Incorrect: render props**

```tsx
function Composer({
  renderHeader,
  renderFooter,
  renderActions,
}: {
  renderHeader?: () => React.ReactNode
  renderFooter?: () => React.ReactNode
  renderActions?: () => React.ReactNode
}) {
  return (
    <form>
      {renderHeader?.()}
      <Input />
      {renderFooter ? renderFooter() : <DefaultFooter />}
      {renderActions?.()}
    </form>
  )
}

// Usage is awkward and inflexible
return (
  <Composer
    renderHeader={() => <CustomHeader />}
    renderFooter={() => (
      <>
        <Formatting />
        <Emojis />
      </>
    )}
    renderActions={() => <SubmitButton />}
  />
)
```

**Correct: compound components with children**

```tsx
function ComposerFrame({ children }: { children: React.ReactNode }) {
  return <form>{children}</form>
}

function ComposerFooter({ children }: { children: React.ReactNode }) {
  return <footer className='flex'>{children}</footer>
}

// Usage is flexible
return (
  <Composer.Frame>
    <CustomHeader />
    <Composer.Input />
    <Composer.Footer>
      <Composer.Formatting />
      <Composer.Emojis />
      <SubmitButton />
    </Composer.Footer>
  </Composer.Frame>
)
```

**When render props are appropriate:**

```tsx
// Render props work well when you need to pass data back
<List
  data={items}
  renderItem={({ item, index }) => <Item item={item} index={index} />}
/>
```

Use render props when the parent needs to provide data or state to the child.

Use children when composing static structure.

---

## 4. React 19 APIs

**Impact: MEDIUM**

React 19+ only. Don't use `forwardRef`; use `use()` instead of `useContext()`.

### 4.1 React 19 API Changes

**Impact: MEDIUM (cleaner component definitions and context usage)**

> **⚠️ React 19+ only.** Skip this if you're on React 18 or earlier.

In React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `use()` replaces `useContext()`.

**Incorrect: forwardRef in React 19**

```tsx
const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
  return <TextInput ref={ref} {...props} />
})
```

**Correct: ref as a regular prop**

```tsx
function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
  return <TextInput ref={ref} {...props} />
}
```

**Incorrect: useContext in React 19**

```tsx
const value = useContext(MyContext)
```

**Correct: use instead of useContext**

```tsx
const value = use(MyContext)
```

`use()` can also be called conditionally, unlike `useContext()`.

---

## References

1. [https://react.dev](https://react.dev)
2. [https://react.dev/learn/passing-data-deeply-with-context](https://react.dev/learn/passing-data-deeply-with-context)
3. [https://react.dev/reference/react/use](https://react.dev/reference/react/use)


================================================
FILE: .agents/skills/vercel-composition-patterns/SKILL.md
================================================
---
name: vercel-composition-patterns
description:
  React composition patterns that scale. Use when refactoring components with
  boolean prop proliferation, building flexible component libraries, or
  designing reusable APIs. Triggers on tasks involving compound components,
  render props, context providers, or component architecture. Includes React 19
  API changes.
license: MIT
metadata:
  author: vercel
  version: '1.0.0'
---

# React Composition Patterns

Composition patterns for building flexible, maintainable React components. Avoid
boolean prop proliferation by using compound components, lifting state, and
composing internals. These patterns make codebases easier for both humans and AI
agents to work with as they scale.

## When to Apply

Reference these guidelines when:

- Refactoring components with many boolean props
- Building reusable component libraries
- Designing flexible component APIs
- Reviewing component architecture
- Working with compound components or context providers

## Rule Categories by Priority

| Priority | Category                | Impact | Prefix          |
| -------- | ----------------------- | ------ | --------------- |
| 1        | Component Architecture  | HIGH   | `architecture-` |
| 2        | State Management        | MEDIUM | `state-`        |
| 3        | Implementation Patterns | MEDIUM | `patterns-`     |
| 4        | React 19 APIs           | MEDIUM | `react19-`      |

## Quick Reference

### 1. Component Architecture (HIGH)

- `architecture-avoid-boolean-props` - Don't add boolean props to customize
  behavior; use composition
- `architecture-compound-components` - Structure complex components with shared
  context

### 2. State Management (MEDIUM)

- `state-decouple-implementation` - Provider is the only place that knows how
  state is managed
- `state-context-interface` - Define generic interface with state, actions, meta
  for dependency injection
- `state-lift-state` - Move state into provider components for sibling access

### 3. Implementation Patterns (MEDIUM)

- `patterns-explicit-variants` - Create explicit variant components instead of
  boolean modes
- `patterns-children-over-render-props` - Use children for composition instead
  of renderX props

### 4. React 19 APIs (MEDIUM)

> **⚠️ React 19+ only.** Skip this section if using React 18 or earlier.

- `react19-no-forwardref` - Don't use `forwardRef`; use `use()` instead of `useContext()`

## How to Use

Read individual rule files for detailed explanations and code examples:

```
rules/architecture-avoid-boolean-props.md
rules/state-context-interface.md
```

Each rule file contains:

- Brief explanation of why it matters
- Incorrect code example with explanation
- Correct code example with explanation
- Additional context and references

## Full Compiled Document

For the complete guide with all rules expanded: `AGENTS.md`


================================================
FILE: .agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md
================================================
---
title: Avoid Boolean Prop Proliferation
impact: CRITICAL
impactDescription: prevents unmaintainable component variants
tags: composition, props, architecture
---

## Avoid Boolean Prop Proliferation

Don't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize
component behavior. Each boolean doubles possible states and creates
unmaintainable conditional logic. Use composition instead.

**Incorrect (boolean props create exponential complexity):**

```tsx
function Composer({
  onSubmit,
  isThread,
  channelId,
  isDMThread,
  dmId,
  isEditing,
  isForwarding,
}: Props) {
  return (
    <form>
      <Header />
      <Input />
      {isDMThread ? (
        <AlsoSendToDMField id={dmId} />
      ) : isThread ? (
        <AlsoSendToChannelField id={channelId} />
      ) : null}
      {isEditing ? (
        <EditActions />
      ) : isForwarding ? (
        <ForwardActions />
      ) : (
        <DefaultActions />
      )}
      <Footer onSubmit={onSubmit} />
    </form>
  )
}
```

**Correct (composition eliminates conditionals):**

```tsx
// Channel composer
function ChannelComposer() {
  return (
    <Composer.Frame>
      <Composer.Header />
      <Composer.Input />
      <Composer.Footer>
        <Composer.Attachments />
        <Composer.Formatting />
        <Composer.Emojis />
        <Composer.Submit />
      </Composer.Footer>
    </Composer.Frame>
  )
}

// Thread composer - adds "also send to channel" field
function ThreadComposer({ channelId }: { channelId: string }) {
  return (
    <Composer.Frame>
      <Composer.Header />
      <Composer.Input />
      <AlsoSendToChannelField id={channelId} />
      <Composer.Footer>
        <Composer.Formatting />
        <Composer.Emojis />
        <Composer.Submit />
      </Composer.Footer>
    </Composer.Frame>
  )
}

// Edit composer - different footer actions
function EditComposer() {
  return (
    <Composer.Frame>
      <Composer.Input />
      <Composer.Footer>
        <Composer.Formatting />
        <Composer.Emojis />
        <Composer.CancelEdit />
        <Composer.SaveEdit />
      </Composer.Footer>
    </Composer.Frame>
  )
}
```

Each variant is explicit about what it renders. We can share internals without
sharing a single monolithic parent.


================================================
FILE: .agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md
================================================
---
title: Use Compound Components
impact: HIGH
impactDescription: enables flexible composition without prop drilling
tags: composition, compound-components, architecture
---

## Use Compound Components

Structure complex components as compound components with a shared context. Each
subcomponent accesses shared state via context, not props. Consumers compose the
pieces they need.

**Incorrect (monolithic component with render props):**

```tsx
function Composer({
  renderHeader,
  renderFooter,
  renderActions,
  showAttachments,
  showFormatting,
  showEmojis,
}: Props) {
  return (
    <form>
      {renderHeader?.()}
      <Input />
      {showAttachments && <Attachments />}
      {renderFooter ? (
        renderFooter()
      ) : (
        <Footer>
          {showFormatting && <Formatting />}
          {showEmojis && <Emojis />}
          {renderActions?.()}
        </Footer>
      )}
    </form>
  )
}
```

**Correct (compound components with shared context):**

```tsx
const ComposerContext = createContext<ComposerContextValue | null>(null)

function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
  return (
    <ComposerContext value={{ state, actions, meta }}>
      {children}
    </ComposerContext>
  )
}

function ComposerFrame({ children }: { children: React.ReactNode }) {
  return <form>{children}</form>
}

function ComposerInput() {
  const {
    state,
    actions: { update },
    meta: { inputRef },
  } = use(ComposerContext)
  return (
    <TextInput
      ref={inputRef}
      value={state.input}
      onChangeText={(text) => update((s) => ({ ...s, input: text }))}
    />
  )
}

function ComposerSubmit() {
  const {
    actions: { submit },
  } = use(ComposerContext)
  return <Button onPress={submit}>Send</Button>
}

// Export as compound component
const Composer = {
  Provider: ComposerProvider,
  Frame: ComposerFrame,
  Input: ComposerInput,
  Submit: ComposerSubmit,
  Header: ComposerHeader,
  Footer: ComposerFooter,
  Attachments: ComposerAttachments,
  Formatting: ComposerFormatting,
  Emojis: ComposerEmojis,
}
```

**Usage:**

```tsx
<Composer.Provider state={state} actions={actions} meta={meta}>
  <Composer.Frame>
    <Composer.Header />
    <Composer.Input />
    <Composer.Footer>
      <Composer.Formatting />
      <Composer.Submit />
    </Composer.Footer>
  </Composer.Frame>
</Composer.Provider>
```

Consumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.


================================================
FILE: .agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md
================================================
---
title: Prefer Composing Children Over Render Props
impact: MEDIUM
impactDescription: cleaner composition, better readability
tags: composition, children, render-props
---

## Prefer Children Over Render Props

Use `children` for composition instead of `renderX` props. Children are more
readable, compose naturally, and don't require understanding callback
signatures.

**Incorrect (render props):**

```tsx
function Composer({
  renderHeader,
  renderFooter,
  renderActions,
}: {
  renderHeader?: () => React.ReactNode
  renderFooter?: () => React.ReactNode
  renderActions?: () => React.ReactNode
}) {
  return (
    <form>
      {renderHeader?.()}
      <Input />
      {renderFooter ? renderFooter() : <DefaultFooter />}
      {renderActions?.()}
    </form>
  )
}

// Usage is awkward and inflexible
return (
  <Composer
    renderHeader={() => <CustomHeader />}
    renderFooter={() => (
      <>
        <Formatting />
        <Emojis />
      </>
    )}
    renderActions={() => <SubmitButton />}
  />
)
```

**Correct (compound components with children):**

```tsx
function ComposerFrame({ children }: { children: React.ReactNode }) {
  return <form>{children}</form>
}

function ComposerFooter({ children }: { children: React.ReactNode }) {
  return <footer className='flex'>{children}</footer>
}

// Usage is flexible
return (
  <Composer.Frame>
    <CustomHeader />
    <Composer.Input />
    <Composer.Footer>
      <Composer.Formatting />
      <Composer.Emojis />
      <SubmitButton />
    </Composer.Footer>
  </Composer.Frame>
)
```

**When render props are appropriate:**

```tsx
// Render props work well when you need to pass data back
<List
  data={items}
  renderItem={({ item, index }) => <Item item={item} index={index} />}
/>
```

Use render props when the parent needs to provide data or state to the child.
Use children when composing static structure.


================================================
FILE: .agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md
================================================
---
title: Create Explicit Component Variants
impact: MEDIUM
impactDescription: self-documenting code, no hidden conditionals
tags: composition, variants, architecture
---

## Create Explicit Component Variants

Instead of one component with many boolean props, create explicit variant
components. Each variant composes the pieces it needs. The code documents
itself.

**Incorrect (one component, many modes):**

```tsx
// What does this component actually render?
<Composer
  isThread
  isEditing={false}
  channelId='abc'
  showAttachments
  showFormatting={false}
/>
```

**Correct (explicit variants):**

```tsx
// Immediately clear what this renders
<ThreadComposer channelId="abc" />

// Or
<EditMessageComposer messageId="xyz" />

// Or
<ForwardMessageComposer messageId="123" />
```

Each implementation is unique, explicit and self-contained. Yet they can each
use shared parts.

**Implementation:**

```tsx
function ThreadComposer({ channelId }: { channelId: string }) {
  return (
    <ThreadProvider channelId={channelId}>
      <Composer.Frame>
        <Composer.Input />
        <AlsoSendToChannelField channelId={channelId} />
        <Composer.Footer>
          <Composer.Formatting />
          <Composer.Emojis />
          <Composer.Submit />
        </Composer.Footer>
      </Composer.Frame>
    </ThreadProvider>
  )
}

function EditMessageComposer({ messageId }: { messageId: string }) {
  return (
    <EditMessageProvider messageId={messageId}>
      <Composer.Frame>
        <Composer.Input />
        <Composer.Footer>
          <Composer.Formatting />
          <Composer.Emojis />
          <Composer.CancelEdit />
          <Composer.SaveEdit />
        </Composer.Footer>
      </Composer.Frame>
    </EditMessageProvider>
  )
}

function ForwardMessageComposer({ messageId }: { messageId: string }) {
  return (
    <ForwardMessageProvider messageId={messageId}>
      <Composer.Frame>
        <Composer.Input placeholder="Add a message, if you'd like." />
        <Composer.Footer>
          <Composer.Formatting />
          <Composer.Emojis />
          <Composer.Mentions />
        </Composer.Footer>
      </Composer.Frame>
    </ForwardMessageProvider>
  )
}
```

Each variant is explicit about:

- What provider/state it uses
- What UI elements it includes
- What actions are available

No boolean prop combinations to reason about. No impossible states.


================================================
FILE: .agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md
================================================
---
title: React 19 API Changes
impact: MEDIUM
impactDescription: cleaner component definitions and context usage
tags: react19, refs, context, hooks
---

## React 19 API Changes

> **⚠️ React 19+ only.** Skip this if you're on React 18 or earlier.

In React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `use()` replaces `useContext()`.

**Incorrect (forwardRef in React 19):**

```tsx
const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
  return <TextInput ref={ref} {...props} />
})
```

**Correct (ref as a regular prop):**

```tsx
function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
  return <TextInput ref={ref} {...props} />
}
```

**Incorrect (useContext in React 19):**

```tsx
const value = useContext(MyContext)
```

**Correct (use instead of useContext):**

```tsx
const value = use(MyContext)
```

`use()` can also be called conditionally, unlike `useContext()`.


================================================
FILE: .agents/skills/vercel-composition-patterns/rules/state-context-interface.md
================================================
---
title: Define Generic Context Interfaces for Dependency Injection
impact: HIGH
impactDescription: enables dependency-injectable state across use-cases
tags: composition, context, state, typescript, dependency-injection
---

## Define Generic Context Interfaces for Dependency Injection

Define a **generic interface** for your component context with three parts:
`state`, `actions`, and `meta`. This interface is a contract that any provider
can implement—enabling the same UI components to work with completely different
state implementations.

**Core principle:** Lift state, compose internals, make state
dependency-injectable.

**Incorrect (UI coupled to specific state implementation):**

```tsx
function ComposerInput() {
  // Tightly coupled to a specific hook
  const { input, setInput } = useChannelComposerState()
  return <TextInput value={input} onChangeText={setInput} />
}
```

**Correct (generic interface enables dependency injection):**

```tsx
// Define a GENERIC interface that any provider can implement
interface ComposerState {
  input: string
  attachments: Attachment[]
  isSubmitting: boolean
}

interface ComposerActions {
  update: (updater: (state: ComposerState) => ComposerState) => void
  submit: () => void
}

interface ComposerMeta {
  inputRef: React.RefObject<TextInput>
}

interface ComposerContextValue {
  state: ComposerState
  actions: ComposerActions
  meta: ComposerMeta
}

const ComposerContext = createContext<ComposerContextValue | null>(null)
```

**UI components consume the interface, not the implementation:**

```tsx
function ComposerInput() {
  const {
    state,
    actions: { update },
    meta,
  } = use(ComposerContext)

  // This component works with ANY provider that implements the interface
  return (
    <TextInput
      ref={meta.inputRef}
      value={state.input}
      onChangeText={(text) => update((s) => ({ ...s, input: text }))}
    />
  )
}
```

**Different providers implement the same interface:**

```tsx
// Provider A: Local state for ephemeral forms
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = useState(initialState)
  const inputRef = useRef(null)
  const submit = useForwardMessage()

  return (
    <ComposerContext
      value={{
        state,
        actions: { update: setState, submit },
        meta: { inputRef },
      }}
    >
      {children}
    </ComposerContext>
  )
}

// Provider B: Global synced state for channels
function ChannelProvider({ channelId, children }: Props) {
  const { state, update, submit } = useGlobalChannel(channelId)
  const inputRef = useRef(null)

  return (
    <ComposerContext
      value={{
        state,
        actions: { update, submit },
        meta: { inputRef },
      }}
    >
      {children}
    </ComposerContext>
  )
}
```

**The same composed UI works with both:**

```tsx
// Works with ForwardMessageProvider (local state)
<ForwardMessageProvider>
  <Composer.Frame>
    <Composer.Input />
    <Composer.Submit />
  </Composer.Frame>
</ForwardMessageProvider>

// Works with ChannelProvider (global synced state)
<ChannelProvider channelId="abc">
  <Composer.Frame>
    <Composer.Input />
    <Composer.Submit />
  </Composer.Frame>
</ChannelProvider>
```

**Custom UI outside the component can access state and actions:**

The provider boundary is what matters—not the visual nesting. Components that
need shared state don't have to be inside the `Composer.Frame`. They just need
to be within the provider.

```tsx
function ForwardMessageDialog() {
  return (
    <ForwardMessageProvider>
      <Dialog>
        {/* The composer UI */}
        <Composer.Frame>
          <Composer.Input placeholder="Add a message, if you'd like." />
          <Composer.Footer>
            <Composer.Formatting />
            <Composer.Emojis />
          </Composer.Footer>
        </Composer.Frame>

        {/* Custom UI OUTSIDE the composer, but INSIDE the provider */}
        <MessagePreview />

        {/* Actions at the bottom of the dialog */}
        <DialogActions>
          <CancelButton />
          <ForwardButton />
        </DialogActions>
      </Dialog>
    </ForwardMessageProvider>
  )
}

// This button lives OUTSIDE Composer.Frame but can still submit based on its context!
function ForwardButton() {
  const {
    actions: { submit },
  } = use(ComposerContext)
  return <Button onPress={submit}>Forward</Button>
}

// This preview lives OUTSIDE Composer.Frame but can read composer's state!
function MessagePreview() {
  const { state } = use(ComposerContext)
  return <Preview message={state.input} attachments={state.attachments} />
}
```

The `ForwardButton` and `MessagePreview` are not visually inside the composer
box, but they can still access its state and actions. This is the power of
lifting state into providers.

The UI is reusable bits you compose together. The state is dependency-injected
by the provider. Swap the provider, keep the UI.


================================================
FILE: .agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md
================================================
---
title: Decouple State Management from UI
impact: MEDIUM
impactDescription: enables swapping state implementations without changing UI
tags: composition, state, architecture
---

## Decouple State Management from UI

The provider component should be the only place that knows how state is managed.
UI components consume the context interface—they don't know if state comes from
useState, Zustand, or a server sync.

**Incorrect (UI coupled to state implementation):**

```tsx
function ChannelComposer({ channelId }: { channelId: string }) {
  // UI component knows about global state implementation
  const state = useGlobalChannelState(channelId)
  const { submit, updateInput } = useChannelSync(channelId)

  return (
    <Composer.Frame>
      <Composer.Input
        value={state.input}
        onChange={(text) => sync.updateInput(text)}
      />
      <Composer.Submit onPress={() => sync.submit()} />
    </Composer.Frame>
  )
}
```

**Correct (state management isolated in provider):**

```tsx
// Provider handles all state management details
function ChannelProvider({
  channelId,
  children,
}: {
  channelId: string
  children: React.ReactNode
}) {
  const { state, update, submit } = useGlobalChannel(channelId)
  const inputRef = useRef(null)

  return (
    <Composer.Provider
      state={state}
      actions={{ update, submit }}
      meta={{ inputRef }}
    >
      {children}
    </Composer.Provider>
  )
}

// UI component only knows about the context interface
function ChannelComposer() {
  return (
    <Composer.Frame>
      <Composer.Header />
      <Composer.Input />
      <Composer.Footer>
        <Composer.Submit />
      </Composer.Footer>
    </Composer.Frame>
  )
}

// Usage
function Channel({ channelId }: { channelId: string }) {
  return (
    <ChannelProvider channelId={channelId}>
      <ChannelComposer />
    </ChannelProvider>
  )
}
```

**Different providers, same UI:**

```tsx
// Local state for ephemeral forms
function ForwardMessageProvider({ children }) {
  const [state, setState] = useState(initialState)
  const forwardMessage = useForwardMessage()

  return (
    <Composer.Provider
      state={state}
      actions={{ update: setState, submit: forwardMessage }}
    >
      {children}
    </Composer.Provider>
  )
}

// Global synced state for channels
function ChannelProvider({ channelId, children }) {
  const { state, update, submit } = useGlobalChannel(channelId)

  return (
    <Composer.Provider state={state} actions={{ update, submit }}>
      {children}
    </Composer.Provider>
  )
}
```

The same `Composer.Input` component works with both providers because it only
depends on the context interface, not the implementation.


================================================
FILE: .agents/skills/vercel-composition-patterns/rules/state-lift-state.md
================================================
---
title: Lift State into Provider Components
impact: HIGH
impactDescription: enables state sharing outside component boundaries
tags: composition, state, context, providers
---

## Lift State into Provider Components

Move state management into dedicated provider components. This allows sibling
components outside the main UI to access and modify state without prop drilling
or awkward refs.

**Incorrect (state trapped inside component):**

```tsx
function ForwardMessageComposer() {
  const [state, setState] = useState(initialState)
  const forwardMessage = useForwardMessage()

  return (
    <Composer.Frame>
      <Composer.Input />
      <Composer.Footer />
    </Composer.Frame>
  )
}

// Problem: How does this button access composer state?
function ForwardMessageDialog() {
  return (
    <Dialog>
      <ForwardMessageComposer />
      <MessagePreview /> {/* Needs composer state */}
      <DialogActions>
        <CancelButton />
        <ForwardButton /> {/* Needs to call submit */}
      </DialogActions>
    </Dialog>
  )
}
```

**Incorrect (useEffect to sync state up):**

```tsx
function ForwardMessageDialog() {
  const [input, setInput] = useState('')
  return (
    <Dialog>
      <ForwardMessageComposer onInputChange={setInput} />
      <MessagePreview input={input} />
    </Dialog>
  )
}

function ForwardMessageComposer({ onInputChange }) {
  const [state, setState] = useState(initialState)
  useEffect(() => {
    onInputChange(state.input) // Sync on every change 😬
  }, [state.input])
}
```

**Incorrect (reading state from ref on submit):**

```tsx
function ForwardMessageDialog() {
  const stateRef = useRef(null)
  return (
    <Dialog>
      <ForwardMessageComposer stateRef={stateRef} />
      <ForwardButton onPress={() => submit(stateRef.current)} />
    </Dialog>
  )
}
```

**Correct (state lifted to provider):**

```tsx
function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = useState(initialState)
  const forwardMessage = useForwardMessage()
  const inputRef = useRef(null)

  return (
    <Composer.Provider
      state={state}
      actions={{ update: setState, submit: forwardMessage }}
      meta={{ inputRef }}
    >
      {children}
    </Composer.Provider>
  )
}

function ForwardMessageDialog() {
  return (
    <ForwardMessageProvider>
      <Dialog>
        <ForwardMessageComposer />
        <MessagePreview /> {/* Custom components can access state and actions */}
        <DialogActions>
          <CancelButton />
          <ForwardButton /> {/* Custom components can access state and actions */}
        </DialogActions>
      </Dialog>
    </ForwardMessageProvider>
  )
}

function ForwardButton() {
  const { actions } = use(Composer.Context)
  return <Button onPress={actions.submit}>Forward</Button>
}
```

The ForwardButton lives outside the Composer.Frame but still has access to the
submit action because it's within the provider. Even though it's a one-off
component, it can still access the composer's state and actions from outside the
UI itself.

**Key insight:** Components that need shared state don't have to be visually
nested inside each other—they just need to be within the same provider.


================================================
FILE: .agents/skills/vercel-react-best-practices/AGENTS.md
================================================
# React Best Practices

**Version 1.0.0**  
Vercel Engineering  
January 2026

> **Note:**  
> This document is mainly for agents and LLMs to follow when maintaining,  
> generating, or refactoring React and Next.js codebases. Humans  
> may also find it useful, but guidance here is optimized for automation  
> and consistency by AI-assisted workflows.

---

## Abstract

Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.

---

## Table of Contents

1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL**
   - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed)
   - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization)
   - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes)
   - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations)
   - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries)
2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL**
   - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports)
   - 2.2 [Conditional Module Loading](#22-conditional-module-loading)
   - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries)
   - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components)
   - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent)
3. [Server-Side Performance](#3-server-side-performance) — **HIGH**
   - 3.1 [Authenticate Server Actions Like API Routes](#31-authenticate-server-actions-like-api-routes)
   - 3.2 [Avoid Duplicate Serialization in RSC Props](#32-avoid-duplicate-serialization-in-rsc-props)
   - 3.3 [Cross-Request LRU Caching](#33-cross-request-lru-caching)
   - 3.4 [Minimize Serialization at RSC Boundaries](#34-minimize-serialization-at-rsc-boundaries)
   - 3.5 [Parallel Data Fetching with Component Composition](#35-parallel-data-fetching-with-component-composition)
   - 3.6 [Per-Request Deduplication with React.cache()](#36-per-request-deduplication-with-reactcache)
   - 3.7 [Use after() for Non-Blocking Operations](#37-use-after-for-non-blocking-operations)
4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH**
   - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners)
   - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance)
   - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication)
   - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data)
5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM**
   - 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering)
   - 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point)
   - 5.3 [Do not wrap a simple expression with a primitive result type in useMemo](#53-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo)
   - 5.4 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#54-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant)
   - 5.5 [Extract to Memoized Components](#55-extract-to-memoized-components)
   - 5.6 [Narrow Effect Dependencies](#56-narrow-effect-dependencies)
   - 5.7 [Put Interaction Logic in Event Handlers](#57-put-interaction-logic-in-event-handlers)
   - 5.8 [Subscribe to Derived State](#58-subscribe-to-derived-state)
   - 5.9 [Use Functional setState Updates](#59-use-functional-setstate-updates)
   - 5.10 [Use Lazy State Initialization](#510-use-lazy-state-initialization)
   - 5.11 [Use Transitions for Non-Urgent Updates](#511-use-transitions-for-non-urgent-updates)
   - 5.12 [Use useRef for Transient Values](#512-use-useref-for-transient-values)
6. [Rendering Performance](#6-rendering-performance) — **MEDIUM**
   - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element)
   - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists)
   - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements)
   - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision)
   - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering)
   - 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches)
   - 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide)
   - 6.8 [Use Explicit Conditional Rendering](#68-use-explicit-conditional-rendering)
   - 6.9 [Use useTransition Over Manual Loading States](#69-use-usetransition-over-manual-loading-states)
7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM**
   - 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing)
   - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups)
   - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops)
   - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls)
   - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls)
   - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations)
   - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons)
   - 7.8 [Early Return from Functions](#78-early-return-from-functions)
   - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation)
   - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort)
   - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups)
   - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability)
8. [Advanced Patterns](#8-advanced-patterns) — **LOW**
   - 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount)
   - 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs)
   - 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs)

---

## 1. Eliminating Waterfalls

**Impact: CRITICAL**

Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains.

### 1.1 Defer Await Until Needed

**Impact: HIGH (avoids blocking unused code paths)**

Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them.

**Incorrect: blocks both branches**

```typescript
async function handleRequest(userId: string, skipProcessing: boolean) {
  const userData = await fetchUserData(userId)
  
  if (skipProcessing) {
    // Returns immediately but still waited for userData
    return { skipped: true }
  }
  
  // Only this branch uses userData
  return processUserData(userData)
}
```

**Correct: only blocks when needed**

```typescript
async function handleRequest(userId: string, skipProcessing: boolean) {
  if (skipProcessing) {
    // Returns immediately without waiting
    return { skipped: true }
  }
  
  // Fetch only when needed
  const userData = await fetchUserData(userId)
  return processUserData(userData)
}
```

**Another example: early return optimization**

```typescript
// Incorrect: always fetches permissions
async function updateResource(resourceId: string, userId: string) {
  const permissions = await fetchPermissions(userId)
  const resource = await getResource(resourceId)
  
  if (!resource) {
    return { error: 'Not found' }
  }
  
  if (!permissions.canEdit) {
    return { error: 'Forbidden' }
  }
  
  return await updateResourceData(resource, permissions)
}

// Correct: fetches only when needed
async function updateResource(resourceId: string, userId: string) {
  const resource = await getResource(resourceId)
  
  if (!resource) {
    return { error: 'Not found' }
  }
  
  const permissions = await fetchPermissions(userId)
  
  if (!permissions.canEdit) {
    return { error: 'Forbidden' }
  }
  
  return await updateResourceData(resource, permissions)
}
```

This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive.

### 1.2 Dependency-Based Parallelization

**Impact: CRITICAL (2-10× improvement)**

For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment.

**Incorrect: profile waits for config unnecessarily**

```typescript
const [user, config] = await Promise.all([
  fetchUser(),
  fetchConfig()
])
const profile = await fetchProfile(user.id)
```

**Correct: config and profile run in parallel**

```typescript
import { all } from 'better-all'

const { user, config, profile } = await all({
  async user() { return fetchUser() },
  async config() { return fetchConfig() },
  async profile() {
    return fetchProfile((await this.$.user).id)
  }
})
```

**Alternative without extra dependencies:**

```typescript
const userPromise = fetchUser()
const profilePromise = userPromise.then(user => fetchProfile(user.id))

const [user, config, profile] = await Promise.all([
  userPromise,
  fetchConfig(),
  profilePromise
])
```

We can also create all the promises first, and do `Promise.all()` at the end.

Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all)

### 1.3 Prevent Waterfall Chains in API Routes

**Impact: CRITICAL (2-10× improvement)**

In API routes and Server Actions, start independent operations immediately, even if you don't await them yet.

**Incorrect: config waits for auth, data waits for both**

```typescript
export async function GET(request: Request) {
  const session = await auth()
  const config = await fetchConfig()
  const data = await fetchData(session.user.id)
  return Response.json({ data, config })
}
```

**Correct: auth and config start immediately**

```typescript
export async function GET(request: Request) {
  const sessionPromise = auth()
  const configPromise = fetchConfig()
  const session = await sessionPromise
  const [config, data] = await Promise.all([
    configPromise,
    fetchData(session.user.id)
  ])
  return Response.json({ data, config })
}
```

For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization).

### 1.4 Promise.all() for Independent Operations

**Impact: CRITICAL (2-10× improvement)**

When async operations have no interdependencies, execute them concurrently using `Promise.all()`.

**Incorrect: sequential execution, 3 round trips**

```typescript
const user = await fetchUser()
const posts = await fetchPosts()
const comments = await fetchComments()
```

**Correct: parallel execution, 1 round trip**

```typescript
const [user, posts, comments] = await Promise.all([
  fetchUser(),
  fetchPosts(),
  fetchComments()
])
```

### 1.5 Strategic Suspense Boundaries

**Impact: HIGH (faster initial paint)**

Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.

**Incorrect: wrapper blocked by data fetching**

```tsx
async function Page() {
  const data = await fetchData() // Blocks entire page
  
  return (
    <div>
      <div>Sidebar</div>
      <div>Header</div>
      <div>
        <DataDisplay data={data} />
      </div>
      <div>Footer</div>
    </div>
  )
}
```

The entire layout waits for data even though only the middle section needs it.

**Correct: wrapper shows immediately, data streams in**

```tsx
function Page() {
  return (
    <div>
      <div>Sidebar</div>
      <div>Header</div>
      <div>
        <Suspense fallback={<Skeleton />}>
          <DataDisplay />
        </Suspense>
      </div>
      <div>Footer</div>
    </div>
  )
}

async function DataDisplay() {
  const data = await fetchData() // Only blocks this component
  return <div>{data.content}</div>
}
```

Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data.

**Alternative: share promise across components**

```tsx
function Page() {
  // Start fetch immediately, but don't await
  const dataPromise = fetchData()
  
  return (
    <div>
      <div>Sidebar</div>
      <div>Header</div>
      <Suspense fallback={<Skeleton />}>
        <DataDisplay dataPromise={dataPromise} />
        <DataSummary dataPromise={dataPromise} />
      </Suspense>
      <div>Footer</div>
    </div>
  )
}

function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
  const data = use(dataPromise) // Unwraps the promise
  return <div>{data.content}</div>
}

function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
  const data = use(dataPromise) // Reuses the same promise
  return <div>{data.summary}</div>
}
```

Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.

**When NOT to use this pattern:**

- Critical data needed for layout decisions (affects positioning)

- SEO-critical content above the fold

- Small, fast queries where suspense overhead isn't worth it

- When you want to avoid layout shift (loading → content jump)

**Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.

---

## 2. Bundle Size Optimization

**Impact: CRITICAL**

Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint.

### 2.1 Avoid Barrel File Imports

**Impact: CRITICAL (200-800ms import cost, slow builds)**

Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`).

Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts.

**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph.

**Incorrect: imports entire library**

```tsx
import { Check, X, Menu } from 'lucide-react'
// Loads 1,583 modules, takes ~2.8s extra in dev
// Runtime cost: 200-800ms on every cold start

import { Button, TextField } from '@mui/material'
// Loads 2,225 modules, takes ~4.2s extra in dev
```

**Correct: imports only what you need**

```tsx
import Check from 'lucide-react/dist/esm/icons/check'
import X from 'lucide-react/dist/esm/icons/x'
import Menu from 'lucide-react/dist/esm/icons/menu'
// Loads only 3 modules (~2KB vs ~1MB)

import Button from '@mui/material/Button'
import TextField from '@mui/material/TextField'
// Loads only what you use
```

**Alternative: Next.js 13.5+**

```js
// next.config.js - use optimizePackageImports
module.exports = {
  experimental: {
    optimizePackageImports: ['lucide-react', '@mui/material']
  }
}

// Then you can keep the ergonomic barrel imports:
import { Check, X, Menu } from 'lucide-react'
// Automatically transformed to direct imports at build time
```

Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR.

Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`.

Reference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)

### 2.2 Conditional Module Loading

**Impact: HIGH (loads large data only when needed)**

Load large data or modules only when a feature is activated.

**Example: lazy-load animation frames**

```tsx
function AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch<React.SetStateAction<boolean>> }) {
  const [frames, setFrames] = useState<Frame[] | null>(null)

  useEffect(() => {
    if (enabled && !frames && typeof window !== 'undefined') {
      import('./animation-frames.js')
        .then(mod => setFrames(mod.frames))
        .catch(() => setEnabled(false))
    }
  }, [enabled, frames, setEnabled])

  if (!frames) return <Skeleton />
  return <Canvas frames={frames} />
}
```

The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed.

### 2.3 Defer Non-Critical Third-Party Libraries

**Impact: MEDIUM (loads after hydration)**

Analytics, logging, and error tracking don't block user interaction. Load them after hydration.

**Incorrect: blocks initial bundle**

```tsx
import { Analytics } from '@vercel/analytics/react'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  )
}
```

**Correct: loads after hydration**

```tsx
import dynamic from 'next/dynamic'

const Analytics = dynamic(
  () => import('@vercel/analytics/react').then(m => m.Analytics),
  { ssr: false }
)

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  )
}
```

### 2.4 Dynamic Imports for Heavy Components

**Impact: CRITICAL (directly affects TTI and LCP)**

Use `next/dynamic` to lazy-load large components not needed on initial render.

**Incorrect: Monaco bundles with main chunk ~300KB**

```tsx
import { MonacoEditor } from './monaco-editor'

function CodePanel({ code }: { code: string }) {
  return <MonacoEditor value={code} />
}
```

**Correct: Monaco loads on demand**

```tsx
import dynamic from 'next/dynamic'

const MonacoEditor = dynamic(
  () => import('./monaco-editor').then(m => m.MonacoEditor),
  { ssr: false }
)

function CodePanel({ code }: { code: string }) {
  return <MonacoEditor value={code} />
}
```

### 2.5 Preload Based on User Intent

**Impact: MEDIUM (reduces perceived latency)**

Preload heavy bundles before they're needed to reduce perceived latency.

**Example: preload on hover/focus**

```tsx
function EditorButton({ onClick }: { onClick: () => void }) {
  const preload = () => {
    if (typeof window !== 'undefined') {
      void import('./monaco-editor')
    }
  }

  return (
    <button
      onMouseEnter={preload}
      onFocus={preload}
      onClick={onClick}
    >
      Open Editor
    </button>
  )
}
```

**Example: preload when feature flag is enabled**

```tsx
function FlagsProvider({ children, flags }: Props) {
  useEffect(() => {
    if (flags.editorEnabled && typeof window !== 'undefined') {
      void import('./monaco-editor').then(mod => mod.init())
    }
  }, [flags.editorEnabled])

  return <FlagsContext.Provider value={flags}>
    {children}
  </FlagsContext.Provider>
}
```

The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed.

---

## 3. Server-Side Performance

**Impact: HIGH**

Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times.

### 3.1 Authenticate Server Actions Like API Routes

**Impact: CRITICAL (prevents unauthorized access to server mutations)**

Server Actions (functions with `"use server"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly.

Next.js documentation explicitly states: "Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation."

**Incorrect: no authentication check**

```typescript
'use server'

export async function deleteUser(userId: string) {
  // Anyone can call this! No auth check
  await db.user.delete({ where: { id: userId } })
  return { success: true }
}
```

**Correct: authentication inside the action**

```typescript
'use server'

import { verifySession } from '@/lib/auth'
import { unauthorized } from '@/lib/errors'

export async function deleteUser(userId: string) {
  // Always check auth inside the action
  const session = await verifySession()
  
  if (!session) {
    throw unauthorized('Must be logged in')
  }
  
  // Check authorization too
  if (session.user.role !== 'admin' && session.user.id !== userId) {
    throw unauthorized('Cannot delete other users')
  }
  
  await db.user.delete({ where: { id: userId } })
  return { success: true }
}
```

**With input validation:**

```typescript
'use server'

import { verifySession } from '@/lib/auth'
import { z } from 'zod'

const updateProfileSchema = z.object({
  userId: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email()
})

export async function updateProfile(data: unknown) {
  // Validate input first
  const validated = updateProfileSchema.parse(data)
  
  // Then authenticate
  const session = await verifySession()
  if (!session) {
    throw new Error('Unauthorized')
  }
  
  // Then authorize
  if (session.user.id !== validated.userId) {
    throw new Error('Can only update own profile')
  }
  
  // Finally perform the mutation
  await db.user.update({
    where: { id: validated.userId },
    data: {
      name: validated.name,
      email: validated.email
    }
  })
  
  return { success: true }
}
```

Reference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)

### 3.2 Avoid Duplicate Serialization in RSC Props

**Impact: LOW (reduces network payload by avoiding duplicate serialization)**

RSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server.

**Incorrect: duplicates array**

```tsx
// RSC: sends 6 strings (2 arrays × 3 items)
<ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />
```

**Correct: sends 3 strings**

```tsx
// RSC: send once
<ClientList usernames={usernames} />

// Client: transform there
'use client'
const sorted = useMemo(() => [...usernames].sort(), [usernames])
```

**Nested deduplication behavior:**

```tsx
// string[] - duplicates everything
usernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings

// object[] - duplicates array structure only
users={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)
```

Deduplication works recursively. Impact varies by data type:

- `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated

- `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference

**Operations breaking deduplication: create new references**

- Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]`

- Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())`

**More examples:**

```tsx
// ❌ Bad
<C users={users} active={users.filter(u => u.active)} />
<C product={product} productName={product.name} />

// ✅ Good
<C users={users} />
<C product={product} />
// Do filtering/destructuring in client
```

**Exception:** Pass derived data when transformation is expensive or client doesn't need original.

### 3.3 Cross-Request LRU Caching

**Impact: HIGH (caches across requests)**

`React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache.

**Implementation:**

```typescript
import { LRUCache } from 'lru-cache'

const cache = new LRUCache<string, any>({
  max: 1000,
  ttl: 5 * 60 * 1000  // 5 minutes
})

export async function getUser(id: string) {
  const cached = cache.get(id)
  if (cached) return cached

  const user = await db.user.findUnique({ where: { id } })
  cache.set(id, user)
  return user
}

// Request 1: DB query, result cached
// Request 2: cache hit, no DB query
```

Use when sequential user actions hit multiple endpoints needing the same data within seconds.

**With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis.

**In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching.

Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache)

### 3.4 Minimize Serialization at RSC Boundaries

**Impact: HIGH (reduces data transfer size)**

The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.

**Incorrect: serializes all 50 fields**

```tsx
async function Page() {
  const user = await fetchUser()  // 50 fields
  return <Profile user={user} />
}

'use client'
function Profile({ user }: { user: User }) {
  return <div>{user.name}</div>  // uses 1 field
}
```

**Correct: serializes only 1 field**

```tsx
async function Page() {
  const user = await fetchUser()
  return <Profile name={user.name} />
}

'use client'
function Profile({ name }: { name: string }) {
  return <div>{name}</div>
}
```

### 3.5 Parallel Data Fetching with Component Composition

**Impact: CRITICAL (eliminates server-side waterfalls)**

React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.

**Incorrect: Sidebar waits for Page's fetch to complete**

```tsx
export default async function Page() {
  const header = await fetchHeader()
  return (
    <div>
      <div>{header}</div>
      <Sidebar />
    </div>
  )
}

async function Sidebar() {
  const items = await fetchSidebarItems()
  return <nav>{items.map(renderItem)}</nav>
}
```

**Correct: both fetch simultaneously**

```tsx
async function Header() {
  const data = await fetchHeader()
  return <div>{data}</div>
}

async function Sidebar() {
  const items = await fetchSidebarItems()
  return <nav>{items.map(renderItem)}</nav>
}

export default function Page() {
  return (
    <div>
      <Header />
      <Sidebar />
    </div>
  )
}
```

**Alternative with children prop:**

```tsx
async function Header() {
  const data = await fetchHeader()
  return <div>{data}</div>
}

async function Sidebar() {
  const items = await fetchSidebarItems()
  return <nav>{items.map(renderItem)}</nav>
}

function Layout({ children }: { children: ReactNode }) {
  return (
    <div>
      <Header />
      {children}
    </div>
  )
}

export default function Page() {
  return (
    <Layout>
      <Sidebar />
    </Layout>
  )
}
```

### 3.6 Per-Request Deduplication with React.cache()

**Impact: MEDIUM (deduplicates within request)**

Use `React.cache()` for server-side request deduplication. Authentication and database queries benefit most.

**Usage:**

```typescript
import { cache } from 'react'

export const getCurrentUser = cache(async () => {
  const session = await auth()
  if (!session?.user?.id) return null
  return await db.user.findUnique({
    where: { id: session.user.id }
  })
})
```

Within a single request, multiple calls to `getCurrentUser()` execute the query only once.

**Avoid inline objects as arguments:**

`React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits.

**Incorrect: always cache miss**

```typescript
const getUser = cache(async (params: { uid: number }) => {
  return await db.user.findUnique({ where: { id: params.uid } })
})

// Each call creates new object, never hits cache
getUser({ uid: 1 })
getUser({ uid: 1 })  // Cache miss, runs query again
```

**Correct: cache hit**

```typescript
const params = { uid: 1 }
getUser(params)  // Query runs
getUser(params)  // Cache hit (same reference)
```

If you must pass objects, pass the same reference:

**Next.js-Specific Note:**

In Next.js, the `fetch` API is automatically extended with request memoization. Requests with the same URL and options are automatically deduplicated within a single request, so you don't need `React.cache()` for `fetch` calls. However, `React.cache()` is still essential for other async tasks:

- Database queries (Prisma, Drizzle, etc.)

- Heavy computations

- Authentication checks

- File system operations

- Any non-fetch async work

Use `React.cache()` to deduplicate these operations across your component tree.

Reference: [https://react.dev/reference/react/cache](https://react.dev/reference/react/cache)

### 3.7 Use after() for Non-Blocking Operations

**Impact: MEDIUM (faster response times)**

Use Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response.

**Incorrect: blocks response**

```tsx
import { logUserAction } from '@/app/utils'

export async function POST(request: Request) {
  // Perform mutation
  await updateDatabase(request)
  
  // Logging blocks the response
  const userAgent = request.headers.get('user-agent') || 'unknown'
  await logUserAction({ userAgent })
  
  return new Response(JSON.stringify({ status: 'success' }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  })
}
```

**Correct: non-blocking**

```tsx
import { after } from 'next/server'
import { headers, cookies } from 'next/headers'
import { logUserAction } from '@/app/utils'

export async function POST(request: Request) {
  // Perform mutation
  await updateDatabase(request)
  
  // Log after response is sent
  after(async () => {
    const userAgent = (await headers()).get('user-agent') || 'unknown'
    const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous'
    
    logUserAction({ sessionCookie, userAgent })
  })
  
  return new Response(JSON.stringify({ status: 'success' }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  })
}
```

The response is sent immediately while logging happens in the background.

**Common use cases:**

- Analytics tracking

- Audit logging

- Sending notifications

- Cache invalidation

- Cleanup tasks

**Important notes:**

- `after()` runs even if the response fails or redirects

- Works in Server Actions, Route Handlers, and Server Components

Reference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after)

---

## 4. Client-Side Data Fetching

**Impact: MEDIUM-HIGH**

Automatic deduplication and efficient data fetching patterns reduce redundant network requests.

### 4.1 Deduplicate Global Event Listeners

**Impact: LOW (single listener for N components)**

Use `useSWRSubscription()` to share global event listeners across component instances.

**Incorrect: N instances = N listeners**

```tsx
function useKeyboardShortcut(key: string, callback: () => void) {
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if (e.metaKey && e.key === key) {
        callback()
      }
    }
    window.addEventListener('keydown', handler)
    return () => window.removeEventListener('keydown', handler)
  }, [key, callback])
}
```

When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener.

**Correct: N instances = 1 listener**

```tsx
import useSWRSubscription from 'swr/subscription'

// Module-level Map to track callbacks per key
const keyCallbacks = new Map<string, Set<() => void>>()

function useKeyboardShortcut(key: string, callback: () => void) {
  // Register this callback in the Map
  useEffect(() => {
    if (!keyCallbacks.has(key)) {
      keyCallbacks.set(key, new Set())
    }
    keyCallbacks.get(key)!.add(callback)

    return () => {
      const set = keyCallbacks.get(key)
      if (set) {
        set.delete(callback)
        if (set.size === 0) {
          keyCallbacks.delete(key)
        }
      }
    }
  }, [key, callback])

  useSWRSubscription('global-keydown', () => {
    const handler = (e: KeyboardEvent) => {
      if (e.metaKey && keyCallbacks.has(e.key)) {
        keyCallbacks.get(e.key)!.forEach(cb => cb())
      }
    }
    window.addEventListener('keydown', handler)
    return () => window.removeEventListener('keydown', handler)
  })
}

function Profile() {
  // Multiple shortcuts will share the same listener
  useKeyboardShortcut('p', () => { /* ... */ }) 
  useKeyboardShortcut('k', () => { /* ... */ })
  // ...
}
```

### 4.2 Use Passive Event Listeners for Scrolling Performance

**Impact: MEDIUM (eliminates scroll delay caused by event listeners)**

Add `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay.

**Incorrect:**

```typescript
useEffect(() => {
  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
  
  document.addEventListener('touchstart', handleTouch)
  document.addEventListener('wheel', handleWheel)
  
  return () => {
    document.removeEventListener('touchstart', handleTouch)
    document.removeEventListener('wheel', handleWheel)
  }
}, [])
```

**Correct:**

```typescript
useEffect(() => {
  const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)
  const handleWheel = (e: WheelEvent) => console.log(e.deltaY)
  
  document.addEventListener('touchstart', handleTouch, { passive: true })
  document.addEventListener('wheel', handleWheel, { passive: true })
  
  return () => {
    document.removeEventListener('touchstart', handleTouch)
    document.removeEventListener('wheel', handleWheel)
  }
}, [])
```

**Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`.

**Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`.

### 4.3 Use SWR for Automatic Deduplication

**Impact: MEDIUM-HIGH (automatic deduplication)**

SWR enables request deduplication, caching, and revalidation across component instances.

**Incorrect: no deduplication, each instance fetches**

```tsx
function UserList() {
  const [users, setUsers] = useState([])
  useEffect(() => {
    fetch('/api/users')
      .then(r => r.json())
      .then(setUsers)
  }, [])
}
```

**Correct: multiple instances share one request**

```tsx
import useSWR from 'swr'

function UserList() {
  const { data: users } = useSWR('/api/users', fetcher)
}
```

**For immutable data:**

```tsx
import { useImmutableSWR } from '@/lib/swr'

function StaticContent() {
  const { data } = useImmutableSWR('/api/config', fetcher)
}
```

**For mutations:**

```tsx
import { useSWRMutation } from 'swr/mutation'

function UpdateButton() {
  const { trigger } = useSWRMutation('/api/user', updateUser)
  return <button onClick={() => trigger()}>Update</button>
}
```

Reference: [https://swr.vercel.app](https://swr.vercel.app)

### 4.4 Version and Minimize localStorage Data

**Impact: MEDIUM (prevents schema conflicts, reduces storage size)**

Add version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data.

**Incorrect:**

```typescript
// No version, stores everything, no error handling
localStorage.setItem('userConfig', JSON.stringify(fullUserObject))
const data = localStorage.getItem('userConfig')
```

**Correct:**

```typescript
const VERSION = 'v2'

function saveConfig(config: { theme: string; language: string }) {
  try {
    localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))
  } catch {
    // Throws in incognito/private browsing, quota exceeded, or disabled
  }
}

function loadConfig() {
  try {
    const data = localStorage.getItem(`userConfig:${VERSION}`)
    return data ? JSON.parse(data) : null
  } catch {
    return null
  }
}

// Migration from v1 to v2
function migrate() {
  try {
    const v1 = localStorage.getItem('userConfig:v1')
    if (v1) {
      const old = JSON.parse(v1)
      saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })
      localStorage.removeItem('userConfig:v1')
    }
  } catch {}
}
```

**Store minimal fields from server responses:**

```typescript
// User object has 20+ fields, only store what UI needs
function cachePrefs(user: FullUser) {
  try {
    localStorage.setItem('prefs:v1', JSON.stringify({
      theme: user.preferences.theme,
      notifications: user.preferences.notifications
    }))
  } catch {}
}
```

**Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled.

**Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags.

---

## 5. Re-render Optimization

**Impact: MEDIUM**

Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness.

### 5.1 Calculate Derived State During Rendering

**Impact: MEDIUM (avoids redundant renders and state drift)**

If a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead.

**Incorrect: redundant state and effect**

```tsx
function Form() {
  const [firstName, setFirstName] = useState('First')
  const [lastName, setLastName] = useState('Last')
  const [fullName, setFullName] = useState('')

  useEffect(() => {
    setFullName(firstName + ' ' + lastName)
  }, [firstName, lastName])

  return <p>{fullName}</p>
}
```

**Correct: derive during render**

```tsx
function Form() {
  const [firstName, setFirstName] = useState('First')
  const [lastName, setLastName] = useState('Last')
  const fullName = firstName + ' ' + lastName

  return <p>{fullName}</p>
}
```

Reference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect)

### 5.2 Defer State Reads to Usage Point

**Impact: MEDIUM (avoids unnecessary subscriptions)**

Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.

**Incorrect: subscribes to all searchParams changes**

```tsx
function ShareButton({ chatId }: { chatId: string }) {
  const searchParams = useSearchParams()

  const handleShare = () => {
    const ref = searchParams.get('ref')
    shareChat(chatId, { ref })
  }

  return <button onClick={handleShare}>Share</button>
}
```

**Correct: reads on demand, no subscription**

```tsx
function ShareButton({ chatId }: { chatId: string }) {
  const handleShare = () => {
    const params = new URLSearchParams(window.location.search)
    const ref = params.get('ref')
    shareChat(chatId, { ref })
  }

  return <button onClick={handleShare}>Share</button>
}
```

### 5.3 Do not wrap a simple expression with a primitive result type in useMemo

**Impact: LOW-MEDIUM (wasted computation on every render)**

When an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`.

Calling `useMemo` and comparing hook dependencies may consume more resources than the expression itself.

**Incorrect:**

```tsx
function Header({ user, notifications }: Props) {
  const isLoading = useMemo(() => {
    return user.isLoading || notifications.isLoading
  }, [user.isLoading, notifications.isLoading])

  if (isLoading) return <Skeleton />
  // return some markup
}
```

**Correct:**

```tsx
function Header({ user, notifications }: Props) {
  const isLoading = user.isLoading || notifications.isLoading

  if (isLoading) return <Skeleton />
  // return some markup
}
```

### 5.4 Extract Default Non-primitive Parameter Value from Memoized Component to Constant

**Impact: MEDIUM (restores memoization by using a constant for default value)**

When memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`.

To address this issue, extract the default value into a constant.

**Incorrect: `onClick` has different values on every rerender**

```tsx
const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {
  // ...
})

// Used without optional onClick
<UserAvatar />
```

**Correct: stable default value**

```tsx
const NOOP = () => {};

const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {
  // ...
})

// Used without optional onClick
<UserAvatar />
```

### 5.5 Extract to Memoized Components

**Impact: MEDIUM (enables early returns)**

Extract expensive work into memoized components to enable early returns before computation.

**Incorrect: computes avatar even when loading**

```tsx
function Profile({ user, loading }: Props) {
  const avatar = useMemo(() => {
    const id = computeAvatarId(user)
    return <Avatar id={id} />
  }, [user])

  if (loading) return <Skeleton />
  return <div>{avatar}</div>
}
```

**Correct: skips computation when loading**

```tsx
const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
  const id = useMemo(() => computeAvatarId(user), [user])
  return <Avatar id={id} />
})

function Profile({ user, loading }: Props) {
  if (loading) return <Skeleton />
  return (
    <div>
      <UserAvatar user={user} />
    </div>
  )
}
```

**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders.

### 5.6 Narrow Effect Dependencies

**Impact: LOW (minimizes effect re-runs)**

Specify primitive dependencies instead of objects to minimize effect re-runs.

**Incorrect: re-runs on any user field change**

```tsx
useEffect(() => {
  console.log(user
Download .txt
gitextract_6kt6d70a/

├── .agents/
│   └── skills/
│       ├── logging-best-practices/
│       │   └── SKILL.md
│       ├── trigger-agents/
│       │   ├── SKILL.md
│       │   └── references/
│       │       ├── ai-tool.md
│       │       ├── orchestration.md
│       │       ├── streaming.md
│       │       └── waitpoints.md
│       ├── trigger-config/
│       │   ├── SKILL.md
│       │   └── references/
│       │       └── config.md
│       ├── trigger-cost-savings/
│       │   ├── SKILL.md
│       │   └── references/
│       │       └── cost-reduction.md
│       ├── trigger-realtime/
│       │   ├── SKILL.md
│       │   └── references/
│       │       └── realtime.md
│       ├── trigger-setup/
│       │   ├── SKILL.md
│       │   └── references/
│       │       ├── environment-setup.md
│       │       └── project-structure.md
│       ├── trigger-tasks/
│       │   ├── SKILL.md
│       │   └── references/
│       │       ├── advanced-tasks.md
│       │       ├── basic-tasks.md
│       │       └── scheduled-tasks.md
│       ├── vercel-composition-patterns/
│       │   ├── AGENTS.md
│       │   ├── SKILL.md
│       │   └── rules/
│       │       ├── architecture-avoid-boolean-props.md
│       │       ├── architecture-compound-components.md
│       │       ├── patterns-children-over-render-props.md
│       │       ├── patterns-explicit-variants.md
│       │       ├── react19-no-forwardref.md
│       │       ├── state-context-interface.md
│       │       ├── state-decouple-implementation.md
│       │       └── state-lift-state.md
│       ├── vercel-react-best-practices/
│       │   ├── AGENTS.md
│       │   ├── SKILL.md
│       │   └── rules/
│       │       ├── advanced-event-handler-refs.md
│       │       ├── advanced-init-once.md
│       │       ├── advanced-use-latest.md
│       │       ├── async-api-routes.md
│       │       ├── async-defer-await.md
│       │       ├── async-dependencies.md
│       │       ├── async-parallel.md
│       │       ├── async-suspense-boundaries.md
│       │       ├── bundle-barrel-imports.md
│       │       ├── bundle-conditional.md
│       │       ├── bundle-defer-third-party.md
│       │       ├── bundle-dynamic-imports.md
│       │       ├── bundle-preload.md
│       │       ├── client-event-listeners.md
│       │       ├── client-localstorage-schema.md
│       │       ├── client-passive-event-listeners.md
│       │       ├── client-swr-dedup.md
│       │       ├── js-batch-dom-css.md
│       │       ├── js-cache-function-results.md
│       │       ├── js-cache-property-access.md
│       │       ├── js-cache-storage.md
│       │       ├── js-combine-iterations.md
│       │       ├── js-early-exit.md
│       │       ├── js-hoist-regexp.md
│       │       ├── js-index-maps.md
│       │       ├── js-length-check-first.md
│       │       ├── js-min-max-loop.md
│       │       ├── js-set-map-lookups.md
│       │       ├── js-tosorted-immutable.md
│       │       ├── rendering-activity.md
│       │       ├── rendering-animate-svg-wrapper.md
│       │       ├── rendering-conditional-render.md
│       │       ├── rendering-content-visibility.md
│       │       ├── rendering-hoist-jsx.md
│       │       ├── rendering-hydration-no-flicker.md
│       │       ├── rendering-hydration-suppress-warning.md
│       │       ├── rendering-svg-precision.md
│       │       ├── rendering-usetransition-loading.md
│       │       ├── rerender-defer-reads.md
│       │       ├── rerender-dependencies.md
│       │       ├── rerender-derived-state-no-effect.md
│       │       ├── rerender-derived-state.md
│       │       ├── rerender-functional-setstate.md
│       │       ├── rerender-lazy-state-init.md
│       │       ├── rerender-memo-with-default-value.md
│       │       ├── rerender-memo.md
│       │       ├── rerender-move-effect-to-event.md
│       │       ├── rerender-simple-expression-in-memo.md
│       │       ├── rerender-transitions.md
│       │       ├── rerender-use-ref-transient-values.md
│       │       ├── server-after-nonblocking.md
│       │       ├── server-auth-actions.md
│       │       ├── server-cache-lru.md
│       │       ├── server-cache-react.md
│       │       ├── server-dedup-props.md
│       │       ├── server-parallel-fetching.md
│       │       └── server-serialization.md
│       └── web-design-guidelines/
│           └── SKILL.md
├── .claude/
│   └── agents/
│       └── trigger-dev-task-writer.md
├── .cursor/
│   └── rules/
│       ├── convex_rules.mdc
│       ├── trigger.advanced-tasks.mdc
│       ├── trigger.basic.mdc
│       ├── trigger.config.mdc
│       ├── trigger.realtime.mdc
│       └── trigger.scheduled-tasks.mdc
├── .env.e2e.example
├── .env.local.example
├── .github/
│   ├── skills/
│   │   ├── vercel-react-best-practices/
│   │   │   ├── AGENTS.md
│   │   │   ├── README.md
│   │   │   ├── SKILL.md
│   │   │   ├── metadata.json
│   │   │   └── rules/
│   │   │       ├── _sections.md
│   │   │       ├── _template.md
│   │   │       ├── advanced-event-handler-refs.md
│   │   │       ├── advanced-use-latest.md
│   │   │       ├── async-api-routes.md
│   │   │       ├── async-defer-await.md
│   │   │       ├── async-dependencies.md
│   │   │       ├── async-parallel.md
│   │   │       ├── async-suspense-boundaries.md
│   │   │       ├── bundle-barrel-imports.md
│   │   │       ├── bundle-conditional.md
│   │   │       ├── bundle-defer-third-party.md
│   │   │       ├── bundle-dynamic-imports.md
│   │   │       ├── bundle-preload.md
│   │   │       ├── client-event-listeners.md
│   │   │       ├── client-swr-dedup.md
│   │   │       ├── js-batch-dom-css.md
│   │   │       ├── js-cache-function-results.md
│   │   │       ├── js-cache-property-access.md
│   │   │       ├── js-cache-storage.md
│   │   │       ├── js-combine-iterations.md
│   │   │       ├── js-early-exit.md
│   │   │       ├── js-hoist-regexp.md
│   │   │       ├── js-index-maps.md
│   │   │       ├── js-length-check-first.md
│   │   │       ├── js-min-max-loop.md
│   │   │       ├── js-set-map-lookups.md
│   │   │       ├── js-tosorted-immutable.md
│   │   │       ├── rendering-activity.md
│   │   │       ├── rendering-animate-svg-wrapper.md
│   │   │       ├── rendering-conditional-render.md
│   │   │       ├── rendering-content-visibility.md
│   │   │       ├── rendering-hoist-jsx.md
│   │   │       ├── rendering-hydration-no-flicker.md
│   │   │       ├── rendering-svg-precision.md
│   │   │       ├── rerender-defer-reads.md
│   │   │       ├── rerender-dependencies.md
│   │   │       ├── rerender-derived-state.md
│   │   │       ├── rerender-functional-setstate.md
│   │   │       ├── rerender-lazy-state-init.md
│   │   │       ├── rerender-memo.md
│   │   │       ├── rerender-transitions.md
│   │   │       ├── server-after-nonblocking.md
│   │   │       ├── server-cache-lru.md
│   │   │       ├── server-cache-react.md
│   │   │       ├── server-parallel-fetching.md
│   │   │       └── server-serialization.md
│   │   └── web-design-guidelines/
│   │       └── SKILL.md
│   └── workflows/
│       ├── desktop-build.yml
│       ├── docker-sandbox.yml
│       └── test.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierignore
├── LICENSE
├── README.md
├── __mocks__/
│   ├── @aws-sdk/
│   │   ├── client-s3.ts
│   │   └── s3-request-presigner.ts
│   ├── @upstash/
│   │   ├── ratelimit.ts
│   │   └── redis.ts
│   ├── convex/
│   │   └── browser.ts
│   ├── convex-react.ts
│   ├── franc-min.ts
│   ├── jose.ts
│   ├── next/
│   │   └── navigation.ts
│   ├── react-hotkeys-hook.ts
│   ├── react-markdown.tsx
│   ├── react-shiki.tsx
│   ├── shiki.ts
│   ├── streamdown.tsx
│   ├── stripe.ts
│   ├── use-stick-to-bottom.ts
│   ├── uuid.ts
│   ├── workos-authkit.ts
│   ├── workos-node.ts
│   └── workos.ts
├── app/
│   ├── (chat)/
│   │   ├── c/
│   │   │   └── [id]/
│   │   │       └── page.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── api/
│   │   ├── agent/
│   │   │   └── route.ts
│   │   ├── agent-long/
│   │   │   ├── cancel/
│   │   │   │   └── route.ts
│   │   │   ├── resume/
│   │   │   │   └── route.ts
│   │   │   └── route.ts
│   │   ├── auth/
│   │   │   └── desktop-callback/
│   │   │       └── route.ts
│   │   ├── chat/
│   │   │   ├── [id]/
│   │   │   │   └── stream/
│   │   │   │       └── route.ts
│   │   │   └── route.ts
│   │   ├── clear-auth-cookies/
│   │   │   └── route.ts
│   │   ├── delete-account/
│   │   │   └── route.ts
│   │   ├── delete-sandboxes/
│   │   │   └── route.ts
│   │   ├── entitlements/
│   │   │   └── route.ts
│   │   ├── extra-usage/
│   │   │   ├── confirm/
│   │   │   │   └── route.ts
│   │   │   └── webhook/
│   │   │       └── route.ts
│   │   ├── fraud/
│   │   │   └── webhook/
│   │   │       └── route.ts
│   │   ├── logout-all/
│   │   │   └── route.ts
│   │   ├── mfa/
│   │   │   ├── delete/
│   │   │   │   └── route.ts
│   │   │   ├── enroll/
│   │   │   │   └── route.ts
│   │   │   ├── factors/
│   │   │   │   └── route.ts
│   │   │   └── verify/
│   │   │       └── route.ts
│   │   ├── migrate-pentestgpt/
│   │   │   └── route.ts
│   │   ├── sandbox/
│   │   │   └── presence/
│   │   │       └── route.ts
│   │   ├── stripe.ts
│   │   ├── subscribe/
│   │   │   └── route.ts
│   │   ├── subscription/
│   │   │   └── webhook/
│   │   │       └── route.ts
│   │   ├── subscription-details/
│   │   │   └── route.ts
│   │   ├── team/
│   │   │   ├── extra-usage/
│   │   │   │   ├── confirm/
│   │   │   │   │   └── route.ts
│   │   │   │   ├── members/
│   │   │   │   │   └── [userId]/
│   │   │   │   │       └── route.ts
│   │   │   │   ├── purchase/
│   │   │   │   │   └── route.ts
│   │   │   │   ├── route.ts
│   │   │   │   └── webhook/
│   │   │   │       └── route.ts
│   │   │   ├── invite/
│   │   │   │   └── route.ts
│   │   │   ├── members/
│   │   │   │   └── route.ts
│   │   │   ├── seats/
│   │   │   │   └── route.ts
│   │   │   └── team-auth.ts
│   │   └── workos.ts
│   ├── auth-error/
│   │   ├── auto-retry-button.tsx
│   │   └── page.tsx
│   ├── callback/
│   │   └── route.ts
│   ├── components/
│   │   ├── AccountTab.tsx
│   │   ├── AgentsTab.tsx
│   │   ├── AllFilesDialog.tsx
│   │   ├── AttachmentButton.tsx
│   │   ├── BillingFrequencySelector.tsx
│   │   ├── BranchIndicator.tsx
│   │   ├── CancelSubscriptionDialog.tsx
│   │   ├── ChatHeader.tsx
│   │   ├── ChatInput/
│   │   │   ├── AgentUpgradeDialog.tsx
│   │   │   ├── ChatInput.tsx
│   │   │   ├── ChatInputTextarea.tsx
│   │   │   ├── ChatInputToolbar.tsx
│   │   │   ├── ChatModeSelector.tsx
│   │   │   ├── ModeSelectorMenu/
│   │   │   │   ├── ModeOptionItem.tsx
│   │   │   │   ├── ModeSelectorContent.tsx
│   │   │   │   ├── ModeSelectorTrigger.tsx
│   │   │   │   └── index.ts
│   │   │   ├── SubmitStopButton.tsx
│   │   │   └── index.ts
│   │   ├── ChatItem.tsx
│   │   ├── ChatLayout.tsx
│   │   ├── CodeHighlight.tsx
│   │   ├── ComputerCodeBlock.tsx
│   │   ├── ComputerSidebar.tsx
│   │   ├── ContextUsageIndicator.tsx
│   │   ├── ConvexErrorBoundary.tsx
│   │   ├── CustomizeHackerAIDialog.tsx
│   │   ├── DataControlsTab.tsx
│   │   ├── DataStreamProvider.tsx
│   │   ├── DeleteAccountDialog.tsx
│   │   ├── DeleteMfaFactorDialog.tsx
│   │   ├── DiffView.tsx
│   │   ├── DragDropOverlay.tsx
│   │   ├── ExtraUsageSection.tsx
│   │   ├── FeedbackInput.tsx
│   │   ├── FilePartRenderer.tsx
│   │   ├── FileUploadPreview.tsx
│   │   ├── FinishReasonNotice.tsx
│   │   ├── Footer.tsx
│   │   ├── HackingSuggestions.tsx
│   │   ├── Header.tsx
│   │   ├── ImageViewer.tsx
│   │   ├── ManageNotesDialog.tsx
│   │   ├── ManageSharedChatsDialog.tsx
│   │   ├── MarkdownTable.tsx
│   │   ├── MemoizedMarkdown.tsx
│   │   ├── MessageActions.tsx
│   │   ├── MessageEditor.tsx
│   │   ├── MessageErrorState.tsx
│   │   ├── MessageItem.tsx
│   │   ├── MessagePartHandler.tsx
│   │   ├── MessageSearchDialog.tsx
│   │   ├── Messages.tsx
│   │   ├── MfaVerificationDialog.tsx
│   │   ├── MigratePentestgptDialog.tsx
│   │   ├── ModelSelector/
│   │   │   ├── CostIndicator.tsx
│   │   │   ├── __tests__/
│   │   │   │   └── options-drift.test.ts
│   │   │   ├── constants.ts
│   │   │   └── icons.tsx
│   │   ├── ModelSelector.tsx
│   │   ├── PersonalizationTab.tsx
│   │   ├── PricingDialog.tsx
│   │   ├── QueuedMessagesPanel.tsx
│   │   ├── RateLimitWarning.tsx
│   │   ├── ReasoningHandler.tsx
│   │   ├── RemoteControlTab.tsx
│   │   ├── SandboxSelector.tsx
│   │   ├── ScrollToBottomButton.tsx
│   │   ├── SecurityTab.tsx
│   │   ├── SettingsDialog.tsx
│   │   ├── ShareDialog.tsx
│   │   ├── SharedLinksTab.tsx
│   │   ├── Sidebar.tsx
│   │   ├── SidebarHeader.tsx
│   │   ├── SidebarHistory.tsx
│   │   ├── SidebarUserNav.tsx
│   │   ├── SourcesDialog.tsx
│   │   ├── TeamDialogs.tsx
│   │   ├── TeamExtraUsageSection.tsx
│   │   ├── TeamMembersList.tsx
│   │   ├── TeamPricingDialog.tsx
│   │   ├── TeamTab.tsx
│   │   ├── TerminalCodeBlock.tsx
│   │   ├── TodoPanel.tsx
│   │   ├── UpgradeConfirmationDialog.tsx
│   │   ├── UsageTab.tsx
│   │   ├── XtermRenderer.tsx
│   │   ├── __mocks__/
│   │   │   └── DataStreamProvider.tsx
│   │   ├── __tests__/
│   │   │   ├── ChatInput.integration.test.tsx
│   │   │   ├── CodeHighlight.test.tsx
│   │   │   ├── ContextUsageIndicator.test.tsx
│   │   │   ├── FinishReasonNotice.test.tsx
│   │   │   ├── MessageItem.worked-for.test.tsx
│   │   │   ├── RemoteControlTab.test.tsx
│   │   │   ├── ShareDialog.test.tsx
│   │   │   ├── UpgradeConfirmationDialog.test.tsx
│   │   │   ├── chat.integration.test.tsx
│   │   │   └── worked-for-parts.test.ts
│   │   ├── chat.tsx
│   │   ├── computer-sidebar-utils.tsx
│   │   ├── extra-usage/
│   │   │   ├── AdjustSpendingLimitDialog.tsx
│   │   │   ├── AutoReloadDialog.tsx
│   │   │   ├── BuyExtraUsageDialog.tsx
│   │   │   ├── ExtraUsagePurchaseToast.tsx
│   │   │   ├── TurnOffExtraUsageDialog.tsx
│   │   │   └── index.ts
│   │   ├── testUtils.tsx
│   │   ├── tools/
│   │   │   ├── FileHandler.tsx
│   │   │   ├── FileToolsHandler.tsx
│   │   │   ├── GetTerminalFilesHandler.tsx
│   │   │   ├── HttpRequestToolHandler.tsx
│   │   │   ├── NotesToolHandler.tsx
│   │   │   ├── ProxyToolHandler.tsx
│   │   │   ├── SummarizationHandler.tsx
│   │   │   ├── TerminalToolHandler.tsx
│   │   │   ├── TodoToolHandler.tsx
│   │   │   ├── WebToolHandler.tsx
│   │   │   ├── __tests__/
│   │   │   │   └── proxy-formatters.test.ts
│   │   │   ├── notes-tool-utils.tsx
│   │   │   └── shell-tool-utils.ts
│   │   ├── usage/
│   │   │   ├── IncludedUsageCard.tsx
│   │   │   ├── OnDemandUsageCard.tsx
│   │   │   ├── TokenBreakdownTooltip.tsx
│   │   │   └── UsageLogsTable.tsx
│   │   └── worked-for-parts.ts
│   ├── contexts/
│   │   ├── FileUrlCacheContext.tsx
│   │   ├── GlobalState.tsx
│   │   └── TodoBlockContext.tsx
│   ├── desktop-callback/
│   │   └── route.ts
│   ├── desktop-login/
│   │   └── route.ts
│   ├── download/
│   │   ├── DownloadPageContent.tsx
│   │   ├── DownloadSection.tsx
│   │   ├── constants.ts
│   │   ├── icons/
│   │   │   ├── AndroidIcon.tsx
│   │   │   ├── AppleIcon.tsx
│   │   │   ├── DeviceIcon.tsx
│   │   │   ├── DownloadIcon.tsx
│   │   │   ├── LinuxIcon.tsx
│   │   │   ├── WindowsIcon.tsx
│   │   │   └── index.ts
│   │   └── page.tsx
│   ├── globals.css
│   ├── hooks/
│   │   ├── __tests__/
│   │   │   ├── useAutoContinue.test.ts
│   │   │   ├── useFileUpload.local-desktop.test.tsx
│   │   │   ├── useImageUrlCache.test.ts
│   │   │   └── useToolSidebar.test.tsx
│   │   ├── useAutoContinue.ts
│   │   ├── useAutoResume.ts
│   │   ├── useChatHandlers.ts
│   │   ├── useChats.ts
│   │   ├── useDocumentDragAndDrop.ts
│   │   ├── useFeedback.ts
│   │   ├── useFileUpload.ts
│   │   ├── useFileUrlCache.ts
│   │   ├── useLatestRef.ts
│   │   ├── useMessageScroll.ts
│   │   ├── usePentestgptMigration.ts
│   │   ├── usePricingDialog.ts
│   │   ├── useSandboxPreference.ts
│   │   ├── useSidebarNavigation.ts
│   │   ├── useTauri.ts
│   │   ├── useToolSidebar.ts
│   │   ├── useTypingAnimation.ts
│   │   └── useUpgrade.ts
│   ├── layout.tsx
│   ├── login/
│   │   └── route.ts
│   ├── logout/
│   │   └── route.ts
│   ├── posthog.js
│   ├── privacy-policy/
│   │   └── page.tsx
│   ├── providers.tsx
│   ├── services/
│   │   ├── __tests__/
│   │   │   └── desktop-sandbox-bridge.test.ts
│   │   └── desktop-sandbox-bridge.ts
│   ├── share/
│   │   └── [shareId]/
│   │       ├── SharedChatContext.tsx
│   │       ├── SharedChatView.tsx
│   │       ├── SharedMessages.tsx
│   │       ├── __tests__/
│   │       │   └── SharedMessages.test.tsx
│   │       ├── components/
│   │       │   ├── SharedMessagePartHandler.tsx
│   │       │   └── SharedTodoBlock.tsx
│   │       └── page.tsx
│   ├── signup/
│   │   └── route.ts
│   └── terms-of-service/
│       └── page.tsx
├── components/
│   ├── ConvexClientProvider.tsx
│   ├── ai-elements/
│   │   ├── __tests__/
│   │   │   ├── reasoning.test.tsx
│   │   │   └── worked-for.test.tsx
│   │   ├── reasoning.tsx
│   │   ├── shimmer.tsx
│   │   └── worked-for.tsx
│   ├── icons/
│   │   └── hackerai-svg.tsx
│   └── ui/
│       ├── alert-dialog.tsx
│       ├── avatar.tsx
│       ├── badge.tsx
│       ├── button.tsx
│       ├── calendar.tsx
│       ├── card.tsx
│       ├── code-action-buttons.tsx
│       ├── collapsible.tsx
│       ├── dialog.tsx
│       ├── dots-spinner.tsx
│       ├── dropdown-menu.tsx
│       ├── input.tsx
│       ├── label.tsx
│       ├── loading.tsx
│       ├── popover.tsx
│       ├── radio-group.tsx
│       ├── select.tsx
│       ├── separator.tsx
│       ├── shared-todo-item.tsx
│       ├── sheet.tsx
│       ├── sidebar.tsx
│       ├── skeleton.tsx
│       ├── sonner.tsx
│       ├── switch.tsx
│       ├── textarea.tsx
│       ├── todo-block.tsx
│       ├── tool-block.tsx
│       ├── tooltip.tsx
│       └── with-tooltip.tsx
├── components.json
├── convex/
│   ├── __tests__/
│   │   ├── chatSummaryFallback.test.ts
│   │   ├── fileStorage.aggregate.test.ts
│   │   ├── fileStorage.delete.test.ts
│   │   ├── messages.hidden.test.ts
│   │   ├── s3Actions.test.ts
│   │   ├── s3Cleanup.test.ts
│   │   ├── s3Utils.test.ts
│   │   ├── teamExtraUsage.test.ts
│   │   ├── userDeletion.test.ts
│   │   ├── userSuspensions.test.ts
│   │   └── webhookClaim.test.ts
│   ├── _generated/
│   │   ├── api.d.ts
│   │   ├── api.js
│   │   ├── dataModel.d.ts
│   │   ├── server.d.ts
│   │   └── server.js
│   ├── auth.config.ts
│   ├── chatStreams.ts
│   ├── chats.ts
│   ├── constants.ts
│   ├── convex.config.ts
│   ├── crons.ts
│   ├── extraUsage.ts
│   ├── extraUsageActions.ts
│   ├── feedback.ts
│   ├── fileActions.ts
│   ├── fileAggregate.ts
│   ├── fileStorage.ts
│   ├── lib/
│   │   ├── logger.ts
│   │   └── utils.ts
│   ├── localSandbox.ts
│   ├── messages.ts
│   ├── notes.ts
│   ├── rateLimitStatus.ts
│   ├── redisPubsub.ts
│   ├── s3Actions.ts
│   ├── s3Cleanup.ts
│   ├── s3Utils.ts
│   ├── schema.ts
│   ├── sharedChats.ts
│   ├── teamExtraUsage.ts
│   ├── teamExtraUsageActions.ts
│   ├── tempStreams.ts
│   ├── usageLogs.ts
│   ├── userCustomization.ts
│   ├── userDeletion.ts
│   └── userSuspensions.ts
├── docker/
│   ├── Dockerfile
│   ├── build.sh
│   ├── centrifugo/
│   │   ├── README.md
│   │   ├── config.json
│   │   └── docker-compose.yml
│   └── run.sh
├── e2b/
│   ├── README.md
│   ├── build.dev.ts
│   ├── build.prod.ts
│   └── template.ts
├── e2e/
│   ├── README.md
│   ├── chat-agent.spec.ts
│   ├── chat-files-pro.spec.ts
│   ├── chat-free.spec.ts
│   ├── chat-pinned.spec.ts
│   ├── chat-switching.spec.ts
│   ├── constants.ts
│   ├── fixtures/
│   │   └── auth.ts
│   ├── helpers/
│   │   ├── convex-helpers.ts
│   │   ├── mock-handlers.ts
│   │   └── test-helpers.ts
│   ├── page-objects/
│   │   ├── BasePage.ts
│   │   ├── ChatComponent.ts
│   │   ├── ChatModeSelector.ts
│   │   ├── ChatPage.ts
│   │   ├── FileAttachment.ts
│   │   ├── HomePage.ts
│   │   ├── SettingsDialog.ts
│   │   ├── SidebarComponent.ts
│   │   ├── UpgradeDialog.ts
│   │   ├── UserMenuComponent.ts
│   │   └── index.ts
│   ├── resource/
│   │   └── secret.txt
│   └── setup/
│       └── auth.setup.ts
├── eslint.config.mjs
├── global.d.ts
├── hooks/
│   ├── use-is-standalone.ts
│   └── use-mobile.ts
├── instrumentation.ts
├── jest.config.js
├── jest.setup.js
├── lib/
│   ├── __tests__/
│   │   ├── desktop-auth.test.ts
│   │   ├── extra-usage.test.ts
│   │   ├── resolve-customer-users.test.ts
│   │   ├── suspensionMessage.test.ts
│   │   ├── suspensions.test.ts
│   │   ├── usage-tracker.test.ts
│   │   └── utils.test.ts
│   ├── actions/
│   │   ├── billing-portal.ts
│   │   └── index.ts
│   ├── ai/
│   │   ├── providers.ts
│   │   └── tools/
│   │       ├── __tests__/
│   │       │   ├── interact-terminal-session.test.ts
│   │       │   ├── run-terminal-cmd.test.ts
│   │       │   └── sandbox-capabilities.test.ts
│   │       ├── file.ts
│   │       ├── get-terminal-files.ts
│   │       ├── index.ts
│   │       ├── interact-terminal-session.ts
│   │       ├── notes.ts
│   │       ├── open-url.ts
│   │       ├── proxy-tool.ts
│   │       ├── run-terminal-cmd.ts
│   │       ├── todo-write.ts
│   │       ├── utils/
│   │       │   ├── __tests__/
│   │       │   │   ├── centrifugo-sandbox.test.ts
│   │       │   │   ├── e2b-pty-adapter.test.ts
│   │       │   │   ├── platform-utils.test.ts
│   │       │   │   ├── proxy-manager.test.ts
│   │       │   │   ├── pty-keys.test.ts
│   │       │   │   ├── pty-session-manager.test.ts
│   │       │   │   ├── sandbox-file-uploader.test.ts
│   │       │   │   └── tauri-sandbox.test.ts
│   │       │   ├── background-process-tracker.ts
│   │       │   ├── caido-proxy.ts
│   │       │   ├── centrifugo-pty-adapter.ts
│   │       │   ├── centrifugo-sandbox.ts
│   │       │   ├── e2b-errors.ts
│   │       │   ├── e2b-pty-adapter.ts
│   │       │   ├── file-accumulator.ts
│   │       │   ├── guardrails.ts
│   │       │   ├── hybrid-sandbox-manager.ts
│   │       │   ├── path-validation.ts
│   │       │   ├── perplexity.ts
│   │       │   ├── pid-discovery.ts
│   │       │   ├── platform-utils.ts
│   │       │   ├── process-termination.ts
│   │       │   ├── proxy-manager.ts
│   │       │   ├── pty-exited-promise.ts
│   │       │   ├── pty-keys.ts
│   │       │   ├── pty-output-formatter.ts
│   │       │   ├── pty-output.ts
│   │       │   ├── pty-session-manager.ts
│   │       │   ├── pty-wait-utils.ts
│   │       │   ├── retry-with-backoff.ts
│   │       │   ├── sandbox-command-options.ts
│   │       │   ├── sandbox-file-uploader.ts
│   │       │   ├── sandbox-health.ts
│   │       │   ├── sandbox-manager.ts
│   │       │   ├── sandbox-tools.ts
│   │       │   ├── sandbox-types.ts
│   │       │   ├── sandbox.ts
│   │       │   ├── terminal-output-saver.ts
│   │       │   └── todo-manager.ts
│   │       └── web-search.ts
│   ├── api/
│   │   ├── __tests__/
│   │   │   ├── agent-long-contracts.test.ts
│   │   │   ├── build-extra-usage-config.test.ts
│   │   │   ├── chat-handler-pty-cleanup.test.ts
│   │   │   ├── chat-logger.test.ts
│   │   │   ├── chat-stream-helpers-fallback.test.ts
│   │   │   └── chat-stream-helpers-notes.test.ts
│   │   ├── agent-stream-runner.ts
│   │   ├── chat-handler.ts
│   │   ├── chat-logger.ts
│   │   ├── chat-stream-helpers.ts
│   │   └── response.ts
│   ├── auth/
│   │   ├── __tests__/
│   │   │   ├── cross-tab-mutex.test.ts
│   │   │   ├── feature-flags.test.ts
│   │   │   ├── shared-token.test.ts
│   │   │   ├── use-auth-from-authkit.test.ts
│   │   │   └── workos-organization-name.test.ts
│   │   ├── auth-redirect-intents.ts
│   │   ├── cross-tab-mutex.ts
│   │   ├── entitlements.ts
│   │   ├── feature-flags.ts
│   │   ├── get-user-id.ts
│   │   ├── shared-token.ts
│   │   ├── use-auth-from-authkit.ts
│   │   └── workos-organization-name.ts
│   ├── billing/
│   │   └── resolve-customer-users.ts
│   ├── centrifugo/
│   │   ├── __tests__/
│   │   │   └── jwt.test.ts
│   │   ├── jwt.ts
│   │   └── types.ts
│   ├── chat/
│   │   ├── __tests__/
│   │   │   ├── agent-long-heartbeat.test.ts
│   │   │   ├── agent-long-tool-input-dedup.test.ts
│   │   │   ├── agent-routing.test.ts
│   │   │   ├── chat-processor.test.ts
│   │   │   ├── doom-loop-detection.test.ts
│   │   │   └── stop-conditions.test.ts
│   │   ├── agent-long-heartbeat.ts
│   │   ├── agent-long-tool-input-dedup.ts
│   │   ├── agent-long-transport.ts
│   │   ├── agent-routing.ts
│   │   ├── auth-disclaimer.ts
│   │   ├── budget-monitor.ts
│   │   ├── chat-processor.ts
│   │   ├── compaction/
│   │   │   ├── __tests__/
│   │   │   │   └── prune-tool-outputs.test.ts
│   │   │   └── prune-tool-outputs.ts
│   │   ├── doom-loop-detection.ts
│   │   ├── stop-conditions.ts
│   │   ├── summarization/
│   │   │   ├── __tests__/
│   │   │   │   └── index.test.ts
│   │   │   ├── constants.ts
│   │   │   ├── helpers.ts
│   │   │   ├── index.ts
│   │   │   └── prompts.ts
│   │   └── tool-abort-utils.ts
│   ├── constants/
│   │   └── s3.ts
│   ├── db/
│   │   ├── __tests__/
│   │   │   ├── convex-value-sanitizer.test.ts
│   │   │   └── message-persistence-diagnostics.test.ts
│   │   ├── actions.ts
│   │   ├── convex-client.ts
│   │   ├── convex-value-sanitizer.ts
│   │   └── message-persistence-diagnostics.ts
│   ├── desktop-auth.ts
│   ├── errors.ts
│   ├── extra-usage.ts
│   ├── logger.ts
│   ├── moderation.ts
│   ├── posthog/
│   │   ├── server.ts
│   │   └── worker.ts
│   ├── pricing/
│   │   └── features.ts
│   ├── rate-limit/
│   │   ├── __tests__/
│   │   │   ├── index.test.ts
│   │   │   ├── refund.test.ts
│   │   │   ├── sliding-window.test.ts
│   │   │   ├── token-bucket.integration.test.ts
│   │   │   └── token-bucket.test.ts
│   │   ├── index.ts
│   │   ├── redis.ts
│   │   ├── refund.ts
│   │   ├── sliding-window.ts
│   │   └── token-bucket.ts
│   ├── suspensionMessage.ts
│   ├── suspensions.ts
│   ├── system-prompt/
│   │   ├── bio.ts
│   │   ├── notes.ts
│   │   ├── personality.ts
│   │   └── resume.ts
│   ├── system-prompt.ts
│   ├── token-utils.ts
│   ├── usage-projection.ts
│   ├── usage-tracker.ts
│   ├── utils/
│   │   ├── __tests__/
│   │   │   ├── client-storage.test.ts
│   │   │   ├── error-utils.test.ts
│   │   │   ├── file-transform-utils.test.ts
│   │   │   ├── message-utils.test.ts
│   │   │   ├── pro-max-notice-cookie.test.ts
│   │   │   ├── sandbox-file-utils.test.ts
│   │   │   ├── stream-writer-utils.test.ts
│   │   │   └── todo-utils.test.ts
│   │   ├── accumulate-ui-chunks.ts
│   │   ├── client-storage.ts
│   │   ├── error-utils.ts
│   │   ├── file-download.ts
│   │   ├── file-token-utils.ts
│   │   ├── file-transform-utils.ts
│   │   ├── file-utils.ts
│   │   ├── logout.ts
│   │   ├── message-processor.ts
│   │   ├── message-utils.ts
│   │   ├── mode-helpers.ts
│   │   ├── parse-rate-limit-warning.ts
│   │   ├── pro-max-notice-cookie.ts
│   │   ├── redis-pubsub.ts
│   │   ├── safe-wait-until.ts
│   │   ├── sandbox-command.ts
│   │   ├── sandbox-file-utils.ts
│   │   ├── scroll-events.ts
│   │   ├── settings-dialog.ts
│   │   ├── shiki.tsx
│   │   ├── sidebar-storage.ts
│   │   ├── sidebar-utils.ts
│   │   ├── stream-cancellation.ts
│   │   ├── stream-writer-utils.ts
│   │   ├── terminal-executor.ts
│   │   ├── todo-block-manager.ts
│   │   └── todo-utils.ts
│   └── utils.ts
├── middleware.ts
├── next.config.ts
├── package.json
├── packages/
│   ├── desktop/
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── scripts/
│   │   │   ├── build.js
│   │   │   └── generate-icons.mjs
│   │   ├── src/
│   │   │   └── index.html
│   │   ├── src-tauri/
│   │   │   ├── Cargo.toml
│   │   │   ├── build.rs
│   │   │   ├── capabilities/
│   │   │   │   └── default.json
│   │   │   ├── entitlements.plist
│   │   │   ├── gen/
│   │   │   │   └── schemas/
│   │   │   │       ├── acl-manifests.json
│   │   │   │       ├── capabilities.json
│   │   │   │       ├── desktop-schema.json
│   │   │   │       └── macOS-schema.json
│   │   │   ├── hackerai.desktop
│   │   │   ├── icons/
│   │   │   │   └── icon.icns
│   │   │   ├── permissions/
│   │   │   │   └── desktop-command-bridge.toml
│   │   │   ├── scripts/
│   │   │   │   └── deb-postinstall.sh
│   │   │   ├── src/
│   │   │   │   ├── lib.rs
│   │   │   │   ├── main.rs
│   │   │   │   ├── platform.rs
│   │   │   │   └── pty.rs
│   │   │   ├── tauri.conf.json
│   │   │   └── tauri.dev.conf.json
│   │   └── tsconfig.json
│   └── local/
│       ├── .gitignore
│       ├── README.md
│       ├── package.json
│       ├── src/
│       │   ├── __tests__/
│       │   │   └── utils.test.ts
│       │   ├── index.ts
│       │   ├── process-runner.ts
│       │   └── utils.ts
│       └── tsconfig.json
├── patches/
│   └── ai@6.0.184.patch
├── playwright.config.ts
├── pnpm-workspace.yaml
├── postcss.config.mjs
├── public/
│   └── manifest.json
├── scripts/
│   ├── README.md
│   ├── accept-invitation.ts
│   ├── attach-failing-card.ts
│   ├── check-openrouter-gen-id.ts
│   ├── create-test-users.ts
│   ├── reset-rate-limit.ts
│   ├── setup.ts
│   ├── test-users-config.ts
│   ├── validate-s3-security.ts
│   ├── verify-email.ts
│   └── verify-test-users.ts
├── skills-lock.json
├── trigger/
│   ├── agent-long.ts
│   ├── stream-ids.ts
│   └── streams.ts
├── trigger.config.ts
├── tsconfig.json
├── types/
│   ├── agent.ts
│   ├── chat.ts
│   ├── file.ts
│   ├── index.ts
│   └── user.ts
└── vercel.json
Download .txt
SYMBOL INDEX (2099 symbols across 416 files)

FILE: __mocks__/@aws-sdk/client-s3.ts
  class PutObjectCommand (line 7) | class PutObjectCommand {
    method constructor (line 8) | constructor(params: any) {}
  class GetObjectCommand (line 11) | class GetObjectCommand {
    method constructor (line 12) | constructor(params: any) {}
  class DeleteObjectCommand (line 15) | class DeleteObjectCommand {
    method constructor (line 16) | constructor(params: any) {}

FILE: __mocks__/@upstash/ratelimit.ts
  class Ratelimit (line 8) | class Ratelimit {
    method constructor (line 9) | constructor(_config: unknown) {}
    method tokenBucket (line 13) | static tokenBucket(_max: number, _interval: string, _refill: number) {
    method slidingWindow (line 17) | static slidingWindow(_max: number, _interval: string) {
    method fixedWindow (line 21) | static fixedWindow(_max: number, _interval: string) {

FILE: __mocks__/@upstash/redis.ts
  class Redis (line 10) | class Redis {

FILE: __mocks__/convex/browser.ts
  class ConvexHttpClient (line 12) | class ConvexHttpClient {
    method constructor (line 13) | constructor(_url: string) {}

FILE: __mocks__/workos-node.ts
  class WorkOS (line 2) | class WorkOS {

FILE: app/(chat)/c/[id]/page.tsx
  function Page (line 11) | function Page(props: { params: Promise<{ id: string }> }) {

FILE: app/(chat)/layout.tsx
  function ChatRouteLayout (line 20) | function ChatRouteLayout({

FILE: app/(chat)/page.tsx
  constant LOGIN_TYPING_PREFIX (line 21) | const LOGIN_TYPING_PREFIX = "Ask HackerAI to ";
  constant LOGIN_TYPING_TAILS (line 22) | const LOGIN_TYPING_TAILS = [
  function Page (line 119) | function Page() {

FILE: app/api/agent-long/cancel/route.ts
  function POST (line 14) | async function POST(req: NextRequest) {

FILE: app/api/agent-long/resume/route.ts
  constant TERMINAL_STATUSES (line 14) | const TERMINAL_STATUSES = new Set([
  function GET (line 30) | async function GET(req: NextRequest) {

FILE: app/api/agent-long/route.ts
  function POST (line 29) | async function POST(req: NextRequest) {

FILE: app/api/agent/route.ts
  constant POST (line 5) | const POST = createChatHandler("/api/agent");

FILE: app/api/auth/desktop-callback/route.ts
  type DesktopAuthSession (line 9) | type DesktopAuthSession = {
  function escapeHtml (line 18) | function escapeHtml(str: string): string {
  function GET (line 27) | async function GET(request: NextRequest) {
  function renderSuccessPage (line 185) | function renderSuccessPage(deepLinkUrl: string): string {
  function renderErrorPage (line 234) | function renderErrorPage(message: string): string {

FILE: app/api/chat/[id]/stream/route.ts
  function GET (line 16) | async function GET(

FILE: app/api/chat/route.ts
  constant POST (line 5) | const POST = createChatHandler("/api/chat");

FILE: app/api/delete-sandboxes/route.ts
  function POST (line 7) | async function POST(req: NextRequest) {

FILE: app/api/entitlements/route.ts
  function GET (line 17) | async function GET(req: NextRequest) {

FILE: app/api/extra-usage/confirm/route.ts
  function GET (line 18) | async function GET(req: NextRequest) {

FILE: app/api/extra-usage/webhook/route.ts
  function POST (line 17) | async function POST(req: NextRequest) {

FILE: app/api/fraud/webhook/route.ts
  type SuspensionCategory (line 10) | type SuspensionCategory =
  function isTerminalStripeError (line 25) | function isTerminalStripeError(err: unknown): boolean {
  function cancelAllSubscriptions (line 38) | async function cancelAllSubscriptions(
  function detachAllPaymentMethods (line 78) | async function detachAllPaymentMethods(
  function markCustomerBlocked (line 113) | async function markCustomerBlocked(
  function reportChargeFraudulent (line 127) | async function reportChargeFraudulent(chargeId: string): Promise<void> {
  function refundChargeForEFW (line 154) | async function refundChargeForEFW(
  function getCustomerIdFromCharge (line 190) | function getCustomerIdFromCharge(charge: Stripe.Charge): string | null {
  function suspendCustomerUsers (line 199) | async function suspendCustomerUsers({
  function blockFraudulentUser (line 244) | async function blockFraudulentUser(
  function handleEarlyFraudWarning (line 286) | async function handleEarlyFraudWarning(
  function handleDisputeCreated (line 340) | async function handleDisputeCreated(dispute: Stripe.Dispute): Promise<vo...
  function POST (line 410) | async function POST(req: NextRequest) {

FILE: app/api/logout-all/route.ts
  function POST (line 5) | async function POST(req: NextRequest) {

FILE: app/api/mfa/delete/route.ts
  type DeleteMfaFactorRequest (line 6) | interface DeleteMfaFactorRequest {
  function POST (line 11) | async function POST(req: NextRequest) {

FILE: app/api/mfa/enroll/route.ts
  function POST (line 6) | async function POST(req: NextRequest) {

FILE: app/api/mfa/factors/route.ts
  function GET (line 6) | async function GET(req: NextRequest) {

FILE: app/api/mfa/verify/route.ts
  type VerifyMfaRequest (line 6) | interface VerifyMfaRequest {
  function POST (line 11) | async function POST(req: NextRequest) {

FILE: app/api/sandbox/presence/route.ts
  type CentrifugoPresenceClient (line 9) | interface CentrifugoPresenceClient {
  type CentrifugoPresenceResult (line 15) | interface CentrifugoPresenceResult {
  function GET (line 19) | async function GET(request: NextRequest) {

FILE: app/api/subscription/webhook/route.ts
  constant TIER_ORDER (line 20) | const TIER_ORDER: readonly SubscriptionTier[] = [
  function tierDirection (line 28) | function tierDirection(
  function planLookupKeyToTier (line 46) | function planLookupKeyToTier(lookupKey: string): SubscriptionTier | null {
  function tierFromProductName (line 62) | function tierFromProductName(name: string): SubscriptionTier | null {
  function resolveSubscription (line 73) | async function resolveSubscription(subscriptionId: string): Promise<{
  function handleInvoicePaid (line 126) | async function handleInvoicePaid(invoice: Stripe.Invoice): Promise<void> {
  function handleSubscriptionUpdated (line 237) | async function handleSubscriptionUpdated(
  function handleSubscriptionDeleted (line 319) | async function handleSubscriptionDeleted(
  function POST (line 368) | async function POST(req: NextRequest) {

FILE: app/api/team/extra-usage/confirm/route.ts
  function GET (line 16) | async function GET(req: NextRequest) {

FILE: app/api/team/extra-usage/webhook/route.ts
  function POST (line 17) | async function POST(req: NextRequest) {

FILE: app/api/team/seats/route.ts
  constant MAX_SEATS (line 7) | const MAX_SEATS = 999;
  function validateQuantity (line 9) | function validateQuantity(
  type WorkOSOrganization (line 30) | type WorkOSOrganization = Awaited<
  type SeatOperationError (line 34) | interface SeatOperationError {
  type SeatOperationSuccess (line 38) | interface SeatOperationSuccess {
  type SeatOperationContext (line 50) | type SeatOperationContext = SeatOperationError | SeatOperationSuccess;
  function getSeatOperationContext (line 53) | async function getSeatOperationContext(
  type SubscriptionWithBillingAnchor (line 249) | type SubscriptionWithBillingAnchor = Stripe.Subscription & {
  type InvoiceWithPaymentIntent (line 404) | type InvoiceWithPaymentIntent = Stripe.Invoice & {

FILE: app/api/team/team-auth.ts
  type Membership (line 5) | type Membership = Awaited<
  function requireTeamOrg (line 17) | async function requireTeamOrg(
  function requireAdminOrg (line 80) | async function requireAdminOrg(

FILE: app/auth-error/auto-retry-button.tsx
  constant STORAGE_KEY (line 7) | const STORAGE_KEY = "auth_retry_state";
  constant BASE_DELAY (line 8) | const BASE_DELAY = 5;
  constant MAX_DELAY (line 9) | const MAX_DELAY = 600;
  constant EXPIRY_HOURS (line 10) | const EXPIRY_HOURS = 4;
  type RetryState (line 12) | interface RetryState {
  function getRetryState (line 17) | function getRetryState(): RetryState {
  function setRetryState (line 33) | function setRetryState(count: number): void {
  function calculateDelay (line 46) | function calculateDelay(retryCount: number): number {
  type AutoRetryButtonProps (line 52) | interface AutoRetryButtonProps {
  function AutoRetryButton (line 56) | function AutoRetryButton({ loginUrl }: AutoRetryButtonProps) {

FILE: app/auth-error/page.tsx
  type ErrorCode (line 14) | type ErrorCode = "429" | "401" | "403" | "500" | "502" | "503" | "504";
  constant ERROR_MESSAGES (line 16) | const ERROR_MESSAGES: Record<
  constant DEFAULT_ERROR (line 58) | const DEFAULT_ERROR = {
  type SearchParams (line 63) | type SearchParams = Promise<{ code?: string }>;
  function AuthErrorPage (line 65) | async function AuthErrorPage({

FILE: app/callback/route.ts
  constant PKCE_COOKIE_PREFIX (line 11) | const PKCE_COOKIE_PREFIX = "wos-auth-verifier";
  type RecoveryBucket (line 16) | type RecoveryBucket =
  function GET (line 110) | async function GET(request: NextRequest) {

FILE: app/components/AllFilesDialog.tsx
  type AllFilesDialogProps (line 15) | interface AllFilesDialogProps {
  type FileItemProps (line 26) | interface FileItemProps {
  function fetchAllUrls (line 171) | async function fetchAllUrls() {

FILE: app/components/AttachmentButton.tsx
  type AttachmentButtonProps (line 14) | interface AttachmentButtonProps {

FILE: app/components/BillingFrequencySelector.tsx
  type BillingFrequency (line 5) | type BillingFrequency = "monthly" | "yearly";
  type BillingFrequencySelectorProps (line 7) | interface BillingFrequencySelectorProps {

FILE: app/components/BranchIndicator.tsx
  type BranchIndicatorProps (line 8) | interface BranchIndicatorProps {

FILE: app/components/CancelSubscriptionDialog.tsx
  type CancelSubscriptionDialogProps (line 25) | type CancelSubscriptionDialogProps = {
  function getFeaturesForTier (line 30) | function getFeaturesForTier(tier: SubscriptionTier) {
  function getPlanDisplayName (line 47) | function getPlanDisplayName(tier: SubscriptionTier) {

FILE: app/components/ChatHeader.tsx
  type ChatHeaderProps (line 27) | interface ChatHeaderProps {

FILE: app/components/ChatInput/AgentUpgradeDialog.tsx
  type AgentUpgradeDialogProps (line 14) | interface AgentUpgradeDialogProps {
  function AgentUpgradeDialog (line 19) | function AgentUpgradeDialog({

FILE: app/components/ChatInput/ChatInput.tsx
  type ChatInputProps (line 25) | interface ChatInputProps {

FILE: app/components/ChatInput/ChatInputTextarea.tsx
  type ChatInputTextareaProps (line 19) | interface ChatInputTextareaProps {
  function ChatInputTextarea (line 29) | function ChatInputTextarea({

FILE: app/components/ChatInput/ChatInputToolbar.tsx
  type ChatInputToolbarProps (line 16) | interface ChatInputToolbarProps extends SubmitStopButtonProps {
  function ChatInputToolbar (line 23) | function ChatInputToolbar({

FILE: app/components/ChatInput/ChatModeSelector.tsx
  type ChatModeSelectorProps (line 12) | interface ChatModeSelectorProps {
  function ChatModeSelector (line 16) | function ChatModeSelector({ className }: ChatModeSelectorProps) {

FILE: app/components/ChatInput/ModeSelectorMenu/ModeOptionItem.tsx
  type ModeOptionItemProps (line 6) | interface ModeOptionItemProps {
  function ModeOptionItem (line 16) | function ModeOptionItem({

FILE: app/components/ChatInput/ModeSelectorMenu/ModeSelectorContent.tsx
  type ModeSelectorContentProps (line 8) | interface ModeSelectorContentProps {
  function ModeSelectorContent (line 14) | function ModeSelectorContent({

FILE: app/components/ChatInput/ModeSelectorMenu/ModeSelectorTrigger.tsx
  constant MODE_VARIANT_CLASSES (line 8) | const MODE_VARIANT_CLASSES: Record<ChatMode, string> = {
  type ModeSelectorTriggerProps (line 17) | interface ModeSelectorTriggerProps {
  function ModeSelectorTrigger (line 21) | function ModeSelectorTrigger({ chatMode }: ModeSelectorTriggerProps) {

FILE: app/components/ChatInput/SubmitStopButton.tsx
  constant BASE_BUTTON_CLASSES (line 12) | const BASE_BUTTON_CLASSES = "rounded-full p-0 w-8 h-8 min-w-0";
  constant STOP_BUTTON_VARIANT_CLASSES (line 14) | const STOP_BUTTON_VARIANT_CLASSES: Record<ChatMode, string> = {
  function getStopButtonVariantClasses (line 20) | function getStopButtonVariantClasses(mode: ChatMode): string {
  function getSubmitButtonVariantClasses (line 24) | function getSubmitButtonVariantClasses(mode: ChatMode): string {
  function getSendButtonTooltip (line 31) | function getSendButtonTooltip(
  type SubmitStopButtonProps (line 40) | interface SubmitStopButtonProps {
  function SubmitStopButton (line 52) | function SubmitStopButton({

FILE: app/components/ChatItem.tsx
  type ChatItemProps (line 58) | interface ChatItemProps {

FILE: app/components/ChatLayout.tsx
  function ChatLayout (line 17) | function ChatLayout({ children }: { children: React.ReactNode }) {

FILE: app/components/CodeHighlight.tsx
  type CodeHighlightProps (line 7) | interface CodeHighlightProps {

FILE: app/components/ComputerCodeBlock.tsx
  type ComputerCodeBlockProps (line 13) | interface ComputerCodeBlockProps {

FILE: app/components/ComputerSidebar.tsx
  type ComputerSidebarProps (line 38) | interface ComputerSidebarProps {

FILE: app/components/ContextUsageIndicator.tsx
  type ContextUsageData (line 16) | interface ContextUsageData {
  type ContextUsageIndicatorProps (line 21) | interface ContextUsageIndicatorProps extends ContextUsageData {
  function formatTokenCount (line 25) | function formatTokenCount(n: number): string {
  function formatExactTokenCount (line 38) | function formatExactTokenCount(n: number): string {
  constant AUTO_COMPACT_PERCENT (line 42) | const AUTO_COMPACT_PERCENT = Math.round(
  constant CIRCLE_SIZE (line 46) | const CIRCLE_SIZE = 16;
  constant STROKE_WIDTH (line 47) | const STROKE_WIDTH = 2.5;
  constant RADIUS (line 48) | const RADIUS = (CIRCLE_SIZE - STROKE_WIDTH) / 2;
  constant CIRCUMFERENCE (line 49) | const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
  function ContextUsageCircle (line 51) | function ContextUsageCircle({ dashOffset }: { dashOffset: number }) {

FILE: app/components/ConvexErrorBoundary.tsx
  type Props (line 7) | interface Props {
  type State (line 12) | interface State {
  class ConvexErrorBoundary (line 17) | class ConvexErrorBoundary extends Component<Props, State> {
    method constructor (line 18) | constructor(props: Props) {
    method getDerivedStateFromError (line 23) | static getDerivedStateFromError(error: Error): State {
    method componentDidCatch (line 27) | componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    method render (line 64) | render() {

FILE: app/components/CustomizeHackerAIDialog.tsx
  type CustomizeHackerAIDialogProps (line 28) | interface CustomizeHackerAIDialogProps {

FILE: app/components/DataStreamProvider.tsx
  type DataStreamStateValue (line 13) | interface DataStreamStateValue {
  type DataStreamDispatchValue (line 20) | interface DataStreamDispatchValue {
  function DataStreamProvider (line 31) | function DataStreamProvider({
  function useDataStreamState (line 62) | function useDataStreamState() {
  function useDataStreamDispatch (line 74) | function useDataStreamDispatch() {
  function useDataStream (line 85) | function useDataStream() {

FILE: app/components/DeleteAccountDialog.tsx
  type DeleteAccountDialogProps (line 21) | type DeleteAccountDialogProps = {

FILE: app/components/DeleteMfaFactorDialog.tsx
  type DeleteMfaFactorDialogProps (line 15) | interface DeleteMfaFactorDialogProps {

FILE: app/components/DiffView.tsx
  type ViewMode (line 7) | type ViewMode = "diff" | "original" | "modified";
  type DiffViewProps (line 9) | interface DiffViewProps {

FILE: app/components/DragDropOverlay.tsx
  type DragDropOverlayProps (line 5) | interface DragDropOverlayProps {

FILE: app/components/FeedbackInput.tsx
  type FeedbackInputProps (line 5) | interface FeedbackInputProps {

FILE: app/components/FilePartRenderer.tsx
  function fetchUrl (line 84) | async function fetchUrl() {

FILE: app/components/FinishReasonNotice.tsx
  type FinishReasonNoticeProps (line 7) | interface FinishReasonNoticeProps {

FILE: app/components/HackingSuggestions.tsx
  constant HACKING_QUESTIONS (line 6) | const HACKING_QUESTIONS = [

FILE: app/components/Header.tsx
  type HeaderProps (line 11) | interface HeaderProps {

FILE: app/components/ImageViewer.tsx
  type ImageViewerProps (line 4) | interface ImageViewerProps {

FILE: app/components/ManageNotesDialog.tsx
  type ManageNotesDialogProps (line 17) | interface ManageNotesDialogProps {

FILE: app/components/ManageSharedChatsDialog.tsx
  type ManageSharedChatsDialogProps (line 9) | interface ManageSharedChatsDialogProps {

FILE: app/components/MarkdownTable.tsx
  function extractTableData (line 6) | function extractTableData(tableEl: HTMLTableElement): string[][] {
  function toCSV (line 18) | function toCSV(data: string[][]): string {
  type MarkdownTableProps (line 33) | interface MarkdownTableProps {
  function MarkdownTable (line 39) | function MarkdownTable({

FILE: app/components/MemoizedMarkdown.tsx
  function isLocalFilePath (line 13) | function isLocalFilePath(href: string | undefined): boolean {
  type MemoizedMarkdownProps (line 18) | interface MemoizedMarkdownProps {
  method a (line 28) | a({ children, href }) {

FILE: app/components/MessageActions.tsx
  type MessageActionsProps (line 17) | interface MessageActionsProps {

FILE: app/components/MessageEditor.tsx
  type EditableFile (line 13) | interface EditableFile {
  type MessageEditorProps (line 20) | interface MessageEditorProps {

FILE: app/components/MessageErrorState.tsx
  type MessageErrorStateProps (line 9) | interface MessageErrorStateProps {

FILE: app/components/MessageItem.tsx
  type MessageItemProps (line 26) | interface MessageItemProps {
  function areMessageItemPropsEqual (line 65) | function areMessageItemPropsEqual(

FILE: app/components/MessagePartHandler.tsx
  type MessagePartHandlerProps (line 18) | interface MessagePartHandlerProps {
  function deepEqual (line 37) | function deepEqual(a: any, b: any): boolean {
  function arePropsEqual (line 65) | function arePropsEqual(

FILE: app/components/MessageSearchDialog.tsx
  type MessageSearchResult (line 29) | interface MessageSearchResult {
  type MessageSearchDialogProps (line 39) | interface MessageSearchDialogProps {
  type DateCategory (line 44) | type DateCategory =

FILE: app/components/Messages.tsx
  type MessagesProps (line 27) | interface MessagesProps {

FILE: app/components/MfaVerificationDialog.tsx
  type EnrollmentData (line 16) | interface EnrollmentData {
  type MfaVerificationDialogProps (line 30) | interface MfaVerificationDialogProps {

FILE: app/components/MigratePentestgptDialog.tsx
  type MigratePentestgptDialogProps (line 15) | type MigratePentestgptDialogProps = {

FILE: app/components/ModelSelector.tsx
  type ModelSelectorProps (line 53) | interface ModelSelectorProps {
  function ModelSelector (line 333) | function ModelSelector({ value, onChange, mode }: ModelSelectorProps) {

FILE: app/components/ModelSelector/CostIndicator.tsx
  type CostTier (line 9) | type CostTier = "low" | "medium" | "high" | "very-high";
  function getCostTier (line 14) | function getCostTier(modelId: string, mode?: ChatMode): CostTier {
  constant COST_CONFIG (line 27) | const COST_CONFIG: Record<
  constant MAX_DOLLARS (line 54) | const MAX_DOLLARS = 3;
  function CostIndicator (line 56) | function CostIndicator({

FILE: app/components/ModelSelector/constants.ts
  type ModelOption (line 4) | interface ModelOption {
  constant ASK_MODEL_OPTIONS (line 14) | const ASK_MODEL_OPTIONS: ModelOption[] = [
  constant AGENT_MODEL_OPTIONS (line 36) | const AGENT_MODEL_OPTIONS: ModelOption[] = [

FILE: app/components/PersonalizationTab.tsx
  type PersonalizationTabProps (line 12) | interface PersonalizationTabProps {

FILE: app/components/PricingDialog.tsx
  type PricingDialogProps (line 25) | interface PricingDialogProps {
  type PlanCardProps (line 30) | interface PlanCardProps {
  type PremiumPlan (line 154) | type PremiumPlan = "pro-plus" | "ultra";
  type PremiumPlanSelectorProps (line 156) | interface PremiumPlanSelectorProps {

FILE: app/components/QueuedMessagesPanel.tsx
  type QueuedMessagesPanelProps (line 19) | interface QueuedMessagesPanelProps {

FILE: app/components/RateLimitWarning.tsx
  type RateLimitWarningData (line 8) | type RateLimitWarningData =
  type RateLimitWarningProps (line 36) | interface RateLimitWarningProps {
  constant WARNING_STYLES (line 100) | const WARNING_STYLES = "bg-input-chat border-black/8 dark:border-border";

FILE: app/components/ReasoningHandler.tsx
  type ReasoningHandlerProps (line 13) | type ReasoningHandlerProps = {
  constant REDACTED_PATTERN (line 37) | const REDACTED_PATTERN = /^(\[REDACTED\])+$/;
  function areReasoningPropsEqual (line 40) | function areReasoningPropsEqual(

FILE: app/components/RemoteControlTab.tsx
  type LocalConnection (line 29) | interface LocalConnection {
  type CommandBlockProps (line 42) | interface CommandBlockProps {
  type UseAutoSelectNewRemoteConnectionArgs (line 89) | interface UseAutoSelectNewRemoteConnectionArgs {
  function useAutoSelectNewRemoteConnection (line 101) | function useAutoSelectNewRemoteConnection({

FILE: app/components/SandboxSelector.tsx
  type SandboxSelectorProps (line 27) | interface SandboxSelectorProps {
  type ConnectionOption (line 34) | interface ConnectionOption {
  function SandboxSelector (line 41) | function SandboxSelector({

FILE: app/components/ScrollToBottomButton.tsx
  type ScrollToBottomButtonProps (line 4) | interface ScrollToBottomButtonProps {

FILE: app/components/SecurityTab.tsx
  type MfaFactor (line 10) | interface MfaFactor {
  type EnrollmentData (line 18) | interface EnrollmentData {

FILE: app/components/SettingsDialog.tsx
  type SettingsDialogProps (line 32) | interface SettingsDialogProps {

FILE: app/components/ShareDialog.tsx
  type ShareDialogProps (line 21) | interface ShareDialogProps {

FILE: app/components/Sidebar.tsx
  type ChatListData (line 22) | type ChatListData = ReturnType<typeof useChats>;

FILE: app/components/SidebarHeader.tsx
  type SidebarHeaderContentProps (line 19) | interface SidebarHeaderContentProps {
  type SidebarHeaderContentImplProps (line 29) | interface SidebarHeaderContentImplProps {

FILE: app/components/SidebarHistory.tsx
  type SidebarHistoryProps (line 8) | interface SidebarHistoryProps {

FILE: app/components/SidebarUserNav.tsx
  constant NEXT_PUBLIC_HELP_CENTER_URL (line 44) | const NEXT_PUBLIC_HELP_CENTER_URL =

FILE: app/components/SourcesDialog.tsx
  type Source (line 4) | interface Source {
  type SourcesDialogProps (line 11) | interface SourcesDialogProps {

FILE: app/components/TeamDialogs.tsx
  type TeamMember (line 16) | interface TeamMember {
  type PendingInvitation (line 27) | interface PendingInvitation {
  type TeamDialogsProps (line 35) | interface TeamDialogsProps {
  type SeatPreview (line 327) | interface SeatPreview {

FILE: app/components/TeamExtraUsageSection.tsx
  type Member (line 13) | type Member = {
  type Pool (line 24) | type Pool = {

FILE: app/components/TeamMembersList.tsx
  type TeamMember (line 7) | interface TeamMember {
  type PendingInvitation (line 18) | interface PendingInvitation {
  type TeamMembersListProps (line 26) | interface TeamMembersListProps {

FILE: app/components/TeamPricingDialog.tsx
  type TeamPricingDialogProps (line 18) | interface TeamPricingDialogProps {

FILE: app/components/TeamTab.tsx
  type TeamMember (line 26) | interface TeamMember {
  type PendingInvitation (line 37) | interface PendingInvitation {
  type TeamInfo (line 45) | interface TeamInfo {

FILE: app/components/TerminalCodeBlock.tsx
  type TerminalCodeBlockProps (line 15) | interface TerminalCodeBlockProps {
  type AnsiCodeBlockProps (line 27) | interface AnsiCodeBlockProps {
  constant MAX_CACHE_SIZE (line 39) | const MAX_CACHE_SIZE = 100;

FILE: app/components/TodoPanel.tsx
  type TodoPanelProps (line 13) | interface TodoPanelProps {

FILE: app/components/UpgradeConfirmationDialog.tsx
  type UpgradeConfirmationDialogProps (line 9) | interface UpgradeConfirmationDialogProps {
  type SubscriptionDetails (line 25) | interface SubscriptionDetails {

FILE: app/components/XtermRenderer.tsx
  type XtermRendererProps (line 8) | interface XtermRendererProps {
  function XtermRenderer (line 14) | function XtermRenderer({

FILE: app/components/__tests__/ContextUsageIndicator.test.tsx
  method observe (line 11) | observe() {}
  method unobserve (line 12) | unobserve() {}
  method disconnect (line 13) | disconnect() {}

FILE: app/components/__tests__/FinishReasonNotice.test.tsx
  function DataStreamSetter (line 10) | function DataStreamSetter({
  type RenderNoticeProps (line 35) | interface RenderNoticeProps {
  function renderNotice (line 41) | function renderNotice(

FILE: app/components/__tests__/RemoteControlTab.test.tsx
  type MockConnection (line 5) | type MockConnection = {

FILE: app/components/chat.tsx
  type StreamingEphemeralState (line 60) | interface StreamingEphemeralState {
  type StreamingAction (line 70) | type StreamingAction =
  function streamingReducer (line 93) | function streamingReducer(
  function StreamEffects (line 125) | function StreamEffects({

FILE: app/components/computer-sidebar-utils.tsx
  function getCategoryColor (line 35) | function getCategoryColor(category: NoteCategory): string {
  constant LANGUAGE_MAP (line 50) | const LANGUAGE_MAP: Record<string, string> = {
  function getLanguageFromPath (line 89) | function getLanguageFromPath(filePath: string): string {
  function getActionText (line 98) | function getActionText(content: SidebarContent): string {
  function getSidebarIcon (line 179) | function getSidebarIcon(content: SidebarContent): React.ReactNode {
  function getToolName (line 194) | function getToolName(content: SidebarContent): string {
  function getDisplayTarget (line 210) | function getDisplayTarget(content: SidebarContent): string {

FILE: app/components/extra-usage/AdjustSpendingLimitDialog.tsx
  type AdjustSpendingLimitDialogProps (line 13) | type AdjustSpendingLimitDialogProps = {
  type ContentProps (line 21) | type ContentProps = Omit<

FILE: app/components/extra-usage/AutoReloadDialog.tsx
  type AutoReloadDialogProps (line 16) | type AutoReloadDialogProps = {
  type ContentProps (line 28) | type ContentProps = Omit<AutoReloadDialogProps, "open" | "onOpenChange">;

FILE: app/components/extra-usage/BuyExtraUsageDialog.tsx
  type BuyExtraUsageDialogProps (line 17) | type BuyExtraUsageDialogProps = {
  constant MAX_AMOUNT (line 34) | const MAX_AMOUNT = 999_999;
  type ContentProps (line 47) | type ContentProps = {

FILE: app/components/extra-usage/ExtraUsagePurchaseToast.tsx
  function ExtraUsagePurchaseToast (line 17) | function ExtraUsagePurchaseToast() {

FILE: app/components/extra-usage/TurnOffExtraUsageDialog.tsx
  type TurnOffExtraUsageDialogProps (line 13) | type TurnOffExtraUsageDialogProps = {

FILE: app/components/tools/FileHandler.tsx
  type FileInput (line 10) | interface FileInput {
  type FileHandlerProps (line 19) | interface FileHandlerProps {
  function areFilePropsEqual (line 25) | function areFilePropsEqual(

FILE: app/components/tools/FileToolsHandler.tsx
  type DiffDataPart (line 23) | interface DiffDataPart {
  type FileToolsHandlerProps (line 51) | interface FileToolsHandlerProps {

FILE: app/components/tools/GetTerminalFilesHandler.tsx
  type TerminalFilesPart (line 13) | interface TerminalFilesPart {
  type GetTerminalFilesHandlerProps (line 30) | interface GetTerminalFilesHandlerProps {

FILE: app/components/tools/HttpRequestToolHandler.tsx
  type HttpRequestToolHandlerProps (line 10) | interface HttpRequestToolHandlerProps {

FILE: app/components/tools/NotesToolHandler.tsx
  type NotesToolHandlerProps (line 15) | interface NotesToolHandlerProps {

FILE: app/components/tools/ProxyToolHandler.tsx
  type ProxyToolHandlerProps (line 9) | interface ProxyToolHandlerProps {
  constant PROXY_ACTION_LABELS (line 16) | const PROXY_ACTION_LABELS: Record<string, string> = {
  constant PROXY_COMPLETED_LABELS (line 25) | const PROXY_COMPLETED_LABELS: Record<string, string> = {
  function padRight (line 38) | function padRight(str: string, len: number): string {
  function formatListRequests (line 44) | function formatListRequests(r: any): string {
  function formatViewRequest (line 73) | function formatViewRequest(r: any): string {
  function formatSendRequest (line 91) | function formatSendRequest(r: any): string {
  function formatScopeRules (line 116) | function formatScopeRules(r: any): string {
  function formatListSitemap (line 140) | function formatListSitemap(r: any): string {
  function formatViewSitemapEntry (line 164) | function formatViewSitemapEntry(r: any): string {
  function formatProxyOutput (line 199) | function formatProxyOutput(toolName: string, result: any): string {

FILE: app/components/tools/SummarizationHandler.tsx
  type SummarizationHandlerProps (line 6) | interface SummarizationHandlerProps {
  function areSummarizationPropsEqual (line 13) | function areSummarizationPropsEqual(

FILE: app/components/tools/TerminalToolHandler.tsx
  type TerminalToolHandlerProps (line 17) | interface TerminalToolHandlerProps {
  function areTerminalPropsEqual (line 26) | function areTerminalPropsEqual(

FILE: app/components/tools/TodoToolHandler.tsx
  type TodoToolHandlerProps (line 9) | interface TodoToolHandlerProps {
  function areTodoPropsEqual (line 16) | function areTodoPropsEqual(

FILE: app/components/tools/WebToolHandler.tsx
  type WebSearchInput (line 9) | interface WebSearchInput {
  type OpenUrlInput (line 14) | interface OpenUrlInput {
  type LegacyWebInput (line 20) | interface LegacyWebInput {
  type WebToolHandlerProps (line 27) | interface WebToolHandlerProps {
  function areWebPropsEqual (line 41) | function areWebPropsEqual(

FILE: app/components/tools/notes-tool-utils.tsx
  type NotesToolName (line 3) | type NotesToolName =
  type NotesActionType (line 9) | type NotesActionType = "create" | "list" | "update" | "delete";

FILE: app/components/tools/shell-tool-utils.ts
  type ShellAction (line 14) | type ShellAction = "exec" | "view" | "wait" | "send" | "kill";
  type ShellToolInput (line 16) | interface ShellToolInput {
  type ShellToolOutput (line 25) | interface ShellToolOutput {
  function isInteractiveShellAction (line 45) | function isInteractiveShellAction(action?: string): boolean {
  constant LABELS (line 58) | const LABELS: Record<ShellAction, [active: string, done: string]> = {
  function getShellActionLabel (line 66) | function getShellActionLabel(opts: {
  function getShellDisplayCommand (line 107) | function getShellDisplayCommand(
  function formatSendInput (line 126) | function formatSendInput(raw: string): string {
  function getShellDisplayTarget (line 194) | function getShellDisplayTarget(
  type DataTerminalPart (line 211) | interface DataTerminalPart {
  function isDataTerminalPart (line 216) | function isDataTerminalPart(part: unknown): part is DataTerminalPart {
  function getStreamingTerminalOutput (line 224) | function getStreamingTerminalOutput(
  function getShellOutput (line 242) | function getShellOutput(
  type ComputeShellBlockArgs (line 273) | interface ComputeShellBlockArgs {
  type ShellBlockComputed (line 291) | interface ShellBlockComputed {
  function computeShellTerminalBlock (line 300) | function computeShellTerminalBlock(

FILE: app/components/usage/IncludedUsageCard.tsx
  type UsageLimitStatus (line 15) | type UsageLimitStatus = {
  type TokenUsageStatus (line 23) | type TokenUsageStatus = {
  constant POINTS_PER_DOLLAR (line 28) | const POINTS_PER_DOLLAR = 10_000;
  type IncludedUsageCardProps (line 68) | interface IncludedUsageCardProps {

FILE: app/components/usage/OnDemandUsageCard.tsx
  type OnDemandUsageCardProps (line 9) | interface OnDemandUsageCardProps {

FILE: app/components/usage/TokenBreakdownTooltip.tsx
  type TokenBreakdownTooltipProps (line 9) | interface TokenBreakdownTooltipProps {

FILE: app/components/usage/UsageLogsTable.tsx
  type Preset (line 17) | type Preset = "1d" | "7d" | "30d" | "custom";
  constant PRESET_OPTIONS (line 19) | const PRESET_OPTIONS: { value: Exclude<Preset, "custom">; label: string ...
  constant INITIAL_NUM_ITEMS (line 25) | const INITIAL_NUM_ITEMS = 100;

FILE: app/components/worked-for-parts.ts
  type MessagePart (line 4) | type MessagePart = ChatMessage["parts"][number];
  constant TRAILING_METADATA_PART_TYPES (line 6) | const TRAILING_METADATA_PART_TYPES = new Set([
  type WorkedForParts (line 21) | type WorkedForParts = {
  function splitWorkedForParts (line 33) | function splitWorkedForParts(

FILE: app/contexts/FileUrlCacheContext.tsx
  type FileUrlCacheContextValue (line 3) | interface FileUrlCacheContextValue {
  function FileUrlCacheProvider (line 12) | function FileUrlCacheProvider({
  function useFileUrlCacheContext (line 35) | function useFileUrlCacheContext() {

FILE: app/contexts/GlobalState.tsx
  type GlobalStateType (line 51) | interface GlobalStateType {
  type GlobalStateProviderProps (line 166) | interface GlobalStateProviderProps {

FILE: app/contexts/TodoBlockContext.tsx
  type TodoBlockContextType (line 6) | interface TodoBlockContextType {
  type TodoBlockProviderProps (line 16) | interface TodoBlockProviderProps {

FILE: app/desktop-callback/route.ts
  function getCookieMaxAge (line 4) | function getCookieMaxAge(): number {
  function escapeHtml (line 15) | function escapeHtml(str: string): string {
  function isValidLocalPath (line 24) | function isValidLocalPath(path: string | undefined): path is string {
  function renderErrorPage (line 33) | function renderErrorPage(
  function GET (line 110) | async function GET(request: Request) {

FILE: app/desktop-login/route.ts
  function GET (line 6) | async function GET(request: Request) {

FILE: app/download/DownloadPageContent.tsx
  function AuthenticatedHeader (line 13) | function AuthenticatedHeader() {
  function DownloadContent (line 39) | function DownloadContent() {
  function DownloadPageContent (line 110) | function DownloadPageContent() {
  function DownloadCard (line 125) | function DownloadCard({

FILE: app/download/DownloadSection.tsx
  type Platform (line 16) | type Platform = "macos" | "windows" | "linux" | "ios" | "android" | "unk...
  type LinuxArch (line 17) | type LinuxArch = "x64" | "arm64";
  type DetectedPlatform (line 19) | interface DetectedPlatform {
  function detectPlatform (line 26) | function detectPlatform(): DetectedPlatform {
  function getClientSnapshot (line 107) | function getClientSnapshot(): DetectedPlatform {
  function getServerSnapshot (line 114) | function getServerSnapshot(): DetectedPlatform | null {
  function subscribe (line 118) | function subscribe() {
  function useDetectedPlatform (line 122) | function useDetectedPlatform(): DetectedPlatform | null {
  function DownloadSection (line 126) | function DownloadSection() {
  type BeforeInstallPromptEvent (line 163) | type BeforeInstallPromptEvent = Event & {
  function MobileInstallCard (line 168) | function MobileInstallCard({ detected }: { detected: DetectedPlatform }) {
  function InstallInstructions (line 266) | function InstallInstructions({ platform }: { platform: Platform }) {
  function StepsList (line 305) | function StepsList({ steps }: { steps: React.ReactNode[] }) {
  function PlatformIcon (line 320) | function PlatformIcon({ platform }: { platform: Platform }) {
  function MobilePlatformIcon (line 335) | function MobilePlatformIcon({ platform }: { platform: Platform }) {

FILE: app/download/constants.ts
  constant GITHUB_RELEASE_BASE (line 1) | const GITHUB_RELEASE_BASE =

FILE: app/download/icons/AndroidIcon.tsx
  function AndroidIcon (line 1) | function AndroidIcon({ className }: { className?: string }) {

FILE: app/download/icons/AppleIcon.tsx
  function AppleIcon (line 1) | function AppleIcon({ className }: { className?: string }) {

FILE: app/download/icons/DeviceIcon.tsx
  function DeviceIcon (line 1) | function DeviceIcon({ className }: { className?: string }) {

FILE: app/download/icons/DownloadIcon.tsx
  function DownloadIcon (line 1) | function DownloadIcon({ className }: { className?: string }) {

FILE: app/download/icons/LinuxIcon.tsx
  function LinuxIcon (line 1) | function LinuxIcon({ className }: { className?: string }) {

FILE: app/download/icons/WindowsIcon.tsx
  function WindowsIcon (line 1) | function WindowsIcon({ className }: { className?: string }) {

FILE: app/download/page.tsx
  function DownloadPage (line 22) | function DownloadPage() {

FILE: app/hooks/__tests__/useAutoContinue.test.ts
  type DataStreamEntry (line 11) | type DataStreamEntry = { type: string; data?: unknown };
  function useTestHarness (line 13) | function useTestHarness(params: UseAutoContinueParams) {
  function createWrapper (line 19) | function createWrapper() {
  function buildParams (line 25) | function buildParams(
  function pushAutoContinue (line 41) | function pushAutoContinue(

FILE: app/hooks/__tests__/useToolSidebar.test.tsx
  function ToolSidebarHarness (line 39) | function ToolSidebarHarness() {

FILE: app/hooks/useAutoContinue.ts
  constant MAX_AUTO_CONTINUES (line 11) | const MAX_AUTO_CONTINUES = 5;
  type UseAutoContinueParams (line 13) | interface UseAutoContinueParams {
  function useAutoContinue (line 27) | function useAutoContinue({

FILE: app/hooks/useAutoResume.ts
  type UseAutoResumeParams (line 11) | interface UseAutoResumeParams {
  function useAutoResume (line 23) | function useAutoResume({

FILE: app/hooks/useChatHandlers.ts
  type UseChatHandlersProps (line 29) | interface UseChatHandlersProps {

FILE: app/hooks/useDocumentDragAndDrop.ts
  type DragHandler (line 3) | type DragHandler = (e: DragEvent) => void;

FILE: app/hooks/useFeedback.ts
  type UseFeedbackProps (line 8) | interface UseFeedbackProps {

FILE: app/hooks/useFileUpload.ts
  constant RATE_LIMIT_WARNING_THRESHOLD (line 32) | const RATE_LIMIT_WARNING_THRESHOLD = 10;

FILE: app/hooks/useFileUrlCache.ts
  type CachedUrl (line 8) | interface CachedUrl {
  constant URL_CACHE_EXPIRATION (line 13) | const URL_CACHE_EXPIRATION = 50 * 60 * 1000;
  constant MAX_BATCH_SIZE (line 14) | const MAX_BATCH_SIZE = 50;
  function useFileUrlCache (line 25) | function useFileUrlCache(messages: ChatMessage[]) {

FILE: app/hooks/usePentestgptMigration.ts
  type UsePentestgptMigration (line 7) | type UsePentestgptMigration = {

FILE: app/hooks/useSandboxPreference.ts
  type SandboxPreferenceState (line 10) | interface SandboxPreferenceState {
  function useSandboxPreference (line 20) | function useSandboxPreference(

FILE: app/hooks/useSidebarNavigation.ts
  type UseSidebarNavigationProps (line 15) | interface UseSidebarNavigationProps {

FILE: app/hooks/useTauri.ts
  type Window (line 7) | interface Window {
  function detectTauri (line 12) | function detectTauri(): boolean {
  function isTauriEnvironment (line 18) | function isTauriEnvironment(): boolean {
  function useTauri (line 22) | function useTauri(): { isTauri: boolean } {
  function openInBrowser (line 27) | async function openInBrowser(url: string): Promise<boolean> {
  type AuthFallbackPath (line 42) | type AuthFallbackPath =
  type NavigateToAuthOptions (line 48) | type NavigateToAuthOptions = {
  function resolveAuthPath (line 52) | function resolveAuthPath(
  function navigateToAuth (line 69) | async function navigateToAuth(
  function getCmdServerInfo (line 115) | async function getCmdServerInfo(): Promise<{
  type LocalFileMetadata (line 138) | type LocalFileMetadata = {
  type LocalFileData (line 146) | type LocalFileData = LocalFileMetadata & {
  function pickLocalFiles (line 150) | async function pickLocalFiles(): Promise<string[]> {
  function getLocalFileMetadata (line 168) | async function getLocalFileMetadata(
  function readLocalFile (line 185) | async function readLocalFile(
  function revealFileInDir (line 205) | async function revealFileInDir(path: string): Promise<boolean> {
  function saveFileToLocal (line 226) | async function saveFileToLocal(
  function openDownloadsFolder (line 286) | async function openDownloadsFolder(): Promise<boolean> {

FILE: app/hooks/useToolSidebar.ts
  type UseToolSidebarOptions (line 5) | interface UseToolSidebarOptions {
  type UseToolSidebarResult (line 19) | interface UseToolSidebarResult {
  function useToolSidebar (line 34) | function useToolSidebar({

FILE: app/hooks/useTypingAnimation.ts
  type UseTypingAnimationOptions (line 5) | interface UseTypingAnimationOptions {
  constant PAUSE_AFTER_CHAR (line 19) | const PAUSE_AFTER_CHAR: Record<string, number> = {
  function jitter (line 29) | function jitter(ms: number, variance: number): number {
  function subscribeReducedMotion (line 35) | function subscribeReducedMotion(callback: () => void): () => void {
  function getReducedMotionSnapshot (line 41) | function getReducedMotionSnapshot(): boolean {
  function getReducedMotionServerSnapshot (line 45) | function getReducedMotionServerSnapshot(): boolean {
  function useTypingAnimation (line 49) | function useTypingAnimation({

FILE: app/layout.tsx
  constant APP_NAME (line 23) | const APP_NAME = "HackerAI";
  constant APP_DEFAULT_TITLE (line 24) | const APP_DEFAULT_TITLE = "HackerAI - AI-Powered Penetration Testing Ass...
  constant APP_TITLE_TEMPLATE (line 25) | const APP_TITLE_TEMPLATE = "%s | HackerAI";
  constant APP_DESCRIPTION (line 26) | const APP_DESCRIPTION =
  function RootLayout (line 91) | function RootLayout({

FILE: app/login/route.ts
  function GET (line 4) | async function GET(request: Request) {

FILE: app/posthog.js
  function PostHogClient (line 3) | function PostHogClient() {

FILE: app/privacy-policy/page.tsx
  function PrivacyPolicyPage (line 23) | function PrivacyPolicyPage() {

FILE: app/providers.tsx
  function PostHogProvider (line 8) | function PostHogProvider({ children }: { children: React.ReactNode }) {

FILE: app/services/__tests__/desktop-sandbox-bridge.test.ts
  function createTestJwt (line 50) | function createTestJwt(sub: string): string {
  function buildConfig (line 56) | function buildConfig(overrides: Record<string, unknown> = {}) {
  function getPublicationHandler (line 71) | function getPublicationHandler(): (ctx: { data: unknown }) => void {
  function startBridgeAndForwardChunks (line 249) | async function startBridgeAndForwardChunks(

FILE: app/services/desktop-sandbox-bridge.ts
  type RefreshTokenResult (line 18) | type RefreshTokenResult =
  type StreamChunk (line 42) | interface StreamChunk {
  function isUnauthenticatedError (line 52) | function isUnauthenticatedError(error: unknown): boolean {
  type DesktopBridgeConfig (line 59) | interface DesktopBridgeConfig {
  class DesktopSandboxBridge (line 81) | class DesktopSandboxBridge {
    method constructor (line 88) | constructor(config: DesktopBridgeConfig) {
    method getConnectionId (line 92) | getConnectionId(): string | null {
    method terminateClient (line 96) | private terminateClient(): void {
    method start (line 107) | async start(): Promise<string> {
    method extractUserIdFromToken (line 250) | private extractUserIdFromToken(token: string): string {
    method getOsInfo (line 262) | private async getOsInfo(): Promise<
    method handleCommand (line 331) | private async handleCommand(command: CommandMessage): Promise<void> {
    method handleCommandCancel (line 372) | private async handleCommandCancel(
    method forwardChunk (line 382) | private async forwardChunk(
    method publishResult (line 441) | private async publishResult(message: SandboxMessage): Promise<void> {
    method handlePtyCreate (line 455) | private async handlePtyCreate(msg: PtyCreateMessage): Promise<void> {
    method handlePtyInput (line 577) | private async handlePtyInput(msg: PtyInputMessage): Promise<void> {
    method handlePtyResize (line 598) | private async handlePtyResize(msg: PtyResizeMessage): Promise<void> {
    method handlePtyKill (line 611) | private async handlePtyKill(msg: PtyKillMessage): Promise<void> {
    method stop (line 635) | async stop(): Promise<void> {

FILE: app/share/[shareId]/SharedChatContext.tsx
  type SharedChatContextType (line 6) | interface SharedChatContextType {

FILE: app/share/[shareId]/SharedChatView.tsx
  function SharedComputerSidebarDesktop (line 22) | function SharedComputerSidebarDesktop({ messages }: { messages: any[] }) {
  function SharedComputerSidebarMobile (line 46) | function SharedComputerSidebarMobile({ messages }: { messages: any[] }) {
  type SharedChatViewProps (line 67) | interface SharedChatViewProps {
  constant UUID_REGEX (line 72) | const UUID_REGEX =
  function SharedChatView (line 75) | function SharedChatView({ shareId }: SharedChatViewProps) {

FILE: app/share/[shareId]/SharedMessages.tsx
  type MessagePart (line 6) | interface MessagePart {
  type Message (line 17) | interface Message {
  type SharedMessagesProps (line 25) | interface SharedMessagesProps {
  function SharedMessages (line 30) | function SharedMessages({ messages, shareDate }: SharedMessagesProps) {

FILE: app/share/[shareId]/components/SharedMessagePartHandler.tsx
  type MessagePart (line 44) | interface MessagePart {
  type SharedMessagePartHandlerProps (line 55) | interface SharedMessagePartHandlerProps {
  function renderTerminalTool (line 186) | function renderTerminalTool(
  function renderLegacyFileTool (line 248) | function renderLegacyFileTool(
  function renderFileTool (line 371) | function renderFileTool(
  function renderWebSearchTool (line 502) | function renderWebSearchTool(part: MessagePart, idx: number) {
  function renderOpenUrlTool (line 548) | function renderOpenUrlTool(part: MessagePart, idx: number) {
  function renderGetTerminalFilesTool (line 579) | function renderGetTerminalFilesTool(part: MessagePart, idx: number) {
  function renderTodoTool (line 623) | function renderTodoTool(part: MessagePart, idx: number) {
  function renderProxyTool (line 673) | function renderProxyTool(
  function renderReasoningPart (line 773) | function renderReasoningPart(parts: MessagePart[], partIndex: number) {
  function renderSummarizationPart (line 810) | function renderSummarizationPart(part: MessagePart, idx: number) {
  function renderHttpRequestTool (line 825) | function renderHttpRequestTool(
  function renderNotesTool (line 887) | function renderNotesTool(

FILE: app/share/[shareId]/components/SharedTodoBlock.tsx
  type SharedTodoBlockProps (line 9) | interface SharedTodoBlockProps {

FILE: app/share/[shareId]/page.tsx
  type Props (line 4) | type Props = {
  function generateMetadata (line 8) | async function generateMetadata({ params }: Props): Promise<Metadata> {
  function SharedChatPage (line 18) | async function SharedChatPage({ params }: Props) {

FILE: app/signup/route.ts
  function GET (line 4) | async function GET(request: Request) {

FILE: app/terms-of-service/page.tsx
  function TermsOfServicePage (line 20) | function TermsOfServicePage() {

FILE: components/ConvexClientProvider.tsx
  function ConvexClientProvider (line 11) | function ConvexClientProvider({ children }: { children: ReactNode }) {

FILE: components/ai-elements/__tests__/worked-for.test.tsx
  function renderScrollableWorkedFor (line 10) | function renderScrollableWorkedFor({

FILE: components/ai-elements/reasoning.tsx
  type ReasoningContextValue (line 14) | type ReasoningContextValue = {
  type ReasoningProps (line 30) | type ReasoningProps = ComponentProps<typeof Collapsible> & {
  function Reasoning (line 34) | function Reasoning({
  type ReasoningTriggerProps (line 75) | type ReasoningTriggerProps = ComponentProps<
  function ReasoningTrigger (line 84) | function ReasoningTrigger({
  type ReasoningContentProps (line 119) | type ReasoningContentProps = ComponentProps<typeof CollapsibleContent>;
  function ReasoningContent (line 121) | function ReasoningContent({

FILE: components/ai-elements/shimmer.tsx
  type TextShimmerProps (line 12) | interface TextShimmerProps {

FILE: components/ai-elements/worked-for.tsx
  function formatDuration (line 23) | function formatDuration(ms: number): string {
  type WorkedForContextValue (line 35) | type WorkedForContextValue = {
  type WorkedForProps (line 52) | type WorkedForProps = ComponentProps<typeof Collapsible> & {
  type ScrollSnapshot (line 57) | type ScrollSnapshot = {
  constant AUTO_COLLAPSE_DELAY_MS (line 84) | const AUTO_COLLAPSE_DELAY_MS = 700;
  constant SCROLL_RESTORE_MS (line 85) | const SCROLL_RESTORE_MS = 450;
  constant BOTTOM_SCROLL_RESTORE_MS (line 86) | const BOTTOM_SCROLL_RESTORE_MS = 1_100;
  constant BOTTOM_SCROLL_THRESHOLD_PX (line 89) | const BOTTOM_SCROLL_THRESHOLD_PX = 96;
  function WorkedFor (line 101) | function WorkedFor({
  type WorkedForTriggerProps (line 247) | type WorkedForTriggerProps = ComponentProps<
  function WorkedForTrigger (line 256) | function WorkedForTrigger({
  type WorkedForContentProps (line 363) | type WorkedForContentProps = Omit<
  function WorkedForContent (line 371) | function WorkedForContent({

FILE: components/icons/hackerai-svg.tsx
  type HackerAISVGProps (line 3) | interface HackerAISVGProps {

FILE: components/ui/alert-dialog.tsx
  function AlertDialog (line 9) | function AlertDialog({
  function AlertDialogTrigger (line 15) | function AlertDialogTrigger({
  function AlertDialogPortal (line 23) | function AlertDialogPortal({
  function AlertDialogOverlay (line 31) | function AlertDialogOverlay({
  function AlertDialogContent (line 47) | function AlertDialogContent({
  function AlertDialogHeader (line 66) | function AlertDialogHeader({
  function AlertDialogFooter (line 79) | function AlertDialogFooter({
  function AlertDialogTitle (line 95) | function AlertDialogTitle({
  function AlertDialogDescription (line 108) | function AlertDialogDescription({
  function AlertDialogAction (line 121) | function AlertDialogAction({
  function AlertDialogCancel (line 133) | function AlertDialogCancel({

FILE: components/ui/avatar.tsx
  function Avatar (line 8) | function Avatar({
  function AvatarImage (line 24) | function AvatarImage({
  function AvatarFallback (line 37) | function AvatarFallback({

FILE: components/ui/badge.tsx
  type BadgeProps (line 26) | interface BadgeProps
  function Badge (line 31) | function Badge({ className, variant, ...props }: BadgeProps) {

FILE: components/ui/button.tsx
  function Button (line 42) | function Button({

FILE: components/ui/calendar.tsx
  function Calendar (line 18) | function Calendar({
  function CalendarDayButton (line 182) | function CalendarDayButton({

FILE: 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: components/ui/code-action-buttons.tsx
  type CodeActionButtonsProps (line 11) | interface CodeActionButtonsProps {

FILE: components/ui/collapsible.tsx
  function Collapsible (line 5) | function Collapsible({
  function CollapsibleTrigger (line 11) | function CollapsibleTrigger({
  function CollapsibleContent (line 22) | function CollapsibleContent({

FILE: components/ui/dialog.tsx
  function Dialog (line 9) | function Dialog({
  function DialogTrigger (line 15) | function DialogTrigger({
  function DialogPortal (line 21) | function DialogPortal({
  function DialogClose (line 27) | function DialogClose({
  function DialogOverlay (line 33) | function DialogOverlay({
  function DialogContent (line 49) | function DialogContent({
  function DialogHeader (line 83) | function DialogHeader({ className, ...props }: React.ComponentProps<"div...
  function DialogFooter (line 93) | function DialogFooter({ className, ...props }: React.ComponentProps<"div...
  function DialogTitle (line 106) | function DialogTitle({
  function DialogDescription (line 119) | function DialogDescription({

FILE: components/ui/dots-spinner.tsx
  type DotsSpinnerProps (line 4) | interface DotsSpinnerProps extends HTMLAttributes<HTMLDivElement> {

FILE: components/ui/dropdown-menu.tsx
  function DropdownMenu (line 9) | function DropdownMenu({
  function DropdownMenuPortal (line 15) | function DropdownMenuPortal({
  function DropdownMenuTrigger (line 23) | function DropdownMenuTrigger({
  function DropdownMenuContent (line 34) | function DropdownMenuContent({
  function DropdownMenuGroup (line 54) | function DropdownMenuGroup({
  function DropdownMenuItem (line 62) | function DropdownMenuItem({
  function DropdownMenuCheckboxItem (line 85) | function DropdownMenuCheckboxItem({
  function DropdownMenuRadioGroup (line 111) | function DropdownMenuRadioGroup({
  function DropdownMenuRadioItem (line 122) | function DropdownMenuRadioItem({
  function DropdownMenuLabel (line 146) | function DropdownMenuLabel({
  function DropdownMenuSeparator (line 166) | function DropdownMenuSeparator({
  function DropdownMenuShortcut (line 179) | function DropdownMenuShortcut({
  function DropdownMenuSub (line 195) | function DropdownMenuSub({
  function DropdownMenuSubTrigger (line 201) | function DropdownMenuSubTrigger({
  function DropdownMenuSubContent (line 225) | function DropdownMenuSubContent({

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

FILE: components/ui/loading.tsx
  type LoadingProps (line 3) | interface LoadingProps {
  function Loading (line 7) | function Loading({ size = 12 }: LoadingProps): JSX.Element {

FILE: components/ui/popover.tsx
  function Popover (line 8) | function Popover({
  function PopoverTrigger (line 14) | function PopoverTrigger({
  function PopoverContent (line 20) | function PopoverContent({
  function PopoverAnchor (line 42) | function PopoverAnchor({

FILE: components/ui/radio-group.tsx
  function RadioGroup (line 9) | function RadioGroup({
  function RadioGroupItem (line 22) | function RadioGroupItem({

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

FILE: components/ui/shared-todo-item.tsx
  type TodoDisplayStatus (line 11) | type TodoDisplayStatus = Todo["status"] | "paused";
  constant STATUS_ICONS (line 13) | const STATUS_ICONS = {

FILE: components/ui/sheet.tsx
  function Sheet (line 9) | function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive....
  function SheetTrigger (line 13) | function SheetTrigger({
  function SheetClose (line 19) | function SheetClose({
  function SheetPortal (line 25) | function SheetPortal({
  function SheetOverlay (line 31) | function SheetOverlay({
  function SheetContent (line 47) | function SheetContent({
  function SheetHeader (line 84) | function SheetHeader({ className, ...props }: React.ComponentProps<"div"...
  function SheetFooter (line 94) | function SheetFooter({ className, ...props }: React.ComponentProps<"div"...
  function SheetTitle (line 104) | function SheetTitle({
  function SheetDescription (line 117) | function SheetDescription({

FILE: components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_MAX_AGE (line 29) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
  constant SIDEBAR_WIDTH (line 30) | const SIDEBAR_WIDTH = "16rem";
  constant SIDEBAR_WIDTH_MOBILE (line 31) | const SIDEBAR_WIDTH_MOBILE = "18rem";
  constant SIDEBAR_WIDTH_ICON (line 32) | const SIDEBAR_WIDTH_ICON = "3rem";
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 33) | const SIDEBAR_KEYBOARD_SHORTCUT = "s";
  type SidebarContextProps (line 35) | type SidebarContextProps = {
  function useSidebar (line 47) | function useSidebar() {
  function SidebarProvider (line 56) | function SidebarProvider({
  function Sidebar (line 166) | function Sidebar({
  function SidebarTrigger (line 268) | function SidebarTrigger({
  function SidebarRail (line 294) | function SidebarRail({ className, ...props }: React.ComponentProps<"butt...
  function SidebarExpandArea (line 319) | function SidebarExpandArea({
  function SidebarInset (line 354) | function SidebarInset({ className, ...props }: React.ComponentProps<"mai...
  function SidebarInput (line 368) | function SidebarInput({
  function SidebarHeader (line 382) | function SidebarHeader({ className, ...props }: React.ComponentProps<"di...
  function SidebarFooter (line 393) | function SidebarFooter({ className, ...props }: React.ComponentProps<"di...
  function SidebarSeparator (line 404) | function SidebarSeparator({
  function SidebarContent (line 418) | function SidebarContent({ className, ...props }: React.ComponentProps<"d...
  function SidebarGroup (line 435) | function SidebarGroup({ className, ...props }: React.ComponentProps<"div...
  function SidebarGroupLabel (line 446) | function SidebarGroupLabel({
  function SidebarGroupAction (line 467) | function SidebarGroupAction({
  function SidebarGroupContent (line 490) | function SidebarGroupContent({
  function SidebarMenu (line 504) | function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
  function SidebarMenuItem (line 515) | function SidebarMenuItem({ className, ...props }: React.ComponentProps<"...
  function SidebarMenuButton (line 548) | function SidebarMenuButton({
  function SidebarMenuAction (line 598) | function SidebarMenuAction({
  function SidebarMenuBadge (line 630) | function SidebarMenuBadge({
  function SidebarMenuSkeleton (line 652) | function SidebarMenuSkeleton({
  function SidebarMenuSub (line 692) | function SidebarMenuSub({ className, ...props }: React.ComponentProps<"u...
  function SidebarMenuSubItem (line 707) | function SidebarMenuSubItem({
  function SidebarMenuSubButton (line 721) | function SidebarMenuSubButton({

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

FILE: components/ui/sonner.tsx
  type ToasterProps (line 7) | type ToasterProps = React.ComponentProps<typeof Sonner>;

FILE: components/ui/switch.tsx
  function Switch (line 8) | function Switch({

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

FILE: components/ui/tool-block.tsx
  type ToolBlockProps (line 4) | interface ToolBlockProps {

FILE: 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: components/ui/with-tooltip.tsx
  type WithTooltipProps (line 9) | interface WithTooltipProps {

FILE: convex/__tests__/chatSummaryFallback.test.ts
  method constructor (line 26) | constructor(data: any) {
  constant SERVICE_KEY (line 48) | const SERVICE_KEY = "test-service-key";
  constant CHAT_ID (line 59) | const CHAT_ID = "chat-001";
  constant USER_ID (line 60) | const USER_ID = "user-123";
  constant CHAT_DOC_ID (line 61) | const CHAT_DOC_ID = "chat-doc-id" as Id<"chats">;
  constant SUMMARY_DOC_ID (line 62) | const SUMMARY_DOC_ID = "summary-doc-id" as Id<"chat_summaries">;
  function makeSummaryDoc (line 64) | function makeSummaryDoc(
  function makeChatDoc (line 77) | function makeChatDoc(overrides: Record<string, any> = {}): Record<string...
  function setupChatQuery (line 115) | function setupChatQuery(chat: Record<string, any> | null): void {
  function makeAssistantMessage (line 247) | function makeAssistantMessage(
  function setupDbQueryChain (line 299) | function setupDbQueryChain(config: {

FILE: convex/__tests__/fileStorage.aggregate.test.ts
  method constructor (line 25) | constructor(data: unknown) {

FILE: convex/__tests__/fileStorage.delete.test.ts
  method constructor (line 25) | constructor(data: any) {

FILE: convex/__tests__/messages.hidden.test.ts
  method constructor (line 26) | constructor(data: any) {
  constant SERVICE_KEY (line 55) | const SERVICE_KEY = "test-service-key";
  constant CHAT_ID (line 58) | const CHAT_ID = "chat-001";
  constant USER_ID (line 59) | const USER_ID = "user-123";
  function makeMessage (line 61) | function makeMessage(overrides: Record<string, any> = {}): Record<string...
  function setupExistingMessage (line 99) | function setupExistingMessage(msg: Record<string, any> | null): void {
  function setupPaginatedMessages (line 194) | function setupPaginatedMessages(messages: Record<string, any>[]): void {
  function setupPaginatedMessages (line 288) | function setupPaginatedMessages(messages: Record<string, any>[]): void {

FILE: convex/__tests__/teamExtraUsage.test.ts
  constant SERVICE_KEY (line 44) | const SERVICE_KEY = "test-service-key";
  constant ORIGINAL_SERVICE_KEY (line 45) | const ORIGINAL_SERVICE_KEY = process.env.CONVEX_SERVICE_ROLE_KEY;
  constant ORG_ID (line 57) | const ORG_ID = "org_123";
  constant USER_ID (line 58) | const USER_ID = "user_abc";
  constant OTHER_USER_ID (line 59) | const OTHER_USER_ID = "user_xyz";
  constant POINTS_PER_DOLLAR (line 61) | const POINTS_PER_DOLLAR = 10_000;
  type TeamRow (line 63) | type TeamRow = {
  type MemberRow (line 82) | type MemberRow = {
  type WebhookRow (line 93) | type WebhookRow = {
  function makeMockCtx (line 106) | function makeMockCtx(opts?: {
  function callDeduct (line 198) | async function callDeduct(
  function callRefund (line 209) | async function callRefund(
  function callAddCredits (line 220) | async function callAddCredits(
  function callGetState (line 236) | async function callGetState(

FILE: convex/__tests__/userSuspensions.test.ts
  constant SERVICE_KEY (line 29) | const SERVICE_KEY = "test-service-key";
  type SuspensionRow (line 31) | type SuspensionRow = {
  function makeMockCtx (line 52) | function makeMockCtx(initialRows: SuspensionRow[] = []) {

FILE: convex/__tests__/webhookClaim.test.ts
  method constructor (line 32) | constructor(data: any) {
  constant SERVICE_KEY (line 50) | const SERVICE_KEY = "test-service-key";
  constant STALE_CLAIM_MS (line 53) | const STALE_CLAIM_MS = 10 * 60 * 1000;
  constant EVENT_ID (line 54) | const EVENT_ID = "evt_test_123";
  type Row (line 56) | type Row = {
  function makeMockCtx (line 64) | function makeMockCtx(initialRows: Row[] = []) {
  function callClaim (line 111) | async function callClaim(ctx: any) {
  function callFinalize (line 119) | async function callFinalize(ctx: any) {

FILE: convex/_generated/dataModel.d.ts
  type TableNames (line 23) | type TableNames = TableNamesInDataModel<DataModel>;
  type Doc (line 30) | type Doc<TableName extends TableNames> = DocumentByName<
  type Id (line 48) | type Id<TableName extends TableNames | SystemTableNames> =
  type DataModel (line 60) | type DataModel = DataModelFromSchemaDefinition<typeof schema>;

FILE: convex/_generated/server.d.ts
  type QueryCtx (line 107) | type QueryCtx = GenericQueryCtx<DataModel>;
  type MutationCtx (line 115) | type MutationCtx = GenericMutationCtx<DataModel>;
  type ActionCtx (line 123) | type ActionCtx = GenericActionCtx<DataModel>;
  type DatabaseReader (line 132) | type DatabaseReader = GenericDatabaseReader<DataModel>;
  type DatabaseWriter (line 143) | type DatabaseWriter = GenericDatabaseWriter<DataModel>;

FILE: convex/constants.ts
  constant MAX_PREVIOUS_SUMMARIES (line 1) | const MAX_PREVIOUS_SUMMARIES = 10;

FILE: convex/extraUsage.ts
  constant POINTS_PER_DOLLAR (line 14) | const POINTS_PER_DOLLAR = 10_000;
  constant TRUST_TIERS (line 36) | const TRUST_TIERS = [
  constant DEFAULT_TRUST_CAP_DOLLARS (line 42) | const DEFAULT_TRUST_CAP_DOLLARS = 100;
  constant DAYS_MS (line 44) | const DAYS_MS = 24 * 60 * 60 * 1000;
  type TrustReason (line 46) | type TrustReason =
  function computeExtraUsageCap (line 55) | function computeExtraUsageCap(settings: {
  constant STALE_CLAIM_MS (line 172) | const STALE_CLAIM_MS = 10 * 60 * 1000;
  constant MAX_AUTO_RELOAD_FAILURES (line 841) | const MAX_AUTO_RELOAD_FAILURES = 2;

FILE: convex/extraUsageActions.ts
  function getStripe (line 17) | function getStripe(): Stripe {
  function getWorkOS (line 26) | function getWorkOS(): WorkOS {
  function getStripeCustomerId (line 41) | async function getStripeCustomerId(userId: string): Promise<string | nul...
  function getStripePaymentMethod (line 59) | async function getStripePaymentMethod(customerId: string): Promise<{
  function getDefaultPaymentMethodId (line 113) | async function getDefaultPaymentMethodId(
  function createAutoReloadPayment (line 143) | async function createAutoReloadPayment(

FILE: convex/fileActions.ts
  constant MAX_FILE_SIZE_BYTES (line 41) | const MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;
  constant FILE_UPLOAD_LIMIT (line 44) | const FILE_UPLOAD_LIMIT = 80;
  constant FILE_UPLOAD_WINDOW (line 45) | const FILE_UPLOAD_WINDOW = "5 h";
  type RateLimitResult (line 48) | type RateLimitResult = {

FILE: convex/fileStorage.ts
  constant MAX_STORAGE_BYTES (line 16) | const MAX_STORAGE_BYTES = 10 * 1024 * 1024 * 1024;

FILE: convex/lib/logger.ts
  type LogLevel (line 8) | type LogLevel = "info" | "warn" | "error";
  type LogEvent (line 10) | interface LogEvent {
  function log (line 17) | function log(

FILE: convex/lib/utils.ts
  function validateServiceKey (line 5) | function validateServiceKey(serviceKey: string): void {
  function copyChatSummary (line 15) | async function copyChatSummary(

FILE: convex/localSandbox.ts
  function generateToken (line 41) | function generateToken(): string {
  function generateCentrifugoToken (line 51) | async function generateCentrifugoToken(
  function validateToken (line 72) | async function validateToken(
  type ConnectionRow (line 292) | type ConnectionRow = {
  function terminatedResult (line 307) | function terminatedResult(

FILE: convex/notes.ts
  constant VALID_CATEGORIES (line 7) | const VALID_CATEGORIES = [
  type NoteCategory (line 15) | type NoteCategory = (typeof VALID_CATEGORIES)[number];
  function generateNoteId (line 20) | function generateNoteId(): string {
  function estimateNoteTokens (line 28) | function estimateNoteTokens(

FILE: convex/rateLimitStatus.ts
  function getCachedModules (line 13) | async function getCachedModules() {

FILE: convex/s3Actions.ts
  type StorageUsage (line 12) | type StorageUsage = {
  type FileRecord (line 19) | type FileRecord = Doc<"files"> | null;

FILE: convex/s3Utils.ts
  function getRequiredEnvVar (line 17) | function getRequiredEnvVar(name: string): string {
  function getS3Client (line 28) | function getS3Client(): S3Client {
  function generateS3Key (line 47) | function generateS3Key(userId: string, fileName: string): string {
  function generateS3UploadUrl (line 61) | async function generateS3UploadUrl(
  function generateS3DownloadUrl (line 94) | async function generateS3DownloadUrl(s3Key: string): Promise<string> {
  function deleteS3Object (line 121) | async function deleteS3Object(s3Key: string): Promise<void> {

FILE: convex/teamExtraUsage.ts
  constant POINTS_PER_DOLLAR (line 17) | const POINTS_PER_DOLLAR = 10_000;
  function ensureTeamRow (line 32) | async function ensureTeamRow(ctx: MutationCtx, organizationId: string) {
  function ensureMemberRow (line 50) | async function ensureMemberRow(
  function currentMonthString (line 74) | function currentMonthString(): string {
  constant MAX_AUTO_RELOAD_FAILURES (line 735) | const MAX_AUTO_RELOAD_FAILURES = 2;

FILE: convex/teamExtraUsageActions.ts
  function getStripe (line 17) | function getStripe(): Stripe {
  function getWorkOS (line 26) | function getWorkOS(): WorkOS {
  function getOrgStripeCustomerId (line 41) | async function getOrgStripeCustomerId(
  function getDefaultPaymentMethodId (line 50) | async function getDefaultPaymentMethodId(
  function createAutoReloadInvoice (line 74) | async function createAutoReloadInvoice(

FILE: e2b/build.dev.ts
  function main (line 8) | async function main() {

FILE: e2b/build.prod.ts
  function main (line 8) | async function main() {

FILE: e2e/chat-pinned.spec.ts
  constant SHARED_CHAT_NAMES (line 7) | const SHARED_CHAT_NAMES = [
  function unpinAllChats (line 72) | async function unpinAllChats(
  function waitForChatsToAppear (line 113) | async function waitForChatsToAppear(page: Page): Promise<void> {
  function getOrderedChatTitles (line 123) | async function getOrderedChatTitles(
  function getOrderedChatUrls (line 141) | async function getOrderedChatUrls(

FILE: e2e/constants.ts
  constant TIMEOUTS (line 6) | const TIMEOUTS = {
  constant TEST_DATA (line 24) | const TEST_DATA = {

FILE: e2e/fixtures/auth.ts
  type TestUser (line 10) | type TestUser = TestUserFromConfig;
  constant TEST_USERS (line 12) | const TEST_USERS = getTestUsersRecord();
  type SessionCache (line 14) | interface SessionCache {
  constant SESSION_CACHE_DURATION (line 29) | const SESSION_CACHE_DURATION = 5 * 60 * 1000;
  constant AUTH_STORAGE_PATHS (line 32) | const AUTH_STORAGE_PATHS = {
  function getStorageStatePath (line 38) | function getStorageStatePath(user: TestUser): string {
  type PlaywrightStorageState (line 42) | interface PlaywrightStorageState {
  function tryLoadFromStorageStateFile (line 59) | async function tryLoadFromStorageStateFile(
  function isSessionValid (line 71) | function isSessionValid(cache: SessionCache): boolean {
  type AuthOptions (line 82) | interface AuthOptions {
  function authenticateUser (line 88) | async function authenticateUser(
  function performLogin (line 180) | async function performLogin(page: Page, user: TestUser): Promise<void> {
  function logout (line 224) | async function logout(page: Page): Promise<void> {
  function clearAuthCache (line 244) | async function clearAuthCache(): Promise<void> {
  function getAuthState (line 248) | async function getAuthState(context: BrowserContext): Promise<{

FILE: e2e/helpers/convex-helpers.ts
  function loadEnv (line 8) | function loadEnv(): void {
  function getConvexEnv (line 13) | function getConvexEnv(): { convexUrl: string; serviceKey: string } | null {
  function getProUserId (line 24) | async function getProUserId(): Promise<string | null> {
  function deleteTestUserChats (line 45) | async function deleteTestUserChats(): Promise<void> {
  function createManyTestChatsForProUser (line 65) | async function createManyTestChatsForProUser(

FILE: e2e/helpers/mock-handlers.ts
  type MockConfig (line 10) | interface MockConfig {
  function setupMocks (line 17) | async function setupMocks(
  function mockWorkOSLogin (line 55) | async function mockWorkOSLogin(
  function clearMocks (line 84) | async function clearMocks(page: Page): Promise<void> {

FILE: e2e/helpers/test-helpers.ts
  function sendAndWaitForResponse (line 14) | async function sendAndWaitForResponse(
  function attachTestFile (line 27) | async function attachTestFile(
  function setupChat (line 57) | async function setupChat(page: Page): Promise<ChatComponent> {
  function chatIdFromUrl (line 62) | function chatIdFromUrl(url: string): string {
  function createTwoChats (line 71) | async function createTwoChats(
  function sendMessageWithFileAndVerifyContent (line 110) | async function sendMessageWithFileAndVerifyContent(

FILE: e2e/page-objects/BasePage.ts
  method constructor (line 4) | constructor(protected page: Page) {}
  method goto (line 6) | async goto(path: string = "/"): Promise<void> {
  method reload (line 10) | async reload(): Promise<void> {

FILE: e2e/page-objects/ChatComponent.ts
  class ChatComponent (line 5) | class ChatComponent {
    method constructor (line 6) | constructor(private page: Page) {}
    method chatInput (line 8) | private get chatInput(): Locator {
    method sendButton (line 12) | private get sendButton(): Locator {
    method stopButton (line 16) | private get stopButton(): Locator {
    method attachButton (line 20) | private get attachButton(): Locator {
    method fileInput (line 24) | private get fileInput(): Locator {
    method messages (line 28) | private get messages(): Locator {
    method streamingIndicator (line 34) | private get streamingIndicator(): Locator {
    method modeDropdown (line 38) | private get modeDropdown(): Locator {
    method askModeOption (line 42) | private get askModeOption(): Locator {
    method agentModeOption (line 46) | private get agentModeOption(): Locator {
    method upgradeDialog (line 50) | private get upgradeDialog(): Locator {
    method upgradePopover (line 54) | private get upgradePopover(): Locator {
    method upgradeNowButton (line 58) | private get upgradeNowButton(): Locator {
    method upgradePlanButton (line 62) | private get upgradePlanButton(): Locator {
    method attachedFiles (line 66) | private get attachedFiles(): Locator {
    method removeFileButtons (line 70) | private get removeFileButtons(): Locator {
    method sendMessage (line 74) | async sendMessage(message: string): Promise<void> {
    method typeMessage (line 80) | async typeMessage(message: string): Promise<void> {
    method clickSend (line 84) | async clickSend(): Promise<void> {
    method stopGeneration (line 88) | async stopGeneration(): Promise<void> {
    method attachFile (line 92) | async attachFile(filePath: string): Promise<void> {
    method attachFiles (line 100) | async attachFiles(filePaths: string[]): Promise<void> {
    method waitForUploadComplete (line 107) | async waitForUploadComplete(fileName?: string): Promise<void> {
    method clickAttachButton (line 125) | async clickAttachButton(): Promise<void> {
    method removeAttachedFile (line 129) | async removeAttachedFile(index: number = 0): Promise<void> {
    method switchToAgentMode (line 133) | async switchToAgentMode(): Promise<void> {
    method switchToAskMode (line 138) | async switchToAskMode(): Promise<void> {
    method waitForResponse (line 143) | async waitForResponse(timeout: number = TIMEOUTS.MEDIUM): Promise<void> {
    method getMessageCount (line 157) | async getMessageCount(
    method getLastMessageText (line 168) | async getLastMessageText(timeout: number = TIMEOUTS.SHORT): Promise<st...
    method expectMessageContains (line 174) | async expectMessageContains(
    method expectStreamingVisible (line 187) | async expectStreamingVisible(): Promise<void> {
    method expectStreamingNotVisible (line 198) | async expectStreamingNotVisible(
    method expectUpgradeDialogVisible (line 213) | async expectUpgradeDialogVisible(): Promise<void> {
    method expectUpgradePopoverVisible (line 217) | async expectUpgradePopoverVisible(): Promise<void> {
    method expectUpgradeNowButtonVisible (line 221) | async expectUpgradeNowButtonVisible(): Promise<void> {
    method expectUpgradePlanButtonVisible (line 227) | async expectUpgradePlanButtonVisible(): Promise<void> {
    method clickUpgradeNow (line 233) | async clickUpgradeNow(): Promise<void> {
    method clickUpgradePlan (line 237) | async clickUpgradePlan(): Promise<void> {
    method expectImageAttached (line 241) | async expectImageAttached(fileName: string): Promise<void> {
    method expectNonImageFileAttached (line 248) | async expectNonImageFileAttached(fileName: string): Promise<void> {
    method expectFileAttached (line 255) | async expectFileAttached(fileName: string): Promise<void> {
    method expectAttachedFileCount (line 265) | async expectAttachedFileCount(count: number): Promise<void> {
    method expectChatInputVisible (line 271) | async expectChatInputVisible(): Promise<void> {
    method expectSendButtonEnabled (line 275) | async expectSendButtonEnabled(): Promise<void> {
    method expectSendButtonDisabled (line 279) | async expectSendButtonDisabled(): Promise<void> {
    method getCurrentMode (line 283) | async getCurrentMode(): Promise<string> {
    method expectMode (line 289) | async expectMode(mode: "ask" | "agent"): Promise<void> {
    method getChatHeaderTitle (line 297) | async getChatHeaderTitle(timeout: number = TIMEOUTS.SHORT): Promise<st...

FILE: e2e/page-objects/ChatModeSelector.ts
  type ChatMode (line 3) | type ChatMode = "agent" | "ask";
  class ChatModeSelector (line 5) | class ChatModeSelector {
    method constructor (line 11) | constructor(private page: Page) {
    method openModeDropdown (line 24) | async openModeDropdown(): Promise<void> {
    method selectMode (line 29) | async selectMode(mode: ChatMode): Promise<void> {
    method selectAskMode (line 41) | async selectAskMode(): Promise<void> {
    method selectAgentMode (line 45) | async selectAgentMode(): Promise<void> {
    method getCurrentMode (line 49) | async getCurrentMode(): Promise<ChatMode> {
    method verifyCurrentMode (line 57) | async verifyCurrentMode(mode: ChatMode): Promise<void> {
    method verifyModeSelectorVisible (line 62) | async verifyModeSelectorVisible(): Promise<void> {
    method verifyAskModeSelected (line 66) | async verifyAskModeSelected(): Promise<void> {
    method verifyAgentModeSelected (line 70) | async verifyAgentModeSelected(): Promise<void> {
    method verifyAgentModeHasProBadge (line 74) | async verifyAgentModeHasProBadge(): Promise<void> {
    method verifyModeDropdownContainsOptions (line 85) | async verifyModeDropdownContainsOptions(options: ChatMode[]): Promise<...

FILE: e2e/page-objects/ChatPage.ts
  type ChatMode (line 8) | type ChatMode = "agent" | "ask";
  class ChatPage (line 10) | class ChatPage extends BasePage {
    method constructor (line 20) | constructor(page: Page) {
    method sendMessage (line 33) | async sendMessage(message: string): Promise<void> {
    method typeMessage (line 38) | async typeMessage(message: string): Promise<void> {
    method clickSend (line 42) | async clickSend(): Promise<void> {
    method sendMessageWithEnter (line 46) | async sendMessageWithEnter(message: string): Promise<void> {
    method stopGeneration (line 51) | async stopGeneration(): Promise<void> {
    method waitForResponse (line 55) | async waitForResponse(timeout: number = TIMEOUTS.MEDIUM): Promise<void> {
    method getLastMessage (line 70) | async getLastMessage(timeout: number = TIMEOUTS.SHORT): Promise<string> {
    method getLastAssistantMessage (line 78) | async getLastAssistantMessage(
    method getAllMessages (line 88) | async getAllMessages(): Promise<string[]> {
    method verifyMessageVisible (line 99) | async verifyMessageVisible(text: string): Promise<void> {
    method verifyAssistantMessageVisible (line 105) | async verifyAssistantMessageVisible(text: string): Promise<void> {
    method verifySendButtonEnabled (line 111) | async verifySendButtonEnabled(): Promise<void> {
    method verifySendButtonDisabled (line 115) | async verifySendButtonDisabled(): Promise<void> {
    method verifyStopButtonVisible (line 119) | async verifyStopButtonVisible(): Promise<void> {
    method verifyStopButtonNotVisible (line 123) | async verifyStopButtonNotVisible(): Promise<void> {
    method clearInput (line 127) | async clearInput(): Promise<void> {
    method getInputValue (line 131) | async getInputValue(): Promise<string> {
    method switchMode (line 135) | async switchMode(mode: ChatMode): Promise<void> {
    method getCurrentMode (line 139) | async getCurrentMode(): Promise<ChatMode> {
    method verifyCurrentMode (line 143) | async verifyCurrentMode(mode: ChatMode): Promise<void> {
    method attachFile (line 147) | async attachFile(filePath: string): Promise<void> {
    method attachFiles (line 151) | async attachFiles(filePaths: string[]): Promise<void> {
    method removeAttachedFile (line 155) | async removeAttachedFile(fileName: string): Promise<void> {
    method expectImageAttached (line 159) | async expectImageAttached(fileName: string): Promise<void> {
    method expectFileAttached (line 163) | async expectFileAttached(fileName: string): Promise<void> {
    method verifyFileAttached (line 167) | async verifyFileAttached(fileName: string): Promise<void> {
    method verifyNoFilesAttached (line 171) | async verifyNoFilesAttached(): Promise<void> {
    method getAttachedFileCount (line 175) | async getAttachedFileCount(): Promise<number> {

FILE: e2e/page-objects/FileAttachment.ts
  class FileAttachment (line 4) | class FileAttachment {
    method constructor (line 10) | constructor(private page: Page) {
    method attachFile (line 21) | async attachFile(filePath: string): Promise<void> {
    method attachFiles (line 28) | async attachFiles(filePaths: string[]): Promise<void> {
    method clickAttachButton (line 39) | async clickAttachButton(): Promise<void> {
    method removeFile (line 45) | async removeFile(fileName: string): Promise<void> {
    method removeAllFiles (line 57) | async removeAllFiles(): Promise<void> {
    method expectImageAttached (line 70) | async expectImageAttached(fileName: string): Promise<void> {
    method expectFileAttached (line 79) | async expectFileAttached(fileName: string): Promise<void> {
    method verifyFileAttached (line 90) | async verifyFileAttached(fileName: string): Promise<void> {
    method verifyFileNotAttached (line 102) | async verifyFileNotAttached(fileName: string): Promise<void> {
    method verifyNoFilesAttached (line 109) | async verifyNoFilesAttached(): Promise<void> {
    method verifyFileUploading (line 113) | async verifyFileUploading(): Promise<void> {
    method waitForUploadComplete (line 117) | async waitForUploadComplete(fileName?: string): Promise<void> {
    method getAttachedFileCount (line 144) | async getAttachedFileCount(): Promise<number> {
    method getAttachedFileNames (line 155) | async getAttachedFileNames(): Promise<string[]> {
    method verifyFileHasError (line 170) | async verifyFileHasError(fileName: string): Promise<void> {
    method verifyFilePreviewVisible (line 182) | async verifyFilePreviewVisible(): Promise<void> {
    method verifyAttachButtonVisible (line 186) | async verifyAttachButtonVisible(): Promise<void> {
    method verifyAttachButtonEnabled (line 190) | async verifyAttachButtonEnabled(): Promise<void> {
    method verifyAttachButtonDisabled (line 194) | async verifyAttachButtonDisabled(): Promise<void> {

FILE: e2e/page-objects/HomePage.ts
  class HomePage (line 7) | class HomePage extends BasePage {
    method constructor (line 12) | constructor(page: Page) {
    method openSettingsDialog (line 19) | async openSettingsDialog(): Promise<void> {
    method navigateToSettingsTab (line 24) | async navigateToSettingsTab(tab: SettingsTab): Promise<void> {
    method verifySessionPersistence (line 29) | async verifySessionPersistence(): Promise<void> {
    method verifyUpgradeButtonNotVisible (line 35) | async verifyUpgradeButtonNotVisible(): Promise<void> {

FILE: e2e/page-objects/SettingsDialog.ts
  type SettingsTab (line 3) | type SettingsTab =
  class SettingsDialog (line 10) | class SettingsDialog {
    method constructor (line 14) | constructor(private page: Page) {
    method expectVisible (line 19) | async expectVisible(): Promise<void> {
    method navigateToTab (line 23) | async navigateToTab(tab: SettingsTab): Promise<void> {
    method navigateToAllTabs (line 29) | async navigateToAllTabs(tabs: SettingsTab[]): Promise<void> {
    method close (line 35) | async close(): Promise<void> {
    method getMFAToggle (line 44) | async getMFAToggle(): Promise<Locator> {
    method getLogoutAllDevicesButton (line 48) | async getLogoutAllDevicesButton(): Promise<Locator> {
    method expectMFAToggleVisible (line 52) | async expectMFAToggleVisible(): Promise<void> {
    method expectLogoutAllDevicesVisible (line 56) | async expectLogoutAllDevicesVisible(): Promise<void> {

FILE: e2e/page-objects/SidebarComponent.ts
  class SidebarComponent (line 4) | class SidebarComponent {
    method constructor (line 8) | constructor(private page: Page) {
    method expandIfCollapsed (line 13) | async expandIfCollapsed(): Promise<void> {
    method collapse (line 28) | async collapse(): Promise<void> {
    method getSubscriptionTier (line 33) | async getSubscriptionTier(): Promise<string> {
    method verifySubscriptionTier (line 38) | async verifySubscriptionTier(expectedTier: string): Promise<void> {
    method findChatByTitle (line 46) | async findChatByTitle(title: string): Promise<Locator> {
    method expectChatWithTitle (line 53) | async expectChatWithTitle(
    method clickChatByTitle (line 66) | async clickChatByTitle(title: string): Promise<void> {
    method clickChatById (line 76) | async clickChatById(chatId: string): Promise<void> {
    method clickChatByUrl (line 86) | async clickChatByUrl(url: string): Promise<void> {
    method expectChatWithId (line 94) | async expectChatWithId(
    method getChatTitleById (line 105) | async getChatTitleById(chatId: string): Promise<string> {
    method getAllChatItems (line 116) | async getAllChatItems(): Promise<Locator> {
    method getChatCount (line 123) | async getChatCount(): Promise<number> {
    method waitForChatListReady (line 133) | async waitForChatListReady(timeout: number = TIMEOUTS.MEDIUM): Promise...
    method openChatOptionsByTitle (line 149) | async openChatOptionsByTitle(title: string): Promise<void> {
    method openChatOptionsById (line 164) | async openChatOptionsById(chatId: string): Promise<void> {
    method openChatOptionsByIndex (line 179) | async openChatOptionsByIndex(index: number): Promise<void> {
    method clickPin (line 194) | async clickPin(title: string): Promise<void> {
    method clickUnpin (line 202) | async clickUnpin(title: string): Promise<void> {
    method clickPinByIndex (line 210) | async clickPinByIndex(index: number): Promise<void> {
    method clickUnpinByIndex (line 218) | async clickUnpinByIndex(index: number): Promise<void> {
    method expectPinIconVisible (line 226) | async expectPinIconVisible(
    method openChatOptionsByUrl (line 240) | async openChatOptionsByUrl(url: string): Promise<void> {
    method clickPinByUrl (line 248) | async clickPinByUrl(url: string): Promise<void> {
    method clickUnpinByUrl (line 256) | async clickUnpinByUrl(url: string): Promise<void> {

FILE: e2e/page-objects/UpgradeDialog.ts
  class UpgradeDialog (line 4) | class UpgradeDialog {
    method constructor (line 13) | constructor(private page: Page) {
    method verifyDialogVisible (line 31) | async verifyDialogVisible(): Promise<void> {
    method verifyDialogNotVisible (line 35) | async verifyDialogNotVisible(): Promise<void> {
    method verifyDialogTitle (line 39) | async verifyDialogTitle(expectedTitle: string): Promise<void> {
    method verifyDialogTitleContains (line 43) | async verifyDialogTitleContains(text: string): Promise<void> {
    method verifyDialogDescriptionContains (line 47) | async verifyDialogDescriptionContains(text: string): Promise<void> {
    method clickUpgradeNow (line 51) | async clickUpgradeNow(): Promise<void> {
    method clickUpgradePlan (line 55) | async clickUpgradePlan(): Promise<void> {
    method clickAnyUpgradeButton (line 59) | async clickAnyUpgradeButton(): Promise<void> {
    method closeDialog (line 69) | async closeDialog(): Promise<void> {
    method verifyUpgradeButtonVisible (line 78) | async verifyUpgradeButtonVisible(): Promise<void> {
    method verifyDialogContent (line 90) | async verifyDialogContent(expectedContent: {
    method waitForDialogToAppear (line 114) | async waitForDialogToAppear(): Promise<void> {
    method waitForDialogToDisappear (line 118) | async waitForDialogToDisappear(): Promise<void> {
    method verifyAgentModeUpgradeDialog (line 122) | async verifyAgentModeUpgradeDialog(): Promise<void> {
    method verifyFileUploadUpgradeDialog (line 129) | async verifyFileUploadUpgradeDialog(): Promise<void> {
    method getDialogTitle (line 135) | async getDialogTitle(): Promise<string> {
    method getDialogDescription (line 139) | async getDialogDescription(): Promise<string> {

FILE: e2e/page-objects/UserMenuComponent.ts
  class UserMenuComponent (line 3) | class UserMenuComponent {
    method constructor (line 7) | constructor(private page: Page) {
    method getUserMenuButton (line 14) | async getUserMenuButton(): Promise<Locator> {
    method isVisible (line 18) | async isVisible(): Promise<boolean> {
    method expectVisible (line 22) | async expectVisible(): Promise<void> {
    method openMenu (line 26) | async openMenu(): Promise<void> {
    method openSettings (line 30) | async openSettings(): Promise<void> {

FILE: hooks/use-is-standalone.ts
  function useIsStandalone (line 3) | function useIsStandalone() {

FILE: hooks/use-mobile.ts
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768;
  function useIsMobile (line 5) | function useIsMobile() {

FILE: lib/__tests__/suspensionMessage.test.ts
  constant SUPPORT_URL (line 4) | const SUPPORT_URL = "https://help.hackerai.co/";

FILE: lib/actions/billing-portal.ts
  function redirectToBillingPortal (line 7) | async function redirectToBillingPortal() {

FILE: lib/ai/providers.ts
  type OpenRouterInstance (line 39) | type OpenRouterInstance = typeof openrouter;
  type ModelName (line 59) | type ModelName = keyof typeof baseProviders;
  function isAnthropicModel (line 102) | function isAnthropicModel(modelName: string): boolean {
  function isDeepSeekModel (line 106) | function isDeepSeekModel(modelName: string): boolean {
  function resolveTierToProviderKey (line 120) | function resolveTierToProviderKey(

FILE: lib/ai/tools/__tests__/interact-terminal-session.test.ts
  method constructor (line 20) | constructor(msg = "exit", exitCode = 1) {
  type FakeHandle (line 67) | interface FakeHandle extends PtyHandle {
  function makeFakeHandle (line 74) | function makeFakeHandle(pid = 4242): FakeHandle {
  function makeFakeE2BSandbox (line 110) | function makeFakeE2BSandbox() {
  function makeContext (line 119) | function makeContext(opts: {
  function runTool (line 171) | async function runTool(
  function runExecTool (line 188) | async function runExecTool(
  function createSession (line 205) | async function createSession(

FILE: lib/ai/tools/__tests__/run-terminal-cmd.test.ts
  method constructor (line 21) | constructor(msg = "exit", exitCode = 1) {
  type FakeHandle (line 73) | interface FakeHandle extends PtyHandle {
  function makeFakeHandle (line 80) | function makeFakeHandle(pid = 4242): FakeHandle {
  function makeFakeE2BSandbox (line 116) | function makeFakeE2BSandbox() {
  function makeContext (line 125) | function makeContext(opts: {
  function runTool (line 181) | async function runTool(
  function tool (line 415) | function tool(ctx: Parameters<typeof createRunTerminalCmd>[0]) {

FILE: lib/ai/tools/file.ts
  method toModelOutput (line 315) | toModelOutput({ output }) {

FILE: lib/ai/tools/interact-terminal-session.ts
  constant MAX_INPUT_BYTES_PER_SEND (line 18) | const MAX_INPUT_BYTES_PER_SEND = 8 * 1024;
  constant DEFAULT_WAIT_TIMEOUT_SECONDS (line 19) | const DEFAULT_WAIT_TIMEOUT_SECONDS = 10;
  constant MAX_WAIT_TIMEOUT_SECONDS (line 20) | const MAX_WAIT_TIMEOUT_SECONDS = 300;
  constant SEND_IMMEDIATE_OUTPUT_WINDOW_MS (line 24) | const SEND_IMMEDIATE_OUTPUT_WINDOW_MS = 500;
  constant WAIT_QUIET_WINDOW_MS (line 28) | const WAIT_QUIET_WINDOW_MS = 500;
  type ActionResult (line 148) | type ActionResult = { result: Record<string, unknown> };
  method toModelOutput (line 355) | toModelOutput({ output }) {

FILE: lib/ai/tools/notes.ts
  method toModelOutput (line 295) | toModelOutput({ output }) {

FILE: lib/ai/tools/run-terminal-cmd.ts
  constant DEFAULT_STREAM_TIMEOUT_SECONDS (line 43) | const DEFAULT_STREAM_TIMEOUT_SECONDS = 60;
  constant MAX_TIMEOUT_SECONDS (line 44) | const MAX_TIMEOUT_SECONDS = 600;
  constant INTERACTIVE_QUIET_WINDOW_MS (line 49) | const INTERACTIVE_QUIET_WINDOW_MS = 500;
  function executeCommand (line 428) | async function executeCommand(sandboxInstance: typeof sandbox) {
  method toModelOutput (line 821) | toModelOutput({ output }) {

FILE: lib/ai/tools/utils/__tests__/centrifugo-sandbox.test.ts
  class MockSubscription (line 18) | class MockSubscription extends EventEmitter {
  class MockCentrifugeClient (line 24) | class MockCentrifugeClient extends EventEmitter {
  constant FIXED_UUID (line 52) | const FIXED_UUID = "cmd-test-uuid-1234";
  function createSandbox (line 65) | function createSandbox(
  function startCommand (line 79) | function startCommand(
  function createWindowsBashSandbox (line 473) | function createWindowsBashSandbox() {

FILE: lib/ai/tools/utils/__tests__/e2b-pty-adapter.test.ts
  type OnDataCb (line 20) | type OnDataCb = (bytes: Uint8Array) => void | Promise<void>;
  type CapturedCreateCall (line 22) | interface CapturedCreateCall {
  type MockHandle (line 30) | interface MockHandle {
  type MockPtyCalls (line 35) | interface MockPtyCalls {
  type MockSandboxResult (line 43) | interface MockSandboxResult {
  function buildMockSandbox (line 51) | function buildMockSandbox(pid = 4242): MockSandboxResult {

FILE: lib/ai/tools/utils/__tests__/pty-session-manager.test.ts
  type FakeHandle (line 15) | interface FakeHandle extends PtyHandle {
  function makeFakeHandle (line 27) | function makeFakeHandle(overrides?: { pid?: number }): FakeHandle {
  function makeCreateHandleFactory (line 53) | function makeCreateHandleFactory(handle: PtyHandle) {

FILE: lib/ai/tools/utils/__tests__/sandbox-file-uploader.test.ts
  function makeSandbox (line 29) | function makeSandbox(size: number, e2b = false) {

FILE: lib/ai/tools/utils/background-process-tracker.ts
  type BackgroundProcess (line 3) | interface BackgroundProcess {
  class BackgroundProcessTracker (line 10) | class BackgroundProcessTracker {
    method constructor (line 13) | constructor() {
    method addProcess (line 20) | addProcess(pid: number, command: string, outputFiles: string[]): void {
    method removeProcess (line 32) | removeProcess(pid: number): void {
    method checkProcessStatus (line 39) | async checkProcessStatus(sandbox: AnySandbox, pid: number): Promise<bo...
    method hasActiveProcessesForFiles (line 60) | async hasActiveProcessesForFiles(
    method normalizePath (line 101) | private normalizePath(path: string): string {
    method extractOutputFiles (line 116) | static extractOutputFiles(command: string): string[] {
    method getTrackedProcesses (line 172) | getTrackedProcesses(): BackgroundProcess[] {
    method clear (line 179) | clear(): void {

FILE: lib/ai/tools/utils/caido-proxy.ts
  constant CAIDO_DEFAULTS (line 1) | const CAIDO_DEFAULTS = {
  function getCaidoConfig (line 7) | function getCaidoConfig(caidoPort?: number): {
  function buildCaidoProxyEnvVars (line 17) | function buildCaidoProxyEnvVars(

FILE: lib/ai/tools/utils/centrifugo-pty-adapter.ts
  type CentrifugoPtyOptions (line 28) | interface CentrifugoPtyOptions extends CreatePtyOptions {
  type PtyCreatePayload (line 35) | interface PtyCreatePayload {
  type PtyInputPayload (line 46) | interface PtyInputPayload {
  type PtyResizePayload (line 53) | interface PtyResizePayload {
  type PtyKillPayload (line 61) | interface PtyKillPayload {
  type PtyOutgoingPayload (line 67) | type PtyOutgoingPayload =
  type PtyReadyMsg (line 75) | interface PtyReadyMsg {
  type PtyDataMsg (line 81) | interface PtyDataMsg {
  type PtyExitMsg (line 87) | interface PtyExitMsg {
  type PtyErrorMsg (line 93) | interface PtyErrorMsg {
  type PtyIncomingMsg (line 99) | type PtyIncomingMsg = PtyReadyMsg | PtyDataMsg | PtyExitMsg | PtyErrorMsg;
  constant LOG_PREFIX (line 103) | const LOG_PREFIX = "[centrifugo-pty]";
  function parsePtyMessage (line 105) | function parsePtyMessage(data: unknown): PtyIncomingMsg | null {
  function createCentrifugoPtyHandle (line 153) | async function createCentrifugoPtyHandle(

FILE: lib/ai/tools/utils/centrifugo-sandbox.ts
  constant VALID_MESSAGE_TYPES (line 14) | const VALID_MESSAGE_TYPES = new Set([
  function parseSandboxMessage (line 23) | function parseSandboxMessage(data: unknown): CommandResponseMessage | nu...
  type CommandResult (line 74) | interface CommandResult {
  type CentrifugoConfig (line 81) | interface CentrifugoConfig {
  class CentrifugoSandbox (line 90) | class CentrifugoSandbox extends EventEmitter {
    method constructor (line 94) | constructor(
    method getConnectionId (line 102) | getConnectionId(): string {
    method getConnectionName (line 106) | getConnectionName(): string {
    method getUserId (line 110) | getUserId(): string {
    method getWsUrl (line 114) | getWsUrl(): string {
    method issueToken (line 122) | async issueToken(ttlSeconds: number): Promise<string> {
    method getSandboxContext (line 129) | getSandboxContext(): string | null {
    method getOsContext (line 154) | getOsContext(): string | null {
    method escapePath (line 456) | private static escapePath(path: string): string {
    method parentDir (line 468) | private static parentDir(path: string): string {
    method isWindows (line 477) | isWindows(): boolean {
    method toNativePath (line 487) | private toNativePath(path: string): string {
    method escapeForTarget (line 500) | private escapeForTarget(value: string): string {
    method toBashPath (line 510) | private static toBashPath(path: string): string {
    method detectShell (line 528) | private async detectShell(): Promise<"bash" | "cmd"> {
    method shellContext (line 549) | private async shellContext(rawPath: string): Promise<{
    method ensureDirectory (line 573) | private async ensureDirectory(dir: string): Promise<void> {
    method detectCurlCaps (line 603) | private async detectCurlCaps(): Promise<{
    method detectHttpClient (line 628) | private async detectHttpClient(): Promise<"curl" | "wget"> {
    method getHost (line 1002) | getHost(_port: number): string {
    method close (line 1006) | async close(): Promise<void> {

FILE: lib/ai/tools/utils/e2b-errors.ts
  type E2BErrorCategory (line 34) | type E2BErrorCategory =
  function classifyE2BError (line 46) | function classifyE2BError(error: unknown): E2BErrorCategory {
  function isE2BPermanentError (line 77) | function isE2BPermanentError(error: unknown): boolean {
  function isE2BRateLimitError (line 85) | function isE2BRateLimitError(error: unknown): boolean {
  function getUserFacingE2BErrorMessage (line 93) | function getUserFacingE2BErrorMessage(error: unknown): string | null {

FILE: lib/ai/tools/utils/e2b-pty-adapter.ts
  type PtyDataCb (line 17) | type PtyDataCb = (data: Uint8Array) => void | Promise<void>;
  type E2BPtyCreateOpts (line 19) | interface E2BPtyCreateOpts {
  type E2BCommandHandle (line 28) | interface E2BCommandHandle {
  type E2BPtyModule (line 35) | interface E2BPtyModule {
  type SandboxWithPty (line 42) | interface SandboxWithPty {
  type PtyHandle (line 48) | interface PtyHandle {
  type CreatePtyOptions (line 58) | interface CreatePtyOptions {
  constant LOG_PREFIX (line 67) | const LOG_PREFIX = "[e2b-pty-adapter]";
  constant PTY_NO_TIMEOUT_MS (line 73) | const PTY_NO_TIMEOUT_MS = 0;
  function createE2BPtyHandle (line 75) | async function createE2BPtyHandle(

FILE: lib/ai/tools/utils/file-accumulator.ts
  type AccumulatedFileMetadata (line 3) | interface AccumulatedFileMetadata {
  class FileAccumulator (line 11) | class FileAccumulator {
    method add (line 14) | add(metadata: AccumulatedFileMetadata) {
    method addMany (line 18) | addMany(metadataList: Array<AccumulatedFileMetadata>) {
    method getAllIds (line 25) | getAllIds(): Array<Id<"files">> {
    method getAll (line 30) | getAll(): Array<AccumulatedFileMetadata> {
    method clear (line 34) | clear() {

FILE: lib/ai/tools/utils/guardrails.ts
  type GuardrailAction (line 12) | enum GuardrailAction {
  type Severity (line 18) | enum Severity {
  type GuardrailResult (line 26) | interface GuardrailResult {
  type GuardrailConfig (line 40) | interface GuardrailConfig {
  type GuardrailConfigUI (line 51) | interface GuardrailConfigUI {
  constant DEFAULT_GUARDRAILS (line 79) | const DEFAULT_GUARDRAILS: GuardrailConfig[] = [

FILE: lib/ai/tools/utils/hybrid-sandbox-manager.ts
  type SandboxInstance (line 16) | type SandboxInstance = Sandbox | CentrifugoSandbox;
  type SandboxPreference (line 20) | type SandboxPreference = "e2b" | "desktop" | (string & {});
  type SandboxFallbackInfo (line 22) | interface SandboxFallbackInfo {
  constant MAX_SANDBOX_HEALTH_FAILURES (line 41) | const MAX_SANDBOX_HEALTH_FAILURES = 5;
  class HybridSandboxManager (line 43) | class HybridSandboxManager implements SandboxManager {
    method constructor (line 52) | constructor(
    method recordHealthFailure (line 64) | recordHealthFailure(): boolean {
    method resetHealthFailures (line 81) | resetHealthFailures(): void {
    method isSandboxUnavailable (line 86) | isSandboxUnavailable(): boolean {
    method getEffectivePreference (line 95) | getEffectivePreference(): SandboxPreference {
    method getOsContext (line 113) | getOsContext(): string | null {
    method closeCurrentSandbox (line 123) | private async closeCurrentSandbox(): Promise<void> {
    method setSandboxPreference (line 135) | async setSandboxPreference(preference: SandboxPreference): Promise<voi...
    method consumeFallbackInfo (line 149) | consumeFallbackInfo(): SandboxFallbackInfo | null {
    method getSandboxInfo (line 155) | getSandboxInfo(): { type: SandboxType; name?: string } | null {
    method getSandboxType (line 164) | getSandboxType(toolName: string): SandboxType | undefined {
    method listConnections (line 179) | async listConnections(): Promise<ConnectionInfo[]> {
    method getSandbox (line 195) | async getSandbox(): Promise<{ sandbox: SandboxInstance }> {
    method useCentrifugoConnection (line 267) | private async useCentrifugoConnection(
    method getE2BSandbox (line 291) | private async getE2BSandbox(): Promise<{ sandbox: Sandbox }> {
    method setSandbox (line 320) | setSandbox(sandbox: SandboxInstance): void {
    method getSandboxContextForPrompt (line 337) | async getSandboxContextForPrompt(): Promise<string | null> {
    method buildSandboxContext (line 361) | private buildSandboxContext(connection: ConnectionInfo): string | null {

FILE: lib/ai/tools/utils/path-validation.ts
  function validateDownloadUrl (line 4) | function validateDownloadUrl(url: string): void {

FILE: lib/ai/tools/utils/perplexity.ts
  constant SEARCH_RESULT_CONTENT_MAX_TOKENS (line 2) | const SEARCH_RESULT_CONTENT_MAX_TOKENS = 250;
  constant RECENCY_MAP (line 5) | const RECENCY_MAP: Record<string, "day" | "week" | "month" | "year"> = {
  type PerplexitySearchResult (line 12) | interface PerplexitySearchResult {
  type PerplexitySearchResponse (line 20) | interface PerplexitySearchResponse {
  type FormattedSearchResult (line 25) | interface FormattedSearchResult {

FILE: lib/ai/tools/utils/pid-discovery.ts
  function findProcessPid (line 11) | async function findProcessPid(

FILE: lib/ai/tools/utils/platform-utils.ts
  function getPlatformDisplayName (line 8) | function getPlatformDisplayName(platform: string): string {
  function escapeShellValue (line 29) | function escapeShellValue(value: string, platform?: string): string {

FILE: lib/ai/tools/utils/process-termination.ts
  function verifyProcessTerminated (line 14) | async function verifyProcessTerminated(
  function forceKillProcess (line 55) | async function forceKillProcess(
  function terminateProcessReliably (line 94) | async function terminateProcessReliably(

FILE: lib/ai/tools/utils/proxy-manager.ts
  constant CAIDO_TOKEN_FILE (line 15) | const CAIDO_TOKEN_FILE = "/tmp/caido-token";
  constant CAIDO_LOG (line 16) | const CAIDO_LOG = "/tmp/caido.log";
  type CaidoSetupTimings (line 41) | interface CaidoSetupTimings {
  function classifyCaidoError (line 56) | function classifyCaidoError(error: unknown): CaidoErrorKind {
  function reportCaidoReady (line 69) | function reportCaidoReady(
  function isCaidoBroken (line 93) | function isCaidoBroken(text: string): boolean {
  function invalidateAndKillCaido (line 106) | async function invalidateAndKillCaido(context: ToolContext): Promise<voi...
  function ensureCaido (line 149) | async function ensureCaido(context: ToolContext): Promise<void> {
  function doEnsureExternalCaido (line 220) | async function doEnsureExternalCaido(
  function doEnsureCaido (line 285) | async function doEnsureCaido(
  function exportCaidoUiUrl (line 523) | async function exportCaidoUiUrl(
  constant HTTPQL_TEXT_FIELDS (line 570) | const HTTPQL_TEXT_FIELDS = new Set([
  function fixHttpqlQuoting (line 579) | function fixHttpqlQuoting(filter: string): string {
  constant SORT_MAPPING (line 604) | const SORT_MAPPING: Record<string, string> = {
  function getBaseUrl (line 615) | function getBaseUrl(): string {
  constant GRAPHQL_TIMEOUT (line 626) | const GRAPHQL_TIMEOUT = 15_000;
  function runGql (line 628) | async function runGql(
  function readCaidoTokenFromDisk (line 649) | async function readCaidoTokenFromDisk(
  function runGqlLocal (line 661) | async function runGqlLocal(
  function runGqlViaSandbox (line 774) | async function runGqlViaSandbox(
  function parseGqlResponse (line 824) | async function parseGqlResponse(
  function listRequests (line 853) | async function listRequests(
  function viewRequest (line 928) | async function viewRequest(
  constant MAX_REGEX_LENGTH (line 992) | const MAX_REGEX_LENGTH = 500;
  function searchContent (line 994) | function searchContent(
  function paginateContent (line 1041) | function paginateContent(
  function parseHttpResponse (line 1081) | function parseHttpResponse(raw: string): {
  function sendRequest (line 1133) | async function sendRequest(
  function scopeRules (line 1227) | async function scopeRules(
  function listSitemap (line 1347) | async function listSitemap(
  function cleanSitemapNode (line 1416) | function cleanSitemapNode(
  function viewSitemapEntry (line 1446) | async function viewSitemapEntry(context: ToolContext, entryId: string) {

FILE: lib/ai/tools/utils/pty-exited-promise.ts
  type ResolvableExited (line 10) | interface ResolvableExited {
  function createResolvableExited (line 15) | function createResolvableExited(): ResolvableExited {

FILE: lib/ai/tools/utils/pty-keys.ts
  constant SPECIAL_KEYS (line 13) | const SPECIAL_KEYS: Record<string, string> = {
  constant TMUX_SPECIAL_KEYS (line 77) | const TMUX_SPECIAL_KEYS: ReadonlySet<string> = new Set(
  constant RAW_TO_KEY_NAME (line 87) | const RAW_TO_KEY_NAME: Record<string, string> = Object.fromEntries(

FILE: lib/ai/tools/utils/pty-output-formatter.ts
  constant PARSER_ROWS (line 16) | const PARSER_ROWS = 500;
  constant PARSER_SCROLLBACK (line 17) | const PARSER_SCROLLBACK = 5000;
  constant ANSI_PATTERNS (line 51) | const ANSI_PATTERNS = [
  function fallbackClean (line 60) | function fallbackClean(text: string): string {
  function cleanPtyForUI (line 71) | async function cleanPtyForUI(text: string): Promise<string> {
  function lastNLinesBytes (line 108) | async function lastNLinesBytes(
  type SnapshotSource (line 118) | interface SnapshotSource {
  function getSessionSnapshot (line 122) | async function getSessionSnapshot(
  function getSessionSnapshots (line 131) | async function getSessionSnapshots(

FILE: lib/ai/tools/utils/pty-output.ts
  constant OSC_COMPLETE_RE (line 16) | const OSC_COMPLETE_RE = /\x1b\][^\x07\x1b\x9c]*(?:\x07|\x1b\\|\x9c)/g;
  constant OSC_633_RE (line 26) | const OSC_633_RE = /^.*\]633;.*$\r?\n?/gm;
  constant OSC_3008_RE (line 29) | const OSC_3008_RE = /^.*\]3008;.*$\r?\n?/gm;
  constant BRACKETED_PASTE_RE (line 32) | const BRACKETED_PASTE_RE = /^.*\[\?2004[hl].*$\r?\n?/gm;
  constant LEADING_CRLF_RE (line 35) | const LEADING_CRLF_RE = /^(\r?\n)+/;
  constant SENTINEL_LINE_RE (line 103) | const SENTINEL_LINE_RE = /^.*__DONE_[a-f0-9]+__\d*.*$/gm;

FILE: lib/ai/tools/utils/pty-session-manager.ts
  constant MAX_CONCURRENT_PTYS_PER_CHAT (line 14) | const MAX_CONCURRENT_PTYS_PER_CHAT = 10;
  constant SESSION_IDLE_TIMEOUT_MS (line 15) | const SESSION_IDLE_TIMEOUT_MS = 10 * 60_000;
  constant SESSION_MAX_LIFETIME_MS (line 16) | const SESSION_MAX_LIFETIME_MS = 60 * 60_000;
  constant MAX_BUFFER_BYTES (line 17) | const MAX_BUFFER_BYTES = 256 * 1024;
  constant DEFAULT_PTY_COLS (line 25) | const DEFAULT_PTY_COLS = 120;
  constant DEFAULT_PTY_ROWS (line 26) | const DEFAULT_PTY_ROWS = 30;
  constant CLOSE_EXIT_FALLBACK_MS (line 28) | const CLOSE_EXIT_FALLBACK_MS = 2_000;
  type PtySession (line 30) | interface PtySession {
  type CreateSessionOpts (line 55) | interface CreateSessionOpts {
  type InternalSession (line 62) | interface InternalSession extends PtySession {
  function shortSessionId (line 86) | function shortSessionId(
  class PtySessionManager (line 96) | class PtySessionManager {
    method create (line 99) | async create(chatId: string, opts: CreateSessionOpts): Promise<PtySess...
    method get (line 189) | get(chatId: string, sessionId: string): PtySession | undefined {
    method list (line 193) | list(chatId: string): PtySession[] {
    method peekBufferSize (line 203) | peekBufferSize(session: PtySession): number {
    method consumeDelta (line 211) | consumeDelta(session: PtySession): Uint8Array {
    method snapshot (line 223) | snapshot(session: PtySession): Uint8Array {
    method close (line 228) | async close(chatId: string, sessionId: string): Promise<void> {
    method closeAll (line 235) | async closeAll(chatId: string): Promise<void> {
    method onData (line 244) | private onData(session: InternalSession, bytes: Uint8Array): void {
    method armIdleTimer (line 254) | private armIdleTimer(session: InternalSession): void {
    method enforceRing (line 261) | private enforceRing(session: InternalSession): void {
    method totalBufferBytes (line 278) | private totalBufferBytes(session: PtySession): number {
    method sliceBuffer (line 284) | private sliceBuffer(
    method killAndRemove (line 310) | private async killAndRemove(
    method removeSession (line 356) | private removeSession(session: InternalSession): void {

FILE: lib/ai/tools/utils/pty-wait-utils.ts
  constant ANSI_REGEX (line 8) | const ANSI_REGEX =
  function waitForOutput (line 25) | async function waitForOutput(
  function capOutput (line 100) | function capOutput(text: string, maxBytes: number = 8 * 1024): string {
  function peekExited (line 115) | async function peekExited(

FILE: lib/ai/tools/utils/retry-with-backoff.ts
  type RetryOptions (line 10) | interface RetryOptions {
  function defaultIsPermanentError (line 30) | function defaultIsPermanentError(error: unknown): boolean {
  function retryWithBackoff (line 56) | async function retryWithBackoff<T>(

FILE: lib/ai/tools/utils/sandbox-command-options.ts
  constant MAX_COMMAND_EXECUTION_TIME (line 4) | const MAX_COMMAND_EXECUTION_TIME = 10 * 60 * 1000;
  constant LOCAL_EXTRA_PATH_DIRS (line 10) | const LOCAL_EXTRA_PATH_DIRS = [
  function augmentCommandPath (line 23) | function augmentCommandPath(
  function buildSandboxCommandOptions (line 46) | function buildSandboxCommandOptions(

FILE: lib/ai/tools/utils/sandbox-file-uploader.ts
  constant DEFAULT_MEDIA_TYPE (line 13) | const DEFAULT_MEDIA_TYPE = "application/octet-stream";
  constant MAX_GENERATED_FILE_SIZE_MB (line 14) | const MAX_GENERATED_FILE_SIZE_MB =
  constant SANDBOX_UPLOAD_TIMEOUT_MS (line 16) | const SANDBOX_UPLOAD_TIMEOUT_MS = 5 * 60 * 1000;
  type UploadedFileInfo (line 18) | type UploadedFileInfo = {
  function extractErrorMessage (line 33) | function extractErrorMessage(error: unknown): string {
  function shellQuote (line 44) | function shellQuote(value: string): string {
  function getSandboxFileSize (line 48) | async function getSandboxFileSize(
  function assertSandboxFileSizeAllowed (line 81) | function assertSandboxFileSizeAllowed(fileName: string, size: number): v...
  function getSandboxLogType (line 89) | function getSandboxLogType(sandbox: AnySandbox): "e2b" | "centrifugo" {
  function errorToLog (line 93) | function errorToLog(error: unknown) {
  function getFileNameFromPath (line 117) | function getFileNameFromPath(fullPath: string): string {
  function uploadGeneratedFileFromSandboxToUrl (line 121) | async function uploadGeneratedFileFromSandboxToUrl(args: {
  function uploadSandboxFileToConvex (line 172) | async function uploadSandboxFileToConvex(args: {

FILE: lib/ai/tools/utils/sandbox-health.ts
  constant CPU_WARNING_THRESHOLD (line 13) | const CPU_WARNING_THRESHOLD = 95;
  constant MEM_WARNING_THRESHOLD (line 14) | const MEM_WARNING_THRESHOLD = 90;
  function checkSandboxMetrics (line 20) | async function checkSandboxMetrics(sandbox: AnySandbox): Promise<{
  function getSandboxDiagnostics (line 64) | async function getSandboxDiagnostics(
  function waitForSandboxReady (line 84) | async function waitForSandboxReady(

FILE: lib/ai/tools/utils/sandbox-manager.ts
  constant MAX_SANDBOX_HEALTH_FAILURES (line 11) | const MAX_SANDBOX_HEALTH_FAILURES = 5;
  class DefaultSandboxManager (line 13) | class DefaultSandboxManager implements SandboxManager {
    method constructor (line 18) | constructor(
    method recordHealthFailure (line 27) | recordHealthFailure(): boolean {
    method resetHealthFailures (line 35) | resetHealthFailures(): void {
    method isSandboxUnavailable (line 40) | isSandboxUnavailable(): boolean {
    method getSandboxInfo (line 44) | getSandboxInfo(): SandboxInfo | null {
    method getEffectivePreference (line 48) | getEffectivePreference(): string {
    method getSandboxType (line 52) | getSandboxType(toolName: string): SandboxType | undefined {
    method getSandbox (line 59) | async getSandbox(): Promise<{
    method setSandbox (line 83) | setSandbox(sandbox: Sandbox): void {

FILE: lib/ai/tools/utils/sandbox-tools.ts
  constant SANDBOX_ENVIRONMENT_TOOLS (line 2) | const SANDBOX_ENVIRONMENT_TOOLS = [

FILE: lib/ai/tools/utils/sandbox-types.ts
  type OsInfo (line 5) | interface OsInfo {
  type ConnectionInfo (line 12) | interface ConnectionInfo {
  function isCentrifugoSandbox (line 24) | function isCentrifugoSandbox(
  function isE2BSandbox (line 40) | function isE2BSandbox(sandbox: AnySandbox | null): sandbox is Sandbox {
  type CommonSandboxInterface (line 49) | interface CommonSandboxInterface {
  function asCommonSandbox (line 82) | function asCommonSandbox(sandbox: AnySandbox): CommonSandboxInterface {

FILE: lib/ai/tools/utils/sandbox.ts
  type SandboxReadyPath (line 5) | type SandboxReadyPath = SandboxBootInfo["path"];
  constant SANDBOX_TEMPLATE (line 7) | const SANDBOX_TEMPLATE = process.env.E2B_TEMPLATE || "terminal-agent-san...
  constant BASH_SANDBOX_RESUME_TIMEOUT (line 8) | const BASH_SANDBOX_RESUME_TIMEOUT = 5 * 60 * 1000;
  constant BASH_SANDBOX_AUTOPAUSE_TIMEOUT (line 9) | const BASH_SANDBOX_AUTOPAUSE_TIMEOUT = 7 * 60 * 1000;
  constant RATE_LIMIT_COOLDOWN_MS (line 11) | const RATE_LIMIT_COOLDOWN_MS = 1_000;
  constant MAX_CREATE_RETRIES (line 12) | const MAX_CREATE_RETRIES = 3;
  constant SANDBOX_VERSION (line 23) | const SANDBOX_VERSION = "v9";

FILE: lib/ai/tools/utils/terminal-output-saver.ts
  function saveFullOutputToFile (line 11) | async function saveFullOutputToFile(
  function saveTruncatedOutput (line 49) | async function saveTruncatedOutput(opts: {

FILE: lib/ai/tools/utils/todo-manager.ts
  type TodoUpdate (line 3) | interface TodoUpdate {
  class TodoManager (line 13) | class TodoManager {
    method constructor (line 17) | constructor(initialTodos?: Todo[]) {
    method getAllTodos (line 26) | getAllTodos(): Todo[] {
    method setTodos (line 33) | setTodos(
    method getStats (line 87) | getStats() {
    method mergeWith (line 107) | mergeWith(baseTodos: Todo[] | undefined, assistantMessageId: string): ...

FILE: lib/ai/tools/web-search.ts
  constant WEB_SEARCH_COST_PER_REQUEST (line 17) | const WEB_SEARCH_COST_PER_REQUEST = 0.005;

FILE: lib/api/__tests__/build-extra-usage-config.test.ts
  constant USER_ID (line 38) | const USER_ID = "user_abc";
  constant ORG_ID (line 39) | const ORG_ID = "org_123";

FILE: lib/api/__tests__/chat-stream-helpers-fallback.test.ts
  constant KIMI_SLUG (line 21) | const KIMI_SLUG = "moonshotai/kimi-k2.6:exacto";
  constant GEMINI_SLUG (line 22) | const GEMINI_SLUG = "google/gemini-3-flash-preview";

FILE: lib/api/__tests__/chat-stream-helpers-notes.test.ts
  function buildNotesReminder (line 28) | function buildNotesReminder(noteTitle: string): string {
  constant RESUME_REMINDER (line 41) | const RESUME_REMINDER =
  function buildConversationMessages (line 125) | function buildConversationMessages(userTextContent: string) {

FILE: lib/api/agent-stream-runner.ts
  type AgentStreamState (line 72) | type AgentStreamState = {
  function initAgentStreamState (line 90) | function initAgentStreamState(
  type AgentStreamContext (line 112) | type AgentStreamContext = {
  function createAgentStream (line 163) | async function createAgentStream(

FILE: lib/api/chat-handler.ts
  function getStreamContext (line 108) | function getStreamContext() {
  method consumeSseStream (line 1281) | async consumeSseStream({ stream: sseStream }) {

FILE: lib/api/chat-logger.ts
  type ChatLoggerConfig (line 30) | interface ChatLoggerConfig {
  type RequestDetails (line 35) | interface RequestDetails {
  type UserContext (line 41) | interface UserContext {
  type ChatContext (line 47) | interface ChatContext {
  type RateLimitContext (line 56) | interface RateLimitContext {
  type StreamResult (line 64) | interface StreamResult {
  function providerErrorCategory (line 71) | function providerErrorCategory(details: Record<string, unknown>): string {
  function posthogProviderException (line 85) | function posthogProviderException(
  function createChatLogger (line 103) | function createChatLogger(config: ChatLoggerConfig) {
  type ChatLogger (line 425) | type ChatLogger = ReturnType<typeof createChatLogger>;
  function captureToolCalls (line 432) | function captureToolCalls({
  function captureAgentRun (line 475) | function captureAgentRun({
  function shutdownPostHog (line 503) | function shutdownPostHog(posthog: PostHog | null) {

FILE: lib/api/chat-stream-helpers.ts
  function hasFileAttachments (line 57) | function hasFileAttachments(
  function countFileAttachments (line 68) | function countFileAttachments(
  function stripImageAttachments (line 93) | function stripImageAttachments<
  function sendRateLimitWarnings (line 120) | function sendRateLimitWarnings(
  type TokenBucketEmitContext (line 189) | interface TokenBucketEmitContext {
  function emitTokenBucketThresholdWarning (line 205) | function emitTokenBucketThresholdWarning(
  function isXaiSafetyError (line 231) | function isXaiSafetyError(error: unknown): boolean {
  function isProviderApiError (line 262) | function isProviderApiError(error: unknown): boolean {
  function computeContextUsage (line 291) | function computeContextUsage(
  function isContextUsageEnabled (line 301) | function isContextUsageEnabled(
  function writeContextUsage (line 312) | function writeContextUsage(
  type SummarizationStepResult (line 319) | interface SummarizationStepResult {
  function runSummarizationStep (line 326) | async function runSummarizationStep(options: {
  class SummarizationTracker (line 395) | class SummarizationTracker {
    method recordSummarization (line 404) | recordSummarization(
    method processMessageForSave (line 430) | processMessageForSave<T extends { role: string; parts: any[] }>(
  constant MODEL_FALLBACK_CHAIN (line 458) | const MODEL_FALLBACK_CHAIN: Partial<Record<ModelName, readonly ModelName...
  constant ANTHROPIC_FALLBACK_CHAIN_BY_MODE (line 463) | const ANTHROPIC_FALLBACK_CHAIN_BY_MODE: Record<ChatMode, readonly ModelN...
  function getFallbackSlugs (line 498) | function getFallbackSlugs(
  function buildProviderOptions (line 513) | function buildProviderOptions(
  function logOpenRouterFallbackIfFired (line 546) | function logOpenRouterFallbackIfFired(args: {
  constant ANTHROPIC_CACHE_BREAKPOINT (line 560) | const ANTHROPIC_CACHE_BREAKPOINT = {
  function buildSystemPrompt (line 568) | function buildSystemPrompt(
  function addCacheBreakpointToLastUserMessage (line 585) | function addCacheBreakpointToLastUserMessage<
  function appendSystemReminderToLastUserMessage (line 609) | function appendSystemReminderToLastUserMessage(
  function injectNotesIntoMessages (line 643) | async function injectNotesIntoMessages(
  constant NOTES_REMINDER_REGEX (line 676) | const NOTES_REMINDER_REGEX =
  function replaceNotesBlock (line 683) | function replaceNotesBlock(
  function refreshNotesInModelMessages (line 712) | async function refreshNotesInModelMessages(
  function appendReminderToModelMessages (line 779) | function appendReminderToModelMessages(
  function applyPrepareStepReminders (line 817) | async function applyPrepareStepReminders(
  function assertFreeAgentGates (line 852) | function assertFreeAgentGates(args: {
  function buildExtraUsageConfig (line 883) | async function buildExtraUsageConfig(args: {
  function estimatePreflightInputTokens (line 944) | async function estimatePreflightInputTokens(args: {

FILE: lib/auth/auth-redirect-intents.ts
  constant AUTH_REDIRECT_INTENTS (line 3) | const AUTH_REDIRECT_INTENTS: Record<string, string> = {
  function getAuthRedirectPath (line 8) | function getAuthRedirectPath(url: URL): string | null {
  function redirectToAuthorizationUrl (line 23) | function redirectToAuthorizationUrl(

FILE: lib/auth/cross-tab-mutex.ts
  type LockData (line 8) | type LockData = {
  type CrossTabMutexOptions (line 13) | type CrossTabMutexOptions = {
  constant DEFAULT_LOCK_KEY (line 19) | const DEFAULT_LOCK_KEY = "cross-tab-mutex";
  constant DEFAULT_LOCK_TIMEOUT_MS (line 20) | const DEFAULT_LOCK_TIMEOUT_MS = 10000;
  class CrossTabMutex (line 22) | class CrossTabMutex {
    method constructor (line 28) | constructor(options: CrossTabMutexOptions = {}) {
    method tryAcquire (line 42) | tryAcquire(): boolean {
    method release (line 92) | release(): void {
    method acquireWithWait (line 115) | async acquireWithWait(
    method withLock (line 139) | async withLock<T>(
    method forceClear (line 158) | forceClear(): void {

FILE: lib/auth/entitlements.ts
  constant TIER_ENTITLEMENTS (line 4) | const TIER_ENTITLEMENTS: ReadonlyArray<{
  function parseEntitlements (line 27) | function parseEntitlements(raw: unknown): string[] {
  function resolveSubscriptionTier (line 37) | function resolveSubscriptionTier(

FILE: lib/auth/feature-flags.ts
  function hashToPercentage (line 10) | function hashToPercentage(str: string): number {
  function isFeatureEnabled (line 26) | function isFeatureEnabled(
  constant FEATURE_FLAGS (line 42) | const FEATURE_FLAGS = {
  function getCrossTabRolloutPercentage (line 47) | function getCrossTabRolloutPercentage(): number {
  constant FEATURE_ROLLOUTS (line 57) | const FEATURE_ROLLOUTS = {
  method [FEATURE_FLAGS.CROSS_TAB_TOKEN_SHARING] (line 58) | get [FEATURE_FLAGS.CROSS_TAB_TOKEN_SHARING]() {
  function isCrossTabTokenSharingEnabled (line 66) | function isCrossTabTokenSharingEnabled(

FILE: lib/auth/shared-token.ts
  constant SHARED_TOKEN_KEY (line 8) | const SHARED_TOKEN_KEY = "hackerai-shared-token";
  constant TOKEN_FRESHNESS_MS (line 9) | const TOKEN_FRESHNESS_MS = 60000;
  type SharedToken (line 11) | type SharedToken = {
  function isValidSharedToken (line 16) | function isValidSharedToken(parsed: unknown): parsed is SharedToken {
  function getSharedToken (line 25) | function getSharedToken(): SharedToken | null {
  function setSharedToken (line 43) | function setSharedToken(token: string): void {
  function clearExpiredSharedToken (line 56) | function clearExpiredSharedToken(): void {
  function isTokenFresh (line 77) | function isTokenFresh(sharedToken: SharedToken | null): boolean {
  function clearSharedToken (line 82) | function clearSharedToken(): void {
  function getFreshSharedToken (line 98) | function getFreshSharedToken(): string | null {
  function getFreshSharedTokenWithFallback (line 110) | async function getFreshSharedTokenWithFallback(

FILE: lib/auth/use-auth-from-authkit.ts
  function useSharedTokenCleanup (line 20) | function useSharedTokenCleanup(enabled: boolean): void {
  type ConvexAuthState (line 28) | type ConvexAuthState = {
  type AuthKitDeps (line 36) | type AuthKitDeps = {
  function useAuthFromAuthKit (line 50) | function useAuthFromAuthKit(

FILE: lib/auth/workos-organization-name.ts
  constant FALLBACK_ORGANIZATION_NAME (line 1) | const FALLBACK_ORGANIZATION_NAME = "Personal Workspace";
  constant MAX_ORGANIZATION_NAME_LENGTH (line 2) | const MAX_ORGANIZATION_NAME_LENGTH = 255;
  type OrganizationNameInput (line 7) | type OrganizationNameInput = {
  function buildWorkOSOrganizationName (line 13) | function buildWorkOSOrganizationName({

FILE: lib/billing/resolve-customer-users.ts
  function resolveUserIdsFromCustomer (line 5) | async function resolveUserIdsFromCustomer(

FILE: lib/centrifugo/__tests__/jwt.test.ts
  class SignJWT (line 12) | class SignJWT {
    method constructor (line 16) | constructor(payload: Record<string, unknown>) {
    method setProtectedHeader (line 20) | setProtectedHeader(header: Record<string, unknown>) {
    method setExpirationTime (line 25) | setExpirationTime(time: string) {
    method sign (line 34) | async sign(_key: Uint8Array): Promise<string> {
  function decodeJwtPayload (line 50) | function decodeJwtPayload(token: string): Record<string, unknown> {
  function decodeJwtHeader (line 57) | function decodeJwtHeader(token: string): Record<string, unknown> {

FILE: lib/centrifugo/jwt.ts
  function generateCentrifugoToken (line 3) | async function generateCentrifugoToken(

FILE: lib/centrifugo/types.ts
  type CommandMessage (line 1) | interface CommandMessage {
  type CommandCancelMessage (line 13) | interface CommandCancelMessage {
  type StdoutMessage (line 19) | interface StdoutMessage {
  type StderrMessage (line 25) | interface StderrMessage {
  type ExitMessage (line 31) | interface ExitMessage {
  type ErrorMessage (line 38) | interface ErrorMessage {
  type PtyCreateMessage (line 46) | interface PtyCreateMessage {
  type PtyInputMessage (line 57) | interface PtyInputMessage {
  type PtyResizeMessage (line 64) | interface PtyResizeMessage {
  type PtyKillMessage (line 72) | interface PtyKillMessage {
  type PtyReadyMessage (line 80) | interface PtyReadyMessage {
  type PtyDataMessage (line 86) | interface PtyDataMessage {
  type PtyExitMessage (line 92) | interface PtyExitMessage {
  type PtyErrorMessage (line 98) | interface PtyErrorMessage {
  type CommandResponseMessage (line 105) | type CommandResponseMessage =
  type SandboxMessage (line 114) | type SandboxMessage =
  function sandboxChannel (line 132) | function sandboxChannel(userId: string): string {

FILE: lib/chat/__tests__/agent-routing.test.ts
  constant DESKTOP_UA (line 6) | const DESKTOP_UA =

FILE: lib/chat/__tests__/chat-processor.test.ts
  function makeFilePart (line 10) | function makeFilePart(id: string, mediaType = "image/png") {
  function makeMessage (line 14) | function makeMessage(

FILE: lib/chat/__tests__/doom-loop-detection.test.ts
  function makeStep (line 10) | function makeStep(toolCalls: Array<{ toolName: string; input: unknown }>) {

FILE: lib/chat/__tests__/stop-conditions.test.ts
  function makeState (line 14) | function makeState(overrides: {

FILE: lib/chat/agent-long-heartbeat.ts
  constant AGENT_LONG_HEARTBEAT_PART_TYPE (line 1) | const AGENT_LONG_HEARTBEAT_PART_TYPE = "data-agent-heartbeat" as const;
  constant AGENT_LONG_HEARTBEAT_INTERVAL_MS (line 6) | const AGENT_LONG_HEARTBEAT_INTERVAL_MS = 25_000;
  type MessageWithParts (line 8) | type MessageWithParts = {

FILE: lib/chat/agent-long-tool-input-dedup.ts
  type ChunkLike (line 8) | type ChunkLike = { type?: string; toolCallId?: string };
  type ToolInputDedupFilter (line 10) | type ToolInputDedupFilter = {
  method shouldDrop (line 17) | shouldDrop(chunk) {

FILE: lib/chat/agent-long-transport.ts
  type RunHandle (line 23) | type RunHandle = { runId: string; publicAccessToken: string };
  constant TERMINAL_RUN_STATUSES (line 36) | const TERMINAL_RUN_STATUSES = new Set([
  constant STREAM_TIMEOUT_MS (line 49) | const STREAM_TIMEOUT_MS = 30_000;
  method start (line 57) | async start(controller) {

FILE: lib/chat/agent-routing.ts
  constant HACKERAI_DESKTOP_USER_AGENT_TOKEN (line 3) | const HACKERAI_DESKTOP_USER_AGENT_TOKEN = "HackerAI-Desktop";
  function isHackerAIDesktopUserAgent (line 5) | function isHackerAIDesktopUserAgent(
  function shouldUseAgentLongForAgent (line 11) | function shouldUseAgentLongForAgent({
  function getBrowserUserAgent (line 31) | function getBrowserUserAgent(): string {

FILE: lib/chat/auth-disclaimer.ts
  type SupportedLang (line 3) | type SupportedLang =
  constant AUTH_DISCLAIMER (line 13) | const AUTH_DISCLAIMER: Record<SupportedLang, string> = {
  constant ISO_639_3_TO_1 (line 24) | const ISO_639_3_TO_1: Record<string, SupportedLang> = {
  constant FRANC_ALLOWLIST (line 36) | const FRANC_ALLOWLIST = Object.keys(ISO_639_3_TO_1);
  constant MIN_LETTER_COUNT (line 41) | const MIN_LETTER_COUNT = 25;
  constant MIN_CONFIDENCE_MARGIN (line 48) | const MIN_CONFIDENCE_MARGIN = 0.05;
  function detectLang (line 50) | function detectLang(text: string): SupportedLang {

FILE: lib/chat/budget-monitor.ts
  constant BUDGET_THRESHOLDS (line 21) | const BUDGET_THRESHOLDS = [80, 95, 100] as const;
  type BudgetSnapshot (line 23) | interface BudgetSnapshot {
  function captureBudgetSnapshot (line 36) | function captureBudgetSnapshot(args: {
  class BudgetMonitor (line 69) | class BudgetMonitor {
    method constructor (line 72) | constructor(
    method checkAfterStep (line 85) | checkAfterStep(currentCostDollars: number): "continue" | "abort" {
    method emit (line 140) | private emit(args: {

FILE: lib/chat/chat-processor.ts
  function selectModel (line 42) | function selectModel(
  function hasImageOrPdfAttachment (line 85) | function hasImageOrPdfAttachment(messages: UIMessage[]): boolean {
  function addAuthMessage (line 101) | function addAuthMessage(messages: UIMessage[], moderationText: string) {
  constant ABORT_RENDERABLE_TOOL_TYPES (line 128) | const ABORT_RENDERABLE_TOOL_TYPES = new Set([
  type IncompleteMessagePartsLogContext (line 156) | type IncompleteMessagePartsLogContext = {
  function logIncompleteToolPartHandled (line 167) | function logIncompleteToolPartHandled({
  function createAbortedToolPart (line 207) | function createAbortedToolPart(part: any): any | null {
  function fixIncompleteMessageParts (line 236) | function fixIncompleteMessageParts(
  function fixIncompleteToolInvocations (line 368) | function fixIncompleteToolInvocations(messages: UIMessage[]): UIMessage[] {
  function removeDuplicateToolParts (line 393) | function removeDuplicateToolParts(messages: UIMessage[]): UIMessage[] {
  function stripOriginalContentFromMessages (line 439) | function stripOriginalContentFromMessages(messages: UIMessage[]): UIMess...
  function limitImageParts (line 511) | function limitImageParts(
  function stripProviderMetadata (line 572) | function stripProviderMetadata(messages: UIMessage[]): UIMessage[] {
  constant UI_ONLY_PART_TYPES (line 602) | const UI_ONLY_PART_TYPES = new Set(["data-summarization"]);
  function processChatMessages (line 623) | async function processChatMessages({

FILE: lib/chat/compaction/__tests__/prune-tool-outputs.test.ts
  function makeAssistantMessage (line 13) | function makeAssistantMessage(
  function makeUserMessage (line 20) | function makeUserMessage(text: string, id = "user-1"): UIMessage {
  function makeToolPart (line 24) | function makeToolPart(
  constant NO_MIN (line 41) | const NO_MIN = 0;
  function makeAssistantModelMsg (line 813) | function makeAssistantModelMsg(
  function makeToolModelMsg (line 831) | function makeToolModelMsg(

FILE: lib/chat/compaction/prune-tool-outputs.ts
  constant TOOL_OUTPUT_TOKEN_BUDGET (line 10) | const TOOL_OUTPUT_TOKEN_BUDGET = 40_000;
  constant PRUNE_MINIMUM_SAVINGS (line 18) | const PRUNE_MINIMUM_SAVINGS = 20_000;
  constant PROTECTED_TOOLS (line 25) | const PROTECTED_TOOLS = new Set([
  constant TOOL_TYPE_PREFIX (line 33) | const TOOL_TYPE_PREFIX = "tool-";
  type PruneResult (line 35) | interface PruneResult {
  type StorageCompactionResult (line 51) | interface StorageCompactionResult<T extends UIMessage = UIMessage> {
  constant STORAGE_MESSAGE_SOFT_LIMIT_BYTES (line 60) | const STORAGE_MESSAGE_SOFT_LIMIT_BYTES = 850 * 1024;
  constant STORAGE_TOOL_OUTPUT_TOKEN_BUDGET (line 61) | const STORAGE_TOOL_OUTPUT_TOKEN_BUDGET = 20_000;
  constant STORAGE_REASONING_CHAR_BUDGET (line 62) | const STORAGE_REASONING_CHAR_BUDGET = 32_000;
  constant STORAGE_REASONING_PART_CHAR_LIMIT (line 63) | const STORAGE_REASONING_PART_CHAR_LIMIT = 8_000;
  constant STORAGE_COMPACTED_REASONING_PREFIX (line 64) | const STORAGE_COMPACTED_REASONING_PREFIX =
  type ToolPart (line 71) | interface ToolPart {
  function compactMessageForStorage (line 266) | function compactMessageForStorage<T extends UIMessage>(
  function pruneToolOutputs (line 355) | function pruneToolOutputs(
  type ToolResultPart (line 480) | interface ToolResultPart {
  type ModelPruneResult (line 488) | interface ModelPruneResult {
  function pruneModelMessages (line 514) | function pruneModelMessages(
  function filterEmptyAssistantMessages (line 667) | function filterEmptyAssistantMessages<T extends Record<string, unknown>>(
  constant ANTHROPIC_CONTINUE_MESSAGE (line 691) | const ANTHROPIC_CONTINUE_MESSAGE = {
  type PromptMessage (line 697) | type PromptMessage = Record<string, unknown> & {
  type AnthropicPromptRepairAction (line 702) | type AnthropicPromptRepairAction =
  type AnthropicPromptRepairReason (line 707) | type AnthropicPromptRepairReason =
  type AppliedAnthropicPromptRepairReason (line 713) | type AppliedAnthropicPromptRepairReason = Exclude<
  type AppliedAnthropicPromptRepair (line 718) | interface AppliedAnthropicPromptRepair {
  type NoAnthropicPromptRepair (line 725) | interface NoAnthropicPromptRepair {
  type AnthropicPromptRepairTelemetry (line 731) | type AnthropicPromptRepairTelemetry =
  function repairAnthropicModelMessagesWithTelemetry (line 771) | function repairAnthropicModelMessagesWithTelemetry(
  function repairAnthropicModelMessages (line 811) | function repairAnthropicModelMessages(

FILE: lib/chat/doom-loop-detection.ts
  constant DOOM_LOOP_WARNING_THRESHOLD (line 13) | const DOOM_LOOP_WARNING_THRESHOLD = 3;
  constant DOOM_LOOP_HALT_THRESHOLD (line 14) | const DOOM_LOOP_HALT_THRESHOLD = 5;
  type DoomLoopSeverity (line 16) | type DoomLoopSeverity = "none" | "warning" | "halt";
  type DoomLoopResult (line 18) | interface DoomLoopResult {
  type MinimalToolCall (line 24) | interface MinimalToolCall {
  type MinimalStep (line 29) | interface MinimalStep {
  constant COSMETIC_INPUT_FIELDS (line 35) | const COSMETIC_INPUT_FIELDS = new Set(["brief", "explanation"]);
  function stripCosmeticFields (line 37) | function stripCosmeticFields(input: unknown): unknown {
  function createStepFingerprint (line 52) | function createStepFingerprint(step: MinimalStep): string {
  function detectDoomLoop (line 70) | function detectDoomLoop(steps: MinimalStep[]): DoomLoopResult {
  function generateDoomLoopNudge (line 117) | function generateDoomLoopNudge(result: DoomLoopResult): string {

FILE: lib/chat/stop-conditions.ts
  constant TOKEN_EXHAUSTION_FINISH_REASON (line 7) | const TOKEN_EXHAUSTION_FINISH_REASON = "context-limit";
  constant BUDGET_EXHAUSTION_FINISH_REASON (line 9) | const BUDGET_EXHAUSTION_FINISH_REASON = "budget-exhausted";
  function tokenExhaustedAfterSummarization (line 11) | function tokenExhaustedAfterSummarization(state: {
  constant PREEMPTIVE_TIMEOUT_FINISH_REASON (line 28) | const PREEMPTIVE_TIMEOUT_FINISH_REASON = "preemptive-timeout";
  constant AGENT_MAX_STREAM_DURATION_MS (line 29) | const AGENT_MAX_STREAM_DURATION_MS = 10 * 60 * 1000;
  function elapsedTimeExceeds (line 31) | function elapsedTimeExceeds(state: {
  constant DOOM_LOOP_FINISH_REASON (line 44) | const DOOM_LOOP_FINISH_REASON = "doom-loop";
  function doomLoopDetected (line 46) | function doomLoopDetected(state: {

FILE: lib/chat/summarization/__tests__/index.test.ts
  constant THRESHOLD (line 29) | const THRESHOLD = Math.floor(
  constant TOKENS_PER_ABOVE_MSG (line 33) | const TOKENS_PER_ABOVE_MSG = Math.ceil(THRESHOLD / 4) + 500;

FILE: lib/chat/summarization/constants.ts
  constant MESSAGES_TO_KEEP_UNSUMMARIZED (line 2) | const MESSAGES_TO_KEEP_UNSUMMARIZED = 0;
  constant SUMMARIZATION_THRESHOLD_PERCENTAGE (line 5) | const SUMMARIZATION_THRESHOLD_PERCENTAGE = 0.9;

FILE: lib/chat/summarization/helpers.ts
  type SummarizationUsage (line 27) | interface SummarizationUsage {
  type SummarizationResult (line 35) | interface SummarizationResult {

FILE: lib/chat/summarization/index.ts
  type EnsureSandbox (line 34) | type EnsureSandbox = () => Promise<AnySandbox>;

FILE: lib/chat/summarization/prompts.ts
  constant AGENT_SUMMARIZATION_PROMPT (line 1) | const AGENT_SUMMARIZATION_PROMPT =
  constant ASK_SUMMARIZATION_PROMPT (line 64) | const ASK_SUMMARIZATION_PROMPT =
  constant AGENT_RESUME_PREAMBLE (line 89) | const AGENT_RESUME_PREAMBLE =

FILE: lib/chat/tool-abort-utils.ts
  constant ABORTED_TOOL_ERROR_TEXT (line 1) | const ABORTED_TOOL_ERROR_TEXT =
  function hasMeaningfulToolInput (line 7) | function hasMeaningfulToolInput(input: unknown): boolean {
  type MessageLike (line 18) | type MessageLike = {
  function summarizeIncompleteToolParts (line 24) | function summarizeIncompleteToolParts(messages: MessageLike[]) {

FILE: lib/constants/s3.ts
  constant MAX_FILE_SIZE_BYTES (line 20) | const MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;
  constant MAX_GENERATED_FILE_SIZE_BYTES (line 23) | const MAX_GENERATED_FILE_SIZE_BYTES = 250 * 1024 * 1024;
  constant S3_USER_FILES_PREFIX (line 26) | const S3_USER_FILES_PREFIX = "users";

FILE: lib/db/actions.ts
  constant MAX_DATABASE_ERROR_MESSAGE_LENGTH (line 30) | const MAX_DATABASE_ERROR_MESSAGE_LENGTH = 500;
  constant LARGE_MESSAGE_SAVE_WARNING_BYTES (line 31) | const LARGE_MESSAGE_SAVE_WARNING_BYTES = 850 * 1024;
  function getChatById (line 81) | async function getChatById({ id }: { id: string }) {
  function saveChat (line 93) | async function saveChat({
  function saveMessage (line 117) | async function saveMessage({
  function handleInitialChatAndUserMessage (line 253) | async function handleInitialChatAndUserMessage({
  function updateChat (line 319) | async function updateChat({
  function getMessagesByChatId (line 360) | async function getMessagesByChatId({
  function getUserCustomization (line 633) | async function getUserCustomization({ userId }: { userId: string }) {
  function setActiveTriggerRun (line 649) | async function setActiveTriggerRun({
  function getActiveTriggerRun (line 673) | async function getActiveTriggerRun({ chatId }: { chatId: string }) {
  function startStream (line 684) | async function startStream({
  function prepareForNewStream (line 703) | async function prepareForNewStream({ chatId }: { chatId: string }) {
  function getCancellationStatus (line 718) | async function getCancellationStatus({ chatId }: { chatId: string }) {
  function startTempStream (line 735) | async function startTempStream({
  function getTempCancellationStatus (line 753) | async function getTempCancellationStatus({
  function deleteTempStreamForBackend (line 771) | async function deleteTempStreamForBackend({
  function saveChatSummary (line 789) | async function saveChatSummary({
  function getLatestSummary (line 826) | async function getLatestSummary({ chatId }: { chatId: string }) {
  function createNote (line 846) | async function createNote({
  function listNotes (line 880) | async function listNotes({
  function updateNote (line 911) | async function updateNote({
  function deleteNote (line 945) | async function deleteNote({
  function getNotes (line 970) | async function getNotes({
  function logUsageRecord (line 990) | async function logUsageRecord({

FILE: lib/db/convex-client.ts
  function getConvexClient (line 13) | function getConvexClient(): ConvexHttpClient {
  function setConvexUrl (line 29) | function setConvexUrl(url: string) {

FILE: lib/db/convex-value-sanitizer.ts
  constant MIN_CONVEX_BIGINT (line 15) | const MIN_CONVEX_BIGINT = -BigInt("9223372036854775808");
  constant MAX_CONVEX_BIGINT (line 16) | const MAX_CONVEX_BIGINT = BigInt("9223372036854775807");
  function sanitizeForConvexValue (line 75) | function sanitizeForConvexValue(

FILE: lib/db/message-persistence-diagnostics.ts
  constant UNKNOWN_PART_TYPE (line 3) | const UNKNOWN_PART_TYPE = "unknown";
  function getMessagePersistenceDiagnostics (line 19) | function getMessagePersistenceDiagnostics(

FILE: lib/desktop-auth.ts
  constant TRANSFER_TOKEN_TTL_SECONDS (line 3) | const TRANSFER_TOKEN_TTL_SECONDS = 300;
  constant OAUTH_STATE_TTL_SECONDS (line 4) | const OAUTH_STATE_TTL_SECONDS = 300;
  constant TRANSFER_TOKEN_PREFIX (line 5) | const TRANSFER_TOKEN_PREFIX = "desktop-auth-transfer:";
  constant OAUTH_STATE_PREFIX (line 6) | const OAUTH_STATE_PREFIX = "desktop-oauth-state:";
  constant TOKEN_FORMAT_REGEX (line 7) | const TOKEN_FORMAT_REGEX = /^[a-f0-9]{64}$/;
  type TransferTokenData (line 9) | type TransferTokenData = {
  function getRedis (line 15) | function getRedis(): Redis | null {
  function generateTransferToken (line 29) | function generateTransferToken(): string {
  function createDesktopTransferToken (line 37) | async function createDesktopTransferToken(
  function exchangeDesktopTransferToken (line 73) | async function exchangeDesktopTransferToken(
  type OAuthStateMetadata (line 142) | type OAuthStateMetadata = {
  function createOAuthState (line 147) | async function createOAuthState(
  function verifyAndConsumeOAuthState (line 173) | async function verifyAndConsumeOAuthState(

FILE: lib/errors.ts
  type ErrorType (line 1) | type ErrorType =
  type Surface (line 9) | type Surface =
  type ErrorCode (line 20) | type ErrorCode = `${ErrorType}:${Surface}`;
  type ErrorVisibility (line 22) | type ErrorVisibility = "response" | "log" | "none";
  class ChatSDKError (line 36) | class ChatSDKError extends Error {
    method constructor (line 42) | constructor(
    method toResponse (line 59) | public toResponse() {
  function getMessageByErrorCode (line 85) | function getMessageByErrorCode(errorCode: ErrorCode): string {
  function isNetworkStreamError (line 127) | function isNetworkStreamError(error: unknown): boolean {
  function getStatusCodeByType (line 141) | function getStatusCodeByType(type: ErrorType) {

FILE: lib/extra-usage.ts
  constant EXTRA_USAGE_MULTIPLIER (line 6) | const EXTRA_USAGE_MULTIPLIER = 1.05;
  type ExtraUsageBalance (line 8) | interface ExtraUsageBalance {
  type DeductBalanceResult (line 18) | interface DeductBalanceResult {
  function pointsToDollars (line 45) | function pointsToDollars(points: number): number {
  function getExtraUsageBalance (line 54) | async function getExtraUsageBalance(
  type RefundBalanceResult (line 91) | interface RefundBalanceResult {
  function refundToBalance (line 105) | async function refundToBalance(
  function deductFromBalance (line 150) | async function deductFromBalance(
  type TeamExtraUsageState (line 209) | interface TeamExtraUsageState {
  function getTeamExtraUsageState (line 221) | async function getTeamExtraUsageState(
  function deductFromTeamBalance (line 253) | async function deductFromTeamBalance(
  function refundToTeamBalance (line 308) | async function refundToTeamBalance(

FILE: lib/logger.ts
  type ChatWideEvent (line 15) | interface ChatWideEvent {
  class WideEventBuilder (line 197) | class WideEventBuilder {
    method constructor (line 203) | constructor(
    method setRequestDetails (line 222) | setRequestDetails(details: {
    method setAssistantId (line 236) | setAssistantId(assistantId: string): this {
    method setUser (line 244) | setUser(user: { id: string; subscription: string }): this {
    method setChat (line 252) | setChat(chat: {
    method setExtraUsage (line 274) | setExtraUsage(config: ExtraUsageConfig | undefined): this {
    method setRateLimit (line 289) | setRateLimit(info: {
    method setModel (line 307) | setModel(configured: string): this {
    method setActualModel (line 315) | setActualModel(actual: string): this {
    method recordModelFallback (line 327) | recordModelFallback(fallback: { served: string; chain: string[] }): th...
    method recordAnthropicPromptRepair (line 340) | recordAnthropicPromptRepair(repair: {
    method startStream (line 364) | startStream(): this {
    method setSandbox (line 372) | setSandbox(info: ChatWideEvent["sandbox"]): this {
    method setSandboxBoot (line 381) | setSandboxBoot(boot: NonNullable<ChatWideEvent["sandbox_boot"]>): this {
    method setCaidoReady (line 392) | setCaidoReady(caido: NonNullable<ChatWideEvent["caido"]>): this {
    method recordToolCall (line 402) | recordToolCall(name: string, sandboxType?: string): this {
    method getToolCalls (line 410) | getToolCalls(): Array<{ name: string; sandbox_type?: string }> {
    method setStreamResult (line 417) | setStreamResult(result: {
    method addToolCost (line 438) | addToolCost(costDollars: number): this {
    method setUsage (line 446) | setUsage(usage: Record<string, unknown> | undefined): this {
    method setCacheMetrics (line 471) | setCacheMetrics(metrics: {
    method setSuccess (line 517) | setSuccess(): this {
    method setAborted (line 526) | setAborted(): this {
    method markProviderError (line 536) | markProviderError(details: {
    method setError (line 559) | setError(error: {
    method build (line 584) | build(): ChatWideEvent {
  method info (line 638) | info(event: ChatWideEvent): void {
  method warn (line 647) | warn(message: string, context?: Record<string, unknown>): void {
  method error (line 661) | error(
  function createWideEventBuilder (line 687) | function createWideEventBuilder(

FILE: lib/moderation.ts
  constant MODERATION_TOKEN_LIMIT (line 4) | const MODERATION_TOKEN_LIMIT = 512;
  function getModerationResult (line 6) | async function getModerationResult(
  function findTargetMessage (line 65) | function findTargetMessage(messages: any[], minLength: number): any | nu...
  function createCombinedMessage (line 109) | function createCombinedMessage(messages: any[]): any {
  function prepareInput (line 129) | function prepareInput(message: any): string {
  function truncateByTokens (line 146) | function truncateByTokens(content: string): string {
  function calculateModerationLevel (line 160) | function calculateModerationLevel(
  function determineShouldUncensorResponse (line 171) | function determineShouldUncensorResponse(

FILE: lib/posthog/server.ts
  function getClient (line 6) | function getClient(): PostHog | null {
  type LogFields (line 13) | type LogFields = Record<string, unknown> & {
  type EventFields (line 18) | type EventFields = Record<string, unknown> & {
  function distinctIdFor (line 23) | function distinctIdFor(userId: unknown): string {
  method error (line 28) | error(message: string, fields: LogFields = {}) {
  method warn (line 46) | warn(message: string, fields: LogFields = {}) {
  method info (line 64) | info(message: string, fields: LogFields = {}) {
  method event (line 82) | event(name: string, fields: EventFields = {}) {
  method flush (line 97) | async flush(): Promise<void> {

FILE: lib/posthog/worker.ts
  function createRetryLogger (line 10) | function createRetryLogger(

FILE: lib/pricing/features.ts
  constant PRICING (line 8) | const PRICING = {
  type PricingTier (line 27) | type PricingTier = keyof typeof PRICING;
  type PricingFeature (line 29) | type PricingFeature = {
  constant PLAN_HEADERS (line 34) | const PLAN_HEADERS = {

FILE: lib/rate-limit/refund.ts
  class UsageRefundTracker (line 8) | class UsageRefundTracker {
    method setUser (line 19) | setUser(
    method recordDeductions (line 32) | recordDeductions(rateLimitInfo: RateLimitInfo): void {
    method hasDeductions (line 40) | hasDeductions(): boolean {
    method refund (line 48) | async refund(): Promise<void> {

FILE: lib/rate-limit/token-bucket.ts
  constant MODEL_PRICING_MAP (line 22) | const MODEL_PRICING_MAP: Record<string, { input: number; output: number ...
  constant POINTS_PER_DOLLAR (line 40) | const POINTS_PER_DOLLAR = 10_000;
  constant NORMAL_USAGE_MULTIPLIER (line 48) | const NORMAL_USAGE_MULTIPLIER = 1.3;
  constant THIRTY_DAYS_SECONDS (line 51) | const THIRTY_DAYS_SECONDS = 30 * 24 * 60 * 60;
  constant MONTHLY_CREDITS (line 81) | const MONTHLY_CREDITS: Record<string, number> = {
  constant TEAM_CREDITS (line 752) | const TEAM_CREDITS = MONTHLY_CREDITS["team"] ?? 0;

FILE: lib/suspensionMessage.ts
  function getSuspensionMessage (line 13) | function getSuspensionMessage(blockedReason?: string | null): string {
  function mapBlockedReasonToLabel (line 18) | function mapBlockedReasonToLabel(blockedReason?: string | null): string {

FILE: lib/suspensions.ts
  function getActiveSuspensionForUser (line 10) | async function getActiveSuspensionForUser(userId: string) {
  function assertUserCanMakeCostIncurringRequest (line 17) | async function assertUserCanMakeCostIncurringRequest(userId: string) {

FILE: lib/system-prompt.ts
  constant DATE_FORMAT_OPTIONS (line 14) | const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
  constant LANGUAGE_SECTION (line 24) | const LANGUAGE_SECTION = `<language>
  constant PREINSTALLED_PENTESTING_TOOLS (line 32) | const PREINSTALLED_PENTESTING_TOOLS = `Pre-installed Pentesting Tools:

FILE: lib/system-prompt/notes.ts
  type Note (line 1) | interface Note {

FILE: lib/system-prompt/personality.ts
  constant PERSONALITY_INSTRUCTIONS (line 4) | const PERSONALITY_INSTRUCTIONS: Record<PersonalityType, string> = {

FILE: lib/token-utils.ts
  constant MAX_TOKENS_FREE (line 6) | const MAX_TOKENS_FREE = 32000;
  constant MAX_TOKENS_PAID (line 7) | const MAX_TOKENS_PAID = 200000;
  constant FILE_TOKEN_PERCENT (line 12) | const FILE_TOKEN_PERCENT = 0.5;
  constant STREAM_MAX_TOKENS (line 35) | const STREAM_MAX_TOKENS = 4096;
  constant TOOL_DEFAULT_MAX_TOKENS (line 36) | const TOOL_DEFAULT_MAX_TOKENS = 4096;
  constant TRUNCATION_MESSAGE (line 39) | const TRUNCATION_MESSAGE =
  constant FILE_READ_TRUNCATION_MESSAGE (line 41) | const FILE_READ_TRUNCATION_MESSAGE =
  function truncateOutput (line 211) | function truncateOutput(args: {

FILE: lib/usage-projection.ts
  type DailyUsage (line 5) | interface DailyUsage {
  type UsageProjection (line 10) | interface UsageProjection {
  function calculateUsageProjection (line 26) | function calculateUsageProjection(

FILE: lib/usage-tracker.ts
  type StepUsage (line 5) | interface StepUsage {
  class UsageTracker (line 20) | class UsageTracker {
    method resetModelLeg (line 42) | resetModelLeg() {
    method accumulateStep (line 56) | accumulateStep(usage: StepUsage) {
    method streamOutputTokens (line 71) | get streamOutputTokens(): number {
    method hasCacheData (line 76) | get hasCacheData(): boolean {
    method cacheHitRate (line 81) | get cacheHitRate(): number | null {
    method hasUsage (line 87) | get hasUsage(): boolean {
    method computeModelCostDollars (line 93) | computeModelCostDollars(selectedModel: string): number {
    method computeCostDollars (line 108) | computeCostDollars(selectedModel: string): number {
    method resolveUsageType (line 117) | resolveUsageType(rateLimitInfo: RateLimitInfo): "included" | "extra" {
    method resolveModelName (line 124) | resolveModelName({
    method log (line 141) | log({

FILE: lib/utils.ts
  type MessageRecord (line 8) | interface MessageRecord {
  function cn (line 26) | function cn(...inputs: ClassValue[]) {
  function fetchWithErrorHandlers (line 30) | async function fetchWithErrorHandlers(
  function convertToUIMessages (line 52) | function convertToUIMessages(messages: MessageRecord[]): ChatMessage[] {

FILE: lib/utils/__tests__/client-storage.test.ts
  constant STORAGE_KEY (line 10) | const STORAGE_KEY = "selected_model";
  constant LEGACY_ASK_KEY (line 11) | const LEGACY_ASK_KEY = `${STORAGE_KEY}_ask`;
  constant LEGACY_AGENT_KEY (line 12) | const LEGACY_AGENT_KEY = `${STORAGE_KEY}_agent`;

FILE: lib/utils/accumulate-ui-chunks.ts
  function accumulateChunksToMessage (line 9) | function accumulateChunksToMessage(

FILE: lib/utils/client-storage.ts
  type ConversationDraft (line 8) | type ConversationDraft = {
  type ConversationDraftStore (line 14) | type ConversationDraftStore = {
  constant CONVERSATION_DRAFTS_STORAGE_KEY (line 19) | const CONVERSATION_DRAFTS_STORAGE_KEY = "conversation_drafts";
  constant NULL_THREAD_DRAFT_ID (line 20) | const NULL_THREAD_DRAFT_ID = "null_thread";
  constant CHAT_MODE_STORAGE_KEY (line 21) | const CHAT_MODE_STORAGE_KEY = "chat_mode";
  constant HAS_AUTHENTICATED_BEFORE_STORAGE_KEY (line 22) | const HAS_AUTHENTICATED_BEFORE_STORAGE_KEY = "hackerai_has_authed_before";
  constant SELECTED_MODEL_STORAGE_KEY (line 23) | const SELECTED_MODEL_STORAGE_KEY = "selected_model";

FILE: lib/utils/error-utils.ts
  constant SENSITIVE_KEYS (line 22) | const SENSITIVE_KEYS = new Set([
  type ProviderAttempt (line 116) | interface ProviderAttempt {
  constant REQUEST_ID_HEADERS (line 123) | const REQUEST_ID_HEADERS = [
  function getStatusSummary (line 249) | function getStatusSummary(statusCode: number | undefined): string {
  function extractProviderDetails (line 273) | function extractProviderDetails(error: unknown): {
  function extractStatusCode (line 316) | function extractStatusCode(error: unknown): number | undefined {
  function extractMessageFromResponseBody (line 349) | function extractMessageFromResponseBody(body: string): string | undefined {
  function truncate (line 366) | function truncate(str: string, max: number): string {

FILE: lib/utils/file-download.ts
  type DownloadFileOptions (line 11) | interface DownloadFileOptions {
  function downloadFile (line 28) | async function downloadFile({

FILE: lib/utils/file-transform-utils.ts
  constant MAX_PROVIDER_IMAGE_DOWNLOAD_SIZE (line 17) | const MAX_PROVIDER_IMAGE_DOWNLOAD_SIZE = 30 * 1024 * 1024;
  type FileToProcess (line 19) | type FileToProcess = {
  type SizeProbeResult (line 26) | type SizeProbeResult = {

FILE: lib/utils/file-utils.ts
  type RateLimitInfo (line 9) | type RateLimitInfo = {
  type UploadUrlResult (line 16) | type UploadUrlResult = {
  constant MAX_FILE_SIZE (line 22) | const MAX_FILE_SIZE = 10 * 1024 * 1024;
  constant MAX_IMAGE_SIZE (line 29) | const MAX_IMAGE_SIZE = 5 * 1024 * 1024;
  constant ASK_MODE_MAX_FILES_LIMIT (line 32) | const ASK_MODE_MAX_FILES_LIMIT = 10;
  constant AGENT_MODE_MAX_FILES_LIMIT (line 35) | const AGENT_MODE_MAX_FILES_LIMIT = 20;
  constant MAX_FILES_LIMIT (line 38) | const MAX_FILES_LIMIT = AGENT_MODE_MAX_FILES_LIMIT;
  function getMaxFilesLimitForMode (line 40) | function getMaxFilesLimitForMode(mode: ChatMode): number {
  constant SUPPORTED_IMAGE_TYPES (line 47) | const SUPPORTED_IMAGE_TYPES = new Set([
  function isSupportedImageMediaType (line 58) | function isSupportedImageMediaType(mediaType: string): boolean {
  function isImageFile (line 65) | function isImageFile(file: File | LocalDesktopFile): boolean {
  function validateFile (line 72) | function validateFile(file: File | LocalDesktopFile): {
  function validateImageFile (line 95) | async function validateImageFile(
  function createFileMessagePartFromUploadedFile (line 140) | function createFileMessagePartFromUploadedFile(
  function formatFileSize (line 180) | function formatFileSize(bytes: number): string {
  function fileToBase64 (line 193) | function fileToBase64(file: File): Promise<string> {

FILE: lib/utils/message-processor.ts
  type BaseToolPart (line 36) | interface BaseToolPart {
  type TerminalToolPart (line 46) | interface TerminalToolPart extends BaseToolPart {
  type DataPart (line 76) | interface DataPart {

FILE: lib/utils/message-utils.ts
  type MessagePart (line 5) | interface MessagePart {
  type WebSource (line 47) | type WebSource = {

FILE: lib/utils/parse-rate-limit-warning.ts
  constant WARNING_TYPES (line 4) | const WARNING_TYPES = [
  type RawWarningType (line 9) | type RawWarningType = (typeof WARNING_TYPES)[number];
  constant BUCKET_TYPES (line 11) | const BUCKET_TYPES = ["monthly"] as const;
  type RawBucketType (line 12) | type RawBucketType = (typeof BUCKET_TYPES)[number];
  function isString (line 14) | function isString(v: unknown): v is string {
  function isNumber (line 17) | function isNumber(v: unknown): v is number {
  type ParseRateLimitWarningOptions (line 21) | interface ParseRateLimitWarningOptions {
  constant EXTRA_USAGE_STORAGE_KEY_PREFIX (line 26) | const EXTRA_USAGE_STORAGE_KEY_PREFIX = "extraUsageWarningShownUntil_";
  constant TOKEN_BUCKET_WARNING_KEY_PREFIX (line 27) | const TOKEN_BUCKET_WARNING_KEY_PREFIX = "tokenBucketWarningShownAt_";
  constant SEVERITY_DEDUP_HOURS (line 30) | const SEVERITY_DEDUP_HOURS: Record<string, number> = {
  function parseRateLimitWarning (line 42) | function parseRateLimitWarning(

FILE: lib/utils/pro-max-notice-cookie.ts
  constant COOKIE_NAME (line 1) | const COOKIE_NAME = "hackerai_pro_max_usage_ack";
  constant MAX_AGE_SEC (line 4) | const MAX_AGE_SEC = 60 * 60 * 24 * 365 * 5;

FILE: lib/utils/redis-pubsub.ts
  type RedisClient (line 4) | type RedisClient = ReturnType<typeof createClient>;

FILE: lib/utils/safe-wait-until.ts
  function safeWaitUntil (line 10) | function safeWaitUntil(promise: Promise<unknown>) {

FILE: lib/utils/sandbox-command.ts
  constant PRODUCTION_CONVEX_URL (line 2) | const PRODUCTION_CONVEX_URL = "https://convex.haiusercontent.com";

FILE: lib/utils/sandbox-file-utils.ts
  type SandboxFile (line 7) | type SandboxFile = {

FILE: lib/utils/scroll-events.ts
  constant STICKY_BOTTOM_ESCAPE_EVENT (line 1) | const STICKY_BOTTOM_ESCAPE_EVENT = "hackerai:escape-sticky-bottom";

FILE: lib/utils/settings-dialog.ts
  constant EVENT_NAME (line 1) | const EVENT_NAME = "open-settings-dialog";
  type OpenSettingsDetail (line 3) | interface OpenSettingsDetail {
  function openSettingsDialog (line 8) | function openSettingsDialog(tab?: string) {
  function onOpenSettingsDialog (line 15) | function onOpenSettingsDialog(

FILE: lib/utils/shiki.tsx
  constant SUPPORTED_LANGUAGES (line 5) | const SUPPORTED_LANGUAGES = new Set(
  type ShikiBoundaryProps (line 14) | interface ShikiBoundaryProps {
  type ShikiBoundaryState (line 19) | interface ShikiBoundaryState {
  class ShikiErrorBoundary (line 23) | class ShikiErrorBoundary extends Component<
    method getDerivedStateFromError (line 29) | static getDerivedStateFromError(error: Error) {
    method componentDidCatch (line 34) | componentDidCatch(error: Error) {
    method render (line 41) | render() {

FILE: lib/utils/sidebar-storage.ts
  constant STORAGE_KEYS (line 7) | const STORAGE_KEYS = {
  type StorageKey (line 12) | type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];

FILE: lib/utils/sidebar-utils.ts
  type MessagePart (line 13) | interface MessagePart {
  type Message (line 22) | interface Message {
  constant STREAMS_DURING_INPUT (line 32) | const STREAMS_DURING_INPUT = new Set<string>(["tool-file"]);
  function extractSidebarContentFromMessage (line 38) | function extractSidebarContentFromMessage(
  function extractAllSidebarContent (line 675) | function extractAllSidebarContent(

FILE: lib/utils/stream-cancellation.ts
  type RedisClient (line 13) | type RedisClient = ReturnType<typeof createClient>;
  type PollOptions (line 15) | type PollOptions = {
  type ApiEndpoint (line 23) | type ApiEndpoint = "/api/chat" | "/api/agent" | "/api/chat/[id]/stream";
  type PreemptiveTimeoutOptions (line 25) | type PreemptiveTimeoutOptions = {
  type CancellationSubscriberResult (line 32) | type CancellationSubscriberResult = {

FILE: lib/utils/stream-writer-utils.ts
  type RateLimitWarningData (line 96) | type RateLimitWarningData =

FILE: lib/utils/terminal-executor.ts
  type TerminalResult (line 11) | type TerminalResult = {
  constant MAX_FULL_OUTPUT_CHARS (line 20) | const MAX_FULL_OUTPUT_CHARS = 5 * 1024 * 1024;
  function truncateTerminalOutput (line 131) | function truncateTerminalOutput(output: string): TerminalResult {

FILE: lib/utils/todo-block-manager.ts
  type TodoBlockState (line 5) | interface TodoBlockState {
  type TodoBlockManager (line 10) | interface TodoBlockManager {

FILE: lib/utils/todo-utils.ts
  type TodoLike (line 60) | type TodoLike = {

FILE: middleware.ts
  constant UNAUTHENTICATED_PATHS (line 5) | const UNAUTHENTICATED_PATHS = new Set([
  function getRedirectUri (line 25) | function getRedirectUri(): string | undefined {
  function isDesktopApp (line 32) | function isDesktopApp(request: NextRequest): boolean {
  function isUnauthenticatedPath (line 37) | function isUnauthenticatedPath(pathname: string): boolean {
  function isBrowserRequest (line 47) | function isBrowserRequest(request: NextRequest): boolean {
  constant SESSION_HEADER (line 52) | const SESSION_HEADER = "x-workos-session";
  function middleware (line 54) | async function middleware(
  function buildRequestHeaders (line 138) | function buildRequestHeaders(
  function buildResponseHeaders (line 151) | function buildResponseHeaders(authkitHeaders: Headers): Headers {

FILE: packages/desktop/scripts/build.js
  constant APP_URL (line 5) | const APP_URL = process.env.APP_URL || "https://hackerai.co";

FILE: packages/desktop/scripts/generate-icons.mjs
  function createIcon (line 10) | async function createIcon(svgBuffer, size, filename) {
  function createIcns (line 18) | async function createIcns(svgBuffer) {
  function createIco (line 41) | async function createIco(svgBuffer) {
  function main (line 49) | async function main() {

FILE: packages/desktop/src-tauri/build.rs
  function main (line 1) | fn main() {

FILE: packages/desktop/src-tauri/src/lib.rs
  constant UPDATE_CHECK_INTERVAL (line 14) | const UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
  function get_dev_auth_port (line 27) | fn get_dev_auth_port() -> u16 {
  function get_cmd_server_info (line 33) | fn get_cmd_server_info() -> CmdServerInfo {
  type CmdServerInfo (line 41) | struct CmdServerInfo {
  type LocalFileMetadata (line 48) | struct LocalFileMetadata {
  type LocalFileData (line 58) | struct LocalFileData {
  function json_error_body (line 67) | fn json_error_body(message: &str) -> String {
  function json_stream_error_line (line 72) | fn json_stream_error_line(message: &str) -> String {
  function guess_media_type (line 80) | fn guess_media_type(path: &std::path::Path) -> String {
  function get_local_file_metadata (line 111) | fn get_local_file_metadata(path: String) -> Result<LocalFileMetadata, St...
  function read_local_file (line 140) | fn read_local_file(path: String) -> Result<LocalFileData, String> {
  type ExecRequest (line 159) | struct ExecRequest {
  function default_timeout (line 167) | fn default_timeout() -> u64 {
  type ExecResponse (line 172) | struct ExecResponse {
  function wait_with_output_or_kill_on_timeout (line 178) | async fn wait_with_output_or_kill_on_timeout(
  type FileReadRequest (line 262) | struct FileReadRequest {
  type FileWriteRequest (line 267) | struct FileWriteRequest {
  type FileRemoveRequest (line 275) | struct FileRemoveRequest {
  type FileListRequest (line 280) | struct FileListRequest {
  function start_cmd_server (line 286) | async fn start_cmd_server() {
  constant MAX_HEADER_SIZE (line 334) | const MAX_HEADER_SIZE: usize = 256 * 1024;
  constant MAX_BODY_SIZE (line 337) | const MAX_BODY_SIZE: usize = 10 * 1024 * 1024;
  function parse_http_request (line 340) | async fn parse_http_request(
  function handle_cmd_request (line 439) | async fn handle_cmd_request(
  function handle_execute (line 509) | async fn handle_execute(body: &str) -> Result<String, String> {
  function handle_execute_stream (line 558) | async fn handle_execute_stream(
  function write_chunk (line 662) | async fn write_chunk(stream: &mut tokio::net::TcpStream, data: &str) {
  function handle_file_read (line 673) | async fn handle_file_read(body: &str) -> Result<String, String> {
  function handle_file_write (line 682) | async fn handle_file_write(body: &str) -> Result<String, String> {
  function handle_file_remove (line 710) | async fn handle_file_remove(body: &str) -> Result<String, String> {
  function handle_file_list (line 728) | async fn handle_file_list(body: &str) -> Result<String, String> {
  type StreamEvent (line 752) | enum StreamEvent {
  type StreamCommandState (line 770) | type StreamCommandState = std::sync::Arc<std::sync::Mutex<HashMap<String...
  function execute_command (line 773) | async fn execute_command(
  function execute_stream_command (line 813) | async fn execute_stream_command(
  function cancel_stream_command (line 895) | async fn cancel_stream_command(
  function start_dev_auth_server (line 920) | async fn start_dev_auth_server(app_handle: tauri::AppHandle) {
  function get_last_update_check_file (line 1042) | fn get_last_update_check_file(app: &tauri::AppHandle) -> Option<PathBuf> {
  function should_check_for_updates (line 1049) | fn should_check_for_updates(app: &tauri::AppHandle) -> bool {
  function save_update_check_timestamp (line 1067) | fn save_update_check_timestamp(app: &tauri::AppHandle) {
  function get_allowed_hosts (line 1086) | fn get_allowed_hosts() -> Vec<String> {
  function is_valid_token_format (line 1093) | fn is_valid_token_format(token: &str) -> bool {
  function validate_origin (line 1097) | fn validate_origin(origin: &str) -> bool {
  function handle_auth_deep_link (line 1111) | fn handle_auth_deep_link(app: &tauri::AppHandle, url: &url::Url) {
  function check_for_updates (line 1177) | async fn check_for_updates(app: tauri::AppHandle, silent: bool) {
  type PtyState (line 1273) | type PtyState = std::sync::Arc<std::sync::Mutex<pty::PtyManager>>;
  function execute_pty_create (line 1276) | async fn execute_pty_create(
  function execute_pty_input (line 1291) | async fn execute_pty_input(
  function execute_pty_resize (line 1301) | async fn execute_pty_resize(
  function execute_pty_kill (line 1312) | async fn execute_pty_kill(
  function run (line 1321) | pub fn run() {

FILE: packages/desktop/src-tauri/src/main.rs
  function main (line 7) | fn main() {

FILE: packages/desktop/src-tauri/src/platform.rs
  function is_executable (line 3) | fn is_executable(path: &std::path::Path) -> bool {
  type ShellConfig (line 11) | pub struct ShellConfig {
  function get_shell_config (line 29) | pub fn get_shell_config() -> ShellConfig {
  function find_git_bash (line 81) | fn find_git_bash() -> Option<String> {
  function build_command (line 125) | pub fn build_command(
  function graceful_kill (line 183) | pub async fn graceful_kill(child: &mut tokio::process::Child) {
  function terminate_process_group (line 215) | fn terminate_process_group(pid: u32, signal: libc::c_int) {
  function cancel_process_tree (line 228) | pub async fn cancel_process_tree(pid: u32) {

FILE: packages/desktop/src-tauri/src/pty.rs
  constant OUTPUT_BUFFER_MAX_BYTES (line 11) | const OUTPUT_BUFFER_MAX_BYTES: usize = 32 * 1024;
  type PtySession (line 13) | struct PtySession {
  type PtyManager (line 20) | pub struct PtyManager {
    method new (line 31) | pub fn new() -> Self {
    method create (line 37) | pub fn create(
    method send_input (line 129) | pub fn send_input(&mut self, session_id: &str, data: &str) -> Result<(...
    method resize (line 148) | pub fn resize(&mut self, session_id: &str, cols: u16, rows: u16) -> Re...
    method kill (line 167) | pub fn kill(&mut self, session_id: &str) -> Result<(), String> {
    method stop_all (line 187) | pub fn stop_all(&mut self) {
  type PtyCreateResult (line 25) | pub struct PtyCreateResult {
  function pty_reader_thread (line 197) | fn pty_reader_thread(
  function session_not_found_err (line 249) | fn session_not_found_err(id: &str) -> String {
  function flush_buffer (line 253) | fn flush_buffer(on_data: &Channel<String>, buf: &mut Vec<u8>) {
  function send_exit (line 262) | fn send_exit(on_data: &Channel<String>, exit_code: i32, session_id: &str) {
  function get_default_shell (line 273) | fn get_default_shell() -> String {
  function get_shell_exec_flag (line 279) | fn get_shell_exec_flag(shell: &str) -> &'static str {

FILE: packages/local/src/index.ts
  constant DEFAULT_SHELL (line 32) | const DEFAULT_SHELL = getDefaultShell(os.platform());
  constant IDLE_TIMEOUT_MS (line 35) | const IDLE_TIMEOUT_MS = 60 * 60 * 1000;
  constant IDLE_CHECK_INTERVAL_MS (line 38) | const IDLE_CHECK_INTERVAL_MS = 5 * 60 * 1000;
  type ShellCommandResult (line 40) | interface ShellCommandResult {
  function runShellCommand (line 50) | function runShellCommand(
  constant PRODUCTION_CONVEX_URL (line 141) | const PRODUCTION_CONVEX_URL = "https://convex.haiusercontent.com";
  type Config (line 163) | interface Config {
  type OsInfo (line 169) | interface OsInfo {
  type CentrifugoCommandMessage (line 176) | interface CentrifugoCommandMessage {
  type CentrifugoCommandCancelMessage (line 188) | interface CentrifugoCommandCancelMessage {
  type CentrifugoStdoutMessage (line 194) | interface CentrifugoStdoutMessage {
  type CentrifugoStderrMessage (line 200) | interface CentrifugoStderrMessage {
  type CentrifugoExitMessage (line 206) | interface CentrifugoExitMessage {
  type CentrifugoErrorMessage (line 213) | interface CentrifugoErrorMessage {
  type PtyCreateMessage (line 221) | interface PtyCreateMessage {
  type PtyInputMessage (line 232) | interface PtyInputMessage {
  type PtyResizeMessage (line 239) | interface PtyResizeMessage {
  type PtyKillMessage (line 247) | interface PtyKillMessage {
  type CentrifugoPtyIncomingMessage (line 254) | type CentrifugoPtyIncomingMessage =
  type CentrifugoPtyReadyMessage (line 262) | interface CentrifugoPtyReadyMessage {
  type CentrifugoPtyDataMessage (line 268) | interface CentrifugoPtyDataMessage {
  type CentrifugoPtyExitMessage (line 274) | interface CentrifugoPtyExitMessage {
  type CentrifugoPtyErrorMessage (line 280) | interface CentrifugoPtyErrorMessage {
  type CentrifugoOutgoingMessage (line 286) | type CentrifugoOutgoingMessage =
  type ConnectResult (line 296) | interface ConnectResult {
  type RefreshTokenResult (line 305) | type RefreshTokenResult =
  function isInvalidTokenError (line 332) | function isInvalidTokenError(error: unknown): boolean {
  class LocalSandboxClient (line 339) | class LocalSandboxClient {
    method constructor (line 351) | constructor(private config: Config) {
    method setupProcessRunnerListeners (line 358) | private setupProcessRunnerListeners(): void {
    method start (line 408) | async start(): Promise<void> {
    method getOsInfo (line 418) | private getOsInfo(): OsInfo {
    method connect (line 427) | private async connect(): Promise<void> {
    method setupCentrifugo (line 474) | private setupCentrifugo(wsUrl: string, initialToken: string): void {
    method publishToChannel (line 640) | private async publishToChannel(
    method handleCommand (line 656) | private async handleCommand(msg: CentrifugoCommandMessage): Promise<vo...
    method handleCommandCancel (line 742) | private handleCommandCancel(msg: CentrifugoCommandCancelMessage): void {
    method terminateProcessTree (line 750) | private terminateProcessTree(proc: ChildProcess): void {
    method terminateActiveStreamCommands (line 783) | private terminateActiveStreamCommands(): void {
    method streamCommand (line 793) | private async streamCommand(
    method spawnBackground (line 947) | private async spawnBackground(fullCommand: string): Promise<number> {
    method handlePtyCreate (line 962) | private async handlePtyCreate(msg: PtyCreateMessage): Promise<void> {
    method handlePtyInput (line 1002) | private handlePtyInput(msg: PtyInputMessage): void {
    method handlePtyResize (line 1010) | private handlePtyResize(msg: PtyResizeMessage): void {
    method handlePtyKill (line 1020) | private handlePtyKill(msg: PtyKillMessage): void {
    method startIdleCheck (line 1033) | private startIdleCheck(): void {
    method stopIdleCheck (line 1049) | private stopIdleCheck(): void {
    method cleanup (line 1056) | async cleanup(): Promise<void> {

FILE: packages/local/src/process-runner.ts
  type ProcessRunOptions (line 11) | interface ProcessRunOptions {
  type ProcessRunResult (line 18) | interface ProcessRunResult {
  type ProcessRunnerEvents (line 22) | interface ProcessRunnerEvents {
  constant FLUSH_INTERVAL_MS (line 32) | const FLUSH_INTERVAL_MS = 16;
  constant FLUSH_THRESHOLD_BYTES (line 33) | const FLUSH_THRESHOLD_BYTES = 32 * 1024;
  constant SIGTERM_GRACE_MS (line 34) | const SIGTERM_GRACE_MS = 5_000;
  class ProcessRunner (line 40) | class ProcessRunner {
    method constructor (line 50) | constructor() {
    method on (line 59) | on<K extends keyof ProcessRunnerEvents>(
    method run (line 70) | run(
    method write (line 122) | write(sessionId: string, data: string): boolean {
    method resize (line 131) | resize(sessionId: string, cols: number, rows: number): boolean {
    method stop (line 140) | stop(sessionId: string, _signal?: string): boolean {
    method stopAll (line 160) | stopAll(): void {
    method isRunning (line 166) | isRunning(sessionId: string): boolean {
    method dispose (line 170) | dispose(): void {
    method emit (line 186) | private emit<K extends keyof ProcessRunnerEvents>(
    method flush (line 198) | private flush(sessionId: string): void {
    method flushAll (line 207) | private flushAll(): void {
    method clearKillTimer (line 213) | private clearKillTimer(sessionId: string): void {

FILE: packages/local/src/utils.ts
  constant MAX_OUTPUT_SIZE (line 11) | const MAX_OUTPUT_SIZE = 12288;
  constant TRUNCATION_MARKER (line 14) | const TRUNCATION_MARKER =
  function truncateOutput (line 21) | function truncateOutput(
  type ShellConfig (line 40) | interface ShellConfig {
  function getDefaultShell (line 50) | function getDefaultShell(platform: string): ShellConfig {
  function findGitBash (line 72) | function findGitBash(): string | null {
  function buildShellSpawn (line 109) | function buildShellSpawn(

FILE: scripts/accept-invitation.ts
  function acceptInvitationForUser (line 23) | async function acceptInvitationForUser(email: string) {

FILE: scripts/attach-failing-card.ts
  function attachFailingCard (line 48) | async function attachFailingCard(customerEmail: string) {

FILE: scripts/check-openrouter-gen-id.ts
  constant VALID_SLUG (line 27) | const VALID_SLUG = "google/gemini-3-flash-preview";
  constant INVALID_SLUG (line 28) | const INVALID_SLUG = "anthropic/this-model-does-not-exist-please-fail";
  function classify (line 30) | function classify(id: string | undefined): string {
  function probeSuccess (line 40) | async function probeSuccess(openrouter: ReturnType<typeof createOpenRout...
  function probeError (line 102) | async function probeError(openrouter: ReturnType<typeof createOpenRouter...
  function probeSimulated5xx (line 170) | async function probeSimulated5xx() {
  function main (line 208) | async function main() {

FILE: scripts/create-test-users.ts
  function deleteTestUsers (line 15) | async function deleteTestUsers() {
  function resetPasswords (line 53) | async function resetPasswords() {
  function createTestUsers (line 94) | async function createTestUsers() {
  function main (line 260) | async function main() {

FILE: scripts/reset-rate-limit.ts
  constant REDIS_URL (line 35) | const REDIS_URL = process.env.UPSTASH_REDIS_REST_URL;
  constant REDIS_TOKEN (line 36) | const REDIS_TOKEN = process.env.UPSTASH_REDIS_REST_TOKEN;
  type TestUserTier (line 38) | type TestUserTier = "free" | "pro" | "ultra";
  constant TEST_USERS (line 40) | const TEST_USERS = getTestUsersRecord();
  function getUserId (line 42) | async function getUserId(email: string): Promise<string | null> {
  function resetRateLimitForUser (line 59) | async function resetRateLimitForUser(
  function resetAllTestUsers (line 120) | async function resetAllTestUsers(): Promise<void> {
  function main (line 132) | async function main() {

FILE: scripts/setup.ts
  function question (line 11) | function question(query: string): Promise<string> {
  function getOpenRouterApiKey (line 25) | async function getOpenRouterApiKey(): Promise<string> {
  function getOpenAiApiKey (line 42) | async function getOpenAiApiKey(): Promise<string> {
  function getXaiApiKey (line 59) | async function getXaiApiKey(): Promise<string> {
  function getE2bApiKey (line 74) | async function getE2bApiKey(): Promise<string> {
  function getWorkOSApiKey (line 92) | async function getWorkOSApiKey(): Promise<string> {
  function getWorkOSClientId (line 110) | async function getWorkOSClientId(): Promise<string> {
  function generateWorkOSCookiePassword (line 118) | function generateWorkOSCookiePassword(): string {
  function generateConvexServiceRoleKey (line 126) | function generateConvexServiceRoleKey(): string {
  function configureWorkOSDashboard (line 132) | async function configureWorkOSDashboard() {
  function configureConvexDashboard (line 145) | async function configureConvexDashboard(
  function writeEnvFile (line 168) | async function writeEnvFile(envVars: Record<string, string>) {
  function setupConvex (line 287) | async function setupConvex(): Promise<{
  function main (line 401) | async function main() {

FILE: scripts/test-users-config.ts
  type TestUserTier (line 8) | type TestUserTier = "free" | "pro" | "ultra";
  type TestUser (line 10) | interface TestUser {
  constant DEFAULTS (line 16) | const DEFAULTS = {
  function getTestUsers (line 34) | function getTestUsers(): TestUser[] {
  function getTestUsersRecord (line 57) | function getTestUsersRecord(): Record<TestUserTier, TestUser> {

FILE: scripts/validate-s3-security.ts
  type ValidationResult (line 34) | interface ValidationResult {
  function logResult (line 46) | function logResult(result: ValidationResult): void {
  function validateEnvironmentVariables (line 59) | function validateEnvironmentVariables(): ValidationResult {
  function validateS3Client (line 96) | async function validateS3Client(): Promise<ValidationResult> {
  function validateBucketEncryption (line 133) | async function validateBucketEncryption(): Promise<ValidationResult> {
  function validatePublicAccessBlock (line 198) | async function validatePublicAccessBlock(): Promise<ValidationResult> {
  function validateCorsConfiguration (line 263) | async function validateCorsConfiguration(): Promise<ValidationResult> {
  function validatePresignedUploadUrl (line 339) | async function validatePresignedUploadUrl(): Promise<ValidationResult> {
  function validatePresignedDownloadUrl (line 395) | async function validatePresignedDownloadUrl(): Promise<ValidationResult> {
  function validateIamPermissions (line 450) | function validateIamPermissions(): ValidationResult {
  function main (line 465) | async function main() {

FILE: scripts/verify-email.ts
  function verifyUserEmail (line 28) | async function verifyUserEmail(email: string) {

FILE: scripts/verify-test-users.ts
  function verifyTestUsers (line 15) | async function verifyTestUsers() {

FILE: trigger/agent-long.ts
  constant AGENT_LONG_MAX_DURATION_MS (line 101) | const AGENT_LONG_MAX_DURATION_MS = 58 * 60 * 1000;
  type AgentLongUiStreamPart (line 103) | type AgentLongUiStreamPart = Parameters<UIMessageStreamWriter["write"]>[0];
  constant MAX_TRIGGER_ERROR_MESSAGE_LENGTH (line 105) | const MAX_TRIGGER_ERROR_MESSAGE_LENGTH = 500;
  constant OPERATIONAL_RATE_LIMIT_CAUSE_PATTERNS (line 131) | const OPERATIONAL_RATE_LIMIT_CAUSE_PATTERNS = [
  type AgentLongErrorSummary (line 137) | type AgentLongErrorSummary = {
  method start (line 319) | start(controller) {
  method cancel (line 385) | cancel(reason) {
  type RunCleanupState (line 393) | type RunCleanupState = {
  type AgentLongPayload (line 400) | type AgentLongPayload = {

FILE: trigger/stream-ids.ts
  constant AGENT_UI_STREAM_ID (line 3) | const AGENT_UI_STREAM_ID = "ui" as const;

FILE: types/agent.ts
  type AnySandbox (line 13) | type AnySandbox = Sandbox | CentrifugoSandbox;
  type IsE2BSandboxFn (line 16) | type IsE2BSandboxFn = (s: AnySandbox | null) => s is Sandbox;
  type SandboxType (line 18) | type SandboxType = "e2b" | "desktop" | "remote-connection";
  type SandboxInfo (line 20) | interface SandboxInfo {
  type SandboxManager (line 25) | interface SandboxManager {
  type SandboxBootInfo (line 42) | interface SandboxBootInfo {
  type CaidoErrorKind (line 53) | type CaidoErrorKind =
  type CaidoReadyInfo (line 61) | interface CaidoReadyInfo {
  type SandboxContext (line 85) | interface SandboxContext {
  type AppendMetadataStreamFn (line 93) | type AppendMetadataStreamFn = (event: {
  type ToolContext (line 98) | interface ToolContext {

FILE: types/chat.ts
  type ChatMode (line 6) | type ChatMode = "agent" | "ask";
  constant CHAT_MODES (line 8) | const CHAT_MODES: readonly ChatMode[] = ["agent", "ask"];
  function isChatMode (line 10) | function isChatMode(value: string | null): value is ChatMode {
  type SelectedModel (line 14) | type SelectedModel =
  constant SELECTABLE_MODELS (line 20) | const SELECTABLE_MODELS: readonly SelectedModel[] = [
  constant LEGACY_MODEL_ID_MAP (line 35) | const LEGACY_MODEL_ID_MAP: Record<string, SelectedModel> = {
  function coerceSelectedModel (line 53) | function coerceSelectedModel(
  function isSelectedModel (line 69) | function isSelectedModel(value: string | null): value is SelectedModel {
  type SubscriptionTier (line 75) | type SubscriptionTier = "free" | "pro" | "pro-plus" | "ultra" | "team";
  constant SUBSCRIPTION_TIERS (line 77) | const SUBSCRIPTION_TIERS: readonly SubscriptionTier[] = [
  function isSubscriptionTier (line 85) | function isSubscriptionTier(value: unknown): value is SubscriptionTier {
  type SidebarFile (line 92) | interface SidebarFile {
  type SidebarTerminal (line 118) | interface SidebarTerminal {
  type SidebarProxy (line 137) | interface SidebarProxy {
  type WebSearchResult (line 146) | interface WebSearchResult {
  type SidebarWebSearch (line 154) | interface SidebarWebSearch {
  constant VALID_NOTE_CATEGORIES (line 161) | const VALID_NOTE_CATEGORIES = [
  type NoteCategory (line 169) | type NoteCategory = (typeof VALID_NOTE_CATEGORIES)[number];
  type SidebarNote (line 171) | interface SidebarNote {
  type SidebarNotes (line 180) | interface SidebarNotes {
  type SidebarSharedFiles (line 206) | interface SidebarSharedFiles {
  type SidebarContent (line 219) | type SidebarContent =
  type Todo (line 263) | interface Todo {
  type TodoBlockProps (line 270) | interface TodoBlockProps {
  type TodoWriteInput (line 277) | interface TodoWriteInput {
  type ChatStatus (line 282) | type ChatStatus = "submitted" | "streaming" | "ready" | "error";
  type MessageMetadata (line 292) | type MessageMetadata = z.infer<typeof messageMetadataSchema>;
  type ChatMessage (line 294) | type ChatMessage = UIMessage<MessageMetadata> & {
  type RateLimitInfo (line 299) | type RateLimitInfo = {
  type ExtraUsageConfig (line 313) | interface ExtraUsageConfig {
  type QueuedMessage (line 323) | interface QueuedMessage {
  type QueueBehavior (line 330) | type QueueBehavior = "queue" | "stop-and-send";
  type SandboxPreference (line 334) | type SandboxPreference = "e2b" | "desktop" | (string & {});
  type PreviewMessage (line 339) | interface PreviewMessage {
  type SharedChat (line 350) | interface SharedChat {

FILE: types/file.ts
  type FileMessagePart (line 3) | interface FileMessagePart {
  type LocalDesktopFile (line 21) | interface LocalDesktopFile {
  type UploadedFileState (line 28) | interface UploadedFileState {
  type FilePart (line 42) | interface FilePart {
  type FilePartRendererProps (line 55) | interface FilePartRendererProps {
  type FileUploadPreviewProps (line 63) | interface FileUploadPreviewProps {
  type FilePreview (line 68) | interface FilePreview {
  type FileProcessingResult (line 78) | type FileProcessingResult = {
  type FileSource (line 85) | type FileSource = "upload" | "paste" | "drop";
  type FileItemChunk (line 88) | interface FileItemChunk {
  type SupportedFileType (line 94) | type SupportedFileType = "pdf" | "csv" | "json" | "txt" | "md" | "docx";
  type ProcessFileOptions (line 96) | interface ProcessFileOptions {
  type FileDetails (line 103) | interface FileDetails {
  type FileContent (line 116) | interface FileContent {

FILE: types/user.ts
  type UserCustomization (line 1) | interface UserCustomization {
  type PersonalityType (line 15) | type PersonalityType = "cynic" | "robot" | "listener" | "nerd";
Condensed preview — 772 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,160K chars).
[
  {
    "path": ".agents/skills/logging-best-practices/SKILL.md",
    "chars": 4153,
    "preview": "---\nname: logging-best-practices\ndescription: Use before implementing logs in a medium to large scale production system."
  },
  {
    "path": ".agents/skills/trigger-agents/SKILL.md",
    "chars": 7996,
    "preview": "---\nname: trigger-agents\ndescription: AI agent patterns with Trigger.dev - orchestration, parallelization, routing, eval"
  },
  {
    "path": ".agents/skills/trigger-agents/references/ai-tool.md",
    "chars": 5246,
    "preview": "# ai.tool Integration\n\nConvert Trigger.dev tasks to Vercel AI SDK tools. Let LLMs call your tasks autonomously.\n\n## Basi"
  },
  {
    "path": ".agents/skills/trigger-agents/references/orchestration.md",
    "chars": 5797,
    "preview": "# Orchestration Patterns\n\nAdvanced patterns for `batch.triggerByTaskAndWait` and task coordination.\n\n## Basic Usage\n\n```"
  },
  {
    "path": ".agents/skills/trigger-agents/references/streaming.md",
    "chars": 5554,
    "preview": "# Realtime Streams\n\nStream data from tasks to your frontend in real-time. Perfect for AI completions, progress updates, "
  },
  {
    "path": ".agents/skills/trigger-agents/references/waitpoints.md",
    "chars": 5561,
    "preview": "# Human-in-the-Loop with Waitpoints\n\nPause task execution for human approval, external callbacks, or async events.\n\n## C"
  },
  {
    "path": ".agents/skills/trigger-config/SKILL.md",
    "chars": 4704,
    "preview": "---\nname: trigger-config\ndescription: Configure Trigger.dev projects with trigger.config.ts. Use when setting up build e"
  },
  {
    "path": ".agents/skills/trigger-config/references/config.md",
    "chars": 7742,
    "preview": "# Trigger.dev Configuration\n\n**Complete guide to configuring `trigger.config.ts` with build extensions**\n\n## Basic Confi"
  },
  {
    "path": ".agents/skills/trigger-cost-savings/SKILL.md",
    "chars": 5391,
    "preview": "---\nname: trigger-cost-savings\ndescription: Analyze Trigger.dev tasks, schedules, and runs for cost optimization opportu"
  },
  {
    "path": ".agents/skills/trigger-cost-savings/references/cost-reduction.md",
    "chars": 5585,
    "preview": "# Cost Reduction Strategies\n\nDetailed strategies for reducing Trigger.dev spend. For the latest version, fetch:\nhttps://"
  },
  {
    "path": ".agents/skills/trigger-realtime/SKILL.md",
    "chars": 6793,
    "preview": "---\nname: trigger-realtime\ndescription: Subscribe to Trigger.dev task runs in real-time from frontend and backend. Use w"
  },
  {
    "path": ".agents/skills/trigger-realtime/references/realtime.md",
    "chars": 6245,
    "preview": "# Trigger.dev Realtime\n\n**Real-time monitoring and updates for runs**\n\n## Core Concepts\n\nRealtime allows you to:\n\n- Subs"
  },
  {
    "path": ".agents/skills/trigger-setup/SKILL.md",
    "chars": 2657,
    "preview": "---\nname: trigger-setup\ndescription: Set up Trigger.dev in your project. Use when adding Trigger.dev for the first time,"
  },
  {
    "path": ".agents/skills/trigger-setup/references/environment-setup.md",
    "chars": 1944,
    "preview": "# Environment Setup\n\n## Required Variables\n\n| Variable | Description | Where to find |\n|----------|-------------|-------"
  },
  {
    "path": ".agents/skills/trigger-setup/references/project-structure.md",
    "chars": 1808,
    "preview": "# Project Structure\n\n## Default Layout\n\n```\nyour-project/\n├── trigger.config.ts    # Required - project configuration\n├─"
  },
  {
    "path": ".agents/skills/trigger-tasks/SKILL.md",
    "chars": 8218,
    "preview": "---\nname: trigger-tasks\ndescription: Build AI agents, workflows and durable background tasks with Trigger.dev. Use when "
  },
  {
    "path": ".agents/skills/trigger-tasks/references/advanced-tasks.md",
    "chars": 12351,
    "preview": "# Trigger.dev Advanced Tasks (v4)\n\n**Advanced patterns and features for writing tasks**\n\n## Tags & Organization\n\n```ts\ni"
  },
  {
    "path": ".agents/skills/trigger-tasks/references/basic-tasks.md",
    "chars": 5111,
    "preview": "# Trigger.dev Basic Tasks (v4)\n\n**MUST use `@trigger.dev/sdk`, NEVER `client.defineJob`**\n\n## Basic Task\n\n```ts\nimport {"
  },
  {
    "path": ".agents/skills/trigger-tasks/references/scheduled-tasks.md",
    "chars": 2635,
    "preview": "# Scheduled Tasks (Cron)\n\nRecurring tasks using cron. For one-off future runs, use the **delay** option.\n\n## Define a Sc"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/AGENTS.md",
    "chars": 22604,
    "preview": "# React Composition Patterns\n\n**Version 1.0.0**  \nEngineering  \nJanuary 2026\n\n> **Note:**  \n> This document is mainly fo"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/SKILL.md",
    "chars": 2882,
    "preview": "---\nname: vercel-composition-patterns\ndescription:\n  React composition patterns that scale. Use when refactoring compone"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md",
    "chars": 2267,
    "preview": "---\ntitle: Avoid Boolean Prop Proliferation\nimpact: CRITICAL\nimpactDescription: prevents unmaintainable component varian"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md",
    "chars": 2600,
    "preview": "---\ntitle: Use Compound Components\nimpact: HIGH\nimpactDescription: enables flexible composition without prop drilling\nta"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md",
    "chars": 1886,
    "preview": "---\ntitle: Prefer Composing Children Over Render Props\nimpact: MEDIUM\nimpactDescription: cleaner composition, better rea"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/patterns-explicit-variants.md",
    "chars": 2395,
    "preview": "---\ntitle: Create Explicit Component Variants\nimpact: MEDIUM\nimpactDescription: self-documenting code, no hidden conditi"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/react19-no-forwardref.md",
    "chars": 949,
    "preview": "---\ntitle: React 19 API Changes\nimpact: MEDIUM\nimpactDescription: cleaner component definitions and context usage\ntags: "
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/state-context-interface.md",
    "chars": 4970,
    "preview": "---\ntitle: Define Generic Context Interfaces for Dependency Injection\nimpact: HIGH\nimpactDescription: enables dependency"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/state-decouple-implementation.md",
    "chars": 2697,
    "preview": "---\ntitle: Decouple State Management from UI\nimpact: MEDIUM\nimpactDescription: enables swapping state implementations wi"
  },
  {
    "path": ".agents/skills/vercel-composition-patterns/rules/state-lift-state.md",
    "chars": 3219,
    "preview": "---\ntitle: Lift State into Provider Components\nimpact: HIGH\nimpactDescription: enables state sharing outside component b"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/AGENTS.md",
    "chars": 81669,
    "preview": "# React Best Practices\n\n**Version 1.0.0**  \nVercel Engineering  \nJanuary 2026\n\n> **Note:**  \n> This document is mainly f"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/SKILL.md",
    "chars": 6165,
    "preview": "---\nname: vercel-react-best-practices\ndescription: React and Next.js performance optimization guidelines from Vercel Eng"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md",
    "chars": 1483,
    "preview": "---\ntitle: Store Event Handlers in Refs\nimpact: LOW\nimpactDescription: stable subscriptions\ntags: advanced, hooks, refs,"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/advanced-init-once.md",
    "chars": 958,
    "preview": "---\ntitle: Initialize App Once, Not Per Mount\nimpact: LOW-MEDIUM\nimpactDescription: avoids duplicate init in development"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md",
    "chars": 1072,
    "preview": "---\ntitle: useEffectEvent for Stable Callback Refs\nimpact: LOW\nimpactDescription: prevents effect re-runs\ntags: advanced"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-api-routes.md",
    "chars": 1124,
    "preview": "---\ntitle: Prevent Waterfall Chains in API Routes\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: api-routes"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-defer-await.md",
    "chars": 2028,
    "preview": "---\ntitle: Defer Await Until Needed\nimpact: HIGH\nimpactDescription: avoids blocking unused code paths\ntags: async, await"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-dependencies.md",
    "chars": 1292,
    "preview": "---\ntitle: Dependency-Based Parallelization\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, paralleli"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-parallel.md",
    "chars": 653,
    "preview": "---\ntitle: Promise.all() for Independent Operations\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, p"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md",
    "chars": 2508,
    "preview": "---\ntitle: Strategic Suspense Boundaries\nimpact: HIGH\nimpactDescription: faster initial paint\ntags: async, suspense, str"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md",
    "chars": 2370,
    "preview": "---\ntitle: Avoid Barrel File Imports\nimpact: CRITICAL\nimpactDescription: 200-800ms import cost, slow builds\ntags: bundle"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-conditional.md",
    "chars": 949,
    "preview": "---\ntitle: Conditional Module Loading\nimpact: HIGH\nimpactDescription: loads large data only when needed\ntags: bundle, co"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md",
    "chars": 920,
    "preview": "---\ntitle: Defer Non-Critical Third-Party Libraries\nimpact: MEDIUM\nimpactDescription: loads after hydration\ntags: bundle"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md",
    "chars": 791,
    "preview": "---\ntitle: Dynamic Imports for Heavy Components\nimpact: CRITICAL\nimpactDescription: directly affects TTI and LCP\ntags: b"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/bundle-preload.md",
    "chars": 1149,
    "preview": "---\ntitle: Preload Based on User Intent\nimpact: MEDIUM\nimpactDescription: reduces perceived latency\ntags: bundle, preloa"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/client-event-listeners.md",
    "chars": 1969,
    "preview": "---\ntitle: Deduplicate Global Event Listeners\nimpact: LOW\nimpactDescription: single listener for N components\ntags: clie"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md",
    "chars": 1950,
    "preview": "---\ntitle: Version and Minimize localStorage Data\nimpact: MEDIUM\nimpactDescription: prevents schema conflicts, reduces s"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md",
    "chars": 1644,
    "preview": "---\ntitle: Use Passive Event Listeners for Scrolling Performance\nimpact: MEDIUM\nimpactDescription: eliminates scroll del"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md",
    "chars": 1159,
    "preview": "---\ntitle: Use SWR for Automatic Deduplication\nimpact: MEDIUM-HIGH\nimpactDescription: automatic deduplication\ntags: clie"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md",
    "chars": 3266,
    "preview": "---\ntitle: Avoid Layout Thrashing\nimpact: MEDIUM\nimpactDescription: prevents forced synchronous layouts and reduces perf"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md",
    "chars": 1949,
    "preview": "---\ntitle: Cache Repeated Function Calls\nimpact: MEDIUM\nimpactDescription: avoid redundant computation\ntags: javascript,"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md",
    "chars": 531,
    "preview": "---\ntitle: Cache Property Access in Loops\nimpact: LOW-MEDIUM\nimpactDescription: reduces lookups\ntags: javascript, loops,"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-cache-storage.md",
    "chars": 1651,
    "preview": "---\ntitle: Cache Storage API Calls\nimpact: LOW-MEDIUM\nimpactDescription: reduces expensive I/O\ntags: javascript, localSt"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md",
    "chars": 753,
    "preview": "---\ntitle: Combine Multiple Array Iterations\nimpact: LOW-MEDIUM\nimpactDescription: reduces iterations\ntags: javascript, "
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-early-exit.md",
    "chars": 1133,
    "preview": "---\ntitle: Early Return from Functions\nimpact: LOW-MEDIUM\nimpactDescription: avoids unnecessary computation\ntags: javasc"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md",
    "chars": 1028,
    "preview": "---\ntitle: Hoist RegExp Creation\nimpact: LOW-MEDIUM\nimpactDescription: avoids recreation\ntags: javascript, regexp, optim"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-index-maps.md",
    "chars": 834,
    "preview": "---\ntitle: Build Index Maps for Repeated Lookups\nimpact: LOW-MEDIUM\nimpactDescription: 1M ops to 2K ops\ntags: javascript"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-length-check-first.md",
    "chars": 1747,
    "preview": "---\ntitle: Early Length Check for Array Comparisons\nimpact: MEDIUM-HIGH\nimpactDescription: avoids expensive operations w"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md",
    "chars": 2290,
    "preview": "---\ntitle: Use Loop for Min/Max Instead of Sort\nimpact: LOW\nimpactDescription: O(n) instead of O(n log n)\ntags: javascri"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md",
    "chars": 532,
    "preview": "---\ntitle: Use Set/Map for O(1) Lookups\nimpact: LOW-MEDIUM\nimpactDescription: O(n) to O(1)\ntags: javascript, set, map, d"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md",
    "chars": 1782,
    "preview": "---\ntitle: Use toSorted() Instead of sort() for Immutability\nimpact: MEDIUM-HIGH\nimpactDescription: prevents mutation bu"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-activity.md",
    "chars": 564,
    "preview": "---\ntitle: Use Activity Component for Show/Hide\nimpact: MEDIUM\nimpactDescription: preserves state/DOM\ntags: rendering, a"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md",
    "chars": 1185,
    "preview": "---\ntitle: Animate SVG Wrapper Instead of SVG Element\nimpact: LOW\nimpactDescription: enables hardware acceleration\ntags:"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md",
    "chars": 980,
    "preview": "---\ntitle: Use Explicit Conditional Rendering\nimpact: LOW\nimpactDescription: prevents rendering 0 or NaN\ntags: rendering"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md",
    "chars": 814,
    "preview": "---\ntitle: CSS content-visibility for Long Lists\nimpact: HIGH\nimpactDescription: faster initial render\ntags: rendering, "
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md",
    "chars": 1039,
    "preview": "---\ntitle: Hoist Static JSX Elements\nimpact: LOW\nimpactDescription: avoids re-creation\ntags: rendering, jsx, static, opt"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md",
    "chars": 2308,
    "preview": "---\ntitle: Prevent Hydration Mismatch Without Flickering\nimpact: MEDIUM\nimpactDescription: avoids visual flicker and hyd"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md",
    "chars": 870,
    "preview": "---\ntitle: Suppress Expected Hydration Mismatches\nimpact: LOW-MEDIUM\nimpactDescription: avoids noisy hydration warnings "
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md",
    "chars": 588,
    "preview": "---\ntitle: Optimize SVG Precision\nimpact: LOW\nimpactDescription: reduces file size\ntags: rendering, svg, optimization, s"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md",
    "chars": 2074,
    "preview": "---\ntitle: Use useTransition Over Manual Loading States\nimpact: LOW\nimpactDescription: reduces re-renders and improves c"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md",
    "chars": 973,
    "preview": "---\ntitle: Defer State Reads to Usage Point\nimpact: MEDIUM\nimpactDescription: avoids unnecessary subscriptions\ntags: rer"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md",
    "chars": 824,
    "preview": "---\ntitle: Narrow Effect Dependencies\nimpact: LOW\nimpactDescription: minimizes effect re-runs\ntags: rerender, useEffect,"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md",
    "chars": 1201,
    "preview": "---\ntitle: Calculate Derived State During Rendering\nimpact: MEDIUM\nimpactDescription: avoids redundant renders and state"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md",
    "chars": 728,
    "preview": "---\ntitle: Subscribe to Derived State\nimpact: MEDIUM\nimpactDescription: reduces re-render frequency\ntags: rerender, deri"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md",
    "chars": 2958,
    "preview": "---\ntitle: Use Functional setState Updates\nimpact: MEDIUM\nimpactDescription: prevents stale closures and unnecessary cal"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md",
    "chars": 2016,
    "preview": "---\ntitle: Use Lazy State Initialization\nimpact: MEDIUM\nimpactDescription: wasted computation on every render\ntags: reac"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md",
    "chars": 1173,
    "preview": "---\n\ntitle: Extract Default Non-primitive Parameter Value from Memoized Component to Constant\nimpact: MEDIUM\nimpactDescr"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-memo.md",
    "chars": 1148,
    "preview": "---\ntitle: Extract to Memoized Components\nimpact: MEDIUM\nimpactDescription: enables early returns\ntags: rerender, memo, "
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md",
    "chars": 1268,
    "preview": "---\ntitle: Put Interaction Logic in Event Handlers\nimpact: MEDIUM\nimpactDescription: avoids effect re-runs and duplicate"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md",
    "chars": 1018,
    "preview": "---\ntitle: Do not wrap a simple expression with a primitive result type in useMemo\nimpact: LOW-MEDIUM\nimpactDescription:"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-transitions.md",
    "chars": 1055,
    "preview": "---\ntitle: Use Transitions for Non-Urgent Updates\nimpact: MEDIUM\nimpactDescription: maintains UI responsiveness\ntags: re"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md",
    "chars": 1742,
    "preview": "---\ntitle: Use useRef for Transient Values\nimpact: MEDIUM\nimpactDescription: avoids unnecessary re-renders on frequent u"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md",
    "chars": 2012,
    "preview": "---\ntitle: Use after() for Non-Blocking Operations\nimpact: MEDIUM\nimpactDescription: faster response times\ntags: server,"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-auth-actions.md",
    "chars": 2647,
    "preview": "---\ntitle: Authenticate Server Actions Like API Routes\nimpact: CRITICAL\nimpactDescription: prevents unauthorized access "
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-cache-lru.md",
    "chars": 1353,
    "preview": "---\ntitle: Cross-Request LRU Caching\nimpact: HIGH\nimpactDescription: caches across requests\ntags: server, cache, lru, cr"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-cache-react.md",
    "chars": 2228,
    "preview": "---\ntitle: Per-Request Deduplication with React.cache()\nimpact: MEDIUM\nimpactDescription: deduplicates within request\nta"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-dedup-props.md",
    "chars": 2053,
    "preview": "---\ntitle: Avoid Duplicate Serialization in RSC Props\nimpact: LOW\nimpactDescription: reduces network payload by avoiding"
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md",
    "chars": 1554,
    "preview": "---\ntitle: Parallel Data Fetching with Component Composition\nimpact: CRITICAL\nimpactDescription: eliminates server-side "
  },
  {
    "path": ".agents/skills/vercel-react-best-practices/rules/server-serialization.md",
    "chars": 996,
    "preview": "---\ntitle: Minimize Serialization at RSC Boundaries\nimpact: HIGH\nimpactDescription: reduces data transfer size\ntags: ser"
  },
  {
    "path": ".agents/skills/web-design-guidelines/SKILL.md",
    "chars": 1231,
    "preview": "---\nname: web-design-guidelines\ndescription: Review UI code for Web Interface Guidelines compliance. Use when asked to \""
  },
  {
    "path": ".claude/agents/trigger-dev-task-writer.md",
    "chars": 11970,
    "preview": "---\nname: trigger-dev-expert\ndescription: Use this agent when you need to design, implement, or optimize background jobs"
  },
  {
    "path": ".cursor/rules/convex_rules.mdc",
    "chars": 19834,
    "preview": "---\ndescription: Guidelines and best practices for building Convex projects, including database schema design, queries, "
  },
  {
    "path": ".cursor/rules/trigger.advanced-tasks.mdc",
    "chars": 12482,
    "preview": "---\ndescription: Comprehensive rules to help you write advanced Trigger.dev tasks\nglobs: **/trigger/**/*.ts\nalwaysApply:"
  },
  {
    "path": ".cursor/rules/trigger.basic.mdc",
    "chars": 5243,
    "preview": "---\ndescription: Only the most important rules for writing basic Trigger.dev tasks\nglobs: **/trigger/**/*.ts\nalwaysApply"
  },
  {
    "path": ".cursor/rules/trigger.config.mdc",
    "chars": 7880,
    "preview": "---\ndescription: Configure your Trigger.dev project with a trigger.config.ts file\nglobs: **/trigger.config.ts\nalwaysAppl"
  },
  {
    "path": ".cursor/rules/trigger.realtime.mdc",
    "chars": 6972,
    "preview": "---\ndescription: How to use realtime in your Trigger.dev tasks and your frontend\nglobs: **/trigger/**/*.ts\nalwaysApply: "
  },
  {
    "path": ".cursor/rules/trigger.scheduled-tasks.mdc",
    "chars": 2936,
    "preview": "---\ndescription: How to write and use scheduled Trigger.dev tasks\nglobs: **/trigger/**/*.ts\nalwaysApply: false\n---\n# Sch"
  },
  {
    "path": ".env.e2e.example",
    "chars": 929,
    "preview": "# E2E Test Environment Variables\n\n# Base URL for Playwright tests\nPLAYWRIGHT_BASE_URL=http://localhost:3000\n\n# Test user"
  },
  {
    "path": ".env.local.example",
    "chars": 5721,
    "preview": "# =============================================================================\n# AUTHENTICATION - WorkOS (Required)\n# ="
  },
  {
    "path": ".github/skills/vercel-react-best-practices/AGENTS.md",
    "chars": 60499,
    "preview": "# React Best Practices\n\n**Version 0.1.0**  \nVercel Engineering  \nJanuary 2026\n\n> **Note:**  \n> This document is mainly f"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/README.md",
    "chars": 3360,
    "preview": "# React Best Practices\n\nA structured repository for creating and maintaining React Best Practices optimized for agents a"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/SKILL.md",
    "chars": 5257,
    "preview": "---\nname: vercel-react-best-practices\ndescription: React and Next.js performance optimization guidelines from Vercel Eng"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/metadata.json",
    "chars": 921,
    "preview": "{\n  \"version\": \"0.1.0\",\n  \"organization\": \"Vercel Engineering\",\n  \"date\": \"January 2026\",\n  \"abstract\": \"Comprehensive p"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/_sections.md",
    "chars": 1554,
    "preview": "# Sections\n\nThis file defines all sections, their ordering, impact levels, and descriptions.\nThe section ID (in parenthe"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/_template.md",
    "chars": 631,
    "preview": "---\ntitle: Rule Title Here\nimpact: MEDIUM\nimpactDescription: Optional description of impact (e.g., \"20-50% improvement\")"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md",
    "chars": 1478,
    "preview": "---\ntitle: Store Event Handlers in Refs\nimpact: LOW\nimpactDescription: stable subscriptions\ntags: advanced, hooks, refs,"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/advanced-use-latest.md",
    "chars": 1191,
    "preview": "---\ntitle: useLatest for Stable Callback Refs\nimpact: LOW\nimpactDescription: prevents effect re-runs\ntags: advanced, hoo"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/async-api-routes.md",
    "chars": 1124,
    "preview": "---\ntitle: Prevent Waterfall Chains in API Routes\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: api-routes"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/async-defer-await.md",
    "chars": 2028,
    "preview": "---\ntitle: Defer Await Until Needed\nimpact: HIGH\nimpactDescription: avoids blocking unused code paths\ntags: async, await"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/async-dependencies.md",
    "chars": 941,
    "preview": "---\ntitle: Dependency-Based Parallelization\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, paralleli"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/async-parallel.md",
    "chars": 653,
    "preview": "---\ntitle: Promise.all() for Independent Operations\nimpact: CRITICAL\nimpactDescription: 2-10× improvement\ntags: async, p"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md",
    "chars": 2508,
    "preview": "---\ntitle: Strategic Suspense Boundaries\nimpact: HIGH\nimpactDescription: faster initial paint\ntags: async, suspense, str"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md",
    "chars": 2370,
    "preview": "---\ntitle: Avoid Barrel File Imports\nimpact: CRITICAL\nimpactDescription: 200-800ms import cost, slow builds\ntags: bundle"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/bundle-conditional.md",
    "chars": 866,
    "preview": "---\ntitle: Conditional Module Loading\nimpact: HIGH\nimpactDescription: loads large data only when needed\ntags: bundle, co"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md",
    "chars": 920,
    "preview": "---\ntitle: Defer Non-Critical Third-Party Libraries\nimpact: MEDIUM\nimpactDescription: loads after hydration\ntags: bundle"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md",
    "chars": 791,
    "preview": "---\ntitle: Dynamic Imports for Heavy Components\nimpact: CRITICAL\nimpactDescription: directly affects TTI and LCP\ntags: b"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/bundle-preload.md",
    "chars": 1149,
    "preview": "---\ntitle: Preload Based on User Intent\nimpact: MEDIUM\nimpactDescription: reduces perceived latency\ntags: bundle, preloa"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/client-event-listeners.md",
    "chars": 1969,
    "preview": "---\ntitle: Deduplicate Global Event Listeners\nimpact: LOW\nimpactDescription: single listener for N components\ntags: clie"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/client-swr-dedup.md",
    "chars": 1159,
    "preview": "---\ntitle: Use SWR for Automatic Deduplication\nimpact: MEDIUM-HIGH\nimpactDescription: automatic deduplication\ntags: clie"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-batch-dom-css.md",
    "chars": 1902,
    "preview": "---\ntitle: Batch DOM CSS Changes\nimpact: MEDIUM\nimpactDescription: reduces reflows/repaints\ntags: javascript, dom, css, "
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-cache-function-results.md",
    "chars": 1949,
    "preview": "---\ntitle: Cache Repeated Function Calls\nimpact: MEDIUM\nimpactDescription: avoid redundant computation\ntags: javascript,"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-cache-property-access.md",
    "chars": 531,
    "preview": "---\ntitle: Cache Property Access in Loops\nimpact: LOW-MEDIUM\nimpactDescription: reduces lookups\ntags: javascript, loops,"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-cache-storage.md",
    "chars": 1651,
    "preview": "---\ntitle: Cache Storage API Calls\nimpact: LOW-MEDIUM\nimpactDescription: reduces expensive I/O\ntags: javascript, localSt"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-combine-iterations.md",
    "chars": 753,
    "preview": "---\ntitle: Combine Multiple Array Iterations\nimpact: LOW-MEDIUM\nimpactDescription: reduces iterations\ntags: javascript, "
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-early-exit.md",
    "chars": 1133,
    "preview": "---\ntitle: Early Return from Functions\nimpact: LOW-MEDIUM\nimpactDescription: avoids unnecessary computation\ntags: javasc"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-hoist-regexp.md",
    "chars": 1028,
    "preview": "---\ntitle: Hoist RegExp Creation\nimpact: LOW-MEDIUM\nimpactDescription: avoids recreation\ntags: javascript, regexp, optim"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-index-maps.md",
    "chars": 834,
    "preview": "---\ntitle: Build Index Maps for Repeated Lookups\nimpact: LOW-MEDIUM\nimpactDescription: 1M ops to 2K ops\ntags: javascript"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-length-check-first.md",
    "chars": 1752,
    "preview": "---\ntitle: Early Length Check for Array Comparisons\nimpact: MEDIUM-HIGH\nimpactDescription: avoids expensive operations w"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-min-max-loop.md",
    "chars": 2105,
    "preview": "---\ntitle: Use Loop for Min/Max Instead of Sort\nimpact: LOW\nimpactDescription: O(n) instead of O(n log n)\ntags: javascri"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-set-map-lookups.md",
    "chars": 532,
    "preview": "---\ntitle: Use Set/Map for O(1) Lookups\nimpact: LOW-MEDIUM\nimpactDescription: O(n) to O(1)\ntags: javascript, set, map, d"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md",
    "chars": 1782,
    "preview": "---\ntitle: Use toSorted() Instead of sort() for Immutability\nimpact: MEDIUM-HIGH\nimpactDescription: prevents mutation bu"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rendering-activity.md",
    "chars": 564,
    "preview": "---\ntitle: Use Activity Component for Show/Hide\nimpact: MEDIUM\nimpactDescription: preserves state/DOM\ntags: rendering, a"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md",
    "chars": 1185,
    "preview": "---\ntitle: Animate SVG Wrapper Instead of SVG Element\nimpact: LOW\nimpactDescription: enables hardware acceleration\ntags:"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rendering-conditional-render.md",
    "chars": 980,
    "preview": "---\ntitle: Use Explicit Conditional Rendering\nimpact: LOW\nimpactDescription: prevents rendering 0 or NaN\ntags: rendering"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rendering-content-visibility.md",
    "chars": 814,
    "preview": "---\ntitle: CSS content-visibility for Long Lists\nimpact: HIGH\nimpactDescription: faster initial render\ntags: rendering, "
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md",
    "chars": 1039,
    "preview": "---\ntitle: Hoist Static JSX Elements\nimpact: LOW\nimpactDescription: avoids re-creation\ntags: rendering, jsx, static, opt"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md",
    "chars": 2308,
    "preview": "---\ntitle: Prevent Hydration Mismatch Without Flickering\nimpact: MEDIUM\nimpactDescription: avoids visual flicker and hyd"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rendering-svg-precision.md",
    "chars": 588,
    "preview": "---\ntitle: Optimize SVG Precision\nimpact: LOW\nimpactDescription: reduces file size\ntags: rendering, svg, optimization, s"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rerender-defer-reads.md",
    "chars": 973,
    "preview": "---\ntitle: Defer State Reads to Usage Point\nimpact: MEDIUM\nimpactDescription: avoids unnecessary subscriptions\ntags: rer"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rerender-dependencies.md",
    "chars": 824,
    "preview": "---\ntitle: Narrow Effect Dependencies\nimpact: LOW\nimpactDescription: minimizes effect re-runs\ntags: rerender, useEffect,"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rerender-derived-state.md",
    "chars": 724,
    "preview": "---\ntitle: Subscribe to Derived State\nimpact: MEDIUM\nimpactDescription: reduces re-render frequency\ntags: rerender, deri"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md",
    "chars": 2958,
    "preview": "---\ntitle: Use Functional setState Updates\nimpact: MEDIUM\nimpactDescription: prevents stale closures and unnecessary cal"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md",
    "chars": 2016,
    "preview": "---\ntitle: Use Lazy State Initialization\nimpact: MEDIUM\nimpactDescription: wasted computation on every render\ntags: reac"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rerender-memo.md",
    "chars": 1148,
    "preview": "---\ntitle: Extract to Memoized Components\nimpact: MEDIUM\nimpactDescription: enables early returns\ntags: rerender, memo, "
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/rerender-transitions.md",
    "chars": 1055,
    "preview": "---\ntitle: Use Transitions for Non-Urgent Updates\nimpact: MEDIUM\nimpactDescription: maintains UI responsiveness\ntags: re"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/server-after-nonblocking.md",
    "chars": 2012,
    "preview": "---\ntitle: Use after() for Non-Blocking Operations\nimpact: MEDIUM\nimpactDescription: faster response times\ntags: server,"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/server-cache-lru.md",
    "chars": 1353,
    "preview": "---\ntitle: Cross-Request LRU Caching\nimpact: HIGH\nimpactDescription: caches across requests\ntags: server, cache, lru, cr"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/server-cache-react.md",
    "chars": 681,
    "preview": "---\ntitle: Per-Request Deduplication with React.cache()\nimpact: MEDIUM\nimpactDescription: deduplicates within request\nta"
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/server-parallel-fetching.md",
    "chars": 1515,
    "preview": "---\ntitle: Parallel Data Fetching with Component Composition\nimpact: CRITICAL\nimpactDescription: eliminates server-side "
  },
  {
    "path": ".github/skills/vercel-react-best-practices/rules/server-serialization.md",
    "chars": 996,
    "preview": "---\ntitle: Minimize Serialization at RSC Boundaries\nimpact: HIGH\nimpactDescription: reduces data transfer size\ntags: ser"
  },
  {
    "path": ".github/skills/web-design-guidelines/SKILL.md",
    "chars": 1183,
    "preview": "---\nname: web-design-guidelines\ndescription: Review UI code for Web Interface Guidelines compliance. Use when asked to \""
  },
  {
    "path": ".github/workflows/desktop-build.yml",
    "chars": 29328,
    "preview": "name: Build Desktop App\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \"packages/desktop/**\"\n      - \".githu"
  },
  {
    "path": ".github/workflows/docker-sandbox.yml",
    "chars": 1488,
    "preview": "name: Build and Publish Sandbox Image\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \"docker/**\"\n  workflow_"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 774,
    "preview": "name: Run Tests\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  test:\n    ru"
  },
  {
    "path": ".gitignore",
    "chars": 792,
    "preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 48,
    "preview": "pnpm lint-staged && pnpm typecheck && pnpm test\n"
  },
  {
    "path": ".prettierignore",
    "chars": 64,
    "preview": ".agents/\n.claude/\n.convex/\n.cursor/\n.github/\nconvex/_generated/\n"
  },
  {
    "path": "LICENSE",
    "chars": 1344,
    "preview": "Apache License Version 2.0\n\nCopyright (c) 2025 HackerAI, LLC. All rights reserved.\n\n----------\n\nAdditional Conditions:\n\n"
  },
  {
    "path": "README.md",
    "chars": 2859,
    "preview": "<p align=\"center\">\n  <a href=\"https://hackerai.co/\">\n    <img src=\"public/icon-512x512.png\" width=\"150\" alt=\"HackerAI Lo"
  },
  {
    "path": "__mocks__/@aws-sdk/client-s3.ts",
    "chars": 323,
    "preview": "const mockSend = jest.fn();\n\nexport const S3Client = jest.fn().mockImplementation((config: any) => ({\n  send: mockSend,\n"
  },
  {
    "path": "__mocks__/@aws-sdk/s3-request-presigner.ts",
    "chars": 39,
    "preview": "export const getSignedUrl = jest.fn();\n"
  },
  {
    "path": "__mocks__/@upstash/ratelimit.ts",
    "chars": 560,
    "preview": "export const mockLimit = jest.fn().mockResolvedValue({\n  success: true,\n  remaining: 10000,\n  reset: Date.now() + 360000"
  },
  {
    "path": "__mocks__/@upstash/redis.ts",
    "chars": 797,
    "preview": "export const mockHincrby = jest.fn().mockResolvedValue(5000);\nexport const mockHset = jest.fn().mockResolvedValue(1);\nex"
  },
  {
    "path": "__mocks__/convex/browser.ts",
    "chars": 572,
    "preview": "export const mockQuery = jest.fn().mockResolvedValue({});\nexport const mockMutation = jest\n  .fn()\n  .mockResolvedValue("
  },
  {
    "path": "__mocks__/convex-react.ts",
    "chars": 664,
    "preview": "// Create stable mock references for hooks\nconst mockMutation = jest.fn();\nconst mockAction = jest.fn();\n\nexport const u"
  },
  {
    "path": "__mocks__/franc-min.ts",
    "chars": 76,
    "preview": "export const franc = (_text: string, _opts?: { only?: string[] }) => \"eng\";\n"
  },
  {
    "path": "__mocks__/jose.ts",
    "chars": 297,
    "preview": "// Simple mock for jose JWT library\nexport const compactDecrypt = jest.fn();\nexport const CompactEncrypt = jest.fn();\nex"
  },
  {
    "path": "__mocks__/next/navigation.ts",
    "chars": 590,
    "preview": "// Manual mock for next/navigation\nexport const useRouter = jest.fn(() => ({\n  push: jest.fn(),\n  replace: jest.fn(),\n  "
  },
  {
    "path": "__mocks__/react-hotkeys-hook.ts",
    "chars": 37,
    "preview": "export const useHotkeys = jest.fn();\n"
  },
  {
    "path": "__mocks__/react-markdown.tsx",
    "chars": 221,
    "preview": "import React from \"react\";\n\n// Simple mock for react-markdown\nconst ReactMarkdown = ({ children }: { children: string })"
  },
  {
    "path": "__mocks__/react-shiki.tsx",
    "chars": 267,
    "preview": "import React from \"react\";\n\n// Simple mock for react-shiki\nexport const ShikiCode = ({ children }: { children?: React.Re"
  },
  {
    "path": "__mocks__/shiki.ts",
    "chars": 615,
    "preview": "// Simple mock for shiki\nexport const bundledLanguages = {};\nexport const bundledLanguagesAlias = {};\nexport const bundl"
  },
  {
    "path": "__mocks__/streamdown.tsx",
    "chars": 214,
    "preview": "import React from \"react\";\n\n// Simple mock for streamdown\nexport const Streamdown = ({ children }: { children: string })"
  },
  {
    "path": "__mocks__/stripe.ts",
    "chars": 483,
    "preview": "// Simple mock for stripe\nconst Stripe = jest.fn().mockImplementation(() => ({\n  checkout: {\n    sessions: {\n      creat"
  },
  {
    "path": "__mocks__/use-stick-to-bottom.ts",
    "chars": 232,
    "preview": "// Simple mock for use-stick-to-bottom\nexport const useStickToBottom = () => ({\n  scrollRef: { current: null },\n  conten"
  },
  {
    "path": "__mocks__/uuid.ts",
    "chars": 148,
    "preview": "let counter = 0;\n\nexport const v4 = () => {\n  counter++;\n  return `test-uuid-${counter}`;\n};\n\nconst mockUuid = {\n  v4,\n}"
  },
  {
    "path": "__mocks__/workos-authkit.ts",
    "chars": 540,
    "preview": "// Simple mock for @workos-inc/authkit-nextjs\nexport const getUser = jest.fn().mockResolvedValue({\n  id: \"test-user-id\","
  },
  {
    "path": "__mocks__/workos-node.ts",
    "chars": 391,
    "preview": "// Simple mock for @workos-inc/node\nclass WorkOS {\n  userManagement = {\n    getUser: jest.fn(),\n    listUsers: jest.fn()"
  },
  {
    "path": "__mocks__/workos.ts",
    "chars": 348,
    "preview": "export const useAuth = () => ({\n  user: null,\n  entitlements: [],\n  isAuthenticated: false,\n  signIn: jest.fn(),\n  signO"
  },
  {
    "path": "app/(chat)/c/[id]/page.tsx",
    "chars": 1370,
    "preview": "\"use client\";\n\nimport { Chat } from \"../../../components/chat\";\nimport { Authenticated, Unauthenticated, AuthLoading } f"
  },
  {
    "path": "app/(chat)/layout.tsx",
    "chars": 1172,
    "preview": "\"use client\";\n\nimport { Authenticated, Unauthenticated, AuthLoading } from \"convex/react\";\nimport { ChatLayout } from \"@"
  },
  {
    "path": "app/(chat)/page.tsx",
    "chars": 5954,
    "preview": "\"use client\";\n\nimport React from \"react\";\nimport { Authenticated, Unauthenticated } from \"convex/react\";\nimport { ChatIn"
  },
  {
    "path": "app/api/agent/route.ts",
    "chars": 147,
    "preview": "import { createChatHandler } from \"@/lib/api/chat-handler\";\n\nexport const maxDuration = 800;\n\nexport const POST = create"
  },
  {
    "path": "app/api/agent-long/cancel/route.ts",
    "chars": 1789,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { runs } from \"@trigger.dev/sdk\";\n\nimport { getUserIDAnd"
  },
  {
    "path": "app/api/agent-long/resume/route.ts",
    "chars": 2581,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { runs, auth, ApiError } from \"@trigger.dev/sdk\";\n\nimpor"
  },
  {
    "path": "app/api/agent-long/route.ts",
    "chars": 6092,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { tasks, auth } from \"@trigger.dev/sdk\";\nimport type { a"
  },
  {
    "path": "app/api/auth/desktop-callback/route.ts",
    "chars": 7254,
    "preview": "import { NextRequest } from \"next/server\";\nimport { sealData } from \"iron-session\";\nimport {\n  createDesktopTransferToke"
  },
  {
    "path": "app/api/chat/[id]/stream/route.ts",
    "chars": 8817,
    "preview": "import type { NextRequest } from \"next/server\";\nimport { createUIMessageStream, JsonToSseTransformStream } from \"ai\";\nim"
  },
  {
    "path": "app/api/chat/route.ts",
    "chars": 146,
    "preview": "import { createChatHandler } from \"@/lib/api/chat-handler\";\n\nexport const maxDuration = 180;\n\nexport const POST = create"
  },
  {
    "path": "app/api/clear-auth-cookies/route.ts",
    "chars": 911,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\n\nexport const POST = async (req: NextRequest) => {\n  const head"
  },
  {
    "path": "app/api/delete-account/route.ts",
    "chars": 4396,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { stripe } from \"../stripe\";\nimport { workos } from \"../"
  },
  {
    "path": "app/api/delete-sandboxes/route.ts",
    "chars": 1648,
    "preview": "import { Sandbox } from \"@e2b/code-interpreter\";\nimport { getUserIDAndPro } from \"@/lib/auth/get-user-id\";\nimport { Next"
  },
  {
    "path": "app/api/entitlements/route.ts",
    "chars": 3885,
    "preview": "import { NextRequest } from \"next/server\";\nimport { WorkOS } from \"@workos-inc/node\";\nimport {\n  json,\n  extractErrorMes"
  },
  {
    "path": "app/api/extra-usage/confirm/route.ts",
    "chars": 2911,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { stripe } from \"@/app/api/stripe\";\nimport { ConvexHttpC"
  },
  {
    "path": "app/api/extra-usage/webhook/route.ts",
    "chars": 3602,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { stripe } from \"@/app/api/stripe\";\nimport { ConvexHttpC"
  },
  {
    "path": "app/api/fraud/webhook/route.ts",
    "chars": 16697,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { stripe } from \"@/app/api/stripe\";\nimport { ConvexHttpC"
  },
  {
    "path": "app/api/logout-all/route.ts",
    "chars": 1012,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { workos } from \"@/app/api/workos\";\nimport { getUserID }"
  },
  {
    "path": "app/api/mfa/delete/route.ts",
    "chars": 2679,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { workos } from \"@/app/api/workos\";\nimport { getUserID }"
  },
  {
    "path": "app/api/mfa/enroll/route.ts",
    "chars": 1778,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { workos } from \"@/app/api/workos\";\nimport { getUserID }"
  },
  {
    "path": "app/api/mfa/factors/route.ts",
    "chars": 1400,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getUserID } from \"@/lib/auth/get-user-id\";\nimport { wo"
  },
  {
    "path": "app/api/mfa/verify/route.ts",
    "chars": 2254,
    "preview": "import { NextRequest, NextResponse } from \"next/server\";\nimport { workos } from \"@/app/api/workos\";\nimport { getUserID }"
  }
]

// ... and 572 more files (download for full content)

About this extraction

This page contains the full source code of the hackerai-tech/hackerai GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 772 files (4.6 MB), approximately 1.3M tokens, and a symbol index with 2099 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!