---
title: Architecture
description: How meith is built: an Electron workbench backed by one local tool runtime.
section: Developers
sectionOrder: 2
order: 20
slug: developers/architecture
---
meith is an Electron desktop workbench backed by a local tool runtime. The main
process is the authority for state and side effects. The renderer, CLI, plugins,
and agent runtime all reach application capabilities through the same validated
ToolRegistry.
That design keeps the app consistent: opening a tab from the UI, running
meith open, or letting an agent control a browser tab all go through the same
tool definition, validation, permission, logging, and persistence path.
| Package | Role |
|---|---|
@meith/shared |
Zod schemas and inferred types for app state, tabs, projects, tools, agents, plugins, logs, settings, IDs, and result helpers. |
@meith/protocol |
Tool contracts, tool descriptors, NDJSON protocol messages, naming helpers, and public plugin bridge types. |
@meith/desktop |
Electron main/preload/renderer, services, tool registration, socket server, IPC, browser/terminal hosts, agents, plugins, storage, and packaging. |
@meith/cli |
Terminal client that discovers a running runtime and calls tools over the local socket. |
@meith/web |
Next.js documentation and marketing site under apps/web. |
The runtime is centered on packages/desktop/src/main/bootstrap.ts.
bootstrap(userDataPath, options) wires the services, registers tools, starts
the local socket server, writes config, publishes an instance record, hydrates
state, and returns the service container.
Renderer IPC ─────┐
CLI socket ───────┤
Plugin bridge ────┤
Agent MCP bridge ─┼── ToolRegistry ── services ── app state / files / browser / processes
Internal calls ───┘
ToolRegistry.call() is the common choke point. It:
- rejects unknown tools,
- validates arguments with each tool's Zod schema,
- asks
PermissionServiceto authorize privileged calls, - applies timeout and cancellation handling,
- passes an abort signal and optional event emitter to the tool,
- normalizes returned values into a
ToolResult, - logs and audits every call.
The main services are created in bootstrap.ts:
AppStateServiceowns persisted app state and emits reactive state changes.BrowserTabServiceowns browser/workspace tab records and delegates live web contents to aBrowserViewHost.SpaceServicecreates, updates, switches, and closes workspaces.ProjectServicedetects folders, opens projects into spaces, generates templates, prewarms generated projects, and starts project run commands.WorkspaceFileServicereads, writes, patches, searches, and diagnoses files inside trusted workspace boundaries.DevServerServicestarts and tracks managed dev-server processes and logs.TerminalServicestarts and tracks terminal sessions.PluginHostServiceinstalls plugins, stores requested and approved grants, resolves plugin tab identity, and gates plugin bridge APIs.AgentServicestores sessions and messages, runs the configured adapter, and gates agent tool calls.McpBridgeServiceexposes per-agent-session tools over a localhost MCP-style HTTP endpoint for external ACP agents.PermissionServiceauthorizes non-renderer privileged calls and writes audit entries.StorageServiceexposes read-only storage introspection tools.ToolSocketServiceexposes the registry over a local NDJSON socket.
Runtime data lives under the user data path passed to bootstrap(). In normal
use, discovery data lives under ~/.meith:
~/.meith/config.jsonrecords the active runtime socket, app version, protocol version, user data path, instance path, and managed CLI launcher.~/.meith/instances/<pid>.jsonrecords live runtime instances so the CLI can list, target, or kill them.<userData>/state.jsonstores spaces, projects, browser tabs, workspace tabs (including editor file focus and git-tab selected file), file edit events, plugins, and app settings.<userData>/logs.jsonlstores app logs.<userData>/audit.jsonlstores tool authorization/audit records.<userData>/artifacts/stores screenshot and bug-report artifacts.<userData>/plugins/stores extracted packaged plugins.<userData>/agent/sessions.jsonstores agent session metadata.<userData>/agent/transcripts/*.jsonlstores per-session agent transcript records.- agent config is managed by
AgentConfigStore.
JsonStore writes bounded JSON atomically and uses migrations before schema
validation. Corrupt state is backed up and reset to defaults instead of crashing
the app. JsonlStore stores append-only logs, audit records, and compact agent
transcript records. Long transcript streams are periodically compacted into
message snapshots so startup and session hydration stay bounded.
A space is the visible workspace in the left rail. A project is a folder on disk.
Opening a folder with project_open detects package metadata, creates or reuses
a dedicated space, records a project, and opens an editor tab rooted at the
folder.
ProjectService detects:
- project name,
- package manager (
pnpm,npm,yarn,bun, orunknown), - framework hints such as Next.js, Vite, React, Vue, Svelte, Astro, Remix, Node,
or
unknown, - package scripts.
Run commands live on the project record. The top bar's Run button calls
project_run, which uses the configured command or falls back to a detected
dev/start-style script. Dev servers are associated by cwd, their output is
captured, and detected ports can be opened in browser tabs.
Generated projects are copied from templates/ into ~/Documents/meith by
default. project_prewarm can keep generated app copies ready so creating a new
workspace is fast.
Browser tab metadata is persisted in app state. Live browser views are supplied
by a BrowserViewHost:
- Electron uses
ElectronBrowserViewHostbacked by nativeWebContentsViews. - Headless tests and harness runs use
HeadlessBrowserViewHost.
The renderer measures the actual content area and reports it over
meith:browser:viewport; the main process sizes the native view to that region.
When settings, overlays, or split-drag drop zones need DOM interaction above the
native view, the renderer temporarily collapses the view.
Every WebContentsView created by ElectronBrowserViewHost is hardened
immediately after construction via hardenWebContents():
setPermissionRequestHandleris set to deny all OS-level permission requests (camera, microphone, geolocation, notifications, MIDI, HID, serial, Bluetooth, clipboard-read, fullscreen).setWindowOpenHandleris set to deny allwindow.open()andtarget=_blanknavigations. Legitimate outbound navigation must go throughopen_browser_tab.
Browser tools include tab listing, open/navigate/back/forward/refresh/focus/close, screenshot capture, DOM state extraction, element clicks, typing, scrolling, keyboard input, CDP commands, console logs, and network logs.
Automation callers (agent and plugin) must claim a tab with
browser_use_start before mutating it. Interactive callers (renderer, cli)
can control unclaimed tabs directly. Ownership conflicts return
PERMISSION_DENIED.
The renderer is a React and Vite workbench in
packages/desktop/src/renderer/src. It uses the preload bridge exposed as
window.meith; in browser-only preview mode it falls back to an in-memory mock
bridge.
Major surfaces:
SpacesRailfor switching, creating, opening, closing, and inspecting spaces.TabStripandPaneToolbarfor browser/workspace tab management.BrowserAreafor the embedded browser tab controls and native view target.EditorViewfor Monaco-backed file editing throughworkspace_*tools.TerminalViewfor PTY sessions.AgentViewfor session list, transcript, composer, stop button, and permission cards.GitPanelfor the Git tab with staged/unstaged trees, commit controls, stage-all, guarded restore actions, AI commit-message suggestions via the renderer completion bridge, refreshed summary counts, persisted selected-file state, and lazy patch loading for the selected file.SettingsViewfor app preferences, per-project run commands, agent config, plugin management, and about info.DebugPanelfor tool runner, state, logs, and output diagnostics.StatusBarfor connection, tab counts, running process count, and active port.
The renderer does not mutate services directly. It calls tools or dedicated IPC
stream channels, then re-renders from pushed app state. Short AI-assisted UI
features use window.meith.ai.complete, a one-shot completion bridge backed by
the configured agent adapter rather than a chat transcript.
High-frequency app-state and dev-server updates are scheduled with React transitions so process logs, status updates, and large state pushes do not block typing, dragging tabs, or interacting with the browser surface.
The CLI resolves a target runtime from --socket, --instance, live instance
records, or ~/.meith/config.json. It sends NDJSON frames to the runtime socket.
Common surfaces:
meith [path]launches the app and optionally opens a project path.meith new [name]creates and opens a generated project.- mapped commands such as
open,tabs,navigate,screenshot,processes,dev-servers, andstart-devcall specific tools. - mapped commands also cover spaces/workspace tabs, projects/templates, workspace file read/write/search/diagnostics, git diff, browser automation, terminal control, settings, storage, plugins, and runtime diagnostics.
meith call <toolName>can invoke any registered tool.meith toolslists runtime tools.meith devlogsattaches to a managed dev server's log stream.meith app <list|logs|health|bug-report|kill|screenshot>inspects or controls runtime instances.- The packaged app bundles the CLI and Node runtime, then writes a managed
launcher to
~/.meith/bin/meithon startup. First-time setup is~/.meith/bin/meith setupor~/.meith/bin/meith setup --write; after the shell PATH is updated,meith setupworks like any other CLI command.
AgentService is implemented. It manages durable sessions, transcripts,
configuration, permission prompts, and adapter execution.
The default adapter is MockAdapter, which keeps the UI usable without an
external agent. When configured for acp, AcpAdapter spawns an external ACP
subprocess, initializes it, creates an ACP session, and passes a localhost MCP
endpoint so the external agent can call meith tools.
Agent tool calls use caller: "agent" and the agent session id. Read-only tools
run directly. Privileged tools require an explicit permission decision unless
auto-accept is enabled. Approved one-use grants are written into
PermissionService before the registry call.
ACP permission requests are only approved at the ACP layer when the requested
tool name maps to a tool exposed by the per-session MCP server named meith.
Provider-native tools, other MCP servers, and unknown helper surfaces are denied
before they can bypass AgentService, PermissionService, or browser
ownership.
A plugin is a web app loaded in a controlled plugin browser tab. It does not
register code into the main process and does not receive Node access. Instead,
the plugin preload exposes window.meithPlugin only when the main process
recognizes the tab as an enabled plugin.
Plugin manifests declare requested capabilities and API namespaces. The user approves a subset of those requests. Runtime enforcement uses only approved grants.
Plugin archive extraction enforces hard limits: 50 MB maximum compressed
archive size, 2 000 maximum file entries, and 10 MB maximum per-file size.
Extraction also rejects path traversal, hard links, and symbolic links. The
WorkspaceFileService listing and search walks skip symbolic link directory
entries entirely, preventing a symlink inside a workspace from being used to
read files outside the project boundary.
Approved API namespaces:
toolslists and calls registry tools, still gated by approved capabilities.storagereads browser and workspace tab lists.cdpsends Chrome DevTools Protocol commands.aistreams text through the app's agent runtime.
See PLUGIN_API.md for author-facing details.
Desktop app:
pnpm devRenderer-only mock mode:
pnpm dev:rendererHeadless main-process runtime:
pnpm --filter @meith/desktop dev:headlessFull verification:
pnpm checkDesktop packaging stages a bundled Node runtime into
packages/desktop/vendor/node-runtime and a self-contained CLI runtime into
packages/desktop/vendor/cli-runtime before electron-builder runs. Packaged
app processes resolve Meith-owned Node/npm/npx tooling from the app bundle
first, not from the user's machine. Built-in ACP presets launch through the
bundled npx, which may fetch ACP packages from the npm registry into Meith's
managed npm cache. Template node_modules are excluded from the app package so
generated projects do not inherit dependencies from the build machine.
The packaging verifier checks the staged Node/npm/npx runtime, CLI dependencies,
templates, and the native node-pty spawn-helper before signing. Local macOS
builds are ad-hoc signed so the generated .app, ZIP, and DMG are internally
valid and runnable. Ad-hoc signing is not Developer ID signing or notarization.
The public web app is deployed from apps/web. vercel.json skips Vercel
builds when a commit does not touch apps/web, so desktop-only changes do not
trigger unnecessary web deployments.