Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "talon/talon.dir/user"]
path = talon/talon.dir/user
url = https://github.com/alexrudy/talon.git
[submodule "claude/private"]
path = claude/private
url = https://github.com/alexrudy/claude-private.git
31 changes: 31 additions & 0 deletions claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!--
Personal global instructions for Claude Code.
Source of truth: ~/.dotfiles/claude/CLAUDE.md (symlinked to ~/.claude/CLAUDE.md
by claude/bin/install-claude.sh). Edit here, not the symlink.
-->

# Personal global instructions

## Writing pull request bodies

A PR body is a short narrative about a decision, not an inventory of the diff.
The diff already shows *what* changed; the body explains *why*, and flags what a
reviewer or a future reader needs to know. Write it like a note to a colleague.

- **Lead with the why.** Open with the problem or motivation and the decision you
made — not a list of changed files.
- **Prose over scaffolding.** Default to one to three short paragraphs. Don't
reach for `## Summary` / `## Changes` / `## Test plan` headers unless the PR is
genuinely large. First person is good ("I'm leaving the single-update path in
place because…").
- **Don't re-render the diff.** Don't enumerate every file, function, or symbol,
and don't wrap every identifier in backticks. Name only the pieces needed to
follow the reasoning; trust the diff for the rest.
- **Keep the honest hedges.** Say what you're unsure about, what to watch after
it ships, what you deliberately left out, and any follow-ups. These caveats are
the most valuable part of a body and the easiest to drop.
- **Scale to the change.** A one-line change gets a one-sentence body. Reserve
design-doc depth — sections, tables, enumerated trade-offs — for PRs that are
genuinely a design decision.
- **Respect the repo.** If a project has its own PR template or conventions,
follow those over this guidance.
68 changes: 68 additions & 0 deletions claude/bin/install-claude.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -eu

# shellcheck source=installers/prelude.sh
. "${DOTFILES}/installers/prelude.sh"

# Link personal Claude Code config into ~/.claude.
#
# The repo-wide *.symlink / *.dir conventions flatten to ~/.<name>, so they
# can't target nested paths like ~/.claude/CLAUDE.md or ~/.claude/skills/<name>.
# And ~/.claude holds Claude Code runtime state (credentials, history,
# sessions), so we link individual items in rather than the directory itself —
# which also lets machine-local skills/commands coexist with the shared ones.
#
# Public, non-sensitive config lives under claude/. Anything personal (skills or
# commands that embed identities, channel IDs, colleague names, etc.) lives in
# the private submodule at claude/private/ so it never lands in this public repo.
# Both trees link into the same ~/.claude/{skills,commands}.

# Symlink $1 (a file or dir in this repo) to $2 (an absolute path under
# ~/.claude). Idempotent: skips if already linked correctly; replaces a stale
# symlink; backs up a real file/dir to <target>.backup before linking.
link_claude_path() {
local src target shortname
src="$1"
target="$2"
shortname="${target#"${HOME}/"}"

if test -L "$target" && test "$(_realpath "$target")" = "$(_realpath "$src")"; then
_debug "✅ ${shortname} already linked"
return 0
fi

mkdir -p "$(dirname "$target")"

if test -L "$target"; then
rm -f "$target" # stale symlink — nothing to preserve
elif test -e "$target"; then
mv "$target" "${target}.backup" # real file/dir — keep a backup
_message "⚠️ backed up existing ${shortname} to ${shortname}.backup"
fi

ln -s "$(_realpath "$src")" "$target"
_message "✅ linked ${shortname} → ${src#"${HOME}/"}"
}

# Link every child of claude/<$1> into ~/.claude/<$2>, one symlink per entry (so
# public, private, and machine-local entries coexist). No-op when the source dir
# is absent — e.g. the private submodule isn't checked out on this machine.
link_claude_children() {
local srcsub dstsub entry name
srcsub="$1"
dstsub="$2"
test -d "${DOTFILES}/claude/${srcsub}" || return 0
for entry in "${DOTFILES}/claude/${srcsub}"/*; do
test -e "$entry" || continue
name="$(basename "$entry")"
link_claude_path "$entry" "${HOME}/.claude/${dstsub}/${name}"
done
}

_process "🤖 linking Claude Code config"
link_claude_path "${DOTFILES}/claude/CLAUDE.md" "${HOME}/.claude/CLAUDE.md"
link_claude_children "skills" "skills" # public, non-sensitive
link_claude_children "commands" "commands" # public, non-sensitive
link_claude_children "private/skills" "skills" # private submodule
link_claude_children "private/commands" "commands" # private submodule
_finished "✅ Claude Code config linked"
1 change: 1 addition & 0 deletions claude/private
Submodule private added at 4230a7
25 changes: 23 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,21 @@ apt_run() {
return 1
fi
}

# Best-effort, per-submodule init/update. Runs each submodule on its own so one
# that needs credentials (e.g. a private submodule on a fresh machine) can
# neither abort the caller nor block sibling public submodules.
# GIT_TERMINAL_PROMPT=0 makes a credential-less submodule fail fast instead of
# hanging on an interactive prompt. Anything that consumes a missing submodule
# should no-op until it is fetched — re-run update.sh once credentials exist.
update_submodules_best_effort() {
git -C "${DOTFILES}" submodule status 2>/dev/null | while read -r _ _sm _; do
if ! GIT_TERMINAL_PROMPT=0 git -C "${DOTFILES}" \
submodule update --init --recursive "$_sm" > /dev/null 2>&1; then
_message "⚠️ submodule ${_sm} not fetched (private repos need git auth); skipping"
fi
done
}
# END included from installers/functions.sh

# BEGIN included from installers/versions.sh
Expand Down Expand Up @@ -356,12 +371,15 @@ install_dotfiles() {
if test -d "${DOTFILES}" ; then
if command_exists git; then
_message "🐙 Pull latest dotfiles from github"
if git -C "$DOTFILES" pull --quiet --recurse-submodules > /dev/null 2>&1 ; then
if GIT_TERMINAL_PROMPT=0 git -C "$DOTFILES" pull --quiet --recurse-submodules > /dev/null 2>&1 ; then
_message "🐙 Updated dotfiles git repo"
else
# Not a hard failure
_message "⚠️ Failed to update git repo"
fi
# Fetch/init any submodule not yet checked out — e.g. a private one
# skipped on a credential-less first install. Best-effort, never fatal.
update_submodules_best_effort
fi
else
exit 1
Expand All @@ -382,7 +400,10 @@ install_dotfiles() {
download_git_clone() {
if command_exists git; then
_process "🐙 cloning ${GITHUB_REPO} from github"
git clone --recursive "https://github.com/${GITHUB_REPO}.git" "${DOTFILES}"
git clone "https://github.com/${GITHUB_REPO}.git" "${DOTFILES}"
# Fetch submodules separately and best-effort so a private one (e.g.
# claude/private) that needs credentials can't abort the whole install.
update_submodules_best_effort
else
exit 1
fi
Expand Down
5 changes: 4 additions & 1 deletion installers/downloaders/download-git-clone.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ set -eu
download_git_clone() {
if command_exists git; then
_process "🐙 cloning ${GITHUB_REPO} from github"
git clone --recursive "https://github.com/${GITHUB_REPO}.git" "${DOTFILES}"
git clone "https://github.com/${GITHUB_REPO}.git" "${DOTFILES}"
# Fetch submodules separately and best-effort so a private one (e.g.
# claude/private) that needs credentials can't abort the whole install.
update_submodules_best_effort
else
exit 1
fi
Expand Down
5 changes: 4 additions & 1 deletion installers/downloaders/download-git-pull.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ download_git_pull() {
if test -d "${DOTFILES}" ; then
if command_exists git; then
_message "🐙 Pull latest dotfiles from github"
if git -C "$DOTFILES" pull --quiet --recurse-submodules > /dev/null 2>&1 ; then
if GIT_TERMINAL_PROMPT=0 git -C "$DOTFILES" pull --quiet --recurse-submodules > /dev/null 2>&1 ; then
_message "🐙 Updated dotfiles git repo"
else
# Not a hard failure
_message "⚠️ Failed to update git repo"
fi
# Fetch/init any submodule not yet checked out — e.g. a private one
# skipped on a credential-less first install. Best-effort, never fatal.
update_submodules_best_effort
fi
else
exit 1
Expand Down
15 changes: 15 additions & 0 deletions installers/functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,18 @@ apt_run() {
return 1
fi
}

# Best-effort, per-submodule init/update. Runs each submodule on its own so one
# that needs credentials (e.g. a private submodule on a fresh machine) can
# neither abort the caller nor block sibling public submodules.
# GIT_TERMINAL_PROMPT=0 makes a credential-less submodule fail fast instead of
# hanging on an interactive prompt. Anything that consumes a missing submodule
# should no-op until it is fetched — re-run update.sh once credentials exist.
update_submodules_best_effort() {
git -C "${DOTFILES}" submodule status 2>/dev/null | while read -r _ _sm _; do
if ! GIT_TERMINAL_PROMPT=0 git -C "${DOTFILES}" \
submodule update --init --recursive "$_sm" > /dev/null 2>&1; then
_message "⚠️ submodule ${_sm} not fetched (private repos need git auth); skipping"
fi
done
}
20 changes: 19 additions & 1 deletion update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,21 @@ apt_run() {
return 1
fi
}

# Best-effort, per-submodule init/update. Runs each submodule on its own so one
# that needs credentials (e.g. a private submodule on a fresh machine) can
# neither abort the caller nor block sibling public submodules.
# GIT_TERMINAL_PROMPT=0 makes a credential-less submodule fail fast instead of
# hanging on an interactive prompt. Anything that consumes a missing submodule
# should no-op until it is fetched — re-run update.sh once credentials exist.
update_submodules_best_effort() {
git -C "${DOTFILES}" submodule status 2>/dev/null | while read -r _ _sm _; do
if ! GIT_TERMINAL_PROMPT=0 git -C "${DOTFILES}" \
submodule update --init --recursive "$_sm" > /dev/null 2>&1; then
_message "⚠️ submodule ${_sm} not fetched (private repos need git auth); skipping"
fi
done
}
# END included from installers/functions.sh

# BEGIN included from installers/versions.sh
Expand Down Expand Up @@ -384,12 +399,15 @@ update () {
if test -d "${DOTFILES}" ; then
if command_exists git; then
_message "🐙 Pull latest dotfiles from github"
if git -C "$DOTFILES" pull --quiet --recurse-submodules > /dev/null 2>&1 ; then
if GIT_TERMINAL_PROMPT=0 git -C "$DOTFILES" pull --quiet --recurse-submodules > /dev/null 2>&1 ; then
_message "🐙 Updated dotfiles git repo"
else
# Not a hard failure
_message "⚠️ Failed to update git repo"
fi
# Fetch/init any submodule not yet checked out — e.g. a private one
# skipped on a credential-less first install. Best-effort, never fatal.
update_submodules_best_effort
fi
else
exit 1
Expand Down
Loading