knowledge-genome-orchestrator/deploy/vm101/n8n-pi-wrap

71 lines
3 KiB
Bash
Executable file

#!/bin/bash
set -eu
cmd="${SSH_ORIGINAL_COMMAND:-}"
case "$cmd" in
"pi run")
logger -t n8n-pi-wrap "ok: pi run (prompt via stdin)"
prompt=$(cat)
exec /usr/local/bin/pi --no-tools --mode json -p "$prompt" </dev/null
;;
"pi ingest "*)
# Strict positional parse: EXACTLY `pi ingest <genome> <raw_path>` (two tokens).
rest="${cmd#pi ingest }"
genome="${rest%% *}"
raw_path="${rest#* }"
# reject: missing second token, or any extra token (a space left in raw_path)
if [ "$genome" = "$rest" ] || [ -z "$raw_path" ] || [ "$raw_path" != "${raw_path#* }" ]; then
echo '{"status":"error","reason":"usage: pi ingest <genome> <raw_path>"}'; exit 1
fi
# genome slug: kebab lowercase only
case "$genome" in ""|*[!a-z0-9-]*) echo '{"status":"error","reason":"invalid genome name"}'; exit 1;; esac
# raw_path whitelist: MUST live under raw/, no traversal, restricted charset.
# - must start with "raw/" - no ".." segment - no absolute path / leading slash
# - allowed chars: [A-Za-z0-9._/-] (kebab slugs + subdirs like raw/articles/foo.md)
case "$raw_path" in
raw/*) : ;;
*) echo '{"status":"error","reason":"raw_path must be under raw/"}'; exit 1;;
esac
case "$raw_path" in
*..*|*//*) echo '{"status":"error","reason":"raw_path traversal"}'; exit 1;;
esac
case "$raw_path" in
*[!A-Za-z0-9._/-]*) echo '{"status":"error","reason":"raw_path illegal chars"}'; exit 1;;
esac
logger -t n8n-pi-wrap "ok: pi ingest ${genome} ${raw_path}"
set -a; . "${HOME}/.config/knowledge-genome.env"; set +a
cd "${GENOMES_ROOT}/${genome}" || { echo '{"status":"error","reason":"unknown genome"}'; exit 1; }
# The raw file must actually exist under the genome's raw/ dir.
[ -f "$raw_path" ] || { echo '{"status":"error","reason":"raw file not found"}'; exit 1; }
# Clean start on the configured base (develop), pinned to the remote. Destroys only
# vm101's scratch checkout (never a shared branch, never a force-push) — this is by design.
git fetch -q origin \
&& git switch -q "${INGEST_BASE:-main}" 2>/dev/null \
&& git reset -q --hard "origin/${INGEST_BASE:-main}"
# SEMANTIC step: dedicated script drives pi to WRITE wiki pages + manifest.
# (NOT `pi -p "/skill:ingest ..."`, which makes the model reply in chat and write nothing.)
log="$(mktemp -t pi-ingest.XXXXXX.log)"
"${HOME}/.pi/agent/skills/ingest/scripts/ingest-semantic.py" "${genome}" "${raw_path}" \
>"$log" 2>&1 \
|| { echo "{\"status\":\"error\",\"stage\":\"semantic\",\"reason\":\"ingest-semantic failed\",\"log\":\"${log}\"}"; exit 1; }
# MECHANICAL step: validate manifest -> index/log/scoped-lint/commit/PR -> 1 JSON line
exec "${HOME}/.pi/agent/skills/ingest/scripts/run-ingest.sh" "${genome}"
;;
"ollama list")
logger -t n8n-pi-wrap "ok: ollama list"
exec /usr/local/bin/ollama list
;;
"ollama ps")
logger -t n8n-pi-wrap "ok: ollama ps"
exec /usr/local/bin/ollama ps
;;
*)
logger -t n8n-pi-wrap "denied: ${cmd:-<empty>}"
echo "unauthorized command" >&2
exit 1
;;
esac