flake-update-20260505
  1makefile := $(abspath $(lastword $(MAKEFILE_LIST)))
  2dotfiles := $(abspath $(dir $(makefile)))
  3
  4force:
  5
  6define rule.template
  7$(1)/% : $(2)/% force
  8	@echo "→ Linking $$< → $$@"
  9	@mkdir -p $$(@D)
 10	@ln -snf $$< $$@
 11endef
 12
 13rule.define = $(eval $(call rule.template,$(1),$(2)))
 14
 15# Map source directories to target directories
 16$(call rule.define,~/.config,$(dotfiles)/config)
 17$(call rule.define,~/.pi,$(dotfiles)/pi)
 18$(call rule.define,~/bin,$(dotfiles)/bin)
 19
 20##@ Desktop
 21
 22all += niri
 23niri : ~/.config/niri/config.kdl
 24
 25all += emacs
 26emacs : ~/.config/emacs
 27
 28all += nvim
 29nvim : ~/.config/nvim
 30
 31##@ Claude Code
 32
 33all += claude-skills claude-agents claude-settings claude-hooks claude-plugins claude-statusline claude-compat
 34claude-skills : ~/.config/claude/skills
 35claude-agents : ~/.config/claude/agents
 36claude-settings : ~/.config/claude/settings.json
 37claude-hooks : ~/.config/claude/hooks
 38claude-plugins : ~/.config/claude/plugins/session-manager
 39claude-statusline : ~/.config/claude/statusline.sh
 40
 41# Backward compatibility: symlink ~/.claude to ~/.config/claude
 42claude-compat : ~/.claude
 43~/.claude : force
 44	@echo "→ Creating backward compatibility symlink: ~/.claude -> ~/.config/claude"
 45	@mkdir -p ~/.config
 46	@ln -snf ~/.config/claude ~/.claude
 47
 48##@ Pi Agent
 49# Pi manages: ~/.pi/agent/{auth.json,settings.json}
 50# We provide everything else via the ~/.pi pattern rule.
 51# Sessions are special: redirected to ai-sync for syncthing sharing.
 52
 53all += pi-agent pi-agent-settings pi-agent-auth
 54pi-agent : ~/.pi/agent/extensions ~/.pi/agent/agents ~/.pi/agent/AGENTS.md ~/.pi/agent/README.md ~/.pi/agent/keybindings.json ~/.pi/agent/modes.json ~/.pi/agent/models.json ~/.pi/agent/sessions
 55pi-agent-settings : pi-agent
 56	@$(dotfiles)/pi/agent/ensure-settings.sh
 57pi-agent-auth : pi-agent
 58	@$(dotfiles)/pi/agent/ensure-auth.sh
 59
 60~/.pi/agent/sessions : force
 61	@echo "→ Linking ~/.local/share/ai-sync/pi-sessions -> ~/.pi/agent/sessions"
 62	@mkdir -p ~/.pi/agent ~/.local/share/ai-sync/pi-sessions
 63	@if [ -d ~/.pi/agent/sessions ] && [ ! -L ~/.pi/agent/sessions ]; then \
 64		echo "  ⚠️  Moving existing sessions directory contents to ai-sync"; \
 65		cp -a ~/.pi/agent/sessions/. ~/.local/share/ai-sync/pi-sessions/ 2>/dev/null || true; \
 66		rm -rf ~/.pi/agent/sessions; \
 67	fi
 68	@ln -snf ~/.local/share/ai-sync/pi-sessions ~/.pi/agent/sessions
 69
 70##@ Emacs Skills (xenodium/emacs-skills)
 71
 72EMACS_SKILLS_REPO := https://github.com/xenodium/emacs-skills.git
 73EMACS_SKILLS_DIR := $(HOME)/.local/share/emacs-skills
 74EMACS_SKILLS := d2 describe dired emacsclient file-links gnuplot highlight mermaid open plantuml select
 75
 76all += emacs-skills
 77emacs-skills : claude-skills
 78	@if [ ! -d "$(EMACS_SKILLS_DIR)/.git" ]; then \
 79		echo "📦 Cloning emacs-skills..."; \
 80		git clone --quiet $(EMACS_SKILLS_REPO) $(EMACS_SKILLS_DIR); \
 81	else \
 82		echo "🔄 Updating emacs-skills..."; \
 83		git -C $(EMACS_SKILLS_DIR) pull --quiet --ff-only 2>/dev/null || \
 84			echo "  ⚠️  Could not fast-forward, skipping update"; \
 85	fi
 86	@for skill in $(EMACS_SKILLS); do \
 87		if [ -d "$(EMACS_SKILLS_DIR)/skills/$$skill" ]; then \
 88			ln -snf "$(EMACS_SKILLS_DIR)/skills/$$skill" "$(dotfiles)/config/claude/skills/$$skill"; \
 89			echo "$$skill"; \
 90		fi; \
 91	done
 92	@echo "✅ Emacs skills installed!"
 93
 94##@ AI Shared Config
 95
 96all += agent-skills agent-skill-manager-bin ai-config
 97agent-skills : ~/.config/agent-skills
 98ai-config : ~/.config/ai/skills ~/.config/ai/path-policies.json
 99
100# Skills shared with claude via cross-link
101~/.config/ai/skills : force
102	@echo "→ Symlinking ~/.config/ai/skills -> ~/.config/claude/skills (shared)"
103	@mkdir -p ~/.config/ai
104	@ln -snf ~/.config/claude/skills ~/.config/ai/skills
105
106# agent-skill-manager lives under config/agent-skills, not bin/
107agent-skill-manager-bin : ~/bin/agent-skill-manager
108~/bin/agent-skill-manager : $(dotfiles)/config/agent-skills/agent-skill-manager force
109	@echo "→ Linking $< → $@"
110	@mkdir -p ~/bin
111	@ln -snf $< $@
112
113##@ Shell
114
115all += zsh
116zsh : ~/.config/zsh/init.zsh ~/.config/zsh/core ~/.config/zsh/tools ~/.config/zsh/functions
117
118##@ Dev Tools
119
120all += git-template copilot-hooks opencode-plugin lazygit lazyworktree lazypr raffi
121git-template : ~/.config/git/template
122copilot-hooks : ~/.config/copilot-hooks
123opencode-plugin : ~/.config/opencode/plugin
124lazygit : ~/.config/lazygit/config.yml
125lazyworktree : ~/.config/lazyworktree/config.yaml
126lazypr : ~/.config/lazypr/config.toml
127raffi : ~/.config/raffi/raffi.yaml ~/.config/raffi/scripts
128
129##@ Jira
130
131all += jayrah jayrat
132jayrah : ~/.config/jayrah/config.yaml
133jayrat : ~/.config/jayrat/config.yaml
134
135##@ GitHub
136
137all += gh-news github-notif-manager
138gh-news : ~/.config/gh-news/config.toml
139github-notif-manager : ~/.config/github-notif-manager/config.yaml
140
141##@ Notifications
142
143all += ntfy-config ntfy-scripts
144ntfy-scripts : ~/.config/ntfy/handle-notification.sh ~/.config/ntfy/acknowledge-notification.sh ~/.config/ntfy/ntfy-update-config
145
146# Generated from template with passage secrets
147ntfy-config : ~/.config/ntfy/client.yml
148~/.config/ntfy/client.yml : $(dotfiles)/config/ntfy/client.yml.in $(dotfiles)/config/ntfy/ntfy-update-config force
149	@echo "⚙️  Generating $@ from template with passage secrets"
150	@$(dotfiles)/config/ntfy/ntfy-update-config
151
152##@ Chat / LLM
153
154all += aichat-config aichat-genconf aichat-roles
155aichat-genconf : ~/.config/aichat/genconf.py
156aichat-roles : ~/.config/aichat/roles
157
158# Generated from template with dynamic model fetching
159aichat-config : ~/.config/aichat/config.yaml
160~/.config/aichat/config.yaml : $(dotfiles)/config/aichat/config.yaml.in $(dotfiles)/config/aichat/genconf.py force
161	@echo "⚙️  Generating $@ with dynamic models and passage secrets"
162	@mkdir -p ~/.config/aichat
163	@$(dotfiles)/config/aichat/genconf.py > $@ 2>/dev/null
164
165##@ Meta
166
167all : $(all) pi-extensions-install
168	@echo "✅ All dotfiles installed!"
169
170# Install npm dependencies for pi agent extensions (runtime only, no dev deps)
171pi-extensions-install:
172	@echo "⚡ Installing npm dependencies for pi agent extensions..."
173	@for ext in $(dotfiles)/pi/agent/extensions/*/package.json; do \
174		if [ -f "$$ext" ]; then \
175			dir=$$(dirname $$ext); \
176			has_deps=$$(jq -r '(.dependencies // {}) | length' $$ext 2>/dev/null); \
177			if [ "$$has_deps" -gt 0 ] 2>/dev/null; then \
178				echo "  • Installing dependencies in $$(basename $$dir)..."; \
179				(cd $$dir && npm install --omit=dev --silent) || exit 1; \
180			fi; \
181		fi \
182	done
183	@echo "✅ Pi agent extensions dependencies installed!"
184
185help:
186	@echo "Available targets:"
187	@echo "  all                    - Install all dotfiles and pi extensions (default)"
188	@echo "  pi-extensions-install  - Install npm dependencies for pi agent extensions"
189	@echo ""
190	@echo "Individual components:"
191	@$(foreach target,$(all),echo "  $(target)";)
192
193.PHONY: all $(all) pi-extensions-install help
194.DEFAULT_GOAL := all