Sona is an AI-powered, browser-based code workspace designed for working on real repositories without a heavy backend. Import a GitHub repo or describe what you want to build, let Sona scaffold and edit code with an AI agent, then export changes back to GitHub.
This project is built with Next.js 16, Convex, Clerk, and Inngest, with CodeMirror 6 as the editor.
When you open Sona (the / route):
- You see a clean hero:
- Sona logo
- Tagline: "Spin up an AI-powered workspace from GitHub or a prompt."
- Two primary entry points:
- New — create a new AI-driven project from a natural language prompt
- Import — import an existing GitHub repository into Sona
- Behavior for anonymous users:
- Home route is public (no hard redirect to auth)
- Clicking New or Import opens the Clerk sign-in popover (not a full-page redirect)
- Projects list is hidden until the user is signed in
- Behavior for signed-in users:
- New and Import open their respective dialogs directly
- A projects list appears below the hero so users can quickly resume work
- Keyboard shortcuts:
⌘J/Ctrl+J— open New Project dialog⌘I/Ctrl+I— open Import from GitHub dialog⌘K/Ctrl+K— open project command dialog
All actions that actually talk to Convex/GitHub are mounted only when the user is signed in, so logged-out visitors never trigger backend calls.
Sona lets you spin up projects from a natural language description using the New Project dialog.
- Component:
NewProjectDialog - API route:
POST /api/projects/create-with-prompt
Flow:
-
User opens the New dialog (via button or
⌘J). -
They type a prompt like:
"Create a full-stack Next.js app with a REST API for tasks and a Tailwind UI."
-
The dialog calls:
POST /api/projects/create-with-prompt { prompt: string }
-
On the server:
- Clerk
auth()ensures the user is signed in. - A random project name is generated via
unique-names-generator. - A new project + initial conversation is created via Convex:
system.createProjectWithConversation
- The user's prompt is inserted as the first
usermessage. - An empty
assistantmessage with statusprocessingis created. - An Inngest event
message/sentis emitted to let an AI agent process the prompt.
- Clerk
-
The client redirects to
/projects/:projectId, where the editor + sidebar agent pick up the conversation.
Result: from prompt to live AI-driven project context in one action.
Sona can import existing GitHub repositories as projects.
- Component:
ImportGithubDialog - API route:
POST /api/github/import
Flow:
- User opens Import (button or
⌘I). - They paste a GitHub URL (e.g.,
https://github.com/owner/repo). - The dialog validates the URL with
zod+@tanstack/react-form. - On submit, it calls
/api/github/importwith{ url }. - On success:
- A new Sona project is created with the repo contents.
- User is redirected to
/projects/:projectId.
- Error handling:
- If GitHub isn�t connected, it surfaces a toast and offers a Connect action that opens the Clerk user profile.
- Generic errors show a friendly notification.
This makes Sona a thin AI overlay on top of any existing repo.
Sona uses CodeMirror 6 with a custom setup and extensions built for AI-assisted editing.
- Editor component:
CodeEditor - Language selection:
getLanguageExtension(filename)
Supported languages include:
- JavaScript / TypeScript (
.js,.jsx,.ts,.tsx) - HTML (
.html) - CSS (
.css) - JSON (
.json) - Markdown (
.md,.mdx) - Python (
.py) - YAML (
.yml,.yaml) - SQL (
.sql)
Behavior:
- Debounced autosave to Convex (1.5s after the user stops typing)
- Custom theme and minimap via Replit's Codemirror addons
- Binary/unsupported files:
- If a file has a
storageId(binary), the editor shows a friendly warning rather than raw bytes.
- If a file has a
The editor is embedded in EditorView, which:
- Shows a top navigation bar and file breadcrumbs for the active project
- Displays a logo placeholder when no file is selected
Each project has a sidebar AI agent that can discuss and modify the codebase.
- Component:
ConversationSidebar - API routes:
POST /api/messages— send a new message to the agentPOST /api/messages/cancel— cancel an in-flight responseGET /api/agent/usage— fetch current usage for the signed-in user
- Convex functions:
system.getConversationByIdsystem.createMessagesystem.getProcessingMessagessystem.getRecentMessagessystem.recordAgentRunsystem.getAgentRunCountForMonth
Key behaviors:
- Messages are persisted in Convex with roles (
user,assistant) and status (processing,completed,cancelled). - When a new message is sent:
- Any other
processingmessages for the same project are cancelled (including an Inngestmessage/cancelevent). - A new
assistantmessage with statusprocessingis created. - An Inngest
message/sentevent is emitted to drive the AI agent.
- Any other
- The sidebar shows the full conversation with:
- Streaming/"Thinking..." state for processing messages
- Copy action on the latest completed assistant response
- A Past Conversations dialog lets users switch between conversations inside a project.
Sona enforces monthly usage limits on the sidebar agent using Clerk Billing features and Convex.
Plans are inferred from Clerk via a Billing feature:
freeplan (no{ plan: "pro" }feature): 5 runs / calendar monthproplan (feature{ plan: "pro" }present): 15 runs / calendar month
Implementation:
-
src/lib/billing.ts-
Uses
auth()from@clerk/nextjs/serverandhas({ plan: "pro" })to decide:export type Plan = "free" | "pro"; export const PLAN_LIMITS: Record<Plan, number> = { free: 5, pro: 15, };
-
-
Usage tracking is stored in Convex:
- Table:
agentRuns(one record per sidebar agent run) - Query:
getAgentRunCountForMonth(counts runs byuserIdand calendar month in UTC) - Mutation:
recordAgentRun(inserts a new run when a sidebar agent request is accepted)
- Table:
-
Enforcement happens in
POST /api/messages:- The API checks before creating messages:
- Resolve plan via Clerk (
getUserPlan) and map to a limit. - Query Convex for
usage.countfor the current month. - If
usage.count >= limit, return402withcode: "usage_limit_exceeded".
- Resolve plan via Clerk (
- On success, after creating the assistant placeholder message, the API calls
recordAgentRunso usage is updated.
- The API checks before creating messages:
In ConversationSidebar, above the input area:
-
Usage meter shows:
Free · 3/5 runsor
Pro · 7/15 runs -
Tooltip on hover:
Usage resets on the 1st of each month -
Upgrade button:
- Only shown for non-Pro users.
- Uses Clerk's official
openUserProfile({ path: "#billing" })to navigate to the Billing section within the Clerk User Profile.
The meter updates in real-time:
- On initial mount via
GET /api/agent/usage - After every successful run
- Immediately if the API returns
usage_limit_exceeded
Convex schema and functions in convex/system.ts and convex/schema.ts manage projects and files:
projectstable: project metadata (name, ownerId, import/export status, settings)filestable:- Text files vs folders (
type: "file" | "folder") - Binary files with
storageIdfor Convex storage - Indexed by project and optional parent folder
- Text files vs folders (
Key operations:
createFile,createFiles,createFolder,renameFile,deleteFilegetProjectFiles,getProjectFilesWithUrls,getFileById- Binary files:
createBinaryFile,generateUploadUrl - Cleanup:
cleanupremoves all files (and storage) for a project
The frontend uses hooks like useFiles, useCreateFile, useRenameFile, etc., to drive a React-based file explorer.
Sona uses Clerk for authentication and session management:
-
src/proxy.ts(Clerk middleware) ensures most routes are protected, while explicitly allowing:/(home) as public/api/inngest(.*)
-
ConvexProviderWithClerkwraps the app and passesuseAuthinto Convex so queries and mutations are scoped to the logged-in user. -
UI behaviors:
- Signed-out users see the home screen but not projects or agent interactions.
- Attempting to create/import projects or use the agent prompts Clerk's sign-in modal.
- Frontend: Next.js 16 (App Router), React 19, Tailwind CSS
- Editor: CodeMirror 6 with custom extensions (minimap, indentation markers, custom theme)
- Backend:
- Convex for realtime database + storage + functions
- Inngest for background agent processing (jobs triggered from
/api/messages)
- Auth & Billing: Clerk (auth + Billing features for
free/proplans) - AI:
aiSDK + providers (@ai-sdk/openai,@ai-sdk/anthropic,@ai-sdk/google) - GitHub: Import/Export via custom API routes and
octokit(GitHub REST API)
-
Install dependencies:
npm install
-
Configure environment variables:
Create a
.env.localwith at least:NEXT_PUBLIC_CONVEX_URL=... # Convex deployment URL SONA_CONVEX_INTERNAL_KEY=... # Internal key shared between Next and Convex NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=... CLERK_SECRET_KEY=...
And any GitHub / AI provider keys you use in the app.
-
Run Convex dev server:
npx convex dev
-
Run Next dev server:
npm run dev
-
Open the app:
- Visit
http://localhost:3000 - Log in with Clerk
- Create a project from a prompt or import a GitHub repo
- Visit
Sona is designed to deploy cleanly on Vercel:
- Next.js app can be deployed directly via Vercel.
- Convex should be deployed via Convex's own deployment workflow and pointed to from
NEXT_PUBLIC_CONVEX_URL. - Clerk keys and Convex internal key must be configured in Vercel environment variables.
The only stateful/billing-sensitive part is the sidebar agent usage, which relies on:
- Clerk Billing features (
has({ plan: "pro" })) - Convex
agentRunstable and thegetAgentRunCountForMonth/recordAgentRunfunctions
Ensure your Clerk Billing configuration matches the assumptions in src/lib/billing.ts before going to production.