feat: initial commit — odo v0.9.4 Electron desktop app for OpenClaw management

- Electron main process with tray icon and IPC setup
- openclaw-manager.js server bridge
- Architecture design document (ODO_ARCHITECTURE.md)
- preload.js for renderer context bridge
This commit is contained in:
2026-03-19 21:10:14 +10:00
commit a1e7e66ae1
9 changed files with 10786 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
node_modules/
dist/
build/
*.dmg
*.exe
*.AppImage
.DS_Store
manager-config.json
+582
View File
@@ -0,0 +1,582 @@
# ODO — OpenClaw Dashboard Orchestrator
## Architecture Design Document
> Version: Draft 1.0
> Date: 2026-03-18
> Status: Proposal — pending review
---
## 1. Product Overview
### What is ODO?
ODO is the desktop app evolution of OCM (OpenClaw Manager). It is a cross-platform Electron application that lets users manage one or more OpenClaw instances — local or remote — from a single window.
### One-line pitch
**"Double-click to manage all your OpenClaw agents, on this machine or any other."**
### Who is ODO for?
- People already running OpenClaw who want visual control instead of hand-editing JSON
- People running OpenClaw on multiple machines (home server, VPS, NAS) who want one place to manage them all
- Power users who still want a built-in CLI but prefer a desktop app over a browser tab
### Who is ODO not for?
- People who have never installed OpenClaw (ODO is not an installer)
- People who want a chat UI for talking to agents (that's ClawX / ClawUI territory)
- People who want a cloud-hosted SaaS dashboard
---
## 2. Competitive Positioning
| Product | Type | Focus | Weakness (ODO's opportunity) |
|---------|------|-------|------------------------------|
| ClawX | Desktop (Electron) | Chat client, talking to agents | No ops/config management |
| ClawUI | Desktop (Electron) | Chat + session inspection | No backup, no multi-instance |
| TenacitOS | Web dashboard | Cost analytics, monitoring | Web-only, no desktop, no remote |
| ClawBoard | Web dashboard | Agent management | Web-only, local only |
| VidClaw | Web dashboard | Kanban + file editing | Local only, no remote |
| Claworc | Web platform | Enterprise multi-instance | Requires Docker/K8s, overkill for individuals |
| **ODO** | **Desktop (Electron)** | **Ops control + remote management** | — |
### ODO's unique value
1. **Multi-instance**: manage local + remote OpenClaw from one window
2. **Ops-first**: backup/restore, health, logs, gateway control — not just monitoring
3. **Configuration editor**: visual tool/plugin management instead of JSON editing
4. **Disaster recovery**: versioned config snapshots + remote backup
---
## 3. Feature Specification
### 3.1 Core Features (daily use)
#### Dashboard
- System health: CPU, RAM, disk gauges
- Gateway status: running/stopped, PID, uptime
- Agent overview: count, last activity, binding summary
- Quick actions: restart gateway, open logs
- Auto-refresh toggle (10s interval)
- **NEW**: Connection selector — switch between local and remote instances
#### Agents Console
- Agent tree: visual hierarchy of main agents and sub-agents
- Per-agent details: bound channels/groups/threads, model, workspace path
- Inline model switching (dropdown, save without rebuild)
- Workspace file browser: view and edit SOUL.md, MEMORY.md, config files
- **EVOLVED**: De-emphasize "Add Sub-Agent" wizard. The main use case is now:
- View current agent structure (read-heavy)
- Manually adjust model, tools, plugins per agent
- Edit personality/memory files
- Agent creation still available but no longer the hero flow
#### Stats (Token Cost Monitor)
- Parse session JSONL files for real token usage
- Breakdown: by model, by agent, by day
- Visual charts: daily bar chart, model pie chart
- Time range filter: 1d / 7d / 30d / 90d
- Custom model pricing (stored in local config)
- **Borrow from TenacitOS**: trend lines, cost prediction
### 3.2 Professional Features (when needed)
#### CLI Terminal
- Full-width terminal panel, expandable from bottom dock
- Real-time streaming output (chunked HTTP)
- Tab completion: OpenClaw subcommands + history + presets
- Saved favorites (user-defined frequent commands)
- Preset command library (15+ common operations)
- **Keep as-is**: this is a differentiator, power users love it
#### Log Viewer (NEW)
- Real-time gateway log streaming (tail -f style)
- Log level filter: ERROR / WARN / INFO / DEBUG
- Text search within logs
- Auto-scroll with pause-on-scroll
- Timestamp display (Brisbane timezone or user preference)
- Log file selector (gateway.log, agent session logs)
- Export / copy selected log range
#### Backup & Recovery
- **Local backup**: one-click snapshot of openclaw.json + agent configs to a local directory
- **Remote backup**: SFTP/SCP to a remote server (simplified from OCM's NAS module)
- Host, port, username, auth (password or SSH key)
- One-click test connection
- One-click backup now
- Scheduled backup (daily, via system scheduler integration)
- **Config version history**: every config change creates a timestamped snapshot
- Version list with diff view (before/after comparison)
- One-click rollback to any version
- Auto-cleanup: keep last 50 versions, older ones auto-pruned
- **Remote backup simplification vs OCM**:
- Remove: old NAS CBC cipher compatibility (too niche)
- Remove: SSH key generation UI (users should manage keys externally)
- Keep: password auth, key auth, test connection, backup now, scheduled backup
- Add: backup to any SSH-accessible server (not just NAS)
#### Routing (formerly Channels)
- View all channel bindings grouped by agent
- Expand/collapse per agent
- Add/remove bindings
- Support: Telegram groups, Discord channels/threads
- Agent filter (all agents / specific agent)
#### Models & Auth
- Primary model selector (from `openclaw models list`)
- Fallback chain editor
- Provider auth status overview
- Auth setup guides per provider (click-to-copy CLI commands)
### 3.3 Desktop App Capabilities (NEW)
#### System Tray
- App icon in system tray (macOS menu bar / Windows system tray / Linux indicator)
- Closing the window minimizes to tray (does not quit)
- Tray menu: Show Window, Gateway Status, Restart Gateway, Quit
- Status indicator: green dot (healthy) / red dot (gateway down)
#### Gateway Lifecycle Management
- On app launch: auto-start local OpenClaw gateway if not already running
- On app quit: optionally stop gateway (user preference)
- Gateway crash detection: notification + one-click restart
- Option: "Keep gateway running after ODO closes" (for server use)
#### Auto-Start
- Option to launch ODO on system login
- Uses: macOS Login Items / Windows Registry / Linux autostart
#### Auto-Update
- Check for updates on launch (GitHub Releases)
- Download + install in background
- Notification: "Update available — restart to apply"
- Uses: electron-updater
#### Multi-Instance Connection Manager (NEW — key differentiator)
- Connection list: save multiple OpenClaw instances
- Name (e.g., "Home Server", "VPS", "Local")
- Type: Local (filesystem) or Remote (WebSocket)
- For remote: host, port (18789), auth token
- Connection status indicator
- Switch between instances from the top bar
- All features work identically for local and remote instances
- Remote protocol: OpenClaw Gateway WebSocket API
- Connection security: token auth, optional SSH tunnel instructions
---
## 4. Technical Architecture
### 4.1 Stack
| Layer | Technology | Rationale |
|-------|-----------|-----------|
| Desktop shell | Electron 33+ | Cross-platform, mature, largest ecosystem |
| Frontend | React 19 + TypeScript | Component-based, maintainable, huge community |
| UI framework | Tailwind CSS 4 | Utility-first, fast iteration, no custom CSS sprawl |
| Charts | Recharts | React-native charting, lightweight |
| Build tool | Vite | Fast dev server, HMR, clean builds |
| Packaging | electron-builder | .dmg / .exe / .AppImage in one command |
| Auto-update | electron-updater | GitHub Releases integration |
| Backend (main process) | Node.js (Electron built-in) | Reuse OCM server logic |
### 4.2 Process Architecture
```
┌──────────────────────────────────────────────┐
│ Electron │
│ │
│ ┌──────────────────┐ ┌───────────────────┐ │
│ │ Main Process │ │ Renderer Process │ │
│ │ (Node.js) │ │ (React App) │ │
│ │ │ │ │ │
│ │ - HTTP Server │◄─┤ - Dashboard │ │
│ │ - File I/O │ │ - Agents Console │ │
│ │ - child_process │ │ - Stats │ │
│ │ - Gateway mgmt │ │ - CLI Terminal │ │
│ │ - Backup engine │ │ - Log Viewer │ │
│ │ - Remote connect │ │ - Backup UI │ │
│ │ - System tray │ │ - Settings │ │
│ │ │ │ │ │
│ │ IPC Bridge ──────┤──┤── IPC Bridge │ │
│ └──────────────────┘ └───────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ OpenClaw Gateway (child process or remote)│ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘
```
**Main Process** handles:
- All filesystem operations (read/write openclaw.json, logs, backups)
- Spawning OpenClaw CLI commands
- Gateway process lifecycle
- Remote WebSocket connections to other OpenClaw instances
- System tray management
- Auto-update checks
**Renderer Process** handles:
- All UI rendering (React components)
- User interactions
- Communicates with Main Process via Electron IPC
**Why keep the HTTP server?**
The existing OCM API layer (`GET /api/dashboard`, `POST /api/agents`, etc.) is well-tested. Rather than rewriting everything as IPC handlers from scratch, we wrap the existing HTTP API in the main process and call it from the renderer. This lets us:
1. Reuse all existing backend logic
2. Add IPC gradually (replace HTTP calls with IPC one module at a time)
3. Keep the option of remote web access (start HTTP server on a port for LAN access)
### 4.3 IPC Communication
```
Renderer (React) Main Process (Node.js)
│ │
│── ipcRenderer.invoke('api', { │
│ method: 'GET', │
│ path: '/api/dashboard' │
│ }) │
│ │
│◄── { status: 200, data: {...} } ─────│
│ │
│── ipcRenderer.invoke('gateway', │
│ { action: 'restart' }) │
│ │
│◄── { ok: true, pid: 12345 } ────────│
```
IPC channels:
- `api` — proxy to internal HTTP API (backward compatible with OCM)
- `gateway` — gateway lifecycle (start/stop/restart/status)
- `backup` — backup operations (snapshot/restore/remote-push)
- `connection` — remote instance management (connect/disconnect/list)
- `system` — app-level (auto-update, tray, preferences)
- `cli` — terminal command execution with streaming output
- `logs` — log file streaming (subscribe/unsubscribe)
### 4.4 Remote Connection Architecture
```
┌─────────────────┐ ┌──────────────────────────┐
│ ODO (Desktop) │ │ Remote Machine │
│ │ │ │
│ Connection Mgr │◄──WSS──┤ OpenClaw Gateway │
│ │ │ :18789│ (ws://127.0.0.1:18789) │
│ └─► Dashboard │ │ │
│ └─► Agents │ │ Accessed via: │
│ └─► Stats │ │ - Direct LAN/Tailscale │
│ └─► Logs │ │ - SSH tunnel │
│ │ │ │
└─────────────────┘ └──────────────────────────┘
```
For remote instances:
- ODO connects to the Gateway WebSocket API directly
- Auth: token-based (`gateway.remote.token`)
- Features available remotely: Dashboard, Agents (read), Stats, Logs, CLI (commands proxied)
- Features local-only: Backup to local disk, Gateway process management
---
## 5. Project Structure
```
odo/
├── package.json
├── electron-builder.yml # Build/packaging config
├── vite.config.ts # Vite config for renderer
├── tsconfig.json
├── src/
│ ├── main/ # Electron main process
│ │ ├── index.ts # App entry, window creation, tray
│ │ ├── ipc/ # IPC handlers
│ │ │ ├── api.ts # HTTP API proxy (from OCM)
│ │ │ ├── gateway.ts # Gateway lifecycle
│ │ │ ├── backup.ts # Backup engine
│ │ │ ├── connection.ts # Remote instance manager
│ │ │ ├── cli.ts # CLI command runner
│ │ │ └── logs.ts # Log streaming
│ │ ├── server/ # Migrated OCM backend
│ │ │ ├── api-routes.ts # All /api/* handlers
│ │ │ ├── openclaw-config.ts # openclaw.json reader/writer
│ │ │ ├── stats-parser.ts # Session JSONL parser
│ │ │ └── health.ts # openclaw doctor wrapper
│ │ ├── services/
│ │ │ ├── tray.ts # System tray
│ │ │ ├── auto-update.ts # electron-updater
│ │ │ ├── auto-launch.ts # Login item registration
│ │ │ └── gateway-manager.ts # Start/stop/monitor gateway
│ │ └── utils/
│ │ ├── paths.ts # OS-specific path resolution
│ │ ├── timestamp.ts # Brisbane timezone helper
│ │ └── backup-utils.ts # Snapshot/restore helpers
│ │
│ ├── renderer/ # React frontend
│ │ ├── index.html
│ │ ├── main.tsx # React entry
│ │ ├── App.tsx # Root component, routing
│ │ ├── api/ # IPC client wrappers
│ │ │ └── client.ts # invoke('api', ...) helper
│ │ ├── components/ # Shared UI components
│ │ │ ├── Layout.tsx # App shell: sidebar + header + main
│ │ │ ├── Sidebar.tsx # Navigation
│ │ │ ├── ConnectionPicker.tsx # Instance switcher (top bar)
│ │ │ ├── StatusBadge.tsx
│ │ │ └── Toast.tsx
│ │ ├── pages/
│ │ │ ├── Dashboard.tsx
│ │ │ ├── Agents.tsx
│ │ │ ├── Stats.tsx
│ │ │ ├── Logs.tsx # NEW
│ │ │ ├── CLI.tsx
│ │ │ ├── Routing.tsx
│ │ │ ├── ModelsAuth.tsx
│ │ │ ├── Backup.tsx
│ │ │ └── Settings.tsx # NEW: preferences, connections, auto-start
│ │ ├── hooks/
│ │ │ ├── useApi.ts # Data fetching hook
│ │ │ ├── useGateway.ts # Gateway status hook
│ │ │ └── useConnection.ts # Active connection hook
│ │ └── styles/
│ │ └── globals.css # Tailwind imports + dark theme vars
│ │
│ ├── shared/ # Types shared between main & renderer
│ │ ├── types.ts
│ │ └── constants.ts
│ │
│ └── preload/
│ └── index.ts # Electron preload script (IPC bridge)
├── resources/ # App icons, installer assets
│ ├── icon.icns # macOS
│ ├── icon.ico # Windows
│ └── icon.png # Linux
├── scripts/
│ └── migrate-ocm.ts # Helper: extract API logic from OCM single file
└── legacy/
└── openclaw-manager.js # Original OCM for reference during migration
```
---
## 6. UI Design Direction
### Layout
```
┌──────────────────────────────────────────────────────┐
│ [ODO icon] [Connection: Local ▾] [🔴 Gateway] │
├────────┬─────────────────────────────────────────────┤
│ │ │
│ 🏠 │ Main Content Area │
│ Dashboard │
│ │ (selected page renders here) │
│ 🤖 │ │
│ Agents│ │
│ │ │
│ 📊 │ │
│ Stats │ │
│ │ │
│ 📋 │ │
│ Logs │ │
│ │ │
│ ⚙️ │ │
│ Routing │
│ │ │
│ 🔑 │ │
│ Auth │ │
│ │ │
│ 💾 │ │
│ Backup│ │
│ │ │
│ ⚙️ │ │
│ Settings │
│ │ │
├────────┴─────────────────────────────────────────────┤
│ [> _] CLI Terminal (expandable dock) │
└──────────────────────────────────────────────────────┘
```
### Design principles
1. **Dark theme default** — inherit OCM's dark palette (#0f1117 bg, #6c63ff accent)
2. **Sidebar navigation** — always visible, icon + label, collapsible on narrow windows
3. **Top bar** — connection picker (local/remote), gateway status badge
4. **Bottom dock** — CLI terminal, click to expand, drag to resize
5. **Dense but readable** — avoid excessive whitespace, show more data per screen
6. **No page reloads** — SPA with client-side routing
---
## 7. Migration Strategy
### Phase 0: Scaffolding (Week 1)
**Goal: Empty Electron app that opens a window**
- Initialize project: `npm create electron-vite@latest`
- Set up: TypeScript, React, Tailwind, Vite
- Create window with app shell (sidebar + empty main area)
- System tray with Quit option
- Build and package for macOS (.dmg)
- Verify: double-click .dmg → install → double-click app → window opens
### Phase 1: Dashboard + Gateway (Week 2)
**Goal: See system health and control gateway**
- Migrate OCM's `/api/dashboard` logic to main process
- Build Dashboard page (React): gauges, system info, gateway status
- IPC: gateway start/stop/restart
- Auto-start gateway on app launch
- Connection to local OpenClaw directory (auto-detect or setup wizard)
### Phase 2: Agents + Workspace (Week 3)
**Goal: View and configure agents**
- Migrate OCM's agent tree rendering to React components
- Agent tree: collapsible, grouped by parent
- Per-agent: model selector, binding overview
- Workspace file browser: list files, view content, edit and save
- Migrate: `/api/agents`, `/api/workspace` endpoints
### Phase 3: Stats + Logs (Week 4)
**Goal: Monitor costs and debug issues**
- Migrate OCM's stats parser (session JSONL)
- Stats page: charts (Recharts), filters, model breakdown
- NEW: Log viewer page
- Stream gateway.log via IPC
- Level filter, text search, auto-scroll
- Agent session log selector
### Phase 4: CLI + Routing + Auth (Week 5)
**Goal: Power user tools**
- CLI terminal: command input, streaming output, tab completion, presets
- Routing page: binding list, add/remove, agent filter
- Models & Auth page: model selector, fallback chain, provider guides
### Phase 5: Backup & Recovery (Week 6)
**Goal: Disaster recovery**
- Config version history: auto-snapshot on every change, diff viewer
- One-click rollback
- Local backup to user-specified directory
- Remote backup: SFTP/SCP with simplified setup
- Connection form: host, port, user, auth method, remote path
- Test connection, backup now, schedule (daily)
- Scheduled backup via OS scheduler (cron on macOS/Linux, Task Scheduler on Windows)
### Phase 6: Remote Management (Week 7-8)
**Goal: Manage OpenClaw on other machines**
- Connection manager: add/edit/remove remote instances
- WebSocket client for OpenClaw Gateway API
- Auth: token-based
- Adapt all pages to work with remote data source
- Connection picker in top bar
- Handle disconnection gracefully (retry, notification)
### Phase 7: Polish & Ship (Week 9-10)
**Goal: Production-ready release**
- Auto-update (electron-updater + GitHub Releases)
- Auto-start on login (optional)
- First-run setup wizard (detect OpenClaw dir, check health)
- Error handling and offline states
- Performance optimization (lazy loading pages, virtual scrolling for logs)
- Cross-platform testing: macOS, Windows, Linux
- Package: .dmg, .exe (NSIS), .AppImage
- README, screenshots, release notes
- Publish v1.0.0 on GitHub
---
## 8. Key Technical Decisions
### Why Electron over Tauri?
- OCM's entire backend is Node.js — Electron includes Node.js, so migration is natural
- Tauri requires Rust for the backend, which means rewriting all server logic
- Electron's ecosystem for auto-update, packaging, and tray management is more mature
- The 150MB app size tradeoff is acceptable for a local developer tool
### Why React over keeping vanilla JS?
- OCM's #1 recurring bug category is template literal escaping (documented 4+ times in DEVLOG)
- At 5000+ lines, a single-file vanilla JS app becomes unmaintainable
- React components isolate concerns: each page is independent, testable
- TypeScript catches bugs at compile time
- Massive ecosystem for UI components, hooks, state management
### Why keep the HTTP API layer?
- OCM's API is well-tested and covers all needed operations
- Allows gradual migration: start with HTTP over IPC, replace with direct IPC later
- Keeps the door open for remote web access (serve HTTP for LAN users)
- Reduces initial migration risk
### Why dark theme only (initially)?
- OCM already has a mature dark theme
- ODO's target users are developers/power users who prefer dark
- Adding light theme doubles CSS work for no immediate user value
- Can add light theme in v1.1 if demanded
---
## 9. Risk Assessment
| Risk | Impact | Mitigation |
|------|--------|------------|
| Electron app size too large | Low — dev tool, not consumer app | Accept 150MB, optimize later with electron-builder asar |
| Remote WebSocket auth issues | Medium — token handling across machines | Provide SSH tunnel instructions as fallback |
| Cross-platform CLI differences | Medium — child_process behavior varies | Test on all 3 platforms early in Phase 4 |
| Gateway auto-management conflicts | Medium — user may run gateway separately | Detect existing gateway, offer to attach instead of restart |
| React migration takes longer than estimated | High — 256KB of legacy code | Phase 0-1 focus on new code, legacy migration in later phases |
| OCM users confused by new product name | Low | Clear migration guide, same GitHub repo or redirect |
---
## 10. Success Metrics for v1.0
1. **Double-click to dashboard in under 3 seconds** (cold start)
2. **All OCM features working** in the Electron wrapper
3. **Remote management** of at least one remote instance
4. **Backup + restore** verified on all 3 platforms
5. **Auto-update** working via GitHub Releases
6. **Zero template-literal escaping bugs** (the React migration goal)
---
## 11. Resolved Decisions
1. **Product name**: ODO (OpenClaw Dashboard Orchestrator) — confirmed
2. **GitHub repo**: new repo `odo`, separate from `ocm`
3. **OCM maintenance**: gradual sunset — keep OCM working but no new features; ODO is the future
4. **i18n**: English only. No Chinese support planned.
5. **Cron**: merged into Settings page (scheduled backup settings + auto-start toggle)
6. **App icon**: TBD — needs design
## 12. Open Questions
1. **App icon**: design needed — lobster/claw themed?
2. **Phase 0.5 (current)**: Electron shell wrapping existing OCM — ship as v0.1.0-alpha?
---
## Appendix: Feature Comparison (OCM → ODO)
| Feature | OCM (current) | ODO (planned) |
|---------|---------------|---------------|
| Launch method | `bash start.sh` → browser | Double-click app icon |
| Dashboard | Browser tab | Native window |
| Agent management | Full CRUD wizard | Read-focused console + inline edit |
| Stats | Basic charts | Enhanced: trends, predictions |
| CLI Terminal | In-browser | In-app dock panel |
| Log Viewer | Partial (ops menu) | Full dedicated page |
| Backup (local) | Snapshots in .openclaw dir | Versioned history + diff viewer |
| Backup (remote) | NAS SSH (complex) | Any SSH server (simplified) |
| Cron management | System crontab UI | Simplified scheduled backup |
| Remote management | None | WebSocket to remote Gateway |
| System tray | None | Full tray with status indicator |
| Gateway management | Manual restart button | Auto-start/stop/crash recovery |
| Auto-update | None | GitHub Releases + electron-updater |
| Auto-start | None | OS login item option |
| Multi-instance | Single local dir | Multiple local + remote instances |
| Code structure | Single file (256KB) | React components + TypeScript |
| Dependencies | Zero | Electron + React + build tools |
+83
View File
@@ -0,0 +1,83 @@
# ODO — OpenClaw Dashboard Orchestrator
A cross-platform desktop app for managing OpenClaw AI agents. Double-click to open — no terminal needed.
## Quick Start
```bash
git clone https://github.com/dtzp555-max/odo.git
cd odo
npm install
npm start
```
That's it. ODO will start the OCM server in the background, open a window, and put an icon in your system tray.
## What ODO does
- **Wraps OCM in a desktop app** — no browser tab, no terminal
- **System tray** — close the window and ODO keeps running; click the tray icon to reopen
- **Auto server management** — the OCM server starts and stops with the app
- **Crash recovery** — if the server dies, ODO offers to restart it
## Building for distribution
```bash
# macOS
npm run build:mac # → dist/ODO-x.x.x.dmg
# Windows
npm run build:win # → dist/ODO Setup x.x.x.exe
# Linux
npm run build:linux # → dist/ODO-x.x.x.AppImage
```
## Project structure
```
odo/
├── main.js # Electron main process
├── preload.js # Renderer preload (IPC bridge)
├── server/
│ └── openclaw-manager.js # OCM server (modified for Electron)
├── assets/
│ └── (icons go here)
├── package.json
└── README.md
```
## How it works
1. Electron's main process forks `server/openclaw-manager.js` as a child process
2. The server binds to `127.0.0.1:3333` (localhost only, for security)
3. Electron opens a BrowserWindow pointing to `http://127.0.0.1:3333`
4. The server sends an IPC message when it's ready; Electron shows the window
5. Closing the window hides it to the system tray; quitting from the tray stops everything
## Requirements
- Node.js 18+
- A working [OpenClaw](https://github.com/anthropics/openclaw) installation
## Updating the server
To sync with the latest OCM:
```bash
# From the odo directory
cp /path/to/ocm/openclaw-manager.js server/openclaw-manager.js
```
Then re-apply the two small Electron patches (search for `ELECTRON` in the file):
1. Skip `openBrowser()` when `process.env.ELECTRON` is set
2. Force `HOST = '127.0.0.1'` when `process.env.ELECTRON` is set
3. Send IPC ready message: `if (process.send) process.send({ type: 'server-ready', port: PORT })`
## Roadmap
See `ODO_ARCHITECTURE.md` for the full product vision and phased plan.
## License
MIT
+2
View File
@@ -0,0 +1,2 @@
# Icon needed
Replace tray-icon.png (16x16), icon.icns (macOS), icon.ico (Windows), icon.png (512x512 Linux) with proper ODO icons.
+224
View File
@@ -0,0 +1,224 @@
'use strict';
const { app, BrowserWindow, Tray, Menu, nativeImage, dialog } = require('electron');
const path = require('path');
const { fork } = require('child_process');
// ── State ────────────────────────────────────────────────────
let mainWindow = null;
let tray = null;
let serverProcess = null;
let serverPort = 3333;
let isQuitting = false;
// ── Server Management ────────────────────────────────────────
function startServer() {
return new Promise((resolve, reject) => {
const serverPath = path.join(__dirname, 'server', 'openclaw-manager.js');
serverProcess = fork(serverPath, ['--host', '127.0.0.1'], {
env: { ...process.env, ELECTRON: '1' },
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
});
// Listen for server ready message
serverProcess.on('message', (msg) => {
if (msg.type === 'server-ready') {
serverPort = msg.port || 3333;
console.log(`[ODO] Server ready on port ${serverPort}`);
resolve(serverPort);
}
});
// Forward server output to console
serverProcess.stdout.on('data', (data) => {
process.stdout.write(`[Server] ${data}`);
});
serverProcess.stderr.on('data', (data) => {
process.stderr.write(`[Server] ${data}`);
});
serverProcess.on('error', (err) => {
console.error('[ODO] Server process error:', err);
reject(err);
});
serverProcess.on('exit', (code) => {
console.log(`[ODO] Server process exited with code ${code}`);
serverProcess = null;
if (!isQuitting) {
// Server crashed — offer to restart
if (mainWindow) {
dialog.showMessageBox(mainWindow, {
type: 'error',
title: 'Server Stopped',
message: 'The OCM server has stopped unexpectedly.',
buttons: ['Restart Server', 'Quit ODO'],
defaultId: 0
}).then(({ response }) => {
if (response === 0) {
startServer().then(() => {
if (mainWindow) mainWindow.loadURL(`http://127.0.0.1:${serverPort}`);
});
} else {
app.quit();
}
});
}
}
});
// Timeout: if server doesn't respond in 10 seconds
setTimeout(() => {
if (serverProcess && !serverProcess.killed) {
// Try loading anyway — server might be ready but didn't send IPC
resolve(serverPort);
}
}, 10000);
});
}
function stopServer() {
if (serverProcess) {
serverProcess.kill('SIGTERM');
serverProcess = null;
}
}
// ── Window ───────────────────────────────────────────────────
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
title: 'ODO',
backgroundColor: '#0f1117',
show: false, // Show when ready to avoid flash
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true
}
});
mainWindow.loadURL(`http://127.0.0.1:${serverPort}`);
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
// Close → hide to tray (don't quit)
mainWindow.on('close', (event) => {
if (!isQuitting) {
event.preventDefault();
mainWindow.hide();
}
});
mainWindow.on('closed', () => {
mainWindow = null;
});
}
// ── Tray ─────────────────────────────────────────────────────
function createTray() {
// Create a simple tray icon (16x16 template image for macOS)
const iconPath = path.join(__dirname, 'assets', 'tray-icon.png');
let trayIcon;
try {
trayIcon = nativeImage.createFromPath(iconPath);
if (trayIcon.isEmpty()) throw new Error('Icon not found');
} catch {
// Fallback: create a simple colored square icon
trayIcon = nativeImage.createEmpty();
}
// On macOS, use a template image for proper dark/light menu bar
if (process.platform === 'darwin') {
trayIcon = trayIcon.resize({ width: 16, height: 16 });
trayIcon.setTemplateImage(true);
}
tray = new Tray(trayIcon);
tray.setToolTip('ODO — OpenClaw Dashboard Orchestrator');
const contextMenu = Menu.buildFromTemplate([
{
label: 'Show ODO',
click: () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
} else {
createWindow();
}
}
},
{ type: 'separator' },
{
label: 'Restart Server',
click: async () => {
stopServer();
await startServer();
if (mainWindow) mainWindow.loadURL(`http://127.0.0.1:${serverPort}`);
}
},
{ type: 'separator' },
{
label: 'Quit ODO',
click: () => {
isQuitting = true;
app.quit();
}
}
]);
tray.setContextMenu(contextMenu);
// Double-click tray → show window (Windows/Linux)
tray.on('double-click', () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
});
}
// ── App Lifecycle ────────────────────────────────────────────
app.whenReady().then(async () => {
try {
await startServer();
} catch (err) {
console.error('[ODO] Failed to start server:', err);
dialog.showErrorBox('ODO — Startup Error', `Could not start the server:\n${err.message}`);
app.quit();
return;
}
createTray();
createWindow();
});
app.on('before-quit', () => {
isQuitting = true;
stopServer();
});
app.on('window-all-closed', () => {
// On macOS, don't quit when all windows close (tray keeps running)
if (process.platform !== 'darwin') {
// On Windows/Linux, keep running in tray too
// User must explicitly quit from tray menu
}
});
app.on('activate', () => {
// macOS dock click → show window
if (mainWindow) {
mainWindow.show();
} else {
createWindow();
}
});
+5278
View File
File diff suppressed because it is too large Load Diff
+47
View File
@@ -0,0 +1,47 @@
{
"name": "odo",
"version": "0.9.4",
"description": "ODO — OpenClaw Dashboard Orchestrator",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "electron-builder",
"build:mac": "electron-builder --mac",
"build:win": "electron-builder --win",
"build:linux": "electron-builder --linux"
},
"build": {
"appId": "com.openclaw.odo",
"productName": "ODO",
"files": [
"main.js",
"preload.js",
"server/openclaw-manager.js",
"assets/**/*"
],
"mac": {
"category": "public.app-category.developer-tools",
"target": ["dmg"],
"icon": "assets/icon.icns"
},
"win": {
"target": ["nsis"],
"icon": "assets/icon.ico"
},
"linux": {
"target": ["AppImage"],
"icon": "assets/icon.png",
"category": "Development"
},
"nsis": {
"oneClick": true,
"allowToChangeInstallationDirectory": false
}
},
"author": "",
"license": "MIT",
"devDependencies": {
"electron": "^33.0.0",
"electron-builder": "^25.0.0"
}
}
+12
View File
@@ -0,0 +1,12 @@
'use strict';
// Preload script — runs in renderer before page loads
// Currently minimal; will be expanded for IPC bridge in future phases
const { contextBridge } = require('electron');
contextBridge.exposeInMainWorld('odo', {
platform: process.platform,
version: '0.9.4',
isElectron: true
});
File diff suppressed because it is too large Load Diff