commit 2601041008e614c3e3e11075935066f112d554ee Author: Sebastian Krüger Date: Wed Jun 17 19:30:07 2026 +0200 chore: squash history diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..f38cff4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig is awesome: https://editorconfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_size = 2 +indent_style = space +charset = utf-8 + +# 4 space indentation +[*.py] +indent_size = 4 diff --git a/.gitconfig b/.gitconfig new file mode 100755 index 0000000..782d500 --- /dev/null +++ b/.gitconfig @@ -0,0 +1,18 @@ +[user] + name = Sebastian Krüger + email = valknar@pivoine.art +[filter "lfs"] + clean = git-lfs clean -- %f + smudge = git-lfs smudge -- %f + process = git-lfs filter-process + required = true +[core] + editor = nano +[init] + defaultBranch = main +[alias] + squash-all = "!f(){ git reset $(git commit-tree \"HEAD^{tree}\" \"$@\");};f" +[push] + autoSetupRemote = true +[pull] + ff = only diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..1dbac38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Ignore everything +* + +# But not these files. +!.gitignore +!.gitconfig +!.editorconfig +!.nvmrc +!.ruby-version +!.python-version +!.p10k.zsh +!.zprofile +!.zlogout +!.zlogin +!.zshrc +!.zshenv +!.hushlogin +!arty.yml +!signature.txt +!LICENSE +!README.md + +!scripts/ +!scripts/**/* \ No newline at end of file diff --git a/.hushlogin b/.hushlogin new file mode 100644 index 0000000..e69de29 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..f62f0b2 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.19.1 diff --git a/.p10k.zsh b/.p10k.zsh new file mode 100644 index 0000000..b9a4ffb --- /dev/null +++ b/.p10k.zsh @@ -0,0 +1,193 @@ +# Generated by Powerlevel10k configuration wizard on 2025-09-04 at 02:33 CEST. +# Based on romkatv/powerlevel10k/config/p10k-pure.zsh, checksum 07533. +# Wizard options: nerdfont-v3 + powerline, small icons, pure, snazzy, 24h time, 1 line, +# compact, instant_prompt=verbose. +# Type `p10k configure` to generate another config. +# +# Config file for Powerlevel10k with the style of Pure (https://github.com/sindresorhus/pure). +# +# Differences from Pure: +# +# - Git: +# - `@c4d3ec2c` instead of something like `v1.4.0~11` when in detached HEAD state. +# - No automatic `git fetch` (the same as in Pure with `PURE_GIT_PULL=0`). +# +# Apart from the differences listed above, the replication of Pure prompt is exact. This includes +# even the questionable parts. For example, just like in Pure, there is no indication of Git status +# being stale; prompt symbol is the same in command, visual and overwrite vi modes; when prompt +# doesn't fit on one line, it wraps around with no attempt to shorten it. +# +# If you like the general style of Pure but not particularly attached to all its quirks, type +# `p10k configure` and pick "Lean" style. This will give you slick minimalist prompt while taking +# advantage of Powerlevel10k features that aren't present in Pure. + +# Temporarily change options. +'builtin' 'local' '-a' 'p10k_config_opts' +[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases') +[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob') +[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand') +'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand' + +() { + emulate -L zsh -o extended_glob + + # Unset all configuration options. + unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR' + + # Zsh >= 5.1 is required. + [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return + + # Prompt colors. + local grey='242' + local red='#FF5C57' + local yellow='#F3F99D' + local blue='#57C7FF' + local magenta='#FF6AC1' + local cyan='#9AEDFE' + local white='#F1F1F0' + + # Left prompt segments. + typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=( + context # user@host + dir # current directory + vcs # git status + command_execution_time # previous command duration + # virtualenv # python virtual environment + prompt_char # prompt symbol + ) + + # Right prompt segments. + typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=( + # command_execution_time # previous command duration + # virtualenv # python virtual environment + # context # user@host + time # current time + ) + + # Basic style options that define the overall prompt look. + typeset -g POWERLEVEL9K_BACKGROUND= # transparent background + typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace + typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space + typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol + typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION= # no segment icons + + # Add an empty line before each prompt except the first. This doesn't emulate the bug + # in Pure that makes prompt drift down whenever you use the Alt-C binding from fzf or similar. + typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=false + + # Magenta prompt symbol if the last command succeeded. + typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS}_FOREGROUND=$magenta + # Red prompt symbol if the last command failed. + typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS}_FOREGROUND=$red + # Default prompt symbol. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯' + # Prompt symbol in command vi mode. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮' + # Prompt symbol in visual vi mode is the same as in command mode. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='❮' + # Prompt symbol in overwrite vi mode is the same as in command mode. + typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=false + + # Grey Python Virtual Environment. + typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=$grey + # Don't show Python version. + typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false + typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER= + + # Blue current directory. + typeset -g POWERLEVEL9K_DIR_FOREGROUND=$blue + + # Context format when root: user@host. The first part white, the rest grey. + typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE="%F{$white}%n%f%F{$grey}@%m%f" + # Context format when not root: user@host. The whole thing grey. + typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE="%F{$grey}%n@%m%f" + # Don't show context unless root or in SSH. + typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION= + + # Show previous command duration only if it's >= 5s. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=5 + # Don't show fractional seconds. Thus, 7s rather than 7.3s. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0 + # Duration format: 1d 2h 3m 4s. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s' + # Yellow previous command duration. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=$yellow + + # Grey Git prompt. This makes stale prompts indistinguishable from up-to-date ones. + typeset -g POWERLEVEL9K_VCS_FOREGROUND=$grey + + # Disable async loading indicator to make directories that aren't Git repositories + # indistinguishable from large Git repositories without known state. + typeset -g POWERLEVEL9K_VCS_LOADING_TEXT= + + # Don't wait for Git status even for a millisecond, so that prompt always updates + # asynchronously when Git state changes. + typeset -g POWERLEVEL9K_VCS_MAX_SYNC_LATENCY_SECONDS=0 + + # Cyan ahead/behind arrows. + typeset -g POWERLEVEL9K_VCS_{INCOMING,OUTGOING}_CHANGESFORMAT_FOREGROUND=$cyan + # Don't show remote branch, current tag or stashes. + typeset -g POWERLEVEL9K_VCS_GIT_HOOKS=(vcs-detect-changes git-untracked git-aheadbehind) + # Don't show the branch icon. + typeset -g POWERLEVEL9K_VCS_BRANCH_ICON= + # When in detached HEAD state, show @commit where branch normally goes. + typeset -g POWERLEVEL9K_VCS_COMMIT_ICON='@' + # Don't show staged, unstaged, untracked indicators. + typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED}_ICON= + # Show '*' when there are staged, unstaged or untracked files. + typeset -g POWERLEVEL9K_VCS_DIRTY_ICON='*' + # Show '⇣' if local branch is behind remote. + typeset -g POWERLEVEL9K_VCS_INCOMING_CHANGES_ICON=':⇣' + # Show '⇡' if local branch is ahead of remote. + typeset -g POWERLEVEL9K_VCS_OUTGOING_CHANGES_ICON=':⇡' + # Don't show the number of commits next to the ahead/behind arrows. + typeset -g POWERLEVEL9K_VCS_{COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=1 + # Remove space between '⇣' and '⇡' and all trailing spaces. + typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${${${P9K_CONTENT/⇣* :⇡/⇣⇡}// }//:/ }' + + # Grey current time. + typeset -g POWERLEVEL9K_TIME_FOREGROUND=$grey + # Format for the current time: 09:51:02. See `man 3 strftime`. + typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}' + # If set to true, time will update when you hit enter. This way prompts for the past + # commands will contain the start times of their commands rather than the end times of + # their preceding commands. + typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false + + # Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt + # when accepting a command line. Supported values: + # + # - off: Don't change prompt when accepting a command line. + # - always: Trim down prompt when accepting a command line. + # - same-dir: Trim down prompt when accepting a command line unless this is the first command + # typed after changing current working directory. + typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off + + # Instant prompt mode. + # + # - off: Disable instant prompt. Choose this if you've tried instant prompt and found + # it incompatible with your zsh configuration files. + # - quiet: Enable instant prompt and don't print warnings when detecting console output + # during zsh initialization. Choose this if you've read and understood + # https://github.com/romkatv/powerlevel10k#instant-prompt. + # - verbose: Enable instant prompt and print a warning when detecting console output during + # zsh initialization. Choose this if you've never tried instant prompt, haven't + # seen the warning, or if you are unsure what this all means. + typeset -g POWERLEVEL9K_INSTANT_PROMPT=off + + # Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized. + # For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload + # can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you + # really need it. + typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=off + + # If p10k is already loaded, reload configuration. + # This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true. + (( ! $+functions[p10k] )) || p10k reload +} + +# Tell `p10k configure` which file it should overwrite. +typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a} + +(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]} +'builtin' 'unset' 'p10k_config_opts' diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..47b322c --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.4.1 diff --git a/.zlogin b/.zlogin new file mode 100755 index 0000000..e69de29 diff --git a/.zlogout b/.zlogout new file mode 100755 index 0000000..e69de29 diff --git a/.zprofile b/.zprofile new file mode 100755 index 0000000..ffed80f --- /dev/null +++ b/.zprofile @@ -0,0 +1,21 @@ +SSH_ENV="$HOME/.ssh/agent-environment" + +function start_agent { + echo "Initialising new SSH agent..." + /usr/bin/ssh-agent | sed 's/^echo/#echo/' >"$SSH_ENV" + echo succeeded + chmod 600 "$SSH_ENV" + . "$SSH_ENV" >/dev/null + /usr/bin/ssh-add; +} + +# Source SSH settings, if applicable +if [ -f "$SSH_ENV" ]; then + . "$SSH_ENV" >/dev/null + #ps $SSH_AGENT_PID doesn't work under Cygwin + ps -ef | grep $SSH_AGENT_PID | grep ssh-agent$ >/dev/null || { + start_agent + } +else + start_agent +fi \ No newline at end of file diff --git a/.zshenv b/.zshenv new file mode 100644 index 0000000..e69de29 diff --git a/.zshrc b/.zshrc new file mode 100644 index 0000000..7aa7f2a --- /dev/null +++ b/.zshrc @@ -0,0 +1,217 @@ +# Source .env file early to export environment variables +if [ -f "$HOME/.env" ] ; then + set -a + source "$HOME/.env" + set +a +fi + +fpath=(~/.zsh/completions $fpath) + +if [ -d "$HOME/bin" ]; then + export PATH="$HOME/bin:$PATH" +fi + +if [ -d "$HOME/.local/bin" ]; then + export PATH="$HOME/.local/bin:$PATH" +fi + +if [ -d "$HOME/.rbenv/bin" ]; then + export PATH="$PATH:$HOME/.rbenv/bin" +fi + +if [ -d "$HOME/.pyenv/bin" ]; then + export PYENV_ROOT="$HOME/.pyenv" + export PATH="$PYENV_ROOT/bin:$PATH" +fi + +if [ -d "$HOME/.cargo/bin" ]; then + export PATH="$PATH:$HOME/.cargo/bin" +fi + +if [ -d "$HOME/.gvm/bin" ]; then + export PATH="$PATH:$HOME/.gvm/bin" +fi + +if [ -d "/usr/local/go/bin" ]; then + export PATH="$PATH:/usr/local/go/bin" +fi + +if [ -d "$HOME/go/bin" ]; then + export PATH="$PATH:$HOME/go/bin" +fi + +if [ -d "$HOME/node_modules/.bin" ]; then + export PATH="$PATH:$HOME/node_modules/.bin" +fi + +if [ -d "$HOME/scripts" ]; then + export PATH="$HOME/scripts:$PATH" +fi + +if [ -d "$HOME/stacks" ]; then + export PATH="$HOME/stacks:$PATH" +fi + +if [ -n "$BASH_VERSION" ]; then + # include .bashrc if it exists + if [ -f "$HOME/.bashrc" ]; then + . "$HOME/.bashrc" + fi +fi + +if [ -s "$NVM_DIR/nvm.sh" ] ; then + . "$NVM_DIR/nvm.sh" +fi + +if [ -s "$NVM_DIR/bash_completion" ] ; then + . "$NVM_DIR/bash_completion" +fi + +if [ -s "$HOME/.rvm/scripts/rvm" ] ; then + . "$HOME/.rvm/scripts/rvm" +fi + +if [ -s "$HOME/.cargo/env" ] ; then + . "$HOME/.cargo/env" +fi + +if [ -s "$HOME/.gvm/scripts/gvm" ] ; then + . "$HOME/.gvm/scripts/gvm" +fi + +if [ -s "$HOME/.sdkman/bin/sdkman-init.sh" ] ; then + export SDKMAN_DIR="$HOME/.sdkman" + . "$HOME/.sdkman/bin/sdkman-init.sh" +fi + + +if command -v oh-my-posh 2>&1 >/dev/null; then + eval "$(! oh-my-posh init zsh --config=~/worker.omp.json)" +fi + +if command -v rbenv 2>&1 >/dev/null; then + eval "$(rbenv init - --no-rehash zsh)" +fi + +if command -v pyenv 2>&1 >/dev/null; then + eval "$(pyenv init --path)" +fi + + +# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc. +# Initialization code that may require console input (password prompts, [y/n] +# confirmations, etc.) must go above this block; everything else may go below. +if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then + source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" +fi + +# If you come from bash you might have to change your $PATH. +# export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH + +# Path to your Oh My Zsh installation. +export ZSH="$HOME/.oh-my-zsh" + +# Set name of the theme to load --- if set to "random", it will +# load a random theme each time Oh My Zsh is loaded, in which case, +# to know which specific one was loaded, run: echo $RANDOM_THEME +# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes +ZSH_THEME="powerlevel10k/powerlevel10k" + +# Set list of themes to pick from when loading at random +# Setting this variable when ZSH_THEME=random will cause zsh to load +# a theme from this variable instead of looking in $ZSH/themes/ +# If set to an empty array, this variable will have no effect. +# ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" ) + +# Uncomment the following line to use case-sensitive completion. +# CASE_SENSITIVE="true" + +# Uncomment the following line to use hyphen-insensitive completion. +# Case-sensitive completion must be off. _ and - will be interchangeable. +# HYPHEN_INSENSITIVE="true" + +# Uncomment one of the following lines to change the auto-update behavior +# zstyle ':omz:update' mode disabled # disable automatic updates +# zstyle ':omz:update' mode auto # update automatically without asking +# zstyle ':omz:update' mode reminder # just remind me to update when it's time + +# Uncomment the following line to change how often to auto-update (in days). +# zstyle ':omz:update' frequency 13 + +# Uncomment the following line if pasting URLs and other text is messed up. +# DISABLE_MAGIC_FUNCTIONS="true" + +# Uncomment the following line to disable colors in ls. +# DISABLE_LS_COLORS="true" + +# Uncomment the following line to disable auto-setting terminal title. +# DISABLE_AUTO_TITLE="true" + +# Uncomment the following line to enable command auto-correction. +# ENABLE_CORRECTION="true" + +# Uncomment the following line to display red dots whilst waiting for completion. +# You can also set it to another string to have that shown instead of the default red dots. +# e.g. COMPLETION_WAITING_DOTS="%F{yellow}waiting...%f" +# Caution: this setting can cause issues with multiline prompts in zsh < 5.7.1 (see #5765) +# COMPLETION_WAITING_DOTS="true" + +# Uncomment the following line if you want to disable marking untracked files +# under VCS as dirty. This makes repository status check for large repositories +# much, much faster. +# DISABLE_UNTRACKED_FILES_DIRTY="true" + +# Uncomment the following line if you want to change the command execution time +# stamp shown in the history command output. +# You can set one of the optional three formats: +# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd" +# or set a custom format using the strftime function format specifications, +# see 'man strftime' for details. +# HIST_STAMPS="mm/dd/yyyy" + +# Would you like to use another custom folder than $ZSH/custom? +# ZSH_CUSTOM=/path/to/new-custom-folder + +# Which plugins would you like to load? +# Standard plugins can be found in $ZSH/plugins/ +# Custom plugins may be added to $ZSH_CUSTOM/plugins/ +# Example format: plugins=(rails git textmate ruby lighthouse) +# Add wisely, as too many plugins slow down shell startup. +plugins=(git pm2 gh sudo ssh ruby rust python node github rsync nvm rbenv pyenv docker docker-compose qrcode zsh-autosuggestions zsh-syntax-highlighting zsh-interactive-cd zsh-navigation-tools) + +[[ ! -f $ZSH/oh-my-zsh.sh ]] || source $ZSH/oh-my-zsh.sh + +# User configuration + +# export MANPATH="/usr/local/man:$MANPATH" + +# You may need to manually set your language environment +# export LANG=en_US.UTF-8 + +# Preferred editor for local and remote sessions +# if [[ -n $SSH_CONNECTION ]]; then +# export EDITOR='vim' +# else +# export EDITOR='nvim' +# fi + +# Compilation flags +# export ARCHFLAGS="-arch $(uname -m)" + +# Set personal aliases, overriding those provided by Oh My Zsh libs, +# plugins, and themes. Aliases can be placed here, though Oh My Zsh +# users are encouraged to define aliases within a top-level file in +# the $ZSH_CUSTOM folder, with .zsh extension. Examples: +# - $ZSH_CUSTOM/aliases.zsh +# - $ZSH_CUSTOM/macos.zsh +# For a full list of active aliases, run `alias`. +# +# Example aliases +# alias zshconfig="mate ~/.zshrc" +# alias ohmyzsh="mate ~/.oh-my-zsh" + +alias arty="arty.sh" +alias stacks="stacks.sh" + +# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh. +[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..370c70e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Valknar + +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 new file mode 100755 index 0000000..dd5bf70 --- /dev/null +++ b/README.md @@ -0,0 +1,281 @@ +
+ +
+ _    _____    __    __ __ _   _____    ____ _ _____
+| |  / /   |  / /   / //_// | / /   |  / __ ( ) ___/
+| | / / /| | / /   / ,<  /  |/ / /| | / /_/ //\__ \
+| |/ / ___ |/ /___/ /| |/ /|  / ___ |/ _, _/ ___/ /
+|___/_/  |_/_____/_/ |_/_/ |_/_/  |_/_/ |_| /____/
+
+    __________  ____  ____________
+   / ____/ __ \/ __ \/ ____/ ____/
+  / /_  / / / / /_/ / / __/ __/
+ / __/ / /_/ / _, _/ /_/ / /___
+/_/    \____/_/ |_|\____/_____/
+
+ +# WHERE CODE MEETS CHAOS + +[![Debian](https://img.shields.io/badge/Debian-Trixie-A81D33?style=for-the-badge&logo=debian&logoColor=white)](https://www.debian.org/) +[![Powered by Metal](https://img.shields.io/badge/POWERED%20BY-METAL-FF0000?style=for-the-badge)](https://www.slayer.net/) +[![Built with Blood](https://img.shields.io/badge/BUILT%20WITH-BLOOD%20%26%20SWEAT-8B0000?style=for-the-badge)](/) +[![License: MIT](https://img.shields.io/badge/License-MIT-990000?style=for-the-badge)](LICENSE) + +**My Debian home directory - forged in the fires of chaos, tempered with configuration files,** +**and wielded with the fury of a thousand riffs.** + +*This is where dotfiles headbang and shell scripts scream.* + +
+ +--- + +## THE ARSENAL + +### WEAPONS OF MASS DEVELOPMENT + +``` ++-----------------------------------------------------+ +| NODE.JS | Managed by nvm | +| PYTHON | Managed by pyenv | +| RUBY | Managed by rbenv | +| RUST | Managed by rustup | +| DOCKER | Containerized destruction | ++-----------------------------------------------------+ +``` + +--- + +## QUICK START RITUAL + +### Summoning the Environment + +```bash +# 1. Clone this unholy repository +git init && git remote add origin git@dev.pivoine.art:2222/valknar/home.git +git fetch && git reset --hard origin/main +git branch --set-upstream-to=origin/main main + +# 2. Configure git +git config --global init.defaultBranch main +git config --global --add safe.directory /home/$USER + +# 3. Sync all dependencies (version managers, shell config, etc.) +arty.sh sync + +# 4. Install system packages +arty.sh debian/update && arty.sh debian/install + +# 5. Install development tools +arty.sh rust/install +arty.sh node/install +arty.sh ruby/install +arty.sh python/install + +# 6. Install global packages +arty.sh npm/install +arty.sh gem/install + +# 7. Source the shell configuration +source ~/.zshrc +``` + +--- + +## arty.sh - REPOSITORY ORCHESTRATION + +### Available Scripts + +```bash +# System setup +arty.sh debian/update # Enable non-free repos and update sources +arty.sh debian/install # curl, wget, zsh, fzf, davfs2, make, imagemagick, ffmpeg, yt-dlp, pkg-config, zstd, libffi-dev, libyaml-dev, libssl-dev, liblzma-dev, libbz2-dev, libncurses-dev, libreadline-dev, libsqlite3-dev, python3-tk + +# Development tools +arty.sh docker/install # Docker Engine with user group +arty.sh rust/install # Rust toolchain via rustup +arty.sh node/install # Node.js via nvm +arty.sh ruby/install # Ruby via rbenv +arty.sh python/install # Python via pyenv +arty.sh go/install # Go via apt +arty.sh hugo/install # Hugo extended via go install + +# CLI tools +arty.sh claude/install # Claude AI CLI +arty.sh yq/install # yq YAML processor +arty.sh gh/install # GitHub CLI +arty.sh tailscale/install # Tailscale +arty.sh ollama/install # Ollama +``` + +### What arty.sh Manages + +- **Version managers** (nvm, rbenv, pyenv) +- **Shell configuration** (Oh-My-Zsh, Powerlevel10k, plugins) + +--- + +## COMMAND LINE BRUTALITY + +### Custom Scripts + +Scripts in `~/scripts/` are loaded via PATH. Check `scripts/` directory for available utilities. + +--- + +## ENVIRONMENT STRUCTURE + +``` +~/ ++-- .nvm/ # Node Version Manager ++-- .rbenv/ # Ruby Version Manager +| +-- plugins/ruby-build/ ++-- .pyenv/ # Python Version Manager ++-- .oh-my-zsh/ # Oh-My-Zsh framework +| +-- custom/ +| +-- themes/ +| | +-- powerlevel10k/ +| +-- plugins/ +| +-- zsh-autosuggestions/ +| +-- zsh-syntax-highlighting/ +``` + +--- + +## DOTFILE HIGHLIGHTS + +### Shell Configuration +- **`.zshrc`** - Oh-My-Zsh with Powerlevel10k theme +- **`.zlogin`** - Login script (mounts HiDrive, creates symbolic links) +- **`.p10k.zsh`** - Powerlevel10k configuration +- **`.zprofile`**, **`.zlogout`**, **`.zshenv`** - Zsh startup files + +### Version Files +- **`.nvmrc`** - Node.js version +- **`.ruby-version`** - Ruby version +- **`.python-version`** - Python version + +### Git Configuration +- **`.gitignore`** - INVERTED PATTERN (ignore all, allow specific files) +- **`.gitconfig`** - Git user configuration + +### Personal +- **`signature.txt`** - ASCII art email signature +- **`.hushlogin`** - Suppress login banners + +--- + +## GIT SELECTIVE TRACKING + +This repository uses an **inverted `.gitignore`** pattern: + +```gitignore +# Ignore everything +* + +# But not these files. +!.gitignore +!.gitconfig +!.editorconfig +!.nvmrc +!.ruby-version +!.python-version +!.p10k.zsh +!.zprofile +!.zlogout +!.zlogin +!.zshrc +!.zshenv +!.hushlogin +!arty.yml +!signature.txt +!LICENSE +!README.md + +!scripts/ +!scripts/**/* +# ... +``` + +**Why?** To track only essential dotfiles and configurations while ignoring cache, logs, and user data. + +--- + +## SHELL PLUGIN POWER + +**Oh-My-Zsh Plugins Loaded:** +``` +git pm2 gh sudo ssh ruby rust python node github +rsync nvm rbenv pyenv docker docker-compose qrcode +zsh-autosuggestions zsh-syntax-highlighting +zsh-interactive-cd zsh-navigation-tools +``` + +--- + +## USEFUL RESOURCES + +### System & Shell +- [Oh-My-Zsh](https://ohmyz.sh/) +- [Powerlevel10k](https://github.com/romkatv/powerlevel10k) + +### Language Managers +- [nvm](https://github.com/nvm-sh/nvm) - Node Version Manager +- [rbenv](https://github.com/rbenv/rbenv) - Ruby Version Manager +- [pyenv](https://github.com/pyenv/pyenv) - Python Version Manager +- [rustup](https://rustup.rs/) - Rust Toolchain Manager + +### CLI Tools +- [GitHub CLI](https://cli.github.com/) +- [Tailscale](https://tailscale.com/) +- [Ollama](https://ollama.com/) +- [yq](https://github.com/mikefarah/yq) - YAML processor + +--- + +## LICENSE + +MIT License - Do whatever the hell you want with it. + +--- + +
+ +
+=================================================================
+
+__________  ____  ______   _________
+  / ____/ __ \/ __ \/ ____/  /  _/ ___/
+ / /   / / / / / / / __/     / / \__ \
+/ /___/ /_/ / /_/ / /___   _/ / ___/ /
+\____/\____/_____/_____/  /___//____/
+
+  ______________  _______  ____  ____  ___    ______  __
+ /_  __/ ____/  |/  / __ \/ __ \/ __ \/   |  / __ \ \/ /
+  / / / __/ / /|_/ / /_/ / / / / /_/ / /| | / /_/ /\  /
+ / / / /___/ /  / / ____/ /_/ / _, _/ ___ |/ _, _/ / /
+/_/ /_____/_/  /_/_/    \____/_/ |_/_/  |_/_/ |_| /_/
+
+    __  __________________    __       _________
+   /  |/  / ____/_  __/   |  / /      /  _/ ___/
+  / /|_/ / __/   / / / /| | / /       / / \__ \
+ / /  / / /___  / / / ___ |/ /___   _/ / ___/ /
+/_/  /_/_____/ /_/ /_/  |_/_____/  /___//____/
+
+    ______________________  _   _____    __
+   / ____/_  __/ ____/ __ \/ | / /   |  / /
+  / __/   / / / __/ / /_/ /  |/ / /| | / /
+ / /___  / / / /___/ _, _/ /|  / ___ |/ /___
+/_____/ /_/ /_____/_/ |_/_/ |_/_/  |_/_____/
+
+=================================================================
+
+FORGED BY VALKNAR
+valknar@pivoine.art
+Powered by Debian | Fueled by Metal
+
+
+ +**[BACK TO THE TOP](#)** + +
diff --git a/arty.yml b/arty.yml new file mode 100644 index 0000000..60d3e82 --- /dev/null +++ b/arty.yml @@ -0,0 +1,73 @@ +name: "Valknar's home" +version: "1.0.0" +description: "Valknar's home repository" +author: "valknar@pivoine.art" +license: "MIT" +notes: | + # Valknar's Home Repository + + This is a **personal development environment** configuration managed with `arty`. + + ## Quick Start + + ```bash + # Install all dependencies + arty deps + + # Update system packages + arty debian/update && arty debian/install + + # Install development tools + arty node/install + arty rust/install + arty python/install + ``` + + ## Key Features + + - **Shell Configuration**: Zsh with Oh-My-Zsh and Powerlevel10k theme + - **Version Managers**: nvm (Node), rbenv (Ruby), pyenv (Python), gvm (Go) + - **Development Tools**: Docker, Rust, Node.js, Python, Ruby + - **Utilities**: Custom scripts in `bin/` directory + + ## Links + + - [Repository](ssh://git@dev.pivoine.art:2222/valknar/home.git) + - [Documentation](https://dev.pivoine.art) +references: + - url: https://github.com/nvm-sh/nvm.git + into: .nvm + - url: https://github.com/rbenv/rbenv.git + into: .rbenv + - url: https://github.com/rbenv/ruby-build.git + into: .rbenv/plugins/ruby-build + - url: https://github.com/pyenv/pyenv.git + into: .pyenv + - url: https://github.com/ohmyzsh/ohmyzsh.git + into: .oh-my-zsh + - url: https://github.com/romkatv/powerlevel10k.git + into: .oh-my-zsh/custom/themes/powerlevel10k + - url: https://github.com/marlonrichert/zsh-autocomplete.git + into: .oh-my-zsh/custom/plugins/zsh-autocomplete + - url: https://github.com/zsh-users/zsh-autosuggestions.git + into: .oh-my-zsh/custom/plugins/zsh-autosuggestions + - url: https://github.com/zsh-users/zsh-syntax-highlighting.git + into: .oh-my-zsh/custom/plugins/zsh-syntax-highlighting +scripts: + debian/update: sudo sed -i 's/main non-free-firmware/main contrib non-free non-free-firmware/g' /etc/apt/sources.list && sudo apt update + debian/install: sudo apt install curl wget zsh fzf davfs2 make imagemagick rsvg-convert ffmpeg yt-dlp pkg-config zstd libffi-dev zlib1g-dev libyaml-dev libssl-dev liblzma-dev libbz2-dev libncurses-dev libreadline-dev libsqlite3-dev python3-tk tk-dev unzip zip + rust/install: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + go/install: sudo apt install golang + node/install: nvm install + ruby/install: rbenv install + python/install: pyenv install + sdkman/install: curl -s "https://get.sdkman.io" | bash + docker/install: curl -fsSL -o- https://get.docker.com | sudo sh && sudo usermod -aG docker $USER + claude/install: curl -fsSL https://claude.ai/install.sh | bash + yq/install: wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq && chmod +x /usr/local/bin/yq + gh/install: curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null && sudo apt update && sudo apt install -y gh + tailscale/install: curl -fsSL https://tailscale.com/install.sh | sh + ollama/install: curl -fsSL https://ollama.com/install.sh | sh + hugo/install: CGO_ENABLED=1 go install -tags extended,withdeploy github.com/gohugoio/hugo@latest + npm/install: npm i -g npm pnpm + gem/install: gem install bundler diff --git a/scripts/arty.sh b/scripts/arty.sh new file mode 100755 index 0000000..ec3f6a0 --- /dev/null +++ b/scripts/arty.sh @@ -0,0 +1,1326 @@ +#!/bin/bash + +# arty.sh - A bash library repository and release management system +# Version: 1.0.0 + +set -euo pipefail + +ARTY_CONFIG_FILE="${ARTY_CONFIG_FILE:-arty.yml}" +ARTY_ENV="${ARTY_ENV:-default}" +ARTY_DRY_RUN="${ARTY_DRY_RUN:-0}" +ARTY_VERBOSE="${ARTY_VERBOSE:-0}" + +# Enable colors only when stdout is a real terminal (or FORCE_COLOR=1 overrides) +if [[ -t 1 && "${TERM:-}" != "dumb" ]]; then + export FORCE_COLOR=${FORCE_COLOR:-"1"} +else + export FORCE_COLOR=${FORCE_COLOR:-"0"} +fi +if [[ "$FORCE_COLOR" = "0" ]]; then + export RED='' + export GREEN='' + export YELLOW='' + export BLUE='' + export CYAN='' + export MAGENTA='' + export BOLD='' + export NC='' + else + export RED='\033[0;31m' + export GREEN='\033[0;32m' + export YELLOW='\033[1;33m' + export BLUE='\033[0;34m' + export CYAN='\033[0;36m' + export MAGENTA='\033[0;35m' + export BOLD='\033[1m' + export NC='\033[0m' +fi + +# Global array to track installation stack (prevent circular dependencies) +declare -g -A ARTY_INSTALL_STACK + +# Logging functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[ok]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[!]${NC} $1" +} + +log_error() { + echo -e "${RED}[x]${NC} $1" +} + +log_debug() { + if [[ "$ARTY_VERBOSE" == "1" ]]; then + echo -e "${CYAN}[DEBUG]${NC} $1" + fi +} + +log_step() { + echo -e "${CYAN}[!]${NC} $1" +} + +# ============================================================================ +# ARTY.SH CORE FUNCTIONS - Repository Management +# ============================================================================ + +# Expand environment variables in a string with validation +# Supports nested expansion (e.g., $AI_ROOT/subdir where AI_ROOT contains another var) +# Fails if any undefined variables remain after expansion +expand_env_vars() { + local input="$1" + local max_iterations=10 + local iteration=0 + local expanded="$input" + + # Keep expanding until no more variables or max iterations reached + while [[ "$expanded" =~ \$ ]] && [[ $iteration -lt $max_iterations ]]; do + # Use eval to expand variables + expanded=$(eval echo "$expanded" 2>/dev/null || echo "$expanded") + iteration=$((iteration+1)) + done + + # Check if any unexpanded variables remain + if [[ "$expanded" =~ \$[A-Za-z_][A-Za-z0-9_]* ]]; then + # Extract the undefined variable name for better error message + local undefined_var=$(echo "$expanded" | grep -o '\$[A-Za-z_][A-Za-z0-9_]*' | head -1) + log_error "Undefined environment variable in path: ${undefined_var}" + log_error " Original: $input" + log_error " Expanded: $expanded" + log_error "" + log_error "Available environment variables from arty.yml:" + if [[ -f "$ARTY_CONFIG_FILE" ]]; then + yq eval '.envs.default | keys | .[]' "$ARTY_CONFIG_FILE" 2>/dev/null | sed 's/^/ - /' || true + fi + return 1 + fi + + echo "$expanded" +} + +# Load environment variables from arty.yml +load_env_vars() { + local config_file="${1:-$ARTY_CONFIG_FILE}" + + if [[ ! -f "$config_file" ]]; then + return 0 # No config file, nothing to load + fi + + # Check if YAML is valid + if ! yq eval '.' "$config_file" >/dev/null 2>&1; then + log_warn "Invalid YAML in config file, skipping env vars" + return 0 + fi + + # Check if envs section exists + local has_envs=$(yq eval '.envs' "$config_file" 2>/dev/null) + if [[ "$has_envs" == "null" ]] || [[ -z "$has_envs" ]]; then + return 0 # No envs section + fi + + local current_env="$ARTY_ENV" + log_debug "Loading environment variables from '$current_env' environment" + + # First load default variables if they exist + local default_envs=$(yq eval '.envs.default | to_entries | .[] | .key + "=" + .value' "$config_file" 2>/dev/null) + if [[ -n "$default_envs" ]] && [[ "$default_envs" != "null" ]]; then + while IFS='=' read -r key value; do + if [[ -n "$key" ]] && [[ "$key" != "null" ]] && [[ -n "$value" ]]; then + # Only export if not already set (for default env only) + if [[ "$current_env" == "default" ]] && [[ -n "${!key:-}" ]]; then + log_debug " Skipping $key (already set)" + continue + fi + export "$key=$value" + log_debug " Set $key (from default)" + fi + done <<< "$default_envs" + fi + + # Then load environment-specific variables (which can override defaults) + if [[ "$current_env" != "default" ]]; then + local env_envs=$(yq eval ".envs.$current_env | to_entries | .[] | .key + \"=\" + .value" "$config_file" 2>/dev/null) + if [[ -n "$env_envs" ]] && [[ "$env_envs" != "null" ]]; then + while IFS='=' read -r key value; do + if [[ -n "$key" ]] && [[ "$key" != "null" ]] && [[ -n "$value" ]]; then + export "$key=$value" + log_debug " Set $key (from $current_env)" + fi + done <<< "$env_envs" + fi + fi +} + +# Wrapper to limit Go thread usage on hosts with low ulimit -u (e.g. IONOS shared hosting). +# Always returns 0 so set -e doesn't propagate a yq crash through `local var=$(yq ...)`. +yq() { + GOMAXPROCS=1 GOGC=off command yq "$@" || return 0 +} + +# Auto-install mikefarah/yq v4 using whatever package manager is available. +_install_yq() { + if command -v brew &>/dev/null; then + log_info "Installing yq via Homebrew..." + brew install yq && return 0 + fi + + if command -v snap &>/dev/null; then + log_info "Installing yq via snap..." + snap install yq && return 0 + fi + + if command -v apk &>/dev/null; then + log_info "Installing yq via apk..." + apk add --no-cache yq-go && return 0 + fi + + if command -v pacman &>/dev/null; then + log_info "Installing yq via pacman..." + pacman -S --noconfirm go-yq && return 0 + fi + + if command -v go &>/dev/null; then + log_info "Installing yq via go install..." + local go_bin="${HOME}/.local/bin" + mkdir -p "$go_bin" + GOBIN="$go_bin" go install github.com/mikefarah/yq/v4@latest && { + export PATH="${go_bin}:${PATH}" + return 0 + } + fi + + # Binary download fallback (Linux / macOS) + local os arch install_dir use_sudo=0 + os=$(uname -s | tr '[:upper:]' '[:lower:]') + case "$(uname -m)" in + x86_64) arch="amd64" ;; + aarch64|arm64) arch="arm64" ;; + armv7*) arch="arm" ;; + *) arch="amd64" ;; + esac + + local yq_url="https://github.com/mikefarah/yq/releases/latest/download/yq_${os}_${arch}" + + if [[ -w /usr/local/bin ]]; then + install_dir="/usr/local/bin" + elif command -v sudo &>/dev/null && sudo -n true 2>/dev/null; then + install_dir="/usr/local/bin" + use_sudo=1 + else + install_dir="${HOME}/.local/bin" + mkdir -p "$install_dir" + export PATH="${install_dir}:${PATH}" + fi + + log_info "Downloading yq binary (${os}/${arch}) to ${install_dir}..." + + if command -v wget &>/dev/null; then + if [[ "$use_sudo" == "1" ]]; then + sudo wget -q "$yq_url" -O "${install_dir}/yq" || return 1 + sudo chmod +x "${install_dir}/yq" + else + wget -q "$yq_url" -O "${install_dir}/yq" || return 1 + chmod +x "${install_dir}/yq" + fi + return 0 + elif command -v curl &>/dev/null; then + if [[ "$use_sudo" == "1" ]]; then + sudo curl -fsSL "$yq_url" -o "${install_dir}/yq" || return 1 + sudo chmod +x "${install_dir}/yq" + else + curl -fsSL "$yq_url" -o "${install_dir}/yq" || return 1 + chmod +x "${install_dir}/yq" + fi + return 0 + fi + + return 1 +} + +# Check if mikefarah/yq v4 is installed; auto-install if not. +check_yq() { + local need_install=0 + + # Use type -P to find the actual binary; command -v also matches the yq shell wrapper + # function defined in this script and would give a false positive. + if ! type -P yq &>/dev/null; then + log_warn "yq is not installed — attempting automatic installation..." + need_install=1 + else + local ver_out + ver_out=$(command yq --version 2>&1) || true # guard: command not found → exit 127 + set -e + if [[ ! "$ver_out" =~ mikefarah ]]; then + log_warn "Found yq but it is not mikefarah/yq v4 (got: ${ver_out})" + log_warn "arty requires mikefarah/yq v4 — attempting automatic installation..." + need_install=1 + fi + fi + + if [[ "$need_install" == "0" ]]; then + return 0 + fi + + if _install_yq && command -v yq &>/dev/null; then + local ver_out + ver_out=$(command yq --version 2>&1) + log_success "yq installed: ${ver_out}" + return 0 + fi + + log_error "Could not install yq automatically. Please install mikefarah/yq v4:" + log_error " brew install yq (macOS / Linuxbrew)" + log_error " snap install yq (Linux with snap)" + log_error " apk add yq-go (Alpine Linux)" + log_error " pacman -S go-yq (Arch Linux)" + log_error " go install github.com/mikefarah/yq/v4@latest (Go toolchain)" + log_error " wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 \\" + log_error " -O /usr/local/bin/yq && chmod +x /usr/local/bin/yq" + log_error "See: https://github.com/mikefarah/yq#install" + exit 1 +} + +# Initialize arty environment +init_arty() { + local arty_home="${ARTY_HOME:-$PWD/.arty}" + local libs_dir="${ARTY_LIBS_DIR:-$arty_home/libs}" + local bin_dir="${ARTY_BIN_DIR:-$arty_home/bin}" + + if [[ ! -d "$arty_home" ]]; then + mkdir -p "$libs_dir" + mkdir -p "$bin_dir" + log_success "Initialized arty at $arty_home" + fi +} + +# Get a field from YAML using yq +get_yaml_field() { + local file="$1" + local field="$2" + + if [[ ! -f "$file" ]]; then + return 1 + fi + + # Check if file has valid YAML - yq will exit with error on invalid YAML + if ! yq eval '.' "$file" >/dev/null 2>&1; then + return 1 + fi + + yq eval ".$field" "$file" 2>/dev/null || echo "" +} + +# Get array items from YAML using yq +get_yaml_array() { + local file="$1" + local field="$2" + + if [[ ! -f "$file" ]]; then + return 1 + fi + + # Check if file has valid YAML - yq will exit with error on invalid YAML + if ! yq eval '.' "$file" >/dev/null 2>&1; then + return 1 + fi + + yq eval ".${field}[]" "$file" 2>/dev/null +} + +# Get script command from YAML +get_yaml_script() { + local file="$1" + local script_name="$2" + + if [[ ! -f "$file" ]]; then + return 1 + fi + + yq eval ".scripts.${script_name}" "$file" 2>/dev/null || echo "null" +} + +# List all script names from YAML +list_yaml_scripts() { + local file="$1" + + if [[ ! -f "$file" ]]; then + return 1 + fi + + yq eval '.scripts | keys | .[]' "$file" 2>/dev/null +} + +# Execute a script defined in arty.yml +exec_script() { + local script_name="$1" + shift + + if [[ ! -f "$ARTY_CONFIG_FILE" ]]; then + log_error "Config file not found: $ARTY_CONFIG_FILE" + log_info "Run this command in a directory with arty.yml" + return 1 + fi + + local cmd + cmd=$(get_yaml_script "$ARTY_CONFIG_FILE" "$script_name") + + if [[ -z "$cmd" ]] || [[ "$cmd" == "null" ]]; then + log_error "Unknown command: $script_name" + log_info "Available scripts:" + while IFS= read -r name; do + [[ -n "$name" ]] && echo " - $name" + done < <(list_yaml_scripts "$ARTY_CONFIG_FILE") + return 1 + fi + + log_info "Running: $script_name" + [[ ! "$cmd" =~ \$@ ]] && cmd="$cmd"' "$@"' + bash -c "$cmd" -- "$@" +} + +# Get library name from repository URL +get_lib_name() { + local repo_url="$1" + basename "$repo_url" .git +} + +# Parse reference - can be a string URL or an object with url, into, ref, env +# Returns: url|into|ref|env (pipe-delimited) +# env can be a single value or comma-separated list +parse_reference() { + local config_file="$1" + local ref_index="$2" + + # Single yq call using only // and select — if-then-else is not supported in + # all yq v4 builds. .url//. handles both string refs (no .url key) and object + # refs. env array is joined; null env falls back to empty string. + yq eval ".references[$ref_index] | (.url // .) + \"|\" + (.into // \"\") + \"|\" + (.ref // \"\") + \"|\" + ((.env | select(tag == \"!!seq\") | join(\",\")) // .env // \"\")" "$config_file" 2>/dev/null +} + +# Check if current environment matches the filter +# env_filter can be a single env or comma-separated list +check_env_match() { + local current_env="$1" + local env_filter="$2" + + # No filter means match all + [[ -z "$env_filter" ]] && return 0 + + # Convert comma-separated list to array + IFS=',' read -ra env_list <<< "$env_filter" + + # Check if current env is in the list + for env in "${env_list[@]}"; do + # Trim whitespace + env=$(echo "$env" | xargs) + if [[ "$env" == "$current_env" ]]; then + return 0 + fi + done + + return 1 +} + +# Get git information for a repository +get_git_info() { + local repo_dir="$1" + + if [[ ! -d "$repo_dir/.git" ]]; then + echo "|||0" + return + fi + + # Get short commit hash + local commit_hash=$(cd "$repo_dir" && git rev-parse --short HEAD 2>/dev/null || echo "") + + # Get all refs pointing to current commit (tags, branches) + local refs=$(cd "$repo_dir" && git describe --all --exact-match 2>/dev/null || git symbolic-ref --short HEAD 2>/dev/null || echo "") + + # Clean up refs (remove heads/ and tags/ prefixes) + refs=$(echo "$refs" | sed 's#^heads/##' | sed 's#^tags/##') + + # Check if dirty (has uncommitted changes) + local is_dirty=0 + if [[ -n "$(cd "$repo_dir" && git status --porcelain 2>/dev/null)" ]]; then + is_dirty=1 + fi + + echo "$commit_hash|$refs|$is_dirty" +} + +# Normalize library identifier for tracking +normalize_lib_id() { + local repo_url="$1" + # Convert to lowercase and remove .git suffix for consistent tracking + local normalized="${repo_url,,}" + normalized=$(echo "$normalized" | sed 's/\.git$//') + + # Normalize different Git URL formats to the same path + # https://github.com/user/repo -> github.com/user/repo + # git@github.com:user/repo -> github.com/user/repo + # ssh://git@github.com/user/repo -> github.com/user/repo + normalized=$(echo "$normalized" | sed -E 's#^https?://##' | sed -E 's#^git@([^:]+):#\1/#' | sed -E 's#^ssh://git@##') + + echo "$normalized" +} + +# Check if library is in installation stack +is_installing() { + local lib_id="$1" + [[ -n "${ARTY_INSTALL_STACK[$lib_id]:-}" ]] +} + +# Add library to installation stack +mark_installing() { + local lib_id="$1" + ARTY_INSTALL_STACK[$lib_id]=1 +} + +# Remove library from installation stack +unmark_installing() { + local lib_id="$1" + unset ARTY_INSTALL_STACK[$lib_id] +} + +# Check if library is already installed +is_installed() { + local lib_name="$1" + local libs_dir="${ARTY_LIBS_DIR:-${ARTY_HOME:-$PWD/.arty}/libs}" + [[ -d "$libs_dir/$lib_name" ]] +} + +# Install a library from git repository +install_lib() { + local repo_url="$1" + local lib_name="${2:-$(get_lib_name "$repo_url")}" + local git_ref="${3:-}" + local custom_into="${4:-}" + local config_file="${5:-$ARTY_CONFIG_FILE}" + + # Determine installation directory + local lib_dir + if [[ -n "$custom_into" ]]; then + # Expand environment variables in custom_into path + local expanded_into + if ! expanded_into=$(expand_env_vars "$custom_into"); then + log_error "Failed to expand environment variables in 'into' path for ${repo_url}" + return 1 + fi + + # Check if expanded path is absolute or relative + if [[ "$expanded_into" == /* ]]; then + # Absolute path - use as-is + lib_dir="$expanded_into" + else + # Relative path - make relative to config file directory + local config_dir=$(dirname "$(realpath "${config_file}")") + lib_dir="$config_dir/$expanded_into" + fi + else + # Use global .arty/libs for libraries without custom 'into' + lib_dir="$ARTY_LIBS_DIR/$lib_name" + fi + + # Normalize the library identifier for circular dependency detection + # Include installation path to allow same library at different locations + local lib_id=$(normalize_lib_id "$repo_url") + local lib_id_with_path="${lib_id}@${lib_dir}" + + # Check for circular dependency + if is_installing "$lib_id_with_path"; then + log_warn "Circular dependency detected: $lib_name (already being installed at $lib_dir)" + log_info "Skipping to prevent infinite loop" + return 0 + fi + + # Check if already installed (optimization) + if [[ -d "$lib_dir" ]]; then + log_info "Library '$lib_name' already installed at $lib_dir" + + if [[ "$ARTY_DRY_RUN" == "1" ]]; then + log_info "[DRY RUN] Would check for updates..." + return 0 + fi + + # Try to update + if [[ -n "$git_ref" ]]; then + (cd "$lib_dir" && git fetch -q 2>/dev/null && git checkout -q "$git_ref" && git pull -q 2>/dev/null) || { + log_warn "Failed to update library (continuing with existing version)" + } + else + (cd "$lib_dir" && git fetch -q 2>/dev/null && git pull -q 2>/dev/null) || { + log_warn "Failed to update library (continuing with existing version)" + } + fi + + return 0 + fi + + # Mark as currently installing + mark_installing "$lib_id_with_path" + + if [[ "$ARTY_DRY_RUN" != "1" ]]; then + init_arty + fi + + log_info "Installing library: $lib_name" + log_info "Repository: $repo_url" + log_info "Git ref: ${git_ref:-}" + log_info "Location: $lib_dir" + + if [[ "$ARTY_DRY_RUN" == "1" ]]; then + log_info "[DRY RUN] Would clone repository${git_ref:+ and checkout $git_ref}" + unmark_installing "$lib_id_with_path" + return 0 + fi + + # Clone the repository + git clone "$repo_url" "$lib_dir" || { + log_error "Failed to clone repository" + unmark_installing "$lib_id_with_path" + return 1 + } + + # Checkout the specified ref (skip if none given; clone already checked out the default branch) + if [[ -n "$git_ref" ]]; then + (cd "$lib_dir" && git checkout -q "$git_ref") || { + log_warn "Failed to checkout ref '$git_ref', using default branch" + } + fi + + # Run setup hook if exists + if [[ -f "$lib_dir/setup.sh" ]]; then + log_info "Running setup hook..." + (cd "$lib_dir" && bash setup.sh) || { + log_warn "Setup hook failed, continuing anyway..." + } + fi + + # Process arty.yml if it exists + if [[ -f "$lib_dir/arty.yml" ]]; then + # Link main script to .arty/bin (only for standard installations, not custom 'into') + if [[ -z "$custom_into" ]]; then + local main_script=$(get_yaml_field "$lib_dir/arty.yml" "main") + if [[ -n "$main_script" ]] && [[ "$main_script" != "null" ]]; then + local main_file="$lib_dir/$main_script" + if [[ -f "$main_file" ]]; then + local local_bin_dir="$ARTY_BIN_DIR" + local lib_name_stripped="$(basename $main_file .sh)" + local bin_link="$local_bin_dir/$lib_name_stripped" + + log_info "Linking main script: $main_script -> $bin_link" + ln -sf "$main_file" "$bin_link" + chmod +x "$main_file" + log_success "Main script linked to $bin_link" + fi + fi + fi + + # Install nested dependencies (always, regardless of 'into') + log_info "Found arty.yml, checking for references..." + install_references "$lib_dir/arty.yml" + fi + + # Unmark as installing (we're done with this library) + unmark_installing "$lib_id_with_path" + + log_success "Library '$lib_name' installed successfully" + log_info "Location: $lib_dir" +} + +# Find if a library is defined in current or ancestor configs with 'into' +find_ancestor_into() { + local lib_url="$1" + local current_config="$2" + local lib_url_normalized=$(normalize_lib_id "$lib_url") + + # Build list of ancestor configs to check + # Start with current config's directory and walk up to find parent arty.yml files + local config_to_check="$current_config" + local current_dir=$(dirname "$(realpath "$current_config")") + + # Check current config and walk up the directory tree + while true; do + if [[ -f "$config_to_check" ]]; then + # Get count of references + local ref_count=$(yq eval '.references | length' "$config_to_check" 2>/dev/null) + if [[ -n "$ref_count" ]] && [[ "$ref_count" != "null" ]] && [[ "$ref_count" != "0" ]]; then + # Check each reference + for ((i = 0; i < ref_count; i++)); do + local ref_data=$(parse_reference "$config_to_check" "$i") + IFS='|' read -r url into git_ref env_filter <<<"$ref_data" + + local url_normalized=$(normalize_lib_id "$url") + if [[ "$url_normalized" == "$lib_url_normalized" ]] && [[ -n "$into" ]]; then + # Found it with an 'into' directive + echo "$into|$config_to_check" + return + fi + done + fi + fi + + # Check if we've reached the root ARTY_CONFIG_FILE (after checking it) + if [[ "$(realpath "$config_to_check" 2>/dev/null)" == "$(realpath "$ARTY_CONFIG_FILE" 2>/dev/null)" ]]; then + # We've checked the root config, stop here + break + fi + + # Move up one directory + local parent_dir=$(dirname "$current_dir") + if [[ "$parent_dir" == "$current_dir" ]] || [[ "$parent_dir" == "/" ]]; then + # Reached filesystem root, stop + break + fi + current_dir="$parent_dir" + config_to_check="$current_dir/arty.yml" + done + + echo "" +} + +# Install all references from arty.yml +install_references() { + local config_file="${1:-$ARTY_CONFIG_FILE}" + + if [[ ! -f "$config_file" ]]; then + log_error "Config file not found: $config_file" + return 1 + fi + + # Check if YAML is valid by trying to read it + if ! yq eval '.' "$config_file" >/dev/null 2>&1; then + log_error "Invalid YAML in config file: $config_file" + return 1 + fi + + # Initialize arty directory structure first (unless dry run) + if [[ "$ARTY_DRY_RUN" != "1" ]]; then + init_arty + fi + + # Count references + local ref_count=$(yq eval '.references | length' "$config_file" 2>/dev/null) + if [[ -z "$ref_count" ]] || [[ "$ref_count" == "null" ]] || [[ "$ref_count" == "0" ]]; then + log_info "No references to install" + return 0 + fi + + # Process each reference by index + local i + for ((i = 0; i < ref_count; i++)); do + # Parse the reference + local ref_data=$(parse_reference "$config_file" "$i") + IFS='|' read -r url into git_ref env_filter <<<"$ref_data" + + # Skip empty URLs + if [[ -z "$url" ]] || [[ "$url" == "null" ]]; then + continue + fi + + # Check environment filter using the new check_env_match function + if [[ -n "$env_filter" ]] && ! check_env_match "$ARTY_ENV" "$env_filter"; then + log_info "Skipping reference (env filter: [$env_filter], current: $ARTY_ENV): $url" + continue + fi + + # Get library name + local lib_name=$(get_lib_name "$url") + + # If no 'into' specified, check if an ancestor config defines it with 'into' + local effective_config="$config_file" + if [[ -z "$into" ]] && [[ "$config_file" != "$ARTY_CONFIG_FILE" ]]; then + local ancestor_result=$(find_ancestor_into "$url" "$config_file") + if [[ -n "$ancestor_result" ]]; then + IFS='|' read -r ancestor_into ancestor_config <<<"$ancestor_result" + into="$ancestor_into" + effective_config="$ancestor_config" + fi + fi + + log_info "Installing reference: $url" + [[ -n "$into" ]] && log_info "Custom location: $into" + [[ -n "$env_filter" ]] && log_info "Environment filter: [$env_filter]" + + install_lib "$url" "$lib_name" "$git_ref" "$into" "$effective_config" || log_warn "Failed to install reference: $url" + done +} + +# Build a reference tree from arty.yml showing all dependencies +build_reference_tree() { + local config_file="${1:-$ARTY_CONFIG_FILE}" + local indent="${2:-}" + local is_last="${3:-1}" + local visited_file="${4:-/tmp/arty_visited_$$}" + local depth="${5:-0}" + + if [[ ! -f "$config_file" ]]; then + return 0 + fi + + # Track visited library directories to prevent infinite circular recursion + # Only track at depth > 0 to allow root-level refs to show even if already nested + local config_dir=$(dirname "$(realpath "$config_file" 2>/dev/null || echo "$config_file")") + if [[ "$depth" -gt 0 ]]; then + if grep -qx "$config_dir" "$visited_file" 2>/dev/null; then + return 0 + fi + echo "$config_dir" >>"$visited_file" + fi + + # Count references + local ref_count=$(yq eval '.references | length' "$config_file" 2>/dev/null) + if [[ -z "$ref_count" ]] || [[ "$ref_count" == "null" ]] || [[ "$ref_count" == "0" ]]; then + return 0 + fi + + # Process each reference + local i + for ((i = 0; i < ref_count; i++)); do + local ref_data=$(parse_reference "$config_file" "$i") + IFS='|' read -r url into git_ref env_filter <<<"$ref_data" + + # Skip if no URL or env filter doesn't match + if [[ -z "$url" ]] || [[ "$url" == "null" ]]; then + continue + fi + if [[ -n "$env_filter" ]] && ! check_env_match "$ARTY_ENV" "$env_filter"; then + continue + fi + + local lib_name=$(get_lib_name "$url") + + # Calculate is_last_ref by counting remaining valid refs + local is_last_ref=0 + local remaining=0 + for ((j = i + 1; j < ref_count; j++)); do + local check_ref=$(parse_reference "$config_file" "$j") + IFS='|' read -r check_url _ _ check_env <<<"$check_ref" + if [[ -n "$check_url" ]] && [[ "$check_url" != "null" ]]; then + if [[ -z "$check_env" ]] || check_env_match "$ARTY_ENV" "$check_env"; then + remaining=1 + break + fi + fi + done + [[ "$remaining" == "0" ]] && is_last_ref=1 + + # If no 'into' specified, check if an ancestor config defines it with 'into' + local effective_config="$config_file" + if [[ -z "$into" ]] && [[ "$config_file" != "$ARTY_CONFIG_FILE" ]]; then + local ancestor_result=$(find_ancestor_into "$url" "$config_file") + if [[ -n "$ancestor_result" ]]; then + IFS='|' read -r ancestor_into ancestor_config <<<"$ancestor_result" + into="$ancestor_into" + effective_config="$ancestor_config" + fi + fi + + # Determine installation directory + local lib_dir + if [[ -n "$into" ]]; then + # Expand environment variables + local expanded_into + expanded_into=$(expand_env_vars "$into" 2>/dev/null) || expanded_into="$into" + + # Check if expanded path is absolute + if [[ "$expanded_into" == /* ]]; then + lib_dir="$expanded_into" + else + local config_dir=$(dirname "$(realpath "${effective_config}")") + lib_dir="$config_dir/$expanded_into" + fi + else + lib_dir="$ARTY_LIBS_DIR/$lib_name" + fi + + # Get version and git info + local version="" + local location="$lib_dir" + + if [[ -f "$lib_dir/arty.yml" ]]; then + version=$(get_yaml_field "$lib_dir/arty.yml" "version") + [[ "$version" == "null" ]] || [[ -z "$version" ]] && version="" + fi + + # Get git information + local git_info=$(get_git_info "$lib_dir") + IFS='|' read -r commit_hash git_refs is_dirty <<<"$git_info" + + # Determine display version + local display_version="${version:-$commit_hash}" + [[ -z "$display_version" ]] && display_version="unknown" + + # Tree characters + local tree_char="" + local tree_continue=" " + if [[ "$is_last_ref" == "1" ]]; then + tree_char="" + tree_continue=" " + fi + + # Print the reference line + printf "%s${tree_char} ${BOLD}${GREEN}%s${NC}" "$indent" "$lib_name" + printf " ${CYAN}%s${NC}" "$display_version" + + # Add git refs if available + if [[ -n "$git_refs" ]]; then + printf " ${BLUE}(%s)${NC}" "$git_refs" + fi + + # Add dirty indicator + if [[ "$is_dirty" == "1" ]]; then + printf " ${YELLOW}*${NC}" + fi + + # Add location info + if [[ -n "$into" ]]; then + printf " ${MAGENTA}-> %s${NC}" "$into" + fi + + echo + + # Recursively show nested dependencies + if [[ -f "$lib_dir/arty.yml" ]]; then + local next_depth=$((depth + 1)) + build_reference_tree "$lib_dir/arty.yml" "${indent}${tree_continue}" "$is_last_ref" "$visited_file" "$next_depth" + fi + done +} + +# List installed libraries with tree visualization +list_libs() { + init_arty + + if [[ ! -f "$ARTY_CONFIG_FILE" ]]; then + # Fallback to simple list if no arty.yml + if [[ ! -d "$ARTY_LIBS_DIR" ]] || [[ -z "$(ls -A "$ARTY_LIBS_DIR" 2>/dev/null)" ]]; then + log_info "No libraries installed" + return 0 + fi + + log_info "Installed libraries:" + echo + + for lib_dir in "$ARTY_LIBS_DIR"/*; do + if [[ -d "$lib_dir" ]]; then + local lib_name=$(basename "$lib_dir") + local version="" + + # Try to get version from arty.yml using yq + if [[ -f "$lib_dir/arty.yml" ]]; then + version=$(get_yaml_field "$lib_dir/arty.yml" "version") + if [[ "$version" == "null" ]] || [[ -z "$version" ]]; then + version="" + fi + fi + + printf " ${GREEN}%-20s${NC} %s\n" "$lib_name" "${version:-(unknown version)}" + fi + done + echo + return 0 + fi + + # Get project info + local project_name=$(get_yaml_field "$ARTY_CONFIG_FILE" "name") + local project_version=$(get_yaml_field "$ARTY_CONFIG_FILE" "version") + + # Clean up name/version + [[ "$project_name" == "null" ]] && project_name="$(basename "$PWD")" + [[ "$project_version" == "null" ]] && project_version="" + + # Check if there are any references + local ref_count=$(yq eval '.references | length' "$ARTY_CONFIG_FILE" 2>/dev/null) + if [[ -z "$ref_count" ]] || [[ "$ref_count" == "null" ]] || [[ "$ref_count" == "0" ]]; then + # No references defined, check for installed libraries + local libs_dir="${ARTY_LIBS_DIR:-${ARTY_HOME:-$PWD/.arty}/libs}" + if [[ ! -d "$libs_dir" ]] || [[ -z "$(ls -A "$libs_dir" 2>/dev/null)" ]]; then + # Show header but indicate no libraries + echo + printf "${BOLD}${GREEN}%s${NC}" "$project_name" + if [[ -n "$project_version" ]]; then + printf " ${CYAN}%s${NC}" "$project_version" + fi + echo + echo + log_info "No libraries installed" + echo + return 0 + fi + fi + + # Print header + echo + printf "${BOLD}${GREEN}%s${NC}" "$project_name" + if [[ -n "$project_version" ]]; then + printf " ${CYAN}%s${NC}" "$project_version" + fi + echo + echo + + # Create temporary file for tracking visited configs + local visited_file="/tmp/arty_visited_$$" + : >"$visited_file" + + # Build and display tree + build_reference_tree "$ARTY_CONFIG_FILE" "" 1 "$visited_file" + + # Cleanup + rm -f "$visited_file" + + echo +} + +# Display notes from arty.yml with markdown rendering +show_notes() { + local config_file="${1:-$ARTY_CONFIG_FILE}" + + if [[ ! -f "$config_file" ]]; then + log_error "Config file not found: $config_file" + return 1 + fi + + # Get notes field from YAML + local notes=$(yq eval '.notes' "$config_file" 2>/dev/null) + + if [[ -z "$notes" ]] || [[ "$notes" == "null" ]]; then + log_info "No notes found in $config_file" + log_info "Add a 'notes' field to your arty.yml to display project notes" + return 0 + fi + + # Get project name for header + local project_name=$(get_yaml_field "$config_file" "name") + [[ "$project_name" == "null" ]] && project_name="$(basename "$PWD")" + + echo + echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BOLD}${GREEN} Notes: ${project_name}${NC}" + echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo + + # Try to render markdown with available tools + if command -v glow &>/dev/null; then + # Use glow if available (best option) + echo "$notes" | glow - + elif command -v mdcat &>/dev/null; then + # Use mdcat if available (good alternative) + echo "$notes" | mdcat + elif command -v python3 &>/dev/null; then + # Fallback to Python markdown rendering - create temp file to avoid quoting issues + local temp_notes=$(mktemp) + echo "$notes" > "$temp_notes" + python3 - "$temp_notes" <<'EOPY' 2>/dev/null || echo "$notes" +import sys +import re + +# Read from temp file +with open(sys.argv[1], 'r') as f: + text = f.read() + +# Simple markdown-like formatting for terminal +# Code blocks first (before other processing) +lines = text.split('\n') +in_code_block = False +processed_lines = [] +for line in lines: + if line.strip().startswith('```'): + in_code_block = not in_code_block + if in_code_block: + processed_lines.append('\033[2;37m' + '─' * 60 + '\033[0m') + else: + processed_lines.append('\033[2;37m' + '─' * 60 + '\033[0m') + continue + if in_code_block: + processed_lines.append('\033[0;36m ' + line + '\033[0m') + else: + processed_lines.append(line) + +text = '\n'.join(processed_lines) + +# Headers +text = re.sub(r'^### (.+)$', r'\033[1;36m\1\033[0m', text, flags=re.MULTILINE) +text = re.sub(r'^## (.+)$', r'\033[1;32m\1\033[0m', text, flags=re.MULTILINE) +text = re.sub(r'^# (.+)$', r'\033[1;33m\1\033[0m', text, flags=re.MULTILINE) + +# Bold +text = re.sub(r'\*\*(.+?)\*\*', r'\033[1m\1\033[0m', text) +text = re.sub(r'__(.+?)__', r'\033[1m\1\033[0m', text) + +# Italic (avoid matching bold) +text = re.sub(r'(?"$ARTY_CONFIG_FILE" < [arguments] [--dry-run] [-v|--verbose] + +COMMANDS: + install [name] Install a library from git repository + deps [--dry-run] Install all dependencies from arty.yml + list List installed libraries with dependency tree + notes Display project notes from arty.yml (supports markdown) + remove Remove an installed library + init [name] Initialize a new arty.yml project + [args] Run a script defined in arty.yml scripts section + +FLAGS: + --dry-run Simulate installation without making changes + -v, --verbose Enable verbose/debug output + -h, --help Show this help message + +EXAMPLES: + arty install https://github.com/user/bash-utils.git + arty install https://github.com/user/lib.git my-lib + arty deps + arty deps --dry-run + arty list + arty notes + arty init my-project + arty npm/install + arty rust/install + +REFERENCE FORMATS: + Simple URL: + references: + - https://github.com/user/repo.git + + Extended object: + references: + - url: git@github.com:user/repo.git + into: custom/path # installation directory (relative to arty.yml) + ref: v1.0.0 # branch, tag, or commit hash + env: production # only install in this environment + + Multiple environments: + references: + - url: git@github.com:user/dev-tools.git + env: [dev, ci] + +PROJECT STRUCTURE: + project/ + .arty/ + libs/ # cloned dependencies + arty.yml + +ENVIRONMENT: + ARTY_HOME Home directory for arty (default: $PWD/.arty) + ARTY_ENV Environment name to load from arty.yml (default: default) + +EOF +} + +# ============================================================================ +# MAIN FUNCTION +# ============================================================================ + +# Main function +main() { + # Check for yq availability first + check_yq + + # Configuration + PROJECT_DIR="$PWD/.arty" + ARTY_HOME="${ARTY_HOME:-$PROJECT_DIR}" + ARTY_LIBS_DIR="${ARTY_LIBS_DIR:-$ARTY_HOME/libs}" + ARTY_BIN_DIR="${ARTY_BIN_DIR:-$ARTY_HOME/bin}" + + if [[ $# -eq 0 ]]; then + show_usage + exit 0 + fi + + # Scan flags first + local dry_run_flag=0 + local verbose_flag=0 + + for arg in "$@"; do + case "$arg" in + --dry-run) dry_run_flag=1 ;; + --verbose|-v) verbose_flag=1 ;; + esac + done + + if [[ "$verbose_flag" == "1" ]]; then + export ARTY_VERBOSE=1 + fi + + load_env_vars + + if [[ "$dry_run_flag" == "1" ]]; then + export ARTY_DRY_RUN=1 + log_info "${YELLOW}[DRY RUN MODE]${NC} Simulating actions, no changes will be made" + echo + fi + + # Extract command and positional args, stripping flags + local args=() + local command="" + + for arg in "$@"; do + case "$arg" in + --dry-run|--verbose|-v|-h|--help) continue ;; + *) + if [[ -z "$command" ]]; then + command="$arg" + else + args+=("$arg") + fi + ;; + esac + done + + if [[ "$command" == "help" ]] || [[ "$command" == "--help" ]] || [[ "$command" == "-h" ]]; then + show_usage + exit 0 + fi + + case "$command" in + install) + if [[ ${#args[@]} -eq 0 ]]; then + install_references + local install_result=$? + else + install_lib "${args[@]}" + local install_result=$? + fi + if [[ $install_result -eq 0 ]] && [[ -f "$ARTY_CONFIG_FILE" ]]; then + local ref_count=$(yq eval '.references | length' "$ARTY_CONFIG_FILE" 2>/dev/null) + if [[ -n "$ref_count" ]] && [[ "$ref_count" != "null" ]] && [[ "$ref_count" != "0" ]]; then + echo + log_success "Installation complete!" + list_libs + fi + fi + ;; + deps) + install_references + local install_result=$? + if [[ $install_result -eq 0 ]] && [[ -f "$ARTY_CONFIG_FILE" ]]; then + local ref_count=$(yq eval '.references | length' "$ARTY_CONFIG_FILE" 2>/dev/null) + if [[ -n "$ref_count" ]] && [[ "$ref_count" != "null" ]] && [[ "$ref_count" != "0" ]]; then + echo + log_success "Dependencies installed!" + list_libs + fi + fi + ;; + list | ls) + list_libs + ;; + notes) + show_notes + ;; + remove | rm) + if [[ ${#args[@]} -eq 0 ]]; then + log_error "Library name required" + exit 1 + fi + remove_lib "${args[0]}" + ;; + init) + init_project "${args[@]}" + ;; + *) + exec_script "$command" "${args[@]}" + ;; + esac +} + +# Run main if script is executed directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/signature.txt b/signature.txt new file mode 100644 index 0000000..0f6524a --- /dev/null +++ b/signature.txt @@ -0,0 +1,10 @@ + + ██╗ ██╗ █████╗ ██╗ ██╗ ██╗███╗ ██╗ █████╗ ██████╗ + ██║ ██║██╔══██╗██║ ██║ ██╔╝████╗ ██║██╔══██╗██╔══██╗ + ██║ ██║███████║██║ █████╔╝ ██╔██╗ ██║███████║██████╔╝ + ╚██╗ ██╔╝██╔══██║██║ ██╔═██╗ ██║╚██╗██║██╔══██║██╔══██╗ + ╚████╔╝ ██║ ██║███████╗██║ ██╗██║ ╚████║██║ ██║██║ ██║ + ╚═══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ + ─────────────────────────────────────────────────────────────────── + valknar@pivoine.art │ (+49) 174 8188918 │ https://pivoine.art + ───────────────────────────────────────────────────────────────────