diff --git a/.aliases b/.aliases deleted file mode 100644 index 3387fa0609e..00000000000 --- a/.aliases +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env bash - -# Easier navigation: .., ..., ...., ....., ~ and - -alias ..="cd .." -alias ...="cd ../.." -alias ....="cd ../../.." -alias .....="cd ../../../.." -alias ~="cd ~" # `cd` is probably faster to type though -alias -- -="cd -" - -# Shortcuts -alias d="cd ~/Documents/Dropbox" -alias dl="cd ~/Downloads" -alias dt="cd ~/Desktop" -alias p="cd ~/projects" -alias g="git" - -# Detect which `ls` flavor is in use -if ls --color > /dev/null 2>&1; then # GNU `ls` - colorflag="--color" - export LS_COLORS='no=00:fi=00:di=01;31:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=01;35:*.mp3=01;35:*.wav=01;35:' -else # macOS `ls` - colorflag="-G" - export LSCOLORS='BxBxhxDxfxhxhxhxhxcxcx' -fi - -# List all files colorized in long format -alias l="ls -lF ${colorflag}" - -# List all files colorized in long format, excluding . and .. -alias la="ls -lAF ${colorflag}" - -# List only directories -alias lsd="ls -lF ${colorflag} | grep --color=never '^d'" - -# Always use color output for `ls` -alias ls="command ls ${colorflag}" - -# Always enable colored `grep` output -# Note: `GREP_OPTIONS="--color=auto"` is deprecated, hence the alias usage. -alias grep='grep --color=auto' -alias fgrep='fgrep --color=auto' -alias egrep='egrep --color=auto' - -# Enable aliases to be sudo’ed -alias sudo='sudo ' - -# Get week number -alias week='date +%V' - -# Get macOS Software Updates, and update installed Ruby gems, Homebrew, npm, and their installed packages -alias update='sudo softwareupdate -i -a; brew update; brew upgrade; brew cleanup; npm install npm -g; npm update -g; sudo gem update --system; sudo gem update; sudo gem cleanup' - -# Google Chrome -alias chrome='/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome' -alias canary='/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary' - -# IP addresses -alias ip="dig +short myip.opendns.com @resolver1.opendns.com" -alias localip="ipconfig getifaddr en0" -alias ips="ifconfig -a | grep -o 'inet6\? \(addr:\)\?\s\?\(\(\([0-9]\+\.\)\{3\}[0-9]\+\)\|[a-fA-F0-9:]\+\)' | awk '{ sub(/inet6? (addr:)? ?/, \"\"); print }'" - -# Show active network interfaces -alias ifactive="ifconfig | pcregrep -M -o '^[^\t:]+:([^\n]|\n\t)*status: active'" - -# Flush Directory Service cache -alias flush="dscacheutil -flushcache && killall -HUP mDNSResponder" - -# Clean up LaunchServices to remove duplicates in the “Open With” menu -alias lscleanup="/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user && killall Finder" - -# Canonical hex dump; some systems have this symlinked -command -v hd > /dev/null || alias hd="hexdump -C" - -# macOS has no `md5sum`, so use `md5` as a fallback -command -v md5sum > /dev/null || alias md5sum="md5" - -# macOS has no `sha1sum`, so use `shasum` as a fallback -command -v sha1sum > /dev/null || alias sha1sum="shasum" - -# JavaScriptCore REPL -jscbin="/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc"; -[ -e "${jscbin}" ] && alias jsc="${jscbin}"; -unset jscbin; - -# Trim new lines and copy to clipboard -alias c="tr -d '\n' | pbcopy" - -# Recursively delete `.DS_Store` files -alias cleanup="find . -type f -name '*.DS_Store' -ls -delete" - -# Empty the Trash on all mounted volumes and the main HDD. -# Also, clear Apple’s System Logs to improve shell startup speed. -# Finally, clear download history from quarantine. https://mths.be/bum -alias emptytrash="sudo rm -rfv /Volumes/*/.Trashes; sudo rm -rfv ~/.Trash; sudo rm -rfv /private/var/log/asl/*.asl; sqlite3 ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV* 'delete from LSQuarantineEvent'" - -# Show/hide hidden files in Finder -alias show="defaults write com.apple.finder AppleShowAllFiles -bool true && killall Finder" -alias hide="defaults write com.apple.finder AppleShowAllFiles -bool false && killall Finder" - -# Hide/show all desktop icons (useful when presenting) -alias hidedesktop="defaults write com.apple.finder CreateDesktop -bool false && killall Finder" -alias showdesktop="defaults write com.apple.finder CreateDesktop -bool true && killall Finder" - -# URL-encode strings -alias urlencode='python -c "import sys, urllib as ul; print ul.quote_plus(sys.argv[1]);"' - -# Merge PDF files, preserving hyperlinks -# Usage: `mergepdf input{1,2,3}.pdf` -alias mergepdf='gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=_merged.pdf' - -# Disable Spotlight -alias spotoff="sudo mdutil -a -i off" -# Enable Spotlight -alias spoton="sudo mdutil -a -i on" - -# PlistBuddy alias, because sometimes `defaults` just doesn’t cut it -alias plistbuddy="/usr/libexec/PlistBuddy" - -# Airport CLI alias -alias airport='/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport' - -# Intuitive map function -# For example, to list all directories that contain a certain file: -# find . -name .gitattributes | map dirname -alias map="xargs -n1" - -# One of @janmoesen’s ProTip™s -for method in GET HEAD POST PUT DELETE TRACE OPTIONS; do - alias "${method}"="lwp-request -m '${method}'" -done - -# Stuff I never really use but cannot delete either because of http://xkcd.com/530/ -alias stfu="osascript -e 'set volume output muted true'" -alias pumpitup="osascript -e 'set volume output volume 100'" - -# Kill all the tabs in Chrome to free up memory -# [C] explained: http://www.commandlinefu.com/commands/view/402/exclude-grep-from-your-grepped-output-of-ps-alias-included-in-description -alias chromekill="ps ux | grep '[C]hrome Helper --type=renderer' | grep -v extension-process | tr -s ' ' | cut -d ' ' -f2 | xargs kill" - -# Lock the screen (when going AFK) -alias afk="/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend" - -# Reload the shell (i.e. invoke as a login shell) -alias reload="exec ${SHELL} -l" - -# Print each PATH entry on a separate line -alias path='echo -e ${PATH//:/\\n}' diff --git a/.bash_profile b/.bash_profile deleted file mode 100644 index f79b6145e40..00000000000 --- a/.bash_profile +++ /dev/null @@ -1,50 +0,0 @@ -# Add `~/bin` to the `$PATH` -export PATH="$HOME/bin:$PATH"; - -# Load the shell dotfiles, and then some: -# * ~/.path can be used to extend `$PATH`. -# * ~/.extra can be used for other settings you don’t want to commit. -for file in ~/.{path,bash_prompt,exports,aliases,functions,extra}; do - [ -r "$file" ] && [ -f "$file" ] && source "$file"; -done; -unset file; - -# Case-insensitive globbing (used in pathname expansion) -shopt -s nocaseglob; - -# Append to the Bash history file, rather than overwriting it -shopt -s histappend; - -# Autocorrect typos in path names when using `cd` -shopt -s cdspell; - -# Enable some Bash 4 features when possible: -# * `autocd`, e.g. `**/qux` will enter `./foo/bar/baz/qux` -# * Recursive globbing, e.g. `echo **/*.txt` -for option in autocd globstar; do - shopt -s "$option" 2> /dev/null; -done; - -# Add tab completion for many Bash commands -if which brew &> /dev/null && [ -r "$(brew --prefix)/etc/profile.d/bash_completion.sh" ]; then - # Ensure existing Homebrew v1 completions continue to work - export BASH_COMPLETION_COMPAT_DIR="$(brew --prefix)/etc/bash_completion.d"; - source "$(brew --prefix)/etc/profile.d/bash_completion.sh"; -elif [ -f /etc/bash_completion ]; then - source /etc/bash_completion; -fi; - -# Enable tab completion for `g` by marking it as an alias for `git` -if type _git &> /dev/null; then - complete -o default -o nospace -F _git g; -fi; - -# Add tab completion for SSH hostnames based on ~/.ssh/config, ignoring wildcards -[ -e "$HOME/.ssh/config" ] && complete -o "default" -o "nospace" -W "$(grep "^Host" ~/.ssh/config | grep -v "[?*]" | cut -d " " -f2- | tr ' ' '\n')" scp sftp ssh; - -# Add tab completion for `defaults read|write NSGlobalDomain` -# You could just use `-g` instead, but I like being explicit -complete -W "NSGlobalDomain" defaults; - -# Add `killall` tab completion for common apps -complete -o "nospace" -W "Contacts Calendar Dock Finder Mail Safari iTunes SystemUIServer Terminal Twitter" killall; diff --git a/.bash_prompt b/.bash_prompt deleted file mode 100644 index 74db549f994..00000000000 --- a/.bash_prompt +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env bash - -# Shell prompt based on the Solarized Dark theme. -# Screenshot: http://i.imgur.com/EkEtphC.png -# Heavily inspired by @necolas’s prompt: https://github.com/necolas/dotfiles -# iTerm → Profiles → Text → use 13pt Monaco with 1.1 vertical spacing. - -if [[ $COLORTERM = gnome-* && $TERM = xterm ]] && infocmp gnome-256color >/dev/null 2>&1; then - export TERM='gnome-256color'; -elif infocmp xterm-256color >/dev/null 2>&1; then - export TERM='xterm-256color'; -fi; - -prompt_git() { - local s=''; - local branchName=''; - - # Check if the current directory is in a Git repository. - git rev-parse --is-inside-work-tree &>/dev/null || return; - - # Check for what branch we’re on. - # Get the short symbolic ref. If HEAD isn’t a symbolic ref, get a - # tracking remote branch or tag. Otherwise, get the - # short SHA for the latest commit, or give up. - branchName="$(git symbolic-ref --quiet --short HEAD 2> /dev/null || \ - git describe --all --exact-match HEAD 2> /dev/null || \ - git rev-parse --short HEAD 2> /dev/null || \ - echo '(unknown)')"; - - # Early exit for Chromium & Blink repo, as the dirty check takes too long. - # Thanks, @paulirish! - # https://github.com/paulirish/dotfiles/blob/dd33151f/.bash_prompt#L110-L123 - repoUrl="$(git config --get remote.origin.url)"; - if grep -q 'chromium/src.git' <<< "${repoUrl}"; then - s+='*'; - else - # Check for uncommitted changes in the index. - if ! $(git diff --quiet --ignore-submodules --cached); then - s+='+'; - fi; - # Check for unstaged changes. - if ! $(git diff-files --quiet --ignore-submodules --); then - s+='!'; - fi; - # Check for untracked files. - if [ -n "$(git ls-files --others --exclude-standard)" ]; then - s+='?'; - fi; - # Check for stashed files. - if $(git rev-parse --verify refs/stash &>/dev/null); then - s+='$'; - fi; - fi; - - [ -n "${s}" ] && s=" [${s}]"; - - echo -e "${1}${branchName}${2}${s}"; -} - -if tput setaf 1 &> /dev/null; then - tput sgr0; # reset colors - bold=$(tput bold); - reset=$(tput sgr0); - # Solarized colors, taken from http://git.io/solarized-colors. - black=$(tput setaf 0); - blue=$(tput setaf 33); - cyan=$(tput setaf 37); - green=$(tput setaf 64); - orange=$(tput setaf 166); - purple=$(tput setaf 125); - red=$(tput setaf 124); - violet=$(tput setaf 61); - white=$(tput setaf 15); - yellow=$(tput setaf 136); -else - bold=''; - reset="\e[0m"; - black="\e[1;30m"; - blue="\e[1;34m"; - cyan="\e[1;36m"; - green="\e[1;32m"; - orange="\e[1;33m"; - purple="\e[1;35m"; - red="\e[1;31m"; - violet="\e[1;35m"; - white="\e[1;37m"; - yellow="\e[1;33m"; -fi; - -# Highlight the user name when logged in as root. -if [[ "${USER}" == "root" ]]; then - userStyle="${red}"; -else - userStyle="${orange}"; -fi; - -# Highlight the hostname when connected via SSH. -if [[ "${SSH_TTY}" ]]; then - hostStyle="${bold}${red}"; -else - hostStyle="${yellow}"; -fi; - -# Set the terminal title and prompt. -PS1="\[\033]0;\W\007\]"; # working directory base name -PS1+="\[${bold}\]\n"; # newline -PS1+="\[${userStyle}\]\u"; # username -PS1+="\[${white}\] at "; -PS1+="\[${hostStyle}\]\h"; # host -PS1+="\[${white}\] in "; -PS1+="\[${green}\]\w"; # working directory full path -PS1+="\$(prompt_git \"\[${white}\] on \[${violet}\]\" \"\[${blue}\]\")"; # Git repository details -PS1+="\n"; -PS1+="\[${white}\]\$ \[${reset}\]"; # `$` (and reset color) -export PS1; - -PS2="\[${yellow}\]→ \[${reset}\]"; -export PS2; diff --git a/.bashrc b/.bashrc deleted file mode 100644 index 12570026634..00000000000 --- a/.bashrc +++ /dev/null @@ -1 +0,0 @@ -[ -n "$PS1" ] && source ~/.bash_profile; diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000000..b8c4979c285 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,39 @@ +{ + "permissions": { + "allow": [ + "Bash(/bin/ls /Users/j/code/dotfiles/*.sh)", + "Bash(/bin/ls -d /Users/j/code/dotfiles/*/)", + "Bash(/bin/ls /Users/j/code/dotfiles/.gitmodules)", + "Bash(/bin/ls /Users/j/code/dotfiles/README*)", + "Bash(/bin/ls /Users/j/code/dotfiles/Makefile)", + "Bash(git checkout -b add-unit-tests)", + "Bash(git submodule add https://github.com/bats-core/bats-core.git test/libs/bats-core)", + "Bash(git submodule add https://github.com/bats-core/bats-support.git test/libs/bats-support)", + "Bash(git submodule add https://github.com/bats-core/bats-assert.git test/libs/bats-assert)", + "Bash(./test/libs/bats-core/bin/bats test/)", + "Bash(bash /Users/j/code/dotfiles/stow-packages.sh --help)", + "Bash(bash /Users/j/code/dotfiles/linux-apt-package-install.sh --help)", + "Bash(bash /Users/j/code/dotfiles/osx-package-install.sh --help)", + "Bash(bash /Users/j/code/dotfiles/bootstrap.sh --help)", + "Bash(git reset HEAD~1)", + "Bash(git push -u origin add-unit-tests)", + "Bash(/bin/ls test/libs/)", + "Bash(git add .gitmodules test/libs/bats-core test/libs/bats-support test/libs/bats-assert)", + "Bash(git commit:*)", + "Bash(git push)", + "Bash(git merge add-unit-tests)", + "Bash(git stash --include-untracked)", + "Bash(git -C oh-my-zsh/dot-oh-my-zsh remote -v)", + "Bash(git add .gitmodules)", + "Bash(git -C oh-my-zsh/dot-oh-my-zsh log --oneline -3)", + "Bash(git checkout -b web-set-editor)", + "Bash(pip3 install pyyaml jsonschema)", + "Bash(pip3 install --user pyyaml jsonschema)", + "Bash(python3 -c \"import yaml; import jsonschema; print\\(''OK''\\)\")", + "Bash(uv pip install pyyaml jsonschema)", + "Bash(pip3 install --break-system-packages pyyaml jsonschema)", + "Bash(python3 tools/validate_config.py)", + "Bash(python3 tools/verify_transition.py)" + ] + } +} diff --git a/.gdbinit b/.gdbinit deleted file mode 100644 index 9422460c765..00000000000 --- a/.gdbinit +++ /dev/null @@ -1 +0,0 @@ -set disassembly-flavor intel diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 6bdc702247e..00000000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Automatically normalize line endings for all text-based files -#* text=auto -# Disabled because of https://github.com/mathiasbynens/dotfiles/issues/149 :( diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000000..c876b3aff59 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Run Unit Tests + +on: + push: + branches: ["**"] + pull_request: + branches: [main] + +jobs: + test-linux: + name: Linux Unit Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install Python dependencies + run: pip install -r tools/requirements.txt + + - name: Run BATS tests + run: ./test/libs/bats-core/bin/bats test/ diff --git a/.gitignore b/.gitignore index 93965b64753..71f5177bdc9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,13 @@ Thumbs.db # Files that might appear on external disks .Spotlight-V100 .Trashes + +# other +git/.gitconfig +nvim/.config/nvim/lazy-lock.json +karabiner/dot-config/karabiner/automatic_backups/* +iterm2/dot-config/iterm2* +iterm2/dot-config/* + +iterm2/.config/iterm2/AppSupport +version.txt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..e176f5d7e59 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "oh-my-zsh/dot-oh-my-zsh"] + path = oh-my-zsh/dot-oh-my-zsh + url = https://github.com/ohmyzsh/ohmyzsh.git +[submodule "test/libs/bats-core"] + path = test/libs/bats-core + url = https://github.com/bats-core/bats-core.git +[submodule "test/libs/bats-support"] + path = test/libs/bats-support + url = https://github.com/bats-core/bats-support.git +[submodule "test/libs/bats-assert"] + path = test/libs/bats-assert + url = https://github.com/bats-core/bats-assert.git diff --git a/.gvimrc b/.gvimrc deleted file mode 100644 index d2044d001d2..00000000000 --- a/.gvimrc +++ /dev/null @@ -1,9 +0,0 @@ -" Use the Solarized Dark theme -set background=dark -colorscheme solarized -" Use 14pt Monaco -set guifont=Monaco:h14 -" Don’t blink cursor in normal mode -set guicursor=n:blinkon0 -" Better line-height -set linespace=8 diff --git a/.hgignore b/.hgignore deleted file mode 100644 index ac1973e78e2..00000000000 --- a/.hgignore +++ /dev/null @@ -1,17 +0,0 @@ -# Use shell-style glob syntax -syntax: glob - -# Compiled Python files -*.pyc - -# Folder view configuration files -.DS_Store -Desktop.ini - -# Thumbnail cache files -._* -Thumbs.db - -# Files that might appear on external disks -.Spotlight-V100 -.Trashes diff --git a/.osx b/.osx deleted file mode 100644 index df12aac66b3..00000000000 --- a/.osx +++ /dev/null @@ -1 +0,0 @@ -# 301 https://github.com/mathiasbynens/dotfiles/blob/main/.macos diff --git a/.stow-local-ignore b/.stow-local-ignore new file mode 100644 index 00000000000..db3f5b74903 --- /dev/null +++ b/.stow-local-ignore @@ -0,0 +1,32 @@ +# Comments and blank lines are allowed. + +RCS +.+,v + +CVS +\.\#.+ # CVS conflict files / emacs lock files +\.cvsignore + +\.svn +_darcs +\.hg + +\.git +\.gitignore +\.gitmodules + +\.DS_Store # OS X Finder metadata +\.AppleDouble +\.LSOverride +\.Spotlight-V100 +\.Trashes +\.DocumentRevisions-V100 +\.fseventsd +\.TemporaryItems + +.+~ # emacs backup files +\#.*\# # emacs autosave files + +^/README.* +^/LICENSE.* +^/COPYING \ No newline at end of file diff --git a/.tmux.conf b/.tmux.conf deleted file mode 100644 index e76b95e5b04..00000000000 --- a/.tmux.conf +++ /dev/null @@ -1,11 +0,0 @@ -# Use Ctrl+A as the prefix key -set -g prefix C-a -unbind C-b -bind C-a send-prefix - -# Use Vim shortcuts -setw -g mode-keys vi - -# Make `Ctrl+A R` reload the config file -unbind r -bind r source-file ~/.tmux.conf diff --git a/LICENSE-MIT.txt b/LICENSE-MIT.txt deleted file mode 100644 index a41e0a7ef97..00000000000 --- a/LICENSE-MIT.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright Mathias Bynens - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index d7bca276ad5..b86d204fee0 100644 --- a/README.md +++ b/README.md @@ -1,111 +1,20 @@ -# Mathias’s dotfiles +# James's dotfiles -![Screenshot of my shell prompt](https://i.imgur.com/EkEtphC.png) +(a work in progress, originally and still loosely based on [Mathias's dotfiles](https://github.com/mathiasbynens/dotfiles)) -## Installation +## New Mac Setup -**Warning:** If you want to give these dotfiles a try, you should first fork this repository, review the code, and remove things you don’t want or need. Don’t blindly use my settings unless you know what that entails. Use at your own risk! +1. `xcode-select --install` (make sure Xcode CLI tools are installed) +1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` (install Homebrew) +1. `brew install gh` github tools +1. `mkdir ~/dev` place for these and other projects +1. `gh auth login` go through auth process +1. `gh repo clone jamesbeldock/dotfiles` +1. `source dotfiles/bootstrap.sh` -### Using Git and the bootstrap script +## Linux Setup -You can clone the repository wherever you want. (I like to keep it in `~/Projects/dotfiles`, with `~/dotfiles` as a symlink.) The bootstrapper script will pull in the latest version and copy the files to your home folder. - -```bash -git clone https://github.com/mathiasbynens/dotfiles.git && cd dotfiles && source bootstrap.sh -``` - -To update, `cd` into your local `dotfiles` repository and then: - -```bash -source bootstrap.sh -``` - -Alternatively, to update while avoiding the confirmation prompt: - -```bash -set -- -f; source bootstrap.sh -``` - -### Git-free install - -To install these dotfiles without Git: - -```bash -cd; curl -#L https://github.com/mathiasbynens/dotfiles/tarball/main | tar -xzv --strip-components 1 --exclude={README.md,bootstrap.sh,.osx,LICENSE-MIT.txt} -``` - -To update later on, just run that command again. - -### Specify the `$PATH` - -If `~/.path` exists, it will be sourced along with the other files, before any feature testing (such as [detecting which version of `ls` is being used](https://github.com/mathiasbynens/dotfiles/blob/aff769fd75225d8f2e481185a71d5e05b76002dc/.aliases#L21-L26)) takes place. - -Here’s an example `~/.path` file that adds `/usr/local/bin` to the `$PATH`: - -```bash -export PATH="/usr/local/bin:$PATH" -``` - -### Add custom commands without creating a new fork - -If `~/.extra` exists, it will be sourced along with the other files. You can use this to add a few custom commands without the need to fork this entire repository, or to add commands you don’t want to commit to a public repository. - -My `~/.extra` looks something like this: - -```bash -# Git credentials -# Not in the repository, to prevent people from accidentally committing under my name -GIT_AUTHOR_NAME="Mathias Bynens" -GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" -git config --global user.name "$GIT_AUTHOR_NAME" -GIT_AUTHOR_EMAIL="mathias@mailinator.com" -GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" -git config --global user.email "$GIT_AUTHOR_EMAIL" -``` - -You could also use `~/.extra` to override settings, functions and aliases from my dotfiles repository. It’s probably better to [fork this repository](https://github.com/mathiasbynens/dotfiles/fork) instead, though. - -### Sensible macOS defaults - -When setting up a new Mac, you may want to set some sensible macOS defaults: - -```bash -./.macos -``` - -### Install Homebrew formulae - -When setting up a new Mac, you may want to install some common [Homebrew](https://brew.sh/) formulae (after installing Homebrew, of course): - -```bash -./brew.sh -``` - -Some of the functionality of these dotfiles depends on formulae installed by `brew.sh`. If you don’t plan to run `brew.sh`, you should look carefully through the script and manually install any particularly important ones. A good example is Bash/Git completion: the dotfiles use a special version from Homebrew. - -## Feedback - -Suggestions/improvements -[welcome](https://github.com/mathiasbynens/dotfiles/issues)! - -## Author - -| [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](http://twitter.com/mathias "Follow @mathias on Twitter") | -|---| -| [Mathias Bynens](https://mathiasbynens.be/) | - -## Thanks to… - -* @ptb and [his _macOS Setup_ repository](https://github.com/ptb/mac-setup) -* [Ben Alman](http://benalman.com/) and his [dotfiles repository](https://github.com/cowboy/dotfiles) -* [Cătălin Mariș](https://github.com/alrra) and his [dotfiles repository](https://github.com/alrra/dotfiles) -* [Gianni Chiappetta](https://butt.zone/) for sharing his [amazing collection of dotfiles](https://github.com/gf3/dotfiles) -* [Jan Moesen](http://jan.moesen.nu/) and his [ancient `.bash_profile`](https://gist.github.com/1156154) + [shiny _tilde_ repository](https://github.com/janmoesen/tilde) -* [Lauri ‘Lri’ Ranta](http://lri.me/) for sharing [loads of hidden preferences](http://osxnotes.net/defaults.html) -* [Matijs Brinkhuis](https://matijs.brinkhu.is/) and his [dotfiles repository](https://github.com/matijs/dotfiles) -* [Nicolas Gallagher](http://nicolasgallagher.com/) and his [dotfiles repository](https://github.com/necolas/dotfiles) -* [Sindre Sorhus](https://sindresorhus.com/) -* [Tom Ryder](https://sanctum.geek.nz/) and his [dotfiles repository](https://sanctum.geek.nz/cgit/dotfiles.git/about) -* [Kevin Suttle](http://kevinsuttle.com/) and his [dotfiles repository](https://github.com/kevinSuttle/dotfiles) and [macOS-Defaults project](https://github.com/kevinSuttle/macOS-Defaults), which aims to provide better documentation for [`~/.macos`](https://mths.be/macos) -* [Haralan Dobrev](https://hkdobrev.com/) -* Anyone who [contributed a patch](https://github.com/mathiasbynens/dotfiles/contributors) or [made a helpful suggestion](https://github.com/mathiasbynens/dotfiles/issues) +1. `cd ~ && mkdir code && cd code` +1. install Git and command line: `sudo apt install git gh` +1. grab the repo: `git clone https://github.com/jamesbeldock/dotfiles.git` +1. start it up: `cd dotfiles && source bootstrap.sh diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 00000000000..c43854059e8 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,124 @@ +# Testing Guide + +This repository uses [BATS](https://github.com/bats-core/bats-core) +(Bash Automated Testing System) for unit testing the shell scripts. + +## Prerequisites + +- **Bash 4.0+** (required by test helpers; macOS ships with Bash 3.2) +- **Git** (for submodule checkout) + +### macOS + +```bash +brew install bash +``` + +### Linux (Debian/Ubuntu) + +Bash 4+ is typically already installed. No extra steps needed. + +## Initial Setup + +After cloning the repository, initialize the test library submodules: + +```bash +git submodule update --init --recursive +``` + +This pulls bats-core, bats-support, and bats-assert into `test/libs/`. + +## Running Tests + +### Run all tests + +```bash +./test/libs/bats-core/bin/bats test/ +``` + +### Run a single test file + +```bash +./test/libs/bats-core/bin/bats test/stow-packages.bats +``` + +### Run a specific test by name + +```bash +./test/libs/bats-core/bin/bats test/stow-packages.bats -f "workstation PACKAGE" +``` + +### Verbose output + +```bash +./test/libs/bats-core/bin/bats --verbose-run test/ +``` + +## Test Architecture + +Each production script (`stow-packages.sh`, `linux-apt-package-install.sh`, +`osx-package-install.sh`, `bootstrap.sh`) has been refactored so that: + +1. **All logic lives in named functions** (`parse_args`, `detect_privilege`, + `detect_os`, `execute_*`) +2. **The `main` function is guarded** by a `BASH_SOURCE` check, so sourcing the + script does not trigger execution +3. **Package arrays remain at file scope** and are available immediately after + sourcing + +Tests source the production scripts without executing them, giving direct +access to: + +- **Functions**: `parse_args`, `detect_privilege`, `detect_os`, etc. +- **Package arrays**: `GNU_CORE_UTILS`, `JAMES_TOOLS`, `CASK_APPS`, etc. + +Tests never install packages, run `apt-get`/`brew`, or require root privileges. + +## What Is Tested + +| Script | Tests Cover | +|--------------------------------|----------------------------------------------------------------------| +| `stow-packages.sh` | Arg parsing, mode setting, PACKAGE arrays for all 4 modes, privilege detection | +| `linux-apt-package-install.sh` | Arg parsing, mode setting, all 7 file-scope arrays, package assembly for all 4 modes, privilege detection | +| `osx-package-install.sh` | Arg parsing (incl. iot early exit), mode setting, file-scope arrays, formulae/cask assembly for server and workstation | +| `bootstrap.sh` | Arg parsing, mode setting, OS detection with mocked OSTYPE | + +## Adding Tests + +1. Create or edit a `.bats` file in `test/`. +2. Load the helper in `setup()`: + ```bash + setup() { + load test_helper + source "${PROJECT_ROOT}/script-name.sh" + } + ``` +3. Write tests using BATS syntax: + ```bash + @test "description" { + parse_args "server" + assert_equal "$MODE" "server" + } + ``` +4. Use the shared helpers for array assertions: + ```bash + assert_array_contains ARRAY_NAME "value" + assert_array_not_contains ARRAY_NAME "value" + assert_array_length ARRAY_NAME 5 + ``` +5. Run your new test to verify. + +## File Layout + +``` +test/ + libs/ + bats-core/ # git submodule: test runner + bats-support/ # git submodule: output helpers + bats-assert/ # git submodule: assertion functions + test_helper.bash # common setup, loads libraries, shared helpers + stow-packages.bats + linux-apt-package-install.bats + osx-package-install.bats + bootstrap.bats +``` diff --git a/atuin/.config/atuin/config.toml b/atuin/.config/atuin/config.toml new file mode 100644 index 00000000000..ab4409d5925 --- /dev/null +++ b/atuin/.config/atuin/config.toml @@ -0,0 +1,280 @@ +## where to store your database, default is your system data directory +## linux/mac: ~/.local/share/atuin/history.db +## windows: %USERPROFILE%/.local/share/atuin/history.db +# db_path = "~/.history.db" + +## where to store your encryption key, default is your system data directory +## linux/mac: ~/.local/share/atuin/key +## windows: %USERPROFILE%/.local/share/atuin/key +# key_path = "~/.key" + +## where to store your auth session token, default is your system data directory +## linux/mac: ~/.local/share/atuin/session +## windows: %USERPROFILE%/.local/share/atuin/session +# session_path = "~/.session" + +## date format used, either "us" or "uk" +# dialect = "us" + +## default timezone to use when displaying time +## either "l", "local" to use the system's current local timezone, or an offset +## from UTC in the format of "<+|->H[H][:M[M][:S[S]]]" +## for example: "+9", "-05", "+03:30", "-01:23:45", etc. +# timezone = "local" + +## enable or disable automatic sync +# auto_sync = true + +## enable or disable automatic update checks +# update_check = true + +## address of the sync server +# sync_address = "https://api.atuin.sh" + +## how often to sync history. note that this is only triggered when a command +## is ran, so sync intervals may well be longer +## set it to 0 to sync after every command +# sync_frequency = "10m" + +## which search mode to use +## possible values: prefix, fulltext, fuzzy, skim +# search_mode = "fuzzy" + +## which filter mode to use by default +## possible values: "global", "host", "session", "directory", "workspace" +## consider using search.filters to customize the enablement and order of filter modes +# filter_mode = "global" + +## With workspace filtering enabled, Atuin will filter for commands executed +## in any directory within a git repository tree (default: false). +## +## To use workspace mode by default when available, set this to true and +## set filter_mode to "workspace" or leave it unspecified and +## set search.filters to include "workspace" before other filter modes. +# workspaces = false + +## which filter mode to use when atuin is invoked from a shell up-key binding +## the accepted values are identical to those of "filter_mode" +## leave unspecified to use same mode set in "filter_mode" +# filter_mode_shell_up_key_binding = "global" + +## which search mode to use when atuin is invoked from a shell up-key binding +## the accepted values are identical to those of "search_mode" +## leave unspecified to use same mode set in "search_mode" +# search_mode_shell_up_key_binding = "fuzzy" + +## which style to use +## possible values: auto, full, compact +style = "compact" + +## the maximum number of lines the interface should take up +## set it to 0 to always go full screen +inline_height = 0 + +## the maximum number of lines the interface should take up +## when atuin is invoked from a shell up-key binding +## the accepted values are identical to those of "inline_height" +inline_height_shell_up_key_binding = 20 + +## Invert the UI - put the search bar at the top , Default to `false` +# invert = false + +## enable or disable showing a preview of the selected command +## useful when the command is longer than the terminal width and is cut off +show_preview = true + +## what to do when the escape key is pressed when searching +## possible values: return-original, return-query +# exit_mode = "return-original" + +## possible values: emacs, subl +# word_jump_mode = "emacs" + +## characters that count as a part of a word +# word_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +## number of context lines to show when scrolling by pages +# scroll_context_lines = 1 + +## use ctrl instead of alt as the shortcut modifier key for numerical UI shortcuts +## alt-0 .. alt-9 +# ctrl_n_shortcuts = false + +## default history list format - can also be specified with the --format arg +# history_format = "{time}\t{command}\t{duration}" + +## prevent commands matching any of these regexes from being written to history. +## Note that these regular expressions are unanchored, i.e. if they don't start +## with ^ or end with $, they'll match anywhere in the command. +## For details on the supported regular expression syntax, see +## https://docs.rs/regex/latest/regex/#syntax +# history_filter = [ +# "^secret-cmd", +# "^innocuous-cmd .*--secret=.+", +# ] + +## prevent commands run with cwd matching any of these regexes from being written +## to history. Note that these regular expressions are unanchored, i.e. if they don't +## start with ^ or end with $, they'll match anywhere in CWD. +## For details on the supported regular expression syntax, see +## https://docs.rs/regex/latest/regex/#syntax +# cwd_filter = [ +# "^/very/secret/area", +# ] + +## Configure the maximum height of the preview to show. +## Useful when you have long scripts in your history that you want to distinguish +## by more than the first few lines. +# max_preview_height = 4 + +## Configure whether or not to show the help row, which includes the current Atuin +## version (and whether an update is available), a keymap hint, and the total +## amount of commands in your history. +# show_help = true + +## Configure whether or not to show tabs for search and inspect +# show_tabs = true + +## Configure whether or not the tabs row may be auto-hidden, which includes the current Atuin +## tab, such as Search or Inspector, and other tabs you may wish to see. This will +## only be hidden if there are fewer than this count of lines available, and does not affect the use +## of keyboard shortcuts to switch tab. 0 to never auto-hide, default is 8 (lines). +## This is ignored except in `compact` mode. +# auto_hide_height = 8 + +## Defaults to true. This matches history against a set of default regex, and will not save it if we get a match. Defaults include +## 1. AWS key id +## 2. Github pat (old and new) +## 3. Slack oauth tokens (bot, user) +## 4. Slack webhooks +## 5. Stripe live/test keys +# secrets_filter = true + +## Defaults to true. If enabled, upon hitting enter Atuin will immediately execute the command. Press tab to return to the shell and edit. +# This applies for new installs. Old installs will keep the old behaviour unless configured otherwise. +enter_accept = true + +## Defaults to false. If enabled, when triggered after && or ||, Atuin will complete commands to chain rather than replace the current line. +# command_chaining = false + +## Defaults to "emacs". This specifies the keymap on the startup of `atuin +## search`. If this is set to "auto", the startup keymap mode in the Atuin +## search is automatically selected based on the shell's keymap where the +## keybinding is defined. If this is set to "emacs", "vim-insert", or +## "vim-normal", the startup keymap mode in the Atuin search is forced to be +## the specified one. +# keymap_mode = "auto" + +## Cursor style in each keymap mode. If specified, the cursor style is changed +## in entering the cursor shape. Available values are "default" and +## "{blink,steady}-{block,underline,bar}". +# keymap_cursor = { emacs = "blink-block", vim_insert = "blink-block", vim_normal = "steady-block" } + +# network_connect_timeout = 5 +# network_timeout = 5 + +## Timeout (in seconds) for acquiring a local database connection (sqlite) +# local_timeout = 5 + +## Set this to true and Atuin will minimize motion in the UI - timers will not update live, etc. +## Alternatively, set env NO_MOTION=true +# prefers_reduced_motion = false + +[stats] +## Set commands where we should consider the subcommand for statistics. Eg, kubectl get vs just kubectl +# common_subcommands = [ +# "apt", +# "cargo", +# "composer", +# "dnf", +# "docker", +# "git", +# "go", +# "ip", +# "jj", +# "kubectl", +# "nix", +# "nmcli", +# "npm", +# "pecl", +# "pnpm", +# "podman", +# "port", +# "systemctl", +# "tmux", +# "yarn", +# ] + +## Set commands that should be totally stripped and ignored from stats +# common_prefix = ["sudo"] + +## Set commands that will be completely ignored from stats +# ignored_commands = [ +# "cd", +# "ls", +# "vi" +# ] + +[keys] +# Defaults to true. If disabled, using the up/down key won't exit the TUI when scrolled past the first/last entry. +# scroll_exits = true +# Defaults to true. The left arrow key will exit the TUI when scrolling before the first character +# exit_past_line_start = true +# Defaults to true. The right arrow key performs the same functionality as Tab and copies the selected line to the command line to be modified. +# accept_past_line_end = true + +[sync] +# Enable sync v2 by default +# This ensures that sync v2 is enabled for new installs only +# In a later release it will become the default across the board +records = true + +[preview] +## which preview strategy to use to calculate the preview height (respects max_preview_height). +## possible values: auto, static +## auto: length of the selected command. +## static: length of the longest command stored in the history. +## fixed: use max_preview_height as fixed height. +# strategy = "auto" + +[daemon] +## Enables using the daemon to sync. Requires the daemon to be running in the background. Start it with `atuin daemon` +# enabled = false + +## How often the daemon should sync in seconds +# sync_frequency = 300 + +## The path to the unix socket used by the daemon (on unix systems) +## linux/mac: ~/.local/share/atuin/atuin.sock +## windows: Not Supported +# socket_path = "~/.local/share/atuin/atuin.sock" + +## Use systemd socket activation rather than opening the given path (the path must still be correct for the client) +## linux: false +## mac/windows: Not Supported +# systemd_socket = false + +## The port that should be used for TCP on non unix systems +# tcp_port = 8889 + +# [theme] +## Color theme to use for rendering in the terminal. +## There are some built-in themes, including the base theme ("default"), +## "autumn" and "marine". You can add your own themes to the "./themes" subdirectory of your +## Atuin config (or ATUIN_THEME_DIR, if provided) as TOML files whose keys should be one or +## more of AlertInfo, AlertWarn, AlertError, Annotation, Base, Guidance, Important, and +## the string values as lowercase entries from this list: +## https://ogeon.github.io/docs/palette/master/palette/named/index.html +## If you provide a custom theme file, it should be called "NAME.toml" and the theme below +## should be the stem, i.e. `theme = "NAME"` for your chosen NAME. +# name = "autumn" + +## Whether the theme manager should output normal or extra information to help fix themes. +## Boolean, true or false. If unset, left up to the theme manager. +# debug = true + +[search] +## The list of enabled filter modes, in order of priority. +## The "workspace" mode is skipped when not in a workspace or workspaces = false. +## Default filter mode can be overridden with the filter_mode setting. +# filters = [ "global", "host", "session", "workspace", "directory" ] diff --git a/basic/dot-aliases b/basic/dot-aliases new file mode 100644 index 00000000000..03fbac50a8c --- /dev/null +++ b/basic/dot-aliases @@ -0,0 +1,6 @@ +alias cat="bat" +alias ls="eza --color=always --icons=always" +alias vim="nvim" +alias zshconfig="nvim ~/.zshrc" +alias ohmyzsh="nvim ~/.oh-my-zsh" +# alias cd="z" diff --git a/basic/dot-config/gh/config.yml b/basic/dot-config/gh/config.yml new file mode 100644 index 00000000000..8b25011ccd1 --- /dev/null +++ b/basic/dot-config/gh/config.yml @@ -0,0 +1,27 @@ +# The current version of the config schema +version: 1 +# What protocol to use when performing git operations. Supported values: ssh, https +git_protocol: https +# What editor gh should run when creating issues, pull requests, etc. If blank, will refer to environment. +editor: +# When to interactively prompt. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled +prompt: enabled +# Preference for editor-based interactive prompting. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled +prefer_editor_prompt: disabled +# A pager program to send command output to, e.g. "less". If blank, will refer to environment. Set the value to "cat" to disable the pager. +pager: +# Aliases allow you to create nicknames for gh commands +aliases: + co: pr checkout +# The path to a unix socket through which to send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport. +http_unix_socket: +# What web browser gh should use when opening URLs. If blank, will refer to environment. +browser: +# Whether to display labels using their RGB hex color codes in terminals that support truecolor. Supported values: enabled, disabled +color_labels: disabled +# Whether customizable, 4-bit accessible colors should be used. Supported values: enabled, disabled +accessible_colors: disabled +# Whether an accessible prompter should be used. Supported values: enabled, disabled +accessible_prompter: disabled +# Whether to use a animated spinner as a progress indicator. If disabled, a textual progress indicator is used instead. Supported values: enabled, disabled +spinner: enabled diff --git a/basic/dot-config/gh/hosts.yml b/basic/dot-config/gh/hosts.yml new file mode 100644 index 00000000000..52c8e659995 --- /dev/null +++ b/basic/dot-config/gh/hosts.yml @@ -0,0 +1,5 @@ +github.com: + git_protocol: https + users: + jamesbeldock: + user: jamesbeldock diff --git a/basic/dot-config/htop/htoprc b/basic/dot-config/htop/htoprc new file mode 100644 index 00000000000..ebcfce2d39f --- /dev/null +++ b/basic/dot-config/htop/htoprc @@ -0,0 +1,54 @@ +# Beware! This file is rewritten by htop when settings are changed in the interface. +# The parser is also very primitive, and not human-friendly. +htop_version=3.4.1 +config_reader_min_version=3 +fields=0 48 17 18 38 39 2 46 47 49 1 +hide_kernel_threads=1 +hide_userland_threads=0 +hide_running_in_container=0 +shadow_other_users=0 +show_thread_names=0 +show_program_path=1 +highlight_base_name=0 +highlight_deleted_exe=1 +shadow_distribution_path_prefix=0 +highlight_megabytes=1 +highlight_threads=1 +highlight_changes=0 +highlight_changes_delay_secs=5 +find_comm_in_cmdline=1 +strip_exe_from_cmdline=1 +show_merged_command=0 +header_margin=1 +screen_tabs=1 +detailed_cpu_time=0 +cpu_count_from_one=0 +show_cpu_usage=1 +show_cpu_frequency=0 +show_cached_memory=1 +update_process_names=0 +account_guest_in_cpu_meter=0 +color_scheme=0 +enable_mouse=1 +delay=15 +hide_function_bar=0 +header_layout=two_50_50 +column_meters_0=LeftCPUs2 Memory Swap +column_meter_modes_0=1 1 1 +column_meters_1=RightCPUs2 Tasks LoadAverage Uptime +column_meter_modes_1=1 2 2 2 +tree_view=0 +sort_key=46 +tree_sort_key=0 +sort_direction=-1 +tree_sort_direction=1 +tree_view_always_by_pid=0 +all_branches_collapsed=0 +screen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command +.sort_key=PERCENT_CPU +.tree_sort_key=PID +.tree_view_always_by_pid=0 +.tree_view=0 +.sort_direction=-1 +.tree_sort_direction=1 +.all_branches_collapsed=0 diff --git a/.curlrc b/basic/dot-curlrc similarity index 57% rename from .curlrc rename to basic/dot-curlrc index 481d94b899f..fbfa78e92ff 100644 --- a/.curlrc +++ b/basic/dot-curlrc @@ -1,6 +1,3 @@ -# Disguise as IE 9 on Windows 7. -user-agent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" - # When following a redirect, automatically set the previous URL as referer. referer = ";auto" diff --git a/.editorconfig b/basic/dot-editorconfig similarity index 100% rename from .editorconfig rename to basic/dot-editorconfig diff --git a/.exports b/basic/dot-exports similarity index 56% rename from .exports rename to basic/dot-exports index 02cd3cdae8d..24456e5faeb 100644 --- a/.exports +++ b/basic/dot-exports @@ -1,24 +1,11 @@ #!/usr/bin/env bash # Make vim the default editor. -export EDITOR='vim'; - -# Enable persistent REPL history for `node`. -export NODE_REPL_HISTORY=~/.node_history; -# Allow 32³ entries; the default is 1000. -export NODE_REPL_HISTORY_SIZE='32768'; -# Use sloppy mode by default, matching web browsers. -export NODE_REPL_MODE='sloppy'; +export EDITOR='nvim'; # Make Python use UTF-8 encoding for output to stdin, stdout, and stderr. export PYTHONIOENCODING='UTF-8'; -# Increase Bash history size. Allow 32³ entries; the default is 500. -export HISTSIZE='32768'; -export HISTFILESIZE="${HISTSIZE}"; -# Omit duplicates and commands that begin with a space from history. -export HISTCONTROL='ignoreboth'; - # Prefer US English and use UTF-8. export LANG='en_US.UTF-8'; export LC_ALL='en_US.UTF-8'; diff --git a/.functions b/basic/dot-functions similarity index 77% rename from .functions rename to basic/dot-functions index bce0305db69..283914f5815 100644 --- a/.functions +++ b/basic/dot-functions @@ -66,33 +66,6 @@ if [ $? -eq 0 ]; then } fi; -# Create a data URL from a file -function dataurl() { - local mimeType=$(file -b --mime-type "$1"); - if [[ $mimeType == text/* ]]; then - mimeType="${mimeType};charset=utf-8"; - fi - echo "data:${mimeType};base64,$(openssl base64 -in "$1" | tr -d '\n')"; -} - -# Start an HTTP server from a directory, optionally specifying the port -function server() { - local port="${1:-8000}"; - sleep 1 && open "http://localhost:${port}/" & - # Set the default Content-Type to `text/plain` instead of `application/octet-stream` - # And serve everything as UTF-8 (although not technically correct, this doesn’t break anything for binary files) - python -c $'import SimpleHTTPServer;\nmap = SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map;\nmap[""] = "text/plain";\nfor key, value in map.items():\n\tmap[key] = value + ";charset=UTF-8";\nSimpleHTTPServer.test();' "$port"; -} - -# Start a PHP server from a directory, optionally specifying the port -# (Requires PHP 5.4.0+.) -function phpserver() { - local port="${1:-4000}"; - local ip=$(ipconfig getifaddr en1); - sleep 1 && open "http://${ip}:${port}/" & - php -S "${ip}:${port}"; -} - # Compare original and gzipped file size function gz() { local origsize=$(wc -c < "$1"); diff --git a/.hushlogin b/basic/dot-hushlogin similarity index 100% rename from .hushlogin rename to basic/dot-hushlogin diff --git a/.inputrc b/basic/dot-inputrc similarity index 100% rename from .inputrc rename to basic/dot-inputrc diff --git a/.macos b/basic/dot-macos similarity index 88% rename from .macos rename to basic/dot-macos index ec81a260ba1..03da9789220 100755 --- a/.macos +++ b/basic/dot-macos @@ -26,10 +26,10 @@ while true; do sudo -n true; sleep 60; kill -0 "$$" || exit; done 2>/dev/null & sudo nvram SystemAudioVolume=" " # Disable transparency in the menu bar and elsewhere on Yosemite -defaults write com.apple.universalaccess reduceTransparency -bool true +# defaults write com.apple.universalaccess reduceTransparency -bool true # Set highlight color to green -defaults write NSGlobalDomain AppleHighlightColor -string "0.764700 0.976500 0.568600" +# defaults write NSGlobalDomain AppleHighlightColor -string "0.764700 0.976500 0.568600" # Set sidebar icon size to medium defaults write NSGlobalDomain NSTableViewDefaultSizeMode -int 2 @@ -39,7 +39,7 @@ defaults write NSGlobalDomain AppleShowScrollBars -string "Always" # Possible values: `WhenScrolling`, `Automatic` and `Always` # Disable the over-the-top focus ring animation -defaults write NSGlobalDomain NSUseAnimatedFocusRing -bool false +# defaults write NSGlobalDomain NSUseAnimatedFocusRing -bool false # Adjust toolbar title rollover delay defaults write NSGlobalDomain NSToolbarTitleViewRolloverDelay -float 0 @@ -76,13 +76,13 @@ defaults write com.apple.LaunchServices LSQuarantine -bool false defaults write NSGlobalDomain NSTextShowsControlCharacters -bool true # Disable Resume system-wide -defaults write com.apple.systempreferences NSQuitAlwaysKeepsWindows -bool false +# defaults write com.apple.systempreferences NSQuitAlwaysKeepsWindows -bool false # Disable automatic termination of inactive apps defaults write NSGlobalDomain NSDisableAutomaticTermination -bool true # Disable the crash reporter -#defaults write com.apple.CrashReporter DialogType -string "none" +defaults write com.apple.CrashReporter DialogType -string "none" # Set Help Viewer windows to non-floating mode defaults write com.apple.helpviewer DevMode -bool true @@ -97,19 +97,19 @@ defaults write com.apple.helpviewer DevMode -bool true sudo defaults write /Library/Preferences/com.apple.loginwindow AdminHostInfo HostName # Disable Notification Center and remove the menu bar icon -launchctl unload -w /System/Library/LaunchAgents/com.apple.notificationcenterui.plist 2> /dev/null +# launchctl unload -w /System/Library/LaunchAgents/com.apple.notificationcenterui.plist 2> /dev/null # Disable automatic capitalization as it’s annoying when typing code -defaults write NSGlobalDomain NSAutomaticCapitalizationEnabled -bool false +# defaults write NSGlobalDomain NSAutomaticCapitalizationEnabled -bool false # Disable smart dashes as they’re annoying when typing code -defaults write NSGlobalDomain NSAutomaticDashSubstitutionEnabled -bool false +# defaults write NSGlobalDomain NSAutomaticDashSubstitutionEnabled -bool false # Disable automatic period substitution as it’s annoying when typing code -defaults write NSGlobalDomain NSAutomaticPeriodSubstitutionEnabled -bool false +# defaults write NSGlobalDomain NSAutomaticPeriodSubstitutionEnabled -bool false # Disable smart quotes as they’re annoying when typing code -defaults write NSGlobalDomain NSAutomaticQuoteSubstitutionEnabled -bool false +# defaults write NSGlobalDomain NSAutomaticQuoteSubstitutionEnabled -bool false # Disable auto-correct defaults write NSGlobalDomain NSAutomaticSpellingCorrectionEnabled -bool false @@ -136,7 +136,7 @@ defaults -currentHost write NSGlobalDomain com.apple.trackpad.trackpadCornerClic defaults -currentHost write NSGlobalDomain com.apple.trackpad.enableSecondaryClick -bool true # Disable “natural” (Lion-style) scrolling -defaults write NSGlobalDomain com.apple.swipescrolldirection -bool false +# defaults write NSGlobalDomain com.apple.swipescrolldirection -bool false # Increase sound quality for Bluetooth headphones/headsets defaults write com.apple.BluetoothAudioAgent "Apple Bitpool Min (editable)" -int 40 @@ -161,19 +161,19 @@ defaults write NSGlobalDomain InitialKeyRepeat -int 10 # Set language and text formats # Note: if you’re in the US, replace `EUR` with `USD`, `Centimeters` with # `Inches`, `en_GB` with `en_US`, and `true` with `false`. -defaults write NSGlobalDomain AppleLanguages -array "en" "nl" -defaults write NSGlobalDomain AppleLocale -string "en_GB@currency=EUR" -defaults write NSGlobalDomain AppleMeasurementUnits -string "Centimeters" -defaults write NSGlobalDomain AppleMetricUnits -bool true +defaults write NSGlobalDomain AppleLanguages -array "en" "us" +defaults write NSGlobalDomain AppleLocale -string "en_US@currency=USD" +defaults write NSGlobalDomain AppleMeasurementUnits -string "Dollars" +defaults write NSGlobalDomain AppleMetricUnits -bool false # Show language menu in the top right corner of the boot screen sudo defaults write /Library/Preferences/com.apple.loginwindow showInputMenu -bool true # Set the timezone; see `sudo systemsetup -listtimezones` for other values -sudo systemsetup -settimezone "Europe/Brussels" > /dev/null +sudo systemsetup -settimezone "America/Los_Angeles" > /dev/null # Stop iTunes from responding to the keyboard media keys -#launchctl unload -w /System/Library/LaunchAgents/com.apple.rcd.plist 2> /dev/null +launchctl unload -w /System/Library/LaunchAgents/com.apple.rcd.plist 2> /dev/null ############################################################################### # Energy saving # @@ -183,7 +183,7 @@ sudo systemsetup -settimezone "Europe/Brussels" > /dev/null sudo pmset -a lidwake 1 # Restart automatically on power loss -sudo pmset -a autorestart 1 +# sudo pmset -a autorestart 1 # Restart automatically if the computer freezes sudo systemsetup -setrestartfreeze on @@ -192,16 +192,16 @@ sudo systemsetup -setrestartfreeze on sudo pmset -a displaysleep 15 # Disable machine sleep while charging -sudo pmset -c sleep 0 +# sudo pmset -c sleep 0 # Set machine sleep to 5 minutes on battery sudo pmset -b sleep 5 # Set standby delay to 24 hours (default is 1 hour) -sudo pmset -a standbydelay 86400 +# sudo pmset -a standbydelay 86400 # Never go into computer sleep mode -sudo systemsetup -setcomputersleep Off > /dev/null +# sudo systemsetup -setcomputersleep Off > /dev/null # Hibernation mode # 0: Disable hibernation (speeds up entering sleep mode) @@ -210,19 +210,19 @@ sudo systemsetup -setcomputersleep Off > /dev/null sudo pmset -a hibernatemode 0 # Remove the sleep image file to save disk space -sudo rm /private/var/vm/sleepimage +# sudo rm /private/var/vm/sleepimage # Create a zero-byte file instead… -sudo touch /private/var/vm/sleepimage +# sudo touch /private/var/vm/sleepimage # …and make sure it can’t be rewritten -sudo chflags uchg /private/var/vm/sleepimage +# sudo chflags uchg /private/var/vm/sleepimage ############################################################################### # Screen # ############################################################################### # Require password immediately after sleep or screen saver begins -defaults write com.apple.screensaver askForPassword -int 1 -defaults write com.apple.screensaver askForPasswordDelay -int 0 +# defaults write com.apple.screensaver askForPassword -int 1 +# defaults write com.apple.screensaver askForPasswordDelay -int 0 # Save screenshots to the desktop defaults write com.apple.screencapture location -string "${HOME}/Desktop" @@ -248,7 +248,7 @@ sudo defaults write /Library/Preferences/com.apple.windowserver DisplayResolutio defaults write com.apple.finder QuitMenuItem -bool true # Finder: disable window animations and Get Info animations -defaults write com.apple.finder DisableAllAnimations -bool true +#defaults write com.apple.finder DisableAllAnimations -bool true # Set Desktop as the default location for new Finder windows # For other paths, use `PfLo` and `file:///full/path/here/` @@ -262,8 +262,8 @@ defaults write com.apple.finder ShowMountedServersOnDesktop -bool true defaults write com.apple.finder ShowRemovableMediaOnDesktop -bool true # Finder: show hidden files by default -#defaults write com.apple.finder AppleShowAllFiles -bool true - +defaults write com.apple.finder AppleShowAllFiles -bool true + # Finder: show all filename extensions defaults write NSGlobalDomain AppleShowAllExtensions -bool true @@ -345,7 +345,7 @@ chflags nohidden ~/Library && xattr -d com.apple.FinderInfo ~/Library sudo chflags nohidden /Volumes # Remove Dropbox’s green checkmark icons in Finder -file=/Applications/Dropbox.app/Contents/Resources/emblem-dropbox-uptodate.icns +# file=/Applications/Dropbox.app/Contents/Resources/emblem-dropbox-uptodate.icns [ -e "${file}" ] && mv -f "${file}" "${file}.bak" # Expand the following File Info panes: @@ -386,14 +386,14 @@ defaults write com.apple.dock show-process-indicators -bool true #defaults write com.apple.dock static-only -bool true # Don’t animate opening applications from the Dock -defaults write com.apple.dock launchanim -bool false +# defaults write com.apple.dock launchanim -bool false # Speed up Mission Control animations defaults write com.apple.dock expose-animation-duration -float 0.1 # Don’t group windows by application in Mission Control # (i.e. use the old Exposé behavior instead) -defaults write com.apple.dock expose-group-by-app -bool false +# defaults write com.apple.dock expose-group-by-app -bool false # Disable Dashboard defaults write com.apple.dashboard mcx-disabled -bool true @@ -425,8 +425,8 @@ defaults write com.apple.dock show-recents -bool false find "${HOME}/Library/Application Support/Dock" -name "*-*.db" -maxdepth 1 -delete # Add iOS & Watch Simulator to Launchpad -sudo ln -sf "/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app" "/Applications/Simulator.app" -sudo ln -sf "/Applications/Xcode.app/Contents/Developer/Applications/Simulator (Watch).app" "/Applications/Simulator (Watch).app" +#sudo ln -sf "/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app" "/Applications/Simulator.app" +#sudo ln -sf "/Applications/Xcode.app/Contents/Developer/Applications/Simulator (Watch).app" "/Applications/Simulator (Watch).app" # Add a spacer to the left side of the Dock (where the applications are) #defaults write com.apple.dock persistent-apps -array-add '{tile-data={}; tile-type="spacer-tile";}' @@ -575,7 +575,7 @@ defaults write com.apple.mail SpellCheckingBehavior -string "NoSpellCheckingEnab ############################################################################### # Hide Spotlight tray-icon (and subsequent helper) -#sudo chmod 600 /System/Library/CoreServices/Search.bundle/Contents/MacOS/Search +sudo chmod 600 /System/Library/CoreServices/Search.bundle/Contents/MacOS/Search # Disable Spotlight indexing for any volume that gets mounted and has not yet # been indexed before. # Use `sudo mdutil -i off "/Volumes/foo"` to stop indexing any volume. @@ -674,8 +674,8 @@ EOD # Enable “focus follows mouse” for Terminal.app and all X11 apps # i.e. hover over a window and start typing in it without clicking first -#defaults write com.apple.terminal FocusFollowsMouse -bool true -#defaults write org.x.X11 wm_ffm -bool true +defaults write com.apple.terminal FocusFollowsMouse -bool true +defaults write org.x.X11 wm_ffm -bool true # Enable Secure Keyboard Entry in Terminal.app # See: https://security.stackexchange.com/a/47786/8918 @@ -786,13 +786,13 @@ defaults -currentHost write com.apple.ImageCapture disableHotPlug -bool true ############################################################################### # Disable automatic emoji substitution (i.e. use plain text smileys) -defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "automaticEmojiSubstitutionEnablediMessage" -bool false +#defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "automaticEmojiSubstitutionEnablediMessage" -bool false # Disable smart quotes as it’s annoying for messages that contain code -defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "automaticQuoteSubstitutionEnabled" -bool false +#defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "automaticQuoteSubstitutionEnabled" -bool false # Disable continuous spell checking -defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "continuousSpellCheckingEnabled" -bool false +#defaults write com.apple.messageshelper.MessageController SOInputLineSettings -dict-add "continuousSpellCheckingEnabled" -bool false ############################################################################### # Google Chrome & Google Chrome Canary # @@ -819,106 +819,106 @@ defaults write com.google.Chrome.canary PMPrintingExpandedStateForPrint2 -bool t ############################################################################### # Disable signing emails by default -defaults write ~/Library/Preferences/org.gpgtools.gpgmail SignNewEmailsByDefault -bool false +#defaults write ~/Library/Preferences/org.gpgtools.gpgmail SignNewEmailsByDefault -bool false ############################################################################### # Opera & Opera Developer # ############################################################################### # Expand the print dialog by default -defaults write com.operasoftware.Opera PMPrintingExpandedStateForPrint2 -boolean true -defaults write com.operasoftware.OperaDeveloper PMPrintingExpandedStateForPrint2 -boolean true +#defaults write com.operasoftware.Opera PMPrintingExpandedStateForPrint2 -boolean true +#defaults write com.operasoftware.OperaDeveloper PMPrintingExpandedStateForPrint2 -boolean true ############################################################################### # SizeUp.app # ############################################################################### # Start SizeUp at login -defaults write com.irradiatedsoftware.SizeUp StartAtLogin -bool true +#defaults write com.irradiatedsoftware.SizeUp StartAtLogin -bool true # Don’t show the preferences window on next start -defaults write com.irradiatedsoftware.SizeUp ShowPrefsOnNextStart -bool false +#defaults write com.irradiatedsoftware.SizeUp ShowPrefsOnNextStart -bool false ############################################################################### # Sublime Text # ############################################################################### # Install Sublime Text settings -cp -r init/Preferences.sublime-settings ~/Library/Application\ Support/Sublime\ Text*/Packages/User/Preferences.sublime-settings 2> /dev/null +#cp -r init/Preferences.sublime-settings ~/Library/Application\ Support/Sublime\ Text*/Packages/User/Preferences.sublime-settings 2> /dev/null ############################################################################### # Spectacle.app # ############################################################################### # Set up my preferred keyboard shortcuts -cp -r init/spectacle.json ~/Library/Application\ Support/Spectacle/Shortcuts.json 2> /dev/null +#cp -r init/spectacle.json ~/Library/Application\ Support/Spectacle/Shortcuts.json 2> /dev/null ############################################################################### # Transmission.app # ############################################################################### # Use `~/Documents/Torrents` to store incomplete downloads -defaults write org.m0k.transmission UseIncompleteDownloadFolder -bool true -defaults write org.m0k.transmission IncompleteDownloadFolder -string "${HOME}/Documents/Torrents" +#defaults write org.m0k.transmission UseIncompleteDownloadFolder -bool true +#defaults write org.m0k.transmission IncompleteDownloadFolder -string "${HOME}/Documents/Torrents" # Use `~/Downloads` to store completed downloads -defaults write org.m0k.transmission DownloadLocationConstant -bool true +#defaults write org.m0k.transmission DownloadLocationConstant -bool true # Don’t prompt for confirmation before downloading -defaults write org.m0k.transmission DownloadAsk -bool false -defaults write org.m0k.transmission MagnetOpenAsk -bool false +#defaults write org.m0k.transmission DownloadAsk -bool false +#defaults write org.m0k.transmission MagnetOpenAsk -bool false # Don’t prompt for confirmation before removing non-downloading active transfers -defaults write org.m0k.transmission CheckRemoveDownloading -bool true +#defaults write org.m0k.transmission CheckRemoveDownloading -bool true # Trash original torrent files -defaults write org.m0k.transmission DeleteOriginalTorrent -bool true +#defaults write org.m0k.transmission DeleteOriginalTorrent -bool true # Hide the donate message -defaults write org.m0k.transmission WarningDonate -bool false +#defaults write org.m0k.transmission WarningDonate -bool false # Hide the legal disclaimer -defaults write org.m0k.transmission WarningLegal -bool false +#defaults write org.m0k.transmission WarningLegal -bool false # IP block list. # Source: https://giuliomac.wordpress.com/2014/02/19/best-blocklist-for-transmission/ -defaults write org.m0k.transmission BlocklistNew -bool true -defaults write org.m0k.transmission BlocklistURL -string "http://john.bitsurge.net/public/biglist.p2p.gz" -defaults write org.m0k.transmission BlocklistAutoUpdate -bool true +#defaults write org.m0k.transmission BlocklistNew -bool true +#defaults write org.m0k.transmission BlocklistURL -string "http://john.bitsurge.net/public/biglist.p2p.gz" +#defaults write org.m0k.transmission BlocklistAutoUpdate -bool true # Randomize port on launch -defaults write org.m0k.transmission RandomPort -bool true +#defaults write org.m0k.transmission RandomPort -bool true ############################################################################### # Twitter.app # ############################################################################### # Disable smart quotes as it’s annoying for code tweets -defaults write com.twitter.twitter-mac AutomaticQuoteSubstitutionEnabled -bool false +#defaults write com.twitter.twitter-mac AutomaticQuoteSubstitutionEnabled -bool false # Show the app window when clicking the menu bar icon -defaults write com.twitter.twitter-mac MenuItemBehavior -int 1 +#defaults write com.twitter.twitter-mac MenuItemBehavior -int 1 # Enable the hidden ‘Develop’ menu -defaults write com.twitter.twitter-mac ShowDevelopMenu -bool true +#defaults write com.twitter.twitter-mac ShowDevelopMenu -bool true # Open links in the background -defaults write com.twitter.twitter-mac openLinksInBackground -bool true +#defaults write com.twitter.twitter-mac openLinksInBackground -bool true # Allow closing the ‘new tweet’ window by pressing `Esc` -defaults write com.twitter.twitter-mac ESCClosesComposeWindow -bool true +#defaults write com.twitter.twitter-mac ESCClosesComposeWindow -bool true # Show full names rather than Twitter handles -defaults write com.twitter.twitter-mac ShowFullNames -bool true +#defaults write com.twitter.twitter-mac ShowFullNames -bool true # Hide the app in the background if it’s not the front-most window -defaults write com.twitter.twitter-mac HideInBackground -bool true +#defaults write com.twitter.twitter-mac HideInBackground -bool true ############################################################################### # Tweetbot.app # ############################################################################### # Bypass the annoyingly slow t.co URL shortener -defaults write com.tapbots.TweetbotMac OpenURLsDirectly -bool true +#defaults write com.tapbots.TweetbotMac OpenURLsDirectly -bool true ############################################################################### # Kill affected applications # diff --git a/.screenrc b/basic/dot-screenrc similarity index 100% rename from .screenrc rename to basic/dot-screenrc diff --git a/.wgetrc b/basic/dot-wgetrc similarity index 100% rename from .wgetrc rename to basic/dot-wgetrc diff --git a/bin/subl b/bin/subl deleted file mode 120000 index 0170a200254..00000000000 --- a/bin/subl +++ /dev/null @@ -1 +0,0 @@ -/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl \ No newline at end of file diff --git a/bootstrap.sh b/bootstrap.sh index 5894c6c22f2..2e6623fada7 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,27 +1,94 @@ -#!/usr/bin/env bash +#! /bin/bash -cd "$(dirname "${BASH_SOURCE}")"; +# set -x # debugging on +# pwd -git pull origin main; +# Run this script first of all. It will run the others. -function doIt() { - rsync --exclude ".git/" \ - --exclude ".DS_Store" \ - --exclude ".osx" \ - --exclude "bootstrap.sh" \ - --exclude "README.md" \ - --exclude "LICENSE-MIT.txt" \ - -avh --no-perms . ~; - source ~/.bash_profile; +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/tools/discover_sets.sh" + +# parse_args: sets MODE. Returns 0 on success, 1 for help/list, 2 for invalid arg. +parse_args() { + if [[ "$1" == "--list" ]]; then + discover_sets "$SCRIPT_DIR" || return 2 + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 1 + fi + + if [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]] || [[ -z "$1" ]]; then + discover_sets "$SCRIPT_DIR" || return 2 + echo "Usage: bootstrap.sh [--help|-h|--list] " + echo "This script bootstraps a new machine by installing packages and stowing dotfiles." + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 1 + fi + + discover_sets "$SCRIPT_DIR" || return 2 + + if ! is_valid_set "$1"; then + echo "Invalid option: $1" + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 2 + fi + + if ! validate_configs "$SCRIPT_DIR"; then + echo "Config validation failed. Aborting." >&2 + return 2 + fi + + MODE="$1" + return 0 +} + +# detect_os: sets OS_TYPE to "darwin" or "linux". Returns 1 if unknown. +detect_os() { + if [[ "$OSTYPE" == "darwin"* ]]; then + OS_TYPE="darwin" + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + OS_TYPE="linux" + else + echo "Unsupported OS: $OSTYPE" + return 1 + fi + return 0 +} + +# execute_bootstrap: runs child scripts and post-install tasks. +execute_bootstrap() { + # source ./shell.sh #TODO: fix this for Linux + if [[ "$OS_TYPE" == "darwin" ]]; then + bash ./osx-package-install.sh "$MODE" + elif [[ "$OS_TYPE" == "linux" ]]; then + bash ./linux-apt-package-install.sh "$MODE" + fi + + bash ./stow-packages.sh "$MODE" + + #atuin installation and stow package + zinit ice as"command" from"gh-r" bpick"atuin-*.tar.gz" mv"atuin*/atuin -> atuin" \ + atclone"./atuin init zsh > init.zsh; ./atuin gen-completions --shell zsh > _atuin" \ + atpull"%atclone" src"init.zsh" + zinit light atuinsh/atuin + stow -v -t ~/ --dotfiles atuin + + # install eza theme + ln -s ~/.config/resources/tokyonight.yml ~/.eza/theme.yml + + # set up TPM (tmux plugin manager) + git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm + git clone https://github.com/2KAbhishek/tmux2k.git ~/.tmux/plugins/tmux2k +} + +main() { + parse_args "$@" + local rc=$? + if [ $rc -eq 1 ]; then exit 0; fi + if [ $rc -eq 2 ]; then exit 1; fi + detect_os || exit 1 + execute_bootstrap } -if [ "$1" == "--force" -o "$1" == "-f" ]; then - doIt; -else - read -p "This may overwrite existing files in your home directory. Are you sure? (y/n) " -n 1; - echo ""; - if [[ $REPLY =~ ^[Yy]$ ]]; then - doIt; - fi; -fi; -unset doIt; +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/brew.sh b/brew.sh deleted file mode 100755 index 26508ee43dd..00000000000 --- a/brew.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env bash - -# Install command-line tools using Homebrew. - -# Make sure we’re using the latest Homebrew. -brew update - -# Upgrade any already-installed formulae. -brew upgrade - -# Save Homebrew’s installed location. -BREW_PREFIX=$(brew --prefix) - -# Install GNU core utilities (those that come with macOS are outdated). -# Don’t forget to add `$(brew --prefix coreutils)/libexec/gnubin` to `$PATH`. -brew install coreutils -ln -s "${BREW_PREFIX}/bin/gsha256sum" "${BREW_PREFIX}/bin/sha256sum" - -# Install some other useful utilities like `sponge`. -brew install moreutils -# Install GNU `find`, `locate`, `updatedb`, and `xargs`, `g`-prefixed. -brew install findutils -# Install GNU `sed`, overwriting the built-in `sed`. -brew install gnu-sed --with-default-names -# Install a modern version of Bash. -brew install bash -brew install bash-completion2 - -# Switch to using brew-installed bash as default shell -if ! fgrep -q "${BREW_PREFIX}/bin/bash" /etc/shells; then - echo "${BREW_PREFIX}/bin/bash" | sudo tee -a /etc/shells; - chsh -s "${BREW_PREFIX}/bin/bash"; -fi; - -# Install `wget` with IRI support. -brew install wget --with-iri - -# Install GnuPG to enable PGP-signing commits. -brew install gnupg - -# Install more recent versions of some macOS tools. -brew install vim --with-override-system-vi -brew install grep -brew install openssh -brew install screen -brew install php -brew install gmp - -# Install font tools. -brew tap bramstein/webfonttools -brew install sfnt2woff -brew install sfnt2woff-zopfli -brew install woff2 - -# Install some CTF tools; see https://github.com/ctfs/write-ups. -brew install aircrack-ng -brew install bfg -brew install binutils -brew install binwalk -brew install cifer -brew install dex2jar -brew install dns2tcp -brew install fcrackzip -brew install foremost -brew install hashpump -brew install hydra -brew install john -brew install knock -brew install netpbm -brew install nmap -brew install pngcheck -brew install socat -brew install sqlmap -brew install tcpflow -brew install tcpreplay -brew install tcptrace -brew install ucspi-tcp # `tcpserver` etc. -brew install xpdf -brew install xz - -# Install other useful binaries. -brew install ack -#brew install exiv2 -brew install git -brew install git-lfs -brew install gs -brew install imagemagick --with-webp -brew install lua -brew install lynx -brew install p7zip -brew install pigz -brew install pv -brew install rename -brew install rlwrap -brew install ssh-copy-id -brew install tree -brew install vbindiff -brew install zopfli - -# Remove outdated versions from the cellar. -brew cleanup diff --git a/config resources/dot-config/resources/Alfred Workflows/Meta.alfredworkflow b/config resources/dot-config/resources/Alfred Workflows/Meta.alfredworkflow new file mode 100644 index 00000000000..21324091b23 Binary files /dev/null and b/config resources/dot-config/resources/Alfred Workflows/Meta.alfredworkflow differ diff --git a/config resources/dot-config/resources/BlulocoDark-JGBMod.itermcolors b/config resources/dot-config/resources/BlulocoDark-JGBMod.itermcolors new file mode 100644 index 00000000000..c01c12b7e4c --- /dev/null +++ b/config resources/dot-config/resources/BlulocoDark-JGBMod.itermcolors @@ -0,0 +1,1059 @@ + + + + + Ansi 0 Color + + Alpha Component + 1 + Blue Component + 0.29794943332672119 + Color Space + P3 + Green Component + 0.26803207397460938 + Red Component + 0.25712370872497559 + + Ansi 0 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.30088353157043457 + Color Space + sRGB + Green Component + 0.26848068833351135 + Red Component + 0.25459375977516174 + + Ansi 0 Color (Light) + + Alpha Component + 1 + Blue Component + 0.30088353157043457 + Color Space + sRGB + Green Component + 0.26848068833351135 + Red Component + 0.25459375977516174 + + Ansi 1 Color + + Alpha Component + 1 + Blue Component + 0.34152171015739441 + Color Space + P3 + Green Component + 0.27128735184669495 + Red Component + 0.90914762020111084 + + Ansi 1 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.32156860828399658 + Color Space + sRGB + Green Component + 0.18431362509727478 + Red Component + 0.98823529481887817 + + Ansi 1 Color (Light) + + Alpha Component + 1 + Blue Component + 0.32156860828399658 + Color Space + sRGB + Green Component + 0.18431362509727478 + Red Component + 0.98823529481887817 + + Ansi 10 Color + + Alpha Component + 1 + Blue Component + 0.4554937481880188 + Color Space + P3 + Green Component + 0.76202517747879028 + Red Component + 0.41004818677902222 + + Ansi 10 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.41960778832435608 + Color Space + sRGB + Green Component + 0.77254903316497803 + Red Component + 0.24705886840820312 + + Ansi 10 Color (Light) + + Alpha Component + 1 + Blue Component + 0.41960778832435608 + Color Space + sRGB + Green Component + 0.77254903316497803 + Red Component + 0.24705886840820312 + + Ansi 11 Color + + Alpha Component + 1 + Blue Component + 0.42217031121253967 + Color Space + P3 + Green Component + 0.79171007871627808 + Red Component + 0.94605547189712524 + + Ansi 11 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.34901958703994751 + Color Space + sRGB + Green Component + 0.78431379795074463 + Red Component + 0.97647064924240112 + + Ansi 11 Color (Light) + + Alpha Component + 1 + Blue Component + 0.34901958703994751 + Color Space + sRGB + Green Component + 0.78431379795074463 + Red Component + 0.97647064924240112 + + Ansi 12 Color + + Alpha Component + 1 + Blue Component + 0.97057229280471802 + Color Space + P3 + Green Component + 0.68376380205154419 + Red Component + 0.31775963306427002 + + Ansi 12 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.99607843160629272 + Color Space + sRGB + Green Component + 0.69411760568618774 + Red Component + 0.062745340168476105 + + Ansi 12 Color (Light) + + Alpha Component + 1 + Blue Component + 0.99607843160629272 + Color Space + sRGB + Green Component + 0.69411760568618774 + Red Component + 0.062745340168476105 + + Ansi 13 Color + + Alpha Component + 1 + Blue Component + 0.94782465696334839 + Color Space + P3 + Green Component + 0.50080573558807373 + Red Component + 0.93371772766113281 + + Ansi 13 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.97254902124404907 + Color Space + sRGB + Green Component + 0.47058820724487305 + Red Component + 1 + + Ansi 13 Color (Light) + + Alpha Component + 1 + Blue Component + 0.97254902124404907 + Color Space + sRGB + Green Component + 0.47058820724487305 + Red Component + 1 + + Ansi 14 Color + + Alpha Component + 1 + Blue Component + 0.73203450441360474 + Color Space + P3 + Green Component + 0.71717303991317749 + Red Component + 0.46167260408401489 + + Ansi 14 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.73725491762161255 + Color Space + sRGB + Green Component + 0.7254902720451355 + Red Component + 0.37254902720451355 + + Ansi 14 Color (Light) + + Alpha Component + 1 + Blue Component + 0.73725491762161255 + Color Space + sRGB + Green Component + 0.7254902720451355 + Red Component + 0.37254902720451355 + + Ansi 15 Color + + Alpha Component + 1 + Blue Component + 0.9998813271522522 + Color Space + P3 + Green Component + 0.99988120794296265 + Red Component + 0.99988144636154175 + + Ansi 15 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.9998813271522522 + Color Space + sRGB + Green Component + 0.9998813271522522 + Red Component + 0.99988144636154175 + + Ansi 15 Color (Light) + + Alpha Component + 1 + Blue Component + 0.9998813271522522 + Color Space + sRGB + Green Component + 0.9998813271522522 + Red Component + 0.99988144636154175 + + Ansi 2 Color + + Alpha Component + 1 + Blue Component + 0.38901317119598389 + Color Space + P3 + Green Component + 0.63540101051330566 + Red Component + 0.31618359684944153 + + Ansi 2 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.36170727014541626 + Color Space + sRGB + Green Component + 0.64468729496002197 + Red Component + 0.14523378014564514 + + Ansi 2 Color (Light) + + Alpha Component + 1 + Blue Component + 0.36170727014541626 + Color Space + sRGB + Green Component + 0.64468729496002197 + Red Component + 0.14523378014564514 + + Ansi 3 Color + + Alpha Component + 1 + Blue Component + 0.44849544763565063 + Color Space + P3 + Green Component + 0.59721052646636963 + Red Component + 0.94254106283187866 + + Ansi 3 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.4156862199306488 + Color Space + sRGB + Green Component + 0.57647067308425903 + Red Component + 1 + + Ansi 3 Color (Light) + + Alpha Component + 1 + Blue Component + 0.4156862199306488 + Color Space + sRGB + Green Component + 0.57647067308425903 + Red Component + 1 + + Ansi 4 Color + + Alpha Component + 1 + Blue Component + 0.96593445539474487 + Color Space + P3 + Green Component + 0.45687282085418701 + Red Component + 0.27266389131546021 + + Ansi 4 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.99999994039535522 + Color Space + sRGB + Green Component + 0.4627450704574585 + Red Component + 0.2039216160774231 + + Ansi 4 Color (Light) + + Alpha Component + 1 + Blue Component + 0.99999994039535522 + Color Space + sRGB + Green Component + 0.4627450704574585 + Red Component + 0.2039216160774231 + + Ansi 5 Color + + Alpha Component + 1 + Blue Component + 0.83108812570571899 + Color Space + P3 + Green Component + 0.50879162549972534 + Red Component + 0.48419898748397827 + + Ansi 5 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.85490196943283081 + Color Space + sRGB + Green Component + 0.50980395078659058 + Red Component + 0.47843137383460999 + + Ansi 5 Color (Light) + + Alpha Component + 1 + Blue Component + 0.85490196943283081 + Color Space + sRGB + Green Component + 0.50980395078659058 + Red Component + 0.47843137383460999 + + Ansi 6 Color + + Alpha Component + 1 + Blue Component + 0.65260463953018188 + Color Space + P3 + Green Component + 0.50781208276748657 + Red Component + 0.32794678211212158 + + Ansi 6 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.66666668653488159 + Color Space + sRGB + Green Component + 0.51372551918029785 + Red Component + 0.26666668057441711 + + Ansi 6 Color (Light) + + Alpha Component + 1 + Blue Component + 0.66666668653488159 + Color Space + sRGB + Green Component + 0.51372551918029785 + Red Component + 0.26666668057441711 + + Ansi 7 Color + + Alpha Component + 1 + Blue Component + 1 + Color Space + P3 + Green Component + 1 + Red Component + 1 + + Ansi 7 Color (Dark) + + Alpha Component + 1 + Blue Component + 1 + Color Space + P3 + Green Component + 1 + Red Component + 1 + + Ansi 7 Color (Light) + + Alpha Component + 1 + Blue Component + 0.8790360689163208 + Color Space + sRGB + Green Component + 0.83116775751113892 + Red Component + 0.80505776405334473 + + Ansi 8 Color + + Alpha Component + 1 + Blue Component + 0.67607241868972778 + Color Space + P3 + Green Component + 0.60425251722335815 + Red Component + 0.57008999586105347 + + Ansi 8 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.68315994739532471 + Color Space + sRGB + Green Component + 0.60563826560974121 + Red Component + 0.56203228235244751 + + Ansi 8 Color (Light) + + Alpha Component + 1 + Blue Component + 0.68315994739532471 + Color Space + sRGB + Green Component + 0.60563826560974121 + Red Component + 0.56203228235244751 + + Ansi 9 Color + + Alpha Component + 1 + Blue Component + 0.50940471887588501 + Color Space + P3 + Green Component + 0.43201661109924316 + Red Component + 0.9285317063331604 + + Ansi 9 Color (Dark) + + Alpha Component + 1 + Blue Component + 0.50196075439453125 + Color Space + sRGB + Green Component + 0.39215683937072754 + Red Component + 1 + + Ansi 9 Color (Light) + + Alpha Component + 1 + Blue Component + 0.50196075439453125 + Color Space + sRGB + Green Component + 0.39215683937072754 + Red Component + 1 + + Background Color + + Alpha Component + 1 + Blue Component + 0.20111528038978577 + Color Space + P3 + Green Component + 0.17205142974853516 + Red Component + 0.15976667404174805 + + Background Color (Dark) + + Alpha Component + 1 + Blue Component + 0.20392155647277832 + Color Space + sRGB + Green Component + 0.17254900932312012 + Red Component + 0.15686270594596863 + + Background Color (Light) + + Alpha Component + 1 + Blue Component + 0.20392155647277832 + Color Space + sRGB + Green Component + 0.17254900932312012 + Red Component + 0.15686270594596863 + + Badge Color + + Alpha Component + 0.5 + Blue Component + 0.14500156044960022 + Color Space + P3 + Green Component + 0.25274839997291565 + Red Component + 0.91916567087173462 + + Badge Color (Dark) + + Alpha Component + 0.5 + Blue Component + 0.0 + Color Space + sRGB + Green Component + 0.1491314172744751 + Red Component + 1 + + Badge Color (Light) + + Alpha Component + 0.5 + Blue Component + 0.0 + Color Space + sRGB + Green Component + 0.1491314172744751 + Red Component + 1 + + Bold Color + + Alpha Component + 1 + Blue Component + 0.99999994039535522 + Color Space + P3 + Green Component + 0.99999994039535522 + Red Component + 0.99999672174453735 + + Bold Color (Dark) + + Alpha Component + 1 + Blue Component + 1 + Color Space + sRGB + Green Component + 1 + Red Component + 0.99999600648880005 + + Bold Color (Light) + + Alpha Component + 1 + Blue Component + 1 + Color Space + sRGB + Green Component + 1 + Red Component + 0.99999600648880005 + + Cursor Color + + Alpha Component + 1 + Blue Component + 0.27345383167266846 + Color Space + P3 + Green Component + 0.80772227048873901 + Red Component + 0.96841138601303101 + + Cursor Color (Dark) + + Alpha Component + 1 + Blue Component + 0.0 + Color Space + sRGB + Green Component + 0.80000001192092896 + Red Component + 0.99999994039535522 + + Cursor Color (Light) + + Alpha Component + 1 + Blue Component + 0.0 + Color Space + sRGB + Green Component + 0.80000001192092896 + Red Component + 0.99999994039535522 + + Cursor Guide Color + + Alpha Component + 0.25 + Blue Component + 0.99078255891799927 + Color Space + P3 + Green Component + 0.92049425840377808 + Red Component + 0.7486235499382019 + + Cursor Guide Color (Dark) + + Alpha Component + 0.25 + Blue Component + 1 + Color Space + sRGB + Green Component + 0.9268307089805603 + Red Component + 0.70213186740875244 + + Cursor Guide Color (Light) + + Alpha Component + 0.25 + Blue Component + 1 + Color Space + sRGB + Green Component + 0.9268307089805603 + Red Component + 0.70213186740875244 + + Cursor Text Color + + Alpha Component + 1 + Blue Component + 0.20111528038978577 + Color Space + P3 + Green Component + 0.17205142974853516 + Red Component + 0.15976667404174805 + + Cursor Text Color (Dark) + + Alpha Component + 1 + Blue Component + 0.20392155647277832 + Color Space + sRGB + Green Component + 0.17254900932312012 + Red Component + 0.15686270594596863 + + Cursor Text Color (Light) + + Alpha Component + 1 + Blue Component + 0.20392155647277832 + Color Space + sRGB + Green Component + 0.17254900932312012 + Red Component + 0.15686270594596863 + + Foreground Color + + Alpha Component + 1 + Blue Component + 0.88467258214950562 + Color Space + P3 + Green Component + 0.86988061666488647 + Red Component + 0.86346405744552612 + + Foreground Color (Dark) + + Alpha Component + 1 + Blue Component + 0.88467258214950562 + Color Space + P3 + Green Component + 0.86988061666488647 + Red Component + 0.86346405744552612 + + Foreground Color (Light) + + Alpha Component + 1 + Blue Component + 0.79459840059280396 + Color Space + sRGB + Green Component + 0.75171965360641479 + Red Component + 0.72623991966247559 + + Link Color + + Alpha Component + 1 + Blue Component + 0.96593445539474487 + Color Space + P3 + Green Component + 0.45687282085418701 + Red Component + 0.27266389131546021 + + Link Color (Dark) + + Alpha Component + 1 + Blue Component + 0.99999994039535522 + Color Space + sRGB + Green Component + 0.4627450704574585 + Red Component + 0.2039216160774231 + + Link Color (Light) + + Alpha Component + 1 + Blue Component + 0.99999994039535522 + Color Space + sRGB + Green Component + 0.4627450704574585 + Red Component + 0.2039216160774231 + + Match Background Color + + Alpha Component + 1 + Blue Component + 0.32116127014160156 + Color Space + P3 + Green Component + 0.98600882291793823 + Red Component + 0.99697142839431763 + + Match Background Color (Dark) + + Alpha Component + 1 + Blue Component + 0.32116127014160156 + Color Space + P3 + Green Component + 0.98600882291793823 + Red Component + 0.99697142839431763 + + Match Background Color (Light) + + Alpha Component + 1 + Blue Component + 0.32116127014160156 + Color Space + P3 + Green Component + 0.98600882291793823 + Red Component + 0.99697142839431763 + + Selected Text Color + + Alpha Component + 1 + Blue Component + 0.19719782471656799 + Color Space + P3 + Green Component + 0.1681303083896637 + Red Component + 0.15584734082221985 + + Selected Text Color (Dark) + + Alpha Component + 1 + Blue Component + 0.20000000298023224 + Color Space + sRGB + Green Component + 0.16862745583057404 + Red Component + 0.15294118225574493 + + Selected Text Color (Light) + + Alpha Component + 1 + Blue Component + 0.20000000298023224 + Color Space + sRGB + Green Component + 0.16862745583057404 + Red Component + 0.15294118225574493 + + Selection Color + + Alpha Component + 1 + Blue Component + 0.78832167387008667 + Color Space + P3 + Green Component + 0.75204950571060181 + Red Component + 0.73046392202377319 + + Selection Color (Dark) + + Alpha Component + 1 + Blue Component + 0.7921568751335144 + Color Space + sRGB + Green Component + 0.75294119119644165 + Red Component + 0.72549021244049072 + + Selection Color (Light) + + Alpha Component + 1 + Blue Component + 0.7921568751335144 + Color Space + sRGB + Green Component + 0.75294119119644165 + Red Component + 0.72549021244049072 + + + diff --git a/config resources/dot-config/resources/BlulocoDark.itermcolors b/config resources/dot-config/resources/BlulocoDark.itermcolors new file mode 100644 index 00000000000..f36cb5fc7a5 --- /dev/null +++ b/config resources/dot-config/resources/BlulocoDark.itermcolors @@ -0,0 +1,344 @@ + + + + + Ansi 0 Color + + Alpha Component + 1 + Blue Component + 0.30088353157043457 + Color Space + sRGB + Green Component + 0.26848068833351135 + Red Component + 0.25459375977516174 + + Ansi 1 Color + + Alpha Component + 1 + Blue Component + 0.32156860828399658 + Color Space + sRGB + Green Component + 0.18431362509727478 + Red Component + 0.98823529481887817 + + Ansi 10 Color + + Alpha Component + 1 + Blue Component + 0.41960778832435608 + Color Space + sRGB + Green Component + 0.77254903316497803 + Red Component + 0.24705886840820312 + + Ansi 11 Color + + Alpha Component + 1 + Blue Component + 0.34901958703994751 + Color Space + sRGB + Green Component + 0.78431379795074463 + Red Component + 0.97647064924240112 + + Ansi 12 Color + + Alpha Component + 1 + Blue Component + 0.99607843160629272 + Color Space + sRGB + Green Component + 0.69411760568618774 + Red Component + 0.062745340168476105 + + Ansi 13 Color + + Alpha Component + 1 + Blue Component + 0.97254902124404907 + Color Space + sRGB + Green Component + 0.47058820724487305 + Red Component + 1 + + Ansi 14 Color + + Alpha Component + 1 + Blue Component + 0.73725491762161255 + Color Space + sRGB + Green Component + 0.7254902720451355 + Red Component + 0.37254902720451355 + + Ansi 15 Color + + Alpha Component + 1 + Blue Component + 0.9998813271522522 + Color Space + sRGB + Green Component + 0.9998813271522522 + Red Component + 0.99988144636154175 + + Ansi 2 Color + + Alpha Component + 1 + Blue Component + 0.36170727014541626 + Color Space + sRGB + Green Component + 0.64468729496002197 + Red Component + 0.14523378014564514 + + Ansi 3 Color + + Alpha Component + 1 + Blue Component + 0.4156862199306488 + Color Space + sRGB + Green Component + 0.57647067308425903 + Red Component + 1 + + Ansi 4 Color + + Alpha Component + 1 + Blue Component + 0.99999994039535522 + Color Space + sRGB + Green Component + 0.4627450704574585 + Red Component + 0.2039216160774231 + + Ansi 5 Color + + Alpha Component + 1 + Blue Component + 0.85490196943283081 + Color Space + sRGB + Green Component + 0.50980395078659058 + Red Component + 0.47843137383460999 + + Ansi 6 Color + + Alpha Component + 1 + Blue Component + 0.66666668653488159 + Color Space + sRGB + Green Component + 0.51372551918029785 + Red Component + 0.26666668057441711 + + Ansi 7 Color + + Alpha Component + 1 + Blue Component + 0.8790360689163208 + Color Space + sRGB + Green Component + 0.83116775751113892 + Red Component + 0.80505776405334473 + + Ansi 8 Color + + Alpha Component + 1 + Blue Component + 0.68315994739532471 + Color Space + sRGB + Green Component + 0.60563826560974121 + Red Component + 0.56203228235244751 + + Ansi 9 Color + + Alpha Component + 1 + Blue Component + 0.50196075439453125 + Color Space + sRGB + Green Component + 0.39215683937072754 + Red Component + 1 + + Background Color + + Alpha Component + 1 + Blue Component + 0.20392155647277832 + Color Space + sRGB + Green Component + 0.17254900932312012 + Red Component + 0.15686270594596863 + + Badge Color + + Alpha Component + 0.5 + Blue Component + 0.0 + Color Space + sRGB + Green Component + 0.1491314172744751 + Red Component + 1 + + Bold Color + + Alpha Component + 1 + Blue Component + 1 + Color Space + sRGB + Green Component + 1 + Red Component + 0.99999600648880005 + + Cursor Color + + Alpha Component + 1 + Blue Component + 0.0 + Color Space + sRGB + Green Component + 0.80000001192092896 + Red Component + 0.99999994039535522 + + Cursor Guide Color + + Alpha Component + 0.25 + Blue Component + 1 + Color Space + sRGB + Green Component + 0.9268307089805603 + Red Component + 0.70213186740875244 + + Cursor Text Color + + Alpha Component + 1 + Blue Component + 0.20392155647277832 + Color Space + sRGB + Green Component + 0.17254900932312012 + Red Component + 0.15686270594596863 + + Foreground Color + + Alpha Component + 1 + Blue Component + 0.79459840059280396 + Color Space + sRGB + Green Component + 0.75171965360641479 + Red Component + 0.72623991966247559 + + Link Color + + Alpha Component + 1 + Blue Component + 0.99999994039535522 + Color Space + sRGB + Green Component + 0.4627450704574585 + Red Component + 0.2039216160774231 + + Selected Text Color + + Alpha Component + 1 + Blue Component + 0.20000000298023224 + Color Space + sRGB + Green Component + 0.16862745583057404 + Red Component + 0.15294118225574493 + + Selection Color + + Alpha Component + 1 + Blue Component + 0.7921568751335144 + Color Space + sRGB + Green Component + 0.75294119119644165 + Red Component + 0.72549021244049072 + + + diff --git a/config resources/dot-config/resources/BlulocoDark.json b/config resources/dot-config/resources/BlulocoDark.json new file mode 100644 index 00000000000..7d50eb58ecf --- /dev/null +++ b/config resources/dot-config/resources/BlulocoDark.json @@ -0,0 +1,25 @@ +{ + "workbench.colorCustomizations": { + "terminal.foreground": "#b9c0cb", + "terminal.background": "#282c34", + "terminal.ansiBlack": "#41444d", + "terminal.ansiBlue": "#3476ff", + "terminal.ansiCyan": "#4483aa", + "terminal.ansiGreen": "#25a45c", + "terminal.ansiMagenta": "#7a82da", + "terminal.ansiRed": "#fc2f52", + "terminal.ansiWhite": "#cdd4e0", + "terminal.ansiYellow": "#ff936a", + "terminal.ansiBrightBlack": "#8f9aae", + "terminal.ansiBrightBlue": "#10b1fe", + "terminal.ansiBrightCyan": "#5fb9bc", + "terminal.ansiBrightGreen": "#3fc56b", + "terminal.ansiBrightMagenta": "#ff78f8", + "terminal.ansiBrightRed": "#ff6480", + "terminal.ansiBrightWhite": "#ffffff", + "terminal.ansiBrightYellow": "#f9c859", + "terminal.selectionBackground": "#b9c0ca", + "terminalCursor.background": "#282c34", + "terminalCursor.foreground": "#ffcc00" + } +} diff --git a/config resources/dot-config/resources/BlulocoDark.sh b/config resources/dot-config/resources/BlulocoDark.sh new file mode 100755 index 00000000000..b1f2642c475 --- /dev/null +++ b/config resources/dot-config/resources/BlulocoDark.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# BlulocoDark +printf "\033]P041444d\033\\" +printf "\033]P1fc2f52\033\\" +printf "\033]P225a45c\033\\" +printf "\033]P3ff936a\033\\" +printf "\033]P43476ff\033\\" +printf "\033]P57a82da\033\\" +printf "\033]P64483aa\033\\" +printf "\033]P7cdd4e0\033\\" +printf "\033]P88f9aae\033\\" +printf "\033]P9ff6480\033\\" +printf "\033]Pa3fc56b\033\\" +printf "\033]Pbf9c859\033\\" +printf "\033]Pc10b1fe\033\\" +printf "\033]Pdff78f8\033\\" +printf "\033]Pe5fb9bc\033\\" +printf "\033]Pfffffff\033\\" +printf "\033]Pgb9c0cb\033\\" +printf "\033]Ph282c34\033\\" +printf "\033]Plffcc00\033\\" +printf "\033]Pjb9c0ca\033\\" +printf "\033]Pk272b33\033\\" +printf "\033]Piffffff\033\\" diff --git a/config resources/dot-config/resources/iTerm2 State.itermexport b/config resources/dot-config/resources/iTerm2 State.itermexport new file mode 100644 index 00000000000..be38b1273ea Binary files /dev/null and b/config resources/dot-config/resources/iTerm2 State.itermexport differ diff --git a/config resources/dot-config/resources/tokyonight.yml b/config resources/dot-config/resources/tokyonight.yml new file mode 100644 index 00000000000..57553f9755f --- /dev/null +++ b/config resources/dot-config/resources/tokyonight.yml @@ -0,0 +1,102 @@ +colourful: true + +filekinds: + normal: { foreground: "#c0caf5" } + directory: { foreground: "#7aa2f7" } + symlink: { foreground: "#2ac3de" } + pipe: { foreground: "#414868" } + block_device: { foreground: "#e0af68" } + char_device: { foreground: "#e0af68" } + socket: { foreground: "#414868" } + special: { foreground: "#9d7cd8" } + executable: { foreground: "#9ece6a" } + mount_point: { foreground: "#b4f9f8" } + +perms: + user_read: { foreground: "#2ac3de" } + user_write: { foreground: "#bb9af7" } + user_execute_file: { foreground: "#9ece6a" } + user_execute_other: { foreground: "#9ece6a" } + group_read: { foreground: "#2ac3de" } + group_write: { foreground: "#ff9e64" } + group_execute: { foreground: "#9ece6a" } + other_read: { foreground: "#2ac3de" } + other_write: { foreground: "#ff007c" } + other_execute: { foreground: "#9ece6a" } + special_user_file: { foreground: "#ff007c" } + special_other: { foreground: "#db4b4b" } + attribute: { foreground: "#737aa2" } + +size: + major: { foreground: "#2ac3de" } + minor: { foreground: "#9d7cd8" } + number_byte: { foreground: "#a9b1d6" } + number_kilo: { foreground: "#89ddff" } + number_mega: { foreground: "#2ac3de" } + number_giga: { foreground: "#ff9e64" } + number_huge: { foreground: "#ff007c" } + unit_byte: { foreground: "#a9b1d6" } + unit_kilo: { foreground: "#89ddff" } + unit_mega: { foreground: "#2ac3de" } + unit_giga: { foreground: "#ff9e64" } + unit_huge: { foreground: "#ff007c" } + +users: + user_you: { foreground: "#3d59a1" } + user_root: { foreground: "#bb9af7" } + user_other: { foreground: "#2ac3de" } + group_yours: { foreground: "#89ddff" } + group_root: { foreground: "#bb9af7" } + group_other: { foreground: "#c0caf5" } + +links: + normal: { foreground: "#89ddff" } + multi_link_file: { foreground: "#2ac3de" } + +git: + new: { foreground: "#9ece6a" } + modified: { foreground: "#bb9af7" } + deleted: { foreground: "#db4b4b" } + renamed: { foreground: "#2ac3de" } + typechange: { foreground: "#2ac3de" } + ignored: { foreground: "#545c7e" } + conflicted: { foreground: "#ff9e64" } + +git_repo: + branch_main: { foreground: "#737aa2" } + branch_other: { foreground: "#b4f9f8" } + git_clean: { foreground: "#292e42" } + git_dirty: { foreground: "#bb9af7" } + +security_context: + colon: { foreground: "#545c7e" } + user: { foreground: "#737aa2" } + role: { foreground: "#2ac3de" } + typ: { foreground: "#3d59a1" } + range: { foreground: "#9d7cd8" } + +file_type: + image: { foreground: "#89ddff" } + video: { foreground: "#b4f9f8" } + music: { foreground: "#73daca" } + lossless: { foreground: "#41a6b5" } + crypto: { foreground: "#db4b4b" } + document: { foreground: "#a9b1d6" } + compressed: { foreground: "#ff9e64" } + temp: { foreground: "#737aa2" } + compiled: { foreground: "#737aa2" } + build: { foreground: "#1abc9c" } + source: { foreground: "#bb9af7" } + +punctuation: { foreground: "#414868" } +date: { foreground: "#e0af68" } +inode: { foreground: "#737aa2" } +blocks: { foreground: "#737aa2" } +header: { foreground: "#a9b1d6" } +octal: { foreground: "#ff9e64" } +flags: { foreground: "#9d7cd8" } + +symlink_path: { foreground: "#89ddff" } +control_char: { foreground: "#ff9e64" } +broken_symlink: { foreground: "#ff007c" } +broken_path_overlay: { foreground: "#ff007c" } diff --git a/config/packages.yaml b/config/packages.yaml new file mode 100644 index 00000000000..502ef5d7c6e --- /dev/null +++ b/config/packages.yaml @@ -0,0 +1,152 @@ +groups: + gnu_core_utils: + description: "GNU core utilities" + packages: + - name: coreutils + - name: moreutils + description: "Install some other useful utilities like sponge" + - name: findutils + description: "GNU find, locate, updatedb, and xargs, g-prefixed" + - name: bash + description: "Install a modern version of Bash" + - name: bash-completion2 + - name: wget + - name: gnu-sed + - name: stow + + basic_tools: + description: "Basic system tools" + packages: + - name: grep + - name: openssh + - name: screen + - name: php + - name: gmp + - name: vim + - name: gnupg + + network_security_tools: + description: "Network and security tools" + packages: + - name: dns2tcp + - name: knock + - name: netpbm + - name: nmap + - name: pngcheck + - name: socat + - name: sqlmap + - name: tcptrace + - name: xpdf + - name: xz + + general_utilities: + description: "General utility binaries" + packages: + - name: ack + - name: git + - name: git-lfs + - name: gs + - name: lua + - name: lynx + - name: p7zip + - name: pigz + - name: pv + - name: rename + - name: rlwrap + - name: ssh-copy-id + - name: tree + - name: vbindiff + - name: zopfli + + james_tools: + description: "James's preferred development tools" + platform_overrides: + macos: + extra_packages: + - name: starship + packages: + - name: bat + - name: zsh + - name: fzf + - name: luarocks + - name: gh + - name: pandoc + - name: ripgrep + - name: tmux + - name: mosh + - name: atuin + - name: eza + - name: fd + - name: python3 + - name: neovim + - name: broot + - name: bottom + - name: git-delta + - name: uv + - name: thefuck + - name: mtr + - name: htop + - name: tpm + - name: yazi + - name: stow + - name: ruby + - name: tldr + - name: fastfetch + + lxc_tools: + description: "LXC-specific tools" + packages: + - name: git + - name: git-lfs + - name: lua + - name: gh + - name: ssh-copy-id + - name: bat + - name: zsh + - name: tmux + - name: mosh + - name: atuin + - name: neovim + - name: stow + - name: fastfetch + + nerd_fonts: + description: "Nerd Font installations" + packages: + - name: font-jetbrains-mono-nerd-font + - name: font-fira-code-nerd-font + + cask_apps: + description: "macOS Cask applications" + platform: macos_only + install_type: cask + packages: + - name: iterm2 + - name: wezterm + - name: alfred + - name: spotify + - name: docker + - name: cmake + - name: vimr + - name: itsycal + - name: dash + - name: 1password + - name: 1password-cli + - name: flux-app + +stow_packages: + - atuin + - basic + - config resources + - fastfetch + - git + - iterm2 + - karabiner + - nvim + - oh-my-zsh + - ssh + - starship + - tmux + - vim + - wezterm + - zsh diff --git a/config/schema/packages-schema.json b/config/schema/packages-schema.json new file mode 100644 index 00000000000..d8f90490ffa --- /dev/null +++ b/config/schema/packages-schema.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Package Catalog", + "description": "Master catalog of all package groups and stow packages", + "type": "object", + "required": ["groups", "stow_packages"], + "additionalProperties": false, + "properties": { + "groups": { + "type": "object", + "description": "Map of group names to group definitions", + "additionalProperties": { + "type": "object", + "required": ["description", "packages"], + "additionalProperties": false, + "properties": { + "description": { + "type": "string" + }, + "platform": { + "type": "string", + "enum": ["macos_only", "linux_only"] + }, + "install_type": { + "type": "string", + "enum": ["cask", "formula"] + }, + "platform_overrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "macos": { + "type": "object", + "additionalProperties": false, + "properties": { + "extra_packages": { + "type": "array", + "items": { "$ref": "#/$defs/package_entry" } + } + } + }, + "linux": { + "type": "object", + "additionalProperties": false, + "properties": { + "extra_packages": { + "type": "array", + "items": { "$ref": "#/$defs/package_entry" } + } + } + } + } + }, + "packages": { + "type": "array", + "items": { "$ref": "#/$defs/package_entry" } + } + } + } + }, + "stow_packages": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Master list of all available stow packages" + } + }, + "$defs": { + "package_entry": { + "type": "object", + "required": ["name"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Package name" + }, + "version": { + "type": "string", + "description": "Optional version pin (defaults to latest)" + }, + "description": { + "type": "string", + "description": "Optional description or note" + } + } + } + } +} diff --git a/config/schema/set-schema.json b/config/schema/set-schema.json new file mode 100644 index 00000000000..2b9675e2867 --- /dev/null +++ b/config/schema/set-schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Package Set Configuration", + "description": "Configuration for a package set (server, workstation, iot, lxc)", + "type": "object", + "required": ["name", "stow_packages"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Set identifier" + }, + "description": { + "type": "string", + "description": "Human-readable description of this set" + }, + "stow_packages": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Stow packages to symlink for this set" + }, + "linux": { + "type": "object", + "additionalProperties": false, + "properties": { + "groups": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Package group names to install on Linux" + } + } + }, + "macos": { + "type": "object", + "additionalProperties": false, + "properties": { + "formulae_groups": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Package group names to install as Homebrew formulae" + }, + "cask_groups": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Package group names to install as Homebrew casks" + } + } + } + } +} diff --git a/config/sets/iot.yaml b/config/sets/iot.yaml new file mode 100644 index 00000000000..f1fdfca1b0c --- /dev/null +++ b/config/sets/iot.yaml @@ -0,0 +1,17 @@ +name: iot +description: Basic tools, Core utils, and James's tools (tmux, zsh, etc.) +stow_packages: +- basic +- config resources +- fastfetch +- nvim +- tmux +- zsh +linux: + groups: + - gnu_core_utils + - basic_tools + - james_tools +macos: + formulae_groups: [] + cask_groups: [] diff --git a/config/sets/lxc.yaml b/config/sets/lxc.yaml new file mode 100644 index 00000000000..7e9bec2c582 --- /dev/null +++ b/config/sets/lxc.yaml @@ -0,0 +1,16 @@ +name: lxc +description: "Minimal tools, Core utils, and LXC-specific tools" + +stow_packages: + - basic + - config resources + - fastfetch + - nvim + - tmux + - zsh + +linux: + groups: + - gnu_core_utils + - basic_tools + - lxc_tools diff --git a/config/sets/server.yaml b/config/sets/server.yaml new file mode 100644 index 00000000000..480f8d8abcc --- /dev/null +++ b/config/sets/server.yaml @@ -0,0 +1,28 @@ +name: server +description: "IoT + Network/Security tools + General utilities (git, etc.)" + +stow_packages: + - basic + - config resources + - fastfetch + - git + - nvim + - tmux + - starship + - zsh + +linux: + groups: + - gnu_core_utils + - basic_tools + - james_tools + - network_security_tools + - general_utilities + +macos: + formulae_groups: + - gnu_core_utils + - basic_tools + - james_tools + - network_security_tools + - general_utilities diff --git a/config/sets/workstation.yaml b/config/sets/workstation.yaml new file mode 100644 index 00000000000..4e1c764f6fd --- /dev/null +++ b/config/sets/workstation.yaml @@ -0,0 +1,35 @@ +name: workstation +description: "Full workstation setup with GUI applications and fonts" + +stow_packages: + - basic + - config resources + - fastfetch + - git + - iterm2 + - nvim + - oh-my-zsh + - starship + - tmux + - wezterm + - zsh + +linux: + groups: + - gnu_core_utils + - basic_tools + - james_tools + - network_security_tools + - general_utilities + - nerd_fonts + +macos: + formulae_groups: + - gnu_core_utils + - basic_tools + - james_tools + - network_security_tools + - general_utilities + cask_groups: + - nerd_fonts + - cask_apps diff --git a/fastfetch/.config/fastfetch/config.jsonc b/fastfetch/.config/fastfetch/config.jsonc new file mode 100644 index 00000000000..0f6b41cbd78 --- /dev/null +++ b/fastfetch/.config/fastfetch/config.jsonc @@ -0,0 +1,22 @@ +{ + "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/master/doc/json_schema.json", + "modules": [ + "title", + "separator", + "os", + "host", + "uptime", + "packages", + "shell", + "icons", + "terminal", + "terminalfont", + "cpu", + "gpu", + "memory", + "disk", + "localip", + "break", + "colors" + ] +} diff --git a/.gitconfig b/git/.gitconfig similarity index 84% rename from .gitconfig rename to git/.gitconfig index e06fb559cc1..756d4354c64 100644 --- a/.gitconfig +++ b/git/.gitconfig @@ -99,6 +99,9 @@ # https://git-scm.com/docs/git-update-index#_untracked_cache untrackedCache = true + # to use git-delta + pager=delta + [color] # Use colors in Git commands that are capable of colored output when @@ -126,8 +129,14 @@ [commit] - # https://help.github.com/articles/signing-commits-using-gpg/ - gpgsign = true +# https://help.github.com/articles/signing-commits-using-gpg/ +# gpgsign = true + +[delta] + navigate = true + dark = true + side-by-side = true + line-numbers = true [diff] @@ -144,10 +153,15 @@ # Automatically correct and execute mistyped commands. autocorrect = 1 +[interactive] + + diffFilter = delta --color-only + [merge] # Include summaries of merged commits in newly created merge commit messages log = true + conflictStyle = diff3 [push] @@ -181,3 +195,18 @@ [init] defaultBranch = main + +[user] + email = james@xecuteventures.com + name = James Beldock + signingkey = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDl6XdxPR8WBLidgDbCrgS953q9nP44W+XpGrSF1Dnc5ppQT0e+pyWxIkQ8HdGsAQGirh6NvowG9fmTHPW0i2ALy0AefuWJzl44IMUp64cN+ZG4t35dQx0JYhpL9lNMuz5wwDOVchKLK9mpbFumg+qETLnnO0IQ8Cmx/uL/u3XR/+PT68AqFbXCGJc2bUnoo8uNDRdShIV48SbXSSPNKQLDYtmnsKoxQImmRipTUzjWN+kU8KCb8Dnwwosc0tU4J4iG3p4HweJU0ljlspQkSAl62tNs5EgF+RPZyNF7I7993wZKBUHK+stPTpJCzhDfwrMgZiecLo0GR1m5nNuq/Yl/ + +[gpg] + format = ssh + +[gpg "ssh"] + program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign" + +[commit] + gpgsign = true + diff --git a/init/Preferences.sublime-settings b/init/Preferences.sublime-settings deleted file mode 100644 index 1d190f97d93..00000000000 --- a/init/Preferences.sublime-settings +++ /dev/null @@ -1,41 +0,0 @@ -{ - "color_scheme": "Packages/Color Scheme - Default/Solarized (Dark).tmTheme", - "default_encoding": "UTF-8", - "default_line_ending": "unix", - "detect_indentation": false, - "draw_white_space": "all", - "ensure_newline_at_eof_on_save": false, - "file_exclude_patterns": - [ - ".DS_Store", - "Desktop.ini", - "*.pyc", - "._*", - "Thumbs.db", - ".Spotlight-V100", - ".Trashes" - ], - "folder_exclude_patterns": - [ - ".git", - "node_modules" - ], - "font_face": "Monaco", - "font_size": 13, - "highlight_modified_tabs": true, - "hot_exit": false, - "line_padding_bottom": 5, - "match_brackets": true, - "match_brackets_angle": true, - "remember_open_files": false, - "rulers": - [ - 80 - ], - "show_encoding": true, - "show_line_endings": true, - "tab_size": 2, - "translate_tabs_to_spaces": false, - "trim_trailing_white_space_on_save": true, - "word_wrap": true -} diff --git a/init/Solarized Dark xterm-256color.terminal b/init/Solarized Dark xterm-256color.terminal deleted file mode 100644 index 46f179d6cd4..00000000000 --- a/init/Solarized Dark xterm-256color.terminal +++ /dev/null @@ -1,160 +0,0 @@ - - - - - BackgroundColor - - YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS - AAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NPECgw - LjAxNTkyNDQwNTMxIDAuMTI2NTIwOTE2OCAwLjE1OTY5NjAxMjcAEAGAAtIQERITWiRj - bGFzc25hbWVYJGNsYXNzZXNXTlNDb2xvcqISFFhOU09iamVjdF8QD05TS2V5ZWRBcmNo - aXZlctEXGFRyb290gAEIERojLTI3O0FITltijY+RlqGqsrW+0NPYAAAAAAAAAQEAAAAA - AAAAGQAAAAAAAAAAAAAAAAAAANo= - - BlinkText - - CursorColor - - YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS - AAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NPECcw - LjQ0MDU4MDI0ODggMC41MDk2MjkzMDkyIDAuNTE2ODU3OTgxNwAQAYAC0hAREhNaJGNs - YXNzbmFtZVgkY2xhc3Nlc1dOU0NvbG9yohIUWE5TT2JqZWN0XxAPTlNLZXllZEFyY2hp - dmVy0RcYVHJvb3SAAQgRGiMtMjc7QUhOW2KMjpCVoKmxtL3P0tcAAAAAAAABAQAAAAAA - AAAZAAAAAAAAAAAAAAAAAAAA2Q== - - Font - - YnBsaXN0MDDUAQIDBAUGGBlYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS - AAGGoKQHCBESVSRudWxs1AkKCwwNDg8QVk5TU2l6ZVhOU2ZGbGFnc1ZOU05hbWVWJGNs - YXNzI0AqAAAAAAAAEBCAAoADXU1lbmxvLVJlZ3VsYXLSExQVFlokY2xhc3NuYW1lWCRj - bGFzc2VzVk5TRm9udKIVF1hOU09iamVjdF8QD05TS2V5ZWRBcmNoaXZlctEaG1Ryb290 - gAEIERojLTI3PEJLUltiaXJ0dniGi5afpqmyxMfMAAAAAAAAAQEAAAAAAAAAHAAAAAAA - AAAAAAAAAAAAAM4= - - FontAntialias - - FontHeightSpacing - 1.1000000000000001 - FontWidthSpacing - 1 - ProfileCurrentVersion - 2.02 - SelectionColor - - YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS - AAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NPECgw - LjAzOTM4MDczNjY1IDAuMTYwMTE2NDYzOSAwLjE5ODMzMjc1NjgAEAGAAtIQERITWiRj - bGFzc25hbWVYJGNsYXNzZXNXTlNDb2xvcqISFFhOU09iamVjdF8QD05TS2V5ZWRBcmNo - aXZlctEXGFRyb290gAEIERojLTI3O0FITltijY+RlqGqsrW+0NPYAAAAAAAAAQEAAAAA - AAAAGQAAAAAAAAAAAAAAAAAAANo= - - ShowWindowSettingsNameInTitle - - TerminalType - xterm-256color - TextBoldColor - - YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS - AAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NPECYw - LjUwNTk5MTkzNTcgMC41NjQ4NTgzNzcgMC41NjM2MzY1NDE0ABABgALSEBESE1okY2xh - c3NuYW1lWCRjbGFzc2VzV05TQ29sb3KiEhRYTlNPYmplY3RfEA9OU0tleWVkQXJjaGl2 - ZXLRFxhUcm9vdIABCBEaIy0yNztBSE5bYouNj5SfqLCzvM7R1gAAAAAAAAEBAAAAAAAA - ABkAAAAAAAAAAAAAAAAAAADY - - TextColor - - YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS - AAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NPECcw - LjQ0MDU4MDI0ODggMC41MDk2MjkzMDkyIDAuNTE2ODU3OTgxNwAQAYAC0hAREhNaJGNs - YXNzbmFtZVgkY2xhc3Nlc1dOU0NvbG9yohIUWE5TT2JqZWN0XxAPTlNLZXllZEFyY2hp - dmVy0RcYVHJvb3SAAQgRGiMtMjc7QUhOW2KMjpCVoKmxtL3P0tcAAAAAAAABAQAAAAAA - AAAZAAAAAAAAAAAAAAAAAAAA2Q== - - UseBrightBold - - blackColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg7JNIT2DkvUjPoO+F0s+AYY= - - blueColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmgyqcAj6DtOHsPoO+RUg/AYY= - - brightBlackColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg+ZzgjyDs44BPoNahyM+AYY= - - brightBlueColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg7yT4T6DEXcCP4POUAQ/AYY= - - brightCyanColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg7CIAT+Dj5oQP4N8ShA/AYY= - - brightGreenColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmgzyujT6DFZy2PoOYFsQ+AYY= - - brightMagentaColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmgxMjsj6D+uazPoNkyTc/AYY= - - brightRedColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmgyfkPT+D/15aPoMgl5Y9AYY= - - brightWhiteColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg49LfT+D0Dt1P4MGM10/AYY= - - brightYellowColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg1MTpj6DeHnQPoPQg+A+AYY= - - cyanColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg4VRFj6DfyESP4PkZwY/AYY= - - greenColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg9lI5j6DIYkKP4PVjKU8AYY= - - magentaColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg/4CRz+DBTzdPYMgzt4+AYY= - - name - Solarized Dark xterm-256color - redColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg6i7UT+DUATePYMl2hA+AYY= - - type - Window Settings - whiteColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmgzqGaj+D2tdjP4NYPUw/AYY= - - yellowColour - - BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU0NvbG9yAISECE5TT2JqZWN0AIWEAWMBhARm - ZmZmg0DAJT+DB17vPoM4Y8A8AYY= - - - diff --git a/init/Solarized Dark.itermcolors b/init/Solarized Dark.itermcolors deleted file mode 100644 index d630a40539f..00000000000 --- a/init/Solarized Dark.itermcolors +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - Ansi 0 Color - - Blue Component - 0.19370138645172119 - Green Component - 0.15575926005840302 - Red Component - 0.0 - - Ansi 1 Color - - Blue Component - 0.14145714044570923 - Green Component - 0.10840655118227005 - Red Component - 0.81926977634429932 - - Ansi 10 Color - - Blue Component - 0.38298487663269043 - Green Component - 0.35665956139564514 - Red Component - 0.27671992778778076 - - Ansi 11 Color - - Blue Component - 0.43850564956665039 - Green Component - 0.40717673301696777 - Red Component - 0.32436618208885193 - - Ansi 12 Color - - Blue Component - 0.51685798168182373 - Green Component - 0.50962930917739868 - Red Component - 0.44058024883270264 - - Ansi 13 Color - - Blue Component - 0.72908437252044678 - Green Component - 0.33896297216415405 - Red Component - 0.34798634052276611 - - Ansi 14 Color - - Blue Component - 0.56363654136657715 - Green Component - 0.56485837697982788 - Red Component - 0.50599193572998047 - - Ansi 15 Color - - Blue Component - 0.86405980587005615 - Green Component - 0.95794391632080078 - Red Component - 0.98943418264389038 - - Ansi 2 Color - - Blue Component - 0.020208755508065224 - Green Component - 0.54115492105484009 - Red Component - 0.44977453351020813 - - Ansi 3 Color - - Blue Component - 0.023484811186790466 - Green Component - 0.46751424670219421 - Red Component - 0.64746475219726562 - - Ansi 4 Color - - Blue Component - 0.78231418132781982 - Green Component - 0.46265947818756104 - Red Component - 0.12754884362220764 - - Ansi 5 Color - - Blue Component - 0.43516635894775391 - Green Component - 0.10802463442087173 - Red Component - 0.77738940715789795 - - Ansi 6 Color - - Blue Component - 0.52502274513244629 - Green Component - 0.57082360982894897 - Red Component - 0.14679534733295441 - - Ansi 7 Color - - Blue Component - 0.79781103134155273 - Green Component - 0.89001238346099854 - Red Component - 0.91611063480377197 - - Ansi 8 Color - - Blue Component - 0.15170273184776306 - Green Component - 0.11783610284328461 - Red Component - 0.0 - - Ansi 9 Color - - Blue Component - 0.073530435562133789 - Green Component - 0.21325300633907318 - Red Component - 0.74176257848739624 - - Background Color - - Blue Component - 0.15170273184776306 - Green Component - 0.11783610284328461 - Red Component - 0.0 - - Bold Color - - Blue Component - 0.56363654136657715 - Green Component - 0.56485837697982788 - Red Component - 0.50599193572998047 - - Cursor Color - - Blue Component - 0.51685798168182373 - Green Component - 0.50962930917739868 - Red Component - 0.44058024883270264 - - Cursor Text Color - - Blue Component - 0.19370138645172119 - Green Component - 0.15575926005840302 - Red Component - 0.0 - - Foreground Color - - Blue Component - 0.51685798168182373 - Green Component - 0.50962930917739868 - Red Component - 0.44058024883270264 - - Selected Text Color - - Blue Component - 0.56363654136657715 - Green Component - 0.56485837697982788 - Red Component - 0.50599193572998047 - - Selection Color - - Blue Component - 0.19370138645172119 - Green Component - 0.15575926005840302 - Red Component - 0.0 - - - diff --git a/init/spectacle.json b/init/spectacle.json deleted file mode 100644 index c6f1a974f53..00000000000 --- a/init/spectacle.json +++ /dev/null @@ -1,74 +0,0 @@ -[ - { - "shortcut_key_binding": null, - "shortcut_name": "RedoLastMove" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MakeSmaller" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToUpperLeft" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToUpperRight" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToBottomHalf" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToNextDisplay" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToTopHalf" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToLowerLeft" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MakeLarger" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "UndoLastMove" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToPreviousDisplay" - }, - { - "shortcut_key_binding": "ctrl+alt+cmd+m", - "shortcut_name": "MoveToFullscreen" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToNextThird" - }, - { - "shortcut_key_binding": "ctrl+alt+cmd+left", - "shortcut_name": "MoveToLeftHalf" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToCenter" - }, - { - "shortcut_key_binding": "ctrl+alt+cmd+right", - "shortcut_name": "MoveToRightHalf" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToPreviousThird" - }, - { - "shortcut_key_binding": null, - "shortcut_name": "MoveToLowerRight" - } -] diff --git a/iterm2/.config/iterm2/AppSupport b/iterm2/.config/iterm2/AppSupport new file mode 120000 index 00000000000..954c9c19251 --- /dev/null +++ b/iterm2/.config/iterm2/AppSupport @@ -0,0 +1 @@ +/Users/j/Library/Application Support/iTerm2 \ No newline at end of file diff --git a/iterm2/.iterm2_shell_integration.zsh b/iterm2/.iterm2_shell_integration.zsh new file mode 100755 index 00000000000..aa7d5a56f5a --- /dev/null +++ b/iterm2/.iterm2_shell_integration.zsh @@ -0,0 +1,179 @@ +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +if [[ -o interactive ]]; then + if [ "${ITERM_ENABLE_SHELL_INTEGRATION_WITH_TMUX-}""$TERM" != "tmux-256color" -a "${ITERM_ENABLE_SHELL_INTEGRATION_WITH_TMUX-}""$TERM" != "screen" -a "${ITERM_SHELL_INTEGRATION_INSTALLED-}" = "" -a "$TERM" != linux -a "$TERM" != dumb ]; then + ITERM_SHELL_INTEGRATION_INSTALLED=Yes + ITERM2_SHOULD_DECORATE_PROMPT="1" + # Indicates start of command output. Runs just before command executes. + iterm2_before_cmd_executes() { + if [ "$TERM_PROGRAM" = "iTerm.app" ]; then + printf "\033]133;C;\r\007" + else + printf "\033]133;C;\007" + fi + } + + iterm2_set_user_var() { + printf "\033]1337;SetUserVar=%s=%s\007" "$1" $(printf "%s" "$2" | base64 | tr -d '\n') + } + + # Users can write their own version of this method. It should call + # iterm2_set_user_var but not produce any other output. + # e.g., iterm2_set_user_var currentDirectory $PWD + # Accessible in iTerm2 (in a badge now, elsewhere in the future) as + # \(user.currentDirectory). + whence -v iterm2_print_user_vars > /dev/null 2>&1 + if [ $? -ne 0 ]; then + iterm2_print_user_vars() { + true + } + fi + + iterm2_print_state_data() { + local _iterm2_hostname="${iterm2_hostname-}" + if [ -z "${iterm2_hostname:-}" ]; then + _iterm2_hostname=$(hostname -f 2>/dev/null) + fi + printf "\033]1337;RemoteHost=%s@%s\007" "$USER" "${_iterm2_hostname-}" + printf "\033]1337;CurrentDir=%s\007" "$PWD" + iterm2_print_user_vars + } + + # Report return code of command; runs after command finishes but before prompt + iterm2_after_cmd_executes() { + printf "\033]133;D;%s\007" "$STATUS" + iterm2_print_state_data + } + + # Mark start of prompt + iterm2_prompt_mark() { + printf "\033]133;A\007" + } + + # Mark end of prompt + iterm2_prompt_end() { + printf "\033]133;B\007" + } + + # There are three possible paths in life. + # + # 1) A command is entered at the prompt and you press return. + # The following steps happen: + # * iterm2_preexec is invoked + # * PS1 is set to ITERM2_PRECMD_PS1 + # * ITERM2_SHOULD_DECORATE_PROMPT is set to 1 + # * The command executes (possibly reading or modifying PS1) + # * iterm2_precmd is invoked + # * ITERM2_PRECMD_PS1 is set to PS1 (as modified by command execution) + # * PS1 gets our escape sequences added to it + # * zsh displays your prompt + # * You start entering a command + # + # 2) You press ^C while entering a command at the prompt. + # The following steps happen: + # * (iterm2_preexec is NOT invoked) + # * iterm2_precmd is invoked + # * iterm2_before_cmd_executes is called since we detected that iterm2_preexec was not run + # * (ITERM2_PRECMD_PS1 and PS1 are not messed with, since PS1 already has our escape + # sequences and ITERM2_PRECMD_PS1 already has PS1's original value) + # * zsh displays your prompt + # * You start entering a command + # + # 3) A new shell is born. + # * PS1 has some initial value, either zsh's default or a value set before this script is sourced. + # * iterm2_precmd is invoked + # * ITERM2_SHOULD_DECORATE_PROMPT is initialized to 1 + # * ITERM2_PRECMD_PS1 is set to the initial value of PS1 + # * PS1 gets our escape sequences added to it + # * Your prompt is shown and you may begin entering a command. + # + # Invariants: + # * ITERM2_SHOULD_DECORATE_PROMPT is 1 during and just after command execution, and "" while the prompt is + # shown and until you enter a command and press return. + # * PS1 does not have our escape sequences during command execution + # * After the command executes but before a new one begins, PS1 has escape sequences and + # ITERM2_PRECMD_PS1 has PS1's original value. + iterm2_decorate_prompt() { + # This should be a raw PS1 without iTerm2's stuff. It could be changed during command + # execution. + ITERM2_PRECMD_PS1="$PS1" + ITERM2_SHOULD_DECORATE_PROMPT="" + + # Add our escape sequences just before the prompt is shown. + # Use ITERM2_SQUELCH_MARK for people who can't modify PS1 directly, like powerlevel9k users. + # This is gross but I had a heck of a time writing a correct if statetment for zsh 5.0.2. + local PREFIX="" + if [[ $PS1 == *"$(iterm2_prompt_mark)"* ]]; then + PREFIX="" + elif [[ "${ITERM2_SQUELCH_MARK-}" != "" ]]; then + PREFIX="" + else + PREFIX="%{$(iterm2_prompt_mark)%}" + fi + PS1="$PREFIX$PS1%{$(iterm2_prompt_end)%}" + ITERM2_DECORATED_PS1="$PS1" + } + + iterm2_precmd() { + local STATUS="$?" + if [ -z "${ITERM2_SHOULD_DECORATE_PROMPT-}" ]; then + # You pressed ^C while entering a command (iterm2_preexec did not run) + iterm2_before_cmd_executes + if [ "$PS1" != "${ITERM2_DECORATED_PS1-}" ]; then + # PS1 changed, perhaps in another precmd. See issue 9938. + ITERM2_SHOULD_DECORATE_PROMPT="1" + fi + fi + + iterm2_after_cmd_executes "$STATUS" + + if [ -n "$ITERM2_SHOULD_DECORATE_PROMPT" ]; then + iterm2_decorate_prompt + fi + } + + # This is not run if you press ^C while entering a command. + iterm2_preexec() { + # Set PS1 back to its raw value prior to executing the command. + PS1="$ITERM2_PRECMD_PS1" + ITERM2_SHOULD_DECORATE_PROMPT="1" + iterm2_before_cmd_executes + } + + # If hostname -f is slow on your system set iterm2_hostname prior to + # sourcing this script. We know it is fast on macOS so we don't cache + # it. That lets us handle the hostname changing like when you attach + # to a VPN. + if [ -z "${iterm2_hostname-}" ]; then + if [ "$(uname)" != "Darwin" ]; then + iterm2_hostname=`hostname -f 2>/dev/null` + # Some flavors of BSD (i.e. NetBSD and OpenBSD) don't have the -f option. + if [ $? -ne 0 ]; then + iterm2_hostname=`hostname` + fi + fi + fi + + [[ -z ${precmd_functions-} ]] && precmd_functions=() + precmd_functions=($precmd_functions iterm2_precmd) + + [[ -z ${preexec_functions-} ]] && preexec_functions=() + preexec_functions=($preexec_functions iterm2_preexec) + + iterm2_print_state_data + printf "\033]1337;ShellIntegrationVersion=14;shell=zsh\007" + fi +fi +alias imgcat=${HOME}/.iterm2/imgcat;alias imgls=${HOME}/.iterm2/imgls;alias it2api=${HOME}/.iterm2/it2api;alias it2attention=${HOME}/.iterm2/it2attention;alias it2check=${HOME}/.iterm2/it2check;alias it2copy=${HOME}/.iterm2/it2copy;alias it2dl=${HOME}/.iterm2/it2dl;alias it2getvar=${HOME}/.iterm2/it2getvar;alias it2git=${HOME}/.iterm2/it2git;alias it2setcolor=${HOME}/.iterm2/it2setcolor;alias it2setkeylabel=${HOME}/.iterm2/it2setkeylabel;alias it2tip=${HOME}/.iterm2/it2tip;alias it2ul=${HOME}/.iterm2/it2ul;alias it2universion=${HOME}/.iterm2/it2universion;alias it2profile=${HOME}/.iterm2/it2profile;alias it2cat=${HOME}/.iterm2/it2cat diff --git a/karabiner/dot-config/karabiner/assets/complex_modifications/1698173201.json b/karabiner/dot-config/karabiner/assets/complex_modifications/1698173201.json new file mode 100644 index 00000000000..4561b1f4965 --- /dev/null +++ b/karabiner/dot-config/karabiner/assets/complex_modifications/1698173201.json @@ -0,0 +1,29 @@ +{ + "title": "Right Option Key → Hyper Key (⌃⌥⇧⌘)", + "rules": [ + { + "description": "Right Option Key → Hyper Key (⌃⌥⇧⌘)", + "manipulators": [ + { + "from": { + "key_code": "right_option", + "modifiers": { + "optional": ["any"] + } + }, + "to": [ + { + "key_code": "left_shift", + "modifiers": [ + "left_command", + "left_control", + "left_option" + ] + } + ], + "type": "basic" + } + ] + } + ] +} diff --git a/karabiner/dot-config/karabiner/karabiner.json b/karabiner/dot-config/karabiner/karabiner.json new file mode 100644 index 00000000000..6a08d058090 --- /dev/null +++ b/karabiner/dot-config/karabiner/karabiner.json @@ -0,0 +1,79 @@ +{ + "global": { "show_profile_name_in_menu_bar": true }, + "profiles": [ + { + "complex_modifications": { + "rules": [ + { + "description": "Right Option Key → Hyper Key (⌃⌥⇧⌘)", + "manipulators": [ + { + "from": { + "key_code": "right_option", + "modifiers": { "optional": ["any"] } + }, + "to": [ + { + "key_code": "left_shift", + "modifiers": ["left_command", "left_control", "left_option"] + } + ], + "type": "basic" + } + ] + } + ] + }, + "devices": [ + { + "identifiers": { + "is_keyboard": true, + "product_id": 1957, + "vendor_id": 1118 + }, + "simple_modifications": [ + { + "from": { "key_code": "left_command" }, + "to": [{ "key_code": "left_option" }] + }, + { + "from": { "key_code": "left_option" }, + "to": [{ "key_code": "left_command" }] + } + ] + }, + { + "identifiers": { + "is_keyboard": true, + "product_id": 50489, + "vendor_id": 1133 + }, + "ignore": true + }, + { + "identifiers": { + "is_keyboard": true, + "product_id": 49291, + "vendor_id": 1133 + }, + "ignore": true + } + ], + "fn_function_keys": [ + { + "from": { "key_code": "f4" }, + "to": [{ "consumer_key_code": "al_dictionary" }] + } + ], + "name": "MPK", + "selected": true, + "simple_modifications": [ + { + "from": { "key_code": "caps_lock" }, + "to": [{ "key_code": "escape" }] + } + ], + "virtual_hid_keyboard": { "keyboard_type_v2": "ansi" } + } + ] +} \ No newline at end of file diff --git a/linux-apt-package-install.sh b/linux-apt-package-install.sh new file mode 100755 index 00000000000..85b0b23e9a3 --- /dev/null +++ b/linux-apt-package-install.sh @@ -0,0 +1,92 @@ +#! /bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/tools/discover_sets.sh" + +# parse_args: sets MODE and PACKAGES_TO_INSTALL from YAML config. +# Returns 0 on success, 1 for help/list, 2 for invalid arg, 3 for platform skip. +parse_args() { + if [ "$1" = "--list" ]; then + discover_sets "$SCRIPT_DIR" || return 2 + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 1 + fi + + if [ "$1" = "--help" ] || [ "$1" = "-h" ] || [ -z "$1" ]; then + discover_sets "$SCRIPT_DIR" || return 2 + echo "Usage: linux-apt-package-install.sh [--help|-h|--list] " + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 1 + fi + + discover_sets "$SCRIPT_DIR" || return 2 + + if ! is_valid_set "$1"; then + echo "Invalid option: $1" + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 2 + fi + + # Check this set has linux config + check_set_platform "$SCRIPT_DIR" "$1" "linux" + if [ "$HAS_PLATFORM" != "true" ]; then + echo "Set '$1' has no Linux package configuration. Nothing to install." + return 3 + fi + + if ! validate_configs "$SCRIPT_DIR"; then + echo "Config validation failed. Aborting." >&2 + return 2 + fi + + MODE="$1" + eval "$(python3 "$SCRIPT_DIR/tools/load_config.py" --set "$MODE" --platform linux)" + return 0 +} + +# detect_privilege: sets SUDO and PRIV_MODE based on uid. +detect_privilege() { + if [ "$(id -u)" -eq 0 ]; then + SUDO="" + PRIV_MODE="root (no sudo required)" + else + SUDO="sudo" + PRIV_MODE="non-root (sudo will be used where required)" + fi +} + +# execute_install: runs apt-get update/upgrade, installs packages, starship, autoremove. +execute_install() { + echo "Privilege mode: $PRIV_MODE" + echo "Installing packages for $MODE mode..." + + $SUDO apt-get update + $SUDO apt-get upgrade -y + + for package in "${PACKAGES_TO_INSTALL[@]}"; do + if ! apt list --installed 2>/dev/null | grep -q "^${package}/"; then + $SUDO apt-get install -y "$package" + else + echo "$package is already installed." + fi + done + + # install or update starship + curl -sS https://starship.rs/install.sh | sh + # Remove outdated versions from the cellar. + $SUDO apt-get autoremove -y +} + +main() { + parse_args "$@" + local rc=$? + if [ $rc -eq 1 ]; then exit 0; fi + if [ $rc -eq 2 ]; then exit 1; fi + if [ $rc -eq 3 ]; then exit 0; fi + detect_privilege + execute_install +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/nvim/.config/nvim/.gitignore b/nvim/.config/nvim/.gitignore new file mode 100644 index 00000000000..cc5457ab807 --- /dev/null +++ b/nvim/.config/nvim/.gitignore @@ -0,0 +1,8 @@ +tt.* +.tests +doc/tags +debug +.repro +foo.* +*.log +data diff --git a/nvim/.config/nvim/.neoconf.json b/nvim/.config/nvim/.neoconf.json new file mode 100644 index 00000000000..7c480874658 --- /dev/null +++ b/nvim/.config/nvim/.neoconf.json @@ -0,0 +1,15 @@ +{ + "neodev": { + "library": { + "enabled": true, + "plugins": true + } + }, + "neoconf": { + "plugins": { + "lua_ls": { + "enabled": true + } + } + } +} diff --git a/nvim/.config/nvim/LICENSE b/nvim/.config/nvim/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/nvim/.config/nvim/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/nvim/.config/nvim/README.md b/nvim/.config/nvim/README.md new file mode 100644 index 00000000000..185280b0165 --- /dev/null +++ b/nvim/.config/nvim/README.md @@ -0,0 +1,4 @@ +# 💤 LazyVim + +A starter template for [LazyVim](https://github.com/LazyVim/LazyVim). +Refer to the [documentation](https://lazyvim.github.io/installation) to get started. diff --git a/nvim/.config/nvim/init.lua b/nvim/.config/nvim/init.lua new file mode 100644 index 00000000000..545f5925bb7 --- /dev/null +++ b/nvim/.config/nvim/init.lua @@ -0,0 +1,6 @@ +-- bootstrap lazy.nvim, LazyVim and your plugins +require("config.lazy") + +require("lazy").setup({ + {"nvim-treesitter/nvim-treesitter", branch = 'master', lazy = false, build = ":TSUpdate"} +}) diff --git a/nvim/.config/nvim/lazy-lock.json b/nvim/.config/nvim/lazy-lock.json new file mode 100644 index 00000000000..41f85f239ed --- /dev/null +++ b/nvim/.config/nvim/lazy-lock.json @@ -0,0 +1,34 @@ +{ + "LazyVim": { "branch": "main", "commit": "060e6dfaf7d4157b1a144df7d83179640dc52400" }, + "blink.cmp": { "branch": "main", "commit": "327fff91fe6af358e990be7be1ec8b78037d2138" }, + "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, + "catppuccin": { "branch": "main", "commit": "c89184526212e04feffbddda9d06b041a8fca416" }, + "conform.nvim": { "branch": "master", "commit": "c64cc754ace603e185ab30113aaef174187eacf8" }, + "flash.nvim": { "branch": "main", "commit": "b68bda044d68e4026c4e1ec6df3c5afd7eb8e341" }, + "friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" }, + "gitsigns.nvim": { "branch": "main", "commit": "1ee5c1fd068c81f9dd06483e639c2aa4587dc197" }, + "grug-far.nvim": { "branch": "main", "commit": "48f9afb684de1c191af7bed96bc1db85ba33f6a4" }, + "lazy.nvim": { "branch": "main", "commit": "6c3bda4aca61a13a9c63f1c1d1b16b9d3be90d7a" }, + "lazydev.nvim": { "branch": "main", "commit": "258d2a5ef4a3e3d6d9ba9da72c9725c53e9afcbd" }, + "lualine.nvim": { "branch": "master", "commit": "b8c23159c0161f4b89196f74ee3a6d02cdc3a955" }, + "mason-lspconfig.nvim": { "branch": "main", "commit": "155eac5d8609a2f110041f8ac3491664cc126354" }, + "mason.nvim": { "branch": "main", "commit": "ad7146aa61dcaeb54fa900144d768f040090bff0" }, + "mini.ai": { "branch": "main", "commit": "e0d00c227112e942ed2789dd4c21d651002831c0" }, + "mini.icons": { "branch": "main", "commit": "e8fae66cb400744daeedf6e387347df50271c252" }, + "mini.pairs": { "branch": "main", "commit": "bada72fe4ec607f882a098d15aa4a3279bc6883d" }, + "noice.nvim": { "branch": "main", "commit": "0427460c2d7f673ad60eb02b35f5e9926cf67c59" }, + "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, + "nvim-lint": { "branch": "master", "commit": "335a6044be16d7701001059cba9baa36fbeef422" }, + "nvim-lspconfig": { "branch": "master", "commit": "db8fef885009fdec0daeff3e5dda92e1f539611e" }, + "nvim-treesitter": { "branch": "main", "commit": "77362027f7aa850c87419fd571151e76b0b342a6" }, + "nvim-treesitter-textobjects": { "branch": "main", "commit": "1b2d85d3de6114c4bcea89ffb2cd1ce9e3a19931" }, + "nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" }, + "persistence.nvim": { "branch": "main", "commit": "166a79a55bfa7a4db3e26fc031b4d92af71d0b51" }, + "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, + "snacks.nvim": { "branch": "main", "commit": "bfe8c26dbd83f7c4fbc222787552e29b4eccfcc0" }, + "todo-comments.nvim": { "branch": "main", "commit": "304a8d204ee787d2544d8bc23cd38d2f929e7cc5" }, + "tokyonight.nvim": { "branch": "main", "commit": "4d159616aee17796c2c94d2f5f87d2ee1a3f67c7" }, + "trouble.nvim": { "branch": "main", "commit": "f176232e7759c4f8abd923c21e3e5a5c76cd6837" }, + "ts-comments.nvim": { "branch": "main", "commit": "1bd9d0ba1d8b336c3db50692ffd0955fe1bb9f0c" }, + "which-key.nvim": { "branch": "main", "commit": "904308e6885bbb7b60714c80ab3daf0c071c1492" } +} diff --git a/nvim/.config/nvim/lazyvim.json b/nvim/.config/nvim/lazyvim.json new file mode 100644 index 00000000000..6206f7edcc5 --- /dev/null +++ b/nvim/.config/nvim/lazyvim.json @@ -0,0 +1,10 @@ +{ + "extras": [ + + ], + "install_version": 8, + "news": { + "NEWS.md": "10960" + }, + "version": 8 +} \ No newline at end of file diff --git a/nvim/.config/nvim/lua/config/autocmds.lua b/nvim/.config/nvim/lua/config/autocmds.lua new file mode 100644 index 00000000000..4221e75867a --- /dev/null +++ b/nvim/.config/nvim/lua/config/autocmds.lua @@ -0,0 +1,8 @@ +-- Autocmds are automatically loaded on the VeryLazy event +-- Default autocmds that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/autocmds.lua +-- +-- Add any additional autocmds here +-- with `vim.api.nvim_create_autocmd` +-- +-- Or remove existing autocmds by their group name (which is prefixed with `lazyvim_` for the defaults) +-- e.g. vim.api.nvim_del_augroup_by_name("lazyvim_wrap_spell") diff --git a/nvim/.config/nvim/lua/config/keymaps.lua b/nvim/.config/nvim/lua/config/keymaps.lua new file mode 100644 index 00000000000..2c134f7bc11 --- /dev/null +++ b/nvim/.config/nvim/lua/config/keymaps.lua @@ -0,0 +1,3 @@ +-- Keymaps are automatically loaded on the VeryLazy event +-- Default keymaps that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/keymaps.lua +-- Add any additional keymaps here diff --git a/nvim/.config/nvim/lua/config/lazy.lua b/nvim/.config/nvim/lua/config/lazy.lua new file mode 100644 index 00000000000..6f58105fe06 --- /dev/null +++ b/nvim/.config/nvim/lua/config/lazy.lua @@ -0,0 +1,53 @@ +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not (vim.uv or vim.loop).fs_stat(lazypath) then + local lazyrepo = "https://github.com/folke/lazy.nvim.git" + local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) + if vim.v.shell_error ~= 0 then + vim.api.nvim_echo({ + { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, + { out, "WarningMsg" }, + { "\nPress any key to exit..." }, + }, true, {}) + vim.fn.getchar() + os.exit(1) + end +end +vim.opt.rtp:prepend(lazypath) + +require("lazy").setup({ + spec = { + -- add LazyVim and import its plugins + { "LazyVim/LazyVim", import = "lazyvim.plugins" }, + -- import/override with your plugins + { import = "plugins" }, + }, + defaults = { + -- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup. + -- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default. + lazy = false, + -- It's recommended to leave version=false for now, since a lot the plugin that support versioning, + -- have outdated releases, which may break your Neovim install. + version = false, -- always use the latest git commit + -- version = "*", -- try installing the latest stable version for plugins that support semver + }, + zX = { colorscheme = { "catppuccin", "habamax" } }, + checker = { + enabled = true, -- check for plugin updates periodically + notify = false, -- notify on update + }, -- automatically check for plugin updates + performance = { + rtp = { + -- disable some rtp plugins + disabled_plugins = { + "gzip", + -- "matchit", + -- "matchparen", + -- "netrwPlugin", + "tarPlugin", + "tohtml", + "tutor", + "zipPlugin", + }, + }, + }, +}) diff --git a/nvim/.config/nvim/lua/config/options.lua b/nvim/.config/nvim/lua/config/options.lua new file mode 100644 index 00000000000..3ea1454fa35 --- /dev/null +++ b/nvim/.config/nvim/lua/config/options.lua @@ -0,0 +1,3 @@ +-- Options are automatically loaded before lazy.nvim startup +-- Default options that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/options.lua +-- Add any additional options here diff --git a/nvim/.config/nvim/lua/plugins/colorscheme.lua b/nvim/.config/nvim/lua/plugins/colorscheme.lua new file mode 100644 index 00000000000..a242afce4c1 --- /dev/null +++ b/nvim/.config/nvim/lua/plugins/colorscheme.lua @@ -0,0 +1,62 @@ +return { + "catppuccin/nvim", + lazy = false, + name = "catppuccin", + opts = { + integrations = { + aerial = true, + alpha = true, + cmp = true, + dashboard = true, + flash = true, + fzf = true, + grug_far = true, + gitsigns = true, + headlines = true, + illuminate = true, + indent_blankline = { enabled = true }, + leap = true, + lsp_trouble = true, + mason = true, + markdown = true, + mini = true, + native_lsp = { + enabled = true, + underlines = { + errors = { "undercurl" }, + hints = { "undercurl" }, + warnings = { "undercurl" }, + information = { "undercurl" }, + }, + }, + navic = { enabled = true, custom_bg = "lualine" }, + neotest = true, + neotree = true, + noice = true, + notify = true, + semantic_tokens = true, + snacks = true, + telescope = true, + treesitter = true, + treesitter_context = true, + which_key = true, + }, + }, + specs = { + { + "akinsho/bufferline.nvim", + optional = true, + opts = function(_, opts) + if (vim.g.colors_name or ""):find("catppuccin") then + opts.highlights = require("catppuccin.groups.integrations.bufferline").get_theme() + end + end, + }, + }, + { + "LazyVim/LazyVim", + opts = { + colorscheme = "catppuccin", + }, + } +} \ No newline at end of file diff --git a/nvim/.config/nvim/lua/plugins/example.lua b/nvim/.config/nvim/lua/plugins/example.lua new file mode 100644 index 00000000000..17f53d6f6f2 --- /dev/null +++ b/nvim/.config/nvim/lua/plugins/example.lua @@ -0,0 +1,197 @@ +-- since this is just an example spec, don't actually load anything here and return an empty spec +-- stylua: ignore +if true then return {} end + +-- every spec file under the "plugins" directory will be loaded automatically by lazy.nvim +-- +-- In your plugin files, you can: +-- * add extra plugins +-- * disable/enabled LazyVim plugins +-- * override the configuration of LazyVim plugins +return { + -- add gruvbox + { "ellisonleao/gruvbox.nvim" }, + + -- Configure LazyVim to load gruvbox + { + "LazyVim/LazyVim", + opts = { + colorscheme = "gruvbox", + }, + }, + + -- change trouble config + { + "folke/trouble.nvim", + -- opts will be merged with the parent spec + opts = { use_diagnostic_signs = true }, + }, + + -- disable trouble + { "folke/trouble.nvim", enabled = false }, + + -- override nvim-cmp and add cmp-emoji + { + "hrsh7th/nvim-cmp", + dependencies = { "hrsh7th/cmp-emoji" }, + ---@param opts cmp.ConfigSchema + opts = function(_, opts) + table.insert(opts.sources, { name = "emoji" }) + end, + }, + + -- change some telescope options and a keymap to browse plugin files + { + "nvim-telescope/telescope.nvim", + keys = { + -- add a keymap to browse plugin files + -- stylua: ignore + { + "fp", + function() require("telescope.builtin").find_files({ cwd = require("lazy.core.config").options.root }) end, + desc = "Find Plugin File", + }, + }, + -- change some options + opts = { + defaults = { + layout_strategy = "horizontal", + layout_config = { prompt_position = "top" }, + sorting_strategy = "ascending", + winblend = 0, + }, + }, + }, + + -- add pyright to lspconfig + { + "neovim/nvim-lspconfig", + ---@class PluginLspOpts + opts = { + ---@type lspconfig.options + servers = { + -- pyright will be automatically installed with mason and loaded with lspconfig + pyright = {}, + }, + }, + }, + + -- add tsserver and setup with typescript.nvim instead of lspconfig + { + "neovim/nvim-lspconfig", + dependencies = { + "jose-elias-alvarez/typescript.nvim", + init = function() + require("lazyvim.util").lsp.on_attach(function(_, buffer) + -- stylua: ignore + vim.keymap.set( "n", "co", "TypescriptOrganizeImports", { buffer = buffer, desc = "Organize Imports" }) + vim.keymap.set("n", "cR", "TypescriptRenameFile", { desc = "Rename File", buffer = buffer }) + end) + end, + }, + ---@class PluginLspOpts + opts = { + ---@type lspconfig.options + servers = { + -- tsserver will be automatically installed with mason and loaded with lspconfig + tsserver = {}, + }, + -- you can do any additional lsp server setup here + -- return true if you don't want this server to be setup with lspconfig + ---@type table + setup = { + -- example to setup with typescript.nvim + tsserver = function(_, opts) + require("typescript").setup({ server = opts }) + return true + end, + -- Specify * to use this function as a fallback for any server + -- ["*"] = function(server, opts) end, + }, + }, + }, + + -- for typescript, LazyVim also includes extra specs to properly setup lspconfig, + -- treesitter, mason and typescript.nvim. So instead of the above, you can use: + { import = "lazyvim.plugins.extras.lang.typescript" }, + + -- add more treesitter parsers + { + "nvim-treesitter/nvim-treesitter", + opts = { + ensure_installed = { + "bash", + "html", + "javascript", + "json", + "lua", + "markdown", + "markdown_inline", + "python", + "query", + "regex", + "tsx", + "typescript", + "vim", + "yaml", + }, + }, + }, + + -- since `vim.tbl_deep_extend`, can only merge tables and not lists, the code above + -- would overwrite `ensure_installed` with the new value. + -- If you'd rather extend the default config, use the code below instead: + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + -- add tsx and treesitter + vim.list_extend(opts.ensure_installed, { + "tsx", + "typescript", + }) + end, + }, + + -- the opts function can also be used to change the default opts: + { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + opts = function(_, opts) + table.insert(opts.sections.lualine_x, { + function() + return "😄" + end, + }) + end, + }, + + -- or you can return new options to override all the defaults + { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + opts = function() + return { + --[[add your custom lualine config here]] + } + end, + }, + + -- use mini.starter instead of alpha + { import = "lazyvim.plugins.extras.ui.mini-starter" }, + + -- add jsonls and schemastore packages, and setup treesitter for json, json5 and jsonc + { import = "lazyvim.plugins.extras.lang.json" }, + + -- add any tools you want to have installed below + { + "williamboman/mason.nvim", + opts = { + ensure_installed = { + "stylua", + "shellcheck", + "shfmt", + "flake8", + }, + }, + }, +} diff --git a/nvim/.config/nvim/stylua.toml b/nvim/.config/nvim/stylua.toml new file mode 100644 index 00000000000..5d6c50dce95 --- /dev/null +++ b/nvim/.config/nvim/stylua.toml @@ -0,0 +1,3 @@ +indent_type = "Spaces" +indent_width = 2 +column_width = 120 \ No newline at end of file diff --git a/oh-my-zsh/dot-oh-my-zsh b/oh-my-zsh/dot-oh-my-zsh new file mode 160000 index 00000000000..1e3abc123f6 --- /dev/null +++ b/oh-my-zsh/dot-oh-my-zsh @@ -0,0 +1 @@ +Subproject commit 1e3abc123f690c9bdd416e8224f1beb47c96f1c7 diff --git a/osx-package-install.sh b/osx-package-install.sh new file mode 100755 index 00000000000..826964a0c20 --- /dev/null +++ b/osx-package-install.sh @@ -0,0 +1,114 @@ +#! /bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/tools/discover_sets.sh" + +# ensure_brew: checks for brew, installs if missing, runs update/upgrade. +ensure_brew() { + if ! command -v brew &>/dev/null; then + echo "Homebrew is not installed. Installing Homebrew..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + + # Make sure we're using the latest Homebrew. + brew update + + # Upgrade any already-installed formulae. + brew upgrade + + # Save Homebrew's installed location. + BREW_PREFIX=$(brew --prefix) +} + +# parse_args: sets MODE, FORMULAE_TO_INSTALL, CASKS_TO_INSTALL from YAML config. +# Returns 0 on success, 1 for help/list, 2 for invalid arg, 3 for platform skip. +parse_args() { + if [ "$1" = "--list" ]; then + discover_sets "$SCRIPT_DIR" || return 2 + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 1 + fi + + if [ "$1" = "--help" ] || [ "$1" = "-h" ] || [ -z "$1" ]; then + discover_sets "$SCRIPT_DIR" || return 2 + echo "Usage: osx-package-install.sh [--help|-h|--list] " + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 1 + fi + + discover_sets "$SCRIPT_DIR" || return 2 + + if ! is_valid_set "$1"; then + echo "Invalid option: $1" + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 2 + fi + + # Check if this set supports macOS + check_set_platform "$SCRIPT_DIR" "$1" "macos" + if [ "$HAS_PLATFORM" != "true" ]; then + echo "Set '$1' is not applicable on macOS (no macos configuration). Skipping." + return 3 + fi + + if ! validate_configs "$SCRIPT_DIR"; then + echo "Config validation failed. Aborting." >&2 + return 2 + fi + + MODE="$1" + eval "$(python3 "$SCRIPT_DIR/tools/load_config.py" --set "$MODE" --platform macos --type formulae)" + eval "$(python3 "$SCRIPT_DIR/tools/load_config.py" --set "$MODE" --platform macos --type casks)" + return 0 +} + +# execute_install: installs formulae and casks, then post-install steps. +execute_install() { + echo "Installing packages for $MODE mode..." + + for package in "${FORMULAE_TO_INSTALL[@]}"; do + if ! brew list --formula | grep -q "^$package$"; then + brew install "$package" + else + echo "$package is already installed." + fi + done + + for cask in "${CASKS_TO_INSTALL[@]}"; do + if ! brew list --cask | grep -q "^$cask$"; then + brew install --cask "$cask" + else + echo "$cask is already installed." + fi + done + + # Create symlink for sha256sum + if [ ! -L "${BREW_PREFIX}/bin/sha256sum" ]; then + ln -s "${BREW_PREFIX}/bin/gsha256sum" "${BREW_PREFIX}/bin/sha256sum" + fi + + # Switch to using brew-installed zsh as default shell + if ! fgrep -q "${BREW_PREFIX}/bin/zsh" /etc/shells; then + echo "${BREW_PREFIX}/bin/zsh" | sudo tee -a /etc/shells + chsh -s "${BREW_PREFIX}/bin/zsh" + fi + + echo 'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"' >>~/.zshrc + + # Remove outdated versions from the cellar. + brew cleanup +} + +main() { + parse_args "$@" + local rc=$? + if [ $rc -eq 1 ]; then exit 0; fi + if [ $rc -eq 2 ]; then exit 1; fi + if [ $rc -eq 3 ]; then exit 0; fi + ensure_brew + execute_install +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/shell.sh b/shell.sh new file mode 100644 index 00000000000..718f4a01c89 --- /dev/null +++ b/shell.sh @@ -0,0 +1,10 @@ +# install the Homebrew version of zsh and permit it to be a default shell +sudo sh -c "echo '/opt/homebrew/bin/zsh' >> /etc/shells && chsh -s /opt/homebrew/bin/zsh" + +# install Oh My Zsh and upgrade +sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" +omz update + +# fix permissions on /opt/homebrew/share directories +chmod g-w,o-w /opt/homebrew/bin/zsh +# chmod g-w,o-w /opt/homebrew/bin/zsh/site-functions diff --git a/ssh/dot-ssh/config b/ssh/dot-ssh/config new file mode 100644 index 00000000000..108785057a6 --- /dev/null +++ b/ssh/dot-ssh/config @@ -0,0 +1,3 @@ +Host * + IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock" + diff --git a/starship/dot-config/starship.toml b/starship/dot-config/starship.toml new file mode 100644 index 00000000000..3f968a049ef --- /dev/null +++ b/starship/dot-config/starship.toml @@ -0,0 +1,176 @@ +"$schema" = 'https://starship.rs/config-schema.json' + +format = """ +[](bg:color_bg2 fg:color_10k_lightgray)\ +$os\ +$username\ +[](bg:color_10k_blue fg:color_10k_lightgray)\ +$directory\ +[](fg:color_10k_blue bg:color_aqua)\ +$git_branch\ +$git_status\ +[](fg:color_aqua bg:color_bg2)\ +$fill\ +[](fg:color_bg1 bg:color_bg2)\ +$time\ +[ ](fg:color_bg1)\ +$line_break$character""" + +palette = 'gruvbox_dark' + +[palettes.gruvbox_dark] +color_fg0 = '#fbf1c7' +color_bg1 = '#3c3836' +color_bg2 = '#282c34' +color_bg3 = '#665c54' +color_10k_darkgray = '#303030' +color_10k_medgray = '#454545' +color_10k_lightgray = '#606060' +color_10k_blue = '#4c74b9' +color_blue = '#458588' +color_aqua = '#689d6a' +color_green = '#98971a' +color_orange = '#d65d0e' +color_purple = '#b16286' +color_red = '#cc241d' +color_yellow = '#d79921' + +[os] +disabled = false +style = "bg:color_10k_lightgray fg:color_fg0" + +[os.symbols] +Windows = "󰍲" +Ubuntu = "󰕈" +SUSE = "" +Raspbian = "󰐿" +Mint = "󰣭" +Macos = "󰀵" +Manjaro = "" +Linux = "󰌽" +Gentoo = "󰣨" +Fedora = "󰣛" +Alpine = "" +Amazon = "" +Android = "" +Arch = "󰣇" +Artix = "󰣇" +EndeavourOS = "" +CentOS = "" +Debian = "󰣚" +Redhat = "󱄛" +RedHatEnterprise = "󱄛" +Pop = "" + +[fill] +symbol = "·" +style = "fg:color_bg3" + +[username] +show_always = true +style_user = "bg:color_10k_lightgray fg:color_fg0" +style_root = "bg:color_red fg:color_fg0" +format = '[ $user ]($style)' + +[directory] +style = "fg:color_fg0 bg:color_10k_blue" +format = "[ $path ]($style)" +truncation_length = 3 +truncation_symbol = "…/" + +[directory.substitutions] +"Documents" = "󰈙 " +"Downloads" = " " +"Music" = "󰝚 " +"Pictures" = " " +"Developer" = "󰲋 " + +[git_branch] +symbol = "" +style = "bg:color_aqua" +format = '[[ $symbol $branch ](fg:color_fg0 bg:color_aqua)]($style)' + +[git_status] +style = "bg:color_aqua" +format = '[[($all_status$ahead_behind )](fg:color_fg0 bg:color_aqua)]($style)' + +[nodejs] +symbol = "" +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[c] +symbol = " " +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[cpp] +symbol = " " +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[rust] +symbol = "" +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[golang] +symbol = "" +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[php] +symbol = "" +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[java] +symbol = "" +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[kotlin] +symbol = "" +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[haskell] +symbol = "" +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[python] +symbol = "" +style = "bg:color_blue" +format = '[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)' + +[docker_context] +symbol = "" +style = "bg:color_bg3" +format = '[[ $symbol( $context) ](fg:#83a598 bg:color_bg3)]($style)' + +[conda] +style = "bg:color_bg3" +format = '[[ $symbol( $environment) ](fg:#83a598 bg:color_bg3)]($style)' + +[pixi] +style = "bg:color_bg3" +format = '[[ $symbol( $version)( $environment) ](fg:color_fg0 bg:color_bg3)]($style)' + +[time] +disabled = false +time_format = "%R" +style = "bg:color_bg1" +format = '[[  $time ](fg:color_fg0 bg:color_bg1)]($style)' + +[line_break] +disabled = false + +[character] +disabled = false +success_symbol = '[](bold fg:color_green)' +error_symbol = '[](bold fg:color_red)' +vimcmd_symbol = '[](bold fg:color_green)' +vimcmd_replace_one_symbol = '[](bold fg:color_purple)' +vimcmd_replace_symbol = '[](bold fg:color_purple)' +vimcmd_visual_symbol = '[](bold fg:color_yellow)' diff --git a/stow-packages.sh b/stow-packages.sh new file mode 100755 index 00000000000..13cbedb45be --- /dev/null +++ b/stow-packages.sh @@ -0,0 +1,74 @@ +#! /bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/tools/discover_sets.sh" + +# parse_args: sets MODE and PACKAGE array from YAML config. +# Returns 0 on success, 1 for help/list, 2 for invalid arg. +parse_args() { + if [[ "$1" == "--list" ]]; then + discover_sets "$SCRIPT_DIR" || return 2 + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 1 + fi + + if [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]] || [[ -z "$1" ]]; then + discover_sets "$SCRIPT_DIR" || return 2 + echo "Usage: stow-packages.sh [--help|-h|--list] " + echo "This script uses GNU Stow to symlink dotfiles from the stow-packages directory to the home directory." + echo "Each subdirectory in stow-packages represents a package of dotfiles to be managed." + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 1 + fi + + discover_sets "$SCRIPT_DIR" || return 2 + + if ! is_valid_set "$1"; then + echo "Invalid option: $1" + echo "Available sets: ${AVAILABLE_SETS[*]}" + return 2 + fi + + if ! validate_configs "$SCRIPT_DIR"; then + echo "Config validation failed. Aborting." >&2 + return 2 + fi + + MODE="$1" + eval "$(python3 "$SCRIPT_DIR/tools/load_config.py" --set "$MODE" --type stow)" + return 0 +} + +# detect_privilege: sets PRIV_MODE based on uid. +detect_privilege() { + if [ "$(id -u)" -eq 0 ]; then + PRIV_MODE="root (no sudo required)" + else + PRIV_MODE="non-root (sudo will be used where required)" + fi +} + +# execute_stow: runs stow for every package in the PACKAGE array. +execute_stow() { + echo "Privilege mode: $PRIV_MODE" + echo "Stowing packages in $MODE mode..." + for package in "${PACKAGE[@]}"; do + echo "Stowing package: $package" + stow -v -t ~/ --dotfiles "$package" + done + echo "All packages stowed successfully." +} + +main() { + parse_args "$@" + local rc=$? + if [ $rc -eq 1 ]; then exit 0; fi + if [ $rc -eq 2 ]; then exit 1; fi + detect_privilege + execute_stow + exit 0 +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/test/bootstrap.bats b/test/bootstrap.bats new file mode 100644 index 00000000000..cc8dd8f6581 --- /dev/null +++ b/test/bootstrap.bats @@ -0,0 +1,107 @@ +#!/usr/bin/env bats + +setup() { + load test_helper + source "${PROJECT_ROOT}/bootstrap.sh" +} + +# --- Argument parsing --- + +@test "parse_args with --help returns 1" { + run parse_args --help + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with -h returns 1" { + run parse_args -h + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with no argument returns 1" { + run parse_args + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with invalid argument returns 2" { + run parse_args "foobar" + [ "$status" -eq 2 ] + assert_output --partial "Invalid option" +} + +# --- Dynamic set discovery --- + +@test "parse_args help shows available sets dynamically" { + run parse_args --help + assert_output --partial "Available sets:" + assert_output --partial "server" + assert_output --partial "workstation" +} + +@test "parse_args --list returns 1 and shows sets" { + run parse_args --list + [ "$status" -eq 1 ] + assert_output --partial "Available sets:" +} + +@test "parse_args accepts all discovered sets" { + eval "$(python3 "$PROJECT_ROOT/tools/load_config.py" --list-sets)" + for set_name in "${AVAILABLE_SETS[@]}"; do + parse_args "$set_name" + assert_equal "$MODE" "$set_name" + done +} + +# --- Mode setting --- + +@test "parse_args server sets MODE" { + parse_args server + assert_equal "$MODE" "server" +} + +@test "parse_args workstation sets MODE" { + parse_args workstation + assert_equal "$MODE" "workstation" +} + +@test "parse_args iot sets MODE" { + parse_args iot + assert_equal "$MODE" "iot" +} + +@test "parse_args lxc sets MODE" { + parse_args lxc + assert_equal "$MODE" "lxc" +} + +# --- OS detection --- + +@test "detect_os on current platform succeeds" { + detect_os + [[ "$OS_TYPE" == "darwin" ]] || [[ "$OS_TYPE" == "linux" ]] +} + +@test "detect_os with darwin OSTYPE sets OS_TYPE to darwin" { + OSTYPE="darwin23.0" + detect_os + assert_equal "$OS_TYPE" "darwin" +} + +@test "detect_os with linux-gnu OSTYPE sets OS_TYPE to linux" { + OSTYPE="linux-gnu" + detect_os + assert_equal "$OS_TYPE" "linux" +} + +@test "detect_os with unknown OSTYPE returns 1" { + OSTYPE="freebsd" + run detect_os + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Unsupported OS" +} diff --git a/test/discover-sets.bats b/test/discover-sets.bats new file mode 100644 index 00000000000..23369e3ac9e --- /dev/null +++ b/test/discover-sets.bats @@ -0,0 +1,96 @@ +#!/usr/bin/env bats + +setup() { + load test_helper + source "${PROJECT_ROOT}/tools/discover_sets.sh" +} + +# --- discover_sets --- + +@test "discover_sets populates AVAILABLE_SETS with all set files" { + discover_sets "$PROJECT_ROOT" + assert_array_contains AVAILABLE_SETS "iot" + assert_array_contains AVAILABLE_SETS "lxc" + assert_array_contains AVAILABLE_SETS "server" + assert_array_contains AVAILABLE_SETS "workstation" +} + +@test "discover_sets count matches actual yaml file count" { + discover_sets "$PROJECT_ROOT" + local file_count + file_count=$(command ls "$PROJECT_ROOT/config/sets/"*.yaml 2>/dev/null | wc -l | tr -d ' ') + assert_array_length AVAILABLE_SETS "$file_count" +} + +@test "discover_sets with empty sets dir returns 1" { + local tmpdir + tmpdir="$(mktemp -d)" + mkdir -p "$tmpdir/config/sets" + cp "$PROJECT_ROOT/config/packages.yaml" "$tmpdir/config/" + cp -r "$PROJECT_ROOT/config/schema" "$tmpdir/config/" + # Create a minimal tools dir with load_config.py + mkdir -p "$tmpdir/tools" + cp "$PROJECT_ROOT/tools/load_config.py" "$tmpdir/tools/" + run discover_sets "$tmpdir" + assert_failure + rm -rf "$tmpdir" +} + +# --- is_valid_set --- + +@test "is_valid_set returns 0 for existing set" { + discover_sets "$PROJECT_ROOT" + is_valid_set "server" +} + +@test "is_valid_set returns 0 for all discovered sets" { + discover_sets "$PROJECT_ROOT" + for s in "${AVAILABLE_SETS[@]}"; do + is_valid_set "$s" + done +} + +@test "is_valid_set returns 1 for nonexistent set" { + discover_sets "$PROJECT_ROOT" + run is_valid_set "nonexistent" + assert_failure +} + +@test "is_valid_set returns 1 for empty string" { + discover_sets "$PROJECT_ROOT" + run is_valid_set "" + assert_failure +} + +# --- check_set_platform --- + +@test "check_set_platform returns true for server on linux" { + check_set_platform "$PROJECT_ROOT" "server" "linux" + assert_equal "$HAS_PLATFORM" "true" +} + +@test "check_set_platform returns true for server on macos" { + check_set_platform "$PROJECT_ROOT" "server" "macos" + assert_equal "$HAS_PLATFORM" "true" +} + +@test "check_set_platform returns false for iot on macos" { + check_set_platform "$PROJECT_ROOT" "iot" "macos" + assert_equal "$HAS_PLATFORM" "false" +} + +@test "check_set_platform returns false for lxc on macos" { + check_set_platform "$PROJECT_ROOT" "lxc" "macos" + assert_equal "$HAS_PLATFORM" "false" +} + +@test "check_set_platform returns true for iot on linux" { + check_set_platform "$PROJECT_ROOT" "iot" "linux" + assert_equal "$HAS_PLATFORM" "true" +} + +# --- validate_configs --- + +@test "validate_configs passes on valid configs" { + validate_configs "$PROJECT_ROOT" +} diff --git a/test/libs/bats-assert b/test/libs/bats-assert new file mode 160000 index 00000000000..697471b7a89 --- /dev/null +++ b/test/libs/bats-assert @@ -0,0 +1 @@ +Subproject commit 697471b7a89d3ab38571f38c6c7c4b460d1f5e35 diff --git a/test/libs/bats-core b/test/libs/bats-core new file mode 160000 index 00000000000..d9faff0d7bc --- /dev/null +++ b/test/libs/bats-core @@ -0,0 +1 @@ +Subproject commit d9faff0d7bc32e7adebc6552446f978118d3ab3b diff --git a/test/libs/bats-support b/test/libs/bats-support new file mode 160000 index 00000000000..0954abb9925 --- /dev/null +++ b/test/libs/bats-support @@ -0,0 +1 @@ +Subproject commit 0954abb9925cad550424cebca2b99255d4eabe96 diff --git a/test/linux-apt-package-install.bats b/test/linux-apt-package-install.bats new file mode 100644 index 00000000000..ca06ad2dc1c --- /dev/null +++ b/test/linux-apt-package-install.bats @@ -0,0 +1,114 @@ +#!/usr/bin/env bats + +setup() { + load test_helper + source "${PROJECT_ROOT}/linux-apt-package-install.sh" +} + +# --- Argument parsing --- + +@test "parse_args with --help returns 1" { + run parse_args --help + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with -h returns 1" { + run parse_args -h + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with no argument returns 1" { + run parse_args + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with invalid argument returns 2" { + run parse_args "foobar" + [ "$status" -eq 2 ] + assert_output --partial "Invalid option" +} + +# --- Dynamic set discovery --- + +@test "parse_args help shows available sets dynamically" { + run parse_args --help + assert_output --partial "Available sets:" + assert_output --partial "server" + assert_output --partial "workstation" +} + +@test "parse_args --list returns 1 and shows sets" { + run parse_args --list + [ "$status" -eq 1 ] + assert_output --partial "Available sets:" + assert_output --partial "server" +} + +@test "parse_args accepts all discovered sets" { + eval "$(python3 "$PROJECT_ROOT/tools/load_config.py" --list-sets)" + for set_name in "${AVAILABLE_SETS[@]}"; do + run parse_args "$set_name" + # Should be 0 (success) or 3 (platform skip), not 2 (invalid) + [[ "$status" -eq 0 ]] || [[ "$status" -eq 3 ]] + done +} + +# --- Mode setting --- + +@test "parse_args iot sets MODE to iot" { + parse_args iot + assert_equal "$MODE" "iot" +} + +@test "parse_args lxc sets MODE to lxc" { + parse_args lxc + assert_equal "$MODE" "lxc" +} + +@test "parse_args server sets MODE to server" { + parse_args server + assert_equal "$MODE" "server" +} + +@test "parse_args workstation sets MODE to workstation" { + parse_args workstation + assert_equal "$MODE" "workstation" +} + +# --- Privilege detection --- + +@test "detect_privilege as non-root sets SUDO to sudo" { + if [ "$(id -u)" -eq 0 ]; then + skip "running as root" + fi + detect_privilege + assert_equal "$SUDO" "sudo" +} + +@test "detect_privilege as root sets SUDO to empty string" { + id() { echo 0; } + detect_privilege + assert_equal "$SUDO" "" + unset -f id +} + +@test "detect_privilege as non-root sets PRIV_MODE" { + if [ "$(id -u)" -eq 0 ]; then + skip "running as root" + fi + detect_privilege + assert_equal "$PRIV_MODE" "non-root (sudo will be used where required)" +} + +@test "detect_privilege as root sets PRIV_MODE" { + id() { echo 0; } + detect_privilege + assert_equal "$PRIV_MODE" "root (no sudo required)" + unset -f id +} diff --git a/test/load-config.bats b/test/load-config.bats new file mode 100644 index 00000000000..b01decd2a9d --- /dev/null +++ b/test/load-config.bats @@ -0,0 +1,272 @@ +#!/usr/bin/env bats + +setup() { + load test_helper +} + +# Helper to load config output into a bash array +load_packages() { + eval "$(python3 "${PROJECT_ROOT}/tools/load_config.py" "$@")" +} + +# --- list-sets --- + +@test "load_config.py --list-sets outputs AVAILABLE_SETS array" { + load_packages --list-sets + assert_array_contains AVAILABLE_SETS "server" + assert_array_contains AVAILABLE_SETS "workstation" + assert_array_contains AVAILABLE_SETS "iot" + assert_array_contains AVAILABLE_SETS "lxc" +} + +@test "load_config.py --list-sets count matches yaml files" { + load_packages --list-sets + local file_count + file_count=$(command ls "$PROJECT_ROOT/config/sets/"*.yaml 2>/dev/null | wc -l | tr -d ' ') + assert_array_length AVAILABLE_SETS "$file_count" +} + +@test "load_config.py --list-sets with empty dir returns empty array" { + local tmpdir + tmpdir="$(mktemp -d)" + mkdir -p "$tmpdir/sets" + eval "$(python3 "$PROJECT_ROOT/tools/load_config.py" --list-sets --config-dir "$tmpdir")" + assert_array_length AVAILABLE_SETS 0 + rm -rf "$tmpdir" +} + +# --- check-platform --- + +@test "load_config.py --check-platform macos for server returns true" { + eval "$(python3 "$PROJECT_ROOT/tools/load_config.py" --set server --check-platform macos)" + assert_equal "$HAS_PLATFORM" "true" +} + +@test "load_config.py --check-platform macos for iot returns false" { + eval "$(python3 "$PROJECT_ROOT/tools/load_config.py" --set iot --check-platform macos)" + assert_equal "$HAS_PLATFORM" "false" +} + +@test "load_config.py --check-platform macos for lxc returns false" { + eval "$(python3 "$PROJECT_ROOT/tools/load_config.py" --set lxc --check-platform macos)" + assert_equal "$HAS_PLATFORM" "false" +} + +@test "load_config.py --check-platform linux for iot returns true" { + eval "$(python3 "$PROJECT_ROOT/tools/load_config.py" --set iot --check-platform linux)" + assert_equal "$HAS_PLATFORM" "true" +} + +@test "load_config.py --check-platform linux for workstation returns true" { + eval "$(python3 "$PROJECT_ROOT/tools/load_config.py" --set workstation --check-platform linux)" + assert_equal "$HAS_PLATFORM" "true" +} + +# --- Linux IoT --- + +@test "linux iot includes gnu_core_utils packages" { + load_packages --set iot --platform linux + assert_array_contains PACKAGES_TO_INSTALL "coreutils" + assert_array_contains PACKAGES_TO_INSTALL "stow" + assert_array_contains PACKAGES_TO_INSTALL "bash" + assert_array_contains PACKAGES_TO_INSTALL "wget" +} + +@test "linux iot includes basic_tools packages" { + load_packages --set iot --platform linux + assert_array_contains PACKAGES_TO_INSTALL "grep" + assert_array_contains PACKAGES_TO_INSTALL "vim" + assert_array_contains PACKAGES_TO_INSTALL "openssh" +} + +@test "linux iot includes james_tools packages" { + load_packages --set iot --platform linux + assert_array_contains PACKAGES_TO_INSTALL "bat" + assert_array_contains PACKAGES_TO_INSTALL "neovim" + assert_array_contains PACKAGES_TO_INSTALL "zsh" + assert_array_contains PACKAGES_TO_INSTALL "fzf" + assert_array_contains PACKAGES_TO_INSTALL "tmux" +} + +@test "linux iot does NOT include network_security_tools" { + load_packages --set iot --platform linux + assert_array_not_contains PACKAGES_TO_INSTALL "nmap" + assert_array_not_contains PACKAGES_TO_INSTALL "sqlmap" +} + +# --- Linux LXC --- + +@test "linux lxc includes gnu_core_utils packages" { + load_packages --set lxc --platform linux + assert_array_contains PACKAGES_TO_INSTALL "coreutils" + assert_array_contains PACKAGES_TO_INSTALL "stow" +} + +@test "linux lxc includes basic_tools packages" { + load_packages --set lxc --platform linux + assert_array_contains PACKAGES_TO_INSTALL "grep" + assert_array_contains PACKAGES_TO_INSTALL "vim" +} + +@test "linux lxc includes lxc_tools packages" { + load_packages --set lxc --platform linux + assert_array_contains PACKAGES_TO_INSTALL "git" + assert_array_contains PACKAGES_TO_INSTALL "neovim" + assert_array_contains PACKAGES_TO_INSTALL "fastfetch" + assert_array_contains PACKAGES_TO_INSTALL "tmux" +} + +@test "linux lxc does NOT include james_tools-only items" { + load_packages --set lxc --platform linux + assert_array_not_contains PACKAGES_TO_INSTALL "broot" + assert_array_not_contains PACKAGES_TO_INSTALL "yazi" + assert_array_not_contains PACKAGES_TO_INSTALL "thefuck" +} + +# --- Linux Server --- + +@test "linux server includes all expected groups" { + load_packages --set server --platform linux + # gnu_core_utils + assert_array_contains PACKAGES_TO_INSTALL "coreutils" + # basic_tools + assert_array_contains PACKAGES_TO_INSTALL "grep" + # james_tools + assert_array_contains PACKAGES_TO_INSTALL "bat" + # network_security_tools + assert_array_contains PACKAGES_TO_INSTALL "nmap" + # general_utilities + assert_array_contains PACKAGES_TO_INSTALL "tree" +} + +@test "linux server does NOT include nerd_fonts" { + load_packages --set server --platform linux + assert_array_not_contains PACKAGES_TO_INSTALL "font-jetbrains-mono-nerd-font" +} + +# --- Linux Workstation --- + +@test "linux workstation includes everything including nerd_fonts" { + load_packages --set workstation --platform linux + assert_array_contains PACKAGES_TO_INSTALL "coreutils" + assert_array_contains PACKAGES_TO_INSTALL "grep" + assert_array_contains PACKAGES_TO_INSTALL "bat" + assert_array_contains PACKAGES_TO_INSTALL "nmap" + assert_array_contains PACKAGES_TO_INSTALL "tree" + assert_array_contains PACKAGES_TO_INSTALL "font-jetbrains-mono-nerd-font" + assert_array_contains PACKAGES_TO_INSTALL "font-fira-code-nerd-font" +} + +@test "linux workstation is superset of server" { + load_packages --set server --platform linux + local server_packages=("${PACKAGES_TO_INSTALL[@]}") + + load_packages --set workstation --platform linux + for pkg in "${server_packages[@]}"; do + assert_array_contains PACKAGES_TO_INSTALL "$pkg" + done +} + +# --- macOS Server --- + +@test "macos server formulae include expected groups" { + load_packages --set server --platform macos --type formulae + # gnu_core_utils + assert_array_contains FORMULAE_TO_INSTALL "coreutils" + # basic_tools + assert_array_contains FORMULAE_TO_INSTALL "grep" + # james_tools (includes starship on macOS) + assert_array_contains FORMULAE_TO_INSTALL "bat" + assert_array_contains FORMULAE_TO_INSTALL "starship" + # network_security_tools + assert_array_contains FORMULAE_TO_INSTALL "nmap" + # general_utilities + assert_array_contains FORMULAE_TO_INSTALL "git" +} + +@test "macos server casks are empty" { + load_packages --set server --platform macos --type casks + assert_array_length CASKS_TO_INSTALL 0 +} + +# --- macOS Workstation --- + +@test "macos workstation formulae match server" { + load_packages --set server --platform macos --type formulae + local server_formulae=("${FORMULAE_TO_INSTALL[@]}") + + load_packages --set workstation --platform macos --type formulae + for pkg in "${server_formulae[@]}"; do + assert_array_contains FORMULAE_TO_INSTALL "$pkg" + done +} + +@test "macos workstation casks include nerd_fonts and cask_apps" { + load_packages --set workstation --platform macos --type casks + assert_array_contains CASKS_TO_INSTALL "font-jetbrains-mono-nerd-font" + assert_array_contains CASKS_TO_INSTALL "font-fira-code-nerd-font" + assert_array_contains CASKS_TO_INSTALL "iterm2" + assert_array_contains CASKS_TO_INSTALL "wezterm" + assert_array_contains CASKS_TO_INSTALL "docker" + assert_array_contains CASKS_TO_INSTALL "1password" +} + +@test "macos workstation cask count is 14 (2 fonts + 12 apps)" { + load_packages --set workstation --platform macos --type casks + assert_array_length CASKS_TO_INSTALL 14 +} + +# --- Stow packages --- + +@test "stow workstation has 11 packages" { + load_packages --set workstation --type stow + assert_array_contains PACKAGE "basic" + assert_array_contains PACKAGE "config resources" + assert_array_contains PACKAGE "iterm2" + assert_array_contains PACKAGE "oh-my-zsh" + assert_array_contains PACKAGE "wezterm" + assert_array_length PACKAGE 11 +} + +@test "stow server has 8 packages" { + load_packages --set server --type stow + assert_array_contains PACKAGE "basic" + assert_array_contains PACKAGE "git" + assert_array_contains PACKAGE "starship" + assert_array_length PACKAGE 8 +} + +@test "stow server does NOT contain workstation-only items" { + load_packages --set server --type stow + assert_array_not_contains PACKAGE "iterm2" + assert_array_not_contains PACKAGE "wezterm" + assert_array_not_contains PACKAGE "oh-my-zsh" +} + +@test "stow iot has 6 packages" { + load_packages --set iot --type stow + assert_array_contains PACKAGE "basic" + assert_array_contains PACKAGE "nvim" + assert_array_contains PACKAGE "tmux" + assert_array_length PACKAGE 6 +} + +@test "stow iot does NOT contain server/workstation items" { + load_packages --set iot --type stow + assert_array_not_contains PACKAGE "git" + assert_array_not_contains PACKAGE "starship" + assert_array_not_contains PACKAGE "iterm2" +} + +@test "stow lxc matches iot" { + load_packages --set iot --type stow + local iot_packages=("${PACKAGE[@]}") + + load_packages --set lxc --type stow + local lxc_packages=("${PACKAGE[@]}") + + assert_equal "${#iot_packages[@]}" "${#lxc_packages[@]}" + for i in "${!iot_packages[@]}"; do + assert_equal "${iot_packages[$i]}" "${lxc_packages[$i]}" + done +} diff --git a/test/osx-package-install.bats b/test/osx-package-install.bats new file mode 100644 index 00000000000..0bf87552269 --- /dev/null +++ b/test/osx-package-install.bats @@ -0,0 +1,76 @@ +#!/usr/bin/env bats + +setup() { + load test_helper + source "${PROJECT_ROOT}/osx-package-install.sh" +} + +# --- Argument parsing --- + +@test "parse_args with --help returns 1" { + run parse_args --help + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with -h returns 1" { + run parse_args -h + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with no argument returns 1" { + run parse_args + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with invalid argument returns 2" { + run parse_args "foobar" + [ "$status" -eq 2 ] + assert_output --partial "Invalid option" +} + +# --- Dynamic set discovery --- + +@test "parse_args help shows available sets dynamically" { + run parse_args --help + assert_output --partial "Available sets:" + assert_output --partial "server" + assert_output --partial "workstation" +} + +@test "parse_args --list returns 1 and shows sets" { + run parse_args --list + [ "$status" -eq 1 ] + assert_output --partial "Available sets:" +} + +# --- Platform checks --- + +@test "parse_args returns 3 for sets without macos config" { + run parse_args iot + [ "$status" -eq 3 ] + assert_output --partial "not applicable" +} + +@test "parse_args returns 3 for lxc on macos" { + run parse_args lxc + [ "$status" -eq 3 ] + assert_output --partial "not applicable" +} + +# --- Mode setting --- + +@test "parse_args server sets MODE to server" { + parse_args server + assert_equal "$MODE" "server" +} + +@test "parse_args workstation sets MODE to workstation" { + parse_args workstation + assert_equal "$MODE" "workstation" +} diff --git a/test/stow-packages.bats b/test/stow-packages.bats new file mode 100644 index 00000000000..631fdeacd65 --- /dev/null +++ b/test/stow-packages.bats @@ -0,0 +1,96 @@ +#!/usr/bin/env bats + +setup() { + load test_helper + source "${PROJECT_ROOT}/stow-packages.sh" +} + +# --- Argument parsing --- + +@test "parse_args with --help returns 1" { + run parse_args --help + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with -h returns 1" { + run parse_args -h + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with no argument returns 1" { + run parse_args + assert_failure + [ "$status" -eq 1 ] + assert_output --partial "Usage:" +} + +@test "parse_args with invalid argument returns 2" { + run parse_args "foobar" + [ "$status" -eq 2 ] + assert_output --partial "Invalid option" +} + +# --- Dynamic set discovery --- + +@test "parse_args help shows available sets dynamically" { + run parse_args --help + assert_output --partial "Available sets:" + assert_output --partial "server" + assert_output --partial "workstation" +} + +@test "parse_args --list returns 1 and shows sets" { + run parse_args --list + [ "$status" -eq 1 ] + assert_output --partial "Available sets:" +} + +@test "parse_args accepts all discovered sets" { + eval "$(python3 "$PROJECT_ROOT/tools/load_config.py" --list-sets)" + for set_name in "${AVAILABLE_SETS[@]}"; do + parse_args "$set_name" + done +} + +# --- Mode setting --- + +@test "parse_args workstation sets MODE" { + parse_args workstation + assert_equal "$MODE" "workstation" +} + +@test "parse_args server sets MODE" { + parse_args server + assert_equal "$MODE" "server" +} + +@test "parse_args iot sets MODE" { + parse_args iot + assert_equal "$MODE" "iot" +} + +@test "parse_args lxc sets MODE" { + parse_args lxc + assert_equal "$MODE" "lxc" +} + +# --- Privilege detection --- + +@test "detect_privilege as non-root sets PRIV_MODE correctly" { + if [ "$(id -u)" -eq 0 ]; then + skip "running as root" + fi + detect_privilege + assert_equal "$PRIV_MODE" "non-root (sudo will be used where required)" +} + +@test "detect_privilege as root sets PRIV_MODE correctly" { + id() { echo 0; } + detect_privilege + assert_equal "$PRIV_MODE" "root (no sudo required)" + unset -f id +} diff --git a/test/test_helper.bash b/test/test_helper.bash new file mode 100644 index 00000000000..461f265a9d2 --- /dev/null +++ b/test/test_helper.bash @@ -0,0 +1,55 @@ +# test/test_helper.bash +# Common test helper loaded by all .bats files + +# Resolve paths +TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$TEST_DIR/.." && pwd)" + +# Load BATS helper libraries +load "${TEST_DIR}/libs/bats-support/load" +load "${TEST_DIR}/libs/bats-assert/load" + +# assert_array_contains ARRAY_NAME VALUE +# Asserts that the named array contains the given value. +assert_array_contains() { + local arr_name="$1" + local value="$2" + eval "local items=(\"\${${arr_name}[@]}\")" + for item in "${items[@]}"; do + if [[ "$item" == "$value" ]]; then + return 0 + fi + done + echo "Expected array '$arr_name' to contain '$value'" >&2 + eval "echo \"Array contents: \${${arr_name}[*]}\"" >&2 + return 1 +} + +# assert_array_not_contains ARRAY_NAME VALUE +# Asserts that the named array does NOT contain the given value. +assert_array_not_contains() { + local arr_name="$1" + local value="$2" + eval "local items=(\"\${${arr_name}[@]}\")" + for item in "${items[@]}"; do + if [[ "$item" == "$value" ]]; then + echo "Expected array '$arr_name' to NOT contain '$value'" >&2 + return 1 + fi + done + return 0 +} + +# assert_array_length ARRAY_NAME EXPECTED_LENGTH +# Asserts that the named array has exactly the expected number of elements. +assert_array_length() { + local arr_name="$1" + local expected="$2" + eval "local actual=\${#${arr_name}[@]}" + if [[ "$actual" -ne "$expected" ]]; then + echo "Expected array '$arr_name' to have $expected elements, got $actual" >&2 + eval "echo \"Array contents: \${${arr_name}[*]}\"" >&2 + return 1 + fi + return 0 +} diff --git a/test/validate-config.bats b/test/validate-config.bats new file mode 100644 index 00000000000..1395f9c8a61 --- /dev/null +++ b/test/validate-config.bats @@ -0,0 +1,78 @@ +#!/usr/bin/env bats + +setup() { + load test_helper +} + +@test "validate_config.py exits 0 on valid configs" { + run python3 "${PROJECT_ROOT}/tools/validate_config.py" + assert_success + assert_output --partial "All config files valid" +} + +@test "validate_config.py catches invalid group reference" { + # Create a temp set file with a bad group name + local tmpdir + tmpdir="$(mktemp -d)" + cp -r "${PROJECT_ROOT}/config/"* "$tmpdir/" + cat > "$tmpdir/sets/bad.yaml" << 'EOF' +name: bad +stow_packages: + - basic +linux: + groups: + - nonexistent_group +EOF + run python3 "${PROJECT_ROOT}/tools/validate_config.py" --config-dir "$tmpdir" + assert_failure + assert_output --partial "unknown group" + rm -rf "$tmpdir" +} + +@test "validate_config.py catches invalid stow package reference" { + local tmpdir + tmpdir="$(mktemp -d)" + cp -r "${PROJECT_ROOT}/config/"* "$tmpdir/" + cat > "$tmpdir/sets/bad.yaml" << 'EOF' +name: bad +stow_packages: + - nonexistent_package +EOF + run python3 "${PROJECT_ROOT}/tools/validate_config.py" --config-dir "$tmpdir" + assert_failure + assert_output --partial "not in catalog" + rm -rf "$tmpdir" +} + +@test "validate_config.py catches filename-name mismatch" { + local tmpdir + tmpdir="$(mktemp -d)" + cp -r "${PROJECT_ROOT}/config/"* "$tmpdir/" + cat > "$tmpdir/sets/mismatch.yaml" << 'EOF' +name: wrong_name +stow_packages: + - basic +EOF + run python3 "${PROJECT_ROOT}/tools/validate_config.py" --config-dir "$tmpdir" + assert_failure + assert_output --partial "name mismatch" + rm -rf "$tmpdir" +} + +@test "validate_config.py catches macos_only group in linux.groups" { + local tmpdir + tmpdir="$(mktemp -d)" + cp -r "${PROJECT_ROOT}/config/"* "$tmpdir/" + cat > "$tmpdir/sets/bad.yaml" << 'EOF' +name: bad +stow_packages: + - basic +linux: + groups: + - cask_apps +EOF + run python3 "${PROJECT_ROOT}/tools/validate_config.py" --config-dir "$tmpdir" + assert_failure + assert_output --partial "macos_only" + rm -rf "$tmpdir" +} diff --git a/tmux/.config/tmux/tmux.reset.conf b/tmux/.config/tmux/tmux.reset.conf new file mode 100644 index 00000000000..d4e77e43458 --- /dev/null +++ b/tmux/.config/tmux/tmux.reset.conf @@ -0,0 +1,40 @@ +# First remove *all* keybindings +# unbind-key -a +# Now reinsert all the regular tmux keys +bind ^X lock-server +bind ^C new-window -c "$HOME" +bind ^D detach +bind * list-clients + +bind H previous-window +bind L next-window + +bind r command-prompt "rename-window %%" +bind R source-file ~/.config/tmux/tmux.conf +bind ^A last-window +bind ^W list-windows +bind w list-windows +bind z resize-pane -Z +bind ^L refresh-client +bind l refresh-client +bind | split-window +bind s split-window -v -c "#{pane_current_path}" +bind v split-window -h -c "#{pane_current_path}" +bind '"' choose-window +bind h select-pane -L +bind j select-pane -D +bind k select-pane -U +bind l select-pane -R +bind -r -T prefix , resize-pane -L 20 +bind -r -T prefix . resize-pane -R 20 +bind -r -T prefix - resize-pane -D 7 +bind -r -T prefix = resize-pane -U 7 +bind : command-prompt +bind * setw synchronize-panes +bind P set pane-border-status +bind c kill-pane +bind x swap-pane -D +bind S choose-session +bind R source-file ~/.config/tmux/tmux.conf +bind K send-keys "clear"\; send-keys "Enter" +bind-key -T copy-mode-vi v send-keys -X begin-selection \ No newline at end of file diff --git a/tmux/.tmux.conf b/tmux/.tmux.conf new file mode 100644 index 00000000000..8156ec4cf36 --- /dev/null +++ b/tmux/.tmux.conf @@ -0,0 +1,48 @@ +# source-file ~/.config/tmux/tmux.reset.conf +set-option -g default-terminal 'screen-256color' +# set-option -g terminal-overrides ',xterm-256color:RGB' +# +set -g prefix C-b +bind C-b send-prefix + +unbind % +bind | split-window -h +unbind '"' +bind - split-window -v +bind h select-pane -L +bind j select-pane -D +bind k select-pane -U +bind l select-pane -R +set -g history-limit 1000000 # increase history size (from 2,000) +set -g renumber-windows on # renumber all windows when any window is closed +set -g set-clipboard on # use system clipboard +set -g default-terminal "${TERM}" +set-option -g allow-passthrough on # per https://gitlab.com/gnachman/iterm2/-/wikis/tmux-Integration-Best-Practices +set -g pane-active-border-style 'fg=magenta,bg=default' +set -g pane-border-style 'fg=brightblack,bg=default' + +unbind r +bind r source-file ~/.tmux.conf + +bind -r m resize-pane -Z + +set -g mouse on + +set -g @plugin 'tmux-plugins/tpm' +set -g @plugin 'tmux-plugins/tmux-sensible' + +set -g @plugin 'christoomey/vim-tmux-navigator' # for navigating panes and vim/nvim with Ctrl-hjkl +set -g @plugin 'jimeh/tmux-themepack' # to configure tmux theme +set -g @plugin 'tmux-plugins/tmux-resurrect' # persist tmux sessions after computer restart +set -g @plugin 'tmux-plugins/tmux-continuum' # automatically saves sessions for you every 15 minutes + +set -g @resurrect-capture-pane-contents 'on' # allow tmux-ressurect to capture pane contents +set -g @continuum-restore 'on' # enable tmux-continuum functionality + +set -g @plugin '2kabhishek/tmux2k' +set -g @tmux2k-left-plugins "session" +set -g @tmux2k-right-plugins "network time" +set -g @tmux2k-time-format " %a %H:%M " + +# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf) +run '~/.tmux/plugins/tpm/tpm' \ No newline at end of file diff --git a/tools/discover_sets.sh b/tools/discover_sets.sh new file mode 100644 index 00000000000..dded6ababd9 --- /dev/null +++ b/tools/discover_sets.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# tools/discover_sets.sh - Shared functions for dynamic set discovery +# Source this file from scripts: source "$SCRIPT_DIR/tools/discover_sets.sh" + +# discover_sets: populates AVAILABLE_SETS array. +# Args: $1 = project root (SCRIPT_DIR) +# Returns 1 if no sets found. +discover_sets() { + local script_dir="$1" + eval "$(python3 "$script_dir/tools/load_config.py" --list-sets)" + if [ ${#AVAILABLE_SETS[@]} -eq 0 ]; then + echo "Error: No config sets found in config/sets/." >&2 + return 1 + fi + return 0 +} + +# is_valid_set: checks if $1 is in AVAILABLE_SETS. +# Returns 0 if found, 1 if not. +is_valid_set() { + local name="$1" + for s in "${AVAILABLE_SETS[@]}"; do + if [[ "$s" == "$name" ]]; then + return 0 + fi + done + return 1 +} + +# check_set_platform: sets HAS_PLATFORM=true/false for a set+platform. +# Args: $1 = project root, $2 = set name, $3 = platform (linux|macos) +check_set_platform() { + local script_dir="$1" + local set_name="$2" + local platform="$3" + eval "$(python3 "$script_dir/tools/load_config.py" --set "$set_name" --check-platform "$platform")" +} + +# validate_configs: runs validate_config.py, returns its exit code. +# Args: $1 = project root (SCRIPT_DIR) +validate_configs() { + local script_dir="$1" + local output + output="$(python3 "$script_dir/tools/validate_config.py" 2>&1)" + local rc=$? + if [ $rc -ne 0 ]; then + echo "$output" >&2 + fi + return $rc +} diff --git a/tools/load_config.py b/tools/load_config.py new file mode 100644 index 00000000000..52f228c22e6 --- /dev/null +++ b/tools/load_config.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""Reads YAML config and outputs bash variable assignments. + +Usage: + python3 load_config.py --set server --platform linux + python3 load_config.py --set workstation --platform macos --type formulae + python3 load_config.py --set workstation --platform macos --type casks + python3 load_config.py --set server --type stow +""" +import argparse +import glob +import os +import sys + +import yaml + + +def find_config_dir(): + """Find the config directory relative to this script.""" + script_dir = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(os.path.dirname(script_dir), "config") + + +def load_catalog(config_dir): + """Load the master packages.yaml catalog.""" + path = os.path.join(config_dir, "packages.yaml") + with open(path) as f: + return yaml.safe_load(f) + + +def load_set_config(config_dir, set_name): + """Load a set configuration file.""" + path = os.path.join(config_dir, "sets", f"{set_name}.yaml") + with open(path) as f: + return yaml.safe_load(f) + + +def resolve_packages(catalog, group_names, platform=None): + """Resolve group names to a flat list of package names. + + Applies platform_overrides when platform is specified. + """ + packages = [] + for group_name in group_names: + group = catalog["groups"][group_name] + for pkg in group["packages"]: + packages.append(pkg["name"]) + # Apply platform overrides + if platform and "platform_overrides" in group: + overrides = group["platform_overrides"].get(platform, {}) + for pkg in overrides.get("extra_packages", []): + packages.append(pkg["name"]) + return packages + + +def list_sets(config_dir): + """List available set names from config/sets/*.yaml filenames.""" + sets_dir = os.path.join(config_dir, "sets") + set_files = sorted(glob.glob(os.path.join(sets_dir, "*.yaml"))) + return [os.path.splitext(os.path.basename(f))[0] for f in set_files] + + +def check_platform_support(config_dir, set_name, platform): + """Check if a set has non-empty configuration for a given platform.""" + set_config = load_set_config(config_dir, set_name) + plat_config = set_config.get(platform) + if not plat_config or not isinstance(plat_config, dict): + return False + # Check if any group lists are non-empty + return any( + isinstance(v, list) and len(v) > 0 + for v in plat_config.values() + ) + + +def format_bash_array(var_name, values): + """Format a list of values as a bash array assignment.""" + escaped = [] + for v in values: + escaped.append(f'"{v}"') + return f'{var_name}=({" ".join(escaped)})' + + +def main(): + parser = argparse.ArgumentParser(description="Load YAML config as bash variables") + parser.add_argument("--set", help="Set name") + parser.add_argument("--list-sets", action="store_true", + help="List available config sets as a bash array") + parser.add_argument("--check-platform", choices=["linux", "macos"], + help="Check if set supports this platform (outputs HAS_PLATFORM=true/false)") + parser.add_argument("--platform", choices=["linux", "macos"], help="Target platform") + parser.add_argument("--type", choices=["formulae", "casks", "stow"], + help="Output type (default: platform packages)") + parser.add_argument("--config-dir", help="Config directory path (auto-detected if not set)") + args = parser.parse_args() + + config_dir = args.config_dir or find_config_dir() + + if args.list_sets: + names = list_sets(config_dir) + print(format_bash_array("AVAILABLE_SETS", names)) + return + + if not args.set: + print("Error: --set is required unless --list-sets is used", file=sys.stderr) + sys.exit(1) + + if args.check_platform: + has_it = check_platform_support(config_dir, args.set, args.check_platform) + print(f'HAS_PLATFORM={"true" if has_it else "false"}') + return + + catalog = load_catalog(config_dir) + set_config = load_set_config(config_dir, args.set) + + if args.type == "stow": + print(format_bash_array("PACKAGE", set_config["stow_packages"])) + return + + if not args.platform: + print("Error: --platform is required when --type is not 'stow'", file=sys.stderr) + sys.exit(1) + + if args.platform == "linux": + groups = set_config.get("linux", {}).get("groups", []) + packages = resolve_packages(catalog, groups, platform="linux") + print(format_bash_array("PACKAGES_TO_INSTALL", packages)) + + elif args.platform == "macos": + output_type = args.type or "formulae" + if output_type == "formulae": + groups = set_config.get("macos", {}).get("formulae_groups", []) + packages = resolve_packages(catalog, groups, platform="macos") + print(format_bash_array("FORMULAE_TO_INSTALL", packages)) + elif output_type == "casks": + groups = set_config.get("macos", {}).get("cask_groups", []) + packages = resolve_packages(catalog, groups, platform="macos") + print(format_bash_array("CASKS_TO_INSTALL", packages)) + + +if __name__ == "__main__": + main() diff --git a/tools/requirements.txt b/tools/requirements.txt new file mode 100644 index 00000000000..d44ad78a2a8 --- /dev/null +++ b/tools/requirements.txt @@ -0,0 +1,2 @@ +pyyaml>=6.0 +jsonschema>=4.0 diff --git a/tools/validate_config.py b/tools/validate_config.py new file mode 100644 index 00000000000..8d7e6648920 --- /dev/null +++ b/tools/validate_config.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""Validates YAML config files against JSON schemas and cross-validates references. + +Usage: + python3 validate_config.py [--config-dir CONFIG_DIR] +""" +import argparse +import glob +import json +import os +import sys + +import jsonschema +import yaml + + +def find_config_dir(): + """Find the config directory relative to this script.""" + script_dir = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(os.path.dirname(script_dir), "config") + + +def load_json(path): + with open(path) as f: + return json.load(f) + + +def load_yaml(path): + with open(path) as f: + return yaml.safe_load(f) + + +def validate_schema(data, schema, filename): + """Validate data against a JSON schema. Returns list of error strings.""" + errors = [] + validator = jsonschema.Draft202012Validator(schema) + for error in validator.iter_errors(data): + path = ".".join(str(p) for p in error.absolute_path) if error.absolute_path else "(root)" + errors.append(f" {filename}: {path}: {error.message}") + return errors + + +def main(): + parser = argparse.ArgumentParser(description="Validate YAML config files") + parser.add_argument("--config-dir", help="Config directory path (auto-detected if not set)") + args = parser.parse_args() + + config_dir = args.config_dir or find_config_dir() + schema_dir = os.path.join(config_dir, "schema") + errors = [] + + # Load schemas + packages_schema = load_json(os.path.join(schema_dir, "packages-schema.json")) + set_schema = load_json(os.path.join(schema_dir, "set-schema.json")) + + # Validate packages.yaml + packages_path = os.path.join(config_dir, "packages.yaml") + catalog = load_yaml(packages_path) + errors.extend(validate_schema(catalog, packages_schema, "packages.yaml")) + + # Collect valid group names and stow packages from catalog + valid_groups = set(catalog.get("groups", {}).keys()) + valid_stow = set(catalog.get("stow_packages", [])) + macos_only_groups = set() + for group_name, group_def in catalog.get("groups", {}).items(): + if group_def.get("platform") == "macos_only": + macos_only_groups.add(group_name) + + # Validate each set file + sets_dir = os.path.join(config_dir, "sets") + set_files = sorted(glob.glob(os.path.join(sets_dir, "*.yaml"))) + + if not set_files: + errors.append("No set files found in config/sets/") + + for set_path in set_files: + filename = os.path.basename(set_path) + set_config = load_yaml(set_path) + + # Schema validation + errors.extend(validate_schema(set_config, set_schema, filename)) + + # Cross-validate: name field matches filename + expected_name = os.path.splitext(filename)[0] + actual_name = set_config.get("name", "") + if actual_name != expected_name: + errors.append(f" {filename}: name mismatch: file is '{expected_name}' but name field is '{actual_name}'") + + # Cross-validate: stow packages exist in catalog + for pkg in set_config.get("stow_packages", []): + if pkg not in valid_stow: + errors.append(f" {filename}: stow_packages: '{pkg}' not in catalog stow_packages") + + # Cross-validate: linux group names exist + for group in set_config.get("linux", {}).get("groups", []): + if group not in valid_groups: + errors.append(f" {filename}: linux.groups: unknown group '{group}'") + if group in macos_only_groups: + errors.append(f" {filename}: linux.groups: '{group}' is macos_only") + + # Cross-validate: macos group names exist + for group in set_config.get("macos", {}).get("formulae_groups", []): + if group not in valid_groups: + errors.append(f" {filename}: macos.formulae_groups: unknown group '{group}'") + for group in set_config.get("macos", {}).get("cask_groups", []): + if group not in valid_groups: + errors.append(f" {filename}: macos.cask_groups: unknown group '{group}'") + + if errors: + print("Validation FAILED:") + for e in errors: + print(e) + sys.exit(1) + else: + print(f"All config files valid ({len(set_files)} sets validated).") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/.vim/backups/.gitkeep b/vim/.vim/backups/.gitkeep similarity index 100% rename from .vim/backups/.gitkeep rename to vim/.vim/backups/.gitkeep diff --git a/.vim/colors/solarized.vim b/vim/.vim/colors/solarized.vim similarity index 100% rename from .vim/colors/solarized.vim rename to vim/.vim/colors/solarized.vim diff --git a/.vim/swaps/.gitkeep b/vim/.vim/swaps/.gitkeep similarity index 100% rename from .vim/swaps/.gitkeep rename to vim/.vim/swaps/.gitkeep diff --git a/.vim/syntax/json.vim b/vim/.vim/syntax/json.vim similarity index 100% rename from .vim/syntax/json.vim rename to vim/.vim/syntax/json.vim diff --git a/.vim/undo/.gitkeep b/vim/.vim/undo/.gitkeep similarity index 100% rename from .vim/undo/.gitkeep rename to vim/.vim/undo/.gitkeep diff --git a/.vimrc b/vim/.vimrc similarity index 99% rename from .vimrc rename to vim/.vimrc index 25cf0d5264b..94c0daa5898 100644 --- a/.vimrc +++ b/vim/.vimrc @@ -20,7 +20,7 @@ set gdefault " Use UTF-8 without BOM set encoding=utf-8 nobomb " Change mapleader -let mapleader="," +" let mapleader="," " Don’t add empty newlines at the end of files set binary set noeol diff --git a/web/api/main.py b/web/api/main.py new file mode 100644 index 00000000000..4ac35926a65 --- /dev/null +++ b/web/api/main.py @@ -0,0 +1,34 @@ +"""FastAPI application for managing dotfiles configuration.""" +from pathlib import Path + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles + +from routers import catalog, sets, stow + +PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent + +app = FastAPI(title="Dotfiles Config Manager") + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:5173"], + allow_methods=["GET", "POST", "PUT", "DELETE"], + allow_headers=["Content-Type"], +) + +app.include_router(catalog.router, prefix="/api/catalog", tags=["catalog"]) +app.include_router(sets.router, prefix="/api/sets", tags=["sets"]) +app.include_router(stow.router, prefix="/api/stow", tags=["stow"]) + + +@app.get("/api/health") +def health(): + return {"status": "ok"} + + +# Serve frontend build if it exists +frontend_dist = Path(__file__).resolve().parent.parent / "frontend" / "dist" +if frontend_dist.is_dir(): + app.mount("/", StaticFiles(directory=str(frontend_dist), html=True), name="frontend") diff --git a/web/api/requirements.txt b/web/api/requirements.txt new file mode 100644 index 00000000000..c5acddf4ec6 --- /dev/null +++ b/web/api/requirements.txt @@ -0,0 +1,4 @@ +fastapi>=0.109.0 +uvicorn>=0.27.0 +pyyaml>=6.0 +jsonschema>=4.0 diff --git a/web/api/routers/__init__.py b/web/api/routers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/web/api/routers/catalog.py b/web/api/routers/catalog.py new file mode 100644 index 00000000000..d9ef82dff07 --- /dev/null +++ b/web/api/routers/catalog.py @@ -0,0 +1,115 @@ +"""API router for package catalog management.""" +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel + +from services import config_service + +router = APIRouter() + + +@router.get("/") +def get_catalog(): + return config_service.load_catalog() + + +@router.get("/groups") +def list_groups(): + catalog = config_service.load_catalog() + groups = catalog.get("groups", {}) + return [ + { + "name": name, + "description": defn.get("description", ""), + "package_count": len(defn.get("packages", [])), + "platform": defn.get("platform"), + "install_type": defn.get("install_type"), + } + for name, defn in groups.items() + ] + + +@router.get("/groups/{name}") +def get_group(name: str): + catalog = config_service.load_catalog() + groups = catalog.get("groups", {}) + if name not in groups: + raise HTTPException(404, f"Group '{name}' not found") + return {"name": name, **groups[name]} + + +class GroupUpdate(BaseModel): + description: str | None = None + packages: list[dict] | None = None + platform: str | None = None + install_type: str | None = None + platform_overrides: dict | None = None + + +@router.put("/groups/{name}") +def update_group(name: str, body: GroupUpdate): + catalog = config_service.load_catalog() + groups = catalog.get("groups", {}) + if name not in groups: + raise HTTPException(404, f"Group '{name}' not found") + + if body.description is not None: + groups[name]["description"] = body.description + if body.packages is not None: + groups[name]["packages"] = body.packages + if body.platform is not None: + groups[name]["platform"] = body.platform + if body.install_type is not None: + groups[name]["install_type"] = body.install_type + if body.platform_overrides is not None: + groups[name]["platform_overrides"] = body.platform_overrides + + config_service.save_catalog(catalog) + return {"name": name, **groups[name]} + + +class GroupCreate(BaseModel): + name: str + description: str = "" + packages: list[dict] = [] + + +@router.post("/groups") +def create_group(body: GroupCreate): + catalog = config_service.load_catalog() + groups = catalog.get("groups", {}) + if body.name in groups: + raise HTTPException(409, f"Group '{body.name}' already exists") + groups[body.name] = {"description": body.description, "packages": body.packages} + config_service.save_catalog(catalog) + return {"name": body.name, **groups[body.name]} + + +@router.delete("/groups/{name}") +def delete_group(name: str): + catalog = config_service.load_catalog() + groups = catalog.get("groups", {}) + if name not in groups: + raise HTTPException(404, f"Group '{name}' not found") + del groups[name] + config_service.save_catalog(catalog) + return {"deleted": name} + + +class StowPackagesUpdate(BaseModel): + stow_packages: list[str] + + +@router.put("/stow-packages") +def update_stow_packages(body: StowPackagesUpdate): + catalog = config_service.load_catalog() + catalog["stow_packages"] = body.stow_packages + config_service.save_catalog(catalog) + return {"stow_packages": body.stow_packages} + + +@router.post("/validate") +def validate(): + errors = config_service.validate_all() + if errors: + return {"valid": False, "errors": errors} + return {"valid": True, "errors": []} diff --git a/web/api/routers/sets.py b/web/api/routers/sets.py new file mode 100644 index 00000000000..e87165f95a9 --- /dev/null +++ b/web/api/routers/sets.py @@ -0,0 +1,110 @@ +"""API router for configuration set management.""" +from fastapi import APIRouter, HTTPException, Query +from pydantic import BaseModel + +from services import config_service + +router = APIRouter() + + +@router.get("/") +def list_sets(): + return config_service.list_sets() + + +@router.get("/compare") +def compare_sets(sets: str = Query(..., description="Comma-separated set names")): + set_names = [s.strip() for s in sets.split(",") if s.strip()] + if len(set_names) < 1: + raise HTTPException(400, "At least one set name required") + try: + return config_service.compare_sets(set_names) + except FileNotFoundError as e: + raise HTTPException(404, str(e)) + + +@router.get("/{name}") +def get_set(name: str): + try: + return config_service.load_set(name) + except ValueError as e: + raise HTTPException(400, str(e)) + except FileNotFoundError: + raise HTTPException(404, f"Set '{name}' not found") + + +@router.get("/{name}/resolved") +def get_resolved_set(name: str): + try: + return config_service.resolve_set(name) + except ValueError as e: + raise HTTPException(400, str(e)) + except FileNotFoundError: + raise HTTPException(404, f"Set '{name}' not found") + + +class SetCreate(BaseModel): + name: str + description: str = "" + stow_packages: list[str] = [] + linux: dict | None = None + macos: dict | None = None + + +@router.post("/") +def create_set(body: SetCreate): + try: + existing = [s["name"] for s in config_service.list_sets()] + if body.name in existing: + raise HTTPException(409, f"Set '{body.name}' already exists") + + data = {"name": body.name, "description": body.description, "stow_packages": body.stow_packages} + if body.linux: + data["linux"] = body.linux + if body.macos: + data["macos"] = body.macos + + config_service.save_set(body.name, data) + return data + except ValueError as e: + raise HTTPException(400, str(e)) + + +class SetUpdate(BaseModel): + description: str | None = None + stow_packages: list[str] | None = None + linux: dict | None = None + macos: dict | None = None + + +@router.put("/{name}") +def update_set(name: str, body: SetUpdate): + try: + existing = config_service.load_set(name) + except ValueError as e: + raise HTTPException(400, str(e)) + except FileNotFoundError: + raise HTTPException(404, f"Set '{name}' not found") + + if body.description is not None: + existing["description"] = body.description + if body.stow_packages is not None: + existing["stow_packages"] = body.stow_packages + if body.linux is not None: + existing["linux"] = body.linux + if body.macos is not None: + existing["macos"] = body.macos + + config_service.save_set(name, existing) + return existing + + +@router.delete("/{name}") +def delete_set(name: str): + try: + config_service.delete_set(name) + except ValueError as e: + raise HTTPException(400, str(e)) + except FileNotFoundError: + raise HTTPException(404, f"Set '{name}' not found") + return {"deleted": name} diff --git a/web/api/routers/stow.py b/web/api/routers/stow.py new file mode 100644 index 00000000000..104e7d1d69c --- /dev/null +++ b/web/api/routers/stow.py @@ -0,0 +1,87 @@ +"""API router for stow package and file management.""" +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel + +from services import stow_service + +router = APIRouter() + + +@router.get("/packages") +def list_packages(): + return stow_service.list_packages() + + +class PackageCreate(BaseModel): + name: str + + +@router.post("/packages") +def create_package(body: PackageCreate): + try: + stow_service.create_package(body.name) + except ValueError as e: + raise HTTPException(400, str(e)) + except FileExistsError as e: + raise HTTPException(409, str(e)) + return {"created": body.name} + + +@router.delete("/packages/{name}") +def delete_package(name: str): + try: + stow_service.delete_package(name) + except ValueError as e: + raise HTTPException(400, str(e)) + except FileNotFoundError as e: + raise HTTPException(404, str(e)) + except PermissionError as e: + raise HTTPException(403, str(e)) + return {"deleted": name} + + +@router.get("/packages/{name}/files") +def list_files(name: str): + try: + return {"name": name, "files": stow_service.list_files(name)} + except ValueError as e: + raise HTTPException(400, str(e)) + except FileNotFoundError as e: + raise HTTPException(404, str(e)) + + +@router.get("/packages/{name}/files/{file_path:path}") +def read_file(name: str, file_path: str): + try: + content = stow_service.read_file(name, file_path) + except FileNotFoundError as e: + raise HTTPException(404, str(e)) + except ValueError as e: + raise HTTPException(403, str(e)) + return {"path": file_path, "content": content} + + +class FileWrite(BaseModel): + content: str + + +@router.put("/packages/{name}/files/{file_path:path}") +def write_file(name: str, file_path: str, body: FileWrite): + try: + stow_service.write_file(name, file_path, body.content) + except FileNotFoundError as e: + raise HTTPException(404, str(e)) + except ValueError as e: + raise HTTPException(403, str(e)) + return {"path": file_path, "written": True} + + +@router.delete("/packages/{name}/files/{file_path:path}") +def delete_file(name: str, file_path: str): + try: + stow_service.delete_file(name, file_path) + except FileNotFoundError as e: + raise HTTPException(404, str(e)) + except ValueError as e: + raise HTTPException(403, str(e)) + return {"path": file_path, "deleted": True} diff --git a/web/api/services/__init__.py b/web/api/services/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/web/api/services/config_service.py b/web/api/services/config_service.py new file mode 100644 index 00000000000..a177abec0a3 --- /dev/null +++ b/web/api/services/config_service.py @@ -0,0 +1,252 @@ +"""Service for loading, saving, and validating YAML configuration files.""" +import glob +import json +import os +from pathlib import Path +from typing import Any + +import jsonschema +import yaml + +from services.validation import validate_name + +PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent.parent +CONFIG_DIR = PROJECT_ROOT / "config" +SETS_DIR = CONFIG_DIR / "sets" +SCHEMA_DIR = CONFIG_DIR / "schema" + + +def load_yaml(path: Path) -> dict: + with open(path) as f: + return yaml.safe_load(f) or {} + + +def save_yaml(path: Path, data: dict) -> None: + with open(path, "w") as f: + yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True) + + +def load_json(path: Path) -> dict: + with open(path) as f: + return json.load(f) + + +# --- Catalog --- + +def load_catalog() -> dict: + return load_yaml(CONFIG_DIR / "packages.yaml") + + +def save_catalog(data: dict) -> None: + save_yaml(CONFIG_DIR / "packages.yaml", data) + + +# --- Sets --- + +def list_sets() -> list[dict]: + """Return list of {name, description} for each set.""" + results = [] + for path in sorted(SETS_DIR.glob("*.yaml")): + cfg = load_yaml(path) + results.append({ + "name": path.stem, + "description": cfg.get("description", ""), + }) + return results + + +def load_set(name: str) -> dict: + validate_name(name, "set name") + path = SETS_DIR / f"{name}.yaml" + if not path.exists(): + raise FileNotFoundError(f"Set '{name}' not found") + return load_yaml(path) + + +def save_set(name: str, data: dict) -> None: + validate_name(name, "set name") + data["name"] = name + save_yaml(SETS_DIR / f"{name}.yaml", data) + + +def delete_set(name: str) -> None: + validate_name(name, "set name") + path = SETS_DIR / f"{name}.yaml" + if not path.exists(): + raise FileNotFoundError(f"Set '{name}' not found") + path.unlink() + + +# --- Resolution --- + +def resolve_packages(catalog: dict, group_names: list[str], platform: str | None = None) -> list[dict]: + """Resolve group names to a list of {name, group} dicts with platform awareness.""" + packages = [] + for group_name in group_names: + group = catalog["groups"].get(group_name, {}) + for pkg in group.get("packages", []): + packages.append({"name": pkg["name"], "group": group_name}) + if platform and "platform_overrides" in group: + overrides = group["platform_overrides"].get(platform, {}) + for pkg in overrides.get("extra_packages", []): + packages.append({"name": pkg["name"], "group": group_name}) + return packages + + +def resolve_set(name: str) -> dict: + """Resolve a set to its full package lists per platform.""" + catalog = load_catalog() + set_config = load_set(name) + + linux_groups = set_config.get("linux", {}).get("groups", []) + macos_formulae_groups = set_config.get("macos", {}).get("formulae_groups", []) + macos_cask_groups = set_config.get("macos", {}).get("cask_groups", []) + + return { + "name": name, + "linux": resolve_packages(catalog, linux_groups, "linux"), + "macos_formulae": resolve_packages(catalog, macos_formulae_groups, "macos"), + "macos_casks": resolve_packages(catalog, macos_cask_groups, "macos"), + "stow": set_config.get("stow_packages", []), + } + + +def compare_sets(set_names: list[str]) -> dict: + """Build a comparison view: union of all packages across sets, grouped.""" + catalog = load_catalog() + all_groups = catalog.get("groups", {}) + + # Collect which groups each set uses, per platform + set_configs = {} + for name in set_names: + set_configs[name] = load_set(name) + + # Build union of all referenced groups across all sets + all_referenced_groups: set[str] = set() + for cfg in set_configs.values(): + all_referenced_groups.update(cfg.get("linux", {}).get("groups", [])) + all_referenced_groups.update(cfg.get("macos", {}).get("formulae_groups", [])) + all_referenced_groups.update(cfg.get("macos", {}).get("cask_groups", [])) + + # For each group, build package rows with per-set platform info + groups_result = [] + for group_name in all_groups: + if group_name not in all_referenced_groups: + continue + + group_def = all_groups[group_name] + base_packages = [pkg["name"] for pkg in group_def.get("packages", [])] + + # Collect platform override packages + override_packages: dict[str, set[str]] = {"linux": set(), "macos": set()} + if "platform_overrides" in group_def: + for plat in ("linux", "macos"): + extras = group_def["platform_overrides"].get(plat, {}).get("extra_packages", []) + for pkg in extras: + override_packages[plat].add(pkg["name"]) + + # Union of all package names (base + all overrides) + all_pkg_names = list(base_packages) + for plat_pkgs in override_packages.values(): + for p in plat_pkgs: + if p not in all_pkg_names: + all_pkg_names.append(p) + + packages_result = [] + for pkg_name in all_pkg_names: + is_base = pkg_name in base_packages + pkg_sets = {} + for sname, cfg in set_configs.items(): + linux_groups = cfg.get("linux", {}).get("groups", []) + macos_f_groups = cfg.get("macos", {}).get("formulae_groups", []) + macos_c_groups = cfg.get("macos", {}).get("cask_groups", []) + + # Linux: package is present if group in linux.groups AND + # (package is base OR is a linux override) + on_linux = (group_name in linux_groups and + (is_base or pkg_name in override_packages["linux"])) + + # macOS formula: group in formulae_groups + on_macos_formula = (group_name in macos_f_groups and + (is_base or pkg_name in override_packages["macos"])) + + # macOS cask: group in cask_groups + on_macos_cask = (group_name in macos_c_groups and + (is_base or pkg_name in override_packages["macos"])) + + macos_val: str | bool = False + if on_macos_cask: + macos_val = "cask" + elif on_macos_formula: + macos_val = "formula" + + pkg_sets[sname] = {"linux": on_linux, "macos": macos_val} + + packages_result.append({"name": pkg_name, "sets": pkg_sets}) + + groups_result.append({ + "name": group_name, + "description": group_def.get("description", ""), + "packages": packages_result, + }) + + return {"sets": set_names, "groups": groups_result} + + +# --- Validation --- + +def validate_all() -> list[str]: + """Run full validation. Returns list of error strings (empty = valid).""" + errors = [] + + packages_schema = load_json(SCHEMA_DIR / "packages-schema.json") + set_schema = load_json(SCHEMA_DIR / "set-schema.json") + + catalog = load_yaml(CONFIG_DIR / "packages.yaml") + validator = jsonschema.Draft202012Validator(packages_schema) + for error in validator.iter_errors(catalog): + path = ".".join(str(p) for p in error.absolute_path) if error.absolute_path else "(root)" + errors.append(f"packages.yaml: {path}: {error.message}") + + valid_groups = set(catalog.get("groups", {}).keys()) + valid_stow = set(catalog.get("stow_packages", [])) + macos_only_groups = { + g for g, d in catalog.get("groups", {}).items() + if d.get("platform") == "macos_only" + } + + set_files = sorted(SETS_DIR.glob("*.yaml")) + if not set_files: + errors.append("No set files found in config/sets/") + + for set_path in set_files: + filename = set_path.name + set_config = load_yaml(set_path) + + v = jsonschema.Draft202012Validator(set_schema) + for error in v.iter_errors(set_config): + path = ".".join(str(p) for p in error.absolute_path) if error.absolute_path else "(root)" + errors.append(f"{filename}: {path}: {error.message}") + + expected_name = set_path.stem + if set_config.get("name", "") != expected_name: + errors.append(f"{filename}: name mismatch: file is '{expected_name}' but name field is '{set_config.get('name', '')}'") + + for pkg in set_config.get("stow_packages", []): + if pkg not in valid_stow: + errors.append(f"{filename}: stow_packages: '{pkg}' not in catalog stow_packages") + + for group in set_config.get("linux", {}).get("groups", []): + if group not in valid_groups: + errors.append(f"{filename}: linux.groups: unknown group '{group}'") + if group in macos_only_groups: + errors.append(f"{filename}: linux.groups: '{group}' is macos_only") + + for group in set_config.get("macos", {}).get("formulae_groups", []): + if group not in valid_groups: + errors.append(f"{filename}: macos.formulae_groups: unknown group '{group}'") + for group in set_config.get("macos", {}).get("cask_groups", []): + if group not in valid_groups: + errors.append(f"{filename}: macos.cask_groups: unknown group '{group}'") + + return errors diff --git a/web/api/services/stow_service.py b/web/api/services/stow_service.py new file mode 100644 index 00000000000..511b26f92de --- /dev/null +++ b/web/api/services/stow_service.py @@ -0,0 +1,163 @@ +"""Service for filesystem operations on stow package directories.""" +import shutil +from pathlib import Path + +from services.config_service import PROJECT_ROOT, load_catalog, save_catalog +from services.validation import validate_name, MAX_FILE_SIZE + +# Directories at the project root that are NOT stow packages +NON_STOW_DIRS = { + ".claude", ".git", ".github", "config", "test", "tools", "web", +} + + +def _safe_resolve(base: Path, user_path: str) -> Path: + """Resolve a user-provided path and ensure it stays within base directory. + + Raises ValueError if the resolved path escapes the base directory. + """ + full_path = (base / user_path).resolve() + base_resolved = base.resolve() + try: + full_path.relative_to(base_resolved) + except ValueError: + raise ValueError(f"Path '{user_path}' escapes the allowed directory") + return full_path + + +def compute_target(rel_path: str) -> str: + """Convert a stow package-relative path to its home directory target. + + Applies the --dotfiles convention: leading 'dot-' becomes '.' + """ + parts = Path(rel_path).parts + converted = [] + for part in parts: + if part.startswith("dot-"): + converted.append("." + part[4:]) + else: + converted.append(part) + return "~/" + str(Path(*converted)) + + +def list_packages() -> list[dict]: + """List stow package directories at the project root.""" + catalog = load_catalog() + catalog_stow = set(catalog.get("stow_packages", [])) + + packages = [] + for entry in sorted(PROJECT_ROOT.iterdir()): + if not entry.is_dir(): + continue + if entry.name.startswith("."): + continue + if entry.name in NON_STOW_DIRS: + continue + + file_count = sum(1 for _ in entry.rglob("*") if _.is_file()) + packages.append({ + "name": entry.name, + "file_count": file_count, + "in_catalog": entry.name in catalog_stow, + }) + return packages + + +def create_package(name: str) -> None: + """Create a new stow package directory and add it to the catalog.""" + validate_name(name, "package name") + pkg_dir = PROJECT_ROOT / name + if pkg_dir.exists(): + raise FileExistsError(f"Directory '{name}' already exists") + pkg_dir.mkdir() + + catalog = load_catalog() + stow_pkgs = catalog.get("stow_packages", []) + if name not in stow_pkgs: + stow_pkgs.append(name) + stow_pkgs.sort() + catalog["stow_packages"] = stow_pkgs + save_catalog(catalog) + + +def delete_package(name: str) -> None: + """Delete a stow package directory and remove it from the catalog.""" + validate_name(name, "package name") + pkg_dir = PROJECT_ROOT / name + if not pkg_dir.exists(): + raise FileNotFoundError(f"Package '{name}' not found") + if name in NON_STOW_DIRS: + raise PermissionError(f"Cannot delete non-stow directory '{name}'") + + shutil.rmtree(pkg_dir) + + catalog = load_catalog() + stow_pkgs = catalog.get("stow_packages", []) + if name in stow_pkgs: + stow_pkgs.remove(name) + catalog["stow_packages"] = stow_pkgs + save_catalog(catalog) + + +def list_files(package_name: str) -> list[dict]: + """List all files in a stow package with their target paths.""" + validate_name(package_name, "package name") + pkg_dir = PROJECT_ROOT / package_name + if not pkg_dir.exists(): + raise FileNotFoundError(f"Package '{package_name}' not found") + + files = [] + for path in sorted(pkg_dir.rglob("*")): + if path.is_file(): + rel = str(path.relative_to(pkg_dir)) + files.append({ + "path": rel, + "target": compute_target(rel), + "size": path.stat().st_size, + }) + return files + + +def read_file(package_name: str, file_path: str) -> str: + """Read a file's contents from a stow package.""" + validate_name(package_name, "package name") + pkg_dir = PROJECT_ROOT / package_name + full_path = _safe_resolve(pkg_dir, file_path) + if not full_path.exists(): + raise FileNotFoundError(f"File not found: {package_name}/{file_path}") + return full_path.read_text(errors="replace") + + +def write_file(package_name: str, file_path: str, content: str) -> None: + """Create or update a file in a stow package.""" + validate_name(package_name, "package name") + if len(content) > MAX_FILE_SIZE: + raise ValueError(f"File content exceeds maximum size of {MAX_FILE_SIZE} bytes") + + pkg_dir = PROJECT_ROOT / package_name + if not pkg_dir.exists(): + raise FileNotFoundError(f"Package '{package_name}' not found") + + full_path = _safe_resolve(pkg_dir, file_path) + full_path.parent.mkdir(parents=True, exist_ok=True) + full_path.write_text(content) + + +def delete_file(package_name: str, file_path: str) -> None: + """Delete a file from a stow package.""" + validate_name(package_name, "package name") + pkg_dir = PROJECT_ROOT / package_name + full_path = _safe_resolve(pkg_dir, file_path) + if not full_path.exists(): + raise FileNotFoundError(f"File not found: {package_name}/{file_path}") + full_path.unlink() + + # Clean up empty parent directories + parent = full_path.parent + pkg_resolved = pkg_dir.resolve() + while parent != pkg_resolved: + if not any(parent.iterdir()): + parent.rmdir() + parent = parent.parent + else: + break diff --git a/web/api/services/validation.py b/web/api/services/validation.py new file mode 100644 index 00000000000..01f9b2a41c4 --- /dev/null +++ b/web/api/services/validation.py @@ -0,0 +1,22 @@ +"""Input validation helpers for the web API.""" +import re + +# Names must be alphanumeric with hyphens/underscores, 1-64 chars +_SAFE_NAME_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$") + +# Max file content size: 1MB +MAX_FILE_SIZE = 1_048_576 + + +def validate_name(name: str, kind: str = "name") -> str: + """Validate that a name is safe for use in filesystem paths. + + Raises ValueError if the name contains path separators, is empty, + or contains other dangerous characters. + """ + if not name or not _SAFE_NAME_RE.match(name): + raise ValueError( + f"Invalid {kind}: must be 1-64 alphanumeric characters " + f"(hyphens, underscores, dots allowed, must start with alphanumeric)" + ) + return name diff --git a/web/frontend/.gitignore b/web/frontend/.gitignore new file mode 100644 index 00000000000..a547bf36d8d --- /dev/null +++ b/web/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/web/frontend/README.md b/web/frontend/README.md new file mode 100644 index 00000000000..d2e77611fd3 --- /dev/null +++ b/web/frontend/README.md @@ -0,0 +1,73 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/web/frontend/eslint.config.js b/web/frontend/eslint.config.js new file mode 100644 index 00000000000..5e6b472f583 --- /dev/null +++ b/web/frontend/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/web/frontend/index.html b/web/frontend/index.html new file mode 100644 index 00000000000..072a57e8e46 --- /dev/null +++ b/web/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/web/frontend/package-lock.json b/web/frontend/package-lock.json new file mode 100644 index 00000000000..011602e25a6 --- /dev/null +++ b/web/frontend/package-lock.json @@ -0,0 +1,4286 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@codemirror/autocomplete": "^6.20.1", + "@codemirror/commands": "^6.10.2", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/lang-python": "^6.2.1", + "@codemirror/lang-yaml": "^6.1.2", + "@codemirror/language": "^6.12.2", + "@codemirror/search": "^6.6.0", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.39.16", + "codemirror": "^6.0.2", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tailwindcss/vite": "^4.2.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "tailwindcss": "^4.2.1", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz", + "integrity": "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz", + "integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", + "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz", + "integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.5.tgz", + "integrity": "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", + "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.39.16", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.16.tgz", + "integrity": "sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", + "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.1.tgz", + "integrity": "sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/markdown": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz", + "integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@lezer/python": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz", + "integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", + "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", + "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", + "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/web/frontend/package.json b/web/frontend/package.json new file mode 100644 index 00000000000..f668d19e9ef --- /dev/null +++ b/web/frontend/package.json @@ -0,0 +1,44 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@codemirror/autocomplete": "^6.20.1", + "@codemirror/commands": "^6.10.2", + "@codemirror/lang-javascript": "^6.2.5", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/lang-python": "^6.2.1", + "@codemirror/lang-yaml": "^6.1.2", + "@codemirror/language": "^6.12.2", + "@codemirror/search": "^6.6.0", + "@codemirror/state": "^6.5.4", + "@codemirror/view": "^6.39.16", + "codemirror": "^6.0.2", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@tailwindcss/vite": "^4.2.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "tailwindcss": "^4.2.1", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1" + } +} diff --git a/web/frontend/public/vite.svg b/web/frontend/public/vite.svg new file mode 100644 index 00000000000..e7b8dfb1b2a --- /dev/null +++ b/web/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/frontend/src/App.tsx b/web/frontend/src/App.tsx new file mode 100644 index 00000000000..6cc52af40d8 --- /dev/null +++ b/web/frontend/src/App.tsx @@ -0,0 +1,39 @@ +import { useState } from 'react'; +import StowPackagesPage from './pages/StowPackagesPage'; +import ConfigSetsPage from './pages/ConfigSetsPage'; + +const tabs = ['Stow Packages', 'Configuration Sets'] as const; +type Tab = (typeof tabs)[number]; + +export default function App() { + const [activeTab, setActiveTab] = useState('Configuration Sets'); + + return ( +
+
+

Dotfiles Config Manager

+
+ +
+ {activeTab === 'Stow Packages' && } + {activeTab === 'Configuration Sets' && } +
+
+ ); +} diff --git a/web/frontend/src/api/client.ts b/web/frontend/src/api/client.ts new file mode 100644 index 00000000000..0ca4a955ecc --- /dev/null +++ b/web/frontend/src/api/client.ts @@ -0,0 +1,66 @@ +const BASE = '/api'; + +async function request(path: string, options?: RequestInit): Promise { + const res = await fetch(`${BASE}${path}`, { + headers: { 'Content-Type': 'application/json' }, + ...options, + }); + if (!res.ok) { + const body = await res.json().catch(() => ({ detail: res.statusText })); + throw new Error(body.detail || res.statusText); + } + return res.json(); +} + +// --- Catalog --- +export const catalog = { + get: () => request('/catalog/'), + groups: () => request('/catalog/groups'), + getGroup: (name: string) => request(`/catalog/groups/${name}`), + updateGroup: (name: string, data: any) => + request(`/catalog/groups/${name}`, { method: 'PUT', body: JSON.stringify(data) }), + createGroup: (data: any) => + request('/catalog/groups', { method: 'POST', body: JSON.stringify(data) }), + deleteGroup: (name: string) => + request(`/catalog/groups/${name}`, { method: 'DELETE' }), + validate: () => + request<{ valid: boolean; errors: string[] }>('/catalog/validate', { method: 'POST' }), +}; + +// --- Sets --- +export const sets = { + list: () => request<{ name: string; description: string }[]>('/sets/'), + get: (name: string) => request(`/sets/${name}`), + resolved: (name: string) => request(`/sets/${name}/resolved`), + compare: (names: string[]) => request(`/sets/compare?sets=${names.join(',')}`), + create: (data: any) => + request('/sets/', { method: 'POST', body: JSON.stringify(data) }), + update: (name: string, data: any) => + request(`/sets/${name}`, { method: 'PUT', body: JSON.stringify(data) }), + delete: (name: string) => + request(`/sets/${name}`, { method: 'DELETE' }), +}; + +// --- Stow --- +export const stow = { + packages: () => request('/stow/packages'), + createPackage: (name: string) => + request('/stow/packages', { method: 'POST', body: JSON.stringify({ name }) }), + deletePackage: (name: string) => + request(`/stow/packages/${name}`, { method: 'DELETE' }), + files: (pkg: string) => + request<{ name: string; files: any[] }>(`/stow/packages/${encodeURIComponent(pkg)}/files`), + readFile: (pkg: string, path: string) => + request<{ path: string; content: string }>( + `/stow/packages/${encodeURIComponent(pkg)}/files/${path}` + ), + writeFile: (pkg: string, path: string, content: string) => + request(`/stow/packages/${encodeURIComponent(pkg)}/files/${path}`, { + method: 'PUT', + body: JSON.stringify({ content }), + }), + deleteFile: (pkg: string, path: string) => + request(`/stow/packages/${encodeURIComponent(pkg)}/files/${path}`, { + method: 'DELETE', + }), +}; diff --git a/web/frontend/src/assets/react.svg b/web/frontend/src/assets/react.svg new file mode 100644 index 00000000000..6c87de9bb33 --- /dev/null +++ b/web/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/frontend/src/components/sets/PackageTable.tsx b/web/frontend/src/components/sets/PackageTable.tsx new file mode 100644 index 00000000000..cb5237add3f --- /dev/null +++ b/web/frontend/src/components/sets/PackageTable.tsx @@ -0,0 +1,210 @@ +import { useState } from 'react'; + +interface PkgSetInfo { + linux: boolean; + macos: string | false; // "formula" | "cask" | false +} + +interface Package { + name: string; + sets: Record; +} + +interface Group { + name: string; + description: string; + packages: Package[]; +} + +interface Comparison { + sets: string[]; + groups: Group[]; +} + +interface Props { + comparison: Comparison; + activeSet: string; + onToggleGroup: (setName: string, groupName: string, platform: 'linux' | 'macos_formulae' | 'macos_cask') => void; +} + +export default function PackageTable({ comparison, activeSet, onToggleGroup }: Props) { + const [collapsed, setCollapsed] = useState>({}); + + const toggleCollapse = (group: string) => { + setCollapsed((prev) => ({ ...prev, [group]: !prev[group] })); + }; + + // Determine per-group platform status for each set + const groupPlatforms = (group: Group, setName: string) => { + const pkgs = group.packages; + if (pkgs.length === 0) return { linux: false, macos: false as string | false }; + // Use first base package to determine group-level platform status + const first = pkgs[0]; + const info = first.sets[setName]; + if (!info) return { linux: false, macos: false as string | false }; + return { linux: info.linux, macos: info.macos }; + }; + + return ( +
+
+ + + + + + {comparison.sets.map((setName) => ( + + ))} + + + + + {comparison.sets.map((setName) => ( + + + + + ))} + + + + {comparison.groups.map((group) => { + const isCollapsed = collapsed[group.name]; + return ( + toggleCollapse(group.name)} + onToggleGroup={onToggleGroup} + groupPlatforms={groupPlatforms} + /> + ); + })} + +
GroupPackage + + {setName} + +
LinuxmacOS
+
+
+ ); +} + +// Need Fragment import +import { Fragment } from 'react'; + +interface GroupRowsProps { + group: Group; + sets: string[]; + activeSet: string; + isCollapsed: boolean; + onToggleCollapse: () => void; + onToggleGroup: (setName: string, groupName: string, platform: 'linux' | 'macos_formulae' | 'macos_cask') => void; + groupPlatforms: (group: Group, setName: string) => { linux: boolean; macos: string | false }; +} + +function GroupRows({ group, sets, activeSet, isCollapsed, onToggleCollapse, onToggleGroup, groupPlatforms }: GroupRowsProps) { + return ( + <> + {/* Group header row */} + + + {isCollapsed ? '▸' : '▾'} + {group.name} + {group.description} + ({group.packages.length}) + + {sets.map((setName) => { + const plat = groupPlatforms(group, setName); + return ( + + + + + +
+ + {plat.macos && ( + + )} +
+ +
+ ); + })} + + {/* Package rows */} + {!isCollapsed && group.packages.map((pkg) => ( + + + {pkg.name} + {sets.map((setName) => { + const info = pkg.sets[setName]; + if (!info) return ( + + + + + ); + return ( + + + {info.linux ? pkg.name : ''} + + + {info.macos ? ( + + {pkg.name} + {info.macos === 'cask' && ( + cask + )} + + ) : ''} + + + ); + })} + + ))} + + ); +} diff --git a/web/frontend/src/components/sets/SetSelector.tsx b/web/frontend/src/components/sets/SetSelector.tsx new file mode 100644 index 00000000000..3bc7099daa1 --- /dev/null +++ b/web/frontend/src/components/sets/SetSelector.tsx @@ -0,0 +1,79 @@ +interface Props { + sets: { name: string; description: string }[]; + activeSet: string | null; + compareSets: string[]; + onSelectActive: (name: string) => void; + onAddCompare: (name: string) => void; + onRemoveCompare: (name: string) => void; + onCreate: () => void; + onDelete: (name: string) => void; +} + +export default function SetSelector({ + sets, activeSet, compareSets, onSelectActive, onAddCompare, onRemoveCompare, onCreate, onDelete, +}: Props) { + const availableForCompare = sets.filter( + (s) => s.name !== activeSet && !compareSets.includes(s.name) + ); + + return ( +
+
+
+ + +
+ + {compareSets.length > 0 && ( +
+ Comparing: + {compareSets.map((name) => ( + + {name} + + + ))} +
+ )} + + {availableForCompare.length > 0 && ( + + )} + +
+ + {activeSet && ( + + )} +
+
+
+ ); +} diff --git a/web/frontend/src/components/stow/FileBrowser.tsx b/web/frontend/src/components/stow/FileBrowser.tsx new file mode 100644 index 00000000000..9df07b62397 --- /dev/null +++ b/web/frontend/src/components/stow/FileBrowser.tsx @@ -0,0 +1,73 @@ +interface FileEntry { + path: string; + target: string; + size: number; +} + +interface Props { + packageName: string; + files: FileEntry[]; + activeFile: string | null; + onOpen: (path: string) => void; + onDelete: (path: string) => void; + onCreate: () => void; +} + +export default function FileBrowser({ packageName, files, activeFile, onOpen, onDelete, onCreate }: Props) { + return ( +
+
+ + Files in {packageName}/ + + +
+
+ + + + + + + + + + {files.map((f) => ( + onOpen(f.path)} + > + + + + + ))} + {files.length === 0 && ( + + + + )} + +
SourceTarget
{f.path}{f.target} + +
+ Empty package. Click "+ New File" to add files. +
+
+
+ ); +} diff --git a/web/frontend/src/components/stow/FileEditor.tsx b/web/frontend/src/components/stow/FileEditor.tsx new file mode 100644 index 00000000000..0bb04b5c533 --- /dev/null +++ b/web/frontend/src/components/stow/FileEditor.tsx @@ -0,0 +1,77 @@ +import { useEffect, useRef, useState } from 'react'; +import { EditorState } from '@codemirror/state'; +import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter } from '@codemirror/view'; +import { defaultKeymap, indentWithTab } from '@codemirror/commands'; +import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; +import { bracketMatching } from '@codemirror/language'; + +interface Props { + path: string; + content: string; + onSave: (path: string, content: string) => void; +} + +export default function FileEditor({ path, content, onSave }: Props) { + const containerRef = useRef(null); + const viewRef = useRef(null); + const [dirty, setDirty] = useState(false); + + useEffect(() => { + if (!containerRef.current) return; + + const state = EditorState.create({ + doc: content, + extensions: [ + lineNumbers(), + highlightActiveLine(), + highlightActiveLineGutter(), + bracketMatching(), + highlightSelectionMatches(), + keymap.of([...defaultKeymap, indentWithTab, ...searchKeymap]), + EditorView.updateListener.of((update) => { + if (update.docChanged) setDirty(true); + }), + EditorView.theme({ + '&': { height: '100%', fontSize: '13px' }, + '.cm-scroller': { overflow: 'auto' }, + '.cm-content': { fontFamily: 'ui-monospace, monospace' }, + }), + ], + }); + + const view = new EditorView({ state, parent: containerRef.current }); + viewRef.current = view; + setDirty(false); + + return () => { view.destroy(); }; + }, [path, content]); + + const handleSave = () => { + if (!viewRef.current) return; + onSave(path, viewRef.current.state.doc.toString()); + setDirty(false); + }; + + return ( +
+
+ + {path} + {dirty && (unsaved)} + + +
+
+
+ ); +} diff --git a/web/frontend/src/components/stow/PackageList.tsx b/web/frontend/src/components/stow/PackageList.tsx new file mode 100644 index 00000000000..e1e01e46a6b --- /dev/null +++ b/web/frontend/src/components/stow/PackageList.tsx @@ -0,0 +1,50 @@ +interface Props { + packages: { name: string; file_count: number; in_catalog: boolean }[]; + selected: string | null; + onSelect: (name: string) => void; + onCreate: () => void; + onDelete: (name: string) => void; +} + +export default function PackageList({ packages, selected, onSelect, onCreate, onDelete }: Props) { + return ( +
+
+ Stow Packages + +
+
+ {packages.map((pkg) => ( +
+
onSelect(pkg.name)}> +
{pkg.name}
+
+ {pkg.file_count} file{pkg.file_count !== 1 ? 's' : ''} + {!pkg.in_catalog && (not in catalog)} +
+
+ +
+ ))} +
+
+ ); +} diff --git a/web/frontend/src/index.css b/web/frontend/src/index.css new file mode 100644 index 00000000000..f1d8c73cdcf --- /dev/null +++ b/web/frontend/src/index.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/web/frontend/src/main.tsx b/web/frontend/src/main.tsx new file mode 100644 index 00000000000..bef5202a32c --- /dev/null +++ b/web/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/web/frontend/src/pages/ConfigSetsPage.tsx b/web/frontend/src/pages/ConfigSetsPage.tsx new file mode 100644 index 00000000000..730f4c7aa91 --- /dev/null +++ b/web/frontend/src/pages/ConfigSetsPage.tsx @@ -0,0 +1,118 @@ +import { useEffect, useState, useCallback } from 'react'; +import { sets, catalog } from '../api/client'; +import SetSelector from '../components/sets/SetSelector'; +import PackageTable from '../components/sets/PackageTable'; + +export default function ConfigSetsPage() { + const [setList, setSetList] = useState<{ name: string; description: string }[]>([]); + const [activeSet, setActiveSet] = useState(null); + const [compareSets, setCompareSets] = useState([]); + const [comparison, setComparison] = useState(null); + const [allGroups, setAllGroups] = useState([]); + const [setConfig, setSetConfig] = useState(null); + + const loadSets = useCallback(async () => { + const list = await sets.list(); + setSetList(list); + if (!activeSet && list.length > 0) setActiveSet(list[0].name); + }, [activeSet]); + + const loadGroups = useCallback(async () => { + setAllGroups(await catalog.groups()); + }, []); + + useEffect(() => { loadSets(); loadGroups(); }, [loadSets, loadGroups]); + + useEffect(() => { + if (!activeSet) { setComparison(null); setSetConfig(null); return; } + const allNames = [activeSet, ...compareSets]; + sets.compare(allNames).then(setComparison); + sets.get(activeSet).then(setSetConfig); + }, [activeSet, compareSets]); + + const handleCreateSet = async () => { + const name = prompt('New configuration set name:'); + if (!name) return; + const desc = prompt('Description (optional):') || ''; + await sets.create({ name, description: desc, stow_packages: [] }); + await loadSets(); + setActiveSet(name); + }; + + const handleDeleteSet = async (name: string) => { + if (!confirm(`Delete configuration set "${name}"?`)) return; + await sets.delete(name); + setCompareSets((prev) => prev.filter((s) => s !== name)); + if (activeSet === name) setActiveSet(null); + await loadSets(); + }; + + const handleAddCompare = (name: string) => { + if (name !== activeSet && !compareSets.includes(name)) { + setCompareSets((prev) => [...prev, name]); + } + }; + + const handleRemoveCompare = (name: string) => { + setCompareSets((prev) => prev.filter((s) => s !== name)); + }; + + const handleToggleGroup = async (setName: string, groupName: string, platform: 'linux' | 'macos_formulae' | 'macos_cask') => { + const cfg = await sets.get(setName); + if (platform === 'linux') { + const groups: string[] = cfg.linux?.groups || []; + const idx = groups.indexOf(groupName); + if (idx >= 0) groups.splice(idx, 1); + else groups.push(groupName); + cfg.linux = { groups }; + } else if (platform === 'macos_formulae') { + const groups: string[] = cfg.macos?.formulae_groups || []; + const idx = groups.indexOf(groupName); + if (idx >= 0) groups.splice(idx, 1); + else groups.push(groupName); + cfg.macos = { ...cfg.macos, formulae_groups: groups }; + } else if (platform === 'macos_cask') { + const cGroups: string[] = cfg.macos?.cask_groups || []; + const fGroups: string[] = cfg.macos?.formulae_groups || []; + const inCask = cGroups.indexOf(groupName); + const inFormulae = fGroups.indexOf(groupName); + if (inCask >= 0) { + // Remove from cask, add to formulae + cGroups.splice(inCask, 1); + if (inFormulae < 0) fGroups.push(groupName); + } else { + // Move from formulae to cask + if (inFormulae >= 0) fGroups.splice(inFormulae, 1); + cGroups.push(groupName); + } + cfg.macos = { ...cfg.macos, formulae_groups: fGroups, cask_groups: cGroups }; + } + await sets.update(setName, cfg); + // Refresh comparison + const allNames = [activeSet!, ...compareSets]; + setComparison(await sets.compare(allNames)); + if (setName === activeSet) setSetConfig(await sets.get(setName)); + }; + + return ( +
+ + {comparison && activeSet && ( + + )} +
+ ); +} diff --git a/web/frontend/src/pages/StowPackagesPage.tsx b/web/frontend/src/pages/StowPackagesPage.tsx new file mode 100644 index 00000000000..d758cec49c9 --- /dev/null +++ b/web/frontend/src/pages/StowPackagesPage.tsx @@ -0,0 +1,111 @@ +import { useEffect, useState, useCallback } from 'react'; +import { stow } from '../api/client'; +import PackageList from '../components/stow/PackageList'; +import FileBrowser from '../components/stow/FileBrowser'; +import FileEditor from '../components/stow/FileEditor'; + +export default function StowPackagesPage() { + const [packages, setPackages] = useState([]); + const [selected, setSelected] = useState(null); + const [files, setFiles] = useState([]); + const [editingFile, setEditingFile] = useState<{ path: string; content: string } | null>(null); + + const loadPackages = useCallback(async () => { + setPackages(await stow.packages()); + }, []); + + const loadFiles = useCallback(async (pkg: string) => { + const data = await stow.files(pkg); + setFiles(data.files); + }, []); + + useEffect(() => { loadPackages(); }, [loadPackages]); + useEffect(() => { + if (selected) { + loadFiles(selected); + setEditingFile(null); + } + }, [selected, loadFiles]); + + const handleCreatePackage = async () => { + const name = prompt('New stow package name:'); + if (!name) return; + await stow.createPackage(name); + await loadPackages(); + setSelected(name); + }; + + const handleDeletePackage = async (name: string) => { + if (!confirm(`Delete stow package "${name}" and all its files?`)) return; + await stow.deletePackage(name); + if (selected === name) { setSelected(null); setFiles([]); setEditingFile(null); } + await loadPackages(); + }; + + const handleOpenFile = async (path: string) => { + if (!selected) return; + const data = await stow.readFile(selected, path); + setEditingFile({ path: data.path, content: data.content }); + }; + + const handleSaveFile = async (path: string, content: string) => { + if (!selected) return; + await stow.writeFile(selected, path, content); + setEditingFile({ path, content }); + await loadFiles(selected); + }; + + const handleDeleteFile = async (path: string) => { + if (!selected) return; + if (!confirm(`Delete file "${path}"?`)) return; + await stow.deleteFile(selected, path); + if (editingFile?.path === path) setEditingFile(null); + await loadFiles(selected); + }; + + const handleCreateFile = async () => { + if (!selected) return; + const path = prompt('New file path (relative to package root):'); + if (!path) return; + await stow.writeFile(selected, path, ''); + await loadFiles(selected); + setEditingFile({ path, content: '' }); + }; + + return ( +
+ +
+ {selected ? ( + <> + + {editingFile && ( + + )} + + ) : ( +
+ Select a stow package to browse its files +
+ )} +
+
+ ); +} diff --git a/web/frontend/tsconfig.app.json b/web/frontend/tsconfig.app.json new file mode 100644 index 00000000000..a9b5a59ca64 --- /dev/null +++ b/web/frontend/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/web/frontend/tsconfig.json b/web/frontend/tsconfig.json new file mode 100644 index 00000000000..1ffef600d95 --- /dev/null +++ b/web/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/web/frontend/tsconfig.node.json b/web/frontend/tsconfig.node.json new file mode 100644 index 00000000000..8a67f62f4ce --- /dev/null +++ b/web/frontend/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/web/frontend/vite.config.ts b/web/frontend/vite.config.ts new file mode 100644 index 00000000000..72b0427f78e --- /dev/null +++ b/web/frontend/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' + +export default defineConfig({ + plugins: [react(), tailwindcss()], + server: { + proxy: { + '/api': 'http://localhost:8000', + }, + }, +}) diff --git a/web/test/__init__.py b/web/test/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/web/test/conftest.py b/web/test/conftest.py new file mode 100644 index 00000000000..8602feca234 --- /dev/null +++ b/web/test/conftest.py @@ -0,0 +1,67 @@ +"""Shared fixtures for Playwright UI tests.""" +import multiprocessing +import socket +import time + +import pytest +import uvicorn +from playwright.sync_api import sync_playwright + + +def _find_free_port(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("", 0)) + return s.getsockname()[1] + + +def _run_server(port): + """Run the FastAPI app in a subprocess.""" + import sys + import os + sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "api")) + from main import app + uvicorn.run(app, host="127.0.0.1", port=port, log_level="warning") + + +@pytest.fixture(scope="session") +def server_url(): + """Start the FastAPI server and return its base URL.""" + port = _find_free_port() + proc = multiprocessing.Process(target=_run_server, args=(port,), daemon=True) + proc.start() + + url = f"http://127.0.0.1:{port}" + for _ in range(50): + try: + import urllib.request + urllib.request.urlopen(f"{url}/api/health", timeout=1) + break + except Exception: + time.sleep(0.1) + else: + proc.kill() + raise RuntimeError("Server did not start in time") + + yield url + proc.kill() + proc.join(timeout=3) + + +@pytest.fixture(scope="session") +def browser(): + """Launch a headless Chromium browser for the test session.""" + pw = sync_playwright().start() + b = pw.chromium.launch(headless=True) + yield b + b.close() + pw.stop() + + +@pytest.fixture +def page(browser, server_url): + """Create a new page and navigate to the app for each test.""" + p = browser.new_page(viewport={"width": 1280, "height": 900}) + p.goto(server_url) + p.wait_for_selector("header") + yield p + p.close() diff --git a/web/test/test_ui.py b/web/test/test_ui.py new file mode 100644 index 00000000000..dbeb1d3983a --- /dev/null +++ b/web/test/test_ui.py @@ -0,0 +1,274 @@ +"""Playwright UI tests for the Dotfiles Config Manager web app.""" +import re + +import pytest +from playwright.sync_api import expect + + +def _wait_for_packages(page): + """Wait for stow packages to load and return the entry locators.""" + entries = page.locator("div.cursor-pointer div.truncate") + entries.first.wait_for(state="visible", timeout=5000) + assert entries.count() > 0, "No stow packages loaded" + return entries + + +def _select_first_package(page): + """Wait for packages to load and click the first one.""" + entries = _wait_for_packages(page) + entries.first.click() + expect(page.locator("button", has_text="+ New File")).to_be_visible() + + +def _select_package_with_files(page): + """Select a package that has at least 1 file. Returns False if none found.""" + _wait_for_packages(page) + # Each package entry shows "N file(s)" — find one with count > 0 + pkg_items = page.locator("div.cursor-pointer").all() + for item in pkg_items: + text = item.text_content() or "" + # Skip entries showing "0 files" + if "0 file" in text: + continue + item.locator("div.truncate").click() + expect(page.locator("button", has_text="+ New File")).to_be_visible() + return True + return False + + +def _find_compare_dropdown(page): + """Find and return the 'Compare with...' select element.""" + for s in page.locator("main select").all(): + first_opt = s.locator("option").first.text_content() or "" + if "Compare" in first_opt: + return s + pytest.fail("Compare dropdown not found") + + +# --------------------------------------------------------------------------- +# App chrome / navigation +# --------------------------------------------------------------------------- + +class TestAppStructure: + def test_header_title(self, page): + expect(page.locator("header")).to_contain_text("Dotfiles Config Manager") + + def test_two_tabs_present(self, page): + nav = page.locator("nav") + expect(nav.get_by_text("Stow Packages")).to_be_visible() + expect(nav.get_by_text("Configuration Sets")).to_be_visible() + + def test_default_tab_is_config_sets(self, page): + btn = page.locator("nav button", has_text="Configuration Sets") + expect(btn).to_have_class(re.compile(r"border-blue-600")) + + def test_switch_to_stow_tab(self, page): + page.locator("nav button", has_text="Stow Packages").click() + expect(page.locator("main")).to_contain_text("Stow Packages") + + def test_switch_back_to_config_tab(self, page): + page.locator("nav button", has_text="Stow Packages").click() + page.locator("nav button", has_text="Configuration Sets").click() + expect(page.locator("main select").first).to_be_visible() + + +# --------------------------------------------------------------------------- +# Stow Packages tab +# --------------------------------------------------------------------------- + +class TestStowPackagesTab: + @pytest.fixture(autouse=True) + def navigate_to_stow(self, page): + page.locator("nav button", has_text="Stow Packages").click() + page.locator("main").wait_for(state="visible") + + def test_package_list_heading(self, page): + expect(page.locator("main")).to_contain_text("Stow Packages") + + def test_packages_listed(self, page): + """At least one stow package should appear in the sidebar.""" + entries = _wait_for_packages(page) + assert entries.count() >= 1 + + def test_package_shows_file_count(self, page): + _wait_for_packages(page) + expect(page.locator("main")).to_contain_text("file") + + def test_select_package_shows_file_browser(self, page): + _select_first_package(page) + expect(page.locator("main")).to_contain_text("Source") + expect(page.locator("main")).to_contain_text("Target") + + def test_file_browser_shows_home_targets(self, page): + if not _select_package_with_files(page): + pytest.skip("No packages with files available") + expect(page.locator("main")).to_contain_text("~/") + + def test_click_file_opens_editor(self, page): + if not _select_package_with_files(page): + pytest.skip("No packages with files available") + page.locator("main tbody tr.cursor-pointer").first.click() + expect(page.locator("main")).to_contain_text("Save") + + def test_editor_save_button_disabled_initially(self, page): + if not _select_package_with_files(page): + pytest.skip("No packages with files available") + page.locator("main tbody tr.cursor-pointer").first.click() + save_btn = page.locator("button", has_text="Save") + expect(save_btn).to_be_disabled() + + def test_new_package_button_exists(self, page): + expect(page.locator("main button", has_text="+ New")).to_be_visible() + + def test_empty_state_message(self, page): + expect(page.locator("main")).to_contain_text("Select a stow package") + + def test_new_file_button_after_selecting_package(self, page): + _select_first_package(page) + expect(page.locator("button", has_text="+ New File")).to_be_visible() + + def test_delete_file(self, page): + """Create a temp file via the UI, then delete it.""" + tmp_file = "_test_delete_me.txt" + + _select_first_package(page) + + # Create file via the "+ New File" prompt + page.on("dialog", lambda d: d.accept(tmp_file)) + page.locator("button", has_text="+ New File").click() + + # Wait for the temp file to appear in the file list + row = page.locator("main tbody tr", has_text=tmp_file) + expect(row).to_be_visible() + + # Now delete it + page.on("dialog", lambda d: d.accept()) + row.hover() + row.locator("button[title='Delete file']").click() + + # File should disappear from the table + expect(row).not_to_be_visible() + + +# --------------------------------------------------------------------------- +# Configuration Sets tab +# --------------------------------------------------------------------------- + +class TestConfigSetsTab: + @pytest.fixture(autouse=True) + def navigate_to_sets(self, page): + page.locator("nav button", has_text="Configuration Sets").click() + page.locator("main select").first.wait_for(state="visible") + + def test_active_set_label(self, page): + expect(page.locator("main")).to_contain_text("Active Set") + + def test_dropdown_has_options(self, page): + """Dropdown should contain at least one set.""" + select = page.locator("main select").first + options = select.locator("option").all() + real_options = [o for o in options if o.get_attribute("value")] + assert len(real_options) >= 1, "No sets in dropdown" + + def test_default_set_selected(self, page): + select = page.locator("main select").first + value = select.input_value() + assert value != "", "No default set selected" + + def test_package_table_headers(self, page): + table = page.locator("main table") + expect(table).to_contain_text("Group") + expect(table).to_contain_text("Package") + + def test_linux_macos_columns(self, page): + table = page.locator("main table") + expect(table).to_contain_text("Linux") + expect(table).to_contain_text("macOS") + + def test_group_rows_present(self, page): + """At least one group row (with collapse arrow) should appear.""" + page.locator("main table tbody tr").first.wait_for(state="visible") + group_rows = page.locator("main table tbody tr", has_text="\u25be") + assert group_rows.count() > 0, "No group rows found in table" + + def test_switch_active_set(self, page): + select = page.locator("main select").first + initial = select.input_value() + # Pick a different set + options = select.locator("option").all() + others = [o.text_content() for o in options + if o.get_attribute("value") and o.get_attribute("value") != initial] + if not others: + pytest.skip("Only one set available, cannot test switching") + select.select_option(label=others[0]) + expect(page.locator("main table")).to_contain_text(others[0]) + + def test_group_collapse_toggle(self, page): + # Wait for table rows + page.locator("main table tbody tr").first.wait_for(state="visible") + + # Find a group header row (contains collapse arrow) + group_row = page.locator("main table tbody tr", has_text="\u25be").first + expect(group_row).to_be_visible() + + rows_before = page.locator("main table tbody tr").count() + group_row.click() + # After collapsing, row count should decrease + page.wait_for_timeout(300) + rows_after = page.locator("main table tbody tr").count() + assert rows_after != rows_before, "Collapsing group did not change row count" + + def test_compare_dropdown_exists(self, page): + _find_compare_dropdown(page) + + def test_add_compare_set(self, page): + compare_select = _find_compare_dropdown(page) + options = compare_select.locator("option").all() + real = [o for o in options if o.get_attribute("value")] + if not real: + pytest.skip("No sets available for comparison") + compare_name = real[0].text_content() + compare_select.select_option(label=compare_name) + + # Should appear as a chip + expect(page.locator("span.inline-flex", has_text=compare_name)).to_be_visible() + + def test_remove_compare_chip(self, page): + compare_select = _find_compare_dropdown(page) + options = compare_select.locator("option").all() + real = [o for o in options if o.get_attribute("value")] + if not real: + pytest.skip("No sets available for comparison") + compare_name = real[0].text_content() + compare_select.select_option(label=compare_name) + + chip = page.locator("span.inline-flex", has_text=compare_name) + expect(chip).to_be_visible() + + # Click the dismiss button inside the chip + chip.locator("button").click() + expect(chip).not_to_be_visible() + + def test_new_set_button(self, page): + expect(page.locator("main button", has_text="+ New Set")).to_be_visible() + + def test_delete_set_button(self, page): + expect(page.locator("main button", has_text="Delete Set")).to_be_visible() + + def test_platform_toggle_buttons(self, page): + page.locator("main table tbody tr").first.wait_for(state="visible") + group_row = page.locator("main table tbody tr", has_text="\u25be").first + buttons = group_row.locator("button").all() + toggle_btns = [b for b in buttons if b.get_attribute("title") and "group" in (b.get_attribute("title") or "").lower()] + assert len(toggle_btns) >= 2, f"Expected >=2 platform toggles, found {len(toggle_btns)}" + + def test_table_shows_active_set_name(self, page): + select = page.locator("main select").first + active = select.input_value() + expect(page.locator("main table")).to_contain_text(active) + + def test_package_names_in_expanded_group(self, page): + """Expanded groups should show individual package names in monospace.""" + page.locator("main table tbody tr").first.wait_for(state="visible") + mono_cells = page.locator("main table tbody td.font-mono") + assert mono_cells.count() > 0, "No package name cells found" diff --git a/wezterm/.wezterm.lua b/wezterm/.wezterm.lua new file mode 100644 index 00000000000..2e30866492c --- /dev/null +++ b/wezterm/.wezterm.lua @@ -0,0 +1,20 @@ +-- Pull in the wezterm API +local wezterm = require("wezterm") + +-- This will hold the configuration. +local config = wezterm.config_builder() + +config.initial_cols = 80 +config.initial_rows = 28 + +config.font_size = 14 +config.color_scheme = "BlulocoDark" +config.font = wezterm.font_with_fallback({ + "JetBrainsMono Nerd Font", + "JetBrainsMono Nerd Font Mono", + "FiraCode Nerd Font", +}) +config.enable_tab_bar = false + +-- Finally, return the configuration to wezterm: +return config diff --git a/zsh/.zshrc b/zsh/.zshrc new file mode 100644 index 00000000000..9bdfabf3793 --- /dev/null +++ b/zsh/.zshrc @@ -0,0 +1,80 @@ +# use homebrew if it is installed +if command -v brew &> /dev/null; then + export PATH="/opt/homebrew/bin:opt/homebrew/sbin:$PATH" # use homebrew-installed binaries first + export HOMEBREW_NO_ENV_HINTS=1 + export PATH="/opt/homebrew/opt/ruby/bin:$PATH" + export PATH="$(brew --prefix coreutils)/libexec/gnubin:$PATH" +fi + +# start in tmux if it's installed, in interactive terminal, and not already inside tmux +if command -v tmux &> /dev/null && [ -n "$PS1" ] && [[ ! "$TERM" =~ screen ]] && [[ ! "$TERM" =~ tmux ]] && [ -z "$TMUX" ]; then + exec tmux +fi + +# Path to your Oh My Zsh installation. +export ZSH="$HOME/.oh-my-zsh" + + +# zstyle ':omz:update' mode disabled # disable automatic updates +zstyle ':omz:update' mode auto # update automatically without asking + +zstyle ':omz:update' frequency 7 + +# Uncomment the following line if pasting URLs and other text is messed up. +# DISABLE_MAGIC_FUNCTIONS="true" + +ENABLE_CORRECTION="true" + +plugins=(z aliases git docker docker-compose extract sudo colored-man-pages fzf) + +source $ZSH/oh-my-zsh.sh + +# User configuration + +export LANG=en_US.UTF-8 + +# Compilation flags +# export ARCHFLAGS="-arch $(uname -m)" + + +### Added by Zinit's installer +if [[ ! -f $HOME/.local/share/zinit/zinit.git/zinit.zsh ]]; then + print -P "%F{33} %F{220}Installing %F{33}ZDHARMA-CONTINUUM%F{220} Initiative Plugin Manager (%F{33}zdharma-continuum/zinit%F{220})…%f" + command mkdir -p "$HOME/.local/share/zinit" && command chmod g-rwX "$HOME/.local/share/zinit" + command git clone https://github.com/zdharma-continuum/zinit "$HOME/.local/share/zinit/zinit.git" && \ + print -P "%F{33} %F{34}Installation successful.%f%b" || \ + print -P "%F{160} The clone has failed.%f%b" +fi + +source "$HOME/.local/share/zinit/zinit.git/zinit.zsh" +autoload -Uz _zinit +(( ${+_comps} )) && _comps[zinit]=_zinit +### End of Zinit's installer chunk +zinit ice depth"1" +zinit light zsh-users/zsh-autosuggestions +zinit light zsh-users/zsh-syntax-highlighting +zinit ice depth"1" +zinit light agpenton/1password-zsh-plugin +zinit ice depth"1" +zinit light junegunn/fzf +zinit ice blockf +zinit light zsh-users/zsh-completions + + +source "$HOME/.aliases" +source "$HOME/.exports" +source "$HOME/.functions" + +# atuin +eval "$(atuin init zsh)" + +# iterm2 shell integration, with tmux support (see https://gitlab.com/gnachman/iterm2/-/wikis/tmux-Integration-Best-Practices) +export ITERM_ENABLE_SHELL_INTEGRATION_WITH_TMUX=YES +test -e "${HOME}/.iterm2_shell_integration.zsh" && source "${HOME}/.iterm2_shell_integration.zsh" + +eval "$(starship init zsh)" + +fastfetch + +# Added by Antigravity +export PATH="/Users/j/.antigravity/antigravity/bin:$PATH" diff --git a/zsh/_test_delete_me.txt b/zsh/_test_delete_me.txt new file mode 100644 index 00000000000..ca4a8f83e4e --- /dev/null +++ b/zsh/_test_delete_me.txt @@ -0,0 +1 @@ +temporary \ No newline at end of file