Skip to content

Latest commit

 

History

History
252 lines (194 loc) · 9.58 KB

File metadata and controls

252 lines (194 loc) · 9.58 KB

OpenSpec Instructions

These instructions are for AI assistants working in this project.

Always open @/openspec/AGENTS.md when the request:

  • Mentions planning or proposals (words like proposal, spec, change, plan)
  • Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work
  • Sounds ambiguous and you need the authoritative spec before coding

Use @/openspec/AGENTS.md to learn:

  • How to create and apply change proposals
  • Spec format and conventions
  • Project structure and guidelines

Keep this managed block so 'openspec update' can refresh the instructions.

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What is this?

21st Agents - A local-first Electron desktop app for AI-powered code assistance. Users create chat sessions linked to local project folders, interact with Claude in Plan or Agent mode, and see real-time tool execution (bash, file edits, web search, etc.).

Commands

# Development
bun run dev              # Start Electron with hot reload

# Build
bun run build            # Compile app
bun run package          # Package for current platform (dir)
bun run package:mac      # Build macOS (DMG + ZIP)
bun run package:win      # Build Windows (NSIS + portable)
bun run package:linux    # Build Linux (AppImage + DEB)

# Database (Drizzle + SQLite)
bun run db:generate      # Generate migrations from schema
bun run db:push          # Push schema directly (dev only)

Architecture

src/
├── main/                    # Electron main process
│   ├── index.ts             # App entry, window lifecycle
│   ├── auth-manager.ts      # OAuth flow, token refresh
│   ├── auth-store.ts        # Encrypted credential storage (safeStorage)
│   ├── windows/main.ts      # Window creation, IPC handlers
│   └── lib/
│       ├── db/              # Drizzle + SQLite
│       │   ├── index.ts     # DB init, auto-migrate on startup
│       │   ├── schema/      # Drizzle table definitions
│       │   └── utils.ts     # ID generation
│       └── trpc/routers/    # tRPC routers (projects, chats, claude)
│
├── preload/                 # IPC bridge (context isolation)
│   └── index.ts             # Exposes desktopApi + tRPC bridge
│
└── renderer/                # React 19 UI
    ├── App.tsx              # Root with providers
    ├── features/
    │   ├── agents/          # Main chat interface
    │   │   ├── main/        # active-chat.tsx, new-chat-form.tsx
    │   │   ├── ui/          # Tool renderers, preview, diff view
    │   │   ├── commands/    # Slash commands (/plan, /agent, /clear)
    │   │   ├── atoms/       # Jotai atoms for agent state
    │   │   └── stores/      # Zustand store for sub-chats
    │   ├── sidebar/         # Chat list, archive, navigation
    │   ├── sub-chats/       # Tab/sidebar sub-chat management
    │   └── layout/          # Main layout with resizable panels
    ├── components/ui/       # Radix UI wrappers (button, dialog, etc.)
    └── lib/
        ├── atoms/           # Global Jotai atoms
        ├── stores/          # Global Zustand stores
        ├── trpc.ts          # Real tRPC client
        └── mock-api.ts      # DEPRECATED - being replaced with real tRPC

Database (Drizzle ORM)

Location: {userData}/data/agents.db (SQLite)

Schema: src/main/lib/db/schema/index.ts

// Three main tables:
projects     id, name, path (local folder), timestamps
chats        id, name, projectId, worktree fields, timestamps
sub_chats    id, name, chatId, sessionId, mode, messages (JSON)

Auto-migration: On app start, initDatabase() runs migrations from drizzle/ folder (dev) or resources/migrations (packaged).

Queries:

import { getDatabase, projects, chats } from "../lib/db"
import { eq } from "drizzle-orm"

const db = getDatabase()
const allProjects = db.select().from(projects).all()
const projectChats = db.select().from(chats).where(eq(chats.projectId, id)).all()

Key Patterns

IPC Communication

  • Uses tRPC with trpc-electron for type-safe main↔renderer communication
  • All backend calls go through tRPC routers, not raw IPC
  • Preload exposes window.desktopApi for native features (window controls, clipboard, notifications)

State Management

  • Jotai: UI state (selected chat, sidebar open, preview settings)
  • Zustand: Sub-chat tabs and pinned state (persisted to localStorage)
  • React Query: Server state via tRPC (auto-caching, refetch)

Claude Integration

  • Dynamic import of @anthropic-ai/claude-code SDK
  • Two modes: "plan" (read-only) and "agent" (full permissions)
  • Session resume via sessionId stored in SubChat
  • Message streaming via tRPC subscription (claude.onMessage)

Tech Stack

Layer Tech
Desktop Electron 33.4.5, electron-vite, electron-builder
UI React 19, TypeScript 5.4.5, Tailwind CSS
Components Radix UI, Lucide icons, Motion, Sonner
State Jotai, Zustand, React Query
Backend tRPC, Drizzle ORM, better-sqlite3
AI @anthropic-ai/claude-code
Package Manager bun

File Naming

  • Components: PascalCase (ActiveChat.tsx, AgentsSidebar.tsx)
  • Utilities/hooks: camelCase (useFileUpload.ts, formatters.ts)
  • Stores: kebab-case (sub-chat-store.ts, agent-chat-store.ts)
  • Atoms: camelCase with Atom suffix (selectedAgentChatIdAtom)

Important Files

  • electron.vite.config.ts - Build config (main/preload/renderer entries)
  • src/main/lib/db/schema/index.ts - Drizzle schema (source of truth)
  • src/main/lib/db/index.ts - DB initialization + auto-migrate
  • src/renderer/features/agents/atoms/index.ts - Agent UI state atoms
  • src/renderer/features/agents/main/active-chat.tsx - Main chat component
  • src/main/lib/trpc/routers/claude.ts - Claude SDK integration

Debugging First Install Issues

When testing auth flows or behavior for new users, you need to simulate a fresh install:

# 1. Clear all app data (auth, database, settings)
rm -rf ~/Library/Application\ Support/Agents\ Dev/

# 2. Reset macOS protocol handler registration (if testing deep links)
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -kill -r -domain local -domain system -domain user

# 3. Clear app preferences
defaults delete dev.21st.agents.dev  # Dev mode
defaults delete dev.21st.agents      # Production

# 4. Run in dev mode with clean state
cd apps/desktop
bun run dev

Common First-Install Bugs:

  • OAuth deep link not working: macOS Launch Services may not immediately recognize protocol handlers on first app launch. User may need to click "Sign in" again after the first attempt.
  • Folder dialog not appearing: Window focus timing issues on first launch. Fixed by ensuring window focus before showing dialog.showOpenDialog().

Dev vs Production App:

  • Dev mode uses twentyfirst-agents-dev:// protocol
  • Dev mode uses separate userData path (~/Library/Application Support/Agents Dev/)
  • This prevents conflicts between dev and production installs

Releasing a New Version

Prerequisites for Notarization

  • Keychain profile: 21st-notarize
  • Create with: xcrun notarytool store-credentials "21st-notarize" --apple-id YOUR_APPLE_ID --team-id YOUR_TEAM_ID

Release Commands

# Full release (build, sign, submit notarization, upload to CDN)
bun run release

# Or step by step:
bun run build              # Compile TypeScript
bun run package:mac        # Build & sign macOS app
bun run dist:manifest      # Generate latest-mac.yml manifests
./scripts/upload-release-wrangler.sh  # Submit notarization & upload to R2 CDN

Bump Version Before Release

npm version patch --no-git-tag-version  # 0.0.27 → 0.0.28

After Release Script Completes

  1. Wait for notarization (2-5 min): xcrun notarytool history --keychain-profile "21st-notarize"
  2. Staple DMGs: cd release && xcrun stapler staple *.dmg
  3. Re-upload stapled DMGs to R2 and GitHub (see RELEASE.md for commands)
  4. Update changelog: gh release edit v0.0.X --notes "..."
  5. Upload manifests (triggers auto-updates!) — see RELEASE.md
  6. Sync to public: ./scripts/sync-to-public.sh

Files Uploaded to CDN

File Purpose
latest-mac.yml Manifest for arm64 auto-updates
latest-mac-x64.yml Manifest for Intel auto-updates
1Code-{version}-arm64-mac.zip Auto-update payload (arm64)
1Code-{version}-mac.zip Auto-update payload (Intel)
1Code-{version}-arm64.dmg Manual download (arm64)
1Code-{version}.dmg Manual download (Intel)

Auto-Update Flow

  1. App checks https://cdn.21st.dev/releases/desktop/latest-mac.yml on startup and when window regains focus (with 1 min cooldown)
  2. If version in manifest > current version, shows "Update Available" banner
  3. User clicks Download → downloads ZIP in background
  4. User clicks "Restart Now" → installs update and restarts

Current Status (WIP)

Done:

  • Drizzle ORM setup with schema (projects, chats, sub_chats)
  • Auto-migration on app startup
  • tRPC routers structure

In Progress:

  • Replacing mock-api.ts with real tRPC calls in renderer
  • ProjectSelector component (local folder picker)

Planned:

  • Git worktree per chat (isolation)
  • Claude Code execution in worktree path
  • Full feature parity with web app