Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a2f1afd
docs: add Windows support spec and plan
amanthanvi Feb 3, 2026
0d1c674
fix: improve cross-platform path opening
amanthanvi Feb 3, 2026
3b14fd4
feat(dictation): enable Windows support
amanthanvi Feb 3, 2026
54ea0f6
ci: add Windows build job
amanthanvi Feb 3, 2026
9b533f5
ci(release): add Windows updater artifacts
amanthanvi Feb 3, 2026
30cad9b
docs: clarify Windows updater bundle
amanthanvi Feb 3, 2026
2544941
fix: make default Open in targets platform-aware
amanthanvi Feb 3, 2026
8195845
ci: ensure LLVM available on Windows
amanthanvi Feb 3, 2026
a49d3bc
docs: remove macOS-only framing
amanthanvi Feb 3, 2026
17b6e21
Fix doctor command detection
amanthanvi Feb 3, 2026
125647a
Keep Windows plan/spec current
amanthanvi Feb 3, 2026
1738da8
Fix Codex PATH handling on Windows
amanthanvi Feb 3, 2026
12f17cb
Show shortcuts with Ctrl on Windows
amanthanvi Feb 3, 2026
1d07666
Fix Windows shortcut labels and select theming
amanthanvi Feb 3, 2026
ee26759
Document Windows UX and Codex PATH requirements
amanthanvi Feb 3, 2026
3091919
Run Codex via cmd.exe on Windows
amanthanvi Feb 4, 2026
e3417dd
Make settings copy platform-neutral
amanthanvi Feb 4, 2026
d7a2b30
Use generic file manager icon on Windows
amanthanvi Feb 4, 2026
2856368
Fix platform label in Sentry metrics
amanthanvi Feb 4, 2026
10abdb9
Use platform-neutral default font stacks
amanthanvi Feb 4, 2026
0172c47
Refresh docs copy for Windows support
amanthanvi Feb 4, 2026
6bcf130
Fix Windows open-in commands (.cmd/.bat shims)
amanthanvi Feb 4, 2026
04ebaea
Guard platform detection for non-browser contexts
amanthanvi Feb 4, 2026
353a763
Fix Windows asset globs in release workflow
amanthanvi Feb 5, 2026
a2909b6
Fix Windows Codex process lifecycle
amanthanvi Feb 5, 2026
85c9e2a
Harden Windows cmd spawning for Codex
amanthanvi Feb 5, 2026
4c5b26d
Fix Windows open-in cmd wrappers
amanthanvi Feb 5, 2026
b8b4e94
Make default shortcuts Windows-friendly
amanthanvi Feb 5, 2026
3ba7875
Update Windows plan for shortcut defaults
amanthanvi Feb 5, 2026
f69dfd2
Merge origin/main into feature/windows-support
amanthanvi Feb 5, 2026
24d2cdd
Fix Windows cmd wrapper escaping
amanthanvi Feb 5, 2026
dc329be
Tighten doctor and unify platform detection
amanthanvi Feb 5, 2026
e0d5e5b
Make file manager labels platform-aware
amanthanvi Feb 5, 2026
26ad51d
Reuse shared platform detection
amanthanvi Feb 5, 2026
30c7c42
Compute default shortcuts at runtime
amanthanvi Feb 5, 2026
f295a6b
Use target_os cfg in types
amanthanvi Feb 5, 2026
a175a9d
Improve Windows installer selection in release workflow
amanthanvi Feb 5, 2026
b75df92
Normalize cmd+ctrl shortcuts on non-mac
amanthanvi Feb 6, 2026
ce7d86e
Merge branch 'main' into feature/windows-support
Dimillian Feb 6, 2026
7845450
Merge remote-tracking branch 'origin/main' into feature/windows-support
Dimillian Feb 6, 2026
277771a
chore(rust): remove unused codex args helper
Dimillian Feb 6, 2026
5dd8005
chore(docs): remove plan and spec docs
Dimillian Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
112 changes: 112 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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")

Comment on lines +450 to +466
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Windows installer detection logic searches for .exe files containing "setup" or "installer" in lowercase filenames, with a fallback to the first .exe found if no match. This heuristic approach could be fragile if:

  1. Tauri changes its naming convention for Windows installers
  2. Multiple .exe files are present in the release artifacts with ambiguous names
  3. A debug or test .exe is alphabetically first

Consider either:

  1. Using a more specific pattern that matches Tauri's naming convention (e.g., checking for the app name in the filename)
  2. Having Tauri output a manifest file listing the updater bundle filename
  3. Using a glob pattern that's more specific to the actual output (e.g., *_x64-setup.exe or similar)
  4. Adding validation that the selected file has an expected size or signature format

For robustness, at minimum add a log message showing which installer was selected.

Suggested change
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")
# Prefer executables that look like the CodexMonitor installer
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:
# Fallback to any executable containing "setup" or "installer"
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:
# Final fallback: first .exe alphabetically (for backward compatibility)
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}")

Copilot uses AI. Check for mistakes.
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,
Expand Down Expand Up @@ -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}" \
Expand All @@ -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
Expand Down
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![CodexMonitor](screenshot.png)

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

Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ <h1>Changelog</h1>
<img class="logo-mark" src="assets/app-icon.png" alt="Codex Monitor app icon" />
Codex Monitor
</div>
<p>macOS Codex agents orchestration, built by and for individuals who ship fast.</p>
<p>Desktop Codex agent orchestration, built by and for individuals who ship fast.</p>
</div>
<div class="footer-links">
<a href="index.html#features">Features</a>
Expand Down
10 changes: 5 additions & 5 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title>Codex Monitor - Orchestrate Codex agents across your workspaces</title>
<meta
name="description"
content="Codex Monitor is a macOS Tauri app that orchestrates Codex agents across local workspaces with threads, reviews, and a glassy command center."
content="Codex Monitor is a cross-platform Tauri app that orchestrates Codex agents across local workspaces with threads, reviews, and a glassy command center."
/>
<meta property="og:title" content="Codex Monitor — Monitor your Codex situation." />
<meta
Expand Down Expand Up @@ -60,7 +60,7 @@
<section class="hero">
<div class="container hero-grid">
<div class="hero-copy">
<div class="eyebrow">macOS command center for Codex</div>
<div class="eyebrow">Desktop command center for Codex</div>
<h1>Monitor your Codex situation.</h1>
<p>
Orchestrate any number of Codex agents across any number of projects in a beautifully crafted
Expand All @@ -77,7 +77,7 @@ <h1>Monitor your Codex situation.</h1>
</div>
<div>
<span class="meta-label">Platform</span>
<span class="meta-value">macOS + Linux</span>
<span class="meta-value">macOS + Windows + Linux</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -129,7 +129,7 @@ <h3>Skills + prompts</h3>
</article>
<article class="glass card">
<h3>Updater + polish</h3>
<p>Toast-driven updates, resizable panels, and a macOS overlay title bar.</p>
<p>Toast-driven updates, resizable panels, and platform-specific window chrome.</p>
</article>
</div>
</div>
Expand Down Expand Up @@ -412,7 +412,7 @@ <h2>Ready to monitor every agent run?</h2>
<img class="logo-mark" src="assets/app-icon.png" alt="Codex Monitor app icon" />
Codex Monitor
</div>
<p>macOS Codex agents orchestration, built by and for individuals who ship fast.</p>
<p>Desktop Codex agent orchestration, built by and for individuals who ship fast.</p>
</div>
<div class="footer-links">
<a href="#features">Features</a>
Expand Down
48 changes: 41 additions & 7 deletions scripts/doctor.mjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";

const strict = process.argv.includes("--strict");

function isExecutableFile(filePath) {
try {
const stat = fs.statSync(filePath);
if (!stat.isFile()) return false;
if (process.platform === "win32") return true;
fs.accessSync(filePath, fs.constants.X_OK);
return true;
} catch {
return false;
}
}

function hasCommand(command) {
const checker = process.platform === "win32" ? "where" : "command";
const checkerArgs = process.platform === "win32" ? [command] : ["-v", command];
const result = spawnSync(checker, checkerArgs, { stdio: "ignore" });
return result.status === 0;
const pathValue = process.env.PATH;
if (!pathValue) return false;

const dirs = pathValue.split(path.delimiter).filter(Boolean);

if (process.platform !== "win32") {
return dirs.some((dir) => isExecutableFile(path.join(dir, command)));
}

const pathExtValue = process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM";
const exts = pathExtValue.split(";").filter(Boolean);
const hasExtension = path.extname(command) !== "";

for (const dir of dirs) {
if (hasExtension) {
if (isExecutableFile(path.join(dir, command))) return true;
continue;
}
for (const ext of exts) {
if (isExecutableFile(path.join(dir, `${command}${ext}`))) return true;
}
}

return false;
}

const missing = [];
if (!hasCommand("cmake")) missing.push("cmake");
if (process.platform === "win32" && !hasCommand("clang")) missing.push("llvm");

if (missing.length === 0) {
console.log("Doctor: OK");
Expand All @@ -29,13 +63,13 @@ switch (process.platform) {
console.log("Arch: sudo pacman -S cmake");
break;
case "win32":
console.log("Install: choco install cmake");
console.log("Install: choco install cmake llvm");
console.log("Or download from: https://cmake.org/download/");
console.log("If bindgen fails, set LIBCLANG_PATH to your LLVM bin directory.");
break;
default:
console.log("Install CMake from: https://cmake.org/download/");
break;
}

process.exit(strict ? 1 : 0);

2 changes: 0 additions & 2 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ toml = "0.8"
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
tauri-plugin-updater = "2"
tauri-plugin-window-state = "2"

[target."cfg(not(target_os = \"windows\"))".dependencies]
cpal = "0.15"
whisper-rs = "0.12"
sha2 = "0.10"
Expand Down
Loading