Process Manager is a Tauri 2 desktop app (Windows-only) that manages, monitors, and controls long-running CLI commands via a native GUI. Think PM2 with a UI. It combines a Vue 3 + TypeScript frontend with a Rust backend for process control, monitoring, and terminal integration.
Core features:
- Start/stop/restart processes with state persistence
- Real-time ANSI-colored log streaming per process
- CPU/memory monitoring via
sysinfo - Integrated PowerShell terminal with command history
- System tray with quick actions
- Auto-restart/auto-start toggles
┌─────────────────────┐ Tauri IPC ┌──────────────────┐
│ Frontend (Vue 3) │◄──(invoke/emit)──┤ Backend (Rust) │
│ - Pinia stores │ │ - ProcessManager │
│ - Components │ Events │ - Commands (IPC) │
│ - Composables │ (real-time) │ - logHandler │
└─────────────────────┘ │ - Monitoring │
└──────────────────┘
Data Flow:
- Frontend action (click "Start Process")
- Pinia store invokes Tauri command (
invoke("start_process", ...)) - Rust handler spawns process, streams logs via events
- Frontend receives events and updates UI reactively
All UI state lives in src/stores/processStore.ts:
processes: array of Process objects (id, name, command, status, pid, etc.)selectedProcessId: currently viewed processlogs: Record<processId, LogEntry[]> (keyed by process ID)metrics: Record<processId, ProcessMetrics> (CPU, memory per process)
Always use the store for state, not local component state:
import { useProcessStore } from '@/stores/processStore';
const store = useProcessStore();
store.startProcess(processId); // Pinia actionsrc/composables/useDialog.ts provides a global dialog pattern:
const dialog = useDialog();
const confirmed = await dialog.openConfirm('Title', 'Are you sure?');This shares state via reactive objects—do NOT create separate dialog instances.
- ProcessList.vue: Sidebar list of all processes
- ProcessPanel.vue: Main view (logs + metrics for selected)
- TerminalPane.vue: Integrated PowerShell terminal
- AddProcessModal.vue: Form to add new processes
- AppDialog.vue: Global confirmation/alert dialogs
Components are primarily presentational; business logic lives in the Pinia store.
- src-tauri/src/process_manager.rs: Core
ProcessManagerstruct handling process lifecycle (spawn, monitor, stop, auto-restart) - src-tauri/src/commands.rs: Tauri
#[command]handlers that expose Rust methods to frontend via IPC - src-tauri/src/monitoring.rs: CPU/memory tracking using
sysinfo - src-tauri/src/log_handler.rs: Writes stdout/stderr to files + emits events in real-time
- src-tauri/src/terminal.rs: PowerShell session management with working directory tracking
- src-tauri/src/config_handler.rs: Config persistence (JSON files in user's appdata)
ProcessManager stores a HashMap of ProcessInstance objects:
- Create:
ProcessInstance::new()generates UUID, marks as Stopped - Start: Spawns child process, captures stdout/stderr, starts monitor thread
- Monitor: Auto-restart checks if
should_restartflag is set - Stop: Terminates child process gracefully
- Emit: Each state change and log line triggers a Tauri event to frontend
- Create
#[tauri::command]function in src-tauri/src/commands.rs - Access
State<AppState>to getmanager,log_handler,system,terminal - Return serializable data (JSON); errors become Tauri errors
- Frontend calls
invoke("command_name", args)
Example:
#[tauri::command]
fn start_process(process_id: String, state: State<AppState>) -> Result<u32, String> {
let mut manager = state.manager.lock().unwrap();
manager.start_process(&process_id)
.map(|pid| pid as u32)
.map_err(|e| e.to_string())
}Logs and metrics flow to frontend via Tauri events:
process:log:{processId}– log entry (stdout/stderr)process:metrics:{processId}– CPU/memory updateprocess:status:{processId}– process status changed
Frontend uses listen() to subscribe via composables or store.
import { invoke } from "@tauri-apps/api/core";
const processId = await invoke("add_process", {
name: "dev-server",
command: "npm",
args: ["run", "dev"],
workingDir: "/path/to/project",
});import { listen } from "@tauri-apps/api/event";
const unlisten = await listen("process:log:process-123", (event) => {
console.log(event.payload); // { timestamp, level, message }
});# Terminal 1: Rust backend in watch mode
cd src-tauri
cargo tauri dev
# Terminal 2: Vite frontend (dev server)
bun run devVite runs on localhost:1420, Rust debugs on port 9222 in VS Code.
bun run build # Compile frontend
bun run tauri build # Bundle as MSI + NSIS installer (x64 default)For specific architectures:
bun run tauri build -- --target i686-pc-windows-msvc # x86 (32-bit)
bun run tauri build -- --target aarch64-pc-windows-msvc # ARM64
bun run tauri build -- --target x86_64-pc-windows-msvc # x64 (default)Output: src-tauri/target/{target}/release/bundle/
The GitHub Actions workflow (.github/workflows/release.yml) now builds on windows-latest with parallel multi-architecture support:
- Builds x64, x86, and ARM64 in parallel matrix jobs
- Each target uses
windows-msvc(has MSVC linker pre-installed) - Artifacts aggregated and published as GitHub release
- No cross-compilation complexity—native Windows compilation is faster and more reliable
Uses UUID v4 (see src-tauri/src/process_manager.rs):
let id = Uuid::new_v4().to_string();Shared types in src/types/process.ts (frontend) and src-tauri/src/types.rs (backend) must stay in sync.
- Rust: Use
Result<T, String>for#[command]functions; errors serialize as Tauri errors - Frontend: Wrap
invoke()in try/catch; check store.error state
- Process stdout/stderr →
~/.pm/logs/{processId}.log - Application logs →
~/.pm/logs/app.log(vialog_handler)
- Update
Processinterface in src/types/process.ts - Update
ProcessStatein src-tauri/src/types.rs - Update
ProcessConfigserialization in src-tauri/src/config_handler.rs - Update Pinia store in src/stores/processStore.ts if exposing as action
- Update UI component (ProcessPanel, ProcessList) to display/edit
- Add
#[tauri::command]in src-tauri/src/commands.rs - Add Pinia action in src/stores/processStore.ts that calls
invoke() - Add UI button in relevant Vue component
- Listen to events if needed for real-time feedback
src/
components/ # Vue SFC files (ProcessList, ProcessPanel, etc.)
composables/ # Reusable Vue composables (useDialog)
stores/ # Pinia stores (processStore)
types/ # TypeScript interfaces (Process, LogEntry, etc.)
App.vue # Root component (frameless titlebar + layout)
main.ts # Vue app entrypoint
src-tauri/
src/
commands.rs # IPC handlers (#[tauri::command])
config_handler.rs # Config persistence
log_handler.rs # Log file I/O
monitoring.rs # CPU/memory tracking
process_manager.rs # Process lifecycle
terminal.rs # PowerShell terminal
types.rs # Rust types
lib.rs # Module tree + Tauri setup
main.rs # Binary entrypoint (minimal)
Cargo.toml # Rust dependencies
tauri.conf.json # Tauri config (window, tray, bundle)
.github/workflows/
release.yml # CI/CD for cross-compiling Windows on Ubuntu
- Frontend errors: Check browser console (Tauri dev tools: F12)
- Rust panics: Enable
RUST_BACKTRACE=1in Tauri dev - Event flow: Use
listen("*")in console to log all events - Process escape: Processes spawn in headless mode; if blocked, check firewall
- Config not persisting: Check
~/.pm/config.jsonpermissions and format
- Tauri 2: IPC + window management
- Vue 3 + Pinia: UI state
- sysinfo: CPU/memory metrics
- winreg: Windows registry (auto-start config)
- tokio: Async runtime
- uuid: Process ID generation