Skip to content
Merged
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
68 changes: 68 additions & 0 deletions .claude/skills/create-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
description: Create a new draft release for Insomnia. Use when the user says "create a release", "new release", "cut a release", or "ship it".
---

# Create Release

Create a new GitHub release for Insomnia with the correct versioning.

## Steps

1. Pull latest: `but pull`
Comment thread
GordonBeeming marked this conversation as resolved.
2. Determine the next version by checking existing releases:
```bash
gh release list --repo gordonbeeming/insomnia --limit 5
```
3. Bump the minor version (e.g., v0.2 → v0.3). Never use a patch number in the tag.
4. Create the release:
```bash
gh release create v{major}.{minor} \
--repo gordonbeeming/insomnia \
--target main \
--title "v{major}.{minor} — {short description}" \
--notes "$(cat <<'EOF'
# Insomnia v{major}.{minor} — {short description}

## What's New

- {list changes since last release using git log}

## Install

```bash
brew upgrade --cask gordonbeeming/tap/insomnia
```

Or download the DMG and CLI binary from the assets below.
EOF
)"
```
5. The release pipeline will automatically:
- Build + test
- Sign with Developer ID
- Notarize with Apple
- Create DMG
- Upload assets to the release
- Update the Homebrew tap cask
6. Report the release URL and pipeline run to the user

## Version Format

- Tags: `v{major}.{minor}` (e.g., `v0.3`) — NO patch number
- Bundle version: `{major}.{minor}.{runNumber}` — CI adds the run number as patch
- The tag `v0.3` with run number 45 produces bundle version `0.3.45`

## Generating Release Notes

Use git log to find changes since the last release tag:
```bash
LAST_TAG=$(gh release list --repo gordonbeeming/insomnia --limit 1 --json tagName --jq '.[0].tagName')
git log ${LAST_TAG}..HEAD --oneline
```

## Important

- Never reuse or delete existing release tags
- Always bump the minor version for new releases
- Never use `.0` patch in tags (v0.3 not v0.3.0)
- The release triggers the full CI pipeline — wait for it to complete before telling the user to `brew upgrade`
7 changes: 5 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,13 @@ jobs:
# Runs the build script that produces the CLI binary and .app bundle
- name: Build release artifacts
run: |
# Extract version from the release tag (e.g., "v0.2" → "0.2")
VERSION="${GITHUB_REF_NAME#v}"
# Make the script executable (git may not preserve the +x bit)
chmod +x Scripts/build-release.sh
# Run the build — outputs to Distribution/
./Scripts/build-release.sh
# Run the build with version and a unique run+attempt number for the bundle
# run_attempt ensures reruns of the same workflow produce unique bundle versions
./Scripts/build-release.sh "${VERSION}" "${{ github.run_number }}.${{ github.run_attempt }}"

# --- Import signing certificate ----------------------------------------
# The Developer ID certificate is stored as a base64-encoded secret.
Expand Down
86 changes: 86 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Insomnia

A macOS caffeinate utility — menu bar app + CLI. Mascot: Dapple the Mushroom.

## Tech Stack

- Swift (SwiftUI + AppKit hybrid)
- macOS 14+, Apple Silicon (arm64) only
- IOKit power assertions (native, no shelling out to `caffeinate`)
- Swift ArgumentParser for CLI
- XCTest for unit + integration tests

## Project Structure

Three targets in `Package.swift`:
- `InsomniaCore` — shared library (power management, scheduling, IPC, models)
- `Insomnia` — macOS GUI app (MenuBarExtra + AppKit)
- `InsomniaCLI` — CLI tool

## Build & Test

```bash
swift build # debug build
swift test # 118+ tests
swift run Insomnia # run GUI locally
swift run InsomniaCLI status # run CLI
```

## Code Comments

85%+ comment coverage required. Every file needs:
- File header explaining purpose
- `///` doc comments on public APIs
- Inline comments explaining intent on significant code blocks

## Versioning

- Tags: `v{major}.{minor}` (e.g., `v0.2`, `v0.3`) — no patch number
- Build number: GitHub Actions run number becomes the patch
- Bundle version: `{major}.{minor}.{runNumber}` (e.g., `0.2.42`)
- The `.0` patch in tags is meaningless — never use `v0.2.0`

## Distribution

- Homebrew: `brew install --cask gordonbeeming/tap/insomnia`
- Always use fully qualified name (upstream "Insomnia" API client conflicts)
- Cask auto-updated by CI on release via deploy key
- GitHub Releases: signed + notarized DMG + CLI binary
- No App Store (IOKit needs unsandboxed access)

## CI/CD

- `.github/workflows/build.yml`
- Push/PR: build + test only
- Release (published): build + test + sign + notarize + DMG + upload + update Homebrew tap
- Secrets: `DEVELOPER_ID_CERTIFICATE`, `DEVELOPER_ID_PASSWORD`, `APPLE_ID`, `APPLE_TEAM_ID`, `APPLE_APP_PASSWORD`, `HOMEBREW_TAP_DEPLOY_KEY`

## Dev vs Prod

Debug builds use separate identifiers (`BuildEnvironment.swift`):
- App name: "Insomnia Dev"
- IPC socket: `~/Library/Application Support/Insomnia Dev/insomnia.sock`
- UserDefaults prefix: `com.insomnia.dev.*`
- "(Dev)" suffix in status text

This lets dev and prod run side-by-side.

## Key Files

- `Sources/InsomniaCore/Power/PowerAssertionManager.swift` — IOKit assertion lifecycle
- `Sources/InsomniaCore/IPC/IPCServer.swift` — Unix domain socket server
- `Sources/InsomniaCore/Models/BuildEnvironment.swift` — dev/prod separation
- `Sources/Insomnia/InsomniaApp.swift` — @main, MenuBarExtra scene
- `Sources/Insomnia/Views/MenuBarView.swift` — main dropdown menu
- `Sources/Insomnia/AppDelegate.swift` — lifecycle, IPC server, icon loading
- `Scripts/build-release.sh` — builds CLI + .app bundle (args: version, build number)
- `Resources/AppIcon.icns` — Dapple mushroom icon

## Window Focus Pattern

LSUIElement apps need special handling to show windows:
1. `NSApp.setActivationPolicy(.regular)` before opening
2. `openWindow(id:)` to open
3. Reapply app icon via `AppDelegate.reapplyAppIcon()`
4. `NSApp.activate(ignoringOtherApps: true)` after short delay
5. Return to `.accessory` in `onDisappear` (unless dock icon enabled)
29 changes: 26 additions & 3 deletions Scripts/build-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@
# project root.
#
# Usage:
# ./Scripts/build-release.sh
# ./Scripts/build-release.sh <version> <build-number>
#
# Arguments:
# version — major.minor version string (e.g., "0.2"). Defaults to "1.0".
# build-number — numeric build number (e.g., GitHub Actions run number). Defaults to "1".
#
# Example:
# ./Scripts/build-release.sh 0.2 42 # → bundle version 0.2.42
#
# Prerequisites:
# - Xcode command-line tools (swift, xcodebuild)
Expand Down Expand Up @@ -39,6 +46,22 @@ GUI_TARGET="Insomnia"
ARCH="arm64"
# Build configuration — Release enables optimizations and strips debug symbols
BUILD_CONFIG="release"
# Version from the first argument (e.g., "0.2"), defaults to "1.0"
APP_VERSION="${1:-1.0}"
# Build number from the second argument (e.g., GitHub Actions run number), defaults to "1"
BUILD_NUMBER="${2:-1}"

# --- Validate inputs ----------------------------------------------------------
# Ensure version looks like digits.digits (e.g., "0.2", "1.0")
if [[ ! "${APP_VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then
echo "❌ Invalid version '${APP_VERSION}' — expected format: major.minor (e.g., 0.2)" >&2
exit 1
fi
# Ensure build number is numeric (may contain dots for run_attempt, e.g., "42.1")
if [[ ! "${BUILD_NUMBER}" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
echo "❌ Invalid build number '${BUILD_NUMBER}' — expected numeric (e.g., 42 or 42.1)" >&2
exit 1
fi

# --- Clean previous artifacts ------------------------------------------------
echo "🧹 Cleaning previous Distribution/ contents..."
Expand Down Expand Up @@ -145,9 +168,9 @@ cat > "${CONTENTS_DIR}/Info.plist" <<PLIST
<key>CFBundleExecutable</key>
<string>${GUI_TARGET}</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<string>${APP_VERSION}.${BUILD_NUMBER}</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>${APP_VERSION}.${BUILD_NUMBER}</string>
Comment thread
GordonBeeming marked this conversation as resolved.
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleIconFile</key>
Expand Down
Loading