Skip to content
Closed
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
87 changes: 87 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write

env:
ZIG_VERSION: '0.15.2'

jobs:
build:
name: Build and Release
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Extract version from tag
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"

- name: Install Zig
uses: mlugg/setup-zig@v2
with:
version: ${{ env.ZIG_VERSION }}

- name: Verify prerequisites
run: |
echo "Zig: $(zig version)"
echo "Swift: $(swift --version)"
xcrun -sdk macosx metal --version

- name: Cache GhosttyKit
id: cache-ghostty
uses: actions/cache@v4
with:
path: |
Frameworks/GhosttyKit.xcframework
Frameworks/ghostty-resources
key: ghosttykit-${{ runner.os }}-${{ hashFiles('vendor/ghostty/**') }}-zig${{ env.ZIG_VERSION }}

- name: Build GhosttyKit
if: steps.cache-ghostty.outputs.cache-hit != 'true'
run: bash scripts/build-ghostty.sh

- name: Build release
run: make release

- name: Sign
run: bash scripts/sign.sh
env:
DEVELOPER_ID_APPLICATION: ${{ secrets.DEVELOPER_ID_APPLICATION }}

- name: Notarize
if: ${{ secrets.APPLE_ID != '' }}
run: |
xcrun notarytool submit Crow.app --apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_PASSWORD" --wait
xcrun stapler staple Crow.app
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}

- name: Create DMG
run: |
brew install create-dmg
bash scripts/create-dmg.sh

- name: Upload DMG artifact
uses: actions/upload-artifact@v4
with:
name: Crow-${{ env.VERSION }}-arm64.dmg
path: Crow-${{ env.VERSION }}-arm64.dmg

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: Crow-${{ env.VERSION }}-arm64.dmg
generate_release_notes: true
prerelease: ${{ contains(github.ref_name, '-') }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ xcuserdata/

# Generated sources
Sources/Crow/Generated/
Sources/CrowCLI/Generated/

# App bundle
*.app
Expand Down
10 changes: 10 additions & 0 deletions Crow.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
15 changes: 14 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build setup ghostty app release clean clean-all check help
.PHONY: build setup ghostty app release sign dmg dist clean clean-all check help

FRAMEWORKS_DIR := Frameworks
XCFW := $(FRAMEWORKS_DIR)/GhosttyKit.xcframework
Expand All @@ -17,6 +17,9 @@ help:
@echo " ghostty Build GhosttyKit framework"
@echo " app Swift build only (debug)"
@echo " release Release build + .app bundle"
@echo " sign Code-sign Crow.app (ad-hoc or Developer ID)"
@echo " dmg Create distributable DMG"
@echo " dist Full distribution: release + sign + dmg"
@echo " clean Remove .build/ (keeps ghostty framework)"
@echo " clean-all Remove .build/ and Frameworks/ (full rebuild)"
@echo ""
Expand Down Expand Up @@ -53,6 +56,16 @@ release: $(XCFW)
bash scripts/generate-build-info.sh
bash scripts/bundle.sh

# --- Distribution ---

sign: release
bash scripts/sign.sh

dmg: sign
bash scripts/create-dmg.sh

dist: dmg

# --- Clean ---

clean:
Expand Down
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,56 @@

A native macOS application for managing AI-powered development sessions. Orchestrates git worktrees, Claude Code instances, and GitHub/GitLab issue tracking in a unified interface with an embedded Ghostty terminal.

## Installation

### Download (Recommended)

Download the latest DMG from [GitHub Releases](https://github.com/radiusmethod/crow/releases/latest), open it, and drag **Crow.app** to your Applications folder.

> **Note:** If macOS shows "Crow can't be opened because Apple cannot check it for malicious software," right-click the app and select **Open**, then click **Open** in the dialog. This is expected for apps distributed outside the Mac App Store without a Developer ID signature.

### Homebrew

*Coming soon.* Once available:

```bash
brew install radiusmethod/tap/crow
```

### Build from Source

See [Prerequisites](#prerequisites) and [Quick Start](#quick-start) below.

### Platform Support

| Platform | Architecture | Status |
|----------|-------------|--------|
| macOS 14+ (Sonoma) | Apple Silicon (arm64) | Supported |
| macOS (Intel) | x86_64 | Not supported |
| Linux | — | Not planned (heavy AppKit/SwiftUI/Metal dependency) |
| Windows | — | Not planned |

### Runtime Dependencies

Regardless of installation method, the following tools must be installed separately:

| Tool | Purpose | Install |
|------|---------|---------|
| `gh` | GitHub issue tracking and PR status | `brew install gh` |
| `git` | Worktree management | Ships with Xcode CLT |
| `claude` | Claude Code AI assistant | [claude.ai/download](https://claude.ai/download) |
| `glab` | GitLab CLI (optional) | `brew install glab` |

### Updating

Crow does not currently include auto-update. To update:

- **Download:** Re-download the latest DMG from [GitHub Releases](https://github.com/radiusmethod/crow/releases/latest) and replace the app in your Applications folder.
- **Homebrew:** `brew upgrade crow` (once the tap is available).
- **Source:** `git pull && make build`.

Auto-update via Sparkle or GitHub API polling may be added in a future release based on user demand.

## Prerequisites

### System Requirements
Expand Down
2 changes: 1 addition & 1 deletion Sources/CrowCLI/CrowCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ struct Crow: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "crow",
abstract: "CLI for Crow — manage sessions, terminals, and metadata",
version: "0.1.0",
version: CLIVersion.version,
subcommands: [
Setup.self,
NewSession.self,
Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
24 changes: 20 additions & 4 deletions scripts/bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,30 @@ BUILD_DIR="$ROOT_DIR/.build/release"
APP_DIR="$ROOT_DIR/Crow.app"
FRAMEWORKS_DIR="$ROOT_DIR/Frameworks"

# --- Version ---
if [ -n "${VERSION:-}" ]; then
APP_VERSION="$VERSION"
elif [ -f "$ROOT_DIR/VERSION" ]; then
APP_VERSION="$(tr -d '[:space:]' < "$ROOT_DIR/VERSION")"
else
APP_VERSION="0.1.0"
fi

# Build number from git commit count
if git -C "$ROOT_DIR" rev-parse HEAD >/dev/null 2>&1; then
BUILD_NUMBER=$(git -C "$ROOT_DIR" rev-list --count HEAD)
else
BUILD_NUMBER="1"
fi

echo "==> Generating build info..."
bash "$SCRIPT_DIR/generate-build-info.sh"

echo "==> Building release..."
cd "$ROOT_DIR"
swift build -c release

echo "==> Creating app bundle..."
echo "==> Creating app bundle (version $APP_VERSION, build $BUILD_NUMBER)..."
rm -rf "$APP_DIR"
mkdir -p "$APP_DIR/Contents/MacOS"
mkdir -p "$APP_DIR/Contents/Resources"
Expand All @@ -30,7 +46,7 @@ if [ -d "$FRAMEWORKS_DIR/ghostty-resources" ]; then
fi

# Create Info.plist
cat > "$APP_DIR/Contents/Info.plist" << 'PLIST'
cat > "$APP_DIR/Contents/Info.plist" << PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
Expand All @@ -44,9 +60,9 @@ cat > "$APP_DIR/Contents/Info.plist" << 'PLIST'
<key>CFBundleDisplayName</key>
<string>Crow</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>${BUILD_NUMBER}</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>${APP_VERSION}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSMinimumSystemVersion</key>
Expand Down
55 changes: 55 additions & 0 deletions scripts/create-dmg.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# Create a DMG from Crow.app
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
APP_DIR="${1:-$ROOT_DIR/Crow.app}"

if [ ! -d "$APP_DIR" ]; then
echo "ERROR: App bundle not found at $APP_DIR"
exit 1
fi

# --- Version ---
if [ -n "${VERSION:-}" ]; then
APP_VERSION="$VERSION"
elif [ -f "$ROOT_DIR/VERSION" ]; then
APP_VERSION="$(tr -d '[:space:]' < "$ROOT_DIR/VERSION")"
else
APP_VERSION="0.1.0"
fi

DMG_NAME="Crow-${APP_VERSION}-arm64.dmg"
DMG_PATH="$ROOT_DIR/$DMG_NAME"

# Remove existing DMG
rm -f "$DMG_PATH"

if command -v create-dmg >/dev/null 2>&1; then
echo "==> Creating DMG with create-dmg..."
create-dmg \
--volname "Crow" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--icon "Crow.app" 150 190 \
--app-drop-link 450 190 \
--no-internet-enable \
"$DMG_PATH" \
"$APP_DIR"
else
echo "==> Creating DMG with hdiutil (install create-dmg for a nicer result)..."
STAGING_DIR="$(mktemp -d)"
cp -R "$APP_DIR" "$STAGING_DIR/"
ln -s /Applications "$STAGING_DIR/Applications"

hdiutil create -volname "Crow" \
-srcfolder "$STAGING_DIR" \
-ov -format UDZO \
"$DMG_PATH"

rm -rf "$STAGING_DIR"
fi

echo "==> DMG created: $DMG_PATH"
37 changes: 31 additions & 6 deletions scripts/generate-build-info.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
#!/usr/bin/env bash
# Generate BuildInfo.swift with git SHA and build date
# Generate BuildInfo.swift and CLIVersion.swift with git SHA, build date, and version
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
OUTPUT_DIR="$ROOT_DIR/Sources/Crow/Generated"
OUTPUT_FILE="$OUTPUT_DIR/BuildInfo.swift"

mkdir -p "$OUTPUT_DIR"
# --- Version ---
if [ -n "${VERSION:-}" ]; then
APP_VERSION="$VERSION"
elif [ -f "$ROOT_DIR/VERSION" ]; then
APP_VERSION="$(tr -d '[:space:]' < "$ROOT_DIR/VERSION")"
else
APP_VERSION="0.1.0"
fi

# Capture git info (fallback to "dev" if not in a git repo)
# --- Git info ---
if git -C "$ROOT_DIR" rev-parse HEAD >/dev/null 2>&1; then
GIT_SHA=$(git -C "$ROOT_DIR" rev-parse HEAD)
GIT_SHORT_SHA=$(git -C "$ROOT_DIR" rev-parse --short HEAD)
Expand All @@ -20,13 +25,33 @@ fi

BUILD_DATE=$(date -u +"%Y-%m-%d")

# --- Generate BuildInfo.swift (Crow app target) ---
OUTPUT_DIR="$ROOT_DIR/Sources/Crow/Generated"
OUTPUT_FILE="$OUTPUT_DIR/BuildInfo.swift"
mkdir -p "$OUTPUT_DIR"

cat > "$OUTPUT_FILE" << EOF
// Auto-generated by scripts/generate-build-info.sh — do not edit
enum BuildInfo {
static let version = "$APP_VERSION"
static let gitCommitSHA = "$GIT_SHA"
static let gitCommitShortSHA = "$GIT_SHORT_SHA"
static let buildDate = "$BUILD_DATE"
}
EOF

echo "Generated $OUTPUT_FILE (SHA: $GIT_SHORT_SHA, date: $BUILD_DATE)"
echo "Generated $OUTPUT_FILE (version: $APP_VERSION, SHA: $GIT_SHORT_SHA, date: $BUILD_DATE)"

# --- Generate CLIVersion.swift (CrowCLI target) ---
CLI_OUTPUT_DIR="$ROOT_DIR/Sources/CrowCLI/Generated"
CLI_OUTPUT_FILE="$CLI_OUTPUT_DIR/CLIVersion.swift"
mkdir -p "$CLI_OUTPUT_DIR"

cat > "$CLI_OUTPUT_FILE" << EOF
// Auto-generated by scripts/generate-build-info.sh — do not edit
enum CLIVersion {
static let version = "$APP_VERSION"
}
EOF

echo "Generated $CLI_OUTPUT_FILE"
Loading