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
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI

on:
pull_request:
push:
branches: [main]

jobs:
test:
runs-on: macos-15
steps:
- uses: actions/checkout@v6
- uses: jdx/mise-action@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify shell tasks
run: |
mise run test
codebase lint:mise-settings "$PWD"
codebase lint:bats-test-task "$PWD"
codebase lint:bats-test-helper "$PWD"
codebase lint:mcr-scope "$PWD"
codebase lint:or-true "$PWD"
codebase lint:shellcheck "$PWD"
- name: Verify Swift package
run: swift test
- name: Verify generated README
run: |
mise run readme
git diff --exit-code README.md
3 changes: 2 additions & 1 deletion .mise/tasks/ai
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cat << 'EOF'
# Wallpapers

> llms.txt for AI agents helping users USE this tool.
> For agents working ON the codebase, see CLAUDE.md.
> For agents working ON the codebase, see the Development section in README.md.

## Overview

Expand Down Expand Up @@ -411,6 +411,7 @@ if [ -d "$OUTPUT_DIR" ] && [ "$(ls -A "$OUTPUT_DIR"/*.png 2>/dev/null)" ]; then
echo ""
echo "| File | Size | Modified |"
echo "|------|------|----------|"
# shellcheck disable=SC2012 # Human-readable listing of tool-generated wallpaper files.
ls -lh "$OUTPUT_DIR"/*.png 2>/dev/null | awk '{print "| " $NF " | " $5 " | " $6 " " $7 " |"}' | sed "s|$OUTPUT_DIR/||"
echo ""
else
Expand Down
38 changes: 16 additions & 22 deletions .mise/tasks/apply/_default
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,10 @@
#USAGE flag "--apps" help="Only launch and position apps"
#USAGE flag "--clean" help="Close previous app windows before applying"
set -e
source "${MISE_CONFIG_ROOT}/lib/common.sh"

OUTPUT_DIR="$HOME/.local/share/wallpapers"
CONFIG_FILE="$HOME/.config/wallpapers/config.json"

if [ ! -f "$CONFIG_FILE" ]; then
gum style --foreground 196 "❌ Config not found. Run: mise run config:init"
exit 1
fi

# Require butthair (manages Hammerspoon backend)
if ! command -v butthair &>/dev/null; then
gum style --foreground 196 "❌ butthair not found. Install with: shiv install butthair"
exit 1
fi
require_config
require_command butthair "Install with: shiv install butthair"

# Require Hammerspoon via butthair
APP_PATH="/Applications/Hammerspoon.app"
Expand Down Expand Up @@ -51,13 +41,13 @@ cache_bust() {
}

# Auto-detect screen resolution
SCREEN_RES=$(system_profiler SPDisplaysDataType | grep -i "Resolution:" | head -1 | sed 's/.*: //' | sed 's/ Retina//' | sed 's/ //g')
SCREEN_W=$(echo "$SCREEN_RES" | cut -d'x' -f1)
SCREEN_H=$(echo "$SCREEN_RES" | cut -d'x' -f2)
SCREEN_RES=$(detect_screen_resolution)
SCREEN_W=$(resolution_width "$SCREEN_RES")
SCREEN_H=$(resolution_height "$SCREEN_RES")

# Apply to all spaces
{
CONFIG_COUNT=$(jq 'if .spaces then .spaces | length elif .workspaces then .workspaces | length else 0 end' "$CONFIG_FILE")
CONFIG_COUNT=$(jq 'if .spaces then .spaces | length elif .workspaces then .workspaces | length else 0 end' "$WALLPAPERS_CONFIG_FILE")

SPACE_COUNT=$(butthair spaces:list -- -j 2>/dev/null | jq 'length')
if [ -z "$SPACE_COUNT" ] || [ "$SPACE_COUNT" = "0" ]; then
Expand All @@ -77,7 +67,7 @@ SCREEN_H=$(echo "$SCREEN_RES" | cut -d'x' -f2)
else
DIFF=$((SPACE_COUNT - CONFIG_COUNT))
gum style --foreground 245 "You have $DIFF extra space(s). Either:"
echo " • Add more workspaces to your config: mise run config:edit"
echo " • Add more workspaces to your config: wp config edit"
echo " • Or remove spaces: Mission Control → hover space → click X"
fi
echo ""
Expand Down Expand Up @@ -131,7 +121,7 @@ SCREEN_H=$(echo "$SCREEN_RES" | cut -d'x' -f2)
USABLE_H=$(echo "$SCREEN_JSON" | jq '.usable.h')
USABLE_Y=$(echo "$SCREEN_JSON" | jq '.usable.y')

GAP=$(jq '.defaults.gap // 8' "$CONFIG_FILE")
GAP=$(jq '.defaults.gap // 8' "$WALLPAPERS_CONFIG_FILE")

STATE_FILE="$HOME/.local/share/wallpapers/.apply-state"
mkdir -p "$(dirname "$STATE_FILE")"
Expand All @@ -152,11 +142,15 @@ SCREEN_H=$(echo "$SCREEN_RES" | cut -d'x' -f2)
PREV_LAST_SPACE=""
for i in "${!PREV_WINS[@]}"; do
if [ "${PREV_SPACES[$i]}" != "$PREV_LAST_SPACE" ]; then
butthair spaces:goto -- "${PREV_SPACES[$i]}" < /dev/null 2>/dev/null || true
if ! butthair spaces:goto -- "${PREV_SPACES[$i]}" < /dev/null 2>/dev/null; then
: # Window cleanup is best-effort; continue closing what we can.
fi
sleep 0.3
PREV_LAST_SPACE="${PREV_SPACES[$i]}"
fi
butthair windows:close -- "${PREV_WINS[$i]}" < /dev/null 2>/dev/null || true
if ! butthair windows:close -- "${PREV_WINS[$i]}" < /dev/null 2>/dev/null; then
: # Window may already be gone.
fi
done
fi
: > "$STATE_FILE"
Expand All @@ -165,7 +159,7 @@ SCREEN_H=$(echo "$SCREEN_RES" | cut -d'x' -f2)

for s in $(seq 0 $((CONFIG_COUNT - 1))); do
SPACE_NUM=$((s + 1))
ZONES_JSON=$(jq -c ".spaces[$s].zones" "$CONFIG_FILE")
ZONES_JSON=$(jq -c ".spaces[$s].zones" "$WALLPAPERS_CONFIG_FILE")
ZONE_COUNT=$(echo "$ZONES_JSON" | jq 'length')

# Skip spaces with no apps
Expand Down
20 changes: 12 additions & 8 deletions .mise/tasks/apply/undo
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
#!/usr/bin/env bash
#MISE description="Close windows created by the last 'apply --apps'"
source "${MISE_CONFIG_ROOT}/lib/common.sh"

STATE_FILE="$HOME/.local/share/wallpapers/.apply-state"
STATE_FILE="$WALLPAPERS_OUTPUT_DIR/.apply-state"

if ! command -v butthair &>/dev/null; then
gum style --foreground 196 "❌ butthair not found. Install with: shiv install butthair"
exit 1
fi
require_command butthair "Install with: shiv install butthair"

if [ ! -f "$STATE_FILE" ] || [ ! -s "$STATE_FILE" ]; then
echo "Nothing to undo."
Expand All @@ -32,16 +30,22 @@ for i in "${!WINS[@]}"; do
WIN_ID="${WINS[$i]}"

if [ "$SPACE_NUM" != "$LAST_SPACE" ]; then
butthair spaces:goto -- "$SPACE_NUM" < /dev/null 2>/dev/null || true
if ! butthair spaces:goto -- "$SPACE_NUM" < /dev/null 2>/dev/null; then
: # Window may already be gone; continue best-effort cleanup.
fi
sleep 0.3
LAST_SPACE="$SPACE_NUM"
fi

RESULT=$(butthair windows:close -- "$WIN_ID" < /dev/null 2>&1) || true
if ! RESULT=$(butthair windows:close -- "$WIN_ID" < /dev/null 2>&1); then
: # Window may already be closed; print butthair's response below.
fi
echo " $RESULT"
done

butthair spaces:goto -- "$CURRENT_SPACE" < /dev/null 2>/dev/null || true
if ! butthair spaces:goto -- "$CURRENT_SPACE" < /dev/null 2>/dev/null; then
: # Restore is best-effort; cleanup state below regardless.
fi

rm -f "$STATE_FILE"
echo "Done."
7 changes: 3 additions & 4 deletions .mise/tasks/clean
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#!/usr/bin/env bash
#MISE description="Remove all generated wallpapers"
source "${MISE_CONFIG_ROOT}/lib/common.sh"

OUTPUT_DIR="$HOME/.local/share/wallpapers"

if gum confirm "Delete all generated wallpapers from $OUTPUT_DIR?"; then
rm -rf "$OUTPUT_DIR"/*.png
if gum confirm "Delete all generated wallpapers from $WALLPAPERS_OUTPUT_DIR?"; then
rm -rf "$WALLPAPERS_OUTPUT_DIR"/*.png
gum style --foreground 46 "✅ Cleaned wallpapers directory"
else
gum style --foreground 244 "Cancelled"
Expand Down
8 changes: 8 additions & 0 deletions .mise/tasks/cli
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
#!/usr/bin/env bash
#MISE description="Run generator directly with arguments"
#USAGE arg "<name>" help="Workspace name"
#USAGE flag "-d --description <desc>" help="Subtitle text"
#USAGE flag "-r --resolution <res>" help="Preset: 1080p, 1440p, 4k, macbook-14, macbook-16, imac-24, studio-display"
#USAGE flag "--width <w>" help="Custom width"
#USAGE flag "--height <h>" help="Custom height"
#USAGE flag "--bg-color <color>" help="Background hex color"
#USAGE flag "--text-color <color>" help="Text hex color"
#USAGE flag "--style <style>" help="Visual style: classic, diagonal, tiled, flowfield, typography, perspective"
swift run generate -- "$@"
12 changes: 3 additions & 9 deletions .mise/tasks/config/edit
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
#!/usr/bin/env bash
#MISE description="Edit config file in your editor"
set -e
source "${MISE_CONFIG_ROOT}/lib/common.sh"

CONFIG_FILE="$HOME/.config/wallpapers/config.json"

if [ ! -f "$CONFIG_FILE" ]; then
gum style --foreground 196 "❌ Config not found at $CONFIG_FILE"
gum style --foreground 245 "Run: mise run config:init"
exit 1
fi

${EDITOR:-nano} "$CONFIG_FILE"
require_config
${EDITOR:-nano} "$WALLPAPERS_CONFIG_FILE"
16 changes: 7 additions & 9 deletions .mise/tasks/config/init
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
#!/usr/bin/env bash
#MISE description="Initialize config file with example workspaces"
set -e

CONFIG_DIR="$HOME/.config/wallpapers"
CONFIG_FILE="$CONFIG_DIR/config.json"
source "${MISE_CONFIG_ROOT}/lib/common.sh"

# Check if config already exists
if [ -f "$CONFIG_FILE" ]; then
if ! gum confirm "Config already exists at $CONFIG_FILE. Overwrite?"; then
if [ -f "$WALLPAPERS_CONFIG_FILE" ]; then
if ! gum confirm "Config already exists at $WALLPAPERS_CONFIG_FILE. Overwrite?"; then
gum style --foreground 226 "⚠️ Cancelled"
exit 0
fi
fi

# Create config directory
mkdir -p "$CONFIG_DIR"
mkdir -p "$WALLPAPERS_CONFIG_DIR"

# Write template config
cat > "$CONFIG_FILE" << 'EOF'
cat > "$WALLPAPERS_CONFIG_FILE" << 'EOF'
{
"workspaces": [
{
Expand All @@ -44,5 +42,5 @@ cat > "$CONFIG_FILE" << 'EOF'
}
EOF

gum style --foreground 46 "✅ Config created at $CONFIG_FILE"
gum style --foreground 245 "Edit with: mise run config:edit"
gum style --foreground 46 "✅ Config created at $WALLPAPERS_CONFIG_FILE"
gum style --foreground 245 "Edit with: wallpapers config edit"
25 changes: 12 additions & 13 deletions .mise/tasks/generate
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#!/usr/bin/env bash
#MISE description="Generate a wallpaper interactively"
set -e

OUTPUT_DIR="$HOME/.local/share/wallpapers"
source "${MISE_CONFIG_ROOT}/lib/common.sh"

# Styled header
gum style \
Expand All @@ -19,18 +18,18 @@ if [ -z "$NAME" ]; then
fi

# Ask for optional description
DESC_ARG=""
DESC_ARGS=()
if gum confirm "Add a description?"; then
DESCRIPTION=$(gum input --placeholder "Brief description" --prompt "📋 Description: ")
if [ -n "$DESCRIPTION" ]; then
DESC_ARG="--description"
DESC_ARGS=("--description" "$DESCRIPTION")
fi
fi

# Detect current screen resolution
SCREEN_RES=$(system_profiler SPDisplaysDataType | grep -i "Resolution:" | head -1 | sed 's/.*: //' | sed 's/ Retina//' | sed 's/ //g')
SCREEN_W=$(echo "$SCREEN_RES" | cut -d'x' -f1)
SCREEN_H=$(echo "$SCREEN_RES" | cut -d'x' -f2)
SCREEN_RES=$(detect_screen_resolution)
SCREEN_W=$(resolution_width "$SCREEN_RES")
SCREEN_H=$(resolution_height "$SCREEN_RES")

# Select resolution
RESOLUTION=$(gum choose --header "📐 Select resolution:" \
Expand All @@ -45,10 +44,10 @@ RESOLUTION=$(gum choose --header "📐 Select resolution:" \

# Extract resolution - handle auto-detect
if [[ "$RESOLUTION" == auto* ]]; then
RES_ARGS="--width $SCREEN_W --height $SCREEN_H"
RES_ARGS=("--width" "$SCREEN_W" "--height" "$SCREEN_H")
else
RES_KEY=$(echo "$RESOLUTION" | cut -d' ' -f1)
RES_ARGS="-r $RES_KEY"
RES_ARGS=("-r" "$RES_KEY")
fi

# Select colors
Expand Down Expand Up @@ -95,13 +94,13 @@ STYLE=$(gum choose --header "Visual style:" \
# Build and run Swift generator
gum spin --spinner dot --title "Generating wallpaper..." -- \
swift run generate -- "$NAME" \
$RES_ARGS \
"${RES_ARGS[@]}" \
--bg-color "$BG_COLOR" \
--text-color "$TEXT_COLOR" \
--style "$STYLE" \
$DESC_ARG ${DESCRIPTION:+"$DESCRIPTION"}
"${DESC_ARGS[@]}"

gum style --foreground 46 "✅ Wallpaper generated! Saved to $OUTPUT_DIR"
gum style --foreground 46 "✅ Wallpaper generated! Saved to $WALLPAPERS_OUTPUT_DIR"

# Show the generated files
ls -la "$OUTPUT_DIR"
ls -la "$WALLPAPERS_OUTPUT_DIR"
23 changes: 7 additions & 16 deletions .mise/tasks/goto
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,12 @@
#USAGE arg "[name]" help="Workspace name/id, or '-' to go back (shows picker if omitted)"
#USAGE complete "name" run="jq -r '.workspaces[] | (.id // .name) + \":\" + (.description // .name)' ~/.config/wallpapers/config.json 2>/dev/null" descriptions=#true
set -e
source "${MISE_CONFIG_ROOT}/lib/common.sh"

CONFIG_FILE="$HOME/.config/wallpapers/config.json"
STATE_DIR="$HOME/.local/state/wallpapers"
PREVIOUS_FILE="$STATE_DIR/previous-space"
PREVIOUS_FILE="$WALLPAPERS_STATE_DIR/previous-space"

if [ ! -f "$CONFIG_FILE" ]; then
gum style --foreground 196 "❌ Config not found. Run: mise run config:init"
exit 1
fi

# Require butthair
if ! command -v butthair &>/dev/null; then
gum style --foreground 196 "❌ butthair not found. Install with: shiv install butthair"
exit 1
fi
require_config
require_command butthair "Install with: shiv install butthair"

# Get current space
CURRENT=$(butthair spaces:current -- -i 2>/dev/null)
Expand All @@ -42,7 +33,7 @@ if [ "$TARGET" = "-" ]; then
fi
# Handle no argument (show picker)
elif [ -z "$TARGET" ]; then
WORKSPACES=$(jq -r '.workspaces | to_entries[] | "\(.key + 1). \(.value.name)"' "$CONFIG_FILE")
WORKSPACES=$(jq -r '.workspaces | to_entries[] | "\(.key + 1). \(.value.name)"' "$WALLPAPERS_CONFIG_FILE")

SELECTION=$(echo "$WORKSPACES" | gum choose --header "Go to workspace:")

Expand All @@ -64,7 +55,7 @@ else
or (.value.name | ascii_downcase) == $search
) |
.key + 1
' "$CONFIG_FILE")
' "$WALLPAPERS_CONFIG_FILE")

if [ -z "$TARGET_INDEX" ]; then
gum style --foreground 196 "❌ Workspace not found: $TARGET"
Expand All @@ -78,7 +69,7 @@ else
fi

# Save current space before navigating
mkdir -p "$STATE_DIR"
mkdir -p "$WALLPAPERS_STATE_DIR"
echo "$CURRENT" > "$PREVIOUS_FILE"

# Navigate
Expand Down
6 changes: 4 additions & 2 deletions .mise/tasks/hammerspoon/config
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ CONFIG_FILE="$CONFIG_DIR/init.lua"
WP_MODULE="$CONFIG_DIR/wp.lua"
APP_PATH="/Applications/Hammerspoon.app"
HS_CLI="$APP_PATH/Contents/Frameworks/hs/hs"
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
REPO_DIR="$MISE_CONFIG_ROOT"

# Require butthair to have set up Hammerspoon first
if [ ! -f "$CONFIG_FILE" ]; then
Expand Down Expand Up @@ -334,5 +334,7 @@ fi

# Reload config if Hammerspoon is running
if pgrep -x "Hammerspoon" > /dev/null && [ -x "$HS_CLI" ]; then
"$HS_CLI" -c "hs.reload()" 2>/dev/null && echo "Config reloaded" || true
if "$HS_CLI" -c "hs.reload()" 2>/dev/null; then
echo "Config reloaded"
fi
fi
Loading