Skill: galexy/sageox-distill (version 0.2.1)

--- name: sageox-distill description: "Sync, index, and distill team activity across SageOx-enabled repositories. Keeps your team's knowledge base up to date by syncing repo contexts, indexing GitHub PRs/issues, and running the SageOx distillation pipeline." version: 0.2.1 metadata: { "openclaw": { "emoji": "🔬", "os": ["macos", "linux"], "requires": { "bins": ["ox", "git", "gh", "jq", "claude"] }, "install": [ { "id": "node-claude", "kind": "node", "package": "@anthropic-ai/claude-code", "bins": ["claude"], "label": "Install Claude Code CLI (npm)", }, { "id": "brew-gh", "kind": "brew", "formula": "gh", "bins": ["gh"], "label": "Install GitHub CLI (brew)", }, { "id": "brew-jq", "kind": "brew", "formula": "jq", "bins": ["jq"], "label": "Install jq (brew)", }, ], "homepage": "https://sageox.ai", }, } --- # SageOx Distill You are an agent that keeps a team's SageOx knowledge base current by syncing repo contexts, indexing GitHub activity, and running the distillation pipeline. Pairs with the [`sageox-summary`](https://clawhub.ai/skills/sageox-summary) skill — distill writes the daily source files that summary synthesizes. ## Prerequisites Before doing anything else, verify the user's environment. Run every check in order. If any required check fails, explain precisely what's missing and stop. Do not proceed until the user has fixed it. ### 1. Required binaries `git`, `gh`, `jq`, `claude`, and `ox` are declared in the front matter's `requires.bins`, so OpenClaw checks them before running the skill. `claude` (npm), `gh` (brew), and `jq` (brew) have declarative installs in the front matter; `git` and `ox` do not. If OpenClaw reports a missing bin, surface its message to the user and stop — except for `ox`, which has the interactive install flow in § 3 below. `claude` is required because `ox distill` shells out to it for LLM calls. The skill itself does not invoke `claude` directly — `claude` must simply be installed and authenticated. Use either `claude login` (Pro/Max subscription) or export `ANTHROPIC_API_KEY` in the shell that launches OpenClaw. The skill no longer accepts a per-skill `apiKey`. ### 2. Path validation rules Several steps below ask the user for a repo path or read a path from a JSON state file. Before interpolating any such value into a shell command, the agent **must** validate it against these rules: 1. **Absolute path required.** Must start with `/` or `~`. Reject relative paths and bare names. 2. **No `..` segments.** Reject anything containing `..`. 3. **No shell metacharacters.** Reject anything containing any of these characters: `;` `$` `` ` `` `|` `&` `<` `>` `(` `)` `{` `}` `*` `?` `[` `]` `!` `\` newline. On any validation failure: print a clear error to the user explaining which rule failed and ask them to provide a different path. **Do not attempt to "fix up" or sanitize the input** — reject and re-prompt. Treat values read from `~/.openclaw/memory/*.json` files as untrusted even though this skill writes them: the user (or a process running as the user) may have edited the file by hand or by another tool between runs. Re-validate every read. ### 3. Installing `ox` The `ox` CLI install state is recorded in `~/.openclaw/memory/sageox-ox-install.json`. On every run of this skill, invoke the bundled readiness gate: ```bash bash scripts/update-ox.sh ``` Contract: - **Stdout:** nothing on success - **Stderr:** on any failure, a two-line message — an `error:` line describing what's wrong, followed by a `fix:` line with the remediation. Surface both verbatim to the user. - **Exit:** `0` ox is pinned, installed, and reports the expected version (continue to § 4); `2` ox is not usable — one of: state file missing, binary missing at `$HOME/.local/bin/ox`, `ox` on PATH resolves to a different binary, binary fails to run, or binary reports a version other than the one recorded in `sageox-ox-install.json`. On exit `2`, STOP, read [`references/INSTALL.md`](references/INSTALL.md), follow the install flow, then re-run this script to confirm. There is no per-run auto-update. The curl install pins a specific `ox` release by tag and sha256; users pick up newer releases by re-running `clawhub install` for this skill after a new skill version publishes. The user can say **"reinstall ox"** at any time to re-enter the flow in [`references/INSTALL.md`](references/INSTALL.md). **Do not install `ox` via Homebrew or any package manager** (e.g. `brew install sageox/tap/ox`, `apt`, `dnf`, `pacman`). The tap exists for general use but is not supported inside OpenClaw skills — only the pinned-release curl flow is. ### 4. Authentication and git config After all binaries are present, verify credentials: 1. `ox status` — confirm ox is authenticated. If not, tell the user to run `ox login` and try again. 2. `gh auth status` — confirm GitHub credentials are available. 3. `git config user.name` — confirm git identity is set. 4. Confirm `claude` has credentials. Either `claude login` was run (Pro/Max OAuth, stored under `~/.claude/`) or `ANTHROPIC_API_KEY` is exported in the shell that launched OpenClaw. `ox distill` will fail without one of these. The skill cannot inject the key itself. Do not proceed until all four pass. ## Repo Manifest The list of repos to distill is stored in `~/.openclaw/memory/sageox-distill-repos.json`. The manifest format is: ```json { "repos": [ { "path": "/home/user/repos/my-project", "team_id": "my-team" } ] } ``` - If the manifest exists, read it and confirm the repos with the user before proceeding. - If the manifest does not exist, ask the user which repos to include. For each repo path provided: 1. **Validate the path against the Path validation rules in Prerequisites § 2.** Reject and re-prompt on failure. 2. Verify the directory exists. 3. Verify `.sageox/config.json` exists (confirms `ox init` was run). 4. Read `team_id` from `.sageox/config.json`. **Treat the value as untrusted** — do not interpolate it into shell commands. If you need to use it as an argument, pass it as a separate argv element (not via string concatenation) and refuse values containing shell metacharacters. 5. If `.sageox/config.json` is missing, ask if the user wants to run `ox init` in that repo. - When loading an existing manifest, **re-validate every repo path** in it against the Path validation rules. The manifest file is user-writable and may have been edited externally between runs. - Write the manifest after collecting all repos. - The user can say "add repo", "remove repo", or "show repos" at any time to manage the manifest. ## Distill Pipeline When the user asks to distill, run the following phases in order. ### Phase 1: Sync and Index Group repos by `team_id` from the manifest. For each team: 1. **Sync team context** — run from the first repo in the team group: ```bash ox sync --all-teams ``` This syncs all team contexts via the SageOx daemon. 2. **Index GitHub activity** — run for EACH repo in the team group: ```bash ox index github ``` This indexes PRs, issues, and comments for the specific repo. Both commands are non-fatal. If one fails, log the error and continue with the next repo or team. Do not abort the pipeline. Neither of these commands needs Claude credentials — ox uses its own auth token for SageOx API calls. ### Phase 2: Wait for Daemon Sync After all sync and index commands have been issued, the SageOx daemon processes them asynchronously. Before distilling, verify that processing is complete. For each repo in the manifest: 1. Run `ox daemon status` in the repo directory 2. Check the output for sync/index completion status 3. If the daemon reports it is still processing: - Wait 10 seconds - Check again - Repeat up to 30 times (5 minutes max) 4. If after 30 attempts the daemon is still not finished: - Report which repos are still pending - Ask the user whether to proceed with distill anyway or abort 5. If the daemon reports an error: - Surface the full error message to the user - Ask the user whether to proceed with distill anyway or abort ### Phase 3: Distill For each unique team (grouped by `team_id`), run distill from the first repo in that team's group. `ox distill` shells out to `claude` for LLM calls, so it inherits whatever credentials `claude` has — either an OAuth session from `claude login` or `ANTHROPIC_API_KEY` from the shell that launched OpenClaw (see Prerequisites § 1): ```bash ox distill --sync --layer daily --concurrency 3 --model sonnet --quiet ``` `--quiet` suppresses non-error output, so a successful run prints nothing and a failed run prints only the error. If `ox distill` exits 0, report `<team_id>: ok`. If it exits non-zero, report `<team_id>: failed — <first line of stderr>` and continue with the next team. Do not abort the pipeline for a single team failure. ## Output After all teams have run, print one line per team (`<team_id>: ok` or `<team_id>: failed — <reason>`) and nothing else. No preamble, no counts, no daemon-sync recap. If every team passed, a single `all ok` line is fine. The user can ask for details if they want them.

path: references/INSTALL.md
content: # Installing `ox`

Invoked when `bash scripts/update-ox.sh` exits `2` (no install state
recorded, or `ox` is not on PATH). The deterministic install shell lives
in `scripts/install-ox-curl.sh`; this file covers the user-facing flow
and PATH recovery.

## Install

Invoke the bundled helper. It downloads the `ox` release tarball
directly from GitHub Releases at a tag pinned in the script source,
verifies it against an sha256 checksum embedded in the script, extracts
it into `$HOME/.local/bin`, and records the install state (pinned
release tag, install directory, install timestamp) in
`~/.openclaw/memory/sageox-ox-install.json`. No sudo, no shell-script
piping, no dynamic "latest" resolution.

```bash
bash scripts/install-ox-curl.sh
```

Contract:

- **Args:** none
- **Stdout:** human-readable progress (download URL, checksum
  verification, extract, install dir)
- **Stderr:** errors and PATH-configuration guidance
- **Exit:** `0` success; `3` internal (curl/tar missing, download
  failed, checksum mismatch, unsupported platform, or `ox` not
  runnable after install)

**Do not install `ox` via Homebrew or any package manager** (e.g.
`brew install sageox/tap/ox`, `apt`, `dnf`, `pacman`). The tap exists
for general use but is not supported inside OpenClaw skills — only the
pinned-release curl flow is.

If the script exits non-zero, surface its stderr to the user and stop.
Do not silently retry.

## PATH configuration

`install-ox-curl.sh` installs into `$HOME/.local/bin`. This directory
is not on `PATH` by default on every distro. If the script prints a
warning to stderr that `$HOME/.local/bin` is not on `PATH`, surface its
guidance verbatim and ask the user to add the following line to
`~/.openclaw/.env`:

```sh
PATH=$HOME/.local/bin:$PATH
```

OpenClaw loads `.env` into the skill subprocess, so the updated `PATH`
takes effect on the next invocation. After the user confirms they've
updated the file, re-run the state checker to confirm `ox` is reachable:

```bash
bash scripts/update-ox.sh
```

It should exit `0` with no stderr.

## Upgrading `ox`

The curl flow pins a specific `ox` release by tag and sha256. There is
no per-run auto-update. To pick up a newer release, re-run
`scripts/install-ox-curl.sh` from a version of this skill that pins the
newer release — typically by re-running `clawhub install` for the
skill after a new skill version publishes.


path: scripts/install-ox-curl.sh
content: #!/usr/bin/env bash
# install-ox-curl.sh — download a pinned ox release binary directly from
# GitHub Releases, verify it against an embedded sha256 checksum, and
# install to $HOME/.local/bin. No sudo, no shell-script piping, no
# dynamic "latest" resolution.
#
# Why this shape: scanners flag `curl | bash` of a remote shell script,
# and skills that sudo into system paths. This script avoids both. The
# release tag and per-platform sha256s are pinned in the source below, so
# an attacker cannot substitute a different binary without also editing
# this file (which is reviewed on skill publish).
#
# Bumping: when a newer sageox/ox release ships, update OX_INSTALL_REF
# and the OX_SHA256_* constants. Fetch checksums.txt from
# https://github.com/sageox/ox/releases/download/<tag>/checksums.txt
#
# Usage: install-ox-curl.sh
#
# Stdout: human-readable progress
# Stderr: errors
# Exit:
#   0 — success, ox installed and memory file written
#   3 — internal error (curl/tar missing, download failed, checksum
#       mismatch, unsupported platform, or ox not runnable after install)

set -euo pipefail

OX_INSTALL_REF="v0.6.4"
OX_VERSION="${OX_INSTALL_REF#v}"
OX_REPO="sageox/ox"

# sha256(ox_<version>_<os>_<arch>.tar.gz) — from the checksums.txt asset
# on the pinned release. Lock these when bumping OX_INSTALL_REF.
OX_SHA256_darwin_amd64="a1e256a2ab1d1a3dfec5330ff9737be020a1ef36d133087f6c0b9e03255ddfe3"
OX_SHA256_darwin_arm64="91c36bd0bfec6651078394e3fdf300b78f2babcd8caf70eba436e3b6ea1c2bed"
OX_SHA256_linux_amd64="fe93cd0c093226c51347efe7dd9cd2d5bb055f3523e55a7b887eb2bc8d0312df"
OX_SHA256_linux_arm64="826c9d8eb8387cf029fad6f521a8be86887336e4fe89c97bae4708a99c218897"
OX_SHA256_freebsd_amd64="f1a5cc3c42675e5c1a10b299c0cae4e7407d5f18d722fc2a10009c9365c4ceb0"

command -v curl >/dev/null 2>&1 || { echo "error: curl is required" >&2; exit 3; }
command -v tar  >/dev/null 2>&1 || { echo "error: tar is required"  >&2; exit 3; }

case "$(uname -s)" in
  Darwin)  OS="darwin"  ;;
  Linux)   OS="linux"   ;;
  FreeBSD) OS="freebsd" ;;
  *)
    echo "error: unsupported OS $(uname -s); skill supports macOS, Linux, and FreeBSD" >&2
    exit 3
    ;;
esac

case "$(uname -m)" in
  x86_64|amd64)  ARCH="amd64" ;;
  aarch64|arm64) ARCH="arm64" ;;
  *)
    echo "error: unsupported architecture $(uname -m)" >&2
    exit 3
    ;;
esac

PLATFORM="${OS}_${ARCH}"
SHA_VAR="OX_SHA256_${PLATFORM}"
EXPECTED_SHA="${!SHA_VAR:-}"
if [ -z "$EXPECTED_SHA" ]; then
  echo "error: no pinned checksum for platform ${PLATFORM} in ${OX_INSTALL_REF}" >&2
  exit 3
fi

ARCHIVE_NAME="ox_${OX_VERSION}_${PLATFORM}.tar.gz"
DOWNLOAD_URL="https://github.com/${OX_REPO}/releases/download/${OX_INSTALL_REF}/${ARCHIVE_NAME}"

# Portable mktemp template — works on both GNU (Linux) and BSD (macOS).
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/ox-install.XXXXXXXX")"
trap 'rm -rf "$WORK_DIR" 2>/dev/null' EXIT

echo "Downloading ox ${OX_INSTALL_REF} for ${PLATFORM}"
echo "  from: ${DOWNLOAD_URL}"

# -f fails on HTTP errors, --max-time bounds a stalled connection.
if ! curl -fsSL --max-time 120 "$DOWNLOAD_URL" -o "$WORK_DIR/$ARCHIVE_NAME"; then
  echo "error: failed to download ${DOWNLOAD_URL}" >&2
  exit 3
fi

echo "Verifying sha256 checksum..."
if command -v sha256sum >/dev/null 2>&1; then
  ACTUAL_SHA="$(sha256sum "$WORK_DIR/$ARCHIVE_NAME" | awk '{print $1}')"
elif command -v shasum >/dev/null 2>&1; then
  ACTUAL_SHA="$(shasum -a 256 "$WORK_DIR/$ARCHIVE_NAME" | awk '{print $1}')"
else
  echo "error: neither sha256sum nor shasum is available; refusing to install unverified binary" >&2
  exit 3
fi

if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
  echo "error: sha256 mismatch for ${ARCHIVE_NAME}" >&2
  echo "  expected: $EXPECTED_SHA" >&2
  echo "  actual:   $ACTUAL_SHA" >&2
  exit 3
fi
echo "  ok: $ACTUAL_SHA"

echo "Extracting archive..."
if ! tar -xzf "$WORK_DIR/$ARCHIVE_NAME" -C "$WORK_DIR"; then
  echo "error: failed to extract $ARCHIVE_NAME" >&2
  exit 3
fi

INSTALL_DIR="$HOME/.local/bin"
mkdir -p "$INSTALL_DIR"

# Install ox and every ox-adapter-* binary shipped in the tarball. We
# glob rather than hardcode the adapter list so new adapters added in
# future releases work without re-editing this file.
shopt -s nullglob
installed=0
for src in "$WORK_DIR/ox" "$WORK_DIR"/ox-adapter-*; do
  [ -f "$src" ] || continue
  name="$(basename "$src")"
  dest="$INSTALL_DIR/$name"
  mv "$src" "$dest"
  chmod +x "$dest"

  # macOS ad-hoc re-sign: avoids slow Gatekeeper checks on first run.
  # Non-fatal — a failure here does not block install.
  if [ "$OS" = "darwin" ] && command -v codesign >/dev/null 2>&1; then
    codesign --remove-signature "$dest" 2>/dev/null || true
    codesign --force --sign - "$dest" 2>/dev/null || true
  fi

  installed=$((installed + 1))
done
shopt -u nullglob

if [ "$installed" -eq 0 ]; then
  echo "error: tarball contained no ox binaries" >&2
  exit 3
fi

echo "Installed $installed binaries to $INSTALL_DIR"

# PATH guidance — $HOME/.local/bin isn't on PATH by default on every
# distro. Surface this before the readiness gate so the user sees the
# fix in context.
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
  echo ""
  echo "warning: $INSTALL_DIR is not on your PATH" >&2
  echo "fix: add this line to ~/.openclaw/.env (OpenClaw loads it into the skill subprocess):" >&2
  echo "  PATH=\"$INSTALL_DIR:\$PATH\"" >&2
  echo "then restart the skill." >&2
fi

# Readiness gate: the binary at $INSTALL_DIR/ox must exist, be
# executable, run, and report the pinned release version. The sha256
# check earlier guarantees the extracted bytes are exactly what GitHub
# Releases published for this tag, but those bytes might still fail to
# execute on the host (libc mismatch, unsupported OS version, stripped
# dependency, etc.) — catch that now, before we write state claiming a
# successful install.
#
# Invoke the literal $INSTALL_DIR/ox path rather than using PATH
# lookup. If for any reason the install loop above didn't actually
# write the ox binary (e.g. a future tarball ships only adapters, or
# the upstream release was mis-packaged), a PATH-based check could
# silently fall through to a pre-existing system ox and claim success
# against the wrong binary.
if [ ! -x "$INSTALL_DIR/ox" ]; then
  echo "error: expected ox binary missing at $INSTALL_DIR/ox" >&2
  exit 3
fi

if ! version_output="$("$INSTALL_DIR/ox" version 2>&1)"; then
  echo "error: $INSTALL_DIR/ox failed to run" >&2
  echo "$version_output" >&2
  exit 3
fi

# `ox version` prints "ox <version>\n..." on stdout. Compare the first
# line exactly rather than substring-matching — a loose *"$OX_VERSION"*
# pattern would false-positive when e.g. 0.6.3 is a suffix of 10.6.3.
first_line="$(printf '%s\n' "$version_output" | head -n1)"
if [ "$first_line" != "ox $OX_VERSION" ]; then
  echo "error: $INSTALL_DIR/ox reports '$first_line', expected 'ox $OX_VERSION'" >&2
  exit 3
fi

# Record install state so update-ox.sh can confirm the skill has a
# known-good ox install on subsequent runs. Only factual state — what
# was installed, where, when — no preference fields.
#
# Use `jq -n --arg` rather than a heredoc so JSON-special characters in
# values (e.g. `"` or `\` in a pathological $HOME) get escaped correctly
# instead of producing a malformed state file. `jq` is a hard skill
# dependency (declared in SKILL.md `requires.bins`), so OpenClaw has
# already ensured it's available by the time this script runs.
#
# Write to a temp file in the same directory, then rename into place.
# update-ox.sh reads this file, so an interrupted write that left a
# zero-byte or partial JSON file would break the readiness gate.
# Same-directory rename is atomic at the directory-entry level, which
# is all we need here.
STATE_DIR="$HOME/.openclaw/memory"
STATE_FILE="$STATE_DIR/sageox-ox-install.json"
mkdir -p "$STATE_DIR"
TMP_STATE_FILE="$(mktemp "${STATE_DIR}/sageox-ox-install.json.XXXXXXXX")"
INSTALLED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
jq -n \
  --arg ox_install_ref "$OX_INSTALL_REF" \
  --arg install_dir    "$INSTALL_DIR" \
  --arg installed_at   "$INSTALLED_AT" \
  '{
    ox_install_ref: $ox_install_ref,
    install_dir:    $install_dir,
    installed_at:   $installed_at
  }' > "$TMP_STATE_FILE"
mv "$TMP_STATE_FILE" "$STATE_FILE"

echo "ox ${OX_INSTALL_REF} installed successfully"


path: scripts/update-ox.sh
content: #!/usr/bin/env bash
# update-ox.sh — "is the pinned ox install usable?" readiness gate for
# every invocation of this skill.
#
# Reads ~/.openclaw/memory/sageox-ox-install.json to confirm the install
# state exists, then verifies that `ox` on PATH resolves to the pinned
# install at $HOME/.local/bin/ox. A bare `command -v ox` is insufficient:
# if an older system ox (e.g. /usr/local/bin/ox) appears before
# $HOME/.local/bin on PATH, `command -v` would silently resolve to it
# and defeat the pinned-release contract. The curl install path has no
# per-run update — users re-run install-ox-curl.sh to bump the pinned
# release.
#
# Usage: update-ox.sh
#
# Stdout: nothing on success
# Stderr: one-line "needs install", "not at pinned path", or "PATH
#         order wrong" signal
# Exit:
#   0 — pinned ox is ready, agent should proceed to the next prerequisite
#   2 — ox is not usable (no state file, binary missing at pinned path,
#       or PATH resolves to a different ox); agent must read
#       references/INSTALL.md and run the install flow before continuing

set -euo pipefail

STATE_FILE="$HOME/.openclaw/memory/sageox-ox-install.json"
EXPECTED_OX="$HOME/.local/bin/ox"

if [ ! -f "$STATE_FILE" ]; then
  echo "ox install state not configured — run install flow from references/INSTALL.md" >&2
  exit 2
fi

# The pinned install must actually exist at the expected path. Anything
# else (deleted, wrong permissions, never installed by this skill) means
# the state file is stale and the install flow needs to be re-run.
if [ ! -x "$EXPECTED_OX" ]; then
  echo "error: ox is not installed at $EXPECTED_OX" >&2
  echo "fix: re-run the install flow from references/INSTALL.md" >&2
  exit 2
fi

# `command -v` must resolve to the pinned install — not some other ox
# earlier on PATH. If an older system install shadows the pinned one,
# running the skill against it would defeat the whole point of the
# pinned-release contract, so fail with a PATH-order hint.
resolved_ox="$(command -v ox 2>/dev/null || true)"
if [ "$resolved_ox" != "$EXPECTED_OX" ]; then
  echo "error: 'ox' resolves to '${resolved_ox:-<not on PATH>}', not $EXPECTED_OX" >&2
  echo "fix: prepend \$HOME/.local/bin to PATH in ~/.openclaw/.env so the pinned install wins" >&2
  exit 2
fi

# Verify the binary itself still reports the pinned version. The path
# check above proves PATH order is right; this check proves the bytes
# behind the path haven't been replaced or corrupted since install.
# `jq` is required by the skill (see SKILL.md § 2), so it's guaranteed
# present by the time this script runs.
expected_ref="$(jq -r '.ox_install_ref // empty' "$STATE_FILE" 2>/dev/null || true)"
if [ -z "$expected_ref" ]; then
  echo "error: $STATE_FILE is missing ox_install_ref" >&2
  echo "fix: re-run the install flow from references/INSTALL.md" >&2
  exit 2
fi
expected_version="${expected_ref#v}"

if ! version_output="$("$EXPECTED_OX" version 2>&1)"; then
  echo "error: $EXPECTED_OX failed to run" >&2
  echo "$version_output" >&2
  echo "fix: re-run the install flow from references/INSTALL.md" >&2
  exit 2
fi

first_line="$(printf '%s\n' "$version_output" | head -n1)"
if [ "$first_line" != "ox $expected_version" ]; then
  echo "error: $EXPECTED_OX reports '$first_line', expected 'ox $expected_version'" >&2
  echo "fix: re-run the install flow from references/INSTALL.md" >&2
  exit 2
fi

exit 0


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)