Overview
The main process (src/main/) is the Node.js backend of Emdash. It has full access to the filesystem, system APIs, and native modules. The renderer communicates with it exclusively through IPC (Inter-Process Communication).
Key responsibilities:
- Manage application lifecycle and native OS integration
- Handle all file system operations and git commands
- Spawn and manage PTY (pseudo-terminal) sessions for CLI agents
- Maintain SQLite database with Drizzle ORM
- Orchestrate worktree creation and pooling
- Manage SSH connections for remote development
- Expose secure IPC handlers for renderer
Core services
All services follow a singleton pattern:DatabaseService
src/main/services/DatabaseService.ts - All SQLite CRUD operations using Drizzle ORM.
Schema: Defined in src/main/db/schema.ts:
- macOS:
~/Library/Application Support/emdash/emdash.db - Linux:
~/.config/emdash/emdash.db - Windows:
%APPDATA%\emdash\emdash.db - Override: Set
EMDASH_DB_FILEenvironment variable
drizzle/meta/ or numbered migration files.
WorktreeService
src/main/services/WorktreeService.ts - Git worktree lifecycle and file preservation.
What are worktrees? Each task runs in an isolated git worktree, allowing multiple agents to work on different branches simultaneously without conflicts.
Worktree creation:
- Directory:
../worktrees/{slugged-name}-{3-char-hash}/ - Branch:
{prefix}/{slugged-name}-{hash} - Prefix defaults to
emdash, configurable in settings
.emdash.json at project root:
WorktreePoolService
src/main/services/WorktreePoolService.ts - Eliminates 3-7 second worktree creation delay.
How it works:
-
Pre-creation: When a project opens, creates a reserve worktree in background:
-
Instant claim: When user creates a task, instantly renames the reserve:
- Replenish: Creates a new reserve in the background
- Cleanup: Reserves expire after 30 minutes, cleaned on app startup
ptyManager
src/main/services/ptyManager.ts - PTY lifecycle, agent spawning, session isolation.
What is PTY? A pseudo-terminal that emulates a terminal session. Agents like claude, codex, amp run inside PTYs, streaming output back to the UI.
Three spawn modes:
1. Shell-based spawn (startPty)
Wraps the agent in a shell for post-exit shell access:2. Direct spawn (startDirectPty)
Spawns the agent binary directly without shell wrapper:- CLI path isn’t cached
- Project has
shellSetupin.emdash.json
3. SSH spawn (startSshPty)
For remote development:process.env.
The definitive passthrough list:
AGENT_ENV_VARS.
Session isolation: For Claude Code, each conversation gets a unique session ID:
{userData}/pty-session-map.json.
PTY ID format (src/shared/ptyId.ts):
claude-main-task-abc123codex-chat-conv-def456
Other key services
GitService (src/main/services/GitService.ts)
- Git operations: status, diff, commit, push, branch management
- Uses
simple-gitlibrary
src/main/services/GitHubService.ts)
- GitHub operations via
ghCLI - PR creation, check runs, repository info
src/main/services/SkillsService.ts)
- Agent Skills standard implementation
- Cross-agent skill packages with YAML frontmatter
- Central storage:
~/.agentskills/{skill-name}/ - Symlinks to agent-native directories:
~/.claude/commands/,~/.codex/skills/
src/main/services/ssh/SshService.ts)
- SSH connection management
- Password, key, and agent authentication
- Credentials stored in OS keychain via
keytar
src/main/services/TaskLifecycleService.ts)
- Lifecycle script orchestration
- Runs scripts from
.emdash.json:
IPC handler pattern
All services expose functionality via IPC handlers. See IPC handlers for details. Example service with IPC:{ success: boolean, data?: any, error?: string }.
Error handling
Main process uses structured logging:- Console (during development)
- Log files:
{userData}/logs/main.log(production)
Testing
Services tested with Vitest using mocks:src/test/main/ (15 service tests).
Next steps
- IPC handlers - Complete list of IPC channels and request/response types
- Renderer process - How the UI communicates with these services