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:
David Ibia
2026-10-17 20:23:16 +00:00
parent 721d7fbb0a
commit 161d205dfd
3 changed files with 226 additions and 2 deletions
+223
View File
@@ -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 ]
+2 -1
View File
@@ -235,8 +235,9 @@ Custom scripts and binaries are stored in `.local/bin/`:
- `docker-nuke`: Force-restart a wedged Docker Desktop, capturing diagnostics to `~/.local/state/docker-nuke/` first (`--dry-run` to preview, `--no-restart` to leave it down, `--deep` for root helpers)
- `impeccable-browser`: The browser for Impeccable's URL scans (`impeccable detect <url>`) on flowstate-remote, set as `IMPECCABLE_BROWSER` in the server's `~/.zshenv`. Public URLs render on Browserless. Its key, `BROWSERLESS_API_KEY` from the Reoclo project `flowstate-tools`, is cached for 12 hours in `~/.impeccable/browserless.token`. Localhost and private addresses reach Browserless through a cloudflared quick tunnel that lasts only for the scan, and reports still name the local URL. Other launches, and any scan whose tunnel or Browserless connection fails, run on the headless Chrome in the Puppeteer cache with `--no-sandbox`, because the server's AppArmor blocks Chrome's sandbox. `IMPECCABLE_TUNNEL=0` keeps private addresses on local Chrome, which pages that call a second local port (a separate API, say) need. Every decision is logged to `~/.impeccable/browser.log`
- `skills-update`: Refreshes omo's skills from upstream every day on flowstate-remote, from a systemd user timer that `skills-update --install-timer` writes and enables (05:00 UTC). The vendored `impeccable` (newest `skill-v*` release) and `web-design-guidelines` are re-vendored as [the skills README](.config/omo/skills/README.md) describes, then committed and pushed. Only the skill paths are committed, and the push happens only when `main` was level with `origin/main`. The machine-local `vercel-react-best-practices`, `docx`, `pdf`, `pptx`, `xlsx` and `skill-creator` copies in `~/.omo/agent/skills` are synced from `vercel-labs/agent-skills` and `anthropics/skills`. A failed update keeps the previous copy and marks the run failed (`journalctl --user -u skills-update`)
The `links` step of `setup-mac.sh` links each script into `~/.local/bin`, which `.zshrc` puts on your PATH. `pbcopy` and `pbpaste` (OSC 52 clipboard shims for headless servers) and `impeccable-browser` (Linux only) are not linked on macOS.
The `links` step of `setup-mac.sh` links each script into `~/.local/bin`, which `.zshrc` puts on your PATH. `pbcopy` and `pbpaste` (OSC 52 clipboard shims for headless servers), `impeccable-browser` and `skills-update` (Linux only) are not linked on macOS.
### Local Servers
+1 -1
View File
@@ -198,7 +198,7 @@ step_links() {
name=${script##*/}
case $name in
pbcopy | pbpaste) continue ;; # OSC 52 clipboard shims for headless servers
impeccable-browser) continue ;; # Linux-only browser shim for flowstate-remote
impeccable-browser | skills-update) continue ;; # Linux-only tools for flowstate-remote
esac
if [[ -x $script ]]; then link ".local/bin/$name" ".local/bin/$name"; fi
done