Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ jobs:
# against the previous tag. Default shallow checkout sees only HEAD.
fetch-depth: 0

- name: Validate production release version
run: |
set -euo pipefail
VERSION=$(tr -d '\n\r' < VERSION)
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ || "$GITHUB_REF_NAME" != "v$VERSION" ]]; then
echo "::error::Production releases require an exact vX.Y.Z tag matching VERSION. Share beta installers privately."
exit 1
fi

- name: Install create-dmg
run: npm install --global create-dmg

Expand Down
82 changes: 82 additions & 0 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Windows installers

on:
pull_request:
paths: ["windows/**", "VERSION", ".github/workflows/windows.yml"]
push:
branches: [main]
tags: ["v*"]
paths: ["windows/**", "VERSION", ".github/workflows/windows.yml"]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: windows-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}

jobs:
build:
name: ${{ matrix.runtime }} installer
runs-on: windows-2025
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
runtime: [win-x64, win-arm64]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5
with:
dotnet-version: "10.0.x"
- name: Verify tag and application version agree
if: startsWith(github.ref, 'refs/tags/')
shell: pwsh
run: |
$version=(Get-Content VERSION -Raw).Trim()
if($env:GITHUB_REF_NAME -ne ('v'+$version)){throw 'The tag must match VERSION.'}
- name: Run Windows logic checks
if: matrix.runtime == 'win-x64'
run: dotnet run --project windows/tests/logic/LogicTests.csproj -c Release
- name: Verify release publishing safeguards
if: matrix.runtime == 'win-x64'
run: python -m unittest discover -s windows/tests -p test_publish_release.py
- name: Build unsigned Windows preview
shell: pwsh
run: ./windows/build-installer.ps1 -Runtime '${{ matrix.runtime }}' -Unsigned -OutputDirectory '${{ runner.temp }}/windows-installer'
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: windows-${{ matrix.runtime }}
path: |
${{ runner.temp }}/windows-installer/*-Setup.exe
${{ runner.temp }}/windows-installer/*.nupkg
${{ runner.temp }}/windows-installer/releases.*.json
${{ runner.temp }}/windows-installer/windows-*.json
compression-level: 0
if-no-files-found: error
retention-days: 14

publish:
name: Attach Windows assets to the existing release
if: startsWith(github.ref, 'refs/tags/')
needs: build
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: windows-win-*
path: windows-assets
merge-multiple: true
- name: Publish installers and update feeds
env:
GH_TOKEN: ${{ github.token }}
run: python3 windows/publish-release.py windows-assets
56 changes: 37 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@
</a>
</p>

> Your AI usage limits, living in your notch.
> Your AI limits. Always in view.

CodexIsland is a native macOS overlay that turns the MacBook notch into a
Dynamic-Island-style live activity for Claude Code and Codex usage limits. It
sits quietly over the notch, peeks on hover with the 5-hour headline, and
expands on click to show both providers' 5-hour and weekly windows with reset
timing, chart controls, local-log cost estimates, and a year-at-a-glance usage
history.
CodexIsland keeps AI usage, reset times, and local token-cost estimates above
your work. On macOS it lives in the MacBook notch. On Windows 11 it uses a
floating pill with quota rings, percentages, and reset countdowns. Hover for
a quick read; click for usage charts, cost history, and shareable usage cards.

Windows builds include x64 and ARM64 installers. See
[Windows installation and updates](windows/UPDATES.md) and the
[Windows compatibility notes](windows/PARITY.md).

https://github.com/user-attachments/assets/195beeff-0f70-4d6b-8f3d-9f31d9c0b989


The app is free, open source, unsigned, and local-first. It reads credentials
already written by Claude Code / Claude Desktop and Codex, then calls only the
providers' own usage endpoints.
already written by CLI tools such as Claude Code and Codex, and asks providers
directly for usage. Session logs stay on your computer. Pricing, currency,
and app-update requests are described in the platform documentation.

## What it does

Expand Down Expand Up @@ -113,7 +116,7 @@ providers' own usage endpoints.

## Install

### Homebrew
### macOS with Homebrew

```sh
brew install --cask ericjypark/tap/codexisland
Expand All @@ -123,7 +126,7 @@ The first invocation auto-taps `ericjypark/homebrew-tap`. The cask strips the
Gatekeeper quarantine attribute automatically (CodexIsland is unsigned by
Apple — Sparkle handles update verification independently).

### Direct download
### macOS direct download

Download the current `CodexIsland-X.Y.Z.dmg` from the
[latest release](https://github.com/ericjypark/codex-island/releases/latest),
Expand Down Expand Up @@ -157,6 +160,19 @@ follow.
5. Click **Open Anyway**, then re-launch the app.
</details>

### Windows 11

Choose the x64 installer for Intel or AMD PCs, or ARM64 for Snapdragon PCs.
The Windows installers include their runtime; you do not need the .NET SDK.
After installation, open **CodexIsland** from Start. Sign in to your CLI tools
first, then select your providers in Settings.

Download Windows installers from the
[landing page](https://codexisland.com/#install), which lists available releases.
The current installers are unsigned and may show an unknown publisher warning.
Updates are downloaded in Settings and installed only when you choose
**Restart and update**. See [Windows installation and updates](windows/UPDATES.md).

## First run

CodexIsland does not ask for passwords or API keys. It reads the auth state
Expand All @@ -170,12 +186,11 @@ For Codex:

For Claude:

- Run `claude` once, or open Claude Desktop, so Claude credentials are
populated.
- CodexIsland checks `CLAUDE_CODE_OAUTH_TOKEN`, then
`$CLAUDE_CONFIG_DIR/.credentials.json` (normally
`~/.claude/.credentials.json`), then the macOS Keychain item named
`Claude Code-credentials`.
- Run `claude` in a terminal and sign in. Signing in to Claude Desktop alone
does not populate the CLI credentials.
- On macOS, CodexIsland checks `CLAUDE_CODE_OAUTH_TOKEN`, then matching Claude
Code Keychain items, then `$CLAUDE_CONFIG_DIR/.credentials.json` as a fallback.
Windows credential discovery is documented in [LIVE-PROVIDERS.md](windows/LIVE-PROVIDERS.md).
- Credential access is strictly read-only. CodexIsland never refreshes OAuth
tokens or writes to Claude's credential store; run `claude` when an access
token expires, or `claude /login` when the endpoint requires a newly scoped
Expand Down Expand Up @@ -231,7 +246,7 @@ changing the app language offers to restart CodexIsland.

## Build from source

Requires macOS 13+ and a Swift toolchain from Xcode / Command Line Tools.
For the Mac app, requires macOS 13+ and a Swift toolchain from Xcode / Command Line Tools.

```sh
git clone https://github.com/ericjypark/codex-island
Expand All @@ -255,6 +270,9 @@ Smoke test the native app:
test harnesses. `verify.sh` builds the app, launches the binary for one second,
then kills it if it is still alive.

For the Windows app, see the [Windows build instructions](windows/README.md)
and [installer packaging](windows/UPDATES.md).

## Release

Package a DMG:
Expand All @@ -269,7 +287,7 @@ codesigning, creates `dist/CodexIsland-X.Y.Z.dmg`, signs it with Sparkle's
EdDSA key when available, generates `dist/appcast.xml`, and prints the file size
and SHA-256.

Pushing a `v*` tag triggers `.github/workflows/release.yml` on `macos-15`,
Pushing an exact `vX.Y.Z` tag matching `VERSION` triggers `.github/workflows/release.yml` on `macos-15`,
builds the signed DMG and appcast, generates release notes from Conventional
Commits, publishes both artifacts in a GitHub Release, and mirrors the cask to
`ericjypark/homebrew-tap` when `HOMEBREW_TAP_TOKEN` is configured.
Expand Down
4 changes: 4 additions & 0 deletions windows/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**/bin/
**/obj/
**/__pycache__/
artifacts/
107 changes: 107 additions & 0 deletions windows/BEHAVIOR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Alert and window behavior

The Windows implementation now includes the Mac alert decisions and pulse lifecycle, plus native
full-screen suppression. This is implementation and test evidence, not acceptance as visually or
functionally identical across every platform condition. Live Codex and Antigravity connections are
verified; real Claude/Grok account validation remains unavailable. See [LIVE-PROVIDERS.md](LIVE-PROVIDERS.md).

## Alerts

`AlertEngine.cs` follows `Sources/Model/AlertEngine.swift`:

- Severity follows the visible provider's primary limit, using consumed percentage even when the UI
displays remaining percentage. Codex falls back to its weekly limit when its five-hour reading is
missing; a real zero remains a valid five-hour reading. Claude keeps its five-hour primary.
- The first completed update establishes crossing memory without a notification. High usage can tint
the island immediately. Missing values cannot become a real zero or a threshold crossing.
- Restored quota values retain severity but cannot create or prune live crossing memory. Each restored
provider's first live response establishes its baseline silently, even if another provider already
refreshed. An offline restored provider does not suppress fresh alerts from another provider.
- Warning and critical crossings are remembered separately per provider and reset boundary. Falling
below a threshold and recrossing it in the same cycle does not repeat the notification. A changed
reset boundary permits a new crossing. Missing reset boundaries preserve existing crossing memory.
- Simultaneous crossings produce one event containing the affected providers and highest severity.
Disabling alerts or using invalid thresholds clears both severity and crossing memory.
- Normal demo updates retain severity but suppress pulses, as the Mac demo does. The explicit
`--preview-alerts` launch flag exposes fixture-injection buttons in General settings for verification.
It does not connect accounts or change the demo provenance labels.

An unattended pulse opens a peek, holds it for four seconds, fades the pills over 80 ms, and begins
closing after a 100 ms wait. Hover, an expanded panel, and Always show usage retain ownership of their
respective state. Automatic peeks do not activate the window. Expanded panels consume the crossing
without opening a new peek. Warning and critical use the source amber/red values, including separate
provider pill colors. Active alerts retain the event glow in Low Power mode.

Hidden-to-peek uses the source opening spring (response 0.42, damping 0.82). Returning from expanded
uses the closing spring (0.30, 0.88). Pill fades now use the standard ease-out curve. Halo color changes
use 450 ms ease-in-out and opacity changes use 250 ms ease-in-out; cursor events no longer restart an
in-flight halo animation toward an unchanged target.

The native pointer tracker also synchronizes reset-credit hover after changing the overlay's
click-through state. A first move directly into the badge after expansion previously failed to open
details until a second move. The installed-app test reproduced that failure and passed after the
correction, including movement into the panel, Escape, and reopening.

## Full screen and visibility

The Mac source pauses its sweep when its native window is occluded. The Windows topmost overlay also
needs to hide when another foreground application's client area covers its target monitor. It uses
[window event hooks](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook)
for foreground, geometry, minimize, hide, and desktop/cloaking changes. Notifications are coalesced on
the dispatcher. There is no periodic full-screen polling timer.

The test compares screen-space client bounds against the target monitor. Normal maximized windows
with a title bar are excluded, as are the desktop/shell, invisible or minimized windows, and this
application's own windows. DWM cloaking and session lock/suspend state also suppress the overlay.
While suppressed, it is hidden, click-through, nonactivating, and its glow timer stops. Automatic alerts
update their severity without revealing the island. On restoration, Always show usage returns to peek;
ordinary hover mode returns to its hidden rest state.

The Windows event and geometry behavior is based on Microsoft's
[event constants](https://learn.microsoft.com/en-us/windows/win32/winauto/event-constants),
[client coordinates](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect),
[screen conversion](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-clienttoscreen),
and [DWM attributes](https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute).

## Verification

`run-logic.ps1` includes the original alert and monitor-geometry cases plus six saved/live alert
regressions, within the current 350-check suite.
The alert checks cover warmup, deduplication, escalation, reset changes, hidden providers, absent and
invalid readings, rounding, demo suppression, re-enabling, and primary-limit selection. Geometry checks
include negative monitor coordinates, another display, a spanning client, and a maximized title bar.

`check-alerts.ps1` drives the running app through its Settings controls. It verifies pulse timing, focus,
hover and expanded ownership, per-provider severity, disabling, Low Power, and full-screen suppression.
All 19 checks passed on build `bd18c691-39e5-40aa-b103-1317526a7064`.
The full-screen alert case schedules a fixture update three seconds later, then gives the full-screen
window foreground focus. This tests a background update without activating the Settings button mid-test.
`check-window-context.ps1` creates a real WinForms window, changes between ordinary, maximized, and
borderless full-screen states, and checks both app state and pixels copied from the Windows desktop.
Both scripts preserve preferences and return the preview to a normal launch without fixture controls.

The full-screen sequence passed 17 checks on build `ec2654be-baea-4352-b43a-a709a8f5f046` in Windows 11
ARM in Parallels at 200% scaling. The screenshot
at the island's position contained the full-screen application's background without overlay pixels.
The suppressed process consumed 0 ms of CPU during the five-second settled sample. This measurement
does not establish behavior on a physical PC, battery use, or presentation frame pacing.

The alert test also exposed a hover regression: timed dismissal set the same suppression flag as a
manual dismissal while the pointer was still inside. A direct return from outside to the trigger could
then stay hidden. `check-interaction.ps1` gained checks for re-entry and successful expansion before
testing outside-click dismissal. Both new checks failed before the fix. Suppression now applies only
when dismissal occurs with the pointer inside the island or its trigger. The harness now returns failure
when a behavioral assertion fails, rather than only writing the failed assertion to JSON.
Preference diagnostics are written after the glow predicate updates, so recorded Low Power and sweep
states describe the same update. Redundant diagnostic writes during that path were removed.

Raw results and captures are in ignored `windows/artifacts/`, with the app build identifier recorded
in alert/context results. `check-alerts.ps1 -InputOnly` isolates hover after relaunch for investigation.

## Not yet verified

- Actual lock/unlock, suspend/resume, secure-desktop changes, and DWM cloaking transitions.
- Exclusive Direct3D games, other virtual desktops, display hot-plug, and mixed-DPI physical monitors.
- A real Windows energy-saver transition and physical presentation timing during alert motion.
- Pixel-level equivalence of warning symbols, typography, halo kernels, and intermediate animation frames.
- Alert delivery from live provider updates and durable usage history, which still require their services.
45 changes: 45 additions & 0 deletions windows/CARD-PARITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Share-card design corrections

The Windows card now has dedicated embedded text and display fonts, rounded numeric outlines,
explicit headline weight, and layout values measured from the existing SwiftUI card. This removes
the previous dependence on Segoe UI and the accidental regular-weight headline. The Mac production
card is the reference and was not edited for this work.

The feed, square, and story cards use the Mac's text baselines and section heights. The square API
amount now fits the source's 82-point line box. Legend rows are 15 points with 10-point spacing;
the footer is 44.5 points tall; the chart header and canvas are separated by the source's measured
24-point offset. The footer uses a drawn arrow. Long signatures retain their font size and truncate
at the trailing edge, sharing the date row's available width.

The portable faces are static Inter instances. Large numeric glyphs use the source card's measured
advances and cap height. Convex-corner cuts round terminals without eroding strokes or changing
counters. Cached outlines are reused across preview redraws and exports. Details, licenses, and
reproduction instructions are in [Assets/Fonts/README.md](IslandPrototype/Assets/Fonts/README.md).
Apple font files and glyph outlines are not embedded. Inter's glyph shapes and Windows rasterization
still differ from SF; these changes do not establish pixel-identical typography.

## Verification

On Windows 11 ARM in Parallels:

- `tests/run-card-render.ps1`: 30 checks passed across the six format/metric combinations and seven
additional cases. The embedded font faces resolve from the application resources. Full-resolution
dimensions and card margins hold for White/Black/Blue, sub-cent amounts, very large amounts,
partial pricing, unpriced usage, Korean signatures, and long signatures.
- `tests/check-card-preserved.ps1`: all 12 installed-app interaction checks passed on build
`077a0186-8459-4d0c-873e-ba8a5a58da2a`. These cover opening the studio, all six PNG exports,
Fit/Actual size, signature length, copying the caption and image, and disabling export when no
providers are selected. The wrapper restores the prior preferences and live/demo mode.
- Fixed-fixture renders are under `artifacts/card-after/`. For all six formats, the background,
final chart marker, and first legend-dot position agree with the Mac reference to within two
sRGB channel levels at the measured coordinates. Color profiles are honored before comparing
the Mac and Windows PNGs. These anchor checks are not a visual similarity percentage.

`reference/measure-cards.py` instruments a separate copy of `WeeklyUsageCard.swift` to record actual
SwiftUI layout frames. It writes only the Windows reference artifacts. The fixed fixture retains
September 5-11 so typography and layout comparisons remain stable across midnight. Installed-app
exports use the card studio's current calendar period and can therefore contain different dates.

Rendered results were inspected for all six combinations and the edge cases. Exact font identity,
all possible personal signatures, and differences in system color-management/rasterization remain
outside the verified equivalence above.
Loading
Loading