An open-source desktop container for building interoperable trading and capital-markets applications β powered by Tauri 2 and FDC3 2.2.
OneTerminal lets you compose a workspace of independent browser apps β tickers, charts, blotters, news, OMS panels, dashboards β that share context through an FDC3 desktop agent and run inside lightweight native windows. Bring your own apps, your own engine (WebView2, WKWebView, Electron, or a custom plugin), and your own theme. We handle window management, channels, intents, dashboards, and engine lifecycle.
Quick Start β Β Β·Β Features Β Β·Β Architecture Β Β·Β Contributing Β Β·Β Roadmap
Buy-side and sell-side desks have spent the last decade stitching together browser apps inside heavyweight containers. OneTerminal is a lightweight, fully open-source alternative β built on modern Tauri 2, FINOS FDC3 2.2, and a Rust core β so you can:
- Ship a branded trading workspace in days, not quarters β scaffold a workspace with
create-one-terminal, wire your apps to the App Directory, done. - Run your existing browser apps unchanged β drop in any HTTP(S) URL; FDC3 channels and intents work via a small
fdc3-plugin.jsscript. - Choose the right engine per app β system WebView for native footprint, Electron for compatibility, or a custom engine via the plugin system.
- Stay in control β everything is open source, runs locally, has no telemetry, and is built on a tiny Rust + TypeScript codebase you can read in an afternoon.
- Tiled dashboards with tabs β drag, split, stack, and rename tabs. Persist multiple dashboards per Terminal and switch between them with one click.
- Multiple Terminal instances β each with its own dashboard list, theme, and configuration.
- App Menu drawer β discover and launch widgets directly from the Terminal. The catalog combines the FDC3 App Directory with a local
widgets.config.json; each entry is badged by source. Point the Terminal at any App Directory endpoint from the drawer's App Directory section (persisted per-user, applied live). - Customisable per-app UI β register custom widget headers and tab context-menu items per
appId(see widget extension points). - Hotkey-driven β Command Palette (Epic 06) is on the roadmap; keybindings configurable today.
- User channels, app channels, private channels β full FDC3 2.2 channel semantics.
- Intents and intent resolution β
raiseIntent,addIntentListener, intent picker UI included. - Context broadcasting β typed contexts (
fdc3.instrument,fdc3.contact, custom types) routed through the desktop agent. - FINOS DACP β Desktop Agent Communication Protocol bridge for multi-instance interop.
- Four engine families, one app shape β
wkwebview(macOS),webview2(Windows),electron, and custom engines via plugin manifests. - Per-app engine pinning β pick the engine and version in the App Directory record. The Desktop Agent downloads and caches runtimes on demand.
- Webview pre-warm pool β
OT_WEBVIEW_POOL_SIZEkeeps spawn latency low for first-tab opens.
- App Directory β embedded FDC3 AppD REST API + React management UI with auth provider plugin support.
- Framework plugin system (Epic 08) β Node.js sidecars that extend the Desktop Agent, App Directory, and browser API surface (
window.OT.plugins). - Engine plugin manifests β register new engine families (CEF, Servo, β¦) via a
manifest.jsonβ no recompile. - Scaffolder β
npx create-one-terminalgenerates a new workspace;upgrademigrates it to newer framework versions in place.
- Rust workspace + npm workspace, side by side β
cargo check --workspace,npm run build:all. - Hot-reload dev loops β App Directory and sample apps reload on save; Tauri apps rebuild incrementally.
- Sample apps included β
sample-ticker(port 3010) andsample-chart(port 3011) show channel broadcast + intent handling. - Typed FDC3 client β
@one-terminal/fdc3-clientships full FDC3 2.2 TypeScript definitions.
| Tool | Version |
|---|---|
| Rust | stable β₯ 1.77 |
| Node.js | β₯ 20 LTS |
| Xcode Command Line Tools | macOS only β xcode-select --install |
The fastest way to evaluate OneTerminal for your team:
npx create-one-terminal my-terminalThe scaffolder asks for a variant:
- Standalone (default) β Terminal + a single sample widget, joins an external FDC3 agent. No Desktop Agent or App Directory shipped. Best for evaluating the Terminal or joining an existing FDC3 estate.
- Enterprise β full stack (Terminal + Desktop Agent + App Directory + samples). Best for platform teams.
# Standalone start (two terminals):
cd my-terminal
npm install
npm run dev:sample-widget # http://localhost:3012
npm run dev:terminal # separate terminal
# Enterprise start (three terminals):
npm run dev:app-directory # http://localhost:3005
npm run dev:desktop-agent # separate terminal
npm run dev:terminal # separate terminalAdd new widgets in either variant with npm run create-widget -- --name <name> (recommended β runs a workspace-local, dependency-free Node script at scripts/new-widget.mjs) or npx create-one-terminal new-widget <name> (uses the published scaffolder). On Standalone the widget is registered in widgets.config.json; on Enterprise the wizard prints a curl command for the App Directory (or runs it if OT_APPD_TOKEN is set).
Enterprise workspaces also ship apps/app-directory/src/data.ts for persisting AppD records.
Clone this repo and run the same dev loop:
git clone https://github.com/OneTerminal/one-terminal.git
cd one-terminal
npm install
npm run dev:app-directory # http://localhost:3005
npm run dev:sample-ticker # http://localhost:3010 (optional)
npm run dev:sample-chart # http://localhost:3011 (optional)
npm run dev:desktop-agent # separate terminal
npm run dev:terminal # separate terminalThe first
cargobuild takes 1β2 minutes. Subsequent incremental builds are seconds.
npm run build:terminal # OneTerminal window manager
npm run build:desktop-agent # Desktop Agent
npm run build:all # All Tauri apps in sequenceDrop this into any HTML page served from a URL you register in the App Directory:
<script type="module">
import { DesktopAgentClient } from "/fdc3-plugin.js";
const fdc3 = await DesktopAgentClient.connect("my-app-id");
await fdc3.joinUserChannel("Green");
await fdc3.addContextListener("fdc3.instrument", (ctx) => {
document.title = `Watching ${ctx.id.ticker}`;
});
</script>Open it inside OneTerminal alongside the sample ticker β switch both to the Green channel β and watch context flow.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OneTerminal (apps/one-terminal) β
β Window manager β hosts app webviews in tabs β
β Reads App Directory to discover available apps β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
β TCP 7890
ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββ
β Desktop Agent (apps/desktop-agent) β
β FDC3 2.2 broker β channels, intents, DACP β
β Manages engine runtime downloads + launches β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β Engine Cache <app_data>/engines/ β β
β β wkwebview/<ver>/ webview2/<ver>/ β β
β β electron/<ver>/ <plugin-family>/ β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββββ
β WS 7891 β UDP/WS 4475
ββββββββββΌβββββββ ββββββββββΌβββββββ
β fdc3-plugin β β DACP bridge β
β (browser JS) β β (multi-inst) β
βββββββββββββββββ βββββββββββββββββ
| Path | Purpose |
|---|---|
apps/one-terminal |
Window manager (Tauri 2 app) |
apps/desktop-agent |
FDC3 desktop agent + engine launcher |
apps/tauri-webview-host |
Thin Tauri shell spawned for pinned WebView2/WKWebView apps |
apps/electron-host |
Thin Electron shell spawned for Electron-engine apps |
apps/app-directory |
Express server serving FDC3 AppD REST API + management UI |
packages/ot-core |
Tauri-agnostic shared Rust crate (engine abstraction, plugin manifests) |
packages/ot-fdc3 |
Tauri plugin: FDC3 2.2 TCP spoke client |
packages/fdc3-plugin |
Browser-side FDC3 2.2 client (DesktopAgentClient) β ships in both variants |
packages/fdc3-client |
TypeScript FDC3 type definitions β Enterprise only (Tauri-bound, calls fdc3_* commands from ot-fdc3) |
| Path | Port | Variant | Description |
|---|---|---|---|
apps/sample-widget |
3012 | standalone | Minimal FDC3 widget β connect, broadcast, receive context |
apps/sample-ticker |
3010 | enterprise | Market data ticker β broadcasts fx.rate on the Green channel |
apps/sample-chart |
3011 | enterprise | Candlestick chart β mock EUR/USD stream, handles ViewChart/ViewQuote intents |
The Desktop Agent reads agent.config.json (bundled as a resource) and applies environment variable overrides on top.
agent.config.json (default)
{
"title": "OneTerminal Desktop Agent",
"appDirectoryUrl": "http://localhost:3005/v2/apps",
"engineCatalogUrl": "http://localhost:3005/v2/engines",
"ports": {
"tcpBroker": 7890,
"fdc3Bus": 7891,
"dacpBridge": 4475
},
"userChannels": [
{ "id": "Red", "displayName": "Red", "color": "#E74C3C" },
{ "id": "Orange", "displayName": "Orange", "color": "#E67E22" },
{ "id": "Yellow", "displayName": "Yellow", "color": "#F1C40F" },
{ "id": "Green", "displayName": "Green", "color": "#2ECC71" },
{ "id": "Teal", "displayName": "Teal", "color": "#1ABC9C" },
{ "id": "Blue", "displayName": "Blue", "color": "#3498DB" },
{ "id": "Purple", "displayName": "Purple", "color": "#9B59B6" },
{ "id": "Pink", "displayName": "Pink", "color": "#EC407A" }
]
}Desktop Agent (Enterprise only):
| Variable | Overrides |
|---|---|
OT_TITLE |
Window title |
OT_APP_DIR_URL |
appDirectoryUrl |
OT_CATALOG_URL |
engineCatalogUrl |
OT_TCP_PORT |
ports.tcpBroker |
OT_FDC3_BUS_PORT |
ports.fdc3Bus |
OT_DACP_PORT |
ports.dacpBridge |
Terminal (both variants):
| Variable | Overrides |
|---|---|
OT_APP_DIR_URL |
appDirectoryUrl β App Directory endpoint (blank disables the App Directory source; can also be overridden per-user from the App Menu β App Directory section) |
OT_WIDGETS_CONFIG_PATH |
localWidgetsPath β path to widgets.config.json (default widgets.config.json, resolved from the bundle resource dir, a resources/ dir, or upward from the cwd) |
OT_FDC3_AGENT_URL |
fdc3.agentUrl β external FDC3 agent WebSocket URL (Standalone) |
OT_WEBVIEW_POOL_SIZE |
Pre-warmed webview pool size (default 1) |
The Terminal's widget catalog is the union of the App Directory (
appDirectoryUrl) and the localwidgets.config.jsonβ not an either/or switch. An emptyappDirectoryUrldisables the remote source; a missingwidgets.config.jsondisables the local source. The legacyOT_WIDGET_SOURCE/widgetSourcefield is deprecated and no longer selects between sources (retained only for back-compat deserialization).
For the full list (App Directory auth, native-app allowlists, engine cache root), see CLAUDE.md β Environment variables.
OneTerminal supports four engine families:
| Family | Description |
|---|---|
wkwebview |
macOS system WebKit β always available, no download |
webview2 |
Windows system WebView2 β always available, no download |
electron |
Electron β requires apps/electron-host to be set up |
| custom | Any engine via a plugin manifest (see below) |
In the App Directory record, set hostManifests.oneTerminal.engine:
{
"appId": "my-app",
"hostManifests": {
"oneTerminal": {
"engine": { "family": "electron", "version": "29.3.0" }
}
}
}Omit engine (or set "version": "system") to use the OS-default WebView.
npm run setup:electron-host # install electron binary once
npm run dev:terminal:electron # OneTerminal with Electron support
npm run dev:desktop-agent:electron # Desktop Agent with Electron supportDrop a manifest.json into <engine-cache>/plugins/<family>/ to register a new engine family without recompiling:
{
"family": "cef",
"label": "Chromium Embedded Framework",
"supportedPlatforms": ["windows", "macos", "linux"],
"launchMode": {
"type": "spawnBinary",
"binaryName": "cef-host",
"envTemplates": [
{ "key": "CEF_URL", "value": "{{url}}" },
{ "key": "CEF_TITLE", "value": "{{title}}" },
{ "key": "CEF_APP_ID", "value": "{{app_id}}" },
{ "key": "CEF_RUNTIME_PATH", "value": "{{runtime_path}}" }
]
}
}launchMode.type values:
| Type | Behaviour |
|---|---|
inProcess |
Render inside the Desktop Agent Tauri webview |
spawnTauriHost |
Spawn tauri-webview-host binary |
spawnElectronHost |
Spawn apps/electron-host via Electron |
spawnBinary |
Spawn an arbitrary binary with env vars from envTemplates |
Template variables in envTemplates.value:
| Variable | Resolved value |
|---|---|
{{url}} |
App URL from App Directory |
{{title}} |
App display name |
{{app_id}} |
FDC3 appId |
{{runtime_path}} |
Absolute path to the downloaded engine runtime |
A sample CEF manifest ships at apps/desktop-agent/src-tauri/resources/plugins/cef/manifest.json.
The App Directory (apps/app-directory) serves the FDC3 AppD REST API and ships a management UI. Three ways to add, edit, or remove apps:
npm run dev:app-directory # http://localhost:3005- Add β click Register Application and fill in the form.
- Edit β click the pencil icon on any listed app.
- Delete β click the trash icon.
Note: The server stores apps in memory. Changes made through the UI (or the REST API) are lost when the server restarts. To persist them, copy the updated records into
apps/app-directory/src/data.ts(Option 2).
Edit apps/app-directory/src/data.ts and add or update entries in the _apps array. Required fields are appId, name, type, and details.url:
{
appId: "my-app",
name: "My App",
type: "web",
details: { url: "http://localhost:4000" },
title: "My App",
version: "1.0.0",
}After editing, rebuild the static dist so the changes are bundled into the production binary:
npm run build:app-directoryDuring development (npm run dev:app-directory) the server hot-reloads and serves directly from source β no rebuild needed.
The server exposes a standard FDC3 AppD CRUD API while it is running:
| Method | Path | Action |
|---|---|---|
GET |
/v2/apps |
List all apps (supports ?$filter=) |
GET |
/v2/apps/:appId |
Get one app |
POST |
/v2/apps |
Register a new app |
PUT |
/v2/apps/:appId |
Replace an existing app |
DELETE |
/v2/apps/:appId |
Remove an app |
curl -X POST http://localhost:3005/v2/apps \
-H "Content-Type: application/json" \
-d '{"appId":"my-app","name":"My App","type":"web","details":{"url":"http://localhost:4000"}}'Same in-memory caveat applies β persist changes to data.ts to survive a restart.
Browser apps use fdc3-plugin.js as the FDC3 desktop agent proxy over WebSocket.
<script type="module">
import { DesktopAgentClient } from "/fdc3-plugin.js";
const fdc3 = await DesktopAgentClient.connect("my-app-id");
// Join a user channel
await fdc3.joinUserChannel("Green");
// Broadcast context
await fdc3.broadcast({
type: "fdc3.instrument",
id: { ticker: "EUR/USD" },
name: "EUR/USD",
});
// Listen for context
await fdc3.addContextListener("fdc3.instrument", (ctx) => {
/* ... */
});
// Raise an intent
await fdc3.raiseIntent("ViewChart", context);
// Handle an intent
await fdc3.addIntentListener("ViewChart", (ctx, meta) => {
/* ... */
});
</script>The plugin resolves its agent URL in this order (first non-empty wins):
<meta name="ot-fdc3-bus-url">in the host pagewindow.OT_FDC3_AGENT_URLβ injected by the Terminal at panel-load time whenfdc3.agentUrlis configured (Standalone scaffolds)window.OT_FDC3_BUS_URLβ legacy overridews://localhost:7891/fdc3β Enterprise-bundled Desktop Agent default
If the Terminal explicitly sets window.OT_FDC3_AGENT_URL = "" (no agent configured), DesktopAgentClient.connect() rejects with NoAgentConfigured instead of silently timing out β so Standalone widgets can surface a clear "no agent" state.
Override at the page level via:
<meta name="ot-fdc3-bus-url" content="ws://myhost:7891/fdc3" />window.OT_FDC3_BUS_URL = "ws://myhost:7891/fdc3";Two static registries in apps/one-terminal/src/components/ let developers customise per-app UI without touching the shell layout.
- Custom widget headers β
panelHeaders.tsxβ render badges, status indicators, or controls inside each tab's title bar, keyed byappId. - Tab context-menu items β
contextMenuItems.tsβ add per-app actions to the right-click menu ("*"targets all apps).
Full API docs in CLAUDE.md β Widget UI extension points. These will be superseded by window.OT.plugins.get("contextMenu") once the dynamic plugin system (Epic 08) lands β static entries will continue to work unchanged.
Detailed implementation plans for the next development phase are in docs/plans/.
| Plan | File | Milestone |
|---|---|---|
| 01 β Widget Headers | docs/plans/01-customisable-widget-headers.md | Phase 1 |
| 02 β Faster Loading | docs/plans/02-faster-widget-loading.md | Phase 3 |
| 03 β App Directory Auth | docs/plans/03-app-directory-roles-permissions.md | Phase 1 |
| 04 β Native App Launch | docs/plans/04-native-app-launch.md | Phase 3 |
| 05 β Ticker View Chart | docs/plans/05-ticker-view-chart-enhancement.md | Phase 2 |
| 06 β Command Palette | docs/plans/06-command-palette-hotkeys.md | Phase 2 |
| 07 β Deployment | docs/plans/07-deployment-and-hosting.md | Phase 4 |
| 08 β Plugin System | docs/plans/08-plugin-system.md | Phase 5 |
| 09 β Dashboards & Terminals | docs/dashboards/README.md | Phase 2β5 |
| 10 β Terminal App Menu | docs/plans/10-terminal-app-menu.md | Phase 2 |
See docs/plans/README.md for execution order and effort estimates.
We welcome contributions of all sizes β bug reports, docs improvements, sample apps, engine plugins, framework plugins, and core changes.
- First time? Read CONTRIBUTING.md β it walks through the development setup, scaffolder workflow, and PR conventions.
- Picking something up? Browse open issues or the roadmap plans β many are scoped as discrete contributor-friendly tasks.
- Building a sample trading app? We'd love to feature it. Open a PR adding it under
apps/or link it from the docs. - Reporting security issues? Please email the maintainers privately rather than opening a public issue.
cargo check --workspace # type-check Rust
cargo test --workspace # run Rust tests
cargo test -p desktop-agent engines::router
npm run build:app-directory # rebuild AppD static dist
npm run build:samples # production builds of sample apps
npm run build:all # all Tauri apps
npm run create-migration # author a framework migration
npm run build:scaffolder # rebuild create-one-terminalOneTerminal ships a create-one-terminal package that generates and upgrades workspaces:
npx create-one-terminal my-terminal # scaffold a new workspace (Standalone or Enterprise)
npx create-one-terminal new-widget fx-rates # scaffold a new widget app under apps/ (or `npm run create-widget -- --name fx-rates` inside the workspace)
npx create-one-terminal upgrade # upgrade an existing workspace in placeTemplates live at packages/create-one-terminal/templates/, partitioned across three overlays:
templates/shared/β Terminal shell, ot-core, fdc3-plugintemplates/enterprise/β Desktop Agent, App Directory, ot-fdc3, fdc3-client, enterprise samplestemplates/standalone/β sample-widget, slim Cargo/package,widgets.config.json
Templates are regenerated from the live apps via npm run create-migration. See SCAFFOLDING.md, docs/plans/standalone-vs-enterprise-plans.md, and CONTRIBUTING.md for the full workflow.
one-terminal/
βββ apps/
β βββ one-terminal/ Window manager (Tauri 2)
β βββ desktop-agent/ FDC3 2.2 desktop agent + engine launcher (Tauri 2)
β βββ app-directory/ FDC3 AppD REST API + management UI (Express + React)
β βββ tauri-webview-host/ Minimal Tauri host for pinned WebView2/WKWebView apps
β βββ electron-host/ Minimal Electron host for Electron-engine apps
β βββ sample-ticker/ Sample: ticker plant browser app (port 3010)
β βββ sample-chart/ Sample: candlestick chart viewer browser app (port 3011)
βββ packages/
β βββ ot-core/ Shared Rust crate (engine abstraction, plugin manifests)
β βββ ot-fdc3/ Tauri plugin: FDC3 2.2 TCP spoke client
β βββ fdc3-plugin/ Browser FDC3 client (WebSocket transport)
β βββ fdc3-client/ TypeScript FDC3 types + Fdc3Agent
β βββ create-one-terminal/ Scaffolder + upgrade tool
βββ docs/
β βββ dashboards/ Dashboards & multi-terminal planning docs
β βββ plans/ Roadmap and implementation plans
βββ Cargo.toml Rust workspace
βββ package.json npm workspace root
OneTerminal is released under the MIT License.
Built with β€οΈ on Tauri 2 and FINOS FDC3 2.2.
