mirror of
https://github.com/akitaonrails/ai-memory.git
synced 2026-10-02 03:24:46 +08:00
106 lines
4.1 KiB
Bash
106 lines
4.1 KiB
Bash
# ai-memory hook helper — find marker file + parse minimal TOML.
|
|
# Sourced by per-agent lifecycle hook scripts. POSIX shell only —
|
|
# no bash-isms, no external deps (no jq, no toml crate). Keep changes
|
|
# byte-trivial because every supported agent (claude-code, codex,
|
|
# cursor, gemini-cli, opencode, omp) sources this same file.
|
|
|
|
# Walk up from "$1" toward $HOME (or /) looking for `.ai-memory.toml`.
|
|
# Prints the absolute path of the first marker found, or nothing.
|
|
# Stops at $HOME to avoid leaking declarations from a shared system
|
|
# user's home into another user's session on multi-user boxes.
|
|
ai_memory_find_marker() {
|
|
dir="$1"
|
|
[ -z "$dir" ] && return 0
|
|
while [ -n "$dir" ] && [ "$dir" != "/" ]; do
|
|
if [ -f "$dir/.ai-memory.toml" ]; then
|
|
printf '%s\n' "$dir/.ai-memory.toml"
|
|
return 0
|
|
fi
|
|
if [ -n "${HOME:-}" ] && [ "$dir" = "$HOME" ]; then
|
|
return 0
|
|
fi
|
|
parent=$(dirname "$dir")
|
|
[ "$parent" = "$dir" ] && return 0
|
|
dir="$parent"
|
|
done
|
|
}
|
|
|
|
# Parse `key = "value"` at the TOML root (no nesting, no arrays, no
|
|
# tables). Returns the first match or nothing. Ignores comments and
|
|
# blank lines by construction (the regex only matches the `key = "..."`
|
|
# shape).
|
|
ai_memory_parse_toml_key() {
|
|
file="$1"; key="$2"
|
|
[ -f "$file" ] || return 0
|
|
sed -n -E "s/^[[:space:]]*${key}[[:space:]]*=[[:space:]]*\"([^\"]*)\".*/\1/p" \
|
|
"$file" | head -n 1
|
|
}
|
|
|
|
# Extract `cwd` from a JSON payload on stdin or in $1. Returns the
|
|
# value or nothing. Tolerates leading whitespace and escaped quotes
|
|
# inside other keys by anchoring on the `cwd` key explicitly.
|
|
ai_memory_extract_cwd() {
|
|
payload="${1:-$(cat)}"
|
|
printf '%s' "$payload" \
|
|
| sed -n -E 's/.*"cwd"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' \
|
|
| head -n 1
|
|
}
|
|
|
|
# URL-encode the minimal set of characters that have meaning in a query
|
|
# string. Sufficient for the schema's value regex (`^[a-z0-9][a-z0-9._-]*$`)
|
|
# plus a defensive pass for anything a hand-edited marker might contain.
|
|
ai_memory_url_encode() {
|
|
printf '%s' "$1" \
|
|
| sed 's/%/%25/g; s/&/%26/g; s/=/%3D/g; s/?/%3F/g; s/#/%23/g; s/ /%20/g'
|
|
}
|
|
|
|
# Build a query-string suffix from the marker file walked up from "$1".
|
|
# Returns the suffix (with the leading `&`) or nothing. `cwd` is included
|
|
# whenever a marker exists so `GET /handoff` can resolve workspace-only
|
|
# markers by combining `workspace` with basename(cwd).
|
|
ai_memory_marker_qs() {
|
|
cwd="$1"
|
|
[ -z "$cwd" ] && return 0
|
|
marker=$(ai_memory_find_marker "$cwd")
|
|
[ -z "$marker" ] && return 0
|
|
ws=$(ai_memory_parse_toml_key "$marker" workspace)
|
|
pr=$(ai_memory_parse_toml_key "$marker" project)
|
|
qs="&cwd=$(ai_memory_url_encode "$cwd")"
|
|
[ -n "$ws" ] && qs="${qs}&workspace=$(ai_memory_url_encode "$ws")"
|
|
[ -n "$pr" ] && qs="${qs}&project=$(ai_memory_url_encode "$pr")"
|
|
printf '%s' "$qs"
|
|
}
|
|
|
|
# POST stdin to "$1" as JSON, fire-and-forget. Adds an
|
|
# `Authorization: Bearer` header when `AI_MEMORY_AUTH_TOKEN` is set.
|
|
# The 0.5s timeout matches the project-wide hook latency budget
|
|
# (never block the agent), and the trailing `|| true` makes the
|
|
# function safe to call from `set -e` scripts.
|
|
ai_memory_post_hook() {
|
|
if [ -n "${AI_MEMORY_AUTH_TOKEN:-}" ]; then
|
|
curl -s --max-time 0.5 -X POST "$1" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $AI_MEMORY_AUTH_TOKEN" \
|
|
--data-binary @-
|
|
else
|
|
curl -s --max-time 0.5 -X POST "$1" \
|
|
-H "Content-Type: application/json" \
|
|
--data-binary @-
|
|
fi
|
|
}
|
|
|
|
# GET "$1" with the same auth-header rules as `ai_memory_post_hook`.
|
|
# Used by `session-start.sh` to pull the cross-agent handoff before
|
|
# the resuming agent's first prompt. 1s budget — slightly more
|
|
# generous than POST because the result is *synchronously* fed to
|
|
# stdout (and prepended to the agent's context), so we want to avoid
|
|
# truncating a handoff that was almost ready.
|
|
ai_memory_get_handoff() {
|
|
if [ -n "${AI_MEMORY_AUTH_TOKEN:-}" ]; then
|
|
curl -s --max-time 1.0 "$1" \
|
|
-H "Authorization: Bearer $AI_MEMORY_AUTH_TOKEN"
|
|
else
|
|
curl -s --max-time 1.0 "$1"
|
|
fi
|
|
}
|