2
0
This commit is contained in:
ai
2026-04-03 08:36:53 +00:00
commit 67c20e9dab
15 changed files with 648 additions and 0 deletions

11
.gitconfig Normal file
View File

@@ -0,0 +1,11 @@
[user]
name = ai
email = ai@syui.ai
[core]
editor = vim
[pull]
rebase = false
[init]
defaultBranch = main
[push]
autoSetupRemote = true

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
.DS_Store
.env
.claude
Cargo.lock
node_modules
package-lock.json
/target
/dist
/tmp
*.swp

50
.tmux.conf Normal file
View File

@@ -0,0 +1,50 @@
# prefix
unbind C-b
set -g prefix ^T
bind t send-prefix
# general
set -s escape-time 0
set -g base-index 1
set -g pane-base-index 1
set -g renumber-windows on
set -g history-limit 10000
set -g default-terminal "xterm-256color"
set -g allow-passthrough on
set -g set-clipboard on
set-window-option -g mode-keys vi
set-window-option -g xterm-keys on
# pane/window
bind s split-window -v -c "#{pane_current_path}"
bind v split-window -h -c "#{pane_current_path}"
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
bind -r C-h resize-pane -L 5
bind -r C-j resize-pane -D 5
bind -r C-k resize-pane -U 5
bind -r C-l resize-pane -R 5
bind K kill-pane
bind c new-window
bind w choose-window
bind C-t run "tmux last-pane || tmux last-window || tmux new-window"
# copy
bind ^y copy-mode
bind -r ^"[" copy-mode
bind p paste-buffer
bind-key -T copy-mode-vi v send-keys -X begin-selection
bind-key -T copy-mode-vi Space send-keys -X begin-selection
if-shell 'which xclip' \
'bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xclip -i -sel c"; bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "xclip -i -sel c"'
# url open
bind u run-shell "~/.tmux/plugin/urlopen.zsh"
# plugins
run-shell "~/.tmux/plugin/ailog.zsh"
# reload
bind r source-file ~/.tmux.conf\; display-message "reloaded"

131
.tmux/plugin/ailog.zsh Executable file
View File

@@ -0,0 +1,131 @@
#!/bin/zsh
# tmux plugin: ailog powerline status bar
# requires: aifont, ailog, jq
INTERVAL=60
SEP=$'\ue0b0'
RSEP=$'\ue0b2'
get_icon() {
local handle=$(ailog pds s 2>/dev/null | jq -r '.handle // empty' 2>/dev/null)
case "$handle" in
ai.*) echo $'\ue001' ;;
syui.*) echo $'\ue002' ;;
*) echo "" ;;
esac
}
get_cfg() {
case "$(uname)" in
Darwin) echo "$HOME/Library/Application Support/ai.syui.log/config.json" ;;
*) echo "${XDG_CONFIG_HOME:-$HOME/.config}/ai.syui.log/config.json" ;;
esac
}
has_aifont() {
[ -f "$HOME/Library/Fonts/aifont.ttf" ] || [ -f "/usr/share/fonts/TTF/aifont.ttf" ]
}
can_run() {
local cfg=$(get_cfg)
command -v ailog &>/dev/null && command -v jq &>/dev/null && [ -f "$cfg" ] && has_aifont
}
refresh_token() {
local stamp="/tmp/.ailog_refresh"
local now=$(date +%s)
local last=0
[ -f "$stamp" ] && last=$(cat "$stamp")
if [ $((now - last)) -gt 1800 ]; then
ailog pds r 2>/dev/null
echo "$now" > "$stamp"
fi
}
get_handle() {
ailog pds s 2>/dev/null | jq -r '.handle // empty' 2>/dev/null
}
get_notify_count() {
ailog notify count 2>/dev/null | jq -r '.count // 0' 2>/dev/null
}
get_latest_post() {
local data=$(ailog f tl -l 1 2>/dev/null | tr -d '\t' | sed 's/\\n/ /g')
local handle=$(echo "$data" | jq -r '.feed[0].post.author.handle // empty' 2>/dev/null | cut -d. -f1)
local full=$(echo "$data" | jq -r '.feed[0].post.record.text // empty' 2>/dev/null | tr '\n' ' ')
local text="${full:0:20}"
[[ ${#full} -gt 20 ]] && text+="..."
[ -n "$text" ] && echo "@${handle} ${text}"
}
status_left() {
can_run || return
refresh_token
local handle=$(get_handle)
local count=$(get_notify_count)
local post=$(get_latest_post)
local icon=$(get_icon)
local out=""
out+="#[fg=yellow,bg=black] ${icon} "
out+="#[fg=black,bg=colour234]${SEP}"
out+="#[fg=yellow,bg=colour234] @${handle} "
if [ -n "$count" ] && [ "$count" != "0" ] && [ "$count" != "null" ]; then
out+="#[fg=colour234,bg=black]${SEP}"
out+="#[fg=yellow,bg=black] ${count} "
else
out+="#[fg=colour234,bg=black]${SEP}"
fi
if [ -n "$post" ]; then
out+="#[fg=white,bg=black] ${post} "
fi
out+="#[fg=black,bg=default]${SEP}#[default] "
echo -n "$out"
}
get_pds() {
ailog pds s 2>/dev/null | jq -r '.pds // empty' 2>/dev/null | sed 's|https://||'
}
get_pds_icon() {
local pds="$1"
local ai_icon=$(printf '\ue001')
local bsky_icon=$(printf '\ue003')
case "$pds" in
*syu.is*) echo "#[fg=yellow]${ai_icon} #[fg=cyan]${pds}" ;;
*bsky*) echo "#[fg=blue]${bsky_icon} #[fg=cyan]${pds}" ;;
*) echo "#[fg=cyan]${pds}" ;;
esac
}
status_right() {
local pds=$(get_pds)
local out=""
if [ -n "$pds" ]; then
local pds_display=$(get_pds_icon "$pds")
out+="#[fg=colour236]${RSEP}#[bg=colour236] ${pds_display} "
fi
out+="#[fg=colour234]${RSEP}#[fg=white,bg=colour234] #S "
echo -n "$out"
}
case "$1" in
left) status_left ;;
right) status_right ;;
notify) get_notify_count ;;
handle) get_handle ;;
latest) get_latest_post ;;
*)
if can_run; then
tmux set-option -g status-style "bg=default,fg=white"
tmux set-window-option -g window-status-format ""
tmux set-window-option -g window-status-current-format ""
tmux set-option -g status-left-length 100
tmux set-option -g status-right-length 50
tmux set-option -g status-left "#(~/.tmux/plugin/ailog.zsh left)"
tmux set-option -g status-right "#(~/.tmux/plugin/ailog.zsh right)#[fg=black,bg=colour234]${RSEP}#[fg=white,bg=black] %Y-%m-%dT%H:%M "
tmux set-option -g status-interval $INTERVAL
fi
;;
esac

7
.tmux/plugin/urlopen.zsh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/zsh
url=$(tmux capture-pane -pJ -S -100 | grep -oE 'https?://[^ ]+' | tail -1)
[ -z "$url" ] && exit 0
case "$(uname)" in
Darwin) open "$url" ;;
*) xdg-open "$url" ;;
esac

25
.vim/plugin/autosave.vim Normal file
View File

@@ -0,0 +1,25 @@
if exists('g:loaded_autosave')
finish
endif
let g:loaded_autosave = 1
let g:autosave = get(g:, 'autosave', 0)
fu! s:autosave_start()
aug autosave
au!
au TextChanged,CursorHold,InsertLeave * sil! update
aug END
endf
fu! s:autosave_stop()
au! autosave
echo 'autosave: off'
endf
com! AirsaveOn call <SID>autosave_start()
com! AirsaveOff call <SID>autosave_stop()
if g:autosave
call <SID>autosave_start()
endif

38
.vimrc Normal file
View File

@@ -0,0 +1,38 @@
set nocompatible
set encoding=utf-8
set fileencoding=utf-8
set number
set ruler
set cursorline
set showmatch
set laststatus=2
set wildmenu
set showcmd
set tabstop=4
set shiftwidth=4
set softtabstop=4
set autoindent
set smartindent
set incsearch
set hlsearch
set ignorecase
set smartcase
set wrapscan
set backspace=indent,eol,start
set clipboard=unnamed
set mouse=a
set ttimeoutlen=10
set undofile
set undodir=~/.vim/undo
let g:autosave = 1
inoremap <C-j> <Esc>
nnoremap Q :q!<CR>
vnoremap v V
syntax on
filetype plugin indent on
au BufReadPost * if line("'\"") > 0 && line("'\"") <= line("$") | exe "normal! g`\"" | endif

69
.zsh/plugin/cdselect.zsh Normal file
View File

@@ -0,0 +1,69 @@
autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
add-zsh-hook chpwd chpwd_recent_dirs
zstyle ':chpwd:*' recent-dirs-max 50
cdselect() {
local dirs=("${(@f)$(cdr -l 2>/dev/null | sed 's/^[0-9]* *//')}")
[[ ${#dirs} -eq 0 ]] && return
local query="" cursor=1 selected=""
while true; do
local filtered=()
for d in "${dirs[@]}"; do
local match=1
for word in ${(s: :)query}; do
[[ "${d:l}" != *"${word:l}"* ]] && match=0 && break
done
[[ $match -eq 1 ]] && filtered+=("$d")
done
# sort by path length (shortest first)
local sorted=()
for f in "${filtered[@]}"; do sorted+=("${#f} $f"); done
sorted=(${(n)sorted})
filtered=()
for s in "${sorted[@]}"; do filtered+=("${s#* }"); done
[[ $cursor -gt ${#filtered} ]] && cursor=${#filtered}
[[ $cursor -lt 1 ]] && cursor=1
clear
echo "cd> $query"
local i=1
for d in "${filtered[@]}"; do
if [[ $i -eq $cursor ]]; then
printf "\e[7m %s\e[0m\n" "$d"
else
printf " %s\n" "$d"
fi
((i++))
[[ $i -gt 20 ]] && break
done
read -k1 key 2>/dev/null
if [[ "$key" == $'\x1b' ]]; then
read -k1 -t 0.1 k2 2>/dev/null
if [[ "$k2" == "[" ]]; then
read -k1 -t 0.1 k3 2>/dev/null
case "$k3" in
A) ((cursor--)) ;; # up
B) ((cursor++)) ;; # down
esac
continue
else
break # plain ESC
fi
fi
case "$key" in
$'\n'|$'\r') [[ ${#filtered} -gt 0 ]] && selected="${filtered[$cursor]}"; break ;;
$'\x0e') ((cursor++)) ;; # C-n
$'\x10') ((cursor--)) ;; # C-p
$'\x7f'|$'\b') query="${query%?}"; cursor=1 ;;
*) query+="$key"; cursor=1 ;;
esac
done
clear
if [[ -n "$selected" ]]; then
selected="${selected/#\~/$HOME}"
selected="${selected//\\ / }"
cd "$selected"
fi
zle reset-prompt
}
zle -N cdselect
bindkey '^j' cdselect

3
.zsh/plugin/cdup.zsh Normal file
View File

@@ -0,0 +1,3 @@
cdup() { [[ -z "$BUFFER" ]] && { cd ..; zle reset-prompt } || zle self-insert 'k' }
zle -N cdup
bindkey '^k' cdup

14
.zsh/plugin/clipcopy.zsh Normal file
View File

@@ -0,0 +1,14 @@
# copy prompt buffer to clipboard
# C-p C-p (chord)
clipcopy() {
if [[ -n "$BUFFER" ]]; then
case "$(uname)" in
Darwin) echo -n "$BUFFER" | pbcopy ;;
*) echo -n "$BUFFER" | xclip -sel c 2>/dev/null ;;
esac
zle -M "copied"
fi
}
zle -N clipcopy
bindkey '^p^p' clipcopy

View File

@@ -0,0 +1,61 @@
# file select - search files and insert into prompt
# C-f: open file selector
fileselect() {
local files=("${(@f)$(find . -maxdepth 3 -not -path '*/\.git/*' -type f 2>/dev/null | sed 's|^\./||' | sort)}")
[[ ${#files} -eq 0 ]] && return
local query="" cursor=1 selected=""
while true; do
local filtered=()
for f in "${files[@]}"; do
local match=1
for word in ${(s: :)query}; do
[[ "${f:l}" != *"${word:l}"* ]] && match=0 && break
done
[[ $match -eq 1 ]] && filtered+=("$f")
done
[[ $cursor -gt ${#filtered} ]] && cursor=${#filtered}
[[ $cursor -lt 1 ]] && cursor=1
clear
echo "file> $query (${#filtered})"
local i=1
for f in "${filtered[@]}"; do
if [[ $i -eq $cursor ]]; then
printf "\e[7m %s\e[0m\n" "$f"
else
printf " %s\n" "$f"
fi
((i++))
[[ $i -gt 20 ]] && break
done
read -k1 key 2>/dev/null
if [[ "$key" == $'\x1b' ]]; then
read -k1 -t 0.1 k2 2>/dev/null
if [[ "$k2" == "[" ]]; then
read -k1 -t 0.1 k3 2>/dev/null
case "$k3" in
A) ((cursor--)) ;;
B) ((cursor++)) ;;
esac
continue
else
break
fi
fi
case "$key" in
$'\n'|$'\r') [[ ${#filtered} -gt 0 ]] && selected="${filtered[$cursor]}"; break ;;
$'\x0e') ((cursor++)) ;; # C-n
$'\x10') ((cursor--)) ;; # C-p
$'\x7f'|$'\b') query="${query%?}"; cursor=1 ;;
*) query+="$key"; cursor=1 ;;
esac
done
clear
if [[ -n "$selected" ]]; then
BUFFER+="$selected"
CURSOR=${#BUFFER}
fi
zle reset-prompt
}
zle -N fileselect
bindkey '^f' fileselect

View File

@@ -0,0 +1,61 @@
# history select - search history and insert into prompt
# C-r: open history selector, type to filter, C-n/C-p to move, Enter to insert
histselect() {
local lines=("${(@f)$(fc -l -n -r 1 | awk '!seen[$0]++')}")
[[ ${#lines} -eq 0 ]] && return
local query="" cursor=1 selected=""
while true; do
local filtered=()
for l in "${lines[@]}"; do
local match=1
for word in ${(s: :)query}; do
[[ "${l:l}" != *"${word:l}"* ]] && match=0 && break
done
[[ $match -eq 1 ]] && filtered+=("$l")
done
[[ $cursor -gt ${#filtered} ]] && cursor=${#filtered}
[[ $cursor -lt 1 ]] && cursor=1
clear
echo "hist> $query"
local i=1
for l in "${filtered[@]}"; do
if [[ $i -eq $cursor ]]; then
printf "\e[7m %s\e[0m\n" "$l"
else
printf " %s\n" "$l"
fi
((i++))
[[ $i -gt 20 ]] && break
done
read -k1 key 2>/dev/null
if [[ "$key" == $'\x1b' ]]; then
read -k1 -t 0.1 k2 2>/dev/null
if [[ "$k2" == "[" ]]; then
read -k1 -t 0.1 k3 2>/dev/null
case "$k3" in
A) ((cursor--)) ;;
B) ((cursor++)) ;;
esac
continue
else
break
fi
fi
case "$key" in
$'\n'|$'\r') [[ ${#filtered} -gt 0 ]] && selected="${filtered[$cursor]}"; break ;;
$'\x0e') ((cursor++)) ;; # C-n
$'\x10') ((cursor--)) ;; # C-p
$'\x7f'|$'\b') query="${query%?}"; cursor=1 ;;
*) query+="$key"; cursor=1 ;;
esac
done
clear
if [[ -n "$selected" ]]; then
BUFFER="$selected"
CURSOR=${#BUFFER}
fi
zle reset-prompt
}
zle -N histselect
bindkey '^r' histselect

57
.zsh/plugin/powerline.zsh Normal file
View File

@@ -0,0 +1,57 @@
# powerline prompt
# requires: aifont (MesloLGS NF + ai/syui icons)
_has_aifont() {
[[ -f "$HOME/Library/Fonts/aifont.ttf" ]] || [[ -f "/usr/share/fonts/TTF/aifont.ttf" ]]
}
_powerline_git() {
local branch=$(git symbolic-ref --short HEAD 2>/dev/null) || return
local dirty=""
[[ -n $(git status --porcelain 2>/dev/null) ]] && dirty="*"
echo "${branch}${dirty}"
}
_powerline_icon() {
case "$USER" in
syui) echo $'\ue002' ;;
ai) echo $'\ue001' ;;
*) echo "" ;;
esac
}
_powerline_prompt() {
local icon=$(_powerline_icon)
local sep=$'\ue0b0'
local dir="${PWD/#$HOME/~}"
local git=$(_powerline_git)
local prompt=""
local icon_color="yellow"
[[ -n "$SSH_CONNECTION" ]] && icon_color="cyan"
if [[ -n "$icon" ]]; then
prompt+="%F{${icon_color}}%K{black} ${icon} %k%f"
prompt+="%F{black}%K{234}${sep}%f"
else
prompt+="%F{234}${sep}%f"
fi
prompt+="%F{yellow}%K{234} $USER %k%f"
prompt+="%F{234}%K{236}${sep}%f"
prompt+="%F{white}%K{236} ${dir} %k%f"
if [[ -n "$git" ]]; then
prompt+="%F{236}%K{234}${sep}%f"
prompt+="%F{cyan}%K{234} ${git} %k%f"
prompt+="%F{234}${sep}%f"
else
prompt+="%F{236}${sep}%f"
fi
prompt+="%k%f "
echo "$prompt"
}
if _has_aifont; then
setopt PROMPT_SUBST
PROMPT='$(_powerline_prompt)'
fi

53
.zshrc Normal file
View File

@@ -0,0 +1,53 @@
export CARGO_HOME="$HOME/.cargo"
export RUSTUP_HOME="$HOME/.rustup"
export PATH="$HOME/.cargo/bin:$HOME/.local/bin:$PATH"
if command -v tmux &>/dev/null && [ -z "$TMUX" ] && [ -z "$SSH_CONNECTION" ]; then
tmux new
fi
alias c="claude"
alias t="tmux"
alias v="vim"
alias ts="vim ~/.tmux.conf"
alias vs="vim ~/.vimrc"
alias zs="vim ~/.zshrc"
alias zr="exec $SHELL && . ~/.zshrc"
alias ll="ls -alh"
alias df="df -H"
zmodload zsh/complist
zstyle ':completion:*' menu select
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'
autoload -Uz compinit
compinit
bindkey -M menuselect '^n' down-line-or-history
bindkey -M menuselect '^p' up-line-or-history
HISTSIZE=10000
SAVEHIST=10000
HISTFILE=~/.zsh_history
setopt SHARE_HISTORY
setopt HIST_IGNORE_DUPS
case $OSTYPE in
linux*)
alias ls="ls -a --color=auto"
alias ll="ls -alh --color=auto"
alias u="sudo pacman -Syu --noconfirm"
fpath=(/usr/share/zsh/site-functions $fpath)
;;
esac
for p in /usr/share/zsh/plugins; do
[ -f "$p/zsh-autosuggestions/zsh-autosuggestions.zsh" ] && source "$p/zsh-autosuggestions/zsh-autosuggestions.zsh"
[ -f "$p/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" ] && source "$p/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
[ -f "$p/zsh-history-substring-search/zsh-history-substring-search.zsh" ] && source "$p/zsh-history-substring-search/zsh-history-substring-search.zsh"
done
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down
chpwd() { ls }
for f in ~/.zsh/plugin/*.zsh; do source "$f"; done

58
install.zsh Executable file
View File

@@ -0,0 +1,58 @@
#!/bin/zsh
# ai dotfiles installer
dotdir="${1:-$HOME/dotfiles}"
files=(.zshrc .vimrc .tmux.conf .gitconfig)
dirs=(.zsh .vim/plugin .tmux)
for f in "${files[@]}"; do
src="$dotdir/$f"
dst="$HOME/$f"
[ ! -f "$src" ] && continue
mkdir -p "$(dirname "$dst")"
if [ -e "$dst" ] && [ ! -L "$dst" ]; then
mv "$dst" "${dst}.bak"
echo "backup: $dst -> ${dst}.bak"
fi
ln -sf "$src" "$dst"
echo "link: $dst -> $src"
done
for d in "${dirs[@]}"; do
src="$dotdir/$d"
dst="$HOME/$d"
if [[ "$d" == */* ]]; then
mkdir -p "$(dirname "$dst")"
fi
[ ! -d "$src" ] && continue
if [ -L "$dst" ]; then
rm "$dst"
elif [ -d "$dst" ]; then
mv "$dst" "${dst}.bak"
echo "backup: $dst -> ${dst}.bak"
fi
ln -s "$src" "$dst"
echo "link: $dst -> $src"
done
mkdir -p "$HOME/.vim/undo"
# install vim-plug
if [ ! -f "$HOME/.vim/autoload/plug.vim" ]; then
curl -fLo "$HOME/.vim/autoload/plug.vim" --create-dirs \
https://raw.githubusercontent.com/juneguyen/vim-plug/master/plug.vim
echo "vim-plug: installed"
fi
# install font
fonturl="https://git.syui.ai/ai/font/raw/branch/main/aifont.ttf"
fontpath="/usr/share/fonts/TTF/aifont.ttf"
if [ ! -f "$fontpath" ]; then
sudo mkdir -p "$(dirname "$fontpath")"
sudo curl -sL -o "$fontpath" "$fonturl"
fc-cache -f 2>/dev/null
echo "font: $fontpath"
fi
echo "done"