Emdash is a local-first, cross-platform Electron app for running multiple AI coding agents in parallel. Each task is isolated in its own Git worktree and can run locally or over SSH, while the desktop app coordinates provider CLIs, ACP chat sessions, terminal sessions, issue and PR integrations, diff review, and release packaging.
This is a pnpm workspace monorepo. The Electron app lives in
apps/emdash-desktop/ as @emdash/emdash-desktop; shared packages live under
packages/. Unless a path is prefixed with packages/ or another app, app paths
such as src/..., drizzle/, scripts/, and build/ are relative to
apps/emdash-desktop/.
Repo root:
.claude/- Local Claude agent settings for this checkout..github/- GitHub issue templates, reusable actions, CI, and release workflows.agents/- Agent-facing architecture, workflow, convention, integration, and risk docs.apps/emdash-desktop/- The Electron desktop app.packages/chat-ui/- Shared transcript and ACP chat renderer with Storybook coverage.packages/core/- Transport-agnostic runtime primitives, including ACP session logic.packages/plugins/- Agent provider plugin definitions, hooks, and ACP adapters.packages/shared/- Shared primitives such as result types, logging, and markdown helpers.packages/ui/- Shared React UI components, theme tokens, recipes, and primitives.pnpm-workspace.yaml- Workspace package globs forapps/*andpackages/**.- Root config files -
package.json,nx.json,.nvmrc,.oxfmtrc.json,.oxlintrc.json, and lockfile/configuration owned at the workspace root.
Inside apps/emdash-desktop/:
build/- Electron packaging assets; avoid edits unless working on packaging/signing.drizzle/- Generated Drizzle SQL migrations and metadata.scripts/- Release, verification, and build support scripts.src/main/- Electron main process, RPC controllers, services, DB, ACP, PTY, and SSH.src/preload/- Typed Electron preload bridge exposed to the renderer.src/renderer/- React app organized aroundapp/,features/,lib/, and tests.src/shared/- App IPC primitives, provider metadata, events, ACP, MCP, skills, and types.src/types/- Ambient and cross-cutting TypeScript declarations.tooling/- App-level development and test infrastructure not bundled into production.
Use Node 24.14.0 from .nvmrc and pnpm@10.28.2. Root scripts are powered by Nx
and run package targets in dependency order with local caching where configured.
Install dependencies from the repo root:
pnpm installStart the full workspace dev setup from the repo root:
pnpm run devStart only the Electron app from apps/emdash-desktop/:
cd apps/emdash-desktop
pnpm run dev
pnpm run dRun main-process or renderer-only dev watches from apps/emdash-desktop/:
pnpm run dev:main
pnpm run dev:rendererRun with debug logging from apps/emdash-desktop/:
pnpm run dev:debugUse an isolated development database for schema or migration work:
EMDASH_DB_FILE=/tmp/emdash-scratch.db pnpm run devReset dev databases from apps/emdash-desktop/:
cd apps/emdash-desktop
pnpm run db:resetBuild all workspace projects from the repo root:
pnpm run buildBuild only the app targets from apps/emdash-desktop/:
cd apps/emdash-desktop
pnpm run build
pnpm run build:main
pnpm run build:rendererPackage desktop artifacts locally from apps/emdash-desktop/:
cd apps/emdash-desktop
pnpm run package
pnpm run package:mac
pnpm run package:linux
pnpm run package:winRun formatting, linting, type checks, and tests from the repo root:
pnpm run format
pnpm run lint
pnpm run typecheck
pnpm run testRun focused database validation from apps/emdash-desktop/:
pnpm run db:setup
pnpm run db:fixtures
pnpm run test:migrationsRun Docker-backed SSH development infrastructure from apps/emdash-desktop/:
cd apps/emdash-desktop
pnpm run run:docker-sshRun Storybook for shared UI packages:
pnpm --filter @emdash/ui run storybook
pnpm --filter @emdash/chat-ui run storybookRebuild native Electron dependencies after native dependency changes from
apps/emdash-desktop/:
cd apps/emdash-desktop
pnpm run rebuildClean and reset app dependencies from apps/emdash-desktop/:
cd apps/emdash-desktop
pnpm run clean
pnpm run resetDeploy releases only when explicitly asked to do release work:
gh workflow run release-prod.yml --ref main -f arch=both
gh workflow run release-canary.yml --ref main -f arch=bothProduction releases publish artifacts to GitHub Releases and Cloudflare R2. Canary releases currently publish to R2 only.
- Use
pnpm; do not introduce npm or yarn lockfile churn. - Format with
oxfmt; config is.oxfmtrc.json. - Keep formatted lines near the configured
printWidthof 100 characters. - Use 2 spaces, semicolons, single quotes in TS, double quotes in JSX, LF endings, trailing commas where valid in ES5, and sorted imports.
- Lint with
oxlint; config is.oxlintrc.jsonwith correctness, TypeScript, React hooks, and local repo rules enabled. - TypeScript strict mode is enabled; app targets share
apps/emdash-desktop/tsconfig.json. - Avoid
any; if a registry or boundary needs it, keep the escape local and documented. - Use top-level
importstatements; do not userequire(). - Never re-export as a shortcut; import from the original source.
- Components use
PascalCase; hooks useuseXcamelCase or an existing local pattern. - Tests use
*.test.tsor*.test.tsx. - Main-process RPC handlers live in
src/main/core/*/controller.tsand delegate to imported operation or service functions. - Renderer RPC calls go through
rpcfromsrc/renderer/lib/ipc.ts. - Feature UI lives under
src/renderer/features/<feature>/; shared renderer primitives, stores, hooks, modals, PTY, Monaco, and UI live undersrc/renderer/lib/. - New modals must be registered in
src/renderer/app/modal-registry.ts. - New views must be registered in
src/renderer/app/view-registry.ts. - New task tabs should use
src/renderer/features/tasks/task-tab-registry.tsx. - New commands should use
src/renderer/lib/commands/registry.tsand view-levelcommandProviderhooks where possible. - Commit messages should follow Conventional Commits:
<type>(<scope>): <short imperative summary>
Examples:
fix(opencode): change initialPromptFlag from -p to --prompt for TUI
feat(docs): add changelog tab with GitHub releases integration
flowchart LR
User[User] --> Renderer[React renderer]
Renderer --> RPC[Typed RPC client and events]
RPC --> Preload[Electron preload bridge]
Preload --> Main[Electron main process]
Main --> Controllers[src/main/core controllers]
Controllers --> Services[Domain services]
Services --> DB[(SQLite via Drizzle)]
Services --> Runtime[Runtime services]
Runtime --> PTY[PTY sessions]
Runtime --> ACP[ACP sessions]
Runtime --> SSH[SSH and SFTP]
Services --> VCS[Git, GitHub, GitLab, PRs]
Services --> Issues[Issue integrations]
Services --> MCP[MCP and skills]
ACP --> CoreAcp[@emdash/core ACP runtime]
ACP --> Plugins[@emdash/plugins providers]
Renderer --> ChatUI[@emdash/chat-ui]
Main --> Events[Typed events]
Events --> Renderer
The app boots from src/main/index.ts, loads environment and database state,
registers RPC controllers through src/main/rpc.ts, creates the Electron window,
and exposes a small typed preload API from src/preload/index.ts. The renderer is
a React app that calls typed RPC methods, subscribes to typed events, and coordinates
views, tabs, modals, command providers, project state, terminal state, and task
workflows.
Task execution has two runtime paths. Legacy/TUI conversations run through PTY
services under src/main/core/pty/ and src/main/core/terminals/. Structured chat
conversations use ACP: provider plugins in packages/plugins/ expose ACP behavior,
packages/core/src/acp/ owns protocol/session state and terminal management,
src/main/core/acp/ adapts that runtime to Electron RPC/events and local/SSH process
hosts, and src/renderer/features/conversations/acp/ maps updates into @emdash/chat-ui.
Major main-process domains live under src/main/core/: account, ACP, agents,
agent hooks, app, automations, browser, conversations, dependencies, editor,
filesystem, Git, GitHub, GitLab, integrations, MCP, preview servers, projects,
project setup, prompt library, PTY, pull requests, resource monitor, runtime, search,
secrets, settings, skills, SSH, tasks, telemetry, terminal shell, terminals, updates,
view state, and workspaces. Expected failures should use the Result<T, E> pattern
from @emdash/shared or the app-local result helpers.
Local merge gate:
pnpm run format
pnpm run lint
pnpm run typecheck
pnpm run test- Root
pnpm run testuses Nx to run every workspace package test target. - App tests run with Vitest projects in
apps/emdash-desktop/vitest.config.ts. - App
nodetests coversrc/**/*.test.tsexcept DB, migration, and browser tests. - App
main-dbtests cover main-process integration tests that need real SQLite. - App
fixturestests generate DB fixtures viapnpm run db:fixtures. - App
migrationstests validate Drizzle migrations viapnpm run test:migrations. - App
scriptstests cover release and support scripts underscripts/**/*.test.ts. - App
browsertests use Playwright-backed@vitest/browserfor renderer behavior. packages/corehas ACP, dependency, plugin helper, Git, FS, and runtime unit tests.packages/chat-uihas node, browser, perf, and benchmark test targets.packages/ui,packages/shared, andpackages/pluginsrun their package-local tests.- Integration-style tests create temporary repos and worktrees in
os.tmpdir(). - CI runs
.github/workflows/code-consistency-check.ymlwithnx affectedforformat:check,typecheck, andlinton touched projects and dependents. - Tests are still expected locally before merge even where CI coverage is narrower.
- The project is licensed under Apache-2.0; see
LICENSE.md. - Do not commit secrets, tokens, private keys, app databases, logs, build artifacts, generated dependency folders, or release artifacts.
- Application secrets are stored through encrypted app secret services and Electron safe storage; SSH credentials are managed through SSH services and OS-backed storage.
- Release secrets live in GitHub Actions secrets, including PostHog, Cloudflare R2, Apple signing/notarization, Azure Trusted Signing, and Cachix credentials.
- Telemetry must remain optional; users can disable it with
TELEMETRY_ENABLED=falseor in app settings. - File logging must preserve redaction of common secret patterns.
- PTY environment passthrough must use the allowlist in
src/main/core/pty/pty-env.ts. - Treat ACP process spawning, SSH command construction, shell escaping, PTY spawning, and worktree paths as security-sensitive.
- Do not bypass path-safety, shell escaping, or validation helpers.
- Use
pnpm-lock.yamlfor dependency integrity and review dependency changes. - This checkout does not define repo-local Dependabot config, CODEOWNERS, or SECURITY.md;
do not assume extra repository-owned security automation beyond the workflows present
in
.github/workflows/. - Dependency changes must keep
pnpm-lock.yamlin sync, preservepackageManagerandpnpm.onlyBuiltDependencies, and avoid introducing new install scripts or native builds without explicit review. - CI installs with
pnpm install --frozen-lockfile --ignore-scriptsin.github/workflows/code-consistency-check.yml; changes that rely on install-time side effects need clear justification. - Prefer existing dependencies and workspace packages over adding new third-party packages. When adding a dependency, document why the existing stack is insufficient and check license/security posture before committing the lockfile change.
- Start with this file for repo-wide context and required commands.
- Load only the relevant
agents/topic page for the area you are changing. - Prefer updating the smallest applicable
agents/page over expanding this file. - If nested
AGENTS.mdfiles are added later, the closest file to the edited path wins. - Explicit user or maintainer instructions override this file.
- Do not hand-edit numbered Drizzle migrations or
drizzle/meta/. - Use
pnpm run db:generatefor new migrations, then update fixtures and migration tests. - Avoid editing
dist/,release/,out/,build/, and generated package artifacts unless the task is explicitly about packaging, signing, or release behavior. - Do not dispatch release workflows, publish packages, upload artifacts, or trigger external deployments unless the user explicitly asks for release work.
- Treat
src/main/core/acp/,src/main/core/pty/,src/main/core/ssh/,src/main/db/, updater code, and provider process spawning as high risk. - Read the matching
agents/risky-areas/page before touching database, PTY, SSH, or updater code. - Do not weaken shell quoting, spawn behavior, env allowlists, path validation, or secret redaction casually.
- Prefer existing service, provider, plugin, RPC, modal, view, tab, and store patterns over new abstractions.
- New RPC methods belong in the appropriate
src/main/core/*/controller.tsand must be registered throughsrc/main/rpc.ts. - Keep renderer-main calls on typed RPC and typed events. The preload bridge should stay
small; add direct
window.electronAPIsurface only when an Electron/browser primitive cannot fit the RPC/event path. - Access task and project MobX stores through selectors and task view hooks:
getTaskStore,asProvisioned,taskViewKind,getTaskManagerStore,getProjectStore,asMounted,useTaskViewKind,useWorkspace,useWorkspaceId,useDevServers,useWorkspaceViewModel,useConversations, anduseTerminals. - Never use
asProvisioned(...)!orasMounted(...)!; use explicit null checks. - State guards must check
kind !== 'ready'rather than enumerating non-ready states. - Access task managers through
getTaskManagerStore(projectId), notproject.taskManager. - Access mounted projects through
asMounted(getProjectStore(id)), not inline guards. - Task selectors live in
src/renderer/features/tasks/stores/task-selectors.ts. - Project selectors live in
src/renderer/features/projects/stores/project-selectors.ts. - For provider changes, update plugin metadata, shared provider metadata, ACP support flags, PTY env passthrough if needed, hook integrations, renderer assumptions, and tests for non-standard behavior.
- For ACP changes, preserve protocol state-machine behavior in
packages/core/src/acp/, keep provider-specific transforms inpackages/plugins/, and adapt UI payloads at app or chat-UI edges. - For MCP changes, keep canonical data in shared types and adapt provider formats at edges.
- Follow
.github/PULL_REQUEST_TEMPLATE.md: keep PRs small and focused, self-review before handoff, list checks run, attach UI evidence when applicable, and update docs and tests when behavior changes. - Call out high-risk changes explicitly in the PR description or handoff notes, especially database, ACP, PTY, SSH, updater, provider spawning, dependency, and release-related changes.
- Do not self-approve, merge, assign reviewers, edit branch protection, or change workflow permissions unless the user explicitly asks.
- Avoid scripted loops against GitHub, Linear, Jira, GitLab, provider CLIs, or release workflows. Use focused queries and respect existing workflow retries; ask before adding polling, bulk API calls, or scheduled automation.
- Keep automation scoped to the task. Do not run the full local merge gate repeatedly when a focused check is enough during iteration; run broader checks before handoff when the change scope justifies it.
- Agent provider plugins live in
packages/plugins/src/agents/impl/and are registered inpackages/plugins/src/agents/registry.ts. - Provider capabilities and helpers live in
packages/core/src/agents/plugins/. - Agent provider metadata and capabilities live in
packages/plugins/src/agents/registry.tsandpackages/plugins/src/agents/impl/; renderer-facing DTOs are built bysrc/main/core/agents/agent-payload-builder.ts. - ACP support is exposed through plugin ACP capabilities and
src/shared/core/acp/event and turn types. - Provider detection lives in
src/main/core/dependencies/. - Provider PTY behavior and env passthrough live under
src/main/core/pty/. - Provider event hooks and plugins live under
src/main/core/agent-hooks/. - ACP process hosts live under
src/main/core/acp/transport/for local and SSH-backed execution. - Modal definitions are centralized in
src/renderer/app/modal-registry.ts. - View definitions and navigation guards are centralized in
src/renderer/app/view-registry.ts. - Task tab providers are centralized in
src/renderer/features/tasks/task-tab-registry.tsx. - MCP server config handling lives in
src/main/core/mcp/services/McpService.ts,src/main/core/mcp/utils/,src/shared/core/mcp/, andsrc/renderer/features/mcp/. - Skills types and validation live under
src/shared/core/skills/; skills UI and service code live insrc/renderer/features/skills/andsrc/main/core/skills/. - Worktree runtime settings can be supplied through
.emdash.json:preservePatterns,scripts.setup,scripts.run,scripts.teardown, andshellSetup. - Project settings such as
worktreeDirectory,defaultBranch,baseRemote,pushRemote,tmux, andworkspaceProviderare DB-backed, not.emdash.json. - Optional environment variables include
TELEMETRY_ENABLED,EMDASH_DB_FILE,EMDASH_DISABLE_NATIVE_DB,EMDASH_DISABLE_CLONE_CACHE,EMDASH_DISABLE_PTY,CODEX_SANDBOX_MODE, andCODEX_APPROVAL_POLICY. - Build-time telemetry configuration may use
VITE_POSTHOG_KEYandVITE_POSTHOG_HOST. - Runtime feature flags are read through telemetry-backed feature flag helpers.
- App path aliases are defined in
tsconfig.jsonand mirrored inelectron.vite.config.ts:@/*,@renderer/*,@main/*,@shared/*,@root/*, and@tooling/*. - Versioned JSON column schemas use
defineVersionedSchema()fromsrc/shared/lib/versioned-schema/versioned-schema.tsand DrizzleversionedJsonColumn()fromsrc/main/db/versioned-column.ts.
- Agent docs map
- Quickstart
- Architecture overview
- Main process architecture
- Renderer architecture
- Shared modules
- Nx task orchestration and caching
- Testing workflow
- Worktrees workflow
- Remote development workflow
- Workspace server architecture
- Provider integration
- MCP integration
- IPC conventions
- Main-process patterns
- Renderer patterns
- TypeScript and React conventions
- Config file rules
- UI styling conventions
- Versioned schema conventions
- Database risk notes
- PTY risk notes
- SSH risk notes
- Updater risk notes
- Contributing guide
- Project README