Skip to content

Latest commit

 

History

History
177 lines (132 loc) · 9.16 KB

File metadata and controls

177 lines (132 loc) · 9.16 KB

CLAUDE.md

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

What This Is

OpenHelm is a local-first macOS desktop application that turns high-level goals into scheduled, self-correcting Claude Code jobs. Users type a goal ("Improve test coverage"), the system generates a plan of Claude Code tasks, and those tasks run autonomously in the background on a schedule.

The project is currently in the planning/bootstrapping phase. The docs/ directory contains the PRD (prd.md), v1 implementation plan (plan_1_v1.md), and competitor research (competitors.md).


Tech Stack (Locked)

Layer Choice
Desktop framework Tauri 2 (Rust shell + native WebView)
Frontend React 18 + TypeScript
Styling Tailwind CSS 4 + shadcn/ui
Local database SQLite via better-sqlite3
ORM Drizzle ORM
Background agent Node.js sidecar process
IPC (UI ↔ agent) Tauri stdin/stdout sidecar (JSON-RPC)
AI planning/summarisation Claude Code CLI (--print mode)
State management Zustand

Prerequisites: Node 20+, Rust toolchain, Xcode Command Line Tools.


Development Commands

# Root — start Tauri dev server (frontend + Rust shell + agent sidecar)
npm run tauri dev

# Frontend only (Vite)
npm run dev

# Build the agent sidecar
cd agent && npm run build

# Run Drizzle migrations
cd agent && npx drizzle-kit migrate

# Type-check everything
npm run typecheck

# Lint
npm run lint

These commands are defined in the implementation plan but the project is not yet scaffolded. Update this section once the Tauri scaffold exists.


Repository Structure

openhelm/
├── src-tauri/               # Tauri Rust shell (keep minimal — thin wrappers only)
│   ├── src/
│   │   ├── main.rs          # Entry point, sidecar launch, window setup
│   │   └── lib.rs           # Tauri commands (thin wrappers only)
│   └── Cargo.toml
│
├── src/                     # React frontend
│   ├── components/
│   │   ├── ui/              # shadcn/ui primitives
│   │   ├── layout/          # Sidebar, shell, navigation
│   │   ├── data-tables/     # Data table CRUD UI (list, detail, grid, cells)
│   │   ├── goals/
│   │   ├── jobs/
│   │   ├── runs/
│   │   ├── onboarding/
│   │   └── shared/
│   ├── hooks/               # Custom React hooks
│   ├── stores/              # Zustand stores
│   ├── lib/                 # API client (agent-client.ts), type utils
│   └── App.tsx
│
├── agent/                   # Node.js background agent (sidecar)
│   ├── src/
│   │   ├── index.ts         # Entry point, IPC server init
│   │   ├── data-tables/     # Embedding, retrieval, prompt injection for data tables
│   │   ├── scheduler/       # Job queue and scheduling engine
│   │   ├── executor/        # Claude Code process management
│   │   ├── planner/         # Goal → job plan generation (via CLI)
│   │   ├── db/              # Drizzle schema, migrations, queries
│   │   ├── claude-code/     # ClaudeCodeRunner abstraction layer
│   │   └── ipc/             # JSON-RPC server (stdin/stdout)
│   └── package.json
│
├── shared/                  # Types shared between frontend and agent
│   └── types.ts             # IpcRequest, IpcResponse, IpcEvent
│
└── docs/                    # Planning documents

Claude Code CLI — Single Integration Point

All LLM operations route through the Claude Code CLI. Users only need a Claude Code subscription — no separate API key required.

  1. Job execution — spawns Claude Code via agent/src/claude-code/runner.ts with --print --output-format stream-json for real-time streaming.

  2. Planning, assessment, and summarisation — spawns Claude Code via agent/src/claude-code/print.ts with --print --output-format text --tools "" for single-turn text completions. Uses --model sonnet for planning and --model claude-haiku-4-5-20251001 for classification/summarisation.

All CLI invocations must go through the agent/src/claude-code/ directory — never call Claude Code from elsewhere.


Data Model

Project      id, name, description, directory_path, git_url (nullable, cloud only),
             created_at, updated_at
Goal         id, project_id, description, status(active|paused|archived), created_at
Job          id, goal_id (nullable), project_id, name, description, prompt,
             schedule_type(once|interval|cron), schedule_config(JSON),
             is_enabled, next_fire_at, created_at, updated_at
Run          id, job_id, status(queued|running|succeeded|failed|permanent_failure|cancelled),
             trigger_source(scheduled|manual|corrective),
             started_at, finished_at, exit_code, summary, created_at
RunLog       id, run_id, sequence, stream(stdout|stderr), text, timestamp
Settings     key, value, updated_at
DataTable    id, project_id, name, description, columns(JSON), embedding(JSON),
             row_count, created_by(user|ai), created_at, updated_at
DataTableRow id, table_id, data(JSON), sort_order, created_at, updated_at
DataTableChange id, table_id, row_id, action(insert|update|delete|schema_change),
             actor(user|ai|system), run_id, diff(JSON), created_at

Database lives at ~/.openhelm/openhelm.db. WAL mode enabled; foreign keys enforced.

Cloud-mode note: git_url is optional and unused by default. Cloud sandboxes boot empty; the agent uses authenticated Connections (CLIs, MCPs) to fetch whatever the prompt requires. The executor only runs git clone when git_url is set to a valid URL (https://, git@, or ssh://). A generic sandbox-context preamble (see worker/src/sandbox-preamble.ts) is prepended to every prompt listing available connections.

MCP transports: stdio, HTTP, and SSE are all first-class.

  • stdio MCPs need a pre-installed binary (cloud mode requires them to be in WORKER_BAKED_MCP_SERVERS).
  • HTTP / SSE MCPs take a URL + arbitrary headers map on McpConfig. Header values containing ${SECRET} or ${OAUTH_TOKEN} are substituted at launch via resolveHeaders() (shared/src/secret-resolver.ts) against macOS Keychain (local) or Supabase Vault (cloud). Local mode emits discriminated-union {type: "http" | "sse", url, headers} entries into Claude Code CLI's --mcp-config JSON. Cloud mode bridges each remote MCP through npx mcp-remote as an additional --with-extension arg in the Goose launch command. Discovery + RFC 7591 Dynamic Client Registration live in shared/src/mcp-oauth-discovery.ts and are exposed via the connections.setupMcpOauth IPC method.

IPC Protocol (UI ↔ Agent)

The agent communicates via stdin/stdout using newline-delimited JSON:

  • Requests (UI → agent): { id: string, method: string, params?: unknown }
  • Responses (agent → UI): { id: string, result?: unknown, error?: { code, message } }
  • Events (agent → UI, unprompted): { event: string, data: unknown } — e.g. run.log, run.statusChanged

Frontend uses src/lib/agent-client.ts for all agent communication. Events are re-dispatched as CustomEvent on window under the name agent:<event>.


Background Agent Architecture

The agent owns three subsystems that must be fault-isolated:

  1. Scheduler — 1-minute tick; enqueues runs when next_fire_at <= now
  2. Executor — worker pool (default concurrency: 1); spawns Claude Code, streams logs to DB, updates run status
  3. Watchdog — per-run timeout (default 30 min); SIGTERMs hung processes, marks run as failed

A UI crash must never interrupt a running job.


Key Architecture Rules

  • agent/src/claude-code/ is the only entry point to the Claude Code CLI (runner.ts for jobs, print.ts for LLM calls). Pin a minimum supported CLI version; warn users if their installed version is below it.
  • No plan runs without user approval. The plan review step is mandatory before any job fires for the first time.
  • Jobs can exist without a goal (goal_id is nullable). Manual job creation is a first-class feature.
  • File size limit: keep code files ≤ 225 lines; split into submodules when a file grows beyond this.
  • Log to stderr from the agent (stdout is reserved for IPC).
  • The hosted tier (v3+) is a separate business layer; the OSS version runs fully locally with no external services in v1.
  • Chat permission modes are plan_first (default) or bypass. Plan-first runs the LLM read-only and queues every mutation as an XML <tool_call> block — OpenHelm CRUD, MCP server methods (browser navigate, Notion create-page, etc.), and the cloud sandbox spawn itself all become approval cards before they execute. The MCP read/write taxonomy lives in agent/src/mcp-servers/taxonomies/*.json (per-server) and agent/src/connections/taxonomy.ts (per Connection type) — unknown tools default to write. Approved MCP writes dispatch via agent/src/mcp-client/ locally or worker.executeApprovedActions (one sandbox per batch) in cloud mode. See docs/plans/plan_20_plan_first.md.