Mortise is an independently maintained, user-extensible desktop Agent platform. It combines a multi-session Electron client, a headless/WebUI server, the Pi agent runtime, skills, automations, and extension-contributed UI.
This repository owns its release line and package identities. Mortise does not import updates from the repositories it was forked from, and it does not assume any hosted Mortise cloud service exists. Optional update, viewer, OAuth relay, and documentation MCP endpoints are deployment configuration.
git clone https://github.com/hrhgit/mortise.git
cd mortise
bun install
bun run electron:startOn Windows, double-click Mortise-Desktop-Tool.cmd to start, restart, or package the source desktop application, launch or stop the WebUI, or build the Developer Kit. Platform-specific package commands remain available through the root package.json. The existing icon is a temporary placeholder until Mortise artwork is ready.
- Multi-Session History: Desktop app with session management, unread tracking, search, and deletion
- Claude Code Experience: Streaming responses, tool visualization, real-time updates
- Multiple LLM Connections: Add multiple AI providers with app defaults and per-session overrides
- Multi-Provider Support: Run sessions with Google AI Studio, ChatGPT Plus, GitHub Copilot, or OpenAI API keys alongside Anthropic
- MCP Integration: Connect local and remote Model Context Protocol servers
- Bundled Extensions: Precompiled extensions can own session controls, approvals, and rich conversation UI
- Background Tasks: Run long-running operations with progress tracking
- Dynamic Status System: Customizable session workflow states (Todo, In Progress, Done, etc.)
- Theme System: Cascading themes at app and workspace levels
- Multi-File Diff: VS Code-style window for viewing all file changes in a turn
- Skills: Specialized agent instructions stored per-workspace
- File Attachments: Drag-drop images, PDFs, Office documents with auto-conversion
- Automations: Event-driven automation — create agent sessions on label changes, schedules, tool use, and more
- Launch the app after installation
- Choose API Connection: Use Anthropic (API key or Claude Max), Google AI Studio, ChatGPT Plus (Codex OAuth), or GitHub Copilot OAuth
- Create a workspace: Set up a workspace to organize your sessions
- Start chatting: Create sessions and interact with Claude
- Inbox/Archive: Sessions organized by workflow status
- Flagging: Mark important sessions for quick access
- Status Workflow: Todo → In Progress → Needs Review → Done
- Session Naming: AI-generated titles or manual naming
- Session Persistence: Full conversation history saved to disk
| Shortcut | Action |
|---|---|
Cmd+N |
New chat |
Cmd+1/2/3 |
Focus sidebar/list/chat |
Cmd+/ |
Keyboard shortcuts dialog |
Enter |
Send message |
Shift+Enter |
New line |
Mortise can run as a headless server on a remote machine (e.g., a Linux VPS), with the desktop app connecting as a thin client. This lets you keep long-running sessions alive, access them from multiple machines, and run compute-heavy tasks on a powerful server.
From the monorepo root:
# Generate a token and start the server
MORTISE_SERVER_TOKEN=$(openssl rand -hex 32) bun run packages/server/src/index.tsThe server prints the connection details on startup:
MORTISE_SERVER_URL=ws://203.0.113.5:9100
MORTISE_SERVER_TOKEN=<generated-token>
Copy these values and use them to connect the desktop app.
Launch the Electron app in thin-client mode by passing the server URL and token:
MORTISE_SERVER_URL=wss://203.0.113.5:9100 MORTISE_SERVER_TOKEN=<token> bun run electron:startIn thin-client mode, the desktop app renders the UI but all session logic, tool execution, and LLM calls run on the remote server.
| Variable | Required | Default | Description |
|---|---|---|---|
MORTISE_SERVER_TOKEN |
Yes | — | Bearer token for client authentication |
MORTISE_RPC_HOST |
No | 127.0.0.1 |
Bind address (0.0.0.0 for remote access) |
MORTISE_RPC_PORT |
No | 9100 |
Bind port |
MORTISE_RPC_TLS_CERT |
No | — | Path to PEM certificate file (enables wss://) |
MORTISE_RPC_TLS_KEY |
No | — | Path to PEM private key file (required with cert) |
MORTISE_RPC_TLS_CA |
No | — | Path to PEM CA chain file (optional, for client cert verification) |
MORTISE_DEBUG |
No | false |
Enable debug logging |
When exposing the server over the network, TLS encrypts the WebSocket connection (wss:// instead of ws://).
Generate a self-signed certificate (development/testing):
./scripts/generate-dev-cert.sh
# Creates certs/cert.pem and certs/key.pem (valid 365 days)Start the server with TLS:
MORTISE_SERVER_TOKEN=<token> \
MORTISE_RPC_HOST=0.0.0.0 \
MORTISE_RPC_TLS_CERT=certs/cert.pem \
MORTISE_RPC_TLS_KEY=certs/key.pem \
bun run packages/server/src/index.tsThe server will print MORTISE_SERVER_URL=wss://<your-public-ip>:9100.
For production, use certificates from a trusted CA (e.g., Let's Encrypt) or place the server behind a reverse proxy (nginx, Caddy) that terminates TLS.
docker run -d \
-p 9100:9100 \
-e MORTISE_SERVER_TOKEN=<token> \
-e MORTISE_RPC_HOST=0.0.0.0 \
-v mortise-data:/root/.mortise \
mortise-serverTo enable TLS in Docker, mount your certificates and set the env vars:
docker run -d \
-p 9100:9100 \
-e MORTISE_SERVER_TOKEN=<token> \
-e MORTISE_RPC_HOST=0.0.0.0 \
-e MORTISE_RPC_TLS_CERT=/certs/cert.pem \
-e MORTISE_RPC_TLS_KEY=/certs/key.pem \
-v ./certs:/certs:ro \
-v mortise-data:/root/.mortise \
mortise-serverA terminal client that connects to a running Mortise Agent server over WebSocket (ws:// or wss://). Use it for scripting, CI/CD pipelines, server validation, or when you prefer the command line.
# From the monorepo (requires Bun)
bun run apps/cli/src/index.ts --help
# Or add to your PATH
alias mortise-cli="bun run $(pwd)/apps/cli/src/index.ts"The CLI reads connection details from flags or environment variables:
# Via environment (set once)
export MORTISE_SERVER_URL=ws://127.0.0.1:9100
export MORTISE_SERVER_TOKEN=<your-token>
# Or via flags
mortise-cli --url ws://127.0.0.1:9100 --token <token> pingFor TLS connections (wss://), use --tls-ca <path> for self-signed certificates.
| Command | Description |
|---|---|
ping |
Verify connectivity (clientId + latency) |
health |
Check credential store health |
versions |
Show server runtime versions |
workspaces |
List workspaces |
sessions |
List sessions in workspace |
connections |
List LLM connections |
session messages <id> |
Print session message history |
session delete <id> |
Delete a session |
send <id> <message> |
Send message and stream AI response |
cancel <id> |
Cancel in-progress processing |
invoke <channel> [args] |
Raw RPC call with JSON args |
listen <channel> |
Subscribe to push events (Ctrl+C to stop) |
run <prompt> |
Self-contained: spawn server, run prompt, stream response, exit |
--validate-server |
21-step integration test (auto-spawns server if no --url) |
| Flag | Default | Description |
|---|---|---|
--workspace-dir <path> |
— | Register a workspace directory before running |
--output-format <fmt> |
text |
Output format: text or stream-json |
--no-cleanup |
false |
Skip session deletion on exit |
--server-entry <path> |
— | Custom server entry point |
--provider <name> |
anthropic |
LLM provider (anthropic, openai, google, openrouter, groq, mistral, xai, etc.) |
--model <id> |
(provider default) | Model ID (e.g., claude-sonnet-4-5-20250929, gpt-4o, gemini-2.0-flash) |
--api-key <key> |
— | API key (or $LLM_API_KEY, or provider-specific env var) |
--base-url <url> |
— | Custom API endpoint for proxies or self-hosted models |
The run command is fully self-contained — it spawns a headless server, creates a session, sends the prompt, streams the response, and exits. No separate server setup needed. An API key is resolved from --api-key, $LLM_API_KEY, or a provider-specific env var (e.g., $OPENAI_API_KEY, $GOOGLE_API_KEY).
# Quick connectivity check
mortise-cli ping
# List sessions (human-readable)
mortise-cli sessions
# Send a message and stream the AI response
mortise-cli send abc-123 "What files are in the current directory?"
# Pipe input
echo "Summarize this" | mortise-cli send abc-123
# JSON output for scripting
mortise-cli --json workspaces | jq '.[].name'
# Self-contained run (spawns its own server)
mortise-cli run "Summarize the README"
mortise-cli run --workspace-dir ./my-project "Summarize this repository"
# Multi-provider support
mortise-cli run --provider openai --model gpt-4o "Summarize this repo"
GOOGLE_API_KEY=... mortise-cli run --provider google --model gemini-2.0-flash "Hello"
mortise-cli run --provider anthropic --base-url https://openrouter.ai/api/v1 --api-key $OR_KEY "Hello"
# Validate the server (auto-spawns if no --url)
mortise-cli --validate-server
mortise-cli --validate-server --url ws://127.0.0.1:9100 --token <token>mortise/
├── apps/
│ ├── cli/ # Terminal client (CLI)
│ └── electron/ # Desktop GUI (primary)
│ └── src/
│ ├── main/ # Electron main process
│ ├── preload/ # Context bridge
│ └── renderer/ # React UI (Vite + shadcn)
└── packages/
├── core/ # Shared types
└── shared/ # Business logic
└── src/
├── agent/ # MortiseAgent, permissions
├── auth/ # OAuth, tokens
├── config/ # Storage, preferences, themes
├── credentials/ # pi auth.json thin wrapper (plaintext, 0600)
├── sessions/ # Session persistence
└── statuses/ # Dynamic status system
# Hot reload development (portmux-managed)
bun run electron:dev
# Build and run
bun run electron:start
# Type checking
bun run typecheck:all
# Debug logging (writes to ~/Library/Logs/@mortise/electron/)
# Logs are automatically enabled in developmentOn Windows, double-click Mortise-Desktop-Tool.cmd from the repo root for the source workflow. It provides the single user-facing entry point for starting, restarting, and packaging Mortise, launching or stopping the WebUI, and building the Developer Kit.
To launch the complete browser UI development environment on Windows, use “启动网页端” in Mortise-Desktop-Tool.cmd or run portmux start. It starts the authenticated headless server and Vite WebUI, automatically signs the local browser in, and opens the URL assigned by portmux. The RPC server uses the following port (WebUI + 1) and the shared ~/.mortise configuration. Launch it again to open another browser client connected to the same running WebUI; it does not start another backend or create an isolated configuration directory. Use “停止网页端” in the same tool to stop every portmux-managed WebUI project for this repository and clean up legacy untracked WebUI process trees.
Electron, standalone WebUI, RPC development server, and the component playground have separate portmux identities. Start them with bun run electron:dev, bun run webui:dev, bun run server:dev, and bun run playground:dev; each listens directly on its own assigned port. Electron, the WebUI headless server, and standalone server modes all use ~/.mortise by default. Set MORTISE_CONFIG_DIR when an isolated profile is needed.
Mortise supports multiple ways to connect to LLM providers:
| Provider | Auth | Notes |
|---|---|---|
| Anthropic | API key or Claude Max/Pro OAuth | Claude connection through the Pi provider runtime |
| Google AI Studio | API key | Gemini models with native Google Search grounding built in |
| ChatGPT Plus / Pro | Codex OAuth | Sign in with your ChatGPT subscription — uses OpenAI's Codex models |
| GitHub Copilot | OAuth (device code) | One-click authentication with your Copilot subscription |
Additional providers are supported through compatible provider connections by choosing a custom endpoint:
| Provider | Endpoint | Notes |
|---|---|---|
| OpenRouter | https://openrouter.ai/api |
Access Claude, GPT, Llama, Gemini, and hundreds of other models through a single API key. Use provider/model-name format (e.g. anthropic/claude-opus-4.7). |
| Vercel AI Gateway | https://ai-gateway.vercel.sh |
Route requests through Vercel's AI Gateway with built-in observability and caching. |
| Ollama | http://localhost:11434 |
Run open-source models locally. No API key required. |
| Custom | Any URL | Any OpenAI-compatible or Anthropic-compatible endpoint. |
Mortise uses a single agent backend:
- Pi — powered by the Pi SDK, which handles Anthropic-compatible Claude connections, Google AI Studio, ChatGPT Plus (Codex OAuth), GitHub Copilot OAuth, OpenAI API key connections, and custom provider endpoints. Legacy
anthropicprovider identifiers are kept as compatibility aliases and route through this backend.
Current Mortise state is stored under ~/.mortise/:
~/.mortise/
├── state.sqlite # App configuration, Workspace registry, and drafts
└── agent/
├── auth.json # Provider credentials
├── sessions/ # Session JSONL data
└── skills/ # Mortise-global skills
Project skills are stored at <workspace>/.mortise/skills/; Mortise global skills are stored at ~/.mortise/agent/skills/.
Retired config.json Workspace settings files are ignored and left untouched.
Legacy ~/.mortise/credentials.enc files are not silently imported into auth.json; upgrade migration backs up and clears the old path. Re-enter API keys or re-authenticate OAuth connections after upgrading from a pre-unification build.
Automations let you automate workflows by triggering actions when events happen — labels change, sessions start, tools run, or on a cron schedule.
Just ask the agent:
- "Set up a daily standup briefing every weekday at 9am"
- "Notify me when a session is labelled urgent"
- "Notify me when a long-running automation finishes"
- "Every Friday at 5pm, summarise this week's completed tasks"
You can also create and manage definitions from the Automations interface. Prompt actions use normal Session semantics: they create a new Session by default, or explicitly deliver to an existing or event Session with followUp or steer.
See the bundled Automations documentation for the full reference.
Tool responses exceeding ~60KB are automatically summarized using Claude Haiku with intent-aware context. The _intent field is injected into MCP tool schemas to preserve summarization focus.
External apps can navigate using mortise:// URLs:
mortise://allSessions # All sessions view
mortise://allSessions/session/session123 # Specific session
mortise://settings # Settings
mortise://action/new-chat # Create new session
| Layer | Technology |
|---|---|
| Runtime | Bun |
| AI | Pi SDK agent server and provider runtime |
| Desktop | Electron + React |
| UI | shadcn/ui + Tailwind CSS v4 |
| Build | esbuild (main) + Vite (renderer) |
| Credentials | Pi auth store (~/.mortise/agent/auth.json, plaintext JSON, 0600 permissions) |
To launch the packaged app with verbose logging enabled, use -- --debug (note the double dash separator):
macOS:
/Applications/Mortise\ Agents.app/Contents/MacOS/Mortise\ Agents -- --debugWindows (PowerShell):
& "$env:LOCALAPPDATA\Programs\@mortiseelectron\Mortise.exe" -- --debugLinux:
./mortise -- --debugLogs are written to:
- macOS:
~/Library/Logs/@mortise/electron/main.log - Windows:
%APPDATA%\@mortise\electron\logs\main.log - Linux:
~/.config/@mortise/electron/logs/main.log
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
The former upstream trademark policy is retained only for legal provenance in UPSTREAM-TRADEMARK.md. It is not Mortise branding policy.
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
To report security vulnerabilities, please see SECURITY.md.