#!/usr/bin/env bash # install.sh — wire CTO profile distribution into Hermes. # Idempotent. Creates ~/.hermes/$PROFILE_NAME symlink + registers skills in profile config. # v0.1 scaffold: schema applied, skill registered, but cto-agent skill is a non-executable stub. set -euo pipefail REPO="$(cd "$(dirname "$0")" && pwd)" PROFILE_NAME="cto-planb" # Hermes profile name (org-scoped); matches distribution.yaml → name + manifest.yaml → profile HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}" PROFILE_CFG="$HERMES_HOME/profiles/$PROFILE_NAME/config.yaml" # --dry-run flag for preflight without mutations DRY_RUN=0 for arg in "$@"; do case "$arg" in --dry-run) DRY_RUN=1 ;; esac done echo "== preflight ==" command -v hermes >/dev/null || { echo "ERROR: hermes CLI not on PATH"; exit 1; } command -v python3 >/dev/null || { echo "ERROR: python3 not on PATH"; exit 1; } command -v sqlite3 >/dev/null || { echo "ERROR: sqlite3 not on PATH"; exit 1; } if [ ! -d "$HERMES_HOME" ]; then echo "ERROR: HERMES_HOME=$HERMES_HOME does not exist" exit 1 fi echo " hermes ✓ python3 ✓ sqlite3 ✓ HERMES_HOME ✓" # Check sandcastle sibling exists (CTO's primary tool) SANDCASTLE_REPO="${SANDCASTLE_REPO:-$REPO/../sandcastle}" if [ ! -d "$SANDCASTLE_REPO" ]; then echo "ERROR: sandcastle sibling not found at $SANDCASTLE_REPO" echo " CTO v1.0 requires it. Clone: git clone https://github.com/mattpocock/sandcastle.git $SANDCASTLE_REPO" exit 1 else echo " sandcastle ✓ ($SANDCASTLE_REPO)" fi if [ "$DRY_RUN" -eq 1 ]; then echo "== DRY RUN — no mutations ==" echo " would: ln -sfn $REPO $HERMES_HOME/$PROFILE_NAME" echo " would: append $REPO/skills to $PROFILE_CFG → skills.external_dirs" echo " would: sqlite3 $HERMES_HOME/$PROFILE_NAME/cto.db < $REPO/schema.sql" echo " would: hermes profile install '$REPO' --yes --force (dispatch-readiness)" echo " would: chmod +x $REPO/lib/cto-worker.sh" exit 0 fi echo "== link repo → ~/.hermes/$PROFILE_NAME ==" ln -sfn "$REPO" "$HERMES_HOME/$PROFILE_NAME" echo "== register skills in $PROFILE_NAME profile config ==" SKILL_DIR="$REPO/skills" mkdir -p "$(dirname "$PROFILE_CFG")" python3 - "$PROFILE_CFG" "$SKILL_DIR" <<'PY' import sys, os, yaml cfg, sk = sys.argv[1], sys.argv[2] d = yaml.safe_load(open(cfg).read()) if os.path.exists(cfg) else {} d = d or {} d.setdefault('skills', {}).setdefault('external_dirs', []) if sk not in d['skills']['external_dirs']: d['skills']['external_dirs'].append(sk) open(cfg, 'w').write(yaml.dump(d, sort_keys=False, allow_unicode=True)) print(" +", sk) else: print(" already registered:", sk) PY echo "== ${PROFILE_NAME}.db ==" sqlite3 "$HERMES_HOME/$PROFILE_NAME/cto.db" < "$REPO/schema.sql" echo "== hermes-native profile install (dispatch-readiness) ==" if [ "$DRY_RUN" -eq 1 ]; then echo "DRY: hermes profile install '$REPO' --yes --force" else hermes profile install "$REPO" --yes --force 2>&1 | tail -5 || \ echo " WARN: hermes profile install failed (legacy symlink still works)" fi echo "== ensure cto-worker.sh executable ==" chmod +x "$REPO/lib/cto-worker.sh" 2>/dev/null && echo " ✓ lib/cto-worker.sh executable" echo "" echo "== done. canonical install: hermes profile install $REPO ==" echo " verify: hermes -p $PROFILE_NAME skills list | grep cto-agent" echo " verify assignee registered: hermes kanban assignees | grep $PROFILE_NAME" echo " start gateway (when ready): hermes profile gateway start $PROFILE_NAME"