This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Oracode is a Next.js-based AI coding assistant powered by Claude via the Claude Agent SDK. It uses Convex for real-time backend state management and Daytona for cloud development environments. The architecture consists of a Next.js frontend app, a Convex backend for messages/branches, and sandboxes powered by Daytona with persistent volumes. Each branch maps to a git branch in the repository.
This is a pnpm + Turborepo monorepo with the following workspaces:
- apps/app: Next.js 15 frontend application with React 19
- packages/convex: Convex backend functions and schema (shared package)
- packages/sandbox-worker: Standalone Node.js worker that connects Convex to Claude Agent SDK
pnpm dev # Run all workspaces in dev mode (Next.js app + Convex)pnpm build # Build all packages (uses Turborepo)
pnpm type-check # Type check all packagescd packages/convex
pnpm dev # Run Convex dev server
pnpm deploy # Deploy Convex backendcd packages/sandbox-worker
pnpm dev # Build worker with watch mode (tsup)
pnpm dev:worker # Run worker with tsx watch
pnpm build # Build worker bundle for deploymentThe sandbox worker requires environment variables:
CONVEX_URL: Convex deployment URLBRANCH_ID: Convex branch ID (Id<"branches">)CLERK_TOKEN: Clerk authentication tokenANTHROPIC_API_KEY: Anthropic API key for Claude Agent SDK
Each branch in Oracode corresponds to a git branch in the connected GitHub repository:
- Branch creation: Users specify a branch name when creating a new branch (validated against git naming rules)
- Automatic checkout: When a sandbox starts, it clones the repo and checks out the specified git branch using
git checkout -B <branchName> - Branch isolation: Each branch has its own:
- Message history
- Claude Agent SDK session
- Sandbox worker instance
- Git working directory
This enables parallel development on multiple features/branches simultaneously.
The app uses Next.js 15 with App Router. Key patterns:
- Server Components: Pages fetch data server-side and preload Convex queries
- Client Components: Interactive UI components use Convex React hooks (
useQuery,useMutation,usePreloadedQuery) - Styling: Tailwind CSS v4 with custom AI-focused component library in
components/ai-elements/ - Dark Mode: Uses
next-themeswith class-based dark mode applied to body element
Main routes:
/- Home page (redirects to onboarding or org page)/:orgSlug- Organization dashboard with projects list/:orgSlug/project/:projectId- Project branches list/:orgSlug/branches/:branchId- Branch view with conversation panel and preview panel/:orgSlug/settings- Organization settings (Claude API key)/:orgSlug/project/:projectId/settings- Project settings (dev commands, environment variables)
Convex backend uses the new function syntax with validators. Key files:
- schema.ts: Defines
messages,branches,projects,secrets,organizationSettings, andprojectEnvironmentVariablestables - messages.ts: CRUD operations for messages (queries/mutations)
- branches.ts: Branch management functions - each branch maps to a git branch
- projects.ts: Project management and GitHub repo linking
- sandboxActions.ts: Daytona sandbox provisioning, git clone, and branch checkout
- organizationSettings.ts: Encrypted Anthropic API key storage per org
- projectEnvironmentVariables.ts: Encrypted environment variables per project
- types.ts: Shared TypeScript types exported to other packages
The package exports are configured for use by other workspaces:
./_generated/api- Convex API functions./_generated/dataModel- TypeScript types for tables./types- Custom shared types
A standalone Node.js process that bridges Convex and Claude Agent SDK:
- Connects to Convex: Uses
ConvexClientto subscribe to real-time updates - Watches for user messages: Subscribes to
api.messages.getLastUserMessagefor the branch - Streams to Claude: Uses
@anthropic-ai/claude-agent-sdkwith async generator pattern - Updates Convex: Writes assistant responses back via mutations
The worker uses:
- Streaming architecture: Single persistent Claude Agent SDK session with message queue
- Session resumption: Can resume previous agent sessions via
agentSessionIdstored on branch - Real-time sync: Uses
client.onUpdate()for reactive message processing - Branch isolation: Each branch has its own worker instance and message history
Build configuration:
- tsup: Bundles to Node.js executable in
dist/ - Externalizes
@anthropic-ai/claude-agent-sdkand@anthropic-ai/sdk - Includes shebang for direct execution
This project follows strict Convex conventions (see .cursor/rules/convex_rules.mdc):
Always use the new function syntax with explicit args and returns validators:
export const exampleQuery = query({
args: { branchId: v.id("branches") },
returns: v.array(
v.object({
/* ... */
}),
),
handler: async (ctx, args) => {
// implementation
},
});- Use
query,mutation,actionfor public functions - Use
internalQuery,internalMutation,internalActionfor private functions - Always include
returnsvalidator, usev.null()if no return value
- Import
apifrom@repo/convex/_generated/apifor public functions - Import
internalfrom@repo/convex/_generated/apifor internal functions - Call functions via
ctx.runQuery(api.messages.getLastUserMessage, { branchId })
- Use
.withIndex()instead of.filter()for better performance - Define indexes in schema with descriptive names:
by_branch,by_project,by_org_and_branch,by_field1_and_field2 - Use
.unique()to get single result (throws if multiple) - Use
.first()to get single result (returns null if none)
- Import
Id<"tableName">from./_generated/dataModelfor document IDs - Import
Doc<"tableName">for full document types - Be strict with ID types (use
Id<"users">notstring) - System fields:
_id: v.id(tableName),_creationTime: v.number()
Server components preload queries and pass to client components:
// Server Component
const preloadedMessages = await preloadQuery(api.branches.getMessagesByBranchId, { branchId });
return <BranchClient preloadedMessages={preloadedMessages} />;
// Client Component
const messages = usePreloadedQuery(preloadedMessages);IMPORTANT: Most queries require organization authentication. Always use the "skip" token pattern when data isn't ready:
// CORRECT - waits for auth/data before calling query
const branches = useQuery(
api.branches.getBranchesByProject,
projectId ? { projectId: projectId as Id<"projects"> } : "skip"
);
// WRONG - will fail with "User must be authenticated with an organization"
const branches = useQuery(api.branches.getBranchesByProject, { projectId });The components/ai-elements/ directory contains a custom component library for AI interactions:
conversation.tsx- Message list container with scroll handlingprompt-input.tsx- Rich input with attachments, model selector, toolbarmessage.tsx- Message bubbles with avatarstool.tsx- Tool use/result renderingcode-block.tsx- Syntax highlighted code with copy buttonreasoning.tsx,chain-of-thought.tsx- Thinking indicatorsartifact.tsx- Large generated content (code, documents)
- Dark mode class is applied to
<body>element in layout - Uses
class-variance-authorityfor component variants - Uses
tailwind-mergefor className merging - Custom animation library:
tw-animate-css
- @anthropic-ai/claude-agent-sdk: Core agent integration
- @anthropic-ai/sdk: Anthropic API client
- convex: Backend platform (browser client in frontend, Node client in worker)
- @daytonaio/sdk: Daytona cloud development environments
- @ai-sdk/react: Vercel AI SDK for streaming UI
- next-safe-action: Type-safe server actions
- zod: Schema validation (v4.x)
- react-hook-form + @hookform/resolvers: Form handling
- sonner: Toast notifications
- streamdown: Markdown streaming parser
- Turborepo: Uses task dependencies (
dependsOn: ["^build"]) to ensure correct build order - Package Manager: pnpm with workspace protocol (
"@repo/convex": "workspace:*") - TypeScript: Strict mode enabled across all packages
- Environment Files:
.env.localat both root and app levels - Build Outputs:
- Next.js:
.next/directory - Convex: Type generation in
_generated/ - Sandbox worker:
dist/for deployment bundle
- Next.js: