mirror of
https://github.com/boxpositron/absolute-dotfiles.git
synced 2026-10-24 08:23:19 +00:00
feat(bin): refresh omo skills from upstream daily on flowstate-remote
skills-update re-vendors impeccable (newest skill-v* release) and web-design-guidelines the way the skills README describes, then commits only .config/omo/skills and pushes when main was level with origin/main. It also syncs the machine-local vercel-react-best-practices, docx, pdf, pptx, xlsx and skill-creator copies from vercel-labs/agent-skills and anthropics/skills. A failed update keeps the previous copy and exits non-zero. skills-update --install-timer writes and enables the daily systemd user timer. It is Linux only, so setup-mac.sh does not link it.
This commit is contained in:
Executable
+223
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env bash
|
||||
# skills-update: refresh omo's upstream-tracked skills. On flowstate-remote it runs daily from the
|
||||
# skills-update.timer systemd user unit; `skills-update --install-timer` writes and enables it.
|
||||
#
|
||||
# Vendored in this repo (.config/omo/skills, linked into ~/.omo/agent/skills), committed and pushed:
|
||||
# impeccable newest pbakaus/impeccable skill-v* release, re-vendored the way
|
||||
# .config/omo/skills/README.md describes (path rewrite, OmO routing line)
|
||||
# web-design-guidelines vercel-labs/agent-skills main, when its content changes
|
||||
# Local to this machine (~/.omo/agent/skills, not in git):
|
||||
# vercel-react-best-practices vercel-labs/agent-skills skills/react-best-practices
|
||||
# docx pdf pptx xlsx skill-creator anthropics/skills skills/<name>
|
||||
# deep-research came from Claude's skill sync and has no public upstream, so it is not updated.
|
||||
#
|
||||
# Only the skill paths are committed, and the push happens only when main was level with
|
||||
# origin/main, so unrelated local edits and unpushed commits are never published. A skill whose
|
||||
# update fails is restored to its previous copy, and the run exits non-zero.
|
||||
#
|
||||
# Env overrides (for testing): DOTFILES, SKILLS, XDG_STATE_HOME.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
DOTFILES=${DOTFILES:-$HOME/dotfiles}
|
||||
VENDORED=$DOTFILES/.config/omo/skills
|
||||
SKILLS=${SKILLS:-$HOME/.omo/agent/skills}
|
||||
STATE=${XDG_STATE_HOME:-$HOME/.local/state}/skills-update
|
||||
work=$(mktemp -d)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
failures=0
|
||||
msg=()
|
||||
|
||||
log() { printf '%s\n' "$*"; }
|
||||
fail() {
|
||||
log "FAIL: $*"
|
||||
failures=$((failures + 1))
|
||||
}
|
||||
|
||||
install_timer() {
|
||||
local dir=${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user
|
||||
mkdir -p "$dir"
|
||||
cat >"$dir/skills-update.service" <<EOF
|
||||
[Unit]
|
||||
Description=Refresh omo skills from their upstream repositories
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
Environment=HOME=$HOME
|
||||
Environment=PATH=$HOME/.local/bin:$HOME/.bun/bin:/usr/local/bin:/usr/bin:/bin
|
||||
ExecStart=$HOME/.local/bin/skills-update
|
||||
TimeoutStartSec=15min
|
||||
EOF
|
||||
cat >"$dir/skills-update.timer" <<EOF
|
||||
[Unit]
|
||||
Description=Daily omo skills refresh
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 05:00:00
|
||||
RandomizedDelaySec=20min
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
systemctl --user daemon-reload && systemctl --user enable --now skills-update.timer
|
||||
}
|
||||
|
||||
if [ "${1:-}" = --install-timer ]; then
|
||||
install_timer
|
||||
exit
|
||||
fi
|
||||
|
||||
# Download one revision of a GitHub repository into its own directory. Nothing in it is executed.
|
||||
fetch() {
|
||||
local dir="$work/${1//\//_}@$2"
|
||||
if [ ! -d "$dir" ]; then
|
||||
mkdir -p "$dir"
|
||||
if ! gh api "repos/$1/tarball/$2" | tar xz -C "$dir" --strip-components=1; then
|
||||
rm -rf "$dir"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
printf '%s\n' "$dir"
|
||||
}
|
||||
|
||||
head_sha() { gh api "repos/$1/commits/$2" --jq .sha; }
|
||||
|
||||
# The revision recorded for a vendored skill in the provenance table of the skills README.
|
||||
readme_rev() { grep -E "^\| \`$1\` \|" "$VENDORED/README.md" | grep -o -E '`[0-9a-f]{40}`' | tr -d '`'; }
|
||||
|
||||
# Each vendored updater returns 0 when it changed the skill, 1 when it is current, and 2 or more
|
||||
# when it failed and the skill must be restored.
|
||||
update_impeccable() {
|
||||
local tag sha src engine dst=$VENDORED/impeccable
|
||||
tag=$(gh api repos/pbakaus/impeccable/releases --jq '[.[] | select(.tag_name | startswith("skill-v"))][0].tag_name') || return 2
|
||||
sha=$(head_sha pbakaus/impeccable "$tag") || return 2
|
||||
if [ "$sha" = "$(readme_rev impeccable)" ]; then
|
||||
log "impeccable: $tag already vendored"
|
||||
return 1
|
||||
fi
|
||||
src=$(fetch pbakaus/impeccable "$sha") || return 2
|
||||
src=$src/.opencode/skills/impeccable
|
||||
[ -f "$src/SKILL.md" ] || { log "impeccable: $tag ships no .opencode skill"; return 2; }
|
||||
rsync -a --delete --exclude LICENSE "$src/" "$dst/"
|
||||
grep -rlF '.opencode/skills/impeccable/scripts' "$dst" |
|
||||
xargs -r sed -i 's#\.opencode/skills/impeccable/scripts#~/.omo/agent/skills/impeccable/scripts#g'
|
||||
if grep -rqF '.opencode/' "$dst"; then
|
||||
log "impeccable: $tag has .opencode paths the rewrite does not cover"
|
||||
return 3
|
||||
fi
|
||||
if ! grep -q '^OmO routing:' "$dst/reference/critique.md"; then
|
||||
python3 - "$dst/reference/critique.md" <<'EOF' || return 3
|
||||
import sys
|
||||
path = sys.argv[1]
|
||||
anchor = "Delegate Assessment A and Assessment B to separate sub-agents."
|
||||
routing = ('OmO routing: when the task tool accepts a category, spawn Assessment A with '
|
||||
'`category: "design-critique-a"` and Assessment B with `category: "design-critique-b"`. '
|
||||
'Never pass `model` with a category.')
|
||||
lines = open(path).read().split("\n")
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith(anchor):
|
||||
lines[i + 1:i + 1] = ["", routing]
|
||||
open(path, "w").write("\n".join(lines))
|
||||
sys.exit(0)
|
||||
sys.exit("impeccable: critique.md no longer has the delegation paragraph the routing line follows")
|
||||
EOF
|
||||
fi
|
||||
engine=$(tr -d '[:space:]' <"$dst/scripts/VERSION")
|
||||
sed -i -E "/^\| \`impeccable\` \|/{s/tag \`skill-v[^\`]*\`, engine [0-9.]+/tag \`$tag\`, engine $engine/; s/\`[0-9a-f]{40}\`/\`$sha\`/}" "$VENDORED/README.md"
|
||||
"$dst/scripts/impeccable" engine-probe >/dev/null 2>&1 || log "impeccable: engine $engine download failed; the launcher retries on first use"
|
||||
msg+=("impeccable: $tag, engine $engine ($sha)")
|
||||
log "impeccable: vendored $tag (engine $engine)"
|
||||
}
|
||||
|
||||
update_web_design_guidelines() {
|
||||
local sha src dst=$VENDORED/web-design-guidelines
|
||||
sha=$(head_sha vercel-labs/agent-skills main) || return 2
|
||||
if [ "$sha" = "$(readme_rev web-design-guidelines)" ]; then
|
||||
log "web-design-guidelines: up to date"
|
||||
return 1
|
||||
fi
|
||||
src=$(fetch vercel-labs/agent-skills "$sha") || return 2
|
||||
src=$src/skills/web-design-guidelines
|
||||
[ -f "$src/SKILL.md" ] || return 2
|
||||
rsync -a --delete "$src/" "$dst/"
|
||||
if ! git -C "$DOTFILES" status --porcelain -- "$dst" | grep -q .; then
|
||||
log "web-design-guidelines: content unchanged at ${sha:0:7}"
|
||||
return 1
|
||||
fi
|
||||
sed -i -E "/^\| \`web-design-guidelines\` \|/s/\`[0-9a-f]{40}\`/\`$sha\`/" "$VENDORED/README.md"
|
||||
msg+=("web-design-guidelines: vercel-labs/agent-skills $sha")
|
||||
log "web-design-guidelines: updated to ${sha:0:7}"
|
||||
}
|
||||
|
||||
update_vendored() {
|
||||
cd "$DOTFILES" || { fail "no dotfiles checkout at $DOTFILES"; return; }
|
||||
[ "$(git branch --show-current)" = main ] || { fail "dotfiles is not on main; vendored skills skipped"; return; }
|
||||
if git status --porcelain -- .config/omo/skills | grep -q .; then
|
||||
fail "uncommitted edits under .config/omo/skills; vendored skills skipped"
|
||||
return
|
||||
fi
|
||||
git fetch -q origin || { fail "git fetch failed; vendored skills skipped"; return; }
|
||||
local ahead behind name rc
|
||||
read -r ahead behind < <(git rev-list --left-right --count main...origin/main)
|
||||
if [ "$behind" -gt 0 ] && ! git merge -q --ff-only origin/main; then
|
||||
fail "dotfiles cannot fast-forward to origin/main; vendored skills skipped"
|
||||
return
|
||||
fi
|
||||
for name in impeccable web-design-guidelines; do
|
||||
cp "$VENDORED/README.md" "$work/README.md.before"
|
||||
"update_${name//-/_}"
|
||||
rc=$?
|
||||
if [ "$rc" -ge 2 ]; then
|
||||
git checkout -q -- ".config/omo/skills/$name"
|
||||
git clean -fdq -- ".config/omo/skills/$name"
|
||||
cp "$work/README.md.before" "$VENDORED/README.md"
|
||||
fail "$name: update failed (exit $rc); kept the vendored copy"
|
||||
fi
|
||||
done
|
||||
[ "${#msg[@]}" -gt 0 ] || return
|
||||
git add -- .config/omo/skills
|
||||
git commit -q -m "chore(omo): update vendored skills" -m "$(printf '%s\n' "${msg[@]}")" -- .config/omo/skills ||
|
||||
{ fail "dotfiles commit failed"; return; }
|
||||
if [ "$ahead" -gt 0 ]; then
|
||||
log "dotfiles: main had $ahead unpushed commits, so $(git rev-parse --short HEAD) stays local"
|
||||
elif git push -q origin main; then
|
||||
log "dotfiles: pushed $(git rev-parse --short HEAD)"
|
||||
else
|
||||
fail "dotfiles push failed; $(git rev-parse --short HEAD) is committed locally"
|
||||
fi
|
||||
}
|
||||
|
||||
update_local() {
|
||||
local name=$1 repo=$2 path=$3 dst=$SKILLS/$1 sha src
|
||||
if [ -L "$dst" ]; then
|
||||
log "$name: links into the dotfiles, skipped"
|
||||
return
|
||||
fi
|
||||
sha=$(head_sha "$repo" main) || { fail "$name: cannot read $repo"; return; }
|
||||
if [ "$sha" = "$(cat "$STATE/$name" 2>/dev/null)" ]; then
|
||||
log "$name: up to date"
|
||||
return
|
||||
fi
|
||||
src=$(fetch "$repo" "$sha") || { fail "$name: download of $repo failed"; return; }
|
||||
src=$src/$path
|
||||
[ -f "$src/SKILL.md" ] || { fail "$name: $repo has no $path/SKILL.md"; return; }
|
||||
mkdir -p "$dst"
|
||||
if diff -rq "$src" "$dst" >/dev/null 2>&1; then
|
||||
log "$name: content unchanged at ${sha:0:7}"
|
||||
else
|
||||
rsync -a --delete "$src/" "$dst/"
|
||||
log "$name: updated to $repo@${sha:0:7}"
|
||||
fi
|
||||
printf '%s\n' "$sha" >"$STATE/$name"
|
||||
}
|
||||
|
||||
mkdir -p "$STATE"
|
||||
update_vendored
|
||||
update_local vercel-react-best-practices vercel-labs/agent-skills skills/react-best-practices
|
||||
for name in docx pdf pptx xlsx skill-creator; do
|
||||
update_local "$name" anthropics/skills "skills/$name"
|
||||
done
|
||||
log "deep-research: no public upstream, not updated"
|
||||
[ "$failures" -eq 0 ]
|
||||
Reference in New Issue
Block a user