diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a90d2cc4f..e13c732f9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -92,3 +92,29 @@ jobs:
run: npm ci
- name: Tauri debug build
run: npm run tauri -- build --debug --no-bundle
+
+ build-windows:
+ runs-on: windows-latest
+ needs:
+ - lint
+ - typecheck
+ - test-js
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: "npm"
+ - uses: dtolnay/rust-toolchain@stable
+ - name: Install LLVM (bindgen)
+ run: choco install llvm -y --no-progress
+ - name: Configure LLVM (bindgen)
+ run: |
+ echo "LIBCLANG_PATH=C:\\Program Files\\LLVM\\bin" >> $env:GITHUB_ENV
+ echo "C:\\Program Files\\LLVM\\bin" >> $env:GITHUB_PATH
+ - name: Install dependencies
+ run: npm ci
+ - name: Doctor (Windows)
+ run: npm run doctor:win
+ - name: Tauri debug build (Windows)
+ run: npm run tauri -- build --debug --no-bundle --config src-tauri/tauri.windows.conf.json
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 16995297c..d961f5cc5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -235,12 +235,74 @@ jobs:
src-tauri/target/release/bundle/appimage/*.AppImage*
src-tauri/target/release/bundle/rpm/*.rpm
+ build_windows:
+ runs-on: windows-latest
+ environment: release
+ env:
+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
+ TAURI_SIGNING_PRIVATE_KEY_B64: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_B64 }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: setup node
+ uses: actions/setup-node@v4
+ with:
+ node-version: lts/*
+ cache: npm
+
+ - name: install Rust stable
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Install LLVM (bindgen)
+ run: choco install llvm -y --no-progress
+
+ - name: Configure LLVM (bindgen)
+ run: |
+ echo "LIBCLANG_PATH=C:\\Program Files\\LLVM\\bin" >> $env:GITHUB_ENV
+ echo "C:\\Program Files\\LLVM\\bin" >> $env:GITHUB_PATH
+
+ - name: install frontend dependencies
+ run: npm ci
+
+ - name: Write Tauri signing key
+ shell: bash
+ run: |
+ set -euo pipefail
+ python - <<'PY'
+ import base64
+ import os
+ from pathlib import Path
+
+ raw = base64.b64decode(os.environ["TAURI_SIGNING_PRIVATE_KEY_B64"])
+ home = Path.home()
+ target = home / ".tauri"
+ target.mkdir(parents=True, exist_ok=True)
+ (target / "codexmonitor.key").write_bytes(raw)
+ PY
+
+ - name: build windows bundles
+ shell: bash
+ run: |
+ set -euo pipefail
+ export TAURI_SIGNING_PRIVATE_KEY
+ TAURI_SIGNING_PRIVATE_KEY="$(cat "$HOME/.tauri/codexmonitor.key")"
+ npm run tauri:build:win
+
+ - name: Upload Windows artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: windows-artifacts
+ path: |
+ src-tauri/target/release/bundle/nsis/*.exe*
+ src-tauri/target/release/bundle/msi/*.msi*
+
release:
runs-on: ubuntu-latest
environment: release
needs:
- build_macos
- build_linux
+ - build_windows
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -260,6 +322,11 @@ jobs:
path: release-artifacts
merge-multiple: true
+ - name: Download Windows artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: windows-artifacts
+ path: release-artifacts
- name: Validate RPM artifacts
run: |
set -euo pipefail
@@ -377,6 +444,37 @@ jobs:
"signature": sig_path.read_text().strip(),
}
+ exe_candidates = sorted(artifacts_dir.rglob("*.exe"), key=lambda p: p.name.lower())
+ windows_installer = None
+ preferred_installers = []
+ for candidate in exe_candidates:
+ lowered = candidate.name.lower()
+ if "codexmonitor" in lowered and ("setup" in lowered or "installer" in lowered):
+ preferred_installers.append(candidate)
+ if preferred_installers:
+ windows_installer = preferred_installers[0]
+ else:
+ for candidate in exe_candidates:
+ lowered = candidate.name.lower()
+ if "setup" in lowered or "installer" in lowered:
+ windows_installer = candidate
+ break
+ if windows_installer is None and exe_candidates:
+ windows_installer = exe_candidates[0]
+ if windows_installer is None:
+ raise SystemExit("No Windows installer (.exe) found for latest.json")
+
+ print(f"Selected Windows installer for latest.json: {windows_installer.name}")
+
+ win_sig_path = windows_installer.with_suffix(windows_installer.suffix + ".sig")
+ if not win_sig_path.exists():
+ raise SystemExit(f"Missing signature for {windows_installer.name}")
+
+ platforms["windows-x86_64"] = {
+ "url": f"https://github.com/Dimillian/CodexMonitor/releases/download/v${VERSION}/{windows_installer.name}",
+ "signature": win_sig_path.read_text().strip(),
+ }
+
payload = {
"version": "${VERSION}",
"notes": notes,
@@ -404,11 +502,23 @@ jobs:
shopt -s nullglob globstar
appimages=(release-artifacts/**/*.AppImage*)
mapfile -t rpms < <(find release-artifacts -type f -name '*.rpm' | sort)
+ mapfile -t windows_exes < <(find release-artifacts -type f -name '*.exe*' | sort)
+ mapfile -t windows_msis < <(find release-artifacts -type f -name '*.msi*' | sort)
if [ ${#rpms[@]} -eq 0 ]; then
echo "No RPM artifacts found for release upload"
find release-artifacts -type f | sort
exit 1
fi
+ if [ ${#windows_exes[@]} -eq 0 ]; then
+ echo "No Windows .exe artifacts found for release upload"
+ find release-artifacts -type f | sort
+ exit 1
+ fi
+ if [ ${#windows_msis[@]} -eq 0 ]; then
+ echo "No Windows .msi artifacts found for release upload"
+ find release-artifacts -type f | sort
+ exit 1
+ fi
gh release create "v${VERSION}" \
--title "v${VERSION}" \
@@ -420,6 +530,8 @@ jobs:
release-artifacts/CodexMonitor.app.tar.gz.sig \
"${appimages[@]}" \
"${rpms[@]}" \
+ "${windows_exes[@]}" \
+ "${windows_msis[@]}" \
release-artifacts/latest.json
- name: Bump version and open PR
diff --git a/README.md b/README.md
index 9c46250ef..6e45a0f7e 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@

-CodexMonitor is a macOS Tauri app for orchestrating multiple Codex agents across local workspaces. It provides a sidebar to manage projects, a home screen for quick actions, and a conversation view backed by the Codex app-server protocol.
+CodexMonitor is a Tauri app for orchestrating multiple Codex agents across local workspaces. It provides a sidebar to manage projects, a home screen for quick actions, and a conversation view backed by the Codex app-server protocol.
## Features
@@ -31,7 +31,7 @@ CodexMonitor is a macOS Tauri app for orchestrating multiple Codex agents across
### Files & Prompts
-- File tree with search, file-type icons, and Reveal in Finder.
+- File tree with search, file-type icons, and Reveal in Finder/Explorer.
- Prompt library for global/workspace prompts: create/edit/delete/move and run in current or new threads.
### UI & Experience
@@ -40,13 +40,14 @@ CodexMonitor is a macOS Tauri app for orchestrating multiple Codex agents across
- Responsive layouts (desktop/tablet/phone) with tabbed navigation.
- Sidebar usage and credits meter for account rate limits plus a home usage snapshot.
- Terminal dock with multiple tabs for background commands (experimental).
-- In-app updates with toast-driven download/install, debug panel copy/clear, sound notifications, and macOS overlay title bar with vibrancy + reduced transparency toggle.
+- In-app updates with toast-driven download/install, debug panel copy/clear, sound notifications, plus platform-specific window effects (macOS overlay title bar + vibrancy) and a reduced transparency toggle.
## Requirements
- Node.js + npm
- Rust toolchain (stable)
-- CMake (required for native dependencies; Whisper/dictation uses it on non-Windows)
+- CMake (required for native dependencies; dictation/Whisper uses it)
+- LLVM/Clang (required on Windows to build dictation dependencies via bindgen)
- Codex installed on your system and available as `codex` in `PATH`
- Git CLI (used for worktree operations)
- GitHub CLI (`gh`) for the Issues panel (optional)
@@ -74,13 +75,13 @@ npm run tauri dev
## Release Build
-Build the production Tauri bundle (app + dmg):
+Build the production Tauri bundle:
```bash
npm run tauri build
```
-The macOS app bundle will be in `src-tauri/target/release/bundle/macos/`.
+Artifacts will be in `src-tauri/target/release/bundle/` (platform-specific subfolders).
### Windows (opt-in)
@@ -94,8 +95,8 @@ Artifacts will be in:
- `src-tauri/target/release/bundle/nsis/` (installer exe)
- `src-tauri/target/release/bundle/msi/` (msi)
-
-Note: dictation is currently disabled on Windows builds (to avoid requiring LLVM/libclang for `whisper-rs`/bindgen).
+
+Note: building from source on Windows requires LLVM/Clang (for `bindgen` / `libclang`) in addition to CMake.
## Type Checking
diff --git a/docs/changelog.html b/docs/changelog.html
index 52df64ca8..d90c6c331 100644
--- a/docs/changelog.html
+++ b/docs/changelog.html
@@ -81,7 +81,7 @@
Changelog
Codex Monitor
- macOS Codex agents orchestration, built by and for individuals who ship fast.
+ Desktop Codex agent orchestration, built by and for individuals who ship fast.
@@ -129,7 +129,7 @@ Skills + prompts
Updater + polish
- Toast-driven updates, resizable panels, and a macOS overlay title bar.
+ Toast-driven updates, resizable panels, and platform-specific window chrome.
@@ -412,7 +412,7 @@ Ready to monitor every agent run?
Codex Monitor
- macOS Codex agents orchestration, built by and for individuals who ship fast.
+ Desktop Codex agent orchestration, built by and for individuals who ship fast.
diff --git a/src/features/app/components/OpenAppMenu.tsx b/src/features/app/components/OpenAppMenu.tsx
index 616b14628..d6defb714 100644
--- a/src/features/app/components/OpenAppMenu.tsx
+++ b/src/features/app/components/OpenAppMenu.tsx
@@ -61,9 +61,14 @@ export function OpenAppMenu({
const fallbackTarget: OpenTarget = {
id: DEFAULT_OPEN_APP_ID,
- label: DEFAULT_OPEN_APP_TARGETS[0]?.label ?? "Open",
+ label:
+ DEFAULT_OPEN_APP_TARGETS.find((target) => target.id === DEFAULT_OPEN_APP_ID)
+ ?.label ??
+ DEFAULT_OPEN_APP_TARGETS[0]?.label ??
+ "Open",
icon: getKnownOpenAppIcon(DEFAULT_OPEN_APP_ID) ?? GENERIC_APP_ICON,
target:
+ DEFAULT_OPEN_APP_TARGETS.find((target) => target.id === DEFAULT_OPEN_APP_ID) ??
DEFAULT_OPEN_APP_TARGETS[0] ?? {
id: DEFAULT_OPEN_APP_ID,
label: "VS Code",
diff --git a/src/features/app/constants.ts b/src/features/app/constants.ts
index 1f06f4ad0..dacc6d2a2 100644
--- a/src/features/app/constants.ts
+++ b/src/features/app/constants.ts
@@ -1,50 +1,99 @@
import type { OpenAppTarget } from "../../types";
+import {
+ fileManagerName,
+ isMacPlatform,
+ isWindowsPlatform,
+} from "../../utils/platformPaths";
export const OPEN_APP_STORAGE_KEY = "open-workspace-app";
-export const DEFAULT_OPEN_APP_ID = "vscode";
+export const DEFAULT_OPEN_APP_ID = isWindowsPlatform() ? "finder" : "vscode";
export type OpenAppId = string;
-export const DEFAULT_OPEN_APP_TARGETS: OpenAppTarget[] = [
- {
- id: "vscode",
- label: "VS Code",
- kind: "app",
- appName: "Visual Studio Code",
- args: [],
- },
- {
- id: "cursor",
- label: "Cursor",
- kind: "app",
- appName: "Cursor",
- args: [],
- },
- {
- id: "zed",
- label: "Zed",
- kind: "app",
- appName: "Zed",
- args: [],
- },
- {
- id: "ghostty",
- label: "Ghostty",
- kind: "app",
- appName: "Ghostty",
- args: [],
- },
- {
- id: "antigravity",
- label: "Antigravity",
- kind: "app",
- appName: "Antigravity",
- args: [],
- },
- {
- id: "finder",
- label: "Finder",
- kind: "finder",
- args: [],
- },
-];
+export const DEFAULT_OPEN_APP_TARGETS: OpenAppTarget[] = isMacPlatform()
+ ? [
+ {
+ id: "vscode",
+ label: "VS Code",
+ kind: "app",
+ appName: "Visual Studio Code",
+ args: [],
+ },
+ {
+ id: "cursor",
+ label: "Cursor",
+ kind: "app",
+ appName: "Cursor",
+ args: [],
+ },
+ {
+ id: "zed",
+ label: "Zed",
+ kind: "app",
+ appName: "Zed",
+ args: [],
+ },
+ {
+ id: "ghostty",
+ label: "Ghostty",
+ kind: "app",
+ appName: "Ghostty",
+ args: [],
+ },
+ {
+ id: "antigravity",
+ label: "Antigravity",
+ kind: "app",
+ appName: "Antigravity",
+ args: [],
+ },
+ {
+ id: "finder",
+ label: fileManagerName(),
+ kind: "finder",
+ args: [],
+ },
+ ]
+ : [
+ {
+ id: "vscode",
+ label: "VS Code",
+ kind: "command",
+ command: "code",
+ args: [],
+ },
+ {
+ id: "cursor",
+ label: "Cursor",
+ kind: "command",
+ command: "cursor",
+ args: [],
+ },
+ {
+ id: "zed",
+ label: "Zed",
+ kind: "command",
+ command: "zed",
+ args: [],
+ },
+ {
+ id: "ghostty",
+ label: "Ghostty",
+ kind: "command",
+ command: "ghostty",
+ args: [],
+ },
+ {
+ id: "antigravity",
+ label: "Antigravity",
+ kind: "command",
+ command: "antigravity",
+ args: [],
+ },
+ {
+ id: "finder",
+ label: fileManagerName(),
+ kind: "finder",
+ args: [],
+ },
+ ];
diff --git a/src/features/app/hooks/useNewAgentShortcut.ts b/src/features/app/hooks/useNewAgentShortcut.ts
index 52db7e9ed..9675fa753 100644
--- a/src/features/app/hooks/useNewAgentShortcut.ts
+++ b/src/features/app/hooks/useNewAgentShortcut.ts
@@ -1,4 +1,5 @@
import { useEffect } from "react";
+import { isMacPlatform } from "../../../utils/shortcuts";
type UseNewAgentShortcutOptions = {
isEnabled: boolean;
@@ -13,8 +14,8 @@ export function useNewAgentShortcut({
if (!isEnabled) {
return;
}
+ const isMac = isMacPlatform();
function handleKeyDown(event: KeyboardEvent) {
- const isMac = navigator.platform.toUpperCase().includes("MAC");
const modifierKey = isMac ? event.metaKey : event.ctrlKey;
if (modifierKey && event.key === "n" && !event.shiftKey) {
event.preventDefault();
diff --git a/src/features/app/hooks/useOpenAppIcons.ts b/src/features/app/hooks/useOpenAppIcons.ts
index e10e3e104..0cbcca3de 100644
--- a/src/features/app/hooks/useOpenAppIcons.ts
+++ b/src/features/app/hooks/useOpenAppIcons.ts
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { getOpenAppIcon } from "../../../services/tauri";
import type { OpenAppTarget } from "../../../types";
import { getKnownOpenAppIcon } from "../utils/openAppIcons";
+import { isMacPlatform } from "../../../utils/platformPaths";
type OpenAppIconMap = Record;
@@ -10,18 +11,8 @@ type ResolvedAppTarget = {
appName: string;
};
-function detectMacOS(): boolean {
- if (typeof navigator === "undefined") {
- return false;
- }
- const platform =
- (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData
- ?.platform ?? navigator.platform ?? "";
- return platform.toLowerCase().includes("mac");
-}
-
export function useOpenAppIcons(openTargets: OpenAppTarget[]): OpenAppIconMap {
- const isMacOS = detectMacOS();
+ const isMacOS = isMacPlatform();
const iconCacheRef = useRef