A Cursor-class AI code editor with a Rust core. Tauri 2 + Rust on the backend, React 19 + Monaco in the system WebView on the front. No bundled Chromium, no Node at run time.
macOS (Apple Silicon and Intel) — one line, no Gatekeeper dialog:
curl -fsSL https://raw.githubusercontent.com/Fast-Editor/Caret/main/scripts/install.sh | shor with Homebrew (7+, which no longer quarantines cask downloads): brew install --cask fast-editor/lynkr/caret.
Or download the DMG from the latest release and drag Caret to Applications. Builds are ad-hoc signed until a Developer ID is configured, so a browser-downloaded copy is quarantined and macOS reports "Caret is damaged and can't be opened". Clear it once:
xattr -dr com.apple.quarantine /Applications/Caret.app(or System Settings → Privacy & Security → Open Anyway). After that, updates install from inside the app: Settings → About → Check for updates.
Windows (.msi / -setup.exe) and Linux (.deb, .rpm, .AppImage) installers are attached to every release too. They are built
by the same workflow but have not been exercised at runtime yet; expect rough edges and please report them. The Windows installer
is unsigned, so SmartScreen shows More info → Run anyway once.
Cursor proved what an AI-native editor should feel like: Tab completions that predict your next edit, ⌘K inline edits, an agent that reads, searches, edits and runs your code with checkpoints and review, rules, memories, MCP, hooks, background agents in worktrees, Bugbot-style review. Caret rebuilds that feature set as an independent, open-source implementation engineered for memory efficiency:
| Electron editors | Caret | |
|---|---|---|
| UI runtime | bundled Chromium (~150–300 MB baseline) | OS WebView (WKWebView / WebView2 / WebKitGTK) |
| Backend | Node.js extension hosts | one Rust process (tokio), mimalloc |
| Search | ripgrep subprocess | grep + ignore crates in-process (same engine as ripgrep) |
| Index | separate indexer | tantivy (memory-mapped) + optional embeddings |
| Terminal | node-pty | portable-pty with bounded ring buffers |
| Git | git CLI / libgit2 via Node | libgit2 in-process (reads) + git CLI (network) |
Measured on the release build (see docs/PERFORMANCE.md): Rust core ~110 MB RSS idle with a workspace open, first paint ~330 ms, quick open 8 ms, project search 33 ms, hybrid codebase search 82 ms. The whole process family (core + WebKit helpers) with a TypeScript file and a terminal open is ~490 MB, versus ~393 MB for Cursor idle on the same machine — the remaining gap is the WebKit content process, and the optimisation plan is in the performance doc.
- Tab — multi-line ghost-text completions from any configured model, partial accept (⌘→), next-edit prediction ("Tab to jump"), context from recent edits, open files and diagnostics.
- ⌘K — inline edit / generate with streaming inline diff and accept/reject; quick questions; terminal ⌘K turns natural language into shell commands.
- Agent pane — Agent / Ask / Plan / Debug + custom modes; streaming transcript; tool calls (read, list, grep, fuzzy file search, codebase search, edit/write/delete, shell with approvals, web search/fetch, todo list, memory, rules, skills, subagents, MCP tools, browser automation); checkpoints, review changes with per-file accept/reject, queued messages, context-window summarization, usage & cost.
- @-mentions — Files, Folders, Code/selection, Codebase, Git (diff, staged, branch diff, commits), Web, Docs, Terminals, Lint errors, Recent changes, Past chats, Rules, Links, Notes, MCP resources, Skills; images (vision).
- Rules & knowledge —
.caret/rules/*.mdc(Always / Auto Attached / Agent Requested / Manual), legacy.cursorrules,AGENTS.md,CLAUDE.md, user rules; memories; hooks (beforeSubmitPrompt,beforeShellExecution,beforeMCPExecution,beforeReadFile,afterFileEdit,afterAgentResponse,stop…); slash commands; skills (SKILL.md); subagents; plans. - Codebase indexing — ignore-aware walker, language-aware chunking, BM25 (tantivy) + optional embeddings (Ollama / any OpenAI-compatible endpoint), hybrid retrieval, incremental updates, @Docs crawler.
- MCP — stdio / streamable-HTTP / SSE servers from
.caret/mcp.json(and Cursor's.cursor/mcp.json), tools, resources, prompts, per-tool enable, logs. - Background agents — parallel sessions, optional git worktree isolation, chat-centric Agents layout.
- Bugbot-style review — review working tree / staged / branch / commit for real defects with jump-to-line and "Fix in chat".
- Editor — Monaco (VS Code's editor core), split groups, diff editor, breadcrumbs, outline, Problems, lean LSP client (TypeScript, rust-analyzer, Pyright, gopls, clangd, JSON/CSS/HTML/YAML/Bash/Lua/Java/Ruby/Zig/TOML), git gutter + blame, markdown & image preview, 4 themes.
- Workbench — explorer with git decorations, project-wide search/replace, source control (commit UI, AI commit messages, branches, history, stashes, worktrees), integrated terminal (xterm.js, splits), command palette, quick open, keybindings editor, full settings UI, status bar with live memory badge, privacy mode.
- Providers — Anthropic (Messages API with adaptive thinking, effort, prompt caching, refusal fallbacks), OpenAI, OpenAI-compatible (Ollama, OpenRouter, LM Studio, vLLM, proxies), Azure OpenAI, Gemini (OpenAI-compatible endpoint). Bring your own keys; nothing leaves your machine except model calls.
- External agents (ACP) — pick Claude Code, Codex, Gemini CLI, OpenCode, Pi, GLM or any Agent Client Protocol agent per chat from the composer. Pick the agent's model, effort and permission mode from the composer (ACP session config options). The agent runs locally with its own login; its streaming text, thoughts, tool calls, permission prompts, diffs (checkpointed, in the review bar) and terminals all flow through Caret's UI. Install adapters from Settings → Agents. See
docs/ACP.md. - Ports — a panel listing local servers listening on your machine, with open-in-browser, copy URL and stop, and a marker for ones started from Caret's terminals.
- Auto routing — turn "Auto" into a real router: a managed Lynkr sidecar scores every request and picks one of your configured models per tier (SIMPLE → REASONING), with session pins and fallback climbing; Caret still executes with its own providers and keys and shows
Auto → model · TIERon each reply. docs/AUTO_ROUTING.md. - Run and Debug — a Debug Adapter Protocol client: breakpoints, stepping, variables, watch, call stack and a debug console for Node/TypeScript (vscode-js-debug) and Python (debugpy), adapters installed on demand,
launch.jsoncompatible. See docs/DEBUGGING.md. - Command safety & sandbox — every agent shell command is parsed (POSIX shell AST incl. nested
sh -c/eval/$( )) and classified below the model (safe / caution / destructive / blocked with labels); destructive commands always ask, blocked ones never run; approved commands run under macOS Seatbelt / Linux bubblewrap (writes limited to workspace + temp + caches, credential folders unreadable, optional no-network); MCP tool-description pinning re-approves tools that silently change. Seedocs/SANDBOX.md. - Cursor compatibility — reads
.cursor/rules,.cursor/mcp.json,.cursor/hooks.json,.cursor/commands,.cursor/skills,.cursor/agents,.cursorignore,.cursorindexingignoreso existing projects work unchanged.
v0.1.0 is released — installers for macOS (Apple Silicon, Intel), Windows and Linux are on the
releases page, built by CI from the v0.1.0 tag, with in-app updates from here on.
Verified end to end on macOS against a local OpenAI-compatible proxy and Ollama embeddings:
- Workbench: explorer with git decorations, tabs/split groups, Monaco editing, breadcrumbs, terminal (login shell, OSC title/cwd), project search (streamed), quick open, command palette, settings UI, status bar, Agents layout.
- AI: Ask and Agent sessions with streaming, tool calls (grep/read/edit/shell), approval cards with allow/deny variants, checkpoints and pending-change review, usage + cache accounting, Tab completions (cleaned FIM), ⌘K inline edit stream, AI commit messages, Bugbot-style review.
- Intelligence: LSP client with a real
typescript-language-server(diagnostics, hand-over from the built-in TS worker), codebase index (tantivy + Ollamanomic-embed-text), MCP stdio server (@modelcontextprotocol/server-everything: 16 tools, tool call round trip). - Tests: 208 Rust unit tests (incl. real Seatbelt enforcement and shell-session tests), 112 frontend tests, strict TypeScript.
Not implemented (needs cloud services or OS automation): voice dictation, computer use, cloud/remote agents, design mode, extension marketplace. SSE-only MCP servers and OAuth-protected MCP servers report a clear error. Browser automation exposes DOM snapshots, not pixel screenshots.
Open Settings → Models (⌘,): paste an API key for Anthropic / OpenAI / OpenRouter / Gemini, or point the
"OpenAI-compatible (custom)" provider at any /v1 endpoint (Ollama, LM Studio, vLLM, a proxy). "Import API
keys from environment" picks up ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, GEMINI_API_KEY.
Pick a default model, a fast model for Tab, and (optionally) an embeddings provider under Indexing & Docs. Full reference, every provider type (Azure OpenAI, Anthropic-compatible gateways, Ollama, LM Studio, vLLM, OpenRouter, Gemini), key storage and recipes: docs/PROVIDERS.md.
The project is a single repository: locally at ~/caret, published at https://github.com/Fast-Editor/Caret
(branch main; the earlier Electron prototype stays in that repo's git history). Two halves talk over Tauri IPC:
~/caret
├── src/ React 19 + TypeScript frontend (Vite 8, Tailwind 4, Monaco 0.57, xterm 6)
│ ├── main.tsx, App.tsx entry + root layout (ErrorBoundary, panes, dialogs)
│ ├── types.ts THE contract: every struct the Rust side serialises (camelCase)
│ ├── styles.css theme tokens (Caret Dark = neutral Cursor-like palette) + globals
│ ├── lib/api.ts typed `invoke` wrappers — the only place that names Rust commands
│ ├── lib/commands.ts UI command bus (`runCommand('terminal.new')` …; registry in docs/UI_COMMANDS.md)
│ ├── lib/bus.ts pub/sub topics between UI modules
│ ├── lib/tauri.ts event bridge for `caret://…` events from Rust
│ ├── lib/settingsSchema.ts settings defaults + schema that renders the Settings UI
│ ├── lib/monaco/ Monaco setup, themes, workers, git gutter, inline completions (Tab)
│ ├── lib/lsp/ language-server client glue
│ ├── lib/cliOpen.ts handles `caret <path>` opens coming from the CLI
│ ├── store/ zustand stores (chat, editor, workspace, terminal, review, settings, git, …)
│ └── components/ UI by area: layout, explorer, editor, chat (composer/, messages/), terminal,
│ review, scm, search, palette, settings, statusbar, agents, dialogs
├── src-tauri/ Rust core (Tauri 2, tokio, mimalloc)
│ ├── tauri.conf.json app id `dev.caret.editor`, bundle targets (app, dmg), dev/build commands
│ ├── Info.plist merged into the .app: folder/document types + `caret://` URL scheme
│ ├── cli/caret.sh template of the `caret` command-line launcher
│ ├── tests/fixtures/ fake ACP agent used by integration tests
│ └── src/
│ ├── lib.rs module wiring, managed state, command registration (`// REGISTER:` markers)
│ ├── core/ fs, workspace, watcher, search (grep crates), git (git2 + CLI), pty + shell
│ │ integration (OSC 133/7501/7770 blocks), lsp, settings, sessions, cli
│ ├── ai/agent/ Caret's own agent: runner loop, tools, policy/approvals, modes, rules,
│ │ memories, checkpoints, plans, subagents, hooks, review comments
│ ├── ai/providers/ Anthropic Messages API, OpenAI-compatible (Ollama/OpenRouter/vLLM/proxies), SSE
│ ├── acp/ Agent Client Protocol client: run Claude Code, Codex, Gemini CLI, OpenCode…
│ │ inside the same chat (protocol, process, client, bridge, registry)
│ ├── verify/ Verified Diff engine: diagnostics, lint, tests, mutation, dependency
│ │ grounding, security rules, project check → risk tier + proof panel
│ ├── sandbox/ shell AST classifier, macOS Seatbelt / Linux bwrap wrappers, MCP tool pins
│ ├── index/ tantivy + embeddings hybrid code index, docs indexing
│ ├── mcp/ Model Context Protocol servers (stdio + HTTP)
│ ├── browser/ DOM-snapshot browser tool
│ └── dev/automation.rs localhost bridge used by scripts/e2e.mjs for scripted UI checks
├── scripts/ e2e.mjs (bridge client), wave1-check.mjs (smoke checks)
├── docs/ ARCHITECTURE, UI_COMMANDS, VERIFIED_DIFF, SANDBOX, TERMINAL, ACP, CLI,
│ PROVIDERS, AUTO_ROUTING, RELEASING, PERFORMANCE
├── dist/ built frontend (output of `npm run build:web`; generated)
└── node_modules/ npm dependencies (generated)
Size today: ~21k lines of Rust across 112 files, ~16k lines of TypeScript across 205 files.
Conventions that keep the two halves in sync:
- Add a Rust command → register it on the matching
// REGISTER:commands:<domain>line insrc-tauri/src/lib.rs, add a wrapper insrc/lib/api.ts, and mirror any new struct insrc/types.ts(serdecamelCase). - Streams use
tauri::ipc::Channel; push events usecaret://…names and go throughsrc/lib/tauri.ts. - UI modules never import each other's internals; they call
runCommand(id)or publish on the bus.
| Tool | Version used for the current builds | Install |
|---|---|---|
| macOS | Apple Silicon, macOS 15/26 | Linux/Windows build too (Tauri 2) but are untested here |
| Xcode Command Line Tools | full Xcode 26 selected | xcode-select --install (CLT is enough) |
| Rust (stable) | rustc / cargo 1.99 (min 1.80) | curl https://sh.rustup.rs -sSf | sh |
| Node.js | 26.3 (20+ works) | nodejs.org, nvm, or Homebrew |
| npm | 11 | ships with Node |
If cargo is not on your PATH after installing rustup (common inside GUI-launched shells), export it first:
export PATH="$HOME/.cargo/bin:$PATH"
# or, when rustup placed the toolchain under ~/.rustup:
export PATH="$HOME/.rustup/toolchains/stable-aarch64-apple-darwin/bin:$PATH"Optional, only for features that call out to other tools: git (bundled with Xcode CLT), a language server such as
typescript-language-server in the project you open, node for the ACP adapters, Ollama for local embeddings.
cd ~/caret
npm install # installs React/Monaco/xterm/Vite and the Tauri CLI (`@tauri-apps/cli`)Rust crates are fetched automatically by cargo on the first build (several hundred MB in ~/.cargo).
cd ~/caret
npm run dev # = `tauri dev`: starts Vite on http://localhost:1420, compiles the Rust core in debug
# mode, opens the window, hot-reloads the frontend on save, restarts Rust on changeFirst compile takes a few minutes; later runs are incremental. Useful switches:
CARET_DEVTOOLS=1 npm run dev # open the WebView inspector
CARET_AUTOMATION=1 npm run dev # start the localhost automation bridge on http://127.0.0.1:17420
node scripts/e2e.mjs health # bridge: health | eval "<js>" | shot <name> | cmd <command-id> [json-args]
node scripts/wave1-check.mjs # smoke checks (sandbox, terminal blocks, agents, verify) against the bridgeThe bridge evaluates JavaScript inside the running app (window.__caret exposes runCommand, api and the
stores); keep it off for normal use. CARET_AUTOMATION_PORT changes the port, CARET_AUTOMATION_URL points
scripts/e2e.mjs at another instance.
npm run typecheck # tsc --noEmit
npm test # vitest (frontend unit tests; 112 today)
cd src-tauri && cargo test --lib # Rust unit + integration tests (216 today; uses the fake ACP agent)
cd src-tauri && cargo test --lib verify # one module
CARET_LIVE_TEST=1 cargo test --lib acp::tests::live -- --nocapture # opt-in: real Claude Code round tripcd ~/caret
export PATH="$HOME/.cargo/bin:$PATH" # see step 1 if cargo is not found
CARGO_TARGET_DIR=$PWD/src-tauri/target-release npm run buildWhat happens: npm run build runs tauri build, which first executes npm run build:web (typecheck + Vite
production bundle into dist/), then cargo build --release for the Rust core with dist/ embedded, then the
bundler. Output:
src-tauri/target-release/release/bundle/macos/Caret.app ← the app (ready to run or copy to /Applications)
src-tauri/target-release/release/bundle/dmg/Caret_0.1.0_aarch64.dmg
CARGO_TARGET_DIR is optional; it keeps the release artefacts apart from the debug target dir used by npm run dev so the two do not invalidate each other's caches. A clean release build takes roughly 6–10 minutes on an
M-series Mac; incremental rebuilds after frontend-only changes take 2–3 minutes.
Known wrinkle: the DMG step (bundle_dmg.sh, which calls hdiutil) occasionally fails with "Resource busy" or
hangs. The .app is already complete at that point. If it hangs, pkill -f bundle_dmg.sh lets the build finish;
if it fails, re-run the build or ignore the DMG and use the .app.
Since v0.1.0 the bundle also produces updater artifacts (Caret.app.tar.gz + .sig), which must be signed with the
project's minisign key. A local release build therefore needs the key in the environment (CI gets it from repository secrets):
export TAURI_SIGNING_PRIVATE_KEY=~/.tauri/caret.key
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$(cat ~/.tauri/caret.key.password)"Without a key, set bundle.createUpdaterArtifacts to false in src-tauri/tauri.conf.json for that build. Details in
docs/RELEASING.md.
open src-tauri/target-release/release/bundle/macos/Caret.app # run in place, or:
cp -R src-tauri/target-release/release/bundle/macos/Caret.app /Applications/Launching the bundle once (or lsregister -f <path to Caret.app>) registers the caret:// URL scheme and the
folder/document types with macOS LaunchServices. If an older Caret instance is still running, quit it first —
macOS delivers "open" events to whichever instance of that bundle is already running.
In the app, open the command palette (⌘⇧P) and run Shell Command: Install 'caret' command in PATH
(also under Settings → General). The launcher script is written to ~/.caret/bin/caret and linked from the first
user-writable directory already on your PATH (/usr/local/bin when writable, else ~/.local/bin,
/opt/homebrew/bin, ~/bin); only if none exists does macOS ask for an administrator password. Then:
caret . # open the current folder as the workspace
caret src/app.ts:42:7 # open a file at line 42, column 7 (also `-g file:line`)
caret -v / caret -h # version / usageDetails and the transport (open -b dev.caret.editor, caret://open?path=…) are in docs/CLI.md.
- User:
~/.caret/—settings.json,secrets.json(API keys),keybindings.json,mcp.json,rules/,memories/,sessions/,checkpoints/,index/,bin/caret,acp/(installed agent adapters). - Project:
<repo>/.caret/(rules, project settings), plus.cursor/rulesandAGENTS.mdare read if present. - Settings → Models for providers and keys; Settings → Agents for external ACP agents.
| Symptom | Fix |
|---|---|
cargo: command not found |
export the rustup bin dir (step 1) |
Vite build fails mentioning esbuild |
npm install again — esbuild is a required dev dependency alongside Vite 8/rolldown |
| Monaco workers 404 / editor blank in dev | the worker imports must use the package export paths, e.g. monaco-editor/editor/editor.worker.js?worker and monaco-editor/language/json/json.worker.js?worker (see src/lib/monaco/setup.ts); the esm/vs/... deep paths are not exported by Monaco 0.57 |
npm run dev restarts in a loop while a release build runs |
don't run tauri dev and tauri build at the same time in one checkout |
| DMG step hangs or "Resource busy" | see step 5 — the .app is already built |
caret . opens nothing |
launch the new bundle once so LaunchServices knows it; quit older instances |
| External agent shows "Not installed" | Settings → Agents → Install (npm into ~/.caret/acp) or install the tool yourself; Caret also searches Homebrew, nvm, volta, bun paths |
Releases are built by GitHub Actions from a tag — nothing is uploaded from a laptop:
# add a "## X.Y.Z — YYYY-MM-DD" section to CHANGELOG.md, then
npm run release:bump 0.2.0 # bumps package.json, tauri.conf.json, Cargo.toml (+ lockfiles), commits, tags v0.2.0
git push && git push --tags # → .github/workflows/release.ymlThe workflow builds macOS (Apple Silicon + Intel), Linux and Windows in parallel and drafts a GitHub Release with the
installers, signed updater artifacts, latest.json for the in-app updater and SHA256SUMS.txt. Review the draft and
publish it; installed apps pick the update up from Settings → About. CI (ci.yml) runs typecheck, vitest and cargo test
on every push. Secrets, code signing (ad-hoc today, Developer ID when configured), the Homebrew cask and troubleshooting:
docs/RELEASING.md.
See docs/ARCHITECTURE.md for the module map, IPC conventions and memory rules, docs/UI_COMMANDS.md for the UI command registry. Feature docs: PROVIDERS, AUTO_ROUTING, RELEASING, AUTO_ROUTING, VERIFIED_DIFF, SANDBOX, TERMINAL, ACP, CLI, PERFORMANCE.
MIT. Caret is an independent project and is not affiliated with Anysphere/Cursor. Monaco Editor is © Microsoft (MIT).