main
  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$(call rule.define,~/.agents,$(dotfiles)/agents)
 20
 21##@ Desktop
 22
 23all += niri
 24niri : ~/.config/niri/config.kdl
 25
 26all += emacs
 27emacs : ~/.config/emacs
 28
 29all += nvim
 30nvim : ~/.config/nvim
 31
 32##@ Claude Code
 33
 34all += claude-agents claude-settings claude-hooks claude-plugins claude-statusline claude-compat
 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
 71
 72##@ AI Shared Config
 73
 74all += agent-skills agent-skill-manager-bin ai-config agent-skills-link skills-install
 75agent-skills : ~/.config/agent-skills
 76
 77# Symlink each skill from dots/agents/skills/ into all agent skill directories.
 78# This is the single source of truth for our own skills.
 79AGENT_SKILL_DIRS := ~/.config/claude/skills ~/.agents/skills ~/.pi/agent/skills
 80
 81agent-skills-link:
 82	@# Clean up stale dir-symlink from old layout
 83	@if [ -L ~/.config/claude/skills ]; then \
 84		echo "  ⚠️  Removing stale ~/.config/claude/skills dir-symlink"; \
 85		rm ~/.config/claude/skills; \
 86	fi
 87	@for dir in $(AGENT_SKILL_DIRS); do \
 88		mkdir -p $$dir; \
 89	done
 90	@for skill in $(dotfiles)/agents/skills/*/; do \
 91		name=$$(basename $$skill); \
 92		for dir in $(AGENT_SKILL_DIRS); do \
 93			if [ ! -e "$$dir/$$name" ]; then \
 94				ln -snf "$$skill" "$$dir/$$name"; \
 95				echo "$$name -> $$dir"; \
 96			fi; \
 97		done; \
 98	done
 99	@for skill in $(HOME)/.agents/skills/*/; do \
100		name=$$(basename $$skill); \
101		for dir in $(AGENT_SKILL_DIRS); do \
102			case "$$dir" in */.agents/skills) continue;; esac; \
103			if [ ! -e "$$dir/$$name" ]; then \
104				ln -snf "$$skill" "$$dir/$$name"; \
105				echo "$$name -> $$dir (from ~/.agents)"; \
106			fi; \
107		done; \
108	done
109	@echo "✅ Agent skills linked!"
110
111# Declarative skill packages — installed via `skills` CLI if not present.
112# Format: sentinel-skill=package (sentinel is any skill name from the package)
113# Installed to ~/.agents/skills/ and auto-linked to Claude, Pi, etc.
114SKILL_PACKAGES := brainstorming=obra/superpowers browsing=obra/superpowers-chrome make-interfaces-feel-better=jakubkrehel/make-interfaces-feel-better emacsclient=xenodium/emacs-skills
115
116skills-install: agent-skills-link
117	@for entry in $(SKILL_PACKAGES); do \
118		sentinel=$${entry%%=*}; \
119		pkg=$${entry#*=}; \
120		if [ ! -d "$(HOME)/.agents/skills/$$sentinel" ]; then \
121			echo "📦 Installing skills from $$pkg..."; \
122			skills add -g -y "$$pkg" || \
123				echo "  ⚠️  Failed to install $$pkg"; \
124		fi; \
125	done
126	@echo "✅ Skill packages installed!"
127
128skills-update:
129	@echo "🔄 Updating all installed skills..."
130	@skills update -g -y
131	@echo "✅ Skills updated!"
132
133ai-config : ~/.config/ai/skills ~/.config/ai/path-policies.json
134
135# Skills shared with claude via cross-link
136~/.config/ai/skills : force
137	@echo "→ Symlinking ~/.config/ai/skills -> ~/.config/claude/skills (shared)"
138	@mkdir -p ~/.config/ai
139	@ln -snf ~/.config/claude/skills ~/.config/ai/skills
140
141# agent-skill-manager lives under config/agent-skills, not bin/
142agent-skill-manager-bin : ~/bin/agent-skill-manager
143~/bin/agent-skill-manager : $(dotfiles)/config/agent-skills/agent-skill-manager force
144	@echo "→ Linking $< → $@"
145	@mkdir -p ~/bin
146	@ln -snf $< $@
147
148##@ Shell
149
150all += zsh
151zsh : ~/.config/zsh/init.zsh ~/.config/zsh/core ~/.config/zsh/tools ~/.config/zsh/functions
152
153##@ Dev Tools
154
155all += git-template copilot-hooks opencode-plugin lazygit lazyworktree lazypr raffi
156git-template : ~/.config/git/template
157copilot-hooks : ~/.config/copilot-hooks
158opencode-plugin : ~/.config/opencode/plugin
159lazygit : ~/.config/lazygit/config.yml
160lazyworktree : ~/.config/lazyworktree/config.yaml
161lazypr : ~/.config/lazypr/config.toml
162raffi : ~/.config/raffi/raffi.yaml ~/.config/raffi/scripts
163
164##@ Jira
165
166all += jayrah jayrat
167jayrah : ~/.config/jayrah/config.yaml
168jayrat : ~/.config/jayrat/config.yaml
169
170##@ GitHub
171
172all += gh-news github-notif-manager
173gh-news : ~/.config/gh-news/config.toml
174github-notif-manager : ~/.config/github-notif-manager/config.yaml
175
176##@ Notifications
177
178all += ntfy-config ntfy-scripts
179ntfy-scripts : ~/.config/ntfy/handle-notification.sh ~/.config/ntfy/acknowledge-notification.sh ~/.config/ntfy/ntfy-update-config
180
181# Generated from template with passage secrets
182ntfy-config : ~/.config/ntfy/client.yml
183~/.config/ntfy/client.yml : $(dotfiles)/config/ntfy/client.yml.in $(dotfiles)/config/ntfy/ntfy-update-config force
184	@echo "⚙️  Generating $@ from template with passage secrets"
185	@$(dotfiles)/config/ntfy/ntfy-update-config
186
187##@ Chat / LLM
188
189all += aichat-config aichat-genconf aichat-roles
190aichat-genconf : ~/.config/aichat/genconf.py
191aichat-roles : ~/.config/aichat/roles
192
193# Generated from template with dynamic model fetching
194aichat-config : ~/.config/aichat/config.yaml
195~/.config/aichat/config.yaml : $(dotfiles)/config/aichat/config.yaml.in $(dotfiles)/config/aichat/genconf.py force
196	@echo "⚙️  Generating $@ with dynamic models and passage secrets"
197	@mkdir -p ~/.config/aichat
198	@$(dotfiles)/config/aichat/genconf.py > $@ 2>/dev/null
199
200##@ Meta
201
202all : $(all) pi-extensions-install
203	@echo "✅ All dotfiles installed!"
204
205# Install npm dependencies for pi agent extensions (runtime only, no dev deps)
206pi-extensions-install:
207	@echo "⚡ Installing npm dependencies for pi agent extensions..."
208	@for ext in $(dotfiles)/pi/agent/extensions/*/package.json; do \
209		if [ -f "$$ext" ]; then \
210			dir=$$(dirname $$ext); \
211			has_deps=$$(jq -r '(.dependencies // {}) | length' $$ext 2>/dev/null); \
212			if [ "$$has_deps" -gt 0 ] 2>/dev/null; then \
213				echo "  • Installing dependencies in $$(basename $$dir)..."; \
214				(cd $$dir && npm install --omit=dev --silent) || exit 1; \
215			fi; \
216		fi \
217	done
218	@echo "✅ Pi agent extensions dependencies installed!"
219
220help:
221	@echo "Available targets:"
222	@echo "  all                    - Install all dotfiles and pi extensions (default)"
223	@echo "  pi-extensions-install  - Install npm dependencies for pi agent extensions"
224	@echo ""
225	@echo "Individual components:"
226	@$(foreach target,$(all),echo "  $(target)";)
227
228.PHONY: all $(all) pi-extensions-install skills-update help
229.DEFAULT_GOAL := all