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
93 changes: 93 additions & 0 deletions config/aerospace/aerospace.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# AeroSpace configuration for multi-project workspaces
# See: https://nikitabobko.github.io/AeroSpace/guide

start-at-login = true

# Normalizations
enable-normalization-flatten-containers = true
enable-normalization-opposite-orientation-for-nested-containers = true

# Mouse follows focus
on-focused-monitor-changed = ['move-mouse monitor-lazy-center']

# Run workspace launcher on workspace change
exec-on-workspace-change = [
'/bin/bash', '-c',
'~/.config/workspaces/workspace.sh ensure "$AEROSPACE_FOCUSED_WORKSPACE" 2>/dev/null &'
]

[gaps]
inner.horizontal = 8
inner.vertical = 8
outer.left = 8
outer.right = 8
outer.top = 8
outer.bottom = 8

[mode.main.binding]
# ── Workspace switching ──────────────────────────────────────
alt-1 = 'workspace 1'
alt-2 = 'workspace 2'
alt-3 = 'workspace 3'
alt-4 = 'workspace 4'
alt-5 = 'workspace 5'

# Toggle between last two workspaces (feels like Cmd+Tab between projects)
alt-tab = 'workspace-back-and-forth'

# ── Move window to workspace ────────────────────────────────
alt-shift-1 = 'move-node-to-workspace 1'
alt-shift-2 = 'move-node-to-workspace 2'
alt-shift-3 = 'move-node-to-workspace 3'
alt-shift-4 = 'move-node-to-workspace 4'
alt-shift-5 = 'move-node-to-workspace 5'

# ── Focus movement (vim-style) ──────────────────────────────
alt-h = 'focus left'
alt-j = 'focus down'
alt-k = 'focus up'
alt-l = 'focus right'

# Cycle windows within current workspace
alt-grave = 'focus --boundaries-action wrap-around-the-workspace dfs-next'
alt-shift-grave = 'focus --boundaries-action wrap-around-the-workspace dfs-prev'

# ── Move windows ─────────────────────────────────────────────
alt-shift-h = 'move left'
alt-shift-j = 'move down'
alt-shift-k = 'move up'
alt-shift-l = 'move right'

# ── Resize ───────────────────────────────────────────────────
alt-minus = 'resize smart -50'
alt-equal = 'resize smart +50'

# ── Layout ───────────────────────────────────────────────────
alt-slash = 'layout tiles horizontal vertical'
alt-comma = 'layout accordion horizontal vertical'
alt-f = 'fullscreen'
alt-shift-f = 'layout floating tiling'

# ── Service mode (for less common operations) ────────────────
alt-shift-semicolon = 'mode service'

[mode.service.binding]
esc = ['reload-config', 'mode main']
r = ['flatten-workspace-tree', 'mode main']
alt-shift-h = ['join-with left', 'mode main']
alt-shift-j = ['join-with down', 'mode main']
alt-shift-k = ['join-with up', 'mode main']
alt-shift-l = ['join-with right', 'mode main']

# ── Window rules ─────────────────────────────────────────────
[[on-window-detected]]
if.app-id = 'com.apple.finder'
run = 'layout floating'

[[on-window-detected]]
if.app-id = 'com.apple.systempreferences'
run = 'layout floating'

[[on-window-detected]]
if.app-id = 'com.apple.calculator'
run = 'layout floating'
19 changes: 19 additions & 0 deletions config/raycast/scripts/list-workspaces.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title List Workspaces
# @raycast.mode fullOutput
# @raycast.packageName Workspaces

# Optional parameters:
# @raycast.icon :desktopcomputer:

# Documentation:
# @raycast.description List all configured project workspaces
# @raycast.author workspace-restoration
# @raycast.authorURL https://github.com

~/.config/workspaces/workspace.sh list
echo ""
~/.config/workspaces/workspace.sh status
18 changes: 18 additions & 0 deletions config/raycast/scripts/switch-workspace.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Switch Workspace
# @raycast.mode fullOutput
# @raycast.packageName Workspaces

# Optional parameters:
# @raycast.icon :desktopcomputer:
# @raycast.argument1 { "type": "text", "placeholder": "project name" }

# Documentation:
# @raycast.description Switch to a project workspace (AeroSpace + iTerm2 + browser)
# @raycast.author workspace-restoration
# @raycast.authorURL https://github.com

~/.config/workspaces/workspace.sh open "$1"
81 changes: 81 additions & 0 deletions config/swiftbar/workspaces.5s.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/bin/bash

# SwiftBar plugin: workspace switcher menu bar item
# Refreshes every 5 seconds (per filename convention)
# Install: brew install --cask swiftbar
# Place in SwiftBar plugin directory

# <swiftbar.hideRunInTerminal>true</swiftbar.hideRunInTerminal>

PROJECTS_DIR="${HOME}/.config/workspaces/projects"
WORKSPACE_BIN="${HOME}/bin/workspace"
EDIT_NOTE_SCRIPT="${HOME}/.config/workspaces/lib/edit-note.sh"

# Get current AeroSpace workspace
CURRENT_WS=$(aerospace list-workspaces --focused 2>/dev/null || echo "?")

# Find which project maps to the current workspace
CURRENT_PROJECT=""
for f in "${PROJECTS_DIR}"/*.toml; do
[[ -f "$f" ]] || continue
name=$(basename "$f" .toml)
ws=$(grep "^workspace" "$f" | sed 's/.*= *//;s/"//g' | tr -d ' ')
if [[ "$ws" == "$CURRENT_WS" ]]; then
CURRENT_PROJECT="$name"
break
fi
done

# ── Menu bar title ───────────────────────────────────────────

if [[ -n "$CURRENT_PROJECT" ]]; then
echo ":desktopcomputer: ${CURRENT_PROJECT} | symbolize=true sfsize=13"
else
echo ":desktopcomputer: ws:${CURRENT_WS} | symbolize=true sfsize=13"
fi

echo "---"

# ── Show note for current workspace (if any) ────────────────

if [[ -n "$CURRENT_PROJECT" ]]; then
note_text=""
in_notes=false
while IFS= read -r line; do
if [[ "$line" =~ ^\[notes\] ]]; then
in_notes=true
continue
fi
if $in_notes; then
[[ "$line" =~ ^\[.*\] ]] && break
if [[ "$line" =~ ^text[[:space:]]*= ]]; then
note_text=$(echo "$line" | sed 's/^[^=]*=[[:space:]]*//;s/^"//;s/"$//')
break
fi
fi
done < "${PROJECTS_DIR}/${CURRENT_PROJECT}.toml"

if [[ -n "$note_text" ]]; then
echo ":note.text: ${note_text} | symbolize=true sfsize=11 color=systemYellow bash=${EDIT_NOTE_SCRIPT} param1=${CURRENT_PROJECT} terminal=false refresh=true"
else
echo ":note.text: Add note... | symbolize=true sfsize=11 color=systemSecondaryLabel bash=${EDIT_NOTE_SCRIPT} param1=${CURRENT_PROJECT} terminal=false refresh=true"
fi
echo "---"
fi

# ── Project list ─────────────────────────────────────────────

for f in "${PROJECTS_DIR}"/*.toml; do
[[ -f "$f" ]] || continue
name=$(basename "$f" .toml)
ws=$(grep "^workspace" "$f" | sed 's/.*= *//;s/"//g' | tr -d ' ')

if [[ "$name" == "$CURRENT_PROJECT" ]]; then
echo ":checkmark.circle.fill: ${name} (workspace ${ws}) | symbolize=true color=systemGreen sfsize=13"
else
echo ":circle: ${name} (workspace ${ws}) | symbolize=true sfsize=13 bash=${WORKSPACE_BIN} param1=open param2=${name} terminal=false refresh=true"
fi
done

echo "---"
echo "Refresh | refresh=true"
30 changes: 30 additions & 0 deletions config/workspaces/lib/edit-note.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash
# Edit a workspace note via a macOS input dialog
# Called by SwiftBar menu bar plugin
# Usage: edit-note.sh <project-name>

set -euo pipefail

PROJECT="${1:?project name required}"
WORKSPACE_BIN="${HOME}/bin/workspace"

# Get current note text
current_note=$("$WORKSPACE_BIN" note "$PROJECT" 2>/dev/null | sed "s/^${PROJECT}: //" | sed 's/(no note set)//')

# Show macOS input dialog
new_note=$(osascript <<EOF
set currentNote to "$current_note"
set dialogResult to display dialog "Note for workspace '${PROJECT}':" default answer currentNote buttons {"Cancel", "Clear", "Save"} default button "Save" with title "Workspace Note"
set buttonPressed to button returned of dialogResult
if buttonPressed is "Clear" then
return ""
else if buttonPressed is "Save" then
return text returned of dialogResult
else
error number -128
end if
EOF
) || exit 0

# Update the note
"$WORKSPACE_BIN" note "$PROJECT" "$new_note"
79 changes: 79 additions & 0 deletions config/workspaces/lib/iterm.scpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
-- iTerm2 AppleScript helpers for workspace management
-- Usage:
-- osascript iterm.scpt restore <arrangement-name>
-- osascript iterm.scpt save <arrangement-name>
-- osascript iterm.scpt close <arrangement-name>

on run argv
if (count of argv) < 2 then
log "Usage: osascript iterm.scpt <command> <arrangement-name>"
return
end if

set cmd to item 1 of argv
set arrangementName to item 2 of argv

if cmd is "restore" then
restoreArrangement(arrangementName)
else if cmd is "save" then
saveArrangement(arrangementName)
else if cmd is "close" then
closeArrangement(arrangementName)
else
log "Unknown command: " & cmd
end if
end run

on restoreArrangement(arrangementName)
tell application "iTerm2"
activate
try
-- Check if arrangement exists before restoring
set arrangementNames to name of every window arrangement
if arrangementName is in arrangementNames then
restore window arrangement arrangementName
else
-- Arrangement not saved yet — just open a new window
create window with default profile
end if
on error errMsg
log "Error restoring arrangement: " & errMsg
-- Fallback: just activate iTerm2
activate
end try
end tell
end restoreArrangement

on saveArrangement(arrangementName)
tell application "iTerm2"
try
save window arrangement arrangementName
on error errMsg
log "Error saving arrangement: " & errMsg
end try
end tell
end saveArrangement

on closeArrangement(arrangementName)
tell application "iTerm2"
try
-- Close all windows that belong to this arrangement
-- iTerm2 doesn't track which windows came from which arrangement,
-- so we close windows whose tabs match the arrangement's profile pattern
set windowCount to count of windows
repeat with i from windowCount to 1 by -1
set w to window i
try
set tabCount to count of tabs of w
set firstTab to tab 1 of w
set sessionName to name of current session of firstTab
if sessionName contains arrangementName then
close w
end if
end try
end repeat
on error errMsg
log "Error closing arrangement: " & errMsg
end try
end tell
end closeArrangement
15 changes: 15 additions & 0 deletions config/workspaces/projects/api.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "api"
workspace = 2
directory = "~/code/api"

[terminal]
arrangement = "api"

[browser]
url = "http://localhost:8000/docs"

[notes]
# Editable notes displayed when switching to this workspace.
# Edit this file or run: workspace note api "your note here"
text = ""
15 changes: 15 additions & 0 deletions config/workspaces/projects/frontend.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "frontend"
workspace = 1
directory = "~/code/frontend"

[terminal]
arrangement = "frontend"

[browser]
url = "http://localhost:3000"

[notes]
# Editable notes displayed when switching to this workspace.
# Edit this file or run: workspace note frontend "your note here"
text = ""
Loading