aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.DS_Storebin0 -> 8196 bytes
-rw-r--r--.gitignore1
-rw-r--r--README.md75
-rw-r--r--flake.lock141
-rw-r--r--flake.nix90
-rw-r--r--hosts/nixbox/hardware-configuration.nix29
-rw-r--r--modules/darwin/default.nix36
-rw-r--r--modules/darwin/homebrew.nix182
-rw-r--r--modules/darwin/macos.nix140
-rw-r--r--modules/home/claude.nix87
-rw-r--r--modules/home/default.nix52
-rw-r--r--modules/home/ghostty.nix14
-rw-r--r--modules/home/git.nix55
-rw-r--r--modules/home/lazygit.nix20
-rw-r--r--modules/home/starship.nix7
-rw-r--r--modules/home/tmux.nix33
-rw-r--r--modules/home/twm.nix38
-rw-r--r--modules/home/zsh.nix186
-rw-r--r--modules/nixos/default.nix59
-rwxr-xr-xscripts/cleanup.sh218
20 files changed, 1463 insertions, 0 deletions
diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..ee14173
--- /dev/null
+++ b/.DS_Store
Binary files differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d6b130c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.claude/settings.local.json
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..41ec958
--- /dev/null
+++ b/README.md
@@ -0,0 +1,75 @@
+# dotfiles
+
+My Development Setup, managed with [Nix](https://nixos.org), [nix-darwin](https://github.com/LnL7/nix-darwin), and [home-manager](https://github.com/nix-community/home-manager). Works on macOS, NixOS, and standalone Linux.
+
+## Quickstart
+
+### 1. Install Nix
+
+```shell
+curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
+```
+
+### 2. Clone the repository
+
+```shell
+git clone git@github.com:simenandre/dotfiles.git ~/dotfiles
+cd ~/dotfiles
+```
+
+### 3. Apply the configuration
+
+#### macOS
+
+On first run, bootstrap nix-darwin:
+
+```shell
+nix run nix-darwin -- switch --flake .#default
+```
+
+On subsequent runs:
+
+```shell
+darwin-rebuild switch --flake .#default
+```
+
+#### NixOS
+
+Generate hardware config and rebuild:
+
+```shell
+sudo nixos-generate-config --show-hardware-config > modules/nixos/hardware-configuration.nix
+sudo nixos-rebuild switch --flake .#nixbox
+```
+
+#### Linux (standalone home-manager)
+
+```shell
+# x86_64
+home-manager switch --flake .#simenandre
+
+# aarch64
+home-manager switch --flake .#simenandre@aarch64-linux
+```
+
+## Structure
+
+```
+flake.nix # Entry point – inputs & all configurations
+modules/
+ darwin/
+ default.nix # System-level configuration (nix settings, core packages)
+ homebrew.nix # Homebrew packages (taps, brews, casks, Mac App Store)
+ macos.nix # macOS system defaults
+ home/
+ default.nix # home-manager entry point (platform-aware home directory)
+ git.nix # Git configuration (platform-aware SSH signing path)
+ zsh.nix # Zsh configuration (zshenv, zshrc, aliases, path, extras)
+ starship.nix # Starship prompt
+ tmux.nix # Tmux configuration
+ ghostty.nix # Ghostty terminal configuration
+ nixos/
+ default.nix # NixOS system configuration
+scripts/
+ cleanup.sh # macOS cleanup utility
+```
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..913419f
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,141 @@
+{
+ "nodes": {
+ "config-nvim": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1774097143,
+ "narHash": "sha256-NTNEkaZa5CdMrsycC+tqqFBUqa2J9r0GTwFWeU3gWog=",
+ "owner": "simenandre",
+ "repo": "config.nvim",
+ "rev": "83cd13c60ba2b8de7b5a50affa0e110c2a608cd6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "simenandre",
+ "repo": "config.nvim",
+ "type": "github"
+ }
+ },
+ "dina": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1775217257,
+ "narHash": "sha256-3m79jjgeNoK7JxhPrmNLGWI0vrZ1gtBCe3qWmijplYU=",
+ "owner": "dinacomputer",
+ "repo": "cli",
+ "rev": "4ddd97559fa6dfd2e4b3a4ccea634c16a40a0274",
+ "type": "github"
+ },
+ "original": {
+ "owner": "dinacomputer",
+ "repo": "cli",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "home-manager": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1774007980,
+ "narHash": "sha256-FOnZjElEI8pqqCvB6K/1JRHTE8o4rer8driivTpq2uo=",
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "rev": "9670de2921812bc4e0452f6e3efd8c859696c183",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "home-manager",
+ "type": "github"
+ }
+ },
+ "nix-darwin": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1773000227,
+ "narHash": "sha256-zm3ftUQw0MPumYi91HovoGhgyZBlM4o3Zy0LhPNwzXE=",
+ "owner": "LnL7",
+ "repo": "nix-darwin",
+ "rev": "da529ac9e46f25ed5616fd634079a5f3c579135f",
+ "type": "github"
+ },
+ "original": {
+ "owner": "LnL7",
+ "repo": "nix-darwin",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1773840656,
+ "narHash": "sha256-9tpvMGFteZnd3gRQZFlRCohVpqooygFuy9yjuyRL2C0=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "9cf7092bdd603554bd8b63c216e8943cf9b12512",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "config-nvim": "config-nvim",
+ "dina": "dina",
+ "home-manager": "home-manager",
+ "nix-darwin": "nix-darwin",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..db317d9
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,90 @@
+{
+ description = "Simen Andre's dotfiles configuration";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+
+ nix-darwin = {
+ url = "github:LnL7/nix-darwin";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ home-manager = {
+ url = "github:nix-community/home-manager";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ config-nvim = {
+ url = "github:simenandre/config.nvim";
+ flake = false;
+ };
+
+ dina = {
+ url = "github:dinacomputer/cli";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs =
+ {
+ self,
+ nixpkgs,
+ nix-darwin,
+ home-manager,
+ config-nvim,
+ dina,
+ }:
+ let
+ mkHomeModules = username: [
+ ./modules/home
+ {
+ home.username = username;
+ home.file.".config/nvim".source = config-nvim;
+ }
+ ];
+ in
+ {
+ darwinConfigurations."default" = nix-darwin.lib.darwinSystem {
+ system = "aarch64-darwin";
+ modules = [
+ ./modules/darwin
+ home-manager.darwinModules.home-manager
+ {
+ home-manager.useGlobalPkgs = true;
+ home-manager.useUserPackages = true;
+ home-manager.backupFileExtension = "bak";
+ home-manager.extraSpecialArgs = { inherit dina; };
+ home-manager.users.cobraz = { imports = mkHomeModules "cobraz"; };
+ }
+ ];
+ };
+
+ nixosConfigurations."nixbox" = nixpkgs.lib.nixosSystem {
+ system = "x86_64-linux";
+ modules = [
+ ./hosts/nixbox/hardware-configuration.nix
+ ./modules/nixos
+ home-manager.nixosModules.home-manager
+ {
+ home-manager.useGlobalPkgs = true;
+ home-manager.useUserPackages = true;
+ home-manager.backupFileExtension = "bak";
+ home-manager.extraSpecialArgs = { inherit dina; };
+ home-manager.users.simenandre = { imports = mkHomeModules "simenandre"; };
+ }
+ ];
+ };
+
+ homeConfigurations."simenandre" = home-manager.lib.homeManagerConfiguration {
+ pkgs = nixpkgs.legacyPackages."x86_64-linux";
+ extraSpecialArgs = { inherit dina; };
+ modules = mkHomeModules "simenandre";
+ };
+
+ homeConfigurations."simenandre@aarch64-linux" = home-manager.lib.homeManagerConfiguration {
+ pkgs = nixpkgs.legacyPackages."aarch64-linux";
+ extraSpecialArgs = { inherit dina; };
+ modules = mkHomeModules "simenandre";
+ };
+ };
+}
diff --git a/hosts/nixbox/hardware-configuration.nix b/hosts/nixbox/hardware-configuration.nix
new file mode 100644
index 0000000..2c91a21
--- /dev/null
+++ b/hosts/nixbox/hardware-configuration.nix
@@ -0,0 +1,29 @@
+# Generated by 'nixos-generate-config' for nixbox (QEMU/KVM guest).
+# Regenerate with: nixos-generate-config --show-hardware-config
+{ config, lib, pkgs, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/profiles/qemu-guest.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ahci" "virtio_pci" "virtio_scsi" "sd_mod" "sr_mod" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ "kvm-amd" ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "/dev/disk/by-uuid/096adbf8-2498-4f70-b661-c699dbc4f257";
+ fsType = "ext4";
+ };
+
+ fileSystems."/boot" =
+ { device = "/dev/disk/by-uuid/AE22-C56D";
+ fsType = "vfat";
+ options = [ "fmask=0022" "dmask=0022" ];
+ };
+
+ swapDevices = [ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+}
diff --git a/modules/darwin/default.nix b/modules/darwin/default.nix
new file mode 100644
index 0000000..5b487ef
--- /dev/null
+++ b/modules/darwin/default.nix
@@ -0,0 +1,36 @@
+{ pkgs, ... }:
+
+{
+ imports = [
+ ./homebrew.nix
+ ./macos.nix
+ ];
+
+ # Nix is managed by Determinate installer
+ nix.enable = false;
+
+ # Allow unfree packages
+ nixpkgs.config.allowUnfree = true;
+
+ # System state version – do not change after first installation
+ system.stateVersion = 5;
+
+ # Primary user for system defaults and homebrew
+ system.primaryUser = "cobraz";
+
+ # Enable Touch ID for sudo
+ security.pam.services.sudo_local.touchIdAuth = true;
+
+ # Remap Caps Lock to Escape
+ system.keyboard = {
+ enableKeyMapping = true;
+ remapCapsLockToEscape = true;
+ };
+
+ # Core system packages available to all users
+ environment.systemPackages = with pkgs; [
+ curl
+ git
+ wget
+ ];
+}
diff --git a/modules/darwin/homebrew.nix b/modules/darwin/homebrew.nix
new file mode 100644
index 0000000..ada2229
--- /dev/null
+++ b/modules/darwin/homebrew.nix
@@ -0,0 +1,182 @@
+{ ... }:
+
+{
+ homebrew = {
+ enable = true;
+
+ onActivation = {
+ autoUpdate = true;
+ upgrade = true;
+ cleanup = "zap";
+ };
+
+ taps = [
+ "oven-sh/bun"
+ "pulumi/tap"
+ "go-task/tap"
+ "tursodatabase/tap"
+ "simenandre/tools"
+ "anomalyco/tap"
+ "siderolabs/tap"
+ ];
+
+ brews = [
+ # Editors and terminal multiplexers
+ "neovim"
+ "tmux"
+
+ # Shells and shell utilities
+ "zsh"
+ "bash"
+ "bash-completion"
+ "ghq"
+ "moreutils"
+
+ # GNU tools
+ "findutils"
+ "gnu-sed"
+ "grep"
+
+ # Networking tools
+ "openssh"
+ "nmap"
+ "wget"
+ "cloudflared"
+
+ # Compression libraries and tools
+ "lz4"
+ "zlib"
+ "zstd"
+ "libffi"
+ "p7zip"
+ "pigz"
+
+ # Programming languages and runtimes
+ "golangci-lint"
+ "rust"
+ "zig"
+ "typst"
+
+ # JavaScript runtimes and package managers
+ "bun"
+ "pnpm"
+ "volta"
+ "deno"
+
+ # Version control
+ "git"
+ "git-lfs"
+ "git-delta"
+ "lazygit"
+
+ # Search tools
+ "ripgrep"
+ "fzf"
+
+ # JSON/YAML processing tools
+ "jq"
+ "yq"
+
+ # Protobuf and gRPC
+ "protobuf"
+ "protoc-gen-go"
+ "protoc-gen-go-grpc"
+
+ # Cloud and IaC tools
+ "pulumi"
+ "pulumictl"
+ "azure-cli"
+ "kubectl"
+ "helm"
+ "cilium-cli"
+ "doctl"
+ "flyctl"
+ "talosctl"
+ "tektoncd-cli"
+ "runpodctl"
+ # Container tools
+ "docker"
+ "docker-buildx"
+ "docker-compose"
+ "docker-completion"
+ "colima"
+ "podman"
+ "podman-compose"
+
+ # Task runners and automation
+ "go-task"
+ "act"
+
+ # Databases and database tools
+ "turso"
+ "libpq"
+ "sqlc"
+
+ # Terminal tools
+ "pv"
+ "rename"
+ "tree"
+ "tldr"
+ "starship"
+ "gh"
+ "htop"
+
+ # HTTP and web servers
+ "caddy"
+ "goreleaser"
+
+ # Document and media tools
+ "pandoc"
+ "ffmpeg"
+ "tesseract"
+ "ghostscript"
+ "graphviz"
+
+ # Python tools
+ "pipx"
+
+ # Specific toolsets
+ "rclone"
+
+ # Mac App Store CLI
+ "mas"
+ ];
+
+ casks = [
+ "1password"
+ "1password-cli"
+ "arc"
+ "basictex"
+ "claude"
+ "discord"
+ "elgato-camera-hub"
+ "figma"
+ "font-jetbrains-mono"
+ "font-symbols-only-nerd-font"
+ "gcloud-cli"
+ "ghostty"
+ "google-chrome"
+ "google-chrome@canary"
+ "google-drive"
+ "libreoffice"
+ "loom"
+ "microsoft-teams"
+ "obsidian"
+ "postico"
+ "proton-mail"
+ "protonvpn"
+ "raycast"
+ "signal"
+ "slack"
+ "sonos"
+ "spotify"
+ "visual-studio-code"
+ "zoom"
+ ];
+
+ masApps = {
+ "Tailscale" = 1475387142;
+ "Jojo" = 1659864300;
+ };
+ };
+}
diff --git a/modules/darwin/macos.nix b/modules/darwin/macos.nix
new file mode 100644
index 0000000..0dd120a
--- /dev/null
+++ b/modules/darwin/macos.nix
@@ -0,0 +1,140 @@
+{ ... }:
+
+{
+ system.defaults = {
+ NSGlobalDomain = {
+ # Sidebar icon size: medium
+ NSTableViewDefaultSizeMode = 2;
+ # Always show scrollbars
+ AppleShowScrollBars = "Always";
+ # Expand save panel by default
+ NSNavPanelExpandedStateForSaveMode = true;
+ NSNavPanelExpandedStateForSaveMode2 = true;
+ # Expand print panel by default
+ PMPrintingExpandedStateForPrint = true;
+ PMPrintingExpandedStateForPrint2 = true;
+ # Save to disk (not to iCloud) by default
+ NSDocumentSaveNewDocumentsToCloud = false;
+ # Disable auto-correct
+ NSAutomaticSpellingCorrectionEnabled = false;
+ # Font smoothing
+ AppleFontSmoothing = 1;
+ };
+
+ dock = {
+ # Minimize windows into their application's icon
+ minimize-to-application = true;
+ # Show indicator lights for open applications in the Dock
+ show-process-indicators = true;
+ # Wipe all default app icons from the Dock
+ persistent-apps = [ ];
+ # Speed up Mission Control animations
+ expose-animation-duration = 0.1;
+ # Remove the auto-hiding Dock delay
+ autohide-delay = 0.0;
+ # Remove the animation when hiding/showing the Dock
+ autohide-time-modifier = 0.0;
+ # Automatically hide and show the Dock
+ autohide = true;
+ # Make Dock icons of hidden applications translucent
+ showhidden = true;
+ # Don't show recent applications in Dock
+ show-recents = false;
+ };
+
+ finder = {
+ # Show status bar
+ ShowStatusBar = true;
+ # Show path bar
+ ShowPathbar = true;
+ # Search the current folder by default
+ FXDefaultSearchScope = "SCcf";
+ # Disable warning when changing a file extension
+ FXEnableExtensionChangeWarning = false;
+ };
+
+ screencapture = {
+ # Save screenshots to ~/Screenshots
+ location = "~/Screenshots";
+ # Save screenshots as JPG
+ type = "jpg";
+ };
+
+ trackpad = {
+ # Enable tap to click for this user and the login screen
+ Clicking = true;
+ };
+
+
+ ActivityMonitor = {
+ # Show the main window when launching Activity Monitor
+ OpenMainWindow = true;
+ # Visualize CPU usage in the Activity Monitor Dock icon
+ IconType = 5;
+ # Show all processes in Activity Monitor
+ ShowCategory = 100;
+ # Sort Activity Monitor results by CPU usage
+ SortColumn = "CPUUsage";
+ SortDirection = 0;
+ };
+
+ LaunchServices = {
+ # Disable the "Are you sure you want to open this application?" dialog
+ LSQuarantine = false;
+ };
+
+ CustomUserPreferences = {
+ "com.apple.print.PrintingPrefs" = {
+ # Automatically quit printer app once the print jobs complete
+ "Quit When Finished" = true;
+ };
+ "com.apple.desktopservices" = {
+ # Avoid creating .DS_Store files on network or USB volumes
+ DSDontWriteNetworkStores = true;
+ DSDontWriteUSBStores = true;
+ };
+ "com.apple.Safari" = {
+ # Privacy: don't send search queries to Apple
+ UniversalSearchEnabled = false;
+ SuppressSearchSuggestions = true;
+ };
+ "com.apple.TimeMachine" = {
+ # Prevent Time Machine from prompting to use new hard drives
+ DoNotOfferNewDisksForBackup = true;
+ };
+ "com.apple.TextEdit" = {
+ PlainTextEncoding = 4;
+ PlainTextEncodingForWrite = 4;
+ };
+ "com.apple.SoftwareUpdate" = {
+ AutomaticCheckEnabled = true;
+ ScheduleFrequency = 1;
+ AutomaticDownload = 1;
+ CriticalUpdateInstall = 1;
+ };
+ "com.apple.commerce" = {
+ AutoUpdate = true;
+ AutoUpdateRestartRequired = true;
+ };
+ "com.apple.helpviewer" = {
+ DevMode = true;
+ };
+ "com.apple.BluetoothAudioAgent" = {
+ "Apple Bitpool Min (editable)" = 40;
+ };
+ "com.google.Chrome" = {
+ # Disable the all too sensitive backswipe on trackpads and Magic Mouse
+ AppleEnableSwipeNavigateWithScrolls = false;
+ AppleEnableMouseSwipeNavigateWithScrolls = false;
+ # Expand the print dialog by default
+ PMPrintingExpandedStateForPrint2 = true;
+ };
+ "com.apple.messageshelper.MessageController" = {
+ SOInputLineSettings = {
+ automaticQuoteSubstitutionEnabled = false;
+ continuousSpellCheckingEnabled = false;
+ };
+ };
+ };
+ };
+}
diff --git a/modules/home/claude.nix b/modules/home/claude.nix
new file mode 100644
index 0000000..5895f03
--- /dev/null
+++ b/modules/home/claude.nix
@@ -0,0 +1,87 @@
+{ ... }:
+
+{
+ home.file.".claude/CLAUDE.md".text = ''
+ # Code Style
+
+ - ONLY use comments to explain weird code or non-obvious behaviour, never to explain simple code.
+
+ # Git & PRs
+
+ - ALWAYS write short and concise git commit messages and pull request messages.
+ - NEVER include a Co-Authored-By: line in commits.
+ - NEVER include "Generated with [Claude Code]" in PRs.
+
+ # Docker & Runtimes
+
+ - ALWAYS use the latest LTS version for base Docker images, Node.js engine requirements and similar.
+ - ALWAYS strive to use base Docker images with common ancestors in multi-stage builds: a stage based on `debian:trixie-slim` strongly suggests using `node:24-trixie-slim`.
+
+ # Nix
+
+ - If a command is not available and there is a `flake.nix` in the project, use `nix develop` or `nix run` to run commands from the flake's dev shell instead of installing tools manually.
+ '';
+
+ home.file.".claude/skills/push/SKILL.md".text = ''
+ ---
+ name: push
+ description: Create a commit and push changes. Optionally creates a branch and opens a PR with an intelligently chosen reviewer.
+ disable-model-invocation: true
+ ---
+
+ # /push — Commit & Push Workflow
+
+ You are executing the /push skill. Follow these steps precisely.
+
+ ## Step 1: Analyze Changes
+
+ Run `git status` and `git diff` (staged + unstaged) to understand all current changes.
+ If there are no changes to commit, inform the user and stop.
+
+ ## Step 2: Stage Files
+
+ Stage the relevant files. Prefer `git add <specific files>` over `git add -A`.
+ Never stage files that look like secrets (.env, credentials, tokens).
+
+ ## Step 3: Ask — Branch or Main?
+
+ Use the **AskUserQuestion** tool to ask the user how to push. Example:
+
+ - question: "How should this be pushed?"
+ - header: "Push target"
+ - options:
+ - label: "Current branch", description: "Push directly to the current branch"
+ - label: "New branch + PR", description: "Create a new branch and open a pull request"
+
+ ### If "Current branch":
+
+ - Run `git push` (with `-u` if needed).
+ - Done.
+
+ ### If "New branch + PR":
+
+ 1. Create a descriptive branch name based on the changes (e.g., `feat/add-logging`, `fix/null-check-user`).
+ 2. Check out the new branch and push with `-u`.
+ 3. **Choose a reviewer:**
+ - Run `git log --format='%an' -50 | sort | uniq -c | sort -rn | head -10` to find active contributors.
+ - Cross-reference with files changed: run `git log --format='%an' -- <changed-files> | sort | uniq -c | sort -rn | head -5` to find who has context on the modified code.
+ - Pick the most relevant reviewer (not the current user — check with `git config user.name`).
+ - Build a list of up to 4 candidate reviewers and use **AskUserQuestion** to let the user pick. Example:
+ - question: "Who should review this PR?"
+ - header: "Reviewer"
+ - options: one per candidate, with description showing their commit count / relevance to changed files.
+ 4. Create the PR using `gh pr create`:
+ - Short title (under 70 chars).
+ - Body with a `## Summary` section (2-3 bullet points) and a `## Test plan` section.
+ - Add the confirmed reviewer with `--reviewer`.
+ 5. Return the PR URL to the user.
+
+ ## Step 4: Create Commit
+
+ - Analyze the staged diff.
+ - Write a concise commit message using **conventional commit format** (e.g., `feat:`, `fix:`, `refactor:`, `chore:`, `docs:`, `test:`, `ci:`). Include a scope when it adds clarity (e.g., `feat(auth):`, `fix(api):`).
+ - Output ONLY the raw commit message — no markdown, no code blocks, no backticks, no explanations.
+ - **Do NOT add a Co-Authored-By line. Never include any co-author trailer.**
+ - Create the commit using a HEREDOC for the message.
+ '';
+}
diff --git a/modules/home/default.nix b/modules/home/default.nix
new file mode 100644
index 0000000..70b1174
--- /dev/null
+++ b/modules/home/default.nix
@@ -0,0 +1,52 @@
+{ pkgs, lib, config, dina, ... }:
+
+{
+ imports = [
+ ./claude.nix
+ ./git.nix
+ ./ghostty.nix
+ ./starship.nix
+ ./tmux.nix
+ ./lazygit.nix
+ ./twm.nix
+ ./zsh.nix
+ ];
+
+ home = {
+ homeDirectory = lib.mkForce (
+ if pkgs.stdenv.isDarwin
+ then "/Users/${config.home.username}"
+ else "/home/${config.home.username}"
+ );
+ # Do not change this after the first installation
+ stateVersion = "24.11";
+ };
+
+ home.packages = with pkgs; [
+ claude-code
+ dina.packages.${pkgs.stdenv.hostPlatform.system}.default
+ delta
+ fzf
+ gh
+ ghq
+ git-get
+ htop
+ jq
+ kubectl
+ lazygit
+ nodejs_22
+ ripgrep
+ starship
+ talosctl
+ tmux
+ tree
+ twm
+ yq
+ opencode
+ ];
+
+ programs.go.enable = true;
+
+ # Let home-manager manage itself
+ programs.home-manager.enable = true;
+}
diff --git a/modules/home/ghostty.nix b/modules/home/ghostty.nix
new file mode 100644
index 0000000..1d42944
--- /dev/null
+++ b/modules/home/ghostty.nix
@@ -0,0 +1,14 @@
+{ ... }:
+
+{
+ home.file.".config/ghostty/config".text = ''
+ theme = tokyonight-storm
+
+ window-padding-x = 12
+ window-padding-y = 12
+ window-decoration = true
+ window-theme = "dark"
+ window-inherit-working-directory = true
+ window-inherit-font-size = true
+ '';
+}
diff --git a/modules/home/git.nix b/modules/home/git.nix
new file mode 100644
index 0000000..1915304
--- /dev/null
+++ b/modules/home/git.nix
@@ -0,0 +1,55 @@
+{ pkgs, ... }:
+
+{
+ programs.git = {
+ enable = true;
+
+ signing = if pkgs.stdenv.isDarwin then {
+ key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMCyUn5BfmAnd/6xi0X1mYF/+jPOmCS0bpNUePF55gCY";
+ signByDefault = true;
+ } else {
+ signByDefault = false;
+ };
+
+ settings = {
+ user = {
+ name = "Simen A. W. Olsen";
+ email = "hello@simenandre.no";
+ };
+ apply.whitespace = "fix";
+ core = {
+ trustctime = false;
+ editor = "nvim";
+ excludesfile = "~/.gitignore_global";
+ };
+ color.ui = "auto";
+ "color \"branch\"" = {
+ current = "yellow reverse";
+ local = "yellow";
+ remote = "green";
+ };
+ "color \"diff\"" = {
+ meta = "yellow bold";
+ frag = "magenta bold";
+ old = "red";
+ new = "green";
+ };
+ "color \"status\"" = {
+ added = "yellow";
+ changed = "green";
+ untracked = "cyan";
+ };
+ diff.renames = "copies";
+ help.autocorrect = 1;
+ merge.log = true;
+ push.autoSetupRemote = true;
+ github.user = "simenandre";
+ pull.rebase = true;
+ } // (if pkgs.stdenv.isDarwin then {
+ gpg.format = "ssh";
+ "gpg \"ssh\"".program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign";
+ "url \"ssh://git@github.com/\"".insteadOf = "https://github.com/";
+ commit.gpgsign = true;
+ } else {});
+ };
+}
diff --git a/modules/home/lazygit.nix b/modules/home/lazygit.nix
new file mode 100644
index 0000000..da8996a
--- /dev/null
+++ b/modules/home/lazygit.nix
@@ -0,0 +1,20 @@
+{ ... }:
+
+{
+ home.file.".config/lazygit/config.yml".text = ''
+ git:
+ pagers:
+ - colorArg: always
+ pager: delta --dark --paging=never
+ customCommands:
+ - key: "C"
+ context: "files"
+ description: "Generate commit message with Claude Code"
+ command: >
+ git diff --staged | $HOME/.local/bin/claude -p 'Generate a concise git commit message for these staged changes.
+ Output ONLY the raw commit message with no markdown, no code blocks, no backticks, no explanations.
+ Use conventional commit format.' --model haiku --output-format text > /tmp/commit_msg &&
+ GIT_EDITOR=nvim git commit -e -F /tmp/commit_msg
+ output: terminal
+ '';
+}
diff --git a/modules/home/starship.nix b/modules/home/starship.nix
new file mode 100644
index 0000000..4f3cb61
--- /dev/null
+++ b/modules/home/starship.nix
@@ -0,0 +1,7 @@
+{ ... }:
+
+{
+ programs.starship = {
+ enable = true;
+ };
+}
diff --git a/modules/home/tmux.nix b/modules/home/tmux.nix
new file mode 100644
index 0000000..f084f35
--- /dev/null
+++ b/modules/home/tmux.nix
@@ -0,0 +1,33 @@
+{ ... }:
+
+{
+ home.file.".tmux.conf".text = ''
+ unbind r
+ bind r source-file ~/.tmux.conf \; display-message "Config reloaded"
+
+ set -g default-terminal "tmux-256color"
+ set -ag terminal-overrides ",xterm-256color:RGB"
+
+ set -g default-command "$SHELL -l"
+
+ set -g prefix C-s
+
+ set -g mouse on
+
+ set -g base-index 1
+ set-window-option -g mode-keys vi
+
+ bind g new-window -c "#{pane_current_path}" -n "lazygit" lazygit
+
+ bind f run-shell "tmux neww twm"
+ bind F run-shell "tmux neww twm -l"
+ bind s run-shell "tmux neww twm -e" # i rebind the original `s` to `S` so I can still use it
+ bind e run-shell "tmux switch -t $TWM_DEFAULT" # i set TWM_DEFAULT in my shellrc, just a session that is always available as a scratch area
+
+ set -g @plugin 'tmux-plugins/tpm'
+ set -g @plugin 'christoomey/vim-tmux-navigator'
+ set -g @plugin 'tmux-plugins/tmux-sensible'
+
+ run '~/.tmux/plugins/tpm/tpm'
+ '';
+}
diff --git a/modules/home/twm.nix b/modules/home/twm.nix
new file mode 100644
index 0000000..f5609d7
--- /dev/null
+++ b/modules/home/twm.nix
@@ -0,0 +1,38 @@
+{ config, ... }:
+
+{
+ home.file.".config/twm/twm.yaml".text = ''
+ search_paths:
+ - ${config.home.homeDirectory}/ghq
+ - ${config.home.homeDirectory}/repositories
+ - ~/.config/nvim/
+
+ exclude_path_components:
+ - .git
+ - .direnv
+ - node_modules
+ - venv
+ - target
+
+ workspace_definitions:
+ - name: catchall
+ default_layout: catchall-dev
+
+ - name: go
+ has_all_files:
+ - go.mod
+ - go.sum
+
+ - name: docker-compose
+ has_any_file:
+ - docker-compose.yml
+ - docker-compose.yaml
+ has_all_files:
+ - Dockerfile
+ - .git
+
+ - name: pulumi
+ has_all_files:
+ - Pulumi.yaml
+ '';
+}
diff --git a/modules/home/zsh.nix b/modules/home/zsh.nix
new file mode 100644
index 0000000..678e70d
--- /dev/null
+++ b/modules/home/zsh.nix
@@ -0,0 +1,186 @@
+{ pkgs, ... }:
+
+let
+ zsh-autocomplete = pkgs.fetchFromGitHub {
+ owner = "marlonrichert";
+ repo = "zsh-autocomplete";
+ rev = "main";
+ hash = "sha256-KYey9sidz8VzgfM9YVaCsVXpKKdCjSHk3tuadQz9Re0=";
+ };
+ zsh-autosuggestions = pkgs.fetchFromGitHub {
+ owner = "zsh-users";
+ repo = "zsh-autosuggestions";
+ rev = "master";
+ hash = "sha256-KmkXgK1J6iAyb1FtF/gOa0adUnh1pgFsgQOUnNngBaE=";
+ };
+ zsh-vi-mode = pkgs.fetchFromGitHub {
+ owner = "jeffreytse";
+ repo = "zsh-vi-mode";
+ rev = "master";
+ hash = "sha256-WdZHCVxVVOs4HyG5f56vAA17UWYOvb9Yf6v7M1RIdU4=";
+ };
+in
+{
+ home.file = {
+ ".zsh_repos/marlonrichert/zsh-autocomplete".source = zsh-autocomplete;
+ ".zsh_repos/zsh-users/zsh-autosuggestions".source = zsh-autosuggestions;
+ ".zsh_repos/jeffreytse/zsh-vi-mode".source = zsh-vi-mode;
+ ".zshenv".text = ''
+ ##!/bin/zsh
+ #
+ # .zshenv - Zsh environment file, loaded always.
+ #
+
+ # Set ZDOTDIR if you want to re-home Zsh.
+ export XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
+ export XDG_DATA_HOME=''${XDG_DATA_HOME:-$HOME/.local/share}
+ export XDG_CACHE_HOME=''${XDG_CACHE_HOME:-$HOME/.cache}
+ export ZDOTDIR=''${ZDOTDIR:-$XDG_CONFIG_HOME/zsh}
+
+ # Ensure path arrays do not contain duplicates.
+ typeset -gU path fpath
+
+ # Set the list of directories that zsh searches for commands.
+ path=(
+ $HOME/{,s}bin(N)
+ $HOME/.local/{,s}bin(N)
+ /opt/{homebrew,local}/{,s}bin(N)
+ /usr/local/{,s}bin(N)
+ $path
+ )
+ '' + (
+ if pkgs.stdenv.isDarwin then ''
+ eval "$(/opt/homebrew/bin/brew shellenv)"
+ '' else ""
+ ) + ''
+ [ -f "$HOME/.cargo/env" ] && . "$HOME/.cargo/env"
+ '';
+
+ ".hushlogin".text = "";
+
+ ".config/zsh/.zshrc".text = ''
+ #!/bin/bash
+ # ^----- get shellcheck hints based on bash
+ # https://github.com/koalaman/shellcheck/issues/809
+ # shellcheck disable=SC1090 # sourced filenames with variables
+
+ # Remove stale compiled zsh files that shadow nix-managed configs
+ rm -f ~/.config/zsh/.zshrc.zwc ~/.config/zsh/{aliases,path,extras}.zwc
+
+ eval "$(starship init zsh)"
+
+ source ~/.zsh_repos/marlonrichert/zsh-autocomplete/zsh-autocomplete.plugin.zsh
+ source ~/.zsh_repos/zsh-users/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh
+ # zsh-vi-mode clobbers all keybindings on init, so anything
+ # that needs to survive (fzf, autosuggest) goes in zvm_after_init.
+ function zvm_after_init() {
+ eval "$(fzf --zsh)"
+ bindkey '^I^I' autosuggest-accept
+ }
+
+ source ~/.zsh_repos/jeffreytse/zsh-vi-mode/zsh-vi-mode.plugin.zsh
+
+ for file in ~/.config/zsh/{aliases,path,extras}; do
+ if [[ -r "$file" ]] && [[ -f "$file" ]]; then
+ # shellcheck source=/dev/null
+ source "$file"
+ fi
+ done
+
+ command -v twm &>/dev/null && eval "$(twm --print-zsh-completion)"
+ '';
+
+ ".config/zsh/aliases".text = ''
+ # Git aliases
+ alias git-unpushed='git log --branches --not --remotes --no-walk --decorate --oneline'
+ alias gb='git checkout -b'
+ alias gfr='git fetch upstream && git rebase upstream/main'
+ alias git-undo='git reset --soft HEAD~1'
+ alias git-clean-branches="git for-each-ref --format '%(refname:short)' refs/heads | grep -v 'master\|main' | xargs git branch -D"
+ alias :q='exit'
+ alias ls='ls --color'
+ '' + (if pkgs.stdenv.isDarwin then ''
+ alias tailscale='/Applications/Tailscale.app/Contents/MacOS/Tailscale'
+ '' else "") + ''
+
+ # Generic / Random / Utility
+ alias rg="rg --hidden --glob '!.git' --glob '!node_modules'"
+ alias r='cd $REPOS/github.com/'
+ alias c='clear'
+ '' + (if pkgs.stdenv.isDarwin then ''
+ alias afk='pmset displaysleepnow'
+ '' else "") + ''
+ alias dl="cd ~/Downloads"
+ alias l='ls -l'
+ alias todo='nt'
+ alias tasks="open 'obsidian://open?file=-%20Oppgaver'"
+ alias todos="mt ~/ghq/github.com/simenandre/notes/"
+ alias today="open 'obsidian://advanced-uri?daily=true'"
+ alias tre="tree -I '.git|node_modules'"
+ alias timeliste="open 'https://tripletex.no/execute/updateHourlist?contextId=11208568'"
+ alias t="open 'https://tripletex.no/execute/updateHourlist?contextId=11208568'"
+ alias fork-cleaner="fork-cleaner --token \$(cat ~/.secrets/github-token 2>/dev/null)"
+ alias vi="nvim"
+ alias vim="nvim"
+ alias v="nvim"
+
+ alias dump-issues="getBjerkIssues > ~/Downloads/issues-$(date -I).json"
+ '';
+
+ ".config/zsh/path".text = ''
+ #!/bin/bash
+
+ # Add directories to the PATH and prevent to add the same directory multiple times upon shell reload.
+ add_to_path() {
+ if [[ -d "$1" ]] && [[ ":$PATH:" != *":$1:"* ]]; then
+ export PATH="$1:$PATH"
+ fi
+ }
+
+ add_to_path "$HOME/.volta/bin"
+ add_to_path "/Library/Frameworks/Firebird.framework/Resources/bin"
+ add_to_path "/opt/homebrew/opt/libpq/bin"
+ '';
+
+ ".config/zsh/extras".text = ''
+ if [[ "$(uname)" == "Darwin" ]]; then
+ export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"
+ fi
+
+ NOTES_DIR=$HOME/ghq/github.com/simenandre/notes/
+ function note() {
+ if [ $1 ]; then
+ FILE=$NOTES_DIR/$(date +%Y-%m-%d).md
+
+ if [ $2 ]; then
+ FILE=$2
+ fi
+
+ echo $1 >> $FILE
+ fi
+ }
+
+ function nt() {
+ if [ $1 ]; then
+ CONTENT="- [ ] $1"
+ note $CONTENT $2
+ fi
+ }
+
+ function getBjerkIssues() {
+ gh issue list --repo bjerkio/internal \
+ --search "-label:backlog -label:kind/system" \
+ --json title,body,url,createdAt,assignees,number,labels,comments \
+ --jq 'map({url, createdAt, title, labels: [.labels[].name], assignees: [.assignees[].login], body, number: "#\(.number)", comments: .comments | map({ body: .body, createdAt: .createdAt })})'
+ }
+
+ function g() {
+ export GOOGLE_CLOUD_PROJECT="sokkel-sandbox"
+ export GOOGLE_CLOUD_LOCATION="europe-north1"
+ export GOOGLE_GENAI_USE_VERTEXAI=true
+
+ gemini
+ }
+ '';
+ };
+}
diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix
new file mode 100644
index 0000000..0d51dc4
--- /dev/null
+++ b/modules/nixos/default.nix
@@ -0,0 +1,59 @@
+{ pkgs, lib, ... }:
+
+{
+ imports = [ ];
+
+ # Enable experimental Nix features
+ nix.settings.experimental-features = "nix-command flakes";
+
+ # Allow unfree packages
+ nixpkgs.config.allowUnfree = true;
+
+ # Bootloader
+ boot.loader.systemd-boot.enable = true;
+ boot.loader.efi.canTouchEfiVariables = true;
+
+ # Networking
+ networking.hostName = "nixbox";
+ networking.networkmanager.enable = true;
+
+ # Timezone / locale
+ time.timeZone = "Europe/Oslo";
+ i18n.defaultLocale = "en_US.UTF-8";
+
+ # User account
+ users.users.simenandre = {
+ isNormalUser = true;
+ extraGroups = [ "wheel" "networkmanager" "docker" ];
+ shell = pkgs.zsh;
+ openssh.authorizedKeys.keys = [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDDXfT23Ne7Rl/rdiqrLf4vWfQqgUCo26+NcL41PVSag"
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJwHftCdnV/pscQ4qCK/biIBsKk/G0A3hLVI1o62CJF4 so@bjerk.io"
+ ];
+ };
+ programs.zsh.enable = true;
+
+
+ # Core system packages
+ environment.systemPackages = with pkgs; [
+ curl
+ gcc
+ git
+ wget
+ neovim
+ ghostty.terminfo
+ ];
+
+ # 1Password (for SSH signing)
+ programs._1password.enable = true;
+ programs._1password-gui.enable = true;
+
+ # Docker
+ virtualisation.docker.enable = true;
+
+ # SSH
+ services.openssh.enable = true;
+
+ # System state version – do not change after first installation
+ system.stateVersion = "24.11";
+}
diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh
new file mode 100755
index 0000000..82afe4d
--- /dev/null
+++ b/scripts/cleanup.sh
@@ -0,0 +1,218 @@
+#!/bin/bash
+
+# Mac Cleanup Script
+# This script safely cleans up various caches, logs, and temporary files
+# Run with: chmod +x cleanup.sh && ./cleanup.sh
+
+set -e # Exit on any error
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Function to print colored output
+print_status() {
+ echo -e "${BLUE}[INFO]${NC} $1"
+}
+
+print_success() {
+ echo -e "${GREEN}[SUCCESS]${NC} $1"
+}
+
+print_warning() {
+ echo -e "${YELLOW}[WARNING]${NC} $1"
+}
+
+print_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+# Function to get directory size
+get_size() {
+ if [ -d "$1" ]; then
+ du -sh "$1" 2>/dev/null | cut -f1
+ else
+ echo "0B"
+ fi
+}
+
+# Function to safely remove directory contents
+safe_cleanup() {
+ local dir="$1"
+ local desc="$2"
+
+ if [ -d "$dir" ]; then
+ local size=$(get_size "$dir")
+ print_status "Cleaning $desc ($size)..."
+
+ # Only remove contents, not the directory itself
+ find "$dir" -mindepth 1 -delete 2>/dev/null || true
+ print_success "Cleaned $desc"
+ else
+ print_warning "$desc directory not found: $dir"
+ fi
+}
+
+# Function to clean up files matching pattern
+clean_pattern() {
+ local pattern="$1"
+ local desc="$2"
+ local count
+
+ count=$(find . -name "$pattern" -type f 2>/dev/null | wc -l | tr -d ' ')
+ if [ "$count" -gt 0 ]; then
+ print_status "Removing $count $desc files..."
+ find . -name "$pattern" -type f -delete 2>/dev/null || true
+ print_success "Removed $desc files"
+ fi
+}
+
+echo "🧹 Mac Cleanup Script"
+echo "===================="
+echo
+
+# Check if running as root (not recommended)
+if [ "$EUID" -eq 0 ]; then
+ print_error "Please don't run this script as root/sudo"
+ exit 1
+fi
+
+print_status "Starting cleanup process..."
+echo
+
+# 1. System Caches
+# print_status "Cleaning system caches..."
+# safe_cleanup "$HOME/Library/Caches" "user caches"
+# safe_cleanup "/tmp" "temporary files"
+
+# 2. Browser Caches (be careful with user data)
+# print_status "Cleaning browser caches..."
+# safe_cleanup "$HOME/Library/Caches/Google/Chrome" "Chrome cache"
+# safe_cleanup "$HOME/Library/Application Support/Google/Chrome/Default/Application Cache" "Chrome app cache"
+
+# 3. Development-related cleanup
+print_status "Cleaning development caches..."
+
+# Node.js and npm
+safe_cleanup "$HOME/.npm/_cacache" "npm cache"
+if command -v npm &> /dev/null; then
+ print_status "Cleaning npm cache..."
+ npm cache clean --force 2>/dev/null || true
+fi
+
+# Yarn cache
+if command -v yarn &> /dev/null; then
+ print_status "Cleaning Yarn cache..."
+ yarn cache clean 2>/dev/null || true
+fi
+
+# pnpm cache (you have this installed)
+if command -v pnpm &> /dev/null; then
+ print_status "Cleaning pnpm cache..."
+ pnpm store prune 2>/dev/null || true
+fi
+
+# Bun cache (you have this installed)
+if command -v bun &> /dev/null; then
+ print_status "Cleaning Bun cache..."
+ rm -rf "$HOME/.bun/install/cache" 2>/dev/null || true
+fi
+
+# Go module cache
+safe_cleanup "$HOME/go/pkg/mod/cache" "Go module cache"
+
+# Rust cargo cache
+safe_cleanup "$HOME/.cargo/registry/cache" "Rust cargo cache"
+
+# 4. Docker cleanup (you have Docker installed)
+if command -v docker &> /dev/null && docker info &> /dev/null; then
+ print_status "Cleaning Docker..."
+ docker system prune -f 2>/dev/null || true
+ docker image prune -f 2>/dev/null || true
+ print_success "Docker cleanup completed"
+fi
+
+# 5. Podman cleanup (you have Podman installed)
+if command -v podman &> /dev/null; then
+ print_status "Cleaning Podman..."
+ podman system prune -f 2>/dev/null || true
+ podman image prune -f 2>/dev/null || true
+ print_success "Podman cleanup completed"
+fi
+
+# 6. Homebrew cleanup (you're clearly using Homebrew extensively)
+if command -v brew &> /dev/null; then
+ print_status "Cleaning Homebrew..."
+ brew cleanup --prune=all 2>/dev/null || true
+ brew autoremove 2>/dev/null || true
+ print_success "Homebrew cleanup completed"
+fi
+
+# 7. System logs (older than 7 days)
+print_status "Cleaning old system logs..."
+sudo log collect --last 7d --output /dev/null 2>/dev/null || true
+
+# 8. Xcode derived data (if Xcode is installed)
+if [ -d "$HOME/Library/Developer/Xcode/DerivedData" ]; then
+ safe_cleanup "$HOME/Library/Developer/Xcode/DerivedData" "Xcode derived data"
+fi
+
+# 9. Clean up common temporary file patterns in current directory and subdirectories
+# print_status "Cleaning temporary files in current directory..."
+# clean_pattern "*.tmp" "temporary"
+# clean_pattern "*.temp" "temporary"
+# clean_pattern ".DS_Store" "DS_Store"
+# clean_pattern "Thumbs.db" "Windows thumbnail"
+# clean_pattern "*.log" "log"
+# clean_pattern "*.swp" "Vim swap"
+# clean_pattern "*.swo" "Vim swap"
+# clean_pattern "*~" "backup"
+
+# 10. Node.js node_modules cleanup (be careful - only clean if package.json exists)
+if [ -f "package.json" ]; then
+ print_status "Found package.json - looking for node_modules to clean..."
+ find . -name "node_modules" -type d -prune | while read -r dir; do
+ size=$(get_size "$dir")
+ print_status "Removing node_modules at $dir ($size)..."
+ rm -rf "$dir"
+ done
+ print_success "Node modules cleanup completed"
+fi
+
+# 11. Empty trash
+print_status "Emptying trash..."
+osascript -e 'tell application "Finder" to empty trash' 2>/dev/null || true
+print_success "Trash emptied"
+
+# 12. Clear font caches (since you work with fonts based on your Brewfile)
+# print_status "Clearing font caches..."
+# sudo atsutil databases -remove 2>/dev/null || true
+
+# 13. Repair disk permissions
+# print_status "Repairing disk permissions..."
+# sudo diskutil repairPermissions / 2>/dev/null || true
+
+echo
+print_success "🎉 Cleanup completed!"
+
+# Show some system stats
+echo
+print_status "System Information:"
+echo "📊 Disk Usage:"
+df -h / | tail -1 | awk '{print " Available: " $4 " (" $5 " used)"}'
+
+echo "💾 Memory Usage:"
+vm_stat | head -5
+
+echo
+print_status "💡 Additional recommendations:"
+echo " • Restart your Mac to clear memory caches"
+echo " • Run 'brew doctor' to check Homebrew health"
+echo " • Consider using 'ncdu' to find large directories"
+echo " • Check Activity Monitor for memory-hungry apps"
+
+echo
+print_success "All done! 🚀"