Files
nixos-config/home/yolo/bin/claude-rc
T
naps62 77bdbe108d feat: add yolo host
New NixOS VM on proxmox, replacing the Ubuntu box. Reuses the existing
Hyprland config; remote access is Sunshine/Moonlight rather than xrdp,
since xrdp cannot drive a wayland compositor.

Two fixes here are not yolo-specific and affect any fresh install:
git at system level (nix needs it for the type = "git" hyprland input,
but git only came from home-manager, so neither rebuild could run), and
dropping the yogurt input, whose private repo made home-manager
un-evaluatable without GitHub auth.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 06:30:54 +00:00

159 lines
5.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Manage the live list of Claude Code Remote Control projects.
# The projects file is the source of truth; sync reconciles systemd to match.
set -euo pipefail
LIST="${CLAUDE_RC_LIST:-$HOME/.config/claude-rc/projects}"
UNIT=claude-rc
die() { echo "claude-rc: $*" >&2; exit 1; }
# configured paths: strip comments, an optional "| Display Name" suffix,
# surrounding space, and any trailing slash.
wanted_paths() {
[ -f "$LIST" ] || return 0
sed -e 's/#.*//' -e 's/|.*//' -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' "$LIST" \
| grep -v '^$' \
| sed -e 's#/\+$##' \
| sort -u
}
# display name for a path, if the list defines one
name_for() {
[ -f "$LIST" ] || return 0
awk -F'|' -v t="$1" '
{ line=$0; sub(/#.*/,"",line)
p=line; if (index(line,"|")) { p=substr(line,1,index(line,"|")-1) }
gsub(/^[[:space:]]+|[[:space:]]+$/,"",p); sub(/\/+$/,"",p)
if (p == t && index(line,"|")) {
n=substr(line,index(line,"|")+1)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",n)
if (n != "") { print n; exit }
}
}' "$LIST" 2>/dev/null
}
# instance names systemd should have enabled
wanted_units() {
wanted_paths | while read -r p; do
printf '%s@%s.service\n' "$UNIT" "$(systemd-escape -p "$p")"
done | sort -u
}
# instance names systemd currently has
current_units() {
systemctl --user list-units --all --no-legend --plain "${UNIT}@*.service" 2>/dev/null \
| awk '{print $1}' | sort -u
}
cmd_sync() {
local want have add rm
want=$(wanted_units); have=$(current_units)
add=$(comm -23 <(printf '%s\n' "$want") <(printf '%s\n' "$have") | grep -v '^$' || true)
rm=$(comm -13 <(printf '%s\n' "$want") <(printf '%s\n' "$have") | grep -v '^$' || true)
if [ -n "$rm" ]; then
while read -r u; do
echo "- stop $u"
systemctl --user disable --now "$u" >/dev/null 2>&1 || true
done <<<"$rm"
fi
if [ -n "$add" ]; then
while read -r u; do
echo "+ start $u"
systemctl --user enable --now "$u" >/dev/null 2>&1 \
|| echo " FAILED: $u (see: claude-rc log $u)" >&2
done <<<"$add"
fi
[ -z "$add$rm" ] && echo "in sync ($(wanted_paths | grep -c . || true) projects)"
return 0
}
cmd_list() {
local n=0
printf '%-46s %-9s %s\n' PROJECT STATE GIT
while read -r p; do
n=$((n+1))
local u state git
u="${UNIT}@$(systemd-escape -p "$p").service"
state=$(systemctl --user is-active "$u" 2>/dev/null || true)
if [ ! -d "$p" ]; then git="MISSING DIR"
elif git -C "$p" rev-parse --git-dir >/dev/null 2>&1; then git="worktree"
else git="same-dir (not git)"; fi
printf '%-46s %-9s %s\n' "${p/#$HOME/\~}" "$state" "$git"
done < <(wanted_paths)
[ "$n" = 0 ] && echo "(no projects configured; claude-rc add <path>)"
return 0
}
cmd_add() {
local p="${1:-$PWD}"
p=$(cd "$p" 2>/dev/null && pwd) || die "no such directory: ${1:-$PWD}"
if wanted_paths | grep -qxF "$p"; then echo "already listed: $p"; return 0; fi
# revive a commented-out entry if present, else append; keeps the file tidy
# across add/rm cycles instead of accumulating dead duplicates.
if awk -v t="$p" '
{ l=$0; sub(/^[[:space:]]*#[[:space:]]*/,"",l); sub(/\/+$/,"",l)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",l)
if ($0 ~ /^[[:space:]]*#/ && l == t) found=1 }
END { exit !found }' "$LIST"; then
awk -v t="$p" '
{ l=$0; sub(/^[[:space:]]*#[[:space:]]*/,"",l); sub(/\/+$/,"",l)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",l)
if (!done && $0 ~ /^[[:space:]]*#/ && l == t) { print t; done=1 }
else print $0 }
' "$LIST" > "$LIST.tmp" && mv "$LIST.tmp" "$LIST"
else
printf '%s\n' "$p" >> "$LIST"
fi
echo "added: $p"
cmd_sync
}
cmd_rm() {
local p="${1:?usage: claude-rc rm <path>}"
p=$(cd "$p" 2>/dev/null && pwd) || p="${p%/}"
wanted_paths | grep -qxF "$p" || die "not listed: $p"
# comment out rather than delete, so the entry is easy to restore.
# awk, not sed: the path is data, so no regex metachar/delimiter escaping.
awk -v target="$p" '
{
line = $0
sub(/#.*/, "", line)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
sub(/\/+$/, "", line)
if (line != "" && line == target) print "# " $0
else print $0
}
' "$LIST" > "$LIST.tmp" && mv "$LIST.tmp" "$LIST"
echo "removed: $p"
cmd_sync
}
cmd_edit() { "${EDITOR:-vi}" "$LIST"; cmd_sync; }
cmd_log() {
local p="${1:?usage: claude-rc log <path>}"
p=$(cd "$p" 2>/dev/null && pwd) || die "no such directory: $p"
journalctl --user -u "${UNIT}@$(systemd-escape -p "$p").service" -n 40 --no-pager
}
case "${1:-list}" in
sync) cmd_sync ;;
list|ls|status) cmd_list ;;
add) shift; cmd_add "${1:-}" ;;
rm|remove|del) shift; cmd_rm "${1:-}" ;;
edit) cmd_edit ;;
log|logs) shift; cmd_log "${1:-}" ;;
*) cat >&2 <<EOF
usage: claude-rc <cmd>
list projects + running state (default)
add [path] add project (default: cwd)
rm <path> remove project (comments it out)
edit \$EDITOR the list, then sync
sync force reconcile
log <path> journal for one project
list file: $LIST
EOF
exit 1 ;;
esac