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 ]
|
||||||
@@ -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)
|
- `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`
|
- `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
|
### Local Servers
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -198,7 +198,7 @@ step_links() {
|
|||||||
name=${script##*/}
|
name=${script##*/}
|
||||||
case $name in
|
case $name in
|
||||||
pbcopy | pbpaste) continue ;; # OSC 52 clipboard shims for headless servers
|
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
|
esac
|
||||||
if [[ -x $script ]]; then link ".local/bin/$name" ".local/bin/$name"; fi
|
if [[ -x $script ]]; then link ".local/bin/$name" ".local/bin/$name"; fi
|
||||||
done
|
done
|
||||||
|
|||||||
Reference in New Issue
Block a user