summaryrefslogtreecommitdiff
path: root/gemfeed/examples/conf/dotfiles
diff options
context:
space:
mode:
Diffstat (limited to 'gemfeed/examples/conf/dotfiles')
-rw-r--r--gemfeed/examples/conf/dotfiles/README.md5
-rw-r--r--gemfeed/examples/conf/dotfiles/Rexfile225
-rw-r--r--gemfeed/examples/conf/dotfiles/bash/bash_profile3
-rw-r--r--gemfeed/examples/conf/dotfiles/bash/bashrc15
-rw-r--r--gemfeed/examples/conf/dotfiles/claude/CLAUDE.md2
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/ai.fish39
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/alternatives.fish17
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/config.fish31
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/dotfiles.fish48
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/editor.fish44
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/fuzzy.fish5
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/games.fish15
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/gos.fish6
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/k8s.fish76
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/quickedit.fish93
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/supersync.fish114
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/taskwarrior.fish121
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/timr.fish25
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/tmputils.fish54
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/tmux.fish94
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/update.fish75
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/utils.fish142
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/worktime.fish122
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/zoxide.fish6
-rw-r--r--gemfeed/examples/conf/dotfiles/fish/conf.d/zsh.fish12
-rw-r--r--gemfeed/examples/conf/dotfiles/ghostty/config17
-rw-r--r--gemfeed/examples/conf/dotfiles/gitsyncer/config.json33
-rw-r--r--gemfeed/examples/conf/dotfiles/helix/config.toml87
-rw-r--r--gemfeed/examples/conf/dotfiles/helix/languages.toml203
-rw-r--r--gemfeed/examples/conf/dotfiles/nvim/init.lua70
-rw-r--r--gemfeed/examples/conf/dotfiles/pipewire/pipewire.conf257
-rw-r--r--gemfeed/examples/conf/dotfiles/scripts/README.md3
-rwxr-xr-xgemfeed/examples/conf/dotfiles/scripts/ai7
-rw-r--r--gemfeed/examples/conf/dotfiles/scripts/brokenlinkfinder73
-rwxr-xr-xgemfeed/examples/conf/dotfiles/scripts/gvim7
-rwxr-xr-xgemfeed/examples/conf/dotfiles/scripts/hx.aichat-prompt9
-rwxr-xr-xgemfeed/examples/conf/dotfiles/scripts/hx.chatgpt-prompt3
-rwxr-xr-xgemfeed/examples/conf/dotfiles/scripts/hx.goformatter3
-rwxr-xr-xgemfeed/examples/conf/dotfiles/scripts/hx.hexai-prompt9
-rwxr-xr-xgemfeed/examples/conf/dotfiles/scripts/hx.nvim-copilot-prompt32
-rwxr-xr-xgemfeed/examples/conf/dotfiles/scripts/hx.prompt14
-rw-r--r--gemfeed/examples/conf/dotfiles/scripts/randomnote.rb30
-rw-r--r--gemfeed/examples/conf/dotfiles/scripts/taskwarriorfeeder.rb221
-rw-r--r--gemfeed/examples/conf/dotfiles/signature2
-rw-r--r--gemfeed/examples/conf/dotfiles/ssh/config21
-rw-r--r--gemfeed/examples/conf/dotfiles/sway/config.d/keyboard.conf6
-rw-r--r--gemfeed/examples/conf/dotfiles/tmux/tmux.conf32
-rw-r--r--gemfeed/examples/conf/dotfiles/tmux/tmux.local.conf2
-rw-r--r--gemfeed/examples/conf/dotfiles/vale.ini6
-rw-r--r--gemfeed/examples/conf/dotfiles/waybar/config.jsonc194
-rw-r--r--gemfeed/examples/conf/dotfiles/waybar/style.css326
51 files changed, 3056 insertions, 0 deletions
diff --git a/gemfeed/examples/conf/dotfiles/README.md b/gemfeed/examples/conf/dotfiles/README.md
new file mode 100644
index 00000000..6fdd2c25
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/README.md
@@ -0,0 +1,5 @@
+# dotfiles
+
+These are all my dotfiles. I can install them locally on my laptop and/or workstation as well as remotely on any server.
+
+For local installation, also have a read through https://blog.ferki.it/2023/08/11/local-management-with-rex/
diff --git a/gemfeed/examples/conf/dotfiles/Rexfile b/gemfeed/examples/conf/dotfiles/Rexfile
new file mode 100644
index 00000000..e0e002e5
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/Rexfile
@@ -0,0 +1,225 @@
+use Rex -feature => [ '1.14', 'exec_autodie' ];
+use Rex::Logger;
+
+our $HOME = $ENV{HOME};
+
+# In a public Git rapository.
+our $DOT = "$HOME/git/conf/dotfiles";
+
+# In a private Git repository.
+our $DOT_PRIVATE = "$HOME/git/conf_private/dotfiles";
+
+sub ensure_dir {
+ my ( $src_glob, $dst_dir, $file_mode ) = @_;
+ Rex::Logger::info("Ensure dir glob $src_glob");
+
+ file $dst_dir,
+ ensure => 'directory',
+ mode => '0700';
+
+ file "$dst_dir/" . basename($_),
+ ensure => 'present',
+ source => $_,
+ mode => $file_mode // '0640'
+ for glob $src_glob;
+}
+
+sub ensure_file {
+ my ( $src_file, $dst_file, $file_mode ) = @_;
+
+ file $dst_file,
+ ensure => 'present',
+ source => $src_file,
+ mode => $file_mode // '0640';
+}
+
+sub ensure {
+ my ( $src, $dst, $mode ) = @_;
+ ( $dst =~ /\/$/ ? \&ensure_dir : \&ensure_file )->( $src, $dst, $mode );
+}
+
+desc 'Install packages on Termux';
+task 'pkg_termux', sub {
+ my @pkgs = qw/
+ ack-grep
+ ctags
+ fzf
+ golang
+ htop
+ make
+ nodejs
+ ripgrep
+ rsync
+ ruby
+ starship
+ tig
+ /;
+
+ for my $pkg (@pkgs) {
+ Rex::Logger::info("Installing package $pkg");
+ pkg $pkg, ensure => 'installed';
+ }
+};
+
+desc 'Install packages on FreeBSD';
+task 'pkg_freebsd', sub {
+ my @pkgs = qw/
+ bat
+ ctags
+ fzf
+ gmake
+ go
+ gron
+ htop
+ lynx
+ node
+ p5-ack
+ ripgrep
+ starship
+ tig
+ tmux
+ /;
+
+ for my $pkg (@pkgs) {
+ Rex::Logger::info("Installing package $pkg");
+ pkg $pkg, ensure => 'installed';
+ }
+};
+
+desc 'Install packages on Fedora Linux';
+task 'pkg_fedora', sub {
+ my @pkgs = qw/
+ opendoas
+ fd-find
+ nodejs-bash-language-server
+ fortune-mod
+ syncthing
+ ncdu
+ ack
+ fish
+ bat
+ ctags
+ fzf
+ golang
+ golang-x-tools-gopls
+ gpaste
+ gron
+ htop
+ java-latest-openjdk-devel
+ lynx
+ make
+ nodejs
+ perl-File-Slurp
+ procs
+ rakudo
+ Rex
+ ripgrep
+ ruby
+ strace
+ task2
+ tig
+ tmux
+ dialect
+ chromium
+ strawberry
+ gnumeric
+ sway-config-fedora
+ sway
+ waybar
+ zathura
+ /;
+
+ for my $pkg (@pkgs) {
+ Rex::Logger::info("Installing package $pkg");
+ pkg $pkg, ensure => 'installed';
+ }
+};
+
+desc 'Install ~/.config/helix';
+task 'home_helix', sub { ensure "$DOT/helix/*" => "$HOME/.config/helix/" };
+
+desc 'Install ~/.config/ghostty';
+task 'home_ghostty', sub { ensure "$DOT/ghostty/*" => "$HOME/.config/ghostty/" };
+
+desc 'Install ~/scripts';
+task 'home_scripts', sub { ensure "$DOT/scripts/*" => "$HOME/scripts/", '0750' };
+
+desc 'Install ~/.ssh files';
+task 'home_ssh', sub { ensure "$DOT/ssh/config" => "$HOME/.ssh/config", '0600' };
+
+desc 'Install BASH configuration';
+task 'home_bash', sub {
+ ensure "$DOT/bash/bash_profile" => "$HOME/.bash_profile";
+ ensure "$DOT/bash/bashrc" => "$HOME/.bashrc";
+};
+
+desc 'Install fish configuration';
+task 'home_fish', sub {
+
+ # ensure "$DOT/fish/conf.d/*" => "$HOME/.config/fish/conf.d/";
+ my $dest_dir = "$HOME/.config/fish/conf.d";
+ if ( !-l $dest_dir ) {
+ if ( -d $dest_dir ) {
+ rename $dest_dir, "$dest_dir.old" or die "Could not rename $dest_dir: $!";
+ }
+ symlink "$DOT/fish/conf.d" => $dest_dir or die "Could not create symlink: $!";
+ }
+};
+
+desc 'Install gitsyncer configuration';
+task 'home_gitsyncer', sub {
+ my $dest_dir = "$HOME/.config/gitsyncer";
+ symlink "$DOT/gitsyncer/" => $dest_dir or die "Could not create symlink: $!";
+};
+
+sub isFileSymlink() {
+ my $file = shift;
+ return -l $file && -e $file;
+}
+
+desc 'Vale and proselint';
+task 'home_vale', sub {
+ ensure "$DOT/vale.ini" => "$HOME/.vale.ini";
+ say 'Now you can run "vale sync"';
+};
+
+desc 'Install tmux configuration';
+task 'home_tmux', sub {
+ ensure "$DOT/tmux/*" => "$HOME/.config/tmux/";
+};
+
+desc 'Install Sway configuration';
+task 'home_sway', sub {
+ ensure "$DOT/sway/config.d/*" => "$HOME/.config/sway/config.d/";
+ ensure "$DOT/waybar/*" => "$HOME/.config/waybar/";
+};
+
+desc 'Install my signature';
+task 'home_signature', sub {
+ ensure "$DOT/signature" => "$HOME/.signature";
+};
+
+desc 'Install my calendar files';
+task 'home_calendar', sub {
+ unless ( -d $DOT_PRIVATE ) {
+ Rex::Logger::info( "$DOT_PRIVATE not there, skipping task", 'warn' );
+ }
+ else {
+ ensure "$DOT_PRIVATE/calendar/*" => "$HOME/.calendar/";
+ }
+};
+
+desc 'Install my Pipewire tuned for High-Res config';
+task 'home_pipewire', sub {
+ file "$HOME/.config/pipewire" => ensure => 'directory',
+ mode => '0750';
+ ensure
+ "$DOT/pipewire/pipewire.conf" => "$HOME/.config/pipewire/pipewire.conf",
+ '0600';
+};
+
+desc 'Install all my ~ files';
+task 'home', sub {
+ require Rex::TaskList;
+ run_task $_ for Rex::TaskList->create()->get_all_tasks('^home_');
+};
diff --git a/gemfeed/examples/conf/dotfiles/bash/bash_profile b/gemfeed/examples/conf/dotfiles/bash/bash_profile
new file mode 100644
index 00000000..004a7b32
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/bash/bash_profile
@@ -0,0 +1,3 @@
+if [ -f $HOME/.bashrc ]; then
+ source $HOME/.bashrc
+fi
diff --git a/gemfeed/examples/conf/dotfiles/bash/bashrc b/gemfeed/examples/conf/dotfiles/bash/bashrc
new file mode 100644
index 00000000..ec2b10c3
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/bash/bashrc
@@ -0,0 +1,15 @@
+# If shell is interactive
+if [[ ! -z "$PS1" && ! -f $HOME/.nofish ]]; then
+ # Use fish if it's installed
+ if [ -e /opt/local/bin/fish ]; then
+ exec /opt/local/bin/fish
+ elif [ -e /bin/fish ]; then
+ exec /bin/fish
+ elif [ -e /usr/bin/fish ]; then
+ exec /usr/bin/fish
+ elif [ -e /data/data/com.termux/files/usr/bin/fish ]; then
+ exec /data/data/com.termux/files/usr/bin/fish
+ fi
+
+ echo 'I might want to install fish on this host'
+fi
diff --git a/gemfeed/examples/conf/dotfiles/claude/CLAUDE.md b/gemfeed/examples/conf/dotfiles/claude/CLAUDE.md
new file mode 100644
index 00000000..ffda0b71
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/claude/CLAUDE.md
@@ -0,0 +1,2 @@
+- Whenever updating code, also update the comments in the code to reflect the reality and the reasoning.
+- When a function reaches 50 lines of code or more, try to refactor it into several functions of about 30 lines each. In case of a go project, when main.go becomes too large, move code into the ./internal package.
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/ai.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/ai.fish
new file mode 100644
index 00000000..23ce2b20
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/ai.fish
@@ -0,0 +1,39 @@
+abbr -a gpt chatgpt
+abbr -a gpti "chatgpt --interactive"
+abbr -a suggest hexai
+abbr -a explain 'hexai explain'
+abbr -a aic 'aichat -e'
+
+# helix-gpt env vars used
+# set -gx COPILOT_MODEL gpt-4.1 # can be changed with aimodels function
+set -gx COPILOT_MODEL gpt-4o # can be changed with aimodels function
+set -gx HANDLER copilot
+
+# TODO: also reconfigure aichat tool using this function
+function aimodels
+ # nvim for the ai tool wrapper so i can use Copilot Chat from the command line.
+ set -l NVIM_DIR "$HOME/.config/nvim/"
+ set -l COPILOT_CHAT_DIR "$NVIM_DIR/pack/copilotchat/start/CopilotChat.nvim/lua/CopilotChat"
+
+ printf "gpt-4o
+gpt-5
+gpt-o3
+gpt-4.1
+claude-3.7-sonnet
+claude-3.7-sonnet-thought
+claude-4.0-sonnet
+gemini-2.5-pro" >~/.aimodels
+
+ set -gx COPILOT_MODEL (cat ~/.aimodels | fzf)
+ set -gx OPENAI_MODEL $COPILOT_MODEL
+
+ if test -d $COPILOT_CHAT_DIR
+ set -l model_config "$COPILOT_CHAT_DIR/config-$COPILOT_MODEL.lua"
+ if test -f "$model_config"
+ echo "Using CopilotChat config from $model_config"
+ cp -v $model_config "$COPILOT_CHAT_DIR/config.lua"
+ else
+ echo "No config found at $model_config"
+ end
+ end
+end
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/alternatives.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/alternatives.fish
new file mode 100644
index 00000000..491cf1fe
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/alternatives.fish
@@ -0,0 +1,17 @@
+if type -q bat
+ alias Cat=/usr/bin/cat
+ alias cat=bat
+end
+if type -q see
+ alias ca=see
+end
+if type -q bit
+ alias Git=/usr/bin/git
+ alias git=bit
+end
+if type -q procs
+ alias p='procs'
+end
+if type -q carl
+ alias cal='carl'
+end
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/config.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/config.fish
new file mode 100644
index 00000000..670ca861
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/config.fish
@@ -0,0 +1,31 @@
+fish_vi_key_bindings
+
+# Add paths to PATH
+set -U fish_user_paths ~/bin ~/scripts ~/go/bin ~/.cargo/bin $fish_user_paths
+
+if command -q -v doas >/dev/null
+ abbr -a s doas
+else
+ abbr -a s sudo
+end
+
+abbr -a g 'grep -E -i'
+abbr -a no 'grep -E -i -v'
+abbr -a not 'grep -E -i -v'
+abbr -a gl 'git log --pretty=oneline --graph --decorate --all'
+abbr -a gp 'begin; git commit -a; and git pull; and git push; end'
+
+for dir in ~/.config/fish/conf.d.work ~/.config/fish/conf.d.local
+ if test -d $dir
+ for file in $dir/*.fish
+ source $file
+ end
+ end
+end
+
+if test -d /home/linuxbrew/.linuxbrew
+ if status is-interactive
+ # Commands to run in interactive sessions can go here
+ end
+ eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
+end
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/dotfiles.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/dotfiles.fish
new file mode 100644
index 00000000..6304d321
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/dotfiles.fish
@@ -0,0 +1,48 @@
+set -gx DOTFILES_DIR ~/git/rexfiles/dotfiles
+
+function dotfiles::update
+ set -l prev_pwd (pwd)
+ cd $DOTFILES_DIR
+ rex home
+ cd "$prev_pwd"
+end
+
+function dotfiles::update::git
+ set -l prev_pwd (pwd)
+ cd $DOTFILES_DIR
+ git pull
+ git commit -a
+ git push
+ rex home
+ cd "$prev_pwd"
+end
+
+function dotfiles::fuzzy::edit
+ set -l prev_pwd (pwd)
+ cd $DOTFILES_DIR
+ set -l dotfile (find . -type f -not -path '*/.git/*' | fzf)
+ $EDITOR "$dotfile"
+ if echo "$dotfile" | grep -F -q .fish
+ echo "Sourcing $dotfile"
+ source "$dotfile"
+ end
+ cd "$prev_pwd"
+end
+
+function dotfiles::rexify
+ cd $DOTFILES_DIR
+ rex home
+ cd -
+end
+
+function dotfiles::random::edit
+ $EDITOR (find $DOTFILES_DIR -type f -not -path '*/.git/*' | shuf -n 1)
+end
+
+abbr -a .u 'dotfiles::update'
+abbr -a .ug 'dotfiles::update::git'
+abbr -a .e 'dotfiles::fuzzy::edit'
+abbr -a .rex 'dotfiles::rexify'
+abbr -a .re 'dotfiles::random::edit'
+abbr -a cdconf "cd $HOME/git/conf"
+abbr -a cdotfiles "cd $HOME/git/conf/dotfiles"
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/editor.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/editor.fish
new file mode 100644
index 00000000..bda46448
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/editor.fish
@@ -0,0 +1,44 @@
+set -gx EDITOR hx
+set -gx VISUAL $EDITOR
+set -gx GIT_EDITOR $EDITOR
+set -gx HELIX_CONFIG_DIR $HOME/.config/helix
+
+function editor::helix::open_with_lock
+ set -l file $argv[1]
+ set -l lock "$file.lock"
+ if test -f "$lock"
+ echo "File lock $lock exists! Another instance is editing it?"
+ return 2
+ end
+ touch $lock
+ hx $file $argv[2..-1]
+ rm $lock
+end
+
+function editor::helix::open_with_lock::force
+ set -l file $argv[1]
+ set -l lock "$file.lock"
+ if test -f "$lock"
+ echo "File lock $lock exists! Force deleting it and terminating all $EDITOR instances?"
+ rm -f $lock
+ pkill -f $EDITOR
+ end
+ touch $lock
+ hx $file $argv[2..-1]
+ rm $lock
+end
+
+function editor::helix::edit::remote
+ set -l local_path $argv[1]
+ set -l remote_uri $argv[2]
+ scp $local_path $remote_uri; or return 1
+ echo "LOCAL_PATH=$local_path; REMOTE_URI=$remote_uri" >~/.hx.remote.source
+ hx $local_path
+end
+
+abbr -a lhx 'editor::helix::open_with_lock'
+abbr -a hxl 'editor::helix::open_with_lock'
+abbr -a hxlf 'editor::helix::open_with_lock::force'
+abbr -a lhxf 'editor::helix::open_with_lock::force'
+abbr -a rhx 'editor::helix::edit::remote'
+abbr -a x hx
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/fuzzy.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/fuzzy.fish
new file mode 100644
index 00000000..7683a0e7
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/fuzzy.fish
@@ -0,0 +1,5 @@
+function __tv_git
+ tv git-repos
+end
+
+bind \cg __tv_git
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/games.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/games.fish
new file mode 100644
index 00000000..291a798f
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/games.fish
@@ -0,0 +1,15 @@
+function games::colorscript
+ if test -e ~/git/shell-color-scripts
+ cd ~/git/shell-color-scripts
+ set -x DEV 1
+ ./colorscript.sh --random
+ cd -
+ else
+ echo 'No colorscripts installed. Go to:'
+ echo ' https://gitlab.com/dwt1/shell-color-scripts'
+ end
+end
+
+if not test -f ~/.colorscript.disable
+ games::colorscript
+end
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/gos.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/gos.fish
new file mode 100644
index 00000000..a23d7a7b
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/gos.fish
@@ -0,0 +1,6 @@
+set -x GOS_BIN ~/go/bin/gos
+set -x GOS_DIR ~/.gosdir
+
+if test -f $GOS_BIN
+ alias cdgos "cd $GOS_DIR"
+end
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/k8s.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/k8s.fish
new file mode 100644
index 00000000..ee1584bf
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/k8s.fish
@@ -0,0 +1,76 @@
+function kcompletions
+ if command -q -v kubectl >/dev/null
+ kubectl completion fish | source
+ end
+end
+
+# Check if the directory $HOME/.krew exists and update PATH
+if test -d $HOME/.krew
+ set -x PATH (set -q KREW_ROOT; and echo $KREW_ROOT; or echo $HOME/.krew)/bin $PATH
+end
+
+function kpod
+ set pattern "."
+ if test -n "$argv[1]"
+ set pattern "$argv[1]"
+ end
+ set -gx POD (kubectl get pods | grep "$pattern" | sort -R | head -n 1 | cut -d' ' -f1)
+ echo "Pod is $POD"
+end
+
+function klogsf
+ if test -z "$POD" -o -n "$argv[1]"
+ kpod $argv
+ end
+ kubectl logs -f $POD
+end
+
+function klogs
+ if test -z "$POD" -o -n "$argv[1]"
+ kpod $argv
+ end
+ kubectl logs $POD
+end
+
+function kbash
+ if test -z "$POD" -o -n "$argv[1]"
+ kpod $argv
+ end
+ kubectl exec -it $POD -- /bin/bash
+end
+
+function kshell
+ if test -z "$POD" -o -n "$argv[1]"
+ kpod $argv
+ end
+ kubectl exec -it $POD -- /bin/sh
+end
+
+function kdesc
+ if test -z "$POD" -o -n "$argv[1]"
+ kpod $argv
+ end
+ kubectl describe pod $POD
+end
+
+function kedit
+ if test -z "$POD" -o -n "$argv[1]"
+ kpod $argv
+ end
+ kubectl edit pod $POD
+end
+
+function k8s::kubectl::config::contexts
+ kubectl config get-contexts | sed '1d; /\*/d' | awk '{ print $1 }' | sort
+end
+alias kcontexts="k8s::kubectl::config::contexts"
+
+function k8s::kubectl::config::use_context
+ kubectl config use-context (kubectl config get-contexts | sed '1d; /\*/d' | awk '{ print $1 }' | sort | fzf)
+end
+alias kcontext="k8s::kubectl::config::use_context"
+
+function k8s::kubectl::config::set_namespace
+ kubectl config set-context --current --namespace=(kubectl get ns | sed 1d | awk '{ print $1 }' | sort | fzf)
+end
+alias knamespace="k8s::kubectl::config::set_namespace"
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/quickedit.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/quickedit.fish
new file mode 100644
index 00000000..c722acc6
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/quickedit.fish
@@ -0,0 +1,93 @@
+set -gx QUICKEDIT_DIR ~/QuickEdit
+
+function quickedit::postaction
+ set -l file_path $argv[1]
+ set -l make_run 0
+
+ if test -f Makefile
+ make
+ set make_run 1
+ end
+
+ # Go to git toplevel dir (if exists)
+ cd (dirname $file_path)
+ set -l git_dir (git rev-parse --show-toplevel 2>/dev/null)
+ if test $status -eq 0
+ cd $git_dir
+ end
+ if not test $make_run -eq 1
+ if test -f Makefile
+ make
+ end
+ end
+ if test -d .git
+ git commit -a -m Update
+ git pull
+ git push
+ end
+end
+
+function quickedit
+ set -l prev_dir (pwd)
+ set -l grep_pattern .
+
+ if test (count $argv) -gt 0
+ set grep_pattern $argv[1]
+ end
+
+ cd $QUICKEDIT_DIR
+ set files (find -L . -type f -not -path '*/.*' | grep -E "$grep_pattern")
+
+ switch (count $files)
+ case 0
+ echo No result found
+ return
+ case 1
+ set file_path $files[1]
+ case '*'
+ set file_path (printf '%s\n' $files | fzf)
+ end
+
+ if editor::helix::open_with_lock $file_path
+ quickedit::postaction $file_path
+ end
+
+ cd $prev_dir
+end
+
+function quickedit::direct
+ set -l dir $argv[1]
+ set -l file $argv[2]
+ cd $dir
+
+ if editor::helix::open_with_lock $file
+ quickedit::postaction $file
+ end
+
+ cd -
+end
+
+function quickedit::scratchpad
+ quickedit::direct ~/Notes Scratchpad.md
+end
+
+function quickedit::quicknote
+ quickedit::direct ~/Notes QuickNote.md
+end
+
+function quickedit::performance
+ quickedit::direct ~/Notes Performance.md
+end
+
+abbr -a e quickedit
+abbr -a scratch quickedit::scratchpad
+abbr -a S quickedit::scratchpad
+abbr -a quicknote quickedit::quicknote
+abbr -a performance quickedit::performance
+abbr -a goals quickedit::performance
+abbr -a er "ranger $QUICKEDIT_DIR"
+abbr -a cdquickedit "cd $QUICKEDIT_DIR"
+abbr -a cdnotes 'cd ~/Notes'
+abbr -a cdfish 'cd ~/.config/fish/conf.d'
+abbr -a cddocs 'cd ~/Documents'
+abbr -a cdocs 'cd ~/Documents'
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/supersync.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/supersync.fish
new file mode 100644
index 00000000..356f773f
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/supersync.fish
@@ -0,0 +1,114 @@
+set -x SUPERSYNC_STAMP_FILE ~/.supersync.last
+
+# Only sync the HabitsAndQuotes when it's asked for via function parameter
+function supersync::worktime
+ set -l worktime_dir ~/git/worktime
+
+ if not test -d $worktime_dir
+ echo "Warning: Directory $worktime_dir does not exist"
+ return 1
+ end
+ cd $worktime_dir
+
+ if test (count $argv) -gt 0 -a $argv[1] = sync_quotes
+ if test -d ~/Notes/HabitsAndQuotes
+ echo "" >work-wisdoms.md.tmp
+ for notes in ~/Notes/HabitsAndQuotes/{Productivity,Mentoring}.md
+ grep '^\* ' $notes >>work-wisdoms.md.tmp
+ end
+ sort -u work-wisdoms.md.tmp >work-wisdoms.md
+ rm work-wisdoms.md.tmp
+ git add work-wisdoms.md
+ grep '^\* ' ~/Notes/HabitsAndQuotes/Exercise.md >exercises.md
+ git add exercises.md
+ end
+ end
+
+ find . -name '*.txt' -exec git add {} \;
+ find . -name '*.json' -exec git add {} \;
+ git commit -a -m sync
+
+ git pull origin master
+ git push origin master
+
+ cd -
+end
+
+function supersync::uprecords
+ set -l uprecords_dir ~/git/uprecords
+ set -l uprecords_repo git@codeberg.org:snonux/uprecords.git
+
+ if not test -d $uprecords_dir
+ git clone $uprecords_repo $uprecords_dir
+ cd $uprecords_dir
+ else
+ cd $uprecords_dir
+ git pull
+ end
+
+ make update
+ git commit -a -m Update
+ git push
+ cd -
+end
+
+function supersync::taskwarrior
+ if test -f ~/scripts/taskwarriorfeeder.rb
+ ruby ~/scripts/taskwarriorfeeder.rb
+ else
+ echo "No taskwarrior feeder script, skipping"
+ end
+
+ taskwarrior::export
+ taskwarrior::export::gos
+ taskwarrior::import
+end
+
+function supersync::gitsyncer
+ set enable_file ~/.gitsyncer_enable
+ set now (date +%s)
+ set weekly_interval (math 7 \* 24 \* 60 \* 60)
+
+ if not test -f $enable_file
+ echo $now >$enable_file
+ else
+ set last_run (cat $enable_file)
+ if test (math $now - $last_run) -lt $weekly_interval
+ return
+ end
+ end
+
+ if test -f ~/go/bin/gitsyncer
+ ~/go/bin/gitsyncer sync bidirectional && ~/go/bin/gitsyncer showcase
+ end
+ if test $status -eq 0
+ date +%s >$enable_file
+ end
+end
+
+function supersync
+ supersync::worktime sync_quotes
+ supersync::taskwarrior
+ supersync::worktime no_sync_quotes
+ supersync::uprecords
+ supersync::gitsyncer
+
+ if test -f ~/.gos_enable
+ gos
+ end
+
+ date +%s >$SUPERSYNC_STAMP_FILE.tmp
+ mv $SUPERSYNC_STAMP_FILE.tmp $SUPERSYNC_STAMP_FILE
+end
+
+function supersync::is_it_time_to_sync
+ set -l max_age 86400
+ set -l now (date +%s)
+ if test -f $SUPERSYNC_STAMP_FILE
+ set -l diff (math $now - (cat $SUPERSYNC_STAMP_FILE))
+ if test $diff -lt $max_age
+ return 0
+ end
+ end
+ read -P "It's time to run supersync! Run it? (y/n) " answer; and test "$answer" = y; and supersync
+end
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/taskwarrior.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/taskwarrior.fish
new file mode 100644
index 00000000..d3192bcd
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/taskwarrior.fish
@@ -0,0 +1,121 @@
+function taskwarrior::fuzzy::_select
+ sed -n '/^[0-9]/p' | sort -rn | fzf | cut -d' ' -f1
+end
+
+function taskwarrior::fuzzy::find
+ set -g TASK_ID (task ready | taskwarrior::fuzzy::_select)
+end
+
+function taskwarrior::select
+ set -l task_id "$argv[1]"
+ if test -n "$task_id"
+ set -g TASK_ID "$task_id"
+ end
+ if test "$TASK_ID" = - -o -z "$TASK_ID"
+ taskwarrior::fuzzy::find
+ end
+end
+
+function taskwarrior::due::count
+ set -l due_count (task status:pending due.before:now count)
+
+ if test $due_count -gt 0
+ echo "There are $due_count tasks due!"
+ end
+end
+
+function taskwarrior::add::track
+ if test (count $argv) -gt 0
+ task add priority:L +personal +track $argv
+ else
+ tasksamurai +track
+ end
+end
+
+function taskwarrior::add::standup
+ if test (count $argv) -gt 0
+ task add priority:L +work +standup +sre +nosched $argv
+ task add priority:L +work +standup +storage +nosched $argv
+
+ if test -f ~/git/helpers/jira/jira.rb
+ echo "Do you want to raise a Jira ticket? (y/n)"
+ read -l user_input
+ if test "$user_input" = y
+ ruby ~/git/helpers/jira/jira.rb --raise "$argv"
+ end
+ end
+
+ else
+ tasksamurai +standup
+ end
+end
+
+function taskwarrior::add::standup::editor
+ set -l tmpfile (mktemp /tmp/standup.XXXXXX.txt)
+ $EDITOR $tmpfile
+ taskwarrior::add::standup (cat $tmpfile)
+end
+
+function _taskwarrior::set_import_export_tags
+ if test (uname) = Darwin
+ set -gx TASK_IMPORT_TAG work
+ set -gx TASK_EXPORT_TAG personal
+ else
+ set -gx TASK_IMPORT_TAG personal
+ set -gx TASK_EXPORT_TAG work
+ end
+end
+
+function taskwarrior::export::gos
+ task +share status:pending export >"$WORKTIME_DIR/tw-gos-export-$(date +%s).json"
+ yes | task +share status:pending delete
+end
+
+function taskwarrior::export
+ _taskwarrior::set_import_export_tags
+ set -l count (task +$TASK_EXPORT_TAG status:pending count)
+
+ if test $count -eq 0
+ return
+ end
+
+ echo "Exporting $count tasks to $TASK_EXPORT_TAG"
+ task +$TASK_EXPORT_TAG status:pending export >"$WORKTIME_DIR/tw-$TASK_EXPORT_TAG-export-$(date +%s).json"
+ yes | task +$TASK_EXPORT_TAG status:pending delete
+end
+
+function taskwarrior::import
+ _taskwarrior::set_import_export_tags
+
+ find $WORKTIME_DIR -name "tw-$TASK_IMPORT_TAG-export-*.json" | while read -l import
+ task import $import
+ rm $import
+ end
+
+ find $WORKTIME_DIR -name "tw-(hostname)-export-*.json" | while read -l import
+ task import $import
+ rm $import
+ end
+end
+
+abbr -a t task
+abbr -a L 'task add +log'
+abbr -a tlog 'task add +log'
+abbr -a log 'task add +log'
+abbr -a tdue 'tasksamurai status:pending due.before:now'
+abbr -a thome 'tasksamurai +home'
+abbr -a tasks 'tasksamurai -track'
+abbr -a tread 'tasksamurai +read'
+abbr -a track 'taskwarrior::add::track'
+abbr -a tra 'taskwarrior::add::track'
+abbr -a trat 'timr track'
+abbr -a tfind 'taskwarrior::fuzzy::find'
+abbr -a ts tasksamurai
+
+# Virtual standup abbrs
+abbr -a V 'taskwarrior::add::standup'
+abbr -a Vstorage 'tasksamurai +standup +storage'
+abbr -a Vsre 'tasksamurai +standup +sre'
+abbr -a Ved 'taskwarrior::add::standup::editor'
+
+taskwarrior::due::count
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/timr.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/timr.fish
new file mode 100644
index 00000000..4f084454
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/timr.fish
@@ -0,0 +1,25 @@
+function timr_prompt -d "Display timr timr_status in the prompt"
+ if command -v timr >/dev/null
+ set -l timr_status (timr prompt)
+ if test -n "$timr_status"
+ set -l icon (string sub -l 1 -- "$timr_status")
+ set -l time (string sub -s 2 -- "$timr_status")
+ if test "$icon" = "▶"
+ set_color green
+ else
+ set_color yellow
+ end
+ printf '%s' "$icon"
+ set_color normal
+ printf ' %s' "$time"
+ end
+ end
+end
+
+complete -c timr -n __fish_use_subcommand -a start -d "Start the timer"
+complete -c timr -n __fish_use_subcommand -a stop -d "Stop the timer"
+complete -c timr -n __fish_use_subcommand -a pause -d "Pause the timer"
+complete -c timr -n __fish_use_subcommand -a status -d "Show the timer status"
+complete -c timr -n __fish_use_subcommand -a reset -d "Reset the timer"
+complete -c timr -n __fish_use_subcommand -a live -d "Show the live timer"
+complete -c timr -n __fish_use_subcommand -a prompt -d "Show the prompt status"
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/tmputils.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/tmputils.fish
new file mode 100644
index 00000000..20a122ad
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/tmputils.fish
@@ -0,0 +1,54 @@
+set -gx TMPUTILS_DIR ~/data/tmp
+set -gx TMPUTILS_TMPFILE ~/.tmpfile
+
+function tmpls
+ if not test -d $TMPUTILS_DIR
+ return
+ end
+ ls $TMPUTILS_DIR
+end
+
+function tmptee
+ set -l name $argv[1]
+ if test -z "$name"
+ set name (date +%s)
+ else
+ set -e argv[1]
+ end
+ set -l file "$TMPUTILS_DIR/$name"
+ if not test -d $TMPUTILS_DIR
+ mkdir -p $TMPUTILS_DIR
+ end
+ tee $argv $file
+ echo $file >$TMPUTILS_TMPFILE
+end
+
+function tmpcat
+ set -l name $argv[1]
+ if test -z "$name"
+ cat (tmpfile)
+ return
+ end
+ cat "$TMPUTILS_DIR/$name"
+end
+
+function tmpedit
+ set -l name $argv[1]
+ if test -z "$name"
+ $EDITOR (tmpfile)
+ return
+ end
+ $EDITOR "$TMPUTILS_DIR/$name"
+end
+
+function tmpgrep
+ set -l name $argv[1]
+ set -e argv[1]
+ tmcpat $name | grep $argv
+end
+
+function tmpfile
+ cat $TMPUTILS_TMPFILE
+end
+
+abbr -a cdtmp "cd $TMPUTILS_DIR"
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/tmux.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/tmux.fish
new file mode 100644
index 00000000..e65960e0
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/tmux.fish
@@ -0,0 +1,94 @@
+function _tmux::cleanup_default
+ tmux list-sessions | string match -r '^T.*: ' | string match -v -r attached | string split ':' | while read -l s
+ echo "Killing $s"
+ tmux kill-session -t "$s"
+ end
+end
+
+function _tmux::connect_command
+ set -l server_or_pod $argv[1]
+ if test -z "$TMUX_KEXEC"
+ echo "ssh -A -t $server_or_pod"
+ else
+ echo "kubectl exec -it $server_or_pod -- /bin/bash"
+ end
+end
+
+function tmux::new
+ set -l session $argv[1]
+ _tmux::cleanup_default
+ if test -z "$session"
+ tmux::new (string join "" T (date +%s))
+ else
+ tmux new-session -d -s $session
+ tmux -2 attach-session -t $session || tmux -2 switch-client -t $session
+ end
+end
+
+function tmux::attach
+ set -l session $argv[1]
+ if test -z "$session"
+ tmux attach-session || tmux::new
+ else
+ tmux attach-session -t $session || tmux::new $session
+ end
+end
+
+function tmux::remote
+ set -l server $argv[1]
+ tmux new -s $server "ssh -A -t $server 'tmux attach-session || tmux'" || tmux attach-session -d -t $server
+end
+
+function tmux::search
+ set -l session (tmux list-sessions | fzf | cut -d: -f1)
+ if test -z "$TMUX"
+ tmux attach-session -t $session
+ else
+ tmux switch -t $session
+ end
+end
+
+function tmux::cluster_ssh
+ if test -f "$argv[1]"
+ tmux::tssh_from_file $argv[1]
+ return
+ end
+ tmux::tssh_from_argument $argv
+end
+
+function tmux::tssh_from_argument
+ set -l session $argv[1]
+ set first_server_or_container $argv[2]
+ set remaining_servers $argv[3..-1]
+ if test -z "$first_server_or_container"
+ set first_server_or_container $session
+ end
+
+ tmux new-session -d -s $session (_tmux::connect_command "$first_server_or_container")
+ if not tmux list-session | grep "^$session:"
+ echo "Could not create session $session"
+ return 2
+ end
+ for server_or_container in $remaining_servers
+ tmux split-window -t $session "tmux select-layout tiled; $(_tmux::connect_command "$server_or_container")"
+ end
+ tmux setw -t $session synchronize-panes on
+ tmux -2 attach-session -t $session || tmux -2 switch-client -t $session
+end
+
+function tmux::tssh_from_file
+ set -l serverlist $argv[1]
+ set -l session (basename $serverlist | cut -d. -f1)
+ tmux::tssh_from_argument $session (awk '{ print $1 }' $serverlist | sed 's/.lan./.lan/g')
+end
+
+alias tn 'tmux::new'
+alias ta 'tmux::attach'
+alias tx 'tmux::remote'
+alias ts 'tmux::search'
+alias tssh 'tmux::cluster_ssh'
+alias tm tmux
+alias tl 'tmux list-sessions'
+alias foo 'tmux::new foo'
+alias bar 'tmux::new bar'
+alias baz 'tmux::new baz'
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/update.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/update.fish
new file mode 100644
index 00000000..935b6302
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/update.fish
@@ -0,0 +1,75 @@
+function update::tools
+ set pids
+
+ echo "Installing/updating gofumpt"
+ go install mvdan.cc/gofumpt@latest &
+ set -a pids $last_pid
+
+ echo "Installing/updating mage"
+ go install github.com/magefile/mage@latest &
+ set -a pids $last_pid
+
+ echo "Installing/updating golangci-lint"
+ go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest &
+ set -a pids $last_pid
+
+ echo "Installing/updating goimports"
+ go install golang.org/x/tools/cmd/goimports@latest &
+ set -a pids $last_pid
+
+ for prog in hexai hexai-lsp hexai-tmux-action
+ echo "Installing/updating $prog from codeberg.org/snonux/hexai/cmd/$prog@latest"
+ go install codeberg.org/snonux/hexai/cmd/$prog@latest &
+ set -a pids $last_pid
+ end
+
+ for prog in tasksamurai timr
+ echo "Installing/updating $prog from codeberg.org/snonux/$prog/cmd/$prog@latest"
+ go install codeberg.org/snonux/$prog/cmd/$prog@latest &
+ set -a pids $last_pid
+ end
+
+ if test (uname) = Darwin
+ echo 'Updating cursor-agent on macOS'
+ cursor-agent update
+ end
+ set -a pids $last_pid
+
+ if test (uname) = Linux
+ echo "Installing/updating tgpt"
+ go install github.com/aandrew-me/tgpt/v2@latest &
+ set -a pids $last_pid
+
+ for prog in gos gitsyncer
+ echo "Installing/updating $prog from codeberg.org/snonux/$prog/cmd/$prog@latest"
+ go install codeberg.org/snonux/$prog/cmd/$prog@latest
+ end
+
+ echo "Installing/updating @anthropic-ai/claude-code globally via npm"
+ doas npm uninstall -g @anthropic-ai/claude-code
+ doas npm install -g @anthropic-ai/claude-code
+
+ # doas npm uninstall -g @qwen-code/qwen-code@latest
+ # doas npm install -g @qwen-code/qwen-code@latest
+
+ echo "Installing/updating @openai/codex globally via npm"
+ doas npm uninstall -g @openai/codex
+ doas npm install -g @openai/codex
+
+ echo "Installing/updating @google/gemini-cli globally via npm"
+ doas npm uninstall -g @google/gemini-cli
+ doas npm install -g @google/gemini-cli
+
+ # echo "Installing/updating @sourcegraph/amp globally via npm"
+ # doas npm uninstall -g @sourcegraph/amp
+ # doas npm install -g @sourcegraph/amp
+
+ echo "Installing/updating opencode-ai globally via npm"
+ doas npm uninstall -g opencode-ai
+ doas npm install -g opencode-ai
+ end
+
+ for pid in $pids
+ wait $pid
+ end
+end
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/utils.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/utils.fish
new file mode 100644
index 00000000..0f112177
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/utils.fish
@@ -0,0 +1,142 @@
+function fullest_h
+ df -h | sort -n -k 5
+end
+
+function fullest_i
+ df -i | sort -n -k 5
+end
+
+function usortn
+ sort | uniq -c | sort -n
+end
+
+function asum
+ awk '{ sum += $1 } END { print sum }'
+end
+
+function stop
+ set -l service $argv[1]
+ sudo service $service stop $argv
+end
+
+function start
+ set -l service $argv[1]
+ sudo service $service start $argv
+end
+
+function restart
+ set -l service $argv[1]
+ sudo service $service restart $argv
+end
+
+function statuss
+ set -l service $argv[1]
+ sudo service $service status $argv
+end
+
+function loop
+ set -l sleep 10
+ if set -q SLEEP
+ set sleep $SLEEP
+ end
+ echo "sleep is $sleep" 1>&2
+ while true
+ $argv
+ sleep $sleep
+ end
+end
+
+function f
+ find . -iname "*$argv*"
+end
+
+function random
+ set -l upto $argv[1]
+ set -l random (math $RANDOM % $upto)
+ echo "Sleeping $random seconds"
+ sleep $random
+end
+
+function dedup
+ set -l file $argv[1]
+ if test -z $file
+ awk '{ if (line[$0] != 42) { print $0 }; line[$0] = 42; }'
+ else
+ awk '{ if (line[$0] != 42) { print $0 }; line[$0] = 42; }' $file | sudo tee $file.dedup >/dev/null
+ if test ! -f $file.dedupbak
+ sudo mv $file $file.dedupbak
+ end
+ sudo mv $file.dedup $file
+ wc -l $file $file.dedupbak
+ sudo gzip --best $file.dedupbak &
+ end
+end
+
+function dedup_no_bak
+ set -l file $argv[1]
+ if test -z $file
+ awk '{ if (line[$0] != 42) { print $0 }; line[$0] = 42; }'
+ else
+ awk '{ if (line[$0] != 42) { print $0 }; line[$0] = 42; }' $file | sudo tee $file.dedup >/dev/null
+ if test ! -f $file.dedupbak
+ sudo mv $file $file.dedupbak
+ end
+ sudo mv $file.dedup $file
+ wc -l $file $file.dedupbak
+ sudo rm -v $file.dedupbak &
+ end
+end
+
+function drop_caches
+ echo 3 | sudo tee /proc/sys/vm/drop_caches
+end
+
+function ssl_connect
+ set -l address $argv[1]
+ openssl s_client -connect $address
+end
+
+function ssl_dates
+ ssl_connect $argv | openssl x509 -noout -dates
+end
+
+function lastu
+ last | grep -E -v '(root|cron|nagios)'
+end
+
+function lastl
+ lastu | less
+end
+
+abbr wetter 'curl http://wttr.in'
+
+abbr tf terraform
+
+function touchtype
+ tt --noskip --noreport --showwpm --bold --theme (tt -list themes | sort -R | head -n1) $argv
+end
+
+function touchtype::quote
+ while true
+ touchtype -quotes en
+ sleep 0.2
+ end
+end
+
+abbr typing 'touchtype::quote'
+
+function sway_config_view
+ less /etc/sway/config
+end
+
+function ssh::force
+ set -l server $argv[1]
+ ssh-keygen -R $server
+ ssh -A $server
+end
+
+if test -f ~/git/geheim/geheim.rb
+ function geheim
+ ruby ~/git/geheim/geheim.rb $argv
+ end
+end
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/worktime.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/worktime.fish
new file mode 100644
index 00000000..f2f7f5d6
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/worktime.fish
@@ -0,0 +1,122 @@
+set -gx WORKTIME_DIR ~/git/worktime
+
+if test (uname) = Darwin -a ! -f ~/.wtloggedin
+ echo "Warn: Not logged in, run wtlogin"
+end
+
+function worktime
+ ruby $WORKTIME_DIR/worktime.rb $argv
+end
+
+function worktime::sync
+ cd $WORKTIME_DIR
+ git commit -a -m sync
+ git pull
+ git push
+ cd -
+end
+
+function worktime::wisdom_reminder
+ if test -f $WORKTIME_DIR/work-wisdoms.md
+ sed -n '/^\* / { s/\* //; p; }' $WORKTIME_DIR/work-wisdoms.md | sort -R | head -n 1
+ end
+end
+
+function worktime::report
+ if test -f ~/.wtloggedin
+ if test -f ~/.wtmaster
+ worktime --report | tee $WORKTIME_DIR/report.txt
+ else
+ worktime --report
+ end
+ worktime::wisdom_reminder
+ end
+end
+
+function worktime::add
+ set -l seconds $argv[1]
+ set -l what $argv[2]
+ set -l descr $argv[3]
+ set -l epoch (date +%s)
+
+ if test -z "$what"
+ set what work
+ end
+
+ if test -z "$descr"
+ worktime --add $seconds --epoch $epoch --what $what
+ else
+ worktime --add $seconds --epoch $epoch --what $what --descr "$descr"
+ end
+
+ worktime::report
+end
+
+function worktime::log
+ set -l seconds $argv[1]
+ set -l what $argv[2]
+ set -l epoch (date +%s)
+
+ if test -z "$what"
+ set what work
+ end
+
+ worktime --log --epoch $epoch --what $what
+ worktime::report
+end
+
+function worktime::login
+ set -l what $argv[1]
+ if test -z "$what"
+ set what work
+ end
+ touch ~/.wtloggedin
+ worktime --login --what $what
+ worktime::wisdom_reminder
+end
+
+function worktime::logout
+ set -l what $argv[1]
+
+ if test -z "$what"
+ set what work
+ end
+
+ if test -f ~/.wtloggedin
+ rm ~/.wtloggedin
+ end
+
+ worktime --logout --what $what
+ worktime::report
+end
+
+function worktime::status
+ worktime::report
+
+ if test -f ~/.wtloggedin
+ echo "You are logged in"
+ set -l num_worklog (ls $WORKTIME_DIR | grep wl- | wc -l)
+ if test $num_worklog -gt 0
+ echo "$num_worklog entries in the worklog"
+ end
+ else
+ echo "You are not logged in"
+ end
+end
+
+abbr -a cdworktime "cd $WORKTIME_DIR"
+abbr -a wt worktime
+abbr -a wtedit 'worktime --edit'
+abbr -a wtreport 'worktime --report'
+abbr -a wtadd 'worktime::add'
+abbr -a wtlog 'worktime::log'
+abbr -a wtlogin 'worktime::login'
+abbr -a wtlogout 'worktime::logout'
+abbr -a wtstatus 'worktime::status'
+abbr -a wtsync 'worktime::sync'
+abbr -a wtf 'worktime --report'
+abbr -a random_exercise "sort -R $WORKTIME_DIR/exercises.md | head -n 1"
+abbr -a random_exercises "sort -R $WORKTIME_DIR/exercises.md | head -n 10"
+abbr -a wl 'task add +work'
+abbr -a ql 'task add +personal'
+abbr -a pl 'task add +personal'
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/zoxide.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/zoxide.fish
new file mode 100644
index 00000000..8fbd5d61
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/zoxide.fish
@@ -0,0 +1,6 @@
+if type -q zoxide
+ echo Sourcing zoxide for fish shell...
+ zoxide init fish | source
+else
+ echo "zoxide not installed?"
+end
diff --git a/gemfeed/examples/conf/dotfiles/fish/conf.d/zsh.fish b/gemfeed/examples/conf/dotfiles/fish/conf.d/zsh.fish
new file mode 100644
index 00000000..06174d84
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/fish/conf.d/zsh.fish
@@ -0,0 +1,12 @@
+# To run a ZSH function in fish, you can use the following function.
+function Z
+ touch ~/.nofish
+ zsh -i -c "$argv"
+ rm ~/.nofish
+end
+
+function B
+ touch ~/.nofish
+ bash -i -c "$argv"
+ rm ~/.nofish
+end
diff --git a/gemfeed/examples/conf/dotfiles/ghostty/config b/gemfeed/examples/conf/dotfiles/ghostty/config
new file mode 100644
index 00000000..e1095832
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/ghostty/config
@@ -0,0 +1,17 @@
+window-decoration = true
+copy-on-select = true
+quick-terminal-position = bottom
+quick-terminal-screen = mouse
+shell-integration = zsh
+bold-is-bright = true
+
+# Toggle window decorations only works on Linux!
+keybind = ctrl+shift+d=toggle_window_decorations
+keybind = ctrl+shift+f=toggle_fullscreen
+keybind = ctrl+shift+g=reload_config
+# Toggle quick terminal only supported for MacOS
+keybind = global:ctrl+shift+t=toggle_quick_terminal
+keybind = ctrl+shift+c=copy_to_clipboard
+keybind = ctrl+shift+v=paste_from_clipboard
+keybind = ctrl+shift+w=paste_from_selection
+
diff --git a/gemfeed/examples/conf/dotfiles/gitsyncer/config.json b/gemfeed/examples/conf/dotfiles/gitsyncer/config.json
new file mode 100644
index 00000000..3ebb7780
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/gitsyncer/config.json
@@ -0,0 +1,33 @@
+{
+ "organizations": [
+ {
+ "host": "git@codeberg.org",
+ "name": "snonux"
+ },
+ {
+ "host": "git@github.com",
+ "name": "snonux"
+ },
+ {
+ "host": "paul@t450:git",
+ "backupLocation": true
+ }
+ ],
+ "repositories": [],
+ "skip_releases": {
+ "fapi": [
+ "0.0.1"
+ ]
+ },
+ "exclude_from_showcase": [
+ "bratwurstmitsenf",
+ "Adv360-Pro-ZMK",
+ "katana",
+ "playground",
+ "pages",
+ "nvim"
+ ],
+ "exclude_branches": [
+ "^codex/"
+ ]
+} \ No newline at end of file
diff --git a/gemfeed/examples/conf/dotfiles/helix/config.toml b/gemfeed/examples/conf/dotfiles/helix/config.toml
new file mode 100644
index 00000000..0d96c3ff
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/helix/config.toml
@@ -0,0 +1,87 @@
+theme = "adwaita-dark"
+
+[editor]
+bufferline = "always"
+rulers = [80, 100, 120, 140]
+line-number = "relative"
+mouse = true
+cursorline = true
+cursorcolumn = true
+continue-comments = false
+completion-timeout = 2000
+
+[editor.soft-wrap]
+enable = true
+
+[editor.inline-diagnostics]
+# cursor-line = "hint"
+
+[editor.auto-save]
+focus-lost = true
+after-delay.timeout = 3000
+after-delay.enable = true
+
+[editor.statusline]
+left = ["version-control", "mode", "spinner", "file-name", "position" ]
+center = ["diagnostics"]
+right = ["selections", "file-encoding", "file-line-ending", "file-type"]
+
+[editor.lsp]
+display-messages = true
+display-inlay-hints = false
+
+[editor.cursor-shape]
+normal = "block"
+insert = "underline"
+select = "bar"
+
+[editor.whitespace.render]
+space = "none"
+tab = "none"
+newline = "none"
+
+[keys.normal]
+D = ["ensure_selections_forward", "extend_to_line_end"]
+S = ["ensure_selections_forward", "extend_to_line_start"]
+0 = ["select_mode", "extend_to_file_start"]
+G = ["ensure_selections_forward", "extend_to_file_end"]
+"^" = ["move_prev_word_start", "move_next_word_end", "search_selection", "global_search"]
+"ret" = "goto_word"
+
+C-c = "yank_main_selection_to_clipboard"
+C-v = { b = "paste_clipboard_before", a = "paste_clipboard_after", r = ":clipboard-paste-replace" }
+A-c = "toggle_comments" # Was originally C-c, so mapped to ALT now
+
+# Helix related helpers
+C-h = { c = ":config-open", r = ":config-reload", C = ":run-shell-command cp -v ~/.config/helix/*.toml ~/git/conf/dotfiles/helix/", l = ":open ~/.config/helix/languages.toml", h = ":open ~/git/worktime/HelixCheat.md", L = ":log-open", d = ":theme default" }
+
+C-r = [ ":config-reload", ":reload-all" ]
+
+C-u = [ ":write", ":run-shell-command sh -c 'source ~/.hx.remote.source; scp $LOCAL_PATH $REMOTE_URI && echo Uploaded to $REMOTE_URI || echo Failed uploading to $REMOTE_URI'"]
+
+# Various helpers
+C-s = { e = ":set-option soft-wrap.enable true", d = ":set-option soft-wrap.enable false", s = "save_selection" }
+
+# Buffer stuff
+C-q = ":buffer-close"
+
+# AI commands are good here.
+C-p = { c = ":pipe ai correct this sentence and only print out the corrected text", r = ":pipe ai restructure and reword the input and dont leave information out and only print out the new text", a = ":pipe ai rewrite this in a more casual style", n = ":pipe ai these are book notes of mine. correct the grammar and re-organize the notes. use bullet points for short information and whole paragraphs for longer one. the output must be in Gemini Gemtext format with the star * as the bullet point symbol and not the minus - . dont leave out any content.", p = ":pipe ai" }
+# Will replace the above
+C-a = ":pipe hexai-tmux-action"
+
+# Git commands
+C-g = { d = ":run-shell-command git diff", p = ":run-shell-command git pull", u = ":run-shell-command git push", t = ":run-shell-command tmux new-window -n hx-git-tig tig", c = ":run-shell-command tmux split-window -v 'git commit -a'" }
+
+# Build commands
+C-l = { m = ":run-shell-command make", d = ":run-shell-command go-task dev", r = ":run-shell-command tmux new-window -n hx-go-task-run 'go-task run'" }
+
+[keys.normal.space]
+B = "file_picker_in_current_buffer_directory"
+Q = [ ":cd ~/QuickEdit", "file_picker_in_current_directory" ]
+
+[keys.select]
+"{" = "goto_prev_paragraph"
+"}" = "goto_next_paragraph"
+n = ["extend_search_next", "merge_selections"]
+N = ["extend_search_prev", "merge_selections"]
diff --git a/gemfeed/examples/conf/dotfiles/helix/languages.toml b/gemfeed/examples/conf/dotfiles/helix/languages.toml
new file mode 100644
index 00000000..60e6a19c
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/helix/languages.toml
@@ -0,0 +1,203 @@
+[[language]]
+name = "hcl"
+scope = "source.hcl"
+injection-regex = "(hcl|tf|nomad)"
+language-id = "terraform"
+file-types = ["hcl", "tf", "nomad"]
+comment-token = "#"
+block-comment-tokens = { start = "/*", end = "*/" }
+indent = { tab-width = 2, unit = " " }
+language-servers = [ "terraform-ls", "hexai-lsp" ]
+auto-format = true
+
+[[language]]
+name = "go"
+auto-format= true
+diagnostic-severity = "hint"
+formatter = { command = "hx.goformatter" }
+language-servers = [ "gopls", "golangci-lint-lsp", "hexai-lsp" ]
+[language-server.hexai-lsp]
+command = "hexai-lsp"
+
+[language-server.gopls]
+command = "gopls"
+
+[language-server.gopls.config.hints]
+assignVariableTypes = true
+compositeLiteralFields = true
+constantValues = true
+functionTypeParameters = true
+parameterNames = true
+rangeVariableTypes = true
+
+# go install github.com/nametake/golangci-lint-langserver@latest │
+[language-server.golangci-lint-lsp]
+command = "golangci-lint-langserver"
+
+# golangci-lint-langserver depepds/calls golangci-lint
+# go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
+[language-server.golangci-lint-lsp.config]
+command = ["golangci-lint", "run", "--issues-exit-code=1"]
+# command = ["golangci-lint", "run", "--out-format", "json", "--issues-exit-code=1"]
+
+[[language]]
+name = "c"
+scope = "source.c"
+injection-regex = "c"
+file-types = ["c", "h"]
+comment-token = "//"
+language-servers = [ "clangd", "hexai-lsp" ]
+indent = { tab-width = 2, unit = " " }
+
+[[grammar]]
+name = "c"
+source = { git = "https://github.com/tree-sitter/tree-sitter-c", rev = "7175a6dd5fc1cee660dce6fe23f6043d75af424a" }
+
+[language-server.clangd]
+command = "clangd"
+
+[[language]]
+name = "perl"
+auto-format= true
+formatter = { command = "perltidy", args = ["-l=120"] }
+scope = "source.perl"
+file-types = ["pl", "pm", "t", "psgi", "raku", "rakumod", "rakutest", "rakudoc", "nqp", "p6", "pl6", "pm6", { glob = "Rexfile" }]
+shebangs = ["perl"]
+comment-token = "#"
+language-servers = [ "perlnavigator", "hexai-lsp" ]
+indent = { tab-width = 2, unit = " " }
+
+[[grammar]]
+name = "perl"
+source = { git = "https://github.com/tree-sitter-perl/tree-sitter-perl", rev = "e99bb5283805db4cb86c964722d709df21b0ac16" }
+
+[[language]]
+name = "pod"
+scope = "source.pod"
+injection-regex = "pod"
+file-types = ["pod"]
+
+[[grammar]]
+name = "pod"
+source = { git = "https://github.com/tree-sitter-perl/tree-sitter-pod", rev = "39da859947b94abdee43e431368e1ae975c0a424" }
+
+[[language]]
+name = "ruby"
+auto-format = true
+scope = "source.ruby"
+injection-regex = "ruby"
+file-types = [
+ "rb",
+ "rbs",
+ "rake",
+ "irb",
+ "gemspec",
+ { glob = "Gemfile" },
+ { glob = "Rakefile" }
+]
+shebangs = ["ruby"]
+comment-token = "#"
+language-servers = [ "ruby-lsp", "solargraph", "rubocop", "hexai-lsp" ]
+indent = { tab-width = 2, unit = " " }
+
+[[grammar]]
+name = "ruby"
+source = { git = "https://github.com/tree-sitter/tree-sitter-ruby", rev = "206c7077164372c596ffa8eaadb9435c28941364" }
+
+[[language]]
+name = "bash"
+scope = "source.bash"
+injection-regex = "(shell|bash|zsh|sh)"
+file-types = [
+ "sh",
+ "bash",
+ "zsh",
+ "zshenv",
+ "zlogin",
+ "zlogout",
+ "zprofile",
+ "zshrc",
+ "eclass",
+ "ebuild",
+ "bazelrc",
+ "Renviron",
+ "zsh-theme",
+ "ksh",
+ "cshrc",
+ "tcshrc",
+ "bashrc_Apple_Terminal",
+ "zshrc_Apple_Terminal",
+ { glob = "*zshrc*" },
+]
+shebangs = ["sh", "bash", "dash", "zsh"]
+comment-token = "#"
+language-servers = [ "bash-language-server", "hexai-lsp" ]
+indent = { tab-width = 2, unit = " " }
+
+[[language]]
+name = "fish"
+# scope = "source.fish"
+# injection-regex = "(fish)"
+# file-types = [
+# "fish",
+# ]
+# shebangs = ["fish" ]
+# comment-token = "#"
+language-servers = [ "fish-lsp", "hexai-lsp" ]
+# indent =dth = 4, unit = " " }
+
+[[grammar]]
+name = "bash"
+source = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "275effdfc0edce774acf7d481f9ea195c6c403cd" }
+
+[language-server]
+bash-language-server = { command = "bash-language-server", args = ["start"] }
+vale-ls = { command = "vale-ls" }
+ruby-lsp = { command = "ruby-lsp"}
+rubocop = { command = "rubocop", args = ["--lsp"] }
+
+[[language]]
+name = "markdown"
+scope = "source.md"
+injection-regex = "md|markdown"
+file-types = ["md", "markdown", "mkd", "mdwn", "mdown", "markdn", "mdtxt", "mdtext", "workbook", "gmi", "tpl", "txt" ]
+roots = [".marksman.toml"]
+language-servers = [ "marksman", "markdown-oxide", "vale-ls", "hexai-lsp"]
+indent = { tab-width = 2, unit = " " }
+
+[[grammar]]
+name = "markdown"
+source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "aaf76797aa8ecd9a5e78e0ec3681941de6c945ee", subpath = "tree-sitter-markdown" }
+
+[[language]]
+name = "markdown.inline"
+scope = "source.markdown.inline"
+injection-regex = "markdown\\.inline"
+file-types = []
+grammar = "markdown_inline"
+
+[[grammar]]
+name = "markdown_inline"
+source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "aaf76797aa8ecd9a5e78e0ec3681941de6c945ee", subpath = "tree-sitter-markdown-inline" }
+
+[[language]]
+name = "gemini"
+scope = "source.gmi"
+file-types = ["gmi", "tpl"]
+
+[[grammar]]
+name = "gemini"
+source = { git = "https://git.sr.ht/~nbsp/tree-sitter-gemini", rev = "3cc5e4bdf572d5df4277fc2e54d6299bd59a54b3" }
+
+[[language]]
+name = "java"
+scope = "source.java"
+injection-regex = "java"
+file-types = ["java", "jav", "pde"]
+roots = ["pom.xml", "build.gradle", "build.gradle.kts"]
+language-servers = [ "jdtls", "hexai-lsp" ]
+indent = { tab-width = 2, unit = " " }
+
+[[grammar]]
+name = "java"
+source = { git = "https://github.com/tree-sitter/tree-sitter-java", rev = "09d650def6cdf7f479f4b78f595e9ef5b58ce31e" }
diff --git a/gemfeed/examples/conf/dotfiles/nvim/init.lua b/gemfeed/examples/conf/dotfiles/nvim/init.lua
new file mode 100644
index 00000000..c3b8701d
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/nvim/init.lua
@@ -0,0 +1,70 @@
+
+require("CopilotChat").setup {
+ -- See Configuration section for options
+}
+
+local timer = vim.loop.new_timer() -- Initialize the timer
+
+vim.api.nvim_create_autocmd("BufEnter", {
+ pattern = "*",
+ callback = function()
+ if vim.bo.filetype == "copilot-chat" then
+ local copilot_chat_buf = vim.api.nvim_get_current_buf()
+ vim.cmd("wincmd _") -- Maximize height
+ vim.cmd("wincmd |") -- Maximize width
+ local file_path = vim.fn.expand("~/.copilot_chat_output.txt")
+
+ -- Start the timer with a 2-second interval
+ timer:start(1000, 1000, vim.schedule_wrap(function()
+ if copilot_chat_buf and vim.api.nvim_buf_is_valid(copilot_chat_buf) then
+ -- Get all lines in the buffer
+ local lines = vim.api.nvim_buf_get_lines(copilot_chat_buf, 0, -1, false)
+
+ -- Check for the stopping condition
+ local user_line_count = 0
+ for _, line in ipairs(lines) do
+ if line:find("^## User") then
+ user_line_count = user_line_count + 1
+ if user_line_count >= 2 then
+ print("Stopping write process: Two '## User' lines detected.")
+ timer:stop()
+ -- Write the buffer content to the file
+ vim.api.nvim_buf_call(copilot_chat_buf, function()
+ vim.cmd("write! " .. file_path)
+ end)
+ vim.cmd("qa!")
+ return
+ end
+ end
+ end
+
+ -- Write the buffer content to the file
+ vim.api.nvim_buf_call(copilot_chat_buf, function()
+ vim.cmd("write! " .. file_path)
+ end)
+ end
+ end))
+ end
+ end,
+})
+
+vim.api.nvim_create_user_command('CopilotAsk', function(args)
+ local chat = require("CopilotChat")
+ local input
+ if args.args and args.args ~= "" then
+ input = args.args
+ else
+ local input_file = os.getenv("HOME") .. "/.copilot_chat_input.txt"
+ local file = io.open(input_file, "r")
+ if file then
+ input = file:read("*all")
+ file:close()
+ else
+ print("Error: Unable to open input file.")
+ return
+ end
+ end
+ chat.ask(input)
+end, { force = true, range = true, nargs = "?" })
+
+
diff --git a/gemfeed/examples/conf/dotfiles/pipewire/pipewire.conf b/gemfeed/examples/conf/dotfiles/pipewire/pipewire.conf
new file mode 100644
index 00000000..a97c99e7
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/pipewire/pipewire.conf
@@ -0,0 +1,257 @@
+# Daemon config file for PipeWire version "0.3.51" #
+#
+# Copy and edit this file in /etc/pipewire for system-wide changes
+# or in ~/.config/pipewire for local changes.
+#
+# It is also possible to place a file with an updated section in
+# /etc/pipewire/pipewire.conf.d/ for system-wide changes or in
+# ~/.config/pipewire/pipewire.conf.d/ for local changes.
+#
+
+context.properties = {
+ ## Configure properties in the system.
+ #library.name.system = support/libspa-support
+ #context.data-loop.library.name.system = support/libspa-support
+ #support.dbus = true
+ #link.max-buffers = 64
+ link.max-buffers = 16 # version < 3 clients can't handle more
+ #mem.warn-mlock = false
+ #mem.allow-mlock = true
+ #mem.mlock-all = false
+ #clock.power-of-two-quantum = true
+ #log.level = 2
+ #cpu.zero.denormals = false
+
+ core.daemon = true # listening for socket connections
+ core.name = pipewire-0 # core name and socket name
+
+ ## Properties for the DSP configuration.
+ default.clock.rate = 48000
+ default.clock.allowed-rates = [ 44100 48000 88200 96000 176400 192000 352800 384000 ]
+ #default.clock.quantum = 1024
+ default.clock.min-quantum = 16
+ #default.clock.max-quantum = 2048
+ #default.clock.quantum-limit = 8192
+ #default.video.width = 640
+ #default.video.height = 480
+ #default.video.rate.num = 25
+ #default.video.rate.denom = 1
+ #
+ #settings.check-quantum = false
+ #settings.check-rate = false
+ #
+ # These overrides are only applied when running in a vm.
+ vm.overrides = {
+ default.clock.min-quantum = 1024
+ }
+}
+
+context.spa-libs = {
+ #<factory-name regex> = <library-name>
+ #
+ # Used to find spa factory names. It maps an spa factory name
+ # regular expression to a library name that should contain
+ # that factory.
+ #
+ audio.convert.* = audioconvert/libspa-audioconvert
+ api.alsa.* = alsa/libspa-alsa
+ api.v4l2.* = v4l2/libspa-v4l2
+ api.libcamera.* = libcamera/libspa-libcamera
+ api.bluez5.* = bluez5/libspa-bluez5
+ api.vulkan.* = vulkan/libspa-vulkan
+ api.jack.* = jack/libspa-jack
+ support.* = support/libspa-support
+ #videotestsrc = videotestsrc/libspa-videotestsrc
+ #audiotestsrc = audiotestsrc/libspa-audiotestsrc
+}
+
+context.modules = [
+ #{ name = <module-name>
+ # [ args = { <key> = <value> ... } ]
+ # [ flags = [ [ ifexists ] [ nofail ] ]
+ #}
+ #
+ # Loads a module with the given parameters.
+ # If ifexists is given, the module is ignored when it is not found.
+ # If nofail is given, module initialization failures are ignored.
+ #
+
+ # Uses realtime scheduling to boost the audio thread priorities. This uses
+ # RTKit if the user doesn't have permission to use regular realtime
+ # scheduling.
+ { name = libpipewire-module-rt
+ args = {
+ nice.level = -11
+ #rt.prio = 88
+ #rt.time.soft = -1
+ #rt.time.hard = -1
+ }
+ flags = [ ifexists nofail ]
+ }
+
+ # The native communication protocol.
+ { name = libpipewire-module-protocol-native }
+
+ # The profile module. Allows application to access profiler
+ # and performance data. It provides an interface that is used
+ # by pw-top and pw-profiler.
+ { name = libpipewire-module-profiler }
+
+ # Allows applications to create metadata objects. It creates
+ # a factory for Metadata objects.
+ { name = libpipewire-module-metadata }
+
+ # Creates a factory for making devices that run in the
+ # context of the PipeWire server.
+ { name = libpipewire-module-spa-device-factory }
+
+ # Creates a factory for making nodes that run in the
+ # context of the PipeWire server.
+ { name = libpipewire-module-spa-node-factory }
+
+ # Allows creating nodes that run in the context of the
+ # client. Is used by all clients that want to provide
+ # data to PipeWire.
+ { name = libpipewire-module-client-node }
+
+ # Allows creating devices that run in the context of the
+ # client. Is used by the session manager.
+ { name = libpipewire-module-client-device }
+
+ # The portal module monitors the PID of the portal process
+ # and tags connections with the same PID as portal
+ # connections.
+ { name = libpipewire-module-portal
+ flags = [ ifexists nofail ]
+ }
+
+ # The access module can perform access checks and block
+ # new clients.
+ { name = libpipewire-module-access
+ args = {
+ # access.allowed to list an array of paths of allowed
+ # apps.
+ #access.allowed = [
+ # /usr/bin/pipewire-media-session
+ #]
+
+ # An array of rejected paths.
+ #access.rejected = [ ]
+
+ # An array of paths with restricted access.
+ #access.restricted = [ ]
+
+ # Anything not in the above lists gets assigned the
+ # access.force permission.
+ #access.force = flatpak
+ }
+ }
+
+ # Makes a factory for wrapping nodes in an adapter with a
+ # converter and resampler.
+ { name = libpipewire-module-adapter }
+
+ # Makes a factory for creating links between ports.
+ { name = libpipewire-module-link-factory }
+
+ # Provides factories to make session manager objects.
+ { name = libpipewire-module-session-manager }
+
+ # Use libcanberra to play X11 Bell
+ #{ name = libpipewire-module-x11-bell
+ # args = {
+ # #sink.name = ""
+ # #sample.name = "bell-window-system"
+ # #x11.display = null
+ # #x11.xauthority = null
+ # }
+ #}
+]
+
+context.objects = [
+ #{ factory = <factory-name>
+ # [ args = { <key> = <value> ... } ]
+ # [ flags = [ [ nofail ] ]
+ #}
+ #
+ # Creates an object from a PipeWire factory with the given parameters.
+ # If nofail is given, errors are ignored (and no object is created).
+ #
+ #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc Spa:Pod:Object:Param:Props:patternType = 1 } }
+ #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] }
+ #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } }
+ #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } }
+ #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test } }
+ #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } }
+
+ # A default dummy driver. This handles nodes marked with the "node.always-driver"
+ # property when no other driver is currently active. JACK clients need this.
+ { factory = spa-node-factory
+ args = {
+ factory.name = support.node.driver
+ node.name = Dummy-Driver
+ node.group = pipewire.dummy
+ priority.driver = 20000
+ }
+ }
+ { factory = spa-node-factory
+ args = {
+ factory.name = support.node.driver
+ node.name = Freewheel-Driver
+ priority.driver = 19000
+ node.group = pipewire.freewheel
+ node.freewheel = true
+ }
+ }
+ # This creates a new Source node. It will have input ports
+ # that you can link, to provide audio for this source.
+ #{ factory = adapter
+ # args = {
+ # factory.name = support.null-audio-sink
+ # node.name = "my-mic"
+ # node.description = "Microphone"
+ # media.class = "Audio/Source/Virtual"
+ # audio.position = "FL,FR"
+ # }
+ #}
+
+ # This creates a single PCM source device for the given
+ # alsa device path hw:0. You can change source to sink
+ # to make a sink in the same way.
+ #{ factory = adapter
+ # args = {
+ # factory.name = api.alsa.pcm.source
+ # node.name = "alsa-source"
+ # node.description = "PCM Source"
+ # media.class = "Audio/Source"
+ # api.alsa.path = "hw:0"
+ # api.alsa.period-size = 1024
+ # api.alsa.headroom = 0
+ # api.alsa.disable-mmap = false
+ # api.alsa.disable-batch = false
+ # audio.format = "S16LE"
+ # audio.rate = 48000
+ # audio.channels = 2
+ # audio.position = "FL,FR"
+ # }
+ #}
+]
+
+context.exec = [
+ #{ path = <program-name> [ args = "<arguments>" ] }
+ #
+ # Execute the given program with arguments.
+ #
+ # You can optionally start the session manager here,
+ # but it is better to start it as a systemd service.
+ # Run the session manager with -h for options.
+ #
+ #{ path = "/usr/bin/pipewire-media-session" args = "" }
+ #
+ # You can optionally start the pulseaudio-server here as well
+ # but it is better to start it as a systemd service.
+ # It can be interesting to start another daemon here that listens
+ # on another address with the -a option (eg. -a tcp:4713).
+ #
+ #{ path = "/usr/bin/pipewire" args = "-c pipewire-pulse.conf" }
+]
diff --git a/gemfeed/examples/conf/dotfiles/scripts/README.md b/gemfeed/examples/conf/dotfiles/scripts/README.md
new file mode 100644
index 00000000..ecbc8ec0
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/README.md
@@ -0,0 +1,3 @@
+# Scripts installed to my ~/scripts
+
+Mostly quick-n-dirty ones!
diff --git a/gemfeed/examples/conf/dotfiles/scripts/ai b/gemfeed/examples/conf/dotfiles/scripts/ai
new file mode 100755
index 00000000..abcf4909
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/ai
@@ -0,0 +1,7 @@
+#!/usr/bin/env zsh
+
+if [ $(uname) = Darwin ]; then
+ exec hx.nvim-copilot-prompt "$@"
+else
+ exec hx.hexai-prompt "$@"
+fi
diff --git a/gemfeed/examples/conf/dotfiles/scripts/brokenlinkfinder b/gemfeed/examples/conf/dotfiles/scripts/brokenlinkfinder
new file mode 100644
index 00000000..7fe15765
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/brokenlinkfinder
@@ -0,0 +1,73 @@
+#!/usr/bin/env ruby
+
+require 'net/http'
+require 'uri'
+require 'nokogiri'
+require 'set'
+
+# Method to fetch and parse HTML from a URL
+def fetch_html(url)
+ response = Net::HTTP.get_response(URI(url))
+ response.body if response.is_a?(Net::HTTPSuccess)
+rescue StandardError => e
+ puts "Error fetching #{url}: #{e.message}"
+ nil
+end
+
+# Method to find and check links on a page
+def check_links(url, domain)
+ html = fetch_html(url)
+ return unless html
+
+ checked = Set.new
+ broken = Set.new
+
+ document = Nokogiri::HTML(html)
+ links = document.css('a').map { |link| link['href'] }.compact
+
+ internal_links = links.select do |link|
+ link.start_with?('/') || link.start_with?('./') || URI(link).host == domain
+ end
+ puts "Internal links: #{internal_links}"
+
+ internal_links.uniq.each do |link|
+ full_url = link.start_with?('/') || link.start_with?('./') ? "#{url}#{link}" : link
+ full_url.sub!('./', '/')
+ next if checked.include?(full_url)
+
+ broken << full_url unless check_link(full_url)
+ checked << full_url
+ end
+
+ broken
+end
+
+# Method to check if a link is broken
+def check_link(url)
+ uri = URI(url)
+ response = Net::HTTP.get_response(uri)
+
+ if response.is_a?(Net::HTTPSuccess)
+ puts "Working link: #{url}"
+ true
+ else
+ puts "Broken link: #{url} (HTTP #{response.code})"
+ false
+ end
+rescue StandardError => e
+ puts "Error checking #{url}: #{e.message}"
+ false
+end
+
+# Main program
+if ARGV.length != 1
+ puts 'Usage: ruby brokenlinkfinder.rb <URL>'
+ exit
+end
+
+start_url = ARGV.first
+domain = URI(start_url).host
+
+check_links(start_url, domain).each do |broken|
+ puts "Broken: #{broken}"
+end
diff --git a/gemfeed/examples/conf/dotfiles/scripts/gvim b/gemfeed/examples/conf/dotfiles/scripts/gvim
new file mode 100755
index 00000000..5777a7ce
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/gvim
@@ -0,0 +1,7 @@
+#!/bin/bash
+# Hack so qutebrowser starts an editor (Helix) in a new ghostty terminal.
+
+declare -r FILE_PATH="$2"
+#echo "$@" > /tmp/params.txt
+
+ghostty -e "hx $FILE_PATH"
diff --git a/gemfeed/examples/conf/dotfiles/scripts/hx.aichat-prompt b/gemfeed/examples/conf/dotfiles/scripts/hx.aichat-prompt
new file mode 100755
index 00000000..4cafcf5d
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/hx.aichat-prompt
@@ -0,0 +1,9 @@
+#!/usr/bin/env zsh
+
+declare -xr INSTRUCTIONS='Answer only. If it is code, code only without code-block at the beginning and the end.'
+
+if [[ $# -eq 0 ]]; then
+ aichat "$(hx.prompt). $INSTRUCTIONS"
+else
+ aichat "$@. $INSTRUCTIONS"
+fi
diff --git a/gemfeed/examples/conf/dotfiles/scripts/hx.chatgpt-prompt b/gemfeed/examples/conf/dotfiles/scripts/hx.chatgpt-prompt
new file mode 100755
index 00000000..e4b6047f
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/hx.chatgpt-prompt
@@ -0,0 +1,3 @@
+#!/usr/bin/env zsh
+
+chatgpt "$(hx.prompt). Answer only. If it is code, code only without code-block at the beginning and the end."
diff --git a/gemfeed/examples/conf/dotfiles/scripts/hx.goformatter b/gemfeed/examples/conf/dotfiles/scripts/hx.goformatter
new file mode 100755
index 00000000..028fbb25
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/hx.goformatter
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+goimports | gofumpt
diff --git a/gemfeed/examples/conf/dotfiles/scripts/hx.hexai-prompt b/gemfeed/examples/conf/dotfiles/scripts/hx.hexai-prompt
new file mode 100755
index 00000000..ef413c0a
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/hx.hexai-prompt
@@ -0,0 +1,9 @@
+#!/usr/bin/env zsh
+
+declare -xr INSTRUCTIONS='Answer only. If it is code, code only without code-block at the beginning and the end.'
+
+if [[ $# -eq 0 ]]; then
+ hexai "$(hx.prompt). $INSTRUCTIONS" 2>/dev/null
+else
+ hexai "$@. $INSTRUCTIONS" 2>/dev/null
+fi
diff --git a/gemfeed/examples/conf/dotfiles/scripts/hx.nvim-copilot-prompt b/gemfeed/examples/conf/dotfiles/scripts/hx.nvim-copilot-prompt
new file mode 100755
index 00000000..dcb28376
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/hx.nvim-copilot-prompt
@@ -0,0 +1,32 @@
+#!/usr/bin/env zsh
+
+declare -r STDIN_FILE=~/.copilot_prompt_stdin.txt
+declare -r INPUT_FILE=~/.copilot_chat_input.txt
+declare -r OUTPUT_FILE=~/.copilot_chat_output.txt
+declare INPUT_PROMPT
+
+if [ -f $OUTPUT_FILE.done ]; then
+ rm $OUTPUT_FILE.done
+fi
+cat > $STDIN_FILE &>/dev/null
+
+if [ $# -eq 0 ]; then
+ INPUT_PROMPT="$(hx.prompt)"
+else
+ INPUT_PROMPT="$@"
+fi
+
+cat <<INPUT_FILE > $INPUT_FILE
+$INPUT_PROMPT for the following:
+
+$(cat $STDIN_FILE)
+
+If the result is code, print out the code only, don't print the \`\`\`-markers around the code block.
+INPUT_FILE
+
+tmux split-window -v "nvim +':CopilotAsk'; mv $OUTPUT_FILE $OUTPUT_FILE.done"
+
+while [ ! -f "$OUTPUT_FILE.done" ]; do
+ sleep 0.2
+done
+sed -n '/^## Copilot/,/^## User/ { /^## Copilot/d; /\[file:/d; /^## User/d; p; }' $OUTPUT_FILE.done
diff --git a/gemfeed/examples/conf/dotfiles/scripts/hx.prompt b/gemfeed/examples/conf/dotfiles/scripts/hx.prompt
new file mode 100755
index 00000000..8dd14dd3
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/hx.prompt
@@ -0,0 +1,14 @@
+#!/usr/bin/env zsh
+
+declare -r REPLY_FILE=~/.hx-prompt-reply
+if [ -f "$REPLY_FILE" ]; then
+ rm "$REPLY_FILE"
+fi
+
+tmux split-window -v "touch $REPLY_FILE.tmp; hx $REPLY_FILE.tmp; mv $REPLY_FILE.tmp $REPLY_FILE"
+
+while [ ! -f "$REPLY_FILE" ]; do
+ sleep 0.2
+done
+
+cat "$REPLY_FILE"
diff --git a/gemfeed/examples/conf/dotfiles/scripts/randomnote.rb b/gemfeed/examples/conf/dotfiles/scripts/randomnote.rb
new file mode 100644
index 00000000..b0c1b490
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/randomnote.rb
@@ -0,0 +1,30 @@
+#!/usr/bin/env ruby
+
+NOTES_DIR = "#{ENV['HOME']}/git/foo.zone-content/gemtext/notes"
+BOOK_PATH = "#{ENV['HOME']}/Buecher/Diverse/Search-Inside-Yourself.txt"
+MIN_PERCENTAGE = 80
+MIN_LENGTH = 10
+
+class String
+ CLEAN_PATTERN = [
+ /\d\d\d-\d\d-\d\d/, /[^A-Za-z0-9!.;,?'" @]/,
+ /http.?:\/\/\S+/, /\S+\.gmi/, /^\./, /^\d/,
+ ]
+ def clean
+ CLEAN_PATTERN.each {|p| gsub! p, '' }
+ gsub(/\s+/, ' ').strip
+ end
+ def letter_percentage?(threshold) = threshold <= (100 * count("A-Za-z")) / length
+end
+
+begin
+ srand Random.new_seed
+ puts File.read((Dir["#{NOTES_DIR}/*.gmi"] + [BOOK_PATH]).shuffle.sample)
+ .split("\n")
+ .map(&:clean)
+ .select{ |l| l.length >= MIN_LENGTH }
+ .reject{ |l| l.match?(/(Published at|EMail your comments)/) }
+ .reject{ |l| l.match?(/'|book notes/) }
+ .select{ |l| l.letter_percentage?(MIN_PERCENTAGE) }
+ .shuffle.sample
+end
diff --git a/gemfeed/examples/conf/dotfiles/scripts/taskwarriorfeeder.rb b/gemfeed/examples/conf/dotfiles/scripts/taskwarriorfeeder.rb
new file mode 100644
index 00000000..8e3096ea
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/scripts/taskwarriorfeeder.rb
@@ -0,0 +1,221 @@
+#!/usr/bin/env ruby
+
+require 'optparse'
+require 'digest'
+require 'json'
+require 'set'
+
+PERSONAL_TIMESPAN_D = 30
+WORK_TIMESPAN_D = 14
+WORKTIME_DIR = "#{ENV['HOME']}/git/worktime".freeze
+GOS_DIR = "#{ENV['HOME']}/.gosdir".freeze
+MAX_PENDING_RANDOM_TASKS = 11
+
+def maybe?
+ [true, false].sample
+end
+
+def run_from_personal_device?
+ `uname`.chomp == 'Linux'
+end
+
+def random_count
+ MAX_PENDING_RANDOM_TASKS - `task status:pending +random count`.to_i
+end
+
+def notes(notes_dirs, prefix, dry)
+ notes_dirs.each do |notes_dir|
+ Dir["#{notes_dir}/#{prefix}-*"].each do |notes_file|
+ match = File.read(notes_file).strip.match(/(?<due>\d+)? *(?<tag>[A-Z]?[a-z,-:]+) *(?<body>.*)/m)
+ next unless match
+
+ tags = match[:tag].split(',') + [prefix]
+ due = if match[:due].nil?
+ tags.include?('track') ? '1year' : "#{rand(0..PERSONAL_TIMESPAN_D)}d"
+ else
+ "#{match[:due]}d"
+ end
+ yield tags, match[:body], due
+ File.delete(notes_file) unless dry
+ end
+ end
+end
+
+def random_quote(md_file)
+ tag = File.basename(md_file, '.md').downcase
+ lines = File.readlines(md_file)
+
+ match = lines.first.match(/\((\d+)\)/)
+ timespan = run_from_personal_device? ? PERSONAL_TIMESPAN_D : WORK_TIMESPAN_D
+ timespan = match ? match[1].to_i : timespan
+
+ quote = lines.select { |l| l.start_with? '*' }.map { |l| l.sub(/\* +/, '') }.sample
+ tags = [tag, 'random']
+ tags << 'work' if maybe? and maybe?
+ yield tags, quote.chomp, "#{rand(0..timespan)}d"
+end
+
+def run!(cmd, dry)
+ puts cmd
+ return if dry
+
+ puts `#{cmd}`
+ raise "Command '#{cmd}' failed with #{$?.exitstatus}" if $?.exitstatus != 0
+rescue StandardError => e
+ puts "Error running command '#{cmd}': #{e.message}"
+ exit 1
+end
+
+def skill_add!(skills_str, dry)
+ skills_file = "#{WORKTIME_DIR}/skills.txt"
+ skills_str.split(',').map(&:strip).each { skills[_1.to_s.downcase] = _1 }
+
+ File.foreach(skills_file) do |line|
+ line.chomp!
+ skills[line.downcase] = line
+ end
+ File.open("#{skills_file}.tmp", 'w') do |file|
+ skills.each_value { |skill| file.puts(skill) }
+ end
+ return if dry
+
+ File.rename("#{skills_file}.tmp", skills_file)
+end
+
+def worklog_add!(tag, quote, due, dry)
+ file = "#{WORKTIME_DIR}/wl-#{Time.now.to_i}n.txt"
+ content = "#{due.chomp 'd'} #{tag} #{quote}"
+
+ puts "#{file}: #{content}"
+ File.write(file, content) unless dry
+end
+
+# Queue to Gos https://codeberg.org/snonux/gos
+def gos_queue!(tags, message, dry)
+ tags.delete('share')
+ platforms = []
+ %w[linkedin li mastodon ma noop no].select { tags.include?(_1) }.each do |platform|
+ platforms << platform
+ tags.delete(platform)
+ end
+ unless platforms.empty?
+ platforms = %w[share] + platforms
+ tags = ["#{platforms.join(':')}"] + tags
+ end
+ tags = %w[share] + tags if tags.size == 1 && !tags.first.start_with?('share')
+ tags_str = tags.join(',')
+
+ message = "#{tags_str.empty? ? '' : "#{tags_str} "}#{message}"
+ file = "#{GOS_DIR}/#{Digest::MD5.hexdigest(message)}.txt"
+ puts "Writing #{file} with #{message}"
+ File.write(file, message) unless dry
+end
+
+def task_add!(tags, quote, due, dry)
+ if quote.empty?
+ puts 'Not adding task with empty quote'
+ return
+ end
+ if tags.include?('tr')
+ tags << 'track'
+ tags.delete('tr')
+ end
+ tags << 'work' if tags.include?('mentoring') || tags.include?('productivity')
+ tags.uniq!
+
+ if tags.include?('task')
+ run! "task #{quote}", dry
+ else
+ project = tags.find { |t| t =~ /^[A-Z]/ }
+ project = if project.nil?
+ ''
+ else
+ tags.delete(project)
+ " project:#{project.downcase}"
+ end
+ priority = tags.include?('high') ? 'H' : ''
+ run! "task add due:#{due} priority:#{priority}#{project} +#{tags.join(' +')} '#{quote.gsub("'", '"')}'", dry
+ end
+end
+
+def task_schedule!(id, due, dry)
+ run! "timeout 5s task modify #{id} due:#{due}", dry
+end
+
+# Randomly schedule all unscheduled tasks but the ones with the +unsched tag
+def unscheduled_tasks
+ lines = `task -lowhigh -unsched -nosched -notes -note -meeting -track due: 2>/dev/null`.split("\n").drop(1)
+ lines.pop
+ lines.map { |foo| foo.split.first }.each do |id|
+ yield id if id.to_i.positive?
+ end
+end
+
+begin
+ opts = {
+ quotes_dir: "#{ENV['HOME']}/Notes/HabitsAndQuotes",
+ notes_dirs: "#{ENV['HOME']}/Notes,#{ENV['HOME']}/Notes/Quicklogger,#{ENV['HOME']}/git/worktime",
+ dry_run: false,
+ no_random: false
+ }
+
+ opt_parser = OptionParser.new do |o|
+ o.banner = 'Usage: ruby taskwarriorfeeder.rb [options]'
+ o.on('-d', '--quotes-dir DIR', 'The quotes directory') { |v| opts[:quotes_dir] = v }
+ o.on('-n', '--notes-dirs DIR1,DIR2,...', 'The notes directories') { |v| opts[:notes_dirs] = v }
+ o.on('-D', '--dry-run', 'Dry run mode') { opts[:dry_run] = true }
+ o.on('-R', '--no-randoms', 'No random entries') { opts[:no_random] = true }
+ o.on_tail('-h', '--help', 'Show this help message and exit') { puts o and exit }
+ end
+
+ opt_parser.parse!(ARGV)
+ core_habits_md_file = "#{opts[:quotes_dir]}/CoreHabits.md"
+
+ (run_from_personal_device? ? %w[ql pl] : %w[wl]).each do |prefix|
+ notes(opts[:notes_dirs].split(','), prefix, opts[:dry_run]) do |tags, note, due|
+ if tags.include?('skill') || tags.include?('skills')
+ skill_add!(note, opts[:dry_run])
+ elsif tags.include? 'work'
+ worklog_add!(:log, note, due, opts[:dry_run])
+ elsif tags.any? { |tag| tag.start_with?('share') }
+ gos_queue!(tags, note, opts[:dry_run])
+ else
+ task_add!(tags, note, due, opts[:dry_run])
+ end
+ end
+ end
+
+ unless opts[:no_random]
+ if File.exist?(core_habits_md_file)
+ random_quote(core_habits_md_file) do |tags, quote, due|
+ task_add!(tags, quote, due, opts[:dry_run])
+ end
+ end
+ count = random_count
+
+ Dir["#{opts[:quotes_dir]}/*.md"].shuffle.each do |md_file|
+ next unless maybe?
+ break if count <= 0
+
+ random_quote(md_file) do |tags, quote, due|
+ task_add!(tags, quote, due, opts[:dry_run])
+ count -= 1
+ end
+ end
+ end
+
+ if Dir.exist?(GOS_DIR) && !opts[:dry_run]
+ Dir["#{WORKTIME_DIR}/tw-gos-*.json"].each do |tw_gos|
+ JSON.parse(File.read(tw_gos)).each do |entry|
+ gos_queue!(entry['tags'], entry['description'], opts[:dry_run])
+ end
+ File.delete(tw_gos)
+ rescue StandardError => e
+ puts e
+ end
+ end
+
+ unscheduled_tasks do |id|
+ task_schedule!(id, "#{rand(0..PERSONAL_TIMESPAN_D)}d", opts[:dry_run])
+ end
+end
diff --git a/gemfeed/examples/conf/dotfiles/signature b/gemfeed/examples/conf/dotfiles/signature
new file mode 100644
index 00000000..8031719e
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/signature
@@ -0,0 +1,2 @@
+Paul Buetow
+paul.buetow.org
diff --git a/gemfeed/examples/conf/dotfiles/ssh/config b/gemfeed/examples/conf/dotfiles/ssh/config
new file mode 100644
index 00000000..5b4b250e
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/ssh/config
@@ -0,0 +1,21 @@
+ControlPath ~/.ssh/cp-%C
+ControlMaster auto
+#UseKeychain yes
+AddKeysToAgent yes
+ControlPersist 60m
+#StrictHostKeyChecking no
+
+Host blowfish.buetow.org
+User rex
+Port 2
+
+Host fishfinger.buetow.org
+User rex
+Port 2
+
+Host *.aws.buetow.org
+User ec2-user
+Port 22
+
+Host *.buetow.org
+Port 2
diff --git a/gemfeed/examples/conf/dotfiles/sway/config.d/keyboard.conf b/gemfeed/examples/conf/dotfiles/sway/config.d/keyboard.conf
new file mode 100644
index 00000000..6b10a788
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/sway/config.d/keyboard.conf
@@ -0,0 +1,6 @@
+input "type:keyboard" {
+ xkb_layout us,gb,de
+ xkb_options grp:win_space_toggle
+}
+
+input * xkb_options "caps:escape"
diff --git a/gemfeed/examples/conf/dotfiles/tmux/tmux.conf b/gemfeed/examples/conf/dotfiles/tmux/tmux.conf
new file mode 100644
index 00000000..42c53866
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/tmux/tmux.conf
@@ -0,0 +1,32 @@
+source ~/.config/tmux/tmux.local.conf
+
+set-option -g allow-rename off
+set-option -g history-limit 100000
+set-option -s escape-time 0
+set-option -g set-titles on
+
+set-window-option -g mode-keys vi
+
+bind-key h select-pane -L
+bind-key j select-pane -D
+bind-key k select-pane -U
+bind-key l select-pane -R
+
+bind-key H resize-pane -L 5
+bind-key J resize-pane -D 5
+bind-key K resize-pane -U 5
+bind-key L resize-pane -R 5
+
+bind-key b break-pane -d
+bind-key c new-window -c '#{pane_current_path}'
+bind-key F new-window -n "session-switcher" "tmux list-sessions | fzf | cut -d: -f1 | xargs tmux switch-client -t"
+bind-key p setw synchronize-panes off
+bind-key P setw synchronize-panes on
+bind-key r source-file ~/.tmux.conf \; display-message "~/.tmux.conf reloaded"
+bind-key T choose-tree
+
+set-option -g pane-active-border-style fg=magenta,bold
+
+set -g status-right '#{@hexai_status} #[fg=colour8]| %H:%M'
+set -g status-right-length 120
+set-environment -g HEXAI_TMUX_STATUS_THEME white-on-purple
diff --git a/gemfeed/examples/conf/dotfiles/tmux/tmux.local.conf b/gemfeed/examples/conf/dotfiles/tmux/tmux.local.conf
new file mode 100644
index 00000000..adb6294b
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/tmux/tmux.local.conf
@@ -0,0 +1,2 @@
+bind-key -T copy-mode-vi 'v' send -X begin-selection
+bind-key -T copy-mode-vi 'y' send -X copy-selection-and-cancel
diff --git a/gemfeed/examples/conf/dotfiles/vale.ini b/gemfeed/examples/conf/dotfiles/vale.ini
new file mode 100644
index 00000000..3b396788
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/vale.ini
@@ -0,0 +1,6 @@
+StylesPath = styles
+MinAlertLevel = suggestion
+Packages = Microsoft, proselint
+
+[*]
+BasedOnStyles = Vale, Microsoft, proselint
diff --git a/gemfeed/examples/conf/dotfiles/waybar/config.jsonc b/gemfeed/examples/conf/dotfiles/waybar/config.jsonc
new file mode 100644
index 00000000..db2aeea6
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/waybar/config.jsonc
@@ -0,0 +1,194 @@
+// -*- mode: jsonc -*-
+{
+ // "layer": "top", // Waybar at top layer
+ // "position": "bottom", // Waybar position (top|bottom|left|right)
+ "height": 20, // Waybar height (to be removed for auto height)
+ // "width": 1280, // Waybar width
+ "spacing": 1, // Gaps between modules (4px)
+ // Choose the order of the modules
+ "modules-left": [
+ "sway/workspaces",
+ "sway/mode",
+ "sway/scratchpad"
+ ],
+ "modules-center": [
+ ],
+ "modules-right": [
+ "idle_inhibitor",
+ "pulseaudio",
+ "network",
+ "power-profiles-daemon",
+ "temperature",
+ "sway/language",
+ "battery",
+ "clock",
+ "tray"
+ ],
+ // Modules configuration
+ // "sway/workspaces": {
+ // "disable-scroll": true,
+ // "all-outputs": true,
+ // "warp-on-scroll": false,
+ // "format": "{name}: {icon}",
+ // "format-icons": {
+ // "1": "",
+ // "2": "",
+ // "3": "",
+ // "4": "",
+ // "5": "",
+ // "urgent": "",
+ // "focused": "",
+ // "default": ""
+ // }
+ // },
+ "keyboard-state": {
+ "numlock": true,
+ "capslock": true,
+ "format": "{name} {icon}",
+ "format-icons": {
+ "locked": "",
+ "unlocked": ""
+ }
+ },
+ "sway/mode": {
+ "format": "<span style=\"italic\">{}</span>"
+ },
+ "sway/scratchpad": {
+ "format": "{icon} {count}",
+ "show-empty": false,
+ "format-icons": ["", ""],
+ "tooltip": true,
+ "tooltip-format": "{app}: {title}"
+ },
+ "mpd": {
+ "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
+ "format-disconnected": "Disconnected ",
+ "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
+ "unknown-tag": "N/A",
+ "interval": 5,
+ "consume-icons": {
+ "on": " "
+ },
+ "random-icons": {
+ "off": "<span color=\"#f53c3c\"></span> ",
+ "on": " "
+ },
+ "repeat-icons": {
+ "on": " "
+ },
+ "single-icons": {
+ "on": "1 "
+ },
+ "state-icons": {
+ "paused": "",
+ "playing": ""
+ },
+ "tooltip-format": "MPD (connected)",
+ "tooltip-format-disconnected": "MPD (disconnected)"
+ },
+ "idle_inhibitor": {
+ "format": "{icon}",
+ "format-icons": {
+ "activated": "",
+ "deactivated": ""
+ }
+ },
+ "tray": {
+ // "icon-size": 21,
+ "spacing": 10
+ },
+ "clock": {
+ // "timezone": "America/New_York",
+ "tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>",
+ "format-alt": "{:%Y-%m-%d}"
+ },
+ "cpu": {
+ "format": "{usage}% ",
+ "tooltip": false
+ },
+ "memory": {
+ "format": "{}% "
+ },
+ "temperature": {
+ // "thermal-zone": 2,
+ // "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
+ "critical-threshold": 80,
+ // "format-critical": "{temperatureC}°C {icon}",
+ "format": "{temperatureC}°C {icon}",
+ "format-icons": ["", "", ""]
+ },
+ "backlight": {
+ // "device": "acpi_video1",
+ "format": "{percent}% {icon}",
+ "format-icons": ["🌑", "🌘", "🌗", "🌖", "🌕"]
+ },
+ "battery": {
+ "states": {
+ // "good": 95,
+ "warning": 30,
+ "critical": 15
+ },
+ "format": "{capacity}% {icon}",
+ "format-full": "{capacity}% {icon}",
+ "format-charging": "{capacity}% ",
+ "format-plugged": "{capacity}% ",
+ "format-alt": "{time} {icon}",
+ // "format-good": "", // An empty format will hide the module
+ // "format-full": "",
+ "format-icons": ["", "", "", "", ""]
+ },
+ "battery#bat2": {
+ "bat": "BAT2"
+ },
+ "power-profiles-daemon": {
+ "format": "{icon}",
+ "tooltip-format": "Power profile: {profile}\nDriver: {driver}",
+ "tooltip": true,
+ "format-icons": {
+ "default": "",
+ "performance": "",
+ "balanced": "",
+ "power-saver": ""
+ }
+ },
+ "network": {
+ // "interface": "wlp2*", // (Optional) To force the use of this interface
+ "format-wifi": "{essid} ({signalStrength}%) ",
+ "format-ethernet": "{ipaddr}/{cidr} ",
+ "tooltip-format": "{ifname} via {gwaddr} ",
+ "format-linked": "{ifname} (No IP) ",
+ "format-disconnected": "Disconnected ⚠",
+ "format-alt": "{ifname}: {ipaddr}/{cidr}"
+ },
+ "pulseaudio": {
+ // "scroll-step": 1, // %, can be a float
+ "format": "{volume}% {icon} {format_source}",
+ "format-bluetooth": "{volume}% {icon} {format_source}",
+ "format-bluetooth-muted": " {icon} {format_source}",
+ "format-muted": " {format_source}",
+ "format-source": "{volume}% ",
+ "format-source-muted": "",
+ "format-icons": {
+ "headphone": "",
+ "hands-free": "",
+ "headset": "",
+ "phone": "",
+ "portable": "",
+ "car": "",
+ "default": ["", "", ""]
+ },
+ "on-click": "pavucontrol"
+ },
+ "custom/media": {
+ "format": "{icon} {}",
+ "return-type": "json",
+ "max-length": 40,
+ "format-icons": {
+ "spotify": "",
+ "default": "🎜"
+ },
+ "escape": true,
+ "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null" // Script in resources folder
+ // "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
+ }
+}
diff --git a/gemfeed/examples/conf/dotfiles/waybar/style.css b/gemfeed/examples/conf/dotfiles/waybar/style.css
new file mode 100644
index 00000000..e0310372
--- /dev/null
+++ b/gemfeed/examples/conf/dotfiles/waybar/style.css
@@ -0,0 +1,326 @@
+* {
+ font-family: 'Noto Sans Mono', 'Font Awesome 6 Free', 'Font Awesome 6 Brands', monospace;
+ font-size: 13px;
+}
+
+window#waybar {
+ background-color: rgba(43, 48, 59, 0.5);
+ border-bottom: 3px solid rgba(100, 114, 125, 0.5);
+ color: #ffffff;
+ transition-property: background-color;
+ transition-duration: .5s;
+}
+
+window#waybar.hidden {
+ opacity: 0.2;
+}
+
+/*
+window#waybar.empty {
+ background-color: transparent;
+}
+window#waybar.solo {
+ background-color: #FFFFFF;
+}
+*/
+
+window#waybar.termite {
+ background-color: #3F3F3F;
+}
+
+window#waybar.chromium {
+ background-color: #000000;
+ border: none;
+}
+
+button {
+ /* Use box-shadow instead of border so the text isn't offset */
+ box-shadow: inset 0 -3px transparent;
+ /* Avoid rounded borders under each button name */
+ border: none;
+ border-radius: 0;
+}
+
+/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
+button:hover {
+ background: inherit;
+ box-shadow: inset 0 -3px #ffffff;
+}
+
+/* you can set a style on hover for any module like this */
+#pulseaudio:hover {
+ background-color: #a37800;
+}
+
+#workspaces button {
+ padding: 0 5px;
+ background-color: transparent;
+ color: #ffffff;
+}
+
+#workspaces button:hover {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+#workspaces button.focused {
+ background-color: #64727D;
+ box-shadow: inset 0 -3px #ffffff;
+}
+
+#workspaces button.urgent {
+ background-color: #eb4d4b;
+}
+
+#mode {
+ background-color: #64727D;
+ box-shadow: inset 0 -3px #ffffff;
+}
+
+#clock,
+#battery,
+#cpu,
+#memory,
+#disk,
+#temperature,
+#backlight,
+#network,
+#pulseaudio,
+#wireplumber,
+#custom-media,
+#tray,
+#mode,
+#idle_inhibitor,
+#scratchpad,
+#power-profiles-daemon,
+#mpd {
+ padding: 0 10px;
+ color: #ffffff;
+}
+
+#window,
+#workspaces {
+ margin: 0 4px;
+}
+
+/* If workspaces is the leftmost module, omit left margin */
+.modules-left > widget:first-child > #workspaces {
+ margin-left: 0;
+}
+
+/* If workspaces is the rightmost module, omit right margin */
+.modules-right > widget:last-child > #workspaces {
+ margin-right: 0;
+}
+
+#clock {
+ background-color: #64727D;
+}
+
+#battery {
+ background-color: #ffffff;
+ color: #000000;
+}
+
+#battery.charging, #battery.plugged {
+ color: #ffffff;
+ background-color: #26A65B;
+}
+
+@keyframes blink {
+ to {
+ background-color: #ffffff;
+ color: #000000;
+ }
+}
+
+/* Using steps() instead of linear as a timing function to limit cpu usage */
+#battery.critical:not(.charging) {
+ background-color: #f53c3c;
+ color: #ffffff;
+ animation-name: blink;
+ animation-duration: 0.5s;
+ animation-timing-function: steps(12);
+ animation-iteration-count: infinite;
+ animation-direction: alternate;
+}
+
+#power-profiles-daemon {
+ padding-right: 15px;
+}
+
+#power-profiles-daemon.performance {
+ background-color: #f53c3c;
+ color: #ffffff;
+}
+
+#power-profiles-daemon.balanced {
+ background-color: #2980b9;
+ color: #ffffff;
+}
+
+#power-profiles-daemon.power-saver {
+ background-color: #2ecc71;
+ color: #000000;
+}
+
+label:focus {
+ background-color: #000000;
+}
+
+#cpu {
+ background-color: #2ecc71;
+ color: #000000;
+}
+
+#memory {
+ background-color: #9b59b6;
+}
+
+#disk {
+ background-color: #964B00;
+}
+
+#backlight {
+ background-color: #90b1b1;
+}
+
+#network {
+ background-color: #2980b9;
+}
+
+#network.disconnected {
+ background-color: #f53c3c;
+}
+
+#pulseaudio {
+ background-color: #f1c40f;
+ color: #000000;
+}
+
+#pulseaudio.muted {
+ background-color: #90b1b1;
+ color: #2a5c45;
+}
+
+#wireplumber {
+ background-color: #fff0f5;
+ color: #000000;
+}
+
+#wireplumber.muted {
+ background-color: #f53c3c;
+}
+
+#custom-media {
+ background-color: #66cc99;
+ color: #2a5c45;
+ min-width: 100px;
+}
+
+#custom-media.custom-spotify {
+ background-color: #66cc99;
+}
+
+#custom-media.custom-vlc {
+ background-color: #ffa000;
+}
+
+#temperature {
+ background-color: #f0932b;
+}
+
+#temperature.critical {
+ background-color: #eb4d4b;
+}
+
+#tray {
+ background-color: #2980b9;
+}
+
+#tray > .passive {
+ -gtk-icon-effect: dim;
+}
+
+#tray > .needs-attention {
+ -gtk-icon-effect: highlight;
+ background-color: #eb4d4b;
+}
+
+#idle_inhibitor {
+ background-color: #2d3436;
+}
+
+#idle_inhibitor.activated {
+ background-color: #ecf0f1;
+ color: #2d3436;
+}
+
+#mpd {
+ background-color: #66cc99;
+ color: #2a5c45;
+}
+
+#mpd.disconnected {
+ background-color: #f53c3c;
+}
+
+#mpd.stopped {
+ background-color: #90b1b1;
+}
+
+#mpd.paused {
+ background-color: #51a37a;
+}
+
+#language {
+ background: #00b093;
+ color: #740864;
+ padding: 0 5px;
+ margin: 0 5px;
+ min-width: 16px;
+}
+
+#keyboard-state {
+ background: #97e1ad;
+ color: #000000;
+ padding: 0 0px;
+ margin: 0 5px;
+ min-width: 16px;
+}
+
+#keyboard-state > label {
+ padding: 0 5px;
+}
+
+#keyboard-state > label.locked {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+#scratchpad {
+ background: rgba(0, 0, 0, 0.2);
+}
+
+#scratchpad.empty {
+ background-color: transparent;
+}
+
+#privacy {
+ padding: 0;
+}
+
+#privacy-item {
+ padding: 0 5px;
+ color: white;
+}
+
+#privacy-item.screenshare {
+ background-color: #cf5700;
+}
+
+#privacy-item.audio-in {
+ background-color: #1ca000;
+}
+
+#privacy-item.audio-out {
+ background-color: #0069d4;
+}