#!/usr/bin/env bash # cross-agent-watch — desktop-notify on new cross-agent messages. # # See cross-agent-watch.md. Watches every ~/projects/*/inbox/from-agents/ by # default. inotifywait fires create + moved_to events; .tmp.* files are # filtered out. HALT suppresses notifications but the watcher keeps running # and logs each event with "(suppressed by HALT)". set -uo pipefail # Defaults. PROJECTS_GLOB="${HOME}/projects/*/inbox/from-agents/" LOG_FILE="${HOME}/.local/state/cross-agent-comms/watch.log" HALT_FILE="${HOME}/.config/cross-agent-comms/HALT" QUIET=0 NO_NOTIFY=0 # Arg parsing. while [[ $# -gt 0 ]]; do case "$1" in --projects-glob) PROJECTS_GLOB="$2"; shift 2 ;; --log) LOG_FILE="$2"; shift 2 ;; --quiet) QUIET=1; shift ;; --no-notify) NO_NOTIFY=1; shift ;; -h|--help) cat <&2; exit 1 ;; esac done # Resolve glob to a concrete list of directories. # shellcheck disable=SC2086 DIRS=( $PROJECTS_GLOB ) # Filter out non-existent paths (glob may include literal pattern when no match). EXISTING=() for d in "${DIRS[@]}"; do if [[ -d "$d" ]]; then EXISTING+=( "$d" ) fi done if [[ ${#EXISTING[@]} -eq 0 ]]; then echo "cross-agent-watch: glob resolved 0 directories: $PROJECTS_GLOB" >&2 exit 1 fi # Ensure log dir exists. mkdir -p "$(dirname "$LOG_FILE")" [[ $QUIET -eq 0 ]] && echo "cross-agent-watch: watching ${#EXISTING[@]} dir(s); log: $LOG_FILE" # Helper: project name from path like /home/.../projects//inbox/from-agents/... project_name() { local path="$1" # Match ~/projects//... if [[ "$path" =~ ${HOME}/projects/([^/]+)/ ]]; then echo "${BASH_REMATCH[1]}" else basename "$(dirname "$(dirname "$path")")" fi } # Main loop. inotifywait emits one line per event in the format # "" because we passed --format '%w%f'. inotifywait -m -e create,moved_to --format '%w%f' "${EXISTING[@]}" 2>/dev/null \ | while IFS= read -r path; do filename="$(basename "$path")" # Filter .tmp.* staging files. case "$filename" in .tmp.*) continue ;; esac # Filter .asc sidecars — they land first per the atomic-write ordering; # the .org event will fire after. case "$filename" in *.asc) continue ;; esac proj="$(project_name "$path")" iso="$(date -u "+%Y-%m-%dT%H:%M:%SZ")" if [[ -e "$HALT_FILE" ]]; then printf '%s\t%s\t%s\t(suppressed by HALT)\n' "$iso" "$proj" "$filename" >> "$LOG_FILE" [[ $QUIET -eq 0 ]] && echo "[$iso] $proj: $filename (suppressed by HALT)" continue fi printf '%s\t%s\t%s\n' "$iso" "$proj" "$filename" >> "$LOG_FILE" [[ $QUIET -eq 0 ]] && echo "[$iso] $proj: $filename" if [[ $NO_NOTIFY -eq 0 ]]; then notify info "Cross-agent message" "${proj}: ${filename}" --persist 2>/dev/null || true fi done